@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.
Files changed (128) hide show
  1. package/README.md +4 -4
  2. package/dist/Outlet.svelte +39 -0
  3. package/dist/Outlet.svelte.d.ts +7 -0
  4. package/dist/browser-DIU6Sxl3.mjs +1237 -0
  5. package/dist/browser-kFMjlLGT.d.mts +262 -0
  6. package/dist/browser.d.mts +7 -2
  7. package/dist/browser.mjs +9 -1
  8. package/dist/controller-types-CgmJ6-le.d.mts +16 -0
  9. package/dist/cookies-Bpf9VayB.d.mts +779 -0
  10. package/dist/fetch-policy-BHT8RtrL.mjs +82 -0
  11. package/dist/host-guard-DDWxLpFL.mjs +222 -0
  12. package/dist/http-B6CJqDyf.d.mts +46 -0
  13. package/dist/http-CaxrMD1A.d.mts +1 -0
  14. package/dist/http-D70PL72H.mjs +257 -0
  15. package/dist/http.d.mts +3 -0
  16. package/dist/http.mjs +2 -0
  17. package/dist/index-node.d.mts +15 -0
  18. package/dist/index-node.mjs +15 -0
  19. package/dist/index.d.mts +48 -697
  20. package/dist/index.mjs +44 -261
  21. package/dist/load-node.d.mts +5 -0
  22. package/dist/load-node.mjs +10 -0
  23. package/dist/load-portable.d.mts +5 -0
  24. package/dist/load-portable.mjs +8 -0
  25. package/dist/lru-map-BKoUAySU.mjs +50 -0
  26. package/dist/messages-CAt2QdGr.mjs +140 -0
  27. package/dist/native-contract-DuR25hYB.d.mts +14 -0
  28. package/dist/native-contract.d.mts +2 -0
  29. package/dist/native-contract.mjs +1 -0
  30. package/dist/node-D9hB4dsz.d.mts +35 -0
  31. package/dist/node.d.mts +2 -0
  32. package/dist/node.mjs +59 -0
  33. package/dist/path-CGFl2w7D.mjs +113 -0
  34. package/dist/path-CXT6xGPO.d.mts +261 -0
  35. package/dist/portable-CaxrMD1A.d.mts +1 -0
  36. package/dist/portable.d.mts +11 -0
  37. package/dist/portable.mjs +12 -0
  38. package/dist/proxy-1SphZ7x7.mjs +436 -0
  39. package/dist/proxy-2dSWO-Xw.d.mts +53 -0
  40. package/dist/public-types-BcJM-AYc.mjs +835 -0
  41. package/dist/react-DhwBRw01.d.mts +16 -0
  42. package/dist/react.d.mts +3 -0
  43. package/dist/react.mjs +29 -0
  44. package/dist/rolldown-runtime-B4iAMlE-.mjs +35 -0
  45. package/dist/secure-fetch-Xlht2jd7.d.mts +30 -0
  46. package/dist/server-controller-proxy-BkVuVWVD.d.mts +38 -0
  47. package/dist/session-DnB4ZC3x.d.mts +1279 -0
  48. package/dist/src-Ftl_0rhu.mjs +28 -0
  49. package/dist/src-qwx7Vw8g.mjs +3807 -0
  50. package/dist/ssr-BEUNDvbj.d.mts +210 -0
  51. package/dist/ssr-C8xnYXoY.mjs +357 -0
  52. package/dist/ssr.d.mts +3 -0
  53. package/dist/ssr.mjs +3 -0
  54. package/dist/svelte-Dr5to3SE.d.mts +16 -0
  55. package/dist/svelte.d.mts +3 -0
  56. package/dist/svelte.mjs +13 -0
  57. package/dist/typegen-C-WeJCtf.d.mts +12 -0
  58. package/dist/typegen-cli.d.mts +1 -0
  59. package/dist/typegen-cli.mjs +11 -0
  60. package/dist/typegen.d.mts +3 -0
  61. package/dist/typegen.mjs +2 -0
  62. package/dist/types-BuaZHRG7.mjs +402 -0
  63. package/dist/undici-CPfL25Hr.mjs +22262 -0
  64. package/dist/vite-Cj4SPA8D.d.mts +277 -0
  65. package/dist/vite.d.mts +4 -0
  66. package/dist/vite.mjs +2354 -0
  67. package/dist/vue-DGmzuKho.d.mts +32 -0
  68. package/dist/vue.d.mts +3 -0
  69. package/dist/vue.mjs +56 -0
  70. package/dist/web.d.mts +6 -0
  71. package/dist/web.mjs +8 -0
  72. package/dist/worker.d.mts +2 -0
  73. package/dist/worker.mjs +2 -0
  74. package/docs/01-getting-started.md +67 -199
  75. package/docs/02-routing-and-controllers.md +163 -241
  76. package/docs/03-middleware.md +10 -212
  77. package/docs/04-rendering-and-hydration.md +6 -333
  78. package/docs/05-i18n.md +7 -237
  79. package/docs/06-http-client.md +20 -263
  80. package/docs/07-di-container.md +23 -257
  81. package/docs/08-observability.md +6 -286
  82. package/docs/09-server-and-deployment.md +59 -219
  83. package/docs/10-features-platform-pwa.md +7 -231
  84. package/docs/11-navigation.md +143 -288
  85. package/docs/12-session-restoration.md +6 -214
  86. package/docs/README.md +7 -7
  87. package/docs/advanced/custom-action-handler.md +29 -229
  88. package/docs/advanced/custom-adapter.md +7 -259
  89. package/docs/advanced/custom-event-recorder.md +7 -312
  90. package/docs/advanced/inline-proxy-codegen.md +6 -185
  91. package/docs/advanced/multi-tenant-scopes.md +10 -323
  92. package/docs/engineering/ci-release-flow.md +35 -222
  93. package/docs/engineering/project-structure.md +34 -277
  94. package/docs/engineering/testing.md +8 -310
  95. package/docs/pitfalls/container-scope-leak.md +2 -214
  96. package/docs/pitfalls/i18n-bundle-size.md +6 -176
  97. package/docs/pitfalls/proxy-binary-payloads.md +8 -12
  98. package/docs/pitfalls/ssr-hydration-mismatch.md +4 -160
  99. package/docs/pitfalls/ssr-vs-csr-globals.md +9 -170
  100. package/docs/zh/01-getting-started.md +67 -199
  101. package/docs/zh/02-routing-and-controllers.md +166 -244
  102. package/docs/zh/03-middleware.md +10 -212
  103. package/docs/zh/04-rendering-and-hydration.md +6 -333
  104. package/docs/zh/05-i18n.md +7 -237
  105. package/docs/zh/06-http-client.md +20 -263
  106. package/docs/zh/07-di-container.md +23 -257
  107. package/docs/zh/08-observability.md +6 -283
  108. package/docs/zh/09-server-and-deployment.md +59 -219
  109. package/docs/zh/10-features-platform-pwa.md +7 -231
  110. package/docs/zh/11-navigation.md +130 -290
  111. package/docs/zh/12-session-restoration.md +6 -214
  112. package/docs/zh/README.md +4 -4
  113. package/docs/zh/advanced/custom-action-handler.md +29 -229
  114. package/docs/zh/advanced/custom-adapter.md +7 -259
  115. package/docs/zh/advanced/custom-event-recorder.md +7 -312
  116. package/docs/zh/advanced/inline-proxy-codegen.md +6 -185
  117. package/docs/zh/advanced/multi-tenant-scopes.md +10 -323
  118. package/docs/zh/engineering/ci-release-flow.md +35 -222
  119. package/docs/zh/engineering/project-structure.md +34 -277
  120. package/docs/zh/engineering/testing.md +8 -310
  121. package/docs/zh/pitfalls/container-scope-leak.md +2 -214
  122. package/docs/zh/pitfalls/i18n-bundle-size.md +6 -176
  123. package/docs/zh/pitfalls/proxy-binary-payloads.md +8 -12
  124. package/docs/zh/pitfalls/ssr-hydration-mismatch.md +4 -160
  125. package/docs/zh/pitfalls/ssr-vs-csr-globals.md +9 -170
  126. package/package.json +118 -20
  127. package/dist/browser-BHhVWXik.mjs +0 -2
  128. package/dist/browser-BV2BBXm7.d.mts +0 -2811
