@taicode/common-server 4.0.0-beta.1 → 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.
Files changed (38) hide show
  1. package/output/app/boot.d.ts +9 -13
  2. package/output/app/boot.d.ts.map +1 -1
  3. package/output/app/boot.js +62 -27
  4. package/output/app/context.d.ts +7 -1
  5. package/output/app/context.d.ts.map +1 -1
  6. package/output/app/context.js +9 -3
  7. package/output/app/core/index.d.ts +3 -3
  8. package/output/app/core/index.d.ts.map +1 -1
  9. package/output/app/core/index.js +1 -1
  10. package/output/app/core/response.d.ts +8 -8
  11. package/output/app/core/response.d.ts.map +1 -1
  12. package/output/app/core/response.js +6 -6
  13. package/output/app/define-server.negative-cases.d.ts +8 -0
  14. package/output/app/define-server.negative-cases.d.ts.map +1 -1
  15. package/output/app/define-server.negative-cases.js +60 -1
  16. package/output/app/entries.d.ts +2 -0
  17. package/output/app/entries.d.ts.map +1 -1
  18. package/output/app/entries.js +2 -0
  19. package/output/app/error-contract.test.js +110 -8
  20. package/output/app/index.d.ts +1 -1
  21. package/output/app/index.d.ts.map +1 -1
  22. package/output/app/lifecycle-logging.test.js +23 -3
  23. package/output/app/lifecycle.d.ts +32 -6
  24. package/output/app/lifecycle.d.ts.map +1 -1
  25. package/output/app/lifecycle.js +34 -6
  26. package/output/app/lifecycle.test.js +4 -3
  27. package/output/app/middleware-order.test.js +4 -3
  28. package/output/app/redis.d.ts +5 -1
  29. package/output/app/redis.d.ts.map +1 -1
  30. package/output/app/redis.js +5 -1
  31. package/output/app/resources.test.js +45 -20
  32. package/output/app/shutdown.d.ts +9 -4
  33. package/output/app/shutdown.d.ts.map +1 -1
  34. package/output/app/shutdown.js +16 -4
  35. package/output/app/shutdown.test.js +125 -18
  36. package/output/app/types.d.ts +45 -5
  37. package/output/app/types.d.ts.map +1 -1
  38. package/package.json +3 -3
@@ -1,4 +1,3 @@
1
- import type { ErrorClassifier } from './core/response.js';
2
1
  import type { ConfigEnv } from './env.js';
3
2
  import type { ResourcesView, Server, ShutdownContext } from './types.js';
4
3
  import type { FastifyInstance } from 'fastify';
@@ -22,27 +21,24 @@ export interface BootedServer<R, S> {
22
21
  services: Awaited<S>;
23
22
  dispose: () => Promise<void>;
24
23
  }
25
- /**
26
- * 资源包里的数据层错误分类器。
27
- *
28
- * 约定:`resources(ctx)` 返回的资源包里**任一**提供 `classifyError` 函数的值都会被用上
29
- * (框架不提供数据层 helper;由服务仓在资源包里自行提供一个带 `classifyError` 的值)。
30
- * 都没有 → 返回 `undefined`,响应层落 `UNKNOWN_ERROR` 兜底。
31
- */
32
- export declare function resolveErrorClassifier(resources: Record<string, unknown>): ErrorClassifier | undefined;
33
24
  /**
34
25
  * 装配一个服务(`startServer` / `generateOpenApi` 共用)。
35
26
  *
36
27
  * 步序(N1 §5):
37
28
  * ```
38
29
  * 1. 加载 env(框架固定,调用方已做) → env
39
- * 2. resources(ctx={env,log}) → R(只构造,不建连)
40
- * 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 路径不跑)
41
32
  * 4. services(ctx={log,disposer,resources,config}) → S,随后按声明序 init()(仅 start 路径)
42
- * 5. middlewares(ctx={app,log,services,config}) → 按数组序 register(app)
43
- * 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}) → 注册路由
44
35
  * 7. ready / listen
45
36
  * ```
37
+ *
38
+ * **每个阶段一个独立 `Disposer`**(构造处注册释放);关闭时按阶段**逆序 flush**
39
+ * `routers → middlewares → services → config → resources`(services 阶段内部先 flush 自己的
40
+ * `disposer`,再逐单元逆序 `dispose`)。资源因此**最后**才断:service 关闭时若还要落库不会
41
+ * 撞死连接。出 spec 路径不关闭,也不执行任何 flush。
46
42
  */
