@enbox/api 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +140 -159
  2. package/dist/browser.mjs +23 -13
  3. package/dist/browser.mjs.map +4 -4
  4. package/dist/esm/advanced.js +11 -0
  5. package/dist/esm/advanced.js.map +1 -0
  6. package/dist/esm/define-protocol.js +3 -3
  7. package/dist/esm/dwn-api.js +84 -130
  8. package/dist/esm/dwn-api.js.map +1 -1
  9. package/dist/esm/dwn-reader-api.js +128 -0
  10. package/dist/esm/dwn-reader-api.js.map +1 -0
  11. package/dist/esm/index.js +4 -2
  12. package/dist/esm/index.js.map +1 -1
  13. package/dist/esm/live-query.js +154 -0
  14. package/dist/esm/live-query.js.map +1 -0
  15. package/dist/esm/read-only-record.js +255 -0
  16. package/dist/esm/read-only-record.js.map +1 -0
  17. package/dist/esm/record.js +46 -57
  18. package/dist/esm/record.js.map +1 -1
  19. package/dist/esm/typed-web5.js +242 -0
  20. package/dist/esm/typed-web5.js.map +1 -0
  21. package/dist/esm/web5.js +71 -3
  22. package/dist/esm/web5.js.map +1 -1
  23. package/dist/types/advanced.d.ts +12 -0
  24. package/dist/types/advanced.d.ts.map +1 -0
  25. package/dist/types/define-protocol.d.ts +3 -3
  26. package/dist/types/dwn-api.d.ts +20 -104
  27. package/dist/types/dwn-api.d.ts.map +1 -1
  28. package/dist/types/dwn-reader-api.d.ts +138 -0
  29. package/dist/types/dwn-reader-api.d.ts.map +1 -0
  30. package/dist/types/index.d.ts +4 -2
  31. package/dist/types/index.d.ts.map +1 -1
  32. package/dist/types/live-query.d.ts +148 -0
  33. package/dist/types/live-query.d.ts.map +1 -0
  34. package/dist/types/protocol-types.d.ts +2 -2
  35. package/dist/types/read-only-record.d.ts +133 -0
  36. package/dist/types/read-only-record.d.ts.map +1 -0
  37. package/dist/types/record.d.ts +37 -17
  38. package/dist/types/record.d.ts.map +1 -1
  39. package/dist/types/{typed-dwn-api.d.ts → typed-web5.d.ts} +75 -76
  40. package/dist/types/typed-web5.d.ts.map +1 -0
  41. package/dist/types/web5.d.ts +79 -3
  42. package/dist/types/web5.d.ts.map +1 -1
  43. package/package.json +10 -6
  44. package/src/advanced.ts +29 -0
  45. package/src/define-protocol.ts +3 -3
  46. package/src/dwn-api.ts +141 -266
  47. package/src/dwn-reader-api.ts +255 -0
  48. package/src/index.ts +4 -2
  49. package/src/live-query.ts +261 -0
  50. package/src/protocol-types.ts +2 -2
  51. package/src/read-only-record.ts +328 -0
  52. package/src/record.ts +116 -86
  53. package/src/typed-web5.ts +445 -0
  54. package/src/web5.ts +104 -5
  55. package/dist/esm/subscription-util.js +0 -35
  56. package/dist/esm/subscription-util.js.map +0 -1
  57. package/dist/esm/typed-dwn-api.js +0 -181
  58. package/dist/esm/typed-dwn-api.js.map +0 -1
  59. package/dist/types/subscription-util.d.ts +0 -19
  60. package/dist/types/subscription-util.d.ts.map +0 -1
  61. package/dist/types/typed-dwn-api.d.ts.map +0 -1
  62. package/src/subscription-util.ts +0 -44
  63. package/src/typed-dwn-api.ts +0 -370
