@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.
@@ -173,6 +173,88 @@ interface MakeDependenciesOptions {
173
173
  }
174
174
  declare function makeDependencies(container: Container, options?: MakeDependenciesOptions): void;
175
175
 
176
+ /**
177
+ * BasePage — 所有页面共享的基础属性
178
+ *
179
+ * 具体页面类型由应用层定义并扩展此接口。
180
+ */
181
+ interface BasePage {
182
+ id: string;
183
+ pageType: string;
184
+ title: string;
185
+ description?: string;
186
+ url?: string;
187
+ }
188
+
189
+ /**
190
+ * Navigation Middleware — 类型定义
191
+ *
192
+ * 两阶段导航中间件管线:
193
+ * 1. beforeLoad — 路由匹配后、数据加载前(认证守卫、权限检查、早期重定向)
194
+ * 2. afterLoad — 数据加载后、渲染前(URL 规范化、基于数据的守卫)
195
+ *
196
+ * SSR 和 CSR 共享同一套中间件定义。
197
+ */
198
+
199
+ /** 导航上下文(beforeLoad 阶段可用) */
200
+ interface NavigationContext {
201
+ /** 完整 URL(path + query) */
202
+ readonly url: string;
203
+ /** 仅路径部分 */
204
+ readonly path: string;
205
+ /** 路由参数 + 查询参数 */
206
+ readonly params: Record<string, string>;
207
+ /** 匹配的 Intent */
208
+ readonly intent: Intent;
209
+ /** 是否在服务端运行 */
210
+ readonly isServer: boolean;
211
+ /** DI 容器(可获取自定义服务) */
212
+ readonly container: Container;
213
+ /** 获取 Cookie 值(两端均可用) */
214
+ getCookie(name: string): string | undefined;
215
+ /** 获取请求头值(仅服务端有值,客户端始终返回 undefined) */
216
+ getHeader(name: string): string | undefined;
217
+ }
218
+ /** 后置上下文(afterLoad 阶段,包含页面数据) */
219
+ interface PostLoadContext extends NavigationContext {
220
+ /** 控制器返回的页面数据 */
221
+ readonly page: BasePage;
222
+ }
223
+ /** 继续执行下一个中间件 */
224
+ interface NextResult {
225
+ readonly kind: "next";
226
+ }
227
+ /** 重定向(服务端: HTTP 301/302,客户端: 触发新导航) */
228
+ interface RedirectResult {
229
+ readonly kind: "redirect";
230
+ readonly url: string;
231
+ readonly status: number;
232
+ }
233
+ /** URL 重写(服务端: HTTP 301,客户端: replaceState 仅更新地址栏) */
234
+ interface RewriteResult {
235
+ readonly kind: "rewrite";
236
+ readonly url: string;
237
+ }
238
+ /** 拒绝访问 */
239
+ interface DenyResult {
240
+ readonly kind: "deny";
241
+ readonly status: number;
242
+ readonly message: string;
243
+ }
244
+ type MiddlewareResult = NextResult | RedirectResult | RewriteResult | DenyResult;
245
+ /** 继续执行 */
246
+ declare function next(): NextResult;
247
+ /** 重定向到新 URL */
248
+ declare function redirect(url: string, status?: 301 | 302): RedirectResult;
249
+ /** URL 重写(不重新加载数据) */
250
+ declare function rewrite(url: string): RewriteResult;
251
+ /** 拒绝访问 */
252
+ declare function deny(status?: number, message?: string): DenyResult;
253
+ /** beforeLoad 守卫:路由匹配后、数据加载前 */
254
+ type BeforeLoadGuard = (ctx: NavigationContext) => MiddlewareResult | Promise<MiddlewareResult>;
255
+ /** afterLoad 守卫:数据加载后、渲染前 */
256
+ type AfterLoadGuard = (ctx: PostLoadContext) => MiddlewareResult | Promise<MiddlewareResult>;
257
+
176
258
  /**
177
259
  * URL 路由器 — URL pattern → Intent + FlowAction
178
260
  */
@@ -182,11 +264,21 @@ interface RouteMatch {
182
264
  intent: Intent;
183
265
  action: FlowAction;
184
266
  renderMode?: string;
267
+ /** 该路由绑定的 beforeLoad 守卫 */
268
+ beforeGuards?: BeforeLoadGuard[];
269
+ /** 该路由绑定的 afterLoad 守卫 */
270
+ afterGuards?: AfterLoadGuard[];
271
+ }
272
+ /** 路由添加选项 */
273
+ interface RouteAddOptions {
274
+ renderMode?: string;
275
+ beforeGuards?: BeforeLoadGuard[];
276
+ afterGuards?: AfterLoadGuard[];
185
277
  }
