@enbox/dwn-sdk-js 0.0.8 → 0.1.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.
Files changed (90) hide show
  1. package/LICENSE +3 -2
  2. package/dist/browser.mjs +5 -5
  3. package/dist/browser.mjs.map +3 -3
  4. package/dist/esm/generated/precompiled-validators.js +219 -165
  5. package/dist/esm/generated/precompiled-validators.js.map +1 -1
  6. package/dist/esm/src/core/dwn-error.js +3 -0
  7. package/dist/esm/src/core/dwn-error.js.map +1 -1
  8. package/dist/esm/src/core/protocol-authorization-action.js +5 -0
  9. package/dist/esm/src/core/protocol-authorization-action.js.map +1 -1
  10. package/dist/esm/src/core/protocol-authorization-validation.js +24 -0
  11. package/dist/esm/src/core/protocol-authorization-validation.js.map +1 -1
  12. package/dist/esm/src/core/protocol-authorization.js +3 -1
  13. package/dist/esm/src/core/protocol-authorization.js.map +1 -1
  14. package/dist/esm/src/core/resumable-task-manager.js +2 -0
  15. package/dist/esm/src/core/resumable-task-manager.js.map +1 -1
  16. package/dist/esm/src/dwn.js +19 -5
  17. package/dist/esm/src/dwn.js.map +1 -1
  18. package/dist/esm/src/handlers/records-read.js +5 -1
  19. package/dist/esm/src/handlers/records-read.js.map +1 -1
  20. package/dist/esm/src/handlers/records-write.js +81 -0
  21. package/dist/esm/src/handlers/records-write.js.map +1 -1
  22. package/dist/esm/src/interfaces/protocols-configure.js +9 -1
  23. package/dist/esm/src/interfaces/protocols-configure.js.map +1 -1
  24. package/dist/esm/src/interfaces/records-write.js +3 -0
  25. package/dist/esm/src/interfaces/records-write.js.map +1 -1
  26. package/dist/esm/src/store/storage-controller.js +64 -0
  27. package/dist/esm/src/store/storage-controller.js.map +1 -1
  28. package/dist/esm/src/types/protocols-types.js +1 -0
  29. package/dist/esm/src/types/protocols-types.js.map +1 -1
  30. package/dist/esm/tests/features/records-delivery.spec.js +236 -0
  31. package/dist/esm/tests/features/records-delivery.spec.js.map +1 -0
  32. package/dist/esm/tests/features/records-squash.spec.js +1055 -0
  33. package/dist/esm/tests/features/records-squash.spec.js.map +1 -0
  34. package/dist/esm/tests/handlers/records-read.spec.js +6 -2
  35. package/dist/esm/tests/handlers/records-read.spec.js.map +1 -1
  36. package/dist/esm/tests/handlers/records-write.spec.js +3 -0
  37. package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
  38. package/dist/esm/tests/test-suite.js +4 -0
  39. package/dist/esm/tests/test-suite.js.map +1 -1
  40. package/dist/esm/tests/utils/test-data-generator.js +1 -0
  41. package/dist/esm/tests/utils/test-data-generator.js.map +1 -1
  42. package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
  43. package/dist/types/src/core/dwn-error.d.ts +3 -0
  44. package/dist/types/src/core/dwn-error.d.ts.map +1 -1
  45. package/dist/types/src/core/protocol-authorization-action.d.ts.map +1 -1
  46. package/dist/types/src/core/protocol-authorization-validation.d.ts +9 -0
  47. package/dist/types/src/core/protocol-authorization-validation.d.ts.map +1 -1
  48. package/dist/types/src/core/protocol-authorization.d.ts.map +1 -1
  49. package/dist/types/src/core/resumable-task-manager.d.ts +2 -1
  50. package/dist/types/src/core/resumable-task-manager.d.ts.map +1 -1
  51. package/dist/types/src/dwn.d.ts +9 -0
  52. package/dist/types/src/dwn.d.ts.map +1 -1
  53. package/dist/types/src/handlers/records-read.d.ts.map +1 -1
  54. package/dist/types/src/handlers/records-write.d.ts +8 -0
  55. package/dist/types/src/handlers/records-write.d.ts.map +1 -1
  56. package/dist/types/src/index.d.ts +1 -1
  57. package/dist/types/src/index.d.ts.map +1 -1
  58. package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
  59. package/dist/types/src/interfaces/records-write.d.ts +6 -0
  60. package/dist/types/src/interfaces/records-write.d.ts.map +1 -1
  61. package/dist/types/src/store/storage-controller.d.ts +16 -2
  62. package/dist/types/src/store/storage-controller.d.ts.map +1 -1
  63. package/dist/types/src/types/protocols-types.d.ts +29 -2
  64. package/dist/types/src/types/protocols-types.d.ts.map +1 -1
  65. package/dist/types/src/types/records-types.d.ts +7 -0
  66. package/dist/types/src/types/records-types.d.ts.map +1 -1
  67. package/dist/types/tests/features/records-delivery.spec.d.ts +2 -0
  68. package/dist/types/tests/features/records-delivery.spec.d.ts.map +1 -0
  69. package/dist/types/tests/features/records-squash.spec.d.ts +2 -0
  70. package/dist/types/tests/features/records-squash.spec.d.ts.map +1 -0
  71. package/dist/types/tests/handlers/records-read.spec.d.ts.map +1 -1
  72. package/dist/types/tests/handlers/records-write.spec.d.ts.map +1 -1
  73. package/dist/types/tests/test-suite.d.ts.map +1 -1
  74. package/dist/types/tests/utils/test-data-generator.d.ts +1 -0
  75. package/dist/types/tests/utils/test-data-generator.d.ts.map +1 -1
  76. package/package.json +3 -3
  77. package/src/core/dwn-error.ts +3 -0
  78. package/src/core/protocol-authorization-action.ts +5 -0
  79. package/src/core/protocol-authorization-validation.ts +37 -0
  80. package/src/core/protocol-authorization.ts +4 -0
  81. package/src/core/resumable-task-manager.ts +3 -1
  82. package/src/dwn.ts +30 -5
  83. package/src/handlers/records-read.ts +5 -1
  84. package/src/handlers/records-write.ts +106 -0
  85. package/src/index.ts +1 -1
  86. package/src/interfaces/protocols-configure.ts +12 -1
  87. package/src/interfaces/records-write.ts +10 -0
  88. package/src/store/storage-controller.ts +78 -1
  89. package/src/types/protocols-types.ts +32 -1
  90. package/src/types/records-types.ts +8 -0
