@ttsc/graph 0.30.4 → 0.31.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.
Files changed (292) hide show
  1. package/README.md +122 -30
  2. package/lib/CapturedProcessOutput.d.ts +47 -0
  3. package/lib/CapturedProcessOutput.js +3 -0
  4. package/lib/CapturedProcessOutput.js.map +1 -0
  5. package/lib/ILauncherOption.d.ts +22 -0
  6. package/lib/ILauncherOption.js +3 -0
  7. package/lib/ILauncherOption.js.map +1 -0
  8. package/lib/IProjectOptions.d.ts +18 -0
  9. package/lib/IProjectOptions.js +3 -0
  10. package/lib/IProjectOptions.js.map +1 -0
  11. package/lib/ParsedLauncherOptions.d.ts +13 -0
  12. package/lib/ParsedLauncherOptions.js +3 -0
  13. package/lib/ParsedLauncherOptions.js.map +1 -0
  14. package/lib/TtscGraphApplication.d.ts +38 -1
  15. package/lib/TtscGraphApplication.js +26 -1
  16. package/lib/TtscGraphApplication.js.map +1 -1
  17. package/lib/TtscGraphLauncherArguments.d.ts +115 -0
  18. package/lib/TtscGraphLauncherArguments.js +181 -0
  19. package/lib/TtscGraphLauncherArguments.js.map +1 -0
  20. package/lib/TtscGraphViewSnapshot.d.ts +35 -0
  21. package/lib/TtscGraphViewSnapshot.js +48 -0
  22. package/lib/TtscGraphViewSnapshot.js.map +1 -0
  23. package/lib/index.d.ts +9 -24
  24. package/lib/index.js +6 -128
  25. package/lib/index.js.map +1 -1
  26. package/lib/internal/GraphProcessTrace.d.ts +1 -0
  27. package/lib/internal/GraphProcessTrace.js +235 -0
  28. package/lib/internal/GraphProcessTrace.js.map +1 -0
  29. package/lib/launcherArgs.d.ts +75 -16
  30. package/lib/launcherArgs.js +77 -8
  31. package/lib/launcherArgs.js.map +1 -1
  32. package/lib/model/IArtifactDirectory.d.ts +26 -0
  33. package/lib/model/IArtifactDirectory.js +3 -0
  34. package/lib/model/IArtifactDirectory.js.map +1 -0
  35. package/lib/model/IArtifactInputs.d.ts +19 -0
  36. package/lib/model/IArtifactInputs.js +3 -0
  37. package/lib/model/IArtifactInputs.js.map +1 -0
  38. package/lib/model/IPublishedArtifacts.d.ts +76 -0
  39. package/lib/model/IPublishedArtifacts.js +3 -0
  40. package/lib/model/IPublishedArtifacts.js.map +1 -0
  41. package/lib/model/IPublishedArtifactsResident.d.ts +19 -0
  42. package/lib/model/IPublishedArtifactsResident.js +3 -0
  43. package/lib/model/IPublishedArtifactsResident.js.map +1 -0
  44. package/lib/model/ITtscLintDaemonTarget.d.ts +20 -0
  45. package/lib/model/ITtscLintDaemonTarget.js +3 -0
  46. package/lib/model/ITtscLintDaemonTarget.js.map +1 -0
  47. package/lib/model/TtscGraphLinePeer.d.ts +146 -0
  48. package/lib/model/TtscGraphLinePeer.js +213 -0
  49. package/lib/model/TtscGraphLinePeer.js.map +1 -0
  50. package/lib/model/TtscGraphMemory.d.ts +156 -16
  51. package/lib/model/TtscGraphMemory.js +228 -222
  52. package/lib/model/TtscGraphMemory.js.map +1 -1
  53. package/lib/model/TtscGraphNativeArguments.d.ts +40 -0
  54. package/lib/model/TtscGraphNativeArguments.js +64 -0
  55. package/lib/model/TtscGraphNativeArguments.js.map +1 -0
  56. package/lib/model/TtscGraphNodeId.d.ts +55 -3
  57. package/lib/model/TtscGraphNodeId.js +40 -2
  58. package/lib/model/TtscGraphNodeId.js.map +1 -1
  59. package/lib/model/TtscGraphProjection.d.ts +156 -0
  60. package/lib/model/TtscGraphProjection.js +390 -0
  61. package/lib/model/TtscGraphProjection.js.map +1 -0
  62. package/lib/model/TtscGraphProtocol.d.ts +28 -0
  63. package/lib/model/TtscGraphProtocol.js +928 -0
  64. package/lib/model/TtscGraphProtocol.js.map +1 -0
  65. package/lib/model/TtscGraphReadonly.d.ts +15 -0
  66. package/lib/model/TtscGraphReadonly.js +3 -0
  67. package/lib/model/TtscGraphReadonly.js.map +1 -0
  68. package/lib/model/TtscGraphRequestOptions.d.ts +16 -0
  69. package/lib/model/TtscGraphRequestOptions.js +3 -0
  70. package/lib/model/TtscGraphRequestOptions.js.map +1 -0
  71. package/lib/model/TtscGraphSession.d.ts +82 -36
  72. package/lib/model/TtscGraphSession.js +164 -1238
  73. package/lib/model/TtscGraphSession.js.map +1 -1
  74. package/lib/model/TtscGraphSessionOptions.d.ts +20 -0
  75. package/lib/model/TtscGraphSessionOptions.js +3 -0
  76. package/lib/model/TtscGraphSessionOptions.js.map +1 -0
  77. package/lib/model/TtscGraphSessionState.d.ts +187 -0
  78. package/lib/model/TtscGraphSessionState.js +417 -0
  79. package/lib/model/TtscGraphSessionState.js.map +1 -0
  80. package/lib/model/TtscGraphShardStore.d.ts +64 -3
  81. package/lib/model/TtscGraphShardStore.js +85 -15
  82. package/lib/model/TtscGraphShardStore.js.map +1 -1
  83. package/lib/model/TtscGraphSourceReader.d.ts +31 -8
  84. package/lib/model/TtscGraphSourceReader.js +37 -13
  85. package/lib/model/TtscGraphSourceReader.js.map +1 -1
  86. package/lib/model/TtscLintDaemon.d.ts +35 -17
  87. package/lib/model/TtscLintDaemon.js +39 -117
  88. package/lib/model/TtscLintDaemon.js.map +1 -1
  89. package/lib/model/TtscLintDaemonState.d.ts +69 -0
  90. package/lib/model/TtscLintDaemonState.js +166 -0
  91. package/lib/model/TtscLintDaemonState.js.map +1 -0
  92. package/lib/model/copyGraphRecords.d.ts +14 -0
  93. package/lib/model/copyGraphRecords.js +19 -0
  94. package/lib/model/copyGraphRecords.js.map +1 -0
  95. package/lib/model/copyGraphSnapshot.d.ts +17 -0
  96. package/lib/model/copyGraphSnapshot.js +33 -0
  97. package/lib/model/copyGraphSnapshot.js.map +1 -0
  98. package/lib/model/loadGraph.d.ts +19 -10
  99. package/lib/model/loadGraph.js +21 -12
  100. package/lib/model/loadGraph.js.map +1 -1
  101. package/lib/model/publishedArtifacts.d.ts +78 -97
  102. package/lib/model/publishedArtifacts.js +271 -85
  103. package/lib/model/publishedArtifacts.js.map +1 -1
  104. package/lib/nativeExecutable.d.ts +19 -8
  105. package/lib/nativeExecutable.js +75 -15
  106. package/lib/nativeExecutable.js.map +1 -1
  107. package/lib/reduce.d.ts +19 -45
  108. package/lib/reduce.js +19 -3
  109. package/lib/reduce.js.map +1 -1
  110. package/lib/resolveGraphBinary.d.ts +9 -0
  111. package/lib/resolveGraphBinary.js +11 -3
  112. package/lib/resolveGraphBinary.js.map +1 -1
  113. package/lib/runGraph.d.ts +21 -0
  114. package/lib/runGraph.js +83 -0
  115. package/lib/runGraph.js.map +1 -0
  116. package/lib/server/IResolvedGraphHandle.d.ts +22 -0
  117. package/lib/server/IResolvedGraphHandle.js +3 -0
  118. package/lib/server/IResolvedGraphHandle.js.map +1 -0
  119. package/lib/server/createServer.d.ts +14 -10
  120. package/lib/server/createServer.js +42 -39
  121. package/lib/server/createServer.js.map +1 -1
  122. package/lib/server/exportSurface.d.ts +23 -1
  123. package/lib/server/exportSurface.js +23 -1
  124. package/lib/server/exportSurface.js.map +1 -1
  125. package/lib/server/formatGraphFailure.d.ts +20 -0
  126. package/lib/server/formatGraphFailure.js +35 -0
  127. package/lib/server/formatGraphFailure.js.map +1 -0
  128. package/lib/server/pathPolicy.d.ts +70 -5
  129. package/lib/server/pathPolicy.js +66 -4
  130. package/lib/server/pathPolicy.js.map +1 -1
  131. package/lib/server/resolveHandle.d.ts +10 -5
  132. package/lib/server/resolveHandle.js +39 -7
  133. package/lib/server/resolveHandle.js.map +1 -1
  134. package/lib/server/resultAudit.d.ts +17 -14
  135. package/lib/server/resultAudit.js +56 -27
  136. package/lib/server/resultAudit.js.map +1 -1
  137. package/lib/server/resultNext.d.ts +26 -1
  138. package/lib/server/resultNext.js +14 -0
  139. package/lib/server/resultNext.js.map +1 -1
  140. package/lib/server/runDetails.d.ts +72 -15
  141. package/lib/server/runDetails.js +104 -69
  142. package/lib/server/runDetails.js.map +1 -1
  143. package/lib/server/runEntrypoints.d.ts +9 -0
  144. package/lib/server/runEntrypoints.js +9 -0
  145. package/lib/server/runEntrypoints.js.map +1 -1
  146. package/lib/server/runLookup.d.ts +13 -3
  147. package/lib/server/runLookup.js +19 -6
  148. package/lib/server/runLookup.js.map +1 -1
  149. package/lib/server/runOverview.d.ts +13 -4
  150. package/lib/server/runOverview.js +13 -4
  151. package/lib/server/runOverview.js.map +1 -1
  152. package/lib/server/runTour.d.ts +9 -0
  153. package/lib/server/runTour.js +33 -65
  154. package/lib/server/runTour.js.map +1 -1
  155. package/lib/server/runTrace.d.ts +25 -1
  156. package/lib/server/runTrace.js +133 -88
  157. package/lib/server/runTrace.js.map +1 -1
  158. package/lib/server/startServer.d.ts +16 -0
  159. package/lib/server/startServer.js +24 -1
  160. package/lib/server/startServer.js.map +1 -1
  161. package/lib/structures/ITtscGraphApplication.d.ts +95 -41
  162. package/lib/structures/ITtscGraphDecorator.d.ts +18 -0
  163. package/lib/structures/ITtscGraphDetails.d.ts +81 -6
  164. package/lib/structures/ITtscGraphDocTag.d.ts +17 -8
  165. package/lib/structures/ITtscGraphDump.d.ts +106 -8
  166. package/lib/structures/ITtscGraphEdge.d.ts +14 -2
  167. package/lib/structures/ITtscGraphEntrypoints.d.ts +81 -6
  168. package/lib/structures/ITtscGraphEscape.d.ts +24 -2
  169. package/lib/structures/ITtscGraphEvidence.d.ts +9 -0
  170. package/lib/structures/ITtscGraphLookup.d.ts +42 -9
  171. package/lib/structures/ITtscGraphNext.d.ts +12 -1
  172. package/lib/structures/ITtscGraphNode.d.ts +38 -11
  173. package/lib/structures/ITtscGraphOverview.d.ts +81 -6
  174. package/lib/structures/ITtscGraphSnapshot.d.ts +97 -2
  175. package/lib/structures/ITtscGraphSpan.d.ts +9 -0
  176. package/lib/structures/ITtscGraphTour.d.ts +83 -9
  177. package/lib/structures/ITtscGraphTrace.d.ts +99 -14
  178. package/lib/structures/RawDump.d.ts +22 -0
  179. package/lib/structures/RawDump.js +3 -0
  180. package/lib/structures/RawDump.js.map +1 -0
  181. package/lib/structures/RawEdge.d.ts +20 -0
  182. package/lib/structures/RawEdge.js +3 -0
  183. package/lib/structures/RawEdge.js.map +1 -0
  184. package/lib/structures/RawNode.d.ts +26 -0
  185. package/lib/structures/RawNode.js +3 -0
  186. package/lib/structures/RawNode.js.map +1 -0
  187. package/lib/structures/TtscGraphArtifactNodeKind.d.ts +9 -0
  188. package/lib/structures/TtscGraphArtifactNodeKind.js +9 -0
  189. package/lib/structures/TtscGraphArtifactNodeKind.js.map +1 -1
  190. package/lib/structures/TtscGraphDumpEdgeKind.d.ts +15 -1
  191. package/lib/structures/TtscGraphDumpNodeKind.d.ts +9 -0
  192. package/lib/structures/TtscGraphEdgeKind.d.ts +13 -4
  193. package/lib/structures/TtscGraphNodeKind.d.ts +9 -0
  194. package/lib/structures/TtscGraphNodeModifier.d.ts +9 -0
  195. package/lib/structures/ViewerLink.d.ts +20 -0
  196. package/lib/structures/ViewerLink.js +3 -0
  197. package/lib/structures/ViewerLink.js.map +1 -0
  198. package/lib/structures/ViewerNode.d.ts +24 -0
  199. package/lib/structures/ViewerNode.js +3 -0
  200. package/lib/structures/ViewerNode.js.map +1 -0
  201. package/lib/structures/ViewerPayload.d.ts +42 -0
  202. package/lib/structures/ViewerPayload.js +3 -0
  203. package/lib/structures/ViewerPayload.js.map +1 -0
  204. package/lib/view.d.ts +9 -0
  205. package/lib/view.js +203 -91
  206. package/lib/view.js.map +1 -1
  207. package/lib/viewer/viewer.js +1 -1
  208. package/package.json +8 -6
  209. package/src/CapturedProcessOutput.ts +50 -0
  210. package/src/ILauncherOption.ts +24 -0
  211. package/src/IProjectOptions.ts +19 -0
  212. package/src/ParsedLauncherOptions.ts +13 -0
  213. package/src/TtscGraphApplication.ts +38 -1
  214. package/src/TtscGraphLauncherArguments.ts +207 -0
  215. package/src/TtscGraphViewSnapshot.ts +46 -0
  216. package/src/index.ts +9 -160
  217. package/src/internal/GraphProcessTrace.ts +242 -0
  218. package/src/launcherArgs.ts +89 -23
  219. package/src/model/IArtifactDirectory.ts +27 -0
  220. package/src/model/IArtifactInputs.ts +21 -0
  221. package/src/model/IPublishedArtifacts.ts +81 -0
  222. package/src/model/IPublishedArtifactsResident.ts +24 -0
  223. package/src/model/ITtscLintDaemonTarget.ts +22 -0
  224. package/src/model/TtscGraphLinePeer.ts +318 -0
  225. package/src/model/TtscGraphMemory.ts +284 -246
  226. package/src/model/TtscGraphNativeArguments.ts +67 -0
  227. package/src/model/TtscGraphNodeId.ts +57 -3
  228. package/src/model/TtscGraphProjection.ts +473 -0
  229. package/src/model/TtscGraphProtocol.ts +90 -0
  230. package/src/model/TtscGraphReadonly.ts +17 -0
  231. package/src/model/TtscGraphRequestOptions.ts +16 -0
  232. package/src/model/TtscGraphSession.ts +196 -475
  233. package/src/model/TtscGraphSessionOptions.ts +22 -0
  234. package/src/model/TtscGraphSessionState.ts +558 -0
  235. package/src/model/TtscGraphShardStore.ts +99 -23
  236. package/src/model/TtscGraphSourceReader.ts +37 -15
  237. package/src/model/TtscLintDaemon.ts +55 -136
  238. package/src/model/TtscLintDaemonState.ts +174 -0
  239. package/src/model/copyGraphRecords.ts +17 -0
  240. package/src/model/copyGraphSnapshot.ts +30 -0
  241. package/src/model/loadGraph.ts +23 -12
  242. package/src/model/publishedArtifacts.ts +361 -182
  243. package/src/nativeExecutable.ts +93 -26
  244. package/src/reduce.ts +24 -51
  245. package/src/resolveGraphBinary.ts +13 -3
  246. package/src/runGraph.ts +90 -0
  247. package/src/server/IResolvedGraphHandle.ts +24 -0
  248. package/src/server/createServer.ts +16 -22
  249. package/src/server/exportSurface.ts +23 -1
  250. package/src/server/formatGraphFailure.ts +32 -0
  251. package/src/server/pathPolicy.ts +70 -5
  252. package/src/server/resolveHandle.ts +45 -14
  253. package/src/server/resultAudit.ts +58 -27
  254. package/src/server/resultNext.ts +26 -1
  255. package/src/server/runDetails.ts +138 -73
  256. package/src/server/runEntrypoints.ts +13 -1
  257. package/src/server/runLookup.ts +23 -7
  258. package/src/server/runOverview.ts +17 -5
  259. package/src/server/runTour.ts +44 -82
  260. package/src/server/runTrace.ts +175 -102
  261. package/src/server/startServer.ts +29 -1
  262. package/src/structures/ITtscGraphApplication.ts +95 -41
  263. package/src/structures/ITtscGraphDecorator.ts +18 -0
  264. package/src/structures/ITtscGraphDetails.ts +81 -6
  265. package/src/structures/ITtscGraphDocTag.ts +17 -8
  266. package/src/structures/ITtscGraphDump.ts +106 -8
  267. package/src/structures/ITtscGraphEdge.ts +14 -2
  268. package/src/structures/ITtscGraphEntrypoints.ts +81 -6
  269. package/src/structures/ITtscGraphEscape.ts +24 -2
  270. package/src/structures/ITtscGraphEvidence.ts +9 -0
  271. package/src/structures/ITtscGraphLookup.ts +42 -9
  272. package/src/structures/ITtscGraphNext.ts +12 -1
  273. package/src/structures/ITtscGraphNode.ts +38 -11
  274. package/src/structures/ITtscGraphOverview.ts +88 -6
  275. package/src/structures/ITtscGraphSnapshot.ts +117 -2
  276. package/src/structures/ITtscGraphSpan.ts +9 -0
  277. package/src/structures/ITtscGraphTour.ts +83 -9
  278. package/src/structures/ITtscGraphTrace.ts +100 -14
  279. package/src/structures/RawDump.ts +25 -0
  280. package/src/structures/RawEdge.ts +22 -0
  281. package/src/structures/RawNode.ts +31 -0
  282. package/src/structures/TtscGraphArtifactNodeKind.ts +9 -0
  283. package/src/structures/TtscGraphDumpEdgeKind.ts +15 -1
  284. package/src/structures/TtscGraphDumpNodeKind.ts +9 -0
  285. package/src/structures/TtscGraphEdgeKind.ts +13 -4
  286. package/src/structures/TtscGraphNodeKind.ts +9 -0
  287. package/src/structures/TtscGraphNodeModifier.ts +9 -0
  288. package/src/structures/ViewerLink.ts +22 -0
  289. package/src/structures/ViewerNode.ts +28 -0
  290. package/src/structures/ViewerPayload.ts +52 -0
  291. package/src/view.ts +208 -110
  292. package/src/viewer/legend.ts +119 -27
