@taicode/common-server 4.0.0-beta.1 → 4.0.0-beta.10
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 +18 -15
- package/output/app/index.d.ts.map +1 -1
- package/output/app/index.js +330 -18
- package/output/auth/index.d.ts +73 -0
- package/output/auth/index.d.ts.map +1 -0
- package/output/auth/index.js +196 -0
- package/output/core/assemble.d.ts +64 -0
- package/output/core/assemble.d.ts.map +1 -0
- package/output/core/assemble.js +88 -0
- package/output/core/boot/context.d.ts +109 -0
- package/output/core/boot/context.d.ts.map +1 -0
- package/output/core/boot/context.js +145 -0
- package/output/core/boot/lifecycle.d.ts +206 -0
- package/output/core/boot/lifecycle.d.ts.map +1 -0
- package/output/core/boot/lifecycle.js +255 -0
- package/output/core/boot/shutdown.d.ts +34 -0
- package/output/core/boot/shutdown.d.ts.map +1 -0
- package/output/core/boot/shutdown.js +50 -0
- package/output/core/boot/signal.d.ts +13 -0
- package/output/core/boot/signal.d.ts.map +1 -0
- package/output/{app → core/boot}/signal.js +3 -2
- package/output/core/boot/steps.d.ts +138 -0
- package/output/core/boot/steps.d.ts.map +1 -0
- package/output/core/boot/steps.js +259 -0
- package/output/core/entries.d.ts +151 -0
- package/output/core/entries.d.ts.map +1 -0
- package/output/core/entries.js +242 -0
- package/output/core/entries.test.d.ts +2 -0
- package/output/core/entries.test.d.ts.map +1 -0
- package/output/core/entries.test.js +458 -0
- package/output/core/env.d.ts +83 -0
- package/output/core/env.d.ts.map +1 -0
- package/output/core/env.js +124 -0
- package/output/{app → core}/env.test.d.ts.map +1 -1
- package/output/core/env.test.js +105 -0
- package/output/core/modules/auth-provider/index.d.ts +10 -0
- package/output/core/modules/auth-provider/index.d.ts.map +1 -0
- package/output/core/modules/auth-provider/index.js +9 -0
- package/output/core/modules/auth-provider/types.d.ts +71 -0
- package/output/core/modules/auth-provider/types.d.ts.map +1 -0
- package/output/core/modules/context/index.d.ts +18 -0
- package/output/core/modules/context/index.d.ts.map +1 -0
- package/output/core/modules/context/index.js +23 -0
- package/output/core/modules/context/index.test.d.ts.map +1 -0
- package/output/core/modules/cookie/index.d.ts +21 -0
- package/output/core/modules/cookie/index.d.ts.map +1 -0
- package/output/{app/core/cookie.js → core/modules/cookie/index.js} +1 -6
- package/output/core/modules/identity/index.d.ts +4 -0
- package/output/core/modules/identity/index.d.ts.map +1 -0
- package/output/{app/core/identity.js → core/modules/identity/index.js} +1 -1
- package/output/core/modules/logging/index.d.ts +5 -0
- package/output/core/modules/logging/index.d.ts.map +1 -0
- package/output/{app/core/logging.js → core/modules/logging/index.js} +2 -2
- package/output/core/modules/openapi/index.d.ts +7 -0
- package/output/core/modules/openapi/index.d.ts.map +1 -0
- package/output/{app/core/openapi.js → core/modules/openapi/index.js} +11 -12
- package/output/core/modules/permission-operations/index.d.ts +26 -0
- package/output/core/modules/permission-operations/index.d.ts.map +1 -0
- package/output/core/modules/permission-operations/index.js +36 -0
- package/output/core/modules/permission-operations/index.test.d.ts +2 -0
- package/output/core/modules/permission-operations/index.test.d.ts.map +1 -0
- package/output/core/modules/permission-operations/index.test.js +45 -0
- package/output/{app/core/request-id.d.ts → core/modules/request-id/index.d.ts} +1 -4
- package/output/core/modules/request-id/index.d.ts.map +1 -0
- package/output/{app/core/request-id.js → core/modules/request-id/index.js} +2 -2
- package/output/core/modules/response/index.d.ts +50 -0
- package/output/core/modules/response/index.d.ts.map +1 -0
- package/output/{app/core/response.js → core/modules/response/index.js} +31 -18
- package/output/{schema/index.d.ts → core/modules/schema/builders.d.ts} +8 -1
- package/output/core/modules/schema/builders.d.ts.map +1 -0
- package/output/{schema/index.js → core/modules/schema/builders.js} +7 -4
- package/output/core/modules/schema/index.d.ts +34 -0
- package/output/core/modules/schema/index.d.ts.map +1 -0
- package/output/core/modules/schema/index.js +62 -0
- package/output/core/modules/schema/index.test.d.ts +2 -0
- package/output/core/modules/schema/index.test.d.ts.map +1 -0
- package/output/core/modules/schema/index.test.js +55 -0
- package/output/core/modules/system-routes/index.d.ts +15 -0
- package/output/core/modules/system-routes/index.d.ts.map +1 -0
- package/output/core/modules/system-routes/index.js +205 -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 +89 -0
- package/output/core/test-support/database.d.ts.map +1 -0
- package/output/core/test-support/database.js +131 -0
- package/output/core/test-support/database.test.d.ts +2 -0
- package/output/core/test-support/database.test.d.ts.map +1 -0
- package/output/core/test-support/database.test.js +111 -0
- package/output/core/test-support/environment.d.ts +38 -0
- package/output/core/test-support/environment.d.ts.map +1 -0
- package/output/core/test-support/environment.js +45 -0
- package/output/core/test-support/service-context.d.ts +64 -0
- package/output/core/test-support/service-context.d.ts.map +1 -0
- package/output/core/test-support/service-context.js +63 -0
- package/output/core/test-support/service-context.test.d.ts +2 -0
- package/output/core/test-support/service-context.test.d.ts.map +1 -0
- package/output/core/test-support/service-context.test.js +55 -0
- package/output/core/test-support/test-server.d.ts +154 -0
- package/output/core/test-support/test-server.d.ts.map +1 -0
- package/output/core/test-support/test-server.js +114 -0
- package/output/core/test-support/test-server.test.d.ts +2 -0
- package/output/core/test-support/test-server.test.d.ts.map +1 -0
- package/output/core/test-support/test-server.test.js +296 -0
- package/output/core/test-support/testdata.d.ts +255 -0
- package/output/core/test-support/testdata.d.ts.map +1 -0
- package/output/core/test-support/testdata.js +271 -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 +334 -0
- package/output/{app → core}/type-augment.d.ts +2 -2
- package/output/core/type-augment.d.ts.map +1 -0
- package/output/core/types.d.ts +582 -0
- package/output/core/types.d.ts.map +1 -0
- package/output/cors/index.d.ts +3 -0
- package/output/cors/index.d.ts.map +1 -0
- package/output/cors/index.js +28 -0
- package/output/index.d.ts +10 -4
- package/output/index.d.ts.map +1 -1
- package/output/index.js +25 -5
- package/output/rate-limit/index.d.ts +23 -0
- package/output/rate-limit/index.d.ts.map +1 -0
- package/output/rate-limit/index.js +43 -0
- package/output/{rate-limiter/rate-limiter.d.ts → rate-limit/limiter.d.ts} +1 -1
- package/output/rate-limit/limiter.d.ts.map +1 -0
- package/output/{rate-limiter → rate-limit}/memory-store.d.ts +6 -0
- package/output/rate-limit/memory-store.d.ts.map +1 -0
- package/output/{rate-limiter → rate-limit}/memory-store.js +8 -0
- package/output/{rate-limiter → rate-limit}/redis-store.d.ts +18 -0
- package/output/rate-limit/redis-store.d.ts.map +1 -0
- package/output/{rate-limiter → rate-limit}/redis-store.js +35 -0
- package/output/rate-limit/redis-store.test.d.ts.map +1 -0
- package/output/{rate-limiter → rate-limit}/redis-store.test.js +2 -2
- package/output/rate-limit/types.d.ts +45 -0
- package/output/rate-limit/types.d.ts.map +1 -0
- package/output/rate-limit/types.js +1 -0
- package/output/{redis-cache/redis-cache.d.ts → redis/cache.d.ts} +1 -1
- package/output/redis/cache.d.ts.map +1 -0
- package/output/redis/cache.test.d.ts +2 -0
- package/output/redis/cache.test.d.ts.map +1 -0
- package/output/{redis-cache/redis-cache.test.js → redis/cache.test.js} +1 -1
- package/output/{app/redis.d.ts → redis/index.d.ts} +12 -7
- package/output/redis/index.d.ts.map +1 -0
- package/output/{app/redis.js → redis/index.js} +15 -5
- package/output/redis/index.test.d.ts +2 -0
- package/output/{context → redis}/index.test.d.ts.map +1 -1
- package/output/redis/index.test.js +55 -0
- package/output/redis-queue/index.js +1 -1
- package/output/redis-queue/test-helpers.d.ts +1 -1
- package/output/redis-queue/test-helpers.js +1 -1
- package/output/redis-queue/types.d.ts +3 -12
- package/output/redis-queue/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/output/app/auth.test.d.ts +0 -2
- package/output/app/auth.test.d.ts.map +0 -1
- package/output/app/auth.test.js +0 -148
- package/output/app/boot.d.ts +0 -48
- package/output/app/boot.d.ts.map +0 -1
- package/output/app/boot.js +0 -123
- package/output/app/config.test.d.ts +0 -2
- package/output/app/config.test.d.ts.map +0 -1
- package/output/app/config.test.js +0 -132
- package/output/app/context.d.ts +0 -68
- package/output/app/context.d.ts.map +0 -1
- package/output/app/context.js +0 -73
- package/output/app/core/cookie.d.ts +0 -25
- package/output/app/core/cookie.d.ts.map +0 -1
- package/output/app/core/identity.d.ts +0 -12
- package/output/app/core/identity.d.ts.map +0 -1
- package/output/app/core/index.d.ts +0 -44
- package/output/app/core/index.d.ts.map +0 -1
- package/output/app/core/index.js +0 -57
- package/output/app/core/logging.d.ts +0 -13
- package/output/app/core/logging.d.ts.map +0 -1
- package/output/app/core/openapi.d.ts +0 -17
- package/output/app/core/openapi.d.ts.map +0 -1
- package/output/app/core/request-id.d.ts.map +0 -1
- package/output/app/core/response.d.ts +0 -41
- package/output/app/core/response.d.ts.map +0 -1
- package/output/app/define-server.negative-cases.d.ts +0 -32
- package/output/app/define-server.negative-cases.d.ts.map +0 -1
- package/output/app/define-server.negative-cases.js +0 -209
- package/output/app/define-server.test.d.ts +0 -2
- package/output/app/define-server.test.d.ts.map +0 -1
- package/output/app/define-server.test.js +0 -97
- package/output/app/entries.d.ts +0 -29
- package/output/app/entries.d.ts.map +0 -1
- package/output/app/entries.js +0 -64
- package/output/app/env.d.ts +0 -59
- package/output/app/env.d.ts.map +0 -1
- package/output/app/env.js +0 -66
- package/output/app/env.test.js +0 -124
- package/output/app/error-contract.test.d.ts +0 -2
- package/output/app/error-contract.test.d.ts.map +0 -1
- package/output/app/error-contract.test.js +0 -125
- package/output/app/errors.d.ts +0 -20
- package/output/app/errors.d.ts.map +0 -1
- package/output/app/errors.js +0 -51
- package/output/app/generate-openapi.test.d.ts +0 -2
- package/output/app/generate-openapi.test.d.ts.map +0 -1
- package/output/app/generate-openapi.test.js +0 -123
- package/output/app/lifecycle-logging.test.d.ts +0 -2
- package/output/app/lifecycle-logging.test.d.ts.map +0 -1
- package/output/app/lifecycle-logging.test.js +0 -155
- package/output/app/lifecycle.d.ts +0 -70
- package/output/app/lifecycle.d.ts.map +0 -1
- package/output/app/lifecycle.js +0 -99
- package/output/app/lifecycle.test.d.ts +0 -2
- package/output/app/lifecycle.test.d.ts.map +0 -1
- package/output/app/lifecycle.test.js +0 -152
- package/output/app/middleware/auth.d.ts +0 -71
- package/output/app/middleware/auth.d.ts.map +0 -1
- package/output/app/middleware/auth.js +0 -190
- package/output/app/middleware/context.d.ts +0 -13
- package/output/app/middleware/context.d.ts.map +0 -1
- package/output/app/middleware/context.js +0 -15
- package/output/app/middleware/cors.d.ts +0 -14
- package/output/app/middleware/cors.d.ts.map +0 -1
- package/output/app/middleware/cors.js +0 -30
- package/output/app/middleware/index.d.ts +0 -17
- package/output/app/middleware/index.d.ts.map +0 -1
- package/output/app/middleware/index.js +0 -16
- package/output/app/middleware/rate-limit.d.ts +0 -24
- package/output/app/middleware/rate-limit.d.ts.map +0 -1
- package/output/app/middleware/rate-limit.js +0 -46
- package/output/app/middleware-order.test.d.ts +0 -2
- package/output/app/middleware-order.test.d.ts.map +0 -1
- package/output/app/middleware-order.test.js +0 -148
- package/output/app/permission-operations.d.ts +0 -48
- package/output/app/permission-operations.d.ts.map +0 -1
- package/output/app/permission-operations.js +0 -77
- package/output/app/permission-operations.test.d.ts +0 -2
- package/output/app/permission-operations.test.d.ts.map +0 -1
- package/output/app/permission-operations.test.js +0 -72
- package/output/app/permission-registration.test.d.ts +0 -2
- package/output/app/permission-registration.test.d.ts.map +0 -1
- package/output/app/permission-registration.test.js +0 -138
- package/output/app/redis.d.ts.map +0 -1
- package/output/app/registered-operations.test.d.ts +0 -2
- package/output/app/registered-operations.test.d.ts.map +0 -1
- package/output/app/registered-operations.test.js +0 -57
- package/output/app/request-id.test.d.ts +0 -2
- package/output/app/request-id.test.d.ts.map +0 -1
- package/output/app/request-id.test.js +0 -130
- package/output/app/resources.test.d.ts +0 -2
- package/output/app/resources.test.d.ts.map +0 -1
- package/output/app/resources.test.js +0 -197
- package/output/app/shutdown.d.ts +0 -22
- package/output/app/shutdown.d.ts.map +0 -1
- package/output/app/shutdown.js +0 -30
- package/output/app/shutdown.test.d.ts +0 -2
- package/output/app/shutdown.test.d.ts.map +0 -1
- package/output/app/shutdown.test.js +0 -103
- package/output/app/signal.d.ts +0 -12
- package/output/app/signal.d.ts.map +0 -1
- package/output/app/smoke.test.d.ts +0 -2
- package/output/app/smoke.test.d.ts.map +0 -1
- package/output/app/smoke.test.js +0 -210
- package/output/app/test-support.d.ts +0 -21
- package/output/app/test-support.d.ts.map +0 -1
- package/output/app/test-support.js +0 -24
- package/output/app/type-augment.d.ts.map +0 -1
- package/output/app/types.d.ts +0 -211
- package/output/app/types.d.ts.map +0 -1
- package/output/context/index.d.ts +0 -6
- package/output/context/index.d.ts.map +0 -1
- package/output/context/index.js +0 -14
- package/output/rate-limiter/index.d.ts +0 -5
- package/output/rate-limiter/index.d.ts.map +0 -1
- package/output/rate-limiter/index.js +0 -4
- package/output/rate-limiter/memory-store.d.ts.map +0 -1
- package/output/rate-limiter/rate-limiter.d.ts.map +0 -1
- package/output/rate-limiter/redis-store.d.ts.map +0 -1
- package/output/rate-limiter/redis-store.test.d.ts.map +0 -1
- package/output/rate-limiter/types.d.ts +0 -27
- package/output/rate-limiter/types.d.ts.map +0 -1
- package/output/redis-cache/index.d.ts +0 -4
- package/output/redis-cache/index.d.ts.map +0 -1
- package/output/redis-cache/index.js +0 -1
- package/output/redis-cache/redis-cache.d.ts.map +0 -1
- package/output/redis-cache/redis-cache.test.d.ts +0 -2
- package/output/redis-cache/redis-cache.test.d.ts.map +0 -1
- package/output/schema/index.d.ts.map +0 -1
- /package/output/{app → core}/env.test.d.ts +0 -0
- /package/output/{rate-limiter → core/modules/auth-provider}/types.js +0 -0
- /package/output/{context → core/modules/context}/index.test.d.ts +0 -0
- /package/output/{context → core/modules/context}/index.test.js +0 -0
- /package/output/{app → core}/type-augment.js +0 -0
- /package/output/{app → core}/types.js +0 -0
- /package/output/{rate-limiter/rate-limiter.js → rate-limit/limiter.js} +0 -0
- /package/output/{rate-limiter → rate-limit}/redis-store.test.d.ts +0 -0
- /package/output/{redis-cache/redis-cache.js → redis/cache.js} +0 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import type { Disposer } from '@taicode/common-base';
|
|
2
|
+
import type { LoggerLike } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* 生命周期序(N1 §4 / N2 §2.5)。
|
|
5
|
+
*
|
|
6
|
+
* **每个阶段一个 `Disposer`**(`ctx.disposer`,所有槽位都有):在创建某个东西的地方当场
|
|
7
|
+
* `ctx.disposer.add(() => 释放它)` —— 创建与释放同处一地。
|
|
8
|
+
*
|
|
9
|
+
* 关闭时按阶段**逆序 flush**:`routers → middlewares → services → clients → config → resources`
|
|
10
|
+
* (后创建的先销毁;Prisma / Redis 这类资源最后才断)。
|
|
11
|
+
*
|
|
12
|
+
* **`clients` 为什么排在 `services` 之后关**:服务单元 `dispose()` 时可能还要落一次外部调用
|
|
13
|
+
* (落库、通知、上报),客户端必须先活着。
|
|
14
|
+
*
|
|
15
|
+
* **`services` 额外有逐单元生命周期**(两个相位,语义互不替代):
|
|
16
|
+
* - 单元之间**不得相互依赖**(`services` 的 ctx 没有 `services` 字段,编译期禁止);
|
|
17
|
+
* - `init`(建状态)按**对象声明序**;
|
|
18
|
+
* - `dispose`(终态释放)**逆序**,只出现在关闭流程里;
|
|
19
|
+
* - services 阶段内部**先 flush `ctx.disposer`**(构造期注册的清理),再逐单元 `dispose`。
|
|
20
|
+
*
|
|
21
|
+
* ## 没有 `reset` 相位
|
|
22
|
+
*
|
|
23
|
+
* 「只回初始态、不释放实例」曾经存在过(单元上的 `reset?.()`)。它被
|
|
24
|
+
* **整体删除**:不复位实例却想复位世界,必然漏掉装配期只跑一次的东西(`onReady` 副作用、
|
|
25
|
+
* `config` 快照、连接池)。用例间的复位由 `TestServer.reprovision()` 承担——关掉实例、重新
|
|
26
|
+
* 装配一遍,新实例的 `init()` 天然给出初始态。删掉这条路径后,单元上不再有「既要在关闭时
|
|
27
|
+
* 调用、又要能在生命周期中途调用」的双重语义。
|
|
28
|
+
*
|
|
29
|
+
* ## `clean` 相位
|
|
30
|
+
*
|
|
31
|
+
* `clean` 是**测试期专属**的「清数据」相位(清行、不清结构),对 `resources` 与 `services`
|
|
32
|
+
* 都成立。它不在关闭流程里,只由 `TestServer.reprovision()` 在重新装配时调用一次,因此
|
|
33
|
+
* 失败**冒泡**(清不干净还继续跑,后面全是伪绿)。
|
|
34
|
+
*
|
|
35
|
+
* `middlewares` 按**数组顺序**注册;它只有 `disposer`,没有逐单元生命周期。
|
|
36
|
+
*/
|
|
37
|
+
export declare const RESOURCES_STAGE = "resources";
|
|
38
|
+
export declare const CONFIG_STAGE = "config";
|
|
39
|
+
export declare const CLIENTS_STAGE = "clients";
|
|
40
|
+
export declare const SERVICES_STAGE = "services";
|
|
41
|
+
export declare const MIDDLEWARES_STAGE = "middlewares";
|
|
42
|
+
export declare const ROUTERS_STAGE = "routers";
|
|
43
|
+
/** 生命周期相位(逐单元日志字段 `phase`) */
|
|
44
|
+
export declare const LIFECYCLE_PHASE_SETUP = "setup";
|
|
45
|
+
export declare const LIFECYCLE_PHASE_INIT = "init";
|
|
46
|
+
export declare const LIFECYCLE_PHASE_DISPOSE = "dispose";
|
|
47
|
+
export declare const LIFECYCLE_PHASE_CLEAN = "clean";
|
|
48
|
+
export type LifecyclePhase = typeof LIFECYCLE_PHASE_SETUP | typeof LIFECYCLE_PHASE_INIT | typeof LIFECYCLE_PHASE_DISPOSE | typeof LIFECYCLE_PHASE_CLEAN;
|
|
49
|
+
/**
|
|
50
|
+
* 逐单元生命周期日志上下文。
|
|
51
|
+
*
|
|
52
|
+
* `log` / `service` 缺省时**不打日志**(纯单测可直接调用生命周期函数,不注入假 logger)。
|
|
53
|
+
* 字段名固定为全拼:`service` + `unit` + `stage` + `phase`。
|
|
54
|
+
*/
|
|
55
|
+
export interface LifecycleLogContext {
|
|
56
|
+
/** 日志标签(`defineServer({ app: { service } })`);缺省时退路名字失去前缀 */
|
|
57
|
+
service?: string;
|
|
58
|
+
/** 记录型 logger(`ctx.log`);缺省 = 不打日志 */
|
|
59
|
+
log?: LoggerLike;
|
|
60
|
+
}
|
|
61
|
+
/** 逐单元日志字段(全拼):`unit` = 有效名字(阶段 flush 时为阶段名),`stage` = 阶段名 */
|
|
62
|
+
export interface LifecycleUnitLogFields {
|
|
63
|
+
service: string;
|
|
64
|
+
unit: string;
|
|
65
|
+
stage: string;
|
|
66
|
+
phase: LifecyclePhase;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* `middlewares` 日志名字:**数组项的名字**(`MiddlewareUnit.name`,或裸函数的占位名 `middleware[i]`)。
|
|
70
|
+
*
|
|
71
|
+
* 名字为空串时退路 `<service>:middleware`(仅用于注册期日志,不影响注册序——顺序由数组位置决定)。
|
|
72
|
+
*/
|
|
73
|
+
export declare function middlewareUnitName(service: string, name: string): string;
|
|
74
|
+
/** `initServiceUnits` 入参(参数签名内联对象类型已按 lint 规则外提) */
|
|
75
|
+
export interface InitServiceUnitsParams extends LifecycleLogContext {
|
|
76
|
+
/** `services(ctx)` 返回的对象(**对象键即单元名**) */
|
|
77
|
+
services?: Record<string, unknown>;
|
|
78
|
+
}
|
|
79
|
+
/** `setupResourceUnits` / `cleanResourceUnits` / `disposeResourceUnits` 入参(资源单元有 `setup` / `clean` / `dispose`) */
|
|
80
|
+
export interface ResourceLifecycleParams extends LifecycleLogContext {
|
|
81
|
+
/** 有生命周期方法的资源单元(**按 `resources` 对象声明序**) */
|
|
82
|
+
units?: readonly ResourceSetupUnit[];
|
|
83
|
+
}
|
|
84
|
+
/** 一个资源单元的可执行生命周期(含名字,用于日志) */
|
|
85
|
+
export interface ResourceSetupUnit {
|
|
86
|
+
/** `resources` 对象里的键 */
|
|
87
|
+
name: string;
|
|
88
|
+
/** `setup()` 本身(已从单元外壳上取好);无 `setup` 时为 `undefined` */
|
|
89
|
+
setup?: () => void | Promise<void>;
|
|
90
|
+
/** `dispose()` 本身(已从单元外壳上取好);无 `dispose` 时为 `undefined` */
|
|
91
|
+
dispose?: () => void | Promise<void>;
|
|
92
|
+
/** `clean()` 本身(已从单元外壳上取好);无 `clean` 时为 `undefined` */
|
|
93
|
+
clean?: () => void | Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 启动序:`services` 按**对象声明序**逐项 `await init?.()`;逐项打日志。
|
|
97
|
+
*/
|
|
98
|
+
export declare function initServiceUnits(params: InitServiceUnitsParams): Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* **测试期清数据**:`services` 按**对象声明序**逐项 `await clean?.()`;逐项打日志。
|
|
101
|
+
*
|
|
102
|
+
* ## 它解决的是「库外的服务数据」,不是「内存里的状态」
|
|
103
|
+
*
|
|
104
|
+
* 库里那部分数据由资源单元的 `clean()` 负责(`cleanResourceUnits`),因为只有持有连接的
|
|
105
|
+
* 单元才知道自己的方言与表。但有些服务数据并不在**业务的**库里:
|
|
106
|
+
*
|
|
107
|
+
* - 队列命名空间里的 key;
|
|
108
|
+
* - 临时目录 / 临时文件;
|
|
109
|
+
* - 外部 mock 的预置状态。
|
|
110
|
+
*
|
|
111
|
+
* 删库删不掉这些,而把它们留在世上就成了下一条用例的隐形污染。由单元自己声明 `clean()`
|
|
112
|
+
* 后,清理变成**单元的公开契约**:单元作者知道怎么清,测试只需要说「给我一份干净基线」。
|
|
113
|
+
*
|
|
114
|
+
* ## 与 `init` / `dispose` 的关系
|
|
115
|
+
*
|
|
116
|
+
* - `init()` 建状态,`clean()` 只清**数据**、不释放句柄、不重置实例;
|
|
117
|
+
* - `clean()` **不是** `dispose()`:调用后单元仍可用(但数据已空,只适合紧接着重新装配);
|
|
118
|
+
* - 没有 `clean` 的单元被跳过(只有持有库外数据的单元需要实现);
|
|
119
|
+
* - 失败**冒泡**:清理没做干净就继续跑,后面的断言全是鬼。
|
|
120
|
+
*
|
|
121
|
+
* ## 没有「内存态」相位
|
|
122
|
+
*
|
|
123
|
+
* 内存态(限流桶、进程内缓存、写入缓冲区)不靠任何 `reset?.()` 拨回,而由调用方重新装配——
|
|
124
|
+
* 新实例的 `init()` 天然给出初始态。这比「不复位实例而复位世界」可靠:后者必然漏掉装配期
|
|
125
|
+
* 只跑一次的东西。
|
|
126
|
+
*/
|
|
127
|
+
export declare function cleanServiceUnits(params: InitServiceUnitsParams): Promise<void>;
|
|
128
|
+
/**
|
|
129
|
+
* `resources` 的装配期准备:按**对象声明序**逐项 `await setup()`;逐项打日志。
|
|
130
|
+
*
|
|
131
|
+
* 与 `initServiceUnits` 同形,差别在单元来源:资源值往往是第三方对象(没有 `setup` 方法),
|
|
132
|
+
* 单元外壳由 `buildResources` 在解包时收集成 `{ name, setup, dispose }`。
|
|
133
|
+
*
|
|
134
|
+
* **失败即中断**:资源准备失败时后续资源、`config`、`services` 都没有意义,直接冒泡给入口,
|
|
135
|
+
* 由入口把已构造的资源释放掉(与「端口占用」同一处理)。不放进关闭流程聚合——那不是关闭失败。
|
|
136
|
+
*/
|
|
137
|
+
export declare function setupResourceUnits(params: ResourceLifecycleParams): Promise<void>;
|
|
138
|
+
/**
|
|
139
|
+
* `resources` 的测试期清理:按**对象声明序**逐项 `await clean()`;逐项打日志。
|
|
140
|
+
*
|
|
141
|
+
* **失败即冒泡**:清理没做干净就重新装配,后面全是伪绿。
|
|
142
|
+
*
|
|
143
|
+
* 「资源 → 服务」的先后由 `cleanServerUnits` 编排,本函数只负责「资源这一段」。
|
|
144
|
+
*/
|
|
145
|
+
export declare function cleanResourceUnits(params: ResourceLifecycleParams): Promise<void>;
|
|
146
|
+
/** `cleanServerUnits` 入参(一次装配的两类单元:资源单元 + 服务单元) */
|
|
147
|
+
export interface CleanServerUnitsParams extends LifecycleLogContext {
|
|
148
|
+
/** `buildResources` 收集的资源单元(**按 `resources` 对象声明序**) */
|
|
149
|
+
resources?: readonly ResourceSetupUnit[];
|
|
150
|
+
/** `services(ctx)` 返回的对象(**对象键即单元名**) */
|
|
151
|
+
services?: Record<string, unknown>;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* **测试期清数据**:把一次装配里「有 `clean()` 的单元」按固定顺序全跑一遍。
|
|
155
|
+
*
|
|
156
|
+
* 顺序不能倒:资源在前、服务在后。数据库资源必须排**最前**(它持有 `TRUNCATE ... CASCADE`),
|
|
157
|
+
* 服务单元的 `clean()` 之后才轮得到「库外的数据」——先清库再清外部,反过来会把刚插进 mock
|
|
158
|
+
* 的引用撞成悬空。
|
|
159
|
+
*
|
|
160
|
+
* ## 为什么编排在这里,而不是调用方
|
|
161
|
+
*
|
|
162
|
+
* 「清哪些、按什么顺序清」是**装配层的事实**:只有 `buildResources` 才知道资源单元是什么、
|
|
163
|
+
* 只有 `initServiceUnits` 的同一份 `services` 对象才知道服务单元有哪些。测试入口只需要知道
|
|
164
|
+
* 「我要一份干净基线」——它不该拿到 `ResourceSetupUnit[]` 这种下层产物,更不该自己拼顺序。
|
|
165
|
+
*
|
|
166
|
+
* 因此本函数是**唯一**的清数据入口;`TestServer.reprovision()` 只调它,不直接碰下面两个分步。
|
|
167
|
+
*
|
|
168
|
+
* **失败冒泡**:清不干净还继续装配,后面的断言全是鬼。
|
|
169
|
+
*/
|
|
170
|
+
export declare function cleanServerUnits(params: CleanServerUnitsParams): Promise<void>;
|
|
171
|
+
/**
|
|
172
|
+
* `resources` 的关闭:按声明序**逆序**逐项 `await dispose()`。
|
|
173
|
+
*
|
|
174
|
+
* 与 `disposeServices` 同语义:单元级失败已 `log.error`,此处吞掉以保证单个资源不阻断其他资源。
|
|
175
|
+
*
|
|
176
|
+
* 在关闭流程中排在 `flushResources`(阶段 `Disposer`)**之前**:单元 `dispose` 是资源自己的
|
|
177
|
+
* 正事(断连),而 `disposer` 是本阶段临时产物的逃生口,后者不应挡住前者。
|
|
178
|
+
*/
|
|
179
|
+
export declare function disposeResourceUnits(params: ResourceLifecycleParams): Promise<void>;
|
|
180
|
+
/** `buildDisposeOrder` / `disposeServiceUnits` 入参(参数签名内联对象类型已按 lint 规则外提) */
|
|
181
|
+
export interface DisposeServiceUnitsParams extends LifecycleLogContext {
|
|
182
|
+
/** `services(ctx)` 返回的对象(**对象键即单元名**) */
|
|
183
|
+
services?: Record<string, unknown>;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 关闭步 `disposeServices`:`services` 声明序逆序释放。
|
|
187
|
+
*
|
|
188
|
+
* 单元级失败已在 `runLifecycleUnit` 内 `log.error`(带 service / unit / stage / phase);
|
|
189
|
+
* 此处仅保证单步不阻断后续。
|
|
190
|
+
*/
|
|
191
|
+
export declare function disposeServices(params: DisposeServiceUnitsParams): Promise<void>;
|
|
192
|
+
/** `disposeStage` 入参(参数签名内联对象类型已按 lint 规则外提) */
|
|
193
|
+
export interface DisposeStageParams extends LifecycleLogContext {
|
|
194
|
+
/** 阶段名(`routers` / `middlewares` / `services` / `config` / `resources`) */
|
|
195
|
+
stage: string;
|
|
196
|
+
/** 该阶段的 `Disposer`(构造期注册的清理函数,**逆注册序**执行) */
|
|
197
|
+
disposer: Disposer;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* 关闭步:flush 某个阶段的 `disposer`。
|
|
201
|
+
*
|
|
202
|
+
* 单元名 = 阶段名(一个阶段一个 `Disposer`,不逐注册项打日志);`phase` 固定 `dispose`。
|
|
203
|
+
* 单个清理函数失败由 `Disposer` 自己捕获记录,不影响同阶段其它函数。
|
|
204
|
+
*/
|
|
205
|
+
export declare function disposeStage(params: DisposeStageParams): Promise<void>;
|
|
206
|
+
//# sourceMappingURL=lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../../source/core/boot/lifecycle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 生命周期序(N1 §4 / N2 §2.5)。
|
|
3
|
+
*
|
|
4
|
+
* **每个阶段一个 `Disposer`**(`ctx.disposer`,所有槽位都有):在创建某个东西的地方当场
|
|
5
|
+
* `ctx.disposer.add(() => 释放它)` —— 创建与释放同处一地。
|
|
6
|
+
*
|
|
7
|
+
* 关闭时按阶段**逆序 flush**:`routers → middlewares → services → clients → config → resources`
|
|
8
|
+
* (后创建的先销毁;Prisma / Redis 这类资源最后才断)。
|
|
9
|
+
*
|
|
10
|
+
* **`clients` 为什么排在 `services` 之后关**:服务单元 `dispose()` 时可能还要落一次外部调用
|
|
11
|
+
* (落库、通知、上报),客户端必须先活着。
|
|
12
|
+
*
|
|
13
|
+
* **`services` 额外有逐单元生命周期**(两个相位,语义互不替代):
|
|
14
|
+
* - 单元之间**不得相互依赖**(`services` 的 ctx 没有 `services` 字段,编译期禁止);
|
|
15
|
+
* - `init`(建状态)按**对象声明序**;
|
|
16
|
+
* - `dispose`(终态释放)**逆序**,只出现在关闭流程里;
|
|
17
|
+
* - services 阶段内部**先 flush `ctx.disposer`**(构造期注册的清理),再逐单元 `dispose`。
|
|
18
|
+
*
|
|
19
|
+
* ## 没有 `reset` 相位
|
|
20
|
+
*
|
|
21
|
+
* 「只回初始态、不释放实例」曾经存在过(单元上的 `reset?.()`)。它被
|
|
22
|
+
* **整体删除**:不复位实例却想复位世界,必然漏掉装配期只跑一次的东西(`onReady` 副作用、
|
|
23
|
+
* `config` 快照、连接池)。用例间的复位由 `TestServer.reprovision()` 承担——关掉实例、重新
|
|
24
|
+
* 装配一遍,新实例的 `init()` 天然给出初始态。删掉这条路径后,单元上不再有「既要在关闭时
|
|
25
|
+
* 调用、又要能在生命周期中途调用」的双重语义。
|
|
26
|
+
*
|
|
27
|
+
* ## `clean` 相位
|
|
28
|
+
*
|
|
29
|
+
* `clean` 是**测试期专属**的「清数据」相位(清行、不清结构),对 `resources` 与 `services`
|
|
30
|
+
* 都成立。它不在关闭流程里,只由 `TestServer.reprovision()` 在重新装配时调用一次,因此
|
|
31
|
+
* 失败**冒泡**(清不干净还继续跑,后面全是伪绿)。
|
|
32
|
+
*
|
|
33
|
+
* `middlewares` 按**数组顺序**注册;它只有 `disposer`,没有逐单元生命周期。
|
|
34
|
+
*/
|
|
35
|
+
export const RESOURCES_STAGE = 'resources';
|
|
36
|
+
export const CONFIG_STAGE = 'config';
|
|
37
|
+
export const CLIENTS_STAGE = 'clients';
|
|
38
|
+
export const SERVICES_STAGE = 'services';
|
|
39
|
+
export const MIDDLEWARES_STAGE = 'middlewares';
|
|
40
|
+
export const ROUTERS_STAGE = 'routers';
|
|
41
|
+
/** 生命周期相位(逐单元日志字段 `phase`) */
|
|
42
|
+
export const LIFECYCLE_PHASE_SETUP = 'setup';
|
|
43
|
+
export const LIFECYCLE_PHASE_INIT = 'init';
|
|
44
|
+
export const LIFECYCLE_PHASE_DISPOSE = 'dispose';
|
|
45
|
+
export const LIFECYCLE_PHASE_CLEAN = 'clean';
|
|
46
|
+
function isObject(value) {
|
|
47
|
+
return typeof value === 'object' && value !== null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* `middlewares` 日志名字:**数组项的名字**(`MiddlewareUnit.name`,或裸函数的占位名 `middleware[i]`)。
|
|
51
|
+
*
|
|
52
|
+
* 名字为空串时退路 `<service>:middleware`(仅用于注册期日志,不影响注册序——顺序由数组位置决定)。
|
|
53
|
+
*/
|
|
54
|
+
export function middlewareUnitName(service, name) {
|
|
55
|
+
return name.length > 0 ? name : `${service}:middleware`;
|
|
56
|
+
}
|
|
57
|
+
async function callLifecycleMethod(unit, method) {
|
|
58
|
+
if (isObject(unit) && typeof unit[method] === 'function') {
|
|
59
|
+
await unit[method]();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 执行单个生命周期单元(init / clean / dispose),并打逐单元日志。
|
|
64
|
+
*
|
|
65
|
+
* 失败时 `error` 后**重新抛出**:调用方(`startServer` 直接冒泡;关闭流程逐步骤聚合)自行决定后续。
|
|
66
|
+
*/
|
|
67
|
+
async function runLifecycleUnit(params, unit, value, phase) {
|
|
68
|
+
const fields = { service: params.service ?? '', unit, stage: SERVICES_STAGE, phase };
|
|
69
|
+
params.log?.debug(fields, `[lifecycle] ${phase} "${unit}" start`);
|
|
70
|
+
try {
|
|
71
|
+
if (phase === LIFECYCLE_PHASE_INIT) {
|
|
72
|
+
await callLifecycleMethod(value, 'init');
|
|
73
|
+
}
|
|
74
|
+
else if (phase === LIFECYCLE_PHASE_CLEAN) {
|
|
75
|
+
await callLifecycleMethod(value, 'clean');
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
await callLifecycleMethod(value, 'dispose');
|
|
79
|
+
}
|
|
80
|
+
params.log?.debug(fields, `[lifecycle] ${phase} "${unit}" done`);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
params.log?.error({ ...fields, error }, `[lifecycle] ${phase} "${unit}" failed`);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 启动序:`services` 按**对象声明序**逐项 `await init?.()`;逐项打日志。
|
|
89
|
+
*/
|
|
90
|
+
export async function initServiceUnits(params) {
|
|
91
|
+
for (const key of Object.keys(params.services ?? {})) {
|
|
92
|
+
await runLifecycleUnit(params, key, params.services?.[key], LIFECYCLE_PHASE_INIT);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* **测试期清数据**:`services` 按**对象声明序**逐项 `await clean?.()`;逐项打日志。
|
|
97
|
+
*
|
|
98
|
+
* ## 它解决的是「库外的服务数据」,不是「内存里的状态」
|
|
99
|
+
*
|
|
100
|
+
* 库里那部分数据由资源单元的 `clean()` 负责(`cleanResourceUnits`),因为只有持有连接的
|
|
101
|
+
* 单元才知道自己的方言与表。但有些服务数据并不在**业务的**库里:
|
|
102
|
+
*
|
|
103
|
+
* - 队列命名空间里的 key;
|
|
104
|
+
* - 临时目录 / 临时文件;
|
|
105
|
+
* - 外部 mock 的预置状态。
|
|
106
|
+
*
|
|
107
|
+
* 删库删不掉这些,而把它们留在世上就成了下一条用例的隐形污染。由单元自己声明 `clean()`
|
|
108
|
+
* 后,清理变成**单元的公开契约**:单元作者知道怎么清,测试只需要说「给我一份干净基线」。
|
|
109
|
+
*
|
|
110
|
+
* ## 与 `init` / `dispose` 的关系
|
|
111
|
+
*
|
|
112
|
+
* - `init()` 建状态,`clean()` 只清**数据**、不释放句柄、不重置实例;
|
|
113
|
+
* - `clean()` **不是** `dispose()`:调用后单元仍可用(但数据已空,只适合紧接着重新装配);
|
|
114
|
+
* - 没有 `clean` 的单元被跳过(只有持有库外数据的单元需要实现);
|
|
115
|
+
* - 失败**冒泡**:清理没做干净就继续跑,后面的断言全是鬼。
|
|
116
|
+
*
|
|
117
|
+
* ## 没有「内存态」相位
|
|
118
|
+
*
|
|
119
|
+
* 内存态(限流桶、进程内缓存、写入缓冲区)不靠任何 `reset?.()` 拨回,而由调用方重新装配——
|
|
120
|
+
* 新实例的 `init()` 天然给出初始态。这比「不复位实例而复位世界」可靠:后者必然漏掉装配期
|
|
121
|
+
* 只跑一次的东西。
|
|
122
|
+
*/
|
|
123
|
+
export async function cleanServiceUnits(params) {
|
|
124
|
+
for (const key of Object.keys(params.services ?? {})) {
|
|
125
|
+
await runLifecycleUnit(params, key, params.services?.[key], LIFECYCLE_PHASE_CLEAN);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const RESOURCE_STEP_KEY = {
|
|
129
|
+
[LIFECYCLE_PHASE_SETUP]: 'setup',
|
|
130
|
+
[LIFECYCLE_PHASE_DISPOSE]: 'dispose',
|
|
131
|
+
[LIFECYCLE_PHASE_CLEAN]: 'clean',
|
|
132
|
+
};
|
|
133
|
+
/** 跑一个资源单元生命周期步骤(统一日志,失败冒泡给调用方决定) */
|
|
134
|
+
async function runResourceUnit(params, unit, phase) {
|
|
135
|
+
const step = unit[RESOURCE_STEP_KEY[phase]];
|
|
136
|
+
if (!step) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const fields = { service: params.service ?? '', unit: unit.name, stage: RESOURCES_STAGE, phase };
|
|
140
|
+
params.log?.debug(fields, `[lifecycle] ${phase} "${unit.name}" start`);
|
|
141
|
+
try {
|
|
142
|
+
await step();
|
|
143
|
+
params.log?.debug(fields, `[lifecycle] ${phase} "${unit.name}" done`);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
params.log?.error({ ...fields, error }, `[lifecycle] ${phase} "${unit.name}" failed`);
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* `resources` 的装配期准备:按**对象声明序**逐项 `await setup()`;逐项打日志。
|
|
152
|
+
*
|
|
153
|
+
* 与 `initServiceUnits` 同形,差别在单元来源:资源值往往是第三方对象(没有 `setup` 方法),
|
|
154
|
+
* 单元外壳由 `buildResources` 在解包时收集成 `{ name, setup, dispose }`。
|
|
155
|
+
*
|
|
156
|
+
* **失败即中断**:资源准备失败时后续资源、`config`、`services` 都没有意义,直接冒泡给入口,
|
|
157
|
+
* 由入口把已构造的资源释放掉(与「端口占用」同一处理)。不放进关闭流程聚合——那不是关闭失败。
|
|
158
|
+
*/
|
|
159
|
+
export async function setupResourceUnits(params) {
|
|
160
|
+
for (const unit of params.units ?? []) {
|
|
161
|
+
await runResourceUnit(params, unit, LIFECYCLE_PHASE_SETUP);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* `resources` 的测试期清理:按**对象声明序**逐项 `await clean()`;逐项打日志。
|
|
166
|
+
*
|
|
167
|
+
* **失败即冒泡**:清理没做干净就重新装配,后面全是伪绿。
|
|
168
|
+
*
|
|
169
|
+
* 「资源 → 服务」的先后由 `cleanServerUnits` 编排,本函数只负责「资源这一段」。
|
|
170
|
+
*/
|
|
171
|
+
export async function cleanResourceUnits(params) {
|
|
172
|
+
for (const unit of params.units ?? []) {
|
|
173
|
+
await runResourceUnit(params, unit, LIFECYCLE_PHASE_CLEAN);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* **测试期清数据**:把一次装配里「有 `clean()` 的单元」按固定顺序全跑一遍。
|
|
178
|
+
*
|
|
179
|
+
* 顺序不能倒:资源在前、服务在后。数据库资源必须排**最前**(它持有 `TRUNCATE ... CASCADE`),
|
|
180
|
+
* 服务单元的 `clean()` 之后才轮得到「库外的数据」——先清库再清外部,反过来会把刚插进 mock
|
|
181
|
+
* 的引用撞成悬空。
|
|
182
|
+
*
|
|
183
|
+
* ## 为什么编排在这里,而不是调用方
|
|
184
|
+
*
|
|
185
|
+
* 「清哪些、按什么顺序清」是**装配层的事实**:只有 `buildResources` 才知道资源单元是什么、
|
|
186
|
+
* 只有 `initServiceUnits` 的同一份 `services` 对象才知道服务单元有哪些。测试入口只需要知道
|
|
187
|
+
* 「我要一份干净基线」——它不该拿到 `ResourceSetupUnit[]` 这种下层产物,更不该自己拼顺序。
|
|
188
|
+
*
|
|
189
|
+
* 因此本函数是**唯一**的清数据入口;`TestServer.reprovision()` 只调它,不直接碰下面两个分步。
|
|
190
|
+
*
|
|
191
|
+
* **失败冒泡**:清不干净还继续装配,后面的断言全是鬼。
|
|
192
|
+
*/
|
|
193
|
+
export async function cleanServerUnits(params) {
|
|
194
|
+
await cleanResourceUnits({ service: params.service, log: params.log, units: params.resources });
|
|
195
|
+
await cleanServiceUnits({ service: params.service, log: params.log, services: params.services });
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* `resources` 的关闭:按声明序**逆序**逐项 `await dispose()`。
|
|
199
|
+
*
|
|
200
|
+
* 与 `disposeServices` 同语义:单元级失败已 `log.error`,此处吞掉以保证单个资源不阻断其他资源。
|
|
201
|
+
*
|
|
202
|
+
* 在关闭流程中排在 `flushResources`(阶段 `Disposer`)**之前**:单元 `dispose` 是资源自己的
|
|
203
|
+
* 正事(断连),而 `disposer` 是本阶段临时产物的逃生口,后者不应挡住前者。
|
|
204
|
+
*/
|
|
205
|
+
export async function disposeResourceUnits(params) {
|
|
206
|
+
for (const unit of [...(params.units ?? [])].reverse()) {
|
|
207
|
+
try {
|
|
208
|
+
await runResourceUnit(params, unit, LIFECYCLE_PHASE_DISPOSE);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// 单元级已 log.error;此处仅保证不阻断其他资源
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* 关闭序:`services` 按声明序**逆序**逐项 `dispose?.()`。
|
|
217
|
+
*/
|
|
218
|
+
function buildDisposeOrder(params) {
|
|
219
|
+
return Object.keys(params.services ?? {})
|
|
220
|
+
.reverse()
|
|
221
|
+
.map((key) => () => runLifecycleUnit(params, key, params.services?.[key], LIFECYCLE_PHASE_DISPOSE));
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* 关闭步 `disposeServices`:`services` 声明序逆序释放。
|
|
225
|
+
*
|
|
226
|
+
* 单元级失败已在 `runLifecycleUnit` 内 `log.error`(带 service / unit / stage / phase);
|
|
227
|
+
* 此处仅保证单步不阻断后续。
|
|
228
|
+
*/
|
|
229
|
+
export async function disposeServices(params) {
|
|
230
|
+
for (const step of buildDisposeOrder(params)) {
|
|
231
|
+
try {
|
|
232
|
+
await step();
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
// 单元级 `runLifecycleUnit` 已 log.error;此处仅保证不阻断后续
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* 关闭步:flush 某个阶段的 `disposer`。
|
|
241
|
+
*
|
|
242
|
+
* 单元名 = 阶段名(一个阶段一个 `Disposer`,不逐注册项打日志);`phase` 固定 `dispose`。
|
|
243
|
+
* 单个清理函数失败由 `Disposer` 自己捕获记录,不影响同阶段其它函数。
|
|
244
|
+
*/
|
|
245
|
+
export async function disposeStage(params) {
|
|
246
|
+
const fields = {
|
|
247
|
+
service: params.service ?? '',
|
|
248
|
+
unit: params.stage,
|
|
249
|
+
stage: params.stage,
|
|
250
|
+
phase: LIFECYCLE_PHASE_DISPOSE,
|
|
251
|
+
};
|
|
252
|
+
params.log?.debug(fields, `[lifecycle] ${LIFECYCLE_PHASE_DISPOSE} "${params.stage}" start`);
|
|
253
|
+
await params.disposer.dispose();
|
|
254
|
+
params.log?.debug(fields, `[lifecycle] ${LIFECYCLE_PHASE_DISPOSE} "${params.stage}" done`);
|
|
255
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 固定关闭步(N1 §5)——**顺序写死在数组里**。
|
|
3
|
+
*
|
|
4
|
+
* `disposer` 是所有槽位 ctx 的标配:每个阶段一个 `Disposer`,在**创建处**注册释放。
|
|
5
|
+
* 关闭时按阶段**逆序 flush**:`routers → middlewares → services → clients → config → resources`
|
|
6
|
+
* (后创建的先销毁;Prisma / Redis 这类资源**最后**才断)。
|
|
7
|
+
*
|
|
8
|
+
* `services` / `resources` 阶段内部保持同一套语义:先 flush 自己的 `disposer`(构造期注册的
|
|
9
|
+
* 临时产物),再逐单元 `dispose`(声明序**逆序**)。
|
|
10
|
+
*
|
|
11
|
+
* ❗`resources` 阶段**顺序与 `services` 相反**:先 `disposeResources`(单元自己的连接)再
|
|
12
|
+
* `flushResources`(`ctx.disposer` 里的逃生口清理)。理由:单元 `dispose` 是「资源自身怎么关」
|
|
13
|
+
* 的正事,`disposer` 是临时产物;临时产物可能在 `dispose` 过程中被用到,不能先拆。
|
|
14
|
+
*
|
|
15
|
+
* `clients` 排在 `services` **之后**:服务单元 `dispose()` 时可能还要落一次外部调用。
|
|
16
|
+
*
|
|
17
|
+
* 固定八步:`flushRouters` → `flushMiddlewares` → `flushServices` → `disposeServices`
|
|
18
|
+
* → `flushClients` → `flushConfig` → `disposeResources` → `flushResources`。
|
|
19
|
+
*
|
|
20
|
+
* 每步失败记日志、不阻断后续,最后聚合上报。
|
|
21
|
+
*/
|
|
22
|
+
declare const SHUTDOWN_ORDER: readonly ["flushRouters", "flushMiddlewares", "flushServices", "disposeServices", "flushClients", "flushConfig", "disposeResources", "flushResources"];
|
|
23
|
+
type ShutdownStepName = (typeof SHUTDOWN_ORDER)[number];
|
|
24
|
+
type ShutdownHandles = Record<ShutdownStepName, () => void | Promise<void>>;
|
|
25
|
+
export interface ShutdownLogger {
|
|
26
|
+
error: (obj: unknown, msg?: string) => void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 顺序执行关闭步骤;单步抛错记录后**继续**后续步骤,最后聚合上报
|
|
30
|
+
* (避免「一步报错导致后面资源没关」)。
|
|
31
|
+
*/
|
|
32
|
+
export declare function runShutdown(handles: ShutdownHandles, log: ShutdownLogger): Promise<void>;
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=shutdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 固定关闭步(N1 §5)——**顺序写死在数组里**。
|
|
3
|
+
*
|
|
4
|
+
* `disposer` 是所有槽位 ctx 的标配:每个阶段一个 `Disposer`,在**创建处**注册释放。
|
|
5
|
+
* 关闭时按阶段**逆序 flush**:`routers → middlewares → services → clients → config → resources`
|
|
6
|
+
* (后创建的先销毁;Prisma / Redis 这类资源**最后**才断)。
|
|
7
|
+
*
|
|
8
|
+
* `services` / `resources` 阶段内部保持同一套语义:先 flush 自己的 `disposer`(构造期注册的
|
|
9
|
+
* 临时产物),再逐单元 `dispose`(声明序**逆序**)。
|
|
10
|
+
*
|
|
11
|
+
* ❗`resources` 阶段**顺序与 `services` 相反**:先 `disposeResources`(单元自己的连接)再
|
|
12
|
+
* `flushResources`(`ctx.disposer` 里的逃生口清理)。理由:单元 `dispose` 是「资源自身怎么关」
|
|
13
|
+
* 的正事,`disposer` 是临时产物;临时产物可能在 `dispose` 过程中被用到,不能先拆。
|
|
14
|
+
*
|
|
15
|
+
* `clients` 排在 `services` **之后**:服务单元 `dispose()` 时可能还要落一次外部调用。
|
|
16
|
+
*
|
|
17
|
+
* 固定八步:`flushRouters` → `flushMiddlewares` → `flushServices` → `disposeServices`
|
|
18
|
+
* → `flushClients` → `flushConfig` → `disposeResources` → `flushResources`。
|
|
19
|
+
*
|
|
20
|
+
* 每步失败记日志、不阻断后续,最后聚合上报。
|
|
21
|
+
*/
|
|
22
|
+
const SHUTDOWN_ORDER = [
|
|
23
|
+
'flushRouters',
|
|
24
|
+
'flushMiddlewares',
|
|
25
|
+
'flushServices',
|
|
26
|
+
'disposeServices',
|
|
27
|
+
'flushClients',
|
|
28
|
+
'flushConfig',
|
|
29
|
+
'disposeResources',
|
|
30
|
+
'flushResources',
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* 顺序执行关闭步骤;单步抛错记录后**继续**后续步骤,最后聚合上报
|
|
34
|
+
* (避免「一步报错导致后面资源没关」)。
|
|
35
|
+
*/
|
|
36
|
+
export async function runShutdown(handles, log) {
|
|
37
|
+
const errors = [];
|
|
38
|
+
for (const step of SHUTDOWN_ORDER) {
|
|
39
|
+
try {
|
|
40
|
+
await handles[step]();
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
errors.push(error);
|
|
44
|
+
log.error({ error, step }, '[shutdown] step failed');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (errors.length > 0) {
|
|
48
|
+
log.error({ errors, count: errors.length }, '[shutdown] completed with errors');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LoggerLike } from '../types.js';
|
|
2
|
+
import type { FastifyInstance } from 'fastify';
|
|
3
|
+
/**
|
|
4
|
+
* 内置信号处理(设计 §7.1 `activate` 尾部 / §7.2)。
|
|
5
|
+
*
|
|
6
|
+
* `SIGTERM` / `SIGINT` / `uncaughtException` / `unhandledRejection` ⇒ `app.close()`(触发 `onClose`
|
|
7
|
+
* 的关闭 7 步)⇒ `process.exit(0/1)`。**只有 `startServer` 装信号处理**(它一定 `listen`);
|
|
8
|
+
* `createTestServer` 不装——它跑在测试进程里,退出由测试框架决定。
|
|
9
|
+
*
|
|
10
|
+
* @returns detach 函数(移除本次注册的监听器);`startServer` 已把它挂到 `onClose` 上
|
|
11
|
+
*/
|
|
12
|
+
export declare function attachSignalHandlers(app: FastifyInstance, log: LoggerLike): () => void;
|
|
13
|
+
//# sourceMappingURL=signal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signal.d.ts","sourceRoot":"","sources":["../../../source/core/boot/signal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE9C;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,UAAU,GAAG,MAAM,IAAI,CA2CtF"}
|
|
@@ -2,9 +2,10 @@
|
|
|
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
|
+
* `createTestServer` 不装——它跑在测试进程里,退出由测试框架决定。
|
|
6
7
|
*
|
|
7
|
-
* @returns detach
|
|
8
|
+
* @returns detach 函数(移除本次注册的监听器);`startServer` 已把它挂到 `onClose` 上
|
|
8
9
|
*/
|
|
9
10
|
export function attachSignalHandlers(app, log) {
|
|
10
11
|
let closing = false;
|