@askrjs/askr 0.0.39 → 0.0.40
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/dist/bench/components/benchmark-row.d.ts.map +1 -1
- package/dist/bench/components/benchmark-row.js.map +1 -1
- package/dist/bench/components/benchmark-table.js.map +1 -1
- package/dist/benchmark.d.ts.map +1 -1
- package/dist/benchmark.js +2 -2
- package/dist/benchmark.js.map +1 -1
- package/dist/bin/askr-ssg.d.ts.map +1 -1
- package/dist/bin/askr-ssg.js.map +1 -1
- package/dist/boot/index.d.ts +9 -6
- package/dist/boot/index.d.ts.map +1 -1
- package/dist/boot/index.js +210 -75
- package/dist/boot/index.js.map +1 -1
- package/dist/common/attr-names.js +17 -0
- package/dist/common/attr-names.js.map +1 -0
- package/dist/common/component.d.ts.map +1 -1
- package/dist/common/control.d.ts.map +1 -1
- package/dist/common/control.js.map +1 -1
- package/dist/common/env.js.map +1 -1
- package/dist/common/jsx.d.ts.map +1 -1
- package/dist/common/jsx.js.map +1 -1
- package/dist/common/promise.js +8 -0
- package/dist/common/promise.js.map +1 -0
- package/dist/common/props.d.ts +286 -1
- package/dist/common/props.d.ts.map +1 -1
- package/dist/common/router-internal.js +6 -0
- package/dist/common/router-internal.js.map +1 -0
- package/dist/common/router.d.ts +45 -36
- package/dist/common/router.d.ts.map +1 -1
- package/dist/common/ssr-errors.d.ts.map +1 -1
- package/dist/common/ssr-errors.js.map +1 -1
- package/dist/common/ssr.d.ts +1 -0
- package/dist/common/ssr.d.ts.map +1 -1
- package/dist/common/ssr.js +6 -0
- package/dist/common/ssr.js.map +1 -0
- package/dist/common/vnode.d.ts +2 -1
- package/dist/common/vnode.d.ts.map +1 -1
- package/dist/common/vnode.js.map +1 -1
- package/dist/components/error-boundary.d.ts +7 -4
- package/dist/components/error-boundary.d.ts.map +1 -1
- package/dist/components/error-boundary.js.map +1 -1
- package/dist/components/link.d.ts +2 -1
- package/dist/components/link.d.ts.map +1 -1
- package/dist/components/link.js +10 -1
- package/dist/components/link.js.map +1 -1
- package/dist/control/case.d.ts +4 -2
- package/dist/control/case.d.ts.map +1 -1
- package/dist/control/case.js.map +1 -1
- package/dist/control/for.d.ts +2 -1
- package/dist/control/for.d.ts.map +1 -1
- package/dist/control/for.js.map +1 -1
- package/dist/control/shared.d.ts +9 -0
- package/dist/control/shared.d.ts.map +1 -0
- package/dist/control/shared.js.map +1 -1
- package/dist/control/show.d.ts +4 -3
- package/dist/control/show.d.ts.map +1 -1
- package/dist/control/show.js.map +1 -1
- package/dist/data/index.d.ts +94 -14
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +65 -14
- package/dist/data/index.js.map +1 -1
- package/dist/dev/invariant.js.map +1 -1
- package/dist/dev/logger.js.map +1 -1
- package/dist/foundations/icon/icon.d.ts.map +1 -1
- package/dist/foundations/icon/icon.js.map +1 -1
- package/dist/foundations/icon/icon.types.d.ts.map +1 -1
- package/dist/foundations/interactions/dismissable.d.ts +9 -3
- package/dist/foundations/interactions/dismissable.d.ts.map +1 -1
- package/dist/foundations/interactions/dismissable.js.map +1 -1
- package/dist/foundations/interactions/focusable.d.ts.map +1 -1
- package/dist/foundations/interactions/focusable.js.map +1 -1
- package/dist/foundations/interactions/hoverable.d.ts.map +1 -1
- package/dist/foundations/interactions/hoverable.js.map +1 -1
- package/dist/foundations/interactions/interaction-policy.d.ts +1 -1
- package/dist/foundations/interactions/interaction-policy.d.ts.map +1 -1
- package/dist/foundations/interactions/interaction-policy.js +3 -3
- package/dist/foundations/interactions/interaction-policy.js.map +1 -1
- package/dist/foundations/interactions/pressable.d.ts.map +1 -1
- package/dist/foundations/interactions/pressable.js.map +1 -1
- package/dist/foundations/interactions/roving-focus.d.ts.map +1 -1
- package/dist/foundations/interactions/roving-focus.js +8 -8
- package/dist/foundations/interactions/roving-focus.js.map +1 -1
- package/dist/foundations/state/controllable.d.ts.map +1 -1
- package/dist/foundations/state/controllable.js.map +1 -1
- package/dist/foundations/structures/collection.d.ts.map +1 -1
- package/dist/foundations/structures/collection.js.map +1 -1
- package/dist/foundations/structures/layer.d.ts.map +1 -1
- package/dist/foundations/structures/layer.js.map +1 -1
- package/dist/foundations/structures/layout.d.ts +4 -2
- package/dist/foundations/structures/layout.d.ts.map +1 -1
- package/dist/foundations/structures/layout.js.map +1 -1
- package/dist/foundations/structures/portal.d.ts +9 -25
- package/dist/foundations/structures/portal.d.ts.map +1 -1
- package/dist/foundations/structures/portal.js +98 -17
- package/dist/foundations/structures/portal.js.map +1 -1
- package/dist/foundations/structures/presence.d.ts +4 -3
- package/dist/foundations/structures/presence.d.ts.map +1 -1
- package/dist/foundations/structures/presence.js +2 -2
- package/dist/foundations/structures/presence.js.map +1 -1
- package/dist/foundations/structures/slot.d.ts +2 -1
- package/dist/foundations/structures/slot.d.ts.map +1 -1
- package/dist/foundations/structures/slot.js.map +1 -1
- package/dist/foundations/utilities/aria.d.ts.map +1 -1
- package/dist/foundations/utilities/aria.js.map +1 -1
- package/dist/foundations/utilities/compose-handlers.d.ts.map +1 -1
- package/dist/foundations/utilities/compose-handlers.js.map +1 -1
- package/dist/foundations/utilities/compose-ref.d.ts.map +1 -1
- package/dist/foundations/utilities/compose-ref.js.map +1 -1
- package/dist/foundations/utilities/event-types.d.ts.map +1 -1
- package/dist/foundations/utilities/merge-props.js.map +1 -1
- package/dist/foundations/utilities/use-id.d.ts.map +1 -1
- package/dist/foundations/utilities/use-id.js.map +1 -1
- package/dist/fx/fx.d.ts.map +1 -1
- package/dist/fx/fx.js.map +1 -1
- package/dist/fx/noop.js.map +1 -1
- package/dist/fx/timing.d.ts +5 -4
- package/dist/fx/timing.d.ts.map +1 -1
- package/dist/fx/timing.js +7 -4
- package/dist/fx/timing.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/jsx/types.d.ts +3 -3
- package/dist/jsx/types.d.ts.map +1 -1
- package/dist/jsx/utils.d.ts.map +1 -1
- package/dist/jsx/utils.js.map +1 -1
- package/dist/jsx-dev-runtime.d.ts +6 -2
- package/dist/jsx-dev-runtime.d.ts.map +1 -1
- package/dist/jsx-dev-runtime.js.map +1 -1
- package/dist/jsx-runtime.d.ts +16 -6
- package/dist/jsx-runtime.d.ts.map +1 -1
- package/dist/jsx-runtime.js.map +1 -1
- package/dist/renderer/children.js.map +1 -1
- package/dist/renderer/cleanup.js +40 -13
- package/dist/renderer/cleanup.js.map +1 -1
- package/dist/renderer/dom.js +163 -63
- package/dist/renderer/dom.js.map +1 -1
- package/dist/renderer/evaluate.js +9 -6
- package/dist/renderer/evaluate.js.map +1 -1
- package/dist/renderer/fastpath.js.map +1 -1
- package/dist/renderer/for-commit.js +1 -0
- package/dist/renderer/for-commit.js.map +1 -1
- package/dist/renderer/index.js.map +1 -1
- package/dist/renderer/keyed.js.map +1 -1
- package/dist/renderer/reconcile.js +34 -0
- package/dist/renderer/reconcile.js.map +1 -1
- package/dist/renderer/utils.js +34 -3
- package/dist/renderer/utils.js.map +1 -1
- package/dist/router/index.d.ts +2 -2
- package/dist/router/match.js +16 -2
- package/dist/router/match.js.map +1 -1
- package/dist/router/navigate.d.ts +9 -1
- package/dist/router/navigate.d.ts.map +1 -1
- package/dist/router/navigate.js +235 -81
- package/dist/router/navigate.js.map +1 -1
- package/dist/router/policy.d.ts +7 -7
- package/dist/router/policy.d.ts.map +1 -1
- package/dist/router/policy.js +5 -7
- package/dist/router/policy.js.map +1 -1
- package/dist/router/route-context.js.map +1 -1
- package/dist/router/route.d.ts +20 -9
- package/dist/router/route.d.ts.map +1 -1
- package/dist/router/route.js +95 -29
- package/dist/router/route.js.map +1 -1
- package/dist/runtime/child-scope.d.ts.map +1 -1
- package/dist/runtime/child-scope.js +5 -1
- package/dist/runtime/child-scope.js.map +1 -1
- package/dist/runtime/component.d.ts +5 -2
- package/dist/runtime/component.d.ts.map +1 -1
- package/dist/runtime/component.js +85 -25
- package/dist/runtime/component.js.map +1 -1
- package/dist/runtime/context.d.ts +5 -2
- package/dist/runtime/context.d.ts.map +1 -1
- package/dist/runtime/context.js +5 -2
- package/dist/runtime/context.js.map +1 -1
- package/dist/runtime/control.d.ts.map +1 -1
- package/dist/runtime/control.js.map +1 -1
- package/dist/runtime/derive.d.ts.map +1 -1
- package/dist/runtime/derive.js +3 -1
- package/dist/runtime/derive.js.map +1 -1
- package/dist/runtime/dev-namespace.js.map +1 -1
- package/dist/runtime/effect.d.ts.map +1 -1
- package/dist/runtime/effect.js +17 -11
- package/dist/runtime/effect.js.map +1 -1
- package/dist/runtime/events.js +43 -1
- package/dist/runtime/events.js.map +1 -1
- package/dist/runtime/execution-model.js.map +1 -1
- package/dist/runtime/fastlane.js +2 -23
- package/dist/runtime/fastlane.js.map +1 -1
- package/dist/runtime/for-bench.d.ts.map +1 -1
- package/dist/runtime/for-bench.js.map +1 -1
- package/dist/runtime/for.d.ts +1 -1
- package/dist/runtime/for.d.ts.map +1 -1
- package/dist/runtime/for.js +36 -12
- package/dist/runtime/for.js.map +1 -1
- package/dist/runtime/operations.d.ts +3 -13
- package/dist/runtime/operations.d.ts.map +1 -1
- package/dist/runtime/operations.js +47 -39
- package/dist/runtime/operations.js.map +1 -1
- package/dist/runtime/perf-metrics.js.map +1 -1
- package/dist/runtime/readable.d.ts +1 -0
- package/dist/runtime/readable.d.ts.map +1 -1
- package/dist/runtime/readable.js +6 -1
- package/dist/runtime/readable.js.map +1 -1
- package/dist/runtime/resource-cell.js +13 -2
- package/dist/runtime/resource-cell.js.map +1 -1
- package/dist/runtime/scheduler.d.ts.map +1 -1
- package/dist/runtime/scheduler.js +8 -4
- package/dist/runtime/scheduler.js.map +1 -1
- package/dist/runtime/selector.d.ts.map +1 -1
- package/dist/runtime/selector.js +8 -2
- package/dist/runtime/selector.js.map +1 -1
- package/dist/runtime/snapshot-source.d.ts.map +1 -1
- package/dist/runtime/snapshot-source.js.map +1 -1
- package/dist/runtime/ssr-bridge.js.map +1 -1
- package/dist/runtime/state.d.ts +4 -3
- package/dist/runtime/state.d.ts.map +1 -1
- package/dist/runtime/state.js.map +1 -1
- package/dist/ssg/batch-render.js +4 -8
- package/dist/ssg/batch-render.js.map +1 -1
- package/dist/ssg/create-static-gen.d.ts +10 -2
- package/dist/ssg/create-static-gen.d.ts.map +1 -1
- package/dist/ssg/create-static-gen.js +5 -4
- package/dist/ssg/create-static-gen.js.map +1 -1
- package/dist/ssg/generate-metadata.js.map +1 -1
- package/dist/ssg/incremental-manifest.js.map +1 -1
- package/dist/ssg/resolve-ssg-data.js +54 -12
- package/dist/ssg/resolve-ssg-data.js.map +1 -1
- package/dist/ssg/route-utils.js.map +1 -1
- package/dist/ssg/types.d.ts +8 -7
- package/dist/ssg/types.d.ts.map +1 -1
- package/dist/ssg/write-static-files.js.map +1 -1
- package/dist/ssr/attrs.js +3 -2
- package/dist/ssr/attrs.js.map +1 -1
- package/dist/ssr/context.d.ts +1 -0
- package/dist/ssr/context.d.ts.map +1 -1
- package/dist/ssr/context.js +1 -0
- package/dist/ssr/context.js.map +1 -1
- package/dist/ssr/errors.d.ts.map +1 -1
- package/dist/ssr/escape.js.map +1 -1
- package/dist/ssr/index.d.ts +8 -12
- package/dist/ssr/index.d.ts.map +1 -1
- package/dist/ssr/index.js +55 -30
- package/dist/ssr/index.js.map +1 -1
- package/dist/ssr/render-keys.js +15 -4
- package/dist/ssr/render-keys.js.map +1 -1
- package/dist/ssr/render-resolved.js +33 -0
- package/dist/ssr/render-resolved.js.map +1 -0
- package/dist/ssr/sink.js.map +1 -1
- package/dist/ssr/types.d.ts.map +1 -1
- package/dist/ssr/verify-hydration.js +5 -6
- package/dist/ssr/verify-hydration.js.map +1 -1
- package/package.json +12 -4
|
@@ -4,6 +4,7 @@ import { globalScheduler } from "./scheduler.js";
|
|
|
4
4
|
import { withContext } from "./context.js";
|
|
5
5
|
import { cleanupReadableSubscriptions, finalizeReadableSubscriptions } from "./readable.js";
|
|
6
6
|
import { incDevCounter, setDevValue } from "./dev-namespace.js";
|
|
7
|
+
import { isPromiseLike } from "../common/promise.js";
|
|
7
8
|
import { isBulkCommitActive } from "./fastlane.js";
|
|
8
9
|
import { cleanupInstancesUnder } from "../renderer/cleanup.js";
|
|
9
10
|
import { evaluate } from "../renderer/evaluate.js";
|
|
@@ -19,6 +20,7 @@ function createComponentInstance(id, fn, props, target) {
|
|
|
19
20
|
fn,
|
|
20
21
|
props,
|
|
21
22
|
target,
|
|
23
|
+
portalScope: currentInstance?.portalScope ?? currentPortalScope ?? null,
|
|
22
24
|
mounted: false,
|
|
23
25
|
abortController: null,
|
|
24
26
|
stateValues: [],
|
|
@@ -32,6 +34,7 @@ function createComponentInstance(id, fn, props, target) {
|
|
|
32
34
|
firstRenderComplete: false,
|
|
33
35
|
mountOperations: [],
|
|
34
36
|
cleanupFns: [],
|
|
37
|
+
lifecycleGeneration: 0,
|
|
35
38
|
hasPendingUpdate: false,
|
|
36
39
|
ownerFrame: null,
|
|
37
40
|
ssr: false,
|
|
@@ -45,6 +48,7 @@ function createComponentInstance(id, fn, props, target) {
|
|
|
45
48
|
};
|
|
46
49
|
instance._pendingRunTask = () => {
|
|
47
50
|
instance.hasPendingUpdate = false;
|
|
51
|
+
if (instance.notifyUpdate === null) return;
|
|
48
52
|
runComponent(instance);
|
|
49
53
|
};
|
|
50
54
|
instance._enqueueRun = () => {
|
|
@@ -57,6 +61,7 @@ function createComponentInstance(id, fn, props, target) {
|
|
|
57
61
|
return instance;
|
|
58
62
|
}
|
|
59
63
|
let currentInstance = null;
|
|
64
|
+
let currentPortalScope = null;
|
|
60
65
|
let stateIndex = 0;
|
|
61
66
|
function ensureAbortController(instance) {
|
|
62
67
|
let controller = instance.abortController;
|
|
@@ -71,6 +76,10 @@ function getCurrentComponentInstance() {
|
|
|
71
76
|
}
|
|
72
77
|
function setCurrentComponentInstance(instance) {
|
|
73
78
|
currentInstance = instance;
|
|
79
|
+
currentPortalScope = instance?.portalScope ?? null;
|
|
80
|
+
}
|
|
81
|
+
function getCurrentPortalScope() {
|
|
82
|
+
return currentInstance?.portalScope ?? currentPortalScope;
|
|
74
83
|
}
|
|
75
84
|
function registerMountOperation(operation) {
|
|
76
85
|
const instance = getCurrentComponentInstance();
|
|
@@ -90,10 +99,23 @@ function executeMountOperations(instance) {
|
|
|
90
99
|
if (!instance.isRoot) return;
|
|
91
100
|
const mountOperations = instance.mountOperations;
|
|
92
101
|
if (mountOperations.length === 0) return;
|
|
102
|
+
const lifecycleGeneration = instance.lifecycleGeneration;
|
|
93
103
|
for (const operation of mountOperations) {
|
|
94
104
|
const result = operation();
|
|
95
|
-
if (result
|
|
96
|
-
if (typeof cleanup === "function")
|
|
105
|
+
if (isPromiseLike(result)) Promise.resolve(result).then((cleanup) => {
|
|
106
|
+
if (typeof cleanup === "function") {
|
|
107
|
+
if (instance.lifecycleGeneration === lifecycleGeneration && instance.mounted) {
|
|
108
|
+
instance.cleanupFns.push(cleanup);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
cleanup();
|
|
113
|
+
} catch (err) {
|
|
114
|
+
logger.error("[Askr] async mount cleanup failed:", err);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}, (err) => {
|
|
118
|
+
logger.error("[Askr] async mount operation failed:", err);
|
|
97
119
|
});
|
|
98
120
|
else if (typeof result === "function") instance.cleanupFns.push(result);
|
|
99
121
|
}
|
|
@@ -133,11 +155,13 @@ function nextRenderToken() {
|
|
|
133
155
|
}
|
|
134
156
|
function renderScopedComponent(instance, startStateIndex, render) {
|
|
135
157
|
const savedInstance = currentInstance;
|
|
158
|
+
const savedPortalScope = currentPortalScope;
|
|
136
159
|
const savedStateIndex = stateIndex;
|
|
137
160
|
instance.notifyUpdate = instance._enqueueRun;
|
|
138
161
|
resetRenderState(instance);
|
|
139
162
|
instance._currentRenderToken = nextRenderToken();
|
|
140
163
|
currentInstance = instance;
|
|
164
|
+
currentPortalScope = instance.portalScope ?? savedPortalScope;
|
|
141
165
|
stateIndex = startStateIndex;
|
|
142
166
|
let didComplete = false;
|
|
143
167
|
try {
|
|
@@ -153,6 +177,7 @@ function renderScopedComponent(instance, startStateIndex, render) {
|
|
|
153
177
|
instance._currentRenderToken = void 0;
|
|
154
178
|
}
|
|
155
179
|
currentInstance = savedInstance;
|
|
180
|
+
currentPortalScope = savedPortalScope;
|
|
156
181
|
stateIndex = savedStateIndex;
|
|
157
182
|
}
|
|
158
183
|
}
|
|
@@ -162,7 +187,7 @@ function runComponent(instance) {
|
|
|
162
187
|
instance._pendingReadSources = void 0;
|
|
163
188
|
const domSnapshot = instance.target ? instance.target.innerHTML : "";
|
|
164
189
|
const result = executeComponentSync(instance);
|
|
165
|
-
if (result
|
|
190
|
+
if (isPromiseLike(result)) throw new Error("Async components are not supported. Components must be synchronous.");
|
|
166
191
|
else {
|
|
167
192
|
const fastlaneBridge = globalThis.__ASKR_FASTLANE;
|
|
168
193
|
try {
|
|
@@ -276,6 +301,8 @@ function renderComponentInline(instance) {
|
|
|
276
301
|
const hadToken = instance._currentRenderToken !== void 0;
|
|
277
302
|
const prevToken = instance._currentRenderToken;
|
|
278
303
|
const prevPendingReads = instance._pendingReadSources;
|
|
304
|
+
const savedInstance = currentInstance;
|
|
305
|
+
const savedPortalScope = currentPortalScope;
|
|
279
306
|
if (!hadToken) {
|
|
280
307
|
instance._currentRenderToken = nextRenderToken();
|
|
281
308
|
instance._pendingReadSources = void 0;
|
|
@@ -287,13 +314,15 @@ function renderComponentInline(instance) {
|
|
|
287
314
|
} finally {
|
|
288
315
|
instance._currentRenderToken = prevToken;
|
|
289
316
|
instance._pendingReadSources = prevPendingReads;
|
|
317
|
+
currentInstance = savedInstance;
|
|
318
|
+
currentPortalScope = savedPortalScope;
|
|
290
319
|
}
|
|
291
320
|
}
|
|
292
321
|
function warnUnusedStateReads(instance) {
|
|
293
322
|
for (let i = 0; i < instance.stateValues.length; i++) {
|
|
294
323
|
const state = instance.stateValues[i];
|
|
295
324
|
const hasCommittedUsage = (state?._readers?.size ?? 0) > 0 || (state?._derivedSubscribers?.size ?? 0) > 0;
|
|
296
|
-
if (state && !state._hasBeenRead && !hasCommittedUsage) try {
|
|
325
|
+
if (state && !state._hasBeenRead && !state._hasEverBeenRead && !hasCommittedUsage) try {
|
|
297
326
|
const name = instance.fn?.name || "<anonymous>";
|
|
298
327
|
warnInstanceOnce(instance, `unused-state:${i}`, `[askr] Unused state variable detected in ${name} at index ${i}. State should be read during render or removed.`);
|
|
299
328
|
} catch {
|
|
@@ -305,7 +334,9 @@ function executeComponentSync(instance) {
|
|
|
305
334
|
resetRenderState(instance);
|
|
306
335
|
incDevCounter("componentRuns");
|
|
307
336
|
incDevCounter("componentReruns");
|
|
337
|
+
const savedPortalScope = currentPortalScope;
|
|
308
338
|
currentInstance = instance;
|
|
339
|
+
currentPortalScope = instance.portalScope ?? savedPortalScope;
|
|
309
340
|
stateIndex = 0;
|
|
310
341
|
try {
|
|
311
342
|
const renderStartTime = isDevelopmentEnvironment() ? Date.now() : 0;
|
|
@@ -322,6 +353,7 @@ function executeComponentSync(instance) {
|
|
|
322
353
|
return result;
|
|
323
354
|
} finally {
|
|
324
355
|
currentInstance = null;
|
|
356
|
+
currentPortalScope = savedPortalScope;
|
|
325
357
|
}
|
|
326
358
|
}
|
|
327
359
|
/**
|
|
@@ -421,27 +453,55 @@ function mountComponent(instance) {
|
|
|
421
453
|
* Called on unmount or route change
|
|
422
454
|
*/
|
|
423
455
|
function cleanupComponent(instance) {
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
456
|
+
const savedInstance = currentInstance;
|
|
457
|
+
const savedPortalScope = currentPortalScope;
|
|
458
|
+
currentInstance = null;
|
|
459
|
+
currentPortalScope = null;
|
|
460
|
+
try {
|
|
461
|
+
const cleanupErrors = [];
|
|
462
|
+
const recordCleanupError = (message, err) => {
|
|
463
|
+
if (instance.cleanupStrict) cleanupErrors.push(err);
|
|
464
|
+
else if (isDevelopmentEnvironment()) logger.warn(message, err);
|
|
465
|
+
};
|
|
466
|
+
const ownedChildScopes = instance._ownedChildScopes;
|
|
467
|
+
if (ownedChildScopes && ownedChildScopes.size > 0) {
|
|
468
|
+
instance._ownedChildScopes = /* @__PURE__ */ new Set();
|
|
469
|
+
for (const scope of ownedChildScopes) try {
|
|
470
|
+
scope.dispose();
|
|
471
|
+
} catch (err) {
|
|
472
|
+
recordCleanupError("[Askr] child scope cleanup threw:", err);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
const cleanupFns = instance.cleanupFns;
|
|
476
|
+
instance.cleanupFns = [];
|
|
477
|
+
for (const cleanup of cleanupFns) try {
|
|
478
|
+
cleanup();
|
|
479
|
+
} catch (err) {
|
|
480
|
+
recordCleanupError("[Askr] cleanup function threw:", err);
|
|
481
|
+
}
|
|
482
|
+
try {
|
|
483
|
+
cleanupReadableSubscriptions(instance);
|
|
484
|
+
} catch (err) {
|
|
485
|
+
recordCleanupError("[Askr] readable subscription cleanup threw:", err);
|
|
486
|
+
}
|
|
487
|
+
try {
|
|
488
|
+
if (instance.abortController && !instance.abortController.signal.aborted) instance.abortController.abort();
|
|
489
|
+
} catch (err) {
|
|
490
|
+
recordCleanupError("[Askr] abort controller cleanup threw:", err);
|
|
491
|
+
}
|
|
492
|
+
instance.abortController = null;
|
|
493
|
+
instance.lifecycleGeneration++;
|
|
494
|
+
instance.evaluationGeneration++;
|
|
495
|
+
instance.mountOperations = [];
|
|
496
|
+
instance.hasPendingUpdate = false;
|
|
497
|
+
instance.notifyUpdate = null;
|
|
498
|
+
instance._placeholder = void 0;
|
|
499
|
+
instance.mounted = false;
|
|
500
|
+
if (cleanupErrors.length > 0) throw new AggregateError(cleanupErrors, `Cleanup failed for component ${instance.id}`);
|
|
501
|
+
} finally {
|
|
502
|
+
currentInstance = savedInstance;
|
|
503
|
+
currentPortalScope = savedPortalScope;
|
|
436
504
|
}
|
|
437
|
-
instance.cleanupFns = [];
|
|
438
|
-
if (cleanupErrors.length > 0) throw new AggregateError(cleanupErrors, `Cleanup failed for component ${instance.id}`);
|
|
439
|
-
cleanupReadableSubscriptions(instance);
|
|
440
|
-
if (instance.abortController && !instance.abortController.signal.aborted) instance.abortController.abort();
|
|
441
|
-
instance.abortController = null;
|
|
442
|
-
instance.notifyUpdate = null;
|
|
443
|
-
instance._placeholder = void 0;
|
|
444
|
-
instance.mounted = false;
|
|
445
505
|
}
|
|
446
506
|
function registerOwnedChildScope(instance, scope) {
|
|
447
507
|
(instance._ownedChildScopes ??= /* @__PURE__ */ new Set()).add(scope);
|
|
@@ -457,6 +517,6 @@ function warnInstanceOnce(instance, key, message) {
|
|
|
457
517
|
logger.warn(message);
|
|
458
518
|
}
|
|
459
519
|
//#endregion
|
|
460
|
-
export { claimHookIndex, cleanupComponent, createComponentInstance, finalizeReadSubscriptions, getCurrentComponentInstance, getCurrentInstance, getCurrentStateIndex, getSignal, mountComponent, mountInstanceInline, registerMountOperation, registerOwnedChildScope, renderComponentInline, renderScopedComponent, setCurrentComponentInstance, unregisterOwnedChildScope, warnUnusedStateReads };
|
|
520
|
+
export { claimHookIndex, cleanupComponent, createComponentInstance, finalizeReadSubscriptions, getCurrentComponentInstance, getCurrentInstance, getCurrentPortalScope, getCurrentStateIndex, getSignal, mountComponent, mountInstanceInline, registerMountOperation, registerOwnedChildScope, renderComponentInline, renderScopedComponent, setCurrentComponentInstance, unregisterOwnedChildScope, warnUnusedStateReads };
|
|
461
521
|
|
|
462
522
|
//# sourceMappingURL=component.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.js","names":[],"sources":["../../src/runtime/component.ts"],"sourcesContent":["/**\n * Component instance lifecycle management\n * Internal only — users never see this\n */\n\nimport { type State } from './state';\nimport { globalScheduler } from './scheduler';\nimport type { Props } from '../common/props';\nimport type { ComponentFunction } from '../common/component';\nimport {\n // withContext is the sole primitive for context restoration\n withContext,\n type ContextFrame,\n} from './context';\nimport {\n type ReadableSource,\n finalizeReadableSubscriptions,\n cleanupReadableSubscriptions,\n} from './readable';\nimport {\n isDevelopmentEnvironment,\n isProductionEnvironment,\n} from '../common/env';\nimport { logger } from '../dev/logger';\nimport { incDevCounter, setDevValue } from './dev-namespace';\n\nexport type { ComponentFunction } from '../common/component';\n\nexport interface ComponentInstance {\n id: string;\n fn: ComponentFunction;\n props: Props;\n target: Element | null;\n mounted: boolean;\n abortController: AbortController | null; // Lazily created per-component abort lifecycle\n ssr?: boolean; // Set to true for SSR temporary instances\n // Opt-in strict cleanup mode: when true cleanup errors are aggregated and re-thrown\n cleanupStrict?: boolean;\n stateValues: State<unknown>[]; // Persistent state storage across renders\n evaluationGeneration: number; // Prevents stale async evaluation completions\n notifyUpdate: (() => void) | null; // Callback for state updates (persisted on instance)\n // Internal: prebound helpers to avoid per-update closures (allocation hot-path)\n _pendingFlushTask?: () => void; // Clears hasPendingUpdate and triggers notifyUpdate\n _pendingRunTask?: () => void; // Clears hasPendingUpdate and runs component\n _enqueueRun?: () => void; // Batches run requests and enqueues _pendingRunTask\n stateIndexCheck: number; // Track state indices to catch conditional calls\n expectedStateIndices: number[]; // Expected sequence of render-scoped hook indices (frozen after first render)\n firstRenderComplete: boolean; // Flag to detect transition from first to subsequent renders\n mountOperations: Array<\n () => void | (() => void) | Promise<void | (() => void)>\n >; // Operations to run when component mounts\n cleanupFns: Array<() => void>; // Cleanup functions to run on unmount\n hasPendingUpdate: boolean; // Flag to batch state updates (coalescing)\n ownerFrame: ContextFrame | null; // Provider chain for this component (set by Scope, never overwritten)\n isRoot?: boolean;\n\n // Render-tracking for precise subscriptions (internal)\n _currentRenderToken?: number; // Token for the in-progress render (set before render)\n lastRenderToken?: number; // Token of the last *committed* render\n _pendingReadSources?: Set<ReadableSource<unknown>>; // Readables read during the in-progress render\n _lastReadSources?: Set<ReadableSource<unknown>>; // Readables read during the last committed render\n devWarningsEmitted?: Set<string>; // Dev-only warning dedupe for this instance\n\n // Placeholder for null-returning components. When a component initially returns\n // null, we create a comment placeholder so updates can replace it with content.\n _placeholder?: Comment;\n _ownedChildScopes?: Set<{\n key: string | number;\n dispose(): void;\n }>;\n errorBoundaryState?: {\n error: unknown | null;\n resetKey: unknown;\n notified: boolean;\n };\n}\n\nexport function createComponentInstance(\n id: string,\n fn: ComponentFunction,\n props: Props,\n target: Element | null\n): ComponentInstance {\n const instance: ComponentInstance = {\n id,\n fn,\n props,\n target,\n mounted: false,\n abortController: null,\n stateValues: [],\n evaluationGeneration: 0,\n notifyUpdate: null,\n // Prebound helpers (initialized below) to avoid per-update allocations\n _pendingFlushTask: undefined,\n _pendingRunTask: undefined,\n _enqueueRun: undefined,\n stateIndexCheck: -1,\n expectedStateIndices: [],\n firstRenderComplete: false,\n mountOperations: [],\n cleanupFns: [],\n hasPendingUpdate: false,\n ownerFrame: null, // Will be set by renderer when vnode is marked\n ssr: false,\n cleanupStrict: false,\n isRoot: false,\n\n // Render-tracking (for precise state subscriptions)\n _currentRenderToken: undefined,\n lastRenderToken: 0,\n _pendingReadSources: undefined,\n _lastReadSources: undefined,\n devWarningsEmitted: undefined,\n };\n\n // Initialize prebound helper tasks once per instance to avoid allocations\n instance._pendingRunTask = () => {\n // Clear pending flag when the run task executes\n instance.hasPendingUpdate = false;\n // Execute component run (will set up notifyUpdate before render)\n runComponent(instance);\n };\n\n instance._enqueueRun = () => {\n if (!instance.hasPendingUpdate) {\n instance.hasPendingUpdate = true;\n // Enqueue single run task (coalesces multiple writes)\n globalScheduler.enqueue(instance._pendingRunTask!);\n }\n };\n\n // Default state-driven updates enqueue the run task directly. Specialized\n // runtimes (for example `For` item instances) can still override this hook.\n instance._pendingFlushTask = instance._pendingRunTask;\n\n return instance;\n}\n\nlet currentInstance: ComponentInstance | null = null;\nlet stateIndex = 0;\n\ntype OwnedChildScope = {\n key: string | number;\n dispose(): void;\n};\n\nfunction ensureAbortController(instance: ComponentInstance): AbortController {\n let controller = instance.abortController;\n if (!controller || controller.signal.aborted) {\n controller = new AbortController();\n instance.abortController = controller;\n }\n return controller;\n}\n\n// Export for state.ts to access\nexport function getCurrentComponentInstance(): ComponentInstance | null {\n return currentInstance;\n}\n\n// Export for SSR to set temporary instance\nexport function setCurrentComponentInstance(\n instance: ComponentInstance | null\n): void {\n currentInstance = instance;\n}\n\n/**\n * Register a mount operation that will run after the component is mounted\n * Used by operations (task, on, timer, etc) to execute after render completes\n */\nimport { isBulkCommitActive } from './fastlane';\nimport { evaluate, cleanupInstancesUnder } from '../renderer';\n\nexport function registerMountOperation(\n operation: () => void | (() => void) | Promise<void | (() => void)>\n): void {\n const instance = getCurrentComponentInstance();\n if (instance) {\n // If we're in bulk-commit fast lane, registering mount operations is a\n // violation of the fast-lane preconditions. Throw in dev, otherwise ignore\n // silently in production (we must avoid scheduling work during bulk commit).\n if (isBulkCommitActive()) {\n if (isDevelopmentEnvironment()) {\n throw new Error(\n 'registerMountOperation called during bulk commit fast-lane'\n );\n }\n return;\n }\n instance.mountOperations.push(operation);\n }\n}\n\n/**\n * Execute all mount operations for a component\n * These run after the component is rendered and mounted to the DOM\n */\nfunction executeMountOperations(instance: ComponentInstance): void {\n // Only execute mount operations for root app instance. Child component\n // operations are currently registered but should not be executed (per\n // contract tests). They remain registered for cleanup purposes.\n if (!instance.isRoot) return;\n\n const mountOperations = instance.mountOperations;\n if (mountOperations.length === 0) {\n return;\n }\n\n for (const operation of mountOperations) {\n const result = operation();\n if (result instanceof Promise) {\n result.then((cleanup) => {\n if (typeof cleanup === 'function') {\n instance.cleanupFns.push(cleanup);\n }\n });\n } else if (typeof result === 'function') {\n instance.cleanupFns.push(result);\n }\n }\n // Clear the operations array so they don't run again on subsequent renders\n instance.mountOperations = [];\n}\n\nexport function mountInstanceInline(\n instance: ComponentInstance,\n target: Element | null\n): void {\n instance.target = target;\n // Record backref on host element so renderer can clean up when the\n // node is removed. Avoids leaks if the node is detached or replaced.\n try {\n if (target instanceof Element) {\n const host = target as Element & {\n __ASKR_INSTANCE?: ComponentInstance;\n __ASKR_INSTANCES?: ComponentInstance[];\n };\n const instances = host.__ASKR_INSTANCES ?? [];\n const nextInstances = instances.filter((entry) => entry !== instance);\n nextInstances.push(instance);\n host.__ASKR_INSTANCES = nextInstances;\n host.__ASKR_INSTANCE = nextInstances[0] ?? instance;\n }\n } catch (err) {\n void err;\n }\n\n // Ensure notifyUpdate is available for async resource completions that may\n // try to trigger re-render. This mirrors the setup in executeComponent().\n // Use prebound enqueue helper to avoid allocating a new closure\n instance.notifyUpdate = instance._enqueueRun!;\n\n const wasFirstMount = !instance.mounted;\n instance.mounted = true;\n if (wasFirstMount && instance.mountOperations.length > 0) {\n executeMountOperations(instance);\n }\n}\n\n/**\n * Run a component synchronously: execute function, handle result\n * This is the internal workhorse that manages async continuations and generation tracking.\n * Must always be called through the scheduler.\n *\n * ACTOR INVARIANT: This function is enqueued as a task, never called directly.\n */\nlet _globalRenderCounter = 0;\n\nfunction resetRenderState(instance: ComponentInstance): void {\n instance.stateIndexCheck = -1;\n\n for (const state of instance.stateValues) {\n if (state) {\n state._hasBeenRead = false;\n }\n }\n\n instance._pendingReadSources = undefined;\n}\n\nfunction nextRenderToken(): number {\n return ++_globalRenderCounter;\n}\n\nexport function renderScopedComponent<T>(\n instance: ComponentInstance,\n startStateIndex: number,\n render: () => T\n): T {\n const savedInstance = currentInstance;\n const savedStateIndex = stateIndex;\n\n instance.notifyUpdate = instance._enqueueRun!;\n resetRenderState(instance);\n instance._currentRenderToken = nextRenderToken();\n\n currentInstance = instance;\n stateIndex = startStateIndex;\n\n let didComplete = false;\n\n try {\n const executionFrame: ContextFrame = {\n parent: instance.ownerFrame,\n values: null,\n };\n const result = withContext(executionFrame, render);\n didComplete = true;\n return result;\n } finally {\n if (!didComplete) {\n instance._pendingReadSources = undefined;\n instance._currentRenderToken = undefined;\n }\n currentInstance = savedInstance;\n stateIndex = savedStateIndex;\n }\n}\n\nfunction runComponent(instance: ComponentInstance): void {\n // CRITICAL: Ensure notifyUpdate is available for state.set() calls during this render.\n // This must be set before executeComponentSync() runs, not after.\n // Use prebound enqueue helper to avoid allocating per-render closures\n instance.notifyUpdate = instance._enqueueRun!;\n\n // Assign a token for this in-progress render and start a fresh pending-read set\n instance._currentRenderToken = nextRenderToken();\n instance._pendingReadSources = undefined;\n const domSnapshot = instance.target ? instance.target.innerHTML : '';\n\n const result = executeComponentSync(instance);\n if (result instanceof Promise) {\n // Async components are not supported. Components must be synchronous and\n // must not return a Promise from their render function.\n throw new Error(\n 'Async components are not supported. Components must be synchronous.'\n );\n } else {\n // Try runtime fast-lane synchronously; if it activates we do not enqueue\n // follow-up work and the commit happens atomically in this task.\n // (Runtime fast-lane has conservative preconditions.)\n const fastlaneBridge = (\n globalThis as {\n __ASKR_FASTLANE?: {\n tryRuntimeFastLaneSync?: (\n instance: unknown,\n result: unknown\n ) => boolean;\n };\n }\n ).__ASKR_FASTLANE;\n try {\n const used = fastlaneBridge?.tryRuntimeFastLaneSync?.(instance, result);\n if (used) {\n warnUnusedStateReads(instance);\n return;\n }\n } catch (err) {\n // If invariant check failed in dev, surface the error; otherwise fall back\n if (isDevelopmentEnvironment()) throw err;\n }\n\n // Fallback: enqueue the render/commit normally\n globalScheduler.enqueue(() => {\n // Handle placeholder-based updates: when a component initially returned null,\n // we created a comment placeholder. If it now has content, we need to create\n // a host element and replace the placeholder.\n if (!instance.target && instance._placeholder) {\n // Component previously returned null (has placeholder), check if now has content\n if (result === null || result === undefined) {\n // Still null - nothing to do, keep placeholder\n finalizeReadSubscriptions(instance);\n warnUnusedStateReads(instance);\n return;\n }\n\n // Has content now - need to create DOM and replace placeholder\n const placeholder = instance._placeholder;\n const parent = placeholder.parentNode;\n if (!parent) {\n // Placeholder was removed from DOM - can't render\n logger.warn(\n '[Askr] placeholder no longer in DOM, cannot render component'\n );\n return;\n }\n\n // Create a new host element for the content\n const host = document.createElement('div');\n const executionFrame: ContextFrame = {\n parent: instance.ownerFrame,\n values: null,\n };\n\n // Set up instance for normal updates\n const oldInstance = currentInstance;\n currentInstance = instance;\n try {\n withContext(executionFrame, () => {\n evaluate(result, host);\n });\n\n // Replace placeholder with host\n parent.replaceChild(host, placeholder);\n\n // Set up instance for future updates\n instance.target = host;\n instance._placeholder = undefined;\n (\n host as Element & { __ASKR_INSTANCE?: ComponentInstance }\n ).__ASKR_INSTANCE = instance;\n\n finalizeReadSubscriptions(instance);\n warnUnusedStateReads(instance);\n } finally {\n currentInstance = oldInstance;\n }\n return;\n }\n\n if (instance.target) {\n let oldChildren: Node[] = [];\n try {\n const wasFirstMount = !instance.mounted;\n // Ensure nested component executions during evaluation have access to\n // the current component instance. This allows nested components to\n // call `state()`, `resource()`, and other runtime helpers which\n // rely on `getCurrentComponentInstance()` being available.\n const oldInstance = currentInstance;\n currentInstance = instance;\n const executionFrame: ContextFrame = {\n parent: instance.ownerFrame,\n values: null,\n };\n // Capture snapshot of current children (by reference) so we can\n // restore them on render failure without losing event listeners or\n // instance attachments.\n oldChildren = Array.from(instance.target.childNodes);\n\n try {\n withContext(executionFrame, () => {\n evaluate(result, instance.target);\n });\n } catch (e) {\n // If evaluation failed, attempt to cleanup any partially-added nodes\n // and restore the old children to preserve listeners and instances.\n try {\n const newChildren = Array.from(instance.target.childNodes);\n for (const n of newChildren) {\n try {\n cleanupInstancesUnder(n);\n } catch (err) {\n logger.warn(\n '[Askr] error cleaning up failed commit children:',\n err\n );\n }\n }\n } catch (_err) {\n void _err;\n }\n\n // Restore original children by re-inserting the old node references\n // this preserves attached listeners and instance backrefs.\n try {\n incDevCounter('__DOM_REPLACE_COUNT');\n setDevValue(\n '__LAST_DOM_REPLACE_STACK_COMPONENT_RESTORE',\n new Error().stack\n );\n } catch (e) {\n void e;\n }\n instance.target.replaceChildren(...oldChildren);\n throw e;\n } finally {\n currentInstance = oldInstance;\n }\n\n // Commit succeeded — finalize recorded state reads so subscriptions reflect\n // the last *committed* render. This updates per-state reader maps\n // deterministically and synchronously with the commit.\n finalizeReadSubscriptions(instance);\n warnUnusedStateReads(instance);\n\n instance.mounted = true;\n // Execute mount operations after first mount (do NOT run these with\n // currentInstance set - they may perform state mutations/registrations)\n if (wasFirstMount && instance.mountOperations.length > 0) {\n executeMountOperations(instance);\n }\n } catch (renderError) {\n // Atomic rendering: rollback on render error. Attempt non-lossy restore of\n // original child node references to preserve listeners/instances.\n try {\n const currentChildren = Array.from(instance.target.childNodes);\n for (const n of currentChildren) {\n try {\n cleanupInstancesUnder(n);\n } catch (err) {\n logger.warn(\n '[Askr] error cleaning up partial children during rollback:',\n err\n );\n }\n }\n } catch (_err) {\n void _err;\n }\n\n try {\n incDevCounter('__DOM_REPLACE_COUNT');\n setDevValue(\n '__LAST_DOM_REPLACE_STACK_COMPONENT_ROLLBACK',\n new Error().stack\n );\n instance.target.replaceChildren(...oldChildren);\n } catch {\n // Fallback to innerHTML restore if replaceChildren fails for some reason.\n instance.target.innerHTML = domSnapshot;\n }\n\n throw renderError;\n }\n }\n });\n }\n}\n\n/**\n * Execute a component's render function synchronously.\n * Returns either a vnode/promise immediately (does NOT render).\n * Rendering happens separately through runComponent.\n */\nexport function renderComponentInline(\n instance: ComponentInstance\n): unknown | Promise<unknown> {\n // Ensure inline executions (rendered during parent's evaluate) still\n // receive a render token and have their state reads finalized so\n // subscriptions are correctly recorded. If this function is called\n // as part of a scheduled run, the token will already be set by\n // runComponent and we should not overwrite it.\n const hadToken = instance._currentRenderToken !== undefined;\n const prevToken = instance._currentRenderToken;\n const prevPendingReads = instance._pendingReadSources;\n if (!hadToken) {\n instance._currentRenderToken = nextRenderToken();\n instance._pendingReadSources = undefined;\n }\n\n try {\n const result = executeComponentSync(instance);\n // If we set the token for inline execution, finalize subscriptions now\n // because the component is effectively committed as part of the parent's\n // synchronous evaluation.\n if (!hadToken) {\n finalizeReadSubscriptions(instance);\n }\n return result;\n } finally {\n // Restore previous token/read states for nested inline render scenarios\n instance._currentRenderToken = prevToken;\n instance._pendingReadSources = prevPendingReads;\n }\n}\n\nexport function warnUnusedStateReads(instance: ComponentInstance): void {\n for (let i = 0; i < instance.stateValues.length; i++) {\n const state = instance.stateValues[i];\n const hasCommittedUsage =\n (state?._readers?.size ?? 0) > 0 ||\n ((state as { _derivedSubscribers?: Set<unknown> } | undefined)\n ?._derivedSubscribers?.size ?? 0) > 0;\n\n if (state && !state._hasBeenRead && !hasCommittedUsage) {\n try {\n const name = instance.fn?.name || '<anonymous>';\n warnInstanceOnce(\n instance,\n `unused-state:${i}`,\n `[askr] Unused state variable detected in ${name} at index ${i}. State should be read during render or removed.`\n );\n } catch {\n warnInstanceOnce(\n instance,\n `unused-state:${i}`,\n `[askr] Unused state variable detected. State should be read during render or removed.`\n );\n }\n }\n }\n}\n\nfunction executeComponentSync(\n instance: ComponentInstance\n): unknown | Promise<unknown> {\n resetRenderState(instance);\n incDevCounter('componentRuns');\n incDevCounter('componentReruns');\n\n currentInstance = instance;\n stateIndex = 0;\n\n try {\n // Track render time in dev mode\n const renderStartTime = isDevelopmentEnvironment() ? Date.now() : 0;\n\n // Create context object with abort signal\n const context = {\n get signal(): AbortSignal {\n return ensureAbortController(instance).signal;\n },\n };\n\n // Execute component within its owner frame (provider chain).\n // This ensures all context reads see the correct provider values.\n // We create a new execution frame whose parent is the ownerFrame. The\n // `values` map is lazily allocated to avoid per-render Map allocations\n // for components that do not use context.\n const executionFrame: ContextFrame = {\n parent: instance.ownerFrame,\n values: null,\n };\n const result = withContext(executionFrame, () =>\n instance.fn(instance.props, context)\n );\n\n // Check render time\n const renderTime = Date.now() - renderStartTime;\n if (renderTime > 5) {\n warnInstanceOnce(\n instance,\n 'slow-render',\n `[askr] Slow render detected: ${renderTime}ms. Consider optimizing component performance.`\n );\n }\n\n // Mark first render complete after successful execution\n // This enables hook order validation on subsequent renders\n if (!instance.firstRenderComplete) {\n instance.firstRenderComplete = true;\n }\n\n return result;\n } finally {\n // Synchronous path: we did not push a fresh frame, so nothing to pop here.\n currentInstance = null;\n }\n}\n\n/**\n * Public entry point: Execute component with full lifecycle (execute + render)\n * Handles both initial mount and re-execution. Always enqueues through scheduler.\n * Single entry point to avoid lifecycle divergence.\n */\nexport function executeComponent(instance: ComponentInstance): void {\n // Lazily recreate abort controller only when signal is actually requested.\n instance.abortController = null;\n\n // Setup notifyUpdate callback using prebound helper to avoid per-call closures\n instance.notifyUpdate = instance._enqueueRun!;\n\n // Enqueue the initial component run\n globalScheduler.enqueue(instance._pendingRunTask!);\n}\n\nexport function getCurrentInstance(): ComponentInstance | null {\n return currentInstance;\n}\n\n/**\n * Get the abort signal for the current component\n * Used to cancel async operations on unmount/navigation\n *\n * The signal is guaranteed to be aborted when:\n * - Component unmounts\n * - Navigation occurs (different route)\n * - Parent is destroyed\n *\n * IMPORTANT: getSignal() must be called during component render execution.\n * It captures the current component instance from context.\n *\n * @returns AbortSignal that will be aborted when component unmounts\n * @throws Error if called outside component execution\n *\n * @example\n * ```ts\n * // ✅ Correct: called during render, used in async operation\n * export async function UserPage({ id }: { id: string }) {\n * const signal = getSignal();\n * const user = await fetch(`/api/users/${id}`, { signal });\n * return <div>{user.name}</div>;\n * }\n *\n * // ✅ Correct: passed to event handler\n * export function Button() {\n * const signal = getSignal();\n * return {\n * type: 'button',\n * props: {\n * onClick: async () => {\n * const data = await fetch(url, { signal });\n * }\n * }\n * };\n * }\n *\n * // ❌ Wrong: called outside component context\n * const signal = getSignal(); // Error: not in component\n * ```\n */\nexport function getSignal(): AbortSignal {\n if (!currentInstance) {\n throw new Error(\n 'getSignal() can only be called during component render execution. ' +\n 'Ensure you are calling this from inside your component function.'\n );\n }\n return ensureAbortController(currentInstance).signal;\n}\n\n/**\n * Finalize read subscriptions for an instance after a successful commit.\n * - Update per-state readers map to point to this instance's last committed token\n * - Remove this instance from states it no longer reads\n * This is deterministic and runs synchronously with commit to ensure\n * subscribers are only notified when they actually read a state in their\n * last committed render.\n */\nexport function finalizeReadSubscriptions(instance: ComponentInstance): void {\n finalizeReadableSubscriptions(instance);\n}\n\nexport function getNextStateIndex(): number {\n return stateIndex++;\n}\n\nexport function claimHookIndex(\n instance: ComponentInstance,\n hookName: string\n): number {\n const index = getNextStateIndex();\n\n if (index < instance.stateIndexCheck) {\n throw new Error(\n `Hook index violation: ${hookName}() call at index ${index}, ` +\n `but previously saw index ${instance.stateIndexCheck}. ` +\n `This happens when render-scoped hooks are called conditionally (inside if/for/etc). ` +\n `Move all ${hookName}() calls to the top level of your component function, ` +\n `before any conditionals.`\n );\n }\n\n instance.stateIndexCheck = index;\n\n if (instance.firstRenderComplete) {\n if (instance.expectedStateIndices[index] !== index) {\n throw new Error(\n `Hook order violation: ${hookName}() called at index ${index}, ` +\n `but this index was not in the first render's sequence [${instance.expectedStateIndices.join(', ')}]. ` +\n `This usually means ${hookName}() is inside a conditional or loop. ` +\n `Move all render-scoped hooks to the top level of your component function.`\n );\n }\n } else {\n instance.expectedStateIndices.push(index);\n }\n\n return index;\n}\n\nexport function getCurrentStateIndex(): number {\n return stateIndex;\n}\n\nexport function resetStateIndex(): void {\n stateIndex = 0;\n}\n\nexport function setStateIndex(value: number): void {\n stateIndex = value;\n}\n\n/**\n * Mount a component instance.\n * This is just an alias to executeComponent() to maintain API compatibility.\n * All lifecycle logic is unified in executeComponent().\n */\nexport function mountComponent(instance: ComponentInstance): void {\n executeComponent(instance);\n}\n\n/**\n * Clean up component — abort pending operations\n * Called on unmount or route change\n */\nexport function cleanupComponent(instance: ComponentInstance): void {\n const ownedChildScopes = instance._ownedChildScopes;\n if (ownedChildScopes && ownedChildScopes.size > 0) {\n instance._ownedChildScopes = new Set();\n for (const scope of ownedChildScopes) {\n scope.dispose();\n }\n }\n\n // Execute cleanup functions (from mount effects)\n const cleanupErrors: unknown[] = [];\n const cleanupFns = instance.cleanupFns;\n for (const cleanup of cleanupFns) {\n try {\n cleanup();\n } catch (err) {\n if (instance.cleanupStrict) {\n cleanupErrors.push(err);\n } else {\n // Preserve previous behavior: log warnings in dev and continue\n if (isDevelopmentEnvironment()) {\n logger.warn('[Askr] cleanup function threw:', err);\n }\n }\n }\n }\n instance.cleanupFns = [];\n if (cleanupErrors.length > 0) {\n // If strict mode, surface all cleanup errors as an AggregateError after attempting all cleanups\n throw new AggregateError(\n cleanupErrors,\n `Cleanup failed for component ${instance.id}`\n );\n }\n\n // Remove deterministic state subscriptions for this instance\n cleanupReadableSubscriptions(instance);\n\n // Abort all pending operations\n if (instance.abortController && !instance.abortController.signal.aborted) {\n instance.abortController.abort();\n }\n instance.abortController = null;\n\n // Clear update callback to prevent dangling references and stale updates\n instance.notifyUpdate = null;\n instance._placeholder = undefined;\n\n // Mark instance as unmounted so external tracking (e.g., portal host lists)\n // can deterministically prune stale instances. Not marking this leads to\n // retained \"mounted\" flags across cleanup boundaries which breaks\n // owner selection in the portal fallback.\n instance.mounted = false;\n}\n\nexport function registerOwnedChildScope(\n instance: ComponentInstance,\n scope: OwnedChildScope\n): void {\n const scopes = (instance._ownedChildScopes ??= new Set());\n scopes.add(scope);\n}\n\nexport function unregisterOwnedChildScope(\n instance: ComponentInstance,\n scope: OwnedChildScope\n): void {\n instance._ownedChildScopes?.delete(scope);\n}\n\nfunction warnInstanceOnce(\n instance: ComponentInstance,\n key: string,\n message: string\n): void {\n if (isProductionEnvironment()) return;\n const warnings = (instance.devWarningsEmitted ??= new Set());\n if (warnings.has(key)) return;\n warnings.add(key);\n logger.warn(message);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"component.js","names":[],"sources":["../../src/runtime/component.ts"],"sourcesContent":["/**\n * Component instance lifecycle management\n * Internal only — users never see this\n */\n\nimport { type State } from './state';\nimport { globalScheduler } from './scheduler';\nimport type { Props } from '../common/props';\nimport type { ComponentFunction } from '../common/component';\nimport {\n // withContext is the sole primitive for context restoration\n withContext,\n type ContextFrame,\n} from './context';\nimport {\n type ReadableSource,\n finalizeReadableSubscriptions,\n cleanupReadableSubscriptions,\n} from './readable';\nimport {\n isDevelopmentEnvironment,\n isProductionEnvironment,\n} from '../common/env';\nimport { logger } from '../dev/logger';\nimport { incDevCounter, setDevValue } from './dev-namespace';\nimport { isPromiseLike } from '../common/promise';\n\nexport type { ComponentFunction } from '../common/component';\n\nexport interface ComponentInstance {\n id: string;\n fn: ComponentFunction;\n props: Props;\n target: Element | null;\n portalScope: object | null;\n mounted: boolean;\n abortController: AbortController | null; // Lazily created per-component abort lifecycle\n ssr?: boolean; // Set to true for SSR temporary instances\n // Opt-in strict cleanup mode: when true cleanup errors are aggregated and re-thrown\n cleanupStrict?: boolean;\n stateValues: State<unknown>[]; // Persistent state storage across renders\n evaluationGeneration: number; // Prevents stale async evaluation completions\n notifyUpdate: (() => void) | null; // Callback for state updates (persisted on instance)\n // Internal: prebound helpers to avoid per-update closures (allocation hot-path)\n _pendingFlushTask?: () => void; // Clears hasPendingUpdate and triggers notifyUpdate\n _pendingRunTask?: () => void; // Clears hasPendingUpdate and runs component\n _enqueueRun?: () => void; // Batches run requests and enqueues _pendingRunTask\n stateIndexCheck: number; // Track state indices to catch conditional calls\n expectedStateIndices: number[]; // Expected sequence of render-scoped hook indices (frozen after first render)\n firstRenderComplete: boolean; // Flag to detect transition from first to subsequent renders\n mountOperations: Array<\n () => void | (() => void) | PromiseLike<void | (() => void)>\n >; // Operations to run when component mounts\n cleanupFns: Array<() => void>; // Cleanup functions to run on unmount\n lifecycleGeneration: number; // Invalidates async mount-operation settlement after disposal\n hasPendingUpdate: boolean; // Flag to batch state updates (coalescing)\n ownerFrame: ContextFrame | null; // Provider chain for this component (set by Scope, never overwritten)\n isRoot?: boolean;\n\n // Render-tracking for precise subscriptions (internal)\n _currentRenderToken?: number; // Token for the in-progress render (set before render)\n lastRenderToken?: number; // Token of the last *committed* render\n _pendingReadSources?: Set<ReadableSource<unknown>>; // Readables read during the in-progress render\n _lastReadSources?: Set<ReadableSource<unknown>>; // Readables read during the last committed render\n devWarningsEmitted?: Set<string>; // Dev-only warning dedupe for this instance\n\n // Placeholder for null-returning components. When a component initially returns\n // null, we create a comment placeholder so updates can replace it with content.\n _placeholder?: Comment;\n _ownedChildScopes?: Set<{\n key: string | number;\n dispose(): void;\n }>;\n errorBoundaryState?: {\n error: unknown | null;\n resetKey: unknown;\n notified: boolean;\n };\n}\n\nexport function createComponentInstance(\n id: string,\n fn: ComponentFunction,\n props: Props,\n target: Element | null\n): ComponentInstance {\n const instance: ComponentInstance = {\n id,\n fn,\n props,\n target,\n portalScope: currentInstance?.portalScope ?? currentPortalScope ?? null,\n mounted: false,\n abortController: null,\n stateValues: [],\n evaluationGeneration: 0,\n notifyUpdate: null,\n // Prebound helpers (initialized below) to avoid per-update allocations\n _pendingFlushTask: undefined,\n _pendingRunTask: undefined,\n _enqueueRun: undefined,\n stateIndexCheck: -1,\n expectedStateIndices: [],\n firstRenderComplete: false,\n mountOperations: [],\n cleanupFns: [],\n lifecycleGeneration: 0,\n hasPendingUpdate: false,\n ownerFrame: null, // Will be set by renderer when vnode is marked\n ssr: false,\n cleanupStrict: false,\n isRoot: false,\n\n // Render-tracking (for precise state subscriptions)\n _currentRenderToken: undefined,\n lastRenderToken: 0,\n _pendingReadSources: undefined,\n _lastReadSources: undefined,\n devWarningsEmitted: undefined,\n };\n\n // Initialize prebound helper tasks once per instance to avoid allocations\n instance._pendingRunTask = () => {\n // Clear pending flag when the run task executes\n instance.hasPendingUpdate = false;\n if (instance.notifyUpdate === null) {\n return;\n }\n // Execute component run (will set up notifyUpdate before render)\n runComponent(instance);\n };\n\n instance._enqueueRun = () => {\n if (!instance.hasPendingUpdate) {\n instance.hasPendingUpdate = true;\n // Enqueue single run task (coalesces multiple writes)\n globalScheduler.enqueue(instance._pendingRunTask!);\n }\n };\n\n // Default state-driven updates enqueue the run task directly. Specialized\n // runtimes (for example `For` item instances) can still override this hook.\n instance._pendingFlushTask = instance._pendingRunTask;\n\n return instance;\n}\n\nlet currentInstance: ComponentInstance | null = null;\nlet currentPortalScope: object | null = null;\nlet stateIndex = 0;\n\ntype OwnedChildScope = {\n key: string | number;\n dispose(): void;\n};\n\nfunction ensureAbortController(instance: ComponentInstance): AbortController {\n let controller = instance.abortController;\n if (!controller || controller.signal.aborted) {\n controller = new AbortController();\n instance.abortController = controller;\n }\n return controller;\n}\n\n// Export for state.ts to access\nexport function getCurrentComponentInstance(): ComponentInstance | null {\n return currentInstance;\n}\n\n// Export for SSR to set temporary instance\nexport function setCurrentComponentInstance(\n instance: ComponentInstance | null\n): void {\n currentInstance = instance;\n currentPortalScope = instance?.portalScope ?? null;\n}\n\nexport function getCurrentPortalScope(): object | null {\n return currentInstance?.portalScope ?? currentPortalScope;\n}\n\n/**\n * Register a mount operation that will run after the component is mounted\n * Used by operations (task, on, timer, etc) to execute after render completes\n */\nimport { isBulkCommitActive } from './fastlane';\nimport { evaluate, cleanupInstancesUnder } from '../renderer';\n\nexport function registerMountOperation(\n operation: () => void | (() => void) | PromiseLike<void | (() => void)>\n): void {\n const instance = getCurrentComponentInstance();\n if (instance) {\n // If we're in bulk-commit fast lane, registering mount operations is a\n // violation of the fast-lane preconditions. Throw in dev, otherwise ignore\n // silently in production (we must avoid scheduling work during bulk commit).\n if (isBulkCommitActive()) {\n if (isDevelopmentEnvironment()) {\n throw new Error(\n 'registerMountOperation called during bulk commit fast-lane'\n );\n }\n return;\n }\n instance.mountOperations.push(operation);\n }\n}\n\n/**\n * Execute all mount operations for a component\n * These run after the component is rendered and mounted to the DOM\n */\nfunction executeMountOperations(instance: ComponentInstance): void {\n // Only execute mount operations for root app instance. Child component\n // operations are currently registered but should not be executed (per\n // contract tests). They remain registered for cleanup purposes.\n if (!instance.isRoot) return;\n\n const mountOperations = instance.mountOperations;\n if (mountOperations.length === 0) {\n return;\n }\n\n const lifecycleGeneration = instance.lifecycleGeneration;\n\n for (const operation of mountOperations) {\n const result = operation();\n if (isPromiseLike(result)) {\n Promise.resolve(result).then(\n (cleanup) => {\n if (typeof cleanup === 'function') {\n if (\n instance.lifecycleGeneration === lifecycleGeneration &&\n instance.mounted\n ) {\n instance.cleanupFns.push(cleanup);\n return;\n }\n\n try {\n cleanup();\n } catch (err) {\n logger.error('[Askr] async mount cleanup failed:', err);\n }\n }\n },\n (err) => {\n logger.error('[Askr] async mount operation failed:', err);\n }\n );\n } else if (typeof result === 'function') {\n instance.cleanupFns.push(result);\n }\n }\n // Clear the operations array so they don't run again on subsequent renders\n instance.mountOperations = [];\n}\n\nexport function mountInstanceInline(\n instance: ComponentInstance,\n target: Element | null\n): void {\n instance.target = target;\n // Record backref on host element so renderer can clean up when the\n // node is removed. Avoids leaks if the node is detached or replaced.\n try {\n if (target instanceof Element) {\n const host = target as Element & {\n __ASKR_INSTANCE?: ComponentInstance;\n __ASKR_INSTANCES?: ComponentInstance[];\n };\n const instances = host.__ASKR_INSTANCES ?? [];\n const nextInstances = instances.filter((entry) => entry !== instance);\n nextInstances.push(instance);\n host.__ASKR_INSTANCES = nextInstances;\n host.__ASKR_INSTANCE = nextInstances[0] ?? instance;\n }\n } catch (err) {\n void err;\n }\n\n // Ensure notifyUpdate is available for async resource completions that may\n // try to trigger re-render. This mirrors the setup in executeComponent().\n // Use prebound enqueue helper to avoid allocating a new closure\n instance.notifyUpdate = instance._enqueueRun!;\n\n const wasFirstMount = !instance.mounted;\n instance.mounted = true;\n if (wasFirstMount && instance.mountOperations.length > 0) {\n executeMountOperations(instance);\n }\n}\n\n/**\n * Run a component synchronously: execute function, handle result\n * This is the internal workhorse that manages async continuations and generation tracking.\n * Must always be called through the scheduler.\n *\n * ACTOR INVARIANT: This function is enqueued as a task, never called directly.\n */\nlet _globalRenderCounter = 0;\n\nfunction resetRenderState(instance: ComponentInstance): void {\n instance.stateIndexCheck = -1;\n\n for (const state of instance.stateValues) {\n if (state) {\n state._hasBeenRead = false;\n }\n }\n\n instance._pendingReadSources = undefined;\n}\n\nfunction nextRenderToken(): number {\n return ++_globalRenderCounter;\n}\n\nexport function renderScopedComponent<T>(\n instance: ComponentInstance,\n startStateIndex: number,\n render: () => T\n): T {\n const savedInstance = currentInstance;\n const savedPortalScope = currentPortalScope;\n const savedStateIndex = stateIndex;\n\n instance.notifyUpdate = instance._enqueueRun!;\n resetRenderState(instance);\n instance._currentRenderToken = nextRenderToken();\n\n currentInstance = instance;\n currentPortalScope = instance.portalScope ?? savedPortalScope;\n stateIndex = startStateIndex;\n\n let didComplete = false;\n\n try {\n const executionFrame: ContextFrame = {\n parent: instance.ownerFrame,\n values: null,\n };\n const result = withContext(executionFrame, render);\n didComplete = true;\n return result;\n } finally {\n if (!didComplete) {\n instance._pendingReadSources = undefined;\n instance._currentRenderToken = undefined;\n }\n currentInstance = savedInstance;\n currentPortalScope = savedPortalScope;\n stateIndex = savedStateIndex;\n }\n}\n\nfunction runComponent(instance: ComponentInstance): void {\n // CRITICAL: Ensure notifyUpdate is available for state.set() calls during this render.\n // This must be set before executeComponentSync() runs, not after.\n // Use prebound enqueue helper to avoid allocating per-render closures\n instance.notifyUpdate = instance._enqueueRun!;\n\n // Assign a token for this in-progress render and start a fresh pending-read set\n instance._currentRenderToken = nextRenderToken();\n instance._pendingReadSources = undefined;\n const domSnapshot = instance.target ? instance.target.innerHTML : '';\n\n const result = executeComponentSync(instance);\n if (isPromiseLike(result)) {\n // Async components are not supported. Components must be synchronous and\n // must not return a Promise from their render function.\n throw new Error(\n 'Async components are not supported. Components must be synchronous.'\n );\n } else {\n // Try runtime fast-lane synchronously; if it activates we do not enqueue\n // follow-up work and the commit happens atomically in this task.\n // (Runtime fast-lane has conservative preconditions.)\n const fastlaneBridge = (\n globalThis as {\n __ASKR_FASTLANE?: {\n tryRuntimeFastLaneSync?: (\n instance: unknown,\n result: unknown\n ) => boolean;\n };\n }\n ).__ASKR_FASTLANE;\n try {\n const used = fastlaneBridge?.tryRuntimeFastLaneSync?.(instance, result);\n if (used) {\n warnUnusedStateReads(instance);\n return;\n }\n } catch (err) {\n // If invariant check failed in dev, surface the error; otherwise fall back\n if (isDevelopmentEnvironment()) throw err;\n }\n\n // Fallback: enqueue the render/commit normally\n globalScheduler.enqueue(() => {\n // Handle placeholder-based updates: when a component initially returned null,\n // we created a comment placeholder. If it now has content, we need to create\n // a host element and replace the placeholder.\n if (!instance.target && instance._placeholder) {\n // Component previously returned null (has placeholder), check if now has content\n if (result === null || result === undefined) {\n // Still null - nothing to do, keep placeholder\n finalizeReadSubscriptions(instance);\n warnUnusedStateReads(instance);\n return;\n }\n\n // Has content now - need to create DOM and replace placeholder\n const placeholder = instance._placeholder;\n const parent = placeholder.parentNode;\n if (!parent) {\n // Placeholder was removed from DOM - can't render\n logger.warn(\n '[Askr] placeholder no longer in DOM, cannot render component'\n );\n return;\n }\n\n // Create a new host element for the content\n const host = document.createElement('div');\n const executionFrame: ContextFrame = {\n parent: instance.ownerFrame,\n values: null,\n };\n\n // Set up instance for normal updates\n const oldInstance = currentInstance;\n currentInstance = instance;\n try {\n withContext(executionFrame, () => {\n evaluate(result, host);\n });\n\n // Replace placeholder with host\n parent.replaceChild(host, placeholder);\n\n // Set up instance for future updates\n instance.target = host;\n instance._placeholder = undefined;\n (\n host as Element & { __ASKR_INSTANCE?: ComponentInstance }\n ).__ASKR_INSTANCE = instance;\n\n finalizeReadSubscriptions(instance);\n warnUnusedStateReads(instance);\n } finally {\n currentInstance = oldInstance;\n }\n return;\n }\n\n if (instance.target) {\n let oldChildren: Node[] = [];\n try {\n const wasFirstMount = !instance.mounted;\n // Ensure nested component executions during evaluation have access to\n // the current component instance. This allows nested components to\n // call `state()`, `resource()`, and other runtime helpers which\n // rely on `getCurrentComponentInstance()` being available.\n const oldInstance = currentInstance;\n currentInstance = instance;\n const executionFrame: ContextFrame = {\n parent: instance.ownerFrame,\n values: null,\n };\n // Capture snapshot of current children (by reference) so we can\n // restore them on render failure without losing event listeners or\n // instance attachments.\n oldChildren = Array.from(instance.target.childNodes);\n\n try {\n withContext(executionFrame, () => {\n evaluate(result, instance.target);\n });\n } catch (e) {\n // If evaluation failed, attempt to cleanup any partially-added nodes\n // and restore the old children to preserve listeners and instances.\n try {\n const newChildren = Array.from(instance.target.childNodes);\n for (const n of newChildren) {\n try {\n cleanupInstancesUnder(n);\n } catch (err) {\n logger.warn(\n '[Askr] error cleaning up failed commit children:',\n err\n );\n }\n }\n } catch (_err) {\n void _err;\n }\n\n // Restore original children by re-inserting the old node references\n // this preserves attached listeners and instance backrefs.\n try {\n incDevCounter('__DOM_REPLACE_COUNT');\n setDevValue(\n '__LAST_DOM_REPLACE_STACK_COMPONENT_RESTORE',\n new Error().stack\n );\n } catch (e) {\n void e;\n }\n instance.target.replaceChildren(...oldChildren);\n throw e;\n } finally {\n currentInstance = oldInstance;\n }\n\n // Commit succeeded — finalize recorded state reads so subscriptions reflect\n // the last *committed* render. This updates per-state reader maps\n // deterministically and synchronously with the commit.\n finalizeReadSubscriptions(instance);\n warnUnusedStateReads(instance);\n\n instance.mounted = true;\n // Execute mount operations after first mount (do NOT run these with\n // currentInstance set - they may perform state mutations/registrations)\n if (wasFirstMount && instance.mountOperations.length > 0) {\n executeMountOperations(instance);\n }\n } catch (renderError) {\n // Atomic rendering: rollback on render error. Attempt non-lossy restore of\n // original child node references to preserve listeners/instances.\n try {\n const currentChildren = Array.from(instance.target.childNodes);\n for (const n of currentChildren) {\n try {\n cleanupInstancesUnder(n);\n } catch (err) {\n logger.warn(\n '[Askr] error cleaning up partial children during rollback:',\n err\n );\n }\n }\n } catch (_err) {\n void _err;\n }\n\n try {\n incDevCounter('__DOM_REPLACE_COUNT');\n setDevValue(\n '__LAST_DOM_REPLACE_STACK_COMPONENT_ROLLBACK',\n new Error().stack\n );\n instance.target.replaceChildren(...oldChildren);\n } catch {\n // Fallback to innerHTML restore if replaceChildren fails for some reason.\n instance.target.innerHTML = domSnapshot;\n }\n\n throw renderError;\n }\n }\n });\n }\n}\n\n/**\n * Execute a component's render function synchronously.\n * Returns either a vnode/promise immediately (does NOT render).\n * Rendering happens separately through runComponent.\n */\nexport function renderComponentInline(\n instance: ComponentInstance\n): unknown | Promise<unknown> {\n // Ensure inline executions (rendered during parent's evaluate) still\n // receive a render token and have their state reads finalized so\n // subscriptions are correctly recorded. If this function is called\n // as part of a scheduled run, the token will already be set by\n // runComponent and we should not overwrite it.\n const hadToken = instance._currentRenderToken !== undefined;\n const prevToken = instance._currentRenderToken;\n const prevPendingReads = instance._pendingReadSources;\n const savedInstance = currentInstance;\n const savedPortalScope = currentPortalScope;\n if (!hadToken) {\n instance._currentRenderToken = nextRenderToken();\n instance._pendingReadSources = undefined;\n }\n\n try {\n const result = executeComponentSync(instance);\n // If we set the token for inline execution, finalize subscriptions now\n // because the component is effectively committed as part of the parent's\n // synchronous evaluation.\n if (!hadToken) {\n finalizeReadSubscriptions(instance);\n }\n return result;\n } finally {\n // Restore previous token/read states for nested inline render scenarios\n instance._currentRenderToken = prevToken;\n instance._pendingReadSources = prevPendingReads;\n currentInstance = savedInstance;\n currentPortalScope = savedPortalScope;\n }\n}\n\nexport function warnUnusedStateReads(instance: ComponentInstance): void {\n for (let i = 0; i < instance.stateValues.length; i++) {\n const state = instance.stateValues[i];\n const hasCommittedUsage =\n (state?._readers?.size ?? 0) > 0 ||\n ((state as { _derivedSubscribers?: Set<unknown> } | undefined)\n ?._derivedSubscribers?.size ?? 0) > 0;\n\n if (\n state &&\n !state._hasBeenRead &&\n !state._hasEverBeenRead &&\n !hasCommittedUsage\n ) {\n try {\n const name = instance.fn?.name || '<anonymous>';\n warnInstanceOnce(\n instance,\n `unused-state:${i}`,\n `[askr] Unused state variable detected in ${name} at index ${i}. State should be read during render or removed.`\n );\n } catch {\n warnInstanceOnce(\n instance,\n `unused-state:${i}`,\n `[askr] Unused state variable detected. State should be read during render or removed.`\n );\n }\n }\n }\n}\n\nfunction executeComponentSync(\n instance: ComponentInstance\n): unknown | Promise<unknown> {\n resetRenderState(instance);\n incDevCounter('componentRuns');\n incDevCounter('componentReruns');\n\n const savedPortalScope = currentPortalScope;\n currentInstance = instance;\n currentPortalScope = instance.portalScope ?? savedPortalScope;\n stateIndex = 0;\n\n try {\n // Track render time in dev mode\n const renderStartTime = isDevelopmentEnvironment() ? Date.now() : 0;\n\n // Create context object with abort signal\n const context = {\n get signal(): AbortSignal {\n return ensureAbortController(instance).signal;\n },\n };\n\n // Execute component within its owner frame (provider chain).\n // This ensures all context reads see the correct provider values.\n // We create a new execution frame whose parent is the ownerFrame. The\n // `values` map is lazily allocated to avoid per-render Map allocations\n // for components that do not use context.\n const executionFrame: ContextFrame = {\n parent: instance.ownerFrame,\n values: null,\n };\n const result = withContext(executionFrame, () =>\n instance.fn(instance.props, context)\n );\n\n // Check render time\n const renderTime = Date.now() - renderStartTime;\n if (renderTime > 5) {\n warnInstanceOnce(\n instance,\n 'slow-render',\n `[askr] Slow render detected: ${renderTime}ms. Consider optimizing component performance.`\n );\n }\n\n // Mark first render complete after successful execution\n // This enables hook order validation on subsequent renders\n if (!instance.firstRenderComplete) {\n instance.firstRenderComplete = true;\n }\n\n return result;\n } finally {\n // Synchronous path: we did not push a fresh frame, so nothing to pop here.\n currentInstance = null;\n currentPortalScope = savedPortalScope;\n }\n}\n\n/**\n * Public entry point: Execute component with full lifecycle (execute + render)\n * Handles both initial mount and re-execution. Always enqueues through scheduler.\n * Single entry point to avoid lifecycle divergence.\n */\nexport function executeComponent(instance: ComponentInstance): void {\n // Lazily recreate abort controller only when signal is actually requested.\n instance.abortController = null;\n\n // Setup notifyUpdate callback using prebound helper to avoid per-call closures\n instance.notifyUpdate = instance._enqueueRun!;\n\n // Enqueue the initial component run\n globalScheduler.enqueue(instance._pendingRunTask!);\n}\n\nexport function getCurrentInstance(): ComponentInstance | null {\n return currentInstance;\n}\n\n/**\n * Get the abort signal for the current component\n * Used to cancel async operations on unmount/navigation\n *\n * The signal is guaranteed to be aborted when:\n * - Component unmounts\n * - Navigation occurs (different route)\n * - Parent is destroyed\n *\n * IMPORTANT: getSignal() must be called during component render execution.\n * It captures the current component instance from context.\n *\n * @returns AbortSignal that will be aborted when component unmounts\n * @throws Error if called outside component execution\n *\n * @example\n * ```ts\n * // ✅ Correct: called during render, used in async operation\n * export async function UserPage({ id }: { id: string }) {\n * const signal = getSignal();\n * const user = await fetch(`/api/users/${id}`, { signal });\n * return <div>{user.name}</div>;\n * }\n *\n * // ✅ Correct: passed to event handler\n * export function Button() {\n * const signal = getSignal();\n * return {\n * type: 'button',\n * props: {\n * onClick: async () => {\n * const data = await fetch(url, { signal });\n * }\n * }\n * };\n * }\n *\n * // ❌ Wrong: called outside component context\n * const signal = getSignal(); // Error: not in component\n * ```\n */\nexport function getSignal(): AbortSignal {\n if (!currentInstance) {\n throw new Error(\n 'getSignal() can only be called during component render execution. ' +\n 'Ensure you are calling this from inside your component function.'\n );\n }\n return ensureAbortController(currentInstance).signal;\n}\n\n/**\n * Finalize read subscriptions for an instance after a successful commit.\n * - Update per-state readers map to point to this instance's last committed token\n * - Remove this instance from states it no longer reads\n * This is deterministic and runs synchronously with commit to ensure\n * subscribers are only notified when they actually read a state in their\n * last committed render.\n */\nexport function finalizeReadSubscriptions(instance: ComponentInstance): void {\n finalizeReadableSubscriptions(instance);\n}\n\nexport function getNextStateIndex(): number {\n return stateIndex++;\n}\n\nexport function claimHookIndex(\n instance: ComponentInstance,\n hookName: string\n): number {\n const index = getNextStateIndex();\n\n if (index < instance.stateIndexCheck) {\n throw new Error(\n `Hook index violation: ${hookName}() call at index ${index}, ` +\n `but previously saw index ${instance.stateIndexCheck}. ` +\n `This happens when render-scoped hooks are called conditionally (inside if/for/etc). ` +\n `Move all ${hookName}() calls to the top level of your component function, ` +\n `before any conditionals.`\n );\n }\n\n instance.stateIndexCheck = index;\n\n if (instance.firstRenderComplete) {\n if (instance.expectedStateIndices[index] !== index) {\n throw new Error(\n `Hook order violation: ${hookName}() called at index ${index}, ` +\n `but this index was not in the first render's sequence [${instance.expectedStateIndices.join(', ')}]. ` +\n `This usually means ${hookName}() is inside a conditional or loop. ` +\n `Move all render-scoped hooks to the top level of your component function.`\n );\n }\n } else {\n instance.expectedStateIndices.push(index);\n }\n\n return index;\n}\n\nexport function getCurrentStateIndex(): number {\n return stateIndex;\n}\n\nexport function resetStateIndex(): void {\n stateIndex = 0;\n}\n\nexport function setStateIndex(value: number): void {\n stateIndex = value;\n}\n\n/**\n * Mount a component instance.\n * This is just an alias to executeComponent() to maintain API compatibility.\n * All lifecycle logic is unified in executeComponent().\n */\nexport function mountComponent(instance: ComponentInstance): void {\n executeComponent(instance);\n}\n\n/**\n * Clean up component — abort pending operations\n * Called on unmount or route change\n */\nexport function cleanupComponent(instance: ComponentInstance): void {\n const savedInstance = currentInstance;\n const savedPortalScope = currentPortalScope;\n currentInstance = null;\n currentPortalScope = null;\n\n try {\n const cleanupErrors: unknown[] = [];\n const recordCleanupError = (message: string, err: unknown): void => {\n if (instance.cleanupStrict) {\n cleanupErrors.push(err);\n } else if (isDevelopmentEnvironment()) {\n logger.warn(message, err);\n }\n };\n\n const ownedChildScopes = instance._ownedChildScopes;\n if (ownedChildScopes && ownedChildScopes.size > 0) {\n instance._ownedChildScopes = new Set();\n for (const scope of ownedChildScopes) {\n try {\n scope.dispose();\n } catch (err) {\n recordCleanupError('[Askr] child scope cleanup threw:', err);\n }\n }\n }\n\n // Execute cleanup functions (from mount effects)\n const cleanupFns = instance.cleanupFns;\n instance.cleanupFns = [];\n for (const cleanup of cleanupFns) {\n try {\n cleanup();\n } catch (err) {\n recordCleanupError('[Askr] cleanup function threw:', err);\n }\n }\n\n // Remove deterministic state subscriptions for this instance\n try {\n cleanupReadableSubscriptions(instance);\n } catch (err) {\n recordCleanupError('[Askr] readable subscription cleanup threw:', err);\n }\n\n // Abort all pending operations\n try {\n if (\n instance.abortController &&\n !instance.abortController.signal.aborted\n ) {\n instance.abortController.abort();\n }\n } catch (err) {\n recordCleanupError('[Askr] abort controller cleanup threw:', err);\n }\n instance.abortController = null;\n\n // Clear update callback to prevent dangling references and stale updates\n instance.lifecycleGeneration++;\n instance.evaluationGeneration++;\n instance.mountOperations = [];\n instance.hasPendingUpdate = false;\n instance.notifyUpdate = null;\n instance._placeholder = undefined;\n\n // Mark instance as unmounted so external tracking (e.g., portal host lists)\n // can deterministically prune stale instances. Not marking this leads to\n // retained \"mounted\" flags across cleanup boundaries which breaks\n // owner selection in the portal fallback.\n instance.mounted = false;\n\n if (cleanupErrors.length > 0) {\n throw new AggregateError(\n cleanupErrors,\n `Cleanup failed for component ${instance.id}`\n );\n }\n } finally {\n currentInstance = savedInstance;\n currentPortalScope = savedPortalScope;\n }\n}\n\nexport function registerOwnedChildScope(\n instance: ComponentInstance,\n scope: OwnedChildScope\n): void {\n const scopes = (instance._ownedChildScopes ??= new Set());\n scopes.add(scope);\n}\n\nexport function unregisterOwnedChildScope(\n instance: ComponentInstance,\n scope: OwnedChildScope\n): void {\n instance._ownedChildScopes?.delete(scope);\n}\n\nfunction warnInstanceOnce(\n instance: ComponentInstance,\n key: string,\n message: string\n): void {\n if (isProductionEnvironment()) return;\n const warnings = (instance.devWarningsEmitted ??= new Set());\n if (warnings.has(key)) return;\n warnings.add(key);\n logger.warn(message);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAgFA,SAAgB,wBACd,IACA,IACA,OACA,QACmB;CACnB,MAAM,WAA8B;EAClC;EACA;EACA;EACA;EACA,aAAa,iBAAiB,eAAe,sBAAsB;EACnE,SAAS;EACT,iBAAiB;EACjB,aAAa,CAAC;EACd,sBAAsB;EACtB,cAAc;EAEd,mBAAmB;EACnB,iBAAiB;EACjB,aAAa;EACb,iBAAiB;EACjB,sBAAsB,CAAC;EACvB,qBAAqB;EACrB,iBAAiB,CAAC;EAClB,YAAY,CAAC;EACb,qBAAqB;EACrB,kBAAkB;EAClB,YAAY;EACZ,KAAK;EACL,eAAe;EACf,QAAQ;EAGR,qBAAqB;EACrB,iBAAiB;EACjB,qBAAqB;EACrB,kBAAkB;EAClB,oBAAoB;CACtB;CAGA,SAAS,wBAAwB;EAE/B,SAAS,mBAAmB;EAC5B,IAAI,SAAS,iBAAiB,MAC5B;EAGF,aAAa,QAAQ;CACvB;CAEA,SAAS,oBAAoB;EAC3B,IAAI,CAAC,SAAS,kBAAkB;GAC9B,SAAS,mBAAmB;GAE5B,gBAAgB,QAAQ,SAAS,eAAgB;EACnD;CACF;CAIA,SAAS,oBAAoB,SAAS;CAEtC,OAAO;AACT;AAEA,IAAI,kBAA4C;AAChD,IAAI,qBAAoC;AACxC,IAAI,aAAa;AAOjB,SAAS,sBAAsB,UAA8C;CAC3E,IAAI,aAAa,SAAS;CAC1B,IAAI,CAAC,cAAc,WAAW,OAAO,SAAS;EAC5C,aAAa,IAAI,gBAAgB;EACjC,SAAS,kBAAkB;CAC7B;CACA,OAAO;AACT;AAGA,SAAgB,8BAAwD;CACtE,OAAO;AACT;AAGA,SAAgB,4BACd,UACM;CACN,kBAAkB;CAClB,qBAAqB,UAAU,eAAe;AAChD;AAEA,SAAgB,wBAAuC;CACrD,OAAO,iBAAiB,eAAe;AACzC;AASA,SAAgB,uBACd,WACM;CACN,MAAM,WAAW,4BAA4B;CAC7C,IAAI,UAAU;EAIZ,IAAI,mBAAmB,GAAG;GACxB,IAAI,yBAAyB,GAC3B,MAAM,IAAI,MACR,4DACF;GAEF;EACF;EACA,SAAS,gBAAgB,KAAK,SAAS;CACzC;AACF;;;;;AAMA,SAAS,uBAAuB,UAAmC;CAIjE,IAAI,CAAC,SAAS,QAAQ;CAEtB,MAAM,kBAAkB,SAAS;CACjC,IAAI,gBAAgB,WAAW,GAC7B;CAGF,MAAM,sBAAsB,SAAS;CAErC,KAAK,MAAM,aAAa,iBAAiB;EACvC,MAAM,SAAS,UAAU;EACzB,IAAI,cAAc,MAAM,GACtB,QAAQ,QAAQ,MAAM,EAAE,MACrB,YAAY;GACX,IAAI,OAAO,YAAY,YAAY;IACjC,IACE,SAAS,wBAAwB,uBACjC,SAAS,SACT;KACA,SAAS,WAAW,KAAK,OAAO;KAChC;IACF;IAEA,IAAI;KACF,QAAQ;IACV,SAAS,KAAK;KACZ,OAAO,MAAM,sCAAsC,GAAG;IACxD;GACF;EACF,IACC,QAAQ;GACP,OAAO,MAAM,wCAAwC,GAAG;EAC1D,CACF;OACK,IAAI,OAAO,WAAW,YAC3B,SAAS,WAAW,KAAK,MAAM;CAEnC;CAEA,SAAS,kBAAkB,CAAC;AAC9B;AAEA,SAAgB,oBACd,UACA,QACM;CACN,SAAS,SAAS;CAGlB,IAAI;EACF,IAAI,kBAAkB,SAAS;GAC7B,MAAM,OAAO;GAKb,MAAM,iBADY,KAAK,oBAAoB,CAAC,GACZ,QAAQ,UAAU,UAAU,QAAQ;GACpE,cAAc,KAAK,QAAQ;GAC3B,KAAK,mBAAmB;GACxB,KAAK,kBAAkB,cAAc,MAAM;EAC7C;CACF,SAAS,KAAK,CAEd;CAKA,SAAS,eAAe,SAAS;CAEjC,MAAM,gBAAgB,CAAC,SAAS;CAChC,SAAS,UAAU;CACnB,IAAI,iBAAiB,SAAS,gBAAgB,SAAS,GACrD,uBAAuB,QAAQ;AAEnC;;;;;;;;AASA,IAAI,uBAAuB;AAE3B,SAAS,iBAAiB,UAAmC;CAC3D,SAAS,kBAAkB;CAE3B,KAAK,MAAM,SAAS,SAAS,aAC3B,IAAI,OACF,MAAM,eAAe;CAIzB,SAAS,sBAAsB;AACjC;AAEA,SAAS,kBAA0B;CACjC,OAAO,EAAE;AACX;AAEA,SAAgB,sBACd,UACA,iBACA,QACG;CACH,MAAM,gBAAgB;CACtB,MAAM,mBAAmB;CACzB,MAAM,kBAAkB;CAExB,SAAS,eAAe,SAAS;CACjC,iBAAiB,QAAQ;CACzB,SAAS,sBAAsB,gBAAgB;CAE/C,kBAAkB;CAClB,qBAAqB,SAAS,eAAe;CAC7C,aAAa;CAEb,IAAI,cAAc;CAElB,IAAI;EAKF,MAAM,SAAS,YAAY;GAHzB,QAAQ,SAAS;GACjB,QAAQ;EAEiB,GAAgB,MAAM;EACjD,cAAc;EACd,OAAO;CACT,UAAU;EACR,IAAI,CAAC,aAAa;GAChB,SAAS,sBAAsB;GAC/B,SAAS,sBAAsB;EACjC;EACA,kBAAkB;EAClB,qBAAqB;EACrB,aAAa;CACf;AACF;AAEA,SAAS,aAAa,UAAmC;CAIvD,SAAS,eAAe,SAAS;CAGjC,SAAS,sBAAsB,gBAAgB;CAC/C,SAAS,sBAAsB;CAC/B,MAAM,cAAc,SAAS,SAAS,SAAS,OAAO,YAAY;CAElE,MAAM,SAAS,qBAAqB,QAAQ;CAC5C,IAAI,cAAc,MAAM,GAGtB,MAAM,IAAI,MACR,qEACF;MACK;EAIL,MAAM,iBACJ,WAQA;EACF,IAAI;GAEF,IADa,gBAAgB,yBAAyB,UAAU,MAAM,GAC5D;IACR,qBAAqB,QAAQ;IAC7B;GACF;EACF,SAAS,KAAK;GAEZ,IAAI,yBAAyB,GAAG,MAAM;EACxC;EAGA,gBAAgB,cAAc;GAI5B,IAAI,CAAC,SAAS,UAAU,SAAS,cAAc;IAE7C,IAAI,WAAW,QAAQ,WAAW,QAAW;KAE3C,0BAA0B,QAAQ;KAClC,qBAAqB,QAAQ;KAC7B;IACF;IAGA,MAAM,cAAc,SAAS;IAC7B,MAAM,SAAS,YAAY;IAC3B,IAAI,CAAC,QAAQ;KAEX,OAAO,KACL,8DACF;KACA;IACF;IAGA,MAAM,OAAO,SAAS,cAAc,KAAK;IACzC,MAAM,iBAA+B;KACnC,QAAQ,SAAS;KACjB,QAAQ;IACV;IAGA,MAAM,cAAc;IACpB,kBAAkB;IAClB,IAAI;KACF,YAAY,sBAAsB;MAChC,SAAS,QAAQ,IAAI;KACvB,CAAC;KAGD,OAAO,aAAa,MAAM,WAAW;KAGrC,SAAS,SAAS;KAClB,SAAS,eAAe;KACxB,KAEE,kBAAkB;KAEpB,0BAA0B,QAAQ;KAClC,qBAAqB,QAAQ;IAC/B,UAAU;KACR,kBAAkB;IACpB;IACA;GACF;GAEA,IAAI,SAAS,QAAQ;IACnB,IAAI,cAAsB,CAAC;IAC3B,IAAI;KACF,MAAM,gBAAgB,CAAC,SAAS;KAKhC,MAAM,cAAc;KACpB,kBAAkB;KAClB,MAAM,iBAA+B;MACnC,QAAQ,SAAS;MACjB,QAAQ;KACV;KAIA,cAAc,MAAM,KAAK,SAAS,OAAO,UAAU;KAEnD,IAAI;MACF,YAAY,sBAAsB;OAChC,SAAS,QAAQ,SAAS,MAAM;MAClC,CAAC;KACH,SAAS,GAAG;MAGV,IAAI;OACF,MAAM,cAAc,MAAM,KAAK,SAAS,OAAO,UAAU;OACzD,KAAK,MAAM,KAAK,aACd,IAAI;QACF,sBAAsB,CAAC;OACzB,SAAS,KAAK;QACZ,OAAO,KACL,oDACA,GACF;OACF;MAEJ,SAAS,MAAM,CAEf;MAIA,IAAI;OACF,cAAc,qBAAqB;OACnC,YACE,+DACA,IAAI,MAAM,GAAE,KACd;MACF,SAAS,GAAG,CAEZ;MACA,SAAS,OAAO,gBAAgB,GAAG,WAAW;MAC9C,MAAM;KACR,UAAU;MACR,kBAAkB;KACpB;KAKA,0BAA0B,QAAQ;KAClC,qBAAqB,QAAQ;KAE7B,SAAS,UAAU;KAGnB,IAAI,iBAAiB,SAAS,gBAAgB,SAAS,GACrD,uBAAuB,QAAQ;IAEnC,SAAS,aAAa;KAGpB,IAAI;MACF,MAAM,kBAAkB,MAAM,KAAK,SAAS,OAAO,UAAU;MAC7D,KAAK,MAAM,KAAK,iBACd,IAAI;OACF,sBAAsB,CAAC;MACzB,SAAS,KAAK;OACZ,OAAO,KACL,8DACA,GACF;MACF;KAEJ,SAAS,MAAM,CAEf;KAEA,IAAI;MACF,cAAc,qBAAqB;MACnC,YACE,gEACA,IAAI,MAAM,GAAE,KACd;MACA,SAAS,OAAO,gBAAgB,GAAG,WAAW;KAChD,QAAQ;MAEN,SAAS,OAAO,YAAY;KAC9B;KAEA,MAAM;IACR;GACF;EACF,CAAC;CACH;AACF;;;;;;AAOA,SAAgB,sBACd,UAC4B;CAM5B,MAAM,WAAW,SAAS,wBAAwB;CAClD,MAAM,YAAY,SAAS;CAC3B,MAAM,mBAAmB,SAAS;CAClC,MAAM,gBAAgB;CACtB,MAAM,mBAAmB;CACzB,IAAI,CAAC,UAAU;EACb,SAAS,sBAAsB,gBAAgB;EAC/C,SAAS,sBAAsB;CACjC;CAEA,IAAI;EACF,MAAM,SAAS,qBAAqB,QAAQ;EAI5C,IAAI,CAAC,UACH,0BAA0B,QAAQ;EAEpC,OAAO;CACT,UAAU;EAER,SAAS,sBAAsB;EAC/B,SAAS,sBAAsB;EAC/B,kBAAkB;EAClB,qBAAqB;CACvB;AACF;AAEA,SAAgB,qBAAqB,UAAmC;CACtE,KAAK,IAAI,IAAI,GAAG,IAAI,SAAS,YAAY,QAAQ,KAAK;EACpD,MAAM,QAAQ,SAAS,YAAY;EACnC,MAAM,qBACH,OAAO,UAAU,QAAQ,KAAK,MAC7B,OACE,qBAAqB,QAAQ,KAAK;EAExC,IACE,SACA,CAAC,MAAM,gBACP,CAAC,MAAM,oBACP,CAAC,mBAED,IAAI;GACF,MAAM,OAAO,SAAS,IAAI,QAAQ;GAClC,iBACE,UACA,gBAAgB,KAChB,4CAA4C,KAAK,YAAY,EAAE,iDACjE;EACF,QAAQ;GACN,iBACE,UACA,gBAAgB,KAChB,uFACF;EACF;CAEJ;AACF;AAEA,SAAS,qBACP,UAC4B;CAC5B,iBAAiB,QAAQ;CACzB,cAAc,eAAe;CAC7B,cAAc,iBAAiB;CAE/B,MAAM,mBAAmB;CACzB,kBAAkB;CAClB,qBAAqB,SAAS,eAAe;CAC7C,aAAa;CAEb,IAAI;EAEF,MAAM,kBAAkB,yBAAyB,IAAI,KAAK,IAAI,IAAI;EAGlE,MAAM,UAAU,EACd,IAAI,SAAsB;GACxB,OAAO,sBAAsB,QAAQ,EAAE;EACzC,EACF;EAWA,MAAM,SAAS,YAAY;GAHzB,QAAQ,SAAS;GACjB,QAAQ;EAEiB,SACzB,SAAS,GAAG,SAAS,OAAO,OAAO,CACrC;EAGA,MAAM,aAAa,KAAK,IAAI,IAAI;EAChC,IAAI,aAAa,GACf,iBACE,UACA,eACA,gCAAgC,WAAW,+CAC7C;EAKF,IAAI,CAAC,SAAS,qBACZ,SAAS,sBAAsB;EAGjC,OAAO;CACT,UAAU;EAER,kBAAkB;EAClB,qBAAqB;CACvB;AACF;;;;;;AAOA,SAAgB,iBAAiB,UAAmC;CAElE,SAAS,kBAAkB;CAG3B,SAAS,eAAe,SAAS;CAGjC,gBAAgB,QAAQ,SAAS,eAAgB;AACnD;AAEA,SAAgB,qBAA+C;CAC7D,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,SAAgB,YAAyB;CACvC,IAAI,CAAC,iBACH,MAAM,IAAI,MACR,oIAEF;CAEF,OAAO,sBAAsB,eAAe,EAAE;AAChD;;;;;;;;;AAUA,SAAgB,0BAA0B,UAAmC;CAC3E,8BAA8B,QAAQ;AACxC;AAEA,SAAgB,oBAA4B;CAC1C,OAAO;AACT;AAEA,SAAgB,eACd,UACA,UACQ;CACR,MAAM,QAAQ,kBAAkB;CAEhC,IAAI,QAAQ,SAAS,iBACnB,MAAM,IAAI,MACR,yBAAyB,SAAS,mBAAmB,MAAM,6BAC7B,SAAS,gBAAgB,iGAEzC,SAAS,+EAEzB;CAGF,SAAS,kBAAkB;CAE3B,IAAI,SAAS,qBACX;MAAI,SAAS,qBAAqB,WAAW,OAC3C,MAAM,IAAI,MACR,yBAAyB,SAAS,qBAAqB,MAAM,2DACD,SAAS,qBAAqB,KAAK,IAAI,EAAE,wBAC7E,SAAS,8GAEnC;CACF,OAEA,SAAS,qBAAqB,KAAK,KAAK;CAG1C,OAAO;AACT;AAEA,SAAgB,uBAA+B;CAC7C,OAAO;AACT;;;;;;AAeA,SAAgB,eAAe,UAAmC;CAChE,iBAAiB,QAAQ;AAC3B;;;;;AAMA,SAAgB,iBAAiB,UAAmC;CAClE,MAAM,gBAAgB;CACtB,MAAM,mBAAmB;CACzB,kBAAkB;CAClB,qBAAqB;CAErB,IAAI;EACF,MAAM,gBAA2B,CAAC;EAClC,MAAM,sBAAsB,SAAiB,QAAuB;GAClE,IAAI,SAAS,eACX,cAAc,KAAK,GAAG;QACjB,IAAI,yBAAyB,GAClC,OAAO,KAAK,SAAS,GAAG;EAE5B;EAEA,MAAM,mBAAmB,SAAS;EAClC,IAAI,oBAAoB,iBAAiB,OAAO,GAAG;GACjD,SAAS,oCAAoB,IAAI,IAAI;GACrC,KAAK,MAAM,SAAS,kBAClB,IAAI;IACF,MAAM,QAAQ;GAChB,SAAS,KAAK;IACZ,mBAAmB,qCAAqC,GAAG;GAC7D;EAEJ;EAGA,MAAM,aAAa,SAAS;EAC5B,SAAS,aAAa,CAAC;EACvB,KAAK,MAAM,WAAW,YACpB,IAAI;GACF,QAAQ;EACV,SAAS,KAAK;GACZ,mBAAmB,kCAAkC,GAAG;EAC1D;EAIF,IAAI;GACF,6BAA6B,QAAQ;EACvC,SAAS,KAAK;GACZ,mBAAmB,+CAA+C,GAAG;EACvE;EAGA,IAAI;GACF,IACE,SAAS,mBACT,CAAC,SAAS,gBAAgB,OAAO,SAEjC,SAAS,gBAAgB,MAAM;EAEnC,SAAS,KAAK;GACZ,mBAAmB,0CAA0C,GAAG;EAClE;EACA,SAAS,kBAAkB;EAG3B,SAAS;EACT,SAAS;EACT,SAAS,kBAAkB,CAAC;EAC5B,SAAS,mBAAmB;EAC5B,SAAS,eAAe;EACxB,SAAS,eAAe;EAMxB,SAAS,UAAU;EAEnB,IAAI,cAAc,SAAS,GACzB,MAAM,IAAI,eACR,eACA,gCAAgC,SAAS,IAC3C;CAEJ,UAAU;EACR,kBAAkB;EAClB,qBAAqB;CACvB;AACF;AAEA,SAAgB,wBACd,UACA,OACM;CAEN,CADgB,SAAS,sCAAsB,IAAI,IAAI,GAChD,IAAI,KAAK;AAClB;AAEA,SAAgB,0BACd,UACA,OACM;CACN,SAAS,mBAAmB,OAAO,KAAK;AAC1C;AAEA,SAAS,iBACP,UACA,KACA,SACM;CACN,IAAI,wBAAwB,GAAG;CAC/B,MAAM,WAAY,SAAS,uCAAuB,IAAI,IAAI;CAC1D,IAAI,SAAS,IAAI,GAAG,GAAG;CACvB,SAAS,IAAI,GAAG;CAChB,OAAO,KAAK,OAAO;AACrB"}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { JSXElement } from "../common/jsx.js";
|
|
2
|
+
import { RenderableChild } from "../common/vnode.js";
|
|
2
3
|
|
|
3
4
|
//#region src/runtime/context.d.ts
|
|
4
5
|
type ContextKey = symbol;
|
|
6
|
+
type Renderable = RenderableChild;
|
|
7
|
+
type ContextScopeChildren = Renderable | (() => Renderable);
|
|
5
8
|
interface Context<T> {
|
|
6
9
|
readonly key: ContextKey;
|
|
7
10
|
readonly defaultValue: T;
|
|
8
11
|
readonly Scope: (props: {
|
|
9
|
-
value:
|
|
10
|
-
children?:
|
|
12
|
+
value: T;
|
|
13
|
+
children?: ContextScopeChildren;
|
|
11
14
|
}) => JSXElement;
|
|
12
15
|
}
|
|
13
16
|
interface ContextFrame {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","names":[],"sources":["../../src/runtime/context.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"context.d.ts","names":[],"sources":["../../src/runtime/context.ts"],"mappings":";;;;KA6BY,UAAA;AAAA,KAEP,UAAA,GAAa,eAAe;AAAA,KAC5B,oBAAA,GAAuB,UAAA,UAAoB,UAAU;AAAA,UAEzC,OAAA;EAAA,SACN,GAAA,EAAK,UAAA;EAAA,SACL,YAAA,EAAc,CAAA;EAAA,SAEd,KAAA,GAAQ,KAAA;IACf,KAAA,EAAO,CAAA;IACP,QAAA,GAAW,oBAAA;EAAA,MACP,UAAA;AAAA;AAAA,UAGS,YAAA;EACf,MAAA,EAAQ,YAAA;EAER,MAAA,EAAQ,GAAA,CAAI,UAAA;AAAA;AAAA,cAID,oBAAA;AAAA,iBAQG,oBAAA,CAAqB,IAAA,YAAgB,YAAY;AAAA,iBASjD,yBAAA,CACd,IAAA,WACA,KAAA,EAAO,YAAY,EACnB,SAAA;AAAA,iBAsEc,6BAAA,CACd,IAAA,WACA,KAAA,EAAO,YAAY,SACnB,SAAA;AAvGgB;AAGlB;;;;;;;;;;AAHkB,iBAsKF,WAAA,GAAA,CAAe,KAAA,EAAO,YAAA,SAAqB,EAAA,QAAU,CAAA,GAAI,CAAA;;;;AAhKjD;AAIxB;;;;AAAmE;iBA+KnD,wBAAA,GAAA,CACd,KAAA,EAAO,YAAA,SACP,EAAA,QAAU,CAAA,GACT,CAAA;AAAA,iBAYa,aAAA,GAAA,CAAiB,YAAA,EAAc,CAAA,GAAI,OAAA,CAAQ,CAAA;AAAA,iBAiD3C,WAAA,GAAA,CAAe,OAAA,EAAS,OAAA,CAAQ,CAAA,IAAK,CAAA;;AAvOY;AASjE;;iBA6WgB,gBAAA,CAAA,GAAoB,YAAY;;;;;iBAgBhC,eAAA,CAAA;;AA1XG;AAsEnB;iBA6TgB,sBAAA,CAAA,GAA0B,YAAY;;;;;iBAQtC,qBAAA,CAAA,GAAyB,YAAY"}
|
package/dist/runtime/context.js
CHANGED
|
@@ -125,6 +125,9 @@ function preserveStaticChildrenMarker(source, target) {
|
|
|
125
125
|
});
|
|
126
126
|
return target;
|
|
127
127
|
}
|
|
128
|
+
function isPresentRenderable(value) {
|
|
129
|
+
return value !== null && value !== void 0 && value !== false;
|
|
130
|
+
}
|
|
128
131
|
function readContext(context) {
|
|
129
132
|
const frame = currentContextFrame || currentAsyncResourceFrame;
|
|
130
133
|
if (!frame) throw new Error("readContext() can only be called during component render or async resource execution. Ensure you are calling this from inside your component or resource function.");
|
|
@@ -168,7 +171,7 @@ function ContextScopeComponent(props) {
|
|
|
168
171
|
return markWithFrame(child, newFrame);
|
|
169
172
|
}));
|
|
170
173
|
else if (typeof children === "function") return createFunctionChildInvoker(children, newFrame, getCurrentComponentInstance());
|
|
171
|
-
else if (children) return markWithFrame(children, newFrame);
|
|
174
|
+
else if (isPresentRenderable(children)) return markWithFrame(children, newFrame);
|
|
172
175
|
return null;
|
|
173
176
|
}
|
|
174
177
|
/**
|
|
@@ -190,7 +193,7 @@ function markWithFrame(node, frame) {
|
|
|
190
193
|
function ContextFunctionChildInvoker(props) {
|
|
191
194
|
const { fn, __frame } = props;
|
|
192
195
|
const res = withContext(__frame, () => fn());
|
|
193
|
-
if (res) return markWithFrame(res, __frame);
|
|
196
|
+
if (isPresentRenderable(res)) return markWithFrame(res, __frame);
|
|
194
197
|
return null;
|
|
195
198
|
}
|
|
196
199
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":[],"sources":["../../src/runtime/context.ts"],"sourcesContent":["/**\n * Context system: lexical scope + render-time snapshots\n *\n * CORE SEMANTIC (Option A — Snapshot-Based):\n * ============================================\n * An async resource observes the context of the render that created it.\n * Context changes only take effect via re-render, not magically mid-await.\n *\n * This ensures:\n * - Deterministic behavior\n * - Concurrency safety\n * - Replayable execution\n * - Debuggability\n *\n * INVARIANTS:\n * - readContext() only works during component render (has currentContextFrame)\n * - Each render captures a context snapshot\n * - Async continuations see the snapshot from render start (frozen)\n * - Provider (Scope) creates a new frame that shadows parent\n * - Context updates require re-render to take effect\n */\n\nimport type { JSXElement } from '../common/jsx';\nimport { ELEMENT_TYPE, STATIC_CHILDREN } from '../common/jsx';\nimport type { Props } from '../common/props';\nimport { getCurrentComponentInstance } from './component';\nimport type { ComponentInstance } from './component';\n\nexport type ContextKey = symbol;\n\n// Lightweight VNode definition used for JSX typing in this module\ntype VNode = {\n type: string;\n props?: Record<string, unknown>;\n children?: (string | VNode | null | undefined | false)[];\n};\n\n// Union of allowed render return values (text, vnode, JSX element, etc.)\ntype Renderable =\n | JSXElement\n | VNode\n | string\n | number\n | null\n | undefined\n | false;\n\nexport interface Context<T> {\n readonly key: ContextKey;\n readonly defaultValue: T;\n // A Scope is a JSX-style element factory returning a JSXElement (component invocation)\n readonly Scope: (props: { value: unknown; children?: unknown }) => JSXElement;\n}\n\nexport interface ContextFrame {\n parent: ContextFrame | null;\n // Lazily allocate `values` Map only when a provider sets values or a read occurs.\n values: Map<ContextKey, unknown> | null;\n}\n\n// Symbol to mark vnodes that need frame restoration\nexport const CONTEXT_FRAME_SYMBOL = Symbol('__tempoContextFrame__');\n\ntype ContextFrameCarrier = {\n [CONTEXT_FRAME_SYMBOL]?: ContextFrame;\n};\n\nconst vnodeContextFrames = new WeakMap<object, ContextFrame>();\n\nexport function getVNodeContextFrame(node: unknown): ContextFrame | undefined {\n if (typeof node !== 'object' || node === null) {\n return undefined;\n }\n\n const objectNode = node as ContextFrameCarrier;\n return vnodeContextFrames.get(node) ?? objectNode[CONTEXT_FRAME_SYMBOL];\n}\n\nexport function markVNodeWithContextFrame(\n node: unknown,\n frame: ContextFrame,\n overwrite = false\n): void {\n if (typeof node !== 'object' || node === null) {\n return;\n }\n\n if (!overwrite && getVNodeContextFrame(node)) {\n return;\n }\n\n const objectNode = node as ContextFrameCarrier;\n if (Object.prototype.hasOwnProperty.call(node, CONTEXT_FRAME_SYMBOL)) {\n try {\n objectNode[CONTEXT_FRAME_SYMBOL] = frame;\n return;\n } catch {\n // Fall through to WeakMap metadata when the symbol slot is readonly.\n }\n }\n\n if (Object.isExtensible(node)) {\n try {\n Object.defineProperty(node, CONTEXT_FRAME_SYMBOL, {\n value: frame,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n return;\n } catch {\n // Fall through to WeakMap metadata for exotic objects/proxies.\n }\n }\n\n vnodeContextFrames.set(node, frame);\n}\n\nfunction isVNodeLike(value: unknown): boolean {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return false;\n }\n\n const objectValue = value as Record<string | symbol, unknown>;\n if (objectValue.$$typeof === ELEMENT_TYPE) {\n return true;\n }\n\n return (\n 'type' in objectValue &&\n ('props' in objectValue || 'children' in objectValue)\n );\n}\n\nfunction markContextPropValue(\n value: unknown,\n frame: ContextFrame,\n overwrite: boolean\n): void {\n if (Array.isArray(value)) {\n for (const item of value) {\n markContextPropValue(item, frame, overwrite);\n }\n return;\n }\n\n if (isVNodeLike(value)) {\n markVNodeTreeWithContextFrame(value, frame, overwrite);\n }\n}\n\nexport function markVNodeTreeWithContextFrame(\n node: unknown,\n frame: ContextFrame | null,\n overwrite = false\n): unknown {\n if (!frame) return node;\n\n if (Array.isArray(node)) {\n for (const child of node) {\n markVNodeTreeWithContextFrame(child, frame, overwrite);\n }\n return node;\n }\n\n if (typeof node !== 'object' || node === null) {\n return node;\n }\n\n markVNodeWithContextFrame(node, frame, overwrite);\n\n const objectNode = node as Record<string | symbol, unknown>;\n const props =\n typeof objectNode.props === 'object' && objectNode.props !== null\n ? (objectNode.props as Record<string, unknown>)\n : null;\n const children = (props?.children ?? objectNode.children) as unknown;\n\n if (Array.isArray(children)) {\n for (const child of children) {\n markVNodeTreeWithContextFrame(child, frame, overwrite);\n }\n } else if (children) {\n markVNodeTreeWithContextFrame(children, frame, overwrite);\n }\n\n if (props) {\n for (const key in props) {\n if (key !== 'children') {\n markContextPropValue(props[key], frame, overwrite);\n }\n }\n }\n\n return node;\n}\n\n// Global context frame stack (maintained during render)\n// INVARIANT: Must NEVER be non-null across an await boundary\nlet currentContextFrame: ContextFrame | null = null;\n\n// Async resource frame (maintained during async resource execution)\n// INVARIANT: Set only for synchronous execution steps, cleared in finally\n// This allows async resources to access their frozen render-time snapshot\nlet currentAsyncResourceFrame: ContextFrame | null = null;\n\n/**\n * Execute a function within a specific context frame.\n *\n * CORE PRIMITIVE for context restoration:\n * - Saves the current context\n * - Sets the provided frame as current\n * - Executes the function\n * - Restores the previous context in finally\n *\n * This ensures no context frame remains globally active across await.\n */\nexport function withContext<T>(frame: ContextFrame | null, fn: () => T): T {\n const oldFrame = currentContextFrame;\n currentContextFrame = frame;\n try {\n return fn();\n } finally {\n currentContextFrame = oldFrame;\n }\n}\n\n/**\n * Execute an async resource step within its frozen context snapshot.\n *\n * CRITICAL: This wrapper is applied only to synchronous execution steps of\n * an async resource (the initial call). We intentionally DO NOT restore\n * the resource frame for post-await continuations — continuations must not\n * observe or rely on a live resource frame. This keeps semantics simple and\n * deterministic: async resources see only their creation-time snapshot.\n */\nexport function withAsyncResourceContext<T>(\n frame: ContextFrame | null,\n fn: () => T\n): T {\n const oldFrame = currentAsyncResourceFrame;\n // Only set the frame for the synchronous execution step\n currentAsyncResourceFrame = frame;\n try {\n return fn();\n } finally {\n // Clear the frame to avoid exposing it across await boundaries\n currentAsyncResourceFrame = oldFrame;\n }\n}\n\nexport function defineContext<T>(defaultValue: T): Context<T> {\n const key = Symbol('AskrContext');\n\n return {\n key,\n defaultValue,\n Scope: (props: { value: unknown; children?: unknown }): JSXElement => {\n // Scope component: accepts an unknown value (tests often pass loosely typed values)\n // Cast to the expected T at the call site to preserve runtime behavior.\n const value = props.value as T;\n // Scope component: creates a new frame and renders children within it\n return {\n $$typeof: ELEMENT_TYPE,\n type: ContextScopeComponent,\n props: { key, value, children: props.children },\n key: null,\n } as JSXElement;\n },\n };\n}\n\nfunction preserveStaticChildrenMarker(\n source: readonly unknown[],\n target: unknown[]\n): unknown[] {\n if (\n (\n source as {\n [STATIC_CHILDREN]?: boolean;\n }\n )[STATIC_CHILDREN] === true\n ) {\n Object.defineProperty(target, STATIC_CHILDREN, {\n value: true,\n enumerable: false,\n configurable: true,\n });\n }\n\n return target;\n}\n\nexport function readContext<T>(context: Context<T>): T {\n // Check render frame first (components), then async resource frame (resources)\n const frame = currentContextFrame || currentAsyncResourceFrame;\n\n if (!frame) {\n throw new Error(\n 'readContext() can only be called during component render or async resource execution. ' +\n 'Ensure you are calling this from inside your component or resource function.'\n );\n }\n\n let current: ContextFrame | null = frame;\n while (current) {\n // `values` may be null when no provider has created it yet — treat as empty\n const values = current.values;\n if (values && values.has(context.key)) {\n return values.get(context.key) as T;\n }\n current = current.parent;\n }\n return context.defaultValue;\n}\n\n/**\n * Internal component that manages context frame\n * Used by Context.Scope to provide shadowed value to children\n */\nfunction ContextScopeComponent(props: Props): Renderable {\n // Extract expected properties (we accept a loose shape so this can be used as a component type)\n const key = props['key'] as ContextKey;\n const value = props['value'];\n const children = props['children'] as Renderable;\n\n // Create a new frame with this value\n const instance = getCurrentComponentInstance();\n const parentFrame: ContextFrame | null = (() => {\n // Prefer the live render frame.\n // Note: the runtime executes component functions inside an empty \"render frame\"\n // whose parent points at the nearest provider chain. Even if this frame has no\n // values, it must still be used to preserve the parent linkage.\n if (currentContextFrame) return currentContextFrame;\n\n // If there is no live render frame (should be rare), fall back to the\n // instance's owner frame.\n if (instance && instance.ownerFrame) return instance.ownerFrame;\n\n // Do NOT fall back to the async snapshot stack here: that stack represents\n // unrelated async continuations and must not affect lexical provider chaining.\n return null;\n })();\n\n const newFrame: ContextFrame = {\n parent: parentFrame,\n values: new Map([[key, value]]),\n };\n\n // Helper: create a function-child invoker node (centralized cast)\n function createFunctionChildInvoker(\n fn: () => Renderable,\n frame: ContextFrame,\n owner: ComponentInstance | null\n ): Renderable {\n return {\n type: ContextFunctionChildInvoker,\n props: { fn, __frame: frame, __owner: owner },\n } as unknown as Renderable;\n }\n\n // The renderer will set ownerFrame on child component instances when they're created.\n // We mark vnodes with the frame so the renderer knows which frame to assign.\n if (Array.isArray(children)) {\n // Mark array elements with the frame. If an element is a function-child,\n // convert it into a lazy invoker so it's executed later inside the frame.\n return preserveStaticChildrenMarker(\n children,\n children.map((child) => {\n if (typeof child === 'function') {\n return createFunctionChildInvoker(\n child as () => Renderable,\n newFrame,\n getCurrentComponentInstance()\n );\n }\n return markWithFrame(child, newFrame);\n })\n ) as unknown as Renderable;\n } else if (typeof children === 'function') {\n // If children is a function (render callback), do NOT execute it eagerly\n // during the parent render. Instead, return a small internal component\n // that will execute the function later (when it itself is rendered) and\n // will execute it within the provider frame so any reads performed during\n // that execution observe the provider's frame.\n return createFunctionChildInvoker(\n children as () => Renderable,\n newFrame,\n getCurrentComponentInstance()\n );\n } else if (children) {\n return markWithFrame(children, newFrame);\n }\n\n return null;\n}\n\n/**\n * Internal: Mark a vnode with a context frame\n * The renderer will restore this frame before executing component functions\n */\nfunction markWithFrame(node: Renderable, frame: ContextFrame): Renderable {\n return markVNodeTreeWithContextFrame(node, frame, true) as Renderable;\n}\n\n/**\n * Internal helper component: executes a function-child lazily inside the\n * provided frame and marks the returned subtree with that frame so later\n * component executions will restore the correct context frame.\n *\n * SNAPSHOT SEMANTIC: The frame passed here is the snapshot captured at render\n * time. Any resources created during this execution will observe this frozen\n * snapshot, ensuring deterministic behavior.\n */\nfunction ContextFunctionChildInvoker(props: {\n fn: () => Renderable;\n __frame: ContextFrame;\n __owner?: ComponentInstance | null;\n}): Renderable {\n const { fn, __frame } = props;\n\n // Execute the function-child within the provider frame.\n // The owner's ownerFrame is already set by the renderer when the component was created.\n // Any resources started during this execution will capture this frame as their\n // snapshot, ensuring they see the context values from this render, not future renders.\n const res = withContext(__frame, () => fn());\n\n // Mark the result so the renderer knows to set ownerFrame on child instances\n if (res) return markWithFrame(res, __frame);\n return null;\n}\n\n/**\n * Push a new context frame (for render entry)\n * Called by component runtime when render starts\n */\nexport function pushContextFrame(): ContextFrame {\n // Lazily allocate the `values` map to avoid per-render allocations when\n // components do not use context. The map will be created when a provider\n // sets a value or when a read discovers no map and needs to behave as empty.\n const frame: ContextFrame = {\n parent: currentContextFrame,\n values: null,\n };\n currentContextFrame = frame;\n return frame;\n}\n\n/**\n * Pop context frame (for render exit)\n * Called by component runtime when render ends\n */\nexport function popContextFrame(): void {\n if (currentContextFrame) {\n currentContextFrame = currentContextFrame.parent;\n }\n}\n\n/**\n * Get the current context frame for inspection (used by tests/diagnostics only)\n */\nexport function getCurrentContextFrame(): ContextFrame | null {\n return currentContextFrame;\n}\n\n/**\n * Get the top of the context snapshot stack (used by runtime when deciding\n * how to link snapshots for async continuations). Returns null if stack empty.\n */\nexport function getTopContextSnapshot(): ContextFrame | null {\n return currentContextFrame;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"context.js","names":[],"sources":["../../src/runtime/context.ts"],"sourcesContent":["/**\n * Context system: lexical scope + render-time snapshots\n *\n * CORE SEMANTIC (Option A — Snapshot-Based):\n * ============================================\n * An async resource observes the context of the render that created it.\n * Context changes only take effect via re-render, not magically mid-await.\n *\n * This ensures:\n * - Deterministic behavior\n * - Concurrency safety\n * - Replayable execution\n * - Debuggability\n *\n * INVARIANTS:\n * - readContext() only works during component render (has currentContextFrame)\n * - Each render captures a context snapshot\n * - Async continuations see the snapshot from render start (frozen)\n * - Provider (Scope) creates a new frame that shadows parent\n * - Context updates require re-render to take effect\n */\n\nimport type { JSXElement } from '../common/jsx';\nimport { ELEMENT_TYPE, STATIC_CHILDREN } from '../common/jsx';\nimport type { Props } from '../common/props';\nimport type { RenderableChild } from '../common/vnode';\nimport { getCurrentComponentInstance } from './component';\nimport type { ComponentInstance } from './component';\n\nexport type ContextKey = symbol;\n\ntype Renderable = RenderableChild;\ntype ContextScopeChildren = Renderable | (() => Renderable);\n\nexport interface Context<T> {\n readonly key: ContextKey;\n readonly defaultValue: T;\n // A Scope is a JSX-style element factory returning a JSXElement (component invocation)\n readonly Scope: (props: {\n value: T;\n children?: ContextScopeChildren;\n }) => JSXElement;\n}\n\nexport interface ContextFrame {\n parent: ContextFrame | null;\n // Lazily allocate `values` Map only when a provider sets values or a read occurs.\n values: Map<ContextKey, unknown> | null;\n}\n\n// Symbol to mark vnodes that need frame restoration\nexport const CONTEXT_FRAME_SYMBOL = Symbol('__tempoContextFrame__');\n\ntype ContextFrameCarrier = {\n [CONTEXT_FRAME_SYMBOL]?: ContextFrame;\n};\n\nconst vnodeContextFrames = new WeakMap<object, ContextFrame>();\n\nexport function getVNodeContextFrame(node: unknown): ContextFrame | undefined {\n if (typeof node !== 'object' || node === null) {\n return undefined;\n }\n\n const objectNode = node as ContextFrameCarrier;\n return vnodeContextFrames.get(node) ?? objectNode[CONTEXT_FRAME_SYMBOL];\n}\n\nexport function markVNodeWithContextFrame(\n node: unknown,\n frame: ContextFrame,\n overwrite = false\n): void {\n if (typeof node !== 'object' || node === null) {\n return;\n }\n\n if (!overwrite && getVNodeContextFrame(node)) {\n return;\n }\n\n const objectNode = node as ContextFrameCarrier;\n if (Object.prototype.hasOwnProperty.call(node, CONTEXT_FRAME_SYMBOL)) {\n try {\n objectNode[CONTEXT_FRAME_SYMBOL] = frame;\n return;\n } catch {\n // Fall through to WeakMap metadata when the symbol slot is readonly.\n }\n }\n\n if (Object.isExtensible(node)) {\n try {\n Object.defineProperty(node, CONTEXT_FRAME_SYMBOL, {\n value: frame,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n return;\n } catch {\n // Fall through to WeakMap metadata for exotic objects/proxies.\n }\n }\n\n vnodeContextFrames.set(node, frame);\n}\n\nfunction isVNodeLike(value: unknown): boolean {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return false;\n }\n\n const objectValue = value as Record<string | symbol, unknown>;\n if (objectValue.$$typeof === ELEMENT_TYPE) {\n return true;\n }\n\n return (\n 'type' in objectValue &&\n ('props' in objectValue || 'children' in objectValue)\n );\n}\n\nfunction markContextPropValue(\n value: unknown,\n frame: ContextFrame,\n overwrite: boolean\n): void {\n if (Array.isArray(value)) {\n for (const item of value) {\n markContextPropValue(item, frame, overwrite);\n }\n return;\n }\n\n if (isVNodeLike(value)) {\n markVNodeTreeWithContextFrame(value, frame, overwrite);\n }\n}\n\nexport function markVNodeTreeWithContextFrame(\n node: unknown,\n frame: ContextFrame | null,\n overwrite = false\n): unknown {\n if (!frame) return node;\n\n if (Array.isArray(node)) {\n for (const child of node) {\n markVNodeTreeWithContextFrame(child, frame, overwrite);\n }\n return node;\n }\n\n if (typeof node !== 'object' || node === null) {\n return node;\n }\n\n markVNodeWithContextFrame(node, frame, overwrite);\n\n const objectNode = node as Record<string | symbol, unknown>;\n const props =\n typeof objectNode.props === 'object' && objectNode.props !== null\n ? (objectNode.props as Record<string, unknown>)\n : null;\n const children = (props?.children ?? objectNode.children) as unknown;\n\n if (Array.isArray(children)) {\n for (const child of children) {\n markVNodeTreeWithContextFrame(child, frame, overwrite);\n }\n } else if (children) {\n markVNodeTreeWithContextFrame(children, frame, overwrite);\n }\n\n if (props) {\n for (const key in props) {\n if (key !== 'children') {\n markContextPropValue(props[key], frame, overwrite);\n }\n }\n }\n\n return node;\n}\n\n// Global context frame stack (maintained during render)\n// INVARIANT: Must NEVER be non-null across an await boundary\nlet currentContextFrame: ContextFrame | null = null;\n\n// Async resource frame (maintained during async resource execution)\n// INVARIANT: Set only for synchronous execution steps, cleared in finally\n// This allows async resources to access their frozen render-time snapshot\nlet currentAsyncResourceFrame: ContextFrame | null = null;\n\n/**\n * Execute a function within a specific context frame.\n *\n * CORE PRIMITIVE for context restoration:\n * - Saves the current context\n * - Sets the provided frame as current\n * - Executes the function\n * - Restores the previous context in finally\n *\n * This ensures no context frame remains globally active across await.\n */\nexport function withContext<T>(frame: ContextFrame | null, fn: () => T): T {\n const oldFrame = currentContextFrame;\n currentContextFrame = frame;\n try {\n return fn();\n } finally {\n currentContextFrame = oldFrame;\n }\n}\n\n/**\n * Execute an async resource step within its frozen context snapshot.\n *\n * CRITICAL: This wrapper is applied only to synchronous execution steps of\n * an async resource (the initial call). We intentionally DO NOT restore\n * the resource frame for post-await continuations — continuations must not\n * observe or rely on a live resource frame. This keeps semantics simple and\n * deterministic: async resources see only their creation-time snapshot.\n */\nexport function withAsyncResourceContext<T>(\n frame: ContextFrame | null,\n fn: () => T\n): T {\n const oldFrame = currentAsyncResourceFrame;\n // Only set the frame for the synchronous execution step\n currentAsyncResourceFrame = frame;\n try {\n return fn();\n } finally {\n // Clear the frame to avoid exposing it across await boundaries\n currentAsyncResourceFrame = oldFrame;\n }\n}\n\nexport function defineContext<T>(defaultValue: T): Context<T> {\n const key = Symbol('AskrContext');\n\n return {\n key,\n defaultValue,\n Scope: (props: {\n value: T;\n children?: ContextScopeChildren;\n }): JSXElement => {\n const value = props.value;\n // Scope component: creates a new frame and renders children within it\n return {\n $$typeof: ELEMENT_TYPE,\n type: ContextScopeComponent,\n props: { key, value, children: props.children },\n key: null,\n } as JSXElement;\n },\n };\n}\n\nfunction preserveStaticChildrenMarker(\n source: readonly unknown[],\n target: unknown[]\n): unknown[] {\n if (\n (\n source as {\n [STATIC_CHILDREN]?: boolean;\n }\n )[STATIC_CHILDREN] === true\n ) {\n Object.defineProperty(target, STATIC_CHILDREN, {\n value: true,\n enumerable: false,\n configurable: true,\n });\n }\n\n return target;\n}\n\nfunction isPresentRenderable(\n value: Renderable | null | undefined\n): value is Renderable {\n return value !== null && value !== undefined && value !== false;\n}\n\nexport function readContext<T>(context: Context<T>): T {\n // Check render frame first (components), then async resource frame (resources)\n const frame = currentContextFrame || currentAsyncResourceFrame;\n\n if (!frame) {\n throw new Error(\n 'readContext() can only be called during component render or async resource execution. ' +\n 'Ensure you are calling this from inside your component or resource function.'\n );\n }\n\n let current: ContextFrame | null = frame;\n while (current) {\n // `values` may be null when no provider has created it yet — treat as empty\n const values = current.values;\n if (values && values.has(context.key)) {\n return values.get(context.key) as T;\n }\n current = current.parent;\n }\n return context.defaultValue;\n}\n\n/**\n * Internal component that manages context frame\n * Used by Context.Scope to provide shadowed value to children\n */\nfunction ContextScopeComponent(props: Props): Renderable {\n // Extract expected properties (we accept a loose shape so this can be used as a component type)\n const key = props['key'] as ContextKey;\n const value = props['value'];\n const children = props['children'] as ContextScopeChildren;\n\n // Create a new frame with this value\n const instance = getCurrentComponentInstance();\n const parentFrame: ContextFrame | null = (() => {\n // Prefer the live render frame.\n // Note: the runtime executes component functions inside an empty \"render frame\"\n // whose parent points at the nearest provider chain. Even if this frame has no\n // values, it must still be used to preserve the parent linkage.\n if (currentContextFrame) return currentContextFrame;\n\n // If there is no live render frame (should be rare), fall back to the\n // instance's owner frame.\n if (instance && instance.ownerFrame) return instance.ownerFrame;\n\n // Do NOT fall back to the async snapshot stack here: that stack represents\n // unrelated async continuations and must not affect lexical provider chaining.\n return null;\n })();\n\n const newFrame: ContextFrame = {\n parent: parentFrame,\n values: new Map([[key, value]]),\n };\n\n // Helper: create a function-child invoker node (centralized cast)\n function createFunctionChildInvoker(\n fn: () => Renderable,\n frame: ContextFrame,\n owner: ComponentInstance | null\n ): Renderable {\n return {\n type: ContextFunctionChildInvoker,\n props: { fn, __frame: frame, __owner: owner },\n } as unknown as Renderable;\n }\n\n // The renderer will set ownerFrame on child component instances when they're created.\n // We mark vnodes with the frame so the renderer knows which frame to assign.\n if (Array.isArray(children)) {\n // Mark array elements with the frame. If an element is a function-child,\n // convert it into a lazy invoker so it's executed later inside the frame.\n return preserveStaticChildrenMarker(\n children,\n children.map((child) => {\n if (typeof child === 'function') {\n return createFunctionChildInvoker(\n child as () => Renderable,\n newFrame,\n getCurrentComponentInstance()\n );\n }\n return markWithFrame(child, newFrame);\n })\n ) as unknown as Renderable;\n } else if (typeof children === 'function') {\n // If children is a function (render callback), do NOT execute it eagerly\n // during the parent render. Instead, return a small internal component\n // that will execute the function later (when it itself is rendered) and\n // will execute it within the provider frame so any reads performed during\n // that execution observe the provider's frame.\n return createFunctionChildInvoker(\n children as () => Renderable,\n newFrame,\n getCurrentComponentInstance()\n );\n } else if (isPresentRenderable(children)) {\n return markWithFrame(children, newFrame);\n }\n\n return null;\n}\n\n/**\n * Internal: Mark a vnode with a context frame\n * The renderer will restore this frame before executing component functions\n */\nfunction markWithFrame(node: Renderable, frame: ContextFrame): Renderable {\n return markVNodeTreeWithContextFrame(node, frame, true) as Renderable;\n}\n\n/**\n * Internal helper component: executes a function-child lazily inside the\n * provided frame and marks the returned subtree with that frame so later\n * component executions will restore the correct context frame.\n *\n * SNAPSHOT SEMANTIC: The frame passed here is the snapshot captured at render\n * time. Any resources created during this execution will observe this frozen\n * snapshot, ensuring deterministic behavior.\n */\nfunction ContextFunctionChildInvoker(props: {\n fn: () => Renderable;\n __frame: ContextFrame;\n __owner?: ComponentInstance | null;\n}): Renderable {\n const { fn, __frame } = props;\n\n // Execute the function-child within the provider frame.\n // The owner's ownerFrame is already set by the renderer when the component was created.\n // Any resources started during this execution will capture this frame as their\n // snapshot, ensuring they see the context values from this render, not future renders.\n const res = withContext(__frame, () => fn());\n\n // Mark the result so the renderer knows to set ownerFrame on child instances\n if (isPresentRenderable(res)) return markWithFrame(res, __frame);\n return null;\n}\n\n/**\n * Push a new context frame (for render entry)\n * Called by component runtime when render starts\n */\nexport function pushContextFrame(): ContextFrame {\n // Lazily allocate the `values` map to avoid per-render allocations when\n // components do not use context. The map will be created when a provider\n // sets a value or when a read discovers no map and needs to behave as empty.\n const frame: ContextFrame = {\n parent: currentContextFrame,\n values: null,\n };\n currentContextFrame = frame;\n return frame;\n}\n\n/**\n * Pop context frame (for render exit)\n * Called by component runtime when render ends\n */\nexport function popContextFrame(): void {\n if (currentContextFrame) {\n currentContextFrame = currentContextFrame.parent;\n }\n}\n\n/**\n * Get the current context frame for inspection (used by tests/diagnostics only)\n */\nexport function getCurrentContextFrame(): ContextFrame | null {\n return currentContextFrame;\n}\n\n/**\n * Get the top of the context snapshot stack (used by runtime when deciding\n * how to link snapshots for async continuations). Returns null if stack empty.\n */\nexport function getTopContextSnapshot(): ContextFrame | null {\n return currentContextFrame;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control.d.ts","names":[],"sources":["../../src/runtime/control.ts"],"mappings":";;;;;;UASiB,WAAA;EACf,GAAA;EACA,MAAA,QAAc,
|
|
1
|
+
{"version":3,"file":"control.d.ts","names":[],"sources":["../../src/runtime/control.ts"],"mappings":";;;;;;UASiB,WAAA;EACf,GAAA;EACA,MAAA,QAAc,KAAK;EACnB,IAAA;AAAA;AAAA,UAGQ,sBAAA;EACR,SAAA;EACA,WAAA,EAAa,UAAA;EACb,YAAA,EAAc,KAAA;EACd,gBAAA,EAAkB,IAAA;EAClB,cAAA,EAAgB,iBAAA;EAChB,sBAAA;EACA,yBAAA;AAAA;AAAA,UAGe,SAAA,SAAkB,sBAAA;EACjC,IAAA;EACA,aAAA,EAAe,UAAA;EACf,cAAA,SAAuB,KAAA;EACvB,YAAA,IAAgB,KAAA,cAAmB,KAAA,WAAgB,KAAA;EACnD,aAAA;EACA,WAAA,EAAa,UAAA;AAAA;AAAA,UAGE,SAAA,SAAkB,sBAAA;EACjC,IAAA;EACA,QAAA,SAAiB,KAAA;EACjB,OAAA,EAAS,WAAA;AAAA;AAAA,KAGC,oBAAA,GAAuB,QAAA,YAAoB,SAAA,GAAY,SAAA;AAAA,iBAkCnD,eAAA,CACd,KAAA,WACA,YAAA,EAAc,SAAA,kBACd,cAAA,EAAgB,SAAA,qBACf,SAAA;AAAA,iBAkBa,iBAAA,CAAkB,KAAA,EAAO,SAAA,GAAY,KAAK;AAAA,iBAiD1C,uBAAA,CAAwB,KAAgB,EAAT,SAAS;AAAA,iBAUxC,eAAA,CACd,OAAA,EAAS,WAAA,IACT,QAAA,SAAiB,KAAA,WAChB,SAAA;AAAA,iBAea,iBAAA,CAAkB,KAAA,EAAO,SAAA,GAAY,KAAK;AAAA,iBAiC1C,uBAAA,CAAwB,KAAgB,EAAT,SAAS"}
|