@finesoft/front 0.5.1 → 0.5.2
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.
- package/README.md +4 -4
- package/dist/Outlet.svelte +39 -0
- package/dist/Outlet.svelte.d.ts +7 -0
- package/dist/browser-DIU6Sxl3.mjs +1237 -0
- package/dist/browser-kFMjlLGT.d.mts +262 -0
- package/dist/browser.d.mts +7 -2
- package/dist/browser.mjs +9 -1
- package/dist/controller-types-CgmJ6-le.d.mts +16 -0
- package/dist/cookies-Bpf9VayB.d.mts +779 -0
- package/dist/fetch-policy-BHT8RtrL.mjs +82 -0
- package/dist/host-guard-DDWxLpFL.mjs +222 -0
- package/dist/http-B6CJqDyf.d.mts +46 -0
- package/dist/http-CaxrMD1A.d.mts +1 -0
- package/dist/http-D70PL72H.mjs +257 -0
- package/dist/http.d.mts +3 -0
- package/dist/http.mjs +2 -0
- package/dist/index-node.d.mts +15 -0
- package/dist/index-node.mjs +15 -0
- package/dist/index.d.mts +48 -697
- package/dist/index.mjs +44 -261
- package/dist/load-node.d.mts +5 -0
- package/dist/load-node.mjs +10 -0
- package/dist/load-portable.d.mts +5 -0
- package/dist/load-portable.mjs +8 -0
- package/dist/lru-map-BKoUAySU.mjs +50 -0
- package/dist/messages-CAt2QdGr.mjs +140 -0
- package/dist/native-contract-DuR25hYB.d.mts +14 -0
- package/dist/native-contract.d.mts +2 -0
- package/dist/native-contract.mjs +1 -0
- package/dist/node-D9hB4dsz.d.mts +35 -0
- package/dist/node.d.mts +2 -0
- package/dist/node.mjs +59 -0
- package/dist/path-CGFl2w7D.mjs +113 -0
- package/dist/path-CXT6xGPO.d.mts +261 -0
- package/dist/portable-CaxrMD1A.d.mts +1 -0
- package/dist/portable.d.mts +11 -0
- package/dist/portable.mjs +12 -0
- package/dist/proxy-1SphZ7x7.mjs +436 -0
- package/dist/proxy-2dSWO-Xw.d.mts +53 -0
- package/dist/public-types-BcJM-AYc.mjs +835 -0
- package/dist/react-DhwBRw01.d.mts +16 -0
- package/dist/react.d.mts +3 -0
- package/dist/react.mjs +29 -0
- package/dist/rolldown-runtime-B4iAMlE-.mjs +35 -0
- package/dist/secure-fetch-Xlht2jd7.d.mts +30 -0
- package/dist/server-controller-proxy-BkVuVWVD.d.mts +38 -0
- package/dist/session-DnB4ZC3x.d.mts +1279 -0
- package/dist/src-Ftl_0rhu.mjs +28 -0
- package/dist/src-qwx7Vw8g.mjs +3807 -0
- package/dist/ssr-BEUNDvbj.d.mts +210 -0
- package/dist/ssr-C8xnYXoY.mjs +357 -0
- package/dist/ssr.d.mts +3 -0
- package/dist/ssr.mjs +3 -0
- package/dist/svelte-Dr5to3SE.d.mts +16 -0
- package/dist/svelte.d.mts +3 -0
- package/dist/svelte.mjs +13 -0
- package/dist/typegen-C-WeJCtf.d.mts +12 -0
- package/dist/typegen-cli.d.mts +1 -0
- package/dist/typegen-cli.mjs +11 -0
- package/dist/typegen.d.mts +3 -0
- package/dist/typegen.mjs +2 -0
- package/dist/types-BuaZHRG7.mjs +402 -0
- package/dist/undici-CPfL25Hr.mjs +22262 -0
- package/dist/vite-Cj4SPA8D.d.mts +277 -0
- package/dist/vite.d.mts +4 -0
- package/dist/vite.mjs +2354 -0
- package/dist/vue-DGmzuKho.d.mts +32 -0
- package/dist/vue.d.mts +3 -0
- package/dist/vue.mjs +56 -0
- package/dist/web.d.mts +6 -0
- package/dist/web.mjs +8 -0
- package/dist/worker.d.mts +2 -0
- package/dist/worker.mjs +2 -0
- package/docs/01-getting-started.md +67 -199
- package/docs/02-routing-and-controllers.md +163 -241
- package/docs/03-middleware.md +10 -212
- package/docs/04-rendering-and-hydration.md +6 -333
- package/docs/05-i18n.md +7 -237
- package/docs/06-http-client.md +20 -263
- package/docs/07-di-container.md +23 -257
- package/docs/08-observability.md +6 -286
- package/docs/09-server-and-deployment.md +59 -219
- package/docs/10-features-platform-pwa.md +7 -231
- package/docs/11-navigation.md +143 -288
- package/docs/12-session-restoration.md +6 -214
- package/docs/README.md +7 -7
- package/docs/advanced/custom-action-handler.md +29 -229
- package/docs/advanced/custom-adapter.md +7 -259
- package/docs/advanced/custom-event-recorder.md +7 -312
- package/docs/advanced/inline-proxy-codegen.md +6 -185
- package/docs/advanced/multi-tenant-scopes.md +10 -323
- package/docs/engineering/ci-release-flow.md +35 -222
- package/docs/engineering/project-structure.md +34 -277
- package/docs/engineering/testing.md +8 -310
- package/docs/pitfalls/container-scope-leak.md +2 -214
- package/docs/pitfalls/i18n-bundle-size.md +6 -176
- package/docs/pitfalls/proxy-binary-payloads.md +8 -12
- package/docs/pitfalls/ssr-hydration-mismatch.md +4 -160
- package/docs/pitfalls/ssr-vs-csr-globals.md +9 -170
- package/docs/zh/01-getting-started.md +67 -199
- package/docs/zh/02-routing-and-controllers.md +166 -244
- package/docs/zh/03-middleware.md +10 -212
- package/docs/zh/04-rendering-and-hydration.md +6 -333
- package/docs/zh/05-i18n.md +7 -237
- package/docs/zh/06-http-client.md +20 -263
- package/docs/zh/07-di-container.md +23 -257
- package/docs/zh/08-observability.md +6 -283
- package/docs/zh/09-server-and-deployment.md +59 -219
- package/docs/zh/10-features-platform-pwa.md +7 -231
- package/docs/zh/11-navigation.md +130 -290
- package/docs/zh/12-session-restoration.md +6 -214
- package/docs/zh/README.md +4 -4
- package/docs/zh/advanced/custom-action-handler.md +29 -229
- package/docs/zh/advanced/custom-adapter.md +7 -259
- package/docs/zh/advanced/custom-event-recorder.md +7 -312
- package/docs/zh/advanced/inline-proxy-codegen.md +6 -185
- package/docs/zh/advanced/multi-tenant-scopes.md +10 -323
- package/docs/zh/engineering/ci-release-flow.md +35 -222
- package/docs/zh/engineering/project-structure.md +34 -277
- package/docs/zh/engineering/testing.md +8 -310
- package/docs/zh/pitfalls/container-scope-leak.md +2 -214
- package/docs/zh/pitfalls/i18n-bundle-size.md +6 -176
- package/docs/zh/pitfalls/proxy-binary-payloads.md +8 -12
- package/docs/zh/pitfalls/ssr-hydration-mismatch.md +4 -160
- package/docs/zh/pitfalls/ssr-vs-csr-globals.md +9 -170
- package/package.json +118 -20
- package/dist/browser-BHhVWXik.mjs +0 -2
- package/dist/browser-BV2BBXm7.d.mts +0 -2811
|
@@ -0,0 +1,1279 @@
|
|
|
1
|
+
import { E as ParamSchema, F as Token, S as EventRecorder, T as InferOutput, _ as OperationPolicy, c as ExecutionContext, d as ExecutionHandle, j as Container, k as StandardSchemaV1, n as PathDescriptor, p as Invocation, s as AppDefinition, y as RuntimeHandle } from "./path-CXT6xGPO.mjs";
|
|
2
|
+
import { t as SecureFetchOptions } from "./secure-fetch-Xlht2jd7.mjs";
|
|
3
|
+
//#region ../core/src/intents/types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A route/navigation target. It remains a data transfer shape only: runtime
|
|
6
|
+
* operation execution never dispatches it or looks up a controller by id.
|
|
7
|
+
*/
|
|
8
|
+
interface Intent<T = unknown> {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly params?: Record<string, unknown>;
|
|
11
|
+
readonly _returnType?: T;
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region ../core/src/schema/multi.d.ts
|
|
15
|
+
/** 多值(数组)query codec:输入为 string[],并带 `multi` 运行时标记。 */
|
|
16
|
+
interface MultiValueSchema<Output> extends StandardSchemaV1<string[], Output> {
|
|
17
|
+
readonly multi: true;
|
|
18
|
+
}
|
|
19
|
+
interface ListOptions {
|
|
20
|
+
/** 最少元素数 */
|
|
21
|
+
min?: number;
|
|
22
|
+
/** 最多元素数 */
|
|
23
|
+
max?: number;
|
|
24
|
+
}
|
|
25
|
+
/** 运行时判断一个 schema 是否为多值 codec(resolve 据此决定取全部值还是单值)。 */
|
|
26
|
+
declare function isMultiValueSchema(schema: StandardSchemaV1): schema is MultiValueSchema<unknown>;
|
|
27
|
+
/**
|
|
28
|
+
* 多值 query 原语:用内部单值 codec 逐项校验/转换,产出数组。
|
|
29
|
+
* 缺失(undefined)→ 空数组;任一项校验失败 → 整体失败(resolve 据此 fall-through)。
|
|
30
|
+
*/
|
|
31
|
+
declare function list<T>(item: ParamSchema<T>, opts?: ListOptions): MultiValueSchema<T[]>;
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region ../core/src/schema/infer.d.ts
|
|
34
|
+
/** 剥离可选参数尾随的 "?" */
|
|
35
|
+
type StripOptional<S extends string> = S extends `${infer N}?` ? N : S;
|
|
36
|
+
/** 从 path pattern 字面量提取参数名联合(处理 :param 与 :param?) */
|
|
37
|
+
type ExtractParamNames<Path extends string> = Path extends `${infer _Head}:${infer Rest}` ? Rest extends `${infer Name}/${infer Tail}` ? StripOptional<Name> | ExtractParamNames<`/${Tail}`> : StripOptional<Rest> : never;
|
|
38
|
+
/** path 参数 codec map 的形状:key 只能是 path 中出现的参数名(均可选声明) */
|
|
39
|
+
type ParamsFor<Path extends string> = string extends Path ? Record<string, ParamSchema> : { [K in ExtractParamNames<Path>]?: ParamSchema; };
|
|
40
|
+
/** query 参数 codec map:key 自由开放;值可为单值 codec 或多值(list)codec */
|
|
41
|
+
type QuerySchemaMap = Record<string, StandardSchemaV1<string, unknown> | MultiValueSchema<unknown>>;
|
|
42
|
+
/** 把对象类型「拍平」为单层,便于阅读与类型相等比较 */
|
|
43
|
+
type Prettify<T> = { [K in keyof T]: T[K]; } & {};
|
|
44
|
+
/** 输出类型含 undefined 的 key(由 optional() 产生)→ 渲染为可选属性 */
|
|
45
|
+
type OptionalOutKeys<M extends Record<string, StandardSchemaV1>> = { [K in keyof M]: undefined extends InferOutput<M[K]> ? K : never; }[keyof M];
|
|
46
|
+
type RequiredOutKeys<M extends Record<string, StandardSchemaV1>> = { [K in keyof M]: undefined extends InferOutput<M[K]> ? never : K; }[keyof M];
|
|
47
|
+
/**
|
|
48
|
+
* 从 codec map 推导运行期参数类型。
|
|
49
|
+
* optional() 让输出含 undefined 的 key 渲染为可选属性(`tab?: T`,并剥掉冗余的 `| undefined`);
|
|
50
|
+
* withDefault() 始终有值,key 保持必选。
|
|
51
|
+
*/
|
|
52
|
+
type InferSchemaMap<M extends Record<string, StandardSchemaV1>> = Prettify<{ [K in RequiredOutKeys<M>]: InferOutput<M[K]>; } & { [K in OptionalOutKeys<M>]?: Exclude<InferOutput<M[K]>, undefined>; }>;
|
|
53
|
+
type InferParams<P extends Record<string, ParamSchema>> = InferSchemaMap<P>;
|
|
54
|
+
type InferQuery<Q extends QuerySchemaMap> = InferSchemaMap<Q>;
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region ../core/src/logger/types.d.ts
|
|
57
|
+
/** 日志级别 */
|
|
58
|
+
type Level = "debug" | "info" | "warn" | "error";
|
|
59
|
+
/**
|
|
60
|
+
* Logger 接口
|
|
61
|
+
*
|
|
62
|
+
* Logging is observational and does not produce presentation output.
|
|
63
|
+
*/
|
|
64
|
+
interface Logger {
|
|
65
|
+
debug(...args: unknown[]): void;
|
|
66
|
+
info(...args: unknown[]): void;
|
|
67
|
+
warn(...args: unknown[]): void;
|
|
68
|
+
error(...args: unknown[]): void;
|
|
69
|
+
}
|
|
70
|
+
interface LoggerFactory {
|
|
71
|
+
loggerFor(category: string): Logger;
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region ../core/src/logger/console.d.ts
|
|
75
|
+
type LogFilter = (category: string, level: "debug" | "info" | "warn" | "error") => boolean;
|
|
76
|
+
declare class ConsoleLogger implements Logger {
|
|
77
|
+
private readonly category;
|
|
78
|
+
private readonly shouldLog;
|
|
79
|
+
constructor(category: string, shouldLog?: LogFilter);
|
|
80
|
+
debug(...args: unknown[]): void;
|
|
81
|
+
info(...args: unknown[]): void;
|
|
82
|
+
warn(...args: unknown[]): void;
|
|
83
|
+
error(...args: unknown[]): void;
|
|
84
|
+
}
|
|
85
|
+
declare class ConsoleLoggerFactory implements LoggerFactory {
|
|
86
|
+
private readonly shouldLog;
|
|
87
|
+
constructor(shouldLog?: LogFilter);
|
|
88
|
+
loggerFor(category: string): Logger;
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region ../core/src/logger/reporting.d.ts
|
|
92
|
+
/** 日志上报回调 */
|
|
93
|
+
interface ReportCallback {
|
|
94
|
+
(level: Level, category: string, args: unknown[]): void;
|
|
95
|
+
}
|
|
96
|
+
/** 配置 */
|
|
97
|
+
interface ReportingLoggerOptions {
|
|
98
|
+
/** 最低上报级别(默认 "warn") */
|
|
99
|
+
minLevel?: Level;
|
|
100
|
+
/** 上报回调 */
|
|
101
|
+
report: ReportCallback;
|
|
102
|
+
}
|
|
103
|
+
declare class ReportingLogger implements Logger {
|
|
104
|
+
private readonly category;
|
|
105
|
+
private readonly minPriority;
|
|
106
|
+
private readonly report;
|
|
107
|
+
constructor(category: string, options: ReportingLoggerOptions);
|
|
108
|
+
debug(...args: unknown[]): void;
|
|
109
|
+
info(...args: unknown[]): void;
|
|
110
|
+
warn(...args: unknown[]): void;
|
|
111
|
+
error(...args: unknown[]): void;
|
|
112
|
+
private maybeReport;
|
|
113
|
+
}
|
|
114
|
+
declare class ReportingLoggerFactory implements LoggerFactory {
|
|
115
|
+
private readonly options;
|
|
116
|
+
constructor(options: ReportingLoggerOptions);
|
|
117
|
+
loggerFor(category: string): Logger;
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region ../core/src/intents/base-controller.d.ts
|
|
121
|
+
interface ControllerInput<TParams extends Record<string, unknown> = Record<string, unknown>, TQuery extends Record<string, unknown> = Record<string, unknown>, TContext extends ExecutionContext = ExecutionContext> {
|
|
122
|
+
readonly params: TParams;
|
|
123
|
+
readonly query: TQuery;
|
|
124
|
+
readonly context: TContext;
|
|
125
|
+
}
|
|
126
|
+
type InputFor<T extends ControllerInput> = T extends ControllerInput ? ControllerInput<T["params"], T["query"], T["context"]> extends T ? T : never : never;
|
|
127
|
+
interface ControllerFailure<TParams extends Record<string, unknown> = Record<string, unknown>, TQuery extends Record<string, unknown> = Record<string, unknown>, TContext extends ExecutionContext = ExecutionContext> extends ControllerInput<TParams, TQuery, TContext> {
|
|
128
|
+
readonly error: Error;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 抽象 Controller 基类
|
|
132
|
+
*
|
|
133
|
+
* 统一处理:
|
|
134
|
+
* - 类型安全的输入 (ControllerInput)
|
|
135
|
+
* - 返回类型约束 (TResult)
|
|
136
|
+
* - try/catch 错误处理 + 可选 fallback
|
|
137
|
+
*
|
|
138
|
+
* 路由生成的输入可直接使用 BaseController<Input, Result>。
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```ts
|
|
142
|
+
* const API = createToken<ApiClient>("api");
|
|
143
|
+
*
|
|
144
|
+
* type Input = ControllerInput<{ productId: string }>;
|
|
145
|
+
* class ProductController extends BaseController<Input, ProductPage> {
|
|
146
|
+
* async execute({ params, context }: Input) {
|
|
147
|
+
* const api = await context.get(API);
|
|
148
|
+
* return api.getProduct(params.productId);
|
|
149
|
+
* }
|
|
150
|
+
*
|
|
151
|
+
* fallback({ params }: ControllerFailure<{ productId: string }>) {
|
|
152
|
+
* return getMockProduct(params.productId);
|
|
153
|
+
* }
|
|
154
|
+
* }
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
declare abstract class BaseController<TInput extends ControllerInput = ControllerInput, TResult = unknown> {
|
|
158
|
+
protected prepareContext(context: ExecutionContext): TInput["context"];
|
|
159
|
+
/**
|
|
160
|
+
* 执行业务逻辑 — 子类必须实现
|
|
161
|
+
*
|
|
162
|
+
* @param input - 分离的 params、query 和执行 context
|
|
163
|
+
* @returns 页面数据
|
|
164
|
+
*/
|
|
165
|
+
abstract execute(input: InputFor<TInput>): Promise<TResult> | TResult;
|
|
166
|
+
/**
|
|
167
|
+
* 错误回退 — 子类可选覆写
|
|
168
|
+
*
|
|
169
|
+
* 当 execute() 抛出异常时调用。
|
|
170
|
+
* 默认行为: 重新抛出原始错误。
|
|
171
|
+
*
|
|
172
|
+
* @param input - 操作输入与 execute() 抛出的 error
|
|
173
|
+
* @returns 回退数据
|
|
174
|
+
*/
|
|
175
|
+
fallback({ error }: InputFor<TInput> & {
|
|
176
|
+
readonly error: Error;
|
|
177
|
+
}): Promise<TResult> | TResult;
|
|
178
|
+
/**
|
|
179
|
+
* 自动 try/catch → fallback 模式。
|
|
180
|
+
*/
|
|
181
|
+
perform(params: InputFor<TInput>["params"], context: ExecutionContext, ...query: {} extends InputFor<TInput>["query"] ? [query?: InputFor<TInput>["query"]] : [query: InputFor<TInput>["query"]]): Promise<TResult>;
|
|
182
|
+
}
|
|
183
|
+
//#endregion
|
|
184
|
+
//#region ../core/src/utils/platform.d.ts
|
|
185
|
+
/**
|
|
186
|
+
* Platform — UA 解析与平台检测
|
|
187
|
+
*
|
|
188
|
+
* 提供统一的平台/浏览器/OS 检测,避免在各处手写 UA 判断。
|
|
189
|
+
*/
|
|
190
|
+
interface PlatformInfo {
|
|
191
|
+
/** 操作系统 */
|
|
192
|
+
os: "ios" | "android" | "macos" | "windows" | "linux" | "unknown";
|
|
193
|
+
/** 浏览器 */
|
|
194
|
+
browser: "safari" | "chrome" | "firefox" | "edge" | "opera" | "samsung" | "unknown";
|
|
195
|
+
/** 渲染引擎 */
|
|
196
|
+
engine: "webkit" | "blink" | "gecko" | "unknown";
|
|
197
|
+
/** 是否为移动设备 */
|
|
198
|
+
isMobile: boolean;
|
|
199
|
+
/** 是否为触摸设备 */
|
|
200
|
+
isTouch: boolean;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* 从 User-Agent 字符串解析平台信息
|
|
204
|
+
*
|
|
205
|
+
* @param ua - 主机显式提供的 User-Agent 字符串,默认空字符串
|
|
206
|
+
* @param maxTouchPoints - 主机显式提供的触摸点数量,默认 0
|
|
207
|
+
*/
|
|
208
|
+
declare function detectPlatform(ua?: string, maxTouchPoints?: number): PlatformInfo;
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region ../core/src/i18n/types.d.ts
|
|
211
|
+
/**
|
|
212
|
+
* i18n — 类型定义
|
|
213
|
+
*
|
|
214
|
+
* 框架级国际化基础设施。
|
|
215
|
+
*/
|
|
216
|
+
/** 翻译函数 */
|
|
217
|
+
interface Translator {
|
|
218
|
+
/**
|
|
219
|
+
* 翻译 key → 本地化字符串
|
|
220
|
+
* @param key - 翻译 key
|
|
221
|
+
* @param values - 插值参数
|
|
222
|
+
*/
|
|
223
|
+
t(key: string, values?: Record<string, string | number>): string;
|
|
224
|
+
/**
|
|
225
|
+
* 复数形式翻译
|
|
226
|
+
* @param key - 翻译 key 前缀
|
|
227
|
+
* @param count - 数量
|
|
228
|
+
* @param values - 附加插值
|
|
229
|
+
*/
|
|
230
|
+
plural(key: string, count: number, values?: Record<string, string | number>): string;
|
|
231
|
+
/** 当前 locale(如 "zh-Hans" / "en-US") */
|
|
232
|
+
readonly locale: string;
|
|
233
|
+
}
|
|
234
|
+
/** 文本方向 */
|
|
235
|
+
type TextDirection = "ltr" | "rtl";
|
|
236
|
+
/** HTML 语言属性 */
|
|
237
|
+
interface LocaleAttributes {
|
|
238
|
+
/** BCP 47 语言标签 */
|
|
239
|
+
lang: string;
|
|
240
|
+
/** 文本方向 */
|
|
241
|
+
dir: TextDirection;
|
|
242
|
+
}
|
|
243
|
+
/** Locale 信息 */
|
|
244
|
+
interface LocaleInfo {
|
|
245
|
+
/** 语言代码(如 "zh-Hans", "en") */
|
|
246
|
+
language: string;
|
|
247
|
+
/** 地区/Storefront 代码(如 "CN", "US") */
|
|
248
|
+
region?: string;
|
|
249
|
+
/** BCP 47 完整标签 */
|
|
250
|
+
bcp47: string;
|
|
251
|
+
/** 文本方向 */
|
|
252
|
+
dir: TextDirection;
|
|
253
|
+
}
|
|
254
|
+
//#endregion
|
|
255
|
+
//#region ../core/src/i18n/messages.d.ts
|
|
256
|
+
/**
|
|
257
|
+
* i18n message helpers shared by SSR and browser startup.
|
|
258
|
+
*/
|
|
259
|
+
/** Flat translation table: key -> localized text */
|
|
260
|
+
type FlatMessages = Record<string, string>;
|
|
261
|
+
/** Nested translation value: plain text or pluralized text map */
|
|
262
|
+
type NestedMessageValue = string | Record<string, string>;
|
|
263
|
+
/** Locale-grouped translation table */
|
|
264
|
+
type LocaleMessages = Record<string, Record<string, NestedMessageValue>>;
|
|
265
|
+
/**
|
|
266
|
+
* Translation message formats supported by the framework:
|
|
267
|
+
* - flat messages for a single locale
|
|
268
|
+
* - locale-grouped messages with optional plural subkeys
|
|
269
|
+
*/
|
|
270
|
+
type TranslationMessages = FlatMessages | LocaleMessages;
|
|
271
|
+
/**
|
|
272
|
+
* Resolve `TranslationMessages` into the flat map consumed by
|
|
273
|
+
* `SimpleTranslator`.
|
|
274
|
+
*/
|
|
275
|
+
declare function resolveMessages(messages: TranslationMessages, locale: string): Record<string, string> | undefined;
|
|
276
|
+
//#endregion
|
|
277
|
+
//#region ../core/src/dependencies/make-dependencies.d.ts
|
|
278
|
+
/** 存储接口 */
|
|
279
|
+
interface Storage {
|
|
280
|
+
get(key: string): string | undefined;
|
|
281
|
+
set(key: string, value: string): void;
|
|
282
|
+
delete(key: string): void;
|
|
283
|
+
}
|
|
284
|
+
/** Feature Flags */
|
|
285
|
+
interface FeatureFlags {
|
|
286
|
+
isEnabled(key: string): boolean;
|
|
287
|
+
getString(key: string): string | undefined;
|
|
288
|
+
getNumber(key: string): number | undefined;
|
|
289
|
+
}
|
|
290
|
+
/** Feature Flags Provider — 用于从远程/外部源加载 flags */
|
|
291
|
+
interface FeatureFlagsProvider {
|
|
292
|
+
isEnabled(key: string): boolean;
|
|
293
|
+
getString?(key: string): string | undefined;
|
|
294
|
+
getNumber?(key: string): number | undefined;
|
|
295
|
+
}
|
|
296
|
+
declare const DEP_KEYS: {
|
|
297
|
+
readonly LOGGER: Token<Logger>;
|
|
298
|
+
readonly LOGGER_FACTORY: Token<LoggerFactory>;
|
|
299
|
+
readonly STORAGE: Token<Storage>;
|
|
300
|
+
readonly FEATURE_FLAGS: Token<FeatureFlags>;
|
|
301
|
+
readonly FETCH: Token<typeof fetch>;
|
|
302
|
+
/**
|
|
303
|
+
* `fetch` 包了 SSRF 防护(拒绝 private / loopback / 保留 IP + DNS resolve 后逐 IP 校验)。
|
|
304
|
+
* 当 controller 用用户可控的 URL 发起请求(图片代理、链接预览、回调等),
|
|
305
|
+
* 优先 resolve 这个 key 而不是 `FETCH`。要 opt-out 可手动调 `secureFetch(baseFetch, { allowInternalHosts: true })`。
|
|
306
|
+
*/
|
|
307
|
+
readonly SAFE_FETCH: Token<typeof fetch>;
|
|
308
|
+
readonly EVENT_RECORDER: Token<EventRecorder>;
|
|
309
|
+
readonly LOCALE: Token<LocaleAttributes>;
|
|
310
|
+
readonly PLATFORM: Token<PlatformInfo>;
|
|
311
|
+
readonly TRANSLATOR: Token<Translator>;
|
|
312
|
+
};
|
|
313
|
+
//#endregion
|
|
314
|
+
//#region ../web/src/router/types.d.ts
|
|
315
|
+
/** 路由参数:键为参数名,值为 codec 转换后的任意类型(string / number / boolean …)。 */
|
|
316
|
+
type RouteParams = Record<string, unknown>;
|
|
317
|
+
/** Path and query share codecs, but retain separate names and values. */
|
|
318
|
+
interface RouteInput {
|
|
319
|
+
readonly params: RouteParams;
|
|
320
|
+
readonly query: RouteParams;
|
|
321
|
+
}
|
|
322
|
+
interface RouteIntent<T = unknown> extends Intent<T> {
|
|
323
|
+
readonly query?: RouteParams;
|
|
324
|
+
}
|
|
325
|
+
//#endregion
|
|
326
|
+
//#region ../web/src/models/page.d.ts
|
|
327
|
+
/**
|
|
328
|
+
* BasePage — 所有页面共享的基础属性
|
|
329
|
+
*
|
|
330
|
+
* 具体页面类型由应用层定义并扩展此接口。
|
|
331
|
+
*
|
|
332
|
+
* SSR prefetch 数据序列化时的可见性由 `FINESOFT_PUBLIC` symbol 控制 —— 见
|
|
333
|
+
* `markPublic` / `isPublicMarked`。没有 marker 时只序列化 BasePage 标准字段。
|
|
334
|
+
* 嵌套对象需独立 markPublic、递归 projection 或 codec;true 仅选择本层字段。
|
|
335
|
+
*/
|
|
336
|
+
interface BasePage {
|
|
337
|
+
id: string;
|
|
338
|
+
pageType: string;
|
|
339
|
+
title: string;
|
|
340
|
+
description?: string;
|
|
341
|
+
url?: string;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* 显式声明 page 对象里哪些字段可以跨 SSR/CSR 边界进入 HTML。未列出的字段在
|
|
345
|
+
* `serializeServerData` 时会被剥除,杜绝整 page 对象(含 apiToken、内部备注等)
|
|
346
|
+
* 被无意中 hydrate 到客户端的失误。
|
|
347
|
+
*
|
|
348
|
+
* 用 symbol 而非普通字段名:避免和应用自己的字段冲突,且 `JSON.stringify` 自动
|
|
349
|
+
* 忽略 symbol key,所以 marker 永远不会出现在序列化输出里。
|
|
350
|
+
*/
|
|
351
|
+
declare const FINESOFT_PUBLIC: unique symbol;
|
|
352
|
+
/** 标准 BasePage 字段 —— marker 缺失但显式标注时使用。 */
|
|
353
|
+
declare const BASE_PAGE_FIELDS: readonly ["id", "pageType", "title", "description", "url"];
|
|
354
|
+
/**
|
|
355
|
+
* 把 `publicFields` 列表写到 page 上,供 `serializeServerData` 读取。
|
|
356
|
+
* 推荐做法是不要直接渲染原始 page 对象,而是经过这个函数显式声明 contract:
|
|
357
|
+
*
|
|
358
|
+
* ```ts
|
|
359
|
+
* return markPublic(
|
|
360
|
+
* {
|
|
361
|
+
* id: "profile",
|
|
362
|
+
* pageType: "profile",
|
|
363
|
+
* title: user.name,
|
|
364
|
+
* email: user.email,
|
|
365
|
+
* apiToken: user.apiToken, // 仍在对象上,给服务端别处用
|
|
366
|
+
* },
|
|
367
|
+
* ["id", "pageType", "title", "email"], // 但 prefetch 只 serialize 这些
|
|
368
|
+
* );
|
|
369
|
+
* ```
|
|
370
|
+
*
|
|
371
|
+
* `true` 选择本层字段;对象/数组仍需独立标记、递归 projection 或 codec。
|
|
372
|
+
*/
|
|
373
|
+
interface PublicValueCodec {
|
|
374
|
+
readonly kind: "codec";
|
|
375
|
+
encode(value: unknown): unknown;
|
|
376
|
+
}
|
|
377
|
+
interface PublicProjection {
|
|
378
|
+
readonly [field: string]: true | PublicProjection | PublicValueCodec;
|
|
379
|
+
}
|
|
380
|
+
declare function markPublic<P extends object>(page: P, publicFields: readonly (keyof P)[] | true | PublicProjection): P;
|
|
381
|
+
/** True 如果 page 用 `markPublic` 显式标过。 */
|
|
382
|
+
declare function isPublicMarked(page: unknown): boolean;
|
|
383
|
+
/** 取出 `markPublic` 写入的字段白名单;`true` 表示本层字段,`null` 表示未标注。 */
|
|
384
|
+
declare function getPublicFields(page: unknown): readonly string[] | true | PublicProjection | null;
|
|
385
|
+
//#endregion
|
|
386
|
+
//#region ../web/src/middleware/types.d.ts
|
|
387
|
+
/** 导航上下文(beforeLoad 阶段可用) */
|
|
388
|
+
interface NavigationContext {
|
|
389
|
+
readonly signal?: AbortSignal;
|
|
390
|
+
/** 完整 URL(path + query) */
|
|
391
|
+
readonly url: string;
|
|
392
|
+
/** 仅路径部分 */
|
|
393
|
+
readonly path: string;
|
|
394
|
+
/** 路径参数(codec 转换后可能是 number/boolean 等) */
|
|
395
|
+
readonly params: Record<string, unknown>;
|
|
396
|
+
readonly query: Record<string, unknown>;
|
|
397
|
+
/** 匹配的 Intent */
|
|
398
|
+
readonly intent: RouteIntent;
|
|
399
|
+
/** 是否在服务端运行 */
|
|
400
|
+
readonly isServer: boolean;
|
|
401
|
+
/** DI 容器(可获取自定义服务) */
|
|
402
|
+
readonly container: Container;
|
|
403
|
+
/** 获取 Cookie 值(两端均可用) */
|
|
404
|
+
getCookie(name: string): string | undefined;
|
|
405
|
+
/** 获取请求头值(仅服务端有值,客户端始终返回 undefined) */
|
|
406
|
+
getHeader(name: string): string | undefined;
|
|
407
|
+
}
|
|
408
|
+
/** 后置上下文(afterLoad 阶段,包含页面数据) */
|
|
409
|
+
interface PostLoadContext extends NavigationContext {
|
|
410
|
+
/** 控制器返回的页面数据 */
|
|
411
|
+
readonly page: BasePage;
|
|
412
|
+
}
|
|
413
|
+
/** 继续执行下一个中间件 */
|
|
414
|
+
interface NextResult {
|
|
415
|
+
readonly kind: "next";
|
|
416
|
+
}
|
|
417
|
+
/** 重定向(服务端: HTTP 301/302,客户端: 触发新导航) */
|
|
418
|
+
interface RedirectResult {
|
|
419
|
+
readonly kind: "redirect";
|
|
420
|
+
readonly url: string;
|
|
421
|
+
readonly status: number;
|
|
422
|
+
}
|
|
423
|
+
/** URL 重写(服务端: HTTP 301,客户端: replaceState 仅更新地址栏) */
|
|
424
|
+
interface RewriteResult {
|
|
425
|
+
readonly kind: "rewrite";
|
|
426
|
+
readonly url: string;
|
|
427
|
+
}
|
|
428
|
+
/** 拒绝访问 */
|
|
429
|
+
interface DenyResult {
|
|
430
|
+
readonly kind: "deny";
|
|
431
|
+
readonly status: number;
|
|
432
|
+
readonly message: string;
|
|
433
|
+
}
|
|
434
|
+
type MiddlewareResult = NextResult | RedirectResult | RewriteResult | DenyResult;
|
|
435
|
+
/** 继续执行 */
|
|
436
|
+
declare function next(): NextResult;
|
|
437
|
+
/** 重定向到新 URL */
|
|
438
|
+
declare function redirect(url: string, status?: 301 | 302): RedirectResult;
|
|
439
|
+
/** URL 重写(不重新加载数据) */
|
|
440
|
+
declare function rewrite(url: string): RewriteResult;
|
|
441
|
+
/** 拒绝访问 */
|
|
442
|
+
declare function deny(status?: number, message?: string): DenyResult;
|
|
443
|
+
/** beforeLoad 守卫:路由匹配后、数据加载前 */
|
|
444
|
+
type BeforeLoadGuard = (ctx: NavigationContext) => MiddlewareResult | Promise<MiddlewareResult>;
|
|
445
|
+
/** afterLoad 守卫:数据加载后、渲染前 */
|
|
446
|
+
type AfterLoadGuard = (ctx: PostLoadContext) => MiddlewareResult | Promise<MiddlewareResult>;
|
|
447
|
+
//#endregion
|
|
448
|
+
//#region ../web/src/navigation/types.d.ts
|
|
449
|
+
/** 导航节点 Kind 常量 */
|
|
450
|
+
declare const NAVIGATION_NODE_KINDS: {
|
|
451
|
+
readonly LEAF: "leaf";
|
|
452
|
+
readonly STACK: "stack";
|
|
453
|
+
readonly TABS: "tabs";
|
|
454
|
+
readonly SPLIT: "split";
|
|
455
|
+
};
|
|
456
|
+
/** 所有导航节点 Kind 的联合类型 */
|
|
457
|
+
type NavigationNodeKind = (typeof NAVIGATION_NODE_KINDS)[keyof typeof NAVIGATION_NODE_KINDS];
|
|
458
|
+
type EntryId = string;
|
|
459
|
+
type ResourceKey = string;
|
|
460
|
+
/** 叶子:一个具体导航目标 */
|
|
461
|
+
interface LeafNode {
|
|
462
|
+
readonly entryId: EntryId;
|
|
463
|
+
readonly url?: string;
|
|
464
|
+
readonly kind: typeof NAVIGATION_NODE_KINDS.LEAF;
|
|
465
|
+
readonly intent: string;
|
|
466
|
+
readonly params: RouteParams;
|
|
467
|
+
readonly query?: RouteParams;
|
|
468
|
+
}
|
|
469
|
+
/** 栈:有序路径,entries[0]=根,末尾=栈顶(可见) */
|
|
470
|
+
interface StackNode {
|
|
471
|
+
readonly kind: typeof NAVIGATION_NODE_KINDS.STACK;
|
|
472
|
+
readonly entries: readonly NavigationNode[];
|
|
473
|
+
}
|
|
474
|
+
/** Tabs:并列分支 + 当前激活键 + 稳定顺序;仅激活分支可见 */
|
|
475
|
+
interface TabsNode {
|
|
476
|
+
readonly kind: typeof NAVIGATION_NODE_KINDS.TABS;
|
|
477
|
+
readonly active: string;
|
|
478
|
+
readonly order: readonly string[];
|
|
479
|
+
readonly branches: Readonly<Record<string, NavigationNode>>;
|
|
480
|
+
}
|
|
481
|
+
/** Split 列:列 id + 该列内容(undefined = 尚未选择) */
|
|
482
|
+
interface SplitColumn {
|
|
483
|
+
readonly id: string;
|
|
484
|
+
readonly content: NavigationNode | undefined;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Split 列可见性,对标 SwiftUI `NavigationSplitViewVisibility`。
|
|
488
|
+
*
|
|
489
|
+
* 这是**可绑定 / 可序列化 / 可恢复的导航状态**(不是渲染样式):它决定哪些列算「可见」,
|
|
490
|
+
* 进而影响 `collectVisibleDestinations` 与 SSR 预取——例如深链到 `detailOnly` 时服务端只预取 detail 列。
|
|
491
|
+
*
|
|
492
|
+
* - `automatic`(缺省):框架不裁剪,所有有内容的列都可见(SSR 端无视口信息时的安全默认;客户端再按视口自适应)。
|
|
493
|
+
* - `all`:显式所有列可见(语义同 automatic 的全列)。
|
|
494
|
+
* - `doubleColumn`:仅首列 + 末列可见(三列时隐藏中间 content 列)。
|
|
495
|
+
* - `detailOnly`:仅末列(detail)可见。
|
|
496
|
+
*
|
|
497
|
+
* 注意:compact 视口塌缩成单栈(SwiftUI 的 `preferredCompactColumn`)是视口反应式的纯渲染决策,
|
|
498
|
+
* 框架不建模,交给应用按 `getPlatform()` / 视口自行处理。
|
|
499
|
+
*/
|
|
500
|
+
declare const SPLIT_VISIBILITIES: {
|
|
501
|
+
readonly AUTOMATIC: "automatic";
|
|
502
|
+
readonly ALL: "all";
|
|
503
|
+
readonly DOUBLE_COLUMN: "doubleColumn";
|
|
504
|
+
readonly DETAIL_ONLY: "detailOnly";
|
|
505
|
+
};
|
|
506
|
+
/** Split 列可见性的联合类型 */
|
|
507
|
+
type SplitVisibility = (typeof SPLIT_VISIBILITIES)[keyof typeof SPLIT_VISIBILITIES];
|
|
508
|
+
/**
|
|
509
|
+
* Split:多列并存,列间通过 selectColumn 设置后续列内容。
|
|
510
|
+
* `visibility` 决定哪些列算可见(缺省 `automatic` = 全列),是可序列化的导航状态。
|
|
511
|
+
*/
|
|
512
|
+
interface SplitNode {
|
|
513
|
+
readonly kind: typeof NAVIGATION_NODE_KINDS.SPLIT;
|
|
514
|
+
readonly columns: readonly SplitColumn[];
|
|
515
|
+
readonly visibility?: SplitVisibility;
|
|
516
|
+
}
|
|
517
|
+
/** 所有导航节点的联合类型 */
|
|
518
|
+
type NavigationNode = LeafNode | StackNode | TabsNode | SplitNode;
|
|
519
|
+
/** 指向树中某节点的路径(从根到目标)的一步 */
|
|
520
|
+
type NavigationPathStep = {
|
|
521
|
+
readonly kind: "stack-entry";
|
|
522
|
+
readonly index: number;
|
|
523
|
+
} | {
|
|
524
|
+
readonly kind: "tab";
|
|
525
|
+
readonly key: string;
|
|
526
|
+
} | {
|
|
527
|
+
readonly kind: "column";
|
|
528
|
+
readonly id: string;
|
|
529
|
+
};
|
|
530
|
+
/** 指向树中某节点的完整路径(从根到目标) */
|
|
531
|
+
type NavigationPath = readonly NavigationPathStep[];
|
|
532
|
+
/** 单个可见目标的解析结果 */
|
|
533
|
+
interface ResolvedDestination {
|
|
534
|
+
readonly renderMode?: string;
|
|
535
|
+
readonly rewriteUrl?: string;
|
|
536
|
+
readonly cache?: "public";
|
|
537
|
+
readonly entryId: EntryId;
|
|
538
|
+
readonly resourceKey: ResourceKey;
|
|
539
|
+
readonly intent: string;
|
|
540
|
+
readonly params: RouteParams;
|
|
541
|
+
readonly query?: RouteParams;
|
|
542
|
+
readonly page: BasePage;
|
|
543
|
+
readonly status?: number;
|
|
544
|
+
}
|
|
545
|
+
/** 导航快照:当前树 + 所有可见目标解析结果(顺序与 collectVisibleDestinations 一致) */
|
|
546
|
+
interface NavigationSnapshot {
|
|
547
|
+
/** Uncommitted redirect, including admission before any destination exists. */
|
|
548
|
+
readonly redirect?: {
|
|
549
|
+
readonly url: string;
|
|
550
|
+
readonly status: number;
|
|
551
|
+
};
|
|
552
|
+
/** Transaction denial is observable even for an empty tree; this snapshot is never committed. */
|
|
553
|
+
readonly rejection?: DenyResult;
|
|
554
|
+
readonly transitionId?: string;
|
|
555
|
+
readonly historyMode?: "push" | "replace";
|
|
556
|
+
readonly tree: NavigationNode;
|
|
557
|
+
readonly destinations: readonly ResolvedDestination[];
|
|
558
|
+
}
|
|
559
|
+
/** 错误类型:序列化 / 路径 / 操作非法时抛出 */
|
|
560
|
+
declare class NavigationError extends Error {
|
|
561
|
+
constructor(message: string);
|
|
562
|
+
}
|
|
563
|
+
//#endregion
|
|
564
|
+
//#region ../web/src/actions/types.d.ts
|
|
565
|
+
/** Action Kind 常量 */
|
|
566
|
+
declare const ACTION_KINDS: {
|
|
567
|
+
FLOW: "flow";
|
|
568
|
+
EXTERNAL_URL: "externalUrl";
|
|
569
|
+
COMPOUND: "compound";
|
|
570
|
+
PUSH: "push";
|
|
571
|
+
POP: "pop";
|
|
572
|
+
POP_TO_ROOT: "popToRoot";
|
|
573
|
+
POP_TO: "popTo";
|
|
574
|
+
REPLACE_TOP: "replaceTop";
|
|
575
|
+
SELECT_TAB: "selectTab";
|
|
576
|
+
SELECT_COLUMN: "selectColumn";
|
|
577
|
+
SET_VISIBILITY: "setVisibility";
|
|
578
|
+
HYDRATE: "hydrate";
|
|
579
|
+
REUSE_ENTRY: "reuseEntry";
|
|
580
|
+
REFRESH: "refresh";
|
|
581
|
+
};
|
|
582
|
+
/** FlowAction — SPA 导航 */
|
|
583
|
+
interface FlowAction {
|
|
584
|
+
kind: typeof ACTION_KINDS.FLOW;
|
|
585
|
+
url: string;
|
|
586
|
+
/** 默认按应用的路由模型导航;modal 使用独立页面会话。 */
|
|
587
|
+
presentationContext?: "default" | "modal";
|
|
588
|
+
}
|
|
589
|
+
/** ExternalUrlAction — 外部链接 */
|
|
590
|
+
interface ExternalUrlAction {
|
|
591
|
+
kind: typeof ACTION_KINDS.EXTERNAL_URL;
|
|
592
|
+
url: string;
|
|
593
|
+
}
|
|
594
|
+
/** CompoundAction — 组合 Action */
|
|
595
|
+
interface CompoundAction<Params extends Record<string, RouteParams> = Record<string, RouteParams>, Queries extends Record<string, RouteParams> = Record<string, RouteParams>> {
|
|
596
|
+
kind: typeof ACTION_KINDS.COMPOUND;
|
|
597
|
+
actions: Action<Params, Queries>[];
|
|
598
|
+
}
|
|
599
|
+
type InputField<P extends RouteParams, Key extends string> = {} extends P ? { readonly [K in Key]?: P; } : { readonly [K in Key]: P; };
|
|
600
|
+
type PageAction<Params extends Record<string, RouteParams>, Queries extends Record<string, RouteParams>> = { [Id in keyof Params & string]: InputField<Params[Id], "params"> & InputField<Queries[Id], "query"> & ({
|
|
601
|
+
readonly kind: "push" | "replaceTop";
|
|
602
|
+
readonly intent: Id;
|
|
603
|
+
readonly target?: NavigationPath;
|
|
604
|
+
readonly url?: string;
|
|
605
|
+
} | {
|
|
606
|
+
readonly kind: "selectColumn";
|
|
607
|
+
readonly columnId: string;
|
|
608
|
+
readonly intent: Id;
|
|
609
|
+
readonly target?: NavigationPath;
|
|
610
|
+
}); }[keyof Params & string];
|
|
611
|
+
/** Structured actions share the same dispatcher and guarded commit as URL actions. */
|
|
612
|
+
type TreeAction<Params extends Record<string, RouteParams> = Record<string, RouteParams>, Queries extends Record<string, RouteParams> = Record<string, RouteParams>> = PageAction<Params, Queries> | {
|
|
613
|
+
readonly kind: "pop";
|
|
614
|
+
readonly count?: number;
|
|
615
|
+
readonly target?: NavigationPath;
|
|
616
|
+
} | {
|
|
617
|
+
readonly kind: "popToRoot";
|
|
618
|
+
readonly target?: NavigationPath;
|
|
619
|
+
} | {
|
|
620
|
+
readonly kind: "popTo";
|
|
621
|
+
readonly index: number;
|
|
622
|
+
readonly target?: NavigationPath;
|
|
623
|
+
} | {
|
|
624
|
+
readonly kind: "selectTab";
|
|
625
|
+
readonly key: string;
|
|
626
|
+
readonly target?: NavigationPath;
|
|
627
|
+
} | {
|
|
628
|
+
readonly kind: "selectColumn";
|
|
629
|
+
readonly columnId: string;
|
|
630
|
+
readonly intent: undefined;
|
|
631
|
+
readonly params?: never;
|
|
632
|
+
readonly target?: NavigationPath;
|
|
633
|
+
} | {
|
|
634
|
+
readonly kind: "setVisibility";
|
|
635
|
+
readonly visibility: SplitVisibility;
|
|
636
|
+
readonly target?: NavigationPath;
|
|
637
|
+
} | {
|
|
638
|
+
readonly kind: "hydrate";
|
|
639
|
+
readonly tree: NavigationNode;
|
|
640
|
+
} | {
|
|
641
|
+
readonly kind: "reuseEntry";
|
|
642
|
+
readonly entryId: string;
|
|
643
|
+
} | {
|
|
644
|
+
readonly kind: "refresh";
|
|
645
|
+
};
|
|
646
|
+
type Action<Params extends Record<string, RouteParams> = Record<string, RouteParams>, Queries extends Record<string, RouteParams> = Record<string, RouteParams>> = FlowAction | TreeAction<Params, Queries> | ExternalUrlAction | CompoundAction<Params, Queries>;
|
|
647
|
+
interface ActionInvocation {
|
|
648
|
+
readonly signal?: AbortSignal;
|
|
649
|
+
}
|
|
650
|
+
declare function isFlowAction(action: Action): action is FlowAction;
|
|
651
|
+
declare function isExternalUrlAction(action: Action): action is ExternalUrlAction;
|
|
652
|
+
declare function isCompoundAction(action: Action): action is CompoundAction;
|
|
653
|
+
declare function makeFlowAction(url: string, presentationContext?: FlowAction["presentationContext"]): FlowAction;
|
|
654
|
+
declare function makeExternalUrlAction(url: string): ExternalUrlAction;
|
|
655
|
+
//#endregion
|
|
656
|
+
//#region ../web/src/actions/dispatcher.d.ts
|
|
657
|
+
/** Action 处理器函数类型 */
|
|
658
|
+
type ActionHandler<A extends Action = Action, Result = void> = (action: A, invocation?: ActionInvocation) => Promise<Result | void> | Result | void;
|
|
659
|
+
declare class ActionDispatcher<Result = void> {
|
|
660
|
+
private readonly currentResult?;
|
|
661
|
+
private handlers;
|
|
662
|
+
private closed;
|
|
663
|
+
private epoch;
|
|
664
|
+
private invocations;
|
|
665
|
+
private pendingFlow?;
|
|
666
|
+
constructor(currentResult?: (() => Result) | undefined);
|
|
667
|
+
get generation(): number;
|
|
668
|
+
/** Cancel pending groups; a handler may retain its own invocation during URL admission. */
|
|
669
|
+
cancel: (invocation?: ActionInvocation) => void;
|
|
670
|
+
/**
|
|
671
|
+
* 注册指定 kind 的 handler。
|
|
672
|
+
*
|
|
673
|
+
* 重复 kind 时保留第一个注册者并发出警告——这是有意设计:
|
|
674
|
+
* framework 内部 handler 先注册,应用层意外覆盖会被记录而非静默生效。
|
|
675
|
+
* 如需显式替换,先调用 removeAction(kind)。
|
|
676
|
+
*/
|
|
677
|
+
onAction: <A extends Action>(kind: string, handler: ActionHandler<A, Result>) => void;
|
|
678
|
+
/** 移除指定 kind 的 handler(用于显式覆盖场景) */
|
|
679
|
+
removeAction: (kind: string) => boolean;
|
|
680
|
+
close(): void;
|
|
681
|
+
/** 执行一个 Action(CompoundAction 递归展开,有深度限制) */
|
|
682
|
+
perform: (action: Action, invocation?: ActionInvocation) => Promise<Result>;
|
|
683
|
+
private dispatch;
|
|
684
|
+
}
|
|
685
|
+
//#endregion
|
|
686
|
+
//#region ../web/src/router/router.d.ts
|
|
687
|
+
interface RouteMatch {
|
|
688
|
+
intent: RouteIntent;
|
|
689
|
+
action: FlowAction;
|
|
690
|
+
cache?: "public";
|
|
691
|
+
renderMode?: string;
|
|
692
|
+
beforeGuards?: BeforeLoadGuard[];
|
|
693
|
+
afterGuards?: AfterLoadGuard[];
|
|
694
|
+
}
|
|
695
|
+
interface RouteAddOptions {
|
|
696
|
+
cache?: "public";
|
|
697
|
+
renderMode?: string;
|
|
698
|
+
beforeGuards?: BeforeLoadGuard[];
|
|
699
|
+
afterGuards?: AfterLoadGuard[];
|
|
700
|
+
paramCodecs?: Record<string, ParamSchema>;
|
|
701
|
+
queryCodecs?: QuerySchemaMap;
|
|
702
|
+
}
|
|
703
|
+
/** The structured, portable part of a route declaration. */
|
|
704
|
+
interface RouteDefinition$1 {
|
|
705
|
+
readonly pattern: string;
|
|
706
|
+
readonly intentId: string;
|
|
707
|
+
readonly path: PathDescriptor;
|
|
708
|
+
}
|
|
709
|
+
declare class Router {
|
|
710
|
+
private readonly debug?;
|
|
711
|
+
private routes;
|
|
712
|
+
private sealed;
|
|
713
|
+
constructor(debug?: ((message: string) => void) | undefined);
|
|
714
|
+
seal(): this;
|
|
715
|
+
add(pattern: string, intentId: string, options?: RouteAddOptions): this;
|
|
716
|
+
resolve(urlOrPath: string): Promise<RouteMatch | null>;
|
|
717
|
+
hasIntent(intentId: string): boolean;
|
|
718
|
+
getDefinitions(): readonly RouteDefinition$1[];
|
|
719
|
+
reverse(intentId: string, params: Readonly<RouteParams>, queryParams?: Readonly<RouteParams>): string | undefined;
|
|
720
|
+
private parseUrl;
|
|
721
|
+
}
|
|
722
|
+
//#endregion
|
|
723
|
+
//#region ../web/src/prefetched-intents/prefetched-intents.d.ts
|
|
724
|
+
/** 预获取的 Intent-Data 对 */
|
|
725
|
+
interface PrefetchedIntent {
|
|
726
|
+
entryId?: string;
|
|
727
|
+
intent: RouteIntent;
|
|
728
|
+
data: unknown;
|
|
729
|
+
}
|
|
730
|
+
declare class PrefetchedIntents {
|
|
731
|
+
private readonly entryIds;
|
|
732
|
+
private intents;
|
|
733
|
+
private constructor();
|
|
734
|
+
/** 从 PrefetchedIntent 数组创建缓存实例 */
|
|
735
|
+
static fromArray(items: PrefetchedIntent[]): PrefetchedIntents;
|
|
736
|
+
/** 创建空缓存实例 */
|
|
737
|
+
static empty(): PrefetchedIntents;
|
|
738
|
+
/**
|
|
739
|
+
* 获取缓存的 Intent 结果(一次性使用)。
|
|
740
|
+
* 命中后从缓存中删除。
|
|
741
|
+
*/
|
|
742
|
+
get<T>(intent: RouteIntent<T>, entryId?: string): T | undefined;
|
|
743
|
+
/** @internal Navigation-owned staged reads; direct get remains one-shot. */
|
|
744
|
+
stage(): {
|
|
745
|
+
cache: PrefetchedIntents;
|
|
746
|
+
commit(): void;
|
|
747
|
+
};
|
|
748
|
+
/** Read identity metadata without consuming data or bypassing operation policies. */
|
|
749
|
+
entryIdFor(intent: RouteIntent): string | undefined;
|
|
750
|
+
/** 检查缓存中是否有某个 Intent 的数据 */
|
|
751
|
+
has(intent: RouteIntent, entryId?: string): boolean;
|
|
752
|
+
/** 缓存中的条目数 */
|
|
753
|
+
get size(): number;
|
|
754
|
+
}
|
|
755
|
+
//#endregion
|
|
756
|
+
//#region ../web/src/bootstrap/define-routes.d.ts
|
|
757
|
+
/** 渲染模式 */
|
|
758
|
+
type RenderMode = "ssr" | "csr" | "prerender";
|
|
759
|
+
/** 单条路由定义 */
|
|
760
|
+
interface PageRoute<Path extends string = string, P extends ParamsFor<Path> = ParamsFor<Path>, Q extends QuerySchemaMap = QuerySchemaMap> {
|
|
761
|
+
/** URL pattern (如 "/product/:id") */
|
|
762
|
+
path: Path;
|
|
763
|
+
/** path 参数 codec;key 必须是 path 中出现的 :param 名 */
|
|
764
|
+
params?: P;
|
|
765
|
+
/** query 参数 codec;key 自由 */
|
|
766
|
+
query?: Q;
|
|
767
|
+
/** 渲染模式(可选,默认 "ssr") */
|
|
768
|
+
cache?: "public";
|
|
769
|
+
renderMode?: RenderMode;
|
|
770
|
+
/** 路由级 beforeLoad 守卫 */
|
|
771
|
+
beforeLoad?: BeforeLoadGuard[];
|
|
772
|
+
/** 路由级 afterLoad 守卫 */
|
|
773
|
+
afterLoad?: AfterLoadGuard[];
|
|
774
|
+
}
|
|
775
|
+
/** A normalized route associates a URL with its owning page. */
|
|
776
|
+
interface RouteDefinition<Path extends string = string, P extends ParamsFor<Path> = ParamsFor<Path>, Q extends QuerySchemaMap = QuerySchemaMap> extends PageRoute<Path, P, Q> {
|
|
777
|
+
intentId: string;
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* 构造一条强类型路由定义。
|
|
781
|
+
* `params` 的 key 受 `path` 字面量约束——写入 path 中不存在的参数名会编译期报错。
|
|
782
|
+
*
|
|
783
|
+
* @example
|
|
784
|
+
* route("/product/:id", { intentId: "product", params: { id: int() } })
|
|
785
|
+
*/
|
|
786
|
+
declare function route<const Path extends string, const Definition extends Omit<RouteDefinition<Path>, "path">>(path: Path, def: Definition & NoInfer<Record<Exclude<keyof Definition, keyof Omit<RouteDefinition<Path>, "path">>, never>>): Readonly<{
|
|
787
|
+
path: Path;
|
|
788
|
+
} & Definition>;
|
|
789
|
+
//#endregion
|
|
790
|
+
//#region ../web/src/navigation/serialization.d.ts
|
|
791
|
+
/** 序列化叶子 */
|
|
792
|
+
interface SerializedLeaf {
|
|
793
|
+
readonly entryId: string;
|
|
794
|
+
readonly url?: string;
|
|
795
|
+
readonly kind: typeof NAVIGATION_NODE_KINDS.LEAF;
|
|
796
|
+
readonly intent: string;
|
|
797
|
+
readonly params: RouteParams;
|
|
798
|
+
readonly query?: RouteParams;
|
|
799
|
+
}
|
|
800
|
+
/** 序列化栈 */
|
|
801
|
+
interface SerializedStack {
|
|
802
|
+
readonly kind: typeof NAVIGATION_NODE_KINDS.STACK;
|
|
803
|
+
readonly entries: readonly SerializedNavigation[];
|
|
804
|
+
}
|
|
805
|
+
/** 序列化 Tabs */
|
|
806
|
+
interface SerializedTabs {
|
|
807
|
+
readonly kind: typeof NAVIGATION_NODE_KINDS.TABS;
|
|
808
|
+
readonly active: string;
|
|
809
|
+
readonly order: readonly string[];
|
|
810
|
+
readonly branches: Readonly<Record<string, SerializedNavigation>>;
|
|
811
|
+
}
|
|
812
|
+
/** 序列化 Split 列(空内容用 null 表示,JSON 安全) */
|
|
813
|
+
interface SerializedSplitColumn {
|
|
814
|
+
readonly id: string;
|
|
815
|
+
readonly content: SerializedNavigation | null;
|
|
816
|
+
}
|
|
817
|
+
/** 序列化 Split(visibility 缺省时不写该字段,保持紧凑) */
|
|
818
|
+
interface SerializedSplit {
|
|
819
|
+
readonly kind: typeof NAVIGATION_NODE_KINDS.SPLIT;
|
|
820
|
+
readonly columns: readonly SerializedSplitColumn[];
|
|
821
|
+
readonly visibility?: SplitVisibility;
|
|
822
|
+
}
|
|
823
|
+
/** 序列化后的导航树(JSON 安全的可辨识联合) */
|
|
824
|
+
type SerializedNavigation = SerializedLeaf | SerializedStack | SerializedTabs | SerializedSplit;
|
|
825
|
+
/** 把导航树序列化为 JSON 安全的纯对象。 */
|
|
826
|
+
declare function serializeNavigation(tree: NavigationNode): SerializedNavigation;
|
|
827
|
+
/** 导航树的确定性字符串形式(keys 排序);用于稳定缓存键 / 紧凑编码。 */
|
|
828
|
+
declare function serializeNavigationStable(tree: NavigationNode): string;
|
|
829
|
+
/** 从 JSON 安全数据还原导航树;结构畸形抛 NavigationError。 */
|
|
830
|
+
declare function deserializeNavigation(data: unknown): NavigationNode;
|
|
831
|
+
//#endregion
|
|
832
|
+
//#region ../web/src/navigation/codec.d.ts
|
|
833
|
+
interface NavigationRouterLike {
|
|
834
|
+
reverse(intentId: string, params: RouteParams, query?: RouteParams): string | undefined;
|
|
835
|
+
}
|
|
836
|
+
interface NavigationCodec {
|
|
837
|
+
encode(tree: NavigationNode, router: NavigationRouterLike): string;
|
|
838
|
+
decode(url: string, router: NavigationRouterLike): NavigationNode | undefined;
|
|
839
|
+
}
|
|
840
|
+
declare const DEFAULT_NAV_PARAM = "__nav";
|
|
841
|
+
declare function encodeNavigationTreeParam(tree: NavigationNode): string;
|
|
842
|
+
declare function decodeNavigationTreeParam(encoded: string): NavigationNode;
|
|
843
|
+
declare function createActiveLeafCodec(): NavigationCodec;
|
|
844
|
+
interface FullStateCodecOptions {
|
|
845
|
+
readonly param?: string;
|
|
846
|
+
}
|
|
847
|
+
declare function createFullStateCodec(options?: FullStateCodecOptions): NavigationCodec;
|
|
848
|
+
//#endregion
|
|
849
|
+
//#region ../web/src/i18n/messages.d.ts
|
|
850
|
+
interface MessagesLoaderContext {
|
|
851
|
+
readonly runtime: "server" | "browser";
|
|
852
|
+
readonly fetch: typeof globalThis.fetch;
|
|
853
|
+
readonly url: string;
|
|
854
|
+
readonly request?: Request;
|
|
855
|
+
}
|
|
856
|
+
type MessagesLoader = (locale: string, context: MessagesLoaderContext) => TranslationMessages | Promise<TranslationMessages | undefined> | undefined;
|
|
857
|
+
interface ResolveConfiguredMessagesOptions {
|
|
858
|
+
locale?: string;
|
|
859
|
+
loadMessages?: MessagesLoader;
|
|
860
|
+
context?: MessagesLoaderContext;
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Resolve the effective translation source for a locale.
|
|
864
|
+
*/
|
|
865
|
+
declare function resolveConfiguredMessages(options: ResolveConfiguredMessagesOptions): Promise<TranslationMessages | undefined>;
|
|
866
|
+
//#endregion
|
|
867
|
+
//#region ../web/src/application/controller-context.d.ts
|
|
868
|
+
/** Page metadata belongs to the current load, including when several pages share an execution. */
|
|
869
|
+
interface ControllerContext extends ExecutionContext {
|
|
870
|
+
readonly url: string;
|
|
871
|
+
readonly path: string;
|
|
872
|
+
readonly intent: NavigationContext["intent"];
|
|
873
|
+
readonly isServer: boolean;
|
|
874
|
+
getCookie(name: string): string | undefined;
|
|
875
|
+
getHeader(name: string): string | undefined;
|
|
876
|
+
}
|
|
877
|
+
type PageControllerInput<P extends Record<string, unknown> = Record<string, unknown>, Q extends Record<string, unknown> = Record<string, unknown>> = ControllerInput<P, Q, ControllerContext>;
|
|
878
|
+
declare const SERVER_CONTROLLER: unique symbol;
|
|
879
|
+
declare const SERVER_REQUEST = "@finesoft/web/server-request";
|
|
880
|
+
interface ServerRequestState {
|
|
881
|
+
readonly request: Request;
|
|
882
|
+
readonly responseHeaders: Headers;
|
|
883
|
+
readonly remote: boolean;
|
|
884
|
+
/** Incoming request cookies; outgoing response mutations do not change this view. */
|
|
885
|
+
readonly cookies: ReadonlyMap<string, string>;
|
|
886
|
+
}
|
|
887
|
+
declare function controllerContext(execution: ExecutionContext, navigation: Pick<NavigationContext, "url" | "path" | "intent" | "isServer" | "getCookie" | "getHeader">): ControllerContext;
|
|
888
|
+
//#endregion
|
|
889
|
+
//#region ../web/src/application/types.d.ts
|
|
890
|
+
interface PageControllerDefinition {
|
|
891
|
+
readonly id: string;
|
|
892
|
+
readonly create?: () => {
|
|
893
|
+
perform(input: RouteParams, context: ExecutionContext, query: RouteParams): BasePage | Promise<BasePage>;
|
|
894
|
+
};
|
|
895
|
+
readonly handler?: (params: RouteParams, context: ControllerContext, query: RouteParams) => BasePage | Promise<BasePage>;
|
|
896
|
+
readonly policies?: readonly OperationPolicy<RouteParams>[];
|
|
897
|
+
readonly routes?: readonly (string | PageRoute)[];
|
|
898
|
+
}
|
|
899
|
+
interface NavigationTarget {
|
|
900
|
+
readonly url: string;
|
|
901
|
+
readonly match?: RouteMatch;
|
|
902
|
+
readonly target?: LeafNode;
|
|
903
|
+
}
|
|
904
|
+
interface WebAppDefinition<Pages extends readonly PageControllerDefinition[] = readonly PageControllerDefinition[]> {
|
|
905
|
+
readonly id: string;
|
|
906
|
+
readonly app?: AppDefinition;
|
|
907
|
+
readonly pages: Pages;
|
|
908
|
+
/** Opt into retained structured navigation. Without it or a codec, URL navigation replaces the page. */
|
|
909
|
+
readonly navigation?: NavigationNode | ((target: NavigationTarget) => NavigationNode | undefined);
|
|
910
|
+
readonly navigationCodec?: NavigationCodec;
|
|
911
|
+
readonly loadMessages?: MessagesLoader;
|
|
912
|
+
readonly getErrorPage: (status: number, message: string) => BasePage;
|
|
913
|
+
readonly configuration?: WebConfiguration;
|
|
914
|
+
readonly beforeNavigate?: readonly BeforeNavigatePolicy[];
|
|
915
|
+
readonly beforeCommit?: readonly BeforeCommitPolicy[];
|
|
916
|
+
readonly beforeLoad?: readonly BeforeLoadGuard[];
|
|
917
|
+
readonly afterLoad?: readonly AfterLoadGuard[];
|
|
918
|
+
}
|
|
919
|
+
type PageInput<P> = P extends {
|
|
920
|
+
leaf: (...args: infer A) => unknown;
|
|
921
|
+
} ? NonNullable<A[0]> extends RouteParams ? NonNullable<A[0]> : RouteParams : P extends {
|
|
922
|
+
handler: (params: infer Input, ...rest: any[]) => unknown;
|
|
923
|
+
} ? Input extends RouteParams ? Input : RouteParams : RouteParams;
|
|
924
|
+
/** Structural metadata; no runtime registry or cross-package private brand. */
|
|
925
|
+
type AppParams<Definition extends WebAppDefinition> = { [P in Definition["pages"][number] as P["id"]]: PageInput<P>; };
|
|
926
|
+
type AppQueries<Definition extends WebAppDefinition> = { [P in Definition["pages"][number] as P["id"]]: P extends {
|
|
927
|
+
leaf: (...args: infer A) => unknown;
|
|
928
|
+
} ? NonNullable<A[1]> extends {
|
|
929
|
+
query?: infer Q extends RouteParams;
|
|
930
|
+
} ? Q : RouteParams : RouteParams; };
|
|
931
|
+
//#endregion
|
|
932
|
+
//#region ../web/src/application/runtime.d.ts
|
|
933
|
+
interface WebConfiguration {
|
|
934
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
935
|
+
readonly locale?: string;
|
|
936
|
+
readonly platform?: PlatformInfo;
|
|
937
|
+
readonly safeFetch?: SecureFetchOptions;
|
|
938
|
+
readonly logFilter?: LogFilter;
|
|
939
|
+
readonly reportCallback?: ReportCallback;
|
|
940
|
+
readonly eventRecorder?: EventRecorder;
|
|
941
|
+
readonly featureFlags?: Readonly<Record<string, boolean | string | number>>;
|
|
942
|
+
readonly featureFlagsProviders?: readonly FeatureFlagsProvider[];
|
|
943
|
+
/** Keep browser defaults shared; SSR supplies execution for request-local memory storage. */
|
|
944
|
+
readonly storageScope?: "runtime" | "execution";
|
|
945
|
+
}
|
|
946
|
+
interface WebRuntimeOptions<Definition extends WebAppDefinition = WebAppDefinition> extends WebConfiguration {
|
|
947
|
+
readonly definition: Definition;
|
|
948
|
+
readonly runtime?: RuntimeHandle;
|
|
949
|
+
readonly invocation?: Invocation;
|
|
950
|
+
readonly prefetchedIntents?: PrefetchedIntents;
|
|
951
|
+
readonly messages?: TranslationMessages;
|
|
952
|
+
/** Host-resolved attributes used by the default locale provider. */
|
|
953
|
+
readonly localeAttributes?: LocaleAttributes;
|
|
954
|
+
}
|
|
955
|
+
declare function createWebRuntime<Definition extends WebAppDefinition>(input: WebRuntimeOptions<Definition>): {
|
|
956
|
+
definition: Definition;
|
|
957
|
+
router: Router;
|
|
958
|
+
runtime: RuntimeHandle;
|
|
959
|
+
prefetchedIntents: PrefetchedIntents;
|
|
960
|
+
getLocale: (execution?: ExecutionHandle) => Promise<LocaleAttributes | undefined>;
|
|
961
|
+
getTranslator: (execution?: ExecutionHandle) => Promise<Translator | undefined>;
|
|
962
|
+
getLogger: (execution?: ExecutionHandle) => Promise<Logger>;
|
|
963
|
+
createExecution(invocation?: Invocation): ExecutionHandle;
|
|
964
|
+
dispose(): Promise<void>;
|
|
965
|
+
};
|
|
966
|
+
type WebRuntime<Definition extends WebAppDefinition = WebAppDefinition> = ReturnType<typeof createWebRuntime<Definition>>;
|
|
967
|
+
//#endregion
|
|
968
|
+
//#region ../web/src/session/types.d.ts
|
|
969
|
+
/**
|
|
970
|
+
* Session — 会话恢复的核心类型
|
|
971
|
+
*
|
|
972
|
+
* 会话恢复把「用户当时在干什么」(导航位置 + 应用注册的状态切片 + 导航作用域状态)
|
|
973
|
+
* 序列化进一份可版本化、JSON 安全的快照,持久化到可插拔 `Storage`,并在全新加载时重水化。
|
|
974
|
+
* 框架只搬运状态、不解释内容,也不参与 UI —— 应用据恢复出的状态自行重渲染。
|
|
975
|
+
*
|
|
976
|
+
* 两层作用域共同序列化进快照:
|
|
977
|
+
* - **全局切片(`slices`)**:app-wide,键 = `provider.key`,生命周期 = 整个会话。
|
|
978
|
+
* - **导航作用域状态(`scoped`)**:绑定到某个导航条目(`entryKey`),对标 SwiftUI `@State`
|
|
979
|
+
* 的「位置作用域」语义 —— 条目离树即被 prune 丢弃(见 `scoped-state.ts`)。
|
|
980
|
+
*/
|
|
981
|
+
/** Asynchronous session persistence. A missing key resolves undefined; failures reject. */
|
|
982
|
+
interface AsyncStorage {
|
|
983
|
+
get(key: string): Promise<string | undefined>;
|
|
984
|
+
set(key: string, value: string): Promise<void>;
|
|
985
|
+
delete(key: string): Promise<void>;
|
|
986
|
+
}
|
|
987
|
+
declare class StorageUnavailableError extends Error {
|
|
988
|
+
constructor();
|
|
989
|
+
}
|
|
990
|
+
type SessionFailure = {
|
|
991
|
+
readonly status: "failed";
|
|
992
|
+
readonly cause: unknown;
|
|
993
|
+
} | {
|
|
994
|
+
readonly status: "unavailable";
|
|
995
|
+
} | {
|
|
996
|
+
readonly status: "closed";
|
|
997
|
+
};
|
|
998
|
+
type SessionWriteResult = {
|
|
999
|
+
readonly status: "saved" | "cleared";
|
|
1000
|
+
} | SessionFailure;
|
|
1001
|
+
type SessionLoadResult = {
|
|
1002
|
+
readonly status: "loaded";
|
|
1003
|
+
readonly snapshot: SessionSnapshot;
|
|
1004
|
+
} | {
|
|
1005
|
+
readonly status: "missing" | "invalid" | "expired";
|
|
1006
|
+
} | SessionFailure;
|
|
1007
|
+
type SessionRestoreResult = {
|
|
1008
|
+
readonly status: "restored";
|
|
1009
|
+
} | {
|
|
1010
|
+
readonly status: "partial";
|
|
1011
|
+
readonly discarded: readonly string[];
|
|
1012
|
+
} | {
|
|
1013
|
+
readonly status: "skipped";
|
|
1014
|
+
} | Exclude<SessionLoadResult, {
|
|
1015
|
+
status: "loaded";
|
|
1016
|
+
}>;
|
|
1017
|
+
interface SessionSlice {
|
|
1018
|
+
readonly version: number;
|
|
1019
|
+
readonly data: unknown;
|
|
1020
|
+
}
|
|
1021
|
+
/** 会话快照在 Storage 中的默认键。 */
|
|
1022
|
+
declare const SESSION_DEFAULT_KEY = "__finesoft_session__";
|
|
1023
|
+
/** 会话快照的默认版本号;解码时不匹配即整份丢弃。 */
|
|
1024
|
+
declare const SESSION_DEFAULT_VERSION = 2;
|
|
1025
|
+
/**
|
|
1026
|
+
* 会话快照:用户「当时在干什么」的可序列化捕获。
|
|
1027
|
+
*
|
|
1028
|
+
* `navigation` 始终是结构化 `SerializedNavigation`;版本 2 不接受早期 URL-only 导航载荷。
|
|
1029
|
+
*/
|
|
1030
|
+
interface SessionSnapshot {
|
|
1031
|
+
/** 快照版本;解码时与期望版本不符即丢弃。 */
|
|
1032
|
+
readonly version: number;
|
|
1033
|
+
/** 导航位置:统一为结构化导航树;缺省则不恢复导航。 */
|
|
1034
|
+
readonly navigation?: SerializedNavigation;
|
|
1035
|
+
/**
|
|
1036
|
+
* 该快照导航位置的可比 URL(捕获时刻与 history 同步的浏览器 URL),供恢复门控做精确匹配。
|
|
1037
|
+
*
|
|
1038
|
+
* 适配器在 `capture` 时记录浏览器 URL。缺省时恢复门控只在根入口放行。
|
|
1039
|
+
*/
|
|
1040
|
+
readonly url?: string;
|
|
1041
|
+
/** 全局切片(app-wide):`provider.key` → `{ version, data }`。 */
|
|
1042
|
+
readonly slices: Readonly<Record<string, unknown>>;
|
|
1043
|
+
/** 导航作用域状态:`entryKey` → 该导航条目的状态袋;条目离树即被 prune 丢弃。 */
|
|
1044
|
+
readonly scoped: Readonly<Record<string, unknown>>;
|
|
1045
|
+
/** 捕获时刻(epoch ms);用于 `maxAgeMs` 过期判断。 */
|
|
1046
|
+
readonly capturedAt: number;
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* 全局状态切片 Provider。
|
|
1050
|
+
*
|
|
1051
|
+
* capture 同步且 JSON 安全;decode 校验当前 schema,migration 按切片独立执行。应用控制捕获什么
|
|
1052
|
+
* (敏感字段在 `capture()` 中自行排除)。
|
|
1053
|
+
*/
|
|
1054
|
+
interface SessionStateProvider<T = unknown> {
|
|
1055
|
+
/** 切片唯一键(快照里 `slices` 的 key)。 */
|
|
1056
|
+
readonly key: string;
|
|
1057
|
+
readonly version: number;
|
|
1058
|
+
/** Validate current-version data. Throw to discard this slice. */
|
|
1059
|
+
decode(data: unknown): T;
|
|
1060
|
+
/** Migrate old data; decoder always validates the result. */
|
|
1061
|
+
migrate?(data: unknown, fromVersion: number): unknown;
|
|
1062
|
+
/** 捕获当前切片状态,必须返回 JSON 安全的同步值。 */
|
|
1063
|
+
capture(): T;
|
|
1064
|
+
/** 用持久化的切片数据恢复(应用自行 setState / 填表单 / 滚动)。 */
|
|
1065
|
+
restore(data: T): void | Promise<void>;
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* 导航作用域状态:`entryKey` → 状态袋;条目离树由框架 prune 丢弃(见 `scoped-state.ts`)。
|
|
1069
|
+
*/
|
|
1070
|
+
interface NavigationScopedState {
|
|
1071
|
+
/** 读取某条目的状态袋(不存在返回 `undefined`,`unknown` 已含此情形)。 */
|
|
1072
|
+
get(entryKey: string): unknown;
|
|
1073
|
+
/** 写入某条目的状态袋。 */
|
|
1074
|
+
set(entryKey: string, data: unknown): void;
|
|
1075
|
+
/** 删除某条目的状态袋。 */
|
|
1076
|
+
delete(entryKey: string): void;
|
|
1077
|
+
/** 仅保留 `presentKeys` 中的键,丢弃其余(导航提交后由 bridge 调用)。 */
|
|
1078
|
+
prune(presentKeys: Iterable<string>): void;
|
|
1079
|
+
/** 当前持有状态的全部条目键。 */
|
|
1080
|
+
keys(): readonly string[];
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* 持久化导航端口:WebSession 直接实现,SessionStore 只依赖捕获和恢复契约。
|
|
1084
|
+
*
|
|
1085
|
+
* 所有页面形态通过同一结构化树恢复。
|
|
1086
|
+
*/
|
|
1087
|
+
interface SessionNavigation {
|
|
1088
|
+
/** 捕获当前导航位置。 */
|
|
1089
|
+
captureNavigation(): SessionSnapshot["navigation"] | undefined;
|
|
1090
|
+
/** 应用恢复的导航位置。 */
|
|
1091
|
+
restoreNavigation(navigation: SessionSnapshot["navigation"]): void | Promise<void>;
|
|
1092
|
+
/**
|
|
1093
|
+
* 可选:计算当前导航位置的可比 URL,写入 `SessionSnapshot.url` 供恢复门控精确匹配。
|
|
1094
|
+
*
|
|
1095
|
+
* 浏览器侧适配器返回当时的 `location`(pushState 后与导航树同步);返回 `undefined`
|
|
1096
|
+
* 或不实现 = 快照不带 `url`,门控回退旧策略(见 `defaultShouldRestore`)。
|
|
1097
|
+
*/
|
|
1098
|
+
captureUrl?(): string | undefined;
|
|
1099
|
+
/** 树中**存在**的全部条目身份键(用于 scoped prune;「存在」非「可见」)。 */
|
|
1100
|
+
presentKeys(): Iterable<string>;
|
|
1101
|
+
}
|
|
1102
|
+
/** 会话错误上下文:标记出错所处阶段,供 `onError` 上报。 */
|
|
1103
|
+
interface SessionErrorContext {
|
|
1104
|
+
readonly phase: "capture" | "restore" | "persist" | "load" | "clear";
|
|
1105
|
+
readonly code?: "slice-failed" | "slice-incompatible" | "navigation-invalid" | "storage-failed" | "storage-unavailable";
|
|
1106
|
+
readonly key?: string;
|
|
1107
|
+
}
|
|
1108
|
+
/** `createSessionStore` 选项。 */
|
|
1109
|
+
interface SessionStoreOptions {
|
|
1110
|
+
/** 异步持久化存储(独立于同步环境 Storage)。 */
|
|
1111
|
+
readonly storage: AsyncStorage;
|
|
1112
|
+
/** 快照键;默认 `SESSION_DEFAULT_KEY`。 */
|
|
1113
|
+
readonly key?: string;
|
|
1114
|
+
/** 快照版本;默认 `SESSION_DEFAULT_VERSION`,不符即丢弃。 */
|
|
1115
|
+
readonly version?: number;
|
|
1116
|
+
/** 快照最大存活时长(ms);省略 = 不过期。 */
|
|
1117
|
+
readonly maxAgeMs?: number;
|
|
1118
|
+
/** 导航端口;省略 = 不恢复导航。 */
|
|
1119
|
+
readonly navigation?: SessionNavigation;
|
|
1120
|
+
/** 注入时钟(测试 / SSR 安全);默认 `() => Date.now()`。 */
|
|
1121
|
+
readonly now?: () => number;
|
|
1122
|
+
/** 错误回调;默认 no-op,仅接收安全错误码与阶段/key,不接收私有异常。 */
|
|
1123
|
+
readonly onError?: (error: unknown, ctx: SessionErrorContext) => void;
|
|
1124
|
+
}
|
|
1125
|
+
/** 会话编排器:组装 / 落盘 / 读取 / 恢复快照,并持有导航作用域状态。 */
|
|
1126
|
+
interface SessionStore {
|
|
1127
|
+
/** 注册全局切片 provider;返回反注册函数。 */
|
|
1128
|
+
register<T>(provider: SessionStateProvider<T>): () => void;
|
|
1129
|
+
/** 导航作用域状态读写 + prune。 */
|
|
1130
|
+
readonly scope: NavigationScopedState;
|
|
1131
|
+
/** 同步复制 nav/slices/scoped 的 JSON 值,独立于可变来源;不冻结来源、不落盘。 */
|
|
1132
|
+
capture(): SessionSnapshot;
|
|
1133
|
+
/** 显式快照在调用时复制/编码;省略参数则在排队写入开始时捕获最新状态。 */
|
|
1134
|
+
persist(snapshot?: SessionSnapshot): Promise<SessionWriteResult>;
|
|
1135
|
+
/** 读取并校验,返回 loaded/missing/invalid/expired/failed/unavailable/closed。 */
|
|
1136
|
+
load(): Promise<SessionLoadResult>;
|
|
1137
|
+
/** 恢复:应用 nav + 回填 scoped + 派发各 slice 给对应 provider(省略则先 `load`)。 */
|
|
1138
|
+
restore(snapshot?: SessionSnapshot): Promise<SessionRestoreResult>;
|
|
1139
|
+
/** 清除持久化快照。 */
|
|
1140
|
+
clear(): Promise<SessionWriteResult>;
|
|
1141
|
+
/** 手动逃生口 = `capture` + `persist`。 */
|
|
1142
|
+
save(): Promise<SessionWriteResult>;
|
|
1143
|
+
/** Stop new work and await all registered operations. */
|
|
1144
|
+
dispose(): Promise<void>;
|
|
1145
|
+
}
|
|
1146
|
+
/** 会话错误:序列化 / 编排过程中需要显式标识的错误类型。 */
|
|
1147
|
+
declare class SessionError extends Error {
|
|
1148
|
+
constructor(message: string);
|
|
1149
|
+
}
|
|
1150
|
+
//#endregion
|
|
1151
|
+
//#region ../web/src/application/view.d.ts
|
|
1152
|
+
interface NavigationSummary {
|
|
1153
|
+
readonly canGoBack: boolean;
|
|
1154
|
+
readonly activeEntryId?: string;
|
|
1155
|
+
readonly tabs?: {
|
|
1156
|
+
readonly active: string;
|
|
1157
|
+
readonly order: readonly string[];
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
interface ViewEntry extends ResolvedDestination {
|
|
1161
|
+
readonly visible: boolean;
|
|
1162
|
+
}
|
|
1163
|
+
interface AppSnapshot extends NavigationSnapshot {
|
|
1164
|
+
readonly revision: number;
|
|
1165
|
+
readonly entries: readonly ViewEntry[];
|
|
1166
|
+
readonly navigation: NavigationSummary;
|
|
1167
|
+
}
|
|
1168
|
+
/** Native roots consume the same view during SSR and in the browser. */
|
|
1169
|
+
interface WebAppView<Definition extends WebAppDefinition = WebAppDefinition> extends Pick<ActionDispatcher<NavigationSnapshot>, "onAction" | "removeAction"> {
|
|
1170
|
+
perform(this: void, action: Action<AppParams<Definition>, AppQueries<Definition>>, invocation?: ActionInvocation): Promise<NavigationSnapshot>;
|
|
1171
|
+
readonly runtime: RuntimeHandle;
|
|
1172
|
+
readonly session?: SessionStore;
|
|
1173
|
+
readonly locale?: LocaleAttributes;
|
|
1174
|
+
readonly translator?: Translator;
|
|
1175
|
+
getSnapshot(this: void): AppSnapshot;
|
|
1176
|
+
subscribe(this: void, listener: () => void): () => void;
|
|
1177
|
+
/** Called by a native post-commit hook; it never waits for session restore. */
|
|
1178
|
+
commit(this: void, revision: number): void;
|
|
1179
|
+
}
|
|
1180
|
+
interface ViewProps<P extends BasePage = BasePage, Definition extends WebAppDefinition = WebAppDefinition> {
|
|
1181
|
+
readonly page: P;
|
|
1182
|
+
readonly app: WebAppView<Definition>;
|
|
1183
|
+
readonly entry: ViewEntry;
|
|
1184
|
+
}
|
|
1185
|
+
//#endregion
|
|
1186
|
+
//#region ../web/src/application/session.d.ts
|
|
1187
|
+
/** Host metadata for a destination; execution owns dependency scope and cancellation. */
|
|
1188
|
+
interface NavigationContextInput {
|
|
1189
|
+
readonly execution: ExecutionContext;
|
|
1190
|
+
readonly intent: string;
|
|
1191
|
+
readonly params: RouteParams;
|
|
1192
|
+
readonly query?: RouteParams;
|
|
1193
|
+
readonly signal?: AbortSignal;
|
|
1194
|
+
readonly url?: string;
|
|
1195
|
+
}
|
|
1196
|
+
/** Admission runs once per transaction; page redirects do not repeat it. */
|
|
1197
|
+
type BeforeNavigateResult = NextResult | DenyResult | RedirectResult;
|
|
1198
|
+
/** The final candidate can only be accepted or denied, never redirected after inspection. */
|
|
1199
|
+
type BeforeCommitResult = Exclude<BeforeNavigateResult, {
|
|
1200
|
+
kind: "redirect";
|
|
1201
|
+
}>;
|
|
1202
|
+
interface NavigationTransactionContext {
|
|
1203
|
+
readonly from: NavigationSnapshot;
|
|
1204
|
+
readonly tree: NavigationNode;
|
|
1205
|
+
readonly transitionId: string;
|
|
1206
|
+
readonly execution: ExecutionContext;
|
|
1207
|
+
readonly signal: AbortSignal;
|
|
1208
|
+
readonly isServer: boolean;
|
|
1209
|
+
}
|
|
1210
|
+
interface NavigationCommitContext extends NavigationTransactionContext {
|
|
1211
|
+
readonly candidate: NavigationSnapshot;
|
|
1212
|
+
}
|
|
1213
|
+
type BeforeNavigatePolicy = (context: NavigationTransactionContext) => BeforeNavigateResult | Promise<BeforeNavigateResult>;
|
|
1214
|
+
type BeforeCommitPolicy = (context: NavigationCommitContext) => BeforeCommitResult | Promise<BeforeCommitResult>;
|
|
1215
|
+
interface WebSessionOptions<Definition extends WebAppDefinition = WebAppDefinition> {
|
|
1216
|
+
readonly commit?: (revision: number) => void;
|
|
1217
|
+
readonly session?: () => SessionStore | undefined;
|
|
1218
|
+
readonly captureUrl?: () => string | undefined;
|
|
1219
|
+
readonly beforeNavigate?: readonly BeforeNavigatePolicy[];
|
|
1220
|
+
readonly beforeCommit?: readonly BeforeCommitPolicy[];
|
|
1221
|
+
readonly web: WebRuntime<Definition>;
|
|
1222
|
+
readonly execution?: ExecutionHandle;
|
|
1223
|
+
readonly viewReady?: (snapshot: NavigationSnapshot, signal?: AbortSignal) => void | Promise<void>;
|
|
1224
|
+
/** 初始导航树;单 LeafNode 表示扁平单页。 */
|
|
1225
|
+
readonly initial: NavigationNode;
|
|
1226
|
+
/** Host-specific guard context, including request cookies and headers. */
|
|
1227
|
+
readonly createContext?: (input: NavigationContextInput) => NavigationContext;
|
|
1228
|
+
/**
|
|
1229
|
+
* 是否运行在服务端——仅用于未配置 `createContext` 时的最小上下文,
|
|
1230
|
+
* 决定该上下文的 `isServer` 字段。缺省为 true;浏览器 host 显式传 false。
|
|
1231
|
+
* 应用若已通过 `createContext` 提供完整上下文,此项不生效。
|
|
1232
|
+
*/
|
|
1233
|
+
readonly isServer?: boolean;
|
|
1234
|
+
/** 目标级 beforeLoad 守卫(在全局/路由守卫之外,由控制器对每个可见目标执行)。 */
|
|
1235
|
+
readonly beforeLoad?: readonly BeforeLoadGuard[];
|
|
1236
|
+
/** 目标级 afterLoad 守卫。 */
|
|
1237
|
+
readonly afterLoad?: readonly AfterLoadGuard[];
|
|
1238
|
+
/** Override the application's error page for this session. */
|
|
1239
|
+
readonly getErrorPage?: (status: number, message: string) => BasePage;
|
|
1240
|
+
/**
|
|
1241
|
+
* Called after the redirecting execution finishes. Return a tree to follow within
|
|
1242
|
+
* this same queued operation (at most five follows), preserving cancellation and
|
|
1243
|
+
* history mode. Return void to report an HTTP redirect or finish an external handoff.
|
|
1244
|
+
* Do not call queued controller operations from this callback.
|
|
1245
|
+
*/
|
|
1246
|
+
readonly onRedirect?: (redirect: {
|
|
1247
|
+
url: string;
|
|
1248
|
+
status: number;
|
|
1249
|
+
}, candidate: NavigationSnapshot) => void | NavigationNode | Promise<void | NavigationNode>;
|
|
1250
|
+
}
|
|
1251
|
+
/** Shared navigation, native view and persistence owner. */
|
|
1252
|
+
interface WebSession<Definition extends WebAppDefinition = WebAppDefinition> extends WebAppView<Definition>, SessionNavigation {
|
|
1253
|
+
/** Initial host presentation; rejected candidates are sanitized without committing them. */
|
|
1254
|
+
start(this: void, invocation?: ActionInvocation): Promise<AppSnapshot>;
|
|
1255
|
+
/** 当前导航树。 */
|
|
1256
|
+
getTree(this: void): NavigationNode;
|
|
1257
|
+
onCommit(this: void, listener: (snapshot: AppSnapshot, previous: AppSnapshot) => void): () => void;
|
|
1258
|
+
/** 当前快照(树 + 已解析的可见目标)。 */
|
|
1259
|
+
getSnapshot(this: void): AppSnapshot;
|
|
1260
|
+
cancel(this: void, invocation?: ActionInvocation): void;
|
|
1261
|
+
dispose(this: void): Promise<void>;
|
|
1262
|
+
/**
|
|
1263
|
+
* 清除页面缓存:给 `entryId` 清单个,
|
|
1264
|
+
* 不传清全部。仅清缓存、不触发重解析——该条目下次被解析时重新 dispatch。
|
|
1265
|
+
*/
|
|
1266
|
+
invalidate(this: void, entryKey?: string): void;
|
|
1267
|
+
/** 订阅快照变更;返回取消订阅函数。 */
|
|
1268
|
+
subscribe(this: void, listener: (snapshot: AppSnapshot) => void): () => void;
|
|
1269
|
+
}
|
|
1270
|
+
declare function createWebSession<Definition extends WebAppDefinition>(options: WebSessionOptions<Definition>): WebSession<Definition>;
|
|
1271
|
+
/** State is already committed; observers and hosts must not report a pre-commit rejection. */
|
|
1272
|
+
declare class NavigationCommitError extends Error {
|
|
1273
|
+
readonly snapshot: NavigationSnapshot;
|
|
1274
|
+
readonly committed = true;
|
|
1275
|
+
constructor(snapshot: NavigationSnapshot, causes: unknown[]);
|
|
1276
|
+
}
|
|
1277
|
+
//#endregion
|
|
1278
|
+
export { createActiveLeafCodec as $, list as $n, MiddlewareResult as $t, SessionStore as A, PlatformInfo as An, isCompoundAction as At, ControllerContext as B, ConsoleLoggerFactory as Bn, NavigationPath as Bt, SessionFailure as C, Storage as Cn, ACTION_KINDS as Ct, SessionSlice as D, LocaleInfo as Dn, ExternalUrlAction as Dt, SessionRestoreResult as E, LocaleAttributes as En, CompoundAction as Et, WebRuntime as F, ReportCallback as Fn, LeafNode as Ft, controllerContext as G, InferParams as Gn, SplitColumn as Gt, SERVER_CONTROLLER as H, Logger as Hn, NavigationSnapshot as Ht, WebRuntimeOptions as I, ReportingLogger as In, NAVIGATION_NODE_KINDS as It, resolveConfiguredMessages as J, QuerySchemaMap as Jn, StackNode as Jt, MessagesLoader as K, InferQuery as Kn, SplitNode as Kt, createWebRuntime as L, ReportingLoggerFactory as Ln, NavigationError as Lt, SessionWriteResult as M, BaseController as Mn, isFlowAction as Mt, StorageUnavailableError as N, ControllerFailure as Nn, makeExternalUrlAction as Nt, SessionSnapshot as O, TextDirection as On, FlowAction as Ot, WebConfiguration as P, ControllerInput as Pn, makeFlowAction as Pt, NavigationRouterLike as Q, isMultiValueSchema as Qn, DenyResult as Qt, PageControllerDefinition as R, ReportingLoggerOptions as Rn, NavigationNode as Rt, SessionErrorContext as S, FeatureFlagsProvider as Sn, ActionHandler as St, SessionNavigation as T, resolveMessages as Tn, ActionInvocation as Tt, SERVER_REQUEST as U, LoggerFactory as Un, ResolvedDestination as Ut, PageControllerInput as V, LogFilter as Vn, NavigationPathStep as Vt, ServerRequestState as W, ExtractParamNames as Wn, SPLIT_VISIBILITIES as Wt, FullStateCodecOptions as X, ListOptions as Xn, AfterLoadGuard as Xt, DEFAULT_NAV_PARAM as Y, StripOptional as Yn, TabsNode as Yt, NavigationCodec as Z, MultiValueSchema as Zn, BeforeLoadGuard as Zt, AsyncStorage as _, RouteInput as _n, PrefetchedIntents as _t, NavigationCommitContext as a, deny as an, SerializedSplit as at, SESSION_DEFAULT_VERSION as b, DEP_KEYS as bn, Router as bt, NavigationTransactionContext as c, rewrite as cn, SerializedTabs as ct, createWebSession as d, FINESOFT_PUBLIC as dn, serializeNavigationStable as dt, NavigationContext as en, Intent as er, createFullStateCodec as et, AppSnapshot as f, PublicProjection as fn, PageRoute as ft, WebAppView as g, markPublic as gn, PrefetchedIntent as gt, ViewProps as h, isPublicMarked as hn, route as ht, BeforeNavigateResult as i, RewriteResult as in, SerializedNavigation as it, SessionStoreOptions as j, detectPlatform as jn, isExternalUrlAction as jt, SessionStateProvider as k, Translator as kn, TreeAction as kt, WebSession as l, BASE_PAGE_FIELDS as ln, deserializeNavigation as lt, ViewEntry as m, getPublicFields as mn, RouteDefinition as mt, BeforeCommitResult as n, PostLoadContext as nn, encodeNavigationTreeParam as nt, NavigationCommitError as o, next as on, SerializedSplitColumn as ot, NavigationSummary as p, PublicValueCodec as pn, RenderMode as pt, MessagesLoaderContext as q, ParamsFor as qn, SplitVisibility as qt, BeforeNavigatePolicy as r, RedirectResult as rn, SerializedLeaf as rt, NavigationContextInput as s, redirect as sn, SerializedStack as st, BeforeCommitPolicy as t, NextResult as tn, decodeNavigationTreeParam as tt, WebSessionOptions as u, BasePage as un, serializeNavigation as ut, NavigationScopedState as v, RouteIntent as vn, RouteAddOptions as vt, SessionLoadResult as w, TranslationMessages as wn, Action as wt, SessionError as x, FeatureFlags as xn, ActionDispatcher as xt, SESSION_DEFAULT_KEY as y, RouteParams as yn, RouteMatch as yt, WebAppDefinition as z, ConsoleLogger as zn, NavigationNodeKind as zt };
|
|
1279
|
+
//# sourceMappingURL=session-DnB4ZC3x.d.mts.map
|