@git-stunts/git-warp 11.2.1 → 11.5.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.
Files changed (114) hide show
  1. package/README.md +24 -1
  2. package/bin/cli/commands/check.js +2 -2
  3. package/bin/cli/commands/doctor/checks.js +12 -12
  4. package/bin/cli/commands/doctor/index.js +2 -2
  5. package/bin/cli/commands/doctor/types.js +1 -1
  6. package/bin/cli/commands/history.js +12 -5
  7. package/bin/cli/commands/install-hooks.js +5 -5
  8. package/bin/cli/commands/materialize.js +2 -2
  9. package/bin/cli/commands/patch.js +142 -0
  10. package/bin/cli/commands/path.js +4 -4
  11. package/bin/cli/commands/query.js +54 -13
  12. package/bin/cli/commands/registry.js +4 -0
  13. package/bin/cli/commands/seek.js +17 -11
  14. package/bin/cli/commands/tree.js +230 -0
  15. package/bin/cli/commands/trust.js +3 -3
  16. package/bin/cli/commands/verify-audit.js +8 -7
  17. package/bin/cli/commands/view.js +6 -5
  18. package/bin/cli/infrastructure.js +26 -12
  19. package/bin/cli/shared.js +2 -2
  20. package/bin/cli/types.js +19 -8
  21. package/bin/presenters/index.js +35 -9
  22. package/bin/presenters/json.js +14 -12
  23. package/bin/presenters/text.js +155 -33
  24. package/index.d.ts +118 -22
  25. package/index.js +2 -0
  26. package/package.json +5 -3
  27. package/src/domain/WarpGraph.js +4 -1
  28. package/src/domain/crdt/ORSet.js +8 -8
  29. package/src/domain/errors/EmptyMessageError.js +2 -2
  30. package/src/domain/errors/ForkError.js +1 -1
  31. package/src/domain/errors/IndexError.js +1 -1
  32. package/src/domain/errors/OperationAbortedError.js +1 -1
  33. package/src/domain/errors/QueryError.js +1 -1
  34. package/src/domain/errors/SchemaUnsupportedError.js +1 -1
  35. package/src/domain/errors/ShardCorruptionError.js +2 -2
  36. package/src/domain/errors/ShardLoadError.js +2 -2
  37. package/src/domain/errors/ShardValidationError.js +4 -4
  38. package/src/domain/errors/StorageError.js +2 -2
  39. package/src/domain/errors/SyncError.js +1 -1
  40. package/src/domain/errors/TraversalError.js +1 -1
  41. package/src/domain/errors/TrustError.js +1 -1
  42. package/src/domain/errors/WarpError.js +2 -2
  43. package/src/domain/errors/WormholeError.js +1 -1
  44. package/src/domain/services/AuditReceiptService.js +6 -6
  45. package/src/domain/services/AuditVerifierService.js +52 -38
  46. package/src/domain/services/BitmapIndexBuilder.js +3 -3
  47. package/src/domain/services/BitmapIndexReader.js +28 -19
  48. package/src/domain/services/BoundaryTransitionRecord.js +18 -17
  49. package/src/domain/services/CheckpointSerializerV5.js +17 -16
  50. package/src/domain/services/CheckpointService.js +22 -3
  51. package/src/domain/services/CommitDagTraversalService.js +13 -13
  52. package/src/domain/services/DagPathFinding.js +7 -7
  53. package/src/domain/services/DagTopology.js +1 -1
  54. package/src/domain/services/DagTraversal.js +1 -1
  55. package/src/domain/services/HealthCheckService.js +1 -1
  56. package/src/domain/services/HookInstaller.js +1 -1
  57. package/src/domain/services/HttpSyncServer.js +92 -41
  58. package/src/domain/services/IndexRebuildService.js +7 -7
  59. package/src/domain/services/IndexStalenessChecker.js +4 -3
  60. package/src/domain/services/JoinReducer.js +26 -11
  61. package/src/domain/services/KeyCodec.js +7 -0
  62. package/src/domain/services/LogicalTraversal.js +1 -1
  63. package/src/domain/services/MessageCodecInternal.js +1 -1
  64. package/src/domain/services/MigrationService.js +1 -1
  65. package/src/domain/services/ObserverView.js +8 -8
  66. package/src/domain/services/PatchBuilderV2.js +96 -30
  67. package/src/domain/services/ProvenanceIndex.js +1 -1
  68. package/src/domain/services/ProvenancePayload.js +1 -1
  69. package/src/domain/services/QueryBuilder.js +3 -3
  70. package/src/domain/services/StateDiff.js +14 -11
  71. package/src/domain/services/StateSerializerV5.js +2 -2
  72. package/src/domain/services/StreamingBitmapIndexBuilder.js +26 -24
  73. package/src/domain/services/SyncAuthService.js +3 -2
  74. package/src/domain/services/SyncProtocol.js +25 -11
  75. package/src/domain/services/TemporalQuery.js +9 -6
  76. package/src/domain/services/TranslationCost.js +7 -5
  77. package/src/domain/services/WormholeService.js +16 -7
  78. package/src/domain/trust/TrustCanonical.js +3 -3
  79. package/src/domain/trust/TrustEvaluator.js +18 -3
  80. package/src/domain/trust/TrustRecordService.js +30 -23
  81. package/src/domain/trust/TrustStateBuilder.js +21 -8
  82. package/src/domain/trust/canonical.js +6 -6
  83. package/src/domain/types/TickReceipt.js +1 -1
  84. package/src/domain/types/WarpErrors.js +45 -0
  85. package/src/domain/types/WarpOptions.js +29 -0
  86. package/src/domain/types/WarpPersistence.js +41 -0
  87. package/src/domain/types/WarpTypes.js +2 -2
  88. package/src/domain/types/WarpTypesV2.js +2 -2
  89. package/src/domain/utils/MinHeap.js +6 -5
  90. package/src/domain/utils/canonicalStringify.js +5 -4
  91. package/src/domain/utils/roaring.js +31 -5
  92. package/src/domain/warp/PatchSession.js +40 -18
  93. package/src/domain/warp/_wiredMethods.d.ts +199 -45
  94. package/src/domain/warp/checkpoint.methods.js +5 -1
  95. package/src/domain/warp/fork.methods.js +2 -2
  96. package/src/domain/warp/materialize.methods.js +55 -5
  97. package/src/domain/warp/materializeAdvanced.methods.js +15 -4
  98. package/src/domain/warp/patch.methods.js +54 -29
  99. package/src/domain/warp/provenance.methods.js +5 -3
  100. package/src/domain/warp/query.methods.js +89 -6
  101. package/src/domain/warp/sync.methods.js +16 -11
  102. package/src/globals.d.ts +64 -0
  103. package/src/infrastructure/adapters/BunHttpAdapter.js +14 -9
  104. package/src/infrastructure/adapters/CasSeekCacheAdapter.js +9 -4
  105. package/src/infrastructure/adapters/DenoHttpAdapter.js +5 -6
  106. package/src/infrastructure/adapters/GitGraphAdapter.js +18 -13
  107. package/src/infrastructure/adapters/NodeHttpAdapter.js +2 -2
  108. package/src/infrastructure/adapters/WebCryptoAdapter.js +2 -2
  109. package/src/visualization/layouts/converters.js +2 -2
  110. package/src/visualization/layouts/elkAdapter.js +1 -1
  111. package/src/visualization/layouts/elkLayout.js +10 -7
  112. package/src/visualization/layouts/index.js +1 -1
  113. package/src/visualization/renderers/ascii/seek.js +16 -6
  114. package/src/visualization/renderers/svg/index.js +1 -1
