@enbox/dwn-sdk-js 0.3.8 → 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 (79) 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/handlers/records-write.js +18 -12
  18. package/dist/esm/src/handlers/records-write.js.map +1 -1
  19. package/dist/esm/src/index.js +1 -1
  20. package/dist/esm/src/index.js.map +1 -1
  21. package/dist/esm/src/interfaces/messages-sync.js +0 -11
  22. package/dist/esm/src/interfaces/messages-sync.js.map +1 -1
  23. package/dist/esm/tests/core/replication-apply.spec.js +220 -0
  24. package/dist/esm/tests/core/replication-apply.spec.js.map +1 -0
  25. package/dist/esm/tests/dwn.spec.js +139 -2
  26. package/dist/esm/tests/dwn.spec.js.map +1 -1
  27. package/dist/esm/tests/features/records-record-limit.spec.js +14 -0
  28. package/dist/esm/tests/features/records-record-limit.spec.js.map +1 -1
  29. package/dist/esm/tests/handlers/messages-sync.spec.js +1 -684
  30. package/dist/esm/tests/handlers/messages-sync.spec.js.map +1 -1
  31. package/dist/esm/tests/handlers/records-write.spec.js +43 -2
  32. package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
  33. package/dist/esm/tests/test-suite.js +0 -2
  34. package/dist/esm/tests/test-suite.js.map +1 -1
  35. package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
  36. package/dist/types/src/core/dwn-error.d.ts +1 -3
  37. package/dist/types/src/core/dwn-error.d.ts.map +1 -1
  38. package/dist/types/src/core/messages-grant-authorization.d.ts +0 -1
  39. package/dist/types/src/core/messages-grant-authorization.d.ts.map +1 -1
  40. package/dist/types/src/core/replication-apply.d.ts +93 -0
  41. package/dist/types/src/core/replication-apply.d.ts.map +1 -0
  42. package/dist/types/src/dwn.d.ts +22 -1
  43. package/dist/types/src/dwn.d.ts.map +1 -1
  44. package/dist/types/src/handlers/messages-sync.d.ts +10 -54
  45. package/dist/types/src/handlers/messages-sync.d.ts.map +1 -1
  46. package/dist/types/src/handlers/records-write.d.ts.map +1 -1
  47. package/dist/types/src/index.d.ts +3 -3
  48. package/dist/types/src/index.d.ts.map +1 -1
  49. package/dist/types/src/interfaces/messages-sync.d.ts +0 -3
  50. package/dist/types/src/interfaces/messages-sync.d.ts.map +1 -1
  51. package/dist/types/src/types/messages-types.d.ts +0 -18
  52. package/dist/types/src/types/messages-types.d.ts.map +1 -1
  53. package/dist/types/tests/core/replication-apply.spec.d.ts +2 -0
  54. package/dist/types/tests/core/replication-apply.spec.d.ts.map +1 -0
  55. package/dist/types/tests/dwn.spec.d.ts.map +1 -1
  56. package/dist/types/tests/features/records-record-limit.spec.d.ts.map +1 -1
  57. package/dist/types/tests/handlers/messages-sync.spec.d.ts.map +1 -1
  58. package/dist/types/tests/handlers/records-write.spec.d.ts.map +1 -1
  59. package/dist/types/tests/test-suite.d.ts.map +1 -1
  60. package/package.json +1 -1
  61. package/src/core/dwn-error.ts +1 -3
  62. package/src/core/messages-grant-authorization.ts +1 -31
  63. package/src/core/protocol-authorization-validation.ts +2 -2
  64. package/src/core/replication-apply.ts +272 -0
  65. package/src/dwn.ts +296 -2
  66. package/src/handlers/messages-sync.ts +92 -585
  67. package/src/handlers/records-write.ts +18 -13
  68. package/src/index.ts +3 -4
  69. package/src/interfaces/messages-sync.ts +8 -25
  70. package/src/types/messages-types.ts +0 -20
  71. package/dist/esm/src/sync/records-projection.js +0 -228
  72. package/dist/esm/src/sync/records-projection.js.map +0 -1
  73. package/dist/esm/tests/sync/records-projection.spec.js +0 -245
  74. package/dist/esm/tests/sync/records-projection.spec.js.map +0 -1
  75. package/dist/types/src/sync/records-projection.d.ts +0 -98
  76. package/dist/types/src/sync/records-projection.d.ts.map +0 -1
  77. package/dist/types/tests/sync/records-projection.spec.d.ts +0 -2
  78. package/dist/types/tests/sync/records-projection.spec.d.ts.map +0 -1
  79. package/src/sync/records-projection.ts +0 -328
