@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
@@ -131,7 +131,7 @@ export function lwwMax(a, b) {
131
131
  return null;
132
132
  }
133
133
  if (a === null || a === undefined) {
134
- return b;
134
+ return /** @type {LWWRegister<T>} */ (b);
135
135
  }
136
136
  if (b === null || b === undefined) {
137
137
  return a;
@@ -118,11 +118,13 @@ export function createORSet() {
118
118
  export function orsetAdd(set, element, dot) {
119
119
  const encoded = encodeDot(dot);
120
120
 
121
- if (!set.entries.has(element)) {
122
- set.entries.set(element, new Set());
121
+ let dots = set.entries.get(element);
122
+ if (!dots) {
123
+ dots = new Set();
124
+ set.entries.set(element, dots);
123
125
  }
124
126
 
125
- set.entries.get(element).add(encoded);
127
+ dots.add(encoded);
126
128
  }
127
129
 
128
130
  /**
@@ -226,10 +228,11 @@ export function orsetJoin(a, b) {
226
228
 
227
229
  // Union entries from b
228
230
  for (const [element, dots] of b.entries) {
229
- if (!result.entries.has(element)) {
230
- result.entries.set(element, new Set());
231
+ let resultDots = result.entries.get(element);
232
+ if (!resultDots) {
233
+ resultDots = new Set();
234
+ result.entries.set(element, resultDots);
231
235
  }
232
- const resultDots = result.entries.get(element);
233
236
  for (const dot of dots) {
234
237
  resultDots.add(dot);
235
238
  }
@@ -312,6 +315,7 @@ export function orsetCompact(set, includedVV) {
312
315
  */
313
316
  export function orsetSerialize(set) {
314
317
  // Serialize entries: convert Map to array of [element, sortedDots]
318
+ /** @type {Array<[any, string[]]>} */
315
319
  const entriesArray = [];
316
320
  for (const [element, dots] of set.entries) {
317
321
  const sortedDots = [...dots].sort((a, b) => {
@@ -158,11 +158,15 @@ export function vvContains(vv, dot) {
158
158
  * @returns {Object<string, number>}
159
159
  */
160
160
  export function vvSerialize(vv) {
161
+ /** @type {Record<string, number>} */
161
162
  const obj = {};
162
163
  const sortedKeys = [...vv.keys()].sort();
163
164
 
164
165
  for (const key of sortedKeys) {
165
- obj[key] = vv.get(key);
166
+ const val = vv.get(key);
167
+ if (val !== undefined) {
168
+ obj[key] = val;
169
+ }
166
170
  }
167
171
 
168
172
  return obj;
@@ -12,7 +12,7 @@ import IndexError from './IndexError.js';
12
12
  * @property {string} name - The error name ('EmptyMessageError')
13
13
  * @property {string} code - Error code ('EMPTY_MESSAGE')
14
14
  * @property {string} operation - The operation that failed due to empty message
15
- * @property {Object} context - Serializable context object for debugging
15
+ * @property {Record<string, *>} context - Serializable context object for debugging
16
16
  *
17
17
  * @example
18
18
  * if (!message || message.trim() === '') {
@@ -27,9 +27,7 @@ export default class EmptyMessageError extends IndexError {
27
27
  * Creates a new EmptyMessageError.
28
28
  *
29
29
  * @param {string} message - Human-readable error message
30
- * @param {Object} [options={}] - Error options
31
- * @param {string} [options.operation] - The operation that failed
32
- * @param {Object} [options.context={}] - Additional context for debugging
30
+ * @param {{ operation?: string, context?: Record<string, *> }} [options={}] - Error options
33
31
  */
34
32
  constructor(message, options = {}) {
35
33
  const context = {
@@ -24,6 +24,10 @@ import WarpError from './WarpError.js';
24
24
  * @property {Object} context - Serializable context object with error details
25
25
  */
26
26
  export default class ForkError extends WarpError {
27
+ /**
28
+ * @param {string} message
29
+ * @param {{ code?: string, context?: Object }} [options={}]
30
+ */
27
31
  constructor(message, options = {}) {
28
32
  super(message, 'FORK_ERROR', options);
29
33
  }
@@ -17,6 +17,10 @@ import WarpError from './WarpError.js';
17
17
  * });
18
18
  */
19
19
  export default class IndexError extends WarpError {
20
+ /**
21
+ * @param {string} message
22
+ * @param {{ code?: string, context?: Object }} [options={}]
23
+ */
20
24
  constructor(message, options = {}) {
21
25
  super(message, 'INDEX_ERROR', options);
22
26
  }
@@ -13,6 +13,10 @@ import WarpError from './WarpError.js';
13
13
  * @property {Object} context - Serializable context object for debugging
14
14
  */
15
15
  export default class OperationAbortedError extends WarpError {
16
+ /**
17
+ * @param {string} operation
18
+ * @param {{ code?: string, context?: Object, reason?: string }} [options={}]
19
+ */
16
20
  constructor(operation, options = {}) {
17
21
  const reason = options.reason || 'Operation was aborted';
18
22
  super(`Operation '${operation}' aborted: ${reason}`, 'OPERATION_ABORTED', options);
@@ -33,6 +33,10 @@ import WarpError from './WarpError.js';
33
33
  * @property {Object} context - Serializable context object with error details
34
34
  */
35
35
  export default class QueryError extends WarpError {
36
+ /**
37
+ * @param {string} message
38
+ * @param {{ code?: string, context?: Object }} [options={}]
39
+ */
36
40
  constructor(message, options = {}) {
37
41
  super(message, 'QUERY_ERROR', options);
38
42
  }
@@ -11,6 +11,10 @@ import WarpError from './WarpError.js';
11
11
  * @property {Object} context - Serializable context object for debugging
12
12
  */
13
13
  export default class SchemaUnsupportedError extends WarpError {
14
+ /**
15
+ * @param {string} message
16
+ * @param {{ code?: string, context?: Object }} [options={}]
17
+ */
14
18
  constructor(message, options = {}) {
15
19
  super(message, 'E_SCHEMA_UNSUPPORTED', options);
16
20
  }
@@ -14,7 +14,7 @@ import IndexError from './IndexError.js';
14
14
  * @property {string} shardPath - Path to the corrupted shard file
15
15
  * @property {string} oid - Object ID associated with the shard
16
16
  * @property {string} reason - Reason for corruption (e.g., 'invalid_checksum', 'invalid_version', 'parse_error')
17
- * @property {Object} context - Serializable context object for debugging
17
+ * @property {Record<string, *>} context - Serializable context object for debugging
18
18
  *
19
19
  * @example
20
20
  * if (!validateChecksum(data)) {
@@ -30,11 +30,7 @@ export default class ShardCorruptionError extends IndexError {
30
30
  * Creates a new ShardCorruptionError.
31
31
  *
32
32
  * @param {string} message - Human-readable error message
33
- * @param {Object} [options={}] - Error options
34
- * @param {string} [options.shardPath] - Path to the corrupted shard file
35
- * @param {string} [options.oid] - Object ID associated with the shard
36
- * @param {string} [options.reason] - Reason for corruption (e.g., 'invalid_checksum', 'invalid_version', 'parse_error')
37
- * @param {Object} [options.context={}] - Additional context for debugging
33
+ * @param {{ shardPath?: string, oid?: string, reason?: string, context?: Record<string, *> }} [options={}] - Error options
38
34
  */
39
35
  constructor(message, options = {}) {
40
36
  const context = {
@@ -14,7 +14,7 @@ import IndexError from './IndexError.js';
14
14
  * @property {string} shardPath - Path to the shard file that failed to load
15
15
  * @property {string} oid - Object ID associated with the shard
16
16
  * @property {Error} cause - The original error that caused the load failure
17
- * @property {Object} context - Serializable context object for debugging
17
+ * @property {Record<string, *>} context - Serializable context object for debugging
18
18
  *
19
19
  * @example
20
20
  * try {
@@ -32,11 +32,7 @@ export default class ShardLoadError extends IndexError {
32
32
  * Creates a new ShardLoadError.
33
33
  *
34
34
  * @param {string} message - Human-readable error message
35
- * @param {Object} [options={}] - Error options
36
- * @param {string} [options.shardPath] - Path to the shard file
37
- * @param {string} [options.oid] - Object ID associated with the shard
38
- * @param {Error} [options.cause] - The original error that caused the failure
39
- * @param {Object} [options.context={}] - Additional context for debugging
35
+ * @param {{ shardPath?: string, oid?: string, cause?: Error, context?: Record<string, *> }} [options={}] - Error options
40
36
  */
41
37
  constructor(message, options = {}) {
42
38
  const context = {
@@ -15,7 +15,7 @@ import IndexError from './IndexError.js';
15
15
  * @property {*} expected - The expected value for the field
16
16
  * @property {*} actual - The actual value found in the shard
17
17
  * @property {string} field - The field that failed validation (e.g., 'checksum', 'version')
18
- * @property {Object} context - Serializable context object for debugging
18
+ * @property {Record<string, *>} context - Serializable context object for debugging
19
19
  *
20
20
  * @example
21
21
  * if (shard.version !== EXPECTED_VERSION) {
@@ -32,12 +32,7 @@ export default class ShardValidationError extends IndexError {
32
32
  * Creates a new ShardValidationError.
33
33
  *
34
34
  * @param {string} message - Human-readable error message
35
- * @param {Object} [options={}] - Error options
36
- * @param {string} [options.shardPath] - Path to the shard file
37
- * @param {*} [options.expected] - The expected value
38
- * @param {*} [options.actual] - The actual value found
39
- * @param {string} [options.field] - The field that failed validation (e.g., 'checksum', 'version')
40
- * @param {Object} [options.context={}] - Additional context for debugging
35
+ * @param {{ shardPath?: string, expected?: *, actual?: *, field?: string, context?: Record<string, *> }} [options={}] - Error options
41
36
  */
42
37
  constructor(message, options = {}) {
43
38
  const context = {
@@ -14,7 +14,7 @@ import IndexError from './IndexError.js';
14
14
  * @property {string} operation - The operation that failed ('read' or 'write')
15
15
  * @property {string} oid - Object ID associated with the operation
16
16
  * @property {Error} cause - The original error that caused the failure
17
- * @property {Object} context - Serializable context object for debugging
17
+ * @property {Record<string, *>} context - Serializable context object for debugging
18
18
  *
19
19
  * @example
20
20
  * try {
@@ -32,11 +32,7 @@ export default class StorageError extends IndexError {
32
32
  * Creates a new StorageError.
33
33
  *
34
34
  * @param {string} message - Human-readable error message
35
- * @param {Object} [options={}] - Error options
36
- * @param {string} [options.operation] - The operation that failed ('read' or 'write')
37
- * @param {string} [options.oid] - Object ID associated with the operation
38
- * @param {Error} [options.cause] - The original error that caused the failure
39
- * @param {Object} [options.context={}] - Additional context for debugging
35
+ * @param {{ operation?: string, oid?: string, cause?: Error, context?: Record<string, *> }} [options={}] - Error options
40
36
  */
41
37
  constructor(message, options = {}) {
42
38
  const context = {
@@ -24,6 +24,10 @@ import WarpError from './WarpError.js';
24
24
  * @property {Object} context - Serializable context object with error details
25
25
  */
26
26
  export default class SyncError extends WarpError {
27
+ /**
28
+ * @param {string} message
29
+ * @param {{ code?: string, context?: Object }} [options={}]
30
+ */
27
31
  constructor(message, options = {}) {
28
32
  super(message, 'SYNC_ERROR', options);
29
33
  }
@@ -17,6 +17,10 @@ import WarpError from './WarpError.js';
17
17
  * });
18
18
  */
19
19
  export default class TraversalError extends WarpError {
20
+ /**
21
+ * @param {string} message
22
+ * @param {{ code?: string, context?: Object }} [options={}]
23
+ */
20
24
  constructor(message, options = {}) {
21
25
  super(message, 'TRAVERSAL_ERROR', options);
22
26
  }
@@ -10,15 +10,13 @@
10
10
  *
11
11
  * @property {string} name - Error name (set from constructor.name)
12
12
  * @property {string} code - Machine-readable error code
13
- * @property {Object} context - Serializable context for debugging
13
+ * @property {Record<string, *>} context - Serializable context for debugging
14
14
  */
15
15
  export default class WarpError extends Error {
16
16
  /**
17
17
  * @param {string} message - Human-readable error message
18
18
  * @param {string} defaultCode - Default error code if not overridden by options
19
- * @param {Object} [options={}] - Error options
20
- * @param {string} [options.code] - Override error code
21
- * @param {Object} [options.context={}] - Serializable context for debugging
19
+ * @param {{ code?: string, context?: Record<string, *> }} [options={}] - Error options
22
20
  */
23
21
  constructor(message, defaultCode, options = {}) {
24
22
  super(message);
@@ -22,6 +22,10 @@ import WarpError from './WarpError.js';
22
22
  * @property {Object} context - Serializable context object with error details
23
23
  */
24
24
  export default class WormholeError extends WarpError {
25
+ /**
26
+ * @param {string} message
27
+ * @param {{ code?: string, context?: Object }} [options={}]
28
+ */
25
29
  constructor(message, options = {}) {
26
30
  super(message, 'WORMHOLE_ERROR', options);
27
31
  }
@@ -56,10 +56,7 @@ export function encodeAnchorMessage({ graph, schema = 2 }) {
56
56
  * Decodes an anchor commit message.
57
57
  *
58
58
  * @param {string} message - The raw commit message
59
- * @returns {Object} The decoded anchor message
60
- * @returns {string} return.kind - Always 'anchor'
61
- * @returns {string} return.graph - The graph name
62
- * @returns {number} return.schema - The schema version
59
+ * @returns {{ kind: 'anchor', graph: string, schema: number }} The decoded anchor message
63
60
  * @throws {Error} If the message is not a valid anchor message
64
61
  *
65
62
  * @example
@@ -1,4 +1,5 @@
1
1
  import defaultCodec from '../utils/defaultCodec.js';
2
+ import defaultCrypto from '../utils/defaultCrypto.js';
2
3
  import { getRoaringBitmap32, getNativeRoaringAvailable } from '../utils/roaring.js';
3
4
  import { canonicalStringify } from '../utils/canonicalStringify.js';
4
5
  import { SHARD_VERSION } from '../utils/shardVersion.js';
@@ -13,10 +14,9 @@ export { SHARD_VERSION };
13
14
  *
14
15
  * @param {Object} data - The data object to checksum
15
16
  * @param {import('../../ports/CryptoPort.js').default} crypto - CryptoPort instance
16
- * @returns {Promise<string|null>} Hex-encoded SHA-256 hash
17
+ * @returns {Promise<string>} Hex-encoded SHA-256 hash
17
18
  */
18
19
  const computeChecksum = async (data, crypto) => {
19
- if (!crypto) { return null; }
20
20
  const json = canonicalStringify(data);
21
21
  return await crypto.hash('sha256', json);
22
22
  };
@@ -51,6 +51,7 @@ const wrapShard = async (data, crypto) => ({
51
51
  * @param {import('../../ports/CodecPort.js').default} codec - Codec for CBOR serialization
52
52
  */
53
53
  function serializeFrontierToTree(frontier, tree, codec) {
54
+ /** @type {Record<string, string|undefined>} */
54
55
  const sorted = {};
55
56
  for (const key of Array.from(frontier.keys()).sort()) {
56
57
  sorted[key] = frontier.get(key);
@@ -95,14 +96,14 @@ export default class BitmapIndexBuilder {
95
96
  */
96
97
  constructor({ crypto, codec } = {}) {
97
98
  /** @type {import('../../ports/CryptoPort.js').default} */
98
- this._crypto = crypto;
99
- /** @type {import('../../ports/CodecPort.js').default|undefined} */
99
+ this._crypto = crypto || defaultCrypto;
100
+ /** @type {import('../../ports/CodecPort.js').default} */
100
101
  this._codec = codec || defaultCodec;
101
102
  /** @type {Map<string, number>} */
102
103
  this.shaToId = new Map();
103
104
  /** @type {string[]} */
104
105
  this.idToSha = [];
105
- /** @type {Map<string, RoaringBitmap32>} */
106
+ /** @type {Map<string, any>} */
106
107
  this.bitmaps = new Map();
107
108
  }
108
109
 
@@ -148,9 +149,11 @@ export default class BitmapIndexBuilder {
148
149
  * @returns {Promise<Record<string, Buffer>>} Map of path → serialized content
149
150
  */
150
151
  async serialize({ frontier } = {}) {
152
+ /** @type {Record<string, Buffer>} */
151
153
  const tree = {};
152
154
 
153
155
  // Serialize ID mappings (sharded by prefix)
156
+ /** @type {Record<string, Record<string, number>>} */
154
157
  const idShards = {};
155
158
  for (const [sha, id] of this.shaToId) {
156
159
  const prefix = sha.substring(0, 2);
@@ -165,6 +168,7 @@ export default class BitmapIndexBuilder {
165
168
 
166
169
  // Serialize bitmaps (sharded by prefix, per-node within shard)
167
170
  // Keys are constructed as '${type}_${sha}' by _addToBitmap (e.g., 'fwd_abc123', 'rev_def456')
171
+ /** @type {Record<string, Record<string, Record<string, string>>>} */
168
172
  const bitmapShards = { fwd: {}, rev: {} };
169
173
  for (const [key, bitmap] of this.bitmaps) {
170
174
  const [type, sha] = [key.substring(0, 3), key.substring(4)];
@@ -198,7 +202,7 @@ export default class BitmapIndexBuilder {
198
202
  */
199
203
  _getOrCreateId(sha) {
200
204
  if (this.shaToId.has(sha)) {
201
- return this.shaToId.get(sha);
205
+ return /** @type {number} */ (this.shaToId.get(sha));
202
206
  }
203
207
  const id = this.idToSha.length;
204
208
  this.idToSha.push(sha);
@@ -1,9 +1,14 @@
1
1
  import { ShardLoadError, ShardCorruptionError, ShardValidationError } from '../errors/index.js';
2
+ import defaultCrypto from '../utils/defaultCrypto.js';
2
3
  import nullLogger from '../utils/nullLogger.js';
3
4
  import LRUCache from '../utils/LRUCache.js';
4
5
  import { getRoaringBitmap32 } from '../utils/roaring.js';
5
6
  import { canonicalStringify } from '../utils/canonicalStringify.js';
6
7
 
8
+ /** @typedef {import('../../ports/IndexStoragePort.js').default} IndexStoragePort */
9
+ /** @typedef {import('../../ports/LoggerPort.js').default} LoggerPort */
10
+ /** @typedef {import('../../ports/CryptoPort.js').default} CryptoPort */
11
+
7
12
  /**
8
13
  * Supported shard format versions for backward compatibility.
9
14
  * Version 1: Original format using JSON.stringify for checksums
@@ -25,10 +30,9 @@ const DEFAULT_MAX_CACHED_SHARDS = 100;
25
30
  * @param {Object} data - The data object to checksum
26
31
  * @param {number} version - Shard version (1 uses JSON.stringify, 2+ uses canonicalStringify)
27
32
  * @param {import('../../ports/CryptoPort.js').default} crypto - CryptoPort instance
28
- * @returns {Promise<string|null>} Hex-encoded SHA-256 hash
33
+ * @returns {Promise<string>} Hex-encoded SHA-256 hash
29
34
  */
30
35
  const computeChecksum = async (data, version, crypto) => {
31
- if (!crypto) { return null; }
32
36
  const json = version === 1 ? JSON.stringify(data) : canonicalStringify(data);
33
37
  return await crypto.hash('sha256', json);
34
38
  };
@@ -77,16 +81,15 @@ export default class BitmapIndexReader {
77
81
  /**
78
82
  * Creates a BitmapIndexReader instance.
79
83
  * @param {Object} options
80
- * @param {import('../../ports/IndexStoragePort.js').default} options.storage - Storage adapter for reading index data
84
+ * @param {IndexStoragePort} options.storage - Storage adapter for reading index data
81
85
  * @param {boolean} [options.strict=false] - If true, throw errors on validation failures; if false, log warnings and return empty shards
82
86
  * @param {import('../../ports/LoggerPort.js').default} [options.logger] - Logger for structured logging.
83
87
  * Defaults to NoOpLogger (no logging).
84
88
  * @param {number} [options.maxCachedShards=100] - Maximum number of shards to keep in the LRU cache.
85
89
  * When exceeded, least recently used shards are evicted to free memory.
86
90
  * @param {import('../../ports/CryptoPort.js').default} [options.crypto] - CryptoPort instance for checksum verification.
87
- * When not provided, checksum validation is skipped.
88
91
  */
89
- constructor({ storage, strict = false, logger = nullLogger, maxCachedShards = DEFAULT_MAX_CACHED_SHARDS, crypto } = {}) {
92
+ constructor({ storage, strict = false, logger = nullLogger, maxCachedShards = DEFAULT_MAX_CACHED_SHARDS, crypto } = /** @type {*} */ ({})) { // TODO(ts-cleanup): needs options type
90
93
  if (!storage) {
91
94
  throw new Error('BitmapIndexReader requires a storage adapter');
92
95
  }
@@ -95,9 +98,10 @@ export default class BitmapIndexReader {
95
98
  this.logger = logger;
96
99
  this.maxCachedShards = maxCachedShards;
97
100
  /** @type {import('../../ports/CryptoPort.js').default} */
98
- this._crypto = crypto;
101
+ this._crypto = crypto || defaultCrypto;
99
102
  this.shardOids = new Map(); // path -> OID
100
103
  this.loadedShards = new LRUCache(maxCachedShards); // path -> Data
104
+ /** @type {string[]|null} */
101
105
  this._idToShaCache = null; // Lazy-built reverse mapping
102
106
  }
103
107
 
@@ -191,7 +195,7 @@ export default class BitmapIndexReader {
191
195
  shardPath,
192
196
  oid: this.shardOids.get(shardPath),
193
197
  reason: 'bitmap_deserialize_error',
194
- originalError: err.message,
198
+ context: { originalError: /** @type {any} */ (err).message }, // TODO(ts-cleanup): type error
195
199
  });
196
200
  this._handleShardError(corruptionError, {
197
201
  path: shardPath,
@@ -243,10 +247,10 @@ export default class BitmapIndexReader {
243
247
  /**
244
248
  * Validates a shard envelope for version and checksum integrity.
245
249
  *
246
- * @param {Object} envelope - The shard envelope to validate
250
+ * @param {{ data?: any, version?: number, checksum?: string }} envelope - The shard envelope to validate
247
251
  * @param {string} path - Shard path (for error context)
248
252
  * @param {string} oid - Object ID (for error context)
249
- * @returns {Promise<Object>} The validated data from the envelope
253
+ * @returns {Promise<any>} The validated data from the envelope
250
254
  * @throws {ShardCorruptionError} If envelope format is invalid
251
255
  * @throws {ShardValidationError} If version or checksum validation fails
252
256
  * @private
@@ -267,7 +271,7 @@ export default class BitmapIndexReader {
267
271
  reason: 'missing_or_invalid_data',
268
272
  });
269
273
  }
270
- if (!SUPPORTED_SHARD_VERSIONS.includes(envelope.version)) {
274
+ if (!SUPPORTED_SHARD_VERSIONS.includes(/** @type {number} */ (envelope.version))) {
271
275
  throw new ShardValidationError('Unsupported version', {
272
276
  shardPath: path,
273
277
  expected: SUPPORTED_SHARD_VERSIONS,
@@ -276,8 +280,8 @@ export default class BitmapIndexReader {
276
280
  });
277
281
  }
278
282
  // Use version-appropriate checksum computation for backward compatibility
279
- const actualChecksum = await computeChecksum(envelope.data, envelope.version, this._crypto);
280
- if (actualChecksum !== null && envelope.checksum !== actualChecksum) {
283
+ const actualChecksum = await computeChecksum(envelope.data, /** @type {number} */ (envelope.version), this._crypto);
284
+ if (envelope.checksum !== actualChecksum) {
281
285
  throw new ShardValidationError('Checksum mismatch', {
282
286
  shardPath: path,
283
287
  expected: envelope.checksum,
@@ -295,7 +299,7 @@ export default class BitmapIndexReader {
295
299
  * @param {string} context.path - Shard path
296
300
  * @param {string} context.oid - Object ID
297
301
  * @param {string} context.format - 'json' or 'bitmap'
298
- * @returns {Object|RoaringBitmap32} Empty shard (non-strict mode only)
302
+ * @returns {any} Empty shard (non-strict mode only)
299
303
  * @throws {ShardCorruptionError|ShardValidationError} In strict mode
300
304
  * @private
301
305
  */
@@ -303,15 +307,17 @@ export default class BitmapIndexReader {
303
307
  if (this.strict) {
304
308
  throw err;
305
309
  }
310
+ /** @type {any} */ // TODO(ts-cleanup): type lazy singleton
311
+ const errAny = err;
306
312
  this.logger.warn('Shard validation warning', {
307
313
  operation: 'loadShard',
308
314
  shardPath: path,
309
315
  oid,
310
316
  error: err.message,
311
317
  code: err.code,
312
- field: err.field,
313
- expected: err.expected,
314
- actual: err.actual,
318
+ field: errAny.field,
319
+ expected: errAny.expected,
320
+ actual: errAny.actual,
315
321
  });
316
322
  const emptyShard = format === 'json' ? {} : new (getRoaringBitmap32())();
317
323
  this.loadedShards.set(path, emptyShard);
@@ -343,12 +349,12 @@ export default class BitmapIndexReader {
343
349
  */
344
350
  async _loadShardBuffer(path, oid) {
345
351
  try {
346
- return await this.storage.readBlob(oid);
352
+ return await /** @type {any} */ (this.storage).readBlob(oid); // TODO(ts-cleanup): narrow port type
347
353
  } catch (cause) {
348
354
  throw new ShardLoadError('Failed to load shard from storage', {
349
355
  shardPath: path,
350
356
  oid,
351
- cause,
357
+ cause: /** @type {Error} */ (cause),
352
358
  });
353
359
  }
354
360
  }
@@ -376,12 +382,12 @@ export default class BitmapIndexReader {
376
382
  /**
377
383
  * Attempts to handle a shard error based on its type.
378
384
  * Returns handled result for validation/corruption errors, null otherwise.
379
- * @param {Error} err - The error to handle
385
+ * @param {any} err - The error to handle
380
386
  * @param {Object} context - Error context
381
387
  * @param {string} context.path - Shard path
382
388
  * @param {string} context.oid - Object ID
383
389
  * @param {string} context.format - 'json' or 'bitmap'
384
- * @returns {Object|RoaringBitmap32|null} Handled result or null if error should be re-thrown
390
+ * @returns {any} Handled result or null if error should be re-thrown
385
391
  * @private
386
392
  */
387
393
  _tryHandleShardError(err, context) {
@@ -400,7 +406,7 @@ export default class BitmapIndexReader {
400
406
  *
401
407
  * @param {string} path - Shard path
402
408
  * @param {string} format - 'json' or 'bitmap'
403
- * @returns {Promise<Object|RoaringBitmap32>}
409
+ * @returns {Promise<any>}
404
410
  * @throws {ShardLoadError} When storage.readBlob fails
405
411
  * @throws {ShardCorruptionError} When shard format is invalid (strict mode only)
406
412
  * @throws {ShardValidationError} When version or checksum validation fails (strict mode only)