@@ -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) => {
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { orsetContains, orsetElements } from '../crdt/ORSet.js';
11
- import { decodePropKey, isEdgePropKey, decodeEdgePropKey, encodeEdgeKey, decodeEdgeKey } from '../services/KeyCodec.js';
11
+ import { decodePropKey, isEdgePropKey, decodeEdgePropKey, encodeEdgeKey, decodeEdgeKey, CONTENT_PROPERTY_KEY } from '../services/KeyCodec.js';
12
12
  import { compareEventIds } from '../utils/EventId.js';
13
13
  import { cloneStateV5 } from '../services/JoinReducer.js';
14
14
  import QueryBuilder from '../services/QueryBuilder.js';
@@ -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,88 @@ 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);
280
+ }
281
+
282
+ /**
283
+ * Gets the content blob OID for a node, or null if none is attached.
284
+ *
285
+ * @this {import('../WarpGraph.js').default}
286
+ * @param {string} nodeId - The node ID to check
287
+ * @returns {Promise<string|null>} Hex blob OID or null
288
+ * @throws {import('../errors/QueryError.js').default} If no cached state exists (code: `E_NO_STATE`)
289
+ */
290
+ export async function getContentOid(nodeId) {
291
+ const props = await getNodeProps.call(this, nodeId);
292
+ if (!props) {
293
+ return null;
294
+ }
295
+ // getNodeProps returns a Map — use .get() for property access
296
+ const oid = props.get(CONTENT_PROPERTY_KEY);
297
+ return (typeof oid === 'string') ? oid : null;
298
+ }
299
+
300
+ /**
301
+ * Gets the content blob for a node, or null if none is attached.
302
+ *
303
+ * Returns the raw Buffer from `readBlob()`. Consumers wanting text
304
+ * should call `.toString('utf8')` on the result.
305
+ *
306
+ * @this {import('../WarpGraph.js').default}
307
+ * @param {string} nodeId - The node ID to get content for
308
+ * @returns {Promise<Buffer|null>} Content buffer or null
309
+ * @throws {Error} If the referenced blob OID is not in the object store
310
+ * (e.g., garbage-collected despite anchoring). Callers should handle this
311
+ * if operating on repos with aggressive GC or partial clones.
312
+ */
313
+ export async function getContent(nodeId) {
314
+ const oid = await getContentOid.call(this, nodeId);
315
+ if (!oid) {
316
+ return null;
317
+ }
318
+ return await this._persistence.readBlob(oid);
319
+ }
320
+
321
+ /**
322
+ * Gets the content blob OID for an edge, or null if none is attached.
323
+ *
324
+ * @this {import('../WarpGraph.js').default}
325
+ * @param {string} from - Source node ID
326
+ * @param {string} to - Target node ID
327
+ * @param {string} label - Edge label
328
+ * @returns {Promise<string|null>} Hex blob OID or null
329
+ * @throws {import('../errors/QueryError.js').default} If no cached state exists (code: `E_NO_STATE`)
330
+ */
331
+ export async function getEdgeContentOid(from, to, label) {
332
+ const props = await getEdgeProps.call(this, from, to, label);
333
+ if (!props) {
334
+ return null;
335
+ }
336
+ // getEdgeProps returns a plain object — use bracket access
337
+ const oid = props[CONTENT_PROPERTY_KEY];
338
+ return (typeof oid === 'string') ? oid : null;
339
+ }
340
+
341
+ /**
342
+ * Gets the content blob for an edge, or null if none is attached.
343
+ *
344
+ * Returns the raw Buffer from `readBlob()`. Consumers wanting text
345
+ * should call `.toString('utf8')` on the result.
346
+ *
347
+ * @this {import('../WarpGraph.js').default}
348
+ * @param {string} from - Source node ID
349
+ * @param {string} to - Target node ID
350
+ * @param {string} label - Edge label
351
+ * @returns {Promise<Buffer|null>} Content buffer or null
352
+ * @throws {Error} If the referenced blob OID is not in the object store
353
+ * (e.g., garbage-collected despite anchoring). Callers should handle this
354
+ * if operating on repos with aggressive GC or partial clones.
355
+ */
356
+ export async function getEdgeContent(from, to, label) {
357
+ const oid = await getEdgeContentOid.call(this, from, to, label);
358
+ if (!oid) {
359
+ return null;
360
+ }
361
+ return await this._persistence.readBlob(oid);
279
362
  }
