@askrjs/askr 0.0.42 → 0.0.44

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 (71) hide show
  1. package/dist/benchmark.js +2 -2
  2. package/dist/boot/index.d.ts.map +1 -1
  3. package/dist/boot/index.js +2 -0
  4. package/dist/boot/index.js.map +1 -1
  5. package/dist/common/route-activity.js +30 -0
  6. package/dist/common/route-activity.js.map +1 -0
  7. package/dist/common/router.d.ts +4 -2
  8. package/dist/common/router.d.ts.map +1 -1
  9. package/dist/common/ssr.d.ts +22 -1
  10. package/dist/common/ssr.d.ts.map +1 -1
  11. package/dist/common/ssr.js +9 -1
  12. package/dist/common/ssr.js.map +1 -1
  13. package/dist/data/index.d.ts +21 -2
  14. package/dist/data/index.d.ts.map +1 -1
  15. package/dist/data/index.js +56 -3
  16. package/dist/data/index.js.map +1 -1
  17. package/dist/data/invalidation-listeners.js +15 -0
  18. package/dist/data/invalidation-listeners.js.map +1 -0
  19. package/dist/renderer/dom.js +23 -15
  20. package/dist/renderer/dom.js.map +1 -1
  21. package/dist/renderer/evaluate.js +9 -13
  22. package/dist/renderer/evaluate.js.map +1 -1
  23. package/dist/renderer/for-commit.js +9 -0
  24. package/dist/renderer/for-commit.js.map +1 -1
  25. package/dist/resources/index.d.ts +2 -2
  26. package/dist/resources/index.js +2 -2
  27. package/dist/router/match.js +31 -6
  28. package/dist/router/match.js.map +1 -1
  29. package/dist/router/navigate.d.ts.map +1 -1
  30. package/dist/router/navigate.js +30 -4
  31. package/dist/router/navigate.js.map +1 -1
  32. package/dist/router/route.d.ts +10 -1
  33. package/dist/router/route.d.ts.map +1 -1
  34. package/dist/router/route.js +38 -8
  35. package/dist/router/route.js.map +1 -1
  36. package/dist/runtime/component.d.ts +7 -1
  37. package/dist/runtime/component.d.ts.map +1 -1
  38. package/dist/runtime/component.js +205 -44
  39. package/dist/runtime/component.js.map +1 -1
  40. package/dist/runtime/fastlane.js +6 -0
  41. package/dist/runtime/fastlane.js.map +1 -1
  42. package/dist/runtime/operations.d.ts +11 -3
  43. package/dist/runtime/operations.d.ts.map +1 -1
  44. package/dist/runtime/operations.js +161 -22
  45. package/dist/runtime/operations.js.map +1 -1
  46. package/dist/runtime/readable.d.ts +1 -0
  47. package/dist/runtime/readable.d.ts.map +1 -1
  48. package/dist/runtime/readable.js +7 -4
  49. package/dist/runtime/readable.js.map +1 -1
  50. package/dist/ssg/batch-render.js +27 -5
  51. package/dist/ssg/batch-render.js.map +1 -1
  52. package/dist/ssg/create-static-gen.d.ts.map +1 -1
  53. package/dist/ssg/create-static-gen.js +6 -4
  54. package/dist/ssg/create-static-gen.js.map +1 -1
  55. package/dist/ssg/index.d.ts +2 -1
  56. package/dist/ssg/resolve-ssg-data.js +18 -1
  57. package/dist/ssg/resolve-ssg-data.js.map +1 -1
  58. package/dist/ssg/types.d.ts +3 -0
  59. package/dist/ssg/types.d.ts.map +1 -1
  60. package/dist/ssr/context.d.ts +1 -2
  61. package/dist/ssr/context.d.ts.map +1 -1
  62. package/dist/ssr/index.d.ts +10 -11
  63. package/dist/ssr/index.d.ts.map +1 -1
  64. package/dist/ssr/index.js +104 -68
  65. package/dist/ssr/index.js.map +1 -1
  66. package/dist/ssr/render-resolved.d.ts +1 -1
  67. package/dist/testing/index.d.ts +53 -0
  68. package/dist/testing/index.d.ts.map +1 -0
  69. package/dist/testing/index.js +172 -0
  70. package/dist/testing/index.js.map +1 -0
  71. package/package.json +12 -4
