@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.
- package/README.md +6 -3
- package/SECURITY.md +89 -1
- package/bin/warp-graph.js +574 -208
- package/index.d.ts +55 -0
- package/index.js +4 -0
- package/package.json +8 -4
- package/src/domain/WarpGraph.js +334 -161
- package/src/domain/crdt/LWW.js +1 -1
- package/src/domain/crdt/ORSet.js +10 -6
- package/src/domain/crdt/VersionVector.js +5 -1
- package/src/domain/errors/EmptyMessageError.js +2 -4
- package/src/domain/errors/ForkError.js +4 -0
- package/src/domain/errors/IndexError.js +4 -0
- package/src/domain/errors/OperationAbortedError.js +4 -0
- package/src/domain/errors/QueryError.js +4 -0
- package/src/domain/errors/SchemaUnsupportedError.js +4 -0
- package/src/domain/errors/ShardCorruptionError.js +2 -6
- package/src/domain/errors/ShardLoadError.js +2 -6
- package/src/domain/errors/ShardValidationError.js +2 -7
- package/src/domain/errors/StorageError.js +2 -6
- package/src/domain/errors/SyncError.js +4 -0
- package/src/domain/errors/TraversalError.js +4 -0
- package/src/domain/errors/WarpError.js +2 -4
- package/src/domain/errors/WormholeError.js +4 -0
- package/src/domain/services/AnchorMessageCodec.js +1 -4
- package/src/domain/services/BitmapIndexBuilder.js +10 -6
- package/src/domain/services/BitmapIndexReader.js +27 -21
- package/src/domain/services/BoundaryTransitionRecord.js +22 -15
- package/src/domain/services/CheckpointMessageCodec.js +1 -7
- package/src/domain/services/CheckpointSerializerV5.js +20 -19
- package/src/domain/services/CheckpointService.js +18 -18
- package/src/domain/services/CommitDagTraversalService.js +13 -1
- package/src/domain/services/DagPathFinding.js +40 -18
- package/src/domain/services/DagTopology.js +7 -6
- package/src/domain/services/DagTraversal.js +5 -3
- package/src/domain/services/Frontier.js +7 -6
- package/src/domain/services/HealthCheckService.js +15 -14
- package/src/domain/services/HookInstaller.js +64 -13
- package/src/domain/services/HttpSyncServer.js +88 -19
- package/src/domain/services/IndexRebuildService.js +12 -12
- package/src/domain/services/IndexStalenessChecker.js +13 -6
- package/src/domain/services/JoinReducer.js +28 -27
- package/src/domain/services/LogicalTraversal.js +7 -6
- package/src/domain/services/MessageCodecInternal.js +2 -0
- package/src/domain/services/ObserverView.js +6 -6
- package/src/domain/services/PatchBuilderV2.js +9 -9
- package/src/domain/services/PatchMessageCodec.js +1 -7
- package/src/domain/services/ProvenanceIndex.js +6 -8
- package/src/domain/services/ProvenancePayload.js +1 -2
- package/src/domain/services/QueryBuilder.js +29 -23
- package/src/domain/services/StateDiff.js +7 -7
- package/src/domain/services/StateSerializerV5.js +8 -6
- package/src/domain/services/StreamingBitmapIndexBuilder.js +29 -23
- package/src/domain/services/SyncAuthService.js +396 -0
- package/src/domain/services/SyncProtocol.js +23 -26
- package/src/domain/services/TemporalQuery.js +4 -3
- package/src/domain/services/TranslationCost.js +4 -4
- package/src/domain/services/WormholeService.js +19 -15
- package/src/domain/types/TickReceipt.js +10 -6
- package/src/domain/types/WarpTypesV2.js +2 -3
- package/src/domain/utils/CachedValue.js +1 -1
- package/src/domain/utils/LRUCache.js +3 -3
- package/src/domain/utils/MinHeap.js +2 -2
- package/src/domain/utils/RefLayout.js +19 -0
- package/src/domain/utils/WriterId.js +2 -2
- package/src/domain/utils/defaultCodec.js +9 -2
- package/src/domain/utils/defaultCrypto.js +36 -0
- package/src/domain/utils/roaring.js +5 -5
- package/src/domain/utils/seekCacheKey.js +32 -0
- package/src/domain/warp/PatchSession.js +3 -3
- package/src/domain/warp/Writer.js +2 -2
- package/src/infrastructure/adapters/BunHttpAdapter.js +21 -8
- package/src/infrastructure/adapters/CasSeekCacheAdapter.js +311 -0
- package/src/infrastructure/adapters/ClockAdapter.js +2 -2
- package/src/infrastructure/adapters/DenoHttpAdapter.js +22 -9
- package/src/infrastructure/adapters/GitGraphAdapter.js +25 -83
- package/src/infrastructure/adapters/InMemoryGraphAdapter.js +488 -0
- package/src/infrastructure/adapters/NodeCryptoAdapter.js +16 -3
- package/src/infrastructure/adapters/NodeHttpAdapter.js +33 -11
- package/src/infrastructure/adapters/WebCryptoAdapter.js +21 -11
- package/src/infrastructure/adapters/adapterValidation.js +90 -0
- package/src/infrastructure/codecs/CborCodec.js +16 -8
- package/src/ports/BlobPort.js +2 -2
- package/src/ports/CodecPort.js +2 -2
- package/src/ports/CommitPort.js +8 -21
- package/src/ports/ConfigPort.js +3 -3
- package/src/ports/CryptoPort.js +7 -7
- package/src/ports/GraphPersistencePort.js +12 -14
- package/src/ports/HttpServerPort.js +1 -5
- package/src/ports/IndexStoragePort.js +1 -0
- package/src/ports/LoggerPort.js +9 -9
- package/src/ports/RefPort.js +5 -5
- package/src/ports/SeekCachePort.js +73 -0
- package/src/ports/TreePort.js +3 -3
- package/src/visualization/layouts/converters.js +14 -7
- package/src/visualization/layouts/elkAdapter.js +17 -4
- package/src/visualization/layouts/elkLayout.js +23 -7
- package/src/visualization/layouts/index.js +3 -3
- package/src/visualization/renderers/ascii/check.js +30 -17
- package/src/visualization/renderers/ascii/graph.js +92 -1
- package/src/visualization/renderers/ascii/history.js +28 -26
- package/src/visualization/renderers/ascii/info.js +9 -7
- package/src/visualization/renderers/ascii/materialize.js +20 -16
- package/src/visualization/renderers/ascii/opSummary.js +15 -7
- package/src/visualization/renderers/ascii/path.js +1 -1
- package/src/visualization/renderers/ascii/seek.js +187 -23
- package/src/visualization/renderers/ascii/table.js +1 -1
- package/src/visualization/renderers/svg/index.js +5 -1
package/src/domain/crdt/LWW.js
CHANGED
package/src/domain/crdt/ORSet.js
CHANGED
|
@@ -118,11 +118,13 @@ export function createORSet() {
|
|
|
118
118
|
export function orsetAdd(set, element, dot) {
|
|
119
119
|
const encoded = encodeDot(dot);
|
|
120
120
|
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
230
|
-
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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 {
|
|
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 {
|
|
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<
|
|
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 (
|
|
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 {
|
|
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:
|
|
313
|
-
expected:
|
|
314
|
-
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 {
|
|
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 {
|
|
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<
|
|
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)
|