@finesoft/front 0.1.38 → 0.1.39

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../browser/src/action-handlers/external-url-action.ts","../../browser/src/utils/try-scroll.ts","../../browser/src/utils/history.ts","../../browser/src/action-handlers/flow-action.ts","../../browser/src/action-handlers/register.ts","../../browser/src/server-data.ts","../../browser/src/start-app.ts"],"sourcesContent":["/**\n * ExternalUrl Action Handler — 外部链接处理器\n */\n\nimport type { ExternalUrlAction, Framework, Logger } from \"@finesoft/core\";\nimport { ACTION_KINDS } from \"@finesoft/core\";\n\nexport interface ExternalUrlDependencies {\n\tframework: Framework;\n\tlog: Logger;\n}\n\nexport function registerExternalUrlHandler(\n\tdeps: ExternalUrlDependencies,\n): void {\n\tconst { framework, log } = deps;\n\n\tframework.onAction(\n\t\tACTION_KINDS.EXTERNAL_URL,\n\t\t(action: ExternalUrlAction) => {\n\t\t\tlog.debug(`ExternalUrlAction → ${action.url}`);\n\t\t\twindow.open(action.url, \"_blank\", \"noopener,noreferrer\");\n\t\t},\n\t);\n}\n","/**\n * tryScroll — 渐进式滚动位置恢复\n *\n * 使用 rAF 循环等待页面高度足够后恢复滚动位置。\n */\n\nimport type { Logger } from \"@finesoft/core\";\n\nconst MAX_TRIES = 100;\nconst FUDGE = 16;\n\nlet pendingFrame: number | null = null;\n\nexport function tryScroll(\n\tlog: Logger,\n\tgetScrollableElement: () => HTMLElement | null,\n\tscrollY: number,\n): void {\n\tif (pendingFrame !== null) {\n\t\tcancelAnimationFrame(pendingFrame);\n\t\tpendingFrame = null;\n\t}\n\n\tlet tries = 0;\n\n\tpendingFrame = requestAnimationFrame(function attempt() {\n\t\tif (++tries >= MAX_TRIES) {\n\t\t\tlog.warn(\n\t\t\t\t`tryScroll: gave up after ${MAX_TRIES} frames, target=${scrollY}`,\n\t\t\t);\n\t\t\tpendingFrame = null;\n\t\t\treturn;\n\t\t}\n\n\t\tconst el = getScrollableElement();\n\t\tif (!el) {\n\t\t\tlog.warn(\n\t\t\t\t\"could not restore scroll: the scrollable element is missing\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tconst { scrollHeight, offsetHeight } = el;\n\t\tconst canScroll = scrollY + offsetHeight <= scrollHeight + FUDGE;\n\n\t\tif (!canScroll) {\n\t\t\tlog.info(\"page is not tall enough for scroll yet\", {\n\t\t\t\tscrollHeight,\n\t\t\t\toffsetHeight,\n\t\t\t});\n\t\t\tpendingFrame = requestAnimationFrame(attempt);\n\t\t\treturn;\n\t\t}\n\n\t\tel.scrollTop = scrollY;\n\t\tlog.info(\"scroll restored to\", scrollY);\n\t\tpendingFrame = null;\n\t});\n}\n","/**\n * History 管理器 — 浏览器历史状态 + 滚动位置\n */\n\nimport type { Logger } from \"@finesoft/core\";\nimport { LruMap, generateUuid } from \"@finesoft/core\";\nimport { tryScroll } from \"./try-scroll\";\n\nconst HISTORY_SIZE_LIMIT = 10;\n\ninterface HistoryEntry<State> {\n\tstate: State;\n\tscrollY: number;\n}\n\ninterface HistoryOptions {\n\tgetScrollablePageElement: () => HTMLElement | null;\n}\n\nexport class History<State> {\n\tprivate readonly entries: LruMap<string, HistoryEntry<State>>;\n\tprivate readonly log: Logger;\n\tprivate readonly getScrollablePageElement: () => HTMLElement | null;\n\tprivate currentStateId: string | undefined;\n\n\tconstructor(\n\t\tlog: Logger,\n\t\toptions: HistoryOptions,\n\t\tsizeLimit = HISTORY_SIZE_LIMIT,\n\t) {\n\t\tthis.entries = new LruMap(sizeLimit);\n\t\tthis.log = log;\n\t\tthis.getScrollablePageElement = options.getScrollablePageElement;\n\t}\n\n\treplaceState(state: State, url: string): void {\n\t\tconst id = generateUuid();\n\t\twindow.history.replaceState({ id }, \"\", url);\n\t\tthis.currentStateId = id;\n\t\tthis.entries.set(id, { state, scrollY: 0 });\n\t\tthis.scrollTop = 0;\n\t\tthis.log.info(\"replaceState\", state, url, id);\n\t}\n\n\tpushState(state: State, url: string): void {\n\t\tconst id = generateUuid();\n\t\twindow.history.pushState({ id }, \"\", url);\n\t\tthis.currentStateId = id;\n\t\tthis.entries.set(id, { state, scrollY: 0 });\n\t\tthis.scrollTop = 0;\n\t\tthis.log.info(\"pushState\", state, url, id);\n\t}\n\n\tbeforeTransition(): void {\n\t\tconst { state } = window.history;\n\t\tif (!state) return;\n\n\t\tconst oldEntry = this.entries.get(state.id);\n\t\tif (!oldEntry) {\n\t\t\tthis.log.info(\n\t\t\t\t\"current history state evicted from LRU, not saving scroll position\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tconst { scrollTop } = this;\n\t\tthis.entries.set(state.id, { ...oldEntry, scrollY: scrollTop });\n\t\tthis.log.info(\"saving scroll position\", scrollTop);\n\t}\n\n\tonPopState(\n\t\tlistener: (url: string, state?: State) => void | Promise<void>,\n\t): void {\n\t\twindow.addEventListener(\"popstate\", async (event: PopStateEvent) => {\n\t\t\tthis.currentStateId = event.state?.id;\n\n\t\t\tif (!this.currentStateId) {\n\t\t\t\tthis.log.warn(\n\t\t\t\t\t\"encountered a null event.state.id in onPopState event:\",\n\t\t\t\t\twindow.location.href,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.log.info(\"popstate\", this.entries, this.currentStateId);\n\n\t\t\tconst entry = this.currentStateId\n\t\t\t\t? this.entries.get(this.currentStateId)\n\t\t\t\t: undefined;\n\n\t\t\tawait listener(window.location.href, entry?.state);\n\n\t\t\tif (!entry) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst { scrollY } = entry;\n\t\t\tthis.log.info(\"restoring scroll to\", scrollY);\n\t\t\ttryScroll(this.log, () => this.getScrollablePageElement(), scrollY);\n\t\t});\n\t}\n\n\t/** 仅推入 URL,不缓存页面状态(用于页面加载失败场景) */\n\tpushUrl(url: string): void {\n\t\tconst id = generateUuid();\n\t\twindow.history.pushState({ id }, \"\", url);\n\t\tthis.currentStateId = id;\n\t\tthis.scrollTop = 0;\n\t\tthis.log.info(\"pushUrl (no state)\", url, id);\n\t}\n\n\t/** 仅替换 URL,不缓存页面状态(用于页面加载失败场景) */\n\treplaceUrl(url: string): void {\n\t\tconst id = generateUuid();\n\t\twindow.history.replaceState({ id }, \"\", url);\n\t\tthis.currentStateId = id;\n\t\tthis.scrollTop = 0;\n\t\tthis.log.info(\"replaceUrl (no state)\", url, id);\n\t}\n\n\tupdateState(update: (current?: State) => State): void {\n\t\tif (!this.currentStateId) {\n\t\t\tthis.log.warn(\n\t\t\t\t\"failed: encountered a null currentStateId inside updateState\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tconst currentState = this.entries.get(this.currentStateId);\n\t\tconst newState = update(currentState?.state);\n\t\tthis.log.info(\"updateState\", newState, this.currentStateId);\n\t\tthis.entries.set(this.currentStateId, {\n\t\t\t...(currentState as HistoryEntry<State>),\n\t\t\tstate: newState,\n\t\t});\n\t}\n\n\tprivate get scrollTop(): number {\n\t\treturn this.getScrollablePageElement()?.scrollTop || 0;\n\t}\n\n\tprivate set scrollTop(scrollTop: number) {\n\t\tconst element = this.getScrollablePageElement();\n\t\tif (element) {\n\t\t\telement.scrollTop = scrollTop;\n\t\t}\n\t}\n}\n","/**\n * FlowAction Handler — 核心 SPA 导航处理器\n *\n * 通过 FlowActionCallbacks 注入 UI 更新回调,\n * 不直接依赖任何 UI 框架的 store。\n */\n\nimport type {\n\tBasePage,\n\tFlowAction,\n\tFramework,\n\tLogger,\n\tPostLoadContext,\n} from \"@finesoft/core\";\nimport { ACTION_KINDS, createBrowserContext } from \"@finesoft/core\";\nimport { History } from \"../utils/history\";\n\n/** FlowAction handler 的 History state */\ninterface FlowState {\n\tpage: BasePage;\n}\n\n/** UI 框架回调 — 解耦 Svelte store 等依赖 */\nexport interface FlowActionCallbacks {\n\t/** 导航后更新当前路径(替代 currentPath.set()) */\n\tonNavigate(pathname: string): void;\n\t/** 模态页面展示(替代 openModal()) */\n\tonModal(page: BasePage): void;\n}\n\n/** 注册 FlowAction handler 所需的依赖 */\nexport interface FlowActionDependencies {\n\tframework: Framework;\n\tlog: Logger;\n\tcallbacks: FlowActionCallbacks;\n\t/** 更新应用 UI 的回调,page 可以是 Promise */\n\tupdateApp: (props: {\n\t\tpage: Promise<BasePage> | BasePage;\n\t\tisFirstPage?: boolean;\n\t}) => void;\n\t/** 获取可滚动页面元素,用于滚动位置保存/恢复 */\n\tgetScrollablePageElement?: () => HTMLElement | null;\n}\n\nexport function registerFlowActionHandler(deps: FlowActionDependencies): void {\n\tconst { framework, log, callbacks, updateApp } = deps;\n\tlet isFirstPage = true;\n\tlet navigationId = 0;\n\n\t/** 重定向循环保护计数器 */\n\tconst MAX_REDIRECTS = 5;\n\n\tconst defaultGetScrollable = (): HTMLElement | null =>\n\t\tdocument.getElementById(\"scrollable-page-override\") ||\n\t\tdocument.getElementById(\"scrollable-page\") ||\n\t\tdocument.documentElement;\n\n\tconst history = new History<FlowState>(log, {\n\t\tgetScrollablePageElement:\n\t\t\tdeps.getScrollablePageElement ?? defaultGetScrollable,\n\t});\n\n\t/**\n\t * 核心导航逻辑(支持递归重定向)\n\t * @param redirectCount 当前重定向次数,用于循环保护\n\t */\n\tasync function navigateTo(\n\t\turl: string,\n\t\tredirectCount: number,\n\t\tthisNav: number,\n\t): Promise<void> {\n\t\tif (redirectCount >= MAX_REDIRECTS) {\n\t\t\tlog.error(\n\t\t\t\t`Navigation redirect loop detected (${MAX_REDIRECTS} redirects), stopping at: ${url}`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tconst shouldReplace =\n\t\t\tisFirstPage ||\n\t\t\turl === window.location.pathname + window.location.search;\n\n\t\tconst match = framework.routeUrl(url);\n\t\tif (!match) {\n\t\t\tlog.warn(`FlowAction: no route for ${url}`);\n\t\t\treturn;\n\t\t}\n\n\t\t// ===== beforeLoad 中间件 =====\n\t\tconst navCtx = createBrowserContext({\n\t\t\turl,\n\t\t\tintent: match.intent,\n\t\t\tcontainer: framework.container,\n\t\t});\n\t\tconst beforeResult = await framework.runBeforeLoad(\n\t\t\tnavCtx,\n\t\t\tmatch.beforeGuards,\n\t\t);\n\n\t\tif (beforeResult.kind === \"redirect\") {\n\t\t\tlog.debug(`beforeLoad → redirect to ${beforeResult.url}`);\n\t\t\tawait navigateTo(beforeResult.url, redirectCount + 1, thisNav);\n\t\t\treturn;\n\t\t}\n\t\tif (beforeResult.kind === \"deny\") {\n\t\t\tlog.warn(\n\t\t\t\t`beforeLoad → denied (${beforeResult.status}): ${beforeResult.message}`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\t// rewrite in beforeLoad: 无数据可言,当作 redirect 处理\n\t\tif (beforeResult.kind === \"rewrite\") {\n\t\t\tlog.debug(`beforeLoad → rewrite to ${beforeResult.url}`);\n\t\t\tawait navigateTo(beforeResult.url, redirectCount + 1, thisNav);\n\t\t\treturn;\n\t\t}\n\n\t\tconst pagePromise = framework.dispatch(\n\t\t\tmatch.intent,\n\t\t) as Promise<BasePage>;\n\n\t\tawait Promise.race([\n\t\t\tpagePromise,\n\t\t\tnew Promise((r) => setTimeout(r, 500)),\n\t\t]).catch(() => {});\n\n\t\tif (thisNav !== navigationId) {\n\t\t\tlog.info(\"FlowAction superseded by newer navigation\", url);\n\t\t\treturn;\n\t\t}\n\n\t\thistory.beforeTransition();\n\n\t\tupdateApp({\n\t\t\tpage: pagePromise.then(\n\t\t\t\tasync (page: BasePage): Promise<BasePage> => {\n\t\t\t\t\tif (thisNav !== navigationId) {\n\t\t\t\t\t\tlog.info(\"FlowAction commit superseded\", url);\n\t\t\t\t\t\treturn page;\n\t\t\t\t\t}\n\n\t\t\t\t\t// ===== afterLoad 中间件 =====\n\t\t\t\t\tconst postCtx: PostLoadContext = {\n\t\t\t\t\t\t...navCtx,\n\t\t\t\t\t\tpage,\n\t\t\t\t\t};\n\t\t\t\t\tconst afterResult = await framework.runAfterLoad(\n\t\t\t\t\t\tpostCtx,\n\t\t\t\t\t\tmatch.afterGuards,\n\t\t\t\t\t);\n\n\t\t\t\t\tif (afterResult.kind === \"redirect\") {\n\t\t\t\t\t\tlog.debug(`afterLoad → redirect to ${afterResult.url}`);\n\t\t\t\t\t\tnavigateTo(afterResult.url, redirectCount + 1, thisNav);\n\t\t\t\t\t\treturn page;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet canonicalURL = url;\n\n\t\t\t\t\tif (afterResult.kind === \"rewrite\") {\n\t\t\t\t\t\t// rewrite: 仅更新地址栏,保留已加载的 page 数据\n\t\t\t\t\t\tcanonicalURL = afterResult.url;\n\t\t\t\t\t\tlog.debug(`afterLoad → rewrite URL to ${canonicalURL}`);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (afterResult.kind === \"deny\") {\n\t\t\t\t\t\tlog.warn(`afterLoad → denied (${afterResult.status})`);\n\t\t\t\t\t\treturn page;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (shouldReplace) {\n\t\t\t\t\t\thistory.replaceState({ page }, canonicalURL);\n\t\t\t\t\t} else {\n\t\t\t\t\t\thistory.pushState({ page }, canonicalURL);\n\t\t\t\t\t}\n\n\t\t\t\t\tcallbacks.onNavigate(\n\t\t\t\t\t\tnew URL(canonicalURL, window.location.origin).pathname,\n\t\t\t\t\t);\n\n\t\t\t\t\tdidEnterPage(page);\n\t\t\t\t\treturn page;\n\t\t\t\t},\n\t\t\t\t(error: unknown) => {\n\t\t\t\t\t// 页面加载失败仍需推入历史记录,确保 back/forward 正常\n\t\t\t\t\tif (thisNav === navigationId) {\n\t\t\t\t\t\tconst canonicalURL = url;\n\t\t\t\t\t\tif (shouldReplace) {\n\t\t\t\t\t\t\thistory.replaceUrl(canonicalURL);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\thistory.pushUrl(canonicalURL);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcallbacks.onNavigate(\n\t\t\t\t\t\t\tnew URL(canonicalURL, window.location.origin)\n\t\t\t\t\t\t\t\t.pathname,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tthrow error;\n\t\t\t\t},\n\t\t\t),\n\t\t\tisFirstPage,\n\t\t});\n\n\t\tisFirstPage = false;\n\t}\n\n\t// ===== FlowAction handler =====\n\tframework.onAction(ACTION_KINDS.FLOW, async (action) => {\n\t\tconst flowAction = action as FlowAction;\n\t\tconst url = flowAction.url;\n\t\tlog.debug(`FlowAction → ${url}`);\n\n\t\t// 模态展示\n\t\tif (flowAction.presentationContext === \"modal\") {\n\t\t\tconst match = framework.routeUrl(url);\n\t\t\tif (match) {\n\t\t\t\tconst page = (await framework.dispatch(\n\t\t\t\t\tmatch.intent,\n\t\t\t\t)) as BasePage;\n\t\t\t\tcallbacks.onModal(page);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst thisNav = ++navigationId;\n\t\tawait navigateTo(url, 0, thisNav);\n\t});\n\n\t// ===== popstate handler =====\n\thistory.onPopState(async (url, cachedState) => {\n\t\tlog.debug(`popstate → ${url}, cached=${!!cachedState}`);\n\n\t\tcallbacks.onNavigate(new URL(url).pathname);\n\n\t\tif (cachedState) {\n\t\t\tconst { page } = cachedState;\n\t\t\tdidEnterPage(page);\n\t\t\tupdateApp({ page, isFirstPage });\n\t\t\treturn;\n\t\t}\n\n\t\tconst parsed = new URL(url);\n\t\tconst routeMatch = framework.routeUrl(parsed.pathname + parsed.search);\n\t\tif (!routeMatch) {\n\t\t\tlog.error(\n\t\t\t\t\"received popstate without data, but URL was unroutable:\",\n\t\t\t\turl,\n\t\t\t);\n\t\t\tdidEnterPage(null);\n\t\t\tupdateApp({\n\t\t\t\tpage: Promise.reject(new Error(\"404\")),\n\t\t\t\tisFirstPage,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\t// ===== beforeLoad 中间件 =====\n\t\tconst navCtx = createBrowserContext({\n\t\t\turl: parsed.pathname + parsed.search,\n\t\t\tintent: routeMatch.intent,\n\t\t\tcontainer: framework.container,\n\t\t});\n\t\tconst beforeResult = await framework.runBeforeLoad(\n\t\t\tnavCtx,\n\t\t\trouteMatch.beforeGuards,\n\t\t);\n\n\t\tif (beforeResult.kind === \"redirect\") {\n\t\t\tlog.debug(`popstate beforeLoad → redirect to ${beforeResult.url}`);\n\t\t\tconst thisNav = ++navigationId;\n\t\t\tawait navigateTo(beforeResult.url, 0, thisNav);\n\t\t\treturn;\n\t\t}\n\t\tif (beforeResult.kind === \"deny\" || beforeResult.kind === \"rewrite\") {\n\t\t\t// popstate 场景下 deny/rewrite 直接触发跳转\n\t\t\tif (beforeResult.kind === \"deny\") {\n\t\t\t\tlog.warn(`popstate beforeLoad → denied`);\n\t\t\t} else {\n\t\t\t\tconst thisNav = ++navigationId;\n\t\t\t\tawait navigateTo(beforeResult.url, 0, thisNav);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst pagePromise = framework.dispatch(\n\t\t\trouteMatch.intent,\n\t\t) as Promise<BasePage>;\n\n\t\tawait Promise.race([\n\t\t\tpagePromise,\n\t\t\tnew Promise((r) => setTimeout(r, 500)),\n\t\t]).catch(() => {});\n\n\t\tupdateApp({\n\t\t\tpage: pagePromise.then((page: BasePage): BasePage => {\n\t\t\t\tdidEnterPage(page);\n\t\t\t\treturn page;\n\t\t\t}),\n\t\t\tisFirstPage,\n\t\t});\n\t});\n\n\tfunction didEnterPage(page: BasePage | null): void {\n\t\t(async (): Promise<void> => {\n\t\t\ttry {\n\t\t\t\tif (page) {\n\t\t\t\t\tawait framework.didEnterPage(page);\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tlog.error(\"didEnterPage error:\", e);\n\t\t\t}\n\t\t})();\n\t}\n}\n","/**\n * Action Handler 统一注册入口\n */\n\nimport type { BasePage, Framework, Logger } from \"@finesoft/core\";\nimport { registerExternalUrlHandler } from \"./external-url-action\";\nimport {\n\tregisterFlowActionHandler,\n\ttype FlowActionCallbacks,\n} from \"./flow-action\";\n\nexport type { FlowActionCallbacks };\n\nexport interface ActionHandlerDependencies {\n\tframework: Framework;\n\tlog: Logger;\n\tcallbacks: FlowActionCallbacks;\n\tupdateApp: (props: {\n\t\tpage: Promise<BasePage> | BasePage;\n\t\tisFirstPage?: boolean;\n\t}) => void;\n\t/** 获取可滚动页面元素,用于滚动位置保存/恢复 */\n\tgetScrollablePageElement?: () => HTMLElement | null;\n}\n\nexport function registerActionHandlers(deps: ActionHandlerDependencies): void {\n\tconst { framework, log, callbacks, updateApp } = deps;\n\n\tregisterFlowActionHandler({\n\t\tframework,\n\t\tlog,\n\t\tcallbacks,\n\t\tupdateApp,\n\t\tgetScrollablePageElement: deps.getScrollablePageElement,\n\t});\n\n\tregisterExternalUrlHandler({ framework, log });\n}\n","/**\n * Server Data (browser side) — 从 DOM 反序列化服务端嵌入数据\n */\n\nimport { PrefetchedIntents, type PrefetchedIntent } from \"@finesoft/core\";\n\n/** DOM 中嵌入数据的 script 标签 ID */\nexport const SERVER_DATA_ID = \"serialized-server-data\";\n\n/**\n * 从 DOM 反序列化服务端嵌入的数据。\n * 读取 `<script id=\"serialized-server-data\">` 的内容并移除标签。\n */\nexport function deserializeServerData(): PrefetchedIntent[] | undefined {\n\tconst script = document.getElementById(SERVER_DATA_ID);\n\tif (!script?.textContent) return undefined;\n\n\tscript.parentNode?.removeChild(script);\n\n\ttry {\n\t\treturn JSON.parse(script.textContent);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * 从 DOM 提取 SSR 数据并构建 PrefetchedIntents 实例。\n * 替代原来的 PrefetchedIntents.fromDom()。\n */\nexport function createPrefetchedIntentsFromDom(): PrefetchedIntents {\n\tconst data = deserializeServerData();\n\tif (!data || !Array.isArray(data)) {\n\t\treturn PrefetchedIntents.empty();\n\t}\n\treturn PrefetchedIntents.fromArray(data);\n}\n","/**\n * startBrowserApp — 客户端 hydration 一站式启动\n *\n * 封装:\n * 1. PrefetchedIntents 从 DOM 提取\n * 2. Framework 创建 + 引导\n * 3. 应用层挂载(通过 mount 回调,框架无关)\n * 4. Action handlers 注册\n * 5. 初始页面触发\n */\n\nimport type { BasePage, Logger } from \"@finesoft/core\";\nimport { DEP_KEYS, Framework, type LoggerFactory } from \"@finesoft/core\";\nimport {\n\tregisterActionHandlers,\n\ttype FlowActionCallbacks,\n} from \"./action-handlers/register\";\nimport { createPrefetchedIntentsFromDom } from \"./server-data\";\n\nexport interface BrowserAppConfig {\n\t/** 注册 controllers 和路由的引导函数 */\n\tbootstrap: (framework: Framework) => void;\n\n\t/** 默认语言(回退值) */\n\tdefaultLocale?: string;\n\n\t/** DOM 挂载点 ID(默认 \"app\") */\n\tmountId?: string;\n\n\t/** 获取可滚动页面元素,用于滚动位置保存/恢复 */\n\tgetScrollablePageElement?: () => HTMLElement | null;\n\n\t/**\n\t * 挂载应用到 DOM\n\t *\n\t * 框架无关 — Svelte / React / Vue 均可通过此回调实现。\n\t *\n\t * @param target - DOM 挂载点\n\t * @param context - Framework 实例 + 语言\n\t * @returns 更新函数,用于后续页面切换\n\t */\n\tmount: (\n\t\ttarget: HTMLElement,\n\t\tcontext: { framework: Framework; locale: string },\n\t) => (props: {\n\t\tpage: Promise<BasePage> | BasePage;\n\t\tisFirstPage?: boolean;\n\t}) => void;\n\n\t/** FlowAction / ExternalUrl 回调 */\n\tcallbacks: FlowActionCallbacks;\n}\n\n/**\n * 启动客户端应用\n *\n * 自动执行 hydration 全流程。\n */\nexport async function startBrowserApp(config: BrowserAppConfig): Promise<void> {\n\tconst {\n\t\tbootstrap,\n\t\tdefaultLocale = \"en\",\n\t\tmountId = \"app\",\n\t\tmount,\n\t\tcallbacks,\n\t} = config;\n\n\t// 1. 从 DOM 提取 PrefetchedIntents 缓存\n\tconst prefetchedIntents = createPrefetchedIntentsFromDom();\n\n\t// 2. 初始化 Framework + 注册 Controllers\n\tconst framework = Framework.create({ prefetchedIntents });\n\tbootstrap(framework);\n\n\tconst loggerFactory = framework.container.resolve<LoggerFactory>(\n\t\tDEP_KEYS.LOGGER_FACTORY,\n\t);\n\tconst log: Logger = loggerFactory.loggerFor(\"browser\");\n\n\t// 3. 路由初始 URL\n\tconst initialAction = framework.routeUrl(\n\t\twindow.location.pathname + window.location.search,\n\t);\n\n\t// 4. 挂载应用(框架无关)\n\tconst locale = document.documentElement.lang || defaultLocale;\n\tconst target = document.getElementById(mountId)!;\n\tconst updateApp = mount(target, { framework, locale });\n\n\t// 5. 注册 Action Handlers\n\tregisterActionHandlers({\n\t\tframework,\n\t\tlog,\n\t\tcallbacks,\n\t\tupdateApp,\n\t\tgetScrollablePageElement: config.getScrollablePageElement,\n\t});\n\n\t// 6. 触发初始页面\n\tif (initialAction) {\n\t\tawait framework.perform(initialAction.action);\n\t} else {\n\t\tupdateApp({\n\t\t\tpage: Promise.reject(new Error(\"404\")),\n\t\t\tisFirstPage: true,\n\t\t});\n\t}\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  injectCSRShell,
3
3
  injectSSRContent
