@taicode/common-server 3.7.13 → 3.8.0

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 (46) hide show
  1. package/output/app/assemble.d.ts +16 -0
  2. package/output/app/assemble.d.ts.map +1 -0
  3. package/output/app/assemble.js +124 -0
  4. package/output/app/database.d.ts +36 -0
  5. package/output/app/database.d.ts.map +1 -0
  6. package/output/app/database.js +40 -0
  7. package/output/app/database.test.d.ts +2 -0
  8. package/output/app/database.test.d.ts.map +1 -0
  9. package/output/app/database.test.js +66 -0
  10. package/output/app/default-modules.d.ts +23 -0
  11. package/output/app/default-modules.d.ts.map +1 -0
  12. package/output/app/default-modules.js +40 -0
  13. package/output/app/define-server.d.ts +12 -0
  14. package/output/app/define-server.d.ts.map +1 -0
  15. package/output/app/define-server.js +289 -0
  16. package/output/app/define-server.test.d.ts +2 -0
  17. package/output/app/define-server.test.d.ts.map +1 -0
  18. package/output/app/define-server.test.js +299 -0
  19. package/output/app/env.d.ts +24 -0
  20. package/output/app/env.d.ts.map +1 -0
  21. package/output/app/env.js +38 -0
  22. package/output/app/env.test.d.ts +2 -0
  23. package/output/app/env.test.d.ts.map +1 -0
  24. package/output/app/env.test.js +78 -0
  25. package/output/app/index.d.ts +16 -0
  26. package/output/app/index.d.ts.map +1 -0
  27. package/output/app/index.js +9 -0
  28. package/output/app/module.d.ts +88 -0
  29. package/output/app/module.d.ts.map +1 -0
  30. package/output/app/module.js +34 -0
  31. package/output/app/shutdown.d.ts +9 -0
  32. package/output/app/shutdown.d.ts.map +1 -0
  33. package/output/app/shutdown.js +15 -0
  34. package/output/app/shutdown.test.d.ts +2 -0
  35. package/output/app/shutdown.test.d.ts.map +1 -0
  36. package/output/app/shutdown.test.js +149 -0
  37. package/output/app/signal.d.ts +12 -0
  38. package/output/app/signal.d.ts.map +1 -0
  39. package/output/app/signal.js +45 -0
  40. package/output/app/steps.d.ts +17 -0
  41. package/output/app/steps.d.ts.map +1 -0
  42. package/output/app/steps.js +41 -0
  43. package/output/app/types.d.ts +149 -0
  44. package/output/app/types.d.ts.map +1 -0
  45. package/output/app/types.js +1 -0
  46. package/package.json +6 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-server.test.d.ts","sourceRoot":"","sources":["../../source/app/define-server.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,299 @@
