@enbox/api 0.2.0 → 0.2.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 CHANGED
@@ -4,7 +4,32 @@
4
4
 
5
5
  [![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/LiranCohen/02d15f39a46173a612a8862ec6d7cfcf/raw/api.json)](https://github.com/enboxorg/enbox/actions/workflows/ci.yml)
6
6
 
7
- The high-level SDK for building decentralized applications with protocol-first data management.
7
+ The high-level SDK for building decentralized applications with protocol-first data management. This is the main consumer-facing package in the Enbox ecosystem -- most applications only need `@enbox/api`.
8
+
9
+ ## Table of Contents
10
+
11
+ - [Installation](#installation)
12
+ - [Quick Start](#quick-start)
13
+ - [Core Concepts](#core-concepts)
14
+ - [Web5.connect()](#web5connectoptions)
15
+ - [defineProtocol()](#defineprotocoldefinition-schemamap)
16
+ - [web5.using()](#web5usingprotocol)
17
+ - [Record Instances](#record-instances)
18
+ - [LiveQuery (Subscriptions)](#livequery-subscriptions)
19
+ - [Web5.anonymous()](#web5anonymousoptions)
20
+ - [Cookbook](#cookbook)
21
+ - [Nested Records](#nested-records)
22
+ - [Querying with Filters and Pagination](#querying-with-filters-and-pagination)
23
+ - [Tags](#tags)
24
+ - [Publishing Records](#publishing-records)
25
+ - [Reading Public Data Anonymously](#reading-public-data-anonymously)
26
+ - [Sending Records to Remote DWNs](#sending-records-to-remote-dwns)
27
+ - [Advanced Usage](#advanced-usage)
28
+ - [Unscoped DWN Access](#unscoped-dwn-access)
29
+ - [Permissions](#permissions)
30
+ - [DID Operations](#did-operations)
31
+ - [API Reference](#api-reference)
32
+ - [License](#license)
8
33
 
9
34
  ## Installation
10
35
 
@@ -17,7 +42,7 @@ bun add @enbox/api
17
42
  ```ts
18
43
  import { defineProtocol, Web5 } from '@enbox/api';
19
44
 
20
- // 1. Connect
45
+ // 1. Connect -- creates or loads a local identity and agent
21
46
  const { web5, did: myDid } = await Web5.connect();
22
47
 
23
48
  // 2. Define a protocol with typed data shapes
@@ -40,15 +65,21 @@ const NotesProtocol = defineProtocol({
40
65
  // 3. Scope all operations to the protocol
41
66
  const notes = web5.using(NotesProtocol);
42
67
 
43
- // 4. Install the protocol
68
+ // 4. Install the protocol on the local DWN
44
69
  await notes.configure();
45
70
 
46
- // 5. Write a record (path, data, and schema are type-checked)
71
+ // 5. Write a record -- path, data, and schema are type-checked
47
72
  const { record } = await notes.records.write('note', {
48
73
  data: { title: 'Hello', body: 'World' },
49
74
  });
50
75
 
51
- // 6. Send to your remote DWN
76
+ // 6. Query records back
77
+ const { records } = await notes.records.query('note');
78
+ for (const r of records) {
79
+ console.log(r.id, await r.data.json());
80
+ }
81
+
82
+ // 7. Send to your remote DWN
52
83
  await record.send(myDid);
53
84
  ```
54
85
 
@@ -56,46 +87,69 @@ await record.send(myDid);
56
87
 
57
88
  ### `Web5.connect(options?)`
58
89
 
59
- Connects to a local identity agent or generates an in-app DID.
90
+ Connects to a local identity agent. On first launch it creates an identity vault, generates a `did:dht` DID, and starts the sync engine. On subsequent launches it unlocks the existing vault.
60
91
 
61
92
  ```ts
62
- const { web5, did, recoveryPhrase } = await Web5.connect();
93
+ const { web5, did, recoveryPhrase } = await Web5.connect({
94
+ password: 'user-chosen-password',
95
+ });
96
+
97
+ // recoveryPhrase is returned on first launch only -- store it safely!
63
98
  ```
64
99
 
65
100
  **Options** (all optional):
66
101
 
67
102
  | Option | Type | Description |
68
103
  |--------|------|-------------|
69
- | `agent` | `Web5Agent` | Custom agent instance. Defaults to a local `Web5UserAgent`. |
70
- | `connectedDid` | `string` | Existing DID to connect to. |
71
- | `password` | `string` | Password to protect the local identity vault. |
72
- | `recoveryPhrase` | `string` | 12-word BIP-39 phrase for vault recovery. |
73
- | `sync` | `string` | Sync interval (e.g. `'2m'`) or `'off'`. Default: `'2m'`. |
74
- | `didCreateOptions.dwnEndpoints` | `string[]` | DWN endpoints for the created DID. |
75
- | `walletConnectOptions` | `ConnectOptions` | Trigger external wallet connect flow. |
104
+ | `password` | `string` | Password to protect the local identity vault. **Defaults to an insecure static value** -- always set this in production. |
105
+ | `recoveryPhrase` | `string` | 12-word BIP-39 phrase for vault recovery. Generated automatically on first launch if not provided. |
106
+ | `sync` | `string` | Sync interval (e.g. `'2m'`, `'30s'`) or `'off'` to disable. Default: `'2m'`. |
107
+ | `didCreateOptions.dwnEndpoints` | `string[]` | DWN service endpoints for the created DID. Default: `['https://enbox-dwn.fly.dev']`. |
108
+ | `connectedDid` | `string` | Use an existing DID instead of creating a new one. |
109
+ | `agent` | `Web5Agent` | Provide a custom agent instance. Defaults to a local `Web5UserAgent`. |
110
+ | `walletConnectOptions` | `ConnectOptions` | Trigger an external wallet connect flow for delegated identity. |
111
+ | `registration` | `{ onSuccess, onFailure }` | Callbacks for DWN endpoint registration status. |
76
112
 
77
113
  **Returns** `{ web5, did, recoveryPhrase?, delegateDid? }`.
78
114
 
115
+ - `web5` -- the `Web5` instance for all subsequent operations
116
+ - `did` -- the connected DID URI (e.g. `did:dht:abc...`)
117
+ - `recoveryPhrase` -- only returned on first initialization
118
+ - `delegateDid` -- only present when using wallet connect
119
+
79
120
  ---
80
121
 
81
- ### `Web5.anonymous(options?)`
122
+ ### `defineProtocol(definition, schemaMap?)`
82
123
 
83
- Creates a lightweight, read-only instance for querying public DWN data. No identity, vault, or signing keys are required.
124
+ Creates a typed protocol definition that enables compile-time path autocompletion and data type checking when used with `web5.using()`.
84
125
 
85
126
  ```ts
86
- const { dwn } = Web5.anonymous();
127
+ import type { ProtocolDefinition } from '@enbox/dwn-sdk-js';
87
128
 
88
- const { records } = await dwn.records.query({
89
- from : 'did:dht:alice...',
90
- filter : { protocol: 'https://example.com/notes', protocolPath: 'note' },
129
+ const ChatProtocol = defineProtocol({
130
+ protocol : 'https://example.com/chat',
131
+ published : true,
132
+ types: {
133
+ thread : { schema: 'https://example.com/schemas/thread', dataFormats: ['application/json'] },
134
+ message : { schema: 'https://example.com/schemas/message', dataFormats: ['application/json'] },
135
+ },
136
+ structure: {
137
+ thread: {
138
+ message: {}, // messages are nested under threads
139
+ },
140
+ },
141
+ } as const satisfies ProtocolDefinition, {} as {
142
+ thread : { title: string; description?: string };
143
+ message : { text: string };
91
144
  });
92
-
93
- for (const record of records) {
94
- console.log(record.id, await record.data.text());
95
- }
96
145
  ```
97
146
 
98
- Returns a `{ dwn: DwnReaderApi }` with read-only `records.query()` and `records.read()`.
147
+ The second argument is a **phantom type** -- it only exists at compile time. Pass `{} as YourSchemaMap` to map protocol type names to TypeScript interfaces. The runtime value is ignored.
148
+
149
+ This gives you:
150
+ - **Path autocompletion**: `'thread'`, `'thread/message'` are inferred from `structure`
151
+ - **Typed `data` payloads**: `write('thread', { data: ... })` type-checks against the schema map
152
+ - **Typed `dataFormat`**: restricted to the formats declared in the protocol type
99
153
 
100
154
  ---
101
155
 
@@ -104,51 +158,99 @@ Returns a `{ dwn: DwnReaderApi }` with read-only `records.query()` and `records.
104
158
  The **primary interface** for all record operations. Returns a `TypedWeb5` instance scoped to the given protocol.
105
159
 
106
160
  ```ts
107
- const notes = web5.using(NotesProtocol);
161
+ const chat = web5.using(ChatProtocol);
108
162
  ```
109
163
 
110
- The returned object provides:
164
+ #### `configure()`
111
165
 
112
- - **`notes.configure()`** -- Install the protocol on the local DWN.
113
- - **`notes.records.write(path, request)`** -- Write a record at a protocol path.
114
- - **`notes.records.query(path, request?)`** -- Query records at a path.
115
- - **`notes.records.read(path, request)`** -- Read a single record.
116
- - **`notes.records.delete(path, request)`** -- Delete a record by ID.
117
- - **`notes.records.subscribe(path, request?)`** -- Subscribe to real-time changes (returns a `LiveQuery`).
166
+ Installs the protocol on the local DWN. If already installed with an identical definition, this is a no-op. If the definition has changed, it reconfigures with the updated version.
118
167
 
119
- Protocol URI, protocolPath, and schema are automatically injected into every operation.
168
+ ```ts
169
+ await chat.configure();
170
+ ```
120
171
 
121
- ---
172
+ #### `records.write(path, request)`
122
173
 
123
- ### `defineProtocol(definition, schemaMap?)`
174
+ Write a record at a protocol path. The protocol URI, protocolPath, schema, and dataFormat are automatically injected.
124
175
 
125
- Creates a typed protocol definition that enables compile-time path autocompletion and data type checking.
176
+ ```ts
177
+ const { record, status } = await chat.records.write('thread', {
178
+ data: { title: 'General', description: 'General discussion' },
179
+ });
180
+
181
+ console.log(status.code); // 202
182
+ console.log(record.id); // unique record ID
183
+ ```
184
+
185
+ #### `records.query(path, request?)`
186
+
187
+ Query records at a protocol path. Returns matching records with optional pagination.
126
188
 
127
189
  ```ts
128
- import type { ProtocolDefinition } from '@enbox/dwn-sdk-js';
190
+ const { records, cursor } = await chat.records.query('thread', {
191
+ dateSort : 'createdDescending',
192
+ pagination : { limit: 20 },
193
+ });
129
194
 
130
- const SocialProtocol = defineProtocol({
131
- protocol : 'https://social.example/protocol',
132
- published : true,
133
- types: {
134
- profile : { schema: 'https://social.example/schemas/profile', dataFormats: ['application/json'] },
135
- post : { schema: 'https://social.example/schemas/post', dataFormats: ['application/json'] },
136
- reply : { schema: 'https://social.example/schemas/reply', dataFormats: ['application/json'] },
137
- },
138
- structure: {
139
- profile : {},
140
- post : {
141
- reply: {},
142
- },
143
- },
144
- } as const satisfies ProtocolDefinition, {} as {
145
- profile : { displayName: string; bio?: string };
146
- post : { title: string; body: string };
147
- reply : { body: string };
195
+ for (const thread of records) {
196
+ console.log(await thread.data.json());
197
+ }
198
+
199
+ // Fetch next page
200
+ if (cursor) {
201
+ const { records: nextPage } = await chat.records.query('thread', {
202
+ pagination: { limit: 20, cursor },
203
+ });
204
+ }
205
+ ```
206
+
207
+ #### `records.read(path, request)`
208
+
209
+ Read a single record by filter criteria.
210
+
211
+ ```ts
212
+ const { record } = await chat.records.read('thread', {
213
+ filter: { recordId: 'bafyrei...' },
148
214
  });
215
+
216
+ const data = await record.data.json();
149
217
  ```
150
218
 
151
- The `schemaMap` is a phantom type -- it exists only at compile time. Pass `{} as YourSchemaMap` as the second argument.
219
+ #### `records.delete(path, request)`
220
+
221
+ Delete a record by ID.
222
+
223
+ ```ts
224
+ const { status } = await chat.records.delete('thread', {
225
+ recordId: record.id,
226
+ });
227
+ ```
228
+
229
+ #### `records.subscribe(path, request?)`
230
+
231
+ Subscribe to real-time changes. Returns a `LiveQuery` with an initial snapshot plus a stream of change events.
232
+
233
+ ```ts
234
+ const { liveQuery } = await chat.records.subscribe('thread/message');
235
+
236
+ // Initial snapshot
237
+ for (const msg of liveQuery.records) {
238
+ console.log(await msg.data.json());
239
+ }
240
+
241
+ // Real-time updates
242
+ liveQuery.on('create', (record) => console.log('new:', record.id));
243
+ liveQuery.on('update', (record) => console.log('updated:', record.id));
244
+ liveQuery.on('delete', (record) => console.log('deleted:', record.id));
245
+ ```
246
+
247
+ All methods also accept a `from` option to query a remote DWN:
248
+
249
+ ```ts
250
+ const { records } = await chat.records.query('thread', {
251
+ from: 'did:dht:other-user...',
252
+ });
253
+ ```
152
254
 
153
255
  ---
154
256
 
@@ -156,74 +258,358 @@ The `schemaMap` is a phantom type -- it exists only at compile time. Pass `{} as
156
258
 
157
259
  Methods like `write`, `query`, and `read` return `Record` instances.
158
260
 
159
- **Properties**: `id`, `contextId`, `dataFormat`, `dateCreated`, `timestamp`, `datePublished`, `protocol`, `protocolPath`, `recipient`, `schema`, `dataCid`, `dataSize`, `published`.
160
-
161
- **Data accessors**:
261
+ **Properties**:
262
+
263
+ | Property | Description |
264
+ |----------|-------------|
265
+ | `id` | Unique record identifier |
266
+ | `contextId` | Context ID (scopes nested records to a parent thread) |
267
+ | `protocol` | Protocol URI |
268
+ | `protocolPath` | Path within the protocol structure (e.g. `'thread/message'`) |
269
+ | `schema` | Schema URI |
270
+ | `dataFormat` | MIME type of the data |
271
+ | `dataCid` | Content-addressed hash of the data |
272
+ | `dataSize` | Size of the data in bytes |
273
+ | `dateCreated` | ISO timestamp of creation |
274
+ | `timestamp` | ISO timestamp of most recent write |
275
+ | `datePublished` | ISO timestamp of publication (if published) |
276
+ | `published` | Whether the record is publicly readable |
277
+ | `author` | DID of the record author |
278
+ | `recipient` | DID of the intended recipient |
279
+ | `parentId` | Record ID of the parent record (for nested structures) |
280
+ | `tags` | Key-value metadata tags |
281
+ | `deleted` | Whether the record has been deleted |
282
+
283
+ **Data accessors** -- read the record payload in different formats:
162
284
 
163
285
  ```ts
164
- await record.data.text(); // string
165
- await record.data.json<MyType>(); // typed JSON
166
- await record.data.blob(); // Blob
167
- await record.data.bytes(); // Uint8Array
168
- await record.data.stream(); // ReadableStream
286
+ const text = await record.data.text(); // string
287
+ const obj = await record.data.json<MyType>(); // parsed JSON (typed)
288
+ const blob = await record.data.blob(); // Blob
289
+ const bytes = await record.data.bytes(); // Uint8Array
290
+ const stream = await record.data.stream(); // ReadableStream
169
291
  ```
170
292
 
171
- **Mutators** (return a new `Record` instance):
293
+ **Mutators**:
172
294
 
173
295
  ```ts
174
- const { record: updated } = await record.update({ data: { title: 'New Title', body: '...' } });
296
+ // Update the record's data
297
+ const { record: updated } = await record.update({
298
+ data: { title: 'Updated Title', body: '...' },
299
+ });
300
+
301
+ // Delete the record
175
302
  const { status } = await record.delete();
176
303
  ```
177
304
 
178
- **Side-effect methods** (return status only):
305
+ **Side-effect methods**:
179
306
 
180
307
  ```ts
181
- await record.send(targetDid); // send to a remote DWN
182
- await record.store(); // persist locally
183
- await record.import(); // import from a remote DWN
308
+ // Send the record to a remote DWN
309
+ await record.send(targetDid);
310
+
311
+ // Persist a remote record to the local DWN
312
+ await record.store();
313
+
314
+ // Import a record from a remote DWN into the local store
315
+ await record.import();
184
316
  ```
185
317
 
186
318
  ---
187
319
 
188
320
  ### LiveQuery (Subscriptions)
189
321
 
190
- `records.subscribe()` returns a `LiveQuery` that provides an initial snapshot plus real-time deduplicated change events.
322
+ `records.subscribe()` returns a `LiveQuery` that provides an initial snapshot of existing records plus a real-time stream of deduplicated change events.
191
323
 
192
324
  ```ts
193
- const { liveQuery } = await notes.records.subscribe('post');
325
+ const { liveQuery } = await chat.records.subscribe('thread/message');
326
+
327
+ // Initial snapshot
328
+ for (const msg of liveQuery.records) {
329
+ renderMessage(msg);
330
+ }
331
+
332
+ // Real-time changes
333
+ const offCreate = liveQuery.on('create', (record) => appendMessage(record));
334
+ const offUpdate = liveQuery.on('update', (record) => refreshMessage(record));
335
+ const offDelete = liveQuery.on('delete', (record) => removeMessage(record));
336
+
337
+ // Catch-all event (receives { type, record })
338
+ liveQuery.on('change', ({ type, record }) => {
339
+ console.log(`${type}: ${record.id}`);
340
+ });
194
341
 
195
- liveQuery.on('create', (record) => { /* new record */ });
196
- liveQuery.on('update', (record) => { /* updated record */ });
197
- liveQuery.on('delete', (record) => { /* deleted record */ });
342
+ // Unsubscribe from a specific handler
343
+ offCreate();
198
344
 
199
- // Clean up
345
+ // Close the subscription entirely
200
346
  await liveQuery.close();
201
347
  ```
202
348
 
349
+ `LiveQuery` extends `EventTarget`, so standard `addEventListener` / `removeEventListener` also work. The `.on()` method is a convenience wrapper that returns an unsubscribe function.
350
+
351
+ Events are automatically deduplicated against the initial snapshot -- you won't receive a `create` event for records already in the `records` array.
352
+
353
+ ---
354
+
355
+ ### `Web5.anonymous(options?)`
356
+
357
+ Creates a lightweight, read-only instance for querying public DWN data. No identity, vault, or signing keys are required.
358
+
359
+ ```ts
360
+ const { dwn } = Web5.anonymous();
361
+
362
+ // Query published records from someone's DWN
363
+ const { records } = await dwn.records.query({
364
+ from : 'did:dht:alice...',
365
+ filter : { protocol: 'https://example.com/notes', protocolPath: 'note' },
366
+ });
367
+
368
+ for (const record of records) {
369
+ console.log(record.id, await record.data.text());
370
+ }
371
+
372
+ // Read a specific record
373
+ const { record } = await dwn.records.read({
374
+ from : 'did:dht:alice...',
375
+ filter : { recordId: 'bafyrei...' },
376
+ });
377
+
378
+ // Count matching records
379
+ const { count } = await dwn.records.count({
380
+ from : 'did:dht:alice...',
381
+ filter : { protocol: 'https://example.com/notes', protocolPath: 'note' },
382
+ });
383
+
384
+ // Query published protocols
385
+ const { protocols } = await dwn.protocols.query({
386
+ from: 'did:dht:alice...',
387
+ });
388
+ ```
389
+
390
+ Returns `ReadOnlyRecord` instances (no `update`, `delete`, `send`, or `store` methods). All calls require a `from` DID since the reader has no local DWN.
391
+
392
+ ---
393
+
394
+ ## Cookbook
395
+
396
+ ### Nested Records
397
+
398
+ Protocols support hierarchical record structures. Child records reference their parent via `parentContextId`.
399
+
400
+ ```ts
401
+ const ChatProtocol = defineProtocol({
402
+ protocol : 'https://example.com/chat',
403
+ published : true,
404
+ types: {
405
+ thread : { schema: 'https://example.com/schemas/thread', dataFormats: ['application/json'] },
406
+ message : { schema: 'https://example.com/schemas/message', dataFormats: ['application/json'] },
407
+ },
408
+ structure: {
409
+ thread: {
410
+ message: {},
411
+ },
412
+ },
413
+ } as const, {} as {
414
+ thread : { title: string };
415
+ message : { text: string };
416
+ });
417
+
418
+ const chat = web5.using(ChatProtocol);
419
+ await chat.configure();
420
+
421
+ // Create a parent thread
422
+ const { record: thread } = await chat.records.write('thread', {
423
+ data: { title: 'General' },
424
+ });
425
+
426
+ // Write a message nested under the thread
427
+ const { record: msg } = await chat.records.write('thread/message', {
428
+ parentContextId : thread.contextId,
429
+ data : { text: 'Hello, world!' },
430
+ });
431
+
432
+ // Query messages within a specific thread
433
+ const { records: messages } = await chat.records.query('thread/message', {
434
+ filter: { parentId: thread.id },
435
+ });
436
+ ```
437
+
438
+ ### Querying with Filters and Pagination
439
+
440
+ ```ts
441
+ // Date-sorted, paginated query
442
+ const { records, cursor } = await notes.records.query('note', {
443
+ dateSort : 'createdDescending',
444
+ pagination : { limit: 10 },
445
+ });
446
+
447
+ // Fetch next page using the cursor
448
+ if (cursor) {
449
+ const { records: page2 } = await notes.records.query('note', {
450
+ dateSort : 'createdDescending',
451
+ pagination : { limit: 10, cursor },
452
+ });
453
+ }
454
+
455
+ // Filter by recipient
456
+ const { records: shared } = await notes.records.query('note', {
457
+ filter: { recipient: 'did:dht:bob...' },
458
+ });
459
+
460
+ // Query from a remote DWN
461
+ const { records: remote } = await notes.records.query('note', {
462
+ from: 'did:dht:alice...',
463
+ });
464
+ ```
465
+
466
+ ### Tags
467
+
468
+ Tags are key-value metadata attached to records, useful for filtering without parsing record data.
469
+
470
+ ```ts
471
+ const { record } = await notes.records.write('note', {
472
+ data : { title: 'Meeting Notes', body: '...' },
473
+ tags : { category: 'work', priority: 'high' },
474
+ });
475
+
476
+ // Query by tag
477
+ const { records } = await notes.records.query('note', {
478
+ filter: { tags: { category: 'work' } },
479
+ });
480
+ ```
481
+
482
+ > Note: tags must be declared in your protocol's type definition for the DWN engine to index them.
483
+
484
+ ### Publishing Records
485
+
486
+ Published records are publicly readable by anyone, including anonymous readers.
487
+
488
+ ```ts
489
+ const { record } = await notes.records.write('note', {
490
+ data : { title: 'Public Note', body: 'Visible to everyone' },
491
+ published : true,
492
+ });
493
+ ```
494
+
495
+ ### Reading Public Data Anonymously
496
+
497
+ ```ts
498
+ const { dwn } = Web5.anonymous();
499
+
500
+ const { records } = await dwn.records.query({
501
+ from : 'did:dht:alice...',
502
+ filter : {
503
+ protocol : 'https://example.com/notes',
504
+ protocolPath : 'note',
505
+ },
506
+ });
507
+
508
+ for (const record of records) {
509
+ const note = await record.data.json();
510
+ console.log(note.title);
511
+ }
512
+ ```
513
+
514
+ ### Sending Records to Remote DWNs
515
+
516
+ Records are initially written to the local DWN. Use `send()` to push them to a remote DWN, or rely on the automatic sync engine.
517
+
518
+ ```ts
519
+ // Explicitly send to your own remote DWN
520
+ await record.send(myDid);
521
+
522
+ // Send to someone else's DWN (requires protocol permissions)
523
+ await record.send('did:dht:bob...');
524
+ ```
525
+
526
+ The sync engine (enabled by default at 2-minute intervals) automatically synchronizes records between local and remote DWNs. For most use cases, you don't need to call `send()` manually.
527
+
203
528
  ---
204
529
 
205
530
  ## Advanced Usage
206
531
 
207
- For power users who need direct, unscoped DWN access (e.g. cross-protocol queries, raw permission management), import from the `@enbox/api/advanced` sub-path:
532
+ ### Unscoped DWN Access
533
+
534
+ For power users who need direct DWN access without protocol scoping (e.g. cross-protocol queries, raw permission management), import from the `@enbox/api/advanced` sub-path:
208
535
 
209
536
  ```ts
210
537
  import { DwnApi } from '@enbox/api/advanced';
211
538
  ```
212
539
 
213
- The `DwnApi` class provides raw `records`, `protocols`, and `permissions` accessors without protocol scoping. Most applications should use `web5.using()` instead.
540
+ The `DwnApi` class provides raw `records`, `protocols`, and `permissions` accessors without automatic protocol/path/schema injection. You must provide those fields manually in every call. Most applications should use `web5.using()` instead.
214
541
 
215
- ---
542
+ ### Permissions
216
543
 
217
- ## DID Operations
544
+ The DWN permission system supports fine-grained access control through permission requests, grants, and revocations.
218
545
 
219
546
  ```ts
220
- // Create a DID
221
- const myDid = await web5.did.create('dht');
547
+ import { DwnApi } from '@enbox/api/advanced';
548
+
549
+ // Query existing permission grants
550
+ const grants = await web5._dwn.permissions.queryGrants();
551
+
552
+ // Request permissions from another DWN
553
+ const request = await web5._dwn.permissions.request({
554
+ scope: {
555
+ interface : 'Records',
556
+ method : 'Write',
557
+ protocol : 'https://example.com/notes',
558
+ },
559
+ });
560
+
561
+ // Send the request to the target DWN
562
+ await request.send('did:dht:alice...');
563
+ ```
564
+
565
+ ### DID Operations
222
566
 
223
- // Resolve a DID
567
+ ```ts
568
+ // Resolve any DID
224
569
  const { didDocument } = await web5.did.resolve('did:dht:abc...');
225
570
  ```
226
571
 
572
+ ---
573
+
574
+ ## API Reference
575
+
576
+ ### Main Exports (`@enbox/api`)
577
+
578
+ | Export | Description |
579
+ |--------|-------------|
580
+ | `Web5` | Main entry point -- `connect()`, `anonymous()`, `using()` |
581
+ | `defineProtocol()` | Factory for creating typed protocol definitions |
582
+ | `TypedWeb5` | Protocol-scoped API returned by `web5.using()` |
583
+ | `Record` | Mutable record instance with data accessors and side-effect methods |
584
+ | `ReadOnlyRecord` | Immutable record for anonymous/read-only access |
585
+ | `LiveQuery` | Real-time subscription with initial snapshot and change events |
586
+ | `Protocol` | Protocol metadata wrapper |
587
+ | `PermissionGrant` | Permission grant record |
588
+ | `PermissionRequest` | Permission request record |
589
+ | `PermissionGrantRevocation` | Permission revocation record |
590
+ | `DidApi` | DID resolution |
591
+ | `VcApi` | Verifiable Credentials (not yet implemented) |
592
+ | `DwnReaderApi` | Read-only DWN API for anonymous access |
593
+
594
+ ### Advanced Export (`@enbox/api/advanced`)
595
+
596
+ | Export | Description |
597
+ |--------|-------------|
598
+ | `DwnApi` | Full unscoped DWN API with `records`, `protocols`, `permissions` |
599
+
600
+ ### Key Types
601
+
602
+ | Export | Description |
603
+ |--------|-------------|
604
+ | `TypedProtocol<D, M>` | Typed protocol wrapper with definition and schema map |
605
+ | `ProtocolPaths<D>` | Union of valid slash-delimited paths for a protocol definition |
606
+ | `SchemaMap` | Maps protocol type names to TypeScript interfaces |
607
+ | `Web5ConnectOptions` | Options for `Web5.connect()` |
608
+ | `Web5ConnectResult` | Return type of `Web5.connect()` |
609
+ | `RecordModel` | Structured data model of a record |
610
+ | `RecordChangeType` | `'create' \| 'update' \| 'delete'` |
611
+ | `RecordChange` | Change event payload `{ type, record }` |
612
+
227
613
  ## License
228
614
 
229
615
  Apache-2.0