4
- } from "./chunk-AYO3UUQC.js";
4
+ } from "./chunk-XQ3UWZOS.js";
5
5
  import {
6
6
  parseAcceptLanguage
7
7
  } from "./chunk-PSPVIVC2.js";
@@ -141,17 +141,22 @@ function createSSRApp(options) {
141
141
  const cached = isrCache.get(cacheKey);
142
142
  if (cached) return c.html(cached);
143
143
  const requestFetch = parentFetch ? createInternalFetch(parentFetch, ssrDepth + 1) : void 0;
144
+ const ssrContext = { request: c.req.raw };
145
+ if (requestFetch) ssrContext.fetch = requestFetch;
144
146
  const {
145
147
  html: appHtml,
146
148
  head,
147
149
  css,
148
150
  serverData,
149
- renderMode
150
- } = await render(
151
- url,
152
- locale,
153
- requestFetch ? { fetch: requestFetch } : void 0
154
- );
151
+ renderMode,
152
+ redirect: middlewareRedirect
153
+ } = await render(url, locale, ssrContext);
154
+ if (middlewareRedirect) {
155
+ return c.redirect(
156
+ middlewareRedirect.url,
157
+ middlewareRedirect.status
158
+ );
159
+ }
155
160
  if (renderMode === "csr") {
156
161
  return c.html(injectCSRShell(template, locale));
157
162
  }
