@askrjs/askr 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/{index.d.cts → component-BBGWJdqJ.d.ts} +42 -33
  2. package/dist/foundations/index.d.ts +32 -0
  3. package/dist/{chunk-WTFWRSHB.js → foundations/index.js} +76 -28
  4. package/dist/foundations/index.js.map +1 -0
  5. package/dist/fx/index.js +295 -77
  6. package/dist/fx/index.js.map +1 -1
  7. package/dist/index.d.ts +4 -134
  8. package/dist/index.js +3709 -42
  9. package/dist/index.js.map +1 -1
  10. package/dist/{jsx/jsx-dev-runtime.d.ts → jsx-dev-runtime.d.ts} +3 -3
  11. package/dist/jsx-dev-runtime.js +17 -0
  12. package/dist/jsx-dev-runtime.js.map +1 -0
  13. package/dist/jsx-runtime.d.ts +14 -0
  14. package/dist/{chunk-SALJX5PZ.js → jsx-runtime.js} +6 -9
  15. package/dist/jsx-runtime.js.map +1 -0
  16. package/dist/layout-D5MqtW_q.d.ts +14 -0
  17. package/dist/resources/index.d.ts +3 -0
  18. package/dist/resources/index.js +753 -239
  19. package/dist/resources/index.js.map +1 -1
  20. package/dist/router/index.d.ts +2 -14
  21. package/dist/router/index.js +3115 -19
  22. package/dist/router/index.js.map +1 -1
  23. package/dist/ssr/index.js +3363 -103
  24. package/dist/ssr/index.js.map +1 -1
  25. package/dist/{types-CZHOAiC1.d.ts → types-uOPfcrdz.d.ts} +3 -1
  26. package/dist/vite/index.d.ts +17 -0
  27. package/dist/vite/index.js +2306 -0
  28. package/dist/vite/index.js.map +1 -0
  29. package/package.json +21 -23
  30. package/dist/chunk-64C7W2AE.js +0 -43
  31. package/dist/chunk-64C7W2AE.js.map +0 -1
  32. package/dist/chunk-6FLMH4EL.js +0 -124
  33. package/dist/chunk-6FLMH4EL.js.map +0 -1
  34. package/dist/chunk-FJUXFA72.js +0 -16
  35. package/dist/chunk-FJUXFA72.js.map +0 -1
  36. package/dist/chunk-SALJX5PZ.js.map +0 -1
  37. package/dist/chunk-VRAEBIJ3.js +0 -82
  38. package/dist/chunk-VRAEBIJ3.js.map +0 -1
  39. package/dist/chunk-WTFWRSHB.js.map +0 -1
  40. package/dist/chunk-XHKZGJE3.js +0 -2907
  41. package/dist/chunk-XHKZGJE3.js.map +0 -1
  42. package/dist/chunk-Z5ZSTLYF.js +0 -420
  43. package/dist/chunk-Z5ZSTLYF.js.map +0 -1
  44. package/dist/fx/index.cjs +0 -1193
  45. package/dist/fx/index.cjs.map +0 -1
  46. package/dist/fx/index.d.cts +0 -186
  47. package/dist/index.cjs +0 -4035
  48. package/dist/index.cjs.map +0 -1
  49. package/dist/jsx/jsx-dev-runtime.cjs +0 -46
  50. package/dist/jsx/jsx-dev-runtime.cjs.map +0 -1
  51. package/dist/jsx/jsx-dev-runtime.d.cts +0 -12
  52. package/dist/jsx/jsx-dev-runtime.js +0 -19
  53. package/dist/jsx/jsx-dev-runtime.js.map +0 -1
  54. package/dist/jsx/jsx-runtime.cjs +0 -54
  55. package/dist/jsx/jsx-runtime.cjs.map +0 -1
  56. package/dist/jsx/jsx-runtime.d.cts +0 -21
  57. package/dist/jsx/jsx-runtime.d.ts +0 -21
  58. package/dist/jsx/jsx-runtime.js +0 -16
  59. package/dist/jsx/jsx-runtime.js.map +0 -1
  60. package/dist/jsx-AzPM8gMS.d.cts +0 -35
  61. package/dist/navigate-LUVYHYZZ.js +0 -17
  62. package/dist/navigate-LUVYHYZZ.js.map +0 -1
  63. package/dist/resources/index.cjs +0 -1200
  64. package/dist/resources/index.cjs.map +0 -1
  65. package/dist/resources/index.d.cts +0 -21
  66. package/dist/route-BCND6MPK.js +0 -32
  67. package/dist/route-BCND6MPK.js.map +0 -1
  68. package/dist/router/index.cjs +0 -3247
  69. package/dist/router/index.cjs.map +0 -1
  70. package/dist/router/index.d.cts +0 -64
  71. package/dist/router-DaGtH1Sq.d.cts +0 -36
  72. package/dist/ssr/index.cjs +0 -4059
  73. package/dist/ssr/index.cjs.map +0 -1
  74. package/dist/ssr/index.d.cts +0 -123
  75. package/dist/types-CZ5sWur5.d.cts +0 -23
  76. package/src/jsx/index.ts +0 -4
  77. package/src/jsx/jsx-dev-runtime.ts +0 -23
  78. package/src/jsx/jsx-runtime.ts +0 -48
  79. package/src/jsx/types.ts +0 -33
  80. package/src/jsx/utils.ts +0 -19