@@ -0,0 +1,402 @@
1
+ import { Yt as isMultiValueSchema, zt as secureFetch } from "./src-qwx7Vw8g.mjs";
2
+ import { i as HttpError } from "./host-guard-DDWxLpFL.mjs";
3
+ //#region ../core/src/schema/standard.ts
4
+ /** 工厂:从一个 validate 函数构造实现了 ~standard 的 codec */
5
+ function makeSchema(validate) {
6
+ return { "~standard": {
7
+ version: 1,
8
+ vendor: "finesoft",
9
+ validate
10
+ } };
11
+ }
12
+ //#endregion
13
+ //#region ../core/src/schema/primitives.ts
14
+ const fail = (message) => ({ issues: [{ message }] });
15
+ function str(opts = {}) {
16
+ return makeSchema((value) => {
17
+ if (typeof value !== "string") return fail("expected a string");
18
+ if (opts.minLength !== void 0 && value.length < opts.minLength) return fail(`must be at least ${opts.minLength} characters`);
19
+ if (opts.maxLength !== void 0 && value.length > opts.maxLength) return fail(`must be at most ${opts.maxLength} characters`);
20
+ if (opts.pattern && !opts.pattern.test(value)) return fail(`must match ${opts.pattern}`);
21
+ return { value };
22
+ });
23
+ }
24
+ function inRange(n, opts) {
25
+ if (opts.min !== void 0 && n < opts.min) return fail(`must be >= ${opts.min}`);
26
+ if (opts.max !== void 0 && n > opts.max) return fail(`must be <= ${opts.max}`);
27
+ return null;
28
+ }
29
+ function int(opts = {}) {
30
+ return makeSchema((value) => {
31
+ if (typeof value !== "string" || !/^-?\d+$/.test(value)) return fail(`"${String(value)}" is not an integer`);
32
+ const n = Number(value);
33
+ return inRange(n, opts) ?? { value: n };
34
+ });
35
+ }
36
+ function num(opts = {}) {
37
+ return makeSchema((value) => {
38
+ if (typeof value !== "string" || !/^-?\d+(\.\d+)?$/.test(value)) return fail(`"${String(value)}" is not a number`);
39
+ const n = Number(value);
40
+ return inRange(n, opts) ?? { value: n };
41
+ });
42
+ }
43
+ const TRUE = /* @__PURE__ */ new Set(["true", "1"]);
44
+ const FALSE = /* @__PURE__ */ new Set(["false", "0"]);
45
+ function bool() {
46
+ return makeSchema((value) => {
47
+ if (typeof value === "string" && TRUE.has(value)) return { value: true };
48
+ if (typeof value === "string" && FALSE.has(value)) return { value: false };
49
+ return fail(`"${String(value)}" is not a boolean (true/false/1/0)`);
50
+ });
51
+ }
52
+ function oneOf(values) {
53
+ return makeSchema((value) => {
54
+ if (typeof value === "string" && values.includes(value)) return { value };
55
+ return fail(`"${String(value)}" is not one of: ${values.join(", ")}`);
56
+ });
57
+ }
58
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
59
+ function uuid() {
60
+ return makeSchema((value) => {
61
+ if (typeof value === "string" && UUID_RE.test(value)) return { value };
62
+ return fail(`"${String(value)}" is not a valid UUID`);
63
+ });
64
+ }
65
+ //#endregion
66
+ //#region ../core/src/schema/modifiers.ts
67
+ function onMissing(codec, fallback) {
68
+ const multi = isMultiValueSchema(codec);
69
+ return {
70
+ ...multi ? { multi: true } : {},
71
+ "~standard": {
72
+ version: 1,
73
+ vendor: "finesoft",
74
+ validate(value) {
75
+ if (value === void 0 || multi && Array.isArray(value) && value.length === 0) return { value: fallback };
76
+ return codec["~standard"].validate(value);
77
+ }
78
+ }
79
+ };
80
+ }
81
+ function optional(codec) {
82
+ return onMissing(codec, void 0);
83
+ }
84
+ function withDefault(codec, fallback) {
85
+ return onMissing(codec, fallback);
86
+ }
87
+ //#endregion
88
+ //#region ../core/src/http/client.ts
89
+ /**
90
+ * 通用 HTTP 客户端基类
91
+ *
92
+ * 使用方式: 创建子类继承 HttpClient,定义业务方法调用 this.get() / this.post() 等。
93
+ *
94
+ * @example
95
+ * ```ts
96
+ * class MyApiClient extends HttpClient {
97
+ * async getUser(id: string) {
98
+ * return this.get<User>(`/users/${id}`);
99
+ * }
100
+ * }
101
+ * ```
102
+ */
103
+ var HttpClient = class {
104
+ baseUrl;
105
+ defaultHeaders;
106
+ fetchFn;
107
+ requestInterceptors;
108
+ responseInterceptors;
109
+ contextSignal;
110
+ constructor(config) {
111
+ this.baseUrl = config.baseUrl;
112
+ this.defaultHeaders = config.defaultHeaders ?? {};
113
+ if (!config.fetch && !config.context) throw new TypeError("HttpClient requires fetch or an execution context");
114
+ this.fetchFn = secureFetch(config.fetch ?? config.context.fetch, config);
115
+ this.contextSignal = config.context?.signal;
116
+ this.requestInterceptors = [...config.requestInterceptors ?? []];
117
+ this.responseInterceptors = [...config.responseInterceptors ?? []];
118
+ }
119
+ /** 动态添加请求拦截器 */
120
+ useRequestInterceptor(interceptor) {
121
+ this.requestInterceptors.push(interceptor);
122
+ return this;
123
+ }
124
+ /** 动态添加响应拦截器 */
125
+ useResponseInterceptor(interceptor) {
126
+ this.responseInterceptors.push(interceptor);
127
+ return this;
128
+ }
129
+ /** GET 请求,返回解析后的 JSON */
130
+ async get(path, params, options) {
131
+ return this.request("GET", path, {
132
+ ...options,
133
+ params
134
+ });
135
+ }
136
+ /** POST 请求,自动序列化 body 为 JSON */
137
+ async post(path, body, params, options) {
138
+ return this.request("POST", path, {
139
+ ...options,
140
+ body,
141
+ params
142
+ });
143
+ }
144
+ /** PUT 请求 */
145
+ async put(path, body, params, options) {
146
+ return this.request("PUT", path, {
147
+ ...options,
148
+ body,
149
+ params
150
+ });
151
+ }
152
+ /** DELETE 请求 */
153
+ async del(path, params, options) {
154
+ return this.request("DELETE", path, {
155
+ ...options,
156
+ params
157
+ });
158
+ }
159
+ /**
160
+ * 底层请求方法 — 子类可覆写以自定义行为
161
+ *
162
+ * 自动处理:
163
+ * - URL 拼接 (baseUrl + path + params)
164
+ * - SSRF 防护(IP 字面量同步校验 + 可选 DNS 解析校验)
165
+ * - 默认 headers 合并
166
+ * - JSON body 序列化
167
+ * - 响应 JSON 解析
168
+ * - 非 2xx 状态码抛出 HttpError
169
+ */
170
+ async request(method, path, options) {
171
+ options?.signal?.throwIfAborted();
172
+ const url = this.buildUrl(path, options?.params);
173
+ const headers = {
174
+ ...this.defaultHeaders,
175
+ ...options?.headers
176
+ };
177
+ let init = {
178
+ method,
179
+ headers,
180
+ signal: options?.signal
181
+ };
182
+ if (options?.body !== void 0) {
183
+ if (!Object.keys(headers).some((k) => k.toLowerCase() === "content-type")) headers["Content-Type"] = "application/json";
184
+ init.body = JSON.stringify(options.body);
185
+ }
186
+ for (const interceptor of this.requestInterceptors) init = await interceptor(url, init);
187
+ const signals = [
188
+ options?.signal,
189
+ init.signal,
190
+ this.contextSignal
191
+ ].filter((signal) => !!signal);
192
+ const signal = signals.length > 1 ? AbortSignal.any(signals) : signals[0];
193
+ signal?.throwIfAborted();
194
+ init = {
195
+ ...init,
196
+ signal
197
+ };
198
+ let response = await this.fetchFn(url, init);
199
+ signal?.throwIfAborted();
200
+ for (const interceptor of this.responseInterceptors) response = await interceptor(response, url);
201
+ if (!response.ok) {
202
+ const body = await response.text().catch(() => void 0);
203
+ throw new HttpError(response.status, response.statusText, body);
204
+ }
205
+ try {
206
+ const result = await response.json();
207
+ signal?.throwIfAborted();
208
+ return result;
209
+ } catch (e) {
210
+ if (e instanceof SyntaxError) throw new HttpError(response.status, "Invalid JSON response", await response.text().catch(() => void 0));
211
+ throw e;
212
+ }
213
+ }
214
+ /** 构建完整 URL — 子类可覆写以自定义 URL 拼接逻辑 */
215
+ buildUrl(path, params) {
216
+ const base = this.baseUrl.endsWith("/") ? this.baseUrl.slice(0, -1) : this.baseUrl;
217
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
218
+ const url = new URL(`${base}${normalizedPath}`, "http://placeholder");
219
+ if (params) for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v);
220
+ if (this.baseUrl.startsWith("http")) return url.toString();
221
+ return `${url.pathname}${url.search}`;
222
+ }
223
+ };
224
+ //#endregion
225
+ //#region ../core/src/data/mapper.ts
226
+ function pipe(...mappers) {
227
+ return (input) => mappers.reduce((acc, mapper) => mapper(acc), input);
228
+ }
229
+ function pipeAsync(...mappers) {
230
+ return async (input) => {
231
+ let acc = input;
232
+ for (const mapper of mappers) acc = await mapper(acc);
233
+ return acc;
234
+ };
235
+ }
236
+ /**
237
+ * 将一个 Mapper 应用到数组的每个元素
238
+ */
239
+ function mapEach(mapper) {
240
+ return (items) => items.map(mapper);
241
+ }
242
+ //#endregion
243
+ //#region ../core/src/utils/optional.ts
244
+ function isSome(value) {
245
+ return value !== null && value !== void 0;
246
+ }
247
+ function isNone(value) {
248
+ return value === null || value === void 0;
249
+ }
250
+ //#endregion
251
+ //#region ../core/src/utils/url.ts
252
+ /**
253
+ * URL 工具函数
254
+ */
255
+ /** 移除 URL scheme (https://, http://) */
256
+ function removeScheme(url) {
257
+ return url.replace(/^https?:\/\//, "");
258
+ }
259
+ /** 移除 URL host 部分,保留路径 */
260
+ function removeHost(url) {
261
+ try {
262
+ const parsed = new URL(url);
263
+ return parsed.pathname + parsed.search + parsed.hash;
264
+ } catch {
265
+ return url;
266
+ }
267
+ }
268
+ /** 移除 query 参数 */
269
+ function removeQueryParams(url) {
270
+ return url.split("?")[0];
271
+ }
272
+ /** 获取 URL 的基础路径(无 query、hash) */
273
+ function getBaseUrl(url) {
274
+ return url.split("?")[0].split("#")[0];
275
+ }
276
+ /** 构建 URL(路径 + query 参数) */
277
+ function buildUrl(path, params) {
278
+ if (!params) return path;
279
+ const searchParams = new URLSearchParams();
280
+ for (const [key, value] of Object.entries(params)) if (value !== void 0) searchParams.set(key, value);
281
+ const qs = searchParams.toString();
282
+ return qs ? `${path}?${qs}` : path;
283
+ }
284
+ //#endregion
285
+ //#region ../core/src/metrics/composite-recorder.ts
286
+ var CompositeEventRecorder = class {
287
+ recorders;
288
+ constructor(recorders) {
289
+ this.recorders = recorders;
290
+ }
291
+ record(type, fields) {
292
+ for (const recorder of this.recorders) recorder.record(type, fields);
293
+ }
294
+ async flush() {
295
+ const pending = [];
296
+ for (const recorder of this.recorders) if (recorder.flush) pending.push(recorder.flush());
297
+ await Promise.all(pending);
298
+ }
299
+ destroy() {
300
+ for (const recorder of this.recorders) recorder.destroy?.();
301
+ }
302
+ };
303
+ //#endregion
304
+ //#region ../core/src/metrics/console-recorder.ts
305
+ var ConsoleEventRecorder = class {
306
+ prefix;
307
+ constructor(prefix = "Metrics") {
308
+ this.prefix = prefix;
309
+ }
310
+ record(type, fields) {
311
+ console.info(`[${this.prefix}:${type}]`, fields ?? "");
312
+ }
313
+ async flush() {}
314
+ destroy() {}
315
+ };
316
+ //#endregion
317
+ //#region ../core/src/metrics/void-recorder.ts
318
+ var VoidEventRecorder = class {
319
+ record() {}
320
+ async flush() {}
321
+ destroy() {}
322
+ };
323
+ //#endregion
324
+ //#region ../core/src/metrics/with-fields-recorder.ts
325
+ var WithFieldsRecorder = class {
326
+ inner;
327
+ providers;
328
+ constructor(inner, providers) {
329
+ this.inner = inner;
330
+ this.providers = providers;
331
+ }
332
+ record(type, fields) {
333
+ let merged = {};
334
+ for (const provider of this.providers) Object.assign(merged, provider.getFields());
335
+ if (fields) Object.assign(merged, fields);
336
+ this.inner.record(type, merged);
337
+ }
338
+ async flush() {
339
+ return this.inner.flush?.();
340
+ }
341
+ destroy() {
342
+ this.inner.destroy?.();
343
+ }
344
+ };
345
+ //#endregion
346
+ //#region ../web/src/models/safe-error-page.ts
347
+ function formatDevError(devError) {
348
+ if (devError instanceof Error) return devError.stack ?? `${devError.name}: ${devError.message}`;
349
+ if (typeof devError === "string") return devError;
350
+ try {
351
+ return JSON.stringify(devError);
352
+ } catch {
353
+ return String(devError);
354
+ }
355
+ }
356
+ /**
357
+ * Build a BasePage for an error condition. In production, only `publicMessage`
358
+ * makes it into the page. In dev, `devError` (if provided) is appended.
359
+ */
360
+ function safeErrorPage(options) {
361
+ const { status, publicMessage, devError, isProduction = true } = options;
362
+ const showDevError = !isProduction && devError !== void 0;
363
+ return {
364
+ id: "error",
365
+ pageType: "error",
366
+ title: `Error ${status}`,
367
+ description: showDevError ? `${publicMessage}\n\n[dev only]\n${formatDevError(devError)}` : publicMessage
368
+ };
369
+ }
370
+ //#endregion
371
+ //#region ../web/src/middleware/types.ts
372
+ /** 继续执行 */
373
+ function next() {
374
+ return { kind: "next" };
375
+ }
376
+ /** 重定向到新 URL */
377
+ function redirect(url, status = 302) {
378
+ return {
379
+ kind: "redirect",
380
+ url,
381
+ status
382
+ };
383
+ }
384
+ /** URL 重写(不重新加载数据) */
385
+ function rewrite(url) {
386
+ return {
387
+ kind: "rewrite",
388
+ url
389
+ };
390
+ }
391
+ /** 拒绝访问 */
392
+ function deny(status = 403, message = "Forbidden") {
393
+ return {
394
+ kind: "deny",
395
+ status,
396
+ message
397
+ };
398
+ }
399
+ //#endregion
400
+ export { bool as C, str as D, oneOf as E, uuid as O, withDefault as S, num as T, mapEach as _, safeErrorPage as a, HttpClient as b, ConsoleEventRecorder as c, getBaseUrl as d, removeHost as f, isSome as g, isNone as h, rewrite as i, makeSchema as k, CompositeEventRecorder as l, removeScheme as m, next as n, WithFieldsRecorder as o, removeQueryParams as p, redirect as r, VoidEventRecorder as s, deny as t, buildUrl as u, pipe as v, int as w, optional as x, pipeAsync as y };
401
+
402
+ //# sourceMappingURL=types-BuaZHRG7.mjs.map