@enbox/api 0.3.2 → 0.4.1

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 (102) hide show
  1. package/README.md +63 -0
  2. package/dist/browser.mjs +11 -28
  3. package/dist/browser.mjs.map +4 -4
  4. package/dist/esm/advanced.js +1 -1
  5. package/dist/esm/define-protocol.js +3 -3
  6. package/dist/esm/did-api.js +1 -1
  7. package/dist/esm/did-api.js.map +1 -1
  8. package/dist/esm/dwn-api.js +6 -6
  9. package/dist/esm/dwn-api.js.map +1 -1
  10. package/dist/esm/dwn-reader-api.js +2 -2
  11. package/dist/esm/enbox.js +205 -0
  12. package/dist/esm/enbox.js.map +1 -0
  13. package/dist/esm/index.js +16 -15
  14. package/dist/esm/index.js.map +1 -1
  15. package/dist/esm/protocol.js +2 -2
  16. package/dist/esm/protocol.js.map +1 -1
  17. package/dist/esm/record-data.js +79 -5
  18. package/dist/esm/record-data.js.map +1 -1
  19. package/dist/esm/record.js +49 -10
  20. package/dist/esm/record.js.map +1 -1
  21. package/dist/esm/repository.js +7 -7
  22. package/dist/esm/repository.js.map +1 -1
  23. package/dist/esm/typed-enbox.js +583 -0
  24. package/dist/esm/typed-enbox.js.map +1 -0
  25. package/dist/esm/typed-live-query.js +1 -1
  26. package/dist/esm/typed-record.js +370 -46
  27. package/dist/esm/typed-record.js.map +1 -1
  28. package/dist/esm/utils.js +25 -0
  29. package/dist/esm/utils.js.map +1 -1
  30. package/dist/esm/vc-api.js.map +1 -1
  31. package/dist/types/advanced.d.ts +1 -1
  32. package/dist/types/define-protocol.d.ts +3 -3
  33. package/dist/types/did-api.d.ts +4 -4
  34. package/dist/types/did-api.d.ts.map +1 -1
  35. package/dist/types/dwn-api.d.ts +12 -7
  36. package/dist/types/dwn-api.d.ts.map +1 -1
  37. package/dist/types/dwn-reader-api.d.ts +2 -2
  38. package/dist/types/enbox.d.ts +202 -0
  39. package/dist/types/enbox.d.ts.map +1 -0
  40. package/dist/types/grant-revocation.d.ts +2 -2
  41. package/dist/types/grant-revocation.d.ts.map +1 -1
  42. package/dist/types/index.d.ts +16 -15
  43. package/dist/types/index.d.ts.map +1 -1
  44. package/dist/types/live-query.d.ts +2 -2
  45. package/dist/types/live-query.d.ts.map +1 -1
  46. package/dist/types/permission-grant.d.ts +2 -2
  47. package/dist/types/permission-grant.d.ts.map +1 -1
  48. package/dist/types/permission-request.d.ts +2 -2
  49. package/dist/types/permission-request.d.ts.map +1 -1
  50. package/dist/types/protocol-types.d.ts +2 -2
  51. package/dist/types/protocol.d.ts +7 -7
  52. package/dist/types/protocol.d.ts.map +1 -1
  53. package/dist/types/record-data.d.ts +17 -0
  54. package/dist/types/record-data.d.ts.map +1 -1
  55. package/dist/types/record.d.ts +24 -10
  56. package/dist/types/record.d.ts.map +1 -1
  57. package/dist/types/repository-types.d.ts +19 -11
  58. package/dist/types/repository-types.d.ts.map +1 -1
  59. package/dist/types/repository.d.ts +7 -7
  60. package/dist/types/repository.d.ts.map +1 -1
  61. package/dist/types/typed-enbox.d.ts +613 -0
  62. package/dist/types/typed-enbox.d.ts.map +1 -0
  63. package/dist/types/typed-live-query.d.ts +1 -1
  64. package/dist/types/typed-record.d.ts +427 -53
  65. package/dist/types/typed-record.d.ts.map +1 -1
  66. package/dist/types/utils.d.ts +23 -0
  67. package/dist/types/utils.d.ts.map +1 -1
  68. package/dist/types/vc-api.d.ts +3 -3
  69. package/dist/types/vc-api.d.ts.map +1 -1
  70. package/package.json +12 -11
  71. package/src/advanced.ts +1 -1
  72. package/src/define-protocol.ts +3 -3
  73. package/src/did-api.ts +5 -5
  74. package/src/dwn-api.ts +22 -17
  75. package/src/dwn-reader-api.ts +2 -2
  76. package/src/enbox.ts +281 -0
  77. package/src/grant-revocation.ts +3 -3
  78. package/src/index.ts +17 -16
  79. package/src/live-query.ts +2 -2
  80. package/src/permission-grant.ts +4 -4
  81. package/src/permission-request.ts +3 -3
  82. package/src/protocol-types.ts +2 -2
  83. package/src/protocol.ts +8 -8
  84. package/src/record-data.ts +86 -5
  85. package/src/record.ts +54 -13
  86. package/src/repository-types.ts +19 -7
  87. package/src/repository.ts +15 -15
  88. package/src/typed-enbox.ts +1169 -0
  89. package/src/typed-live-query.ts +1 -1
  90. package/src/typed-record.ts +431 -53
  91. package/src/utils.ts +27 -0
  92. package/src/vc-api.ts +4 -4
  93. package/dist/esm/typed-web5.js +0 -339
  94. package/dist/esm/typed-web5.js.map +0 -1
  95. package/dist/esm/web5.js +0 -410
  96. package/dist/esm/web5.js.map +0 -1
  97. package/dist/types/typed-web5.d.ts +0 -221
  98. package/dist/types/typed-web5.d.ts.map +0 -1
  99. package/dist/types/web5.d.ts +0 -351
  100. package/dist/types/web5.d.ts.map +0 -1
  101. package/src/typed-web5.ts +0 -598
  102. package/src/web5.ts +0 -762
