Skip to content

Commit

Permalink
Further restructuring of RecordsReadReply & corrected UnionMessageRep…
Browse files Browse the repository at this point in the history
…ly type for downstream consumption (#821)
  • Loading branch information
thehenrytsai authored Oct 8, 2024
1 parent 475270d commit e983cd8
Show file tree
Hide file tree
Showing 18 changed files with 126 additions and 125 deletions.
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.5.0",
"version": "0.5.1",
"description": "A reference implementation of https://identity.foundation/decentralized-web-node/spec/",
"repository": {
"type": "git",
Expand Down
27 changes: 7 additions & 20 deletions src/core/message-reply.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import type { MessagesReadReplyEntry } from '../types/messages-types.js';
import type { PaginationCursor } from '../types/query-types.js';
import type { ProtocolsConfigureMessage } from '../types/protocols-types.js';
import type { Readable } from 'readable-stream';
import type { RecordsWriteMessage } from '../types/records-types.js';
import type { RecordsReadReplyEntry } from '../types/records-types.js';
import type { GenericMessageReply, MessageSubscription, QueryResultEntry } from '../types/message-types.js';

export function messageReplyFromError(e: unknown, code: number): GenericMessageReply {
Expand All @@ -16,31 +15,19 @@ export function messageReplyFromError(e: unknown, code: number): GenericMessageR
* Catch-all message reply type. It is recommended to use GenericMessageReply or a message-specific reply type wherever possible.
*/
export type UnionMessageReply = GenericMessageReply & {
/**
* A container for the data returned from a `RecordsRead` or `MessagesRead`.
* Mutually exclusive with (`entries` + `cursor`) and `subscription`.
*/
entry?: MessagesReadReplyEntry & RecordsReadReplyEntry;

/**
* Resulting message entries or events returned from the invocation of the corresponding message.
* e.g. the resulting messages from a RecordsQuery, or array of messageCid strings for MessagesQuery
* Mutually exclusive with `record`.
*/
entries?: QueryResultEntry[] | ProtocolsConfigureMessage[] | string[];

/**
* A single message entry if applicable (e.g. MessagesRead).
* Mutually exclusive with `record`, `entries` and `cursor`.
*/
entry?: MessagesReadReplyEntry;

/**
* Record corresponding to the message received if applicable (e.g. RecordsRead).
* Mutually exclusive with `entries` and `cursor`.
*/
record?: RecordsWriteMessage & {
/**
* The initial write of the record if the returned RecordsWrite message itself is not the initial write.
*/
initialWrite?: RecordsWriteMessage;
data: Readable;
};

/**
* A cursor for pagination if applicable (e.g. RecordsQuery).
* Mutually exclusive with `record`.
Expand Down
18 changes: 11 additions & 7 deletions src/handlers/records-read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,11 @@ export class RecordsReadHandler implements MethodHandler {
const recordsDeleteMessage = matchedMessage as RecordsDeleteMessage;
const initialWrite = await RecordsWrite.fetchInitialRecordsWriteMessage(this.messageStore, tenant, recordsDeleteMessage.descriptor.recordId);
return {
status : { code: 404, detail: 'Not Found' },
recordsDelete : recordsDeleteMessage,
initialWrite
status : { code: 404, detail: 'Not Found' },
entry : {
recordsDelete: recordsDeleteMessage,
initialWrite
}
};
}

Expand Down Expand Up @@ -103,9 +105,11 @@ export class RecordsReadHandler implements MethodHandler {
}

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

// attach initial write if latest RecordsWrite is not initial write
Expand All @@ -116,7 +120,7 @@ export class RecordsReadHandler implements MethodHandler {
);
const initialWrite = initialWriteQueryResult.messages[0] as RecordsQueryReplyEntry;
delete initialWrite.encodedData; // just defensive because technically should already be deleted when a later RecordsWrite is written
recordsReadReply.initialWrite = initialWrite;
recordsReadReply.entry!.initialWrite = initialWrite;
}

return recordsReadReply;
Expand Down
3 changes: 1 addition & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ export type { GenericMessage, GenericMessageReply, MessageSort, MessageSubscript
export type { MessagesFilter, MessagesReadMessage as MessagesReadMessage, MessagesReadReply as MessagesReadReply, MessagesReadReplyEntry as MessagesReadReplyEntry, MessagesQueryMessage, MessagesQueryReply, MessagesSubscribeDescriptor, MessagesSubscribeMessage, MessagesSubscribeReply, MessageSubscriptionHandler } from './types/messages-types.js';
export type { Filter, EqualFilter, OneOfFilter, RangeFilter, RangeCriterion, PaginationCursor, QueryOptions } from './types/query-types.js';
export type { ProtocolsConfigureDescriptor, ProtocolDefinition, ProtocolTypes, ProtocolRuleSet, ProtocolsQueryFilter, ProtocolsConfigureMessage, ProtocolsQueryMessage, ProtocolsQueryReply } from './types/protocols-types.js';
export type { DataEncodedRecordsWriteMessage, EncryptionProperty, RecordsDeleteMessage, RecordsQueryMessage, RecordsQueryReply, RecordsQueryReplyEntry, RecordsReadMessage, RecordsReadReply, RecordsSubscribeDescriptor, RecordsSubscribeMessage, RecordsSubscribeReply, RecordSubscriptionHandler, RecordsWriteDescriptor, RecordsWriteTags, RecordsWriteTagValue, RecordsWriteMessage } from './types/records-types.js';
export { authenticate } from './core/auth.js';
export { ActiveTenantCheckResult, AllowAllTenantGate, TenantGate } from './core/tenant-gate.js';
export { Cid } from './utils/cid.js';
export { RecordsQuery, RecordsQueryOptions } from './interfaces/records-query.js';
export { DataStore, DataStorePutResult, DataStoreGetResult } from './types/data-store.js';
export { ResumableTaskStore, ManagedResumableTask } from './types/resumable-task-store.js';
export { DataStream } from './utils/data-stream.js';
export { DateSort } from './types/records-types.js';
export { DerivedPrivateJwk, HdKey, KeyDerivationScheme } from './utils/hd-key.js';
export { Dwn } from './dwn.js';
export { DwnConstant } from './core/dwn-constant.js';
Expand Down Expand Up @@ -49,6 +47,7 @@ export { Signer } from './types/signer.js';
export { SortDirection } from './types/query-types.js';
export { Time } from './utils/time.js';
export * from './types/permission-types.js';
export * from './types/records-types.js';

// concrete implementations of stores and event stream
export { DataStoreLevel } from './store/data-store-level.js';
Expand Down
11 changes: 11 additions & 0 deletions src/types/records-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,17 @@ export type RecordsReadMessage = {
* The reply to a RecordsRead message.
*/
export type RecordsReadReply = GenericMessageReply & {
/**
* A container for the data returned from a `RecordsRead`.
* `undefined` if no data needs to be returned.
*/
entry?: RecordsReadReplyEntry;
};

/**
* The structure of the `entry` container property in `RecordsReadReplyEntry`.
*/
export type RecordsReadReplyEntry = {
/**
* The latest RecordsWrite message of the record if record exists (not deleted).
*/
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.recordsWrite?.recordId).to.equal(chatRecord.message.recordId);
expect(deviceXRecordsReadReply.entry!.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.recordsWrite).to.exist;
expect(readReply.recordsWrite?.descriptor).to.exist;
expect(readReply.entry!.recordsWrite).to.exist;
expect(readReply.entry!.recordsWrite?.descriptor).to.exist;

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

// Test that Alice can successfully retain/write Bob's message to her DWN
const aliceDataStream = readReply.data!;
const aliceDataStream = readReply.entry!.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.recordsWrite).to.exist;
expect(readReply2.recordsWrite?.descriptor).to.exist;
expect(readReply2.entry!.recordsWrite).to.exist;
expect(readReply2.entry!.recordsWrite?.descriptor).to.exist;

const dataFetched = await DataStream.toBytes(readReply2.data!);
const dataFetched = await DataStream.toBytes(readReply2.entry!.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.recordsWrite).to.exist;
expect(readReply.recordsWrite?.descriptor).to.exist;
expect(readReply.entry!.recordsWrite).to.exist;
expect(readReply.entry!.recordsWrite?.descriptor).to.exist;

const dataFetched = await DataStream.toBytes(readReply.data!);
const dataFetched = await DataStream.toBytes(readReply.entry!.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.recordsWrite?.recordId).to.equal(revokeWrite.recordsWrite.message.recordId);
expect(revocationReadReply2.entry!.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.data).to.exist;
const dataFromReply = await DataStream.toBytes(recordsReadReply.data!);
expect(recordsReadReply.entry!.data).to.exist;
const dataFromReply = await DataStream.toBytes(recordsReadReply.entry!.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.data).to.exist;
const dataFromBobBarRead = await DataStream.toBytes(bobBarReadReply.data!);
expect(bobBarReadReply.entry!.data).to.exist;
const dataFromBobBarRead = await DataStream.toBytes(bobBarReadReply.entry!.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.data).to.exist;
const dataFromBobBazRead = await DataStream.toBytes(bobBazReadReply.data!);
expect(bobBazReadReply.entry!.data).to.exist;
const dataFromBobBazRead = await DataStream.toBytes(bobBazReadReply.entry!.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.data).to.exist;
const dataFromBobFooRead = await DataStream.toBytes(bobFooReadReply.data!);
expect(bobFooReadReply.entry!.data).to.exist;
const dataFromBobFooRead = await DataStream.toBytes(bobFooReadReply.entry!.data!);
expect(dataFromBobFooRead).to.eql(bobFooNewBytes);

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

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

Expand Down Expand Up @@ -2114,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.recordsWrite).to.not.be.undefined;
expect(tagsRecord1ReadReply.recordsWrite!.descriptor.tags).to.deep.equal({
expect(tagsRecord1ReadReply.entry!.recordsWrite).to.not.be.undefined;
expect(tagsRecord1ReadReply.entry!.recordsWrite!.descriptor.tags).to.deep.equal({
stringTag : 'string-value',
numberTag : 54566975,
booleanTag : false,
Expand Down Expand Up @@ -2149,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.recordsWrite).to.exist;
expect(updatedRecordReadReply.recordsWrite!.descriptor.tags).to.deep.equal({ newTag: 'new-value' });
expect(updatedRecordReadReply.entry!.recordsWrite).to.exist;
expect(updatedRecordReadReply.entry!.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.recordsWrite).to.exist;
expect(readReply.entry!.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.recordsWrite).to.be.undefined;
expect(readReply2.entry!.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.data!);
const aliceDataFetched = await DataStream.toBytes(aliceRead1Reply.entry!.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.data!);
const bobDataFetched = await DataStream.toBytes(bobRead1Reply.entry!.data!);
expect(ArrayUtility.byteArraysEqual(bobDataFetched, data)).to.be.true;
});

Expand Down
Loading

0 comments on commit e983cd8

Please sign in to comment.