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

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 (145) hide show
  1. package/output/app/auth.test.d.ts +2 -0
  2. package/output/app/auth.test.d.ts.map +1 -0
  3. package/output/app/auth.test.js +148 -0
  4. package/output/app/boot.d.ts +39 -0
  5. package/output/app/boot.d.ts.map +1 -0
  6. package/output/app/boot.js +140 -0
  7. package/output/app/config.test.d.ts +2 -0
  8. package/output/app/config.test.d.ts.map +1 -0
  9. package/output/app/config.test.js +132 -0
  10. package/output/app/context.d.ts +70 -0
  11. package/output/app/context.d.ts.map +1 -0
  12. package/output/app/context.js +73 -0
  13. package/output/app/core/cookie.d.ts +25 -0
  14. package/output/app/core/cookie.d.ts.map +1 -0
  15. package/output/app/core/cookie.js +22 -0
  16. package/output/app/core/identity.d.ts +12 -0
  17. package/output/app/core/identity.d.ts.map +1 -0
  18. package/output/app/core/identity.js +14 -0
  19. package/output/app/core/index.d.ts +44 -0
  20. package/output/app/core/index.d.ts.map +1 -0
  21. package/output/app/core/index.js +57 -0
  22. package/output/app/core/logging.d.ts +13 -0
  23. package/output/app/core/logging.d.ts.map +1 -0
  24. package/output/app/core/logging.js +29 -0
  25. package/output/app/core/openapi.d.ts +17 -0
  26. package/output/app/core/openapi.d.ts.map +1 -0
  27. package/output/app/core/openapi.js +42 -0
  28. package/output/app/core/request-id.d.ts +36 -0
  29. package/output/app/core/request-id.d.ts.map +1 -0
  30. package/output/app/core/request-id.js +66 -0
  31. package/output/app/core/response.d.ts +41 -0
  32. package/output/app/core/response.d.ts.map +1 -0
  33. package/output/app/core/response.js +89 -0
  34. package/output/app/define-server.negative-cases.d.ts +40 -0
  35. package/output/app/define-server.negative-cases.d.ts.map +1 -0
  36. package/output/app/define-server.negative-cases.js +268 -0
  37. package/output/app/define-server.test.js +86 -288
  38. package/output/app/entries.d.ts +29 -0
  39. package/output/app/entries.d.ts.map +1 -0
  40. package/output/app/entries.js +64 -0
  41. package/output/app/env.d.ts +39 -4
  42. package/output/app/env.d.ts.map +1 -1
  43. package/output/app/env.js +32 -4
  44. package/output/app/env.test.js +67 -21
  45. package/output/app/error-contract.test.d.ts +2 -0
  46. package/output/app/error-contract.test.d.ts.map +1 -0
  47. package/output/app/error-contract.test.js +227 -0
  48. package/output/app/errors.d.ts +20 -0
  49. package/output/app/errors.d.ts.map +1 -0
  50. package/output/app/errors.js +51 -0
  51. package/output/app/generate-openapi.test.d.ts +2 -0
  52. package/output/app/generate-openapi.test.d.ts.map +1 -0
  53. package/output/app/generate-openapi.test.js +123 -0
  54. package/output/app/index.d.ts +15 -15
  55. package/output/app/index.d.ts.map +1 -1
  56. package/output/app/index.js +18 -9
  57. package/output/app/lifecycle-logging.test.d.ts +2 -0
  58. package/output/app/lifecycle-logging.test.d.ts.map +1 -0
  59. package/output/app/lifecycle-logging.test.js +155 -0
  60. package/output/app/lifecycle.d.ts +70 -0
  61. package/output/app/lifecycle.d.ts.map +1 -0
  62. package/output/app/lifecycle.js +99 -0
  63. package/output/app/lifecycle.test.d.ts +2 -0
  64. package/output/app/lifecycle.test.d.ts.map +1 -0
  65. package/output/app/lifecycle.test.js +152 -0
  66. package/output/app/middleware/auth.d.ts +71 -0
  67. package/output/app/middleware/auth.d.ts.map +1 -0
  68. package/output/app/middleware/auth.js +190 -0
  69. package/output/app/middleware/context.d.ts +13 -0
  70. package/output/app/middleware/context.d.ts.map +1 -0
  71. package/output/app/middleware/context.js +15 -0
  72. package/output/app/middleware/cors.d.ts +14 -0
  73. package/output/app/middleware/cors.d.ts.map +1 -0
  74. package/output/app/middleware/cors.js +30 -0
  75. package/output/app/middleware/index.d.ts +17 -0
  76. package/output/app/middleware/index.d.ts.map +1 -0
  77. package/output/app/middleware/index.js +16 -0
  78. package/output/app/middleware/rate-limit.d.ts +24 -0
  79. package/output/app/middleware/rate-limit.d.ts.map +1 -0
  80. package/output/app/middleware/rate-limit.js +46 -0
  81. package/output/app/middleware-order.test.d.ts +2 -0
  82. package/output/app/middleware-order.test.d.ts.map +1 -0
  83. package/output/app/middleware-order.test.js +148 -0
  84. package/output/app/permission-operations.d.ts +48 -0
  85. package/output/app/permission-operations.d.ts.map +1 -0
  86. package/output/app/permission-operations.js +77 -0
  87. package/output/app/permission-operations.test.d.ts +2 -0
  88. package/output/app/permission-operations.test.d.ts.map +1 -0
  89. package/output/app/permission-operations.test.js +72 -0
  90. package/output/app/permission-registration.test.d.ts +2 -0
  91. package/output/app/permission-registration.test.d.ts.map +1 -0
  92. package/output/app/permission-registration.test.js +138 -0
  93. package/output/app/redis.d.ts +45 -0
  94. package/output/app/redis.d.ts.map +1 -0
  95. package/output/app/redis.js +61 -0
  96. package/output/app/registered-operations.test.d.ts +2 -0
  97. package/output/app/registered-operations.test.d.ts.map +1 -0
  98. package/output/app/registered-operations.test.js +57 -0
  99. package/output/app/request-id.test.d.ts +2 -0
  100. package/output/app/request-id.test.d.ts.map +1 -0
  101. package/output/app/request-id.test.js +130 -0
  102. package/output/app/resources.test.d.ts +2 -0
  103. package/output/app/resources.test.d.ts.map +1 -0
  104. package/output/app/resources.test.js +199 -0
  105. package/output/app/shutdown.d.ts +17 -4
  106. package/output/app/shutdown.d.ts.map +1 -1
  107. package/output/app/shutdown.js +27 -12
  108. package/output/app/shutdown.test.js +78 -124
  109. package/output/app/signal.d.ts +4 -4
  110. package/output/app/signal.d.ts.map +1 -1
  111. package/output/app/signal.js +3 -3
  112. package/output/app/smoke.test.d.ts +2 -0
  113. package/output/app/smoke.test.d.ts.map +1 -0
  114. package/output/app/smoke.test.js +210 -0
  115. package/output/app/test-support.d.ts +21 -0
  116. package/output/app/test-support.d.ts.map +1 -0
  117. package/output/app/test-support.js +24 -0
  118. package/output/app/type-augment.d.ts +34 -0
  119. package/output/app/type-augment.d.ts.map +1 -0
  120. package/output/app/type-augment.js +7 -0
  121. package/output/app/types.d.ts +205 -126
  122. package/output/app/types.d.ts.map +1 -1
  123. package/output/app/types.js +2 -1
  124. package/package.json +10 -6
  125. package/output/app/assemble.d.ts +0 -16
  126. package/output/app/assemble.d.ts.map +0 -1
  127. package/output/app/assemble.js +0 -124
  128. package/output/app/database.d.ts +0 -36
  129. package/output/app/database.d.ts.map +0 -1
  130. package/output/app/database.js +0 -40
  131. package/output/app/database.test.d.ts +0 -2
  132. package/output/app/database.test.d.ts.map +0 -1
  133. package/output/app/database.test.js +0 -66
  134. package/output/app/default-modules.d.ts +0 -23
  135. package/output/app/default-modules.d.ts.map +0 -1
  136. package/output/app/default-modules.js +0 -40
  137. package/output/app/define-server.d.ts +0 -12
  138. package/output/app/define-server.d.ts.map +0 -1
  139. package/output/app/define-server.js +0 -289
  140. package/output/app/module.d.ts +0 -88
  141. package/output/app/module.d.ts.map +0 -1
  142. package/output/app/module.js +0 -34
  143. package/output/app/steps.d.ts +0 -17
  144. package/output/app/steps.d.ts.map +0 -1
  145. package/output/app/steps.js +0 -41
