@git-stunts/git-warp 12.3.0 → 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.
Files changed (114) hide show
  1. package/README.md +5 -6
  2. package/bin/cli/commands/info.js +1 -5
  3. package/bin/cli/infrastructure.js +6 -9
  4. package/bin/cli/shared.js +8 -0
  5. package/bin/warp-graph.js +6 -6
  6. package/package.json +1 -1
  7. package/src/domain/WarpGraph.js +5 -35
  8. package/src/domain/crdt/VersionVector.js +1 -1
  9. package/src/domain/entities/GraphNode.js +1 -6
  10. package/src/domain/errors/ForkError.js +1 -1
  11. package/src/domain/errors/IndexError.js +1 -1
  12. package/src/domain/errors/OperationAbortedError.js +1 -1
  13. package/src/domain/errors/PatchError.js +1 -1
  14. package/src/domain/errors/PersistenceError.js +45 -0
  15. package/src/domain/errors/QueryError.js +1 -1
  16. package/src/domain/errors/SchemaUnsupportedError.js +1 -1
  17. package/src/domain/errors/SyncError.js +1 -1
  18. package/src/domain/errors/TraversalError.js +1 -1
  19. package/src/domain/errors/TrustError.js +1 -1
  20. package/src/domain/errors/WormholeError.js +1 -1
  21. package/src/domain/errors/index.js +1 -0
  22. package/src/domain/services/AdjacencyNeighborProvider.js +1 -4
  23. package/src/domain/services/AnchorMessageCodec.js +1 -3
  24. package/src/domain/services/AuditMessageCodec.js +1 -5
  25. package/src/domain/services/AuditReceiptService.js +2 -17
  26. package/src/domain/services/AuditVerifierService.js +2 -7
  27. package/src/domain/services/BitmapIndexBuilder.js +6 -12
  28. package/src/domain/services/BitmapIndexReader.js +7 -20
  29. package/src/domain/services/BitmapNeighborProvider.js +1 -3
  30. package/src/domain/services/BoundaryTransitionRecord.js +6 -23
  31. package/src/domain/services/CheckpointMessageCodec.js +1 -6
  32. package/src/domain/services/CheckpointSerializerV5.js +8 -12
  33. package/src/domain/services/CheckpointService.js +9 -39
  34. package/src/domain/services/CommitDagTraversalService.js +1 -3
  35. package/src/domain/services/DagPathFinding.js +9 -59
  36. package/src/domain/services/DagTopology.js +4 -16
  37. package/src/domain/services/DagTraversal.js +7 -31
  38. package/src/domain/services/Frontier.js +4 -6
  39. package/src/domain/services/GitLogParser.js +1 -2
  40. package/src/domain/services/GraphTraversal.js +14 -114
  41. package/src/domain/services/HealthCheckService.js +3 -9
  42. package/src/domain/services/HookInstaller.js +2 -8
  43. package/src/domain/services/HttpSyncServer.js +24 -25
  44. package/src/domain/services/IncrementalIndexUpdater.js +4 -6
  45. package/src/domain/services/IndexRebuildService.js +6 -52
  46. package/src/domain/services/IndexStalenessChecker.js +2 -3
  47. package/src/domain/services/JoinReducer.js +39 -65
  48. package/src/domain/services/LogicalBitmapIndexBuilder.js +1 -2
  49. package/src/domain/services/LogicalIndexBuildService.js +2 -6
  50. package/src/domain/services/LogicalIndexReader.js +1 -2
  51. package/src/domain/services/LogicalTraversal.js +13 -64
  52. package/src/domain/services/MaterializedViewService.js +4 -18
  53. package/src/domain/services/MigrationService.js +1 -4
  54. package/src/domain/services/ObserverView.js +1 -7
  55. package/src/domain/services/PatchBuilderV2.js +6 -18
  56. package/src/domain/services/PatchMessageCodec.js +1 -6
  57. package/src/domain/services/PropertyIndexBuilder.js +1 -2
  58. package/src/domain/services/PropertyIndexReader.js +1 -4
  59. package/src/domain/services/ProvenanceIndex.js +5 -7
  60. package/src/domain/services/ProvenancePayload.js +1 -1
  61. package/src/domain/services/QueryBuilder.js +3 -16
  62. package/src/domain/services/StateDiff.js +3 -9
  63. package/src/domain/services/StateSerializerV5.js +10 -10
  64. package/src/domain/services/StreamingBitmapIndexBuilder.js +13 -41
  65. package/src/domain/services/SyncAuthService.js +5 -32
  66. package/src/domain/services/SyncController.js +5 -25
  67. package/src/domain/services/SyncProtocol.js +4 -8
  68. package/src/domain/services/SyncTrustGate.js +4 -9
  69. package/src/domain/services/TemporalQuery.js +9 -27
  70. package/src/domain/services/TranslationCost.js +2 -8
  71. package/src/domain/services/WarpStateIndexBuilder.js +2 -4
  72. package/src/domain/services/WormholeService.js +9 -25
  73. package/src/domain/trust/TrustCrypto.js +1 -5
  74. package/src/domain/trust/TrustEvaluator.js +1 -8
  75. package/src/domain/trust/TrustRecordService.js +5 -10
  76. package/src/domain/types/TickReceipt.js +3 -7
  77. package/src/domain/types/WarpTypes.js +1 -5
  78. package/src/domain/types/WarpTypesV2.js +1 -8
  79. package/src/domain/utils/CachedValue.js +1 -4
  80. package/src/domain/utils/MinHeap.js +3 -3
  81. package/src/domain/utils/RefLayout.js +26 -0
  82. package/src/domain/utils/WriterId.js +2 -7
  83. package/src/domain/utils/canonicalCbor.js +1 -1
  84. package/src/domain/utils/defaultCodec.js +1 -1
  85. package/src/domain/utils/parseCursorBlob.js +4 -4
  86. package/src/domain/warp/PatchSession.js +3 -8
  87. package/src/domain/warp/Writer.js +3 -12
  88. package/src/domain/warp/_wire.js +2 -2
  89. package/src/domain/warp/_wiredMethods.d.ts +5 -7
  90. package/src/domain/warp/checkpoint.methods.js +1 -1
  91. package/src/domain/warp/fork.methods.js +1 -5
  92. package/src/domain/warp/materializeAdvanced.methods.js +3 -3
  93. package/src/domain/warp/patch.methods.js +6 -8
  94. package/src/domain/warp/provenance.methods.js +5 -5
  95. package/src/domain/warp/query.methods.js +9 -18
  96. package/src/domain/warp/subscribe.methods.js +2 -8
  97. package/src/globals.d.ts +7 -0
  98. package/src/infrastructure/adapters/BunHttpAdapter.js +14 -18
  99. package/src/infrastructure/adapters/ConsoleLogger.js +2 -9
  100. package/src/infrastructure/adapters/DenoHttpAdapter.js +15 -15
  101. package/src/infrastructure/adapters/GitGraphAdapter.js +234 -58
  102. package/src/infrastructure/adapters/InMemoryGraphAdapter.js +9 -2
  103. package/src/infrastructure/adapters/NodeHttpAdapter.js +14 -14
  104. package/src/infrastructure/adapters/WebCryptoAdapter.js +1 -2
  105. package/src/ports/BlobPort.js +2 -2
  106. package/src/ports/HttpServerPort.js +24 -2
  107. package/src/ports/RefPort.js +2 -1
  108. package/src/visualization/renderers/ascii/box.js +1 -1
  109. package/src/visualization/renderers/ascii/check.js +1 -5
  110. package/src/visualization/renderers/ascii/history.js +1 -6
  111. package/src/visualization/renderers/ascii/path.js +4 -22
  112. package/src/visualization/renderers/ascii/progress.js +1 -4
  113. package/src/visualization/renderers/ascii/seek.js +1 -5
  114. package/src/visualization/renderers/ascii/table.js +1 -3
