@git-stunts/git-warp 10.3.2 → 10.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/README.md +6 -3
  2. package/SECURITY.md +89 -1
  3. package/bin/warp-graph.js +574 -208
  4. package/index.d.ts +55 -0
  5. package/index.js +4 -0
  6. package/package.json +8 -4
  7. package/src/domain/WarpGraph.js +334 -161
  8. package/src/domain/crdt/LWW.js +1 -1
  9. package/src/domain/crdt/ORSet.js +10 -6
  10. package/src/domain/crdt/VersionVector.js +5 -1
  11. package/src/domain/errors/EmptyMessageError.js +2 -4
  12. package/src/domain/errors/ForkError.js +4 -0
  13. package/src/domain/errors/IndexError.js +4 -0
  14. package/src/domain/errors/OperationAbortedError.js +4 -0
  15. package/src/domain/errors/QueryError.js +4 -0
  16. package/src/domain/errors/SchemaUnsupportedError.js +4 -0
  17. package/src/domain/errors/ShardCorruptionError.js +2 -6
  18. package/src/domain/errors/ShardLoadError.js +2 -6
  19. package/src/domain/errors/ShardValidationError.js +2 -7
  20. package/src/domain/errors/StorageError.js +2 -6
  21. package/src/domain/errors/SyncError.js +4 -0
  22. package/src/domain/errors/TraversalError.js +4 -0
  23. package/src/domain/errors/WarpError.js +2 -4
  24. package/src/domain/errors/WormholeError.js +4 -0
  25. package/src/domain/services/AnchorMessageCodec.js +1 -4
  26. package/src/domain/services/BitmapIndexBuilder.js +10 -6
  27. package/src/domain/services/BitmapIndexReader.js +27 -21
  28. package/src/domain/services/BoundaryTransitionRecord.js +22 -15
  29. package/src/domain/services/CheckpointMessageCodec.js +1 -7
  30. package/src/domain/services/CheckpointSerializerV5.js +20 -19
  31. package/src/domain/services/CheckpointService.js +18 -18
  32. package/src/domain/services/CommitDagTraversalService.js +13 -1
  33. package/src/domain/services/DagPathFinding.js +40 -18
  34. package/src/domain/services/DagTopology.js +7 -6
  35. package/src/domain/services/DagTraversal.js +5 -3
  36. package/src/domain/services/Frontier.js +7 -6
  37. package/src/domain/services/HealthCheckService.js +15 -14
  38. package/src/domain/services/HookInstaller.js +64 -13
  39. package/src/domain/services/HttpSyncServer.js +88 -19
  40. package/src/domain/services/IndexRebuildService.js +12 -12
  41. package/src/domain/services/IndexStalenessChecker.js +13 -6
  42. package/src/domain/services/JoinReducer.js +28 -27
  43. package/src/domain/services/LogicalTraversal.js +7 -6
  44. package/src/domain/services/MessageCodecInternal.js +2 -0
  45. package/src/domain/services/ObserverView.js +6 -6
  46. package/src/domain/services/PatchBuilderV2.js +9 -9
  47. package/src/domain/services/PatchMessageCodec.js +1 -7
  48. package/src/domain/services/ProvenanceIndex.js +6 -8
  49. package/src/domain/services/ProvenancePayload.js +1 -2
  50. package/src/domain/services/QueryBuilder.js +29 -23
  51. package/src/domain/services/StateDiff.js +7 -7
  52. package/src/domain/services/StateSerializerV5.js +8 -6
  53. package/src/domain/services/StreamingBitmapIndexBuilder.js +29 -23
  54. package/src/domain/services/SyncAuthService.js +396 -0
  55. package/src/domain/services/SyncProtocol.js +23 -26
  56. package/src/domain/services/TemporalQuery.js +4 -3
  57. package/src/domain/services/TranslationCost.js +4 -4
  58. package/src/domain/services/WormholeService.js +19 -15
  59. package/src/domain/types/TickReceipt.js +10 -6
  60. package/src/domain/types/WarpTypesV2.js +2 -3
  61. package/src/domain/utils/CachedValue.js +1 -1
  62. package/src/domain/utils/LRUCache.js +3 -3
  63. package/src/domain/utils/MinHeap.js +2 -2
  64. package/src/domain/utils/RefLayout.js +19 -0
  65. package/src/domain/utils/WriterId.js +2 -2
  66. package/src/domain/utils/defaultCodec.js +9 -2
  67. package/src/domain/utils/defaultCrypto.js +36 -0
  68. package/src/domain/utils/roaring.js +5 -5
  69. package/src/domain/utils/seekCacheKey.js +32 -0
  70. package/src/domain/warp/PatchSession.js +3 -3
  71. package/src/domain/warp/Writer.js +2 -2
  72. package/src/infrastructure/adapters/BunHttpAdapter.js +21 -8
  73. package/src/infrastructure/adapters/CasSeekCacheAdapter.js +311 -0
  74. package/src/infrastructure/adapters/ClockAdapter.js +2 -2
  75. package/src/infrastructure/adapters/DenoHttpAdapter.js +22 -9
  76. package/src/infrastructure/adapters/GitGraphAdapter.js +25 -83
  77. package/src/infrastructure/adapters/InMemoryGraphAdapter.js +488 -0
  78. package/src/infrastructure/adapters/NodeCryptoAdapter.js +16 -3
  79. package/src/infrastructure/adapters/NodeHttpAdapter.js +33 -11
  80. package/src/infrastructure/adapters/WebCryptoAdapter.js +21 -11
  81. package/src/infrastructure/adapters/adapterValidation.js +90 -0
  82. package/src/infrastructure/codecs/CborCodec.js +16 -8
  83. package/src/ports/BlobPort.js +2 -2
  84. package/src/ports/CodecPort.js +2 -2
  85. package/src/ports/CommitPort.js +8 -21
  86. package/src/ports/ConfigPort.js +3 -3
  87. package/src/ports/CryptoPort.js +7 -7
  88. package/src/ports/GraphPersistencePort.js +12 -14
  89. package/src/ports/HttpServerPort.js +1 -5
  90. package/src/ports/IndexStoragePort.js +1 -0
  91. package/src/ports/LoggerPort.js +9 -9
  92. package/src/ports/RefPort.js +5 -5
  93. package/src/ports/SeekCachePort.js +73 -0
  94. package/src/ports/TreePort.js +3 -3
  95. package/src/visualization/layouts/converters.js +14 -7
  96. package/src/visualization/layouts/elkAdapter.js +17 -4
  97. package/src/visualization/layouts/elkLayout.js +23 -7
  98. package/src/visualization/layouts/index.js +3 -3
  99. package/src/visualization/renderers/ascii/check.js +30 -17
  100. package/src/visualization/renderers/ascii/graph.js +92 -1
  101. package/src/visualization/renderers/ascii/history.js +28 -26
  102. package/src/visualization/renderers/ascii/info.js +9 -7
  103. package/src/visualization/renderers/ascii/materialize.js +20 -16
  104. package/src/visualization/renderers/ascii/opSummary.js +15 -7
  105. package/src/visualization/renderers/ascii/path.js +1 -1
  106. package/src/visualization/renderers/ascii/seek.js +187 -23
  107. package/src/visualization/renderers/ascii/table.js +1 -1
  108. package/src/visualization/renderers/svg/index.js +5 -1