@@ -1,328 +0,0 @@
1
- import type { Filter } from '../types/query-types.js';
2
- import type { Hash } from '../types/smt-types.js';
3
- import type { MessageStore, MessageStoreOptions } from '../types/message-store.js';
4
-
5
- import { DwnInterfaceName } from '../enums/dwn-interface-method.js';
6
- import { FilterUtility } from '../utils/filter.js';
7
- import { hashToHex } from '../smt/smt-utils.js';
8
- import { isRecordsPrimaryProjectionExcludedProtocol } from '../core/constants.js';
9
- import { lexicographicalCompare } from '../utils/string.js';
10
- import { Message } from '../core/message.js';
11
- import { SMTStoreMemory } from '../smt/smt-store-memory.js';
12
- import { SparseMerkleTree } from '../smt/sparse-merkle-tree.js';
13
-
14
- /**
15
- * Projection-root algorithm for record-primary scoped subsets.
16
- *
17
- * This version builds an on-demand SMT over latest Records primary message CIDs
18
- * selected by protocol plus optional exact protocolPath or context subtree.
19
- * Dependency records, protocol configs, and record data payloads are not part
20
- * of this root.
21
- */
22
- export const RECORDS_PROJECTION_ROOT_VERSION = 'records-primary-scope-root-v1';
23
-
24
- /**
25
- * A Records primary projection scope.
26
- *
27
- * `protocolPath` is an exact type-path match. `contextId` is a boundary-aware
28
- * subtree match: the candidate context must equal the scoped context or start
29
- * with `${contextId}/`. `protocolPath` and `contextId` are mutually exclusive.
30
- */
31
- export type RecordsProjectionScope = {
32
- protocol: string;
33
- protocolPath?: string;
34
- contextId?: string;
35
- };
36
-
37
- export type RecordsProjectionInput = {
38
- tenant: string;
39
- messageStore: MessageStore;
40
- scopes: readonly [RecordsProjectionScope, ...RecordsProjectionScope[]];
41
- options?: MessageStoreOptions;
42
- };
43
-
44
- export type RecordsProjectionTreeInput = RecordsProjectionInput & {
45
- prefix: boolean[];
46
- };
47
-
48
- export type RecordsProjectionSnapshot = {
49
- getRoot(): Promise<Hash>;
50
- getRootHex(): Promise<string>;
51
- getSubtreeHash(prefix: boolean[]): Promise<Hash>;
52
- getLeaves(prefix: boolean[]): Promise<string[]>;
53
- close(): Promise<void>;
54
- };
55
-
56
- export type NormalizedRecordsProjectionScope = {
57
- protocol: string;
58
- } | {
59
- protocol: string;
60
- protocolPath: string;
61
- } | {
62
- protocol: string;
63
- contextId: string;
64
- };
65
-
66
- /**
67
- * Computes deterministic on-demand roots for Records projections.
68
- *
69
- * The snapshot API performs one store enumeration and serves tree operations
70
- * from that in-memory view. A future store-level snapshot/high-watermark can be
71
- * threaded through the `options` input without changing the projection shape.
72
- */
73
- export class RecordsProjection {
74
-
75
- /**
76
- * Returns the sorted latest Records primary message CIDs covered by a scope union.
77
- */
78
- public static async getPrimaryMessageCids({
79
- tenant,
80
- messageStore,
81
- scopes,
82
- options,
83
- }: RecordsProjectionInput): Promise<string[]> {
84
- const filters = RecordsProjection.normalizeScopes(scopes)
85
- .filter(scope => !isRecordsPrimaryProjectionExcludedProtocol(scope.protocol))
86
- .flatMap(scope => RecordsProjection.constructFilters(scope));
87
- if (filters.length === 0) {
88
- return [];
89
- }
90
-
91
- const { messages } = await messageStore.query(tenant, filters, undefined, undefined, options);
92
- const messageCids = await Promise.all(messages.map(message => Message.getCid(message)));
93
-
94
- return [...new Set(messageCids)].sort(lexicographicalCompare); // NOSONAR — projection IDs require locale-independent bytewise ordering.
95
- }
96
-
97
- /**
98
- * Returns the projection root hash.
99
- */
100
- public static async getRoot(input: RecordsProjectionInput): Promise<Hash> {
101
- return RecordsProjection.withTree(input, tree => tree.getRoot());
102
- }
103
-
104
- /**
105
- * Returns the projection root hash encoded as lowercase hex.
106
- */
107
- public static async getRootHex(input: RecordsProjectionInput): Promise<string> {
108
- return hashToHex(await RecordsProjection.getRoot(input));
109
- }
110
-
111
- /**
112
- * Returns the subtree hash for a bit prefix within this projection.
113
- */
114
- public static async getSubtreeHash(input: RecordsProjectionTreeInput): Promise<Hash> {
115
- return RecordsProjection.withTree(input, tree => tree.getSubtreeHash(input.prefix));
116
- }
117
-
118
- /**
119
- * Returns the message CIDs under a bit prefix within this projection.
120
- */
121
- public static async getLeaves(input: RecordsProjectionTreeInput): Promise<string[]> {
122
- return RecordsProjection.withTree(input, tree => tree.getLeaves(input.prefix));
123
- }
124
-
125
- /**
126
- * Builds an in-memory projection tree from one primary-CID enumeration.
127
- */
128
- public static async createSnapshot(input: RecordsProjectionInput): Promise<RecordsProjectionSnapshot> {
129
- const tree = await RecordsProjection.createTree(input);
130
- return {
131
- close : () => tree.close(),
132
- getLeaves : (prefix: boolean[]) => tree.getLeaves(prefix),
133
- getRoot : () => tree.getRoot(),
134
- getRootHex : async () => hashToHex(await tree.getRoot()),
135
- getSubtreeHash : (prefix: boolean[]) => tree.getSubtreeHash(prefix),
136
- };
137
- }
138
-
139
- /**
140
- * Normalizes a scope union into sorted, duplicate-free, subsumption-reduced entries.
141
- */
142
- public static normalizeScopes(
143
- scopes: readonly [RecordsProjectionScope, ...RecordsProjectionScope[]],
144
- ): [NormalizedRecordsProjectionScope, ...NormalizedRecordsProjectionScope[]] {
145
- if (scopes.length === 0) {
146
- throw new Error('RecordsProjection: scopes must contain at least one scope.');
147
- }
148
-
149
- const normalized = scopes.map(scope => RecordsProjection.normalizeScope(scope));
150
- const deduped = new Map<string, NormalizedRecordsProjectionScope>();
151
- for (const scope of normalized) {
152
- deduped.set(RecordsProjection.scopeKey(scope), scope);
153
- }
154
-
155
- const uniqueScopes = [...deduped.values()];
156
- const protocolWide = new Set(
157
- uniqueScopes
158
- .filter(scope => RecordsProjection.isProtocolWideScope(scope))
159
- .map(scope => scope.protocol)
160
- );
161
-
162
- const reduced = uniqueScopes.filter(scope => {
163
- if (protocolWide.has(scope.protocol)) {
164
- return RecordsProjection.isProtocolWideScope(scope);
165
- }
166
-
167
- if (RecordsProjection.isContextScope(scope)) {
168
- return !uniqueScopes.some(candidate =>
169
- candidate !== scope &&
170
- RecordsProjection.isContextScope(candidate) &&
171
- candidate.protocol === scope.protocol &&
172
- RecordsProjection.contextIdSubsumes(candidate.contextId, scope.contextId)
173
- );
174
- }
175
-
176
- return true;
177
- });
178
-
179
- const result = reduced.sort(RecordsProjection.compareScopes);
180
- return result as [NormalizedRecordsProjectionScope, ...NormalizedRecordsProjectionScope[]];
181
- }
182
-
183
- private static async withTree<T>(
184
- input: RecordsProjectionInput,
185
- fn: (tree: SparseMerkleTree) => Promise<T>,
186
- ): Promise<T> {
187
- const tree = await RecordsProjection.createTree(input);
188
-
189
- try {
190
- return await fn(tree);
191
- } finally {
192
- await tree.close();
193
- }
194
- }
195
-
196
- private static async createTree(input: RecordsProjectionInput): Promise<SparseMerkleTree> {
197
- const tree = new SparseMerkleTree(new SMTStoreMemory());
198
- await tree.initialize();
199
-
200
- try {
201
- const messageCids = await RecordsProjection.getPrimaryMessageCids(input);
202
- for (const messageCid of messageCids) {
203
- await tree.insert(messageCid);
204
- }
205
-
206
- return tree;
207
- } catch (error) {
208
- await tree.close();
209
- throw error;
210
- }
211
- }
212
-
213
- private static normalizeScope(scope: RecordsProjectionScope): NormalizedRecordsProjectionScope {
214
- const protocol = RecordsProjection.requireNonEmptyString(scope.protocol, 'protocol');
215
-
216
- if (scope.protocolPath !== undefined && scope.contextId !== undefined) {
217
- throw new Error('RecordsProjection: protocolPath and contextId scopes are mutually exclusive.');
218
- }
219
-
220
- if (scope.protocolPath !== undefined) {
221
- return {
222
- protocol,
223
- protocolPath: RecordsProjection.requireNonEmptyString(scope.protocolPath, 'protocolPath'),
224
- };
225
- }
226
-
227
- if (scope.contextId !== undefined) {
228
- return {
229
- protocol,
230
- contextId: RecordsProjection.requireNonEmptyString(scope.contextId, 'contextId'),
231
- };
232
- }
233
-
234
- return { protocol };
235
- }
236
-
237
- private static constructFilters(scope: NormalizedRecordsProjectionScope): Filter[] {
238
- const baseFilter: Filter = {
239
- interface : DwnInterfaceName.Records,
240
- isLatestBaseState : true,
241
- protocol : scope.protocol,
242
- };
243
-
244
- if ('protocolPath' in scope) {
245
- return [{ ...baseFilter, protocolPath: scope.protocolPath }];
246
- }
247
-
248
- if ('contextId' in scope) {
249
- const childContextPrefix = `${scope.contextId}/`;
250
- return [
251
- { ...baseFilter, contextId: scope.contextId },
252
- {
253
- ...baseFilter,
254
- contextId: FilterUtility.constructPrefixFilterAsRangeFilter(childContextPrefix),
255
- },
256
- ];
257
- }
258
-
259
- return [baseFilter];
260
- }
261
-
262
- private static requireNonEmptyString(value: string, field: string): string {
263
- if (typeof value !== 'string' || value.length === 0) {
264
- throw new Error(`RecordsProjection: ${field} must be a non-empty string.`);
265
- }
266
-
267
- return value;
268
- }
269
-
270
- private static scopeKey(scope: NormalizedRecordsProjectionScope): string {
271
- if ('protocolPath' in scope) {
272
- return `${scope.protocol}\u001fprotocolPath\u001f${scope.protocolPath}`;
273
- }
274
-
275
- if ('contextId' in scope) {
276
- return `${scope.protocol}\u001fcontextId\u001f${scope.contextId}`;
277
- }
278
-
279
- return `${scope.protocol}\u001fprotocol`;
280
- }
281
-
282
- private static isProtocolWideScope(
283
- scope: NormalizedRecordsProjectionScope,
284
- ): scope is { protocol: string } {
285
- return !('protocolPath' in scope) && !('contextId' in scope);
286
- }
287
-
288
- private static isContextScope(
289
- scope: NormalizedRecordsProjectionScope,
290
- ): scope is { protocol: string; contextId: string } {
291
- return 'contextId' in scope;
292
- }
293
-
294
- private static contextIdSubsumes(parentContextId: string, childContextId: string): boolean {
295
- return childContextId === parentContextId ||
296
- childContextId.startsWith(`${parentContextId}/`);
297
- }
298
-
299
- private static compareScopes(
300
- a: NormalizedRecordsProjectionScope,
301
- b: NormalizedRecordsProjectionScope,
302
- ): number {
303
- const protocolCompare = lexicographicalCompare(a.protocol, b.protocol);
304
- if (protocolCompare !== 0) {
305
- return protocolCompare;
306
- }
307
-
308
- const aRank = RecordsProjection.scopeRank(a);
309
- const bRank = RecordsProjection.scopeRank(b);
310
- if (aRank !== bRank) {
311
- return aRank - bRank;
312
- }
313
-
314
- return lexicographicalCompare(RecordsProjection.scopeValue(a), RecordsProjection.scopeValue(b));
315
- }
316
-
317
- private static scopeRank(scope: NormalizedRecordsProjectionScope): number {
318
- if ('protocolPath' in scope) { return 1; }
319
- if ('contextId' in scope) { return 2; }
320
- return 0;
321
- }
322
-
323
- private static scopeValue(scope: NormalizedRecordsProjectionScope): string {
324
- if ('protocolPath' in scope) { return scope.protocolPath; }
325
- if ('contextId' in scope) { return scope.contextId; }
326
- return '';
327
- }
328
- }