@@ -1,81 +1,47 @@
1
- import { ChildProcessWithoutNullStreams, spawn } from "node:child_process";
2
- import readline from "node:readline";
3
- import typia from "typia";
1
+ import { CapabilityPluginResolver } from "ttsc";
4
2
 
5
3
  import { ensureExecutable } from "../nativeExecutable";
6
4
  import { resolveGraphBinary } from "../resolveGraphBinary";
7
- import { ITtscGraphSnapshot } from "../structures/ITtscGraphSnapshot";
5
+ import type { IPublishedArtifactsResident } from "./IPublishedArtifactsResident";
6
+ import { TtscGraphLinePeer } from "./TtscGraphLinePeer";
8
7
  import { TtscGraphMemory } from "./TtscGraphMemory";
9
- import { TtscGraphShardStore } from "./TtscGraphShardStore";
8
+ import { TtscGraphNativeArguments } from "./TtscGraphNativeArguments";
9
+ import { TtscGraphProtocol } from "./TtscGraphProtocol";
10
+ import type { TtscGraphRequestOptions } from "./TtscGraphRequestOptions";
11
+ import type { TtscGraphSessionOptions } from "./TtscGraphSessionOptions";
12
+ import { TtscGraphSessionState } from "./TtscGraphSessionState";
10
13
  import { TtscLintDaemon } from "./TtscLintDaemon";