1
+ import { describe, expect, expectTypeOf, it } from 'vitest';
2
+ import { createPrismaDatabaseModule } from './database.js';
3
+ import { defineServer } from './define-server.js';
4
+ import { STEP_ORDER } from './steps.js';
5
+ const P1_OPT_OUT = {
6
+ context: false,
7
+ response: false,
8
+ cors: false,
9
+ schema: false,
10
+ 'rate-limit': false,
11
+ auth: false,
12
+ openapi: false,
13
+ database: false,
14
+ };
15
+ const DUMMY_ERRORS = {
16
+ UserError: class extends Error {
17
+ },
18
+ SystemError: class extends Error {
19
+ },
20
+ };
21
+ const TEST_CONFIG = { loadConfig: () => ({ debug: false, port: 0 }) };
22
+ const FAKE_PRISMA_NAMESPACE = {
23
+ PrismaClientKnownRequestError: class extends Error {
24
+ },
25
+ PrismaClientUnknownRequestError: class extends Error {
26
+ },
27
+ PrismaClientValidationError: class extends Error {
28
+ },
29
+ PrismaClientRustPanicError: class extends Error {
30
+ },
31
+ PrismaClientInitializationError: class extends Error {
32
+ },
33
+ };
34
+ class FakeClient {
35
+ args;
36
+ disconnected = false;
37
+ constructor(args = []) {
38
+ this.args = args;
39
+ }
40
+ async $disconnect() {
41
+ this.disconnected = true;
42
+ }
43
+ }
44
+ function createTestLog() {
45
+ const steps = [];
46
+ const errors = [];
47
+ const log = {
48
+ debug: (obj) => {
49
+ const step = obj.step;
50
+ if (typeof step === 'string') {
51
+ steps.push(step);
52
+ }
53
+ },
54
+ info: () => undefined,
55
+ warn: () => undefined,
56
+ error: (obj) => {
57
+ errors.push(obj);
58
+ },
59
+ };
60
+ return { log, steps, errors };
61
+ }
62
+ function without(list, drop) {
63
+ return list.filter((step) => !drop.includes(step));
64
+ }
65
+ function createBaseOptions(overrides = {}) {
66
+ return {
67
+ serviceName: 'test',
68
+ title: 'Test',
69
+ description: 'Test service',
70
+ errors: DUMMY_ERRORS,
71
+ config: TEST_CONFIG,
72
+ modules: { ...P1_OPT_OUT },
73
+ serverOptions: { port: 0, host: '127.0.0.1' },
74
+ signal: false,
75
+ createServices: () => ({ dispose: () => undefined }),
76
+ registerRouters: () => undefined,
77
+ ...overrides,
78
+ };
79
+ }
80
+ describe('defineServer 装配期校验', () => {
81
+ it('未知模块名报错(文案逐字对齐 design §3.2)', async () => {
82
+ const modules = { ...P1_OPT_OUT, foo: false };
83
+ await expect(defineServer(createBaseOptions({ modules }))).rejects.toThrow('Unknown module "foo". Allowed: env, database, redis, context, response, cors, schema, rate-limit, auth, openapi');
84
+ });
85
+ it('重复注册报错', async () => {
86
+ const modules = { ...P1_OPT_OUT, cors: { name: 'cors' }, redis: { name: 'cors' } };
87
+ await expect(defineServer(createBaseOptions({ modules }))).rejects.toThrow('Module "cors" registered twice');
88
+ });
89
+ it('键名不符报错', async () => {
90
+ const modules = { ...P1_OPT_OUT, redis: { name: 'mysql' } };
91
+ await expect(defineServer(createBaseOptions({ modules }))).rejects.toThrow('Module key "redis" != module.name "mysql"');
92
+ });
93
+ it('dependsOn 指向被跳过的模块报错', async () => {
94
+ const modules = {
95
+ ...P1_OPT_OUT,
96
+ database: false,
97
+ redis: { name: 'redis', dependsOn: ['database'] },
98
+ };
99
+ await expect(defineServer(createBaseOptions({ modules }))).rejects.toThrow('Module "redis" depends on "database" which is disabled');
100
+ });
101
+ it('P1 未实现模块未显式 false 报错', async () => {
102
+ const modules = {
103
+ context: false,
104
+ cors: false,
105
+ schema: false,
106
+ 'rate-limit': false,
107
+ auth: false,
108
+ openapi: false,
109
+ database: false,
110
+ };
111
+ await expect(defineServer(createBaseOptions({ modules }))).rejects.toThrow('Module "response" is not implemented until P3; pass false to opt out');
112
+ });
113
+ it('过渡期报错按真实阶段号:openapi = P2、auth = P4', async () => {
114
+ const modulesWithoutOpenapi = {
115
+ context: false,
116
+ response: false,
117
+ cors: false,
118
+ schema: false,
119
+ 'rate-limit': false,
120
+ auth: false,
121
+ database: false,
122
+ };
123
+ await expect(defineServer(createBaseOptions({ modules: modulesWithoutOpenapi }))).rejects.toThrow('Module "openapi" is not implemented until P2; pass false to opt out');
124
+ const modulesWithoutAuth = {
125
+ context: false,
126
+ response: false,
127
+ cors: false,
128
+ schema: false,
129
+ 'rate-limit': false,
130
+ openapi: false,
131
+ database: false,
132
+ };
133
+ await expect(defineServer(createBaseOptions({ modules: modulesWithoutAuth }))).rejects.toThrow('Module "auth" is not implemented until P4; pass false to opt out');
134
+ });
135
+ it('未显式提供 database 报错(提示需 createPrismaDatabaseModule({...}) 或 false)', async () => {
136
+ const modules = {
137
+ context: false,
138
+ response: false,
139
+ cors: false,
140
+ schema: false,
141
+ 'rate-limit': false,
142
+ auth: false,
143
+ openapi: false,
144
+ };
145
+ await expect(defineServer(createBaseOptions({ modules }))).rejects.toThrow('Module "database" requires createPrismaDatabaseModule({...}) or false');
146
+ });
147
+ it('装配期报错发生在 promise resolve 之前(不拖到 listen)', async () => {
148
+ const promise = defineServer(createBaseOptions({ modules: { ...P1_OPT_OUT, database: false, redis: { name: 'redis', dependsOn: ['database'] } } }));
149
+ await expect(promise).rejects.toThrow();
150
+ });
151
+ });
152
+ describe('defineServer 15 步骨架', () => {
153
+ it('production 模式执行完整 15 步且顺序固定', async () => {
154
+ const { log, steps } = createTestLog();
155
+ const app = await defineServer(createBaseOptions({ log, mode: 'production' }));
156
+ try {
157
+ expect(steps).toEqual([...STEP_ORDER]);
158
+ }
159
+ finally {
160
+ await app.close();
161
+ }
162
+ });
163
+ it('test 模式跳过第 10 / 14 步', async () => {
164
+ const { log, steps } = createTestLog();
165
+ const app = await defineServer(createBaseOptions({ log, mode: 'test' }));
166
+ try {
167
+ expect(steps).toEqual(without(STEP_ORDER, ['backgroundWorkers', 'afterReady']));
168
+ }
169
+ finally {
170
+ await app.close();
171
+ }
172
+ });
173
+ it('openapi-build 模式跳过 [5,6,9,10,14,15],但保留第 3 / 4 步(决策 10-3)', async () => {
174
+ const { log, steps } = createTestLog();
175
+ const app = await defineServer(createBaseOptions({ log, mode: 'openapi-build' }));
176
+ expect(steps).toEqual(without(STEP_ORDER, ['initGlobalConfig', 'initClients', 'initServices', 'backgroundWorkers', 'afterReady', 'listen']));
177
+ expect(steps).toContain('database.createClient');
178
+ expect(steps).toContain('createRedis');
179
+ await app.close();
180
+ });
181
+ });
182
+ describe('defineServer 跳过语义', () => {
183
+ it('test 模式下 backgroundWorkers / afterReady 确实未执行', async () => {
184
+ let workers = 0;
185
+ let ready = 0;
186
+ const app = await defineServer(createBaseOptions({
187
+ mode: 'test',
188
+ backgroundWorkers: async () => {
189
+ workers += 1;
190
+ },
191
+ afterReady: async () => {
192
+ ready += 1;
193
+ },
194
+ }));
195
+ await app.close();
196
+ expect(workers).toBe(0);
197
+ expect(ready).toBe(0);
198
+ });
199
+ it('openapi-build 模式下连库/连 Redis 与集合类初始化确实未执行', async () => {
200
+ const calls = { initGlobalConfig: 0, initClients: 0, initServices: 0, workers: 0, ready: 0, routers: 0 };
201
+ const app = await defineServer(createBaseOptions({
202
+ mode: 'openapi-build',
203
+ initGlobalConfig: () => {
204
+ calls.initGlobalConfig += 1;
205
+ },
206
+ initClients: async () => {
207
+ calls.initClients += 1;
208
+ },
209
+ initServices: async () => {
210
+ calls.initServices += 1;
211
+ },
212
+ backgroundWorkers: async () => {
213
+ calls.workers += 1;
214
+ },
215
+ afterReady: async () => {
216
+ calls.ready += 1;
217
+ },
218
+ registerRouters: () => {
219
+ calls.routers += 1;
220
+ },
221
+ }));
222
+ await app.close();
223
+ expect(calls).toEqual({ initGlobalConfig: 0, initClients: 0, initServices: 0, workers: 0, ready: 0, routers: 1 });
224
+ });
225
+ it('production 模式下后台 worker / afterReady 都会执行', async () => {
226
+ let workers = 0;
227
+ let ready = 0;
228
+ const app = await defineServer(createBaseOptions({
229
+ mode: 'production',
230
+ backgroundWorkers: async () => {
231
+ workers += 1;
232
+ },
233
+ afterReady: async () => {
234
+ ready += 1;
235
+ },
236
+ }));
237
+ await app.close();
238
+ expect(workers).toBe(1);
239
+ expect(ready).toBe(1);
240
+ });
241
+ it('database: false 时第 3 步空转,createServices 收到 undefined db', async () => {
242
+ let received = 'sentinel';
243
+ const app = await defineServer(createBaseOptions({
244
+ modules: { ...P1_OPT_OUT, database: false },
245
+ createServices: (ctx) => {
246
+ received = ctx.db;
247
+ return {};
248
+ },
249
+ }));
250
+ await app.close();
251
+ expect(received).toBeUndefined();
252
+ });
253
+ });
254
+ describe('defineServer 模块覆盖与 Db 推断', () => {
255
+ it('Db 泛型从 database 模块推断(决策 10-2,无需强转)', async () => {
256
+ const database = createPrismaDatabaseModule({
257
+ PrismaClient: FakeClient,
258
+ PrismaNamespace: FAKE_PRISMA_NAMESPACE,
259
+ });
260
+ const app = await defineServer({
261
+ serviceName: 'test',
262
+ title: 'Test',
263
+ description: 'Test service',
264
+ errors: DUMMY_ERRORS,
265
+ config: TEST_CONFIG,
266
+ modules: { ...P1_OPT_OUT, database },
267
+ serverOptions: { port: 0, host: '127.0.0.1' },
268
+ signal: false,
269
+ createServices: (ctx) => {
270
+ expectTypeOf(ctx.db).toEqualTypeOf();
271
+ return {};
272
+ },
273
+ registerRouters: () => undefined,
274
+ });
275
+ await app.close();
276
+ });
277
+ it('整体覆盖 database 模块时使用覆盖实现的 createClient', async () => {
278
+ let created = 0;
279
+ const database = {
280
+ name: 'database',
281
+ createClient: () => {
282
+ created += 1;
283
+ return { tag: 'custom' };
284
+ },
285
+ classifyError: () => null,
286
+ };
287
+ let received;
288
+ const app = await defineServer(createBaseOptions({
289
+ modules: { ...P1_OPT_OUT, database: database },
290
+ createServices: (ctx) => {
291
+ received = ctx.db;
292
+ return {};
293
+ },
294
+ }));
295
+ await app.close();
296
+ expect(created).toBe(1);
297
+ expect(received).toEqual({ tag: 'custom' });
298
+ });
299
+ });
@@ -0,0 +1,24 @@
1
+ /**
2
+ * 默认 env 文件分层(低 → 高优先级,后者覆盖前者):
3
+ *
4
+ * .env.dev 已提交进仓库的 dev 默认值(克隆后 `pnpm run dev` 开箱即用)
5
+ * .env 本机私有覆盖(已被 gitignore)
6
+ * .env.local 本机更高优先级覆盖(已被 gitignore)
7
+ *
8
+ * 真实环境变量(进程/容器注入)优先级最高,任何 .env 文件都不会覆盖它们 —— 这条保证让 CI、
9
+ * helm、`docker run -e` 等显式注入始终生效,也避免测试探针
10
+ * (`check-test-database-isolation`)注入的连接串被仓库里的默认值顶掉。
11
+ *
12
+ * 解析器与 account / expense / notice / release 四家现状保持一致,统一使用 `dotenv`,
13
+ * 以保证多行值、引号/转义、`export` 前缀等细节语义完全等价(design §6:与现状逐字一致)。
14
+ */
15
+ export declare const ENV_FILE_LAYERS: readonly [".env.dev", ".env", ".env.local"];
16
+ /**
17
+ * 按分层顺序加载 env 文件(design 决策 #5:独立导出,不塞进 `defineServer`)。
18
+ *
19
+ * 必须在任何 `config` 读取之前调用;测试 / 脚本同样复用本函数。
20
+ *
21
+ * @param extraLayers 追加在最高优先级的额外层,例如测试配置传 `['.env.test.local']`
22
+ */
23
+ export declare function loadEnvFiles(extraLayers?: readonly string[]): void;
24
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../source/app/env.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,eAAe,6CAA8C,CAAA;AAU1E;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,WAAW,GAAE,SAAS,MAAM,EAAO,GAAG,IAAI,CAUtE"}
@@ -0,0 +1,38 @@
1
+ import { config } from 'dotenv';
2
+ /**
3
+ * 默认 env 文件分层(低 → 高优先级,后者覆盖前者):
4
+ *
5
+ * .env.dev 已提交进仓库的 dev 默认值(克隆后 `pnpm run dev` 开箱即用)
6
+ * .env 本机私有覆盖(已被 gitignore)
7
+ * .env.local 本机更高优先级覆盖(已被 gitignore)
8
+ *
9
+ * 真实环境变量(进程/容器注入)优先级最高,任何 .env 文件都不会覆盖它们 —— 这条保证让 CI、
10
+ * helm、`docker run -e` 等显式注入始终生效,也避免测试探针
11
+ * (`check-test-database-isolation`)注入的连接串被仓库里的默认值顶掉。
12
+ *
13
+ * 解析器与 account / expense / notice / release 四家现状保持一致,统一使用 `dotenv`,
14
+ * 以保证多行值、引号/转义、`export` 前缀等细节语义完全等价(design §6:与现状逐字一致)。
15
+ */
16
+ export const ENV_FILE_LAYERS = ['.env.dev', '.env', '.env.local'];
17
+ /**
18
+ * 首次 import 时的真实环境变量快照(此时还没有任何 .env 文件被加载)。
19
+ * 加载结束后原样恢复,实现「外部注入 > .env.local > .env > .env.dev」。
20
+ */
21
+ const EXTERNAL_ENV = new Map(Object.entries(process.env).filter((entry) => entry[1] !== undefined));
22
+ /**
23
+ * 按分层顺序加载 env 文件(design 决策 #5:独立导出,不塞进 `defineServer`)。
24
+ *
25
+ * 必须在任何 `config` 读取之前调用;测试 / 脚本同样复用本函数。
26
+ *
27
+ * @param extraLayers 追加在最高优先级的额外层,例如测试配置传 `['.env.test.local']`
28
+ */
29
+ export function loadEnvFiles(extraLayers = []) {
30
+ const layers = [...ENV_FILE_LAYERS, ...extraLayers];
31
+ layers.forEach((path, index) => {
32
+ // 第一层不覆盖已有值,后续层逐层覆盖前一层
33
+ config({ path, override: index > 0 });
34
+ });
35
+ for (const [key, value] of EXTERNAL_ENV) {
36
+ process.env[key] = value;
37
+ }
38
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=env.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.test.d.ts","sourceRoot":"","sources":["../../source/app/env.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,78 @@
1
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ const ENV_KEYS = [
6
+ 'LAYER',
7
+ 'OVERRIDE',
8
+ 'ONLY_DEV',
9
+ 'ONLY_ENV',
10
+ 'ONLY_LOCAL',
11
+ 'EXTERNAL_ONLY',
12
+ 'EXPORTED',
13
+ 'QUOTED',
14
+ 'MULTI',
15
+ 'HASHED',
16
+ ];
17
+ describe('loadEnvFiles', () => {
18
+ let previousCwd;
19
+ let directory;
20
+ beforeEach(() => {
21
+ previousCwd = process.cwd();
22
+ directory = mkdtempSync(join(tmpdir(), 'common-app-env-'));
23
+ writeFileSync(join(directory, '.env.dev'), 'LAYER=dev\nONLY_DEV=dev\nOVERRIDE=dev\n');
24
+ writeFileSync(join(directory, '.env'), 'LAYER=env\nONLY_ENV=env\nOVERRIDE=env\n');
25
+ writeFileSync(join(directory, '.env.local'), 'LAYER=local\nONLY_LOCAL=local\nOVERRIDE=local\n');
26
+ process.chdir(directory);
27
+ });
28
+ afterEach(() => {
29
+ process.chdir(previousCwd);
30
+ rmSync(directory, { recursive: true, force: true });
31
+ for (const key of ENV_KEYS) {
32
+ delete process.env[key];
33
+ }
34
+ vi.resetModules();
35
+ });
36
+ it('优先级:外部注入 > .env.local > .env > .env.dev', async () => {
37
+ process.env.LAYER = 'external';
38
+ process.env.EXTERNAL_ONLY = 'external';
39
+ vi.resetModules();
40
+ const { loadEnvFiles } = await import('./env.js');
41
+ loadEnvFiles();
42
+ expect(process.env.LAYER).toBe('external');
43
+ expect(process.env.EXTERNAL_ONLY).toBe('external');
44
+ expect(process.env.OVERRIDE).toBe('local');
45
+ expect(process.env.ONLY_DEV).toBe('dev');
46
+ expect(process.env.ONLY_ENV).toBe('env');
47
+ expect(process.env.ONLY_LOCAL).toBe('local');
48
+ });
49
+ it('追加层(如 .env.test.local)优先级最高', async () => {
50
+ writeFileSync(join(directory, '.env.test.local'), 'OVERRIDE=test-local\n');
51
+ vi.resetModules();
52
+ const { loadEnvFiles } = await import('./env.js');
53
+ loadEnvFiles(['.env.test.local']);
54
+ expect(process.env.OVERRIDE).toBe('test-local');
55
+ });
56
+ it('缺失文件不报错', async () => {
57
+ rmSync(join(directory, '.env.local'));
58
+ vi.resetModules();
59
+ const { loadEnvFiles } = await import('./env.js');
60
+ expect(() => loadEnvFiles()).not.toThrow();
61
+ expect(process.env.ONLY_DEV).toBe('dev');
62
+ });
63
+ it('dotenv 语义:export 前缀 / 引号 / 转义 / 多行 / 行尾注释(与四家现状逐字一致)', async () => {
64
+ writeFileSync(join(directory, '.env.local'), [
65
+ 'export EXPORTED=1',
66
+ 'QUOTED="a b"',
67
+ 'MULTI="line1\\nline2"',
68
+ 'HASHED=value # 行尾注释',
69
+ ].join('\n'));
70
+ vi.resetModules();
71
+ const { loadEnvFiles } = await import('./env.js');
72
+ loadEnvFiles();
73
+ expect(process.env.EXPORTED).toBe('1');
74
+ expect(process.env.QUOTED).toBe('a b');
75
+ expect(process.env.MULTI).toBe('line1\nline2');
76
+ expect(process.env.HASHED).toBe('value');
77
+ });
78
+ });
@@ -0,0 +1,16 @@
1
+ export { defineServer } from './define-server.js';
2
+ export { loadEnvFiles, ENV_FILE_LAYERS } from './env.js';
3
+ export { createPrismaDatabaseModule } from './database.js';
4
+ export { createEnvModule, createRedisModule, createDefaultModules } from './default-modules.js';
5
+ export { MODULE_NAMES, PENDING_MODULES } from './module.js';
6
+ export { STEP_ORDER, SKIP_BY_MODE } from './steps.js';
7
+ export { SHUTDOWN_ORDER } from './shutdown.js';
8
+ export { assembleModules } from './assemble.js';
9
+ export { attachSignalHandlers } from './signal.js';
10
+ export type { DefineServerOptions, ServerInstance, ServerMode, ErrorFactory, ErrorFactories, RuntimeConfig, ConfigModule, LoggerLike, RedisLike, RedisHandle, DisposeFunction, UnsubscribeFunction, GlobalConfigOptions, DbContext, ServicesContext, InitContext, WorkersContext, ReadyContext, ServerOptions, ResolveMessage, MapStatus, RuntimeState, StepContext, StepRunner, StepImplementations, ShutdownRunner, ShutdownImplementations, ServerHooks, } from './types.js';
11
+ export type { ModuleName, ModuleAssembly, ServerModule, DatabaseModule, DatabaseClientParams, PrismaErrorConstructors, PrismaOperationKey, PrismaOperationLabels, PendingModuleName, PendingModuleStage, } from './module.js';
12
+ export type { ResolvedModules } from './assemble.js';
13
+ export type { StepName } from './steps.js';
14
+ export type { ShutdownStepName } from './shutdown.js';
15
+ export type { PrismaDatabaseModuleParams } from './database.js';
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/app/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AACxD,OAAO,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAA;AAC1D,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC/F,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC3D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAA;AAElD,YAAY,EACV,mBAAmB,EACnB,cAAc,EACd,UAAU,EACV,YAAY,EACZ,cAAc,EACd,aAAa,EACb,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,SAAS,EACT,eAAe,EACf,WAAW,EACX,cAAc,EACd,YAAY,EACZ,aAAa,EACb,cAAc,EACd,SAAS,EACT,YAAY,EACZ,WAAW,EACX,UAAU,EACV,mBAAmB,EACnB,cAAc,EACd,uBAAuB,EACvB,WAAW,GACZ,MAAM,YAAY,CAAA;AACnB,YAAY,EACV,UAAU,EACV,cAAc,EACd,YAAY,EACZ,cAAc,EACd,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,aAAa,CAAA;AACpB,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACpD,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAC1C,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AACrD,YAAY,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAA"}
@@ -0,0 +1,9 @@
1
+ export { defineServer } from './define-server.js';
2
+ export { loadEnvFiles, ENV_FILE_LAYERS } from './env.js';
3
+ export { createPrismaDatabaseModule } from './database.js';
4
+ export { createEnvModule, createRedisModule, createDefaultModules } from './default-modules.js';
5
+ export { MODULE_NAMES, PENDING_MODULES } from './module.js';
6
+ export { STEP_ORDER, SKIP_BY_MODE } from './steps.js';
7
+ export { SHUTDOWN_ORDER } from './shutdown.js';
8
+ export { assembleModules } from './assemble.js';
9
+ export { attachSignalHandlers } from './signal.js';
@@ -0,0 +1,88 @@
1
+ import type { SystemError } from '@taicode/common-base';
2
+ import type { FastifyPluginAsync } from 'fastify';
3
+ import type { ConfigModule, ServerHooks, ShutdownImplementations, StepImplementations } from './types.js';
4
+ /**
5
+ * 封闭模块名集合(design §3.1 十项)。
6
+ *
7
+ * 数组顺序即「注册顺序」,也是装配期报错文案里 `Allowed: ...` 的顺序,**不得重排**。
8
+ */
9
+ export declare const MODULE_NAMES: readonly ["env", "database", "redis", "context", "response", "cors", "schema", "rate-limit", "auth", "openapi"];
10
+ export type ModuleName = (typeof MODULE_NAMES)[number];
11
+ /**
12
+ * P1 尚未提供默认实现的模块 → 各自的落地阶段(design §3.1 十项里除 `env` / `database` / `redis`
13
+ * 之外的七个)。阶段号取自 `proposals/P1-boot-shutdown.md`:`openapi` 在 P2、`auth` 在 P4、其余在 P3。
14
+ *
15
+ * P1 过渡规则(design §3.2 + 提案 §3.3):这些名字**必须由调用方显式写 `false`**(或直接提供
16
+ * 一个同名的整体覆盖实现),否则装配期按**真实阶段号**报错 —— 静默 no-op 会让调用方少注册插件
17
+ * 却启动成功。
18
+ */
19
+ export declare const PENDING_MODULES: {
20
+ readonly context: "P3";
21
+ readonly response: "P3";
22
+ readonly cors: "P3";
23
+ readonly schema: "P3";
24
+ readonly 'rate-limit': "P3";
25
+ readonly auth: "P4";
26
+ readonly openapi: "P2";
27
+ };
28
+ export type PendingModuleName = keyof typeof PENDING_MODULES;
29
+ export type PendingModuleStage = (typeof PENDING_MODULES)[PendingModuleName];
30
+ /** 模块契约:可贡献「初始化步骤实现 / Fastify 插件 / 生命周期 hook / 关闭逻辑」四类东西(design §3.1) */
31
+ export interface ServerModule {
32
+ /** 具名单元;装配期校验「键 == name」「不重复」「在封闭集合内」 */
33
+ name: ModuleName;
34
+ /** ① 初始化步骤实现:key 只能是 §4.1 的既定槽位,不得新增 / 重排步骤 */
35
+ steps?: Partial<StepImplementations>;
36
+ /** ② Fastify 插件:注册进第 7 步 `registerCorePlugins` */
37
+ plugins?: FastifyPluginAsync[];
38
+ /** ③ 生命周期 hook(见 design §4.2);模块提供的 hook 不再是顶层选项 */
39
+ hooks?: ServerHooks;
40
+ /** ④ 关闭逻辑:只能命中 §5 的 7 个既定槽位 */
41
+ shutdown?: Partial<ShutdownImplementations>;
42
+ /** 依赖的其它模块名;装配期做拓扑校验(指向被跳过的模块即报错) */
43
+ dependsOn?: ModuleName[];
44
+ }
45
+ /** 模块装配入参(design §3.2):未出现 = 默认实现;`false` = 跳过;对象 = 整体覆盖 */
46
+ export interface ModuleAssembly<Db = unknown> {
47
+ env?: false | ServerModule;
48
+ database?: false | DatabaseModule<Db>;
49
+ redis?: false | ServerModule;
50
+ context?: false | ServerModule;
51
+ response?: false | ServerModule;
52
+ cors?: false | ServerModule;
53
+ schema?: false | ServerModule;
54
+ 'rate-limit'?: false | ServerModule;
55
+ auth?: false | ServerModule;
56
+ openapi?: false | ServerModule;
57
+ }
58
+ /** `database` 模块步骤 3 的入参(design §3.3) */
59
+ export interface DatabaseClientParams {
60
+ config: ConfigModule;
61
+ postgresConfig: unknown;
62
+ }
63
+ /** 各仓生成的 Prisma 命名空间里、错误分类用到的 5 个错误类构造器(跨包 instanceof 不可靠,决策 #9) */
64
+ export interface PrismaErrorConstructors {
65
+ PrismaClientKnownRequestError: new (...args: never[]) => unknown;
66
+ PrismaClientUnknownRequestError: new (...args: never[]) => unknown;
67
+ PrismaClientValidationError: new (...args: never[]) => unknown;
68
+ PrismaClientInitializationError: new (...args: never[]) => unknown;
69
+ PrismaClientRustPanicError: new (...args: never[]) => unknown;
70
+ }
71
+ export type PrismaOperationKey = 'known' | 'unknown' | 'validation' | 'initialization' | 'rustPanic';
72
+ /** 可选覆盖的 operation 标签(默认对齐 design §2.3 的统一口径) */
73
+ export type PrismaOperationLabels = Partial<Record<PrismaOperationKey, string>>;
74
+ /**
75
+ * 数据层端口(design §3.3):任何实现只要满足它即可整体替换。
76
+ *
77
+ * 契约相同(同名、同贡献面)即可换实现——「支持非 Prisma 数据库」靠模块替换达成。
78
+ */
79
+ export interface DatabaseModule<Db = unknown> extends ServerModule {
80
+ name: 'database';
81
+ /** 步骤 3:建立连接。默认 Prisma 实现 = `new PrismaClient(...)` */
82
+ createClient(params: DatabaseClientParams): Db | Promise<Db>;
83
+ /** 错误分类:命中 → `SystemError('DATABASE_ERROR', …)`;未命中 → `null`(落回 UNKNOWN_ERROR 兜底) */
84
+ classifyError(error: unknown): SystemError | null;
85
+ /** §5-7:释放连接。默认 = `db.$disconnect()` */
86
+ dispose?(db: Db): Promise<void>;
87
+ }
88
+ //# sourceMappingURL=module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../source/app/module.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * 封闭模块名集合(design §3.1 十项)。
3
+ *
4
+ * 数组顺序即「注册顺序」,也是装配期报错文案里 `Allowed: ...` 的顺序,**不得重排**。
5
+ */
6
+ export const MODULE_NAMES = [
7
+ 'env',
8
+ 'database',
9
+ 'redis',
10
+ 'context',
11
+ 'response',
12
+ 'cors',
13
+ 'schema',
14
+ 'rate-limit',
15
+ 'auth',
16
+ 'openapi',
17
+ ];
18
+ /**
19
+ * P1 尚未提供默认实现的模块 → 各自的落地阶段(design §3.1 十项里除 `env` / `database` / `redis`
20
+ * 之外的七个)。阶段号取自 `proposals/P1-boot-shutdown.md`:`openapi` 在 P2、`auth` 在 P4、其余在 P3。
21
+ *
22
+ * P1 过渡规则(design §3.2 + 提案 §3.3):这些名字**必须由调用方显式写 `false`**(或直接提供
23
+ * 一个同名的整体覆盖实现),否则装配期按**真实阶段号**报错 —— 静默 no-op 会让调用方少注册插件
24
+ * 却启动成功。
25
+ */
26
+ export const PENDING_MODULES = {
27
+ context: 'P3',
28
+ response: 'P3',
29
+ cors: 'P3',
30
+ schema: 'P3',
31
+ 'rate-limit': 'P3',
32
+ auth: 'P4',
33
+ openapi: 'P2',
34
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 固定关闭 7 步(design §5)——**顺序写死在数组里**。
3
+ *
4
+ * 关键修正(相对现状):Redis 晚于 queue/cache 消费者释放;afterInit(如 LDAP)资源进第 1 步。
5
+ * 单步抛错记录后**继续**后续步骤,最后聚合上报(避免「config 报错导致 DB 没关」)。
6
+ */
7
+ export declare const SHUTDOWN_ORDER: readonly ["stopProducers", "unsubscribeConfig", "disposeConfig", "disposeServices", "disposeClients", "disposeRedis", "disposeDb"];
8
+ export type ShutdownStepName = (typeof SHUTDOWN_ORDER)[number];
9
+ //# sourceMappingURL=shutdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../../source/app/shutdown.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,cAAc,oIAQjB,CAAA;AAEV,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * 固定关闭 7 步(design §5)——**顺序写死在数组里**。
3
+ *
4
+ * 关键修正(相对现状):Redis 晚于 queue/cache 消费者释放;afterInit(如 LDAP)资源进第 1 步。
5
+ * 单步抛错记录后**继续**后续步骤,最后聚合上报(避免「config 报错导致 DB 没关」)。
6
+ */
7
+ export const SHUTDOWN_ORDER = [
8
+ 'stopProducers',
9
+ 'unsubscribeConfig',
10
+ 'disposeConfig',
11
+ 'disposeServices',
12
+ 'disposeClients',
13
+ 'disposeRedis',
14
+ 'disposeDb',
15
+ ];
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=shutdown.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shutdown.test.d.ts","sourceRoot":"","sources":["../../source/app/shutdown.test.ts"],"names":[],"mappings":""}