@git-stunts/git-warp 11.5.0 → 12.0.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/README.md +145 -1
- package/bin/cli/commands/registry.js +4 -0
- package/bin/cli/commands/reindex.js +41 -0
- package/bin/cli/commands/verify-index.js +59 -0
- package/bin/cli/infrastructure.js +7 -2
- package/bin/cli/schemas.js +19 -0
- package/bin/cli/types.js +2 -0
- package/index.d.ts +49 -12
- package/package.json +2 -2
- package/src/domain/WarpGraph.js +62 -2
- package/src/domain/errors/ShardIdOverflowError.js +28 -0
- package/src/domain/errors/index.js +1 -0
- package/src/domain/services/AdjacencyNeighborProvider.js +140 -0
- package/src/domain/services/BitmapIndexReader.js +32 -10
- package/src/domain/services/BitmapNeighborProvider.js +178 -0
- package/src/domain/services/CheckpointMessageCodec.js +3 -3
- package/src/domain/services/CheckpointService.js +77 -12
- package/src/domain/services/GraphTraversal.js +1239 -0
- package/src/domain/services/IncrementalIndexUpdater.js +765 -0
- package/src/domain/services/JoinReducer.js +310 -46
- package/src/domain/services/LogicalBitmapIndexBuilder.js +323 -0
- package/src/domain/services/LogicalIndexBuildService.js +108 -0
- package/src/domain/services/LogicalIndexReader.js +315 -0
- package/src/domain/services/LogicalTraversal.js +321 -202
- package/src/domain/services/MaterializedViewService.js +379 -0
- package/src/domain/services/ObserverView.js +138 -47
- package/src/domain/services/PatchBuilderV2.js +3 -3
- package/src/domain/services/PropertyIndexBuilder.js +64 -0
- package/src/domain/services/PropertyIndexReader.js +111 -0
- package/src/domain/services/SyncController.js +576 -0
- package/src/domain/services/TemporalQuery.js +128 -14
- package/src/domain/types/PatchDiff.js +90 -0
- package/src/domain/types/WarpTypesV2.js +4 -4
- package/src/domain/utils/MinHeap.js +45 -17
- package/src/domain/utils/canonicalCbor.js +36 -0
- package/src/domain/utils/fnv1a.js +20 -0
- package/src/domain/utils/roaring.js +14 -3
- package/src/domain/utils/shardKey.js +40 -0
- package/src/domain/utils/toBytes.js +17 -0
- package/src/domain/utils/validateShardOid.js +13 -0
- package/src/domain/warp/_internal.js +0 -9
- package/src/domain/warp/_wiredMethods.d.ts +8 -2
- package/src/domain/warp/checkpoint.methods.js +21 -5
- package/src/domain/warp/materialize.methods.js +17 -5
- package/src/domain/warp/materializeAdvanced.methods.js +142 -3
- package/src/domain/warp/query.methods.js +78 -12
- package/src/infrastructure/adapters/CasSeekCacheAdapter.js +26 -5
- package/src/ports/BlobPort.js +1 -1
- package/src/ports/NeighborProviderPort.js +59 -0
- package/src/ports/SeekCachePort.js +4 -3
- package/src/domain/warp/sync.methods.js +0 -554
|
@@ -9,13 +9,14 @@
|
|
|
9
9
|
* }
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { createORSet, orsetAdd, orsetRemove, orsetJoin } from '../crdt/ORSet.js';
|
|
12
|
+
import { createORSet, orsetAdd, orsetRemove, orsetJoin, orsetContains } from '../crdt/ORSet.js';
|
|
13
13
|
import { createVersionVector, vvMerge, vvClone, vvDeserialize } from '../crdt/VersionVector.js';
|
|
14
14
|
import { lwwSet, lwwMax } from '../crdt/LWW.js';
|
|
15
15
|
import { createEventId, compareEventIds } from '../utils/EventId.js';
|
|
16
16
|
import { createTickReceipt, OP_TYPES } from '../types/TickReceipt.js';
|
|
17
17
|
import { encodeDot } from '../crdt/Dot.js';
|
|
18
|
-
import { encodeEdgeKey, encodePropKey } from './KeyCodec.js';
|
|
18
|
+
import { encodeEdgeKey, decodeEdgeKey, encodePropKey } from './KeyCodec.js';
|
|
19
|
+
import { createEmptyDiff, mergeDiffs } from '../types/PatchDiff.js';
|
|
19
20
|
|
|
20
21
|
// Re-export key codec functions for backward compatibility
|
|
21
22
|
export {
|
|
@@ -342,48 +343,267 @@ function foldPatchDot(frontier, writer, lamport) {
|
|
|
342
343
|
}
|
|
343
344
|
|
|
344
345
|
/**
|
|
345
|
-
*
|
|
346
|
+
* Merges a patch's context into state and folds the patch dot.
|
|
347
|
+
* @param {WarpStateV5} state
|
|
348
|
+
* @param {Object} patch
|
|
349
|
+
* @param {string} patch.writer
|
|
350
|
+
* @param {number} patch.lamport
|
|
351
|
+
* @param {Map<string, number>|{[x: string]: number}} patch.context
|
|
352
|
+
*/
|
|
353
|
+
function updateFrontierFromPatch(state, patch) {
|
|
354
|
+
const contextVV = patch.context instanceof Map
|
|
355
|
+
? patch.context
|
|
356
|
+
: vvDeserialize(patch.context || {});
|
|
357
|
+
state.observedFrontier = vvMerge(state.observedFrontier, contextVV);
|
|
358
|
+
foldPatchDot(state.observedFrontier, patch.writer, patch.lamport);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Applies a patch to state without receipt collection (zero overhead).
|
|
346
363
|
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
364
|
+
* @param {WarpStateV5} state - The state to mutate in place
|
|
365
|
+
* @param {Object} patch - The patch to apply
|
|
366
|
+
* @param {string} patch.writer
|
|
367
|
+
* @param {number} patch.lamport
|
|
368
|
+
* @param {Array<{type: string, node?: string, dot?: import('../crdt/Dot.js').Dot, observedDots?: string[], from?: string, to?: string, label?: string, key?: string, value?: unknown, oid?: string}>} patch.ops
|
|
369
|
+
* @param {Map<string, number>|{[x: string]: number}} patch.context
|
|
370
|
+
* @param {string} patchSha - Git SHA of the patch commit
|
|
371
|
+
* @returns {WarpStateV5} The mutated state
|
|
372
|
+
*/
|
|
373
|
+
export function applyFast(state, patch, patchSha) {
|
|
374
|
+
for (let i = 0; i < patch.ops.length; i++) {
|
|
375
|
+
const eventId = createEventId(patch.lamport, patch.writer, patchSha, i);
|
|
376
|
+
applyOpV2(state, patch.ops[i], eventId);
|
|
377
|
+
}
|
|
378
|
+
updateFrontierFromPatch(state, patch);
|
|
379
|
+
return state;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Builds a reverse map from dot string → element ID for an OR-Set.
|
|
350
384
|
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
* operation (applied, redundant, or superseded) and returns a TickReceipt for
|
|
354
|
-
* provenance tracking. This has a small performance cost, so it's disabled by default.
|
|
385
|
+
* Only includes mappings for dots that appear in the given targetDots set,
|
|
386
|
+
* allowing early termination once all target dots are accounted for.
|
|
355
387
|
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
388
|
+
* @param {import('../crdt/ORSet.js').ORSet} orset
|
|
389
|
+
* @param {Set<string>} targetDots - The dots we care about
|
|
390
|
+
* @returns {Map<string, string>} dot → elementId
|
|
391
|
+
*/
|
|
392
|
+
function buildDotToElement(orset, targetDots) {
|
|
393
|
+
const dotToElement = new Map();
|
|
394
|
+
let remaining = targetDots.size;
|
|
395
|
+
for (const [element, dots] of orset.entries) {
|
|
396
|
+
if (remaining === 0) { break; }
|
|
397
|
+
for (const d of dots) {
|
|
398
|
+
if (targetDots.has(d)) {
|
|
399
|
+
dotToElement.set(d, element);
|
|
400
|
+
remaining--;
|
|
401
|
+
if (remaining === 0) { break; }
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return dotToElement;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Collects the set of alive elements that own at least one of the target dots.
|
|
358
410
|
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
* @param {
|
|
364
|
-
* @param {
|
|
365
|
-
* @
|
|
366
|
-
* @param {boolean} [collectReceipts=false] - When true, computes and returns receipt data
|
|
367
|
-
* @returns {WarpStateV5|{state: WarpStateV5, receipt: import('../types/TickReceipt.js').TickReceipt}}
|
|
368
|
-
* Returns mutated state directly when collectReceipts is false;
|
|
369
|
-
* returns {state, receipt} object when collectReceipts is true
|
|
411
|
+
* Uses a reverse-index from dot → element to avoid scanning every entry in the
|
|
412
|
+
* OR-Set. Complexity: O(total_dots_in_orset) for index build (with early exit)
|
|
413
|
+
* + O(|targetDots|) for lookups, vs the previous O(N * |targetDots|) full scan.
|
|
414
|
+
*
|
|
415
|
+
* @param {import('../crdt/ORSet.js').ORSet} orset
|
|
416
|
+
* @param {Set<string>} observedDots
|
|
417
|
+
* @returns {Set<string>} element IDs that were alive and own at least one observed dot
|
|
370
418
|
*/
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
419
|
+
function aliveElementsForDots(orset, observedDots) {
|
|
420
|
+
const result = new Set();
|
|
421
|
+
const dotToElement = buildDotToElement(orset, observedDots);
|
|
422
|
+
for (const d of observedDots) {
|
|
423
|
+
const element = dotToElement.get(d);
|
|
424
|
+
if (element !== undefined && !result.has(element) && orsetContains(orset, element)) {
|
|
425
|
+
result.add(element);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return result;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* @typedef {Object} SnapshotBeforeOp
|
|
433
|
+
* @property {boolean} [nodeWasAlive]
|
|
434
|
+
* @property {boolean} [edgeWasAlive]
|
|
435
|
+
* @property {string} [edgeKey]
|
|
436
|
+
* @property {unknown} [prevPropValue]
|
|
437
|
+
* @property {string} [propKey]
|
|
438
|
+
* @property {Set<string>} [aliveBeforeNodes]
|
|
439
|
+
* @property {Set<string>} [aliveBeforeEdges]
|
|
440
|
+
*/
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Snapshots alive-ness of a node or edge before an op is applied.
|
|
444
|
+
*
|
|
445
|
+
* @param {WarpStateV5} state
|
|
446
|
+
* @param {import('../types/WarpTypesV2.js').OpV2} op
|
|
447
|
+
* @returns {SnapshotBeforeOp}
|
|
448
|
+
*/
|
|
449
|
+
function snapshotBeforeOp(state, op) {
|
|
450
|
+
switch (op.type) {
|
|
451
|
+
case 'NodeAdd':
|
|
452
|
+
return { nodeWasAlive: orsetContains(state.nodeAlive, op.node) };
|
|
453
|
+
case 'NodeRemove': {
|
|
454
|
+
const rawDots = /** @type {Iterable<string>} */ (op.observedDots);
|
|
455
|
+
/** @type {Set<string>} */
|
|
456
|
+
const nodeDots = rawDots instanceof Set ? rawDots : new Set(rawDots);
|
|
457
|
+
const aliveBeforeNodes = aliveElementsForDots(state.nodeAlive, nodeDots);
|
|
458
|
+
return { aliveBeforeNodes };
|
|
459
|
+
}
|
|
460
|
+
case 'EdgeAdd': {
|
|
461
|
+
const ek = encodeEdgeKey(op.from, op.to, op.label);
|
|
462
|
+
return { edgeWasAlive: orsetContains(state.edgeAlive, ek), edgeKey: ek };
|
|
463
|
+
}
|
|
464
|
+
case 'EdgeRemove': {
|
|
465
|
+
const rawEdgeDots = /** @type {Iterable<string>} */ (op.observedDots);
|
|
466
|
+
/** @type {Set<string>} */
|
|
467
|
+
const edgeDots = rawEdgeDots instanceof Set ? rawEdgeDots : new Set(rawEdgeDots);
|
|
468
|
+
const aliveBeforeEdges = aliveElementsForDots(state.edgeAlive, edgeDots);
|
|
469
|
+
return { aliveBeforeEdges };
|
|
470
|
+
}
|
|
471
|
+
case 'PropSet': {
|
|
472
|
+
const pk = encodePropKey(op.node, op.key);
|
|
473
|
+
const reg = state.prop.get(pk);
|
|
474
|
+
return { prevPropValue: reg ? reg.value : undefined, propKey: pk };
|
|
475
|
+
}
|
|
476
|
+
default:
|
|
477
|
+
return {};
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Computes diff entries by comparing pre/post alive-ness after an op.
|
|
483
|
+
*
|
|
484
|
+
* @param {import('../types/PatchDiff.js').PatchDiff} diff
|
|
485
|
+
* @param {WarpStateV5} state
|
|
486
|
+
* @param {import('../types/WarpTypesV2.js').OpV2} op
|
|
487
|
+
* @param {SnapshotBeforeOp} before
|
|
488
|
+
*/
|
|
489
|
+
function accumulateOpDiff(diff, state, op, before) {
|
|
490
|
+
switch (op.type) {
|
|
491
|
+
case 'NodeAdd': {
|
|
492
|
+
if (!before.nodeWasAlive && orsetContains(state.nodeAlive, op.node)) {
|
|
493
|
+
diff.nodesAdded.push(op.node);
|
|
494
|
+
}
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
497
|
+
case 'NodeRemove': {
|
|
498
|
+
collectNodeRemovals(diff, state, before);
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
case 'EdgeAdd': {
|
|
502
|
+
if (!before.edgeWasAlive && orsetContains(state.edgeAlive, /** @type {string} */ (before.edgeKey))) {
|
|
503
|
+
const { from, to, label } = op;
|
|
504
|
+
diff.edgesAdded.push({ from, to, label });
|
|
505
|
+
}
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
case 'EdgeRemove': {
|
|
509
|
+
collectEdgeRemovals(diff, state, before);
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
case 'PropSet': {
|
|
513
|
+
const reg = state.prop.get(/** @type {string} */ (before.propKey));
|
|
514
|
+
const newVal = reg ? reg.value : undefined;
|
|
515
|
+
if (newVal !== before.prevPropValue) {
|
|
516
|
+
diff.propsChanged.push({
|
|
517
|
+
nodeId: op.node,
|
|
518
|
+
key: op.key,
|
|
519
|
+
value: newVal,
|
|
520
|
+
prevValue: before.prevPropValue,
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
default:
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Records removal only for elements that were alive before AND dead after.
|
|
532
|
+
*
|
|
533
|
+
* @param {import('../types/PatchDiff.js').PatchDiff} diff
|
|
534
|
+
* @param {WarpStateV5} state
|
|
535
|
+
* @param {SnapshotBeforeOp} before
|
|
536
|
+
*/
|
|
537
|
+
function collectNodeRemovals(diff, state, before) {
|
|
538
|
+
if (!before.aliveBeforeNodes) { return; }
|
|
539
|
+
for (const element of before.aliveBeforeNodes) {
|
|
540
|
+
if (!orsetContains(state.nodeAlive, element)) {
|
|
541
|
+
diff.nodesRemoved.push(element);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Records removal only for edges that were alive before AND dead after.
|
|
548
|
+
*
|
|
549
|
+
* @param {import('../types/PatchDiff.js').PatchDiff} diff
|
|
550
|
+
* @param {WarpStateV5} state
|
|
551
|
+
* @param {SnapshotBeforeOp} before
|
|
552
|
+
*/
|
|
553
|
+
function collectEdgeRemovals(diff, state, before) {
|
|
554
|
+
if (!before.aliveBeforeEdges) { return; }
|
|
555
|
+
for (const edgeKey of before.aliveBeforeEdges) {
|
|
556
|
+
if (!orsetContains(state.edgeAlive, edgeKey)) {
|
|
557
|
+
diff.edgesRemoved.push(decodeEdgeKey(edgeKey));
|
|
377
558
|
}
|
|
378
|
-
const contextVV = patch.context instanceof Map
|
|
379
|
-
? patch.context
|
|
380
|
-
: vvDeserialize(patch.context);
|
|
381
|
-
state.observedFrontier = vvMerge(state.observedFrontier, contextVV);
|
|
382
|
-
foldPatchDot(state.observedFrontier, patch.writer, patch.lamport);
|
|
383
|
-
return state;
|
|
384
559
|
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Applies a patch to state with diff tracking for incremental index updates.
|
|
564
|
+
*
|
|
565
|
+
* Captures alive-ness transitions: only records a diff entry when the
|
|
566
|
+
* alive-ness of a node/edge actually changes, or when an LWW property
|
|
567
|
+
* winner changes. Redundant ops produce no diff entries.
|
|
568
|
+
*
|
|
569
|
+
* @param {WarpStateV5} state - The state to mutate in place
|
|
570
|
+
* @param {Object} patch - The patch to apply
|
|
571
|
+
* @param {string} patch.writer
|
|
572
|
+
* @param {number} patch.lamport
|
|
573
|
+
* @param {Array<Object>} patch.ops
|
|
574
|
+
* @param {Map<string, number>|{[x: string]: number}} patch.context
|
|
575
|
+
* @param {string} patchSha - Git SHA of the patch commit
|
|
576
|
+
* @returns {{state: WarpStateV5, diff: import('../types/PatchDiff.js').PatchDiff}}
|
|
577
|
+
*/
|
|
578
|
+
export function applyWithDiff(state, patch, patchSha) {
|
|
579
|
+
const diff = createEmptyDiff();
|
|
580
|
+
|
|
581
|
+
for (let i = 0; i < patch.ops.length; i++) {
|
|
582
|
+
const op = patch.ops[i];
|
|
583
|
+
const eventId = createEventId(patch.lamport, patch.writer, patchSha, i);
|
|
584
|
+
const typedOp = /** @type {import('../types/WarpTypesV2.js').OpV2} */ (op);
|
|
585
|
+
const before = snapshotBeforeOp(state, typedOp);
|
|
586
|
+
applyOpV2(state, typedOp, eventId);
|
|
587
|
+
accumulateOpDiff(diff, state, typedOp, before);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
updateFrontierFromPatch(state, patch);
|
|
591
|
+
return { state, diff };
|
|
592
|
+
}
|
|
385
593
|
|
|
386
|
-
|
|
594
|
+
/**
|
|
595
|
+
* Applies a patch to state with receipt collection for provenance tracking.
|
|
596
|
+
*
|
|
597
|
+
* @param {WarpStateV5} state - The state to mutate in place
|
|
598
|
+
* @param {Object} patch - The patch to apply
|
|
599
|
+
* @param {string} patch.writer
|
|
600
|
+
* @param {number} patch.lamport
|
|
601
|
+
* @param {Array<{type: string, node?: string, dot?: import('../crdt/Dot.js').Dot, observedDots?: string[], from?: string, to?: string, label?: string, key?: string, value?: unknown, oid?: string}>} patch.ops
|
|
602
|
+
* @param {Map<string, number>|{[x: string]: number}} patch.context
|
|
603
|
+
* @param {string} patchSha - Git SHA of the patch commit
|
|
604
|
+
* @returns {{state: WarpStateV5, receipt: import('../types/TickReceipt.js').TickReceipt}}
|
|
605
|
+
*/
|
|
606
|
+
export function applyWithReceipt(state, patch, patchSha) {
|
|
387
607
|
/** @type {import('../types/TickReceipt.js').OpOutcome[]} */
|
|
388
608
|
const opResults = [];
|
|
389
609
|
for (let i = 0; i < patch.ops.length; i++) {
|
|
@@ -433,11 +653,7 @@ export function join(state, patch, patchSha, collectReceipts) {
|
|
|
433
653
|
opResults.push(entry);
|
|
434
654
|
}
|
|
435
655
|
|
|
436
|
-
|
|
437
|
-
? patch.context
|
|
438
|
-
: vvDeserialize(patch.context);
|
|
439
|
-
state.observedFrontier = vvMerge(state.observedFrontier, contextVV);
|
|
440
|
-
foldPatchDot(state.observedFrontier, patch.writer, patch.lamport);
|
|
656
|
+
updateFrontierFromPatch(state, patch);
|
|
441
657
|
|
|
442
658
|
const receipt = createTickReceipt({
|
|
443
659
|
patchSha,
|
|
@@ -449,6 +665,39 @@ export function join(state, patch, patchSha, collectReceipts) {
|
|
|
449
665
|
return { state, receipt };
|
|
450
666
|
}
|
|
451
667
|
|
|
668
|
+
/**
|
|
669
|
+
* Joins a patch into state, applying all operations in order.
|
|
670
|
+
*
|
|
671
|
+
* This is the primary function for incorporating a single patch into WARP state.
|
|
672
|
+
* It iterates through all operations in the patch, creates EventIds for causality
|
|
673
|
+
* tracking, and applies each operation using `applyOpV2`.
|
|
674
|
+
*
|
|
675
|
+
* **Receipt Collection Mode**:
|
|
676
|
+
* When `collectReceipts` is true, this function also computes the outcome of each
|
|
677
|
+
* operation (applied, redundant, or superseded) and returns a TickReceipt for
|
|
678
|
+
* provenance tracking. This has a small performance cost, so it's disabled by default.
|
|
679
|
+
*
|
|
680
|
+
* **Warning**: This function mutates `state` in place. For immutable operations,
|
|
681
|
+
* clone the state first using `cloneStateV5()`.
|
|
682
|
+
*
|
|
683
|
+
* @param {WarpStateV5} state - The state to mutate. Modified in place.
|
|
684
|
+
* @param {Object} patch - The patch to apply
|
|
685
|
+
* @param {string} patch.writer - Writer ID who created this patch
|
|
686
|
+
* @param {number} patch.lamport - Lamport timestamp of this patch
|
|
687
|
+
* @param {Array<{type: string, node?: string, dot?: import('../crdt/Dot.js').Dot, observedDots?: string[], from?: string, to?: string, label?: string, key?: string, value?: unknown, oid?: string}>} patch.ops - Array of operations to apply
|
|
688
|
+
* @param {Map<string, number>|{[x: string]: number}} patch.context - Version vector context (Map or serialized form)
|
|
689
|
+
* @param {string} patchSha - The Git SHA of the patch commit (used for EventId creation)
|
|
690
|
+
* @param {boolean} [collectReceipts=false] - When true, computes and returns receipt data
|
|
691
|
+
* @returns {WarpStateV5|{state: WarpStateV5, receipt: import('../types/TickReceipt.js').TickReceipt}}
|
|
692
|
+
* Returns mutated state directly when collectReceipts is false;
|
|
693
|
+
* returns {state, receipt} object when collectReceipts is true
|
|
694
|
+
*/
|
|
695
|
+
export function join(state, patch, patchSha, collectReceipts) {
|
|
696
|
+
return collectReceipts
|
|
697
|
+
? applyWithReceipt(state, patch, patchSha)
|
|
698
|
+
: applyFast(state, patch, patchSha);
|
|
699
|
+
}
|
|
700
|
+
|
|
452
701
|
/**
|
|
453
702
|
* Joins two V5 states together using CRDT merge semantics.
|
|
454
703
|
*
|
|
@@ -549,9 +798,15 @@ function mergeEdgeBirthEvent(a, b) {
|
|
|
549
798
|
* @param {WarpStateV5} [initialState] - Optional starting state (for incremental materialization from checkpoint)
|
|
550
799
|
* @param {Object} [options] - Optional configuration
|
|
551
800
|
* @param {boolean} [options.receipts=false] - When true, collect and return TickReceipts
|
|
552
|
-
* @
|
|
553
|
-
*
|
|
554
|
-
*
|
|
801
|
+
* @param {boolean} [options.trackDiff=false] - When true, collect and return PatchDiff
|
|
802
|
+
* @returns {WarpStateV5|{state: WarpStateV5, receipts: import('../types/TickReceipt.js').TickReceipt[]}|{state: WarpStateV5, diff: import('../types/PatchDiff.js').PatchDiff}}
|
|
803
|
+
* Returns state directly when no options;
|
|
804
|
+
* returns {state, receipts} when receipts is true;
|
|
805
|
+
* returns {state, diff} when trackDiff is true
|
|
806
|
+
*
|
|
807
|
+
* @note When initialState is provided, the returned diff records transitions
|
|
808
|
+
* relative to that state. The caller must ensure any index tree passed to
|
|
809
|
+
* IncrementalIndexUpdater was built from the same initialState.
|
|
555
810
|
*/
|
|
556
811
|
export function reduceV5(patches, initialState, options) {
|
|
557
812
|
const state = initialState ? cloneStateV5(initialState) : createEmptyStateV5();
|
|
@@ -560,14 +815,23 @@ export function reduceV5(patches, initialState, options) {
|
|
|
560
815
|
if (options && options.receipts) {
|
|
561
816
|
const receipts = [];
|
|
562
817
|
for (const { patch, sha } of patches) {
|
|
563
|
-
const result =
|
|
818
|
+
const result = applyWithReceipt(state, patch, sha);
|
|
564
819
|
receipts.push(result.receipt);
|
|
565
820
|
}
|
|
566
821
|
return { state, receipts };
|
|
567
822
|
}
|
|
568
823
|
|
|
824
|
+
if (options && options.trackDiff) {
|
|
825
|
+
let merged = createEmptyDiff();
|
|
826
|
+
for (const { patch, sha } of patches) {
|
|
827
|
+
const { diff } = applyWithDiff(state, patch, sha);
|
|
828
|
+
merged = mergeDiffs(merged, diff);
|
|
829
|
+
}
|
|
830
|
+
return { state, diff: merged };
|
|
831
|
+
}
|
|
832
|
+
|
|
569
833
|
for (const { patch, sha } of patches) {
|
|
570
|
-
|
|
834
|
+
applyFast(state, patch, sha);
|
|
571
835
|
}
|
|
572
836
|
return state;
|
|
573
837
|
}
|