ttsc 0.23.0 → 0.25.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 (149) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +66 -63
  3. package/cmd/ttscgraph/serve.go +264 -48
  4. package/cmd/ttscgraph/serve_answers_a_project_that_implements_its_own_ambient_declaration_test.go +69 -0
  5. package/cmd/ttscgraph/serve_early_error_encode_failure_is_observable_test.go +40 -0
  6. package/cmd/ttscgraph/serve_graph_snapshot_version_matches_typescript_client_test.go +32 -0
  7. package/cmd/ttscgraph/serve_phase_trace_accounts_for_load_failure_test.go +45 -0
  8. package/cmd/ttscgraph/serve_phase_trace_is_opt_in_and_payload_free_test.go +52 -0
  9. package/cmd/ttscgraph/serve_protocol_negotiates_incremental_shards_test.go +44 -0
  10. package/cmd/ttscgraph/serve_session_reloads_commonjs_package_entry_for_esm_test.go +2 -2
  11. package/cmd/ttscgraph/serve_session_reloads_superseding_module_candidates_test.go +46 -20
  12. package/cmd/ttscgraph/serve_session_retries_failed_dump_test.go +9 -9
  13. package/cmd/ttscgraph/serve_shards.go +1390 -0
  14. package/cmd/ttscgraph/serve_shards_abandoned_generation_converges_on_retry_test.go +92 -0
  15. package/cmd/ttscgraph/serve_shards_delete_removed_source_test.go +57 -0
  16. package/cmd/ttscgraph/serve_shards_external_endpoints_test.go +53 -0
  17. package/cmd/ttscgraph/serve_shards_keep_source_distributed_dependencies_at_the_boundary_test.go +108 -0
  18. package/cmd/ttscgraph/serve_shards_match_full_dump_oracle_test.go +88 -0
  19. package/cmd/ttscgraph/serve_shards_merge_pending_and_new_edits_test.go +50 -0
  20. package/cmd/ttscgraph/serve_shards_prune_unreferenced_external_endpoints_test.go +65 -0
  21. package/cmd/ttscgraph/serve_shards_publish_created_source_test.go +92 -0
  22. package/cmd/ttscgraph/serve_shards_rebuild_declaration_edit_test.go +43 -0
  23. package/cmd/ttscgraph/serve_shards_refresh_cross_file_implementation_test.go +63 -0
  24. package/cmd/ttscgraph/serve_shards_refresh_dependent_diagnostics_test.go +80 -0
  25. package/cmd/ttscgraph/serve_shards_rename_moves_source_identity_test.go +95 -0
  26. package/cmd/ttscgraph/serve_shards_retry_preserves_committed_generation_test.go +48 -0
  27. package/cmd/ttscgraph/serve_shards_reuse_unaffected_sources_test.go +121 -0
  28. package/driver/declaration_shape.go +85 -0
  29. package/driver/program.go +39 -10
  30. package/go.sum +22 -22
  31. package/internal/graph/assigned_implementations_of_declaration_file_symbols_leave_no_edge_source_test.go +84 -0
  32. package/internal/graph/assigned_implementations_of_node_modules_symbols_leave_no_edge_source_test.go +85 -0
  33. package/internal/graph/build.go +62 -10
  34. package/internal/graph/build_files_reuses_committed_cross_file_endpoints_test.go +87 -0
  35. package/internal/graph/build_keeps_source_distributed_dependencies_at_the_external_boundary_test.go +74 -0
  36. package/internal/graph/dump.go +84 -43
  37. package/internal/graph/dump_path_mapper_caches_alias_resolution_test.go +29 -0
  38. package/internal/graph/dump_paths.go +134 -11
  39. package/internal/graph/dump_paths_canonicalize_windows_short_root_test.go +39 -0
  40. package/internal/graph/dump_rejects_provenance_only_path_errors_test.go +32 -0
  41. package/internal/graph/dump_serializes_the_full_graph_test.go +2 -2
  42. package/internal/graph/dump_symlink_project_publishes_matching_base_test.go +82 -0
  43. package/internal/graph/edges.go +73 -17
  44. package/internal/graph/exports.go +9 -2
  45. package/internal/graph/graph.go +47 -7
  46. package/internal/graph/graph_kind_contracts_match_their_producers_test.go +121 -0
  47. package/internal/graph/node_modifiers_emit_union_strings_test.go +1 -1
  48. package/internal/graph/provenance.go +11 -1
  49. package/internal/graph/resolve.go +1 -4
  50. package/internal/graph/source.go +23 -0
  51. package/internal/graph/wire_node_ids_share_alias_resolution_test.go +29 -0
  52. package/internal/lspserver/lsp_native_plugin_source.go +5 -6
  53. package/internal/lspserver/lsp_native_resident.go +17 -24
  54. package/lib/TtscService.d.ts +1 -6
  55. package/lib/TtscService.js +2 -2
  56. package/lib/TtscService.js.map +1 -1
  57. package/lib/compiler/internal/captureProcessOutput.d.ts +26 -0
  58. package/lib/compiler/internal/captureProcessOutput.js +92 -0
  59. package/lib/compiler/internal/captureProcessOutput.js.map +1 -0
  60. package/lib/compiler/internal/compileProjectInMemory.js +4 -7
  61. package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
  62. package/lib/compiler/internal/residentCheckProcess.d.ts +0 -2
  63. package/lib/compiler/internal/residentCheckProcess.js +1 -15
  64. package/lib/compiler/internal/residentCheckProcess.js.map +1 -1
  65. package/lib/compiler/internal/residentTransformProcess.d.ts +0 -6
  66. package/lib/compiler/internal/residentTransformProcess.js +0 -25
  67. package/lib/compiler/internal/residentTransformProcess.js.map +1 -1
  68. package/lib/compiler/internal/spawnNative.d.ts +14 -6
  69. package/lib/compiler/internal/spawnNative.js +32 -11
  70. package/lib/compiler/internal/spawnNative.js.map +1 -1
  71. package/lib/compiler/internal/startResidentTransform.d.ts +1 -5
  72. package/lib/compiler/internal/startResidentTransform.js +1 -3
  73. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  74. package/lib/launcher/internal/prepareExecution.d.ts +2 -1
  75. package/lib/launcher/internal/prepareExecution.js +269 -23
  76. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  77. package/lib/launcher/internal/registerRuntimeHooks.js +6 -1
  78. package/lib/launcher/internal/registerRuntimeHooks.js.map +1 -1
  79. package/lib/launcher/internal/runTtscserver.js +4 -9
  80. package/lib/launcher/internal/runTtscserver.js.map +1 -1
  81. package/lib/launcher/internal/runTtsx.js +2 -55
  82. package/lib/launcher/internal/runTtsx.js.map +1 -1
  83. package/lib/launcher/internal/runtimeHooks.d.ts +25 -2
  84. package/lib/launcher/internal/runtimeHooks.js +344 -69
  85. package/lib/launcher/internal/runtimeHooks.js.map +1 -1
  86. package/lib/plugin/internal/buildSourcePlugin.js +31 -3
  87. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  88. package/lib/plugin/internal/descriptorProcessFailure.d.ts +23 -19
  89. package/lib/plugin/internal/descriptorProcessFailure.js +40 -51
  90. package/lib/plugin/internal/descriptorProcessFailure.js.map +1 -1
  91. package/lib/plugin/internal/loadProjectPlugins.d.ts +12 -0
  92. package/lib/plugin/internal/loadProjectPlugins.js +77 -28
  93. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  94. package/package.json +8 -8
  95. package/shim/ast/extra-shim.json +4 -4
  96. package/shim/ast/go.mod +14 -14
  97. package/shim/ast/go.sum +20 -20
  98. package/shim/astnav/go.mod +14 -14
  99. package/shim/astnav/go.sum +20 -20
  100. package/shim/bundled/extra-shim.json +1 -1
  101. package/shim/bundled/go.mod +5 -5
  102. package/shim/checker/extra-shim.json +31 -31
  103. package/shim/checker/go.mod +5 -5
  104. package/shim/compiler/extra-shim.json +4 -4
  105. package/shim/compiler/go.mod +14 -14
  106. package/shim/compiler/go.sum +22 -22
  107. package/shim/core/extra-shim.json +4 -4
  108. package/shim/core/go.mod +5 -5
  109. package/shim/diagnosticwriter/go.mod +5 -5
  110. package/shim/lsp/extra-shim.json +4 -4
  111. package/shim/lsp/go.mod +16 -16
  112. package/shim/lsp/go.sum +26 -26
  113. package/shim/parser/extra-shim.json +4 -4
  114. package/shim/parser/go.mod +5 -5
  115. package/shim/printer/go.mod +14 -14
  116. package/shim/printer/go.sum +22 -22
  117. package/shim/scanner/extra-shim.json +4 -4
  118. package/shim/scanner/go.mod +5 -5
  119. package/shim/transformers/go.mod +14 -14
  120. package/shim/transformers/go.sum +20 -20
  121. package/shim/tsoptions/extra-shim.json +4 -4
  122. package/shim/tsoptions/go.mod +5 -5
  123. package/shim/tspath/extra-shim.json +4 -4
  124. package/shim/tspath/go.mod +5 -5
  125. package/shim/vfs/cachedvfs/extra-shim.json +4 -4
  126. package/shim/vfs/cachedvfs/go.mod +5 -5
  127. package/shim/vfs/extra-shim.json +4 -4
  128. package/shim/vfs/go.mod +5 -5
  129. package/shim/vfs/osvfs/extra-shim.json +4 -4
  130. package/shim/vfs/osvfs/go.mod +5 -5
  131. package/src/TtscService.ts +8 -20
  132. package/src/compiler/internal/captureProcessOutput.ts +96 -0
  133. package/src/compiler/internal/compileProjectInMemory.ts +7 -7
  134. package/src/compiler/internal/residentCheckProcess.ts +1 -21
  135. package/src/compiler/internal/residentTransformProcess.ts +0 -42
  136. package/src/compiler/internal/spawnNative.ts +39 -18
  137. package/src/compiler/internal/startResidentTransform.ts +1 -12
  138. package/src/launcher/internal/prepareExecution.ts +297 -26
  139. package/src/launcher/internal/registerRuntimeHooks.ts +11 -2
  140. package/src/launcher/internal/runTtscserver.ts +4 -9
  141. package/src/launcher/internal/runTtsx.ts +2 -78
  142. package/src/launcher/internal/runtimeHooks.ts +385 -82
  143. package/src/plugin/internal/buildSourcePlugin.ts +36 -3
  144. package/src/plugin/internal/descriptorProcessFailure.ts +34 -65
  145. package/src/plugin/internal/loadProjectPlugins.ts +78 -39
  146. package/tools/gen_shims/go.mod +14 -14
  147. package/tools/gen_shims/go.sum +12 -12
  148. package/tools/shim_audit/go.mod +10 -10
  149. package/tools/shim_audit/go.sum +8 -8
