@git-stunts/git-warp 11.2.1 → 11.3.3

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 (111) hide show
  1. package/bin/cli/commands/check.js +2 -2
  2. package/bin/cli/commands/doctor/checks.js +12 -12
  3. package/bin/cli/commands/doctor/index.js +2 -2
  4. package/bin/cli/commands/doctor/types.js +1 -1
  5. package/bin/cli/commands/history.js +12 -5
  6. package/bin/cli/commands/install-hooks.js +5 -5
  7. package/bin/cli/commands/materialize.js +2 -2
  8. package/bin/cli/commands/patch.js +142 -0
  9. package/bin/cli/commands/path.js +4 -4
  10. package/bin/cli/commands/query.js +54 -13
  11. package/bin/cli/commands/registry.js +4 -0
  12. package/bin/cli/commands/seek.js +17 -11
  13. package/bin/cli/commands/tree.js +230 -0
  14. package/bin/cli/commands/trust.js +3 -3
  15. package/bin/cli/commands/verify-audit.js +8 -7
  16. package/bin/cli/commands/view.js +6 -5
  17. package/bin/cli/infrastructure.js +26 -12
  18. package/bin/cli/shared.js +2 -2
  19. package/bin/cli/types.js +19 -8
  20. package/bin/presenters/index.js +35 -9
  21. package/bin/presenters/json.js +14 -12
  22. package/bin/presenters/text.js +155 -33
  23. package/index.d.ts +82 -22
  24. package/package.json +3 -2
  25. package/src/domain/WarpGraph.js +4 -1
  26. package/src/domain/crdt/ORSet.js +8 -8
  27. package/src/domain/errors/EmptyMessageError.js +2 -2
  28. package/src/domain/errors/ForkError.js +1 -1
  29. package/src/domain/errors/IndexError.js +1 -1
  30. package/src/domain/errors/OperationAbortedError.js +1 -1
  31. package/src/domain/errors/QueryError.js +1 -1
  32. package/src/domain/errors/SchemaUnsupportedError.js +1 -1
  33. package/src/domain/errors/ShardCorruptionError.js +2 -2
  34. package/src/domain/errors/ShardLoadError.js +2 -2
  35. package/src/domain/errors/ShardValidationError.js +4 -4
  36. package/src/domain/errors/StorageError.js +2 -2
  37. package/src/domain/errors/SyncError.js +1 -1
  38. package/src/domain/errors/TraversalError.js +1 -1
  39. package/src/domain/errors/TrustError.js +1 -1
  40. package/src/domain/errors/WarpError.js +2 -2
  41. package/src/domain/errors/WormholeError.js +1 -1
  42. package/src/domain/services/AuditReceiptService.js +6 -6
  43. package/src/domain/services/AuditVerifierService.js +52 -38
  44. package/src/domain/services/BitmapIndexBuilder.js +3 -3
  45. package/src/domain/services/BitmapIndexReader.js +28 -19
  46. package/src/domain/services/BoundaryTransitionRecord.js +18 -17
  47. package/src/domain/services/CheckpointSerializerV5.js +17 -16
  48. package/src/domain/services/CheckpointService.js +2 -2
  49. package/src/domain/services/CommitDagTraversalService.js +13 -13
  50. package/src/domain/services/DagPathFinding.js +7 -7
  51. package/src/domain/services/DagTopology.js +1 -1
  52. package/src/domain/services/DagTraversal.js +1 -1
  53. package/src/domain/services/HealthCheckService.js +1 -1
  54. package/src/domain/services/HookInstaller.js +1 -1
  55. package/src/domain/services/HttpSyncServer.js +92 -41
  56. package/src/domain/services/IndexRebuildService.js +7 -7
  57. package/src/domain/services/IndexStalenessChecker.js +4 -3
  58. package/src/domain/services/JoinReducer.js +11 -11
  59. package/src/domain/services/LogicalTraversal.js +1 -1
  60. package/src/domain/services/MessageCodecInternal.js +1 -1
  61. package/src/domain/services/MigrationService.js +1 -1
  62. package/src/domain/services/ObserverView.js +8 -8
  63. package/src/domain/services/PatchBuilderV2.js +42 -26
  64. package/src/domain/services/ProvenanceIndex.js +1 -1
  65. package/src/domain/services/ProvenancePayload.js +1 -1
  66. package/src/domain/services/QueryBuilder.js +3 -3
  67. package/src/domain/services/StateDiff.js +14 -11
  68. package/src/domain/services/StateSerializerV5.js +2 -2
  69. package/src/domain/services/StreamingBitmapIndexBuilder.js +26 -24
  70. package/src/domain/services/SyncAuthService.js +3 -2
  71. package/src/domain/services/SyncProtocol.js +25 -11
  72. package/src/domain/services/TemporalQuery.js +9 -6
  73. package/src/domain/services/TranslationCost.js +7 -5
  74. package/src/domain/services/WormholeService.js +16 -7
  75. package/src/domain/trust/TrustCanonical.js +3 -3
  76. package/src/domain/trust/TrustEvaluator.js +18 -3
  77. package/src/domain/trust/TrustRecordService.js +30 -23
  78. package/src/domain/trust/TrustStateBuilder.js +21 -8
  79. package/src/domain/trust/canonical.js +6 -6
  80. package/src/domain/types/TickReceipt.js +1 -1
  81. package/src/domain/types/WarpErrors.js +45 -0
  82. package/src/domain/types/WarpOptions.js +29 -0
  83. package/src/domain/types/WarpPersistence.js +41 -0
  84. package/src/domain/types/WarpTypes.js +2 -2
  85. package/src/domain/types/WarpTypesV2.js +2 -2
  86. package/src/domain/utils/MinHeap.js +6 -5
  87. package/src/domain/utils/canonicalStringify.js +5 -4
  88. package/src/domain/utils/roaring.js +31 -5
  89. package/src/domain/warp/PatchSession.js +9 -18
  90. package/src/domain/warp/_wiredMethods.d.ts +199 -45
  91. package/src/domain/warp/checkpoint.methods.js +5 -1
  92. package/src/domain/warp/fork.methods.js +2 -2
  93. package/src/domain/warp/materialize.methods.js +55 -5
  94. package/src/domain/warp/materializeAdvanced.methods.js +15 -4
  95. package/src/domain/warp/patch.methods.js +54 -29
  96. package/src/domain/warp/provenance.methods.js +5 -3
  97. package/src/domain/warp/query.methods.js +6 -5
  98. package/src/domain/warp/sync.methods.js +16 -11
  99. package/src/globals.d.ts +64 -0
  100. package/src/infrastructure/adapters/BunHttpAdapter.js +14 -9
  101. package/src/infrastructure/adapters/CasSeekCacheAdapter.js +9 -4
  102. package/src/infrastructure/adapters/DenoHttpAdapter.js +5 -6
  103. package/src/infrastructure/adapters/GitGraphAdapter.js +14 -12
  104. package/src/infrastructure/adapters/NodeHttpAdapter.js +2 -2
  105. package/src/infrastructure/adapters/WebCryptoAdapter.js +2 -2
  106. package/src/visualization/layouts/converters.js +2 -2
  107. package/src/visualization/layouts/elkAdapter.js +1 -1
  108. package/src/visualization/layouts/elkLayout.js +10 -7
  109. package/src/visualization/layouts/index.js +1 -1
  110. package/src/visualization/renderers/ascii/seek.js +16 -6
  111. package/src/visualization/renderers/svg/index.js +1 -1
