@alloy-js/core 0.23.0-dev.10 → 0.23.0-dev.11

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 (131) hide show
  1. package/dist/devtools/index.html +29 -17
  2. package/dist/src/binder.d.ts.map +1 -1
  3. package/dist/src/binder.js +5 -0
  4. package/dist/src/binder.js.map +1 -1
  5. package/dist/src/components/For.d.ts.map +1 -1
  6. package/dist/src/components/For.js +1 -1
  7. package/dist/src/components/For.js.map +1 -1
  8. package/dist/src/components/List.d.ts.map +1 -1
  9. package/dist/src/components/List.js +1 -1
  10. package/dist/src/components/List.js.map +1 -1
  11. package/dist/src/components/Switch.d.ts.map +1 -1
  12. package/dist/src/components/Switch.js +1 -1
  13. package/dist/src/components/Switch.js.map +1 -1
  14. package/dist/src/debug/diagnostics.test.js +3 -2
  15. package/dist/src/debug/diagnostics.test.js.map +1 -1
  16. package/dist/src/debug/effects.d.ts +12 -4
  17. package/dist/src/debug/effects.d.ts.map +1 -1
  18. package/dist/src/debug/effects.js +182 -52
  19. package/dist/src/debug/effects.js.map +1 -1
  20. package/dist/src/debug/effects.test.js +213 -41
  21. package/dist/src/debug/effects.test.js.map +1 -1
  22. package/dist/src/debug/files.d.ts.map +1 -1
  23. package/dist/src/debug/files.js +7 -18
  24. package/dist/src/debug/files.js.map +1 -1
  25. package/dist/src/debug/files.test.js +13 -36
  26. package/dist/src/debug/files.test.js.map +1 -1
  27. package/dist/src/debug/index.d.ts +4 -2
  28. package/dist/src/debug/index.d.ts.map +1 -1
  29. package/dist/src/debug/index.js +4 -2
  30. package/dist/src/debug/index.js.map +1 -1
  31. package/dist/src/debug/message-format.test.d.ts +2 -0
  32. package/dist/src/debug/message-format.test.d.ts.map +1 -0
  33. package/dist/src/debug/message-format.test.js +700 -0
  34. package/dist/src/debug/message-format.test.js.map +1 -0
  35. package/dist/src/debug/render-tree-orphans.test.d.ts +2 -0
  36. package/dist/src/debug/render-tree-orphans.test.d.ts.map +1 -0
  37. package/dist/src/debug/render-tree-orphans.test.js +297 -0
  38. package/dist/src/debug/render-tree-orphans.test.js.map +1 -0
  39. package/dist/src/debug/render.d.ts.map +1 -1
  40. package/dist/src/debug/render.js +83 -130
  41. package/dist/src/debug/render.js.map +1 -1
  42. package/dist/src/debug/render.test.js +91 -128
  43. package/dist/src/debug/render.test.js.map +1 -1
  44. package/dist/src/debug/symbols.d.ts +6 -5
  45. package/dist/src/debug/symbols.d.ts.map +1 -1
  46. package/dist/src/debug/symbols.js +46 -23
  47. package/dist/src/debug/symbols.js.map +1 -1
  48. package/dist/src/debug/symbols.test.js +15 -26
  49. package/dist/src/debug/symbols.test.js.map +1 -1
  50. package/dist/src/debug/trace-writer.d.ts +55 -0
  51. package/dist/src/debug/trace-writer.d.ts.map +1 -0
  52. package/dist/src/debug/trace-writer.js +658 -0
  53. package/dist/src/debug/trace-writer.js.map +1 -0
  54. package/dist/src/debug/trace.d.ts +10 -10
  55. package/dist/src/debug/trace.d.ts.map +1 -1
  56. package/dist/src/debug/trace.js +23 -20
  57. package/dist/src/debug/trace.js.map +1 -1
  58. package/dist/src/devtools/devtools-protocol.d.ts +318 -161
  59. package/dist/src/devtools/devtools-protocol.d.ts.map +1 -1
  60. package/dist/src/devtools/devtools-server.browser.d.ts +0 -5
  61. package/dist/src/devtools/devtools-server.browser.d.ts.map +1 -1
  62. package/dist/src/devtools/devtools-server.browser.js +0 -3
  63. package/dist/src/devtools/devtools-server.browser.js.map +1 -1
  64. package/dist/src/devtools/devtools-server.d.ts +0 -6
  65. package/dist/src/devtools/devtools-server.d.ts.map +1 -1
  66. package/dist/src/devtools/devtools-server.js +212 -24
  67. package/dist/src/devtools/devtools-server.js.map +1 -1
  68. package/dist/src/devtools/devtools-transport.d.ts +2 -2
  69. package/dist/src/devtools/devtools-transport.d.ts.map +1 -1
  70. package/dist/src/devtools/devtools-transport.js +2 -2
  71. package/dist/src/devtools/devtools-transport.js.map +1 -1
  72. package/dist/src/devtools-entry.browser.d.ts +1 -1
  73. package/dist/src/devtools-entry.browser.d.ts.map +1 -1
  74. package/dist/src/devtools-entry.browser.js.map +1 -1
  75. package/dist/src/devtools-entry.d.ts +1 -1
  76. package/dist/src/devtools-entry.d.ts.map +1 -1
  77. package/dist/src/devtools-entry.js.map +1 -1
  78. package/dist/src/diagnostics.d.ts.map +1 -1
  79. package/dist/src/diagnostics.js +5 -5
  80. package/dist/src/diagnostics.js.map +1 -1
  81. package/dist/src/reactivity.d.ts +13 -2
  82. package/dist/src/reactivity.d.ts.map +1 -1
  83. package/dist/src/reactivity.js +96 -13
  84. package/dist/src/reactivity.js.map +1 -1
  85. package/dist/src/render.d.ts.map +1 -1
  86. package/dist/src/render.js +84 -30
  87. package/dist/src/render.js.map +1 -1
  88. package/dist/src/scheduler.d.ts +5 -0
  89. package/dist/src/scheduler.d.ts.map +1 -1
  90. package/dist/src/scheduler.js +94 -23
  91. package/dist/src/scheduler.js.map +1 -1
  92. package/dist/src/utils.d.ts.map +1 -1
  93. package/dist/src/utils.js +11 -5
  94. package/dist/src/utils.js.map +1 -1
  95. package/dist/testing/devtools-utils.d.ts +12 -3
  96. package/dist/testing/devtools-utils.d.ts.map +1 -1
  97. package/dist/testing/devtools-utils.js +26 -4
  98. package/dist/testing/devtools-utils.js.map +1 -1
  99. package/dist/tsconfig.tsbuildinfo +1 -1
  100. package/package.json +1 -1
  101. package/src/binder.ts +47 -38
  102. package/src/components/For.tsx +14 -10
  103. package/src/components/List.tsx +7 -4
  104. package/src/components/Switch.tsx +11 -7
  105. package/src/debug/diagnostics.test.tsx +3 -2
  106. package/src/debug/effects.test.tsx +248 -36
  107. package/src/debug/effects.ts +276 -62
  108. package/src/debug/files.test.tsx +15 -35
  109. package/src/debug/files.ts +11 -11
  110. package/src/debug/index.ts +4 -0
  111. package/src/debug/message-format.test.tsx +759 -0
  112. package/src/debug/render-tree-orphans.test.tsx +344 -0
  113. package/src/debug/render.test.tsx +96 -118
  114. package/src/debug/render.ts +183 -124
  115. package/src/debug/symbols.test.tsx +19 -20
  116. package/src/debug/symbols.ts +106 -23
  117. package/src/debug/trace-writer.ts +969 -0
  118. package/src/debug/trace.ts +25 -28
  119. package/src/devtools/devtools-protocol.ts +361 -176
  120. package/src/devtools/devtools-server.browser.ts +0 -9
  121. package/src/devtools/devtools-server.ts +210 -32
  122. package/src/devtools/devtools-transport.ts +4 -4
  123. package/src/devtools-entry.browser.ts +11 -15
  124. package/src/devtools-entry.ts +9 -15
  125. package/src/diagnostics.ts +14 -5
  126. package/src/reactivity.ts +113 -17
  127. package/src/render.ts +104 -30
  128. package/src/scheduler.ts +145 -26
  129. package/src/utils.tsx +7 -4
  130. package/temp/api.json +142 -20
  131. package/testing/devtools-utils.ts +46 -4