@@ -1,11 +1,13 @@
1
1
  import type { DataStore } from '../types/data-store.js';
2
2
  import type { EventLog } from '../types/subscriptions.js';
3
+ import type { Filter } from '../types/query-types.js';
3
4
  import type { GenericMessage } from '../types/message-types.js';
4
5
  import type { MessageStore } from '../types/message-store.js';
5
6
  import type { StateIndex } from '../types/state-index.js';
6
7
  import type { RecordsDeleteMessage, RecordsQueryReplyEntry, RecordsWriteMessage } from '../types/records-types.js';
7
8
 
8
9
  import { DwnConstant } from '../core/dwn-constant.js';
10
+ import { FilterUtility } from '../utils/filter.js';
9
11
  import { Message } from '../core/message.js';
10
12
  import { Records } from '../utils/records.js';
11
13
  import { RecordsDelete } from '../interfaces/records-delete.js';
@@ -18,6 +20,11 @@ export type ResumableRecordsDeleteData = {
18
20
  message: RecordsDeleteMessage;
19
21
  };
20
22
 
23
+ export type ResumableRecordsSquashData = {
24
+ tenant: string;
25
+ message: RecordsWriteMessage;
26
+ };
27
+
21
28
  /**
22
29
  * A class that provides an abstraction for the usage of MessageStore, DataStore, and StateIndex.
23
30
  */
@@ -82,6 +89,76 @@ export class StorageController {
82
89
  );
83
90
  }
84
91
 
