@ttsc/graph 0.23.0 → 0.24.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 +1 -1
- package/lib/index.js +36 -0
- package/lib/index.js.map +1 -1
- package/lib/model/TtscGraphMemory.d.ts +1 -1
- package/lib/model/TtscGraphMemory.js +16 -13
- package/lib/model/TtscGraphMemory.js.map +1 -1
- package/lib/model/TtscGraphSession.d.ts +2 -0
- package/lib/model/TtscGraphSession.js +414 -220
- package/lib/model/TtscGraphSession.js.map +1 -1
- package/lib/model/TtscGraphShardStore.d.ts +16 -0
- package/lib/model/TtscGraphShardStore.js +272 -0
- package/lib/model/TtscGraphShardStore.js.map +1 -0
- package/lib/model/loadGraph.js +48 -54
- package/lib/model/loadGraph.js.map +1 -1
- package/lib/server/runDetails.js +4 -9
- package/lib/server/runDetails.js.map +1 -1
- package/lib/server/runEntrypoints.js +4 -7
- package/lib/server/runEntrypoints.js.map +1 -1
- package/lib/server/runLookup.js +1 -1
- package/lib/server/runLookup.js.map +1 -1
- package/lib/server/runOverview.js +3 -3
- package/lib/server/runOverview.js.map +1 -1
- package/lib/server/runTour.js +7 -11
- package/lib/server/runTour.js.map +1 -1
- package/lib/server/runTrace.d.ts +4 -5
- package/lib/server/runTrace.js +10 -19
- package/lib/server/runTrace.js.map +1 -1
- package/lib/structures/ITtscGraphDump.d.ts +8 -2
- package/lib/structures/ITtscGraphNode.d.ts +1 -2
- package/lib/structures/ITtscGraphSnapshot.d.ts +37 -1
- package/lib/structures/TtscGraphDumpEdgeKind.d.ts +2 -0
- package/lib/structures/TtscGraphDumpEdgeKind.js +3 -0
- package/lib/structures/TtscGraphDumpEdgeKind.js.map +1 -0
- package/lib/structures/TtscGraphDumpNodeKind.d.ts +2 -0
- package/lib/structures/TtscGraphDumpNodeKind.js +3 -0
- package/lib/structures/TtscGraphDumpNodeKind.js.map +1 -0
- package/lib/structures/TtscGraphEdgeKind.d.ts +8 -6
- package/lib/structures/TtscGraphNodeKind.d.ts +5 -6
- package/lib/structures/TtscGraphNodeModifier.d.ts +1 -1
- package/lib/structures/index.d.ts +2 -0
- package/lib/structures/index.js +2 -0
- package/lib/structures/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +40 -0
- package/src/model/TtscGraphMemory.ts +10 -8
- package/src/model/TtscGraphSession.ts +64 -16
- package/src/model/TtscGraphShardStore.ts +371 -0
- package/src/server/runDetails.ts +5 -10
- package/src/server/runEntrypoints.ts +4 -7
- package/src/server/runLookup.ts +1 -1
- package/src/server/runOverview.ts +3 -3
- package/src/server/runTour.ts +8 -14
- package/src/server/runTrace.ts +10 -19
- package/src/structures/ITtscGraphDump.ts +10 -2
- package/src/structures/ITtscGraphNode.ts +1 -2
- package/src/structures/ITtscGraphSnapshot.ts +42 -1
- package/src/structures/TtscGraphDumpEdgeKind.ts +11 -0
- package/src/structures/TtscGraphDumpNodeKind.ts +10 -0
- package/src/structures/TtscGraphEdgeKind.ts +8 -9
- package/src/structures/TtscGraphNodeKind.ts +5 -11
- package/src/structures/TtscGraphNodeModifier.ts +1 -2
- package/src/structures/index.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttsc/graph",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Checker-resolved architecture graph over MCP for coding agents, backed by ttsc's in-process TypeScript-Go compiler.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"typia": "13.2.0"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
|
-
"ttsc": "^0.
|
|
23
|
+
"ttsc": "^0.24.0"
|
|
24
24
|
},
|
|
25
25
|
"keywords": [
|
|
26
26
|
"ttsc",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"three": "^0.184.0",
|
|
43
43
|
"three-forcegraph": "^1.43.4",
|
|
44
44
|
"typescript": "^7.0.2",
|
|
45
|
-
"ttsc": "0.
|
|
45
|
+
"ttsc": "0.24.0"
|
|
46
46
|
},
|
|
47
47
|
"repository": {
|
|
48
48
|
"type": "git",
|
package/src/index.ts
CHANGED
|
@@ -83,6 +83,37 @@ export function runGraph(
|
|
|
83
83
|
);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/** The help spellings `ttscgraph` itself accepts, mirrored for the fallback. */
|
|
87
|
+
const DUMP_HELP_FLAGS = new Set(["--help", "-help", "-h"]);
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Print a `dump` usage summary when the native binary cannot be resolved.
|
|
91
|
+
*
|
|
92
|
+
* This is a fallback, not a second contract: `cmd/ttscgraph/main.go` owns these
|
|
93
|
+
* flags and answers whenever it is installed. Keep this short and point at that
|
|
94
|
+
* authority, so a drift degrades to a stale summary rather than a wrong
|
|
95
|
+
* answer.
|
|
96
|
+
*/
|
|
97
|
+
function printDumpHelp(): void {
|
|
98
|
+
process.stdout.write(
|
|
99
|
+
[
|
|
100
|
+
"Usage: ttsc-graph dump [options]",
|
|
101
|
+
"",
|
|
102
|
+
"Write the whole compiler graph as JSON to stdout: every node and edge,",
|
|
103
|
+
"none of the MCP response caps.",
|
|
104
|
+
"",
|
|
105
|
+
"Options:",
|
|
106
|
+
" --cwd <dir> Project root (default: current directory).",
|
|
107
|
+
" --tsconfig <path> Project tsconfig path (default: tsconfig.json).",
|
|
108
|
+
" --pretty Indent the JSON output.",
|
|
109
|
+
"",
|
|
110
|
+
"The native `ttscgraph` binary owns these flags and is not installed here,",
|
|
111
|
+
"so this summary may lag it. Install `ttsc` and rerun for the exact list.",
|
|
112
|
+
"",
|
|
113
|
+
].join("\n"),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
86
117
|
/**
|
|
87
118
|
* Pass `dump` through to the native binary, inheriting stdio so the JSON lands
|
|
88
119
|
* on this process's stdout. Returns the child's exit code.
|
|
@@ -93,6 +124,15 @@ function runDump(argv: readonly string[]): number {
|
|
|
93
124
|
const { cwd } = projectOptions(parseLauncherOptions(argv, DUMP_OPTIONS));
|
|
94
125
|
const binary = resolveGraphBinary(process.env, cwd);
|
|
95
126
|
if (binary === null) {
|
|
127
|
+
// `ttscgraph` owns the flag contract, so a resolvable binary always answers
|
|
128
|
+
// `--help` itself and stays the single source of truth. It cannot answer
|
|
129
|
+
// when it is absent, and that is exactly when a caller is most likely to be
|
|
130
|
+
// asking what the command needs — so fall back to a summary that names the
|
|
131
|
+
// authority rather than making usage unreachable behind an install error.
|
|
132
|
+
if (argv.some((argument) => DUMP_HELP_FLAGS.has(argument))) {
|
|
133
|
+
printDumpHelp();
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
96
136
|
process.stderr.write(
|
|
97
137
|
"@ttsc/graph: could not resolve the ttscgraph binary. " +
|
|
98
138
|
"Install `ttsc` so its platform package is present, " +
|
|
@@ -27,7 +27,7 @@ export class TtscGraphMemory {
|
|
|
27
27
|
|
|
28
28
|
/** The absolute project root the dump was built for. */
|
|
29
29
|
readonly project: string;
|
|
30
|
-
/** Every node,
|
|
30
|
+
/** Every post-fold node, including refined properties and file containers. */
|
|
31
31
|
readonly nodes: readonly ITtscGraphNode[];
|
|
32
32
|
/** Every edge, raw plus synthesized containment. */
|
|
33
33
|
readonly edges: readonly ITtscGraphEdge[];
|
|
@@ -190,20 +190,22 @@ function synthesize(dump: ITtscGraphDump): {
|
|
|
190
190
|
// small — the two copies are 17% of the document, 55 MB of VS Code's 323 MB,
|
|
191
191
|
// paid again in the encode, the pipe, the parse and the validation. Nothing
|
|
192
192
|
// downstream of this line sees a span without its file.
|
|
193
|
-
const nodes: ITtscGraphNode[] = dump.nodes
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
193
|
+
const nodes: ITtscGraphNode[] = dump.nodes.flatMap((n): ITtscGraphNode[] => {
|
|
194
|
+
if (n.kind === "module") return [];
|
|
195
|
+
const { evidence, implementation, ...rest } = n;
|
|
196
|
+
return [
|
|
197
|
+
{
|
|
198
198
|
...rest,
|
|
199
|
+
kind: n.kind,
|
|
199
200
|
...(evidence !== undefined
|
|
200
201
|
? { evidence: spanIn(evidence, n.file) }
|
|
201
202
|
: {}),
|
|
202
203
|
...(implementation !== undefined
|
|
203
204
|
? { implementation: spanIn(implementation, n.file) }
|
|
204
205
|
: {}),
|
|
205
|
-
}
|
|
206
|
-
|
|
206
|
+
},
|
|
207
|
+
];
|
|
208
|
+
});
|
|
207
209
|
const edges: ITtscGraphEdge[] = dump.edges.map((edge) => {
|
|
208
210
|
const { evidence, ...rest } = edge;
|
|
209
211
|
const from = moduleIds.get(edge.from);
|
|
@@ -6,6 +6,7 @@ import { ensureExecutable } from "../nativeExecutable";
|
|
|
6
6
|
import { resolveGraphBinary } from "../resolveGraphBinary";
|
|
7
7
|
import { ITtscGraphSnapshot } from "../structures/ITtscGraphSnapshot";
|
|
8
8
|
import { TtscGraphMemory } from "./TtscGraphMemory";
|
|
9
|
+
import { TtscGraphShardStore } from "./TtscGraphShardStore";
|
|
9
10
|
import { DUMP_SCHEMA_VERSION } from "./loadGraph";
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -17,6 +18,7 @@ import { DUMP_SCHEMA_VERSION } from "./loadGraph";
|
|
|
17
18
|
* constant out of this file and fails if the pair drifts.
|
|
18
19
|
*/
|
|
19
20
|
const PROTOCOL_VERSION = 1;
|
|
21
|
+
const GRAPH_SNAPSHOT_PROTOCOL_VERSION = 1;
|
|
20
22
|
const DEFAULT_REQUEST_TIMEOUT_MS = 300_000;
|
|
21
23
|
const MAX_TIMER_MS = 2_147_483_647;
|
|
22
24
|
const TERMINATION_GRACE_MS = 1_000;
|
|
@@ -75,6 +77,7 @@ export class TtscGraphSession {
|
|
|
75
77
|
private readonly pending = new Map<number, Pending>();
|
|
76
78
|
private queue: Promise<void> = Promise.resolve();
|
|
77
79
|
private current: TtscGraphMemory | undefined;
|
|
80
|
+
private shardStore = new TtscGraphShardStore();
|
|
78
81
|
private closed = false;
|
|
79
82
|
|
|
80
83
|
public constructor(options: TtscGraphSessionOptions) {
|
|
@@ -167,16 +170,30 @@ export class TtscGraphSession {
|
|
|
167
170
|
// The protocol version and the envelope shape were both settled in onLine,
|
|
168
171
|
// before this frame was ever routed here.
|
|
169
172
|
const response = await this.request(signal);
|
|
173
|
+
this.assertResponseSemantics(response);
|
|
170
174
|
if (response.error !== undefined) {
|
|
171
175
|
throw new Error(`@ttsc/graph: ${response.error}`);
|
|
172
176
|
}
|
|
173
177
|
if (response.changed) {
|
|
174
|
-
if (response.
|
|
178
|
+
if (response.snapshot !== undefined) {
|
|
179
|
+
try {
|
|
180
|
+
this.current = TtscGraphMemory.from(
|
|
181
|
+
this.shardStore.apply(response.snapshot),
|
|
182
|
+
);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
const failure = asError(error);
|
|
185
|
+
if (this.child !== undefined) this.failChild(this.child, failure);
|
|
186
|
+
throw failure;
|
|
187
|
+
}
|
|
188
|
+
} else if (response.dump !== undefined) {
|
|
189
|
+
// A serve-protocol-v1 binary predating graph shard negotiation ignores
|
|
190
|
+
// the optional request field and retains the complete-dump response.
|
|
191
|
+
this.current = TtscGraphMemory.from(response.dump);
|
|
192
|
+
} else {
|
|
175
193
|
throw new Error(
|
|
176
|
-
`@ttsc/graph: native ${response.mode} response omitted
|
|
194
|
+
`@ttsc/graph: native ${response.mode} response omitted both graph snapshot protocol v${String(GRAPH_SNAPSHOT_PROTOCOL_VERSION)} data and a compatible dump`,
|
|
177
195
|
);
|
|
178
196
|
}
|
|
179
|
-
this.current = TtscGraphMemory.from(response.dump);
|
|
180
197
|
}
|
|
181
198
|
if (this.current === undefined) {
|
|
182
199
|
throw new Error(
|
|
@@ -186,6 +203,30 @@ export class TtscGraphSession {
|
|
|
186
203
|
return this.current;
|
|
187
204
|
}
|
|
188
205
|
|
|
206
|
+
private assertResponseSemantics(response: ITtscGraphSnapshot): void {
|
|
207
|
+
const bodies =
|
|
208
|
+
Number(response.dump !== undefined) +
|
|
209
|
+
Number(response.snapshot !== undefined);
|
|
210
|
+
let problem: string | undefined;
|
|
211
|
+
if (response.error !== undefined) {
|
|
212
|
+
if (response.mode !== "error" || response.changed || bodies !== 0) {
|
|
213
|
+
problem = "an error response carried snapshot state";
|
|
214
|
+
}
|
|
215
|
+
} else if (response.mode === "error") {
|
|
216
|
+
problem = "an error-mode response omitted its error";
|
|
217
|
+
} else if (response.changed) {
|
|
218
|
+
if (response.mode === "unchanged" || bodies !== 1) {
|
|
219
|
+
problem = "a changed response did not carry exactly one snapshot body";
|
|
220
|
+
}
|
|
221
|
+
} else if (response.mode !== "unchanged" || bodies !== 0) {
|
|
222
|
+
problem = "an unchanged response carried changed mode or snapshot state";
|
|
223
|
+
}
|
|
224
|
+
if (problem === undefined) return;
|
|
225
|
+
const error = new Error(`@ttsc/graph: native session returned ${problem}`);
|
|
226
|
+
if (this.child !== undefined) this.failChild(this.child, error);
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
|
|
189
230
|
private request(signal?: AbortSignal): Promise<ITtscGraphSnapshot> {
|
|
190
231
|
if (signal?.aborted) throw cancelledError(signal);
|
|
191
232
|
const child = this.ensureChild();
|
|
@@ -216,16 +257,19 @@ export class TtscGraphSession {
|
|
|
216
257
|
pending.abort!();
|
|
217
258
|
return;
|
|
218
259
|
}
|
|
219
|
-
child.process.stdin.write(
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
260
|
+
child.process.stdin.write(
|
|
261
|
+
`${JSON.stringify({ id, graphSnapshotVersion: GRAPH_SNAPSHOT_PROTOCOL_VERSION })}\n`,
|
|
262
|
+
(error) => {
|
|
263
|
+
if (error === null || error === undefined) return;
|
|
264
|
+
if (this.pending.get(id) !== pending) return;
|
|
265
|
+
this.failChild(
|
|
266
|
+
child,
|
|
267
|
+
new Error(
|
|
268
|
+
`@ttsc/graph: could not request native snapshot: ${error.message}`,
|
|
269
|
+
),
|
|
270
|
+
);
|
|
271
|
+
},
|
|
272
|
+
);
|
|
229
273
|
});
|
|
230
274
|
}
|
|
231
275
|
|
|
@@ -344,8 +388,10 @@ export class TtscGraphSession {
|
|
|
344
388
|
// looking like a type with no members. Hold the body to its own number too,
|
|
345
389
|
// once the frame is understood.
|
|
346
390
|
if (
|
|
347
|
-
response.dump !== undefined &&
|
|
348
|
-
|
|
391
|
+
(response.dump !== undefined &&
|
|
392
|
+
response.dump.provenance.schemaVersion !== DUMP_SCHEMA_VERSION) ||
|
|
393
|
+
(response.snapshot !== undefined &&
|
|
394
|
+
response.snapshot.schemaVersion !== DUMP_SCHEMA_VERSION)
|
|
349
395
|
) {
|
|
350
396
|
// Session-wide, for the same reason the protocol mismatch above is: it is
|
|
351
397
|
// the wrong binary, not one bad frame.
|
|
@@ -353,7 +399,8 @@ export class TtscGraphSession {
|
|
|
353
399
|
child,
|
|
354
400
|
new Error(
|
|
355
401
|
`@ttsc/graph: ttscgraph sends dump schema v${String(
|
|
356
|
-
response.dump
|
|
402
|
+
response.dump?.provenance.schemaVersion ??
|
|
403
|
+
response.snapshot?.schemaVersion,
|
|
357
404
|
)}, this client reads v${String(DUMP_SCHEMA_VERSION)}. ` +
|
|
358
405
|
"Install a matching `ttsc` (the binary resolves from the target " +
|
|
359
406
|
"project, or from TTSC_GRAPH_BINARY).",
|
|
@@ -371,6 +418,7 @@ export class TtscGraphSession {
|
|
|
371
418
|
if (this.child !== child) return;
|
|
372
419
|
this.child = undefined;
|
|
373
420
|
this.current = undefined;
|
|
421
|
+
this.shardStore = new TtscGraphShardStore();
|
|
374
422
|
child.lines.close();
|
|
375
423
|
this.failPending(error, child);
|
|
376
424
|
if (terminate) terminateChild(child.process);
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
import { ITtscGraphDump } from "../structures/ITtscGraphDump";
|
|
5
|
+
import { ITtscGraphSnapshot } from "../structures/ITtscGraphSnapshot";
|
|
6
|
+
import { DUMP_SCHEMA_VERSION } from "./loadGraph";
|
|
7
|
+
|
|
8
|
+
/** Atomic validator and assembler for native `ttscgraph` shard transactions. */
|
|
9
|
+
export class TtscGraphShardStore {
|
|
10
|
+
static readonly PROTOCOL_VERSION = 1;
|
|
11
|
+
|
|
12
|
+
private sequence: number | undefined;
|
|
13
|
+
private generation: string | undefined;
|
|
14
|
+
private project: string | undefined;
|
|
15
|
+
private tsconfig: string | undefined;
|
|
16
|
+
private shards = new Map<
|
|
17
|
+
string,
|
|
18
|
+
{ digest: string; shard: ITtscGraphSnapshot.IShard }
|
|
19
|
+
>();
|
|
20
|
+
|
|
21
|
+
/** Validate and atomically commit one complete or base-generation delta. */
|
|
22
|
+
apply(transaction: ITtscGraphSnapshot.ITransaction): ITtscGraphDump {
|
|
23
|
+
this.assertCoordinates(transaction);
|
|
24
|
+
const next = new Map(this.shards);
|
|
25
|
+
const touched = new Set<string>();
|
|
26
|
+
for (const key of transaction.deletes) {
|
|
27
|
+
assertShardKey(key);
|
|
28
|
+
if (touched.has(key)) {
|
|
29
|
+
throw new Error(`@ttsc/graph: native transaction repeats shard ${key}`);
|
|
30
|
+
}
|
|
31
|
+
touched.add(key);
|
|
32
|
+
if (!next.delete(key)) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`@ttsc/graph: native transaction deletes unknown shard ${key}`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for (const upsert of transaction.upserts) {
|
|
39
|
+
assertShardKey(upsert.shard.key);
|
|
40
|
+
if (touched.has(upsert.shard.key)) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`@ttsc/graph: native transaction touches shard ${upsert.shard.key} more than once`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
touched.add(upsert.shard.key);
|
|
46
|
+
const digest = TtscGraphShardStore.shardDigest(upsert.shard);
|
|
47
|
+
if (digest !== upsert.digest) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`@ttsc/graph: native shard ${upsert.shard.key} digest ${upsert.digest} does not match ${digest}`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
next.set(upsert.shard.key, { digest, shard: upsert.shard });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const manifest = [...transaction.manifest];
|
|
56
|
+
for (let index = 0; index < manifest.length; index++) {
|
|
57
|
+
const reference = manifest[index]!;
|
|
58
|
+
if (
|
|
59
|
+
index !== 0 &&
|
|
60
|
+
compareText(manifest[index - 1]!.key, reference.key) >= 0
|
|
61
|
+
) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
"@ttsc/graph: native shard manifest must be strictly key-sorted",
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (manifest.length !== next.size) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
"@ttsc/graph: native shard manifest does not describe the reconstructed generation",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
for (const reference of manifest) {
|
|
73
|
+
assertShardKey(reference.key);
|
|
74
|
+
const committed = next.get(reference.key);
|
|
75
|
+
if (committed === undefined || committed.digest !== reference.digest) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`@ttsc/graph: native shard manifest disagrees at ${reference.key}`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const generation = digest({
|
|
82
|
+
tsconfig: transaction.tsconfig,
|
|
83
|
+
producer: transaction.producer,
|
|
84
|
+
capabilities: transaction.capabilities,
|
|
85
|
+
universe: transaction.universe,
|
|
86
|
+
manifest,
|
|
87
|
+
});
|
|
88
|
+
if (generation !== transaction.generation) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`@ttsc/graph: native generation ${transaction.generation} does not match ${generation}`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const dump = assemble(transaction, next);
|
|
95
|
+
this.sequence = transaction.sequence;
|
|
96
|
+
this.generation = transaction.generation;
|
|
97
|
+
this.project = transaction.project;
|
|
98
|
+
this.tsconfig = transaction.tsconfig;
|
|
99
|
+
this.shards = next;
|
|
100
|
+
return dump;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** SHA-256 over the producer's deterministic Go JSON encoding. */
|
|
104
|
+
static shardDigest(shard: ITtscGraphSnapshot.IShard): string {
|
|
105
|
+
return digest(shard);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private assertCoordinates(
|
|
109
|
+
transaction: ITtscGraphSnapshot.ITransaction,
|
|
110
|
+
): void {
|
|
111
|
+
if (transaction.protocolVersion !== TtscGraphShardStore.PROTOCOL_VERSION) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`@ttsc/graph: ttscgraph sends graph snapshot protocol v${String(transaction.protocolVersion)}, this client reads v${String(TtscGraphShardStore.PROTOCOL_VERSION)}`,
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (transaction.schemaVersion !== DUMP_SCHEMA_VERSION) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`@ttsc/graph: ttscgraph sends dump schema v${String(transaction.schemaVersion)}, this client reads v${String(DUMP_SCHEMA_VERSION)}`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
if (
|
|
122
|
+
!Number.isSafeInteger(transaction.sequence) ||
|
|
123
|
+
transaction.sequence < 1
|
|
124
|
+
) {
|
|
125
|
+
throw new Error("@ttsc/graph: native transaction sequence is invalid");
|
|
126
|
+
}
|
|
127
|
+
if (transaction.generation === "") {
|
|
128
|
+
throw new Error("@ttsc/graph: native transaction generation is empty");
|
|
129
|
+
}
|
|
130
|
+
if (this.sequence === undefined || this.generation === undefined) {
|
|
131
|
+
if (
|
|
132
|
+
transaction.sequence !== 1 ||
|
|
133
|
+
transaction.baseSequence !== undefined ||
|
|
134
|
+
transaction.baseGeneration !== undefined ||
|
|
135
|
+
transaction.deletes.length !== 0
|
|
136
|
+
) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
"@ttsc/graph: initial native transaction is not a complete generation",
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (
|
|
144
|
+
transaction.sequence !== this.sequence + 1 ||
|
|
145
|
+
transaction.baseSequence !== this.sequence ||
|
|
146
|
+
transaction.baseGeneration !== this.generation
|
|
147
|
+
) {
|
|
148
|
+
throw new Error(
|
|
149
|
+
`@ttsc/graph: native transaction has stale base ${String(transaction.baseSequence)}/${String(transaction.baseGeneration)}`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
if (
|
|
153
|
+
transaction.project !== this.project ||
|
|
154
|
+
transaction.tsconfig !== this.tsconfig
|
|
155
|
+
) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
"@ttsc/graph: native transaction changed its resident project coordinates",
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function assemble(
|
|
164
|
+
transaction: ITtscGraphSnapshot.ITransaction,
|
|
165
|
+
committed: ReadonlyMap<
|
|
166
|
+
string,
|
|
167
|
+
{ digest: string; shard: ITtscGraphSnapshot.IShard }
|
|
168
|
+
>,
|
|
169
|
+
): ITtscGraphDump {
|
|
170
|
+
const nodes: ITtscGraphDump.INode[] = [];
|
|
171
|
+
const edges: ITtscGraphDump.IEdge[] = [];
|
|
172
|
+
const diagnostics: ITtscGraphDump.IDiagnostic[] = [];
|
|
173
|
+
const sources: ITtscGraphDump.ISourceDigest[] = [];
|
|
174
|
+
const nodeOwners = new Map<string, string>();
|
|
175
|
+
const sourceFiles = new Set<string>();
|
|
176
|
+
const configInputs = new Map<string, string>();
|
|
177
|
+
for (const [key, value] of committed) {
|
|
178
|
+
const shard = value.shard;
|
|
179
|
+
if (shard.key !== key) {
|
|
180
|
+
throw new Error(`@ttsc/graph: native shard key disagrees at ${key}`);
|
|
181
|
+
}
|
|
182
|
+
if (shard.source !== undefined && shard.config !== undefined) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
`@ttsc/graph: native shard ${key} claims both source and config input`,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
if (
|
|
188
|
+
shard.config !== undefined &&
|
|
189
|
+
(shard.nodes.length !== 0 || shard.edges.length !== 0)
|
|
190
|
+
) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`@ttsc/graph: native config shard ${key} unexpectedly owns facts`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
if (shard.config !== undefined) {
|
|
196
|
+
if (configInputs.has(shard.config.file)) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`@ttsc/graph: native config ${shard.config.file} has more than one shard`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
configInputs.set(shard.config.file, shard.config.digest);
|
|
202
|
+
}
|
|
203
|
+
if (shard.source !== undefined) {
|
|
204
|
+
if (sourceFiles.has(shard.source.file)) {
|
|
205
|
+
throw new Error(
|
|
206
|
+
`@ttsc/graph: native source ${shard.source.file} has more than one shard`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
sourceFiles.add(shard.source.file);
|
|
210
|
+
sources.push({ ...shard.source });
|
|
211
|
+
}
|
|
212
|
+
assertShardContents(key, shard);
|
|
213
|
+
for (const node of shard.nodes) {
|
|
214
|
+
const owner = nodeOwners.get(node.id);
|
|
215
|
+
if (owner !== undefined) {
|
|
216
|
+
throw new Error(
|
|
217
|
+
`@ttsc/graph: native node ${node.id} is owned by both ${owner} and ${key}`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
nodeOwners.set(node.id, key);
|
|
221
|
+
nodes.push(node);
|
|
222
|
+
}
|
|
223
|
+
edges.push(...shard.edges);
|
|
224
|
+
diagnostics.push(...shard.diagnostics);
|
|
225
|
+
}
|
|
226
|
+
for (const [key, value] of committed) {
|
|
227
|
+
for (const edge of value.shard.edges) {
|
|
228
|
+
if (nodeOwners.get(edge.from) !== key) {
|
|
229
|
+
throw new Error(
|
|
230
|
+
`@ttsc/graph: native shard ${key} does not own edge source ${edge.from}`,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
if (!nodeOwners.has(edge.to)) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
`@ttsc/graph: native edge target is absent from the generation: ${edge.to}`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const unmatchedConfigs = new Map(configInputs);
|
|
241
|
+
for (const config of transaction.universe.configs) {
|
|
242
|
+
if (
|
|
243
|
+
unmatchedConfigs.get(config.file) !== config.digest ||
|
|
244
|
+
!unmatchedConfigs.delete(config.file)
|
|
245
|
+
) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
`@ttsc/graph: native config shard disagrees with universe input ${config.file}`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (unmatchedConfigs.size !== 0) {
|
|
252
|
+
throw new Error(
|
|
253
|
+
"@ttsc/graph: native config shards do not cover the build universe",
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
nodes.sort((left, right) => compareText(left.id, right.id));
|
|
257
|
+
edges.sort(
|
|
258
|
+
(left, right) =>
|
|
259
|
+
compareText(left.from, right.from) ||
|
|
260
|
+
compareText(left.to, right.to) ||
|
|
261
|
+
compareText(left.kind, right.kind),
|
|
262
|
+
);
|
|
263
|
+
diagnostics.sort(
|
|
264
|
+
(left, right) =>
|
|
265
|
+
compareText(left.file, right.file) ||
|
|
266
|
+
left.line - right.line ||
|
|
267
|
+
left.column - right.column ||
|
|
268
|
+
left.code - right.code,
|
|
269
|
+
);
|
|
270
|
+
sources.sort((left, right) => compareText(left.file, right.file));
|
|
271
|
+
return {
|
|
272
|
+
project: transaction.project,
|
|
273
|
+
tsconfig: transaction.tsconfig,
|
|
274
|
+
provenance: {
|
|
275
|
+
schemaVersion: transaction.schemaVersion,
|
|
276
|
+
capabilities: [...transaction.capabilities],
|
|
277
|
+
producer: { ...transaction.producer },
|
|
278
|
+
universe: {
|
|
279
|
+
configs: transaction.universe.configs.map((config) => ({ ...config })),
|
|
280
|
+
roots: transaction.universe.roots.map((root) => ({ ...root })),
|
|
281
|
+
},
|
|
282
|
+
sources,
|
|
283
|
+
},
|
|
284
|
+
diagnostics,
|
|
285
|
+
nodes,
|
|
286
|
+
edges,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function assertShardContents(
|
|
291
|
+
key: string,
|
|
292
|
+
shard: ITtscGraphSnapshot.IShard,
|
|
293
|
+
): void {
|
|
294
|
+
if (shard.source !== undefined) {
|
|
295
|
+
for (const node of shard.nodes) {
|
|
296
|
+
if (node.external || node.file !== shard.source.file) {
|
|
297
|
+
throw new Error(
|
|
298
|
+
`@ttsc/graph: native source shard ${key} owns node ${node.id} from ${node.file}`,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
for (const diagnostic of shard.diagnostics) {
|
|
303
|
+
if (diagnostic.file !== shard.source.file) {
|
|
304
|
+
throw new Error(
|
|
305
|
+
`@ttsc/graph: native source shard ${key} owns diagnostic from ${diagnostic.file}`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (shard.edges.length !== 0) {
|
|
312
|
+
throw new Error(
|
|
313
|
+
`@ttsc/graph: native non-source shard ${key} unexpectedly owns edges`,
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
if (shard.config !== undefined) {
|
|
317
|
+
for (const diagnostic of shard.diagnostics) {
|
|
318
|
+
if (diagnostic.file !== shard.config.file) {
|
|
319
|
+
throw new Error(
|
|
320
|
+
`@ttsc/graph: native config shard ${key} owns diagnostic from ${diagnostic.file}`,
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
for (const node of shard.nodes) {
|
|
327
|
+
if (!node.external) {
|
|
328
|
+
throw new Error(
|
|
329
|
+
`@ttsc/graph: native metadata shard ${key} owns authored node ${node.id}`,
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
for (const diagnostic of shard.diagnostics) {
|
|
334
|
+
if (diagnostic.file !== "") {
|
|
335
|
+
throw new Error(
|
|
336
|
+
`@ttsc/graph: native metadata shard ${key} owns diagnostic from ${diagnostic.file}`,
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function assertShardKey(key: string): void {
|
|
343
|
+
if (key === "" || key.includes("\0")) {
|
|
344
|
+
throw new Error(`@ttsc/graph: native shard key is invalid: ${key}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function compareText(left: string, right: string): number {
|
|
349
|
+
return Buffer.compare(Buffer.from(left, "utf8"), Buffer.from(right, "utf8"));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function goJSON(value: unknown): string {
|
|
353
|
+
return JSON.stringify(value).replace(/[<>&\u2028\u2029]/gu, (character) => {
|
|
354
|
+
switch (character) {
|
|
355
|
+
case "<":
|
|
356
|
+
return "\\u003c";
|
|
357
|
+
case ">":
|
|
358
|
+
return "\\u003e";
|
|
359
|
+
case "&":
|
|
360
|
+
return "\\u0026";
|
|
361
|
+
case "\u2028":
|
|
362
|
+
return "\\u2028";
|
|
363
|
+
default:
|
|
364
|
+
return "\\u2029";
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function digest(value: unknown): string {
|
|
370
|
+
return createHash("sha256").update(goJSON(value)).digest("hex");
|
|
371
|
+
}
|
package/src/server/runDetails.ts
CHANGED
|
@@ -24,13 +24,11 @@ const MAX_NEIGHBORS = 3;
|
|
|
24
24
|
const DEFAULT_DEPENDENCIES = 2;
|
|
25
25
|
const MAX_DEPENDENCIES = 4;
|
|
26
26
|
// Structural relationships are navigation, not the dependency picture details is for.
|
|
27
|
-
const STRUCTURAL_KINDS = new Set<string>(["contains", "exports"
|
|
27
|
+
const STRUCTURAL_KINDS = new Set<string>(["contains", "exports"]);
|
|
28
28
|
// Kinds whose value is their member outline, not implementation text.
|
|
29
|
-
const CONTAINER_KINDS = new Set<
|
|
29
|
+
const CONTAINER_KINDS = new Set<ITtscGraphNode["kind"]>([
|
|
30
30
|
"class",
|
|
31
31
|
"interface",
|
|
32
|
-
"namespace",
|
|
33
|
-
"module",
|
|
34
32
|
"enum",
|
|
35
33
|
"file",
|
|
36
34
|
]);
|
|
@@ -464,16 +462,13 @@ function edgeKindRank(kind: string): number {
|
|
|
464
462
|
case "accesses":
|
|
465
463
|
case "renders":
|
|
466
464
|
return 2;
|
|
467
|
-
case "tests":
|
|
468
|
-
return 3;
|
|
469
465
|
case "overrides":
|
|
470
|
-
|
|
471
|
-
return 4;
|
|
466
|
+
return 3;
|
|
472
467
|
case "extends":
|
|
473
468
|
case "implements":
|
|
474
|
-
return
|
|
469
|
+
return 4;
|
|
475
470
|
case "type_ref":
|
|
476
|
-
return
|
|
471
|
+
return 5;
|
|
477
472
|
default:
|
|
478
473
|
return 10;
|
|
479
474
|
}
|