@@ -1,7 +1,14 @@
1
1
  import { isReactive, isRef } from "@vue/reactivity";
2
2
  import {
3
- emitDevtoolsMessage,
4
- isDevtoolsEnabled,
3
+ formatReactivePropertyLabel,
4
+ getReactiveCreationLocation,
5
+ nextReactiveId,
6
+ } from "../reactivity.js";
7
+ import { insertEdge, insertEffect, insertRef } from "./trace-writer.js";
8
+ import {
9
+ isDebugEnabled,
10
+ isTraceEnabled,
11
+ logDevtoolsMessage,
5
12
  TracePhase,
6
13
  traceType,
7
14
  } from "./trace.js";
@@ -22,15 +29,19 @@ export interface EffectDebugInfo {
22
29
  name?: string;
23
30
  type?: string;
24
31
  createdAt?: SourceLocation;
32
+ contextId?: number;
33
+ ownerContextId?: number | null;
34
+ component?: string;
25
35
  lastTriggeredByRefId?: number;
26
- lastTriggeredAt?: SourceLocation;
27
36
  }
28
37
 
29
38
  export interface RefDebugInfo {
30
39
  id: number;
31
40
  kind?: string;
41
+ label?: string;
32
42
  createdAt?: SourceLocation;
33
43
  createdByEffectId?: number;
44
+ isApproxLocation?: boolean;
34
45
  }