92
+ /**
93
+ * Performs the squash processing for a `RecordsWrite` with `squash: true`.
94
+ * Deletes all sibling records at the same protocol path and parent context whose
95
+ * `messageTimestamp` is strictly older than the squash record's `messageTimestamp`.
96
+ * Unlike normal `RecordsDelete` tombstones, squash targets are fully purged — no initial writes
97
+ * are retained.
98
+ *
99
+ * This method is idempotent — it can be safely re-run on resume after a crash.
100
+ */
101
+ public async performRecordsSquash({ tenant, message }: ResumableRecordsSquashData): Promise<void> {
102
+ const { protocol, protocolPath, messageTimestamp } = message.descriptor;
103
+
104
+ // Build a filter to find all sibling records at the same protocol path and parent context.
105
+ // We query by interface only (not method) so that both RecordsWrite and RecordsDelete messages are found.
106
+ const filter: Filter = {
107
+ interface : DwnInterfaceName.Records,
108
+ protocol,
109
+ protocolPath : protocolPath,
110
+ };
111
+
112
+ // Scope by parent context for nested records
113
+ const parentContextId = Records.getParentContextFromOfContextId(message.contextId);
114
+ if (parentContextId !== undefined && parentContextId !== '') {
115
+ const prefixFilter = FilterUtility.constructPrefixFilterAsRangeFilter(parentContextId);
116
+ filter.contextId = prefixFilter;
117
+ }
118
+
119
+ // Query for all records at this path and context
120
+ const { messages: siblingMessages } = await this.messageStore.query(tenant, [filter]);
121
+
122
+ // Group messages by recordId — RecordsWrite messages use `message.recordId`,
123
+ // RecordsDelete messages use `message.descriptor.recordId`.
124
+ const recordIdToMessages = new Map<string, GenericMessage[]>();
125
+ for (const msg of siblingMessages) {
126
+ let recordId: string;
127
+ if (Records.isRecordsWrite(msg)) {
128
+ recordId = msg.recordId;
129
+ } else {
130
+ recordId = (msg as RecordsDeleteMessage).descriptor.recordId;
131
+ }
132
+
133
+ const existing = recordIdToMessages.get(recordId);
134
+ if (existing !== undefined) {
135
+ existing.push(msg);
136
+ } else {
137
+ recordIdToMessages.set(recordId, [msg]);
138
+ }
139
+ }
140
+
141
+ // Delete all records whose newest message timestamp is strictly older than the squash timestamp.
142
+ // Skip the squash record itself.
143
+ for (const [recordId, messages] of recordIdToMessages) {
144
+ if (recordId === message.recordId) {
145
+ continue;
146
+ }
147
+
148
+ // Use the newest message's timestamp to determine if the record predates the squash.
149
+ const newestMessage = await Message.getNewestMessage(messages);
150
+ if (newestMessage === undefined) {
151
+ continue;
152
+ }
153
+
154
+ const newestTimestamp = newestMessage.descriptor.messageTimestamp;
155
+ if (newestTimestamp < messageTimestamp) {
156
+ // Fully purge this record — all messages (including initial write) and their data
157
+ await StorageController.purgeRecordMessages(tenant, messages, this.messageStore, this.dataStore, this.stateIndex);
158
+ }
159
+ }
160
+ }
161
+
85
162
  /**
86
163
  * Deletes the data referenced by the given message if needed.
87
164
  * @param message The message to check if the data it references should be deleted.
@@ -164,7 +241,7 @@ export class StorageController {
164
241
  * Purges (permanent hard-delete) all messages of the SAME `recordId` given and their associated data and events.
165
242
  * Assumes that the given `recordMessages` are all of the same `recordId`.
166
243
  */
