@taicode/common-server 3.8.0 → 4.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/output/app/index.d.ts +18 -15
  2. package/output/app/index.d.ts.map +1 -1
  3. package/output/app/index.js +330 -9
  4. package/output/auth/index.d.ts +73 -0
  5. package/output/auth/index.d.ts.map +1 -0
  6. package/output/auth/index.js +196 -0
  7. package/output/core/assemble.d.ts +64 -0
  8. package/output/core/assemble.d.ts.map +1 -0
  9. package/output/core/assemble.js +88 -0
  10. package/output/core/boot/context.d.ts +109 -0
  11. package/output/core/boot/context.d.ts.map +1 -0
  12. package/output/core/boot/context.js +145 -0
  13. package/output/core/boot/lifecycle.d.ts +206 -0
  14. package/output/core/boot/lifecycle.d.ts.map +1 -0
  15. package/output/core/boot/lifecycle.js +255 -0
  16. package/output/core/boot/shutdown.d.ts +34 -0
  17. package/output/core/boot/shutdown.d.ts.map +1 -0
  18. package/output/core/boot/shutdown.js +50 -0
  19. package/output/core/boot/signal.d.ts +13 -0
  20. package/output/core/boot/signal.d.ts.map +1 -0
  21. package/output/{app → core/boot}/signal.js +5 -4
  22. package/output/core/boot/steps.d.ts +138 -0
  23. package/output/core/boot/steps.d.ts.map +1 -0
  24. package/output/core/boot/steps.js +259 -0
  25. package/output/core/entries.d.ts +151 -0
  26. package/output/core/entries.d.ts.map +1 -0
  27. package/output/core/entries.js +242 -0
  28. package/output/core/entries.test.d.ts +2 -0
  29. package/output/core/entries.test.d.ts.map +1 -0
  30. package/output/core/entries.test.js +458 -0
  31. package/output/core/env.d.ts +83 -0
  32. package/output/core/env.d.ts.map +1 -0
  33. package/output/core/env.js +124 -0
  34. package/output/{app → core}/env.test.d.ts.map +1 -1
  35. package/output/core/env.test.js +105 -0
  36. package/output/core/modules/auth-provider/index.d.ts +10 -0
  37. package/output/core/modules/auth-provider/index.d.ts.map +1 -0
  38. package/output/core/modules/auth-provider/index.js +9 -0
  39. package/output/core/modules/auth-provider/types.d.ts +71 -0
  40. package/output/core/modules/auth-provider/types.d.ts.map +1 -0
  41. package/output/core/modules/context/index.d.ts +18 -0
  42. package/output/core/modules/context/index.d.ts.map +1 -0
  43. package/output/core/modules/context/index.js +23 -0
  44. package/output/core/modules/context/index.test.d.ts.map +1 -0
  45. package/output/core/modules/cookie/index.d.ts +21 -0
  46. package/output/core/modules/cookie/index.d.ts.map +1 -0
  47. package/output/core/modules/cookie/index.js +17 -0
  48. package/output/core/modules/identity/index.d.ts +4 -0
  49. package/output/core/modules/identity/index.d.ts.map +1 -0
  50. package/output/core/modules/identity/index.js +14 -0
  51. package/output/core/modules/logging/index.d.ts +5 -0
  52. package/output/core/modules/logging/index.d.ts.map +1 -0
  53. package/output/core/modules/logging/index.js +29 -0
  54. package/output/core/modules/openapi/index.d.ts +7 -0
  55. package/output/core/modules/openapi/index.d.ts.map +1 -0
  56. package/output/core/modules/openapi/index.js +41 -0
  57. package/output/core/modules/permission-operations/index.d.ts +26 -0
  58. package/output/core/modules/permission-operations/index.d.ts.map +1 -0
  59. package/output/core/modules/permission-operations/index.js +36 -0
  60. package/output/core/modules/permission-operations/index.test.d.ts +2 -0
  61. package/output/core/modules/permission-operations/index.test.d.ts.map +1 -0
  62. package/output/core/modules/permission-operations/index.test.js +45 -0
  63. package/output/core/modules/request-id/index.d.ts +33 -0
  64. package/output/core/modules/request-id/index.d.ts.map +1 -0
  65. package/output/core/modules/request-id/index.js +66 -0
  66. package/output/core/modules/response/index.d.ts +50 -0
  67. package/output/core/modules/response/index.d.ts.map +1 -0
  68. package/output/core/modules/response/index.js +102 -0
  69. package/output/{schema/index.d.ts → core/modules/schema/builders.d.ts} +8 -1
  70. package/output/core/modules/schema/builders.d.ts.map +1 -0
  71. package/output/{schema/index.js → core/modules/schema/builders.js} +7 -4
  72. package/output/core/modules/schema/index.d.ts +34 -0
  73. package/output/core/modules/schema/index.d.ts.map +1 -0
  74. package/output/core/modules/schema/index.js +62 -0
  75. package/output/core/modules/schema/index.test.d.ts +2 -0
  76. package/output/core/modules/schema/index.test.d.ts.map +1 -0
  77. package/output/core/modules/schema/index.test.js +55 -0
  78. package/output/core/modules/system-routes/index.d.ts +15 -0
  79. package/output/core/modules/system-routes/index.d.ts.map +1 -0
  80. package/output/core/modules/system-routes/index.js +205 -0
  81. package/output/core/resource.d.ts +56 -0
  82. package/output/core/resource.d.ts.map +1 -0
  83. package/output/core/resource.js +56 -0
  84. package/output/core/test-support/database.d.ts +89 -0
  85. package/output/core/test-support/database.d.ts.map +1 -0
  86. package/output/core/test-support/database.js +131 -0
  87. package/output/core/test-support/database.test.d.ts.map +1 -0
  88. package/output/core/test-support/database.test.js +111 -0
  89. package/output/core/test-support/environment.d.ts +38 -0
  90. package/output/core/test-support/environment.d.ts.map +1 -0
  91. package/output/core/test-support/environment.js +45 -0
  92. package/output/core/test-support/service-context.d.ts +64 -0
  93. package/output/core/test-support/service-context.d.ts.map +1 -0
  94. package/output/core/test-support/service-context.js +63 -0
  95. package/output/core/test-support/service-context.test.d.ts +2 -0
  96. package/output/core/test-support/service-context.test.d.ts.map +1 -0
  97. package/output/core/test-support/service-context.test.js +55 -0
  98. package/output/core/test-support/test-server.d.ts +154 -0
  99. package/output/core/test-support/test-server.d.ts.map +1 -0
  100. package/output/core/test-support/test-server.js +114 -0
  101. package/output/core/test-support/test-server.test.d.ts +2 -0
  102. package/output/core/test-support/test-server.test.d.ts.map +1 -0
  103. package/output/core/test-support/test-server.test.js +296 -0
  104. package/output/core/test-support/testdata.d.ts +255 -0
  105. package/output/core/test-support/testdata.d.ts.map +1 -0
  106. package/output/core/test-support/testdata.js +271 -0
  107. package/output/core/test-support/testdata.test.d.ts +2 -0
  108. package/output/core/test-support/testdata.test.d.ts.map +1 -0
  109. package/output/core/test-support/testdata.test.js +334 -0
  110. package/output/core/type-augment.d.ts +34 -0
  111. package/output/core/type-augment.d.ts.map +1 -0
  112. package/output/core/type-augment.js +7 -0
  113. package/output/core/types.d.ts +582 -0
  114. package/output/core/types.d.ts.map +1 -0
  115. package/output/core/types.js +2 -0
  116. package/output/cors/index.d.ts +3 -0
  117. package/output/cors/index.d.ts.map +1 -0
  118. package/output/cors/index.js +28 -0
  119. package/output/index.d.ts +10 -4
  120. package/output/index.d.ts.map +1 -1
  121. package/output/index.js +25 -5
  122. package/output/rate-limit/index.d.ts +23 -0
  123. package/output/rate-limit/index.d.ts.map +1 -0
  124. package/output/rate-limit/index.js +43 -0
  125. package/output/{rate-limiter/rate-limiter.d.ts → rate-limit/limiter.d.ts} +1 -1
  126. package/output/rate-limit/limiter.d.ts.map +1 -0
  127. package/output/{rate-limiter → rate-limit}/memory-store.d.ts +6 -0
  128. package/output/rate-limit/memory-store.d.ts.map +1 -0
  129. package/output/{rate-limiter → rate-limit}/memory-store.js +8 -0
  130. package/output/{rate-limiter → rate-limit}/redis-store.d.ts +18 -0
  131. package/output/rate-limit/redis-store.d.ts.map +1 -0
  132. package/output/{rate-limiter → rate-limit}/redis-store.js +35 -0
  133. package/output/rate-limit/redis-store.test.d.ts.map +1 -0
  134. package/output/{rate-limiter → rate-limit}/redis-store.test.js +2 -2
  135. package/output/rate-limit/types.d.ts +45 -0
  136. package/output/rate-limit/types.d.ts.map +1 -0
  137. package/output/{redis-cache/redis-cache.d.ts → redis/cache.d.ts} +1 -1
  138. package/output/redis/cache.d.ts.map +1 -0
  139. package/output/redis/cache.test.d.ts +2 -0
  140. package/output/redis/cache.test.d.ts.map +1 -0
  141. package/output/{redis-cache/redis-cache.test.js → redis/cache.test.js} +1 -1
  142. package/output/redis/index.d.ts +50 -0
  143. package/output/redis/index.d.ts.map +1 -0
  144. package/output/redis/index.js +71 -0
  145. package/output/redis/index.test.d.ts +2 -0
  146. package/output/{context → redis}/index.test.d.ts.map +1 -1
  147. package/output/redis/index.test.js +55 -0
  148. package/output/redis-queue/index.js +1 -1
  149. package/output/redis-queue/test-helpers.d.ts +1 -1
  150. package/output/redis-queue/test-helpers.js +1 -1
  151. package/output/redis-queue/types.d.ts +3 -12
  152. package/output/redis-queue/types.d.ts.map +1 -1
  153. package/package.json +11 -7
  154. package/output/app/assemble.d.ts +0 -16
  155. package/output/app/assemble.d.ts.map +0 -1
  156. package/output/app/assemble.js +0 -124
  157. package/output/app/database.d.ts +0 -36
  158. package/output/app/database.d.ts.map +0 -1
  159. package/output/app/database.js +0 -40
  160. package/output/app/database.test.d.ts.map +0 -1
  161. package/output/app/database.test.js +0 -66
  162. package/output/app/default-modules.d.ts +0 -23
  163. package/output/app/default-modules.d.ts.map +0 -1
  164. package/output/app/default-modules.js +0 -40
  165. package/output/app/define-server.d.ts +0 -12
  166. package/output/app/define-server.d.ts.map +0 -1
  167. package/output/app/define-server.js +0 -289
  168. package/output/app/define-server.test.d.ts +0 -2
  169. package/output/app/define-server.test.d.ts.map +0 -1
  170. package/output/app/define-server.test.js +0 -299
  171. package/output/app/env.d.ts +0 -24
  172. package/output/app/env.d.ts.map +0 -1
  173. package/output/app/env.js +0 -38
  174. package/output/app/env.test.js +0 -78
  175. package/output/app/module.d.ts +0 -88
  176. package/output/app/module.d.ts.map +0 -1
  177. package/output/app/module.js +0 -34
  178. package/output/app/shutdown.d.ts +0 -9
  179. package/output/app/shutdown.d.ts.map +0 -1
  180. package/output/app/shutdown.js +0 -15
  181. package/output/app/shutdown.test.d.ts +0 -2
  182. package/output/app/shutdown.test.d.ts.map +0 -1
  183. package/output/app/shutdown.test.js +0 -149
  184. package/output/app/signal.d.ts +0 -12
  185. package/output/app/signal.d.ts.map +0 -1
  186. package/output/app/steps.d.ts +0 -17
  187. package/output/app/steps.d.ts.map +0 -1
  188. package/output/app/steps.js +0 -41
  189. package/output/app/types.d.ts +0 -149
  190. package/output/app/types.d.ts.map +0 -1
  191. package/output/context/index.d.ts +0 -6
  192. package/output/context/index.d.ts.map +0 -1
  193. package/output/context/index.js +0 -14
  194. package/output/rate-limiter/index.d.ts +0 -5
  195. package/output/rate-limiter/index.d.ts.map +0 -1
  196. package/output/rate-limiter/index.js +0 -4
  197. package/output/rate-limiter/memory-store.d.ts.map +0 -1
  198. package/output/rate-limiter/rate-limiter.d.ts.map +0 -1
  199. package/output/rate-limiter/redis-store.d.ts.map +0 -1
  200. package/output/rate-limiter/redis-store.test.d.ts.map +0 -1
  201. package/output/rate-limiter/types.d.ts +0 -27
  202. package/output/rate-limiter/types.d.ts.map +0 -1
  203. package/output/redis-cache/index.d.ts +0 -4
  204. package/output/redis-cache/index.d.ts.map +0 -1
  205. package/output/redis-cache/index.js +0 -1
  206. package/output/redis-cache/redis-cache.d.ts.map +0 -1
  207. package/output/redis-cache/redis-cache.test.d.ts +0 -2
  208. package/output/redis-cache/redis-cache.test.d.ts.map +0 -1
  209. package/output/schema/index.d.ts.map +0 -1
  210. /package/output/{app → core}/env.test.d.ts +0 -0
  211. /package/output/{app → core/modules/auth-provider}/types.js +0 -0
  212. /package/output/{context → core/modules/context}/index.test.d.ts +0 -0
  213. /package/output/{context → core/modules/context}/index.test.js +0 -0
  214. /package/output/{app → core/test-support}/database.test.d.ts +0 -0
  215. /package/output/{rate-limiter/rate-limiter.js → rate-limit/limiter.js} +0 -0
  216. /package/output/{rate-limiter → rate-limit}/redis-store.test.d.ts +0 -0
  217. /package/output/{rate-limiter → rate-limit}/types.js +0 -0
  218. /package/output/{redis-cache/redis-cache.js → redis/cache.js} +0 -0