@@ -185,4 +190,4 @@ export {
185
190
  createInternalFetch,
186
191
  createSSRApp
187
192
  };
188
- //# sourceMappingURL=chunk-SFGR32K6.js.map
193
+ //# sourceMappingURL=chunk-OYTIGVEG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../server/src/app.ts","../../server/src/internal-fetch.ts"],"sourcesContent":["/**\n * createSSRApp — 创建 Hono SSR 应用\n *\n * 提供 SSR 通配路由,读取模板、加载 SSR 模块、渲染。\n * 应用层可在此之上追加自定义路由(API 代理等)。\n */\n\nimport { injectCSRShell, injectSSRContent } from \"@finesoft/ssr\";\nimport { Hono } from \"hono\";\nimport type { ViteDevServer } from \"vite\";\nimport {\n\tcreateInternalFetch,\n\tMAX_SSR_DEPTH,\n\tSSR_DEPTH_HEADER,\n} from \"./internal-fetch\";\nimport { parseAcceptLanguage } from \"./locale\";\n\n/**\n * 匹配 Vite 配置级别的 renderMode 覆盖。\n * 精确路径优先,然后 glob 模式。\n */\nfunction matchRenderModeOverride(\n\turl: string,\n\trenderModes?: Record<string, string>,\n): string | null {\n\tif (!renderModes) return null;\n\tconst path = url.split(\"?\")[0];\n\tif (renderModes[path]) return renderModes[path];\n\tfor (const [pattern, mode] of Object.entries(renderModes)) {\n\t\tif (pattern.includes(\"*\")) {\n\t\t\tconst escaped = pattern.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n\t\t\tconst re = new RegExp(\"^\" + escaped.replace(/\\*/g, \".*\") + \"$\");\n\t\t\tif (re.test(path)) return mode;\n\t\t}\n\t}\n\treturn null;\n}\n\nexport interface SSRModule {\n\trender: (\n\t\turl: string,\n\t\tlocale: string,\n\t\tssrContext?: {\n\t\t\tfetch?: typeof globalThis.fetch;\n\t\t\trequest?: Request;\n\t\t},\n\t) => Promise<{\n\t\thtml: string;\n\t\thead: string;\n\t\tcss: string;\n\t\tserverData: unknown;\n\t\trenderMode?: string;\n\t\tredirect?: { url: string; status: number };\n\t}>;\n\tserializeServerData: (data: unknown) => string;\n}\n\nexport interface SSRAppOptions {\n\t/** 项目根路径 */\n\troot: string;\n\t/** Vite dev server(仅开发模式) */\n\tvite?: ViteDevServer;\n\t/** 是否生产环境 */\n\tisProduction: boolean;\n\t/** SSR 入口文件路径(开发用,如 \"/src/ssr.ts\") */\n\tssrEntryPath?: string;\n\t/** 生产环境 SSR 模块路径(如 \"../dist/server/ssr.js\") */\n\tssrProductionModule?: string;\n\t/** 支持的语言列表 */\n\tsupportedLocales?: string[];\n\t/** 默认语言 */\n\tdefaultLocale?: string;\n\t/**\n\t * 父级 Hono app 的 fetch 函数,用于 SSR 内部路由回环。\n\t * SSR 渲染时,控制器的 fetch 请求(如 /api/apple/*)会通过此函数\n\t * 直接在进程内路由到代理 handler,避免网络自请求死锁。\n\t */\n\tparentFetch?: (request: Request) => Response | Promise<Response>;\n\t/**\n\t * 按路由覆盖渲染模式(精确路径或 glob 模式)。\n\t * 优先级高于路由级 renderMode。\n\t */\n\trenderModes?: Record<string, string>;\n}\n\nexport function createSSRApp(options: SSRAppOptions): Hono {\n\tconst {\n\t\troot,\n\t\tvite,\n\t\tisProduction,\n\t\tssrEntryPath = \"/src/ssr.ts\",\n\t\tssrProductionModule,\n\t\tsupportedLocales,\n\t\tdefaultLocale,\n\t\tparentFetch,\n\t\trenderModes,\n\t} = options;\n\n\tconst app = new Hono();\n\n\t/** ISR 内存缓存(prerender 路由首次请求后缓存,LRU 驱逐) */\n\tconst ISR_CACHE_MAX = 1000;\n\tconst isrCache = new Map<string, string>();\n\tfunction isrSet(key: string, val: string) {\n\t\tif (isrCache.size >= ISR_CACHE_MAX) {\n\t\t\tconst first = isrCache.keys().next().value;\n\t\t\tif (first !== undefined) isrCache.delete(first);\n\t\t}\n\t\tisrCache.set(key, val);\n\t}\n\n\t/** 生产环境模板缓存(模板不变,避免每请求重复读盘) */\n\tlet templateCache: string | undefined;\n\n\tasync function readTemplate(url: string): Promise<string> {\n\t\tif (!isProduction && vite) {\n\t\t\tconst { readFileSync } = await import(/* @vite-ignore */ \"node:fs\");\n\t\t\tconst { resolve } = await import(/* @vite-ignore */ \"node:path\");\n\t\t\tconst raw = readFileSync(resolve(root, \"index.html\"), \"utf-8\");\n\t\t\treturn vite.transformIndexHtml(url, raw);\n\t\t}\n\n\t\tif (templateCache) return templateCache;\n\n\t\tconst isDeno = typeof (globalThis as any).Deno !== \"undefined\";\n\t\tif (isDeno) {\n\t\t\ttemplateCache = (globalThis as any).Deno.readTextFileSync(\n\t\t\t\tnew URL(\"../dist/client/index.html\", import.meta.url),\n\t\t\t);\n\t\t\treturn templateCache!;\n\t\t}\n\n\t\tconst { readFileSync } = await import(/* @vite-ignore */ \"node:fs\");\n\t\tconst { resolve } = await import(/* @vite-ignore */ \"node:path\");\n\t\ttemplateCache = readFileSync(\n\t\t\tresolve(root, \"dist/client/index.html\"),\n\t\t\t\"utf-8\",\n\t\t);\n\t\treturn templateCache!;\n\t}\n\n\tasync function loadSSRModule(): Promise<SSRModule> {\n\t\tif (!isProduction && vite) {\n\t\t\treturn (await vite.ssrLoadModule(ssrEntryPath)) as SSRModule;\n\t\t}\n\t\tif (ssrProductionModule) {\n\t\t\treturn import(\n\t\t\t\t/* @vite-ignore */ ssrProductionModule\n\t\t\t) as Promise<SSRModule>;\n\t\t}\n\t\tconst { resolve } = await import(/* @vite-ignore */ \"node:path\");\n\t\tconst { pathToFileURL } = await import(/* @vite-ignore */ \"node:url\");\n\t\tconst absPath = pathToFileURL(resolve(root, \"dist/server/ssr.js\")).href;\n\t\treturn import(/* @vite-ignore */ absPath) as Promise<SSRModule>;\n\t}\n\n\tapp.get(\"*\", async (c) => {\n\t\t// 递归深度保护:从请求头读取 SSR 深度\n\t\tconst ssrDepth = parseInt(c.req.header(SSR_DEPTH_HEADER) ?? \"0\", 10);\n\t\tif (ssrDepth >= MAX_SSR_DEPTH) {\n\t\t\treturn c.text(\"SSR recursion loop detected\", 508);\n\t\t}\n\n\t\tconst url =\n\t\t\tc.req.path +\n\t\t\t(c.req.url.includes(\"?\") ? \"?\" + c.req.url.split(\"?\")[1] : \"\");\n\n\t\ttry {\n\t\t\tconst template = await readTemplate(url);\n\t\t\tconst { render, serializeServerData } = await loadSSRModule();\n\n\t\t\tconst locale = parseAcceptLanguage(\n\t\t\t\tc.req.header(\"accept-language\"),\n\t\t\t\tsupportedLocales,\n\t\t\t\tdefaultLocale,\n\t\t\t);\n\n\t\t\t// Vite 配置级别覆盖:CSR 直接返回空壳\n\t\t\tconst overrideMode = matchRenderModeOverride(url, renderModes);\n\t\t\tif (overrideMode === \"csr\") {\n\t\t\t\treturn c.html(injectCSRShell(template, locale));\n\t\t\t}\n\n\t\t\t// ISR 缓存命中(key 含 locale,避免跨语言缓存污染)\n\t\t\tconst cacheKey = `${locale}:${url}`;\n\t\t\tconst cached = isrCache.get(cacheKey);\n\t\t\tif (cached) return c.html(cached);\n\n\t\t\t// 每请求创建 internalFetch,深度通过请求头传递\n\t\t\tconst requestFetch = parentFetch\n\t\t\t\t? createInternalFetch(parentFetch, ssrDepth + 1)\n\t\t\t\t: undefined;\n\n\t\t\tconst ssrContext: {\n\t\t\t\tfetch?: typeof globalThis.fetch;\n\t\t\t\trequest?: Request;\n\t\t\t} = { request: c.req.raw };\n\t\t\tif (requestFetch) ssrContext.fetch = requestFetch;\n\n\t\t\tconst {\n\t\t\t\thtml: appHtml,\n\t\t\t\thead,\n\t\t\t\tcss,\n\t\t\t\tserverData,\n\t\t\t\trenderMode,\n\t\t\t\tredirect: middlewareRedirect,\n\t\t\t} = await render(url, locale, ssrContext);\n\n\t\t\t// 中间件要求重定向\n\t\t\tif (middlewareRedirect) {\n\t\t\t\treturn c.redirect(\n\t\t\t\t\tmiddlewareRedirect.url,\n\t\t\t\t\tmiddlewareRedirect.status as 301 | 302,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// CSR 模式:返回空壳 HTML\n\t\t\tif (renderMode === \"csr\") {\n\t\t\t\treturn c.html(injectCSRShell(template, locale));\n\t\t\t}\n\n\t\t\tconst serializedData = serializeServerData(serverData);\n\n\t\t\tconst finalHtml = injectSSRContent({\n\t\t\t\ttemplate,\n\t\t\t\tlocale,\n\t\t\t\thead,\n\t\t\t\tcss,\n\t\t\t\thtml: appHtml,\n\t\t\t\tserializedData,\n\t\t\t});\n\n\t\t\t// Prerender ISR 缓存(包括 Vite 配置覆盖和路由级)\n\t\t\tif (renderMode === \"prerender\" || overrideMode === \"prerender\") {\n\t\t\t\tisrSet(cacheKey, finalHtml);\n\t\t\t}\n\n\t\t\treturn c.html(finalHtml);\n\t\t} catch (e) {\n\t\t\tif (!isProduction && vite) {\n\t\t\t\tvite.ssrFixStacktrace(e as Error);\n\t\t\t}\n\t\t\tconsole.error(\"[SSR Error]\", e);\n\t\t\treturn c.text(\"Internal Server Error\", 500);\n\t\t}\n\t});\n\n\treturn app;\n}\n","/**\n * createInternalFetch — SSR 内部路由回环 fetch 包装器\n *\n * 将相对路径(/api/…)请求转为 Hono app.fetch 内存调用,\n * 绝对 URL 和非字符串 input 走 globalThis.fetch(真实网络)。\n *\n * 递归深度保护采用请求头传递:每次 SSR 回环在请求头中写入深度值,\n * SSR catch-all 读取深度判断是否超限。对比闭包计数器方案:\n * - 并发安全:无共享可变状态\n * - 跨渲染准确:深度随请求在 Hono 路由链中传递\n */\n\nexport const SSR_DEPTH_HEADER = \"x-ssr-depth\";\nexport const MAX_SSR_DEPTH = 5;\n\n/**\n * 创建请求级 internal fetch\n *\n * @param appFetch - Hono app.fetch(父级路由)\n * @param depth - 当前 SSR 深度(由 catch-all handler 从请求头读取后 +1 传入)\n */\nexport function createInternalFetch(\n\tappFetch: (request: Request) => Response | Promise<Response>,\n\tdepth: number = 1,\n): typeof globalThis.fetch {\n\treturn ((input: RequestInfo | URL, init?: RequestInit) => {\n\t\t// 只拦截相对路径(以 / 开头),其他一律走真实网络\n\t\tif (typeof input === \"string\" && input.startsWith(\"/\")) {\n\t\t\tconst request = new Request(`http://localhost${input}`, init);\n\t\t\trequest.headers.set(SSR_DEPTH_HEADER, String(depth));\n\t\t\treturn Promise.resolve(appFetch(request));\n\t\t}\n\n\t\t// 绝对 URL / URL 对象 / Request 对象 → 走正常网络\n\t\treturn globalThis.fetch(input, init);\n\t}) as typeof globalThis.fetch;\n}\n"],"mappings":"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","names":["readFileSync","resolve"]}
@@ -308,7 +308,8 @@ function makeDependencies(container, options = {}) {
308
308
  var Router = class {
309
309
  routes = [];
310
310
  /** 添加路由规则 */
311
- add(pattern, intentId, renderMode) {
311
+ add(pattern, intentId, renderModeOrOptions) {
312
+ const opts = typeof renderModeOrOptions === "string" ? { renderMode: renderModeOrOptions } : renderModeOrOptions ?? {};
312
313
  const paramNames = [];
313
314
  const regexStr = pattern.split(/(\/:[\w]+\??)/).map((segment) => {
314
315
  const paramMatch = segment.match(/^\/:(\w+)(\?)?$/);
@@ -323,7 +324,9 @@ var Router = class {
323
324
  intentId,
324
325
  regex: new RegExp(`^${regexStr}/?$`),
325
326
  paramNames,
326
- renderMode
327
+ renderMode: opts.renderMode,
328
+ beforeGuards: opts.beforeGuards,
329
+ afterGuards: opts.afterGuards
327
330
  });
328
331
  return this;
329
332
  }
@@ -345,7 +348,9 @@ var Router = class {
345
348
  return {
346
349
  intent: { id: route.intentId, params },
347
350
  action: makeFlowAction(urlOrPath),
348
- renderMode: route.renderMode
351
+ renderMode: route.renderMode,
352
+ beforeGuards: route.beforeGuards,
353
+ afterGuards: route.afterGuards
349
354
  };
350
355
  }
351
356
  }
@@ -412,6 +417,22 @@ var CompositeLogger = class {
412
417
  }
413
418
  };
414
419
 
420
+ // ../core/src/middleware/pipeline.ts
421
+ async function runBeforeLoadGuards(guards, ctx) {
422
+ for (const guard of guards) {
423
+ const result = await guard(ctx);
424
+ if (result.kind !== "next") return result;
425
+ }
426
+ return { kind: "next" };
427
+ }
428
+ async function runAfterLoadGuards(guards, ctx) {
429
+ for (const guard of guards) {
430
+ const result = await guard(ctx);
431
+ if (result.kind !== "next") return result;
432
+ }
433
+ return { kind: "next" };
434
+ }
435
+
415
436
  // ../core/src/prefetched-intents/stable-stringify.ts
416
437
  function stableStringify(obj, _seen) {
417
438
  if (obj === null || obj === void 0) return String(obj);
@@ -480,6 +501,8 @@ var Framework = class _Framework {
480
501
  actionDispatcher;
481
502
  router;
482
503
  prefetchedIntents;
504
+ beforeGuards = [];
505
+ afterGuards = [];
483
506
  constructor(container, prefetchedIntents) {
484
507
  this.container = container;
485
508
  this.intentDispatcher = new IntentDispatcher();
@@ -543,6 +566,25 @@ var Framework = class _Framework {
543
566
  registerIntent(controller) {
544
567
  this.intentDispatcher.register(controller);
545
568
  }
569
+ // ===== Navigation Middleware =====
570
+ /** 注册 beforeLoad 守卫(路由匹配后、数据加载前) */
571
+ beforeLoad(guard) {
572
+ this.beforeGuards.push(guard);
573
+ }
574
+ /** 注册 afterLoad 守卫(数据加载后、渲染前) */
575
+ afterLoad(guard) {
576
+ this.afterGuards.push(guard);
577
+ }
578
+ /** 执行所有 beforeLoad 守卫(全局 → 路由级) */
579
+ runBeforeLoad(ctx, routeGuards) {
580
+ const guards = routeGuards?.length ? [...this.beforeGuards, ...routeGuards] : this.beforeGuards;
581
+ return runBeforeLoadGuards(guards, ctx);
582
+ }
583
+ /** 执行所有 afterLoad 守卫(全局 → 路由级) */
584
+ runAfterLoad(ctx, routeGuards) {
585
+ const guards = routeGuards?.length ? [...this.afterGuards, ...routeGuards] : this.afterGuards;
586
+ return runAfterLoadGuards(guards, ctx);
587
+ }
546
588
  /** 销毁 Framework 实例 */
547
589
  dispose() {
548
590
  this.container.dispose();
@@ -687,7 +729,11 @@ function defineRoutes(framework, definitions) {
687
729
  framework.registerIntent(def.controller);
688
730
  registeredIntents.add(def.intentId);
689
731
  }
690
- framework.router.add(def.path, def.intentId, def.renderMode);
732
+ framework.router.add(def.path, def.intentId, {
733
+ renderMode: def.renderMode,
734
+ beforeGuards: def.beforeLoad,
735
+ afterGuards: def.afterLoad
736
+ });
691
737
  }
692
738
  }
693
739
 
@@ -781,6 +827,64 @@ function generateUuid() {
781
827
  });
782
828
  }
783
829
 
830
+ // ../core/src/middleware/context.ts
831
+ function parseCookieString(str) {
832
+ const map = /* @__PURE__ */ new Map();
833
+ if (!str) return map;
834
+ for (const pair of str.split(";")) {
835
+ const idx = pair.indexOf("=");
836
+ if (idx === -1) continue;
837
+ const key = pair.slice(0, idx).trim();
838
+ const val = pair.slice(idx + 1).trim();
839
+ if (key) map.set(key, val);
840
+ }
841
+ return map;
842
+ }
843
+ function createServerContext(options) {
844
+ const { url, intent, container, request } = options;
845
+ const parsed = new URL(url, "http://localhost");
846
+ const cookieHeader = request?.headers.get("cookie") ?? "";
847
+ const cookies = parseCookieString(cookieHeader);
848
+ return {
849
+ url,
850
+ path: parsed.pathname,
851
+ params: intent.params ?? {},
852
+ intent,
853
+ isServer: true,
854
+ container,
855
+ getCookie: (name) => cookies.get(name),
856
+ getHeader: (name) => request?.headers.get(name) ?? void 0
857
+ };
858
+ }
859
+ function createBrowserContext(options) {
860
+ const { url, intent, container } = options;
861
+ const parsed = new URL(url, window.location.origin);
862
+ return {
863
+ url,
864
+ path: parsed.pathname,
865
+ params: intent.params ?? {},
866
+ intent,
867
+ isServer: false,
868
+ container,
869
+ getCookie: (name) => parseCookieString(document.cookie).get(name),
870
+ getHeader: () => void 0
871
+ };
872
+ }
873
+
874
+ // ../core/src/middleware/types.ts
875
+ function next() {
876
+ return { kind: "next" };
877
+ }
878
+ function redirect(url, status = 302) {
879
+ return { kind: "redirect", url, status };
880
+ }
881
+ function rewrite(url) {
882
+ return { kind: "rewrite", url };
883
+ }
884
+ function deny(status = 403, message = "Forbidden") {
885
+ return { kind: "deny", status, message };
886
+ }
887
+
784
888
  export {
785
889
  ACTION_KINDS,
786
890
  isFlowAction,
@@ -801,6 +905,8 @@ export {
801
905
  Router,
802
906
  CompositeLoggerFactory,
803
907
  CompositeLogger,
908
+ runBeforeLoadGuards,
909
+ runAfterLoadGuards,
804
910
  stableStringify,
805
911
  PrefetchedIntents,
806
912
  Framework,
@@ -819,6 +925,12 @@ export {
819
925
  removeQueryParams,
820
926
  getBaseUrl,
821
927
  buildUrl,
822
- generateUuid
928
+ generateUuid,
929
+ createServerContext,
930
+ createBrowserContext,
931
+ next,
932
+ redirect,
933
+ rewrite,
934
+ deny
823
935
  };
824
- //# sourceMappingURL=chunk-OXKFPW4U.js.map
936
+ //# sourceMappingURL=chunk-SDPWQT2T.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../core/src/actions/types.ts","../../core/src/actions/dispatcher.ts","../../core/src/intents/dispatcher.ts","../../core/src/dependencies/container.ts","../../core/src/logger/base.ts","../../core/src/logger/local-storage-filter.ts","../../core/src/logger/console.ts","../../core/src/dependencies/make-dependencies.ts","../../core/src/router/router.ts","../../core/src/logger/composite.ts","../../core/src/middleware/pipeline.ts","../../core/src/prefetched-intents/stable-stringify.ts","../../core/src/prefetched-intents/prefetched-intents.ts","../../core/src/framework.ts","../../core/src/http/client.ts","../../core/src/intents/base-controller.ts","../../core/src/data/mapper.ts","../../core/src/bootstrap/define-routes.ts","../../core/src/utils/lru-map.ts","../../core/src/utils/optional.ts","../../core/src/utils/url.ts","../../core/src/utils/uuid.ts","../../core/src/middleware/context.ts","../../core/src/middleware/types.ts"],"sourcesContent":["/**\n * Action 类型定义\n *\n * FlowAction — SPA 内部导航\n * ExternalUrlAction — 打开外部链接\n * CompoundAction — 组合多个 Action\n */\n\n/** Action Kind 常量 */\nexport const ACTION_KINDS = {\n\tFLOW: \"flow\" as const,\n\tEXTERNAL_URL: \"externalUrl\" as const,\n\tCOMPOUND: \"compound\" as const,\n};\n\n/** FlowAction — SPA 导航 */\nexport interface FlowAction {\n\tkind: typeof ACTION_KINDS.FLOW;\n\turl: string;\n\t/** 展示方式: 默认 push,modal 弹窗 */\n\tpresentationContext?: \"default\" | \"modal\";\n}\n\n/** ExternalUrlAction — 外部链接 */\nexport interface ExternalUrlAction {\n\tkind: typeof ACTION_KINDS.EXTERNAL_URL;\n\turl: string;\n}\n\n/** CompoundAction — 组合 Action */\nexport interface CompoundAction {\n\tkind: typeof ACTION_KINDS.COMPOUND;\n\tactions: Action[];\n}\n\n/** 所有 Action 的联合类型 */\nexport type Action = FlowAction | ExternalUrlAction | CompoundAction;\n\n// ===== Type Guards =====\n\nexport function isFlowAction(action: Action): action is FlowAction {\n\treturn action.kind === ACTION_KINDS.FLOW;\n}\n\nexport function isExternalUrlAction(\n\taction: Action,\n): action is ExternalUrlAction {\n\treturn action.kind === ACTION_KINDS.EXTERNAL_URL;\n}\n\nexport function isCompoundAction(action: Action): action is CompoundAction {\n\treturn action.kind === ACTION_KINDS.COMPOUND;\n}\n\n// ===== Factory =====\n\nexport function makeFlowAction(\n\turl: string,\n\tpresentationContext?: FlowAction[\"presentationContext\"],\n): FlowAction {\n\treturn { kind: ACTION_KINDS.FLOW, url, presentationContext };\n}\n\nexport function makeExternalUrlAction(url: string): ExternalUrlAction {\n\treturn { kind: ACTION_KINDS.EXTERNAL_URL, url };\n}\n","/**\n * ActionDispatcher — Action 分发器\n *\n * 注册不同 kind 的 handler,按类型分发。\n * CompoundAction 自动展开递归执行。\n */\n\nimport type { Action } from \"./types\";\nimport { isCompoundAction } from \"./types\";\n\n/** Action 处理器函数类型 */\nexport type ActionHandler<A extends Action = Action> = (\n\taction: A,\n) => Promise<void> | void;\n\nexport class ActionDispatcher {\n\tprivate handlers = new Map<string, ActionHandler>();\n\tprivate wiredActions = new Set<string>();\n\n\t/** 注册指定 kind 的 handler(防止重复注册) */\n\tonAction<A extends Action>(kind: string, handler: ActionHandler<A>): void {\n\t\tif (this.wiredActions.has(kind)) {\n\t\t\tconsole.warn(\n\t\t\t\t`[ActionDispatcher] kind=\"${kind}\" already registered, skipping`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tthis.wiredActions.add(kind);\n\t\tthis.handlers.set(kind, handler as ActionHandler);\n\t}\n\n\t/** 执行一个 Action(CompoundAction 递归展开) */\n\tasync perform(action: Action): Promise<void> {\n\t\tif (isCompoundAction(action)) {\n\t\t\tfor (const subAction of action.actions) {\n\t\t\t\tawait this.perform(subAction);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst handler = this.handlers.get(action.kind);\n\t\tif (!handler) {\n\t\t\tconsole.warn(\n\t\t\t\t`[ActionDispatcher] No handler for kind=\"${action.kind}\"`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tawait handler(action);\n\t}\n}\n","/**\n * IntentDispatcher — Intent 分发器\n *\n * 注册 IntentController,按 intentId 分发。\n */\n\nimport type { Container } from \"../dependencies/container\";\nimport type { Intent, IntentController } from \"./types\";\n\nexport class IntentDispatcher {\n\tprivate controllers = new Map<string, IntentController>();\n\n\t/** 注册一个 IntentController */\n\tregister(controller: IntentController): void {\n\t\tthis.controllers.set(controller.intentId, controller);\n\t}\n\n\t/** 分发 Intent 到对应 Controller */\n\tasync dispatch<T>(intent: Intent<T>, container: Container): Promise<T> {\n\t\tconst controller = this.controllers.get(intent.id);\n\t\tif (!controller) {\n\t\t\tthrow new Error(\n\t\t\t\t`[IntentDispatcher] No controller for \"${intent.id}\". ` +\n\t\t\t\t\t`Registered: [${Array.from(this.controllers.keys()).join(\", \")}]`,\n\t\t\t);\n\t\t}\n\t\treturn controller.perform(intent, container) as Promise<T>;\n\t}\n\n\t/** 检查是否已注册某个 Intent */\n\thas(intentId: string): boolean {\n\t\treturn this.controllers.has(intentId);\n\t}\n}\n","/**\n * Container — 通用的依赖注入容器\n */\n\ntype Factory<T> = () => T;\n\ninterface Registration<T> {\n\tfactory: Factory<T>;\n\tsingleton: boolean;\n\tinstance?: T;\n}\n\nexport class Container {\n\tprivate registrations = new Map<string, Registration<unknown>>();\n\n\t/** 注册依赖(默认单例) */\n\tregister<T>(key: string, factory: Factory<T>, singleton = true): this {\n\t\tthis.registrations.set(key, { factory, singleton });\n\t\treturn this;\n\t}\n\n\t/** 解析依赖 */\n\tresolve<T>(key: string): T {\n\t\tconst reg = this.registrations.get(key);\n\t\tif (!reg) {\n\t\t\tthrow new Error(`[Container] No registration for key: \"${key}\"`);\n\t\t}\n\n\t\tif (reg.singleton) {\n\t\t\tif (reg.instance === undefined) {\n\t\t\t\treg.instance = reg.factory();\n\t\t\t}\n\t\t\treturn reg.instance as T;\n\t\t}\n\t\treturn reg.factory() as T;\n\t}\n\n\t/** 检查是否已注册 */\n\thas(key: string): boolean {\n\t\treturn this.registrations.has(key);\n\t}\n\n\t/** 销毁容器,清除所有缓存 */\n\tdispose(): void {\n\t\tfor (const reg of this.registrations.values()) {\n\t\t\treg.instance = undefined;\n\t\t}\n\t\tthis.registrations.clear();\n\t}\n}\n","/**\n * BaseLogger — 抽象日志基类\n */\n\nimport type { Logger } from \"./types\";\n\nexport abstract class BaseLogger implements Logger {\n\tprotected category: string;\n\n\tconstructor(category: string) {\n\t\tthis.category = category;\n\t}\n\n\tabstract debug(...args: unknown[]): string;\n\tabstract info(...args: unknown[]): string;\n\tabstract warn(...args: unknown[]): string;\n\tabstract error(...args: unknown[]): string;\n}\n","/**\n * localStorage 日志级别过滤\n *\n * 通过 localStorage.onyxLog 控制日志级别:\n * '*=info' — 全局 info 及以上\n * '*=info,Foo=off' — 全局 info,Foo 静默\n * 'Bar=error,Baz=warn' — Bar 只输出 error,Baz 输出 warn+\n */\n\nimport type { Level } from \"./types\";\n\ntype LevelNum = 4 | 3 | 2 | 1 | 0;\n\nconst LEVEL_TO_NUM: Record<string, LevelNum> = {\n\t\"*\": 4,\n\tdebug: 4,\n\tinfo: 3,\n\twarn: 2,\n\terror: 1,\n\toff: 0,\n\t\"\": 0,\n};\n\ninterface Rules {\n\tnamed?: Record<string, LevelNum>;\n\tdefaultLevel?: LevelNum;\n}\n\nlet cachedRules: Rules | undefined;\nlet cachedRaw: string | undefined;\n\nfunction parseRules(): Rules {\n\tif (typeof globalThis.localStorage === \"undefined\") {\n\t\treturn {};\n\t}\n\n\tlet raw: string | null;\n\ttry {\n\t\traw = globalThis.localStorage.getItem(\"onyxLog\");\n\t} catch {\n\t\treturn {};\n\t}\n\n\tif (!raw) return {};\n\n\tif (raw === cachedRaw && cachedRules) return cachedRules;\n\tcachedRaw = raw;\n\n\tconst rules: Rules = {};\n\tconst parts = raw.split(\",\");\n\n\tfor (const part of parts) {\n\t\tconst [name, level] = part.trim().split(\"=\");\n\t\tif (!name || level === undefined) continue;\n\n\t\tconst num = LEVEL_TO_NUM[level.toLowerCase()] ?? undefined;\n\t\tif (num === undefined) continue;\n\n\t\tif (name === \"*\") {\n\t\t\trules.defaultLevel = num;\n\t\t} else {\n\t\t\trules.named ??= {};\n\t\t\trules.named[name] = num;\n\t\t}\n\t}\n\n\tcachedRules = rules;\n\treturn rules;\n}\n\nexport function shouldLog(name: string, level: Level): boolean {\n\tconst rules = parseRules();\n\n\tif (rules.defaultLevel === undefined && !rules.named) {\n\t\treturn true;\n\t}\n\n\tconst currentNum = LEVEL_TO_NUM[level] ?? 4;\n\n\tif (rules.named?.[name] !== undefined) {\n\t\treturn currentNum <= rules.named[name];\n\t}\n\n\tif (rules.defaultLevel !== undefined) {\n\t\treturn currentNum <= rules.defaultLevel;\n\t}\n\n\treturn true;\n}\n\nexport function resetFilterCache(): void {\n\tcachedRules = undefined;\n\tcachedRaw = undefined;\n}\n","/**\n * ConsoleLogger — 基于 console 的日志实现\n */\n\nimport { BaseLogger } from \"./base\";\nimport { shouldLog } from \"./local-storage-filter\";\nimport type { Logger, LoggerFactory } from \"./types\";\n\nexport class ConsoleLogger extends BaseLogger {\n\tdebug(...args: unknown[]): string {\n\t\tif (shouldLog(this.category, \"debug\")) {\n\t\t\tconsole.debug(`[${this.category}]`, ...args);\n\t\t}\n\t\treturn \"\";\n\t}\n\n\tinfo(...args: unknown[]): string {\n\t\tif (shouldLog(this.category, \"info\")) {\n\t\t\tconsole.info(`[${this.category}]`, ...args);\n\t\t}\n\t\treturn \"\";\n\t}\n\n\twarn(...args: unknown[]): string {\n\t\tif (shouldLog(this.category, \"warn\")) {\n\t\t\tconsole.warn(`[${this.category}]`, ...args);\n\t\t}\n\t\treturn \"\";\n\t}\n\n\terror(...args: unknown[]): string {\n\t\tconsole.error(`[${this.category}]`, ...args);\n\t\treturn \"\";\n\t}\n}\n\nexport class ConsoleLoggerFactory implements LoggerFactory {\n\tloggerFor(category: string): Logger {\n\t\treturn new ConsoleLogger(category);\n\t}\n}\n","/**\n * 依赖工厂 — 创建所有基础依赖\n */\n\nimport { ConsoleLoggerFactory } from \"../logger/console\";\nimport type { Logger, LoggerFactory } from \"../logger/types\";\nimport { Container } from \"./container\";\n\n// ===== 重新导出 Logger 类型 =====\nexport type { Logger, LoggerFactory };\n\n// ===== 依赖接口 =====\n\n/** 网络请求层 */\nexport interface Net {\n\tfetch(url: string, options?: RequestInit): Promise<Response>;\n}\n\n/** 多语言状态 */\nexport interface Locale {\n\tlanguage: string;\n\tstorefront: string;\n\tsetActiveLocale(language: string, storefront: string): void;\n}\n\n/** 存储接口 */\nexport interface Storage {\n\tget(key: string): string | undefined;\n\tset(key: string, value: string): void;\n\tdelete(key: string): void;\n}\n\n/** Feature Flags */\nexport interface FeatureFlags {\n\tisEnabled(key: string): boolean;\n\tgetString(key: string): string | undefined;\n\tgetNumber(key: string): number | undefined;\n}\n\n/** Metrics 记录器 */\nexport interface MetricsRecorder {\n\trecordPageView(page: string, fields?: Record<string, unknown>): void;\n\trecordEvent(name: string, fields?: Record<string, unknown>): void;\n}\n\n// ===== 依赖 Key 常量 =====\n\nexport const DEP_KEYS = {\n\tLOGGER: \"logger\",\n\tLOGGER_FACTORY: \"loggerFactory\",\n\tNET: \"net\",\n\tLOCALE: \"locale\",\n\tSTORAGE: \"storage\",\n\tFEATURE_FLAGS: \"featureFlags\",\n\tMETRICS: \"metrics\",\n\tFETCH: \"fetch\",\n} as const;\n\n// ===== 默认实现 =====\n\nclass DefaultLocale implements Locale {\n\tlanguage = \"en\";\n\tstorefront = \"us\";\n\tsetActiveLocale(language: string, storefront: string) {\n\t\tthis.language = language;\n\t\tthis.storefront = storefront;\n\t}\n}\n\nclass MemoryStorage implements Storage {\n\tprivate store = new Map<string, string>();\n\tget(key: string) {\n\t\treturn this.store.get(key);\n\t}\n\tset(key: string, value: string) {\n\t\tthis.store.set(key, value);\n\t}\n\tdelete(key: string) {\n\t\tthis.store.delete(key);\n\t}\n}\n\nclass DefaultFeatureFlags implements FeatureFlags {\n\tprivate flags: Record<string, boolean | string | number>;\n\tconstructor(flags: Record<string, boolean | string | number> = {}) {\n\t\tthis.flags = flags;\n\t}\n\tisEnabled(key: string) {\n\t\treturn this.flags[key] === true;\n\t}\n\tgetString(key: string) {\n\t\tconst v = this.flags[key];\n\t\treturn typeof v === \"string\" ? v : undefined;\n\t}\n\tgetNumber(key: string) {\n\t\tconst v = this.flags[key];\n\t\treturn typeof v === \"number\" ? v : undefined;\n\t}\n}\n\nclass ConsoleMetrics implements MetricsRecorder {\n\trecordPageView(page: string, fields?: Record<string, unknown>) {\n\t\tconsole.info(`[Metrics:PageView] ${page}`, fields ?? \"\");\n\t}\n\trecordEvent(name: string, fields?: Record<string, unknown>) {\n\t\tconsole.info(`[Metrics:Event] ${name}`, fields ?? \"\");\n\t}\n}\n\n// ===== 依赖工厂 =====\n\nexport interface MakeDependenciesOptions {\n\tfetch?: typeof globalThis.fetch;\n\tlanguage?: string;\n\tstorefront?: string;\n\tfeatureFlags?: Record<string, boolean | string | number>;\n}\n\nexport function makeDependencies(\n\tcontainer: Container,\n\toptions: MakeDependenciesOptions = {},\n): void {\n\tconst {\n\t\tfetch: fetchFn = globalThis.fetch?.bind(globalThis),\n\t\tlanguage = \"en\",\n\t\tstorefront = \"us\",\n\t\tfeatureFlags = {},\n\t} = options;\n\n\tconst loggerFactory = new ConsoleLoggerFactory();\n\tcontainer.register<LoggerFactory>(\n\t\tDEP_KEYS.LOGGER_FACTORY,\n\t\t() => loggerFactory,\n\t);\n\n\tcontainer.register<Logger>(DEP_KEYS.LOGGER, () =>\n\t\tloggerFactory.loggerFor(\"framework\"),\n\t);\n\n\tcontainer.register<Net>(DEP_KEYS.NET, () => ({\n\t\tfetch: (url: string, opts?: RequestInit) => fetchFn(url, opts),\n\t}));\n\n\tcontainer.register<Locale>(DEP_KEYS.LOCALE, () => {\n\t\tconst locale = new DefaultLocale();\n\t\tlocale.setActiveLocale(language, storefront);\n\t\treturn locale;\n\t});\n\n\tcontainer.register<Storage>(DEP_KEYS.STORAGE, () => new MemoryStorage());\n\n\tcontainer.register<FeatureFlags>(\n\t\tDEP_KEYS.FEATURE_FLAGS,\n\t\t() => new DefaultFeatureFlags(featureFlags),\n\t);\n\n\tcontainer.register<MetricsRecorder>(\n\t\tDEP_KEYS.METRICS,\n\t\t() => new ConsoleMetrics(),\n\t);\n\n\tcontainer.register(DEP_KEYS.FETCH, () => fetchFn);\n}\n","/**\n * URL 路由器 — URL pattern → Intent + FlowAction\n */\n\nimport { makeFlowAction, type FlowAction } from \"../actions/types\";\nimport type { Intent } from \"../intents/types\";\nimport type { AfterLoadGuard, BeforeLoadGuard } from \"../middleware/types\";\n\n/** 路由匹配结果 */\nexport interface RouteMatch {\n\tintent: Intent;\n\taction: FlowAction;\n\trenderMode?: string;\n\t/** 该路由绑定的 beforeLoad 守卫 */\n\tbeforeGuards?: BeforeLoadGuard[];\n\t/** 该路由绑定的 afterLoad 守卫 */\n\tafterGuards?: AfterLoadGuard[];\n}\n\n/** 路由添加选项 */\nexport interface RouteAddOptions {\n\trenderMode?: string;\n\tbeforeGuards?: BeforeLoadGuard[];\n\tafterGuards?: AfterLoadGuard[];\n}\n\ninterface InternalRouteDefinition {\n\tpattern: string;\n\tintentId: string;\n\tregex: RegExp;\n\tparamNames: string[];\n\trenderMode?: string;\n\tbeforeGuards?: BeforeLoadGuard[];\n\tafterGuards?: AfterLoadGuard[];\n}\n\nexport class Router {\n\tprivate routes: InternalRouteDefinition[] = [];\n\n\t/** 添加路由规则 */\n\tadd(\n\t\tpattern: string,\n\t\tintentId: string,\n\t\trenderModeOrOptions?: string | RouteAddOptions,\n\t): this {\n\t\tconst opts: RouteAddOptions =\n\t\t\ttypeof renderModeOrOptions === \"string\"\n\t\t\t\t? { renderMode: renderModeOrOptions }\n\t\t\t\t: renderModeOrOptions ?? {};\n\n\t\tconst paramNames: string[] = [];\n\n\t\t// 将 /:param 和 /:param? 替换为捕获组,其余部分转义正则元字符\n\t\tconst regexStr = pattern\n\t\t\t.split(/(\\/:[\\w]+\\??)/)\n\t\t\t.map((segment) => {\n\t\t\t\tconst paramMatch = segment.match(/^\\/:(\\w+)(\\?)?$/);\n\t\t\t\tif (paramMatch) {\n\t\t\t\t\tparamNames.push(paramMatch[1]);\n\t\t\t\t\treturn paramMatch[2] ? \"(?:/([^/]+))?\" : \"/([^/]+)\";\n\t\t\t\t}\n\t\t\t\t// 非参数片段:转义所有正则特殊字符\n\t\t\t\treturn segment.replace(/[.+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n\t\t\t})\n\t\t\t.join(\"\");\n\n\t\tthis.routes.push({\n\t\t\tpattern,\n\t\t\tintentId,\n\t\t\tregex: new RegExp(`^${regexStr}/?$`),\n\t\t\tparamNames,\n\t\t\trenderMode: opts.renderMode,\n\t\t\tbeforeGuards: opts.beforeGuards,\n\t\t\tafterGuards: opts.afterGuards,\n\t\t});\n\n\t\treturn this;\n\t}\n\n\t/** 解析 URL → RouteMatch */\n\tresolve(urlOrPath: string): RouteMatch | null {\n\t\tconst path = this.extractPath(urlOrPath);\n\t\tconst queryParams = this.extractQueryParams(urlOrPath);\n\n\t\tfor (const route of this.routes) {\n\t\t\tconst match = path.match(route.regex);\n\t\t\tif (match) {\n\t\t\t\tconst params: Record<string, string> = {};\n\t\t\t\troute.paramNames.forEach((name, index) => {\n\t\t\t\t\tconst value = match[index + 1];\n\t\t\t\t\tif (value) params[name] = value;\n\t\t\t\t});\n\t\t\t\tfor (const [k, v] of Object.entries(queryParams)) {\n\t\t\t\t\tif (!(k in params)) params[k] = v;\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tintent: { id: route.intentId, params },\n\t\t\t\t\taction: makeFlowAction(urlOrPath),\n\t\t\t\t\trenderMode: route.renderMode,\n\t\t\t\t\tbeforeGuards: route.beforeGuards,\n\t\t\t\t\tafterGuards: route.afterGuards,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/** 获取所有已注册的路由 */\n\tgetRoutes(): string[] {\n\t\treturn this.routes.map((r) => `${r.pattern} → ${r.intentId}`);\n\t}\n\n\tprivate extractPath(url: string): string {\n\t\ttry {\n\t\t\tconst parsed = new URL(url, \"http://localhost\");\n\t\t\treturn parsed.pathname;\n\t\t} catch {\n\t\t\treturn url.split(\"?\")[0].split(\"#\")[0];\n\t\t}\n\t}\n\n\tprivate extractQueryParams(url: string): Record<string, string> {\n\t\ttry {\n\t\t\tconst parsed = new URL(url, \"http://localhost\");\n\t\t\tconst params: Record<string, string> = {};\n\t\t\tparsed.searchParams.forEach((v, k) => {\n\t\t\t\tparams[k] = v;\n\t\t\t});\n\t\t\treturn params;\n\t\t} catch {\n\t\t\treturn {};\n\t\t}\n\t}\n}\n","/**\n * Composite Logger — 组合日志,广播到多个后端\n */\n\nimport type { Logger, LoggerFactory } from \"./types\";\n\nexport class CompositeLoggerFactory implements LoggerFactory {\n\tconstructor(private readonly factories: LoggerFactory[]) {}\n\n\tloggerFor(name: string): Logger {\n\t\treturn new CompositeLogger(\n\t\t\tthis.factories.map((f) => f.loggerFor(name)),\n\t\t);\n\t}\n}\n\nexport class CompositeLogger implements Logger {\n\tconstructor(private readonly loggers: Logger[]) {}\n\n\tdebug(...args: unknown[]): string {\n\t\treturn this.callAll(\"debug\", args);\n\t}\n\n\tinfo(...args: unknown[]): string {\n\t\treturn this.callAll(\"info\", args);\n\t}\n\n\twarn(...args: unknown[]): string {\n\t\treturn this.callAll(\"warn\", args);\n\t}\n\n\terror(...args: unknown[]): string {\n\t\treturn this.callAll(\"error\", args);\n\t}\n\n\tprivate callAll(\n\t\tmethod: \"debug\" | \"info\" | \"warn\" | \"error\",\n\t\targs: unknown[],\n\t): string {\n\t\tfor (const logger of this.loggers) {\n\t\t\tlogger[method](...args);\n\t\t}\n\t\treturn \"\";\n\t}\n}\n","/**\n * Guard Pipeline — 顺序执行守卫链\n *\n * 按注册顺序执行守卫,遇到第一个非 `next` 结果立即短路返回。\n */\n\nimport type {\n\tAfterLoadGuard,\n\tBeforeLoadGuard,\n\tMiddlewareResult,\n\tNavigationContext,\n\tPostLoadContext,\n} from \"./types\";\n\n/** 执行 beforeLoad 守卫链 */\nexport async function runBeforeLoadGuards(\n\tguards: BeforeLoadGuard[],\n\tctx: NavigationContext,\n): Promise<MiddlewareResult> {\n\tfor (const guard of guards) {\n\t\tconst result = await guard(ctx);\n\t\tif (result.kind !== \"next\") return result;\n\t}\n\treturn { kind: \"next\" };\n}\n\n/** 执行 afterLoad 守卫链 */\nexport async function runAfterLoadGuards(\n\tguards: AfterLoadGuard[],\n\tctx: PostLoadContext,\n): Promise<MiddlewareResult> {\n\tfor (const guard of guards) {\n\t\tconst result = await guard(ctx);\n\t\tif (result.kind !== \"next\") return result;\n\t}\n\treturn { kind: \"next\" };\n}\n","/**\n * stableStringify — 确定性 JSON 序列化(keys 按字母排序)\n *\n * 用作缓存 key:相同内容的对象始终产生相同字符串。\n */\n\nexport function stableStringify(obj: unknown, _seen?: Set<object>): string {\n\tif (obj === null || obj === undefined) return String(obj);\n\tif (typeof obj !== \"object\") return JSON.stringify(obj);\n\n\tconst seen = _seen ?? new Set<object>();\n\tif (seen.has(obj as object)) return '\"[Circular]\"';\n\tseen.add(obj as object);\n\n\tif (Array.isArray(obj)) {\n\t\treturn \"[\" + obj.map((v) => stableStringify(v, seen)).join(\",\") + \"]\";\n\t}\n\tconst keys = Object.keys(obj as Record<string, unknown>).sort();\n\tconst parts = keys\n\t\t.filter((k) => (obj as Record<string, unknown>)[k] !== undefined)\n\t\t.map(\n\t\t\t(k) =>\n\t\t\t\tJSON.stringify(k) +\n\t\t\t\t\":\" +\n\t\t\t\tstableStringify((obj as Record<string, unknown>)[k], seen),\n\t\t);\n\treturn \"{\" + parts.join(\",\") + \"}\";\n}\n","/**\n * PrefetchedIntents — SSR 数据缓存\n *\n * 服务端渲染时将 Intent→Data 映射序列化嵌入 HTML,\n * 客户端 hydrate 时提取缓存。Framework.dispatch() 优先查缓存,\n * 命中则直接返回,未命中则走 Controller 调度。\n */\n\nimport type { Intent } from \"../intents/types\";\nimport { stableStringify } from \"./stable-stringify\";\n\n/** 预获取的 Intent-Data 对 */\nexport interface PrefetchedIntent {\n\tintent: Intent;\n\tdata: unknown;\n}\n\nexport class PrefetchedIntents {\n\tprivate intents: Map<string, unknown>;\n\n\tprivate constructor(intents: Map<string, unknown>) {\n\t\tthis.intents = intents;\n\t}\n\n\t/** 从 PrefetchedIntent 数组创建缓存实例 */\n\tstatic fromArray(items: PrefetchedIntent[]): PrefetchedIntents {\n\t\tconst map = new Map<string, unknown>();\n\t\tfor (const item of items) {\n\t\t\tif (item.intent && item.data !== undefined) {\n\t\t\t\tconst key = stableStringify(item.intent);\n\t\t\t\tmap.set(key, item.data);\n\t\t\t}\n\t\t}\n\t\treturn new PrefetchedIntents(map);\n\t}\n\n\t/** 创建空缓存实例 */\n\tstatic empty(): PrefetchedIntents {\n\t\treturn new PrefetchedIntents(new Map());\n\t}\n\n\t/**\n\t * 获取缓存的 Intent 结果(一次性使用)。\n\t * 命中后从缓存中删除。\n\t */\n\tget<T>(intent: Intent<T>): T | undefined {\n\t\tconst key = stableStringify(intent);\n\t\tconst data = this.intents.get(key);\n\t\tif (data !== undefined) {\n\t\t\tthis.intents.delete(key);\n\t\t\treturn data as T;\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/** 检查缓存中是否有某个 Intent 的数据 */\n\thas(intent: Intent): boolean {\n\t\treturn this.intents.has(stableStringify(intent));\n\t}\n\n\t/** 缓存中的条目数 */\n\tget size(): number {\n\t\treturn this.intents.size;\n\t}\n}\n","/**\n * Framework — 框架核心类\n *\n * 对应原版 Jet 类,统一管理: DI 容器、Intent 分发、Action 分发、路由、Metrics。\n * 纯 TypeScript,不依赖任何 UI 框架。\n */\n\nimport { ActionDispatcher, type ActionHandler } from \"./actions/dispatcher\";\nimport type { Action } from \"./actions/types\";\nimport { Container } from \"./dependencies/container\";\nimport type { MetricsRecorder } from \"./dependencies/make-dependencies\";\nimport {\n\tDEP_KEYS,\n\tmakeDependencies,\n\ttype MakeDependenciesOptions,\n} from \"./dependencies/make-dependencies\";\nimport { IntentDispatcher } from \"./intents/dispatcher\";\nimport type { Intent, IntentController } from \"./intents/types\";\nimport type { Logger } from \"./logger/types\";\nimport { runAfterLoadGuards, runBeforeLoadGuards } from \"./middleware/pipeline\";\nimport type {\n\tAfterLoadGuard,\n\tBeforeLoadGuard,\n\tMiddlewareResult,\n\tNavigationContext,\n\tPostLoadContext,\n} from \"./middleware/types\";\nimport type { BasePage } from \"./models/page\";\nimport { PrefetchedIntents } from \"./prefetched-intents/prefetched-intents\";\nimport { Router, type RouteMatch } from \"./router/router\";\n\n/** Framework 初始化配置 */\nexport interface FrameworkConfig extends MakeDependenciesOptions {\n\tsetupRoutes?: (router: Router) => void;\n\tprefetchedIntents?: PrefetchedIntents;\n}\n\nexport class Framework {\n\treadonly container: Container;\n\treadonly intentDispatcher: IntentDispatcher;\n\treadonly actionDispatcher: ActionDispatcher;\n\treadonly router: Router;\n\treadonly prefetchedIntents: PrefetchedIntents;\n\n\tprivate readonly beforeGuards: BeforeLoadGuard[] = [];\n\tprivate readonly afterGuards: AfterLoadGuard[] = [];\n\n\tprivate constructor(\n\t\tcontainer: Container,\n\t\tprefetchedIntents: PrefetchedIntents,\n\t) {\n\t\tthis.container = container;\n\t\tthis.intentDispatcher = new IntentDispatcher();\n\t\tthis.actionDispatcher = new ActionDispatcher();\n\t\tthis.router = new Router();\n\t\tthis.prefetchedIntents = prefetchedIntents;\n\t}\n\n\t/** 创建并初始化 Framework 实例 */\n\tstatic create(config: FrameworkConfig = {}): Framework {\n\t\tconst container = new Container();\n\t\tmakeDependencies(container, config);\n\n\t\tconst fw = new Framework(\n\t\t\tcontainer,\n\t\t\tconfig.prefetchedIntents ?? PrefetchedIntents.empty(),\n\t\t);\n\n\t\tconfig.setupRoutes?.(fw.router);\n\n\t\treturn fw;\n\t}\n\n\t/** 分发 Intent — 获取页面数据 */\n\tasync dispatch<T>(intent: Intent<T>): Promise<T> {\n\t\tconst logger = this.container.resolve<Logger>(DEP_KEYS.LOGGER);\n\n\t\tconst cached = this.prefetchedIntents.get(intent);\n\t\tif (cached !== undefined) {\n\t\t\tlogger.debug(\n\t\t\t\t`[Framework] re-using prefetched intent response for: ${intent.id}`,\n\t\t\t\tintent.params,\n\t\t\t);\n\t\t\treturn cached;\n\t\t}\n\n\t\tlogger.debug(\n\t\t\t`[Framework] dispatch intent: ${intent.id}`,\n\t\t\tintent.params,\n\t\t);\n\t\treturn this.intentDispatcher.dispatch(intent, this.container);\n\t}\n\n\t/** 执行 Action — 处理用户交互 */\n\tasync perform(action: Action): Promise<void> {\n\t\tconst logger = this.container.resolve<Logger>(DEP_KEYS.LOGGER);\n\t\tlogger.debug(`[Framework] perform action: ${action.kind}`);\n\t\treturn this.actionDispatcher.perform(action);\n\t}\n\n\t/** 路由 URL — 将 URL 解析为 Intent + Action */\n\trouteUrl(url: string): RouteMatch | null {\n\t\treturn this.router.resolve(url);\n\t}\n\n\t/** 记录页面访问事件 */\n\tdidEnterPage(page: BasePage): void {\n\t\tconst metrics = this.container.resolve<MetricsRecorder>(\n\t\t\tDEP_KEYS.METRICS,\n\t\t);\n\t\tmetrics.recordPageView(page.pageType, {\n\t\t\tpageId: page.id,\n\t\t\ttitle: page.title,\n\t\t});\n\t}\n\n\t/** 注册 Action 处理器 */\n\tonAction<A extends Action>(kind: string, handler: ActionHandler<A>): void {\n\t\tthis.actionDispatcher.onAction(kind, handler);\n\t}\n\n\t/** 注册 Intent Controller */\n\tregisterIntent(controller: IntentController): void {\n\t\tthis.intentDispatcher.register(controller);\n\t}\n\n\t// ===== Navigation Middleware =====\n\n\t/** 注册 beforeLoad 守卫(路由匹配后、数据加载前) */\n\tbeforeLoad(guard: BeforeLoadGuard): void {\n\t\tthis.beforeGuards.push(guard);\n\t}\n\n\t/** 注册 afterLoad 守卫(数据加载后、渲染前) */\n\tafterLoad(guard: AfterLoadGuard): void {\n\t\tthis.afterGuards.push(guard);\n\t}\n\n\t/** 执行所有 beforeLoad 守卫(全局 → 路由级) */\n\trunBeforeLoad(\n\t\tctx: NavigationContext,\n\t\trouteGuards?: BeforeLoadGuard[],\n\t): Promise<MiddlewareResult> {\n\t\tconst guards = routeGuards?.length\n\t\t\t? [...this.beforeGuards, ...routeGuards]\n\t\t\t: this.beforeGuards;\n\t\treturn runBeforeLoadGuards(guards, ctx);\n\t}\n\n\t/** 执行所有 afterLoad 守卫(全局 → 路由级) */\n\trunAfterLoad(\n\t\tctx: PostLoadContext,\n\t\trouteGuards?: AfterLoadGuard[],\n\t): Promise<MiddlewareResult> {\n\t\tconst guards = routeGuards?.length\n\t\t\t? [...this.afterGuards, ...routeGuards]\n\t\t\t: this.afterGuards;\n\t\treturn runAfterLoadGuards(guards, ctx);\n\t}\n\n\t/** 销毁 Framework 实例 */\n\tdispose(): void {\n\t\tthis.container.dispose();\n\t}\n}\n","/**\n * HttpClient — 通用 HTTP 客户端基类\n *\n * 为 API Client 提供标准化的 HTTP 请求能力。\n * 子类继承后只需关注业务端点定义,不需要重复实现 fetch / JSON 解析 / 错误处理。\n */\n\n/** HTTP 请求错误 */\nexport class HttpError extends Error {\n\tconstructor(\n\t\tpublic readonly status: number,\n\t\tpublic readonly statusText: string,\n\t\tpublic readonly body?: string,\n\t) {\n\t\tsuper(`HTTP ${status}: ${statusText}`);\n\t\tthis.name = \"HttpError\";\n\t}\n}\n\n/** HttpClient 构造配置 */\nexport interface HttpClientConfig {\n\t/** API base URL(如 \"/api\" 或 \"https://example.com/api\") */\n\tbaseUrl: string;\n\t/** 默认请求头 */\n\tdefaultHeaders?: Record<string, string>;\n\t/** 自定义 fetch 实现(便于测试或 SSR) */\n\tfetch?: typeof globalThis.fetch;\n}\n\n/**\n * 通用 HTTP 客户端基类\n *\n * 使用方式: 创建子类继承 HttpClient,定义业务方法调用 this.get() / this.post() 等。\n *\n * @example\n * ```ts\n * class MyApiClient extends HttpClient {\n * async getUser(id: string) {\n * return this.get<User>(`/users/${id}`);\n * }\n * }\n * ```\n */\nexport abstract class HttpClient {\n\tprotected readonly baseUrl: string;\n\tprotected readonly defaultHeaders: Record<string, string>;\n\tprotected readonly fetchFn: typeof globalThis.fetch;\n\n\tconstructor(config: HttpClientConfig) {\n\t\tthis.baseUrl = config.baseUrl;\n\t\tthis.defaultHeaders = config.defaultHeaders ?? {};\n\t\tthis.fetchFn = config.fetch ?? globalThis.fetch.bind(globalThis);\n\t}\n\n\t/** GET 请求,返回解析后的 JSON */\n\tprotected async get<T>(\n\t\tpath: string,\n\t\tparams?: Record<string, string>,\n\t): Promise<T> {\n\t\treturn this.request<T>(\"GET\", path, { params });\n\t}\n\n\t/** POST 请求,自动序列化 body 为 JSON */\n\tprotected async post<T>(\n\t\tpath: string,\n\t\tbody?: unknown,\n\t\tparams?: Record<string, string>,\n\t): Promise<T> {\n\t\treturn this.request<T>(\"POST\", path, { body, params });\n\t}\n\n\t/** PUT 请求 */\n\tprotected async put<T>(\n\t\tpath: string,\n\t\tbody?: unknown,\n\t\tparams?: Record<string, string>,\n\t): Promise<T> {\n\t\treturn this.request<T>(\"PUT\", path, { body, params });\n\t}\n\n\t/** DELETE 请求 */\n\tprotected async del<T>(\n\t\tpath: string,\n\t\tparams?: Record<string, string>,\n\t): Promise<T> {\n\t\treturn this.request<T>(\"DELETE\", path, { params });\n\t}\n\n\t/**\n\t * 底层请求方法 — 子类可覆写以自定义行为\n\t *\n\t * 自动处理:\n\t * - URL 拼接 (baseUrl + path + params)\n\t * - 默认 headers 合并\n\t * - JSON body 序列化\n\t * - 响应 JSON 解析\n\t * - 非 2xx 状态码抛出 HttpError\n\t */\n\tprotected async request<T>(\n\t\tmethod: string,\n\t\tpath: string,\n\t\toptions?: {\n\t\t\tparams?: Record<string, string>;\n\t\t\tbody?: unknown;\n\t\t\theaders?: Record<string, string>;\n\t\t},\n\t): Promise<T> {\n\t\tconst url = this.buildUrl(path, options?.params);\n\n\t\tconst headers: Record<string, string> = {\n\t\t\t...this.defaultHeaders,\n\t\t\t...options?.headers,\n\t\t};\n\n\t\tconst init: RequestInit = { method, headers };\n\n\t\tif (options?.body !== undefined) {\n\t\t\theaders[\"Content-Type\"] =\n\t\t\t\theaders[\"Content-Type\"] ?? \"application/json\";\n\t\t\tinit.body = JSON.stringify(options.body);\n\t\t}\n\n\t\tconst response = await this.fetchFn(url, init);\n\n\t\tif (!response.ok) {\n\t\t\tconst body = await response.text().catch(() => undefined);\n\t\t\tthrow new HttpError(response.status, response.statusText, body);\n\t\t}\n\n\t\treturn response.json();\n\t}\n\n\t/** 构建完整 URL — 子类可覆写以自定义 URL 拼接逻辑 */\n\tprotected buildUrl(path: string, params?: Record<string, string>): string {\n\t\tconst base = this.baseUrl.endsWith(\"/\")\n\t\t\t? this.baseUrl.slice(0, -1)\n\t\t\t: this.baseUrl;\n\t\tconst normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n\t\tconst url = new URL(`${base}${normalizedPath}`, \"http://placeholder\");\n\n\t\tif (params) {\n\t\t\tfor (const [k, v] of Object.entries(params)) {\n\t\t\t\turl.searchParams.set(k, v);\n\t\t\t}\n\t\t}\n\n\t\t// 如果 baseUrl 是绝对 URL,返回完整 URL;否则只返回 path + search\n\t\tif (this.baseUrl.startsWith(\"http\")) {\n\t\t\treturn url.toString();\n\t\t}\n\t\treturn `${url.pathname}${url.search}`;\n\t}\n}\n","/**\n * BaseController — 抽象 Intent Controller 基类\n *\n * 提供标准化的 try/catch → fallback 模式。\n * 子类只需实现 execute() 和可选的 fallback()。\n */\n\nimport type { Container } from \"../dependencies/container\";\nimport type { Intent, IntentController } from \"./types\";\n\n/**\n * 抽象 Controller 基类\n *\n * 统一处理:\n * - 类型安全的参数提取 (TParams)\n * - 返回类型约束 (TResult)\n * - try/catch 错误处理 + 可选 fallback\n *\n * @example\n * ```ts\n * class ProductController extends BaseController<{ productId: string }, ProductPage> {\n * readonly intentId = \"product-page\";\n *\n * async execute(params: { productId: string }, container: Container) {\n * const api = container.resolve<ApiClient>(\"api\");\n * return api.getProduct(params.productId);\n * }\n *\n * fallback(params: { productId: string }, error: Error) {\n * return getMockProduct(params.productId);\n * }\n * }\n * ```\n */\nexport abstract class BaseController<\n\tTParams extends Record<string, string | undefined> = Record<string, string>,\n\tTResult = unknown,\n> implements IntentController<TResult> {\n\t/** Controller 对应的 Intent ID */\n\tabstract readonly intentId: string;\n\n\t/**\n\t * 执行业务逻辑 — 子类必须实现\n\t *\n\t * @param params - Intent 参数(已类型化)\n\t * @param container - DI 容器\n\t * @returns 页面数据\n\t */\n\tabstract execute(\n\t\tparams: TParams,\n\t\tcontainer: Container,\n\t): Promise<TResult> | TResult;\n\n\t/**\n\t * 错误回退 — 子类可选覆写\n\t *\n\t * 当 execute() 抛出异常时调用。\n\t * 默认行为: 重新抛出原始错误。\n\t *\n\t * @param params - Intent 参数\n\t * @param error - execute() 抛出的错误\n\t * @returns 回退数据\n\t */\n\tfallback(params: TParams, error: Error): Promise<TResult> | TResult {\n\t\tthrow error;\n\t}\n\n\t/**\n\t * IntentController.perform() 实现\n\t *\n\t * 自动 try/catch → fallback 模式。\n\t */\n\tasync perform(\n\t\tintent: Intent<TResult>,\n\t\tcontainer: Container,\n\t): Promise<TResult> {\n\t\tconst params = (intent.params ?? {}) as TParams;\n\t\ttry {\n\t\t\treturn await this.execute(params, container);\n\t\t} catch (e) {\n\t\t\treturn this.fallback(\n\t\t\t\tparams,\n\t\t\t\te instanceof Error ? e : new Error(String(e)),\n\t\t\t);\n\t\t}\n\t}\n}\n","/**\n * Mapper 类型工具 — 标准化数据转换管线\n *\n * 提供类型约定和组合函数,让 API 响应 → 页面模型 的转换有统一的签名模式。\n */\n\n/** 同步映射函数 */\nexport type Mapper<TInput, TOutput> = (input: TInput) => TOutput;\n\n/** 异步映射函数 */\nexport type AsyncMapper<TInput, TOutput> = (\n\tinput: TInput,\n) => TOutput | Promise<TOutput>;\n\n/**\n * 组合两个同步 Mapper: A → B → C\n */\nexport function pipe<A, B, C>(m1: Mapper<A, B>, m2: Mapper<B, C>): Mapper<A, C>;\n/**\n * 组合三个同步 Mapper: A → B → C → D\n */\nexport function pipe<A, B, C, D>(\n\tm1: Mapper<A, B>,\n\tm2: Mapper<B, C>,\n\tm3: Mapper<C, D>,\n): Mapper<A, D>;\n/**\n * 组合四个同步 Mapper: A → B → C → D → E\n */\nexport function pipe<A, B, C, D, E>(\n\tm1: Mapper<A, B>,\n\tm2: Mapper<B, C>,\n\tm3: Mapper<C, D>,\n\tm4: Mapper<D, E>,\n): Mapper<A, E>;\n/**\n * 组合任意数量的同步 Mapper\n */\nexport function pipe(\n\t...mappers: Mapper<unknown, unknown>[]\n): Mapper<unknown, unknown>;\nexport function pipe(\n\t...mappers: Mapper<unknown, unknown>[]\n): Mapper<unknown, unknown> {\n\treturn (input: unknown) =>\n\t\tmappers.reduce((acc, mapper) => mapper(acc), input);\n}\n\n/**\n * 组合两个可能异步的 Mapper: A → B → C\n */\nexport function pipeAsync<A, B, C>(\n\tm1: AsyncMapper<A, B>,\n\tm2: AsyncMapper<B, C>,\n): AsyncMapper<A, C>;\n/**\n * 组合三个可能异步的 Mapper\n */\nexport function pipeAsync<A, B, C, D>(\n\tm1: AsyncMapper<A, B>,\n\tm2: AsyncMapper<B, C>,\n\tm3: AsyncMapper<C, D>,\n): AsyncMapper<A, D>;\nexport function pipeAsync(\n\t...mappers: AsyncMapper<unknown, unknown>[]\n): AsyncMapper<unknown, unknown> {\n\treturn async (input: unknown) => {\n\t\tlet acc = input;\n\t\tfor (const mapper of mappers) {\n\t\t\tacc = await mapper(acc);\n\t\t}\n\t\treturn acc;\n\t};\n}\n\n/**\n * 将一个 Mapper 应用到数组的每个元素\n */\nexport function mapEach<TInput, TOutput>(\n\tmapper: Mapper<TInput, TOutput>,\n): Mapper<TInput[], TOutput[]> {\n\treturn (items) => items.map(mapper);\n}\n","/**\n * defineRoutes — 声明式路由 + Controller 注册\n *\n * 将命令式的路由注册 (20+ 行 framework.router.add / framework.registerIntent)\n * 简化为声明式配置数组。\n */\n\nimport type { Framework } from \"../framework\";\nimport type { IntentController } from \"../intents/types\";\nimport type { AfterLoadGuard, BeforeLoadGuard } from \"../middleware/types\";\n\n/** 渲染模式 */\nexport type RenderMode = \"ssr\" | \"csr\" | \"prerender\";\n\n/** 单条路由定义 */\nexport interface RouteDefinition {\n\t/** URL pattern (如 \"/product/:productId\") */\n\tpath: string;\n\t/** Intent ID (如 \"product-page\") */\n\tintentId: string;\n\t/**\n\t * Controller 实例(可选)。\n\t * 同一个 intentId 的多条路由只需在第一条提供 controller。\n\t */\n\tcontroller?: IntentController;\n\t/**\n\t * 渲染模式(可选,默认 \"ssr\")。\n\t * - \"ssr\": 服务端渲染(默认)\n\t * - \"csr\": 客户端渲染(返回空壳 HTML,由客户端 JS 渲染)\n\t * - \"prerender\": 预渲染(构建时生成静态 HTML + ISR 缓存)\n\t */\n\trenderMode?: RenderMode;\n\t/**\n\t * 路由级 beforeLoad 守卫(可选)。\n\t * 在全局守卫之后执行,仅对匹配此路由的请求生效。\n\t */\n\tbeforeLoad?: BeforeLoadGuard[];\n\t/**\n\t * 路由级 afterLoad 守卫(可选)。\n\t * 在全局守卫之后执行,仅对匹配此路由的请求生效。\n\t */\n\tafterLoad?: AfterLoadGuard[];\n}\n\n/**\n * 声明式注册路由和 Controller\n *\n * - 自动去重: 同一 intentId 的 controller 只注册一次\n * - 路由和 controller 在同一个配置数组中,方便检查一致性\n *\n * @example\n * ```ts\n * defineRoutes(framework, [\n * { path: \"/\", intentId: \"home\", controller: new HomeController() },\n * { path: \"/product/:id\", intentId: \"product\", controller: new ProductController() },\n * { path: \"/search\", intentId: \"search\", controller: new SearchController() },\n * { path: \"/charts/:type\", intentId: \"charts\", controller: new ChartsController() },\n * { path: \"/charts\", intentId: \"charts\" }, // 同 intentId,不需要重复 controller\n * ]);\n * ```\n */\nexport function defineRoutes(\n\tframework: Framework,\n\tdefinitions: RouteDefinition[],\n): void {\n\tconst registeredIntents = new Set<string>();\n\n\tfor (const def of definitions) {\n\t\t// 注册 Controller(每个 intentId 只注册一次)\n\t\tif (def.controller && !registeredIntents.has(def.intentId)) {\n\t\t\tframework.registerIntent(def.controller);\n\t\t\tregisteredIntents.add(def.intentId);\n\t\t}\n\n\t\t// 注册路由(含路由级守卫)\n\t\tframework.router.add(def.path, def.intentId, {\n\t\t\trenderMode: def.renderMode,\n\t\t\tbeforeGuards: def.beforeLoad,\n\t\t\tafterGuards: def.afterLoad,\n\t\t});\n\t}\n}\n","/**\n * LruMap — 固定容量的 LRU 缓存\n */\n\nexport class LruMap<K, V> {\n\tprivate map = new Map<K, V>();\n\tprivate readonly capacity: number;\n\n\tconstructor(capacity: number) {\n\t\tthis.capacity = capacity;\n\t}\n\n\tget(key: K): V | undefined {\n\t\tconst value = this.map.get(key);\n\t\tif (value !== undefined) {\n\t\t\t// 移到末尾(最近使用)\n\t\t\tthis.map.delete(key);\n\t\t\tthis.map.set(key, value);\n\t\t}\n\t\treturn value;\n\t}\n\n\tset(key: K, value: V): void {\n\t\tif (this.map.has(key)) {\n\t\t\tthis.map.delete(key);\n\t\t} else if (this.map.size >= this.capacity) {\n\t\t\t// 删除最旧的(第一个)\n\t\t\tconst oldest = this.map.keys().next().value;\n\t\t\tif (oldest !== undefined) {\n\t\t\t\tthis.map.delete(oldest);\n\t\t\t}\n\t\t}\n\t\tthis.map.set(key, value);\n\t}\n\n\thas(key: K): boolean {\n\t\treturn this.map.has(key);\n\t}\n\n\tdelete(key: K): boolean {\n\t\treturn this.map.delete(key);\n\t}\n\n\tget size(): number {\n\t\treturn this.map.size;\n\t}\n\n\tclear(): void {\n\t\tthis.map.clear();\n\t}\n}\n","/**\n * Optional 类型工具\n */\n\nexport type None = null | undefined;\nexport type Optional<T> = T | None;\n\nexport function isSome<T>(value: Optional<T>): value is T {\n\treturn value !== null && value !== undefined;\n}\n\nexport function isNone<T>(value: Optional<T>): value is None {\n\treturn value === null || value === undefined;\n}\n","/**\n * URL 工具函数\n */\n\n/** 移除 URL scheme (https://, http://) */\nexport function removeScheme(url: string): string {\n\treturn url.replace(/^https?:\\/\\//, \"\");\n}\n\n/** 移除 URL host 部分,保留路径 */\nexport function removeHost(url: string): string {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\treturn parsed.pathname + parsed.search + parsed.hash;\n\t} catch {\n\t\treturn url;\n\t}\n}\n\n/** 移除 query 参数 */\nexport function removeQueryParams(url: string): string {\n\treturn url.split(\"?\")[0];\n}\n\n/** 获取 URL 的基础路径(无 query、hash) */\nexport function getBaseUrl(url: string): string {\n\treturn url.split(\"?\")[0].split(\"#\")[0];\n}\n\n/** 构建 URL(路径 + query 参数) */\nexport function buildUrl(\n\tpath: string,\n\tparams?: Record<string, string | undefined>,\n): string {\n\tif (!params) return path;\n\n\tconst searchParams = new URLSearchParams();\n\tfor (const [key, value] of Object.entries(params)) {\n\t\tif (value !== undefined) {\n\t\t\tsearchParams.set(key, value);\n\t\t}\n\t}\n\n\tconst qs = searchParams.toString();\n\treturn qs ? `${path}?${qs}` : path;\n}\n","/**\n * UUID v4 生成器\n */\n\nexport function generateUuid(): string {\n\tif (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n\t\treturn crypto.randomUUID();\n\t}\n\t// Fallback\n\treturn \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n\t\tconst r = (Math.random() * 16) | 0;\n\t\tconst v = c === \"x\" ? r : (r & 0x3) | 0x8;\n\t\treturn v.toString(16);\n\t});\n}\n","/**\n * Navigation Context — 构建器\n *\n * 为 SSR(服务端)和 Browser(客户端)两种环境创建统一的 NavigationContext。\n */\n\nimport type { Container } from \"../dependencies/container\";\nimport type { Intent } from \"../intents/types\";\nimport type { NavigationContext } from \"./types\";\n\n// =====================================================================\n// Cookie 解析\n// =====================================================================\n\nfunction parseCookieString(str: string): Map<string, string> {\n\tconst map = new Map<string, string>();\n\tif (!str) return map;\n\tfor (const pair of str.split(\";\")) {\n\t\tconst idx = pair.indexOf(\"=\");\n\t\tif (idx === -1) continue;\n\t\tconst key = pair.slice(0, idx).trim();\n\t\tconst val = pair.slice(idx + 1).trim();\n\t\tif (key) map.set(key, val);\n\t}\n\treturn map;\n}\n\n// =====================================================================\n// Server Context\n// =====================================================================\n\nexport interface ServerContextOptions {\n\turl: string;\n\tintent: Intent;\n\tcontainer: Container;\n\t/** 原始 Request 对象(提取 cookie 和 header) */\n\trequest?: Request;\n}\n\n/** 从 Request 对象构建服务端上下文 */\nexport function createServerContext(\n\toptions: ServerContextOptions,\n): NavigationContext {\n\tconst { url, intent, container, request } = options;\n\tconst parsed = new URL(url, \"http://localhost\");\n\n\tconst cookieHeader = request?.headers.get(\"cookie\") ?? \"\";\n\tconst cookies = parseCookieString(cookieHeader);\n\n\treturn {\n\t\turl,\n\t\tpath: parsed.pathname,\n\t\tparams: (intent.params as Record<string, string>) ?? {},\n\t\tintent,\n\t\tisServer: true,\n\t\tcontainer,\n\t\tgetCookie: (name: string) => cookies.get(name),\n\t\tgetHeader: (name: string) => request?.headers.get(name) ?? undefined,\n\t};\n}\n\n// =====================================================================\n// Browser Context\n// =====================================================================\n\nexport interface BrowserContextOptions {\n\turl: string;\n\tintent: Intent;\n\tcontainer: Container;\n}\n\n/** 从 document.cookie 构建浏览器端上下文 */\nexport function createBrowserContext(\n\toptions: BrowserContextOptions,\n): NavigationContext {\n\tconst { url, intent, container } = options;\n\tconst parsed = new URL(url, window.location.origin);\n\n\treturn {\n\t\turl,\n\t\tpath: parsed.pathname,\n\t\tparams: (intent.params as Record<string, string>) ?? {},\n\t\tintent,\n\t\tisServer: false,\n\t\tcontainer,\n\t\tgetCookie: (name: string) =>\n\t\t\tparseCookieString(document.cookie).get(name),\n\t\tgetHeader: () => undefined,\n\t};\n}\n","/**\n * Navigation Middleware — 类型定义\n *\n * 两阶段导航中间件管线:\n * 1. beforeLoad — 路由匹配后、数据加载前(认证守卫、权限检查、早期重定向)\n * 2. afterLoad — 数据加载后、渲染前(URL 规范化、基于数据的守卫)\n *\n * SSR 和 CSR 共享同一套中间件定义。\n */\n\nimport type { Container } from \"../dependencies/container\";\nimport type { Intent } from \"../intents/types\";\nimport type { BasePage } from \"../models/page\";\n\n// =====================================================================\n// Navigation Context — 中间件上下文\n// =====================================================================\n\n/** 导航上下文(beforeLoad 阶段可用) */\nexport interface NavigationContext {\n\t/** 完整 URL(path + query) */\n\treadonly url: string;\n\t/** 仅路径部分 */\n\treadonly path: string;\n\t/** 路由参数 + 查询参数 */\n\treadonly params: Record<string, string>;\n\t/** 匹配的 Intent */\n\treadonly intent: Intent;\n\t/** 是否在服务端运行 */\n\treadonly isServer: boolean;\n\t/** DI 容器(可获取自定义服务) */\n\treadonly container: Container;\n\t/** 获取 Cookie 值(两端均可用) */\n\tgetCookie(name: string): string | undefined;\n\t/** 获取请求头值(仅服务端有值,客户端始终返回 undefined) */\n\tgetHeader(name: string): string | undefined;\n}\n\n/** 后置上下文(afterLoad 阶段,包含页面数据) */\nexport interface PostLoadContext extends NavigationContext {\n\t/** 控制器返回的页面数据 */\n\treadonly page: BasePage;\n}\n\n// =====================================================================\n// Middleware Result — 中间件返回值\n// =====================================================================\n\n/** 继续执行下一个中间件 */\nexport interface NextResult {\n\treadonly kind: \"next\";\n}\n\n/** 重定向(服务端: HTTP 301/302,客户端: 触发新导航) */\nexport interface RedirectResult {\n\treadonly kind: \"redirect\";\n\treadonly url: string;\n\treadonly status: number;\n}\n\n/** URL 重写(服务端: HTTP 301,客户端: replaceState 仅更新地址栏) */\nexport interface RewriteResult {\n\treadonly kind: \"rewrite\";\n\treadonly url: string;\n}\n\n/** 拒绝访问 */\nexport interface DenyResult {\n\treadonly kind: \"deny\";\n\treadonly status: number;\n\treadonly message: string;\n}\n\nexport type MiddlewareResult =\n\t| NextResult\n\t| RedirectResult\n\t| RewriteResult\n\t| DenyResult;\n\n// =====================================================================\n// 便捷工厂函数\n// =====================================================================\n\n/** 继续执行 */\nexport function next(): NextResult {\n\treturn { kind: \"next\" };\n}\n\n/** 重定向到新 URL */\nexport function redirect(url: string, status: 301 | 302 = 302): RedirectResult {\n\treturn { kind: \"redirect\", url, status };\n}\n\n/** URL 重写(不重新加载数据) */\nexport function rewrite(url: string): RewriteResult {\n\treturn { kind: \"rewrite\", url };\n}\n\n/** 拒绝访问 */\nexport function deny(status = 403, message = \"Forbidden\"): DenyResult {\n\treturn { kind: \"deny\", status, message };\n}\n\n// =====================================================================\n// Guard 函数类型\n// =====================================================================\n\n/** beforeLoad 守卫:路由匹配后、数据加载前 */\nexport type BeforeLoadGuard = (\n\tctx: NavigationContext,\n) => MiddlewareResult | Promise<MiddlewareResult>;\n\n/** afterLoad 守卫:数据加载后、渲染前 */\nexport type AfterLoadGuard = (\n\tctx: PostLoadContext,\n) => MiddlewareResult | Promise<MiddlewareResult>;\n"],"mappings":"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","names":[]}
@@ -1,6 +1,7 @@
1
1
  import {
2
- Framework
3
- } from "./chunk-OXKFPW4U.js";
2
+ Framework,
3
+ createServerContext
4
+ } from "./chunk-SDPWQT2T.js";
4
5
 
5
6
  // ../ssr/src/render.ts
6
7
  async function ssrRender(options) {
@@ -31,6 +32,25 @@ async function ssrRender(options) {
31
32
  let page;
32
33
  let serverData = [];
33
34
  if (match) {
35
+ const navCtx = createServerContext({
36
+ url: fullPath,
37
+ intent: match.intent,
38
+ container: framework.container,
39
+ request: ssrContext?.request
40
+ });
41
+ const beforeResult = await framework.runBeforeLoad(
42
+ navCtx,
43
+ match.beforeGuards
44
+ );
45
+ if (beforeResult.kind !== "next") {
46
+ const earlyReturn = handleMiddlewareResult(
47
+ beforeResult,
48
+ getErrorPage,
49
+ renderApp,
50
+ framework
51
+ );
52
+ if (earlyReturn) return earlyReturn;
53
+ }
34
54
  try {
35
55
  page = await framework.dispatch(match.intent);
36
56
  serverData = [{ intent: match.intent, data: page }];
@@ -41,6 +61,23 @@ async function ssrRender(options) {
41
61
  );
42
62
  page = getErrorPage(500, "Internal error");
43
63
  }
64
+ const postCtx = {
65
+ ...navCtx,
66
+ page
67
+ };
68
+ const afterResult = await framework.runAfterLoad(
69
+ postCtx,
70
+ match.afterGuards
71
+ );
72
+ if (afterResult.kind !== "next") {
73
+ const lateReturn = handleMiddlewareResult(
74
+ afterResult,
75
+ getErrorPage,
76
+ renderApp,
77
+ framework
78
+ );
79
+ if (lateReturn) return lateReturn;
80
+ }
44
81
  } else {
45
82
  page = getErrorPage(404, "Page not found");
46
83
  }
@@ -56,6 +93,38 @@ async function ssrRender(options) {
56
93
  framework.dispose();
57
94
  }
58
95
  }
96
+ function handleMiddlewareResult(result, getErrorPage, renderApp, framework) {
97
+ switch (result.kind) {
98
+ case "next":
99
+ return null;
100
+ case "redirect":
101
+ return {
102
+ html: "",
103
+ head: "",
104
+ css: "",
105
+ serverData: [],
106
+ redirect: { url: result.url, status: result.status }
107
+ };
108
+ case "rewrite":
109
+ return {
110
+ html: "",
111
+ head: "",
112
+ css: "",
113
+ serverData: [],
114
+ redirect: { url: result.url, status: 301 }
115
+ };
116
+ case "deny": {
117
+ const errorPage = getErrorPage(result.status, result.message);
118
+ const rendered = renderApp(errorPage, framework);
119
+ return {
120
+ html: rendered.html,
121
+ head: rendered.head,
122
+ css: rendered.css,
123
+ serverData: []
124
+ };
125
+ }
126
+ }
127
+ }
59
128
 
60
129
  // ../ssr/src/create-render.ts
61
130
  function createSSRRender(config) {
@@ -115,4 +184,4 @@ export {
115
184
  injectCSRShell,
116
185
  serializeServerData
117
186
  };
118
- //# sourceMappingURL=chunk-AYO3UUQC.js.map
187
+ //# sourceMappingURL=chunk-XQ3UWZOS.js.map