@@ -1,5 +1,4 @@
1
- import { P as Props, J as JSXElement } from './jsx-AzPM8gMS.cjs';
2
- import { R as Route } from './router-DaGtH1Sq.cjs';
1
+ import { P as Props, J as JSXElement } from './jsx-AzPM8gMS.js';
3
2
 
4
3
  /**
5
4
  * State primitive for Askr components
@@ -132,37 +131,47 @@ interface ComponentInstance {
132
131
  _lastReadStates?: Set<State<unknown>>;
133
132
  _placeholder?: Comment;
134
133
  }
135
-
136
- /**
137
- * App bootstrap and mount
138
- */
139
-
140
- type IslandConfig = {
141
- root: Element | string;
142
- component: ComponentFunction;
143
- cleanupStrict?: boolean;
144
- routes?: never;
145
- };
146
- type SPAConfig = {
147
- root: Element | string;
148
- routes: Route[];
149
- cleanupStrict?: boolean;
150
- component?: never;
151
- };
152
- /**
153
- * createIsland: Enhances existing DOM (no router, mounts once)
154
- */
155
- declare function createIsland(config: IslandConfig): void;
156
134
  /**
157
- * createSPA: Initializes router and mounts the app with provided route table
135
+ * Get the abort signal for the current component
136
+ * Used to cancel async operations on unmount/navigation
137
+ *
138
+ * The signal is guaranteed to be aborted when:
139
+ * - Component unmounts
140
+ * - Navigation occurs (different route)
141
+ * - Parent is destroyed
142
+ *
143
+ * IMPORTANT: getSignal() must be called during component render execution.
144
+ * It captures the current component instance from context.
145
+ *
146
+ * @returns AbortSignal that will be aborted when component unmounts
147
+ * @throws Error if called outside component execution
148
+ *
149
+ * @example
150
+ * ```ts
151
+ * // ✅ Correct: called during render, used in async operation
152
+ * export async function UserPage({ id }: { id: string }) {
153
+ * const signal = getSignal();
154
+ * const user = await fetch(`/api/users/${id}`, { signal });
155
+ * return <div>{user.name}</div>;
156
+ * }
157
+ *
158
+ * // ✅ Correct: passed to event handler
159
+ * export function Button() {
160
+ * const signal = getSignal();
161
+ * return {
162
+ * type: 'button',
163
+ * props: {
164
+ * onClick: async () => {
165
+ * const data = await fetch(url, { signal });
166
+ * }
167
+ * }
168
+ * };
169
+ * }
170
+ *
171
+ * // ❌ Wrong: called outside component context
172
+ * const signal = getSignal(); // Error: not in component
173
+ * ```
158
174
  */