@@ -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 {Array<{label: string, to: string}>} edgesOut - Outgoing edges sorted by label then target
16
- * @property {Array<{label: string, from: string}>} edgesIn - Incoming edges sorted by label then source
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 (spec[key] !== undefined && typeof spec[key] !== 'string') {
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 spec[key] },
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 }) => op.fn(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) => spec[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: spec[key].replace(/^props\./, '').split('.'),
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 {Object} a
108
- * @param {Object} b
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 = [];
@@ -1,4 +1,5 @@
1
1
  import defaultCodec from '../utils/defaultCodec.js';
2
+ import defaultCrypto from '../utils/defaultCrypto.js';
2
3
  import { orsetContains, orsetElements } from '../crdt/ORSet.js';
3
4
  import { decodeEdgeKey, decodePropKey } from './KeyCodec.js';
4
5
 
@@ -75,7 +76,7 @@ export function propVisibleV5(state, propKey) {
75
76
  * @param {import('./JoinReducer.js').WarpStateV5} state
76
77
  * @param {Object} [options]
77
78
  * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for serialization
78
- * @returns {Buffer}
79
+ * @returns {Buffer|Uint8Array}
79
80
  */
80
81
  export function serializeStateV5(state, { codec } = {}) {
81
82
  const c = codec || defaultCodec;
@@ -122,13 +123,14 @@ export function serializeStateV5(state, { codec } = {}) {
122
123
  * Computes SHA-256 hash of canonical state bytes.
123
124
  * @param {import('./JoinReducer.js').WarpStateV5} state
124
125
  * @param {Object} [options] - Options
125
- * @param {import('../../ports/CryptoPort.js').default} options.crypto - CryptoPort instance
126
+ * @param {import('../../ports/CryptoPort.js').default} [options.crypto] - CryptoPort instance
126
127
  * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for serialization
127
- * @returns {Promise<string|null>} Hex-encoded SHA-256 hash, or null if no crypto
128
+ * @returns {Promise<string>} Hex-encoded SHA-256 hash
128
129
  */
129
- export async function computeStateHashV5(state, { crypto, codec } = {}) {
130
+ export async function computeStateHashV5(state, { crypto, codec } = /** @type {{crypto?: import('../../ports/CryptoPort.js').default, codec?: import('../../ports/CodecPort.js').default}} */ ({})) {
131
+ const c = crypto || defaultCrypto;
130
132
  const serialized = serializeStateV5(state, { codec });
131
- return crypto ? await crypto.hash('sha256', serialized) : null;
133
+ return await c.hash('sha256', serialized);
132
134
  }
133
135
 
134
136
  /**
@@ -141,7 +143,7 @@ export async function computeStateHashV5(state, { crypto, codec } = {}) {
141
143
  */
142
144
  export function deserializeStateV5(buffer, { codec } = {}) {
143
145
  const c = codec || defaultCodec;
144
- return c.decode(buffer);
146
+ return /** @type {{nodes: string[], edges: Array<{from: string, to: string, label: string}>, props: Array<{node: string, key: string, value: *}>}} */ (c.decode(buffer));
145
147
  }
146
148
 
147
149
  // ============================================================================
@@ -1,4 +1,5 @@
1
1
  import defaultCodec from '../utils/defaultCodec.js';
2
+ import defaultCrypto from '../utils/defaultCrypto.js';
2
3
  import ShardCorruptionError from '../errors/ShardCorruptionError.js';
3
4
  import ShardValidationError from '../errors/ShardValidationError.js';
4
5
  import nullLogger from '../utils/nullLogger.js';
@@ -36,10 +37,9 @@ const BITMAP_BASE_OVERHEAD = 64;
36
37
  *
37
38
  * @param {Object} data - The data object to checksum
38
39
  * @param {import('../../ports/CryptoPort.js').default} crypto - CryptoPort instance
39
- * @returns {Promise<string|null>} Hex-encoded SHA-256 hash
40
+ * @returns {Promise<string>} Hex-encoded SHA-256 hash
40
41
  */
41
42
  const computeChecksum = async (data, crypto) => {
42
- if (!crypto) { return null; }
43
43
  const json = canonicalStringify(data);
44
44
  return await crypto.hash('sha256', json);
45
45
  };
@@ -89,6 +89,8 @@ export default class StreamingBitmapIndexBuilder {
89
89
  * Receives { flushedBytes, totalFlushedBytes, flushCount }.
90
90
  * @param {import('../../ports/LoggerPort.js').default} [options.logger] - Logger for structured logging.
91
91
  * Defaults to NoOpLogger (no logging).
92
+ * @param {import('../../ports/CryptoPort.js').default} [options.crypto] - CryptoPort instance for hashing
93
+ * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for serialization
92
94
  */
93
95
  constructor({ storage, maxMemoryBytes = DEFAULT_MAX_MEMORY_BYTES, onFlush, logger = nullLogger, crypto, codec }) {
94
96
  if (!storage) {
@@ -99,9 +101,9 @@ export default class StreamingBitmapIndexBuilder {
99
101
  }
100
102
 
101
103
  /** @type {import('../../ports/CryptoPort.js').default} */
102
- this._crypto = crypto;
104
+ this._crypto = crypto || defaultCrypto;
103
105
 
104
- /** @type {import('../../ports/CodecPort.js').default|undefined} */
106
+ /** @type {import('../../ports/CodecPort.js').default} */
105
107
  this._codec = codec || defaultCodec;
106
108
 
107
109
  /** @type {Object} */
@@ -122,7 +124,7 @@ export default class StreamingBitmapIndexBuilder {
122
124
  /** @type {string[]} ID → SHA reverse mapping (kept in memory) */
123
125
  this.idToSha = [];
124
126
 
125
- /** @type {Map<string, RoaringBitmap32>} Current in-memory bitmaps */
127
+ /** @type {Map<string, any>} Current in-memory bitmaps */
126
128
  this.bitmaps = new Map();
127
129
 
128
130
  /** @type {number} Estimated bytes used by current bitmaps */
@@ -137,8 +139,8 @@ export default class StreamingBitmapIndexBuilder {
137
139
  /** @type {number} Number of flush operations performed */
138
140
  this.flushCount = 0;
139
141
 
140
- /** @type {typeof import('roaring').RoaringBitmap32} Cached constructor */
141
- this._RoaringBitmap32 = getRoaringBitmap32();
142
+ /** @type {any} Cached Roaring bitmap constructor */ // TODO(ts-cleanup): type lazy singleton
143
+ this._RoaringBitmap32 = getRoaringBitmap32(); // TODO(ts-cleanup): type lazy singleton
142
144
  }
143
145
 
144
146
  /**
@@ -189,11 +191,12 @@ export default class StreamingBitmapIndexBuilder {
189
191
  * Groups bitmaps by type ('fwd' or 'rev') and SHA prefix (first 2 hex chars).
190
192
  * Each bitmap is serialized to a portable format and base64-encoded.
191
193
  *
192
- * @returns {{fwd: Object<string, Object<string, string>>, rev: Object<string, Object<string, string>>}}
194
+ * @returns {Record<string, Record<string, Record<string, string>>>}
193
195
  * Object with 'fwd' and 'rev' keys, each mapping prefix to SHA→base64Bitmap entries
194
196
  * @private
195
197
  */
196
198
  _serializeBitmapsToShards() {
199
+ /** @type {Record<string, Record<string, Record<string, string>>>} */
197
200
  const bitmapShards = { fwd: {}, rev: {} };
198
201
  for (const [key, bitmap] of this.bitmaps) {
199
202
  const type = key.substring(0, 3);
@@ -215,7 +218,7 @@ export default class StreamingBitmapIndexBuilder {
215
218
  * The resulting blob OIDs are tracked in `flushedChunks` for later merging.
216
219
  * Writes are performed in parallel for efficiency.
217
220
  *
218
- * @param {{fwd: Object<string, Object<string, string>>, rev: Object<string, Object<string, string>>}} bitmapShards
221
+ * @param {Record<string, Record<string, Record<string, string>>>} bitmapShards
219
222
  * Object with 'fwd' and 'rev' keys containing prefix-grouped bitmap data
220
223
  * @returns {Promise<void>} Resolves when all shards have been written
221
224
  * @async
@@ -235,11 +238,11 @@ export default class StreamingBitmapIndexBuilder {
235
238
  data: shardData,
236
239
  };
237
240
  const buffer = Buffer.from(JSON.stringify(envelope));
238
- const oid = await this.storage.writeBlob(buffer);
241
+ const oid = await /** @type {any} */ (this.storage).writeBlob(buffer); // TODO(ts-cleanup): narrow port type
239
242
  if (!this.flushedChunks.has(path)) {
240
243
  this.flushedChunks.set(path, []);
241
244
  }
242
- this.flushedChunks.get(path).push(oid);
245
+ /** @type {string[]} */ (this.flushedChunks.get(path)).push(oid);
243
246
  })
244
247
  );
245
248
  }
@@ -310,6 +313,7 @@ export default class StreamingBitmapIndexBuilder {
310
313
  * @private
311
314
  */
312
315
  _buildMetaShards() {
316
+ /** @type {Record<string, Record<string, number>>} */
313
317
  const idShards = {};
314
318
  for (const [sha, id] of this.shaToId) {
315
319
  const prefix = sha.substring(0, 2);
@@ -344,7 +348,7 @@ export default class StreamingBitmapIndexBuilder {
344
348
  data: map,
345
349
  };
346
350
  const buffer = Buffer.from(JSON.stringify(envelope));
347
- const oid = await this.storage.writeBlob(buffer);
351
+ const oid = await /** @type {any} */ (this.storage).writeBlob(buffer); // TODO(ts-cleanup): narrow port type
348
352
  return `100644 blob ${oid}\t${path}`;
349
353
  })
350
354
  );
@@ -436,18 +440,19 @@ export default class StreamingBitmapIndexBuilder {
436
440
 
437
441
  // Store frontier metadata for staleness detection
438
442
  if (frontier) {
443
+ /** @type {Record<string, number|undefined>} */
439
444
  const sorted = {};
440
445
  for (const key of Array.from(frontier.keys()).sort()) {
441
446
  sorted[key] = frontier.get(key);
442
447
  }
443
448
  const envelope = { version: 1, writerCount: frontier.size, frontier: sorted };
444
- const cborOid = await this.storage.writeBlob(Buffer.from(this._codec.encode(envelope)));
449
+ const cborOid = await /** @type {any} */ (this.storage).writeBlob(Buffer.from(/** @type {any} */ (this._codec).encode(envelope))); // TODO(ts-cleanup): narrow port type
445
450
  flatEntries.push(`100644 blob ${cborOid}\tfrontier.cbor`);
446
- const jsonOid = await this.storage.writeBlob(Buffer.from(canonicalStringify(envelope)));
451
+ const jsonOid = await /** @type {any} */ (this.storage).writeBlob(Buffer.from(canonicalStringify(envelope))); // TODO(ts-cleanup): narrow port type
447
452
  flatEntries.push(`100644 blob ${jsonOid}\tfrontier.json`);
448
453
  }
449
454
 
450
- const treeOid = await this.storage.writeTree(flatEntries);
455
+ const treeOid = await /** @type {any} */ (this.storage).writeTree(flatEntries); // TODO(ts-cleanup): narrow port type
451
456
 
452
457
  this.logger.debug('Index finalized', {
453
458
  operation: 'finalize',
@@ -501,7 +506,7 @@ export default class StreamingBitmapIndexBuilder {
501
506
  */
502
507
  _getOrCreateId(sha) {
503
508
  if (this.shaToId.has(sha)) {
504
- return this.shaToId.get(sha);
509
+ return /** @type {number} */ (this.shaToId.get(sha));
505
510
  }
506
511
  const id = this.idToSha.length;
507
512
  this.idToSha.push(sha);
@@ -564,7 +569,7 @@ export default class StreamingBitmapIndexBuilder {
564
569
  * @private
565
570
  */
566
571
  async _loadAndValidateChunk(oid) {
567
- const buffer = await this.storage.readBlob(oid);
572
+ const buffer = await /** @type {any} */ (this.storage).readBlob(oid); // TODO(ts-cleanup): narrow port type
568
573
  let envelope;
569
574
  try {
570
575
  envelope = JSON.parse(buffer.toString('utf-8'));
@@ -572,14 +577,13 @@ export default class StreamingBitmapIndexBuilder {
572
577
  throw new ShardCorruptionError('Failed to parse shard JSON', {
573
578
  oid,
574
579
  reason: 'invalid_format',
575
- originalError: err.message,
580
+ context: { originalError: /** @type {any} */ (err).message }, // TODO(ts-cleanup): type error
576
581
  });
577
582
  }
578
583
 
579
584
  // Validate version
580
585
  if (envelope.version !== SHARD_VERSION) {
581
586
  throw new ShardValidationError('Shard version mismatch', {
582
- oid,
583
587
  expected: SHARD_VERSION,
584
588
  actual: envelope.version,
585
589
  field: 'version',
@@ -610,7 +614,7 @@ export default class StreamingBitmapIndexBuilder {
610
614
  * it using `orInPlace` to combine edge sets.
611
615
  *
612
616
  * @param {Object} opts - Options object
613
- * @param {Object<string, RoaringBitmap32>} opts.merged - Object mapping SHA to
617
+ * @param {Record<string, any>} opts.merged - Object mapping SHA to
614
618
  * RoaringBitmap32 instances (mutated in place)
615
619
  * @param {string} opts.sha - The SHA key for this bitmap (40-character hex string)
616
620
  * @param {string} opts.base64Bitmap - Base64-encoded serialized RoaringBitmap32 data
@@ -627,7 +631,7 @@ export default class StreamingBitmapIndexBuilder {
627
631
  throw new ShardCorruptionError('Failed to deserialize bitmap', {
628
632
  oid,
629
633
  reason: 'invalid_bitmap',
630
- originalError: err.message,
634
+ context: { originalError: /** @type {any} */ (err).message }, // TODO(ts-cleanup): type error
631
635
  });
632
636
  }
633
637
 
@@ -671,6 +675,7 @@ export default class StreamingBitmapIndexBuilder {
671
675
  */
672
676
  async _mergeChunks(oids, { signal } = {}) {
673
677
  // Load all chunks and merge bitmaps by SHA
678
+ /** @type {Record<string, any>} */
674
679
  const merged = {};
675
680
 
676
681
  for (const oid of oids) {
@@ -683,6 +688,7 @@ export default class StreamingBitmapIndexBuilder {
683
688
  }
684
689
 
685
690
  // Serialize merged result
691
+ /** @type {Record<string, string>} */
686
692
  const result = {};
687
693
  for (const [sha, bitmap] of Object.entries(merged)) {
688
694
  result[sha] = bitmap.serialize(true).toString('base64');
@@ -701,9 +707,9 @@ export default class StreamingBitmapIndexBuilder {
701
707
  } catch (err) {
702
708
  throw new ShardCorruptionError('Failed to serialize merged shard', {
703
709
  reason: 'serialization_error',
704
- originalError: err.message,
710
+ context: { originalError: /** @type {any} */ (err).message }, // TODO(ts-cleanup): type error
705
711
  });
706
712
  }
707
- return this.storage.writeBlob(serialized);
713
+ return /** @type {any} */ (this.storage).writeBlob(serialized); // TODO(ts-cleanup): narrow port type
708
714
  }
709
715
  }