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

Corrected UnionMessageReply type for downstream consumption #821

Merged
merged 2 commits into from
Oct 8, 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.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