taist 0.2.8 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/toon-formatter.js +12 -8
- package/lib/trace-collector.js +8 -5
- package/lib/trace-reporter.js +26 -2
- package/lib/transform.js +35 -11
- package/package.json +1 -1
package/lib/toon-formatter.js
CHANGED
|
@@ -392,16 +392,20 @@ export class ToonFormatter {
|
|
|
392
392
|
const maxGroups = options.maxGroups ?? 10;
|
|
393
393
|
const showHeader = options.showHeader !== false;
|
|
394
394
|
const debug = process.env.TAIST_DEBUG === 'true';
|
|
395
|
+
const lifecycleDebug = process.env.TAIST_TRACE_LIFECYCLE === 'true';
|
|
395
396
|
const lines = [];
|
|
396
397
|
|
|
397
398
|
if (!traces || traces.length === 0) {
|
|
398
399
|
return 'No traces collected';
|
|
399
400
|
}
|
|
400
401
|
|
|
401
|
-
if (debug) {
|
|
402
|
-
console.log('[formatter] Total traces:', traces.length);
|
|
403
|
-
|
|
404
|
-
|
|
402
|
+
if (debug || lifecycleDebug) {
|
|
403
|
+
console.log('[LIFECYCLE formatter] Total traces:', traces.length);
|
|
404
|
+
// Show all depth 0-2 traces (likely the missing resolver and intermediate calls)
|
|
405
|
+
const lowDepthTraces = traces.filter(t => t.depth <= 2);
|
|
406
|
+
console.log('[LIFECYCLE formatter] Traces with depth 0-2:', lowDepthTraces.length);
|
|
407
|
+
for (const t of lowDepthTraces) {
|
|
408
|
+
console.log('[LIFECYCLE formatter] depth', t.depth, ':', t.name, 'correlationId:', t.correlationId);
|
|
405
409
|
}
|
|
406
410
|
}
|
|
407
411
|
|
|
@@ -411,12 +415,12 @@ export class ToonFormatter {
|
|
|
411
415
|
// Group by traceId
|
|
412
416
|
const groups = this.groupTracesByRequest(sorted);
|
|
413
417
|
|
|
414
|
-
if (debug) {
|
|
415
|
-
console.log('[formatter] Groups:', groups.size);
|
|
418
|
+
if (debug || lifecycleDebug) {
|
|
419
|
+
console.log('[LIFECYCLE formatter] Groups:', groups.size);
|
|
416
420
|
for (const [id, groupTraces] of groups) {
|
|
417
|
-
console.log('[formatter] Group', id, ':', groupTraces.length, 'traces');
|
|
418
421
|
const root = groupTraces.find(t => t.depth === 0);
|
|
419
|
-
|
|
422
|
+
const minDepth = Math.min(...groupTraces.map(t => t.depth));
|
|
423
|
+
console.log('[LIFECYCLE formatter] Group', id, ':', groupTraces.length, 'traces, minDepth:', minDepth, 'root:', root?.name || 'none');
|
|
420
424
|
}
|
|
421
425
|
}
|
|
422
426
|
|
package/lib/trace-collector.js
CHANGED
|
@@ -121,27 +121,30 @@ export class TraceCollector extends EventEmitter {
|
|
|
121
121
|
|
|
122
122
|
_addTrace(trace) {
|
|
123
123
|
const debug = process.env.TAIST_DEBUG === 'true';
|
|
124
|
+
const lifecycleDebug = process.env.TAIST_TRACE_LIFECYCLE === 'true';
|
|
124
125
|
|
|
125
126
|
// Generate trace ID for deduplication
|
|
126
127
|
const traceId =
|
|
127
128
|
trace.id || `${trace.name}-${trace.timestamp}-${trace.type}`;
|
|
128
129
|
|
|
129
130
|
if (this.traceIds.has(traceId)) {
|
|
130
|
-
if (debug) {
|
|
131
|
-
console.log('[collector] DUPLICATE:', trace.name, 'id:', traceId);
|
|
131
|
+
if (debug || lifecycleDebug) {
|
|
132
|
+
console.log('[LIFECYCLE collector] DUPLICATE:', trace.name, 'id:', traceId);
|
|
132
133
|
}
|
|
133
134
|
return; // Duplicate
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
// Apply filter
|
|
137
138
|
if (!this.filter(trace)) {
|
|
138
|
-
if (debug) {
|
|
139
|
-
console.log('[collector] FILTERED:', trace.name);
|
|
139
|
+
if (debug || lifecycleDebug) {
|
|
140
|
+
console.log('[LIFECYCLE collector] FILTERED:', trace.name);
|
|
140
141
|
}
|
|
141
142
|
return; // Filtered out
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
if (
|
|
145
|
+
if (lifecycleDebug) {
|
|
146
|
+
console.log('[LIFECYCLE collector] RECEIVED:', trace.name, 'depth:', trace.depth, 'correlationId:', trace.correlationId);
|
|
147
|
+
} else if (debug) {
|
|
145
148
|
console.log('[collector] RECEIVED:', trace.name, 'depth:', trace.depth, 'correlationId:', trace.correlationId);
|
|
146
149
|
}
|
|
147
150
|
|
package/lib/trace-reporter.js
CHANGED
|
@@ -245,7 +245,13 @@ export class TraceReporter extends EventEmitter {
|
|
|
245
245
|
* Report a single trace event
|
|
246
246
|
*/
|
|
247
247
|
report(trace) {
|
|
248
|
-
|
|
248
|
+
// Use TAIST_TRACE_LIFECYCLE for detailed lifecycle logging (always via console.log)
|
|
249
|
+
const lifecycleDebug = process.env.TAIST_TRACE_LIFECYCLE === 'true';
|
|
250
|
+
|
|
251
|
+
if (this.closed) {
|
|
252
|
+
if (lifecycleDebug) console.log('[LIFECYCLE reporter] REJECTED (closed):', trace.name);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
249
255
|
|
|
250
256
|
// Update socketPath from env if not set (env may be set after construction)
|
|
251
257
|
// This handles the case where vitest-reporter sets TAIST_COLLECTOR_SOCKET
|
|
@@ -256,6 +262,10 @@ export class TraceReporter extends EventEmitter {
|
|
|
256
262
|
this._setupExitHandlers();
|
|
257
263
|
}
|
|
258
264
|
|
|
265
|
+
if (lifecycleDebug) {
|
|
266
|
+
console.log('[LIFECYCLE reporter] BUFFERED:', trace.name, 'depth:', trace.depth, 'correlationId:', trace.correlationId, 'socketPath:', this.socketPath, 'connected:', this.connected);
|
|
267
|
+
}
|
|
268
|
+
|
|
259
269
|
logger.debug("[reporter] report() called:", trace.name, trace.type);
|
|
260
270
|
|
|
261
271
|
this.buffer.push(trace);
|
|
@@ -280,26 +290,39 @@ export class TraceReporter extends EventEmitter {
|
|
|
280
290
|
* Async flush - sends buffered traces to collector
|
|
281
291
|
*/
|
|
282
292
|
async flush() {
|
|
293
|
+
const lifecycleDebug = process.env.TAIST_TRACE_LIFECYCLE === 'true';
|
|
294
|
+
|
|
283
295
|
if (this.buffer.length === 0 || this.closed) {
|
|
296
|
+
if (lifecycleDebug && this.closed) console.log('[LIFECYCLE reporter] flush() skipped - reporter closed');
|
|
284
297
|
return;
|
|
285
298
|
}
|
|
286
299
|
|
|
287
300
|
// Ensure connected
|
|
288
301
|
if (!this.connected && this.socketPath) {
|
|
289
302
|
try {
|
|
303
|
+
if (lifecycleDebug) console.log('[LIFECYCLE reporter] flush() connecting to:', this.socketPath);
|
|
290
304
|
await this.connect();
|
|
291
|
-
} catch {
|
|
305
|
+
} catch (e) {
|
|
306
|
+
if (lifecycleDebug) console.log('[LIFECYCLE reporter] flush() connect failed:', e.message);
|
|
292
307
|
// Connection failed, keep traces buffered
|
|
293
308
|
return;
|
|
294
309
|
}
|
|
295
310
|
}
|
|
296
311
|
|
|
297
312
|
if (!this.connected) {
|
|
313
|
+
if (lifecycleDebug) console.log('[LIFECYCLE reporter] flush() skipped - not connected, no socketPath');
|
|
298
314
|
return;
|
|
299
315
|
}
|
|
300
316
|
|
|
301
317
|
const traces = this.buffer.splice(0, this.buffer.length);
|
|
302
318
|
|
|
319
|
+
if (lifecycleDebug) {
|
|
320
|
+
console.log('[LIFECYCLE reporter] SENDING', traces.length, 'traces to collector');
|
|
321
|
+
for (const t of traces) {
|
|
322
|
+
console.log('[LIFECYCLE reporter] -', t.name, 'depth:', t.depth);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
303
326
|
const message = JSON.stringify({
|
|
304
327
|
type: "batch",
|
|
305
328
|
workerId: this.workerId,
|
|
@@ -309,6 +332,7 @@ export class TraceReporter extends EventEmitter {
|
|
|
309
332
|
return new Promise((resolve, reject) => {
|
|
310
333
|
this.socket.write(message + "\n", (err) => {
|
|
311
334
|
if (err) {
|
|
335
|
+
if (lifecycleDebug) console.log('[LIFECYCLE reporter] SEND FAILED:', err.message);
|
|
312
336
|
// Put traces back in buffer on failure
|
|
313
337
|
this.buffer.unshift(...traces);
|
|
314
338
|
reject(err);
|
package/lib/transform.js
CHANGED
|
@@ -317,6 +317,27 @@ const __taist_module = "${moduleName}";
|
|
|
317
317
|
// This avoids TDZ issues with circular dependencies
|
|
318
318
|
// We'll add __taist_instrumentClass() calls at the end instead of re-exporting
|
|
319
319
|
|
|
320
|
+
// For OBJECTS: Rename and re-export wrapped version (BUILD-TIME instrumentation)
|
|
321
|
+
// This is critical for GraphQL resolvers and similar nested object patterns.
|
|
322
|
+
// Runtime instrumentation doesn't work because bundlers capture the original reference.
|
|
323
|
+
for (const exp of objectExports) {
|
|
324
|
+
if (exp.declaration === "inline") {
|
|
325
|
+
// Inline exports: export const resolver = { ... }
|
|
326
|
+
// Rename: export const resolver -> const __taist_orig_resolver
|
|
327
|
+
transformed = transformed.replace(
|
|
328
|
+
new RegExp(`export\\s+const\\s+${exp.name}\\s*=\\s*\\{`, "g"),
|
|
329
|
+
`const __taist_orig_${exp.name} = {`
|
|
330
|
+
);
|
|
331
|
+
} else if (exp.declaration === "named") {
|
|
332
|
+
// Named exports: const resolver = { ... }; export { resolver }
|
|
333
|
+
// Rename: const resolver -> const __taist_orig_resolver
|
|
334
|
+
transformed = transformed.replace(
|
|
335
|
+
new RegExp(`\\b(const|let|var)\\s+${exp.name}\\s*=\\s*\\{`, "g"),
|
|
336
|
+
`$1 __taist_orig_${exp.name} = {`
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
320
341
|
// For FUNCTIONS: Rename and re-export (original behavior)
|
|
321
342
|
for (const exp of functionExports) {
|
|
322
343
|
if (exp.declaration === "inline") {
|
|
@@ -354,10 +375,12 @@ const __taist_module = "${moduleName}";
|
|
|
354
375
|
}
|
|
355
376
|
}
|
|
356
377
|
|
|
357
|
-
// Remove named export statements for FUNCTIONS we're replacing (not classes)
|
|
378
|
+
// Remove named export statements for FUNCTIONS and OBJECTS we're replacing (not classes)
|
|
358
379
|
// Match: export { Name1, Name2 } and remove names we're wrapping
|
|
359
380
|
const namedFunctionExportNames = functionExports.filter(e => e.declaration === "named").map(e => e.name);
|
|
360
|
-
|
|
381
|
+
const namedObjectExportNames = objectExports.filter(e => e.declaration === "named").map(e => e.name);
|
|
382
|
+
const allNamedExportsToReplace = [...namedFunctionExportNames, ...namedObjectExportNames];
|
|
383
|
+
if (allNamedExportsToReplace.length > 0) {
|
|
361
384
|
transformed = transformed.replace(
|
|
362
385
|
/export\s*\{([^}]+)\}/g,
|
|
363
386
|
(match, names) => {
|
|
@@ -365,7 +388,7 @@ const __taist_module = "${moduleName}";
|
|
|
365
388
|
.map(n => n.trim())
|
|
366
389
|
.filter(n => {
|
|
367
390
|
const name = n.split(/\s+as\s+/)[0].trim();
|
|
368
|
-
return !
|
|
391
|
+
return !allNamedExportsToReplace.includes(name);
|
|
369
392
|
});
|
|
370
393
|
if (remaining.length === 0) {
|
|
371
394
|
return '// export moved to end';
|
|
@@ -418,12 +441,13 @@ const __taist_module = "${moduleName}";
|
|
|
418
441
|
})
|
|
419
442
|
.join("\n");
|
|
420
443
|
|
|
421
|
-
// Add
|
|
422
|
-
//
|
|
423
|
-
|
|
444
|
+
// Add wrapped re-exports for OBJECTS at the end (BUILD-TIME instrumentation)
|
|
445
|
+
// This wraps nested methods like GraphQL resolvers: resolver.Mutation.upsertOrder
|
|
446
|
+
// Runtime instrumentation doesn't work because bundlers capture original references
|
|
447
|
+
const objectReexports = objectExports
|
|
424
448
|
.map((exp) => {
|
|
425
449
|
const nameExpr = `(__taist_module === "${exp.name}" ? "${exp.name}" : __taist_module + ".${exp.name}")`;
|
|
426
|
-
return `__taist_instrumentObject(${exp.name}, ${nameExpr});`;
|
|
450
|
+
return `export const ${exp.name} = __taist_instrumentObject(__taist_orig_${exp.name}, ${nameExpr});`;
|
|
427
451
|
})
|
|
428
452
|
.join("\n");
|
|
429
453
|
|
|
@@ -433,12 +457,12 @@ const __taist_module = "${moduleName}";
|
|
|
433
457
|
transformed += `// Wrapped function exports\n${functionReexports}\n`;
|
|
434
458
|
}
|
|
435
459
|
|
|
436
|
-
if (
|
|
437
|
-
transformed += `//
|
|
460
|
+
if (objectReexports) {
|
|
461
|
+
transformed += `// Wrapped object exports (build-time instrumentation for nested methods)\n${objectReexports}\n`;
|
|
438
462
|
}
|
|
439
463
|
|
|
440
|
-
if (
|
|
441
|
-
transformed += `// In-place
|
|
464
|
+
if (classInstrumentations) {
|
|
465
|
+
transformed += `// In-place class instrumentation (preserves hoisting)\n${classInstrumentations}\n`;
|
|
442
466
|
}
|
|
443
467
|
|
|
444
468
|
// Add default exports at the end (after the wrapped versions are defined)
|