@enbox/api 0.0.8 → 0.1.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.
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Type-level utilities for extracting typed paths, type names, schemas,
3
+ * data formats, and tag shapes from a {@link ProtocolDefinition}.
4
+ *
5
+ * These types are purely compile-time — they produce no runtime code.
6
+ */
7
+
8
+ import type { ProtocolDefinition, ProtocolRuleSet, ProtocolTagsDefinition, ProtocolType } from '@enbox/dwn-sdk-js';
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Path extraction
12
+ // ---------------------------------------------------------------------------
13
+
14
+ /**
15
+ * Recursively extracts all valid protocol path strings from a `ProtocolRuleSet`.
16
+ *
17
+ * Given a structure like `{ foo: { bar: { ... } } }`, this produces
18
+ * `'foo' | 'foo/bar'`. Directive keys (starting with `$`) are excluded.
19
+ */
20
+ export type RuleSetPaths<R, Prefix extends string = ''> = {
21
+ [K in Extract<keyof R, string>]: K extends `$${string}`
22
+ ? never
23
+ : R[K] extends ProtocolRuleSet
24
+ ? `${Prefix}${K}` | RuleSetPaths<R[K], `${Prefix}${K}/`>
25
+ : never;
26
+ }[Extract<keyof R, string>];
27
+
28
+ /**
29
+ * All valid protocol path strings for a given `ProtocolDefinition`.
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * type Paths = ProtocolPaths<typeof myDef>;
34
+ * // 'friend' | 'friend/message' | 'group' | 'group/member'
35
+ * ```
36
+ */
37
+ export type ProtocolPaths<D extends ProtocolDefinition> = RuleSetPaths<D['structure']>;
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Type-name extraction
41
+ // ---------------------------------------------------------------------------
42
+
43
+ /**
44
+ * Extracts the last segment (type name) from a protocol path string.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * type T = TypeNameAtPath<'group/member'>; // 'member'
49
+ * ```
50
+ */
51
+ export type TypeNameAtPath<Path extends string> =
52
+ Path extends `${string}/${infer Rest}`
53
+ ? TypeNameAtPath<Rest>
54
+ : Path;
55
+
56
+ // ---------------------------------------------------------------------------
57
+ // Schema & data-format lookup
58
+ // ---------------------------------------------------------------------------
59
+
60
+ /**
61
+ * The type names declared in the `types` map of a `ProtocolDefinition`.
62
+ */
63
+ export type TypeNames<D extends ProtocolDefinition> = Extract<keyof D['types'], string>;
64
+
65
+ /**
66
+ * Looks up the `schema` URI for a given type name in the protocol definition.
67
+ * Returns `undefined` if the type does not declare a schema.
68
+ */
69
+ export type SchemaForType<D extends ProtocolDefinition, TypeName extends string> =
70
+ TypeName extends keyof D['types']
71
+ ? D['types'][TypeName] extends ProtocolType
72
+ ? D['types'][TypeName]['schema']
73
+ : undefined
74
+ : undefined;
75
+
76
+ /**
77
+ * Looks up the `dataFormats` array for a given type name in the protocol definition.
78
+ * Returns `undefined` if the type does not declare dataFormats.
79
+ */
80
+ export type DataFormatsForType<D extends ProtocolDefinition, TypeName extends string> =
81
+ TypeName extends keyof D['types']
82
+ ? D['types'][TypeName] extends ProtocolType
83
+ ? D['types'][TypeName]['dataFormats']
84
+ : undefined
85
+ : undefined;
86
+
87
+ // ---------------------------------------------------------------------------
88
+ // Tag extraction helpers
89
+ // ---------------------------------------------------------------------------
90
+
91
+ /**
92
+ * Navigates a `ProtocolRuleSet` tree to the node at the given slash-delimited path.
93
+ */
94
+ type RuleSetAtPath<R, Path extends string> =
95
+ Path extends `${infer Head}/${infer Tail}`
96
+ ? Head extends keyof R
97
+ ? R[Head] extends ProtocolRuleSet
98
+ ? RuleSetAtPath<R[Head], Tail>
99
+ : never
100
+ : never
101
+ : Path extends keyof R
102
+ ? R[Path] extends ProtocolRuleSet
103
+ ? R[Path]
104
+ : never
105
+ : never;
106
+
107
+ /**
108
+ * Extracts the `$tags` definition for a given protocol path.
109
+ * Returns `never` if the path does not declare `$tags`.
110
+ */
111
+ export type TagsAtPath<D extends ProtocolDefinition, Path extends string> =
112
+ RuleSetAtPath<D['structure'], Path> extends infer RS
113
+ ? RS extends ProtocolRuleSet
114
+ ? RS['$tags'] extends ProtocolTagsDefinition
115
+ ? RS['$tags']
116
+ : never
117
+ : never
118
+ : never;
119
+
120
+ /**
121
+ * Extracts the user-defined tag keys (excluding `$`-prefixed meta-keys)
122
+ * from a `ProtocolTagsDefinition`.
123
+ */
124
+ export type TagKeys<Tags extends ProtocolTagsDefinition> = Exclude<
125
+ Extract<keyof Tags, string>,
126
+ `$${string}`
127
+ >;
128
+
129
+ // ---------------------------------------------------------------------------
130
+ // Schema map — associates TypeScript data types with protocol type names
131
+ // ---------------------------------------------------------------------------
132
+
133
+ /**
134
+ * A mapping from protocol type names to their TypeScript data shapes.
135
+ *
136
+ * Used as a type parameter to `defineProtocol()` and `TypedDwnApi` so that
137
+ * the protocol definition JSON stays JSON-compatible while TypeScript types
138
+ * are tracked separately.
139
+ *
140
+ * @example
141
+ * ```ts
142
+ * type MySchemaMap = {
143
+ * profile : { displayName: string; bio?: string };
144
+ * avatar : Blob;
145
+ * };
146
+ * ```
147
+ */
148
+ export type SchemaMap = Record<string, unknown>;
149
+
150
+ // ---------------------------------------------------------------------------
151
+ // Typed protocol — output of defineProtocol()
152
+ // ---------------------------------------------------------------------------
153
+
154
+ /**
155
+ * The return type of `defineProtocol()`. Bundles the raw protocol definition
156
+ * with its inferred path strings and schema type map for downstream use
157
+ * by `TypedDwnApi`.
158
+ */
159
+ export type TypedProtocol<
160
+ D extends ProtocolDefinition = ProtocolDefinition,
161
+ M extends SchemaMap = SchemaMap,
162
+ > = {
163
+ /** The raw DWN protocol definition (JSON-compatible). */
164
+ readonly definition: D;
165
+
166
+ /**
167
+ * Phantom property carrying the schema map type. Not present at runtime;
168
+ * used only by TypeScript to thread the generic through.
169
+ */
170
+ readonly _schemaMap?: M;
171
+ };
package/src/record.ts CHANGED
@@ -445,7 +445,7 @@ export class Record implements RecordModel {
445
445
  get data(): {
446
446
  blob: () => Promise<Blob>;
447
447
  bytes: () => Promise<Uint8Array>;
448
- json: () => Promise<any>;
448
+ json: <T = unknown>() => Promise<T>;
449
449
  text: () => Promise<string>;
450
450
  stream: () => Promise<ReadableStream>;
451
451
  then: (
@@ -489,8 +489,8 @@ export class Record implements RecordModel {
489
489
  *
490
490
  * @beta
491
491
  */
492
- async json(): Promise<any> {
493
- return await Stream.consumeToJson({ readableStream: await this.stream() });
492
+ async json<T = unknown>(): Promise<T> {
493
+ return await Stream.consumeToJson({ readableStream: await this.stream() }) as T;
494
494
  },
495
495
 
496
496
  /**
@@ -0,0 +1,370 @@
1
+ /**
2
+ * A type-safe wrapper around {@link DwnApi} scoped to a single protocol.
3
+ *
4
+ * `TypedDwnApi` is created via `dwn.using(typedProtocol)` and provides
5
+ * autocompletion for protocol paths, typed data payloads, and tag shapes.
6
+ *
7
+ * Every method delegates to the corresponding `dwn.records.*` method,
8
+ * injecting the protocol URI, protocolPath, and schema automatically.
9
+ */
10
+
11
+ import type { Protocol } from './protocol.js';
12
+ import type { Record } from './record.js';
13
+ import type { DateSort, ProtocolDefinition, ProtocolType, RecordsFilter } from '@enbox/dwn-sdk-js';
14
+ import type { DwnApi, RecordsSubscriptionHandler } from './dwn-api.js';
15
+ import type { DwnMessageSubscription, DwnPaginationCursor, DwnResponseStatus } from '@enbox/agent';
16
+ import type { ProtocolPaths, SchemaMap, TypedProtocol, TypeNameAtPath } from './protocol-types.js';
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Helper types
20
+ // ---------------------------------------------------------------------------
21
+
22
+ /**
23
+ * Resolves the TypeScript data type for a given protocol path.
24
+ *
25
+ * If the schema map contains a mapping for the type name at the given path,
26
+ * that type is returned. Otherwise falls back to `unknown`.
27
+ */
28
+ type DataForPath<
29
+ _D extends ProtocolDefinition,
30
+ M extends SchemaMap,
31
+ Path extends string,
32
+ > = TypeNameAtPath<Path> extends keyof M ? M[TypeNameAtPath<Path>] : unknown;
33
+
34
+ /**
35
+ * Resolves the `ProtocolType` entry for a given protocol path.
36
+ */
37
+ type ProtocolTypeForPath<
38
+ D extends ProtocolDefinition,
39
+ Path extends string,
40
+ > = TypeNameAtPath<Path> extends keyof D['types']
41
+ ? D['types'][TypeNameAtPath<Path>] extends ProtocolType
42
+ ? D['types'][TypeNameAtPath<Path>]
43
+ : undefined
44
+ : undefined;
45
+
46
+ /**
47
+ * Resolves a `dataFormat` string literal union for a path, or `string` if none.
48
+ */
49
+ type DataFormatForPath<
50
+ D extends ProtocolDefinition,
51
+ Path extends string,
52
+ > = ProtocolTypeForPath<D, Path> extends { dataFormats: infer F }
53
+ ? F extends readonly string[]
54
+ ? F[number]
55
+ : string
56
+ : string;
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Request / response types
60
+ // ---------------------------------------------------------------------------
61
+
62
+ /** Options for `TypedDwnApi.write()`. */
63
+ export type TypedWriteRequest<
64
+ D extends ProtocolDefinition,
65
+ M extends SchemaMap,
66
+ Path extends string,
67
+ > = {
68
+ /** The data payload. Type-checked against the schema map. */
69
+ data: DataForPath<D, M, Path>;
70
+
71
+ /** Additional message parameters (protocolPath and protocol are injected). */
72
+ message?: {
73
+ parentContextId? : string;
74
+ published? : boolean;
75
+ datePublished? : string;
76
+ recipient? : string;
77
+ protocolRole? : string;
78
+ dataFormat? : DataFormatForPath<D, Path>;
79
+ tags? : globalThis.Record<string, string | number | boolean | string[] | number[]>;
80
+ };
81
+
82
+ /** Whether to persist immediately (defaults to `true`). */
83
+ store?: boolean;
84
+
85
+ /** Whether to auto-encrypt (follows protocol definition if omitted). */
86
+ encryption?: boolean;
87
+ };
88
+
89
+ /** Response from `TypedDwnApi.write()`. */
90
+ export type TypedWriteResponse = DwnResponseStatus & {
91
+ record?: Record;
92
+ };
93
+
94
+ /** Filter options for `TypedDwnApi.query()`. */
95
+ export type TypedQueryFilter = Omit<RecordsFilter, 'protocol' | 'protocolPath' | 'schema'> & {
96
+ tags?: globalThis.Record<string, string | number | boolean | (string | number)[]>;
97
+ };
98
+
99
+ /** Options for `TypedDwnApi.query()`. */
100
+ export type TypedQueryRequest = {
101
+ /** Optional remote DWN DID to query from. */
102
+ from?: string;
103
+
104
+ /** Query filter (protocol, protocolPath, schema are injected). */
105
+ filter? : TypedQueryFilter;
106
+ dateSort? : DateSort;
107
+ pagination? : { limit?: number; cursor?: DwnPaginationCursor };
108
+ protocolRole? : string;
109
+
110
+ /** When true, automatically decrypts encrypted records. */
111
+ encryption?: boolean;
112
+ };
113
+
114
+ /** Response from `TypedDwnApi.query()`. */
115
+ export type TypedQueryResponse = DwnResponseStatus & {
116
+ records?: Record[];
117
+ cursor? : DwnPaginationCursor;
118
+ };
119
+
120
+ /** Options for `TypedDwnApi.read()`. */
121
+ export type TypedReadRequest = {
122
+ /** Optional remote DWN DID to read from. */
123
+ from?: string;
124
+
125
+ /** Filter to identify the record (protocol and protocolPath are injected). */
126
+ filter: Omit<RecordsFilter, 'protocol' | 'protocolPath' | 'schema'>;
127
+
128
+ /** When true, automatically decrypts the record. */
129
+ encryption?: boolean;
130
+ };
131
+
132
+ /** Response from `TypedDwnApi.read()`. */
133
+ export type TypedReadResponse = DwnResponseStatus & {
134
+ record: Record;
135
+ };
136
+
137
+ /** Options for `TypedDwnApi.delete()`. */
138
+ export type TypedDeleteRequest = {
139
+ /** Optional remote DWN DID to delete from. */
140
+ from?: string;
141
+
142
+ /** The `recordId` of the record to delete. */
143
+ recordId: string;
144
+ };
145
+
146
+ /** Options for `TypedDwnApi.subscribe()`. */
147
+ export type TypedSubscribeRequest = {
148
+ /** Optional remote DWN DID to subscribe to. */
149
+ from?: string;
150
+
151
+ /** Subscription filter (protocol, protocolPath, schema are injected). */
152
+ filter? : TypedQueryFilter;
153
+ protocolRole? : string;
154
+ subscriptionHandler : RecordsSubscriptionHandler;
155
+
156
+ /** When true, indicates encryption is active. */
157
+ encryption?: boolean;
158
+ };
159
+
160
+ /** Response from `TypedDwnApi.subscribe()`. */
161
+ export type TypedSubscribeResponse = DwnResponseStatus & {
162
+ subscription?: DwnMessageSubscription;
163
+ };
164
+
165
+ // ---------------------------------------------------------------------------
166
+ // TypedDwnApi class
167
+ // ---------------------------------------------------------------------------
168
+
169
+ /**
170
+ * A protocol-scoped wrapper around `DwnApi` that automatically injects
171
+ * the `protocol` URI, `protocolPath`, and `schema` into every DWN operation.
172
+ *
173
+ * Obtain an instance via `dwn.using(typedProtocol)`.
174
+ *
175
+ * @example
176
+ * ```ts
177
+ * const social = dwn.using(SocialGraphProtocol);
178
+ *
179
+ * // Write — path and data type are checked at compile time
180
+ * const { record } = await social.write('friend', {
181
+ * data: { did: 'did:example:alice', alias: 'Alice' },
182
+ * });
183
+ *
184
+ * // Query — protocol and protocolPath are auto-injected
185
+ * const { records } = await social.query('friend', {
186
+ * filter: { tags: { did: 'did:example:alice' } },
187
+ * });
188
+ * ```
189
+ */
190
+ export class TypedDwnApi<
191
+ D extends ProtocolDefinition = ProtocolDefinition,
192
+ M extends SchemaMap = SchemaMap,
193
+ > {
194
+ private _dwn: DwnApi;
195
+ private _definition: D;
196
+
197
+ constructor(dwn: DwnApi, protocol: TypedProtocol<D, M>) {
198
+ this._dwn = dwn;
199
+ this._definition = protocol.definition;
200
+ }
201
+
202
+ /** The protocol URI. */
203
+ public get protocol(): string {
204
+ return this._definition.protocol;
205
+ }
206
+
207
+ /** The raw protocol definition. */
208
+ public get definition(): D {
209
+ return this._definition;
210
+ }
211
+
212
+ /**
213
+ * Configures (installs) this protocol on the local DWN.
214
+ *
215
+ * @param options - Optional overrides like `encryption`.
216
+ */
217
+ public async configure(options?: { encryption?: boolean }): Promise<DwnResponseStatus & { protocol?: Protocol }> {
218
+ return this._dwn.protocols.configure({
219
+ message : { definition: this._definition },
220
+ encryption : options?.encryption,
221
+ });
222
+ }
223
+
224
+ /**
225
+ * Write a record at the given protocol path.
226
+ *
227
+ * @param path - The protocol path (e.g. `'friend'`, `'group/member'`).
228
+ * @param request - Write options including typed `data`.
229
+ */
230
+ public async write<Path extends ProtocolPaths<D> & string>(
231
+ path : Path,
232
+ request : TypedWriteRequest<D, M, Path>,
233
+ ): Promise<TypedWriteResponse> {
234
+ const typeName = lastSegment(path);
235
+ const typeEntry = this._definition.types[typeName] as ProtocolType | undefined;
236
+
237
+ return this._dwn.records.write({
238
+ data : request.data,
239
+ store : request.store,
240
+ encryption : request.encryption,
241
+ message : {
242
+ ...request.message,
243
+ protocol : this._definition.protocol,
244
+ protocolPath : path,
245
+ schema : typeEntry?.schema,
246
+ dataFormat : request.message?.dataFormat ?? typeEntry?.dataFormats?.[0],
247
+ },
248
+ });
249
+ }
250
+
251
+ /**
252
+ * Query records at the given protocol path.
253
+ *
254
+ * @param path - The protocol path to query.
255
+ * @param request - Query options including optional filter, sort, and pagination.
256
+ */
257
+ public async query<Path extends ProtocolPaths<D> & string>(
258
+ path : Path,
259
+ request? : TypedQueryRequest,
260
+ ): Promise<TypedQueryResponse> {
261
+ const typeName = lastSegment(path);
262
+ const typeEntry = this._definition.types[typeName] as ProtocolType | undefined;
263
+
264
+ return this._dwn.records.query({
265
+ from : request?.from,
266
+ protocol : this._definition.protocol,
267
+ encryption : request?.encryption,
268
+ message : {
269
+ filter: {
270
+ ...request?.filter,
271
+ protocol : this._definition.protocol,
272
+ protocolPath : path,
273
+ schema : typeEntry?.schema,
274
+ },
275
+ dateSort : request?.dateSort,
276
+ pagination : request?.pagination,
277
+ protocolRole : request?.protocolRole,
278
+ },
279
+ });
280
+ }
281
+
282
+ /**
283
+ * Read a single record at the given protocol path.
284
+ *
285
+ * @param path - The protocol path to read from.
286
+ * @param request - Read options including a filter to identify the record.
287
+ */
288
+ public async read<Path extends ProtocolPaths<D> & string>(
289
+ path : Path,
290
+ request : TypedReadRequest,
291
+ ): Promise<TypedReadResponse> {
292
+ const typeName = lastSegment(path);
293
+ const typeEntry = this._definition.types[typeName] as ProtocolType | undefined;
294
+
295
+ return this._dwn.records.read({
296
+ from : request.from,
297
+ protocol : this._definition.protocol,
298
+ encryption : request.encryption,
299
+ message : {
300
+ filter: {
301
+ ...request.filter,
302
+ protocol : this._definition.protocol,
303
+ protocolPath : path,
304
+ schema : typeEntry?.schema,
305
+ },
306
+ },
307
+ });
308
+ }
309
+
310
+ /**
311
+ * Delete a record at the given protocol path.
312
+ *
313
+ * @param path - The protocol path (used for permission scoping).
314
+ * @param request - Delete options including the `recordId`.
315
+ */
316
+ public async delete<Path extends ProtocolPaths<D> & string>(
317
+ _path : Path,
318
+ request : TypedDeleteRequest,
319
+ ): Promise<DwnResponseStatus> {
320
+ return this._dwn.records.delete({
321
+ from : request.from,
322
+ protocol : this._definition.protocol,
323
+ message : {
324
+ recordId: request.recordId,
325
+ },
326
+ });
327
+ }
328
+
329
+ /**
330
+ * Subscribe to records at the given protocol path.
331
+ *
332
+ * @param path - The protocol path to subscribe to.
333
+ * @param request - Subscribe options including the subscription handler.
334
+ */
335
+ public async subscribe<Path extends ProtocolPaths<D> & string>(
336
+ path : Path,
337
+ request : TypedSubscribeRequest,
338
+ ): Promise<TypedSubscribeResponse> {
339
+ const typeName = lastSegment(path);
340
+ const typeEntry = this._definition.types[typeName] as ProtocolType | undefined;
341
+
342
+ return this._dwn.records.subscribe({
343
+ from : request.from,
344
+ protocol : this._definition.protocol,
345
+ encryption : request.encryption,
346
+ subscriptionHandler : request.subscriptionHandler,
347
+ message : {
348
+ filter: {
349
+ ...request.filter,
350
+ protocol : this._definition.protocol,
351
+ protocolPath : path,
352
+ schema : typeEntry?.schema,
353
+ },
354
+ protocolRole: request.protocolRole,
355
+ },
356
+ });
357
+ }
358
+ }
359
+
360
+ // ---------------------------------------------------------------------------
361
+ // Helpers
362
+ // ---------------------------------------------------------------------------
363
+
364
+ /**
365
+ * Returns the last segment of a slash-delimited path.
366
+ */
367
+ function lastSegment(path: string): string {
368
+ const parts = path.split('/');
369
+ return parts[parts.length - 1];
370
+ }
package/src/web5.ts CHANGED
@@ -16,7 +16,8 @@ import type {
16
16
  Web5Agent,
17
17
  } from '@enbox/agent';
18
18
 
19
- import { DwnRegistrar, WalletConnect, Web5UserAgent } from '@enbox/agent';
19
+ import { DwnRegistrar } from '@enbox/dwn-clients';
20
+ import { WalletConnect, Web5UserAgent } from '@enbox/agent';
20
21
 
21
22
  import { DidApi } from './did-api.js';
22
23
  import { DwnApi } from './dwn-api.js';
@@ -380,7 +381,7 @@ export class Web5 {
380
381
  purposes : ['assertionMethod', 'authentication']
381
382
  },
382
383
  {
383
- algorithm : 'secp256k1',
384
+ algorithm : 'X25519',
384
385
  id : 'enc',
385
386
  purposes : ['keyAgreement']
386
387
  }