@askrjs/askr 0.0.33 → 0.0.35

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.
@@ -1 +1 @@
1
- {"version":3,"file":"route.js","names":[],"sources":["../../src/router/route.ts"],"sourcesContent":["/**\n * Route definition, registration, and matching.\n *\n * Primary public authoring:\n * - `registerRoutes(() => { ... }, options?)`\n * - `group(options, () => { ... })`\n * - `route(path, Component, options?)`\n * - `fallback(Component)`\n * - `currentRoute()` for render-time access\n *\n */\n\nimport { matchSegments, parseSegments, computeRank } from './match';\nimport {\n buildRouteContext,\n buildRouteContextBase,\n deepFreeze,\n makeQuery,\n parseLocation,\n} from './route-context';\nimport { getCurrentComponentInstance } from '../runtime/component';\nimport { getExecutionModel } from '../runtime/execution-model';\nimport { getRenderContext } from '../ssr/context';\nimport {\n requireAuth,\n requireGuest,\n requirePermission,\n requireRole,\n} from './policy';\n\nexport type {\n AccessDecision,\n AccessDenyDecision,\n AccessRedirectDecision,\n GroupHelperOptions,\n RegisterRoutesOptions,\n RouteDefinition,\n RouteAuthOptions,\n RouteHandler,\n Route,\n RouteContext,\n RoutePolicy,\n RouteRenderResult,\n RouteRequestOptions,\n RouteRequestResult,\n ResolvedRoute,\n RouteMatch,\n RouteQuery,\n RouteSnapshot,\n RouteComponent,\n RouteOptions,\n ParsedSegment,\n LayoutScopeRecord,\n RouteRecord,\n RouteManifest,\n} from '../common/router';\n\nimport type {\n AccessDecision,\n AccessDenyDecision,\n AccessRedirectDecision,\n GroupHelperOptions,\n RegisterRoutesOptions,\n RouteDefinition,\n RouteAuthOptions,\n RouteHandler,\n Route,\n RouteContext,\n RoutePolicy,\n RouteRenderResult,\n RouteRequestOptions,\n RouteRequestResult,\n ResolvedRoute,\n RouteMatch,\n RouteQuery,\n RouteSnapshot,\n RouteComponent,\n RouteOptions,\n LayoutScopeRecord,\n RouteRecord,\n RouteManifest,\n} from '../common/router';\n\n// ---------------------------------------------------------------------------\n// Module-level stores\n// ---------------------------------------------------------------------------\n\n/** Legacy flat route array — kept for resolver and route() accessor backward compat. */\nconst routes: Route[] = [];\n\n/** Normalized route records built by the declarative registration API. */\nconst records: RouteRecord[] = [];\n\ntype RegistrationScope = {\n layout?: LayoutScopeRecord['component'];\n policies: readonly RoutePolicy[];\n state: AccessScopeState;\n};\n\ntype RegistrationSession = {\n authConfigured: boolean;\n};\n\n/** Active registration scope stack during module-load-time registration. */\nconst registrationScopeStack: RegistrationScope[] = [];\nconst registrationSessionStack: RegistrationSession[] = [];\n\nconst namespaces = new Set<string>();\n\ntype AccessScopeState = {\n guestOnly: boolean;\n authenticated: boolean;\n};\n\nlet defaultRouteAuthOptions: RouteAuthOptions | undefined;\nlet activeClientRouteAuthOptions: RouteAuthOptions | undefined;\n\nconst HAS_ROUTES_KEY = Symbol.for('__ASKR_HAS_ROUTES__');\n\nfunction setHasRoutes(value: boolean): void {\n try {\n const g = globalThis as unknown as Record<string | symbol, unknown>;\n g[HAS_ROUTES_KEY] = value;\n } catch {\n // ignore\n }\n}\n\n// Initialize to false at module load.\nsetHasRoutes(false);\n\n// Route index by depth - maintains insertion order\nconst routesByDepth = new Map<number, Route[]>();\n\n/**\n * Parse route path depth\n */\nfunction getDepth(path: string): number {\n const normalized =\n path.endsWith('/') && path !== '/' ? path.slice(0, -1) : path;\n return normalized === '/' ? 0 : normalized.split('/').filter(Boolean).length;\n}\n\n// ---------------------------------------------------------------------------\n// WeakMap caches for external Route[] objects (e.g. SSR per-request tables)\n// Amortises parseSegments / computeRank across repeated resolveRouteFromRoutes\n// calls on the same route list (same object reference).\n// ---------------------------------------------------------------------------\n\n/** Pre-parsed segments for an externally supplied Route object. */\nconst routeSegsCache = new WeakMap<Route, ReturnType<typeof parseSegments>>();\n/** Pre-computed rank for an externally supplied Route object. */\nconst routeRankCache = new WeakMap<Route, number>();\n/** A rank-descending sorted copy of an external readonly Route array. */\nconst sortedListCache = new WeakMap<\n ReadonlyArray<Route>,\n ReadonlyArray<Route>\n>();\n\nfunction cachedSegs(r: Route): ReturnType<typeof parseSegments> {\n let s = routeSegsCache.get(r);\n if (!s) {\n s = parseSegments(r.path);\n routeSegsCache.set(r, s);\n }\n return s;\n}\n\nfunction cachedRank(r: Route): number {\n let n = routeRankCache.get(r);\n if (n === undefined) {\n n = computeRank(cachedSegs(r));\n routeRankCache.set(r, n);\n }\n return n;\n}\n\nfunction cachedSortedList(\n routeList: ReadonlyArray<Route>\n): ReadonlyArray<Route> {\n let sorted = sortedListCache.get(routeList);\n if (!sorted) {\n sorted = [...routeList].sort((a, b) => cachedRank(b) - cachedRank(a));\n sortedListCache.set(routeList, sorted);\n }\n return sorted;\n}\n\n// SSR helper: when rendering on the server, callers may set a location so that\n// render-time route() returns deterministic server values that match client\n// hydration. This is deliberately an opt-in escape for SSR and tests.\nlet serverLocation: string | null = null;\n\nexport function setServerLocation(url: string | null): void {\n serverLocation = url;\n}\n\nfunction isPromise<T>(value: T | Promise<T>): value is Promise<T> {\n return value instanceof Promise;\n}\n\n// Compute matches for a specific route list.\nfunction computeMatchesFromRoutes(\n pathname: string,\n routesList: readonly Route[]\n): RouteMatch[] {\n const matches: Array<{\n pattern: string;\n params: Record<string, string>;\n name?: string;\n namespace?: string;\n rank: number;\n }> = [];\n\n const normalized =\n pathname.endsWith('/') && pathname !== '/'\n ? pathname.slice(0, -1)\n : pathname;\n const urlParts =\n normalized === '/' ? [] : normalized.split('/').filter(Boolean);\n\n for (const r of routesList) {\n const params = matchSegments(urlParts, cachedSegs(r));\n if (params !== null) {\n matches.push({\n pattern: r.path,\n params,\n name: (r as { name?: string }).name,\n namespace: r.namespace,\n rank: cachedRank(r),\n });\n }\n }\n\n matches.sort((a, b) => b.rank - a.rank);\n\n return matches.map((m) => ({\n path: m.pattern,\n params: deepFreeze({ ...m.params }),\n name: m.name,\n namespace: m.namespace,\n }));\n}\n\nfunction getActiveRoutes(): readonly Route[] {\n const renderContext = getRenderContext();\n return renderContext?.routes ?? routes;\n}\n\nfunction getActiveRouteAuthOptions(\n override?: RouteAuthOptions\n): RouteAuthOptions | undefined {\n if (override) {\n return override;\n }\n\n const renderContext = getRenderContext();\n return (\n renderContext?.routeAuth ??\n activeClientRouteAuthOptions ??\n defaultRouteAuthOptions\n );\n}\n\nexport function _setActiveRouteAuthOptions(\n auth: RouteAuthOptions | undefined\n): void {\n activeClientRouteAuthOptions = auth;\n}\n\nfunction getCurrentRegistrationSession(): RegistrationSession {\n return (\n registrationSessionStack[registrationSessionStack.length - 1] ?? {\n authConfigured: !!defaultRouteAuthOptions?.resolve,\n }\n );\n}\n\nfunction getCurrentAccessScopeState(): AccessScopeState {\n return (\n registrationScopeStack[registrationScopeStack.length - 1]?.state ?? {\n guestOnly: false,\n authenticated: false,\n }\n );\n}\n\nfunction getCurrentLayoutChain(): LayoutScopeRecord[] {\n const layoutChain: LayoutScopeRecord[] = [];\n\n for (const scope of registrationScopeStack) {\n if (scope.layout) {\n layoutChain.push({ component: scope.layout });\n }\n }\n\n return layoutChain;\n}\n\nfunction getCurrentInheritedPolicies(): RoutePolicy[] {\n const policies: RoutePolicy[] = [];\n\n for (const scope of registrationScopeStack) {\n if (scope.policies.length > 0) {\n policies.push(...scope.policies);\n }\n }\n\n return policies;\n}\n\n// ---------------------------------------------------------------------------\n// Registration lock\n// ---------------------------------------------------------------------------\n\n/**\n * Prevent route registrations after the app has started.\n * Enforced in production; tests may unlock explicitly.\n */\nlet registrationLocked = false;\n\nexport function lockRouteRegistration(): void {\n registrationLocked = true;\n}\n\nexport function _lockRouteRegistrationForTests(): void {\n registrationLocked = true;\n}\n\nexport function _unlockRouteRegistrationForTests(): void {\n registrationLocked = false;\n}\n\n// ---------------------------------------------------------------------------\n// Path validation\n// ---------------------------------------------------------------------------\n\nfunction validateRoutePath(path: string): void {\n if (!path.startsWith('/')) {\n throw new Error(`Route path must begin with \"/\". Got: \"${path}\"`);\n }\n // Reject Express-style :param syntax — Askr uses {param} interpolation\n if (/:([^/{}]+)/.test(path)) {\n const suggested = path.replace(/:([^/{}]+)/g, '{$1}');\n throw new Error(\n `Route parameter syntax uses {name} interpolation, not :name. ` +\n `Use \"${suggested}\" instead of \"${path}\".`\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Internal helper: insert a RouteRecord in rank-descending order\n// so that resolveRoute can use first-match-wins without sorting.\n// Ties preserve declaration order (stable binary-search insertion).\n// ---------------------------------------------------------------------------\n\nfunction insertRecordSorted(record: RouteRecord): void {\n let lo = 0;\n let hi = records.length;\n // Find the insertion point: after all existing records with rank >= this one\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (records[mid].rank >= record.rank) lo = mid + 1;\n else hi = mid;\n }\n records.splice(lo, 0, record);\n}\n\n// ---------------------------------------------------------------------------\n// Internal helper: add a single pre-built Route object to the runtime stores\n// ---------------------------------------------------------------------------\n\nfunction addRouteToStores(routeObj: Route): void {\n routes.push(routeObj);\n setHasRoutes(true);\n\n const depth = getDepth(routeObj.path);\n let depthRoutes = routesByDepth.get(depth);\n if (!depthRoutes) {\n depthRoutes = [];\n routesByDepth.set(depth, depthRoutes);\n }\n depthRoutes.push(routeObj);\n\n if (routeObj.namespace) {\n namespaces.add(routeObj.namespace);\n }\n}\n\n// ---------------------------------------------------------------------------\n// lazy() — eager-prefetch code-split component wrapper\n// ---------------------------------------------------------------------------\n\n/** Promises from in-flight lazy() imports, drained by createSPA / hydrateSPA. */\nconst pendingLazy = new Set<Promise<unknown>>();\n\n/**\n * Snapshot the current in-flight lazy() imports.\n * Boot uses this before clearing route state so manifest-based startup can\n * still await chunks that were kicked off during route module evaluation.\n */\nexport function _snapshotLazy(): Promise<unknown>[] {\n return [...pendingLazy];\n}\n\n/**\n * Declare a code-split route component. The `import()` fires immediately at\n * module evaluation time (creating a bundler split point), and the resolved\n * chunk is guaranteed to be available before the app mounts — so the renderer\n * always receives a plain synchronous function.\n *\n * ```ts\n * registerRoutes(() => {\n * group({ layout: AppLayout }, () => {\n * route('/', lazy(() => import('./pages/landing')));\n * route('/dashboard', lazy(() => import('./pages/dashboard')));\n * });\n * });\n * ```\n *\n * The module must export the component as its **default** export:\n * ```ts\n * // pages/dashboard.tsx\n * export default function DashboardPage() { … }\n * ```\n */\nexport function lazy(\n factory: () => Promise<{ default: RouteComponent } | RouteComponent>\n): RouteComponent {\n let resolved: RouteComponent | null = null;\n let loadError: unknown = null;\n\n const promise = factory().then(\n (mod) => {\n resolved =\n typeof mod === 'function'\n ? mod\n : (mod as { default: RouteComponent }).default;\n pendingLazy.delete(promise);\n },\n (err: unknown) => {\n loadError = err;\n pendingLazy.delete(promise);\n }\n );\n pendingLazy.add(promise);\n\n return (params) => {\n if (loadError) throw loadError as Error;\n if (!resolved) {\n throw new Error(\n 'lazy() component used before it was resolved. ' +\n 'Await createSPA() / hydrateSPA() to ensure all chunks load first.'\n );\n }\n return resolved(params);\n };\n}\n\n/**\n * Wait for all pending `lazy()` imports to settle.\n * Called automatically by `createSPA` / `hydrateSPA` before mounting.\n */\nexport function _drainLazy(\n additionalPending: Iterable<Promise<unknown>> = []\n): Promise<void> {\n const combined = new Set<Promise<unknown>>([\n ...additionalPending,\n ...pendingLazy,\n ]);\n if (combined.size === 0) return Promise.resolve();\n return Promise.allSettled(combined).then(() => undefined);\n}\n\nexport function group(options: GroupHelperOptions, fn: RouteDefinition): void;\nexport function group(options: GroupHelperOptions, fn: RouteDefinition): void {\n pushGroupScope(options, fn);\n}\n\nexport function fallback(Component: RouteComponent): void {\n const allowsRootFallback = registrationScopeStack.every(\n (scope) =>\n scope.policies.length === 0 &&\n !scope.state.guestOnly &&\n !scope.state.authenticated\n );\n\n if (!allowsRootFallback) {\n throw new Error(\n 'fallback() can only be registered at the root scope. ' +\n 'Use route(\"/*\", Component) if you need compatibility behavior.'\n );\n }\n\n route('/*', Component);\n}\n\nfunction pushRegistrationScope(\n scope: RegistrationScope,\n fn: RouteDefinition\n): void {\n registrationScopeStack.push(scope);\n try {\n fn();\n } finally {\n registrationScopeStack.pop();\n }\n}\n\nfunction pushGroupScope(\n options: GroupHelperOptions,\n fn: RouteDefinition\n): void {\n const session = getCurrentRegistrationSession();\n validateAccessMetadata(options, {\n authConfigured: session.authConfigured,\n state: getCurrentAccessScopeState(),\n });\n const policies = compileNodePolicies(options);\n\n pushRegistrationScope(\n {\n layout: options.layout,\n policies,\n state: nextAccessScopeState(options, getCurrentAccessScopeState()),\n },\n fn\n );\n}\n\nfunction hasBuiltInAuthMetadata(\n node: Pick<GroupHelperOptions | RouteOptions, 'auth' | 'role' | 'permission'>\n): boolean {\n return node.auth !== undefined || !!node.role || !!node.permission;\n}\n\nfunction validateSameNodeAccessMetadata(\n node: Pick<GroupHelperOptions | RouteOptions, 'auth' | 'role' | 'permission'>\n): void {\n if (node.auth === 'guest' && (!!node.role || !!node.permission)) {\n throw new Error(\n 'Guest-only routes cannot be combined with role or permission requirements.'\n );\n }\n}\n\nfunction validateAccessMetadata(\n node: Pick<GroupHelperOptions | RouteOptions, 'auth' | 'role' | 'permission'>,\n context: {\n authConfigured: boolean;\n state: AccessScopeState;\n }\n): void {\n validateSameNodeAccessMetadata(node);\n\n const requiresAuthenticated =\n node.auth === true || !!node.role || !!node.permission;\n\n if (\n node.auth === 'guest' &&\n (context.state.authenticated || !!node.role || !!node.permission)\n ) {\n throw new Error(\n 'Guest-only routes cannot be combined with authenticated access requirements.'\n );\n }\n\n if (context.state.guestOnly && requiresAuthenticated) {\n throw new Error(\n 'Child routes cannot weaken a guest-only access scope with authenticated requirements.'\n );\n }\n\n if (hasBuiltInAuthMetadata(node) && !context.authConfigured) {\n throw new Error(\n 'Routes using `auth`, `role`, or `permission` require `auth.resolve` in registerRoutes(...).'\n );\n }\n}\n\nfunction nextAccessScopeState(\n node: Pick<GroupHelperOptions | RouteOptions, 'auth' | 'role' | 'permission'>,\n state: AccessScopeState\n): AccessScopeState {\n const requiresAuthenticated =\n node.auth === true || !!node.role || !!node.permission;\n\n return {\n guestOnly: state.guestOnly || node.auth === 'guest',\n authenticated: state.authenticated || requiresAuthenticated,\n };\n}\n\nfunction compileNodePolicies(\n node: Pick<\n RouteOptions | GroupHelperOptions,\n 'auth' | 'role' | 'permission' | 'policies'\n >\n): RoutePolicy[] {\n validateSameNodeAccessMetadata(node);\n\n const compiled: RoutePolicy[] = [];\n\n if (node.auth === true) {\n compiled.push(requireAuth());\n } else if (node.auth === 'guest') {\n compiled.push(requireGuest());\n }\n\n if (node.role) {\n compiled.push(requireRole(node.role));\n }\n\n if (node.permission) {\n compiled.push(requirePermission(node.permission));\n }\n\n if (node.policies?.length) {\n compiled.push(...node.policies);\n }\n\n return compiled;\n}\n\nfunction normalizeRouteOptions(\n options: RouteOptions | undefined\n): RouteOptions | undefined {\n if (!options) {\n return undefined;\n }\n\n const loader = options.loader;\n const policies = compileNodePolicies(options);\n\n if (\n !loader &&\n !options.entries &&\n policies.length === 0 &&\n !options.title &&\n !options.namespace &&\n options.auth === undefined &&\n !options.role &&\n !options.permission\n ) {\n return undefined;\n }\n\n return {\n ...(loader ? { loader } : {}),\n ...(options.entries ? { entries: options.entries } : {}),\n ...(options.auth !== undefined ? { auth: options.auth } : {}),\n ...(options.role ? { role: options.role } : {}),\n ...(options.permission ? { permission: options.permission } : {}),\n ...(policies.length > 0 ? { policies } : {}),\n ...(options.title ? { title: options.title } : {}),\n ...(options.namespace ? { namespace: options.namespace } : {}),\n };\n}\nexport function registerRoutes(\n definition: RouteDefinition,\n options: RegisterRoutesOptions = {}\n): void {\n defaultRouteAuthOptions = options.auth;\n\n registrationSessionStack.push({\n authConfigured: !!options.auth?.resolve,\n });\n try {\n definition();\n } finally {\n registrationSessionStack.pop();\n }\n}\n\n// ---------------------------------------------------------------------------\n// route() — dual-purpose: registration (module load) + accessor (render time)\n// ---------------------------------------------------------------------------\n\nfunction readCurrentRouteSnapshot(): RouteSnapshot {\n const instance = getCurrentComponentInstance();\n if (!instance) {\n throw new Error(\n 'currentRoute() can only be called during component render execution. ' +\n 'Call currentRoute() from inside your component function.'\n );\n }\n\n let pathname = '/';\n let search = '';\n let hash = '';\n const renderContext = getRenderContext();\n\n if (instance.ssr && renderContext?.url) {\n const parsed = parseLocation(renderContext.url);\n pathname = parsed.pathname;\n search = parsed.search;\n hash = parsed.hash;\n } else if (typeof window !== 'undefined' && window.location) {\n pathname = window.location.pathname || '/';\n search = window.location.search || '';\n hash = window.location.hash || '';\n } else if (serverLocation) {\n const parsed = parseLocation(serverLocation);\n pathname = parsed.pathname;\n search = parsed.search;\n hash = parsed.hash;\n }\n\n const query = makeQuery(search);\n const matches = computeMatchesFromRoutes(pathname, getActiveRoutes());\n const instanceParams = instance.props as Record<string, string>;\n const routeParams =\n Object.keys(instanceParams).length > 0\n ? instanceParams\n : (matches[0]?.params ?? {});\n const params = deepFreeze({\n ...routeParams,\n });\n\n return Object.freeze({\n path: pathname,\n params,\n query,\n hash: hash || null,\n matches: Object.freeze(matches),\n });\n}\n\nexport function currentRoute(): RouteSnapshot {\n return readCurrentRouteSnapshot();\n}\n\n/**\n * Register a route.\n *\n * ```ts\n * route('/posts/{slug}', PostPage, {\n * loader: ({ params }) => getPost(params.slug),\n * entries: async () => getPosts().map(p => ({ slug: p.slug })),\n * title: 'Post',\n * policies:[requireAuth()],\n * });\n * ```\n */\nexport function route(\n path: string,\n Component: RouteComponent,\n options?: RouteOptions\n): void;\nexport function route(\n path: string,\n Component: RouteComponent,\n options?: RouteOptions\n): void {\n if (getExecutionModel() === 'islands') {\n throw new Error(\n 'Routes are not supported with islands. Use createSPA (client) or createSSR (server) instead.'\n );\n }\n\n // ── Render-time accessor (no arguments) ─────────────────────────────────\n if (typeof path === 'undefined') {\n throw new Error(\n 'route() is only for route registration. Use currentRoute() inside components.'\n );\n }\n\n // ── Registration mode ────────────────────────────────────────────────────\n\n // Disallow registration during SSR render\n const currentInst = getCurrentComponentInstance();\n if (currentInst && currentInst.ssr) {\n throw new Error(\n 'route() cannot be called during SSR rendering. Register routes at module load time instead.'\n );\n }\n\n if (registrationLocked) {\n throw new Error(\n 'Route registration is locked after app startup. ' +\n 'Register routes at module load time before calling createSPA or createSSR.'\n );\n }\n\n if (typeof Component !== 'function') {\n throw new Error(\n 'route(path, Component) requires a component function as the second argument. ' +\n 'Passing JSX elements or VNodes directly is not supported.'\n );\n }\n\n validateRoutePath(path);\n validateAccessMetadata(options ?? {}, {\n authConfigured: getCurrentRegistrationSession().authConfigured,\n state: getCurrentAccessScopeState(),\n });\n\n const chain = getCurrentLayoutChain();\n const segments = parseSegments(path);\n const rank = computeRank(segments);\n const isFallback = path === '/*';\n const comp = Component;\n const normalizedOptions = normalizeRouteOptions(options);\n const policies = [\n ...getCurrentInheritedPolicies(),\n ...(normalizedOptions?.policies ?? []),\n ];\n\n const handler: RouteHandler = (params) => {\n let content = comp(params);\n\n for (let i = chain.length - 1; i >= 0; i--) {\n content = chain[i].component({ children: content });\n }\n\n return content;\n };\n\n const record: RouteRecord = {\n path,\n component: comp,\n segments,\n rank,\n layoutChain: chain,\n options: normalizedOptions\n ? {\n ...normalizedOptions,\n ...(policies.length > 0 ? { policies } : {}),\n }\n : policies.length > 0\n ? { policies }\n : {},\n isFallback,\n handler,\n };\n\n insertRecordSorted(record);\n addRouteToStores({\n path,\n handler,\n namespace: normalizedOptions?.namespace ?? options?.namespace,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Manifest access\n// ---------------------------------------------------------------------------\n\n/**\n * Return the normalized route manifest built from registered route definitions.\n *\n * Pass this to `createSPA`, `hydrateSPA`, or `renderToString` as the\n * authoritative routing input:\n *\n * ```ts\n * import { getManifest } from '@askrjs/askr/router';\n * await createSPA({ root: '#app', manifest: getManifest() });\n * ```\n */\nexport function getManifest(): RouteManifest {\n return {\n records: [...records],\n ...(defaultRouteAuthOptions ? { auth: defaultRouteAuthOptions } : {}),\n };\n}\n\n/**\n * Internal: apply a pre-built manifest to the runtime stores without running\n * route() again. Called by createSPA / hydrateSPA when a manifest is passed.\n */\nexport function _applyManifest(manifest: RouteManifest): void {\n defaultRouteAuthOptions = manifest.auth;\n for (const record of manifest.records) {\n records.push(record);\n addRouteToStores({\n path: record.path,\n handler: record.handler,\n namespace: record.options.namespace,\n });\n }\n // Sort once after bulk insert so resolveRoute can use first-match-wins\n records.sort((a, b) => b.rank - a.rank);\n}\n\n// ---------------------------------------------------------------------------\n// Route collection helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Get all registered routes (flat list, insertion order).\n * Prefer `getManifest()` when metadata (loader, policies, entries) is needed.\n */\nexport function getRoutes(): Route[] {\n return [...routes];\n}\n\n/** Get routes for a specific namespace. */\nexport function getNamespaceRoutes(namespace: string): Route[] {\n return routes.filter((r) => r.namespace === namespace);\n}\n\n/** Unload all routes from a namespace (for MFE unmounting). */\nexport function unloadNamespace(namespace: string): number {\n const before = routes.length;\n\n for (let i = routes.length - 1; i >= 0; i--) {\n if (routes[i].namespace === namespace) {\n const removed = routes[i];\n routes.splice(i, 1);\n\n const depth = getDepth(removed.path);\n const depthRoutes = routesByDepth.get(depth);\n if (depthRoutes) {\n const idx = depthRoutes.indexOf(removed);\n if (idx >= 0) depthRoutes.splice(idx, 1);\n }\n }\n }\n\n // Remove matching records too\n for (let i = records.length - 1; i >= 0; i--) {\n if (records[i].options.namespace === namespace) {\n records.splice(i, 1);\n }\n }\n\n namespaces.delete(namespace);\n return before - routes.length;\n}\n\n/** Clear all registered routes and records (testing / boot reset). */\nexport function clearRoutes(): void {\n routes.length = 0;\n records.length = 0;\n namespaces.clear();\n routesByDepth.clear();\n registrationScopeStack.length = 0;\n registrationSessionStack.length = 0;\n registrationLocked = false;\n defaultRouteAuthOptions = undefined;\n activeClientRouteAuthOptions = undefined;\n setHasRoutes(false);\n pendingLazy.clear();\n}\n\n/**\n * Get all loaded namespaces (MFE identifiers)\n */\nexport function getLoadedNamespaces(): string[] {\n return Array.from(namespaces);\n}\n\n/**\n * Resolve a path to a route handler.\n *\n * Hot path: walks the module-level `records[]` array which is kept sorted by\n * rank descending at registration time — so the first `matchSegments` hit is\n * always the most specific match. No per-call allocations for the common\n * case of purely-static routes.\n */\nexport function resolveRoute(pathname: string): ResolvedRoute | null {\n const normalized =\n pathname.endsWith('/') && pathname !== '/'\n ? pathname.slice(0, -1)\n : pathname;\n const urlParts =\n normalized === '/' ? [] : normalized.split('/').filter(Boolean);\n\n for (const record of records) {\n const params = matchSegments(urlParts, record.segments);\n if (params !== null) {\n return { handler: record.handler, params };\n }\n }\n return null;\n}\n\nfunction getMatchingRecord(\n target: string,\n routeRecords: readonly RouteRecord[]\n): { record: RouteRecord; params: Record<string, string> } | null {\n const location = parseLocation(target);\n const normalized =\n location.pathname.endsWith('/') && location.pathname !== '/'\n ? location.pathname.slice(0, -1)\n : location.pathname;\n const urlParts =\n normalized === '/' ? [] : normalized.split('/').filter(Boolean);\n\n for (const record of routeRecords) {\n const params = matchSegments(urlParts, record.segments);\n if (params !== null) {\n return { record, params };\n }\n }\n\n return null;\n}\n\nfunction getRoutePolicies(\n options: RouteOptions | undefined\n): readonly RoutePolicy[] {\n if (!options) {\n return [];\n }\n\n if (options.policies?.length) {\n return options.policies;\n }\n\n return compileNodePolicies(options);\n}\n\nfunction getDefaultRouteMode(): RouteContext['mode'] {\n if (typeof window !== 'undefined') {\n return 'spa';\n }\n\n return 'ssr';\n}\n\nfunction buildRenderResult(\n record: RouteRecord,\n params: Record<string, string>\n): RouteRenderResult {\n return {\n kind: 'render',\n handler: record.handler,\n params,\n };\n}\n\nfunction continueRoutePolicies(\n policies: readonly RoutePolicy[],\n context: RouteContext,\n record: RouteRecord,\n params: Record<string, string>,\n startIndex = 0\n): RouteRequestResult | Promise<RouteRequestResult> {\n for (let index = startIndex; index < policies.length; index += 1) {\n const policyResult = policies[index](context);\n\n if (isPromise(policyResult)) {\n return policyResult.then((next) => {\n if (next.kind !== 'allow') {\n return next;\n }\n\n return continueRoutePolicies(\n policies,\n context,\n record,\n params,\n index + 1\n );\n });\n }\n\n if (policyResult.kind !== 'allow') {\n return policyResult;\n }\n }\n\n return buildRenderResult(record, params);\n}\n\nexport function resolveRouteRequest(\n target: string,\n options: RouteRequestOptions = {}\n): RouteRequestResult | Promise<RouteRequestResult> {\n const routeRecords = options.manifest?.records ?? records;\n const match = getMatchingRecord(target, routeRecords);\n\n if (!match) {\n return null;\n }\n\n const { record, params } = match;\n const policies = getRoutePolicies(record.options);\n\n if (policies.length === 0) {\n return buildRenderResult(record, params);\n }\n\n const signal =\n options.signal ??\n getRenderContext()?.signal ??\n new AbortController().signal;\n const mode = options.mode ?? getDefaultRouteMode();\n const auth = getActiveRouteAuthOptions(\n options.auth ?? options.manifest?.auth\n );\n const baseContext = buildRouteContextBase(target, params, {\n mode,\n signal,\n });\n\n const finalize = (authState: { session: unknown; user: unknown }) =>\n continueRoutePolicies(\n policies,\n buildRouteContext(target, params, {\n mode,\n signal,\n auth,\n session: authState.session,\n user: authState.user,\n }),\n record,\n params\n );\n\n if (!auth?.resolve) {\n return finalize({ session: null, user: null });\n }\n\n const authState = auth.resolve(baseContext);\n if (isPromise(authState)) {\n return authState.then((next) => finalize(next));\n }\n\n return finalize(authState);\n}\n\n/**\n * Resolve a path against an explicit route list (e.g. an SSR per-render\n * context). When called with the global `routes` array this delegates to\n * the faster `resolveRoute` which uses pre-sorted `records[]`.\n *\n * For externally supplied lists the function:\n * 1. Builds a rank-sorted copy of the list on first call and caches it\n * in a WeakMap so subsequent resolutions against the same list pay\n * zero sort cost.\n * 2. Uses pre-parsed `ParsedSegment[]` from a WeakMap cache so no string\n * splitting or segment parsing occurs on the hot path.\n * 3. Uses a running-best with an early-exit once the sorted list reaches\n * a rank that cannot beat the current best match.\n */\nexport function resolveRouteFromRoutes(\n pathname: string,\n routeList: readonly Route[]\n): ResolvedRoute | null {\n if (routeList === routes) return resolveRoute(pathname);\n\n const normalized =\n pathname.endsWith('/') && pathname !== '/'\n ? pathname.slice(0, -1)\n : pathname;\n const urlParts =\n normalized === '/' ? [] : normalized.split('/').filter(Boolean);\n\n // Use the rank-sorted cached copy so we can exit as soon as the rank\n // drops below the current best — typically saving 30-50% of iterations.\n const sorted = cachedSortedList(routeList);\n let bestHandler: RouteHandler | null = null;\n let bestParams: Record<string, string> = {};\n let bestRank = -Infinity;\n\n for (const r of sorted) {\n const rank = cachedRank(r);\n // sorted descending: once rank < bestRank every remaining route loses\n if (rank < bestRank) break;\n // already have the best match at this rank (first-declared wins)\n if (bestHandler !== null && rank === bestRank) continue;\n const params = matchSegments(urlParts, cachedSegs(r));\n if (params !== null) {\n bestHandler = r.handler;\n bestParams = params;\n bestRank = rank;\n }\n }\n\n return bestHandler !== null\n ? { handler: bestHandler, params: bestParams }\n : null;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"route.js","names":[],"sources":["../../src/router/route.ts"],"sourcesContent":["/**\n * Route definition, registration, and matching.\n *\n * Primary public authoring:\n * - `registerRoutes(() => { ... }, options?)`\n * - `group(options, () => { ... })`\n * - `page(path, Component, () => { ... })`\n * - `index(Component, options?)`\n * - `route(path, Component, options?)`\n * - `fallback(Component)`\n * - `Outlet()` for page child rendering\n * - `currentRoute()` for render-time access\n *\n */\n\nimport { matchSegments, parseSegments, computeRank } from './match';\nimport {\n buildRouteContext,\n buildRouteContextBase,\n deepFreeze,\n makeQuery,\n parseLocation,\n} from './route-context';\nimport { defineContext, readContext } from '../runtime/context';\nimport { getCurrentComponentInstance } from '../runtime/component';\nimport { getExecutionModel } from '../runtime/execution-model';\nimport {\n markReadableDerivedSubscribersDirty,\n markReactivePropsDirtySource,\n notifyReadableReaders,\n recordReadableRead,\n type ReadableSource,\n} from '../runtime/readable';\nimport { getRenderContext } from '../ssr/context';\nimport {\n requireAuth,\n requireGuest,\n requirePermission,\n requireRole,\n} from './policy';\n\nexport type {\n AccessDecision,\n AccessDenyDecision,\n AccessRedirectDecision,\n GroupHelperOptions,\n PageHelperOptions,\n RegisterRoutesOptions,\n RouteDefinition,\n RouteAuthOptions,\n RouteHandler,\n Route,\n RouteContext,\n RoutePolicy,\n RouteRenderResult,\n RouteRequestOptions,\n RouteRequestResult,\n ResolvedRoute,\n RouteMatch,\n RouteQuery,\n RouteSnapshot,\n RouteComponent,\n RouteOptions,\n ParsedSegment,\n LayoutScopeRecord,\n PageScopeRecord,\n RouteRecord,\n RouteManifest,\n} from '../common/router';\n\nimport type {\n AccessDecision,\n AccessDenyDecision,\n AccessRedirectDecision,\n GroupHelperOptions,\n PageHelperOptions,\n RegisterRoutesOptions,\n RouteDefinition,\n RouteAuthOptions,\n RouteHandler,\n Route,\n RouteContext,\n RoutePolicy,\n RouteRenderResult,\n RouteRequestOptions,\n RouteRequestResult,\n ResolvedRoute,\n RouteMatch,\n RouteQuery,\n RouteSnapshot,\n RouteComponent,\n RouteOptions,\n LayoutScopeRecord,\n PageScopeRecord,\n RouteRecord,\n RouteManifest,\n} from '../common/router';\n\n// ---------------------------------------------------------------------------\n// Module-level stores\n// ---------------------------------------------------------------------------\n\n/** Legacy flat route array — kept for resolver and route() accessor backward compat. */\ntype InternalRoute = Route & {\n fallbackPrefix?: string;\n};\n\ntype InternalRouteRecord = RouteRecord & {\n fallbackPrefix?: string;\n};\n\nconst routes: InternalRoute[] = [];\n\n/** Normalized route records built by the declarative registration API. */\nconst records: InternalRouteRecord[] = [];\n\ntype RegistrationScope = {\n kind: 'group' | 'page';\n pathPrefix: string;\n layout?: LayoutScopeRecord['component'];\n page?: PageScopeRecord['component'];\n policies: readonly RoutePolicy[];\n state: AccessScopeState;\n};\n\ntype RegistrationSession = {\n authConfigured: boolean;\n};\n\n/** Active registration scope stack during module-load-time registration. */\nconst registrationScopeStack: RegistrationScope[] = [];\nconst registrationSessionStack: RegistrationSession[] = [];\n\nconst namespaces = new Set<string>();\nlet registrationLocked = false;\n\ntype AccessScopeState = {\n guestOnly: boolean;\n authenticated: boolean;\n};\n\nlet defaultRouteAuthOptions: RouteAuthOptions | undefined;\nlet activeClientRouteAuthOptions: RouteAuthOptions | undefined;\n\nconst HAS_ROUTES_KEY = Symbol.for('__ASKR_HAS_ROUTES__');\n\nfunction setHasRoutes(value: boolean): void {\n try {\n const g = globalThis as unknown as Record<string | symbol, unknown>;\n g[HAS_ROUTES_KEY] = value;\n } catch {\n // ignore\n }\n}\n\n// Initialize to false at module load.\nsetHasRoutes(false);\n\n// Route index by depth - maintains insertion order\nconst routesByDepth = new Map<number, Route[]>();\n\n/**\n * Parse route path depth\n */\nfunction getDepth(path: string): number {\n const normalized =\n path.endsWith('/') && path !== '/' ? path.slice(0, -1) : path;\n return normalized === '/' ? 0 : normalized.split('/').filter(Boolean).length;\n}\n\n// ---------------------------------------------------------------------------\n// WeakMap caches for external Route[] objects (e.g. SSR per-request tables)\n// Amortises parseSegments / computeRank across repeated resolveRouteFromRoutes\n// calls on the same route list (same object reference).\n// ---------------------------------------------------------------------------\n\n/** Pre-parsed segments for an externally supplied Route object. */\nconst routeSegsCache = new WeakMap<Route, ReturnType<typeof parseSegments>>();\n/** Pre-computed rank for an externally supplied Route object. */\nconst routeRankCache = new WeakMap<Route, number>();\n/** A rank-descending sorted copy of an external readonly Route array. */\nconst sortedListCache = new WeakMap<\n ReadonlyArray<Route>,\n ReadonlyArray<Route>\n>();\n\nfunction cachedSegs(r: Route): ReturnType<typeof parseSegments> {\n let s = routeSegsCache.get(r);\n if (!s) {\n s = parseSegments(r.path);\n routeSegsCache.set(r, s);\n }\n return s;\n}\n\nfunction cachedRank(r: Route): number {\n let n = routeRankCache.get(r);\n if (n === undefined) {\n n = computeRank(cachedSegs(r));\n routeRankCache.set(r, n);\n }\n return n;\n}\n\nfunction cachedSortedList(\n routeList: ReadonlyArray<Route>\n): ReadonlyArray<Route> {\n let sorted = sortedListCache.get(routeList);\n if (!sorted) {\n sorted = [...routeList].sort((a, b) => cachedRank(b) - cachedRank(a));\n sortedListCache.set(routeList, sorted);\n }\n return sorted;\n}\n\nlet currentRouteSnapshot = buildRouteSnapshot('/', '', '');\n\nconst currentRouteSource = (() =>\n currentRouteSnapshot) as ReadableSource<RouteSnapshot> &\n (() => RouteSnapshot);\n\ncurrentRouteSource._readers = new Map();\n\n// SSR helper: when rendering on the server, callers may set a location so that\n// render-time route() returns deterministic server values that match client\n// hydration. This is deliberately an opt-in escape for SSR and tests.\nlet serverLocation: string | null = null;\n\nexport function setServerLocation(url: string | null): void {\n serverLocation = url;\n}\n\nfunction isPromise<T>(value: T | Promise<T>): value is Promise<T> {\n return value instanceof Promise;\n}\n\nfunction buildRouteSnapshot(\n pathname: string,\n search: string,\n hash: string\n): RouteSnapshot {\n const query = makeQuery(search);\n const matches = computeMatchesFromRoutes(pathname, getActiveRoutes());\n\n return Object.freeze({\n path: pathname,\n params: deepFreeze({ ...matches[0]?.params }),\n query,\n hash: hash || null,\n matches: Object.freeze(matches),\n });\n}\n\nfunction setCurrentRouteSnapshot(\n pathname: string,\n search: string,\n hash: string\n): void {\n currentRouteSnapshot = buildRouteSnapshot(pathname, search, hash);\n\n const instance = getCurrentComponentInstance();\n markReadableDerivedSubscribersDirty(currentRouteSource);\n markReactivePropsDirtySource(currentRouteSource);\n notifyReadableReaders(currentRouteSource, instance);\n}\n\n// Compute matches for a specific route list.\nfunction matchFallbackPrefix(\n pathname: string,\n fallbackPrefix: string\n): Record<string, string> | null {\n const normalizedPath =\n pathname.endsWith('/') && pathname !== '/'\n ? pathname.slice(0, -1)\n : pathname;\n const normalizedPrefix =\n fallbackPrefix.endsWith('/') && fallbackPrefix !== '/'\n ? fallbackPrefix.slice(0, -1)\n : fallbackPrefix;\n\n if (normalizedPrefix === '/') {\n const urlParts =\n normalizedPath === '/' ? [] : normalizedPath.split('/').filter(Boolean);\n return {\n '*':\n urlParts.length === 0\n ? '/'\n : urlParts.length === 1\n ? urlParts[0]\n : '/' + urlParts.join('/'),\n };\n }\n\n if (\n normalizedPath !== normalizedPrefix &&\n !normalizedPath.startsWith(`${normalizedPrefix}/`)\n ) {\n return null;\n }\n\n const remainder =\n normalizedPath === normalizedPrefix\n ? '/'\n : normalizedPath.slice(normalizedPrefix.length + 1);\n const remainderParts =\n remainder === '/' ? [] : remainder.split('/').filter(Boolean);\n\n return {\n '*':\n remainderParts.length === 0\n ? '/'\n : remainderParts.length === 1\n ? remainderParts[0]\n : '/' + remainderParts.join('/'),\n };\n}\n\nfunction computeMatchesFromRoutes(\n pathname: string,\n routesList: readonly Route[]\n): RouteMatch[] {\n const bestMatch =\n routesList === routes\n ? getMatchingRecord(pathname, records)\n : findBestResolvedRouteFromRoutes(pathname, routesList);\n\n if (!bestMatch) {\n return [];\n }\n\n return [\n {\n path: 'route' in bestMatch ? bestMatch.route.path : bestMatch.record.path,\n params: deepFreeze({ ...bestMatch.params }),\n name:\n 'route' in bestMatch\n ? (bestMatch.route as { name?: string }).name\n : undefined,\n namespace:\n 'route' in bestMatch\n ? bestMatch.route.namespace\n : bestMatch.record.options.namespace,\n },\n ];\n}\n\nfunction findBestResolvedRouteFromRoutes(\n pathname: string,\n routeList: readonly Route[]\n): { route: Route; params: Record<string, string> } | null {\n const normalized =\n pathname.endsWith('/') && pathname !== '/'\n ? pathname.slice(0, -1)\n : pathname;\n const urlParts =\n normalized === '/' ? [] : normalized.split('/').filter(Boolean);\n\n const sorted = cachedSortedList(routeList);\n let bestRoute: Route | null = null;\n let bestParams: Record<string, string> = {};\n let bestRank = -Infinity;\n\n for (const route of sorted) {\n const internalRoute = route as InternalRoute;\n if (internalRoute.fallbackPrefix) {\n continue;\n }\n\n const rank = cachedRank(route);\n if (rank < bestRank) break;\n if (bestRoute !== null && rank === bestRank) continue;\n\n const params = matchSegments(urlParts, cachedSegs(route));\n if (params !== null) {\n bestRoute = route;\n bestParams = params;\n bestRank = rank;\n }\n }\n\n if (bestRoute !== null) {\n return { route: bestRoute, params: bestParams };\n }\n\n let bestFallback: InternalRoute | null = null;\n let bestFallbackParams: Record<string, string> | null = null;\n let bestPrefixLength = -1;\n\n for (const route of routeList) {\n const internalRoute = route as InternalRoute;\n if (!internalRoute.fallbackPrefix) {\n continue;\n }\n\n const params = matchFallbackPrefix(\n normalized,\n internalRoute.fallbackPrefix\n );\n if (params === null) {\n continue;\n }\n\n if (internalRoute.fallbackPrefix.length > bestPrefixLength) {\n bestFallback = internalRoute;\n bestFallbackParams = params;\n bestPrefixLength = internalRoute.fallbackPrefix.length;\n }\n }\n\n return bestFallback && bestFallbackParams\n ? { route: bestFallback, params: bestFallbackParams }\n : null;\n}\n\nfunction getActiveRoutes(): readonly Route[] {\n const renderContext = getRenderContext();\n return renderContext?.routes ?? routes;\n}\n\nfunction getActiveRouteAuthOptions(\n override?: RouteAuthOptions\n): RouteAuthOptions | undefined {\n if (override) {\n return override;\n }\n\n const renderContext = getRenderContext();\n return (\n renderContext?.routeAuth ??\n activeClientRouteAuthOptions ??\n defaultRouteAuthOptions\n );\n}\n\nexport function _setActiveRouteAuthOptions(\n auth: RouteAuthOptions | undefined\n): void {\n activeClientRouteAuthOptions = auth;\n}\n\nfunction getCurrentRegistrationSession(): RegistrationSession {\n return (\n registrationSessionStack[registrationSessionStack.length - 1] ?? {\n authConfigured: !!defaultRouteAuthOptions?.resolve,\n }\n );\n}\n\nfunction getCurrentAccessScopeState(): AccessScopeState {\n return (\n registrationScopeStack[registrationScopeStack.length - 1]?.state ?? {\n guestOnly: false,\n authenticated: false,\n }\n );\n}\n\nfunction getCurrentLayoutChain(): LayoutScopeRecord[] {\n const layoutChain: LayoutScopeRecord[] = [];\n\n for (const scope of registrationScopeStack) {\n if (scope.layout) {\n layoutChain.push({ component: scope.layout });\n }\n }\n\n return layoutChain;\n}\n\nfunction getCurrentPageChain(): PageScopeRecord[] {\n const pageChain: PageScopeRecord[] = [];\n\n for (const scope of registrationScopeStack) {\n if (scope.page) {\n pageChain.push({ component: scope.page });\n }\n }\n\n return pageChain;\n}\n\nfunction hasActivePageScope(): boolean {\n return registrationScopeStack.some((scope) => !!scope.page);\n}\n\nfunction getCurrentScopeKind(): RegistrationScope['kind'] | null {\n return (\n registrationScopeStack[registrationScopeStack.length - 1]?.kind ?? null\n );\n}\n\nfunction getCurrentPathPrefix(): string {\n return (\n registrationScopeStack[registrationScopeStack.length - 1]?.pathPrefix ?? ''\n );\n}\n\nfunction getCurrentInheritedPolicies(): RoutePolicy[] {\n const policies: RoutePolicy[] = [];\n\n for (const scope of registrationScopeStack) {\n if (scope.policies.length > 0) {\n policies.push(...scope.policies);\n }\n }\n\n return policies;\n}\n\n// ---------------------------------------------------------------------------\n// Registration lock\n// ---------------------------------------------------------------------------\n\n/**\n * Prevent route registrations after the app has started.\n * Enforced in production; tests may unlock explicitly.\n */\n\nexport function lockRouteRegistration(): void {\n registrationLocked = true;\n}\n\nexport function _lockRouteRegistrationForTests(): void {\n registrationLocked = true;\n}\n\nexport function _unlockRouteRegistrationForTests(): void {\n registrationLocked = false;\n}\n\n// ---------------------------------------------------------------------------\n// Path validation\n// ---------------------------------------------------------------------------\n\nfunction validateRoutePath(path: string): void {\n if (!path.startsWith('/')) {\n throw new Error(`Route path must begin with \"/\". Got: \"${path}\"`);\n }\n // Reject Express-style :param syntax — Askr uses {param} interpolation\n if (/:([^/{}]+)/.test(path)) {\n const suggested = path.replace(/:([^/{}]+)/g, '{$1}');\n throw new Error(\n `Route parameter syntax uses {name} interpolation, not :name. ` +\n `Use \"${suggested}\" instead of \"${path}\".`\n );\n }\n}\n\nfunction normalizeAbsoluteRoutePath(path: string): string {\n if (!path || path === '/') {\n return '/';\n }\n\n const normalized = path.endsWith('/') ? path.slice(0, -1) : path;\n return normalized || '/';\n}\n\nfunction joinRoutePaths(prefix: string, path: string): string {\n const normalizedPrefix = normalizeAbsoluteRoutePath(prefix || '/');\n const normalizedPath = path.replace(/^\\/+|\\/+$/g, '');\n\n if (!normalizedPath) {\n return normalizedPrefix;\n }\n\n return normalizedPrefix === '/'\n ? `/${normalizedPath}`\n : `${normalizedPrefix}/${normalizedPath}`;\n}\n\nfunction resolvePageScopePath(path: string): string {\n if (!path) {\n throw new Error('page(path, Component, fn) requires a non-empty path.');\n }\n\n if (path.startsWith('/')) {\n validateRoutePath(path);\n return normalizeAbsoluteRoutePath(path);\n }\n\n return joinRoutePaths(getCurrentPathPrefix(), path);\n}\n\nfunction resolveIndexPath(): string {\n return normalizeAbsoluteRoutePath(getCurrentPathPrefix() || '/');\n}\n\nfunction resolveRouteRegistrationPath(path: string): string {\n if (path.startsWith('/')) {\n if (hasActivePageScope()) {\n throw new Error(\n 'Child route paths inside page() must be relative. ' +\n `Use \"${path.slice(1)}\" instead of \"${path}\".`\n );\n }\n\n validateRoutePath(path);\n return normalizeAbsoluteRoutePath(path);\n }\n\n const prefix = getCurrentPathPrefix();\n\n if (!prefix) {\n throw new Error(`Route path must begin with \"/\". Got: \"${path}\"`);\n }\n\n return joinRoutePaths(prefix, path);\n}\n\n// ---------------------------------------------------------------------------\n// Internal helper: insert a RouteRecord in rank-descending order\n// so that resolveRoute can use first-match-wins without sorting.\n// Ties preserve declaration order (stable binary-search insertion).\n// ---------------------------------------------------------------------------\n\nfunction insertRecordSorted(record: RouteRecord): void {\n let lo = 0;\n let hi = records.length;\n // Find the insertion point: after all existing records with rank >= this one\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (records[mid].rank >= record.rank) lo = mid + 1;\n else hi = mid;\n }\n records.splice(lo, 0, record);\n}\n\n// ---------------------------------------------------------------------------\n// Internal helper: add a single pre-built Route object to the runtime stores\n// ---------------------------------------------------------------------------\n\nfunction addRouteToStores(routeObj: Route): void {\n routes.push(routeObj);\n setHasRoutes(true);\n\n const depth = getDepth(routeObj.path);\n let depthRoutes = routesByDepth.get(depth);\n if (!depthRoutes) {\n depthRoutes = [];\n routesByDepth.set(depth, depthRoutes);\n }\n depthRoutes.push(routeObj);\n\n if (routeObj.namespace) {\n namespaces.add(routeObj.namespace);\n }\n}\n\n// ---------------------------------------------------------------------------\n// lazy() — eager-prefetch code-split component wrapper\n// ---------------------------------------------------------------------------\n\n/** Promises from in-flight lazy() imports, drained by createSPA / hydrateSPA. */\nconst pendingLazy = new Set<Promise<unknown>>();\n\nconst outletContext = defineContext<unknown>(null);\n\nexport function Outlet(): unknown {\n return readContext(outletContext);\n}\n\n/**\n * Snapshot the current in-flight lazy() imports.\n * Boot uses this before clearing route state so manifest-based startup can\n * still await chunks that were kicked off during route module evaluation.\n */\nexport function _snapshotLazy(): Promise<unknown>[] {\n return [...pendingLazy];\n}\n\n/**\n * Declare a code-split route component. The `import()` fires immediately at\n * module evaluation time (creating a bundler split point), and the resolved\n * chunk is guaranteed to be available before the app mounts — so the renderer\n * always receives a plain synchronous function.\n *\n * ```ts\n * registerRoutes(() => {\n * group({ layout: AppLayout }, () => {\n * route('/', lazy(() => import('./pages/landing')));\n * route('/dashboard', lazy(() => import('./pages/dashboard')));\n * });\n * });\n * ```\n *\n * The module must export the component as its **default** export:\n * ```ts\n * // pages/dashboard.tsx\n * export default function DashboardPage() { … }\n * ```\n */\nexport function lazy(\n factory: () => Promise<{ default: RouteComponent } | RouteComponent>\n): RouteComponent {\n let resolved: RouteComponent | null = null;\n let loadError: unknown = null;\n\n const promise = factory().then(\n (mod) => {\n resolved =\n typeof mod === 'function'\n ? mod\n : (mod as { default: RouteComponent }).default;\n pendingLazy.delete(promise);\n },\n (err: unknown) => {\n loadError = err;\n pendingLazy.delete(promise);\n }\n );\n pendingLazy.add(promise);\n\n return (params) => {\n if (loadError) throw loadError as Error;\n if (!resolved) {\n throw new Error(\n 'lazy() component used before it was resolved. ' +\n 'Await createSPA() / hydrateSPA() to ensure all chunks load first.'\n );\n }\n return resolved(params);\n };\n}\n\n/**\n * Wait for all pending `lazy()` imports to settle.\n * Called automatically by `createSPA` / `hydrateSPA` before mounting.\n */\nexport function _drainLazy(\n additionalPending: Iterable<Promise<unknown>> = []\n): Promise<void> {\n const combined = new Set<Promise<unknown>>([\n ...additionalPending,\n ...pendingLazy,\n ]);\n if (combined.size === 0) return Promise.resolve();\n return Promise.allSettled(combined).then(() => undefined);\n}\n\nexport function group(options: GroupHelperOptions, fn: RouteDefinition): void;\nexport function group(options: GroupHelperOptions, fn: RouteDefinition): void {\n pushGroupScope(options, fn);\n}\n\nexport function page(\n path: string,\n Component: RouteComponent,\n fn: RouteDefinition\n): void;\nexport function page(\n path: string,\n Component: RouteComponent,\n options: PageHelperOptions,\n fn: RouteDefinition\n): void;\nexport function page(\n path: string,\n Component: RouteComponent,\n optionsOrFn: PageHelperOptions | RouteDefinition,\n maybeFn?: RouteDefinition\n): void {\n const options =\n typeof optionsOrFn === 'function' ? ({} as PageHelperOptions) : optionsOrFn;\n const fn = typeof optionsOrFn === 'function' ? optionsOrFn : maybeFn;\n\n if (typeof Component !== 'function') {\n throw new Error(\n 'page(path, Component, fn) requires a component function as the second argument.'\n );\n }\n\n if (typeof fn !== 'function') {\n throw new Error(\n 'page(path, Component, fn) requires a route definition callback as the final argument.'\n );\n }\n\n pushPageScope(path, Component, options, fn);\n}\n\nexport function index(Component: RouteComponent, options?: RouteOptions): void {\n registerRouteAtResolvedPath(resolveIndexPath(), Component, options);\n}\n\nexport function fallback(Component: RouteComponent): void {\n if (hasActivePageScope()) {\n if (getCurrentScopeKind() !== 'page') {\n throw new Error(\n 'fallback() inside page() must be declared directly in the page scope, not inside nested group().'\n );\n }\n\n registerRouteAtResolvedPath(\n `${getCurrentPathPrefix()}/*`,\n Component,\n undefined,\n { isFallback: true, fallbackPrefix: getCurrentPathPrefix() }\n );\n return;\n }\n\n const allowsRootFallback = registrationScopeStack.every(\n (scope) =>\n scope.policies.length === 0 &&\n !scope.state.guestOnly &&\n !scope.state.authenticated\n );\n\n if (!allowsRootFallback) {\n throw new Error(\n 'fallback() can only be registered at the root scope. ' +\n 'Use route(\"/*\", Component) if you need compatibility behavior.'\n );\n }\n\n registerRouteAtResolvedPath('/*', Component, undefined, {\n isFallback: true,\n fallbackPrefix: '/',\n });\n}\n\nfunction pushRegistrationScope(\n scope: RegistrationScope,\n fn: RouteDefinition\n): void {\n registrationScopeStack.push(scope);\n try {\n fn();\n } finally {\n registrationScopeStack.pop();\n }\n}\n\nfunction pushGroupScope(\n options: GroupHelperOptions,\n fn: RouteDefinition\n): void {\n const session = getCurrentRegistrationSession();\n validateAccessMetadata(options, {\n authConfigured: session.authConfigured,\n state: getCurrentAccessScopeState(),\n });\n const policies = compileNodePolicies(options);\n\n pushRegistrationScope(\n {\n kind: 'group',\n pathPrefix: getCurrentPathPrefix(),\n layout: options.layout,\n policies,\n state: nextAccessScopeState(options, getCurrentAccessScopeState()),\n },\n fn\n );\n}\n\nfunction pushPageScope(\n path: string,\n Component: RouteComponent,\n options: PageHelperOptions,\n fn: RouteDefinition\n): void {\n if (hasActivePageScope()) {\n throw new Error(\n 'page() cannot be nested inside another page(). ' +\n 'Use route() for child leaves or group() for inherited behavior inside the existing page scope.'\n );\n }\n\n const session = getCurrentRegistrationSession();\n validateAccessMetadata(options, {\n authConfigured: session.authConfigured,\n state: getCurrentAccessScopeState(),\n });\n\n const policies = compileNodePolicies(options);\n\n pushRegistrationScope(\n {\n kind: 'page',\n pathPrefix: resolvePageScopePath(path),\n page: Component,\n policies,\n state: nextAccessScopeState(options, getCurrentAccessScopeState()),\n },\n fn\n );\n}\n\nfunction hasBuiltInAuthMetadata(\n node: Pick<GroupHelperOptions | RouteOptions, 'auth' | 'role' | 'permission'>\n): boolean {\n return node.auth !== undefined || !!node.role || !!node.permission;\n}\n\nfunction validateSameNodeAccessMetadata(\n node: Pick<GroupHelperOptions | RouteOptions, 'auth' | 'role' | 'permission'>\n): void {\n if (node.auth === 'guest' && (!!node.role || !!node.permission)) {\n throw new Error(\n 'Guest-only routes cannot be combined with role or permission requirements.'\n );\n }\n}\n\nfunction validateAccessMetadata(\n node: Pick<GroupHelperOptions | RouteOptions, 'auth' | 'role' | 'permission'>,\n context: {\n authConfigured: boolean;\n state: AccessScopeState;\n }\n): void {\n validateSameNodeAccessMetadata(node);\n\n const requiresAuthenticated =\n node.auth === true || !!node.role || !!node.permission;\n\n if (\n node.auth === 'guest' &&\n (context.state.authenticated || !!node.role || !!node.permission)\n ) {\n throw new Error(\n 'Guest-only routes cannot be combined with authenticated access requirements.'\n );\n }\n\n if (context.state.guestOnly && requiresAuthenticated) {\n throw new Error(\n 'Child routes cannot weaken a guest-only access scope with authenticated requirements.'\n );\n }\n\n if (hasBuiltInAuthMetadata(node) && !context.authConfigured) {\n throw new Error(\n 'Routes using `auth`, `role`, or `permission` require `auth.resolve` in registerRoutes(...).'\n );\n }\n}\n\nfunction nextAccessScopeState(\n node: Pick<GroupHelperOptions | RouteOptions, 'auth' | 'role' | 'permission'>,\n state: AccessScopeState\n): AccessScopeState {\n const requiresAuthenticated =\n node.auth === true || !!node.role || !!node.permission;\n\n return {\n guestOnly: state.guestOnly || node.auth === 'guest',\n authenticated: state.authenticated || requiresAuthenticated,\n };\n}\n\nfunction compileNodePolicies(\n node: Pick<\n RouteOptions | GroupHelperOptions,\n 'auth' | 'role' | 'permission' | 'policies'\n >\n): RoutePolicy[] {\n validateSameNodeAccessMetadata(node);\n\n const compiled: RoutePolicy[] = [];\n\n if (node.auth === true) {\n compiled.push(requireAuth());\n } else if (node.auth === 'guest') {\n compiled.push(requireGuest());\n }\n\n if (node.role) {\n compiled.push(requireRole(node.role));\n }\n\n if (node.permission) {\n compiled.push(requirePermission(node.permission));\n }\n\n if (node.policies?.length) {\n compiled.push(...node.policies);\n }\n\n return compiled;\n}\n\nfunction normalizeRouteOptions(\n options: RouteOptions | undefined\n): RouteOptions | undefined {\n if (!options) {\n return undefined;\n }\n\n const loader = options.loader;\n const policies = compileNodePolicies(options);\n\n if (\n !loader &&\n !options.entries &&\n policies.length === 0 &&\n !options.title &&\n !options.namespace &&\n options.auth === undefined &&\n !options.role &&\n !options.permission\n ) {\n return undefined;\n }\n\n return {\n ...(loader ? { loader } : {}),\n ...(options.entries ? { entries: options.entries } : {}),\n ...(options.auth !== undefined ? { auth: options.auth } : {}),\n ...(options.role ? { role: options.role } : {}),\n ...(options.permission ? { permission: options.permission } : {}),\n ...(policies.length > 0 ? { policies } : {}),\n ...(options.title ? { title: options.title } : {}),\n ...(options.namespace ? { namespace: options.namespace } : {}),\n };\n}\n\nfunction applyPageChain(\n pageChain: readonly PageScopeRecord[],\n params: Record<string, string>,\n content: unknown\n): unknown {\n let nextContent = content;\n\n for (let i = pageChain.length - 1; i >= 0; i--) {\n nextContent = outletContext.Scope({\n value: nextContent,\n children: pageChain[i].component(params),\n });\n }\n\n return nextContent;\n}\n\nfunction registerRouteAtResolvedPath(\n path: string,\n Component: RouteComponent,\n options?: RouteOptions,\n metadata?: {\n isFallback?: boolean;\n fallbackPrefix?: string;\n }\n): void {\n validateAccessMetadata(options ?? {}, {\n authConfigured: getCurrentRegistrationSession().authConfigured,\n state: getCurrentAccessScopeState(),\n });\n\n const chain = getCurrentLayoutChain();\n const pageChain = getCurrentPageChain();\n const segments = parseSegments(path);\n const rank = computeRank(segments);\n const isFallback = metadata?.isFallback ?? path === '/*';\n const comp = Component;\n const normalizedOptions = normalizeRouteOptions(options);\n const policies = [\n ...getCurrentInheritedPolicies(),\n ...(normalizedOptions?.policies ?? []),\n ];\n\n const handler: RouteHandler = (params) => {\n let content = comp(params);\n\n content = applyPageChain(pageChain, params, content);\n\n for (let i = chain.length - 1; i >= 0; i--) {\n content = chain[i].component({ children: content });\n }\n\n return content;\n };\n\n const record: InternalRouteRecord = {\n path,\n component: comp,\n segments,\n rank,\n layoutChain: chain,\n pageChain,\n options: normalizedOptions\n ? {\n ...normalizedOptions,\n ...(policies.length > 0 ? { policies } : {}),\n }\n : policies.length > 0\n ? { policies }\n : {},\n isFallback,\n handler,\n ...(metadata?.fallbackPrefix\n ? { fallbackPrefix: metadata.fallbackPrefix }\n : {}),\n };\n\n insertRecordSorted(record);\n addRouteToStores({\n path,\n handler,\n namespace: normalizedOptions?.namespace ?? options?.namespace,\n ...(metadata?.fallbackPrefix\n ? { fallbackPrefix: metadata.fallbackPrefix }\n : {}),\n });\n}\n\nexport function registerRoutes(\n definition: RouteDefinition,\n options: RegisterRoutesOptions = {}\n): void {\n defaultRouteAuthOptions = options.auth;\n\n registrationSessionStack.push({\n authConfigured: !!options.auth?.resolve,\n });\n try {\n definition();\n } finally {\n registrationSessionStack.pop();\n }\n}\n\n// ---------------------------------------------------------------------------\n// route() — dual-purpose: registration (module load) + accessor (render time)\n// ---------------------------------------------------------------------------\n\nfunction readCurrentRouteSnapshot(): RouteSnapshot {\n const instance = getCurrentComponentInstance();\n if (!instance) {\n throw new Error(\n 'currentRoute() can only be called during component render execution. ' +\n 'Call currentRoute() from inside your component function.'\n );\n }\n\n let pathname = '/';\n let search = '';\n let hash = '';\n const renderContext = getRenderContext();\n\n if (instance.ssr && renderContext?.url) {\n const parsed = parseLocation(renderContext.url);\n pathname = parsed.pathname;\n search = parsed.search;\n hash = parsed.hash;\n } else if (typeof window !== 'undefined' && window.location) {\n pathname = window.location.pathname || '/';\n search = window.location.search || '';\n hash = window.location.hash || '';\n } else if (serverLocation) {\n const parsed = parseLocation(serverLocation);\n pathname = parsed.pathname;\n search = parsed.search;\n hash = parsed.hash;\n }\n\n const query = makeQuery(search);\n const matches = computeMatchesFromRoutes(pathname, getActiveRoutes());\n const instanceParams = instance.props as Record<string, string>;\n const routeParams =\n Object.keys(instanceParams).length > 0\n ? instanceParams\n : (matches[0]?.params ?? {});\n const params = deepFreeze({\n ...routeParams,\n });\n\n return Object.freeze({\n path: pathname,\n params,\n query,\n hash: hash || null,\n matches: Object.freeze(matches),\n });\n}\n\nexport function currentRoute(): RouteSnapshot {\n const instance = getCurrentComponentInstance();\n if (!instance) {\n throw new Error(\n 'currentRoute() can only be called during component render execution. ' +\n 'Call currentRoute() from inside your component function.'\n );\n }\n\n if (typeof window === 'undefined' || instance.ssr) {\n return readCurrentRouteSnapshot();\n }\n\n if (currentRouteSnapshot.path !== (window.location.pathname || '/')) {\n recordReadableRead(currentRouteSource);\n return readCurrentRouteSnapshot();\n }\n\n recordReadableRead(currentRouteSource);\n return readCurrentRouteSnapshot();\n}\n\nexport function syncCurrentRouteSnapshot(\n pathname: string,\n search: string,\n hash: string\n): void {\n setCurrentRouteSnapshot(pathname, search, hash);\n}\n\n/**\n * Register a route.\n *\n * ```ts\n * route('/posts/{slug}', PostPage, {\n * loader: ({ params }) => getPost(params.slug),\n * entries: async () => getPosts().map(p => ({ slug: p.slug })),\n * title: 'Post',\n * policies:[requireAuth()],\n * });\n * ```\n */\nexport function route(\n path: string,\n Component: RouteComponent,\n options?: RouteOptions\n): void;\nexport function route(\n path: string,\n Component: RouteComponent,\n options?: RouteOptions\n): void {\n if (getExecutionModel() === 'islands') {\n throw new Error(\n 'Routes are not supported with islands. Use createSPA (client) or createSSR (server) instead.'\n );\n }\n\n // ── Render-time accessor (no arguments) ─────────────────────────────────\n if (typeof path === 'undefined') {\n throw new Error(\n 'route() is only for route registration. Use currentRoute() inside components.'\n );\n }\n\n // ── Registration mode ────────────────────────────────────────────────────\n\n // Disallow registration during SSR render\n const currentInst = getCurrentComponentInstance();\n if (currentInst && currentInst.ssr) {\n throw new Error(\n 'route() cannot be called during SSR rendering. Register routes at module load time instead.'\n );\n }\n\n if (registrationLocked) {\n throw new Error(\n 'Route registration is locked after app startup. ' +\n 'Register routes at module load time before calling createSPA or createSSR.'\n );\n }\n\n if (typeof Component !== 'function') {\n throw new Error(\n 'route(path, Component) requires a component function as the second argument. ' +\n 'Passing JSX elements or VNodes directly is not supported.'\n );\n }\n\n registerRouteAtResolvedPath(\n resolveRouteRegistrationPath(path),\n Component,\n options\n );\n}\n\n// ---------------------------------------------------------------------------\n// Manifest access\n// ---------------------------------------------------------------------------\n\n/**\n * Return the normalized route manifest built from registered route definitions.\n *\n * Pass this to `createSPA`, `hydrateSPA`, or `renderToString` as the\n * authoritative routing input:\n *\n * ```ts\n * import { getManifest } from '@askrjs/askr/router';\n * await createSPA({ root: '#app', manifest: getManifest() });\n * ```\n */\nexport function getManifest(): RouteManifest {\n return {\n records: [...records],\n ...(defaultRouteAuthOptions ? { auth: defaultRouteAuthOptions } : {}),\n };\n}\n\n/**\n * Internal: apply a pre-built manifest to the runtime stores without running\n * route() again. Called by createSPA / hydrateSPA when a manifest is passed.\n */\nexport function _applyManifest(manifest: RouteManifest): void {\n defaultRouteAuthOptions = manifest.auth;\n for (const record of manifest.records) {\n insertRecordSorted(record as InternalRouteRecord);\n addRouteToStores({\n path: record.path,\n handler: record.handler,\n namespace: record.options.namespace,\n ...('fallbackPrefix' in record &&\n typeof (record as InternalRouteRecord).fallbackPrefix === 'string'\n ? {\n fallbackPrefix: (record as InternalRouteRecord).fallbackPrefix,\n }\n : {}),\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Route collection helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Get all registered routes (flat list, insertion order).\n * Prefer `getManifest()` when metadata (loader, policies, entries) is needed.\n */\nexport function getRoutes(): Route[] {\n return [...routes];\n}\n\n/** Get routes for a specific namespace. */\nexport function getNamespaceRoutes(namespace: string): Route[] {\n return routes.filter((r) => r.namespace === namespace);\n}\n\n/** Unload all routes from a namespace (for MFE unmounting). */\nexport function unloadNamespace(namespace: string): number {\n const before = routes.length;\n\n for (let i = routes.length - 1; i >= 0; i--) {\n if (routes[i].namespace === namespace) {\n const removed = routes[i];\n routes.splice(i, 1);\n\n const depth = getDepth(removed.path);\n const depthRoutes = routesByDepth.get(depth);\n if (depthRoutes) {\n const idx = depthRoutes.indexOf(removed);\n if (idx >= 0) depthRoutes.splice(idx, 1);\n }\n }\n }\n\n // Remove matching records too\n for (let i = records.length - 1; i >= 0; i--) {\n if (records[i].options.namespace === namespace) {\n records.splice(i, 1);\n }\n }\n\n namespaces.delete(namespace);\n return before - routes.length;\n}\n\n/** Clear all registered routes and records (testing / boot reset). */\nexport function clearRoutes(): void {\n routes.length = 0;\n records.length = 0;\n namespaces.clear();\n routesByDepth.clear();\n registrationScopeStack.length = 0;\n registrationSessionStack.length = 0;\n registrationLocked = false;\n defaultRouteAuthOptions = undefined;\n activeClientRouteAuthOptions = undefined;\n setHasRoutes(false);\n pendingLazy.clear();\n}\n\n/**\n * Get all loaded namespaces (MFE identifiers)\n */\nexport function getLoadedNamespaces(): string[] {\n return Array.from(namespaces);\n}\n\nfunction findBestScopedFallbackRecord(\n pathname: string,\n routeRecords: readonly RouteRecord[]\n): { record: InternalRouteRecord; params: Record<string, string> } | null {\n let bestRecord: InternalRouteRecord | null = null;\n let bestParams: Record<string, string> | null = null;\n let bestPrefixLength = -1;\n\n for (const routeRecord of routeRecords) {\n const record = routeRecord as InternalRouteRecord;\n if (!record.fallbackPrefix) {\n continue;\n }\n\n const params = matchFallbackPrefix(pathname, record.fallbackPrefix);\n if (params === null) {\n continue;\n }\n\n if (record.fallbackPrefix.length > bestPrefixLength) {\n bestRecord = record;\n bestParams = params;\n bestPrefixLength = record.fallbackPrefix.length;\n }\n }\n\n return bestRecord && bestParams\n ? { record: bestRecord, params: bestParams }\n : null;\n}\n\n/**\n * Resolve a path to a route handler.\n *\n * Hot path: walks the module-level `records[]` array which is kept sorted by\n * rank descending at registration time — so the first `matchSegments` hit is\n * always the most specific match. No per-call allocations for the common\n * case of purely-static routes.\n */\nexport function resolveRoute(pathname: string): ResolvedRoute | null {\n const normalized =\n pathname.endsWith('/') && pathname !== '/'\n ? pathname.slice(0, -1)\n : pathname;\n const urlParts =\n normalized === '/' ? [] : normalized.split('/').filter(Boolean);\n\n for (const record of records) {\n if (record.fallbackPrefix) {\n continue;\n }\n\n const params = matchSegments(urlParts, record.segments);\n if (params !== null) {\n return { handler: record.handler, params };\n }\n }\n\n const fallbackMatch = findBestScopedFallbackRecord(normalized, records);\n return fallbackMatch\n ? { handler: fallbackMatch.record.handler, params: fallbackMatch.params }\n : null;\n}\n\nfunction getMatchingRecord(\n target: string,\n routeRecords: readonly RouteRecord[]\n): { record: RouteRecord; params: Record<string, string> } | null {\n const location = parseLocation(target);\n const normalized =\n location.pathname.endsWith('/') && location.pathname !== '/'\n ? location.pathname.slice(0, -1)\n : location.pathname;\n const urlParts =\n normalized === '/' ? [] : normalized.split('/').filter(Boolean);\n\n for (const record of routeRecords) {\n const internalRecord = record as InternalRouteRecord;\n if (internalRecord.fallbackPrefix) {\n continue;\n }\n\n const params = matchSegments(urlParts, record.segments);\n if (params !== null) {\n return { record, params };\n }\n }\n\n return findBestScopedFallbackRecord(normalized, routeRecords);\n}\n\nfunction getRoutePolicies(\n options: RouteOptions | undefined\n): readonly RoutePolicy[] {\n if (!options) {\n return [];\n }\n\n if (options.policies?.length) {\n return options.policies;\n }\n\n return compileNodePolicies(options);\n}\n\nfunction getDefaultRouteMode(): RouteContext['mode'] {\n if (typeof window !== 'undefined') {\n return 'spa';\n }\n\n return 'ssr';\n}\n\nfunction buildRenderResult(\n record: RouteRecord,\n params: Record<string, string>\n): RouteRenderResult {\n return {\n kind: 'render',\n handler: record.handler,\n params,\n };\n}\n\nfunction continueRoutePolicies(\n policies: readonly RoutePolicy[],\n context: RouteContext,\n record: RouteRecord,\n params: Record<string, string>,\n startIndex = 0\n): RouteRequestResult | Promise<RouteRequestResult> {\n for (let index = startIndex; index < policies.length; index += 1) {\n const policyResult = policies[index](context);\n\n if (isPromise(policyResult)) {\n return policyResult.then((next) => {\n if (next.kind !== 'allow') {\n return next;\n }\n\n return continueRoutePolicies(\n policies,\n context,\n record,\n params,\n index + 1\n );\n });\n }\n\n if (policyResult.kind !== 'allow') {\n return policyResult;\n }\n }\n\n return buildRenderResult(record, params);\n}\n\nexport function resolveRouteRequest(\n target: string,\n options: RouteRequestOptions = {}\n): RouteRequestResult | Promise<RouteRequestResult> {\n const routeRecords = options.manifest?.records ?? records;\n const match = getMatchingRecord(target, routeRecords);\n\n if (!match) {\n return null;\n }\n\n const { record, params } = match;\n const policies = getRoutePolicies(record.options);\n\n if (policies.length === 0) {\n return buildRenderResult(record, params);\n }\n\n const signal =\n options.signal ??\n getRenderContext()?.signal ??\n new AbortController().signal;\n const mode = options.mode ?? getDefaultRouteMode();\n const auth = getActiveRouteAuthOptions(\n options.auth ?? options.manifest?.auth\n );\n const baseContext = buildRouteContextBase(target, params, {\n mode,\n signal,\n });\n\n const finalize = (authState: { session: unknown; user: unknown }) =>\n continueRoutePolicies(\n policies,\n buildRouteContext(target, params, {\n mode,\n signal,\n auth,\n session: authState.session,\n user: authState.user,\n }),\n record,\n params\n );\n\n if (!auth?.resolve) {\n return finalize({ session: null, user: null });\n }\n\n const authState = auth.resolve(baseContext);\n if (isPromise(authState)) {\n return authState.then((next) => finalize(next));\n }\n\n return finalize(authState);\n}\n\n/**\n * Resolve a path against an explicit route list (e.g. an SSR per-render\n * context). When called with the global `routes` array this delegates to\n * the faster `resolveRoute` which uses pre-sorted `records[]`.\n *\n * For externally supplied lists the function:\n * 1. Builds a rank-sorted copy of the list on first call and caches it\n * in a WeakMap so subsequent resolutions against the same list pay\n * zero sort cost.\n * 2. Uses pre-parsed `ParsedSegment[]` from a WeakMap cache so no string\n * splitting or segment parsing occurs on the hot path.\n * 3. Uses a running-best with an early-exit once the sorted list reaches\n * a rank that cannot beat the current best match.\n */\nexport function resolveRouteFromRoutes(\n pathname: string,\n routeList: readonly Route[]\n): ResolvedRoute | null {\n if (routeList === routes) return resolveRoute(pathname);\n\n const match = findBestResolvedRouteFromRoutes(pathname, routeList);\n return match ? { handler: match.route.handler, params: match.params } : null;\n}\n"],"mappings":"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"}
@@ -50,7 +50,7 @@ function unwrapFragmentForFastPath(vnode) {
50
50
  if (!vnode || typeof vnode !== "object" || !("type" in vnode)) return vnode;
51
51
  const v = vnode;
52
52
  if (typeof v.type === "symbol" && (v.type === Fragment || String(v.type) === "Symbol(askr.fragment)")) {
53
- const children = v.children || v.props?.children;
53
+ const children = v.props?.children ?? v.children;
54
54
  if (Array.isArray(children) && children.length > 0) {
55
55
  for (const child of children) if (child && typeof child === "object" && "type" in child) {
56
56
  if (typeof child.type === "string") return child;
@@ -84,7 +84,7 @@ function classifyUpdate(instance, result) {
84
84
  useFastPath: false,
85
85
  reason: "root-tag-mismatch"
86
86
  };
87
- const children = vnode.children || vnode.props?.children;
87
+ const children = vnode.props?.children ?? vnode.children;
88
88
  if (!Array.isArray(children)) return {
89
89
  useFastPath: false,
90
90
  reason: "no-children-array"
@@ -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\n // Clear any previously scheduled synchronous scheduler tasks so they don't\n // retrigger evaluations during the committed fast-path.\n try {\n const cleared = globalScheduler.clearPendingSyncTasks?.() ?? 0;\n setDevValue('__ASKR_FASTLANE_CLEARED_TASKS', cleared);\n } catch (err) {\n if (isDevelopmentEnvironment()) throw err;\n }\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.children || v.props?.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.children || vnode.props?.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 // Clear any synchronous tasks scheduled during the commit\n const clearedAfter = globalScheduler.clearPendingSyncTasks?.() ?? 0;\n setDevValue('__FASTLANE_CLEARED_AFTER', clearedAfter);\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 // Final quiescence assertion\n let finalState = globalScheduler.getState();\n const executing = globalScheduler.isExecuting();\n let outstandingAfter = Math.max(\n 0,\n finalState.taskCount - (executing ? 1 : 0)\n );\n\n if (outstandingAfter !== 0) {\n // Attempt to clear newly enqueued synchronous tasks\n let attempts = 0;\n while (attempts < 5) {\n const cleared = globalScheduler.clearPendingSyncTasks?.() ?? 0;\n if (cleared === 0) break;\n attempts++;\n }\n finalState = globalScheduler.getState();\n outstandingAfter = Math.max(\n 0,\n finalState.taskCount - (globalScheduler.isExecuting() ? 1 : 0)\n );\n if (outstandingAfter !== 0) {\n console.error(\n '[FASTLANE] Post-commit enqueue logs:',\n getDevValue('__ENQUEUE_LOGS')\n );\n console.error(\n '[FASTLANE] Cleared counts:',\n getDevValue('__FASTLANE_CLEARED_TASKS'),\n getDevValue('__FASTLANE_CLEARED_AFTER')\n );\n throw new Error(\n `Fast-lane invariant violated: scheduler has ${finalState.taskCount} pending task(s) after commit`\n );\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\n // Clear any previously scheduled synchronous scheduler tasks so they don't\n // retrigger evaluations during the committed fast-path.\n try {\n const cleared = globalScheduler.clearPendingSyncTasks?.() ?? 0;\n setDevValue('__ASKR_FASTLANE_CLEARED_TASKS', cleared);\n } catch (err) {\n if (isDevelopmentEnvironment()) throw err;\n }\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 // Clear any synchronous tasks scheduled during the commit\n const clearedAfter = globalScheduler.clearPendingSyncTasks?.() ?? 0;\n setDevValue('__FASTLANE_CLEARED_AFTER', clearedAfter);\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 // Final quiescence assertion\n let finalState = globalScheduler.getState();\n const executing = globalScheduler.isExecuting();\n let outstandingAfter = Math.max(\n 0,\n finalState.taskCount - (executing ? 1 : 0)\n );\n\n if (outstandingAfter !== 0) {\n // Attempt to clear newly enqueued synchronous tasks\n let attempts = 0;\n while (attempts < 5) {\n const cleared = globalScheduler.clearPendingSyncTasks?.() ?? 0;\n if (cleared === 0) break;\n attempts++;\n }\n finalState = globalScheduler.getState();\n outstandingAfter = Math.max(\n 0,\n finalState.taskCount - (globalScheduler.isExecuting() ? 1 : 0)\n );\n if (outstandingAfter !== 0) {\n console.error(\n '[FASTLANE] Post-commit enqueue logs:',\n getDevValue('__ENQUEUE_LOGS')\n );\n console.error(\n '[FASTLANE] Cleared counts:',\n getDevValue('__FASTLANE_CLEARED_TASKS'),\n getDevValue('__FASTLANE_CLEARED_AFTER')\n );\n throw new Error(\n `Fast-lane invariant violated: scheduler has ${finalState.taskCount} pending task(s) after commit`\n );\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 +1 @@
1
- {"version":3,"file":"for.d.ts","names":[],"sources":["../../src/runtime/for.ts"],"mappings":";;;;;;;;;UAoDiB,eAAA;EACf,GAAA;EACA,IAAA,EAAM,CAAA;EACN,YAAA,EAAc,CAAA;EACd,UAAA,EAAY,aAAA,CAAc,CAAA;EAC1B,mBAAA,EAAqB,GAAA,CAAI,WAAA,EAAa,qBAAA;EACtC,WAAA,EAAa,cAAA;EACb,KAAA,EAAO,UAAA;AAAA;AAAA,KAGJ,aAAA,MAAmB,cAAA,CAAe,CAAA,WAC9B,CAAA;EACL,IAAA,IAAQ,CAAA;EACR,GAAA,CAAI,QAAA,EAAU,CAAA,EAAG,aAAA;AAAA;AAAA,KAGhB,qBAAA,GAAwB,cAAA;EAEzB,IAAA;EACA,GAAA,CAAI,QAAA,WAAmB,aAAA;AAAA;AAAA,KAGtB,cAAA,GAAiB,cAAA;EAElB,IAAA;EACA,GAAA,CAAI,QAAA,aAAqB,IAAA;AAAA;AAAA,KAGjB,iBAAA;AAAA,UAOK,QAAA;EACf,IAAA;EACA,YAAA,EAAc,CAAA;EACd,UAAA,EAAY,aAAA,CAAc,CAAA;EAC1B,QAAA,EAAU,KAAA;EACV,aAAA,EAAe,UAAA;EACf,KAAA,EAAO,GAAA,kBAAqB,eAAA,CAAgB,CAAA;EAC5C,WAAA,EAAa,KAAA;EACb,aAAA,EAAe,KAAA;EACf,IAAA,EAAM,WAAA,CAAY,QAAA,CAAS,CAAA,aAAc,IAAA,EAAM,CAAA,EAAG,KAAA;EAClD,QAAA,GAAW,IAAA,EAAM,CAAA,EAAG,KAAA,mBAAwB,KAAA;EAC5C,cAAA,EAAgB,iBAAA;EAChB,kBAAA,EAAoB,iBAAA;EACpB,gBAAA,EAAkB,IAAA;EAClB,mBAAA;EACA,kBAAA;EACA,eAAA;EACA,mBAAA;EACA,qBAAA;EACA,aAAA,EAAe,uBAAA,CAAwB,CAAA;EACvC,oBAAA;EACA,sBAAA;EACA,yBAAA;EACA,WAAA,GAAc,GAAA;AAAA;AAAA,iBAmBA,cAAA,GAAA,CACd,UAAA,EAAY,aAAA,CAAc,CAAA,GAC1B,IAAA,EAAM,QAAA,CAAS,CAAA,WACf,QAAA,GAAW,IAAA,EAAM,CAAA,EAAG,KAAA,mBAAwB,KAAA,EAC5C,QAAA,EAAU,KAAA,UACT,QAAA,CAAS,CAAA;AAAA,iBA6BI,WAAA,GAAA,CACd,UAAA,EAAY,aAAA,CAAc,CAAA,GAC1B,IAAA,EAAM,QAAA,CAAS,CAAA,WACf,QAAA,GAAW,IAAA,EAAM,CAAA,EAAG,KAAA,mBAAwB,KAAA,EAC5C,QAAA,EAAU,KAAA,UACT,QAAA,CAAS,CAAA;AAAA,iBA4SI,kBAAA,GAAA,CACd,GAAA,mBACA,IAAA,EAAM,CAAA,EACN,KAAA,UACA,QAAA,EAAU,QAAA,CAAS,CAAA,IAClB,eAAA,CAAgB,CAAA;AAAA,iBA+KH,iBAAA,GAAA,CACd,QAAA,EAAU,QAAA,CAAS,CAAA,GACnB,QAAA,EAAU,CAAA,KACT,KAAA;AAAA,iBAygBa,gBAAA,GAAA,CAAoB,QAAA,EAAU,QAAA,CAAS,CAAA,IAAK,KAAA;AAAA,iBAS5C,sBAAA,GAAA,CAA0B,QAAA,EAAU,QAAA,CAAS,CAAA"}
1
+ {"version":3,"file":"for.d.ts","names":[],"sources":["../../src/runtime/for.ts"],"mappings":";;;;;;;;;UAoDiB,eAAA;EACf,GAAA;EACA,IAAA,EAAM,CAAA;EACN,YAAA,EAAc,CAAA;EACd,UAAA,EAAY,aAAA,CAAc,CAAA;EAC1B,mBAAA,EAAqB,GAAA,CAAI,WAAA,EAAa,qBAAA;EACtC,WAAA,EAAa,cAAA;EACb,KAAA,EAAO,UAAA;AAAA;AAAA,KAGJ,aAAA,MAAmB,cAAA,CAAe,CAAA,WAC9B,CAAA;EACL,IAAA,IAAQ,CAAA;EACR,GAAA,CAAI,QAAA,EAAU,CAAA,EAAG,aAAA;AAAA;AAAA,KAGhB,qBAAA,GAAwB,cAAA;EAEzB,IAAA;EACA,GAAA,CAAI,QAAA,WAAmB,aAAA;AAAA;AAAA,KAGtB,cAAA,GAAiB,cAAA;EAElB,IAAA;EACA,GAAA,CAAI,QAAA,aAAqB,IAAA;AAAA;AAAA,KAGjB,iBAAA;AAAA,UAOK,QAAA;EACf,IAAA;EACA,YAAA,EAAc,CAAA;EACd,UAAA,EAAY,aAAA,CAAc,CAAA;EAC1B,QAAA,EAAU,KAAA;EACV,aAAA,EAAe,UAAA;EACf,KAAA,EAAO,GAAA,kBAAqB,eAAA,CAAgB,CAAA;EAC5C,WAAA,EAAa,KAAA;EACb,aAAA,EAAe,KAAA;EACf,IAAA,EAAM,WAAA,CAAY,QAAA,CAAS,CAAA,aAAc,IAAA,EAAM,CAAA,EAAG,KAAA;EAClD,QAAA,GAAW,IAAA,EAAM,CAAA,EAAG,KAAA,mBAAwB,KAAA;EAC5C,cAAA,EAAgB,iBAAA;EAChB,kBAAA,EAAoB,iBAAA;EACpB,gBAAA,EAAkB,IAAA;EAClB,mBAAA;EACA,kBAAA;EACA,eAAA;EACA,mBAAA;EACA,qBAAA;EACA,aAAA,EAAe,uBAAA,CAAwB,CAAA;EACvC,oBAAA;EACA,sBAAA;EACA,yBAAA;EACA,WAAA,GAAc,GAAA;AAAA;AAAA,iBAmBA,cAAA,GAAA,CACd,UAAA,EAAY,aAAA,CAAc,CAAA,GAC1B,IAAA,EAAM,QAAA,CAAS,CAAA,WACf,QAAA,GAAW,IAAA,EAAM,CAAA,EAAG,KAAA,mBAAwB,KAAA,EAC5C,QAAA,EAAU,KAAA,UACT,QAAA,CAAS,CAAA;AAAA,iBA6BI,WAAA,GAAA,CACd,UAAA,EAAY,aAAA,CAAc,CAAA,GAC1B,IAAA,EAAM,QAAA,CAAS,CAAA,WACf,QAAA,GAAW,IAAA,EAAM,CAAA,EAAG,KAAA,mBAAwB,KAAA,EAC5C,QAAA,EAAU,KAAA,UACT,QAAA,CAAS,CAAA;AAAA,iBAiTI,kBAAA,GAAA,CACd,GAAA,mBACA,IAAA,EAAM,CAAA,EACN,KAAA,UACA,QAAA,EAAU,QAAA,CAAS,CAAA,IAClB,eAAA,CAAgB,CAAA;AAAA,iBAgLH,iBAAA,GAAA,CACd,QAAA,EAAU,QAAA,CAAS,CAAA,GACnB,QAAA,EAAU,CAAA,KACT,KAAA;AAAA,iBAygBa,gBAAA,GAAA,CAAoB,QAAA,EAAU,QAAA,CAAS,CAAA,IAAK,KAAA;AAAA,iBAS5C,sBAAA,GAAA,CAA0B,QAAA,EAAU,QAAA,CAAS,CAAA"}
@@ -151,22 +151,27 @@ function createReactiveForItem(itemSignal, propertySignals) {
151
151
  if (Reflect.getOwnPropertyDescriptor(target, prop)) return Reflect.get(target, prop, receiver);
152
152
  const currentItem = itemSignal.peek();
153
153
  if (typeof prop !== "symbol") return getOrCreateForItemPropertySignal(currentItem, propertySignals, prop)();
154
+ recordReadableRead(itemSignal);
154
155
  return Reflect.get(Object(currentItem), prop, receiver);
155
156
  },
156
157
  has(target, prop) {
158
+ recordReadableRead(itemSignal);
157
159
  return prop in target || prop in Object(itemSignal.peek());
158
160
  },
159
161
  ownKeys(target) {
162
+ recordReadableRead(itemSignal);
160
163
  const keys = new Set(Reflect.ownKeys(target));
161
164
  for (const key of Reflect.ownKeys(Object(itemSignal.peek()))) keys.add(key);
162
165
  return Array.from(keys);
163
166
  },
164
167
  getOwnPropertyDescriptor(target, prop) {
168
+ recordReadableRead(itemSignal);
165
169
  const ownDescriptor = Reflect.getOwnPropertyDescriptor(target, prop);
166
170
  if (ownDescriptor) return ownDescriptor;
167
171
  return Object.getOwnPropertyDescriptor(Object(itemSignal.peek()), prop);
168
172
  },
169
173
  getPrototypeOf() {
174
+ recordReadableRead(itemSignal);
170
175
  return Object.getPrototypeOf(Object(itemSignal.peek()));
171
176
  }
172
177
  });
@@ -253,25 +258,25 @@ function updateItemInstance(forState, itemInstance, item) {
253
258
  if (itemInstance.item === item) return false;
254
259
  const previousItem = itemInstance.item;
255
260
  itemInstance.item = item;
261
+ let needsRerender = false;
256
262
  const itemSignal = itemInstance.itemSignal;
257
263
  if (!itemSignal) {
258
264
  rerenderItemInstance(forState, itemInstance, item);
259
265
  return true;
260
266
  }
261
- let hasPropertyRenderReaders = false;
262
267
  const propertySignals = itemInstance.itemPropertySignals;
263
268
  if (propertySignals && propertySignals.size > 0) for (const [prop, propertySignal] of propertySignals) {
264
269
  const previousValue = readForItemProperty(previousItem, prop);
265
270
  const nextValue = readForItemProperty(item, prop);
266
271
  if (Object.is(previousValue, nextValue)) continue;
267
272
  const propertyHasRenderReaders = (propertySignal._readers?.size ?? 0) > 0;
268
- if (propertyHasRenderReaders) hasPropertyRenderReaders = true;
273
+ if (propertyHasRenderReaders) needsRerender = true;
269
274
  propertySignal.set(nextValue, !propertyHasRenderReaders);
270
275
  }
271
276
  const hasRenderReaders = (itemSignal._readers?.size ?? 0) > 0;
277
+ if (hasRenderReaders) needsRerender = true;
272
278
  itemSignal.set(item, hasRenderReaders);
273
- if (!hasRenderReaders && !hasPropertyRenderReaders) return false;
274
- rerenderItemInstance(forState, itemInstance, itemInstance.reactiveItem);
279
+ if (needsRerender) rerenderItemInstance(forState, itemInstance, itemInstance.reactiveItem);
275
280
  return true;
276
281
  }
277
282
  const FOR_FALLBACK_SCOPE_KEY = "__for-fallback__";