package/src/dwn-api.ts CHANGED
@@ -8,9 +8,7 @@ import type {
8
8
  CreateGrantParams,
9
9
  CreateRequestParams,
10
10
  DwnMessage,
11
- DwnMessageParams
12
- ,
13
- DwnMessageSubscription,
11
+ DwnMessageParams,
14
12
  DwnPaginationCursor,
15
13
  DwnResponse,
16
14
  DwnResponseStatus,
@@ -23,20 +21,14 @@ import {
23
21
  AgentPermissionsApi,
24
22
  } from '@enbox/agent';
25
23
 
26
- import { isEmptyObject } from '@enbox/common';
27
24
  import { DwnInterface, getRecordAuthor } from '@enbox/agent';
28
25
 
29
- import type { ProtocolDefinition } from '@enbox/dwn-sdk-js';
30
-
31
- import type { SchemaMap, TypedProtocol } from './protocol-types.js';
32
-
33
26
  import { dataToBlob } from './utils.js';
27
+ import { LiveQuery } from './live-query.js';
34
28
  import { PermissionGrant } from './permission-grant.js';
35
29
  import { PermissionRequest } from './permission-request.js';
36
30
  import { Protocol } from './protocol.js';
37
31
  import { Record } from './record.js';
38
- import { SubscriptionUtil } from './subscription-util.js';
39
- import { TypedDwnApi } from './typed-dwn-api.js';
40
32
 
41
33
  /**
42
34
  * Represents the request payload for fetching permission requests from a Decentralized Web Node (DWN).
@@ -66,9 +58,7 @@ export type FetchGrantsRequest = Omit<FetchPermissionsParams, 'author' | 'target
66
58
  *
67
59
  * This request type is used to specify the configuration options for the protocol.
68
60
  */
69
- export type ProtocolsConfigureRequest = {
70
- /** Configuration options for the protocol. */
71
- message: Omit<DwnMessageParams[DwnInterface.ProtocolsConfigure], 'signer'>;
61
+ export type ProtocolsConfigureRequest = Omit<DwnMessageParams[DwnInterface.ProtocolsConfigure], 'signer'> & {
72
62
  /** When true, derives and injects $encryption public keys into the protocol definition. */
73
63
  encryption?: boolean;
74
64
  };
@@ -94,12 +84,9 @@ export type ProtocolsConfigureResponse = DwnResponseStatus & {
94
84
  * target the local DWN. If the `from` property is provided, the query will target the specified
95
85
  * remote DWN.
96
86
  */
97
- export type ProtocolsQueryRequest = {
87
+ export type ProtocolsQueryRequest = Omit<DwnMessageParams[DwnInterface.ProtocolsQuery], 'signer'> & {
98
88
  /** Optional DID specifying the remote target DWN tenant to be queried. */
99
89
  from?: string;
100
-
101
- /** Query filters and options that influence the results returned. */
102
- message: Omit<DwnMessageParams[DwnInterface.ProtocolsQuery], 'signer'>
103
90
  };
104
91
 
105
92
  /**
@@ -111,40 +98,6 @@ export type ProtocolsQueryResponse = DwnResponseStatus & {
111
98
  protocols: Protocol[];
112
99
  };
113
100
 
114
- /**
115
- * Type alias for {@link RecordsWriteRequest}
116
- */
117
- export type RecordsCreateRequest = RecordsWriteRequest;
118
-
119
- /**
120
- * Type alias for {@link RecordsWriteResponse}
121
- */
122
- export type RecordsCreateResponse = RecordsWriteResponse;
123
-
124
- /**
125
- * Represents a request to create a new record based on an existing one.
126
- *
127
- * This request type allows specifying the new data for the record, along with any additional
128
- * message parameters required for the write operation.
129
- */
130
- export type RecordsCreateFromRequest = {
131
- /** The DID of the entity authoring the record. */
132
- author: string;
133
- /** The new data for the record. */
134
- data: unknown;
135
- /** Optional additional parameters for the record write operation */
136
- message?: Omit<DwnMessageParams[DwnInterface.RecordsWrite], 'signer'>;
137
- /** The existing record instance that is being used as a basis for the new record. */
138
- record: Record;
139
- /**
140
- * Controls whether the new record should be auto-encrypted.
141
- *
142
- * If omitted, auto-detected from the source record: if the source record was
143
- * encrypted, the new record is automatically encrypted with a fresh DEK.
144
- */
145
- encryption?: boolean;
146
- };
147
-
148
101
  /**
149
102
  * Defines a request to delete a record from the Decentralized Web Node (DWN).
150
103
  *
@@ -152,15 +105,12 @@ export type RecordsCreateFromRequest = {
152
105
  * message parameters for the delete operation. If the `from` property is not provided, the record
153
106
  * will be deleted from the local DWN.
154
107
  */
155
- export type RecordsDeleteRequest = {
108
+ export type RecordsDeleteRequest = Omit<DwnMessageParams[DwnInterface.RecordsDelete], 'signer'> & {
156
109
  /** Optional DID specifying the remote target DWN tenant the record will be deleted from. */
157
110
  from?: string;
158
111
 
159
- /** Records must be scoped to a specific protocol */
112
+ /** Protocol URI for permission grant resolution. */
160
113
  protocol?: string;
161
-
162
- /** The parameters for the delete operation. */
163
- message: Omit<DwnMessageParams[DwnInterface.RecordsDelete], 'signer'>;
164
114
  };
165
115
 
166
116
  /**
@@ -170,16 +120,10 @@ export type RecordsDeleteRequest = {
170
120
  * parameters, and optionally the target DWN to query from. If the `from` property is not provided,
171
121
  * the query will target the local DWN.
172
122
  */
173
- export type RecordsQueryRequest = {
123
+ export type RecordsQueryRequest = Omit<DwnMessageParams[DwnInterface.RecordsQuery], 'signer'> & {
174
124
  /** Optional DID specifying the remote target DWN tenant to query from and return results. */
175
125
  from?: string;
176
126
 
177
- /** Records must be scoped to a specific protocol */
178
- protocol?: string;
179
-
180
- /** The parameters for the query operation, detailing the criteria for selecting records. */
181
- message: Omit<DwnMessageParams[DwnInterface.RecordsQuery], 'signer'>;
182
-
183
127
  /** When true, automatically decrypts encrypted records in the query results. */
184
128
  encryption?: boolean;
185
129
  };
@@ -190,7 +134,7 @@ export type RecordsQueryRequest = {
190
134
  */
191
135
  export type RecordsQueryResponse = DwnResponseStatus & {
192
136
  /** Array of records matching the query. */
193
- records?: Record[]
137
+ records: Record[];
194
138
 
195
139
  /** If there are additional results, the messageCid of the last record will be returned as a pagination cursor. */
196
140
  cursor?: DwnPaginationCursor;
@@ -203,16 +147,13 @@ export type RecordsQueryResponse = DwnResponseStatus & {
203
147
  * additional parameters for the read operation. It's useful for fetching the details of a single
204
148
  * record by its identifier or other criteria.
205
149
  */
206
- export type RecordsReadRequest = {
150
+ export type RecordsReadRequest = Omit<DwnMessageParams[DwnInterface.RecordsRead], 'signer'> & {
207
151
  /** Optional DID specifying the remote target DWN tenant the record will be read from. */
208
152
  from?: string;
209
153
 
210
- /** Records must be scoped to a specific protocol */
154
+ /** Protocol URI for permission grant resolution. */
211
155
  protocol?: string;
212
156
 
213
- /** The parameters for the read operation, detailing the criteria for selecting the record. */
214
- message: Omit<DwnMessageParams[DwnInterface.RecordsRead], 'signer'>;
215
-
216
157
  /** When true, automatically decrypts the encrypted record data. */
217
158
  encryption?: boolean;
218
159
  };
@@ -226,39 +167,22 @@ export type RecordsReadResponse = DwnResponseStatus & {
226
167
  record: Record;
227
168
  };
228
169
 
229
- /** Subscription handler for Records */
230
- export type RecordsSubscriptionHandler = (record: Record) => void;
231
-
232
170
  /**
233
171
  * Represents a request to subscribe to records from a Decentralized Web Node (DWN).
234
172
  *
235
- * This request type is used to specify the target DWN from which records matching the subscription
236
- * criteria should be emitted. It's useful for being notified in real time when records are written, deleted or modified.
173
+ * Returns a {@link LiveQuery} that atomically provides an initial snapshot of
174
+ * matching records alongside a real-time stream of deduplicated, semantically-
175
+ * typed change events (`create`, `update`, `delete`).
237
176
  */
238
- export type RecordsSubscribeRequest = {
177
+ export type RecordsSubscribeRequest = Omit<DwnMessageParams[DwnInterface.RecordsSubscribe], 'signer'> & {
239
178
  /** Optional DID specifying the remote target DWN tenant to subscribe from. */
240
179
  from?: string;
241
-
242
- /** Records must be scoped to a specific protocol */
243
- protocol?: string;
244
-
245
- /** The parameters for the subscription operation, detailing the criteria for the subscription filter */
246
- message: Omit<DwnMessageParams[DwnInterface.RecordsSubscribe], 'signer'>;
247
-
248
- /** The handler to process the subscription events */
249
- subscriptionHandler: RecordsSubscriptionHandler;
250
-
251
- /** When true, indicates encryption is active (decryption happens on subsequent reads). */
252
- encryption?: boolean;
253
180
  };
254
181
 
255
- /** Encapsulates the response from a DWN RecordsSubscriptionRequest */
182
+ /** Encapsulates the response from a DWN RecordsSubscribeRequest. */
256
183
  export type RecordsSubscribeResponse = DwnResponseStatus & {
257
- /**
258
- * Represents the subscription that was created. Includes an ID and the close method to stop the subscription.
259
- *
260
- * */
261
- subscription?: DwnMessageSubscription;
184
+ /** The live query instance, or `undefined` if the request failed. */
185
+ liveQuery?: LiveQuery;
262
186
  };
263
187
 
264
188
  /**
@@ -267,18 +191,11 @@ export type RecordsSubscribeResponse = DwnResponseStatus & {
267
191
  * This request type allows specifying the data for the new or updated record, along with any
268
192
  * additional message parameters required for the write operation, and an optional flag to indicate
269
193
  * whether the record should be immediately stored.
270
- *
271
- * @param data -
272
- * @param message - , excluding the signer.
273
- * @param store -
274
194
  */
275
- export type RecordsWriteRequest = {
195
+ export type RecordsWriteRequest = Omit<Partial<DwnMessageParams[DwnInterface.RecordsWrite]>, 'signer' | 'data'> & {
276
196
  /** The data payload for the record, which can be of any type. */
277
197
  data: unknown;
278
198
 
279
- /** Optional additional parameters for the record write operation. */
280
- message?: Omit<Partial<DwnMessageParams[DwnInterface.RecordsWrite]>, 'signer'>;
281
-
282
199
  /**
283
200
  * Optional flag indicating whether the record should be immediately stored. If true, the record
284
201
  * is persisted in the DWN as part of the write operation. If false, the record is created,
@@ -307,7 +224,7 @@ export type RecordsWriteResponse = DwnResponseStatus & {
307
224
  * The `Record` instance representing the record that was successfully written to the
308
225
  * DWN as a result of the write operation.
309
226
  */
310
- record?: Record
227
+ record: Record;
311
228
  };
312
229
 
313
230
  /**
@@ -336,30 +253,6 @@ export class DwnApi {
336
253
  this.permissionsApi = new AgentPermissionsApi({ agent: this.agent });
337
254
  }
338
255
 
339
- /**
340
- * Returns a {@link TypedDwnApi} scoped to the given typed protocol.
341
- *
342
- * The returned API provides path autocompletion, typed data payloads,
343
- * and automatically injects the protocol URI and protocolPath into every
344
- * DWN operation.
345
- *
346
- * @param protocol - A typed protocol created via `defineProtocol()`.
347
- * @returns A `TypedDwnApi` instance bound to the given protocol.
348
- *
349
- * @example
350
- * ```ts
351
- * const social = dwn.using(SocialGraphProtocol);
352
- * const { record } = await social.write('friend', {
353
- * data: { did: 'did:example:alice' },
354
- * });
355
- * ```
356
- */
357
- public using<D extends ProtocolDefinition, M extends SchemaMap>(
358
- protocol: TypedProtocol<D, M>,
359
- ): TypedDwnApi<D, M> {
360
- return new TypedDwnApi<D, M>(this, protocol);
361
- }
362
-
363
256
  /**
364
257
  * API to interact with Grants
365
258
  *
@@ -485,20 +378,21 @@ export class DwnApi {
485
378
  * Configure method, used to setup a new protocol (or update) with the passed definitions
486
379
  */
487
380
  configure: async (request: ProtocolsConfigureRequest): Promise<ProtocolsConfigureResponse> => {
488
-
489
- const agentRequest:ProcessDwnRequest<DwnInterface.ProtocolsConfigure> = {
490
- author : this.connectedDid,
491
- messageParams : request.message,
492
- messageType : DwnInterface.ProtocolsConfigure,
493
- target : this.connectedDid,
494
- encryption : request.encryption,
381
+ const { encryption, ...messageParams } = request;
382
+
383
+ const agentRequest: ProcessDwnRequest<DwnInterface.ProtocolsConfigure> = {
384
+ author : this.connectedDid,
385
+ messageParams,
386
+ messageType : DwnInterface.ProtocolsConfigure,
387
+ target : this.connectedDid,
388
+ encryption,
495
389
  };
496
390
 
497
391
  if (this.delegateDid) {
498
392
  const { message: delegatedGrant } = await this.permissionsApi.getPermissionForRequest({
499
393
  connectedDid : this.connectedDid,
500
394
  delegateDid : this.delegateDid,
501
- protocol : request.message.definition.protocol,
395
+ protocol : messageParams.definition.protocol,
502
396
  delegate : true,
503
397
  cached : true,
504
398
  messageType : agentRequest.messageType
@@ -528,11 +422,13 @@ export class DwnApi {
528
422
  * Query the available protocols
529
423
  */
530
424
  query: async (request: ProtocolsQueryRequest): Promise<ProtocolsQueryResponse> => {
425
+ const { from, ...messageParams } = request;
426
+
531
427
  const agentRequest: ProcessDwnRequest<DwnInterface.ProtocolsQuery> = {
532
- author : this.connectedDid,
533
- messageParams : request.message,
534
- messageType : DwnInterface.ProtocolsQuery,
535
- target : request.from || this.connectedDid
428
+ author : this.connectedDid,
429
+ messageParams,
430
+ messageType : DwnInterface.ProtocolsQuery,
431
+ target : from || this.connectedDid,
536
432
  };
537
433
 
538
434
  if (this.delegateDid) {
@@ -543,7 +439,7 @@ export class DwnApi {
543
439
  const { grant: { id: permissionGrantId } } = await this.permissionsApi.getPermissionForRequest({
544
440
  connectedDid : this.connectedDid,
545
441
  delegateDid : this.delegateDid,
546
- protocol : request.message.filter.protocol,
442
+ protocol : messageParams.filter.protocol,
547
443
  cached : true,
548
444
  messageType : agentRequest.messageType
549
445
  });
@@ -561,7 +457,7 @@ export class DwnApi {
561
457
 
562
458
  let agentResponse: DwnResponse<DwnInterface.ProtocolsQuery>;
563
459
 
564
- if (request.from) {
460
+ if (from) {
565
461
  agentResponse = await this.agent.sendDwnRequest(agentRequest);
566
462
  } else {
567
463
  agentResponse = await this.agent.processDwnRequest(agentRequest);
@@ -581,11 +477,9 @@ export class DwnApi {
581
477
  }
582
478
 
583
479
  /**
584
- * API to interact with DWN records (e.g., `dwn.records.create()`).
480
+ * API to interact with DWN records (e.g., `dwn.records.write()`).
585
481
  */
586
482
  get records(): {
587
- create: (request: RecordsCreateRequest) => Promise<RecordsCreateResponse>;
588
- createFrom: (request: RecordsCreateFromRequest) => Promise<RecordsWriteResponse>;
589
483
  delete: (request: RecordsDeleteRequest) => Promise<DwnResponseStatus>;
590
484
  query: (request: RecordsQueryRequest) => Promise<RecordsQueryResponse>;
591
485
  read: (request: RecordsReadRequest) => Promise<RecordsReadResponse>;
@@ -594,77 +488,33 @@ export class DwnApi {
594
488
  } {
595
489
 
596
490
  return {
597
- /**
598
- * Alias for the `write` method
599
- */
600
- create: async (request: RecordsCreateRequest): Promise<RecordsCreateResponse> => {
601
- return this.records.write(request);
602
- },
603
-
604
- /**
605
- * Write a record based on an existing one (useful for updating an existing record)
606
- */
607
- createFrom: async (request: RecordsCreateFromRequest): Promise<RecordsWriteResponse> => {
608
- const { author: inheritedAuthor, ...inheritedProperties } = request.record.toJSON();
609
-
610
- // If `data` is being updated then `dataCid` and `dataSize` must not be present.
611
- if (request.data !== undefined) {
612
- delete inheritedProperties.dataCid;
613
- delete inheritedProperties.dataSize;
614
- }
615
-
616
- // If `published` is set to false, ensure that `datePublished` is undefined. Otherwise, DWN SDK's schema validation
617
- // will throw an error if `published` is false but `datePublished` is set.
618
- if (request.message?.published === false && inheritedProperties.datePublished !== undefined) {
619
- delete inheritedProperties.datePublished;
620
- delete inheritedProperties.published;
621
- }
622
-
623
- // If the request changes the `author` or message `descriptor` then the deterministic `recordId` will change.
624
- // As a result, we will discard the `recordId` if either of these changes occur.
625
- if (!isEmptyObject(request.message) || (request.author && request.author !== inheritedAuthor)) {
626
- delete inheritedProperties.recordId;
627
- }
628
-
629
- // Auto-detect encryption from the source record if not explicitly set.
630
- const shouldEncrypt = request.encryption
631
- ?? (request.record.encryption !== undefined);
632
-
633
- return this.records.write({
634
- data : request.data,
635
- message : {
636
- ...inheritedProperties,
637
- ...request.message,
638
- },
639
- encryption: shouldEncrypt || undefined,
640
- });
641
- },
642
-
643
491
  /**
644
492
  * Delete a record
645
493
  */
646
494
  delete: async (request: RecordsDeleteRequest): Promise<DwnResponseStatus> => {
495
+ const { from, protocol, ...messageParams } = request;
496
+
647
497
  const agentRequest: ProcessDwnRequest<DwnInterface.RecordsDelete> = {
648
498
  /**
649
499
  * The `author` is the DID that will sign the message and must be the DID the Web5 app is
650
500
  * connected with and is authorized to access the signing private key of.
651
501
  */
652
- author : this.connectedDid,
653
- messageParams : request.message,
654
- messageType : DwnInterface.RecordsDelete,
502
+ author : this.connectedDid,
503
+ messageParams,
504
+ messageType : DwnInterface.RecordsDelete,
655
505
  /**
656
506
  * The `target` is the DID of the DWN tenant under which the delete will be executed.
657
507
  * If `from` is provided, the delete operation will be executed on a remote DWN.
658
508
  * Otherwise, the record will be deleted on the local DWN.
659
509
  */
660
- target : request.from || this.connectedDid
510
+ target : from || this.connectedDid,
661
511
  };
662
512
 
663
513
  if (this.delegateDid) {
664
514
  const { message: delegatedGrant } = await this.permissionsApi.getPermissionForRequest({
665
515
  connectedDid : this.connectedDid,
666
516
  delegateDid : this.delegateDid,
667
- protocol : request.protocol,
517
+ protocol,
668
518
  delegate : true,
669
519
  cached : true,
670
520
  messageType : agentRequest.messageType
@@ -679,7 +529,7 @@ export class DwnApi {
679
529
 
680
530
  let agentResponse: DwnResponse<DwnInterface.RecordsDelete>;
681
531
 
682
- if (request.from) {
532
+ if (from) {
683
533
  agentResponse = await this.agent.sendDwnRequest(agentRequest);
684
534
  } else {
685
535
  agentResponse = await this.agent.processDwnRequest(agentRequest);
@@ -689,39 +539,41 @@ export class DwnApi {
689
539
 
690
540
  return { status };
691
541
  },
542
+
692
543
  /**
693
544
  * Query a single or multiple records based on the given filter
694
545
  */
695
546
  query: async (request: RecordsQueryRequest): Promise<RecordsQueryResponse> => {
547
+ const { from, encryption, ...messageParams } = request;
548
+
696
549
  const agentRequest: ProcessDwnRequest<DwnInterface.RecordsQuery> = {
697
550
  /**
698
551
  * The `author` is the DID that will sign the message and must be the DID the Web5 app is
699
552
  * connected with and is authorized to access the signing private key of.
700
553
  */
701
- author : this.connectedDid,
702
- messageParams : request.message,
703
- messageType : DwnInterface.RecordsQuery,
554
+ author : this.connectedDid,
555
+ messageParams,
556
+ messageType : DwnInterface.RecordsQuery,
704
557
  /**
705
558
  * The `target` is the DID of the DWN tenant under which the query will be executed.
706
559
  * If `from` is provided, the query operation will be executed on a remote DWN.
707
560
  * Otherwise, the local DWN will be queried.
708
561
  */
709
- target : request.from || this.connectedDid,
710
- encryption : request.encryption,
562
+ target : from || this.connectedDid,
563
+ encryption,
711
564
  };
712
565
 
713
566
  if (this.delegateDid) {
714
567
  // if we don't find a delegated grant, we will attempt to query signing as the delegated DID
715
568
  // This is to allow the API caller to query public records without needing to impersonate the delegate.
716
569
  //
717
- // NOTE: When a read-only DwnApi is implemented, callers should use that instead when they don't have an explicit permission.
718
- // This should fail if a permission is not found.
719
- // TODO: https://github.com/enboxorg/enbox/issues/898
570
+ // NOTE: For anonymous/public queries without explicit permissions, callers can use `DwnReaderApi` via `Web5.anonymous()`.
571
+ // See: https://github.com/enboxorg/enbox/issues/898
720
572
  try {
721
573
  const { message: delegatedGrant } = await this.permissionsApi.getPermissionForRequest({
722
574
  connectedDid : this.connectedDid,
723
575
  delegateDid : this.delegateDid,
724
- protocol : request.protocol,
576
+ protocol : messageParams.filter?.protocol,
725
577
  delegate : true,
726
578
  cached : true,
727
579
  messageType : agentRequest.messageType
@@ -738,10 +590,9 @@ export class DwnApi {
738
590
  }
739
591
  }
740
592
 
741
-
742
593
  let agentResponse: DwnResponse<DwnInterface.RecordsQuery>;
743
594
 
744
- if (request.from) {
595
+ if (from) {
745
596
  agentResponse = await this.agent.sendDwnRequest(agentRequest);
746
597
  } else {
747
598
  agentResponse = await this.agent.processDwnRequest(agentRequest);
@@ -751,7 +602,6 @@ export class DwnApi {
751
602
  const { entries = [], status, cursor } = reply;
752
603
 
753
604
  const records = entries.map((entry) => {
754
-
755
605
  const recordOptions = {
756
606
  /**
757
607
  * Extract the `author` DID from the record entry since records may be signed by the
@@ -770,7 +620,7 @@ export class DwnApi {
770
620
  * to determine which DWN to send subsequent read requests to in the event the data
771
621
  * payload exceeds the threshold for being returned with queries.
772
622
  */
773
- remoteOrigin : request.from,
623
+ remoteOrigin : from,
774
624
  delegateDid : this.delegateDid,
775
625
  protocolRole : agentRequest.messageParams.protocolRole,
776
626
  ...entry as DwnMessage[DwnInterface.RecordsWrite]
@@ -786,35 +636,37 @@ export class DwnApi {
786
636
  * Read a single record based on the given filter
787
637
  */
788
638
  read: async (request: RecordsReadRequest): Promise<RecordsReadResponse> => {
639
+ const { from, protocol, encryption, ...messageParams } = request;
640
+
789
641
  const agentRequest: ProcessDwnRequest<DwnInterface.RecordsRead> = {
790
642
  /**
791
643
  * The `author` is the DID that will sign the message and must be the DID the Web5 app is
792
644
  * connected with and is authorized to access the signing private key of.
793
645
  */
794
- author : this.connectedDid,
795
- messageParams : request.message,
796
- messageType : DwnInterface.RecordsRead,
646
+ author : this.connectedDid,
647
+ messageParams,
648
+ messageType : DwnInterface.RecordsRead,
797
649
  /**
798
650
  * The `target` is the DID of the DWN tenant under which the read will be executed.
799
651
  * If `from` is provided, the read operation will be executed on a remote DWN.
800
652
  * Otherwise, the read will occur on the local DWN.
801
653
  */
802
- target : request.from || this.connectedDid,
803
- encryption : request.encryption,
654
+ target : from || this.connectedDid,
655
+ encryption,
804
656
  };
657
+
805
658
  if (this.delegateDid) {
806
659
  // if we don't find a delegated grant, we will attempt to read signing as the delegated DID
807
660
  // This is to allow the API caller to read public records without needing to impersonate the delegate.
808
661
  //
809
- // NOTE: When a read-only DwnApi is implemented, callers should use that instead when they don't have an explicit permission.
810
- // This should fail if a permission is not found.
811
- // TODO: https://github.com/enboxorg/enbox/issues/898
662
+ // NOTE: For anonymous/public reads without explicit permissions, callers can use `DwnReaderApi` via `Web5.anonymous()`.
663
+ // See: https://github.com/enboxorg/enbox/issues/898
812
664
 
813
665
  try {
814
666
  const { message: delegatedGrant } = await this.permissionsApi.getPermissionForRequest({
815
667
  connectedDid : this.connectedDid,
816
668
  delegateDid : this.delegateDid,
817
- protocol : request.protocol,
669
+ protocol,
818
670
  delegate : true,
819
671
  cached : true,
820
672
  messageType : agentRequest.messageType
@@ -833,7 +685,7 @@ export class DwnApi {
833
685
 
834
686
  let agentResponse: DwnResponse<DwnInterface.RecordsRead>;
835
687
 
836
- if (request.from) {
688
+ if (from) {
837
689
  agentResponse = await this.agent.sendDwnRequest(agentRequest);
838
690
  } else {
839
691
  agentResponse = await this.agent.processDwnRequest(agentRequest);
@@ -861,7 +713,7 @@ export class DwnApi {
861
713
  * to determine which DWN to send subsequent read requests to in the event the data
862
714
  * payload must be read again (e.g., if the data stream is consumed).
863
715
  */
864
- remoteOrigin : request.from,
716
+ remoteOrigin : from,
865
717
  delegateDid : this.delegateDid,
866
718
  data : entry.data,
867
719
  initialWrite : entry.initialWrite,
@@ -875,52 +727,61 @@ export class DwnApi {
875
727
  },
876
728
 
877
729
  /**
878
- * Subscribes to records based on the given filter and emits events to the `subscriptionHandler`.
730
+ * Subscribe to records matching the given filter.
879
731
  *
880
- * @param request must include the `message` with the subscription filter and the `subscriptionHandler` to process the events.
881
- * @returns the subscription status and the subscription object used to close the subscription.
732
+ * Returns a {@link LiveQuery} that atomically provides an initial snapshot
733
+ * of matching records and a real-time stream of deduplicated, semantically-
734
+ * typed change events (`create`, `update`, `delete`).
882
735
  */
883
736
  subscribe: async (request: RecordsSubscribeRequest): Promise<RecordsSubscribeResponse> => {
884
- const agentRequest: ProcessDwnRequest<DwnInterface.RecordsSubscribe> = {
885
- /**
886
- * The `author` is the DID that will sign the message and must be the DID the Web5 app is
887
- * connected with and is authorized to access the signing private key of.
888
- */
889
- author : this.connectedDid,
890
- messageParams : request.message,
891
- messageType : DwnInterface.RecordsSubscribe,
892
- /**
893
- * The `target` is the DID of the DWN tenant under which the subscribe operation will be executed.
894
- * If `from` is provided, the subscribe operation will be executed on a remote DWN.
895
- * Otherwise, the local DWN will execute the subscribe operation.
896
- */
897
- target : request.from || this.connectedDid,
737
+ const { from, ...messageParams } = request;
898
738
 
899
- /**
900
- * The handler to process the subscription events.
901
- */
902
- subscriptionHandler: SubscriptionUtil.recordSubscriptionHandler({
903
- agent : this.agent,
904
- connectedDid : this.connectedDid,
905
- delegateDid : this.delegateDid,
906
- permissionsApi : this.permissionsApi,
907
- protocolRole : request.message.protocolRole,
908
- request
909
- })
739
+ // Build a DWN-level subscription handler that wraps raw RecordEvents
740
+ // into Record objects and feeds them into the LiveQuery.
741
+ let liveQuery: LiveQuery | undefined;
742
+
743
+ const remoteOrigin = from;
744
+ const protocolRole = messageParams.protocolRole;
745
+
746
+ type RecordEvent = {
747
+ message: DwnMessage[DwnInterface.RecordsWrite];
748
+ initialWrite?: DwnMessage[DwnInterface.RecordsWrite];
749
+ };
750
+
751
+ const subscriptionHandler = async (event: RecordEvent): Promise<void> => {
752
+ const { message, initialWrite } = event;
753
+ const record = new Record(this.agent, {
754
+ ...message,
755
+ author : getRecordAuthor(message),
756
+ connectedDid : this.connectedDid,
757
+ remoteOrigin,
758
+ initialWrite,
759
+ protocolRole,
760
+ delegateDid : this.delegateDid,
761
+ }, this.permissionsApi);
762
+
763
+ liveQuery?.handleEvent(record);
764
+ };
765
+
766
+ const agentRequest: ProcessDwnRequest<DwnInterface.RecordsSubscribe> = {
767
+ author : this.connectedDid,
768
+ messageParams,
769
+ messageType : DwnInterface.RecordsSubscribe,
770
+ target : from || this.connectedDid,
771
+ subscriptionHandler,
910
772
  };
911
773
 
912
774
  if (this.delegateDid) {
913
775
  // if we don't find a delegated grant, we will attempt to subscribe signing as the delegated DID
914
776
  // This is to allow the API caller to subscribe to public records without needing to impersonate the delegate.
915
777
  //
916
- // NOTE: When a read-only DwnApi is implemented, callers should use that instead when they don't have an explicit permission.
917
- // This should fail if a permission is not found.
918
- // TODO: https://github.com/enboxorg/enbox/issues/898
778
+ // NOTE: For anonymous/public subscriptions without explicit permissions, callers can use `DwnReaderApi` via `Web5.anonymous()`.
779
+ // See: https://github.com/enboxorg/enbox/issues/898
919
780
  try {
920
781
  const { message: delegatedGrant } = await this.permissionsApi.getPermissionForRequest({
921
782
  connectedDid : this.connectedDid,
922
783
  delegateDid : this.delegateDid,
923
- protocol : request.protocol,
784
+ protocol : messageParams.filter?.protocol,
924
785
  delegate : true,
925
786
  cached : true,
926
787
  messageType : agentRequest.messageType
@@ -939,16 +800,30 @@ export class DwnApi {
939
800
 
940
801
  let agentResponse: DwnResponse<DwnInterface.RecordsSubscribe>;
941
802
 
942
- if (request.from) {
803
+ if (from) {
943
804
  agentResponse = await this.agent.sendDwnRequest(agentRequest);
944
805
  } else {
945
806
  agentResponse = await this.agent.processDwnRequest(agentRequest);
946
807
  }
947
808
 
948
809
  const reply = agentResponse.reply;
949
- const { status, subscription } = reply;
810
+ const { status, subscription, entries = [], cursor } = reply;
811
+
812
+ if (subscription) {
813
+ liveQuery = new LiveQuery({
814
+ agent : this.agent,
815
+ connectedDid : this.connectedDid,
816
+ delegateDid : this.delegateDid,
817
+ protocolRole,
818
+ remoteOrigin,
819
+ permissionsApi : this.permissionsApi,
820
+ initialEntries : entries,
821
+ cursor,
822
+ subscription,
823
+ });
824
+ }
950
825
 
951
- return { status, subscription };
826
+ return { status, liveQuery };
952
827
  },
953
828
 
954
829
  /**
@@ -961,19 +836,19 @@ export class DwnApi {
961
836
  * requires fetching from the DWN datastore.
962
837
  */
963
838
  write: async (request: RecordsWriteRequest): Promise<RecordsWriteResponse> => {
964
- const { dataBlob, dataFormat } = dataToBlob(request.data, request.message?.dataFormat);
839
+ const { data, store, encryption, ...restParams } = request;
840
+ const { dataBlob, dataFormat } = dataToBlob(data, restParams.dataFormat);
841
+
842
+ const messageParams = { ...restParams, dataFormat };
965
843
 
966
844
  const dwnRequestParams: ProcessDwnRequest<DwnInterface.RecordsWrite> = {
967
- store : request.store,
968
- messageType : DwnInterface.RecordsWrite,
969
- messageParams : {
970
- ...request.message,
971
- dataFormat
972
- },
973
- author : this.connectedDid,
974
- target : this.connectedDid,
975
- dataStream : dataBlob,
976
- encryption : request.encryption,
845
+ store,
846
+ messageType : DwnInterface.RecordsWrite,
847
+ messageParams,
848
+ author : this.connectedDid,
849
+ target : this.connectedDid,
850
+ dataStream : dataBlob,
851
+ encryption,
977
852
  };
978
853
 
979
854
  // if impersonation is enabled, fetch the delegated grant to use with the write operation
@@ -981,7 +856,7 @@ export class DwnApi {
981
856
  const { message: delegatedGrant } = await this.permissionsApi.getPermissionForRequest({
982
857
  connectedDid : this.connectedDid,
983
858
  delegateDid : this.delegateDid,
984
- protocol : request.message.protocol,
859
+ protocol : messageParams.protocol,
985
860
  delegate : true,
986
861
  cached : true,
987
862
  messageType : dwnRequestParams.messageType