@@ -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 {
@@ -43,6 +43,7 @@
43
43
  * @see {@link https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain} for Git plumbing concepts
44
44
  */
45
45
 
46
+ import { Buffer } from 'node:buffer';
46
47
  import { retry } from '@git-stunts/alfred';
47
48
  import GraphPersistencePort from '../../ports/GraphPersistencePort.js';
48
49
  import { validateOid, validateRef, validateLimit, validateConfigKey } from './adapterValidation.js';
@@ -145,11 +146,12 @@ async function refExists(execute, ref) {
145
146
  try {
146
147
  await execute({ args: ['show-ref', '--verify', '--quiet', ref] });
147
148
  return true;
148
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): type error
149
- if (getExitCode(err) === 1) {
149
+ } catch (err) {
150
+ const gitErr = /** @type {GitError} */ (err);
151
+ if (getExitCode(gitErr) === 1) {
150
152
  return false;
151
153
  }
152
- if (isDanglingObjectError(err)) {
154
+ if (isDanglingObjectError(gitErr)) {
153
155
  return false;
154
156
  }
155
157
  throw err;
@@ -509,7 +511,9 @@ export default class GitGraphAdapter extends GraphPersistencePort {
509
511
  const stream = await this.plumbing.executeStream({
510
512
  args: ['cat-file', 'blob', oid]
511
513
  });
512
- return await stream.collect({ asString: false });
514
+ const raw = await stream.collect({ asString: false });
515
+ // Ensure a real Node Buffer (plumbing may return Uint8Array)
516
+ return Buffer.isBuffer(raw) ? raw : Buffer.from(raw);
513
517
  }
514
518
 
515
519
  /**
@@ -544,11 +548,12 @@ export default class GitGraphAdapter extends GraphPersistencePort {
544
548
  args: ['rev-parse', ref]
545
549
  });
546
550
  return oid.trim();
547
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): type error
548
- if (getExitCode(err) === 1) {
551
+ } catch (err) {
552
+ const gitErr = /** @type {GitError} */ (err);
553
+ if (getExitCode(gitErr) === 1) {
549
554
  return null;
550
555
  }
551
- if (isDanglingObjectError(err)) {
556
+ if (isDanglingObjectError(gitErr)) {
552
557
  return null;
553
558
  }
554
559
  throw err;
@@ -628,8 +633,8 @@ export default class GitGraphAdapter extends GraphPersistencePort {
628
633
  try {
629
634
  await this._executeWithRetry({ args: ['cat-file', '-e', sha] });
630
635
  return true;
631
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): type error
632
- if (getExitCode(err) === 1) {
636
+ } catch (err) {
637
+ if (getExitCode(/** @type {GitError} */ (err)) === 1) {
633
638
  return false;
634
639
  }
635
640
  throw err;
@@ -704,8 +709,8 @@ export default class GitGraphAdapter extends GraphPersistencePort {
704
709
  args: ['merge-base', '--is-ancestor', potentialAncestor, descendant]
705
710
  });
706
711
  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) {
712
+ } catch (err) {
713
+ if (this._getExitCode(/** @type {GitError} */ (err)) === 1) {
709
714
  return false; // Exit code 1 means it is NOT an ancestor
710
715
  }
711
716
  throw err; // Re-throw unexpected errors
@@ -726,8 +731,8 @@ export default class GitGraphAdapter extends GraphPersistencePort {
726
731
  });
727
732
  // Preserve empty-string values; only drop trailing newline
728
733
  return value.replace(/\n$/, '');
729
- } catch (/** @type {*} */ err) { // TODO(ts-cleanup): type error
730
- if (this._isConfigKeyNotFound(err)) {
734
+ } catch (err) {
735
+ if (this._isConfigKeyNotFound(/** @type {GitError} */ (err))) {
731
736
  return null;
732
737
  }
733
738
  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');
@@ -7,7 +7,7 @@
7
7
  */
8
8
 
9
9
  /**
10
- * @typedef {{ id: string, label: string, props?: Record<string, any> }} GraphDataNode
10
+ * @typedef {{ id: string, label: string, props?: Record<string, unknown> }} GraphDataNode
11
11
  * @typedef {{ from: string, to: string, label?: string }} GraphDataEdge
12
12
  * @typedef {{ nodes: GraphDataNode[], edges: GraphDataEdge[] }} GraphData
13
13
  */
@@ -16,7 +16,7 @@
16
16
  * Converts a query result payload + edge array into graph data.
17
17
  * Edges are filtered to only those connecting matched nodes.
18
18
  *
19
- * @param {{ nodes?: Array<{ id: string, props?: Record<string, any> }> } | null} payload - Query result
19
+ * @param {{ nodes?: Array<{ id: string, props?: Record<string, unknown> }> } | null} payload - Query result
20
20
  * @param {Array<{ from: string, to: string, label?: string }>} edges - Edge array from graph.getEdges()
21
21
  * @returns {GraphData}
22
22
  */
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  /**
6
- * @typedef {{ id: string, label: string, props?: Record<string, any> }} GraphDataNode
6
+ * @typedef {{ id: string, label: string, props?: Record<string, unknown> }} GraphDataNode
7
7
  * @typedef {{ from: string, to: string, label?: string }} GraphDataEdge
8
8
  * @typedef {{ nodes: GraphDataNode[], edges: GraphDataEdge[] }} GraphData
9
9
  * @typedef {{ text: string }} ElkLabel