Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored reply structure to a RecordsRead #817

Merged
merged 5 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tbd54566975/dwn-sdk-js",
"version": "0.4.7",
"version": "0.5.0",
"description": "A reference implementation of https://identity.foundation/decentralized-web-node/spec/",
"repository": {
"type": "git",
Expand Down
29 changes: 15 additions & 14 deletions src/handlers/records-read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,20 @@ export class RecordsReadHandler implements MethodHandler {

const matchedMessage = existingMessages[0];

// if the matched message is a RecordsDelete, we mark the record as not-found and return both the RecordsDelete and the initial RecordsWrite
// TODO: https://github.com/TBD54566975/dwn-sdk-js/issues/819:
// Consider performing authorization checks like when records exists before returning RecordsDelete and initial RecordsWrite of a deleted record
if (matchedMessage.descriptor.method === DwnMethodName.Delete) {
const recordsDeleteMessage = matchedMessage as RecordsDeleteMessage;
const initialWrite = await RecordsWrite.fetchInitialRecordsWriteMessage(this.messageStore, tenant, recordsDeleteMessage.descriptor.recordId);
return {
status : { code: 404, detail: 'Not Found' },
delete : recordsDeleteMessage,
status : { code: 404, detail: 'Not Found' },
recordsDelete : recordsDeleteMessage,
initialWrite
};
}

// else the matched message is a RecordsWrite
const matchedRecordsWrite = matchedMessage as RecordsQueryReplyEntry;

try {
Expand All @@ -98,27 +102,24 @@ export class RecordsReadHandler implements MethodHandler {
data = result.dataStream;
}

const record = {
...matchedRecordsWrite,
const recordsReadReply: RecordsReadReply = {
status : { code: 200, detail: 'OK' },
recordsWrite : matchedRecordsWrite,
data
};

// attach initial write if returned RecordsWrite is not initial write
if (!await RecordsWrite.isInitialWrite(record)) {
// attach initial write if latest RecordsWrite is not initial write
if (!await RecordsWrite.isInitialWrite(matchedRecordsWrite)) {
const initialWriteQueryResult = await this.messageStore.query(
tenant,
[{ recordId: record.recordId, isLatestBaseState: false, method: DwnMethodName.Write }]
[{ recordId: matchedRecordsWrite.recordId, isLatestBaseState: false, method: DwnMethodName.Write }]
);
const initialWrite = initialWriteQueryResult.messages[0] as RecordsQueryReplyEntry;
delete initialWrite.encodedData; // defensive measure but technically optional because we do this when an update RecordsWrite takes place
record.initialWrite = initialWrite;
delete initialWrite.encodedData; // just defensive because technically should already be deleted when a later RecordsWrite is written
recordsReadReply.initialWrite = initialWrite;
}

const messageReply: RecordsReadReply = {
status: { code: 200, detail: 'OK' },
record
};
return messageReply;
return recordsReadReply;
};

/**
Expand Down
18 changes: 13 additions & 5 deletions src/types/records-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,21 +202,29 @@ export type RecordsReadMessage = {
descriptor: RecordsReadDescriptor;
};

/**
* The reply to a RecordsRead message.
*/
export type RecordsReadReply = GenericMessageReply & {
/**
* The latest RecordsWrite message of the record if record exists (not deleted).
*/
recordsWrite?: RecordsWriteMessage;

/**
* The RecordsDelete if the record is deleted.
*/
delete?: RecordsDeleteMessage;
recordsDelete?: RecordsDeleteMessage;

/**
* The initial write of the record if the returned RecordsWrite message itself is not the initial write or if a RecordsDelete is returned.
*/
initialWrite?: RecordsWriteMessage;

record?: RecordsWriteMessage & {
initialWrite?: RecordsWriteMessage;
data: Readable;
};
/**
* The data stream associated with the record if the records exists (not deleted).
*/
data?: Readable;
};

export type RecordsReadDescriptor = {
Expand Down
2 changes: 1 addition & 1 deletion tests/features/author-delegated-grant.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ export function testAuthorDelegatedGrant(): void {
});
const deviceXRecordsReadReply = await dwn.processMessage(bob.did, recordsReadByDeviceX.message);
expect(deviceXRecordsReadReply.status.code).to.equal(200);
expect(deviceXRecordsReadReply.record?.recordId).to.equal(chatRecord.message.recordId);
expect(deviceXRecordsReadReply.recordsWrite?.recordId).to.equal(chatRecord.message.recordId);

// Verify that Carol cannot query as Alice by invoking the delegated grant granted to Device X
const recordsQueryByCarol = await RecordsQuery.create({
Expand Down
22 changes: 11 additions & 11 deletions tests/features/owner-signature.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,26 +78,26 @@ export function testOwnerSignature(): void {

const readReply = await dwn.processMessage(bob.did, recordsRead.message);
expect(readReply.status.code).to.equal(200);
expect(readReply.record).to.exist;
expect(readReply.record?.descriptor).to.exist;
expect(readReply.recordsWrite).to.exist;
expect(readReply.recordsWrite?.descriptor).to.exist;

// Alice augments Bob's message as an external owner
const { data, ...messageFetched } = readReply.record!; // remove data from message
const ownerSignedMessage = await RecordsWrite.parse(messageFetched);
const { recordsWrite } = readReply; // remove data from message
const ownerSignedMessage = await RecordsWrite.parse(recordsWrite!);
await ownerSignedMessage.signAsOwner(Jws.createSigner(alice));

// Test that Alice can successfully retain/write Bob's message to her DWN
const aliceDataStream = readReply.record!.data;
const aliceDataStream = readReply.data!;
const aliceWriteReply = await dwn.processMessage(alice.did, ownerSignedMessage.message, { dataStream: aliceDataStream });
expect(aliceWriteReply.status.code).to.equal(202);

// Test that Bob's message can be read from Alice's DWN
const readReply2 = await dwn.processMessage(alice.did, recordsRead.message);
expect(readReply2.status.code).to.equal(200);
expect(readReply2.record).to.exist;
expect(readReply2.record?.descriptor).to.exist;
expect(readReply2.recordsWrite).to.exist;
expect(readReply2.recordsWrite?.descriptor).to.exist;

const dataFetched = await DataStream.toBytes(readReply2.record!.data!);
const dataFetched = await DataStream.toBytes(readReply2.data!);
expect(ArrayUtility.byteArraysEqual(dataFetched, dataBytes!)).to.be.true;
});

Expand Down Expand Up @@ -144,10 +144,10 @@ export function testOwnerSignature(): void {
});
const readReply = await dwn.processMessage(alice.did, recordsRead.message);
expect(readReply.status.code).to.equal(200);
expect(readReply.record).to.exist;
expect(readReply.record?.descriptor).to.exist;
expect(readReply.recordsWrite).to.exist;
expect(readReply.recordsWrite?.descriptor).to.exist;

const dataFetched = await DataStream.toBytes(readReply.record!.data!);
const dataFetched = await DataStream.toBytes(readReply.data!);
expect(ArrayUtility.byteArraysEqual(dataFetched, bobRecordsWrite.dataBytes!)).to.be.true;
});

Expand Down
2 changes: 1 addition & 1 deletion tests/features/permissions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ export function testPermissions(): void {
// 9. Verify that any third-party can fetch the revocation status of the permission grant
const revocationReadReply2 = await dwn.processMessage(alice.did, revocationRead.message);
expect(revocationReadReply2.status.code).to.equal(200);
expect(revocationReadReply2.record?.recordId).to.equal(revokeWrite.recordsWrite.message.recordId);
expect(revocationReadReply2.recordsWrite?.recordId).to.equal(revokeWrite.recordsWrite.message.recordId);
});

it('should fail if a RecordsPermissionScope in a Request or Grant record is created without a protocol', async () => {
Expand Down
16 changes: 8 additions & 8 deletions tests/features/protocol-update-action.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ export function testProtocolUpdateAction(): void {
});
const recordsReadReply = await dwn.processMessage(alice.did, recordsRead.message);
expect(recordsReadReply.status.code).to.equal(200);
expect(recordsReadReply.record?.data).to.exist;
const dataFromReply = await DataStream.toBytes(recordsReadReply.record!.data);
expect(recordsReadReply.data).to.exist;
const dataFromReply = await DataStream.toBytes(recordsReadReply.data!);
expect(dataFromReply).to.eql(bobFooNewBytes);

// 5. Carol creates a `foo` by invoking the user role.
Expand Down Expand Up @@ -350,8 +350,8 @@ export function testProtocolUpdateAction(): void {
});
const bobBarReadReply = await dwn.processMessage(alice.did, bobBarRead.message);
expect(bobBarReadReply.status.code).to.equal(200);
expect(bobBarReadReply.record?.data).to.exist;
const dataFromBobBarRead = await DataStream.toBytes(bobBarReadReply.record!.data);
expect(bobBarReadReply.data).to.exist;
const dataFromBobBarRead = await DataStream.toBytes(bobBarReadReply.data!);
expect(dataFromBobBarRead).to.eql(bobBarNewBytes);

// 7. Verify that Bob cannot update Carol's `bar`.
Expand Down Expand Up @@ -423,8 +423,8 @@ export function testProtocolUpdateAction(): void {
});
const bobBazReadReply = await dwn.processMessage(alice.did, bobBazRead.message);
expect(bobBazReadReply.status.code).to.equal(200);
expect(bobBazReadReply.record?.data).to.exist;
const dataFromBobBazRead = await DataStream.toBytes(bobBazReadReply.record!.data);
expect(bobBazReadReply.data).to.exist;
const dataFromBobBazRead = await DataStream.toBytes(bobBazReadReply.data!);
expect(dataFromBobBazRead).to.eql(bobBazNewBytes);