11
- import { DUMP_SCHEMA_VERSION } from "./loadGraph";
12
14
  import {
13
- type IPublishedArtifacts,
14
- artifactsAreStale,
15
- publishArtifacts,
15
+ artifactsAreStaleResident,
16
16
  publishArtifactsResident,
17
17
  } from "./publishedArtifacts";
18
18
 
19
- /**
20
- * The serve protocol version this client speaks.
21
- *
22
- * Keep it equal to `serveProtocolVersion` in
23
- * `packages/ttsc/cmd/ttscgraph/serve.go`. The two are hand-synchronized, and
24
- * `serve_protocol_version_matches_the_typescript_client_test.go` reads this
25
- * constant out of this file and fails if the pair drifts.
26
- */
27
- const PROTOCOL_VERSION = 1;
28
- const GRAPH_SNAPSHOT_PROTOCOL_VERSION = 1;
29
- const TERMINATION_GRACE_MS = 1_000;
30
-
31
- interface Pending {
32
- child: NativeChild;
33
- resolve: (response: ITtscGraphSnapshot) => void;
34
- reject: (error: Error) => void;
35
- signal?: AbortSignal;
36
- abort?: () => void;
37
- }
38
-
39
- interface NativeChild {
40
- process: ChildProcessWithoutNullStreams;
41
- lines: readline.Interface;
42
- stderr: string;
43
- }
44
-
45
- /** Construction options for a resident native graph session. */
46
- export interface TtscGraphSessionOptions {
47
- /** Project root passed to `ttscgraph serve`. */
48
- cwd: string;
49
- /** Project tsconfig passed to `ttscgraph serve`. */
50
- tsconfig: string;
51
- /** Absolute native binary path, resolved from `cwd` when omitted. */
52
- binary?: string;
53
- }
54
-
55
- /** Per-call controls for a native graph refresh. */
56
- export interface TtscGraphRequestOptions {
57
- /** Cancel this refresh and retire its native session. */
58
- signal?: AbortSignal;
59
- }
60
-
61
19
  /**
62
20
  * Resident bridge to `ttscgraph serve`.
63
21
  *
64
- * Every graph request first asks the native session for the current disk
65
- * snapshot. Unchanged requests reuse the existing {@link TtscGraphMemory}; an
66
- * edited source reuses tsgo's resident Program through `driver.Session`, while
67
- * config and root-file-set changes force a safe full reload.
22
+ * Requests admitted before a freshness check starts share that check and its
23
+ * snapshot. Later arrivals require another check. Unchanged requests reuse the
24
+ * existing {@link TtscGraphMemory}; an edited source reuses tsgo's resident
25
+ * Program through `driver.Session`, while config and root-file-set changes
26
+ * force a safe full reload.
27
+ *
28
+ * Cold publisher discovery, proof validation and direct publisher commands run
29
+ * through the SDK's asynchronous owner. Request-group cancellation and session
30
+ * shutdown join those producers before a later admission can publish.
31
+ *
32
+ * @evidence contracts/common.md#principled-implementation Validated versioned responses and atomic shard transactions preserve generation consistency while serialized requests correlate native replies by id.
33
+ * @evidence contracts/common.md#clear-and-simple-design This facade owns project/binary and artifact sidecars; the state owns queue/model/shards, the protocol owns generated decoding and the line adapter owns the native child.
34
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Compatible full dumps remain an explicit protocol fallback; malformed or mismatched responses retire the child rather than producing partial trusted facts.
35
+ * @evidence contracts/common.md#meaningful-documentation Native paragraphs explain resident reuse, reload boundaries and artifact ownership, while public methods describe request and shutdown behavior.
36
+ * @evidenceExclude contracts/performance.md#efficient-algorithms graph delegates admission and refresh to the state owner; this facade describes project, binary and artifact sidecar identity.
37
+ * @evidenceExclude contracts/performance.md#reuse-equivalent-work graph owns coordination of model/producer reuse and its invalidation decisions.
38
+ * @evidenceExclude contracts/performance.md#bound-retention-and-release-resources graph/close own actual acquisition and retirement; the class declaration describes the owner without a separate lifecycle operation.
39
+ * @evidenceExclude contracts/portability.md#os-neutral-implementation Native project resolution and process control are reviewed through graph and close, including their private helpers.
68
40
  */
