@enbox/dwn-sdk-js 0.3.9 → 0.4.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.
Files changed (71) hide show
  1. package/dist/browser.mjs +11 -11
  2. package/dist/browser.mjs.map +4 -4
  3. package/dist/esm/generated/precompiled-validators.js +175 -512
  4. package/dist/esm/generated/precompiled-validators.js.map +1 -1
  5. package/dist/esm/src/core/dwn-error.js +1 -3
  6. package/dist/esm/src/core/dwn-error.js.map +1 -1
  7. package/dist/esm/src/core/messages-grant-authorization.js +1 -17
  8. package/dist/esm/src/core/messages-grant-authorization.js.map +1 -1
  9. package/dist/esm/src/core/protocol-authorization-validation.js +1 -1
  10. package/dist/esm/src/core/protocol-authorization-validation.js.map +1 -1
  11. package/dist/esm/src/core/replication-apply.js +200 -0
  12. package/dist/esm/src/core/replication-apply.js.map +1 -0
  13. package/dist/esm/src/dwn.js +212 -0
  14. package/dist/esm/src/dwn.js.map +1 -1
  15. package/dist/esm/src/handlers/messages-sync.js +66 -369
  16. package/dist/esm/src/handlers/messages-sync.js.map +1 -1
  17. package/dist/esm/src/index.js +1 -1
  18. package/dist/esm/src/index.js.map +1 -1
  19. package/dist/esm/src/interfaces/messages-sync.js +0 -11
  20. package/dist/esm/src/interfaces/messages-sync.js.map +1 -1
  21. package/dist/esm/tests/core/replication-apply.spec.js +220 -0
  22. package/dist/esm/tests/core/replication-apply.spec.js.map +1 -0
  23. package/dist/esm/tests/dwn.spec.js +139 -2
  24. package/dist/esm/tests/dwn.spec.js.map +1 -1
  25. package/dist/esm/tests/handlers/messages-sync.spec.js +1 -684
  26. package/dist/esm/tests/handlers/messages-sync.spec.js.map +1 -1
  27. package/dist/esm/tests/handlers/records-write.spec.js +2 -2
  28. package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
  29. package/dist/esm/tests/test-suite.js +0 -2
  30. package/dist/esm/tests/test-suite.js.map +1 -1
  31. package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
  32. package/dist/types/src/core/dwn-error.d.ts +1 -3
  33. package/dist/types/src/core/dwn-error.d.ts.map +1 -1
  34. package/dist/types/src/core/messages-grant-authorization.d.ts +0 -1
  35. package/dist/types/src/core/messages-grant-authorization.d.ts.map +1 -1
  36. package/dist/types/src/core/replication-apply.d.ts +93 -0
  37. package/dist/types/src/core/replication-apply.d.ts.map +1 -0
  38. package/dist/types/src/dwn.d.ts +22 -1
  39. package/dist/types/src/dwn.d.ts.map +1 -1
  40. package/dist/types/src/handlers/messages-sync.d.ts +10 -54
  41. package/dist/types/src/handlers/messages-sync.d.ts.map +1 -1
  42. package/dist/types/src/index.d.ts +3 -3
  43. package/dist/types/src/index.d.ts.map +1 -1
  44. package/dist/types/src/interfaces/messages-sync.d.ts +0 -3
  45. package/dist/types/src/interfaces/messages-sync.d.ts.map +1 -1
  46. package/dist/types/src/types/messages-types.d.ts +0 -18
  47. package/dist/types/src/types/messages-types.d.ts.map +1 -1
  48. package/dist/types/tests/core/replication-apply.spec.d.ts +2 -0
  49. package/dist/types/tests/core/replication-apply.spec.d.ts.map +1 -0
  50. package/dist/types/tests/dwn.spec.d.ts.map +1 -1
  51. package/dist/types/tests/handlers/messages-sync.spec.d.ts.map +1 -1
  52. package/dist/types/tests/test-suite.d.ts.map +1 -1
  53. package/package.json +1 -1
  54. package/src/core/dwn-error.ts +1 -3
  55. package/src/core/messages-grant-authorization.ts +1 -31
  56. package/src/core/protocol-authorization-validation.ts +2 -2
  57. package/src/core/replication-apply.ts +272 -0
  58. package/src/dwn.ts +296 -2
  59. package/src/handlers/messages-sync.ts +92 -585
  60. package/src/index.ts +3 -4
  61. package/src/interfaces/messages-sync.ts +8 -25
  62. package/src/types/messages-types.ts +0 -20
  63. package/dist/esm/src/sync/records-projection.js +0 -228
  64. package/dist/esm/src/sync/records-projection.js.map +0 -1
  65. package/dist/esm/tests/sync/records-projection.spec.js +0 -245
  66. package/dist/esm/tests/sync/records-projection.spec.js.map +0 -1
  67. package/dist/types/src/sync/records-projection.d.ts +0 -98
  68. package/dist/types/src/sync/records-projection.d.ts.map +0 -1
  69. package/dist/types/tests/sync/records-projection.spec.d.ts +0 -2
  70. package/dist/types/tests/sync/records-projection.spec.d.ts.map +0 -1
  71. package/src/sync/records-projection.ts +0 -328
@@ -2,8 +2,7 @@ import type { GenericMessage } from '../types/message-types.js';
2
2
  import type { MessageStore } from '../types/message-store.js';
3
3
  import type { StateIndex } from '../types/state-index.js';
4
4
  import type { HandlerDependencies, MethodHandler } from '../types/method-handler.js';