// 11. Verify that Bob cannot update Carol's `baz`
Expand Down Expand Up @@ -534,8 +534,8 @@ export function testProtocolUpdateAction(): void {
});
const bobFooReadReply = await dwn.processMessage(alice.did, bobBarRead.message);
expect(bobFooReadReply.status.code).to.equal(200);
expect(bobFooReadReply.record?.data).to.exist;
const dataFromBobFooRead = await DataStream.toBytes(bobFooReadReply.record!.data);
expect(bobFooReadReply.data).to.exist;
const dataFromBobFooRead = await DataStream.toBytes(bobFooReadReply.data!);
expect(dataFromBobFooRead).to.eql(bobFooNewBytes);

// 5. Verify that Bob cannot update Carol's `foo`.
Expand Down
13 changes: 7 additions & 6 deletions tests/features/records-tags.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2079,8 +2079,9 @@ export function testRecordsTags(): void {

const tagsRecord1ReadReply = await dwn.processMessage(alice.did, tagsRecord1Read.message);
expect(tagsRecord1ReadReply.status.code).to.equal(200);
expect(tagsRecord1ReadReply.record).to.not.be.undefined;
expect(tagsRecord1ReadReply.record!.descriptor.tags).to.deep.equal({ stringTag, numberTag, booleanTag, stringArrayTag, numberArrayTag });
expect(tagsRecord1ReadReply.recordsWrite).to.not.be.undefined;
expect(tagsRecord1ReadReply.recordsWrite!.descriptor.tags)
.to.deep.equal({ stringTag, numberTag, booleanTag, stringArrayTag, numberArrayTag });
});

