@git-stunts/git-warp 12.2.1 → 12.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/bin/cli/commands/info.js +1 -5
- package/bin/cli/infrastructure.js +6 -9
- package/bin/cli/shared.js +8 -0
- package/bin/presenters/text.js +10 -3
- package/bin/warp-graph.js +6 -6
- package/package.json +1 -1
- package/src/domain/WarpGraph.js +5 -35
- package/src/domain/crdt/ORSet.js +3 -0
- package/src/domain/crdt/VersionVector.js +1 -1
- package/src/domain/entities/GraphNode.js +1 -6
- package/src/domain/errors/ForkError.js +1 -1
- package/src/domain/errors/IndexError.js +1 -1
- package/src/domain/errors/OperationAbortedError.js +1 -1
- package/src/domain/errors/PatchError.js +1 -1
- package/src/domain/errors/PersistenceError.js +45 -0
- package/src/domain/errors/QueryError.js +1 -1
- package/src/domain/errors/SchemaUnsupportedError.js +1 -1
- package/src/domain/errors/SyncError.js +1 -1
- package/src/domain/errors/TraversalError.js +1 -1
- package/src/domain/errors/TrustError.js +1 -1
- package/src/domain/errors/WormholeError.js +1 -1
- package/src/domain/errors/index.js +1 -0
- package/src/domain/services/AdjacencyNeighborProvider.js +1 -4
- package/src/domain/services/AnchorMessageCodec.js +1 -3
- package/src/domain/services/AuditMessageCodec.js +1 -5
- package/src/domain/services/AuditReceiptService.js +4 -18
- package/src/domain/services/AuditVerifierService.js +3 -7
- package/src/domain/services/BitmapIndexBuilder.js +6 -12
- package/src/domain/services/BitmapIndexReader.js +7 -20
- package/src/domain/services/BitmapNeighborProvider.js +1 -3
- package/src/domain/services/BoundaryTransitionRecord.js +7 -23
- package/src/domain/services/CheckpointMessageCodec.js +6 -6
- package/src/domain/services/CheckpointSerializerV5.js +8 -12
- package/src/domain/services/CheckpointService.js +28 -40
- package/src/domain/services/CommitDagTraversalService.js +1 -3
- package/src/domain/services/DagPathFinding.js +9 -59
- package/src/domain/services/DagTopology.js +4 -16
- package/src/domain/services/DagTraversal.js +7 -31
- package/src/domain/services/Frontier.js +4 -6
- package/src/domain/services/GitLogParser.js +1 -2
- package/src/domain/services/GraphTraversal.js +14 -114
- package/src/domain/services/HealthCheckService.js +3 -9
- package/src/domain/services/HookInstaller.js +2 -8
- package/src/domain/services/HttpSyncServer.js +24 -25
- package/src/domain/services/IncrementalIndexUpdater.js +4 -6
- package/src/domain/services/IndexRebuildService.js +6 -52
- package/src/domain/services/IndexStalenessChecker.js +2 -3
- package/src/domain/services/JoinReducer.js +200 -100
- package/src/domain/services/KeyCodec.js +48 -0
- package/src/domain/services/LogicalBitmapIndexBuilder.js +1 -2
- package/src/domain/services/LogicalIndexBuildService.js +2 -6
- package/src/domain/services/LogicalIndexReader.js +1 -2
- package/src/domain/services/LogicalTraversal.js +13 -64
- package/src/domain/services/MaterializedViewService.js +5 -19
- package/src/domain/services/MessageSchemaDetector.js +35 -5
- package/src/domain/services/MigrationService.js +1 -4
- package/src/domain/services/ObserverView.js +1 -7
- package/src/domain/services/OpNormalizer.js +79 -0
- package/src/domain/services/PatchBuilderV2.js +67 -38
- package/src/domain/services/PatchMessageCodec.js +1 -6
- package/src/domain/services/PropertyIndexBuilder.js +1 -2
- package/src/domain/services/PropertyIndexReader.js +1 -4
- package/src/domain/services/ProvenanceIndex.js +5 -7
- package/src/domain/services/ProvenancePayload.js +1 -1
- package/src/domain/services/QueryBuilder.js +3 -16
- package/src/domain/services/StateDiff.js +3 -9
- package/src/domain/services/StateSerializerV5.js +10 -10
- package/src/domain/services/StreamingBitmapIndexBuilder.js +13 -41
- package/src/domain/services/SyncAuthService.js +8 -32
- package/src/domain/services/SyncController.js +5 -25
- package/src/domain/services/SyncProtocol.js +10 -13
- package/src/domain/services/SyncTrustGate.js +4 -9
- package/src/domain/services/TemporalQuery.js +9 -27
- package/src/domain/services/TranslationCost.js +2 -8
- package/src/domain/services/WarpMessageCodec.js +2 -0
- package/src/domain/services/WarpStateIndexBuilder.js +2 -4
- package/src/domain/services/WormholeService.js +9 -25
- package/src/domain/trust/TrustCrypto.js +9 -10
- package/src/domain/trust/TrustEvaluator.js +1 -8
- package/src/domain/trust/TrustRecordService.js +5 -10
- package/src/domain/types/TickReceipt.js +9 -11
- package/src/domain/types/WarpTypes.js +1 -5
- package/src/domain/types/WarpTypesV2.js +78 -13
- package/src/domain/utils/CachedValue.js +1 -4
- package/src/domain/utils/MinHeap.js +3 -3
- package/src/domain/utils/RefLayout.js +26 -0
- package/src/domain/utils/WriterId.js +2 -7
- package/src/domain/utils/canonicalCbor.js +1 -1
- package/src/domain/utils/defaultClock.js +1 -0
- package/src/domain/utils/defaultCodec.js +1 -1
- package/src/domain/utils/parseCursorBlob.js +4 -4
- package/src/domain/warp/PatchSession.js +3 -8
- package/src/domain/warp/Writer.js +9 -12
- package/src/domain/warp/_wire.js +2 -2
- package/src/domain/warp/_wiredMethods.d.ts +5 -7
- package/src/domain/warp/checkpoint.methods.js +1 -1
- package/src/domain/warp/fork.methods.js +2 -6
- package/src/domain/warp/materializeAdvanced.methods.js +3 -3
- package/src/domain/warp/patch.methods.js +8 -8
- package/src/domain/warp/provenance.methods.js +5 -5
- package/src/domain/warp/query.methods.js +9 -18
- package/src/domain/warp/subscribe.methods.js +2 -8
- package/src/globals.d.ts +7 -0
- package/src/infrastructure/adapters/BunHttpAdapter.js +14 -18
- package/src/infrastructure/adapters/ConsoleLogger.js +2 -9
- package/src/infrastructure/adapters/DenoHttpAdapter.js +15 -15
- package/src/infrastructure/adapters/GitGraphAdapter.js +234 -58
- package/src/infrastructure/adapters/InMemoryGraphAdapter.js +9 -2
- package/src/infrastructure/adapters/NodeHttpAdapter.js +14 -14
- package/src/infrastructure/adapters/WebCryptoAdapter.js +1 -2
- package/src/ports/BlobPort.js +2 -2
- package/src/ports/HttpServerPort.js +24 -2
- package/src/ports/RefPort.js +2 -1
- package/src/visualization/renderers/ascii/box.js +1 -1
- package/src/visualization/renderers/ascii/check.js +1 -5
- package/src/visualization/renderers/ascii/history.js +1 -6
- package/src/visualization/renderers/ascii/path.js +4 -22
- package/src/visualization/renderers/ascii/progress.js +1 -4
- package/src/visualization/renderers/ascii/seek.js +1 -5
- package/src/visualization/renderers/ascii/table.js +1 -3
|
@@ -15,7 +15,8 @@ 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, decodeEdgeKey, encodePropKey } from './KeyCodec.js';
|
|
18
|
+
import { encodeEdgeKey, decodeEdgeKey, encodePropKey, encodeEdgePropKey, EDGE_PROP_PREFIX } from './KeyCodec.js';
|
|
19
|
+
import { normalizeRawOp } from './OpNormalizer.js';
|
|
19
20
|
import { createEmptyDiff, mergeDiffs } from '../types/PatchDiff.js';
|
|
20
21
|
import PatchError from '../errors/PatchError.js';
|
|
21
22
|
|
|
@@ -27,6 +28,9 @@ export {
|
|
|
27
28
|
encodeEdgePropKey, isEdgePropKey, decodeEdgePropKey,
|
|
28
29
|
} from './KeyCodec.js';
|
|
29
30
|
|
|
31
|
+
// Re-export op normalization for consumers that operate on raw patches
|
|
32
|
+
export { normalizeRawOp, lowerCanonicalOp } from './OpNormalizer.js';
|
|
33
|
+
|
|
30
34
|
/**
|
|
31
35
|
* @typedef {Object} WarpStateV5
|
|
32
36
|
* @property {import('../crdt/ORSet.js').ORSet} nodeAlive - ORSet of alive nodes
|
|
@@ -40,6 +44,28 @@ export {
|
|
|
40
44
|
* always produces an empty Map for them.
|
|
41
45
|
*/
|
|
42
46
|
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {Object} OpLike
|
|
49
|
+
* @property {string} type - Operation type discriminator
|
|
50
|
+
* @property {string} [node] - Node ID (for NodeAdd, NodeRemove, PropSet)
|
|
51
|
+
* @property {import('../crdt/Dot.js').Dot} [dot] - Dot identifier (for NodeAdd, EdgeAdd)
|
|
52
|
+
* @property {string[]} [observedDots] - Encoded dots to remove (for NodeRemove, EdgeRemove)
|
|
53
|
+
* @property {string} [from] - Source node ID (for EdgeAdd, EdgeRemove)
|
|
54
|
+
* @property {string} [to] - Target node ID (for EdgeAdd, EdgeRemove)
|
|
55
|
+
* @property {string} [label] - Edge label (for EdgeAdd, EdgeRemove)
|
|
56
|
+
* @property {string} [key] - Property key (for PropSet)
|
|
57
|
+
* @property {unknown} [value] - Property value (for PropSet)
|
|
58
|
+
* @property {string} [oid] - Blob object ID (for BlobValue)
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @typedef {Object} PatchLike
|
|
63
|
+
* @property {string} writer - Writer ID who created this patch
|
|
64
|
+
* @property {number} lamport - Lamport timestamp of this patch
|
|
65
|
+
* @property {OpLike[]} ops - Ordered array of operations
|
|
66
|
+
* @property {Map<string, number>|Record<string, number>} context - Version vector context
|
|
67
|
+
*/
|
|
68
|
+
|
|
43
69
|
/**
|
|
44
70
|
* Creates an empty V5 state with all CRDT structures initialized.
|
|
45
71
|
*
|
|
@@ -78,33 +104,63 @@ export function createEmptyStateV5() {
|
|
|
78
104
|
* clone the state first using `cloneStateV5()`.
|
|
79
105
|
*
|
|
80
106
|
* @param {WarpStateV5} state - The state to mutate. Modified in place.
|
|
81
|
-
* @param {
|
|
82
|
-
* @param {string} op.type - One of: 'NodeAdd', 'NodeRemove', 'EdgeAdd', 'EdgeRemove', 'PropSet', 'BlobValue'
|
|
83
|
-
* @param {string} [op.node] - Node ID (for NodeAdd, NodeRemove, PropSet)
|
|
84
|
-
* @param {import('../crdt/Dot.js').Dot} [op.dot] - Dot identifier (for NodeAdd, EdgeAdd)
|
|
85
|
-
* @param {string[]} [op.observedDots] - Encoded dots to remove (for NodeRemove, EdgeRemove)
|
|
86
|
-
* @param {string} [op.from] - Source node ID (for EdgeAdd, EdgeRemove)
|
|
87
|
-
* @param {string} [op.to] - Target node ID (for EdgeAdd, EdgeRemove)
|
|
88
|
-
* @param {string} [op.label] - Edge label (for EdgeAdd, EdgeRemove)
|
|
89
|
-
* @param {string} [op.key] - Property key (for PropSet)
|
|
90
|
-
* @param {unknown} [op.value] - Property value (for PropSet)
|
|
107
|
+
* @param {OpLike} op - The operation to apply
|
|
91
108
|
* @param {import('../utils/EventId.js').EventId} eventId - Event ID for causality tracking
|
|
92
109
|
* @returns {void}
|
|
93
110
|
*/
|
|
94
111
|
/**
|
|
95
|
-
* Known V2 operation types.
|
|
112
|
+
* Known raw (wire-format) V2 operation types. These are the 6 types that
|
|
113
|
+
* appear in persisted patches and on the sync wire.
|
|
114
|
+
* @type {ReadonlySet<string>}
|
|
115
|
+
*/
|
|
116
|
+
export const RAW_KNOWN_OPS = new Set([
|
|
117
|
+
'NodeAdd', 'NodeRemove', 'EdgeAdd', 'EdgeRemove',
|
|
118
|
+
'PropSet', 'BlobValue',
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Known canonical (internal) V2 operation types. Includes the 6 raw types
|
|
123
|
+
* plus the ADR 1 canonical split types `NodePropSet` and `EdgePropSet`.
|
|
96
124
|
* @type {ReadonlySet<string>}
|
|
97
125
|
*/
|
|
98
|
-
const
|
|
126
|
+
export const CANONICAL_KNOWN_OPS = new Set([
|
|
127
|
+
'NodeAdd', 'NodeRemove', 'EdgeAdd', 'EdgeRemove',
|
|
128
|
+
'PropSet', 'NodePropSet', 'EdgePropSet', 'BlobValue',
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Validates that an operation has a known raw (wire-format) type.
|
|
133
|
+
* Use this at sync/wire boundaries to fail-close on unknown or
|
|
134
|
+
* canonical-only types arriving over the wire.
|
|
135
|
+
*
|
|
136
|
+
* @param {{ type: string }} op
|
|
137
|
+
* @returns {boolean} True if the op type is in RAW_KNOWN_OPS
|
|
138
|
+
*/
|
|
139
|
+
export function isKnownRawOp(op) {
|
|
140
|
+
return Boolean(op && typeof op.type === 'string' && RAW_KNOWN_OPS.has(op.type));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Validates that an operation has a known canonical (internal) type.
|
|
145
|
+
* Use this for internal guards after normalization.
|
|
146
|
+
*
|
|
147
|
+
* @param {{ type: string }} op
|
|
148
|
+
* @returns {boolean} True if the op type is in CANONICAL_KNOWN_OPS
|
|
149
|
+
*/
|
|
150
|
+
export function isKnownCanonicalOp(op) {
|
|
151
|
+
return Boolean(op && typeof op.type === 'string' && CANONICAL_KNOWN_OPS.has(op.type));
|
|
152
|
+
}
|
|
99
153
|
|
|
100
154
|
/**
|
|
101
155
|
* Validates that an operation has a known type.
|
|
102
156
|
*
|
|
157
|
+
* @deprecated Use {@link isKnownRawOp} for wire validation or
|
|
158
|
+
* {@link isKnownCanonicalOp} for internal guards.
|
|
103
159
|
* @param {{ type: string }} op
|
|
104
|
-
* @returns {boolean} True if the op type is
|
|
160
|
+
* @returns {boolean} True if the op type is a known raw wire type
|
|
105
161
|
*/
|
|
106
162
|
export function isKnownOp(op) {
|
|
107
|
-
return
|
|
163
|
+
return isKnownRawOp(op);
|
|
108
164
|
}
|
|
109
165
|
|
|
110
166
|
/**
|
|
@@ -206,6 +262,16 @@ function validateOp(op) {
|
|
|
206
262
|
requireString(op, 'node');
|
|
207
263
|
requireString(op, 'key');
|
|
208
264
|
break;
|
|
265
|
+
case 'NodePropSet':
|
|
266
|
+
requireString(op, 'node');
|
|
267
|
+
requireString(op, 'key');
|
|
268
|
+
break;
|
|
269
|
+
case 'EdgePropSet':
|
|
270
|
+
requireString(op, 'from');
|
|
271
|
+
requireString(op, 'to');
|
|
272
|
+
requireString(op, 'label');
|
|
273
|
+
requireString(op, 'key');
|
|
274
|
+
break;
|
|
209
275
|
default:
|
|
210
276
|
// BlobValue and unknown types: no validation (forward-compat)
|
|
211
277
|
break;
|
|
@@ -245,8 +311,34 @@ export function applyOpV2(state, op, eventId) {
|
|
|
245
311
|
case 'EdgeRemove':
|
|
246
312
|
orsetRemove(state.edgeAlive, /** @type {Set<string>} */ (/** @type {unknown} */ (op.observedDots)));
|
|
247
313
|
break;
|
|
314
|
+
case 'NodePropSet': {
|
|
315
|
+
const key = encodePropKey(/** @type {string} */ (op.node), /** @type {string} */ (op.key));
|
|
316
|
+
const current = state.prop.get(key);
|
|
317
|
+
state.prop.set(key, /** @type {import('../crdt/LWW.js').LWWRegister<unknown>} */ (lwwMax(current, lwwSet(eventId, op.value))));
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
case 'EdgePropSet': {
|
|
321
|
+
const key = encodeEdgePropKey(
|
|
322
|
+
/** @type {string} */ (op.from),
|
|
323
|
+
/** @type {string} */ (op.to),
|
|
324
|
+
/** @type {string} */ (op.label),
|
|
325
|
+
/** @type {string} */ (op.key),
|
|
326
|
+
);
|
|
327
|
+
const current = state.prop.get(key);
|
|
328
|
+
state.prop.set(key, /** @type {import('../crdt/LWW.js').LWWRegister<unknown>} */ (lwwMax(current, lwwSet(eventId, op.value))));
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
248
331
|
case 'PropSet': {
|
|
249
|
-
//
|
|
332
|
+
// Legacy raw PropSet — must NOT carry edge-property encoding at this point.
|
|
333
|
+
// If it does, normalization was skipped.
|
|
334
|
+
if (typeof op.node === 'string' && op.node[0] === EDGE_PROP_PREFIX) {
|
|
335
|
+
throw new PatchError(
|
|
336
|
+
'Unnormalized legacy edge-property PropSet reached canonical apply path. ' +
|
|
337
|
+
'Call normalizeRawOp() at the decode boundary.',
|
|
338
|
+
{ context: { opType: 'PropSet', node: op.node } },
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
// Plain node property (backward-compat for callers that bypass normalization)
|
|
250
342
|
const key = encodePropKey(/** @type {string} */ (op.node), /** @type {string} */ (op.key));
|
|
251
343
|
const current = state.prop.get(key);
|
|
252
344
|
state.prop.set(key, /** @type {import('../crdt/LWW.js').LWWRegister<unknown>} */ (lwwMax(current, lwwSet(eventId, op.value))));
|
|
@@ -270,7 +362,7 @@ export function applyOpV2(state, op, eventId) {
|
|
|
270
362
|
* - EdgeRemove -> EdgeTombstone (CRDT tombstone semantics)
|
|
271
363
|
* - All others pass through unchanged
|
|
272
364
|
*
|
|
273
|
-
* @const {
|
|
365
|
+
* @const {Record<string, string>}
|
|
274
366
|
*/
|
|
275
367
|
const RECEIPT_OP_TYPE = {
|
|
276
368
|
NodeAdd: 'NodeAdd',
|
|
@@ -278,6 +370,8 @@ const RECEIPT_OP_TYPE = {
|
|
|
278
370
|
EdgeAdd: 'EdgeAdd',
|
|
279
371
|
EdgeRemove: 'EdgeTombstone',
|
|
280
372
|
PropSet: 'PropSet',
|
|
373
|
+
NodePropSet: 'NodePropSet',
|
|
374
|
+
EdgePropSet: 'EdgePropSet',
|
|
281
375
|
BlobValue: 'BlobValue',
|
|
282
376
|
};
|
|
283
377
|
|
|
@@ -295,9 +389,7 @@ const VALID_RECEIPT_OPS = new Set(OP_TYPES);
|
|
|
295
389
|
* this add operation is effective or redundant (idempotent re-delivery).
|
|
296
390
|
*
|
|
297
391
|
* @param {import('../crdt/ORSet.js').ORSet} orset - The node OR-Set containing alive nodes
|
|
298
|
-
* @param {
|
|
299
|
-
* @param {string} op.node - The node ID being added
|
|
300
|
-
* @param {import('../crdt/Dot.js').Dot} op.dot - The dot uniquely identifying this add event
|
|
392
|
+
* @param {{node: string, dot: import('../crdt/Dot.js').Dot}} op - The NodeAdd operation
|
|
301
393
|
* @returns {{target: string, result: 'applied'|'redundant'}} Outcome with node ID as target
|
|
302
394
|
*/
|
|
303
395
|
function nodeAddOutcome(orset, op) {
|
|
@@ -318,9 +410,7 @@ function nodeAddOutcome(orset, op) {
|
|
|
318
410
|
* observed at the time the remove was issued.
|
|
319
411
|
*
|
|
320
412
|
* @param {import('../crdt/ORSet.js').ORSet} orset - The node OR-Set containing alive nodes
|
|
321
|
-
* @param {
|
|
322
|
-
* @param {string} [op.node] - The node ID being removed (may be absent for dot-only removes)
|
|
323
|
-
* @param {string[]} op.observedDots - Array of encoded dots that were observed when the remove was issued
|
|
413
|
+
* @param {{node?: string, observedDots: string[] | Set<string>}} op - The NodeRemove operation
|
|
324
414
|
* @returns {{target: string, result: 'applied'|'redundant'}} Outcome with node ID (or '*') as target
|
|
325
415
|
*/
|
|
326
416
|
function nodeRemoveOutcome(orset, op) {
|
|
@@ -350,11 +440,7 @@ function nodeRemoveOutcome(orset, op) {
|
|
|
350
440
|
* Unlike nodes, edges are keyed by the composite (from, to, label) tuple.
|
|
351
441
|
*
|
|
352
442
|
* @param {import('../crdt/ORSet.js').ORSet} orset - The edge OR-Set containing alive edges
|
|
353
|
-
* @param {
|
|
354
|
-
* @param {string} op.from - Source node ID
|
|
355
|
-
* @param {string} op.to - Target node ID
|
|
356
|
-
* @param {string} op.label - Edge label
|
|
357
|
-
* @param {import('../crdt/Dot.js').Dot} op.dot - The dot uniquely identifying this add event
|
|
443
|
+
* @param {{from: string, to: string, label: string, dot: import('../crdt/Dot.js').Dot}} op - The EdgeAdd operation
|
|
358
444
|
* @param {string} edgeKey - Pre-encoded edge key (from\0to\0label format)
|
|
359
445
|
* @returns {{target: string, result: 'applied'|'redundant'}} Outcome with encoded edge key as target
|
|
360
446
|
*/
|
|
@@ -379,11 +465,7 @@ function edgeAddOutcome(orset, op, edgeKey) {
|
|
|
379
465
|
* otherwise falls back to '*' for wildcard/unknown targets.
|
|
380
466
|
*
|
|
381
467
|
* @param {import('../crdt/ORSet.js').ORSet} orset - The edge OR-Set containing alive edges
|
|
382
|
-
* @param {
|
|
383
|
-
* @param {string} [op.from] - Source node ID (optional for computing target)
|
|
384
|
-
* @param {string} [op.to] - Target node ID (optional for computing target)
|
|
385
|
-
* @param {string} [op.label] - Edge label (optional for computing target)
|
|
386
|
-
* @param {string[]} op.observedDots - Array of encoded dots that were observed when the remove was issued
|
|
468
|
+
* @param {{from?: string, to?: string, label?: string, observedDots: string[] | Set<string>}} op - The EdgeRemove operation
|
|
387
469
|
* @returns {{target: string, result: 'applied'|'redundant'}} Outcome with encoded edge key (or '*') as target
|
|
388
470
|
*/
|
|
389
471
|
function edgeRemoveOutcome(orset, op) {
|
|
@@ -409,7 +491,7 @@ function edgeRemoveOutcome(orset, op) {
|
|
|
409
491
|
}
|
|
410
492
|
|
|
411
493
|
/**
|
|
412
|
-
* Determines the receipt outcome for a
|
|
494
|
+
* Determines the receipt outcome for a property operation given a pre-computed key.
|
|
413
495
|
*
|
|
414
496
|
* Uses LWW (Last-Write-Wins) semantics to determine whether the incoming property
|
|
415
497
|
* value wins over any existing value. The comparison is based on EventId ordering:
|
|
@@ -423,41 +505,55 @@ function edgeRemoveOutcome(orset, op) {
|
|
|
423
505
|
* - `redundant`: Exact same write (identical EventId)
|
|
424
506
|
*
|
|
425
507
|
* @param {Map<string, import('../crdt/LWW.js').LWWRegister<unknown>>} propMap - The properties map keyed by encoded prop keys
|
|
426
|
-
* @param {
|
|
427
|
-
* @param {string} op.node - Node ID owning the property
|
|
428
|
-
* @param {string} op.key - Property key/name
|
|
429
|
-
* @param {unknown} op.value - Property value to set
|
|
508
|
+
* @param {string} key - Pre-encoded property key (node or edge)
|
|
430
509
|
* @param {import('../utils/EventId.js').EventId} eventId - The event ID for this operation, used for LWW comparison
|
|
431
510
|
* @returns {{target: string, result: 'applied'|'superseded'|'redundant', reason?: string}}
|
|
432
511
|
* Outcome with encoded prop key as target; includes reason when superseded
|
|
433
512
|
*/
|
|
434
|
-
function
|
|
435
|
-
const key = encodePropKey(op.node, op.key);
|
|
513
|
+
function propOutcomeForKey(propMap, key, eventId) {
|
|
436
514
|
const current = propMap.get(key);
|
|
437
|
-
const target = key;
|
|
438
515
|
|
|
439
516
|
if (!current) {
|
|
440
|
-
|
|
441
|
-
return { target, result: 'applied' };
|
|
517
|
+
return { target: key, result: 'applied' };
|
|
442
518
|
}
|
|
443
519
|
|
|
444
|
-
// Compare the incoming EventId with the existing register's EventId
|
|
445
520
|
const cmp = compareEventIds(eventId, current.eventId);
|
|
446
521
|
if (cmp > 0) {
|
|
447
|
-
|
|
448
|
-
return { target, result: 'applied' };
|
|
522
|
+
return { target: key, result: 'applied' };
|
|
449
523
|
}
|
|
450
524
|
if (cmp < 0) {
|
|
451
|
-
// Existing write wins
|
|
452
525
|
const winner = current.eventId;
|
|
453
526
|
return {
|
|
454
|
-
target,
|
|
527
|
+
target: key,
|
|
455
528
|
result: 'superseded',
|
|
456
529
|
reason: `LWW: writer ${winner.writerId} at lamport ${winner.lamport} wins`,
|
|
457
530
|
};
|
|
458
531
|
}
|
|
459
|
-
|
|
460
|
-
|
|
532
|
+
return { target: key, result: 'redundant' };
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Determines the receipt outcome for a PropSet/NodePropSet operation.
|
|
537
|
+
*
|
|
538
|
+
* @param {Map<string, import('../crdt/LWW.js').LWWRegister<unknown>>} propMap
|
|
539
|
+
* @param {{node: string, key: string}} op - The PropSet or NodePropSet operation
|
|
540
|
+
* @param {import('../utils/EventId.js').EventId} eventId
|
|
541
|
+
* @returns {{target: string, result: 'applied'|'superseded'|'redundant', reason?: string}}
|
|
542
|
+
*/
|
|
543
|
+
function propSetOutcome(propMap, op, eventId) {
|
|
544
|
+
return propOutcomeForKey(propMap, encodePropKey(op.node, op.key), eventId);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Determines the receipt outcome for an EdgePropSet operation.
|
|
549
|
+
*
|
|
550
|
+
* @param {Map<string, import('../crdt/LWW.js').LWWRegister<unknown>>} propMap
|
|
551
|
+
* @param {{from: string, to: string, label: string, key: string}} op - The EdgePropSet operation
|
|
552
|
+
* @param {import('../utils/EventId.js').EventId} eventId
|
|
553
|
+
* @returns {{target: string, result: 'applied'|'superseded'|'redundant', reason?: string}}
|
|
554
|
+
*/
|
|
555
|
+
function edgePropSetOutcome(propMap, op, eventId) {
|
|
556
|
+
return propOutcomeForKey(propMap, encodeEdgePropKey(op.from, op.to, op.label, op.key), eventId);
|
|
461
557
|
}
|
|
462
558
|
|
|
463
559
|
/**
|
|
@@ -476,10 +572,7 @@ function foldPatchDot(frontier, writer, lamport) {
|
|
|
476
572
|
/**
|
|
477
573
|
* Merges a patch's context into state and folds the patch dot.
|
|
478
574
|
* @param {WarpStateV5} state
|
|
479
|
-
* @param {
|
|
480
|
-
* @param {string} patch.writer
|
|
481
|
-
* @param {number} patch.lamport
|
|
482
|
-
* @param {Map<string, number>|{[x: string]: number}} patch.context
|
|
575
|
+
* @param {{writer: string, lamport: number, context: Map<string, number>|Record<string, number>}} patch
|
|
483
576
|
*/
|
|
484
577
|
function updateFrontierFromPatch(state, patch) {
|
|
485
578
|
const contextVV = patch.context instanceof Map
|
|
@@ -493,18 +586,14 @@ function updateFrontierFromPatch(state, patch) {
|
|
|
493
586
|
* Applies a patch to state without receipt collection (zero overhead).
|
|
494
587
|
*
|
|
495
588
|
* @param {WarpStateV5} state - The state to mutate in place
|
|
496
|
-
* @param {
|
|
497
|
-
* @param {string} patch.writer
|
|
498
|
-
* @param {number} patch.lamport
|
|
499
|
-
* @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
|
|
500
|
-
* @param {Map<string, number>|{[x: string]: number}} patch.context
|
|
589
|
+
* @param {PatchLike} patch - The patch to apply
|
|
501
590
|
* @param {string} patchSha - Git SHA of the patch commit
|
|
502
591
|
* @returns {WarpStateV5} The mutated state
|
|
503
592
|
*/
|
|
504
593
|
export function applyFast(state, patch, patchSha) {
|
|
505
594
|
for (let i = 0; i < patch.ops.length; i++) {
|
|
506
595
|
const eventId = createEventId(patch.lamport, patch.writer, patchSha, i);
|
|
507
|
-
applyOpV2(state, patch.ops[i], eventId);
|
|
596
|
+
applyOpV2(state, normalizeRawOp(patch.ops[i]), eventId);
|
|
508
597
|
}
|
|
509
598
|
updateFrontierFromPatch(state, patch);
|
|
510
599
|
return state;
|
|
@@ -599,11 +688,17 @@ function snapshotBeforeOp(state, op) {
|
|
|
599
688
|
const aliveBeforeEdges = aliveElementsForDots(state.edgeAlive, edgeDots);
|
|
600
689
|
return { aliveBeforeEdges };
|
|
601
690
|
}
|
|
602
|
-
case 'PropSet':
|
|
691
|
+
case 'PropSet':
|
|
692
|
+
case 'NodePropSet': {
|
|
603
693
|
const pk = encodePropKey(op.node, op.key);
|
|
604
694
|
const reg = state.prop.get(pk);
|
|
605
695
|
return { prevPropValue: reg ? reg.value : undefined, propKey: pk };
|
|
606
696
|
}
|
|
697
|
+
case 'EdgePropSet': {
|
|
698
|
+
const epk = encodeEdgePropKey(op.from, op.to, op.label, op.key);
|
|
699
|
+
const ereg = state.prop.get(epk);
|
|
700
|
+
return { prevPropValue: ereg ? ereg.value : undefined, propKey: epk };
|
|
701
|
+
}
|
|
607
702
|
default:
|
|
608
703
|
return {};
|
|
609
704
|
}
|
|
@@ -640,7 +735,8 @@ function accumulateOpDiff(diff, state, op, before) {
|
|
|
640
735
|
collectEdgeRemovals(diff, state, before);
|
|
641
736
|
break;
|
|
642
737
|
}
|
|
643
|
-
case 'PropSet':
|
|
738
|
+
case 'PropSet':
|
|
739
|
+
case 'NodePropSet': {
|
|
644
740
|
const reg = state.prop.get(/** @type {string} */ (before.propKey));
|
|
645
741
|
const newVal = reg ? reg.value : undefined;
|
|
646
742
|
if (newVal !== before.prevPropValue) {
|
|
@@ -653,6 +749,19 @@ function accumulateOpDiff(diff, state, op, before) {
|
|
|
653
749
|
}
|
|
654
750
|
break;
|
|
655
751
|
}
|
|
752
|
+
case 'EdgePropSet': {
|
|
753
|
+
const ereg = state.prop.get(/** @type {string} */ (before.propKey));
|
|
754
|
+
const eNewVal = ereg ? ereg.value : undefined;
|
|
755
|
+
if (eNewVal !== before.prevPropValue) {
|
|
756
|
+
diff.propsChanged.push({
|
|
757
|
+
nodeId: encodeEdgeKey(op.from, op.to, op.label),
|
|
758
|
+
key: op.key,
|
|
759
|
+
value: eNewVal,
|
|
760
|
+
prevValue: before.prevPropValue,
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
break;
|
|
764
|
+
}
|
|
656
765
|
default:
|
|
657
766
|
break;
|
|
658
767
|
}
|
|
@@ -698,11 +807,7 @@ function collectEdgeRemovals(diff, state, before) {
|
|
|
698
807
|
* winner changes. Redundant ops produce no diff entries.
|
|
699
808
|
*
|
|
700
809
|
* @param {WarpStateV5} state - The state to mutate in place
|
|
701
|
-
* @param {
|
|
702
|
-
* @param {string} patch.writer
|
|
703
|
-
* @param {number} patch.lamport
|
|
704
|
-
* @param {Array<Object>} patch.ops
|
|
705
|
-
* @param {Map<string, number>|{[x: string]: number}} patch.context
|
|
810
|
+
* @param {PatchLike} patch - The patch to apply
|
|
706
811
|
* @param {string} patchSha - Git SHA of the patch commit
|
|
707
812
|
* @returns {{state: WarpStateV5, diff: import('../types/PatchDiff.js').PatchDiff}}
|
|
708
813
|
*/
|
|
@@ -710,13 +815,12 @@ export function applyWithDiff(state, patch, patchSha) {
|
|
|
710
815
|
const diff = createEmptyDiff();
|
|
711
816
|
|
|
712
817
|
for (let i = 0; i < patch.ops.length; i++) {
|
|
713
|
-
const
|
|
714
|
-
validateOp(/** @type {Record<string, unknown>} */ (
|
|
818
|
+
const canonOp = /** @type {import('../types/WarpTypesV2.js').CanonicalOpV2} */ (normalizeRawOp(patch.ops[i]));
|
|
819
|
+
validateOp(/** @type {Record<string, unknown>} */ (canonOp));
|
|
715
820
|
const eventId = createEventId(patch.lamport, patch.writer, patchSha, i);
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
accumulateOpDiff(diff, state, typedOp, before);
|
|
821
|
+
const before = snapshotBeforeOp(state, canonOp);
|
|
822
|
+
applyOpV2(state, canonOp, eventId);
|
|
823
|
+
accumulateOpDiff(diff, state, canonOp, before);
|
|
720
824
|
}
|
|
721
825
|
|
|
722
826
|
updateFrontierFromPatch(state, patch);
|
|
@@ -727,11 +831,7 @@ export function applyWithDiff(state, patch, patchSha) {
|
|
|
727
831
|
* Applies a patch to state with receipt collection for provenance tracking.
|
|
728
832
|
*
|
|
729
833
|
* @param {WarpStateV5} state - The state to mutate in place
|
|
730
|
-
* @param {
|
|
731
|
-
* @param {string} patch.writer
|
|
732
|
-
* @param {number} patch.lamport
|
|
733
|
-
* @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
|
|
734
|
-
* @param {Map<string, number>|{[x: string]: number}} patch.context
|
|
834
|
+
* @param {PatchLike} patch - The patch to apply
|
|
735
835
|
* @param {string} patchSha - Git SHA of the patch commit
|
|
736
836
|
* @returns {{state: WarpStateV5, receipt: import('../types/TickReceipt.js').TickReceipt}}
|
|
737
837
|
*/
|
|
@@ -739,41 +839,47 @@ export function applyWithReceipt(state, patch, patchSha) {
|
|
|
739
839
|
/** @type {import('../types/TickReceipt.js').OpOutcome[]} */
|
|
740
840
|
const opResults = [];
|
|
741
841
|
for (let i = 0; i < patch.ops.length; i++) {
|
|
742
|
-
const
|
|
743
|
-
validateOp(/** @type {Record<string, unknown>} */ (
|
|
842
|
+
const canonOp = /** @type {import('../types/WarpTypesV2.js').OpV2} */ (normalizeRawOp(patch.ops[i]));
|
|
843
|
+
validateOp(/** @type {Record<string, unknown>} */ (canonOp));
|
|
744
844
|
const eventId = createEventId(patch.lamport, patch.writer, patchSha, i);
|
|
745
845
|
|
|
746
846
|
// Determine outcome BEFORE applying the op (state is pre-op)
|
|
747
847
|
/** @type {{target: string, result: string, reason?: string}} */
|
|
748
848
|
let outcome;
|
|
749
|
-
switch (
|
|
849
|
+
switch (canonOp.type) {
|
|
750
850
|
case 'NodeAdd':
|
|
751
|
-
outcome = nodeAddOutcome(state.nodeAlive, /** @type {{node: string, dot: import('../crdt/Dot.js').Dot}} */ (
|
|
851
|
+
outcome = nodeAddOutcome(state.nodeAlive, /** @type {{node: string, dot: import('../crdt/Dot.js').Dot}} */ (canonOp));
|
|
752
852
|
break;
|
|
753
853
|
case 'NodeRemove':
|
|
754
|
-
outcome = nodeRemoveOutcome(state.nodeAlive, /** @type {{node?: string, observedDots: string[]}} */ (
|
|
854
|
+
outcome = nodeRemoveOutcome(state.nodeAlive, /** @type {{node?: string, observedDots: string[]}} */ (canonOp));
|
|
755
855
|
break;
|
|
756
856
|
case 'EdgeAdd': {
|
|
757
|
-
const edgeKey = encodeEdgeKey(
|
|
758
|
-
outcome = edgeAddOutcome(state.edgeAlive, /** @type {{from: string, to: string, label: string, dot: import('../crdt/Dot.js').Dot}} */ (
|
|
857
|
+
const edgeKey = encodeEdgeKey(canonOp.from, canonOp.to, canonOp.label);
|
|
858
|
+
outcome = edgeAddOutcome(state.edgeAlive, /** @type {{from: string, to: string, label: string, dot: import('../crdt/Dot.js').Dot}} */ (canonOp), edgeKey);
|
|
759
859
|
break;
|
|
760
860
|
}
|
|
761
861
|
case 'EdgeRemove':
|
|
762
|
-
outcome = edgeRemoveOutcome(state.edgeAlive, /** @type {{from?: string, to?: string, label?: string, observedDots: string[]}} */ (
|
|
862
|
+
outcome = edgeRemoveOutcome(state.edgeAlive, /** @type {{from?: string, to?: string, label?: string, observedDots: string[]}} */ (canonOp));
|
|
763
863
|
break;
|
|
764
864
|
case 'PropSet':
|
|
765
|
-
|
|
865
|
+
case 'NodePropSet':
|
|
866
|
+
outcome = propSetOutcome(state.prop, /** @type {{node: string, key: string, value: unknown}} */ (canonOp), eventId);
|
|
766
867
|
break;
|
|
767
|
-
|
|
868
|
+
case 'EdgePropSet':
|
|
869
|
+
outcome = edgePropSetOutcome(state.prop, /** @type {{from: string, to: string, label: string, key: string, value: unknown}} */ (canonOp), eventId);
|
|
870
|
+
break;
|
|
871
|
+
default: {
|
|
768
872
|
// Unknown or BlobValue — always applied
|
|
769
|
-
|
|
873
|
+
const anyOp = /** @type {Record<string, string>} */ (canonOp);
|
|
874
|
+
outcome = { target: anyOp.node || anyOp.oid || '*', result: 'applied' };
|
|
770
875
|
break;
|
|
876
|
+
}
|
|
771
877
|
}
|
|
772
878
|
|
|
773
879
|
// Apply the op (mutates state)
|
|
774
|
-
applyOpV2(state,
|
|
880
|
+
applyOpV2(state, canonOp, eventId);
|
|
775
881
|
|
|
776
|
-
const receiptOp = /** @type {Record<string, string>} */ (RECEIPT_OP_TYPE)[
|
|
882
|
+
const receiptOp = /** @type {Record<string, string>} */ (RECEIPT_OP_TYPE)[canonOp.type] || canonOp.type;
|
|
777
883
|
// Skip unknown/forward-compatible op types that aren't valid receipt ops
|
|
778
884
|
if (!VALID_RECEIPT_OPS.has(receiptOp)) {
|
|
779
885
|
continue;
|
|
@@ -814,11 +920,7 @@ export function applyWithReceipt(state, patch, patchSha) {
|
|
|
814
920
|
* clone the state first using `cloneStateV5()`.
|
|
815
921
|
*
|
|
816
922
|
* @param {WarpStateV5} state - The state to mutate. Modified in place.
|
|
817
|
-
* @param {
|
|
818
|
-
* @param {string} patch.writer - Writer ID who created this patch
|
|
819
|
-
* @param {number} patch.lamport - Lamport timestamp of this patch
|
|
820
|
-
* @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
|
|
821
|
-
* @param {Map<string, number>|{[x: string]: number}} patch.context - Version vector context (Map or serialized form)
|
|
923
|
+
* @param {PatchLike} patch - The patch to apply
|
|
822
924
|
* @param {string} patchSha - The Git SHA of the patch commit (used for EventId creation)
|
|
823
925
|
* @param {boolean} [collectReceipts=false] - When true, computes and returns receipt data
|
|
824
926
|
* @returns {WarpStateV5|{state: WarpStateV5, receipt: import('../types/TickReceipt.js').TickReceipt}}
|
|
@@ -927,11 +1029,9 @@ function mergeEdgeBirthEvent(a, b) {
|
|
|
927
1029
|
* - When `options.receipts` is true, returns a TickReceipt per patch for
|
|
928
1030
|
* provenance tracking and debugging.
|
|
929
1031
|
*
|
|
930
|
-
* @param {Array<{patch:
|
|
1032
|
+
* @param {Array<{patch: PatchLike, sha: string}>} patches - Array of patch objects with their Git SHAs
|
|
931
1033
|
* @param {WarpStateV5} [initialState] - Optional starting state (for incremental materialization from checkpoint)
|
|
932
|
-
* @param {
|
|
933
|
-
* @param {boolean} [options.receipts=false] - When true, collect and return TickReceipts
|
|
934
|
-
* @param {boolean} [options.trackDiff=false] - When true, collect and return PatchDiff
|
|
1034
|
+
* @param {{receipts?: boolean, trackDiff?: boolean}} [options] - Optional configuration
|
|
935
1035
|
* @returns {WarpStateV5|{state: WarpStateV5, receipts: import('../types/TickReceipt.js').TickReceipt[]}|{state: WarpStateV5, diff: import('../types/PatchDiff.js').PatchDiff}}
|
|
936
1036
|
* Returns state directly when no options;
|
|
937
1037
|
* returns {state, receipts} when receipts is true;
|
|
@@ -91,6 +91,54 @@ export function encodeEdgePropKey(from, to, label, propKey) {
|
|
|
91
91
|
return `${EDGE_PROP_PREFIX}${from}\0${to}\0${label}\0${propKey}`;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
// -------------------------------------------------------------------------
|
|
95
|
+
// Legacy edge-property node encoding (raw PropSet ↔ canonical EdgePropSet)
|
|
96
|
+
// -------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Encodes edge identity as the legacy `node` field value for raw PropSet ops.
|
|
100
|
+
*
|
|
101
|
+
* Format: `\x01from\0to\0label`
|
|
102
|
+
*
|
|
103
|
+
* @param {string} from - Source node ID
|
|
104
|
+
* @param {string} to - Target node ID
|
|
105
|
+
* @param {string} label - Edge label
|
|
106
|
+
* @returns {string}
|
|
107
|
+
*/
|
|
108
|
+
export function encodeLegacyEdgePropNode(from, to, label) {
|
|
109
|
+
return `${EDGE_PROP_PREFIX}${from}\0${to}\0${label}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Returns true if a raw PropSet `node` field encodes an edge identity.
|
|
114
|
+
* @param {string} node - The `node` field from a raw PropSet op
|
|
115
|
+
* @returns {boolean}
|
|
116
|
+
*/
|
|
117
|
+
export function isLegacyEdgePropNode(node) {
|
|
118
|
+
return typeof node === 'string' && node.length > 0 && node[0] === EDGE_PROP_PREFIX;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Decodes a legacy edge-property `node` field back to its components.
|
|
123
|
+
* @param {string} node - The `node` field (must start with \x01)
|
|
124
|
+
* @returns {{from: string, to: string, label: string}}
|
|
125
|
+
* @throws {Error} If the node field is not a valid legacy edge-property encoding
|
|
126
|
+
*/
|
|
127
|
+
export function decodeLegacyEdgePropNode(node) {
|
|
128
|
+
if (!isLegacyEdgePropNode(node)) {
|
|
129
|
+
throw new Error('Invalid legacy edge-property node: missing \\x01 prefix');
|
|
130
|
+
}
|
|
131
|
+
const parts = node.slice(1).split('\0');
|
|
132
|
+
if (parts.length !== 3) {
|
|
133
|
+
throw new Error(`Invalid legacy edge-property node: expected 3 segments, got ${parts.length}`);
|
|
134
|
+
}
|
|
135
|
+
const [from, to, label] = parts;
|
|
136
|
+
if (!from || !to || !label) {
|
|
137
|
+
throw new Error('Invalid legacy edge-property node: empty segment in decoded parts');
|
|
138
|
+
}
|
|
139
|
+
return { from, to, label };
|
|
140
|
+
}
|
|
141
|
+
|
|
94
142
|
/**
|
|
95
143
|
* Returns true if the encoded key is an edge property key.
|
|
96
144
|
* @param {string} key - Encoded property key
|
|
@@ -24,8 +24,7 @@ const MAX_LOCAL_ID = 1 << 24;
|
|
|
24
24
|
|
|
25
25
|
export default class LogicalBitmapIndexBuilder {
|
|
26
26
|
/**
|
|
27
|
-
* @param {
|
|
28
|
-
* @param {import('../../ports/CodecPort.js').default} [options.codec]
|
|
27
|
+
* @param {{ codec?: import('../../ports/CodecPort.js').default }} [options]
|
|
29
28
|
*/
|
|
30
29
|
constructor({ codec } = {}) {
|
|
31
30
|
this._codec = codec || defaultCodec;
|
|
@@ -17,9 +17,7 @@ import { nodeVisibleV5, edgeVisibleV5 } from './StateSerializerV5.js';
|
|
|
17
17
|
|
|
18
18
|
export default class LogicalIndexBuildService {
|
|
19
19
|
/**
|
|
20
|
-
* @param {
|
|
21
|
-
* @param {import('../../ports/CodecPort.js').default} [options.codec]
|
|
22
|
-
* @param {import('../../ports/LoggerPort.js').default} [options.logger]
|
|
20
|
+
* @param {{ codec?: import('../../ports/CodecPort.js').default, logger?: import('../../ports/LoggerPort.js').default }} [options]
|
|
23
21
|
*/
|
|
24
22
|
constructor({ codec, logger } = {}) {
|
|
25
23
|
this._codec = codec || defaultCodec;
|
|
@@ -30,9 +28,7 @@ export default class LogicalIndexBuildService {
|
|
|
30
28
|
* Builds a complete logical index from materialized state.
|
|
31
29
|
*
|
|
32
30
|
* @param {import('./JoinReducer.js').WarpStateV5} state
|
|
33
|
-
* @param {
|
|
34
|
-
* @param {Record<string, { nodeToGlobal: Record<string, number>, nextLocalId: number }>} [options.existingMeta] - Prior meta shards for ID stability
|
|
35
|
-
* @param {Record<string, number>|Array<[string, number]>} [options.existingLabels] - Prior label registry for append-only stability
|
|
31
|
+
* @param {{ existingMeta?: Record<string, { nodeToGlobal: Record<string, number>, nextLocalId: number }>, existingLabels?: Record<string, number>|Array<[string, number]> }} [options]
|
|
36
32
|
* @returns {{ tree: Record<string, Uint8Array>, receipt: Record<string, unknown> }}
|
|
37
33
|
*/
|
|
38
34
|
build(state, options = {}) {
|
|
@@ -89,8 +89,7 @@ function classifyShards(items) {
|
|
|
89
89
|
|
|
90
90
|
export default class LogicalIndexReader {
|
|
91
91
|
/**
|
|
92
|
-
* @param {
|
|
93
|
-
* @param {import('../../ports/CodecPort.js').default} [options.codec]
|
|
92
|
+
* @param {{ codec?: import('../../ports/CodecPort.js').default }} [options]
|
|
94
93
|
*/
|
|
95
94
|
constructor({ codec } = {}) {
|
|
96
95
|
this._codec = codec || defaultCodec;
|