@enbox/api 0.1.1 → 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 (58) 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 +55 -107
  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 +3 -2
  12. package/dist/esm/index.js.map +1 -1
  13. package/dist/esm/live-query.js +5 -4
  14. package/dist/esm/live-query.js.map +1 -1
  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 +12 -89
  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 +3 -2
  31. package/dist/types/index.d.ts.map +1 -1
  32. package/dist/types/live-query.d.ts +13 -1
  33. package/dist/types/live-query.d.ts.map +1 -1
  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} +70 -73
  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 +9 -5
  44. package/src/advanced.ts +29 -0
  45. package/src/define-protocol.ts +3 -3
  46. package/src/dwn-api.ts +88 -222
  47. package/src/dwn-reader-api.ts +255 -0
  48. package/src/index.ts +3 -2
  49. package/src/live-query.ts +20 -4
  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/typed-dwn-api.js +0 -182
  56. package/dist/esm/typed-dwn-api.js.map +0 -1
  57. package/dist/types/typed-dwn-api.d.ts.map +0 -1
  58. package/src/typed-dwn-api.ts +0 -370
package/src/dwn-api.ts CHANGED
@@ -21,20 +21,14 @@ import {
21
21
  AgentPermissionsApi,
22
22
  } from '@enbox/agent';
23
23
 
24
- import { isEmptyObject } from '@enbox/common';
25
24
  import { DwnInterface, getRecordAuthor } from '@enbox/agent';
26
25
 
27
- import type { ProtocolDefinition } from '@enbox/dwn-sdk-js';
28
-
29
- import type { SchemaMap, TypedProtocol } from './protocol-types.js';
30
-
31
26
  import { dataToBlob } from './utils.js';
32
27
  import { LiveQuery } from './live-query.js';
33
28
  import { PermissionGrant } from './permission-grant.js';
34
29
  import { PermissionRequest } from './permission-request.js';
35
30
  import { Protocol } from './protocol.js';
36
31
  import { Record } from './record.js';
37
- import { TypedDwnApi } from './typed-dwn-api.js';
38
32
 
39
33
  /**
40
34
  * Represents the request payload for fetching permission requests from a Decentralized Web Node (DWN).
@@ -64,9 +58,7 @@ export type FetchGrantsRequest = Omit<FetchPermissionsParams, 'author' | 'target
64
58
  *
65
59
  * This request type is used to specify the configuration options for the protocol.
66
60
  */
