@enbox/api 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +140 -159
- package/dist/browser.mjs +23 -13
- package/dist/browser.mjs.map +4 -4
- package/dist/esm/advanced.js +11 -0
- package/dist/esm/advanced.js.map +1 -0
- package/dist/esm/define-protocol.js +3 -3
- package/dist/esm/dwn-api.js +55 -107
- package/dist/esm/dwn-api.js.map +1 -1
- package/dist/esm/dwn-reader-api.js +128 -0
- package/dist/esm/dwn-reader-api.js.map +1 -0
- package/dist/esm/index.js +3 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/live-query.js +5 -4
- package/dist/esm/live-query.js.map +1 -1
- package/dist/esm/read-only-record.js +255 -0
- package/dist/esm/read-only-record.js.map +1 -0
- package/dist/esm/record.js +46 -57
- package/dist/esm/record.js.map +1 -1
- package/dist/esm/typed-web5.js +242 -0
- package/dist/esm/typed-web5.js.map +1 -0
- package/dist/esm/web5.js +71 -3
- package/dist/esm/web5.js.map +1 -1
- package/dist/types/advanced.d.ts +12 -0
- package/dist/types/advanced.d.ts.map +1 -0
- package/dist/types/define-protocol.d.ts +3 -3
- package/dist/types/dwn-api.d.ts +12 -89
- package/dist/types/dwn-api.d.ts.map +1 -1
- package/dist/types/dwn-reader-api.d.ts +138 -0
- package/dist/types/dwn-reader-api.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/live-query.d.ts +13 -1
- package/dist/types/live-query.d.ts.map +1 -1
- package/dist/types/protocol-types.d.ts +2 -2
- package/dist/types/read-only-record.d.ts +133 -0
- package/dist/types/read-only-record.d.ts.map +1 -0
- package/dist/types/record.d.ts +37 -17
- package/dist/types/record.d.ts.map +1 -1
- package/dist/types/{typed-dwn-api.d.ts → typed-web5.d.ts} +70 -73
- package/dist/types/typed-web5.d.ts.map +1 -0
- package/dist/types/web5.d.ts +79 -3
- package/dist/types/web5.d.ts.map +1 -1
- package/package.json +9 -5
- package/src/advanced.ts +29 -0
- package/src/define-protocol.ts +3 -3
- package/src/dwn-api.ts +88 -222
- package/src/dwn-reader-api.ts +255 -0
- package/src/index.ts +3 -2
- package/src/live-query.ts +20 -4
- package/src/protocol-types.ts +2 -2
- package/src/read-only-record.ts +328 -0
- package/src/record.ts +116 -86
- package/src/typed-web5.ts +445 -0
- package/src/web5.ts +104 -5
- package/dist/esm/typed-dwn-api.js +0 -182
- package/dist/esm/typed-dwn-api.js.map +0 -1
- package/dist/types/typed-dwn-api.d.ts.map +0 -1
- package/src/typed-dwn-api.ts +0 -370
|
@@ -0,0 +1,445 @@
|
|
|
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
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const social = web5.using(SocialProtocol);
|
|
12
|
+
*
|
|
13
|
+
* // Install the protocol
|
|
14
|
+
* await social.configure();
|
|
15
|
+
*
|
|
16
|
+
* // Write — path and data type are checked at compile time
|
|
17
|
+
* const { record } = await social.records.write('thread', {
|
|
18
|
+
* data: { title: 'Hello World', body: '...' },
|
|
19
|
+
* });
|
|
20
|
+
*
|
|
21
|
+
* // Query — protocol and protocolPath are auto-injected
|
|
22
|
+
* const { records } = await social.records.query('thread');
|
|
23
|
+
*
|
|
24
|
+
* // Subscribe — real-time changes via LiveQuery
|
|
25
|
+
* const { liveQuery } = await social.records.subscribe('thread/reply');
|
|
26
|
+
* liveQuery.on('create', (record) => { ... });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import type { DwnApi } from './dwn-api.js';
|
|
31
|
+
import type { LiveQuery } from './live-query.js';
|
|
32
|
+
import type { Protocol } from './protocol.js';
|
|
33
|
+
import type { Record } from './record.js';
|
|
34
|
+
|
|
35
|
+
import type { DateSort, ProtocolDefinition, ProtocolType, RecordsFilter } from '@enbox/dwn-sdk-js';
|
|
36
|
+
import type { DwnPaginationCursor, DwnResponseStatus } from '@enbox/agent';
|
|
37
|
+
import type { ProtocolPaths, SchemaMap, TypedProtocol, TypeNameAtPath } from './protocol-types.js';
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Helper types
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolves the TypeScript data type for a given protocol path.
|
|
45
|
+
*
|
|
46
|
+
* If the schema map contains a mapping for the type name at the given path,
|
|
47
|
+
* that type is returned. Otherwise falls back to `unknown`.
|
|
48
|
+
*/
|
|
49
|
+
type DataForPath<
|
|
50
|
+
_D extends ProtocolDefinition,
|
|
51
|
+
M extends SchemaMap,
|
|
52
|
+
Path extends string,
|
|
53
|
+
> = TypeNameAtPath<Path> extends keyof M ? M[TypeNameAtPath<Path>] : unknown;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resolves the `ProtocolType` entry for a given protocol path.
|
|
57
|
+
*/
|
|
58
|
+
type ProtocolTypeForPath<
|
|
59
|
+
D extends ProtocolDefinition,
|
|
60
|
+
Path extends string,
|
|
61
|
+
> = TypeNameAtPath<Path> extends keyof D['types']
|
|
62
|
+
? D['types'][TypeNameAtPath<Path>] extends ProtocolType
|
|
63
|
+
? D['types'][TypeNameAtPath<Path>]
|
|
64
|
+
: undefined
|
|
65
|
+
: undefined;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Resolves a `dataFormat` string literal union for a path, or `string` if none.
|
|
69
|
+
*/
|
|
70
|
+
type DataFormatForPath<
|
|
71
|
+
D extends ProtocolDefinition,
|
|
72
|
+
Path extends string,
|
|
73
|
+
> = ProtocolTypeForPath<D, Path> extends { dataFormats: infer F }
|
|
74
|
+
? F extends readonly string[]
|
|
75
|
+
? F[number]
|
|
76
|
+
: string
|
|
77
|
+
: string;
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Request / response types
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
/** Options for {@link TypedWeb5} `records.write()`. */
|
|
84
|
+
export type TypedWriteRequest<
|
|
85
|
+
D extends ProtocolDefinition,
|
|
86
|
+
M extends SchemaMap,
|
|
87
|
+
Path extends string,
|
|
88
|
+
> = {
|
|
89
|
+
/** The data payload. Type-checked against the schema map. */
|
|
90
|
+
data: DataForPath<D, M, Path>;
|
|
91
|
+
|
|
92
|
+
parentContextId?: string;
|
|
93
|
+
published?: boolean;
|
|
94
|
+
datePublished?: string;
|
|
95
|
+
recipient?: string;
|
|
96
|
+
protocolRole?: string;
|
|
97
|
+
dataFormat?: DataFormatForPath<D, Path>;
|
|
98
|
+
tags?: globalThis.Record<string, string | number | boolean | string[] | number[]>;
|
|
99
|
+
|
|
100
|
+
/** Whether to persist immediately (defaults to `true`). */
|
|
101
|
+
store?: boolean;
|
|
102
|
+
|
|
103
|
+
/** Whether to auto-encrypt (follows protocol definition if omitted). */
|
|
104
|
+
encryption?: boolean;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/** Response from {@link TypedWeb5} `records.write()`. */
|
|
108
|
+
export type TypedWriteResponse = DwnResponseStatus & {
|
|
109
|
+
record: Record;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/** Filter options for {@link TypedWeb5} `records.query()`. */
|
|
113
|
+
export type TypedQueryFilter = Omit<RecordsFilter, 'protocol' | 'protocolPath' | 'schema'> & {
|
|
114
|
+
tags?: globalThis.Record<string, string | number | boolean | (string | number)[]>;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/** Options for {@link TypedWeb5} `records.query()`. */
|
|
118
|
+
export type TypedQueryRequest = {
|
|
119
|
+
/** Optional remote DWN DID to query from. */
|
|
120
|
+
from?: string;
|
|
121
|
+
|
|
122
|
+
/** Query filter (protocol, protocolPath, schema are injected). */
|
|
123
|
+
filter?: TypedQueryFilter;
|
|
124
|
+
dateSort?: DateSort;
|
|
125
|
+
pagination?: { limit?: number; cursor?: DwnPaginationCursor };
|
|
126
|
+
protocolRole?: string;
|
|
127
|
+
|
|
128
|
+
/** When true, automatically decrypts encrypted records. */
|
|
129
|
+
encryption?: boolean;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/** Response from {@link TypedWeb5} `records.query()`. */
|
|
133
|
+
export type TypedQueryResponse = DwnResponseStatus & {
|
|
134
|
+
records: Record[];
|
|
135
|
+
cursor?: DwnPaginationCursor;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/** Options for {@link TypedWeb5} `records.read()`. */
|
|
139
|
+
export type TypedReadRequest = {
|
|
140
|
+
/** Optional remote DWN DID to read from. */
|
|
141
|
+
from?: string;
|
|
142
|
+
|
|
143
|
+
/** Filter to identify the record (protocol and protocolPath are injected). */
|
|
144
|
+
filter: Omit<RecordsFilter, 'protocol' | 'protocolPath' | 'schema'>;
|
|
145
|
+
|
|
146
|
+
/** When true, automatically decrypts the record. */
|
|
147
|
+
encryption?: boolean;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/** Response from {@link TypedWeb5} `records.read()`. */
|
|
151
|
+
export type TypedReadResponse = DwnResponseStatus & {
|
|
152
|
+
record: Record;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/** Options for {@link TypedWeb5} `records.delete()`. */
|
|
156
|
+
export type TypedDeleteRequest = {
|
|
157
|
+
/** Optional remote DWN DID to delete from. */
|
|
158
|
+
from?: string;
|
|
159
|
+
|
|
160
|
+
/** The `recordId` of the record to delete. */
|
|
161
|
+
recordId: string;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/** Options for {@link TypedWeb5} `records.subscribe()`. */
|
|
165
|
+
export type TypedSubscribeRequest = {
|
|
166
|
+
/** Optional remote DWN DID to subscribe to. */
|
|
167
|
+
from?: string;
|
|
168
|
+
|
|
169
|
+
/** Subscription filter (protocol, protocolPath, schema are injected). */
|
|
170
|
+
filter?: TypedQueryFilter;
|
|
171
|
+
protocolRole?: string;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
/** Response from {@link TypedWeb5} `records.subscribe()`. */
|
|
175
|
+
export type TypedSubscribeResponse = DwnResponseStatus & {
|
|
176
|
+
/** The live query instance, or `undefined` if the request failed. */
|
|
177
|
+
liveQuery?: LiveQuery;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// TypedWeb5 class
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* A protocol-scoped API that auto-injects `protocol`, `protocolPath`, and
|
|
186
|
+
* `schema` into every DWN operation.
|
|
187
|
+
*
|
|
188
|
+
* Obtain an instance via `web5.using(typedProtocol)`.
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```ts
|
|
192
|
+
* const social = web5.using(SocialProtocol);
|
|
193
|
+
*
|
|
194
|
+
* await social.configure();
|
|
195
|
+
*
|
|
196
|
+
* const { record } = await social.records.write('friend', {
|
|
197
|
+
* data: { did: 'did:example:alice', alias: 'Alice' },
|
|
198
|
+
* });
|
|
199
|
+
*
|
|
200
|
+
* const { records } = await social.records.query('friend', {
|
|
201
|
+
* filter: { tags: { did: 'did:example:alice' } },
|
|
202
|
+
* });
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
export class TypedWeb5<
|
|
206
|
+
D extends ProtocolDefinition = ProtocolDefinition,
|
|
207
|
+
M extends SchemaMap = SchemaMap,
|
|
208
|
+
> {
|
|
209
|
+
private _dwn: DwnApi;
|
|
210
|
+
private _definition: D;
|
|
211
|
+
|
|
212
|
+
constructor(dwn: DwnApi, protocol: TypedProtocol<D, M>) {
|
|
213
|
+
this._dwn = dwn;
|
|
214
|
+
this._definition = protocol.definition;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** The protocol URI. */
|
|
218
|
+
public get protocol(): string {
|
|
219
|
+
return this._definition.protocol;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** The raw protocol definition. */
|
|
223
|
+
public get definition(): D {
|
|
224
|
+
return this._definition;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Configures (installs) this protocol on the local DWN.
|
|
229
|
+
*
|
|
230
|
+
* If the protocol is already installed with an identical definition,
|
|
231
|
+
* this is a no-op and returns the existing protocol. If the definition
|
|
232
|
+
* has changed (e.g. new types, modified structure), the protocol is
|
|
233
|
+
* re-configured with the updated definition.
|
|
234
|
+
*
|
|
235
|
+
* @param options - Optional overrides like `encryption`.
|
|
236
|
+
*/
|
|
237
|
+
public async configure(options?: { encryption?: boolean }): Promise<DwnResponseStatus & { protocol?: Protocol }> {
|
|
238
|
+
// Query for an existing installation of this protocol.
|
|
239
|
+
const { protocols } = await this._dwn.protocols.query({
|
|
240
|
+
filter: { protocol: this._definition.protocol },
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// If already installed with the same definition, return it as-is.
|
|
244
|
+
if (protocols.length > 0) {
|
|
245
|
+
const existing = protocols[0];
|
|
246
|
+
if (definitionsEqual(existing.definition, this._definition)) {
|
|
247
|
+
return { status: { code: 200, detail: 'OK' }, protocol: existing };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Not installed or definition has changed — configure the new version.
|
|
252
|
+
return this._dwn.protocols.configure({
|
|
253
|
+
definition : this._definition,
|
|
254
|
+
encryption : options?.encryption,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Protocol-scoped record operations.
|
|
260
|
+
*
|
|
261
|
+
* Every method auto-injects the protocol URI, protocolPath, and schema
|
|
262
|
+
* from the protocol definition. Path parameters provide compile-time
|
|
263
|
+
* autocompletion via `ProtocolPaths<D>`.
|
|
264
|
+
*/
|
|
265
|
+
public get records(): {
|
|
266
|
+
write: <Path extends ProtocolPaths<D> & string>(path: Path, request: TypedWriteRequest<D, M, Path>) => Promise<TypedWriteResponse>;
|
|
267
|
+
query: <Path extends ProtocolPaths<D> & string>(path: Path, request?: TypedQueryRequest) => Promise<TypedQueryResponse>;
|
|
268
|
+
read: <Path extends ProtocolPaths<D> & string>(path: Path, request: TypedReadRequest) => Promise<TypedReadResponse>;
|
|
269
|
+
delete: <Path extends ProtocolPaths<D> & string>(path: Path, request: TypedDeleteRequest) => Promise<DwnResponseStatus>;
|
|
270
|
+
subscribe: <Path extends ProtocolPaths<D> & string>(path: Path, request?: TypedSubscribeRequest) => Promise<TypedSubscribeResponse>;
|
|
271
|
+
} {
|
|
272
|
+
return {
|
|
273
|
+
/**
|
|
274
|
+
* Write a record at the given protocol path.
|
|
275
|
+
*
|
|
276
|
+
* @param path - The protocol path (e.g. `'friend'`, `'group/member'`).
|
|
277
|
+
* @param request - Write options including typed `data`.
|
|
278
|
+
*/
|
|
279
|
+
write: async <Path extends ProtocolPaths<D> & string>(
|
|
280
|
+
path: Path,
|
|
281
|
+
request: TypedWriteRequest<D, M, Path>,
|
|
282
|
+
): Promise<TypedWriteResponse> => {
|
|
283
|
+
const typeName = lastSegment(path);
|
|
284
|
+
const typeEntry = this._definition.types[typeName] as ProtocolType | undefined;
|
|
285
|
+
|
|
286
|
+
return this._dwn.records.write({
|
|
287
|
+
data : request.data,
|
|
288
|
+
store : request.store,
|
|
289
|
+
encryption : request.encryption,
|
|
290
|
+
parentContextId : request.parentContextId,
|
|
291
|
+
published : request.published,
|
|
292
|
+
datePublished : request.datePublished,
|
|
293
|
+
recipient : request.recipient,
|
|
294
|
+
protocolRole : request.protocolRole,
|
|
295
|
+
tags : request.tags,
|
|
296
|
+
protocol : this._definition.protocol,
|
|
297
|
+
protocolPath : path,
|
|
298
|
+
schema : typeEntry?.schema,
|
|
299
|
+
dataFormat : request.dataFormat ?? typeEntry?.dataFormats?.[0],
|
|
300
|
+
});
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Query records at the given protocol path.
|
|
305
|
+
*
|
|
306
|
+
* @param path - The protocol path to query.
|
|
307
|
+
* @param request - Optional filter, sort, and pagination.
|
|
308
|
+
*/
|
|
309
|
+
query: async <Path extends ProtocolPaths<D> & string>(
|
|
310
|
+
path: Path,
|
|
311
|
+
request?: TypedQueryRequest,
|
|
312
|
+
): Promise<TypedQueryResponse> => {
|
|
313
|
+
const typeName = lastSegment(path);
|
|
314
|
+
const typeEntry = this._definition.types[typeName] as ProtocolType | undefined;
|
|
315
|
+
|
|
316
|
+
return this._dwn.records.query({
|
|
317
|
+
from : request?.from,
|
|
318
|
+
encryption : request?.encryption,
|
|
319
|
+
filter : {
|
|
320
|
+
...request?.filter,
|
|
321
|
+
protocol : this._definition.protocol,
|
|
322
|
+
protocolPath : path,
|
|
323
|
+
schema : typeEntry?.schema,
|
|
324
|
+
},
|
|
325
|
+
dateSort : request?.dateSort,
|
|
326
|
+
pagination : request?.pagination,
|
|
327
|
+
protocolRole : request?.protocolRole,
|
|
328
|
+
});
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Read a single record at the given protocol path.
|
|
333
|
+
*
|
|
334
|
+
* @param path - The protocol path to read from.
|
|
335
|
+
* @param request - Read options including a filter to identify the record.
|
|
336
|
+
*/
|
|
337
|
+
read: async <Path extends ProtocolPaths<D> & string>(
|
|
338
|
+
path: Path,
|
|
339
|
+
request: TypedReadRequest,
|
|
340
|
+
): Promise<TypedReadResponse> => {
|
|
341
|
+
const typeName = lastSegment(path);
|
|
342
|
+
const typeEntry = this._definition.types[typeName] as ProtocolType | undefined;
|
|
343
|
+
|
|
344
|
+
return this._dwn.records.read({
|
|
345
|
+
from : request.from,
|
|
346
|
+
encryption : request.encryption,
|
|
347
|
+
protocol : this._definition.protocol,
|
|
348
|
+
filter : {
|
|
349
|
+
...request.filter,
|
|
350
|
+
protocol : this._definition.protocol,
|
|
351
|
+
protocolPath : path,
|
|
352
|
+
schema : typeEntry?.schema,
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Delete a record at the given protocol path.
|
|
359
|
+
*
|
|
360
|
+
* @param path - The protocol path (used for permission scoping).
|
|
361
|
+
* @param request - Delete options including the `recordId`.
|
|
362
|
+
*/
|
|
363
|
+
delete: async <Path extends ProtocolPaths<D> & string>(
|
|
364
|
+
_path: Path,
|
|
365
|
+
request: TypedDeleteRequest,
|
|
366
|
+
): Promise<DwnResponseStatus> => {
|
|
367
|
+
return this._dwn.records.delete({
|
|
368
|
+
from : request.from,
|
|
369
|
+
protocol : this._definition.protocol,
|
|
370
|
+
recordId : request.recordId,
|
|
371
|
+
});
|
|
372
|
+
},
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Subscribe to records at the given protocol path.
|
|
376
|
+
*
|
|
377
|
+
* Returns a {@link LiveQuery} that atomically provides an initial snapshot
|
|
378
|
+
* and a real-time stream of deduplicated change events.
|
|
379
|
+
*
|
|
380
|
+
* @param path - The protocol path to subscribe to.
|
|
381
|
+
* @param request - Optional filter and role.
|
|
382
|
+
*/
|
|
383
|
+
subscribe: async <Path extends ProtocolPaths<D> & string>(
|
|
384
|
+
path: Path,
|
|
385
|
+
request?: TypedSubscribeRequest,
|
|
386
|
+
): Promise<TypedSubscribeResponse> => {
|
|
387
|
+
const typeName = lastSegment(path);
|
|
388
|
+
const typeEntry = this._definition.types[typeName] as ProtocolType | undefined;
|
|
389
|
+
|
|
390
|
+
return this._dwn.records.subscribe({
|
|
391
|
+
from : request?.from,
|
|
392
|
+
filter : {
|
|
393
|
+
...request?.filter,
|
|
394
|
+
protocol : this._definition.protocol,
|
|
395
|
+
protocolPath : path,
|
|
396
|
+
schema : typeEntry?.schema,
|
|
397
|
+
},
|
|
398
|
+
protocolRole: request?.protocolRole,
|
|
399
|
+
});
|
|
400
|
+
},
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ---------------------------------------------------------------------------
|
|
406
|
+
// Helpers
|
|
407
|
+
// ---------------------------------------------------------------------------
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Compares two protocol definitions for deep equality using deterministic
|
|
411
|
+
* JSON serialization.
|
|
412
|
+
*
|
|
413
|
+
* Keys are sorted recursively so that semantically identical definitions
|
|
414
|
+
* with different key ordering are treated as equal.
|
|
415
|
+
*/
|
|
416
|
+
function definitionsEqual(a: unknown, b: unknown): boolean {
|
|
417
|
+
return stableStringify(a) === stableStringify(b);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Returns the last segment of a slash-delimited path.
|
|
422
|
+
*/
|
|
423
|
+
function lastSegment(path: string): string {
|
|
424
|
+
const parts = path.split('/');
|
|
425
|
+
return parts[parts.length - 1];
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Deterministic JSON serialization with sorted keys.
|
|
430
|
+
*/
|
|
431
|
+
function stableStringify(value: unknown): string {
|
|
432
|
+
if (value === null || value === undefined || typeof value !== 'object') {
|
|
433
|
+
return JSON.stringify(value);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (Array.isArray(value)) {
|
|
437
|
+
return '[' + value.map((item) => stableStringify(item)).join(',') + ']';
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const keys = Object.keys(value as globalThis.Record<string, unknown>).sort();
|
|
441
|
+
const pairs = keys.map((key) =>
|
|
442
|
+
JSON.stringify(key) + ':' + stableStringify((value as globalThis.Record<string, unknown>)[key])
|
|
443
|
+
);
|
|
444
|
+
return '{' + pairs.join(',') + '}';
|
|
445
|
+
}
|
package/src/web5.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
/// <reference types="@enbox/dwn-sdk-js" />
|
|
6
6
|
|
|
7
|
+
import type { DidMethodResolver } from '@enbox/dids';
|
|
8
|
+
import type { ProtocolDefinition } from '@enbox/dwn-sdk-js';
|
|
7
9
|
import type {
|
|
8
10
|
BearerIdentity,
|
|
9
11
|
DwnDataEncodedRecordsWriteMessage,
|
|
@@ -16,12 +18,17 @@ import type {
|
|
|
16
18
|
Web5Agent,
|
|
17
19
|
} from '@enbox/agent';
|
|
18
20
|
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
+
import type { SchemaMap, TypedProtocol } from './protocol-types.js';
|
|
22
|
+
|
|
23
|
+
import { AnonymousDwnApi, WalletConnect, Web5UserAgent } from '@enbox/agent';
|
|
24
|
+
import { DidDht, DidJwk, DidKey, DidResolverCacheMemory, DidWeb, UniversalResolver } from '@enbox/dids';
|
|
25
|
+
import { DwnRegistrar, Web5RpcClient } from '@enbox/dwn-clients';
|
|
21
26
|
|
|
22
27
|
import { DidApi } from './did-api.js';
|
|
23
28
|
import { DwnApi } from './dwn-api.js';
|
|
29
|
+
import { DwnReaderApi } from './dwn-reader-api.js';
|
|
24
30
|
import { PermissionGrant } from './permission-grant.js';
|
|
31
|
+
import { TypedWeb5 } from './typed-web5.js';
|
|
25
32
|
import { VcApi } from './vc-api.js';
|
|
26
33
|
|
|
27
34
|
/** Override defaults configured during the technical preview phase. */
|
|
@@ -67,6 +74,28 @@ export type ConnectOptions = Omit<WalletConnectOptions, 'permissionRequests'> &
|
|
|
67
74
|
permissionRequests: ConnectPermissionRequest[];
|
|
68
75
|
};
|
|
69
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Options for creating an anonymous (read-only) Web5 instance via {@link Web5.anonymous}.
|
|
79
|
+
*
|
|
80
|
+
* @beta
|
|
81
|
+
*/
|
|
82
|
+
export type Web5AnonymousOptions = {
|
|
83
|
+
/** Override the default DID method resolvers. Defaults to `[DidDht, DidJwk, DidKey, DidWeb]`. */
|
|
84
|
+
didResolvers?: DidMethodResolver[];
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The result of calling {@link Web5.anonymous}.
|
|
89
|
+
*
|
|
90
|
+
* Contains only a read-only `dwn` property — no `did`, `vc`, or `agent`.
|
|
91
|
+
*
|
|
92
|
+
* @beta
|
|
93
|
+
*/
|
|
94
|
+
export type Web5AnonymousApi = {
|
|
95
|
+
/** A read-only DWN API for querying public data on remote DWNs. */
|
|
96
|
+
dwn: DwnReaderApi;
|
|
97
|
+
};
|
|
98
|
+
|
|
70
99
|
/** Optional overrides that can be provided when calling {@link Web5.connect}. */
|
|
71
100
|
export type Web5ConnectOptions = {
|
|
72
101
|
/**
|
|
@@ -227,8 +256,8 @@ export class Web5 {
|
|
|
227
256
|
/** Exposed instance to the DID APIs, allow users to create and resolve DIDs */
|
|
228
257
|
did: DidApi;
|
|
229
258
|
|
|
230
|
-
/**
|
|
231
|
-
|
|
259
|
+
/** Internal DWN API instance. Use {@link Web5.using} for protocol-scoped access. */
|
|
260
|
+
private _dwn: DwnApi;
|
|
232
261
|
|
|
233
262
|
/** Exposed instance to the VC APIs, allow users to issue, present and verify VCs */
|
|
234
263
|
vc: VcApi;
|
|
@@ -236,10 +265,80 @@ export class Web5 {
|
|
|
236
265
|
constructor({ agent, connectedDid, delegateDid }: Web5Params) {
|
|
237
266
|
this.agent = agent;
|
|
238
267
|
this.did = new DidApi({ agent, connectedDid });
|
|
239
|
-
this.
|
|
268
|
+
this._dwn = new DwnApi({ agent, connectedDid, delegateDid });
|
|
240
269
|
this.vc = new VcApi({ agent, connectedDid });
|
|
241
270
|
}
|
|
242
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Returns a {@link TypedWeb5} instance scoped to the given protocol.
|
|
274
|
+
*
|
|
275
|
+
* This is the **primary developer interface** for interacting with
|
|
276
|
+
* protocol-backed records. It auto-injects the protocol URI, protocolPath,
|
|
277
|
+
* and schema into every operation, and provides compile-time path
|
|
278
|
+
* autocompletion plus typed data payloads via the schema map.
|
|
279
|
+
*
|
|
280
|
+
* @param protocol - A typed protocol created via `defineProtocol()`.
|
|
281
|
+
* @returns A `TypedWeb5` instance bound to the given protocol.
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* ```ts
|
|
285
|
+
* const social = web5.using(SocialProtocol);
|
|
286
|
+
*
|
|
287
|
+
* await social.configure();
|
|
288
|
+
*
|
|
289
|
+
* const { record } = await social.records.write('friend', {
|
|
290
|
+
* data: { did: 'did:example:alice', alias: 'Alice' },
|
|
291
|
+
* });
|
|
292
|
+
*
|
|
293
|
+
* const { records } = await social.records.query('friend');
|
|
294
|
+
* ```
|
|
295
|
+
*/
|
|
296
|
+
public using<D extends ProtocolDefinition, M extends SchemaMap>(
|
|
297
|
+
protocol: TypedProtocol<D, M>,
|
|
298
|
+
): TypedWeb5<D, M> {
|
|
299
|
+
return new TypedWeb5<D, M>(this._dwn, protocol);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Creates a lightweight, read-only Web5 instance for querying public DWN data.
|
|
304
|
+
*
|
|
305
|
+
* No identity, vault, password, or signing keys are required. The returned
|
|
306
|
+
* API supports querying and reading published records and protocols from any
|
|
307
|
+
* remote DWN, using **unsigned** (anonymous) DWN messages.
|
|
308
|
+
*
|
|
309
|
+
* @param options - Optional configuration overrides.
|
|
310
|
+
* @returns A {@link Web5AnonymousApi} with a read-only `dwn` property.
|
|
311
|
+
*
|
|
312
|
+
* @example
|
|
313
|
+
* ```ts
|
|
314
|
+
* const { dwn } = Web5.anonymous();
|
|
315
|
+
*
|
|
316
|
+
* const { records } = await dwn.records.query({
|
|
317
|
+
* from: 'did:dht:alice...',
|
|
318
|
+
* filter: { protocol: 'https://social.example/posts', protocolPath: 'post' },
|
|
319
|
+
* });
|
|
320
|
+
*
|
|
321
|
+
* for (const record of records) {
|
|
322
|
+
* console.log(record.id, await record.data.text());
|
|
323
|
+
* }
|
|
324
|
+
* ```
|
|
325
|
+
*
|
|
326
|
+
* @beta
|
|
327
|
+
*/
|
|
328
|
+
static anonymous(options?: Web5AnonymousOptions): Web5AnonymousApi {
|
|
329
|
+
const didResolver = new UniversalResolver({
|
|
330
|
+
didResolvers : options?.didResolvers ?? [DidDht, DidJwk, DidKey, DidWeb],
|
|
331
|
+
cache : new DidResolverCacheMemory(),
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const rpcClient = new Web5RpcClient();
|
|
335
|
+
const anonymousDwn = new AnonymousDwnApi({ didResolver, rpcClient });
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
dwn: new DwnReaderApi(anonymousDwn),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
243
342
|
/**
|
|
244
343
|
* Connects to a {@link Web5Agent}. Defaults to creating a local {@link Web5UserAgent} if one
|
|
245
344
|
* isn't provided.
|