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

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 (106) hide show
  1. package/dist/src/components/Prose.js +2 -2
  2. package/dist/src/components/Prose.js.map +1 -1
  3. package/dist/src/components/Scope.d.ts.map +1 -1
  4. package/dist/src/components/Scope.js +2 -0
  5. package/dist/src/components/Scope.js.map +1 -1
  6. package/dist/src/components/SourceDirectory.d.ts.map +1 -1
  7. package/dist/src/components/SourceDirectory.js +1 -2
  8. package/dist/src/components/SourceDirectory.js.map +1 -1
  9. package/dist/src/content-slot.js +2 -2
  10. package/dist/src/content-slot.js.map +1 -1
  11. package/dist/src/context.js +2 -2
  12. package/dist/src/context.js.map +1 -1
  13. package/dist/src/debug/effects.d.ts +4 -0
  14. package/dist/src/debug/effects.d.ts.map +1 -1
  15. package/dist/src/debug/effects.js.map +1 -1
  16. package/dist/src/debug/effects.test.js +22 -24
  17. package/dist/src/debug/effects.test.js.map +1 -1
  18. package/dist/src/debug/index.d.ts +2 -1
  19. package/dist/src/debug/index.d.ts.map +1 -1
  20. package/dist/src/debug/index.js +2 -1
  21. package/dist/src/debug/index.js.map +1 -1
  22. package/dist/src/debug/symbols.d.ts +6 -0
  23. package/dist/src/debug/symbols.d.ts.map +1 -1
  24. package/dist/src/debug/symbols.js +9 -0
  25. package/dist/src/debug/symbols.js.map +1 -1
  26. package/dist/src/index.d.ts +1 -1
  27. package/dist/src/index.d.ts.map +1 -1
  28. package/dist/src/index.js +1 -1
  29. package/dist/src/index.js.map +1 -1
  30. package/dist/src/reactive-union-set.d.ts.map +1 -1
  31. package/dist/src/reactive-union-set.js +13 -3
  32. package/dist/src/reactive-union-set.js.map +1 -1
  33. package/dist/src/reactivity.d.ts +34 -6
  34. package/dist/src/reactivity.d.ts.map +1 -1
  35. package/dist/src/reactivity.js +161 -123
  36. package/dist/src/reactivity.js.map +1 -1
  37. package/dist/src/render-stack.d.ts +1 -0
  38. package/dist/src/render-stack.d.ts.map +1 -1
  39. package/dist/src/render-stack.js +4 -0
  40. package/dist/src/render-stack.js.map +1 -1
  41. package/dist/src/render.d.ts.map +1 -1
  42. package/dist/src/render.js +15 -13
  43. package/dist/src/render.js.map +1 -1
  44. package/dist/src/scheduler.d.ts +5 -0
  45. package/dist/src/scheduler.d.ts.map +1 -1
  46. package/dist/src/scheduler.js +24 -1
  47. package/dist/src/scheduler.js.map +1 -1
  48. package/dist/src/symbols/output-scope.d.ts.map +1 -1
  49. package/dist/src/symbols/output-scope.js +2 -2
  50. package/dist/src/symbols/output-scope.js.map +1 -1
  51. package/dist/src/symbols/output-symbol.d.ts.map +1 -1
  52. package/dist/src/symbols/output-symbol.js +2 -2
  53. package/dist/src/symbols/output-symbol.js.map +1 -1
  54. package/dist/src/symbols/symbol-flow.d.ts.map +1 -1
  55. package/dist/src/symbols/symbol-flow.js +2 -2
  56. package/dist/src/symbols/symbol-flow.js.map +1 -1
  57. package/dist/src/utils.d.ts.map +1 -1
  58. package/dist/src/utils.js +2 -5
  59. package/dist/src/utils.js.map +1 -1
  60. package/dist/test/lazy-isempty.test.d.ts +2 -0
  61. package/dist/test/lazy-isempty.test.d.ts.map +1 -0
  62. package/dist/test/lazy-isempty.test.js +89 -0
  63. package/dist/test/lazy-isempty.test.js.map +1 -0
  64. package/dist/test/reactive-union-set-disposers.test.d.ts +2 -0
  65. package/dist/test/reactive-union-set-disposers.test.d.ts.map +1 -0
  66. package/dist/test/reactive-union-set-disposers.test.js +98 -0
  67. package/dist/test/reactive-union-set-disposers.test.js.map +1 -0
  68. package/dist/test/reactivity/shallow-reactive.test.d.ts +2 -0
  69. package/dist/test/reactivity/shallow-reactive.test.d.ts.map +1 -0
  70. package/dist/test/reactivity/shallow-reactive.test.js +52 -0
  71. package/dist/test/reactivity/shallow-reactive.test.js.map +1 -0
  72. package/dist/test/scheduler-extended.test.d.ts +2 -0
  73. package/dist/test/scheduler-extended.test.d.ts.map +1 -0
  74. package/dist/test/scheduler-extended.test.js +96 -0
  75. package/dist/test/scheduler-extended.test.js.map +1 -0
  76. package/dist/test/scheduler.test.d.ts +2 -0
  77. package/dist/test/scheduler.test.d.ts.map +1 -0
  78. package/dist/test/scheduler.test.js +46 -0
  79. package/dist/test/scheduler.test.js.map +1 -0
  80. package/dist/tsconfig.tsbuildinfo +1 -1
  81. package/package.json +1 -1
  82. package/src/components/Prose.tsx +1 -1
  83. package/src/components/Scope.tsx +2 -0
  84. package/src/components/SourceDirectory.tsx +1 -2
  85. package/src/content-slot.tsx +2 -2
  86. package/src/context.ts +3 -3
  87. package/src/debug/effects.test.tsx +24 -31
  88. package/src/debug/effects.ts +4 -0
  89. package/src/debug/index.ts +2 -0
  90. package/src/debug/symbols.ts +9 -0
  91. package/src/index.ts +0 -1
  92. package/src/reactive-union-set.ts +14 -3
  93. package/src/reactivity.ts +189 -130
  94. package/src/render-stack.ts +5 -0
  95. package/src/render.ts +16 -14
  96. package/src/scheduler.ts +25 -1
  97. package/src/symbols/output-scope.ts +1 -2
  98. package/src/symbols/output-symbol.ts +1 -2
  99. package/src/symbols/symbol-flow.ts +8 -2
  100. package/src/utils.tsx +2 -4
  101. package/temp/api.json +425 -14
  102. package/test/lazy-isempty.test.tsx +106 -0
  103. package/test/reactive-union-set-disposers.test.tsx +112 -0
  104. package/test/reactivity/shallow-reactive.test.tsx +56 -0
  105. package/test/scheduler-extended.test.tsx +122 -0
  106. package/test/scheduler.test.tsx +50 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alloy-js/core",
