@git-stunts/git-warp 10.1.2 → 10.4.2
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 +31 -4
- package/bin/warp-graph.js +1242 -59
- package/index.d.ts +31 -0
- package/index.js +4 -0
- package/package.json +13 -3
- package/src/domain/WarpGraph.js +487 -140
- package/src/domain/crdt/LWW.js +1 -1
- package/src/domain/crdt/ORSet.js +10 -6
- package/src/domain/crdt/VersionVector.js +5 -1
- package/src/domain/errors/EmptyMessageError.js +2 -4
- package/src/domain/errors/ForkError.js +4 -0
- package/src/domain/errors/IndexError.js +4 -0
- package/src/domain/errors/OperationAbortedError.js +4 -0
- package/src/domain/errors/QueryError.js +4 -0
- package/src/domain/errors/SchemaUnsupportedError.js +4 -0
- package/src/domain/errors/ShardCorruptionError.js +2 -6
- package/src/domain/errors/ShardLoadError.js +2 -6
- package/src/domain/errors/ShardValidationError.js +2 -7
- package/src/domain/errors/StorageError.js +2 -6
- package/src/domain/errors/SyncError.js +4 -0
- package/src/domain/errors/TraversalError.js +4 -0
- package/src/domain/errors/WarpError.js +2 -4
- package/src/domain/errors/WormholeError.js +4 -0
- package/src/domain/services/AnchorMessageCodec.js +1 -4
- package/src/domain/services/BitmapIndexBuilder.js +10 -6
- package/src/domain/services/BitmapIndexReader.js +27 -21
- package/src/domain/services/BoundaryTransitionRecord.js +22 -15
- package/src/domain/services/CheckpointMessageCodec.js +1 -7
- package/src/domain/services/CheckpointSerializerV5.js +20 -19
- package/src/domain/services/CheckpointService.js +18 -18
- package/src/domain/services/CommitDagTraversalService.js +13 -1
- package/src/domain/services/DagPathFinding.js +40 -18
- package/src/domain/services/DagTopology.js +7 -6
- package/src/domain/services/DagTraversal.js +5 -3
- package/src/domain/services/Frontier.js +7 -6
- package/src/domain/services/HealthCheckService.js +15 -14
- package/src/domain/services/HookInstaller.js +64 -13
- package/src/domain/services/HttpSyncServer.js +15 -14
- package/src/domain/services/IndexRebuildService.js +12 -12
- package/src/domain/services/IndexStalenessChecker.js +13 -6
- package/src/domain/services/JoinReducer.js +28 -27
- package/src/domain/services/LogicalTraversal.js +7 -6
- package/src/domain/services/MessageCodecInternal.js +2 -0
- package/src/domain/services/ObserverView.js +6 -6
- package/src/domain/services/PatchBuilderV2.js +9 -9
- package/src/domain/services/PatchMessageCodec.js +1 -7
- package/src/domain/services/ProvenanceIndex.js +6 -8
- package/src/domain/services/ProvenancePayload.js +1 -2
- package/src/domain/services/QueryBuilder.js +29 -23
- package/src/domain/services/StateDiff.js +7 -7
- package/src/domain/services/StateSerializerV5.js +8 -6
- package/src/domain/services/StreamingBitmapIndexBuilder.js +29 -23
- package/src/domain/services/SyncProtocol.js +23 -26
- package/src/domain/services/TemporalQuery.js +4 -3
- package/src/domain/services/TranslationCost.js +4 -4
- package/src/domain/services/WormholeService.js +19 -15
- package/src/domain/types/TickReceipt.js +10 -6
- package/src/domain/types/WarpTypesV2.js +2 -3
- package/src/domain/utils/CachedValue.js +1 -1
- package/src/domain/utils/LRUCache.js +3 -3
- package/src/domain/utils/MinHeap.js +2 -2
- package/src/domain/utils/RefLayout.js +106 -15
- package/src/domain/utils/WriterId.js +2 -2
- package/src/domain/utils/defaultCodec.js +9 -2
- package/src/domain/utils/defaultCrypto.js +36 -0
- package/src/domain/utils/parseCursorBlob.js +51 -0
- package/src/domain/utils/roaring.js +5 -5
- package/src/domain/utils/seekCacheKey.js +32 -0
- package/src/domain/warp/PatchSession.js +3 -3
- package/src/domain/warp/Writer.js +2 -2
- package/src/infrastructure/adapters/BunHttpAdapter.js +21 -8
- package/src/infrastructure/adapters/CasSeekCacheAdapter.js +311 -0
- package/src/infrastructure/adapters/ClockAdapter.js +2 -2
- package/src/infrastructure/adapters/DenoHttpAdapter.js +22 -9
- package/src/infrastructure/adapters/GitGraphAdapter.js +16 -27
- package/src/infrastructure/adapters/NodeCryptoAdapter.js +16 -3
- package/src/infrastructure/adapters/NodeHttpAdapter.js +33 -11
- package/src/infrastructure/adapters/WebCryptoAdapter.js +21 -11
- package/src/infrastructure/codecs/CborCodec.js +16 -8
- package/src/ports/BlobPort.js +2 -2
- package/src/ports/CodecPort.js +2 -2
- package/src/ports/CommitPort.js +8 -21
- package/src/ports/ConfigPort.js +3 -3
- package/src/ports/CryptoPort.js +7 -7
- package/src/ports/GraphPersistencePort.js +12 -14
- package/src/ports/HttpServerPort.js +1 -5
- package/src/ports/IndexStoragePort.js +1 -0
- package/src/ports/LoggerPort.js +9 -9
- package/src/ports/RefPort.js +5 -5
- package/src/ports/SeekCachePort.js +73 -0
- package/src/ports/TreePort.js +3 -3
- package/src/visualization/layouts/converters.js +14 -7
- package/src/visualization/layouts/elkAdapter.js +24 -11
- package/src/visualization/layouts/elkLayout.js +23 -7
- package/src/visualization/layouts/index.js +3 -3
- package/src/visualization/renderers/ascii/check.js +30 -17
- package/src/visualization/renderers/ascii/graph.js +122 -16
- package/src/visualization/renderers/ascii/history.js +29 -90
- package/src/visualization/renderers/ascii/index.js +1 -1
- package/src/visualization/renderers/ascii/info.js +9 -7
- package/src/visualization/renderers/ascii/materialize.js +20 -16
- package/src/visualization/renderers/ascii/opSummary.js +81 -0
- package/src/visualization/renderers/ascii/path.js +1 -1
- package/src/visualization/renderers/ascii/seek.js +344 -0
- package/src/visualization/renderers/ascii/table.js +1 -1
- package/src/visualization/renderers/svg/index.js +5 -1
|
@@ -29,7 +29,7 @@ export {
|
|
|
29
29
|
* @typedef {Object} WarpStateV5
|
|
30
30
|
* @property {import('../crdt/ORSet.js').ORSet} nodeAlive - ORSet of alive nodes
|
|
31
31
|
* @property {import('../crdt/ORSet.js').ORSet} edgeAlive - ORSet of alive edges
|
|
32
|
-
* @property {Map<string, import('../crdt/LWW.js').LWWRegister
|
|
32
|
+
* @property {Map<string, import('../crdt/LWW.js').LWWRegister<*>>} prop - Properties with LWW
|
|
33
33
|
* @property {import('../crdt/VersionVector.js').VersionVector} observedFrontier - Observed version vector
|
|
34
34
|
* @property {Map<string, import('../utils/EventId.js').EventId>} edgeBirthEvent - EdgeKey → EventId of most recent EdgeAdd (for clean-slate prop visibility)
|
|
35
35
|
*/
|
|
@@ -88,14 +88,14 @@ export function createEmptyStateV5() {
|
|
|
88
88
|
export function applyOpV2(state, op, eventId) {
|
|
89
89
|
switch (op.type) {
|
|
90
90
|
case 'NodeAdd':
|
|
91
|
-
orsetAdd(state.nodeAlive, op.node, op.dot);
|
|
91
|
+
orsetAdd(state.nodeAlive, /** @type {string} */ (op.node), /** @type {import('../crdt/Dot.js').Dot} */ (op.dot));
|
|
92
92
|
break;
|
|
93
93
|
case 'NodeRemove':
|
|
94
|
-
orsetRemove(state.nodeAlive, op.observedDots);
|
|
94
|
+
orsetRemove(state.nodeAlive, /** @type {Set<string>} */ (/** @type {unknown} */ (op.observedDots)));
|
|
95
95
|
break;
|
|
96
96
|
case 'EdgeAdd': {
|
|
97
|
-
const edgeKey = encodeEdgeKey(op.from, op.to, op.label);
|
|
98
|
-
orsetAdd(state.edgeAlive, edgeKey, op.dot);
|
|
97
|
+
const edgeKey = encodeEdgeKey(/** @type {string} */ (op.from), /** @type {string} */ (op.to), /** @type {string} */ (op.label));
|
|
98
|
+
orsetAdd(state.edgeAlive, edgeKey, /** @type {import('../crdt/Dot.js').Dot} */ (op.dot));
|
|
99
99
|
// Track the EventId at which this edge incarnation was born.
|
|
100
100
|
// On re-add after remove, the greater EventId replaces the old one,
|
|
101
101
|
// allowing the query layer to filter out stale properties.
|
|
@@ -108,13 +108,13 @@ export function applyOpV2(state, op, eventId) {
|
|
|
108
108
|
break;
|
|
109
109
|
}
|
|
110
110
|
case 'EdgeRemove':
|
|
111
|
-
orsetRemove(state.edgeAlive, op.observedDots);
|
|
111
|
+
orsetRemove(state.edgeAlive, /** @type {Set<string>} */ (/** @type {unknown} */ (op.observedDots)));
|
|
112
112
|
break;
|
|
113
113
|
case 'PropSet': {
|
|
114
114
|
// Uses EventId-based LWW, same as v4
|
|
115
|
-
const key = encodePropKey(op.node, op.key);
|
|
115
|
+
const key = encodePropKey(/** @type {string} */ (op.node), /** @type {string} */ (op.key));
|
|
116
116
|
const current = state.prop.get(key);
|
|
117
|
-
state.prop.set(key, lwwMax(current, lwwSet(eventId, op.value)));
|
|
117
|
+
state.prop.set(key, /** @type {import('../crdt/LWW.js').LWWRegister<*>} */ (lwwMax(current, lwwSet(eventId, op.value))));
|
|
118
118
|
break;
|
|
119
119
|
}
|
|
120
120
|
default:
|
|
@@ -290,7 +290,7 @@ function edgeRemoveOutcome(orset, op) {
|
|
|
290
290
|
* - `superseded`: An existing value with higher EventId wins
|
|
291
291
|
* - `redundant`: Exact same write (identical EventId)
|
|
292
292
|
*
|
|
293
|
-
* @param {Map<string, import('../crdt/LWW.js').LWWRegister
|
|
293
|
+
* @param {Map<string, import('../crdt/LWW.js').LWWRegister<*>>} propMap - The properties map keyed by encoded prop keys
|
|
294
294
|
* @param {Object} op - The PropSet operation
|
|
295
295
|
* @param {string} op.node - Node ID owning the property
|
|
296
296
|
* @param {string} op.key - Property key/name
|
|
@@ -347,8 +347,8 @@ function propSetOutcome(propMap, op, eventId) {
|
|
|
347
347
|
* @param {Object} patch - The patch to apply
|
|
348
348
|
* @param {string} patch.writer - Writer ID who created this patch
|
|
349
349
|
* @param {number} patch.lamport - Lamport timestamp of this patch
|
|
350
|
-
* @param {
|
|
351
|
-
* @param {Map
|
|
350
|
+
* @param {Array<{type: string, node?: string, dot?: import('../crdt/Dot.js').Dot, observedDots?: string[], from?: string, to?: string, label?: string, key?: string, value?: *, oid?: string}>} patch.ops - Array of operations to apply
|
|
351
|
+
* @param {Map<string, number>|{[x: string]: number}} patch.context - Version vector context (Map or serialized form)
|
|
352
352
|
* @param {string} patchSha - The Git SHA of the patch commit (used for EventId creation)
|
|
353
353
|
* @param {boolean} [collectReceipts=false] - When true, computes and returns receipt data
|
|
354
354
|
* @returns {WarpStateV5|{state: WarpStateV5, receipt: import('../types/TickReceipt.js').TickReceipt}}
|
|
@@ -370,30 +370,32 @@ export function join(state, patch, patchSha, collectReceipts) {
|
|
|
370
370
|
}
|
|
371
371
|
|
|
372
372
|
// Receipt-enabled path
|
|
373
|
+
/** @type {import('../types/TickReceipt.js').OpOutcome[]} */
|
|
373
374
|
const opResults = [];
|
|
374
375
|
for (let i = 0; i < patch.ops.length; i++) {
|
|
375
376
|
const op = patch.ops[i];
|
|
376
377
|
const eventId = createEventId(patch.lamport, patch.writer, patchSha, i);
|
|
377
378
|
|
|
378
379
|
// Determine outcome BEFORE applying the op (state is pre-op)
|
|
380
|
+
/** @type {{target: string, result: string, reason?: string}} */
|
|
379
381
|
let outcome;
|
|
380
382
|
switch (op.type) {
|
|
381
383
|
case 'NodeAdd':
|
|
382
|
-
outcome = nodeAddOutcome(state.nodeAlive, op);
|
|
384
|
+
outcome = nodeAddOutcome(state.nodeAlive, /** @type {{node: string, dot: import('../crdt/Dot.js').Dot}} */ (op));
|
|
383
385
|
break;
|
|
384
386
|
case 'NodeRemove':
|
|
385
|
-
outcome = nodeRemoveOutcome(state.nodeAlive, op);
|
|
387
|
+
outcome = nodeRemoveOutcome(state.nodeAlive, /** @type {{node?: string, observedDots: string[]}} */ (op));
|
|
386
388
|
break;
|
|
387
389
|
case 'EdgeAdd': {
|
|
388
|
-
const edgeKey = encodeEdgeKey(op.from, op.to, op.label);
|
|
389
|
-
outcome = edgeAddOutcome(state.edgeAlive, op, edgeKey);
|
|
390
|
+
const edgeKey = encodeEdgeKey(/** @type {string} */ (op.from), /** @type {string} */ (op.to), /** @type {string} */ (op.label));
|
|
391
|
+
outcome = edgeAddOutcome(state.edgeAlive, /** @type {{from: string, to: string, label: string, dot: import('../crdt/Dot.js').Dot}} */ (op), edgeKey);
|
|
390
392
|
break;
|
|
391
393
|
}
|
|
392
394
|
case 'EdgeRemove':
|
|
393
|
-
outcome = edgeRemoveOutcome(state.edgeAlive, op);
|
|
395
|
+
outcome = edgeRemoveOutcome(state.edgeAlive, /** @type {{from?: string, to?: string, label?: string, observedDots: string[]}} */ (op));
|
|
394
396
|
break;
|
|
395
397
|
case 'PropSet':
|
|
396
|
-
outcome = propSetOutcome(state.prop, op, eventId);
|
|
398
|
+
outcome = propSetOutcome(state.prop, /** @type {{node: string, key: string, value: *}} */ (op), eventId);
|
|
397
399
|
break;
|
|
398
400
|
default:
|
|
399
401
|
// Unknown or BlobValue — always applied
|
|
@@ -404,12 +406,13 @@ export function join(state, patch, patchSha, collectReceipts) {
|
|
|
404
406
|
// Apply the op (mutates state)
|
|
405
407
|
applyOpV2(state, op, eventId);
|
|
406
408
|
|
|
407
|
-
const receiptOp = RECEIPT_OP_TYPE[op.type] || op.type;
|
|
409
|
+
const receiptOp = /** @type {Record<string, string>} */ (RECEIPT_OP_TYPE)[op.type] || op.type;
|
|
408
410
|
// Skip unknown/forward-compatible op types that aren't valid receipt ops
|
|
409
411
|
if (!VALID_RECEIPT_OPS.has(receiptOp)) {
|
|
410
412
|
continue;
|
|
411
413
|
}
|
|
412
|
-
|
|
414
|
+
/** @type {import('../types/TickReceipt.js').OpOutcome} */
|
|
415
|
+
const entry = { op: receiptOp, target: outcome.target, result: /** @type {'applied'|'superseded'|'redundant'} */ (outcome.result) };
|
|
413
416
|
if (outcome.reason) {
|
|
414
417
|
entry.reason = outcome.reason;
|
|
415
418
|
}
|
|
@@ -467,16 +470,16 @@ export function joinStates(a, b) {
|
|
|
467
470
|
*
|
|
468
471
|
* This is a pure function that does not mutate its inputs.
|
|
469
472
|
*
|
|
470
|
-
* @param {Map<string, import('../crdt/LWW.js').LWWRegister
|
|
471
|
-
* @param {Map<string, import('../crdt/LWW.js').LWWRegister
|
|
472
|
-
* @returns {Map<string, import('../crdt/LWW.js').LWWRegister
|
|
473
|
+
* @param {Map<string, import('../crdt/LWW.js').LWWRegister<*>>} a - First property map
|
|
474
|
+
* @param {Map<string, import('../crdt/LWW.js').LWWRegister<*>>} b - Second property map
|
|
475
|
+
* @returns {Map<string, import('../crdt/LWW.js').LWWRegister<*>>} New map containing merged properties
|
|
473
476
|
*/
|
|
474
477
|
function mergeProps(a, b) {
|
|
475
478
|
const result = new Map(a);
|
|
476
479
|
|
|
477
480
|
for (const [key, regB] of b) {
|
|
478
481
|
const regA = result.get(key);
|
|
479
|
-
result.set(key, lwwMax(regA, regB));
|
|
482
|
+
result.set(key, /** @type {import('../crdt/LWW.js').LWWRegister<*>} */ (lwwMax(regA, regB)));
|
|
480
483
|
}
|
|
481
484
|
|
|
482
485
|
return result;
|
|
@@ -527,9 +530,7 @@ function mergeEdgeBirthEvent(a, b) {
|
|
|
527
530
|
* - When `options.receipts` is true, returns a TickReceipt per patch for
|
|
528
531
|
* provenance tracking and debugging.
|
|
529
532
|
*
|
|
530
|
-
* @param {Array<{patch:
|
|
531
|
-
* @param {Object} patches[].patch - The decoded patch object (writer, lamport, ops, context)
|
|
532
|
-
* @param {string} patches[].sha - The Git SHA of the patch commit
|
|
533
|
+
* @param {Array<{patch: {writer: string, lamport: number, ops: Array<{type: string, node?: string, dot?: import('../crdt/Dot.js').Dot, observedDots?: string[], from?: string, to?: string, label?: string, key?: string, value?: *, oid?: string}>, context: Map<string, number>|{[x: string]: number}}, sha: string}>} patches - Array of patch objects with their Git SHAs
|
|
533
534
|
* @param {WarpStateV5} [initialState] - Optional starting state (for incremental materialization from checkpoint)
|
|
534
535
|
* @param {Object} [options] - Optional configuration
|
|
535
536
|
* @param {boolean} [options.receipts=false] - When true, collect and return TickReceipts
|
|
@@ -544,7 +545,7 @@ export function reduceV5(patches, initialState, options) {
|
|
|
544
545
|
if (options && options.receipts) {
|
|
545
546
|
const receipts = [];
|
|
546
547
|
for (const { patch, sha } of patches) {
|
|
547
|
-
const result = join(state, patch, sha, true);
|
|
548
|
+
const result = /** @type {{state: WarpStateV5, receipt: import('../types/TickReceipt.js').TickReceipt}} */ (join(state, patch, sha, true));
|
|
548
549
|
receipts.push(result.receipt);
|
|
549
550
|
}
|
|
550
551
|
return { state, receipts };
|
|
@@ -145,14 +145,14 @@ export default class LogicalTraversal {
|
|
|
145
145
|
* @param {'out'|'in'|'both'} [options.dir] - Edge direction to follow
|
|
146
146
|
* @param {string|string[]} [options.labelFilter] - Edge label(s) to include
|
|
147
147
|
* @param {number} [options.maxDepth] - Maximum depth to traverse
|
|
148
|
-
* @returns {Promise<{dir: 'out'|'in'|'both', labelSet: Set<string>|null, adjacency:
|
|
148
|
+
* @returns {Promise<{dir: 'out'|'in'|'both', labelSet: Set<string>|null, adjacency: {outgoing: Map<string, Array<{neighborId: string, label: string}>>, incoming: Map<string, Array<{neighborId: string, label: string}>>}, depthLimit: number}>}
|
|
149
149
|
* The normalized traversal parameters
|
|
150
150
|
* @throws {TraversalError} If the start node is not found (NODE_NOT_FOUND)
|
|
151
151
|
* @throws {TraversalError} If the direction is invalid (INVALID_DIRECTION)
|
|
152
152
|
* @throws {TraversalError} If the labelFilter is invalid (INVALID_LABEL_FILTER)
|
|
153
153
|
*/
|
|
154
154
|
async _prepare(start, { dir, labelFilter, maxDepth }) {
|
|
155
|
-
const materialized = await this._graph._materializeGraph();
|
|
155
|
+
const materialized = await /** @type {any} */ (this._graph)._materializeGraph(); // TODO(ts-cleanup): narrow port type
|
|
156
156
|
|
|
157
157
|
if (!(await this._graph.hasNode(start))) {
|
|
158
158
|
throw new TraversalError(`Start node not found: ${start}`, {
|
|
@@ -187,7 +187,7 @@ export default class LogicalTraversal {
|
|
|
187
187
|
const result = [];
|
|
188
188
|
|
|
189
189
|
while (queue.length > 0) {
|
|
190
|
-
const current = queue.shift();
|
|
190
|
+
const current = /** @type {{nodeId: string, depth: number}} */ (queue.shift());
|
|
191
191
|
if (visited.has(current.nodeId)) {
|
|
192
192
|
continue;
|
|
193
193
|
}
|
|
@@ -237,7 +237,7 @@ export default class LogicalTraversal {
|
|
|
237
237
|
const result = [];
|
|
238
238
|
|
|
239
239
|
while (stack.length > 0) {
|
|
240
|
-
const current = stack.pop();
|
|
240
|
+
const current = /** @type {{nodeId: string, depth: number}} */ (stack.pop());
|
|
241
241
|
if (visited.has(current.nodeId)) {
|
|
242
242
|
continue;
|
|
243
243
|
}
|
|
@@ -298,7 +298,7 @@ export default class LogicalTraversal {
|
|
|
298
298
|
visited.add(from);
|
|
299
299
|
|
|
300
300
|
while (queue.length > 0) {
|
|
301
|
-
const current = queue.shift();
|
|
301
|
+
const current = /** @type {{nodeId: string, depth: number}} */ (queue.shift());
|
|
302
302
|
if (current.depth >= depthLimit) {
|
|
303
303
|
continue;
|
|
304
304
|
}
|
|
@@ -319,10 +319,11 @@ export default class LogicalTraversal {
|
|
|
319
319
|
|
|
320
320
|
if (edge.neighborId === to) {
|
|
321
321
|
const path = [to];
|
|
322
|
+
/** @type {string|undefined} */
|
|
322
323
|
let cursor = current.nodeId;
|
|
323
324
|
while (cursor) {
|
|
324
325
|
path.push(cursor);
|
|
325
|
-
cursor = parent.get(cursor)
|
|
326
|
+
cursor = parent.get(cursor);
|
|
326
327
|
}
|
|
327
328
|
path.reverse();
|
|
328
329
|
return { found: true, path, length: path.length - 1 };
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* @private
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
// @ts-expect-error -- no declaration file for @git-stunts/trailer-codec
|
|
15
16
|
import { TrailerCodec, TrailerCodecService } from '@git-stunts/trailer-codec';
|
|
16
17
|
|
|
17
18
|
// -----------------------------------------------------------------------------
|
|
@@ -62,6 +63,7 @@ const SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
|
62
63
|
// -----------------------------------------------------------------------------
|
|
63
64
|
|
|
64
65
|
// Lazy singleton codec instance
|
|
66
|
+
/** @type {*} */ // TODO(ts-cleanup): type lazy singleton
|
|
65
67
|
let _codec = null;
|
|
66
68
|
|
|
67
69
|
/**
|
|
@@ -102,7 +102,7 @@ export default class ObserverView {
|
|
|
102
102
|
this._graph = graph;
|
|
103
103
|
|
|
104
104
|
/** @type {LogicalTraversal} */
|
|
105
|
-
this.traverse = new LogicalTraversal(this);
|
|
105
|
+
this.traverse = new LogicalTraversal(/** @type {*} */ (this)); // TODO(ts-cleanup): type observer cast
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/**
|
|
@@ -124,11 +124,11 @@ export default class ObserverView {
|
|
|
124
124
|
* Builds a filtered adjacency structure that only includes edges
|
|
125
125
|
* where both endpoints pass the match filter.
|
|
126
126
|
*
|
|
127
|
-
* @returns {Promise<{state: *, stateHash: string, adjacency: {outgoing: Map, incoming: Map}}>}
|
|
127
|
+
* @returns {Promise<{state: *, stateHash: string, adjacency: {outgoing: Map<string, *[]>, incoming: Map<string, *[]>}}>}
|
|
128
128
|
* @private
|
|
129
129
|
*/
|
|
130
130
|
async _materializeGraph() {
|
|
131
|
-
const materialized = await this._graph._materializeGraph();
|
|
131
|
+
const materialized = await /** @type {*} */ (this._graph)._materializeGraph(); // TODO(ts-cleanup): narrow port type
|
|
132
132
|
const { state, stateHash } = materialized;
|
|
133
133
|
|
|
134
134
|
// Build filtered adjacency: only edges where both endpoints match
|
|
@@ -159,8 +159,8 @@ export default class ObserverView {
|
|
|
159
159
|
incoming.get(to).push({ neighborId: from, label });
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
const sortNeighbors = (list) => {
|
|
163
|
-
list.sort((a, b) => {
|
|
162
|
+
const sortNeighbors = (/** @type {{ neighborId: string, label: string }[]} */ list) => {
|
|
163
|
+
list.sort((/** @type {{ neighborId: string, label: string }} */ a, /** @type {{ neighborId: string, label: string }} */ b) => {
|
|
164
164
|
if (a.neighborId !== b.neighborId) {
|
|
165
165
|
return a.neighborId < b.neighborId ? -1 : 1;
|
|
166
166
|
}
|
|
@@ -260,6 +260,6 @@ export default class ObserverView {
|
|
|
260
260
|
* @returns {QueryBuilder} A query builder scoped to this observer
|
|
261
261
|
*/
|
|
262
262
|
query() {
|
|
263
|
-
return new QueryBuilder(this);
|
|
263
|
+
return new QueryBuilder(/** @type {*} */ (this)); // TODO(ts-cleanup): type observer cast
|
|
264
264
|
}
|
|
265
265
|
}
|
|
@@ -85,8 +85,8 @@ export class PatchBuilderV2 {
|
|
|
85
85
|
* @param {{ warn: Function }} [options.logger] - Logger for non-fatal warnings
|
|
86
86
|
*/
|
|
87
87
|
constructor({ persistence, graphName, writerId, lamport, versionVector, getCurrentState, expectedParentSha = null, onCommitSuccess = null, onDeleteWithData = 'warn', codec, logger }) {
|
|
88
|
-
/** @type {import('../../ports/GraphPersistencePort.js').default} */
|
|
89
|
-
this._persistence = persistence;
|
|
88
|
+
/** @type {import('../../ports/GraphPersistencePort.js').default & import('../../ports/RefPort.js').default & import('../../ports/CommitPort.js').default & import('../../ports/BlobPort.js').default & import('../../ports/TreePort.js').default} */
|
|
89
|
+
this._persistence = /** @type {*} */ (persistence); // TODO(ts-cleanup): narrow port type
|
|
90
90
|
|
|
91
91
|
/** @type {string} */
|
|
92
92
|
this._graphName = graphName;
|
|
@@ -214,7 +214,7 @@ export class PatchBuilderV2 {
|
|
|
214
214
|
const { edges } = findAttachedData(state, nodeId);
|
|
215
215
|
for (const edgeKey of edges) {
|
|
216
216
|
const [from, to, label] = edgeKey.split('\0');
|
|
217
|
-
const edgeDots = [...orsetGetDots(state.edgeAlive, edgeKey)];
|
|
217
|
+
const edgeDots = /** @type {import('../crdt/Dot.js').Dot[]} */ (/** @type {unknown} */ ([...orsetGetDots(state.edgeAlive, edgeKey)]));
|
|
218
218
|
this._ops.push(createEdgeRemoveV2(from, to, label, edgeDots));
|
|
219
219
|
// Provenance: cascade-generated EdgeRemove reads the edge key (to observe its dots)
|
|
220
220
|
this._reads.add(edgeKey);
|
|
@@ -251,7 +251,7 @@ export class PatchBuilderV2 {
|
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
-
const observedDots = state ? [...orsetGetDots(state.nodeAlive, nodeId)] : [];
|
|
254
|
+
const observedDots = /** @type {import('../crdt/Dot.js').Dot[]} */ (/** @type {unknown} */ (state ? [...orsetGetDots(state.nodeAlive, nodeId)] : []));
|
|
255
255
|
this._ops.push(createNodeRemoveV2(nodeId, observedDots));
|
|
256
256
|
// Provenance: NodeRemove reads the node (to observe its dots)
|
|
257
257
|
this._reads.add(nodeId);
|
|
@@ -325,7 +325,7 @@ export class PatchBuilderV2 {
|
|
|
325
325
|
// Get observed dots from current state (orsetGetDots returns already-encoded dot strings)
|
|
326
326
|
const state = this._getCurrentState();
|
|
327
327
|
const edgeKey = encodeEdgeKey(from, to, label);
|
|
328
|
-
const observedDots = state ? [...orsetGetDots(state.edgeAlive, edgeKey)] : [];
|
|
328
|
+
const observedDots = /** @type {import('../crdt/Dot.js').Dot[]} */ (/** @type {unknown} */ (state ? [...orsetGetDots(state.edgeAlive, edgeKey)] : []));
|
|
329
329
|
this._ops.push(createEdgeRemoveV2(from, to, label, observedDots));
|
|
330
330
|
// Provenance: EdgeRemove reads the edge key (to observe its dots)
|
|
331
331
|
this._reads.add(edgeKey);
|
|
@@ -454,7 +454,7 @@ export class PatchBuilderV2 {
|
|
|
454
454
|
schema,
|
|
455
455
|
writer: this._writerId,
|
|
456
456
|
lamport: this._lamport,
|
|
457
|
-
context: this._vv,
|
|
457
|
+
context: /** @type {*} */ (this._vv), // TODO(ts-cleanup): narrow port type
|
|
458
458
|
ops: this._ops,
|
|
459
459
|
reads: [...this._reads].sort(),
|
|
460
460
|
writes: [...this._writes].sort(),
|
|
@@ -515,10 +515,10 @@ export class PatchBuilderV2 {
|
|
|
515
515
|
const currentRefSha = await this._persistence.readRef(writerRef);
|
|
516
516
|
|
|
517
517
|
if (currentRefSha !== this._expectedParentSha) {
|
|
518
|
-
const err = new WriterError(
|
|
518
|
+
const err = /** @type {WriterError & { expectedSha: string|null, actualSha: string|null }} */ (new WriterError(
|
|
519
519
|
'WRITER_CAS_CONFLICT',
|
|
520
520
|
'Commit failed: writer ref was updated by another process. Re-materialize and retry.'
|
|
521
|
-
);
|
|
521
|
+
));
|
|
522
522
|
err.expectedSha = this._expectedParentSha;
|
|
523
523
|
err.actualSha = currentRefSha;
|
|
524
524
|
throw err;
|
|
@@ -556,7 +556,7 @@ export class PatchBuilderV2 {
|
|
|
556
556
|
const patchCbor = this._codec.encode(patch);
|
|
557
557
|
|
|
558
558
|
// 5. Write patch.cbor blob
|
|
559
|
-
const patchBlobOid = await this._persistence.writeBlob(patchCbor);
|
|
559
|
+
const patchBlobOid = await this._persistence.writeBlob(/** @type {Buffer} */ (patchCbor));
|
|
560
560
|
|
|
561
561
|
// 6. Create tree with the blob
|
|
562
562
|
// Format for mktree: "mode type oid\tpath"
|
|
@@ -72,13 +72,7 @@ export function encodePatchMessage({ graph, writer, lamport, patchOid, schema =
|
|
|
72
72
|
* Decodes a patch commit message.
|
|
73
73
|
*
|
|
74
74
|
* @param {string} message - The raw commit message
|
|
75
|
-
* @returns {
|
|
76
|
-
* @returns {string} return.kind - Always 'patch'
|
|
77
|
-
* @returns {string} return.graph - The graph name
|
|
78
|
-
* @returns {string} return.writer - The writer ID
|
|
79
|
-
* @returns {number} return.lamport - The Lamport timestamp
|
|
80
|
-
* @returns {string} return.patchOid - The patch blob OID
|
|
81
|
-
* @returns {number} return.schema - The schema version
|
|
75
|
+
* @returns {{ kind: 'patch', graph: string, writer: string, lamport: number, patchOid: string, schema: number }} The decoded patch message
|
|
82
76
|
* @throws {Error} If the message is not a valid patch message
|
|
83
77
|
*
|
|
84
78
|
* @example
|
|
@@ -52,7 +52,6 @@ class ProvenanceIndex {
|
|
|
52
52
|
/**
|
|
53
53
|
* Internal index mapping nodeId/edgeKey to Set of patch SHAs.
|
|
54
54
|
* @type {Map<string, Set<string>>}
|
|
55
|
-
* @private
|
|
56
55
|
*/
|
|
57
56
|
#index;
|
|
58
57
|
|
|
@@ -120,7 +119,6 @@ class ProvenanceIndex {
|
|
|
120
119
|
*
|
|
121
120
|
* @param {string} entityId - The node ID or edge key
|
|
122
121
|
* @param {string} patchSha - The patch SHA
|
|
123
|
-
* @private
|
|
124
122
|
*/
|
|
125
123
|
#addEntry(entityId, patchSha) {
|
|
126
124
|
let shas = this.#index.get(entityId);
|
|
@@ -227,12 +225,12 @@ class ProvenanceIndex {
|
|
|
227
225
|
* Returns sorted entries for deterministic output.
|
|
228
226
|
*
|
|
229
227
|
* @returns {Array<[string, string[]]>} Sorted array of [entityId, sortedShas[]] pairs
|
|
230
|
-
* @private
|
|
231
228
|
*/
|
|
232
229
|
#sortedEntries() {
|
|
230
|
+
/** @type {Array<[string, string[]]>} */
|
|
233
231
|
const entries = [];
|
|
234
232
|
for (const [entityId, shas] of this.#index) {
|
|
235
|
-
entries.push([entityId, [...shas].sort()]);
|
|
233
|
+
entries.push(/** @type {[string, string[]]} */ ([entityId, [...shas].sort()]));
|
|
236
234
|
}
|
|
237
235
|
entries.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
|
|
238
236
|
return entries;
|
|
@@ -246,7 +244,7 @@ class ProvenanceIndex {
|
|
|
246
244
|
*
|
|
247
245
|
* @param {Object} [options]
|
|
248
246
|
* @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for serialization
|
|
249
|
-
* @returns {Buffer} CBOR-encoded index
|
|
247
|
+
* @returns {Buffer|Uint8Array} CBOR-encoded index
|
|
250
248
|
*/
|
|
251
249
|
serialize({ codec } = {}) {
|
|
252
250
|
const c = codec || defaultCodec;
|
|
@@ -258,7 +256,6 @@ class ProvenanceIndex {
|
|
|
258
256
|
*
|
|
259
257
|
* @param {Array<[string, string[]]>} entries - Array of [entityId, shas[]] pairs
|
|
260
258
|
* @returns {Map<string, Set<string>>} The built index
|
|
261
|
-
* @private
|
|
262
259
|
*/
|
|
263
260
|
static #buildIndex(entries) {
|
|
264
261
|
const index = new Map();
|
|
@@ -279,7 +276,8 @@ class ProvenanceIndex {
|
|
|
279
276
|
*/
|
|
280
277
|
static deserialize(buffer, { codec } = {}) {
|
|
281
278
|
const c = codec || defaultCodec;
|
|
282
|
-
|
|
279
|
+
/** @type {{ version?: number, entries?: Array<[string, string[]]> }} */
|
|
280
|
+
const obj = /** @type {any} */ (c.decode(buffer)); // TODO(ts-cleanup): narrow port type
|
|
283
281
|
|
|
284
282
|
if (obj.version !== 1) {
|
|
285
283
|
throw new Error(`Unsupported ProvenanceIndex version: ${obj.version}`);
|
|
@@ -304,7 +302,7 @@ class ProvenanceIndex {
|
|
|
304
302
|
/**
|
|
305
303
|
* Creates a ProvenanceIndex from a JSON representation.
|
|
306
304
|
*
|
|
307
|
-
* @param {
|
|
305
|
+
* @param {{ version?: number, entries?: Array<[string, string[]]> }} json - Object with version and entries array
|
|
308
306
|
* @returns {ProvenanceIndex} The deserialized index
|
|
309
307
|
* @throws {Error} If the JSON contains an unsupported version
|
|
310
308
|
*/
|
|
@@ -68,7 +68,6 @@ class ProvenancePayload {
|
|
|
68
68
|
/**
|
|
69
69
|
* The internal array of patch entries. Frozen after construction.
|
|
70
70
|
* @type {ReadonlyArray<PatchEntry>}
|
|
71
|
-
* @private
|
|
72
71
|
*/
|
|
73
72
|
#patches;
|
|
74
73
|
|
|
@@ -173,7 +172,7 @@ class ProvenancePayload {
|
|
|
173
172
|
// Use JoinReducer's reduceV5 for deterministic materialization.
|
|
174
173
|
// Note: reduceV5 returns { state, receipts } when options.receipts is truthy,
|
|
175
174
|
// but returns bare WarpStateV5 when no options passed (as here).
|
|
176
|
-
return reduceV5(this.#patches, initialState);
|
|
175
|
+
return /** @type {import('./JoinReducer.js').WarpStateV5} */ (reduceV5(/** @type {*} */ (this.#patches), initialState)); // TODO(ts-cleanup): type patch array
|
|
177
176
|
}
|
|
178
177
|
|
|
179
178
|
/**
|
|
@@ -12,8 +12,8 @@ const DEFAULT_PATTERN = '*';
|
|
|
12
12
|
* @typedef {Object} QueryNodeSnapshot
|
|
13
13
|
* @property {string} id - The unique identifier of the node
|
|
14
14
|
* @property {Record<string, unknown>} props - Frozen snapshot of node properties
|
|
15
|
-
* @property {
|
|
16
|
-
* @property {
|
|
15
|
+
* @property {ReadonlyArray<{label: string, to?: string, from?: string}>} edgesOut - Outgoing edges sorted by label then target
|
|
16
|
+
* @property {ReadonlyArray<{label: string, to?: string, from?: string}>} edgesIn - Incoming edges sorted by label then source
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -271,6 +271,7 @@ function cloneValue(value) {
|
|
|
271
271
|
* @private
|
|
272
272
|
*/
|
|
273
273
|
function buildPropsSnapshot(propsMap) {
|
|
274
|
+
/** @type {Record<string, unknown>} */
|
|
274
275
|
const props = {};
|
|
275
276
|
const keys = [...propsMap.keys()].sort();
|
|
276
277
|
for (const key of keys) {
|
|
@@ -299,8 +300,8 @@ function buildEdgesSnapshot(edges, directionKey) {
|
|
|
299
300
|
if (a.label !== b.label) {
|
|
300
301
|
return a.label < b.label ? -1 : 1;
|
|
301
302
|
}
|
|
302
|
-
const aPeer = a[directionKey];
|
|
303
|
-
const bPeer = b[directionKey];
|
|
303
|
+
const aPeer = /** @type {string} */ (a[directionKey]);
|
|
304
|
+
const bPeer = /** @type {string} */ (b[directionKey]);
|
|
304
305
|
return aPeer < bPeer ? -1 : aPeer > bPeer ? 1 : 0;
|
|
305
306
|
});
|
|
306
307
|
return deepFreeze(list);
|
|
@@ -493,9 +494,13 @@ export default class QueryBuilder {
|
|
|
493
494
|
*/
|
|
494
495
|
constructor(graph) {
|
|
495
496
|
this._graph = graph;
|
|
497
|
+
/** @type {string|null} */
|
|
496
498
|
this._pattern = null;
|
|
499
|
+
/** @type {Array<{type: string, fn?: (node: QueryNodeSnapshot) => boolean, label?: string, depth?: [number, number]}>} */
|
|
497
500
|
this._operations = [];
|
|
501
|
+
/** @type {string[]|null} */
|
|
498
502
|
this._select = null;
|
|
503
|
+
/** @type {AggregateSpec|null} */
|
|
499
504
|
this._aggregate = null;
|
|
500
505
|
}
|
|
501
506
|
|
|
@@ -531,7 +536,7 @@ export default class QueryBuilder {
|
|
|
531
536
|
*/
|
|
532
537
|
where(fn) {
|
|
533
538
|
assertPredicate(fn);
|
|
534
|
-
const predicate = isPlainObject(fn) ? objectToPredicate(fn) : fn;
|
|
539
|
+
const predicate = isPlainObject(fn) ? objectToPredicate(/** @type {Record<string, unknown>} */ (fn)) : /** @type {(node: QueryNodeSnapshot) => boolean} */ (fn);
|
|
535
540
|
this._operations.push({ type: 'where', fn: predicate });
|
|
536
541
|
return this;
|
|
537
542
|
}
|
|
@@ -628,11 +633,6 @@ export default class QueryBuilder {
|
|
|
628
633
|
* The "props." prefix is optional and will be stripped automatically.
|
|
629
634
|
*
|
|
630
635
|
* @param {AggregateSpec} spec - Aggregation specification
|
|
631
|
-
* @param {boolean} [spec.count] - If true, include count of matched nodes
|
|
632
|
-
* @param {string} [spec.sum] - Property path to sum
|
|
633
|
-
* @param {string} [spec.avg] - Property path to average
|
|
634
|
-
* @param {string} [spec.min] - Property path to find minimum
|
|
635
|
-
* @param {string} [spec.max] - Property path to find maximum
|
|
636
636
|
* @returns {QueryBuilder} This builder for chaining
|
|
637
637
|
* @throws {QueryError} If spec is not a plain object (code: E_QUERY_AGGREGATE_TYPE)
|
|
638
638
|
* @throws {QueryError} If numeric aggregation keys are not strings (code: E_QUERY_AGGREGATE_TYPE)
|
|
@@ -646,11 +646,12 @@ export default class QueryBuilder {
|
|
|
646
646
|
});
|
|
647
647
|
}
|
|
648
648
|
const numericKeys = ['sum', 'avg', 'min', 'max'];
|
|
649
|
+
const specAny = /** @type {Record<string, unknown>} */ (/** @type {unknown} */ (spec));
|
|
649
650
|
for (const key of numericKeys) {
|
|
650
|
-
if (
|
|
651
|
+
if (specAny[key] !== undefined && typeof specAny[key] !== 'string') {
|
|
651
652
|
throw new QueryError(`aggregate() expects ${key} to be a string path`, {
|
|
652
653
|
code: 'E_QUERY_AGGREGATE_TYPE',
|
|
653
|
-
context: { key, receivedType: typeof
|
|
654
|
+
context: { key, receivedType: typeof specAny[key] },
|
|
654
655
|
});
|
|
655
656
|
}
|
|
656
657
|
}
|
|
@@ -674,7 +675,7 @@ export default class QueryBuilder {
|
|
|
674
675
|
* @throws {QueryError} If an unknown select field is specified (code: E_QUERY_SELECT_FIELD)
|
|
675
676
|
*/
|
|
676
677
|
async run() {
|
|
677
|
-
const materialized = await this._graph._materializeGraph();
|
|
678
|
+
const materialized = await /** @type {any} */ (this._graph)._materializeGraph(); // TODO(ts-cleanup): narrow port type
|
|
678
679
|
const { adjacency, stateHash } = materialized;
|
|
679
680
|
const allNodes = sortIds(await this._graph.getNodes());
|
|
680
681
|
|
|
@@ -696,15 +697,16 @@ export default class QueryBuilder {
|
|
|
696
697
|
};
|
|
697
698
|
})
|
|
698
699
|
);
|
|
700
|
+
const predicate = /** @type {(node: QueryNodeSnapshot) => boolean} */ (op.fn);
|
|
699
701
|
const filtered = snapshots
|
|
700
|
-
.filter(({ snapshot }) =>
|
|
702
|
+
.filter(({ snapshot }) => predicate(snapshot))
|
|
701
703
|
.map(({ nodeId }) => nodeId);
|
|
702
704
|
workingSet = sortIds(filtered);
|
|
703
705
|
continue;
|
|
704
706
|
}
|
|
705
707
|
|
|
706
708
|
if (op.type === 'outgoing' || op.type === 'incoming') {
|
|
707
|
-
const [minD, maxD] = op.depth;
|
|
709
|
+
const [minD, maxD] = /** @type {[number, number]} */ (op.depth);
|
|
708
710
|
if (minD === 1 && maxD === 1) {
|
|
709
711
|
workingSet = applyHop({
|
|
710
712
|
direction: op.type,
|
|
@@ -718,7 +720,7 @@ export default class QueryBuilder {
|
|
|
718
720
|
label: op.label,
|
|
719
721
|
workingSet,
|
|
720
722
|
adjacency,
|
|
721
|
-
depth: op.depth,
|
|
723
|
+
depth: /** @type {[number, number]} */ (op.depth),
|
|
722
724
|
});
|
|
723
725
|
}
|
|
724
726
|
}
|
|
@@ -778,21 +780,24 @@ export default class QueryBuilder {
|
|
|
778
780
|
* @private
|
|
779
781
|
*/
|
|
780
782
|
async _runAggregate(workingSet, stateHash) {
|
|
781
|
-
const spec = this._aggregate;
|
|
783
|
+
const spec = /** @type {AggregateSpec} */ (this._aggregate);
|
|
784
|
+
/** @type {AggregateResult} */
|
|
782
785
|
const result = { stateHash };
|
|
786
|
+
const specRec = /** @type {Record<string, unknown>} */ (/** @type {unknown} */ (spec));
|
|
783
787
|
|
|
784
788
|
if (spec.count) {
|
|
785
789
|
result.count = workingSet.length;
|
|
786
790
|
}
|
|
787
791
|
|
|
788
792
|
const numericAggs = ['sum', 'avg', 'min', 'max'];
|
|
789
|
-
const activeAggs = numericAggs.filter((key) =>
|
|
793
|
+
const activeAggs = numericAggs.filter((key) => specRec[key]);
|
|
790
794
|
|
|
791
795
|
if (activeAggs.length > 0) {
|
|
796
|
+
/** @type {Map<string, {segments: string[], values: number[]}>} */
|
|
792
797
|
const propsByAgg = new Map();
|
|
793
798
|
for (const key of activeAggs) {
|
|
794
799
|
propsByAgg.set(key, {
|
|
795
|
-
segments:
|
|
800
|
+
segments: /** @type {string} */ (specRec[key]).replace(/^props\./, '').split('.'),
|
|
796
801
|
values: [],
|
|
797
802
|
});
|
|
798
803
|
}
|
|
@@ -800,6 +805,7 @@ export default class QueryBuilder {
|
|
|
800
805
|
for (const nodeId of workingSet) {
|
|
801
806
|
const propsMap = (await this._graph.getNodeProps(nodeId)) || new Map();
|
|
802
807
|
for (const { segments, values } of propsByAgg.values()) {
|
|
808
|
+
/** @type {*} */ // TODO(ts-cleanup): type deep property traversal
|
|
803
809
|
let value = propsMap.get(segments[0]);
|
|
804
810
|
for (let i = 1; i < segments.length; i++) {
|
|
805
811
|
if (value && typeof value === 'object') {
|
|
@@ -817,15 +823,15 @@ export default class QueryBuilder {
|
|
|
817
823
|
|
|
818
824
|
for (const [key, { values }] of propsByAgg) {
|
|
819
825
|
if (key === 'sum') {
|
|
820
|
-
result.sum = values.length > 0 ? values.reduce((a, b) => a + b, 0) : 0;
|
|
826
|
+
result.sum = values.length > 0 ? values.reduce((/** @type {number} */ a, /** @type {number} */ b) => a + b, 0) : 0;
|
|
821
827
|
} else if (key === 'avg') {
|
|
822
|
-
result.avg = values.length > 0 ? values.reduce((a, b) => a + b, 0) / values.length : 0;
|
|
828
|
+
result.avg = values.length > 0 ? values.reduce((/** @type {number} */ a, /** @type {number} */ b) => a + b, 0) / values.length : 0;
|
|
823
829
|
} else if (key === 'min') {
|
|
824
830
|
result.min =
|
|
825
|
-
values.length > 0 ? values.reduce((m, v) => (v < m ? v : m), Infinity) : 0;
|
|
831
|
+
values.length > 0 ? values.reduce((/** @type {number} */ m, /** @type {number} */ v) => (v < m ? v : m), Infinity) : 0;
|
|
826
832
|
} else if (key === 'max') {
|
|
827
833
|
result.max =
|
|
828
|
-
values.length > 0 ? values.reduce((m, v) => (v > m ? v : m), -Infinity) : 0;
|
|
834
|
+
values.length > 0 ? values.reduce((/** @type {number} */ m, /** @type {number} */ v) => (v > m ? v : m), -Infinity) : 0;
|
|
829
835
|
}
|
|
830
836
|
}
|
|
831
837
|
}
|
|
@@ -86,8 +86,8 @@ function compareProps(a, b) {
|
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
88
|
* Checks if two arrays are deeply equal.
|
|
89
|
-
* @param {Array} a
|
|
90
|
-
* @param {Array} b
|
|
89
|
+
* @param {Array<*>} a
|
|
90
|
+
* @param {Array<*>} b
|
|
91
91
|
* @returns {boolean}
|
|
92
92
|
*/
|
|
93
93
|
function arraysEqual(a, b) {
|
|
@@ -104,8 +104,8 @@ function arraysEqual(a, b) {
|
|
|
104
104
|
|
|
105
105
|
/**
|
|
106
106
|
* Checks if two objects are deeply equal.
|
|
107
|
-
* @param {
|
|
108
|
-
* @param {
|
|
107
|
+
* @param {Record<string, *>} a
|
|
108
|
+
* @param {Record<string, *>} b
|
|
109
109
|
* @returns {boolean}
|
|
110
110
|
*/
|
|
111
111
|
function objectsEqual(a, b) {
|
|
@@ -155,9 +155,9 @@ function deepEqual(a, b) {
|
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
157
|
* Computes set difference: elements in `after` not in `before`.
|
|
158
|
-
* @param {Set} before
|
|
159
|
-
* @param {Set} after
|
|
160
|
-
* @returns {Array}
|
|
158
|
+
* @param {Set<string>} before
|
|
159
|
+
* @param {Set<string>} after
|
|
160
|
+
* @returns {Array<string>}
|
|
161
161
|
*/
|
|
162
162
|
function setAdded(before, after) {
|
|
163
163
|
const result = [];
|