@taicode/common-server 3.8.0 → 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 -9
- 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 +5 -4
- 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/core/modules/cookie/index.js +17 -0
- package/output/core/modules/identity/index.d.ts +4 -0
- package/output/core/modules/identity/index.d.ts.map +1 -0
- package/output/core/modules/identity/index.js +14 -0
- package/output/core/modules/logging/index.d.ts +5 -0
- package/output/core/modules/logging/index.d.ts.map +1 -0
- package/output/core/modules/logging/index.js +29 -0
- package/output/core/modules/openapi/index.d.ts +7 -0
- package/output/core/modules/openapi/index.d.ts.map +1 -0
- package/output/core/modules/openapi/index.js +41 -0
- 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/core/modules/request-id/index.d.ts +33 -0
- package/output/core/modules/request-id/index.d.ts.map +1 -0
- package/output/core/modules/request-id/index.js +66 -0
- package/output/core/modules/response/index.d.ts +50 -0
- package/output/core/modules/response/index.d.ts.map +1 -0
- package/output/core/modules/response/index.js +102 -0
- 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.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/core/type-augment.d.ts +34 -0
- package/output/core/type-augment.d.ts.map +1 -0
- package/output/core/type-augment.js +7 -0
- package/output/core/types.d.ts +582 -0
- package/output/core/types.d.ts.map +1 -0
- package/output/core/types.js +2 -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/{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/redis/index.d.ts +50 -0
- package/output/redis/index.d.ts.map +1 -0
- package/output/redis/index.js +71 -0
- 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 +11 -7
- 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.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/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 -299
- package/output/app/env.d.ts +0 -24
- package/output/app/env.d.ts.map +0 -1
- package/output/app/env.js +0 -38
- package/output/app/env.test.js +0 -78
- 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/shutdown.d.ts +0 -9
- package/output/app/shutdown.d.ts.map +0 -1
- package/output/app/shutdown.js +0 -15
- 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 -149
- package/output/app/signal.d.ts +0 -12
- package/output/app/signal.d.ts.map +0 -1
- package/output/app/steps.d.ts +0 -17
- package/output/app/steps.d.ts.map +0 -1
- package/output/app/steps.js +0 -41
- package/output/app/types.d.ts +0 -149
- 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/{app → 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/test-support}/database.test.d.ts +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/{rate-limiter → rate-limit}/types.js +0 -0
- /package/output/{redis-cache/redis-cache.js → redis/cache.js} +0 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { loadEnvironment } from './env.js';
|
|
6
|
+
const ORIGINAL_ENV = { ...process.env };
|
|
7
|
+
const ORIGINAL_CWD = process.cwd();
|
|
8
|
+
const TEMP_ROOTS = [];
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
process.env = { ...ORIGINAL_ENV };
|
|
11
|
+
process.chdir(ORIGINAL_CWD);
|
|
12
|
+
for (const root of TEMP_ROOTS.splice(0)) {
|
|
13
|
+
rmSync(root, { recursive: true, force: true });
|
|
14
|
+
}
|
|
15
|
+
vi.resetModules();
|
|
16
|
+
});
|
|
17
|
+
/** 在临时目录里起一个项目根并写文件,然后切过去(`dotenv` 按 `process.cwd()` 解析相对路径) */
|
|
18
|
+
function useTempProject(files) {
|
|
19
|
+
const root = mkdtempSync(join(tmpdir(), 'common-env-'));
|
|
20
|
+
TEMP_ROOTS.push(root);
|
|
21
|
+
for (const [name, content] of Object.entries(files)) {
|
|
22
|
+
writeFileSync(join(root, name), content);
|
|
23
|
+
}
|
|
24
|
+
process.chdir(root);
|
|
25
|
+
}
|
|
26
|
+
describe('loadEnvironment · runtime(工具进程 / startServer)', () => {
|
|
27
|
+
it('加载 .env.dev / .env / .env.local 三层,且逐层覆盖', () => {
|
|
28
|
+
useTempProject({
|
|
29
|
+
'.env.dev': 'PROBE_LAYER=dev\nPROBE_DEV_ONLY=yes\n',
|
|
30
|
+
'.env': 'PROBE_LAYER=env\n',
|
|
31
|
+
'.env.local': 'PROBE_LAYER=local\n',
|
|
32
|
+
});
|
|
33
|
+
delete process.env.PROBE_LAYER;
|
|
34
|
+
delete process.env.PROBE_DEV_ONLY;
|
|
35
|
+
loadEnvironment();
|
|
36
|
+
expect(process.env.PROBE_LAYER).toBe('local');
|
|
37
|
+
expect(process.env.PROBE_DEV_ONLY).toBe('yes');
|
|
38
|
+
});
|
|
39
|
+
it('不加载测试层(ENV 不是 test 时)', () => {
|
|
40
|
+
useTempProject({ '.env.test': 'PROBE_TEST_ONLY=yes\n' });
|
|
41
|
+
process.env.ENV = 'development';
|
|
42
|
+
delete process.env.PROBE_TEST_ONLY;
|
|
43
|
+
loadEnvironment();
|
|
44
|
+
expect(process.env.PROBE_TEST_ONLY).toBeUndefined();
|
|
45
|
+
});
|
|
46
|
+
it('ENV=test 时追加测试层', () => {
|
|
47
|
+
useTempProject({ '.env.test': 'PROBE_TEST_ONLY=yes\n' });
|
|
48
|
+
process.env.ENV = 'test';
|
|
49
|
+
delete process.env.PROBE_TEST_ONLY;
|
|
50
|
+
loadEnvironment();
|
|
51
|
+
expect(process.env.PROBE_TEST_ONLY).toBe('yes');
|
|
52
|
+
});
|
|
53
|
+
it('不给 ENV 时不兜底(运行期必须有明确身份)', () => {
|
|
54
|
+
useTempProject({});
|
|
55
|
+
delete process.env.ENV;
|
|
56
|
+
expect(loadEnvironment().ENV).toBe('');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe('loadEnvironment · test(测试入口)', () => {
|
|
60
|
+
it('无条件加载测试层,不需要调用方先声明 ENV=test', () => {
|
|
61
|
+
useTempProject({ '.env.test': 'PROBE_TEST_ONLY=yes\n' });
|
|
62
|
+
process.env.ENV = 'development';
|
|
63
|
+
delete process.env.PROBE_TEST_ONLY;
|
|
64
|
+
loadEnvironment({ mode: 'test' });
|
|
65
|
+
expect(process.env.PROBE_TEST_ONLY).toBe('yes');
|
|
66
|
+
});
|
|
67
|
+
it('.env.test.local 覆盖 .env.test', () => {
|
|
68
|
+
useTempProject({
|
|
69
|
+
'.env.test': 'PROBE_LAYER=from-test\n',
|
|
70
|
+
'.env.test.local': 'PROBE_LAYER=from-local\n',
|
|
71
|
+
});
|
|
72
|
+
delete process.env.PROBE_LAYER;
|
|
73
|
+
loadEnvironment({ mode: 'test' });
|
|
74
|
+
expect(process.env.PROBE_LAYER).toBe('from-local');
|
|
75
|
+
});
|
|
76
|
+
it('没有任何 ENV 时兜底成 test', () => {
|
|
77
|
+
useTempProject({});
|
|
78
|
+
delete process.env.ENV;
|
|
79
|
+
expect(loadEnvironment({ mode: 'test' }).ENV).toBe('test');
|
|
80
|
+
});
|
|
81
|
+
it('已声明 ENV 时不被兜底改写', () => {
|
|
82
|
+
useTempProject({});
|
|
83
|
+
process.env.ENV = 'development';
|
|
84
|
+
expect(loadEnvironment({ mode: 'test' }).ENV).toBe('development');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe('外部注入优先级(两种模式都必须成立)', () => {
|
|
88
|
+
it('runtime:.env 不覆盖进程已注入的值', async () => {
|
|
89
|
+
useTempProject({ '.env': 'PROBE_LAYER=from-file\n' });
|
|
90
|
+
// 同上:外部注入的快照在模块 import 时建立,重新 import 才能模拟真实注入时机
|
|
91
|
+
vi.resetModules();
|
|
92
|
+
process.env.PROBE_LAYER = 'from-process';
|
|
93
|
+
const fresh = await import('./env.js');
|
|
94
|
+
fresh.loadEnvironment();
|
|
95
|
+
expect(process.env.PROBE_LAYER).toBe('from-process');
|
|
96
|
+
});
|
|
97
|
+
it('test:线上注入的 ENV=production 不被 .env.test 顶掉(环境闸门因此仍会拒绝)', async () => {
|
|
98
|
+
useTempProject({ '.env.test': 'ENV=test\n' });
|
|
99
|
+
// EXTERNAL_ENV 是模块首次 import 时的快照,重新 import 才能模拟「进程启动前注入」
|
|
100
|
+
vi.resetModules();
|
|
101
|
+
process.env.ENV = 'production';
|
|
102
|
+
const fresh = await import('./env.js');
|
|
103
|
+
expect(fresh.loadEnvironment({ mode: 'test' }).ENV).toBe('production');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `core/modules/auth-provider` 的出口:**只有类型,没有实现、没有地址**。
|
|
3
|
+
*
|
|
4
|
+
* 实现由业务服务提供(`createAuthMiddleware({ client })`):框架不建 HTTP 客户端、
|
|
5
|
+
* 不认任何地址、不做签名、不管超时——见 `types.ts` 文件头与设计 §5.2.2。
|
|
6
|
+
*
|
|
7
|
+
* 提供方是**可替换**的:account 只是当前的主要实现,不是框架的预设。
|
|
8
|
+
*/
|
|
9
|
+
export type { AuthClient, AuthVerifyMetadata, AuthVerifyPayload, AuthVerifyResult, RegisterPermissionOperationsPayload, } from './types.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../source/core/modules/auth-provider/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,YAAY,EACV,UAAU,EACV,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,mCAAmC,GACpC,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { PermissionOperation } from '../permission-operations/index.js';
|
|
2
|
+
import type { AccessControl } from '../../type-augment.js';
|
|
3
|
+
/**
|
|
4
|
+
* core `auth-provider` 集成层:**只有最小接口约束,没有实现、没有地址**。
|
|
5
|
+
*
|
|
6
|
+
* 鉴权提供方(Auth Provider)是**可替换**的——account 只是当前的主要实现之一,
|
|
7
|
+
* 任何能「校验凭证 + 接收权限目录」的系统都可以填进来(自建 IAM、OIDC 网关、
|
|
8
|
+
* 单点登录服务、甚至进程内的静态校验)。
|
|
9
|
+
*
|
|
10
|
+
* 为什么只留契约(设计 §5.2.2):
|
|
11
|
+
*
|
|
12
|
+
* - 客户端的**构造**依赖业务侧才知道的东西——`serviceUrl`(来自配置 / 服务发现 /
|
|
13
|
+
* 私有网络)、端点路径(属于提供方的业务契约,不在 framework)、服务凭证、
|
|
14
|
+
* 以及项目自己的 `fetch` / 拦截器 / 超时 / 重试策略。框架猜不出来,也不该猜。
|
|
15
|
+
* - 所以框架只声明「**调用鉴权提供方需要什么形状**」,由每个服务在自己 `config` /
|
|
16
|
+
* `resources` 槽位里实现 `AuthClient`,注入给 `auth` 的 `client` 选项。
|
|
17
|
+
*
|
|
18
|
+
* 契约本身是冻结的(框架只用到两个能力:校验凭证、注册权限操作):改形状等于改契约。
|
|
19
|
+
* **地址与路径不进 framework**——它们随部署环境变化,属于业务配置。
|
|
20
|
+
*/
|
|
21
|
+
/** 校验请求附带的请求元信息(仅供下游审计 / 日志;不参与鉴权判定) */
|
|
22
|
+
export interface AuthVerifyMetadata {
|
|
23
|
+
ip?: string;
|
|
24
|
+
ips?: string[];
|
|
25
|
+
url?: string;
|
|
26
|
+
method?: string;
|
|
27
|
+
referrer?: string;
|
|
28
|
+
requestId?: string;
|
|
29
|
+
userAgent?: string;
|
|
30
|
+
}
|
|
31
|
+
/** `verify` 的请求体:调鉴权提供方的校验能力时提交什么 */
|
|
32
|
+
export interface AuthVerifyPayload {
|
|
33
|
+
token?: string;
|
|
34
|
+
apiKey?: string;
|
|
35
|
+
/** 调用方服务名(`AppIdentity.service`) */
|
|
36
|
+
service: string;
|
|
37
|
+
/** 操作标识(路由 `schema.operationId`,缺省回落 url) */
|
|
38
|
+
operation?: string;
|
|
39
|
+
/** 路由参数(`request.params`),如 `:id` 这类路径参数 */
|
|
40
|
+
resource?: Record<string, unknown>;
|
|
41
|
+
accessControls?: AccessControl[];
|
|
42
|
+
metadata?: AuthVerifyMetadata;
|
|
43
|
+
}
|
|
44
|
+
/** `verify` 的响应数据 */
|
|
45
|
+
export interface AuthVerifyResult {
|
|
46
|
+
user?: unknown;
|
|
47
|
+
token?: unknown;
|
|
48
|
+
}
|
|
49
|
+
/** 权限操作自注册的请求体:调鉴权提供方的权限目录端点时提交什么 */
|
|
50
|
+
export interface RegisterPermissionOperationsPayload {
|
|
51
|
+
service: string;
|
|
52
|
+
version: string;
|
|
53
|
+
operations: PermissionOperation[];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 鉴权客户端契约:框架只需要**两个**方法。
|
|
57
|
+
*
|
|
58
|
+
* 实现归业务服务(`createAuthMiddleware` 的 `client` 选项):**URL / 路径 / 签名 /
|
|
59
|
+
* 协议(HTTP、gRPC、进程内调用)/ 超时 / 重试全在业务侧**,框架不认识任何地址。
|
|
60
|
+
* account 只是其中一种实现,不是唯一一种。
|
|
61
|
+
*
|
|
62
|
+
* 失败语义:
|
|
63
|
+
* - `verify` 抛错 = 校验不通过或提供方不可用;提供方应**自己抛** `UserError` /
|
|
64
|
+
* `SystemError`(框架原样透出),其余错误统一归类为 `EXTERNAL_SERVICE_ERROR`;
|
|
65
|
+
* - `registerPermissionOperations` 抛错**不致命**(`auth` 只记日志,不中断启动)。
|
|
66
|
+
*/
|
|
67
|
+
export interface AuthClient {
|
|
68
|
+
verify(payload: AuthVerifyPayload): Promise<AuthVerifyResult>;
|
|
69
|
+
registerPermissionOperations(payload: RegisterPermissionOperationsPayload): Promise<void>;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../source/core/modules/auth-provider/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAA;AAC5E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAE1D;;;;;;;;;;;;;;;;;GAiBG;AAEH,wCAAwC;AACxC,MAAM,WAAW,kBAAkB;IACjC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,EAAE,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,qCAAqC;AACrC,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAA;IACf,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC,cAAc,CAAC,EAAE,aAAa,EAAE,CAAA;IAChC,QAAQ,CAAC,EAAE,kBAAkB,CAAA;CAC9B;AAED,qBAAqB;AACrB,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,qCAAqC;AACrC,MAAM,WAAW,mCAAmC;IAClD,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,mBAAmB,EAAE,CAAA;CAClC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAC7D,4BAA4B,CAAC,OAAO,EAAE,mCAAmC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1F"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FastifyBaseLogger, FastifyInstance, FastifyRequest } from 'fastify';
|
|
2
|
+
/**
|
|
3
|
+
* core `context`(设计 §5.1)——**框架内置、锁定、不可替换/修改**。
|
|
4
|
+
*
|
|
5
|
+
* 两部分:
|
|
6
|
+
* 1. `RequestContext` / `createContext`:请求级上下文的形状与构造;
|
|
7
|
+
* 2. `registerRequestContext`:把上下文装配到 `request.ctx`(`decorateRequest` + `onRequest`)。
|
|
8
|
+
*
|
|
9
|
+
* `request.ctx` 是 `type-augment` 对外承诺的类型契约,装配必须由框架保证——不能交由
|
|
10
|
+
* 各仓决定「装不装 / 装成什么名字」,否则类型存在而运行时缺失。装饰名固定 `ctx`,无选项。
|
|
11
|
+
*/
|
|
12
|
+
export interface RequestContext {
|
|
13
|
+
log: FastifyBaseLogger;
|
|
14
|
+
}
|
|
15
|
+
export declare function createContext(request: FastifyRequest): RequestContext;
|
|
16
|
+
/** 装配 `request.ctx`(框架固定,先于任何路由 / 业务中间件注册) */
|
|
17
|
+
export declare function registerRequestContext(app: FastifyInstance): void;
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../source/core/modules/context/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAEjF;;;;;;;;;GASG;AAEH,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,iBAAiB,CAAA;CACvB;AAcD,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,CAErE;AAED,8CAA8C;AAC9C,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,CAOjE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const fallbackLogger = {
|
|
2
|
+
trace: () => { },
|
|
3
|
+
debug: () => { },
|
|
4
|
+
info: () => { },
|
|
5
|
+
warn: () => { },
|
|
6
|
+
error: () => { },
|
|
7
|
+
fatal: () => { },
|
|
8
|
+
child: () => fallbackLogger,
|
|
9
|
+
level: 'info',
|
|
10
|
+
silent: () => { },
|
|
11
|
+
};
|
|
12
|
+
export function createContext(request) {
|
|
13
|
+
return { log: request.log || fallbackLogger };
|
|
14
|
+
}
|
|
15
|
+
/** 装配 `request.ctx`(框架固定,先于任何路由 / 业务中间件注册) */
|
|
16
|
+
export function registerRequestContext(app) {
|
|
17
|
+
// 先声明属性(保持 hidden class 稳定),再在 `onRequest` 逐个请求写入
|
|
18
|
+
app.decorateRequest('ctx', null);
|
|
19
|
+
app.addHook('onRequest', (request, _reply, done) => {
|
|
20
|
+
request.ctx = createContext(request);
|
|
21
|
+
done();
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../../../source/core/modules/context/index.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ConfigEnv } from '../../env.js';
|
|
2
|
+
import type { FastifyInstance } from 'fastify';
|
|
3
|
+
/**
|
|
4
|
+
* core `cookie`(设计 §5.1)——**框架内置、锁定、不可替换/修改**。
|
|
5
|
+
*
|
|
6
|
+
* `@fastify/cookie`;cookie 的 domain 从 `ctx.readConfig('server')` 取(**请求期惰性现读**,刷新后即生效),
|
|
7
|
+
* cookie 名由调用方(`auth` 中间件)显式给出;显式设 `sameSite`(默认 `lax`)与 `secure`
|
|
8
|
+
* (默认 `ENV === 'production'`);注册在 `auth` 之前(框架内置序),故 `auth` 读 cookie 无需用户保证。
|
|
9
|
+
*/
|
|
10
|
+
export interface AuthCookieOptions {
|
|
11
|
+
maxAge?: number;
|
|
12
|
+
path?: string;
|
|
13
|
+
domain?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface CookieCoreOptions {
|
|
16
|
+
env: ConfigEnv;
|
|
17
|
+
/** 取最新配置中某个键的访问器(**每次调用现读**,不得缓存返回值) */
|
|
18
|
+
readConfig: (key: string) => unknown;
|
|
19
|
+
}
|
|
20
|
+
export declare function registerCookie(app: FastifyInstance, options: CookieCoreOptions): void;
|
|
21
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../source/core/modules/cookie/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,SAAS,CAAA;AAE5D;;;;;;GAMG;AAEH,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAMD,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,SAAS,CAAA;IACd,wCAAwC;IACxC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAA;CACrC;AAOD,wBAAgB,cAAc,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,iBAAiB,GAAG,IAAI,CAerF"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import cookie from '@fastify/cookie'; // eslint-disable-line import/default -- @fastify/cookie 是 CJS `export =`,esModuleInterop 下默认导入即正确用法
|
|
2
|
+
/** 从 `readConfig('server')` 的返回值读取 cookie domain */
|
|
3
|
+
function resolveCookieDomain(server) {
|
|
4
|
+
return server?.domain;
|
|
5
|
+
}
|
|
6
|
+
export function registerCookie(app, options) {
|
|
7
|
+
app.register(cookie);
|
|
8
|
+
app.decorateReply('setAuthCookie', function setAuthCookie(name, value, cookieOptions = {}) {
|
|
9
|
+
this.setCookie(name, value, {
|
|
10
|
+
path: cookieOptions.path ?? '/',
|
|
11
|
+
maxAge: cookieOptions.maxAge,
|
|
12
|
+
domain: cookieOptions.domain ?? resolveCookieDomain(options.readConfig('server')),
|
|
13
|
+
sameSite: 'lax',
|
|
14
|
+
secure: options.env.ENV === 'production',
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../source/core/modules/identity/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAWjD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,GAAG,IAAI,CAE9E;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,WAAW,GAAG,SAAS,CAExE"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 身份块在 `app` 上的存放键(Symbol,**不进公共契约**)。
|
|
3
|
+
*
|
|
4
|
+
* `defineServer({ app })` 的身份块只属于定义产物;`middlewares` / `routers` 的 ctx 里没有它
|
|
5
|
+
* (可见性按需收窄),框架内置单元(`auth` 权限自注册 / `rateLimit` 前缀)需要 `service` /
|
|
6
|
+
* `version` 时从这里读。
|
|
7
|
+
*/
|
|
8
|
+
const SERVER_IDENTITY = Symbol.for('taicode.server/identity');
|
|
9
|
+
export function attachServerIdentity(app, identity) {
|
|
10
|
+
Object.defineProperty(app, SERVER_IDENTITY, { value: identity, enumerable: false, configurable: true });
|
|
11
|
+
}
|
|
12
|
+
export function readServerIdentity(app) {
|
|
13
|
+
return app?.[SERVER_IDENTITY];
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../source/core/modules/logging/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAsBnD,6BAA6B;AAC7B,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,SAAS,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAQlF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { DEFAULT_LOG_LEVEL } from '../../env.js';
|
|
2
|
+
/**
|
|
3
|
+
* core `logging`(设计 §5.1)——**框架内置、锁定、不可替换/修改**。
|
|
4
|
+
*
|
|
5
|
+
* `createFastify` **总是**建 pino logger(级别取 `LOG_LEVEL`,默认 `info`);原 `logging: false`
|
|
6
|
+
* 开关随 `CoreOptions` 一并删除,日志始终开启,不再由 `debug` 控制。
|
|
7
|
+
*/
|
|
8
|
+
/** 默认 `redact` 路径(设计 §5.1) */
|
|
9
|
+
const LOG_REDACT_PATHS = [
|
|
10
|
+
'authorization',
|
|
11
|
+
'cookie',
|
|
12
|
+
'set-cookie',
|
|
13
|
+
'req.headers.authorization',
|
|
14
|
+
'req.headers.cookie',
|
|
15
|
+
'*.token',
|
|
16
|
+
'*.password',
|
|
17
|
+
'*.secretKey',
|
|
18
|
+
'*.secretValue',
|
|
19
|
+
];
|
|
20
|
+
/** 构造 Fastify 的 logger 选项 */
|
|
21
|
+
export function createLoggerOptions(env) {
|
|
22
|
+
return {
|
|
23
|
+
level: env.LOG_LEVEL || DEFAULT_LOG_LEVEL,
|
|
24
|
+
redact: {
|
|
25
|
+
paths: [...LOG_REDACT_PATHS],
|
|
26
|
+
censor: '[Redacted]',
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
export interface OpenApiCoreOptions {
|
|
3
|
+
title: string;
|
|
4
|
+
version: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function registerOpenApi(app: FastifyInstance, options: OpenApiCoreOptions): Promise<void>;
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../source/core/modules/openapi/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAgB9C,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BtG"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import swagger from '@fastify/swagger';
|
|
2
|
+
import { createJsonSchemaTransform } from 'fastify-type-provider-zod';
|
|
3
|
+
/**
|
|
4
|
+
* core `openapi`(设计 §5.1)——**框架内置、锁定、不可替换/修改**。
|
|
5
|
+
*
|
|
6
|
+
* 只做**文档生成**:`@fastify/swagger` 动态模式(不注册任何 HTTP 路由),`servers: [{ url: '/' }]`
|
|
7
|
+
* (构建期不依赖 env / DB)、`version` 取 `app.version`(必填,无默认)、`securitySchemes.bearerAuth`。
|
|
8
|
+
*
|
|
9
|
+
* 文档的**出口**在系统路由里(`GET /system/openapi`,见 `core/modules/system-routes`):
|
|
10
|
+
* 文档是服务的一部分元信息,不该单独占一个顶层前缀;也不再需要 UI——
|
|
11
|
+
* `@scalar/fastify-api-reference` 已移除,各仓 console 用自己的 api-gen 消费 spec。
|
|
12
|
+
*/
|
|
13
|
+
/** OpenAPI 规范版本(固定,不可配置) */
|
|
14
|
+
const OPENAPI_SPEC_VERSION = '3.0.3';
|
|
15
|
+
export async function registerOpenApi(app, options) {
|
|
16
|
+
await app.register(swagger, {
|
|
17
|
+
openapi: {
|
|
18
|
+
openapi: OPENAPI_SPEC_VERSION,
|
|
19
|
+
info: {
|
|
20
|
+
title: options.title,
|
|
21
|
+
version: options.version,
|
|
22
|
+
},
|
|
23
|
+
// 构建期不依赖 env / DB(设计 §5.1 / §3.2 规则 A/B)
|
|
24
|
+
servers: [{ url: '/' }],
|
|
25
|
+
components: {
|
|
26
|
+
securitySchemes: {
|
|
27
|
+
bearerAuth: { type: 'http', scheme: 'bearer' },
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
security: [{ bearerAuth: [] }],
|
|
31
|
+
},
|
|
32
|
+
transform: createJsonSchemaTransform({ skipList: [] }),
|
|
33
|
+
});
|
|
34
|
+
// 确定性冲刷插件队列:让 swagger 的 root `onRoute` 钩子在**任何路由注册之前**就位——
|
|
35
|
+
// 后续 `routers` 阶段注册的路由才会被采集进文档。不得依赖「恰好有 cors 等其它
|
|
36
|
+
// `app.register` 顺带冲刷队列」这种隐式巧合(设计 §5.2 / §10-18)。
|
|
37
|
+
await app.after();
|
|
38
|
+
app.addHook('onListen', () => {
|
|
39
|
+
app.log.info({ docPath: '/system/openapi' }, '[openapi] spec ready');
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 权限操作提取(设计 §5.2)——**框架内置**。
|
|
3
|
+
*
|
|
4
|
+
* **唯一来源是 OpenAPI 文档**:`openapi` 已是 core 内置能力(§5.1),文档里的 `paths` 就是
|
|
5
|
+
* 本服务对外的完整操作面。权限目录直接从文档提取,**不再自建第二份路由注册表**——两套来源
|
|
6
|
+
* 必然漂移(`HEAD` / 前缀 / 插件路由的处理只要有一处不同就会对不上)。
|
|
7
|
+
*
|
|
8
|
+
* 文档里有什么就是什么:框架不给任何路由开「不进文档」的口子,因此也不存在
|
|
9
|
+
* 「路由活着、权限系统里却没有」的隐式状态。
|
|
10
|
+
*
|
|
11
|
+
* 目录元素形状(`{ operation, summary?, description?, tags? }`)与各仓原自写实现一致:
|
|
12
|
+
*
|
|
13
|
+
* - 缺 `operationId` 的条目(如 `parameters` / vendor 扩展)**跳过**;
|
|
14
|
+
* - 缺 `paths` / 空文档 → 返回 `[]`;
|
|
15
|
+
* - `summary` / `description` / `tags` 缺省分别归一为 `''` / `''` / `[]`。
|
|
16
|
+
* (正常路径下不会发生:core 的 schema 守卫已在注册期强制四项齐全。)
|
|
17
|
+
*/
|
|
18
|
+
/** 单条权限操作(对齐 account `POST /permission/operation/register` 的 `operations` 元素) */
|
|
19
|
+
export interface PermissionOperation {
|
|
20
|
+
operation: string;
|
|
21
|
+
summary: string;
|
|
22
|
+
description: string;
|
|
23
|
+
tags: string[];
|
|
24
|
+
}
|
|
25
|
+
export declare function extractPermissionOperations(openApiSpec: unknown): PermissionOperation[];
|
|
26
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../source/core/modules/permission-operations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,iFAAiF;AACjF,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,EAAE,CAAA;CACf;AAeD,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,OAAO,GAAG,mBAAmB,EAAE,CAoBvF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 权限操作提取(设计 §5.2)——**框架内置**。
|
|
3
|
+
*
|
|
4
|
+
* **唯一来源是 OpenAPI 文档**:`openapi` 已是 core 内置能力(§5.1),文档里的 `paths` 就是
|
|
5
|
+
* 本服务对外的完整操作面。权限目录直接从文档提取,**不再自建第二份路由注册表**——两套来源
|
|
6
|
+
* 必然漂移(`HEAD` / 前缀 / 插件路由的处理只要有一处不同就会对不上)。
|
|
7
|
+
*
|
|
8
|
+
* 文档里有什么就是什么:框架不给任何路由开「不进文档」的口子,因此也不存在
|
|
9
|
+
* 「路由活着、权限系统里却没有」的隐式状态。
|
|
10
|
+
*
|
|
11
|
+
* 目录元素形状(`{ operation, summary?, description?, tags? }`)与各仓原自写实现一致:
|
|
12
|
+
*
|
|
13
|
+
* - 缺 `operationId` 的条目(如 `parameters` / vendor 扩展)**跳过**;
|
|
14
|
+
* - 缺 `paths` / 空文档 → 返回 `[]`;
|
|
15
|
+
* - `summary` / `description` / `tags` 缺省分别归一为 `''` / `''` / `[]`。
|
|
16
|
+
* (正常路径下不会发生:core 的 schema 守卫已在注册期强制四项齐全。)
|
|
17
|
+
*/
|
|
18
|
+
export function extractPermissionOperations(openApiSpec) {
|
|
19
|
+
// 出 spec 路径拿到的文档是未知形态;只按结构取字段,缺什么就跳过什么
|
|
20
|
+
const spec = (openApiSpec ?? {});
|
|
21
|
+
const operations = [];
|
|
22
|
+
for (const pathItem of Object.values(spec.paths ?? {})) {
|
|
23
|
+
for (const operation of Object.values(pathItem ?? {})) {
|
|
24
|
+
if (!operation?.operationId) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
operations.push({
|
|
28
|
+
operation: operation.operationId,
|
|
29
|
+
summary: operation.summary ?? '',
|
|
30
|
+
description: operation.description ?? '',
|
|
31
|
+
tags: operation.tags ?? [],
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return operations;
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../../../source/core/modules/permission-operations/index.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { extractPermissionOperations } from './index.js';
|
|
3
|
+
describe('extractPermissionOperations', () => {
|
|
4
|
+
it('从 OpenAPI 文档的 paths 提取操作', () => {
|
|
5
|
+
const spec = {
|
|
6
|
+
paths: {
|
|
7
|
+
'/user': {
|
|
8
|
+
get: { operationId: 'listUser', summary: '列表', description: '分页列出用户', tags: ['用户'] },
|
|
9
|
+
post: { operationId: 'createUser', summary: '创建', description: '创建用户', tags: ['用户'] },
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
expect(extractPermissionOperations(spec)).toEqual([
|
|
14
|
+
{ operation: 'listUser', summary: '列表', description: '分页列出用户', tags: ['用户'] },
|
|
15
|
+
{ operation: 'createUser', summary: '创建', description: '创建用户', tags: ['用户'] },
|
|
16
|
+
]);
|
|
17
|
+
});
|
|
18
|
+
it('跳过没有 operationId 的条目(parameters / vendor 扩展 / 未声明 schema 的路由)', () => {
|
|
19
|
+
const spec = {
|
|
20
|
+
paths: {
|
|
21
|
+
'/user': {
|
|
22
|
+
parameters: [{ name: 'id', in: 'path' }],
|
|
23
|
+
get: { operationId: 'listUser' },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
expect(extractPermissionOperations(spec).map((item) => item.operation)).toEqual(['listUser']);
|
|
28
|
+
});
|
|
29
|
+
it('summary / description / tags 缺省归一为空值', () => {
|
|
30
|
+
const spec = { paths: { '/user': { get: { operationId: 'listUser' } } } };
|
|
31
|
+
expect(extractPermissionOperations(spec)).toEqual([
|
|
32
|
+
{ operation: 'listUser', summary: '', description: '', tags: [] },
|
|
33
|
+
]);
|
|
34
|
+
});
|
|
35
|
+
it('文档缺失或没有 paths 时返回空数组', () => {
|
|
36
|
+
expect(extractPermissionOperations(undefined)).toEqual([]);
|
|
37
|
+
expect(extractPermissionOperations(null)).toEqual([]);
|
|
38
|
+
expect(extractPermissionOperations({})).toEqual([]);
|
|
39
|
+
expect(extractPermissionOperations({ paths: {} })).toEqual([]);
|
|
40
|
+
});
|
|
41
|
+
it('只按 paths 取,文档里没有的条目不会凭空出现', () => {
|
|
42
|
+
const spec = { paths: { '/user': { get: { operationId: 'listUser' } } } };
|
|
43
|
+
expect(extractPermissionOperations(spec).map((item) => item.operation)).toEqual(['listUser']);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { FastifyInstance } from 'fastify';
|
|
2
|
+
/**
|
|
3
|
+
* core `requestId`(设计 §5.1 / §8)——**框架内置、锁定、不可替换/修改**。
|
|
4
|
+
*
|
|
5
|
+
* 入站 header(固定 `x-request-id`)存在且**非空** → 复用,否则用 `randomUUID` 写入 `request.id`;
|
|
6
|
+
* **响应统一回吐 `reply.header('x-request-id', request.id)`**(进日志 + 进响应 header,
|
|
7
|
+
* **不进响应体**);日志字段键名统一为全拼 `requestId`。
|
|
8
|
+
*
|
|
9
|
+
* 归一语义(实现裁定 §10-8):入站值 **trim 后为空**视为缺失 → 重新生成 `randomUUID`;
|
|
10
|
+
* 非空则 **trim 后复用**。Fastify 的 `requestIdHeader` 对**已存在**的 header 直接取用
|
|
11
|
+
* (`headers[name] || genReqId`),故空白串(`' '`)会原样透传、带空格的也会保留——由下面的
|
|
12
|
+
* `onRequest` 钩子在链路最前补齐归一,并同步 `request.id` / 日志字段 / 响应 header。
|
|
13
|
+
*/
|
|
14
|
+
export declare const REQUEST_ID_HEADER = "x-request-id";
|
|
15
|
+
/** 传给 `Fastify()` 的 requestId 相关选项 */
|
|
16
|
+
export declare function createRequestIdOptions(): {
|
|
17
|
+
requestIdHeader: string;
|
|
18
|
+
requestIdLogLabel: string;
|
|
19
|
+
genReqId: (request: {
|
|
20
|
+
headers: Record<string, string | string[] | undefined>;
|
|
21
|
+
}) => string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* 注册 `requestId` 钩子(设计 §5.1 / §8):
|
|
25
|
+
*
|
|
26
|
+
* ① `onRequest` 归一化入站 `x-request-id`——`trim` 后为空 → 重新 `randomUUID`,非空 → `trim` 后复用。
|
|
27
|
+
* ② `onSend` 响应统一回吐 `x-request-id`(进日志 + 进响应 header,**不进响应体**)。
|
|
28
|
+
*
|
|
29
|
+
* Fastify 在 `onRequest` **之前**已用原始值建好 child logger,故归一后须重绑日志,才能保证
|
|
30
|
+
* 入口 `request.id`、日志字段 `requestId`、响应 header 三处一致。
|
|
31
|
+
*/
|
|
32
|
+
export declare function registerRequestId(app: FastifyInstance): void;
|
|
33
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../source/core/modules/request-id/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE9C;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,iBAAiB,iBAAiB,CAAA;AAa/C,sCAAsC;AACtC,wBAAgB,sBAAsB,IAAI;IACxC,eAAe,EAAE,MAAM,CAAA;IACvB,iBAAiB,EAAE,MAAM,CAAA;IACzB,QAAQ,EAAE,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAA;KAAE,KAAK,MAAM,CAAA;CAC1F,CAOA;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,CAwB5D"}
|