@@ -22,7 +22,7 @@ import { reduceV5, createEmptyStateV5, cloneStateV5 } from './JoinReducer.js';
22
22
  * A single patch entry in the provenance payload.
23
23
  *
24
24
  * @typedef {Object} PatchEntry
25
- * @property {Object} patch - The decoded patch object (writer, lamport, ops, context)
25
+ * @property {import('../types/WarpTypesV2.js').PatchV2} patch - The decoded patch object
26
26
  * @property {string} sha - The Git SHA of the patch commit
27
27
  */
28
28
 
@@ -307,11 +307,7 @@ function buildEdgesSnapshot(edges, directionKey) {
307
307
  * The snapshot includes the node's ID, properties, outgoing edges, and incoming edges.
308
308
  * All data is deeply frozen to prevent mutation.
309
309
  *
310
- * @param {Object} params - Node data
311
- * @param {string} params.id - The node ID
312
- * @param {Map<string, unknown>} params.propsMap - Map of property names to values
313
- * @param {Array<{label: string, neighborId: string}>} params.edgesOut - Outgoing edges
314
- * @param {Array<{label: string, neighborId: string}>} params.edgesIn - Incoming edges
310
+ * @param {{ id: string, propsMap: Map<string, unknown>, edgesOut: Array<{label: string, neighborId: string}>, edgesIn: Array<{label: string, neighborId: string}> }} params - Node data
315
311
  * @returns {Readonly<QueryNodeSnapshot>} Frozen node snapshot
316
312
  * @private
317
313
  */
@@ -385,11 +381,7 @@ function normalizeDepth(depth) {
385
381
  * Collects all neighbors reachable via one edge in the specified direction,
386
382
  * optionally filtered by edge label.
387
383
  *
388
- * @param {Object} params - Traversal parameters
389
- * @param {'outgoing' | 'incoming'} params.direction - Direction of traversal
390
- * @param {string | undefined} params.label - Edge label filter (undefined = all labels)
391
- * @param {string[]} params.workingSet - Current set of node IDs to traverse from
392
- * @param {AdjacencyMaps} params.adjacency - Adjacency maps from materialized state
384
+ * @param {{ direction: 'outgoing' | 'incoming', label: string | undefined, workingSet: string[], adjacency: AdjacencyMaps }} params - Traversal parameters
393
385
  * @returns {string[]} Sorted array of neighbor node IDs
394
386
  * @private
395
387
  */
@@ -420,12 +412,7 @@ function applyHop({ direction, label, workingSet, adjacency }) {
420
412
  *
421
413
  * If minDepth is 0, the starting nodes themselves are included in the result.
422
414
  *
423
- * @param {Object} params - Traversal parameters
424
- * @param {'outgoing' | 'incoming'} params.direction - Direction of traversal
425
- * @param {string | undefined} params.label - Edge label filter (undefined = all labels)
426
- * @param {string[]} params.workingSet - Current set of node IDs to traverse from
427
- * @param {AdjacencyMaps} params.adjacency - Adjacency maps from materialized state
428
- * @param {[number, number]} params.depth - Tuple of [minDepth, maxDepth]
415
+ * @param {{ direction: 'outgoing' | 'incoming', label: string | undefined, workingSet: string[], adjacency: AdjacencyMaps, depth: [number, number] }} params - Traversal parameters
429
416
  * @returns {string[]} Sorted array of reachable node IDs within the depth range
430
417
  * @private
431
418
  */
@@ -38,15 +38,9 @@ import { decodeEdgeKey, decodePropKey, isEdgePropKey } from './KeyCodec.js';
38
38
 
39
39
  /**
40
40
  * @typedef {Object} StateDiffResult
41
- * @property {Object} nodes - Node changes
42
- * @property {string[]} nodes.added - Added node IDs (sorted)
43
- * @property {string[]} nodes.removed - Removed node IDs (sorted)
44
- * @property {Object} edges - Edge changes
45
- * @property {EdgeChange[]} edges.added - Added edges (sorted)
46
- * @property {EdgeChange[]} edges.removed - Removed edges (sorted)
47
- * @property {Object} props - Property changes
48
- * @property {PropSet[]} props.set - Set/changed properties (sorted)
49
- * @property {PropRemoved[]} props.removed - Removed properties (sorted)
41
+ * @property {{ added: string[], removed: string[] }} nodes - Node changes
42
+ * @property {{ added: EdgeChange[], removed: EdgeChange[] }} edges - Edge changes
43
+ * @property {{ set: PropSet[], removed: PropRemoved[] }} props - Property changes
50
44
  */
51
45
 
52
46
  /**
@@ -74,9 +74,8 @@ export function propVisibleV5(state, propKey) {
74
74
  * Same canonical ordering as v4 for visible projection.
75
75
  *
76
76
  * @param {import('./JoinReducer.js').WarpStateV5} state
77
- * @param {Object} [options]
78
- * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for serialization
79
- * @returns {Buffer|Uint8Array}
77
+ * @param {{ codec?: import('../../ports/CodecPort.js').default }} [options]
78
+ * @returns {Uint8Array}
80
79
  */
81
80
  export function serializeStateV5(state, { codec } = {}) {
82
81
  const c = codec || defaultCodec;
@@ -119,15 +118,17 @@ export function serializeStateV5(state, { codec } = {}) {
119
118
  return c.encode({ nodes, edges: visibleEdges, props: visibleProps });
120
119
  }
121
120
 
121
+ /**
122
+ * @typedef {{ crypto?: import('../../ports/CryptoPort.js').default, codec?: import('../../ports/CodecPort.js').default }} StateHashOptions
123
+ */
124
+
122
125
  /**
123
126
  * Computes SHA-256 hash of canonical state bytes.
124
127
  * @param {import('./JoinReducer.js').WarpStateV5} state
125
- * @param {Object} [options] - Options
126
- * @param {import('../../ports/CryptoPort.js').default} [options.crypto] - CryptoPort instance
127
- * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for serialization
128
+ * @param {StateHashOptions} [options] - Options
128
129
  * @returns {Promise<string>} Hex-encoded SHA-256 hash
129
130
  */
130
- export async function computeStateHashV5(state, { crypto, codec } = /** @type {{crypto?: import('../../ports/CryptoPort.js').default, codec?: import('../../ports/CodecPort.js').default}} */ ({})) {
131
+ export async function computeStateHashV5(state, { crypto, codec } = /** @type {StateHashOptions} */ ({})) {
131
132
  const c = crypto || defaultCrypto;
132
133
  const serialized = serializeStateV5(state, { codec });
133
134
  return await c.hash('sha256', serialized);
@@ -136,9 +137,8 @@ export async function computeStateHashV5(state, { crypto, codec } = /** @type {{
136
137
  /**
137
138
  * Deserializes state from CBOR bytes.
138
139
  * Note: This reconstructs the visible projection only.
139
- * @param {Buffer} buffer
140
- * @param {Object} [options]
141
- * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for deserialization
140
+ * @param {Uint8Array} buffer
141
+ * @param {{ codec?: import('../../ports/CodecPort.js').default }} [options]
142
142
  * @returns {{nodes: string[], edges: Array<{from: string, to: string, label: string}>, props: Array<{node: string, key: string, value: unknown}>}}
143
143
  */
144
144
  export function deserializeStateV5(buffer, { codec } = {}) {
@@ -37,7 +37,7 @@ const BITMAP_BASE_OVERHEAD = 64;
37
37
  * Uses canonical JSON stringification for deterministic output
38
38
  * across different JavaScript engines.
39
39
  *
40
- * @param {Object} data - The data object to checksum
40
+ * @param {Record<string, unknown>} data - The data object to checksum
41
41
  * @param {import('../../ports/CryptoPort.js').default} crypto - CryptoPort instance
42
42
  * @returns {Promise<string>} Hex-encoded SHA-256 hash
43
43
  */
@@ -82,17 +82,7 @@ export default class StreamingBitmapIndexBuilder {
82
82
  /**
83
83
  * Creates a new StreamingBitmapIndexBuilder instance.
84
84
  *
85
- * @param {Object} options - Configuration options
86
- * @param {import('../../ports/IndexStoragePort.js').default} options.storage - Storage adapter implementing IndexStoragePort.
87
- * Required methods: writeBlob, writeTree, readBlob
88
- * @param {number} [options.maxMemoryBytes=52428800] - Maximum bitmap memory before flush (default 50MB).
89
- * Note: SHA→ID mappings are not counted against this limit as they must remain in memory.
90
- * @param {Function} [options.onFlush] - Optional callback invoked on each flush.
91
- * Receives { flushedBytes, totalFlushedBytes, flushCount }.
92
- * @param {import('../../ports/LoggerPort.js').default} [options.logger] - Logger for structured logging.
93
- * Defaults to NoOpLogger (no logging).
94
- * @param {import('../../ports/CryptoPort.js').default} [options.crypto] - CryptoPort instance for hashing
95
- * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for serialization
85
+ * @param {{ storage: import('../../ports/IndexStoragePort.js').default, maxMemoryBytes?: number, onFlush?: (stats: {flushedBytes: number, totalFlushedBytes: number, flushCount: number}) => void, logger?: import('../../ports/LoggerPort.js').default, crypto?: import('../../ports/CryptoPort.js').default, codec?: import('../../ports/CodecPort.js').default }} options - Configuration options
96
86
  */
97
87
  constructor({ storage, maxMemoryBytes = DEFAULT_MAX_MEMORY_BYTES, onFlush, logger = nullLogger, crypto, codec }) {
98
88
  if (!storage) {
@@ -114,7 +104,7 @@ export default class StreamingBitmapIndexBuilder {
114
104
  /** @type {number} */
115
105
  this.maxMemoryBytes = maxMemoryBytes;
116
106
 
117
- /** @type {Function|undefined} */
107
+ /** @type {((stats: {flushedBytes: number, totalFlushedBytes: number, flushCount: number}) => void)|undefined} */
118
108
  this.onFlush = onFlush;
119
109
 
120
110
  /** @type {import('../../ports/LoggerPort.js').default} */
@@ -310,7 +300,7 @@ export default class StreamingBitmapIndexBuilder {
310
300
  * of the SHA. This enables efficient loading of only relevant shards
311
301
  * during index reads.
312
302
  *
313
- * @returns {Object<string, Object<string, number>>} Object mapping 2-char hex prefix
303
+ * @returns {Record<string, Record<string, number>>} Object mapping 2-char hex prefix
314
304
  * to objects of SHA→numeric ID mappings
315
305
  * @private
316
306
  */
@@ -333,7 +323,7 @@ export default class StreamingBitmapIndexBuilder {
333
323
  * Each shard is wrapped in a versioned envelope with checksum before writing.
334
324
  * Writes are performed in parallel using Promise.all for efficiency.
335
325
  *
336
- * @param {Object<string, Object<string, number>>} idShards - Object mapping 2-char hex prefix
326
+ * @param {Record<string, Record<string, number>>} idShards - Object mapping 2-char hex prefix
337
327
  * to objects of SHA→numeric ID mappings
338
328
  * @returns {Promise<string[]>} Array of Git tree entry strings in format
339
329
  * "100644 blob <oid>\tmeta_<prefix>.json"
@@ -365,9 +355,7 @@ export default class StreamingBitmapIndexBuilder {
365
355
  *
366
356
  * Processing is parallelized across shard paths for efficiency.
367
357
  *
368
- * @param {Object} [options] - Options
369
- * @param {AbortSignal} [options.signal] - Optional AbortSignal for cancellation.
370
- * If aborted, throws an error with code 'ABORT_ERR'.
358
+ * @param {{ signal?: AbortSignal }} [options] - Options
371
359
  * @returns {Promise<string[]>} Array of Git tree entry strings in format
372
360
  * "100644 blob <oid>\tshards_<type>_<prefix>.json"
373
361
  * @throws {Error} If the operation is aborted via signal
@@ -409,12 +397,7 @@ export default class StreamingBitmapIndexBuilder {
409
397
  * frontier.json # Optional: JSON-encoded frontier
410
398
  * ```
411
399
  *
412
- * @param {Object} [options] - Finalization options
413
- * @param {AbortSignal} [options.signal] - Optional AbortSignal for cancellation.
414
- * If aborted, throws an error with code 'ABORT_ERR'.
415
- * @param {Map<string, string>} [options.frontier] - Optional writer frontier
416
- * (writerId → tip SHA) for staleness detection. If provided, frontier.cbor and
417
- * frontier.json files are included in the tree.
400
+ * @param {{ signal?: AbortSignal, frontier?: Map<string, string> }} [options] - Finalization options
418
401
  * @returns {Promise<string>} OID of the created Git tree containing the complete index
419
402
  * @throws {Error} If the operation is aborted via signal
420
403
  * @throws {ShardValidationError} If a chunk has an unsupported version during merge
@@ -472,7 +455,7 @@ export default class StreamingBitmapIndexBuilder {
472
455
  * Useful for understanding memory pressure during index building and
473
456
  * tuning the `maxMemoryBytes` threshold.
474
457
  *
475
- * @returns {Object} Memory statistics object
458
+ * @returns {{estimatedBitmapBytes: number, estimatedMappingBytes: number, totalFlushedBytes: number, flushCount: number, nodeCount: number, bitmapCount: number}} Memory statistics object
476
459
  * @property {number} estimatedBitmapBytes - Current estimated size of in-memory bitmaps in bytes.
477
460
  * This is an approximation based on bitmap operations; actual memory usage may vary.
478
461
  * @property {number} estimatedMappingBytes - Estimated size of SHA→ID mappings in bytes.
@@ -527,11 +510,7 @@ export default class StreamingBitmapIndexBuilder {
527
510
  * - New bitmap: adds BITMAP_BASE_OVERHEAD (64 bytes)
528
511
  * - New entry in existing bitmap: adds ~4 bytes (approximation)
529
512
  *
530
- * @param {Object} opts - Options object
531
- * @param {string} opts.sha - The SHA to use as bitmap key (40-character hex string)
532
- * @param {number} opts.id - The numeric ID to add to the bitmap
533
- * @param {'fwd'|'rev'} opts.type - Edge direction type: 'fwd' for forward edges
534
- * (this node's children), 'rev' for reverse edges (this node's parents)
513
+ * @param {{ sha: string, id: number, type: 'fwd'|'rev' }} opts - Options object
535
514
  * @private
536
515
  */
537
516
  _addToBitmap({ sha, id, type }) {
@@ -562,7 +541,7 @@ export default class StreamingBitmapIndexBuilder {
562
541
  * 4. Recomputes and validates checksum (throws ShardCorruptionError if mismatch)
563
542
  *
564
543
  * @param {string} oid - Git blob OID of the chunk to load (40-character hex string)
565
- * @returns {Promise<Object<string, string>>} The validated chunk data (SHA→base64Bitmap mappings)
544
+ * @returns {Promise<Record<string, string>>} The validated chunk data (SHA→base64Bitmap mappings)
566
545
  * @throws {ShardCorruptionError} If the chunk cannot be parsed as JSON or checksum is invalid.
567
546
  * Error context includes: oid, reason ('invalid_format' or 'invalid_checksum'), originalError
568
547
  * @throws {ShardValidationError} If the chunk has an unsupported version.
@@ -574,7 +553,7 @@ export default class StreamingBitmapIndexBuilder {
574
553
  const buffer = await this.storage.readBlob(oid);
575
554
  let envelope;
576
555
  try {
577
- envelope = JSON.parse(buffer.toString('utf-8'));
556
+ envelope = JSON.parse(new TextDecoder().decode(buffer));
578
557
  } catch (err) {
579
558
  throw new ShardCorruptionError('Failed to parse shard JSON', {
580
559
  oid,
@@ -615,12 +594,7 @@ export default class StreamingBitmapIndexBuilder {
615
594
  * is stored directly. If a bitmap already exists, the new bitmap is ORed into
616
595
  * it using `orInPlace` to combine edge sets.
617
596
  *
618
- * @param {Object} opts - Options object
619
- * @param {Record<string, import('../utils/roaring.js').RoaringBitmapSubset>} opts.merged - Object mapping SHA to
620
- * RoaringBitmap32 instances (mutated in place)
621
- * @param {string} opts.sha - The SHA key for this bitmap (40-character hex string)
622
- * @param {string} opts.base64Bitmap - Base64-encoded serialized RoaringBitmap32 data
623
- * @param {string} opts.oid - Git blob OID of the source chunk (for error reporting)
597
+ * @param {{ merged: Record<string, import('../utils/roaring.js').RoaringBitmapSubset>, sha: string, base64Bitmap: string, oid: string }} opts - Options object
624
598
  * @throws {ShardCorruptionError} If the bitmap cannot be deserialized from base64.
625
599
  * Error context includes: oid, reason ('invalid_bitmap'), originalError
626
600
  * @private
@@ -662,9 +636,7 @@ export default class StreamingBitmapIndexBuilder {
662
636
  * Supports cancellation via AbortSignal between chunk processing iterations.
663
637
  *
664
638
  * @param {string[]} oids - Git blob OIDs of chunks to merge (40-character hex strings)
665
- * @param {Object} [options] - Options object
666
- * @param {AbortSignal} [options.signal] - Optional AbortSignal for cancellation.
667
- * Checked between chunk iterations; if aborted, throws with code 'ABORT_ERR'.
639
+ * @param {{ signal?: AbortSignal }} [options] - Options object
668
640
  * @returns {Promise<string>} Git blob OID of the merged shard (40-character hex string)
669
641
  * @throws {Error} If the operation is aborted via signal
670
642
  * @throws {ShardValidationError} If a chunk has an unsupported version.
@@ -38,14 +38,7 @@ export function canonicalizePath(url) {
38
38
  /**
39
39
  * Builds the canonical string that gets signed.
40
40
  *
41
- * @param {Object} params
42
- * @param {string} params.keyId - Key identifier
43
- * @param {string} params.method - HTTP method (uppercased by caller)
44
- * @param {string} params.path - Canonical path
45
- * @param {string} params.timestamp - Epoch milliseconds as string
46
- * @param {string} params.nonce - UUIDv4 nonce
47
- * @param {string} params.contentType - Content-Type header value
48
- * @param {string} params.bodySha256 - Hex SHA-256 of request body
41
+ * @param {{ keyId: string, method: string, path: string, timestamp: string, nonce: string, contentType: string, bodySha256: string }} params
49
42
  * @returns {string} Pipe-delimited canonical payload
50
43
  */
51
44
  export function buildCanonicalPayload({ keyId, method, path, timestamp, nonce, contentType, bodySha256 }) {
@@ -55,15 +48,8 @@ export function buildCanonicalPayload({ keyId, method, path, timestamp, nonce, c
55
48
  /**
56
49
  * Signs an outgoing sync request.
57
50
  *
58
- * @param {Object} params
59
- * @param {string} params.method - HTTP method
60
- * @param {string} params.path - Canonical path
61
- * @param {string} params.contentType - Content-Type header value
62
- * @param {Buffer|Uint8Array} params.body - Raw request body
63
- * @param {string} params.secret - Shared secret
64
- * @param {string} params.keyId - Key identifier
65
- * @param {Object} deps
66
- * @param {import('../../ports/CryptoPort.js').default} [deps.crypto] - Crypto port
51
+ * @param {{ method: string, path: string, contentType: string, body: Buffer|Uint8Array, secret: string, keyId: string }} params
52
+ * @param {{ crypto?: import('../../ports/CryptoPort.js').default }} [deps]
67
53
  * @returns {Promise<Record<string, string>>} Auth headers
68
54
  */
69
55
  export async function signSyncRequest({ method, path, contentType, body, secret, keyId }, { crypto } = {}) {
@@ -173,15 +159,7 @@ function _validateAllowedWriters(allowedWriters) {
173
159
 
174
160
  export default class SyncAuthService {
175
161
  /**
176
- * @param {Object} options
177
- * @param {Record<string, string>} options.keys - Key-id to secret mapping
178
- * @param {'enforce'|'log-only'} [options.mode='enforce'] - Auth enforcement mode
179
- * @param {number} [options.nonceCapacity] - Nonce LRU capacity
180
- * @param {number} [options.maxClockSkewMs] - Max clock skew tolerance
181
- * @param {import('../../ports/CryptoPort.js').default} [options.crypto] - Crypto port
182
- * @param {import('../../ports/LoggerPort.js').default} [options.logger] - Logger port
183
- * @param {() => number} [options.wallClockMs] - Wall clock function
184
- * @param {string[]} [options.allowedWriters] - Optional whitelist of allowed writer IDs. If set, sync requests with unlisted writers are rejected with 403.
162
+ * @param {{ keys: Record<string, string>, mode?: 'enforce'|'log-only', nonceCapacity?: number, maxClockSkewMs?: number, crypto?: import('../../ports/CryptoPort.js').default, logger?: import('../../ports/LoggerPort.js').default, wallClockMs?: () => number, allowedWriters?: string[] }} options
185
163
  */
186
164
  constructor({ keys, mode = 'enforce', nonceCapacity, maxClockSkewMs, crypto, logger, wallClockMs, allowedWriters } = /** @type {{ keys: Record<string, string> }} */ ({})) {
187
165
  _validateKeys(keys);
@@ -289,12 +267,7 @@ export default class SyncAuthService {
289
267
  /**
290
268
  * Verifies the HMAC signature against the canonical payload.
291
269
  *
292
- * @param {Object} params
293
- * @param {{ method: string, url: string, headers: Record<string, string>, body?: Buffer|Uint8Array }} params.request
294
- * @param {string} params.secret
295
- * @param {string} params.keyId
296
- * @param {string} params.timestamp
297
- * @param {string} params.nonce
270
+ * @param {{ request: { method: string, url: string, headers: Record<string, string>, body?: Buffer|Uint8Array }, secret: string, keyId: string, timestamp: string, nonce: string }} params
298
271
  * @returns {Promise<{ ok: false, reason: string, status: number } | { ok: true }>}
299
272
  * @private
300
273
  */
@@ -98,11 +98,7 @@ function normalizeSyncPath(path) {
98
98
  /**
99
99
  * Builds auth headers for an outgoing sync request if auth is configured.
100
100
  *
101
- * @param {Object} params
102
- * @param {{ secret: string, keyId?: string }|undefined} params.auth
103
- * @param {string} params.bodyStr - Serialized request body
104
- * @param {URL} params.targetUrl
105
- * @param {import('../../ports/CryptoPort.js').default} params.crypto
101
+ * @param {{ auth: ({ secret: string, keyId?: string }|undefined), bodyStr: string, targetUrl: URL, crypto: import('../../ports/CryptoPort.js').default }} params
106
102
  * @returns {Promise<Record<string, string>>}
107
103
  */
108
104
  async function buildSyncAuthHeaders({ auth, bodyStr, targetUrl, crypto }) {
@@ -131,8 +127,7 @@ async function buildSyncAuthHeaders({ auth, bodyStr, targetUrl, crypto }) {
131
127
  export default class SyncController {
132
128
  /**
133
129
  * @param {SyncHost} host - The WarpGraph instance (or any object satisfying SyncHost)
134
- * @param {Object} [options]
135
- * @param {SyncTrustGate} [options.trustGate] - Trust gate for evaluating patch authors
130
+ * @param {{ trustGate?: SyncTrustGate }} [options]
136
131
  */
137
132
  constructor(host, options = {}) {
138
133
  /** @type {SyncHost} */
@@ -367,16 +362,7 @@ export default class SyncController {
367
362
  * Syncs with a remote peer (HTTP or direct graph instance).
368
363
  *
369
364
  * @param {string|import('../WarpGraph.js').default} remote - URL or peer graph instance
370
- * @param {Object} [options]
371
- * @param {string} [options.path='/sync'] - Sync path (HTTP mode)
372
- * @param {number} [options.retries=3] - Retry count
373
- * @param {number} [options.baseDelayMs=250] - Base backoff delay
374
- * @param {number} [options.maxDelayMs=2000] - Max backoff delay
375
- * @param {number} [options.timeoutMs=10000] - Request timeout
376
- * @param {AbortSignal} [options.signal] - Abort signal
377
- * @param {(event: {type: string, attempt: number, durationMs?: number, status?: number, error?: Error}) => void} [options.onStatus]
378
- * @param {boolean} [options.materialize=false] - Auto-materialize after sync
379
- * @param {{ secret: string, keyId?: string }} [options.auth] - Client auth credentials
365
+ * @param {{ path?: string, retries?: number, baseDelayMs?: number, maxDelayMs?: number, timeoutMs?: number, signal?: AbortSignal, onStatus?: (event: {type: string, attempt: number, durationMs?: number, status?: number, error?: Error}) => void, materialize?: boolean, auth?: { secret: string, keyId?: string } }} [options]
380
366
  * @returns {Promise<{applied: number, attempts: number, skippedWriters: Array<{writerId: string, reason: string, localSha: string, remoteSha: string|null}>, state?: import('./JoinReducer.js').WarpStateV5}>}
381
367
  */
382
368
  async syncWith(remote, options = {}) {
@@ -583,13 +569,7 @@ export default class SyncController {
583
569
  /**
584
570
  * Starts a built-in sync server for this graph.
585
571
  *
586
- * @param {Object} options
587
- * @param {number} options.port - Port to listen on
588
- * @param {string} [options.host='127.0.0.1'] - Host to bind
589
- * @param {string} [options.path='/sync'] - Path to handle sync requests
590
- * @param {number} [options.maxRequestBytes=4194304] - Max request size in bytes
591
- * @param {import('../../ports/HttpServerPort.js').default} options.httpPort - HTTP server adapter
592
- * @param {{ keys: Record<string, string>, mode?: 'enforce'|'log-only' }} [options.auth] - Auth configuration
572
+ * @param {{ port: number, host?: string, path?: string, maxRequestBytes?: number, httpPort: import('../../ports/HttpServerPort.js').default, auth?: { keys: Record<string, string>, mode?: 'enforce'|'log-only' } }} options
593
573
  * @returns {Promise<{close: () => Promise<void>, url: string}>} Server handle
594
574
  * @throws {Error} If port is not a number
595
575
  * @throws {Error} If httpPort adapter is not provided
@@ -608,7 +588,7 @@ export default class SyncController {
608
588
 
609
589
  const httpServer = new HttpSyncServer({
610
590
  httpPort,
611
- graph: /** @type {{ processSyncRequest: Function }} */ (/** @type {unknown} */ (this._host)),
591
+ graph: /** @type {{ processSyncRequest: (req: import('./SyncProtocol.js').SyncRequest) => Promise<unknown> }} */ (/** @type {unknown} */ (this._host)),
612
592
  path,
613
593
  host,
614
594
  maxRequestBytes,
@@ -125,8 +125,7 @@ function objectToFrontier(obj) {
125
125
  * @param {import('../../ports/GraphPersistencePort.js').default & import('../../ports/CommitPort.js').default & import('../../ports/BlobPort.js').default} persistence - Git persistence layer
126
126
  * (uses CommitPort.showNode() + BlobPort.readBlob() methods)
127
127
  * @param {string} sha - The 40-character commit SHA to load the patch from
128
- * @param {Object} [options]
129
- * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for deserialization
128
+ * @param {{ codec?: import('../../ports/CodecPort.js').default }} [options]
130
129
  * @returns {Promise<DecodedPatch>} The decoded and normalized patch object containing:
131
130
  * - `ops`: Array of patch operations
132
131
  * - `context`: VersionVector (Map) of causal dependencies
@@ -173,8 +172,7 @@ async function loadPatchFromCommit(persistence, sha, { codec: codecOpt } = /** @
173
172
  * @param {string|null} fromSha - Start SHA (exclusive). Pass null to load ALL patches
174
173
  * for this writer from the beginning of their chain.
175
174
  * @param {string} toSha - End SHA (inclusive). This is typically the writer's current tip.
176
- * @param {Object} [options]
177
- * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for deserialization
175
+ * @param {{ codec?: import('../../ports/CodecPort.js').default }} [options]
178
176
  * @returns {Promise<Array<{patch: DecodedPatch, sha: string}>>} Array of patch objects in
179
177
  * chronological order (oldest first). Each entry contains:
180
178
  * - `patch`: The decoded patch object
@@ -396,9 +394,7 @@ export function createSyncRequest(frontier) {
396
394
  * @param {import('../../ports/GraphPersistencePort.js').default & import('../../ports/CommitPort.js').default & import('../../ports/BlobPort.js').default} persistence - Git persistence
397
395
  * layer for loading patches (uses CommitPort + BlobPort methods)
398
396
  * @param {string} graphName - Graph name for error messages and logging
399
- * @param {Object} [options]
400
- * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for deserialization
401
- * @param {import('../../ports/LoggerPort.js').default} [options.logger] - Logger for divergence warnings
397
+ * @param {{ codec?: import('../../ports/CodecPort.js').default, logger?: import('../../ports/LoggerPort.js').default }} [options]
402
398
  * @returns {Promise<SyncResponse>} Response containing local frontier and patches.
403
399
  * Patches are ordered chronologically within each writer.
404
400
  * @throws {Error} If patch loading fails for reasons other than divergence
@@ -518,7 +514,7 @@ export async function processSyncRequest(request, localFrontier, persistence, gr
518
514
  * @param {import('./JoinReducer.js').WarpStateV5} state - Current CRDT state
519
515
  * (nodeAlive, edgeAlive, prop, observedFrontier)
520
516
  * @param {Map<string, string>} frontier - Current frontier mapping writer IDs to SHAs
521
- * @returns {Object} Result containing:
517
+ * @returns {{state: import('./JoinReducer.js').WarpStateV5, frontier: Map<string, string>, applied: number}} Result containing:
522
518
  * - `state`: New WarpStateV5 with patches applied
523
519
  * - `frontier`: New frontier with updated writer tips
524
520
  * - `applied`: Number of patches successfully applied
@@ -30,10 +30,7 @@ const PASS = () => ({ allowed: true, untrustedWriters: [], verdict: 'pass' });
30
30
 
31
31
  export default class SyncTrustGate {
32
32
  /**
33
- * @param {Object} options
34
- * @param {{evaluateWriters: (writerIds: string[]) => Promise<{trusted: Set<string>}>}} [options.trustEvaluator] - Trust evaluator instance
35
- * @param {TrustMode} [options.trustMode='off'] - Trust enforcement mode
36
- * @param {import('../../ports/LoggerPort.js').default} [options.logger] - Logger
33
+ * @param {{ trustEvaluator?: {evaluateWriters: (writerIds: string[]) => Promise<{trusted: Set<string>}>}, trustMode?: TrustMode, logger?: import('../../ports/LoggerPort.js').default }} [options]
37
34
  */
38
35
  constructor({ trustEvaluator, trustMode = 'off', logger } = {}) {
39
36
  this._evaluator = trustEvaluator || null;
@@ -45,9 +42,7 @@ export default class SyncTrustGate {
45
42
  * Evaluates whether the given patch writers are trusted.
46
43
  *
47
44
  * @param {string[]} writerIds - Writer IDs from patches being applied
48
- * @param {Object} [context] - Additional context for logging
49
- * @param {string} [context.graphName] - Graph name
50
- * @param {string} [context.peerId] - Remote peer identity (if authenticated)
45
+ * @param {{ graphName?: string, peerId?: string }} [context] - Additional context for logging
51
46
  * @returns {Promise<TrustGateResult>}
52
47
  */
53
48
  async evaluate(writerIds, context = {}) {
@@ -71,7 +66,7 @@ export default class SyncTrustGate {
71
66
  * Decides the gate result based on untrusted writers and mode.
72
67
  * @param {string[]} untrusted
73
68
  * @param {string[]} writerIds
74
- * @param {Object} context
69
+ * @param {Record<string, unknown>} context
75
70
  * @returns {TrustGateResult}
76
71
  * @private
77
72
  */
@@ -110,7 +105,7 @@ export default class SyncTrustGate {
110
105
  * Handles trust evaluation errors with fail-open/fail-closed semantics.
111
106
  * @param {unknown} err
112
107
  * @param {string[]} writerIds
113
- * @param {Object} context
108
+ * @param {Record<string, unknown>} context
114
109
  * @returns {TrustGateResult}
115
110
  * @private
116
111
  */
@@ -86,12 +86,7 @@ function extractNodeSnapshot(state, nodeId) {
86
86
  /**
87
87
  * Evaluates checkpoint boundary semantics for `always()`.
88
88
  *
89
- * @param {Object} params
90
- * @param {import('./JoinReducer.js').WarpStateV5} params.state
91
- * @param {string} params.nodeId
92
- * @param {Function} params.predicate
93
- * @param {number|null} params.checkpointMaxLamport
94
- * @param {number} params.since
89
+ * @param {{ state: import('./JoinReducer.js').WarpStateV5, nodeId: string, predicate: (snapshot: {id: string, exists: boolean, props: Record<string, unknown>}) => boolean, checkpointMaxLamport: number|null, since: number }} params
95
90
  * @returns {{ nodeEverExisted: boolean, shouldReturn: boolean, returnValue: boolean }}
96
91
  * @private
97
92
  */
@@ -118,12 +113,7 @@ function evaluateAlwaysCheckpointBoundary({
118
113
  /**
119
114
  * Evaluates checkpoint boundary semantics for `eventually()`.
120
115
  *
121
- * @param {Object} params
122
- * @param {import('./JoinReducer.js').WarpStateV5} params.state
123
- * @param {string} params.nodeId
124
- * @param {Function} params.predicate
125
- * @param {number|null} params.checkpointMaxLamport
126
- * @param {number} params.since
116
+ * @param {{ state: import('./JoinReducer.js').WarpStateV5, nodeId: string, predicate: (snapshot: {id: string, exists: boolean, props: Record<string, unknown>}) => boolean, checkpointMaxLamport: number|null, since: number }} params
127
117
  * @returns {boolean}
128
118
  * @private
129
119
  */
@@ -149,16 +139,12 @@ function evaluateEventuallyCheckpointBoundary({
149
139
  */
150
140
  export class TemporalQuery {
151
141
  /**
152
- * @param {Object} options
153
- * @param {Function} options.loadAllPatches - Async function that returns
154
- * all patches as Array<{ patch, sha }> in causal order.
155
- * @param {Function} [options.loadCheckpoint] - Async function returning
156
- * { state: WarpStateV5, maxLamport: number } or null.
142
+ * @param {{ loadAllPatches: () => Promise<Array<{patch: import('../types/WarpTypesV2.js').PatchV2, sha: string}>>, loadCheckpoint?: () => Promise<{state: import('./JoinReducer.js').WarpStateV5, maxLamport: number}|null> }} options
157
143
  */
158
144
  constructor({ loadAllPatches, loadCheckpoint }) {
159
- /** @type {Function} */
145
+ /** @type {() => Promise<Array<{patch: import('../types/WarpTypesV2.js').PatchV2, sha: string}>>} */
160
146
  this._loadAllPatches = loadAllPatches;
161
- /** @type {Function|null} */
147
+ /** @type {(() => Promise<{state: import('./JoinReducer.js').WarpStateV5, maxLamport: number}|null>)|null} */
162
148
  this._loadCheckpoint = loadCheckpoint || null;
163
149
  }
164
150
 
@@ -172,11 +158,9 @@ export class TemporalQuery {
172
158
  * Returns false if the node never existed in the range.
173
159
  *
174
160
  * @param {string} nodeId - The node ID to evaluate
175
- * @param {Function} predicate - Predicate receiving node snapshot
161
+ * @param {(snapshot: {id: string, exists: boolean, props: Record<string, unknown>}) => boolean} predicate - Predicate receiving node snapshot
176
162
  * `{ id, exists, props }`. Should return boolean.
177
- * @param {Object} [options={}] - Options
178
- * @param {number} [options.since=0] - Minimum Lamport tick (inclusive).
179
- * Only patches with lamport >= since are considered.
163
+ * @param {{ since?: number }} [options={}] - Options
180
164
  * @returns {Promise<boolean>} True if predicate held at every tick
181
165
  *
182
166
  * @example
@@ -232,11 +216,9 @@ export class TemporalQuery {
232
216
  * soon as the predicate returns true at any tick.
233
217
  *
234
218
  * @param {string} nodeId - The node ID to evaluate
235
- * @param {Function} predicate - Predicate receiving node snapshot
219
+ * @param {(snapshot: {id: string, exists: boolean, props: Record<string, unknown>}) => boolean} predicate - Predicate receiving node snapshot
236
220
  * `{ id, exists, props }`. Should return boolean.
237
- * @param {Object} [options={}] - Options
238
- * @param {number} [options.since=0] - Minimum Lamport tick (inclusive).
239
- * Only patches with lamport >= since are considered.
221
+ * @param {{ since?: number }} [options={}] - Options
240
222
  * @returns {Promise<boolean>} True if predicate held at any tick
241
223
  *
242
224
  * @example
@@ -169,14 +169,8 @@ function computePropLoss(state, { nodesA, nodesBSet, configA, configB }) {
169
169
  * The cost measures how much information is lost when translating from
170
170
  * A's view to B's view. It is asymmetric: cost(A->B) != cost(B->A) in general.
171
171
  *
172
- * @param {Object} configA - Observer configuration for A
173
- * @param {string|string[]} configA.match - Glob pattern(s) for visible nodes
174
- * @param {string[]} [configA.expose] - Property keys to include
175
- * @param {string[]} [configA.redact] - Property keys to exclude
176
- * @param {Object} configB - Observer configuration for B
177
- * @param {string|string[]} configB.match - Glob pattern(s) for visible nodes
178
- * @param {string[]} [configB.expose] - Property keys to include
179
- * @param {string[]} [configB.redact] - Property keys to exclude
172
+ * @param {{ match: string|string[], expose?: string[], redact?: string[] }} configA - Observer configuration for A
173
+ * @param {{ match: string|string[], expose?: string[], redact?: string[] }} configB - Observer configuration for B
180
174
  * @param {WarpStateV5} state - WarpStateV5 materialized state
181
175
  * @returns {{ cost: number, breakdown: { nodeLoss: number, edgeLoss: number, propLoss: number } }}
182
176
  */
@@ -30,8 +30,7 @@ import { decodeEdgeKey } from './KeyCodec.js';
30
30
  export default class WarpStateIndexBuilder {
31
31
  /**
32
32
  * Creates a new WarpStateIndexBuilder.
33
- * @param {Object} [options] - Configuration
34
- * @param {import('../../ports/CryptoPort.js').default} [options.crypto] - CryptoPort for shard checksums
33
+ * @param {{ crypto?: import('../../ports/CryptoPort.js').default }} [options] - Configuration
35
34
  */
36
35
  constructor({ crypto } = {}) {
37
36
  /** @type {BitmapIndexBuilder} */
@@ -109,8 +108,7 @@ export default class WarpStateIndexBuilder {
109
108
  * Convenience function to build and serialize a WARP state index.
110
109
  *
111
110
  * @param {import('./JoinReducer.js').WarpStateV5} state - The materialized state
112
- * @param {Object} [options] - Configuration
113
- * @param {import('../../ports/CryptoPort.js').default} [options.crypto] - CryptoPort for shard checksums
111
+ * @param {{ crypto?: import('../../ports/CryptoPort.js').default }} [options] - Configuration
114
112
  * @returns {Promise<{tree: Record<string, Buffer>, stats: {nodes: number, edges: number}}>} Serialized index and stats
115
113
  *
116
114
  * @example