@alloy-js/core 0.23.0-dev.0 → 0.23.0-dev.8

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 (269) hide show
  1. package/CHANGELOG.md +0 -22
  2. package/dist/devtools/index.html +68 -0
  3. package/dist/src/binder.d.ts +2 -0
  4. package/dist/src/binder.d.ts.map +1 -1
  5. package/dist/src/binder.js +55 -12
  6. package/dist/src/binder.js.map +1 -1
  7. package/dist/src/components/AppendFile.d.ts.map +1 -1
  8. package/dist/src/components/AppendFile.js +14 -3
  9. package/dist/src/components/AppendFile.js.map +1 -1
  10. package/dist/src/components/Block.js +1 -1
  11. package/dist/src/components/Block.js.map +1 -1
  12. package/dist/src/components/Declaration.d.ts.map +1 -1
  13. package/dist/src/components/Declaration.js +2 -1
  14. package/dist/src/components/Declaration.js.map +1 -1
  15. package/dist/src/components/Scope.d.ts.map +1 -1
  16. package/dist/src/components/Scope.js +4 -1
  17. package/dist/src/components/Scope.js.map +1 -1
  18. package/dist/src/components/TemplateFile.d.ts.map +1 -1
  19. package/dist/src/components/TemplateFile.js +18 -3
  20. package/dist/src/components/TemplateFile.js.map +1 -1
  21. package/dist/src/content-slot.d.ts.map +1 -1
  22. package/dist/src/content-slot.js +6 -5
  23. package/dist/src/content-slot.js.map +1 -1
  24. package/dist/src/context.d.ts.map +1 -1
  25. package/dist/src/context.js +8 -1
  26. package/dist/src/context.js.map +1 -1
  27. package/dist/src/debug/cli.d.ts +6 -0
  28. package/dist/src/debug/cli.d.ts.map +1 -0
  29. package/dist/src/{debug.js → debug/cli.js} +78 -84
  30. package/dist/src/debug/cli.js.map +1 -0
  31. package/dist/src/debug/diagnostics.test.d.ts +2 -0
  32. package/dist/src/debug/diagnostics.test.d.ts.map +1 -0
  33. package/dist/src/debug/diagnostics.test.js +45 -0
  34. package/dist/src/debug/diagnostics.test.js.map +1 -0
  35. package/dist/src/debug/effects.d.ts +69 -0
  36. package/dist/src/debug/effects.d.ts.map +1 -0
  37. package/dist/src/debug/effects.js +228 -0
  38. package/dist/src/debug/effects.js.map +1 -0
  39. package/dist/src/debug/effects.test.d.ts +2 -0
  40. package/dist/src/debug/effects.test.d.ts.map +1 -0
  41. package/dist/src/debug/effects.test.js +86 -0
  42. package/dist/src/debug/effects.test.js.map +1 -0
  43. package/dist/src/debug/files.d.ts +14 -0
  44. package/dist/src/debug/files.d.ts.map +1 -0
  45. package/dist/src/debug/files.js +40 -0
  46. package/dist/src/debug/files.js.map +1 -0
  47. package/dist/src/debug/files.test.d.ts +2 -0
  48. package/dist/src/debug/files.test.d.ts.map +1 -0
  49. package/dist/src/debug/files.test.js +89 -0
  50. package/dist/src/debug/files.test.js.map +1 -0
  51. package/dist/src/debug/index.d.ts +60 -0
  52. package/dist/src/debug/index.d.ts.map +1 -0
  53. package/dist/src/debug/index.js +68 -0
  54. package/dist/src/debug/index.js.map +1 -0
  55. package/dist/src/debug/render.d.ts +57 -0
  56. package/dist/src/debug/render.d.ts.map +1 -0
  57. package/dist/src/debug/render.js +519 -0
  58. package/dist/src/debug/render.js.map +1 -0
  59. package/dist/src/debug/render.test.d.ts +2 -0
  60. package/dist/src/debug/render.test.d.ts.map +1 -0
  61. package/dist/src/debug/render.test.js +328 -0
  62. package/dist/src/debug/render.test.js.map +1 -0
  63. package/dist/src/debug/serialize.d.ts +9 -0
  64. package/dist/src/debug/serialize.d.ts.map +1 -0
  65. package/dist/src/debug/serialize.js +70 -0
  66. package/dist/src/debug/serialize.js.map +1 -0
  67. package/dist/src/debug/symbols.d.ts +9 -0
  68. package/dist/src/debug/symbols.d.ts.map +1 -0
  69. package/dist/src/debug/symbols.js +164 -0
  70. package/dist/src/debug/symbols.js.map +1 -0
  71. package/dist/src/debug/symbols.test.d.ts +2 -0
  72. package/dist/src/debug/symbols.test.d.ts.map +1 -0
  73. package/dist/src/debug/symbols.test.js +104 -0
  74. package/dist/src/debug/symbols.test.js.map +1 -0
  75. package/dist/src/debug/trace.d.ts +342 -0
  76. package/dist/src/debug/trace.d.ts.map +1 -0
  77. package/dist/src/debug/trace.js +443 -0
  78. package/dist/src/debug/trace.js.map +1 -0
  79. package/dist/src/devtools/devtools-protocol.d.ts +232 -0
  80. package/dist/src/devtools/devtools-protocol.d.ts.map +1 -0
  81. package/dist/src/devtools/devtools-protocol.js +2 -0
  82. package/dist/src/devtools/devtools-protocol.js.map +1 -0
  83. package/dist/src/devtools/devtools-server.browser.d.ts +28 -0
  84. package/dist/src/devtools/devtools-server.browser.d.ts.map +1 -0
  85. package/dist/src/devtools/devtools-server.browser.js +36 -0
  86. package/dist/src/devtools/devtools-server.browser.js.map +1 -0
  87. package/dist/src/devtools/devtools-server.d.ts +72 -0
  88. package/dist/src/devtools/devtools-server.d.ts.map +1 -0
  89. package/dist/src/devtools/devtools-server.js +256 -0
  90. package/dist/src/devtools/devtools-server.js.map +1 -0
  91. package/dist/src/devtools/devtools-transport.d.ts +23 -0
  92. package/dist/src/devtools/devtools-transport.d.ts.map +1 -0
  93. package/dist/src/devtools/devtools-transport.js +114 -0
  94. package/dist/src/devtools/devtools-transport.js.map +1 -0
  95. package/dist/src/devtools-entry.browser.d.ts +4 -0
  96. package/dist/src/devtools-entry.browser.d.ts.map +1 -0
  97. package/dist/src/devtools-entry.browser.js +2 -0
  98. package/dist/src/devtools-entry.browser.js.map +1 -0
  99. package/dist/src/devtools-entry.d.ts +4 -0
  100. package/dist/src/devtools-entry.d.ts.map +1 -0
  101. package/dist/src/devtools-entry.js +2 -0
  102. package/dist/src/devtools-entry.js.map +1 -0
  103. package/dist/src/diagnostics.d.ts +34 -0
  104. package/dist/src/diagnostics.d.ts.map +1 -0
  105. package/dist/src/diagnostics.js +89 -0
  106. package/dist/src/diagnostics.js.map +1 -0
  107. package/dist/src/index.d.ts +3 -2
  108. package/dist/src/index.d.ts.map +1 -1
  109. package/dist/src/index.js +3 -2
  110. package/dist/src/index.js.map +1 -1
  111. package/dist/src/print-hook.d.ts +14 -0
  112. package/dist/src/print-hook.d.ts.map +1 -0
  113. package/dist/src/print-hook.js +10 -0
  114. package/dist/src/print-hook.js.map +1 -0
  115. package/dist/src/reactive-union-set.d.ts.map +1 -1
  116. package/dist/src/reactive-union-set.js +15 -0
  117. package/dist/src/reactive-union-set.js.map +1 -1
  118. package/dist/src/reactivity.d.ts +17 -3
  119. package/dist/src/reactivity.d.ts.map +1 -1
  120. package/dist/src/reactivity.js +162 -14
  121. package/dist/src/reactivity.js.map +1 -1
  122. package/dist/src/render-stack.d.ts +29 -0
  123. package/dist/src/render-stack.d.ts.map +1 -0
  124. package/dist/src/render-stack.js +247 -0
  125. package/dist/src/render-stack.js.map +1 -0
  126. package/dist/src/render.d.ts +9 -19
  127. package/dist/src/render.d.ts.map +1 -1
  128. package/dist/src/render.js +363 -153
  129. package/dist/src/render.js.map +1 -1
  130. package/dist/src/resource.d.ts.map +1 -1
  131. package/dist/src/resource.js +5 -0
  132. package/dist/src/resource.js.map +1 -1
  133. package/dist/src/runtime/component.d.ts +7 -1
  134. package/dist/src/runtime/component.d.ts.map +1 -1
  135. package/dist/src/runtime/component.js +4 -1
  136. package/dist/src/runtime/component.js.map +1 -1
  137. package/dist/src/scheduler.d.ts +3 -0
  138. package/dist/src/scheduler.d.ts.map +1 -1
  139. package/dist/src/scheduler.js +45 -2
  140. package/dist/src/scheduler.js.map +1 -1
  141. package/dist/src/symbols/basic-symbol.d.ts.map +1 -1
  142. package/dist/src/symbols/basic-symbol.js +6 -1
  143. package/dist/src/symbols/basic-symbol.js.map +1 -1
  144. package/dist/src/symbols/decl.d.ts.map +1 -1
  145. package/dist/src/symbols/decl.js +5 -1
  146. package/dist/src/symbols/decl.js.map +1 -1
  147. package/dist/src/symbols/output-scope.d.ts +2 -1
  148. package/dist/src/symbols/output-scope.d.ts.map +1 -1
  149. package/dist/src/symbols/output-scope.js +13 -8
  150. package/dist/src/symbols/output-scope.js.map +1 -1
  151. package/dist/src/symbols/output-symbol.d.ts +1 -0
  152. package/dist/src/symbols/output-symbol.d.ts.map +1 -1
  153. package/dist/src/symbols/output-symbol.js +23 -6
  154. package/dist/src/symbols/output-symbol.js.map +1 -1
  155. package/dist/src/symbols/symbol-flow.d.ts.map +1 -1
  156. package/dist/src/symbols/symbol-flow.js +22 -6
  157. package/dist/src/symbols/symbol-flow.js.map +1 -1
  158. package/dist/src/symbols/symbol-slot.d.ts.map +1 -1
  159. package/dist/src/symbols/symbol-slot.js +15 -0
  160. package/dist/src/symbols/symbol-slot.js.map +1 -1
  161. package/dist/src/symbols/symbol-slot.test.d.ts +2 -0
  162. package/dist/src/symbols/symbol-slot.test.d.ts.map +1 -0
  163. package/dist/src/symbols/symbol-slot.test.js +35 -0
  164. package/dist/src/symbols/symbol-slot.test.js.map +1 -0
  165. package/dist/src/symbols/symbol-table.d.ts.map +1 -1
  166. package/dist/src/symbols/symbol-table.js +6 -5
  167. package/dist/src/symbols/symbol-table.js.map +1 -1
  168. package/dist/src/trace.d.ts +2 -0
  169. package/dist/src/trace.d.ts.map +1 -0
  170. package/dist/src/trace.js +2 -0
  171. package/dist/src/trace.js.map +1 -0
  172. package/dist/src/tracer.d.ts +2 -228
  173. package/dist/src/tracer.d.ts.map +1 -1
  174. package/dist/src/tracer.js +5 -298
  175. package/dist/src/tracer.js.map +1 -1
  176. package/dist/src/utils.d.ts.map +1 -1
  177. package/dist/src/utils.js +5 -0
  178. package/dist/src/utils.js.map +1 -1
  179. package/dist/test/components/append-file.test.d.ts.map +1 -1
  180. package/dist/test/components/append-file.test.js +18 -10
  181. package/dist/test/components/append-file.test.js.map +1 -1
  182. package/dist/test/components/template-file.test.d.ts.map +1 -1
  183. package/dist/test/components/template-file.test.js +6 -4
  184. package/dist/test/components/template-file.test.js.map +1 -1
  185. package/dist/test/rendering/basic.test.js +3 -0
  186. package/dist/test/rendering/basic.test.js.map +1 -1
  187. package/dist/test/rendering/print-render-stack.test.d.ts +2 -0
  188. package/dist/test/rendering/print-render-stack.test.d.ts.map +1 -0
  189. package/dist/test/rendering/print-render-stack.test.js +207 -0
  190. package/dist/test/rendering/print-render-stack.test.js.map +1 -0
  191. package/dist/testing/create-test-wrapper.d.ts +1 -1
  192. package/dist/testing/create-test-wrapper.d.ts.map +1 -1
  193. package/dist/testing/create-test-wrapper.js +1 -1
  194. package/dist/testing/create-test-wrapper.js.map +1 -1
  195. package/dist/testing/devtools-utils.d.ts +26 -0
  196. package/dist/testing/devtools-utils.d.ts.map +1 -0
  197. package/dist/testing/devtools-utils.js +140 -0
  198. package/dist/testing/devtools-utils.js.map +1 -0
  199. package/dist/testing/extend-expect.d.ts.map +1 -1
  200. package/dist/testing/extend-expect.js +63 -1
  201. package/dist/testing/extend-expect.js.map +1 -1
  202. package/dist/testing/render.d.ts +2 -2
  203. package/dist/testing/render.d.ts.map +1 -1
  204. package/dist/testing/render.js +2 -2
  205. package/dist/testing/render.js.map +1 -1
  206. package/dist/tsconfig.tsbuildinfo +1 -1
  207. package/package.json +21 -7
  208. package/scripts/copy-devtools-ui.mjs +26 -0
  209. package/src/binder.ts +71 -16
  210. package/src/components/AppendFile.tsx +14 -9
  211. package/src/components/Block.tsx +1 -1
  212. package/src/components/Declaration.tsx +2 -1
  213. package/src/components/Scope.tsx +4 -1
  214. package/src/components/TemplateFile.tsx +18 -9
  215. package/src/content-slot.tsx +6 -6
  216. package/src/context.ts +15 -4
  217. package/src/{debug.ts → debug/cli.ts} +112 -127
  218. package/src/debug/diagnostics.test.tsx +55 -0
  219. package/src/debug/effects.test.tsx +96 -0
  220. package/src/debug/effects.ts +313 -0
  221. package/src/debug/files.test.tsx +96 -0
  222. package/src/debug/files.ts +40 -0
  223. package/src/debug/index.ts +126 -0
  224. package/src/debug/render.test.tsx +379 -0
  225. package/src/debug/render.ts +639 -0
  226. package/src/debug/serialize.ts +85 -0
  227. package/src/debug/symbols.test.tsx +106 -0
  228. package/src/debug/symbols.ts +230 -0
  229. package/src/debug/trace.ts +312 -0
  230. package/src/devtools/devtools-protocol.ts +312 -0
  231. package/src/devtools/devtools-server.browser.ts +71 -0
  232. package/src/devtools/devtools-server.ts +290 -0
  233. package/src/devtools/devtools-transport.ts +154 -0
  234. package/src/devtools-entry.browser.ts +52 -0
  235. package/src/devtools-entry.ts +54 -0
  236. package/src/diagnostics.ts +141 -0
  237. package/src/index.ts +2 -6
  238. package/src/print-hook.ts +22 -0
  239. package/src/reactive-union-set.ts +71 -41
  240. package/src/reactivity.ts +206 -23
  241. package/src/render-stack.ts +289 -0
  242. package/src/render.ts +464 -212
  243. package/src/resource.ts +28 -19
  244. package/src/runtime/component.ts +11 -0
  245. package/src/scheduler.ts +55 -3
  246. package/src/symbols/basic-symbol.ts +6 -1
  247. package/src/symbols/decl.ts +5 -1
  248. package/src/symbols/output-scope.ts +21 -12
  249. package/src/symbols/output-symbol.ts +33 -12
  250. package/src/symbols/symbol-flow.ts +68 -37
  251. package/src/symbols/symbol-slot.test.tsx +41 -0
  252. package/src/symbols/symbol-slot.tsx +47 -20
  253. package/src/symbols/symbol-table.ts +6 -10
  254. package/src/trace.ts +1 -0
  255. package/src/tracer.ts +13 -242
  256. package/src/utils.tsx +22 -13
  257. package/temp/api.json +1811 -277
  258. package/test/components/append-file.test.tsx +36 -29
  259. package/test/components/template-file.test.tsx +11 -11
  260. package/test/rendering/basic.test.tsx +4 -0
  261. package/test/rendering/print-render-stack.test.tsx +244 -0
  262. package/testing/create-test-wrapper.tsx +1 -1
  263. package/testing/devtools-utils.ts +203 -0
  264. package/testing/extend-expect.ts +89 -0
  265. package/testing/render.ts +2 -2
  266. package/testing/vitest.d.ts +9 -0
  267. package/dist/src/debug.d.ts +0 -15
  268. package/dist/src/debug.d.ts.map +0 -1
  269. package/dist/src/debug.js.map +0 -1