35
46
 
36
47
  export interface EffectEdgeDebugInfo {
@@ -42,33 +53,50 @@ export interface EffectEdgeDebugInfo {
42
53
  targetKind?: "ref" | "target";
43
54
  targetLabel?: string;
44
55
  targetKey?: string | number;
45
- location?: SourceLocation;
46
56
  }
47
57
 
48
58
  const effects = new Map<number, EffectDebugInfo>();
49
59
  const refs = new Map<number, RefDebugInfo>();
50
60
  let effectIdCounter = 1;
51
61
  let edgeEventIdCounter = 1;
52
- let nonRefTargetIdCounter = 1;
53
62
  const nonRefTargetIds = new WeakMap<object, number>();
54
63
  const primitiveTargetIds = new Map<unknown, number>();
55
64
 
56
- const STACK_LINE = /\s*at\s+(?:.+?\s+\()?(.+?):(\d+):(\d+)\)?$/;
65
+ // Alloy-internal paths to skip when capturing source locations.
66
+ // We skip core infrastructure (reactivity, render, debug, scheduler, etc.)
67
+ // but allow component and symbol frames through so the location points at
68
+ // the component/symbol that created the effect.
69
+ // These patterns match both src/ and dist/src/ paths.
70
+ const CORE_INTERNAL_PATHS = [
71
+ "/core/src/reactivity",
72
+ "/core/src/render",
73
+ "/core/src/scheduler",
74
+ "/core/src/debug/",
75
+ "/core/src/devtools/",
76
+ "/core/src/resource",
77
+ "/core/src/context",
78
+ "/core/src/tracer",
79
+ "/core/src/reactive-union-set",
80
+ "/core/src/utils",
81
+ "/core/dist/src/reactivity",
82
+ "/core/dist/src/render",
83
+ "/core/dist/src/scheduler",
84
+ "/core/dist/src/debug/",
85
+ "/core/dist/src/devtools/",
86
+ "/core/dist/src/resource",
87
+ "/core/dist/src/context",
88
+ "/core/dist/src/tracer",
89
+ "/core/dist/src/reactive-union-set",
90
+ "/core/dist/src/utils",
91
+ ];
92
+
57
93
  const STACK_SKIP = [
58
94
  "node:internal",
59
- "/node_modules/",
60
- "\\node_modules\\",
61
95
  "/@vue/",
62
96
  "\\@vue\\",
63
- "/@alloy-js/",
64
- "\\@alloy-js\\",
65
- "/packages/core/src/reactivity",
66
- "/packages/core/src/devtools/effects-debug",
67
- "/packages/core/dist/src/reactivity",
68
- "\\packages\\core\\dist\\src\\reactivity",
69
- "/packages/core/dist/src/devtools/effects-debug",
70
- "\\packages\\core\\dist\\src\\devtools\\effects-debug",
71
97
  "captureSourceLocation",
98
+ ...CORE_INTERNAL_PATHS,
99
+ ...CORE_INTERNAL_PATHS.map((p) => p.replace(/\//g, "\\")),
72
100
  ];
73
101
 
74
102
  const VUE_REACTIVITY_MARKERS = [
@@ -80,70 +108,185 @@ const VUE_REACTIVITY_MARKERS = [
80
108
  "\\@vue\\",
81
109
  ];
82
110
 
83
- function isVueReactivityLine(line: string) {
84
- return VUE_REACTIVITY_MARKERS.some((marker) => line.includes(marker));
111
+ // ─────────────────────────────────────────────────────────────────────────────
112
+ // Fast source location capture using V8 structured CallSite API
113
+ // ─────────────────────────────────────────────────────────────────────────────
114
+
115
+ // Lazily loaded findSourceMap from node:module
116
+ let findSourceMap:
117
+ | ((path: string) =>
118
+ | {
119
+ findEntry: (
120
+ line: number,
121
+ col: number,
122
+ ) =>
123
+ | {
124
+ originalSource: string;
125
+ originalLine: number;
126
+ originalColumn: number;
127
+ }
128
+ | undefined;
129
+ }
130
+ | undefined)
131
+ | undefined;
132
+ let findSourceMapLoaded = false;
133
+ let realpathSync: ((path: string) => string) | undefined;
134
+ // Cache realpath lookups to avoid repeated fs calls
135
+ const realpathCache = new Map<string, string>();
136
+
137
+ function loadFindSourceMap() {
138
+ if (findSourceMapLoaded) return;
139
+ findSourceMapLoaded = true;
140
+ // process.getBuiltinModule works in both ESM and CJS contexts
141
+ try {
142
+ const mod = process.getBuiltinModule?.("node:module") as
143
+ | typeof import("node:module")
144
+ | undefined;
145
+ if (mod && typeof mod.findSourceMap === "function") {
146
+ findSourceMap = mod.findSourceMap as typeof findSourceMap;
147
+ }
148
+ } catch {
149
+ // not available
150
+ }
151
+ try {
152
+ const fs = process.getBuiltinModule?.("node:fs") as
153
+ | typeof import("node:fs")
154
+ | undefined;
155
+ if (fs) {
156
+ realpathSync = fs.realpathSync;
157
+ }
158
+ } catch {
159
+ // not available
160
+ }
85
161
  }
86
162
 
87
- function parseStackLine(
88
- line: string,
89
- stack?: string,
90
- ): SourceLocation | undefined {
91
- const match = STACK_LINE.exec(line);
92
- if (!match) return undefined;
93
- const [, fileName, lineNumber, columnNumber] = match;
163
+ function getRealPath(fileName: string): string {
164
+ if (!realpathSync) return fileName;
165
+ let real = realpathCache.get(fileName);
166
+ if (real === undefined) {
167
+ try {
168
+ real = realpathSync(fileName);
169
+ } catch {
170
+ real = fileName;
171
+ }
172
+ realpathCache.set(fileName, real);
173
+ }
174
+ return real;
175
+ }
176
+
177
+ function isSkipFile(fileName: string): boolean {
178
+ for (const skip of STACK_SKIP) {
179
+ if (fileName.includes(skip)) return true;
180
+ }
181
+ return false;
182
+ }
183
+
184
+ function isVueReactivityFile(fileName: string): boolean {
185
+ for (const marker of VUE_REACTIVITY_MARKERS) {
186
+ if (fileName.includes(marker)) return true;
187
+ }
188
+ return false;
189
+ }
190
+
191
+ function resolveSourceMap(
192
+ fileName: string,
193
+ line: number,
194
+ col: number,
195
+ ): { fileName: string; line: number; col: number } {
196
+ if (!findSourceMap) return { fileName, line, col };
197
+ // pnpm uses symlinks; findSourceMap only matches the real path
198
+ const real = getRealPath(fileName);
199
+ const map = findSourceMap(real);
200
+ if (!map) return { fileName, line, col };
201
+ const entry = map.findEntry(line - 1, col - 1);
202
+ if (!entry) return { fileName, line, col };
94
203
  return {
95
- fileName,
96
- lineNumber: Number(lineNumber),
97
- columnNumber: Number(columnNumber),
98
- stack,
204
+ fileName: entry.originalSource,
205
+ line: entry.originalLine + 1,
206
+ col: entry.originalColumn + 1,
99
207
  };
100
208
  }
101
209
 
210
+ // V8 structured stack capture — avoids string formatting entirely
211
+ const structuredPrepare = (
212
+ _err: Error,
213
+ callSites: NodeJS.CallSite[],
214
+ ): NodeJS.CallSite[] => callSites;
215
+
216
+ function captureCallSites(): NodeJS.CallSite[] {
217
+ const orig = Error.prepareStackTrace;
218
+ Error.prepareStackTrace = structuredPrepare;
219
+ const obj: { stack?: NodeJS.CallSite[] } = {};
220
+ Error.captureStackTrace(obj, captureCallSites);
221
+ const sites = obj.stack ?? [];
222
+ Error.prepareStackTrace = orig;
223
+ return sites;
224
+ }
225
+
102
226
  export function captureSourceLocation(
103
227
  skipReactives = true,
104
228
  ): SourceLocation | undefined {
105
- if (!isDevtoolsEnabled()) return undefined;
106
- const stack = new Error().stack;
107
- if (!stack) {
108
- return { stack: "" };
109
- }
110
- const lines = stack.split("\n").slice(1);
111
- for (const line of lines) {
112
- if (STACK_SKIP.some((skip) => line.includes(skip))) continue;
113
- const parsed = parseStackLine(line, stack);
114
- if (parsed) return parsed;
229
+ if (!isDebugEnabled()) return undefined;
230
+ loadFindSourceMap();
231
+
232
+ const sites = captureCallSites();
233
+
234
+ // First pass: skip internal/framework frames
235
+ for (const site of sites) {
236
+ const fn = site.getFileName();
237
+ if (!fn) continue;
238
+ if (isSkipFile(fn)) continue;
239
+ if (skipReactives && isVueReactivityFile(fn)) continue;
240
+
241
+ const line = site.getLineNumber() ?? 0;
242
+ const col = site.getColumnNumber() ?? 0;
243
+ const resolved = resolveSourceMap(fn, line, col);
244
+ return {
245
+ fileName: resolved.fileName,
246
+ lineNumber: resolved.line,
247
+ columnNumber: resolved.col,
248
+ };
115
249
  }
116
250
 
251
+ // Second pass without reactive filter
117
252
  if (skipReactives) {
118
- for (const line of lines) {
119
- if (STACK_SKIP.some((skip) => line.includes(skip))) continue;
120
- if (isVueReactivityLine(line)) continue;
121
- const parsed = parseStackLine(line, stack);
122
- if (parsed) return parsed;
253
+ for (const site of sites) {
254
+ const fn = site.getFileName();
255
+ if (!fn) continue;
256
+ if (isSkipFile(fn)) continue;
257
+
258
+ const line = site.getLineNumber() ?? 0;
259
+ const col = site.getColumnNumber() ?? 0;
260
+ const resolved = resolveSourceMap(fn, line, col);
261
+ return {
262
+ fileName: resolved.fileName,
263
+ lineNumber: resolved.line,
264
+ columnNumber: resolved.col,
265
+ };
123
266
  }
124
267
  }
125
268
 
126
- return { stack };
269
+ return undefined;
127
270
  }
128
271
 
129
272
  function getNonRefTargetId(target: unknown): number {
130
273
  if (typeof target === "object" && target !== null) {
131
274
  const existing = nonRefTargetIds.get(target);
132
275
  if (existing) return existing;
133
- const id = nonRefTargetIdCounter++;
276
+ const id = nextReactiveId();
134
277
  nonRefTargetIds.set(target, id);
135
278
  return id;
136
279
  }
137
280
  if (typeof target === "function") {
138
281
  const existing = nonRefTargetIds.get(target as object);
139
282
  if (existing) return existing;
140
- const id = nonRefTargetIdCounter++;
283
+ const id = nextReactiveId();
141
284
  nonRefTargetIds.set(target as object, id);
142
285
  return id;
143
286
  }
144
287
  const existing = primitiveTargetIds.get(target);
145
288
  if (existing) return existing;
146
- const id = nonRefTargetIdCounter++;
289
+ const id = nextReactiveId();
147
290
  primitiveTargetIds.set(target, id);
148
291
  return id;
149
292
  }
@@ -192,11 +335,11 @@ function buildEffectTargetInfo(input: {
192
335
  }
193
336
 
194
337
  function emitEffect(message: { type: string; [key: string]: unknown }) {
195
- emitDevtoolsMessage(message);
338
+ logDevtoolsMessage(message);
196
339
  }
197
340
 
198
341
  export function update(input: Partial<EffectDebugInfo> & { id: number }) {
199
- if (!isDevtoolsEnabled()) return;
342
+ if (!isDebugEnabled()) return;
200
343
  const existing = effects.get(input.id);
201
344
  if (!existing) return;
202
345
  const next: EffectDebugInfo = { ...existing, ...input };
@@ -214,16 +357,32 @@ export function register(input: {
214
357
  contextId?: number;
215
358
  ownerContextId?: number | null;
216
359
  }): number {
217
- if (!isDevtoolsEnabled()) return -1;
360
+ if (!isDebugEnabled()) return -1;
218
361
  const id = effectIdCounter++;
219
362
  const info: EffectDebugInfo = {
220
363
  id,
221
364
  name: input.name,
222
365
  type: input.type,
223
366
  createdAt: input.createdAt ?? captureSourceLocation(),
367
+ contextId: input.contextId,
368
+ ownerContextId: input.ownerContextId ?? null,
224
369
  };
225
370
  effects.set(id, info);
226
371
  emitEffect({ type: traceType(TracePhase.effect.effectAdded), effect: info });
372
+
373
+ if (isTraceEnabled()) {
374
+ insertEffect(
375
+ id,
376
+ input.name,
377
+ input.type,
378
+ input.contextId,
379
+ input.ownerContextId ?? null,
380
+ info.createdAt?.fileName,
381
+ info.createdAt?.lineNumber,
382
+ info.createdAt?.columnNumber,
383
+ );
384
+ }
385
+
227
386
  return id;
228
387
  }
229
388
 
@@ -233,42 +392,90 @@ export function registerRef(input: {
233
392
  createdAt?: SourceLocation;
234
393
  createdByEffectId?: number;
235
394
  isInfrastructure?: boolean;
395
+ isApproxLocation?: boolean;
396
+ label?: string;
236
397
  }) {
237
- if (!isDevtoolsEnabled()) return;
398
+ if (!isDebugEnabled()) return;
238
399
  if (refs.has(input.id)) return;
239
400
  const info: RefDebugInfo = {
240
401
  id: input.id,
241
402
  kind: input.kind,
242
403
  createdAt: input.createdAt ?? captureSourceLocation(),
243
404
  createdByEffectId: input.createdByEffectId,
405
+ label: input.label,
406
+ isApproxLocation: input.isApproxLocation,
244
407
  };
245
408
  refs.set(input.id, info);
246
409
  emitEffect({ type: traceType(TracePhase.effect.refAdded), ref: info });
410
+
411
+ if (isTraceEnabled()) {
412
+ insertRef(
413
+ input.id,
414
+ input.kind,
415
+ input.createdByEffectId,
416
+ info.createdAt?.fileName,
417
+ info.createdAt?.lineNumber,
418
+ info.createdAt?.columnNumber,
419
+ input.label,
420
+ input.isApproxLocation,
421
+ );
422
+ }
247
423
  }
248
424
 
249
425
  export function ensureRef(input: { id: number; kind?: string }) {
250
- if (!isDevtoolsEnabled()) return;
426
+ if (!isDebugEnabled()) return;
251
427
  if (refs.has(input.id)) return;
252
428
  registerRef({ id: input.id, kind: input.kind });
253
429
  }
254
430
 
431
+ export function ensureReactivePropertyRef(input: {
432
+ id: number;
433
+ target: object;
434
+ key: string | number;
435
+ }) {
436
+ if (!isDebugEnabled()) return;
437
+ if (refs.has(input.id)) return;
438
+ const label = formatReactivePropertyLabel(input.target, input.key);
439
+ const createdAt = getReactiveCreationLocation(input.target);
440
+ // If the reactive wasn't created via alloy's shallowReactive wrapper,
441
+ // createdAt is undefined and registerRef falls back to captureSourceLocation
442
+ // (the first track site). Flag this as approximate.
443
+ const isApproxLocation = !createdAt;
444
+ registerRef({
445
+ id: input.id,
446
+ kind: "reactive-property",
447
+ label,
448
+ createdAt,
449
+ isApproxLocation,
450
+ });
451
+ }
452
+
255
453
  export function track(input: {
256
454
  effectId: number;
257
455
  target: unknown;
258
456
  refId?: number;
259
457
  targetKey?: unknown;
260
- location?: SourceLocation;
261
458
  }) {
262
- if (!isDevtoolsEnabled()) return;
459
+ if (!isDebugEnabled()) return;
263
460
  const edge: EffectEdgeDebugInfo = {
264
461
  id: edgeEventIdCounter++,
265
462
  type: "track",
266
463
  effectId: input.effectId,
267
464
  ...buildEffectTargetInfo({ target: input.target, refId: input.refId }),
268
465
  targetKey: sanitizeTargetKey(input.targetKey),
269
- location: input.location ?? captureSourceLocation(),
270
466
  };
271
467
  emitEffect({ type: traceType(TracePhase.effect.track), edge });
468
+
469
+ if (isTraceEnabled()) {
470
+ insertEdge(
471
+ "track",
472
+ input.effectId,
473
+ edge.refId,
474
+ edge.targetId,
475
+ edge.targetKey,
476
+ undefined,
477
+ );
478
+ }
272
479
  }
273
480
 
274
481
  export function trigger(input: {
@@ -276,25 +483,33 @@ export function trigger(input: {
276
483
  target: unknown;
277
484
  refId?: number;
278
485
  targetKey?: unknown;
279
- location?: SourceLocation;
280
486
  kind?: "trigger" | "triggered-by";
281
487
  causedBy?: number;
282
488
  }) {
283
- if (!isDevtoolsEnabled()) return;
489
+ if (!isDebugEnabled()) return;
284
490
  const edge: EffectEdgeDebugInfo = {
285
491
  id: edgeEventIdCounter++,
286
- type: input.kind ?? "triggered-by",
492
+ type: input.kind ?? "trigger",
287
493
  effectId: input.effectId,
288
494
  ...buildEffectTargetInfo({ target: input.target, refId: input.refId }),
289
495
  targetKey: sanitizeTargetKey(input.targetKey),
290
- location: input.location ?? captureSourceLocation(),
291
496
  };
292
497
  emitEffect({ type: traceType(TracePhase.effect.trigger), edge });
293
498
 
499
+ if (isTraceEnabled()) {
500
+ insertEdge(
501
+ edge.type,
502
+ input.effectId,
503
+ edge.refId,
504
+ edge.targetId,
505
+ edge.targetKey,
506
+ input.causedBy,
507
+ );
508
+ }
509
+
294
510
  update({
295
511
  id: input.effectId,
296
512
  ...(input.refId !== undefined ? { lastTriggeredByRefId: input.refId } : {}),
297
- lastTriggeredAt: input.location ?? captureSourceLocation(),
298
513
  });
299
514
  }
300
515
 
@@ -304,7 +519,6 @@ export function reset() {
304
519
  primitiveTargetIds.clear();
305
520
  effectIdCounter = 1;
306
521
  edgeEventIdCounter = 1;
307
- nonRefTargetIdCounter = 1;
308
522
  }
309
523
 
310
524
  // Utilities used by other debug sections
@@ -5,16 +5,13 @@ import {
5
5
  type DevtoolsMessage,
6
6
  } from "../../testing/devtools-utils.js";
7
7
  import { Output } from "../components/Output.jsx";
8
- import { Show } from "../components/Show.jsx";
9
8
  import { SourceDirectory } from "../components/SourceDirectory.jsx";
10
9
  import { SourceFile } from "../components/SourceFile.jsx";
11
10
  import {
12
11
  enableDevtools,
13
12
  resetDevtoolsServerForTests,
14
13
  } from "../devtools/devtools-server.js";
15
- import { ref } from "../reactivity.js";
16
14
  import { renderAsync } from "../render.js";
17
- import { flushJobsAsync } from "../scheduler.js";
18
15
 
19
16
  let socket: WebSocket | undefined;
20
17
 
@@ -38,59 +35,42 @@ afterEach(async () => {
38
35
  });
39
36
 
40
37
  it("emits file and directory add/update/remove messages", async () => {
41
- const show = ref(true);
42
- const collector = createMessageCollector(socket!);
38
+ const collector = await createMessageCollector(socket!);
43
39
 
44
40
  await renderAsync(
45
41
  <Output>
46
- <Show when={show.value}>
47
- <SourceDirectory path="src">
48
- <SourceFile path="index.ts" filetype="ts">
49
- {"export const value = 1;"}
50
- </SourceFile>
51
- </SourceDirectory>
52
- </Show>
42
+ <SourceDirectory path="src">
43
+ <SourceFile path="index.ts" filetype="ts">
44
+ {"export const value = 1;"}
45
+ </SourceFile>
46
+ </SourceDirectory>
53
47
  </Output>,
54
48
  );
55
49
 
56
50
  const initialMessages = await collector.waitForRender();
57
- const initialFiles = initialMessages.filter((m: DevtoolsMessage) =>
58
- m.type.startsWith("files:"),
51
+ const initialFiles = initialMessages.filter(
52
+ (m: DevtoolsMessage) =>
53
+ (m.type.startsWith("file:") || m.type.startsWith("directory:")) &&
54
+ !("triggerIds" in m),
59
55
  );
60
56
  expect(initialFiles[0]).toMatchObject({
57
+ type: "directory:added",
61
58
  path: "./",
62
59
  });
63
60
  expect(initialFiles[1]).toMatchObject({
64
- type: "files:directoryAdded",
61
+ type: "directory:added",
65
62
  path: "src",
66
63
  });
67
64
  expect(initialFiles[2]).toMatchObject({
68
- type: "files:fileAdded",
65
+ type: "file:added",
69
66
  path: "src/index.ts",
70
67
  filetype: "ts",
71
68
  });
72
69
  expect(initialFiles[3]).toMatchObject({
73
- type: "files:fileUpdated",
70
+ type: "file:updated",
74
71
  path: "src/index.ts",
75
- filetype: "ts",
76
- contents: expect.any(String),
72
+ content: expect.any(String),
77
73
  });
78
74
 
79
- show.value = false;
80
- await flushJobsAsync();
81
-
82
- const updateMessages = await collector.waitForFlush();
83
- const updateFiles = updateMessages.filter((m: DevtoolsMessage) =>
84
- m.type.startsWith("files:"),
85
- );
86
75
  collector.stop();
87
-
88
- expect(updateFiles[0]).toMatchObject({
89
- type: "files:fileRemoved",
90
- path: "src/index.ts",
91
- });
92
- expect(updateFiles[1]).toMatchObject({
93
- type: "files:directoryRemoved",
94
- path: "src",
95
- });
96
76
  });
@@ -1,4 +1,9 @@
1
- import { emitDevtoolsMessage, isDevtoolsEnabled } from "./trace.js";
1
+ import {
2
+ insertDirectory,
3
+ insertOutputFile,
4
+ updateOutputFileContent,
5
+ } from "./trace-writer.js";
6
+ import { isDebugEnabled } from "./trace.js";
2
7
 
3
8
  export interface FileUpdateInfo {
4
9
  path: string;
@@ -10,14 +15,14 @@ const fileContentCache = new Map<string, string>();
10
15
 
11
16
  /** Record a directory being added to the output. */
12
17
  export function recordDirectory(path: string) {
13
- if (!isDevtoolsEnabled()) return;
14
- emitDevtoolsMessage({ type: "files:directoryAdded", path });
18
+ if (!isDebugEnabled()) return;
19
+ insertDirectory(path);
15
20
  }
16
21
 
17
22
  /** Record a file being added to the output. */
18
23
  export function recordFile(path: string, filetype: string) {
19
- if (!isDevtoolsEnabled()) return;
20
- emitDevtoolsMessage({ type: "files:fileAdded", path, filetype });
24
+ if (!isDebugEnabled()) return;
25
+ insertOutputFile(path, filetype, undefined);
21
26
  }
22
27
 
23
28
  /** Notify devtools that a file's contents have changed. De-duplicates by content. */
@@ -26,12 +31,7 @@ export function updated(info: FileUpdateInfo) {
26
31
  if (previous === info.contents) return;
27
32
  fileContentCache.set(info.path, info.contents);
28
33
 
29
- emitDevtoolsMessage({
30
- type: "files:fileUpdated",
31
- path: info.path,
32
- filetype: info.filetype,
33
- contents: info.contents,
34
- });
34
+ updateOutputFileContent(info.path, info.contents);
35
35
  }
36
36
 
37
37
  /** Clear all cached file state. Called when a new render begins. */
@@ -11,6 +11,7 @@ import {
11
11
  debugWatch,
12
12
  } from "./cli.js";
13
13
  import {
14
+ ensureReactivePropertyRef,
14
15
  ensureRef,
15
16
  register,
16
17
  registerRef,
@@ -47,6 +48,7 @@ import {
47
48
  } from "./symbols.js";
48
49
  import { trace, type TracePhaseInfo } from "./trace.js";
49
50
 
51
+ export { isDevtoolsConnected } from "../devtools/devtools-server.js";
50
52
  export { captureSourceLocation } from "./effects.js";
51
53
  export type {
52
54
  EffectDebugInfo,
@@ -62,6 +64,7 @@ export type {
62
64
  } from "./render.js";
63
65
  export {
64
66
  isConsoleTraceEnabled,
67
+ isDebugEnabled,
65
68
  isDevtoolsEnabled,
66
69
  trace,
67
70
  TracePhase,
@@ -84,6 +87,7 @@ export const debug = {
84
87
  update,
85
88
  registerRef,
86
89
  ensureRef,
90
+ ensureReactivePropertyRef,
87
91
  track,
88
92
  trigger,
89
93
  reset,