@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.
- package/README.md +63 -0
- package/dist/browser.mjs +11 -28
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/advanced.js +1 -1
- package/dist/esm/define-protocol.js +3 -3
- package/dist/esm/did-api.js +1 -1
- package/dist/esm/did-api.js.map +1 -1
- package/dist/esm/dwn-api.js +6 -6
- package/dist/esm/dwn-api.js.map +1 -1
- package/dist/esm/dwn-reader-api.js +2 -2
- package/dist/esm/enbox.js +205 -0
- package/dist/esm/enbox.js.map +1 -0
- package/dist/esm/index.js +16 -15
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/protocol.js +2 -2
- package/dist/esm/protocol.js.map +1 -1
- package/dist/esm/record-data.js +79 -5
- package/dist/esm/record-data.js.map +1 -1
- package/dist/esm/record.js +49 -10
- package/dist/esm/record.js.map +1 -1
- package/dist/esm/repository.js +7 -7
- package/dist/esm/repository.js.map +1 -1
- package/dist/esm/typed-enbox.js +583 -0
- package/dist/esm/typed-enbox.js.map +1 -0
- package/dist/esm/typed-live-query.js +1 -1
- package/dist/esm/typed-record.js +370 -46
- package/dist/esm/typed-record.js.map +1 -1
- package/dist/esm/utils.js +25 -0
- package/dist/esm/utils.js.map +1 -1
- package/dist/esm/vc-api.js.map +1 -1
- package/dist/types/advanced.d.ts +1 -1
- package/dist/types/define-protocol.d.ts +3 -3
- package/dist/types/did-api.d.ts +4 -4
- package/dist/types/did-api.d.ts.map +1 -1
- package/dist/types/dwn-api.d.ts +12 -7
- package/dist/types/dwn-api.d.ts.map +1 -1
- package/dist/types/dwn-reader-api.d.ts +2 -2
- package/dist/types/enbox.d.ts +202 -0
- package/dist/types/enbox.d.ts.map +1 -0
- package/dist/types/grant-revocation.d.ts +2 -2
- package/dist/types/grant-revocation.d.ts.map +1 -1
- package/dist/types/index.d.ts +16 -15
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/live-query.d.ts +2 -2
- package/dist/types/live-query.d.ts.map +1 -1
- package/dist/types/permission-grant.d.ts +2 -2
- package/dist/types/permission-grant.d.ts.map +1 -1
- package/dist/types/permission-request.d.ts +2 -2
- package/dist/types/permission-request.d.ts.map +1 -1
- package/dist/types/protocol-types.d.ts +2 -2
- package/dist/types/protocol.d.ts +7 -7
- package/dist/types/protocol.d.ts.map +1 -1
- package/dist/types/record-data.d.ts +17 -0
- package/dist/types/record-data.d.ts.map +1 -1
- package/dist/types/record.d.ts +24 -10
- package/dist/types/record.d.ts.map +1 -1
- package/dist/types/repository-types.d.ts +19 -11
- package/dist/types/repository-types.d.ts.map +1 -1
- package/dist/types/repository.d.ts +7 -7
- package/dist/types/repository.d.ts.map +1 -1
- package/dist/types/typed-enbox.d.ts +613 -0
- package/dist/types/typed-enbox.d.ts.map +1 -0
- package/dist/types/typed-live-query.d.ts +1 -1
- package/dist/types/typed-record.d.ts +427 -53
- package/dist/types/typed-record.d.ts.map +1 -1
- package/dist/types/utils.d.ts +23 -0
- package/dist/types/utils.d.ts.map +1 -1
- package/dist/types/vc-api.d.ts +3 -3
- package/dist/types/vc-api.d.ts.map +1 -1
- package/package.json +12 -11
- package/src/advanced.ts +1 -1
- package/src/define-protocol.ts +3 -3
- package/src/did-api.ts +5 -5
- package/src/dwn-api.ts +22 -17
- package/src/dwn-reader-api.ts +2 -2
- package/src/enbox.ts +281 -0
- package/src/grant-revocation.ts +3 -3
- package/src/index.ts +17 -16
- package/src/live-query.ts +2 -2
- package/src/permission-grant.ts +4 -4
- package/src/permission-request.ts +3 -3
- package/src/protocol-types.ts +2 -2
- package/src/protocol.ts +8 -8
- package/src/record-data.ts +86 -5
- package/src/record.ts +54 -13
- package/src/repository-types.ts +19 -7
- package/src/repository.ts +15 -15
- package/src/typed-enbox.ts +1169 -0
- package/src/typed-live-query.ts +1 -1
- package/src/typed-record.ts +431 -53
- package/src/utils.ts +27 -0
- package/src/vc-api.ts +4 -4
- package/dist/esm/typed-web5.js +0 -339
- package/dist/esm/typed-web5.js.map +0 -1
- package/dist/esm/web5.js +0 -410
- package/dist/esm/web5.js.map +0 -1
- package/dist/types/typed-web5.d.ts +0 -221
- package/dist/types/typed-web5.d.ts.map +0 -1
- package/dist/types/web5.d.ts +0 -351
- package/dist/types/web5.d.ts.map +0 -1
- package/src/typed-web5.ts +0 -598
- 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:
|
|
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():
|
|
98
|
+
private get agent(): EnboxAgent {
|
|
99
99
|
return this._permissions.agent;
|
|
100
100
|
}
|
|
101
101
|
|
package/src/protocol-types.ts
CHANGED
|
@@ -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 `
|
|
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 `
|
|
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,
|
|
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
|
|
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
|
|
34
|
-
private _agent:
|
|
33
|
+
/** The {@link EnboxAgent} instance that handles DWNs requests. */
|
|
34
|
+
private _agent: EnboxAgent;
|
|
35
35
|
|
|
36
|
-
/**
|
|
36
|
+
/** Metadata associated with the protocol, including the author and optional message CID. */
|
|
37
37
|
private _metadata: ProtocolMetadata;
|
|
38
38
|
|
|
39
|
-
/**
|
|
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
|
|
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:
|
|
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;
|
package/src/record-data.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
98
|
-
private _agent:
|
|
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
|
|
218
|
+
/** Record's authorization signature(s). */
|
|
219
219
|
get authorization(): DwnMessage[DwnInterface.RecordsWrite | DwnInterface.RecordsDelete]['authorization'] { return this._authorization; }
|
|
220
220
|
|
|
221
|
-
/** Record's
|
|
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:
|
|
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('
|
|
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
|
|
388
|
-
*
|
|
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 `
|
|
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);
|
package/src/repository-types.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type {
|
|
|
18
18
|
TypedCreateRequest,
|
|
19
19
|
TypedQueryRequest,
|
|
20
20
|
TypedSubscribeRequest,
|
|
21
|
-
} from './typed-
|
|
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<
|
|
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<
|
|
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<
|
|
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<
|
|
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 `
|
|
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(
|
|
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 {
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 `
|
|
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 `
|
|
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(
|
|
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:
|
|
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
|
|
377
|
+
typed as unknown as TypedEnbox<ProtocolDefinition, SchemaMap>,
|
|
378
378
|
definition,
|
|
379
379
|
prop,
|
|
380
380
|
false,
|