167
- private static async purgeRecordMessages(
244
+ public static async purgeRecordMessages(
168
245
  tenant: string,
169
246
  recordMessages: GenericMessage[],
170
247
  messageStore: MessageStore,
@@ -75,6 +75,7 @@ export enum ProtocolAction {
75
75
  Delete = 'delete',
76
76
  Prune = 'prune',
77
77
  Read = 'read',
78
+ Squash = 'squash',
78
79
  Update = 'update'
79
80
  }
80
81
 
@@ -184,6 +185,17 @@ export type ProtocolRecordLimitDefinition = {
184
185
  strategy: ProtocolRecordLimitStrategy | `${ProtocolRecordLimitStrategy}` | (string & {});
185
186
  };
186
187
 
188
+ /**
189
+ * Delivery strategy for records at a given protocol path.
190
+ * Controls how a DWN server proactively distributes records to participants' DWN endpoints.
191
+ *
192
+ * - `'direct'` — The origin DWN pushes new records to all participants' DWN endpoints.
193
+ * Best for small participant sets (2–50).
194
+ * - `'subscribe'` — Participant providers establish `RecordsSubscribe` connections to the
195
+ * origin DWN. Best for asymmetric fan-out or unbounded audiences.
196
+ */
197
+ export type ProtocolDeliveryStrategy = 'direct' | 'subscribe';
198
+
187
199
  /**
188
200
  * Tag rules for records at a given protocol path. Each non-`$`-prefixed property
189
201
  * is a JSON Schema object constraining that tag's value.
@@ -222,7 +234,7 @@ export type ProtocolTagSchema = {
222
234
  /**
223
235
  * Union of all value types that can appear as properties of a `ProtocolRuleSet`.
224
236
  * This includes:
225
- * - `$`-prefixed directive values (`$encryption`, `$actions`, `$role`, `$ref`, `$size`, `$tags`)
237
+ * - `$`-prefixed directive values (`$encryption`, `$actions`, `$role`, `$ref`, `$size`, `$tags`, `$delivery`)
226
238
  * - Child `ProtocolRuleSet` entries (non-`$` keys)
227
239
  */
228
240
  type ProtocolRuleSetValue =
@@ -232,6 +244,7 @@ type ProtocolRuleSetValue =
232
244
  | ProtocolTagsDefinition
233
245
  | ProtocolSizeDefinition
234
246
  | ProtocolRecordLimitDefinition
247
+ | ProtocolDeliveryStrategy
235
248
  | boolean
236
249
  | string
237
250
  | undefined;
@@ -293,6 +306,24 @@ export type ProtocolRuleSet = {
293
306
  */
294
307
  $immutable?: boolean;
295
308
 
309
+ /**
310
+ * Delivery strategy hint for records at this protocol path.
311
+ * When set, the DWN server SHOULD proactively deliver records to participants' DWN endpoints.
312
+ *
313
+ * - `'direct'` — Origin DWN pushes to all participant DWN endpoints upon receipt.
314
+ * - `'subscribe'` — Participant providers subscribe to the origin DWN via `RecordsSubscribe`.
315
+ */
316
+ $delivery?: ProtocolDeliveryStrategy;
317
+
318
+ /**
319
+ * If `$squash` is `true`, enables squash writes at this protocol path.
320
+ * A squash write is a `RecordsWrite` with `squash: true` in the descriptor that
321
+ * atomically creates a new record (the snapshot) and deletes all sibling records
322
+ * at the same protocol path within the same parent context that have a
323
+ * `messageTimestamp` strictly older than the squash record's `messageTimestamp`.
324
+ */
325
+ $squash?: boolean;
326
+
296
327
  /**
297
328
  * Non-`$`-prefixed keys are nested child `ProtocolRuleSet` entries.
298
329
  * At runtime, JSON Schema validation ensures only valid child rule sets appear here.
@@ -38,6 +38,14 @@ export type RecordsWriteDescriptor = {
38
38
  datePublished?: string;
39
39
  dataFormat: string;
40
40
  permissionGrantId?: string;
41
+
42
+ /**
43
+ * When `true`, this record is a squash (snapshot) write. The protocol rule set at this record's
44
+ * `protocolPath` must have `$squash: true`; otherwise the message is rejected.
45
+ * A squash write must be an initial write (a new record, not an update).
46
+ * This is an immutable property.
47
+ */
48
+ squash?: true;
41
49
  };
42
50
 
43
51
  export type RecordsWriteMessageOptions = {