package/src/reactivity.ts CHANGED
@@ -2,21 +2,61 @@ import {
2
2
  isRef,
3
3
  pauseTracking,
4
4
  ReactiveEffectRunner,
5
- ref,
6
5
  Ref,
7
6
  resetTracking,
8
7
  ShallowReactive,
9
- shallowRef,
10
8
  stop,
9
+ computed as vueComputed,
11
10
  effect as vueEffect,
11
+ ref as vueRef,
12
+ shallowRef as vueShallowRef,
13
+ toRef as vueToRef,
14
+ toRefs as vueToRefs,
12
15
  } from "@vue/reactivity";
13
- import type { RenderedTextTree } from "./render.js";
14
- import type { Children, ComponentCreator } from "./runtime/component.js";
16
+ import {
17
+ captureSourceLocation,
18
+ debug,
19
+ isDevtoolsEnabled,
20
+ } from "./debug/index.js";
21
+ import { RenderedTextTree } from "./render.js";
22
+ import { Children, ComponentCreator } from "./runtime/component.js";
15
23
  import { scheduler } from "./scheduler.js";
16
24
  import type { OutputSymbol } from "./symbols/output-symbol.js";
17
- import { trace, TracePhase } from "./tracer.js";
18
25
 
19
- // check for multiple versions of alloy here.
26
+ function attachEffectWriteDebug(refValue: Ref<unknown>, kind: string) {
27
+ if (!isDevtoolsEnabled()) return;
28
+ const descriptor =
29
+ Object.getOwnPropertyDescriptor(refValue, "value") ??
30
+ Object.getOwnPropertyDescriptor(Object.getPrototypeOf(refValue), "value");
31
+ if (!descriptor?.get || !descriptor?.set) return;
32
+ if ((refValue as any).__alloyDebugWrapped) return;
33
+ Object.defineProperty(refValue, "value", {
34
+ get: descriptor.get,
35
+ set(value: unknown) {
36
+ descriptor.set!.call(this, value);
37
+ const effectId = globalContext?.meta?.effectId;
38
+ if (effectId !== undefined && effectId !== -1) {
39
+ const id = refId(refValue);
40
+ debug.effect.ensureRef({ id, kind });
41
+ debug.effect.trigger({
42
+ effectId,
43
+ target: refValue,
44
+ refId: id,
45
+ location: captureSourceLocation(),
46
+ kind: "trigger",
47
+ });
48
+ }
49
+ },
50
+ enumerable: descriptor.enumerable ?? true,
51
+ configurable: true,
52
+ });
53
+ Object.defineProperty(refValue, "__alloyDebugWrapped", {
54
+ value: true,
55
+ enumerable: false,
56
+ configurable: false,
57
+ });
58
+ }
59
+
20
60
  if ((globalThis as any).__ALLOY__) {
21
61
  throw new Error(
22
62
  "Multiple versions of Alloy are loaded for this project. This will likely cause undesirable behavior.",
@@ -116,7 +156,7 @@ export function root<T>(fn: (d: Disposable) => T, options?: RootOptions): T {
116
156
  ret = untrack(() =>
117
157
  fn(() => {
118
158
  for (const d of context!.disposables) {
119
- d();
159
+ untrack(d);
120
160
  }
121
161
  }),
122
162
  );
@@ -127,6 +167,15 @@ export function root<T>(fn: (d: Disposable) => T, options?: RootOptions): T {
127
167
  return ret;
128
168
  }
129
169
 
170
+ export interface EffectDebugOptions {
171
+ name?: string;
172
+ type?: string;
173
+ }
174
+
175
+ export interface EffectOptions {
176
+ debug?: EffectDebugOptions;
177
+ }
178
+
130
179
  export function untrack<T>(fn: () => T): T {
131
180
  pauseTracking();
132
181
  const v = fn();
@@ -135,17 +184,27 @@ export function untrack<T>(fn: () => T): T {
135
184
  }
136
185
 
137
186
  export function memo<T>(fn: () => T, equal?: boolean): () => T {
138
- const o = shallowRef();
139
- effect((prev) => {
140
- const res = fn();
141
- // eslint-disable-next-line @typescript-eslint/no-unused-expressions
142
- (!equal || prev !== res) && (o.value = res);
143
- return res;
144
- }, undefined as T);
145
- return () => o.value;
187
+ const o = shallowRef<T>();
188
+ effect(
189
+ (prev) => {
190
+ const res = fn();
191
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
192
+ (!equal || prev !== res) && (o.value = res);
193
+ return res;
194
+ },
195
+ undefined as T,
196
+ {
197
+ debug: { name: "memo" },
198
+ },
199
+ );
200
+ return () => o.value as T;
146
201
  }
147
202
 
148
- export function effect<T>(fn: (prev?: T) => T, current?: T) {
203
+ export function effect<T>(
204
+ fn: (prev?: T) => T,
205
+ current?: T,
206
+ options?: EffectOptions,
207
+ ) {
149
208
  const context: Context = {
150
209
  context: {},
151
210
  disposables: [] as (() => void)[],
@@ -157,10 +216,22 @@ export function effect<T>(fn: (prev?: T) => T, current?: T) {
157
216
  isRoot: false,
158
217
  };
159
218
 
219
+ const debugInfo = options?.debug;
220
+ const effectId = debug.effect.register({
221
+ name: debugInfo?.name ?? fn.name,
222
+ type: debugInfo?.type,
223
+ createdAt: captureSourceLocation(),
224
+ });
225
+
226
+ context.meta ??= {};
227
+ if (effectId !== -1) {
228
+ context.meta.effectId = effectId;
229
+ }
230
+
160
231
  const cleanupFn = (final: boolean) => {
161
232
  const d = context.disposables;
162
233
  context.disposables = [];
163
- for (let k = 0, len = d.length; k < len; k++) d[k]();
234
+ for (let k = 0, len = d.length; k < len; k++) untrack(d[k]);
164
235
 
165
236
  // eslint-disable-next-line @typescript-eslint/no-unused-expressions
166
237
  final && stop(runner);
@@ -185,14 +256,56 @@ export function effect<T>(fn: (prev?: T) => T, current?: T) {
185
256
  flags: 1 | 4 | 32,
186
257
  scheduler: scheduler(),
187
258
  onTrack(event) {
188
- trace(TracePhase.effect.track, () => {
189
- return `tracking ${isRef(event.target) ? `Ref:${refId(event.target)}` : event.target}, ${String(event.key)}`;
190
- });
259
+ if (effectId !== -1) {
260
+ const targetKey =
261
+ typeof event.key === "symbol" ? event.key.toString() : event.key;
262
+ if (isRef(event.target)) {
263
+ const id = refId(event.target);
264
+ debug.effect.ensureRef({ id, kind: "ref" });
265
+ debug.effect.track({
266
+ effectId,
267
+ target: event.target,
268
+ refId: id,
269
+ targetKey,
270
+ location: captureSourceLocation(),
271
+ });
272
+ } else {
273
+ debug.effect.track({
274
+ effectId,
275
+ target: event.target,
276
+ targetKey,
277
+ location: captureSourceLocation(),
278
+ });
279
+ }
280
+ }
281
+ // track edge emitted via recordEffectTrack
191
282
  },
192
283
  onTrigger(event) {
193
- trace(TracePhase.effect.trigger, () => {
194
- return `triggering ${isRef(event.target) ? `Ref:${refId(event.target)}` : event.target}, ${String(event.key)}`;
195
- });
284
+ if (effectId !== -1) {
285
+ const targetKey =
286
+ typeof event.key === "symbol" ? event.key.toString() : event.key;
287
+ if (isRef(event.target)) {
288
+ const id = refId(event.target);
289
+ debug.effect.ensureRef({ id, kind: "ref" });
290
+ debug.effect.trigger({
291
+ effectId,
292
+ target: event.target,
293
+ refId: id,
294
+ targetKey,
295
+ location: captureSourceLocation(),
296
+ kind: "triggered-by",
297
+ });
298
+ } else {
299
+ debug.effect.trigger({
300
+ effectId,
301
+ target: event.target,
302
+ targetKey,
303
+ location: captureSourceLocation(),
304
+ kind: "triggered-by",
305
+ });
306
+ }
307
+ }
308
+ // trigger edge emitted via recordEffectTrigger
196
309
  },
197
310
  },
198
311
  );
@@ -253,6 +366,76 @@ export function isCustomContext(child: Children): child is CustomContext {
253
366
  );
254
367
  }
255
368
 
369
+ export function ref<T>(value?: T): Ref<T> {
370
+ const result = vueRef(value) as Ref<T>;
371
+ attachEffectWriteDebug(result, "ref");
372
+ debug.effect.registerRef({
373
+ id: refId(result),
374
+ kind: "ref",
375
+ createdAt: captureSourceLocation(),
376
+ createdByEffectId: globalContext?.meta?.effectId,
377
+ });
378
+ return result;
379
+ }
380
+
381
+ export function shallowRef<T>(value?: T): Ref<T> {
382
+ const result = vueShallowRef(value) as Ref<T>;
383
+ attachEffectWriteDebug(result, "shallowRef");
384
+ debug.effect.registerRef({
385
+ id: refId(result),
386
+ kind: "shallowRef",
387
+ createdAt: captureSourceLocation(),
388
+ createdByEffectId: globalContext?.meta?.effectId,
389
+ });
390
+ return result;
391
+ }
392
+
393
+ export function computed<T>(getter: () => T): Ref<T> {
394
+ const result = vueComputed(getter) as Ref<T>;
395
+ debug.effect.registerRef({
396
+ id: refId(result),
397
+ kind: "computed",
398
+ createdAt: captureSourceLocation(),
399
+ createdByEffectId: globalContext?.meta?.effectId,
400
+ });
401
+ return result;
402
+ }
403
+
404
+ export function toRef<T extends object, K extends keyof T>(
405
+ object: T,
406
+ key: K,
407
+ defaultValue?: T[K],
408
+ ): Ref<T[K]> {
409
+ const result =
410
+ defaultValue === undefined ?
411
+ (vueToRef(object, key) as Ref<T[K]>)
412
+ : (vueToRef(object, key, defaultValue) as Ref<T[K]>);
413
+ attachEffectWriteDebug(result, "toRef");
414
+ debug.effect.registerRef({
415
+ id: refId(result),
416
+ kind: "toRef",
417
+ createdAt: captureSourceLocation(),
418
+ createdByEffectId: globalContext?.meta?.effectId,
419
+ });
420
+ return result;
421
+ }
422
+
423
+ export function toRefs<T extends object>(
424
+ object: T,
425
+ ): { [K in keyof T]: Ref<T[K]> } {
426
+ const result = vueToRefs(object) as { [K in keyof T]: Ref<T[K]> };
427
+ for (const refValue of Object.values(result) as Ref<unknown>[]) {
428
+ attachEffectWriteDebug(refValue, "toRef");
429
+ debug.effect.registerRef({
430
+ id: refId(refValue),
431
+ kind: "toRef",
432
+ createdAt: captureSourceLocation(),
433
+ createdByEffectId: globalContext?.meta?.effectId,
434
+ });
435
+ }
436
+ return result;
437
+ }
438
+
256
439
  const seenRefs = new WeakMap<Ref<unknown>, number>();
257
440
  let refIdCounter = 1;
258
441
 
@@ -0,0 +1,289 @@
1
+ import pc from "picocolors";
2
+ import { contextsByKey } from "./context.js";
3
+ import { SourceDirectoryContext } from "./context/source-directory.js";
4
+ import { SourceFileContext } from "./context/source-file.js";
5
+ import { Context, getContext } from "./reactivity.js";
6
+ import { Component, Props, SourceLocation } from "./runtime/component.js";
7
+
8
+ // Store render stack for error diagnostics
9
+ const renderStack: Array<{
10
+ component: Component<any>;
11
+ props: Props;
12
+ context: Context | null;
13
+ source?: SourceLocation;
14
+ }> = [];
15
+
16
+ export interface RenderStackSnapshotEntry {
17
+ component: Component<any>;
18
+ props: Props;
19
+ context: Context | null;
20
+ source?: SourceLocation;
21
+ displayName: string;
22
+ }
23
+
24
+ export function pushStack(
25
+ component: Component<any>,
26
+ props: Props,
27
+ source?: SourceLocation,
28
+ ) {
29
+ renderStack.push({ component, props, context: getContext(), source });
30
+ }
31
+
32
+ export function popStack() {
33
+ renderStack.pop();
34
+ }
35
+
36
+ export function clearRenderStack() {
37
+ renderStack.length = 0;
38
+ }
39
+
40
+ /**
41
+ * Get the current rendering path from the render stack context.
42
+ * Prefers SourceFileContext over SourceDirectoryContext.
43
+ */
44
+ export function getCurrentRenderPath(): string | undefined {
45
+ let currentPath: string | undefined;
46
+ for (let i = renderStack.length - 1; i >= 0; i--) {
47
+ const { context } = renderStack[i];
48
+ // Prefer SourceFileContext over SourceDirectoryContext
49
+ if (context?.context?.[SourceFileContext.id]) {
50
+ const fileContext = context.context[
51
+ SourceFileContext.id
52
+ ] as SourceFileContext;
53
+ return fileContext.path;
54
+ }
55
+ if (!currentPath && context?.context?.[SourceDirectoryContext.id]) {
56
+ const dirContext = context.context[
57
+ SourceDirectoryContext.id
58
+ ] as SourceDirectoryContext;
59
+ currentPath = dirContext.path;
60
+ // Don't break - keep looking for a SourceFileContext
61
+ }
62
+ }
63
+ return currentPath;
64
+ }
65
+
66
+ export function getRenderStackSnapshot(): RenderStackSnapshotEntry[] {
67
+ return renderStack.map((entry) => ({
68
+ component: entry.component,
69
+ props: entry.props,
70
+ context: entry.context,
71
+ source: entry.source,
72
+ displayName: getComponentDisplayName(entry.component, entry.props),
73
+ }));
74
+ }
75
+
76
+ // Helper functions
77
+ function getComponentDisplayName(
78
+ component: Component<any>,
79
+ props: Props,
80
+ ): string {
81
+ // Check if this is a Provider and if we can find its context name
82
+ if (component.name === "Provider") {
83
+ // Try to find the context this provider is associated with
84
+ for (const ctx of contextsByKey.values()) {
85
+ if (ctx.Provider === component && ctx.name) {
86
+ return ctx.name;
87
+ }
88
+ }
89
+ }
90
+ return component.name;
91
+ }
92
+
93
+ function inspectProps(props: Props) {
94
+ const entries = Object.entries(props)
95
+ .filter(([key]) => key !== "children") // Exclude children prop
96
+ .map(([key, value]) => {
97
+ const formattedValue = formatValue(value);
98
+ return `${pc.dim(key)}: ${formattedValue}`;
99
+ });
100
+
101
+ return entries.length > 0 ? entries.join(pc.dim(", ")) : "";
102
+ }
103
+
104
+ function formatValue(value: unknown): string {
105
+ switch (typeof value) {
106
+ case "string":
107
+ return pc.blue(`"${value}"`);
108
+ case "number":
109
+ case "boolean":
110
+ return pc.blue(String(value));
111
+ case "undefined":
112
+ return pc.gray("undefined");
113
+ case "object":
114
+ return value ? pc.gray("{...}") : pc.gray("null");
115
+ case "function":
116
+ return pc.gray("function");
117
+ default:
118
+ return pc.gray(String(value));
119
+ }
120
+ }
121
+
122
+ function formatSourceLocation(source: SourceLocation): string {
123
+ const cwd = process.cwd();
124
+ let filePath = source.fileName;
125
+
126
+ // Convert to relative path if under cwd
127
+ if (filePath.startsWith(cwd)) {
128
+ filePath = filePath.slice(cwd.length + 1); // +1 to remove leading slash
129
+ }
130
+
131
+ return `${filePath}:${source.lineNumber}:${source.columnNumber}`;
132
+ }
133
+
134
+ /**
135
+ * Print the current render stack to the console for debugging.
136
+ *
137
+ * This differs from debug.component.stack in that this uses a purpose-built
138
+ * stack rather than walking the context chain. When this is called, the context
139
+ * chain has been restored. In the future this can probably be unified nicely.
140
+ *
141
+ * @param error - Optional error to print the stack trace from
142
+ */
143
+ export function printRenderStack(error?: unknown) {
144
+ // Find the nearest SourceFileContext or SourceDirectoryContext from the render stack
145
+ let currentPath: string | undefined;
146
+ for (let i = renderStack.length - 1; i >= 0; i--) {
147
+ const { context } = renderStack[i];
148
+ // Prefer SourceFileContext over SourceDirectoryContext
149
+ if (context?.context?.[SourceFileContext.id]) {
150
+ const fileContext = context.context[
151
+ SourceFileContext.id
152
+ ] as SourceFileContext;
153
+ currentPath = fileContext.path;
154
+ break;
155
+ }
156
+ if (!currentPath && context?.context?.[SourceDirectoryContext.id]) {
157
+ const dirContext = context.context[
158
+ SourceDirectoryContext.id
159
+ ] as SourceDirectoryContext;
160
+ currentPath = dirContext.path;
161
+ // Don't break - keep looking for a SourceFileContext
162
+ }
163
+ }
164
+
165
+ if (currentPath) {
166
+ // eslint-disable-next-line no-console
167
+ console.error(pc.red(`Error rendering in file ${currentPath}`));
168
+ } else {
169
+ // eslint-disable-next-line no-console
170
+ console.error(pc.red("Error rendering:"));
171
+ }
172
+
173
+ // Print the error message and stack if provided
174
+ if (error) {
175
+ if (error instanceof Error) {
176
+ // eslint-disable-next-line no-console
177
+ console.error(pc.red(` ${error.message}`));
178
+ if (error.stack) {
179
+ // Print stack lines (skip the first line which is the error message)
180
+ const stackLines = error.stack.split("\n").slice(1);
181
+ for (const line of stackLines) {
182
+ // eslint-disable-next-line no-console
183
+ console.error(pc.gray(line));
184
+ }
185
+ }
186
+ } else {
187
+ // eslint-disable-next-line no-console
188
+ console.error(pc.red(` ${String(error)}`));
189
+ }
190
+ // eslint-disable-next-line no-console
191
+ console.error(); // Empty line before component stack
192
+ }
193
+
194
+ // First pass: determine which providers should be nested vs standalone
195
+ // A provider should be nested under its parent if it's from a different file
196
+ // (i.e., it's part of the component's implementation, not user-provided)
197
+ const nestedProviderIndices = new Set<number>();
198
+
199
+ for (let i = renderStack.length - 1; i >= 0; i--) {
200
+ const { component, source } = renderStack[i];
201
+
202
+ // Skip anonymous components and providers
203
+ if (!component.name || component.name === "Provider") {
204
+ continue;
205
+ }
206
+
207
+ // Look for providers that come immediately after this component
208
+ for (let j = i + 1; j < renderStack.length; j++) {
209
+ const providerEntry = renderStack[j];
210
+ if (!providerEntry.component.name) continue;
211
+ if (providerEntry.component.name !== "Provider") break;
212
+
213
+ // Nest provider if it's from a different file (component-internal)
214
+ const shouldNest =
215
+ !source ||
216
+ !providerEntry.source ||
217
+ source.fileName !== providerEntry.source.fileName;
218
+
219
+ if (shouldNest) {
220
+ nestedProviderIndices.add(j);
221
+ } else {
222
+ // Stop looking - this provider and all after should be standalone
223
+ break;
224
+ }
225
+ }
226
+ }
227
+
228
+ // Second pass: print stack entries in order, nesting providers where appropriate
229
+ for (let i = renderStack.length - 1; i >= 0; i--) {
230
+ const { component, props, source } = renderStack[i];
231
+
232
+ // Skip anonymous components
233
+ if (!component.name) {
234
+ continue;
235
+ }
236
+
237
+ // Skip providers that will be nested under their parent
238
+ if (nestedProviderIndices.has(i)) {
239
+ continue;
240
+ }
241
+
242
+ const displayName = getComponentDisplayName(component, props);
243
+ const sourceStr =
244
+ source ? pc.gray(` (${formatSourceLocation(source)})`) : "";
245
+
246
+ // eslint-disable-next-line no-console
247
+ console.error(` ${pc.cyan("at")} ${pc.bold(displayName)}${sourceStr}`);
248
+
249
+ // Print props on next line if there are any
250
+ const propsStr = inspectProps(props);
251
+ if (propsStr) {
252
+ // eslint-disable-next-line no-console
253
+ console.error(` ${propsStr}`);
254
+ }
255
+
256
+ // For non-Provider components, print any nested providers
257
+ if (component.name !== "Provider") {
258
+ for (let j = i + 1; j < renderStack.length; j++) {
259
+ if (!nestedProviderIndices.has(j)) break;
260
+
261
+ const providerEntry = renderStack[j];
262
+ if (!providerEntry.component.name) continue;
263
+
264
+ const providerName = getComponentDisplayName(
265
+ providerEntry.component,
266
+ providerEntry.props,
267
+ );
268
+ const providerSourceStr =
269
+ providerEntry.source ?
270
+ pc.gray(` (${formatSourceLocation(providerEntry.source)})`)
271
+ : "";
272
+
273
+ // eslint-disable-next-line no-console
274
+ console.error(
275
+ ` ${pc.magenta("provides")} ${pc.bold(providerName)}${providerSourceStr}`,
276
+ );
277
+
278
+ const providerPropsStr = inspectProps(providerEntry.props);
279
+ if (providerPropsStr) {
280
+ // eslint-disable-next-line no-console
281
+ console.error(` ${providerPropsStr}`);
282
+ }
283
+ }
284
+ }
285
+ }
286
+
287
+ // Clear the stack after printing to avoid stale data on subsequent render errors
288
+ clearRenderStack();
289
+ }