67
- export type ProtocolsConfigureRequest = {
68
- /** Configuration options for the protocol. */
69
- message: Omit<DwnMessageParams[DwnInterface.ProtocolsConfigure], 'signer'>;
61
+ export type ProtocolsConfigureRequest = Omit<DwnMessageParams[DwnInterface.ProtocolsConfigure], 'signer'> & {
70
62
  /** When true, derives and injects $encryption public keys into the protocol definition. */
71
63
  encryption?: boolean;
72
64
  };
@@ -92,12 +84,9 @@ export type ProtocolsConfigureResponse = DwnResponseStatus & {
92
84
  * target the local DWN. If the `from` property is provided, the query will target the specified
93
85
  * remote DWN.
94
86
  */
95
- export type ProtocolsQueryRequest = {
87
+ export type ProtocolsQueryRequest = Omit<DwnMessageParams[DwnInterface.ProtocolsQuery], 'signer'> & {
96
88
  /** Optional DID specifying the remote target DWN tenant to be queried. */
97
89
  from?: string;
98
-
99
- /** Query filters and options that influence the results returned. */
100
- message: Omit<DwnMessageParams[DwnInterface.ProtocolsQuery], 'signer'>
101
90
  };
102
91
 
103
92
  /**
@@ -109,40 +98,6 @@ export type ProtocolsQueryResponse = DwnResponseStatus & {
109
98
  protocols: Protocol[];
110
99
  };
111
100
 
112
- /**
113
- * Type alias for {@link RecordsWriteRequest}
114
- */
115
- export type RecordsCreateRequest = RecordsWriteRequest;
116
-
117
- /**
118
- * Type alias for {@link RecordsWriteResponse}
119
- */
120
- export type RecordsCreateResponse = RecordsWriteResponse;
121
-
122
- /**
123
- * Represents a request to create a new record based on an existing one.
124
- *
125
- * This request type allows specifying the new data for the record, along with any additional
126
- * message parameters required for the write operation.
127
- */
128
- export type RecordsCreateFromRequest = {
129
- /** The DID of the entity authoring the record. */
130
- author: string;
131
- /** The new data for the record. */
132
- data: unknown;
133
- /** Optional additional parameters for the record write operation */
134
- message?: Omit<DwnMessageParams[DwnInterface.RecordsWrite], 'signer'>;
135
- /** The existing record instance that is being used as a basis for the new record. */
136
- record: Record;
137
- /**
138
- * Controls whether the new record should be auto-encrypted.
139
- *
140
- * If omitted, auto-detected from the source record: if the source record was
141
- * encrypted, the new record is automatically encrypted with a fresh DEK.
142
- */
143
- encryption?: boolean;
144
- };
145
-
146
101
  /**
147
102
  * Defines a request to delete a record from the Decentralized Web Node (DWN).
148
103
  *
@@ -150,15 +105,12 @@ export type RecordsCreateFromRequest = {
150
105
  * message parameters for the delete operation. If the `from` property is not provided, the record
151
106
  * will be deleted from the local DWN.
152
107
  */
153
- export type RecordsDeleteRequest = {
108
+ export type RecordsDeleteRequest = Omit<DwnMessageParams[DwnInterface.RecordsDelete], 'signer'> & {
154
109
  /** Optional DID specifying the remote target DWN tenant the record will be deleted from. */
155
110
  from?: string;
156
111
 
157
- /** Records must be scoped to a specific protocol */
112
+ /** Protocol URI for permission grant resolution. */
158
113
  protocol?: string;
159
-
160
- /** The parameters for the delete operation. */
161
- message: Omit<DwnMessageParams[DwnInterface.RecordsDelete], 'signer'>;
162
114
  };
163
115
 
164
116
  /**
@@ -168,16 +120,10 @@ export type RecordsDeleteRequest = {
168
120
  * parameters, and optionally the target DWN to query from. If the `from` property is not provided,
169
121
  * the query will target the local DWN.
170
122
  */
171
- export type RecordsQueryRequest = {
123
+ export type RecordsQueryRequest = Omit<DwnMessageParams[DwnInterface.RecordsQuery], 'signer'> & {
172
124
  /** Optional DID specifying the remote target DWN tenant to query from and return results. */
173
125
  from?: string;
174
126
 
175
- /** Records must be scoped to a specific protocol */
176
- protocol?: string;
177
-
178
- /** The parameters for the query operation, detailing the criteria for selecting records. */
179
- message: Omit<DwnMessageParams[DwnInterface.RecordsQuery], 'signer'>;
180
-
181
127
  /** When true, automatically decrypts encrypted records in the query results. */
182
128
  encryption?: boolean;
183
129
  };
@@ -188,7 +134,7 @@ export type RecordsQueryRequest = {
188
134
  */
189
135
  export type RecordsQueryResponse = DwnResponseStatus & {
190
136
  /** Array of records matching the query. */
191
- records?: Record[]
137
+ records: Record[];
192
138
 
193
139
  /** If there are additional results, the messageCid of the last record will be returned as a pagination cursor. */
194
140
  cursor?: DwnPaginationCursor;
@@ -201,16 +147,13 @@ export type RecordsQueryResponse = DwnResponseStatus & {
201
147
  * additional parameters for the read operation. It's useful for fetching the details of a single
202
148
  * record by its identifier or other criteria.
203
149
  */
204
- export type RecordsReadRequest = {
150
+ export type RecordsReadRequest = Omit<DwnMessageParams[DwnInterface.RecordsRead], 'signer'> & {
205
151
  /** Optional DID specifying the remote target DWN tenant the record will be read from. */
206
152
  from?: string;
207
153
 
208
- /** Records must be scoped to a specific protocol */
154
+ /** Protocol URI for permission grant resolution. */
209
155
  protocol?: string;
210
156
 
211
- /** The parameters for the read operation, detailing the criteria for selecting the record. */
212
- message: Omit<DwnMessageParams[DwnInterface.RecordsRead], 'signer'>;
213
-
214
157
  /** When true, automatically decrypts the encrypted record data. */
215
158
  encryption?: boolean;
216
159
  };
@@ -231,15 +174,9 @@ export type RecordsReadResponse = DwnResponseStatus & {
231
174
  * matching records alongside a real-time stream of deduplicated, semantically-
232
175
  * typed change events (`create`, `update`, `delete`).
233
176
  */
234
- export type RecordsSubscribeRequest = {
177
+ export type RecordsSubscribeRequest = Omit<DwnMessageParams[DwnInterface.RecordsSubscribe], 'signer'> & {
235
178
  /** Optional DID specifying the remote target DWN tenant to subscribe from. */
236
179
  from?: string;
237
-
238
- /** Records must be scoped to a specific protocol. */
239
- protocol?: string;
240
-
241
- /** The parameters for the subscription operation, detailing the criteria for the subscription filter. */
242
- message: Omit<DwnMessageParams[DwnInterface.RecordsSubscribe], 'signer'>;
243
180
  };
244
181
 
245
182
  /** Encapsulates the response from a DWN RecordsSubscribeRequest. */
@@ -254,18 +191,11 @@ export type RecordsSubscribeResponse = DwnResponseStatus & {
254
191
  * This request type allows specifying the data for the new or updated record, along with any
255
192
  * additional message parameters required for the write operation, and an optional flag to indicate
256
193
  * whether the record should be immediately stored.
257
- *
258
- * @param data -
259
- * @param message - , excluding the signer.
260
- * @param store -
261
194
  */
262
- export type RecordsWriteRequest = {
195
+ export type RecordsWriteRequest = Omit<Partial<DwnMessageParams[DwnInterface.RecordsWrite]>, 'signer' | 'data'> & {
263
196
  /** The data payload for the record, which can be of any type. */
264
197
  data: unknown;
265
198
 
266
- /** Optional additional parameters for the record write operation. */
267
- message?: Omit<Partial<DwnMessageParams[DwnInterface.RecordsWrite]>, 'signer'>;
268
-
269
199
  /**
270
200
  * Optional flag indicating whether the record should be immediately stored. If true, the record
271
201
  * is persisted in the DWN as part of the write operation. If false, the record is created,
@@ -294,7 +224,7 @@ export type RecordsWriteResponse = DwnResponseStatus & {
294
224
  * The `Record` instance representing the record that was successfully written to the
295
225
  * DWN as a result of the write operation.
296
226
  */
297
- record?: Record
227
+ record: Record;
298
228
  };
299
229
 
300
230
  /**
@@ -323,30 +253,6 @@ export class DwnApi {
323
253
  this.permissionsApi = new AgentPermissionsApi({ agent: this.agent });
324
254
  }
325
255
 
326
- /**
327
- * Returns a {@link TypedDwnApi} scoped to the given typed protocol.
328
- *
329
- * The returned API provides path autocompletion, typed data payloads,
330
- * and automatically injects the protocol URI and protocolPath into every
331
- * DWN operation.
332
- *
333
- * @param protocol - A typed protocol created via `defineProtocol()`.
334
- * @returns A `TypedDwnApi` instance bound to the given protocol.
335
- *
336
- * @example
337
- * ```ts
338
- * const social = dwn.using(SocialGraphProtocol);
339
- * const { record } = await social.write('friend', {
340
- * data: { did: 'did:example:alice' },
341
- * });
342
- * ```
343
- */
344
- public using<D extends ProtocolDefinition, M extends SchemaMap>(
345
- protocol: TypedProtocol<D, M>,
346
- ): TypedDwnApi<D, M> {
347
- return new TypedDwnApi<D, M>(this, protocol);
348
- }
349
-
350
256
  /**
351
257
  * API to interact with Grants
352
258
  *
@@ -472,20 +378,21 @@ export class DwnApi {
472
378
  * Configure method, used to setup a new protocol (or update) with the passed definitions
473
379
  */
474
380
  configure: async (request: ProtocolsConfigureRequest): Promise<ProtocolsConfigureResponse> => {
475
-
476
- const agentRequest:ProcessDwnRequest<DwnInterface.ProtocolsConfigure> = {
477
- author : this.connectedDid,
478
- messageParams : request.message,
479
- messageType : DwnInterface.ProtocolsConfigure,
480
- target : this.connectedDid,
481
- 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,
482
389
  };
483
390
 
484
391
  if (this.delegateDid) {
485
392
  const { message: delegatedGrant } = await this.permissionsApi.getPermissionForRequest({
486
393
  connectedDid : this.connectedDid,
487
394
  delegateDid : this.delegateDid,
488
- protocol : request.message.definition.protocol,
395
+ protocol : messageParams.definition.protocol,
489
396
  delegate : true,
490
397
  cached : true,
491
398
  messageType : agentRequest.messageType
@@ -515,11 +422,13 @@ export class DwnApi {
515
422
  * Query the available protocols
516
423
  */
517
424
  query: async (request: ProtocolsQueryRequest): Promise<ProtocolsQueryResponse> => {
425
+ const { from, ...messageParams } = request;
426
+
518
427
  const agentRequest: ProcessDwnRequest<DwnInterface.ProtocolsQuery> = {
519
- author : this.connectedDid,
520
- messageParams : request.message,
521
- messageType : DwnInterface.ProtocolsQuery,
522
- target : request.from || this.connectedDid
428
+ author : this.connectedDid,
429
+ messageParams,
430
+ messageType : DwnInterface.ProtocolsQuery,
431
+ target : from || this.connectedDid,
523
432
  };
524
433
 
525
434
  if (this.delegateDid) {
@@ -530,7 +439,7 @@ export class DwnApi {
530
439
  const { grant: { id: permissionGrantId } } = await this.permissionsApi.getPermissionForRequest({
531
440
  connectedDid : this.connectedDid,
532
441
  delegateDid : this.delegateDid,
533
- protocol : request.message.filter.protocol,
442
+ protocol : messageParams.filter.protocol,
534
443
  cached : true,
535
444
  messageType : agentRequest.messageType
536
445
  });
@@ -548,7 +457,7 @@ export class DwnApi {
548
457
 
549
458
  let agentResponse: DwnResponse<DwnInterface.ProtocolsQuery>;
550
459
 
551
- if (request.from) {
460
+ if (from) {
552
461
  agentResponse = await this.agent.sendDwnRequest(agentRequest);
553
462
  } else {
554
463
  agentResponse = await this.agent.processDwnRequest(agentRequest);
@@ -568,11 +477,9 @@ export class DwnApi {
568
477
  }
569
478
 
570
479
  /**
571
- * API to interact with DWN records (e.g., `dwn.records.create()`).
480
+ * API to interact with DWN records (e.g., `dwn.records.write()`).
572
481
  */
573
482
  get records(): {
574
- create: (request: RecordsCreateRequest) => Promise<RecordsCreateResponse>;
575
- createFrom: (request: RecordsCreateFromRequest) => Promise<RecordsWriteResponse>;
576
483
  delete: (request: RecordsDeleteRequest) => Promise<DwnResponseStatus>;
577
484
  query: (request: RecordsQueryRequest) => Promise<RecordsQueryResponse>;
578
485
  read: (request: RecordsReadRequest) => Promise<RecordsReadResponse>;
@@ -581,77 +488,33 @@ export class DwnApi {
581
488
  } {
582
489
 
583
490
  return {
584
- /**
585
- * Alias for the `write` method
586
- */
587
- create: async (request: RecordsCreateRequest): Promise<RecordsCreateResponse> => {
588
- return this.records.write(request);
589
- },
590
-
591
- /**
592
- * Write a record based on an existing one (useful for updating an existing record)
593
- */
594
- createFrom: async (request: RecordsCreateFromRequest): Promise<RecordsWriteResponse> => {
595
- const { author: inheritedAuthor, ...inheritedProperties } = request.record.toJSON();
596
-
597
- // If `data` is being updated then `dataCid` and `dataSize` must not be present.
598
- if (request.data !== undefined) {
599
- delete inheritedProperties.dataCid;
600
- delete inheritedProperties.dataSize;
601
- }
602
-
603
- // If `published` is set to false, ensure that `datePublished` is undefined. Otherwise, DWN SDK's schema validation
604
- // will throw an error if `published` is false but `datePublished` is set.
605
- if (request.message?.published === false && inheritedProperties.datePublished !== undefined) {
606
- delete inheritedProperties.datePublished;
607
- delete inheritedProperties.published;
608
- }
609
-
610
- // If the request changes the `author` or message `descriptor` then the deterministic `recordId` will change.
611
- // As a result, we will discard the `recordId` if either of these changes occur.
612
- if (!isEmptyObject(request.message) || (request.author && request.author !== inheritedAuthor)) {
613
- delete inheritedProperties.recordId;
614
- }
615
-
616
- // Auto-detect encryption from the source record if not explicitly set.
617
- const shouldEncrypt = request.encryption
618
- ?? (request.record.encryption !== undefined);
619
-
620
- return this.records.write({
621
- data : request.data,
622
- message : {
623
- ...inheritedProperties,
624
- ...request.message,
625
- },
626
- encryption: shouldEncrypt || undefined,
627
- });
628
- },
629
-
630
491
  /**
631
492
  * Delete a record
632
493
  */
633
494
  delete: async (request: RecordsDeleteRequest): Promise<DwnResponseStatus> => {
495
+ const { from, protocol, ...messageParams } = request;
496
+
634
497
  const agentRequest: ProcessDwnRequest<DwnInterface.RecordsDelete> = {
635
498
  /**
636
499
  * The `author` is the DID that will sign the message and must be the DID the Web5 app is
637
500
  * connected with and is authorized to access the signing private key of.
638
501
  */
639
- author : this.connectedDid,
640
- messageParams : request.message,
641
- messageType : DwnInterface.RecordsDelete,
502
+ author : this.connectedDid,
503
+ messageParams,
504
+ messageType : DwnInterface.RecordsDelete,
642
505
  /**
643
506
  * The `target` is the DID of the DWN tenant under which the delete will be executed.
644
507
  * If `from` is provided, the delete operation will be executed on a remote DWN.
645
508
  * Otherwise, the record will be deleted on the local DWN.
646
509
  */
647
- target : request.from || this.connectedDid
510
+ target : from || this.connectedDid,
648
511
  };
649
512
 
650
513
  if (this.delegateDid) {
651
514
  const { message: delegatedGrant } = await this.permissionsApi.getPermissionForRequest({
652
515
  connectedDid : this.connectedDid,
653
516
  delegateDid : this.delegateDid,
654
- protocol : request.protocol,
517
+ protocol,
655
518
  delegate : true,
656
519
  cached : true,
657
520
  messageType : agentRequest.messageType
@@ -666,7 +529,7 @@ export class DwnApi {
666
529
 
667
530
  let agentResponse: DwnResponse<DwnInterface.RecordsDelete>;
668
531
 
669
- if (request.from) {
532
+ if (from) {
670
533
  agentResponse = await this.agent.sendDwnRequest(agentRequest);
671
534
  } else {
672
535
  agentResponse = await this.agent.processDwnRequest(agentRequest);
@@ -681,35 +544,36 @@ export class DwnApi {
681
544
  * Query a single or multiple records based on the given filter
682
545
  */
683
546
  query: async (request: RecordsQueryRequest): Promise<RecordsQueryResponse> => {
547
+ const { from, encryption, ...messageParams } = request;
548
+
684
549
  const agentRequest: ProcessDwnRequest<DwnInterface.RecordsQuery> = {
685
550
  /**
686
551
  * The `author` is the DID that will sign the message and must be the DID the Web5 app is
687
552
  * connected with and is authorized to access the signing private key of.
688
553
  */
689
- author : this.connectedDid,
690
- messageParams : request.message,
691
- messageType : DwnInterface.RecordsQuery,
554
+ author : this.connectedDid,
555
+ messageParams,
556
+ messageType : DwnInterface.RecordsQuery,
692
557
  /**
693
558
  * The `target` is the DID of the DWN tenant under which the query will be executed.
694
559
  * If `from` is provided, the query operation will be executed on a remote DWN.
695
560
  * Otherwise, the local DWN will be queried.
696
561
  */
697
- target : request.from || this.connectedDid,
698
- encryption : request.encryption,
562
+ target : from || this.connectedDid,
563
+ encryption,
699
564
  };
700
565
 
701
566
  if (this.delegateDid) {
702
567
  // if we don't find a delegated grant, we will attempt to query signing as the delegated DID
703
568
  // This is to allow the API caller to query public records without needing to impersonate the delegate.
704
569
  //
705
- // NOTE: When a read-only DwnApi is implemented, callers should use that instead when they don't have an explicit permission.
706
- // This should fail if a permission is not found.
707
- // 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
708
572
  try {
709
573
  const { message: delegatedGrant } = await this.permissionsApi.getPermissionForRequest({
710
574
  connectedDid : this.connectedDid,
711
575
  delegateDid : this.delegateDid,
712
- protocol : request.protocol,
576
+ protocol : messageParams.filter?.protocol,
713
577
  delegate : true,
714
578
  cached : true,
715
579
  messageType : agentRequest.messageType
@@ -726,10 +590,9 @@ export class DwnApi {
726
590
  }
727
591
  }
728
592
 
729
-
730
593
  let agentResponse: DwnResponse<DwnInterface.RecordsQuery>;
731
594
 
732
- if (request.from) {
595
+ if (from) {
733
596
  agentResponse = await this.agent.sendDwnRequest(agentRequest);
734
597
  } else {
735
598
  agentResponse = await this.agent.processDwnRequest(agentRequest);
@@ -739,7 +602,6 @@ export class DwnApi {
739
602
  const { entries = [], status, cursor } = reply;
740
603
 
741
604
  const records = entries.map((entry) => {
742
-
743
605
  const recordOptions = {
744
606
  /**
745
607
  * Extract the `author` DID from the record entry since records may be signed by the
@@ -758,7 +620,7 @@ export class DwnApi {
758
620
  * to determine which DWN to send subsequent read requests to in the event the data
759
621
  * payload exceeds the threshold for being returned with queries.
760
622
  */
761
- remoteOrigin : request.from,
623
+ remoteOrigin : from,
762
624
  delegateDid : this.delegateDid,
763
625
  protocolRole : agentRequest.messageParams.protocolRole,
764
626
  ...entry as DwnMessage[DwnInterface.RecordsWrite]
@@ -774,35 +636,37 @@ export class DwnApi {
774
636
  * Read a single record based on the given filter
775
637
  */
776
638
  read: async (request: RecordsReadRequest): Promise<RecordsReadResponse> => {
639
+ const { from, protocol, encryption, ...messageParams } = request;
640
+
777
641
  const agentRequest: ProcessDwnRequest<DwnInterface.RecordsRead> = {
778
642
  /**
779
643
  * The `author` is the DID that will sign the message and must be the DID the Web5 app is
780
644
  * connected with and is authorized to access the signing private key of.
781
645
  */
782
- author : this.connectedDid,
783
- messageParams : request.message,
784
- messageType : DwnInterface.RecordsRead,
646
+ author : this.connectedDid,
647
+ messageParams,
648
+ messageType : DwnInterface.RecordsRead,
785
649
  /**
786
650
  * The `target` is the DID of the DWN tenant under which the read will be executed.
787
651
  * If `from` is provided, the read operation will be executed on a remote DWN.
788
652
  * Otherwise, the read will occur on the local DWN.
789
653
  */
790
- target : request.from || this.connectedDid,
791
- encryption : request.encryption,
654
+ target : from || this.connectedDid,
655
+ encryption,
792
656
  };
657
+
793
658
  if (this.delegateDid) {
794
659
  // if we don't find a delegated grant, we will attempt to read signing as the delegated DID
795
660
  // This is to allow the API caller to read public records without needing to impersonate the delegate.
796
661
  //
797
- // NOTE: When a read-only DwnApi is implemented, callers should use that instead when they don't have an explicit permission.
798
- // This should fail if a permission is not found.
799
- // 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
800
664
 
801
665
  try {
802
666
  const { message: delegatedGrant } = await this.permissionsApi.getPermissionForRequest({
803
667
  connectedDid : this.connectedDid,
804
668
  delegateDid : this.delegateDid,
805
- protocol : request.protocol,
669
+ protocol,
806
670
  delegate : true,
807
671
  cached : true,
808
672
  messageType : agentRequest.messageType
@@ -821,7 +685,7 @@ export class DwnApi {
821
685
 
822
686
  let agentResponse: DwnResponse<DwnInterface.RecordsRead>;
823
687
 
824
- if (request.from) {
688
+ if (from) {
825
689
  agentResponse = await this.agent.sendDwnRequest(agentRequest);
826
690
  } else {
827
691
  agentResponse = await this.agent.processDwnRequest(agentRequest);
@@ -849,7 +713,7 @@ export class DwnApi {
849
713
  * to determine which DWN to send subsequent read requests to in the event the data
850
714
  * payload must be read again (e.g., if the data stream is consumed).
851
715
  */
852
- remoteOrigin : request.from,
716
+ remoteOrigin : from,
853
717
  delegateDid : this.delegateDid,
854
718
  data : entry.data,
855
719
  initialWrite : entry.initialWrite,
@@ -870,12 +734,14 @@ export class DwnApi {
870
734
  * typed change events (`create`, `update`, `delete`).
871
735
  */
872
736
  subscribe: async (request: RecordsSubscribeRequest): Promise<RecordsSubscribeResponse> => {
737
+ const { from, ...messageParams } = request;
738
+
873
739
  // Build a DWN-level subscription handler that wraps raw RecordEvents
874
740
  // into Record objects and feeds them into the LiveQuery.
875
741
  let liveQuery: LiveQuery | undefined;
876
742
 
877
- const remoteOrigin = request.from;
878
- const protocolRole = request.message.protocolRole;
743
+ const remoteOrigin = from;
744
+ const protocolRole = messageParams.protocolRole;
879
745
 
880
746
  type RecordEvent = {
881
747
  message: DwnMessage[DwnInterface.RecordsWrite];
@@ -898,10 +764,10 @@ export class DwnApi {
898
764
  };
899
765
 
900
766
  const agentRequest: ProcessDwnRequest<DwnInterface.RecordsSubscribe> = {
901
- author : this.connectedDid,
902
- messageParams : request.message,
903
- messageType : DwnInterface.RecordsSubscribe,
904
- target : request.from || this.connectedDid,
767
+ author : this.connectedDid,
768
+ messageParams,
769
+ messageType : DwnInterface.RecordsSubscribe,
770
+ target : from || this.connectedDid,
905
771
  subscriptionHandler,
906
772
  };
907
773
 
@@ -909,14 +775,13 @@ export class DwnApi {
909
775
  // if we don't find a delegated grant, we will attempt to subscribe signing as the delegated DID
910
776
  // This is to allow the API caller to subscribe to public records without needing to impersonate the delegate.
911
777
  //
912
- // NOTE: When a read-only DwnApi is implemented, callers should use that instead when they don't have an explicit permission.
913
- // This should fail if a permission is not found.
914
- // 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
915
780
  try {
916
781
  const { message: delegatedGrant } = await this.permissionsApi.getPermissionForRequest({
917
782
  connectedDid : this.connectedDid,
918
783
  delegateDid : this.delegateDid,
919
- protocol : request.protocol,
784
+ protocol : messageParams.filter?.protocol,
920
785
  delegate : true,
921
786
  cached : true,
922
787
  messageType : agentRequest.messageType
@@ -935,14 +800,14 @@ export class DwnApi {
935
800
 
936
801
  let agentResponse: DwnResponse<DwnInterface.RecordsSubscribe>;
937
802
 
938
- if (request.from) {
803
+ if (from) {
939
804
  agentResponse = await this.agent.sendDwnRequest(agentRequest);
940
805
  } else {
941
806
  agentResponse = await this.agent.processDwnRequest(agentRequest);
942
807
  }
943
808
 
944
809
  const reply = agentResponse.reply;
945
- const { status, subscription, entries = [] } = reply;
810
+ const { status, subscription, entries = [], cursor } = reply;
946
811
 
947
812
  if (subscription) {
948
813
  liveQuery = new LiveQuery({
@@ -953,6 +818,7 @@ export class DwnApi {
953
818
  remoteOrigin,
954
819
  permissionsApi : this.permissionsApi,
955
820
  initialEntries : entries,
821
+ cursor,
956
822
  subscription,
957
823
  });
958
824
  }
@@ -970,19 +836,19 @@ export class DwnApi {
970
836
  * requires fetching from the DWN datastore.
971
837
  */
972
838
  write: async (request: RecordsWriteRequest): Promise<RecordsWriteResponse> => {
973
- 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 };
974
843
 
975
844
  const dwnRequestParams: ProcessDwnRequest<DwnInterface.RecordsWrite> = {
976
- store : request.store,
977
- messageType : DwnInterface.RecordsWrite,
978
- messageParams : {
979
- ...request.message,
980
- dataFormat
981
- },
982
- author : this.connectedDid,
983
- target : this.connectedDid,
984
- dataStream : dataBlob,
985
- encryption : request.encryption,
845
+ store,
846
+ messageType : DwnInterface.RecordsWrite,
847
+ messageParams,
848
+ author : this.connectedDid,
849
+ target : this.connectedDid,
850
+ dataStream : dataBlob,
851
+ encryption,
986
852
  };
987
853
 
988
854
  // if impersonation is enabled, fetch the delegated grant to use with the write operation
@@ -990,7 +856,7 @@ export class DwnApi {
990
856
  const { message: delegatedGrant } = await this.permissionsApi.getPermissionForRequest({
991
857
  connectedDid : this.connectedDid,
992
858
  delegateDid : this.delegateDid,
993
- protocol : request.message.protocol,
859
+ protocol : messageParams.protocol,
994
860
  delegate : true,
995
861
  cached : true,
996
862
  messageType : dwnRequestParams.messageType