@git-stunts/git-warp 12.2.1 → 12.4.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 (121) hide show
  1. package/README.md +5 -5
  2. package/bin/cli/commands/info.js +1 -5
  3. package/bin/cli/infrastructure.js +6 -9
  4. package/bin/cli/shared.js +8 -0
  5. package/bin/presenters/text.js +10 -3
  6. package/bin/warp-graph.js +6 -6
  7. package/package.json +1 -1
  8. package/src/domain/WarpGraph.js +5 -35
  9. package/src/domain/crdt/ORSet.js +3 -0
  10. package/src/domain/crdt/VersionVector.js +1 -1
  11. package/src/domain/entities/GraphNode.js +1 -6
  12. package/src/domain/errors/ForkError.js +1 -1
  13. package/src/domain/errors/IndexError.js +1 -1
  14. package/src/domain/errors/OperationAbortedError.js +1 -1
  15. package/src/domain/errors/PatchError.js +1 -1
  16. package/src/domain/errors/PersistenceError.js +45 -0
  17. package/src/domain/errors/QueryError.js +1 -1
  18. package/src/domain/errors/SchemaUnsupportedError.js +1 -1
  19. package/src/domain/errors/SyncError.js +1 -1
  20. package/src/domain/errors/TraversalError.js +1 -1
  21. package/src/domain/errors/TrustError.js +1 -1
  22. package/src/domain/errors/WormholeError.js +1 -1
  23. package/src/domain/errors/index.js +1 -0
  24. package/src/domain/services/AdjacencyNeighborProvider.js +1 -4
  25. package/src/domain/services/AnchorMessageCodec.js +1 -3
  26. package/src/domain/services/AuditMessageCodec.js +1 -5
  27. package/src/domain/services/AuditReceiptService.js +4 -18
  28. package/src/domain/services/AuditVerifierService.js +3 -7
  29. package/src/domain/services/BitmapIndexBuilder.js +6 -12
  30. package/src/domain/services/BitmapIndexReader.js +7 -20
  31. package/src/domain/services/BitmapNeighborProvider.js +1 -3
  32. package/src/domain/services/BoundaryTransitionRecord.js +7 -23
  33. package/src/domain/services/CheckpointMessageCodec.js +6 -6
  34. package/src/domain/services/CheckpointSerializerV5.js +8 -12
  35. package/src/domain/services/CheckpointService.js +28 -40
  36. package/src/domain/services/CommitDagTraversalService.js +1 -3
  37. package/src/domain/services/DagPathFinding.js +9 -59
  38. package/src/domain/services/DagTopology.js +4 -16
  39. package/src/domain/services/DagTraversal.js +7 -31
  40. package/src/domain/services/Frontier.js +4 -6
  41. package/src/domain/services/GitLogParser.js +1 -2
  42. package/src/domain/services/GraphTraversal.js +14 -114
  43. package/src/domain/services/HealthCheckService.js +3 -9
  44. package/src/domain/services/HookInstaller.js +2 -8
  45. package/src/domain/services/HttpSyncServer.js +24 -25
  46. package/src/domain/services/IncrementalIndexUpdater.js +4 -6
  47. package/src/domain/services/IndexRebuildService.js +6 -52
  48. package/src/domain/services/IndexStalenessChecker.js +2 -3
  49. package/src/domain/services/JoinReducer.js +200 -100
  50. package/src/domain/services/KeyCodec.js +48 -0
  51. package/src/domain/services/LogicalBitmapIndexBuilder.js +1 -2
  52. package/src/domain/services/LogicalIndexBuildService.js +2 -6
  53. package/src/domain/services/LogicalIndexReader.js +1 -2
  54. package/src/domain/services/LogicalTraversal.js +13 -64
  55. package/src/domain/services/MaterializedViewService.js +5 -19
  56. package/src/domain/services/MessageSchemaDetector.js +35 -5
  57. package/src/domain/services/MigrationService.js +1 -4
  58. package/src/domain/services/ObserverView.js +1 -7
  59. package/src/domain/services/OpNormalizer.js +79 -0
  60. package/src/domain/services/PatchBuilderV2.js +67 -38
  61. package/src/domain/services/PatchMessageCodec.js +1 -6
  62. package/src/domain/services/PropertyIndexBuilder.js +1 -2
  63. package/src/domain/services/PropertyIndexReader.js +1 -4
  64. package/src/domain/services/ProvenanceIndex.js +5 -7
  65. package/src/domain/services/ProvenancePayload.js +1 -1
  66. package/src/domain/services/QueryBuilder.js +3 -16
  67. package/src/domain/services/StateDiff.js +3 -9
  68. package/src/domain/services/StateSerializerV5.js +10 -10
  69. package/src/domain/services/StreamingBitmapIndexBuilder.js +13 -41
  70. package/src/domain/services/SyncAuthService.js +8 -32
  71. package/src/domain/services/SyncController.js +5 -25
  72. package/src/domain/services/SyncProtocol.js +10 -13
  73. package/src/domain/services/SyncTrustGate.js +4 -9
  74. package/src/domain/services/TemporalQuery.js +9 -27
  75. package/src/domain/services/TranslationCost.js +2 -8
  76. package/src/domain/services/WarpMessageCodec.js +2 -0
  77. package/src/domain/services/WarpStateIndexBuilder.js +2 -4
  78. package/src/domain/services/WormholeService.js +9 -25
  79. package/src/domain/trust/TrustCrypto.js +9 -10
  80. package/src/domain/trust/TrustEvaluator.js +1 -8
  81. package/src/domain/trust/TrustRecordService.js +5 -10
  82. package/src/domain/types/TickReceipt.js +9 -11
  83. package/src/domain/types/WarpTypes.js +1 -5
  84. package/src/domain/types/WarpTypesV2.js +78 -13
  85. package/src/domain/utils/CachedValue.js +1 -4
  86. package/src/domain/utils/MinHeap.js +3 -3
  87. package/src/domain/utils/RefLayout.js +26 -0
  88. package/src/domain/utils/WriterId.js +2 -7
  89. package/src/domain/utils/canonicalCbor.js +1 -1
  90. package/src/domain/utils/defaultClock.js +1 -0
  91. package/src/domain/utils/defaultCodec.js +1 -1
  92. package/src/domain/utils/parseCursorBlob.js +4 -4
  93. package/src/domain/warp/PatchSession.js +3 -8
  94. package/src/domain/warp/Writer.js +9 -12
  95. package/src/domain/warp/_wire.js +2 -2
  96. package/src/domain/warp/_wiredMethods.d.ts +5 -7
  97. package/src/domain/warp/checkpoint.methods.js +1 -1
  98. package/src/domain/warp/fork.methods.js +2 -6
  99. package/src/domain/warp/materializeAdvanced.methods.js +3 -3
  100. package/src/domain/warp/patch.methods.js +8 -8
  101. package/src/domain/warp/provenance.methods.js +5 -5
  102. package/src/domain/warp/query.methods.js +9 -18
  103. package/src/domain/warp/subscribe.methods.js +2 -8
  104. package/src/globals.d.ts +7 -0
  105. package/src/infrastructure/adapters/BunHttpAdapter.js +14 -18
  106. package/src/infrastructure/adapters/ConsoleLogger.js +2 -9
  107. package/src/infrastructure/adapters/DenoHttpAdapter.js +15 -15
  108. package/src/infrastructure/adapters/GitGraphAdapter.js +234 -58
  109. package/src/infrastructure/adapters/InMemoryGraphAdapter.js +9 -2
  110. package/src/infrastructure/adapters/NodeHttpAdapter.js +14 -14
  111. package/src/infrastructure/adapters/WebCryptoAdapter.js +1 -2
  112. package/src/ports/BlobPort.js +2 -2
  113. package/src/ports/HttpServerPort.js +24 -2
  114. package/src/ports/RefPort.js +2 -1
  115. package/src/visualization/renderers/ascii/box.js +1 -1
  116. package/src/visualization/renderers/ascii/check.js +1 -5
  117. package/src/visualization/renderers/ascii/history.js +1 -6
  118. package/src/visualization/renderers/ascii/path.js +4 -22
  119. package/src/visualization/renderers/ascii/progress.js +1 -4
  120. package/src/visualization/renderers/ascii/seek.js +1 -5
  121. package/src/visualization/renderers/ascii/table.js +1 -3
