ducjs 3.0.4 → 3.0.5

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.
@@ -1,28 +1,30 @@
1
1
  /* tslint:disable */
2
2
  /* eslint-disable */
3
3
  /**
4
- * Parse a `.duc` file (Uint8Array) into a JS object (ExportedDataState).
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 parseDuc(buf: Uint8Array): any;
8
+ export function getExternalFile(buf: Uint8Array, file_id: string): any;
7
9
  /**
8
- * Read the full VersionGraph from a `.duc` file buffer.
10
+ * Restore a specific checkpoint by its ID from a `.duc` file buffer.
9
11
  *
10
- * Returns a JS object matching the `VersionGraph` TypeScript interface,
11
- * or `undefined` if no version graph exists.
12
+ * Returns a JS object `{ versionNumber, schemaVersion, data, fromCheckpoint }`.
12
13
  */
13
- export function readVersionGraph(duc_buf: Uint8Array): any;
14
+ export function restoreCheckpoint(duc_buf: Uint8Array, checkpoint_id: string): any;
14
15
  /**
15
- * Fetch a single external file from a `.duc` buffer by file ID.
16
+ * List all versions (checkpoints + deltas) from a `.duc` file buffer.
16
17
  *
17
- * Returns the file entry as a JS object, or `undefined` if not found.
18
+ * Returns a JS array of `VersionEntry` objects (no heavy data blobs).
18
19
  */
19
- export function getExternalFile(buf: Uint8Array, file_id: string): any;
20
+ export function listVersions(duc_buf: Uint8Array): any;
20
21
  /**
21
- * Revert the document to a specific version, removing all newer versions.
22
+ * Read the full VersionGraph from a `.duc` file buffer.
22
23
  *
23
- * Returns a JS object `{ versionNumber, schemaVersion, data, fromCheckpoint }`.
24
+ * Returns a JS object matching the `VersionGraph` TypeScript interface,
25
+ * or `undefined` if no version graph exists.
24
26
  */
25
- export function revertToVersion(duc_buf: Uint8Array, target_version: number): any;
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
- * Restore a specific checkpoint by its ID from a `.duc` file buffer.
71
+ * Compute a checkpoint-relative binary diff changeset using bsdiff.
56
72
  *
57
- * Returns a JS object `{ versionNumber, schemaVersion, data, fromCheckpoint }`.
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 restoreCheckpoint(duc_buf: Uint8Array, checkpoint_id: string): any;
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];
@@ -195,14 +195,19 @@ function takeFromExternrefTable0(idx) {
195
195
  return value;
196
196
  }
197
197
  /**
198
- * Parse a `.duc` file (Uint8Array) into a JS object (ExportedDataState).
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 parseDuc(buf) {
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 ret = wasm.parseDuc(ptr0, len0);
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
- * Read the full VersionGraph from a `.duc` file buffer.
218
+ * Restore a specific checkpoint by its ID from a `.duc` file buffer.
214
219
  *
215
- * Returns a JS object matching the `VersionGraph` TypeScript interface,
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 readVersionGraph(duc_buf) {
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 ret = wasm.readVersionGraph(ptr0, len0);
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
- * Fetch a single external file from a `.duc` buffer by file ID.
238
+ * List all versions (checkpoints + deltas) from a `.duc` file buffer.
232
239
  *
233
- * Returns the file entry as a JS object, or `undefined` if not found.
234
- * @param {Uint8Array} buf
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 getExternalFile(buf, file_id) {
239
- const ptr0 = passArray8ToWasm0(buf, wasm.__wbindgen_malloc);
244
+ export function listVersions(duc_buf) {
245
+ const ptr0 = passArray8ToWasm0(duc_buf, wasm.__wbindgen_malloc);
240
246
  const len0 = WASM_VECTOR_LEN;
241
- const ptr1 = passStringToWasm0(file_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
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
- * Revert the document to a specific version, removing all newer versions.
255
+ * Read the full VersionGraph from a `.duc` file buffer.
252
256
  *
253
- * Returns a JS object `{ versionNumber, schemaVersion, data, fromCheckpoint }`.
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 revertToVersion(duc_buf, target_version) {
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.revertToVersion(ptr0, len0, target_version);
265
+ const ret = wasm.readVersionGraph(ptr0, len0);
262
266
  if (ret[2]) {
263
267
  throw takeFromExternrefTable0(ret[1]);
264
268
  }
@@ -279,16 +283,20 @@ export function getCurrentSchemaVersion() {
279
283
  }
280
284
 
281
285
  /**
282
- * List all versions (checkpoints + deltas) from a `.duc` file buffer.
286
+ * Restore the document state at `version_number` from a `.duc` file buffer.
283
287
  *
284
- * Returns a JS array of `VersionEntry` objects (no heavy data blobs).
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 listVersions(duc_buf) {
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.listVersions(ptr0, len0);
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
- * Restore the document state at `version_number` from a `.duc` file buffer.
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} version_number
311
+ * @param {number} target_version
307
312
  * @returns {any}
308
313
  */
