@stalfh233/omc-fake-platform 0.4.76 → 0.4.78

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/dist/model.js CHANGED
@@ -20,10 +20,47 @@ export const modelRoute = (ctx, input) => {
20
20
  // discover_environment with applicationCode returned no models. Consumers
21
21
  // must flattenTreeRows (as resolveModelTreeNode already does); the fake
22
22
  // must nest so a regression cannot pass.
23
- const children = [...ctx.state.models.values()]
24
- .filter((model) => !model.treeHidden && (appCode === undefined || (model.appCode ?? "test") === appCode))
25
- .map((model) => ({ id: model.id, type: "BizModel", code: model.code, name: model.nodeName ?? model.name, parentId: "folder-1" }));
26
- return [{ id: "folder-1", type: "FOLDER", code: "general", name: "General", children }];
23
+ const folders = [...ctx.state.folders.values()].filter((folder) => appCode === undefined || folder.appCode === appCode);
24
+ // Every real application has at least one folder (the designer's default
25
+ // group). An app seeded without one still needs a resolvable folder or
26
+ // model.create could never run there — synthesize the default.
27
+ if (appCode !== undefined && !folders.some((folder) => folder.appCode === appCode))
28
+ folders.push({ id: `folder-${appCode}`, appCode, name: "General", code: "general" });
29
+ const models = [...ctx.state.models.values()].filter((model) => !model.treeHidden && (appCode === undefined || (model.appCode ?? "test") === appCode));
30
+ const ROOT = "folder-1";
31
+ const nodeOf = (folder) => ({
32
+ id: folder.id,
33
+ type: "Folder",
34
+ code: folder.code ?? null,
35
+ name: folder.name,
36
+ parentId: folder.parentId ?? ROOT,
37
+ children: [],
38
+ });
39
+ const nodes = new Map();
40
+ for (const folder of folders)
41
+ nodes.set(folder.id, nodeOf(folder));
42
+ const rootChildren = [];
43
+ for (const folder of folders) {
44
+ const node = nodes.get(folder.id);
45
+ const parent = folder.parentId ? nodes.get(folder.parentId) : undefined;
46
+ if (parent)
47
+ parent.children.push(node);
48
+ else
49
+ rootChildren.push(node);
50
+ }
51
+ for (const model of models) {
52
+ const node = { id: model.id, type: "BizModel", code: model.code, name: model.nodeName ?? model.name, parentId: model.parentId ?? ROOT };
53
+ const parent = model.parentId ? nodes.get(model.parentId) : undefined;
54
+ if (parent)
55
+ parent.children.push(node);
56
+ else
57
+ rootChildren.push(node);
58
+ }
59
+ // Live 1.1.25 fidelity (2026-10-23, folder.tree readback): the tree
60
+ // returns the TOP-LEVEL folders directly — no synthetic app-package
61
+ // container row. The fake must match, or a depth-0 assertion would pass
62
+ // against a shape the platform never returns.
63
+ return rootChildren;
27
64
  }
28
65
  case "/api/api/app/bizmodels/list_schema":
29
66
  return [...ctx.state.models.values()].map((model) => ({ id: model.recordId, schemaCode: model.code, name: model.name }));
@@ -36,6 +73,7 @@ export const modelRoute = (ctx, input) => {
36
73
  code,
37
74
  name: String(body.name ?? code),
38
75
  appCode: String(body.applicationCode ?? body.appCode ?? "test"),
76
+ ...(body.folderId !== undefined && body.folderId !== null && String(body.folderId) !== "" ? { parentId: String(body.folderId) } : {}),
39
77
  });
40
78
  // Live fidelity (ticket 42): bizmodels/create seeds NO form — the model
41
79
  // has no h_biz_sheet row until the form is created (bizsheet/create, or
@@ -105,6 +143,61 @@ export const modelRoute = (ctx, input) => {
105
143
  const model = ctx.state.models.get(q.schemaCode);
106
144
  return model ? { id: model.recordId, name: model.name, modelType: "LIST" } : null;
107
145
  }
