@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.
- package/README.md +24 -1
- package/bin/cli/commands/check.js +2 -2
- package/bin/cli/commands/doctor/checks.js +12 -12
- package/bin/cli/commands/doctor/index.js +2 -2
- package/bin/cli/commands/doctor/types.js +1 -1
- package/bin/cli/commands/history.js +12 -5
- package/bin/cli/commands/install-hooks.js +5 -5
- package/bin/cli/commands/materialize.js +2 -2
- package/bin/cli/commands/patch.js +142 -0
- package/bin/cli/commands/path.js +4 -4
- package/bin/cli/commands/query.js +54 -13
- package/bin/cli/commands/registry.js +4 -0
- package/bin/cli/commands/seek.js +17 -11
- package/bin/cli/commands/tree.js +230 -0
- package/bin/cli/commands/trust.js +3 -3
- package/bin/cli/commands/verify-audit.js +8 -7
- package/bin/cli/commands/view.js +6 -5
- package/bin/cli/infrastructure.js +26 -12
- package/bin/cli/shared.js +2 -2
- package/bin/cli/types.js +19 -8
- package/bin/presenters/index.js +35 -9
- package/bin/presenters/json.js +14 -12
- package/bin/presenters/text.js +155 -33
- package/index.d.ts +118 -22
- package/index.js +2 -0
- package/package.json +5 -3
- package/src/domain/WarpGraph.js +4 -1
- package/src/domain/crdt/ORSet.js +8 -8
- package/src/domain/errors/EmptyMessageError.js +2 -2
- package/src/domain/errors/ForkError.js +1 -1
- package/src/domain/errors/IndexError.js +1 -1
- package/src/domain/errors/OperationAbortedError.js +1 -1
- package/src/domain/errors/QueryError.js +1 -1
- package/src/domain/errors/SchemaUnsupportedError.js +1 -1
- package/src/domain/errors/ShardCorruptionError.js +2 -2
- package/src/domain/errors/ShardLoadError.js +2 -2
- package/src/domain/errors/ShardValidationError.js +4 -4
- package/src/domain/errors/StorageError.js +2 -2
- package/src/domain/errors/SyncError.js +1 -1
- package/src/domain/errors/TraversalError.js +1 -1
- package/src/domain/errors/TrustError.js +1 -1
- package/src/domain/errors/WarpError.js +2 -2
- package/src/domain/errors/WormholeError.js +1 -1
- package/src/domain/services/AuditReceiptService.js +6 -6
- package/src/domain/services/AuditVerifierService.js +52 -38
- package/src/domain/services/BitmapIndexBuilder.js +3 -3
- package/src/domain/services/BitmapIndexReader.js +28 -19
- package/src/domain/services/BoundaryTransitionRecord.js +18 -17
- package/src/domain/services/CheckpointSerializerV5.js +17 -16
- package/src/domain/services/CheckpointService.js +22 -3
- package/src/domain/services/CommitDagTraversalService.js +13 -13
- package/src/domain/services/DagPathFinding.js +7 -7
- package/src/domain/services/DagTopology.js +1 -1
- package/src/domain/services/DagTraversal.js +1 -1
- package/src/domain/services/HealthCheckService.js +1 -1
- package/src/domain/services/HookInstaller.js +1 -1
- package/src/domain/services/HttpSyncServer.js +92 -41
- package/src/domain/services/IndexRebuildService.js +7 -7
- package/src/domain/services/IndexStalenessChecker.js +4 -3
- package/src/domain/services/JoinReducer.js +26 -11
- package/src/domain/services/KeyCodec.js +7 -0
- package/src/domain/services/LogicalTraversal.js +1 -1
- package/src/domain/services/MessageCodecInternal.js +1 -1
- package/src/domain/services/MigrationService.js +1 -1
- package/src/domain/services/ObserverView.js +8 -8
- package/src/domain/services/PatchBuilderV2.js +96 -30
- package/src/domain/services/ProvenanceIndex.js +1 -1
- package/src/domain/services/ProvenancePayload.js +1 -1
- package/src/domain/services/QueryBuilder.js +3 -3
- package/src/domain/services/StateDiff.js +14 -11
- package/src/domain/services/StateSerializerV5.js +2 -2
- package/src/domain/services/StreamingBitmapIndexBuilder.js +26 -24
- package/src/domain/services/SyncAuthService.js +3 -2
- package/src/domain/services/SyncProtocol.js +25 -11
- package/src/domain/services/TemporalQuery.js +9 -6
- package/src/domain/services/TranslationCost.js +7 -5
- package/src/domain/services/WormholeService.js +16 -7
- package/src/domain/trust/TrustCanonical.js +3 -3
- package/src/domain/trust/TrustEvaluator.js +18 -3
- package/src/domain/trust/TrustRecordService.js +30 -23
- package/src/domain/trust/TrustStateBuilder.js +21 -8
- package/src/domain/trust/canonical.js +6 -6
- package/src/domain/types/TickReceipt.js +1 -1
- package/src/domain/types/WarpErrors.js +45 -0
- package/src/domain/types/WarpOptions.js +29 -0
- package/src/domain/types/WarpPersistence.js +41 -0
- package/src/domain/types/WarpTypes.js +2 -2
- package/src/domain/types/WarpTypesV2.js +2 -2
- package/src/domain/utils/MinHeap.js +6 -5
- package/src/domain/utils/canonicalStringify.js +5 -4
- package/src/domain/utils/roaring.js +31 -5
- package/src/domain/warp/PatchSession.js +40 -18
- package/src/domain/warp/_wiredMethods.d.ts +199 -45
- package/src/domain/warp/checkpoint.methods.js +5 -1
- package/src/domain/warp/fork.methods.js +2 -2
- package/src/domain/warp/materialize.methods.js +55 -5
- package/src/domain/warp/materializeAdvanced.methods.js +15 -4
- package/src/domain/warp/patch.methods.js +54 -29
- package/src/domain/warp/provenance.methods.js +5 -3
- package/src/domain/warp/query.methods.js +89 -6
- package/src/domain/warp/sync.methods.js +16 -11
- package/src/globals.d.ts +64 -0
- package/src/infrastructure/adapters/BunHttpAdapter.js +14 -9
- package/src/infrastructure/adapters/CasSeekCacheAdapter.js +9 -4
- package/src/infrastructure/adapters/DenoHttpAdapter.js +5 -6
- package/src/infrastructure/adapters/GitGraphAdapter.js +18 -13
- package/src/infrastructure/adapters/NodeHttpAdapter.js +2 -2
- package/src/infrastructure/adapters/WebCryptoAdapter.js +2 -2
- package/src/visualization/layouts/converters.js +2 -2
- package/src/visualization/layouts/elkAdapter.js +1 -1
- package/src/visualization/layouts/elkLayout.js +10 -7
- package/src/visualization/layouts/index.js +1 -1
- package/src/visualization/renderers/ascii/seek.js +16 -6
- 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 {
|
|
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:
|
|
265
|
-
* @returns {Array<{patch:
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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,
|
|
360
|
+
const emit = (/** @type {string} */ type, /** @type {Record<string, unknown>} */ payload = {}) => {
|
|
356
361
|
if (typeof onStatus === 'function') {
|
|
357
|
-
onStatus(/** @type {
|
|
362
|
+
onStatus(/** @type {{type: string, attempt: number}} */ ({ type, attempt, ...payload }));
|
|
358
363
|
}
|
|
359
364
|
};
|
|
360
|
-
const shouldRetry = (/** @type {
|
|
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 (
|
|
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:
|
|
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 {
|
|
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 (
|
|
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
|
|
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 {
|
|
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
|
}
|
package/src/globals.d.ts
ADDED
|
@@ -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 (
|
|
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 {
|
|
139
|
+
* @param {BunServeOptions} serveOptions
|
|
136
140
|
* @param {Function|undefined} cb - Node-style callback
|
|
137
|
-
* @returns {
|
|
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:
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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 (
|
|
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 {
|
|
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 {
|
|
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 (
|
|
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 (
|
|
149
|
-
|
|
149
|
+
} catch (err) {
|
|
150
|
+
const gitErr = /** @type {GitError} */ (err);
|
|
151
|
+
if (getExitCode(gitErr) === 1) {
|
|
150
152
|
return false;
|
|
151
153
|
}
|
|
152
|
-
if (isDanglingObjectError(
|
|
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
|
-
|
|
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 (
|
|
548
|
-
|
|
551
|
+
} catch (err) {
|
|
552
|
+
const gitErr = /** @type {GitError} */ (err);
|
|
553
|
+
if (getExitCode(gitErr) === 1) {
|
|
549
554
|
return null;
|
|
550
555
|
}
|
|
551
|
-
if (isDanglingObjectError(
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 {
|
|
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 {
|
|
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(
|
|
42
|
-
const buf = /** @type {Buffer} */ (
|
|
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,
|
|
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,
|
|
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,
|
|
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
|