69
41
  export class TtscGraphSession {
70
42
  private readonly cwd: string;
71
43
  private readonly tsconfig: string;
72
44
  private readonly binary: string;
73
- private child: NativeChild | undefined;
74
- private nextId = 0;
75
- private readonly pending = new Map<number, Pending>();
76
- private queue: Promise<void> = Promise.resolve();
77
- private current: TtscGraphMemory | undefined;
78
- private shardStore = new TtscGraphShardStore();
79
45
  /**
80
46
  * The artifact answer the resident child was last handed, and the state of
81
47
  * the inputs it came from.
@@ -85,7 +51,9 @@ export class TtscGraphSession {
85
51
  * still carries inputs, so that adding a publisher is something a running
86
52
  * session can notice.
87
53
  */
88
- private artifacts: IPublishedArtifacts | undefined;
54
+ private artifacts: IPublishedArtifactsResident | undefined;
55
+ private readonly resolver = new CapabilityPluginResolver();
56
+
89
57
  /**
90
58
  * One resident `@ttsc/lint` sidecar per plugin binary, opened lazily.
91
59
  *
@@ -96,7 +64,10 @@ export class TtscGraphSession {
96
64
  * binary answering for the same project.
97
65
  */
98
66
  private readonly daemons = new Map<string, TtscLintDaemon>();
99
- private closed = false;
67
+ private readonly daemonIdentities = new Map<string, string>();
68
+ private readonly state: TtscGraphSessionState;
69
+ private readonly shutdown = new AbortController();
70
+ private readonly retiredDaemons = new Set<Promise<void>>();
100
71
 
101
72
  public constructor(options: TtscGraphSessionOptions) {
102
73
  // Resolve the platform binary from the project this session serves, so the
@@ -115,129 +86,78 @@ export class TtscGraphSession {
115
86
  this.cwd = options.cwd;
116
87
  this.tsconfig = options.tsconfig;
117
88
  this.binary = binary;
89
+ this.state = new TtscGraphSessionState({
90
+ open: (events) => this.open(events),
91
+ decode: TtscGraphProtocol.decode,
92
+ beforeRequest: (signal) => this.republishArtifacts(signal),
93
+ artifacts: () =>
94
+ this.artifacts === undefined ? undefined : (this.artifacts.file ?? ""),
95
+ close: async () => {
96
+ for (const daemon of this.daemons.values()) this.retireDaemon(daemon);
97
+ this.daemons.clear();
98
+ this.daemonIdentities.clear();
99
+ const results = await Promise.allSettled([
100
+ ...this.retiredDaemons,
101
+ this.resolver.close(),
102
+ ]);
103
+ this.artifacts = undefined;
104
+ const failures = results
105
+ .filter(
106
+ (result): result is PromiseRejectedResult =>
107
+ result.status === "rejected",
108
+ )
109
+ .map((result) => result.reason);
110
+ if (failures.length > 0)
111
+ throw new AggregateError(
112
+ failures,
113
+ "@ttsc/graph: sidecar shutdown failed",
114
+ );
115
+ },
116
+ });
118
117
  }
119
118
 
120
- /** Return a graph for the current disk snapshot, serialized per tool call. */
119
+ /**
120
+ * Return a graph refreshed after this caller's admission.
121
+ *
122
+ * Callers already waiting when a check starts share it. Cancellation rejects
123
+ * only that caller. The last cancellation aborts shared work; an outstanding
124
+ * native request then retires its peer. Closed sessions reject new requests
125
+ * and do not respawn.
126
+ *
127
+ * @evidence contracts/common.md#principled-implementation The state groups only callers admitted before its refresh starts, so every returned snapshot was validated after that caller's admission; independent cancellation cannot settle siblings.
128
+ * @evidence contracts/common.md#clear-and-simple-design The state owner handles admission, cancellation, response semantics and model replacement; facade callbacks synchronize artifacts and open the actual native transport.
129
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts A cancelled or closed request cannot be fulfilled from fabricated empty facts or restart a disposed session.
130
+ * @evidence contracts/common.md#meaningful-documentation Native prose states admission-based snapshot sharing, independent cancellation and closed-session behavior.
131
+ * @evidence contracts/portability.md#os-neutral-implementation Native binary resolution, argv spawning and Node termination APIs own host differences; project coordinates are never passed through a shell.
132
+ * @evidence contracts/performance.md#efficient-algorithms An admission group validates artifact inputs and a native frame once, then shares its model among live callers; changed frames additionally validate shards and rebuild indexes.
133
+ * @evidence contracts/performance.md#reuse-equivalent-work Only callers admitted before validation starts share that validation. Later groups check again; unchanged responses reuse current memory, while changed generations replace it and changed artifacts are republished before requesting facts.
134
+ * @evidence contracts/performance.md#bound-retention-and-release-resources The session owns one graph child, current model, shard map and current publisher sidecars; republishing retires removed publishers, while pending/queued demand grows with submitted requests and completion/cancellation remove registrations.
135
+ */
121
136
  public graph(
122
137
  options: TtscGraphRequestOptions = {},
123
138
  ): Promise<TtscGraphMemory> {
124
- if (this.closed) {
125
- return Promise.reject(new Error("@ttsc/graph: native session is closed"));
126
- }
127
- let resolve!: (graph: TtscGraphMemory) => void;
128
- let reject!: (error: Error) => void;
129
- let started = false;
130
- let settled = false;
131
- const result = new Promise<TtscGraphMemory>((res, rej) => {
132
- resolve = (graph) => {
133
- if (settled) return;
134
- settled = true;
135
- res(graph);
136
- };
137
- reject = (error) => {
138
- if (settled) return;
139
- settled = true;
140
- rej(error);
141
- };
142
- });
143
- const cancelQueued = () => {
144
- if (!started) reject(cancelledError(options.signal));
145
- };
146
- if (options.signal?.aborted) {
147
- reject(cancelledError(options.signal));
148
- return result;
149
- }
150
- options.signal?.addEventListener("abort", cancelQueued, { once: true });
151
- this.queue = this.queue
152
- .catch(() => undefined)
153
- .then(async () => {
154
- started = true;
155
- options.signal?.removeEventListener("abort", cancelQueued);
156
- if (settled) return;
157
- try {
158
- resolve(await this.refresh(options.signal));
159
- } catch (error) {
160
- reject(asError(error));
161
- }
162
- });
163
- return result;
164
- }
165
-
166
- /** Close the native session. Safe to call more than once. */
167
- public close(): void {
168
- if (this.closed) return;
169
- this.closed = true;
170
- // The sidecars outlive nothing. A daemon is a child process this session
171
- // opened, and a session that closed without stopping them would leave one
172
- // resident Program per configured publisher alive for as long as the parent
173
- // ran.
174
- for (const daemon of this.daemons.values()) daemon.close();
175
- this.daemons.clear();
176
- const error = new Error("@ttsc/graph: native session closed");
177
- if (this.child !== undefined) this.failChild(this.child, error);
178
- else this.failPending(error);
139
+ return this.state.graph(options);
179
140
  }
180
141
 
181
- private async refresh(signal?: AbortSignal): Promise<TtscGraphMemory> {
182
- // The protocol version and the envelope shape were both settled in onLine,
183
- // before this frame was ever routed here.
184
- await this.republishArtifacts(signal);
185
- const response = await this.request(signal);
186
- this.assertResponseSemantics(response);
187
- if (response.error !== undefined) {
188
- throw new Error(`@ttsc/graph: ${response.error}`);
189
- }
190
- if (response.changed) {
191
- if (response.snapshot !== undefined) {
192
- try {
193
- this.current = TtscGraphMemory.from(
194
- this.shardStore.apply(response.snapshot),
195
- );
196
- } catch (error) {
197
- const failure = asError(error);
198
- if (this.child !== undefined) this.failChild(this.child, failure);
199
- throw failure;
200
- }
201
- } else if (response.dump !== undefined) {
202
- // A serve-protocol-v1 binary predating graph shard negotiation ignores
203
- // the optional request field and retains the complete-dump response.
204
- this.current = TtscGraphMemory.from(response.dump);
205
- } else {
206
- throw new Error(
207
- `@ttsc/graph: native ${response.mode} response omitted both graph snapshot protocol v${String(GRAPH_SNAPSHOT_PROTOCOL_VERSION)} data and a compatible dump`,
208
- );
209
- }
210
- }
211
- if (this.current === undefined) {
212
- throw new Error(
213
- "@ttsc/graph: native session returned no initial graph snapshot",
214
- );
215
- }
216
- return this.current;
217
- }
218
-
219
- private assertResponseSemantics(response: ITtscGraphSnapshot): void {
220
- const bodies =
221
- Number(response.dump !== undefined) +
222
- Number(response.snapshot !== undefined);
223
- let problem: string | undefined;
224
- if (response.error !== undefined) {
225
- if (response.mode !== "error" || response.changed || bodies !== 0) {
226
- problem = "an error response carried snapshot state";
227
- }
228
- } else if (response.mode === "error") {
229
- problem = "an error-mode response omitted its error";
230
- } else if (response.changed) {
231
- if (response.mode === "unchanged" || bodies !== 1) {
232
- problem = "a changed response did not carry exactly one snapshot body";
233
- }
234
- } else if (response.mode !== "unchanged" || bodies !== 0) {
235
- problem = "an unchanged response carried changed mode or snapshot state";
236
- }
237
- if (problem === undefined) return;
238
- const error = new Error(`@ttsc/graph: native session returned ${problem}`);
239
- if (this.child !== undefined) this.failChild(this.child, error);
240
- throw error;
142
+ /**
143
+ * Close the native session. Safe to call more than once.
144
+ *
145
+ * Retires artifact sidecars, discovery worker and supervised publisher
146
+ * commands, and rejects pending native requests. Queued requests observe the
147
+ * closed state before they can create another child.
148
+ *
149
+ * @evidence contracts/common.md#principled-implementation The closed flag precedes sidecar disposal and child failure, so every pending or subsequent native operation observes retired ownership.
150
+ * @evidence contracts/common.md#clear-and-simple-design The explicit close path reuses failChild/failPending cleanup instead of a second cancellation implementation.
151
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Disposing a session cannot trigger a compensating respawn or pretend outstanding requests succeeded.
152
+ * @evidence contracts/common.md#meaningful-documentation Native prose documents idempotence, pending rejection and queued-request behavior.
153
+ * @evidence contracts/portability.md#os-neutral-implementation Node child termination ends stdin, signals the process and escalates after a grace period without platform shell commands.
154
+ * @evidence contracts/performance.md#efficient-algorithms Shutdown visits sidecars and pending replies once; a delayed force-kill timer is cancelled when the child exits.
155
+ * @evidence contracts/performance.md#reuse-equivalent-work Closure ends this owner's permission to reuse native Program, model and sidecars; subsequent graph calls reject.
156
+ * @evidence contracts/performance.md#bound-retention-and-release-resources Shutdown aborts discovery and publisher commands, joins the resolver and sidecars, and drops opaque proofs. Native readers and reply listeners are removed, model/shards are released with child retirement, and graph-peer termination owns its finite grace timer.
157
+ */
158
+ public close(): Promise<void> {
159
+ this.shutdown.abort(new Error("@ttsc/graph: native session closed"));
160
+ return this.state.close();
241
161
  }
242
162
 
243
163
  /**
@@ -261,317 +181,118 @@ export class TtscGraphSession {
261
181
  * evaluation per verb. An already-cancelled request does not start one.
262
182
  */
263
183
  private async republishArtifacts(signal?: AbortSignal): Promise<void> {
264
- if (signal?.aborted) return;
265
- // A child yet to be spawned publishes on the way up, so there is nothing
266
- // here to keep fresh until one does.
267
- if (this.child === undefined || this.artifacts === undefined) return;
268
- if (!artifactsAreStale(this.artifacts)) return;
269
- const next = await publishArtifactsResident(
270
- { cwd: this.cwd, tsconfig: this.tsconfig },
271
- (plugin) => this.daemon(plugin),
272
- );
273
- // The new answer is taken whatever it says, including that the project now
274
- // publishes nothing. Keeping the old set on a `null` would be guessing that
275
- // the publisher failed rather than that it was removed, and guessing wrong
276
- // in that direction is the unrecoverable one: a session that answers with
277
- // artifacts from a plugin the user deleted keeps doing so until it is
278
- // restarted, while a transient failure is repaired by the next edit.
279
- this.artifacts = next;
184
+ const active =
185
+ signal === undefined
186
+ ? this.shutdown.signal
187
+ : AbortSignal.any([signal, this.shutdown.signal]);
188
+ active.throwIfAborted();
189
+ await Promise.all(this.retiredDaemons);
190
+ active.throwIfAborted();
191
+ if (
192
+ this.artifacts !== undefined &&
193
+ !(await artifactsAreStaleResident(this.artifacts, { signal: active }))
194
+ )
195
+ return;
196
+ const publishers = new Set<string>();
197
+ let next: IPublishedArtifactsResident | undefined;
198
+ let failure: unknown;
199
+ try {
200
+ next = await publishArtifactsResident(
201
+ { cwd: this.cwd, tsconfig: this.tsconfig, signal: active },
202
+ (plugin) => {
203
+ publishers.add(plugin.binary);
204
+ return this.daemon(plugin);
205
+ },
206
+ this.resolver,
207
+ );
208
+ for (const [binary, daemon] of this.daemons) {
209
+ if (publishers.has(binary)) continue;
210
+ await this.retireDaemon(daemon);
211
+ this.daemons.delete(binary);
212
+ this.daemonIdentities.delete(binary);
213
+ }
214
+ active.throwIfAborted();
215
+ // Successful absence replaces the old overlay too. The exact old proof
216
+ // is released before the new one becomes this session's authority.
217
+ await this.artifacts?.discovery.release();
218
+ active.throwIfAborted();
219
+ this.artifacts = next;
220
+ next = undefined;
221
+ } catch (error) {
222
+ failure = error;
223
+ throw error;
224
+ } finally {
225
+ const closing: Promise<void>[] = [];
226
+ if (next !== undefined) closing.push(next.discovery.release());
227
+ if (active.aborted) {
228
+ for (const binary of publishers) {
229
+ const daemon = this.daemons.get(binary);
230
+ if (daemon !== undefined) closing.push(this.retireDaemon(daemon));
231
+ this.daemons.delete(binary);
232
+ this.daemonIdentities.delete(binary);
233
+ }
234
+ }
235
+ const results = await Promise.allSettled(closing);
236
+ const failures = results
237
+ .filter(
238
+ (result): result is PromiseRejectedResult =>
239
+ result.status === "rejected",
240
+ )
241
+ .map((result) => result.reason);
242
+ if (failures.length > 0)
243
+ throw new AggregateError(
244
+ [...(failure === undefined ? [] : [failure]), ...failures],
245
+ "@ttsc/graph: artifact cleanup failed",
246
+ );
247
+ }
280
248
  }
281
249
 
282
250
  /** The open sidecar for one plugin, opened on first use. */
283
- private daemon(plugin: {
251
+ private async daemon(plugin: {
284
252
  binary: string;
285
253
  manifest: string;
286
254
  projectContext?: string;
287
- }): TtscLintDaemon {
255
+ }): Promise<TtscLintDaemon> {
256
+ this.shutdown.signal.throwIfAborted();
288
257
  const open = this.daemons.get(plugin.binary);
289
- if (open !== undefined) return open;
258
+ const identity = JSON.stringify([plugin.manifest, plugin.projectContext]);
259
+ if (
260
+ open !== undefined &&
261
+ this.daemonIdentities.get(plugin.binary) === identity
262
+ )
263
+ return open;
264
+ if (open !== undefined) {
265
+ const closing = this.retireDaemon(open);
266
+ await closing;
267
+ }
268
+ this.shutdown.signal.throwIfAborted();
290
269
  const created = new TtscLintDaemon(plugin, this.cwd, this.tsconfig);
291
270
  this.daemons.set(plugin.binary, created);
271
+ this.daemonIdentities.set(plugin.binary, identity);
292
272
  return created;
293
273
  }
294
274
 
295
- private request(signal?: AbortSignal): Promise<ITtscGraphSnapshot> {
296
- if (signal?.aborted) throw cancelledError(signal);
297
- const child = this.ensureChild();
298
- const id = ++this.nextId;
299
- return new Promise<ITtscGraphSnapshot>((resolve, reject) => {
300
- const pending: Pending = {
301
- child,
302
- resolve,
303
- reject,
304
- signal,
305
- };
306
- if (signal !== undefined) {
307
- pending.abort = () =>
308
- this.failChild(child, cancelledError(signal, child));
309
- signal.addEventListener("abort", pending.abort, { once: true });
310
- }
311
- this.pending.set(id, pending);
312
- if (signal?.aborted) {
313
- pending.abort!();
314
- return;
315
- }
316
- child.process.stdin.write(
317
- `${JSON.stringify({
318
- id,
319
- graphSnapshotVersion: GRAPH_SNAPSHOT_PROTOCOL_VERSION,
320
- // Empty when the project publishes nothing, which withdraws whatever
321
- // the server holds: a publisher the user removed must stop being
322
- // answered with, and omitting the field instead would say only that
323
- // this client has no opinion. Omitted only before a child exists,
324
- // which no request reaches.
325
- artifacts:
326
- this.artifacts === undefined
327
- ? undefined
328
- : (this.artifacts.file ?? ""),
329
- })}\n`,
330
- (error) => {
331
- if (error === null || error === undefined) return;
332
- if (this.pending.get(id) !== pending) return;
333
- this.failChild(
334
- child,
335
- new Error(
336
- `@ttsc/graph: could not request native snapshot: ${error.message}`,
337
- ),
338
- );
339
- },
340
- );
341
- });
275
+ /** Keep failures visible to shutdown, releasing fulfilled historical owners. */
276
+ private retireDaemon(daemon: TtscLintDaemon): Promise<void> {
277
+ const closing = daemon.close();
278
+ this.retiredDaemons.add(closing);
279
+ void closing.then(
280
+ () => this.retiredDaemons.delete(closing),
281
+ () => undefined,
282
+ );
283
+ return closing;
342
284
  }
343
285
 
344
- private ensureChild(): NativeChild {
345
- if (this.closed) {
346
- // A request queued behind the close must not respawn the native
347
- // process; an orphaned resident compiler would outlive the MCP server.
348
- throw new Error("@ttsc/graph: native session is closed");
349
- }
350
- if (
351
- this.child !== undefined &&
352
- this.child.process.exitCode === null &&
353
- this.child.process.signalCode === null
354
- ) {
355
- return this.child;
356
- }
357
- const artifacts = publishArtifacts({
358
- cwd: this.cwd,
359
- tsconfig: this.tsconfig,
360
- });
361
- this.artifacts = artifacts;
362
- const process = spawn(
286
+ private open(events: TtscGraphLinePeer.Events): TtscGraphLinePeer.Connection {
287
+ this.shutdown.signal.throwIfAborted();
288
+ const artifacts = this.artifacts;
289
+ if (artifacts === undefined)
290
+ throw new Error("@ttsc/graph: artifact publication was not admitted");
291
+ return TtscGraphLinePeer.open(
363
292
  this.binary,
364
- [
365
- "serve",
366
- "--cwd",
367
- this.cwd,
368
- "--tsconfig",
369
- this.tsconfig,
370
- ...(artifacts.file === null ? [] : ["--artifacts", artifacts.file]),
371
- ],
372
- { stdio: ["pipe", "pipe", "pipe"], windowsHide: true },
373
- );
374
- const lines = readline.createInterface({ input: process.stdout });
375
- const child: NativeChild = { process, lines, stderr: "" };
376
- this.child = child;
377
- process.stderr.setEncoding("utf8");
378
- process.stderr.on("data", (chunk: string) => {
379
- child.stderr = (child.stderr + chunk).slice(-64 * 1024);
380
- });
381
- lines.on("line", (line) => this.onLine(child, line));
382
- process.on("error", (error) =>
383
- this.failChild(
384
- child,
385
- new Error(`@ttsc/graph: native session failed: ${error.message}`),
386
- ),
293
+ TtscGraphNativeArguments.serve(this.cwd, this.tsconfig, artifacts.file),
294
+ events,
295
+ { stderr: "capture" },
387
296
  );
388
- process.on("exit", (code, signal) => {
389
- if (this.child !== child) return;
390
- this.failChild(
391
- child,
392
- new Error(
393
- `@ttsc/graph: native session exited (code=${String(code)}, signal=${String(signal)})${stderrSuffix(
394
- child,
395
- )}`,
396
- ),
397
- false,
398
- );
399
- });
400
- return child;
401
297
  }
402
-
403
- private onLine(child: NativeChild, line: string): void {
404
- if (this.child !== child) return;
405
- let parsed: unknown;
406
- try {
407
- parsed = JSON.parse(line);
408
- } catch (error) {
409
- this.failChild(
410
- child,
411
- new Error(
412
- `@ttsc/graph: native session returned invalid JSON: ${asError(error).message}`,
413
- ),
414
- );
415
- return;
416
- }
417
-
418
- // Read the version before the shape, because a server speaking another
419
- // version is entitled to a different shape. Asserting first would report
420
- // that mismatch as a field complaint — "expected string at $input.mode" —
421
- // about a contract the other side never agreed to, which is the misparse
422
- // this field exists to prevent. Ask what protocol it is first, then hold it
423
- // to that protocol.
424
- const version: number | undefined = typia.is<{ protocolVersion: number }>(
425
- parsed,
426
- )
427
- ? parsed.protocolVersion
428
- : undefined;
429
- if (version !== PROTOCOL_VERSION) {
430
- // Session-wide: a version mismatch is not one bad frame, it is the wrong
431
- // binary, and every request against it is equally doomed.
432
- this.failChild(
433
- child,
434
- new Error(
435
- `@ttsc/graph: ttscgraph speaks serve protocol ${
436
- version === undefined ? "an unknown version" : `v${String(version)}`
437
- }, this client speaks v${String(PROTOCOL_VERSION)}. ` +
438
- "Install a matching `ttsc` (the binary resolves from the target " +
439
- "project, or from TTSC_GRAPH_BINARY).",
440
- ),
441
- );
442
- return;
443
- }
444
-
445
- let response: ITtscGraphSnapshot;
446
- try {
447
- // Validate the envelope, not just the dump it carries. The dump was
448
- // typia-asserted while the envelope around it was a bare cast, so the
449
- // fields the client actually branches on — the mode, and the id that
450
- // routes the frame — were the unchecked ones. Anything added to the
451
- // envelope belongs on this side of that line.
452
- response = typia.assert<ITtscGraphSnapshot>(parsed);
453
- } catch (error) {
454
- this.failChild(
455
- child,
456
- new Error(
457
- `@ttsc/graph: native session returned an unreadable response: ${asError(error).message}`,
458
- ),
459
- );
460
- return;
461
- }
462
- // The envelope's version is not the body's, and only the envelope has been
463
- // held to one so far. A producer can speak this protocol and still carry a
464
- // dump from another schema — the two move apart the moment a node field is
465
- // added without the frame around it changing — and then the facts that field
466
- // holds are silently absent rather than refused. `literals` is exactly that
467
- // shape: an older producer resolves no value set, so a union comes back
468
- // looking like a type with no members. Hold the body to its own number too,
469
- // once the frame is understood.
470
- if (
471
- (response.dump !== undefined &&
472
- response.dump.provenance.schemaVersion !== DUMP_SCHEMA_VERSION) ||
473
- (response.snapshot !== undefined &&
474
- response.snapshot.schemaVersion !== DUMP_SCHEMA_VERSION)
475
- ) {
476
- // Session-wide, for the same reason the protocol mismatch above is: it is
477
- // the wrong binary, not one bad frame.
478
- this.failChild(
479
- child,
480
- new Error(
481
- `@ttsc/graph: ttscgraph sends dump schema v${String(
482
- response.dump?.provenance.schemaVersion ??
483
- response.snapshot?.schemaVersion,
484
- )}, this client reads v${String(DUMP_SCHEMA_VERSION)}. ` +
485
- "Install a matching `ttsc` (the binary resolves from the target " +
486
- "project, or from TTSC_GRAPH_BINARY).",
487
- ),
488
- );
489
- return;
490
- }
491
-
492
- const pending = this.pending.get(response.id);
493
- if (pending === undefined || pending.child !== child) return;
494
- this.settlePending(response.id, pending, response);
495
- }
496
-
497
- private failChild(child: NativeChild, error: Error, terminate = true): void {
498
- if (this.child !== child) return;
499
- this.child = undefined;
500
- this.current = undefined;
501
- this.shardStore = new TtscGraphShardStore();
502
- child.lines.close();
503
- this.failPending(error, child);
504
- if (terminate) terminateChild(child.process);
505
- }
506
-
507
- private failPending(error: Error, child?: NativeChild): void {
508
- for (const [id, pending] of this.pending) {
509
- if (child === undefined || pending.child === child) {
510
- this.settlePending(id, pending, error);
511
- }
512
- }
513
- }
514
-
515
- private settlePending(
516
- id: number,
517
- pending: Pending,
518
- result: ITtscGraphSnapshot | Error,
519
- ): void {
520
- if (this.pending.get(id) !== pending) return;
521
- this.pending.delete(id);
522
- if (pending.signal !== undefined && pending.abort !== undefined) {
523
- pending.signal.removeEventListener("abort", pending.abort);
524
- }
525
- if (result instanceof Error) pending.reject(result);
526
- else pending.resolve(result);
527
- }
528
- }
529
-
530
- function terminateChild(child: ChildProcessWithoutNullStreams): void {
531
- if (!child.stdin.destroyed) child.stdin.destroy();
532
- if (child.exitCode !== null || child.signalCode !== null) return;
533
- try {
534
- child.kill();
535
- } catch {
536
- return;
537
- }
538
- const force = setTimeout(() => {
539
- if (child.exitCode !== null || child.signalCode !== null) return;
540
- try {
541
- child.kill("SIGKILL");
542
- } catch {
543
- // The process exited between the liveness check and the signal.
544
- }
545
- }, TERMINATION_GRACE_MS);
546
- force.unref();
547
- child.once("exit", () => clearTimeout(force));
548
- }
549
-
550
- function cancelledError(signal?: AbortSignal, child?: NativeChild): Error {
551
- const error = new Error(
552
- `@ttsc/graph: native snapshot request cancelled${abortDetail(signal)}${
553
- child === undefined ? "" : stderrSuffix(child)
554
- }`,
555
- );
556
- error.name = "AbortError";
557
- return error;
558
- }
559
-
560
- function abortDetail(signal?: AbortSignal): string {
561
- const reason = signal?.reason;
562
- if (reason === undefined) return "";
563
- try {
564
- return `: ${reason instanceof Error ? reason.message : String(reason)}`;
565
- } catch {
566
- return "";
567
- }
568
- }
569
-
570
- function stderrSuffix(child: NativeChild): string {
571
- const stderr = child.stderr.trim();
572
- return stderr === "" ? "" : `: ${stderr}`;
573
- }
574
-
575
- function asError(error: unknown): Error {
576
- return error instanceof Error ? error : new Error(String(error));
577
298
  }