146
+ case "/api/api/app/folders/create": {
147
+ const appCode = String(body.appCode ?? "");
148
+ if (!appCode)
149
+ return businessRefusal(10005, "应用编码不能为空");
150
+ const name = String(body.name ?? "");
151
+ if (!name)
152
+ return businessRefusal(10005, "名称不能为空");
153
+ const parentId = body.parentId === undefined || body.parentId === null || String(body.parentId) === "" ? "folder-1" : String(body.parentId);
154
+ if (parentId !== "folder-1" && !ctx.state.folders.has(parentId))
155
+ return businessRefusal(10005, `目录不存在: ${parentId}`);
156
+ const id = `folder-${ctx.state.folders.size + 2}`;
157
+ ctx.state.folders.set(id, {
158
+ id,
159
+ appCode,
160
+ name,
161
+ ...(typeof body.name_i18n === "string" ? { name_i18n: body.name_i18n } : {}),
162
+ ...(typeof body.code === "string" ? { code: body.code } : {}),
163
+ ...(parentId !== "folder-1" ? { parentId } : {}),
164
+ });
165
+ return null;
166
+ }
167
+ case "/api/api/app/folders/get": {
168
+ const folder = ctx.state.folders.get(String(q.folderId ?? ""));
169
+ if (!folder)
170
+ return businessRefusal(10005, "目录不存在");
171
+ return { ...folder, type: "Folder" };
172
+ }
173
+ case "/api/api/app/folders/update": {
174
+ const folder = ctx.state.folders.get(String(body.id ?? ""));
175
+ if (!folder)
176
+ return businessRefusal(10005, "目录不存在");
177
+ const parentId = body.parentId === undefined || body.parentId === null ? "" : String(body.parentId);
178
+ if (parentId !== "" && parentId === folder.id)
179
+ return businessRefusal(10005, "上级不能选择自己");
180
+ if (parentId !== "" && parentId !== "folder-1" && !ctx.state.folders.has(parentId))
181
+ return businessRefusal(10005, `目录不存在: ${parentId}`);
182
+ ctx.state.folders.set(folder.id, {
183
+ ...folder,
184
+ ...(body.name !== undefined ? { name: String(body.name) } : {}),
185
+ ...(typeof body.name_i18n === "string" ? { name_i18n: body.name_i18n } : {}),
186
+ ...(parentId !== "" ? { parentId: parentId === "folder-1" ? undefined : parentId } : {}),
187
+ });
188
+ return null;
189
+ }
190
+ case "/api/api/app/folders/delete": {
191
+ const folderId = String(q.folderId ?? "");
192
+ if (!ctx.state.folders.has(folderId))
193
+ return businessRefusal(10005, "目录不存在");
194
+ const hasChildFolder = [...ctx.state.folders.values()].some((folder) => folder.parentId === folderId);
195
+ const hasChildModel = [...ctx.state.models.values()].some((model) => model.parentId === folderId);
196
+ if (hasChildFolder || hasChildModel)
197
+ return businessRefusal(10005, "目录不为空,无法删除");
198
+ ctx.state.folders.delete(folderId);
199
+ return null;
200
+ }
108
201
  case "/api/api/app/bizmodels/update": {
109
202
  // Ticket 83 (source-pinned BizSchemaController.update :266-305): the
110
203
  // endpoint patches the AppFunction MENU NODE — bizSchemaCode, id, name,
@@ -126,7 +219,10 @@ export const modelRoute = (ctx, input) => {
126
219
  return businessRefusal(10005, "父id不可为空");
127
220
  if (nodeId !== model.id)
128
221
  return businessRefusal(10005, `id不是模型 ${schemaCode} 的应用树节点`);
222
+ if (parentId !== (model.parentId ?? "folder-1") && !ctx.state.folders.has(parentId) && parentId !== "folder-1")
223
+ return businessRefusal(10005, `目录不存在: ${parentId}`);
129
224
  model.nodeName = name;
225
+ model.parentId = parentId;
130
226
  return null;
131
227
  }
132
228
  default:
package/dist/state.d.ts CHANGED
@@ -277,6 +277,15 @@ export interface FakeCallLogEntry {
277
277
  bytes: number;
278
278
  };
279
279
  }
280
+ /** App-tree folder node (h_app_function type=Folder) — issue 2026-10-23. */
281
+ export interface FakeFolder {
282
+ id: string;
283
+ appCode: string;
284
+ name: string;
285
+ name_i18n?: string;
286
+ code?: string;
287
+ parentId?: string;
288
+ }
280
289
  export interface FakeState {
281
290
  fields: Map<string, FakeField>;
282
291
  dictionaries: Map<string, FakeDictionary>;
@@ -289,6 +298,7 @@ export interface FakeState {
289
298
  sheets: Map<string, FakeSheet>;
290
299
  rules: Map<string, FakeRule>;
291
300
  buttons: Map<string, FakeButton>;
301
+ folders: Map<string, FakeFolder>;
292
302
  /** Schema codes for which bizproperty/list answers 601003 (see field.ts). */
293
303
  fieldListUnavailable: Set<string>;
294
304
  /**
@@ -406,6 +416,7 @@ export interface FakeState {
406
416
  appCode?: string;
407
417
  treeHidden?: boolean;
408
418
  nodeName?: string;
419
+ parentId?: string;
409
420
  }>;
410
421
  apps: Map<string, FakeApp>;
411
422
  appGroups: Map<string, FakeAppGroup>;
package/dist/state.js CHANGED
@@ -34,6 +34,10 @@ export function createFakeState(options = {}) {
34
34
  sheets: new Map(),
35
35
  rules: new Map(),
36
36
  buttons: new Map(),
37
+ // model.create resolves the first Folder in the tree when no folderId is
38
+ // given; this row is that default. Its parent is the app package (implicit
39
+ // root), so it is a top-level tree node.
40
+ folders: new Map([["folder-1", { id: "folder-1", appCode: "test", name: "General", code: "general" }]]),
37
41
  fieldListUnavailable: new Set(),
38
42
  ruleHistory: [],
39
43
  ruleSeq: 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stalfh233/omc-fake-platform",
3
- "version": "0.4.76",
3
+ "version": "0.4.78",
4
4
  "description": "OMC 的内存假 CloudPivot 平台(wire 接缝的第二个适配器)+ HTTP 假服务器——@stalfh233/omc-cli 的测试与 bench 基准基座",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -18,7 +18,7 @@
18
18
  "build": "tsc --project tsconfig.build.json"
19
19
  },
20
20
  "dependencies": {
21
- "omc-core": "0.4.76"
21
+ "omc-core": "0.4.78"
22
22
  },
23
23
  "engines": {
24
24
  "node": ">=22.13"