159
- declare function createSPA(config: SPAConfig): Promise<void>;
160
-
161
- declare function derive<TOut>(fn: () => TOut): TOut | null;
162
- declare function derive<TIn, TOut>(source: {
163
- value: TIn | null;
164
- pending?: boolean;
165
- error?: Error | null;
166
- } | TIn | (() => TIn), map: (value: TIn) => TOut): TOut | null;
175
+ declare function getSignal(): AbortSignal;
167
176
 
168
- export { type IslandConfig, Props, type SPAConfig, type State, createIsland, createSPA, derive, state };
177
+ export { type ComponentFunction as C, type State as S, getSignal as g, state as s };
@@ -0,0 +1,32 @@
1
+ export { L as LayoutComponent, l as layout } from '../layout-D5MqtW_q.js';
2
+ import '../types-uOPfcrdz.js';
3
+ import { J as JSXElement } from '../jsx-AzPM8gMS.js';
4
+
5
+ type SlotProps = {
6
+ asChild: true;
7
+ children: JSXElement;
8
+ [key: string]: unknown;
9
+ } | {
10
+ asChild?: false;
11
+ children?: unknown;
12
+ };
13
+ declare function Slot(props: SlotProps): JSXElement | null;
14
+
15
+ /**
16
+ * Portal / Host primitive.
17
+ *
18
+ * A portal is a named render slot within the existing tree.
19
+ * It does NOT create a second tree or touch the DOM directly.
20
+ */
21
+ interface Portal<T = unknown> {
22
+ /** Mount point — rendered exactly once */
23
+ (): unknown;
24
+ /** Render content into the portal */
25
+ render(props: {
26
+ children?: T;
27
+ }): unknown;
28
+ }
29
+ declare function definePortal<T = unknown>(): Portal<T>;
30
+ declare const DefaultPortal: Portal<unknown>;
31
+
32
+ export { DefaultPortal, JSXElement, type Portal, Slot, type SlotProps, definePortal };
@@ -1,42 +1,91 @@
1
- import {
2
- getCurrentComponentInstance,
3
- logger
4
- } from "./chunk-XHKZGJE3.js";
1
+ // src/foundations/layout.tsx
2
+ function layout(Layout) {
3
+ return (children, props) => Layout({ ...props, children });
4
+ }
5
+
6
+ // src/dev/logger.ts
7
+ function callConsole(method, args) {
8
+ const c = typeof console !== "undefined" ? console : void 0;
9
+ if (!c) return;
10
+ const fn = c[method];
11
+ if (typeof fn === "function") {
12
+ try {
13
+ fn.apply(console, args);
14
+ } catch {
15
+ }
16
+ }
17
+ }
18
+ var logger = {
19
+ debug: (...args) => {
20
+ if (process.env.NODE_ENV === "production") return;
21
+ callConsole("debug", args);
22
+ },
23
+ info: (...args) => {
24
+ if (process.env.NODE_ENV === "production") return;
25
+ callConsole("info", args);
26
+ },
27
+ warn: (...args) => {
28
+ if (process.env.NODE_ENV === "production") return;
29
+ callConsole("warn", args);
30
+ },
31
+ error: (...args) => {
32
+ callConsole("error", args);
33
+ }
34
+ };
35
+
36
+ // src/common/jsx.ts
37
+ var ELEMENT_TYPE = /* @__PURE__ */ Symbol.for("askr.element");
38
+ var Fragment = /* @__PURE__ */ Symbol.for("askr.fragment");
39
+
40
+ // src/jsx/utils.ts
41
+ function isElement(value) {
42
+ return typeof value === "object" && value !== null && value.$$typeof === ELEMENT_TYPE;
43
+ }
44
+ function cloneElement(element, props) {
45
+ return {
46
+ ...element,
47
+ props: { ...element.props, ...props }
48
+ };
49
+ }
50
+
51
+ // src/foundations/slot.tsx
52
+ function Slot(props) {
53
+ if (props.asChild) {
54
+ const { children, ...rest } = props;
55
+ if (isElement(children)) {
56
+ return cloneElement(children, rest);
57
+ }
58
+ logger.warn("<Slot asChild> expects a single JSX element child.");
59
+ return null;
60
+ }
61
+ return {
62
+ $$typeof: ELEMENT_TYPE,
63
+ type: Fragment,
64
+ props: { children: props.children }
65
+ };
66
+ }
67
+
68
+ // src/runtime/component.ts
69
+ var currentInstance = null;
70
+ function getCurrentComponentInstance() {
71
+ return currentInstance;
72
+ }
5
73
 
