@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.
Files changed (143) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +16 -0
  3. package/README.md +480 -0
  4. package/SECURITY.md +30 -0
  5. package/bin/git-warp +24 -0
  6. package/bin/warp-graph.js +1574 -0
  7. package/index.d.ts +2366 -0
  8. package/index.js +180 -0
  9. package/package.json +129 -0
  10. package/scripts/install-git-warp.sh +258 -0
  11. package/scripts/uninstall-git-warp.sh +139 -0
  12. package/src/domain/WarpGraph.js +3157 -0
  13. package/src/domain/crdt/Dot.js +160 -0
  14. package/src/domain/crdt/LWW.js +154 -0
  15. package/src/domain/crdt/ORSet.js +371 -0
  16. package/src/domain/crdt/VersionVector.js +222 -0
  17. package/src/domain/entities/GraphNode.js +60 -0
  18. package/src/domain/errors/EmptyMessageError.js +47 -0
  19. package/src/domain/errors/ForkError.js +30 -0
  20. package/src/domain/errors/IndexError.js +23 -0
  21. package/src/domain/errors/OperationAbortedError.js +22 -0
  22. package/src/domain/errors/QueryError.js +39 -0
  23. package/src/domain/errors/SchemaUnsupportedError.js +17 -0
  24. package/src/domain/errors/ShardCorruptionError.js +56 -0
  25. package/src/domain/errors/ShardLoadError.js +57 -0
  26. package/src/domain/errors/ShardValidationError.js +61 -0
  27. package/src/domain/errors/StorageError.js +57 -0
  28. package/src/domain/errors/SyncError.js +30 -0
  29. package/src/domain/errors/TraversalError.js +23 -0
  30. package/src/domain/errors/WarpError.js +31 -0
  31. package/src/domain/errors/WormholeError.js +28 -0
  32. package/src/domain/errors/WriterError.js +39 -0
  33. package/src/domain/errors/index.js +21 -0
  34. package/src/domain/services/AnchorMessageCodec.js +99 -0
  35. package/src/domain/services/BitmapIndexBuilder.js +225 -0
  36. package/src/domain/services/BitmapIndexReader.js +435 -0
  37. package/src/domain/services/BoundaryTransitionRecord.js +463 -0
  38. package/src/domain/services/CheckpointMessageCodec.js +147 -0
  39. package/src/domain/services/CheckpointSerializerV5.js +281 -0
  40. package/src/domain/services/CheckpointService.js +384 -0
  41. package/src/domain/services/CommitDagTraversalService.js +156 -0
  42. package/src/domain/services/DagPathFinding.js +712 -0
  43. package/src/domain/services/DagTopology.js +239 -0
  44. package/src/domain/services/DagTraversal.js +245 -0
  45. package/src/domain/services/Frontier.js +108 -0
  46. package/src/domain/services/GCMetrics.js +101 -0
  47. package/src/domain/services/GCPolicy.js +122 -0
  48. package/src/domain/services/GitLogParser.js +205 -0
  49. package/src/domain/services/HealthCheckService.js +246 -0
  50. package/src/domain/services/HookInstaller.js +326 -0
  51. package/src/domain/services/HttpSyncServer.js +262 -0
  52. package/src/domain/services/IndexRebuildService.js +426 -0
  53. package/src/domain/services/IndexStalenessChecker.js +103 -0
  54. package/src/domain/services/JoinReducer.js +582 -0
  55. package/src/domain/services/KeyCodec.js +113 -0
  56. package/src/domain/services/LegacyAnchorDetector.js +67 -0
  57. package/src/domain/services/LogicalTraversal.js +351 -0
  58. package/src/domain/services/MessageCodecInternal.js +132 -0
  59. package/src/domain/services/MessageSchemaDetector.js +145 -0
  60. package/src/domain/services/MigrationService.js +55 -0
  61. package/src/domain/services/ObserverView.js +265 -0
  62. package/src/domain/services/PatchBuilderV2.js +669 -0
  63. package/src/domain/services/PatchMessageCodec.js +140 -0
  64. package/src/domain/services/ProvenanceIndex.js +337 -0
  65. package/src/domain/services/ProvenancePayload.js +242 -0
  66. package/src/domain/services/QueryBuilder.js +835 -0
  67. package/src/domain/services/StateDiff.js +300 -0
  68. package/src/domain/services/StateSerializerV5.js +156 -0
  69. package/src/domain/services/StreamingBitmapIndexBuilder.js +709 -0
  70. package/src/domain/services/SyncProtocol.js +593 -0
  71. package/src/domain/services/TemporalQuery.js +201 -0
  72. package/src/domain/services/TranslationCost.js +221 -0
  73. package/src/domain/services/TraversalService.js +8 -0
  74. package/src/domain/services/WarpMessageCodec.js +29 -0
  75. package/src/domain/services/WarpStateIndexBuilder.js +127 -0
  76. package/src/domain/services/WormholeService.js +353 -0
  77. package/src/domain/types/TickReceipt.js +285 -0
  78. package/src/domain/types/WarpTypes.js +209 -0
  79. package/src/domain/types/WarpTypesV2.js +200 -0
  80. package/src/domain/utils/CachedValue.js +140 -0
  81. package/src/domain/utils/EventId.js +89 -0
  82. package/src/domain/utils/LRUCache.js +112 -0
  83. package/src/domain/utils/MinHeap.js +114 -0
  84. package/src/domain/utils/RefLayout.js +280 -0
  85. package/src/domain/utils/WriterId.js +205 -0
  86. package/src/domain/utils/cancellation.js +33 -0
  87. package/src/domain/utils/canonicalStringify.js +42 -0
  88. package/src/domain/utils/defaultClock.js +20 -0
  89. package/src/domain/utils/defaultCodec.js +51 -0
  90. package/src/domain/utils/nullLogger.js +21 -0
  91. package/src/domain/utils/roaring.js +181 -0
  92. package/src/domain/utils/shardVersion.js +9 -0
  93. package/src/domain/warp/PatchSession.js +217 -0
  94. package/src/domain/warp/Writer.js +181 -0
  95. package/src/hooks/post-merge.sh +60 -0
  96. package/src/infrastructure/adapters/BunHttpAdapter.js +225 -0
  97. package/src/infrastructure/adapters/ClockAdapter.js +57 -0
  98. package/src/infrastructure/adapters/ConsoleLogger.js +150 -0
  99. package/src/infrastructure/adapters/DenoHttpAdapter.js +230 -0
  100. package/src/infrastructure/adapters/GitGraphAdapter.js +787 -0
  101. package/src/infrastructure/adapters/GlobalClockAdapter.js +5 -0
  102. package/src/infrastructure/adapters/NoOpLogger.js +62 -0
  103. package/src/infrastructure/adapters/NodeCryptoAdapter.js +32 -0
  104. package/src/infrastructure/adapters/NodeHttpAdapter.js +98 -0
  105. package/src/infrastructure/adapters/PerformanceClockAdapter.js +5 -0
  106. package/src/infrastructure/adapters/WebCryptoAdapter.js +121 -0
  107. package/src/infrastructure/codecs/CborCodec.js +384 -0
  108. package/src/ports/BlobPort.js +30 -0
  109. package/src/ports/ClockPort.js +25 -0
  110. package/src/ports/CodecPort.js +25 -0
  111. package/src/ports/CommitPort.js +114 -0
  112. package/src/ports/ConfigPort.js +31 -0
  113. package/src/ports/CryptoPort.js +38 -0
  114. package/src/ports/GraphPersistencePort.js +57 -0
  115. package/src/ports/HttpServerPort.js +25 -0
  116. package/src/ports/IndexStoragePort.js +39 -0
  117. package/src/ports/LoggerPort.js +68 -0
  118. package/src/ports/RefPort.js +51 -0
  119. package/src/ports/TreePort.js +51 -0
  120. package/src/visualization/index.js +26 -0
  121. package/src/visualization/layouts/converters.js +75 -0
  122. package/src/visualization/layouts/elkAdapter.js +86 -0
  123. package/src/visualization/layouts/elkLayout.js +95 -0
  124. package/src/visualization/layouts/index.js +29 -0
  125. package/src/visualization/renderers/ascii/box.js +16 -0
  126. package/src/visualization/renderers/ascii/check.js +271 -0
  127. package/src/visualization/renderers/ascii/colors.js +13 -0
  128. package/src/visualization/renderers/ascii/formatters.js +73 -0
  129. package/src/visualization/renderers/ascii/graph.js +344 -0
  130. package/src/visualization/renderers/ascii/history.js +335 -0
  131. package/src/visualization/renderers/ascii/index.js +14 -0
  132. package/src/visualization/renderers/ascii/info.js +245 -0
  133. package/src/visualization/renderers/ascii/materialize.js +255 -0
  134. package/src/visualization/renderers/ascii/path.js +240 -0
  135. package/src/visualization/renderers/ascii/progress.js +32 -0
  136. package/src/visualization/renderers/ascii/symbols.js +33 -0
  137. package/src/visualization/renderers/ascii/table.js +19 -0
  138. package/src/visualization/renderers/browser/index.js +1 -0
  139. package/src/visualization/renderers/svg/index.js +159 -0
  140. package/src/visualization/utils/ansi.js +14 -0
  141. package/src/visualization/utils/time.js +40 -0
  142. package/src/visualization/utils/truncate.js +40 -0
  143. package/src/visualization/utils/unicode.js +52 -0