5
- import type { MessagesSyncDependencyEntry, MessagesSyncDiffEntry, MessagesSyncMessage, MessagesSyncReply } from '../types/messages-types.js';
6
- import type { RecordsProjectionScope, RecordsProjectionSnapshot } from '../sync/records-projection.js';
5
+ import type { MessagesSyncDiffEntry, MessagesSyncMessage, MessagesSyncReply } from '../types/messages-types.js';
7
6
 
8
7
  import { authenticate } from '../core/auth.js';
9
8
  import { DwnConstant } from '../core/dwn-constant.js';
@@ -14,39 +13,20 @@ import { messageReplyFromError } from '../core/message-reply.js';
14
13
  import { MessagesGrantAuthorization } from '../core/messages-grant-authorization.js';
15
14
  import { MessagesSync } from '../interfaces/messages-sync.js';
16
15
  import { Records } from '../utils/records.js';
17
- import { RecordsProjection } from '../sync/records-projection.js';
18
- import { RecordsWrite } from '../interfaces/records-write.js';
19
- import { SortDirection } from '../types/query-types.js';
20
16
  import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
21
- import { DwnInterfaceName, DwnMethodName } from '../enums/dwn-interface-method.js';
22
17
 
23
18
  /**
24
- * Maximum inline data size for diff responses aligned with the
25
- * {@link DwnConstant.maxDataSizeAllowedToBeEncoded} threshold (30 KB).
26
- * RecordsWrite data payloads smaller than this are base64url-encoded and
27
- * included directly in the diff reply. Larger payloads must be fetched
28
- * separately via MessagesRead.
19
+ * Maximum inline data size for diff responses, aligned with the
20
+ * {@link DwnConstant.maxDataSizeAllowedToBeEncoded} threshold.
29
21
  */
30
22
  const DEFAULT_MAX_INLINE_DATA_SIZE = DwnConstant.maxDataSizeAllowedToBeEncoded;
31
23
 
32
24
  type StoredMessageWithEncodedData = GenericMessage & { encodedData?: string };
33
- type StoredMessageWithInternalFields = GenericMessage & {
34
- encodedData?: unknown;
35
- initialWrite?: unknown;
36
- };
37
- type RecordsWriteProtocolDescriptor = GenericMessage['descriptor'] & { protocol?: unknown };
38
- type RecordsWriteProtocolMetadata = { protocol: string; messageTimestamp: string };
39
- type ProtocolsConfigureDefinitionDescriptor = GenericMessage['descriptor'] & {
40
- definition?: {
41
- protocol?: unknown;
42
- uses?: Record<string, unknown>;
43
- };
44
- };
45
- type ProjectionScopes = readonly [RecordsProjectionScope, ...RecordsProjectionScope[]];
46
-
47
25
 
