@taicode/common-server 4.0.0-beta.1 → 4.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/output/app/index.d.ts +18 -15
- package/output/app/index.d.ts.map +1 -1
- package/output/app/index.js +330 -18
- package/output/auth/index.d.ts +73 -0
- package/output/auth/index.d.ts.map +1 -0
- package/output/auth/index.js +196 -0
- package/output/core/assemble.d.ts +64 -0
- package/output/core/assemble.d.ts.map +1 -0
- package/output/core/assemble.js +88 -0
- package/output/core/boot/context.d.ts +109 -0
- package/output/core/boot/context.d.ts.map +1 -0
- package/output/core/boot/context.js +145 -0
- package/output/core/boot/lifecycle.d.ts +206 -0
- package/output/core/boot/lifecycle.d.ts.map +1 -0
- package/output/core/boot/lifecycle.js +255 -0
- package/output/core/boot/shutdown.d.ts +34 -0
- package/output/core/boot/shutdown.d.ts.map +1 -0
- package/output/core/boot/shutdown.js +50 -0
- package/output/core/boot/signal.d.ts +13 -0
- package/output/core/boot/signal.d.ts.map +1 -0
- package/output/{app → core/boot}/signal.js +3 -2
- package/output/core/boot/steps.d.ts +138 -0
- package/output/core/boot/steps.d.ts.map +1 -0
- package/output/core/boot/steps.js +259 -0
- package/output/core/entries.d.ts +151 -0
- package/output/core/entries.d.ts.map +1 -0
- package/output/core/entries.js +242 -0
- package/output/core/entries.test.d.ts +2 -0
- package/output/core/entries.test.d.ts.map +1 -0
- package/output/core/entries.test.js +458 -0
- package/output/core/env.d.ts +83 -0
- package/output/core/env.d.ts.map +1 -0
- package/output/core/env.js +124 -0
- package/output/{app → core}/env.test.d.ts.map +1 -1
- package/output/core/env.test.js +105 -0
- package/output/core/modules/auth-provider/index.d.ts +10 -0
- package/output/core/modules/auth-provider/index.d.ts.map +1 -0
- package/output/core/modules/auth-provider/index.js +9 -0
- package/output/core/modules/auth-provider/types.d.ts +71 -0
- package/output/core/modules/auth-provider/types.d.ts.map +1 -0
- package/output/core/modules/context/index.d.ts +18 -0
- package/output/core/modules/context/index.d.ts.map +1 -0
- package/output/core/modules/context/index.js +23 -0
- package/output/core/modules/context/index.test.d.ts.map +1 -0
- package/output/core/modules/cookie/index.d.ts +21 -0
- package/output/core/modules/cookie/index.d.ts.map +1 -0
- package/output/{app/core/cookie.js → core/modules/cookie/index.js} +1 -6
- package/output/core/modules/identity/index.d.ts +4 -0
- package/output/core/modules/identity/index.d.ts.map +1 -0
- package/output/{app/core/identity.js → core/modules/identity/index.js} +1 -1
- package/output/core/modules/logging/index.d.ts +5 -0
- package/output/core/modules/logging/index.d.ts.map +1 -0
- package/output/{app/core/logging.js → core/modules/logging/index.js} +2 -2
- package/output/core/modules/openapi/index.d.ts +7 -0
- package/output/core/modules/openapi/index.d.ts.map +1 -0
- package/output/{app/core/openapi.js → core/modules/openapi/index.js} +11 -12
- package/output/core/modules/permission-operations/index.d.ts +26 -0
- package/output/core/modules/permission-operations/index.d.ts.map +1 -0
- package/output/core/modules/permission-operations/index.js +36 -0
- package/output/core/modules/permission-operations/index.test.d.ts +2 -0
- package/output/core/modules/permission-operations/index.test.d.ts.map +1 -0
- package/output/core/modules/permission-operations/index.test.js +45 -0
- package/output/{app/core/request-id.d.ts → core/modules/request-id/index.d.ts} +1 -4
- package/output/core/modules/request-id/index.d.ts.map +1 -0
- package/output/{app/core/request-id.js → core/modules/request-id/index.js} +2 -2
- package/output/core/modules/response/index.d.ts +50 -0
- package/output/core/modules/response/index.d.ts.map +1 -0
- package/output/{app/core/response.js → core/modules/response/index.js} +31 -18
- package/output/{schema/index.d.ts → core/modules/schema/builders.d.ts} +8 -1
- package/output/core/modules/schema/builders.d.ts.map +1 -0
- package/output/{schema/index.js → core/modules/schema/builders.js} +7 -4
- package/output/core/modules/schema/index.d.ts +34 -0
- package/output/core/modules/schema/index.d.ts.map +1 -0
- package/output/core/modules/schema/index.js +62 -0
- package/output/core/modules/schema/index.test.d.ts +2 -0
- package/output/core/modules/schema/index.test.d.ts.map +1 -0
- package/output/core/modules/schema/index.test.js +55 -0
- package/output/core/modules/system-routes/index.d.ts +15 -0
- package/output/core/modules/system-routes/index.d.ts.map +1 -0
- package/output/core/modules/system-routes/index.js +205 -0
- package/output/core/resource.d.ts +56 -0
- package/output/core/resource.d.ts.map +1 -0
- package/output/core/resource.js +56 -0
- package/output/core/test-support/database.d.ts +89 -0
- package/output/core/test-support/database.d.ts.map +1 -0
- package/output/core/test-support/database.js +131 -0
- package/output/core/test-support/database.test.d.ts +2 -0
- package/output/core/test-support/database.test.d.ts.map +1 -0
- package/output/core/test-support/database.test.js +111 -0
- package/output/core/test-support/environment.d.ts +38 -0
- package/output/core/test-support/environment.d.ts.map +1 -0
- package/output/core/test-support/environment.js +45 -0
- package/output/core/test-support/service-context.d.ts +64 -0
- package/output/core/test-support/service-context.d.ts.map +1 -0
- package/output/core/test-support/service-context.js +63 -0
- package/output/core/test-support/service-context.test.d.ts +2 -0
- package/output/core/test-support/service-context.test.d.ts.map +1 -0
- package/output/core/test-support/service-context.test.js +55 -0
- package/output/core/test-support/test-server.d.ts +154 -0
- package/output/core/test-support/test-server.d.ts.map +1 -0
- package/output/core/test-support/test-server.js +114 -0
- package/output/core/test-support/test-server.test.d.ts +2 -0
- package/output/core/test-support/test-server.test.d.ts.map +1 -0
- package/output/core/test-support/test-server.test.js +296 -0
- package/output/core/test-support/testdata.d.ts +255 -0
- package/output/core/test-support/testdata.d.ts.map +1 -0
- package/output/core/test-support/testdata.js +271 -0
- package/output/core/test-support/testdata.test.d.ts +2 -0
- package/output/core/test-support/testdata.test.d.ts.map +1 -0
- package/output/core/test-support/testdata.test.js +334 -0
- package/output/{app → core}/type-augment.d.ts +2 -2
- package/output/core/type-augment.d.ts.map +1 -0
- package/output/core/types.d.ts +582 -0
- package/output/core/types.d.ts.map +1 -0
- package/output/cors/index.d.ts +3 -0
- package/output/cors/index.d.ts.map +1 -0
- package/output/cors/index.js +28 -0
- package/output/index.d.ts +10 -4
- package/output/index.d.ts.map +1 -1
- package/output/index.js +25 -5
- package/output/rate-limit/index.d.ts +23 -0
- package/output/rate-limit/index.d.ts.map +1 -0
- package/output/rate-limit/index.js +43 -0
- package/output/{rate-limiter/rate-limiter.d.ts → rate-limit/limiter.d.ts} +1 -1
- package/output/rate-limit/limiter.d.ts.map +1 -0
- package/output/{rate-limiter → rate-limit}/memory-store.d.ts +6 -0
- package/output/rate-limit/memory-store.d.ts.map +1 -0
- package/output/{rate-limiter → rate-limit}/memory-store.js +8 -0
- package/output/{rate-limiter → rate-limit}/redis-store.d.ts +18 -0
- package/output/rate-limit/redis-store.d.ts.map +1 -0
- package/output/{rate-limiter → rate-limit}/redis-store.js +35 -0
- package/output/rate-limit/redis-store.test.d.ts.map +1 -0
- package/output/{rate-limiter → rate-limit}/redis-store.test.js +2 -2
- package/output/rate-limit/types.d.ts +45 -0
- package/output/rate-limit/types.d.ts.map +1 -0
- package/output/rate-limit/types.js +1 -0
- package/output/{redis-cache/redis-cache.d.ts → redis/cache.d.ts} +1 -1
- package/output/redis/cache.d.ts.map +1 -0
- package/output/redis/cache.test.d.ts +2 -0
- package/output/redis/cache.test.d.ts.map +1 -0
- package/output/{redis-cache/redis-cache.test.js → redis/cache.test.js} +1 -1
- package/output/{app/redis.d.ts → redis/index.d.ts} +12 -7
- package/output/redis/index.d.ts.map +1 -0
- package/output/{app/redis.js → redis/index.js} +15 -5
- package/output/redis/index.test.d.ts +2 -0
- package/output/{context → redis}/index.test.d.ts.map +1 -1
- package/output/redis/index.test.js +55 -0
- package/output/redis-queue/index.js +1 -1
- package/output/redis-queue/test-helpers.d.ts +1 -1
- package/output/redis-queue/test-helpers.js +1 -1
- package/output/redis-queue/types.d.ts +3 -12
- package/output/redis-queue/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/output/app/auth.test.d.ts +0 -2
- package/output/app/auth.test.d.ts.map +0 -1
- package/output/app/auth.test.js +0 -148
- package/output/app/boot.d.ts +0 -48
- package/output/app/boot.d.ts.map +0 -1
- package/output/app/boot.js +0 -123
- package/output/app/config.test.d.ts +0 -2
- package/output/app/config.test.d.ts.map +0 -1
- package/output/app/config.test.js +0 -132
- package/output/app/context.d.ts +0 -68
- package/output/app/context.d.ts.map +0 -1
- package/output/app/context.js +0 -73
- package/output/app/core/cookie.d.ts +0 -25
- package/output/app/core/cookie.d.ts.map +0 -1
- package/output/app/core/identity.d.ts +0 -12
- package/output/app/core/identity.d.ts.map +0 -1
- package/output/app/core/index.d.ts +0 -44
- package/output/app/core/index.d.ts.map +0 -1
- package/output/app/core/index.js +0 -57
- package/output/app/core/logging.d.ts +0 -13
- package/output/app/core/logging.d.ts.map +0 -1
- package/output/app/core/openapi.d.ts +0 -17
- package/output/app/core/openapi.d.ts.map +0 -1
- package/output/app/core/request-id.d.ts.map +0 -1
- package/output/app/core/response.d.ts +0 -41
- package/output/app/core/response.d.ts.map +0 -1
- package/output/app/define-server.negative-cases.d.ts +0 -32
- package/output/app/define-server.negative-cases.d.ts.map +0 -1
- package/output/app/define-server.negative-cases.js +0 -209
- package/output/app/define-server.test.d.ts +0 -2
- package/output/app/define-server.test.d.ts.map +0 -1
- package/output/app/define-server.test.js +0 -97
- package/output/app/entries.d.ts +0 -29
- package/output/app/entries.d.ts.map +0 -1
- package/output/app/entries.js +0 -64
- package/output/app/env.d.ts +0 -59
- package/output/app/env.d.ts.map +0 -1
- package/output/app/env.js +0 -66
- package/output/app/env.test.js +0 -124
- package/output/app/error-contract.test.d.ts +0 -2
- package/output/app/error-contract.test.d.ts.map +0 -1
- package/output/app/error-contract.test.js +0 -125
- package/output/app/errors.d.ts +0 -20
- package/output/app/errors.d.ts.map +0 -1
- package/output/app/errors.js +0 -51
- package/output/app/generate-openapi.test.d.ts +0 -2
- package/output/app/generate-openapi.test.d.ts.map +0 -1
- package/output/app/generate-openapi.test.js +0 -123
- package/output/app/lifecycle-logging.test.d.ts +0 -2
- package/output/app/lifecycle-logging.test.d.ts.map +0 -1
- package/output/app/lifecycle-logging.test.js +0 -155
- package/output/app/lifecycle.d.ts +0 -70
- package/output/app/lifecycle.d.ts.map +0 -1
- package/output/app/lifecycle.js +0 -99
- package/output/app/lifecycle.test.d.ts +0 -2
- package/output/app/lifecycle.test.d.ts.map +0 -1
- package/output/app/lifecycle.test.js +0 -152
- package/output/app/middleware/auth.d.ts +0 -71
- package/output/app/middleware/auth.d.ts.map +0 -1
- package/output/app/middleware/auth.js +0 -190
- package/output/app/middleware/context.d.ts +0 -13
- package/output/app/middleware/context.d.ts.map +0 -1
- package/output/app/middleware/context.js +0 -15
- package/output/app/middleware/cors.d.ts +0 -14
- package/output/app/middleware/cors.d.ts.map +0 -1
- package/output/app/middleware/cors.js +0 -30
- package/output/app/middleware/index.d.ts +0 -17
- package/output/app/middleware/index.d.ts.map +0 -1
- package/output/app/middleware/index.js +0 -16
- package/output/app/middleware/rate-limit.d.ts +0 -24
- package/output/app/middleware/rate-limit.d.ts.map +0 -1
- package/output/app/middleware/rate-limit.js +0 -46
- package/output/app/middleware-order.test.d.ts +0 -2
- package/output/app/middleware-order.test.d.ts.map +0 -1
- package/output/app/middleware-order.test.js +0 -148
- package/output/app/permission-operations.d.ts +0 -48
- package/output/app/permission-operations.d.ts.map +0 -1
- package/output/app/permission-operations.js +0 -77
- package/output/app/permission-operations.test.d.ts +0 -2
- package/output/app/permission-operations.test.d.ts.map +0 -1
- package/output/app/permission-operations.test.js +0 -72
- package/output/app/permission-registration.test.d.ts +0 -2
- package/output/app/permission-registration.test.d.ts.map +0 -1
- package/output/app/permission-registration.test.js +0 -138
- package/output/app/redis.d.ts.map +0 -1
- package/output/app/registered-operations.test.d.ts +0 -2
- package/output/app/registered-operations.test.d.ts.map +0 -1
- package/output/app/registered-operations.test.js +0 -57
- package/output/app/request-id.test.d.ts +0 -2
- package/output/app/request-id.test.d.ts.map +0 -1
- package/output/app/request-id.test.js +0 -130
- package/output/app/resources.test.d.ts +0 -2
- package/output/app/resources.test.d.ts.map +0 -1
- package/output/app/resources.test.js +0 -197
- package/output/app/shutdown.d.ts +0 -22
- package/output/app/shutdown.d.ts.map +0 -1
- package/output/app/shutdown.js +0 -30
- package/output/app/shutdown.test.d.ts +0 -2
- package/output/app/shutdown.test.d.ts.map +0 -1
- package/output/app/shutdown.test.js +0 -103
- package/output/app/signal.d.ts +0 -12
- package/output/app/signal.d.ts.map +0 -1
- package/output/app/smoke.test.d.ts +0 -2
- package/output/app/smoke.test.d.ts.map +0 -1
- package/output/app/smoke.test.js +0 -210
- package/output/app/test-support.d.ts +0 -21
- package/output/app/test-support.d.ts.map +0 -1
- package/output/app/test-support.js +0 -24
- package/output/app/type-augment.d.ts.map +0 -1
- package/output/app/types.d.ts +0 -211
- package/output/app/types.d.ts.map +0 -1
- package/output/context/index.d.ts +0 -6
- package/output/context/index.d.ts.map +0 -1
- package/output/context/index.js +0 -14
- package/output/rate-limiter/index.d.ts +0 -5
- package/output/rate-limiter/index.d.ts.map +0 -1
- package/output/rate-limiter/index.js +0 -4
- package/output/rate-limiter/memory-store.d.ts.map +0 -1
- package/output/rate-limiter/rate-limiter.d.ts.map +0 -1
- package/output/rate-limiter/redis-store.d.ts.map +0 -1
- package/output/rate-limiter/redis-store.test.d.ts.map +0 -1
- package/output/rate-limiter/types.d.ts +0 -27
- package/output/rate-limiter/types.d.ts.map +0 -1
- package/output/redis-cache/index.d.ts +0 -4
- package/output/redis-cache/index.d.ts.map +0 -1
- package/output/redis-cache/index.js +0 -1
- package/output/redis-cache/redis-cache.d.ts.map +0 -1
- package/output/redis-cache/redis-cache.test.d.ts +0 -2
- package/output/redis-cache/redis-cache.test.d.ts.map +0 -1
- package/output/schema/index.d.ts.map +0 -1
- /package/output/{app → core}/env.test.d.ts +0 -0
- /package/output/{rate-limiter → core/modules/auth-provider}/types.js +0 -0
- /package/output/{context → core/modules/context}/index.test.d.ts +0 -0
- /package/output/{context → core/modules/context}/index.test.js +0 -0
- /package/output/{app → core}/type-augment.js +0 -0
- /package/output/{app → core}/types.js +0 -0
- /package/output/{rate-limiter/rate-limiter.js → rate-limit/limiter.js} +0 -0
- /package/output/{rate-limiter → rate-limit}/redis-store.test.d.ts +0 -0
- /package/output/{redis-cache/redis-cache.js → redis/cache.js} +0 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { catchIt, SystemError, UserError } from '@taicode/common-base';
|
|
2
|
+
import { readConfigKey } from '../core/boot/context.js';
|
|
3
|
+
import { readServerIdentity } from '../core/modules/identity/index.js';
|
|
4
|
+
import { extractPermissionOperations } from '../core/modules/permission-operations/index.js';
|
|
5
|
+
/** cookie 名缺省值(既未显式传入、配置也没给时使用) */
|
|
6
|
+
const DEFAULT_COOKIE_NAMES = ['token'];
|
|
7
|
+
/**
|
|
8
|
+
* 解析 cookie 名:显式参数 → 配置 `server.cookieNames` → 缺省值。
|
|
9
|
+
*
|
|
10
|
+
* **必须请求期调用**:出 spec 路径不跑 `config` 槽位,`ctx.config` 读到即抛 `CONFIG_UNAVAILABLE`。
|
|
11
|
+
*/
|
|
12
|
+
function resolveCookieNames(config, declared) {
|
|
13
|
+
if (declared !== undefined && declared.length > 0) {
|
|
14
|
+
return declared;
|
|
15
|
+
}
|
|
16
|
+
const server = readConfigKey(config, 'server');
|
|
17
|
+
return Array.isArray(server?.cookieNames) && server.cookieNames.length > 0 ? server.cookieNames : DEFAULT_COOKIE_NAMES;
|
|
18
|
+
}
|
|
19
|
+
function extractAuth(request, cookieNames) {
|
|
20
|
+
const authorization = request.headers.authorization;
|
|
21
|
+
if (typeof authorization === 'string' && authorization.startsWith('Bearer ')) {
|
|
22
|
+
return { token: authorization.slice('Bearer '.length).trim() };
|
|
23
|
+
}
|
|
24
|
+
if (typeof authorization === 'string' && authorization.startsWith('apikey-')) {
|
|
25
|
+
return { apiKey: authorization.slice('apikey-'.length).trim() };
|
|
26
|
+
}
|
|
27
|
+
for (const name of cookieNames) {
|
|
28
|
+
const value = request.cookies?.[name];
|
|
29
|
+
if (typeof value === 'string' && value) {
|
|
30
|
+
return { token: value };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 固定跳过判定(**不可覆盖**):`OPTIONS` → 未匹配路由。
|
|
37
|
+
*
|
|
38
|
+
* 不按路径前缀特判文档出口:`GET /system/openapi` 自己声明了 `NoLoginRequired`,
|
|
39
|
+
* **路由是唯一事实来源**——这样「哪些接口免登录」永远能从路由声明上读出来。
|
|
40
|
+
*
|
|
41
|
+
* ## `NoLoginRequired` 为什么**不在这里**跳过
|
|
42
|
+
*
|
|
43
|
+
* 免登录 ≠ 不解析身份。二者混为一谈会有两类真实故障(account 上都发生过):
|
|
44
|
+
*
|
|
45
|
+
* - `POST /permission/user/operation/list`(免登录 + `Request-user` 为空就返回空清单)
|
|
46
|
+
* 永远返回空清单,前端拿到的权限快照恒为空;
|
|
47
|
+
* - `POST /sign-out`(免登录 + `request.token` 存在才删除 SignIn Secret)永远删不掉密钥,
|
|
48
|
+
* 登出后 token 仍然有效。
|
|
49
|
+
*
|
|
50
|
+
* 所以免登录路由的处理放到 `preValidation` 里:**有凭证就尝试解析并回填身份,解析失败
|
|
51
|
+
* 一律当匿名放行**(公开接口不该因为一个过期 cookie 变成 400)。没有凭证时直接返回,
|
|
52
|
+
* 探针 / 文档抓取这类无 cookie 的高频请求不会多出一次校验调用。
|
|
53
|
+
*/
|
|
54
|
+
function shouldSkip(request) {
|
|
55
|
+
if (request.method === 'OPTIONS') {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return !request.routeOptions?.url;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 固定错误归类(**不可覆盖**):`UserError` / `SystemError` 原样透出,其余 → `EXTERNAL_SERVICE_ERROR`。
|
|
62
|
+
*
|
|
63
|
+
* 提供方若想表达「凭证无效 / 权限不足」,必须自己抛标准错误——这正是「按标准设计实现」的落点。
|
|
64
|
+
*/
|
|
65
|
+
function toStandardAuthError(error) {
|
|
66
|
+
if (UserError.is(error) || SystemError.is(error)) {
|
|
67
|
+
return error;
|
|
68
|
+
}
|
|
69
|
+
return new SystemError('EXTERNAL_SERVICE_ERROR', { cause: error });
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 从 OpenAPI 文档收集权限目录(**去重 + 稳定排序**)。
|
|
73
|
+
*
|
|
74
|
+
* 唯一来源就是 `app.swagger()`:不再同时维护一份 `onRoute` 注册表。两套来源必然漂移
|
|
75
|
+
* (`HEAD` / 前缀 / 插件路由只要有一处处理不同就对不上),而文档既能保证与对外契约一致,
|
|
76
|
+
* 也让「文档里有什么、权限系统里就有什么」成为可验证的事实。
|
|
77
|
+
*
|
|
78
|
+
* 同一 `operationId` 出现两次是路由声明问题(core 的 schema 守卫只保证字段存在,不保证唯一),
|
|
79
|
+
* 按首次出现保留并告警。稳定排序让目录内容不随注册顺序抖动,方便提供方 diff 与缓存。
|
|
80
|
+
*/
|
|
81
|
+
function collectPermissionOperations(app, log) {
|
|
82
|
+
const swagger = app.swagger;
|
|
83
|
+
if (typeof swagger !== 'function') {
|
|
84
|
+
log.warn('[PermissionOperation][auth] 未启用 OpenAPI 文档,跳过权限目录自注册');
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
const collected = extractPermissionOperations(swagger.call(app));
|
|
88
|
+
const seen = new Set();
|
|
89
|
+
const operations = [];
|
|
90
|
+
const duplicates = [];
|
|
91
|
+
for (const operation of collected) {
|
|
92
|
+
if (seen.has(operation.operation)) {
|
|
93
|
+
duplicates.push(operation.operation);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
seen.add(operation.operation);
|
|
97
|
+
operations.push(operation);
|
|
98
|
+
}
|
|
99
|
+
if (duplicates.length > 0) {
|
|
100
|
+
log.warn({ operations: duplicates }, '[PermissionOperation][auth] 检测到重复 operationId,已按首次声明去重(请修正路由声明)');
|
|
101
|
+
}
|
|
102
|
+
operations.sort((a, b) => (a.operation < b.operation ? -1 : a.operation > b.operation ? 1 : 0));
|
|
103
|
+
return operations;
|
|
104
|
+
}
|
|
105
|
+
/** 提交权限目录(失败**不致命**:只记日志,不中断启动) */
|
|
106
|
+
async function submitPermissionOperations(client, log, identity, operations) {
|
|
107
|
+
const { service, version } = identity;
|
|
108
|
+
if (!version) {
|
|
109
|
+
log.warn({ service }, '[PermissionOperation][auth] 身份块 version 为空:权限目录将无法按版本归组');
|
|
110
|
+
}
|
|
111
|
+
if (operations.length === 0) {
|
|
112
|
+
log.warn({ service, version }, '[PermissionOperation][auth] 未采集到任何带 operationId 的路由,跳过权限目录自注册(请检查路由 schema)');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const result = await catchIt(client.registerPermissionOperations({ service, version, operations }));
|
|
116
|
+
if (result.isError()) {
|
|
117
|
+
log.error({ error: result.error, service, version, count: operations.length }, '[PermissionOperation][auth] 权限目录自注册失败(不阻断启动)');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
log.info({ service, version, count: operations.length }, '[PermissionOperation][auth] 权限目录自注册完成');
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* 构造标准 `auth` 中间件(具名单元 `name: 'auth'`)。
|
|
124
|
+
*
|
|
125
|
+
* `client` **必填**(类型上)且必须有运行期实现:装了鉴权却没有校验实现是配置错误,
|
|
126
|
+
* 构造期直接抛错——不留「请求期才 fail-closed」的窗口,也不允许静默放行。
|
|
127
|
+
*/
|
|
128
|
+
export function createAuthMiddleware(ctx, options) {
|
|
129
|
+
const client = options?.client;
|
|
130
|
+
if (!client || typeof client.verify !== 'function') {
|
|
131
|
+
throw new Error('[auth] auth 中间件缺少校验实现:请通过 `createAuthMiddleware(ctx, { client })` 注入鉴权提供方');
|
|
132
|
+
}
|
|
133
|
+
const register = (app) => {
|
|
134
|
+
const identity = readServerIdentity(app);
|
|
135
|
+
if (!identity) {
|
|
136
|
+
throw new Error('[auth] 读不到身份块:`createAuthMiddleware` 必须挂在 `startServer` / `generateOpenApi` 的 app 上');
|
|
137
|
+
}
|
|
138
|
+
app.addHook('preValidation', async (request) => {
|
|
139
|
+
if (shouldSkip(request)) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const optionalLogin = request.routeOptions.config?.accessControls?.includes('NoLoginRequired') === true;
|
|
143
|
+
// cookie 名**请求期**解析:显式参数优先,其次现读配置(配置可被运维改动)
|
|
144
|
+
const { token, apiKey } = extractAuth(request, resolveCookieNames(ctx.config, options.cookieNames));
|
|
145
|
+
if (!token && !apiKey) {
|
|
146
|
+
// 免登录路由:没有凭证就直接匿名,不必白跑一次校验(探针 / 文档抓取都属于这一类)
|
|
147
|
+
if (optionalLogin) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
throw new UserError('INVALID_TOKEN', undefined, '缺少访问凭证');
|
|
151
|
+
}
|
|
152
|
+
const verifyInput = {
|
|
153
|
+
token,
|
|
154
|
+
apiKey,
|
|
155
|
+
service: identity.service,
|
|
156
|
+
// 权限目录同源、与 url 解耦:优先路由 `schema.operationId`,未声明时回落 url
|
|
157
|
+
operation: request.routeOptions?.schema?.operationId ?? request.routeOptions?.url,
|
|
158
|
+
resource: request.params,
|
|
159
|
+
accessControls: request.routeOptions?.config?.accessControls,
|
|
160
|
+
metadata: {
|
|
161
|
+
requestId: request.id,
|
|
162
|
+
method: request.method,
|
|
163
|
+
url: request.url,
|
|
164
|
+
ip: request.ip,
|
|
165
|
+
ips: request.ips,
|
|
166
|
+
userAgent: request.headers['user-agent'],
|
|
167
|
+
referrer: request.headers.referer,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
try {
|
|
171
|
+
const result = await client.verify(verifyInput);
|
|
172
|
+
request.user = result.user;
|
|
173
|
+
request.token = result.token ?? token;
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
// 免登录路由:凭证坏了也只是「没有身份」,不能因为一个过期 cookie 把公开接口变成 400
|
|
177
|
+
if (optionalLogin) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
throw toStandardAuthError(error);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
// 权限目录自注册:**运行期无条件执行**(唯一前提是有 client,而 client 是必填的)。
|
|
184
|
+
//
|
|
185
|
+
// 时点必须在 `onReady`:`app.swagger()` 只有在 `ready()` 之后才可用(swagger 自身也挂
|
|
186
|
+
// `onReady`,且它在 core 阶段先注册,故本钩子执行时文档已就绪)。
|
|
187
|
+
//
|
|
188
|
+
// **为什么不需要区分入口**:`generateOpenApi` 根本不注册中间件(它只装路由与 schema
|
|
189
|
+
// 契约),所以本单元在出文档路径里压根不会被构造,不存在「构建期误写权限系统」的可能。
|
|
190
|
+
// 这也是「出文档不装配运行期能力」的直接收益:副作用随能力一起消失,不需要额外标记。
|
|
191
|
+
app.addHook('onReady', async () => {
|
|
192
|
+
await submitPermissionOperations(client, ctx.log, identity, collectPermissionOperations(app, ctx.log));
|
|
193
|
+
});
|
|
194
|
+
};
|
|
195
|
+
return { name: 'auth', register };
|
|
196
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ErrorClassifier } from './modules/response/index.js';
|
|
2
|
+
import type { ConfigEnv } from './env.js';
|
|
3
|
+
import type { AppIdentity } from './types.js';
|
|
4
|
+
import type { FastifyInstance } from 'fastify';
|
|
5
|
+
/**
|
|
6
|
+
* core 步骤 `createFastify`(`prepare`)。
|
|
7
|
+
*
|
|
8
|
+
* **总是**建 pino logger(level = `LOG_LEVEL`,默认 `info`) + 内置 `requestId`(入站 `x-request-id`
|
|
9
|
+
* 复用 / `randomUUID`,日志字段 `requestId`)。`trustProxy: true` 与四家现状一致。
|
|
10
|
+
*
|
|
11
|
+
* 固定装配,**不从包对外导出**:对外入口只有 `defineServer` / `startServer` / `generateOpenApi`。
|
|
12
|
+
*/
|
|
13
|
+
export declare function createFastify(env: ConfigEnv): FastifyInstance;
|
|
14
|
+
/**
|
|
15
|
+
* 出文档用的**最小实例**(`generateOpenApi` 专用)。
|
|
16
|
+
*
|
|
17
|
+
* 没有 logger、没有 `requestId`、没有 `trustProxy`——这些只在**有请求**时才有意义,
|
|
18
|
+
* 而构建期一条请求都不会发。`logger: false` 下 `app.log` 仍是 `abstract-logging` 的
|
|
19
|
+
* 空实现,满足 `LoggerLike`,因此下游单元拿到的 `ctx.log` 调用不会炸,也不会产生输出。
|
|
20
|
+
*/
|
|
21
|
+
export declare function createDocumentFastify(): FastifyInstance;
|
|
22
|
+
/** core 装配入参 */
|
|
23
|
+
export interface CoreRegistrationOptions {
|
|
24
|
+
/** 身份块(`service` / `title` / `version`):openapi + 内置单元从这里读 */
|
|
25
|
+
identity: AppIdentity;
|
|
26
|
+
env: ConfigEnv;
|
|
27
|
+
/** 资源错误分类(各资源经 `ctx.registerClassifyError` 注册;缺省 = 落 `UNKNOWN_ERROR`) */
|
|
28
|
+
classifyResourceError?: ErrorClassifier;
|
|
29
|
+
/** 取**已加载**配置中某个键的访问器(cookie domain 等**惰性**取值;不得在构造期调用) */
|
|
30
|
+
readConfig: (key: string) => unknown;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* **schema 契约**(固定序,不可替换):`identity` → `schema`(zod compiler)→ 路由契约守卫。
|
|
34
|
+
*
|
|
35
|
+
* 两个入口都必须有它:
|
|
36
|
+
* - `startServer` 要让 zod 真的校验/序列化请求,并让「路由缺 `schema`」在注册处就失败;
|
|
37
|
+
* - `generateOpenApi` 要把 zod 转成 JSON Schema 出文档,也要同一个守卫——**文档就是权限目录**,
|
|
38
|
+
* 构建期放过的路由会在线上变成「接口永远判不过」。
|
|
39
|
+
*
|
|
40
|
+
* 单独成函数是为了让「运行期 core」与「文档 core」两条装配路径共用同一段契约,
|
|
41
|
+
* 而不用把整段运行期能力拖进出文档路径。
|
|
42
|
+
*/
|
|
43
|
+
export declare function registerSchemaContract(app: FastifyInstance, options: Pick<CoreRegistrationOptions, 'identity'>): void;
|
|
44
|
+
/**
|
|
45
|
+
* **文档 core**(`generateOpenApi` 专用):schema 契约 + swagger。
|
|
46
|
+
*
|
|
47
|
+
* 只有这三件事与「把 zod schema 转成 OpenAPI 文档」有关:身份(`info.title` /
|
|
48
|
+
* `info.version`)、zod compiler(转换前得能读懂 zod)、守卫(不让文档缺条目)、swagger(转换器)。
|
|
49
|
+
* `requestContext` / `cookie` / `response` / `requestId` 全部**不装**——它们只影响
|
|
50
|
+
* 请求怎么被处理,而构建期一条请求都不会发。
|
|
51
|
+
*/
|
|
52
|
+
export declare function registerDocumentCore(app: FastifyInstance, options: Pick<CoreRegistrationOptions, 'identity'>): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* core 装配(固定序,不可替换):`identity` → `schema`(+ 路由契约守卫)→ `requestContext`
|
|
55
|
+
* → `cookie` → `response` → `openapi` → `requestId` 回吐。
|
|
56
|
+
*
|
|
57
|
+
* 全部为框架内置能力,**不暴露选项面**;`request.ctx` 的装配也在这一步完成(`type-augment` 的
|
|
58
|
+
* 类型承诺因此总有运行期对应物)。**构造期不读配置**(`cookie` 的 domain 在请求期惰性读取),
|
|
59
|
+
* 故出 spec 路径(`readConfig` 不可用)不会触发。
|
|
60
|
+
*
|
|
61
|
+
* 这里**不建路由注册表**:权限目录直接由 `app.swagger()` 生成的文档派生(OpenAPI 是唯一来源)。
|
|
62
|
+
*/
|
|
63
|
+
export declare function registerCore(app: FastifyInstance, options: CoreRegistrationOptions): Promise<void>;
|
|
64
|
+
//# sourceMappingURL=assemble.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assemble.d.ts","sourceRoot":"","sources":["../../source/core/assemble.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAClE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE9C;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,SAAS,GAAG,eAAe,CAM7D;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CAEvD;AAED,gBAAgB;AAChB,MAAM,WAAW,uBAAuB;IACtC,8DAA8D;IAC9D,QAAQ,EAAE,WAAW,CAAA;IACrB,GAAG,EAAE,SAAS,CAAA;IACd,yEAAyE;IACzE,qBAAqB,CAAC,EAAE,eAAe,CAAA;IACvC,2DAA2D;IAC3D,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAA;CACrC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC,uBAAuB,EAAE,UAAU,CAAC,GAAG,IAAI,CAIrH;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,IAAI,CAAC,uBAAuB,EAAE,UAAU,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAOlI;AAED;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAWxG"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import Fastify from 'fastify';
|
|
2
|
+
import { registerCookie } from './modules/cookie/index.js';
|
|
3
|
+
import { registerRequestContext } from './modules/context/index.js';
|
|
4
|
+
import { attachServerIdentity } from './modules/identity/index.js';
|
|
5
|
+
import { createLoggerOptions } from './modules/logging/index.js';
|
|
6
|
+
import { registerOpenApi } from './modules/openapi/index.js';
|
|
7
|
+
import { createRequestIdOptions, registerRequestId } from './modules/request-id/index.js';
|
|
8
|
+
import { registerResponse } from './modules/response/index.js';
|
|
9
|
+
import { installRouteSchemaGuard, registerSchema } from './modules/schema/index.js';
|
|
10
|
+
/**
|
|
11
|
+
* core 步骤 `createFastify`(`prepare`)。
|
|
12
|
+
*
|
|
13
|
+
* **总是**建 pino logger(level = `LOG_LEVEL`,默认 `info`) + 内置 `requestId`(入站 `x-request-id`
|
|
14
|
+
* 复用 / `randomUUID`,日志字段 `requestId`)。`trustProxy: true` 与四家现状一致。
|
|
15
|
+
*
|
|
16
|
+
* 固定装配,**不从包对外导出**:对外入口只有 `defineServer` / `startServer` / `generateOpenApi`。
|
|
17
|
+
*/
|
|
18
|
+
export function createFastify(env) {
|
|
19
|
+
return Fastify({
|
|
20
|
+
trustProxy: true,
|
|
21
|
+
logger: createLoggerOptions(env),
|
|
22
|
+
...createRequestIdOptions(),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 出文档用的**最小实例**(`generateOpenApi` 专用)。
|
|
27
|
+
*
|
|
28
|
+
* 没有 logger、没有 `requestId`、没有 `trustProxy`——这些只在**有请求**时才有意义,
|
|
29
|
+
* 而构建期一条请求都不会发。`logger: false` 下 `app.log` 仍是 `abstract-logging` 的
|
|
30
|
+
* 空实现,满足 `LoggerLike`,因此下游单元拿到的 `ctx.log` 调用不会炸,也不会产生输出。
|
|
31
|
+
*/
|
|
32
|
+
export function createDocumentFastify() {
|
|
33
|
+
return Fastify({ logger: false });
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* **schema 契约**(固定序,不可替换):`identity` → `schema`(zod compiler)→ 路由契约守卫。
|
|
37
|
+
*
|
|
38
|
+
* 两个入口都必须有它:
|
|
39
|
+
* - `startServer` 要让 zod 真的校验/序列化请求,并让「路由缺 `schema`」在注册处就失败;
|
|
40
|
+
* - `generateOpenApi` 要把 zod 转成 JSON Schema 出文档,也要同一个守卫——**文档就是权限目录**,
|
|
41
|
+
* 构建期放过的路由会在线上变成「接口永远判不过」。
|
|
42
|
+
*
|
|
43
|
+
* 单独成函数是为了让「运行期 core」与「文档 core」两条装配路径共用同一段契约,
|
|
44
|
+
* 而不用把整段运行期能力拖进出文档路径。
|
|
45
|
+
*/
|
|
46
|
+
export function registerSchemaContract(app, options) {
|
|
47
|
+
attachServerIdentity(app, options.identity);
|
|
48
|
+
registerSchema(app); // schema → zod compiler
|
|
49
|
+
installRouteSchemaGuard(app); // 强制所有路由声明 schema(含 operationId / summary / description / tags)
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* **文档 core**(`generateOpenApi` 专用):schema 契约 + swagger。
|
|
53
|
+
*
|
|
54
|
+
* 只有这三件事与「把 zod schema 转成 OpenAPI 文档」有关:身份(`info.title` /
|
|
55
|
+
* `info.version`)、zod compiler(转换前得能读懂 zod)、守卫(不让文档缺条目)、swagger(转换器)。
|
|
56
|
+
* `requestContext` / `cookie` / `response` / `requestId` 全部**不装**——它们只影响
|
|
57
|
+
* 请求怎么被处理,而构建期一条请求都不会发。
|
|
58
|
+
*/
|
|
59
|
+
export async function registerDocumentCore(app, options) {
|
|
60
|
+
registerSchemaContract(app, options);
|
|
61
|
+
// await:确定性冲刷插件队列,保证 swagger 的 root `onRoute` 钩子在路由注册之前就位
|
|
62
|
+
await registerOpenApi(app, {
|
|
63
|
+
title: options.identity.title,
|
|
64
|
+
version: options.identity.version,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* core 装配(固定序,不可替换):`identity` → `schema`(+ 路由契约守卫)→ `requestContext`
|
|
69
|
+
* → `cookie` → `response` → `openapi` → `requestId` 回吐。
|
|
70
|
+
*
|
|
71
|
+
* 全部为框架内置能力,**不暴露选项面**;`request.ctx` 的装配也在这一步完成(`type-augment` 的
|
|
72
|
+
* 类型承诺因此总有运行期对应物)。**构造期不读配置**(`cookie` 的 domain 在请求期惰性读取),
|
|
73
|
+
* 故出 spec 路径(`readConfig` 不可用)不会触发。
|
|
74
|
+
*
|
|
75
|
+
* 这里**不建路由注册表**:权限目录直接由 `app.swagger()` 生成的文档派生(OpenAPI 是唯一来源)。
|
|
76
|
+
*/
|
|
77
|
+
export async function registerCore(app, options) {
|
|
78
|
+
registerSchemaContract(app, options);
|
|
79
|
+
registerRequestContext(app); // request.ctx(type-augment 承诺的运行期对应物)
|
|
80
|
+
registerCookie(app, { env: options.env, readConfig: options.readConfig });
|
|
81
|
+
registerResponse(app, { classifyResourceError: options.classifyResourceError });
|
|
82
|
+
// await:确定性冲刷插件队列,保证 swagger 的 root `onRoute` 钩子在路由注册之前就位
|
|
83
|
+
await registerOpenApi(app, {
|
|
84
|
+
title: options.identity.title,
|
|
85
|
+
version: options.identity.version,
|
|
86
|
+
});
|
|
87
|
+
registerRequestId(app); // 响应回吐 x-request-id
|
|
88
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { ResourceSetupUnit } from './lifecycle.js';
|
|
2
|
+
import type { Disposer } from '@taicode/common-base';
|
|
3
|
+
import type { AppEnv, AppInstance, ClientsContext, ClientsView, ConfigContext, ErrorClassifier, LoggerLike, MiddlewareContext, ResourcesContext, ResourcesView, RouterContext, ServiceContext } from '../types.js';
|
|
4
|
+
/**
|
|
5
|
+
* 分阶段 ctx 的运行时构造(N1 §2)。
|
|
6
|
+
*
|
|
7
|
+
* 每个槽位拿到的是**字段最小集合**的普通对象;`config` 用 getter 暴露,出 spec 路径
|
|
8
|
+
* (`generateOpenApi` 不跑 `config` 槽位)读到它时抛 `SystemError('CONFIG_UNAVAILABLE')`
|
|
9
|
+
* —— 让「构造期不得读配置」有运行期护栏,而不仅是文档规则。
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* 运行时镜像 `Live<Value>`:剔除值为 `false` 的键(编译期静态关闭)。
|
|
13
|
+
*
|
|
14
|
+
* `resources` 与 `clients` 两个槽位共用这套口径。
|
|
15
|
+
*/
|
|
16
|
+
export declare function liveResources(resources: object): Record<string, unknown>;
|
|
17
|
+
/**
|
|
18
|
+
* 把 `resources(ctx)` 的返回值拆成「对外视图」与「可执行生命周期列表」。
|
|
19
|
+
*
|
|
20
|
+
* 三件事:
|
|
21
|
+
* - **解包**:`defineResource(client, …)` → 视图里是 `client` 本身;裸对象原样(单元可选);
|
|
22
|
+
* - **收集**:有 `setup` / `clean` / `dispose` 的单元按**对象声明序**收集成
|
|
23
|
+
* `{ name, setup, clean, dispose }`,分别交给 `setupResourceUnits` / `cleanResourceUnits`
|
|
24
|
+
* / `disposeResourceUnits` 执行;
|
|
25
|
+
* - **剔除**:`false` 键与 `liveResources` 同口径(静态关闭的资源不参与生命周期)。
|
|
26
|
+
*
|
|
27
|
+
* ❗识别靠 `'value' in obj && !('then' in obj)`:`Resources` 允许是 Promise(`Awaited<>`
|
|
28
|
+
* 归一),但**单个资源值本身是 thenable 的极端情况**不当单元——宁可把它当裸值,
|
|
29
|
+
* 也不要把它错当单元。
|
|
30
|
+
*/
|
|
31
|
+
export declare function splitResourceUnits(resources: object): {
|
|
32
|
+
view: Record<string, unknown>;
|
|
33
|
+
units: ResourceSetupUnit[];
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* `config` 槽位的**读取能力**(不是容器)。
|
|
37
|
+
*
|
|
38
|
+
* 装配期各单元需要的是「能不能读、读到什么」,不是「一个有 `current` 字段的箱子」——
|
|
39
|
+
* 后者把**写入**也一并交给了每个拿到 `BootContext` 的步骤,绕开 `buildConfig` 就能改配置。
|
|
40
|
+
* 因此这里只暴露读:`available`(有没有产出)+ `read()`(读,未产出即抛)。
|
|
41
|
+
*
|
|
42
|
+
* 写入通道是**私有 Symbol**(本文件内、不导出):调用方连键名都写不出来,只能通过
|
|
43
|
+
* `provideConfig()` 写,而它只被 `buildConfig` 调用。
|
|
44
|
+
*/
|
|
45
|
+
export interface ConfigAccess<Config> {
|
|
46
|
+
/** `config` 槽位是否已产出(出 spec 路径恒为 `false`) */
|
|
47
|
+
readonly available: boolean;
|
|
48
|
+
/** 读配置;未产出时抛 `SystemError('CONFIG_UNAVAILABLE')` */
|
|
49
|
+
read(): Awaited<Config>;
|
|
50
|
+
}
|
|
51
|
+
export declare function createConfigAccess<Config>(): ConfigAccess<Config>;
|
|
52
|
+
/** 写入 `config` 产物(唯一的写入口:由 `buildConfig` 调用,不在 `BootContext` 上暴露) */
|
|
53
|
+
export declare function provideConfig<Config>(access: ConfigAccess<Config>, value: Awaited<Config>): void;
|
|
54
|
+
/** 按**已知键**从配置值里取字段(框架内置单元消费配置的单一入口;不做形状校验) */
|
|
55
|
+
export declare function readConfigKey(config: unknown, key: string): unknown;
|
|
56
|
+
/** `resources(ctx)` 入参 */
|
|
57
|
+
export interface ResourcesContextParams<Env = object> {
|
|
58
|
+
env: AppEnv<Env>;
|
|
59
|
+
log: LoggerLike;
|
|
60
|
+
disposer: Disposer;
|
|
61
|
+
/** 收集器:`buildResources` 注入,把各资源注册的分类器按注册顺序收起来 */
|
|
62
|
+
registerClassifyError: (classifier: ErrorClassifier) => void;
|
|
63
|
+
}
|
|
64
|
+
export declare function createResourcesContext<Env = object>(params: ResourcesContextParams<Env>): ResourcesContext<Env>;
|
|
65
|
+
/** `config(ctx)` 入参 */
|
|
66
|
+
export interface ConfigContextParams<Resources, Env = object> {
|
|
67
|
+
env: AppEnv<Env>;
|
|
68
|
+
log: LoggerLike;
|
|
69
|
+
disposer: Disposer;
|
|
70
|
+
resources: ResourcesView<Resources>;
|
|
71
|
+
}
|
|
72
|
+
export declare function createConfigContext<Resources, Env = object>(params: ConfigContextParams<Resources, Env>): ConfigContext<Resources, Env>;
|
|
73
|
+
/** `clients(ctx)` 入参(与 `services` 同依赖集:`resources` + `config`) */
|
|
74
|
+
export interface ClientsContextParams<Resources, Config> {
|
|
75
|
+
log: LoggerLike;
|
|
76
|
+
disposer: Disposer;
|
|
77
|
+
resources: ResourcesView<Resources>;
|
|
78
|
+
configAccess: ConfigAccess<Config>;
|
|
79
|
+
}
|
|
80
|
+
export declare function createClientsContext<Resources, Config>(params: ClientsContextParams<Resources, Config>): ClientsContext<Resources, Config>;
|
|
81
|
+
/** `services(ctx)` 入参 */
|
|
82
|
+
export interface ServiceContextParams<Resources, Config, Clients> {
|
|
83
|
+
log: LoggerLike;
|
|
84
|
+
disposer: Disposer;
|
|
85
|
+
resources: ResourcesView<Resources>;
|
|
86
|
+
configAccess: ConfigAccess<Config>;
|
|
87
|
+
clients: ClientsView<Clients>;
|
|
88
|
+
}
|
|
89
|
+
export declare function createServiceContext<Resources, Config, Clients>(params: ServiceContextParams<Resources, Config, Clients>): ServiceContext<Resources, Config, Clients>;
|
|
90
|
+
/** `middlewares(ctx)` 入参 */
|
|
91
|
+
export interface MiddlewareContextParams<Config, Services, Clients> {
|
|
92
|
+
app: AppInstance;
|
|
93
|
+
log: LoggerLike;
|
|
94
|
+
disposer: Disposer;
|
|
95
|
+
services: Awaited<Services>;
|
|
96
|
+
configAccess: ConfigAccess<Config>;
|
|
97
|
+
clients: ClientsView<Clients>;
|
|
98
|
+
}
|
|
99
|
+
export declare function createMiddlewareContext<Config, Services, Clients>(params: MiddlewareContextParams<Config, Services, Clients>): MiddlewareContext<Config, Services, Clients>;
|
|
100
|
+
/** `routers(ctx)` 入参 */
|
|
101
|
+
export interface RouterContextParams<Services, Clients> {
|
|
102
|
+
app: AppInstance;
|
|
103
|
+
log: LoggerLike;
|
|
104
|
+
disposer: Disposer;
|
|
105
|
+
services: Awaited<Services>;
|
|
106
|
+
clients: ClientsView<Clients>;
|
|
107
|
+
}
|
|
108
|
+
export declare function createRouterContext<Services, Clients>(params: RouterContextParams<Services, Clients>): RouterContext<Services, Clients>;
|
|
109
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../source/core/boot/context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { SystemError } from '@taicode/common-base';
|
|
2
|
+
/**
|
|
3
|
+
* 分阶段 ctx 的运行时构造(N1 §2)。
|
|
4
|
+
*
|
|
5
|
+
* 每个槽位拿到的是**字段最小集合**的普通对象;`config` 用 getter 暴露,出 spec 路径
|
|
6
|
+
* (`generateOpenApi` 不跑 `config` 槽位)读到它时抛 `SystemError('CONFIG_UNAVAILABLE')`
|
|
7
|
+
* —— 让「构造期不得读配置」有运行期护栏,而不仅是文档规则。
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* 运行时镜像 `Live<Value>`:剔除值为 `false` 的键(编译期静态关闭)。
|
|
11
|
+
*
|
|
12
|
+
* `resources` 与 `clients` 两个槽位共用这套口径。
|
|
13
|
+
*/
|
|
14
|
+
export function liveResources(resources) {
|
|
15
|
+
const result = {};
|
|
16
|
+
for (const [key, value] of Object.entries(resources)) {
|
|
17
|
+
if (value !== false) {
|
|
18
|
+
result[key] = value;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
/** 单元外壳的运行时识别(`defineResource` 产物的唯一形状) */
|
|
24
|
+
function isResourceUnit(value) {
|
|
25
|
+
return typeof value === 'object' && value !== null && 'value' in value && !('then' in value);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 把 `resources(ctx)` 的返回值拆成「对外视图」与「可执行生命周期列表」。
|
|
29
|
+
*
|
|
30
|
+
* 三件事:
|
|
31
|
+
* - **解包**:`defineResource(client, …)` → 视图里是 `client` 本身;裸对象原样(单元可选);
|
|
32
|
+
* - **收集**:有 `setup` / `clean` / `dispose` 的单元按**对象声明序**收集成
|
|
33
|
+
* `{ name, setup, clean, dispose }`,分别交给 `setupResourceUnits` / `cleanResourceUnits`
|
|
34
|
+
* / `disposeResourceUnits` 执行;
|
|
35
|
+
* - **剔除**:`false` 键与 `liveResources` 同口径(静态关闭的资源不参与生命周期)。
|
|
36
|
+
*
|
|
37
|
+
* ❗识别靠 `'value' in obj && !('then' in obj)`:`Resources` 允许是 Promise(`Awaited<>`
|
|
38
|
+
* 归一),但**单个资源值本身是 thenable 的极端情况**不当单元——宁可把它当裸值,
|
|
39
|
+
* 也不要把它错当单元。
|
|
40
|
+
*/
|
|
41
|
+
export function splitResourceUnits(resources) {
|
|
42
|
+
const view = {};
|
|
43
|
+
const units = [];
|
|
44
|
+
for (const [key, value] of Object.entries(resources)) {
|
|
45
|
+
if (value === false) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (isResourceUnit(value)) {
|
|
49
|
+
view[key] = value.value;
|
|
50
|
+
const setup = typeof value.setup === 'function' ? value.setup.bind(value) : undefined;
|
|
51
|
+
const clean = typeof value.clean === 'function' ? value.clean.bind(value) : undefined;
|
|
52
|
+
const dispose = typeof value.dispose === 'function' ? value.dispose.bind(value) : undefined;
|
|
53
|
+
if (setup || clean || dispose) {
|
|
54
|
+
units.push({ name: key, setup, clean, dispose });
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
view[key] = value;
|
|
59
|
+
}
|
|
60
|
+
return { view, units };
|
|
61
|
+
}
|
|
62
|
+
const CONFIG_WRITE = Symbol('config.write');
|
|
63
|
+
export function createConfigAccess() {
|
|
64
|
+
let value;
|
|
65
|
+
const access = {
|
|
66
|
+
get available() {
|
|
67
|
+
return value !== undefined;
|
|
68
|
+
},
|
|
69
|
+
read() {
|
|
70
|
+
if (value === undefined) {
|
|
71
|
+
throw new SystemError('CONFIG_UNAVAILABLE', undefined, '配置尚未就绪(generateOpenApi 路径不跑 config 槽位):任何单元不得在构造期读配置');
|
|
72
|
+
}
|
|
73
|
+
return value;
|
|
74
|
+
},
|
|
75
|
+
[CONFIG_WRITE](next) {
|
|
76
|
+
value = next;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
return access;
|
|
80
|
+
}
|
|
81
|
+
/** 写入 `config` 产物(唯一的写入口:由 `buildConfig` 调用,不在 `BootContext` 上暴露) */
|
|
82
|
+
export function provideConfig(access, value) {
|
|
83
|
+
const write = access[CONFIG_WRITE];
|
|
84
|
+
if (write === undefined) {
|
|
85
|
+
throw new Error('[config] 非法写入:`ConfigAccess` 只能由 `createConfigAccess()` 产出');
|
|
86
|
+
}
|
|
87
|
+
write(value);
|
|
88
|
+
}
|
|
89
|
+
/** 按**已知键**从配置值里取字段(框架内置单元消费配置的单一入口;不做形状校验) */
|
|
90
|
+
export function readConfigKey(config, key) {
|
|
91
|
+
return config?.[key];
|
|
92
|
+
}
|
|
93
|
+
export function createResourcesContext(params) {
|
|
94
|
+
return {
|
|
95
|
+
env: params.env,
|
|
96
|
+
log: params.log,
|
|
97
|
+
disposer: params.disposer,
|
|
98
|
+
registerClassifyError: params.registerClassifyError,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export function createConfigContext(params) {
|
|
102
|
+
return { env: params.env, log: params.log, disposer: params.disposer, resources: params.resources };
|
|
103
|
+
}
|
|
104
|
+
export function createClientsContext(params) {
|
|
105
|
+
return {
|
|
106
|
+
log: params.log,
|
|
107
|
+
disposer: params.disposer,
|
|
108
|
+
resources: params.resources,
|
|
109
|
+
get config() {
|
|
110
|
+
return params.configAccess.read();
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
export function createServiceContext(params) {
|
|
115
|
+
return {
|
|
116
|
+
log: params.log,
|
|
117
|
+
disposer: params.disposer,
|
|
118
|
+
resources: params.resources,
|
|
119
|
+
clients: params.clients,
|
|
120
|
+
get config() {
|
|
121
|
+
return params.configAccess.read();
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
export function createMiddlewareContext(params) {
|
|
126
|
+
return {
|
|
127
|
+
app: params.app,
|
|
128
|
+
log: params.log,
|
|
129
|
+
disposer: params.disposer,
|
|
130
|
+
services: params.services,
|
|
131
|
+
clients: params.clients,
|
|
132
|
+
get config() {
|
|
133
|
+
return params.configAccess.read();
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export function createRouterContext(params) {
|
|
138
|
+
return {
|
|
139
|
+
app: params.app,
|
|
140
|
+
log: params.log,
|
|
141
|
+
disposer: params.disposer,
|
|
142
|
+
services: params.services,
|
|
143
|
+
clients: params.clients,
|
|
144
|
+
};
|
|
145
|
+
}
|