it('should overwrite tags when updating a Record', async () => {
Expand Down Expand Up @@ -2113,8 +2114,8 @@ export function testRecordsTags(): void {

const tagsRecord1ReadReply = await dwn.processMessage(alice.did, tagsRecord1Read.message);
expect(tagsRecord1ReadReply.status.code).to.equal(200);
expect(tagsRecord1ReadReply.record).to.not.be.undefined;
expect(tagsRecord1ReadReply.record!.descriptor.tags).to.deep.equal({
expect(tagsRecord1ReadReply.recordsWrite).to.not.be.undefined;
expect(tagsRecord1ReadReply.recordsWrite!.descriptor.tags).to.deep.equal({
stringTag : 'string-value',
numberTag : 54566975,
booleanTag : false,
Expand Down Expand Up @@ -2148,8 +2149,8 @@ export function testRecordsTags(): void {

const updatedRecordReadReply = await dwn.processMessage(alice.did, tagsRecord1Read.message);
expect(updatedRecordReadReply.status.code).to.equal(200);
expect(updatedRecordReadReply.record).to.not.be.undefined;
expect(updatedRecordReadReply.record!.descriptor.tags).to.deep.equal({ newTag: 'new-value' });
expect(updatedRecordReadReply.recordsWrite).to.exist;
expect(updatedRecordReadReply.recordsWrite!.descriptor.tags).to.deep.equal({ newTag: 'new-value' });

// Sanity: Query for the old tag value should return no results
const tagsQueryMatchReply2 = await dwn.processMessage(alice.did, tagsQueryMatch.message);
Expand Down
4 changes: 2 additions & 2 deletions tests/features/resumable-tasks.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export function testResumableTasks(): void {

const readReply = await dwn.processMessage(alice.did, recordsRead.message);
expect(readReply.status.code).to.equal(200);
expect(readReply.record).to.exist;
expect(readReply.recordsWrite).to.exist;

// 3. Restart the DWN to trigger the resumable task to be resumed.
await dwn.close();
Expand All @@ -147,7 +147,7 @@ export function testResumableTasks(): void {
// 4. Verify that the record is deleted.
const readReply2 = await dwn.processMessage(alice.did, recordsRead.message);
expect(readReply2.status.code).to.equal(404);
expect(readReply2.record).to.be.undefined;
expect(readReply2.recordsWrite).to.be.undefined;
});

it('should only resume tasks that are timed-out up to the batch size when DWN starts', async () => {
Expand Down
4 changes: 2 additions & 2 deletions tests/handlers/records-delete.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export function testRecordsDeleteHandler(): void {
const aliceRead1Reply = await dwn.processMessage(alice.did, aliceRead1.message);
expect(aliceRead1Reply.status.code).to.equal(200);

const aliceDataFetched = await DataStream.toBytes(aliceRead1Reply.record!.data!);
const aliceDataFetched = await DataStream.toBytes(aliceRead1Reply.data!);
expect(ArrayUtility.byteArraysEqual(aliceDataFetched, data)).to.be.true;

// alice deletes the other record
Expand All @@ -194,7 +194,7 @@ export function testRecordsDeleteHandler(): void {
const bobRead1Reply = await dwn.processMessage(bob.did, bobRead1.message);
expect(bobRead1Reply.status.code).to.equal(200);

const bobDataFetched = await DataStream.toBytes(bobRead1Reply.record!.data!);
const bobDataFetched = await DataStream.toBytes(bobRead1Reply.data!);
expect(ArrayUtility.byteArraysEqual(bobDataFetched, data)).to.be.true;
});

Expand Down
Loading