@@ -1,40 +0,0 @@
1
- import { SystemError } from '@taicode/common-base';
2
- /** common 默认的统一 operation 标签(design §2.3:抽进 common 时统一口径) */
3
- const DEFAULT_OPERATION_LABELS = {
4
- known: '已知请求错误',
5
- unknown: '未知请求错误',
6
- validation: '参数校验错误',
7
- initialization: '初始化错误',
8
- rustPanic: '引擎崩溃',
9
- };
10
- /**
11
- * `database` 模块的默认实现:Prisma(design §3.3 / 调研 D)。
12
- *
13
- * 返回类型即 `defineServer<Db>` 的 `Db`,调用方据此**推断**类型、无需强转(决策 10-2)。
14
- */
15
- export function createPrismaDatabaseModule(params) {
16
- const labels = { ...DEFAULT_OPERATION_LABELS, ...params.operationLabels };
17
- return {
18
- name: 'database',
19
- createClient: (clientParams) => {
20
- const args = params.createClientArgs ? params.createClientArgs(clientParams) : [];
21
- return Reflect.construct(params.PrismaClient, args);
22
- },
23
- classifyError: (error) => {
24
- const hit = (Ctor, label) => (error instanceof Ctor ? label : null);
25
- const namespace = params.PrismaNamespace;
26
- const label = hit(namespace.PrismaClientKnownRequestError, labels.known) ??
27
- hit(namespace.PrismaClientUnknownRequestError, labels.unknown) ??
28
- hit(namespace.PrismaClientValidationError, labels.validation) ??
29
- hit(namespace.PrismaClientInitializationError, labels.initialization) ??
30
- hit(namespace.PrismaClientRustPanicError, labels.rustPanic);
31
- return label ? new SystemError('DATABASE_ERROR', { operation: label }, label) : null;
32
- },
33
- dispose: async (db) => {
34
- const target = db;
35
- if (typeof target.$disconnect === 'function') {
36
- await target.$disconnect();
37
- }
38
- },
39
- };
40
- }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=database.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"database.test.d.ts","sourceRoot":"","sources":["../../source/app/database.test.ts"],"names":[],"mappings":""}
@@ -1,66 +0,0 @@
1
- import { SystemError } from '@taicode/common-base';
2
- import { describe, expect, it } from 'vitest';
3
- import { createPrismaDatabaseModule } from './database.js';
4
- const TEST_CONFIG = { loadConfig: () => ({ debug: false, port: 0 }) };
5
- const NAMESPACE = {
6
- PrismaClientKnownRequestError: class extends Error {
7
- },
8
- PrismaClientUnknownRequestError: class extends Error {
9
- },
10
- PrismaClientValidationError: class extends Error {
11
- },
12
- PrismaClientInitializationError: class extends Error {
13
- },
14
- PrismaClientRustPanicError: class extends Error {
15
- },
16
- };
17
- class Client {
18
- static lastArgs = [];
19
- disconnected = false;
20
- constructor(...args) {
21
- Client.lastArgs = args;
22
- }
23
- async $disconnect() {
24
- this.disconnected = true;
25
- }
26
- }
27
- describe('createPrismaDatabaseModule', () => {
28
- it('createClient 使用注入的构造函数与参数工厂(common 不依赖 @prisma/client)', async () => {
29
- const database = createPrismaDatabaseModule({
30
- PrismaClient: Client,
31
- PrismaNamespace: NAMESPACE,
32
- createClientArgs: (params) => [{ adapter: params.postgresConfig }],
33
- });
34
- const client = await database.createClient({ config: TEST_CONFIG, postgresConfig: { schema: 'test' } });
35
- expect(client).toBeInstanceOf(Client);
36
- expect(Client.lastArgs).toEqual([{ adapter: { schema: 'test' } }]);
37
- });
38
- it('省略 createClientArgs 时等价于 new PrismaClient()', async () => {
39
- const database = createPrismaDatabaseModule({ PrismaClient: Client, PrismaNamespace: NAMESPACE });
40
- await database.createClient({ config: TEST_CONFIG, postgresConfig: undefined });
41
- expect(Client.lastArgs).toEqual([]);
42
- });
43
- it('classifyError 命中 5 类错误时返回 SystemError(DATABASE_ERROR),未命中返回 null', () => {
44
- const database = createPrismaDatabaseModule({ PrismaClient: Client, PrismaNamespace: NAMESPACE });
45
- const known = database.classifyError(new NAMESPACE.PrismaClientKnownRequestError());
46
- expect(SystemError.is(known)).toBe(true);
47
- expect(known?.type).toBe('DATABASE_ERROR');
48
- expect(known?.context?.operation).toBe('已知请求错误');
49
- expect(database.classifyError(new NAMESPACE.PrismaClientRustPanicError())?.context?.operation).toBe('引擎崩溃');
50
- expect(database.classifyError(new Error('plain'))).toBeNull();
51
- });
52
- it('operationLabels 可覆盖默认标签', () => {
53
- const database = createPrismaDatabaseModule({
54
- PrismaClient: Client,
55
- PrismaNamespace: NAMESPACE,
56
- operationLabels: { known: 'ClientKnown' },
57
- });
58
- expect(database.classifyError(new NAMESPACE.PrismaClientKnownRequestError())?.context?.operation).toBe('ClientKnown');
59
- });
60
- it('dispose 调用 db.$disconnect()', async () => {
61
- const database = createPrismaDatabaseModule({ PrismaClient: Client, PrismaNamespace: NAMESPACE });
62
- const client = await database.createClient({ config: TEST_CONFIG, postgresConfig: undefined });
63
- await database.dispose?.(client);
64
- expect(client.disconnected).toBe(true);
65
- });
66
- });
@@ -1,23 +0,0 @@
1
- import type { ModuleName, ServerModule } from './module.js';
2
- /**
3
- * 默认 `env` 模块(P1 空实现)。
4
- *
5
- * `loadEnvFiles()` 是独立导出、在骨架之前调用(决策 #5),因此这里只作为「封闭集合内的合法名字」存在。
6
- */
7
- export declare function createEnvModule(): ServerModule;
8
- /**
9
- * P1 默认 `redis` 模块(透传)。
10
- *
11
- * 骨架只持有 `createRedis()` 返回的 `{ client, dispose }`;未传 `createRedis` 时第 4 步空转
12
- * (notice 即是此形态)。
13
- */
14
- export declare function createRedisModule(): ServerModule;
15
- /**
16
- * P1 有默认实现的模块(`env` / `redis`)。
17
- *
18
- * ⚠️ 故意不含 `database`:common 无法引用各仓生成的 Prisma client(决策 #8),给个「不建连」的
19
- * 空实现会埋下静默无 DB 的雷,因此 `database` 必须由调用方**显式**给出
20
- * `createPrismaDatabaseModule({ ... })` 或 `false`(装配期强制,见 `assemble.ts`)。
21
- */
22
- export declare function createDefaultModules(): Partial<Record<ModuleName, ServerModule>>;
23
- //# sourceMappingURL=default-modules.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"default-modules.d.ts","sourceRoot":"","sources":["../../source/app/default-modules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE3D;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,YAAY,CAE9C;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,YAAY,CAYhD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC,CAKhF"}
@@ -1,40 +0,0 @@
1
- /**
2
- * 默认 `env` 模块(P1 空实现)。
3
- *
4
- * `loadEnvFiles()` 是独立导出、在骨架之前调用(决策 #5),因此这里只作为「封闭集合内的合法名字」存在。
5
- */
6
- export function createEnvModule() {
7
- return { name: 'env' };
8
- }
9
- /**
10
- * P1 默认 `redis` 模块(透传)。
11
- *
12
- * 骨架只持有 `createRedis()` 返回的 `{ client, dispose }`;未传 `createRedis` 时第 4 步空转
13
- * (notice 即是此形态)。
14
- */
15
- export function createRedisModule() {
16
- return {
17
- name: 'redis',
18
- steps: {
19
- createRedis: (ctx) => {
20
- const handle = ctx.options.createRedis?.(ctx.options.config);
21
- if (handle) {
22
- ctx.state.redis = handle;
23
- }
24
- },
25
- },
26
- };
27
- }
28
- /**
29
- * P1 有默认实现的模块(`env` / `redis`)。
30
- *
31
- * ⚠️ 故意不含 `database`:common 无法引用各仓生成的 Prisma client(决策 #8),给个「不建连」的
32
- * 空实现会埋下静默无 DB 的雷,因此 `database` 必须由调用方**显式**给出
33
- * `createPrismaDatabaseModule({ ... })` 或 `false`(装配期强制,见 `assemble.ts`)。
34
- */
35
- export function createDefaultModules() {
36
- return {
37
- env: createEnvModule(),
38
- redis: createRedisModule(),
39
- };
40
- }
@@ -1,12 +0,0 @@
1
- import type { DefineServerOptions, ServerInstance } from './types.js';
2
- /**
3
- * 模块化装配 + 15 步固定编排(design §3 / §4 / §5;提案 §11)。
4
- *
5
- * 返回**原生 `FastifyInstance`**(不包 `fastify-plugin`、不返回包装对象)——调用方可直接
6
- * `app.swagger()` / `app.inject()` / `app.decorate()`。`mode: 'openapi-build'` 时不 listen,
7
- * 由调用方读 `app.swagger()`。
8
- *
9
- * `Db` 由 `modules.database`(`database` 模块的 `createClient` 返回类型)推断(决策 10-2)。
10
- */
11
- export declare function defineServer<Db = unknown, S = unknown>(options: DefineServerOptions<Db, S>): Promise<ServerInstance>;
12
- //# sourceMappingURL=define-server.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"define-server.d.ts","sourceRoot":"","sources":["../../source/app/define-server.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,mBAAmB,EAInB,cAAc,EAIf,MAAM,YAAY,CAAA;AA4QnB;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,GAAG,OAAO,EAC1D,OAAO,EAAE,mBAAmB,CAAC,EAAE,EAAE,CAAC,CAAC,GAClC,OAAO,CAAC,cAAc,CAAC,CAsBzB"}
@@ -1,289 +0,0 @@
1
- import { Disposer } from '@taicode/common-base';
2
- import Fastify, {} from 'fastify';
3
- import { assembleModules } from './assemble.js';
4
- import { attachSignalHandlers } from './signal.js';
5
- import { SHUTDOWN_ORDER } from './shutdown.js';
6
- import { SKIP_BY_MODE, STEP_ORDER } from './steps.js';
7
- const NOOP_LOGGER = {
8
- debug: () => undefined,
9
- info: () => undefined,
10
- warn: () => undefined,
11
- error: () => undefined,
12
- };
13
- function requireApp(state) {
14
- if (!state.app) {
15
- throw new Error('Fastify instance has not been created yet');
16
- }
17
- return state.app;
18
- }
19
- function resolveLog(options, state) {
20
- if (options.log) {
21
- return options.log;
22
- }
23
- if (state.app) {
24
- return state.app.log;
25
- }
26
- return NOOP_LOGGER;
27
- }
28
- function collectHooks(resolved) {
29
- const hooks = {};
30
- for (const name of resolved.order) {
31
- const module = resolved.byName[name];
32
- if (module?.hooks) {
33
- Object.assign(hooks, module.hooks);
34
- }
35
- }
36
- return hooks;
37
- }
38
- function collectPlugins(resolved, options) {
39
- const plugins = [];
40
- for (const name of resolved.order) {
41
- const module = resolved.byName[name];
42
- if (module?.plugins) {
43
- plugins.push(...module.plugins);
44
- }
45
- }
46
- if (options.extraPlugins) {
47
- plugins.push(...options.extraPlugins);
48
- }
49
- return plugins;
50
- }
51
- function buildStepImplementations(resolved, options, moduleHooks) {
52
- const implementations = {};
53
- // 1) 固定骨架步骤的内置实现(模块可覆盖既定槽位,但无法新增 / 重排步骤)
54
- implementations.createFastify = (ctx) => {
55
- ctx.state.app = Fastify({ logger: ctx.options.config.loadConfig().debug, trustProxy: true });
56
- };
57
- implementations.registerCorePlugins = async (ctx) => {
58
- const app = requireApp(ctx.state);
59
- for (const plugin of collectPlugins(resolved, ctx.options)) {
60
- await app.register(plugin);
61
- }
62
- };
63
- implementations.onClose = (ctx) => {
64
- const shutdownImplementations = buildShutdownImplementations(resolved, moduleHooks);
65
- requireApp(ctx.state).addHook('onClose', () => runShutdown(ctx, shutdownImplementations, moduleHooks));
66
- };
67
- implementations.ready = async (ctx) => {
68
- await requireApp(ctx.state).ready();
69
- };
70
- implementations.listen = async (ctx) => {
71
- const app = requireApp(ctx.state);
72
- const host = ctx.options.serverOptions?.host ?? '0.0.0.0';
73
- const port = ctx.options.serverOptions?.port ?? ctx.options.config.loadConfig().port;
74
- await app.listen({ port, host });
75
- if (ctx.options.signal !== false) {
76
- attachSignalHandlers(app, ctx.log);
77
- }
78
- };
79
- // 2) 数据层端口 → 第 3 步(database 模块的整体覆盖也在此生效)
80
- const database = resolved.database;
81
- if (database) {
82
- implementations['database.createClient'] = async (ctx) => {
83
- const loadPostgresConfig = ctx.options.config.loadPostgresConfig;
84
- const postgresConfig = loadPostgresConfig ? await loadPostgresConfig() : undefined;
85
- ctx.state.db = await database.createClient({ config: ctx.options.config, postgresConfig });
86
- };
87
- }
88
- // 3) 模块贡献的步骤实现(注册顺序 = MODULE_NAMES 顺序)
89
- for (const name of resolved.order) {
90
- const module = resolved.byName[name];
91
- if (module?.steps) {
92
- Object.assign(implementations, module.steps);
93
- }
94
- }
95
- // 4) 调用方槽位(调用方优先级最高)
96
- if (options.setupCompilers) {
97
- implementations.setupCompilers = (ctx) => {
98
- ctx.options.setupCompilers?.(requireApp(ctx.state));
99
- };
100
- }
101
- if (options.initGlobalConfig) {
102
- implementations.initGlobalConfig = (ctx) => {
103
- const globalConfigOptions = {
104
- redisUrl: ctx.options.config.resolveRedisUrl?.(),
105
- channelPrefix: ctx.options.config.resolveCachePrefix?.(),
106
- };
107
- const dispose = ctx.options.initGlobalConfig?.(ctx.state.db, globalConfigOptions);
108
- if (typeof dispose === 'function') {
109
- ctx.state.disposeConfig = dispose;
110
- }
111
- };
112
- }
113
- if (options.initClients) {
114
- implementations.initClients = async (ctx) => {
115
- const unsubscribe = await ctx.options.initClients?.({
116
- db: ctx.state.db,
117
- redis: ctx.state.redis?.client,
118
- globalConfig: ctx.state.globalConfig,
119
- });
120
- if (typeof unsubscribe === 'function') {
121
- ctx.state.unsubscribe = unsubscribe;
122
- }
123
- };
124
- }
125
- implementations.createServices = (ctx) => {
126
- ctx.state.services = ctx.options.createServices({
127
- db: ctx.state.db,
128
- redis: ctx.state.redis?.client,
129
- globalConfig: ctx.state.globalConfig,
130
- });
131
- };
132
- if (options.initServices || options.afterInit) {
133
- implementations.initServices = async (ctx) => {
134
- if (ctx.options.initServices) {
135
- await ctx.options.initServices({ services: ctx.state.services });
136
- }
137
- if (ctx.options.afterInit) {
138
- const disposer = await ctx.options.afterInit({
139
- db: ctx.state.db,
140
- redis: ctx.state.redis?.client,
141
- globalConfig: ctx.state.globalConfig,
142
- services: ctx.state.services,
143
- });
144
- if (typeof disposer === 'function') {
145
- ctx.state.disposer.addDisposer(disposer);
146
- }
147
- }
148
- };
149
- }
150
- if (options.backgroundWorkers) {
151
- implementations.backgroundWorkers = async (ctx) => {
152
- await ctx.options.backgroundWorkers?.({
153
- db: ctx.state.db,
154
- redis: ctx.state.redis?.client,
155
- globalConfig: ctx.state.globalConfig,
156
- services: ctx.state.services,
157
- disposer: ctx.state.disposer,
158
- });
159
- };
160
- }
161
- implementations.registerRouters = async (ctx) => {
162
- await ctx.options.registerRouters(requireApp(ctx.state), { services: ctx.state.services });
163
- };
164
- if (options.afterReady) {
165
- implementations.afterReady = async (ctx) => {
166
- await ctx.options.afterReady?.({ app: requireApp(ctx.state), services: ctx.state.services });
167
- };
168
- }
169
- return implementations;
170
- }
171
- async function disposeServiceMap(services) {
172
- if (typeof services !== 'object' || services === null) {
173
- return;
174
- }
175
- const record = services;
176
- const pending = Object.values(record).map((service) => {
177
- const candidate = service;
178
- return typeof candidate.dispose === 'function' ? candidate.dispose() : undefined;
179
- });
180
- await Promise.allSettled(pending);
181
- }
182
- function buildShutdownImplementations(resolved, moduleHooks) {
183
- const implementations = {
184
- stopProducers: async (ctx) => {
185
- await ctx.state.disposer.dispose();
186
- },
187
- unsubscribeConfig: async (ctx) => {
188
- await ctx.state.unsubscribe?.();
189
- },
190
- disposeConfig: async (ctx) => {
191
- await ctx.state.disposeConfig?.();
192
- },
193
- disposeServices: async (ctx) => {
194
- const disposeServices = ctx.options.disposeServices ?? moduleHooks.disposeServices;
195
- if (disposeServices) {
196
- await disposeServices({ services: ctx.state.services });
197
- }
198
- else {
199
- await disposeServiceMap(ctx.state.services);
200
- }
201
- },
202
- disposeClients: async (ctx) => {
203
- const disposeClients = ctx.options.disposeClients ?? moduleHooks.disposeClients;
204
- await disposeClients?.();
205
- },
206
- disposeRedis: async (ctx) => {
207
- await ctx.state.redis?.dispose();
208
- },
209
- disposeDb: async (ctx) => {
210
- await resolved.database?.dispose?.(ctx.state.db);
211
- },
212
- };
213
- for (const name of resolved.order) {
214
- const module = resolved.byName[name];
215
- if (module?.shutdown) {
216
- Object.assign(implementations, module.shutdown);
217
- }
218
- }
219
- return implementations;
220
- }
221
- async function runShutdown(ctx, implementations, moduleHooks) {
222
- const errors = [];
223
- const options = ctx.options;
224
- const shutdown = options.shutdown ?? moduleHooks.shutdown;
225
- const beforeShutdown = options.beforeShutdown ?? moduleHooks.beforeShutdown;
226
- if (shutdown) {
227
- try {
228
- await shutdown('onClose');
229
- }
230
- catch (error) {
231
- errors.push(error);
232
- }
233
- }
234
- else {
235
- if (beforeShutdown) {
236
- try {
237
- await beforeShutdown();
238
- }
239
- catch (error) {
240
- errors.push(error);
241
- }
242
- }
243
- for (const step of SHUTDOWN_ORDER) {
244
- const implementation = implementations[step];
245
- if (!implementation) {
246
- continue;
247
- }
248
- try {
249
- await implementation(ctx);
250
- }
251
- catch (error) {
252
- errors.push(error);
253
- }
254
- }
255
- }
256
- if (errors.length > 0) {
257
- ctx.log.error({ errors, count: errors.length }, '[shutdown] completed with errors');
258
- }
259
- }
260
- /**
261
- * 模块化装配 + 15 步固定编排(design §3 / §4 / §5;提案 §11)。
262
- *
263
- * 返回**原生 `FastifyInstance`**(不包 `fastify-plugin`、不返回包装对象)——调用方可直接
264
- * `app.swagger()` / `app.inject()` / `app.decorate()`。`mode: 'openapi-build'` 时不 listen,
265
- * 由调用方读 `app.swagger()`。
266
- *
267
- * `Db` 由 `modules.database`(`database` 模块的 `createClient` 返回类型)推断(决策 10-2)。
268
- */
269
- export async function defineServer(options) {
270
- const internal = options;
271
- const mode = internal.mode ?? 'production';
272
- const resolved = assembleModules(internal.modules);
273
- const state = { disposer: new Disposer() };
274
- const moduleHooks = collectHooks(resolved);
275
- const implementations = buildStepImplementations(resolved, internal, moduleHooks);
276
- const skipped = new Set(SKIP_BY_MODE[mode]);
277
- for (const step of STEP_ORDER) {
278
- if (skipped.has(step)) {
279
- continue;
280
- }
281
- const ctx = { options: internal, state, log: resolveLog(internal, state) };
282
- ctx.log.debug({ step }, '[boot]');
283
- const implementation = implementations[step];
284
- if (implementation) {
285
- await implementation(ctx);
286
- }
287
- }
288
- return requireApp(state);
289
- }
@@ -1,88 +0,0 @@
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
@@ -1 +0,0 @@
1
- {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../source/app/module.ts"],"names":[],"mappings":"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"}
@@ -1,34 +0,0 @@
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
- };
@@ -1,17 +0,0 @@
1
- import type { ServerMode } from './types.js';
2
- /**
3
- * 固定 15 步(design §4.1)——**顺序写死在数组里**。
4
- *
5
- * 模块只能往这些「既定槽位」里填实现,不得新增步骤、不得重排、不得改变 §4.1 的依赖偏序。
6
- */
7
- export declare const STEP_ORDER: readonly ["createFastify", "setupCompilers", "database.createClient", "createRedis", "initGlobalConfig", "initClients", "registerCorePlugins", "createServices", "initServices", "backgroundWorkers", "registerRouters", "onClose", "ready", "afterReady", "listen"];
8
- export type StepName = (typeof STEP_ORDER)[number];
9
- /**
10
- * 各模式跳过的步骤(design §4.1 / §4.3)。
11
- *
12
- * - `test`:关后台 worker 与 ready 后动作(第 10 / 14 步)。
13
- * - `openapi-build`:跳过 [5,6,9,10,14,15] 与一切连库/连 Redis 的动作,但仍保留 [3,4](10-3:
14
- * P1 沿用四家现状「不跳过第 3/4 步」,跳过错步留 P2),并保留 [7,8,11] 以保证 `app.swagger()` 完整。
15
- */
16
- export declare const SKIP_BY_MODE: Record<ServerMode, readonly StepName[]>;
17
- //# sourceMappingURL=steps.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"steps.d.ts","sourceRoot":"","sources":["../../source/app/steps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C;;;;GAIG;AACH,eAAO,MAAM,UAAU,sQAgBb,CAAA;AAEV,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAA;AAElD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,UAAU,EAAE,SAAS,QAAQ,EAAE,CAWhE,CAAA"}