@ynight/model-runtime 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadModels = loadModels;
4
+ const tslib_1 = require("tslib");
5
+ const glob_1 = require("glob");
6
+ const node_path_1 = tslib_1.__importDefault(require("node:path"));
7
+ const merge_model_1 = require("./merge-model");
8
+ /**
9
+ * 从 CommonJS 加载的模块中取出默认值。
10
+ *
11
+ * 处理两种写法:
12
+ * - `export default { ... }`:通过 `default` 字段暴露
13
+ * - `module.exports = { ... }`:模块本身就是值
14
+ */
15
+ function getModuleValue(filePath) {
16
+ const module = require(filePath);
17
+ if (module && typeof module === 'object' && 'default' in module) {
18
+ return module.default;
19
+ }
20
+ return module;
21
+ }
22
+ /**
23
+ * 计算模型基础文件的绝对路径。
24
+ * 若基础文件不存在(仅有 project),返回约定的路径,便于后续 require 报错定位。
25
+ */
26
+ function getModelFile(modelDir, modelKey, extension) {
27
+ return node_path_1.default.resolve(modelDir, modelKey, `model${extension}`);
28
+ }
29
+ /**
30
+ * 构造一个带文件路径上下文的错误,便于定位到具体出错文件。
31
+ */
32
+ function modelError(message, filePath) {
33
+ return new Error(`${message}: ${filePath}`);
34
+ }
35
+ /**
36
+ * 扫描基础模型和项目模型,并按模型 key 合并项目配置。
37
+ *
38
+ * 目录约定:
39
+ * - `<modelDir>/<modelKey>/model.<ext>`:基础模型
40
+ * - `<modelDir>/<modelKey>/project/<projectKey>.<ext>`:项目配置
41
+ *
42
+ * 处理流程:
43
+ * 1. glob 扫描所有相关文件
44
+ * 2. 区分基础模型文件与项目文件,记录到 modelFiles / projectFiles
45
+ * 3. 校验:基础模型文件唯一、项目文件能找到对应基础模型
46
+ * 4. 按需加载基础模型,并把每个项目按菜单 key 合并进对应基础模型
47
+ *
48
+ * @param options 扫描配置
49
+ * @returns 已加载并合并的模型列表
50
+ */
51
+ function loadModels({ modelDir, extension }) {
52
+ // 目录不存在时直接返回空数组,调用方可据此判断是否配置了模型目录。
53
+ if (!require('node:fs').existsSync(modelDir)) {
54
+ return [];
55
+ }
56
+ const models = [];
57
+ const modelMap = new Map();
58
+ const fileList = (0, glob_1.globSync)(node_path_1.default.resolve(modelDir, `**/*${extension}`), { nodir: true }).sort();
59
+ const modelFiles = new Map();
60
+ const projectFiles = [];
61
+ for (const filePath of fileList) {
62
+ const relativePath = node_path_1.default.relative(modelDir, filePath);
63
+ const segments = relativePath.split(node_path_1.default.sep);
64
+ const fileName = segments.at(-1);
65
+ // 忽略 index 文件(聚合入口)和测试文件。
66
+ if (!fileName || fileName === `index${extension}` || fileName.endsWith(`.test${extension}`)) {
67
+ continue;
68
+ }
69
+ if (fileName === `model${extension}` && segments.length > 1) {
70
+ // 基础模型文件:modelKey 取目录路径(支持嵌套子目录)。
71
+ const modelKey = segments.slice(0, -1).join(node_path_1.default.sep);
72
+ if (modelFiles.has(modelKey)) {
73
+ throw modelError(`模型基础文件重复`, filePath);
74
+ }
75
+ modelFiles.set(modelKey, filePath);
76
+ continue;
77
+ }
78
+ // 项目文件:要求路径中包含 `project` 段,且其上层目录是模型目录。
79
+ const projectIndex = segments.indexOf('project');
80
+ const projectKey = node_path_1.default.basename(fileName, extension);
81
+ const modelKey = projectIndex > 0 ? segments.slice(0, projectIndex).join(node_path_1.default.sep) : '';
82
+ if (projectIndex > 0 && projectKey && modelKey) {
83
+ projectFiles.push({ modelKey, projectKey, filePath });
84
+ }
85
+ }
86
+ for (const project of projectFiles) {
87
+ if (!modelFiles.has(project.modelKey)) {
88
+ throw modelError(`项目配置 ${project.modelKey}/${project.projectKey} 缺少对应的 model${extension}`, project.filePath);
89
+ }
90
+ }
91
+ /**
92
+ * 按 modelKey 获取/创建 LoadedModel,确保同一模型只加载一次基础 model。
93
+ */
94
+ function getOrCreateModel(modelKey) {
95
+ const existing = modelMap.get(modelKey);
96
+ if (existing) {
97
+ return existing;
98
+ }
99
+ // 没找到基础文件时仍按约定路径 require,让 require 抛出更友好的错误信息。
100
+ const modelFile = modelFiles.get(modelKey) ?? getModelFile(modelDir, modelKey, extension);
101
+ const model = getModuleValue(modelFile);
102
+ const loadedModel = { model, key: modelKey, project: {} };
103
+ modelMap.set(modelKey, loadedModel);
104
+ models.push(loadedModel);
105
+ return loadedModel;
106
+ }
107
+ for (const { modelKey, projectKey, filePath } of projectFiles) {
108
+ const loadedModel = getOrCreateModel(modelKey);
109
+ if (projectKey in loadedModel.project) {
110
+ throw modelError(`项目 key 重复`, filePath);
111
+ }
112
+ const projectConfig = getModuleValue(filePath);
113
+ // 项目配置与基础模型按菜单 key 合并,得到完整的项目 DSL。
114
+ loadedModel.project[projectKey] = (0, merge_model_1.mergeModel)(loadedModel.model, projectConfig);
115
+ }
116
+ return models;
117
+ }
118
+ //# sourceMappingURL=load-models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-models.js","sourceRoot":"","sources":["../src/load-models.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 合并模型对象:仅菜单树按 key 递归合并,其余数组由项目配置整体替换。
3
+ *
4
+ * 用法:项目侧只写"差异部分"(覆盖字段、新增菜单项),基础模型其余字段自动保留。
5
+ */
6
+ export declare function mergeModel<T extends object>(base: T, current: T): T;
7
+ //# sourceMappingURL=merge-model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-model.d.ts","sourceRoot":"","sources":["../src/merge-model.ts"],"names":[],"mappings":"AA6HA;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAEnE"}
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mergeModel = mergeModel;
4
+ /**
5
+ * 深拷贝任意值(针对 JSON 安全数据)。
6
+ *
7
+ * 与 structuredClone 不同:本实现显式递归数组与对象,
8
+ * 对原始值直接返回,不依赖宿主全局 API(便于在受限环境运行)。
9
+ */
10
+ function cloneValue(value) {
11
+ if (Array.isArray(value)) {
12
+ return value.map(item => cloneValue(item));
13
+ }
14
+ if (value && typeof value === 'object') {
15
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, cloneValue(item)]));
16
+ }
17
+ return value;
18
+ }
19
+ /**
20
+ * 按 key 递归合并两个菜单数组。
21
+ *
22
+ * - 已存在相同 key 的项:递归合并两者
23
+ * - 不存在的项:作为新分支加入
24
+ * - 不带 key 的项:直接抛错(菜单必须稳定标识,便于框架寻址)
25
+ */
26
+ function mergeMenu(base, current) {
27
+ const result = base.map(item => cloneValue(item));
28
+ const indexes = new Map();
29
+ // 记录基础菜单中每项 key 对应的索引,方便后续合并时定位。
30
+ result.forEach((item, index) => {
31
+ if (item && typeof item === 'object' && 'key' in item) {
32
+ indexes.set(String(item.key), index);
33
+ }
34
+ });
35
+ current.forEach(item => {
36
+ if (!item || typeof item !== 'object' || !('key' in item)) {
37
+ throw new Error('模型菜单数组中的每一项必须是带 key 的对象');
38
+ }
39
+ const key = String(item.key);
40
+ const index = indexes.get(key);
41
+ if (index === undefined) {
42
+ // 全新 key:直接追加到结果末尾。
43
+ indexes.set(key, result.length);
44
+ result.push(cloneValue(item));
45
+ }
46
+ else {
47
+ // 已存在:进入分支级合并(处理 menuType / moduleType 变更的整分支替换)。
48
+ result[index] = mergeMenuItem(result[index], item, key);
49
+ }
50
+ });
51
+ return result;
52
+ }
53
+ /**
54
+ * 合并两个菜单项(同一 key)。
55
+ *
56
+ * 菜单是带判别字段的联合类型。
57
+ * menuType/moduleType 变化时,项目配置必须替换整个分支,不能把旧的
58
+ * subMenu 或 moduleConfig 残留到新的分支上。
59
+ */
60
+ function mergeMenuItem(base, current, key) {
61
+ if (base &&
62
+ typeof base === 'object' &&
63
+ current &&
64
+ typeof current === 'object' &&
65
+ ('menuType' in base || 'menuType' in current)) {
66
+ const baseRecord = base;
67
+ const currentRecord = current;
68
+ // menuType 不同(group <-> module):必须整体替换。
69
+ if (baseRecord.menuType !== currentRecord.menuType) {
70
+ return cloneValue(current);
71
+ }
72
+ // 都是 module 但 moduleType 不同(schema/sider/iframe/custom 互转):也整体替换。
73
+ if (baseRecord.menuType === 'module' &&
74
+ currentRecord.menuType === 'module' &&
75
+ baseRecord.moduleType !== currentRecord.moduleType) {
76
+ return cloneValue(current);
77
+ }
78
+ }
79
+ // 同分支:递归合并字段。
80
+ return mergeValue(base, current, key);
81
+ }
82
+ /**
83
+ * 通用合并:按 key 递归合并对象/数组。
84
+ *
85
+ * 仅 `menu` / `subMenu` 两个键按 key 合并(菜单树需要保留基础项并允许项目侧覆盖);
86
+ * 其余数组(含 JSON Schema 的 oneOf / required / 按钮列表等)整体替换,避免错位合并。
87
+ */
88
+ function mergeValue(base, current, key) {
89
+ if (Array.isArray(base) && Array.isArray(current)) {
90
+ // 只有菜单树需要按 key 合并;oneOf、required、按钮等数组整体替换。
91
+ return key === 'menu' || key === 'subMenu'
92
+ ? mergeMenu(base, current)
93
+ : current.map(item => cloneValue(item));
94
+ }
95
+ if (base && typeof base === 'object' && current && typeof current === 'object') {
96
+ const keys = new Set([...Object.keys(base), ...Object.keys(current)]);
97
+ return Object.fromEntries([...keys].map(childKey => [
98
+ childKey,
99
+ // current 中存在则继续递归;否则保留 base 的深拷贝。
100
+ childKey in current
101
+ ? mergeValue(base[childKey], current[childKey], childKey)
102
+ : cloneValue(base[childKey]),
103
+ ]));
104
+ }
105
+ // 标量或类型不一致:项目配置整体覆盖基础。
106
+ return cloneValue(current);
107
+ }
108
+ /**
109
+ * 合并模型对象:仅菜单树按 key 递归合并,其余数组由项目配置整体替换。
110
+ *
111
+ * 用法:项目侧只写"差异部分"(覆盖字段、新增菜单项),基础模型其余字段自动保留。
112
+ */
113
+ function mergeModel(base, current) {
114
+ return mergeValue(base, current);
115
+ }
116
+ //# sourceMappingURL=merge-model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-model.js","sourceRoot":"","sources":["../src/merge-model.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { LoadedModel } from './load-models';
2
+ /**
3
+ * 单个控件的契约定义。
4
+ *
5
+ * 由业务项目以纯 JSON 形式声明,描述某个自定义 comType 应该满足的 props schema。
6
+ * 不允许包含 Vue 组件或函数——一切必须能被序列化。
7
+ */
8
+ export interface ControlContractDefinition {
9
+ /** 该控件 props 的 JSON Schema;用于 ajv 校验每个使用点的 props。 */
10
+ propsSchema: unknown;
11
+ }
12
+ /**
13
+ * `validateModels` 的入参配置。
14
+ */
15
+ export interface ValidateModelsOptions {
16
+ /** 由业务项目提供的纯 JSON 控件契约;不得包含 Vue 组件或函数。 */
17
+ controlContracts?: Record<string, ControlContractDefinition>;
18
+ }
19
+ /**
20
+ * 校验已加载模型的 JSON 边界、菜单键、控件契约和跨模型项目键。
21
+ *
22
+ * 校验步骤:
23
+ * 1. 编译控件契约(若提供)
24
+ * 2. 逐模型校验:JSON 边界 + 菜单键 + 模型结构 + 字段控件契约
25
+ * 3. 跨模型唯一性:同名 projectKey 不能同时归属多个模型
26
+ *
27
+ * @throws 任何校验失败均抛出 Error,message 含定位路径与失败原因。
28
+ */
29
+ export declare function validateModels(models: LoadedModel[], options?: ValidateModelsOptions): void;
30
+ //# sourceMappingURL=validate-models.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-models.d.ts","sourceRoot":"","sources":["../src/validate-models.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,WAAW,yBAAyB;IACxC,qDAAqD;IACrD,WAAW,EAAE,OAAO,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,0CAA0C;IAC1C,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;CAC9D;AA4aD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE,OAAO,GAAE,qBAA0B,GAAG,IAAI,CAe/F"}