@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
package/src/typed-web5.ts
DELETED
|
@@ -1,598 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A protocol-scoped API returned by {@link Web5.using}.
|
|
3
|
-
*
|
|
4
|
-
* `TypedWeb5` is the **primary developer interface** for interacting with
|
|
5
|
-
* protocol-backed records. It auto-injects the protocol URI, protocolPath,
|
|
6
|
-
* and schema into every operation, and provides compile-time path
|
|
7
|
-
* autocompletion plus typed data payloads via the schema map.
|
|
8
|
-
*
|
|
9
|
-
* All record-returning methods wrap the underlying `Record` instances in
|
|
10
|
-
* {@link TypedRecord} so that type information flows through reads, queries,
|
|
11
|
-
* updates, and subscriptions without manual casts.
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* ```ts
|
|
15
|
-
* const social = web5.using(SocialProtocol);
|
|
16
|
-
*
|
|
17
|
-
* // Install the protocol
|
|
18
|
-
* await social.configure();
|
|
19
|
-
*
|
|
20
|
-
* // Create — path and data type are checked at compile time
|
|
21
|
-
* const { record } = await social.records.create('thread', {
|
|
22
|
-
* data: { title: 'Hello World', body: '...' },
|
|
23
|
-
* });
|
|
24
|
-
* // record is TypedRecord<ThreadData>
|
|
25
|
-
*
|
|
26
|
-
* const data = await record.data.json(); // ThreadData — no cast needed
|
|
27
|
-
*
|
|
28
|
-
* // Query — protocol and protocolPath are auto-injected
|
|
29
|
-
* const { records } = await social.records.query('thread');
|
|
30
|
-
* // records is TypedRecord<ThreadData>[]
|
|
31
|
-
*
|
|
32
|
-
* // Subscribe — real-time changes via TypedLiveQuery
|
|
33
|
-
* const { liveQuery } = await social.records.subscribe('thread/reply');
|
|
34
|
-
* liveQuery.on('create', (record) => {
|
|
35
|
-
* // record is TypedRecord<ReplyData>
|
|
36
|
-
* });
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
|
|
40
|
-
import type { DwnApi } from './dwn-api.js';
|
|
41
|
-
import type { Protocol } from './protocol.js';
|
|
42
|
-
|
|
43
|
-
import type { DateSort, ProtocolDefinition, ProtocolType, RecordsFilter } from '@enbox/dwn-sdk-js';
|
|
44
|
-
import type { DwnPaginationCursor, DwnResponseStatus } from '@enbox/agent';
|
|
45
|
-
import type { ProtocolPaths, SchemaMap, TypedProtocol, TypeNameAtPath } from './protocol-types.js';
|
|
46
|
-
|
|
47
|
-
import { TypedLiveQuery } from './typed-live-query.js';
|
|
48
|
-
import { TypedRecord } from './typed-record.js';
|
|
49
|
-
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
// Helper types
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Resolves the TypeScript data type for a given protocol path.
|
|
56
|
-
*
|
|
57
|
-
* If the schema map contains a mapping for the type name at the given path,
|
|
58
|
-
* that type is returned. Otherwise falls back to `unknown`.
|
|
59
|
-
*/
|
|
60
|
-
export type DataForPath<
|
|
61
|
-
_D extends ProtocolDefinition,
|
|
62
|
-
M extends SchemaMap,
|
|
63
|
-
Path extends string,
|
|
64
|
-
> = TypeNameAtPath<Path> extends keyof M ? M[TypeNameAtPath<Path>] : unknown;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Resolves the `ProtocolType` entry for a given protocol path.
|
|
68
|
-
*/
|
|
69
|
-
type ProtocolTypeForPath<
|
|
70
|
-
D extends ProtocolDefinition,
|
|
71
|
-
Path extends string,
|
|
72
|
-
> = TypeNameAtPath<Path> extends keyof D['types']
|
|
73
|
-
? D['types'][TypeNameAtPath<Path>] extends ProtocolType
|
|
74
|
-
? D['types'][TypeNameAtPath<Path>]
|
|
75
|
-
: undefined
|
|
76
|
-
: undefined;
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Resolves a `dataFormat` string literal union for a path, or `string` if none.
|
|
80
|
-
*/
|
|
81
|
-
type DataFormatForPath<
|
|
82
|
-
D extends ProtocolDefinition,
|
|
83
|
-
Path extends string,
|
|
84
|
-
> = ProtocolTypeForPath<D, Path> extends { dataFormats: infer F }
|
|
85
|
-
? F extends readonly string[]
|
|
86
|
-
? F[number]
|
|
87
|
-
: string
|
|
88
|
-
: string;
|
|
89
|
-
|
|
90
|
-
// ---------------------------------------------------------------------------
|
|
91
|
-
// Request / response types
|
|
92
|
-
// ---------------------------------------------------------------------------
|
|
93
|
-
|
|
94
|
-
/** Options for {@link TypedWeb5} `records.create()`. */
|
|
95
|
-
export type TypedCreateRequest<
|
|
96
|
-
D extends ProtocolDefinition,
|
|
97
|
-
M extends SchemaMap,
|
|
98
|
-
Path extends string,
|
|
99
|
-
> = {
|
|
100
|
-
/** The data payload. Type-checked against the schema map. */
|
|
101
|
-
data: DataForPath<D, M, Path>;
|
|
102
|
-
|
|
103
|
-
parentContextId?: string;
|
|
104
|
-
published?: boolean;
|
|
105
|
-
datePublished?: string;
|
|
106
|
-
recipient?: string;
|
|
107
|
-
protocolRole?: string;
|
|
108
|
-
dataFormat?: DataFormatForPath<D, Path>;
|
|
109
|
-
tags?: globalThis.Record<string, string | number | boolean | string[] | number[]>;
|
|
110
|
-
|
|
111
|
-
/** Whether to persist immediately (defaults to `true`). */
|
|
112
|
-
store?: boolean;
|
|
113
|
-
|
|
114
|
-
/** Whether to auto-encrypt (follows protocol definition if omitted). */
|
|
115
|
-
encryption?: boolean;
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
/** Response from {@link TypedWeb5} `records.create()`. */
|
|
119
|
-
export type TypedCreateResponse<T = unknown> = DwnResponseStatus & {
|
|
120
|
-
record: TypedRecord<T>;
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
/** Filter options for {@link TypedWeb5} `records.query()`. */
|
|
124
|
-
export type TypedQueryFilter = Omit<RecordsFilter, 'protocol' | 'protocolPath' | 'schema'> & {
|
|
125
|
-
tags?: globalThis.Record<string, string | number | boolean | (string | number)[]>;
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
/** Options for {@link TypedWeb5} `records.query()`. */
|
|
129
|
-
export type TypedQueryRequest = {
|
|
130
|
-
/** Optional remote DWN DID to query from. */
|
|
131
|
-
from?: string;
|
|
132
|
-
|
|
133
|
-
/** Query filter (protocol, protocolPath, schema are injected). */
|
|
134
|
-
filter?: TypedQueryFilter;
|
|
135
|
-
dateSort?: DateSort;
|
|
136
|
-
pagination?: { limit?: number; cursor?: DwnPaginationCursor };
|
|
137
|
-
protocolRole?: string;
|
|
138
|
-
|
|
139
|
-
/** When true, automatically decrypts encrypted records. */
|
|
140
|
-
encryption?: boolean;
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
/** Response from {@link TypedWeb5} `records.query()`. */
|
|
144
|
-
export type TypedQueryResponse<T = unknown> = DwnResponseStatus & {
|
|
145
|
-
records: TypedRecord<T>[];
|
|
146
|
-
cursor?: DwnPaginationCursor;
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
/** Options for {@link TypedWeb5} `records.read()`. */
|
|
150
|
-
export type TypedReadRequest = {
|
|
151
|
-
/** Optional remote DWN DID to read from. */
|
|
152
|
-
from?: string;
|
|
153
|
-
|
|
154
|
-
/** Filter to identify the record (protocol and protocolPath are injected). */
|
|
155
|
-
filter: Omit<RecordsFilter, 'protocol' | 'protocolPath' | 'schema'>;
|
|
156
|
-
|
|
157
|
-
/** When true, automatically decrypts the record. */
|
|
158
|
-
encryption?: boolean;
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
/** Response from {@link TypedWeb5} `records.read()`. */
|
|
162
|
-
export type TypedReadResponse<T = unknown> = DwnResponseStatus & {
|
|
163
|
-
record: TypedRecord<T>;
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
/** Options for {@link TypedWeb5} `records.delete()`. */
|
|
167
|
-
export type TypedDeleteRequest = {
|
|
168
|
-
/** Optional remote DWN DID to delete from. */
|
|
169
|
-
from?: string;
|
|
170
|
-
|
|
171
|
-
/** The `recordId` of the record to delete. */
|
|
172
|
-
recordId: string;
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
/** Options for {@link TypedWeb5} `records.subscribe()`. */
|
|
176
|
-
export type TypedSubscribeRequest = {
|
|
177
|
-
/** Optional remote DWN DID to subscribe to. */
|
|
178
|
-
from?: string;
|
|
179
|
-
|
|
180
|
-
/** Subscription filter (protocol, protocolPath, schema are injected). */
|
|
181
|
-
filter?: TypedQueryFilter;
|
|
182
|
-
protocolRole?: string;
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
/** Response from {@link TypedWeb5} `records.subscribe()`. */
|
|
186
|
-
export type TypedSubscribeResponse<T = unknown> = DwnResponseStatus & {
|
|
187
|
-
/** The typed live query instance, or `undefined` if the request failed. */
|
|
188
|
-
liveQuery?: TypedLiveQuery<T>;
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
// ---------------------------------------------------------------------------
|
|
192
|
-
// TypedWeb5 class
|
|
193
|
-
// ---------------------------------------------------------------------------
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* A protocol-scoped API that auto-injects `protocol`, `protocolPath`, and
|
|
197
|
-
* `schema` into every DWN operation.
|
|
198
|
-
*
|
|
199
|
-
* All record-returning methods wrap results in {@link TypedRecord} so that
|
|
200
|
-
* the data type `T` (resolved from the schema map) flows end-to-end — from
|
|
201
|
-
* write through read, query, update, and subscribe — without manual casts.
|
|
202
|
-
*
|
|
203
|
-
* Obtain an instance via `web5.using(typedProtocol)`.
|
|
204
|
-
*
|
|
205
|
-
* @example
|
|
206
|
-
* ```ts
|
|
207
|
-
* const social = web5.using(SocialProtocol);
|
|
208
|
-
*
|
|
209
|
-
* await social.configure();
|
|
210
|
-
*
|
|
211
|
-
* const { record } = await social.records.create('friend', {
|
|
212
|
-
* data: { did: 'did:example:alice', alias: 'Alice' },
|
|
213
|
-
* });
|
|
214
|
-
* const data = await record.data.json(); // FriendData — no cast
|
|
215
|
-
*
|
|
216
|
-
* const { records } = await social.records.query('friend', {
|
|
217
|
-
* filter: { tags: { did: 'did:example:alice' } },
|
|
218
|
-
* });
|
|
219
|
-
* for (const r of records) {
|
|
220
|
-
* const d = await r.data.json(); // FriendData
|
|
221
|
-
* }
|
|
222
|
-
* ```
|
|
223
|
-
*/
|
|
224
|
-
export class TypedWeb5<
|
|
225
|
-
D extends ProtocolDefinition = ProtocolDefinition,
|
|
226
|
-
M extends SchemaMap = SchemaMap,
|
|
227
|
-
> {
|
|
228
|
-
private _dwn: DwnApi;
|
|
229
|
-
private _definition: D;
|
|
230
|
-
private _configured: boolean = false;
|
|
231
|
-
private _validPaths: Set<string>;
|
|
232
|
-
private _records?: TypedWeb5<D, M>['records'];
|
|
233
|
-
|
|
234
|
-
constructor(dwn: DwnApi, protocol: TypedProtocol<D, M>) {
|
|
235
|
-
this._dwn = dwn;
|
|
236
|
-
this._definition = protocol.definition;
|
|
237
|
-
this._validPaths = collectPaths(this._definition.structure);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/** The protocol URI. */
|
|
241
|
-
public get protocol(): string {
|
|
242
|
-
return this._definition.protocol;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/** The raw protocol definition. */
|
|
246
|
-
public get definition(): D {
|
|
247
|
-
return this._definition;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Configures (installs) this protocol on the local DWN.
|
|
252
|
-
*
|
|
253
|
-
* If the protocol is already installed with an identical definition,
|
|
254
|
-
* this is a no-op and returns the existing protocol. If the definition
|
|
255
|
-
* has changed (e.g. new types, modified structure), the protocol is
|
|
256
|
-
* re-configured with the updated definition.
|
|
257
|
-
*
|
|
258
|
-
* @param options - Optional overrides like `encryption`.
|
|
259
|
-
*/
|
|
260
|
-
public async configure(options?: { encryption?: boolean }): Promise<DwnResponseStatus & { protocol?: Protocol }> {
|
|
261
|
-
// Query for an existing installation of this protocol.
|
|
262
|
-
const { protocols } = await this._dwn.protocols.query({
|
|
263
|
-
filter: { protocol: this._definition.protocol },
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
// If already installed with the same definition, return it as-is.
|
|
267
|
-
if (protocols.length > 0) {
|
|
268
|
-
const existing = protocols[0];
|
|
269
|
-
if (definitionsEqual(existing.definition, this._definition)) {
|
|
270
|
-
this._configured = true;
|
|
271
|
-
return { status: { code: 200, detail: 'OK' }, protocol: existing };
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
// Not installed or definition has changed — configure the new version.
|
|
276
|
-
const result = await this._dwn.protocols.configure({
|
|
277
|
-
definition : this._definition,
|
|
278
|
-
encryption : options?.encryption,
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
if (result.status.code === 202) {
|
|
282
|
-
this._configured = true;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
return result;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/** Whether the protocol has been configured (installed) on the local DWN. */
|
|
289
|
-
public get isConfigured(): boolean {
|
|
290
|
-
return this._configured;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/**
|
|
294
|
-
* Validates that the protocol has been configured and that the path is
|
|
295
|
-
* recognized. Throws a descriptive error if either check fails.
|
|
296
|
-
*/
|
|
297
|
-
private _assertReady(path: string): void {
|
|
298
|
-
if (!this._configured) {
|
|
299
|
-
throw new Error(
|
|
300
|
-
`TypedWeb5: protocol '${this._definition.protocol}' has not been configured. ` +
|
|
301
|
-
'Call configure() before performing record operations.',
|
|
302
|
-
);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (!this._validPaths.has(path)) {
|
|
306
|
-
throw new Error(
|
|
307
|
-
`TypedWeb5: invalid protocol path '${path}'. ` +
|
|
308
|
-
`Valid paths are: ${[...this._validPaths].join(', ')}.`,
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Protocol-scoped record operations.
|
|
315
|
-
*
|
|
316
|
-
* Every method auto-injects the protocol URI, protocolPath, and schema
|
|
317
|
-
* from the protocol definition. Path parameters provide compile-time
|
|
318
|
-
* autocompletion via `ProtocolPaths<D>`.
|
|
319
|
-
*
|
|
320
|
-
* All methods return {@link TypedRecord} or {@link TypedLiveQuery} instances
|
|
321
|
-
* that carry the resolved data type from the schema map.
|
|
322
|
-
*/
|
|
323
|
-
public get records(): {
|
|
324
|
-
create: <Path extends ProtocolPaths<D> & string>(
|
|
325
|
-
path: Path,
|
|
326
|
-
request: TypedCreateRequest<D, M, Path>,
|
|
327
|
-
) => Promise<TypedCreateResponse<DataForPath<D, M, Path>>>;
|
|
328
|
-
|
|
329
|
-
query: <Path extends ProtocolPaths<D> & string>(
|
|
330
|
-
path: Path,
|
|
331
|
-
request?: TypedQueryRequest,
|
|
332
|
-
) => Promise<TypedQueryResponse<DataForPath<D, M, Path>>>;
|
|
333
|
-
|
|
334
|
-
read: <Path extends ProtocolPaths<D> & string>(
|
|
335
|
-
path: Path,
|
|
336
|
-
request: TypedReadRequest,
|
|
337
|
-
) => Promise<TypedReadResponse<DataForPath<D, M, Path>>>;
|
|
338
|
-
|
|
339
|
-
delete: <Path extends ProtocolPaths<D> & string>(
|
|
340
|
-
path: Path,
|
|
341
|
-
request: TypedDeleteRequest,
|
|
342
|
-
) => Promise<DwnResponseStatus>;
|
|
343
|
-
|
|
344
|
-
subscribe: <Path extends ProtocolPaths<D> & string>(
|
|
345
|
-
path: Path,
|
|
346
|
-
request?: TypedSubscribeRequest,
|
|
347
|
-
) => Promise<TypedSubscribeResponse<DataForPath<D, M, Path>>>;
|
|
348
|
-
} {
|
|
349
|
-
if (this._records !== undefined) {
|
|
350
|
-
return this._records;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
const cached = {
|
|
354
|
-
/**
|
|
355
|
-
* Create a new record at the given protocol path.
|
|
356
|
-
*
|
|
357
|
-
* @param path - The protocol path (e.g. `'friend'`, `'group/member'`).
|
|
358
|
-
* @param request - Create options including typed `data`.
|
|
359
|
-
*/
|
|
360
|
-
create: async <Path extends ProtocolPaths<D> & string>(
|
|
361
|
-
path: Path,
|
|
362
|
-
request: TypedCreateRequest<D, M, Path>,
|
|
363
|
-
): Promise<TypedCreateResponse<DataForPath<D, M, Path>>> => {
|
|
364
|
-
const normalizedPath = normalizePath(path);
|
|
365
|
-
this._assertReady(normalizedPath);
|
|
366
|
-
const typeName = lastSegment(normalizedPath);
|
|
367
|
-
const typeEntry = this._definition.types[typeName] as ProtocolType | undefined;
|
|
368
|
-
|
|
369
|
-
const { status, record } = await this._dwn.records.write({
|
|
370
|
-
data : request.data,
|
|
371
|
-
store : request.store,
|
|
372
|
-
encryption : request.encryption,
|
|
373
|
-
parentContextId : request.parentContextId,
|
|
374
|
-
published : request.published,
|
|
375
|
-
datePublished : request.datePublished,
|
|
376
|
-
recipient : request.recipient,
|
|
377
|
-
protocolRole : request.protocolRole,
|
|
378
|
-
tags : request.tags,
|
|
379
|
-
protocol : this._definition.protocol,
|
|
380
|
-
protocolPath : normalizedPath,
|
|
381
|
-
...(typeEntry?.schema !== undefined ? { schema: typeEntry.schema } : {}),
|
|
382
|
-
dataFormat : request.dataFormat ?? typeEntry?.dataFormats?.[0],
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
return {
|
|
386
|
-
status,
|
|
387
|
-
record: new TypedRecord<DataForPath<D, M, Path>>(record),
|
|
388
|
-
};
|
|
389
|
-
},
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* Query records at the given protocol path.
|
|
393
|
-
*
|
|
394
|
-
* @param path - The protocol path to query.
|
|
395
|
-
* @param request - Optional filter, sort, and pagination.
|
|
396
|
-
*/
|
|
397
|
-
query: async <Path extends ProtocolPaths<D> & string>(
|
|
398
|
-
path: Path,
|
|
399
|
-
request?: TypedQueryRequest,
|
|
400
|
-
): Promise<TypedQueryResponse<DataForPath<D, M, Path>>> => {
|
|
401
|
-
const normalizedPath = normalizePath(path);
|
|
402
|
-
this._assertReady(normalizedPath);
|
|
403
|
-
const typeName = lastSegment(normalizedPath);
|
|
404
|
-
const typeEntry = this._definition.types[typeName] as ProtocolType | undefined;
|
|
405
|
-
|
|
406
|
-
const { status, records, cursor } = await this._dwn.records.query({
|
|
407
|
-
from : request?.from,
|
|
408
|
-
encryption : request?.encryption,
|
|
409
|
-
filter : {
|
|
410
|
-
...request?.filter,
|
|
411
|
-
protocol : this._definition.protocol,
|
|
412
|
-
protocolPath : normalizedPath,
|
|
413
|
-
...(typeEntry?.schema !== undefined ? { schema: typeEntry.schema } : {}),
|
|
414
|
-
},
|
|
415
|
-
dateSort : request?.dateSort,
|
|
416
|
-
pagination : request?.pagination,
|
|
417
|
-
protocolRole : request?.protocolRole,
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
return {
|
|
421
|
-
status,
|
|
422
|
-
records: records.map((r) => new TypedRecord<DataForPath<D, M, Path>>(r)),
|
|
423
|
-
cursor,
|
|
424
|
-
};
|
|
425
|
-
},
|
|
426
|
-
|
|
427
|
-
/**
|
|
428
|
-
* Read a single record at the given protocol path.
|
|
429
|
-
*
|
|
430
|
-
* @param path - The protocol path to read from.
|
|
431
|
-
* @param request - Read options including a filter to identify the record.
|
|
432
|
-
*/
|
|
433
|
-
read: async <Path extends ProtocolPaths<D> & string>(
|
|
434
|
-
path: Path,
|
|
435
|
-
request: TypedReadRequest,
|
|
436
|
-
): Promise<TypedReadResponse<DataForPath<D, M, Path>>> => {
|
|
437
|
-
const normalizedPath = normalizePath(path);
|
|
438
|
-
this._assertReady(normalizedPath);
|
|
439
|
-
const typeName = lastSegment(normalizedPath);
|
|
440
|
-
const typeEntry = this._definition.types[typeName] as ProtocolType | undefined;
|
|
441
|
-
|
|
442
|
-
const { status, record } = await this._dwn.records.read({
|
|
443
|
-
from : request.from,
|
|
444
|
-
encryption : request.encryption,
|
|
445
|
-
protocol : this._definition.protocol,
|
|
446
|
-
filter : {
|
|
447
|
-
...request.filter,
|
|
448
|
-
protocol : this._definition.protocol,
|
|
449
|
-
protocolPath : normalizedPath,
|
|
450
|
-
...(typeEntry?.schema !== undefined ? { schema: typeEntry.schema } : {}),
|
|
451
|
-
},
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
return {
|
|
455
|
-
status,
|
|
456
|
-
record: new TypedRecord<DataForPath<D, M, Path>>(record),
|
|
457
|
-
};
|
|
458
|
-
},
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Delete a record at the given protocol path.
|
|
462
|
-
*
|
|
463
|
-
* @param path - The protocol path (used for permission scoping).
|
|
464
|
-
* @param request - Delete options including the `recordId`.
|
|
465
|
-
*/
|
|
466
|
-
delete: async <Path extends ProtocolPaths<D> & string>(
|
|
467
|
-
_path: Path,
|
|
468
|
-
request: TypedDeleteRequest,
|
|
469
|
-
): Promise<DwnResponseStatus> => {
|
|
470
|
-
this._assertReady(normalizePath(_path));
|
|
471
|
-
return this._dwn.records.delete({
|
|
472
|
-
from : request.from,
|
|
473
|
-
protocol : this._definition.protocol,
|
|
474
|
-
recordId : request.recordId,
|
|
475
|
-
});
|
|
476
|
-
},
|
|
477
|
-
|
|
478
|
-
/**
|
|
479
|
-
* Subscribe to records at the given protocol path.
|
|
480
|
-
*
|
|
481
|
-
* Returns a {@link TypedLiveQuery} that atomically provides an initial
|
|
482
|
-
* snapshot and a real-time stream of deduplicated change events, with
|
|
483
|
-
* all records typed as `TypedRecord<T>`.
|
|
484
|
-
*
|
|
485
|
-
* @param path - The protocol path to subscribe to.
|
|
486
|
-
* @param request - Optional filter and role.
|
|
487
|
-
*/
|
|
488
|
-
subscribe: async <Path extends ProtocolPaths<D> & string>(
|
|
489
|
-
path: Path,
|
|
490
|
-
request?: TypedSubscribeRequest,
|
|
491
|
-
): Promise<TypedSubscribeResponse<DataForPath<D, M, Path>>> => {
|
|
492
|
-
const normalizedPath = normalizePath(path);
|
|
493
|
-
this._assertReady(normalizedPath);
|
|
494
|
-
const typeName = lastSegment(normalizedPath);
|
|
495
|
-
const typeEntry = this._definition.types[typeName] as ProtocolType | undefined;
|
|
496
|
-
|
|
497
|
-
const { status, liveQuery } = await this._dwn.records.subscribe({
|
|
498
|
-
from : request?.from,
|
|
499
|
-
filter : {
|
|
500
|
-
...request?.filter,
|
|
501
|
-
protocol : this._definition.protocol,
|
|
502
|
-
protocolPath : normalizedPath,
|
|
503
|
-
...(typeEntry?.schema !== undefined ? { schema: typeEntry.schema } : {}),
|
|
504
|
-
},
|
|
505
|
-
protocolRole: request?.protocolRole,
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
return {
|
|
509
|
-
status,
|
|
510
|
-
liveQuery: liveQuery ? new TypedLiveQuery<DataForPath<D, M, Path>>(liveQuery) : undefined,
|
|
511
|
-
};
|
|
512
|
-
},
|
|
513
|
-
};
|
|
514
|
-
|
|
515
|
-
this._records = cached;
|
|
516
|
-
return cached;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// ---------------------------------------------------------------------------
|
|
521
|
-
// Helpers
|
|
522
|
-
// ---------------------------------------------------------------------------
|
|
523
|
-
|
|
524
|
-
/**
|
|
525
|
-
* Compares two protocol definitions for deep equality using deterministic
|
|
526
|
-
* JSON serialization.
|
|
527
|
-
*
|
|
528
|
-
* Keys are sorted recursively so that semantically identical definitions
|
|
529
|
-
* with different key ordering are treated as equal.
|
|
530
|
-
*/
|
|
531
|
-
function definitionsEqual(a: unknown, b: unknown): boolean {
|
|
532
|
-
return stableStringify(a) === stableStringify(b);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* Strips leading and trailing slashes from a path.
|
|
537
|
-
*
|
|
538
|
-
* `'friend/'` → `'friend'`, `'/group/member/'` → `'group/member'`.
|
|
539
|
-
*/
|
|
540
|
-
function normalizePath(path: string): string {
|
|
541
|
-
return path.replace(/^\/+|\/+$/g, '');
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
/**
|
|
545
|
-
* Returns the last segment of a slash-delimited path.
|
|
546
|
-
*/
|
|
547
|
-
function lastSegment(path: string): string {
|
|
548
|
-
const parts = path.split('/');
|
|
549
|
-
return parts[parts.length - 1];
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Recursively collects all valid protocol path strings from a structure object.
|
|
554
|
-
*
|
|
555
|
-
* Given `{ foo: { bar: { $actions: [...] } } }`, returns `Set(['foo', 'foo/bar'])`.
|
|
556
|
-
* Keys starting with `$` are skipped.
|
|
557
|
-
*/
|
|
558
|
-
function collectPaths(
|
|
559
|
-
structure: Record<string, unknown>,
|
|
560
|
-
prefix: string = '',
|
|
561
|
-
): Set<string> {
|
|
562
|
-
const paths = new Set<string>();
|
|
563
|
-
|
|
564
|
-
for (const key of Object.keys(structure)) {
|
|
565
|
-
if (key.startsWith('$')) { continue; }
|
|
566
|
-
|
|
567
|
-
const fullPath = prefix ? `${prefix}/${key}` : key;
|
|
568
|
-
paths.add(fullPath);
|
|
569
|
-
|
|
570
|
-
const child = structure[key];
|
|
571
|
-
if (child !== null && typeof child === 'object') {
|
|
572
|
-
for (const nested of collectPaths(child as Record<string, unknown>, fullPath)) {
|
|
573
|
-
paths.add(nested);
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
return paths;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
/**
|
|
582
|
-
* Deterministic JSON serialization with sorted keys.
|
|
583
|
-
*/
|
|
584
|
-
function stableStringify(value: unknown): string {
|
|
585
|
-
if (value === null || value === undefined || typeof value !== 'object') {
|
|
586
|
-
return JSON.stringify(value);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
if (Array.isArray(value)) {
|
|
590
|
-
return '[' + value.map((item) => stableStringify(item)).join(',') + ']';
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
const keys = Object.keys(value as globalThis.Record<string, unknown>).sort();
|
|
594
|
-
const pairs = keys.map((key) =>
|
|
595
|
-
JSON.stringify(key) + ':' + stableStringify((value as globalThis.Record<string, unknown>)[key])
|
|
596
|
-
);
|
|
597
|
-
return '{' + pairs.join(',') + '}';
|
|
598
|
-
}
|