@@ -1 +1 @@
1
- {"version":3,"file":"fastlane.js","names":[],"sources":["../../src/runtime/fastlane.ts"],"sourcesContent":["import { isDevelopmentEnvironment } from '../common/env';\nimport { globalScheduler } from './scheduler';\nimport { logger } from '../dev/logger';\nimport type { ComponentInstance } from './component';\nimport { finalizeReadableSubscriptions } from './readable';\nimport {\n getKeyMapForElement,\n isKeyedReorderFastPathEligible,\n populateKeyMapForElement,\n} from '../renderer/keyed';\nimport { Fragment } from '../common/jsx';\nimport { setDevValue, getDevValue, getDevNamespace } from './dev-namespace';\n\nlet _bulkCommitActive = false;\nlet _appliedParents: WeakSet<Element> | null = null;\n\nexport function enterBulkCommit(): void {\n _bulkCommitActive = true;\n // Initialize registry of parents that had fast-path applied during this bulk commit\n _appliedParents = new WeakSet<Element>();\n setDevValue('__ASKR_FASTLANE_CLEARED_TASKS', 0);\n}\n\nexport function exitBulkCommit(): void {\n _bulkCommitActive = false;\n // Clear registry to avoid leaking across commits\n _appliedParents = null;\n}\n\nexport function isBulkCommitActive(): boolean {\n return _bulkCommitActive;\n}\n\n// Mark that a fast-path was applied on a parent element during the active\n// bulk commit. No-op if there is no active bulk commit.\nexport function markFastPathApplied(parent: Element): void {\n if (!_appliedParents) return;\n try {\n _appliedParents.add(parent);\n } catch (e) {\n void e;\n }\n}\n\nexport function isFastPathApplied(parent: Element): boolean {\n return !!(_appliedParents && _appliedParents.has(parent));\n}\n\nfunction finalizeReadSubscriptions(instance: ComponentInstance): void {\n finalizeReadableSubscriptions(instance);\n}\n\n/**\n * Attempt to execute a runtime fast-lane for a single component's synchronous\n * render result. Returns true if the fast-lane was used and commit was done.\n *\n * Preconditions (checked conservatively):\n * - The render result is an intrinsic element root with keyed children\n * - The renderer's fast-path heuristics indicate to use the fast-path\n * - No mount operations are pending on the component instance\n * - No child vnodes are component functions (avoid async/component mounts)\n */\n\n// Helper to unwrap Fragment vnodes to get the first intrinsic element child\nfunction unwrapFragmentForFastPath(vnode: unknown): unknown {\n if (!vnode || typeof vnode !== 'object' || !('type' in vnode)) return vnode;\n const v = vnode as {\n type: unknown;\n children?: unknown;\n props?: { children?: unknown };\n };\n // Check if it's a Fragment\n if (\n typeof v.type === 'symbol' &&\n (v.type === Fragment || String(v.type) === 'Symbol(askr.fragment)')\n ) {\n const children = v.props?.children ?? v.children;\n if (Array.isArray(children) && children.length > 0) {\n // Return the first child that's an intrinsic element\n for (const child of children) {\n if (child && typeof child === 'object' && 'type' in child) {\n const c = child as { type: unknown };\n if (typeof c.type === 'string') {\n return child;\n }\n }\n }\n }\n }\n return vnode;\n}\n\nexport function classifyUpdate(instance: ComponentInstance, result: unknown) {\n // Returns a classification describing whether this update is eligible for\n // the reorder-only fast-lane. The classifier mirrors renderer-level\n // heuristics and performs runtime-level checks (mounts, effects, component\n // children) that the renderer cannot reason about.\n\n // Unwrap Fragment to get the actual element vnode for classification\n const unwrappedResult = unwrapFragmentForFastPath(result);\n\n if (\n !unwrappedResult ||\n typeof unwrappedResult !== 'object' ||\n !('type' in unwrappedResult)\n )\n return { useFastPath: false, reason: 'not-vnode' };\n\n const vnode = unwrappedResult as {\n type: unknown;\n children?: unknown;\n props?: { children?: unknown };\n };\n if (vnode == null || typeof vnode.type !== 'string')\n return { useFastPath: false, reason: 'not-intrinsic' };\n\n const parent = instance.target;\n if (!parent) return { useFastPath: false, reason: 'no-root' };\n\n const firstChild = parent.children[0] as Element | undefined;\n if (!firstChild) return { useFastPath: false, reason: 'no-first-child' };\n if (firstChild.tagName.toLowerCase() !== String(vnode.type).toLowerCase())\n return { useFastPath: false, reason: 'root-tag-mismatch' };\n\n const children = vnode.props?.children ?? vnode.children;\n if (!Array.isArray(children))\n return { useFastPath: false, reason: 'no-children-array' };\n\n // Avoid component child vnodes (they may mount/unmount or trigger async)\n for (const c of children) {\n if (\n typeof c === 'object' &&\n c !== null &&\n 'type' in c &&\n typeof (c as { type?: unknown }).type === 'function'\n ) {\n return { useFastPath: false, reason: 'component-child-present' };\n }\n }\n\n if ((instance.mountOperations?.length ?? 0) > 0)\n return { useFastPath: false, reason: 'pending-mounts' };\n\n // Ask renderer for keyed reorder eligibility (prop differences & heuristics)\n // Ensure a keyed map is available for the first child by populating it proactively.\n try {\n populateKeyMapForElement(firstChild);\n } catch {\n // ignore\n }\n\n const oldKeyMap = getKeyMapForElement(firstChild);\n const decision = isKeyedReorderFastPathEligible(\n firstChild,\n children,\n oldKeyMap\n );\n\n if (!decision.useFastPath || decision.totalKeyed < 128)\n return { ...decision, useFastPath: false, reason: 'renderer-declined' };\n\n return { ...decision, useFastPath: true } as const;\n}\n\nexport function commitReorderOnly(\n instance: ComponentInstance,\n result: unknown\n): boolean {\n // Performs the minimal, synchronous reorder-only commit.\n const evaluate = (\n globalThis as {\n __ASKR_RENDERER?: {\n evaluate?: (node: unknown, target: Element | null) => void;\n };\n }\n ).__ASKR_RENDERER?.evaluate;\n\n if (typeof evaluate !== 'function') {\n logger.warn(\n '[Tempo][FASTPATH][DEV] renderer.evaluate not available; declining fast-lane'\n );\n return false;\n }\n\n const schedBefore = isDevelopmentEnvironment()\n ? globalScheduler.getState()\n : null;\n\n enterBulkCommit();\n\n try {\n globalScheduler.runWithSyncProgress(() => {\n evaluate(result, instance.target);\n\n // Finalize runtime bookkeeping (read subscriptions / tokens)\n try {\n finalizeReadSubscriptions(instance);\n } catch (e) {\n if (isDevelopmentEnvironment()) throw e;\n }\n });\n\n setDevValue('__FASTLANE_CLEARED_AFTER', 0);\n\n // Dev-only invariant checks\n if (isDevelopmentEnvironment()) {\n validateFastLaneInvariants(instance, schedBefore);\n }\n\n return true;\n } finally {\n // Clear bulk commit flag first\n exitBulkCommit();\n }\n // Note: The original code had a check here that was dead code because\n // exitBulkCommit() already ran in the finally block. This comment serves\n // as documentation that we've intentionally removed that unreachable code.\n}\n\n/**\n * Validates fast-lane invariants in dev mode.\n * Extracted to reduce complexity in commitReorderOnly.\n */\nfunction validateFastLaneInvariants(\n instance: ComponentInstance,\n schedBefore: ReturnType<typeof globalScheduler.getState> | null\n): void {\n const commitCount = getDevValue<number>('__LAST_FASTPATH_COMMIT_COUNT') ?? 0;\n const invariants = {\n commitCount,\n mountOps: instance.mountOperations?.length ?? 0,\n cleanupFns: instance.cleanupFns?.length ?? 0,\n };\n setDevValue('__LAST_FASTLANE_INVARIANTS', invariants);\n\n if (commitCount !== 1) {\n console.error(\n '[FASTLANE][INV] commitCount',\n commitCount,\n 'diag',\n getDevNamespace()\n );\n throw new Error(\n 'Fast-lane invariant violated: expected exactly one DOM commit during reorder-only commit'\n );\n }\n\n if (invariants.mountOps > 0) {\n throw new Error(\n 'Fast-lane invariant violated: mount operations were registered during bulk commit'\n );\n }\n\n if (invariants.cleanupFns > 0) {\n throw new Error(\n 'Fast-lane invariant violated: cleanup functions were added during bulk commit'\n );\n }\n\n const schedAfter = globalScheduler.getState();\n if (\n schedBefore &&\n schedAfter &&\n schedAfter.taskCount > schedBefore.taskCount\n ) {\n console.error(\n '[FASTLANE] schedBefore, schedAfter',\n schedBefore,\n schedAfter\n );\n console.error('[FASTLANE] enqueue logs', getDevValue('__ENQUEUE_LOGS'));\n throw new Error(\n 'Fast-lane invariant violated: scheduler enqueued leftover work during bulk commit'\n );\n }\n}\n\nexport function tryRuntimeFastLaneSync(\n instance: ComponentInstance,\n result: unknown\n): boolean {\n const cls = classifyUpdate(instance, result);\n if (!cls.useFastPath) {\n // Clear stale fast-path diagnostics\n setDevValue('__LAST_FASTPATH_STATS', undefined);\n setDevValue('__LAST_FASTPATH_COMMIT_COUNT', 0);\n return false;\n }\n\n try {\n return commitReorderOnly(instance, result);\n } catch (err) {\n // Surface dev-only invariant failures, otherwise decline silently\n if (isDevelopmentEnvironment()) throw err;\n return false;\n }\n}\n\n// Expose fastlane bridge on globalThis for environments/tests\nif (typeof globalThis !== 'undefined') {\n (globalThis as Record<string, unknown>).__ASKR_FASTLANE = {\n isBulkCommitActive,\n enterBulkCommit,\n exitBulkCommit,\n tryRuntimeFastLaneSync,\n markFastPathApplied,\n isFastPathApplied,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"fastlane.js","names":[],"sources":["../../src/runtime/fastlane.ts"],"sourcesContent":["import { isDevelopmentEnvironment } from '../common/env';\nimport { globalScheduler } from './scheduler';\nimport { logger } from '../dev/logger';\nimport type { ComponentInstance } from './component';\nimport { finalizeReadableSubscriptions } from './readable';\nimport {\n getKeyMapForElement,\n isKeyedReorderFastPathEligible,\n populateKeyMapForElement,\n} from '../renderer/keyed';\nimport { Fragment } from '../common/jsx';\nimport { setDevValue, getDevValue, getDevNamespace } from './dev-namespace';\n\nlet _bulkCommitActive = false;\nlet _appliedParents: WeakSet<Element> | null = null;\n\nexport function enterBulkCommit(): void {\n _bulkCommitActive = true;\n // Initialize registry of parents that had fast-path applied during this bulk commit\n _appliedParents = new WeakSet<Element>();\n setDevValue('__ASKR_FASTLANE_CLEARED_TASKS', 0);\n}\n\nexport function exitBulkCommit(): void {\n _bulkCommitActive = false;\n // Clear registry to avoid leaking across commits\n _appliedParents = null;\n}\n\nexport function isBulkCommitActive(): boolean {\n return _bulkCommitActive;\n}\n\n// Mark that a fast-path was applied on a parent element during the active\n// bulk commit. No-op if there is no active bulk commit.\nexport function markFastPathApplied(parent: Element): void {\n if (!_appliedParents) return;\n try {\n _appliedParents.add(parent);\n } catch (e) {\n void e;\n }\n}\n\nexport function isFastPathApplied(parent: Element): boolean {\n return !!(_appliedParents && _appliedParents.has(parent));\n}\n\nfunction finalizeReadSubscriptions(instance: ComponentInstance): void {\n finalizeReadableSubscriptions(instance);\n}\n\n/**\n * Attempt to execute a runtime fast-lane for a single component's synchronous\n * render result. Returns true if the fast-lane was used and commit was done.\n *\n * Preconditions (checked conservatively):\n * - The render result is an intrinsic element root with keyed children\n * - The renderer's fast-path heuristics indicate to use the fast-path\n * - No mount operations are pending on the component instance\n * - No child vnodes are component functions (avoid async/component mounts)\n */\n\n// Helper to unwrap Fragment vnodes to get the first intrinsic element child\nfunction unwrapFragmentForFastPath(vnode: unknown): unknown {\n if (!vnode || typeof vnode !== 'object' || !('type' in vnode)) return vnode;\n const v = vnode as {\n type: unknown;\n children?: unknown;\n props?: { children?: unknown };\n };\n // Check if it's a Fragment\n if (\n typeof v.type === 'symbol' &&\n (v.type === Fragment || String(v.type) === 'Symbol(askr.fragment)')\n ) {\n const children = v.props?.children ?? v.children;\n if (Array.isArray(children) && children.length > 0) {\n // Return the first child that's an intrinsic element\n for (const child of children) {\n if (child && typeof child === 'object' && 'type' in child) {\n const c = child as { type: unknown };\n if (typeof c.type === 'string') {\n return child;\n }\n }\n }\n }\n }\n return vnode;\n}\n\nexport function classifyUpdate(instance: ComponentInstance, result: unknown) {\n // Returns a classification describing whether this update is eligible for\n // the reorder-only fast-lane. The classifier mirrors renderer-level\n // heuristics and performs runtime-level checks (mounts, effects, component\n // children) that the renderer cannot reason about.\n\n // Unwrap Fragment to get the actual element vnode for classification\n const unwrappedResult = unwrapFragmentForFastPath(result);\n\n if (\n !unwrappedResult ||\n typeof unwrappedResult !== 'object' ||\n !('type' in unwrappedResult)\n )\n return { useFastPath: false, reason: 'not-vnode' };\n\n const vnode = unwrappedResult as {\n type: unknown;\n children?: unknown;\n props?: { children?: unknown };\n };\n if (vnode == null || typeof vnode.type !== 'string')\n return { useFastPath: false, reason: 'not-intrinsic' };\n\n const parent = instance.target;\n if (!parent) return { useFastPath: false, reason: 'no-root' };\n\n const firstChild = parent.children[0] as Element | undefined;\n if (!firstChild) return { useFastPath: false, reason: 'no-first-child' };\n if (firstChild.tagName.toLowerCase() !== String(vnode.type).toLowerCase())\n return { useFastPath: false, reason: 'root-tag-mismatch' };\n\n const children = vnode.props?.children ?? vnode.children;\n if (!Array.isArray(children))\n return { useFastPath: false, reason: 'no-children-array' };\n\n // Avoid component child vnodes (they may mount/unmount or trigger async)\n for (const c of children) {\n if (\n typeof c === 'object' &&\n c !== null &&\n 'type' in c &&\n typeof (c as { type?: unknown }).type === 'function'\n ) {\n return { useFastPath: false, reason: 'component-child-present' };\n }\n }\n\n if ((instance.mountOperations?.length ?? 0) > 0)\n return { useFastPath: false, reason: 'pending-mounts' };\n if ((instance.commitOperations?.length ?? 0) > 0)\n return { useFastPath: false, reason: 'pending-lifecycle-commits' };\n\n // Ask renderer for keyed reorder eligibility (prop differences & heuristics)\n // Ensure a keyed map is available for the first child by populating it proactively.\n try {\n populateKeyMapForElement(firstChild);\n } catch {\n // ignore\n }\n\n const oldKeyMap = getKeyMapForElement(firstChild);\n const decision = isKeyedReorderFastPathEligible(\n firstChild,\n children,\n oldKeyMap\n );\n\n if (!decision.useFastPath || decision.totalKeyed < 128)\n return { ...decision, useFastPath: false, reason: 'renderer-declined' };\n\n return { ...decision, useFastPath: true } as const;\n}\n\nexport function commitReorderOnly(\n instance: ComponentInstance,\n result: unknown\n): boolean {\n // Performs the minimal, synchronous reorder-only commit.\n const evaluate = (\n globalThis as {\n __ASKR_RENDERER?: {\n evaluate?: (node: unknown, target: Element | null) => void;\n };\n }\n ).__ASKR_RENDERER?.evaluate;\n\n if (typeof evaluate !== 'function') {\n logger.warn(\n '[Tempo][FASTPATH][DEV] renderer.evaluate not available; declining fast-lane'\n );\n return false;\n }\n\n const schedBefore = isDevelopmentEnvironment()\n ? globalScheduler.getState()\n : null;\n\n enterBulkCommit();\n\n try {\n globalScheduler.runWithSyncProgress(() => {\n evaluate(result, instance.target);\n\n // Finalize runtime bookkeeping (read subscriptions / tokens)\n try {\n finalizeReadSubscriptions(instance);\n } catch (e) {\n if (isDevelopmentEnvironment()) throw e;\n }\n });\n\n setDevValue('__FASTLANE_CLEARED_AFTER', 0);\n\n // Dev-only invariant checks\n if (isDevelopmentEnvironment()) {\n validateFastLaneInvariants(instance, schedBefore);\n }\n\n return true;\n } finally {\n // Clear bulk commit flag first\n exitBulkCommit();\n }\n // Note: The original code had a check here that was dead code because\n // exitBulkCommit() already ran in the finally block. This comment serves\n // as documentation that we've intentionally removed that unreachable code.\n}\n\n/**\n * Validates fast-lane invariants in dev mode.\n * Extracted to reduce complexity in commitReorderOnly.\n */\nfunction validateFastLaneInvariants(\n instance: ComponentInstance,\n schedBefore: ReturnType<typeof globalScheduler.getState> | null\n): void {\n const commitCount = getDevValue<number>('__LAST_FASTPATH_COMMIT_COUNT') ?? 0;\n const invariants = {\n commitCount,\n mountOps: instance.mountOperations?.length ?? 0,\n commitOps: instance.commitOperations?.length ?? 0,\n cleanupFns: instance.cleanupFns?.length ?? 0,\n };\n setDevValue('__LAST_FASTLANE_INVARIANTS', invariants);\n\n if (commitCount !== 1) {\n console.error(\n '[FASTLANE][INV] commitCount',\n commitCount,\n 'diag',\n getDevNamespace()\n );\n throw new Error(\n 'Fast-lane invariant violated: expected exactly one DOM commit during reorder-only commit'\n );\n }\n\n if (invariants.mountOps > 0) {\n throw new Error(\n 'Fast-lane invariant violated: mount operations were registered during bulk commit'\n );\n }\n\n if (invariants.commitOps > 0) {\n throw new Error(\n 'Fast-lane invariant violated: lifecycle commit operations were registered during bulk commit'\n );\n }\n\n if (invariants.cleanupFns > 0) {\n throw new Error(\n 'Fast-lane invariant violated: cleanup functions were added during bulk commit'\n );\n }\n\n const schedAfter = globalScheduler.getState();\n if (\n schedBefore &&\n schedAfter &&\n schedAfter.taskCount > schedBefore.taskCount\n ) {\n console.error(\n '[FASTLANE] schedBefore, schedAfter',\n schedBefore,\n schedAfter\n );\n console.error('[FASTLANE] enqueue logs', getDevValue('__ENQUEUE_LOGS'));\n throw new Error(\n 'Fast-lane invariant violated: scheduler enqueued leftover work during bulk commit'\n );\n }\n}\n\nexport function tryRuntimeFastLaneSync(\n instance: ComponentInstance,\n result: unknown\n): boolean {\n const cls = classifyUpdate(instance, result);\n if (!cls.useFastPath) {\n // Clear stale fast-path diagnostics\n setDevValue('__LAST_FASTPATH_STATS', undefined);\n setDevValue('__LAST_FASTPATH_COMMIT_COUNT', 0);\n return false;\n }\n\n try {\n return commitReorderOnly(instance, result);\n } catch (err) {\n // Surface dev-only invariant failures, otherwise decline silently\n if (isDevelopmentEnvironment()) throw err;\n return false;\n }\n}\n\n// Expose fastlane bridge on globalThis for environments/tests\nif (typeof globalThis !== 'undefined') {\n (globalThis as Record<string, unknown>).__ASKR_FASTLANE = {\n isBulkCommitActive,\n enterBulkCommit,\n exitBulkCommit,\n tryRuntimeFastLaneSync,\n markFastPathApplied,\n isFastPathApplied,\n };\n}\n"],"mappings":"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"}
@@ -5,11 +5,19 @@ interface ResourceResult<T> {
5
5
  error: Error | null;
6
6
  refresh(): void;
7
7
  }
8
+ type ActivityPredicate = () => boolean;
9
+ interface TimerOptions {
10
+ when?: ActivityPredicate | readonly ActivityPredicate[];
11
+ }
12
+ declare function routeActive(pathOrPaths: string | readonly string[]): ActivityPredicate;
13
+ declare function documentVisible(): ActivityPredicate;
14
+ declare function windowFocused(): ActivityPredicate;
8
15
  declare function resource<T, const TDeps extends readonly unknown[]>(fn: (opts: {
9
16
  signal: AbortSignal;
10
17
  }) => PromiseLike<T> | T, deps: TDeps): ResourceResult<T>;
11
- declare function on(target: EventTarget, event: string, handler: EventListener): void;
12
- declare function timer(intervalMs: number, fn: () => void): void;
18
+ type ListenerOptions = boolean | AddEventListenerOptions | undefined;
19
+ declare function on(target: EventTarget, event: string, handler: EventListener, options?: ListenerOptions): void;
20
+ declare function timer(intervalMs: number, fn: () => void, options?: TimerOptions): void;
13
21
  declare function stream<T>(_source: unknown, _options?: Record<string, unknown>): {
14
22
  value: T | null;
15
23
  pending: boolean;
@@ -28,5 +36,5 @@ declare function task(fn: () => void | (() => void) | PromiseLike<void | (() =>
28
36
  */
29
37
  declare function capture<T>(fn: () => T): () => T;
30
38
  //#endregion
31
- export { ResourceResult, capture, on, resource, stream, task, timer };
39
+ export { ActivityPredicate, ResourceResult, TimerOptions, capture, documentVisible, on, resource, routeActive, stream, task, timer, windowFocused };
32
40
  //# sourceMappingURL=operations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"operations.d.ts","names":[],"sources":["../../src/runtime/operations.ts"],"mappings":";UAaiB,cAAA;EACf,KAAA,EAAO,CAAA;EACP,OAAA;EACA,KAAA,EAAO,KAAK;EACZ,OAAA;AAAA;AAAA,iBAGc,QAAA,4CACd,EAAA,GAAK,IAAA;EAAQ,MAAA,EAAQ,WAAA;AAAA,MAAkB,WAAA,CAAY,CAAA,IAAK,CAAA,EACxD,IAAA,EAAM,KAAA,GACL,cAAA,CAAe,CAAA;AAAA,iBAwPF,EAAA,CACd,MAAA,EAAQ,WAAA,EACR,KAAA,UACA,OAAA,EAAS,aAAa;AAAA,iBAiBR,KAAA,CAAM,UAAA,UAAoB,EAAc;AAAA,iBAgBxC,MAAA,IACd,OAAA,WACA,QAAA,GAAW,MAAA;EACR,KAAA,EAAO,CAAA;EAAU,OAAA;EAAkB,KAAA,EAAO,KAAA;AAAA;AAAA,iBAK/B,IAAA,CACd,EAAgE,8BAAhC,WAAW;;;;;;;;;;;iBAwB7B,OAAA,IAAW,EAAA,QAAU,CAAA,SAAU,CAAC"}
1
+ {"version":3,"file":"operations.d.ts","names":[],"sources":["../../src/runtime/operations.ts"],"mappings":";UAeiB,cAAA;EACf,KAAA,EAAO,CAAA;EACP,OAAA;EACA,KAAA,EAAO,KAAK;EACZ,OAAA;AAAA;AAAA,KAGU,iBAAA;AAAA,UAEK,YAAA;EACf,IAAA,GAAO,iBAAA,YAA6B,iBAAiB;AAAA;AAAA,iBAsDvC,WAAA,CACd,WAAA,+BACC,iBAAiB;AAAA,iBAIJ,eAAA,IAAmB,iBAAiB;AAAA,iBAKpC,aAAA,IAAiB,iBAAiB;AAAA,iBAOlC,QAAA,4CACd,EAAA,GAAK,IAAA;EAAQ,MAAA,EAAQ,WAAA;AAAA,MAAkB,WAAA,CAAY,CAAA,IAAK,CAAA,EACxD,IAAA,EAAM,KAAA,GACL,cAAA,CAAe,CAAA;AAAA,KAwPb,eAAA,aAA4B,uBAAuB;AAAA,iBAuGxC,EAAA,CACd,MAAA,EAAQ,WAAA,EACR,KAAA,UACA,OAAA,EAAS,aAAA,EACT,OAAA,GAAU,eAAA;AAAA,iBA8FI,KAAA,CACd,UAAA,UACA,EAAA,cACA,OAAA,GAAU,YAAY;AAAA,iBA8BR,MAAA,IACd,OAAA,WACA,QAAA,GAAW,MAAA;EACR,KAAA,EAAO,CAAA;EAAU,OAAA;EAAkB,KAAA,EAAO,KAAA;AAAA;AAAA,iBAK/B,IAAA,CACd,EAAgE,8BAAhC,WAAW;;;AAtjBU;AAsDvD;;;;AAEoB;AAIpB;;iBAqiBgB,OAAA,IAAW,EAAA,QAAU,CAAA,SAAU,CAAC"}
@@ -1,12 +1,41 @@
1
1
  import { globalScheduler } from "./scheduler.js";
2
2
  import { getCurrentContextFrame } from "./context.js";
3
- import { getCurrentComponentInstance, registerMountOperation } from "./component.js";
3
+ import { claimHookIndex, getCurrentComponentInstance, registerCommitOperation } from "./component.js";
4
4
  import { state } from "./state.js";
5
5
  import { brandSnapshotSource } from "./snapshot-source.js";
6
6
  import { SSRDataMissingError } from "../common/ssr-errors.js";
7
+ import { isRouteActivityActive } from "../common/route-activity.js";
7
8
  import { getSSRBridge } from "./ssr-bridge.js";
8
9
  import { ResourceCell } from "./resource-cell.js";
9
10
  //#region src/runtime/operations.ts
11
+ function normalizePredicates(predicates) {
12
+ if (!predicates) return [];
13
+ return typeof predicates === "function" ? [predicates] : predicates;
14
+ }
15
+ function allPredicatesPass(predicates) {
16
+ for (const predicate of predicates) if (!predicate()) return false;
17
+ return true;
18
+ }
19
+ function getLifecycleSlot(instance, index, kind, create) {
20
+ const existing = instance.lifecycleSlots[index];
21
+ if (existing) {
22
+ const slot = existing;
23
+ if (slot.kind !== kind) throw new Error(`${kind}() lifecycle order violation: slot ${index} already belongs to ${slot.kind}(). Keep lifecycle primitives in a stable top-level order.`);
24
+ return existing;
25
+ }
26
+ const slot = create();
27
+ instance.lifecycleSlots[index] = slot;
28
+ return slot;
29
+ }
30
+ function routeActive(pathOrPaths) {
31
+ return () => isRouteActivityActive(pathOrPaths);
32
+ }
33
+ function documentVisible() {
34
+ return () => typeof document === "undefined" || document.visibilityState !== "hidden";
35
+ }
36
+ function windowFocused() {
37
+ return () => typeof document === "undefined" || typeof document.hasFocus !== "function" || document.hasFocus();
38
+ }
10
39
  /**
11
40
  * Resource primitive — simple, deterministic async primitive
12
41
  * Usage: resource(fn, deps)
@@ -165,24 +194,124 @@ function resource(fn, deps = []) {
165
194
  }
166
195
  return h.snapshot;
167
196
  }
168
- function on(target, event, handler) {
169
- const ownerIsRoot = getCurrentComponentInstance()?.isRoot ?? false;
170
- registerMountOperation(() => {
171
- if (!ownerIsRoot) throw new Error("[Askr] on() may only be used in root components");
172
- target.addEventListener(event, handler);
173
- return () => {
174
- target.removeEventListener(event, handler);
197
+ function normalizeListenerOptions(options) {
198
+ if (options === void 0 || typeof options === "boolean") return options;
199
+ return {
200
+ ...options.capture !== void 0 ? { capture: options.capture } : {},
201
+ ...options.once !== void 0 ? { once: options.once } : {},
202
+ ...options.passive !== void 0 ? { passive: options.passive } : {},
203
+ ...options.signal !== void 0 ? { signal: options.signal } : {}
204
+ };
205
+ }
206
+ function listenerOptionsEqual(a, b) {
207
+ if (a === b) return true;
208
+ if (typeof a === "boolean" || typeof b === "boolean") return a === b;
209
+ if (!a || !b) return a === b;
210
+ return a.capture === b.capture && a.once === b.once && a.passive === b.passive && a.signal === b.signal;
211
+ }
212
+ function detachListenerSlot(slot) {
213
+ if (!slot.attached || !slot.target) return;
214
+ slot.target.removeEventListener(slot.event, slot.listener, slot.options);
215
+ slot.attached = false;
216
+ }
217
+ function commitListenerSlot(instance, slot) {
218
+ slot.handler = slot.pendingHandler;
219
+ if (!slot.attached || slot.target !== slot.pendingTarget || slot.event !== slot.pendingEvent || !listenerOptionsEqual(slot.options, slot.pendingOptions)) {
220
+ detachListenerSlot(slot);
221
+ slot.target = slot.pendingTarget;
222
+ slot.event = slot.pendingEvent;
223
+ slot.options = slot.pendingOptions;
224
+ slot.target.addEventListener(slot.event, slot.listener, slot.options);
225
+ slot.attached = true;
226
+ }
227
+ if (!slot.cleanupRegistered) {
228
+ slot.cleanupRegistered = true;
229
+ instance.cleanupFns.push(() => {
230
+ detachListenerSlot(slot);
231
+ slot.cleanupRegistered = false;
232
+ });
233
+ }
234
+ }
235
+ function on(target, event, handler, options) {
236
+ const instance = getCurrentComponentInstance();
237
+ if (!instance) return;
238
+ const index = claimHookIndex(instance, "on");
239
+ const normalizedOptions = normalizeListenerOptions(options);
240
+ const slot = getLifecycleSlot(instance, index, "listener", () => {
241
+ const createdSlot = {
242
+ kind: "listener",
243
+ target: null,
244
+ event,
245
+ handler,
246
+ listener: ((evt) => {
247
+ createdSlot.handler.call(createdSlot.target, evt);
248
+ }),
249
+ options: void 0,
250
+ pendingTarget: target,
251
+ pendingEvent: event,
252
+ pendingHandler: handler,
253
+ pendingOptions: normalizedOptions,
254
+ attached: false,
255
+ cleanupRegistered: false
175
256
  };
257
+ return createdSlot;
258
+ });
259
+ slot.pendingTarget = target;
260
+ slot.pendingEvent = event;
261
+ slot.pendingHandler = handler;
262
+ slot.pendingOptions = normalizedOptions;
263
+ registerCommitOperation(() => {
264
+ commitListenerSlot(instance, slot);
176
265
  });
177
266
  }
178
- function timer(intervalMs, fn) {
179
- const ownerIsRoot = getCurrentComponentInstance()?.isRoot ?? false;
180
- registerMountOperation(() => {
181
- if (!ownerIsRoot) throw new Error("[Askr] timer() may only be used in root components");
182
- const id = setInterval(fn, intervalMs);
183
- return () => {
184
- clearInterval(id);
185
- };
267
+ function stopTimerSlot(slot) {
268
+ if (slot.id === null) return;
269
+ clearInterval(slot.id);
270
+ slot.id = null;
271
+ }
272
+ function startTimerSlot(slot) {
273
+ slot.id = setInterval(() => {
274
+ if (allPredicatesPass(slot.predicates)) slot.callback();
275
+ }, slot.intervalMs ?? slot.pendingIntervalMs);
276
+ }
277
+ function commitTimerSlot(instance, slot) {
278
+ const intervalChanged = slot.intervalMs !== slot.pendingIntervalMs;
279
+ slot.callback = slot.pendingCallback;
280
+ slot.predicates = slot.pendingPredicates;
281
+ if (slot.id === null || intervalChanged) {
282
+ stopTimerSlot(slot);
283
+ slot.intervalMs = slot.pendingIntervalMs;
284
+ startTimerSlot(slot);
285
+ }
286
+ if (!slot.cleanupRegistered) {
287
+ slot.cleanupRegistered = true;
288
+ instance.cleanupFns.push(() => {
289
+ stopTimerSlot(slot);
290
+ slot.cleanupRegistered = false;
291
+ });
292
+ }
293
+ }
294
+ function timer(intervalMs, fn, options) {
295
+ const instance = getCurrentComponentInstance();
296
+ if (!instance) return;
297
+ const index = claimHookIndex(instance, "timer");
298
+ const predicates = normalizePredicates(options?.when);
299
+ const slot = getLifecycleSlot(instance, index, "timer", () => ({
300
+ kind: "timer",
301
+ id: null,
302
+ intervalMs: null,
303
+ pendingIntervalMs: intervalMs,
304
+ callback: fn,
305
+ predicates,
306
+ pendingCallback: fn,
307
+ pendingPredicates: predicates,
308
+ cleanupRegistered: false
309
+ }));
310
+ slot.pendingIntervalMs = intervalMs;
311
+ slot.pendingCallback = fn;
312
+ slot.pendingPredicates = predicates;
313
+ registerCommitOperation(() => {
314
+ commitTimerSlot(instance, slot);
186
315
  });
187
316
  }
188
317
  function stream(_source, _options) {
@@ -193,11 +322,21 @@ function stream(_source, _options) {
193
322
  };
194
323
  }
195
324
  function task(fn) {
196
- const ownerIsRoot = getCurrentComponentInstance()?.isRoot ?? false;
197
- registerMountOperation(async () => {
198
- if (!ownerIsRoot) throw new Error("[Askr] task() may only be used in root components");
199
- return await fn();
200
- });
325
+ const instance = getCurrentComponentInstance();
326
+ if (!instance) return;
327
+ const slot = getLifecycleSlot(instance, claimHookIndex(instance, "task"), "task", () => ({
328
+ kind: "task",
329
+ started: false,
330
+ task: fn
331
+ }));
332
+ if (!slot.started) {
333
+ slot.task = fn;
334
+ registerCommitOperation(async () => {
335
+ if (slot.started) return;
336
+ slot.started = true;
337
+ return await slot.task();
338
+ });
339
+ }
201
340
  }
202
341
  /**
203
342
  * Capture the result of a synchronous expression at call time and return a
@@ -214,6 +353,6 @@ function capture(fn) {
214
353
  return () => value;
215
354
  }
216
355
  //#endregion
217
- export { capture, on, resource, stream, task, timer };
356
+ export { capture, documentVisible, on, resource, routeActive, stream, task, timer, windowFocused };
218
357
 
219
358
  //# sourceMappingURL=operations.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"operations.js","names":[],"sources":["../../src/runtime/operations.ts"],"sourcesContent":["import {\n getCurrentComponentInstance,\n registerMountOperation,\n type ComponentInstance,\n} from './component';\nimport { getCurrentContextFrame } from './context';\nimport { ResourceCell } from './resource-cell';\nimport { state } from './state';\nimport { globalScheduler } from './scheduler';\nimport { getSSRBridge } from './ssr-bridge';\nimport { brandSnapshotSource } from './snapshot-source';\nimport { SSRDataMissingError } from '../common/ssr-errors';\n\nexport interface ResourceResult<T> {\n value: T | null;\n pending: boolean;\n error: Error | null;\n refresh(): void;\n}\n\nexport function resource<T, const TDeps extends readonly unknown[]>(\n fn: (opts: { signal: AbortSignal }) => PromiseLike<T> | T,\n deps: TDeps\n): ResourceResult<T>;\n\n/**\n * Resource primitive — simple, deterministic async primitive\n * Usage: resource(fn, deps)\n * - fn receives { signal }\n * - captures execution context once at creation (synchronous step only)\n * - executes at most once per generation; stale async results are ignored\n * - refresh() cancels in-flight execution, increments generation and re-runs\n * - exposes { value, pending, error, refresh }\n * - during SSR, async results are disallowed and will throw synchronously\n */\nexport function resource<T>(\n fn: (opts: { signal: AbortSignal }) => PromiseLike<T> | T,\n deps: readonly unknown[] = []\n): ResourceResult<T> {\n const instance = getCurrentComponentInstance();\n // Create a non-null alias early so it can be used in nested closures\n // without TypeScript complaining about possible null access.\n const inst = instance as ComponentInstance;\n\n if (!instance) {\n const ssr = getSSRBridge();\n // If we're in a synchronous SSR render that has resolved data, use it.\n const renderData = ssr.getCurrentRenderData();\n if (renderData) {\n const key = ssr.getNextKey();\n if (!(key in renderData)) {\n ssr.throwSSRDataMissing();\n }\n const val = renderData[key] as T;\n return brandSnapshotSource({\n value: val,\n pending: false,\n error: null,\n refresh: () => {},\n }) as ResourceResult<T>;\n }\n\n // If we are in an SSR render pass without supplied data, throw for clarity.\n const ssrCtx = ssr.getCurrentSSRContext();\n if (ssrCtx) {\n ssr.throwSSRDataMissing();\n }\n\n // No active component instance and not in SSR render with data.\n // Autopilot invariant: resources must be created during render within an app.\n throw new Error(\n '[Askr] resource() must be called during component render inside an app. ' +\n 'Do not create resources at module scope or outside render.'\n );\n }\n\n // Internal ResourceCell — pure state machine now moved to its own module\n // to keep component wiring separate and ensure no component access here.\n // (See ./resource-cell.ts)\n\n // If we're in a synchronous SSR render that was supplied resolved data, use it\n const ssr = getSSRBridge();\n const renderData = ssr.getCurrentRenderData();\n if (renderData) {\n // Deterministic key generation: the collection step and render step use\n // the same incremental key generation to align resources.\n const key = ssr.getNextKey();\n if (!(key in renderData)) {\n ssr.throwSSRDataMissing();\n }\n\n // Commit synchronous value from render data and return a stable snapshot\n const val = renderData[key] as T;\n\n const holder = state<{\n cell?: ResourceCell<T>;\n snapshot: ResourceResult<T>;\n }>({\n cell: undefined,\n snapshot: brandSnapshotSource({\n value: val,\n pending: false,\n error: null,\n refresh: () => {},\n }) as ResourceResult<T>,\n });\n\n const h = holder();\n h.snapshot.value = val;\n h.snapshot.pending = false;\n h.snapshot.error = null;\n return h.snapshot;\n }\n\n // Persist a holder so the snapshot identity is stable across renders.\n const holder = state<{ cell?: ResourceCell<T>; snapshot: ResourceResult<T> }>(\n {\n cell: undefined,\n snapshot: brandSnapshotSource({\n value: null,\n pending: true,\n error: null,\n refresh: () => {},\n }) as ResourceResult<T>,\n }\n );\n\n const h = holder();\n\n // Initialize cell on first call\n if (!h.cell) {\n const frame = getCurrentContextFrame();\n const cell = new ResourceCell<T>(fn, deps, frame);\n // Attach debug label (component name) for richer logs\n cell.ownerName = inst.fn?.name || '<anonymous>';\n h.cell = cell;\n h.snapshot = cell.snapshot as ResourceResult<T>;\n\n // Subscribe and schedule component updates when cell changes\n const unsubscribe = cell.subscribe(() => {\n const cur = holder();\n cur.snapshot.value = cell.snapshot.value;\n cur.snapshot.pending = cell.snapshot.pending;\n cur.snapshot.error = cell.snapshot.error;\n holder.set(cur);\n try {\n inst.notifyUpdate?.();\n } catch {\n // ignore\n }\n });\n\n // Cleanup on unmount\n (inst.cleanupFns ??= []).push(() => {\n unsubscribe();\n cell.dispose();\n });\n\n // Render invariant: do NOT start async work during render on the client.\n // SSR remains strict/synchronous and must throw immediately if async is encountered.\n if (inst.ssr) {\n // SSR: must run synchronously so missing data throws during render\n cell.start(true, false);\n if (!cell.pending) {\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = cell.error;\n }\n } else {\n // Client: start after render via scheduler (never inline)\n const scheduledGeneration = cell.generation;\n globalScheduler.enqueueInLane('post', () => {\n if (!inst.notifyUpdate || cell.generation !== scheduledGeneration) {\n return;\n }\n\n try {\n cell.start(false, false);\n } catch (err) {\n // Non-SSR: reflect synchronous errors into snapshot via manual update\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = (err as Error) ?? null;\n holder.set(cur);\n inst.notifyUpdate?.();\n return;\n }\n\n // If the resource completed synchronously, subscribers were not notified.\n // Force a re-render so the component can observe the value.\n if (!cell.pending) {\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = cell.error;\n holder.set(cur);\n inst.notifyUpdate?.();\n }\n });\n }\n }\n\n const cell = h.cell!;\n cell.setLoader(fn);\n\n // Detect dependency changes and refresh immediately\n const depsChanged =\n !cell.deps ||\n cell.deps.length !== deps.length ||\n cell.deps.some((d, i) => !Object.is(d, deps[i]));\n\n if (depsChanged) {\n cell.deps = deps.slice();\n cell.generation++;\n cell.pending = true;\n cell.error = null;\n\n // Synchronously reflect the pending state into the stable snapshot so the\n // render that triggered the deps change can surface a loading indicator.\n // The async start() runs with notify=false and the deps-change branch never\n // re-published the snapshot, so without this a deps-driven refetch jumped\n // straight from the old value to the new value, never exposing pending.\n // Stale-while-revalidate: the previous value is retained until the new\n // fetch resolves.\n h.snapshot.pending = true;\n h.snapshot.error = null;\n try {\n if (inst.ssr) {\n cell.start(true, false);\n if (!cell.pending) {\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = cell.error;\n }\n } else {\n const scheduledGeneration = cell.generation;\n globalScheduler.enqueueInLane('post', () => {\n if (!inst.notifyUpdate || cell.generation !== scheduledGeneration) {\n return;\n }\n\n cell.start(false, false);\n if (!cell.pending) {\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = cell.error;\n holder.set(cur);\n inst.notifyUpdate?.();\n }\n });\n }\n } catch (err) {\n if (err instanceof SSRDataMissingError) throw err;\n cell.error = err as Error;\n cell.pending = false;\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = cell.error;\n // Do not call holder.set() here; this is still render.\n }\n }\n\n // Return the stable snapshot object owned by the cell\n return h.snapshot;\n}\n\nexport function on(\n target: EventTarget,\n event: string,\n handler: EventListener\n): void {\n const ownerIsRoot = getCurrentComponentInstance()?.isRoot ?? false;\n // Register the listener to be attached on mount. If the owner is not the\n // root app instance, fail loudly to prevent silent no-op behavior.\n registerMountOperation(() => {\n if (!ownerIsRoot) {\n throw new Error('[Askr] on() may only be used in root components');\n }\n target.addEventListener(event, handler);\n // Return cleanup function\n return () => {\n target.removeEventListener(event, handler);\n };\n });\n}\n\nexport function timer(intervalMs: number, fn: () => void): void {\n const ownerIsRoot = getCurrentComponentInstance()?.isRoot ?? false;\n // Register the timer to be started on mount. Fail loudly when used outside\n // of the root component to avoid silent no-ops.\n registerMountOperation(() => {\n if (!ownerIsRoot) {\n throw new Error('[Askr] timer() may only be used in root components');\n }\n const id = setInterval(fn, intervalMs);\n // Return cleanup function\n return () => {\n clearInterval(id);\n };\n });\n}\n\nexport function stream<T>(\n _source: unknown,\n _options?: Record<string, unknown>\n): { value: T | null; pending: boolean; error: Error | null } {\n // Stub implementation: no-op.\n return { value: null, pending: true, error: null };\n}\n\nexport function task(\n fn: () => void | (() => void) | PromiseLike<void | (() => void)>\n): void {\n const ownerIsRoot = getCurrentComponentInstance()?.isRoot ?? false;\n // Register the task to run on mount. Fail loudly when used outside the root\n // component so callers get immediate feedback rather than silent no-op.\n registerMountOperation(async () => {\n if (!ownerIsRoot) {\n throw new Error('[Askr] task() may only be used in root components');\n }\n // Execute the task (may be async) and return its cleanup\n return await fn();\n });\n}\n\n/**\n * Capture the result of a synchronous expression at call time and return a\n * thunk that returns the captured value later. This is a low-level helper for\n * cases where async continuations need to observe a snapshot of values at the\n * moment scheduling occurred.\n *\n * Usage (public API):\n * const snapshot = capture(() => someState());\n * Promise.resolve().then(() => { use(snapshot()); });\n */\nexport function capture<T>(fn: () => T): () => T {\n const value = fn();\n return () => value;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"operations.js","names":[],"sources":["../../src/runtime/operations.ts"],"sourcesContent":["import {\n claimHookIndex,\n getCurrentComponentInstance,\n registerCommitOperation,\n type ComponentInstance,\n} from './component';\nimport { getCurrentContextFrame } from './context';\nimport { ResourceCell } from './resource-cell';\nimport { state } from './state';\nimport { globalScheduler } from './scheduler';\nimport { getSSRBridge } from './ssr-bridge';\nimport { brandSnapshotSource } from './snapshot-source';\nimport { SSRDataMissingError } from '../common/ssr-errors';\nimport { isRouteActivityActive } from '../common/route-activity';\n\nexport interface ResourceResult<T> {\n value: T | null;\n pending: boolean;\n error: Error | null;\n refresh(): void;\n}\n\nexport type ActivityPredicate = () => boolean;\n\nexport interface TimerOptions {\n when?: ActivityPredicate | readonly ActivityPredicate[];\n}\n\nfunction normalizePredicates(\n predicates: TimerOptions['when']\n): readonly ActivityPredicate[] {\n if (!predicates) {\n return [];\n }\n\n return typeof predicates === 'function' ? [predicates] : predicates;\n}\n\nfunction allPredicatesPass(predicates: readonly ActivityPredicate[]): boolean {\n for (const predicate of predicates) {\n if (!predicate()) {\n return false;\n }\n }\n\n return true;\n}\n\ntype LifecycleSlotKind = 'timer' | 'listener' | 'task';\n\ntype LifecycleSlot = {\n kind: LifecycleSlotKind;\n};\n\nfunction getLifecycleSlot<TSlot extends LifecycleSlot>(\n instance: ComponentInstance,\n index: number,\n kind: TSlot['kind'],\n create: () => TSlot\n): TSlot {\n const existing = instance.lifecycleSlots[index];\n\n if (existing) {\n const slot = existing as LifecycleSlot;\n if (slot.kind !== kind) {\n throw new Error(\n `${kind}() lifecycle order violation: slot ${index} already belongs to ${slot.kind}(). ` +\n 'Keep lifecycle primitives in a stable top-level order.'\n );\n }\n\n return existing as TSlot;\n }\n\n const slot = create();\n instance.lifecycleSlots[index] = slot;\n return slot;\n}\n\nexport function routeActive(\n pathOrPaths: string | readonly string[]\n): ActivityPredicate {\n return () => isRouteActivityActive(pathOrPaths);\n}\n\nexport function documentVisible(): ActivityPredicate {\n return () =>\n typeof document === 'undefined' || document.visibilityState !== 'hidden';\n}\n\nexport function windowFocused(): ActivityPredicate {\n return () =>\n typeof document === 'undefined' ||\n typeof document.hasFocus !== 'function' ||\n document.hasFocus();\n}\n\nexport function resource<T, const TDeps extends readonly unknown[]>(\n fn: (opts: { signal: AbortSignal }) => PromiseLike<T> | T,\n deps: TDeps\n): ResourceResult<T>;\n\n/**\n * Resource primitive — simple, deterministic async primitive\n * Usage: resource(fn, deps)\n * - fn receives { signal }\n * - captures execution context once at creation (synchronous step only)\n * - executes at most once per generation; stale async results are ignored\n * - refresh() cancels in-flight execution, increments generation and re-runs\n * - exposes { value, pending, error, refresh }\n * - during SSR, async results are disallowed and will throw synchronously\n */\nexport function resource<T>(\n fn: (opts: { signal: AbortSignal }) => PromiseLike<T> | T,\n deps: readonly unknown[] = []\n): ResourceResult<T> {\n const instance = getCurrentComponentInstance();\n // Create a non-null alias early so it can be used in nested closures\n // without TypeScript complaining about possible null access.\n const inst = instance as ComponentInstance;\n\n if (!instance) {\n const ssr = getSSRBridge();\n // If we're in a synchronous SSR render that has resolved data, use it.\n const renderData = ssr.getCurrentRenderData();\n if (renderData) {\n const key = ssr.getNextKey();\n if (!(key in renderData)) {\n ssr.throwSSRDataMissing();\n }\n const val = renderData[key] as T;\n return brandSnapshotSource({\n value: val,\n pending: false,\n error: null,\n refresh: () => {},\n }) as ResourceResult<T>;\n }\n\n // If we are in an SSR render pass without supplied data, throw for clarity.\n const ssrCtx = ssr.getCurrentSSRContext();\n if (ssrCtx) {\n ssr.throwSSRDataMissing();\n }\n\n // No active component instance and not in SSR render with data.\n // Autopilot invariant: resources must be created during render within an app.\n throw new Error(\n '[Askr] resource() must be called during component render inside an app. ' +\n 'Do not create resources at module scope or outside render.'\n );\n }\n\n // Internal ResourceCell — pure state machine now moved to its own module\n // to keep component wiring separate and ensure no component access here.\n // (See ./resource-cell.ts)\n\n // If we're in a synchronous SSR render that was supplied resolved data, use it\n const ssr = getSSRBridge();\n const renderData = ssr.getCurrentRenderData();\n if (renderData) {\n // Deterministic key generation: the collection step and render step use\n // the same incremental key generation to align resources.\n const key = ssr.getNextKey();\n if (!(key in renderData)) {\n ssr.throwSSRDataMissing();\n }\n\n // Commit synchronous value from render data and return a stable snapshot\n const val = renderData[key] as T;\n\n const holder = state<{\n cell?: ResourceCell<T>;\n snapshot: ResourceResult<T>;\n }>({\n cell: undefined,\n snapshot: brandSnapshotSource({\n value: val,\n pending: false,\n error: null,\n refresh: () => {},\n }) as ResourceResult<T>,\n });\n\n const h = holder();\n h.snapshot.value = val;\n h.snapshot.pending = false;\n h.snapshot.error = null;\n return h.snapshot;\n }\n\n // Persist a holder so the snapshot identity is stable across renders.\n const holder = state<{ cell?: ResourceCell<T>; snapshot: ResourceResult<T> }>(\n {\n cell: undefined,\n snapshot: brandSnapshotSource({\n value: null,\n pending: true,\n error: null,\n refresh: () => {},\n }) as ResourceResult<T>,\n }\n );\n\n const h = holder();\n\n // Initialize cell on first call\n if (!h.cell) {\n const frame = getCurrentContextFrame();\n const cell = new ResourceCell<T>(fn, deps, frame);\n // Attach debug label (component name) for richer logs\n cell.ownerName = inst.fn?.name || '<anonymous>';\n h.cell = cell;\n h.snapshot = cell.snapshot as ResourceResult<T>;\n\n // Subscribe and schedule component updates when cell changes\n const unsubscribe = cell.subscribe(() => {\n const cur = holder();\n cur.snapshot.value = cell.snapshot.value;\n cur.snapshot.pending = cell.snapshot.pending;\n cur.snapshot.error = cell.snapshot.error;\n holder.set(cur);\n try {\n inst.notifyUpdate?.();\n } catch {\n // ignore\n }\n });\n\n // Cleanup on unmount\n (inst.cleanupFns ??= []).push(() => {\n unsubscribe();\n cell.dispose();\n });\n\n // Render invariant: do NOT start async work during render on the client.\n // SSR remains strict/synchronous and must throw immediately if async is encountered.\n if (inst.ssr) {\n // SSR: must run synchronously so missing data throws during render\n cell.start(true, false);\n if (!cell.pending) {\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = cell.error;\n }\n } else {\n // Client: start after render via scheduler (never inline)\n const scheduledGeneration = cell.generation;\n globalScheduler.enqueueInLane('post', () => {\n if (!inst.notifyUpdate || cell.generation !== scheduledGeneration) {\n return;\n }\n\n try {\n cell.start(false, false);\n } catch (err) {\n // Non-SSR: reflect synchronous errors into snapshot via manual update\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = (err as Error) ?? null;\n holder.set(cur);\n inst.notifyUpdate?.();\n return;\n }\n\n // If the resource completed synchronously, subscribers were not notified.\n // Force a re-render so the component can observe the value.\n if (!cell.pending) {\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = cell.error;\n holder.set(cur);\n inst.notifyUpdate?.();\n }\n });\n }\n }\n\n const cell = h.cell!;\n cell.setLoader(fn);\n\n // Detect dependency changes and refresh immediately\n const depsChanged =\n !cell.deps ||\n cell.deps.length !== deps.length ||\n cell.deps.some((d, i) => !Object.is(d, deps[i]));\n\n if (depsChanged) {\n cell.deps = deps.slice();\n cell.generation++;\n cell.pending = true;\n cell.error = null;\n\n // Synchronously reflect the pending state into the stable snapshot so the\n // render that triggered the deps change can surface a loading indicator.\n // The async start() runs with notify=false and the deps-change branch never\n // re-published the snapshot, so without this a deps-driven refetch jumped\n // straight from the old value to the new value, never exposing pending.\n // Stale-while-revalidate: the previous value is retained until the new\n // fetch resolves.\n h.snapshot.pending = true;\n h.snapshot.error = null;\n try {\n if (inst.ssr) {\n cell.start(true, false);\n if (!cell.pending) {\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = cell.error;\n }\n } else {\n const scheduledGeneration = cell.generation;\n globalScheduler.enqueueInLane('post', () => {\n if (!inst.notifyUpdate || cell.generation !== scheduledGeneration) {\n return;\n }\n\n cell.start(false, false);\n if (!cell.pending) {\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = cell.error;\n holder.set(cur);\n inst.notifyUpdate?.();\n }\n });\n }\n } catch (err) {\n if (err instanceof SSRDataMissingError) throw err;\n cell.error = err as Error;\n cell.pending = false;\n const cur = holder();\n cur.snapshot.value = cell.value;\n cur.snapshot.pending = cell.pending;\n cur.snapshot.error = cell.error;\n // Do not call holder.set() here; this is still render.\n }\n }\n\n // Return the stable snapshot object owned by the cell\n return h.snapshot;\n}\n\ntype ListenerOptions = boolean | AddEventListenerOptions | undefined;\n\ntype NormalizedListenerOptions =\n | boolean\n | {\n capture?: boolean;\n once?: boolean;\n passive?: boolean;\n signal?: AbortSignal;\n }\n | undefined;\n\ninterface ListenerSlot extends LifecycleSlot {\n kind: 'listener';\n target: EventTarget | null;\n event: string;\n handler: EventListener;\n listener: EventListener;\n options: NormalizedListenerOptions;\n pendingTarget: EventTarget;\n pendingEvent: string;\n pendingHandler: EventListener;\n pendingOptions: NormalizedListenerOptions;\n attached: boolean;\n cleanupRegistered: boolean;\n}\n\nfunction normalizeListenerOptions(\n options: ListenerOptions\n): NormalizedListenerOptions {\n if (options === undefined || typeof options === 'boolean') {\n return options;\n }\n\n return {\n ...(options.capture !== undefined ? { capture: options.capture } : {}),\n ...(options.once !== undefined ? { once: options.once } : {}),\n ...(options.passive !== undefined ? { passive: options.passive } : {}),\n ...(options.signal !== undefined ? { signal: options.signal } : {}),\n };\n}\n\nfunction listenerOptionsEqual(\n a: NormalizedListenerOptions,\n b: NormalizedListenerOptions\n): boolean {\n if (a === b) {\n return true;\n }\n if (typeof a === 'boolean' || typeof b === 'boolean') {\n return a === b;\n }\n if (!a || !b) {\n return a === b;\n }\n\n return (\n a.capture === b.capture &&\n a.once === b.once &&\n a.passive === b.passive &&\n a.signal === b.signal\n );\n}\n\nfunction detachListenerSlot(slot: ListenerSlot): void {\n if (!slot.attached || !slot.target) {\n return;\n }\n\n slot.target.removeEventListener(slot.event, slot.listener, slot.options);\n slot.attached = false;\n}\n\nfunction commitListenerSlot(\n instance: ComponentInstance,\n slot: ListenerSlot\n): void {\n slot.handler = slot.pendingHandler;\n\n const shouldReattach =\n !slot.attached ||\n slot.target !== slot.pendingTarget ||\n slot.event !== slot.pendingEvent ||\n !listenerOptionsEqual(slot.options, slot.pendingOptions);\n\n if (shouldReattach) {\n detachListenerSlot(slot);\n slot.target = slot.pendingTarget;\n slot.event = slot.pendingEvent;\n slot.options = slot.pendingOptions;\n slot.target.addEventListener(slot.event, slot.listener, slot.options);\n slot.attached = true;\n }\n\n if (!slot.cleanupRegistered) {\n slot.cleanupRegistered = true;\n instance.cleanupFns.push(() => {\n detachListenerSlot(slot);\n slot.cleanupRegistered = false;\n });\n }\n}\n\nexport function on(\n target: EventTarget,\n event: string,\n handler: EventListener,\n options?: ListenerOptions\n): void {\n const instance = getCurrentComponentInstance();\n if (!instance) {\n return;\n }\n\n const index = claimHookIndex(instance, 'on');\n const normalizedOptions = normalizeListenerOptions(options);\n const slot = getLifecycleSlot<ListenerSlot>(\n instance,\n index,\n 'listener',\n () => {\n const createdSlot = {\n kind: 'listener' as const,\n target: null,\n event,\n handler,\n listener: ((evt: Event) => {\n createdSlot.handler.call(createdSlot.target, evt);\n }) as EventListener,\n options: undefined,\n pendingTarget: target,\n pendingEvent: event,\n pendingHandler: handler,\n pendingOptions: normalizedOptions,\n attached: false,\n cleanupRegistered: false,\n };\n return createdSlot;\n }\n );\n\n slot.pendingTarget = target;\n slot.pendingEvent = event;\n slot.pendingHandler = handler;\n slot.pendingOptions = normalizedOptions;\n\n registerCommitOperation(() => {\n commitListenerSlot(instance, slot);\n });\n}\n\ninterface TimerSlot extends LifecycleSlot {\n kind: 'timer';\n id: ReturnType<typeof setInterval> | null;\n intervalMs: number | null;\n pendingIntervalMs: number;\n callback: () => void;\n predicates: readonly ActivityPredicate[];\n pendingCallback: () => void;\n pendingPredicates: readonly ActivityPredicate[];\n cleanupRegistered: boolean;\n}\n\nfunction stopTimerSlot(slot: TimerSlot): void {\n if (slot.id === null) {\n return;\n }\n\n clearInterval(slot.id);\n slot.id = null;\n}\n\nfunction startTimerSlot(slot: TimerSlot): void {\n slot.id = setInterval(() => {\n if (allPredicatesPass(slot.predicates)) {\n slot.callback();\n }\n }, slot.intervalMs ?? slot.pendingIntervalMs);\n}\n\nfunction commitTimerSlot(instance: ComponentInstance, slot: TimerSlot): void {\n const intervalChanged = slot.intervalMs !== slot.pendingIntervalMs;\n\n slot.callback = slot.pendingCallback;\n slot.predicates = slot.pendingPredicates;\n\n if (slot.id === null || intervalChanged) {\n stopTimerSlot(slot);\n slot.intervalMs = slot.pendingIntervalMs;\n startTimerSlot(slot);\n }\n\n if (!slot.cleanupRegistered) {\n slot.cleanupRegistered = true;\n instance.cleanupFns.push(() => {\n stopTimerSlot(slot);\n slot.cleanupRegistered = false;\n });\n }\n}\n\nexport function timer(\n intervalMs: number,\n fn: () => void,\n options?: TimerOptions\n): void {\n const instance = getCurrentComponentInstance();\n if (!instance) {\n return;\n }\n\n const index = claimHookIndex(instance, 'timer');\n const predicates = normalizePredicates(options?.when);\n const slot = getLifecycleSlot<TimerSlot>(instance, index, 'timer', () => ({\n kind: 'timer',\n id: null,\n intervalMs: null,\n pendingIntervalMs: intervalMs,\n callback: fn,\n predicates,\n pendingCallback: fn,\n pendingPredicates: predicates,\n cleanupRegistered: false,\n }));\n\n slot.pendingIntervalMs = intervalMs;\n slot.pendingCallback = fn;\n slot.pendingPredicates = predicates;\n\n registerCommitOperation(() => {\n commitTimerSlot(instance, slot);\n });\n}\n\nexport function stream<T>(\n _source: unknown,\n _options?: Record<string, unknown>\n): { value: T | null; pending: boolean; error: Error | null } {\n // Stub implementation: no-op.\n return { value: null, pending: true, error: null };\n}\n\nexport function task(\n fn: () => void | (() => void) | PromiseLike<void | (() => void)>\n): void {\n const instance = getCurrentComponentInstance();\n if (!instance) {\n return;\n }\n\n const index = claimHookIndex(instance, 'task');\n const slot = getLifecycleSlot<\n LifecycleSlot & {\n kind: 'task';\n started: boolean;\n task: typeof fn;\n }\n >(instance, index, 'task', () => ({\n kind: 'task',\n started: false,\n task: fn,\n }));\n\n if (!slot.started) {\n slot.task = fn;\n registerCommitOperation(async () => {\n if (slot.started) {\n return;\n }\n\n slot.started = true;\n return await slot.task();\n });\n }\n}\n\n/**\n * Capture the result of a synchronous expression at call time and return a\n * thunk that returns the captured value later. This is a low-level helper for\n * cases where async continuations need to observe a snapshot of values at the\n * moment scheduling occurred.\n *\n * Usage (public API):\n * const snapshot = capture(() => someState());\n * Promise.resolve().then(() => { use(snapshot()); });\n */\nexport function capture<T>(fn: () => T): () => T {\n const value = fn();\n return () => value;\n}\n"],"mappings":";;;;;;;;;;AA4BA,SAAS,oBACP,YAC8B;CAC9B,IAAI,CAAC,YACH,OAAO,CAAC;CAGV,OAAO,OAAO,eAAe,aAAa,CAAC,UAAU,IAAI;AAC3D;AAEA,SAAS,kBAAkB,YAAmD;CAC5E,KAAK,MAAM,aAAa,YACtB,IAAI,CAAC,UAAU,GACb,OAAO;CAIX,OAAO;AACT;AAQA,SAAS,iBACP,UACA,OACA,MACA,QACO;CACP,MAAM,WAAW,SAAS,eAAe;CAEzC,IAAI,UAAU;EACZ,MAAM,OAAO;EACb,IAAI,KAAK,SAAS,MAChB,MAAM,IAAI,MACR,GAAG,KAAK,qCAAqC,MAAM,sBAAsB,KAAK,KAAK,2DAErF;EAGF,OAAO;CACT;CAEA,MAAM,OAAO,OAAO;CACpB,SAAS,eAAe,SAAS;CACjC,OAAO;AACT;AAEA,SAAgB,YACd,aACmB;CACnB,aAAa,sBAAsB,WAAW;AAChD;AAEA,SAAgB,kBAAqC;CACnD,aACE,OAAO,aAAa,eAAe,SAAS,oBAAoB;AACpE;AAEA,SAAgB,gBAAmC;CACjD,aACE,OAAO,aAAa,eACpB,OAAO,SAAS,aAAa,cAC7B,SAAS,SAAS;AACtB;;;;;;;;;;;AAiBA,SAAgB,SACd,IACA,OAA2B,CAAC,GACT;CACnB,MAAM,WAAW,4BAA4B;CAG7C,MAAM,OAAO;CAEb,IAAI,CAAC,UAAU;EACb,MAAM,MAAM,aAAa;EAEzB,MAAM,aAAa,IAAI,qBAAqB;EAC5C,IAAI,YAAY;GACd,MAAM,MAAM,IAAI,WAAW;GAC3B,IAAI,EAAE,OAAO,aACX,IAAI,oBAAoB;GAE1B,MAAM,MAAM,WAAW;GACvB,OAAO,oBAAoB;IACzB,OAAO;IACP,SAAS;IACT,OAAO;IACP,eAAe,CAAC;GAClB,CAAC;EACH;EAIA,IADe,IAAI,qBACf,GACF,IAAI,oBAAoB;EAK1B,MAAM,IAAI,MACR,oIAEF;CACF;CAOA,MAAM,MAAM,aAAa;CACzB,MAAM,aAAa,IAAI,qBAAqB;CAC5C,IAAI,YAAY;EAGd,MAAM,MAAM,IAAI,WAAW;EAC3B,IAAI,EAAE,OAAO,aACX,IAAI,oBAAoB;EAI1B,MAAM,MAAM,WAAW;EAevB,MAAM,IAbS,MAGZ;GACD,MAAM;GACN,UAAU,oBAAoB;IAC5B,OAAO;IACP,SAAS;IACT,OAAO;IACP,eAAe,CAAC;GAClB,CAAC;EACH,CAEU,EAAO;EACjB,EAAE,SAAS,QAAQ;EACnB,EAAE,SAAS,UAAU;EACrB,EAAE,SAAS,QAAQ;EACnB,OAAO,EAAE;CACX;CAGA,MAAM,SAAS,MACb;EACE,MAAM;EACN,UAAU,oBAAoB;GAC5B,OAAO;GACP,SAAS;GACT,OAAO;GACP,eAAe,CAAC;EAClB,CAAC;CACH,CACF;CAEA,MAAM,IAAI,OAAO;CAGjB,IAAI,CAAC,EAAE,MAAM;EAEX,MAAM,OAAO,IAAI,aAAgB,IAAI,MADvB,uBAC6B,CAAK;EAEhD,KAAK,YAAY,KAAK,IAAI,QAAQ;EAClC,EAAE,OAAO;EACT,EAAE,WAAW,KAAK;EAGlB,MAAM,cAAc,KAAK,gBAAgB;GACvC,MAAM,MAAM,OAAO;GACnB,IAAI,SAAS,QAAQ,KAAK,SAAS;GACnC,IAAI,SAAS,UAAU,KAAK,SAAS;GACrC,IAAI,SAAS,QAAQ,KAAK,SAAS;GACnC,OAAO,IAAI,GAAG;GACd,IAAI;IACF,KAAK,eAAe;GACtB,QAAQ,CAER;EACF,CAAC;EAGD,CAAC,KAAK,eAAe,CAAC,GAAG,WAAW;GAClC,YAAY;GACZ,KAAK,QAAQ;EACf,CAAC;EAID,IAAI,KAAK,KAAK;GAEZ,KAAK,MAAM,MAAM,KAAK;GACtB,IAAI,CAAC,KAAK,SAAS;IACjB,MAAM,MAAM,OAAO;IACnB,IAAI,SAAS,QAAQ,KAAK;IAC1B,IAAI,SAAS,UAAU,KAAK;IAC5B,IAAI,SAAS,QAAQ,KAAK;GAC5B;EACF,OAAO;GAEL,MAAM,sBAAsB,KAAK;GACjC,gBAAgB,cAAc,cAAc;IAC1C,IAAI,CAAC,KAAK,gBAAgB,KAAK,eAAe,qBAC5C;IAGF,IAAI;KACF,KAAK,MAAM,OAAO,KAAK;IACzB,SAAS,KAAK;KAEZ,MAAM,MAAM,OAAO;KACnB,IAAI,SAAS,QAAQ,KAAK;KAC1B,IAAI,SAAS,UAAU,KAAK;KAC5B,IAAI,SAAS,QAAS,OAAiB;KACvC,OAAO,IAAI,GAAG;KACd,KAAK,eAAe;KACpB;IACF;IAIA,IAAI,CAAC,KAAK,SAAS;KACjB,MAAM,MAAM,OAAO;KACnB,IAAI,SAAS,QAAQ,KAAK;KAC1B,IAAI,SAAS,UAAU,KAAK;KAC5B,IAAI,SAAS,QAAQ,KAAK;KAC1B,OAAO,IAAI,GAAG;KACd,KAAK,eAAe;IACtB;GACF,CAAC;EACH;CACF;CAEA,MAAM,OAAO,EAAE;CACf,KAAK,UAAU,EAAE;CAQjB,IAJE,CAAC,KAAK,QACN,KAAK,KAAK,WAAW,KAAK,UAC1B,KAAK,KAAK,MAAM,GAAG,MAAM,CAAC,OAAO,GAAG,GAAG,KAAK,EAAE,CAAC,GAEhC;EACf,KAAK,OAAO,KAAK,MAAM;EACvB,KAAK;EACL,KAAK,UAAU;EACf,KAAK,QAAQ;EASb,EAAE,SAAS,UAAU;EACrB,EAAE,SAAS,QAAQ;EACnB,IAAI;GACF,IAAI,KAAK,KAAK;IACZ,KAAK,MAAM,MAAM,KAAK;IACtB,IAAI,CAAC,KAAK,SAAS;KACjB,MAAM,MAAM,OAAO;KACnB,IAAI,SAAS,QAAQ,KAAK;KAC1B,IAAI,SAAS,UAAU,KAAK;KAC5B,IAAI,SAAS,QAAQ,KAAK;IAC5B;GACF,OAAO;IACL,MAAM,sBAAsB,KAAK;IACjC,gBAAgB,cAAc,cAAc;KAC1C,IAAI,CAAC,KAAK,gBAAgB,KAAK,eAAe,qBAC5C;KAGF,KAAK,MAAM,OAAO,KAAK;KACvB,IAAI,CAAC,KAAK,SAAS;MACjB,MAAM,MAAM,OAAO;MACnB,IAAI,SAAS,QAAQ,KAAK;MAC1B,IAAI,SAAS,UAAU,KAAK;MAC5B,IAAI,SAAS,QAAQ,KAAK;MAC1B,OAAO,IAAI,GAAG;MACd,KAAK,eAAe;KACtB;IACF,CAAC;GACH;EACF,SAAS,KAAK;GACZ,IAAI,eAAe,qBAAqB,MAAM;GAC9C,KAAK,QAAQ;GACb,KAAK,UAAU;GACf,MAAM,MAAM,OAAO;GACnB,IAAI,SAAS,QAAQ,KAAK;GAC1B,IAAI,SAAS,UAAU,KAAK;GAC5B,IAAI,SAAS,QAAQ,KAAK;EAE5B;CACF;CAGA,OAAO,EAAE;AACX;AA6BA,SAAS,yBACP,SAC2B;CAC3B,IAAI,YAAY,UAAa,OAAO,YAAY,WAC9C,OAAO;CAGT,OAAO;EACL,GAAI,QAAQ,YAAY,SAAY,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;EACpE,GAAI,QAAQ,SAAS,SAAY,EAAE,MAAM,QAAQ,KAAK,IAAI,CAAC;EAC3D,GAAI,QAAQ,YAAY,SAAY,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;EACpE,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;CACnE;AACF;AAEA,SAAS,qBACP,GACA,GACS;CACT,IAAI,MAAM,GACR,OAAO;CAET,IAAI,OAAO,MAAM,aAAa,OAAO,MAAM,WACzC,OAAO,MAAM;CAEf,IAAI,CAAC,KAAK,CAAC,GACT,OAAO,MAAM;CAGf,OACE,EAAE,YAAY,EAAE,WAChB,EAAE,SAAS,EAAE,QACb,EAAE,YAAY,EAAE,WAChB,EAAE,WAAW,EAAE;AAEnB;AAEA,SAAS,mBAAmB,MAA0B;CACpD,IAAI,CAAC,KAAK,YAAY,CAAC,KAAK,QAC1B;CAGF,KAAK,OAAO,oBAAoB,KAAK,OAAO,KAAK,UAAU,KAAK,OAAO;CACvE,KAAK,WAAW;AAClB;AAEA,SAAS,mBACP,UACA,MACM;CACN,KAAK,UAAU,KAAK;CAQpB,IALE,CAAC,KAAK,YACN,KAAK,WAAW,KAAK,iBACrB,KAAK,UAAU,KAAK,gBACpB,CAAC,qBAAqB,KAAK,SAAS,KAAK,cAAc,GAErC;EAClB,mBAAmB,IAAI;EACvB,KAAK,SAAS,KAAK;EACnB,KAAK,QAAQ,KAAK;EAClB,KAAK,UAAU,KAAK;EACpB,KAAK,OAAO,iBAAiB,KAAK,OAAO,KAAK,UAAU,KAAK,OAAO;EACpE,KAAK,WAAW;CAClB;CAEA,IAAI,CAAC,KAAK,mBAAmB;EAC3B,KAAK,oBAAoB;EACzB,SAAS,WAAW,WAAW;GAC7B,mBAAmB,IAAI;GACvB,KAAK,oBAAoB;EAC3B,CAAC;CACH;AACF;AAEA,SAAgB,GACd,QACA,OACA,SACA,SACM;CACN,MAAM,WAAW,4BAA4B;CAC7C,IAAI,CAAC,UACH;CAGF,MAAM,QAAQ,eAAe,UAAU,IAAI;CAC3C,MAAM,oBAAoB,yBAAyB,OAAO;CAC1D,MAAM,OAAO,iBACX,UACA,OACA,kBACM;EACJ,MAAM,cAAc;GAClB,MAAM;GACN,QAAQ;GACR;GACA;GACA,YAAY,QAAe;IACzB,YAAY,QAAQ,KAAK,YAAY,QAAQ,GAAG;GAClD;GACA,SAAS;GACT,eAAe;GACf,cAAc;GACd,gBAAgB;GAChB,gBAAgB;GAChB,UAAU;GACV,mBAAmB;EACrB;EACA,OAAO;CACT,CACF;CAEA,KAAK,gBAAgB;CACrB,KAAK,eAAe;CACpB,KAAK,iBAAiB;CACtB,KAAK,iBAAiB;CAEtB,8BAA8B;EAC5B,mBAAmB,UAAU,IAAI;CACnC,CAAC;AACH;AAcA,SAAS,cAAc,MAAuB;CAC5C,IAAI,KAAK,OAAO,MACd;CAGF,cAAc,KAAK,EAAE;CACrB,KAAK,KAAK;AACZ;AAEA,SAAS,eAAe,MAAuB;CAC7C,KAAK,KAAK,kBAAkB;EAC1B,IAAI,kBAAkB,KAAK,UAAU,GACnC,KAAK,SAAS;CAElB,GAAG,KAAK,cAAc,KAAK,iBAAiB;AAC9C;AAEA,SAAS,gBAAgB,UAA6B,MAAuB;CAC3E,MAAM,kBAAkB,KAAK,eAAe,KAAK;CAEjD,KAAK,WAAW,KAAK;CACrB,KAAK,aAAa,KAAK;CAEvB,IAAI,KAAK,OAAO,QAAQ,iBAAiB;EACvC,cAAc,IAAI;EAClB,KAAK,aAAa,KAAK;EACvB,eAAe,IAAI;CACrB;CAEA,IAAI,CAAC,KAAK,mBAAmB;EAC3B,KAAK,oBAAoB;EACzB,SAAS,WAAW,WAAW;GAC7B,cAAc,IAAI;GAClB,KAAK,oBAAoB;EAC3B,CAAC;CACH;AACF;AAEA,SAAgB,MACd,YACA,IACA,SACM;CACN,MAAM,WAAW,4BAA4B;CAC7C,IAAI,CAAC,UACH;CAGF,MAAM,QAAQ,eAAe,UAAU,OAAO;CAC9C,MAAM,aAAa,oBAAoB,SAAS,IAAI;CACpD,MAAM,OAAO,iBAA4B,UAAU,OAAO,gBAAgB;EACxE,MAAM;EACN,IAAI;EACJ,YAAY;EACZ,mBAAmB;EACnB,UAAU;EACV;EACA,iBAAiB;EACjB,mBAAmB;EACnB,mBAAmB;CACrB,EAAE;CAEF,KAAK,oBAAoB;CACzB,KAAK,kBAAkB;CACvB,KAAK,oBAAoB;CAEzB,8BAA8B;EAC5B,gBAAgB,UAAU,IAAI;CAChC,CAAC;AACH;AAEA,SAAgB,OACd,SACA,UAC4D;CAE5D,OAAO;EAAE,OAAO;EAAM,SAAS;EAAM,OAAO;CAAK;AACnD;AAEA,SAAgB,KACd,IACM;CACN,MAAM,WAAW,4BAA4B;CAC7C,IAAI,CAAC,UACH;CAIF,MAAM,OAAO,iBAMX,UAPY,eAAe,UAAU,MAO3B,GAAO,eAAe;EAChC,MAAM;EACN,SAAS;EACT,MAAM;CACR,EAAE;CAEF,IAAI,CAAC,KAAK,SAAS;EACjB,KAAK,OAAO;EACZ,wBAAwB,YAAY;GAClC,IAAI,KAAK,SACP;GAGF,KAAK,UAAU;GACf,OAAO,MAAM,KAAK,KAAK;EACzB,CAAC;CACH;AACF;;;;;;;;;;;AAYA,SAAgB,QAAW,IAAsB;CAC/C,MAAM,QAAQ,GAAG;CACjB,aAAa;AACf"}
@@ -16,6 +16,7 @@ declare function markReadableUsage(source: unknown): void;
16
16
  declare function recordReadableRead(source: ReadableSource<unknown>): void;
17
17
  declare function withFineGrainedReadTracking<T>(pendingSources: Set<ReadableSource<unknown>>, fn: () => T): T;
18
18
  declare function finalizeReadableSubscriptions(instance: ComponentInstance): void;
19
+ declare function finalizeReadableSubscriptionsFromSnapshot(instance: ComponentInstance, token: number | undefined, newSet: Set<ReadableSource<unknown>> | undefined): void;
19
20
  declare function cleanupReadableSubscriptions(instance: ComponentInstance): void;
20
21
  declare function withDerivedReadTracking<T>(subscriber: DerivedSubscriber, fn: () => T): T;
21
22
  declare function syncDerivedDependencySubscriptions(subscriber: DerivedSubscriber, prevSources: Set<ReadableSource<unknown>>, nextSources: Set<ReadableSource<unknown>>): void;
@@ -1 +1 @@
1
- {"version":3,"file":"readable.d.ts","names":[],"sources":["../../src/runtime/readable.ts"],"mappings":";;;UAGiB,iBAAA;EACf,UAAA;EACA,yBAAA,GAA4B,GAAG,CAAC,cAAA;AAAA;AAAA,UAGjB,cAAA;EAAA,IACX,CAAA;EACJ,YAAA;EACA,gBAAA;EACA,QAAA,GAAW,GAAA,CAAI,iBAAA;EACf,mBAAA,GAAsB,GAAA,CAAI,iBAAA;AAAA;AAAA,iBAGZ,iBAAA,CAAkB,MAAe;AAAA,iBAoBjC,kBAAA,CAAmB,MAA+B,EAAvB,cAAc;AAAA,iBA8BzC,2BAAA,IACd,cAAA,EAAgB,GAAA,CAAI,cAAA,YACpB,EAAA,QAAU,CAAA,GACT,CAAA;AAAA,iBAWa,6BAAA,CACd,QAA2B,EAAjB,iBAAiB;AAAA,iBAoCb,4BAAA,CACd,QAA2B,EAAjB,iBAAiB;AAAA,iBAab,uBAAA,IACd,UAAA,EAAY,iBAAA,EACZ,EAAA,QAAU,CAAA,GACT,CAAA;AAAA,iBAaa,kCAAA,CACd,UAAA,EAAY,iBAAA,EACZ,WAAA,EAAa,GAAA,CAAI,cAAA,YACjB,WAAA,EAAa,GAAA,CAAI,cAAA;AAAA,iBAkBH,mCAAA,CACd,UAAA,EAAY,iBAAA,EACZ,OAAA,EAAS,GAAA,CAAI,cAAA;AAAA,iBAQC,mCAAA,CACd,MAA+B,EAAvB,cAAc;AAAA,iBAYR,4BAAA,CACd,MAA+B,EAAvB,cAAc;AAAA,iBAaR,qBAAA,CACd,MAAA,EAAQ,cAAA,WACR,YAAA,GAAe,iBAAiB"}
1
+ {"version":3,"file":"readable.d.ts","names":[],"sources":["../../src/runtime/readable.ts"],"mappings":";;;UAGiB,iBAAA;EACf,UAAA;EACA,yBAAA,GAA4B,GAAG,CAAC,cAAA;AAAA;AAAA,UAGjB,cAAA;EAAA,IACX,CAAA;EACJ,YAAA;EACA,gBAAA;EACA,QAAA,GAAW,GAAA,CAAI,iBAAA;EACf,mBAAA,GAAsB,GAAA,CAAI,iBAAA;AAAA;AAAA,iBAGZ,iBAAA,CAAkB,MAAe;AAAA,iBAoBjC,kBAAA,CAAmB,MAA+B,EAAvB,cAAc;AAAA,iBA8BzC,2BAAA,IACd,cAAA,EAAgB,GAAA,CAAI,cAAA,YACpB,EAAA,QAAU,CAAA,GACT,CAAA;AAAA,iBAWa,6BAAA,CACd,QAA2B,EAAjB,iBAAiB;AAAA,iBAUb,yCAAA,CACd,QAAA,EAAU,iBAAA,EACV,KAAA,sBACA,MAAA,EAAQ,GAAA,CAAI,cAAA;AAAA,iBAgCE,4BAAA,CACd,QAA2B,EAAjB,iBAAiB;AAAA,iBAab,uBAAA,IACd,UAAA,EAAY,iBAAA,EACZ,EAAA,QAAU,CAAA,GACT,CAAA;AAAA,iBAaa,kCAAA,CACd,UAAA,EAAY,iBAAA,EACZ,WAAA,EAAa,GAAA,CAAI,cAAA,YACjB,WAAA,EAAa,GAAA,CAAI,cAAA;AAAA,iBAkBH,mCAAA,CACd,UAAA,EAAY,iBAAA,EACZ,OAAA,EAAS,GAAA,CAAI,cAAA;AAAA,iBAQC,mCAAA,CACd,MAA+B,EAAvB,cAAc;AAAA,iBAYR,4BAAA,CACd,MAA+B,EAAvB,cAAc;AAAA,iBAaR,qBAAA,CACd,MAAA,EAAQ,cAAA,WACR,YAAA,GAAe,iBAAiB"}
@@ -38,9 +38,14 @@ function withFineGrainedReadTracking(pendingSources, fn) {
38
38
  }
39
39
  function finalizeReadableSubscriptions(instance) {
40
40
  const newSet = instance._pendingReadSources;
41
- const oldSet = instance._lastReadSources;
42
41
  const token = instance._currentRenderToken;
42
+ finalizeReadableSubscriptionsFromSnapshot(instance, token, newSet);
43
+ instance._pendingReadSources = void 0;
44
+ instance._currentRenderToken = void 0;
45
+ }
46
+ function finalizeReadableSubscriptionsFromSnapshot(instance, token, newSet) {
43
47
  if (token === void 0) return;
48
+ const oldSet = instance._lastReadSources;
44
49
  if (oldSet) {
45
50
  for (const source of oldSet) if (!newSet?.has(source)) source._readers?.delete(instance);
46
51
  }
@@ -54,8 +59,6 @@ function finalizeReadableSubscriptions(instance) {
54
59
  readers.set(instance, instance.lastRenderToken ?? 0);
55
60
  }
56
61
  instance._lastReadSources = newSet ?? /* @__PURE__ */ new Set();
57
- instance._pendingReadSources = void 0;
58
- instance._currentRenderToken = void 0;
59
62
  }
60
63
  function cleanupReadableSubscriptions(instance) {
61
64
  const sources = instance._lastReadSources;
@@ -119,6 +122,6 @@ function notifyReadableReaders(source, skipInstance) {
119
122
  return didScheduleUpdate;
120
123
  }
121
124
  //#endregion
122
- export { cleanupReadableSubscriptions, clearDerivedDependencySubscriptions, finalizeReadableSubscriptions, markReactivePropsDirtySource, markReadableDerivedSubscribersDirty, markReadableUsage, notifyReadableReaders, recordReadableRead, syncDerivedDependencySubscriptions, withDerivedReadTracking, withFineGrainedReadTracking };
125
+ export { cleanupReadableSubscriptions, clearDerivedDependencySubscriptions, finalizeReadableSubscriptions, finalizeReadableSubscriptionsFromSnapshot, markReactivePropsDirtySource, markReadableDerivedSubscribersDirty, markReadableUsage, notifyReadableReaders, recordReadableRead, syncDerivedDependencySubscriptions, withDerivedReadTracking, withFineGrainedReadTracking };
123
126
 
124
127
  //# sourceMappingURL=readable.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"readable.js","names":[],"sources":["../../src/runtime/readable.ts"],"sourcesContent":["import { globalScheduler } from './scheduler';\nimport { getCurrentInstance, type ComponentInstance } from './component';\n\nexport interface DerivedSubscriber {\n _markDirty(): void;\n _pendingDependencySources?: Set<ReadableSource<unknown>>;\n}\n\nexport interface ReadableSource<T = unknown> {\n (): T;\n _hasBeenRead?: boolean;\n _hasEverBeenRead?: boolean;\n _readers?: Map<ComponentInstance, number>;\n _derivedSubscribers?: Set<DerivedSubscriber>;\n}\n\nexport function markReadableUsage(source: unknown): void {\n if (\n source &&\n typeof source === 'function' &&\n ('_hasBeenRead' in source || '_readers' in source)\n ) {\n const readable = source as ReadableSource<unknown>;\n readable._hasBeenRead = true;\n readable._hasEverBeenRead = true;\n }\n}\n\ntype RendererBridge = {\n markReactivePropsDirtySource?: (source: ReadableSource<unknown>) => void;\n};\n\nlet currentDerivedSubscriber: DerivedSubscriber | null = null;\nlet suppressComponentReadTrackingDepth = 0;\nlet currentFineGrainedReadSources: Set<ReadableSource<unknown>> | null = null;\n\nexport function recordReadableRead(source: ReadableSource<unknown>): void {\n source._hasEverBeenRead = true;\n\n if (currentFineGrainedReadSources) {\n currentFineGrainedReadSources.add(source);\n return;\n }\n\n if (currentDerivedSubscriber) {\n if (!currentDerivedSubscriber._pendingDependencySources) {\n currentDerivedSubscriber._pendingDependencySources = new Set();\n }\n currentDerivedSubscriber._pendingDependencySources.add(source);\n }\n\n if (suppressComponentReadTrackingDepth > 0) {\n return;\n }\n\n const inst = getCurrentInstance();\n if (!inst || inst._currentRenderToken === undefined) {\n return;\n }\n\n if (!inst._pendingReadSources) {\n inst._pendingReadSources = new Set();\n }\n inst._pendingReadSources.add(source);\n}\n\nexport function withFineGrainedReadTracking<T>(\n pendingSources: Set<ReadableSource<unknown>>,\n fn: () => T\n): T {\n const previousPendingSources = currentFineGrainedReadSources;\n currentFineGrainedReadSources = pendingSources;\n\n try {\n return fn();\n } finally {\n currentFineGrainedReadSources = previousPendingSources;\n }\n}\n\nexport function finalizeReadableSubscriptions(\n instance: ComponentInstance\n): void {\n const newSet = instance._pendingReadSources;\n const oldSet = instance._lastReadSources;\n const token = instance._currentRenderToken;\n\n if (token === undefined) {\n return;\n }\n\n if (oldSet) {\n for (const source of oldSet) {\n if (!newSet?.has(source)) {\n source._readers?.delete(instance);\n }\n }\n }\n\n instance.lastRenderToken = token;\n\n if (newSet) {\n for (const source of newSet) {\n let readers = source._readers;\n if (!readers) {\n readers = new Map();\n source._readers = readers;\n }\n readers.set(instance, instance.lastRenderToken ?? 0);\n }\n }\n\n instance._lastReadSources = newSet ?? new Set();\n instance._pendingReadSources = undefined;\n instance._currentRenderToken = undefined;\n}\n\nexport function cleanupReadableSubscriptions(\n instance: ComponentInstance\n): void {\n const sources = instance._lastReadSources;\n if (!sources || sources.size === 0) {\n return;\n }\n\n for (const source of sources) {\n source._readers?.delete(instance);\n }\n instance._lastReadSources = new Set();\n}\n\nexport function withDerivedReadTracking<T>(\n subscriber: DerivedSubscriber,\n fn: () => T\n): T {\n const prevDerivedSubscriber = currentDerivedSubscriber;\n currentDerivedSubscriber = subscriber;\n suppressComponentReadTrackingDepth += 1;\n\n try {\n return fn();\n } finally {\n suppressComponentReadTrackingDepth -= 1;\n currentDerivedSubscriber = prevDerivedSubscriber;\n }\n}\n\nexport function syncDerivedDependencySubscriptions(\n subscriber: DerivedSubscriber,\n prevSources: Set<ReadableSource<unknown>>,\n nextSources: Set<ReadableSource<unknown>>\n): void {\n for (const source of prevSources) {\n if (!nextSources.has(source)) {\n source._derivedSubscribers?.delete(subscriber);\n }\n }\n\n for (const source of nextSources) {\n let subscribers = source._derivedSubscribers;\n if (!subscribers) {\n subscribers = new Set();\n source._derivedSubscribers = subscribers;\n }\n subscribers.add(subscriber);\n }\n}\n\nexport function clearDerivedDependencySubscriptions(\n subscriber: DerivedSubscriber,\n sources: Set<ReadableSource<unknown>>\n): void {\n for (const source of sources) {\n source._derivedSubscribers?.delete(subscriber);\n }\n sources.clear();\n}\n\nexport function markReadableDerivedSubscribersDirty(\n source: ReadableSource<unknown>\n): void {\n const subscribers = source._derivedSubscribers;\n if (!subscribers || subscribers.size === 0) {\n return;\n }\n\n for (const subscriber of subscribers) {\n subscriber._markDirty();\n }\n}\n\nexport function markReactivePropsDirtySource(\n source: ReadableSource<unknown>\n): void {\n try {\n (\n globalThis as {\n __ASKR_RENDERER?: RendererBridge;\n }\n ).__ASKR_RENDERER?.markReactivePropsDirtySource?.(source);\n } catch {\n // Keep readable notifications side-effect safe.\n }\n}\n\nexport function notifyReadableReaders(\n source: ReadableSource<unknown>,\n skipInstance?: ComponentInstance | null\n): boolean {\n const readers = source._readers;\n let didScheduleUpdate = false;\n\n if (!readers || readers.size === 0) {\n return false;\n }\n\n for (const [instance, token] of readers) {\n if (skipInstance && instance === skipInstance) {\n continue;\n }\n if (instance.lastRenderToken !== token) {\n continue;\n }\n if (instance.hasPendingUpdate) {\n continue;\n }\n\n instance.hasPendingUpdate = true;\n const task = instance._pendingFlushTask;\n if (task) {\n globalScheduler.enqueue(task);\n } else {\n globalScheduler.enqueue(() => {\n instance.hasPendingUpdate = false;\n instance.notifyUpdate?.();\n });\n }\n didScheduleUpdate = true;\n }\n\n return didScheduleUpdate;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"readable.js","names":[],"sources":["../../src/runtime/readable.ts"],"sourcesContent":["import { globalScheduler } from './scheduler';\nimport { getCurrentInstance, type ComponentInstance } from './component';\n\nexport interface DerivedSubscriber {\n _markDirty(): void;\n _pendingDependencySources?: Set<ReadableSource<unknown>>;\n}\n\nexport interface ReadableSource<T = unknown> {\n (): T;\n _hasBeenRead?: boolean;\n _hasEverBeenRead?: boolean;\n _readers?: Map<ComponentInstance, number>;\n _derivedSubscribers?: Set<DerivedSubscriber>;\n}\n\nexport function markReadableUsage(source: unknown): void {\n if (\n source &&\n typeof source === 'function' &&\n ('_hasBeenRead' in source || '_readers' in source)\n ) {\n const readable = source as ReadableSource<unknown>;\n readable._hasBeenRead = true;\n readable._hasEverBeenRead = true;\n }\n}\n\ntype RendererBridge = {\n markReactivePropsDirtySource?: (source: ReadableSource<unknown>) => void;\n};\n\nlet currentDerivedSubscriber: DerivedSubscriber | null = null;\nlet suppressComponentReadTrackingDepth = 0;\nlet currentFineGrainedReadSources: Set<ReadableSource<unknown>> | null = null;\n\nexport function recordReadableRead(source: ReadableSource<unknown>): void {\n source._hasEverBeenRead = true;\n\n if (currentFineGrainedReadSources) {\n currentFineGrainedReadSources.add(source);\n return;\n }\n\n if (currentDerivedSubscriber) {\n if (!currentDerivedSubscriber._pendingDependencySources) {\n currentDerivedSubscriber._pendingDependencySources = new Set();\n }\n currentDerivedSubscriber._pendingDependencySources.add(source);\n }\n\n if (suppressComponentReadTrackingDepth > 0) {\n return;\n }\n\n const inst = getCurrentInstance();\n if (!inst || inst._currentRenderToken === undefined) {\n return;\n }\n\n if (!inst._pendingReadSources) {\n inst._pendingReadSources = new Set();\n }\n inst._pendingReadSources.add(source);\n}\n\nexport function withFineGrainedReadTracking<T>(\n pendingSources: Set<ReadableSource<unknown>>,\n fn: () => T\n): T {\n const previousPendingSources = currentFineGrainedReadSources;\n currentFineGrainedReadSources = pendingSources;\n\n try {\n return fn();\n } finally {\n currentFineGrainedReadSources = previousPendingSources;\n }\n}\n\nexport function finalizeReadableSubscriptions(\n instance: ComponentInstance\n): void {\n const newSet = instance._pendingReadSources;\n const token = instance._currentRenderToken;\n\n finalizeReadableSubscriptionsFromSnapshot(instance, token, newSet);\n instance._pendingReadSources = undefined;\n instance._currentRenderToken = undefined;\n}\n\nexport function finalizeReadableSubscriptionsFromSnapshot(\n instance: ComponentInstance,\n token: number | undefined,\n newSet: Set<ReadableSource<unknown>> | undefined\n): void {\n if (token === undefined) {\n return;\n }\n\n const oldSet = instance._lastReadSources;\n\n if (oldSet) {\n for (const source of oldSet) {\n if (!newSet?.has(source)) {\n source._readers?.delete(instance);\n }\n }\n }\n\n instance.lastRenderToken = token;\n\n if (newSet) {\n for (const source of newSet) {\n let readers = source._readers;\n if (!readers) {\n readers = new Map();\n source._readers = readers;\n }\n readers.set(instance, instance.lastRenderToken ?? 0);\n }\n }\n\n instance._lastReadSources = newSet ?? new Set();\n}\n\nexport function cleanupReadableSubscriptions(\n instance: ComponentInstance\n): void {\n const sources = instance._lastReadSources;\n if (!sources || sources.size === 0) {\n return;\n }\n\n for (const source of sources) {\n source._readers?.delete(instance);\n }\n instance._lastReadSources = new Set();\n}\n\nexport function withDerivedReadTracking<T>(\n subscriber: DerivedSubscriber,\n fn: () => T\n): T {\n const prevDerivedSubscriber = currentDerivedSubscriber;\n currentDerivedSubscriber = subscriber;\n suppressComponentReadTrackingDepth += 1;\n\n try {\n return fn();\n } finally {\n suppressComponentReadTrackingDepth -= 1;\n currentDerivedSubscriber = prevDerivedSubscriber;\n }\n}\n\nexport function syncDerivedDependencySubscriptions(\n subscriber: DerivedSubscriber,\n prevSources: Set<ReadableSource<unknown>>,\n nextSources: Set<ReadableSource<unknown>>\n): void {\n for (const source of prevSources) {\n if (!nextSources.has(source)) {\n source._derivedSubscribers?.delete(subscriber);\n }\n }\n\n for (const source of nextSources) {\n let subscribers = source._derivedSubscribers;\n if (!subscribers) {\n subscribers = new Set();\n source._derivedSubscribers = subscribers;\n }\n subscribers.add(subscriber);\n }\n}\n\nexport function clearDerivedDependencySubscriptions(\n subscriber: DerivedSubscriber,\n sources: Set<ReadableSource<unknown>>\n): void {\n for (const source of sources) {\n source._derivedSubscribers?.delete(subscriber);\n }\n sources.clear();\n}\n\nexport function markReadableDerivedSubscribersDirty(\n source: ReadableSource<unknown>\n): void {\n const subscribers = source._derivedSubscribers;\n if (!subscribers || subscribers.size === 0) {\n return;\n }\n\n for (const subscriber of subscribers) {\n subscriber._markDirty();\n }\n}\n\nexport function markReactivePropsDirtySource(\n source: ReadableSource<unknown>\n): void {\n try {\n (\n globalThis as {\n __ASKR_RENDERER?: RendererBridge;\n }\n ).__ASKR_RENDERER?.markReactivePropsDirtySource?.(source);\n } catch {\n // Keep readable notifications side-effect safe.\n }\n}\n\nexport function notifyReadableReaders(\n source: ReadableSource<unknown>,\n skipInstance?: ComponentInstance | null\n): boolean {\n const readers = source._readers;\n let didScheduleUpdate = false;\n\n if (!readers || readers.size === 0) {\n return false;\n }\n\n for (const [instance, token] of readers) {\n if (skipInstance && instance === skipInstance) {\n continue;\n }\n if (instance.lastRenderToken !== token) {\n continue;\n }\n if (instance.hasPendingUpdate) {\n continue;\n }\n\n instance.hasPendingUpdate = true;\n const task = instance._pendingFlushTask;\n if (task) {\n globalScheduler.enqueue(task);\n } else {\n globalScheduler.enqueue(() => {\n instance.hasPendingUpdate = false;\n instance.notifyUpdate?.();\n });\n }\n didScheduleUpdate = true;\n }\n\n return didScheduleUpdate;\n}\n"],"mappings":"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"}
@@ -1,5 +1,7 @@
1
+ import { renderDocument } from "../common/ssr.js";
1
2
  import { renderToString } from "../ssr/index.js";
2
3
  import { getOutputFilePath, interpolateRoutePath } from "./route-utils.js";
4
+ import { resolveSsgRouteData } from "./resolve-ssg-data.js";
3
5
  //#region src/ssg/batch-render.ts
4
6
  /**
5
7
  * Batch rendering of multiple routes for SSG
@@ -8,7 +10,7 @@ import { getOutputFilePath, interpolateRoutePath } from "./route-utils.js";
8
10
  * Render multiple routes in parallel with error handling
9
11
  */
10
12
  async function batchRenderRoutes(routes, options = {}) {
11
- const { seed = 12345, dataMap = {}, concurrency = 1 } = options;
13
+ const { seed = 12345, dataMap = {}, concurrency = 1, document } = options;
12
14
  const workerCount = Math.max(1, Math.min(concurrency, routes.length || 1));
13
15
  const results = [];
14
16
  results.length = routes.length;
@@ -16,7 +18,10 @@ async function batchRenderRoutes(routes, options = {}) {
16
18
  const renderOne = async (route) => {
17
19
  const startTime = performance.now();
18
20
  const url = interpolateRoutePath(route.path, route.params);
19
- const baseData = dataMap[route.path] ?? dataMap[url] ?? {};
21
+ const requestUrl = new URL(url, "http://localhost");
22
+ const resolvedData = resolveSsgRouteData(dataMap, route.path, url);
23
+ const baseData = resolvedData.hasData ? resolvedData.data : void 0;
24
+ const resourceCount = resolvedData.hasData && baseData ? Object.keys(baseData).length : 0;
20
25
  const mergedHandler = route.handler ? route.handler : (params, ctx) => {
21
26
  const component = route.component;
22
27
  return component({
@@ -33,7 +38,24 @@ async function batchRenderRoutes(routes, options = {}) {
33
38
  namespace: route.namespace
34
39
  }],
35
40
  seed,
36
- data: baseData
41
+ data: baseData,
42
+ document: document === void 0 ? void 0 : ({ appHtml }) => renderDocument(document, {
43
+ appHtml,
44
+ context: {
45
+ mode: "ssg",
46
+ url,
47
+ pathname: requestUrl.pathname,
48
+ search: requestUrl.search,
49
+ hash: requestUrl.hash,
50
+ params: route.params ?? {},
51
+ data: baseData,
52
+ seed,
53
+ route: {
54
+ path: route.path,
55
+ namespace: route.namespace
56
+ }
57
+ }
58
+ }, "createStaticGen()")
37
59
  });
38
60
  const duration = performance.now() - startTime;
39
61
  return {
@@ -42,7 +64,7 @@ async function batchRenderRoutes(routes, options = {}) {
42
64
  html,
43
65
  fileSize: Buffer.byteLength(html, "utf8"),
44
66
  renderDuration: Math.round(duration),
45
- resourceCount: Object.keys(baseData).length,
67
+ resourceCount,
46
68
  status: "success",
47
69
  reason: "full",
48
70
  written: false
@@ -55,7 +77,7 @@ async function batchRenderRoutes(routes, options = {}) {
55
77
  html: "",
56
78
  fileSize: 0,
57
79
  renderDuration: Math.round(duration),
58
- resourceCount: Object.keys(baseData).length,
80
+ resourceCount,
59
81
  status: "error",
60
82
  reason: "full",
61
83
  written: false,
@@ -1 +1 @@
1
- {"version":3,"file":"batch-render.js","names":[],"sources":["../../src/ssg/batch-render.ts"],"sourcesContent":["/**\n * Batch rendering of multiple routes for SSG\n */\n\nimport { renderToString } from '../ssr';\nimport type { RouteConfig, RouteRenderResult } from './types';\nimport type { RouteHandler } from '../common/router';\nimport type { ComponentFunction } from '../common/component';\nimport type { SSRData } from '../common/ssr';\nimport { getOutputFilePath, interpolateRoutePath } from './route-utils';\n\ninterface BatchRenderOptions {\n seed?: number;\n dataMap?: Record<string, SSRData>;\n concurrency?: number;\n}\n\n/**\n * Render multiple routes in parallel with error handling\n */\nexport async function batchRenderRoutes(\n routes: RouteConfig[],\n options: BatchRenderOptions = {}\n): Promise<RouteRenderResult[]> {\n const { seed = 12345, dataMap = {}, concurrency = 1 } = options;\n\n const workerCount = Math.max(1, Math.min(concurrency, routes.length || 1));\n const results: RouteRenderResult[] = [];\n results.length = routes.length;\n let nextIndex = 0;\n\n const renderOne = async (route: RouteConfig): Promise<RouteRenderResult> => {\n const startTime = performance.now();\n const url = interpolateRoutePath(route.path, route.params);\n const baseData = dataMap[route.path] ?? dataMap[url] ?? {};\n\n const mergedHandler: RouteHandler = route.handler\n ? route.handler\n : (params, ctx?: unknown) => {\n const component = route.component as ComponentFunction;\n return component(\n { ...route.props, ...params },\n ctx as { signal: AbortSignal; ssr?: unknown }\n );\n };\n\n try {\n const routeEntry = {\n path: route.path,\n handler: mergedHandler,\n namespace: route.namespace,\n };\n\n const html = renderToString({\n url,\n routes: [routeEntry],\n seed,\n data: baseData,\n });\n\n const duration = performance.now() - startTime;\n return {\n path: url,\n filePath: getOutputFilePath(url),\n html,\n fileSize: Buffer.byteLength(html, 'utf8'),\n renderDuration: Math.round(duration),\n resourceCount: Object.keys(baseData).length,\n status: 'success',\n reason: 'full',\n written: false,\n };\n } catch (error) {\n const duration = performance.now() - startTime;\n return {\n path: url,\n filePath: getOutputFilePath(url),\n html: '',\n fileSize: 0,\n renderDuration: Math.round(duration),\n resourceCount: Object.keys(baseData).length,\n status: 'error',\n reason: 'full',\n written: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n };\n\n const worker = async () => {\n while (true) {\n const current = nextIndex;\n nextIndex += 1;\n if (current >= routes.length) return;\n results[current] = await renderOne(routes[current]);\n }\n };\n\n await Promise.all(Array.from({ length: workerCount }, () => worker()));\n return results;\n}\n"],"mappings":";;;;;;;;;AAoBA,eAAsB,kBACpB,QACA,UAA8B,CAAC,GACD;CAC9B,MAAM,EAAE,OAAO,OAAO,UAAU,CAAC,GAAG,cAAc,MAAM;CAExD,MAAM,cAAc,KAAK,IAAI,GAAG,KAAK,IAAI,aAAa,OAAO,UAAU,CAAC,CAAC;CACzE,MAAM,UAA+B,CAAC;CACtC,QAAQ,SAAS,OAAO;CACxB,IAAI,YAAY;CAEhB,MAAM,YAAY,OAAO,UAAmD;EAC1E,MAAM,YAAY,YAAY,IAAI;EAClC,MAAM,MAAM,qBAAqB,MAAM,MAAM,MAAM,MAAM;EACzD,MAAM,WAAW,QAAQ,MAAM,SAAS,QAAQ,QAAQ,CAAC;EAEzD,MAAM,gBAA8B,MAAM,UACtC,MAAM,WACL,QAAQ,QAAkB;GACzB,MAAM,YAAY,MAAM;GACxB,OAAO,UACL;IAAE,GAAG,MAAM;IAAO,GAAG;GAAO,GAC5B,GACF;EACF;EAEJ,IAAI;GAOF,MAAM,OAAO,eAAe;IAC1B;IACA,QAAQ,CAAC;KAPT,MAAM,MAAM;KACZ,SAAS;KACT,WAAW,MAAM;IAKR,CAAU;IACnB;IACA,MAAM;GACR,CAAC;GAED,MAAM,WAAW,YAAY,IAAI,IAAI;GACrC,OAAO;IACL,MAAM;IACN,UAAU,kBAAkB,GAAG;IAC/B;IACA,UAAU,OAAO,WAAW,MAAM,MAAM;IACxC,gBAAgB,KAAK,MAAM,QAAQ;IACnC,eAAe,OAAO,KAAK,QAAQ,EAAE;IACrC,QAAQ;IACR,QAAQ;IACR,SAAS;GACX;EACF,SAAS,OAAO;GACd,MAAM,WAAW,YAAY,IAAI,IAAI;GACrC,OAAO;IACL,MAAM;IACN,UAAU,kBAAkB,GAAG;IAC/B,MAAM;IACN,UAAU;IACV,gBAAgB,KAAK,MAAM,QAAQ;IACnC,eAAe,OAAO,KAAK,QAAQ,EAAE;IACrC,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAC9D;EACF;CACF;CAEA,MAAM,SAAS,YAAY;EACzB,OAAO,MAAM;GACX,MAAM,UAAU;GAChB,aAAa;GACb,IAAI,WAAW,OAAO,QAAQ;GAC9B,QAAQ,WAAW,MAAM,UAAU,OAAO,QAAQ;EACpD;CACF;CAEA,MAAM,QAAQ,IAAI,MAAM,KAAK,EAAE,QAAQ,YAAY,SAAS,OAAO,CAAC,CAAC;CACrE,OAAO;AACT"}
1
+ {"version":3,"file":"batch-render.js","names":[],"sources":["../../src/ssg/batch-render.ts"],"sourcesContent":["/**\n * Batch rendering of multiple routes for SSG\n */\n\nimport { renderToString } from '../ssr';\nimport { renderDocument, type DocumentRenderer } from '../common/ssr';\nimport type { RouteConfig, RouteRenderResult } from './types';\nimport type { RouteHandler } from '../common/router';\nimport type { ComponentFunction } from '../common/component';\nimport type { SSRData } from '../common/ssr';\nimport { resolveSsgRouteData } from './resolve-ssg-data';\nimport { getOutputFilePath, interpolateRoutePath } from './route-utils';\n\ninterface BatchRenderOptions {\n seed?: number;\n dataMap?: Record<string, SSRData>;\n concurrency?: number;\n document?: DocumentRenderer;\n}\n\n/**\n * Render multiple routes in parallel with error handling\n */\nexport async function batchRenderRoutes(\n routes: RouteConfig[],\n options: BatchRenderOptions = {}\n): Promise<RouteRenderResult[]> {\n const { seed = 12345, dataMap = {}, concurrency = 1, document } = options;\n\n const workerCount = Math.max(1, Math.min(concurrency, routes.length || 1));\n const results: RouteRenderResult[] = [];\n results.length = routes.length;\n let nextIndex = 0;\n\n const renderOne = async (route: RouteConfig): Promise<RouteRenderResult> => {\n const startTime = performance.now();\n const url = interpolateRoutePath(route.path, route.params);\n const requestUrl = new URL(url, 'http://localhost');\n const resolvedData = resolveSsgRouteData(dataMap, route.path, url);\n const baseData = resolvedData.hasData ? resolvedData.data : undefined;\n const resourceCount =\n resolvedData.hasData && baseData ? Object.keys(baseData).length : 0;\n\n const mergedHandler: RouteHandler = route.handler\n ? route.handler\n : (params, ctx?: unknown) => {\n const component = route.component as ComponentFunction;\n return component(\n { ...route.props, ...params },\n ctx as { signal: AbortSignal; ssr?: unknown }\n );\n };\n\n try {\n const routeEntry = {\n path: route.path,\n handler: mergedHandler,\n namespace: route.namespace,\n };\n\n const html = renderToString({\n url,\n routes: [routeEntry],\n seed,\n data: baseData,\n document:\n document === undefined\n ? undefined\n : ({ appHtml }) =>\n renderDocument(\n document,\n {\n appHtml,\n context: {\n mode: 'ssg',\n url,\n pathname: requestUrl.pathname,\n search: requestUrl.search,\n hash: requestUrl.hash,\n params: route.params ?? {},\n data: baseData,\n seed,\n route: {\n path: route.path,\n namespace: route.namespace,\n },\n },\n },\n 'createStaticGen()'\n ),\n });\n\n const duration = performance.now() - startTime;\n return {\n path: url,\n filePath: getOutputFilePath(url),\n html,\n fileSize: Buffer.byteLength(html, 'utf8'),\n renderDuration: Math.round(duration),\n resourceCount,\n status: 'success',\n reason: 'full',\n written: false,\n };\n } catch (error) {\n const duration = performance.now() - startTime;\n return {\n path: url,\n filePath: getOutputFilePath(url),\n html: '',\n fileSize: 0,\n renderDuration: Math.round(duration),\n resourceCount,\n status: 'error',\n reason: 'full',\n written: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n };\n\n const worker = async () => {\n while (true) {\n const current = nextIndex;\n nextIndex += 1;\n if (current >= routes.length) return;\n results[current] = await renderOne(routes[current]);\n }\n };\n\n await Promise.all(Array.from({ length: workerCount }, () => worker()));\n return results;\n}\n"],"mappings":";;;;;;;;;;;AAuBA,eAAsB,kBACpB,QACA,UAA8B,CAAC,GACD;CAC9B,MAAM,EAAE,OAAO,OAAO,UAAU,CAAC,GAAG,cAAc,GAAG,aAAa;CAElE,MAAM,cAAc,KAAK,IAAI,GAAG,KAAK,IAAI,aAAa,OAAO,UAAU,CAAC,CAAC;CACzE,MAAM,UAA+B,CAAC;CACtC,QAAQ,SAAS,OAAO;CACxB,IAAI,YAAY;CAEhB,MAAM,YAAY,OAAO,UAAmD;EAC1E,MAAM,YAAY,YAAY,IAAI;EAClC,MAAM,MAAM,qBAAqB,MAAM,MAAM,MAAM,MAAM;EACzD,MAAM,aAAa,IAAI,IAAI,KAAK,kBAAkB;EAClD,MAAM,eAAe,oBAAoB,SAAS,MAAM,MAAM,GAAG;EACjE,MAAM,WAAW,aAAa,UAAU,aAAa,OAAO;EAC5D,MAAM,gBACJ,aAAa,WAAW,WAAW,OAAO,KAAK,QAAQ,EAAE,SAAS;EAEpE,MAAM,gBAA8B,MAAM,UACtC,MAAM,WACL,QAAQ,QAAkB;GACzB,MAAM,YAAY,MAAM;GACxB,OAAO,UACL;IAAE,GAAG,MAAM;IAAO,GAAG;GAAO,GAC5B,GACF;EACF;EAEJ,IAAI;GAOF,MAAM,OAAO,eAAe;IAC1B;IACA,QAAQ,CAAC;KAPT,MAAM,MAAM;KACZ,SAAS;KACT,WAAW,MAAM;IAKR,CAAU;IACnB;IACA,MAAM;IACN,UACE,aAAa,SACT,UACC,EAAE,cACD,eACE,UACA;KACE;KACA,SAAS;MACP,MAAM;MACN;MACA,UAAU,WAAW;MACrB,QAAQ,WAAW;MACnB,MAAM,WAAW;MACjB,QAAQ,MAAM,UAAU,CAAC;MACzB,MAAM;MACN;MACA,OAAO;OACL,MAAM,MAAM;OACZ,WAAW,MAAM;MACnB;KACF;IACF,GACA,mBACF;GACV,CAAC;GAED,MAAM,WAAW,YAAY,IAAI,IAAI;GACrC,OAAO;IACL,MAAM;IACN,UAAU,kBAAkB,GAAG;IAC/B;IACA,UAAU,OAAO,WAAW,MAAM,MAAM;IACxC,gBAAgB,KAAK,MAAM,QAAQ;IACnC;IACA,QAAQ;IACR,QAAQ;IACR,SAAS;GACX;EACF,SAAS,OAAO;GACd,MAAM,WAAW,YAAY,IAAI,IAAI;GACrC,OAAO;IACL,MAAM;IACN,UAAU,kBAAkB,GAAG;IAC/B,MAAM;IACN,UAAU;IACV,gBAAgB,KAAK,MAAM,QAAQ;IACnC;IACA,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAC9D;EACF;CACF;CAEA,MAAM,SAAS,YAAY;EACzB,OAAO,MAAM;GACX,MAAM,UAAU;GAChB,aAAa;GACb,IAAI,WAAW,OAAO,QAAQ;GAC9B,QAAQ,WAAW,MAAM,UAAU,OAAO,QAAQ;EACpD;CACF;CAEA,MAAM,QAAQ,IAAI,MAAM,KAAK,EAAE,QAAQ,YAAY,SAAS,OAAO,CAAC,CAAC;CACrE,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"create-static-gen.d.ts","names":[],"sources":["../../src/ssg/create-static-gen.ts"],"mappings":";;;KA2CK,cAAA,GAAiB,WAAW;AAAA,KAE5B,iBAAA,gBAAiC,cAAA,IAAkB,IAAA,CACtD,MAAA;EAGA,MAAA,GAAS,WAAA,CAAY,MAAA;EACrB,OAAA,GAAU,WAAA,CAAY,MAAA;AAAA;AAAA,KAGnB,kBAAA,0BAA4C,cAAA,yBAC9B,OAAA,GAAU,OAAA,CAAQ,MAAA,UAAgB,cAAA,GAC/C,iBAAA,CAAkB,OAAA,CAAQ,MAAA;;;;;;;;;;;;;;;;;;AALF;AAAA;;iBAqFd,eAAA,gCACiB,cAAA,IAE/B,OAAA,EAAS,IAAA,CAAK,UAAA,CAAW,OAAA;EACvB,MAAA,EAAQ,OAAA,GAAU,kBAAA,CAAmB,OAAA;AAAA;EArFZ;;;;;6BA+GN,kBAAA,GAChB,OAAA,CAAQ,SAAA;EA/GQ;;;;;;;;;;;EAAS;;AAAM;AAgFtC;eAkRiB,SAAA;AAAA"}
1
+ {"version":3,"file":"create-static-gen.d.ts","names":[],"sources":["../../src/ssg/create-static-gen.ts"],"mappings":";;;KA4CK,cAAA,GAAiB,WAAW;AAAA,KAE5B,iBAAA,gBAAiC,cAAA,IAAkB,IAAA,CACtD,MAAA;EAGA,MAAA,GAAS,WAAA,CAAY,MAAA;EACrB,OAAA,GAAU,WAAA,CAAY,MAAA;AAAA;AAAA,KAGnB,kBAAA,0BAA4C,cAAA,yBAC9B,OAAA,GAAU,OAAA,CAAQ,MAAA,UAAgB,cAAA,GAC/C,iBAAA,CAAkB,OAAA,CAAQ,MAAA;;;;;;;;;;;;;;;;;;AALF;AAAA;;iBAqFd,eAAA,gCACiB,cAAA,IAE/B,OAAA,EAAS,IAAA,CAAK,UAAA,CAAW,OAAA;EACvB,MAAA,EAAQ,OAAA,GAAU,kBAAA,CAAmB,OAAA;AAAA;EArFZ;;;;;6BA+GN,kBAAA,GAChB,OAAA,CAAQ,SAAA;EA/GQ;;;;;;;;;;;EAAS;;AAAM;AAgFtC;eAwRiB,SAAA;AAAA"}