47
43
  export declare function bootServer<R, C, S>(input: BootInput<R, C, S>): Promise<BootedServer<R, S>>;
48
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,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACzC,OAAO,KAAK,EAAe,aAAa,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACrF,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;AAcD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,GAAG,SAAS,CAOtG;AAED;;;;;;;;;;;;;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,CAkGhG"}
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` 共用。 */
@@ -16,23 +16,31 @@ function resolvePort(env) {
16
16
  }
17
17
  return parsed;
18
18
  }
19
- function isRecord(value) {
20
- return typeof value === 'object' && value !== null;
21
- }
22
19
  /**
23
- * 资源包里的数据层错误分类器。
20
+ * 把各资源通过 `ctx.registerClassifyError` 注册的错误分类器合成单个分类器。
21
+ *
22
+ * 范式:**一个资源一个分类器,各管各的错误域**(认领不了就返回 `null` 交给下一个)。
24
23
  *
25
- * 约定:`resources(ctx)` 返回的资源包里**任一**提供 `classifyError` 函数的值都会被用上
26
- * (框架不提供数据层 helper;由服务仓在资源包里自行提供一个带 `classifyError` 的值)。
27
- * 都没有 → 返回 `undefined`,响应层落 `UNKNOWN_ERROR` 兜底。
24
+ * - 零个 → `undefined`(响应层落 `UNKNOWN_ERROR` 兜底);
25
+ * - 一个 → 原样返回(少一层包装);
26
+ * - 多个 → 按**注册顺序**串成链,返回第一个非 `null` 的结果(短路)。
28
27
  */
29
- export function resolveErrorClassifier(resources) {
30
- for (const value of Object.values(resources)) {
31
- if (isRecord(value) && typeof value.classifyError === 'function') {
32
- return (error) => value.classifyError(error);
33
- }
28
+ function combineClassifiers(classifiers) {
29
+ if (classifiers.length === 0) {
30
+ return undefined;
34
31
  }
35
- return undefined;
32
+ if (classifiers.length === 1) {
33
+ return classifiers[0];
34
+ }
35
+ return (error) => {
36
+ for (const classifier of classifiers) {
37
+ const result = classifier(error);
38
+ if (result !== null) {
39
+ return result;
40
+ }
41
+ }
42
+ return null;
43
+ };
36
44
  }
37
45
  /**
38
46
  * 装配一个服务(`startServer` / `generateOpenApi` 共用)。
@@ -40,13 +48,18 @@ export function resolveErrorClassifier(resources) {
40
48
  * 步序(N1 §5):
41
49
  * ```
42
50
  * 1. 加载 env(框架固定,调用方已做) → env
43
- * 2. resources(ctx={env,log}) → R(只构造,不建连)
44
- * 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 路径不跑)
45
53
  * 4. services(ctx={log,disposer,resources,config}) → S,随后按声明序 init()(仅 start 路径)
46
- * 5. middlewares(ctx={app,log,services,config}) → 按数组序 register(app)
47
- * 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}) → 注册路由
48
56
  * 7. ready / listen
