@taicode/common-server 3.7.13 → 4.0.0-beta.1
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.
- package/output/app/auth.test.d.ts +2 -0
- package/output/app/auth.test.d.ts.map +1 -0
- package/output/app/auth.test.js +148 -0
- package/output/app/boot.d.ts +48 -0
- package/output/app/boot.d.ts.map +1 -0
- package/output/app/boot.js +123 -0
- package/output/app/config.test.d.ts +2 -0
- package/output/app/config.test.d.ts.map +1 -0
- package/output/app/config.test.js +132 -0
- package/output/app/context.d.ts +68 -0
- package/output/app/context.d.ts.map +1 -0
- package/output/app/context.js +73 -0
- package/output/app/core/cookie.d.ts +25 -0
- package/output/app/core/cookie.d.ts.map +1 -0
- package/output/app/core/cookie.js +22 -0
- package/output/app/core/identity.d.ts +12 -0
- package/output/app/core/identity.d.ts.map +1 -0
- package/output/app/core/identity.js +14 -0
- package/output/app/core/index.d.ts +44 -0
- package/output/app/core/index.d.ts.map +1 -0
- package/output/app/core/index.js +57 -0
- package/output/app/core/logging.d.ts +13 -0
- package/output/app/core/logging.d.ts.map +1 -0
- package/output/app/core/logging.js +29 -0
- package/output/app/core/openapi.d.ts +17 -0
- package/output/app/core/openapi.d.ts.map +1 -0
- package/output/app/core/openapi.js +42 -0
- package/output/app/core/request-id.d.ts +36 -0
- package/output/app/core/request-id.d.ts.map +1 -0
- package/output/app/core/request-id.js +66 -0
- package/output/app/core/response.d.ts +41 -0
- package/output/app/core/response.d.ts.map +1 -0
- package/output/app/core/response.js +89 -0
- package/output/app/define-server.negative-cases.d.ts +32 -0
- package/output/app/define-server.negative-cases.d.ts.map +1 -0
- package/output/app/define-server.negative-cases.js +209 -0
- package/output/app/define-server.test.d.ts +2 -0
- package/output/app/define-server.test.d.ts.map +1 -0
- package/output/app/define-server.test.js +97 -0
- package/output/app/entries.d.ts +29 -0
- package/output/app/entries.d.ts.map +1 -0
- package/output/app/entries.js +64 -0
- package/output/app/env.d.ts +59 -0
- package/output/app/env.d.ts.map +1 -0
- package/output/app/env.js +66 -0
- package/output/app/env.test.d.ts +2 -0
- package/output/app/env.test.d.ts.map +1 -0
- package/output/app/env.test.js +124 -0
- package/output/app/error-contract.test.d.ts +2 -0
- package/output/app/error-contract.test.d.ts.map +1 -0
- package/output/app/error-contract.test.js +125 -0
- package/output/app/errors.d.ts +20 -0
- package/output/app/errors.d.ts.map +1 -0
- package/output/app/errors.js +51 -0
- package/output/app/generate-openapi.test.d.ts +2 -0
- package/output/app/generate-openapi.test.d.ts.map +1 -0
- package/output/app/generate-openapi.test.js +123 -0
- package/output/app/index.d.ts +16 -0
- package/output/app/index.d.ts.map +1 -0
- package/output/app/index.js +18 -0
- package/output/app/lifecycle-logging.test.d.ts +2 -0
- package/output/app/lifecycle-logging.test.d.ts.map +1 -0
- package/output/app/lifecycle-logging.test.js +155 -0
- package/output/app/lifecycle.d.ts +70 -0
- package/output/app/lifecycle.d.ts.map +1 -0
- package/output/app/lifecycle.js +99 -0
- package/output/app/lifecycle.test.d.ts +2 -0
- package/output/app/lifecycle.test.d.ts.map +1 -0
- package/output/app/lifecycle.test.js +152 -0
- package/output/app/middleware/auth.d.ts +71 -0
- package/output/app/middleware/auth.d.ts.map +1 -0
- package/output/app/middleware/auth.js +190 -0
- package/output/app/middleware/context.d.ts +13 -0
- package/output/app/middleware/context.d.ts.map +1 -0
- package/output/app/middleware/context.js +15 -0
- package/output/app/middleware/cors.d.ts +14 -0
- package/output/app/middleware/cors.d.ts.map +1 -0
- package/output/app/middleware/cors.js +30 -0
- package/output/app/middleware/index.d.ts +17 -0
- package/output/app/middleware/index.d.ts.map +1 -0
- package/output/app/middleware/index.js +16 -0
- package/output/app/middleware/rate-limit.d.ts +24 -0
- package/output/app/middleware/rate-limit.d.ts.map +1 -0
- package/output/app/middleware/rate-limit.js +46 -0
- package/output/app/middleware-order.test.d.ts +2 -0
- package/output/app/middleware-order.test.d.ts.map +1 -0
- package/output/app/middleware-order.test.js +148 -0
- package/output/app/permission-operations.d.ts +48 -0
- package/output/app/permission-operations.d.ts.map +1 -0
- package/output/app/permission-operations.js +77 -0
- package/output/app/permission-operations.test.d.ts +2 -0
- package/output/app/permission-operations.test.d.ts.map +1 -0
- package/output/app/permission-operations.test.js +72 -0
- package/output/app/permission-registration.test.d.ts +2 -0
- package/output/app/permission-registration.test.d.ts.map +1 -0
- package/output/app/permission-registration.test.js +138 -0
- package/output/app/redis.d.ts +45 -0
- package/output/app/redis.d.ts.map +1 -0
- package/output/app/redis.js +61 -0
- package/output/app/registered-operations.test.d.ts +2 -0
- package/output/app/registered-operations.test.d.ts.map +1 -0
- package/output/app/registered-operations.test.js +57 -0
- package/output/app/request-id.test.d.ts +2 -0
- package/output/app/request-id.test.d.ts.map +1 -0
- package/output/app/request-id.test.js +130 -0
- package/output/app/resources.test.d.ts +2 -0
- package/output/app/resources.test.d.ts.map +1 -0
- package/output/app/resources.test.js +197 -0
- package/output/app/shutdown.d.ts +22 -0
- package/output/app/shutdown.d.ts.map +1 -0
- package/output/app/shutdown.js +30 -0
- package/output/app/shutdown.test.d.ts +2 -0
- package/output/app/shutdown.test.d.ts.map +1 -0
- package/output/app/shutdown.test.js +103 -0
- package/output/app/signal.d.ts +12 -0
- package/output/app/signal.d.ts.map +1 -0
- package/output/app/signal.js +45 -0
- package/output/app/smoke.test.d.ts +2 -0
- package/output/app/smoke.test.d.ts.map +1 -0
- package/output/app/smoke.test.js +210 -0
- package/output/app/test-support.d.ts +21 -0
- package/output/app/test-support.d.ts.map +1 -0
- package/output/app/test-support.js +24 -0
- package/output/app/type-augment.d.ts +34 -0
- package/output/app/type-augment.d.ts.map +1 -0
- package/output/app/type-augment.js +7 -0
- package/output/app/types.d.ts +211 -0
- package/output/app/types.d.ts.map +1 -0
- package/output/app/types.js +2 -0
- package/package.json +12 -5
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 内置信号处理(设计 §7.1 `activate` 尾部 / §7.2)。
|
|
3
|
+
*
|
|
4
|
+
* `SIGTERM` / `SIGINT` / `uncaughtException` / `unhandledRejection` ⇒ `app.close()`(触发 `onClose`
|
|
5
|
+
* 的关闭 6 步)⇒ `process.exit(0/1)`。**仅 `listen: true` 时装信号处理**。
|
|
6
|
+
*
|
|
7
|
+
* @returns detach 函数(移除本次注册的监听器)
|
|
8
|
+
*/
|
|
9
|
+
export function attachSignalHandlers(app, log) {
|
|
10
|
+
let closing = false;
|
|
11
|
+
const shutdown = (signal, code) => {
|
|
12
|
+
if (closing) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
closing = true;
|
|
16
|
+
log.info({ signal }, '[server] received shutdown signal');
|
|
17
|
+
app.close().then(() => {
|
|
18
|
+
log.info({ signal }, '[server] shutdown complete');
|
|
19
|
+
process.exit(code);
|
|
20
|
+
}, (error) => {
|
|
21
|
+
log.error({ error }, '[server] error during shutdown');
|
|
22
|
+
process.exit(1);
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
const onSigterm = () => shutdown('SIGTERM', 0);
|
|
26
|
+
const onSigint = () => shutdown('SIGINT', 0);
|
|
27
|
+
const onUncaught = (error) => {
|
|
28
|
+
log.error({ error }, '[server] uncaught exception');
|
|
29
|
+
shutdown('uncaughtException', 1);
|
|
30
|
+
};
|
|
31
|
+
const onRejection = (reason) => {
|
|
32
|
+
log.error({ reason }, '[server] unhandled rejection');
|
|
33
|
+
shutdown('unhandledRejection', 1);
|
|
34
|
+
};
|
|
35
|
+
process.on('SIGTERM', onSigterm);
|
|
36
|
+
process.on('SIGINT', onSigint);
|
|
37
|
+
process.on('uncaughtException', onUncaught);
|
|
38
|
+
process.on('unhandledRejection', onRejection);
|
|
39
|
+
return () => {
|
|
40
|
+
process.off('SIGTERM', onSigterm);
|
|
41
|
+
process.off('SIGINT', onSigint);
|
|
42
|
+
process.off('uncaughtException', onUncaught);
|
|
43
|
+
process.off('unhandledRejection', onRejection);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoke.test.d.ts","sourceRoot":"","sources":["../../source/app/smoke.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { defineServer, generateOpenApi, startServer } from './entries.js';
|
|
4
|
+
import { SystemError, UserError } from './errors.js';
|
|
5
|
+
import { defaultMiddlewares } from './middleware/index.js';
|
|
6
|
+
import { createCache, createRedis } from './redis.js';
|
|
7
|
+
const summarySchema = {
|
|
8
|
+
querystring: z.object({ currency: z.string().optional() }),
|
|
9
|
+
response: {
|
|
10
|
+
200: z.object({
|
|
11
|
+
status: z.string(),
|
|
12
|
+
message: z.string(),
|
|
13
|
+
data: z.object({ siteName: z.string(), currency: z.string() }),
|
|
14
|
+
}),
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
describe('端到端冒烟:defineServer → startServer → inject → generateOpenApi', () => {
|
|
18
|
+
it('真实装配路径跑通最小服务器(无 Postgres / 无 Redis / 无网络)', async () => {
|
|
19
|
+
const previousDatabaseUrl = process.env.DATABASE_URL;
|
|
20
|
+
const previousRedisUrl = process.env.REDIS_URL;
|
|
21
|
+
delete process.env.DATABASE_URL;
|
|
22
|
+
delete process.env.REDIS_URL;
|
|
23
|
+
const lifecycleEvents = [];
|
|
24
|
+
const counters = { readConfiguration: 0, disposeDatabase: 0 };
|
|
25
|
+
const database = {
|
|
26
|
+
readSystemConfiguration: async () => {
|
|
27
|
+
counters.readConfiguration += 1;
|
|
28
|
+
return { siteName: 'taicode-smoke', currency: 'CNY' };
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
try {
|
|
32
|
+
const server = defineServer({
|
|
33
|
+
app: { service: 'smoke', title: 'Smoke Service', version: '1.4.0' },
|
|
34
|
+
resources: (ctx) => {
|
|
35
|
+
const redis = createRedis(ctx.env, ctx.log);
|
|
36
|
+
return {
|
|
37
|
+
// 构造但不建连;出 spec 路径也会构造
|
|
38
|
+
database,
|
|
39
|
+
redis,
|
|
40
|
+
cache: createCache({ env: ctx.env, prefix: 'smoke', redis, log: ctx.log }),
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
config: async ({ resources }) => {
|
|
44
|
+
const configuration = await resources.database.readSystemConfiguration();
|
|
45
|
+
return { siteName: configuration.siteName, currency: configuration.currency };
|
|
46
|
+
},
|
|
47
|
+
// 声明序即启动序(services 之间不得相互依赖)
|
|
48
|
+
services: (ctx) => {
|
|
49
|
+
return {
|
|
50
|
+
// routers 拿不到 resources / config,只能穿过 service
|
|
51
|
+
// 配置一律在**调用期**现读:构造期不得读配置(出 spec 路径不跑 config 槽位)
|
|
52
|
+
configuration: {
|
|
53
|
+
readSiteName: () => ctx.config.siteName,
|
|
54
|
+
readCurrency: () => ctx.config.currency,
|
|
55
|
+
},
|
|
56
|
+
ledger: {
|
|
57
|
+
init: () => {
|
|
58
|
+
lifecycleEvents.push('init:ledger');
|
|
59
|
+
},
|
|
60
|
+
dispose: () => {
|
|
61
|
+
lifecycleEvents.push('dispose:ledger');
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
repository: {
|
|
65
|
+
init: () => {
|
|
66
|
+
lifecycleEvents.push('init:repository');
|
|
67
|
+
},
|
|
68
|
+
dispose: () => {
|
|
69
|
+
lifecycleEvents.push('dispose:repository');
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
mailer: {
|
|
73
|
+
init: () => {
|
|
74
|
+
lifecycleEvents.push('init:mailer');
|
|
75
|
+
},
|
|
76
|
+
dispose: () => {
|
|
77
|
+
lifecycleEvents.push('dispose:mailer');
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
databaseLifecycle: {
|
|
81
|
+
dispose: () => {
|
|
82
|
+
counters.disposeDatabase += 1;
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
// 默认预设 [cors, context, auth]
|
|
88
|
+
middlewares: (ctx) => defaultMiddlewares(ctx),
|
|
89
|
+
routers: (ctx) => {
|
|
90
|
+
ctx.app.get('/ledger/summary', { schema: summarySchema }, () => ({
|
|
91
|
+
siteName: ctx.services.configuration.readSiteName(),
|
|
92
|
+
currency: ctx.services.configuration.readCurrency(),
|
|
93
|
+
}));
|
|
94
|
+
ctx.app.get('/ledger/invalid-token', () => {
|
|
95
|
+
throw new UserError('INVALID_TOKEN', undefined, '令牌无效或已过期');
|
|
96
|
+
});
|
|
97
|
+
ctx.app.get('/ledger/database-down', () => {
|
|
98
|
+
throw new SystemError('DATABASE_UNAVAILABLE', undefined, '数据库暂不可用');
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
const app = await startServer(server, { listen: false });
|
|
103
|
+
// ── 装配期:声明序 init ────────────────────────────────────────────
|
|
104
|
+
expect(lifecycleEvents).toEqual(['init:ledger', 'init:repository', 'init:mailer']);
|
|
105
|
+
expect(counters.readConfiguration).toBe(1);
|
|
106
|
+
try {
|
|
107
|
+
// ── 业务路由:成功信封 ────────────────────────────────────────────
|
|
108
|
+
const summaryResponse = await app.inject({
|
|
109
|
+
method: 'GET',
|
|
110
|
+
url: '/ledger/summary?currency=CNY',
|
|
111
|
+
headers: { 'x-request-id': 'smoke-summary-1' },
|
|
112
|
+
});
|
|
113
|
+
expect(summaryResponse.statusCode).toBe(200);
|
|
114
|
+
expect(summaryResponse.json()).toEqual({
|
|
115
|
+
status: 'SUCCESS',
|
|
116
|
+
message: 'ok',
|
|
117
|
+
data: { siteName: 'taicode-smoke', currency: 'CNY' },
|
|
118
|
+
});
|
|
119
|
+
expect(summaryResponse.headers['x-request-id']).toBe('smoke-summary-1');
|
|
120
|
+
// ── UserError → 400 ───────────────────────────────────────────────
|
|
121
|
+
const userErrorResponse = await app.inject({ method: 'GET', url: '/ledger/invalid-token' });
|
|
122
|
+
expect(userErrorResponse.statusCode).toBe(400);
|
|
123
|
+
expect(userErrorResponse.json()).toEqual({
|
|
124
|
+
status: 'INVALID_TOKEN',
|
|
125
|
+
message: '令牌无效或已过期',
|
|
126
|
+
data: null,
|
|
127
|
+
});
|
|
128
|
+
// ── SystemError → 500 ─────────────────────────────────────────────
|
|
129
|
+
const systemErrorResponse = await app.inject({ method: 'GET', url: '/ledger/database-down' });
|
|
130
|
+
expect(systemErrorResponse.statusCode).toBe(500);
|
|
131
|
+
expect(systemErrorResponse.json()).toEqual({
|
|
132
|
+
status: 'DATABASE_UNAVAILABLE',
|
|
133
|
+
message: '数据库暂不可用',
|
|
134
|
+
data: null,
|
|
135
|
+
});
|
|
136
|
+
// ── 未匹配路由 → 404 ──────────────────────────────────────────────
|
|
137
|
+
const notFoundResponse = await app.inject({ method: 'GET', url: '/no-such-route' });
|
|
138
|
+
expect(notFoundResponse.statusCode).toBe(404);
|
|
139
|
+
expect(notFoundResponse.json()).toMatchObject({ status: 'NOT_IMPLEMENTED', data: null });
|
|
140
|
+
expect(notFoundResponse.headers['x-request-id']).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
|
|
141
|
+
expect(notFoundResponse.json()).not.toHaveProperty('requestId');
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
await app.close();
|
|
145
|
+
}
|
|
146
|
+
// ── 关闭:services 逆序 dispose ─────────────────────────────────────
|
|
147
|
+
expect(lifecycleEvents).toEqual([
|
|
148
|
+
'init:ledger',
|
|
149
|
+
'init:repository',
|
|
150
|
+
'init:mailer',
|
|
151
|
+
'dispose:mailer',
|
|
152
|
+
'dispose:repository',
|
|
153
|
+
'dispose:ledger',
|
|
154
|
+
]);
|
|
155
|
+
expect(counters.disposeDatabase).toBe(1);
|
|
156
|
+
// ── 同一份定义 → generateOpenApi:不跑 config、不重跑 init ──────────
|
|
157
|
+
const spec = (await generateOpenApi(server));
|
|
158
|
+
expect(counters.readConfiguration).toBe(1);
|
|
159
|
+
expect(lifecycleEvents).toHaveLength(6);
|
|
160
|
+
expect(spec.openapi).toBe('3.0.3');
|
|
161
|
+
expect(spec.info).toMatchObject({ title: 'Smoke Service', version: '1.4.0' });
|
|
162
|
+
expect(Object.keys(spec.paths ?? {})).toEqual(expect.arrayContaining(['/ledger/summary', '/ledger/invalid-token', '/ledger/database-down']));
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
if (previousDatabaseUrl === undefined) {
|
|
166
|
+
delete process.env.DATABASE_URL;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
process.env.DATABASE_URL = previousDatabaseUrl;
|
|
170
|
+
}
|
|
171
|
+
if (previousRedisUrl === undefined) {
|
|
172
|
+
delete process.env.REDIS_URL;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
process.env.REDIS_URL = previousRedisUrl;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
it('非预置原生插件可在 routers(ctx) 里经 ctx.app.register() 注册并生效', async () => {
|
|
180
|
+
const smokeDecoratorPlugin = async (app) => {
|
|
181
|
+
app.decorate('smokeDecorator', () => 'smoke-decorated');
|
|
182
|
+
app.get('/plugin/decorated', () => ({
|
|
183
|
+
value: app.smokeDecorator(),
|
|
184
|
+
}));
|
|
185
|
+
};
|
|
186
|
+
const server = defineServer({
|
|
187
|
+
app: { service: 'smoke-plugin', title: 'Smoke Plugin', version: '1.0.0' },
|
|
188
|
+
resources: () => ({}),
|
|
189
|
+
config: () => ({}),
|
|
190
|
+
services: () => ({}),
|
|
191
|
+
middlewares: () => [],
|
|
192
|
+
routers: (ctx) => {
|
|
193
|
+
void ctx.app.register(smokeDecoratorPlugin);
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
const app = await startServer(server, { listen: false });
|
|
197
|
+
try {
|
|
198
|
+
const response = await app.inject({ method: 'GET', url: '/plugin/decorated' });
|
|
199
|
+
expect(response.statusCode).toBe(200);
|
|
200
|
+
expect(response.json()).toEqual({
|
|
201
|
+
status: 'SUCCESS',
|
|
202
|
+
message: 'ok',
|
|
203
|
+
data: { value: 'smoke-decorated' },
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
await app.close();
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ConfigEnv } from './env.js';
|
|
2
|
+
import type { LoggerLike } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* 测试公共助手(仅测试使用)。
|
|
5
|
+
*
|
|
6
|
+
* 只放「多个测试文件都要用」的小工具:记录型 logger、测试用 env。
|
|
7
|
+
* 断言与业务语义一律写在各自 `*.test.ts` 内,避免助手文件成为第二真相源。
|
|
8
|
+
*/
|
|
9
|
+
export interface LogEntry {
|
|
10
|
+
level: 'debug' | 'info' | 'warn' | 'error';
|
|
11
|
+
obj: unknown;
|
|
12
|
+
msg?: string;
|
|
13
|
+
}
|
|
14
|
+
/** 记录型 logger:满足 `LoggerLike`,便于断言「被调用了 / 在什么级别」 */
|
|
15
|
+
export interface RecordingLogger extends LoggerLike {
|
|
16
|
+
readonly entries: LogEntry[];
|
|
17
|
+
}
|
|
18
|
+
export declare function createRecordingLogger(): RecordingLogger;
|
|
19
|
+
/** 测试用 `ConfigEnv`(固定读取约定:`ENV` / `API_PORT` / `LOG_LEVEL` / `DATABASE_URL` / `REDIS_URL`) */
|
|
20
|
+
export declare function createTestConfigEnv(overrides?: Partial<ConfigEnv>): ConfigEnv;
|
|
21
|
+
//# sourceMappingURL=test-support.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-support.d.ts","sourceRoot":"","sources":["../../source/app/test-support.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C;;;;;GAKG;AAEH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;IAC1C,GAAG,EAAE,OAAO,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,oDAAoD;AACpD,MAAM,WAAW,eAAgB,SAAQ,UAAU;IACjD,QAAQ,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAA;CAC7B;AAED,wBAAgB,qBAAqB,IAAI,eAAe,CAcvD;AAED,8FAA8F;AAC9F,wBAAgB,mBAAmB,CAAC,SAAS,GAAE,OAAO,CAAC,SAAS,CAAM,GAAG,SAAS,CASjF"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function createRecordingLogger() {
|
|
2
|
+
const entries = [];
|
|
3
|
+
const record = (level) => (obj, msg) => {
|
|
4
|
+
entries.push({ level, obj, msg });
|
|
5
|
+
};
|
|
6
|
+
return {
|
|
7
|
+
entries,
|
|
8
|
+
debug: record('debug'),
|
|
9
|
+
info: record('info'),
|
|
10
|
+
warn: record('warn'),
|
|
11
|
+
error: record('error'),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/** 测试用 `ConfigEnv`(固定读取约定:`ENV` / `API_PORT` / `LOG_LEVEL` / `DATABASE_URL` / `REDIS_URL`) */
|
|
15
|
+
export function createTestConfigEnv(overrides = {}) {
|
|
16
|
+
return {
|
|
17
|
+
ENV: 'test',
|
|
18
|
+
API_PORT: '',
|
|
19
|
+
LOG_LEVEL: 'info',
|
|
20
|
+
DATABASE_URL: '',
|
|
21
|
+
REDIS_URL: undefined,
|
|
22
|
+
...overrides,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 框架统一提供的类型增强(设计 §8「类型增强」红线)。
|
|
3
|
+
*
|
|
4
|
+
* `request.user` / `request.token` / `request.id` 与 `routeOptions.config.accessControls`(以及
|
|
5
|
+
* 限流规则 `rateLimit`)的 `declare module 'fastify'` 由 common 统一提供,各仓删除自建声明。
|
|
6
|
+
*/
|
|
7
|
+
import type { RequestContext } from '../context/index.js';
|
|
8
|
+
/** 路由级访问控制声明(各服务在 `routeOptions.config.accessControls` 里写) */
|
|
9
|
+
export type AccessControl = 'NoLoginRequired' | 'IgnorePermission' | 'RequiredSecurityCode';
|
|
10
|
+
/** 路由级限流规则(`createRateLimitMiddleware` 消费,另有路由级 `config.rateLimit`) */
|
|
11
|
+
export interface RateLimitRule {
|
|
12
|
+
limit: number;
|
|
13
|
+
/** 窗口大小(秒) */
|
|
14
|
+
window: number;
|
|
15
|
+
/** 限流键维度,默认 `['ip']` */
|
|
16
|
+
keys?: string[];
|
|
17
|
+
}
|
|
18
|
+
declare module 'fastify' {
|
|
19
|
+
interface FastifyRequest {
|
|
20
|
+
/** 认证后由 `auth` 中间件填充 */
|
|
21
|
+
user?: unknown;
|
|
22
|
+
/** 认证后由 `auth` 中间件填充 */
|
|
23
|
+
token?: unknown;
|
|
24
|
+
/** `createContextMiddleware()` 注入的请求级上下文 */
|
|
25
|
+
ctx?: RequestContext;
|
|
26
|
+
}
|
|
27
|
+
interface FastifyContextConfig {
|
|
28
|
+
/** 路由级访问控制(`NoLoginRequired` / `IgnorePermission` / `RequiredSecurityCode`) */
|
|
29
|
+
accessControls?: AccessControl[];
|
|
30
|
+
/** 路由级限流规则 */
|
|
31
|
+
rateLimit?: RateLimitRule;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=type-augment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type-augment.d.ts","sourceRoot":"","sources":["../../source/app/type-augment.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAEzD,8DAA8D;AAC9D,MAAM,MAAM,aAAa,GAAG,iBAAiB,GAAG,kBAAkB,GAAG,sBAAsB,CAAA;AAE3F,uEAAuE;AACvE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,cAAc;IACd,MAAM,EAAE,MAAM,CAAA;IACd,wBAAwB;IACxB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;AAED,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,cAAc;QACtB,wBAAwB;QACxB,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,wBAAwB;QACxB,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,4CAA4C;QAC5C,GAAG,CAAC,EAAE,cAAc,CAAA;KACrB;IAED,UAAU,oBAAoB;QAC5B,+EAA+E;QAC/E,cAAc,CAAC,EAAE,aAAa,EAAE,CAAA;QAChC,cAAc;QACd,SAAS,CAAC,EAAE,aAAa,CAAA;KAC1B;CACF"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import type { Disposer } from '@taicode/common-base';
|
|
2
|
+
import type { FastifyBaseLogger, FastifyInstance, RawServerDefault } from 'fastify';
|
|
3
|
+
import type { ZodTypeProvider } from 'fastify-type-provider-zod';
|
|
4
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
5
|
+
import type { ConfigEnv } from './env.js';
|
|
6
|
+
/**
|
|
7
|
+
* `defineServer` 槽位口径与接口类型(N1 / N2,**唯一规格来源**)。
|
|
8
|
+
*
|
|
9
|
+
* 顶层是「一个身份块 + 五个工厂槽位」,**键顺序即分层**:
|
|
10
|
+
*
|
|
11
|
+
* ```
|
|
12
|
+
* app (身份块,无依赖)
|
|
13
|
+
* resources ← env 引导期:唯一读 env 的地方
|
|
14
|
+
* config ← env + resources 引导期:❗不给 services(结构性解耦)
|
|
15
|
+
* services ← resources + config 运行期开始:❗不给 env、不给 services
|
|
16
|
+
* middlewares ← app + services + config (不给 resources / env)
|
|
17
|
+
* routers ← app + services (不给 resources / env / config)
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* 槽位只能引用**排在它前面**的产物(向下引用是编译错误);`env` 只属引导期
|
|
21
|
+
* (`resources` / `config` 可见,运行期三层一律看不到)。
|
|
22
|
+
*
|
|
23
|
+
* 类型安全机制(N2 §1):
|
|
24
|
+
* - **M1 分阶段 ctx**:每个工厂只拿到「该阶段该看的字段」;
|
|
25
|
+
* - **M2 `Awaited<>` 归一**:工厂签名写 `(ctx) => R`(不是 `R | Promise<R>`),下游一律
|
|
26
|
+
* `Awaited<R>` / `Awaited<C>` / `Awaited<S>`;
|
|
27
|
+
* - **M3 资源嵌套**:资源包挂在 `ctx.resources` 上(不再摊平到 ctx,键冲突从根上消失);
|
|
28
|
+
* - **M4 `Live<T>` 静态开关**:资源包里值为 `false` 的键在类型上剔除;
|
|
29
|
+
* - **M5 强类型 `services`**:`middlewares` / `routers` 的 `ctx.services` 是 `S` 实体;
|
|
30
|
+
* - **M6 品牌 + 类型守卫**:`Server` 带品牌,`isServer()` 收窄;
|
|
31
|
+
* - **M7 负向类型测试**:`define-server.negative-cases.ts`,由 `tsc --noEmit` 覆盖。
|
|
32
|
+
*
|
|
33
|
+
* 命名全拼:`requestId` / `database` / `configuration` / `service`(不用 `db` / `cfg`)。
|
|
34
|
+
*/
|
|
35
|
+
/** 骨架只按 `(obj, msg)` 形态打日志;`app.log` 与 `@taicode/common-base` 的 `Logger` 都满足 */
|
|
36
|
+
export interface LoggerLike {
|
|
37
|
+
debug: (obj: unknown, msg?: string) => void;
|
|
38
|
+
info: (obj: unknown, msg?: string) => void;
|
|
39
|
+
warn: (obj: unknown, msg?: string) => void;
|
|
40
|
+
error: (obj: unknown, msg?: string) => void;
|
|
41
|
+
}
|
|
42
|
+
/** 同步或异步(`Awaited<>` 归一的原料) */
|
|
43
|
+
export type MaybePromise<T> = T | Promise<T>;
|
|
44
|
+
/**
|
|
45
|
+
* 路由级类型安全的载体:带 `ZodTypeProvider` 的 `FastifyInstance`。
|
|
46
|
+
*
|
|
47
|
+
* 裸 `FastifyInstance` 会让路由的 `params` / `body` / `query` 全是 `unknown`;带上 provider 后
|
|
48
|
+
* `request.params.id` 有类型。
|
|
49
|
+
*/
|
|
50
|
+
export type AppInstance = FastifyInstance<RawServerDefault, IncomingMessage, ServerResponse, FastifyBaseLogger, ZodTypeProvider>;
|
|
51
|
+
/**
|
|
52
|
+
* 身份块:非 env 派生,出 spec 路径也要用。
|
|
53
|
+
*
|
|
54
|
+
* `version` **必填、无默认值**(缺它就是编译错误,杜绝「悄悄兜底成 1.0.0」)。
|
|
55
|
+
*/
|
|
56
|
+
export interface AppIdentity {
|
|
57
|
+
/** 权限注册键 / cache 前缀 / 失效通道前缀 / 日志标签 */
|
|
58
|
+
readonly service: string;
|
|
59
|
+
readonly title: string;
|
|
60
|
+
/** openapi `info.version` + 权限操作自注册;建议从 package.json 读入 */
|
|
61
|
+
readonly version: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Redis 句柄(惰性包装)。
|
|
65
|
+
*
|
|
66
|
+
* 框架**不操作** Redis 命令,只持有并在关闭时透传 dispose;构造时不建连、不 ping。
|
|
67
|
+
* 因此保持开放类型,各仓注入自己的客户端实现不受约束。
|
|
68
|
+
*/
|
|
69
|
+
export interface RedisHandle {
|
|
70
|
+
client: unknown;
|
|
71
|
+
dispose: () => void | Promise<void>;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 剔掉值为 `false` 的键(编译期静态开关)。
|
|
75
|
+
*
|
|
76
|
+
* 资源包里写 `cache: false` 表示**静态关闭**:`Live<T>` 里该键**不存在**,访问直接编译失败;
|
|
77
|
+
* 而 `cache: Cache | undefined`(显式可选)表示**运行时缺省**:键存在,类型为 `Cache | undefined`。
|
|
78
|
+
*/
|
|
79
|
+
export type Live<T> = {
|
|
80
|
+
[K in keyof T as T[K] extends false ? never : K]: T[K];
|
|
81
|
+
};
|
|
82
|
+
/** 框架在 ctx 上暴露的资源视图(`Awaited<>` 归一 + `false` 键剔除) */
|
|
83
|
+
export type ResourcesView<R> = Live<Awaited<R>>;
|
|
84
|
+
/**
|
|
85
|
+
* `resources(ctx)` 的入参:**只有 `env` + `log`**。
|
|
86
|
+
*
|
|
87
|
+
* 结构上排除 `config` / `services` / `app` → 「资源依赖配置」这类循环**编译期写不出来**。
|
|
88
|
+
*/
|
|
89
|
+
export interface ResourcesContext {
|
|
90
|
+
readonly env: ConfigEnv;
|
|
91
|
+
readonly log: LoggerLike;
|
|
92
|
+
}
|
|
93
|
+
export type ResourcesFactory<R> = (ctx: ResourcesContext) => R;
|
|
94
|
+
/**
|
|
95
|
+
* `config(ctx)` 的入参:`env` + `log` + 资源视图。
|
|
96
|
+
*
|
|
97
|
+
* **没有** `services`(此时尚未构造)/ `app`(此时尚未装配)。
|
|
98
|
+
*/
|
|
99
|
+
export interface ConfigContext<R> {
|
|
100
|
+
readonly env: ConfigEnv;
|
|
101
|
+
readonly log: LoggerLike;
|
|
102
|
+
readonly resources: ResourcesView<R>;
|
|
103
|
+
}
|
|
104
|
+
export type ConfigFactory<R, C> = (ctx: ConfigContext<R>) => C;
|
|
105
|
+
/**
|
|
106
|
+
* 一个 service 值:可选实现 `init` / `dispose`。
|
|
107
|
+
*
|
|
108
|
+
* **没有 `name` / `dependsOn`**:名字用对象键,依赖不存在(`services` ctx 没有 `services` 字段,
|
|
109
|
+
* 编译期禁止互依)。顺序语义只有一条:按**对象声明序** `init` / **逆序** `dispose`。
|
|
110
|
+
*/
|
|
111
|
+
export interface ServiceUnit {
|
|
112
|
+
init?(): void | Promise<void>;
|
|
113
|
+
dispose?(): void | Promise<void>;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* `services(ctx)` 的入参:`log` + `disposer` + `resources` + `config`。
|
|
117
|
+
*
|
|
118
|
+
* ❗**故意没有 `env`**(服务层属运行期,不得读 env);❗**故意没有 `services`**(禁 service 互依)。
|
|
119
|
+
*/
|
|
120
|
+
export interface ServiceContext<R, C> {
|
|
121
|
+
readonly log: LoggerLike;
|
|
122
|
+
readonly disposer: Disposer;
|
|
123
|
+
readonly resources: ResourcesView<R>;
|
|
124
|
+
readonly config: Awaited<C>;
|
|
125
|
+
}
|
|
126
|
+
export type ServicesFactory<R, C, S> = (ctx: ServiceContext<R, C>) => S;
|
|
127
|
+
/**
|
|
128
|
+
* 中间件单元:一个注册函数(`register(app)`),按**数组序**注册。
|
|
129
|
+
*
|
|
130
|
+
* `name` 仅用于报错定位 / 日志跟踪;**不参与生命周期**(要常驻资源就放 `services`)。
|
|
131
|
+
*/
|
|
132
|
+
export interface MiddlewareUnit {
|
|
133
|
+
readonly name?: string;
|
|
134
|
+
register(app: AppInstance): void | Promise<void>;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* `middlewares(ctx)` 的入参:`app` + `log` + `services` + `config`。
|
|
138
|
+
*
|
|
139
|
+
* ❗**故意没有 `resources` / `env`** —— 中间件要访问数据层必须穿过 service。
|
|
140
|
+
*/
|
|
141
|
+
export interface MiddlewareContext<C, S> {
|
|
142
|
+
readonly app: AppInstance;
|
|
143
|
+
readonly log: LoggerLike;
|
|
144
|
+
readonly services: Awaited<S>;
|
|
145
|
+
readonly config: Awaited<C>;
|
|
146
|
+
}
|
|
147
|
+
export type MiddlewaresFactory<C, S> = (ctx: MiddlewareContext<C, S>) => MiddlewareUnit[];
|
|
148
|
+
/**
|
|
149
|
+
* `routers(ctx)` 的入参:`app` + `log` + `services`。
|
|
150
|
+
*
|
|
151
|
+
* ❗**故意没有 `resources` / `env` / `config`** —— 路由**只能穿过 service**,绕不过层直连数据层。
|
|
152
|
+
*/
|
|
153
|
+
export interface RouterContext<S> {
|
|
154
|
+
readonly app: AppInstance;
|
|
155
|
+
readonly log: LoggerLike;
|
|
156
|
+
readonly services: Awaited<S>;
|
|
157
|
+
}
|
|
158
|
+
export type RoutersFactory<S> = (ctx: RouterContext<S>) => void | Promise<void>;
|
|
159
|
+
/**
|
|
160
|
+
* 若某个 service 值声明了 `dependsOn`,该值类型塌成 `never` → 赋值报错。
|
|
161
|
+
*/
|
|
162
|
+
export type AssertNoDependencies<S> = {
|
|
163
|
+
[K in keyof S]: S[K] extends {
|
|
164
|
+
dependsOn: unknown;
|
|
165
|
+
} ? never : S[K];
|
|
166
|
+
};
|
|
167
|
+
/**
|
|
168
|
+
* `services` 返回值形状闸门:
|
|
169
|
+
* - 原始类型值(`string` / `number` / `boolean`)**拒绝**(防把配置塞进 services);
|
|
170
|
+
* - 声明了 `dependsOn` 的值**拒绝**(禁 service 互依)。
|
|
171
|
+
*/
|
|
172
|
+
export type ServicesShape<S> = {
|
|
173
|
+
[K in keyof S]: S[K] extends string | number | boolean ? never : S[K] extends {
|
|
174
|
+
dependsOn: unknown;
|
|
175
|
+
} ? never : S[K];
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* `defineServer()` 入参:一个身份块 + 五个工厂槽位(键顺序=分层)。
|
|
179
|
+
*
|
|
180
|
+
* `S` 上的两道闸门(`ServicesShape<S>`,N2 §2.5)写在 `services` 的返回类型上:
|
|
181
|
+
* `S & ServicesShape<S>` —— 形状合法时恒等,非法时该值塌成 `never` → 编译失败。
|
|
182
|
+
*/
|
|
183
|
+
export interface ServerConfig<R, C, S> {
|
|
184
|
+
app: AppIdentity;
|
|
185
|
+
resources: ResourcesFactory<R>;
|
|
186
|
+
config: ConfigFactory<NoInfer<R>, C>;
|
|
187
|
+
services: (ctx: ServiceContext<NoInfer<R>, NoInfer<C>>) => S & ServicesShape<S>;
|
|
188
|
+
middlewares: MiddlewaresFactory<NoInfer<C>, NoInfer<S>>;
|
|
189
|
+
routers: RoutersFactory<NoInfer<S>>;
|
|
190
|
+
}
|
|
191
|
+
/** 品牌字段值(`Server` 的 `__brand`) */
|
|
192
|
+
export declare const SERVER_BRAND = "taicode.server";
|
|
193
|
+
/** 纯定义产物:同一份定义可同时喂给「启动」与「出 spec」 */
|
|
194
|
+
export type Server<R, C, S> = ServerConfig<R, C, S> & {
|
|
195
|
+
readonly __brand: typeof SERVER_BRAND;
|
|
196
|
+
};
|
|
197
|
+
/** `startServer()` 的逃生选项 */
|
|
198
|
+
export interface StartServerOptions<R = unknown, C = unknown, S = unknown> {
|
|
199
|
+
/** 是否 listen + 装信号;默认 `NODE_ENV !== 'test'` */
|
|
200
|
+
listen?: boolean;
|
|
201
|
+
/** 整体覆盖关闭流程 */
|
|
202
|
+
shutdown?: (ctx: ShutdownContext<R, C, S>) => Promise<void>;
|
|
203
|
+
}
|
|
204
|
+
/** 关闭阶段视图:`services` ctx + `app` + `services` 实体 */
|
|
205
|
+
export interface ShutdownContext<R, C, S> extends ServiceContext<R, C> {
|
|
206
|
+
readonly app: AppInstance;
|
|
207
|
+
readonly services: Awaited<S>;
|
|
208
|
+
}
|
|
209
|
+
/** `generateOpenApi()` 的返回(OpenAPI 文档对象) */
|
|
210
|
+
export type OpenApiDocument = Record<string, unknown>;
|
|
211
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../source/app/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taicode/common-server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-beta.1",
|
|
4
4
|
"author": "Alain",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"description": "",
|
|
@@ -23,11 +23,19 @@
|
|
|
23
23
|
"files": [
|
|
24
24
|
"output"
|
|
25
25
|
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@fastify/cookie": "^11",
|
|
28
|
+
"@fastify/cors": "^11",
|
|
29
|
+
"@fastify/swagger": "^9",
|
|
30
|
+
"@scalar/fastify-api-reference": "^1",
|
|
31
|
+
"dotenv": "^17.3.1",
|
|
32
|
+
"fastify-type-provider-zod": "^6",
|
|
33
|
+
"zod": "^4.3.6"
|
|
34
|
+
},
|
|
26
35
|
"peerDependencies": {
|
|
27
36
|
"fastify": "^5.5.0",
|
|
28
37
|
"redis": "^5.11.0",
|
|
29
|
-
"
|
|
30
|
-
"@taicode/common-base": "^3.7.13"
|
|
38
|
+
"@taicode/common-base": "^4.0.0-beta.1"
|
|
31
39
|
},
|
|
32
40
|
"devDependencies": {
|
|
33
41
|
"@eslint/js": "^9.39.3",
|
|
@@ -41,8 +49,7 @@
|
|
|
41
49
|
"redis": "^5.11.0",
|
|
42
50
|
"typescript-eslint": "^8.56.1",
|
|
43
51
|
"vitest": "^3.2.4",
|
|
44
|
-
"
|
|
45
|
-
"@taicode/common-base": "^3.7.13"
|
|
52
|
+
"@taicode/common-base": "^4.0.0-beta.1"
|
|
46
53
|
},
|
|
47
54
|
"scripts": {
|
|
48
55
|
"test": "vitest run",
|