ducjs 3.0.4 → 3.1.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.
- package/dist/ducjs_wasm.d.ts +48 -25
- package/dist/ducjs_wasm.js +107 -54
- package/dist/ducjs_wasm_bg.wasm +0 -0
- package/dist/ducjs_wasm_bg.wasm.d.ts +2 -0
- package/dist/lazy-files.d.ts +9 -7
- package/dist/lazy-files.js +62 -35
- package/dist/parse.js +21 -2
- package/dist/restore/restoreDataState.js +97 -38
- package/dist/serialize.js +102 -6
- package/dist/types/index.d.ts +23 -17
- package/dist/version-control.d.ts +26 -0
- package/dist/version-control.js +33 -1
- package/dist/wasm.d.ts +3 -1
- package/dist/wasm.js +3 -1
- package/package.json +1 -1
package/dist/ducjs_wasm.d.ts
CHANGED
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
/* tslint:disable */
|
|
2
2
|
/* eslint-disable */
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Fetch a single external file from a `.duc` buffer by file ID.
|
|
5
|
+
*
|
|
6
|
+
* Returns the file entry as a JS object, or `undefined` if not found.
|
|
5
7
|
*/
|
|
6
|
-
export function
|
|
8
|
+
export function getExternalFile(buf: Uint8Array, file_id: string): any;
|
|
7
9
|
/**
|
|
8
|
-
*
|
|
10
|
+
* Restore a specific checkpoint by its ID from a `.duc` file buffer.
|
|
9
11
|
*
|
|
10
|
-
* Returns a JS object
|
|
11
|
-
* or `undefined` if no version graph exists.
|
|
12
|
+
* Returns a JS object `{ versionNumber, schemaVersion, data, fromCheckpoint }`.
|
|
12
13
|
*/
|
|
13
|
-
export function
|
|
14
|
+
export function restoreCheckpoint(duc_buf: Uint8Array, checkpoint_id: string): any;
|
|
14
15
|
/**
|
|
15
|
-
*
|
|
16
|
+
* List all versions (checkpoints + deltas) from a `.duc` file buffer.
|
|
16
17
|
*
|
|
17
|
-
* Returns
|
|
18
|
+
* Returns a JS array of `VersionEntry` objects (no heavy data blobs).
|
|
18
19
|
*/
|
|
19
|
-
export function
|
|
20
|
+
export function listVersions(duc_buf: Uint8Array): any;
|
|
20
21
|
/**
|
|
21
|
-
*
|
|
22
|
+
* Read the full VersionGraph from a `.duc` file buffer.
|
|
22
23
|
*
|
|
23
|
-
* Returns a JS object `
|
|
24
|
+
* Returns a JS object matching the `VersionGraph` TypeScript interface,
|
|
25
|
+
* or `undefined` if no version graph exists.
|
|
24
26
|
*/
|
|
25
|
-
export function
|
|
27
|
+
export function readVersionGraph(duc_buf: Uint8Array): any;
|
|
26
28
|
/**
|
|
27
29
|
* Returns the current version-control schema version defined in Rust.
|
|
28
30
|
*
|
|
@@ -32,12 +34,6 @@ export function revertToVersion(duc_buf: Uint8Array, target_version: number): an
|
|
|
32
34
|
* recording migrations) the next time a checkpoint or delta is created.
|
|
33
35
|
*/
|
|
34
36
|
export function getCurrentSchemaVersion(): number;
|
|
35
|
-
/**
|
|
36
|
-
* List all versions (checkpoints + deltas) from a `.duc` file buffer.
|
|
37
|
-
*
|
|
38
|
-
* Returns a JS array of `VersionEntry` objects (no heavy data blobs).
|
|
39
|
-
*/
|
|
40
|
-
export function listVersions(duc_buf: Uint8Array): any;
|
|
41
37
|
/**
|
|
42
38
|
* Restore the document state at `version_number` from a `.duc` file buffer.
|
|
43
39
|
*
|
|
@@ -47,31 +43,58 @@ export function listVersions(duc_buf: Uint8Array): any;
|
|
|
47
43
|
* Returns a JS object `{ versionNumber, schemaVersion, data, fromCheckpoint }`.
|
|
48
44
|
*/
|
|
49
45
|
export function restoreVersion(duc_buf: Uint8Array, version_number: number): any;
|
|
46
|
+
/**
|
|
47
|
+
* Revert the document to a specific version, removing all newer versions.
|
|
48
|
+
*
|
|
49
|
+
* Returns a JS object `{ versionNumber, schemaVersion, data, fromCheckpoint }`.
|
|
50
|
+
*/
|
|
51
|
+
export function revertToVersion(duc_buf: Uint8Array, target_version: number): any;
|
|
52
|
+
/**
|
|
53
|
+
* Apply a changeset to reconstruct document state.
|
|
54
|
+
*
|
|
55
|
+
* `base_state` must be the exact checkpoint data used when the changeset
|
|
56
|
+
* was created. Returns the full document state as `Uint8Array`.
|
|
57
|
+
*
|
|
58
|
+
* Handles all changeset formats transparently:
|
|
59
|
+
* - v3 (bsdiff), v2 (XOR diff), v1 (zlib full snapshot)
|
|
60
|
+
*/
|
|
61
|
+
export function applyDeltaChangeset(base_state: Uint8Array, changeset: Uint8Array): Uint8Array;
|
|
62
|
+
/**
|
|
63
|
+
* List metadata for all external files (without loading the heavy data blobs).
|
|
64
|
+
*/
|
|
65
|
+
export function listExternalFiles(buf: Uint8Array): any;
|
|
50
66
|
/**
|
|
51
67
|
* Serialize a JS object (ExportedDataState) into `.duc` bytes (Uint8Array).
|
|
52
68
|
*/
|
|
53
69
|
export function serializeDuc(data: any): Uint8Array;
|
|
54
70
|
/**
|
|
55
|
-
*
|
|
71
|
+
* Compute a checkpoint-relative binary diff changeset using bsdiff.
|
|
56
72
|
*
|
|
57
|
-
*
|
|
73
|
+
* `base_state` is the checkpoint's full data blob.
|
|
74
|
+
* `current_state` is the full document state at the new version.
|
|
75
|
+
*
|
|
76
|
+
* Returns an encoded changeset (`Uint8Array`) ready for storage in a
|
|
77
|
+
* `Delta.payload`. bsdiff finds matching blocks even when they shift
|
|
78
|
+
* offsets, which is critical for SQLite databases.
|
|
58
79
|
*/
|
|
59
|
-
export function
|
|
80
|
+
export function createDeltaChangeset(base_state: Uint8Array, current_state: Uint8Array): Uint8Array;
|
|
81
|
+
/**
|
|
82
|
+
* Parse a `.duc` file (Uint8Array) into a JS object (ExportedDataState).
|
|
83
|
+
*/
|
|
84
|
+
export function parseDuc(buf: Uint8Array): any;
|
|
60
85
|
/**
|
|
61
86
|
* Parse a `.duc` file lazily — returns everything EXCEPT external file data blobs.
|
|
62
87
|
*
|
|
63
88
|
* Use `getExternalFile()` or `listExternalFiles()` for on-demand access.
|
|
64
89
|
*/
|
|
65
90
|
export function parseDucLazy(buf: Uint8Array): any;
|
|
66
|
-
/**
|
|
67
|
-
* List metadata for all external files (without loading the heavy data blobs).
|
|
68
|
-
*/
|
|
69
|
-
export function listExternalFiles(buf: Uint8Array): any;
|
|
70
91
|
|
|
71
92
|
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
|
72
93
|
|
|
73
94
|
export interface InitOutput {
|
|
74
95
|
readonly memory: WebAssembly.Memory;
|
|
96
|
+
readonly applyDeltaChangeset: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
|
97
|
+
readonly createDeltaChangeset: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
|
75
98
|
readonly getCurrentSchemaVersion: () => number;
|
|
76
99
|
readonly getExternalFile: (a: number, b: number, c: number, d: number) => [number, number, number];
|
|
77
100
|
readonly listExternalFiles: (a: number, b: number) => [number, number, number];
|
package/dist/ducjs_wasm.js
CHANGED
|
@@ -195,14 +195,19 @@ function takeFromExternrefTable0(idx) {
|
|
|
195
195
|
return value;
|
|
196
196
|
}
|
|
197
197
|
/**
|
|
198
|
-
*
|
|
198
|
+
* Fetch a single external file from a `.duc` buffer by file ID.
|
|
199
|
+
*
|
|
200
|
+
* Returns the file entry as a JS object, or `undefined` if not found.
|
|
199
201
|
* @param {Uint8Array} buf
|
|
202
|
+
* @param {string} file_id
|
|
200
203
|
* @returns {any}
|
|
201
204
|
*/
|
|
202
|
-
export function
|
|
205
|
+
export function getExternalFile(buf, file_id) {
|
|
203
206
|
const ptr0 = passArray8ToWasm0(buf, wasm.__wbindgen_malloc);
|
|
204
207
|
const len0 = WASM_VECTOR_LEN;
|
|
205
|
-
const
|
|
208
|
+
const ptr1 = passStringToWasm0(file_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
209
|
+
const len1 = WASM_VECTOR_LEN;
|
|
210
|
+
const ret = wasm.getExternalFile(ptr0, len0, ptr1, len1);
|
|
206
211
|
if (ret[2]) {
|
|
207
212
|
throw takeFromExternrefTable0(ret[1]);
|
|
208
213
|
}
|
|
@@ -210,17 +215,19 @@ export function parseDuc(buf) {
|
|
|
210
215
|
}
|
|
211
216
|
|
|
212
217
|
/**
|
|
213
|
-
*
|
|
218
|
+
* Restore a specific checkpoint by its ID from a `.duc` file buffer.
|
|
214
219
|
*
|
|
215
|
-
* Returns a JS object
|
|
216
|
-
* or `undefined` if no version graph exists.
|
|
220
|
+
* Returns a JS object `{ versionNumber, schemaVersion, data, fromCheckpoint }`.
|
|
217
221
|
* @param {Uint8Array} duc_buf
|
|
222
|
+
* @param {string} checkpoint_id
|
|
218
223
|
* @returns {any}
|
|
219
224
|
*/
|
|
220
|
-
export function
|
|
225
|
+
export function restoreCheckpoint(duc_buf, checkpoint_id) {
|
|
221
226
|
const ptr0 = passArray8ToWasm0(duc_buf, wasm.__wbindgen_malloc);
|
|
222
227
|
const len0 = WASM_VECTOR_LEN;
|
|
223
|
-
const
|
|
228
|
+
const ptr1 = passStringToWasm0(checkpoint_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
229
|
+
const len1 = WASM_VECTOR_LEN;
|
|
230
|
+
const ret = wasm.restoreCheckpoint(ptr0, len0, ptr1, len1);
|
|
224
231
|
if (ret[2]) {
|
|
225
232
|
throw takeFromExternrefTable0(ret[1]);
|
|
226
233
|
}
|
|
@@ -228,19 +235,16 @@ export function readVersionGraph(duc_buf) {
|
|
|
228
235
|
}
|
|
229
236
|
|
|
230
237
|
/**
|
|
231
|
-
*
|
|
238
|
+
* List all versions (checkpoints + deltas) from a `.duc` file buffer.
|
|
232
239
|
*
|
|
233
|
-
* Returns
|
|
234
|
-
* @param {Uint8Array}
|
|
235
|
-
* @param {string} file_id
|
|
240
|
+
* Returns a JS array of `VersionEntry` objects (no heavy data blobs).
|
|
241
|
+
* @param {Uint8Array} duc_buf
|
|
236
242
|
* @returns {any}
|
|
237
243
|
*/
|
|
238
|
-
export function
|
|
239
|
-
const ptr0 = passArray8ToWasm0(
|
|
244
|
+
export function listVersions(duc_buf) {
|
|
245
|
+
const ptr0 = passArray8ToWasm0(duc_buf, wasm.__wbindgen_malloc);
|
|
240
246
|
const len0 = WASM_VECTOR_LEN;
|
|
241
|
-
const
|
|
242
|
-
const len1 = WASM_VECTOR_LEN;
|
|
243
|
-
const ret = wasm.getExternalFile(ptr0, len0, ptr1, len1);
|
|
247
|
+
const ret = wasm.listVersions(ptr0, len0);
|
|
244
248
|
if (ret[2]) {
|
|
245
249
|
throw takeFromExternrefTable0(ret[1]);
|
|
246
250
|
}
|
|
@@ -248,17 +252,17 @@ export function getExternalFile(buf, file_id) {
|
|
|
248
252
|
}
|
|
249
253
|
|
|
250
254
|
/**
|
|
251
|
-
*
|
|
255
|
+
* Read the full VersionGraph from a `.duc` file buffer.
|
|
252
256
|
*
|
|
253
|
-
* Returns a JS object `
|
|
257
|
+
* Returns a JS object matching the `VersionGraph` TypeScript interface,
|
|
258
|
+
* or `undefined` if no version graph exists.
|
|
254
259
|
* @param {Uint8Array} duc_buf
|
|
255
|
-
* @param {number} target_version
|
|
256
260
|
* @returns {any}
|
|
257
261
|
*/
|
|
258
|
-
export function
|
|
262
|
+
export function readVersionGraph(duc_buf) {
|
|
259
263
|
const ptr0 = passArray8ToWasm0(duc_buf, wasm.__wbindgen_malloc);
|
|
260
264
|
const len0 = WASM_VECTOR_LEN;
|
|
261
|
-
const ret = wasm.
|
|
265
|
+
const ret = wasm.readVersionGraph(ptr0, len0);
|
|
262
266
|
if (ret[2]) {
|
|
263
267
|
throw takeFromExternrefTable0(ret[1]);
|
|
264
268
|
}
|
|
@@ -275,20 +279,24 @@ export function revertToVersion(duc_buf, target_version) {
|
|
|
275
279
|
* @returns {number}
|
|
276
280
|
*/
|
|
277
281
|
export function getCurrentSchemaVersion() {
|
|
278
|
-
return
|
|
282
|
+
return 3000001;
|
|
279
283
|
}
|
|
280
284
|
|
|
281
285
|
/**
|
|
282
|
-
*
|
|
286
|
+
* Restore the document state at `version_number` from a `.duc` file buffer.
|
|
283
287
|
*
|
|
284
|
-
*
|
|
288
|
+
* The `.duc` file is a SQLite database — this function opens it and queries
|
|
289
|
+
* the `checkpoints` / `deltas` tables directly for version restoration.
|
|
290
|
+
*
|
|
291
|
+
* Returns a JS object `{ versionNumber, schemaVersion, data, fromCheckpoint }`.
|
|
285
292
|
* @param {Uint8Array} duc_buf
|
|
293
|
+
* @param {number} version_number
|
|
286
294
|
* @returns {any}
|
|
287
295
|
*/
|
|
288
|
-
export function
|
|
296
|
+
export function restoreVersion(duc_buf, version_number) {
|
|
289
297
|
const ptr0 = passArray8ToWasm0(duc_buf, wasm.__wbindgen_malloc);
|
|
290
298
|
const len0 = WASM_VECTOR_LEN;
|
|
291
|
-
const ret = wasm.
|
|
299
|
+
const ret = wasm.restoreVersion(ptr0, len0, version_number);
|
|
292
300
|
if (ret[2]) {
|
|
293
301
|
throw takeFromExternrefTable0(ret[1]);
|
|
294
302
|
}
|
|
@@ -296,20 +304,58 @@ export function listVersions(duc_buf) {
|
|
|
296
304
|
}
|
|
297
305
|
|
|
298
306
|
/**
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
* The `.duc` file is a SQLite database — this function opens it and queries
|
|
302
|
-
* the `checkpoints` / `deltas` tables directly for version restoration.
|
|
307
|
+
* Revert the document to a specific version, removing all newer versions.
|
|
303
308
|
*
|
|
304
309
|
* Returns a JS object `{ versionNumber, schemaVersion, data, fromCheckpoint }`.
|
|
305
310
|
* @param {Uint8Array} duc_buf
|
|
306
|
-
* @param {number}
|
|
311
|
+
* @param {number} target_version
|
|
307
312
|
* @returns {any}
|
|
308
313
|
*/
|
|
309
|
-
export function
|
|
314
|
+
export function revertToVersion(duc_buf, target_version) {
|
|
310
315
|
const ptr0 = passArray8ToWasm0(duc_buf, wasm.__wbindgen_malloc);
|
|
311
316
|
const len0 = WASM_VECTOR_LEN;
|
|
312
|
-
const ret = wasm.
|
|
317
|
+
const ret = wasm.revertToVersion(ptr0, len0, target_version);
|
|
318
|
+
if (ret[2]) {
|
|
319
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
320
|
+
}
|
|
321
|
+
return takeFromExternrefTable0(ret[0]);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Apply a changeset to reconstruct document state.
|
|
326
|
+
*
|
|
327
|
+
* `base_state` must be the exact checkpoint data used when the changeset
|
|
328
|
+
* was created. Returns the full document state as `Uint8Array`.
|
|
329
|
+
*
|
|
330
|
+
* Handles all changeset formats transparently:
|
|
331
|
+
* - v3 (bsdiff), v2 (XOR diff), v1 (zlib full snapshot)
|
|
332
|
+
* @param {Uint8Array} base_state
|
|
333
|
+
* @param {Uint8Array} changeset
|
|
334
|
+
* @returns {Uint8Array}
|
|
335
|
+
*/
|
|
336
|
+
export function applyDeltaChangeset(base_state, changeset) {
|
|
337
|
+
const ptr0 = passArray8ToWasm0(base_state, wasm.__wbindgen_malloc);
|
|
338
|
+
const len0 = WASM_VECTOR_LEN;
|
|
339
|
+
const ptr1 = passArray8ToWasm0(changeset, wasm.__wbindgen_malloc);
|
|
340
|
+
const len1 = WASM_VECTOR_LEN;
|
|
341
|
+
const ret = wasm.applyDeltaChangeset(ptr0, len0, ptr1, len1);
|
|
342
|
+
if (ret[3]) {
|
|
343
|
+
throw takeFromExternrefTable0(ret[2]);
|
|
344
|
+
}
|
|
345
|
+
var v3 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
|
346
|
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
|
347
|
+
return v3;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* List metadata for all external files (without loading the heavy data blobs).
|
|
352
|
+
* @param {Uint8Array} buf
|
|
353
|
+
* @returns {any}
|
|
354
|
+
*/
|
|
355
|
+
export function listExternalFiles(buf) {
|
|
356
|
+
const ptr0 = passArray8ToWasm0(buf, wasm.__wbindgen_malloc);
|
|
357
|
+
const len0 = WASM_VECTOR_LEN;
|
|
358
|
+
const ret = wasm.listExternalFiles(ptr0, len0);
|
|
313
359
|
if (ret[2]) {
|
|
314
360
|
throw takeFromExternrefTable0(ret[1]);
|
|
315
361
|
}
|
|
@@ -332,36 +378,41 @@ export function serializeDuc(data) {
|
|
|
332
378
|
}
|
|
333
379
|
|
|
334
380
|
/**
|
|
335
|
-
*
|
|
381
|
+
* Compute a checkpoint-relative binary diff changeset using bsdiff.
|
|
336
382
|
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
383
|
+
* `base_state` is the checkpoint's full data blob.
|
|
384
|
+
* `current_state` is the full document state at the new version.
|
|
385
|
+
*
|
|
386
|
+
* Returns an encoded changeset (`Uint8Array`) ready for storage in a
|
|
387
|
+
* `Delta.payload`. bsdiff finds matching blocks even when they shift
|
|
388
|
+
* offsets, which is critical for SQLite databases.
|
|
389
|
+
* @param {Uint8Array} base_state
|
|
390
|
+
* @param {Uint8Array} current_state
|
|
391
|
+
* @returns {Uint8Array}
|
|
341
392
|
*/
|
|
342
|
-
export function
|
|
343
|
-
const ptr0 = passArray8ToWasm0(
|
|
393
|
+
export function createDeltaChangeset(base_state, current_state) {
|
|
394
|
+
const ptr0 = passArray8ToWasm0(base_state, wasm.__wbindgen_malloc);
|
|
344
395
|
const len0 = WASM_VECTOR_LEN;
|
|
345
|
-
const ptr1 =
|
|
396
|
+
const ptr1 = passArray8ToWasm0(current_state, wasm.__wbindgen_malloc);
|
|
346
397
|
const len1 = WASM_VECTOR_LEN;
|
|
347
|
-
const ret = wasm.
|
|
348
|
-
if (ret[
|
|
349
|
-
throw takeFromExternrefTable0(ret[
|
|
398
|
+
const ret = wasm.createDeltaChangeset(ptr0, len0, ptr1, len1);
|
|
399
|
+
if (ret[3]) {
|
|
400
|
+
throw takeFromExternrefTable0(ret[2]);
|
|
350
401
|
}
|
|
351
|
-
|
|
402
|
+
var v3 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
|
403
|
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
|
404
|
+
return v3;
|
|
352
405
|
}
|
|
353
406
|
|
|
354
407
|
/**
|
|
355
|
-
* Parse a `.duc` file
|
|
356
|
-
*
|
|
357
|
-
* Use `getExternalFile()` or `listExternalFiles()` for on-demand access.
|
|
408
|
+
* Parse a `.duc` file (Uint8Array) into a JS object (ExportedDataState).
|
|
358
409
|
* @param {Uint8Array} buf
|
|
359
410
|
* @returns {any}
|
|
360
411
|
*/
|
|
361
|
-
export function
|
|
412
|
+
export function parseDuc(buf) {
|
|
362
413
|
const ptr0 = passArray8ToWasm0(buf, wasm.__wbindgen_malloc);
|
|
363
414
|
const len0 = WASM_VECTOR_LEN;
|
|
364
|
-
const ret = wasm.
|
|
415
|
+
const ret = wasm.parseDuc(ptr0, len0);
|
|
365
416
|
if (ret[2]) {
|
|
366
417
|
throw takeFromExternrefTable0(ret[1]);
|
|
367
418
|
}
|
|
@@ -369,14 +420,16 @@ export function parseDucLazy(buf) {
|
|
|
369
420
|
}
|
|
370
421
|
|
|
371
422
|
/**
|
|
372
|
-
*
|
|
423
|
+
* Parse a `.duc` file lazily — returns everything EXCEPT external file data blobs.
|
|
424
|
+
*
|
|
425
|
+
* Use `getExternalFile()` or `listExternalFiles()` for on-demand access.
|
|
373
426
|
* @param {Uint8Array} buf
|
|
374
427
|
* @returns {any}
|
|
375
428
|
*/
|
|
376
|
-
export function
|
|
429
|
+
export function parseDucLazy(buf) {
|
|
377
430
|
const ptr0 = passArray8ToWasm0(buf, wasm.__wbindgen_malloc);
|
|
378
431
|
const len0 = WASM_VECTOR_LEN;
|
|
379
|
-
const ret = wasm.
|
|
432
|
+
const ret = wasm.parseDucLazy(ptr0, len0);
|
|
380
433
|
if (ret[2]) {
|
|
381
434
|
throw takeFromExternrefTable0(ret[1]);
|
|
382
435
|
}
|
package/dist/ducjs_wasm_bg.wasm
CHANGED
|
Binary file
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/* tslint:disable */
|
|
2
2
|
/* eslint-disable */
|
|
3
3
|
export const memory: WebAssembly.Memory;
|
|
4
|
+
export const applyDeltaChangeset: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
|
5
|
+
export const createDeltaChangeset: (a: number, b: number, c: number, d: number) => [number, number, number, number];
|
|
4
6
|
export const getCurrentSchemaVersion: () => number;
|
|
5
7
|
export const getExternalFile: (a: number, b: number, c: number, d: number) => [number, number, number];
|
|
6
8
|
export const listExternalFiles: (a: number, b: number) => [number, number, number];
|
package/dist/lazy-files.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DucExternalFile, DucExternalFiles, ExternalFileRevision } from "./types";
|
|
2
2
|
export type LazyFileMetadata = {
|
|
3
3
|
id: string;
|
|
4
4
|
mimeType: string;
|
|
@@ -26,17 +26,19 @@ export declare class LazyExternalFileStore {
|
|
|
26
26
|
getMetadata(fileId: string): LazyFileMetadata | undefined;
|
|
27
27
|
/** Get metadata for all files. */
|
|
28
28
|
getAllMetadata(): LazyFileMetadata[];
|
|
29
|
-
/** Fetch the full file
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
|
|
29
|
+
/** Fetch the full file (including data blobs for all revisions) for a specific file. */
|
|
30
|
+
getFile(fileId: string): DucExternalFile | null;
|
|
31
|
+
/** Get the active revision data for a specific file. */
|
|
32
|
+
getFileData(fileId: string): ExternalFileRevision | null;
|
|
33
|
+
/** Fetch active revision data and return a copy of the data buffer (safe for transfer). */
|
|
34
|
+
getFileDataCopy(fileId: string): ExternalFileRevision | null;
|
|
33
35
|
/** Add a file at runtime (not persisted in .duc until next serialize). */
|
|
34
|
-
addRuntimeFile(fileId: string,
|
|
36
|
+
addRuntimeFile(fileId: string, file: DucExternalFile): void;
|
|
35
37
|
/** Remove a runtime file. */
|
|
36
38
|
removeRuntimeFile(fileId: string): boolean;
|
|
37
39
|
/** Export all files eagerly as a DucExternalFiles record. */
|
|
38
40
|
toExternalFiles(): DucExternalFiles;
|
|
39
|
-
/** Merge files from another source
|
|
41
|
+
/** Merge files from another source. Adds missing files and merges new revisions into existing ones. */
|
|
40
42
|
mergeFiles(files: DucExternalFiles): void;
|
|
41
43
|
/** Release the underlying buffer to free memory. */
|
|
42
44
|
release(): void;
|
package/dist/lazy-files.js
CHANGED
|
@@ -26,11 +26,14 @@ export class LazyExternalFileStore {
|
|
|
26
26
|
getMetadata(fileId) {
|
|
27
27
|
const rt = this.runtimeFiles.get(fileId);
|
|
28
28
|
if (rt) {
|
|
29
|
+
const active = rt.revisions[rt.activeRevisionId];
|
|
30
|
+
if (!active)
|
|
31
|
+
return undefined;
|
|
29
32
|
return {
|
|
30
33
|
id: rt.id,
|
|
31
|
-
mimeType:
|
|
32
|
-
created:
|
|
33
|
-
lastRetrieved:
|
|
34
|
+
mimeType: active.mimeType,
|
|
35
|
+
created: active.created,
|
|
36
|
+
lastRetrieved: active.lastRetrieved,
|
|
34
37
|
version: rt.version,
|
|
35
38
|
};
|
|
36
39
|
}
|
|
@@ -39,24 +42,28 @@ export class LazyExternalFileStore {
|
|
|
39
42
|
/** Get metadata for all files. */
|
|
40
43
|
getAllMetadata() {
|
|
41
44
|
const result = [];
|
|
42
|
-
|
|
45
|
+
const persisted = this.getMetadataMap();
|
|
46
|
+
for (const meta of persisted.values()) {
|
|
43
47
|
result.push(meta);
|
|
44
48
|
}
|
|
45
|
-
for (const [id,
|
|
46
|
-
if (!
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
for (const [id, file] of this.runtimeFiles) {
|
|
50
|
+
if (!persisted.has(id)) {
|
|
51
|
+
const active = file.revisions[file.activeRevisionId];
|
|
52
|
+
if (active) {
|
|
53
|
+
result.push({
|
|
54
|
+
id: file.id,
|
|
55
|
+
mimeType: active.mimeType,
|
|
56
|
+
created: active.created,
|
|
57
|
+
lastRetrieved: active.lastRetrieved,
|
|
58
|
+
version: file.version,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
54
61
|
}
|
|
55
62
|
}
|
|
56
63
|
return result;
|
|
57
64
|
}
|
|
58
|
-
/** Fetch the full file
|
|
59
|
-
|
|
65
|
+
/** Fetch the full file (including data blobs for all revisions) for a specific file. */
|
|
66
|
+
getFile(fileId) {
|
|
60
67
|
const rt = this.runtimeFiles.get(fileId);
|
|
61
68
|
if (rt)
|
|
62
69
|
return rt;
|
|
@@ -65,15 +72,17 @@ export class LazyExternalFileStore {
|
|
|
65
72
|
const result = wasmGetExternalFile(this.buffer, fileId);
|
|
66
73
|
if (!result)
|
|
67
74
|
return null;
|
|
68
|
-
|
|
69
|
-
// Unwrap to get the actual file data.
|
|
70
|
-
const entry = result;
|
|
71
|
-
if (entry.value && typeof entry.value === "object") {
|
|
72
|
-
return entry.value;
|
|
73
|
-
}
|
|
74
|
-
return entry;
|
|
75
|
+
return result;
|
|
75
76
|
}
|
|
76
|
-
/**
|
|
77
|
+
/** Get the active revision data for a specific file. */
|
|
78
|
+
getFileData(fileId) {
|
|
79
|
+
var _a;
|
|
80
|
+
const file = this.getFile(fileId);
|
|
81
|
+
if (!file)
|
|
82
|
+
return null;
|
|
83
|
+
return (_a = file.revisions[file.activeRevisionId]) !== null && _a !== void 0 ? _a : null;
|
|
84
|
+
}
|
|
85
|
+
/** Fetch active revision data and return a copy of the data buffer (safe for transfer). */
|
|
77
86
|
getFileDataCopy(fileId) {
|
|
78
87
|
const data = this.getFileData(fileId);
|
|
79
88
|
if (!data)
|
|
@@ -81,8 +90,8 @@ export class LazyExternalFileStore {
|
|
|
81
90
|
return Object.assign(Object.assign({}, data), { data: new Uint8Array(data.data) });
|
|
82
91
|
}
|
|
83
92
|
/** Add a file at runtime (not persisted in .duc until next serialize). */
|
|
84
|
-
addRuntimeFile(fileId,
|
|
85
|
-
this.runtimeFiles.set(fileId,
|
|
93
|
+
addRuntimeFile(fileId, file) {
|
|
94
|
+
this.runtimeFiles.set(fileId, file);
|
|
86
95
|
}
|
|
87
96
|
/** Remove a runtime file. */
|
|
88
97
|
removeRuntimeFile(fileId) {
|
|
@@ -92,24 +101,42 @@ export class LazyExternalFileStore {
|
|
|
92
101
|
toExternalFiles() {
|
|
93
102
|
const result = {};
|
|
94
103
|
if (this.buffer) {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
result[id] = data;
|
|
104
|
+
for (const [id] of this.getMetadataMap()) {
|
|
105
|
+
const file = this.getFile(id);
|
|
106
|
+
if (file) {
|
|
107
|
+
result[id] = file;
|
|
100
108
|
}
|
|
101
109
|
}
|
|
102
110
|
}
|
|
103
|
-
for (const [id,
|
|
104
|
-
result[id] =
|
|
111
|
+
for (const [id, file] of this.runtimeFiles) {
|
|
112
|
+
result[id] = file;
|
|
105
113
|
}
|
|
106
114
|
return result;
|
|
107
115
|
}
|
|
108
|
-
/** Merge files from another source
|
|
116
|
+
/** Merge files from another source. Adds missing files and merges new revisions into existing ones. */
|
|
109
117
|
mergeFiles(files) {
|
|
110
|
-
|
|
118
|
+
var _a, _b, _c;
|
|
119
|
+
for (const [id, file] of Object.entries(files)) {
|
|
111
120
|
if (!this.has(id)) {
|
|
112
|
-
this.runtimeFiles.set(id,
|
|
121
|
+
this.runtimeFiles.set(id, file);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const existing = (_a = this.runtimeFiles.get(id)) !== null && _a !== void 0 ? _a : this.getFile(id);
|
|
125
|
+
if (!existing) {
|
|
126
|
+
this.runtimeFiles.set(id, file);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
// Merge: add any new revisions that don't exist yet, and update metadata
|
|
130
|
+
let merged = false;
|
|
131
|
+
const mergedRevisions = Object.assign({}, existing.revisions);
|
|
132
|
+
for (const [revId, rev] of Object.entries(file.revisions)) {
|
|
133
|
+
if (!mergedRevisions[revId]) {
|
|
134
|
+
mergedRevisions[revId] = rev;
|
|
135
|
+
merged = true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (merged || file.updated > existing.updated) {
|
|
139
|
+
this.runtimeFiles.set(id, Object.assign(Object.assign({}, existing), { activeRevisionId: file.activeRevisionId, updated: Math.max(file.updated, existing.updated), version: Math.max((_b = file.version) !== null && _b !== void 0 ? _b : 0, (_c = existing.version) !== null && _c !== void 0 ? _c : 0), revisions: mergedRevisions }));
|
|
113
140
|
}
|
|
114
141
|
}
|
|
115
142
|
}
|
package/dist/parse.js
CHANGED
|
@@ -40,7 +40,18 @@ export function parseDuc(blob, _fileHandle, elementsConfig, restoreConfig) {
|
|
|
40
40
|
throw new Error(`[parseDuc] wasm parse failed (size=${buffer.byteLength}, header="${header}", prefix=${prefixHex}): ${error instanceof Error ? error.message : String(error)}`);
|
|
41
41
|
}
|
|
42
42
|
const data = transformFromRust(raw);
|
|
43
|
-
|
|
43
|
+
// Preserve the original version graph from Rust before restore() can
|
|
44
|
+
// filter out entries through restoreCheckpoint/restoreDelta validation.
|
|
45
|
+
// The VG data is already validated by the Rust parser.
|
|
46
|
+
const originalVG = data.versionGraph;
|
|
47
|
+
const restored = restore(Object.assign(Object.assign({}, data), {
|
|
48
|
+
// Preserve versionGraph from Rust separately; do not run it through restore()
|
|
49
|
+
versionGraph: undefined }), elementsConfig !== null && elementsConfig !== void 0 ? elementsConfig : { syncInvalidIndices: (els) => els }, restoreConfig);
|
|
50
|
+
// Use the original version graph from Rust, bypassing restore's lossy filtering
|
|
51
|
+
if (originalVG) {
|
|
52
|
+
restored.versionGraph = originalVG;
|
|
53
|
+
}
|
|
54
|
+
return restored;
|
|
44
55
|
});
|
|
45
56
|
}
|
|
46
57
|
/**
|
|
@@ -65,10 +76,18 @@ export function parseDucLazy(buffer, elementsConfig, restoreConfig) {
|
|
|
65
76
|
throw new Error(`[parseDucLazy] wasm parse failed (size=${buffer.byteLength}, header="${header}", prefix=${prefixHex}): ${error instanceof Error ? error.message : String(error)}`);
|
|
66
77
|
}
|
|
67
78
|
const data = transformFromRust(raw);
|
|
79
|
+
// Preserve the original version graph from Rust before restore() filters
|
|
80
|
+
const originalVG = data.versionGraph;
|
|
68
81
|
const lazyFileStore = new LazyExternalFileStore(buffer);
|
|
69
82
|
const files = {};
|
|
70
|
-
const restored = restore(Object.assign(Object.assign({}, data), { files
|
|
83
|
+
const restored = restore(Object.assign(Object.assign({}, data), { files,
|
|
84
|
+
// Preserve versionGraph from Rust separately; do not run it through restore()
|
|
85
|
+
versionGraph: undefined }), elementsConfig !== null && elementsConfig !== void 0 ? elementsConfig : { syncInvalidIndices: (els) => els }, restoreConfig);
|
|
71
86
|
restored.lazyFileStore = lazyFileStore;
|
|
87
|
+
// Use the original version graph from Rust, bypassing restore's lossy filtering
|
|
88
|
+
if (originalVG) {
|
|
89
|
+
restored.versionGraph = originalVG;
|
|
90
|
+
}
|
|
72
91
|
return restored;
|
|
73
92
|
});
|
|
74
93
|
}
|
|
@@ -41,46 +41,94 @@ export const restore = (data, elementsConfig, restoreConfig = {}) => {
|
|
|
41
41
|
};
|
|
42
42
|
};
|
|
43
43
|
export const restoreFiles = (importedFiles) => {
|
|
44
|
-
var _a;
|
|
44
|
+
var _a, _b;
|
|
45
45
|
if (!importedFiles || typeof importedFiles !== "object") {
|
|
46
46
|
return {};
|
|
47
47
|
}
|
|
48
48
|
const restoredFiles = {};
|
|
49
49
|
const files = importedFiles;
|
|
50
50
|
for (const key in files) {
|
|
51
|
-
if (Object.prototype.hasOwnProperty.call(files, key))
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
if (!Object.prototype.hasOwnProperty.call(files, key))
|
|
52
|
+
continue;
|
|
53
|
+
const fileData = files[key];
|
|
54
|
+
if (!fileData || typeof fileData !== "object")
|
|
55
|
+
continue;
|
|
56
|
+
const fd = fileData;
|
|
57
|
+
// New format: DucExternalFile with revisions map
|
|
58
|
+
if (fd.revisions && typeof fd.revisions === "object" && fd.activeRevisionId) {
|
|
59
|
+
const id = isValidExternalFileId(fd.id);
|
|
60
|
+
if (!id)
|
|
54
61
|
continue;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
id,
|
|
62
|
+
const restoredRevisions = {};
|
|
63
|
+
const rawRevisions = fd.revisions;
|
|
64
|
+
for (const revKey in rawRevisions) {
|
|
65
|
+
if (!Object.prototype.hasOwnProperty.call(rawRevisions, revKey))
|
|
66
|
+
continue;
|
|
67
|
+
const rev = rawRevisions[revKey];
|
|
68
|
+
if (!rev || typeof rev !== "object")
|
|
69
|
+
continue;
|
|
70
|
+
const r = rev;
|
|
71
|
+
const revId = isValidString(r.id);
|
|
72
|
+
const mimeType = isValidString(r.mimeType);
|
|
73
|
+
const dataSource = (_a = r.data) !== null && _a !== void 0 ? _a : r.dataURL;
|
|
74
|
+
const data = isValidUint8Array(dataSource);
|
|
75
|
+
if (!revId || !mimeType || !data)
|
|
76
|
+
continue;
|
|
77
|
+
restoredRevisions[revKey] = {
|
|
78
|
+
id: revId,
|
|
79
|
+
sizeBytes: isFiniteNumber(r.sizeBytes) ? r.sizeBytes : data.byteLength,
|
|
80
|
+
checksum: isValidString(r.checksum) || undefined,
|
|
81
|
+
sourceName: isValidString(r.sourceName) || undefined,
|
|
72
82
|
mimeType,
|
|
83
|
+
message: isValidString(r.message) || undefined,
|
|
84
|
+
created: isFiniteNumber(r.created) ? r.created : Date.now(),
|
|
85
|
+
lastRetrieved: isFiniteNumber(r.lastRetrieved) ? r.lastRetrieved : undefined,
|
|
73
86
|
data,
|
|
74
|
-
created,
|
|
75
|
-
lastRetrieved: isFiniteNumber(fileData.lastRetrieved)
|
|
76
|
-
? fileData.lastRetrieved
|
|
77
|
-
: undefined,
|
|
78
|
-
version: isFiniteNumber(fileData.version)
|
|
79
|
-
? fileData.version
|
|
80
|
-
: undefined,
|
|
81
87
|
};
|
|
82
88
|
}
|
|
89
|
+
if (Object.keys(restoredRevisions).length === 0)
|
|
90
|
+
continue;
|
|
91
|
+
restoredFiles[id] = {
|
|
92
|
+
id,
|
|
93
|
+
activeRevisionId: isValidString(fd.activeRevisionId) || Object.keys(restoredRevisions)[0],
|
|
94
|
+
updated: isFiniteNumber(fd.updated) ? fd.updated : Date.now(),
|
|
95
|
+
revisions: restoredRevisions,
|
|
96
|
+
version: isFiniteNumber(fd.version) ? fd.version : undefined,
|
|
97
|
+
};
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
// Legacy flat format: DucExternalFileData — wrap in a single-revision DucExternalFile.
|
|
101
|
+
let legacyData = fd;
|
|
102
|
+
// Handle the nested { key, value: { ... } } structure from old Rust serde output.
|
|
103
|
+
if (fd.value && typeof fd.value === "object") {
|
|
104
|
+
legacyData = fd.value;
|
|
83
105
|
}
|
|
106
|
+
const id = isValidExternalFileId(legacyData.id);
|
|
107
|
+
const mimeType = isValidString(legacyData.mimeType);
|
|
108
|
+
const dataSource = (_b = legacyData.data) !== null && _b !== void 0 ? _b : legacyData.dataURL;
|
|
109
|
+
const data = isValidUint8Array(dataSource);
|
|
110
|
+
if (!id || !mimeType || !data)
|
|
111
|
+
continue;
|
|
112
|
+
const revId = `${id}_rev1`;
|
|
113
|
+
const created = isFiniteNumber(legacyData.created) ? legacyData.created : Date.now();
|
|
114
|
+
restoredFiles[id] = {
|
|
115
|
+
id,
|
|
116
|
+
activeRevisionId: revId,
|
|
117
|
+
updated: created,
|
|
118
|
+
version: isFiniteNumber(legacyData.version) ? legacyData.version : undefined,
|
|
119
|
+
revisions: {
|
|
120
|
+
[revId]: {
|
|
121
|
+
id: revId,
|
|
122
|
+
sizeBytes: data.byteLength,
|
|
123
|
+
mimeType,
|
|
124
|
+
created,
|
|
125
|
+
lastRetrieved: isFiniteNumber(legacyData.lastRetrieved)
|
|
126
|
+
? legacyData.lastRetrieved
|
|
127
|
+
: undefined,
|
|
128
|
+
data,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
};
|
|
84
132
|
}
|
|
85
133
|
return restoredFiles;
|
|
86
134
|
};
|
|
@@ -367,11 +415,6 @@ export const restoreVersionGraph = (importedGraph) => {
|
|
|
367
415
|
if (!importedGraph || typeof importedGraph !== "object") {
|
|
368
416
|
return undefined;
|
|
369
417
|
}
|
|
370
|
-
const userCheckpointVersionId = isValidString(importedGraph.userCheckpointVersionId);
|
|
371
|
-
const latestVersionId = isValidString(importedGraph.latestVersionId);
|
|
372
|
-
if (!userCheckpointVersionId || !latestVersionId) {
|
|
373
|
-
return undefined;
|
|
374
|
-
}
|
|
375
418
|
const checkpoints = Array.isArray(importedGraph.checkpoints)
|
|
376
419
|
? importedGraph.checkpoints
|
|
377
420
|
.map((checkpoint) => restoreCheckpoint(checkpoint))
|
|
@@ -382,6 +425,17 @@ export const restoreVersionGraph = (importedGraph) => {
|
|
|
382
425
|
.map((delta) => restoreDelta(delta))
|
|
383
426
|
.filter((delta) => Boolean(delta))
|
|
384
427
|
: [];
|
|
428
|
+
if (checkpoints.length === 0 && deltas.length === 0) {
|
|
429
|
+
return undefined;
|
|
430
|
+
}
|
|
431
|
+
// Head IDs can legitimately be empty strings (e.g. after Rust roundtrip
|
|
432
|
+
// where the DB stores NULL → unwrap_or_default → ""). Accept any string.
|
|
433
|
+
const userCheckpointVersionId = typeof importedGraph.userCheckpointVersionId === "string"
|
|
434
|
+
? importedGraph.userCheckpointVersionId
|
|
435
|
+
: "";
|
|
436
|
+
const latestVersionId = typeof importedGraph.latestVersionId === "string"
|
|
437
|
+
? importedGraph.latestVersionId
|
|
438
|
+
: "";
|
|
385
439
|
const importedMetadata = importedGraph.metadata;
|
|
386
440
|
const metadata = {
|
|
387
441
|
currentVersion: isFiniteNumber(importedMetadata === null || importedMetadata === void 0 ? void 0 : importedMetadata.currentVersion) && importedMetadata.currentVersion >= 0
|
|
@@ -411,6 +465,7 @@ export const restoreVersionGraph = (importedGraph) => {
|
|
|
411
465
|
};
|
|
412
466
|
};
|
|
413
467
|
export const restoreCheckpoint = (importedCheckpoint) => {
|
|
468
|
+
var _a, _b, _c;
|
|
414
469
|
if (!importedCheckpoint || typeof importedCheckpoint !== "object") {
|
|
415
470
|
return undefined;
|
|
416
471
|
}
|
|
@@ -419,10 +474,11 @@ export const restoreCheckpoint = (importedCheckpoint) => {
|
|
|
419
474
|
return undefined;
|
|
420
475
|
}
|
|
421
476
|
const id = isValidString(checkpoint.id);
|
|
422
|
-
|
|
423
|
-
if (!id || !data) {
|
|
477
|
+
if (!id) {
|
|
424
478
|
return undefined;
|
|
425
479
|
}
|
|
480
|
+
// Accept empty Uint8Array — shell/remote entries have byteLength === 0
|
|
481
|
+
const data = (_c = (_b = (_a = isValidUint8Array(checkpoint.data)) !== null && _a !== void 0 ? _a : (checkpoint.data instanceof Uint8Array ? checkpoint.data : undefined)) !== null && _b !== void 0 ? _b : (checkpoint.data instanceof ArrayBuffer ? new Uint8Array(checkpoint.data) : undefined)) !== null && _c !== void 0 ? _c : new Uint8Array(0);
|
|
426
482
|
return {
|
|
427
483
|
type: "checkpoint",
|
|
428
484
|
id,
|
|
@@ -443,6 +499,7 @@ export const restoreCheckpoint = (importedCheckpoint) => {
|
|
|
443
499
|
};
|
|
444
500
|
};
|
|
445
501
|
export const restoreDelta = (importedDelta) => {
|
|
502
|
+
var _a, _b, _c;
|
|
446
503
|
if (!importedDelta || typeof importedDelta !== "object") {
|
|
447
504
|
return undefined;
|
|
448
505
|
}
|
|
@@ -451,11 +508,13 @@ export const restoreDelta = (importedDelta) => {
|
|
|
451
508
|
return undefined;
|
|
452
509
|
}
|
|
453
510
|
const id = isValidString(delta.id);
|
|
454
|
-
|
|
455
|
-
const baseCheckpointId = isValidString(delta.baseCheckpointId);
|
|
456
|
-
if (!id || !payload || !baseCheckpointId) {
|
|
511
|
+
if (!id) {
|
|
457
512
|
return undefined;
|
|
458
513
|
}
|
|
514
|
+
// Accept empty Uint8Array — shell/remote entries have byteLength === 0
|
|
515
|
+
const payload = (_c = (_b = (_a = isValidUint8Array(delta.payload)) !== null && _a !== void 0 ? _a : (delta.payload instanceof Uint8Array ? delta.payload : undefined)) !== null && _b !== void 0 ? _b : (delta.payload instanceof ArrayBuffer ? new Uint8Array(delta.payload) : undefined)) !== null && _c !== void 0 ? _c : new Uint8Array(0);
|
|
516
|
+
// baseCheckpointId can be empty for shell entries or first-in-chain deltas
|
|
517
|
+
const baseCheckpointId = typeof delta.baseCheckpointId === "string" ? delta.baseCheckpointId : "";
|
|
459
518
|
return {
|
|
460
519
|
type: "delta",
|
|
461
520
|
id,
|
package/dist/serialize.js
CHANGED
|
@@ -33,15 +33,111 @@ export function serializeDuc(data, elementsConfig, restoreConfig) {
|
|
|
33
33
|
return __awaiter(this, void 0, void 0, function* () {
|
|
34
34
|
var _a, _b, _c, _d;
|
|
35
35
|
yield ensureWasm();
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
const inputVG = data === null || data === void 0 ? void 0 : data.versionGraph;
|
|
37
|
+
const restored = restore(Object.assign(Object.assign({}, data), {
|
|
38
|
+
// Version graph is preserved separately — bypass restore()'s VG processing.
|
|
39
|
+
versionGraph: undefined }), elementsConfig !== null && elementsConfig !== void 0 ? elementsConfig : { syncInvalidIndices: (els) => els }, restoreConfig);
|
|
40
|
+
const shouldDropLegacyVersionGraph = hasLegacyVersionGraphShape(inputVG);
|
|
41
|
+
// Use the ORIGINAL version graph data instead of the restored one.
|
|
42
|
+
// The restore pipeline (restoreCheckpoint/restoreDelta) filters checkpoints
|
|
43
|
+
// and deltas through isValidUint8Array which can reject valid in-memory
|
|
44
|
+
// Uint8Array data (e.g. empty remote placeholders or detached buffers),
|
|
45
|
+
// silently dropping version history.
|
|
46
|
+
// hasLegacyVersionGraphShape already validates structural integrity.
|
|
47
|
+
const versionGraphForPayload = shouldDropLegacyVersionGraph
|
|
48
|
+
? undefined
|
|
49
|
+
: prepareVersionGraphForSerialization(inputVG);
|
|
50
|
+
const normalizedVersionGraphForPayload = versionGraphForPayload
|
|
51
|
+
? normalizeVersionGraphVersionNumbers(versionGraphForPayload)
|
|
52
|
+
: undefined;
|
|
53
|
+
const payloadForRust = Object.assign(Object.assign({ type: (_a = data.type) !== null && _a !== void 0 ? _a : "duc", version: (_c = (_b = data.version) !== null && _b !== void 0 ? _b : DUC_SCHEMA_VERSION) !== null && _c !== void 0 ? _c : decodeUserVersionToSemver(wasmGetCurrentSchemaVersion()), source: (_d = data.source) !== null && _d !== void 0 ? _d : "ducjs" }, restored), { versionGraph: normalizedVersionGraphForPayload });
|
|
41
54
|
const prepared = transformToRust(payloadForRust);
|
|
42
|
-
|
|
55
|
+
const serialized = wasmSerializeDuc(prepared);
|
|
56
|
+
return serialized;
|
|
43
57
|
});
|
|
44
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Ensure version numbers are unique before persistence.
|
|
61
|
+
* SQLite schema enforces UNIQUE(version_number) on both checkpoints and deltas.
|
|
62
|
+
* Collisions would otherwise cause INSERT OR REPLACE to drop historical entries.
|
|
63
|
+
*/
|
|
64
|
+
function normalizeVersionGraphVersionNumbers(vg) {
|
|
65
|
+
var _a;
|
|
66
|
+
const normalizeList = (items) => {
|
|
67
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
const sorted = [...items].sort((a, b) => {
|
|
71
|
+
const aNum = Number.isFinite(a === null || a === void 0 ? void 0 : a.versionNumber) ? Number(a.versionNumber) : Number.MAX_SAFE_INTEGER;
|
|
72
|
+
const bNum = Number.isFinite(b === null || b === void 0 ? void 0 : b.versionNumber) ? Number(b.versionNumber) : Number.MAX_SAFE_INTEGER;
|
|
73
|
+
if (aNum !== bNum)
|
|
74
|
+
return aNum - bNum;
|
|
75
|
+
const aTs = Number.isFinite(a === null || a === void 0 ? void 0 : a.timestamp) ? Number(a.timestamp) : 0;
|
|
76
|
+
const bTs = Number.isFinite(b === null || b === void 0 ? void 0 : b.timestamp) ? Number(b.timestamp) : 0;
|
|
77
|
+
if (aTs !== bTs)
|
|
78
|
+
return aTs - bTs;
|
|
79
|
+
const aId = typeof (a === null || a === void 0 ? void 0 : a.id) === "string" ? a.id : "";
|
|
80
|
+
const bId = typeof (b === null || b === void 0 ? void 0 : b.id) === "string" ? b.id : "";
|
|
81
|
+
return aId.localeCompare(bId);
|
|
82
|
+
});
|
|
83
|
+
let nextVersion = 0;
|
|
84
|
+
return sorted.map((item) => {
|
|
85
|
+
const candidate = Number.isFinite(item === null || item === void 0 ? void 0 : item.versionNumber) ? Number(item.versionNumber) : nextVersion;
|
|
86
|
+
const assigned = Math.max(candidate, nextVersion);
|
|
87
|
+
nextVersion = assigned + 1;
|
|
88
|
+
return Object.assign(Object.assign({}, item), { versionNumber: assigned });
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
const checkpoints = normalizeList(Array.isArray(vg === null || vg === void 0 ? void 0 : vg.checkpoints) ? vg.checkpoints : []);
|
|
92
|
+
const deltas = normalizeList(Array.isArray(vg === null || vg === void 0 ? void 0 : vg.deltas) ? vg.deltas : []);
|
|
93
|
+
const maxCheckpointVersion = checkpoints.length
|
|
94
|
+
? Math.max(...checkpoints.map((cp) => Number(cp.versionNumber) || 0))
|
|
95
|
+
: 0;
|
|
96
|
+
const maxDeltaVersion = deltas.length
|
|
97
|
+
? Math.max(...deltas.map((d) => Number(d.versionNumber) || 0))
|
|
98
|
+
: 0;
|
|
99
|
+
const maxVersion = Math.max(maxCheckpointVersion, maxDeltaVersion, 0);
|
|
100
|
+
return Object.assign(Object.assign({}, vg), { checkpoints,
|
|
101
|
+
deltas, metadata: Object.assign(Object.assign({}, vg.metadata), { currentVersion: Number.isFinite((_a = vg === null || vg === void 0 ? void 0 : vg.metadata) === null || _a === void 0 ? void 0 : _a.currentVersion)
|
|
102
|
+
? Math.max(Number(vg.metadata.currentVersion), maxVersion)
|
|
103
|
+
: maxVersion }) });
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Prepares version graph for serialization by filtering out shell entries
|
|
107
|
+
* (remote placeholders with empty data/payload) while preserving all entries
|
|
108
|
+
* with actual binary data intact.
|
|
109
|
+
*/
|
|
110
|
+
function prepareVersionGraphForSerialization(vg) {
|
|
111
|
+
if (!vg || typeof vg !== "object")
|
|
112
|
+
return undefined;
|
|
113
|
+
const checkpoints = Array.isArray(vg.checkpoints)
|
|
114
|
+
? vg.checkpoints.filter((cp) => {
|
|
115
|
+
const data = cp === null || cp === void 0 ? void 0 : cp.data;
|
|
116
|
+
if (data instanceof Uint8Array)
|
|
117
|
+
return data.byteLength > 0;
|
|
118
|
+
if (data instanceof ArrayBuffer)
|
|
119
|
+
return data.byteLength > 0;
|
|
120
|
+
// Accept base64 strings (from JSON imports)
|
|
121
|
+
if (typeof data === "string" && data.length > 0)
|
|
122
|
+
return true;
|
|
123
|
+
return false;
|
|
124
|
+
})
|
|
125
|
+
: [];
|
|
126
|
+
const deltas = Array.isArray(vg.deltas)
|
|
127
|
+
? vg.deltas.filter((d) => {
|
|
128
|
+
const payload = d === null || d === void 0 ? void 0 : d.payload;
|
|
129
|
+
if (payload instanceof Uint8Array)
|
|
130
|
+
return payload.byteLength > 0;
|
|
131
|
+
if (payload instanceof ArrayBuffer)
|
|
132
|
+
return payload.byteLength > 0;
|
|
133
|
+
if (typeof payload === "string" && payload.length > 0)
|
|
134
|
+
return true;
|
|
135
|
+
return false;
|
|
136
|
+
})
|
|
137
|
+
: [];
|
|
138
|
+
return Object.assign(Object.assign({}, vg), { checkpoints,
|
|
139
|
+
deltas });
|
|
140
|
+
}
|
|
45
141
|
function hasLegacyVersionGraphShape(versionGraph) {
|
|
46
142
|
if (!versionGraph || typeof versionGraph !== "object") {
|
|
47
143
|
return false;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -79,30 +79,36 @@ export type DucUcs = {
|
|
|
79
79
|
angle: Radian;
|
|
80
80
|
};
|
|
81
81
|
export type Scope = SupportedMeasures;
|
|
82
|
-
export type
|
|
82
|
+
export type ExternalFileRevision = {
|
|
83
|
+
id: string;
|
|
84
|
+
sizeBytes: number;
|
|
85
|
+
/** Content hash for integrity checks and optional deduplication. */
|
|
86
|
+
checksum?: string;
|
|
87
|
+
/** Original upload filename shown to the user. */
|
|
88
|
+
sourceName?: string;
|
|
83
89
|
mimeType: string;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Epoch timestamp in milliseconds
|
|
88
|
-
*/
|
|
90
|
+
/** Optional note describing what changed in this revision. */
|
|
91
|
+
message?: string;
|
|
92
|
+
/** Epoch timestamp in milliseconds when this revision was created. */
|
|
89
93
|
created: number;
|
|
90
94
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* files from storage.
|
|
94
|
-
*
|
|
95
|
-
* Epoch timestamp in milliseconds.
|
|
95
|
+
* Epoch timestamp in milliseconds when this revision was last loaded onto
|
|
96
|
+
* the scene. Used to determine whether to delete unused files from storage.
|
|
96
97
|
*/
|
|
97
98
|
lastRetrieved?: number;
|
|
98
|
-
/**
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
/** The actual file content bytes. */
|
|
100
|
+
data: Uint8Array;
|
|
101
|
+
};
|
|
102
|
+
export type DucExternalFile = {
|
|
103
|
+
id: ExternalFileId;
|
|
104
|
+
activeRevisionId: string;
|
|
105
|
+
/** Epoch ms when the logical file was last mutated (revision added or active changed). */
|
|
106
|
+
updated: number;
|
|
107
|
+
/** All revisions of this file, keyed by their id. */
|
|
108
|
+
revisions: Record<string, ExternalFileRevision>;
|
|
102
109
|
version?: number;
|
|
103
110
|
};
|
|
104
|
-
export type
|
|
105
|
-
export type DucExternalFiles = Record<DucElement["id"], DucExternalFileData>;
|
|
111
|
+
export type DucExternalFiles = Record<ExternalFileId, DucExternalFile>;
|
|
106
112
|
export type SuggestedBinding = NonDeleted<DucBindableElement> | SuggestedPointBinding;
|
|
107
113
|
export type SuggestedPointBinding = [
|
|
108
114
|
NonDeleted<DucLinearElement>,
|
|
@@ -59,3 +59,29 @@ export declare const revertToVersion: (ducBuffer: Uint8Array, targetVersion: num
|
|
|
59
59
|
* on the next checkpoint or delta creation.
|
|
60
60
|
*/
|
|
61
61
|
export declare const getCurrentSchemaVersion: () => Promise<number>;
|
|
62
|
+
/**
|
|
63
|
+
* Compute a checkpoint-relative binary diff changeset using bsdiff.
|
|
64
|
+
*
|
|
65
|
+
* `baseState` is the checkpoint's full data blob (the snapshot at the
|
|
66
|
+
* base checkpoint version). `currentState` is the full document state
|
|
67
|
+
* at the new version being saved as a delta.
|
|
68
|
+
*
|
|
69
|
+
* Returns an encoded changeset (`Uint8Array`) suitable for use as
|
|
70
|
+
* `Delta.payload`. bsdiff finds matching blocks even when they shift
|
|
71
|
+
* offsets, which is critical for SQLite databases where internal page
|
|
72
|
+
* reordering makes simple byte-level diffs ineffective.
|
|
73
|
+
*
|
|
74
|
+
* Use this when constructing `Delta` objects for the `VersionGraph`
|
|
75
|
+
* before calling `serializeDuc()`.
|
|
76
|
+
*/
|
|
77
|
+
export declare const createDeltaChangeset: (baseState: Uint8Array, currentState: Uint8Array) => Promise<Uint8Array>;
|
|
78
|
+
/**
|
|
79
|
+
* Apply a changeset to reconstruct document state.
|
|
80
|
+
*
|
|
81
|
+
* `baseState` must be the exact checkpoint data used when the changeset
|
|
82
|
+
* was created. Returns the full document state as `Uint8Array`.
|
|
83
|
+
*
|
|
84
|
+
* Handles all changeset formats transparently:
|
|
85
|
+
* - v3 (bsdiff), v2 (XOR diff), v1 (zlib full snapshot)
|
|
86
|
+
*/
|
|
87
|
+
export declare const applyDeltaChangeset: (baseState: Uint8Array, changeset: Uint8Array) => Promise<Uint8Array>;
|
package/dist/version-control.js
CHANGED
|
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { ensureWasm, wasmGetCurrentSchemaVersion, wasmListVersions, wasmReadVersionGraph, wasmRestoreCheckpoint, wasmRestoreVersion, wasmRevertToVersion, } from "./wasm";
|
|
10
|
+
import { ensureWasm, wasmApplyDeltaChangeset, wasmCreateDeltaChangeset, wasmGetCurrentSchemaVersion, wasmListVersions, wasmReadVersionGraph, wasmRestoreCheckpoint, wasmRestoreVersion, wasmRevertToVersion, } from "./wasm";
|
|
11
11
|
/**
|
|
12
12
|
* Restore the full document state at a specific version number.
|
|
13
13
|
*
|
|
@@ -82,3 +82,35 @@ export const getCurrentSchemaVersion = () => __awaiter(void 0, void 0, void 0, f
|
|
|
82
82
|
yield ensureWasm();
|
|
83
83
|
return wasmGetCurrentSchemaVersion();
|
|
84
84
|
});
|
|
85
|
+
/**
|
|
86
|
+
* Compute a checkpoint-relative binary diff changeset using bsdiff.
|
|
87
|
+
*
|
|
88
|
+
* `baseState` is the checkpoint's full data blob (the snapshot at the
|
|
89
|
+
* base checkpoint version). `currentState` is the full document state
|
|
90
|
+
* at the new version being saved as a delta.
|
|
91
|
+
*
|
|
92
|
+
* Returns an encoded changeset (`Uint8Array`) suitable for use as
|
|
93
|
+
* `Delta.payload`. bsdiff finds matching blocks even when they shift
|
|
94
|
+
* offsets, which is critical for SQLite databases where internal page
|
|
95
|
+
* reordering makes simple byte-level diffs ineffective.
|
|
96
|
+
*
|
|
97
|
+
* Use this when constructing `Delta` objects for the `VersionGraph`
|
|
98
|
+
* before calling `serializeDuc()`.
|
|
99
|
+
*/
|
|
100
|
+
export const createDeltaChangeset = (baseState, currentState) => __awaiter(void 0, void 0, void 0, function* () {
|
|
101
|
+
yield ensureWasm();
|
|
102
|
+
return wasmCreateDeltaChangeset(baseState, currentState);
|
|
103
|
+
});
|
|
104
|
+
/**
|
|
105
|
+
* Apply a changeset to reconstruct document state.
|
|
106
|
+
*
|
|
107
|
+
* `baseState` must be the exact checkpoint data used when the changeset
|
|
108
|
+
* was created. Returns the full document state as `Uint8Array`.
|
|
109
|
+
*
|
|
110
|
+
* Handles all changeset formats transparently:
|
|
111
|
+
* - v3 (bsdiff), v2 (XOR diff), v1 (zlib full snapshot)
|
|
112
|
+
*/
|
|
113
|
+
export const applyDeltaChangeset = (baseState, changeset) => __awaiter(void 0, void 0, void 0, function* () {
|
|
114
|
+
yield ensureWasm();
|
|
115
|
+
return wasmApplyDeltaChangeset(baseState, changeset);
|
|
116
|
+
});
|
package/dist/wasm.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getCurrentSchemaVersion as _getCurrentSchemaVersion, getExternalFile as _getExternalFile, listExternalFiles as _listExternalFiles, listVersions as _listVersions, parseDuc as _parseDuc, parseDucLazy as _parseDucLazy, readVersionGraph as _readVersionGraph, restoreCheckpoint as _restoreCheckpoint, restoreVersion as _restoreVersion, revertToVersion as _revertToVersion, serializeDuc as _serializeDuc } from "../dist/ducjs_wasm";
|
|
1
|
+
import { applyDeltaChangeset as _applyDeltaChangeset, createDeltaChangeset as _createDeltaChangeset, getCurrentSchemaVersion as _getCurrentSchemaVersion, getExternalFile as _getExternalFile, listExternalFiles as _listExternalFiles, listVersions as _listVersions, parseDuc as _parseDuc, parseDucLazy as _parseDucLazy, readVersionGraph as _readVersionGraph, restoreCheckpoint as _restoreCheckpoint, restoreVersion as _restoreVersion, revertToVersion as _revertToVersion, serializeDuc as _serializeDuc } from "../dist/ducjs_wasm";
|
|
2
2
|
export declare function ensureWasm(wasmUrl?: string | URL | BufferSource): Promise<void>;
|
|
3
3
|
/**
|
|
4
4
|
* Fetch the raw WASM binary as an ArrayBuffer.
|
|
@@ -18,3 +18,5 @@ export declare const wasmListVersions: typeof _listVersions;
|
|
|
18
18
|
export declare const wasmReadVersionGraph: typeof _readVersionGraph;
|
|
19
19
|
export declare const wasmRevertToVersion: typeof _revertToVersion;
|
|
20
20
|
export declare const wasmGetCurrentSchemaVersion: typeof _getCurrentSchemaVersion;
|
|
21
|
+
export declare const wasmCreateDeltaChangeset: typeof _createDeltaChangeset;
|
|
22
|
+
export declare const wasmApplyDeltaChangeset: typeof _applyDeltaChangeset;
|
package/dist/wasm.js
CHANGED
|
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import init, { getCurrentSchemaVersion as _getCurrentSchemaVersion, getExternalFile as _getExternalFile, listExternalFiles as _listExternalFiles, listVersions as _listVersions, parseDuc as _parseDuc, parseDucLazy as _parseDucLazy, readVersionGraph as _readVersionGraph, restoreCheckpoint as _restoreCheckpoint, restoreVersion as _restoreVersion, revertToVersion as _revertToVersion, serializeDuc as _serializeDuc, } from "../dist/ducjs_wasm";
|
|
10
|
+
import init, { applyDeltaChangeset as _applyDeltaChangeset, createDeltaChangeset as _createDeltaChangeset, getCurrentSchemaVersion as _getCurrentSchemaVersion, getExternalFile as _getExternalFile, listExternalFiles as _listExternalFiles, listVersions as _listVersions, parseDuc as _parseDuc, parseDucLazy as _parseDucLazy, readVersionGraph as _readVersionGraph, restoreCheckpoint as _restoreCheckpoint, restoreVersion as _restoreVersion, revertToVersion as _revertToVersion, serializeDuc as _serializeDuc, } from "../dist/ducjs_wasm";
|
|
11
11
|
let initialized = false;
|
|
12
12
|
let initPromise = null;
|
|
13
13
|
export function ensureWasm(wasmUrl) {
|
|
@@ -50,3 +50,5 @@ export const wasmListVersions = _listVersions;
|
|
|
50
50
|
export const wasmReadVersionGraph = _readVersionGraph;
|
|
51
51
|
export const wasmRevertToVersion = _revertToVersion;
|
|
52
52
|
export const wasmGetCurrentSchemaVersion = _getCurrentSchemaVersion;
|
|
53
|
+
export const wasmCreateDeltaChangeset = _createDeltaChangeset;
|
|
54
|
+
export const wasmApplyDeltaChangeset = _applyDeltaChangeset;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ducjs",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "The duc 2D CAD file format is a cornerstone of our advanced design system, conceived to cater to professionals seeking precision and efficiency in their design work.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|