6
74
  // src/foundations/portal.tsx
7
75
  function definePortal() {
8
76
  if (typeof createPortalSlot !== "function") {
9
77
  let HostFallback2 = function() {
10
- if (owner && owner.mounted === false) {
11
- owner = null;
12
- pending = void 0;
13
- }
14
78
  const inst = getCurrentComponentInstance();
15
- if (!owner && inst) owner = inst;
16
79
  if (process.env.NODE_ENV !== "production") {
17
80
  const ns = globalThis.__ASKR__ || (globalThis.__ASKR__ = {});
18
81
  ns.__PORTAL_READS = (ns.__PORTAL_READS || 0) + 1;
19
82
  }
20
- if (process.env.NODE_ENV !== "production") {
21
- if (inst && owner && inst !== owner && inst.mounted === true) {
22
- logger.warn(
23
- "[Portal] multiple mounted hosts detected; first mounted host is owner"
24
- );
25
- }
26
- }
83
+ if (process.env.NODE_ENV !== "production") ;
27
84
  return inst && owner && inst === owner ? pending : void 0;
28
85
  };
29
- var HostFallback = HostFallback2;
30
86
  let owner = null;
31
87
  let pending;
32
88
  HostFallback2.render = function RenderFallback(props) {
33
- if (!owner || owner.mounted !== true) return null;
34
- if (process.env.NODE_ENV !== "production") {
35
- const ns = globalThis.__ASKR__ || (globalThis.__ASKR__ = {});
36
- ns.__PORTAL_WRITES = (ns.__PORTAL_WRITES || 0) + 1;
37
- }
38
- pending = props.children;
39
- if (owner.notifyUpdate) owner.notifyUpdate();
40
89
  return null;
41
90
  };
42
91
  return HostFallback2;
@@ -92,7 +141,6 @@ var DefaultPortal = (() => {
92
141
  return Host;
93
142
  })();
94
143
 
95
- export {
96
- DefaultPortal
97
- };
98
- //# sourceMappingURL=chunk-WTFWRSHB.js.map
144
+ export { DefaultPortal, Slot, definePortal, layout };
145
+ //# sourceMappingURL=index.js.map
146
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/foundations/layout.tsx","../../src/dev/logger.ts","../../src/common/jsx.ts","../../src/jsx/utils.ts","../../src/foundations/slot.tsx","../../src/runtime/component.ts","../../src/foundations/portal.tsx"],"names":["HostFallback"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Layout helper.\n *\n * A layout is just a normal component that wraps children.\n * Persistence and reuse are handled by the runtime via component identity.\n *\n * This helper exists purely for readability and convention.\n */\n\nexport type LayoutComponent<P = object> = (\n props: P & { children?: unknown }\n) => unknown;\n\nexport function layout<P = object>(Layout: LayoutComponent<P>) {\n return (children?: unknown, props?: P) =>\n Layout({ ...(props as P), children });\n}\n","/**\n * Centralized logger interface\n * - Keeps production builds silent for debug/warn/info messages\n * - Ensures consistent behavior across the codebase\n * - Protects against missing `console` in some environments\n */\n\nfunction callConsole(method: string, args: unknown[]): void {\n const c = typeof console !== 'undefined' ? (console as unknown) : undefined;\n if (!c) return;\n const fn = (c as Record<string, unknown>)[method];\n if (typeof fn === 'function') {\n try {\n (fn as (...a: unknown[]) => unknown).apply(console, args as unknown[]);\n } catch {\n // ignore logging errors\n }\n }\n}\n\nexport const logger = {\n debug: (...args: unknown[]) => {\n if (process.env.NODE_ENV === 'production') return;\n callConsole('debug', args);\n },\n\n info: (...args: unknown[]) => {\n if (process.env.NODE_ENV === 'production') return;\n callConsole('info', args);\n },\n\n warn: (...args: unknown[]) => {\n if (process.env.NODE_ENV === 'production') return;\n callConsole('warn', args);\n },\n\n error: (...args: unknown[]) => {\n callConsole('error', args);\n },\n};\n","/**\n * Common call contracts: JSX element shape\n */\n\nimport type { Props } from './props';\n\nexport const ELEMENT_TYPE = Symbol.for('askr.element');\nexport const Fragment = Symbol.for('askr.fragment');\n\nexport interface JSXElement {\n /** Internal element marker (optional for plain vnode objects) */\n $$typeof?: symbol;\n\n /** Element type: string, component, Fragment, etc */\n type: unknown;\n\n /** Props bag */\n props: Props;\n\n /** Optional key (normalized by runtime) */\n key?: string | number | null;\n}\n","import { ELEMENT_TYPE, JSXElement } from './types';\n\nexport function isElement(value: unknown): value is JSXElement {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as JSXElement).$$typeof === ELEMENT_TYPE\n );\n}\n\nexport function cloneElement(\n element: JSXElement,\n props: Record<string, unknown>\n): JSXElement {\n return {\n ...element,\n props: { ...element.props, ...props },\n };\n}\n","import { logger } from '../dev/logger';\nimport { Fragment, cloneElement, isElement, ELEMENT_TYPE } from '../jsx';\nimport type { JSXElement } from '../jsx';\n\nexport type SlotProps =\n | {\n asChild: true;\n children: JSXElement;\n [key: string]: unknown;\n }\n | {\n asChild?: false;\n children?: unknown;\n };\n\nexport function Slot(props: SlotProps): JSXElement | null {\n if (props.asChild) {\n const { children, ...rest } = props;\n\n if (isElement(children)) {\n return cloneElement(children, rest);\n }\n\n logger.warn('<Slot asChild> expects a single JSX element child.');\n\n return null;\n }\n\n // Structural no-op: Slot does not introduce DOM\n // Return a vnode object for the fragment with the internal element marker.\n return {\n $$typeof: ELEMENT_TYPE,\n type: Fragment,\n props: { children: props.children },\n } as JSXElement;\n}\n","/**\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 { logger } from '../dev/logger';\nimport { __ASKR_incCounter, __ASKR_set } from '../renderer/diag';\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; // 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 state 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 _pendingReadStates?: Set<State<unknown>>; // States read during the in-progress render\n _lastReadStates?: Set<State<unknown>>; // States read during the last committed render\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}\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: new AbortController(), // Create per-component\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 _pendingReadStates: new Set(),\n _lastReadStates: new Set(),\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 instance._pendingFlushTask = () => {\n // Called by state.set() when we want to flush a pending update\n instance.hasPendingUpdate = false;\n // Trigger a run via enqueue helper — this will schedule the component run\n instance._enqueueRun?.();\n };\n\n return instance;\n}\n\nlet currentInstance: ComponentInstance | null = null;\nlet stateIndex = 0;\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 (process.env.NODE_ENV !== 'production') {\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 for (const operation of instance.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 (\n target as Element & { __ASKR_INSTANCE?: ComponentInstance }\n ).__ASKR_INSTANCE = instance;\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 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 = ++_globalRenderCounter;\n instance._pendingReadStates = new Set();\n\n // Atomic rendering: capture DOM state for rollback on error\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) return;\n } catch (err) {\n // If invariant check failed in dev, surface the error; otherwise fall back\n if (process.env.NODE_ENV !== 'production') 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 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\n // Set up instance for normal updates\n const oldInstance = currentInstance;\n currentInstance = instance;\n try {\n evaluate(result, host);\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 } finally {\n currentInstance = oldInstance;\n }\n return;\n }\n\n if (instance.target) {\n // Keep `oldChildren` in the outer scope so rollback handlers can\n // reference the original node list even if the inner try block\n // throws. This preserves listeners and instance backrefs on rollback.\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 // 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 evaluate(result, instance.target);\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 __ASKR_incCounter('__DOM_REPLACE_COUNT');\n __ASKR_set(\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\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 try {\n __ASKR_incCounter('__DOM_REPLACE_COUNT');\n __ASKR_set(\n '__LAST_DOM_REPLACE_STACK_COMPONENT_ROLLBACK',\n new Error().stack\n );\n } catch (e) {\n void e;\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._pendingReadStates;\n if (!hadToken) {\n instance._currentRenderToken = ++_globalRenderCounter;\n instance._pendingReadStates = new Set();\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._pendingReadStates = prevPendingReads ?? new Set();\n }\n}\n\nfunction executeComponentSync(\n instance: ComponentInstance\n): unknown | Promise<unknown> {\n // Reset state index tracking for this render\n instance.stateIndexCheck = -1;\n\n // Reset read tracking for all existing state\n for (const state of instance.stateValues) {\n if (state) {\n state._hasBeenRead = false;\n }\n }\n\n // Prepare pending read set for this render (reads will be finalized on commit)\n instance._pendingReadStates = new Set();\n\n currentInstance = instance;\n stateIndex = 0;\n\n try {\n // Track render time in dev mode\n const renderStartTime =\n process.env.NODE_ENV !== 'production' ? Date.now() : 0;\n\n // Create context object with abort signal\n const context = {\n signal: instance.abortController.signal,\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 // Warn if render takes more than 5ms\n logger.warn(\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 // Check for unused state\n for (let i = 0; i < instance.stateValues.length; i++) {\n const state = instance.stateValues[i];\n if (state && !state._hasBeenRead) {\n try {\n const name = instance.fn?.name || '<anonymous>';\n logger.warn(\n `[askr] Unused state variable detected in ${name} at index ${i}. State should be read during render or removed.`\n );\n } catch {\n logger.warn(\n `[askr] Unused state variable detected. State should be read during render or removed.`\n );\n }\n }\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 // Create a fresh abort controller on mount to allow remounting\n // (old one may have been aborted during previous cleanup)\n instance.abortController = new AbortController();\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(() => runComponent(instance));\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 currentInstance.abortController.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 const newSet = instance._pendingReadStates ?? new Set();\n const oldSet = instance._lastReadStates ?? new Set();\n const token = instance._currentRenderToken;\n\n if (token === undefined) return;\n\n // Remove subscriptions for states that were read previously but not in this render\n for (const s of oldSet) {\n if (!newSet.has(s)) {\n const readers = (s as State<unknown>)._readers;\n if (readers) readers.delete(instance);\n }\n }\n\n // Commit token becomes the authoritative token for this instance's last render\n instance.lastRenderToken = token;\n\n // Record subscriptions for states read during this render\n for (const s of newSet) {\n let readers = (s as State<unknown>)._readers;\n if (!readers) {\n readers = new Map();\n // s is a State object; assign its _readers map\n (s as State<unknown>)._readers = readers;\n }\n readers.set(instance, instance.lastRenderToken ?? 0);\n }\n\n instance._lastReadStates = newSet;\n instance._pendingReadStates = new Set();\n instance._currentRenderToken = undefined;\n}\n\nexport function getNextStateIndex(): number {\n return stateIndex++;\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 // Execute cleanup functions (from mount effects)\n const cleanupErrors: unknown[] = [];\n for (const cleanup of instance.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 (process.env.NODE_ENV !== 'production') {\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 if (instance._lastReadStates) {\n for (const s of instance._lastReadStates) {\n const readers = (s as State<unknown>)._readers;\n if (readers) readers.delete(instance);\n }\n instance._lastReadStates = new Set();\n }\n\n // Abort all pending operations\n instance.abortController.abort();\n\n // Clear update callback to prevent dangling references and stale updates\n instance.notifyUpdate = null;\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","/**\n * Portal / Host primitive.\n *\n * A portal is a named render slot within the existing tree.\n * It does NOT create a second tree or touch the DOM directly.\n */\n\nimport { getCurrentComponentInstance } from '../runtime/component';\nimport type { ComponentInstance } from '../runtime/component';\nimport { logger } from '../dev/logger';\n\nexport interface Portal<T = unknown> {\n /** Mount point — rendered exactly once */\n (): unknown;\n\n /** Render content into the portal */\n render(props: { children?: T }): unknown;\n}\n\nexport function definePortal<T = unknown>(): Portal<T> {\n // If the runtime primitive isn't installed yet, provide a no-op fallback.\n // Using `typeof createPortalSlot` is safe even if the identifier is not\n // defined at runtime (it returns 'undefined' rather than throwing).\n if (typeof createPortalSlot !== 'function') {\n // Fallback implementation for environments where the runtime primitive\n // isn't available (tests, SSR).\n //\n // Invariants this fallback tries to maintain:\n // - Always use the *current* host instance (update `owner` each render)\n // - Preserve the last `value` written before host mounts and expose it so\n // it can be flushed into a real portal if/when the runtime installs\n // - Schedule `owner.notifyUpdate()` when a host exists so updates are\n // reflected immediately\n // Fast fallback for module/SSR/test environments.\n // Track a single owner to avoid per-render array scans.\n let owner: ComponentInstance | null = null;\n let pending: T | undefined;\n\n function HostFallback() {\n // Drop owner + pending when owner unmounts to avoid replay.\n if (owner && owner.mounted === false) {\n owner = null;\n pending = undefined;\n }\n\n const inst = getCurrentComponentInstance();\n\n // Capture the first host as the owner.\n // We intentionally do NOT require `mounted === true` here because the\n // host can render before the runtime flips its mounted flag. Capturing\n // early ensures `DefaultPortal.render()` works immediately after mount.\n if (!owner && inst) owner = inst;\n\n /* istanbul ignore if */\n if (process.env.NODE_ENV !== 'production') {\n const ns =\n (globalThis as unknown as { __ASKR__?: Record<string, unknown> })\n .__ASKR__ ||\n ((\n globalThis as unknown as { __ASKR__?: Record<string, unknown> }\n ).__ASKR__ = {} as Record<string, unknown>);\n ns.__PORTAL_READS = ((ns.__PORTAL_READS as number) || 0) + 1;\n }\n\n /* istanbul ignore if */\n if (process.env.NODE_ENV !== 'production') {\n // Minimal dev diagnostics; avoid heavy allocations in the hot path.\n if (inst && owner && inst !== owner && inst.mounted === true) {\n logger.warn(\n '[Portal] multiple mounted hosts detected; first mounted host is owner'\n );\n }\n }\n\n return inst && owner && inst === owner ? (pending as unknown) : undefined;\n }\n\n HostFallback.render = function RenderFallback(props: { children?: T }) {\n // Owner must be fully mounted (mounted === true) to accept writes.\n if (!owner || owner.mounted !== true) return null;\n\n /* istanbul ignore if */\n if (process.env.NODE_ENV !== 'production') {\n const ns =\n (globalThis as unknown as { __ASKR__?: Record<string, unknown> })\n .__ASKR__ ||\n ((\n globalThis as unknown as { __ASKR__?: Record<string, unknown> }\n ).__ASKR__ = {} as Record<string, unknown>);\n ns.__PORTAL_WRITES = ((ns.__PORTAL_WRITES as number) || 0) + 1;\n }\n\n // Update pending value for the live owner\n pending = props.children as T | undefined;\n\n // Schedule an update on the owner so it re-renders\n if (owner.notifyUpdate) owner.notifyUpdate();\n return null;\n };\n\n return HostFallback as Portal<T>;\n }\n\n // Runtime-provided slot implementation\n const slot = createPortalSlot<T>();\n\n function PortalHost() {\n return slot.read();\n }\n\n PortalHost.render = function PortalRender(props: { children?: T }) {\n // Keep counter increment guarded for dev-only behavior\n /* istanbul ignore if */\n if (process.env.NODE_ENV !== 'production') {\n const ns =\n (globalThis as unknown as { __ASKR__?: Record<string, unknown> })\n .__ASKR__ ||\n ((\n globalThis as unknown as { __ASKR__?: Record<string, unknown> }\n ).__ASKR__ = {} as Record<string, unknown>);\n ns.__PORTAL_WRITES = ((ns.__PORTAL_WRITES as number) || 0) + 1;\n }\n slot.write(props.children);\n return null;\n };\n\n return PortalHost as Portal<T>;\n}\n\n// Default portal instance: lazily created wrapper so runtime primitive is not\n// invoked during module initialization (avoids ReferenceError when runtime\n// slot primitive is not yet installed).\nlet _defaultPortal: Portal<unknown> | undefined;\nlet _defaultPortalIsFallback = false;\n\n/**\n * Reset the default portal state. Used by tests to ensure isolation.\n * @internal\n */\nexport function _resetDefaultPortal(): void {\n _defaultPortal = undefined;\n _defaultPortalIsFallback = false;\n}\n\nfunction ensureDefaultPortal(): Portal<unknown> {\n // If a portal hasn't been initialized yet, create a real portal if the\n // runtime primitive exists; otherwise create a fallback. If a fallback\n // was previously created and the runtime primitive becomes available\n // later, replace the fallback with a real portal on first use.\n if (!_defaultPortal) {\n if (typeof createPortalSlot === 'function') {\n _defaultPortal = definePortal<unknown>();\n _defaultPortalIsFallback = false;\n } else {\n // Create a fallback via definePortal so it uses the same owner/pending\n // semantics as the non-default portals (keeps runtime and fallback\n // behavior consistent).\n _defaultPortal = definePortal<unknown>();\n _defaultPortalIsFallback = true;\n }\n return _defaultPortal;\n }\n\n // Replace fallback with real portal once runtime primitive becomes available\n // NOTE: We intentionally do NOT replay pending writes from a fallback.\n // Early writes are dropped by design to avoid replaying invisible UI.\n if (_defaultPortalIsFallback && typeof createPortalSlot === 'function') {\n const real = definePortal<unknown>();\n _defaultPortal = real;\n _defaultPortalIsFallback = false;\n }\n\n // If the runtime primitive is removed (tests may simulate this by\n // deleting `createPortalSlot` between runs), revert to a fallback so\n // subsequent tests observe the appropriate fallback semantics.\n if (!_defaultPortalIsFallback && typeof createPortalSlot !== 'function') {\n const fallback = definePortal<unknown>();\n _defaultPortal = fallback;\n _defaultPortalIsFallback = true;\n }\n\n return _defaultPortal;\n}\n\nexport const DefaultPortal: Portal<unknown> = (() => {\n function Host() {\n // Delegate to the lazily-created portal host (created when runtime is ready)\n // Return null when no pending value exists so the component renders nothing\n // (consistent with SSR which renders Fragment children as empty string)\n const v = ensureDefaultPortal()();\n return v === undefined ? null : v;\n }\n Host.render = function Render(props: { children?: unknown }) {\n ensureDefaultPortal().render(props);\n return null;\n };\n return Host as Portal<unknown>;\n})();\n\n/**\n * NOTE:\n * createPortalSlot is a runtime primitive.\n * It owns scheduling, consistency, and SSR behavior.\n */\ndeclare function createPortalSlot<T>(): {\n read(): unknown;\n write(value: T | undefined): void;\n};\n"]}