@taicode/common-server 3.8.0 → 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.js +86 -288
- 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 +39 -4
- package/output/app/env.d.ts.map +1 -1
- package/output/app/env.js +32 -4
- package/output/app/env.test.js +67 -21
- 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 +15 -15
- package/output/app/index.d.ts.map +1 -1
- package/output/app/index.js +18 -9
- 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 +17 -4
- package/output/app/shutdown.d.ts.map +1 -1
- package/output/app/shutdown.js +27 -12
- package/output/app/shutdown.test.js +78 -124
- package/output/app/signal.d.ts +4 -4
- package/output/app/signal.d.ts.map +1 -1
- package/output/app/signal.js +3 -3
- 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 +185 -123
- package/output/app/types.d.ts.map +1 -1
- package/output/app/types.js +2 -1
- package/package.json +10 -6
- package/output/app/assemble.d.ts +0 -16
- package/output/app/assemble.d.ts.map +0 -1
- package/output/app/assemble.js +0 -124
- package/output/app/database.d.ts +0 -36
- package/output/app/database.d.ts.map +0 -1
- package/output/app/database.js +0 -40
- package/output/app/database.test.d.ts +0 -2
- package/output/app/database.test.d.ts.map +0 -1
- package/output/app/database.test.js +0 -66
- package/output/app/default-modules.d.ts +0 -23
- package/output/app/default-modules.d.ts.map +0 -1
- package/output/app/default-modules.js +0 -40
- package/output/app/define-server.d.ts +0 -12
- package/output/app/define-server.d.ts.map +0 -1
- package/output/app/define-server.js +0 -289
- package/output/app/module.d.ts +0 -88
- package/output/app/module.d.ts.map +0 -1
- package/output/app/module.js +0 -34
- package/output/app/steps.d.ts +0 -17
- package/output/app/steps.d.ts.map +0 -1
- package/output/app/steps.js +0 -41
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { defineServer, startServer } from './entries.js';
|
|
2
|
+
/**
|
|
3
|
+
* N2 §4 的 **15 条负向用例**:这些写法**必须编译失败**,全部由 `tsc --noEmit` 覆盖
|
|
4
|
+
* (**不进**运行期 vitest 断言)。
|
|
5
|
+
*
|
|
6
|
+
* 文件名刻意**不带** `.test.ts`:它由 `tsc --noEmit` 校验,不被 vitest 当作测试文件执行。
|
|
7
|
+
* 每个用例的 `@ts-expect-error` 必须**确有错误**,否则 tsc 会报「未使用」而失败。
|
|
8
|
+
*
|
|
9
|
+
* 说明:`defineServer` 的调用结果一律不用 —— 这些函数只作编译期证据。
|
|
10
|
+
*/
|
|
11
|
+
const identity = { service: 'negative-cases', title: 'Negative Cases', version: '1.0.0' };
|
|
12
|
+
/** #1 `resources` 看不到 `config` */
|
|
13
|
+
export function resourcesCannotSeeConfig() {
|
|
14
|
+
defineServer({
|
|
15
|
+
app: identity,
|
|
16
|
+
// @ts-expect-error `config` 不存在于 ResourcesContext
|
|
17
|
+
resources: ({ config }) => ({ config }),
|
|
18
|
+
config: () => ({}),
|
|
19
|
+
services: () => ({}),
|
|
20
|
+
middlewares: () => [],
|
|
21
|
+
routers: () => undefined,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/** #2 `resources` 看不到 `services` */
|
|
25
|
+
export function resourcesCannotSeeServices() {
|
|
26
|
+
defineServer({
|
|
27
|
+
app: identity,
|
|
28
|
+
// @ts-expect-error `services` 不存在于 ResourcesContext
|
|
29
|
+
resources: ({ services }) => ({ services }),
|
|
30
|
+
config: () => ({}),
|
|
31
|
+
services: () => ({}),
|
|
32
|
+
middlewares: () => [],
|
|
33
|
+
routers: () => undefined,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/** #3 `config` 看不到 `services` */
|
|
37
|
+
export function configCannotSeeServices() {
|
|
38
|
+
defineServer({
|
|
39
|
+
app: identity,
|
|
40
|
+
resources: () => ({}),
|
|
41
|
+
config: (ctx) => {
|
|
42
|
+
// @ts-expect-error `services` 不存在于 ConfigContext
|
|
43
|
+
void ctx.services;
|
|
44
|
+
return {};
|
|
45
|
+
},
|
|
46
|
+
services: () => ({}),
|
|
47
|
+
middlewares: () => [],
|
|
48
|
+
routers: () => undefined,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/** #4 `services` 看不到 `services`(禁 service 互依) */
|
|
52
|
+
export function servicesCannotSeeServices() {
|
|
53
|
+
defineServer({
|
|
54
|
+
app: identity,
|
|
55
|
+
resources: () => ({}),
|
|
56
|
+
config: () => ({}),
|
|
57
|
+
services: (ctx) => {
|
|
58
|
+
// @ts-expect-error `services` 不存在于 ServiceContext(禁互依)
|
|
59
|
+
void ctx.services;
|
|
60
|
+
return {};
|
|
61
|
+
},
|
|
62
|
+
middlewares: () => [],
|
|
63
|
+
routers: () => undefined,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/** #5 `routers` 看不到 `resources`(禁绕层直连数据层) */
|
|
67
|
+
export function routersCannotSeeResources() {
|
|
68
|
+
defineServer({
|
|
69
|
+
app: identity,
|
|
70
|
+
resources: () => ({ database: {} }),
|
|
71
|
+
config: () => ({}),
|
|
72
|
+
services: () => ({}),
|
|
73
|
+
middlewares: () => [],
|
|
74
|
+
routers: (ctx) => {
|
|
75
|
+
// @ts-expect-error `resources` 不存在于 RouterContext
|
|
76
|
+
void ctx.resources.database;
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/** #6 `routers` 看不到 `config` */
|
|
81
|
+
export function routersCannotSeeConfig() {
|
|
82
|
+
defineServer({
|
|
83
|
+
app: identity,
|
|
84
|
+
resources: () => ({}),
|
|
85
|
+
config: () => ({}),
|
|
86
|
+
services: () => ({}),
|
|
87
|
+
middlewares: () => [],
|
|
88
|
+
routers: (ctx) => {
|
|
89
|
+
// @ts-expect-error `config` 不存在于 RouterContext
|
|
90
|
+
void ctx.config;
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/** #7 service 值声明 `dependsOn` → 该值塌成 `never` */
|
|
95
|
+
export function serviceValueCannotDeclareDependsOn() {
|
|
96
|
+
defineServer({
|
|
97
|
+
app: identity,
|
|
98
|
+
resources: () => ({}),
|
|
99
|
+
config: () => ({}),
|
|
100
|
+
// @ts-expect-error `dependsOn` 声明使该值塌成 never
|
|
101
|
+
services: () => ({ a: { dependsOn: ['b'] } }),
|
|
102
|
+
middlewares: () => [],
|
|
103
|
+
routers: () => undefined,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/** #8 service 值是原始类型 → 被拒(防把配置塞进 services) */
|
|
107
|
+
export function serviceValueCannotBePrimitive() {
|
|
108
|
+
defineServer({
|
|
109
|
+
app: identity,
|
|
110
|
+
resources: () => ({}),
|
|
111
|
+
config: () => ({}),
|
|
112
|
+
// @ts-expect-error 原始类型值被 ServicesShape 拒绝
|
|
113
|
+
services: () => ({ count: 1 }),
|
|
114
|
+
middlewares: () => [],
|
|
115
|
+
routers: () => undefined,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/** #9 `startServer` 只接受品牌产物,裸 `ServerConfig` 编译失败 */
|
|
119
|
+
export function startServerRejectsBareConfig(bare) {
|
|
120
|
+
// @ts-expect-error 缺 `__brand`,不是 Server
|
|
121
|
+
void startServer(bare);
|
|
122
|
+
}
|
|
123
|
+
/** #10 `app.version` 必填 */
|
|
124
|
+
export function appVersionIsRequired() {
|
|
125
|
+
defineServer({
|
|
126
|
+
// @ts-expect-error `AppIdentity.version` 必填
|
|
127
|
+
app: { service: 'no-version', title: 'No Version' },
|
|
128
|
+
resources: () => ({}),
|
|
129
|
+
config: () => ({}),
|
|
130
|
+
services: () => ({}),
|
|
131
|
+
middlewares: () => [],
|
|
132
|
+
routers: () => undefined,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/** #11 `cache: false` 静态关闭:键被 `Live<>` 从资源视图剔除 */
|
|
136
|
+
export function staticallyDisabledResourceIsRemoved() {
|
|
137
|
+
defineServer({
|
|
138
|
+
app: identity,
|
|
139
|
+
resources: () => ({ cache: false, database: { query: () => '' } }),
|
|
140
|
+
config: (ctx) => {
|
|
141
|
+
// @ts-expect-error `cache: false` 的键已被 `Live<>` 剔除
|
|
142
|
+
void ctx.cache;
|
|
143
|
+
return {};
|
|
144
|
+
},
|
|
145
|
+
services: () => ({}),
|
|
146
|
+
middlewares: () => [],
|
|
147
|
+
routers: () => undefined,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/** #12 `config` 看不到 `app` */
|
|
151
|
+
export function configCannotSeeApp() {
|
|
152
|
+
defineServer({
|
|
153
|
+
app: identity,
|
|
154
|
+
resources: () => ({}),
|
|
155
|
+
config: (ctx) => {
|
|
156
|
+
// @ts-expect-error `app` 不存在于 ConfigContext
|
|
157
|
+
void ctx.app;
|
|
158
|
+
return {};
|
|
159
|
+
},
|
|
160
|
+
services: () => ({}),
|
|
161
|
+
middlewares: () => [],
|
|
162
|
+
routers: () => undefined,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/** #13 `middlewares` 看不到 `resources`(中间件禁直连数据层) */
|
|
166
|
+
export function middlewaresCannotSeeResources() {
|
|
167
|
+
defineServer({
|
|
168
|
+
app: identity,
|
|
169
|
+
resources: () => ({ database: {} }),
|
|
170
|
+
config: () => ({}),
|
|
171
|
+
services: () => ({}),
|
|
172
|
+
middlewares: (ctx) => {
|
|
173
|
+
// @ts-expect-error `resources` 不存在于 MiddlewareContext
|
|
174
|
+
void ctx.resources.database;
|
|
175
|
+
return [];
|
|
176
|
+
},
|
|
177
|
+
routers: () => undefined,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/** #14 `middlewares` 看不到 `env` */
|
|
181
|
+
export function middlewaresCannotSeeEnv() {
|
|
182
|
+
defineServer({
|
|
183
|
+
app: identity,
|
|
184
|
+
resources: () => ({}),
|
|
185
|
+
config: () => ({}),
|
|
186
|
+
services: () => ({}),
|
|
187
|
+
middlewares: (ctx) => {
|
|
188
|
+
// @ts-expect-error `env` 不存在于 MiddlewareContext
|
|
189
|
+
void ctx.env;
|
|
190
|
+
return [];
|
|
191
|
+
},
|
|
192
|
+
routers: () => undefined,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
/** #15 `services` 看不到 `env`(服务层禁读 env) */
|
|
196
|
+
export function servicesCannotSeeEnv() {
|
|
197
|
+
defineServer({
|
|
198
|
+
app: identity,
|
|
199
|
+
resources: () => ({}),
|
|
200
|
+
config: () => ({}),
|
|
201
|
+
services: (ctx) => {
|
|
202
|
+
// @ts-expect-error `env` 不存在于 ServiceContext
|
|
203
|
+
void ctx.env.DATABASE_URL;
|
|
204
|
+
return {};
|
|
205
|
+
},
|
|
206
|
+
middlewares: () => [],
|
|
207
|
+
routers: () => undefined,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
@@ -1,299 +1,97 @@
|
|
|
1
1
|
import { describe, expect, expectTypeOf, it } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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;
|
|
2
|
+
import { defineServer, isServer, SERVER_BRAND, generateOpenApi } from './entries.js';
|
|
3
|
+
/**
|
|
4
|
+
* `defineServer()`(N1 §1)——**纯定义**:产出一个 `Server` 对象,不建连、不起服务。
|
|
5
|
+
*
|
|
6
|
+
* 同一份定义可同时喂给「启动」与「出 spec」,这也是 `mode` 消失的原因。
|
|
7
|
+
*/
|
|
8
|
+
describe('defineServer 纯定义(N1 §1)', () => {
|
|
9
|
+
it('返回带品牌字段的 Server,并原样保留六个槽位', () => {
|
|
10
|
+
let resourcesCalls = 0;
|
|
11
|
+
const config = {
|
|
12
|
+
app: { service: 'define-server-test', title: 'Define Server Test', version: '1.0.0' },
|
|
13
|
+
resources: () => {
|
|
14
|
+
resourcesCalls += 1;
|
|
247
15
|
return {};
|
|
248
16
|
},
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
17
|
+
config: () => ({ enabled: true }),
|
|
18
|
+
services: () => ({}),
|
|
19
|
+
middlewares: () => [],
|
|
20
|
+
routers: () => undefined,
|
|
21
|
+
};
|
|
22
|
+
const server = defineServer(config);
|
|
23
|
+
expect(server.__brand).toBe(SERVER_BRAND);
|
|
24
|
+
expect(server.app).toEqual({ service: 'define-server-test', title: 'Define Server Test', version: '1.0.0' });
|
|
25
|
+
expect(server.services).toBe(config.services);
|
|
26
|
+
expect(server.routers).toBe(config.routers);
|
|
27
|
+
// 纯定义:不跑任何工厂
|
|
28
|
+
expect(resourcesCalls).toBe(0);
|
|
29
|
+
});
|
|
30
|
+
it('isServer 只识别 defineServer 产物', () => {
|
|
31
|
+
const server = defineServer({
|
|
32
|
+
app: { service: 'is-server-test', title: 'Is Server Test', version: '1.0.0' },
|
|
33
|
+
resources: () => ({}),
|
|
34
|
+
config: () => ({}),
|
|
35
|
+
services: () => ({}),
|
|
36
|
+
middlewares: () => [],
|
|
37
|
+
routers: () => undefined,
|
|
259
38
|
});
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
39
|
+
expect(isServer(server)).toBe(true);
|
|
40
|
+
expect(isServer({ __brand: 'other' })).toBe(false);
|
|
41
|
+
expect(isServer(null)).toBe(false);
|
|
42
|
+
expect(isServer('taicode.server')).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
it('定义产物三入口共用:generateOpenApi 只读定义,不跑 config 槽位', async () => {
|
|
45
|
+
let configCalls = 0;
|
|
46
|
+
const server = defineServer({
|
|
47
|
+
app: { service: 'define-shared', title: 'Define Shared', version: '1.0.0' },
|
|
48
|
+
resources: () => ({}),
|
|
49
|
+
config: () => {
|
|
50
|
+
configCalls += 1;
|
|
271
51
|
return {};
|
|
272
52
|
},
|
|
273
|
-
|
|
53
|
+
services: () => ({}),
|
|
54
|
+
middlewares: () => [],
|
|
55
|
+
routers: () => undefined,
|
|
274
56
|
});
|
|
275
|
-
await
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
57
|
+
await generateOpenApi(server);
|
|
58
|
+
expect(configCalls).toBe(0);
|
|
59
|
+
});
|
|
60
|
+
it('app.version 必填:缺 version 时 TypeScript 编译失败(无默认值)', () => {
|
|
61
|
+
// 纯类型约束:这里只靠 `tsc --noEmit` 校验。若 `version` 又变回可选,
|
|
62
|
+
// `@ts-expect-error` 会因「未使用」而报错,测试反而变红。
|
|
63
|
+
const build = () => ({
|
|
64
|
+
// @ts-expect-error 缺少必填 app.version
|
|
65
|
+
app: { service: 'missing-version', title: 'Missing Version' },
|
|
66
|
+
resources: () => ({}),
|
|
67
|
+
config: () => ({}),
|
|
68
|
+
services: () => ({}),
|
|
69
|
+
middlewares: () => [],
|
|
70
|
+
routers: () => undefined,
|
|
71
|
+
});
|
|
72
|
+
expect(build).toBeTypeOf('function');
|
|
73
|
+
});
|
|
74
|
+
it('R / C / S 全推导:middlewares 与 routers 的 ctx.services 是强类型实体(M5)', () => {
|
|
75
|
+
defineServer({
|
|
76
|
+
app: { service: 'infer', title: 'Infer', version: '1.0.0' },
|
|
77
|
+
resources: () => ({ database: { query: (sql) => sql } }),
|
|
78
|
+
config: ({ resources }) => ({ domain: resources.database.query('domain') }),
|
|
79
|
+
services: ({ resources, config }) => ({
|
|
80
|
+
user: {
|
|
81
|
+
find: (id) => `${config.domain}:${resources.database.query(id)}`,
|
|
82
|
+
},
|
|
83
|
+
}),
|
|
84
|
+
middlewares: (ctx) => {
|
|
85
|
+
// 类型自证:ctx.services 是 services 的返回类型(不是 unknown)
|
|
86
|
+
expectTypeOf(ctx.services.user.find).toEqualTypeOf();
|
|
87
|
+
// 类型自证:ctx.config 是 config 的返回类型
|
|
88
|
+
expectTypeOf(ctx.config.domain).toEqualTypeOf();
|
|
89
|
+
return [];
|
|
284
90
|
},
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const app = await defineServer(createBaseOptions({
|
|
289
|
-
modules: { ...P1_OPT_OUT, database: database },
|
|
290
|
-
createServices: (ctx) => {
|
|
291
|
-
received = ctx.db;
|
|
292
|
-
return {};
|
|
91
|
+
routers: (ctx) => {
|
|
92
|
+
expectTypeOf(ctx.services.user.find).toEqualTypeOf();
|
|
93
|
+
ctx.app.get('/user', () => ({ name: ctx.services.user.find('1') }));
|
|
293
94
|
},
|
|
294
|
-
})
|
|
295
|
-
await app.close();
|
|
296
|
-
expect(created).toBe(1);
|
|
297
|
-
expect(received).toEqual({ tag: 'custom' });
|
|
95
|
+
});
|
|
298
96
|
});
|
|
299
97
|
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { OpenApiDocument, Server, ServerConfig, StartServerOptions } from './types.js';
|
|
2
|
+
import type { FastifyInstance } from 'fastify';
|
|
3
|
+
export { SERVER_BRAND } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* `defineServer()`(N1 §1)——**纯定义**:产出一个 `Server` 对象,不建连、不起服务。
|
|
6
|
+
*
|
|
7
|
+
* 启动是另一个函数(`startServer`)的事——同一份定义可同时喂给「启动」与「出 spec」
|
|
8
|
+
* (`generateOpenApi`)。调用方**不写泛型**:`R` / `C` / `S` 全部从工厂返回类型推导。
|
|
9
|
+
*/
|
|
10
|
+
export declare function defineServer<R, C, S>(config: ServerConfig<R, C, S>): Server<R, C, S>;
|
|
11
|
+
/** 供内部/调试使用:判断是否为 `defineServer()` 产物 */
|
|
12
|
+
export declare function isServer(value: unknown): value is Server<unknown, unknown, unknown>;
|
|
13
|
+
/**
|
|
14
|
+
* `startServer()`——真正跑起来:构造资源、加载配置、建服务、装中间件、注册路由、listen、装信号。
|
|
15
|
+
*
|
|
16
|
+
* - 最早阶段**固定加载 env**(`.env.dev < .env < .env.local`;`ENV=test` 追加 `.env.test.local`)。
|
|
17
|
+
* - 返回**原生 `FastifyInstance`**:不包 `fastify-plugin`、不返回包装对象。
|
|
18
|
+
* - `listen` 默认 `NODE_ENV !== 'test'`(测试不监听)。
|
|
19
|
+
* - 硬性要求 `Server`(品牌)产物:传裸 `ServerConfig` 是编译错误。
|
|
20
|
+
*/
|
|
21
|
+
export declare function startServer<R, C, S>(server: Server<R, C, S>, options?: StartServerOptions<R, C, S>): Promise<FastifyInstance>;
|
|
22
|
+
/**
|
|
23
|
+
* `generateOpenApi()`——只出 spec,跑到能拿到 schema 就停。
|
|
24
|
+
*
|
|
25
|
+
* **红线**:全程**不得要求配置有效** —— 不跑 `config` 槽位、不要求 `DATABASE_URL` / `REDIS_URL`
|
|
26
|
+
* 有效;`resources`(只构造不建连)/ `services` / 中间件注册 / 路由注册照常执行。
|
|
27
|
+
*/
|
|
28
|
+
export declare function generateOpenApi<R, C, S>(server: Server<R, C, S>): Promise<OpenApiDocument>;
|
|
29
|
+
//# sourceMappingURL=entries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../source/app/entries.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC3F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE9C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAEzC;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAMpF;AAED,yCAAyC;AACzC,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAMnF;AAED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,GAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAavI;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC,CAiBhG"}
|