ttsc 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cmd/ttscgraph/serve.go +264 -48
- package/cmd/ttscgraph/serve_early_error_encode_failure_is_observable_test.go +40 -0
- package/cmd/ttscgraph/serve_graph_snapshot_version_matches_typescript_client_test.go +32 -0
- package/cmd/ttscgraph/serve_phase_trace_accounts_for_load_failure_test.go +45 -0
- package/cmd/ttscgraph/serve_phase_trace_is_opt_in_and_payload_free_test.go +52 -0
- package/cmd/ttscgraph/serve_protocol_negotiates_incremental_shards_test.go +44 -0
- package/cmd/ttscgraph/serve_session_reloads_commonjs_package_entry_for_esm_test.go +2 -2
- package/cmd/ttscgraph/serve_session_reloads_superseding_module_candidates_test.go +46 -20
- package/cmd/ttscgraph/serve_session_retries_failed_dump_test.go +9 -9
- package/cmd/ttscgraph/serve_shards.go +1390 -0
- package/cmd/ttscgraph/serve_shards_abandoned_generation_converges_on_retry_test.go +92 -0
- package/cmd/ttscgraph/serve_shards_delete_removed_source_test.go +57 -0
- package/cmd/ttscgraph/serve_shards_external_endpoints_test.go +53 -0
- package/cmd/ttscgraph/serve_shards_keep_source_distributed_dependencies_at_the_boundary_test.go +108 -0
- package/cmd/ttscgraph/serve_shards_match_full_dump_oracle_test.go +88 -0
- package/cmd/ttscgraph/serve_shards_merge_pending_and_new_edits_test.go +50 -0
- package/cmd/ttscgraph/serve_shards_prune_unreferenced_external_endpoints_test.go +65 -0
- package/cmd/ttscgraph/serve_shards_publish_created_source_test.go +92 -0
- package/cmd/ttscgraph/serve_shards_rebuild_declaration_edit_test.go +43 -0
- package/cmd/ttscgraph/serve_shards_refresh_cross_file_implementation_test.go +63 -0
- package/cmd/ttscgraph/serve_shards_refresh_dependent_diagnostics_test.go +80 -0
- package/cmd/ttscgraph/serve_shards_rename_moves_source_identity_test.go +95 -0
- package/cmd/ttscgraph/serve_shards_retry_preserves_committed_generation_test.go +48 -0
- package/cmd/ttscgraph/serve_shards_reuse_unaffected_sources_test.go +121 -0
- package/driver/declaration_shape.go +85 -0
- package/driver/program.go +39 -10
- package/go.mod +1 -1
- package/internal/graph/build.go +62 -10
- package/internal/graph/build_files_reuses_committed_cross_file_endpoints_test.go +87 -0
- package/internal/graph/build_keeps_source_distributed_dependencies_at_the_external_boundary_test.go +74 -0
- package/internal/graph/dump.go +84 -43
- package/internal/graph/dump_path_mapper_caches_alias_resolution_test.go +29 -0
- package/internal/graph/dump_paths.go +134 -11
- package/internal/graph/dump_paths_canonicalize_windows_short_root_test.go +39 -0
- package/internal/graph/dump_rejects_provenance_only_path_errors_test.go +32 -0
- package/internal/graph/dump_serializes_the_full_graph_test.go +2 -2
- package/internal/graph/dump_symlink_project_publishes_matching_base_test.go +82 -0
- package/internal/graph/edges.go +58 -16
- package/internal/graph/exports.go +9 -2
- package/internal/graph/graph.go +47 -7
- package/internal/graph/graph_kind_contracts_match_their_producers_test.go +121 -0
- package/internal/graph/node_modifiers_emit_union_strings_test.go +1 -1
- package/internal/graph/provenance.go +11 -1
- package/internal/graph/resolve.go +1 -4
- package/internal/graph/source.go +23 -0
- package/internal/graph/wire_node_ids_share_alias_resolution_test.go +29 -0
- package/internal/lspserver/lsp_launcher_reload_directory_fingerprint_parity_windows_test.go +94 -0
- package/internal/lspserver/lsp_project_input_glob_path_other.go +16 -0
- package/internal/lspserver/lsp_project_input_glob_path_windows.go +36 -0
- package/internal/lspserver/lsp_project_input_path_key_other.go +17 -0
- package/internal/lspserver/lsp_project_input_path_key_windows.go +228 -0
- package/internal/lspserver/lsp_project_input_path_key_windows_test.go +460 -0
- package/internal/lspserver/lsp_project_inputs.go +117 -49
- package/internal/lspserver/lsp_reload_directories_separate_glob_territory_test.go +181 -0
- package/lib/flags/schema.js +1 -1
- package/lib/flags/schema.js.map +1 -1
- package/lib/internal/projectInputPathIdentity.d.ts +13 -5
- package/lib/internal/projectInputPathIdentity.js +95 -41
- package/lib/internal/projectInputPathIdentity.js.map +1 -1
- package/lib/internal/windowsDirectoryCaseSensitivity.d.ts +10 -0
- package/lib/internal/windowsDirectoryCaseSensitivity.js +34 -0
- package/lib/internal/windowsDirectoryCaseSensitivity.js.map +1 -0
- package/lib/launcher/internal/runTtsc.js +5 -0
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.js +59 -8
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/launcher/internal/runTtsx.js +61 -7
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/launcher/internal/runtimeHooks.d.ts +6 -5
- package/lib/launcher/internal/runtimeHooks.js +7 -15
- package/lib/launcher/internal/runtimeHooks.js.map +1 -1
- package/lib/launcher/internal/watchTopology.d.ts +82 -8
- package/lib/launcher/internal/watchTopology.js +535 -187
- package/lib/launcher/internal/watchTopology.js.map +1 -1
- package/lib/plugin/internal/descriptorProcessFailure.d.ts +32 -0
- package/lib/plugin/internal/descriptorProcessFailure.js +73 -0
- package/lib/plugin/internal/descriptorProcessFailure.js.map +1 -0
- package/lib/plugin/internal/loadProjectPlugins.js +29 -8
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/package.json +12 -8
- package/src/flags/schema.ts +2 -1
- package/src/internal/projectInputPathIdentity.ts +150 -46
- package/src/internal/windowsDirectoryCaseSensitivity.ts +33 -0
- package/src/launcher/internal/runTtsc.ts +7 -0
- package/src/launcher/internal/runTtscserver.ts +81 -11
- package/src/launcher/internal/runTtsx.ts +87 -10
- package/src/launcher/internal/runtimeHooks.ts +16 -16
- package/src/launcher/internal/watchTopology.ts +679 -208
- package/src/plugin/internal/descriptorProcessFailure.ts +100 -0
- package/src/plugin/internal/loadProjectPlugins.ts +41 -8
- package/tools/shim_audit/baseline.json +0 -9
package/cmd/ttscgraph/serve.go
CHANGED
|
@@ -13,6 +13,7 @@ import (
|
|
|
13
13
|
"slices"
|
|
14
14
|
"sort"
|
|
15
15
|
"strings"
|
|
16
|
+
"time"
|
|
16
17
|
|
|
17
18
|
shimtsoptions "github.com/microsoft/typescript-go/shim/tsoptions"
|
|
18
19
|
shimtspath "github.com/microsoft/typescript-go/shim/tspath"
|
|
@@ -45,7 +46,9 @@ const (
|
|
|
45
46
|
serveModeUnchanged = "unchanged"
|
|
46
47
|
// serveModeIncremental is edits applied onto the reused resident program.
|
|
47
48
|
serveModeIncremental = "incremental"
|
|
48
|
-
// serveModeRebuild is edits applied but
|
|
49
|
+
// serveModeRebuild is edits applied but graph projection requires a complete
|
|
50
|
+
// rebuild, either because the program could not be reused or because a
|
|
51
|
+
// declaration-file boundary has no authored source shard of its own.
|
|
49
52
|
serveModeRebuild = "rebuild"
|
|
50
53
|
// serveModeError is a request that produced no snapshot. It is a transport
|
|
51
54
|
// mode, not a computation mode: it exists so mode is never absent, because a
|
|
@@ -71,10 +74,15 @@ var serveCapabilities = fullSnapshotCapabilities
|
|
|
71
74
|
|
|
72
75
|
type serveRequest struct {
|
|
73
76
|
ID int `json:"id"`
|
|
77
|
+
// GraphSnapshotVersion opts into the incremental shard protocol. Omitted
|
|
78
|
+
// requests retain the schema-v6 full-dump response for existing
|
|
79
|
+
// @ttsc/graph clients.
|
|
80
|
+
GraphSnapshotVersion int `json:"graphSnapshotVersion,omitempty"`
|
|
74
81
|
}
|
|
75
82
|
|
|
76
83
|
type serveResponse struct {
|
|
77
|
-
Dump
|
|
84
|
+
Dump *graph.Dump `json:"dump,omitempty"`
|
|
85
|
+
Snapshot *serveGraphSnapshot `json:"snapshot,omitempty"`
|
|
78
86
|
// Error is set when the request produced no snapshot; Mode is then
|
|
79
87
|
// serveModeError.
|
|
80
88
|
Error string `json:"error,omitempty"`
|
|
@@ -90,6 +98,8 @@ type serveResponse struct {
|
|
|
90
98
|
Changed bool `json:"changed"`
|
|
91
99
|
}
|
|
92
100
|
|
|
101
|
+
const graphPhaseTraceEnvironment = "SAMCHON_GRAPH_TTSC_PHASE_TRACE"
|
|
102
|
+
|
|
93
103
|
// newServeResponse stamps the fields every response owes the client, so no exit
|
|
94
104
|
// from the serve loop can forget one.
|
|
95
105
|
func newServeResponse(id int) serveResponse {
|
|
@@ -124,13 +134,17 @@ type graphSession struct {
|
|
|
124
134
|
// generation, captured from the same parse that produced configHashes and
|
|
125
135
|
// rootFiles so the published evidence and the invalidation state can never
|
|
126
136
|
// describe different loads.
|
|
127
|
-
configDigests
|
|
128
|
-
roots
|
|
129
|
-
initialized
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
137
|
+
configDigests []graph.FileDigest
|
|
138
|
+
roots []graph.RootFile
|
|
139
|
+
initialized bool
|
|
140
|
+
graphStore *serveGraphStore
|
|
141
|
+
requestProtocol int
|
|
142
|
+
requestProtocolSelected bool
|
|
143
|
+
// pending remembers a generation whose state was captured but whose selected
|
|
144
|
+
// projection failed. Until an input change lets the session rebuild, an
|
|
145
|
+
// unchanged request retries the same full or partial invalidation instead of
|
|
146
|
+
// falsely confirming the older client graph.
|
|
147
|
+
pending *graphChange
|
|
134
148
|
}
|
|
135
149
|
|
|
136
150
|
func newGraphSession(cwd, tsconfig string) (*graphSession, error) {
|
|
@@ -149,106 +163,187 @@ func (s *graphSession) Close() error {
|
|
|
149
163
|
}
|
|
150
164
|
|
|
151
165
|
func (s *graphSession) Snapshot() (*graph.Dump, string, bool, error) {
|
|
166
|
+
change, err := s.nextChange(false)
|
|
167
|
+
if err != nil {
|
|
168
|
+
return nil, "", false, err
|
|
169
|
+
}
|
|
170
|
+
if change == nil {
|
|
171
|
+
return nil, serveModeUnchanged, false, nil
|
|
172
|
+
}
|
|
173
|
+
dump, err := s.buildDump()
|
|
174
|
+
if err != nil {
|
|
175
|
+
s.pending = change
|
|
176
|
+
return nil, "", false, err
|
|
177
|
+
}
|
|
178
|
+
s.pending = nil
|
|
179
|
+
return &dump, change.mode, true, nil
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
type graphChange struct {
|
|
183
|
+
mode string
|
|
184
|
+
files []string
|
|
185
|
+
publicFiles []string
|
|
186
|
+
full bool
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// nextChange advances only the resident compiler and its captured input state.
|
|
190
|
+
// Projection is deliberately outside this method so the legacy full-dump and
|
|
191
|
+
// incremental shard protocols share exactly one invalidation decision without
|
|
192
|
+
// forcing either representation onto the other.
|
|
193
|
+
func (s *graphSession) nextChange(trackPublicShape bool) (*graphChange, error) {
|
|
152
194
|
if !s.initialized {
|
|
153
195
|
// The captured compiler state is initialized even when its first dump is
|
|
154
196
|
// not publishable. Mark the attempt before building so a later request can
|
|
155
197
|
// observe a config/source edit that repairs the path error instead of
|
|
156
198
|
// retrying the stale Program forever.
|
|
157
199
|
s.initialized = true
|
|
158
|
-
|
|
159
|
-
if err != nil {
|
|
160
|
-
s.pendingDumpMode = serveModeInitial
|
|
161
|
-
return nil, "", false, err
|
|
162
|
-
}
|
|
163
|
-
s.pendingDumpMode = ""
|
|
164
|
-
return &dump, serveModeInitial, true, nil
|
|
200
|
+
return &graphChange{mode: serveModeInitial, full: true}, nil
|
|
165
201
|
}
|
|
166
202
|
|
|
167
203
|
configChanged, err := hashesChanged(s.configHashes)
|
|
168
204
|
if err != nil {
|
|
169
|
-
return nil,
|
|
205
|
+
return nil, err
|
|
170
206
|
}
|
|
171
207
|
if configChanged {
|
|
172
208
|
if err := s.reload(); err != nil {
|
|
173
|
-
return nil,
|
|
209
|
+
return nil, err
|
|
174
210
|
}
|
|
175
|
-
return
|
|
211
|
+
return &graphChange{mode: serveModeReload, full: true}, nil
|
|
176
212
|
}
|
|
177
213
|
|
|
178
214
|
if diskStatesChanged(s.auxStates) {
|
|
179
215
|
if err := s.reload(); err != nil {
|
|
180
|
-
return nil,
|
|
216
|
+
return nil, err
|
|
181
217
|
}
|
|
182
|
-
return
|
|
218
|
+
return &graphChange{mode: serveModeReload, full: true}, nil
|
|
183
219
|
}
|
|
184
220
|
|
|
185
221
|
roots, err := projectRootFiles(s.compiler.Program(), true)
|
|
186
222
|
if err != nil {
|
|
187
|
-
return nil,
|
|
223
|
+
return nil, err
|
|
188
224
|
}
|
|
189
225
|
if !slices.Equal(s.rootFiles, roots) {
|
|
190
226
|
if err := s.reload(); err != nil {
|
|
191
|
-
return nil,
|
|
227
|
+
return nil, err
|
|
192
228
|
}
|
|
193
|
-
return
|
|
229
|
+
return &graphChange{mode: serveModeReload, full: true}, nil
|
|
194
230
|
}
|
|
195
231
|
|
|
196
232
|
changed, deleted, err := changedSources(s.sourceHashes)
|
|
197
233
|
if err != nil {
|
|
198
|
-
return nil,
|
|
234
|
+
return nil, err
|
|
199
235
|
}
|
|
200
236
|
if deleted {
|
|
201
237
|
if err := s.reload(); err != nil {
|
|
202
|
-
return nil,
|
|
238
|
+
return nil, err
|
|
203
239
|
}
|
|
204
|
-
return
|
|
240
|
+
return &graphChange{mode: serveModeReload, full: true}, nil
|
|
205
241
|
}
|
|
206
242
|
if len(changed) == 0 {
|
|
207
|
-
if s.
|
|
208
|
-
return s.
|
|
243
|
+
if s.pending != nil {
|
|
244
|
+
return s.pending, nil
|
|
209
245
|
}
|
|
210
|
-
return nil,
|
|
246
|
+
return nil, nil
|
|
211
247
|
}
|
|
212
248
|
if s.compiler.Program().HasLinkedProgramPlugins() {
|
|
213
249
|
if err := s.reload(); err != nil {
|
|
214
|
-
return nil,
|
|
250
|
+
return nil, err
|
|
215
251
|
}
|
|
216
|
-
return
|
|
252
|
+
return &graphChange{mode: serveModeReload, full: true}, nil
|
|
217
253
|
}
|
|
218
254
|
|
|
219
255
|
mode := serveModeIncremental
|
|
256
|
+
full := false
|
|
220
257
|
paths := make([]string, 0, len(changed))
|
|
221
258
|
for path := range changed {
|
|
222
259
|
paths = append(paths, path)
|
|
223
260
|
}
|
|
224
261
|
sort.Strings(paths)
|
|
262
|
+
oldShapes := make(map[string]string, len(paths))
|
|
263
|
+
for _, path := range paths {
|
|
264
|
+
source := s.compiler.Program().SourceFile(path)
|
|
265
|
+
if source == nil {
|
|
266
|
+
if err := s.reload(); err != nil {
|
|
267
|
+
return nil, err
|
|
268
|
+
}
|
|
269
|
+
return &graphChange{mode: serveModeReload, full: true}, nil
|
|
270
|
+
}
|
|
271
|
+
if source.IsDeclarationFile {
|
|
272
|
+
mode = serveModeRebuild
|
|
273
|
+
full = true
|
|
274
|
+
continue
|
|
275
|
+
}
|
|
276
|
+
if trackPublicShape {
|
|
277
|
+
shape, err := s.compiler.Program().DeclarationShapeDigest(source)
|
|
278
|
+
if err != nil {
|
|
279
|
+
return nil, err
|
|
280
|
+
}
|
|
281
|
+
oldShapes[path] = shape
|
|
282
|
+
}
|
|
283
|
+
}
|
|
225
284
|
for _, path := range paths {
|
|
226
285
|
if reused := s.compiler.Apply(path, changed[path]); !reused {
|
|
227
286
|
mode = serveModeRebuild
|
|
287
|
+
full = true
|
|
228
288
|
}
|
|
229
289
|
current, exists := s.compiler.SourceText(path)
|
|
230
290
|
expected := driver.ApplySourcePreambleToFile(path, changed[path], s.compiler.Program().SourcePreamble)
|
|
231
291
|
if !exists || current != expected {
|
|
232
292
|
if err := s.reload(); err != nil {
|
|
233
|
-
return nil,
|
|
293
|
+
return nil, err
|
|
234
294
|
}
|
|
235
|
-
return
|
|
295
|
+
return &graphChange{mode: serveModeReload, full: true}, nil
|
|
296
|
+
}
|
|
297
|
+
source := s.compiler.Program().SourceFile(path)
|
|
298
|
+
if source == nil {
|
|
299
|
+
if err := s.reload(); err != nil {
|
|
300
|
+
return nil, err
|
|
301
|
+
}
|
|
302
|
+
return &graphChange{mode: serveModeReload, full: true}, nil
|
|
303
|
+
}
|
|
304
|
+
if source.IsDeclarationFile {
|
|
305
|
+
mode = serveModeRebuild
|
|
306
|
+
full = true
|
|
236
307
|
}
|
|
237
308
|
}
|
|
238
309
|
if err := s.captureState(); err != nil {
|
|
239
|
-
|
|
310
|
+
// The compiler already accepted the edit. Preserve a full retry marker so
|
|
311
|
+
// a transient hashing/input failure cannot make the next request compare
|
|
312
|
+
// public shape against the uncommitted compiler generation and publish an
|
|
313
|
+
// incomplete closure.
|
|
314
|
+
s.pending = &graphChange{mode: serveModeRebuild, full: true}
|
|
315
|
+
return nil, err
|
|
240
316
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
317
|
+
publicFiles := []string{}
|
|
318
|
+
if trackPublicShape && !full {
|
|
319
|
+
for _, path := range paths {
|
|
320
|
+
source := s.compiler.Program().SourceFile(path)
|
|
321
|
+
if source == nil {
|
|
322
|
+
return &graphChange{mode: serveModeRebuild, full: true}, nil
|
|
323
|
+
}
|
|
324
|
+
shape, err := s.compiler.Program().DeclarationShapeDigest(source)
|
|
325
|
+
if err != nil {
|
|
326
|
+
mode = serveModeRebuild
|
|
327
|
+
full = true
|
|
328
|
+
publicFiles = nil
|
|
329
|
+
break
|
|
330
|
+
}
|
|
331
|
+
if shape != oldShapes[path] {
|
|
332
|
+
publicFiles = append(publicFiles, path)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if s.pending != nil {
|
|
337
|
+
if s.pending.full {
|
|
338
|
+
mode = s.pending.mode
|
|
339
|
+
full = true
|
|
340
|
+
paths = nil
|
|
341
|
+
} else if !full {
|
|
342
|
+
paths = compactSortedStrings(append(paths, s.pending.files...))
|
|
343
|
+
publicFiles = compactSortedStrings(append(publicFiles, s.pending.publicFiles...))
|
|
344
|
+
}
|
|
249
345
|
}
|
|
250
|
-
|
|
251
|
-
return &dump, mode, true, nil
|
|
346
|
+
return &graphChange{mode: mode, files: paths, publicFiles: publicFiles, full: full}, nil
|
|
252
347
|
}
|
|
253
348
|
|
|
254
349
|
func (s *graphSession) reload() error {
|
|
@@ -750,26 +845,86 @@ func serveSnapshots(input io.Reader, output io.Writer, cwd, tsconfig string) int
|
|
|
750
845
|
fmt.Fprintf(stderr, "ttscgraph: unaddressable serve request: %v\n", err)
|
|
751
846
|
return 1
|
|
752
847
|
}
|
|
848
|
+
requestStarted := time.Now()
|
|
849
|
+
if request.GraphSnapshotVersion != 0 && request.GraphSnapshotVersion != graphSnapshotProtocolVersion {
|
|
850
|
+
if err := encodeServeResponseWithTrace(encoder, errorResponse(
|
|
851
|
+
request.ID,
|
|
852
|
+
fmt.Sprintf(
|
|
853
|
+
"ttscgraph: unsupported graph snapshot protocol v%d (supported: v%d)",
|
|
854
|
+
request.GraphSnapshotVersion,
|
|
855
|
+
graphSnapshotProtocolVersion,
|
|
856
|
+
),
|
|
857
|
+
), requestStarted, 0, 0, 0); err != nil {
|
|
858
|
+
fmt.Fprintf(stderr, "ttscgraph: write serve response: %v\n", err)
|
|
859
|
+
return 1
|
|
860
|
+
}
|
|
861
|
+
continue
|
|
862
|
+
}
|
|
863
|
+
var loadDuration time.Duration
|
|
753
864
|
if session == nil {
|
|
865
|
+
loadStarted := time.Now()
|
|
754
866
|
created, err := newGraphSession(cwd, tsconfig)
|
|
867
|
+
loadDuration = time.Since(loadStarted)
|
|
755
868
|
if err != nil {
|
|
756
|
-
|
|
869
|
+
if err := encodeServeResponseWithTrace(
|
|
870
|
+
encoder,
|
|
871
|
+
errorResponse(request.ID, err.Error()),
|
|
872
|
+
requestStarted,
|
|
873
|
+
loadDuration,
|
|
874
|
+
0,
|
|
875
|
+
0,
|
|
876
|
+
); err != nil {
|
|
877
|
+
fmt.Fprintf(stderr, "ttscgraph: write serve response: %v\n", err)
|
|
878
|
+
return 1
|
|
879
|
+
}
|
|
757
880
|
continue
|
|
758
881
|
}
|
|
759
882
|
session = created
|
|
760
883
|
}
|
|
761
|
-
|
|
884
|
+
if session.requestProtocolSelected && session.requestProtocol != request.GraphSnapshotVersion {
|
|
885
|
+
if err := encodeServeResponseWithTrace(encoder, errorResponse(
|
|
886
|
+
request.ID,
|
|
887
|
+
"ttscgraph: graphSnapshotVersion cannot change within one resident session",
|
|
888
|
+
), requestStarted, loadDuration, 0, 0); err != nil {
|
|
889
|
+
fmt.Fprintf(stderr, "ttscgraph: write serve response: %v\n", err)
|
|
890
|
+
return 1
|
|
891
|
+
}
|
|
892
|
+
continue
|
|
893
|
+
}
|
|
894
|
+
session.requestProtocol = request.GraphSnapshotVersion
|
|
895
|
+
session.requestProtocolSelected = true
|
|
896
|
+
var dump *graph.Dump
|
|
897
|
+
var snapshot *serveGraphSnapshot
|
|
898
|
+
var mode string
|
|
899
|
+
var changed bool
|
|
900
|
+
var semanticDuration time.Duration
|
|
901
|
+
var exportDuration time.Duration
|
|
902
|
+
var err error
|
|
903
|
+
if request.GraphSnapshotVersion == graphSnapshotProtocolVersion {
|
|
904
|
+
snapshot, mode, changed, semanticDuration, exportDuration, err = session.snapshotShardsWithTiming()
|
|
905
|
+
} else {
|
|
906
|
+
dump, mode, changed, err = session.Snapshot()
|
|
907
|
+
}
|
|
762
908
|
response := newServeResponse(request.ID)
|
|
763
909
|
response.Dump = dump
|
|
910
|
+
response.Snapshot = snapshot
|
|
764
911
|
response.Mode = mode
|
|
765
912
|
response.Changed = changed
|
|
766
913
|
if err != nil {
|
|
767
914
|
response.Error = err.Error()
|
|
768
915
|
response.Dump = nil
|
|
916
|
+
response.Snapshot = nil
|
|
769
917
|
response.Mode = serveModeError
|
|
770
918
|
response.Changed = false
|
|
771
919
|
}
|
|
772
|
-
if err :=
|
|
920
|
+
if err := encodeServeResponseWithTrace(
|
|
921
|
+
encoder,
|
|
922
|
+
response,
|
|
923
|
+
requestStarted,
|
|
924
|
+
loadDuration,
|
|
925
|
+
semanticDuration,
|
|
926
|
+
exportDuration,
|
|
927
|
+
); err != nil {
|
|
773
928
|
fmt.Fprintf(stderr, "ttscgraph: write serve response: %v\n", err)
|
|
774
929
|
return 1
|
|
775
930
|
}
|
|
@@ -780,3 +935,64 @@ func serveSnapshots(input io.Reader, output io.Writer, cwd, tsconfig string) int
|
|
|
780
935
|
}
|
|
781
936
|
return 0
|
|
782
937
|
}
|
|
938
|
+
|
|
939
|
+
// encodeServeResponseWithTrace accounts for every addressed response, including
|
|
940
|
+
// failures that occur before a graph session exists.
|
|
941
|
+
func encodeServeResponseWithTrace(
|
|
942
|
+
encoder *json.Encoder,
|
|
943
|
+
response serveResponse,
|
|
944
|
+
requestStarted time.Time,
|
|
945
|
+
loadDuration time.Duration,
|
|
946
|
+
semanticDuration time.Duration,
|
|
947
|
+
exportDuration time.Duration,
|
|
948
|
+
) error {
|
|
949
|
+
encodeStarted := time.Now()
|
|
950
|
+
if err := encoder.Encode(response); err != nil {
|
|
951
|
+
return err
|
|
952
|
+
}
|
|
953
|
+
writeServePhaseTrace(
|
|
954
|
+
response.ID,
|
|
955
|
+
response.Mode,
|
|
956
|
+
loadDuration,
|
|
957
|
+
semanticDuration,
|
|
958
|
+
exportDuration,
|
|
959
|
+
time.Since(encodeStarted),
|
|
960
|
+
time.Since(requestStarted),
|
|
961
|
+
)
|
|
962
|
+
return nil
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// writeServePhaseTrace emits opt-in timings without source paths or payloads.
|
|
966
|
+
func writeServePhaseTrace(
|
|
967
|
+
request int,
|
|
968
|
+
mode string,
|
|
969
|
+
load time.Duration,
|
|
970
|
+
semantic time.Duration,
|
|
971
|
+
export time.Duration,
|
|
972
|
+
encode time.Duration,
|
|
973
|
+
total time.Duration,
|
|
974
|
+
) {
|
|
975
|
+
if os.Getenv(graphPhaseTraceEnvironment) != "1" {
|
|
976
|
+
return
|
|
977
|
+
}
|
|
978
|
+
phases := []struct {
|
|
979
|
+
name string
|
|
980
|
+
duration time.Duration
|
|
981
|
+
}{
|
|
982
|
+
{name: "native-load", duration: load},
|
|
983
|
+
{name: "semantic-refresh", duration: semantic},
|
|
984
|
+
{name: "shard-export", duration: export},
|
|
985
|
+
{name: "encode", duration: encode},
|
|
986
|
+
{name: "producer-total", duration: total},
|
|
987
|
+
}
|
|
988
|
+
for _, phase := range phases {
|
|
989
|
+
fmt.Fprintf(
|
|
990
|
+
stderr,
|
|
991
|
+
"@samchon/graph: ttscgraph-phase owner=producer request=%d mode=%s phase=%s durationMs=%.3f\n",
|
|
992
|
+
request,
|
|
993
|
+
mode,
|
|
994
|
+
phase.name,
|
|
995
|
+
float64(phase.duration)/float64(time.Millisecond),
|
|
996
|
+
)
|
|
997
|
+
}
|
|
998
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"errors"
|
|
6
|
+
"strings"
|
|
7
|
+
"testing"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
type rejectedServeWriter struct{}
|
|
11
|
+
|
|
12
|
+
func (rejectedServeWriter) Write([]byte) (int, error) {
|
|
13
|
+
return 0, errors.New("synthetic response write failure")
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// TestServeEarlyErrorEncodeFailureIsObservable proves a rejected early error
|
|
17
|
+
// response fails the stream without claiming that an unencoded response ran.
|
|
18
|
+
//
|
|
19
|
+
// 1. Reject protocol negotiation before a graph session is constructed.
|
|
20
|
+
// 2. Fail the response writer and require a nonzero server result.
|
|
21
|
+
// 3. Require the stderr cause while forbidding successful phase rows.
|
|
22
|
+
func TestServeEarlyErrorEncodeFailureIsObservable(t *testing.T) {
|
|
23
|
+
root := graphSessionFixture(t)
|
|
24
|
+
oldStderr := stderr
|
|
25
|
+
defer func() { stderr = oldStderr }()
|
|
26
|
+
t.Setenv(graphPhaseTraceEnvironment, "1")
|
|
27
|
+
|
|
28
|
+
var diagnostic bytes.Buffer
|
|
29
|
+
stderr = &diagnostic
|
|
30
|
+
request := "{\"id\":29,\"graphSnapshotVersion\":99}\n"
|
|
31
|
+
if code := serveSnapshots(strings.NewReader(request), rejectedServeWriter{}, root, "tsconfig.json"); code == 0 {
|
|
32
|
+
t.Fatal("failed early error encoding returned success")
|
|
33
|
+
}
|
|
34
|
+
if !strings.Contains(diagnostic.String(), "write serve response: synthetic response write failure") {
|
|
35
|
+
t.Fatalf("failed early error encoding hid its cause: %q", diagnostic.String())
|
|
36
|
+
}
|
|
37
|
+
if strings.Contains(diagnostic.String(), "ttscgraph-phase") {
|
|
38
|
+
t.Fatalf("failed early error encoding claimed successful phase rows: %q", diagnostic.String())
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"regexp"
|
|
7
|
+
"strconv"
|
|
8
|
+
"testing"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// TestServeGraphSnapshotVersionMatchesTypeScriptClient verifies the native
|
|
12
|
+
// opt-in number and the resident client move together. The outer serve protocol
|
|
13
|
+
// stays backward compatible, so only this explicit negotiation field can stop
|
|
14
|
+
// an old full-dump client from misreading a shard response.
|
|
15
|
+
func TestServeGraphSnapshotVersionMatchesTypeScriptClient(t *testing.T) {
|
|
16
|
+
source := filepath.Join("..", "..", "..", "graph", "src", "model", "TtscGraphSession.ts")
|
|
17
|
+
content, err := os.ReadFile(source)
|
|
18
|
+
if err != nil {
|
|
19
|
+
t.Fatal(err)
|
|
20
|
+
}
|
|
21
|
+
match := regexp.MustCompile(`const GRAPH_SNAPSHOT_PROTOCOL_VERSION = ([0-9]+);`).FindSubmatch(content)
|
|
22
|
+
if len(match) != 2 {
|
|
23
|
+
t.Fatalf("could not find GRAPH_SNAPSHOT_PROTOCOL_VERSION in %s", source)
|
|
24
|
+
}
|
|
25
|
+
version, err := strconv.Atoi(string(match[1]))
|
|
26
|
+
if err != nil {
|
|
27
|
+
t.Fatal(err)
|
|
28
|
+
}
|
|
29
|
+
if version != graphSnapshotProtocolVersion {
|
|
30
|
+
t.Fatalf("TypeScript client graph snapshot protocol v%d, native server v%d", version, graphSnapshotProtocolVersion)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"strings"
|
|
6
|
+
"testing"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// TestServePhaseTraceAccountsForLoadFailure proves addressed requests retain a
|
|
10
|
+
// complete timing record even when no resident compiler session can be built.
|
|
11
|
+
//
|
|
12
|
+
// 1. Request a missing tsconfig with phase tracing enabled.
|
|
13
|
+
// 2. Require a normal addressed error response rather than process failure.
|
|
14
|
+
// 3. Require all five payload-free phases under mode=error.
|
|
15
|
+
func TestServePhaseTraceAccountsForLoadFailure(t *testing.T) {
|
|
16
|
+
root := graphSessionFixture(t)
|
|
17
|
+
request := "{\"id\":23,\"graphSnapshotVersion\":1}\n"
|
|
18
|
+
oldStderr := stderr
|
|
19
|
+
defer func() { stderr = oldStderr }()
|
|
20
|
+
t.Setenv(graphPhaseTraceEnvironment, "1")
|
|
21
|
+
|
|
22
|
+
var output bytes.Buffer
|
|
23
|
+
var trace bytes.Buffer
|
|
24
|
+
stderr = &trace
|
|
25
|
+
if code := serveSnapshots(strings.NewReader(request), &output, root, "missing-tsconfig.json"); code != 0 {
|
|
26
|
+
t.Fatalf("failed load exited %d", code)
|
|
27
|
+
}
|
|
28
|
+
if !strings.Contains(output.String(), `"id":23`) || !strings.Contains(output.String(), `"mode":"error"`) {
|
|
29
|
+
t.Fatalf("failed load omitted its addressed error response: %q", output.String())
|
|
30
|
+
}
|
|
31
|
+
for _, phase := range []string{
|
|
32
|
+
"native-load",
|
|
33
|
+
"semantic-refresh",
|
|
34
|
+
"shard-export",
|
|
35
|
+
"encode",
|
|
36
|
+
"producer-total",
|
|
37
|
+
} {
|
|
38
|
+
if !strings.Contains(trace.String(), "owner=producer request=23 mode=error phase="+phase+" durationMs=") {
|
|
39
|
+
t.Fatalf("failed-load trace omitted %s: %q", phase, trace.String())
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if strings.Contains(trace.String(), root) || strings.Contains(trace.String(), "missing-tsconfig") || strings.Contains(trace.String(), "{\"") {
|
|
43
|
+
t.Fatalf("failed-load trace exposed request or project content: %q", trace.String())
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"strings"
|
|
6
|
+
"testing"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// TestServePhaseTraceIsOptInAndPayloadFree proves the benchmark diagnostic is
|
|
10
|
+
// disabled by default and exposes timings without project or request content.
|
|
11
|
+
//
|
|
12
|
+
// 1. Run the same native shard request with tracing disabled and enabled.
|
|
13
|
+
// 2. Capture only the server diagnostic stream, not the response payload.
|
|
14
|
+
// 3. Require the five named phases and reject fixture paths and JSON bodies.
|
|
15
|
+
func TestServePhaseTraceIsOptInAndPayloadFree(t *testing.T) {
|
|
16
|
+
root := graphSessionFixture(t)
|
|
17
|
+
request := "{\"id\":17,\"graphSnapshotVersion\":1}\n"
|
|
18
|
+
oldStderr := stderr
|
|
19
|
+
defer func() { stderr = oldStderr }()
|
|
20
|
+
|
|
21
|
+
t.Setenv(graphPhaseTraceEnvironment, "")
|
|
22
|
+
var disabled bytes.Buffer
|
|
23
|
+
stderr = &disabled
|
|
24
|
+
if code := serveSnapshots(strings.NewReader(request), &bytes.Buffer{}, root, "tsconfig.json"); code != 0 {
|
|
25
|
+
t.Fatalf("disabled trace exited %d", code)
|
|
26
|
+
}
|
|
27
|
+
if disabled.Len() != 0 {
|
|
28
|
+
t.Fatalf("disabled trace wrote %q", disabled.String())
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
t.Setenv(graphPhaseTraceEnvironment, "1")
|
|
32
|
+
var enabled bytes.Buffer
|
|
33
|
+
stderr = &enabled
|
|
34
|
+
if code := serveSnapshots(strings.NewReader(request), &bytes.Buffer{}, root, "tsconfig.json"); code != 0 {
|
|
35
|
+
t.Fatalf("enabled trace exited %d", code)
|
|
36
|
+
}
|
|
37
|
+
trace := enabled.String()
|
|
38
|
+
for _, phase := range []string{
|
|
39
|
+
"native-load",
|
|
40
|
+
"semantic-refresh",
|
|
41
|
+
"shard-export",
|
|
42
|
+
"encode",
|
|
43
|
+
"producer-total",
|
|
44
|
+
} {
|
|
45
|
+
if !strings.Contains(trace, "owner=producer request=17 mode=initial phase="+phase+" durationMs=") {
|
|
46
|
+
t.Fatalf("trace omitted %s: %q", phase, trace)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if strings.Contains(trace, root) || strings.Contains(trace, "graphSnapshotVersion") || strings.Contains(trace, "{\"") {
|
|
50
|
+
t.Fatalf("trace exposed request or project content: %q", trace)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"strings"
|
|
7
|
+
"testing"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// TestServeProtocolNegotiatesIncrementalShards pins the opt-in boundary between
|
|
11
|
+
// existing full-dump clients and the native shard protocol. A negotiated first
|
|
12
|
+
// response carries one complete manifest and transaction, while the following
|
|
13
|
+
// no-op carries neither a dump nor a shard payload.
|
|
14
|
+
func TestServeProtocolNegotiatesIncrementalShards(t *testing.T) {
|
|
15
|
+
root := graphSessionFixture(t)
|
|
16
|
+
input := strings.NewReader("{\"id\":1,\"graphSnapshotVersion\":1}\n{\"id\":2,\"graphSnapshotVersion\":1}\n")
|
|
17
|
+
var output bytes.Buffer
|
|
18
|
+
if code := serveSnapshots(input, &output, root, "tsconfig.json"); code != 0 {
|
|
19
|
+
t.Fatalf("serveSnapshots exited %d", code)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
decoder := json.NewDecoder(&output)
|
|
23
|
+
var initial serveResponse
|
|
24
|
+
var unchanged serveResponse
|
|
25
|
+
if err := decoder.Decode(&initial); err != nil {
|
|
26
|
+
t.Fatal(err)
|
|
27
|
+
}
|
|
28
|
+
if err := decoder.Decode(&unchanged); err != nil {
|
|
29
|
+
t.Fatal(err)
|
|
30
|
+
}
|
|
31
|
+
if initial.Error != "" || initial.Mode != serveModeInitial || !initial.Changed || initial.Dump != nil || initial.Snapshot == nil {
|
|
32
|
+
t.Fatalf("negotiated initial response: %#v", initial)
|
|
33
|
+
}
|
|
34
|
+
snapshot := initial.Snapshot
|
|
35
|
+
if snapshot.ProtocolVersion != graphSnapshotProtocolVersion || snapshot.Sequence != 1 || snapshot.BaseSequence != 0 || snapshot.BaseGeneration != "" {
|
|
36
|
+
t.Fatalf("initial transaction coordinates: %#v", snapshot)
|
|
37
|
+
}
|
|
38
|
+
if len(snapshot.Manifest) == 0 || len(snapshot.Upserts) != len(snapshot.Manifest) || len(snapshot.Deletes) != 0 {
|
|
39
|
+
t.Fatalf("initial transaction is not complete: manifest=%d upserts=%d deletes=%d", len(snapshot.Manifest), len(snapshot.Upserts), len(snapshot.Deletes))
|
|
40
|
+
}
|
|
41
|
+
if unchanged.Error != "" || unchanged.Mode != serveModeUnchanged || unchanged.Changed || unchanged.Dump != nil || unchanged.Snapshot != nil {
|
|
42
|
+
t.Fatalf("negotiated unchanged response: %#v", unchanged)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -39,12 +39,12 @@ func TestServeSessionReloadsCommonJSPackageEntryForESM(t *testing.T) {
|
|
|
39
39
|
t.Fatal(err)
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
writeGraphFile(t, filepath.Join(root, "node_modules", "fixture-package", "dist", "main.ts"), "export function winner(): void {}\
|
|
42
|
+
writeGraphFile(t, filepath.Join(root, "node_modules", "fixture-package", "dist", "main.ts"), "export function winner(): void {}\n")
|
|
43
43
|
dump, mode, changed, err := session.Snapshot()
|
|
44
44
|
if err != nil {
|
|
45
45
|
t.Fatal(err)
|
|
46
46
|
}
|
|
47
|
-
if dump == nil || mode != serveModeReload || !changed || !
|
|
47
|
+
if dump == nil || mode != serveModeReload || !changed || !hasDumpNodeAt(*dump, "winner", "node_modules/fixture-package/dist/main.ts") {
|
|
48
48
|
t.Fatalf("CommonJS package main.ts = dump:%v mode:%q changed:%v", dump != nil, mode, changed)
|
|
49
49
|
}
|
|
50
50
|
}
|