@@ -5,13 +5,13 @@
5
5
  */
6
6
 
7
7
  /**
8
- * Parses and validates a cursor blob (Buffer) into a cursor object.
8
+ * Parses and validates a cursor blob (Uint8Array) into a cursor object.
9
9
  *
10
10
  * The blob must contain UTF-8-encoded JSON representing a plain object with at
11
11
  * minimum a finite numeric `tick` field. Any additional fields (e.g. `mode`,
12
12
  * `name`) are preserved in the returned object.
13
13
  *
14
- * @param {Buffer} buf - Raw blob contents (UTF-8 encoded JSON)
14
+ * @param {Uint8Array} buf - Raw blob contents (UTF-8 encoded JSON)
15
15
  * @param {string} label - Human-readable label used in error messages
16
16
  * (e.g. `"active cursor"`, `"saved cursor 'foo'"`)
17
17
  * @returns {{ tick: number, mode?: string, [key: string]: unknown }}
@@ -23,13 +23,13 @@
23
23
  * Infinity
24
24
  *
25
25
  * @example
26
- * const buf = Buffer.from('{"tick":5,"mode":"lamport"}', 'utf8');
26
+ * const buf = new TextEncoder().encode('{"tick":5,"mode":"lamport"}');
27
27
  * const cursor = parseCursorBlob(buf, 'active cursor');
28
28
  * // => { tick: 5, mode: 'lamport' }
29
29
  *
30
30
  * @example
31
31
  * // Throws: "Corrupted active cursor: blob is not valid JSON"
32
- * parseCursorBlob(Buffer.from('not json', 'utf8'), 'active cursor');
32
+ * parseCursorBlob(new TextEncoder().encode('not json'), 'active cursor');
33
33
  */