@@ -0,0 +1,138 @@
1
+ import { Disposer } from '@taicode/common-base';
2
+ import type { ConfigAccess } from './context.js';
3
+ import type { ResourceSetupUnit } from './lifecycle.js';
4
+ import type { ConfigEnv } from '../env.js';
5
+ import type { AppEnv, AppInstance, ClientsView, ErrorClassifier, LoggerLike, ResourcesView, Server } from '../types.js';
6
+ import type { FastifyInstance } from 'fastify';
7
+ /**
8
+ * 装配**步料**(不是装配流程)。
9
+ *
10
+ * `startServer` / `generateOpenApi`(`entries.ts`)各自写明自己的步序——
11
+ * 这里只放「一步」的实现,**不含任何 `phase` / `mode` / 跳过集**。哪个入口跑哪几步,
12
+ * 在入口函数里一眼可见,不需要在数据结构和分支之间来回对照。
13
+ */
14
+ /**
15
+ * 结算 `ctx.env`:**完全按内置逻辑加载**(dotenv 分层 + 进程注入已由 `loadEnvironment` /
16
+ * `prepareTestDatabase` 做完,结果在 `base` 与 `process.env` 里),这里只把两者合成最终视图。
17
+ *
18
+ * - `base`:框架固定约定值(`ENV` / `API_PORT` / `DATABASE_URL` / ...,已归一化);
19
+ * - 应用声明的扩展字段(`Env`,`defineServer` 的第一个泛型):运行期取**内置加载后的**
20
+ * `process.env[key]` 字符串直通——框架**不提供自定义加载**,也不对扩展字段做映射 / 校验 / 默认值。
21
+ *
22
+ * 返回「`process.env` 直通 + 固定约定覆盖」:固定键以归一化后的 `base` 为准(放最后覆盖),
23
+ * 其余键保持内置加载后的原始字符串。类型上窄化为 `AppEnv<Env>`(只暴露固定键 + 声明的扩展键)。
24
+ *
25
+ * `Env` 由调用方(`startServer` / `createTestServer`)显式传入其自身的 `Env` 泛型。
26
+ */
27
+ export declare function resolveAppEnv<Env>(base: ConfigEnv): AppEnv<Env>;
28
+ /** 每个阶段一个 `Disposer`(创建与释放同处一地;关闭时按阶段逆序 flush) */
29
+ export interface StageDisposers {
30
+ resources: Disposer;
31
+ config: Disposer;
32
+ clients: Disposer;
33
+ services: Disposer;
34
+ middlewares: Disposer;
35
+ routers: Disposer;
36
+ }
37
+ /** 一次装配的**运行期上下文**(步料之间传递的、不放公共类型里的东西) */
38
+ export interface BootContext<Resources, Config, Clients, Services, Env> {
39
+ server: Server<Resources, Config, Clients, Services, Env>;
40
+ env: AppEnv<Env>;
41
+ /** 原生 Fastify 实例(对外返回的就是它) */
42
+ app: FastifyInstance;
43
+ /** 同一实例的框架视图(`request.ctx` 等类型增强的载体) */
44
+ instance: AppInstance;
45
+ log: LoggerLike;
46
+ /** 身份块的 `service`(日志标签用) */
47
+ service: string;
48
+ /** `config` 槽位的**读取能力**(出 spec 路径未产出 → `read()` 抛错) */
49
+ configAccess: ConfigAccess<Config>;
50
+ disposers: StageDisposers;
51
+ }
52
+ /** 建 app + 六个阶段 `Disposer`(两个入口的**第一步**,内容完全相同) */
53
+ export declare function createBootContext<Resources, Config, Clients, Services, Env>(server: Server<Resources, Config, Clients, Services, Env>, env: AppEnv<Env>): BootContext<Resources, Config, Clients, Services, Env>;
54
+ /**
55
+ * 出文档用的上下文:**同一个 `BootContext` 形状,但实例是最小 Fastify**(无 logger /
56
+ * 无 requestId / 无 trustProxy,见 `createDocumentFastify`)。
57
+ *
58
+ * 形状不变是刻意的——`buildResources` / `registerRouters` 这些单步实现
59
+ * 因此**不需要知道自己在哪个入口跑**,轻量只体现在「实例怎么造」和「跑哪几步」上。
60
+ */
61
+ export declare function createDocumentContext<Resources, Config, Clients, Services, Env>(server: Server<Resources, Config, Clients, Services, Env>, env: AppEnv<Env>): BootContext<Resources, Config, Clients, Services, Env>;
62
+ /**
63
+ * 取**已加载**配置的访问器(框架内置单元消费配置的唯一入口)。
64
+ *
65
+ * 出 spec 路径下 `config` 槽位未产出,读到即抛 `CONFIG_UNAVAILABLE`——
66
+ * 「构造期不得读配置」因此有运行期护栏,而不只是文档约定。
67
+ */
68
+ export declare function createConfigReader<Config>(configAccess: ConfigAccess<Config>): (key: string) => unknown;
69
+ export interface ResourceBuildResult<Resources> {
70
+ /** 剔除了「编译期静态关闭」(值为 `false`)的键,并完成单元解包 */
71
+ resources: ResourcesView<Resources>;
72
+ /** 待执行的资源 `setup`(按声明序;空数组 = 无需准备) */
73
+ units: ResourceSetupUnit[];
74
+ /** 各资源注册的分类器合成结果(零个 = `undefined`) */
75
+ classifyResourceError: ErrorClassifier | undefined;
76
+ }
77
+ /**
78
+ * `resources(ctx)`:**只构造、不建连**(仅运行期入口执行)。
79
+ *
80
+ * 只看两件事:调用工厂、拆解单元(视图 + 待执行的 `setup`)。**不执行** `setup` —— 那是下一个
81
+ * 单步 `setupResources`,两个入口各自显式写明(与整个 `steps.ts` 【步料不是流程】的定位一致)。
82
+ */
83
+ export declare function buildResources<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>): Promise<ResourceBuildResult<Resources>>;
84
+ /**
85
+ * 资源单元 `setup` 步:建连验证 / 同步库结构 / 预热,**在 `config` 之前**。
86
+ *
87
+ * 为什么必须是独立一步而不是 `buildResources` 内部:它是一道**有失败可能的装配动作**,
88
+ * 入口需要能把它跟「构造」分开看(失败时已构造的资源照样要释放)。
89
+ */
90
+ export declare function setupResources<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>, units: readonly ResourceSetupUnit[]): Promise<void>;
91
+ /**
92
+ * core 装配:`identity` → `schema`(+ 路由契约守卫)→ `requestContext` → `cookie` →
93
+ * `response` → `openapi` → `requestId` 回吐。
94
+ *
95
+ * 仅运行期入口执行——它装的是「请求怎么被处理」,构建期没有请求。
96
+ */
97
+ export declare function registerCoreStep<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>, classifyResourceError?: ErrorClassifier): Promise<void>;
98
+ /**
99
+ * **文档 core** 装配:`identity` → `schema`(+ 路由契约守卫)→ `openapi`。
100
+ *
101
+ * 只装「把 zod schema 转成 OpenAPI 文档」所需的四件事。`requestContext` / `cookie` /
102
+ * `response` / `requestId` 不装:它们影响的是请求怎么被处理,而构建期一条请求都不会发。
103
+ * 守卫仍必须在——文档就是权限目录(§10-26)。
104
+ */
105
+ export declare function registerDocumentCoreStep<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>): Promise<void>;
106
+ /** `config(ctx)`:**仅运行期**(出 spec 路径不跑) */
107
+ export declare function buildConfig<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>, resources: ResourcesView<Resources>): Promise<void>;
108
+ /**
109
+ * `clients(ctx)`:**仅运行期**(需要 `config`,出 spec 路径不跑)。
110
+ *
111
+ * 外部客户端的地址 / 凭证来自业务配置,所以它必须排在 `config` 之后;
112
+ * 又因为多个 service 要共享同一份实例(而 `services` 之间禁止互依),所以它必须排在 `services` 之前。
113
+ *
114
+ * **槽位可选**:应用省略 `clients` 时,这里按「空客户端包」处理(`Clients` 退化为空)。
115
+ */
116
+ export declare function buildClients<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>, resources: ResourcesView<Resources>): Promise<ClientsView<Clients>>;
117
+ /** `services(ctx)`:**只构造**(`init()` 由调用方决定跑不跑) */
118
+ export declare function buildServices<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>, resources: ResourcesView<Resources>, clients: ClientsView<Clients>): Promise<Awaited<Services>>;
119
+ /** `middlewares(ctx)`:按**数组顺序**逐个 `register(app)`(两个入口都执行) */
120
+ export declare function registerMiddlewares<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>, services: Awaited<Services>, clients: ClientsView<Clients>): Promise<void>;
121
+ /** 框架内置系统路由(`/` / `/health` / `/system/health` / `/system/openapi` / `/system/info`) */
122
+ export declare function registerSystemRoutesStep<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>, services: Awaited<Services>): void;
123
+ /** `routers(ctx)`:业务路由(文档与权限目录都从这里来) */
124
+ export declare function registerRouters<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>, services: Awaited<Services>, clients: ClientsView<Clients>): Promise<void>;
125
+ /** 系统路由(出文档路径:不构造 `services`) */
126
+ export declare function registerDocumentSystemRoutesStep<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>): void;
127
+ /** 业务路由(出文档路径:不构造 `services` / `clients`;这是文档内容的**唯一**业务来源) */
128
+ export declare function registerDocumentRoutersStep<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>): Promise<void>;
129
+ /**
130
+ * 关闭流程(固定八步,顺序写在 `shutdown.ts` 里,不可覆盖)。
131
+ *
132
+ * 由 `onClose` 钩子触发;spec 入口在 `finally` 里 `close()` 同样会走它——
133
+ * 「构造了什么就释放什么」,构建脚本不再留下未释放的资源。
134
+ */
135
+ export declare function buildShutdownFlow<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>, services: Awaited<Services>, resourceUnits?: readonly ResourceSetupUnit[]): () => Promise<void>;
136
+ /** 把关闭流程挂到 `onClose`(两个入口都挂:谁 `close()` 谁负责释放) */
137
+ export declare function installShutdownHook<Resources, Config, Clients, Services, Env>(ctx: BootContext<Resources, Config, Clients, Services, Env>, services: Awaited<Services>, resourceUnits?: readonly ResourceSetupUnit[]): void;
138
+ //# sourceMappingURL=steps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"steps.d.ts","sourceRoot":"","sources":["../../../source/core/boot/steps.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,259 @@
1
+ import { Disposer } from '@taicode/common-base';
2
+ import { registerCore, registerDocumentCore, createDocumentFastify, createFastify } from '../assemble.js';
3
+ import { registerSystemRoutes } from '../modules/system-routes/index.js';
4
+ import { createClientsContext, createConfigAccess, createConfigContext, createMiddlewareContext, createResourcesContext, createRouterContext, createServiceContext, liveResources, provideConfig, readConfigKey, splitResourceUnits, } from './context.js';
5
+ import { CLIENTS_STAGE, CONFIG_STAGE, disposeResourceUnits, disposeServices, disposeStage, MIDDLEWARES_STAGE, middlewareUnitName, RESOURCES_STAGE, ROUTERS_STAGE, SERVICES_STAGE, setupResourceUnits, } from './lifecycle.js';
6
+ import { runShutdown } from './shutdown.js';
7
+ /**
8
+ * 装配**步料**(不是装配流程)。
9
+ *
10
+ * `startServer` / `generateOpenApi`(`entries.ts`)各自写明自己的步序——
11
+ * 这里只放「一步」的实现,**不含任何 `phase` / `mode` / 跳过集**。哪个入口跑哪几步,
12
+ * 在入口函数里一眼可见,不需要在数据结构和分支之间来回对照。
13
+ */
14
+ /**
15
+ * 结算 `ctx.env`:**完全按内置逻辑加载**(dotenv 分层 + 进程注入已由 `loadEnvironment` /
16
+ * `prepareTestDatabase` 做完,结果在 `base` 与 `process.env` 里),这里只把两者合成最终视图。
17
+ *
18
+ * - `base`:框架固定约定值(`ENV` / `API_PORT` / `DATABASE_URL` / ...,已归一化);
19
+ * - 应用声明的扩展字段(`Env`,`defineServer` 的第一个泛型):运行期取**内置加载后的**
20
+ * `process.env[key]` 字符串直通——框架**不提供自定义加载**,也不对扩展字段做映射 / 校验 / 默认值。
21
+ *
22
+ * 返回「`process.env` 直通 + 固定约定覆盖」:固定键以归一化后的 `base` 为准(放最后覆盖),
23
+ * 其余键保持内置加载后的原始字符串。类型上窄化为 `AppEnv<Env>`(只暴露固定键 + 声明的扩展键)。
24
+ *
25
+ * `Env` 由调用方(`startServer` / `createTestServer`)显式传入其自身的 `Env` 泛型。
26
+ */
27
+ export function resolveAppEnv(base) {
28
+ const passthrough = {};
29
+ for (const [key, value] of Object.entries(process.env)) {
30
+ if (typeof value === 'string') {
31
+ passthrough[key] = value;
32
+ }
33
+ }
34
+ return { ...passthrough, ...base };
35
+ }
36
+ /** 建 app + 六个阶段 `Disposer`(两个入口的**第一步**,内容完全相同) */
37
+ export function createBootContext(server, env) {
38
+ return buildContext(server, env, createFastify(env));
39
+ }
40
+ /**
41
+ * 出文档用的上下文:**同一个 `BootContext` 形状,但实例是最小 Fastify**(无 logger /
42
+ * 无 requestId / 无 trustProxy,见 `createDocumentFastify`)。
43
+ *
44
+ * 形状不变是刻意的——`buildResources` / `registerRouters` 这些单步实现
45
+ * 因此**不需要知道自己在哪个入口跑**,轻量只体现在「实例怎么造」和「跑哪几步」上。
46
+ */
47
+ export function createDocumentContext(server, env) {
48
+ return buildContext(server, env, createDocumentFastify());
49
+ }
50
+ function buildContext(server, env, app) {
51
+ const log = app.log;
52
+ return {
53
+ server,
54
+ env,
55
+ app,
56
+ instance: app,
57
+ log,
58
+ service: server.app.service,
59
+ configAccess: createConfigAccess(),
60
+ disposers: {
61
+ resources: new Disposer({ log, label: 'resources' }),
62
+ config: new Disposer({ log, label: 'config' }),
63
+ clients: new Disposer({ log, label: 'clients' }),
64
+ services: new Disposer({ log, label: 'services' }),
65
+ middlewares: new Disposer({ log, label: 'middlewares' }),
66
+ routers: new Disposer({ log, label: 'routers' }),
67
+ },
68
+ };
69
+ }
70
+ /**
71
+ * 取**已加载**配置的访问器(框架内置单元消费配置的唯一入口)。
72
+ *
73
+ * 出 spec 路径下 `config` 槽位未产出,读到即抛 `CONFIG_UNAVAILABLE`——
74
+ * 「构造期不得读配置」因此有运行期护栏,而不只是文档约定。
75
+ */
76
+ export function createConfigReader(configAccess) {
77
+ return (key) => readConfigKey(configAccess.read(), key);
78
+ }
79
+ /**
80
+ * 把各资源通过 `ctx.registerClassifyError` 注册的分类器合成单个分类器。
81
+ *
82
+ * 范式:**一个资源一个分类器,各管各的错误域**(认领不了就返回 `null` 交给下一个)。
83
+ * 零个 → `undefined`(响应层落 `UNKNOWN_ERROR` 兜底);一个 → 原样返回;多个 → 按注册
84
+ * 顺序串成链,返回第一个非 `null`(短路)。
85
+ */
86
+ function combineClassifiers(classifiers) {
87
+ if (classifiers.length === 0) {
88
+ return undefined;
89
+ }
90
+ if (classifiers.length === 1) {
91
+ return classifiers[0];
92
+ }
93
+ return (error) => {
94
+ for (const classifier of classifiers) {
95
+ const result = classifier(error);
96
+ if (result !== null) {
97
+ return result;
98
+ }
99
+ }
100
+ return null;
101
+ };
102
+ }
103
+ /**
104
+ * `resources(ctx)`:**只构造、不建连**(仅运行期入口执行)。
105
+ *
106
+ * 只看两件事:调用工厂、拆解单元(视图 + 待执行的 `setup`)。**不执行** `setup` —— 那是下一个
107
+ * 单步 `setupResources`,两个入口各自显式写明(与整个 `steps.ts` 【步料不是流程】的定位一致)。
108
+ */
109
+ export async function buildResources(ctx) {
110
+ const classifiers = [];
111
+ const built = (await ctx.server.resources(createResourcesContext({
112
+ env: ctx.env,
113
+ log: ctx.log,
114
+ disposer: ctx.disposers.resources,
115
+ registerClassifyError: (classifier) => {
116
+ classifiers.push(classifier);
117
+ },
118
+ })));
119
+ const { view, units } = splitResourceUnits(built);
120
+ return {
121
+ resources: view,
122
+ units,
123
+ classifyResourceError: combineClassifiers(classifiers),
124
+ };
125
+ }
126
+ /**
127
+ * 资源单元 `setup` 步:建连验证 / 同步库结构 / 预热,**在 `config` 之前**。
128
+ *
129
+ * 为什么必须是独立一步而不是 `buildResources` 内部:它是一道**有失败可能的装配动作**,
130
+ * 入口需要能把它跟「构造」分开看(失败时已构造的资源照样要释放)。
131
+ */
132
+ export async function setupResources(ctx, units) {
133
+ await setupResourceUnits({ service: ctx.service, log: ctx.log, units });
134
+ }
135
+ /**
136
+ * core 装配:`identity` → `schema`(+ 路由契约守卫)→ `requestContext` → `cookie` →
137
+ * `response` → `openapi` → `requestId` 回吐。
138
+ *
139
+ * 仅运行期入口执行——它装的是「请求怎么被处理」,构建期没有请求。
140
+ */
141
+ export async function registerCoreStep(ctx, classifyResourceError) {
142
+ await registerCore(ctx.app, {
143
+ identity: ctx.server.app,
144
+ env: ctx.env,
145
+ classifyResourceError,
146
+ readConfig: createConfigReader(ctx.configAccess),
147
+ });
148
+ }
149
+ /**
150
+ * **文档 core** 装配:`identity` → `schema`(+ 路由契约守卫)→ `openapi`。
151
+ *
152
+ * 只装「把 zod schema 转成 OpenAPI 文档」所需的四件事。`requestContext` / `cookie` /
153
+ * `response` / `requestId` 不装:它们影响的是请求怎么被处理,而构建期一条请求都不会发。
154
+ * 守卫仍必须在——文档就是权限目录(§10-26)。
155
+ */
156
+ export async function registerDocumentCoreStep(ctx) {
157
+ await registerDocumentCore(ctx.app, { identity: ctx.server.app });
158
+ }
159
+ /** `config(ctx)`:**仅运行期**(出 spec 路径不跑) */
160
+ export async function buildConfig(ctx, resources) {
161
+ const config = (await ctx.server.config(createConfigContext({ env: ctx.env, log: ctx.log, disposer: ctx.disposers.config, resources })));
162
+ provideConfig(ctx.configAccess, config);
163
+ }
164
+ /**
165
+ * `clients(ctx)`:**仅运行期**(需要 `config`,出 spec 路径不跑)。
166
+ *
167
+ * 外部客户端的地址 / 凭证来自业务配置,所以它必须排在 `config` 之后;
168
+ * 又因为多个 service 要共享同一份实例(而 `services` 之间禁止互依),所以它必须排在 `services` 之前。
169
+ *
170
+ * **槽位可选**:应用省略 `clients` 时,这里按「空客户端包」处理(`Clients` 退化为空)。
171
+ */
172
+ export async function buildClients(ctx, resources) {
173
+ if (ctx.server.clients === undefined) {
174
+ return {};
175
+ }
176
+ const built = (await ctx.server.clients(createClientsContext({ log: ctx.log, disposer: ctx.disposers.clients, resources, configAccess: ctx.configAccess })));
177
+ return liveResources(built);
178
+ }
179
+ /** `services(ctx)`:**只构造**(`init()` 由调用方决定跑不跑) */
180
+ export async function buildServices(ctx, resources, clients) {
181
+ return ctx.server.services(createServiceContext({ log: ctx.log, disposer: ctx.disposers.services, resources, configAccess: ctx.configAccess, clients }));
182
+ }
183
+ /** 归一化一个 `MiddlewareEntry`:裸函数补一个用于日志的占位名(`middleware[i]`) */
184
+ function normalizeMiddleware(entry, index) {
185
+ if (typeof entry === 'function') {
186
+ return { name: `middleware[${index}]`, register: entry };
187
+ }
188
+ return { name: entry.name, register: entry.register };
189
+ }
190
+ /** `middlewares(ctx)`:按**数组顺序**逐个 `register(app)`(两个入口都执行) */
191
+ export async function registerMiddlewares(ctx, services, clients) {
192
+ const middlewares = ctx.server.middlewares(createMiddlewareContext({
193
+ app: ctx.instance,
194
+ log: ctx.log,
195
+ disposer: ctx.disposers.middlewares,
196
+ services,
197
+ configAccess: ctx.configAccess,
198
+ clients,
199
+ }));
200
+ for (const [index, entry] of middlewares.entries()) {
201
+ const { name, register } = normalizeMiddleware(entry, index);
202
+ await register(ctx.instance);
203
+ ctx.log.debug({ service: ctx.service, unit: middlewareUnitName(ctx.service, name), stage: MIDDLEWARES_STAGE }, '[middleware] registered');
204
+ }
205
+ }
206
+ /** 框架内置系统路由(`/` / `/health` / `/system/health` / `/system/openapi` / `/system/info`) */
207
+ export function registerSystemRoutesStep(ctx, services) {
208
+ registerSystemRoutes(ctx.instance, {
209
+ identity: ctx.server.app,
210
+ services: services,
211
+ readConfig: createConfigReader(ctx.configAccess),
212
+ });
213
+ }
214
+ /** `routers(ctx)`:业务路由(文档与权限目录都从这里来) */
215
+ export async function registerRouters(ctx, services, clients) {
216
+ await ctx.server.routers(createRouterContext({ app: ctx.instance, log: ctx.log, disposer: ctx.disposers.routers, services, clients }));
217
+ }
218
+ /**
219
+ * 出文档路径传给路由 / 中间件槽位的 `services` / `clients` 占位。
220
+ *
221
+ * 文档**只由路由声明决定**,所以出 spec 既不构造、也不 `init` 业务单元。这依赖一条契约:
222
+ * `routers` / 系统路由在**注册期不得读取 `services` / `clients`**,只能捕获进 handler、在
223
+ * **请求期**使用——构建期一条请求都不会发,因此这些空对象永远不会被读到(与规则 B「构造期
224
+ * 不得查库 / 建连 / 发请求」同类)。
225
+ */
226
+ const DOCUMENT_UNITS = Object.freeze({});
227
+ /** 系统路由(出文档路径:不构造 `services`) */
228
+ export function registerDocumentSystemRoutesStep(ctx) {
229
+ registerSystemRoutesStep(ctx, DOCUMENT_UNITS);
230
+ }
231
+ /** 业务路由(出文档路径:不构造 `services` / `clients`;这是文档内容的**唯一**业务来源) */
232
+ export async function registerDocumentRoutersStep(ctx) {
233
+ await registerRouters(ctx, DOCUMENT_UNITS, DOCUMENT_UNITS);
234
+ }
235
+ /**
236
+ * 关闭流程(固定八步,顺序写在 `shutdown.ts` 里,不可覆盖)。
237
+ *
238
+ * 由 `onClose` 钩子触发;spec 入口在 `finally` 里 `close()` 同样会走它——
239
+ * 「构造了什么就释放什么」,构建脚本不再留下未释放的资源。
240
+ */
241
+ export function buildShutdownFlow(ctx, services, resourceUnits = []) {
242
+ return async () => {
243
+ await runShutdown({
244
+ flushRouters: () => disposeStage({ service: ctx.service, log: ctx.log, stage: ROUTERS_STAGE, disposer: ctx.disposers.routers }),
245
+ flushMiddlewares: () => disposeStage({ service: ctx.service, log: ctx.log, stage: MIDDLEWARES_STAGE, disposer: ctx.disposers.middlewares }),
246
+ flushServices: () => disposeStage({ service: ctx.service, log: ctx.log, stage: SERVICES_STAGE, disposer: ctx.disposers.services }),
247
+ disposeServices: () => disposeServices({ service: ctx.service, log: ctx.log, services: services }),
248
+ flushClients: () => disposeStage({ service: ctx.service, log: ctx.log, stage: CLIENTS_STAGE, disposer: ctx.disposers.clients }),
249
+ flushConfig: () => disposeStage({ service: ctx.service, log: ctx.log, stage: CONFIG_STAGE, disposer: ctx.disposers.config }),
250
+ disposeResources: () => disposeResourceUnits({ service: ctx.service, log: ctx.log, units: resourceUnits }),
251
+ flushResources: () => disposeStage({ service: ctx.service, log: ctx.log, stage: RESOURCES_STAGE, disposer: ctx.disposers.resources }),
252
+ }, ctx.log);
253
+ };
254
+ }
255
+ /** 把关闭流程挂到 `onClose`(两个入口都挂:谁 `close()` 谁负责释放) */
256
+ export function installShutdownHook(ctx, services, resourceUnits = []) {
257
+ const flow = buildShutdownFlow(ctx, services, resourceUnits);
258
+ ctx.app.addHook('onClose', () => flow());
259
+ }
@@ -0,0 +1,151 @@
1
+ import type { OpenApiDocument, Server, ServerConfig, ServicesGate } from './types.js';
2
+ import type { FastifyInstance } from 'fastify';
3
+ /**
4
+ * `defineServer()`(N1 §1)——**纯定义**:产出一个 `Server` 对象,不建连、不起服务。
5
+ *
6
+ * 启动是另一个函数(`startServer`)的事——同一份定义可同时喂给「启动」与「出 spec」
7
+ * (`generateOpenApi`)。
8
+ *
9
+ * ## 怎么用
10
+ *
11
+ * 一个服务**只在 `source/server.ts` 里写一次**,然后在三个地方消费它:
12
+ *
13
+ * ```ts
14
+ * // source/server.ts
15
+ * export const server = defineServer({ app, resources, config, clients, services, middlewares, routers })
16
+ * ```
17
+ * ```ts
18
+ * await startServer(server) // source/index.ts —— 启动
19
+ * await generateOpenApi(server) // build/openapi-gen.ts —— 出 spec
20
+ * await createTestServer(server) // *.test.ts —— 测试
21
+ * ```
22
+ *
23
+ * ## 两条容易踩的规矩
24
+ *
25
+ * - **本函数不读 env、不建连、不读库**。放在模块顶层是安全的:`import { server }` 不会有副作用。
26
+ * (需要 env 的工具进程自己调 `loadRuntimeEnvironment()`。)
27
+ * - **`clients` 可省略**,其余槽位恒定:没有业务路由也要写 `routers: () => {}`。省略 `clients`
28
+ * → 空客户端包。其余槽位不留「可省略」分支,省掉一个就会让「这份定义到底声明了什么」需要靠猜。
29
+ *
30
+ * ## 扩展 env(`Env`):`Env` 是**第一个**泛型,类型声明,不影响加载
31
+ *
32
+ * `Env` 排在泛型首位,就是为了能**单独手写**:`defineServer<{ FOO?: string }>({ ... })` 把扩展字段
33
+ * 的类型并进 `ctx.env`(`resources` / `config` 的 `ctx.env` 上可读、有类型)。不写即 `Env = object`
34
+ * (无扩展,`ctx.env` 就是 `ConfigEnv`)。
35
+ *
36
+ * ⚠️ **TypeScript 限制(务必知道)**:只要**显式写了 `Env`**,其余四个泛型
37
+ * (`Resources` / `Config` / `Clients` / `Services`)就**不再从工厂推导、一律退化为 `object`**
38
+ * —— 这是 TS「类型参数全有或全无」的规则,不是本函数能绕过的。后果:`defineServer<MyEnv>({...})`
39
+ * 产出的 `server` 上,`services` 等类型会塌成 `object`,`testServer.services.xxx` 之类会丢类型。
40
+ *
41
+ * 因此两种写法各有适用:
42
+ * - **要完整推导**(典型业务服务,依赖 `ctx.services` / `testServer.services` 的类型):**不写泛型**,
43
+ * 把扩展 env 的类型写在**工厂参数标注**上——`resources: (ctx: ResourcesContext<{ FOO?: string }>) => ...`
44
+ * 或 point-free 传入参数已标注的命名工厂。这样 `Env` 与其余泛型**一起被推导**,不丢类型。
45
+ * - **只在意 `Env`、不依赖其余被推导类型**(简单服务 / 测试):直接 `defineServer<MyEnv>({...})`。
46
+ *
47
+ * ⚠️ **只声明类型,不参与加载**:env 的加载完全走框架内置逻辑(dotenv 分层 + 进程注入),框架
48
+ * **不提供自定义加载**;运行期扩展字段的值就是内置加载后的 `process.env[key]`(字符串直通)。
49
+ *
50
+ * ```ts
51
+ * // 写法一(推荐业务服务用):不写泛型,Env 从工厂参数标注推导,其余泛型一起推导
52
+ * function createResources(ctx: ResourcesContext<{ REDIS_KEY_PREFIX?: string }>) { ... }
53
+ * export const server = defineServer({ app, resources: createResources, config, services, middlewares, routers })
54
+ *
55
+ * // 写法二:显式手写 Env(首位)——简单;但其余泛型退化为 object
56
+ * export const server = defineServer<{ REDIS_KEY_PREFIX?: string }>({ app, resources, config, services, middlewares, routers })
57
+ * ```
58
+ */
59
+ export declare function defineServer<Env = object, Resources = object, Config = object, Clients = object, Services = object>(config: ServerConfig<Resources, Config, Clients, Services, Env> & ServicesGate<Services>): Server<Resources, Config, Clients, Services, Env>;
60
+ /**
61
+ * `startServer()`——真正跑起来:构造资源、加载配置、建客户端、建服务、装中间件、注册路由、listen、装信号。
62
+ *
63
+ * 步序**就写在本函数里**,不抽到共享的「按模式分支」的装配器:
64
+ * ```
65
+ * env → resources → core → config → clients → services → services.init() → middlewares
66
+ * → systemRoutes → routers → onClose → ready → testdata → listen/signal
67
+ * ```
68
+ *
69
+ * - 最早阶段**固定加载 env**(`.env.dev < .env < .env.local`;`ENV=test` 追加 `.env.test.local`)。
70
+ * - 返回**原生 `FastifyInstance`**:不包 `fastify-plugin`、不返回包装对象。
71
+ * - **一定 listen**:监听端口 + 装信号处理器是这个入口的定义,没有开关。不监听端口的是
72
+ * `createTestServer()`(`inject`),那是另一个入口——两个入口各自固定自己的行为,
73
+ * 而不是同一个入口按环境变量 / 选项改行为。
74
+ * - 硬性要求 `Server`(品牌)产物:传裸 `ServerConfig` 是编译错误。
75
+ *
76
+ * ## `development` / `test` 下会自动装载 `testdata/`
77
+ *
78
+ * 真启动(本地 `pnpm dev`)在 `ready()` 之后、**listen 之前**装载 `<cwd>/testdata` 下的脚本并整体
79
+ * `load()`(见 `test-support/testdata.ts`):端口一旦对外,数据必须已经在位。脚本拿到的是装配好的
80
+ * ctx(`env` + `resources` + `log`),所以它们写数据用的就是被测服务读数据的那份连接。
81
+ * `ENV` 不在 `CLEANABLE_ENVS` 里(即生产)时**整段跳过**——这是 `startServer` 的唯一一处环境
82
+ * 分支,且是安静的:生产不需要测试数据,也没有什么东西需要失败。
83
+ *
84
+ * `cleanup()` 在 `startServer` 路径上不会被调用:真启动要的就是「数据在」,不是「数据不在」。
85
+ *
86
+ * ## 怎么用
87
+ *
88
+ * ```ts
89
+ * // source/index.ts —— 整个进程的启动点,只做这一件事
90
+ * import { startServer } from '@taicode/common-server/app'
91
+ * import { server } from './server'
92
+ *
93
+ * const app = await startServer(server)
94
+ * ```
95
+ *
96
+ * 你**不需要**写:加载 `.env`、创建 Fastify、注册响应信封 / cookie / OpenAPI / 系统路由、
97
+ * 装关闭钩子、装 `SIGTERM` / `SIGINT`。这些都由本函数固定完成,顺序写在下面的步序里。
98
+ *
99
+ * 你**需要**知道的两件事:
100
+ * - 端口只读 env 的 `API_PORT`,**不回退 `PORT`**(`PORT` 属 nginx)。`API_PORT=0` 让内核
101
+ * 分配空闲端口,多实例本地调试靠它。
102
+ * - 失败时已构造的资源会被按固定顺序释放(关闭流程八步),不会漏掉连接池;错误原样抛给
103
+ * 调用方——所以端口占用会让 `startServer` 直接 reject,需要在入口处接住。
104
+ */
105
+ export declare function startServer<Resources, Config, Clients, Services, Env>(server: Server<Resources, Config, Clients, Services, Env>): Promise<FastifyInstance>;
106
+ /**
107
+ * `generateOpenApi()`——只出 spec。
108
+ *
109
+ * **文档只由「路由声明 + zod → JSON Schema 变换」决定**,因此这里只跑那两件事:
110
+ * ```
111
+ * 最小 Fastify(无 logger / 无 requestId)
112
+ * → schema 契约(zod compiler + 路由守卫)+ swagger
113
+ * → 5 条系统路由 → routers
114
+ * → ready → swagger() → close()
115
+ * ```
116
+ *
117
+ * **不构造 `resources` / `config` / `clients` / `services`**:它们存在的意义是「让服务跑起来」
118
+ * (连接池、配置、外部客户端、业务单元及其 `init()`),而构建期不跑服务。
119
+ *
120
+ * **文档来源只有两处**:`routers` 与系统路由。其他槽位结构上就拿不到 `app`
121
+ * (`resources` / `config` / `clients` / `services` 的 ctx 里**没有 `app`**),因此根本无法
122
+ * 注册路由;于是也不需要执行它们去「发现」什么——那本来就不可能发现出接口。
123
+ *
124
+ * 代价是一条必须遵守的契约:`routers` / 系统路由在**注册期不得读取 `services` / `clients`**
125
+ * (只能捕获进 handler、请求期使用)。这与既有规则 B 同属一类——构造期本来就不允许
126
+ * 查库 / 建连 / 发请求。
127
+ *
128
+ * **红线**:全程**不得要求配置有效** —— 不跑 `config` / `clients` 槽位、不要求 `DATABASE_URL` /
129
+ * `REDIS_URL` 有效;`ctx.config` / `ctx.clients` 读到即抛 `CONFIG_UNAVAILABLE`。
130
+ *
131
+ * **没有关闭流程**:`init()` 与建连都没发生,无物可 flush;`close()` 只为释放 Fastify
132
+ * 自身句柄。
133
+ *
134
+ * ## 怎么用
135
+ *
136
+ * ```ts
137
+ * // build/openapi-gen.ts
138
+ * import { writeFile } from 'node:fs/promises'
139
+ * import { generateOpenApi } from '@taicode/common-server/app'
140
+ * import { server } from '../source/server'
141
+ *
142
+ * const document = await generateOpenApi(server)
143
+ * await writeFile('openapi.json', JSON.stringify(document, null, 2))
144
+ * ```
145
+ *
146
+ * 这一段在**构建期**跑,因此必须能离线成功:不需要数据库、不需要 Redis、不需要任何 env。
147
+ * 如果你的 `routers` 里写了 `services.xxx`(而不是只把它捕获进 handler),这条路会抛
148
+ * `CONFIG_UNAVAILABLE`——那不是 bug,是在提醒「路由注册期不该依赖运行期产物」。
149
+ */
150
+ export declare function generateOpenApi<Resources, Config, Clients, Services, Env>(server: Server<Resources, Config, Clients, Services, Env>): Promise<OpenApiDocument>;
151
+ //# sourceMappingURL=entries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../source/core/entries.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAU,eAAe,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE7F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAc9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,wBAAgB,YAAY,CAAC,GAAG,GAAG,MAAM,EAAE,SAAS,GAAG,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,GAAG,MAAM,EAAE,QAAQ,GAAG,MAAM,EACjH,MAAM,EAAE,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,YAAY,CAAC,QAAQ,CAAC,GACvF,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,CAMnD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAsB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,CA8DhK;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,CAsBpK"}