@taicode/common-server 4.0.0-beta.5 → 4.0.0-beta.7
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/output/app/index.d.ts +3 -1
- package/output/app/index.d.ts.map +1 -1
- package/output/app/index.js +47 -14
- package/output/auth/index.d.ts +3 -3
- package/output/auth/index.d.ts.map +1 -1
- package/output/auth/index.js +3 -2
- package/output/core/boot/context.d.ts +54 -34
- package/output/core/boot/context.d.ts.map +1 -1
- package/output/core/boot/context.js +42 -4
- package/output/core/boot/lifecycle.d.ts +39 -7
- package/output/core/boot/lifecycle.d.ts.map +1 -1
- package/output/core/boot/lifecycle.js +59 -19
- package/output/core/boot/shutdown.d.ts +9 -5
- package/output/core/boot/shutdown.d.ts.map +1 -1
- package/output/core/boot/shutdown.js +9 -4
- package/output/core/boot/signal.d.ts +1 -1
- package/output/core/boot/signal.js +1 -1
- package/output/core/boot/steps.d.ts +60 -29
- package/output/core/boot/steps.d.ts.map +1 -1
- package/output/core/boot/steps.js +72 -13
- package/output/core/entries.d.ts +51 -23
- package/output/core/entries.d.ts.map +1 -1
- package/output/core/entries.js +70 -31
- package/output/core/entries.test.js +251 -10
- package/output/core/env.d.ts +21 -0
- package/output/core/env.d.ts.map +1 -1
- package/output/core/env.js +28 -0
- package/output/core/resource.d.ts +56 -0
- package/output/core/resource.d.ts.map +1 -0
- package/output/core/resource.js +56 -0
- package/output/core/test-support/database.d.ts +2 -9
- package/output/core/test-support/database.d.ts.map +1 -1
- package/output/core/test-support/database.js +3 -16
- package/output/core/test-support/database.test.js +2 -1
- package/output/core/test-support/environment.d.ts +8 -3
- package/output/core/test-support/environment.d.ts.map +1 -1
- package/output/core/test-support/environment.js +10 -7
- package/output/core/test-support/test-server.d.ts +49 -19
- package/output/core/test-support/test-server.d.ts.map +1 -1
- package/output/core/test-support/test-server.js +25 -6
- package/output/core/test-support/test-server.test.js +7 -5
- package/output/core/test-support/testdata.d.ts +171 -0
- package/output/core/test-support/testdata.d.ts.map +1 -0
- package/output/core/test-support/testdata.js +202 -0
- package/output/core/test-support/testdata.test.d.ts +2 -0
- package/output/core/test-support/testdata.test.d.ts.map +1 -0
- package/output/core/test-support/testdata.test.js +240 -0
- package/output/core/types.d.ts +235 -71
- package/output/core/types.d.ts.map +1 -1
- package/output/cors/index.d.ts +2 -2
- package/output/cors/index.d.ts.map +1 -1
- package/output/cors/index.js +2 -1
- package/output/rate-limit/index.d.ts +2 -2
- package/output/rate-limit/index.d.ts.map +1 -1
- package/output/rate-limit/index.js +2 -1
- package/package.json +3 -3
|
@@ -5,17 +5,21 @@
|
|
|
5
5
|
* 关闭时按阶段**逆序 flush**:`routers → middlewares → services → clients → config → resources`
|
|
6
6
|
* (后创建的先销毁;Prisma / Redis 这类资源**最后**才断)。
|
|
7
7
|
*
|
|
8
|
-
* `services`
|
|
9
|
-
*
|
|
8
|
+
* `services` / `resources` 阶段内部保持同一套语义:先 flush 自己的 `disposer`(构造期注册的
|
|
9
|
+
* 临时产物),再逐单元 `dispose`(声明序**逆序**)。
|
|
10
|
+
*
|
|
11
|
+
* ❗`resources` 阶段**顺序与 `services` 相反**:先 `disposeResources`(单元自己的连接)再
|
|
12
|
+
* `flushResources`(`ctx.disposer` 里的逃生口清理)。理由:单元 `dispose` 是「资源自身怎么关」
|
|
13
|
+
* 的正事,`disposer` 是临时产物;临时产物可能在 `dispose` 过程中被用到,不能先拆。
|
|
10
14
|
*
|
|
11
15
|
* `clients` 排在 `services` **之后**:服务单元 `dispose()` 时可能还要落一次外部调用。
|
|
12
16
|
*
|
|
13
|
-
*
|
|
14
|
-
* → `flushClients` → `flushConfig` → `flushResources`。
|
|
17
|
+
* 固定八步:`flushRouters` → `flushMiddlewares` → `flushServices` → `disposeServices`
|
|
18
|
+
* → `flushClients` → `flushConfig` → `disposeResources` → `flushResources`。
|
|
15
19
|
*
|
|
16
20
|
* 每步失败记日志、不阻断后续,最后聚合上报。
|
|
17
21
|
*/
|
|
18
|
-
declare const SHUTDOWN_ORDER: readonly ["flushRouters", "flushMiddlewares", "flushServices", "disposeServices", "flushClients", "flushConfig", "flushResources"];
|
|
22
|
+
declare const SHUTDOWN_ORDER: readonly ["flushRouters", "flushMiddlewares", "flushServices", "disposeServices", "flushClients", "flushConfig", "disposeResources", "flushResources"];
|
|
19
23
|
type ShutdownStepName = (typeof SHUTDOWN_ORDER)[number];
|
|
20
24
|
type ShutdownHandles = Record<ShutdownStepName, () => void | Promise<void>>;
|
|
21
25
|
export interface ShutdownLogger {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../../../source/core/boot/shutdown.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../../../source/core/boot/shutdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,QAAA,MAAM,cAAc,wJASV,CAAA;AAEV,KAAK,gBAAgB,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvD,KAAK,eAAe,GAAG,MAAM,CAAC,gBAAgB,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;AAE3E,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAC5C;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAe9F"}
|
|
@@ -5,13 +5,17 @@
|
|
|
5
5
|
* 关闭时按阶段**逆序 flush**:`routers → middlewares → services → clients → config → resources`
|
|
6
6
|
* (后创建的先销毁;Prisma / Redis 这类资源**最后**才断)。
|
|
7
7
|
*
|
|
8
|
-
* `services`
|
|
9
|
-
*
|
|
8
|
+
* `services` / `resources` 阶段内部保持同一套语义:先 flush 自己的 `disposer`(构造期注册的
|
|
9
|
+
* 临时产物),再逐单元 `dispose`(声明序**逆序**)。
|
|
10
|
+
*
|
|
11
|
+
* ❗`resources` 阶段**顺序与 `services` 相反**:先 `disposeResources`(单元自己的连接)再
|
|
12
|
+
* `flushResources`(`ctx.disposer` 里的逃生口清理)。理由:单元 `dispose` 是「资源自身怎么关」
|
|
13
|
+
* 的正事,`disposer` 是临时产物;临时产物可能在 `dispose` 过程中被用到,不能先拆。
|
|
10
14
|
*
|
|
11
15
|
* `clients` 排在 `services` **之后**:服务单元 `dispose()` 时可能还要落一次外部调用。
|
|
12
16
|
*
|
|
13
|
-
*
|
|
14
|
-
* → `flushClients` → `flushConfig` → `flushResources`。
|
|
17
|
+
* 固定八步:`flushRouters` → `flushMiddlewares` → `flushServices` → `disposeServices`
|
|
18
|
+
* → `flushClients` → `flushConfig` → `disposeResources` → `flushResources`。
|
|
15
19
|
*
|
|
16
20
|
* 每步失败记日志、不阻断后续,最后聚合上报。
|
|
17
21
|
*/
|
|
@@ -22,6 +26,7 @@ const SHUTDOWN_ORDER = [
|
|
|
22
26
|
'disposeServices',
|
|
23
27
|
'flushClients',
|
|
24
28
|
'flushConfig',
|
|
29
|
+
'disposeResources',
|
|
25
30
|
'flushResources',
|
|
26
31
|
];
|
|
27
32
|
/**
|
|
@@ -4,7 +4,7 @@ import type { FastifyInstance } from 'fastify';
|
|
|
4
4
|
* 内置信号处理(设计 §7.1 `activate` 尾部 / §7.2)。
|
|
5
5
|
*
|
|
6
6
|
* `SIGTERM` / `SIGINT` / `uncaughtException` / `unhandledRejection` ⇒ `app.close()`(触发 `onClose`
|
|
7
|
-
* 的关闭
|
|
7
|
+
* 的关闭 7 步)⇒ `process.exit(0/1)`。**只有 `startServer` 装信号处理**(它一定 `listen`);
|
|
8
8
|
* `createTestServer` 不装——它跑在测试进程里,退出由测试框架决定。
|
|
9
9
|
*
|
|
10
10
|
* @returns detach 函数(移除本次注册的监听器);`startServer` 已把它挂到 `onClose` 上
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 内置信号处理(设计 §7.1 `activate` 尾部 / §7.2)。
|
|
3
3
|
*
|
|
4
4
|
* `SIGTERM` / `SIGINT` / `uncaughtException` / `unhandledRejection` ⇒ `app.close()`(触发 `onClose`
|
|
5
|
-
* 的关闭
|
|
5
|
+
* 的关闭 7 步)⇒ `process.exit(0/1)`。**只有 `startServer` 装信号处理**(它一定 `listen`);
|
|
6
6
|
* `createTestServer` 不装——它跑在测试进程里,退出由测试框架决定。
|
|
7
7
|
*
|
|
8
8
|
* @returns detach 函数(移除本次注册的监听器);`startServer` 已把它挂到 `onClose` 上
|
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
import { Disposer } from '@taicode/common-base';
|
|
2
2
|
import type { ConfigAccess } from './context.js';
|
|
3
|
+
import type { ResourceSetupUnit } from './lifecycle.js';
|
|
3
4
|
import type { ConfigEnv } from '../env.js';
|
|
4
|
-
import type { AppInstance, ClientsView, ErrorClassifier, LoggerLike, ResourcesView, Server } from '../types.js';
|
|
5
|
+
import type { AppEnv, AppInstance, ClientsView, ErrorClassifier, LoggerLike, ResourcesView, Server } from '../types.js';
|
|
5
6
|
import type { FastifyInstance } from 'fastify';
|
|
6
7
|
/**
|
|
7
8
|
* 装配**步料**(不是装配流程)。
|
|
8
9
|
*
|
|
9
|
-
* `startServer` / `generateOpenApi
|
|
10
|
+
* `startServer` / `generateOpenApi`(`entries.ts`)各自写明自己的步序——
|
|
10
11
|
* 这里只放「一步」的实现,**不含任何 `phase` / `mode` / 跳过集**。哪个入口跑哪几步,
|
|
11
|
-
*
|
|
12
|
+
* 在入口函数里一眼可见,不需要在数据结构和分支之间来回对照。
|
|
12
13
|
*/
|
|
14
|
+
/**
|
|
15
|
+
* 结算 `ctx.env`:**完全按内置逻辑加载**(dotenv 分层 + 进程注入已由 `loadEnvironment` /
|
|
16
|
+
* `prepareTestDatabase` 做完,结果在 `base` 与 `process.env` 里),这里只把两者合成最终视图。
|
|
17
|
+
*
|
|
18
|
+
* - `base`:框架固定约定值(`ENV` / `API_PORT` / `DATABASE_URL` / ...,已归一化);
|
|
19
|
+
* - 应用声明的扩展字段(`Env`,`defineServer` 的第一个泛型):运行期取**内置加载后的**
|
|
20
|
+
* `process.env[key]` 字符串直通——框架**不提供自定义加载**,也不对扩展字段做映射 / 校验 / 默认值。
|
|
21
|
+
*
|
|
22
|
+
* 返回「`process.env` 直通 + 固定约定覆盖」:固定键以归一化后的 `base` 为准(放最后覆盖),
|
|
23
|
+
* 其余键保持内置加载后的原始字符串。类型上窄化为 `AppEnv<Env>`(只暴露固定键 + 声明的扩展键)。
|
|
24
|
+
*
|
|
25
|
+
* `Env` 由调用方(`startServer` / `createTestServer`)显式传入其自身的 `Env` 泛型。
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveAppEnv<Env>(base: ConfigEnv): AppEnv<Env>;
|
|
13
28
|
/** 每个阶段一个 `Disposer`(创建与释放同处一地;关闭时按阶段逆序 flush) */
|
|
14
29
|
export interface StageDisposers {
|
|
15
30
|
resources: Disposer;
|
|
@@ -20,9 +35,9 @@ export interface StageDisposers {
|
|
|
20
35
|
routers: Disposer;
|
|
21
36
|
}
|
|
22
37
|
/** 一次装配的**运行期上下文**(步料之间传递的、不放公共类型里的东西) */
|
|
23
|
-
export interface BootContext<
|
|
24
|
-
server: Server<
|
|
25
|
-
env:
|
|
38
|
+
export interface BootContext<Resources, Config, Clients, Services, Env> {
|
|
39
|
+
server: Server<Resources, Config, Clients, Services, Env>;
|
|
40
|
+
env: AppEnv<Env>;
|
|
26
41
|
/** 原生 Fastify 实例(对外返回的就是它) */
|
|
27
42
|
app: FastifyInstance;
|
|
28
43
|
/** 同一实例的框架视图(`request.ctx` 等类型增强的载体) */
|
|
@@ -31,11 +46,11 @@ export interface BootContext<R, C, CL, S> {
|
|
|
31
46
|
/** 身份块的 `service`(日志标签用) */
|
|
32
47
|
service: string;
|
|
33
48
|
/** `config` 槽位的**读取能力**(出 spec 路径未产出 → `read()` 抛错) */
|
|
34
|
-
configAccess: ConfigAccess<
|
|
49
|
+
configAccess: ConfigAccess<Config>;
|
|
35
50
|
disposers: StageDisposers;
|
|
36
51
|
}
|
|
37
52
|
/** 建 app + 六个阶段 `Disposer`(两个入口的**第一步**,内容完全相同) */
|
|
38
|
-
export declare function createBootContext<
|
|
53
|
+
export declare function createBootContext<Resources, Config, Clients, Services, Env>(server: Server<Resources, Config, Clients, Services, Env>, env: AppEnv<Env>): BootContext<Resources, Config, Clients, Services, Env>;
|
|
39
54
|
/**
|
|
40
55
|
* 出文档用的上下文:**同一个 `BootContext` 形状,但实例是最小 Fastify**(无 logger /
|
|
41
56
|
* 无 requestId / 无 trustProxy,见 `createDocumentFastify`)。
|
|
@@ -43,29 +58,43 @@ export declare function createBootContext<R, C, CL, S>(server: Server<R, C, CL,
|
|
|
43
58
|
* 形状不变是刻意的——`buildResources` / `registerRouters` 这些单步实现
|
|
44
59
|
* 因此**不需要知道自己在哪个入口跑**,轻量只体现在「实例怎么造」和「跑哪几步」上。
|
|
45
60
|
*/
|
|
46
|
-
export declare function createDocumentContext<
|
|
61
|
+
export declare function createDocumentContext<Resources, Config, Clients, Services, Env>(server: Server<Resources, Config, Clients, Services, Env>, env: AppEnv<Env>): BootContext<Resources, Config, Clients, Services, Env>;
|
|
47
62
|
/**
|
|
48
63
|
* 取**已加载**配置的访问器(框架内置单元消费配置的唯一入口)。
|
|
49
64
|
*
|
|
50
65
|
* 出 spec 路径下 `config` 槽位未产出,读到即抛 `CONFIG_UNAVAILABLE`——
|
|
51
66
|
* 「构造期不得读配置」因此有运行期护栏,而不只是文档约定。
|
|
52
67
|
*/
|
|
53
|
-
export declare function createConfigReader<
|
|
54
|
-
export interface ResourceBuildResult<
|
|
55
|
-
/** 剔除了「编译期静态关闭」(值为 `false
|
|
56
|
-
resources: ResourcesView<
|
|
68
|
+
export declare function createConfigReader<Config>(configAccess: ConfigAccess<Config>): (key: string) => unknown;
|
|
69
|
+
export interface ResourceBuildResult<Resources> {
|
|
70
|
+
/** 剔除了「编译期静态关闭」(值为 `false`)的键,并完成单元解包 */
|
|
71
|
+
resources: ResourcesView<Resources>;
|
|
72
|
+
/** 待执行的资源 `setup`(按声明序;空数组 = 无需准备) */
|
|
73
|
+
units: ResourceSetupUnit[];
|
|
57
74
|
/** 各资源注册的分类器合成结果(零个 = `undefined`) */
|
|
58
75
|
classifyResourceError: ErrorClassifier | undefined;
|
|
59
76
|
}
|
|
60
|
-
/**
|
|
61
|
-
|
|
77
|
+
/**
|
|
78
|
+
* `resources(ctx)`:**只构造、不建连**(仅运行期入口执行)。
|
|
79
|
+
*
|
|
80
|
+
* 只看两件事:调用工厂、拆解单元(视图 + 待执行的 `setup`)。**不执行** `setup` —— 那是下一个
|
|
81
|
+
* 单步 `setupResources`,两个入口各自显式写明(与整个 `steps.ts` 【步料不是流程】的定位一致)。
|
|
82
|
+
*/
|
|
83
|
+
export declare function buildResources<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>): Promise<ResourceBuildResult<Resources>>;
|
|
84
|
+
/**
|
|
85
|
+
* 资源单元 `setup` 步:建连验证 / 同步库结构 / 预热,**在 `config` 之前**。
|
|
86
|
+
*
|
|
87
|
+
* 为什么必须是独立一步而不是 `buildResources` 内部:它是一道**有失败可能的装配动作**,
|
|
88
|
+
* 入口需要能把它跟「构造」分开看(失败时已构造的资源照样要释放)。
|
|
89
|
+
*/
|
|
90
|
+
export declare function setupResources<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>, units: readonly ResourceSetupUnit[]): Promise<void>;
|
|
62
91
|
/**
|
|
63
92
|
* core 装配:`identity` → `schema`(+ 路由契约守卫)→ `requestContext` → `cookie` →
|
|
64
93
|
* `response` → `openapi` → `requestId` 回吐。
|
|
65
94
|
*
|
|
66
95
|
* 仅运行期入口执行——它装的是「请求怎么被处理」,构建期没有请求。
|
|
67
96
|
*/
|
|
68
|
-
export declare function registerCoreStep<
|
|
97
|
+
export declare function registerCoreStep<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>, classifyResourceError?: ErrorClassifier): Promise<void>;
|
|
69
98
|
/**
|
|
70
99
|
* **文档 core** 装配:`identity` → `schema`(+ 路由契约守卫)→ `openapi`。
|
|
71
100
|
*
|
|
@@ -73,35 +102,37 @@ export declare function registerCoreStep<R, C, CL, S>(ctx: BootContext<R, C, CL,
|
|
|
73
102
|
* `response` / `requestId` 不装:它们影响的是请求怎么被处理,而构建期一条请求都不会发。
|
|
74
103
|
* 守卫仍必须在——文档就是权限目录(§10-26)。
|
|
75
104
|
*/
|
|
76
|
-
export declare function registerDocumentCoreStep<
|
|
105
|
+
export declare function registerDocumentCoreStep<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>): Promise<void>;
|
|
77
106
|
/** `config(ctx)`:**仅运行期**(出 spec 路径不跑) */
|
|
78
|
-
export declare function buildConfig<
|
|
107
|
+
export declare function buildConfig<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>, resources: ResourcesView<Resources>): Promise<void>;
|
|
79
108
|
/**
|
|
80
109
|
* `clients(ctx)`:**仅运行期**(需要 `config`,出 spec 路径不跑)。
|
|
81
110
|
*
|
|
82
111
|
* 外部客户端的地址 / 凭证来自业务配置,所以它必须排在 `config` 之后;
|
|
83
112
|
* 又因为多个 service 要共享同一份实例(而 `services` 之间禁止互依),所以它必须排在 `services` 之前。
|
|
113
|
+
*
|
|
114
|
+
* **槽位可选**:应用省略 `clients` 时,这里按「空客户端包」处理(`Clients` 退化为空)。
|
|
84
115
|
*/
|
|
85
|
-
export declare function buildClients<
|
|
116
|
+
export declare function buildClients<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>, resources: ResourcesView<Resources>): Promise<ClientsView<Clients>>;
|
|
86
117
|
/** `services(ctx)`:**只构造**(`init()` 由调用方决定跑不跑) */
|
|
87
|
-
export declare function buildServices<
|
|
88
|
-
/** `middlewares(ctx)
|
|
89
|
-
export declare function registerMiddlewares<
|
|
118
|
+
export declare function buildServices<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>, resources: ResourcesView<Resources>, clients: ClientsView<Clients>): Promise<Awaited<Services>>;
|
|
119
|
+
/** `middlewares(ctx)`:按**数组顺序**逐个 `register(app)`(两个入口都执行) */
|
|
120
|
+
export declare function registerMiddlewares<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>, services: Awaited<Services>, clients: ClientsView<Clients>): Promise<void>;
|
|
90
121
|
/** 框架内置系统路由(`/` / `/health` / `/system/health` / `/system/openapi` / `/system/info`) */
|
|
91
|
-
export declare function registerSystemRoutesStep<
|
|
122
|
+
export declare function registerSystemRoutesStep<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>, services: Awaited<Services>): void;
|
|
92
123
|
/** `routers(ctx)`:业务路由(文档与权限目录都从这里来) */
|
|
93
|
-
export declare function registerRouters<
|
|
124
|
+
export declare function registerRouters<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>, services: Awaited<Services>, clients: ClientsView<Clients>): Promise<void>;
|
|
94
125
|
/** 系统路由(出文档路径:不构造 `services`) */
|
|
95
|
-
export declare function registerDocumentSystemRoutesStep<
|
|
126
|
+
export declare function registerDocumentSystemRoutesStep<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>): void;
|
|
96
127
|
/** 业务路由(出文档路径:不构造 `services` / `clients`;这是文档内容的**唯一**业务来源) */
|
|
97
|
-
export declare function registerDocumentRoutersStep<
|
|
128
|
+
export declare function registerDocumentRoutersStep<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>): Promise<void>;
|
|
98
129
|
/**
|
|
99
|
-
*
|
|
130
|
+
* 关闭流程(固定八步,顺序写在 `shutdown.ts` 里,不可覆盖)。
|
|
100
131
|
*
|
|
101
132
|
* 由 `onClose` 钩子触发;spec 入口在 `finally` 里 `close()` 同样会走它——
|
|
102
133
|
* 「构造了什么就释放什么」,构建脚本不再留下未释放的资源。
|
|
103
134
|
*/
|
|
104
|
-
export declare function buildShutdownFlow<
|
|
135
|
+
export declare function buildShutdownFlow<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>, services: Awaited<Services>, resourceUnits?: readonly ResourceSetupUnit[]): () => Promise<void>;
|
|
105
136
|
/** 把关闭流程挂到 `onClose`(两个入口都挂:谁 `close()` 谁负责释放) */
|
|
106
|
-
export declare function installShutdownHook<
|
|
137
|
+
export declare function installShutdownHook<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>, services: Awaited<Services>, resourceUnits?: readonly ResourceSetupUnit[]): void;
|
|
107
138
|
//# sourceMappingURL=steps.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"steps.d.ts","sourceRoot":"","sources":["../../../source/core/boot/steps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"steps.d.ts","sourceRoot":"","sources":["../../../source/core/boot/steps.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,38 @@
|
|
|
1
1
|
import { Disposer } from '@taicode/common-base';
|
|
2
2
|
import { registerCore, registerDocumentCore, createDocumentFastify, createFastify } from '../assemble.js';
|
|
3
3
|
import { registerSystemRoutes } from '../modules/system-routes/index.js';
|
|
4
|
-
import { createClientsContext, createConfigAccess, createConfigContext, createMiddlewareContext, createResourcesContext, createRouterContext, createServiceContext,
|
|
5
|
-
import { CLIENTS_STAGE, CONFIG_STAGE, disposeServices, disposeStage,
|
|
4
|
+
import { createClientsContext, createConfigAccess, createConfigContext, createMiddlewareContext, createResourcesContext, createRouterContext, createServiceContext, liveResources, provideConfig, readConfigKey, splitResourceUnits, } from './context.js';
|
|
5
|
+
import { CLIENTS_STAGE, CONFIG_STAGE, disposeResourceUnits, disposeServices, disposeStage, MIDDLEWARES_STAGE, middlewareUnitName, RESOURCES_STAGE, ROUTERS_STAGE, SERVICES_STAGE, setupResourceUnits, } from './lifecycle.js';
|
|
6
6
|
import { runShutdown } from './shutdown.js';
|
|
7
|
+
/**
|
|
8
|
+
* 装配**步料**(不是装配流程)。
|
|
9
|
+
*
|
|
10
|
+
* `startServer` / `generateOpenApi`(`entries.ts`)各自写明自己的步序——
|
|
11
|
+
* 这里只放「一步」的实现,**不含任何 `phase` / `mode` / 跳过集**。哪个入口跑哪几步,
|
|
12
|
+
* 在入口函数里一眼可见,不需要在数据结构和分支之间来回对照。
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* 结算 `ctx.env`:**完全按内置逻辑加载**(dotenv 分层 + 进程注入已由 `loadEnvironment` /
|
|
16
|
+
* `prepareTestDatabase` 做完,结果在 `base` 与 `process.env` 里),这里只把两者合成最终视图。
|
|
17
|
+
*
|
|
18
|
+
* - `base`:框架固定约定值(`ENV` / `API_PORT` / `DATABASE_URL` / ...,已归一化);
|
|
19
|
+
* - 应用声明的扩展字段(`Env`,`defineServer` 的第一个泛型):运行期取**内置加载后的**
|
|
20
|
+
* `process.env[key]` 字符串直通——框架**不提供自定义加载**,也不对扩展字段做映射 / 校验 / 默认值。
|
|
21
|
+
*
|
|
22
|
+
* 返回「`process.env` 直通 + 固定约定覆盖」:固定键以归一化后的 `base` 为准(放最后覆盖),
|
|
23
|
+
* 其余键保持内置加载后的原始字符串。类型上窄化为 `AppEnv<Env>`(只暴露固定键 + 声明的扩展键)。
|
|
24
|
+
*
|
|
25
|
+
* `Env` 由调用方(`startServer` / `createTestServer`)显式传入其自身的 `Env` 泛型。
|
|
26
|
+
*/
|
|
27
|
+
export function resolveAppEnv(base) {
|
|
28
|
+
const passthrough = {};
|
|
29
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
30
|
+
if (typeof value === 'string') {
|
|
31
|
+
passthrough[key] = value;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { ...passthrough, ...base };
|
|
35
|
+
}
|
|
7
36
|
/** 建 app + 六个阶段 `Disposer`(两个入口的**第一步**,内容完全相同) */
|
|
8
37
|
export function createBootContext(server, env) {
|
|
9
38
|
return buildContext(server, env, createFastify(env));
|
|
@@ -71,7 +100,12 @@ function combineClassifiers(classifiers) {
|
|
|
71
100
|
return null;
|
|
72
101
|
};
|
|
73
102
|
}
|
|
74
|
-
/**
|
|
103
|
+
/**
|
|
104
|
+
* `resources(ctx)`:**只构造、不建连**(仅运行期入口执行)。
|
|
105
|
+
*
|
|
106
|
+
* 只看两件事:调用工厂、拆解单元(视图 + 待执行的 `setup`)。**不执行** `setup` —— 那是下一个
|
|
107
|
+
* 单步 `setupResources`,两个入口各自显式写明(与整个 `steps.ts` 【步料不是流程】的定位一致)。
|
|
108
|
+
*/
|
|
75
109
|
export async function buildResources(ctx) {
|
|
76
110
|
const classifiers = [];
|
|
77
111
|
const built = (await ctx.server.resources(createResourcesContext({
|
|
@@ -82,11 +116,22 @@ export async function buildResources(ctx) {
|
|
|
82
116
|
classifiers.push(classifier);
|
|
83
117
|
},
|
|
84
118
|
})));
|
|
119
|
+
const { view, units } = splitResourceUnits(built);
|
|
85
120
|
return {
|
|
86
|
-
resources:
|
|
121
|
+
resources: view,
|
|
122
|
+
units,
|
|
87
123
|
classifyResourceError: combineClassifiers(classifiers),
|
|
88
124
|
};
|
|
89
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* 资源单元 `setup` 步:建连验证 / 同步库结构 / 预热,**在 `config` 之前**。
|
|
128
|
+
*
|
|
129
|
+
* 为什么必须是独立一步而不是 `buildResources` 内部:它是一道**有失败可能的装配动作**,
|
|
130
|
+
* 入口需要能把它跟「构造」分开看(失败时已构造的资源照样要释放)。
|
|
131
|
+
*/
|
|
132
|
+
export async function setupResources(ctx, units) {
|
|
133
|
+
await setupResourceUnits({ service: ctx.service, log: ctx.log, units });
|
|
134
|
+
}
|
|
90
135
|
/**
|
|
91
136
|
* core 装配:`identity` → `schema`(+ 路由契约守卫)→ `requestContext` → `cookie` →
|
|
92
137
|
* `response` → `openapi` → `requestId` 回吐。
|
|
@@ -121,16 +166,28 @@ export async function buildConfig(ctx, resources) {
|
|
|
121
166
|
*
|
|
122
167
|
* 外部客户端的地址 / 凭证来自业务配置,所以它必须排在 `config` 之后;
|
|
123
168
|
* 又因为多个 service 要共享同一份实例(而 `services` 之间禁止互依),所以它必须排在 `services` 之前。
|
|
169
|
+
*
|
|
170
|
+
* **槽位可选**:应用省略 `clients` 时,这里按「空客户端包」处理(`Clients` 退化为空)。
|
|
124
171
|
*/
|
|
125
172
|
export async function buildClients(ctx, resources) {
|
|
173
|
+
if (ctx.server.clients === undefined) {
|
|
174
|
+
return {};
|
|
175
|
+
}
|
|
126
176
|
const built = (await ctx.server.clients(createClientsContext({ log: ctx.log, disposer: ctx.disposers.clients, resources, configAccess: ctx.configAccess })));
|
|
127
|
-
return
|
|
177
|
+
return liveResources(built);
|
|
128
178
|
}
|
|
129
179
|
/** `services(ctx)`:**只构造**(`init()` 由调用方决定跑不跑) */
|
|
130
180
|
export async function buildServices(ctx, resources, clients) {
|
|
131
181
|
return ctx.server.services(createServiceContext({ log: ctx.log, disposer: ctx.disposers.services, resources, configAccess: ctx.configAccess, clients }));
|
|
132
182
|
}
|
|
133
|
-
/**
|
|
183
|
+
/** 归一化一个 `MiddlewareEntry`:裸函数补一个用于日志的占位名(`middleware[i]`) */
|
|
184
|
+
function normalizeMiddleware(entry, index) {
|
|
185
|
+
if (typeof entry === 'function') {
|
|
186
|
+
return { name: `middleware[${index}]`, register: entry };
|
|
187
|
+
}
|
|
188
|
+
return { name: entry.name, register: entry.register };
|
|
189
|
+
}
|
|
190
|
+
/** `middlewares(ctx)`:按**数组顺序**逐个 `register(app)`(两个入口都执行) */
|
|
134
191
|
export async function registerMiddlewares(ctx, services, clients) {
|
|
135
192
|
const middlewares = ctx.server.middlewares(createMiddlewareContext({
|
|
136
193
|
app: ctx.instance,
|
|
@@ -140,9 +197,10 @@ export async function registerMiddlewares(ctx, services, clients) {
|
|
|
140
197
|
configAccess: ctx.configAccess,
|
|
141
198
|
clients,
|
|
142
199
|
}));
|
|
143
|
-
for (const [
|
|
144
|
-
|
|
145
|
-
|
|
200
|
+
for (const [index, entry] of middlewares.entries()) {
|
|
201
|
+
const { name, register } = normalizeMiddleware(entry, index);
|
|
202
|
+
await register(ctx.instance);
|
|
203
|
+
ctx.log.debug({ service: ctx.service, unit: middlewareUnitName(ctx.service, name), stage: MIDDLEWARES_STAGE }, '[middleware] registered');
|
|
146
204
|
}
|
|
147
205
|
}
|
|
148
206
|
/** 框架内置系统路由(`/` / `/health` / `/system/health` / `/system/openapi` / `/system/info`) */
|
|
@@ -175,12 +233,12 @@ export async function registerDocumentRoutersStep(ctx) {
|
|
|
175
233
|
await registerRouters(ctx, DOCUMENT_UNITS, DOCUMENT_UNITS);
|
|
176
234
|
}
|
|
177
235
|
/**
|
|
178
|
-
*
|
|
236
|
+
* 关闭流程(固定八步,顺序写在 `shutdown.ts` 里,不可覆盖)。
|
|
179
237
|
*
|
|
180
238
|
* 由 `onClose` 钩子触发;spec 入口在 `finally` 里 `close()` 同样会走它——
|
|
181
239
|
* 「构造了什么就释放什么」,构建脚本不再留下未释放的资源。
|
|
182
240
|
*/
|
|
183
|
-
export function buildShutdownFlow(ctx, services) {
|
|
241
|
+
export function buildShutdownFlow(ctx, services, resourceUnits = []) {
|
|
184
242
|
return async () => {
|
|
185
243
|
await runShutdown({
|
|
186
244
|
flushRouters: () => disposeStage({ service: ctx.service, log: ctx.log, stage: ROUTERS_STAGE, disposer: ctx.disposers.routers }),
|
|
@@ -189,12 +247,13 @@ export function buildShutdownFlow(ctx, services) {
|
|
|
189
247
|
disposeServices: () => disposeServices({ service: ctx.service, log: ctx.log, services: services }),
|
|
190
248
|
flushClients: () => disposeStage({ service: ctx.service, log: ctx.log, stage: CLIENTS_STAGE, disposer: ctx.disposers.clients }),
|
|
191
249
|
flushConfig: () => disposeStage({ service: ctx.service, log: ctx.log, stage: CONFIG_STAGE, disposer: ctx.disposers.config }),
|
|
250
|
+
disposeResources: () => disposeResourceUnits({ service: ctx.service, log: ctx.log, units: resourceUnits }),
|
|
192
251
|
flushResources: () => disposeStage({ service: ctx.service, log: ctx.log, stage: RESOURCES_STAGE, disposer: ctx.disposers.resources }),
|
|
193
252
|
}, ctx.log);
|
|
194
253
|
};
|
|
195
254
|
}
|
|
196
255
|
/** 把关闭流程挂到 `onClose`(两个入口都挂:谁 `close()` 谁负责释放) */
|
|
197
|
-
export function installShutdownHook(ctx, services) {
|
|
198
|
-
const flow = buildShutdownFlow(ctx, services);
|
|
256
|
+
export function installShutdownHook(ctx, services, resourceUnits = []) {
|
|
257
|
+
const flow = buildShutdownFlow(ctx, services, resourceUnits);
|
|
199
258
|
ctx.app.addHook('onClose', () => flow());
|
|
200
259
|
}
|
package/output/core/entries.d.ts
CHANGED
|
@@ -1,51 +1,69 @@
|
|
|
1
1
|
import type { OpenApiDocument, Server, ServerConfig } from './types.js';
|
|
2
2
|
import type { FastifyInstance } from 'fastify';
|
|
3
|
-
export { SERVER_BRAND } from './types.js';
|
|
4
3
|
/**
|
|
5
4
|
* `defineServer()`(N1 §1)——**纯定义**:产出一个 `Server` 对象,不建连、不起服务。
|
|
6
5
|
*
|
|
7
6
|
* 启动是另一个函数(`startServer`)的事——同一份定义可同时喂给「启动」与「出 spec」
|
|
8
|
-
* (`generateOpenApi
|
|
7
|
+
* (`generateOpenApi`)。
|
|
9
8
|
*
|
|
10
9
|
* ## 怎么用
|
|
11
10
|
*
|
|
12
|
-
* 一个服务**只在 `source/server.ts`
|
|
11
|
+
* 一个服务**只在 `source/server.ts` 里写一次**,然后在三个地方消费它:
|
|
13
12
|
*
|
|
14
13
|
* ```ts
|
|
15
14
|
* // source/server.ts
|
|
16
15
|
* export const server = defineServer({ app, resources, config, clients, services, middlewares, routers })
|
|
17
16
|
* ```
|
|
18
17
|
* ```ts
|
|
19
|
-
* // source/index.ts —— 启动
|
|
20
|
-
* await
|
|
21
|
-
*
|
|
22
|
-
* ```ts
|
|
23
|
-
* // build/openapi-gen.ts —— 出 spec
|
|
24
|
-
* await generateOpenApi(server)
|
|
25
|
-
* ```
|
|
26
|
-
* ```ts
|
|
27
|
-
* // *.test.ts —— 测试
|
|
28
|
-
* const testServer = await createTestServer(server)
|
|
18
|
+
* await startServer(server) // source/index.ts —— 启动
|
|
19
|
+
* await generateOpenApi(server) // build/openapi-gen.ts —— 出 spec
|
|
20
|
+
* await createTestServer(server) // *.test.ts —— 测试
|
|
29
21
|
* ```
|
|
30
22
|
*
|
|
31
23
|
* ## 两条容易踩的规矩
|
|
32
24
|
*
|
|
33
25
|
* - **本函数不读 env、不建连、不读库**。放在模块顶层是安全的:`import { server }` 不会有副作用。
|
|
34
26
|
* (需要 env 的工具进程自己调 `loadRuntimeEnvironment()`。)
|
|
35
|
-
* -
|
|
36
|
-
*
|
|
37
|
-
*
|
|
27
|
+
* - **`clients` 可省略**,其余槽位恒定:没有业务路由也要写 `routers: () => {}`。省略 `clients`
|
|
28
|
+
* → 空客户端包。其余槽位不留「可省略」分支,省掉一个就会让「这份定义到底声明了什么」需要靠猜。
|
|
29
|
+
*
|
|
30
|
+
* ## 扩展 env(`Env`):`Env` 是**第一个**泛型,类型声明,不影响加载
|
|
31
|
+
*
|
|
32
|
+
* `Env` 排在泛型首位,就是为了能**单独手写**:`defineServer<{ FOO?: string }>({ ... })` 把扩展字段
|
|
33
|
+
* 的类型并进 `ctx.env`(`resources` / `config` 的 `ctx.env` 上可读、有类型)。不写即 `Env = object`
|
|
34
|
+
* (无扩展,`ctx.env` 就是 `ConfigEnv`)。
|
|
35
|
+
*
|
|
36
|
+
* ⚠️ **TypeScript 限制(务必知道)**:只要**显式写了 `Env`**,其余四个泛型
|
|
37
|
+
* (`Resources` / `Config` / `Clients` / `Services`)就**不再从工厂推导、一律退化为 `object`**
|
|
38
|
+
* —— 这是 TS「类型参数全有或全无」的规则,不是本函数能绕过的。后果:`defineServer<MyEnv>({...})`
|
|
39
|
+
* 产出的 `server` 上,`services` 等类型会塌成 `object`,`testServer.services.xxx` 之类会丢类型。
|
|
40
|
+
*
|
|
41
|
+
* 因此两种写法各有适用:
|
|
42
|
+
* - **要完整推导**(典型业务服务,依赖 `ctx.services` / `testServer.services` 的类型):**不写泛型**,
|
|
43
|
+
* 把扩展 env 的类型写在**工厂参数标注**上——`resources: (ctx: ResourcesContext<{ FOO?: string }>) => ...`
|
|
44
|
+
* 或 point-free 传入参数已标注的命名工厂。这样 `Env` 与其余泛型**一起被推导**,不丢类型。
|
|
45
|
+
* - **只在意 `Env`、不依赖其余被推导类型**(简单服务 / 测试):直接 `defineServer<MyEnv>({...})`。
|
|
46
|
+
*
|
|
47
|
+
* ⚠️ **只声明类型,不参与加载**:env 的加载完全走框架内置逻辑(dotenv 分层 + 进程注入),框架
|
|
48
|
+
* **不提供自定义加载**;运行期扩展字段的值就是内置加载后的 `process.env[key]`(字符串直通)。
|
|
49
|
+
*
|
|
50
|
+
* ```ts
|
|
51
|
+
* // 写法一(推荐业务服务用):不写泛型,Env 从工厂参数标注推导,其余泛型一起推导
|
|
52
|
+
* function createResources(ctx: ResourcesContext<{ REDIS_KEY_PREFIX?: string }>) { ... }
|
|
53
|
+
* export const server = defineServer({ app, resources: createResources, config, services, middlewares, routers })
|
|
54
|
+
*
|
|
55
|
+
* // 写法二:显式手写 Env(首位)——简单;但其余泛型退化为 object
|
|
56
|
+
* export const server = defineServer<{ REDIS_KEY_PREFIX?: string }>({ app, resources, config, services, middlewares, routers })
|
|
57
|
+
* ```
|
|
38
58
|
*/
|
|
39
|
-
export declare function defineServer<
|
|
40
|
-
/** 供内部/调试使用:判断是否为 `defineServer()` 产物 */
|
|
41
|
-
export declare function isServer(value: unknown): value is Server<unknown, unknown, unknown, unknown>;
|
|
59
|
+
export declare function defineServer<Env = object, Resources = object, Config = object, Clients = object, Services = object>(config: ServerConfig<Resources, Config, Clients, Services, Env>): Server<Resources, Config, Clients, Services, Env>;
|
|
42
60
|
/**
|
|
43
61
|
* `startServer()`——真正跑起来:构造资源、加载配置、建客户端、建服务、装中间件、注册路由、listen、装信号。
|
|
44
62
|
*
|
|
45
63
|
* 步序**就写在本函数里**,不抽到共享的「按模式分支」的装配器:
|
|
46
64
|
* ```
|
|
47
65
|
* env → resources → core → config → clients → services → services.init() → middlewares
|
|
48
|
-
* → systemRoutes → routers → onClose → ready → listen/signal
|
|
66
|
+
* → systemRoutes → routers → onClose → ready → testdata → listen/signal
|
|
49
67
|
* ```
|
|
50
68
|
*
|
|
51
69
|
* - 最早阶段**固定加载 env**(`.env.dev < .env < .env.local`;`ENV=test` 追加 `.env.test.local`)。
|
|
@@ -55,6 +73,16 @@ export declare function isServer(value: unknown): value is Server<unknown, unkno
|
|
|
55
73
|
* 而不是同一个入口按环境变量 / 选项改行为。
|
|
56
74
|
* - 硬性要求 `Server`(品牌)产物:传裸 `ServerConfig` 是编译错误。
|
|
57
75
|
*
|
|
76
|
+
* ## `development` / `test` 下会自动装载 `testdata/`
|
|
77
|
+
*
|
|
78
|
+
* 真启动(本地 `pnpm dev`)在 `ready()` 之后、**listen 之前**装载 `<cwd>/testdata` 下的脚本并整体
|
|
79
|
+
* `load()`(见 `test-support/testdata.ts`):端口一旦对外,数据必须已经在位。脚本拿到的是装配好的
|
|
80
|
+
* ctx(`env` + `resources` + `log`),所以它们写数据用的就是被测服务读数据的那份连接。
|
|
81
|
+
* `ENV` 不在 `CLEANABLE_ENVS` 里(即生产)时**整段跳过**——这是 `startServer` 的唯一一处环境
|
|
82
|
+
* 分支,且是安静的:生产不需要测试数据,也没有什么东西需要失败。
|
|
83
|
+
*
|
|
84
|
+
* `cleanup()` 在 `startServer` 路径上不会被调用:真启动要的就是「数据在」,不是「数据不在」。
|
|
85
|
+
*
|
|
58
86
|
* ## 怎么用
|
|
59
87
|
*
|
|
60
88
|
* ```ts
|
|
@@ -71,10 +99,10 @@ export declare function isServer(value: unknown): value is Server<unknown, unkno
|
|
|
71
99
|
* 你**需要**知道的两件事:
|
|
72
100
|
* - 端口只读 env 的 `API_PORT`,**不回退 `PORT`**(`PORT` 属 nginx)。`API_PORT=0` 让内核
|
|
73
101
|
* 分配空闲端口,多实例本地调试靠它。
|
|
74
|
-
* -
|
|
102
|
+
* - 失败时已构造的资源会被按固定顺序释放(关闭流程八步),不会漏掉连接池;错误原样抛给
|
|
75
103
|
* 调用方——所以端口占用会让 `startServer` 直接 reject,需要在入口处接住。
|
|
76
104
|
*/
|
|
77
|
-
export declare function startServer<
|
|
105
|
+
export declare function startServer<Resources, Config, Clients, Services, Env>(server: Server<Resources, Config, Clients, Services, Env>): Promise<FastifyInstance>;
|
|
78
106
|
/**
|
|
79
107
|
* `generateOpenApi()`——只出 spec。
|
|
80
108
|
*
|
|
@@ -119,5 +147,5 @@ export declare function startServer<R, C, CL, S>(server: Server<R, C, CL, S>): P
|
|
|
119
147
|
* 如果你的 `routers` 里写了 `services.xxx`(而不是只把它捕获进 handler),这条路会抛
|
|
120
148
|
* `CONFIG_UNAVAILABLE`——那不是 bug,是在提醒「路由注册期不该依赖运行期产物」。
|
|
121
149
|
*/
|
|
122
|
-
export declare function generateOpenApi<
|
|
150
|
+
export declare function generateOpenApi<Resources, Config, Clients, Services, Env>(server: Server<Resources, Config, Clients, Services, Env>): Promise<OpenApiDocument>;
|
|
123
151
|
//# sourceMappingURL=entries.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../source/core/entries.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../source/core/entries.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAU,eAAe,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAc9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,wBAAgB,YAAY,CAAC,GAAG,GAAG,MAAM,EAAE,SAAS,GAAG,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,GAAG,MAAM,EAAE,QAAQ,GAAG,MAAM,EACjH,MAAM,EAAE,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,GAC9D,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,CAMnD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,CAoDhK;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,CAsBpK"}
|