3
- "version": "0.23.0-dev.8",
3
+ "version": "0.23.0-dev.9",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,7 +22,7 @@ export function Prose(props: Prose) {
22
22
  .map((word, index, array) => (
23
23
  <>
24
24
  {word}
25
- {index < array.length - 1 && <br />}
25
+ {/*@once*/ index < array.length - 1 && <br />}
26
26
  </>
27
27
  ));
28
28
  }
@@ -1,5 +1,6 @@
1
1
  import { createScope } from "../binder.js";
2
2
  import { ScopeContext, useScope } from "../context/scope.js";
3
+ import { debug } from "../debug/index.js";
3
4
  import type { Children } from "../runtime/component.js";
4
5
  import { BasicScope } from "../symbols/basic-scope.js";
5
6
  import { OutputScope } from "../symbols/output-scope.js";
@@ -52,6 +53,7 @@ export function Scope(props: ScopeProps) {
52
53
  let scope: OutputScope;
53
54
  if ("value" in props) {
54
55
  scope = props.value;
56
+ debug.symbols.relocateScope(scope);
55
57
  } else {
56
58
  const parentScope = useScope();
57
59
  if (parentScope && !(parentScope instanceof BasicScope)) {
@@ -1,8 +1,7 @@
1
- import { shallowReactive } from "@vue/reactivity";
2
1
  import { join } from "pathe";
3
2
  import { useContext } from "../context.js";
4
3
  import { SourceDirectoryContext } from "../context/source-directory.js";
5
- import { getContext } from "../reactivity.js";
4
+ import { getContext, shallowReactive } from "../reactivity.js";
6
5
  import type { Children } from "../runtime/component.js";
7
6
 
8
7
  export interface SourceDirectoryProps {
@@ -1,6 +1,6 @@
1
1
  import { computed, Ref, shallowRef } from "@vue/reactivity";
2
2
  import { Show } from "./components/Show.jsx";
3
- import { getContext } from "./reactivity.js";
3
+ import { ensureIsEmpty, getContext } from "./reactivity.js";
4
4
  import { Children, Component } from "./runtime/component.js";
5
5
 
6
6
  export interface ContentSlot {
@@ -61,7 +61,7 @@ export function createContentSlot(): ContentSlot {
61
61
 
62
62
  function ContentSlot(props: { children: Children }) {
63
63
  const context = getContext()!;
64
- isEmptySource.value = context.isEmpty!;
64
+ isEmptySource.value = ensureIsEmpty(context);
65
65
  return props.children;
66
66
  }
67
67
  ContentSlot.ref = isEmpty;
package/src/context.ts CHANGED
@@ -20,8 +20,8 @@ export function useContext<T>(context: ComponentContext<T>): T | undefined {
20
20
  // context must come from a parent
21
21
  let current = getContext();
22
22
  while (current) {
23
- if (Object.hasOwn(current.context!, context.id)) {
24
- return current.context![context.id] as T | undefined;
23
+ if (current.context && Object.hasOwn(current.context, context.id)) {
24
+ return current.context[context.id] as T | undefined;
25
25
  }
26
26
  current = current.owner;
27
27
  }
@@ -43,7 +43,7 @@ export function createContext<T = unknown>(
43
43
  const rendered = shallowRef();
44
44
  effect(
45
45
  () => {
46
- context!.context![id] = props.value;
46
+ (context!.context ??= {})[id] = props.value;
47
47
  rendered.value = () => props.children;
48
48
  },
49
49
  undefined,
@@ -39,58 +39,51 @@ afterEach(async () => {
39
39
  it("emits effect, ref, edge, and update messages", async () => {
40
40
  const collector = createMessageCollector(socket!);
41
41
  const r1 = ref(0);
42
- const r2 = ref(1);
43
42
 
43
+ // Create an effect that reads r1.
44
+ let observed = 0;
44
45
  effect(() => {
45
- r1.value = r2.value + 1;
46
+ observed = r1.value;
46
47
  });
47
48
 
49
+ // Mutate r1 to trigger the effect.
50
+ r1.value = 42;
51
+
48
52
  await renderAsync(<Output>{"ok"}</Output>);
49
53
 
50
54
  const messages = await collector.waitForRender();
51
55
  const effectsMessages = filterEffectsMessages(messages);
52
56
  collector.stop();
53
57
 
54
- expect(effectsMessages[0]).toMatchObject({
55
- type: "effect:refAdded",
56
- ref: expect.objectContaining({
57
- id: expect.any(Number),
58
- kind: "ref",
59
- }),
58
+ // Check that core message types are present
59
+ const byType = (type: string) =>
60
+ effectsMessages.filter((m: any) => m.type === type);
61
+ expect(byType("effect:refAdded").length).toBeGreaterThanOrEqual(1);
62
+ expect(byType("effect:effectAdded").length).toBeGreaterThanOrEqual(1);
63
+ expect(byType("effect:track").length).toBeGreaterThanOrEqual(1);
64
+ expect(byType("effect:trigger").length).toBeGreaterThanOrEqual(1);
65
+ expect(byType("effect:effectUpdated").length).toBeGreaterThanOrEqual(1);
66
+
67
+ // Verify message shapes
68
+ expect(byType("effect:refAdded")[0]).toMatchObject({
69
+ ref: expect.objectContaining({ id: expect.any(Number), kind: "ref" }),
60
70
  });
61
- expect(effectsMessages[1]).toMatchObject({
62
- type: "effect:refAdded",
63
- ref: expect.objectContaining({
64
- id: expect.any(Number),
65
- kind: "ref",
66
- }),
71
+ expect(byType("effect:effectAdded")[0]).toMatchObject({
72
+ effect: expect.objectContaining({ id: expect.any(Number) }),
67
73
  });
68
- expect(effectsMessages[2]).toMatchObject({
69
- type: "effect:effectAdded",
70
- effect: expect.objectContaining({
71
- id: expect.any(Number),
72
- }),
73
- });
74
- expect(effectsMessages[3]).toMatchObject({
75
- type: "effect:track",
74
+ expect(byType("effect:track")[0]).toMatchObject({
76
75
  edge: expect.objectContaining({
77
76
  type: "track",
78
77
  effectId: expect.any(Number),
79
78
  refId: expect.any(Number),
80
79
  }),
81
80
  });
82
- expect(effectsMessages[4]).toMatchObject({
83
- type: "effect:trigger",
81
+ expect(byType("effect:trigger")[0]).toMatchObject({
84
82
  edge: expect.objectContaining({
85
- type: "trigger",
86
83
  effectId: expect.any(Number),
87
84
  refId: expect.any(Number),
88
85
  }),
89
86
  });
90
- expect(effectsMessages[5]).toMatchObject({
91
- type: "effect:effectUpdated",
92
- effect: expect.objectContaining({
93
- id: expect.any(Number),
94
- }),
95
- });
87
+
88
+ expect(observed).toBe(42);
96
89
  });
@@ -211,6 +211,8 @@ export function register(input: {
211
211
  name?: string;
212
212
  type?: string;
213
213
  createdAt?: SourceLocation;
214
+ contextId?: number;
215
+ ownerContextId?: number | null;
214
216
  }): number {
215
217
  if (!isDevtoolsEnabled()) return -1;
216
218
  const id = effectIdCounter++;
@@ -230,6 +232,7 @@ export function registerRef(input: {
230
232
  kind?: string;
231
233
  createdAt?: SourceLocation;
232
234
  createdByEffectId?: number;
235
+ isInfrastructure?: boolean;
233
236
  }) {
234
237
  if (!isDevtoolsEnabled()) return;
235
238
  if (refs.has(input.id)) return;
@@ -275,6 +278,7 @@ export function trigger(input: {
275
278
  targetKey?: unknown;
276
279
  location?: SourceLocation;
277
280
  kind?: "trigger" | "triggered-by";
281
+ causedBy?: number;
278
282
  }) {
279
283
  if (!isDevtoolsEnabled()) return;
280
284
  const edge: EffectEdgeDebugInfo = {
@@ -40,6 +40,7 @@ import {
40
40
  import {
41
41
  registerScope,
42
42
  registerSymbol,
43
+ relocateScope,
43
44
  reset as resetSymbols,
44
45
  unregisterScope,
45
46
  unregisterSymbol,
@@ -107,6 +108,7 @@ export const debug = {
107
108
  },
108
109
  symbols: {
109
110
  registerScope,
111
+ relocateScope,
110
112
  unregisterScope,
111
113
  registerSymbol,
112
114
  unregisterSymbol,
@@ -184,6 +184,15 @@ export function unregisterScope(scope: OutputScope) {
184
184
  });
185
185
  }
186
186
 
187
+ /**
188
+ * Re-register a pre-existing scope under the current render context.
189
+ * Called when `<Scope value={existingScope}>` mounts an already-created scope
190
+ * into a new location in the render tree.
191
+ */
192
+ export function relocateScope(_scope: OutputScope) {
193
+ // Placeholder — real implementation added in the trace-writer branch.
194
+ }
195
+
187
196
  export function registerSymbol(symbol: OutputSymbol) {
188
197
  if (!isDevtoolsEnabled()) return;
189
198
  if (symbolWatchers.has(symbol.id)) return;
package/src/index.ts CHANGED
@@ -5,7 +5,6 @@ export {
5
5
  isReactive,
6
6
  isRef,
7
7
  reactive,
8
- shallowReactive,
9
8
  toRaw,
10
9
  track,
11
10
  trigger,
@@ -1,14 +1,13 @@
1
1
  import {
2
2
  ITERATE_KEY,
3
3
  ReactiveFlags,
4
- shallowReactive,
5
4
  shallowReadonly,
6
5
  track,
7
6
  TrackOpTypes,
8
7
  trigger,
9
8
  TriggerOpTypes,
10
9
  } from "@vue/reactivity";
11
- import { effect, untrack } from "./reactivity.js";
10
+ import { effect, root, shallowReactive, untrack } from "./reactivity.js";
12
11
 
13
12
  export interface ReactiveUnionSetOptions<T> {
14
13
  onAdd?: OnReactiveSetAddCallback<T>;
@@ -129,6 +128,7 @@ export class ReactiveUnionSet<T> extends Set<T> {
129
128
  * that were added to the parent set.
130
129
  */
131
130
  const prevValues = new Map<T, T>();
131
+ const itemDisposers = new Map<T, () => void>();
132
132
 
133
133
  effect(
134
134
  () => {
@@ -137,13 +137,24 @@ export class ReactiveUnionSet<T> extends Set<T> {
137
137
  untrack(() => onDelete?.(prevSourceValue));
138
138
  prevValues.delete(prevSourceValue);
139
139
  this.delete(prevTargetValue);
140
+ const disposer = itemDisposers.get(prevSourceValue);
141
+ if (disposer) {
142
+ disposer();
143
+ itemDisposers.delete(prevSourceValue);
144
+ }
140
145
  }
141
146
  }
142
147
 
143
148
  for (const value of subset) {
144
149
  if (!prevValues.has(value)) {
145
150
  if (onAdd) {
146
- const added = untrack(() => onAdd(value));
151
+ const added = untrack(() =>
152
+ root((disposer) => {
153
+ const result = onAdd(value);
154
+ itemDisposers.set(value, disposer);
155
+ return result;
156
+ }),
157
+ );
147
158
  prevValues.set(value, added);
148
159
  } else {
149
160
  this.add(value);