zentao-api 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -2
- package/dist/browser/zentao-api.global.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/modules/define.d.ts +63 -0
- package/dist/modules/define.js +119 -0
- package/dist/modules/override.d.ts +60 -0
- package/dist/modules/override.js +113 -0
- package/dist/modules/query.d.ts +44 -0
- package/dist/modules/query.js +68 -0
- package/dist/modules/registry-store.d.ts +31 -0
- package/dist/modules/registry-store.js +133 -0
- package/dist/modules/registry.d.ts +4 -82
- package/dist/modules/registry.js +16 -221
- package/dist/types/client.d.ts +38 -0
- package/dist/types/client.js +1 -0
- package/dist/types/data.d.ts +42 -0
- package/dist/types/data.js +1 -0
- package/dist/types/index.d.ts +17 -359
- package/dist/types/index.js +17 -1
- package/dist/types/module.d.ts +124 -0
- package/dist/types/module.js +1 -0
- package/dist/types/options.d.ts +35 -0
- package/dist/types/options.js +1 -0
- package/dist/types/profile.d.ts +54 -0
- package/dist/types/profile.js +1 -0
- package/dist/types/response.d.ts +70 -0
- package/dist/types/response.js +1 -0
- package/dist/version.js +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ModuleAction, ModuleDefinition } from '../types/index.js';
|
|
2
|
+
/** {@link defineModules} 的选项。 */
|
|
3
|
+
export interface DefineModulesOptions {
|
|
4
|
+
/**
|
|
5
|
+
* 同名模块的写入策略。
|
|
6
|
+
*
|
|
7
|
+
* - `false`(默认):合并模块的元数据,并按动作名对动作做"同名替换、未知追加"。
|
|
8
|
+
* - `true`:整体替换已存在的模块定义,原有动作会被丢弃。
|
|
9
|
+
*/
|
|
10
|
+
replace?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 注册或扩展模块定义。
|
|
14
|
+
*
|
|
15
|
+
* 行为细节:
|
|
16
|
+
* - 模块名匹配大小写不敏感。
|
|
17
|
+
* - 未知模块直接追加到注册表末尾。
|
|
18
|
+
* - 已存在的模块默认按 `mergeModule` 合并:模块元数据浅合并、动作按名同名替换/未知追加;
|
|
19
|
+
* `options.replace` 为 `true` 时整体替换。
|
|
20
|
+
* - 所有写入都会做深克隆 + 深冻结:调用方后续修改自己的对象不会污染注册表,注册表也不可被外部改写。
|
|
21
|
+
*
|
|
22
|
+
* @param input - 单个或一组模块定义。
|
|
23
|
+
* @param options - 写入策略,参见 {@link DefineModulesOptions}。
|
|
24
|
+
* @throws {ZentaoError} `E_INVALID_MODULE_DEFINITION` —— 缺少 `name` 或 `actions` 字段。
|
|
25
|
+
*/
|
|
26
|
+
export declare function defineModules(input: ModuleDefinition | ModuleDefinition[], options?: DefineModulesOptions): void;
|
|
27
|
+
/**
|
|
28
|
+
* 为已存在的模块追加或覆盖动作。
|
|
29
|
+
*
|
|
30
|
+
* 不做深度合并:同名动作整体替换,未知动作追加。这避免在 schema、参数数组等字段上出现隐式合并规则。
|
|
31
|
+
*
|
|
32
|
+
* @param moduleName - 目标模块名(大小写不敏感)。
|
|
33
|
+
* @param input - 单个或一组动作定义。
|
|
34
|
+
* @throws {ZentaoError} `E_INVALID_MODULE`(模块未注册)或 `E_INVALID_ACTION_DEFINITION`
|
|
35
|
+
* (动作缺少 `name` / `path` / `method` 等必填字段)。
|
|
36
|
+
*/
|
|
37
|
+
export declare function defineModuleActions(moduleName: string, input: ModuleAction | ModuleAction[]): void;
|
|
38
|
+
/**
|
|
39
|
+
* 扩展已存在的模块动作。
|
|
40
|
+
*
|
|
41
|
+
* 与 {@link defineModuleActions} 的「整体替换」不同,这里对单个动作做**深度合并**:
|
|
42
|
+
* 只需给出待修改的字段,其余字段沿用原动作定义。普通对象递归合并,
|
|
43
|
+
* 数组及其他值由补丁整体替换,值为 `undefined` 的键会被忽略。
|
|
44
|
+
*
|
|
45
|
+
* 当 `action` 为函数时**不做合并**:会以当前动作的深克隆为入参调用它,
|
|
46
|
+
* 其返回值作为完整的动作定义直接取代原动作定义。
|
|
47
|
+
*
|
|
48
|
+
* @param moduleName - 目标模块名(大小写不敏感)。
|
|
49
|
+
* @param actionName - 目标动作名(大小写不敏感)。
|
|
50
|
+
* @param action - 深度合并的补丁对象,或接收当前动作深克隆并返回完整动作定义的函数。
|
|
51
|
+
* @throws {ZentaoError} `E_INVALID_MODULE`(模块未注册)、`E_INVALID_ACTION`(动作不存在)
|
|
52
|
+
* 或 `E_INVALID_ACTION_DEFINITION`(合并结果缺少 `name` / `path` / `method` 等必填字段)。
|
|
53
|
+
*/
|
|
54
|
+
export declare function extendModuleAction(moduleName: string, actionName: string, action: Partial<ModuleAction> | ((action: ModuleAction) => ModuleAction)): void;
|
|
55
|
+
/**
|
|
56
|
+
* 将注册表重置为内置基线。
|
|
57
|
+
*
|
|
58
|
+
* 重置会触发 store 的「重置后钩子」,由 barrel(`./registry.ts`)在其中重新应用内置覆盖
|
|
59
|
+
* (见 `./override.ts`),因此重置后内置扩展依旧生效。
|
|
60
|
+
*
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
export declare function resetModuleDefinitions(): void;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { ZentaoError } from '../misc/errors.js';
|
|
2
|
+
import { asArray } from '../utils/index.js';
|
|
3
|
+
import { deepClone, deepMerge, findActionIndex, freezeAction, freezeModule, getModuleMapState, getModulesState, mergeModule, rebuildModuleMap, resetState, validateAction, validateModule, } from './registry-store.js';
|
|
4
|
+
/**
|
|
5
|
+
* 注册或扩展模块定义。
|
|
6
|
+
*
|
|
7
|
+
* 行为细节:
|
|
8
|
+
* - 模块名匹配大小写不敏感。
|
|
9
|
+
* - 未知模块直接追加到注册表末尾。
|
|
10
|
+
* - 已存在的模块默认按 `mergeModule` 合并:模块元数据浅合并、动作按名同名替换/未知追加;
|
|
11
|
+
* `options.replace` 为 `true` 时整体替换。
|
|
12
|
+
* - 所有写入都会做深克隆 + 深冻结:调用方后续修改自己的对象不会污染注册表,注册表也不可被外部改写。
|
|
13
|
+
*
|
|
14
|
+
* @param input - 单个或一组模块定义。
|
|
15
|
+
* @param options - 写入策略,参见 {@link DefineModulesOptions}。
|
|
16
|
+
* @throws {ZentaoError} `E_INVALID_MODULE_DEFINITION` —— 缺少 `name` 或 `actions` 字段。
|
|
17
|
+
*/
|
|
18
|
+
export function defineModules(input, options = {}) {
|
|
19
|
+
const modules = getModulesState();
|
|
20
|
+
for (const module of asArray(input)) {
|
|
21
|
+
validateModule(module);
|
|
22
|
+
const key = module.name.toLowerCase();
|
|
23
|
+
const index = modules.findIndex((item) => item.name.toLowerCase() === key);
|
|
24
|
+
if (index >= 0) {
|
|
25
|
+
modules[index] = options.replace
|
|
26
|
+
? freezeModule(deepClone(module))
|
|
27
|
+
: mergeModule(modules[index], module);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
modules.push(freezeModule(deepClone(module)));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
rebuildModuleMap();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 为已存在的模块追加或覆盖动作。
|
|
37
|
+
*
|
|
38
|
+
* 不做深度合并:同名动作整体替换,未知动作追加。这避免在 schema、参数数组等字段上出现隐式合并规则。
|
|
39
|
+
*
|
|
40
|
+
* @param moduleName - 目标模块名(大小写不敏感)。
|
|
41
|
+
* @param input - 单个或一组动作定义。
|
|
42
|
+
* @throws {ZentaoError} `E_INVALID_MODULE`(模块未注册)或 `E_INVALID_ACTION_DEFINITION`
|
|
43
|
+
* (动作缺少 `name` / `path` / `method` 等必填字段)。
|
|
44
|
+
*/
|
|
45
|
+
export function defineModuleActions(moduleName, input) {
|
|
46
|
+
const key = moduleName.toLowerCase();
|
|
47
|
+
const module = getModuleMapState().get(key);
|
|
48
|
+
if (!module) {
|
|
49
|
+
throw new ZentaoError('E_INVALID_MODULE', { module: moduleName });
|
|
50
|
+
}
|
|
51
|
+
const actions = module.actions.slice();
|
|
52
|
+
for (const action of asArray(input)) {
|
|
53
|
+
validateAction(action);
|
|
54
|
+
const index = findActionIndex(actions, String(action.name));
|
|
55
|
+
const frozen = freezeAction(deepClone(action));
|
|
56
|
+
// 同名动作替换,未知动作追加;不做深度合并,避免 schema/数组字段出现隐式规则。
|
|
57
|
+
if (index >= 0) {
|
|
58
|
+
actions[index] = frozen;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
actions.push(frozen);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const nextModule = freezeModule({ ...module, actions });
|
|
65
|
+
const modules = getModulesState();
|
|
66
|
+
const index = modules.findIndex((item) => item.name.toLowerCase() === key);
|
|
67
|
+
modules[index] = nextModule;
|
|
68
|
+
rebuildModuleMap();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 扩展已存在的模块动作。
|
|
72
|
+
*
|
|
73
|
+
* 与 {@link defineModuleActions} 的「整体替换」不同,这里对单个动作做**深度合并**:
|
|
74
|
+
* 只需给出待修改的字段,其余字段沿用原动作定义。普通对象递归合并,
|
|
75
|
+
* 数组及其他值由补丁整体替换,值为 `undefined` 的键会被忽略。
|
|
76
|
+
*
|
|
77
|
+
* 当 `action` 为函数时**不做合并**:会以当前动作的深克隆为入参调用它,
|
|
78
|
+
* 其返回值作为完整的动作定义直接取代原动作定义。
|
|
79
|
+
*
|
|
80
|
+
* @param moduleName - 目标模块名(大小写不敏感)。
|
|
81
|
+
* @param actionName - 目标动作名(大小写不敏感)。
|
|
82
|
+
* @param action - 深度合并的补丁对象,或接收当前动作深克隆并返回完整动作定义的函数。
|
|
83
|
+
* @throws {ZentaoError} `E_INVALID_MODULE`(模块未注册)、`E_INVALID_ACTION`(动作不存在)
|
|
84
|
+
* 或 `E_INVALID_ACTION_DEFINITION`(合并结果缺少 `name` / `path` / `method` 等必填字段)。
|
|
85
|
+
*/
|
|
86
|
+
export function extendModuleAction(moduleName, actionName, action) {
|
|
87
|
+
const key = moduleName.toLowerCase();
|
|
88
|
+
const module = getModuleMapState().get(key);
|
|
89
|
+
if (!module) {
|
|
90
|
+
throw new ZentaoError('E_INVALID_MODULE', { module: moduleName });
|
|
91
|
+
}
|
|
92
|
+
const actions = module.actions.slice();
|
|
93
|
+
const actionIndex = findActionIndex(actions, actionName);
|
|
94
|
+
if (actionIndex < 0) {
|
|
95
|
+
throw new ZentaoError('E_INVALID_ACTION', { module: moduleName, action: actionName });
|
|
96
|
+
}
|
|
97
|
+
const current = actions[actionIndex];
|
|
98
|
+
// 函数:以当前动作的深克隆为入参,返回值作为完整动作定义直接取代原定义,不做合并。
|
|
99
|
+
// 对象:作为补丁与原动作深度合并。
|
|
100
|
+
const next = typeof action === 'function' ? action(deepClone(current)) : deepMerge(current, action);
|
|
101
|
+
validateAction(next);
|
|
102
|
+
actions[actionIndex] = freezeAction(next);
|
|
103
|
+
const nextModule = freezeModule({ ...module, actions });
|
|
104
|
+
const modules = getModulesState();
|
|
105
|
+
const index = modules.findIndex((item) => item.name.toLowerCase() === key);
|
|
106
|
+
modules[index] = nextModule;
|
|
107
|
+
rebuildModuleMap();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 将注册表重置为内置基线。
|
|
111
|
+
*
|
|
112
|
+
* 重置会触发 store 的「重置后钩子」,由 barrel(`./registry.ts`)在其中重新应用内置覆盖
|
|
113
|
+
* (见 `./override.ts`),因此重置后内置扩展依旧生效。
|
|
114
|
+
*
|
|
115
|
+
* @internal
|
|
116
|
+
*/
|
|
117
|
+
export function resetModuleDefinitions() {
|
|
118
|
+
resetState();
|
|
119
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 内置覆盖 / 扩展定义。
|
|
3
|
+
*
|
|
4
|
+
* 这里集中存放对自动生成注册表(`./generated.ts`)的手工扩展:补充缺失的模块动作、
|
|
5
|
+
* 修正个别动作的元数据,或登记 OpenAPI 尚未覆盖的自定义模块。
|
|
6
|
+
*
|
|
7
|
+
* 与「用户运行时调用 {@link defineModules}」不同,这里的定义会在模块加载时自动应用,
|
|
8
|
+
* 并在 {@link resetModuleDefinitions} 重置后重新应用,因此它们等同于**内置定义**,
|
|
9
|
+
* 会随 SDK 一起发布。
|
|
10
|
+
*
|
|
11
|
+
* 维护约定:
|
|
12
|
+
* - 不要修改 `./generated.ts`(它由 `scripts/update-registry.ts` 自动生成)。
|
|
13
|
+
* 能通过更新 OpenAPI 数据解决的,优先走生成流程;只有生成器无法表达的扩展才写在这里。
|
|
14
|
+
* - 复用 {@link defineModules} / {@link defineModuleActions} 的语义:
|
|
15
|
+
* - {@link defineModuleActions}:为**已存在**的模块追加动作(同名替换、未知追加)。
|
|
16
|
+
* - {@link defineModules}:登记**新模块**,或对已存在模块做合并 / 整体替换(`replace`)。
|
|
17
|
+
* - 写入会自动深克隆 + 深冻结,无需自己处理不可变性。
|
|
18
|
+
*
|
|
19
|
+
* @example 为已存在的 `bug` 模块补充一个自定义动作:
|
|
20
|
+
* ```ts
|
|
21
|
+
* defineModuleActions('bug', {
|
|
22
|
+
* name: 'assignTo',
|
|
23
|
+
* display: '指派 Bug',
|
|
24
|
+
* type: 'action',
|
|
25
|
+
* method: 'put',
|
|
26
|
+
* path: '/bugs/{bugID}/assignto',
|
|
27
|
+
* resultType: 'text',
|
|
28
|
+
* pathParams: { bugID: 'Bug ID' },
|
|
29
|
+
* requestBody: {
|
|
30
|
+
* required: true,
|
|
31
|
+
* schema: {
|
|
32
|
+
* assignedTo: { type: 'string', description: '指派给' },
|
|
33
|
+
* comment: { type: 'string', description: '备注' },
|
|
34
|
+
* },
|
|
35
|
+
* },
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @example 登记一个 OpenAPI 未覆盖的新模块:
|
|
40
|
+
* ```ts
|
|
41
|
+
* defineModules({
|
|
42
|
+
* name: 'custom',
|
|
43
|
+
* display: '自定义模块',
|
|
44
|
+
* actions: [
|
|
45
|
+
* {
|
|
46
|
+
* name: 'list',
|
|
47
|
+
* type: 'list',
|
|
48
|
+
* method: 'get',
|
|
49
|
+
* path: '/customs',
|
|
50
|
+
* resultType: 'list',
|
|
51
|
+
* pagerGetter: 'pager',
|
|
52
|
+
* resultGetter: 'customs',
|
|
53
|
+
* },
|
|
54
|
+
* ],
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @internal
|
|
59
|
+
*/
|
|
60
|
+
export declare function applyBuiltinOverrides(): void;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { extendModuleAction } from './define.js';
|
|
2
|
+
/**
|
|
3
|
+
* 内置覆盖 / 扩展定义。
|
|
4
|
+
*
|
|
5
|
+
* 这里集中存放对自动生成注册表(`./generated.ts`)的手工扩展:补充缺失的模块动作、
|
|
6
|
+
* 修正个别动作的元数据,或登记 OpenAPI 尚未覆盖的自定义模块。
|
|
7
|
+
*
|
|
8
|
+
* 与「用户运行时调用 {@link defineModules}」不同,这里的定义会在模块加载时自动应用,
|
|
9
|
+
* 并在 {@link resetModuleDefinitions} 重置后重新应用,因此它们等同于**内置定义**,
|
|
10
|
+
* 会随 SDK 一起发布。
|
|
11
|
+
*
|
|
12
|
+
* 维护约定:
|
|
13
|
+
* - 不要修改 `./generated.ts`(它由 `scripts/update-registry.ts` 自动生成)。
|
|
14
|
+
* 能通过更新 OpenAPI 数据解决的,优先走生成流程;只有生成器无法表达的扩展才写在这里。
|
|
15
|
+
* - 复用 {@link defineModules} / {@link defineModuleActions} 的语义:
|
|
16
|
+
* - {@link defineModuleActions}:为**已存在**的模块追加动作(同名替换、未知追加)。
|
|
17
|
+
* - {@link defineModules}:登记**新模块**,或对已存在模块做合并 / 整体替换(`replace`)。
|
|
18
|
+
* - 写入会自动深克隆 + 深冻结,无需自己处理不可变性。
|
|
19
|
+
*
|
|
20
|
+
* @example 为已存在的 `bug` 模块补充一个自定义动作:
|
|
21
|
+
* ```ts
|
|
22
|
+
* defineModuleActions('bug', {
|
|
23
|
+
* name: 'assignTo',
|
|
24
|
+
* display: '指派 Bug',
|
|
25
|
+
* type: 'action',
|
|
26
|
+
* method: 'put',
|
|
27
|
+
* path: '/bugs/{bugID}/assignto',
|
|
28
|
+
* resultType: 'text',
|
|
29
|
+
* pathParams: { bugID: 'Bug ID' },
|
|
30
|
+
* requestBody: {
|
|
31
|
+
* required: true,
|
|
32
|
+
* schema: {
|
|
33
|
+
* assignedTo: { type: 'string', description: '指派给' },
|
|
34
|
+
* comment: { type: 'string', description: '备注' },
|
|
35
|
+
* },
|
|
36
|
+
* },
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @example 登记一个 OpenAPI 未覆盖的新模块:
|
|
41
|
+
* ```ts
|
|
42
|
+
* defineModules({
|
|
43
|
+
* name: 'custom',
|
|
44
|
+
* display: '自定义模块',
|
|
45
|
+
* actions: [
|
|
46
|
+
* {
|
|
47
|
+
* name: 'list',
|
|
48
|
+
* type: 'list',
|
|
49
|
+
* method: 'get',
|
|
50
|
+
* path: '/customs',
|
|
51
|
+
* resultType: 'list',
|
|
52
|
+
* pagerGetter: 'pager',
|
|
53
|
+
* resultGetter: 'customs',
|
|
54
|
+
* },
|
|
55
|
+
* ],
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @internal
|
|
60
|
+
*/
|
|
61
|
+
export function applyBuiltinOverrides() {
|
|
62
|
+
// 创建执行时,需要添加产品字段
|
|
63
|
+
extendModuleAction('execution', 'create', (action) => {
|
|
64
|
+
const required = action.requestBody.schema?.required;
|
|
65
|
+
if (Array.isArray(required) && !required.includes('products')) {
|
|
66
|
+
required.push('products');
|
|
67
|
+
}
|
|
68
|
+
return action;
|
|
69
|
+
});
|
|
70
|
+
// 修改 story/update 字段定义
|
|
71
|
+
extendModuleAction('story', 'update', (action) => {
|
|
72
|
+
const properties = action.requestBody.schema?.properties;
|
|
73
|
+
// 为 story/update 增加 plan 字段
|
|
74
|
+
if (properties && !properties.plan) {
|
|
75
|
+
properties.plan = {
|
|
76
|
+
type: 'integer',
|
|
77
|
+
description: '所属计划',
|
|
78
|
+
format: 'int32',
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
// 修改 category 字段类型为 string
|
|
82
|
+
if (properties && properties.category) {
|
|
83
|
+
properties.category = {
|
|
84
|
+
type: 'string',
|
|
85
|
+
description: '类别',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return action;
|
|
89
|
+
});
|
|
90
|
+
// 修改 task/list URL 定义
|
|
91
|
+
extendModuleAction('task', 'list', (action) => {
|
|
92
|
+
action.path = '/executions/{executionID}/tasks';
|
|
93
|
+
action.pathParams = {
|
|
94
|
+
executionID: '执行ID',
|
|
95
|
+
};
|
|
96
|
+
return action;
|
|
97
|
+
});
|
|
98
|
+
// 修改 acl 字段默认值为 open
|
|
99
|
+
[
|
|
100
|
+
['product', 'create'],
|
|
101
|
+
['product', 'update'],
|
|
102
|
+
['execution', 'create'],
|
|
103
|
+
['execution', 'update'],
|
|
104
|
+
].forEach(([moduleName, actionName]) => {
|
|
105
|
+
extendModuleAction(moduleName, actionName, (action) => {
|
|
106
|
+
const properties = action.requestBody.schema?.properties;
|
|
107
|
+
if (properties.acl && properties.acl.defaultValue === undefined) {
|
|
108
|
+
properties.acl.defaultValue = 'open';
|
|
109
|
+
}
|
|
110
|
+
return action;
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ModuleAction, ModuleDefinition } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* 获取模块定义。
|
|
4
|
+
*
|
|
5
|
+
* 模块名匹配大小写不敏感。返回值是注册表内部的已深冻结引用(O(1) 查询、零拷贝),
|
|
6
|
+
* 任何写入尝试在严格模式下会抛 `TypeError`;如需修改请使用 {@link defineModules}。
|
|
7
|
+
*
|
|
8
|
+
* @param moduleName - 模块名。
|
|
9
|
+
* @returns 已注册的模块定义。
|
|
10
|
+
* @throws {ZentaoError} `E_INVALID_MODULE` —— 模块未注册。
|
|
11
|
+
*/
|
|
12
|
+
export declare function getModule(moduleName: string): ModuleDefinition;
|
|
13
|
+
/**
|
|
14
|
+
* 获取指定模块下的某个动作。
|
|
15
|
+
*
|
|
16
|
+
* 解析顺序:
|
|
17
|
+
* 1. `actionName === 'ls'` 时映射为 `list`(仅作为别名,不会修改注册表)。
|
|
18
|
+
* 2. 在该模块的动作中按名称大小写不敏感匹配。
|
|
19
|
+
* 3. 当请求的动作不是基础 CRUD(`list`/`get`/`create`/`update`/`delete`)时,
|
|
20
|
+
* 额外允许命中 `type === 'action'` 的自定义动作(即使名字不在基础 CRUD 中)。
|
|
21
|
+
*
|
|
22
|
+
* 返回值同样是已深冻结的引用,请勿尝试修改。
|
|
23
|
+
*
|
|
24
|
+
* @param moduleName - 模块名(大小写不敏感)。
|
|
25
|
+
* @param actionName - 动作名(大小写不敏感);支持 `ls` 作为 `list` 的别名。
|
|
26
|
+
* @returns 匹配到的动作定义。
|
|
27
|
+
* @throws {ZentaoError} `E_INVALID_MODULE`(模块未注册)或 `E_INVALID_ACTION`(动作不存在)。
|
|
28
|
+
*/
|
|
29
|
+
export declare function getModuleAction(moduleName: string, actionName: string): ModuleAction;
|
|
30
|
+
/**
|
|
31
|
+
* 返回当前运行时注册表中的所有模块名。
|
|
32
|
+
*
|
|
33
|
+
* 顺序与模块写入注册表的顺序一致;包括内置模块和通过 {@link defineModules} 追加的用户模块。
|
|
34
|
+
*
|
|
35
|
+
* @returns 模块名数组(保留原始大小写)。
|
|
36
|
+
*/
|
|
37
|
+
export declare function getModuleNames(): string[];
|
|
38
|
+
/**
|
|
39
|
+
* 判断模块名是否已注册。
|
|
40
|
+
*
|
|
41
|
+
* @param moduleName - 模块名;匹配大小写不敏感。
|
|
42
|
+
* @returns 已注册返回 `true`,否则 `false`。
|
|
43
|
+
*/
|
|
44
|
+
export declare function isModuleName(moduleName: string): boolean;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ZentaoError } from '../misc/errors.js';
|
|
2
|
+
import { getModuleMapState, getModulesState } from './registry-store.js';
|
|
3
|
+
/**
|
|
4
|
+
* 获取模块定义。
|
|
5
|
+
*
|
|
6
|
+
* 模块名匹配大小写不敏感。返回值是注册表内部的已深冻结引用(O(1) 查询、零拷贝),
|
|
7
|
+
* 任何写入尝试在严格模式下会抛 `TypeError`;如需修改请使用 {@link defineModules}。
|
|
8
|
+
*
|
|
9
|
+
* @param moduleName - 模块名。
|
|
10
|
+
* @returns 已注册的模块定义。
|
|
11
|
+
* @throws {ZentaoError} `E_INVALID_MODULE` —— 模块未注册。
|
|
12
|
+
*/
|
|
13
|
+
export function getModule(moduleName) {
|
|
14
|
+
const module = getModuleMapState().get(moduleName.toLowerCase());
|
|
15
|
+
if (!module) {
|
|
16
|
+
throw new ZentaoError('E_INVALID_MODULE', { module: moduleName });
|
|
17
|
+
}
|
|
18
|
+
return module;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 获取指定模块下的某个动作。
|
|
22
|
+
*
|
|
23
|
+
* 解析顺序:
|
|
24
|
+
* 1. `actionName === 'ls'` 时映射为 `list`(仅作为别名,不会修改注册表)。
|
|
25
|
+
* 2. 在该模块的动作中按名称大小写不敏感匹配。
|
|
26
|
+
* 3. 当请求的动作不是基础 CRUD(`list`/`get`/`create`/`update`/`delete`)时,
|
|
27
|
+
* 额外允许命中 `type === 'action'` 的自定义动作(即使名字不在基础 CRUD 中)。
|
|
28
|
+
*
|
|
29
|
+
* 返回值同样是已深冻结的引用,请勿尝试修改。
|
|
30
|
+
*
|
|
31
|
+
* @param moduleName - 模块名(大小写不敏感)。
|
|
32
|
+
* @param actionName - 动作名(大小写不敏感);支持 `ls` 作为 `list` 的别名。
|
|
33
|
+
* @returns 匹配到的动作定义。
|
|
34
|
+
* @throws {ZentaoError} `E_INVALID_MODULE`(模块未注册)或 `E_INVALID_ACTION`(动作不存在)。
|
|
35
|
+
*/
|
|
36
|
+
export function getModuleAction(moduleName, actionName) {
|
|
37
|
+
const module = getModule(moduleName);
|
|
38
|
+
const normalized = actionName === 'ls' ? 'list' : actionName;
|
|
39
|
+
const direct = module.actions.find((action) => String(action.name).toLowerCase() === normalized.toLowerCase());
|
|
40
|
+
if (direct)
|
|
41
|
+
return direct;
|
|
42
|
+
const crud = new Set(['list', 'get', 'create', 'update', 'delete']);
|
|
43
|
+
if (!crud.has(normalized)) {
|
|
44
|
+
const custom = module.actions.find((action) => action.type === 'action' && String(action.name).toLowerCase() === normalized.toLowerCase());
|
|
45
|
+
if (custom)
|
|
46
|
+
return custom;
|
|
47
|
+
}
|
|
48
|
+
throw new ZentaoError('E_INVALID_ACTION', { module: moduleName, action: actionName });
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 返回当前运行时注册表中的所有模块名。
|
|
52
|
+
*
|
|
53
|
+
* 顺序与模块写入注册表的顺序一致;包括内置模块和通过 {@link defineModules} 追加的用户模块。
|
|
54
|
+
*
|
|
55
|
+
* @returns 模块名数组(保留原始大小写)。
|
|
56
|
+
*/
|
|
57
|
+
export function getModuleNames() {
|
|
58
|
+
return getModulesState().map((module) => module.name);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 判断模块名是否已注册。
|
|
62
|
+
*
|
|
63
|
+
* @param moduleName - 模块名;匹配大小写不敏感。
|
|
64
|
+
* @returns 已注册返回 `true`,否则 `false`。
|
|
65
|
+
*/
|
|
66
|
+
export function isModuleName(moduleName) {
|
|
67
|
+
return getModuleMapState().has(moduleName.toLowerCase());
|
|
68
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ModuleAction, ModuleDefinition } from '../types/index.js';
|
|
2
|
+
export declare function deepClone<T>(value: T): T;
|
|
3
|
+
/**
|
|
4
|
+
* 深度合并:以 `patch` 覆盖 `base`,返回新对象,不修改任何入参。
|
|
5
|
+
*
|
|
6
|
+
* - 仅当两侧同为普通对象时递归合并;其余情况(含数组)由 `patch` 整体替换。
|
|
7
|
+
* - `patch` 中值为 `undefined` 的键会被跳过,因此扩展方只需给出待修改的字段。
|
|
8
|
+
* - 未被 `patch` 触及的嵌套值沿用 `base` 的引用(外层会重新深冻结,引用本就不可变)。
|
|
9
|
+
*/
|
|
10
|
+
export declare function deepMerge<T>(base: T, patch: unknown): T;
|
|
11
|
+
export declare function cloneBuiltinModules(): ModuleDefinition[];
|
|
12
|
+
export declare function deepFreeze<T>(value: T): T;
|
|
13
|
+
export declare function freezeAction(action: ModuleAction): ModuleAction;
|
|
14
|
+
export declare function freezeModule(module: ModuleDefinition): ModuleDefinition;
|
|
15
|
+
export declare function freezeModules(source: ModuleDefinition[]): ModuleDefinition[];
|
|
16
|
+
export declare function findActionIndex(source: readonly ModuleAction[], actionName: string): number;
|
|
17
|
+
export declare function mergeActions(base: readonly ModuleAction[], extension: readonly ModuleAction[]): ModuleAction[];
|
|
18
|
+
export declare function mergeModule(base: ModuleDefinition, extension: ModuleDefinition): ModuleDefinition;
|
|
19
|
+
export declare function buildModuleMap(source: readonly ModuleDefinition[]): Map<string, ModuleDefinition>;
|
|
20
|
+
export declare function validateModule(module: ModuleDefinition): void;
|
|
21
|
+
export declare function validateAction(action: ModuleAction): void;
|
|
22
|
+
/** 当前运行时注册表中的模块数组(define 侧原地修改,query 侧只读)。 */
|
|
23
|
+
export declare function getModulesState(): ModuleDefinition[];
|
|
24
|
+
/** 当前模块名(小写)到模块定义的查找表(O(1) 查询)。 */
|
|
25
|
+
export declare function getModuleMapState(): Map<string, ModuleDefinition>;
|
|
26
|
+
/** 依据当前 modules 重建 moduleMap。所有写入完成后调用以保持二者一致。 */
|
|
27
|
+
export declare function rebuildModuleMap(): void;
|
|
28
|
+
/** 注册「重置后钩子」,在每次 {@link resetState} 还原内置基线后调用。 */
|
|
29
|
+
export declare function setPostResetHook(hook: (() => void) | undefined): void;
|
|
30
|
+
/** 将注册表重置为内置模块(深克隆 + 深冻结),重建查找表,并触发重置后钩子。 */
|
|
31
|
+
export declare function resetState(): void;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { ZentaoError } from '../misc/errors.js';
|
|
2
|
+
import { isRecord } from '../utils/object.js';
|
|
3
|
+
import { BUILTIN_MODULES } from './generated.js';
|
|
4
|
+
// 运行时注册表存放「深克隆 + 深冻结」后的模块定义:
|
|
5
|
+
// - 深克隆:避免用户后续修改自己的输入对象时污染注册表;
|
|
6
|
+
// - 深冻结:让 getModule / getModuleAction 可以零拷贝返回引用,
|
|
7
|
+
// 外部尝试改写会在严格模式下抛 TypeError,开销也降到 O(1) 查询。
|
|
8
|
+
//
|
|
9
|
+
// 该模块是「定义」(define)与「查询」(query)两侧共享的底层存储:
|
|
10
|
+
// - define 侧通过 mutate* 系列函数维护状态,并复用 freeze/clone/merge/validate 原语;
|
|
11
|
+
// - query 侧只读访问 modules / moduleMap。
|
|
12
|
+
let modules = freezeModules(cloneBuiltinModules());
|
|
13
|
+
let moduleMap = buildModuleMap(modules);
|
|
14
|
+
export function deepClone(value) {
|
|
15
|
+
if (Array.isArray(value)) {
|
|
16
|
+
return value.map((item) => deepClone(item));
|
|
17
|
+
}
|
|
18
|
+
if (value && typeof value === 'object' && !(value instanceof Function)) {
|
|
19
|
+
const result = {};
|
|
20
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
21
|
+
result[key] = deepClone(nestedValue);
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 深度合并:以 `patch` 覆盖 `base`,返回新对象,不修改任何入参。
|
|
29
|
+
*
|
|
30
|
+
* - 仅当两侧同为普通对象时递归合并;其余情况(含数组)由 `patch` 整体替换。
|
|
31
|
+
* - `patch` 中值为 `undefined` 的键会被跳过,因此扩展方只需给出待修改的字段。
|
|
32
|
+
* - 未被 `patch` 触及的嵌套值沿用 `base` 的引用(外层会重新深冻结,引用本就不可变)。
|
|
33
|
+
*/
|
|
34
|
+
export function deepMerge(base, patch) {
|
|
35
|
+
if (!isRecord(patch)) {
|
|
36
|
+
return (patch === undefined ? base : deepClone(patch));
|
|
37
|
+
}
|
|
38
|
+
const result = isRecord(base) ? { ...base } : {};
|
|
39
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
40
|
+
if (value === undefined)
|
|
41
|
+
continue;
|
|
42
|
+
const current = result[key];
|
|
43
|
+
result[key] = isRecord(value) && isRecord(current) ? deepMerge(current, value) : deepClone(value);
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
export function cloneBuiltinModules() {
|
|
48
|
+
return deepClone(BUILTIN_MODULES);
|
|
49
|
+
}
|
|
50
|
+
export function deepFreeze(value) {
|
|
51
|
+
if (value === null || typeof value !== 'object')
|
|
52
|
+
return value;
|
|
53
|
+
if (Object.isFrozen(value))
|
|
54
|
+
return value;
|
|
55
|
+
for (const key of Object.keys(value)) {
|
|
56
|
+
deepFreeze(value[key]);
|
|
57
|
+
}
|
|
58
|
+
return Object.freeze(value);
|
|
59
|
+
}
|
|
60
|
+
export function freezeAction(action) {
|
|
61
|
+
return deepFreeze(action);
|
|
62
|
+
}
|
|
63
|
+
export function freezeModule(module) {
|
|
64
|
+
module.actions.forEach(freezeAction);
|
|
65
|
+
return deepFreeze(module);
|
|
66
|
+
}
|
|
67
|
+
export function freezeModules(source) {
|
|
68
|
+
source.forEach(freezeModule);
|
|
69
|
+
return source;
|
|
70
|
+
}
|
|
71
|
+
export function findActionIndex(source, actionName) {
|
|
72
|
+
const key = actionName.toLowerCase();
|
|
73
|
+
return source.findIndex((action) => String(action.name).toLowerCase() === key);
|
|
74
|
+
}
|
|
75
|
+
export function mergeActions(base, extension) {
|
|
76
|
+
const next = base.slice();
|
|
77
|
+
for (const action of extension) {
|
|
78
|
+
const index = findActionIndex(next, String(action.name));
|
|
79
|
+
const frozen = freezeAction(deepClone(action));
|
|
80
|
+
if (index >= 0) {
|
|
81
|
+
next[index] = frozen;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
next.push(frozen);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return next;
|
|
88
|
+
}
|
|
89
|
+
export function mergeModule(base, extension) {
|
|
90
|
+
return freezeModule({
|
|
91
|
+
...base,
|
|
92
|
+
...deepClone(extension),
|
|
93
|
+
actions: mergeActions(base.actions, extension.actions),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
export function buildModuleMap(source) {
|
|
97
|
+
return new Map(source.map((module) => [module.name.toLowerCase(), module]));
|
|
98
|
+
}
|
|
99
|
+
export function validateModule(module) {
|
|
100
|
+
if (!module || typeof module.name !== 'string' || !Array.isArray(module.actions)) {
|
|
101
|
+
throw new ZentaoError('E_INVALID_MODULE_DEFINITION');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export function validateAction(action) {
|
|
105
|
+
if (!action || typeof action.name !== 'string' || typeof action.path !== 'string' || typeof action.method !== 'string') {
|
|
106
|
+
throw new ZentaoError('E_INVALID_ACTION_DEFINITION');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** 当前运行时注册表中的模块数组(define 侧原地修改,query 侧只读)。 */
|
|
110
|
+
export function getModulesState() {
|
|
111
|
+
return modules;
|
|
112
|
+
}
|
|
113
|
+
/** 当前模块名(小写)到模块定义的查找表(O(1) 查询)。 */
|
|
114
|
+
export function getModuleMapState() {
|
|
115
|
+
return moduleMap;
|
|
116
|
+
}
|
|
117
|
+
/** 依据当前 modules 重建 moduleMap。所有写入完成后调用以保持二者一致。 */
|
|
118
|
+
export function rebuildModuleMap() {
|
|
119
|
+
moduleMap = buildModuleMap(modules);
|
|
120
|
+
}
|
|
121
|
+
// 重置后钩子:在 resetState 把状态还原为内置基线之后触发,
|
|
122
|
+
// 用于让上层(barrel)重新应用内置覆盖(override),而无需 store / define 直接依赖 override。
|
|
123
|
+
let postResetHook;
|
|
124
|
+
/** 注册「重置后钩子」,在每次 {@link resetState} 还原内置基线后调用。 */
|
|
125
|
+
export function setPostResetHook(hook) {
|
|
126
|
+
postResetHook = hook;
|
|
127
|
+
}
|
|
128
|
+
/** 将注册表重置为内置模块(深克隆 + 深冻结),重建查找表,并触发重置后钩子。 */
|
|
129
|
+
export function resetState() {
|
|
130
|
+
modules = freezeModules(cloneBuiltinModules());
|
|
131
|
+
rebuildModuleMap();
|
|
132
|
+
postResetHook?.();
|
|
133
|
+
}
|