@ttsc/graph 0.19.1 → 0.19.2
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/lib/model/TtscGraphSession.js +449 -162
- package/lib/model/TtscGraphSession.js.map +1 -1
- package/lib/model/loadGraph.js +321 -88
- package/lib/model/loadGraph.js.map +1 -1
- package/lib/structures/ITtscGraphDump.d.ts +169 -6
- package/lib/structures/ITtscGraphSnapshot.d.ts +55 -0
- package/lib/structures/ITtscGraphSnapshot.js +3 -0
- package/lib/structures/ITtscGraphSnapshot.js.map +1 -0
- package/lib/structures/index.d.ts +1 -0
- package/lib/structures/index.js +1 -0
- package/lib/structures/index.js.map +1 -1
- package/package.json +2 -2
- package/src/model/TtscGraphSession.ts +62 -16
- package/src/model/loadGraph.ts +31 -0
- package/src/structures/ITtscGraphDump.ts +194 -6
- package/src/structures/ITtscGraphSnapshot.ts +69 -0
- package/src/structures/index.ts +1 -0
|
@@ -6,25 +6,188 @@ import { ITtscGraphSpan } from "./ITtscGraphSpan";
|
|
|
6
6
|
* wire contract between the Go fact-builder and the TypeScript graph engine.
|
|
7
7
|
*
|
|
8
8
|
* It is the complete graph with none of the per-response caps the MCP tools
|
|
9
|
-
* apply: every node and edge the build resolved
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* apply: every node and edge the build resolved, plus the `provenance` that
|
|
10
|
+
* says which program resolved them. The server parses each changed native
|
|
11
|
+
* snapshot (typia-validated) into an in-memory resident graph and reuses that
|
|
12
|
+
* warm model while project inputs stay unchanged; the bundled 3D viewer reduces
|
|
13
|
+
* the same dump.
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
-
* edges
|
|
15
|
+
* `project` is absolute. Every other path is relative to it — `tsconfig`, and
|
|
16
|
+
* the `file` fields on nodes, edges, diagnostics, and the provenance manifest.
|
|
17
|
+
*
|
|
18
|
+
* Two kinds of path fall outside the project and so cannot be relative to it: a
|
|
19
|
+
* dependency keeps its `node_modules/`-relative tail, which is what makes a
|
|
20
|
+
* dependency leaf readable, and anything else the compiler loaded keeps the
|
|
21
|
+
* identity the compiler gave it — a virtual lib stays `bundled:///…`.
|
|
16
22
|
*/
|
|
17
23
|
export interface ITtscGraphDump {
|
|
18
24
|
/** Absolute path of the project root the graph was built for. */
|
|
19
25
|
project: string;
|
|
20
26
|
/** The tsconfig the program was loaded from, relative to `project`. */
|
|
21
27
|
tsconfig: string;
|
|
28
|
+
/** Evidence about the one program that produced everything below. */
|
|
29
|
+
provenance: ITtscGraphDump.IProvenance;
|
|
30
|
+
/**
|
|
31
|
+
* The compiler's findings for the same generation that produced the facts.
|
|
32
|
+
*
|
|
33
|
+
* Empty means the program reported none. It does not mean they were not
|
|
34
|
+
* collected — `provenance.capabilities` is what says whether they were.
|
|
35
|
+
*/
|
|
36
|
+
diagnostics: ITtscGraphDump.IDiagnostic[];
|
|
22
37
|
/** Every node the build recorded. */
|
|
23
38
|
nodes: ITtscGraphDump.INode[];
|
|
24
39
|
/** Every edge the build resolved. */
|
|
25
40
|
edges: ITtscGraphDump.IEdge[];
|
|
26
41
|
}
|
|
27
42
|
export declare namespace ITtscGraphDump {
|
|
43
|
+
/**
|
|
44
|
+
* What a snapshot knows about its own origin.
|
|
45
|
+
*
|
|
46
|
+
* The graph's claim is that its nodes, edges, spans, and diagnostics all came
|
|
47
|
+
* from one `Program`. Without this the claim is unprovable from the response:
|
|
48
|
+
* a consumer could only re-read the disk afterwards and hope nothing moved,
|
|
49
|
+
* which is not sound — a write that lands and reverts in between is invisible
|
|
50
|
+
* to it, and a re-read proves what the disk says now, never what the checker
|
|
51
|
+
* resolved against.
|
|
52
|
+
*
|
|
53
|
+
* This carries no source text. A digest is the opposite of inlining: it is
|
|
54
|
+
* what lets a consumer prove byte-identity against text it read itself,
|
|
55
|
+
* without the graph ever shipping that text.
|
|
56
|
+
*/
|
|
57
|
+
interface IProvenance {
|
|
58
|
+
/**
|
|
59
|
+
* The dump body's schema version, moved when a field is added, removed, or
|
|
60
|
+
* redefined. Independent of the serve protocol's version: a dump written to
|
|
61
|
+
* a file has a schema but never rode the protocol.
|
|
62
|
+
*/
|
|
63
|
+
schemaVersion: number;
|
|
64
|
+
/**
|
|
65
|
+
* What this snapshot proves. A consumer degrades against this rather than
|
|
66
|
+
* guessing from a field's emptiness, because an empty list and an
|
|
67
|
+
* uncollected one look identical on the wire.
|
|
68
|
+
*
|
|
69
|
+
* The known members are `universe`, `sourceDigests`, `diskDigests`, and
|
|
70
|
+
* `diagnostics`. The type stays `string[]` rather than a union of those on
|
|
71
|
+
* purpose: a union would make `typia.assert` reject a newer producer for
|
|
72
|
+
* naming a capability this client has not heard of, turning "proves more
|
|
73
|
+
* than you know about" into a hard failure. An unknown capability is
|
|
74
|
+
* exactly the case a consumer should ignore.
|
|
75
|
+
*/
|
|
76
|
+
capabilities: string[];
|
|
77
|
+
/** What built the snapshot. */
|
|
78
|
+
producer: IProducer;
|
|
79
|
+
/** The inputs that decide which files are in the program at all. */
|
|
80
|
+
universe: IUniverse;
|
|
81
|
+
/** One entry per file the program loaded, ordered by file. */
|
|
82
|
+
sources: ISourceDigest[];
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Identifies the binary and the checker behind the facts.
|
|
86
|
+
*
|
|
87
|
+
* `tool` and `version` are separate because more than one binary can produce
|
|
88
|
+
* a dump and they do not share a version line — the shipped `ttscgraph` is
|
|
89
|
+
* stamped at release, the internal viewer tool is not versioned at all — so
|
|
90
|
+
* folding the name in would hand a consumer that parses a version a tool
|
|
91
|
+
* name.
|
|
92
|
+
*/
|
|
93
|
+
interface IProducer {
|
|
94
|
+
/** The producing binary's name, such as `ttscgraph`. */
|
|
95
|
+
tool: string;
|
|
96
|
+
/**
|
|
97
|
+
* The producing binary's build version, as its `--version` prints it. A
|
|
98
|
+
* local build reports the dev placeholder; a tool that carries no version
|
|
99
|
+
* reports `""`.
|
|
100
|
+
*/
|
|
101
|
+
version: string;
|
|
102
|
+
/** The TypeScript version typescript-go implements. */
|
|
103
|
+
typescript: string;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The build universe: the inputs that decide which files the program
|
|
107
|
+
* contains, as opposed to what is inside them. A change to any of them can
|
|
108
|
+
* add or drop whole files, so a consumer reusing facts across snapshots must
|
|
109
|
+
* treat a universe change as invalidating everything.
|
|
110
|
+
*/
|
|
111
|
+
interface IUniverse {
|
|
112
|
+
/**
|
|
113
|
+
* The tsconfig chain — the project's config and everything it extends.
|
|
114
|
+
*
|
|
115
|
+
* It stays an input regardless of what any source contains: compiler
|
|
116
|
+
* options change the meaning of code the checker resolves without any
|
|
117
|
+
* source file changing.
|
|
118
|
+
*/
|
|
119
|
+
configs: IFileDigest[];
|
|
120
|
+
/**
|
|
121
|
+
* The resolved root file set, one entry per (config, file) pair. A root a
|
|
122
|
+
* config names but that does not exist is still listed: its absence is part
|
|
123
|
+
* of the fingerprint, and creating it later changes the program.
|
|
124
|
+
*/
|
|
125
|
+
roots: IRootFile[];
|
|
126
|
+
}
|
|
127
|
+
/** A root file attributed to the config that named it. */
|
|
128
|
+
interface IRootFile {
|
|
129
|
+
/** The tsconfig that named this root, project-relative. */
|
|
130
|
+
config: string;
|
|
131
|
+
/** The root file, project-relative. */
|
|
132
|
+
file: string;
|
|
133
|
+
}
|
|
134
|
+
/** A file and the hex-encoded SHA-256 of its on-disk bytes. */
|
|
135
|
+
interface IFileDigest {
|
|
136
|
+
/** Project-relative. */
|
|
137
|
+
file: string;
|
|
138
|
+
/** Hex-encoded SHA-256. */
|
|
139
|
+
digest: string;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The manifest entry for one source file the program loaded.
|
|
143
|
+
*
|
|
144
|
+
* Two digests, because "the bytes the checker read" and "the bytes on disk"
|
|
145
|
+
* are not always the same string and a consumer needs to know which one it
|
|
146
|
+
* compares against. They diverge when a source-preamble plugin injects text
|
|
147
|
+
* ahead of the file before tsgo parses it, which a real plugin project does
|
|
148
|
+
* on every build.
|
|
149
|
+
*/
|
|
150
|
+
interface ISourceDigest {
|
|
151
|
+
/** Project-relative. */
|
|
152
|
+
file: string;
|
|
153
|
+
/**
|
|
154
|
+
* Hex-encoded SHA-256 of the text the checker resolved against — the ground
|
|
155
|
+
* truth for the facts. Every node, edge, and span attributed to this file
|
|
156
|
+
* was computed from these bytes.
|
|
157
|
+
*/
|
|
158
|
+
checkerDigest: string;
|
|
159
|
+
/**
|
|
160
|
+
* Hex-encoded SHA-256 of the file's on-disk bytes at snapshot time, or `""`
|
|
161
|
+
* when it could not be read: it vanished mid-load, or it is a virtual
|
|
162
|
+
* source with no on-disk identity.
|
|
163
|
+
*
|
|
164
|
+
* This is the one a consumer that opens the file itself can reproduce. When
|
|
165
|
+
* it equals `checkerDigest`, a matching read proves byte-identity with the
|
|
166
|
+
* facts. When it does not, the checker saw augmented text and that proof is
|
|
167
|
+
* simply not available for this file — which is a thing to report, not to
|
|
168
|
+
* paper over.
|
|
169
|
+
*
|
|
170
|
+
* Read it only when `provenance.capabilities` lists `diskDigests`. Without
|
|
171
|
+
* that claim every one of these is empty because the producer never hashed
|
|
172
|
+
* the disk, which is a different fact from a file that could not be read.
|
|
173
|
+
*/
|
|
174
|
+
diskDigest: string;
|
|
175
|
+
}
|
|
176
|
+
/** One compiler diagnostic from the generation that produced the facts. */
|
|
177
|
+
interface IDiagnostic {
|
|
178
|
+
/** Project-relative. */
|
|
179
|
+
file: string;
|
|
180
|
+
/** 1-based line. */
|
|
181
|
+
line: number;
|
|
182
|
+
/** 1-based column. */
|
|
183
|
+
column: number;
|
|
184
|
+
/** The TypeScript diagnostic code, such as 2322. */
|
|
185
|
+
code: number;
|
|
186
|
+
/** Whether the finding fails a build. */
|
|
187
|
+
category: "error" | "warning";
|
|
188
|
+
/** The diagnostic text, without the code prefix. */
|
|
189
|
+
message: string;
|
|
190
|
+
}
|
|
28
191
|
/**
|
|
29
192
|
* A node as the builder sends it: the graph node, minus the file paths inside
|
|
30
193
|
* its spans, which the loader puts back from the node's own `file`.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ITtscGraphDump } from "./ITtscGraphDump";
|
|
2
|
+
/**
|
|
3
|
+
* One response frame of the `ttscgraph serve` protocol.
|
|
4
|
+
*
|
|
5
|
+
* This is the envelope around a snapshot, mirrored by hand from `serveResponse`
|
|
6
|
+
* in `packages/ttsc/cmd/ttscgraph/serve.go`. There is no generator between the
|
|
7
|
+
* Go struct and this interface, so the two drift silently unless something
|
|
8
|
+
* checks them; `TtscGraphSession` validates every frame against this shape
|
|
9
|
+
* rather than casting it, so a drift surfaces as a precise error on the first
|
|
10
|
+
* frame instead of an `undefined` several layers downstream.
|
|
11
|
+
*/
|
|
12
|
+
export interface ITtscGraphSnapshot {
|
|
13
|
+
/** Echoes the request's id, so a response finds its caller. */
|
|
14
|
+
id: number;
|
|
15
|
+
/**
|
|
16
|
+
* The protocol version the server speaks.
|
|
17
|
+
*
|
|
18
|
+
* It rides every frame rather than a handshake. The binary and this package
|
|
19
|
+
* version independently — the session runs whichever `ttscgraph` the target
|
|
20
|
+
* project installed, or whatever `TTSC_GRAPH_BINARY` points at — so a
|
|
21
|
+
* mismatched pair is reachable, and before this field nothing detected it:
|
|
22
|
+
* the first symptom was a misparsed dump or a silently absent value.
|
|
23
|
+
*/
|
|
24
|
+
protocolVersion: number;
|
|
25
|
+
/**
|
|
26
|
+
* What the producer did to answer this request.
|
|
27
|
+
*
|
|
28
|
+
* Required, and never absent — including on the error path, where it is
|
|
29
|
+
* `"error"`. A consumer can report `rebuild` versus `incremental` honestly
|
|
30
|
+
* because the compiler said so; no generation counter can distinguish a reuse
|
|
31
|
+
* from a full rebuild after the fact.
|
|
32
|
+
*/
|
|
33
|
+
mode: ITtscGraphSnapshot.Mode;
|
|
34
|
+
/** What this server can prove about the snapshots it publishes. */
|
|
35
|
+
capabilities: string[];
|
|
36
|
+
/** Whether the graph moved since the last snapshot. */
|
|
37
|
+
changed: boolean;
|
|
38
|
+
/** The snapshot, present exactly when `changed` is true. */
|
|
39
|
+
dump?: ITtscGraphDump;
|
|
40
|
+
/** Set when the request produced no snapshot; `mode` is then `"error"`. */
|
|
41
|
+
error?: string;
|
|
42
|
+
}
|
|
43
|
+
export declare namespace ITtscGraphSnapshot {
|
|
44
|
+
/**
|
|
45
|
+
* The computation modes the producer reports, plus the transport's `error`.
|
|
46
|
+
*
|
|
47
|
+
* - `initial`: the session's first snapshot.
|
|
48
|
+
* - `reload`: the build universe moved, so the program was reloaded whole.
|
|
49
|
+
* - `unchanged`: nothing moved; no dump rides it and the last one still holds.
|
|
50
|
+
* - `incremental`: edits applied onto the reused resident program.
|
|
51
|
+
* - `rebuild`: edits applied, but the program could not be reused.
|
|
52
|
+
* - `error`: no snapshot was produced.
|
|
53
|
+
*/
|
|
54
|
+
type Mode = "initial" | "reload" | "unchanged" | "incremental" | "rebuild" | "error";
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ITtscGraphSnapshot.js","sourceRoot":"","sources":["../../src/structures/ITtscGraphSnapshot.ts"],"names":[],"mappings":""}
|
|
@@ -11,6 +11,7 @@ export * from "./ITtscGraphOverview";
|
|
|
11
11
|
export * from "./ITtscGraphLookup";
|
|
12
12
|
export * from "./ITtscGraphNext";
|
|
13
13
|
export * from "./ITtscGraphTrace";
|
|
14
|
+
export * from "./ITtscGraphSnapshot";
|
|
14
15
|
export * from "./ITtscGraphSpan";
|
|
15
16
|
export * from "./ITtscGraphTour";
|
|
16
17
|
export * from "./TtscGraphEdgeKind";
|
package/lib/structures/index.js
CHANGED
|
@@ -32,6 +32,7 @@ __exportStar(require("./ITtscGraphOverview"), exports);
|
|
|
32
32
|
__exportStar(require("./ITtscGraphLookup"), exports);
|
|
33
33
|
__exportStar(require("./ITtscGraphNext"), exports);
|
|
34
34
|
__exportStar(require("./ITtscGraphTrace"), exports);
|
|
35
|
+
__exportStar(require("./ITtscGraphSnapshot"), exports);
|
|
35
36
|
__exportStar(require("./ITtscGraphSpan"), exports);
|
|
36
37
|
__exportStar(require("./ITtscGraphTour"), exports);
|
|
37
38
|
__exportStar(require("./TtscGraphEdgeKind"), exports);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/structures/index.ts"],"names":[],"mappings":";AAAA,uEAAuE;AACvE,0EAA0E;AAC1E,8EAA8E;AAC9E,gFAAgF;AAChF,6BAA6B;;;;;;;;;;;;;;;;AAE7B,0DAAwC;AACxC,wDAAsC;AACtC,mDAAiC;AACjC,mDAAiC;AACjC,uDAAqC;AACrC,qDAAmC;AACnC,sDAAoC;AACpC,0DAAwC;AACxC,mDAAiC;AACjC,uDAAqC;AACrC,qDAAmC;AACnC,mDAAiC;AACjC,oDAAkC;AAClC,mDAAiC;AACjC,mDAAiC;AACjC,sDAAoC;AACpC,sDAAoC;AACpC,0DAAwC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/structures/index.ts"],"names":[],"mappings":";AAAA,uEAAuE;AACvE,0EAA0E;AAC1E,8EAA8E;AAC9E,gFAAgF;AAChF,6BAA6B;;;;;;;;;;;;;;;;AAE7B,0DAAwC;AACxC,wDAAsC;AACtC,mDAAiC;AACjC,mDAAiC;AACjC,uDAAqC;AACrC,qDAAmC;AACnC,sDAAoC;AACpC,0DAAwC;AACxC,mDAAiC;AACjC,uDAAqC;AACrC,qDAAmC;AACnC,mDAAiC;AACjC,oDAAkC;AAClC,uDAAqC;AACrC,mDAAiC;AACjC,mDAAiC;AACjC,sDAAoC;AACpC,sDAAoC;AACpC,0DAAwC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttsc/graph",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.2",
|
|
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",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"three": "^0.184.0",
|
|
40
40
|
"three-forcegraph": "^1.43.4",
|
|
41
41
|
"typescript": "^7.0.2",
|
|
42
|
-
"ttsc": "0.19.
|
|
42
|
+
"ttsc": "0.19.2"
|
|
43
43
|
},
|
|
44
44
|
"repository": {
|
|
45
45
|
"type": "git",
|
|
@@ -4,19 +4,21 @@ import typia from "typia";
|
|
|
4
4
|
|
|
5
5
|
import { ensureExecutable } from "../nativeExecutable";
|
|
6
6
|
import { resolveGraphBinary } from "../resolveGraphBinary";
|
|
7
|
-
import {
|
|
7
|
+
import { ITtscGraphSnapshot } from "../structures/ITtscGraphSnapshot";
|
|
8
8
|
import { TtscGraphMemory } from "./TtscGraphMemory";
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
/**
|
|
11
|
+
* The serve protocol version this client speaks.
|
|
12
|
+
*
|
|
13
|
+
* Keep it equal to `serveProtocolVersion` in
|
|
14
|
+
* `packages/ttsc/cmd/ttscgraph/serve.go`. The two are hand-synchronized, and
|
|
15
|
+
* `serve_protocol_version_matches_the_typescript_client_test.go` reads this
|
|
16
|
+
* constant out of this file and fails if the pair drifts.
|
|
17
|
+
*/
|
|
18
|
+
const PROTOCOL_VERSION = 1;
|
|
17
19
|
|
|
18
20
|
interface Pending {
|
|
19
|
-
resolve: (response:
|
|
21
|
+
resolve: (response: ITtscGraphSnapshot) => void;
|
|
20
22
|
reject: (error: Error) => void;
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -93,6 +95,8 @@ export class TtscGraphSession {
|
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
private async refresh(): Promise<TtscGraphMemory> {
|
|
98
|
+
// The protocol version and the envelope shape were both settled in onLine,
|
|
99
|
+
// before this frame was ever routed here.
|
|
96
100
|
const response = await this.request();
|
|
97
101
|
if (response.error !== undefined) {
|
|
98
102
|
throw new Error(`@ttsc/graph: ${response.error}`);
|
|
@@ -100,11 +104,10 @@ export class TtscGraphSession {
|
|
|
100
104
|
if (response.changed) {
|
|
101
105
|
if (response.dump === undefined) {
|
|
102
106
|
throw new Error(
|
|
103
|
-
`@ttsc/graph: native ${response.mode
|
|
107
|
+
`@ttsc/graph: native ${response.mode} response omitted its dump`,
|
|
104
108
|
);
|
|
105
109
|
}
|
|
106
|
-
|
|
107
|
-
this.current = TtscGraphMemory.from(dump);
|
|
110
|
+
this.current = TtscGraphMemory.from(response.dump);
|
|
108
111
|
}
|
|
109
112
|
if (this.current === undefined) {
|
|
110
113
|
throw new Error(
|
|
@@ -114,10 +117,10 @@ export class TtscGraphSession {
|
|
|
114
117
|
return this.current;
|
|
115
118
|
}
|
|
116
119
|
|
|
117
|
-
private request(): Promise<
|
|
120
|
+
private request(): Promise<ITtscGraphSnapshot> {
|
|
118
121
|
const child = this.ensureChild();
|
|
119
122
|
const id = ++this.nextId;
|
|
120
|
-
return new Promise<
|
|
123
|
+
return new Promise<ITtscGraphSnapshot>((resolve, reject) => {
|
|
121
124
|
this.pending.set(id, { resolve, reject });
|
|
122
125
|
child.stdin.write(`${JSON.stringify({ id })}\n`, (error) => {
|
|
123
126
|
if (error === null || error === undefined) return;
|
|
@@ -169,9 +172,9 @@ export class TtscGraphSession {
|
|
|
169
172
|
}
|
|
170
173
|
|
|
171
174
|
private onLine(line: string): void {
|
|
172
|
-
let
|
|
175
|
+
let parsed: unknown;
|
|
173
176
|
try {
|
|
174
|
-
|
|
177
|
+
parsed = JSON.parse(line);
|
|
175
178
|
} catch (error) {
|
|
176
179
|
this.failPending(
|
|
177
180
|
new Error(
|
|
@@ -180,6 +183,49 @@ export class TtscGraphSession {
|
|
|
180
183
|
);
|
|
181
184
|
return;
|
|
182
185
|
}
|
|
186
|
+
|
|
187
|
+
// Read the version before the shape, because a server speaking another
|
|
188
|
+
// version is entitled to a different shape. Asserting first would report
|
|
189
|
+
// that mismatch as a field complaint — "expected string at $input.mode" —
|
|
190
|
+
// about a contract the other side never agreed to, which is the misparse
|
|
191
|
+
// this field exists to prevent. Ask what protocol it is first, then hold it
|
|
192
|
+
// to that protocol.
|
|
193
|
+
const version: number | undefined = typia.is<{ protocolVersion: number }>(
|
|
194
|
+
parsed,
|
|
195
|
+
)
|
|
196
|
+
? parsed.protocolVersion
|
|
197
|
+
: undefined;
|
|
198
|
+
if (version !== PROTOCOL_VERSION) {
|
|
199
|
+
// Session-wide: a version mismatch is not one bad frame, it is the wrong
|
|
200
|
+
// binary, and every request against it is equally doomed.
|
|
201
|
+
this.failPending(
|
|
202
|
+
new Error(
|
|
203
|
+
`@ttsc/graph: ttscgraph speaks serve protocol ${
|
|
204
|
+
version === undefined ? "an unknown version" : `v${String(version)}`
|
|
205
|
+
}, this client speaks v${String(PROTOCOL_VERSION)}. ` +
|
|
206
|
+
"Install a matching `ttsc` (the binary resolves from the target " +
|
|
207
|
+
"project, or from TTSC_GRAPH_BINARY).",
|
|
208
|
+
),
|
|
209
|
+
);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let response: ITtscGraphSnapshot;
|
|
214
|
+
try {
|
|
215
|
+
// Validate the envelope, not just the dump it carries. The dump was
|
|
216
|
+
// typia-asserted while the envelope around it was a bare cast, so the
|
|
217
|
+
// fields the client actually branches on — the mode, and the id that
|
|
218
|
+
// routes the frame — were the unchecked ones. Anything added to the
|
|
219
|
+
// envelope belongs on this side of that line.
|
|
220
|
+
response = typia.assert<ITtscGraphSnapshot>(parsed);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
this.failPending(
|
|
223
|
+
new Error(
|
|
224
|
+
`@ttsc/graph: native session returned an unreadable response: ${asError(error).message}`,
|
|
225
|
+
),
|
|
226
|
+
);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
183
229
|
const pending = this.pending.get(response.id);
|
|
184
230
|
if (pending === undefined) return;
|
|
185
231
|
this.pending.delete(response.id);
|
package/src/model/loadGraph.ts
CHANGED
|
@@ -11,6 +11,16 @@ import { TtscGraphMemory } from "./TtscGraphMemory";
|
|
|
11
11
|
// buffer is raised to a ceiling no real graph reaches.
|
|
12
12
|
const MAX_DUMP_BYTES = 1024 * 1024 * 1024;
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* The dump schema version this client reads.
|
|
16
|
+
*
|
|
17
|
+
* Keep it equal to `DumpSchemaVersion` in
|
|
18
|
+
* `packages/ttsc/internal/graph/provenance.go`. The two are hand-synchronized,
|
|
19
|
+
* and `dump_schema_version_matches_the_typescript_client_test.go` reads this
|
|
20
|
+
* constant out of this file and fails if the pair drifts.
|
|
21
|
+
*/
|
|
22
|
+
const DUMP_SCHEMA_VERSION = 1;
|
|
23
|
+
|
|
14
24
|
/**
|
|
15
25
|
* Build the resident {@link TtscGraphMemory} for a project by running `ttscgraph
|
|
16
26
|
* dump` once and loading its JSON. This is the one-shot path for direct callers
|
|
@@ -73,6 +83,13 @@ export function loadGraph(
|
|
|
73
83
|
* {@link ITtscGraphDump} shape so a malformed or stale dump fails loudly here
|
|
74
84
|
* rather than producing wrong answers downstream, and the schema version is
|
|
75
85
|
* checked so an incompatible producer is refused.
|
|
86
|
+
*
|
|
87
|
+
* The version is read before the shape: a dump from another schema is entitled
|
|
88
|
+
* to another shape, so asserting first would report the mismatch as a field
|
|
89
|
+
* complaint about a contract that producer never agreed to. A dump also
|
|
90
|
+
* outlives the process that wrote it — this is the one-shot path, and the JSON
|
|
91
|
+
* on disk may be from any build — so the version is the first question to ask
|
|
92
|
+
* of it.
|
|
76
93
|
*/
|
|
77
94
|
function parseDump(json: string): ITtscGraphDump {
|
|
78
95
|
let value: unknown;
|
|
@@ -85,5 +102,19 @@ function parseDump(json: string): ITtscGraphDump {
|
|
|
85
102
|
}`,
|
|
86
103
|
);
|
|
87
104
|
}
|
|
105
|
+
const version: number | undefined = typia.is<{
|
|
106
|
+
provenance: { schemaVersion: number };
|
|
107
|
+
}>(value)
|
|
108
|
+
? value.provenance.schemaVersion
|
|
109
|
+
: undefined;
|
|
110
|
+
if (version !== DUMP_SCHEMA_VERSION) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`@ttsc/graph: ttscgraph dump is schema ${
|
|
113
|
+
version === undefined ? "unknown" : `v${String(version)}`
|
|
114
|
+
}, this client reads v${String(DUMP_SCHEMA_VERSION)}. ` +
|
|
115
|
+
"Install a matching `ttsc` (the binary resolves from the target " +
|
|
116
|
+
"project, or from TTSC_GRAPH_BINARY).",
|
|
117
|
+
);
|
|
118
|
+
}
|
|
88
119
|
return typia.assert<ITtscGraphDump>(value);
|
|
89
120
|
}
|