@@ -18,9 +18,19 @@ import { decodePatchMessage, detectMessageKind } from '../services/WarpMessageCo
18
18
  import { Writer } from './Writer.js';
19
19
  import { generateWriterId, resolveWriterId } from '../utils/WriterId.js';
20
20
 
21
+ /** @typedef {import('../types/WarpPersistence.js').CorePersistence} CorePersistence */
22
+
21
23
  /**
22
24
  * Creates a new PatchBuilderV2 for this graph.
23
25
  *
26
+ * In multi-writer scenarios, call `materialize()` (or a query method that
27
+ * auto-materializes) before creating a patch so that `_maxObservedLamport`
28
+ * reflects all known writers. Without this, `_nextLamport()` still produces
29
+ * locally-monotonic ticks (`Math.max(ownTick, _maxObservedLamport) + 1`),
30
+ * and `PatchBuilderV2.commit()` re-reads the writer's own ref at commit
31
+ * time, so correctness is preserved — but the tick may be lower than
32
+ * necessary, losing LWW tiebreakers against other writers.
33
+ *
24
34
  * @this {import('../WarpGraph.js').default}
25
35
  * @returns {Promise<PatchBuilderV2>} A new patch builder
26
36
  */
@@ -52,6 +62,9 @@ export async function createPatch() {
52
62
  * Not reentrant: calling `graph.patch()` inside a callback throws.
53
63
  * Use `createPatch()` directly for advanced multi-patch workflows.
54
64
  *
65
+ * **Multi-writer note:** call `materialize()` before `patch()` so that
66
+ * `_maxObservedLamport` is up-to-date. See `createPatch()` for details.
67
+ *
55
68
  * @this {import('../WarpGraph.js').default}
56
69
  * @param {(p: PatchBuilderV2) => void | Promise<void>} build - Callback that adds operations to the patch
57
70
  * @returns {Promise<string>} The commit SHA of the new patch
@@ -89,30 +102,35 @@ export async function _nextLamport() {
89
102
  const writerRef = buildWriterRef(this._graphName, this._writerId);
90
103
  const currentRefSha = await this._persistence.readRef(writerRef);
91
104
 
92
- if (!currentRefSha) {
93
- // First commit for this writer
94
- return { lamport: 1, parentSha: null };
95
- }
105
+ let ownTick = 0;
96
106
 
97
- // Read the current patch commit to get its lamport timestamp
98
- const commitMessage = await this._persistence.showNode(currentRefSha);
99
- const kind = detectMessageKind(commitMessage);
107
+ if (currentRefSha) {
108
+ // Read the current patch commit to get its lamport timestamp
109
+ const commitMessage = await this._persistence.showNode(currentRefSha);
110
+ const kind = detectMessageKind(commitMessage);
100
111
 
101
- if (kind !== 'patch') {
102
- // Writer ref doesn't point to a patch commit - treat as first commit
103
- return { lamport: 1, parentSha: currentRefSha };
112
+ if (kind === 'patch') {
113
+ try {
114
+ const patchInfo = decodePatchMessage(commitMessage);
115
+ ownTick = patchInfo.lamport;
116
+ } catch (err) {
117
+ throw new Error(
118
+ `Failed to parse lamport from writer ref ${writerRef}: ` +
119
+ `commit ${currentRefSha} has invalid patch message format`,
120
+ { cause: err }
121
+ );
122
+ }
123
+ }
124
+ // Non-patch ref: ownTick stays 0 (fresh start), falls through to standard return.
104
125
  }
105
126
 
106
- try {
107
- const patchInfo = decodePatchMessage(commitMessage);
108
- return { lamport: patchInfo.lamport + 1, parentSha: currentRefSha };
109
- } catch {
110
- // Malformed message - error with actionable message
111
- throw new Error(
112
- `Failed to parse lamport from writer ref ${writerRef}: ` +
113
- `commit ${currentRefSha} has invalid patch message format`
114
- );
115
- }
127
+ // Standard Lamport clock rule: next tick = max(own chain, globally observed max) + 1.
128
+ // _maxObservedLamport is updated during materialize() and after each commit, so this
129
+ // is O(1) no additional git reads required at commit time.
130
+ return {
131
+ lamport: Math.max(ownTick, this._maxObservedLamport) + 1,
132
+ parentSha: currentRefSha ?? null,
133
+ };
116
134
  }
117
135
 
118
136
  /**
@@ -193,19 +211,22 @@ export async function getWriterPatches(writerId, stopAtSha = null) {
193
211
  */
194
212
  export async function _onPatchCommitted(writerId, { patch: committed, sha } = {}) {
195
213
  vvIncrement(this._versionVector, writerId);
214
+ // Keep _maxObservedLamport up to date so _nextLamport() issues globally-monotonic ticks.
215
+ if (committed?.lamport !== undefined && committed.lamport > this._maxObservedLamport) {
216
+ this._maxObservedLamport = committed.lamport;
217
+ }
196
218
  this._patchesSinceCheckpoint++;
197
219
  // Eager re-materialize: apply the just-committed patch to cached state
198
220
  // Only when the cache is clean — applying a patch to stale state would be incorrect
199
221
  if (this._cachedState && !this._stateDirty && committed && sha) {
200
222
  let tickReceipt = null;
201
223
  if (this._auditService) {
202
- // TODO(ts-cleanup): narrow joinPatch return + patch type to PatchV2
203
224
  const result = /** @type {{state: import('../services/JoinReducer.js').WarpStateV5, receipt: import('../types/TickReceipt.js').TickReceipt}} */ (
204
- joinPatch(this._cachedState, /** @type {any} */ (committed), sha, true) // TODO(ts-cleanup): narrow patch type
225
+ joinPatch(this._cachedState, /** @type {Parameters<typeof joinPatch>[1]} */ (committed), sha, true)
205
226
  );
206
227
  tickReceipt = result.receipt;
207
228
  } else {
208
- joinPatch(this._cachedState, /** @type {any} */ (committed), sha); // TODO(ts-cleanup): narrow patch type to PatchV2
229
+ joinPatch(this._cachedState, /** @type {Parameters<typeof joinPatch>[1]} */ (committed), sha);
209
230
  }
210
231
  await this._setMaterializedState(this._cachedState);
211
232
  // Update provenance index with new patch
@@ -257,14 +278,16 @@ export async function writer(writerId) {
257
278
  configSet,
258
279
  });
259
280
 
281
+ /** @type {CorePersistence} */
282
+ const persistence = this._persistence;
260
283
  return new Writer({
261
- persistence: /** @type {any} */ (this._persistence), // TODO(ts-cleanup): narrow port type
284
+ persistence,
262
285
  graphName: this._graphName,
263
286
  writerId: resolvedWriterId,
264
287
  versionVector: this._versionVector,
265
- getCurrentState: () => /** @type {any} */ (this._cachedState), // TODO(ts-cleanup): narrow port type
288
+ getCurrentState: /** @type {() => Promise<import('../services/JoinReducer.js').WarpStateV5>} */ (/** @type {unknown} */ (() => this._cachedState)),
266
289
  onDeleteWithData: this._onDeleteWithData,
267
- onCommitSuccess: (/** @type {any} */ opts) => this._onPatchCommitted(resolvedWriterId, opts), // TODO(ts-cleanup): type sync protocol
290
+ onCommitSuccess: /** @type {(result: {patch: Object, sha: string}) => void} */ (/** @type {unknown} */ ((/** @type {{patch?: import('../types/WarpTypesV2.js').PatchV2, sha?: string}} */ opts) => this._onPatchCommitted(resolvedWriterId, opts))),
268
291
  codec: this._codec,
269
292
  });
270
293
  }
@@ -313,14 +336,16 @@ export async function createWriter(opts = {}) {
313
336
  await this._persistence.configSet(configKey, freshWriterId);
314
337
  }
315
338
 
339
+ /** @type {CorePersistence} */
340
+ const writerPersistence = this._persistence;
316
341
  return new Writer({
317
- persistence: /** @type {any} */ (this._persistence), // TODO(ts-cleanup): narrow port type
342
+ persistence: writerPersistence,
318
343
  graphName: this._graphName,
319
344
  writerId: freshWriterId,
320
345
  versionVector: this._versionVector,
321
- getCurrentState: () => /** @type {any} */ (this._cachedState), // TODO(ts-cleanup): narrow port type
346
+ getCurrentState: /** @type {() => Promise<import('../services/JoinReducer.js').WarpStateV5>} */ (/** @type {unknown} */ (() => this._cachedState)),
322
347
  onDeleteWithData: this._onDeleteWithData,
323
- onCommitSuccess: (/** @type {any} */ commitOpts) => this._onPatchCommitted(freshWriterId, commitOpts), // TODO(ts-cleanup): type sync protocol
348
+ onCommitSuccess: /** @type {(result: {patch: Object, sha: string}) => void} */ (/** @type {unknown} */ ((/** @type {{patch?: import('../types/WarpTypesV2.js').PatchV2, sha?: string}} */ commitOpts) => this._onPatchCommitted(freshWriterId, commitOpts))),
324
349
  codec: this._codec,
325
350
  });
326
351
  }
@@ -13,6 +13,8 @@ import { createEmptyStateV5, reduceV5 } from '../services/JoinReducer.js';
13
13
  import { ProvenancePayload } from '../services/ProvenancePayload.js';
14
14
  import { decodePatchMessage, detectMessageKind } from '../services/WarpMessageCodec.js';
15
15
 
16
+ /** @typedef {import('../types/WarpTypesV2.js').PatchV2} PatchV2 */
17
+
16
18
  /**
17
19
  * Returns all patch SHAs that affected a given node or edge.
18
20
  *
@@ -184,7 +186,7 @@ export async function _computeBackwardCone(nodeId) {
184
186
  cone.set(sha, patch);
185
187
 
186
188
  // Add read dependencies to the queue
187
- const patchReads = /** @type {any} */ (patch)?.reads; // TODO(ts-cleanup): type patch array
189
+ const patchReads = /** @type {{reads?: string[]}} */ (patch).reads;
188
190
  if (patchReads) {
189
191
  for (const readEntity of patchReads) {
190
192
  if (!visited.has(readEntity)) {
@@ -261,8 +263,8 @@ export async function _loadPatchesBySha(shas) {
261
263
  * This ensures deterministic ordering regardless of discovery order.
262
264
  *
263
265
  * @this {import('../WarpGraph.js').default}
264
- * @param {Array<{patch: any, sha: string}>} patches - Unsorted patch entries
265
- * @returns {Array<{patch: any, sha: string}>} Sorted patch entries
266
+ * @param {Array<{patch: PatchV2, sha: string}>} patches - Unsorted patch entries
267
+ * @returns {Array<{patch: PatchV2, sha: string}>} Sorted patch entries
266
268
  */
267
269
  export function _sortPatchesCausally(patches) {
268
270
  return [...patches].sort((a, b) => {
@@ -37,7 +37,7 @@ export async function hasNode(nodeId) {
37
37
  *
38
38
  * @this {import('../WarpGraph.js').default}
39
39
  * @param {string} nodeId - The node ID to get properties for
40
- * @returns {Promise<Map<string, *>|null>} Map of property key → value, or null if node doesn't exist
40
+ * @returns {Promise<Map<string, unknown>|null>} Map of property key → value, or null if node doesn't exist
41
41
  * @throws {import('../errors/QueryError.js').default} If no cached state exists (code: `E_NO_STATE`)
42
42
  */
43
43
  export async function getNodeProps(nodeId) {
@@ -66,7 +66,7 @@ export async function getNodeProps(nodeId) {
66
66
  * @param {string} from - Source node ID
67
67
  * @param {string} to - Target node ID
68
68
  * @param {string} label - Edge label
69
- * @returns {Promise<Record<string, *>|null>} Object of property key → value, or null if edge doesn't exist
69
+ * @returns {Promise<Record<string, unknown>|null>} Object of property key → value, or null if edge doesn't exist
70
70
  * @throws {import('../errors/QueryError.js').default} If no cached state exists (code: `E_NO_STATE`)
71
71
  */
72
72
  export async function getEdgeProps(from, to, label) {
@@ -85,7 +85,7 @@ export async function getEdgeProps(from, to, label) {
85
85
 
86
86
  const birthEvent = s.edgeBirthEvent?.get(edgeKey);
87
87
 
88
- /** @type {Record<string, any>} */
88
+ /** @type {Record<string, unknown>} */
89
89
  const props = {};
90
90
  for (const [propKey, register] of s.prop) {
91
91
  if (!isEdgePropKey(propKey)) {
@@ -177,7 +177,7 @@ export async function getNodes() {
177
177
  * Gets all visible edges in the materialized state.
178
178
  *
179
179
  * @this {import('../WarpGraph.js').default}
180
- * @returns {Promise<Array<{from: string, to: string, label: string, props: Record<string, *>}>>} Array of edge info
180
+ * @returns {Promise<Array<{from: string, to: string, label: string, props: Record<string, unknown>}>>} Array of edge info
181
181
  * @throws {import('../errors/QueryError.js').default} If no cached state exists (code: `E_NO_STATE`)
182
182
  */
183
183
  export async function getEdges() {
@@ -275,5 +275,6 @@ export async function observer(name, config) {
275
275
  */
276
276
  export async function translationCost(configA, configB) {
277
277
  await this._ensureFreshState();
278
- return computeTranslationCost(configA, configB, this._cachedState);
278
+ const s = /** @type {import('../services/JoinReducer.js').WarpStateV5} */ (this._cachedState);
279
+ return computeTranslationCost(configA, configB, s);
279
280
  }
@@ -31,6 +31,9 @@ import { buildWriterRef } from '../utils/RefLayout.js';
31
31
  import { collectGCMetrics } from '../services/GCMetrics.js';
32
32
  import HttpSyncServer from '../services/HttpSyncServer.js';
33
33
  import { signSyncRequest, canonicalizePath } from '../services/SyncAuthService.js';
34
+ import { isError } from '../types/WarpErrors.js';
35
+
36
+ /** @typedef {import('../types/WarpPersistence.js').CorePersistence} CorePersistence */
34
37
 
35
38
  // ── Private helpers ─────────────────────────────────────────────────────────
36
39
 
@@ -221,10 +224,12 @@ export async function createSyncRequest() {
221
224
  */
222
225
  export async function processSyncRequest(request) {
223
226
  const localFrontier = await this.getFrontier();
227
+ /** @type {CorePersistence} */
228
+ const persistence = this._persistence;
224
229
  return await processSyncRequestImpl(
225
230
  request,
226
231
  localFrontier,
227
- /** @type {any} */ (this._persistence), // TODO(ts-cleanup): narrow port type
232
+ persistence,
228
233
  this._graphName,
229
234
  { codec: this._codec }
230
235
  );
@@ -253,7 +258,7 @@ export function applySyncResponse(response) {
253
258
  });
254
259
  }
255
260
 
256
- const currentFrontier = /** @type {any} */ (this._cachedState.observedFrontier); // TODO(ts-cleanup): narrow port type
261
+ const currentFrontier = /** @type {Map<string, string>} */ (/** @type {unknown} */ (this._cachedState.observedFrontier));
257
262
  const result = /** @type {{state: import('../services/JoinReducer.js').WarpStateV5, frontier: Map<string, string>, applied: number}} */ (applySyncResponseImpl(response, this._cachedState, currentFrontier));
258
263
 
259
264
  // Update cached state
@@ -352,12 +357,12 @@ export async function syncWith(remote, options = {}) {
352
357
  targetUrl.hash = '';
353
358
  }
354
359
  let attempt = 0;
355
- const emit = (/** @type {string} */ type, /** @type {Record<string, any>} */ payload = {}) => {
360
+ const emit = (/** @type {string} */ type, /** @type {Record<string, unknown>} */ payload = {}) => {
356
361
  if (typeof onStatus === 'function') {
357
- onStatus(/** @type {any} */ ({ type, attempt, ...payload })); // TODO(ts-cleanup): type sync protocol
362
+ onStatus(/** @type {{type: string, attempt: number}} */ ({ type, attempt, ...payload }));
358
363
  }
359
364
  };
360
- const shouldRetry = (/** @type {any} */ err) => { // TODO(ts-cleanup): type error
365
+ const shouldRetry = (/** @type {unknown} */ err) => {
361
366
  if (isDirectPeer) { return false; }
362
367
  if (err instanceof SyncError) {
363
368
  return ['E_SYNC_REMOTE', 'E_SYNC_TIMEOUT', 'E_SYNC_NETWORK'].includes(err.code);
@@ -400,7 +405,7 @@ export async function syncWith(remote, options = {}) {
400
405
  });
401
406
  });
402
407
  } catch (err) {
403
- if (/** @type {any} */ (err)?.name === 'AbortError') { // TODO(ts-cleanup): type error
408
+ if (isError(err) && err.name === 'AbortError') {
404
409
  throw new OperationAbortedError('syncWith', { reason: 'Signal received' });
405
410
  }
406
411
  if (err instanceof TimeoutError) {
@@ -411,7 +416,7 @@ export async function syncWith(remote, options = {}) {
411
416
  }
412
417
  throw new SyncError('Network error', {
413
418
  code: 'E_SYNC_NETWORK',
414
- context: { message: /** @type {any} */ (err)?.message }, // TODO(ts-cleanup): type error
419
+ context: { message: isError(err) ? err.message : String(err) },
415
420
  });
416
421
  }
417
422
 
@@ -474,7 +479,7 @@ export async function syncWith(remote, options = {}) {
474
479
  shouldRetry,
475
480
  onRetry: (/** @type {Error} */ error, /** @type {number} */ attemptNumber, /** @type {number} */ delayMs) => {
476
481
  if (typeof onStatus === 'function') {
477
- onStatus(/** @type {any} */ ({ type: 'retrying', attempt: attemptNumber, delayMs, error })); // TODO(ts-cleanup): type sync protocol
482
+ onStatus(/** @type {{type: string, attempt: number, delayMs: number, error: Error}} */ ({ type: 'retrying', attempt: attemptNumber, delayMs, error }));
478
483
  }
479
484
  },
480
485
  });
@@ -488,7 +493,7 @@ export async function syncWith(remote, options = {}) {
488
493
  return syncResult;
489
494
  } catch (err) {
490
495
  this._logTiming('syncWith', t0, { error: /** @type {Error} */ (err) });
491
- if (/** @type {any} */ (err)?.name === 'AbortError') { // TODO(ts-cleanup): type error
496
+ if (isError(err) && err.name === 'AbortError') {
492
497
  const abortedError = new OperationAbortedError('syncWith', { reason: 'Signal received' });
493
498
  if (typeof onStatus === 'function') {
494
499
  onStatus({ type: 'failed', attempt, error: abortedError });
@@ -518,13 +523,13 @@ export async function syncWith(remote, options = {}) {
518
523
  * @param {string} [options.host='127.0.0.1'] - Host to bind
519
524
  * @param {string} [options.path='/sync'] - Path to handle sync requests
520
525
  * @param {number} [options.maxRequestBytes=4194304] - Max request size in bytes
521
- * @param {import('../../ports/HttpServerPort.js').default} options.httpPort - HTTP server adapter (required)
526
+ * @param {import('../../ports/HttpServerPort.js').default} options.httpPort - HTTP server adapter
522
527
  * @param {{ keys: Record<string, string>, mode?: 'enforce'|'log-only' }} [options.auth] - Auth configuration
523
528
  * @returns {Promise<{close: () => Promise<void>, url: string}>} Server handle
524
529
  * @throws {Error} If port is not a number
525
530
  * @throws {Error} If httpPort adapter is not provided
526
531
  */
527
- export async function serve({ port, host = '127.0.0.1', path = '/sync', maxRequestBytes = DEFAULT_SYNC_SERVER_MAX_BYTES, httpPort, auth } = /** @type {any} */ ({})) { // TODO(ts-cleanup): needs options type
532
+ export async function serve({ port, host = '127.0.0.1', path = '/sync', maxRequestBytes = DEFAULT_SYNC_SERVER_MAX_BYTES, httpPort, auth } = /** @type {{ port: number, httpPort: import('../../ports/HttpServerPort.js').default }} */ ({})) {
528
533
  if (typeof port !== 'number') {
529
534
  throw new Error('serve() requires a numeric port');
530
535
  }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Minimal ambient declarations for Deno and Bun runtime globals.
3
+ *
4
+ * These cover ONLY the APIs actually used in this codebase:
5
+ * - Deno.serve() (DenoHttpAdapter.js)
6
+ * - Deno.env.get() (bin/cli/infrastructure.js)
7
+ * - Bun.serve() (BunHttpAdapter.js)
8
+ *
9
+ * Do NOT install @types/deno or @types/bun — this file is intentionally
10
+ * narrow to avoid pulling in thousands of unrelated declarations.
11
+ */
12
+
13
+ /* ------------------------------------------------------------------ */
14
+ /* Deno */
15
+ /* ------------------------------------------------------------------ */
16
+
17
+ interface DenoAddr {
18
+ transport: string;
19
+ hostname: string;
20
+ port: number;
21
+ }
22
+
23
+ interface DenoServer {
24
+ shutdown(): Promise<void>;
25
+ addr: DenoAddr;
26
+ }
27
+
28
+ interface DenoServeOptions {
29
+ port?: number;
30
+ hostname?: string;
31
+ onListen?: () => void;
32
+ }
33
+
34
+ interface DenoEnv {
35
+ get(name: string): string | undefined;
36
+ }
37
+
38
+ declare namespace Deno {
39
+ const env: DenoEnv;
40
+ function serve(
41
+ options: DenoServeOptions,
42
+ handler: (request: Request) => Promise<Response> | Response,
43
+ ): DenoServer;
44
+ }
45
+
46
+ /* ------------------------------------------------------------------ */
47
+ /* Bun */
48
+ /* ------------------------------------------------------------------ */
49
+
50
+ interface BunServer {
51
+ stop(closeActiveConnections?: boolean): Promise<void>;
52
+ hostname: string;
53
+ port: number;
54
+ }
55
+
56
+ interface BunServeOptions {
57
+ port?: number;
58
+ hostname?: string;
59
+ fetch: (request: Request) => Promise<Response> | Response;
60
+ }
61
+
62
+ declare namespace Bun {
63
+ function serve(options: BunServeOptions): BunServer;
64
+ }
@@ -106,8 +106,8 @@ function createFetchHandler(requestHandler, logger) {
106
106
  const portReq = await toPortRequest(request);
107
107
  const portRes = await requestHandler(portReq);
108
108
  return toResponse(portRes);
109
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): type error
110
- if (err.status === 413) {
109
+ } catch (err) {
110
+ if (typeof err === 'object' && err !== null && /** @type {{status?: number}} */ (err).status === 413) {
111
111
  return new Response(PAYLOAD_TOO_LARGE, {
112
112
  status: 413,
113
113
  headers: { 'Content-Type': 'text/plain', 'Content-Length': PAYLOAD_TOO_LARGE_LENGTH },
@@ -125,6 +125,10 @@ function createFetchHandler(requestHandler, logger) {
125
125
  };
126
126
  }
127
127
 
128
+ /**
129
+ * @typedef {{ hostname: string, port: number, stop: (closeActiveConnections?: boolean) => Promise<void> }} BunServer
130
+ */
131
+
128
132
  /**
129
133
  * Starts a Bun server and invokes the callback with (null) on success
130
134
  * or (err) on failure.
@@ -132,12 +136,11 @@ function createFetchHandler(requestHandler, logger) {
132
136
  * Note: Bun.serve() is synchronous, so cb fires on the same tick
133
137
  * (unlike Node's server.listen which defers via the event loop).
134
138
  *
135
- * @param {*} serveOptions
139
+ * @param {BunServeOptions} serveOptions
136
140
  * @param {Function|undefined} cb - Node-style callback
137
- * @returns {*} The Bun server instance
141
+ * @returns {BunServer} The Bun server instance
138
142
  */
139
143
  function startServer(serveOptions, cb) {
140
- // @ts-expect-error — Bun global is only available in Bun runtime
141
144
  const server = globalThis.Bun.serve(serveOptions);
142
145
  if (cb) {
143
146
  cb(null);
@@ -148,13 +151,15 @@ function startServer(serveOptions, cb) {
148
151
  /**
149
152
  * Safely stops a Bun server, forwarding errors to the callback.
150
153
  *
151
- * @param {{ server: * }} state - Shared mutable state
154
+ * @param {{ server: BunServer | null }} state - Shared mutable state
152
155
  * @param {Function} [callback]
153
156
  */
154
157
  function stopServer(state, callback) {
155
158
  try {
156
159
  if (state.server) {
157
- state.server.stop();
160
+ // stop() synchronously halts the listener; the returned Promise
161
+ // represents draining of active connections — safe to ignore here.
162
+ void state.server.stop();
158
163
  state.server = null;
159
164
  }
160
165
  if (callback) {
@@ -192,7 +197,7 @@ export default class BunHttpAdapter extends HttpServerPort {
192
197
  */
193
198
  createServer(requestHandler) {
194
199
  const fetchHandler = createFetchHandler(requestHandler, this._logger);
195
- /** @type {{ server: * }} */
200
+ /** @type {{ server: BunServer | null }} */
196
201
  const state = { server: null };
197
202
 
198
203
  return {
@@ -204,7 +209,7 @@ export default class BunHttpAdapter extends HttpServerPort {
204
209
  listen(port, host, callback) {
205
210
  const cb = typeof host === 'function' ? host : callback;
206
211
  const bindHost = typeof host === 'string' ? host : undefined;
207
- /** @type {*} */ // TODO(ts-cleanup): type Bun.serve options
212
+ /** @type {BunServeOptions} */
208
213
  const serveOptions = { port, fetch: fetchHandler };
209
214
 
210
215
  if (bindHost !== undefined) {
@@ -16,6 +16,11 @@
16
16
  * @module infrastructure/adapters/CasSeekCacheAdapter
17
17
  */
18
18
 
19
+ /**
20
+ * Minimal interface for the ContentAddressableStore from @git-stunts/git-cas.
21
+ * @typedef {{ readManifest: Function, restore: Function, store: Function, createTree: Function }} CasStore
22
+ */
23
+
19
24
  import SeekCachePort from '../../ports/SeekCachePort.js';
20
25
  import { buildSeekCacheRef } from '../../domain/utils/RefLayout.js';
21
26
  import { Readable } from 'node:stream';
@@ -59,7 +64,7 @@ export default class CasSeekCacheAdapter extends SeekCachePort {
59
64
  /**
60
65
  * Lazily initializes the ContentAddressableStore.
61
66
  * @private
62
- * @returns {Promise<*>}
67
+ * @returns {Promise<CasStore>}
63
68
  */
64
69
  async _getCas() {
65
70
  if (!this._casPromise) {
@@ -73,7 +78,7 @@ export default class CasSeekCacheAdapter extends SeekCachePort {
73
78
 
74
79
  /**
75
80
  * @private
76
- * @returns {Promise<*>}
81
+ * @returns {Promise<CasStore>}
77
82
  */
78
83
  async _initCas() {
79
84
  const { default: ContentAddressableStore } = await import(
@@ -132,7 +137,7 @@ export default class CasSeekCacheAdapter extends SeekCachePort {
132
137
  * @returns {Promise<CacheIndex>} The mutated index
133
138
  */
134
139
  async _mutateIndex(mutate) {
135
- /** @type {*} */ // TODO(ts-cleanup): type CAS retry error
140
+ /** @type {unknown} */
136
141
  let lastErr;
137
142
  for (let attempt = 0; attempt < MAX_CAS_RETRIES; attempt++) {
138
143
  const index = await this._readIndex();
@@ -144,7 +149,7 @@ export default class CasSeekCacheAdapter extends SeekCachePort {
144
149
  lastErr = err;
145
150
  // Transient write failure — retry with fresh read
146
151
  if (attempt === MAX_CAS_RETRIES - 1) {
147
- throw new Error(`CasSeekCacheAdapter: index update failed after retries: ${lastErr.message}`);
152
+ throw new Error(`CasSeekCacheAdapter: index update failed after retries: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}`);
148
153
  }
149
154
  }
150
155
  }
@@ -100,8 +100,8 @@ function createHandler(requestHandler, logger) {
100
100
  const plain = await toPlainRequest(request);
101
101
  const response = await requestHandler(plain);
102
102
  return toDenoResponse(response);
103
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): type error
104
- if (err.status === 413) {
103
+ } catch (err) {
104
+ if (typeof err === 'object' && err !== null && /** @type {{status?: number}} */ (err).status === 413) {
105
105
  const msg = new TextEncoder().encode('Payload Too Large');
106
106
  return new Response(msg, {
107
107
  status: 413,
@@ -140,7 +140,7 @@ function closeImpl(state, callback) {
140
140
  callback();
141
141
  }
142
142
  },
143
- /** @param {*} err */ (err) => {
143
+ /** @param {unknown} err */ (err) => {
144
144
  state.server = null;
145
145
  if (callback) {
146
146
  callback(err);
@@ -210,7 +210,7 @@ export default class DenoHttpAdapter extends HttpServerPort {
210
210
  const hostname = typeof host === 'string' ? host : undefined;
211
211
 
212
212
  try {
213
- /** @type {*} */ // TODO(ts-cleanup): type Deno.serve options
213
+ /** @type {DenoServeOptions} */
214
214
  const serveOptions = {
215
215
  port,
216
216
  onListen() {
@@ -223,9 +223,8 @@ export default class DenoHttpAdapter extends HttpServerPort {
223
223
  serveOptions.hostname = hostname;
224
224
  }
225
225
 
226
- // @ts-expect-error — Deno global is only available in Deno runtime
227
226
  state.server = globalThis.Deno.serve(serveOptions, handler);
228
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): type error
227
+ } catch (err) {
229
228
  if (cb) {
230
229
  cb(err);
231
230
  } else {
@@ -145,11 +145,12 @@ async function refExists(execute, ref) {
145
145
  try {
146
146
  await execute({ args: ['show-ref', '--verify', '--quiet', ref] });
147
147
  return true;
148
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): type error
149
- if (getExitCode(err) === 1) {
148
+ } catch (err) {
149
+ const gitErr = /** @type {GitError} */ (err);
150
+ if (getExitCode(gitErr) === 1) {
150
151
  return false;
151
152
  }
152
- if (isDanglingObjectError(err)) {
153
+ if (isDanglingObjectError(gitErr)) {
153
154
  return false;
154
155
  }
155
156
  throw err;
@@ -544,11 +545,12 @@ export default class GitGraphAdapter extends GraphPersistencePort {
544
545
  args: ['rev-parse', ref]
545
546
  });
546
547
  return oid.trim();
547
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): type error
548
- if (getExitCode(err) === 1) {
548
+ } catch (err) {
549
+ const gitErr = /** @type {GitError} */ (err);
550
+ if (getExitCode(gitErr) === 1) {
549
551
  return null;
550
552
  }
551
- if (isDanglingObjectError(err)) {
553
+ if (isDanglingObjectError(gitErr)) {
552
554
  return null;
553
555
  }
554
556
  throw err;
@@ -628,8 +630,8 @@ export default class GitGraphAdapter extends GraphPersistencePort {
628
630
  try {
629
631
  await this._executeWithRetry({ args: ['cat-file', '-e', sha] });
630
632
  return true;
631
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): type error
632
- if (getExitCode(err) === 1) {
633
+ } catch (err) {
634
+ if (getExitCode(/** @type {GitError} */ (err)) === 1) {
633
635
  return false;
634
636
  }
635
637
  throw err;
@@ -704,8 +706,8 @@ export default class GitGraphAdapter extends GraphPersistencePort {
704
706
  args: ['merge-base', '--is-ancestor', potentialAncestor, descendant]
705
707
  });
706
708
  return true; // Exit code 0 means it IS an ancestor
707
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): type error
708
- if (this._getExitCode(err) === 1) {
709
+ } catch (err) {
710
+ if (this._getExitCode(/** @type {GitError} */ (err)) === 1) {
709
711
  return false; // Exit code 1 means it is NOT an ancestor
710
712
  }
711
713
  throw err; // Re-throw unexpected errors
@@ -726,8 +728,8 @@ export default class GitGraphAdapter extends GraphPersistencePort {
726
728
  });
727
729
  // Preserve empty-string values; only drop trailing newline
728
730
  return value.replace(/\n$/, '');
729
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): type error
730
- if (this._isConfigKeyNotFound(err)) {
731
+ } catch (err) {
732
+ if (this._isConfigKeyNotFound(/** @type {GitError} */ (err))) {
731
733
  return null;
732
734
  }
733
735
  throw err;
@@ -71,7 +71,7 @@ export default class NodeHttpAdapter extends HttpServerPort {
71
71
  const logger = this._logger;
72
72
  const server = createServer((req, res) => {
73
73
  dispatch(req, res, { handler: requestHandler, logger }).catch(
74
- /** @param {*} err */ (err) => {
74
+ /** @param {unknown} err */ (err) => {
75
75
  logger.error('[NodeHttpAdapter] unhandled dispatch error:', err);
76
76
  });
77
77
  });
@@ -85,7 +85,7 @@ export default class NodeHttpAdapter extends HttpServerPort {
85
85
  listen(port, host, callback) {
86
86
  const cb = typeof host === 'function' ? host : callback;
87
87
  const bindHost = typeof host === 'string' ? host : undefined;
88
- /** @param {*} err */
88
+ /** @param {unknown} err */
89
89
  const onError = (err) => {
90
90
  if (cb) {
91
91
  cb(err);
@@ -38,8 +38,8 @@ function toWebCryptoAlgo(algorithm) {
38
38
  function toUint8Array(data) {
39
39
  if (data instanceof Uint8Array) { return data; }
40
40
  if (typeof data === 'string') { return new TextEncoder().encode(data); }
41
- if (typeof Buffer !== 'undefined' && Buffer.isBuffer(/** @type {*} */ (data))) { // TODO(ts-cleanup): narrow port type
42
- const buf = /** @type {Buffer} */ (/** @type {*} */ (data)); // TODO(ts-cleanup): narrow port type
41
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(data)) {
42
+ const buf = /** @type {Buffer} */ (data);
43
43
  return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
44
44
  }
45
45
  throw new Error('WebCryptoAdapter: data must be string, Buffer, or Uint8Array');