@@ -0,0 +1,1390 @@
1
+ package main
2
+
3
+ import (
4
+ "crypto/sha256"
5
+ "encoding/hex"
6
+ "encoding/json"
7
+ "fmt"
8
+ "maps"
9
+ "path/filepath"
10
+ "sort"
11
+ "strings"
12
+ "time"
13
+
14
+ shimast "github.com/microsoft/typescript-go/shim/ast"
15
+ shimcompiler "github.com/microsoft/typescript-go/shim/compiler"
16
+ shimtspath "github.com/microsoft/typescript-go/shim/tspath"
17
+
18
+ "github.com/samchon/ttsc/packages/ttsc/driver"
19
+ "github.com/samchon/ttsc/packages/ttsc/internal/graph"
20
+ )
21
+
22
+ const graphSnapshotProtocolVersion = 1
23
+
24
+ // serveGraphSnapshot is one atomic native shard transaction. Manifest is the
25
+ // complete committed generation; Upserts and Deletes are the delta from
26
+ // BaseGeneration. The consumer validates and normalizes it into its own Graph
27
+ // Snapshot Protocol before publication.
28
+ type serveGraphSnapshot struct {
29
+ ProtocolVersion int `json:"protocolVersion"`
30
+ SchemaVersion int `json:"schemaVersion"`
31
+ Project string `json:"project"`
32
+ Tsconfig string `json:"tsconfig"`
33
+ Producer graph.Producer `json:"producer"`
34
+ Capabilities []string `json:"capabilities"`
35
+ Universe graph.Universe `json:"universe"`
36
+ Sequence int `json:"sequence"`
37
+ Generation string `json:"generation"`
38
+ BaseSequence int `json:"baseSequence,omitempty"`
39
+ BaseGeneration string `json:"baseGeneration,omitempty"`
40
+ Upserts []serveGraphShardUpsert `json:"upserts"`
41
+ Deletes []string `json:"deletes"`
42
+ Manifest []serveGraphShardReference `json:"manifest"`
43
+ }
44
+
45
+ type serveGraphShardUpsert struct {
46
+ Digest string `json:"digest"`
47
+ Shard serveGraphShard `json:"shard"`
48
+ }
49
+
50
+ type serveGraphShardReference struct {
51
+ Key string `json:"key"`
52
+ Digest string `json:"digest"`
53
+ }
54
+
55
+ // serveGraphShard owns one compiler source's outgoing facts or one config
56
+ // input. Source and Config are both absent only on the external-node and
57
+ // fileless-diagnostic metadata shards.
58
+ type serveGraphShard struct {
59
+ Key string `json:"key"`
60
+ Source *graph.SourceDigest `json:"source,omitempty"`
61
+ Config *graph.FileDigest `json:"config,omitempty"`
62
+ Nodes []graph.DumpNode `json:"nodes"`
63
+ Edges []graph.DumpEdge `json:"edges"`
64
+ Diagnostics []graph.Diagnostic `json:"diagnostics"`
65
+ }
66
+
67
+ type committedServeGraphShard struct {
68
+ digest string
69
+ shard serveGraphShard
70
+ }
71
+
72
+ type serveGraphStore struct {
73
+ sequence int
74
+ generation string
75
+ shards map[string]committedServeGraphShard
76
+ nodes map[string]*graph.Node
77
+ provenance graph.Provenance
78
+ wireProvenance graph.Provenance
79
+ wireSources map[string]string
80
+ identity serveGraphIdentity
81
+ resolutionDigests map[string]string
82
+ sourceKeys map[string]string
83
+ dumpNodeFiles map[string]string
84
+ reverseDependencies map[string][]string
85
+ sourceExternal map[string]map[string]bool
86
+ externalNodes map[string]graph.DumpNode
87
+ externalReferences map[string]int
88
+ externalNodeWireIDs map[string]string
89
+ implementationSources map[string]map[string]bool
90
+ nodeOwners map[string]string
91
+ incomingEdges map[string]map[string]int
92
+ // extractedFiles records the exact authored closure read for this generation.
93
+ // It supports phase/closure evidence without retaining an older generation.
94
+ extractedFiles []string
95
+ }
96
+
97
+ type serveGraphIdentity struct {
98
+ project string
99
+ target string
100
+ universe string
101
+ producer graph.Producer
102
+ }
103
+
104
+ func (s *graphSession) SnapshotShards() (*serveGraphSnapshot, string, bool, error) {
105
+ snapshot, mode, changed, _, _, err := s.snapshotShardsWithTiming()
106
+ return snapshot, mode, changed, err
107
+ }
108
+
109
+ func (s *graphSession) snapshotShardsWithTiming() (*serveGraphSnapshot, string, bool, time.Duration, time.Duration, error) {
110
+ semanticStarted := time.Now()
111
+ change, err := s.nextChange(true)
112
+ semanticDuration := time.Since(semanticStarted)
113
+ if err != nil {
114
+ return nil, "", false, semanticDuration, 0, err
115
+ }
116
+ if change == nil {
117
+ return nil, serveModeUnchanged, false, semanticDuration, 0, nil
118
+ }
119
+
120
+ exportStarted := time.Now()
121
+ snapshot, store, err := s.buildShardSnapshot(change)
122
+ exportDuration := time.Since(exportStarted)
123
+ if err != nil {
124
+ s.pending = change
125
+ return nil, "", false, semanticDuration, exportDuration, err
126
+ }
127
+ s.graphStore = store
128
+ s.pending = nil
129
+ return snapshot, change.mode, true, semanticDuration, exportDuration, nil
130
+ }
131
+
132
+ func (s *graphSession) buildShardSnapshot(change *graphChange) (*serveGraphSnapshot, *serveGraphStore, error) {
133
+ if change.full || s.graphStore == nil {
134
+ return s.buildFullShardSnapshot()
135
+ }
136
+ return s.buildIncrementalShardSnapshot(change)
137
+ }
138
+
139
+ func (s *graphSession) buildFullShardSnapshot() (*serveGraphSnapshot, *serveGraphStore, error) {
140
+ program := s.compiler.Program()
141
+ built := graph.Build(program)
142
+ texts := graph.SourceTexts(program)
143
+ provenance := graph.NewProvenance(
144
+ serveProducer(),
145
+ fullSnapshotCapabilities,
146
+ s.configDigests,
147
+ s.roots,
148
+ texts,
149
+ s.diskDigests,
150
+ )
151
+ identity, wireProvenance, wireSources, err := newServeGraphIdentity(s.cwd, s.tsconfig, provenance)
152
+ if err != nil {
153
+ return nil, nil, err
154
+ }
155
+ facts, err := graph.NewDumpFacts(
156
+ built,
157
+ s.cwd,
158
+ graph.GitIgnoredFiles(s.cwd, built),
159
+ texts,
160
+ )
161
+ if err != nil {
162
+ return nil, nil, err
163
+ }
164
+ resolutionDigests, err := serveGraphResolutionDigests(program, s.cwd)
165
+ if err != nil {
166
+ return nil, nil, err
167
+ }
168
+ shards, sourceKeys, dumpNodeFiles, externalNodes, sourceExternal, err := partitionServeGraphFacts(
169
+ identity,
170
+ provenance,
171
+ facts,
172
+ graph.NewDiagnostics(program),
173
+ nil,
174
+ nil,
175
+ resolutionDigests,
176
+ )
177
+ if err != nil {
178
+ return nil, nil, err
179
+ }
180
+ externalReferences := map[string]int{}
181
+ for _, targets := range sourceExternal {
182
+ for id := range targets {
183
+ externalReferences[id]++
184
+ }
185
+ }
186
+ if err := installExternalShard(shards, identity, externalNodes, externalReferences); err != nil {
187
+ return nil, nil, err
188
+ }
189
+ externalNodeWireIDs, err := serveGraphExternalNodeWireIDs(s.cwd, built.Nodes)
190
+ if err != nil {
191
+ return nil, nil, err
192
+ }
193
+ store := &serveGraphStore{
194
+ shards: map[string]committedServeGraphShard{},
195
+ nodes: maps.Clone(built.Nodes),
196
+ provenance: provenance,
197
+ wireProvenance: wireProvenance,
198
+ wireSources: wireSources,
199
+ identity: identity,
200
+ resolutionDigests: resolutionDigests,
201
+ sourceKeys: sourceKeys,
202
+ dumpNodeFiles: dumpNodeFiles,
203
+ reverseDependencies: reverseGraphDependencies(program),
204
+ sourceExternal: sourceExternal,
205
+ externalNodes: externalNodes,
206
+ externalReferences: externalReferences,
207
+ externalNodeWireIDs: externalNodeWireIDs,
208
+ implementationSources: cloneServeGraphStringSets(built.ImplementationSources),
209
+ extractedFiles: sortedSelectedFiles(authoredGraphFiles(program)),
210
+ }
211
+ snapshot, committed, nodeOwners, incomingEdges, err := commitServeGraphSnapshot(
212
+ identity,
213
+ wireProvenance,
214
+ s.graphStore,
215
+ shards,
216
+ nil,
217
+ true,
218
+ )
219
+ if err != nil {
220
+ return nil, nil, err
221
+ }
222
+ store.sequence = snapshot.Sequence
223
+ store.generation = snapshot.Generation
224
+ store.shards = committed
225
+ store.nodeOwners = nodeOwners
226
+ store.incomingEdges = incomingEdges
227
+ return snapshot, store, nil
228
+ }
229
+
230
+ func (s *graphSession) buildIncrementalShardSnapshot(change *graphChange) (*serveGraphSnapshot, *serveGraphStore, error) {
231
+ prior := s.graphStore
232
+ program := s.compiler.Program()
233
+ selected := invalidatedGraphFiles(program, prior.reverseDependencies, change.files, change.publicFiles)
234
+ if len(selected) == 0 {
235
+ // A changed declaration or virtual input outside the authored graph can
236
+ // alter external endpoints and global types. Rebuild when no exact source
237
+ // owner can be established instead of publishing an empty semantic delta.
238
+ return s.buildCompleteShardFallback(change)
239
+ }
240
+
241
+ // Re-export edges can stamp a declaration owned by a forward dependency.
242
+ // Include both the prior and next targets in the same replacement set, then
243
+ // rebuild once more so every selected declaration is fresh before commit.
244
+ addPriorExportTargets(selected, prior)
245
+ expandPriorImplementationSources(selected, prior)
246
+ var partial *graph.Graph
247
+ for {
248
+ files := sortedSelectedFiles(selected)
249
+ partial = graph.BuildFiles(program, files, prior.nodes)
250
+ expanded := false
251
+ for id := range partial.ExportedTargets {
252
+ file := graph.NodeFile(id)
253
+ if file != "" && !selected[file] && graphSourceFile(program, file) != nil {
254
+ selected[file] = true
255
+ expanded = true
256
+ }
257
+ }
258
+ for id := range partial.ImplementationSources {
259
+ file := graph.NodeFile(id)
260
+ if file != "" && !selected[file] && graphSourceFile(program, file) != nil {
261
+ selected[file] = true
262
+ expanded = true
263
+ }
264
+ }
265
+ if !expanded {
266
+ break
267
+ }
268
+ expandPriorImplementationSources(selected, prior)
269
+ }
270
+
271
+ projectionFiles := maps.Clone(selected)
272
+ for _, node := range partial.Nodes {
273
+ projectionFiles[node.File] = true
274
+ if node.ImplementationFile != "" {
275
+ projectionFiles[node.ImplementationFile] = true
276
+ }
277
+ }
278
+ for _, sources := range partial.ImplementationSources {
279
+ for file := range sources {
280
+ projectionFiles[file] = true
281
+ }
282
+ }
283
+ texts := graph.SourceTextsForFiles(program, sortedSelectedFiles(projectionFiles))
284
+ provenance, wireProvenance, ok := advanceServeGraphProvenance(
285
+ prior.provenance,
286
+ prior.wireProvenance,
287
+ prior.wireSources,
288
+ program,
289
+ change.files,
290
+ s.diskDigests,
291
+ )
292
+ if !ok {
293
+ return s.buildCompleteShardFallback(change)
294
+ }
295
+ identity := prior.identity
296
+ facts, err := graph.NewDumpFacts(
297
+ partial,
298
+ s.cwd,
299
+ graph.GitIgnoredFiles(s.cwd, partial),
300
+ texts,
301
+ )
302
+ if err != nil {
303
+ return nil, nil, err
304
+ }
305
+ selectedFiles := sortedSelectedFiles(selected)
306
+ selectedSources := make([]*shimast.SourceFile, 0, len(selectedFiles))
307
+ for _, file := range selectedFiles {
308
+ source := graphSourceFile(program, file)
309
+ if source == nil {
310
+ return s.buildCompleteShardFallback(change)
311
+ }
312
+ selectedSources = append(selectedSources, source)
313
+ }
314
+ replacements, replacementSourceKeys, replacementDumpNodeFiles, discoveredExternal, replacementExternal, err := partitionServeGraphFacts(
315
+ identity,
316
+ provenance,
317
+ facts,
318
+ graph.NewDiagnosticsForFiles(program, selectedSources),
319
+ selected,
320
+ prior.externalNodes,
321
+ prior.resolutionDigests,
322
+ )
323
+ if err != nil {
324
+ return nil, nil, err
325
+ }
326
+
327
+ nextShards := maps.Clone(prior.shards)
328
+ nextSourceExternal := maps.Clone(prior.sourceExternal)
329
+ nextExternalNodes := maps.Clone(prior.externalNodes)
330
+ nextExternalReferences := maps.Clone(prior.externalReferences)
331
+ nextSourceKeys := maps.Clone(prior.sourceKeys)
332
+ nextDumpNodeFiles := maps.Clone(prior.dumpNodeFiles)
333
+ for file := range selected {
334
+ key := nextSourceKeys[file]
335
+ if old := nextSourceExternal[key]; old != nil {
336
+ for id := range old {
337
+ nextExternalReferences[id]--
338
+ }
339
+ }
340
+ delete(nextSourceExternal, key)
341
+ delete(nextShards, key)
342
+ delete(nextSourceKeys, file)
343
+ }
344
+ for id, file := range nextDumpNodeFiles {
345
+ if selected[file] {
346
+ delete(nextDumpNodeFiles, id)
347
+ }
348
+ }
349
+ for file, key := range replacementSourceKeys {
350
+ nextSourceKeys[file] = key
351
+ }
352
+ for id, file := range replacementDumpNodeFiles {
353
+ nextDumpNodeFiles[id] = file
354
+ }
355
+ for key, targets := range replacementExternal {
356
+ nextSourceExternal[key] = targets
357
+ for id := range targets {
358
+ nextExternalReferences[id]++
359
+ if node, ok := discoveredExternal[id]; ok {
360
+ nextExternalNodes[id] = node
361
+ } else if node, ok := prior.externalNodes[id]; ok {
362
+ nextExternalNodes[id] = node
363
+ }
364
+ }
365
+ }
366
+ for id, count := range nextExternalReferences {
367
+ if count <= 0 {
368
+ delete(nextExternalReferences, id)
369
+ delete(nextExternalNodes, id)
370
+ }
371
+ }
372
+
373
+ nextRaw := rawServeShards(nextShards)
374
+ for key, shard := range replacements {
375
+ nextRaw[key] = shard
376
+ }
377
+ if err := installExternalShard(nextRaw, identity, nextExternalNodes, nextExternalReferences); err != nil {
378
+ return nil, nil, err
379
+ }
380
+ dirty := make(map[string]bool, len(replacements)+1)
381
+ for key := range replacements {
382
+ dirty[key] = true
383
+ }
384
+ dirty[externalServeGraphShardKey(identity)] = true
385
+
386
+ nextNodes := maps.Clone(prior.nodes)
387
+ for id, node := range nextNodes {
388
+ if selected[node.File] {
389
+ delete(nextNodes, id)
390
+ }
391
+ }
392
+
393
+ nextImplementationSources := cloneServeGraphStringSets(prior.implementationSources)
394
+ for id := range nextImplementationSources {
395
+ if selected[graph.NodeFile(id)] {
396
+ delete(nextImplementationSources, id)
397
+ }
398
+ }
399
+ for id, sources := range partial.ImplementationSources {
400
+ nextImplementationSources[id] = maps.Clone(sources)
401
+ }
402
+ for id, node := range partial.Nodes {
403
+ nextNodes[id] = node
404
+ }
405
+ nextExternalNodeWireIDs := maps.Clone(prior.externalNodeWireIDs)
406
+ changedExternalNodeWireIDs, err := serveGraphExternalNodeWireIDs(s.cwd, partial.Nodes)
407
+ if err != nil {
408
+ return nil, nil, err
409
+ }
410
+ for id, wireID := range changedExternalNodeWireIDs {
411
+ nextExternalNodeWireIDs[id] = wireID
412
+ }
413
+ for id, node := range nextNodes {
414
+ if node.External {
415
+ wireID, exists := nextExternalNodeWireIDs[id]
416
+ if !exists {
417
+ return nil, nil, fmt.Errorf("ttscgraph: external node %s has no wire identity", id)
418
+ }
419
+ if nextExternalReferences[wireID] <= 0 {
420
+ delete(nextNodes, id)
421
+ delete(nextExternalNodeWireIDs, id)
422
+ }
423
+ }
424
+ }
425
+
426
+ snapshot, committed, nodeOwners, incomingEdges, err := commitServeGraphSnapshot(
427
+ identity,
428
+ wireProvenance,
429
+ prior,
430
+ nextRaw,
431
+ dirty,
432
+ false,
433
+ )
434
+ if err != nil {
435
+ return nil, nil, err
436
+ }
437
+ store := &serveGraphStore{
438
+ sequence: snapshot.Sequence,
439
+ generation: snapshot.Generation,
440
+ shards: committed,
441
+ nodes: nextNodes,
442
+ provenance: provenance,
443
+ wireProvenance: wireProvenance,
444
+ wireSources: maps.Clone(prior.wireSources),
445
+ identity: identity,
446
+ resolutionDigests: prior.resolutionDigests,
447
+ sourceKeys: nextSourceKeys,
448
+ dumpNodeFiles: nextDumpNodeFiles,
449
+ reverseDependencies: prior.reverseDependencies,
450
+ sourceExternal: nextSourceExternal,
451
+ externalNodes: nextExternalNodes,
452
+ externalReferences: nextExternalReferences,
453
+ externalNodeWireIDs: nextExternalNodeWireIDs,
454
+ implementationSources: nextImplementationSources,
455
+ nodeOwners: nodeOwners,
456
+ incomingEdges: incomingEdges,
457
+ extractedFiles: append([]string{}, selectedFiles...),
458
+ }
459
+ return snapshot, store, nil
460
+ }
461
+
462
+ func (s *graphSession) buildCompleteShardFallback(change *graphChange) (*serveGraphSnapshot, *serveGraphStore, error) {
463
+ change.mode = serveModeRebuild
464
+ change.full = true
465
+ return s.buildFullShardSnapshot()
466
+ }
467
+
468
+ func commitServeGraphSnapshot(
469
+ identity serveGraphIdentity,
470
+ provenance graph.Provenance,
471
+ prior *serveGraphStore,
472
+ shards map[string]serveGraphShard,
473
+ dirty map[string]bool,
474
+ forceAll bool,
475
+ ) (*serveGraphSnapshot, map[string]committedServeGraphShard, map[string]string, map[string]map[string]int, error) {
476
+ manifest := make([]serveGraphShardReference, 0, len(shards))
477
+ current := make(map[string]committedServeGraphShard, len(shards))
478
+ keys := make([]string, 0, len(shards))
479
+ for key := range shards {
480
+ keys = append(keys, key)
481
+ }
482
+ sort.Strings(keys)
483
+ for _, key := range keys {
484
+ if !forceAll && prior != nil && !dirty[key] {
485
+ committed, exists := prior.shards[key]
486
+ if !exists {
487
+ return nil, nil, nil, nil, fmt.Errorf("ttscgraph: new graph shard %s was not marked dirty", key)
488
+ }
489
+ current[key] = committed
490
+ manifest = append(manifest, serveGraphShardReference{Key: key, Digest: committed.digest})
491
+ continue
492
+ }
493
+ shard := shards[key]
494
+ normalizeServeGraphShard(&shard)
495
+ digest, err := serveGraphShardDigest(shard)
496
+ if err != nil {
497
+ return nil, nil, nil, nil, err
498
+ }
499
+ current[key] = committedServeGraphShard{digest: digest, shard: shard}
500
+ manifest = append(manifest, serveGraphShardReference{Key: key, Digest: digest})
501
+ }
502
+ var nodeOwners map[string]string
503
+ var incomingEdges map[string]map[string]int
504
+ var err error
505
+ if forceAll || prior == nil {
506
+ nodeOwners, incomingEdges, err = indexServeGraphShards(current, provenance.Universe)
507
+ } else {
508
+ nodeOwners, incomingEdges, err = updateServeGraphShardIndex(prior, current, dirty, provenance.Universe)
509
+ }
510
+ if err != nil {
511
+ return nil, nil, nil, nil, err
512
+ }
513
+ generation, err := digestJSON(struct {
514
+ Tsconfig string `json:"tsconfig"`
515
+ Producer graph.Producer `json:"producer"`
516
+ Capabilities []string `json:"capabilities"`
517
+ Universe graph.Universe `json:"universe"`
518
+ Manifest []serveGraphShardReference `json:"manifest"`
519
+ }{
520
+ Tsconfig: identity.target,
521
+ Producer: provenance.Producer,
522
+ Capabilities: provenance.Capabilities,
523
+ Universe: provenance.Universe,
524
+ Manifest: manifest,
525
+ })
526
+ if err != nil {
527
+ return nil, nil, nil, nil, err
528
+ }
529
+ sequence := 1
530
+ if prior != nil {
531
+ sequence = prior.sequence + 1
532
+ }
533
+ snapshot := &serveGraphSnapshot{
534
+ ProtocolVersion: graphSnapshotProtocolVersion,
535
+ SchemaVersion: graph.DumpSchemaVersion,
536
+ Project: identity.project,
537
+ Tsconfig: identity.target,
538
+ Producer: provenance.Producer,
539
+ Capabilities: append([]string{}, provenance.Capabilities...),
540
+ Universe: provenance.Universe,
541
+ Sequence: sequence,
542
+ Generation: generation,
543
+ Upserts: []serveGraphShardUpsert{},
544
+ Deletes: []string{},
545
+ Manifest: manifest,
546
+ }
547
+ if prior != nil {
548
+ snapshot.BaseSequence = prior.sequence
549
+ snapshot.BaseGeneration = prior.generation
550
+ for key := range prior.shards {
551
+ if _, exists := current[key]; !exists {
552
+ snapshot.Deletes = append(snapshot.Deletes, key)
553
+ }
554
+ }
555
+ sort.Strings(snapshot.Deletes)
556
+ }
557
+ for _, key := range keys {
558
+ value := current[key]
559
+ if forceAll || prior == nil || prior.shards[key].digest != value.digest {
560
+ snapshot.Upserts = append(snapshot.Upserts, serveGraphShardUpsert{
561
+ Digest: value.digest,
562
+ Shard: value.shard,
563
+ })
564
+ }
565
+ }
566
+ return snapshot, current, nodeOwners, incomingEdges, nil
567
+ }
568
+
569
+ func validateServeGraphShardHeaders(shards map[string]committedServeGraphShard, universe graph.Universe) error {
570
+ sources := map[string]string{}
571
+ configs := map[string]string{}
572
+ for key, committed := range shards {
573
+ if key == "" || strings.ContainsRune(key, '\x00') || committed.shard.Key != key {
574
+ return fmt.Errorf("ttscgraph: invalid graph shard identity: %q", key)
575
+ }
576
+ if committed.shard.Source != nil && committed.shard.Config != nil {
577
+ return fmt.Errorf("ttscgraph: shard %s claims both a source and a config input", key)
578
+ }
579
+ if committed.shard.Config != nil && (len(committed.shard.Nodes) != 0 || len(committed.shard.Edges) != 0) {
580
+ return fmt.Errorf("ttscgraph: config shard %s unexpectedly owns graph facts", key)
581
+ }
582
+ if committed.shard.Config != nil {
583
+ if _, exists := configs[committed.shard.Config.File]; exists {
584
+ return fmt.Errorf("ttscgraph: config input %s has more than one shard", committed.shard.Config.File)
585
+ }
586
+ configs[committed.shard.Config.File] = committed.shard.Config.Digest
587
+ }
588
+ if committed.shard.Source != nil {
589
+ if owner, exists := sources[committed.shard.Source.File]; exists {
590
+ return fmt.Errorf("ttscgraph: source input %s is owned by both %s and %s", committed.shard.Source.File, owner, key)
591
+ }
592
+ sources[committed.shard.Source.File] = key
593
+ }
594
+ }
595
+ if len(configs) != len(universe.Configs) {
596
+ return fmt.Errorf("ttscgraph: config shards do not cover the build universe")
597
+ }
598
+ for _, config := range universe.Configs {
599
+ if configs[config.File] != config.Digest {
600
+ return fmt.Errorf("ttscgraph: config shard disagrees with universe input %s", config.File)
601
+ }
602
+ }
603
+ return nil
604
+ }
605
+
606
+ func indexServeGraphShards(
607
+ shards map[string]committedServeGraphShard,
608
+ universe graph.Universe,
609
+ ) (map[string]string, map[string]map[string]int, error) {
610
+ if err := validateServeGraphShardHeaders(shards, universe); err != nil {
611
+ return nil, nil, err
612
+ }
613
+ owners := map[string]string{}
614
+ incoming := map[string]map[string]int{}
615
+ for key, committed := range shards {
616
+ if err := validateServeGraphShardContents(key, committed.shard); err != nil {
617
+ return nil, nil, err
618
+ }
619
+ for _, node := range committed.shard.Nodes {
620
+ if owner, exists := owners[node.ID]; exists {
621
+ return nil, nil, fmt.Errorf("ttscgraph: node %s is owned by both %s and %s", node.ID, owner, key)
622
+ }
623
+ owners[node.ID] = key
624
+ }
625
+ }
626
+ for key, committed := range shards {
627
+ for _, edge := range committed.shard.Edges {
628
+ if owners[edge.From] != key {
629
+ return nil, nil, fmt.Errorf("ttscgraph: shard %s does not own edge source %s", key, edge.From)
630
+ }
631
+ if _, exists := owners[edge.To]; !exists {
632
+ return nil, nil, fmt.Errorf("ttscgraph: edge target is absent from committed shards: %s", edge.To)
633
+ }
634
+ addServeGraphIncomingEdge(incoming, edge.To, key)
635
+ }
636
+ }
637
+ return owners, incoming, nil
638
+ }
639
+
640
+ func updateServeGraphShardIndex(
641
+ prior *serveGraphStore,
642
+ current map[string]committedServeGraphShard,
643
+ dirty map[string]bool,
644
+ universe graph.Universe,
645
+ ) (map[string]string, map[string]map[string]int, error) {
646
+ if err := validateServeGraphShardHeaders(current, universe); err != nil {
647
+ return nil, nil, err
648
+ }
649
+ owners := maps.Clone(prior.nodeOwners)
650
+ incoming := cloneServeGraphIncomingEdges(prior.incomingEdges)
651
+ touched := maps.Clone(dirty)
652
+ for key := range prior.shards {
653
+ if _, exists := current[key]; !exists {
654
+ touched[key] = true
655
+ }
656
+ }
657
+ removedNodes := map[string]bool{}
658
+ for key := range touched {
659
+ committed, exists := prior.shards[key]
660
+ if !exists {
661
+ continue
662
+ }
663
+ for _, edge := range committed.shard.Edges {
664
+ removeServeGraphIncomingEdge(incoming, edge.To, key)
665
+ }
666
+ for _, node := range committed.shard.Nodes {
667
+ if owners[node.ID] == key {
668
+ delete(owners, node.ID)
669
+ removedNodes[node.ID] = true
670
+ }
671
+ }
672
+ }
673
+ for key := range dirty {
674
+ committed, exists := current[key]
675
+ if !exists {
676
+ return nil, nil, fmt.Errorf("ttscgraph: dirty graph shard %s is absent from the next generation", key)
677
+ }
678
+ if err := validateServeGraphShardContents(key, committed.shard); err != nil {
679
+ return nil, nil, err
680
+ }
681
+ for _, node := range committed.shard.Nodes {
682
+ if owner, exists := owners[node.ID]; exists {
683
+ return nil, nil, fmt.Errorf("ttscgraph: node %s is owned by both %s and %s", node.ID, owner, key)
684
+ }
685
+ owners[node.ID] = key
686
+ }
687
+ }
688
+ for key := range dirty {
689
+ for _, edge := range current[key].shard.Edges {
690
+ if owners[edge.From] != key {
691
+ return nil, nil, fmt.Errorf("ttscgraph: shard %s does not own edge source %s", key, edge.From)
692
+ }
693
+ if _, exists := owners[edge.To]; !exists {
694
+ return nil, nil, fmt.Errorf("ttscgraph: edge target is absent from committed shards: %s", edge.To)
695
+ }
696
+ addServeGraphIncomingEdge(incoming, edge.To, key)
697
+ }
698
+ }
699
+ for id := range removedNodes {
700
+ if _, exists := owners[id]; exists {
701
+ continue
702
+ }
703
+ if len(incoming[id]) != 0 {
704
+ return nil, nil, fmt.Errorf("ttscgraph: removed node %s is still referenced by an unchanged shard", id)
705
+ }
706
+ delete(incoming, id)
707
+ }
708
+ return owners, incoming, nil
709
+ }
710
+
711
+ func validateServeGraphShardContents(key string, shard serveGraphShard) error {
712
+ if shard.Source != nil {
713
+ for _, node := range shard.Nodes {
714
+ if node.External || node.File != shard.Source.File {
715
+ return fmt.Errorf("ttscgraph: source shard %s owns node %s from %s", key, node.ID, node.File)
716
+ }
717
+ }
718
+ for _, diagnostic := range shard.Diagnostics {
719
+ if diagnostic.File != shard.Source.File {
720
+ return fmt.Errorf("ttscgraph: source shard %s owns diagnostic from %s", key, diagnostic.File)
721
+ }
722
+ }
723
+ return nil
724
+ }
725
+ if len(shard.Edges) != 0 {
726
+ return fmt.Errorf("ttscgraph: non-source shard %s unexpectedly owns edges", key)
727
+ }
728
+ if shard.Config != nil {
729
+ for _, diagnostic := range shard.Diagnostics {
730
+ if diagnostic.File != shard.Config.File {
731
+ return fmt.Errorf("ttscgraph: config shard %s owns diagnostic from %s", key, diagnostic.File)
732
+ }
733
+ }
734
+ return nil
735
+ }
736
+ for _, node := range shard.Nodes {
737
+ if !node.External {
738
+ return fmt.Errorf("ttscgraph: metadata shard %s owns authored node %s", key, node.ID)
739
+ }
740
+ }
741
+ for _, diagnostic := range shard.Diagnostics {
742
+ if diagnostic.File != "" {
743
+ return fmt.Errorf("ttscgraph: metadata shard %s owns diagnostic from %s", key, diagnostic.File)
744
+ }
745
+ }
746
+ return nil
747
+ }
748
+
749
+ func cloneServeGraphIncomingEdges(input map[string]map[string]int) map[string]map[string]int {
750
+ cloned := make(map[string]map[string]int, len(input))
751
+ for id, owners := range input {
752
+ cloned[id] = maps.Clone(owners)
753
+ }
754
+ return cloned
755
+ }
756
+
757
+ func cloneServeGraphStringSets(input map[string]map[string]bool) map[string]map[string]bool {
758
+ cloned := make(map[string]map[string]bool, len(input))
759
+ for key, values := range input {
760
+ cloned[key] = maps.Clone(values)
761
+ }
762
+ return cloned
763
+ }
764
+
765
+ func serveGraphExternalNodeWireIDs(project string, nodes map[string]*graph.Node) (map[string]string, error) {
766
+ ids := []string{}
767
+ for id, node := range nodes {
768
+ if node.External {
769
+ ids = append(ids, id)
770
+ }
771
+ }
772
+ sort.Strings(ids)
773
+ return graph.WireNodeIDs(project, ids)
774
+ }
775
+
776
+ func addServeGraphIncomingEdge(incoming map[string]map[string]int, target, owner string) {
777
+ owners := incoming[target]
778
+ if owners == nil {
779
+ owners = map[string]int{}
780
+ incoming[target] = owners
781
+ }
782
+ owners[owner]++
783
+ }
784
+
785
+ func removeServeGraphIncomingEdge(incoming map[string]map[string]int, target, owner string) {
786
+ owners := incoming[target]
787
+ if owners == nil {
788
+ return
789
+ }
790
+ if owners[owner] <= 1 {
791
+ delete(owners, owner)
792
+ } else {
793
+ owners[owner]--
794
+ }
795
+ if len(owners) == 0 {
796
+ delete(incoming, target)
797
+ }
798
+ }
799
+
800
+ func partitionServeGraphFacts(
801
+ identity serveGraphIdentity,
802
+ provenance graph.Provenance,
803
+ facts graph.DumpFacts,
804
+ diagnostics []graph.Diagnostic,
805
+ selected map[string]bool,
806
+ knownExternal map[string]graph.DumpNode,
807
+ resolutionDigests map[string]string,
808
+ ) (
809
+ map[string]serveGraphShard,
810
+ map[string]string,
811
+ map[string]string,
812
+ map[string]graph.DumpNode,
813
+ map[string]map[string]bool,
814
+ error,
815
+ ) {
816
+ shards := map[string]serveGraphShard{}
817
+ sourceKeys := map[string]string{}
818
+ relativeToPhysical := map[string]string{}
819
+ for _, source := range provenance.Sources {
820
+ if selected != nil && !selected[source.File] {
821
+ continue
822
+ }
823
+ relative, err := serveGraphFile(identity.project, source.File)
824
+ if err != nil {
825
+ return nil, nil, nil, nil, nil, err
826
+ }
827
+ sourceCopy := source
828
+ sourceCopy.File = relative
829
+ key := sourceServeGraphShardKey(identity, relative, source, resolutionDigests[source.File])
830
+ shards[key] = emptyServeGraphShard(key, &sourceCopy)
831
+ sourceKeys[source.File] = key
832
+ relativeToPhysical[relative] = source.File
833
+ }
834
+ if selected == nil {
835
+ for _, config := range provenance.Universe.Configs {
836
+ relative, err := serveGraphFile(identity.project, config.File)
837
+ if err != nil {
838
+ return nil, nil, nil, nil, nil, err
839
+ }
840
+ configCopy := config
841
+ configCopy.File = relative
842
+ key := configServeGraphShardKey(identity, relative, config.Digest)
843
+ shard := emptyServeGraphShard(key, nil)
844
+ shard.Config = &configCopy
845
+ shards[key] = shard
846
+ if _, exists := sourceKeys[config.File]; !exists {
847
+ sourceKeys[config.File] = key
848
+ }
849
+ relativeToPhysical[relative] = config.File
850
+ }
851
+ }
852
+ metadataKey := metadataServeGraphShardKey(identity)
853
+ shards[metadataKey] = emptyServeGraphShard(metadataKey, nil)
854
+
855
+ dumpNodeFiles := map[string]string{}
856
+ externalNodes := map[string]graph.DumpNode{}
857
+ for _, node := range facts.Nodes {
858
+ if node.External {
859
+ externalNodes[node.ID] = node
860
+ continue
861
+ }
862
+ physical, ok := relativeToPhysical[node.File]
863
+ if !ok {
864
+ return nil, nil, nil, nil, nil, fmt.Errorf("ttscgraph: node source is absent from shard manifest: %s", node.File)
865
+ }
866
+ dumpNodeFiles[node.ID] = physical
867
+ key := sourceKeys[physical]
868
+ shard := shards[key]
869
+ shard.Nodes = append(shard.Nodes, node)
870
+ shards[key] = shard
871
+ }
872
+ sourceExternal := map[string]map[string]bool{}
873
+ for _, edge := range facts.Edges {
874
+ physical, ok := dumpNodeFiles[edge.From]
875
+ if !ok {
876
+ return nil, nil, nil, nil, nil, fmt.Errorf("ttscgraph: edge source node is absent from shard facts: %s", edge.From)
877
+ }
878
+ key := sourceKeys[physical]
879
+ shard := shards[key]
880
+ shard.Edges = append(shard.Edges, edge)
881
+ shards[key] = shard
882
+ _, discovered := externalNodes[edge.To]
883
+ _, committed := knownExternal[edge.To]
884
+ if discovered || committed {
885
+ targets := sourceExternal[key]
886
+ if targets == nil {
887
+ targets = map[string]bool{}
888
+ sourceExternal[key] = targets
889
+ }
890
+ targets[edge.To] = true
891
+ }
892
+ }
893
+ for _, diagnostic := range diagnostics {
894
+ if diagnostic.File == "" {
895
+ shard := shards[metadataKey]
896
+ shard.Diagnostics = append(shard.Diagnostics, diagnostic)
897
+ shards[metadataKey] = shard
898
+ continue
899
+ }
900
+ key := sourceKeys[diagnostic.File]
901
+ shard, ok := shards[key]
902
+ if !ok {
903
+ if selected != nil {
904
+ // Config and stable program diagnostics are repeated by tsgo's
905
+ // per-file diagnostic API. Their owning shards are outside this
906
+ // replacement set and remain committed unchanged.
907
+ continue
908
+ }
909
+ return nil, nil, nil, nil, nil, fmt.Errorf("ttscgraph: diagnostic source is absent from shard manifest: %s", diagnostic.File)
910
+ }
911
+ normalized := diagnostic
912
+ relative, err := serveGraphFile(identity.project, diagnostic.File)
913
+ if err != nil {
914
+ return nil, nil, nil, nil, nil, err
915
+ }
916
+ normalized.File = relative
917
+ shard.Diagnostics = append(shard.Diagnostics, normalized)
918
+ shards[key] = shard
919
+ }
920
+ return shards, sourceKeys, dumpNodeFiles, externalNodes, sourceExternal, nil
921
+ }
922
+
923
+ func installExternalShard(
924
+ shards map[string]serveGraphShard,
925
+ identity serveGraphIdentity,
926
+ externalNodes map[string]graph.DumpNode,
927
+ references map[string]int,
928
+ ) error {
929
+ key := externalServeGraphShardKey(identity)
930
+ shard := emptyServeGraphShard(key, nil)
931
+ for id, count := range references {
932
+ if count > 0 {
933
+ node, ok := externalNodes[id]
934
+ if !ok {
935
+ return fmt.Errorf("ttscgraph: external edge target is absent from committed endpoints: %s", id)
936
+ }
937
+ shard.Nodes = append(shard.Nodes, node)
938
+ }
939
+ }
940
+ shards[key] = shard
941
+ return nil
942
+ }
943
+
944
+ func normalizeServeGraphShard(shard *serveGraphShard) {
945
+ if shard.Nodes == nil {
946
+ shard.Nodes = []graph.DumpNode{}
947
+ }
948
+ if shard.Edges == nil {
949
+ shard.Edges = []graph.DumpEdge{}
950
+ }
951
+ if shard.Diagnostics == nil {
952
+ shard.Diagnostics = []graph.Diagnostic{}
953
+ }
954
+ sort.Slice(shard.Nodes, func(i, j int) bool { return shard.Nodes[i].ID < shard.Nodes[j].ID })
955
+ sort.Slice(shard.Edges, func(i, j int) bool {
956
+ if shard.Edges[i].From != shard.Edges[j].From {
957
+ return shard.Edges[i].From < shard.Edges[j].From
958
+ }
959
+ if shard.Edges[i].To != shard.Edges[j].To {
960
+ return shard.Edges[i].To < shard.Edges[j].To
961
+ }
962
+ return shard.Edges[i].Kind < shard.Edges[j].Kind
963
+ })
964
+ sort.Slice(shard.Diagnostics, func(i, j int) bool {
965
+ if shard.Diagnostics[i].File != shard.Diagnostics[j].File {
966
+ return shard.Diagnostics[i].File < shard.Diagnostics[j].File
967
+ }
968
+ if shard.Diagnostics[i].Line != shard.Diagnostics[j].Line {
969
+ return shard.Diagnostics[i].Line < shard.Diagnostics[j].Line
970
+ }
971
+ if shard.Diagnostics[i].Column != shard.Diagnostics[j].Column {
972
+ return shard.Diagnostics[i].Column < shard.Diagnostics[j].Column
973
+ }
974
+ return shard.Diagnostics[i].Code < shard.Diagnostics[j].Code
975
+ })
976
+ }
977
+
978
+ func emptyServeGraphShard(key string, source *graph.SourceDigest) serveGraphShard {
979
+ return serveGraphShard{
980
+ Key: key,
981
+ Source: source,
982
+ Nodes: []graph.DumpNode{},
983
+ Edges: []graph.DumpEdge{},
984
+ Diagnostics: []graph.Diagnostic{},
985
+ }
986
+ }
987
+
988
+ func serveGraphShardDigest(shard serveGraphShard) (string, error) {
989
+ return digestJSON(shard)
990
+ }
991
+
992
+ func digestJSON(value any) (string, error) {
993
+ encoded, err := json.Marshal(value)
994
+ if err != nil {
995
+ return "", fmt.Errorf("ttscgraph: encode shard digest: %w", err)
996
+ }
997
+ sum := sha256.Sum256(encoded)
998
+ return hex.EncodeToString(sum[:]), nil
999
+ }
1000
+
1001
+ func newServeGraphIdentity(project, tsconfig string, provenance graph.Provenance) (serveGraphIdentity, graph.Provenance, map[string]string, error) {
1002
+ if !filepath.IsAbs(project) {
1003
+ return serveGraphIdentity{}, graph.Provenance{}, nil, fmt.Errorf("ttscgraph: project root must be absolute: %s", project)
1004
+ }
1005
+ wireProject, err := graph.WireProject(project)
1006
+ if err != nil {
1007
+ return serveGraphIdentity{}, graph.Provenance{}, nil, err
1008
+ }
1009
+ target := tsconfig
1010
+ if !filepath.IsAbs(target) {
1011
+ target = filepath.Join(project, target)
1012
+ }
1013
+ target, err = serveGraphFile(project, filepath.Clean(target))
1014
+ if err != nil {
1015
+ return serveGraphIdentity{}, graph.Provenance{}, nil, err
1016
+ }
1017
+ normalized, wireSources, err := normalizeServeGraphProvenance(project, provenance)
1018
+ if err != nil {
1019
+ return serveGraphIdentity{}, graph.Provenance{}, nil, err
1020
+ }
1021
+ universe, err := digestJSON(normalized.Universe)
1022
+ if err != nil {
1023
+ return serveGraphIdentity{}, graph.Provenance{}, nil, err
1024
+ }
1025
+ return serveGraphIdentity{
1026
+ project: wireProject,
1027
+ target: target,
1028
+ universe: universe,
1029
+ producer: provenance.Producer,
1030
+ }, normalized, wireSources, nil
1031
+ }
1032
+
1033
+ func normalizeServeGraphProvenance(project string, provenance graph.Provenance) (graph.Provenance, map[string]string, error) {
1034
+ normalized := provenance
1035
+ normalized.Capabilities = append([]string{}, provenance.Capabilities...)
1036
+ normalized.Sources = make([]graph.SourceDigest, 0, len(provenance.Sources))
1037
+ wireSources := make(map[string]string, len(provenance.Sources))
1038
+ sourceOwners := map[string]string{}
1039
+ for _, source := range provenance.Sources {
1040
+ file, err := serveGraphFile(project, source.File)
1041
+ if err != nil {
1042
+ return graph.Provenance{}, nil, err
1043
+ }
1044
+ if previous, exists := sourceOwners[file]; exists {
1045
+ return graph.Provenance{}, nil, fmt.Errorf("ttscgraph: source paths %q and %q collide at wire identity %q", previous, source.File, file)
1046
+ }
1047
+ sourceOwners[file] = source.File
1048
+ wireSources[source.File] = file
1049
+ normalized.Sources = append(normalized.Sources, graph.SourceDigest{
1050
+ File: file,
1051
+ Checker: source.Checker,
1052
+ Disk: source.Disk,
1053
+ })
1054
+ }
1055
+ normalized.Universe.Configs = make([]graph.FileDigest, 0, len(provenance.Universe.Configs))
1056
+ configOwners := map[string]string{}
1057
+ for _, config := range provenance.Universe.Configs {
1058
+ file, err := serveGraphFile(project, config.File)
1059
+ if err != nil {
1060
+ return graph.Provenance{}, nil, err
1061
+ }
1062
+ if previous, exists := configOwners[file]; exists {
1063
+ return graph.Provenance{}, nil, fmt.Errorf("ttscgraph: config paths %q and %q collide at wire identity %q", previous, config.File, file)
1064
+ }
1065
+ configOwners[file] = config.File
1066
+ normalized.Universe.Configs = append(normalized.Universe.Configs, graph.FileDigest{File: file, Digest: config.Digest})
1067
+ }
1068
+ normalized.Universe.Roots = make([]graph.RootFile, 0, len(provenance.Universe.Roots))
1069
+ for _, root := range provenance.Universe.Roots {
1070
+ config, err := serveGraphFile(project, root.Config)
1071
+ if err != nil {
1072
+ return graph.Provenance{}, nil, err
1073
+ }
1074
+ file, err := serveGraphFile(project, root.File)
1075
+ if err != nil {
1076
+ return graph.Provenance{}, nil, err
1077
+ }
1078
+ normalized.Universe.Roots = append(normalized.Universe.Roots, graph.RootFile{Config: config, File: file})
1079
+ }
1080
+ sort.Slice(normalized.Sources, func(i, j int) bool { return normalized.Sources[i].File < normalized.Sources[j].File })
1081
+ sort.Slice(normalized.Universe.Configs, func(i, j int) bool {
1082
+ return normalized.Universe.Configs[i].File < normalized.Universe.Configs[j].File
1083
+ })
1084
+ sort.Slice(normalized.Universe.Roots, func(i, j int) bool {
1085
+ if normalized.Universe.Roots[i].Config != normalized.Universe.Roots[j].Config {
1086
+ return normalized.Universe.Roots[i].Config < normalized.Universe.Roots[j].Config
1087
+ }
1088
+ return normalized.Universe.Roots[i].File < normalized.Universe.Roots[j].File
1089
+ })
1090
+ return normalized, wireSources, nil
1091
+ }
1092
+
1093
+ func advanceServeGraphProvenance(
1094
+ previous graph.Provenance,
1095
+ previousWire graph.Provenance,
1096
+ wireSources map[string]string,
1097
+ program *driver.Program,
1098
+ changed []string,
1099
+ diskDigests map[string]string,
1100
+ ) (graph.Provenance, graph.Provenance, bool) {
1101
+ next := previous
1102
+ next.Capabilities = append([]string{}, previous.Capabilities...)
1103
+ next.Sources = append([]graph.SourceDigest{}, previous.Sources...)
1104
+ next.Universe.Configs = append([]graph.FileDigest{}, previous.Universe.Configs...)
1105
+ next.Universe.Roots = append([]graph.RootFile{}, previous.Universe.Roots...)
1106
+ nextWire := previousWire
1107
+ nextWire.Capabilities = append([]string{}, previousWire.Capabilities...)
1108
+ nextWire.Sources = append([]graph.SourceDigest{}, previousWire.Sources...)
1109
+ nextWire.Universe.Configs = append([]graph.FileDigest{}, previousWire.Universe.Configs...)
1110
+ nextWire.Universe.Roots = append([]graph.RootFile{}, previousWire.Universe.Roots...)
1111
+ if len(nextWire.Sources) != len(next.Sources) {
1112
+ return graph.Provenance{}, graph.Provenance{}, false
1113
+ }
1114
+ positions := make(map[string]int, len(next.Sources))
1115
+ for index, source := range next.Sources {
1116
+ positions[source.File] = index
1117
+ }
1118
+ wirePositions := make(map[string]int, len(nextWire.Sources))
1119
+ for index, source := range nextWire.Sources {
1120
+ wirePositions[source.File] = index
1121
+ }
1122
+ for _, file := range changed {
1123
+ source := program.SourceFile(file)
1124
+ if source == nil {
1125
+ return graph.Provenance{}, graph.Provenance{}, false
1126
+ }
1127
+ physical := source.FileName()
1128
+ index, ok := positions[physical]
1129
+ if !ok {
1130
+ return graph.Provenance{}, graph.Provenance{}, false
1131
+ }
1132
+ updated := graph.SourceDigest{
1133
+ File: physical,
1134
+ Checker: graph.Digest(sha256.Sum256([]byte(source.Text()))),
1135
+ Disk: diskDigests[physical],
1136
+ }
1137
+ wireFile, ok := wireSources[physical]
1138
+ if !ok {
1139
+ return graph.Provenance{}, graph.Provenance{}, false
1140
+ }
1141
+ wireIndex, ok := wirePositions[wireFile]
1142
+ if !ok {
1143
+ return graph.Provenance{}, graph.Provenance{}, false
1144
+ }
1145
+ next.Sources[index] = updated
1146
+ nextWire.Sources[wireIndex].Checker = updated.Checker
1147
+ nextWire.Sources[wireIndex].Disk = updated.Disk
1148
+ }
1149
+ return next, nextWire, true
1150
+ }
1151
+
1152
+ func sourceServeGraphShardKey(identity serveGraphIdentity, file string, source graph.SourceDigest, resolutionDigest string) string {
1153
+ prefix := "1"
1154
+ if strings.HasPrefix(file, "bundled:///") {
1155
+ prefix = "2"
1156
+ }
1157
+ return prefix + ":source:" + serveGraphShardKey(identity, file, source.Checker, source.Disk, resolutionDigest)
1158
+ }
1159
+
1160
+ func configServeGraphShardKey(identity serveGraphIdentity, file, digest string) string {
1161
+ return "3:config:" + serveGraphShardKey(identity, file, digest)
1162
+ }
1163
+
1164
+ func externalServeGraphShardKey(identity serveGraphIdentity) string {
1165
+ return "0:external:" + serveGraphShardKey(identity, "external")
1166
+ }
1167
+
1168
+ func metadataServeGraphShardKey(identity serveGraphIdentity) string {
1169
+ return "0:metadata:" + serveGraphShardKey(identity, "metadata")
1170
+ }
1171
+
1172
+ func serveGraphShardKey(identity serveGraphIdentity, values ...string) string {
1173
+ coordinates := []any{
1174
+ graphSnapshotProtocolVersion,
1175
+ identity.producer.Tool,
1176
+ identity.producer.Version,
1177
+ identity.producer.Typescript,
1178
+ identity.target,
1179
+ identity.universe,
1180
+ }
1181
+ for _, value := range values {
1182
+ coordinates = append(coordinates, value)
1183
+ }
1184
+ encoded, _ := json.Marshal(coordinates)
1185
+ return string(encoded)
1186
+ }
1187
+
1188
+ func serveGraphFile(project, file string) (string, error) {
1189
+ relative, err := graph.WirePath(project, file)
1190
+ if err != nil {
1191
+ return "", fmt.Errorf("ttscgraph: relativize shard source %s: %w", file, err)
1192
+ }
1193
+ return relative, nil
1194
+ }
1195
+
1196
+ func rawServeShards(shards map[string]committedServeGraphShard) map[string]serveGraphShard {
1197
+ out := make(map[string]serveGraphShard, len(shards))
1198
+ for key, shard := range shards {
1199
+ out[key] = shard.shard
1200
+ }
1201
+ return out
1202
+ }
1203
+
1204
+ func invalidatedGraphFiles(
1205
+ program *driver.Program,
1206
+ reverse map[string][]string,
1207
+ changed []string,
1208
+ publicChanged []string,
1209
+ ) map[string]bool {
1210
+ authored := authoredGraphFiles(program)
1211
+ selected := map[string]bool{}
1212
+ for _, file := range changed {
1213
+ source := graphSourceFile(program, file)
1214
+ if source == nil {
1215
+ return nil
1216
+ }
1217
+ if shimcompiler.FileAffectsGlobalScope(source) {
1218
+ return authored
1219
+ }
1220
+ selected[file] = true
1221
+ }
1222
+ pending := append([]string{}, publicChanged...)
1223
+ for len(pending) > 0 {
1224
+ file := pending[0]
1225
+ pending = pending[1:]
1226
+ source := graphSourceFile(program, file)
1227
+ if source == nil {
1228
+ return nil
1229
+ }
1230
+ if shimcompiler.FileAffectsGlobalScope(source) {
1231
+ return authored
1232
+ }
1233
+ selected[file] = true
1234
+ for _, dependent := range reverse[file] {
1235
+ if !selected[dependent] {
1236
+ selected[dependent] = true
1237
+ pending = append(pending, dependent)
1238
+ }
1239
+ }
1240
+ }
1241
+ return selected
1242
+ }
1243
+
1244
+ func authoredGraphFiles(program *driver.Program) map[string]bool {
1245
+ authored := map[string]bool{}
1246
+ for _, file := range program.SourceFiles() {
1247
+ if !graph.IsWorkspaceSourceFile(file) {
1248
+ continue
1249
+ }
1250
+ authored[file.FileName()] = true
1251
+ }
1252
+ return authored
1253
+ }
1254
+
1255
+ func reverseGraphDependencies(program *driver.Program) map[string][]string {
1256
+ reverseSets := map[string]map[string]bool{}
1257
+ authored := map[string]bool{}
1258
+ authoredByPath := map[string]string{}
1259
+ for _, file := range program.SourceFiles() {
1260
+ if !graph.IsWorkspaceSourceFile(file) {
1261
+ continue
1262
+ }
1263
+ authored[file.FileName()] = true
1264
+ authoredByPath[string(file.Path())] = file.FileName()
1265
+ }
1266
+ add := func(target, dependent string) {
1267
+ if !authored[target] || target == dependent {
1268
+ return
1269
+ }
1270
+ values := reverseSets[target]
1271
+ if values == nil {
1272
+ values = map[string]bool{}
1273
+ reverseSets[target] = values
1274
+ }
1275
+ values[dependent] = true
1276
+ }
1277
+ for _, source := range program.SourceFiles() {
1278
+ if !graph.IsWorkspaceSourceFile(source) {
1279
+ continue
1280
+ }
1281
+ for _, referencedPath := range shimcompiler.GetReferencedFilePaths(program.TSProgram, source) {
1282
+ if target, ok := authoredByPath[referencedPath]; ok {
1283
+ add(target, source.FileName())
1284
+ }
1285
+ }
1286
+ }
1287
+ reverse := map[string][]string{}
1288
+ for target, dependents := range reverseSets {
1289
+ values := make([]string, 0, len(dependents))
1290
+ for dependent := range dependents {
1291
+ values = append(values, dependent)
1292
+ }
1293
+ sort.Strings(values)
1294
+ reverse[target] = values
1295
+ }
1296
+ return reverse
1297
+ }
1298
+
1299
+ func serveGraphResolutionDigests(program *driver.Program, project string) (map[string]string, error) {
1300
+ digests := map[string]string{}
1301
+ for _, source := range program.TSProgram.SourceFiles() {
1302
+ raw := shimcompiler.GetReferencedFilePaths(program.TSProgram, source)
1303
+ references := make([]string, 0, len(raw))
1304
+ for _, reference := range raw {
1305
+ target := program.TSProgram.GetSourceFileByPath(shimtspath.Path(reference))
1306
+ if target == nil {
1307
+ continue
1308
+ }
1309
+ file, err := serveGraphFile(project, target.FileName())
1310
+ if err != nil {
1311
+ return nil, err
1312
+ }
1313
+ references = append(references, file)
1314
+ }
1315
+ sort.Strings(references)
1316
+ digest, _ := digestJSON(references)
1317
+ digests[source.FileName()] = digest
1318
+ }
1319
+ return digests, nil
1320
+ }
1321
+
1322
+ func graphSourceFile(program *driver.Program, file string) *shimast.SourceFile {
1323
+ for _, source := range program.SourceFiles() {
1324
+ if graph.IsWorkspaceSourceFile(source) && source.FileName() == file {
1325
+ return source
1326
+ }
1327
+ }
1328
+ return nil
1329
+ }
1330
+
1331
+ func sortedSelectedFiles(selected map[string]bool) []string {
1332
+ files := make([]string, 0, len(selected))
1333
+ for file := range selected {
1334
+ files = append(files, file)
1335
+ }
1336
+ sort.Strings(files)
1337
+ return files
1338
+ }
1339
+
1340
+ func addPriorExportTargets(selected map[string]bool, prior *serveGraphStore) {
1341
+ for file := range maps.Clone(selected) {
1342
+ shard, ok := prior.shards[prior.sourceKeys[file]]
1343
+ if !ok {
1344
+ continue
1345
+ }
1346
+ for _, edge := range shard.shard.Edges {
1347
+ if edge.Kind != "exports" {
1348
+ continue
1349
+ }
1350
+ target, ok := prior.dumpNodeFiles[edge.To]
1351
+ if ok {
1352
+ selected[target] = true
1353
+ }
1354
+ }
1355
+ }
1356
+ }
1357
+
1358
+ func expandPriorImplementationSources(selected map[string]bool, prior *serveGraphStore) {
1359
+ for {
1360
+ expanded := false
1361
+ for id, sources := range prior.implementationSources {
1362
+ owner := graph.NodeFile(id)
1363
+ affected := selected[owner]
1364
+ if !affected {
1365
+ for source := range sources {
1366
+ if selected[source] {
1367
+ affected = true
1368
+ break
1369
+ }
1370
+ }
1371
+ }
1372
+ if !affected {
1373
+ continue
1374
+ }
1375
+ if owner != "" && !selected[owner] {
1376
+ selected[owner] = true
1377
+ expanded = true
1378
+ }
1379
+ for source := range sources {
1380
+ if !selected[source] {
1381
+ selected[source] = true
1382
+ expanded = true
1383
+ }
1384
+ }
1385
+ }
1386
+ if !expanded {
1387
+ return
1388
+ }
1389
+ }
1390
+ }