@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.
- package/LICENSE +3 -2
- package/dist/browser.mjs +5 -5
- package/dist/browser.mjs.map +3 -3
- package/dist/esm/generated/precompiled-validators.js +219 -165
- package/dist/esm/generated/precompiled-validators.js.map +1 -1
- package/dist/esm/src/core/dwn-error.js +3 -0
- package/dist/esm/src/core/dwn-error.js.map +1 -1
- package/dist/esm/src/core/protocol-authorization-action.js +5 -0
- package/dist/esm/src/core/protocol-authorization-action.js.map +1 -1
- package/dist/esm/src/core/protocol-authorization-validation.js +24 -0
- package/dist/esm/src/core/protocol-authorization-validation.js.map +1 -1
- package/dist/esm/src/core/protocol-authorization.js +3 -1
- package/dist/esm/src/core/protocol-authorization.js.map +1 -1
- package/dist/esm/src/core/resumable-task-manager.js +2 -0
- package/dist/esm/src/core/resumable-task-manager.js.map +1 -1
- package/dist/esm/src/dwn.js +19 -5
- package/dist/esm/src/dwn.js.map +1 -1
- package/dist/esm/src/handlers/records-read.js +5 -1
- package/dist/esm/src/handlers/records-read.js.map +1 -1
- package/dist/esm/src/handlers/records-write.js +81 -0
- package/dist/esm/src/handlers/records-write.js.map +1 -1
- package/dist/esm/src/interfaces/protocols-configure.js +9 -1
- package/dist/esm/src/interfaces/protocols-configure.js.map +1 -1
- package/dist/esm/src/interfaces/records-write.js +3 -0
- package/dist/esm/src/interfaces/records-write.js.map +1 -1
- package/dist/esm/src/store/storage-controller.js +64 -0
- package/dist/esm/src/store/storage-controller.js.map +1 -1
- package/dist/esm/src/types/protocols-types.js +1 -0
- package/dist/esm/src/types/protocols-types.js.map +1 -1
- package/dist/esm/tests/features/records-delivery.spec.js +236 -0
- package/dist/esm/tests/features/records-delivery.spec.js.map +1 -0
- package/dist/esm/tests/features/records-squash.spec.js +1055 -0
- package/dist/esm/tests/features/records-squash.spec.js.map +1 -0
- package/dist/esm/tests/handlers/records-read.spec.js +6 -2
- package/dist/esm/tests/handlers/records-read.spec.js.map +1 -1
- package/dist/esm/tests/handlers/records-write.spec.js +3 -0
- package/dist/esm/tests/handlers/records-write.spec.js.map +1 -1
- package/dist/esm/tests/test-suite.js +4 -0
- package/dist/esm/tests/test-suite.js.map +1 -1
- package/dist/esm/tests/utils/test-data-generator.js +1 -0
- package/dist/esm/tests/utils/test-data-generator.js.map +1 -1
- package/dist/types/generated/precompiled-validators.d.ts.map +1 -1
- package/dist/types/src/core/dwn-error.d.ts +3 -0
- package/dist/types/src/core/dwn-error.d.ts.map +1 -1
- package/dist/types/src/core/protocol-authorization-action.d.ts.map +1 -1
- package/dist/types/src/core/protocol-authorization-validation.d.ts +9 -0
- package/dist/types/src/core/protocol-authorization-validation.d.ts.map +1 -1
- package/dist/types/src/core/protocol-authorization.d.ts.map +1 -1
- package/dist/types/src/core/resumable-task-manager.d.ts +2 -1
- package/dist/types/src/core/resumable-task-manager.d.ts.map +1 -1
- package/dist/types/src/dwn.d.ts +9 -0
- package/dist/types/src/dwn.d.ts.map +1 -1
- package/dist/types/src/handlers/records-read.d.ts.map +1 -1
- package/dist/types/src/handlers/records-write.d.ts +8 -0
- package/dist/types/src/handlers/records-write.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/interfaces/protocols-configure.d.ts.map +1 -1
- package/dist/types/src/interfaces/records-write.d.ts +6 -0
- package/dist/types/src/interfaces/records-write.d.ts.map +1 -1
- package/dist/types/src/store/storage-controller.d.ts +16 -2
- package/dist/types/src/store/storage-controller.d.ts.map +1 -1
- package/dist/types/src/types/protocols-types.d.ts +29 -2
- package/dist/types/src/types/protocols-types.d.ts.map +1 -1
- package/dist/types/src/types/records-types.d.ts +7 -0
- package/dist/types/src/types/records-types.d.ts.map +1 -1
- package/dist/types/tests/features/records-delivery.spec.d.ts +2 -0
- package/dist/types/tests/features/records-delivery.spec.d.ts.map +1 -0
- package/dist/types/tests/features/records-squash.spec.d.ts +2 -0
- package/dist/types/tests/features/records-squash.spec.d.ts.map +1 -0
- package/dist/types/tests/handlers/records-read.spec.d.ts.map +1 -1
- package/dist/types/tests/handlers/records-write.spec.d.ts.map +1 -1
- package/dist/types/tests/test-suite.d.ts.map +1 -1
- package/dist/types/tests/utils/test-data-generator.d.ts +1 -0
- package/dist/types/tests/utils/test-data-generator.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/core/dwn-error.ts +3 -0
- package/src/core/protocol-authorization-action.ts +5 -0
- package/src/core/protocol-authorization-validation.ts +37 -0
- package/src/core/protocol-authorization.ts +4 -0
- package/src/core/resumable-task-manager.ts +3 -1
- package/src/dwn.ts +30 -5
- package/src/handlers/records-read.ts +5 -1
- package/src/handlers/records-write.ts +106 -0
- package/src/index.ts +1 -1
- package/src/interfaces/protocols-configure.ts +12 -1
- package/src/interfaces/records-write.ts +10 -0
- package/src/store/storage-controller.ts +78 -1
- package/src/types/protocols-types.ts +32 -1
- 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
|
-
|
|
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 = {
|