34
34
  export function parseCursorBlob(buf, label) {
35
35
  let obj;
@@ -19,12 +19,7 @@ export class PatchSession {
19
19
  /**
20
20
  * Creates a new PatchSession.
21
21
  *
22
- * @param {Object} options
23
- * @param {import('../services/PatchBuilderV2.js').PatchBuilderV2} options.builder - Internal builder
24
- * @param {import('../../ports/GraphPersistencePort.js').default & import('../../ports/RefPort.js').default} options.persistence - Git adapter
25
- * @param {string} options.graphName - Graph namespace
26
- * @param {string} options.writerId - Writer ID
27
- * @param {string|null} options.expectedOldHead - Expected parent SHA for CAS
22
+ * @param {{ builder: import('../services/PatchBuilderV2.js').PatchBuilderV2, persistence: import('../../ports/GraphPersistencePort.js').default & import('../../ports/RefPort.js').default, graphName: string, writerId: string, expectedOldHead: string|null }} options
28
23
  */
29
24
  constructor({ builder, persistence, graphName, writerId, expectedOldHead }) {
30
25
  /** @type {import('../services/PatchBuilderV2.js').PatchBuilderV2} */
@@ -152,7 +147,7 @@ export class PatchSession {
152
147
  * Attaches content to a node.
153
148
  *
154
149
  * @param {string} nodeId - The node ID to attach content to
155
- * @param {Buffer|string} content - The content to attach
150
+ * @param {Uint8Array|string} content - The content to attach
156
151
  * @returns {Promise<this>} This session for chaining
157
152
  * @throws {WriterError} SESSION_COMMITTED if already committed
158
153
  */
@@ -168,7 +163,7 @@ export class PatchSession {
168
163
  * @param {string} from - Source node ID
169
164
  * @param {string} to - Target node ID
170
165
  * @param {string} label - Edge label/type
171
- * @param {Buffer|string} content - The content to attach
166
+ * @param {Uint8Array|string} content - The content to attach
172
167
  * @returns {Promise<this>} This session for chaining
173
168
  * @throws {WriterError} SESSION_COMMITTED if already committed
174
169
  */
@@ -15,6 +15,7 @@
15
15
  */
16
16
 
17
17
  import defaultCodec from '../utils/defaultCodec.js';
18
+ import nullLogger from '../utils/nullLogger.js';
18
19
  import { validateWriterId, buildWriterRef } from '../utils/RefLayout.js';
19
20
  import { PatchSession } from './PatchSession.js';
20
21
  import { PatchBuilderV2 } from '../services/PatchBuilderV2.js';
@@ -35,17 +36,9 @@ export class Writer {
35
36
  /**
36
37
  * Creates a new Writer instance.
37
38
  *
38
- * @param {Object} options
39
- * @param {import('../../ports/GraphPersistencePort.js').default & import('../../ports/RefPort.js').default & import('../../ports/CommitPort.js').default} options.persistence - Git adapter
40
- * @param {string} options.graphName - Graph namespace
41
- * @param {string} options.writerId - This writer's ID
42
- * @param {import('../crdt/VersionVector.js').VersionVector} options.versionVector - Current version vector
43
- * @param {() => Promise<import('../services/JoinReducer.js').WarpStateV5>} options.getCurrentState - Async function returning the current materialized V5 state
44
- * @param {(result: {patch: Object, sha: string}) => void | Promise<void>} [options.onCommitSuccess] - Callback invoked after successful commit with { patch, sha }
45
- * @param {'reject'|'cascade'|'warn'} [options.onDeleteWithData='warn'] - Policy when deleting a node with attached data
46
- * @param {import('../../ports/CodecPort.js').default} [options.codec] - Codec for CBOR serialization (defaults to domain-local codec)
39
+ * @param {{ persistence: import('../../ports/GraphPersistencePort.js').default & import('../../ports/RefPort.js').default & import('../../ports/CommitPort.js').default, graphName: string, writerId: string, versionVector: import('../crdt/VersionVector.js').VersionVector, getCurrentState: () => import('../services/JoinReducer.js').WarpStateV5 | null, onCommitSuccess?: (result: {patch: import('../types/WarpTypesV2.js').PatchV2, sha: string}) => void | Promise<void>, onDeleteWithData?: 'reject'|'cascade'|'warn', codec?: import('../../ports/CodecPort.js').default, logger?: import('../../ports/LoggerPort.js').default }} options
47
40
  */
48
- constructor({ persistence, graphName, writerId, versionVector, getCurrentState, onCommitSuccess, onDeleteWithData = 'warn', codec }) {
41
+ constructor({ persistence, graphName, writerId, versionVector, getCurrentState, onCommitSuccess, onDeleteWithData = 'warn', codec, logger }) {
49
42
  validateWriterId(writerId);
50
43
 
51
44
  /** @type {import('../../ports/GraphPersistencePort.js').default & import('../../ports/RefPort.js').default & import('../../ports/CommitPort.js').default} */
@@ -60,10 +53,10 @@ export class Writer {
60
53
  /** @type {import('../crdt/VersionVector.js').VersionVector} */
61
54
  this._versionVector = versionVector;
62
55
 
63
- /** @type {Function} */
56
+ /** @type {() => import('../services/JoinReducer.js').WarpStateV5 | null} */
64
57
  this._getCurrentState = getCurrentState;
65
58
 
66
- /** @type {Function|undefined} */
59
+ /** @type {((result: {patch: import('../types/WarpTypesV2.js').PatchV2, sha: string}) => void | Promise<void>)|undefined} */
67
60
  this._onCommitSuccess = onCommitSuccess;
68
61
 
69
62
  /** @type {'reject'|'cascade'|'warn'} */
@@ -72,6 +65,9 @@ export class Writer {
72
65
  /** @type {import('../../ports/CodecPort.js').default|undefined} */
73
66
  this._codec = codec || defaultCodec;
74
67
 
68
+ /** @type {import('../../ports/LoggerPort.js').default} */
69
+ this._logger = logger || nullLogger;
70
+
75
71
  /** @type {boolean} */
76
72
  this._commitInProgress = false;
77
73
  }
@@ -151,6 +147,7 @@ export class Writer {
151
147
  onCommitSuccess: this._onCommitSuccess,
152
148
  onDeleteWithData: this._onDeleteWithData,
153
149
  codec: this._codec,
150
+ logger: this._logger,
154
151
  });
155
152
 
156
153
  // Return PatchSession wrapping the builder
@@ -14,8 +14,8 @@
14
14
  * become method names on the prototype. Duplicates across modules are
15
15
  * detected eagerly and throw at import time (not at call time).
16
16
  *
17
- * @param {Function} Class - The class constructor whose prototype to extend
18
- * @param {Array<Record<string, Function>>} methodModules - Array of method module namespace objects
17
+ * @param {{ prototype: object, name: string }} Class - The class constructor whose prototype to extend
18
+ * @param {Array<Record<string, unknown>>} methodModules - Array of method module namespace objects
19
19
  * @throws {Error} If a method name appears in more than one module
20
20
  */
21
21
  export function wireWarpMethods(Class, methodModules) {
@@ -179,13 +179,11 @@ declare module '../WarpGraph.js' {
179
179
  // ── provenance.methods.js ─────────────────────────────────────────────
180
180
  patchesFor(entityId: string): Promise<string[]>;
181
181
  materializeSlice(nodeId: string, options?: { receipts?: boolean }): Promise<{ state: WarpStateV5; patchCount: number; receipts?: TickReceipt[] }>;
182
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- internal method; `any` avoids breaking provenance.methods.js callers
183
- _computeBackwardCone(nodeId: string): Promise<Map<string, any>>;
184
- loadPatchBySha(sha: string): Promise<{ patch: PatchV2; sha: string }>;
185
- _loadPatchBySha(sha: string): Promise<{ patch: PatchV2; sha: string }>;
182
+ _computeBackwardCone(nodeId: string): Promise<Map<string, PatchV2>>;
183
+ loadPatchBySha(sha: string): Promise<PatchV2>;
184
+ _loadPatchBySha(sha: string): Promise<PatchV2>;
186
185
  _loadPatchesBySha(shas: string[]): Promise<Array<{ patch: PatchV2; sha: string }>>;
187
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- internal method; `any` avoids breaking provenance.methods.js callers
188
- _sortPatchesCausally(patches: Array<{ patch: any; sha: string }>): Array<{ patch: any; sha: string }>;
186
+ _sortPatchesCausally(patches: Array<{ patch: PatchV2; sha: string }>): Array<{ patch: PatchV2; sha: string }>;
189
187
 
190
188
  // ── fork.methods.js ───────────────────────────────────────────────────
191
189
  fork(options: { from: string; at: string; forkName?: string; forkWriterId?: string }): Promise<WarpGraph>;
@@ -251,7 +249,7 @@ declare module '../WarpGraph.js' {
251
249
  _buildView(state: WarpStateV5, stateHash: string, diff?: import('../types/PatchDiff.js').PatchDiff): void;
252
250
  _setMaterializedState(state: WarpStateV5, optionsOrDiff?: import('../types/PatchDiff.js').PatchDiff | { diff?: import('../types/PatchDiff.js').PatchDiff | null }): Promise<{ state: WarpStateV5; stateHash: string; adjacency: unknown }>;
253
251
  _materializeWithCeiling(ceiling: number, collectReceipts: boolean, t0: number): Promise<WarpStateV5 | { state: WarpStateV5; receipts: TickReceipt[] }>;
254
- _persistSeekCacheEntry(cacheKey: string, buf: Buffer, state: WarpStateV5): Promise<void>;
252
+ _persistSeekCacheEntry(cacheKey: string, buf: Uint8Array, state: WarpStateV5): Promise<void>;
255
253
  _restoreIndexFromCache(indexTreeOid: string): Promise<void>;
256
254
  materializeAt(checkpointSha: string): Promise<WarpStateV5>;
257
255
  verifyIndex(options?: { seed?: number; sampleRate?: number }): { passed: number; failed: number; errors: Array<{ nodeId: string; direction: string; expected: string[]; actual: string[] }> };
@@ -375,7 +375,7 @@ export function _maybeRunGC(state) {
375
375
  * **Requires a cached state.**
376
376
  *
377
377
  * @this {import('../WarpGraph.js').default}
378
- * @returns {{ran: boolean, result: Object|null, reasons: string[]}} GC result
378
+ * @returns {{ran: boolean, result: import('../services/GCPolicy.js').GCExecuteResult|null, reasons: string[]}} GC result
379
379
  *
380
380
  * @example
381
381
  * await graph.materialize();
@@ -31,11 +31,7 @@ import { createWormhole as createWormholeImpl } from '../services/WormholeServic
31
31
  * - History up to the fork point is shared (content-addressed dedup)
32
32
  *
33
33
  * @this {import('../WarpGraph.js').default}
34
- * @param {Object} options - Fork configuration
35
- * @param {string} options.from - Writer ID whose chain to fork from
36
- * @param {string} options.at - Patch SHA to fork at (must be in the writer's chain)
37
- * @param {string} [options.forkName] - Name for the forked graph. Defaults to `<graphName>-fork-<timestamp>`
38
- * @param {string} [options.forkWriterId] - Writer ID for the fork. Defaults to a new canonical ID.
34
+ * @param {{ from: string, at: string, forkName?: string, forkWriterId?: string }} options - Fork configuration
39
35
  * @returns {Promise<import('../WarpGraph.js').default>} A new WarpGraph instance for the fork
40
36
  * @throws {ForkError} If `from` writer does not exist (code: `E_FORK_WRITER_NOT_FOUND`)
41
37
  * @throws {ForkError} If `at` SHA does not exist (code: `E_FORK_PATCH_NOT_FOUND`)
@@ -104,7 +100,7 @@ export async function fork({ from, at, forkName, forkWriterId }) {
104
100
 
105
101
  // 4. Generate or validate fork name (add random suffix to prevent collisions)
106
102
  const resolvedForkName =
107
- forkName ?? `${this._graphName}-fork-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
103
+ forkName ?? `${this._graphName}-fork-${Math.random().toString(36).slice(2, 10).padEnd(8, '0')}`;
108
104
  try {
109
105
  validateGraphName(resolvedForkName);
110
106
  } catch (err) {
@@ -351,7 +351,7 @@ export async function _materializeWithCeiling(ceiling, collectReceipts, t0) {
351
351
  cacheKey = buildSeekCacheKey(ceiling, frontier);
352
352
  }
353
353
  const buf = serializeFullStateV5(state, { codec: this._codec });
354
- this._persistSeekCacheEntry(cacheKey, /** @type {Buffer} */ (buf), state)
354
+ this._persistSeekCacheEntry(cacheKey, buf, state)
355
355
  .catch(() => {});
356
356
  }
357
357
 
@@ -377,7 +377,7 @@ export async function _materializeWithCeiling(ceiling, collectReceipts, t0) {
377
377
  *
378
378
  * @this {import('../WarpGraph.js').default}
379
379
  * @param {string} cacheKey - Seek cache key
380
- * @param {Buffer} buf - Serialized WarpStateV5 buffer
380
+ * @param {Uint8Array} buf - Serialized WarpStateV5 buffer
381
381
  * @param {import('../services/JoinReducer.js').WarpStateV5} state
382
382
  * @returns {Promise<void>}
383
383
  * @private
@@ -473,7 +473,7 @@ export async function materializeAt(checkpointSha) {
473
473
 
474
474
  const patchMeta = decodePatchMessage(message);
475
475
  const patchBuffer = await this._persistence.readBlob(patchMeta.patchOid);
476
- const patch = this._codec.decode(patchBuffer);
476
+ const patch = /** @type {import('../types/WarpTypesV2.js').PatchV2} */ (this._codec.decode(patchBuffer));
477
477
 
478
478
  patches.push({ patch, sha: currentSha });
479
479
 
@@ -287,10 +287,11 @@ export async function writer(writerId) {
287
287
  graphName: this._graphName,
288
288
  writerId: resolvedWriterId,
289
289
  versionVector: this._versionVector,
290
- getCurrentState: /** @type {() => Promise<import('../services/JoinReducer.js').WarpStateV5>} */ (/** @type {unknown} */ (() => this._cachedState)),
290
+ getCurrentState: () => this._cachedState,
291
291
  onDeleteWithData: this._onDeleteWithData,
292
- onCommitSuccess: /** @type {(result: {patch: Object, sha: string}) => void} */ (/** @type {unknown} */ ((/** @type {{patch?: import('../types/WarpTypesV2.js').PatchV2, sha?: string}} */ opts) => this._onPatchCommitted(resolvedWriterId, opts))),
292
+ onCommitSuccess: /** @type {(result: {patch: import('../types/WarpTypesV2.js').PatchV2, sha: string}) => void} */ ((/** @type {{patch?: import('../types/WarpTypesV2.js').PatchV2, sha?: string}} */ opts) => this._onPatchCommitted(resolvedWriterId, opts)),
293
293
  codec: this._codec,
294
+ logger: this._logger || undefined,
294
295
  });
295
296
  }
296
297
 
@@ -303,9 +304,7 @@ export async function writer(writerId) {
303
304
  *
304
305
  * @deprecated Use `writer()` to resolve a stable ID from git config, or `writer(id)` with an explicit ID.
305
306
  * @this {import('../WarpGraph.js').default}
306
- * @param {Object} [opts]
307
- * @param {'config'|'none'} [opts.persist='none'] - Whether to persist the new ID to git config
308
- * @param {string} [opts.alias] - Optional alias for config key (used with persist:'config')
307
+ * @param {{ persist?: 'config'|'none', alias?: string }} [opts]
309
308
  * @returns {Promise<Writer>} A Writer instance with new canonical ID
310
309
  * @throws {Error} If config operations fail (when persist:'config')
311
310
  *
@@ -345,10 +344,11 @@ export async function createWriter(opts = {}) {
345
344
  graphName: this._graphName,
346
345
  writerId: freshWriterId,
347
346
  versionVector: this._versionVector,
348
- getCurrentState: /** @type {() => Promise<import('../services/JoinReducer.js').WarpStateV5>} */ (/** @type {unknown} */ (() => this._cachedState)),
347
+ getCurrentState: () => this._cachedState,
349
348
  onDeleteWithData: this._onDeleteWithData,
350
- onCommitSuccess: /** @type {(result: {patch: Object, sha: string}) => void} */ (/** @type {unknown} */ ((/** @type {{patch?: import('../types/WarpTypesV2.js').PatchV2, sha?: string}} */ commitOpts) => this._onPatchCommitted(freshWriterId, commitOpts))),
349
+ onCommitSuccess: /** @type {(result: {patch: import('../types/WarpTypesV2.js').PatchV2, sha: string}) => void} */ ((/** @type {{patch?: import('../types/WarpTypesV2.js').PatchV2, sha?: string}} */ commitOpts) => this._onPatchCommitted(freshWriterId, commitOpts)),
351
350
  codec: this._codec,
351
+ logger: this._logger || undefined,
352
352
  });
353
353
  }
354
354
 
@@ -482,7 +482,7 @@ export async function discoverTicks() {
482
482
  *
483
483
  * @this {import('../WarpGraph.js').default}
484
484
  * @param {import('../services/JoinReducer.js').WarpStateV5} otherState - The state to merge in
485
- * @returns {{state: import('../services/JoinReducer.js').WarpStateV5, receipt: Object}} Merged state and receipt
485
+ * @returns {{state: import('../services/JoinReducer.js').WarpStateV5, receipt: {nodesAdded: number, nodesRemoved: number, edgesAdded: number, edgesRemoved: number, propsChanged: number, frontierMerged: boolean}}} Merged state and receipt
486
486
  * @throws {QueryError} If no cached state exists (code: `E_NO_STATE`)
487
487
  * @throws {Error} If otherState is invalid
488
488
  */
@@ -152,7 +152,7 @@ export async function materializeSlice(nodeId, options) {
152
152
  *
153
153
  * @this {import('../WarpGraph.js').default}
154
154
  * @param {string} nodeId - The target node ID
155
- * @returns {Promise<Map<string, Object>>} Map of patch SHA to loaded patch object
155
+ * @returns {Promise<Map<string, import('../types/WarpTypesV2.js').PatchV2>>} Map of patch SHA to loaded patch object
156
156
  */
157
157
  export async function _computeBackwardCone(nodeId) {
158
158
  if (!this._provenanceIndex) {
@@ -209,7 +209,7 @@ export async function _computeBackwardCone(nodeId) {
209
209
  *
210
210
  * @this {import('../WarpGraph.js').default}
211
211
  * @param {string} sha - The patch commit SHA
212
- * @returns {Promise<Object>} The decoded patch object
212
+ * @returns {Promise<import('../types/WarpTypesV2.js').PatchV2>} The decoded patch object
213
213
  * @throws {Error} If the commit is not a patch or loading fails
214
214
  */
215
215
  export async function loadPatchBySha(sha) {
@@ -221,7 +221,7 @@ export async function loadPatchBySha(sha) {
221
221
  *
222
222
  * @this {import('../WarpGraph.js').default}
223
223
  * @param {string} sha - The patch commit SHA
224
- * @returns {Promise<Object>} The decoded patch object
224
+ * @returns {Promise<import('../types/WarpTypesV2.js').PatchV2>} The decoded patch object
225
225
  * @throws {Error} If the commit is not a patch or loading fails
226
226
  */
227
227
  export async function _loadPatchBySha(sha) {
@@ -234,7 +234,7 @@ export async function _loadPatchBySha(sha) {
234
234
 
235
235
  const patchMeta = decodePatchMessage(nodeInfo.message);
236
236
  const patchBuffer = await this._persistence.readBlob(patchMeta.patchOid);
237
- return /** @type {Object} */ (this._codec.decode(patchBuffer));
237
+ return /** @type {import('../types/WarpTypesV2.js').PatchV2} */ (this._codec.decode(patchBuffer));
238
238
  }
239
239
 
240
240
  /**
@@ -242,7 +242,7 @@ export async function _loadPatchBySha(sha) {
242
242
  *
243
243
  * @this {import('../WarpGraph.js').default}
244
244
  * @param {string[]} shas - Array of patch commit SHAs
245
- * @returns {Promise<Array<{patch: Object, sha: string}>>} Array of patch entries
245
+ * @returns {Promise<Array<{patch: import('../types/WarpTypesV2.js').PatchV2, sha: string}>>} Array of patch entries
246
246
  * @throws {Error} If any SHA is not a patch or loading fails
247
247
  */
248
248
  export async function _loadPatchesBySha(shas) {
@@ -311,10 +311,7 @@ export function query() {
311
311
  *
312
312
  * @this {import('../WarpGraph.js').default}
313
313
  * @param {string} name - Observer name
314
- * @param {Object} config - Observer configuration
315
- * @param {string|string[]} config.match - Glob pattern(s) for visible nodes
316
- * @param {string[]} [config.expose] - Property keys to include
317
- * @param {string[]} [config.redact] - Property keys to exclude
314
+ * @param {{ match: string|string[], expose?: string[], redact?: string[] }} config - Observer configuration
318
315
  * @returns {Promise<import('../services/ObserverView.js').default>} A read-only observer view
319
316
  */
320
317
  export async function observer(name, config) {
@@ -331,14 +328,8 @@ export async function observer(name, config) {
331
328
  * Computes the directed MDL translation cost from observer A to observer B.
332
329
  *
333
330
  * @this {import('../WarpGraph.js').default}
334
- * @param {Object} configA - Observer configuration for A
335
- * @param {string|string[]} configA.match - Glob pattern(s) for visible nodes
336
- * @param {string[]} [configA.expose] - Property keys to include
337
- * @param {string[]} [configA.redact] - Property keys to exclude
338
- * @param {Object} configB - Observer configuration for B
339
- * @param {string|string[]} configB.match - Glob pattern(s) for visible nodes
340
- * @param {string[]} [configB.expose] - Property keys to include
341
- * @param {string[]} [configB.redact] - Property keys to exclude
331
+ * @param {{ match: string|string[], expose?: string[], redact?: string[] }} configA - Observer configuration for A
332
+ * @param {{ match: string|string[], expose?: string[], redact?: string[] }} configB - Observer configuration for B
342
333
  * @returns {Promise<{cost: number, breakdown: {nodeLoss: number, edgeLoss: number, propLoss: number}}>}
343
334
  */
344
335
  export async function translationCost(configA, configB) {
@@ -368,12 +359,12 @@ export async function getContentOid(nodeId) {
368
359
  /**
369
360
  * Gets the content blob for a node, or null if none is attached.
370
361
  *
371
- * Returns the raw Buffer from `readBlob()`. Consumers wanting text
372
- * should call `.toString('utf8')` on the result.
362
+ * Returns the raw bytes from `readBlob()`. Consumers wanting text
363
+ * should decode the result with `new TextDecoder().decode(buf)`.
373
364
  *
374
365
  * @this {import('../WarpGraph.js').default}
375
366
  * @param {string} nodeId - The node ID to get content for
376
- * @returns {Promise<Buffer|null>} Content buffer or null
367
+ * @returns {Promise<Uint8Array|null>} Content bytes or null
377
368
  * @throws {Error} If the referenced blob OID is not in the object store
378
369
  * (e.g., garbage-collected despite anchoring). Callers should handle this
379
370
  * if operating on repos with aggressive GC or partial clones.
@@ -409,14 +400,14 @@ export async function getEdgeContentOid(from, to, label) {
409
400
  /**
410
401
  * Gets the content blob for an edge, or null if none is attached.
411
402
  *
412
- * Returns the raw Buffer from `readBlob()`. Consumers wanting text
413
- * should call `.toString('utf8')` on the result.
403
+ * Returns the raw bytes from `readBlob()`. Consumers wanting text
404
+ * should decode the result with `new TextDecoder().decode(buf)`.
414
405
  *
415
406
  * @this {import('../WarpGraph.js').default}
416
407
  * @param {string} from - Source node ID
417
408
  * @param {string} to - Target node ID
418
409
  * @param {string} label - Edge label
419
- * @returns {Promise<Buffer|null>} Content buffer or null
410
+ * @returns {Promise<Uint8Array|null>} Content bytes or null
420
411
  * @throws {Error} If the referenced blob OID is not in the object store
421
412
  * (e.g., garbage-collected despite anchoring). Callers should handle this
422
413
  * if operating on repos with aggressive GC or partial clones.
@@ -22,10 +22,7 @@ import { matchGlob } from '../utils/matchGlob.js';
22
22
  * One handler's error does not prevent other handlers from being called.
23
23
  *
24
24
  * @this {import('../WarpGraph.js').default}
25
- * @param {Object} options - Subscription options
26
- * @param {(diff: import('../services/StateDiff.js').StateDiffResult) => void} options.onChange - Called with diff when graph changes
27
- * @param {(error: Error) => void} [options.onError] - Called if onChange throws an error
28
- * @param {boolean} [options.replay=false] - If true, immediately fires onChange with initial state diff
25
+ * @param {{ onChange: (diff: import('../services/StateDiff.js').StateDiffResult) => void, onError?: (error: unknown) => void, replay?: boolean }} options - Subscription options
29
26
  * @returns {{unsubscribe: () => void}} Subscription handle
30
27
  * @throws {Error} If onChange is not a function
31
28
  *
@@ -103,10 +100,7 @@ export function subscribe({ onChange, onError, replay = false }) {
103
100
  *
104
101
  * @this {import('../WarpGraph.js').default}
105
102
  * @param {string|string[]} pattern - Glob pattern(s) (e.g., 'user:*', 'order:123', '*')
106
- * @param {Object} options - Watch options
107
- * @param {(diff: import('../services/StateDiff.js').StateDiffResult) => void} options.onChange - Called with filtered diff when matching changes occur
108
- * @param {(error: Error) => void} [options.onError] - Called if onChange throws an error
109
- * @param {number} [options.poll] - Poll interval in ms (min 1000); checks frontier and auto-materializes
103
+ * @param {{ onChange: (diff: import('../services/StateDiff.js').StateDiffResult) => void, onError?: (error: unknown) => void, poll?: number }} options - Watch options
110
104
  * @returns {{unsubscribe: () => void}} Subscription handle
111
105
  * @throws {Error} If pattern is not a string or array of strings
112
106
  * @throws {Error} If onChange is not a function
package/src/globals.d.ts CHANGED
@@ -62,3 +62,10 @@ interface BunServeOptions {
62
62
  declare namespace Bun {
63
63
  function serve(options: BunServeOptions): BunServer;
64
64
  }
65
+
66
+ /* ------------------------------------------------------------------ */
67
+ /* globalThis augmentation */
68
+ /* ------------------------------------------------------------------ */
69
+
70
+ declare var Bun: typeof Bun | undefined;
71
+ declare var Deno: typeof Deno | undefined;
@@ -96,8 +96,8 @@ function toResponse(portResponse) {
96
96
  * Creates the Bun fetch handler that bridges between Request/Response
97
97
  * and the HttpServerPort plain-object contract.
98
98
  *
99
- * @param {Function} requestHandler - Port-style async handler
100
- * @param {{ error: Function }} logger
99
+ * @param {(request: import('../../ports/HttpServerPort.js').HttpRequest) => Promise<import('../../ports/HttpServerPort.js').HttpResponse>} requestHandler - Port-style async handler
100
+ * @param {{ error: (...args: unknown[]) => void }} logger
101
101
  * @returns {(request: Request) => Promise<Response>}
102
102
  */
103
103
  function createFetchHandler(requestHandler, logger) {
@@ -125,10 +125,6 @@ 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
-
132
128
  /**
133
129
  * Starts a Bun server and invokes the callback with (null) on success
134
130
  * or (err) on failure.
@@ -137,7 +133,7 @@ function createFetchHandler(requestHandler, logger) {
137
133
  * (unlike Node's server.listen which defers via the event loop).
138
134
  *
139
135
  * @param {BunServeOptions} serveOptions
140
- * @param {Function|undefined} cb - Node-style callback
136
+ * @param {((err: Error | null) => void) | undefined} cb - Node-style callback
141
137
  * @returns {BunServer} The Bun server instance
142
138
  */
143
139
  function startServer(serveOptions, cb) {
@@ -152,7 +148,7 @@ function startServer(serveOptions, cb) {
152
148
  * Safely stops a Bun server, forwarding errors to the callback.
153
149
  *
154
150
  * @param {{ server: BunServer | null }} state - Shared mutable state
155
- * @param {Function} [callback]
151
+ * @param {(err?: Error) => void} [callback]
156
152
  */
157
153
  function stopServer(state, callback) {
158
154
  try {
@@ -165,9 +161,9 @@ function stopServer(state, callback) {
165
161
  if (callback) {
166
162
  callback();
167
163
  }
168
- } catch (err) {
164
+ } catch (/** @type {unknown} */ err) {
169
165
  if (callback) {
170
- callback(err);
166
+ callback(err instanceof Error ? err : new Error(String(err)));
171
167
  }
172
168
  }
173
169
  }
@@ -184,7 +180,7 @@ const noopLogger = { error() {} };
184
180
  */
185
181
  export default class BunHttpAdapter extends HttpServerPort {
186
182
  /**
187
- * @param {{ logger?: { error: Function } }} [options]
183
+ * @param {{ logger?: { error: (...args: unknown[]) => void } }} [options]
188
184
  */
189
185
  constructor({ logger } = {}) {
190
186
  super();
@@ -192,8 +188,8 @@ export default class BunHttpAdapter extends HttpServerPort {
192
188
  }
193
189
 
194
190
  /**
195
- * @param {Function} requestHandler
196
- * @returns {{ listen: Function, close: Function, address: Function }}
191
+ * @param {(request: import('../../ports/HttpServerPort.js').HttpRequest) => Promise<import('../../ports/HttpServerPort.js').HttpResponse>} requestHandler
192
+ * @returns {import('../../ports/HttpServerPort.js').HttpServerHandle}
197
193
  */
198
194
  createServer(requestHandler) {
199
195
  const fetchHandler = createFetchHandler(requestHandler, this._logger);
@@ -203,8 +199,8 @@ export default class BunHttpAdapter extends HttpServerPort {
203
199
  return {
204
200
  /**
205
201
  * @param {number} port
206
- * @param {string|Function} [host]
207
- * @param {Function} [callback]
202
+ * @param {string|((err?: Error | null) => void)} [host]
203
+ * @param {(err?: Error | null) => void} [callback]
208
204
  */
209
205
  listen(port, host, callback) {
210
206
  const cb = typeof host === 'function' ? host : callback;
@@ -218,14 +214,14 @@ export default class BunHttpAdapter extends HttpServerPort {
218
214
 
219
215
  try {
220
216
  state.server = startServer(serveOptions, cb);
221
- } catch (err) {
217
+ } catch (/** @type {unknown} */ err) {
222
218
  if (cb) {
223
- cb(err);
219
+ cb(err instanceof Error ? err : new Error(String(err)));
224
220
  }
225
221
  }
226
222
  },
227
223
 
228
- /** @param {Function} [callback] */
224
+ /** @param {(err?: Error) => void} [callback] */
229
225
  close: (callback) => stopServer(state, callback),
230
226
 
231
227
  address() {
@@ -46,10 +46,7 @@ const LEVEL_NAMES = Object.freeze({
46
46
  export default class ConsoleLogger extends LoggerPort {
47
47
  /**
48
48
  * Creates a new ConsoleLogger instance.
49
- * @param {Object} [options] - Logger options
50
- * @param {number} [options.level=LogLevel.INFO] - Minimum log level to output
51
- * @param {Record<string, unknown>} [options.context={}] - Base context for all log entries
52
- * @param {function(): string} [options.timestampFn] - Custom timestamp function (defaults to ISO string)
49
+ * @param {{ level?: number | string, context?: Record<string, unknown>, timestampFn?: function(): string }} [options] - Logger options
53
50
  */
54
51
  constructor({ level = LogLevel.INFO, context = {}, timestampFn } = {}) {
55
52
  super();
@@ -114,11 +111,7 @@ export default class ConsoleLogger extends LoggerPort {
114
111
 
115
112
  /**
116
113
  * Internal logging implementation.
117
- * @param {Object} opts - Log options
118
- * @param {number} opts.level - Numeric log level
119
- * @param {string} opts.levelName - String representation of level
120
- * @param {string} opts.message - Log message
121
- * @param {Record<string, unknown>} [opts.context] - Additional context
114
+ * @param {{ level: number, levelName: string, message: string, context?: Record<string, unknown> }} opts - Log options
122
115
  * @private
123
116
  */
124
117
  _log({ level, levelName, message, context }) {
@@ -90,8 +90,8 @@ function toDenoResponse(plain) {
90
90
  * Creates a Deno.serve-compatible handler that bridges to the
91
91
  * HttpServerPort request handler contract.
92
92
  *
93
- * @param {Function} requestHandler
94
- * @param {{ error: Function }} logger
93
+ * @param {(request: import('../../ports/HttpServerPort.js').HttpRequest) => Promise<import('../../ports/HttpServerPort.js').HttpResponse>} requestHandler
94
+ * @param {{ error: (...args: unknown[]) => void }} logger
95
95
  * @returns {(request: Request) => Promise<Response>}
96
96
  */
97
97
  function createHandler(requestHandler, logger) {
@@ -123,8 +123,8 @@ function createHandler(requestHandler, logger) {
123
123
  /**
124
124
  * Gracefully shuts down the Deno HTTP server.
125
125
  *
126
- * @param {{ server: *}} state - Shared mutable state `{ server }`
127
- * @param {Function} [callback]
126
+ * @param {{ server: { shutdown: () => Promise<void> } | null }} state - Shared mutable state `{ server }`
127
+ * @param {(err?: Error) => void} [callback]
128
128
  */
129
129
  function closeImpl(state, callback) {
130
130
  if (!state.server) {
@@ -143,7 +143,7 @@ function closeImpl(state, callback) {
143
143
  /** @param {unknown} err */ (err) => {
144
144
  state.server = null;
145
145
  if (callback) {
146
- callback(err);
146
+ callback(err instanceof Error ? err : new Error(String(err)));
147
147
  }
148
148
  }
149
149
  );
@@ -152,7 +152,7 @@ function closeImpl(state, callback) {
152
152
  /**
153
153
  * Returns the server's bound address info.
154
154
  *
155
- * @param {{ server: * }} state - Shared mutable state `{ server }`
155
+ * @param {{ server: { addr: { transport: string, hostname: string, port: number } } | null }} state - Shared mutable state `{ server }`
156
156
  * @returns {{ address: string, port: number, family: string }|null}
157
157
  */
158
158
  function addressImpl(state) {
@@ -183,7 +183,7 @@ const noopLogger = { error() {} };
183
183
  */
184
184
  export default class DenoHttpAdapter extends HttpServerPort {
185
185
  /**
186
- * @param {{ logger?: { error: Function } }} [options]
186
+ * @param {{ logger?: { error: (...args: unknown[]) => void } }} [options]
187
187
  */
188
188
  constructor({ logger } = {}) {
189
189
  super();
@@ -191,19 +191,19 @@ export default class DenoHttpAdapter extends HttpServerPort {
191
191
  }
192
192
 
193
193
  /**
194
- * @param {Function} requestHandler
195
- * @returns {{ listen: Function, close: Function, address: Function }}
194
+ * @param {(request: import('../../ports/HttpServerPort.js').HttpRequest) => Promise<import('../../ports/HttpServerPort.js').HttpResponse>} requestHandler
195
+ * @returns {import('../../ports/HttpServerPort.js').HttpServerHandle}
196
196
  */
197
197
  createServer(requestHandler) {
198
198
  const handler = createHandler(requestHandler, this._logger);
199
- /** @type {{ server: * }} */
199
+ /** @type {{ server: { shutdown: () => Promise<void>, addr: { transport: string, hostname: string, port: number } } | null }} */
200
200
  const state = { server: null };
201
201
 
202
202
  return {
203
203
  /**
204
204
  * @param {number} port
205
- * @param {string|Function} [host]
206
- * @param {Function} [callback]
205
+ * @param {string|((err?: Error | null) => void)} [host]
206
+ * @param {(err?: Error | null) => void} [callback]
207
207
  */
208
208
  listen: (port, host, callback) => {
209
209
  const cb = typeof host === 'function' ? host : callback;
@@ -224,15 +224,15 @@ export default class DenoHttpAdapter extends HttpServerPort {
224
224
  }
225
225
 
226
226
  state.server = globalThis.Deno.serve(serveOptions, handler);
227
- } catch (err) {
227
+ } catch (/** @type {unknown} */ err) {
228
228
  if (cb) {
229
- cb(err);
229
+ cb(err instanceof Error ? err : new Error(String(err)));
230
230
  } else {
231
231
  throw err;
232
232
  }
233
233
  }
234
234
  },
235
- /** @param {Function} [callback] */
235
+ /** @param {(err?: Error) => void} [callback] */
236
236
  close: (callback) => {
237
237
  closeImpl(state, callback);
238
238
  },