@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
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared input validation for persistence adapters.
|
|
3
|
+
*
|
|
4
|
+
* These functions are extracted from GitGraphAdapter so that both Git-backed
|
|
5
|
+
* and in-memory adapters apply identical validation rules. This prevents
|
|
6
|
+
* divergence and ensures conformance tests exercise the same constraints.
|
|
7
|
+
*
|
|
8
|
+
* @module infrastructure/adapters/adapterValidation
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validates that an OID is a safe hex string (4–64 characters).
|
|
13
|
+
* @param {string} oid - The OID to validate
|
|
14
|
+
* @throws {Error} If OID is invalid
|
|
15
|
+
*/
|
|
16
|
+
export function validateOid(oid) {
|
|
17
|
+
if (!oid || typeof oid !== 'string') {
|
|
18
|
+
throw new Error('OID must be a non-empty string');
|
|
19
|
+
}
|
|
20
|
+
if (oid.length > 64) {
|
|
21
|
+
throw new Error(`OID too long: ${oid.length} chars. Maximum is 64`);
|
|
22
|
+
}
|
|
23
|
+
const validOidPattern = /^[0-9a-fA-F]{4,64}$/;
|
|
24
|
+
if (!validOidPattern.test(oid)) {
|
|
25
|
+
throw new Error(`Invalid OID format: ${oid}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validates that a ref is safe to use in git commands.
|
|
31
|
+
* Prevents command injection via malicious ref names.
|
|
32
|
+
* @param {string} ref - The ref to validate
|
|
33
|
+
* @throws {Error} If ref contains invalid characters, is too long, or starts with -/--
|
|
34
|
+
*/
|
|
35
|
+
export function validateRef(ref) {
|
|
36
|
+
if (!ref || typeof ref !== 'string') {
|
|
37
|
+
throw new Error('Ref must be a non-empty string');
|
|
38
|
+
}
|
|
39
|
+
if (ref.length > 1024) {
|
|
40
|
+
throw new Error(`Ref too long: ${ref.length} chars. Maximum is 1024`);
|
|
41
|
+
}
|
|
42
|
+
if (ref.startsWith('-')) {
|
|
43
|
+
throw new Error(`Invalid ref: ${ref}. Refs cannot start with - or --. See https://github.com/git-stunts/git-warp#security`);
|
|
44
|
+
}
|
|
45
|
+
const validRefPattern = /^[a-zA-Z0-9._/-]+((~\d*|\^\d*|\.\.[a-zA-Z0-9._/-]+)*)$/;
|
|
46
|
+
if (!validRefPattern.test(ref)) {
|
|
47
|
+
throw new Error(`Invalid ref format: ${ref}. Only alphanumeric characters, ., /, -, _, ^, ~, and range operators are allowed. See https://github.com/git-stunts/git-warp#ref-validation`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Validates that a limit is a safe positive integer (max 10M).
|
|
53
|
+
* @param {number} limit - The limit to validate
|
|
54
|
+
* @throws {Error} If limit is invalid
|
|
55
|
+
*/
|
|
56
|
+
export function validateLimit(limit) {
|
|
57
|
+
if (typeof limit !== 'number' || !Number.isFinite(limit)) {
|
|
58
|
+
throw new Error('Limit must be a finite number');
|
|
59
|
+
}
|
|
60
|
+
if (!Number.isInteger(limit)) {
|
|
61
|
+
throw new Error('Limit must be an integer');
|
|
62
|
+
}
|
|
63
|
+
if (limit <= 0) {
|
|
64
|
+
throw new Error('Limit must be a positive integer');
|
|
65
|
+
}
|
|
66
|
+
if (limit > 10_000_000) {
|
|
67
|
+
throw new Error(`Limit too large: ${limit}. Maximum is 10,000,000`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Validates that a config key is safe and well-formed.
|
|
73
|
+
* @param {string} key - The config key to validate
|
|
74
|
+
* @throws {Error} If key is invalid
|
|
75
|
+
*/
|
|
76
|
+
export function validateConfigKey(key) {
|
|
77
|
+
if (!key || typeof key !== 'string') {
|
|
78
|
+
throw new Error('Config key must be a non-empty string');
|
|
79
|
+
}
|
|
80
|
+
if (key.length > 256) {
|
|
81
|
+
throw new Error(`Config key too long: ${key.length} chars. Maximum is 256`);
|
|
82
|
+
}
|
|
83
|
+
if (key.startsWith('-')) {
|
|
84
|
+
throw new Error(`Invalid config key: ${key}. Keys cannot start with -`);
|
|
85
|
+
}
|
|
86
|
+
const validKeyPattern = /^[a-zA-Z][a-zA-Z0-9._-]*$/;
|
|
87
|
+
if (!validKeyPattern.test(key)) {
|
|
88
|
+
throw new Error(`Invalid config key format: ${key}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -83,17 +83,18 @@ const encoder = new Encoder({
|
|
|
83
83
|
* @private
|
|
84
84
|
*/
|
|
85
85
|
function isPlainObject(value) {
|
|
86
|
-
return typeof value === 'object' && (value.constructor === Object || value.constructor === undefined);
|
|
86
|
+
return typeof value === 'object' && value !== null && (value.constructor === Object || value.constructor === undefined);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
90
|
* Sorts the keys of a plain object and recursively processes values.
|
|
91
91
|
*
|
|
92
|
-
* @param {
|
|
93
|
-
* @returns {
|
|
92
|
+
* @param {Record<string, unknown>} obj - The plain object to process
|
|
93
|
+
* @returns {Record<string, unknown>} A new object with sorted keys
|
|
94
94
|
* @private
|
|
95
95
|
*/
|
|
96
96
|
function sortPlainObject(obj) {
|
|
97
|
+
/** @type {Record<string, unknown>} */
|
|
97
98
|
const sorted = {};
|
|
98
99
|
const keys = Object.keys(obj).sort();
|
|
99
100
|
for (const key of keys) {
|
|
@@ -106,8 +107,8 @@ function sortPlainObject(obj) {
|
|
|
106
107
|
* Converts a Map to a sorted plain object with recursive value processing.
|
|
107
108
|
* Validates that all Map keys are strings (required for CBOR encoding).
|
|
108
109
|
*
|
|
109
|
-
* @param {Map} map - The Map instance to convert
|
|
110
|
-
* @returns {
|
|
110
|
+
* @param {Map<string, unknown>} map - The Map instance to convert
|
|
111
|
+
* @returns {Record<string, unknown>} A plain object with sorted keys
|
|
111
112
|
* @throws {TypeError} If any Map key is not a string
|
|
112
113
|
* @private
|
|
113
114
|
*/
|
|
@@ -118,6 +119,7 @@ function sortMapToObject(map) {
|
|
|
118
119
|
throw new TypeError(`Map keys must be strings for CBOR encoding, got ${typeof key}`);
|
|
119
120
|
}
|
|
120
121
|
}
|
|
122
|
+
/** @type {Record<string, unknown>} */
|
|
121
123
|
const sorted = {};
|
|
122
124
|
keys.sort();
|
|
123
125
|
for (const key of keys) {
|
|
@@ -198,7 +200,7 @@ function sortKeys(value) {
|
|
|
198
200
|
|
|
199
201
|
// Plain objects: sort keys and recursively process values
|
|
200
202
|
if (isPlainObject(value)) {
|
|
201
|
-
return sortPlainObject(value);
|
|
203
|
+
return sortPlainObject(/** @type {Record<string, unknown>} */ (value));
|
|
202
204
|
}
|
|
203
205
|
|
|
204
206
|
// Map instances: convert to sorted object
|
|
@@ -370,12 +372,18 @@ export function decode(buffer) {
|
|
|
370
372
|
* @extends CodecPort
|
|
371
373
|
*/
|
|
372
374
|
export class CborCodec extends CodecPort {
|
|
373
|
-
/**
|
|
375
|
+
/**
|
|
376
|
+
* @param {unknown} data
|
|
377
|
+
* @returns {Buffer|Uint8Array}
|
|
378
|
+
*/
|
|
374
379
|
encode(data) {
|
|
375
380
|
return encode(data);
|
|
376
381
|
}
|
|
377
382
|
|
|
378
|
-
/**
|
|
383
|
+
/**
|
|
384
|
+
* @param {Buffer|Uint8Array} buffer
|
|
385
|
+
* @returns {unknown}
|
|
386
|
+
*/
|
|
379
387
|
decode(buffer) {
|
|
380
388
|
return decode(buffer);
|
|
381
389
|
}
|
package/src/ports/BlobPort.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
export default class BlobPort {
|
|
11
11
|
/**
|
|
12
12
|
* Writes content as a Git blob and returns its OID.
|
|
13
|
-
* @param {Buffer|string}
|
|
13
|
+
* @param {Buffer|string} _content - The blob content to write
|
|
14
14
|
* @returns {Promise<string>} The Git OID of the created blob
|
|
15
15
|
* @throws {Error} If not implemented by a concrete adapter
|
|
16
16
|
*/
|
|
@@ -20,7 +20,7 @@ export default class BlobPort {
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Reads the content of a Git blob.
|
|
23
|
-
* @param {string}
|
|
23
|
+
* @param {string} _oid - The blob OID to read
|
|
24
24
|
* @returns {Promise<Buffer>} The blob content
|
|
25
25
|
* @throws {Error} If not implemented by a concrete adapter
|
|
26
26
|
*/
|
package/src/ports/CodecPort.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
export default class CodecPort {
|
|
8
8
|
/**
|
|
9
9
|
* Encodes data to binary format.
|
|
10
|
-
* @param {unknown}
|
|
10
|
+
* @param {unknown} _data - Data to encode
|
|
11
11
|
* @returns {Buffer|Uint8Array} Encoded bytes
|
|
12
12
|
*/
|
|
13
13
|
encode(_data) {
|
|
@@ -16,7 +16,7 @@ export default class CodecPort {
|
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Decodes binary data back to a JavaScript value.
|
|
19
|
-
* @param {Buffer|Uint8Array}
|
|
19
|
+
* @param {Buffer|Uint8Array} _bytes - Encoded bytes to decode
|
|
20
20
|
* @returns {unknown} Decoded value
|
|
21
21
|
*/
|
|
22
22
|
decode(_bytes) {
|
package/src/ports/CommitPort.js
CHANGED
|
@@ -10,10 +10,7 @@
|
|
|
10
10
|
export default class CommitPort {
|
|
11
11
|
/**
|
|
12
12
|
* Creates a commit pointing to the empty tree.
|
|
13
|
-
* @param {
|
|
14
|
-
* @param {string} options.message - The commit message (typically CBOR-encoded patch data)
|
|
15
|
-
* @param {string[]} [options.parents=[]] - Parent commit SHAs for the commit graph
|
|
16
|
-
* @param {boolean} [options.sign=false] - Whether to GPG-sign the commit
|
|
13
|
+
* @param {{ message: string, parents?: string[], sign?: boolean }} _options
|
|
17
14
|
* @returns {Promise<string>} The SHA of the created commit
|
|
18
15
|
* @throws {Error} If not implemented by a concrete adapter
|
|
19
16
|
*/
|
|
@@ -23,7 +20,7 @@ export default class CommitPort {
|
|
|
23
20
|
|
|
24
21
|
/**
|
|
25
22
|
* Retrieves the raw commit message for a given SHA.
|
|
26
|
-
* @param {string}
|
|
23
|
+
* @param {string} _sha - The commit SHA to read
|
|
27
24
|
* @returns {Promise<string>} The raw commit message content
|
|
28
25
|
* @throws {Error} If not implemented by a concrete adapter
|
|
29
26
|
*/
|
|
@@ -33,7 +30,7 @@ export default class CommitPort {
|
|
|
33
30
|
|
|
34
31
|
/**
|
|
35
32
|
* Gets full commit metadata for a node.
|
|
36
|
-
* @param {string}
|
|
33
|
+
* @param {string} _sha - The commit SHA to retrieve
|
|
37
34
|
* @returns {Promise<{sha: string, message: string, author: string, date: string, parents: string[]}>}
|
|
38
35
|
* Full commit metadata including SHA, message, author, date, and parent SHAs
|
|
39
36
|
* @throws {Error} If not implemented by a concrete adapter
|
|
@@ -44,10 +41,7 @@ export default class CommitPort {
|
|
|
44
41
|
|
|
45
42
|
/**
|
|
46
43
|
* Returns raw git log output for a ref.
|
|
47
|
-
* @param {
|
|
48
|
-
* @param {string} options.ref - The Git ref to log from
|
|
49
|
-
* @param {number} [options.limit=50] - Maximum number of commits to return
|
|
50
|
-
* @param {string} [options.format] - Custom format string for git log
|
|
44
|
+
* @param {{ ref: string, limit?: number, format?: string }} _options
|
|
51
45
|
* @returns {Promise<string>} The raw log output
|
|
52
46
|
* @throws {Error} If not implemented by a concrete adapter
|
|
53
47
|
*/
|
|
@@ -57,10 +51,7 @@ export default class CommitPort {
|
|
|
57
51
|
|
|
58
52
|
/**
|
|
59
53
|
* Streams git log output for a ref.
|
|
60
|
-
* @param {
|
|
61
|
-
* @param {string} options.ref - The Git ref to log from
|
|
62
|
-
* @param {number} [options.limit=1000000] - Maximum number of commits to return
|
|
63
|
-
* @param {string} [options.format] - Custom format string for git log
|
|
54
|
+
* @param {{ ref: string, limit?: number, format?: string }} _options
|
|
64
55
|
* @returns {Promise<import('node:stream').Readable>} A readable stream of log output
|
|
65
56
|
* @throws {Error} If not implemented by a concrete adapter
|
|
66
57
|
*/
|
|
@@ -70,7 +61,7 @@ export default class CommitPort {
|
|
|
70
61
|
|
|
71
62
|
/**
|
|
72
63
|
* Counts nodes reachable from a ref without loading them into memory.
|
|
73
|
-
* @param {string}
|
|
64
|
+
* @param {string} _ref - Git ref to count from (e.g., 'HEAD', 'main', or a SHA)
|
|
74
65
|
* @returns {Promise<number>} The count of reachable nodes
|
|
75
66
|
* @throws {Error} If not implemented by a concrete adapter
|
|
76
67
|
*/
|
|
@@ -81,11 +72,7 @@ export default class CommitPort {
|
|
|
81
72
|
/**
|
|
82
73
|
* Creates a commit pointing to a specified tree (not the empty tree).
|
|
83
74
|
* Used by CheckpointService and PatchBuilderV2 for tree-backed commits.
|
|
84
|
-
* @param {
|
|
85
|
-
* @param {string} options.treeOid - The tree OID to commit
|
|
86
|
-
* @param {string[]} [options.parents=[]] - Parent commit SHAs
|
|
87
|
-
* @param {string} options.message - The commit message
|
|
88
|
-
* @param {boolean} [options.sign=false] - Whether to GPG-sign the commit
|
|
75
|
+
* @param {{ treeOid: string, parents?: string[], message: string, sign?: boolean }} _options
|
|
89
76
|
* @returns {Promise<string>} The SHA of the created commit
|
|
90
77
|
* @throws {Error} If not implemented by a concrete adapter
|
|
91
78
|
*/
|
|
@@ -95,7 +82,7 @@ export default class CommitPort {
|
|
|
95
82
|
|
|
96
83
|
/**
|
|
97
84
|
* Checks whether a commit exists in the repository.
|
|
98
|
-
* @param {string}
|
|
85
|
+
* @param {string} _sha - The commit SHA to check
|
|
99
86
|
* @returns {Promise<boolean>} True if the commit exists
|
|
100
87
|
* @throws {Error} If not implemented by a concrete adapter
|
|
101
88
|
*/
|
package/src/ports/ConfigPort.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
export default class ConfigPort {
|
|
11
11
|
/**
|
|
12
12
|
* Reads a git config value.
|
|
13
|
-
* @param {string}
|
|
13
|
+
* @param {string} _key - The config key to read (e.g., 'warp.writerId.events')
|
|
14
14
|
* @returns {Promise<string|null>} The config value, or null if not set
|
|
15
15
|
* @throws {Error} If not implemented by a concrete adapter
|
|
16
16
|
*/
|
|
@@ -20,8 +20,8 @@ export default class ConfigPort {
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Sets a git config value.
|
|
23
|
-
* @param {string}
|
|
24
|
-
* @param {string}
|
|
23
|
+
* @param {string} _key - The config key to set (e.g., 'warp.writerId.events')
|
|
24
|
+
* @param {string} _value - The value to set
|
|
25
25
|
* @returns {Promise<void>}
|
|
26
26
|
* @throws {Error} If not implemented by a concrete adapter
|
|
27
27
|
*/
|
package/src/ports/CryptoPort.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
export default class CryptoPort {
|
|
8
8
|
/**
|
|
9
9
|
* Computes a hash digest of the given data.
|
|
10
|
-
* @param {string}
|
|
11
|
-
* @param {string|Buffer|Uint8Array}
|
|
10
|
+
* @param {string} _algorithm - Hash algorithm (e.g. 'sha1', 'sha256')
|
|
11
|
+
* @param {string|Buffer|Uint8Array} _data - Data to hash
|
|
12
12
|
* @returns {Promise<string>} Hex-encoded digest
|
|
13
13
|
*/
|
|
14
14
|
async hash(_algorithm, _data) {
|
|
@@ -17,9 +17,9 @@ export default class CryptoPort {
|
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Computes an HMAC of the given data.
|
|
20
|
-
* @param {string}
|
|
21
|
-
* @param {string|Buffer|Uint8Array}
|
|
22
|
-
* @param {string|Buffer|Uint8Array}
|
|
20
|
+
* @param {string} _algorithm - Hash algorithm (e.g. 'sha256')
|
|
21
|
+
* @param {string|Buffer|Uint8Array} _key - HMAC key
|
|
22
|
+
* @param {string|Buffer|Uint8Array} _data - Data to authenticate
|
|
23
23
|
* @returns {Promise<Buffer|Uint8Array>} Raw HMAC digest
|
|
24
24
|
*/
|
|
25
25
|
async hmac(_algorithm, _key, _data) {
|
|
@@ -28,8 +28,8 @@ export default class CryptoPort {
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Constant-time comparison of two buffers.
|
|
31
|
-
* @param {Buffer|Uint8Array}
|
|
32
|
-
* @param {Buffer|Uint8Array}
|
|
31
|
+
* @param {Buffer|Uint8Array} _a - First buffer
|
|
32
|
+
* @param {Buffer|Uint8Array} _b - Second buffer
|
|
33
33
|
* @returns {boolean} True if buffers are equal
|
|
34
34
|
*/
|
|
35
35
|
timingSafeEqual(_a, _b) {
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import CommitPort from './CommitPort.js';
|
|
2
|
+
import BlobPort from './BlobPort.js';
|
|
3
|
+
import TreePort from './TreePort.js';
|
|
4
|
+
import RefPort from './RefPort.js';
|
|
5
|
+
import ConfigPort from './ConfigPort.js';
|
|
6
|
+
|
|
1
7
|
/**
|
|
2
8
|
* Abstract port for graph persistence operations.
|
|
3
9
|
*
|
|
@@ -20,27 +26,19 @@
|
|
|
20
26
|
* All methods throw by default and must be overridden by implementations.
|
|
21
27
|
*
|
|
22
28
|
* @abstract
|
|
23
|
-
* @implements {CommitPort}
|
|
24
|
-
* @implements {BlobPort}
|
|
25
|
-
* @implements {TreePort}
|
|
26
|
-
* @implements {RefPort}
|
|
27
|
-
* @implements {ConfigPort}
|
|
28
29
|
*/
|
|
29
|
-
|
|
30
|
-
import CommitPort from './CommitPort.js';
|
|
31
|
-
import BlobPort from './BlobPort.js';
|
|
32
|
-
import TreePort from './TreePort.js';
|
|
33
|
-
import RefPort from './RefPort.js';
|
|
34
|
-
import ConfigPort from './ConfigPort.js';
|
|
35
|
-
|
|
36
30
|
class GraphPersistencePort {}
|
|
37
31
|
|
|
32
|
+
/** @type {Array<typeof CommitPort | typeof BlobPort | typeof TreePort | typeof RefPort | typeof ConfigPort>} */
|
|
38
33
|
const focusedPorts = [CommitPort, BlobPort, TreePort, RefPort, ConfigPort];
|
|
39
34
|
const seen = new Map();
|
|
40
35
|
|
|
41
36
|
for (const Port of focusedPorts) {
|
|
42
|
-
const
|
|
43
|
-
|
|
37
|
+
const allDescriptors = Object.getOwnPropertyDescriptors(Port.prototype);
|
|
38
|
+
/** @type {Record<string, PropertyDescriptor>} */
|
|
39
|
+
const descriptors = Object.fromEntries(
|
|
40
|
+
Object.entries(allDescriptors).filter(([k]) => k !== 'constructor'),
|
|
41
|
+
);
|
|
44
42
|
|
|
45
43
|
for (const [name, descriptor] of Object.entries(descriptors)) {
|
|
46
44
|
if (seen.has(name)) {
|
|
@@ -12,11 +12,7 @@ export default class HttpServerPort {
|
|
|
12
12
|
* and must return `{ status, headers, body }`. No raw req/res objects
|
|
13
13
|
* are exposed to the domain.
|
|
14
14
|
*
|
|
15
|
-
* @param {
|
|
16
|
-
* @param {string} requestHandler.method - HTTP method
|
|
17
|
-
* @param {string} requestHandler.url - Request URL
|
|
18
|
-
* @param {Object} requestHandler.headers - Request headers (lowercased keys)
|
|
19
|
-
* @param {Buffer|undefined} requestHandler.body - Request body (undefined if none)
|
|
15
|
+
* @param {(request: { method: string, url: string, headers: Object, body: Buffer|undefined }) => Promise<{ status: number, headers: Object, body: string|Buffer }>} _requestHandler - Async function (request) => response
|
|
20
16
|
* @returns {{ listen: Function, close: Function, address: Function }} Server with listen(port, [host], cb(err)), close(cb), and address()
|
|
21
17
|
*/
|
|
22
18
|
createServer(_requestHandler) {
|
package/src/ports/LoggerPort.js
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
export default class LoggerPort {
|
|
14
14
|
/**
|
|
15
15
|
* Log a debug-level message.
|
|
16
|
-
* @param {string}
|
|
17
|
-
* @param {Record<string, unknown>} [
|
|
16
|
+
* @param {string} _message - The log message
|
|
17
|
+
* @param {Record<string, unknown>} [_context] - Structured metadata
|
|
18
18
|
* @returns {void}
|
|
19
19
|
* @abstract
|
|
20
20
|
*/
|
|
@@ -24,8 +24,8 @@ export default class LoggerPort {
|
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Log an info-level message.
|
|
27
|
-
* @param {string}
|
|
28
|
-
* @param {Record<string, unknown>} [
|
|
27
|
+
* @param {string} _message - The log message
|
|
28
|
+
* @param {Record<string, unknown>} [_context] - Structured metadata
|
|
29
29
|
* @returns {void}
|
|
30
30
|
* @abstract
|
|
31
31
|
*/
|
|
@@ -35,8 +35,8 @@ export default class LoggerPort {
|
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Log a warning-level message.
|
|
38
|
-
* @param {string}
|
|
39
|
-
* @param {Record<string, unknown>} [
|
|
38
|
+
* @param {string} _message - The log message
|
|
39
|
+
* @param {Record<string, unknown>} [_context] - Structured metadata
|
|
40
40
|
* @returns {void}
|
|
41
41
|
* @abstract
|
|
42
42
|
*/
|
|
@@ -46,8 +46,8 @@ export default class LoggerPort {
|
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Log an error-level message.
|
|
49
|
-
* @param {string}
|
|
50
|
-
* @param {Record<string, unknown>} [
|
|
49
|
+
* @param {string} _message - The log message
|
|
50
|
+
* @param {Record<string, unknown>} [_context] - Structured metadata
|
|
51
51
|
* @returns {void}
|
|
52
52
|
* @abstract
|
|
53
53
|
*/
|
|
@@ -58,7 +58,7 @@ export default class LoggerPort {
|
|
|
58
58
|
/**
|
|
59
59
|
* Create a child logger with additional base context.
|
|
60
60
|
* Child loggers inherit parent context and merge with their own.
|
|
61
|
-
* @param {Record<string, unknown>}
|
|
61
|
+
* @param {Record<string, unknown>} _context - Base context for the child logger
|
|
62
62
|
* @returns {LoggerPort} A new logger instance with merged context
|
|
63
63
|
* @abstract
|
|
64
64
|
*/
|
package/src/ports/RefPort.js
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
export default class RefPort {
|
|
11
11
|
/**
|
|
12
12
|
* Updates a ref to point to an OID.
|
|
13
|
-
* @param {string}
|
|
14
|
-
* @param {string}
|
|
13
|
+
* @param {string} _ref - The ref name (e.g., 'refs/warp/events/writers/alice')
|
|
14
|
+
* @param {string} _oid - The OID to point to
|
|
15
15
|
* @returns {Promise<void>}
|
|
16
16
|
* @throws {Error} If not implemented by a concrete adapter
|
|
17
17
|
*/
|
|
@@ -21,7 +21,7 @@ export default class RefPort {
|
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Reads the OID a ref points to.
|
|
24
|
-
* @param {string}
|
|
24
|
+
* @param {string} _ref - The ref name
|
|
25
25
|
* @returns {Promise<string|null>} The OID, or null if the ref does not exist
|
|
26
26
|
* @throws {Error} If not implemented by a concrete adapter
|
|
27
27
|
*/
|
|
@@ -31,7 +31,7 @@ export default class RefPort {
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Deletes a ref.
|
|
34
|
-
* @param {string}
|
|
34
|
+
* @param {string} _ref - The ref name to delete
|
|
35
35
|
* @returns {Promise<void>}
|
|
36
36
|
* @throws {Error} If not implemented by a concrete adapter
|
|
37
37
|
*/
|
|
@@ -41,7 +41,7 @@ export default class RefPort {
|
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Lists refs matching a prefix.
|
|
44
|
-
* @param {string}
|
|
44
|
+
* @param {string} _prefix - The ref prefix to match (e.g., 'refs/warp/events/writers/')
|
|
45
45
|
* @returns {Promise<string[]>} Array of matching ref names
|
|
46
46
|
* @throws {Error} If not implemented by a concrete adapter
|
|
47
47
|
*/
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port interface for seek materialization cache operations.
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract for caching and retrieving serialized WarpStateV5
|
|
5
|
+
* snapshots keyed by (ceiling, frontier) tuples. Used by the seek time-travel
|
|
6
|
+
* feature to avoid full re-materialization for previously-visited ticks.
|
|
7
|
+
*
|
|
8
|
+
* Concrete adapters (e.g., CasSeekCacheAdapter) implement this interface
|
|
9
|
+
* to store cached states in different backends (git-cas, filesystem, etc.).
|
|
10
|
+
*
|
|
11
|
+
* @abstract
|
|
12
|
+
*/
|
|
13
|
+
export default class SeekCachePort {
|
|
14
|
+
/**
|
|
15
|
+
* Retrieves a cached state buffer by key.
|
|
16
|
+
* @param {string} _key - Cache key (e.g., 'v1:t42-<frontierHash>')
|
|
17
|
+
* @returns {Promise<Buffer|null>} The cached buffer, or null on miss
|
|
18
|
+
* @throws {Error} If not implemented by a concrete adapter
|
|
19
|
+
*/
|
|
20
|
+
async get(_key) {
|
|
21
|
+
throw new Error('SeekCachePort.get() not implemented');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Stores a state buffer under the given key.
|
|
26
|
+
* @param {string} _key - Cache key
|
|
27
|
+
* @param {Buffer} _buffer - Serialized state to cache
|
|
28
|
+
* @returns {Promise<void>}
|
|
29
|
+
* @throws {Error} If not implemented by a concrete adapter
|
|
30
|
+
*/
|
|
31
|
+
async set(_key, _buffer) {
|
|
32
|
+
throw new Error('SeekCachePort.set() not implemented');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Checks whether a key exists in the cache.
|
|
37
|
+
* @param {string} _key - Cache key
|
|
38
|
+
* @returns {Promise<boolean>}
|
|
39
|
+
* @throws {Error} If not implemented by a concrete adapter
|
|
40
|
+
*/
|
|
41
|
+
async has(_key) {
|
|
42
|
+
throw new Error('SeekCachePort.has() not implemented');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Lists all keys currently in the cache index.
|
|
47
|
+
* Note: keys may reference GC'd blobs; callers should handle miss on get().
|
|
48
|
+
* @returns {Promise<string[]>}
|
|
49
|
+
* @throws {Error} If not implemented by a concrete adapter
|
|
50
|
+
*/
|
|
51
|
+
async keys() {
|
|
52
|
+
throw new Error('SeekCachePort.keys() not implemented');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Removes a single entry from the cache.
|
|
57
|
+
* @param {string} _key - Cache key to remove
|
|
58
|
+
* @returns {Promise<boolean>} True if the entry existed and was removed
|
|
59
|
+
* @throws {Error} If not implemented by a concrete adapter
|
|
60
|
+
*/
|
|
61
|
+
async delete(_key) {
|
|
62
|
+
throw new Error('SeekCachePort.delete() not implemented');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Removes all entries from the cache.
|
|
67
|
+
* @returns {Promise<void>}
|
|
68
|
+
* @throws {Error} If not implemented by a concrete adapter
|
|
69
|
+
*/
|
|
70
|
+
async clear() {
|
|
71
|
+
throw new Error('SeekCachePort.clear() not implemented');
|
|
72
|
+
}
|
|
73
|
+
}
|
package/src/ports/TreePort.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
export default class TreePort {
|
|
11
11
|
/**
|
|
12
12
|
* Creates a Git tree from mktree-formatted entries.
|
|
13
|
-
* @param {string[]}
|
|
13
|
+
* @param {string[]} _entries - Lines in git mktree format (e.g., "100644 blob <oid>\t<path>")
|
|
14
14
|
* @returns {Promise<string>} The Git OID of the created tree
|
|
15
15
|
* @throws {Error} If not implemented by a concrete adapter
|
|
16
16
|
*/
|
|
@@ -20,7 +20,7 @@ export default class TreePort {
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Reads a tree and returns a map of path to content.
|
|
23
|
-
* @param {string}
|
|
23
|
+
* @param {string} _treeOid - The tree OID to read
|
|
24
24
|
* @returns {Promise<Record<string, Buffer>>} Map of file path to blob content
|
|
25
25
|
* @throws {Error} If not implemented by a concrete adapter
|
|
26
26
|
*/
|
|
@@ -31,7 +31,7 @@ export default class TreePort {
|
|
|
31
31
|
/**
|
|
32
32
|
* Reads a tree and returns a map of path to blob OID.
|
|
33
33
|
* Useful for lazy-loading shards without reading all blob contents.
|
|
34
|
-
* @param {string}
|
|
34
|
+
* @param {string} _treeOid - The tree OID to read
|
|
35
35
|
* @returns {Promise<Record<string, string>>} Map of file path to blob OID
|
|
36
36
|
* @throws {Error} If not implemented by a concrete adapter
|
|
37
37
|
*/
|
|
@@ -6,13 +6,19 @@
|
|
|
6
6
|
* { nodes: [{ id, label, props? }], edges: [{ from, to, label? }] }
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {{ id: string, label: string, props?: Record<string, any> }} GraphDataNode
|
|
11
|
+
* @typedef {{ from: string, to: string, label?: string }} GraphDataEdge
|
|
12
|
+
* @typedef {{ nodes: GraphDataNode[], edges: GraphDataEdge[] }} GraphData
|
|
13
|
+
*/
|
|
14
|
+
|
|
9
15
|
/**
|
|
10
16
|
* Converts a query result payload + edge array into graph data.
|
|
11
17
|
* Edges are filtered to only those connecting matched nodes.
|
|
12
18
|
*
|
|
13
|
-
* @param {
|
|
14
|
-
* @param {Array} edges - Edge array from graph.getEdges()
|
|
15
|
-
* @returns {
|
|
19
|
+
* @param {{ nodes?: Array<{ id: string, props?: Record<string, any> }> } | null} payload - Query result
|
|
20
|
+
* @param {Array<{ from: string, to: string, label?: string }>} edges - Edge array from graph.getEdges()
|
|
21
|
+
* @returns {GraphData}
|
|
16
22
|
*/
|
|
17
23
|
export function queryResultToGraphData(payload, edges) {
|
|
18
24
|
const nodes = (payload?.nodes ?? []).map((n) => ({
|
|
@@ -34,8 +40,8 @@ export function queryResultToGraphData(payload, edges) {
|
|
|
34
40
|
* Converts a path result payload into graph data.
|
|
35
41
|
* Builds a linear chain of nodes with labelled edges.
|
|
36
42
|
*
|
|
37
|
-
* @param {
|
|
38
|
-
* @returns {
|
|
43
|
+
* @param {{ path?: string[], edges?: string[] } | null} payload - Path result
|
|
44
|
+
* @returns {GraphData}
|
|
39
45
|
*/
|
|
40
46
|
export function pathResultToGraphData(payload) {
|
|
41
47
|
const pathArr = payload?.path ?? [];
|
|
@@ -43,6 +49,7 @@ export function pathResultToGraphData(payload) {
|
|
|
43
49
|
|
|
44
50
|
const nodes = pathArr.map((id) => ({ id, label: id }));
|
|
45
51
|
|
|
52
|
+
/** @type {GraphDataEdge[]} */
|
|
46
53
|
const edges = [];
|
|
47
54
|
for (let i = 0; i < pathArr.length - 1; i++) {
|
|
48
55
|
edges.push({
|
|
@@ -59,8 +66,8 @@ export function pathResultToGraphData(payload) {
|
|
|
59
66
|
* Converts raw getNodes() + getEdges() output into graph data.
|
|
60
67
|
*
|
|
61
68
|
* @param {string[]} nodeIds - Array of node IDs
|
|
62
|
-
* @param {Array} edges - Edge array from graph.getEdges()
|
|
63
|
-
* @returns {
|
|
69
|
+
* @param {Array<{ from: string, to: string, label?: string }>} edges - Edge array from graph.getEdges()
|
|
70
|
+
* @returns {GraphData}
|
|
64
71
|
*/
|
|
65
72
|
export function rawGraphToGraphData(nodeIds, edges) {
|
|
66
73
|
const nodes = (nodeIds ?? []).map((id) => ({ id, label: id }));
|