@git-stunts/git-warp 10.8.0 → 11.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -32
- package/SECURITY.md +64 -0
- package/bin/cli/commands/check.js +168 -0
- package/bin/cli/commands/doctor/checks.js +422 -0
- package/bin/cli/commands/doctor/codes.js +46 -0
- package/bin/cli/commands/doctor/index.js +239 -0
- package/bin/cli/commands/doctor/types.js +89 -0
- package/bin/cli/commands/history.js +80 -0
- package/bin/cli/commands/info.js +139 -0
- package/bin/cli/commands/install-hooks.js +128 -0
- package/bin/cli/commands/materialize.js +99 -0
- package/bin/cli/commands/patch.js +142 -0
- package/bin/cli/commands/path.js +88 -0
- package/bin/cli/commands/query.js +235 -0
- package/bin/cli/commands/registry.js +32 -0
- package/bin/cli/commands/seek.js +598 -0
- package/bin/cli/commands/tree.js +230 -0
- package/bin/cli/commands/trust.js +154 -0
- package/bin/cli/commands/verify-audit.js +114 -0
- package/bin/cli/commands/view.js +46 -0
- package/bin/cli/infrastructure.js +350 -0
- package/bin/cli/schemas.js +177 -0
- package/bin/cli/shared.js +244 -0
- package/bin/cli/types.js +96 -0
- package/bin/presenters/index.js +41 -9
- package/bin/presenters/json.js +14 -12
- package/bin/presenters/text.js +286 -28
- package/bin/warp-graph.js +5 -2346
- package/index.d.ts +111 -21
- package/index.js +2 -0
- package/package.json +10 -8
- package/src/domain/WarpGraph.js +109 -3252
- 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 +3 -3
- 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 +29 -0
- package/src/domain/errors/WarpError.js +2 -2
- package/src/domain/errors/WormholeError.js +1 -1
- package/src/domain/errors/index.js +1 -0
- package/src/domain/services/AuditMessageCodec.js +137 -0
- package/src/domain/services/AuditReceiptService.js +471 -0
- package/src/domain/services/AuditVerifierService.js +707 -0
- 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 +2 -2
- 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 +120 -55
- package/src/domain/services/IndexRebuildService.js +7 -7
- package/src/domain/services/IndexStalenessChecker.js +4 -3
- package/src/domain/services/JoinReducer.js +11 -11
- package/src/domain/services/LogicalTraversal.js +1 -1
- package/src/domain/services/MessageCodecInternal.js +4 -1
- package/src/domain/services/MessageSchemaDetector.js +2 -2
- package/src/domain/services/MigrationService.js +1 -1
- package/src/domain/services/ObserverView.js +8 -8
- package/src/domain/services/PatchBuilderV2.js +42 -26
- 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 +71 -4
- 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/WarpMessageCodec.js +4 -1
- package/src/domain/services/WormholeService.js +16 -7
- package/src/domain/trust/TrustCanonical.js +42 -0
- package/src/domain/trust/TrustCrypto.js +111 -0
- package/src/domain/trust/TrustEvaluator.js +195 -0
- package/src/domain/trust/TrustRecordService.js +281 -0
- package/src/domain/trust/TrustStateBuilder.js +222 -0
- package/src/domain/trust/canonical.js +68 -0
- package/src/domain/trust/reasonCodes.js +64 -0
- package/src/domain/trust/schemas.js +160 -0
- package/src/domain/trust/verdict.js +42 -0
- 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/types/git-cas.d.ts +20 -0
- package/src/domain/utils/MinHeap.js +6 -5
- package/src/domain/utils/RefLayout.js +59 -0
- package/src/domain/utils/canonicalStringify.js +5 -4
- package/src/domain/utils/roaring.js +31 -5
- package/src/domain/warp/PatchSession.js +26 -17
- package/src/domain/warp/Writer.js +18 -3
- package/src/domain/warp/_internal.js +26 -0
- package/src/domain/warp/_wire.js +58 -0
- package/src/domain/warp/_wiredMethods.d.ts +254 -0
- package/src/domain/warp/checkpoint.methods.js +401 -0
- package/src/domain/warp/fork.methods.js +323 -0
- package/src/domain/warp/materialize.methods.js +238 -0
- package/src/domain/warp/materializeAdvanced.methods.js +350 -0
- package/src/domain/warp/patch.methods.js +554 -0
- package/src/domain/warp/provenance.methods.js +286 -0
- package/src/domain/warp/query.methods.js +280 -0
- package/src/domain/warp/subscribe.methods.js +272 -0
- package/src/domain/warp/sync.methods.js +554 -0
- 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 +79 -11
- package/src/infrastructure/adapters/InMemoryGraphAdapter.js +36 -0
- package/src/infrastructure/adapters/NodeHttpAdapter.js +2 -2
- package/src/infrastructure/adapters/WebCryptoAdapter.js +2 -2
- package/src/ports/CommitPort.js +10 -0
- package/src/ports/RefPort.js +17 -0
- 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
- package/src/hooks/post-merge.sh +0 -60
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import readline from 'node:readline';
|
|
5
|
+
import { execFileSync } from 'node:child_process';
|
|
6
|
+
// @ts-expect-error — no type declarations for @git-stunts/plumbing
|
|
7
|
+
import GitPlumbing, { ShellRunnerFactory } from '@git-stunts/plumbing';
|
|
8
|
+
import WarpGraph from '../../src/domain/WarpGraph.js';
|
|
9
|
+
import GitGraphAdapter from '../../src/infrastructure/adapters/GitGraphAdapter.js';
|
|
10
|
+
import WebCryptoAdapter from '../../src/infrastructure/adapters/WebCryptoAdapter.js';
|
|
11
|
+
import {
|
|
12
|
+
REF_PREFIX,
|
|
13
|
+
buildCursorActiveRef,
|
|
14
|
+
} from '../../src/domain/utils/RefLayout.js';
|
|
15
|
+
import CasSeekCacheAdapter from '../../src/infrastructure/adapters/CasSeekCacheAdapter.js';
|
|
16
|
+
import { HookInstaller } from '../../src/domain/services/HookInstaller.js';
|
|
17
|
+
import { parseCursorBlob } from '../../src/domain/utils/parseCursorBlob.js';
|
|
18
|
+
import { usageError, notFoundError } from './infrastructure.js';
|
|
19
|
+
|
|
20
|
+
/** @typedef {import('./types.js').Persistence} Persistence */
|
|
21
|
+
/** @typedef {import('./types.js').WarpGraphInstance} WarpGraphInstance */
|
|
22
|
+
/** @typedef {import('./types.js').CursorBlob} CursorBlob */
|
|
23
|
+
/** @typedef {import('./types.js').CliOptions} CliOptions */
|
|
24
|
+
/** @typedef {import('./types.js').SeekSpec} SeekSpec */
|
|
25
|
+
|
|
26
|
+
/** @param {string} repoPath @returns {Promise<{persistence: Persistence}>} */
|
|
27
|
+
export async function createPersistence(repoPath) {
|
|
28
|
+
const runner = ShellRunnerFactory.create();
|
|
29
|
+
const plumbing = new GitPlumbing({ cwd: repoPath, runner });
|
|
30
|
+
const persistence = new GitGraphAdapter({ plumbing });
|
|
31
|
+
const ping = await persistence.ping();
|
|
32
|
+
if (!ping.ok) {
|
|
33
|
+
throw usageError(`Repository not accessible: ${repoPath}`);
|
|
34
|
+
}
|
|
35
|
+
return { persistence };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** @param {Persistence} persistence @returns {Promise<string[]>} */
|
|
39
|
+
export async function listGraphNames(persistence) {
|
|
40
|
+
if (typeof persistence.listRefs !== 'function') {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
const refs = await persistence.listRefs(REF_PREFIX);
|
|
44
|
+
const prefix = `${REF_PREFIX}/`;
|
|
45
|
+
const names = new Set();
|
|
46
|
+
|
|
47
|
+
for (const ref of refs) {
|
|
48
|
+
if (!ref.startsWith(prefix)) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const rest = ref.slice(prefix.length);
|
|
52
|
+
const [graphName] = rest.split('/');
|
|
53
|
+
if (graphName) {
|
|
54
|
+
names.add(graphName);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return [...names].sort();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @param {Persistence} persistence
|
|
63
|
+
* @param {string|null} explicitGraph
|
|
64
|
+
* @returns {Promise<string>}
|
|
65
|
+
*/
|
|
66
|
+
export async function resolveGraphName(persistence, explicitGraph) {
|
|
67
|
+
if (explicitGraph) {
|
|
68
|
+
return explicitGraph;
|
|
69
|
+
}
|
|
70
|
+
const graphNames = await listGraphNames(persistence);
|
|
71
|
+
if (graphNames.length === 1) {
|
|
72
|
+
return graphNames[0];
|
|
73
|
+
}
|
|
74
|
+
if (graphNames.length === 0) {
|
|
75
|
+
throw notFoundError('No graphs found in repo; specify --graph');
|
|
76
|
+
}
|
|
77
|
+
throw usageError('Multiple graphs found; specify --graph');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Opens a WarpGraph for the given CLI options.
|
|
82
|
+
* @param {CliOptions} options - Parsed CLI options
|
|
83
|
+
* @returns {Promise<{graph: WarpGraphInstance, graphName: string, persistence: Persistence}>}
|
|
84
|
+
* @throws {import('./infrastructure.js').CliError} If the specified graph is not found
|
|
85
|
+
*/
|
|
86
|
+
export async function openGraph(options) {
|
|
87
|
+
const { persistence } = await createPersistence(options.repo);
|
|
88
|
+
const graphName = await resolveGraphName(persistence, options.graph);
|
|
89
|
+
if (options.graph) {
|
|
90
|
+
const graphNames = await listGraphNames(persistence);
|
|
91
|
+
if (!graphNames.includes(options.graph)) {
|
|
92
|
+
throw notFoundError(`Graph not found: ${options.graph}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const graph = /** @type {WarpGraphInstance} */ (/** @type {unknown} */ (await WarpGraph.open({
|
|
96
|
+
persistence,
|
|
97
|
+
graphName,
|
|
98
|
+
writerId: options.writer,
|
|
99
|
+
crypto: new WebCryptoAdapter(),
|
|
100
|
+
})));
|
|
101
|
+
return { graph, graphName, persistence };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Reads the active cursor and sets `_seekCeiling` on the graph instance
|
|
106
|
+
* so that subsequent materialize calls respect the time-travel boundary.
|
|
107
|
+
*
|
|
108
|
+
* @param {WarpGraphInstance} graph - WarpGraph instance
|
|
109
|
+
* @param {Persistence} persistence - GraphPersistencePort adapter
|
|
110
|
+
* @param {string} graphName - Name of the WARP graph
|
|
111
|
+
* @returns {Promise<{active: boolean, tick: number|null, maxTick: number|null}>}
|
|
112
|
+
*/
|
|
113
|
+
export async function applyCursorCeiling(graph, persistence, graphName) {
|
|
114
|
+
const cursor = await readActiveCursor(persistence, graphName);
|
|
115
|
+
if (cursor) {
|
|
116
|
+
graph._seekCeiling = cursor.tick;
|
|
117
|
+
return { active: true, tick: cursor.tick, maxTick: null };
|
|
118
|
+
}
|
|
119
|
+
return { active: false, tick: null, maxTick: null };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Prints a seek cursor warning banner to stderr when a cursor is active.
|
|
124
|
+
*
|
|
125
|
+
* @param {{active: boolean, tick: number|null, maxTick: number|null}} cursorInfo
|
|
126
|
+
* @param {number|null} maxTick
|
|
127
|
+
* @returns {void}
|
|
128
|
+
*/
|
|
129
|
+
export function emitCursorWarning(cursorInfo, maxTick) {
|
|
130
|
+
if (cursorInfo.active) {
|
|
131
|
+
const maxLabel = maxTick !== null && maxTick !== undefined ? ` of ${maxTick}` : '';
|
|
132
|
+
process.stderr.write(`\u26A0 seek active (tick ${cursorInfo.tick}${maxLabel}) \u2014 run "git warp seek --latest" to return to present\n`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Reads the active seek cursor for a graph from Git ref storage.
|
|
138
|
+
*
|
|
139
|
+
* @param {Persistence} persistence - GraphPersistencePort adapter
|
|
140
|
+
* @param {string} graphName - Name of the WARP graph
|
|
141
|
+
* @returns {Promise<CursorBlob|null>}
|
|
142
|
+
*/
|
|
143
|
+
export async function readActiveCursor(persistence, graphName) {
|
|
144
|
+
const ref = buildCursorActiveRef(graphName);
|
|
145
|
+
const oid = await persistence.readRef(ref);
|
|
146
|
+
if (!oid) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const buf = await persistence.readBlob(oid);
|
|
150
|
+
return parseCursorBlob(buf, 'active cursor');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Writes (creates or overwrites) the active seek cursor for a graph.
|
|
155
|
+
*
|
|
156
|
+
* @param {Persistence} persistence - GraphPersistencePort adapter
|
|
157
|
+
* @param {string} graphName - Name of the WARP graph
|
|
158
|
+
* @param {CursorBlob} cursor - Cursor state to persist
|
|
159
|
+
* @returns {Promise<void>}
|
|
160
|
+
*/
|
|
161
|
+
export async function writeActiveCursor(persistence, graphName, cursor) {
|
|
162
|
+
const ref = buildCursorActiveRef(graphName);
|
|
163
|
+
const json = JSON.stringify(cursor);
|
|
164
|
+
const oid = await persistence.writeBlob(Buffer.from(json, 'utf8'));
|
|
165
|
+
await persistence.updateRef(ref, oid);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @param {Persistence} persistence
|
|
170
|
+
* @param {string|null} checkpointSha
|
|
171
|
+
*/
|
|
172
|
+
export async function readCheckpointDate(persistence, checkpointSha) {
|
|
173
|
+
if (!checkpointSha) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
const info = await persistence.getNodeInfo(checkpointSha);
|
|
177
|
+
return info.date || null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function createHookInstaller() {
|
|
181
|
+
const __filename = new URL(import.meta.url).pathname;
|
|
182
|
+
const __dirname = path.dirname(__filename);
|
|
183
|
+
const templateDir = path.resolve(__dirname, '..', '..', 'scripts', 'hooks');
|
|
184
|
+
const { version } = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', '..', 'package.json'), 'utf8'));
|
|
185
|
+
return new HookInstaller({
|
|
186
|
+
fs: /** @type {import('../../src/domain/services/HookInstaller.js').FsAdapter} */ (/** @type {unknown} */ (fs)),
|
|
187
|
+
execGitConfig: execGitConfigValue,
|
|
188
|
+
version,
|
|
189
|
+
templateDir,
|
|
190
|
+
path,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* @param {string} repoPath
|
|
196
|
+
* @param {string} key
|
|
197
|
+
* @returns {string|null}
|
|
198
|
+
*/
|
|
199
|
+
export function execGitConfigValue(repoPath, key) {
|
|
200
|
+
try {
|
|
201
|
+
if (key === '--git-dir') {
|
|
202
|
+
return execFileSync('git', ['-C', repoPath, 'rev-parse', '--git-dir'], {
|
|
203
|
+
encoding: 'utf8',
|
|
204
|
+
}).trim();
|
|
205
|
+
}
|
|
206
|
+
return execFileSync('git', ['-C', repoPath, 'config', key], {
|
|
207
|
+
encoding: 'utf8',
|
|
208
|
+
}).trim();
|
|
209
|
+
} catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function isInteractive() {
|
|
215
|
+
return Boolean(process.stderr.isTTY);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** @param {string} question @returns {Promise<string>} */
|
|
219
|
+
export function promptUser(question) {
|
|
220
|
+
const rl = readline.createInterface({
|
|
221
|
+
input: process.stdin,
|
|
222
|
+
output: process.stderr,
|
|
223
|
+
});
|
|
224
|
+
return new Promise((resolve) => {
|
|
225
|
+
rl.question(question, (answer) => {
|
|
226
|
+
rl.close();
|
|
227
|
+
resolve(answer.trim());
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @param {{graph: WarpGraphInstance, persistence: Persistence, graphName: string, seekSpec: SeekSpec}} params
|
|
234
|
+
*/
|
|
235
|
+
export function wireSeekCache({ graph, persistence, graphName, seekSpec }) {
|
|
236
|
+
if (seekSpec.noPersistentCache) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
graph.setSeekCache(new CasSeekCacheAdapter({
|
|
240
|
+
persistence,
|
|
241
|
+
plumbing: persistence.plumbing,
|
|
242
|
+
graphName,
|
|
243
|
+
}));
|
|
244
|
+
}
|
package/bin/cli/types.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} Persistence
|
|
3
|
+
* @property {(prefix: string) => Promise<string[]>} listRefs
|
|
4
|
+
* @property {(ref: string) => Promise<string|null>} readRef
|
|
5
|
+
* @property {(ref: string, oid: string) => Promise<void>} updateRef
|
|
6
|
+
* @property {(ref: string) => Promise<void>} deleteRef
|
|
7
|
+
* @property {(oid: string) => Promise<Buffer>} readBlob
|
|
8
|
+
* @property {(buf: Buffer) => Promise<string>} writeBlob
|
|
9
|
+
* @property {(sha: string) => Promise<{date?: string|null}>} getNodeInfo
|
|
10
|
+
* @property {(sha: string) => Promise<boolean>} nodeExists
|
|
11
|
+
* @property {(sha: string, coverageSha: string) => Promise<boolean>} isAncestor
|
|
12
|
+
* @property {() => Promise<{ok: boolean}>} ping
|
|
13
|
+
* @property {unknown} plumbing
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} WarpGraphInstance
|
|
18
|
+
* @property {(opts?: {ceiling?: number}) => Promise<void>} materialize
|
|
19
|
+
* @property {() => Promise<Array<{id: string}>>} getNodes
|
|
20
|
+
* @property {() => Promise<Array<{from: string, to: string, label?: string}>>} getEdges
|
|
21
|
+
* @property {() => Promise<string|null>} createCheckpoint
|
|
22
|
+
* @property {() => QueryBuilderLike} query
|
|
23
|
+
* @property {{ shortestPath: Function }} traverse
|
|
24
|
+
* @property {(writerId: string) => Promise<Array<{patch: {schema?: number, lamport: number, ops?: Array<{type: string, node?: string, from?: string, to?: string}>}, sha: string}>>} getWriterPatches
|
|
25
|
+
* @property {() => Promise<{frontier: Record<string, string>}>} status
|
|
26
|
+
* @property {() => Promise<string[]>} discoverWriters
|
|
27
|
+
* @property {() => Promise<Map<string, string>>} getFrontier
|
|
28
|
+
* @property {() => {totalTombstones: number, tombstoneRatio: number}} getGCMetrics
|
|
29
|
+
* @property {() => Promise<number>} getPropertyCount
|
|
30
|
+
* @property {() => Promise<import('../../src/domain/services/JoinReducer.js').WarpStateV5 | null>} getStateSnapshot
|
|
31
|
+
* @property {() => Promise<{ticks: number[], maxTick: number, perWriter: Map<string, WriterTickInfo>}>} discoverTicks
|
|
32
|
+
* @property {(sha: string) => Promise<{ops?: Array<{type: string, node?: string, from?: string, to?: string}>}>} loadPatchBySha
|
|
33
|
+
* @property {(cache: import('../../src/ports/SeekCachePort.js').default) => void} setSeekCache
|
|
34
|
+
* @property {{clear: () => Promise<void>} | null} seekCache
|
|
35
|
+
* @property {number} [_seekCeiling]
|
|
36
|
+
* @property {boolean} [_provenanceDegraded]
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @typedef {Object} WriterTickInfo
|
|
41
|
+
* @property {number[]} ticks
|
|
42
|
+
* @property {string|null} tipSha
|
|
43
|
+
* @property {Record<number, string>} [tickShas]
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @typedef {Object} CursorBlob
|
|
48
|
+
* @property {number} tick
|
|
49
|
+
* @property {string} [mode]
|
|
50
|
+
* @property {number} [nodes]
|
|
51
|
+
* @property {number} [edges]
|
|
52
|
+
* @property {string} [frontierHash]
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @typedef {Object} CliOptions
|
|
57
|
+
* @property {string} repo
|
|
58
|
+
* @property {boolean} json
|
|
59
|
+
* @property {boolean} ndjson
|
|
60
|
+
* @property {string|null} view
|
|
61
|
+
* @property {string|null} graph
|
|
62
|
+
* @property {string} writer
|
|
63
|
+
* @property {boolean} help
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @typedef {Object} GraphInfoResult
|
|
68
|
+
* @property {string} name
|
|
69
|
+
* @property {{count: number, ids?: string[]}} writers
|
|
70
|
+
* @property {{ref: string, sha: string|null, date?: string|null}} [checkpoint]
|
|
71
|
+
* @property {{ref: string, sha: string|null}} [coverage]
|
|
72
|
+
* @property {Record<string, number>} [writerPatches]
|
|
73
|
+
* @property {{active: boolean, tick?: number, mode?: string}} [cursor]
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @typedef {Object} SeekSpec
|
|
78
|
+
* @property {string} action
|
|
79
|
+
* @property {string|null} tickValue
|
|
80
|
+
* @property {string|null} name
|
|
81
|
+
* @property {boolean} noPersistentCache
|
|
82
|
+
* @property {boolean} diff
|
|
83
|
+
* @property {number} diffLimit
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @typedef {Object} QueryBuilderLike
|
|
88
|
+
* @property {(label?: string) => QueryBuilderLike} outgoing
|
|
89
|
+
* @property {(label?: string) => QueryBuilderLike} incoming
|
|
90
|
+
* @property {(fn: Function) => QueryBuilderLike} where
|
|
91
|
+
* @property {(pattern: string) => QueryBuilderLike} match
|
|
92
|
+
* @property {(fields: string[]) => QueryBuilderLike} select
|
|
93
|
+
* @property {() => Promise<{nodes: Array<{id: string, props?: Record<string, unknown>}>, stateHash?: string}>} run
|
|
94
|
+
*/
|
|
95
|
+
|
|
96
|
+
export {};
|
package/bin/presenters/index.js
CHANGED
|
@@ -22,11 +22,16 @@ import {
|
|
|
22
22
|
renderQuery,
|
|
23
23
|
renderPath,
|
|
24
24
|
renderCheck,
|
|
25
|
+
renderDoctor,
|
|
25
26
|
renderHistory,
|
|
26
27
|
renderError,
|
|
27
28
|
renderMaterialize,
|
|
28
29
|
renderInstallHooks,
|
|
29
30
|
renderSeek,
|
|
31
|
+
renderVerifyAudit,
|
|
32
|
+
renderTrust,
|
|
33
|
+
renderPatchShow,
|
|
34
|
+
renderPatchList,
|
|
30
35
|
} from './text.js';
|
|
31
36
|
|
|
32
37
|
// ── Color control ────────────────────────────────────────────────────────────
|
|
@@ -58,20 +63,46 @@ export function shouldStripColor() {
|
|
|
58
63
|
|
|
59
64
|
// ── Text renderer map ────────────────────────────────────────────────────────
|
|
60
65
|
|
|
61
|
-
/** @
|
|
62
|
-
|
|
66
|
+
/** @param {import('./text.js').PatchShowPayload & Partial<import('./text.js').PatchListPayload>} payload */
|
|
67
|
+
function renderPatch(payload) {
|
|
68
|
+
if (payload.ops) {
|
|
69
|
+
return renderPatchShow(payload);
|
|
70
|
+
}
|
|
71
|
+
return renderPatchList(/** @type {import('./text.js').PatchListPayload} */ (payload));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** @param {{ graph: string, tree?: string, orphanCount?: number, orphans?: string[] }} payload */
|
|
75
|
+
function renderTree(payload) {
|
|
76
|
+
const lines = [`Graph: ${payload.graph}`];
|
|
77
|
+
if (payload.tree) {
|
|
78
|
+
lines.push(payload.tree);
|
|
79
|
+
}
|
|
80
|
+
if (payload.orphanCount && payload.orphanCount > 0 && payload.orphans) {
|
|
81
|
+
lines.push('');
|
|
82
|
+
lines.push(`Orphans (${payload.orphanCount}): ${payload.orphans.join(', ')}`);
|
|
83
|
+
}
|
|
84
|
+
return `${lines.join('\n')}\n`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** @type {Map<string, function(unknown): string>} */
|
|
88
|
+
const TEXT_RENDERERS = new Map(/** @type {[string, function(unknown): string][]} */ ([
|
|
63
89
|
['info', renderInfo],
|
|
64
90
|
['query', renderQuery],
|
|
65
91
|
['path', renderPath],
|
|
66
92
|
['check', renderCheck],
|
|
93
|
+
['doctor', renderDoctor],
|
|
67
94
|
['history', renderHistory],
|
|
68
95
|
['materialize', renderMaterialize],
|
|
69
96
|
['seek', renderSeek],
|
|
97
|
+
['verify-audit', renderVerifyAudit],
|
|
98
|
+
['trust', renderTrust],
|
|
99
|
+
['patch', renderPatch],
|
|
100
|
+
['tree', renderTree],
|
|
70
101
|
['install-hooks', renderInstallHooks],
|
|
71
102
|
]));
|
|
72
103
|
|
|
73
|
-
/** @type {Map<string, function(
|
|
74
|
-
const VIEW_RENDERERS = new Map(/** @type {[string, function(
|
|
104
|
+
/** @type {Map<string, function(unknown): string>} */
|
|
105
|
+
const VIEW_RENDERERS = new Map(/** @type {[string, function(unknown): string][]} */ ([
|
|
75
106
|
['info', renderInfoView],
|
|
76
107
|
['check', renderCheckView],
|
|
77
108
|
['history', renderHistoryView],
|
|
@@ -96,7 +127,7 @@ function writeHtmlExport(filePath, svgContent) {
|
|
|
96
127
|
|
|
97
128
|
/**
|
|
98
129
|
* Handles svg:PATH and html:PATH view modes for commands that carry _renderedSvg.
|
|
99
|
-
* @param {
|
|
130
|
+
* @param {{ _renderedSvg?: string }} payload
|
|
100
131
|
* @param {string} view
|
|
101
132
|
* @returns {boolean} true if handled
|
|
102
133
|
*/
|
|
@@ -140,13 +171,13 @@ function writeText(text, strip) {
|
|
|
140
171
|
/**
|
|
141
172
|
* Writes a command result to stdout/stderr in the requested format.
|
|
142
173
|
*
|
|
143
|
-
* @param {
|
|
174
|
+
* @param {Record<string, unknown>} payload - Command result payload
|
|
144
175
|
* @param {{format: string, command: string, view: string|null|boolean}} options
|
|
145
176
|
*/
|
|
146
177
|
export function present(payload, { format, command, view }) {
|
|
147
178
|
// Error payloads always go to stderr as plain text
|
|
148
179
|
if (payload?.error) {
|
|
149
|
-
process.stderr.write(renderError(payload));
|
|
180
|
+
process.stderr.write(renderError(/** @type {import('./text.js').ErrorPayload} */ (payload)));
|
|
150
181
|
return;
|
|
151
182
|
}
|
|
152
183
|
|
|
@@ -180,7 +211,7 @@ export function present(payload, { format, command, view }) {
|
|
|
180
211
|
|
|
181
212
|
/**
|
|
182
213
|
* Handles --view output dispatch (ASCII view, SVG file, HTML file).
|
|
183
|
-
* @param {
|
|
214
|
+
* @param {Record<string, unknown>} payload
|
|
184
215
|
* @param {string} command
|
|
185
216
|
* @param {string|boolean} view
|
|
186
217
|
*/
|
|
@@ -194,7 +225,8 @@ function presentView(payload, command, view) {
|
|
|
194
225
|
|
|
195
226
|
// query is special: uses pre-rendered _renderedAscii
|
|
196
227
|
if (command === 'query') {
|
|
197
|
-
|
|
228
|
+
const ascii = typeof payload._renderedAscii === 'string' ? payload._renderedAscii : '';
|
|
229
|
+
writeText(`${ascii}\n`, strip);
|
|
198
230
|
return;
|
|
199
231
|
}
|
|
200
232
|
|
package/bin/presenters/json.js
CHANGED
|
@@ -8,18 +8,19 @@
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Recursively sorts object keys for deterministic JSON output.
|
|
11
|
-
* @param {
|
|
12
|
-
* @returns {
|
|
11
|
+
* @param {unknown} input
|
|
12
|
+
* @returns {unknown}
|
|
13
13
|
*/
|
|
14
14
|
function normalize(input) {
|
|
15
15
|
if (Array.isArray(input)) {
|
|
16
16
|
return input.map(normalize);
|
|
17
17
|
}
|
|
18
18
|
if (input && typeof input === 'object') {
|
|
19
|
-
/** @type {Record<string,
|
|
19
|
+
const rec = /** @type {Record<string, unknown>} */ (input);
|
|
20
|
+
/** @type {Record<string, unknown>} */
|
|
20
21
|
const sorted = {};
|
|
21
|
-
for (const key of Object.keys(
|
|
22
|
-
sorted[key] = normalize(
|
|
22
|
+
for (const key of Object.keys(rec).sort()) {
|
|
23
|
+
sorted[key] = normalize(rec[key]);
|
|
23
24
|
}
|
|
24
25
|
return sorted;
|
|
25
26
|
}
|
|
@@ -28,7 +29,7 @@ function normalize(input) {
|
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Pretty-printed JSON with sorted keys (2-space indent).
|
|
31
|
-
* @param {
|
|
32
|
+
* @param {unknown} value
|
|
32
33
|
* @returns {string}
|
|
33
34
|
*/
|
|
34
35
|
export function stableStringify(value) {
|
|
@@ -37,7 +38,7 @@ export function stableStringify(value) {
|
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
40
|
* Single-line JSON with sorted keys (no indent).
|
|
40
|
-
* @param {
|
|
41
|
+
* @param {unknown} value
|
|
41
42
|
* @returns {string}
|
|
42
43
|
*/
|
|
43
44
|
export function compactStringify(value) {
|
|
@@ -48,18 +49,19 @@ export function compactStringify(value) {
|
|
|
48
49
|
* Shallow-clones a payload, removing all top-level underscore-prefixed keys.
|
|
49
50
|
* These are internal rendering artifacts (e.g. _renderedSvg, _renderedAscii)
|
|
50
51
|
* that should not leak into JSON/NDJSON output.
|
|
51
|
-
* @param {
|
|
52
|
-
* @returns {
|
|
52
|
+
* @param {Record<string, unknown> | unknown} payload
|
|
53
|
+
* @returns {Record<string, unknown> | unknown}
|
|
53
54
|
*/
|
|
54
55
|
export function sanitizePayload(payload) {
|
|
55
56
|
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
56
57
|
return payload;
|
|
57
58
|
}
|
|
58
|
-
/** @type {Record<string,
|
|
59
|
+
const rec = /** @type {Record<string, unknown>} */ (payload);
|
|
60
|
+
/** @type {Record<string, unknown>} */
|
|
59
61
|
const clean = {};
|
|
60
|
-
for (const key of Object.keys(
|
|
62
|
+
for (const key of Object.keys(rec)) {
|
|
61
63
|
if (!key.startsWith('_')) {
|
|
62
|
-
clean[key] =
|
|
64
|
+
clean[key] = rec[key];
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
return clean;
|