@@ -0,0 +1,384 @@
1
+ /**
2
+ * @fileoverview CBOR Codec with Canonical/Deterministic Encoding.
3
+ *
4
+ * This module wraps the `cbor-x` library to provide **canonical CBOR encoding**
5
+ * for WARP graph patches. Canonical encoding is critical for WARP's content-addressed
6
+ * storage model: the same logical patch must always produce identical bytes so that
7
+ * Git SHA comparisons work correctly.
8
+ *
9
+ * ## Why Canonical Encoding Matters
10
+ *
11
+ * WARP stores patches as Git commits where the commit message contains CBOR-encoded
12
+ * patch data. Git identifies commits by SHA-256 hash of their content. If the same
13
+ * logical patch could encode to different byte sequences (e.g., due to non-deterministic
14
+ * object key ordering), then:
15
+ *
16
+ * 1. **Duplicate patches** could exist with different SHAs
17
+ * 2. **Deduplication** would fail - the same operation stored multiple times
18
+ * 3. **Causality tracking** would break - version vectors rely on exact SHA matching
19
+ * 4. **Sync protocols** would transfer redundant data
20
+ *
21
+ * ## The cbor-x Limitation
22
+ *
23
+ * Unlike some CBOR libraries, `cbor-x` does not provide built-in canonical/deterministic
24
+ * encoding. JavaScript object key enumeration order is insertion-order (per ES2015+),
25
+ * which means `{ b: 1, a: 2 }` and `{ a: 2, b: 1 }` would encode differently despite
26
+ * being logically equivalent.
27
+ *
28
+ * ## Our Solution: Pre-encoding Key Sorting
29
+ *
30
+ * Before encoding, we recursively sort all object keys using JavaScript's default
31
+ * lexicographic sort (via {@link sortKeys}). This produces deterministic,
32
+ * lexicographically-sorted CBOR for WARP patches.
33
+ *
34
+ * **Important**: This is NOT RFC 7049 Section 3.9 canonical CBOR, which requires
35
+ * byte-length-first ordering (shorter keys before longer keys, then lexicographic
36
+ * within same length). Our approach uses simple lexicographic ordering, which is
37
+ * sufficient for WARP's content-addressing needs but not interoperable with systems
38
+ * expecting strict RFC 7049 canonical form.
39
+ *
40
+ * This ensures:
41
+ *
42
+ * - **Determinism**: Same input always produces identical bytes
43
+ * - **Verifiability**: Patches can be re-encoded and compared byte-for-byte
44
+ *
45
+ * ## Performance Considerations
46
+ *
47
+ * The key sorting step adds O(n log n) overhead per encoding operation where n is
48
+ * the total number of keys across all nested objects. For typical WARP patches
49
+ * (tens to hundreds of keys), this overhead is negligible compared to I/O costs.
50
+ *
51
+ * @module infrastructure/codecs/CborCodec
52
+ * @see {@link https://cbor.io/} for CBOR specification
53
+ * @see {@link https://tools.ietf.org/html/rfc7049#section-3.9} for Canonical CBOR
54
+ * @see {@link https://github.com/kriszyp/cbor-x} for cbor-x library
55
+ */
56
+
57
+ import { Encoder, decode as cborDecode } from 'cbor-x';
58
+ import CodecPort from '../../ports/CodecPort.js';
59
+
60
+ /**
61
+ * Pre-configured cbor-x encoder instance.
62
+ *
63
+ * Configuration options:
64
+ * - `useRecords: false` - Disables cbor-x's record extension (which uses a custom
65
+ * CBOR tag for repeated object structures). We use standard CBOR maps for maximum
66
+ * interoperability with other CBOR implementations.
67
+ * - `mapsAsObjects: true` - Decodes CBOR maps as JavaScript plain objects rather than
68
+ * Map instances. This matches the expected input format for WARP patches.
69
+ *
70
+ * @type {Encoder}
71
+ * @private
72
+ */
73
+ const encoder = new Encoder({
74
+ useRecords: false,
75
+ mapsAsObjects: true,
76
+ });
77
+
78
+ /**
79
+ * Checks if a value is a plain object (constructed via Object or Object.create(null)).
80
+ *
81
+ * @param {unknown} value - The value to check
82
+ * @returns {boolean} True if value is a plain object
83
+ * @private
84
+ */
85
+ function isPlainObject(value) {
86
+ return typeof value === 'object' && (value.constructor === Object || value.constructor === undefined);
87
+ }
88
+
89
+ /**
90
+ * Sorts the keys of a plain object and recursively processes values.
91
+ *
92
+ * @param {Object} obj - The plain object to process
93
+ * @returns {Object} A new object with sorted keys
94
+ * @private
95
+ */
96
+ function sortPlainObject(obj) {
97
+ const sorted = {};
98
+ const keys = Object.keys(obj).sort();
99
+ for (const key of keys) {
100
+ sorted[key] = sortKeys(obj[key]);
101
+ }
102
+ return sorted;
103
+ }
104
+
105
+ /**
106
+ * Converts a Map to a sorted plain object with recursive value processing.
107
+ * Validates that all Map keys are strings (required for CBOR encoding).
108
+ *
109
+ * @param {Map} map - The Map instance to convert
110
+ * @returns {Object} A plain object with sorted keys
111
+ * @throws {TypeError} If any Map key is not a string
112
+ * @private
113
+ */
114
+ function sortMapToObject(map) {
115
+ const keys = Array.from(map.keys());
116
+ for (const key of keys) {
117
+ if (typeof key !== 'string') {
118
+ throw new TypeError(`Map keys must be strings for CBOR encoding, got ${typeof key}`);
119
+ }
120
+ }
121
+ const sorted = {};
122
+ keys.sort();
123
+ for (const key of keys) {
124
+ sorted[key] = sortKeys(map.get(key));
125
+ }
126
+ return sorted;
127
+ }
128
+
129
+ /**
130
+ * Recursively sorts object keys to ensure deterministic/canonical encoding.
131
+ *
132
+ * This function transforms any JavaScript value into an equivalent structure
133
+ * where all object keys are sorted lexicographically. This is necessary because
134
+ * cbor-x encodes object keys in enumeration order, and JavaScript object key
135
+ * enumeration follows insertion order.
136
+ *
137
+ * ## Transformation Rules
138
+ *
139
+ * | Input Type | Output Type | Transformation |
140
+ * |----------------|----------------|-----------------------------------------|
141
+ * | `null` | `null` | Pass through |
142
+ * | `undefined` | `undefined` | Pass through |
143
+ * | Primitive | Primitive | Pass through (number, string, boolean, bigint) |
144
+ * | Array | Array | Elements recursively sorted |
145
+ * | Plain Object | Plain Object | Keys sorted, values recursively sorted |
146
+ * | Map | Plain Object | Converted to object with sorted keys (keys must be strings) |
147
+ * | Other objects | Same object | Pass through (Date, Buffer, etc.) |
148
+ *
149
+ * ## Why Only Plain Objects?
150
+ *
151
+ * We only sort keys for plain objects (`value.constructor === Object`) because:
152
+ * 1. Plain objects are the standard structure for WARP patches
153
+ * 2. Special objects (Date, Buffer, TypedArray) have specific CBOR encodings
154
+ * 3. Class instances may have non-enumerable or symbol keys that shouldn't be modified
155
+ *
156
+ * ## Complexity
157
+ *
158
+ * Time: O(n log n) where n is total number of keys across all nested objects
159
+ * Space: O(d) where d is maximum nesting depth (recursive call stack)
160
+ *
161
+ * @param {unknown} value - The value to transform. Can be any JavaScript value.
162
+ * @returns {unknown} A new value with all object keys sorted. Primitives are
163
+ * returned as-is. Objects are shallow-copied with sorted keys. Arrays are
164
+ * shallow-copied with transformed elements.
165
+ *
166
+ * @example
167
+ * // Object keys are sorted lexicographically
168
+ * sortKeys({ z: 1, a: 2 })
169
+ * // Returns: { a: 2, z: 1 }
170
+ *
171
+ * @example
172
+ * // Nested objects are recursively sorted
173
+ * sortKeys({ outer: { z: 1, a: 2 }, b: 3 })
174
+ * // Returns: { b: 3, outer: { a: 2, z: 1 } }
175
+ *
176
+ * @example
177
+ * // Arrays preserve order but sort object elements
178
+ * sortKeys([{ b: 1, a: 2 }, { d: 3, c: 4 }])
179
+ * // Returns: [{ a: 2, b: 1 }, { c: 4, d: 3 }]
180
+ *
181
+ * @example
182
+ * // Map instances are converted to sorted objects
183
+ * sortKeys(new Map([['z', 1], ['a', 2]]))
184
+ * // Returns: { a: 2, z: 1 }
185
+ *
186
+ * @private
187
+ */
188
+ function sortKeys(value) {
189
+ // Nullish values pass through
190
+ if (value === null || value === undefined) {
191
+ return value;
192
+ }
193
+
194
+ // Arrays: recursively sort elements
195
+ if (Array.isArray(value)) {
196
+ return value.map(sortKeys);
197
+ }
198
+
199
+ // Plain objects: sort keys and recursively process values
200
+ if (isPlainObject(value)) {
201
+ return sortPlainObject(value);
202
+ }
203
+
204
+ // Map instances: convert to sorted object
205
+ if (value instanceof Map) {
206
+ return sortMapToObject(value);
207
+ }
208
+
209
+ // Primitive values (number, string, boolean, bigint) and other objects pass through
210
+ return value;
211
+ }
212
+
213
+ /**
214
+ * Encodes data to canonical CBOR bytes with deterministic output.
215
+ *
216
+ * This function guarantees that logically equivalent inputs always produce
217
+ * byte-identical outputs. It achieves this by recursively sorting all object
218
+ * keys before encoding, ensuring consistent key ordering regardless of how
219
+ * the input object was constructed.
220
+ *
221
+ * ## Determinism Guarantee
222
+ *
223
+ * The following invariant always holds:
224
+ * ```javascript
225
+ * // Logically equivalent inputs produce identical bytes
226
+ * encode({ b: 1, a: 2 }).equals(encode({ a: 2, b: 1 })) // true
227
+ *
228
+ * // Re-encoding decoded data produces identical bytes
229
+ * const original = encode(data);
230
+ * const roundTrip = encode(decode(original));
231
+ * original.equals(roundTrip) // true
232
+ * ```
233
+ *
234
+ * ## CBOR Type Mappings
235
+ *
236
+ * | JavaScript Type | CBOR Major Type | Notes |
237
+ * |-----------------|-----------------|----------------------------------|
238
+ * | number (int) | 0 or 1 | Positive or negative integer |
239
+ * | number (float) | 7 | IEEE 754 float |
240
+ * | string | 3 | UTF-8 text string |
241
+ * | boolean | 7 | Simple value (true=21, false=20)|
242
+ * | null | 7 | Simple value (null=22) |
243
+ * | undefined | 7 | Simple value (undefined=23) |
244
+ * | Array | 4 | Array of data items |
245
+ * | Object | 5 | Map of pairs (keys sorted) |
246
+ * | Buffer | 2 | Byte string |
247
+ * | BigInt | 0, 1, or 6 | Integer or tagged bignum |
248
+ *
249
+ * @param {unknown} data - The data to encode. Can be any JSON-serializable value,
250
+ * plus Buffer, BigInt, and other types supported by cbor-x. Objects have their
251
+ * keys sorted before encoding.
252
+ * @returns {Buffer} CBOR-encoded bytes. The buffer is a Node.js Buffer instance
253
+ * that can be written directly to files, network sockets, or Git objects.
254
+ *
255
+ * @example
256
+ * // Encode a simple patch operation
257
+ * const patch = {
258
+ * schema: 2,
259
+ * ops: [
260
+ * { op: 'NodeAdd', id: 'user:alice', dot: ['w1', 1] },
261
+ * { op: 'PropSet', target: 'user:alice', key: 'name', value: 'Alice' }
262
+ * ]
263
+ * };
264
+ * const bytes = encode(patch);
265
+ * console.log(bytes.length); // ~80 bytes (much smaller than JSON)
266
+ *
267
+ * @example
268
+ * // Demonstrating determinism - key order doesn't matter
269
+ * const bytes1 = encode({ z: 1, a: 2, m: 3 });
270
+ * const bytes2 = encode({ a: 2, m: 3, z: 1 });
271
+ * const bytes3 = encode({ m: 3, z: 1, a: 2 });
272
+ * console.log(bytes1.equals(bytes2)); // true
273
+ * console.log(bytes2.equals(bytes3)); // true
274
+ *
275
+ * @example
276
+ * // Canonical encoding enables content-addressing
277
+ * const sha = crypto.createHash('sha256').update(encode(data)).digest('hex');
278
+ * // Same data always produces the same SHA
279
+ */
280
+ export function encode(data) {
281
+ const sorted = sortKeys(data);
282
+ return encoder.encode(sorted);
283
+ }
284
+
285
+ /**
286
+ * Decodes CBOR bytes to a JavaScript value.
287
+ *
288
+ * This function deserializes CBOR-encoded data back into JavaScript values.
289
+ * It uses cbor-x's native decoder which is optimized for performance.
290
+ *
291
+ * ## CBOR to JavaScript Type Mappings
292
+ *
293
+ * | CBOR Major Type | JavaScript Type | Notes |
294
+ * |-----------------|-----------------|----------------------------------|
295
+ * | 0 (pos int) | number | Up to Number.MAX_SAFE_INTEGER |
296
+ * | 1 (neg int) | number | Down to Number.MIN_SAFE_INTEGER |
297
+ * | 2 (byte string) | Buffer | Node.js Buffer |
298
+ * | 3 (text string) | string | UTF-8 decoded |
299
+ * | 4 (array) | Array | Recursive decode |
300
+ * | 5 (map) | Object | Due to mapsAsObjects: true |
301
+ * | 6 (tagged) | varies | Depends on tag number |
302
+ * | 7 (simple) | varies | true, false, null, undefined |
303
+ *
304
+ * ## Large Integers
305
+ *
306
+ * CBOR integers outside JavaScript's safe integer range (2^53 - 1) are decoded
307
+ * as BigInt values. Ensure your code handles both number and BigInt if you
308
+ * expect large values.
309
+ *
310
+ * ## Round-Trip Safety
311
+ *
312
+ * Values encoded with {@link encode} can be round-tripped through decode without
313
+ * data loss, and re-encoding will produce byte-identical output due to our
314
+ * canonical encoding strategy:
315
+ *
316
+ * ```javascript
317
+ * const original = { foo: 'bar', count: 42 };
318
+ * const bytes = encode(original);
319
+ * const decoded = decode(bytes);
320
+ * const reEncoded = encode(decoded);
321
+ * bytes.equals(reEncoded); // true - canonical encoding is idempotent
322
+ * ```
323
+ *
324
+ * @param {Buffer|Uint8Array} buffer - CBOR-encoded bytes to decode. Accepts
325
+ * Node.js Buffer, Uint8Array, or any ArrayBufferView.
326
+ * @returns {unknown} The decoded JavaScript value. Type depends on the encoded
327
+ * CBOR data - could be a primitive, array, or plain object.
328
+ * @throws {Error} If the buffer contains invalid CBOR data or is truncated.
329
+ *
330
+ * @example
331
+ * // Decode a CBOR-encoded patch from a Git commit message
332
+ * const commitMessage = await adapter.showNode(sha);
333
+ * const patchData = decode(Buffer.from(commitMessage, 'binary'));
334
+ * console.log(patchData.schema); // 2
335
+ * console.log(patchData.ops.length); // number of operations
336
+ *
337
+ * @example
338
+ * // Error handling for invalid data
339
+ * try {
340
+ * const data = decode(Buffer.from([0xff, 0xff])); // Invalid CBOR
341
+ * } catch (err) {
342
+ * console.error('Invalid CBOR:', err.message);
343
+ * }
344
+ *
345
+ * @example
346
+ * // Handling large integers
347
+ * const bytes = encode({ bigNum: BigInt('9007199254740993') }); // > MAX_SAFE_INTEGER
348
+ * const decoded = decode(bytes);
349
+ * console.log(typeof decoded.bigNum); // 'bigint'
350
+ */
351
+ export function decode(buffer) {
352
+ return cborDecode(buffer);
353
+ }
354
+
355
+ /**
356
+ * Default export providing both encode and decode functions.
357
+ *
358
+ * @type {{ encode: typeof encode, decode: typeof decode }}
359
+ *
360
+ * @example
361
+ * import cbor from './CborCodec.js';
362
+ *
363
+ * const bytes = cbor.encode({ key: 'value' });
364
+ * const data = cbor.decode(bytes);
365
+ */
366
+ /**
367
+ * CBOR codec implementing CodecPort with canonical/deterministic encoding.
368
+ *
369
+ * @class CborCodec
370
+ * @extends CodecPort
371
+ */
372
+ export class CborCodec extends CodecPort {
373
+ /** @inheritdoc */
374
+ encode(data) {
375
+ return encode(data);
376
+ }
377
+
378
+ /** @inheritdoc */
379
+ decode(buffer) {
380
+ return decode(buffer);
381
+ }
382
+ }
383
+
384
+ export default new CborCodec();
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Port for Git blob operations.
3
+ *
4
+ * Defines the contract for writing and reading Git blob objects.
5
+ * This is one of five focused ports extracted from GraphPersistencePort.
6
+ *
7
+ * @abstract
8
+ * @see GraphPersistencePort - Composite port implementing all five focused ports
9
+ */
10
+ export default class BlobPort {
11
+ /**
12
+ * Writes content as a Git blob and returns its OID.
13
+ * @param {Buffer|string} content - The blob content to write
14
+ * @returns {Promise<string>} The Git OID of the created blob
15
+ * @throws {Error} If not implemented by a concrete adapter
16
+ */
17
+ async writeBlob(_content) {
18
+ throw new Error('BlobPort.writeBlob() not implemented');
19
+ }
20
+
21
+ /**
22
+ * Reads the content of a Git blob.
23
+ * @param {string} oid - The blob OID to read
24
+ * @returns {Promise<Buffer>} The blob content
25
+ * @throws {Error} If not implemented by a concrete adapter
26
+ */
27
+ async readBlob(_oid) {
28
+ throw new Error('BlobPort.readBlob() not implemented');
29
+ }
30
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Port for time-related operations.
3
+ *
4
+ * Abstracts platform-specific timing APIs to keep domain services pure.
5
+ * Implementations can use performance.now(), Date.now(), or test doubles.
6
+ */
7
+ export default class ClockPort {
8
+ /**
9
+ * Returns a high-resolution timestamp in milliseconds.
10
+ * Used for measuring durations (latency, elapsed time).
11
+ * @returns {number} Timestamp in milliseconds
12
+ */
13
+ now() {
14
+ throw new Error('Not implemented');
15
+ }
16
+
17
+ /**
18
+ * Returns the current wall-clock time as an ISO string.
19
+ * Used for timestamps in logs and cached results.
20
+ * @returns {string} ISO 8601 timestamp
21
+ */
22
+ timestamp() {
23
+ throw new Error('Not implemented');
24
+ }
25
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Port for serialization/deserialization operations.
3
+ *
4
+ * Abstracts codec implementations to allow substitution of
5
+ * CBOR, JSON, MessagePack, or other formats.
6
+ */
7
+ export default class CodecPort {
8
+ /**
9
+ * Encodes data to binary format.
10
+ * @param {unknown} data - Data to encode
11
+ * @returns {Buffer|Uint8Array} Encoded bytes
12
+ */
13
+ encode(_data) {
14
+ throw new Error('CodecPort.encode() not implemented');
15
+ }
16
+
17
+ /**
18
+ * Decodes binary data back to a JavaScript value.
19
+ * @param {Buffer|Uint8Array} bytes - Encoded bytes to decode
20
+ * @returns {unknown} Decoded value
21
+ */
22
+ decode(_bytes) {
23
+ throw new Error('CodecPort.decode() not implemented');
24
+ }
25
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Port for Git commit operations.
3
+ *
4
+ * Defines the contract for creating, reading, and querying Git commits.
5
+ * This is one of five focused ports extracted from GraphPersistencePort.
6
+ *
7
+ * @abstract
8
+ * @see GraphPersistencePort - Composite port implementing all five focused ports
9
+ */
10
+ export default class CommitPort {
11
+ /**
12
+ * Creates a commit pointing to the empty tree.
13
+ * @param {Object} options
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
17
+ * @returns {Promise<string>} The SHA of the created commit
18
+ * @throws {Error} If not implemented by a concrete adapter
19
+ */
20
+ async commitNode(_options) {
21
+ throw new Error('CommitPort.commitNode() not implemented');
22
+ }
23
+
24
+ /**
25
+ * Retrieves the raw commit message for a given SHA.
26
+ * @param {string} sha - The commit SHA to read
27
+ * @returns {Promise<string>} The raw commit message content
28
+ * @throws {Error} If not implemented by a concrete adapter
29
+ */
30
+ async showNode(_sha) {
31
+ throw new Error('CommitPort.showNode() not implemented');
32
+ }
33
+
34
+ /**
35
+ * Gets full commit metadata for a node.
36
+ * @param {string} sha - The commit SHA to retrieve
37
+ * @returns {Promise<{sha: string, message: string, author: string, date: string, parents: string[]}>}
38
+ * Full commit metadata including SHA, message, author, date, and parent SHAs
39
+ * @throws {Error} If not implemented by a concrete adapter
40
+ */
41
+ async getNodeInfo(_sha) {
42
+ throw new Error('CommitPort.getNodeInfo() not implemented');
43
+ }
44
+
45
+ /**
46
+ * Returns raw git log output for a ref.
47
+ * @param {Object} options
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
51
+ * @returns {Promise<string>} The raw log output
52
+ * @throws {Error} If not implemented by a concrete adapter
53
+ */
54
+ async logNodes(_options) {
55
+ throw new Error('CommitPort.logNodes() not implemented');
56
+ }
57
+
58
+ /**
59
+ * Streams git log output for a ref.
60
+ * @param {Object} options
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
64
+ * @returns {Promise<import('node:stream').Readable>} A readable stream of log output
65
+ * @throws {Error} If not implemented by a concrete adapter
66
+ */
67
+ async logNodesStream(_options) {
68
+ throw new Error('CommitPort.logNodesStream() not implemented');
69
+ }
70
+
71
+ /**
72
+ * Counts nodes reachable from a ref without loading them into memory.
73
+ * @param {string} ref - Git ref to count from (e.g., 'HEAD', 'main', or a SHA)
74
+ * @returns {Promise<number>} The count of reachable nodes
75
+ * @throws {Error} If not implemented by a concrete adapter
76
+ */
77
+ async countNodes(_ref) {
78
+ throw new Error('CommitPort.countNodes() not implemented');
79
+ }
80
+
81
+ /**
82
+ * Creates a commit pointing to a specified tree (not the empty tree).
83
+ * Used by CheckpointService and PatchBuilderV2 for tree-backed commits.
84
+ * @param {Object} options
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
89
+ * @returns {Promise<string>} The SHA of the created commit
90
+ * @throws {Error} If not implemented by a concrete adapter
91
+ */
92
+ async commitNodeWithTree(_options) {
93
+ throw new Error('CommitPort.commitNodeWithTree() not implemented');
94
+ }
95
+
96
+ /**
97
+ * Checks whether a commit exists in the repository.
98
+ * @param {string} sha - The commit SHA to check
99
+ * @returns {Promise<boolean>} True if the commit exists
100
+ * @throws {Error} If not implemented by a concrete adapter
101
+ */
102
+ async nodeExists(_sha) {
103
+ throw new Error('CommitPort.nodeExists() not implemented');
104
+ }
105
+
106
+ /**
107
+ * Pings the repository to verify accessibility.
108
+ * @returns {Promise<{ok: boolean, latencyMs: number}>} Health check result with latency
109
+ * @throws {Error} If not implemented by a concrete adapter
110
+ */
111
+ async ping() {
112
+ throw new Error('CommitPort.ping() not implemented');
113
+ }
114
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Port for Git config operations.
3
+ *
4
+ * Defines the contract for reading and writing Git configuration values.
5
+ * This is one of five focused ports extracted from GraphPersistencePort.
6
+ *
7
+ * @abstract
8
+ * @see GraphPersistencePort - Composite port implementing all five focused ports
9
+ */
10
+ export default class ConfigPort {
11
+ /**
12
+ * Reads a git config value.
13
+ * @param {string} key - The config key to read (e.g., 'warp.writerId.events')
14
+ * @returns {Promise<string|null>} The config value, or null if not set
15
+ * @throws {Error} If not implemented by a concrete adapter
16
+ */
17
+ async configGet(_key) {
18
+ throw new Error('ConfigPort.configGet() not implemented');
19
+ }
20
+
21
+ /**
22
+ * Sets a git config value.
23
+ * @param {string} key - The config key to set (e.g., 'warp.writerId.events')
24
+ * @param {string} value - The value to set
25
+ * @returns {Promise<void>}
26
+ * @throws {Error} If not implemented by a concrete adapter
27
+ */
28
+ async configSet(_key, _value) {
29
+ throw new Error('ConfigPort.configSet() not implemented');
30
+ }
31
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Port for cryptographic operations.
3
+ *
4
+ * Abstracts platform-specific crypto APIs to keep domain services pure.
5
+ * Implementations can use Node.js crypto, Web Crypto API, or test doubles.
6
+ */
7
+ export default class CryptoPort {
8
+ /**
9
+ * Computes a hash digest of the given data.
10
+ * @param {string} algorithm - Hash algorithm (e.g. 'sha1', 'sha256')
11
+ * @param {string|Buffer|Uint8Array} data - Data to hash
12
+ * @returns {Promise<string>} Hex-encoded digest
13
+ */
14
+ async hash(_algorithm, _data) {
15
+ throw new Error('CryptoPort.hash() not implemented');
16
+ }
17
+
18
+ /**
19
+ * Computes an HMAC of the given data.
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
+ * @returns {Promise<Buffer|Uint8Array>} Raw HMAC digest
24
+ */
25
+ async hmac(_algorithm, _key, _data) {
26
+ throw new Error('CryptoPort.hmac() not implemented');
27
+ }
28
+
29
+ /**
30
+ * Constant-time comparison of two buffers.
31
+ * @param {Buffer|Uint8Array} a - First buffer
32
+ * @param {Buffer|Uint8Array} b - Second buffer
33
+ * @returns {boolean} True if buffers are equal
34
+ */
35
+ timingSafeEqual(_a, _b) {
36
+ throw new Error('CryptoPort.timingSafeEqual() not implemented');
37
+ }
38
+ }