49
57
  * ```
58
+ *
59
+ * **每个阶段一个独立 `Disposer`**(构造处注册释放);关闭时按阶段**逆序 flush**
60
+ * `routers → middlewares → services → config → resources`(services 阶段内部先 flush 自己的
61
+ * `disposer`,再逐单元逆序 `dispose`)。资源因此**最后**才断:service 关闭时若还要落库不会
62
+ * 撞死连接。出 spec 路径不关闭,也不执行任何 flush。
50
63
  */
51
64
  export async function bootServer(input) {
52
65
  const { server, env, phase } = input;
@@ -57,27 +70,44 @@ export async function bootServer(input) {
57
70
  const app = createFastify(env);
58
71
  const instance = app;
59
72
  const log = app.log;
60
- 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
+ };
61
81
  const holder = createConfigHolder();
62
82
  // ② resources:只构造,不建连(出 spec 路径同样执行)
63
- const resources = (await server.resources(createResourcesContext({ env, log })));
83
+ // 资源错误分类器由各资源通过 `ctx.registerClassifyError` 各自注册:在调 `resources(ctx)` 前建局部收集器,
84
+ // 返回后把注册结果按注册顺序合成单个分类器喂给 core。
85
+ const classifiers = [];
86
+ const resources = (await server.resources(createResourcesContext({
87
+ env,
88
+ log,
89
+ disposer: disposers.resources,
90
+ registerClassifyError: (classifier) => {
91
+ classifiers.push(classifier);
92
+ },
93
+ })));
64
94
  const resourcesView = liveResources(resources);
65
95
  // ③ core 装配(schema → cookie → response → openapi → 路由注册表)
66
96
  await registerCore(app, {
67
97
  identity: server.app,
68
98
  env,
69
- classifyError: resolveErrorClassifier(resourcesView),
99
+ classifyResourceError: combineClassifiers(classifiers),
70
100
  readConfig: (key) => readConfigKey(requireConfig(holder), key),
71
101
  });
72
102
  // ── compose ──────────────────────────────────────────────────────────────
73
103
  // ① config(仅 start 路径;出 spec 路径不跑 → `ctx.config` 读到即抛 CONFIG_UNAVAILABLE)
74
104
  if (isStart) {
75
- holder.current = (await server.config(createConfigContext({ env, log, resources: resourcesView })));
105
+ holder.current = (await server.config(createConfigContext({ env, log, disposer: disposers.config, resources: resourcesView })));
76
106
  }
77
107
  // ② services(两个入口都执行;ctx 无 env、无 services)
78
108
  const services = server.services(createServiceContext({
79
109
  log,
80
- disposer,
110
+ disposer: disposers.services,
81
111
  resources: resourcesView,
82
112
  holder,
83
113
  }));
@@ -86,20 +116,21 @@ export async function bootServer(input) {
86
116
  await initServiceUnits({ service, log, services: services });
87
117
  }
88
118
  // ④ middlewares(两个入口都执行:出 spec 也照注册,保证 schema 一致)
89
- const middlewares = server.middlewares(createMiddlewareContext({ app: instance, log, services, holder }));
119
+ const middlewares = server.middlewares(createMiddlewareContext({ app: instance, log, disposer: disposers.middlewares, services, holder }));
90
120
  for (const [index, unit] of middlewares.entries()) {
91
121
  await unit.register(instance);
92
122
  log.debug({ service, unit: middlewareUnitName(service, unit, index), stage: MIDDLEWARES_LOG_STAGE }, '[middleware] registered');
93
123
  }
94
124
  // ⑤ routers(两个入口都执行)
95
- await server.routers(createRouterContext({ app: instance, log, services }));
125
+ await server.routers(createRouterContext({ app: instance, log, disposer: disposers.routers, services }));
96
126
  // ── activate ─────────────────────────────────────────────────────────────
97
127
  const runShutdownFlow = async () => {
98
128
  if (input.shutdown) {
129
+ // 整体覆盖:自定义 shutdown 自行负责各阶段 flush(框架不再代劳)
99
130
  await input.shutdown(createShutdownContext({
100
131
  app: instance,
101
132
  log,
102
- disposer,
133
+ disposer: disposers.services,
103
134
  resources: resourcesView,
104
135
  holder,
105
136
  services,
@@ -107,8 +138,12 @@ export async function bootServer(input) {
107
138
  return;
108
139
  }
109
140
  await runShutdown({
110
- 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 }),
111
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 }),
112
147
  }, log);
113
148
  };
114
149
  app.addHook('onClose', () => runShutdownFlow());
@@ -1,6 +1,6 @@
1
1
  import type { Disposer } from '@taicode/common-base';
2
2
  import type { ConfigEnv } from './env.js';
3
- import type { AppInstance, ConfigContext, LoggerLike, MiddlewareContext, ResourcesContext, ResourcesView, RouterContext, ServiceContext, ShutdownContext } from './types.js';
3
+ import type { AppInstance, ConfigContext, ErrorClassifier, LoggerLike, MiddlewareContext, ResourcesContext, ResourcesView, RouterContext, ServiceContext, ShutdownContext } from './types.js';
4
4
  /**
5
5
  * 分阶段 ctx 的运行时构造(N1 §2)。
6
6
  *
@@ -23,12 +23,16 @@ 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;
27
+ /** 收集器:`bootServer` 注入,把各资源注册的分类器按注册顺序收起来 */
28
+ registerClassifyError: (classifier: ErrorClassifier) => void;
26
29
  }
27
30
  export declare function createResourcesContext(params: ResourcesContextParams): ResourcesContext;
28
31
  /** `config(ctx)` 入参 */
29
32
  export interface ConfigContextParams<R> {
30
33
  env: ConfigEnv;
31
34
  log: LoggerLike;
35
+ disposer: Disposer;
32
36
  resources: ResourcesView<R>;
33
37
  }
34
38
  export declare function createConfigContext<R>(params: ConfigContextParams<R>): ConfigContext<R>;
@@ -44,6 +48,7 @@ export declare function createServiceContext<R, C>(params: ServiceContextParams<
44
48
  export interface MiddlewareContextParams<C, S> {
45
49
  app: AppInstance;
46
50
  log: LoggerLike;
51
+ disposer: Disposer;
47
52
  services: Awaited<S>;
48
53
  holder: ConfigHolder<C>;
49
54
  }
@@ -52,6 +57,7 @@ export declare function createMiddlewareContext<C, S>(params: MiddlewareContextP
52
57
  export interface RouterContextParams<S> {
53
58
  app: AppInstance;
54
59
  log: LoggerLike;
60
+ disposer: Disposer;
55
61
  services: Awaited<S>;
56
62
  }
57
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 };
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 {
@@ -9,7 +9,7 @@ export { registerRequestId, REQUEST_ID_HEADER, REQUEST_ID_LOG_LABEL } from './re
9
9
  export { createLoggerOptions, LOG_REDACT_PATHS } from './logging.js';
10
10
  export { SERVER_IDENTITY, attachServerIdentity, readServerIdentity } from './identity.js';
11
11
  export type { AuthCookieOptions, CookieCoreOptions } from './cookie.js';
12
- export type { ResponseCoreOptions, ResponseEnvelope } from './response.js';
12
+ export type { ResponseCoreOptions, ResponseEnvelope, ErrorClassifier } from './response.js';
13
13
  export type { OpenApiCoreOptions } from './openapi.js';
14
14
  /**
15
15
  * core `schema`(设计 §5.1)——**框架内置、锁定、不可替换/修改**。
@@ -30,8 +30,8 @@ export interface CoreRegistrationOptions {
30
30
  /** 身份块(`service` / `title` / `version`):openapi + 内置单元从这里读 */
31
31
  identity: AppIdentity;
32
32
  env: ConfigEnv;
33
- /** 数据层错误分类(资源包提供;缺省 = 落 `UNKNOWN_ERROR`) */
34
- classifyError?: ErrorClassifier;
33
+ /** 资源错误分类(各资源经 `ctx.registerClassifyError` 注册;缺省 = 落 `UNKNOWN_ERROR`) */
34
+ classifyResourceError?: ErrorClassifier;
35
35
  /** 取**已加载**配置中某个键的访问器(cookie domain 等**惰性**取值;不得在构造期调用) */
36
36
  readConfig: (key: string) => unknown;
37
37
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../source/app/core/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAC1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE9C,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACrF,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAC5F,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAChF,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAC5F,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACpE,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAEzF,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACvE,YAAY,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAC1E,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAEtD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,CAMzD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,SAAS,GAAG,eAAe,CAM7D;AAED,gBAAgB;AAChB,MAAM,WAAW,uBAAuB;IACtC,8DAA8D;IAC9D,QAAQ,EAAE,WAAW,CAAA;IACrB,GAAG,EAAE,SAAS,CAAA;IACd,4CAA4C;IAC5C,aAAa,CAAC,EAAE,eAAe,CAAA;IAC/B,2DAA2D;IAC3D,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAA;CACrC;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAaxG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../source/app/core/index.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAC1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE9C,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACrF,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAC5F,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAChF,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAC5F,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACpE,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAEzF,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AACvE,YAAY,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC3F,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAEtD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,CAMzD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,SAAS,GAAG,eAAe,CAM7D;AAED,gBAAgB;AAChB,MAAM,WAAW,uBAAuB;IACtC,8DAA8D;IAC9D,QAAQ,EAAE,WAAW,CAAA;IACrB,GAAG,EAAE,SAAS,CAAA;IACd,yEAAyE;IACzE,qBAAqB,CAAC,EAAE,eAAe,CAAA;IACvC,2DAA2D;IAC3D,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAA;CACrC;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAaxG"}
@@ -45,7 +45,7 @@ export async function registerCore(app, options) {
45
45
  attachServerIdentity(app, options.identity);
46
46
  registerSchema(app); // schema → zod compiler
47
47
  registerCookie(app, { env: options.env, readConfig: options.readConfig });
48
- registerResponse(app, { classifyError: options.classifyError });
48
+ registerResponse(app, { classifyResourceError: options.classifyResourceError });
49
49
  // await:确定性冲刷插件队列,保证 swagger 的 root `onRoute` 钩子在路由注册之前就位
50
50
  await registerOpenApi(app, {
51
51
  title: options.identity.title,
@@ -1,12 +1,12 @@
1
- import { SystemError } from '../errors.js';
2
1
  import type { FastifyInstance } from 'fastify';
2
+ import type { ErrorClassifier } from '../types.js';
3
3
  /**
4
4
  * core `response`(设计 §5.1 / §8,错误契约 D7)——**框架内置、锁定、不可替换/修改**。
5
5
  *
6
6
  * - 成功信封 `{ status: 'SUCCESS', message: 'ok', data }`(由 `preSerialization` 统一包裹)。
7
7
  * - 错误信封 `{ status: <错误码字符串>, message, data: null }`。
8
8
  * - `setErrorHandler` 固定分支:`UserError`→400(`error.status` 字面量)/ `SystemError`→500 /
9
- * zod→400 / serde→500 / 数据层(资源包的 `classifyError`)→500 / 未知→500;**禁止按 `error.type` 细分**。
9
+ * zod→400 / serde→500 / 资源(各资源 `ctx.registerClassifyError` 注册的分类器)→500 / 未知→500;**禁止按 `error.type` 细分**。
10
10
  * - `setNotFoundHandler` → 404 + `NOT_IMPLEMENTED`(落 `warn`,文案对齐 expense/notice)。
11
11
  * - 错误日志分级:`UserError` / zod → `warn`,其余 → `error`(带 stack)。
12
12
  * - **错误只给稳定码**(`status` 放错误码字符串),文案不归框架(由前端 / i18n 决定,§10-6)。
@@ -27,15 +27,15 @@ export interface ResponseEnvelope<T> {
27
27
  export declare function createSuccessResponse<T>(data: T): ResponseEnvelope<T>;
28
28
  export declare function createErrorResponse(status: string, message: string): ResponseEnvelope<null>;
29
29
  /**
30
- * 数据层错误分类器(资源包里带 `classifyError` 的值的裸函数形态)。
30
+ * 资源错误分类器(单一来源:`types.ts`)。
31
31
  *
32
- * 资源包(`resources` 槽位返回值)里任何提供 `classifyError` 的值都会被用作响应错误分类;
33
- * 都没有时错误落 `UNKNOWN_ERROR` 兜底。
32
+ * **每个资源注册自己的分类器**(一个资源一个,各管各的错误域);由服务仓在 `resources(ctx)` 里
33
+ * 通过 `ctx.registerClassifyError` 注册;未注册时错误落 `UNKNOWN_ERROR` 兜底。
34
34
  */
35
- export type ErrorClassifier = (error: unknown) => SystemError | null;
35
+ export type { ErrorClassifier };
36
36
  export interface ResponseCoreOptions {
37
- /** Prisma / 数据层错误分类(缺省 = 不做数据层分类) */
38
- classifyError?: ErrorClassifier;
37
+ /** 资源错误分类(各资源经 `ctx.registerClassifyError` 注册;缺省 = 不做资源错误分类) */
38
+ classifyResourceError?: ErrorClassifier;
39
39
  }
40
40
  export declare function registerResponse(app: FastifyInstance, options?: ResponseCoreOptions): void;
41
41
  //# sourceMappingURL=response.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../../source/app/core/response.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAa,MAAM,cAAc,CAAA;AAErD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE9C;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,cAAc,YAAY,CAAA;AACvC,eAAO,MAAM,sBAAsB,oBAAoB,CAAA;AACvD,eAAO,MAAM,oBAAoB,kBAAkB,CAAA;AACnD,eAAO,MAAM,0BAA0B,iCAAiC,CAAA;AACxE,eAAO,MAAM,oBAAoB,kBAAkB,CAAA;AAEnD,uCAAuC;AACvC,eAAO,MAAM,oBAAoB,6BAAS,CAAA;AAC1C,eAAO,MAAM,sBAAsB,6BAAS,CAAA;AAE5C,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,CAAC,CAAA;CACR;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAErE;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAE3F;AAsBD;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,OAAO,KAAK,WAAW,GAAG,IAAI,CAAA;AAEpE,MAAM,WAAW,mBAAmB;IAClC,qCAAqC;IACrC,aAAa,CAAC,EAAE,eAAe,CAAA;CAChC;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,GAAE,mBAAwB,GAAG,IAAI,CAyD9F"}
1
+ {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../../source/app/core/response.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAElD;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,cAAc,YAAY,CAAA;AACvC,eAAO,MAAM,sBAAsB,oBAAoB,CAAA;AACvD,eAAO,MAAM,oBAAoB,kBAAkB,CAAA;AACnD,eAAO,MAAM,0BAA0B,iCAAiC,CAAA;AACxE,eAAO,MAAM,oBAAoB,kBAAkB,CAAA;AAEnD,uCAAuC;AACvC,eAAO,MAAM,oBAAoB,6BAAS,CAAA;AAC1C,eAAO,MAAM,sBAAsB,6BAAS,CAAA;AAE5C,MAAM,WAAW,gBAAgB,CAAC,CAAC;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,CAAC,CAAA;CACR;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAErE;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAE3F;AAsBD;;;;;GAKG;AACH,YAAY,EAAE,eAAe,EAAE,CAAA;AAE/B,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,qBAAqB,CAAC,EAAE,eAAe,CAAA;CACxC;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,GAAE,mBAAwB,GAAG,IAAI,CAyD9F"}
@@ -6,7 +6,7 @@ import { SystemError, UserError } from '../errors.js';
6
6
  * - 成功信封 `{ status: 'SUCCESS', message: 'ok', data }`(由 `preSerialization` 统一包裹)。
7
7
  * - 错误信封 `{ status: <错误码字符串>, message, data: null }`。
8
8
  * - `setErrorHandler` 固定分支:`UserError`→400(`error.status` 字面量)/ `SystemError`→500 /
9
- * zod→400 / serde→500 / 数据层(资源包的 `classifyError`)→500 / 未知→500;**禁止按 `error.type` 细分**。
9
+ * zod→400 / serde→500 / 资源(各资源 `ctx.registerClassifyError` 注册的分类器)→500 / 未知→500;**禁止按 `error.type` 细分**。
10
10
  * - `setNotFoundHandler` → 404 + `NOT_IMPLEMENTED`(落 `warn`,文案对齐 expense/notice)。
11
11
  * - 错误日志分级:`UserError` / zod → `warn`,其余 → `error`(带 stack)。
12
12
  * - **错误只给稳定码**(`status` 放错误码字符串),文案不归框架(由前端 / i18n 决定,§10-6)。
@@ -72,13 +72,13 @@ export function registerResponse(app, options = {}) {
72
72
  request.log.error({ err: error }, '[response] response serialization error');
73
73
  return reply.code(500).send(createErrorResponse(SERIALIZATION_ERROR_STATUS, error.message));
74
74
  }
75
- // ⑤ 数据层(Prisma 等)分类 → 500
76
- const databaseError = options.classifyError?.(error);
77
- if (databaseError) {
78
- request.log.error({ err: error }, '[response] database error');
75
+ // ⑤ 资源错误(各资源注册的分类器依次认领)→ 500
76
+ const resourceError = options.classifyResourceError?.(error);
77
+ if (resourceError) {
78
+ request.log.error({ err: error }, '[response] resource error');
79
79
  return reply
80
80
  .code(500)
81
- .send(createErrorResponse(databaseError.type, resolveMessage(databaseError)));
81
+ .send(createErrorResponse(resourceError.type, resolveMessage(resourceError)));
82
82
  }
83
83
  // ⑥ 未知 → 500
84
84
  request.log.error({ err: error }, '[response] unknown error');
@@ -29,4 +29,12 @@ export declare function middlewaresCannotSeeResources(): void;
29
29
  export declare function middlewaresCannotSeeEnv(): void;
30
30
  /** #15 `services` 看不到 `env`(服务层禁读 env) */
31
31
  export declare function servicesCannotSeeEnv(): void;
32
+ /** #16 `config` 看不到 `registerClassifyError`(resources 槽位专属能力) */
33
+ export declare function configCannotRegisterClassifyError(): void;
34
+ /** #17 `services` 看不到 `registerClassifyError`(resources 槽位专属能力) */
35
+ export declare function servicesCannotRegisterClassifyError(): void;
36
+ /** #18 `middlewares` 看不到 `registerClassifyError`(resources 槽位专属能力) */
37
+ export declare function middlewaresCannotRegisterClassifyError(): void;
38
+ /** #19 `routers` 看不到 `registerClassifyError`(resources 槽位专属能力) */
39
+ export declare function routersCannotRegisterClassifyError(): void;
32
40
  //# sourceMappingURL=define-server.negative-cases.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"define-server.negative-cases.d.ts","sourceRoot":"","sources":["../../source/app/define-server.negative-cases.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAgB9C,kCAAkC;AAClC,wBAAgB,wBAAwB,IAAI,IAAI,CAU/C;AAED,oCAAoC;AACpC,wBAAgB,0BAA0B,IAAI,IAAI,CAUjD;AAED,iCAAiC;AACjC,wBAAgB,uBAAuB,IAAI,IAAI,CAa9C;AAED,iDAAiD;AACjD,wBAAgB,yBAAyB,IAAI,IAAI,CAahD;AAED,6CAA6C;AAC7C,wBAAgB,yBAAyB,IAAI,IAAI,CAYhD;AAED,gCAAgC;AAChC,wBAAgB,sBAAsB,IAAI,IAAI,CAY7C;AAED,gDAAgD;AAChD,wBAAgB,kCAAkC,IAAI,IAAI,CAUzD;AAED,8CAA8C;AAC9C,wBAAgB,6BAA6B,IAAI,IAAI,CAUpD;AAED,qDAAqD;AACrD,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,IAAI,CAGhG;AAED,2BAA2B;AAC3B,wBAAgB,oBAAoB,IAAI,IAAI,CAU3C;AAED,kDAAkD;AAClD,wBAAgB,mCAAmC,IAAI,IAAI,CAa1D;AAED,6BAA6B;AAC7B,wBAAgB,kBAAkB,IAAI,IAAI,CAazC;AAED,mDAAmD;AACnD,wBAAgB,6BAA6B,IAAI,IAAI,CAapD;AAED,kCAAkC;AAClC,wBAAgB,uBAAuB,IAAI,IAAI,CAa9C;AAED,0CAA0C;AAC1C,wBAAgB,oBAAoB,IAAI,IAAI,CAa3C"}
1
+ {"version":3,"file":"define-server.negative-cases.d.ts","sourceRoot":"","sources":["../../source/app/define-server.negative-cases.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAgB9C,kCAAkC;AAClC,wBAAgB,wBAAwB,IAAI,IAAI,CAU/C;AAED,oCAAoC;AACpC,wBAAgB,0BAA0B,IAAI,IAAI,CAUjD;AAED,iCAAiC;AACjC,wBAAgB,uBAAuB,IAAI,IAAI,CAa9C;AAED,iDAAiD;AACjD,wBAAgB,yBAAyB,IAAI,IAAI,CAahD;AAED,6CAA6C;AAC7C,wBAAgB,yBAAyB,IAAI,IAAI,CAYhD;AAED,gCAAgC;AAChC,wBAAgB,sBAAsB,IAAI,IAAI,CAY7C;AAED,gDAAgD;AAChD,wBAAgB,kCAAkC,IAAI,IAAI,CAUzD;AAED,8CAA8C;AAC9C,wBAAgB,6BAA6B,IAAI,IAAI,CAUpD;AAED,qDAAqD;AACrD,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,IAAI,CAGhG;AAED,2BAA2B;AAC3B,wBAAgB,oBAAoB,IAAI,IAAI,CAU3C;AAED,kDAAkD;AAClD,wBAAgB,mCAAmC,IAAI,IAAI,CAa1D;AAED,6BAA6B;AAC7B,wBAAgB,kBAAkB,IAAI,IAAI,CAazC;AAED,mDAAmD;AACnD,wBAAgB,6BAA6B,IAAI,IAAI,CAapD;AAED,kCAAkC;AAClC,wBAAgB,uBAAuB,IAAI,IAAI,CAa9C;AAED,0CAA0C;AAC1C,wBAAgB,oBAAoB,IAAI,IAAI,CAa3C;AAED,iEAAiE;AACjE,wBAAgB,iCAAiC,IAAI,IAAI,CAaxD;AAED,mEAAmE;AACnE,wBAAgB,mCAAmC,IAAI,IAAI,CAa1D;AAED,sEAAsE;AACtE,wBAAgB,sCAAsC,IAAI,IAAI,CAa7D;AAED,kEAAkE;AAClE,wBAAgB,kCAAkC,IAAI,IAAI,CAYzD"}
@@ -1,6 +1,6 @@
1
1
  import { defineServer, startServer } from './entries.js';
2
2
  /**
3
- * N2 §4 的 **15 条负向用例**:这些写法**必须编译失败**,全部由 `tsc --noEmit` 覆盖
3
+ * N2 §4 的 **19 条负向用例**:这些写法**必须编译失败**,全部由 `tsc --noEmit` 覆盖
4
4
  * (**不进**运行期 vitest 断言)。
5
5
  *
6
6
  * 文件名刻意**不带** `.test.ts`:它由 `tsc --noEmit` 校验,不被 vitest 当作测试文件执行。
@@ -207,3 +207,62 @@ export function servicesCannotSeeEnv() {
207
207
  routers: () => undefined,
208
208
  });
209
209
  }
210
+ /** #16 `config` 看不到 `registerClassifyError`(resources 槽位专属能力) */
211
+ export function configCannotRegisterClassifyError() {
212
+ defineServer({
213
+ app: identity,
214
+ resources: () => ({}),
215
+ config: (ctx) => {
216
+ // @ts-expect-error `registerClassifyError` 是 resources 槽位专属能力
217
+ void ctx.registerClassifyError;
218
+ return {};
219
+ },
220
+ services: () => ({}),
221
+ middlewares: () => [],
222
+ routers: () => undefined,
223
+ });
224
+ }
225
+ /** #17 `services` 看不到 `registerClassifyError`(resources 槽位专属能力) */
226
+ export function servicesCannotRegisterClassifyError() {
227
+ defineServer({
228
+ app: identity,
229
+ resources: () => ({}),
230
+ config: () => ({}),
231
+ services: (ctx) => {
232
+ // @ts-expect-error `registerClassifyError` 是 resources 槽位专属能力
233
+ void ctx.registerClassifyError;
234
+ return {};
235
+ },
236
+ middlewares: () => [],
237
+ routers: () => undefined,
238
+ });
239
+ }
240
+ /** #18 `middlewares` 看不到 `registerClassifyError`(resources 槽位专属能力) */
241
+ export function middlewaresCannotRegisterClassifyError() {
242
+ defineServer({
243
+ app: identity,
244
+ resources: () => ({}),
245
+ config: () => ({}),
246
+ services: () => ({}),
247
+ middlewares: (ctx) => {
248
+ // @ts-expect-error `registerClassifyError` 是 resources 槽位专属能力
249
+ void ctx.registerClassifyError;
250
+ return [];
251
+ },
252
+ routers: () => undefined,
253
+ });
254
+ }
255
+ /** #19 `routers` 看不到 `registerClassifyError`(resources 槽位专属能力) */
256
+ export function routersCannotRegisterClassifyError() {
257
+ defineServer({
258
+ app: identity,
259
+ resources: () => ({}),
260
+ config: () => ({}),
261
+ services: () => ({}),
262
+ middlewares: () => [],
263
+ routers: (ctx) => {
264
+ // @ts-expect-error `registerClassifyError` 是 resources 槽位专属能力
265
+ void ctx.registerClassifyError;
266
+ },
267
+ });
268
+ }
@@ -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 文件、不读配置