186
278
  declare class Router {
187
279
  private routes;
188
280
  /** 添加路由规则 */
189
- add(pattern: string, intentId: string, renderMode?: string): this;
281
+ add(pattern: string, intentId: string, renderModeOrOptions?: string | RouteAddOptions): this;
190
282
  /** 解析 URL → RouteMatch */
191
283
  resolve(urlOrPath: string): RouteMatch | null;
192
284
  /** 获取所有已注册的路由 */
@@ -253,19 +345,6 @@ declare class ConsoleLoggerFactory implements LoggerFactory {
253
345
  declare function shouldLog(name: string, level: Level): boolean;
254
346
  declare function resetFilterCache(): void;
255
347
 
256
- /**
257
- * BasePage — 所有页面共享的基础属性
258
- *
259
- * 具体页面类型由应用层定义并扩展此接口。
260
- */
261
- interface BasePage {
262
- id: string;
263
- pageType: string;
264
- title: string;
265
- description?: string;
266
- url?: string;
267
- }
268
-
269
348
  /**
270
349
  * PrefetchedIntents — SSR 数据缓存
271
350
  *
@@ -315,6 +394,8 @@ declare class Framework {
315
394
  readonly actionDispatcher: ActionDispatcher;
316
395
  readonly router: Router;
317
396
  readonly prefetchedIntents: PrefetchedIntents;
397
+ private readonly beforeGuards;
398
+ private readonly afterGuards;
318
399
  private constructor();
319
400
  /** 创建并初始化 Framework 实例 */
320
401
  static create(config?: FrameworkConfig): Framework;
@@ -330,6 +411,14 @@ declare class Framework {
330
411
  onAction<A extends Action>(kind: string, handler: ActionHandler<A>): void;
331
412
  /** 注册 Intent Controller */
332
413
  registerIntent(controller: IntentController): void;
414
+ /** 注册 beforeLoad 守卫(路由匹配后、数据加载前) */
415
+ beforeLoad(guard: BeforeLoadGuard): void;
416
+ /** 注册 afterLoad 守卫(数据加载后、渲染前) */
417
+ afterLoad(guard: AfterLoadGuard): void;
418
+ /** 执行所有 beforeLoad 守卫(全局 → 路由级) */
419
+ runBeforeLoad(ctx: NavigationContext, routeGuards?: BeforeLoadGuard[]): Promise<MiddlewareResult>;
420
+ /** 执行所有 afterLoad 守卫(全局 → 路由级) */
421
+ runAfterLoad(ctx: PostLoadContext, routeGuards?: AfterLoadGuard[]): Promise<MiddlewareResult>;
333
422
  /** 销毁 Framework 实例 */
334
423
  dispose(): void;
335
424
  }
@@ -555,6 +644,16 @@ interface RouteDefinition {
555
644
  * - "prerender": 预渲染(构建时生成静态 HTML + ISR 缓存)
556
645
  */
557
646
  renderMode?: RenderMode;
647
+ /**
648
+ * 路由级 beforeLoad 守卫(可选)。
649
+ * 在全局守卫之后执行,仅对匹配此路由的请求生效。
650
+ */
651
+ beforeLoad?: BeforeLoadGuard[];
652
+ /**
653
+ * 路由级 afterLoad 守卫(可选)。
654
+ * 在全局守卫之后执行,仅对匹配此路由的请求生效。
655
+ */
656
+ afterLoad?: AfterLoadGuard[];
558
657
  }
559
658
  /**
560
659
  * 声明式注册路由和 Controller
@@ -617,6 +716,40 @@ declare function buildUrl(path: string, params?: Record<string, string | undefin
617
716
  */
618
717
  declare function generateUuid(): string;
619
718
 
719
+ /**
720
+ * Navigation Context — 构建器
721
+ *
722
+ * 为 SSR(服务端)和 Browser(客户端)两种环境创建统一的 NavigationContext。
723
+ */
724
+
725
+ interface ServerContextOptions {
726
+ url: string;
727
+ intent: Intent;
728
+ container: Container;
729
+ /** 原始 Request 对象(提取 cookie 和 header) */
730
+ request?: Request;
731
+ }
732
+ /** 从 Request 对象构建服务端上下文 */
733
+ declare function createServerContext(options: ServerContextOptions): NavigationContext;
734
+ interface BrowserContextOptions {
735
+ url: string;
736
+ intent: Intent;
737
+ container: Container;
738
+ }
739
+ /** 从 document.cookie 构建浏览器端上下文 */
740
+ declare function createBrowserContext(options: BrowserContextOptions): NavigationContext;
741
+
742
+ /**
743
+ * Guard Pipeline — 顺序执行守卫链
744
+ *
745
+ * 按注册顺序执行守卫,遇到第一个非 `next` 结果立即短路返回。
746
+ */
747
+
748
+ /** 执行 beforeLoad 守卫链 */
749
+ declare function runBeforeLoadGuards(guards: BeforeLoadGuard[], ctx: NavigationContext): Promise<MiddlewareResult>;
750
+ /** 执行 afterLoad 守卫链 */
751
+ declare function runAfterLoadGuards(guards: AfterLoadGuard[], ctx: PostLoadContext): Promise<MiddlewareResult>;
752
+
620
753
  /**
621
754
  * ExternalUrl Action Handler — 外部链接处理器
622
755
  */
@@ -768,4 +901,4 @@ declare function deserializeServerData(): PrefetchedIntent[] | undefined;
768
901
  */
769
902
  declare function createPrefetchedIntentsFromDom(): PrefetchedIntents;
770
903
 
771
- export { ACTION_KINDS, type Action, ActionDispatcher, type ActionHandler, type ActionHandlerDependencies, type AsyncMapper, BaseController, type BaseItem, BaseLogger, type BasePage, type BaseShelf, type BrowserAppConfig, CompositeLogger, CompositeLoggerFactory, type CompoundAction, ConsoleLogger, ConsoleLoggerFactory, Container, DEP_KEYS, type ExternalUrlAction, type ExternalUrlDependencies, type FeatureFlags, type FlowAction, type FlowActionCallbacks, type FlowActionDependencies, Framework, type FrameworkConfig, History, HttpClient, type HttpClientConfig, HttpError, type Intent, type IntentController, IntentDispatcher, type Locale, type Logger, type LoggerFactory, type Logger as LoggerInterface, LruMap, type Mapper, type MetricsRecorder, type Net, type None, type Optional, type PrefetchedIntent, PrefetchedIntents, type RenderMode, type RouteDefinition, type RouteMatch, Router, type Storage, buildUrl, createPrefetchedIntentsFromDom, defineRoutes, deserializeServerData, generateUuid, getBaseUrl, isCompoundAction, isExternalUrlAction, isFlowAction, isNone, isSome, makeDependencies, makeExternalUrlAction, makeFlowAction, mapEach, pipe, pipeAsync, registerActionHandlers, registerExternalUrlHandler, registerFlowActionHandler, removeHost, removeQueryParams, removeScheme, resetFilterCache, shouldLog, stableStringify, startBrowserApp, tryScroll };
904
+ export { ACTION_KINDS, type Action, ActionDispatcher, type ActionHandler, type ActionHandlerDependencies, type AfterLoadGuard, type AsyncMapper, BaseController, type BaseItem, BaseLogger, type BasePage, type BaseShelf, type BeforeLoadGuard, type BrowserAppConfig, type BrowserContextOptions, CompositeLogger, CompositeLoggerFactory, type CompoundAction, ConsoleLogger, ConsoleLoggerFactory, Container, DEP_KEYS, type DenyResult, type ExternalUrlAction, type ExternalUrlDependencies, type FeatureFlags, type FlowAction, type FlowActionCallbacks, type FlowActionDependencies, Framework, type FrameworkConfig, History, HttpClient, type HttpClientConfig, HttpError, type Intent, type IntentController, IntentDispatcher, type Locale, type Logger, type LoggerFactory, type Logger as LoggerInterface, LruMap, type Mapper, type MetricsRecorder, type MiddlewareResult, type NavigationContext, type Net, type NextResult, type None, type Optional, type PostLoadContext, type PrefetchedIntent, PrefetchedIntents, type RedirectResult, type RenderMode, type RewriteResult, type RouteAddOptions, type RouteDefinition, type RouteMatch, Router, type ServerContextOptions, type Storage, buildUrl, createBrowserContext, createPrefetchedIntentsFromDom, createServerContext, defineRoutes, deny, deserializeServerData, generateUuid, getBaseUrl, isCompoundAction, isExternalUrlAction, isFlowAction, isNone, isSome, makeDependencies, makeExternalUrlAction, makeFlowAction, mapEach, next, pipe, pipeAsync, redirect, registerActionHandlers, registerExternalUrlHandler, registerFlowActionHandler, removeHost, removeQueryParams, removeScheme, resetFilterCache, rewrite, runAfterLoadGuards, runBeforeLoadGuards, shouldLog, stableStringify, startBrowserApp, tryScroll };
package/dist/browser.d.ts CHANGED
@@ -173,6 +173,88 @@ interface MakeDependenciesOptions {
173
173
  }
174
174
  declare function makeDependencies(container: Container, options?: MakeDependenciesOptions): void;
175
175
 
176
+ /**
177
+ * BasePage — 所有页面共享的基础属性
178
+ *
179
+ * 具体页面类型由应用层定义并扩展此接口。
180
+ */
181
+ interface BasePage {
182
+ id: string;
183
+ pageType: string;
184
+ title: string;
185
+ description?: string;
186
+ url?: string;
187
+ }
188
+
189
+ /**
190
+ * Navigation Middleware — 类型定义
191
+ *
192
+ * 两阶段导航中间件管线:
193
+ * 1. beforeLoad — 路由匹配后、数据加载前(认证守卫、权限检查、早期重定向)
194
+ * 2. afterLoad — 数据加载后、渲染前(URL 规范化、基于数据的守卫)
195
+ *
196
+ * SSR 和 CSR 共享同一套中间件定义。
197
+ */
198
+
199
+ /** 导航上下文(beforeLoad 阶段可用) */
200
+ interface NavigationContext {
201
+ /** 完整 URL(path + query) */
202
+ readonly url: string;
203
+ /** 仅路径部分 */
204
+ readonly path: string;
205
+ /** 路由参数 + 查询参数 */
206
+ readonly params: Record<string, string>;
207
+ /** 匹配的 Intent */
208
+ readonly intent: Intent;
209
+ /** 是否在服务端运行 */
210
+ readonly isServer: boolean;
211
+ /** DI 容器(可获取自定义服务) */
212
+ readonly container: Container;
213
+ /** 获取 Cookie 值(两端均可用) */
214
+ getCookie(name: string): string | undefined;
215
+ /** 获取请求头值(仅服务端有值,客户端始终返回 undefined) */
216
+ getHeader(name: string): string | undefined;
217
+ }
218
+ /** 后置上下文(afterLoad 阶段,包含页面数据) */
219
+ interface PostLoadContext extends NavigationContext {
220
+ /** 控制器返回的页面数据 */
221
+ readonly page: BasePage;
222
+ }
223
+ /** 继续执行下一个中间件 */
224
+ interface NextResult {
225
+ readonly kind: "next";
226
+ }
227
+ /** 重定向(服务端: HTTP 301/302,客户端: 触发新导航) */
228
+ interface RedirectResult {
229
+ readonly kind: "redirect";
230
+ readonly url: string;
231
+ readonly status: number;
232
+ }
233
+ /** URL 重写(服务端: HTTP 301,客户端: replaceState 仅更新地址栏) */
234
+ interface RewriteResult {
235
+ readonly kind: "rewrite";
236
+ readonly url: string;
237
+ }
238
+ /** 拒绝访问 */
239
+ interface DenyResult {
240
+ readonly kind: "deny";
241
+ readonly status: number;
242
+ readonly message: string;
243
+ }
244
+ type MiddlewareResult = NextResult | RedirectResult | RewriteResult | DenyResult;
245
+ /** 继续执行 */
246
+ declare function next(): NextResult;
247
+ /** 重定向到新 URL */
248
+ declare function redirect(url: string, status?: 301 | 302): RedirectResult;
249
+ /** URL 重写(不重新加载数据) */
250
+ declare function rewrite(url: string): RewriteResult;
251
+ /** 拒绝访问 */
252
+ declare function deny(status?: number, message?: string): DenyResult;
253
+ /** beforeLoad 守卫:路由匹配后、数据加载前 */
254
+ type BeforeLoadGuard = (ctx: NavigationContext) => MiddlewareResult | Promise<MiddlewareResult>;
255
+ /** afterLoad 守卫:数据加载后、渲染前 */
256
+ type AfterLoadGuard = (ctx: PostLoadContext) => MiddlewareResult | Promise<MiddlewareResult>;
257
+
176
258
  /**
177
259
  * URL 路由器 — URL pattern → Intent + FlowAction
178
260
  */
@@ -182,11 +264,21 @@ interface RouteMatch {
182
264
  intent: Intent;
183
265
  action: FlowAction;
184
266
  renderMode?: string;
267
+ /** 该路由绑定的 beforeLoad 守卫 */
268
+ beforeGuards?: BeforeLoadGuard[];
269
+ /** 该路由绑定的 afterLoad 守卫 */
270
+ afterGuards?: AfterLoadGuard[];
271
+ }
272
+ /** 路由添加选项 */
273
+ interface RouteAddOptions {
274
+ renderMode?: string;
275
+ beforeGuards?: BeforeLoadGuard[];
276
+ afterGuards?: AfterLoadGuard[];
185
277
  }
186
278
  declare class Router {
187
279
  private routes;
188
280
  /** 添加路由规则 */
189
- add(pattern: string, intentId: string, renderMode?: string): this;
281
+ add(pattern: string, intentId: string, renderModeOrOptions?: string | RouteAddOptions): this;
190
282
  /** 解析 URL → RouteMatch */
191
283
  resolve(urlOrPath: string): RouteMatch | null;
192
284
  /** 获取所有已注册的路由 */
@@ -253,19 +345,6 @@ declare class ConsoleLoggerFactory implements LoggerFactory {
253
345
  declare function shouldLog(name: string, level: Level): boolean;
254
346
  declare function resetFilterCache(): void;
255
347
 
256
- /**
257
- * BasePage — 所有页面共享的基础属性
258
- *
259
- * 具体页面类型由应用层定义并扩展此接口。
260
- */
261
- interface BasePage {
262
- id: string;
263
- pageType: string;
264
- title: string;
265
- description?: string;
266
- url?: string;
267
- }
268
-
269
348
  /**
270
349
  * PrefetchedIntents — SSR 数据缓存
271
350
  *
@@ -315,6 +394,8 @@ declare class Framework {
315
394
  readonly actionDispatcher: ActionDispatcher;
316
395
  readonly router: Router;
317
396
  readonly prefetchedIntents: PrefetchedIntents;
397
+ private readonly beforeGuards;
398
+ private readonly afterGuards;
318
399
  private constructor();
319
400
  /** 创建并初始化 Framework 实例 */
320
401
  static create(config?: FrameworkConfig): Framework;
@@ -330,6 +411,14 @@ declare class Framework {
330
411
  onAction<A extends Action>(kind: string, handler: ActionHandler<A>): void;
331
412
  /** 注册 Intent Controller */
332
413
  registerIntent(controller: IntentController): void;
414
+ /** 注册 beforeLoad 守卫(路由匹配后、数据加载前) */
415
+ beforeLoad(guard: BeforeLoadGuard): void;
416
+ /** 注册 afterLoad 守卫(数据加载后、渲染前) */
417
+ afterLoad(guard: AfterLoadGuard): void;
418
+ /** 执行所有 beforeLoad 守卫(全局 → 路由级) */
419
+ runBeforeLoad(ctx: NavigationContext, routeGuards?: BeforeLoadGuard[]): Promise<MiddlewareResult>;
420
+ /** 执行所有 afterLoad 守卫(全局 → 路由级) */
421
+ runAfterLoad(ctx: PostLoadContext, routeGuards?: AfterLoadGuard[]): Promise<MiddlewareResult>;
333
422
  /** 销毁 Framework 实例 */
334
423
  dispose(): void;
335
424
  }
@@ -555,6 +644,16 @@ interface RouteDefinition {
555
644
  * - "prerender": 预渲染(构建时生成静态 HTML + ISR 缓存)
556
645
  */
557
646
  renderMode?: RenderMode;
647
+ /**
648
+ * 路由级 beforeLoad 守卫(可选)。
649
+ * 在全局守卫之后执行,仅对匹配此路由的请求生效。
650
+ */
651
+ beforeLoad?: BeforeLoadGuard[];
652
+ /**
653
+ * 路由级 afterLoad 守卫(可选)。
654
+ * 在全局守卫之后执行,仅对匹配此路由的请求生效。
655
+ */
656
+ afterLoad?: AfterLoadGuard[];
558
657
  }
559
658
  /**
560
659
  * 声明式注册路由和 Controller
@@ -617,6 +716,40 @@ declare function buildUrl(path: string, params?: Record<string, string | undefin
617
716
  */
618
717
  declare function generateUuid(): string;
619
718
 
719
+ /**
720
+ * Navigation Context — 构建器
721
+ *
722
+ * 为 SSR(服务端)和 Browser(客户端)两种环境创建统一的 NavigationContext。
723
+ */
724
+
725
+ interface ServerContextOptions {
726
+ url: string;
727
+ intent: Intent;
728
+ container: Container;
729
+ /** 原始 Request 对象(提取 cookie 和 header) */
730
+ request?: Request;
731
+ }
732
+ /** 从 Request 对象构建服务端上下文 */
733
+ declare function createServerContext(options: ServerContextOptions): NavigationContext;
734
+ interface BrowserContextOptions {
735
+ url: string;
736
+ intent: Intent;
737
+ container: Container;
738
+ }
739
+ /** 从 document.cookie 构建浏览器端上下文 */
740
+ declare function createBrowserContext(options: BrowserContextOptions): NavigationContext;
741
+
742
+ /**
743
+ * Guard Pipeline — 顺序执行守卫链
744
+ *
745
+ * 按注册顺序执行守卫,遇到第一个非 `next` 结果立即短路返回。
746
+ */
747
+
748
+ /** 执行 beforeLoad 守卫链 */
749
+ declare function runBeforeLoadGuards(guards: BeforeLoadGuard[], ctx: NavigationContext): Promise<MiddlewareResult>;
750
+ /** 执行 afterLoad 守卫链 */
751
+ declare function runAfterLoadGuards(guards: AfterLoadGuard[], ctx: PostLoadContext): Promise<MiddlewareResult>;
752
+
620
753
  /**
621
754
  * ExternalUrl Action Handler — 外部链接处理器
622
755
  */
@@ -768,4 +901,4 @@ declare function deserializeServerData(): PrefetchedIntent[] | undefined;
768
901
  */
769
902
  declare function createPrefetchedIntentsFromDom(): PrefetchedIntents;
770
903
 
771
- export { ACTION_KINDS, type Action, ActionDispatcher, type ActionHandler, type ActionHandlerDependencies, type AsyncMapper, BaseController, type BaseItem, BaseLogger, type BasePage, type BaseShelf, type BrowserAppConfig, CompositeLogger, CompositeLoggerFactory, type CompoundAction, ConsoleLogger, ConsoleLoggerFactory, Container, DEP_KEYS, type ExternalUrlAction, type ExternalUrlDependencies, type FeatureFlags, type FlowAction, type FlowActionCallbacks, type FlowActionDependencies, Framework, type FrameworkConfig, History, HttpClient, type HttpClientConfig, HttpError, type Intent, type IntentController, IntentDispatcher, type Locale, type Logger, type LoggerFactory, type Logger as LoggerInterface, LruMap, type Mapper, type MetricsRecorder, type Net, type None, type Optional, type PrefetchedIntent, PrefetchedIntents, type RenderMode, type RouteDefinition, type RouteMatch, Router, type Storage, buildUrl, createPrefetchedIntentsFromDom, defineRoutes, deserializeServerData, generateUuid, getBaseUrl, isCompoundAction, isExternalUrlAction, isFlowAction, isNone, isSome, makeDependencies, makeExternalUrlAction, makeFlowAction, mapEach, pipe, pipeAsync, registerActionHandlers, registerExternalUrlHandler, registerFlowActionHandler, removeHost, removeQueryParams, removeScheme, resetFilterCache, shouldLog, stableStringify, startBrowserApp, tryScroll };
904
+ export { ACTION_KINDS, type Action, ActionDispatcher, type ActionHandler, type ActionHandlerDependencies, type AfterLoadGuard, type AsyncMapper, BaseController, type BaseItem, BaseLogger, type BasePage, type BaseShelf, type BeforeLoadGuard, type BrowserAppConfig, type BrowserContextOptions, CompositeLogger, CompositeLoggerFactory, type CompoundAction, ConsoleLogger, ConsoleLoggerFactory, Container, DEP_KEYS, type DenyResult, type ExternalUrlAction, type ExternalUrlDependencies, type FeatureFlags, type FlowAction, type FlowActionCallbacks, type FlowActionDependencies, Framework, type FrameworkConfig, History, HttpClient, type HttpClientConfig, HttpError, type Intent, type IntentController, IntentDispatcher, type Locale, type Logger, type LoggerFactory, type Logger as LoggerInterface, LruMap, type Mapper, type MetricsRecorder, type MiddlewareResult, type NavigationContext, type Net, type NextResult, type None, type Optional, type PostLoadContext, type PrefetchedIntent, PrefetchedIntents, type RedirectResult, type RenderMode, type RewriteResult, type RouteAddOptions, type RouteDefinition, type RouteMatch, Router, type ServerContextOptions, type Storage, buildUrl, createBrowserContext, createPrefetchedIntentsFromDom, createServerContext, defineRoutes, deny, deserializeServerData, generateUuid, getBaseUrl, isCompoundAction, isExternalUrlAction, isFlowAction, isNone, isSome, makeDependencies, makeExternalUrlAction, makeFlowAction, mapEach, next, pipe, pipeAsync, redirect, registerActionHandlers, registerExternalUrlHandler, registerFlowActionHandler, removeHost, removeQueryParams, removeScheme, resetFilterCache, rewrite, runAfterLoadGuards, runBeforeLoadGuards, shouldLog, stableStringify, startBrowserApp, tryScroll };
package/dist/browser.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  registerFlowActionHandler,
8
8
  startBrowserApp,
9
9
  tryScroll
10
- } from "./chunk-PHDR7PIL.js";
10
+ } from "./chunk-4PPCVAKZ.js";
11
11
  import {
12
12
  ACTION_KINDS,
13
13
  ActionDispatcher,
@@ -27,7 +27,10 @@ import {
27
27
  PrefetchedIntents,
28
28
  Router,
29
29
  buildUrl,
30
+ createBrowserContext,
31
+ createServerContext,
30
32
  defineRoutes,
33
+ deny,
31
34
  generateUuid,
32
35
  getBaseUrl,
33
36
  isCompoundAction,
@@ -39,15 +42,20 @@ import {
39
42
  makeExternalUrlAction,
40
43
  makeFlowAction,
41
44
  mapEach,
45
+ next,
42
46
  pipe,
43
47
  pipeAsync,
48
+ redirect,
44
49
  removeHost,
45
50
  removeQueryParams,
46
51
  removeScheme,
47
52
  resetFilterCache,
53
+ rewrite,
54
+ runAfterLoadGuards,
55
+ runBeforeLoadGuards,
48
56
  shouldLog,
49
57
  stableStringify
50
- } from "./chunk-OXKFPW4U.js";
58
+ } from "./chunk-SDPWQT2T.js";
51
59
  export {
52
60
  ACTION_KINDS,
53
61
  ActionDispatcher,
@@ -68,8 +76,11 @@ export {
68
76
  PrefetchedIntents,
69
77
  Router,
70
78
  buildUrl,
79
+ createBrowserContext,
71
80
  createPrefetchedIntentsFromDom,
81
+ createServerContext,
72
82
  defineRoutes,
83
+ deny,
73
84
  deserializeServerData,
74
85
  generateUuid,
75
86
  getBaseUrl,
@@ -82,8 +93,10 @@ export {
82
93
  makeExternalUrlAction,
83
94
  makeFlowAction,
84
95
  mapEach,
96
+ next,
85
97
  pipe,
86
98
  pipeAsync,
99
+ redirect,
87
100
  registerActionHandlers,
88
101
  registerExternalUrlHandler,
89
102
  registerFlowActionHandler,
@@ -91,6 +104,9 @@ export {
91
104
  removeQueryParams,
92
105
  removeScheme,
93
106
  resetFilterCache,
107
+ rewrite,
108
+ runAfterLoadGuards,
109
+ runBeforeLoadGuards,
94
110
  shouldLog,
95
111
  stableStringify,
96
112
  startBrowserApp,
@@ -4,8 +4,9 @@ import {
4
4
  Framework,
5
5
  LruMap,
6
6
  PrefetchedIntents,
7
+ createBrowserContext,
7
8
  generateUuid
8
- } from "./chunk-OXKFPW4U.js";
9
+ } from "./chunk-SDPWQT2T.js";
9
10
 
10
11
  // ../browser/src/action-handlers/external-url-action.ts
11
12
  function registerExternalUrlHandler(deps) {
@@ -169,31 +170,49 @@ function registerFlowActionHandler(deps) {
169
170
  const { framework, log, callbacks, updateApp } = deps;
170
171
  let isFirstPage = true;
171
172
  let navigationId = 0;
173
+ const MAX_REDIRECTS = 5;
172
174
  const defaultGetScrollable = () => document.getElementById("scrollable-page-override") || document.getElementById("scrollable-page") || document.documentElement;
173
175
  const history = new History(log, {
174
176
  getScrollablePageElement: deps.getScrollablePageElement ?? defaultGetScrollable
175
177
  });
176
- framework.onAction(ACTION_KINDS.FLOW, async (action) => {
177
- const flowAction = action;
178
- const url = flowAction.url;
179
- log.debug(`FlowAction \u2192 ${url}`);
180
- if (flowAction.presentationContext === "modal") {
181
- const match2 = framework.routeUrl(url);
182
- if (match2) {
183
- const page = await framework.dispatch(
184
- match2.intent
185
- );
186
- callbacks.onModal(page);
187
- }
178
+ async function navigateTo(url, redirectCount, thisNav) {
179
+ if (redirectCount >= MAX_REDIRECTS) {
180
+ log.error(
181
+ `Navigation redirect loop detected (${MAX_REDIRECTS} redirects), stopping at: ${url}`
182
+ );
188
183
  return;
189
184
  }
190
- const thisNav = ++navigationId;
191
185
  const shouldReplace = isFirstPage || url === window.location.pathname + window.location.search;
192
186
  const match = framework.routeUrl(url);
193
187
  if (!match) {
194
188
  log.warn(`FlowAction: no route for ${url}`);
195
189
  return;
196
190
  }
191
+ const navCtx = createBrowserContext({
192
+ url,
193
+ intent: match.intent,
194
+ container: framework.container
195
+ });
196
+ const beforeResult = await framework.runBeforeLoad(
197
+ navCtx,
198
+ match.beforeGuards
199
+ );
200
+ if (beforeResult.kind === "redirect") {
201
+ log.debug(`beforeLoad \u2192 redirect to ${beforeResult.url}`);
202
+ await navigateTo(beforeResult.url, redirectCount + 1, thisNav);
203
+ return;
204
+ }
205
+ if (beforeResult.kind === "deny") {
206
+ log.warn(
207
+ `beforeLoad \u2192 denied (${beforeResult.status}): ${beforeResult.message}`
208
+ );
209
+ return;
210
+ }
211
+ if (beforeResult.kind === "rewrite") {
212
+ log.debug(`beforeLoad \u2192 rewrite to ${beforeResult.url}`);
213
+ await navigateTo(beforeResult.url, redirectCount + 1, thisNav);
214
+ return;
215
+ }
197
216
  const pagePromise = framework.dispatch(
198
217
  match.intent
199
218
  );
@@ -209,12 +228,33 @@ function registerFlowActionHandler(deps) {
209
228
  history.beforeTransition();
210
229
  updateApp({
211
230
  page: pagePromise.then(
212
- (page) => {
231
+ async (page) => {
213
232
  if (thisNav !== navigationId) {
214
233
  log.info("FlowAction commit superseded", url);
215
234
  return page;
216
235
  }
217
- const canonicalURL = url;
236
+ const postCtx = {
237
+ ...navCtx,
238
+ page
239
+ };
240
+ const afterResult = await framework.runAfterLoad(
241
+ postCtx,
242
+ match.afterGuards
243
+ );
244
+ if (afterResult.kind === "redirect") {
245
+ log.debug(`afterLoad \u2192 redirect to ${afterResult.url}`);
246
+ navigateTo(afterResult.url, redirectCount + 1, thisNav);
247
+ return page;
248
+ }
249
+ let canonicalURL = url;
250
+ if (afterResult.kind === "rewrite") {
251
+ canonicalURL = afterResult.url;
252
+ log.debug(`afterLoad \u2192 rewrite URL to ${canonicalURL}`);
253
+ }
254
+ if (afterResult.kind === "deny") {
255
+ log.warn(`afterLoad \u2192 denied (${afterResult.status})`);
256
+ return page;
257
+ }
218
258
  if (shouldReplace) {
219
259
  history.replaceState({ page }, canonicalURL);
220
260
  } else {
@@ -244,6 +284,23 @@ function registerFlowActionHandler(deps) {
244
284
  isFirstPage
245
285
  });
246
286
  isFirstPage = false;
287
+ }
288
+ framework.onAction(ACTION_KINDS.FLOW, async (action) => {
289
+ const flowAction = action;
290
+ const url = flowAction.url;
291
+ log.debug(`FlowAction \u2192 ${url}`);
292
+ if (flowAction.presentationContext === "modal") {
293
+ const match = framework.routeUrl(url);
294
+ if (match) {
295
+ const page = await framework.dispatch(
296
+ match.intent
297
+ );
298
+ callbacks.onModal(page);
299
+ }
300
+ return;
301
+ }
302
+ const thisNav = ++navigationId;
303
+ await navigateTo(url, 0, thisNav);
247
304
  });
248
305
  history.onPopState(async (url, cachedState) => {
249
306
  log.debug(`popstate \u2192 ${url}, cached=${!!cachedState}`);
@@ -268,6 +325,30 @@ function registerFlowActionHandler(deps) {
268
325
  });
269
326
  return;
270
327
  }
328
+ const navCtx = createBrowserContext({
329
+ url: parsed.pathname + parsed.search,
330
+ intent: routeMatch.intent,
331
+ container: framework.container
332
+ });
333
+ const beforeResult = await framework.runBeforeLoad(
334
+ navCtx,
335
+ routeMatch.beforeGuards
336
+ );
337
+ if (beforeResult.kind === "redirect") {
338
+ log.debug(`popstate beforeLoad \u2192 redirect to ${beforeResult.url}`);
339
+ const thisNav = ++navigationId;
340
+ await navigateTo(beforeResult.url, 0, thisNav);
341
+ return;
342
+ }
343
+ if (beforeResult.kind === "deny" || beforeResult.kind === "rewrite") {
344
+ if (beforeResult.kind === "deny") {
345
+ log.warn(`popstate beforeLoad \u2192 denied`);
346
+ } else {
347
+ const thisNav = ++navigationId;
348
+ await navigateTo(beforeResult.url, 0, thisNav);
349
+ }
350
+ return;
351
+ }
271
352
  const pagePromise = framework.dispatch(
272
353
  routeMatch.intent
273
354
  );
@@ -379,4 +460,4 @@ export {
379
460
  createPrefetchedIntentsFromDom,
380
461
  startBrowserApp
381
462
  };
382
- //# sourceMappingURL=chunk-PHDR7PIL.js.map
463
+ //# sourceMappingURL=chunk-4PPCVAKZ.js.map