309
- export function restoreVersion(duc_buf, version_number) {
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.restoreVersion(ptr0, len0, version_number);
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
- * Restore a specific checkpoint by its ID from a `.duc` file buffer.
381
+ * Compute a checkpoint-relative binary diff changeset using bsdiff.
336
382
  *
337
- * Returns a JS object `{ versionNumber, schemaVersion, data, fromCheckpoint }`.
338
- * @param {Uint8Array} duc_buf
339
- * @param {string} checkpoint_id
340
- * @returns {any}
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 restoreCheckpoint(duc_buf, checkpoint_id) {
343
- const ptr0 = passArray8ToWasm0(duc_buf, wasm.__wbindgen_malloc);
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 = passStringToWasm0(checkpoint_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
396
+ const ptr1 = passArray8ToWasm0(current_state, wasm.__wbindgen_malloc);
346
397
  const len1 = WASM_VECTOR_LEN;
347
- const ret = wasm.restoreCheckpoint(ptr0, len0, ptr1, len1);
348
- if (ret[2]) {
349
- throw takeFromExternrefTable0(ret[1]);
398
+ const ret = wasm.createDeltaChangeset(ptr0, len0, ptr1, len1);
399
+ if (ret[3]) {
400
+ throw takeFromExternrefTable0(ret[2]);
350
401
  }
351
- return takeFromExternrefTable0(ret[0]);
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 lazily returns everything EXCEPT external file data blobs.
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 parseDucLazy(buf) {
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.parseDucLazy(ptr0, len0);
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
- * List metadata for all external files (without loading the heavy data blobs).
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 listExternalFiles(buf) {
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.listExternalFiles(ptr0, len0);
432
+ const ret = wasm.parseDucLazy(ptr0, len0);
380
433
  if (ret[2]) {
381
434
  throw takeFromExternrefTable0(ret[1]);
382
435
  }
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/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
- return restore(data, elementsConfig !== null && elementsConfig !== void 0 ? elementsConfig : { syncInvalidIndices: (els) => els }, restoreConfig);
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 }), elementsConfig !== null && elementsConfig !== void 0 ? elementsConfig : { syncInvalidIndices: (els) => els }, restoreConfig);
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
  }
@@ -367,11 +367,6 @@ export const restoreVersionGraph = (importedGraph) => {
367
367
  if (!importedGraph || typeof importedGraph !== "object") {
368
368
  return undefined;
369
369
  }
370
- const userCheckpointVersionId = isValidString(importedGraph.userCheckpointVersionId);
371
- const latestVersionId = isValidString(importedGraph.latestVersionId);
372
- if (!userCheckpointVersionId || !latestVersionId) {
373
- return undefined;
374
- }
375
370
  const checkpoints = Array.isArray(importedGraph.checkpoints)
376
371
  ? importedGraph.checkpoints
377
372
  .map((checkpoint) => restoreCheckpoint(checkpoint))
@@ -382,6 +377,17 @@ export const restoreVersionGraph = (importedGraph) => {
382
377
  .map((delta) => restoreDelta(delta))
383
378
  .filter((delta) => Boolean(delta))
384
379
  : [];
380
+ if (checkpoints.length === 0 && deltas.length === 0) {
381
+ return undefined;
382
+ }
383
+ // Head IDs can legitimately be empty strings (e.g. after Rust roundtrip
384
+ // where the DB stores NULL → unwrap_or_default → ""). Accept any string.
385
+ const userCheckpointVersionId = typeof importedGraph.userCheckpointVersionId === "string"
386
+ ? importedGraph.userCheckpointVersionId
387
+ : "";
388
+ const latestVersionId = typeof importedGraph.latestVersionId === "string"
389
+ ? importedGraph.latestVersionId
390
+ : "";
385
391
  const importedMetadata = importedGraph.metadata;
386
392
  const metadata = {
387
393
  currentVersion: isFiniteNumber(importedMetadata === null || importedMetadata === void 0 ? void 0 : importedMetadata.currentVersion) && importedMetadata.currentVersion >= 0
@@ -411,6 +417,7 @@ export const restoreVersionGraph = (importedGraph) => {
411
417
  };
412
418
  };
413
419
  export const restoreCheckpoint = (importedCheckpoint) => {
420
+ var _a, _b, _c;
414
421
  if (!importedCheckpoint || typeof importedCheckpoint !== "object") {
415
422
  return undefined;
416
423
  }
@@ -419,10 +426,11 @@ export const restoreCheckpoint = (importedCheckpoint) => {
419
426
  return undefined;
420
427
  }
421
428
  const id = isValidString(checkpoint.id);
422
- const data = isValidUint8Array(checkpoint.data);
423
- if (!id || !data) {
429
+ if (!id) {
424
430
  return undefined;
425
431
  }
432
+ // Accept empty Uint8Array — shell/remote entries have byteLength === 0
433
+ 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
434
  return {
427
435
  type: "checkpoint",
428
436
  id,
@@ -443,6 +451,7 @@ export const restoreCheckpoint = (importedCheckpoint) => {
443
451
  };
444
452
  };
445
453
  export const restoreDelta = (importedDelta) => {
454
+ var _a, _b, _c;
446
455
  if (!importedDelta || typeof importedDelta !== "object") {
447
456
  return undefined;
448
457
  }
@@ -451,11 +460,13 @@ export const restoreDelta = (importedDelta) => {
451
460
  return undefined;
452
461
  }
453
462
  const id = isValidString(delta.id);
454
- const payload = isValidUint8Array(delta.payload);
455
- const baseCheckpointId = isValidString(delta.baseCheckpointId);
456
- if (!id || !payload || !baseCheckpointId) {
463
+ if (!id) {
457
464
  return undefined;
458
465
  }
466
+ // Accept empty Uint8Array — shell/remote entries have byteLength === 0
467
+ 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);
468
+ // baseCheckpointId can be empty for shell entries or first-in-chain deltas
469
+ const baseCheckpointId = typeof delta.baseCheckpointId === "string" ? delta.baseCheckpointId : "";
459
470
  return {
460
471
  type: "delta",
461
472
  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 restored = restore(data, elementsConfig !== null && elementsConfig !== void 0 ? elementsConfig : { syncInvalidIndices: (els) => els }, restoreConfig);
37
- const shouldDropLegacyVersionGraph = hasLegacyVersionGraphShape(data === null || data === void 0 ? void 0 : data.versionGraph);
38
- 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: shouldDropLegacyVersionGraph
39
- ? undefined
40
- : restored.versionGraph });
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
- return wasmSerializeDuc(prepared);
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;
@@ -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>;
@@ -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.4",
3
+ "version": "3.0.5",
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",