48
26
  export class MessagesSyncHandler implements MethodHandler {
49
27
 
28
+ private _defaultHashHexCache?: Map<number, string>;
29
+
50
30
  constructor(private readonly deps: HandlerDependencies) { }
51
31
 
52
32
  public async handle({
@@ -68,33 +48,25 @@ export class MessagesSyncHandler implements MethodHandler {
68
48
  return messageReplyFromError(e, 401);
69
49
  }
70
50
 
71
- const { action } = message.descriptor;
72
- const projectionScopes = MessagesSyncHandler.getProjectionScopes(message);
73
-
74
51
  try {
75
- switch (action) {
76
- case 'root': {
77
- return await this.handleRoot(tenant, message, projectionScopes);
78
- }
52
+ switch (message.descriptor.action) {
53
+ case 'root':
54
+ return await this.handleRoot(tenant, message);
79
55
 
80
- case 'subtree': {
81
- return await this.handleSubtree(tenant, message, projectionScopes);
82
- }
56
+ case 'subtree':
57
+ return await this.handleSubtree(tenant, message);
83
58
 
84
- case 'leaves': {
85
- return await this.handleLeaves(tenant, message, projectionScopes);
86
- }
59
+ case 'leaves':
60
+ return await this.handleLeaves(tenant, message);
87
61
 
88
- case 'diff': {
62
+ case 'diff':
89
63
  return await this.handleDiff(tenant, message);
90
- }
91
64
 
92
- default: {
65
+ default:
93
66
  return {
94
- status: { code: 400, detail: `Unknown action: ${action as string}` },
67
+ status: { code: 400, detail: `Unknown action: ${message.descriptor.action as string}` },
95
68
  };
96
69
  }
97
- }
98
70
  } catch (e) {
99
71
  return messageReplyFromError(e, 500);
100
72
  }
@@ -103,46 +75,25 @@ export class MessagesSyncHandler implements MethodHandler {
103
75
  private async handleRoot(
104
76
  tenant: string,
105
77
  message: MessagesSyncMessage,
106
- projectionScopes: ProjectionScopes | undefined
107
78
  ): Promise<MessagesSyncReply> {
108
- const root = await this.getRootHex(tenant, message.descriptor.protocol, projectionScopes);
79
+ const root = hashToHex(await this.getIndexedRootHash(tenant, message.descriptor.protocol));
109
80
  return {
110
- status : { code: 200, detail: 'OK' },
111
- root : root,
81
+ status: { code: 200, detail: 'OK' },
82
+ root,
112
83
  };
113
84
  }
114
85
 
115
- private async getRootHex(
116
- tenant: string,
117
- protocol: string | undefined,
118
- projectionScopes: ProjectionScopes | undefined
119
- ): Promise<string> {
120
- if (projectionScopes === undefined) {
121
- const rootHash = await this.getIndexedRootHash(tenant, protocol);
122
- return hashToHex(rootHash);
123
- }
124
-
125
- return this.withProjectionSnapshot(tenant, projectionScopes, snapshot => snapshot.getRootHex());
126
- }
127
-
128
- private async getIndexedRootHash(
129
- tenant: string,
130
- protocol: string | undefined
131
- ): Promise<Uint8Array> {
132
- if (protocol === undefined) {
133
- return this.deps.stateIndex!.getRoot(tenant);
134
- }
135
-
136
- return this.deps.stateIndex!.getProtocolRoot(tenant, protocol);
137
- }
138
-
139
86
  private async handleSubtree(
140
87
  tenant: string,
141
88
  message: MessagesSyncMessage,
142
- projectionScopes: ProjectionScopes | undefined
143
89
  ): Promise<MessagesSyncReply> {
144
90
  const bitPath = MessagesSyncHandler.parseBitPrefix(message.descriptor.prefix!);
145
- const hash = await this.getSubtreeHash(tenant, message.descriptor.protocol, projectionScopes, bitPath);
91
+ const hash = await MessagesSyncHandler.getIndexedSubtreeHash(
92
+ this.deps.stateIndex!,
93
+ tenant,
94
+ message.descriptor.protocol,
95
+ bitPath,
96
+ );
146
97
 
147
98
  return {
148
99
  status : { code: 200, detail: 'OK' },
@@ -153,10 +104,14 @@ export class MessagesSyncHandler implements MethodHandler {
153
104
  private async handleLeaves(
154
105
  tenant: string,
155
106
  message: MessagesSyncMessage,
156
- projectionScopes: ProjectionScopes | undefined
157
107
  ): Promise<MessagesSyncReply> {
158
108
  const bitPath = MessagesSyncHandler.parseBitPrefix(message.descriptor.prefix!);
159
- const leaves = await this.getLeaves(tenant, message.descriptor.protocol, projectionScopes, bitPath);
109
+ const leaves = await MessagesSyncHandler.getIndexedLeaves(
110
+ this.deps.stateIndex!,
111
+ tenant,
112
+ message.descriptor.protocol,
113
+ bitPath,
114
+ );
160
115
 
161
116
  return {
162
117
  status : { code: 200, detail: 'OK' },
@@ -164,65 +119,15 @@ export class MessagesSyncHandler implements MethodHandler {
164
119
  };
165
120
  }
166
121
 
167
- private async getSubtreeHash(
168
- tenant: string,
169
- protocol: string | undefined,
170
- projectionScopes: ProjectionScopes | undefined,
171
- bitPath: boolean[]
172
- ): Promise<Uint8Array> {
173
- if (projectionScopes === undefined) {
174
- return this.getIndexedSubtreeHash(tenant, protocol, bitPath);
175
- }
176
-
177
- return this.withProjectionSnapshot(tenant, projectionScopes, snapshot => snapshot.getSubtreeHash(bitPath));
178
- }
179
-
180
- private async getLeaves(
181
- tenant: string,
182
- protocol: string | undefined,
183
- projectionScopes: ProjectionScopes | undefined,
184
- bitPath: boolean[]
185
- ): Promise<string[]> {
186
- if (projectionScopes === undefined) {
187
- return this.getIndexedLeaves(tenant, protocol, bitPath);
188
- }
189
-
190
- return this.withProjectionSnapshot(tenant, projectionScopes, snapshot => snapshot.getLeaves(bitPath));
191
- }
192
-
193
- private async getIndexedSubtreeHash(
194
- tenant: string,
195
- protocol: string | undefined,
196
- bitPath: boolean[]
197
- ): Promise<Uint8Array> {
198
- return MessagesSyncHandler.getIndexedSubtreeHashFromStateIndex(this.deps.stateIndex!, tenant, protocol, bitPath);
199
- }
200
-
201
- private async getIndexedLeaves(
202
- tenant: string,
203
- protocol: string | undefined,
204
- bitPath: boolean[]
205
- ): Promise<string[]> {
206
- return MessagesSyncHandler.getIndexedLeavesFromStateIndex(this.deps.stateIndex!, tenant, protocol, bitPath);
207
- }
208
-
209
122
  /**
210
- * Handle the 'diff' action: the client sends its subtree hashes at a given
211
- * depth, and the server compares them against its own tree to compute the
212
- * set difference in a single round-trip.
213
- *
214
- * Response includes:
215
- * - `onlyRemote`: messages the server has that the client doesn't, with
216
- * inline data for small payloads.
217
- * - `onlyLocal`: bit prefixes where the client has entries the server
218
- * doesn't (client can enumerate its own leaves for these prefixes).
123
+ * Computes a single-round diff between the client's sparse Merkle tree view
124
+ * and this DWN's full/protocol StateIndex tree.
219
125
  */
220
126
  private async handleDiff(
221
127
  tenant: string,
222
128
  message: MessagesSyncMessage,
223
129
  ): Promise<MessagesSyncReply> {
224
130
  const { protocol, hashes: clientHashes, depth } = message.descriptor;
225
- const projectionScopes = MessagesSyncHandler.getProjectionScopes(message);
226
131
 
227
132
  if (!clientHashes || depth === undefined) {
228
133
  return {
@@ -230,121 +135,98 @@ export class MessagesSyncHandler implements MethodHandler {
230
135
  };
231
136
  }
232
137
 
233
- const stateIndex = this.deps.stateIndex!;
234
- const projectionSnapshot = await this.createProjectionSnapshot(tenant, projectionScopes);
235
138
  const onlyRemoteCids: string[] = [];
236
139
  const onlyLocalPrefixes: string[] = [];
140
+ const defaultHashHex = await this.getDefaultHashHex(depth);
141
+ const allPrefixes = new Set<string>();
237
142
 
238
- try {
239
- // Get the default (empty subtree) hash at the given depth so we can
240
- // filter out client entries that represent empty subtrees.
241
- const defaultHashHex = await this.getDefaultHashHex(depth);
242
-
243
- // Build the set of all prefixes at the given depth that either side has.
244
- // Filter out client prefixes whose hash equals the default (empty subtree)
245
- // hash — these represent empty subtrees and should be treated the same as
246
- // omitted prefixes.
247
- const allPrefixes = new Set<string>();
248
- for (const [pfx, hash] of Object.entries(clientHashes)) {
249
- if (hash !== defaultHashHex) {
250
- allPrefixes.add(pfx);
251
- }
252
- }
253
-
254
- // Enumerate server-side non-empty prefixes by walking the server's
255
- // tree to the given depth and collecting leaf prefixes.
256
- const serverHashes = await this.collectSubtreeHashes(tenant, protocol, projectionSnapshot, depth);
257
- for (const prefix of Object.keys(serverHashes)) {
143
+ for (const [prefix, hash] of Object.entries(clientHashes)) {
144
+ if (hash !== defaultHashHex) {
258
145
  allPrefixes.add(prefix);
259
146
  }
147
+ }
260
148
 
261
- // Compare each prefix's hash between client and server.
262
- for (const pfx of allPrefixes) {
263
- const clientHash = clientHashes[pfx]; // undefined if client has empty subtree
264
- const serverHash = serverHashes[pfx]; // undefined if server has empty subtree
149
+ const serverHashes = await this.collectSubtreeHashes(tenant, protocol, depth);
150
+ for (const prefix of Object.keys(serverHashes)) {
151
+ allPrefixes.add(prefix);
152
+ }
265
153
 
266
- if (clientHash === serverHash) {
267
- // Identical subtree — skip.
268
- continue;
269
- }
154
+ for (const prefix of allPrefixes) {
155
+ const clientHash = clientHashes[prefix];
156
+ const serverHash = serverHashes[prefix];
270
157
 
271
- if (serverHash === undefined) {
272
- // Client has entries the server doesn't.
273
- onlyLocalPrefixes.push(pfx);
274
- continue;
275
- }
158
+ if (clientHash === serverHash) {
159
+ continue;
160
+ }
276
161
 
277
- const bitPath = MessagesSyncHandler.parseBitPrefix(pfx);
278
- const serverLeaves = await MessagesSyncHandler.getServerLeaves(
279
- stateIndex,
280
- tenant,
281
- protocol,
282
- projectionSnapshot,
283
- bitPath
284
- );
285
-
286
- onlyRemoteCids.push(...serverLeaves);
287
- if (clientHash !== undefined) {
288
- // Both sides have entries but they differ. The client will enumerate
289
- // its own leaves for this prefix and de-duplicate server leaves.
290
- onlyLocalPrefixes.push(pfx);
291
- }
162
+ if (serverHash === undefined) {
163
+ onlyLocalPrefixes.push(prefix);
164
+ continue;
292
165
  }
293
- } finally {
294
- await projectionSnapshot?.close();
295
- }
296
166
 
297
- // Build response entries with inline message data where possible.
298
- const onlyRemote = await this.buildDiffEntries(tenant, onlyRemoteCids);
299
- const dependencies = projectionScopes === undefined
300
- ? []
301
- : await this.buildProjectedDependencyEntries(tenant, onlyRemote);
167
+ const bitPath = MessagesSyncHandler.parseBitPrefix(prefix);
168
+ const serverLeaves = await MessagesSyncHandler.getIndexedLeaves(
169
+ this.deps.stateIndex!,
170
+ tenant,
171
+ protocol,
172
+ bitPath,
173
+ );
174
+
175
+ onlyRemoteCids.push(...serverLeaves);
176
+ if (clientHash !== undefined) {
177
+ onlyLocalPrefixes.push(prefix);
178
+ }
179
+ }
302
180
 
303
181
  return {
304
- status : { code: 200, detail: 'OK' },
305
- onlyRemote,
306
- onlyLocal : onlyLocalPrefixes,
307
- ...(dependencies.length > 0 ? { dependencies } : {}),
182
+ status : { code: 200, detail: 'OK' },
183
+ onlyRemote : await this.buildDiffEntries(tenant, onlyRemoteCids),
184
+ onlyLocal : onlyLocalPrefixes,
308
185
  };
309
186
  }
310
187
 
188
+ private async getIndexedRootHash(
189
+ tenant: string,
190
+ protocol: string | undefined
191
+ ): Promise<Uint8Array> {
192
+ if (protocol === undefined) {
193
+ return this.deps.stateIndex!.getRoot(tenant);
194
+ }
195
+
196
+ return this.deps.stateIndex!.getProtocolRoot(tenant, protocol);
197
+ }
198
+
311
199
  /**
312
- * Walk the server's SMT to the given depth and collect all non-empty
313
- * subtree hashes as a `{ prefix: hexHash }` map.
200
+ * Walks this DWN's StateIndex tree to the requested depth and returns only
201
+ * non-empty subtree hashes keyed by bit prefix.
314
202
  */
315
203
  private async collectSubtreeHashes(
316
204
  tenant: string,
317
205
  protocol: string | undefined,
318
- projectionSnapshot: RecordsProjectionSnapshot | undefined,
319
206
  depth: number,
320
207
  ): Promise<Record<string, string>> {
321
- const stateIndex = this.deps.stateIndex!;
322
208
  const result: Record<string, string> = {};
323
209
 
324
210
  const walk = async (prefix: string, currentDepth: number): Promise<void> => {
325
211
  const bitPath = MessagesSyncHandler.parseBitPrefix(prefix);
326
- const hash = await MessagesSyncHandler.getServerSubtreeHash(
327
- stateIndex,
212
+ const hash = await MessagesSyncHandler.getIndexedSubtreeHash(
213
+ this.deps.stateIndex!,
328
214
  tenant,
329
215
  protocol,
330
- projectionSnapshot,
331
- bitPath
216
+ bitPath,
332
217
  );
333
218
  const hexHash = hashToHex(hash);
334
219
  const defaultHashHex = await this.getDefaultHashHex(currentDepth);
335
220
 
336
221
  if (hexHash === defaultHashHex) {
337
- // Empty subtree — don't include in the result.
338
222
  return;
339
223
  }
340
224
 
341
225
  if (currentDepth >= depth) {
342
- // Reached target depth with a non-empty subtree.
343
226
  result[prefix] = hexHash;
344
227
  return;
345
228
  }
346
229
 
347
- // Recurse into children.
348
230
  await Promise.all([
349
231
  walk(prefix + '0', currentDepth + 1),
350
232
  walk(prefix + '1', currentDepth + 1),
@@ -355,50 +237,7 @@ export class MessagesSyncHandler implements MethodHandler {
355
237
  return result;
356
238
  }
357
239
 
358
- private async createProjectionSnapshot(
359
- tenant: string,
360
- projectionScopes: ProjectionScopes | undefined
361
- ): Promise<RecordsProjectionSnapshot | undefined> {
362
- if (projectionScopes === undefined) {
363
- return undefined;
364
- }
365
-
366
- return RecordsProjection.createSnapshot({
367
- tenant,
368
- messageStore : this.deps.messageStore,
369
- scopes : projectionScopes,
370
- });
371
- }
372
-
373
- private static async getServerLeaves(
374
- stateIndex: StateIndex,
375
- tenant: string,
376
- protocol: string | undefined,
377
- projectionSnapshot: RecordsProjectionSnapshot | undefined,
378
- bitPath: boolean[]
379
- ): Promise<string[]> {
380
- if (projectionSnapshot === undefined) {
381
- return MessagesSyncHandler.getIndexedLeavesFromStateIndex(stateIndex, tenant, protocol, bitPath);
382
- }
383
-
384
- return projectionSnapshot.getLeaves(bitPath);
385
- }
386
-
387
- private static async getServerSubtreeHash(
388
- stateIndex: StateIndex,
389
- tenant: string,
390
- protocol: string | undefined,
391
- projectionSnapshot: RecordsProjectionSnapshot | undefined,
392
- bitPath: boolean[]
393
- ): Promise<Uint8Array> {
394
- if (projectionSnapshot === undefined) {
395
- return MessagesSyncHandler.getIndexedSubtreeHashFromStateIndex(stateIndex, tenant, protocol, bitPath);
396
- }
397
-
398
- return projectionSnapshot.getSubtreeHash(bitPath);
399
- }
400
-
401
- private static async getIndexedLeavesFromStateIndex(
240
+ private static async getIndexedLeaves(
402
241
  stateIndex: StateIndex,
403
242
  tenant: string,
404
243
  protocol: string | undefined,
@@ -411,7 +250,7 @@ export class MessagesSyncHandler implements MethodHandler {
411
250
  return stateIndex.getProtocolLeaves(tenant, protocol, bitPath);
412
251
  }
413
252
 
414
- private static async getIndexedSubtreeHashFromStateIndex(
253
+ private static async getIndexedSubtreeHash(
415
254
  stateIndex: StateIndex,
416
255
  tenant: string,
417
256
  protocol: string | undefined,
@@ -424,10 +263,6 @@ export class MessagesSyncHandler implements MethodHandler {
424
263
  return stateIndex.getProtocolSubtreeHash(tenant, protocol, bitPath);
425
264
  }
426
265
 
427
- /**
428
- * Get the hex-encoded default hash for a given depth. Lazily cached.
429
- */
430
- private _defaultHashHexCache?: Map<number, string>;
431
266
  private async getDefaultHashHex(depth: number): Promise<string> {
432
267
  if (this._defaultHashHexCache === undefined) {
433
268
  const { initDefaultHashes } = await import('../smt/smt-utils.js');
@@ -441,9 +276,8 @@ export class MessagesSyncHandler implements MethodHandler {
441
276
  }
442
277
 
443
278
  /**
444
- * Build diff response entries for the given messageCids.
445
- * Reads each message from the MessageStore and, for small RecordsWrite
446
- * payloads, inlines the data as base64url.
279
+ * Builds diff entries and inlines data when it is small enough for the
280
+ * MessagesSync response. Large record data remains fetch-by-CID.
447
281
  */
448
282
  private async buildDiffEntries(
449
283
  tenant: string,
@@ -454,22 +288,18 @@ export class MessagesSyncHandler implements MethodHandler {
454
288
  for (const messageCid of messageCids) {
455
289
  const { message, encodedData: inlineData, data } = await this.readMessageByCid(tenant, messageCid);
456
290
  if (!message) {
457
- // Message was deleted between diff computation and read — skip.
458
291
  continue;
459
292
  }
460
293
 
461
294
  const entry: MessagesSyncDiffEntry = { messageCid, message };
462
295
 
463
- // Use inline data from the MessageStore if available (small payloads).
464
296
  if (inlineData) {
465
297
  entry.encodedData = inlineData;
466
298
  } else if (data) {
467
- // Data is in the DataStore — inline it if small enough.
468
299
  const bytes = await MessagesSyncHandler.streamToBytes(data);
469
300
  if (bytes.byteLength <= DEFAULT_MAX_INLINE_DATA_SIZE) {
470
301
  entry.encodedData = Encoder.bytesToBase64Url(bytes);
471
302
  }
472
- // Large payloads are NOT inlined — client fetches via MessagesRead.
473
303
  }
474
304
 
475
305
  entries.push(entry);
@@ -478,281 +308,6 @@ export class MessagesSyncHandler implements MethodHandler {
478
308
  return entries;
479
309
  }
480
310
 
481
- private async buildProjectedDependencyEntries(
482
- tenant: string,
483
- primaryEntries: MessagesSyncDiffEntry[],
484
- ): Promise<MessagesSyncDependencyEntry[]> {
485
- const dependenciesByCid = new Map<string, MessagesSyncDependencyEntry>();
486
- const configsByProtocol = new Map<string, GenericMessage[]>();
487
-
488
- for (const primaryEntry of primaryEntries) {
489
- const protocolMetadata = MessagesSyncHandler.recordsWriteProtocolMetadata(primaryEntry.message);
490
- if (protocolMetadata !== undefined) {
491
- await this.addProtocolConfigClosureDependencies(
492
- tenant,
493
- protocolMetadata.protocol,
494
- protocolMetadata.messageTimestamp,
495
- primaryEntry.messageCid,
496
- configsByProtocol,
497
- dependenciesByCid,
498
- );
499
- continue;
500
- }
501
-
502
- const initialWrite = await this.readRecordsDeleteInitialWrite(tenant, primaryEntry.message);
503
- if (initialWrite === undefined) {
504
- continue;
505
- }
506
-
507
- await MessagesSyncHandler.addRecordsInitialWriteDependency(
508
- primaryEntry.messageCid,
509
- initialWrite,
510
- dependenciesByCid,
511
- );
512
-
513
- const initialWriteMetadata = MessagesSyncHandler.recordsWriteProtocolMetadata(initialWrite);
514
- if (initialWriteMetadata === undefined) {
515
- continue;
516
- }
517
-
518
- await this.addProtocolConfigClosureDependencies(
519
- tenant,
520
- initialWriteMetadata.protocol,
521
- initialWriteMetadata.messageTimestamp,
522
- primaryEntry.messageCid,
523
- configsByProtocol,
524
- dependenciesByCid,
525
- );
526
- }
527
-
528
- return [...dependenciesByCid.values()];
529
- }
530
-
531
- private async readRecordsDeleteInitialWrite(
532
- tenant: string,
533
- message: GenericMessage | undefined,
534
- ): Promise<GenericMessage | undefined> {
535
- const recordId = MessagesSyncHandler.recordsDeleteRecordId(message);
536
- if (recordId === undefined) {
537
- return undefined;
538
- }
539
-
540
- const { messages } = await this.deps.messageStore.query(tenant, [{
541
- interface : DwnInterfaceName.Records,
542
- method : DwnMethodName.Write,
543
- recordId,
544
- }]);
545
- const initialWrite = await RecordsWrite.getInitialWrite(messages);
546
- return initialWrite === undefined ? undefined : MessagesSyncHandler.toWireMessage(initialWrite);
547
- }
548
-
549
- private static async addRecordsInitialWriteDependency(
550
- rootMessageCid: string,
551
- dependency: GenericMessage,
552
- dependenciesByCid: Map<string, MessagesSyncDependencyEntry>,
553
- ): Promise<void> {
554
- const dependencyCid = await Message.getCid(dependency);
555
- if (dependenciesByCid.has(dependencyCid)) {
556
- return;
557
- }
558
-
559
- dependenciesByCid.set(dependencyCid, {
560
- dependencyClass : 'recordsInitialWrite',
561
- messageCid : dependencyCid,
562
- message : dependency,
563
- rootMessageCid,
564
- });
565
- }
566
-
567
- private async addProtocolConfigClosureDependencies(
568
- tenant: string,
569
- rootProtocol: string,
570
- rootMessageTimestamp: string,
571
- rootMessageCid: string,
572
- configsByProtocol: Map<string, GenericMessage[]>,
573
- dependenciesByCid: Map<string, MessagesSyncDependencyEntry>,
574
- ): Promise<void> {
575
- // Dependency hints are not part of the projected root; they are advisory
576
- // bootstrap data for the receiver. Bound each closure to the primary
577
- // RecordsWrite timestamp because protocol authorization uses the definition
578
- // active when that record was created, not whatever config is newest today.
579
- const visitedProtocols = new Set<string>();
580
- const pendingProtocols = [rootProtocol];
581
-
582
- for (
583
- let protocol = MessagesSyncHandler.takeNextUnvisitedProtocol(pendingProtocols, visitedProtocols);
584
- protocol !== undefined;
585
- protocol = MessagesSyncHandler.takeNextUnvisitedProtocol(pendingProtocols, visitedProtocols)
586
- ) {
587
- const configs = await this.getCachedGoverningProtocolsConfigure(
588
- tenant,
589
- protocol,
590
- rootMessageTimestamp,
591
- configsByProtocol,
592
- );
593
- await MessagesSyncHandler.addProtocolConfigDependencies({
594
- configs,
595
- rootMessageCid,
596
- visitedProtocols,
597
- pendingProtocols,
598
- dependenciesByCid,
599
- });
600
- }
601
- }
602
-
603
- private async getCachedGoverningProtocolsConfigure(
604
- tenant: string,
605
- protocol: string,
606
- messageTimestamp: string,
607
- configsByProtocol: Map<string, GenericMessage[]>,
608
- ): Promise<GenericMessage[]> {
609
- const configCacheKey = JSON.stringify([protocol, messageTimestamp]);
610
- const cachedConfigs = configsByProtocol.get(configCacheKey);
611
- if (cachedConfigs !== undefined) {
612
- return cachedConfigs;
613
- }
614
-
615
- const configs = await this.readGoverningProtocolsConfigure(tenant, protocol, messageTimestamp);
616
- configsByProtocol.set(configCacheKey, configs);
617
- return configs;
618
- }
619
-
620
- private static async addProtocolConfigDependencies({
621
- configs,
622
- rootMessageCid,
623
- visitedProtocols,
624
- pendingProtocols,
625
- dependenciesByCid,
626
- }: {
627
- configs: GenericMessage[];
628
- rootMessageCid: string;
629
- visitedProtocols: Set<string>;
630
- pendingProtocols: string[];
631
- dependenciesByCid: Map<string, MessagesSyncDependencyEntry>;
632
- }): Promise<void> {
633
- for (const dependency of configs) {
634
- await MessagesSyncHandler.addProtocolConfigDependency(rootMessageCid, dependency, dependenciesByCid);
635
- MessagesSyncHandler.queueUnvisitedProtocols(
636
- MessagesSyncHandler.protocolsConfigureUses(dependency),
637
- visitedProtocols,
638
- pendingProtocols,
639
- );
640
- }
641
- }
642
-
643
- private static async addProtocolConfigDependency(
644
- rootMessageCid: string,
645
- dependency: GenericMessage,
646
- dependenciesByCid: Map<string, MessagesSyncDependencyEntry>,
647
- ): Promise<void> {
648
- const dependencyCid = await Message.getCid(dependency);
649
- if (dependenciesByCid.has(dependencyCid)) {
650
- return;
651
- }
652
-
653
- dependenciesByCid.set(dependencyCid, {
654
- dependencyClass : 'protocolsConfigure',
655
- messageCid : dependencyCid,
656
- message : dependency,
657
- rootMessageCid,
658
- });
659
- }
660
-
661
- private static takeNextUnvisitedProtocol(
662
- pendingProtocols: string[],
663
- visitedProtocols: Set<string>,
664
- ): string | undefined {
665
- while (pendingProtocols.length > 0) {
666
- const protocol = pendingProtocols.shift()!;
667
- if (visitedProtocols.has(protocol)) {
668
- continue;
669
- }
670
- visitedProtocols.add(protocol);
671
- return protocol;
672
- }
673
- return undefined;
674
- }
675
-
676
- private static queueUnvisitedProtocols(
677
- protocols: string[],
678
- visitedProtocols: Set<string>,
679
- pendingProtocols: string[],
680
- ): void {
681
- for (const protocol of protocols) {
682
- if (!visitedProtocols.has(protocol)) {
683
- pendingProtocols.push(protocol);
684
- }
685
- }
686
- }
687
-
688
- private static protocolsConfigureUses(message: GenericMessage): string[] {
689
- if (
690
- message.descriptor.interface !== DwnInterfaceName.Protocols ||
691
- message.descriptor.method !== DwnMethodName.Configure
692
- ) {
693
- return [];
694
- }
695
-
696
- const uses = (message.descriptor as ProtocolsConfigureDefinitionDescriptor).definition?.uses;
697
- return uses === undefined
698
- ? []
699
- : Object.values(uses).filter((protocol): protocol is string => typeof protocol === 'string');
700
- }
701
-
702
- private async readGoverningProtocolsConfigure(
703
- tenant: string,
704
- protocol: string,
705
- messageTimestamp: string,
706
- ): Promise<GenericMessage[]> {
707
- const { messages } = await this.deps.messageStore.query(
708
- tenant,
709
- [{
710
- interface : DwnInterfaceName.Protocols,
711
- method : DwnMethodName.Configure,
712
- protocol,
713
- messageTimestamp : { lte: messageTimestamp },
714
- }],
715
- { messageTimestamp: SortDirection.Descending },
716
- );
717
-
718
- const governingMessage = await Message.getNewestMessage(messages);
719
- return governingMessage === undefined ? [] : [governingMessage];
720
- }
721
-
722
- private static recordsWriteProtocolMetadata(message: GenericMessage | undefined): RecordsWriteProtocolMetadata | undefined {
723
- if (
724
- message?.descriptor.interface !== DwnInterfaceName.Records ||
725
- message.descriptor.method !== DwnMethodName.Write
726
- ) {
727
- return undefined;
728
- }
729
-
730
- const protocol = (message.descriptor as RecordsWriteProtocolDescriptor).protocol;
731
- return typeof protocol === 'string'
732
- ? { protocol, messageTimestamp: message.descriptor.messageTimestamp }
733
- : undefined;
734
- }
735
-
736
- private static recordsDeleteRecordId(message: GenericMessage | undefined): string | undefined {
737
- if (
738
- message?.descriptor.interface !== DwnInterfaceName.Records ||
739
- message.descriptor.method !== DwnMethodName.Delete
740
- ) {
741
- return undefined;
742
- }
743
-
744
- const recordId = (message.descriptor as Record<string, unknown>).recordId;
745
- return typeof recordId === 'string' ? recordId : undefined;
746
- }
747
-
748
- private static toWireMessage(message: GenericMessage): GenericMessage {
749
- const { encodedData: _encodedData, initialWrite: _initialWrite, ...wireMessage } = message as StoredMessageWithInternalFields;
750
- return wireMessage;
751
- }
752
-
753
- /**
754
- * Read a message and its data from the MessageStore + DataStore by CID.
755
- */
756
311
  private async readMessageByCid(
757
312
  tenant: string,
758
313
  messageCid: string,
@@ -762,11 +317,6 @@ export class MessagesSyncHandler implements MethodHandler {
762
317
  return {};
763
318
  }
764
319
 
765
- // Extract and strip `encodedData` from the stored message.
766
- // `encodedData` is an internal storage optimization for small payloads
767
- // that are stored inline in the MessageStore rather than in the DataStore.
768
- // It must not be included in the wire-format message (the recipient's
769
- // DWN would reject it as an unexpected top-level property).
770
320
  let inlineEncodedData: string | undefined;
771
321
  if (MessagesSyncHandler.hasEncodedData(storedMessage)) {
772
322
  inlineEncodedData = storedMessage.encodedData;
@@ -774,20 +324,11 @@ export class MessagesSyncHandler implements MethodHandler {
774
324
  }
775
325
 
776
326
  let data: ReadableStream<Uint8Array> | undefined;
777
-
778
- // Check if this is a RecordsWrite with data that is small enough to inline.
779
327
  if (inlineEncodedData === undefined && Records.isRecordsWrite(storedMessage)) {
780
328
  const { dataCid, dataSize } = storedMessage.descriptor;
781
- if (dataSize <= DEFAULT_MAX_INLINE_DATA_SIZE) {
782
- // Some stores may have small payload bytes in DataStore instead of encodedData.
783
- if (this.deps.dataStore) {
784
- // DataStore uses recordId, not messageCid. For RecordsWrite,
785
- // recordId is a top-level message property.
786
- const dataResult = await this.deps.dataStore.get(tenant, storedMessage.recordId, dataCid);
787
- if (dataResult?.dataStream) {
788
- data = dataResult.dataStream;
789
- }
790
- }
329
+ if (dataSize <= DEFAULT_MAX_INLINE_DATA_SIZE && this.deps.dataStore) {
330
+ const dataResult = await this.deps.dataStore.get(tenant, storedMessage.recordId, dataCid);
331
+ data = dataResult?.dataStream;
791
332
  }
792
333
  }
793
334
 
@@ -798,38 +339,6 @@ export class MessagesSyncHandler implements MethodHandler {
798
339
  return 'encodedData' in message && typeof message.encodedData === 'string';
799
340
  }
800
341
 
801
- private static getProjectionScopes(
802
- message: MessagesSyncMessage,
803
- ): ProjectionScopes | undefined {
804
- const { projectionScopes } = message.descriptor;
805
- if (projectionScopes === undefined) {
806
- return undefined;
807
- }
808
-
809
- return projectionScopes as [RecordsProjectionScope, ...RecordsProjectionScope[]];
810
- }
811
-
812
- private async withProjectionSnapshot<T>(
813
- tenant: string,
814
- scopes: readonly [RecordsProjectionScope, ...RecordsProjectionScope[]],
815
- fn: (snapshot: RecordsProjectionSnapshot) => Promise<T>,
816
- ): Promise<T> {
817
- const snapshot = await RecordsProjection.createSnapshot({
818
- tenant : tenant,
819
- messageStore : this.deps.messageStore,
820
- scopes : scopes,
821
- });
822
-
823
- try {
824
- return await fn(snapshot);
825
- } finally {
826
- await snapshot.close();
827
- }
828
- }
829
-
830
- /**
831
- * Read a ReadableStream to completion and return the bytes.
832
- */
833
342
  private static async streamToBytes(stream: ReadableStream<Uint8Array>): Promise<Uint8Array> {
834
343
  const reader = stream.getReader();
835
344
  const chunks: Uint8Array[] = [];
@@ -851,9 +360,6 @@ export class MessagesSyncHandler implements MethodHandler {
851
360
  return result;
852
361
  }
853
362
 
854
- /**
855
- * Parse a bit prefix string (e.g. "0110101") into a boolean array.
856
- */
857
363
  private static parseBitPrefix(prefix: string): boolean[] {
858
364
  if (!/^[01]*$/.test(prefix)) {
859
365
  throw new DwnError(
@@ -889,8 +395,9 @@ export class MessagesSyncHandler implements MethodHandler {
889
395
  permissionGrants,
890
396
  messageStore
891
397
  });
892
- } else {
893
- throw new DwnError(DwnErrorCode.MessagesSyncAuthorizationFailed, 'message failed authorization');
398
+ return;
894
399
  }
400
+
401
+ throw new DwnError(DwnErrorCode.MessagesSyncAuthorizationFailed, 'message failed authorization');
895
402
  }
896
403
  }