@@ -3,8 +3,8 @@ import type {
3
3
  DwnPermissionConditions,
4
4
  DwnPermissionScope,
5
5
  DwnResponseStatus,
6
+ EnboxAgent,
6
7
  SendDwnRequest,
7
- Web5Agent,
8
8
  } from '@enbox/agent';
9
9
 
10
10
  import { Convert } from '@enbox/common';
@@ -86,7 +86,7 @@ export class PermissionRequest implements PermissionRequestModel {
86
86
  /** parses the request given an agent, connectedDid and data encoded records write message */
87
87
  static parse({ connectedDid, agent, message }:{
88
88
  connectedDid: string;
89
- agent: Web5Agent;
89
+ agent: EnboxAgent;
90
90
  message: DwnDataEncodedRecordsWriteMessage;
91
91
  }): PermissionRequest {
92
92
  const request = DwnPermissionRequest.parse(message);
@@ -95,7 +95,7 @@ export class PermissionRequest implements PermissionRequestModel {
95
95
  }
96
96
 
97
97
  /** The agent to use for this instantiation of the request */
98
- private get agent(): Web5Agent {
98
+ private get agent(): EnboxAgent {
99
99
  return this._permissions.agent;
100
100
  }
101
101
 
@@ -133,7 +133,7 @@ export type TagKeys<Tags extends ProtocolTagsDefinition> = Exclude<
133
133
  /**
134
134
  * A mapping from protocol type names to their TypeScript data shapes.
135
135
  *
136
- * Used as a type parameter to `defineProtocol()` and `TypedWeb5` so that
136
+ * Used as a type parameter to `defineProtocol()` and `TypedEnbox` so that
137
137
  * the protocol definition JSON stays JSON-compatible while TypeScript types
138
138
  * are tracked separately.
139
139
  *
@@ -154,7 +154,7 @@ export type SchemaMap = Record<string, unknown>;
154
154
  /**
155
155
  * The return type of `defineProtocol()`. Bundles the raw protocol definition
156
156
  * with its inferred path strings and schema type map for downstream use
157
- * by `TypedWeb5`.
157
+ * by `TypedEnbox`.
158
158
  */
159
159
  export type TypedProtocol<
160
160
  D extends ProtocolDefinition = ProtocolDefinition,
package/src/protocol.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
  /// <reference types="@enbox/dwn-sdk-js" />
6
6
 
7
- import type { DwnMessage, DwnResponseStatus, Web5Agent } from '@enbox/agent';
7
+ import type { DwnMessage, DwnResponseStatus, EnboxAgent } from '@enbox/agent';
8
8
 
9
9
  import { DwnInterface } from '@enbox/agent';
10
10
 
@@ -26,27 +26,27 @@ export type ProtocolMetadata = {
26
26
  /**
27
27
  * Encapsulates a DWN Protocol with its associated metadata and configuration.
28
28
  *
29
- * This class primarly exists to provide developers with a convenient way to configure/install
29
+ * This class primarily exists to provide developers with a convenient way to configure/install
30
30
  * protocols on remote DWNs.
31
31
  */
32
32
  export class Protocol {
33
- /** The {@link Web5Agent} instance that handles DWNs requests. */
34
- private _agent: Web5Agent;
33
+ /** The {@link EnboxAgent} instance that handles DWNs requests. */
34
+ private _agent: EnboxAgent;
35
35
 
36
- /** The ProtocolsConfigureMessage containing the detailed configuration for the protocol. */
36
+ /** Metadata associated with the protocol, including the author and optional message CID. */
37
37
  private _metadata: ProtocolMetadata;
38
38
 
39
- /** Metadata associated with the protocol, including the author and optional message CID. */
39
+ /** The ProtocolsConfigureMessage containing the detailed configuration for the protocol. */
40
40
  private _protocolsConfigureMessage: DwnMessage[DwnInterface.ProtocolsConfigure];
41
41
 
42
42
  /**
43
43
  * Constructs a new instance of the Protocol class.
44
44
  *
45
- * @param agent - The Web5Agent instance used for network interactions.
45
+ * @param agent - The EnboxAgent instance used for network interactions.
46
46
  * @param protocolsConfigureMessage - The configuration message containing the protocol details.
47
47
  * @param metadata - Metadata associated with the protocol, including the author and optional message CID.
48
48
  */
49
- constructor(agent: Web5Agent, protocolsConfigureMessage: DwnMessage[DwnInterface.ProtocolsConfigure], metadata: ProtocolMetadata) {
49
+ constructor(agent: EnboxAgent, protocolsConfigureMessage: DwnMessage[DwnInterface.ProtocolsConfigure], metadata: ProtocolMetadata) {
50
50
  this._agent = agent;
51
51
  this._metadata = metadata;
52
52
  this._protocolsConfigureMessage = protocolsConfigureMessage;
@@ -5,6 +5,10 @@
5
5
  * evaluated `stream()` function with convenience accessors that mirror the
6
6
  * Fetch `Response` API (`blob()`, `bytes()`, `json()`, `text()`).
7
7
  *
8
+ * Data is **cached after first read** so that subsequent calls to any accessor
9
+ * return the same data without re-fetching. Concurrent calls are safe — only
10
+ * one fetch is performed and all callers share the result.
11
+ *
8
12
  * Extracted from `record.ts` so the convenience-method boilerplate lives in
9
13
  * its own module while the stream-resolution logic (which is tightly coupled
10
14
  * to `Record` internals) stays inside the `Record` class.
@@ -14,6 +18,13 @@
14
18
 
15
19
  import { Stream } from '@enbox/common';
16
20
 
21
+ /**
22
+ * Maximum data size (in bytes) that will be cached in-memory after the first
23
+ * read. Payloads larger than this threshold are not cached and will be
24
+ * re-fetched on every access.
25
+ */
26
+ const DATA_CACHE_LIMIT = 10 * 1024 * 1024; // 10 MB
27
+
17
28
  /**
18
29
  * A thenable data accessor returned by {@link Record.data}.
19
30
  *
@@ -21,6 +32,10 @@ import { Stream } from '@enbox/common';
21
32
  * formats, plus `then`/`catch` so the object can be awaited directly to
22
33
  * obtain the underlying `ReadableStream`.
23
34
  *
35
+ * Data is cached after the first read so that repeated calls (e.g.,
36
+ * `data.json()` followed by `data.text()`) do not trigger redundant
37
+ * network requests.
38
+ *
24
39
  * @beta
25
40
  */
26
41
  export type RecordData = {
@@ -46,6 +61,15 @@ export type RecordData = {
46
61
  /**
47
62
  * Create a {@link RecordData} wrapper around a `stream` provider function.
48
63
  *
64
+ * The first call to any accessor (`blob`, `bytes`, `json`, `text`, `stream`)
65
+ * consumes the underlying stream and caches the raw bytes (up to
66
+ * {@link DATA_CACHE_LIMIT}). Subsequent calls reconstruct a fresh stream
67
+ * from the cache, preventing the common footgun of stale data after stream
68
+ * consumption.
69
+ *
70
+ * Concurrent calls are safe: if multiple accessors are called simultaneously,
71
+ * only one stream fetch is performed and all callers share the result.
72
+ *
49
73
  * @param streamFn - A function that returns a `Promise<ReadableStream>` for the record data.
50
74
  * @param dataFormat - The MIME type used when constructing Blobs.
51
75
  * @returns A {@link RecordData} object with convenience accessors.
@@ -53,6 +77,46 @@ export type RecordData = {
53
77
  * @beta
54
78
  */
55
79
  export function createRecordData(streamFn: () => Promise<ReadableStream>, dataFormat: string | undefined): RecordData {
80
+ // In-memory byte cache. Populated after the first successful read.
81
+ let cachedBytes: Uint8Array | undefined;
82
+ // In-flight read promise. Ensures concurrent callers share a single fetch.
83
+ let inflight: Promise<Uint8Array> | undefined;
84
+
85
+ /**
86
+ * Returns the record data as raw bytes, using the cache when available.
87
+ * If a read is already in-flight, waits for it instead of starting a new one.
88
+ */
89
+ async function getBytes(): Promise<Uint8Array> {
90
+ // Fast path: cache hit.
91
+ if (cachedBytes) {
92
+ return cachedBytes;
93
+ }
94
+
95
+ // If another call is already fetching, wait for it.
96
+ if (inflight) {
97
+ return inflight;
98
+ }
99
+
100
+ // Start a new fetch and share the promise.
101
+ inflight = (async (): Promise<Uint8Array> => {
102
+ const readableStream = await streamFn();
103
+ const bytes = await Stream.consumeToBytes({ readableStream });
104
+
105
+ // Cache only if within the size limit.
106
+ if (bytes.byteLength <= DATA_CACHE_LIMIT) {
107
+ cachedBytes = bytes;
108
+ }
109
+
110
+ return bytes;
111
+ })();
112
+
113
+ try {
114
+ return await inflight;
115
+ } finally {
116
+ inflight = undefined;
117
+ }
118
+ }
119
+
56
120
  const dataObj: RecordData = {
57
121
 
58
122
  /**
@@ -64,7 +128,7 @@ export function createRecordData(streamFn: () => Promise<ReadableStream>, dataFo
64
128
  * @beta
65
129
  */
66
130
  async blob(): Promise<Blob> {
67
- return new Blob([await Stream.consumeToBytes({ readableStream: await this.stream() })], { type: dataFormat });
131
+ return new Blob([await getBytes()], { type: dataFormat });
68
132
  },
69
133
 
70
134
  /**
@@ -76,7 +140,7 @@ export function createRecordData(streamFn: () => Promise<ReadableStream>, dataFo
76
140
  * @beta
77
141
  */
78
142
  async bytes(): Promise<Uint8Array> {
79
- return await Stream.consumeToBytes({ readableStream: await this.stream() });
143
+ return await getBytes();
80
144
  },
81
145
 
82
146
  /**
@@ -88,7 +152,8 @@ export function createRecordData(streamFn: () => Promise<ReadableStream>, dataFo
88
152
  * @beta
89
153
  */
90
154
  async json<T = unknown>(): Promise<T> {
91
- return await Stream.consumeToJson({ readableStream: await this.stream() }) as T;
155
+ const bytes = await getBytes();
156
+ return JSON.parse(new TextDecoder().decode(bytes)) as T;
92
157
  },
93
158
 
94
159
  /**
@@ -100,7 +165,8 @@ export function createRecordData(streamFn: () => Promise<ReadableStream>, dataFo
100
165
  * @beta
101
166
  */
102
167
  async text(): Promise<string> {
103
- return await Stream.consumeToText({ readableStream: await this.stream() });
168
+ const bytes = await getBytes();
169
+ return new TextDecoder().decode(bytes);
104
170
  },
105
171
 
106
172
  /**
@@ -109,12 +175,27 @@ export function createRecordData(streamFn: () => Promise<ReadableStream>, dataFo
109
175
  * Uses the standard Web Streams API for cross-platform compatibility across
110
176
  * browsers, Node.js, Bun, and Deno.
111
177
  *
178
+ * If the data has already been read and cached, returns a fresh stream
179
+ * reconstructed from the cache.
180
+ *
112
181
  * @returns A promise that resolves to a Web `ReadableStream` of the record's data.
113
182
  * @throws If the record data is not available in-memory and cannot be fetched.
114
183
  *
115
184
  * @beta
116
185
  */
117
- stream: streamFn,
186
+ async stream(): Promise<ReadableStream> {
187
+ // If we have cached bytes, reconstruct a fresh stream from them.
188
+ if (cachedBytes) {
189
+ return new ReadableStream({
190
+ start(controller): void {
191
+ controller.enqueue(cachedBytes);
192
+ controller.close();
193
+ },
194
+ });
195
+ }
196
+ // Otherwise delegate to the original stream provider.
197
+ return streamFn();
198
+ },
118
199
 
119
200
  /**
120
201
  * Attaches callbacks for the resolution and/or rejection of the `Promise` returned by
package/src/record.ts CHANGED
@@ -11,10 +11,10 @@ import type {
11
11
  DwnMessageParams,
12
12
  DwnPaginationCursor,
13
13
  DwnResponseStatus,
14
+ EnboxAgent,
14
15
  PermissionsApi,
15
16
  ProcessDwnRequest,
16
17
  SendDwnRequest,
17
- Web5Agent,
18
18
  } from '@enbox/agent';
19
19
 
20
20
  import type {
@@ -94,8 +94,8 @@ export class Record implements RecordModel {
94
94
 
95
95
  // Record instance metadata.
96
96
 
97
- /** The {@link Web5Agent} instance that handles DWNs requests. */
98
- private _agent: Web5Agent;
97
+ /** The {@link EnboxAgent} instance that handles DWNs requests. */
98
+ private _agent: EnboxAgent;
99
99
  /** The DID of the DWN tenant under which operations are being performed. */
100
100
  private _connectedDid: string;
101
101
  /** The optional DID that is delegated to act on behalf of the connectedDid */
@@ -215,10 +215,10 @@ export class Record implements RecordModel {
215
215
  /** Record's encryption */
216
216
  get encryption(): DwnMessage[DwnInterface.RecordsWrite]['encryption'] { return this._encryption; }
217
217
 
218
- /** Record's signatures attestation */
218
+ /** Record's authorization signature(s). */
219
219
  get authorization(): DwnMessage[DwnInterface.RecordsWrite | DwnInterface.RecordsDelete]['authorization'] { return this._authorization; }
220
220
 
221
- /** Record's signatures attestation */
221
+ /** Record's attestation signature. */
222
222
  get attestation(): DwnMessage[DwnInterface.RecordsWrite]['attestation'] | undefined { return this._attestation; }
223
223
 
224
224
  /** Role under which the author is writing the record */
@@ -264,7 +264,7 @@ export class Record implements RecordModel {
264
264
  return message;
265
265
  }
266
266
 
267
- constructor(agent: Web5Agent, options: RecordOptions, permissionsApi?: PermissionsApi) {
267
+ constructor(agent: EnboxAgent, options: RecordOptions, permissionsApi?: PermissionsApi) {
268
268
 
269
269
  this._agent = agent;
270
270
 
@@ -324,7 +324,7 @@ export class Record implements RecordModel {
324
324
  get data(): RecordData {
325
325
  return createRecordData(async (): Promise<ReadableStream> => {
326
326
  if (this.deleted) {
327
- throw new Error('404: Not Found');
327
+ throw new Error('Cannot access data of a deleted record.');
328
328
  }
329
329
 
330
330
  if (this._encodedData) {
@@ -381,15 +381,18 @@ export class Record implements RecordModel {
381
381
  }
382
382
 
383
383
  /**
384
- * Send the current record to a remote DWN by specifying their DID
384
+ * Send the current record to a remote DWN by specifying their DID.
385
385
  * If no DID is specified, the target is assumed to be the owner (connectedDID).
386
386
  *
387
- * If an initial write is present and the Record class send cache has no awareness of it, the initial write is sent first
388
- * (vs waiting for the regular DWN sync)
387
+ * If the record is in a deleted state, a `RecordsDelete` message is sent
388
+ * so the remote DWN reflects the deletion.
389
+ *
390
+ * If an initial write is present and the Record class send cache has no
391
+ * awareness of it, the initial write is sent first (vs waiting for the
392
+ * regular DWN sync).
389
393
  *
390
394
  * @param target - the optional DID to send the record to, if none is set it is sent to the connectedDid
391
395
  * @returns the status of the send record request
392
- * @throws `Error` if the record has already been deleted.
393
396
  *
394
397
  * @beta
395
398
  */
@@ -506,8 +509,14 @@ export class Record implements RecordModel {
506
509
 
507
510
  /**
508
511
  * Update the current record on the DWN.
512
+ *
513
+ * On success, **both** a new `Record` instance is returned *and* the
514
+ * current instance (`this`) is mutated in-place to reflect the updated
515
+ * state. This means callers can safely continue using the original
516
+ * reference after an update without capturing the returned record.
517
+ *
509
518
  * @param params - Parameters to update the record.
510
- * @returns the status of the update request
519
+ * @returns the status of the update request and the updated Record
511
520
  * @throws `Error` if the record has already been deleted.
512
521
  *
513
522
  * @beta
@@ -600,11 +609,33 @@ export class Record implements RecordModel {
600
609
  ...responseMessage as DwnMessage[DwnInterface.RecordsWrite],
601
610
  }, this._permissionsApi);
602
611
 
612
+ // Also mutate *this* record's internal state so that the caller's
613
+ // original reference reflects the update without having to capture
614
+ // the returned record. This eliminates the common footgun where
615
+ // `await record.update({ data }); await record.data.json()` returns
616
+ // stale data because `update()` historically only returned a *new* Record.
617
+ const msg = responseMessage as DwnMessage[DwnInterface.RecordsWrite];
618
+ this._descriptor = msg.descriptor;
619
+ this._attestation = msg.attestation;
620
+ this._authorization = msg.authorization;
621
+ this._encryption = msg.encryption;
622
+ this._contextId = msg.contextId;
623
+ this._initialWrite = initialWrite;
624
+ this._protocolRole = protocolRole ?? this._protocolRole;
625
+ this._encodedData = data !== undefined ? dataBlob : this._encodedData;
626
+ this._readableStream = undefined; // Invalidate any consumed stream.
627
+ this._rawMessageDirty = true; // Force rawMessage cache rebuild.
628
+
603
629
  return { status, record: updatedRecord };
604
630
  }
605
631
 
606
632
  /**
607
633
  * Delete the current record on the DWN.
634
+ *
635
+ * On success, **both** a new `Record` instance is returned *and* the
636
+ * current instance (`this`) is mutated in-place to reflect the deleted
637
+ * state (the {@link Record.deleted | deleted} getter will return `true`).
638
+ *
608
639
  * @param params - Parameters to delete the record.
609
640
  * @returns the status and a new Record instance reflecting the deleted state
610
641
  */
@@ -675,6 +706,16 @@ export class Record implements RecordModel {
675
706
  ...message as DwnMessage[DwnInterface.RecordsDelete],
676
707
  }, this._permissionsApi);
677
708
 
709
+ // Also mutate *this* record so the caller's original reference reflects
710
+ // the deletion without having to capture the returned record.
711
+ const deleteMsg = message as DwnMessage[DwnInterface.RecordsDelete];
712
+ this._descriptor = deleteMsg.descriptor;
713
+ this._authorization = deleteMsg.authorization;
714
+ this._protocolRole = deleteParams?.protocolRole ?? this._protocolRole;
715
+ this._encodedData = undefined;
716
+ this._readableStream = undefined;
717
+ this._rawMessageDirty = true;
718
+
678
719
  return { status, record: deletedRecord };
679
720
  }
680
721
 
@@ -799,7 +840,7 @@ export class Record implements RecordModel {
799
840
  // When reading the data as a delegate, if we don't find a grant we will attempt to read it with the delegate DID as the author.
800
841
  // This allows users to read publicly available data without needing explicit grants.
801
842
  //
802
- // NOTE: For anonymous/public record data access, callers can use `ReadOnlyRecord` via `Web5.anonymous()`.
843
+ // NOTE: For anonymous/public record data access, callers can use `ReadOnlyRecord` via `Enbox.anonymous()`.
803
844
  // See: https://github.com/enboxorg/enbox/issues/898
804
845
  try {
805
846
  await this.applyDelegateGrant(readRequest);
@@ -18,7 +18,7 @@ import type {
18
18
  TypedCreateRequest,
19
19
  TypedQueryRequest,
20
20
  TypedSubscribeRequest,
21
- } from './typed-web5.js';
21
+ } from './typed-enbox.js';
22
22
  import type { DwnPaginationCursor, DwnResponseStatus } from '@enbox/agent';
23
23
  import type { ProtocolDefinition, ProtocolRuleSet } from '@enbox/dwn-sdk-js';
24
24
 
@@ -108,9 +108,12 @@ export type CollectionCRUD<
108
108
  M extends SchemaMap,
109
109
  Path extends string,
110
110
  > = {
111
- create(options: CollectionCreateOptions<D, M, Path>): Promise<DwnResponseStatus & { record: TypedRecord<DataAt<D, M, Path>> }>;
111
+ create(options: CollectionCreateOptions<D, M, Path>): Promise<
112
+ | (DwnResponseStatus & { record: TypedRecord<DataAt<D, M, Path>> })
113
+ | (DwnResponseStatus & { record: undefined })
114
+ >;
112
115
  query(options?: TypedQueryRequest): Promise<DwnResponseStatus & { records: TypedRecord<DataAt<D, M, Path>>[]; cursor?: DwnPaginationCursor }>;
113
- get(recordId: string): Promise<TypedRecord<DataAt<D, M, Path>>>;
116
+ get(recordId: string): Promise<TypedRecord<DataAt<D, M, Path>> | undefined>;
114
117
  delete(recordId: string): Promise<DwnResponseStatus>;
115
118
  subscribe(options?: TypedSubscribeRequest): Promise<TypedLiveQuery<DataAt<D, M, Path>> | undefined>;
116
119
  };
@@ -121,7 +124,10 @@ export type SingletonCRUD<
121
124
  M extends SchemaMap,
122
125
  Path extends string,
123
126
  > = {
124
- set(options: SingletonSetOptions<D, M, Path>): Promise<DwnResponseStatus & { record: TypedRecord<DataAt<D, M, Path>> }>;
127
+ set(options: SingletonSetOptions<D, M, Path>): Promise<
128
+ | (DwnResponseStatus & { record: TypedRecord<DataAt<D, M, Path>> })
129
+ | (DwnResponseStatus & { record: undefined })
130
+ >;
125
131
  get(): Promise<TypedRecord<DataAt<D, M, Path>> | undefined>;
126
132
  delete(recordId: string): Promise<DwnResponseStatus>;
127
133
  };
@@ -135,12 +141,15 @@ export type NestedCollectionCRUD<
135
141
  create(
136
142
  parentContextId: string,
137
143
  options: CollectionCreateOptions<D, M, Path>,
138
- ): Promise<DwnResponseStatus & { record: TypedRecord<DataAt<D, M, Path>> }>;
144
+ ): Promise<
145
+ | (DwnResponseStatus & { record: TypedRecord<DataAt<D, M, Path>> })
146
+ | (DwnResponseStatus & { record: undefined })
147
+ >;
139
148
  query(
140
149
  parentContextId: string,
141
150
  options?: TypedQueryRequest,
142
151
  ): Promise<DwnResponseStatus & { records: TypedRecord<DataAt<D, M, Path>>[]; cursor?: DwnPaginationCursor }>;
143
- get(recordId: string): Promise<TypedRecord<DataAt<D, M, Path>>>;
152
+ get(recordId: string): Promise<TypedRecord<DataAt<D, M, Path>> | undefined>;
144
153
  delete(recordId: string): Promise<DwnResponseStatus>;
145
154
  subscribe(
146
155
  parentContextId: string,
@@ -157,7 +166,10 @@ export type NestedSingletonCRUD<
157
166
  set(
158
167
  parentContextId: string,
159
168
  options: SingletonSetOptions<D, M, Path>,
160
- ): Promise<DwnResponseStatus & { record: TypedRecord<DataAt<D, M, Path>> }>;
169
+ ): Promise<
170
+ | (DwnResponseStatus & { record: TypedRecord<DataAt<D, M, Path>> })
171
+ | (DwnResponseStatus & { record: undefined })
172
+ >;
161
173
  get(parentContextId: string): Promise<TypedRecord<DataAt<D, M, Path>> | undefined>;
162
174
  delete(recordId: string): Promise<DwnResponseStatus>;
163
175
  };
package/src/repository.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Protocol-aware repository factory.
3
3
  *
4
- * `repository()` takes a `TypedWeb5` instance and returns a Proxy-backed
4
+ * `repository()` takes a `TypedEnbox` instance and returns a Proxy-backed
5
5
  * object whose shape mirrors the protocol's `structure` tree with
6
6
  * ergonomic CRUD methods on each node.
7
7
  *
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * @example
14
14
  * ```ts
15
- * const social = repository(web5.using(SocialGraphProtocol));
15
+ * const social = repository(enbox.using(SocialGraphProtocol));
16
16
  * await social.configure();
17
17
  *
18
18
  * // Root collection
@@ -30,7 +30,7 @@
30
30
  import type { DwnResponseStatus } from '@enbox/agent';
31
31
  import type { Repository } from './repository-types.js';
32
32
  import type { SchemaMap } from './protocol-types.js';
33
- import type { TypedWeb5 } from './typed-web5.js';
33
+ import type { TypedEnbox } from './typed-enbox.js';
34
34
  import type { ProtocolDefinition, ProtocolRuleSet } from '@enbox/dwn-sdk-js';
35
35
 
36
36
  // ---------------------------------------------------------------------------
@@ -92,7 +92,7 @@ function getChildKeys(definition: ProtocolDefinition, path: string): string[] {
92
92
  * Build collection CRUD methods for a root-level path.
93
93
  */
94
94
  function buildRootCollectionMethods(
95
- typed: TypedWeb5<ProtocolDefinition, SchemaMap>,
95
+ typed: TypedEnbox<ProtocolDefinition, SchemaMap>,
96
96
  path: string,
97
97
  ): Record<string, Function> {
98
98
  return {
@@ -110,7 +110,7 @@ function buildRootCollectionMethods(
110
110
  const { record } = await typed.records.read(path, {
111
111
  filter: { recordId },
112
112
  });
113
- return record;
113
+ return record; // undefined when the read fails (e.g. 404)
114
114
  },
115
115
 
116
116
  async delete(recordId: string): Promise<DwnResponseStatus> {
@@ -133,7 +133,7 @@ function buildRootCollectionMethods(
133
133
  * in query-then-create/update.
134
134
  */
135
135
  function buildRootSingletonMethods(
136
- typed: TypedWeb5<ProtocolDefinition, SchemaMap>,
136
+ typed: TypedEnbox<ProtocolDefinition, SchemaMap>,
137
137
  path: string,
138
138
  ): Record<string, Function> {
139
139
  return {
@@ -171,7 +171,7 @@ function buildRootSingletonMethods(
171
171
  * Build collection CRUD methods for a nested path.
172
172
  */
173
173
  function buildNestedCollectionMethods(
174
- typed: TypedWeb5<ProtocolDefinition, SchemaMap>,
174
+ typed: TypedEnbox<ProtocolDefinition, SchemaMap>,
175
175
  path: string,
176
176
  ): Record<string, Function> {
177
177
  return {
@@ -198,7 +198,7 @@ function buildNestedCollectionMethods(
198
198
  const { record } = await typed.records.read(path, {
199
199
  filter: { recordId },
200
200
  });
201
- return record;
201
+ return record; // undefined when the read fails (e.g. 404)
202
202
  },
203
203
 
204
204
  async delete(recordId: string): Promise<DwnResponseStatus> {
@@ -225,7 +225,7 @@ function buildNestedCollectionMethods(
225
225
  * conditions between concurrent set() calls.
226
226
  */
227
227
  function buildNestedSingletonMethods(
228
- typed: TypedWeb5<ProtocolDefinition, SchemaMap>,
228
+ typed: TypedEnbox<ProtocolDefinition, SchemaMap>,
229
229
  path: string,
230
230
  ): Record<string, Function> {
231
231
  return {
@@ -276,7 +276,7 @@ function buildNestedSingletonMethods(
276
276
  * and Proxy-based child nodes.
277
277
  */
278
278
  function buildNode(
279
- typed: TypedWeb5<ProtocolDefinition, SchemaMap>,
279
+ typed: TypedEnbox<ProtocolDefinition, SchemaMap>,
280
280
  definition: ProtocolDefinition,
281
281
  path: string,
282
282
  isNested: boolean,
@@ -324,7 +324,7 @@ function buildNode(
324
324
  // ---------------------------------------------------------------------------
325
325
 
326
326
  /**
327
- * Creates a protocol-aware repository from a `TypedWeb5` instance.
327
+ * Creates a protocol-aware repository from a `TypedEnbox` instance.
328
328
  *
329
329
  * The returned object provides domain-specific CRUD methods that mirror
330
330
  * the protocol's structure tree:
@@ -334,12 +334,12 @@ function buildNode(
334
334
  * - Singletons: `repo.profile.set()`, `repo.profile.get()`
335
335
  * - Protocol install: `repo.configure()`
336
336
  *
337
- * @param typed - A `TypedWeb5` instance from `web5.using(protocol)`.
337
+ * @param typed - A `TypedEnbox` instance from `enbox.using(protocol)`.
338
338
  * @returns A typed repository object.
339
339
  *
340
340
  * @example
341
341
  * ```ts
342
- * const social = repository(web5.using(SocialGraphProtocol));
342
+ * const social = repository(enbox.using(SocialGraphProtocol));
343
343
  * await social.configure();
344
344
  *
345
345
  * const rec = await social.friend.create({
@@ -351,7 +351,7 @@ function buildNode(
351
351
  export function repository<
352
352
  D extends ProtocolDefinition,
353
353
  M extends SchemaMap,
354
- >(typed: TypedWeb5<D, M>): Repository<D, M> {
354
+ >(typed: TypedEnbox<D, M>): Repository<D, M> {
355
355
  const definition = typed.definition as ProtocolDefinition;
356
356
 
357
357
  // Get root-level type keys from the structure
@@ -374,7 +374,7 @@ export function repository<
374
374
  if (rootKeys.includes(prop)) {
375
375
  if (!(prop in nodeCache)) {
376
376
  nodeCache[prop] = buildNode(
377
- typed as unknown as TypedWeb5<ProtocolDefinition, SchemaMap>,
377
+ typed as unknown as TypedEnbox<ProtocolDefinition, SchemaMap>,
378
378
  definition,
379
379
  prop,
380
380
  false,