@taicode/common-server 4.0.0-beta.2 → 4.0.0-beta.3

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.
@@ -27,13 +27,18 @@ export interface BootedServer<R, S> {
27
27
  * 步序(N1 §5):
28
28
  * ```
29
29
  * 1. 加载 env(框架固定,调用方已做) → env
30
- * 2. resources(ctx={env,log}) → R(只构造,不建连)
31
- * 3. config(ctx={env,log,resources}) → C(仅 start 路径;出 spec 路径不跑)
30
+ * 2. resources(ctx={env,log,disposer}) → R(只构造,不建连)
31
+ * 3. config(ctx={env,log,disposer,resources}) → C(仅 start 路径;出 spec 路径不跑)
32
32
  * 4. services(ctx={log,disposer,resources,config}) → S,随后按声明序 init()(仅 start 路径)
33
- * 5. middlewares(ctx={app,log,services,config}) → 按数组序 register(app)
34
- * 6. routers(ctx={app,log,services}) → 注册路由
33
+ * 5. middlewares(ctx={app,log,disposer,services,config}) → 按数组序 register(app)
34
+ * 6. routers(ctx={app,log,disposer,services}) → 注册路由
35
35
  * 7. ready / listen
36
36
  * ```
37
+ *
38
+ * **每个阶段一个独立 `Disposer`**(构造处注册释放);关闭时按阶段**逆序 flush**
39
+ * `routers → middlewares → services → config → resources`(services 阶段内部先 flush 自己的
40
+ * `disposer`,再逐单元逆序 `dispose`)。资源因此**最后**才断:service 关闭时若还要落库不会
41
+ * 撞死连接。出 spec 路径不关闭,也不执行任何 flush。
37
42
  */
38
43
  export declare function bootServer<R, C, S>(input: BootInput<R, C, S>): Promise<BootedServer<R, S>>;
39
44
  //# sourceMappingURL=boot.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"boot.d.ts","sourceRoot":"","sources":["../../source/app/boot.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACzC,OAAO,KAAK,EAAgC,aAAa,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACtG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE9C,qFAAqF;AACrF,eAAO,MAAM,gBAAgB,UAAU,CAAA;AACvC,eAAO,MAAM,kBAAkB,YAAY,CAAA;AAC3C,mBAAmB;AACnB,MAAM,MAAM,SAAS,GAAG,OAAO,gBAAgB,GAAG,OAAO,kBAAkB,CAAA;AAC3E,aAAa;AACb,eAAO,MAAM,YAAY,YAAY,CAAA;AAErC,MAAM,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IACvB,GAAG,EAAE,SAAS,CAAA;IACd,KAAK,EAAE,SAAS,CAAA;IAChB,MAAM,EAAE,OAAO,CAAA;IACf,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5D;AAED,MAAM,WAAW,YAAY,CAAC,CAAC,EAAE,CAAC;IAChC,GAAG,EAAE,eAAe,CAAA;IACpB,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;IAC3B,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7B;AAqCD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CA6GhG"}
1
+ {"version":3,"file":"boot.d.ts","sourceRoot":"","sources":["../../source/app/boot.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACzC,OAAO,KAAK,EAAgC,aAAa,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACtG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE9C,qFAAqF;AACrF,eAAO,MAAM,gBAAgB,UAAU,CAAA;AACvC,eAAO,MAAM,kBAAkB,YAAY,CAAA;AAC3C,mBAAmB;AACnB,MAAM,MAAM,SAAS,GAAG,OAAO,gBAAgB,GAAG,OAAO,kBAAkB,CAAA;AAC3E,aAAa;AACb,eAAO,MAAM,YAAY,YAAY,CAAA;AAErC,MAAM,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IACvB,GAAG,EAAE,SAAS,CAAA;IACd,KAAK,EAAE,SAAS,CAAA;IAChB,MAAM,EAAE,OAAO,CAAA;IACf,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5D;AAED,MAAM,WAAW,YAAY,CAAC,CAAC,EAAE,CAAC;IAChC,GAAG,EAAE,eAAe,CAAA;IACpB,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;IAC3B,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IACpB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC7B;AAqCD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,UAAU,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CA6HhG"}
@@ -1,7 +1,7 @@
1
1
  import { Disposer } from '@taicode/common-base';
2
2
  import { createConfigContext, createConfigHolder, createMiddlewareContext, createResourcesContext, createRouterContext, createServiceContext, createShutdownContext, liveResources, readConfigKey, requireConfig, } from './context.js';
3
3
  import { createFastify, registerCore } from './core/index.js';
4
- import { disposeServices, initServiceUnits, MIDDLEWARES_LOG_STAGE, middlewareUnitName } from './lifecycle.js';
4
+ import { CONFIG_STAGE, disposeServices, disposeStage, initServiceUnits, MIDDLEWARES_LOG_STAGE, MIDDLEWARES_STAGE, middlewareUnitName, RESOURCES_STAGE, ROUTERS_STAGE, SERVICES_STAGE, } from './lifecycle.js';
5
5
  import { runShutdown } from './shutdown.js';
6
6
  import { attachSignalHandlers } from './signal.js';
7
7
  /** 内部步序(`prepare` → `compose` → `activate`):`startServer` / `generateOpenApi` 共用。 */
@@ -48,13 +48,18 @@ function combineClassifiers(classifiers) {
48
48
  * 步序(N1 §5):
49
49
  * ```
50
50
  * 1. 加载 env(框架固定,调用方已做) → env
51
- * 2. resources(ctx={env,log}) → R(只构造,不建连)
52
- * 3. config(ctx={env,log,resources}) → C(仅 start 路径;出 spec 路径不跑)
51
+ * 2. resources(ctx={env,log,disposer}) → R(只构造,不建连)
52
+ * 3. config(ctx={env,log,disposer,resources}) → C(仅 start 路径;出 spec 路径不跑)
53
53
  * 4. services(ctx={log,disposer,resources,config}) → S,随后按声明序 init()(仅 start 路径)
54
- * 5. middlewares(ctx={app,log,services,config}) → 按数组序 register(app)
55
- * 6. routers(ctx={app,log,services}) → 注册路由
54
+ * 5. middlewares(ctx={app,log,disposer,services,config}) → 按数组序 register(app)
55
+ * 6. routers(ctx={app,log,disposer,services}) → 注册路由
56
56
  * 7. ready / listen
57
57
  * ```
58
+ *
59
+ * **每个阶段一个独立 `Disposer`**(构造处注册释放);关闭时按阶段**逆序 flush**
60
+ * `routers → middlewares → services → config → resources`(services 阶段内部先 flush 自己的
61
+ * `disposer`,再逐单元逆序 `dispose`)。资源因此**最后**才断:service 关闭时若还要落库不会
62
+ * 撞死连接。出 spec 路径不关闭,也不执行任何 flush。
58
63
  */
59
64
  export async function bootServer(input) {
60
65
  const { server, env, phase } = input;
@@ -65,7 +70,14 @@ export async function bootServer(input) {
65
70
  const app = createFastify(env);
66
71
  const instance = app;
67
72
  const log = app.log;
68
- const disposer = new Disposer();
73
+ // 每个阶段一个 Disposer:创建与释放同处一地,关闭时按阶段逆序 flush
74
+ const disposers = {
75
+ resources: new Disposer(),
76
+ config: new Disposer(),
77
+ services: new Disposer(),
78
+ middlewares: new Disposer(),
79
+ routers: new Disposer(),
80
+ };
69
81
  const holder = createConfigHolder();
70
82
  // ② resources:只构造,不建连(出 spec 路径同样执行)
71
83
  // 资源错误分类器由各资源通过 `ctx.registerClassifyError` 各自注册:在调 `resources(ctx)` 前建局部收集器,
@@ -74,6 +86,7 @@ export async function bootServer(input) {
74
86
  const resources = (await server.resources(createResourcesContext({
75
87
  env,
76
88
  log,
89
+ disposer: disposers.resources,
77
90
  registerClassifyError: (classifier) => {
78
91
  classifiers.push(classifier);
79
92
  },
@@ -89,12 +102,12 @@ export async function bootServer(input) {
89
102
  // ── compose ──────────────────────────────────────────────────────────────
90
103
  // ① config(仅 start 路径;出 spec 路径不跑 → `ctx.config` 读到即抛 CONFIG_UNAVAILABLE)
91
104
  if (isStart) {
92
- holder.current = (await server.config(createConfigContext({ env, log, resources: resourcesView })));
105
+ holder.current = (await server.config(createConfigContext({ env, log, disposer: disposers.config, resources: resourcesView })));
93
106
  }
94
107
  // ② services(两个入口都执行;ctx 无 env、无 services)
95
108
  const services = server.services(createServiceContext({
96
109
  log,
97
- disposer,
110
+ disposer: disposers.services,
98
111
  resources: resourcesView,
99
112
  holder,
100
113
  }));
@@ -103,20 +116,21 @@ export async function bootServer(input) {
103
116
  await initServiceUnits({ service, log, services: services });
104
117
  }
105
118
  // ④ middlewares(两个入口都执行:出 spec 也照注册,保证 schema 一致)
106
- const middlewares = server.middlewares(createMiddlewareContext({ app: instance, log, services, holder }));
119
+ const middlewares = server.middlewares(createMiddlewareContext({ app: instance, log, disposer: disposers.middlewares, services, holder }));
107
120
  for (const [index, unit] of middlewares.entries()) {
108
121
  await unit.register(instance);
109
122
  log.debug({ service, unit: middlewareUnitName(service, unit, index), stage: MIDDLEWARES_LOG_STAGE }, '[middleware] registered');
110
123
  }
111
124
  // ⑤ routers(两个入口都执行)
112
- await server.routers(createRouterContext({ app: instance, log, services }));
125
+ await server.routers(createRouterContext({ app: instance, log, disposer: disposers.routers, services }));
113
126
  // ── activate ─────────────────────────────────────────────────────────────
114
127
  const runShutdownFlow = async () => {
115
128
  if (input.shutdown) {
129
+ // 整体覆盖:自定义 shutdown 自行负责各阶段 flush(框架不再代劳)
116
130
  await input.shutdown(createShutdownContext({
117
131
  app: instance,
118
132
  log,
119
- disposer,
133
+ disposer: disposers.services,
120
134
  resources: resourcesView,
121
135
  holder,
122
136
  services,
@@ -124,8 +138,12 @@ export async function bootServer(input) {
124
138
  return;
125
139
  }
126
140
  await runShutdown({
127
- stopProducers: () => disposer.dispose(),
141
+ flushRouters: () => disposeStage({ service, log, stage: ROUTERS_STAGE, disposer: disposers.routers }),
142
+ flushMiddlewares: () => disposeStage({ service, log, stage: MIDDLEWARES_STAGE, disposer: disposers.middlewares }),
143
+ flushServices: () => disposeStage({ service, log, stage: SERVICES_STAGE, disposer: disposers.services }),
128
144
  disposeServices: () => disposeServices({ service, log, services: services }),
145
+ flushConfig: () => disposeStage({ service, log, stage: CONFIG_STAGE, disposer: disposers.config }),
146
+ flushResources: () => disposeStage({ service, log, stage: RESOURCES_STAGE, disposer: disposers.resources }),
129
147
  }, log);
130
148
  };
131
149
  app.addHook('onClose', () => runShutdownFlow());
@@ -23,6 +23,7 @@ export declare function readConfigKey(config: unknown, key: string): unknown;
23
23
  export interface ResourcesContextParams {
24
24
  env: ConfigEnv;
25
25
  log: LoggerLike;
26
+ disposer: Disposer;
26
27
  /** 收集器:`bootServer` 注入,把各资源注册的分类器按注册顺序收起来 */
27
28
  registerClassifyError: (classifier: ErrorClassifier) => void;
28
29
  }
@@ -31,6 +32,7 @@ export declare function createResourcesContext(params: ResourcesContextParams):
31
32
  export interface ConfigContextParams<R> {
32
33
  env: ConfigEnv;
33
34
  log: LoggerLike;
35
+ disposer: Disposer;
34
36
  resources: ResourcesView<R>;
35
37
  }
36
38
  export declare function createConfigContext<R>(params: ConfigContextParams<R>): ConfigContext<R>;
@@ -46,6 +48,7 @@ export declare function createServiceContext<R, C>(params: ServiceContextParams<
46
48
  export interface MiddlewareContextParams<C, S> {
47
49
  app: AppInstance;
48
50
  log: LoggerLike;
51
+ disposer: Disposer;
49
52
  services: Awaited<S>;
50
53
  holder: ConfigHolder<C>;
51
54
  }
@@ -54,6 +57,7 @@ export declare function createMiddlewareContext<C, S>(params: MiddlewareContextP
54
57
  export interface RouterContextParams<S> {
55
58
  app: AppInstance;
56
59
  log: LoggerLike;
60
+ disposer: Disposer;
57
61
  services: Awaited<S>;
58
62
  }
59
63
  export declare function createRouterContext<S>(params: RouterContextParams<S>): RouterContext<S>;
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../source/app/context.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../source/app/context.ts"],"names":[],"mappings":"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"}
@@ -31,10 +31,15 @@ export function readConfigKey(config, key) {
31
31
  return config?.[key];
32
32
  }
33
33
  export function createResourcesContext(params) {
34
- return { env: params.env, log: params.log, registerClassifyError: params.registerClassifyError };
34
+ return {
35
+ env: params.env,
36
+ log: params.log,
37
+ disposer: params.disposer,
38
+ registerClassifyError: params.registerClassifyError,
39
+ };
35
40
  }
36
41
  export function createConfigContext(params) {
37
- return { env: params.env, log: params.log, resources: params.resources };
42
+ return { env: params.env, log: params.log, disposer: params.disposer, resources: params.resources };
38
43
  }
39
44
  export function createServiceContext(params) {
40
45
  return {
@@ -50,6 +55,7 @@ export function createMiddlewareContext(params) {
50
55
  return {
51
56
  app: params.app,
52
57
  log: params.log,
58
+ disposer: params.disposer,
53
59
  services: params.services,
54
60
  get config() {
55
61
  return requireConfig(params.holder);
@@ -57,7 +63,7 @@ export function createMiddlewareContext(params) {
57
63
  };
58
64
  }
59
65
  export function createRouterContext(params) {
60
- return { app: params.app, log: params.log, services: params.services };
66
+ return { app: params.app, log: params.log, disposer: params.disposer, services: params.services };
61
67
  }
62
68
  export function createShutdownContext(params) {
63
69
  return {
@@ -24,6 +24,8 @@ export declare function startServer<R, C, S>(server: Server<R, C, S>, options?:
24
24
  *
25
25
  * **红线**:全程**不得要求配置有效** —— 不跑 `config` 槽位、不要求 `DATABASE_URL` / `REDIS_URL`
26
26
  * 有效;`resources`(只构造不建连)/ `services` / 中间件注册 / 路由注册照常执行。
27
+ *
28
+ * 出 spec 路径**不关闭服务**,因此也不会 flush 任何阶段的 `disposer`。
27
29
  */
28
30
  export declare function generateOpenApi<R, C, S>(server: Server<R, C, S>): Promise<OpenApiDocument>;
29
31
  //# sourceMappingURL=entries.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../source/app/entries.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC3F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE9C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAEzC;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAMpF;AAED,yCAAyC;AACzC,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAMnF;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,GAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAavI;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,CAiBhG"}
1
+ {"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../source/app/entries.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC3F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE9C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAEzC;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAMpF;AAED,yCAAyC;AACzC,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAMnF;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,GAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAavI;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,CAiBhG"}
@@ -46,6 +46,8 @@ export async function startServer(server, options = {}) {
46
46
  *
47
47
  * **红线**:全程**不得要求配置有效** —— 不跑 `config` 槽位、不要求 `DATABASE_URL` / `REDIS_URL`
48
48
  * 有效;`resources`(只构造不建连)/ `services` / 中间件注册 / 路由注册照常执行。
49
+ *
50
+ * 出 spec 路径**不关闭服务**,因此也不会 flush 任何阶段的 `disposer`。
49
51
  */
50
52
  export async function generateOpenApi(server) {
51
53
  // 不加载 .env 文件、不读配置
@@ -1,12 +1,14 @@
1
1
  import { describe, expect, it, vi } from 'vitest';
2
+ import { Disposer } from '@taicode/common-base';
2
3
  import { defineServer, startServer } from './entries.js';
3
- import { buildDisposeOrder, initServiceUnits, LIFECYCLE_PHASE_DISPOSE, LIFECYCLE_PHASE_INIT, MIDDLEWARES_LOG_STAGE, middlewareUnitName, SERVICES_STAGE, serviceUnitName, } from './lifecycle.js';
4
+ import { buildDisposeOrder, disposeStage, initServiceUnits, LIFECYCLE_PHASE_DISPOSE, LIFECYCLE_PHASE_INIT, MIDDLEWARES_LOG_STAGE, middlewareUnitName, RESOURCES_STAGE, SERVICES_STAGE, serviceUnitName, } from './lifecycle.js';
4
5
  import { createRecordingLogger } from './test-support.js';
5
6
  /**
6
7
  * 逐单元日志。
7
8
  *
8
9
  * - **注册期**:每个中间件注册后打一条 `debug`,带 `{ service, unit, stage: 'middlewares' }`;
9
10
  * - **init / dispose**:逐单元 `debug`(开始 / 完成),失败 `error`;带 `{ service, unit, stage, phase }`;
11
+ * - **阶段 flush**:`disposeStage` 每阶段一条 `debug`(`unit` = 阶段名);
10
12
  * - 中间件名字:`unit.name ?? '<service>:middleware[<index>]'`;services 用对象键。
11
13
  *
12
14
  * 分两层验证:
@@ -91,6 +93,19 @@ describe('逐单元日志:init / dispose', () => {
91
93
  expect(middlewareUnitName('svc', {}, 3)).toBe('svc:middleware[3]');
92
94
  expect(middlewareUnitName('svc', { name: 'named' }, 1)).toBe('named');
93
95
  });
96
+ it('阶段 flush:`unit` / `stage` = 阶段名,`phase` = dispose', async () => {
97
+ const log = createRecordingLogger();
98
+ const disposer = new Disposer();
99
+ await disposeStage({ service: 'svc', log, stage: RESOURCES_STAGE, disposer });
100
+ const fields = log.entries.map((entry) => entry.obj);
101
+ expect(fields.length).toBeGreaterThan(0);
102
+ for (const field of fields) {
103
+ expect(field.service).toBe('svc');
104
+ expect(field.unit).toBe(RESOURCES_STAGE);
105
+ expect(field.stage).toBe(RESOURCES_STAGE);
106
+ expect(field.phase).toBe(LIFECYCLE_PHASE_DISPOSE);
107
+ }
108
+ });
94
109
  });
95
110
  // ── 真路径:`startServer` 走完整装配,捕获框架 logger 写入 ────────
96
111
  const captured = vi.hoisted(() => ({ lines: [] }));
@@ -148,8 +163,13 @@ describe('startServer 真路径:注册 / init / dispose 逐单元日志', () =
148
163
  // init / dispose:services 逐单元 debug(带 `phase`)
149
164
  expect(matchRecords(records, { unit: 'alpha', stage: SERVICES_STAGE, phase: LIFECYCLE_PHASE_INIT }).length).toBeGreaterThan(0);
150
165
  expect(matchRecords(records, { unit: 'alpha', stage: SERVICES_STAGE, phase: LIFECYCLE_PHASE_DISPOSE }).length).toBeGreaterThan(0);
151
- // 中间件**不参与生命周期**:没有 middlewares 阶段的 init / dispose 日志
166
+ // 中间件**没有逐单元生命周期**:没有 middlewares 阶段的 init 日志;dispose 只有阶段 flush
152
167
  expect(matchRecords(records, { stage: MIDDLEWARES_LOG_STAGE, phase: LIFECYCLE_PHASE_INIT })).toHaveLength(0);
153
- expect(matchRecords(records, { stage: MIDDLEWARES_LOG_STAGE, phase: LIFECYCLE_PHASE_DISPOSE })).toHaveLength(0);
168
+ const middlewareDispose = matchRecords(records, {
169
+ stage: MIDDLEWARES_LOG_STAGE,
170
+ phase: LIFECYCLE_PHASE_DISPOSE,
171
+ });
172
+ expect(middlewareDispose.length).toBeGreaterThan(0);
173
+ expect(middlewareDispose.every((record) => record.unit === MIDDLEWARES_LOG_STAGE)).toBe(true);
154
174
  });
155
175
  });
@@ -1,15 +1,27 @@
1
+ import type { Disposer } from '@taicode/common-base';
1
2
  import type { LoggerLike } from './types.js';
2
3
  /**
3
4
  * 生命周期序(N1 §4 / N2 §2.5)。
4
5
  *
5
- * **只有 `services` 参与生命周期**:
6
+ * **每个阶段一个 `Disposer`**(`ctx.disposer`,所有槽位都有):在创建某个东西的地方当场
7
+ * `ctx.disposer.add(() => 释放它)` —— 创建与释放同处一地。
8
+ *
9
+ * 关闭时按阶段**逆序 flush**:`routers → middlewares → services → config → resources`
10
+ * (后创建的先销毁;Prisma / Redis 这类资源最后才断)。
11
+ *
12
+ * **`services` 额外有逐单元生命周期**:
6
13
  * - 单元之间**不得相互依赖**(`services` 的 ctx 没有 `services` 字段,编译期禁止);
7
- * - 顺序语义只有一条:按**对象声明序** `init`、**逆序** `dispose`;
8
- * - `middlewares` 按数组序注册,**不参与生命周期**(要常驻资源就放 `services`);
9
- * - `resources` 只是惰性包装(构造不建连),框架不替它们做生命周期。
14
+ * - `init` 按**对象声明序**,`dispose` **逆序**;
15
+ * - services 阶段内部**先 flush `ctx.disposer`**(构造期注册的清理),再逐单元 `dispose`。
16
+ *
17
+ * `middlewares` 按数组序注册;它只有 `disposer`,没有逐单元生命周期。
10
18
  */
11
19
  export declare const SERVICES_STAGE = "services";
12
- /** `middlewares` 的**注册期日志**阶段标签(中间件不参与生命周期,只用于注册日志) */
20
+ export declare const CONFIG_STAGE = "config";
21
+ export declare const MIDDLEWARES_STAGE = "middlewares";
22
+ export declare const ROUTERS_STAGE = "routers";
23
+ export declare const RESOURCES_STAGE = "resources";
24
+ /** `middlewares` 的**注册期日志**阶段标签(与生命周期阶段同名) */
13
25
  export declare const MIDDLEWARES_LOG_STAGE = "middlewares";
14
26
  /** 生命周期相位(逐单元日志字段 `phase`) */
15
27
  export declare const LIFECYCLE_PHASE_INIT = "init";
@@ -27,7 +39,7 @@ export interface LifecycleLogContext {
27
39
  /** 记录型 logger(`ctx.log`);缺省 = 不打日志 */
28
40
  log?: LoggerLike;
29
41
  }
30
- /** 逐单元日志字段(全拼):`unit` = 有效名字,`stage` = `services` */
42
+ /** 逐单元日志字段(全拼):`unit` = 有效名字(阶段 flush 时为阶段名),`stage` = 阶段名 */
31
43
  export interface LifecycleUnitLogFields {
32
44
  service: string;
33
45
  unit: string;
@@ -67,4 +79,18 @@ export declare function buildDisposeOrder(params: DisposeServiceUnitsParams): Ar
67
79
  * 此处仅保证单步不阻断后续。
68
80
  */
69
81
  export declare function disposeServices(params: DisposeServiceUnitsParams): Promise<void>;
82
+ /** `disposeStage` 入参(参数签名内联对象类型已按 lint 规则外提) */
83
+ export interface DisposeStageParams extends LifecycleLogContext {
84
+ /** 阶段名(`routers` / `middlewares` / `services` / `config` / `resources`) */
85
+ stage: string;
86
+ /** 该阶段的 `Disposer`(构造期注册的清理函数,**逆注册序**执行) */
87
+ disposer: Disposer;
88
+ }
89
+ /**
90
+ * 关闭步:flush 某个阶段的 `disposer`。
91
+ *
92
+ * 单元名 = 阶段名(一个阶段一个 `Disposer`,不逐注册项打日志);`phase` 固定 `dispose`。
93
+ * 单个清理函数失败由 `Disposer` 自己捕获记录,不影响同阶段其它函数。
94
+ */
95
+ export declare function disposeStage(params: DisposeStageParams): Promise<void>;
70
96
  //# sourceMappingURL=lifecycle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../source/app/lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C;;;;;;;;GAQG;AAEH,eAAO,MAAM,cAAc,aAAa,CAAA;AACxC,uDAAuD;AACvD,eAAO,MAAM,qBAAqB,gBAAgB,CAAA;AAElD,8BAA8B;AAC9B,eAAO,MAAM,oBAAoB,SAAS,CAAA;AAC1C,eAAO,MAAM,uBAAuB,YAAY,CAAA;AAChD,MAAM,MAAM,cAAc,GAAG,OAAO,oBAAoB,GAAG,OAAO,uBAAuB,CAAA;AAEzF;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sCAAsC;IACtC,GAAG,CAAC,EAAE,UAAU,CAAA;CACjB;AAED,qDAAqD;AACrD,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,cAAc,CAAA;CACtB;AAMD;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAKxF;AAED,oDAAoD;AACpD,MAAM,WAAW,sBAAuB,SAAQ,mBAAmB;IACjE,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAmCD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIpF;AAED,6EAA6E;AAC7E,MAAM,WAAW,yBAA0B,SAAQ,mBAAmB;IACpE,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,yBAAyB,GAAG,KAAK,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAMtG;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQtF"}
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../source/app/lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,cAAc,aAAa,CAAA;AACxC,eAAO,MAAM,YAAY,WAAW,CAAA;AACpC,eAAO,MAAM,iBAAiB,gBAAgB,CAAA;AAC9C,eAAO,MAAM,aAAa,YAAY,CAAA;AACtC,eAAO,MAAM,eAAe,cAAc,CAAA;AAC1C,8CAA8C;AAC9C,eAAO,MAAM,qBAAqB,gBAAoB,CAAA;AAEtD,8BAA8B;AAC9B,eAAO,MAAM,oBAAoB,SAAS,CAAA;AAC1C,eAAO,MAAM,uBAAuB,YAAY,CAAA;AAChD,MAAM,MAAM,cAAc,GAAG,OAAO,oBAAoB,GAAG,OAAO,uBAAuB,CAAA;AAEzF;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sCAAsC;IACtC,GAAG,CAAC,EAAE,UAAU,CAAA;CACjB;AAED,8DAA8D;AAC9D,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,cAAc,CAAA;CACtB;AAMD;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAKxF;AAED,oDAAoD;AACpD,MAAM,WAAW,sBAAuB,SAAQ,mBAAmB;IACjE,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAmCD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAIpF;AAED,6EAA6E;AAC7E,MAAM,WAAW,yBAA0B,SAAQ,mBAAmB;IACpE,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,yBAAyB,GAAG,KAAK,CAAC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAMtG;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQtF;AAED,gDAAgD;AAChD,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB;IAC7D,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAA;IACb,6CAA6C;IAC7C,QAAQ,EAAE,QAAQ,CAAA;CACnB;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAU5E"}
@@ -1,15 +1,26 @@
1
1
  /**
2
2
  * 生命周期序(N1 §4 / N2 §2.5)。
3
3
  *
4
- * **只有 `services` 参与生命周期**:
4
+ * **每个阶段一个 `Disposer`**(`ctx.disposer`,所有槽位都有):在创建某个东西的地方当场
5
+ * `ctx.disposer.add(() => 释放它)` —— 创建与释放同处一地。
6
+ *
7
+ * 关闭时按阶段**逆序 flush**:`routers → middlewares → services → config → resources`
8
+ * (后创建的先销毁;Prisma / Redis 这类资源最后才断)。
9
+ *
10
+ * **`services` 额外有逐单元生命周期**:
5
11
  * - 单元之间**不得相互依赖**(`services` 的 ctx 没有 `services` 字段,编译期禁止);
6
- * - 顺序语义只有一条:按**对象声明序** `init`、**逆序** `dispose`;
7
- * - `middlewares` 按数组序注册,**不参与生命周期**(要常驻资源就放 `services`);
8
- * - `resources` 只是惰性包装(构造不建连),框架不替它们做生命周期。
12
+ * - `init` 按**对象声明序**,`dispose` **逆序**;
13
+ * - services 阶段内部**先 flush `ctx.disposer`**(构造期注册的清理),再逐单元 `dispose`。
14
+ *
15
+ * `middlewares` 按数组序注册;它只有 `disposer`,没有逐单元生命周期。
9
16
  */
10
17
  export const SERVICES_STAGE = 'services';
11
- /** `middlewares` 的**注册期日志**阶段标签(中间件不参与生命周期,只用于注册日志) */
12
- export const MIDDLEWARES_LOG_STAGE = 'middlewares';
18
+ export const CONFIG_STAGE = 'config';
19
+ export const MIDDLEWARES_STAGE = 'middlewares';
20
+ export const ROUTERS_STAGE = 'routers';
21
+ export const RESOURCES_STAGE = 'resources';
22
+ /** `middlewares` 的**注册期日志**阶段标签(与生命周期阶段同名) */
23
+ export const MIDDLEWARES_LOG_STAGE = MIDDLEWARES_STAGE;
13
24
  /** 生命周期相位(逐单元日志字段 `phase`) */
14
25
  export const LIFECYCLE_PHASE_INIT = 'init';
15
26
  export const LIFECYCLE_PHASE_DISPOSE = 'dispose';
@@ -97,3 +108,20 @@ export async function disposeServices(params) {
97
108
  }
98
109
  }
99
110
  }
111
+ /**
112
+ * 关闭步:flush 某个阶段的 `disposer`。
113
+ *
114
+ * 单元名 = 阶段名(一个阶段一个 `Disposer`,不逐注册项打日志);`phase` 固定 `dispose`。
115
+ * 单个清理函数失败由 `Disposer` 自己捕获记录,不影响同阶段其它函数。
116
+ */
117
+ export async function disposeStage(params) {
118
+ const fields = {
119
+ service: params.service ?? '',
120
+ unit: params.stage,
121
+ stage: params.stage,
122
+ phase: LIFECYCLE_PHASE_DISPOSE,
123
+ };
124
+ params.log?.debug(fields, `[lifecycle] ${LIFECYCLE_PHASE_DISPOSE} "${params.stage}" start`);
125
+ await params.disposer.dispose();
126
+ params.log?.debug(fields, `[lifecycle] ${LIFECYCLE_PHASE_DISPOSE} "${params.stage}" done`);
127
+ }
@@ -4,10 +4,11 @@ import { buildDisposeOrder, initServiceUnits, serviceUnitName } from './lifecycl
4
4
  /**
5
5
  * 生命周期(N1 §4)。
6
6
  *
7
- * - **只有 `services` 参与**:按**对象声明序** `init` / **逆序** `dispose`;
7
+ * - **每个阶段一个 `Disposer`**(`ctx.disposer`):创建处注册释放,关闭时按阶段逆序 flush;
8
+ * - **`services` 额外有逐单元生命周期**:按**对象声明序** `init` / **逆序** `dispose`;
9
+ * - services 阶段内部:先 flush 自己的 `disposer`,再逐单元 `dispose`;
8
10
  * - **没有 `dependsOn`**(`services` 的 ctx 没有 `services` 字段,编译期禁止互依);
9
- * - 单元名 = **对象键**;
10
- * - `middlewares` 按数组序注册,**不参与生命周期**。
11
+ * - 单元名 = **对象键**。
11
12
  */
12
13
  describe('init / dispose 顺序:声明序 / 逆序(N1 §4 / §5)', () => {
13
14
  it('init:services 按对象声明序', async () => {
@@ -10,20 +10,21 @@ import { defaultMiddlewares } from './middleware/index.js';
10
10
  const fakeMiddlewareContext = {
11
11
  app: {},
12
12
  log: { debug: () => { }, info: () => { }, warn: () => { }, error: () => { } },
13
+ disposer: { add: () => { }, dispose: async () => { } },
13
14
  services: {},
14
15
  config: {},
15
16
  };
16
17
  describe('ctx 可见性(N1 §2)', () => {
17
- it('MiddlewareContext 有 app / log / services / config;没有 resources / env / disposer', () => {
18
+ it('MiddlewareContext 有 app / log / disposer / services / config;没有 resources / env', () => {
18
19
  expectTypeOf().toHaveProperty('app');
19
20
  expectTypeOf().toHaveProperty('services');
20
21
  expectTypeOf().toHaveProperty('config');
22
+ // `disposer` 是所有槽位的标配(不是 services 专属)
23
+ expectTypeOf().toHaveProperty('disposer');
21
24
  // @ts-expect-error 中间件看不到 resources(禁直连数据层)
22
25
  void 0..resources;
23
26
  // @ts-expect-error 中间件看不到 env
24
27
  void 0..env;
25
- // @ts-expect-error 中间件看不到 disposer(disposer 只属 services)
26
- void 0..disposer;
27
28
  });
28
29
  });
29
30
  describe('默认预设', () => {
@@ -4,17 +4,21 @@ import type { LoggerLike, RedisHandle } from './types.js';
4
4
  /**
5
5
  * 引导期 Redis / cache helper(N2 §2.13)。
6
6
  *
7
- * 在 `resources(ctx)` 槽位里按需调用(框架自己**不**创建 Redis):
7
+ * 在 `resources(ctx)` 槽位里按需调用(框架自己**不**创建 Redis);释放**在创建处**登记:
8
8
  *
9
9
  * ```ts
10
10
  * resources: (ctx) => {
11
11
  * const redis = createRedis(ctx.env, ctx.log) // 无 REDIS_URL → undefined
12
+ * if (redis) {
13
+ * ctx.disposer.add(() => redis.dispose()) // 关闭时 resources 阶段最后 flush
14
+ * }
12
15
  * return { redis, cache: createCache({ env: ctx.env, prefix: 'account', redis }) }
13
16
  * }
14
17
  * ```
15
18
  *
16
19
  * `REDIS_URL` 由 helper 直接读 `env`(固定约定),**不**取自 `config`;`redis` / `cache` 一律是
17
20
  * **惰性包装**:构造时不建连、不 ping,首次真正使用才建连 —— 「出 spec 不连库/Redis」结构性成立。
21
+ * 释放永远走调用方在 `ctx.disposer` 里的登记,框架不替资源持有句柄。
18
22
  */
19
23
  /** 一级/二级缓存默认 TTL(秒),与 `RedisCache` 默认一致 */
20
24
  export declare const DEFAULT_REDIS_CACHE_TTL_SECONDS = 300;
@@ -1 +1 @@
1
- {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../source/app/redis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,UAAU,EAAE,MAAM,yBAAyB,CAAA;AAEvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAEzD;;;;;;;;;;;;;;GAcG;AAEH,2CAA2C;AAC3C,eAAO,MAAM,+BAA+B,MAAM,CAAA;AAElD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,WAAW,GAAG,SAAS,CAmBrF;AAED,uBAAuB;AACvB,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,SAAS,CAAA;IACd,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,GAAG,CAAC,EAAE,UAAU,CAAA;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CAYhE"}
1
+ {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../source/app/redis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,UAAU,EAAE,MAAM,yBAAyB,CAAA;AAEvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAEzD;;;;;;;;;;;;;;;;;;GAkBG;AAEH,2CAA2C;AAC3C,eAAO,MAAM,+BAA+B,MAAM,CAAA;AAElD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,WAAW,GAAG,SAAS,CAmBrF;AAED,uBAAuB;AACvB,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,SAAS,CAAA;IACd,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,GAAG,CAAC,EAAE,UAAU,CAAA;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CAYhE"}
@@ -2,17 +2,21 @@ import { createRedisClient, RedisCache } from '../redis-cache/index.js';
2
2
  /**
3
3
  * 引导期 Redis / cache helper(N2 §2.13)。
4
4
  *
5
- * 在 `resources(ctx)` 槽位里按需调用(框架自己**不**创建 Redis):
5
+ * 在 `resources(ctx)` 槽位里按需调用(框架自己**不**创建 Redis);释放**在创建处**登记:
6
6
  *
7
7
  * ```ts
8
8
  * resources: (ctx) => {
9
9
  * const redis = createRedis(ctx.env, ctx.log) // 无 REDIS_URL → undefined
10
+ * if (redis) {
11
+ * ctx.disposer.add(() => redis.dispose()) // 关闭时 resources 阶段最后 flush
12
+ * }
10
13
  * return { redis, cache: createCache({ env: ctx.env, prefix: 'account', redis }) }
11
14
  * }
12
15
  * ```
13
16
  *
14
17
  * `REDIS_URL` 由 helper 直接读 `env`(固定约定),**不**取自 `config`;`redis` / `cache` 一律是
15
18
  * **惰性包装**:构造时不建连、不 ping,首次真正使用才建连 —— 「出 spec 不连库/Redis」结构性成立。
19
+ * 释放永远走调用方在 `ctx.disposer` 里的登记,框架不替资源持有句柄。
16
20
  */
17
21
  /** 一级/二级缓存默认 TTL(秒),与 `RedisCache` 默认一致 */
18
22
  export const DEFAULT_REDIS_CACHE_TTL_SECONDS = 300;
@@ -16,7 +16,7 @@ const assertLiveKeysRemovesFalse = () => {
16
16
  };
17
17
  void assertLiveKeysRemovesFalse;
18
18
  describe('resources 槽位(N1 §2 / N2 §2.3)', () => {
19
- it('① 入参为 env + log + registerClassifyError,且资源嵌套在 ctx.resources 下(config / services 都能用)', async () => {
19
+ it('① 入参为 env + log + disposer + registerClassifyError,且资源嵌套在 ctx.resources 下(config / services 都能用)', async () => {
20
20
  let captured;
21
21
  const observed = {};
22
22
  const server = defineServer({
@@ -43,9 +43,11 @@ describe('resources 槽位(N1 §2 / N2 §2.3)', () => {
43
43
  });
44
44
  const app = await startServer(server, { listen: false });
45
45
  try {
46
- expect(Object.keys(captured ?? {}).sort()).toEqual(['env', 'log', 'registerClassifyError']);
46
+ // 槽位 ctx:env + log + disposer(+ 专属的 registerClassifyError)
47
+ expect(Object.keys(captured ?? {}).sort()).toEqual(['disposer', 'env', 'log', 'registerClassifyError']);
47
48
  expect(captured?.env.ENV).toBeTypeOf('string');
48
49
  expect(typeof captured?.log.info).toBe('function');
50
+ expect(typeof captured?.disposer.add).toBe('function');
49
51
  // resources 槽位专属能力:每个资源注册自己的错误分类器(函数)
50
52
  expect(typeof captured?.registerClassifyError).toBe('function');
51
53
  expect(observed.inConfig).toBe('rows:select 1');
@@ -143,38 +145,59 @@ describe('resources 槽位(N1 §2 / N2 §2.3)', () => {
143
145
  await app.close();
144
146
  }
145
147
  });
146
- it('⑤ 框架不管资源生命周期:资源上的 dispose 不被调用(释放要放 services)', async () => {
148
+ it('⑤ resources 的 disposer 在 services 阶段**之后**执行;阶段内按逆注册序;资源对象自带的 dispose 不被调用', async () => {
147
149
  const calls = [];
148
150
  const server = defineServer({
149
151
  app: { service: 'resources-lifecycle', title: 'Resources Lifecycle', version: '1.0.0' },
150
- resources: () => ({
151
- database: {
152
+ resources: (ctx) => {
153
+ // 资源对象自带的 `dispose` 不参与生命周期:释放只认 `ctx.disposer.add(...)`
154
+ const database = {
152
155
  dispose: () => {
153
- calls.push('dispose:resource');
156
+ calls.push('dispose:resource-object');
154
157
  },
155
- },
156
- }),
158
+ };
159
+ ctx.disposer.add(() => {
160
+ calls.push('dispose:resource-a');
161
+ });
162
+ ctx.disposer.add(() => {
163
+ calls.push('dispose:resource-b');
164
+ });
165
+ return { database };
166
+ },
157
167
  config: () => ({}),
158
- services: () => ({
159
- databaseClient: {
160
- dispose: () => {
161
- calls.push('dispose:service');
168
+ services: (ctx) => {
169
+ ctx.disposer.add(() => {
170
+ calls.push('dispose:services-disposer');
171
+ });
172
+ return {
173
+ databaseClient: {
174
+ dispose: () => {
175
+ calls.push('dispose:service-unit');
176
+ },
162
177
  },
163
- },
164
- }),
178
+ };
179
+ },
165
180
  middlewares: () => [],
166
181
  routers: () => undefined,
167
182
  });
168
183
  const app = await startServer(server, { listen: false });
169
184
  await app.close();
170
- expect(calls).toEqual(['dispose:service']);
185
+ expect(calls).toEqual([
186
+ 'dispose:services-disposer',
187
+ 'dispose:service-unit',
188
+ 'dispose:resource-b',
189
+ 'dispose:resource-a',
190
+ ]);
171
191
  });
172
- it('⑥ 出 spec 路径同样跑 resources(只构造,不建连)', async () => {
173
- let calls = 0;
192
+ it('⑥ 出 spec 路径同样跑 resources(只构造,不建连),但不 flush 任何 disposer', async () => {
193
+ const calls = [];
174
194
  const server = defineServer({
175
195
  app: { service: 'resources-openapi', title: 'Resources OpenApi', version: '1.0.0' },
176
- resources: () => {
177
- calls += 1;
196
+ resources: (ctx) => {
197
+ calls.push('resources');
198
+ ctx.disposer.add(() => {
199
+ calls.push('dispose:resources');
200
+ });
178
201
  return {};
179
202
  },
180
203
  config: () => ({}),
@@ -183,7 +206,7 @@ describe('resources 槽位(N1 §2 / N2 §2.3)', () => {
183
206
  routers: () => undefined,
184
207
  });
185
208
  await generateOpenApi(server);
186
- expect(calls).toBe(1);
209
+ expect(calls).toEqual(['resources']);
187
210
  });
188
211
  });
189
212
  /** `ResourcesView` 自证:`Awaited<>` 归一(同步 / 异步两写法等价) */
@@ -1,14 +1,19 @@
1
1
  /**
2
2
  * 固定关闭步(N1 §5)——**顺序写死在数组里**。
3
3
  *
4
- * 新口径下框架不再持有 Redis / 数据库 / 外链客户端(这些都在 `resources` 里,由各仓自行构造,
5
- * 需要释放时放 `services` 单元);配置失效通道已删除,故没有 `unsubscribeConfig` 步。
4
+ * `disposer` 是所有槽位 ctx 的标配:每个阶段一个 `Disposer`,在**创建处**注册释放。
5
+ * 关闭时按阶段**逆序 flush**:`routers → middlewares → services → config → resources`
6
+ * (后创建的先销毁;Prisma / Redis 这类资源**最后**才断)。
6
7
  *
7
- * 固定两步:`stopProducers`(`ctx.disposer` 注册的清理)→ `disposeServices`(services 逆序)。
8
+ * `services` 阶段内部保持既有语义:先 flush 自己的 `disposer`(原 `stopProducers`),
9
+ * 再 `disposeServices`(单元**逆序** `dispose`)。配置失效通道已删除,故没有 `unsubscribeConfig` 步。
10
+ *
11
+ * 固定六步:`flushRouters` → `flushMiddlewares` → `flushServices` → `disposeServices`
12
+ * → `flushConfig` → `flushResources`。
8
13
  *
9
14
  * 每步失败记日志、不阻断后续,最后聚合上报。
10
15
  */
11
- export declare const SHUTDOWN_ORDER: readonly ["stopProducers", "disposeServices"];
16
+ export declare const SHUTDOWN_ORDER: readonly ["flushRouters", "flushMiddlewares", "flushServices", "disposeServices", "flushConfig", "flushResources"];
12
17
  export type ShutdownStepName = (typeof SHUTDOWN_ORDER)[number];
13
18
  export type ShutdownHandles = Record<ShutdownStepName, () => void | Promise<void>>;
14
19
  export interface ShutdownLogger {
@@ -1 +1 @@
1
- {"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../../source/app/shutdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,+CAAgD,CAAA;AAE3E,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAE9D,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,gBAAgB,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;AAElF,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"}
1
+ {"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../../source/app/shutdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,oHAOjB,CAAA;AAEV,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAE9D,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,gBAAgB,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;AAElF,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"}
@@ -1,14 +1,26 @@
1
1
  /**
2
2
  * 固定关闭步(N1 §5)——**顺序写死在数组里**。
3
3
  *
4
- * 新口径下框架不再持有 Redis / 数据库 / 外链客户端(这些都在 `resources` 里,由各仓自行构造,
5
- * 需要释放时放 `services` 单元);配置失效通道已删除,故没有 `unsubscribeConfig` 步。
4
+ * `disposer` 是所有槽位 ctx 的标配:每个阶段一个 `Disposer`,在**创建处**注册释放。
5
+ * 关闭时按阶段**逆序 flush**:`routers → middlewares → services → config → resources`
6
+ * (后创建的先销毁;Prisma / Redis 这类资源**最后**才断)。
6
7
  *
7
- * 固定两步:`stopProducers`(`ctx.disposer` 注册的清理)→ `disposeServices`(services 逆序)。
8
+ * `services` 阶段内部保持既有语义:先 flush 自己的 `disposer`(原 `stopProducers`),
9
+ * 再 `disposeServices`(单元**逆序** `dispose`)。配置失效通道已删除,故没有 `unsubscribeConfig` 步。
10
+ *
11
+ * 固定六步:`flushRouters` → `flushMiddlewares` → `flushServices` → `disposeServices`
12
+ * → `flushConfig` → `flushResources`。
8
13
  *
9
14
  * 每步失败记日志、不阻断后续,最后聚合上报。
10
15
  */
11
- export const SHUTDOWN_ORDER = ['stopProducers', 'disposeServices'];
16
+ export const SHUTDOWN_ORDER = [
17
+ 'flushRouters',
18
+ 'flushMiddlewares',
19
+ 'flushServices',
20
+ 'disposeServices',
21
+ 'flushConfig',
22
+ 'flushResources',
23
+ ];
12
24
  /**
13
25
  * 顺序执行关闭步骤;单步抛错记录后**继续**后续步骤,最后聚合上报
14
26
  * (避免「一步报错导致后面资源没关」)。
@@ -13,13 +13,24 @@ function createRecordingHandles(record, throwOn) {
13
13
  }
14
14
  };
15
15
  return {
16
- stopProducers: step('stopProducers'),
16
+ flushRouters: step('flushRouters'),
17
+ flushMiddlewares: step('flushMiddlewares'),
18
+ flushServices: step('flushServices'),
17
19
  disposeServices: step('disposeServices'),
20
+ flushConfig: step('flushConfig'),
21
+ flushResources: step('flushResources'),
18
22
  };
19
23
  }
20
24
  describe('SHUTDOWN_ORDER', () => {
21
- it('固定两步且顺序不可变', () => {
22
- expect(SHUTDOWN_ORDER).toEqual(['stopProducers', 'disposeServices']);
25
+ it('固定六步且顺序不可变:阶段逆序 flush,services 内部先 flush 再逐单元 dispose', () => {
26
+ expect(SHUTDOWN_ORDER).toEqual([
27
+ 'flushRouters',
28
+ 'flushMiddlewares',
29
+ 'flushServices',
30
+ 'disposeServices',
31
+ 'flushConfig',
32
+ 'flushResources',
33
+ ]);
23
34
  });
24
35
  });
25
36
  describe('runShutdown', () => {
@@ -32,7 +43,7 @@ describe('runShutdown', () => {
32
43
  it('单步抛错不阻断后续,并聚合上报', async () => {
33
44
  const record = [];
34
45
  const { entries, log } = createShutdownLog();
35
- await runShutdown(createRecordingHandles(record, 'stopProducers'), log);
46
+ await runShutdown(createRecordingHandles(record, 'flushRouters'), log);
36
47
  expect(record).toEqual([...SHUTDOWN_ORDER]);
37
48
  const aggregate = entries.find((entry) => entry.msg === '[shutdown] completed with errors');
38
49
  expect(aggregate).toBeDefined();
@@ -42,15 +53,25 @@ describe('runShutdown', () => {
42
53
  });
43
54
  });
44
55
  describe('关闭流程集成', () => {
45
- it('默认两步按序执行:disposer → services 逆序', async () => {
56
+ it('各阶段 disposer 按阶段逆序执行:routers → middlewares → services → config → resources', async () => {
46
57
  const calls = [];
47
58
  const server = defineServer({
48
- app: { service: 'shutdown-integration', title: 'Shutdown Integration', version: '1.0.0' },
49
- resources: () => ({ database: { dispose: () => calls.push('disposeResource') } }),
50
- config: () => ({}),
59
+ app: { service: 'shutdown-stages', title: 'Shutdown Stages', version: '1.0.0' },
60
+ resources: (ctx) => {
61
+ ctx.disposer.add(() => {
62
+ calls.push('flushResources');
63
+ });
64
+ return {};
65
+ },
66
+ config: (ctx) => {
67
+ ctx.disposer.add(() => {
68
+ calls.push('flushConfig');
69
+ });
70
+ return {};
71
+ },
51
72
  services: (ctx) => {
52
73
  ctx.disposer.add(() => {
53
- calls.push('stopProducers');
74
+ calls.push('flushServices');
54
75
  });
55
76
  return {
56
77
  a: {
@@ -58,26 +79,111 @@ describe('关闭流程集成', () => {
58
79
  calls.push('disposeServiceA');
59
80
  },
60
81
  },
61
- b: {
62
- dispose: () => {
63
- calls.push('disposeServiceB');
64
- },
65
- },
66
82
  };
67
83
  },
84
+ middlewares: (ctx) => {
85
+ ctx.disposer.add(() => {
86
+ calls.push('flushMiddlewares');
87
+ });
88
+ return [];
89
+ },
90
+ routers: (ctx) => {
91
+ ctx.disposer.add(() => {
92
+ calls.push('flushRouters');
93
+ });
94
+ },
95
+ });
96
+ const app = await startServer(server, { listen: false });
97
+ await app.close();
98
+ expect(calls).toEqual([
99
+ 'flushRouters',
100
+ 'flushMiddlewares',
101
+ 'flushServices',
102
+ 'disposeServiceA',
103
+ 'flushConfig',
104
+ 'flushResources',
105
+ ]);
106
+ });
107
+ it('阶段内多个 disposer 按**逆注册序**(LIFO)执行', async () => {
108
+ const calls = [];
109
+ const server = defineServer({
110
+ app: { service: 'shutdown-lifo', title: 'Shutdown LIFO', version: '1.0.0' },
111
+ resources: (ctx) => {
112
+ // 先建 A,再建 B(B 可能依赖 A)→ 关闭时先 B 后 A
113
+ ctx.disposer.add(() => {
114
+ calls.push('disposeA');
115
+ });
116
+ ctx.disposer.add(() => {
117
+ calls.push('disposeB');
118
+ });
119
+ return {};
120
+ },
121
+ config: () => ({}),
122
+ services: () => ({}),
68
123
  middlewares: () => [],
69
124
  routers: () => undefined,
70
125
  });
71
126
  const app = await startServer(server, { listen: false });
72
127
  await app.close();
73
- // 资源不在生命周期内(框架只管 services)
74
- expect(calls).toEqual(['stopProducers', 'disposeServiceB', 'disposeServiceA']);
128
+ expect(calls).toEqual(['disposeB', 'disposeA']);
129
+ });
130
+ it('某个 disposer 抛错不影响同阶段其它 disposer(且 `close()` 不抛)', async () => {
131
+ const calls = [];
132
+ const server = defineServer({
133
+ app: { service: 'shutdown-disposer-error', title: 'Shutdown Disposer Error', version: '1.0.0' },
134
+ resources: (ctx) => {
135
+ ctx.disposer.add(() => {
136
+ calls.push('disposeA');
137
+ });
138
+ ctx.disposer.add(() => {
139
+ calls.push('disposeB:throw');
140
+ throw new Error('disposer boom');
141
+ });
142
+ ctx.disposer.add(() => {
143
+ calls.push('disposeC');
144
+ });
145
+ return {};
146
+ },
147
+ config: () => ({}),
148
+ services: () => ({}),
149
+ middlewares: () => [],
150
+ routers: () => undefined,
151
+ });
152
+ const app = await startServer(server, { listen: false });
153
+ await expect(app.close()).resolves.toBeUndefined();
154
+ // LIFO:C → B(抛错)→ A;B 的错误不阻断 A
155
+ expect(calls).toEqual(['disposeC', 'disposeB:throw', 'disposeA']);
156
+ });
157
+ it('未注册任何 disposer 时无操作:关闭照常完成', async () => {
158
+ const calls = [];
159
+ const server = defineServer({
160
+ app: { service: 'shutdown-empty', title: 'Shutdown Empty', version: '1.0.0' },
161
+ resources: () => ({}),
162
+ config: () => ({}),
163
+ services: () => ({
164
+ a: {
165
+ dispose: () => {
166
+ calls.push('disposeServiceA');
167
+ },
168
+ },
169
+ }),
170
+ middlewares: () => [],
171
+ routers: () => undefined,
172
+ });
173
+ const app = await startServer(server, { listen: false });
174
+ await expect(app.close()).resolves.toBeUndefined();
175
+ expect(calls).toEqual(['disposeServiceA']);
75
176
  });
76
177
  it('StartServerOptions.shutdown 整体覆盖默认关闭流程', async () => {
77
178
  const calls = [];
78
179
  const server = defineServer({
79
180
  app: { service: 'shutdown-override', title: 'Shutdown Override', version: '1.0.0' },
80
- resources: () => ({}),
181
+ resources: (ctx) => {
182
+ ctx.disposer.add(() => {
183
+ calls.push('flushResources');
184
+ });
185
+ return {};
186
+ },
81
187
  config: () => ({}),
82
188
  services: () => ({
83
189
  a: {
@@ -98,6 +204,7 @@ describe('关闭流程集成', () => {
98
204
  },
99
205
  });
100
206
  await app.close();
207
+ // 整体覆盖:默认六步(含各阶段 flush)都不执行
101
208
  expect(calls).toEqual(['custom']);
102
209
  });
103
210
  });
@@ -21,8 +21,16 @@ import type { SystemError } from './errors.js';
21
21
  * 槽位只能引用**排在它前面**的产物(向下引用是编译错误);`env` 只属引导期
22
22
  * (`resources` / `config` 可见,运行期三层一律看不到)。
23
23
  *
24
+ * **`disposer` 是每个槽位 ctx 的标配**(不是某个槽位专属):在**创建某个东西的地方**当场
25
+ * `ctx.disposer.add(() => 释放它)`,创建与释放同处一地;`disposer` 是**出口**(不产出任何依赖),
26
+ * 因此不会造成循环,也不削弱「只给该阶段该看的字段」的约束。
27
+ *
28
+ * 生命周期:每个阶段一个独立的 `Disposer` 实例;关闭时**按阶段逆序 flush**
29
+ * `routers → middlewares → services → config → resources`(services 阶段内部:先 flush
30
+ * disposer,再逐单元逆序 `dispose`)。详见 `lifecycle.ts` / `shutdown.ts`。
31
+ *
24
32
  * 类型安全机制(N2 §1):
25
- * - **M1 分阶段 ctx**:每个工厂只拿到「该阶段该看的字段」;
33
+ * - **M1 分阶段 ctx**:每个工厂只拿到「该阶段该看的字段」(**外加每个阶段都有的 `disposer`**);
26
34
  * - **M2 `Awaited<>` 归一**:工厂签名写 `(ctx) => R`(不是 `R | Promise<R>`),下游一律
27
35
  * `Awaited<R>` / `Awaited<C>` / `Awaited<S>`;
28
36
  * - **M3 资源嵌套**:资源包挂在 `ctx.resources` 上(不再摊平到 ctx,键冲突从根上消失);
@@ -91,13 +99,21 @@ export type ResourcesView<R> = Live<Awaited<R>>;
91
99
  */
92
100
  export type ErrorClassifier = (error: unknown) => SystemError | null;
93
101
  /**
94
- * `resources(ctx)` 的入参:**只有 `env` + `log`**(外加槽位专属的注册能力)。
102
+ * `resources(ctx)` 的入参:**`env` + `log` + `disposer`**(外加槽位专属的注册能力)。
95
103
  *
96
104
  * 结构上排除 `config` / `services` / `app` → 「资源依赖配置」这类循环**编译期写不出来**。
105
+ * `disposer` 是**出口**(只能注册释放函数,不产出依赖),因此不构成新的循环面:
106
+ * 资源在构造处当场 `ctx.disposer.add(() => 释放它)`,关闭时框架最后 flush。
97
107
  */
98
108
  export interface ResourcesContext {
99
109
  readonly env: ConfigEnv;
100
110
  readonly log: LoggerLike;
111
+ /**
112
+ * 本阶段的释放器:**在创建资源的地方当场注册释放**。
113
+ *
114
+ * 关闭序中 `resources` 阶段**最后** flush(所有其它阶段都还活着)。
115
+ */
116
+ readonly disposer: Disposer;
101
117
  /**
102
118
  * **每个资源注册自己的错误分类器**(响应层用)。
103
119
  *
@@ -109,13 +125,15 @@ export interface ResourcesContext {
109
125
  }
110
126
  export type ResourcesFactory<R> = (ctx: ResourcesContext) => R;
111
127
  /**
112
- * `config(ctx)` 的入参:`env` + `log` + 资源视图。
128
+ * `config(ctx)` 的入参:`env` + `log` + `disposer` + 资源视图。
113
129
  *
114
130
  * **没有** `services`(此时尚未构造)/ `app`(此时尚未装配)。
115
131
  */
116
132
  export interface ConfigContext<R> {
117
133
  readonly env: ConfigEnv;
118
134
  readonly log: LoggerLike;
135
+ /** 本阶段的释放器(关闭时在 services 之后、resources 之前 flush) */
136
+ readonly disposer: Disposer;
119
137
  readonly resources: ResourcesView<R>;
120
138
  }
121
139
  export type ConfigFactory<R, C> = (ctx: ConfigContext<R>) => C;
@@ -136,6 +154,7 @@ export interface ServiceUnit {
136
154
  */
137
155
  export interface ServiceContext<R, C> {
138
156
  readonly log: LoggerLike;
157
+ /** 本阶段的释放器(关闭时**先** flush 它,再逐单元逆序 `dispose`) */
139
158
  readonly disposer: Disposer;
140
159
  readonly resources: ResourcesView<R>;
141
160
  readonly config: Awaited<C>;
@@ -151,25 +170,29 @@ export interface MiddlewareUnit {
151
170
  register(app: AppInstance): void | Promise<void>;
152
171
  }
153
172
  /**
154
- * `middlewares(ctx)` 的入参:`app` + `log` + `services` + `config`。
173
+ * `middlewares(ctx)` 的入参:`app` + `log` + `disposer` + `services` + `config`。
155
174
  *
156
175
  * ❗**故意没有 `resources` / `env`** —— 中间件要访问数据层必须穿过 service。
157
176
  */
158
177
  export interface MiddlewareContext<C, S> {
159
178
  readonly app: AppInstance;
160
179
  readonly log: LoggerLike;
180
+ /** 本阶段的释放器(关闭时最早 flush:在 services / config / resources 之前) */
181
+ readonly disposer: Disposer;
161
182
  readonly services: Awaited<S>;
162
183
  readonly config: Awaited<C>;
163
184
  }
164
185
  export type MiddlewaresFactory<C, S> = (ctx: MiddlewareContext<C, S>) => MiddlewareUnit[];
165
186
  /**
166
- * `routers(ctx)` 的入参:`app` + `log` + `services`。
187
+ * `routers(ctx)` 的入参:`app` + `log` + `disposer` + `services`。
167
188
  *
168
189
  * ❗**故意没有 `resources` / `env` / `config`** —— 路由**只能穿过 service**,绕不过层直连数据层。
169
190
  */
170
191
  export interface RouterContext<S> {
171
192
  readonly app: AppInstance;
172
193
  readonly log: LoggerLike;
194
+ /** 本阶段的释放器(关闭时**最先** flush) */
195
+ readonly disposer: Disposer;
173
196
  readonly services: Awaited<S>;
174
197
  }
175
198
  export type RoutersFactory<S> = (ctx: RouterContext<S>) => void | Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../source/app/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AACnF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAChE,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAEhE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAMH,gFAAgF;AAChF,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAC3C,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C,KAAK,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAC5C;AAED,+BAA+B;AAC/B,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;AAE5C;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,eAAe,CACvC,gBAAgB,EAChB,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,eAAe,CAChB,CAAA;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,CAAA;IACf,OAAO,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACpC;AAED;;;;;GAKG;AACH,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAE,CAAA;AAEhF,qDAAqD;AACrD,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;AAM/C;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,OAAO,KAAK,WAAW,GAAG,IAAI,CAAA;AAEpE;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAA;IACvB,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAA;IACxB;;;;;;OAMG;IACH,qBAAqB,CAAC,UAAU,EAAE,eAAe,GAAG,IAAI,CAAA;CACzD;AAED,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,KAAK,CAAC,CAAA;AAE9D;;;;GAIG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAA;IACvB,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAA;IACxB,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;CACrC;AAED,MAAM,MAAM,aAAa,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;AAE9D;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7B,OAAO,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACjC;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC,EAAE,CAAC;IAClC,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAA;IACxB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;IAC3B,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,CAAA;IACpC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;CAC5B;AAED,MAAM,MAAM,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAA;AAEvE;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACjD;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC,EAAE,CAAC;IACrC,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAA;IACzB,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAA;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IAC7B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;CAC5B;AAED,MAAM,MAAM,kBAAkB,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,cAAc,EAAE,CAAA;AAEzF;;;;GAIG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAA;IACzB,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAA;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;CAC9B;AAED,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAM/E;;GAEG;AACH,MAAM,MAAM,oBAAoB,CAAC,CAAC,IAAI;KACnC,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;CACnE,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;KAC5B,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,GAAG,OAAO,GAClD,KAAK,GACL,CAAC,CAAC,CAAC,CAAC,SAAS;QAAE,SAAS,EAAE,OAAO,CAAA;KAAE,GACjC,KAAK,GACL,CAAC,CAAC,CAAC,CAAC;CACX,CAAA;AAMD;;;;;GAKG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;IACnC,GAAG,EAAE,WAAW,CAAA;IAChB,SAAS,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAA;IAC9B,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACpC,QAAQ,EAAE,CAAC,GAAG,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;IAC/E,WAAW,EAAE,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;IACvD,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;CACpC;AAED,kCAAkC;AAClC,eAAO,MAAM,YAAY,mBAAmB,CAAA;AAE5C,oCAAoC;AACpC,MAAM,MAAM,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG;IACpD,QAAQ,CAAC,OAAO,EAAE,OAAO,YAAY,CAAA;CACtC,CAAA;AAED,4BAA4B;AAC5B,MAAM,WAAW,kBAAkB,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO;IACvE,+CAA+C;IAC/C,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,eAAe;IACf,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5D;AAED,oDAAoD;AACpD,MAAM,WAAW,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAE,SAAQ,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC;IACpE,QAAQ,CAAC,GAAG,EAAE,WAAW,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;CAC9B;AAED,4CAA4C;AAC5C,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../source/app/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taicode/common-server",
3
- "version": "4.0.0-beta.2",
3
+ "version": "4.0.0-beta.3",
4
4
  "author": "Alain",
5
5
  "license": "ISC",
6
6
  "description": "",
@@ -35,7 +35,7 @@
35
35
  "peerDependencies": {
36
36
  "fastify": "^5.5.0",
37
37
  "redis": "^5.11.0",
38
- "@taicode/common-base": "^4.0.0-beta.2"
38
+ "@taicode/common-base": "^4.0.0-beta.3"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@eslint/js": "^9.39.3",
@@ -49,7 +49,7 @@
49
49
  "redis": "^5.11.0",
50
50
  "typescript-eslint": "^8.56.1",
51
51
  "vitest": "^3.2.4",
52
- "@taicode/common-base": "^4.0.0-beta.2"
52
+ "@taicode/common-base": "^4.0.0-beta.3"
53
53
  },
54
54
  "scripts": {
55
55
  "test": "vitest run",