@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
|
@@ -49,9 +49,8 @@ export function getWriters(frontier) {
|
|
|
49
49
|
* Serializes frontier to canonical CBOR bytes.
|
|
50
50
|
* Keys are sorted for determinism.
|
|
51
51
|
* @param {Frontier} frontier
|
|
52
|
-
* @param {
|
|
53
|
-
* @
|
|
54
|
-
* @returns {Buffer|Uint8Array}
|
|
52
|
+
* @param {{ codec?: import('../../ports/CodecPort.js').default }} [options]
|
|
53
|
+
* @returns {Uint8Array}
|
|
55
54
|
*/
|
|
56
55
|
export function serializeFrontier(frontier, { codec } = /** @type {{codec?: import('../../ports/CodecPort.js').default}} */ ({})) {
|
|
57
56
|
const c = codec || defaultCodec;
|
|
@@ -67,9 +66,8 @@ export function serializeFrontier(frontier, { codec } = /** @type {{codec?: impo
|
|
|
67
66
|
|
|
68
67
|
/**
|
|
69
68
|
* Deserializes frontier from CBOR bytes.
|
|
70
|
-
* @param {
|
|
71
|
-
* @param {
|
|
72
|
-
* @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for deserialization
|
|
69
|
+
* @param {Uint8Array} buffer
|
|
70
|
+
* @param {{ codec?: import('../../ports/CodecPort.js').default }} [options]
|
|
73
71
|
* @returns {Frontier}
|
|
74
72
|
*/
|
|
75
73
|
export function deserializeFrontier(buffer, { codec } = /** @type {{codec?: import('../../ports/CodecPort.js').default}} */ ({})) {
|
|
@@ -88,8 +88,7 @@ export default class GitLogParser {
|
|
|
88
88
|
*
|
|
89
89
|
* @param {AsyncIterable<Buffer|Uint8Array|string>} stream - The git log output stream.
|
|
90
90
|
* May yield Buffer, Uint8Array, or string chunks.
|
|
91
|
-
* @param {
|
|
92
|
-
* @param {AbortSignal} [options.signal] - Optional abort signal for cancellation
|
|
91
|
+
* @param {{ signal?: AbortSignal }} [options] - Parse options
|
|
93
92
|
* @yields {GraphNode} Parsed graph nodes. Invalid records are silently skipped.
|
|
94
93
|
* @throws {OperationAbortedError} If signal is aborted during parsing
|
|
95
94
|
*
|
|
@@ -92,11 +92,7 @@ const lexTieBreaker = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
|
92
92
|
/**
|
|
93
93
|
* Distinguishes true topological cycles from maxNodes truncation.
|
|
94
94
|
*
|
|
95
|
-
* @param {
|
|
96
|
-
* @param {number} params.sortedLength
|
|
97
|
-
* @param {number} params.discoveredSize
|
|
98
|
-
* @param {number} params.maxNodes
|
|
99
|
-
* @param {boolean} params.readyRemaining
|
|
95
|
+
* @param {{ sortedLength: number, discoveredSize: number, maxNodes: number, readyRemaining: boolean }} params
|
|
100
96
|
* @returns {boolean}
|
|
101
97
|
*/
|
|
102
98
|
function computeTopoHasCycle({
|
|
@@ -110,10 +106,7 @@ function computeTopoHasCycle({
|
|
|
110
106
|
|
|
111
107
|
export default class GraphTraversal {
|
|
112
108
|
/**
|
|
113
|
-
* @param {
|
|
114
|
-
* @param {NeighborProviderPort} params.provider
|
|
115
|
-
* @param {import('../../ports/LoggerPort.js').default} [params.logger]
|
|
116
|
-
* @param {number} [params.neighborCacheSize]
|
|
109
|
+
* @param {{ provider: NeighborProviderPort, logger?: import('../../ports/LoggerPort.js').default, neighborCacheSize?: number }} params
|
|
117
110
|
*/
|
|
118
111
|
constructor({ provider, logger = nullLogger, neighborCacheSize = 256 }) {
|
|
119
112
|
this._provider = provider;
|
|
@@ -187,14 +180,7 @@ export default class GraphTraversal {
|
|
|
187
180
|
*
|
|
188
181
|
* Deterministic: nodes at equal depth are visited in lexicographic nodeId order.
|
|
189
182
|
*
|
|
190
|
-
* @param {
|
|
191
|
-
* @param {string} params.start
|
|
192
|
-
* @param {Direction} [params.direction]
|
|
193
|
-
* @param {NeighborOptions} [params.options]
|
|
194
|
-
* @param {number} [params.maxNodes]
|
|
195
|
-
* @param {number} [params.maxDepth]
|
|
196
|
-
* @param {AbortSignal} [params.signal]
|
|
197
|
-
* @param {TraversalHooks} [params.hooks]
|
|
183
|
+
* @param {{ start: string, direction?: Direction, options?: NeighborOptions, maxNodes?: number, maxDepth?: number, signal?: AbortSignal, hooks?: TraversalHooks }} params
|
|
198
184
|
* @returns {Promise<{nodes: string[], stats: TraversalStats}>}
|
|
199
185
|
*/
|
|
200
186
|
async bfs({
|
|
@@ -254,14 +240,7 @@ export default class GraphTraversal {
|
|
|
254
240
|
*
|
|
255
241
|
* Deterministic: leftmost-first via reverse-push of sorted neighbors.
|
|
256
242
|
*
|
|
257
|
-
* @param {
|
|
258
|
-
* @param {string} params.start
|
|
259
|
-
* @param {Direction} [params.direction]
|
|
260
|
-
* @param {NeighborOptions} [params.options]
|
|
261
|
-
* @param {number} [params.maxNodes]
|
|
262
|
-
* @param {number} [params.maxDepth]
|
|
263
|
-
* @param {AbortSignal} [params.signal]
|
|
264
|
-
* @param {TraversalHooks} [params.hooks]
|
|
243
|
+
* @param {{ start: string, direction?: Direction, options?: NeighborOptions, maxNodes?: number, maxDepth?: number, signal?: AbortSignal, hooks?: TraversalHooks }} params
|
|
265
244
|
* @returns {Promise<{nodes: string[], stats: TraversalStats}>}
|
|
266
245
|
*/
|
|
267
246
|
async dfs({
|
|
@@ -311,14 +290,7 @@ export default class GraphTraversal {
|
|
|
311
290
|
/**
|
|
312
291
|
* Unweighted shortest path (BFS-based).
|
|
313
292
|
*
|
|
314
|
-
* @param {
|
|
315
|
-
* @param {string} params.start
|
|
316
|
-
* @param {string} params.goal
|
|
317
|
-
* @param {Direction} [params.direction]
|
|
318
|
-
* @param {NeighborOptions} [params.options]
|
|
319
|
-
* @param {number} [params.maxNodes]
|
|
320
|
-
* @param {number} [params.maxDepth]
|
|
321
|
-
* @param {AbortSignal} [params.signal]
|
|
293
|
+
* @param {{ start: string, goal: string, direction?: Direction, options?: NeighborOptions, maxNodes?: number, maxDepth?: number, signal?: AbortSignal }} params
|
|
322
294
|
* @returns {Promise<{found: boolean, path: string[], length: number, stats: TraversalStats}>}
|
|
323
295
|
*/
|
|
324
296
|
async shortestPath({
|
|
@@ -374,14 +346,7 @@ export default class GraphTraversal {
|
|
|
374
346
|
/**
|
|
375
347
|
* Reachability check — BFS with early termination.
|
|
376
348
|
*
|
|
377
|
-
* @param {
|
|
378
|
-
* @param {string} params.start
|
|
379
|
-
* @param {string} params.goal
|
|
380
|
-
* @param {Direction} [params.direction]
|
|
381
|
-
* @param {NeighborOptions} [params.options]
|
|
382
|
-
* @param {number} [params.maxNodes]
|
|
383
|
-
* @param {number} [params.maxDepth]
|
|
384
|
-
* @param {AbortSignal} [params.signal]
|
|
349
|
+
* @param {{ start: string, goal: string, direction?: Direction, options?: NeighborOptions, maxNodes?: number, maxDepth?: number, signal?: AbortSignal }} params
|
|
385
350
|
* @returns {Promise<{reachable: boolean, stats: TraversalStats}>}
|
|
386
351
|
*/
|
|
387
352
|
async isReachable({
|
|
@@ -432,15 +397,7 @@ export default class GraphTraversal {
|
|
|
432
397
|
* Tie-breaking: equal-priority by lexicographic nodeId. Equal-cost
|
|
433
398
|
* predecessor update: when altCost === bestCost && candidatePredecessor < currentPredecessor.
|
|
434
399
|
*
|
|
435
|
-
* @param {
|
|
436
|
-
* @param {string} params.start
|
|
437
|
-
* @param {string} params.goal
|
|
438
|
-
* @param {Direction} [params.direction]
|
|
439
|
-
* @param {NeighborOptions} [params.options]
|
|
440
|
-
* @param {(from: string, to: string, label: string) => number | Promise<number>} [params.weightFn]
|
|
441
|
-
* @param {(nodeId: string) => number | Promise<number>} [params.nodeWeightFn]
|
|
442
|
-
* @param {number} [params.maxNodes]
|
|
443
|
-
* @param {AbortSignal} [params.signal]
|
|
400
|
+
* @param {{ start: string, goal: string, direction?: Direction, options?: NeighborOptions, weightFn?: (from: string, to: string, label: string) => number | Promise<number>, nodeWeightFn?: (nodeId: string) => number | Promise<number>, maxNodes?: number, signal?: AbortSignal }} params
|
|
444
401
|
* @returns {Promise<{path: string[], totalCost: number, stats: TraversalStats}>}
|
|
445
402
|
* @throws {TraversalError} code 'NO_PATH' if unreachable
|
|
446
403
|
* @throws {TraversalError} code 'E_WEIGHT_FN_CONFLICT' if both weightFn and nodeWeightFn provided
|
|
@@ -501,16 +458,7 @@ export default class GraphTraversal {
|
|
|
501
458
|
/**
|
|
502
459
|
* A* search with heuristic guidance.
|
|
503
460
|
*
|
|
504
|
-
* @param {
|
|
505
|
-
* @param {string} params.start
|
|
506
|
-
* @param {string} params.goal
|
|
507
|
-
* @param {Direction} [params.direction]
|
|
508
|
-
* @param {NeighborOptions} [params.options]
|
|
509
|
-
* @param {(from: string, to: string, label: string) => number | Promise<number>} [params.weightFn]
|
|
510
|
-
* @param {(nodeId: string) => number | Promise<number>} [params.nodeWeightFn]
|
|
511
|
-
* @param {(nodeId: string, goalId: string) => number} [params.heuristicFn]
|
|
512
|
-
* @param {number} [params.maxNodes]
|
|
513
|
-
* @param {AbortSignal} [params.signal]
|
|
461
|
+
* @param {{ start: string, goal: string, direction?: Direction, options?: NeighborOptions, weightFn?: (from: string, to: string, label: string) => number | Promise<number>, nodeWeightFn?: (nodeId: string) => number | Promise<number>, heuristicFn?: (nodeId: string, goalId: string) => number, maxNodes?: number, signal?: AbortSignal }} params
|
|
514
462
|
* @returns {Promise<{path: string[], totalCost: number, nodesExplored: number, stats: TraversalStats}>}
|
|
515
463
|
* @throws {TraversalError} code 'NO_PATH' if unreachable
|
|
516
464
|
* @throws {TraversalError} code 'E_WEIGHT_FN_CONFLICT' if both weightFn and nodeWeightFn provided
|
|
@@ -583,16 +531,7 @@ export default class GraphTraversal {
|
|
|
583
531
|
* bidirectional algorithm — forward always means outgoing, backward always
|
|
584
532
|
* means incoming.
|
|
585
533
|
*
|
|
586
|
-
* @param {
|
|
587
|
-
* @param {string} params.start
|
|
588
|
-
* @param {string} params.goal
|
|
589
|
-
* @param {NeighborOptions} [params.options]
|
|
590
|
-
* @param {(from: string, to: string, label: string) => number | Promise<number>} [params.weightFn]
|
|
591
|
-
* @param {(nodeId: string) => number | Promise<number>} [params.nodeWeightFn]
|
|
592
|
-
* @param {(nodeId: string, goalId: string) => number} [params.forwardHeuristic]
|
|
593
|
-
* @param {(nodeId: string, goalId: string) => number} [params.backwardHeuristic]
|
|
594
|
-
* @param {number} [params.maxNodes]
|
|
595
|
-
* @param {AbortSignal} [params.signal]
|
|
534
|
+
* @param {{ start: string, goal: string, options?: NeighborOptions, weightFn?: (from: string, to: string, label: string) => number | Promise<number>, nodeWeightFn?: (nodeId: string) => number | Promise<number>, forwardHeuristic?: (nodeId: string, goalId: string) => number, backwardHeuristic?: (nodeId: string, goalId: string) => number, maxNodes?: number, signal?: AbortSignal }} params
|
|
596
535
|
* @returns {Promise<{path: string[], totalCost: number, nodesExplored: number, stats: TraversalStats}>}
|
|
597
536
|
* @throws {TraversalError} code 'NO_PATH' if unreachable
|
|
598
537
|
* @throws {TraversalError} code 'E_WEIGHT_FN_CONFLICT' if both weightFn and nodeWeightFn provided
|
|
@@ -674,21 +613,7 @@ export default class GraphTraversal {
|
|
|
674
613
|
/**
|
|
675
614
|
* Expand one node in bidirectional A*.
|
|
676
615
|
* @private
|
|
677
|
-
* @param {
|
|
678
|
-
* @param {MinHeap<string>} p.heap
|
|
679
|
-
* @param {Set<string>} p.visited
|
|
680
|
-
* @param {Map<string, number>} p.gScore
|
|
681
|
-
* @param {Map<string, string>} p.predMap
|
|
682
|
-
* @param {Set<string>} p.otherVisited
|
|
683
|
-
* @param {Map<string, number>} p.otherG
|
|
684
|
-
* @param {(from: string, to: string, label: string) => number | Promise<number>} p.weightFn
|
|
685
|
-
* @param {(nodeId: string, goalId: string) => number} p.heuristicFn
|
|
686
|
-
* @param {string} p.target
|
|
687
|
-
* @param {Direction} p.directionForNeighbors
|
|
688
|
-
* @param {NeighborOptions} [p.options]
|
|
689
|
-
* @param {number} p.mu
|
|
690
|
-
* @param {string|null} p.meeting
|
|
691
|
-
* @param {RunStats} p.rs
|
|
616
|
+
* @param {{ heap: MinHeap<string>, visited: Set<string>, gScore: Map<string, number>, predMap: Map<string, string>, otherVisited: Set<string>, otherG: Map<string, number>, weightFn: (from: string, to: string, label: string) => number | Promise<number>, heuristicFn: (nodeId: string, goalId: string) => number, target: string, directionForNeighbors: Direction, options?: NeighborOptions, mu: number, meeting: string|null, rs: RunStats }} p
|
|
692
617
|
* @returns {Promise<{explored: number, mu: number, meeting: string|null}>}
|
|
693
618
|
*/
|
|
694
619
|
async _biAStarExpand({
|
|
@@ -749,12 +674,7 @@ export default class GraphTraversal {
|
|
|
749
674
|
/**
|
|
750
675
|
* Connected component — delegates to BFS with direction 'both'.
|
|
751
676
|
*
|
|
752
|
-
* @param {
|
|
753
|
-
* @param {string} params.start
|
|
754
|
-
* @param {NeighborOptions} [params.options]
|
|
755
|
-
* @param {number} [params.maxNodes]
|
|
756
|
-
* @param {number} [params.maxDepth]
|
|
757
|
-
* @param {AbortSignal} [params.signal]
|
|
677
|
+
* @param {{ start: string, options?: NeighborOptions, maxNodes?: number, maxDepth?: number, signal?: AbortSignal }} params
|
|
758
678
|
* @returns {Promise<{nodes: string[], stats: TraversalStats}>}
|
|
759
679
|
*/
|
|
760
680
|
async connectedComponent({ start, options, maxNodes, maxDepth, signal }) {
|
|
@@ -766,14 +686,7 @@ export default class GraphTraversal {
|
|
|
766
686
|
*
|
|
767
687
|
* Deterministic: zero-indegree nodes dequeued in lexicographic nodeId order.
|
|
768
688
|
*
|
|
769
|
-
* @param {
|
|
770
|
-
* @param {string | string[]} params.start - One or more start nodes
|
|
771
|
-
* @param {Direction} [params.direction]
|
|
772
|
-
* @param {NeighborOptions} [params.options]
|
|
773
|
-
* @param {number} [params.maxNodes]
|
|
774
|
-
* @param {boolean} [params.throwOnCycle]
|
|
775
|
-
* @param {AbortSignal} [params.signal]
|
|
776
|
-
* @param {boolean} [params._returnAdjList] - Private: return neighbor edge map alongside sorted (for internal reuse)
|
|
689
|
+
* @param {{ start: string | string[], direction?: Direction, options?: NeighborOptions, maxNodes?: number, throwOnCycle?: boolean, signal?: AbortSignal, _returnAdjList?: boolean }} params
|
|
777
690
|
* @returns {Promise<{sorted: string[], hasCycle: boolean, stats: TraversalStats, _neighborEdgeMap?: Map<string, NeighborEdge[]>}>}
|
|
778
691
|
* @throws {TraversalError} code 'ERR_GRAPH_HAS_CYCLES' if throwOnCycle is true and cycle found
|
|
779
692
|
*/
|
|
@@ -914,12 +827,7 @@ export default class GraphTraversal {
|
|
|
914
827
|
* `[A, B, C]`, then B and C may appear in the result because A's BFS
|
|
915
828
|
* reaches them and their own BFS includes themselves at depth 0.
|
|
916
829
|
*
|
|
917
|
-
* @param {
|
|
918
|
-
* @param {string[]} params.nodes - Nodes to find common ancestors of
|
|
919
|
-
* @param {NeighborOptions} [params.options]
|
|
920
|
-
* @param {number} [params.maxDepth]
|
|
921
|
-
* @param {number} [params.maxResults]
|
|
922
|
-
* @param {AbortSignal} [params.signal]
|
|
830
|
+
* @param {{ nodes: string[], options?: NeighborOptions, maxDepth?: number, maxResults?: number, signal?: AbortSignal }} params
|
|
923
831
|
* @returns {Promise<{ancestors: string[], stats: TraversalStats}>}
|
|
924
832
|
*/
|
|
925
833
|
async commonAncestors({
|
|
@@ -981,15 +889,7 @@ export default class GraphTraversal {
|
|
|
981
889
|
*
|
|
982
890
|
* Only valid on DAGs. Throws ERR_GRAPH_HAS_CYCLES if graph has cycles.
|
|
983
891
|
*
|
|
984
|
-
* @param {
|
|
985
|
-
* @param {string} params.start
|
|
986
|
-
* @param {string} params.goal
|
|
987
|
-
* @param {Direction} [params.direction]
|
|
988
|
-
* @param {NeighborOptions} [params.options]
|
|
989
|
-
* @param {(from: string, to: string, label: string) => number | Promise<number>} [params.weightFn]
|
|
990
|
-
* @param {(nodeId: string) => number | Promise<number>} [params.nodeWeightFn]
|
|
991
|
-
* @param {number} [params.maxNodes]
|
|
992
|
-
* @param {AbortSignal} [params.signal]
|
|
892
|
+
* @param {{ start: string, goal: string, direction?: Direction, options?: NeighborOptions, weightFn?: (from: string, to: string, label: string) => number | Promise<number>, nodeWeightFn?: (nodeId: string) => number | Promise<number>, maxNodes?: number, signal?: AbortSignal }} params
|
|
993
893
|
* @returns {Promise<{path: string[], totalCost: number, stats: TraversalStats}>}
|
|
994
894
|
* @throws {TraversalError} code 'ERR_GRAPH_HAS_CYCLES' if graph has cycles
|
|
995
895
|
* @throws {TraversalError} code 'NO_PATH' if unreachable
|
|
@@ -45,11 +45,7 @@ export const HealthStatus = {
|
|
|
45
45
|
export default class HealthCheckService {
|
|
46
46
|
/**
|
|
47
47
|
* Creates a HealthCheckService instance.
|
|
48
|
-
* @param {
|
|
49
|
-
* @param {import('../../ports/GraphPersistencePort.js').default & import('../../ports/CommitPort.js').default} options.persistence - Persistence port for repository checks
|
|
50
|
-
* @param {import('../../ports/ClockPort.js').default} options.clock - Clock port for timing operations
|
|
51
|
-
* @param {number} [options.cacheTtlMs=5000] - How long to cache health results in milliseconds
|
|
52
|
-
* @param {import('../../ports/LoggerPort.js').default} [options.logger] - Logger for structured logging
|
|
48
|
+
* @param {{ persistence: import('../../ports/GraphPersistencePort.js').default & import('../../ports/CommitPort.js').default, clock: import('../../ports/ClockPort.js').default, cacheTtlMs?: number, logger?: import('../../ports/LoggerPort.js').default }} options
|
|
53
49
|
*/
|
|
54
50
|
constructor({ persistence, clock, cacheTtlMs = DEFAULT_CACHE_TTL_MS, logger = nullLogger }) {
|
|
55
51
|
this._persistence = persistence;
|
|
@@ -116,9 +112,7 @@ export default class HealthCheckService {
|
|
|
116
112
|
*
|
|
117
113
|
* @typedef {Object} HealthResult
|
|
118
114
|
* @property {'healthy'|'degraded'|'unhealthy'} status - Overall health status
|
|
119
|
-
* @property {
|
|
120
|
-
* @property {RepositoryHealth} components.repository - Repository health
|
|
121
|
-
* @property {IndexHealth} components.index - Index health
|
|
115
|
+
* @property {{ repository: RepositoryHealth, index: IndexHealth }} components - Component health breakdown
|
|
122
116
|
* @property {string} [cachedAt] - ISO timestamp if result is cached
|
|
123
117
|
*
|
|
124
118
|
* @typedef {Object} RepositoryHealth
|
|
@@ -154,7 +148,7 @@ export default class HealthCheckService {
|
|
|
154
148
|
/**
|
|
155
149
|
* Computes health by checking all components.
|
|
156
150
|
* This is called by CachedValue when the cache is stale.
|
|
157
|
-
* @returns {Promise<
|
|
151
|
+
* @returns {Promise<{status: 'healthy'|'degraded'|'unhealthy', components: {repository: RepositoryHealth, index: IndexHealth}}>}
|
|
158
152
|
* @private
|
|
159
153
|
*/
|
|
160
154
|
async _computeHealth() {
|
|
@@ -73,12 +73,7 @@ export class HookInstaller {
|
|
|
73
73
|
/**
|
|
74
74
|
* Creates a new HookInstaller.
|
|
75
75
|
*
|
|
76
|
-
* @param {
|
|
77
|
-
* @param {FsAdapter} deps.fs - Filesystem adapter with methods: readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync
|
|
78
|
-
* @param {(repoPath: string, key: string) => string|null} deps.execGitConfig - Function to read git config values
|
|
79
|
-
* @param {string} deps.version - Package version
|
|
80
|
-
* @param {string} deps.templateDir - Directory containing hook templates
|
|
81
|
-
* @param {PathUtils} deps.path - Path utilities (join and resolve)
|
|
76
|
+
* @param {{ fs: FsAdapter, execGitConfig: (repoPath: string, key: string) => string|null, version: string, templateDir: string, path: PathUtils }} deps - Injected dependencies
|
|
82
77
|
*/
|
|
83
78
|
constructor({ fs, execGitConfig, version, templateDir, path }) {
|
|
84
79
|
/** @type {FsAdapter} */
|
|
@@ -125,8 +120,7 @@ export class HookInstaller {
|
|
|
125
120
|
* Installs the post-merge hook.
|
|
126
121
|
*
|
|
127
122
|
* @param {string} repoPath - Path to git repo
|
|
128
|
-
* @param {
|
|
129
|
-
* @param {'install'|'upgrade'|'append'|'replace'} opts.strategy - Installation strategy
|
|
123
|
+
* @param {{ strategy: 'install'|'upgrade'|'append'|'replace' }} opts - Install options
|
|
130
124
|
* @returns {{ action: string, hookPath: string, version: string, backupPath?: string }}
|
|
131
125
|
* @throws {Error} If the strategy is unknown
|
|
132
126
|
*/
|
|
@@ -92,7 +92,7 @@ function canonicalStringify(value) {
|
|
|
92
92
|
*
|
|
93
93
|
* @param {number} status - HTTP status code
|
|
94
94
|
* @param {string} message - Error message
|
|
95
|
-
* @returns {{ status: number, headers:
|
|
95
|
+
* @returns {{ status: number, headers: Record<string, string>, body: string }}
|
|
96
96
|
* @private
|
|
97
97
|
*/
|
|
98
98
|
function errorResponse(status, message) {
|
|
@@ -107,7 +107,7 @@ function errorResponse(status, message) {
|
|
|
107
107
|
* Builds a JSON success response with canonical key ordering.
|
|
108
108
|
*
|
|
109
109
|
* @param {unknown} data - Response payload
|
|
110
|
-
* @returns {{ status: number, headers:
|
|
110
|
+
* @returns {{ status: number, headers: Record<string, string>, body: string }}
|
|
111
111
|
* @private
|
|
112
112
|
*/
|
|
113
113
|
function jsonResponse(data) {
|
|
@@ -125,7 +125,7 @@ function jsonResponse(data) {
|
|
|
125
125
|
* content type is present but not application/json, otherwise null.
|
|
126
126
|
*
|
|
127
127
|
* @param {{ [x: string]: string }} headers - Request headers
|
|
128
|
-
* @returns {{ status: number, headers:
|
|
128
|
+
* @returns {{ status: number, headers: Record<string, string>, body: string }|null}
|
|
129
129
|
* @private
|
|
130
130
|
*/
|
|
131
131
|
function checkContentType(headers) {
|
|
@@ -143,7 +143,7 @@ function checkContentType(headers) {
|
|
|
143
143
|
* @param {{ method: string, url: string, headers: { [x: string]: string } }} request
|
|
144
144
|
* @param {string} expectedPath
|
|
145
145
|
* @param {string} defaultHost
|
|
146
|
-
* @returns {{ status: number, headers:
|
|
146
|
+
* @returns {{ status: number, headers: Record<string, string>, body: string }|null}
|
|
147
147
|
* @private
|
|
148
148
|
*/
|
|
149
149
|
function validateRoute(request, expectedPath, defaultHost) {
|
|
@@ -168,9 +168,9 @@ function validateRoute(request, expectedPath, defaultHost) {
|
|
|
168
168
|
/**
|
|
169
169
|
* Checks if the request body exceeds the maximum allowed size.
|
|
170
170
|
*
|
|
171
|
-
* @param {Buffer|undefined} body
|
|
171
|
+
* @param {Buffer | Uint8Array | undefined} body
|
|
172
172
|
* @param {number} maxBytes
|
|
173
|
-
* @returns {{ status: number, headers:
|
|
173
|
+
* @returns {{ status: number, headers: Record<string, string>, body: string }|null} Error response or null if within limits
|
|
174
174
|
* @private
|
|
175
175
|
*/
|
|
176
176
|
function checkBodySize(body, maxBytes) {
|
|
@@ -184,12 +184,12 @@ function checkBodySize(body, maxBytes) {
|
|
|
184
184
|
* Parses and validates the request body as a sync request.
|
|
185
185
|
* Uses Zod-based SyncPayloadSchema for shape + resource limit validation.
|
|
186
186
|
*
|
|
187
|
-
* @param {Buffer|undefined} body
|
|
188
|
-
* @returns {{ error: { status: number, headers:
|
|
187
|
+
* @param {Buffer | Uint8Array | undefined} body
|
|
188
|
+
* @returns {{ error: { status: number, headers: Record<string, string>, body: string }, parsed: null } | { error: null, parsed: import('./SyncProtocol.js').SyncRequest }}
|
|
189
189
|
* @private
|
|
190
190
|
*/
|
|
191
191
|
function parseBody(body) {
|
|
192
|
-
const bodyStr = body ?
|
|
192
|
+
const bodyStr = body ? new TextDecoder().decode(body) : '';
|
|
193
193
|
|
|
194
194
|
let parsed;
|
|
195
195
|
try {
|
|
@@ -223,14 +223,7 @@ function initAuth(auth, allowedWriters) {
|
|
|
223
223
|
|
|
224
224
|
export default class HttpSyncServer {
|
|
225
225
|
/**
|
|
226
|
-
* @param {
|
|
227
|
-
* @param {import('../../ports/HttpServerPort.js').default} options.httpPort - HTTP server port abstraction
|
|
228
|
-
* @param {{ processSyncRequest: Function }} options.graph - WarpGraph instance (must expose processSyncRequest)
|
|
229
|
-
* @param {string} [options.path='/sync'] - URL path to handle sync requests on
|
|
230
|
-
* @param {string} [options.host='127.0.0.1'] - Host to bind
|
|
231
|
-
* @param {number} [options.maxRequestBytes=4194304] - Maximum request body size in bytes
|
|
232
|
-
* @param {{ keys: Record<string, string>, mode?: 'enforce'|'log-only', crypto?: import('../../ports/CryptoPort.js').default, logger?: import('../../ports/LoggerPort.js').default, wallClockMs?: () => number }} [options.auth] - Auth configuration
|
|
233
|
-
* @param {string[]} [options.allowedWriters] - Optional whitelist of allowed writer IDs
|
|
226
|
+
* @param {{ httpPort: import('../../ports/HttpServerPort.js').default, graph: { processSyncRequest: (req: import('./SyncProtocol.js').SyncRequest) => Promise<unknown> }, path?: string, host?: string, maxRequestBytes?: number, auth?: { keys: Record<string, string>, mode?: 'enforce'|'log-only', crypto?: import('../../ports/CryptoPort.js').default, logger?: import('../../ports/LoggerPort.js').default, wallClockMs?: () => number }, allowedWriters?: string[] }} options
|
|
234
227
|
*/
|
|
235
228
|
constructor(options) {
|
|
236
229
|
/** @type {z.infer<typeof optionsSchema>} */
|
|
@@ -263,9 +256,9 @@ export default class HttpSyncServer {
|
|
|
263
256
|
* In log-only mode both checks record metrics/logs but always return
|
|
264
257
|
* null so the request proceeds.
|
|
265
258
|
*
|
|
266
|
-
* @param {{ method: string, url: string, headers:
|
|
259
|
+
* @param {{ method: string, url: string, headers: Record<string, string>, body: Buffer | Uint8Array | undefined }} request
|
|
267
260
|
* @param {Record<string, unknown>} parsed - Parsed sync request body
|
|
268
|
-
* @returns {Promise<{ status: number, headers:
|
|
261
|
+
* @returns {Promise<{ status: number, headers: Record<string, string>, body: string }|null>}
|
|
269
262
|
* @private
|
|
270
263
|
*/
|
|
271
264
|
async _authorize(request, parsed) {
|
|
@@ -299,9 +292,15 @@ export default class HttpSyncServer {
|
|
|
299
292
|
return null;
|
|
300
293
|
}
|
|
301
294
|
|
|
302
|
-
/**
|
|
295
|
+
/**
|
|
296
|
+
* Handles an incoming HTTP request through the sync pipeline.
|
|
297
|
+
*
|
|
298
|
+
* @param {import('../../ports/HttpServerPort.js').HttpRequest} request
|
|
299
|
+
* @returns {Promise<import('../../ports/HttpServerPort.js').HttpResponse>}
|
|
300
|
+
* @private
|
|
301
|
+
*/
|
|
303
302
|
async _handleRequest(request) {
|
|
304
|
-
/** @type {{ method: string, url: string, headers: Record<string, string>, body: Buffer|undefined }} */
|
|
303
|
+
/** @type {{ method: string, url: string, headers: Record<string, string>, body: Buffer | Uint8Array | undefined }} */
|
|
305
304
|
const req = { ...request, headers: /** @type {Record<string, string>} */ (request.headers) };
|
|
306
305
|
const contentTypeError = checkContentType(req.headers);
|
|
307
306
|
if (contentTypeError) {
|
|
@@ -348,14 +347,14 @@ export default class HttpSyncServer {
|
|
|
348
347
|
throw new Error('listen() requires a numeric port');
|
|
349
348
|
}
|
|
350
349
|
|
|
351
|
-
/** @type {{ listen: Function, close: Function, address: Function }} */
|
|
352
350
|
const server = this._httpPort.createServer(
|
|
353
|
-
(/** @type {
|
|
351
|
+
(/** @type {import('../../ports/HttpServerPort.js').HttpRequest} */ request) =>
|
|
352
|
+
this._handleRequest(request),
|
|
354
353
|
);
|
|
355
354
|
this._server = server;
|
|
356
355
|
|
|
357
356
|
await /** @type {Promise<void>} */ (new Promise((resolve, reject) => {
|
|
358
|
-
server.listen(port, this._host, (
|
|
357
|
+
server.listen(port, this._host, (err) => {
|
|
359
358
|
if (err) {
|
|
360
359
|
reject(err);
|
|
361
360
|
} else {
|
|
@@ -372,7 +371,7 @@ export default class HttpSyncServer {
|
|
|
372
371
|
url,
|
|
373
372
|
close: () =>
|
|
374
373
|
/** @type {Promise<void>} */ (new Promise((resolve, reject) => {
|
|
375
|
-
server.close((
|
|
374
|
+
server.close((err) => {
|
|
376
375
|
if (err) {
|
|
377
376
|
reject(err);
|
|
378
377
|
} else {
|
|
@@ -42,8 +42,9 @@ const MAX_LOCAL_ID = 1 << 24;
|
|
|
42
42
|
|
|
43
43
|
export default class IncrementalIndexUpdater {
|
|
44
44
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
45
|
+
* Create an incremental index updater.
|
|
46
|
+
*
|
|
47
|
+
* @param {{ codec?: import('../../ports/CodecPort.js').default }} [options]
|
|
47
48
|
*/
|
|
48
49
|
constructor({ codec } = {}) {
|
|
49
50
|
this._codec = codec || defaultCodec;
|
|
@@ -61,10 +62,7 @@ export default class IncrementalIndexUpdater {
|
|
|
61
62
|
/**
|
|
62
63
|
* Computes only the dirty shards from a PatchDiff.
|
|
63
64
|
*
|
|
64
|
-
* @param {
|
|
65
|
-
* @param {import('../types/PatchDiff.js').PatchDiff} params.diff
|
|
66
|
-
* @param {import('./JoinReducer.js').WarpStateV5} params.state
|
|
67
|
-
* @param {(path: string) => Uint8Array|undefined} params.loadShard
|
|
65
|
+
* @param {{ diff: import('../types/PatchDiff.js').PatchDiff, state: import('./JoinReducer.js').WarpStateV5, loadShard: (path: string) => Uint8Array|undefined }} params
|
|
68
66
|
* @returns {Record<string, Uint8Array>} dirty shard buffers (path -> Uint8Array)
|
|
69
67
|
*/
|
|
70
68
|
computeDirtyShards({ diff, state, loadShard }) {
|
|
@@ -39,14 +39,7 @@ export default class IndexRebuildService {
|
|
|
39
39
|
/**
|
|
40
40
|
* Creates an IndexRebuildService instance.
|
|
41
41
|
*
|
|
42
|
-
* @param {
|
|
43
|
-
* @param {{ iterateNodes: (opts: { ref: string, limit: number }) => AsyncIterable<{ sha: string, parents: string[] }> }} options.graphService - Graph service providing node iteration.
|
|
44
|
-
* @param {import('../../ports/IndexStoragePort.js').default} options.storage - Storage adapter
|
|
45
|
-
* for persisting index blobs and trees. Typically GitGraphAdapter.
|
|
46
|
-
* @param {import('../../ports/LoggerPort.js').default} [options.logger] - Logger for
|
|
47
|
-
* structured logging. Defaults to null logger (no logging).
|
|
48
|
-
* @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for serialization
|
|
49
|
-
* @param {import('../../ports/CryptoPort.js').default} [options.crypto] - Crypto adapter for checksums
|
|
42
|
+
* @param {{ graphService: { iterateNodes: (opts: { ref: string, limit: number }) => AsyncIterable<{ sha: string, parents: string[] }> }, storage: import('../../ports/IndexStoragePort.js').default, logger?: import('../../ports/LoggerPort.js').default, codec?: import('../../ports/CodecPort.js').default, crypto?: import('../../ports/CryptoPort.js').default }} options - Configuration options
|
|
50
43
|
* @throws {Error} If graphService is not provided
|
|
51
44
|
* @throws {Error} If storage adapter is not provided
|
|
52
45
|
*/
|
|
@@ -87,19 +80,7 @@ export default class IndexRebuildService {
|
|
|
87
80
|
* - `shards_rev_XX.json`: Reverse edge bitmaps (parent lookups)
|
|
88
81
|
*
|
|
89
82
|
* @param {string} ref - Git ref to start traversal from (e.g., 'HEAD', branch name, SHA)
|
|
90
|
-
* @param {
|
|
91
|
-
* @param {number} [options.limit=10000000] - Maximum nodes to process (1 to 10,000,000)
|
|
92
|
-
* @param {number} [options.maxMemoryBytes] - Enable streaming mode with this memory threshold.
|
|
93
|
-
* When bitmap memory exceeds this value, data is flushed to storage.
|
|
94
|
-
* Recommended: 50-100MB for most systems. Minimum: 1MB.
|
|
95
|
-
* @param {Function} [options.onFlush] - Callback invoked on each flush (streaming mode only).
|
|
96
|
-
* Receives { flushedBytes, totalFlushedBytes, flushCount }.
|
|
97
|
-
* @param {Function} [options.onProgress] - Callback invoked periodically during processing.
|
|
98
|
-
* Receives { processedNodes, currentMemoryBytes }.
|
|
99
|
-
* @param {AbortSignal} [options.signal] - Optional AbortSignal for cancellation support.
|
|
100
|
-
* When aborted, throws OperationAbortedError at the next loop boundary.
|
|
101
|
-
* @param {Map<string, string>} [options.frontier] - Frontier to persist alongside the rebuilt index.
|
|
102
|
-
* Maps writer IDs to their tip SHAs; stored in the index tree for staleness detection.
|
|
83
|
+
* @param {{ limit?: number, maxMemoryBytes?: number, onFlush?: (stats: {flushedBytes: number, totalFlushedBytes: number, flushCount: number}) => void, onProgress?: (stats: {processedNodes: number, currentMemoryBytes: number | null}) => void, signal?: AbortSignal, frontier?: Map<string, string> }} [options] - Rebuild options
|
|
103
84
|
* @returns {Promise<string>} OID of the created tree containing the index
|
|
104
85
|
* @throws {Error} If maxMemoryBytes is specified but not positive
|
|
105
86
|
* @throws {OperationAbortedError} If the signal is aborted during rebuild
|
|
@@ -174,13 +155,7 @@ export default class IndexRebuildService {
|
|
|
174
155
|
* data structures, plus temporary overhead during serialization.
|
|
175
156
|
*
|
|
176
157
|
* @param {string} ref - Git ref to traverse from
|
|
177
|
-
* @param {
|
|
178
|
-
* @param {number} options.limit - Maximum nodes to process
|
|
179
|
-
* @param {Function} [options.onProgress] - Progress callback invoked every 10,000 nodes.
|
|
180
|
-
* Receives `{ processedNodes: number, currentMemoryBytes: null }`.
|
|
181
|
-
* @param {AbortSignal} [options.signal] - Abort signal for cancellation. Checked every
|
|
182
|
-
* 10,000 nodes to balance responsiveness with performance.
|
|
183
|
-
* @param {Map<string, string>} [options.frontier] - Frontier to persist with the index
|
|
158
|
+
* @param {{ limit: number, onProgress?: (stats: {processedNodes: number, currentMemoryBytes: number | null}) => void, signal?: AbortSignal, frontier?: Map<string, string> }} options - Options
|
|
184
159
|
* @returns {Promise<string>} Tree OID of the persisted index
|
|
185
160
|
* @throws {OperationAbortedError} If the signal is aborted during iteration
|
|
186
161
|
* @throws {Error} If node iteration fails (e.g., invalid ref, Git error)
|
|
@@ -229,17 +204,7 @@ export default class IndexRebuildService {
|
|
|
229
204
|
* - You can tolerate longer rebuild times for lower memory usage
|
|
230
205
|
*
|
|
231
206
|
* @param {string} ref - Git ref to traverse from
|
|
232
|
-
* @param {
|
|
233
|
-
* @param {number} options.limit - Maximum nodes to process
|
|
234
|
-
* @param {number} options.maxMemoryBytes - Memory threshold in bytes. When estimated
|
|
235
|
-
* bitmap memory exceeds this, a flush is triggered.
|
|
236
|
-
* @param {Function} [options.onFlush] - Flush callback invoked after each flush.
|
|
237
|
-
* Receives `{ flushedBytes, totalFlushedBytes, flushCount }`.
|
|
238
|
-
* @param {Function} [options.onProgress] - Progress callback invoked every 10,000 nodes.
|
|
239
|
-
* Receives `{ processedNodes, currentMemoryBytes }`.
|
|
240
|
-
* @param {AbortSignal} [options.signal] - Abort signal for cancellation. Checked every
|
|
241
|
-
* 10,000 nodes during iteration and at finalization.
|
|
242
|
-
* @param {Map<string, string>} [options.frontier] - Frontier to persist with the index
|
|
207
|
+
* @param {{ limit: number, maxMemoryBytes: number, onFlush?: (stats: {flushedBytes: number, totalFlushedBytes: number, flushCount: number}) => void, onProgress?: (stats: {processedNodes: number, currentMemoryBytes: number}) => void, signal?: AbortSignal, frontier?: Map<string, string> }} options - Options
|
|
243
208
|
* @returns {Promise<string>} Tree OID of the persisted index
|
|
244
209
|
* @throws {OperationAbortedError} If the signal is aborted during iteration or finalization
|
|
245
210
|
* @throws {Error} If node iteration fails (e.g., invalid ref, Git error)
|
|
@@ -291,8 +256,7 @@ export default class IndexRebuildService {
|
|
|
291
256
|
* - `100644 blob <oid>\tfrontier.json` (if frontier provided)
|
|
292
257
|
*
|
|
293
258
|
* @param {BitmapIndexBuilder} builder - The builder containing index data
|
|
294
|
-
* @param {
|
|
295
|
-
* @param {Map<string, string>} [options.frontier] - Frontier to include in the tree
|
|
259
|
+
* @param {{ frontier?: Map<string, string> }} [options] - Persistence options
|
|
296
260
|
* @returns {Promise<string>} OID of the created tree
|
|
297
261
|
* @throws {Error} If storage.writeBlob() fails for any shard
|
|
298
262
|
* @throws {Error} If storage.writeTree() fails
|
|
@@ -329,17 +293,7 @@ export default class IndexRebuildService {
|
|
|
329
293
|
* incomplete or incorrect query results.
|
|
330
294
|
*
|
|
331
295
|
* @param {string} treeOid - OID of the index tree (from rebuild() or a saved ref)
|
|
332
|
-
* @param {
|
|
333
|
-
* @param {boolean} [options.strict=true] - Enable strict integrity verification (fail-closed).
|
|
334
|
-
* When true, throws on any shard validation or corruption errors.
|
|
335
|
-
* When false, attempts graceful degradation.
|
|
336
|
-
* @param {Map<string, string>} [options.currentFrontier] - Frontier to compare for staleness.
|
|
337
|
-
* Maps writer IDs to their current tip SHAs. When provided, triggers a staleness
|
|
338
|
-
* check against the frontier stored in the index.
|
|
339
|
-
* @param {boolean} [options.autoRebuild=false] - Auto-rebuild when a stale index is detected.
|
|
340
|
-
* Requires `rebuildRef` to be set.
|
|
341
|
-
* @param {string} [options.rebuildRef] - Git ref to rebuild from when `autoRebuild` is true.
|
|
342
|
-
* Required if `autoRebuild` is true.
|
|
296
|
+
* @param {{ strict?: boolean, currentFrontier?: Map<string, string>, autoRebuild?: boolean, rebuildRef?: string }} [options] - Load options
|
|
343
297
|
* @returns {Promise<BitmapIndexReader>} Configured reader ready for O(1) queries.
|
|
344
298
|
* The reader lazily loads shards on demand; initial load is O(1).
|
|
345
299
|
* @throws {Error} If treeOid is invalid or tree cannot be read from storage
|
|
@@ -22,8 +22,7 @@ function validateEnvelope(envelope, label) {
|
|
|
22
22
|
*
|
|
23
23
|
* @param {Record<string, string>} shardOids - Map of path → blob OID from readTreeOids
|
|
24
24
|
* @param {import('../../ports/IndexStoragePort.js').default & import('../../ports/BlobPort.js').default} storage - Storage adapter
|
|
25
|
-
* @param {
|
|
26
|
-
* @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for deserialization
|
|
25
|
+
* @param {{ codec?: import('../../ports/CodecPort.js').default }} [options]
|
|
27
26
|
* @returns {Promise<Map<string, string>|null>} Frontier map, or null if not present (legacy index)
|
|
28
27
|
*/
|
|
29
28
|
export async function loadIndexFrontier(shardOids, storage, { codec } = {}) {
|
|
@@ -39,7 +38,7 @@ export async function loadIndexFrontier(shardOids, storage, { codec } = {}) {
|
|
|
39
38
|
const jsonOid = shardOids['frontier.json'];
|
|
40
39
|
if (jsonOid) {
|
|
41
40
|
const buffer = await storage.readBlob(jsonOid);
|
|
42
|
-
const envelope = /** @type {{ frontier: Record<string, string> }} */ (JSON.parse(
|
|
41
|
+
const envelope = /** @type {{ frontier: Record<string, string> }} */ (JSON.parse(new TextDecoder().decode(buffer)));
|
|
43
42
|
validateEnvelope(envelope, 'frontier.json');
|
|
44
43
|
return new Map(Object.entries(envelope.frontier));
|
|
45
44
|
}
|