@git-stunts/git-warp 10.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +16 -0
- package/README.md +480 -0
- package/SECURITY.md +30 -0
- package/bin/git-warp +24 -0
- package/bin/warp-graph.js +1574 -0
- package/index.d.ts +2366 -0
- package/index.js +180 -0
- package/package.json +129 -0
- package/scripts/install-git-warp.sh +258 -0
- package/scripts/uninstall-git-warp.sh +139 -0
- package/src/domain/WarpGraph.js +3157 -0
- package/src/domain/crdt/Dot.js +160 -0
- package/src/domain/crdt/LWW.js +154 -0
- package/src/domain/crdt/ORSet.js +371 -0
- package/src/domain/crdt/VersionVector.js +222 -0
- package/src/domain/entities/GraphNode.js +60 -0
- package/src/domain/errors/EmptyMessageError.js +47 -0
- package/src/domain/errors/ForkError.js +30 -0
- package/src/domain/errors/IndexError.js +23 -0
- package/src/domain/errors/OperationAbortedError.js +22 -0
- package/src/domain/errors/QueryError.js +39 -0
- package/src/domain/errors/SchemaUnsupportedError.js +17 -0
- package/src/domain/errors/ShardCorruptionError.js +56 -0
- package/src/domain/errors/ShardLoadError.js +57 -0
- package/src/domain/errors/ShardValidationError.js +61 -0
- package/src/domain/errors/StorageError.js +57 -0
- package/src/domain/errors/SyncError.js +30 -0
- package/src/domain/errors/TraversalError.js +23 -0
- package/src/domain/errors/WarpError.js +31 -0
- package/src/domain/errors/WormholeError.js +28 -0
- package/src/domain/errors/WriterError.js +39 -0
- package/src/domain/errors/index.js +21 -0
- package/src/domain/services/AnchorMessageCodec.js +99 -0
- package/src/domain/services/BitmapIndexBuilder.js +225 -0
- package/src/domain/services/BitmapIndexReader.js +435 -0
- package/src/domain/services/BoundaryTransitionRecord.js +463 -0
- package/src/domain/services/CheckpointMessageCodec.js +147 -0
- package/src/domain/services/CheckpointSerializerV5.js +281 -0
- package/src/domain/services/CheckpointService.js +384 -0
- package/src/domain/services/CommitDagTraversalService.js +156 -0
- package/src/domain/services/DagPathFinding.js +712 -0
- package/src/domain/services/DagTopology.js +239 -0
- package/src/domain/services/DagTraversal.js +245 -0
- package/src/domain/services/Frontier.js +108 -0
- package/src/domain/services/GCMetrics.js +101 -0
- package/src/domain/services/GCPolicy.js +122 -0
- package/src/domain/services/GitLogParser.js +205 -0
- package/src/domain/services/HealthCheckService.js +246 -0
- package/src/domain/services/HookInstaller.js +326 -0
- package/src/domain/services/HttpSyncServer.js +262 -0
- package/src/domain/services/IndexRebuildService.js +426 -0
- package/src/domain/services/IndexStalenessChecker.js +103 -0
- package/src/domain/services/JoinReducer.js +582 -0
- package/src/domain/services/KeyCodec.js +113 -0
- package/src/domain/services/LegacyAnchorDetector.js +67 -0
- package/src/domain/services/LogicalTraversal.js +351 -0
- package/src/domain/services/MessageCodecInternal.js +132 -0
- package/src/domain/services/MessageSchemaDetector.js +145 -0
- package/src/domain/services/MigrationService.js +55 -0
- package/src/domain/services/ObserverView.js +265 -0
- package/src/domain/services/PatchBuilderV2.js +669 -0
- package/src/domain/services/PatchMessageCodec.js +140 -0
- package/src/domain/services/ProvenanceIndex.js +337 -0
- package/src/domain/services/ProvenancePayload.js +242 -0
- package/src/domain/services/QueryBuilder.js +835 -0
- package/src/domain/services/StateDiff.js +300 -0
- package/src/domain/services/StateSerializerV5.js +156 -0
- package/src/domain/services/StreamingBitmapIndexBuilder.js +709 -0
- package/src/domain/services/SyncProtocol.js +593 -0
- package/src/domain/services/TemporalQuery.js +201 -0
- package/src/domain/services/TranslationCost.js +221 -0
- package/src/domain/services/TraversalService.js +8 -0
- package/src/domain/services/WarpMessageCodec.js +29 -0
- package/src/domain/services/WarpStateIndexBuilder.js +127 -0
- package/src/domain/services/WormholeService.js +353 -0
- package/src/domain/types/TickReceipt.js +285 -0
- package/src/domain/types/WarpTypes.js +209 -0
- package/src/domain/types/WarpTypesV2.js +200 -0
- package/src/domain/utils/CachedValue.js +140 -0
- package/src/domain/utils/EventId.js +89 -0
- package/src/domain/utils/LRUCache.js +112 -0
- package/src/domain/utils/MinHeap.js +114 -0
- package/src/domain/utils/RefLayout.js +280 -0
- package/src/domain/utils/WriterId.js +205 -0
- package/src/domain/utils/cancellation.js +33 -0
- package/src/domain/utils/canonicalStringify.js +42 -0
- package/src/domain/utils/defaultClock.js +20 -0
- package/src/domain/utils/defaultCodec.js +51 -0
- package/src/domain/utils/nullLogger.js +21 -0
- package/src/domain/utils/roaring.js +181 -0
- package/src/domain/utils/shardVersion.js +9 -0
- package/src/domain/warp/PatchSession.js +217 -0
- package/src/domain/warp/Writer.js +181 -0
- package/src/hooks/post-merge.sh +60 -0
- package/src/infrastructure/adapters/BunHttpAdapter.js +225 -0
- package/src/infrastructure/adapters/ClockAdapter.js +57 -0
- package/src/infrastructure/adapters/ConsoleLogger.js +150 -0
- package/src/infrastructure/adapters/DenoHttpAdapter.js +230 -0
- package/src/infrastructure/adapters/GitGraphAdapter.js +787 -0
- package/src/infrastructure/adapters/GlobalClockAdapter.js +5 -0
- package/src/infrastructure/adapters/NoOpLogger.js +62 -0
- package/src/infrastructure/adapters/NodeCryptoAdapter.js +32 -0
- package/src/infrastructure/adapters/NodeHttpAdapter.js +98 -0
- package/src/infrastructure/adapters/PerformanceClockAdapter.js +5 -0
- package/src/infrastructure/adapters/WebCryptoAdapter.js +121 -0
- package/src/infrastructure/codecs/CborCodec.js +384 -0
- package/src/ports/BlobPort.js +30 -0
- package/src/ports/ClockPort.js +25 -0
- package/src/ports/CodecPort.js +25 -0
- package/src/ports/CommitPort.js +114 -0
- package/src/ports/ConfigPort.js +31 -0
- package/src/ports/CryptoPort.js +38 -0
- package/src/ports/GraphPersistencePort.js +57 -0
- package/src/ports/HttpServerPort.js +25 -0
- package/src/ports/IndexStoragePort.js +39 -0
- package/src/ports/LoggerPort.js +68 -0
- package/src/ports/RefPort.js +51 -0
- package/src/ports/TreePort.js +51 -0
- package/src/visualization/index.js +26 -0
- package/src/visualization/layouts/converters.js +75 -0
- package/src/visualization/layouts/elkAdapter.js +86 -0
- package/src/visualization/layouts/elkLayout.js +95 -0
- package/src/visualization/layouts/index.js +29 -0
- package/src/visualization/renderers/ascii/box.js +16 -0
- package/src/visualization/renderers/ascii/check.js +271 -0
- package/src/visualization/renderers/ascii/colors.js +13 -0
- package/src/visualization/renderers/ascii/formatters.js +73 -0
- package/src/visualization/renderers/ascii/graph.js +344 -0
- package/src/visualization/renderers/ascii/history.js +335 -0
- package/src/visualization/renderers/ascii/index.js +14 -0
- package/src/visualization/renderers/ascii/info.js +245 -0
- package/src/visualization/renderers/ascii/materialize.js +255 -0
- package/src/visualization/renderers/ascii/path.js +240 -0
- package/src/visualization/renderers/ascii/progress.js +32 -0
- package/src/visualization/renderers/ascii/symbols.js +33 -0
- package/src/visualization/renderers/ascii/table.js +19 -0
- package/src/visualization/renderers/browser/index.js +1 -0
- package/src/visualization/renderers/svg/index.js +159 -0
- package/src/visualization/utils/ansi.js +14 -0
- package/src/visualization/utils/time.js +40 -0
- package/src/visualization/utils/truncate.js +40 -0
- package/src/visualization/utils/unicode.js +52 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cancellation utilities for async operations.
|
|
3
|
+
*
|
|
4
|
+
* @module domain/utils/cancellation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import OperationAbortedError from '../errors/OperationAbortedError.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Checks if an abort signal has been aborted and throws if so.
|
|
11
|
+
*
|
|
12
|
+
* @param {AbortSignal} [signal] - The abort signal to check
|
|
13
|
+
* @param {string} [operation] - Name of the operation being checked
|
|
14
|
+
* @throws {OperationAbortedError} If signal is aborted
|
|
15
|
+
*/
|
|
16
|
+
export function checkAborted(signal, operation) {
|
|
17
|
+
if (signal?.aborted) {
|
|
18
|
+
throw new OperationAbortedError(operation || 'unknown', { context: { operation } });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Creates an AbortSignal that will abort after the specified timeout.
|
|
24
|
+
*
|
|
25
|
+
* Note: This signal cannot be manually cancelled. If callers need early
|
|
26
|
+
* cancellation, they should use AbortController directly.
|
|
27
|
+
*
|
|
28
|
+
* @param {number} ms - Timeout in milliseconds
|
|
29
|
+
* @returns {AbortSignal} The abort signal
|
|
30
|
+
*/
|
|
31
|
+
export function createTimeoutSignal(ms) {
|
|
32
|
+
return AbortSignal.timeout(ms);
|
|
33
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recursively stringifies a value with sorted object keys for deterministic output.
|
|
3
|
+
* Used for computing checksums that must match across builders and readers.
|
|
4
|
+
*
|
|
5
|
+
* Matches JSON.stringify semantics:
|
|
6
|
+
* - Top-level undefined returns "null"
|
|
7
|
+
* - Array elements that are undefined/function/symbol become "null"
|
|
8
|
+
* - Object properties with undefined/function/symbol values are omitted
|
|
9
|
+
*
|
|
10
|
+
* @param {*} value - Any JSON-serializable value
|
|
11
|
+
* @returns {string} Canonical JSON string with sorted keys
|
|
12
|
+
*/
|
|
13
|
+
export function canonicalStringify(value) {
|
|
14
|
+
if (value === undefined) {
|
|
15
|
+
return 'null';
|
|
16
|
+
}
|
|
17
|
+
if (value === null) {
|
|
18
|
+
return 'null';
|
|
19
|
+
}
|
|
20
|
+
if (Array.isArray(value)) {
|
|
21
|
+
// Map elements: undefined/function/symbol -> "null", others recurse
|
|
22
|
+
const elements = value.map(el => {
|
|
23
|
+
if (el === undefined || typeof el === 'function' || typeof el === 'symbol') {
|
|
24
|
+
return 'null';
|
|
25
|
+
}
|
|
26
|
+
return canonicalStringify(el);
|
|
27
|
+
});
|
|
28
|
+
return `[${elements.join(',')}]`;
|
|
29
|
+
}
|
|
30
|
+
if (typeof value === 'object') {
|
|
31
|
+
// Filter out keys with undefined/function/symbol values, then sort
|
|
32
|
+
const keys = Object.keys(value)
|
|
33
|
+
.filter(k => {
|
|
34
|
+
const v = value[k];
|
|
35
|
+
return v !== undefined && typeof v !== 'function' && typeof v !== 'symbol';
|
|
36
|
+
})
|
|
37
|
+
.sort();
|
|
38
|
+
const pairs = keys.map(k => `${JSON.stringify(k)}:${canonicalStringify(value[k])}`);
|
|
39
|
+
return `{${pairs.join(',')}}`;
|
|
40
|
+
}
|
|
41
|
+
return JSON.stringify(value);
|
|
42
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default clock implementation for domain services.
|
|
3
|
+
*
|
|
4
|
+
* Uses standard globalThis.performance.now() for high-resolution timing
|
|
5
|
+
* and Date for wall-clock timestamps, avoiding concrete adapter imports.
|
|
6
|
+
*
|
|
7
|
+
* @module domain/utils/defaultClock
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** @type {import('../../ports/ClockPort.js').default} */
|
|
11
|
+
const defaultClock = {
|
|
12
|
+
now() {
|
|
13
|
+
return performance.now();
|
|
14
|
+
},
|
|
15
|
+
timestamp() {
|
|
16
|
+
return new Date().toISOString();
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default defaultClock;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default codec implementation for domain services.
|
|
3
|
+
*
|
|
4
|
+
* Provides canonical CBOR encoding/decoding using cbor-x directly,
|
|
5
|
+
* avoiding concrete adapter imports from the infrastructure layer.
|
|
6
|
+
* This follows the same pattern as defaultClock.js.
|
|
7
|
+
*
|
|
8
|
+
* Keys are recursively sorted before encoding for deterministic output,
|
|
9
|
+
* which is critical for content-addressed storage (Git SHA matching).
|
|
10
|
+
*
|
|
11
|
+
* @module domain/utils/defaultCodec
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Encoder, decode as cborDecode } from 'cbor-x';
|
|
15
|
+
|
|
16
|
+
const encoder = new Encoder({
|
|
17
|
+
useRecords: false,
|
|
18
|
+
mapsAsObjects: true,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
function sortKeys(value) {
|
|
22
|
+
if (value === null || value === undefined) { return value; }
|
|
23
|
+
if (Array.isArray(value)) { return value.map(sortKeys); }
|
|
24
|
+
if (value instanceof Map) {
|
|
25
|
+
const sorted = {};
|
|
26
|
+
for (const key of Array.from(value.keys()).sort()) {
|
|
27
|
+
sorted[key] = sortKeys(value.get(key));
|
|
28
|
+
}
|
|
29
|
+
return sorted;
|
|
30
|
+
}
|
|
31
|
+
if (typeof value === 'object' && (value.constructor === Object || value.constructor === undefined)) {
|
|
32
|
+
const sorted = {};
|
|
33
|
+
for (const key of Object.keys(value).sort()) {
|
|
34
|
+
sorted[key] = sortKeys(value[key]);
|
|
35
|
+
}
|
|
36
|
+
return sorted;
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** @type {import('../../ports/CodecPort.js').default} */
|
|
42
|
+
const defaultCodec = {
|
|
43
|
+
encode(data) {
|
|
44
|
+
return encoder.encode(sortKeys(data));
|
|
45
|
+
},
|
|
46
|
+
decode(buffer) {
|
|
47
|
+
return cborDecode(buffer);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default defaultCodec;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Null-object logger for use as a default when no logger is provided.
|
|
3
|
+
*
|
|
4
|
+
* All methods are no-ops. This keeps the domain layer free of
|
|
5
|
+
* adapter dependencies by providing an inline null object.
|
|
6
|
+
*
|
|
7
|
+
* @module domain/utils/nullLogger
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** @type {import('../../ports/LoggerPort.js').default} */
|
|
11
|
+
const nullLogger = {
|
|
12
|
+
debug() {},
|
|
13
|
+
info() {},
|
|
14
|
+
warn() {},
|
|
15
|
+
error() {},
|
|
16
|
+
child() {
|
|
17
|
+
return nullLogger;
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default nullLogger;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy-loading wrapper for the roaring-wasm/roaring native bitmap library.
|
|
3
|
+
*
|
|
4
|
+
* This module provides deferred loading of the `roaring` npm package to avoid
|
|
5
|
+
* incurring the startup cost of loading native C++ bindings until they are
|
|
6
|
+
* actually needed. The roaring package provides highly efficient compressed
|
|
7
|
+
* bitmap data structures used by the bitmap index system for O(1) neighbor lookups.
|
|
8
|
+
*
|
|
9
|
+
* ## Why Lazy Loading?
|
|
10
|
+
*
|
|
11
|
+
* The `roaring` package includes native C++ bindings that can take 50-100ms to
|
|
12
|
+
* initialize on cold start. By deferring the load until first use,
|
|
13
|
+
* applications that don't use bitmap indexes avoid this overhead entirely.
|
|
14
|
+
*
|
|
15
|
+
* ## Module Caching
|
|
16
|
+
*
|
|
17
|
+
* Once loaded, the module reference is cached in `roaringModule` and reused
|
|
18
|
+
* for all subsequent calls. Similarly, native availability is cached after
|
|
19
|
+
* the first check to avoid repeated introspection.
|
|
20
|
+
*
|
|
21
|
+
* @module roaring
|
|
22
|
+
* @see BitmapIndexBuilder - Primary consumer of roaring bitmaps
|
|
23
|
+
* @see StreamingBitmapIndexBuilder - Memory-bounded variant
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Sentinel indicating availability has not been checked yet.
|
|
28
|
+
* @const {symbol}
|
|
29
|
+
* @private
|
|
30
|
+
*/
|
|
31
|
+
const NOT_CHECKED = Symbol('NOT_CHECKED');
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Cached reference to the loaded roaring module.
|
|
35
|
+
* @type {Object|null}
|
|
36
|
+
* @private
|
|
37
|
+
*/
|
|
38
|
+
let roaringModule = null;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Cached result of native availability check.
|
|
42
|
+
* `NOT_CHECKED` means not yet checked, `null` means indeterminate.
|
|
43
|
+
* @type {boolean|symbol|null}
|
|
44
|
+
* @private
|
|
45
|
+
*/
|
|
46
|
+
let nativeAvailability = NOT_CHECKED;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Lazily loads and caches the roaring module.
|
|
50
|
+
*
|
|
51
|
+
* Uses a top-level-await-friendly pattern with dynamic import.
|
|
52
|
+
* The module is cached after first load.
|
|
53
|
+
*
|
|
54
|
+
* @returns {Object} The roaring module exports
|
|
55
|
+
* @throws {Error} If the roaring package is not installed or fails to load
|
|
56
|
+
* @private
|
|
57
|
+
*/
|
|
58
|
+
function loadRoaring() {
|
|
59
|
+
if (!roaringModule) {
|
|
60
|
+
throw new Error('Roaring module not loaded. Call initRoaring() first or ensure top-level await import completed.');
|
|
61
|
+
}
|
|
62
|
+
return roaringModule;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Initializes the roaring module. Must be called before getRoaringBitmap32().
|
|
67
|
+
* This is called automatically via top-level await when the module is imported,
|
|
68
|
+
* but can also be called manually with a pre-loaded module for testing.
|
|
69
|
+
*
|
|
70
|
+
* @param {Object} [mod] - Pre-loaded roaring module (for testing/DI)
|
|
71
|
+
* @returns {Promise<void>}
|
|
72
|
+
*/
|
|
73
|
+
export async function initRoaring(mod) {
|
|
74
|
+
if (mod) {
|
|
75
|
+
roaringModule = mod;
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (!roaringModule) {
|
|
79
|
+
roaringModule = await import('roaring');
|
|
80
|
+
// Handle both ESM default export and CJS module.exports
|
|
81
|
+
if (roaringModule.default && roaringModule.default.RoaringBitmap32) {
|
|
82
|
+
roaringModule = roaringModule.default;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Auto-initialize on module load (top-level await)
|
|
88
|
+
try {
|
|
89
|
+
await initRoaring();
|
|
90
|
+
} catch {
|
|
91
|
+
// Roaring may not be installed; functions will throw on use
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Returns the RoaringBitmap32 class from the roaring library.
|
|
96
|
+
*
|
|
97
|
+
* RoaringBitmap32 is a compressed bitmap implementation that provides
|
|
98
|
+
* efficient set operations (union, intersection, difference) on large
|
|
99
|
+
* sets of 32-bit integers. It's used by the bitmap index system to
|
|
100
|
+
* store edge adjacency lists in a highly compressed format.
|
|
101
|
+
*
|
|
102
|
+
* @returns {typeof import('roaring').RoaringBitmap32} The RoaringBitmap32 constructor
|
|
103
|
+
* @throws {Error} If the roaring package is not installed
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* const RoaringBitmap32 = getRoaringBitmap32();
|
|
107
|
+
* const bitmap = new RoaringBitmap32([1, 2, 3, 100, 1000]);
|
|
108
|
+
* bitmap.has(100); // true
|
|
109
|
+
* bitmap.size; // 5
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* // Set operations
|
|
113
|
+
* const a = new RoaringBitmap32([1, 2, 3]);
|
|
114
|
+
* const b = new RoaringBitmap32([2, 3, 4]);
|
|
115
|
+
* const union = RoaringBitmap32.or(a, b); // [1, 2, 3, 4]
|
|
116
|
+
* const intersection = RoaringBitmap32.and(a, b); // [2, 3]
|
|
117
|
+
*/
|
|
118
|
+
export function getRoaringBitmap32() {
|
|
119
|
+
return loadRoaring().RoaringBitmap32;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Checks whether the native C++ roaring implementation is available.
|
|
124
|
+
*
|
|
125
|
+
* The `roaring` package can operate in two modes:
|
|
126
|
+
* - **Native mode**: Uses prebuilt C++ bindings for maximum performance
|
|
127
|
+
* - **WASM fallback**: Uses WebAssembly when native bindings aren't available
|
|
128
|
+
*
|
|
129
|
+
* This function checks which mode is active by introspecting the loaded
|
|
130
|
+
* module. The result is cached after the first call.
|
|
131
|
+
*
|
|
132
|
+
* @returns {boolean|null} `true` if native bindings are installed,
|
|
133
|
+
* `false` if using WASM fallback or if loading failed,
|
|
134
|
+
* `null` if the installation status could not be determined
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* if (getNativeRoaringAvailable()) {
|
|
138
|
+
* console.log('Using native roaring bindings (fastest)');
|
|
139
|
+
* } else if (getNativeRoaringAvailable() === false) {
|
|
140
|
+
* console.log('Using WASM fallback (slower but portable)');
|
|
141
|
+
* } else {
|
|
142
|
+
* console.log('Could not determine roaring installation type');
|
|
143
|
+
* }
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* // Useful for diagnostics and performance tuning
|
|
147
|
+
* const diagnostics = {
|
|
148
|
+
* roaringNative: getNativeRoaringAvailable(),
|
|
149
|
+
* // ... other system info
|
|
150
|
+
* };
|
|
151
|
+
*/
|
|
152
|
+
export function getNativeRoaringAvailable() {
|
|
153
|
+
if (nativeAvailability !== NOT_CHECKED) {
|
|
154
|
+
return nativeAvailability;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
const roaring = loadRoaring();
|
|
159
|
+
const { RoaringBitmap32 } = roaring;
|
|
160
|
+
|
|
161
|
+
// Try the method-based API first (roaring >= 2.x)
|
|
162
|
+
if (typeof RoaringBitmap32.isNativelyInstalled === 'function') {
|
|
163
|
+
nativeAvailability = RoaringBitmap32.isNativelyInstalled();
|
|
164
|
+
return nativeAvailability;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Fall back to property-based API (roaring 1.x)
|
|
168
|
+
if (roaring.isNativelyInstalled !== undefined) {
|
|
169
|
+
nativeAvailability = roaring.isNativelyInstalled;
|
|
170
|
+
return nativeAvailability;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Could not determine - leave as null (indeterminate)
|
|
174
|
+
nativeAvailability = null;
|
|
175
|
+
return nativeAvailability;
|
|
176
|
+
} catch {
|
|
177
|
+
// Loading failed entirely - definitely not available
|
|
178
|
+
nativeAvailability = false;
|
|
179
|
+
return nativeAvailability;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared shard format version constant.
|
|
3
|
+
* Used by BitmapIndexBuilder, StreamingBitmapIndexBuilder, and BitmapIndexReader.
|
|
4
|
+
*
|
|
5
|
+
* Increment when changing the shard structure to ensure reader/writer compatibility.
|
|
6
|
+
*
|
|
7
|
+
* @const {number}
|
|
8
|
+
*/
|
|
9
|
+
export const SHARD_VERSION = 2;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PatchSession - Fluent patch builder with CAS-safe commit.
|
|
3
|
+
*
|
|
4
|
+
* A PatchSession is created by Writer.beginPatch() and provides a fluent API
|
|
5
|
+
* for building graph mutations. The commit uses compare-and-swap semantics
|
|
6
|
+
* to prevent concurrent forks of the writer chain.
|
|
7
|
+
*
|
|
8
|
+
* @module domain/warp/PatchSession
|
|
9
|
+
* @see WARP Writer Spec v1
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { buildWriterRef } from '../utils/RefLayout.js';
|
|
13
|
+
import WriterError from '../errors/WriterError.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Fluent patch session for building and committing graph mutations.
|
|
17
|
+
*/
|
|
18
|
+
export class PatchSession {
|
|
19
|
+
/**
|
|
20
|
+
* Creates a new PatchSession.
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} options
|
|
23
|
+
* @param {import('../services/PatchBuilderV2.js').PatchBuilderV2} options.builder - Internal builder
|
|
24
|
+
* @param {import('../../ports/GraphPersistencePort.js').default} options.persistence - Git adapter
|
|
25
|
+
* @param {string} options.graphName - Graph namespace
|
|
26
|
+
* @param {string} options.writerId - Writer ID
|
|
27
|
+
* @param {string|null} options.expectedOldHead - Expected parent SHA for CAS
|
|
28
|
+
*/
|
|
29
|
+
constructor({ builder, persistence, graphName, writerId, expectedOldHead }) {
|
|
30
|
+
/** @type {import('../services/PatchBuilderV2.js').PatchBuilderV2} */
|
|
31
|
+
this._builder = builder;
|
|
32
|
+
|
|
33
|
+
/** @type {import('../../ports/GraphPersistencePort.js').default} */
|
|
34
|
+
this._persistence = persistence;
|
|
35
|
+
|
|
36
|
+
/** @type {string} */
|
|
37
|
+
this._graphName = graphName;
|
|
38
|
+
|
|
39
|
+
/** @type {string} */
|
|
40
|
+
this._writerId = writerId;
|
|
41
|
+
|
|
42
|
+
/** @type {string|null} */
|
|
43
|
+
this._expectedOldHead = expectedOldHead;
|
|
44
|
+
|
|
45
|
+
/** @type {boolean} */
|
|
46
|
+
this._committed = false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Gets the expected old head SHA (for testing).
|
|
51
|
+
* @returns {string|null}
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
get _expectedOldHeadForTest() {
|
|
55
|
+
return this._expectedOldHead;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Adds a node to the graph.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} nodeId - The node ID to add
|
|
62
|
+
* @returns {this} This session for chaining
|
|
63
|
+
* @throws {Error} If this session has already been committed
|
|
64
|
+
*/
|
|
65
|
+
addNode(nodeId) {
|
|
66
|
+
this._ensureNotCommitted();
|
|
67
|
+
this._builder.addNode(nodeId);
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Removes a node from the graph.
|
|
73
|
+
*
|
|
74
|
+
* Uses observed dots from materialized state for OR-Set removal.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} nodeId - The node ID to remove
|
|
77
|
+
* @returns {this} This session for chaining
|
|
78
|
+
* @throws {Error} If this session has already been committed
|
|
79
|
+
*/
|
|
80
|
+
removeNode(nodeId) {
|
|
81
|
+
this._ensureNotCommitted();
|
|
82
|
+
this._builder.removeNode(nodeId);
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Adds an edge between two nodes.
|
|
88
|
+
*
|
|
89
|
+
* @param {string} from - Source node ID
|
|
90
|
+
* @param {string} to - Target node ID
|
|
91
|
+
* @param {string} label - Edge label/type
|
|
92
|
+
* @returns {this} This session for chaining
|
|
93
|
+
* @throws {Error} If this session has already been committed
|
|
94
|
+
*/
|
|
95
|
+
addEdge(from, to, label) {
|
|
96
|
+
this._ensureNotCommitted();
|
|
97
|
+
this._builder.addEdge(from, to, label);
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Removes an edge between two nodes.
|
|
103
|
+
*
|
|
104
|
+
* Uses observed dots from materialized state for OR-Set removal.
|
|
105
|
+
*
|
|
106
|
+
* @param {string} from - Source node ID
|
|
107
|
+
* @param {string} to - Target node ID
|
|
108
|
+
* @param {string} label - Edge label/type
|
|
109
|
+
* @returns {this} This session for chaining
|
|
110
|
+
* @throws {Error} If this session has already been committed
|
|
111
|
+
*/
|
|
112
|
+
removeEdge(from, to, label) {
|
|
113
|
+
this._ensureNotCommitted();
|
|
114
|
+
this._builder.removeEdge(from, to, label);
|
|
115
|
+
return this;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Sets a property on a node.
|
|
120
|
+
*
|
|
121
|
+
* @param {string} nodeId - The node ID
|
|
122
|
+
* @param {string} key - Property key
|
|
123
|
+
* @param {*} value - Property value (must be JSON-serializable)
|
|
124
|
+
* @returns {this} This session for chaining
|
|
125
|
+
* @throws {Error} If this session has already been committed
|
|
126
|
+
*/
|
|
127
|
+
setProperty(nodeId, key, value) {
|
|
128
|
+
this._ensureNotCommitted();
|
|
129
|
+
this._builder.setProperty(nodeId, key, value);
|
|
130
|
+
return this;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Builds the PatchV2 object without committing.
|
|
135
|
+
*
|
|
136
|
+
* @returns {import('../types/WarpTypesV2.js').PatchV2} The constructed patch
|
|
137
|
+
*/
|
|
138
|
+
build() {
|
|
139
|
+
return this._builder.build();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Commits the patch to the graph with CAS protection.
|
|
144
|
+
*
|
|
145
|
+
* @returns {Promise<string>} The commit SHA of the new patch
|
|
146
|
+
* @throws {WriterError} EMPTY_PATCH if no operations were added
|
|
147
|
+
* @throws {WriterError} WRITER_REF_ADVANCED if CAS fails (ref moved since beginPatch)
|
|
148
|
+
* @throws {WriterError} PERSIST_WRITE_FAILED if git operations fail
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* const sha = await patch.commit();
|
|
152
|
+
*/
|
|
153
|
+
async commit() {
|
|
154
|
+
this._ensureNotCommitted();
|
|
155
|
+
|
|
156
|
+
// Validate not empty
|
|
157
|
+
if (this._builder.ops.length === 0) {
|
|
158
|
+
throw new WriterError('EMPTY_PATCH', 'Cannot commit empty patch: no operations added');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const writerRef = buildWriterRef(this._graphName, this._writerId);
|
|
162
|
+
|
|
163
|
+
// Pre-commit CAS check: verify ref hasn't moved
|
|
164
|
+
const currentHead = await this._persistence.readRef(writerRef);
|
|
165
|
+
if (currentHead !== this._expectedOldHead) {
|
|
166
|
+
throw new WriterError(
|
|
167
|
+
'WRITER_REF_ADVANCED',
|
|
168
|
+
`Writer ref ${writerRef} has advanced since beginPatch(). ` +
|
|
169
|
+
`Expected ${this._expectedOldHead || '(none)'}, found ${currentHead || '(none)'}. ` +
|
|
170
|
+
`Call beginPatch() again to retry.`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
// Delegate to PatchBuilderV2.commit() which handles the git operations
|
|
176
|
+
const sha = await this._builder.commit();
|
|
177
|
+
this._committed = true;
|
|
178
|
+
return sha;
|
|
179
|
+
} catch (err) {
|
|
180
|
+
// Check if it's a concurrent commit error from PatchBuilderV2
|
|
181
|
+
if (err.message?.includes('Concurrent commit detected') ||
|
|
182
|
+
err.message?.includes('has advanced')) {
|
|
183
|
+
throw new WriterError(
|
|
184
|
+
'WRITER_REF_ADVANCED',
|
|
185
|
+
err.message,
|
|
186
|
+
err
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Wrap other errors
|
|
191
|
+
throw new WriterError(
|
|
192
|
+
'PERSIST_WRITE_FAILED',
|
|
193
|
+
`Failed to persist patch: ${err.message}`,
|
|
194
|
+
err
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Gets the number of operations in this patch.
|
|
201
|
+
* @returns {number}
|
|
202
|
+
*/
|
|
203
|
+
get opCount() {
|
|
204
|
+
return this._builder.ops.length;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Ensures the session hasn't been committed yet.
|
|
209
|
+
* @throws {Error} If already committed
|
|
210
|
+
* @private
|
|
211
|
+
*/
|
|
212
|
+
_ensureNotCommitted() {
|
|
213
|
+
if (this._committed) {
|
|
214
|
+
throw new Error('PatchSession already committed. Call beginPatch() to create a new session.');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|