@stalfh233/omc-fake-platform 0.0.0-stage → 0.4.39

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 ADDED
@@ -0,0 +1,135 @@
1
+ import { relationTargetOf } from "omc-core/relation-shape";
2
+ import { MODEL_REFERENCED_DELETE_ERRCODE } from "omc-core/wire-facts";
3
+ import { businessRefusal, NOT_HANDLED } from "./state.js";
4
+ /** model-management lane: app tree, model CRUD, physical delete. Twin of src/lanes/model.ts. */
5
+ export const modelRoute = (ctx, input) => {
6
+ const q = input.query ?? {};
7
+ const body = (input.body ?? {});
8
+ switch (input.path) {
9
+ case "/api/api/app/apppackage/trees": {
10
+ // Live fidelity (ticket 68): the tree row id is the AppFunction id, not
11
+ // the biz-model record id; appCode filters the tree, treeHidden models
12
+ // keep a record but no tree node. Ticket 83: rows carry parentId (live
13
+ // AppFunctionModel shape; bizmodels/update requires it) and the node
14
+ // display name comes from nodeName (the menu node is renamed
15
+ // independently of the schema record).
16
+ const appCode = q.appCode === undefined ? undefined : String(q.appCode);
17
+ // Live 8.6.26 fidelity (2026-09-17): the tree is NESTED — a Folder row
18
+ // carrying its BizModel children — not a flat list. The environment lane
19
+ // used a top-level map and silently dropped every model, so a live
20
+ // discover_environment with applicationCode returned no models. Consumers
21
+ // must flattenTreeRows (as resolveModelTreeNode already does); the fake
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 }];
27
+ }
28
+ case "/api/api/app/bizmodels/list_schema":
29
+ return [...ctx.state.models.values()].map((model) => ({ id: model.recordId, schemaCode: model.code, name: model.name }));
30
+ case "/api/api/app/bizmodels/create": {
31
+ const code = String(body.bizSchemaCode ?? body.code);
32
+ const n = ctx.state.models.size + 1;
33
+ ctx.state.models.set(code, {
34
+ id: `model-${n}-fn`,
35
+ recordId: `model-${n}-rec`,
36
+ code,
37
+ name: String(body.name ?? code),
38
+ appCode: String(body.applicationCode ?? body.appCode ?? "test"),
39
+ });
40
+ // Live fidelity (ticket 42): bizmodels/create seeds NO form — the model
41
+ // has no h_biz_sheet row until the form is created (bizsheet/create, or
42
+ // form.draft's scaffold-on-absent path). Do not pre-seed a sheet here.
43
+ return { schemaCode: code };
44
+ }
45
+ case "/api/api/app/apppackage/physically_delete": {
46
+ const rows = Array.isArray(input.body) ? input.body : [];
47
+ for (const row of rows) {
48
+ if (row.type === undefined || row.type === null || row.type === "") {
49
+ ctx.state.apps.delete(String(row.code));
50
+ continue;
51
+ }
52
+ const code = String(row.code);
53
+ // deleteFunctions semantics (ticket 23): a model referenced by
54
+ // another model's relation field cannot be deleted. Live: the sync
55
+ // face answers errcode 0 and the async dispatcher fails with
56
+ // ServiceException "xxx被模型 yyy 引用,不能删除"
57
+ // (AppIntegrationFacadeImpl.deleteFunctions:2201); the fake surfaces
58
+ // the check synchronously (MODEL_REFERENCED_DELETE_ERRCODE stands for
59
+ // the ServiceException envelope family) so the blocker and the
60
+ // referencing list are visible even when a caller bypasses the tool
61
+ // precheck. The live per-column async cascade (3-4s/column) is not
62
+ // simulated — the fake removes the model synchronously.
63
+ const referencers = [
64
+ ...new Set([...ctx.state.fields.entries()]
65
+ .filter(([key, field]) => !key.startsWith(`${code}#`) && relationTargetOf(field) === code)
66
+ .map(([key]) => key.split("#")[0])),
67
+ ];
68
+ if (referencers.length > 0)
69
+ return businessRefusal(MODEL_REFERENCED_DELETE_ERRCODE, `${code}被模型 ${referencers.join(",")} 引用,不能删除`);
70
+ // Ticket 68 live fidelity: physically_delete resolves each row id as an
71
+ // AppFunction id (removeBizModel → getAppFunction(id)); a biz-model
72
+ // record id is refused with 601003 业务模型不存在.
73
+ const model = ctx.state.models.get(code);
74
+ if (model && row.id !== model.id)
75
+ return businessRefusal(601003, "业务模型不存在");
76
+ ctx.state.models.delete(code);
77
+ ctx.state.sheets.delete(code);
78
+ // The cascade drops the model's own bizproperty rows too — without
79
+ // this cleanup a deleted referencing model would keep blocking the
80
+ // referenced model's delete (dependency-inverted chains must pass).
81
+ for (const key of [...ctx.state.fields.keys()])
82
+ if (key.startsWith(`${code}#`))
83
+ ctx.state.fields.delete(key);
84
+ }
85
+ return null;
86
+ }
87
+ case "/api/api/app/bizmodels/exits_bizschema":
88
+ return ctx.state.models.has(q.schemaCode);
89
+ case "/api/api/app/bizmodels/get_summary":
90
+ // BizSchemaController.get_summary: an absent schema answers "" (ok), an
91
+ // existing one returns its summary string.
92
+ return ctx.state.models.get(q.schemaCode)?.summary ?? "";
93
+ case "/api/api/app/bizmodels/update_summary": {
94
+ // BizSchemaController.updateSummary: non-empty summary required
95
+ // (validateNotEmpty) and a missing schema answers the
96
+ // BIZ_SCHEMA_NOT_PUBLISHED errcode (ErrorCode.java:194 → 300014).
97
+ const schemaCode = String(body.schemaCode ?? "");
98
+ if (!ctx.state.models.has(schemaCode))
99
+ return businessRefusal(300014, "数据模型未发布");
100
+ const model = ctx.state.models.get(schemaCode);
101
+ model.summary = String(body.summary ?? "");
102
+ return null;
103
+ }
104
+ case "/api/api/app/bizmodels/get_all": {
105
+ const model = ctx.state.models.get(q.schemaCode);
106
+ return model ? { id: model.recordId, name: model.name, modelType: "LIST" } : null;
107
+ }
108
+ case "/api/api/app/bizmodels/update": {
109
+ // Ticket 83 (source-pinned BizSchemaController.update :266-305): the
110
+ // endpoint patches the AppFunction MENU NODE — bizSchemaCode, id, name,
111
+ // parentId are all validateNotEmpty-required; the schema RECORD is not
112
+ // migrated (get_all keeps the original name/type per probe evidence),
113
+ // so the rename lands on nodeName only.
114
+ const schemaCode = String(body.bizSchemaCode ?? body.schemaCode ?? "");
115
+ const model = ctx.state.models.get(schemaCode);
116
+ if (!model)
117
+ return businessRefusal(300014, "数据模型未发布");
118
+ const nodeId = String(body.id ?? "");
119
+ const name = String(body.name ?? "");
120
+ const parentId = String(body.parentId ?? "");
121
+ if (!nodeId)
122
+ return businessRefusal(10005, "id不能为空");
123
+ if (!name)
124
+ return businessRefusal(10005, "名称不能为空");
125
+ if (!parentId)
126
+ return businessRefusal(10005, "父id不可为空");
127
+ if (nodeId !== model.id)
128
+ return businessRefusal(10005, `id不是模型 ${schemaCode} 的应用树节点`);
129
+ model.nodeName = name;
130
+ return null;
131
+ }
132
+ default:
133
+ return NOT_HANDLED;
134
+ }
135
+ };
@@ -0,0 +1,3 @@
1
+ import { type LaneRoute } from "./state.js";
2
+ /** online-js lane: programming compile/get_source/publish. Twin of src/lanes/online-js.ts. */
3
+ export declare const onlineJsRoute: LaneRoute;
@@ -0,0 +1,17 @@
1
+ import { NOT_HANDLED } from "./state.js";
2
+ /** online-js lane: programming compile/get_source/publish. Twin of src/lanes/online-js.ts. */
3
+ export const onlineJsRoute = (ctx, input) => {
4
+ const q = input.query ?? {};
5
+ const body = (input.body ?? {});
6
+ switch (input.path) {
7
+ case "/api/api/programming/compile":
8
+ return { compiled: true };
9
+ case "/api/api/programming/get_source":
10
+ return ctx.state.onlineJs.get(`${q.schemaCode}#${q.sheetCode}`) ?? null;
11
+ case "/api/api/programming/publish":
12
+ ctx.state.onlineJs.set(`${body.schemaCode}#${body.sheetCode}`, String(body.source));
13
+ return null;
14
+ default:
15
+ return NOT_HANDLED;
16
+ }
17
+ };
@@ -0,0 +1,2 @@
1
+ import { type LaneRoute } from "./state.js";
2
+ export declare const reportRoute: LaneRoute;
package/dist/report.js ADDED
@@ -0,0 +1,252 @@
1
+ import { businessRefusal, NOT_HANDLED, rawResponse } from "./state.js";
2
+ /**
3
+ * R1-report fake. Twin of src/lanes/report.ts: the platform channel
4
+ * (`/api/api/report/*`, errcode envelope) owns the report page; the BI channel
5
+ * (`/dashboard/v1/*`, `{data}` / `{errorCode}` envelope) owns the dashboard and
6
+ * charts. Fidelity notes matching live (aozhe tenant):
7
+ * - a BI call with no in-body `config` returns `{data:true}` but mutates
8
+ * nothing (silent no-op);
9
+ * - an absent dashboard answers `errorCode:"dashboard.not.exist"`;
10
+ * - a report tree reports a page's charts from its BI dashboard.
11
+ */
12
+ function reportTree(ctx, appCode) {
13
+ const pages = [...ctx.state.reportPages.values()].filter((page) => !appCode || page.appCode === appCode);
14
+ const byApp = new Map();
15
+ for (const page of pages) {
16
+ const list = byApp.get(page.appCode) ?? [];
17
+ list.push(page);
18
+ byApp.set(page.appCode, list);
19
+ }
20
+ return [...byApp.entries()].map(([app, appPages]) => ({
21
+ code: app,
22
+ appFunctionList: appPages.map((page) => ({
23
+ code: page.code,
24
+ name: page.name,
25
+ appCode: page.appCode,
26
+ type: "Report",
27
+ reportObjectId: page.id,
28
+ appReportChartList: (ctx.state.biDashboards.get(page.id)?.charts ?? []).map((chart) => ({
29
+ uuid: chart.uuid,
30
+ title: chart.title,
31
+ type: chart.type,
32
+ objectId: page.id,
33
+ })),
34
+ })),
35
+ }));
36
+ }
37
+ function hasConfig(body) {
38
+ const config = body.config;
39
+ return typeof config === "object" && config !== null;
40
+ }
41
+ /** BI error envelope (`{errorCode,...}`), returned verbatim with HTTP 200. */
42
+ function biError(errorCode, errorMessage) {
43
+ return rawResponse({ errorCode, errorMessage, errorType: "BIZ" });
44
+ }
45
+ function biData(data) {
46
+ return rawResponse({ data });
47
+ }
48
+ function biNoop() {
49
+ return biData(true);
50
+ }
51
+ export const reportRoute = (ctx, input) => {
52
+ const q = input.query ?? {};
53
+ const body = (input.body ?? {});
54
+ switch (input.path) {
55
+ // ---- platform channel ----
56
+ case "/api/api/app/folders/queryReportChartTree":
57
+ return reportTree(ctx);
58
+ case "/api/api/runtime/app/queryReportChartTreeByApp":
59
+ return reportTree(ctx, q.appCode);
60
+ case "/api/api/report/get": {
61
+ const page = ctx.state.reportPages.get(q.code);
62
+ if (!page)
63
+ return businessRefusal(302035, "报表不存在!");
64
+ // Live fidelity: report/get omits a top-level `parentId`; the immediate
65
+ // folder is the LAST folderList entry. The tool must derive parentId from
66
+ // it (report/update 500s without one).
67
+ const { parentId, ...rest } = page;
68
+ return { ...rest, reportObjectId: page.id, folderList: [{ id: parentId, type: "Folder" }] };
69
+ }
70
+ case "/api/api/report/create": {
71
+ const code = String(body.code ?? "");
72
+ if (!code)
73
+ return businessRefusal(50000, "code 不能为空");
74
+ if (ctx.state.reportPages.has(code))
75
+ return businessRefusal(50000, "编码重复");
76
+ const page = {
77
+ id: `report-${ctx.state.reportPages.size + 1}`,
78
+ code,
79
+ name: String(body.name ?? code),
80
+ appCode: String(body.appCode ?? ""),
81
+ parentId: String(body.parentId ?? ""),
82
+ icon: String(body.icon ?? "h-icon-all-Report"),
83
+ pcAble: body.pcAble !== false,
84
+ mobileAble: body.mobileAble !== false,
85
+ name_i18n: typeof body.name_i18n === "string" ? body.name_i18n : JSON.stringify(body.name_i18n ?? {}),
86
+ };
87
+ ctx.state.reportPages.set(code, page);
88
+ return { ...page, reportObjectId: page.id };
89
+ }
90
+ case "/api/api/report/update": {
91
+ const id = String(body.id ?? "");
92
+ const page = [...ctx.state.reportPages.values()].find((entry) => entry.id === id);
93
+ if (!page)
94
+ return businessRefusal(50000, "报表不存在");
95
+ // Live fidelity: a missing parentId answers 50000 服务异常 (the platform
96
+ // dereferences it), even though report/get never returns it directly.
97
+ if (body.parentId === undefined || body.parentId === null || body.parentId === "")
98
+ return businessRefusal(50000, "服务异常");
99
+ if (typeof body.name === "string")
100
+ page.name = body.name;
101
+ if (typeof body.icon === "string")
102
+ page.icon = body.icon;
103
+ if (typeof body.name_i18n === "string")
104
+ page.name_i18n = body.name_i18n;
105
+ if (typeof body.pcAble === "boolean")
106
+ page.pcAble = body.pcAble;
107
+ if (typeof body.mobileAble === "boolean")
108
+ page.mobileAble = body.mobileAble;
109
+ if (typeof body.parentId === "string")
110
+ page.parentId = body.parentId;
111
+ return page;
112
+ }
113
+ case "/api/api/report/logical_delete": {
114
+ const page = ctx.state.reportPages.get(q.code);
115
+ if (page) {
116
+ ctx.state.reportPages.delete(q.code);
117
+ ctx.state.biDashboards.delete(page.id);
118
+ }
119
+ return null;
120
+ }
121
+ // ---- BI channel ----
122
+ case "/dashboard/v1/report/v2/config": {
123
+ const objectId = String(body.objectId ?? "");
124
+ const dashboard = ctx.state.biDashboards.get(objectId);
125
+ if (!dashboard)
126
+ return biError("dashboard.not.exist", "The dashboard does not exist");
127
+ return biData({ corpId: "a", objectId, title: dashboard.title, global: dashboard.global, attributes: dashboard.attributes, charts: dashboard.charts });
128
+ }
129
+ case "/dashboard/v1/report/v2/reportpage": {
130
+ if (!hasConfig(body))
131
+ return biNoop();
132
+ const report = (body.report ?? {});
133
+ const objectId = String(report.objectId ?? "");
134
+ if (!objectId)
135
+ return biError("dashboard.save.fail", "objectId required");
136
+ const existing = ctx.state.biDashboards.get(objectId);
137
+ const dashboard = {
138
+ objectId,
139
+ title: String(report.title ?? existing?.title ?? ""),
140
+ global: typeof report.global === "string" ? report.global : JSON.stringify(report.global ?? {}),
141
+ attributes: typeof report.attributes === "string" ? report.attributes : JSON.stringify(report.attributes ?? {}),
142
+ charts: existing?.charts ?? [],
143
+ };
144
+ ctx.state.biDashboards.set(objectId, dashboard);
145
+ return biData(objectId);
146
+ }
147
+ case "/dashboard/v1/report/removeReport": {
148
+ if (!hasConfig(body))
149
+ return biNoop();
150
+ const objectId = String(body.objectId ?? "");
151
+ if (!ctx.state.biDashboards.has(objectId))
152
+ return biError("dashboard.not.exist", "The dashboard does not exist");
153
+ ctx.state.biDashboards.delete(objectId);
154
+ return biData(true);
155
+ }
156
+ case "/dashboard/v1/report/saveGlobal": {
157
+ if (!hasConfig(body))
158
+ return biNoop();
159
+ const dashboard = ctx.state.biDashboards.get(String(body.objectId ?? ""));
160
+ if (!dashboard)
161
+ return biError("dashboard.not.exist", "The dashboard does not exist");
162
+ dashboard.global = typeof body.global === "string" ? body.global : JSON.stringify(body.global ?? {});
163
+ return biData(true);
164
+ }
165
+ case "/dashboard/v1/report/saveAttributes": {
166
+ if (!hasConfig(body))
167
+ return biNoop();
168
+ const dashboard = ctx.state.biDashboards.get(String(body.objectId ?? ""));
169
+ if (!dashboard)
170
+ return biError("dashboard.not.exist", "The dashboard does not exist");
171
+ dashboard.attributes = typeof body.attributes === "string" ? body.attributes : JSON.stringify(body.attributes ?? {});
172
+ return biData(true);
173
+ }
174
+ case "/dashboard/v1/chart/addChart": {
175
+ if (!hasConfig(body))
176
+ return biNoop();
177
+ const charts = Array.isArray(body.charts) ? body.charts : [];
178
+ const objectId = String(charts[0]?.objectId ?? "");
179
+ const dashboard = ctx.state.biDashboards.get(objectId);
180
+ if (!dashboard)
181
+ return biError("dashboard.not.exist", "The dashboard does not exist");
182
+ for (const chart of charts) {
183
+ if (typeof chart.uuid !== "string" || !chart.uuid)
184
+ return biError("chart.invalid", "chart uuid required");
185
+ if (dashboard.charts.some((entry) => entry.uuid === chart.uuid))
186
+ return biError("chart.exist", "chart already exists");
187
+ dashboard.charts.push(chart);
188
+ }
189
+ return biData(true);
190
+ }
191
+ case "/dashboard/v1/chart/updateChart": {
192
+ if (!hasConfig(body))
193
+ return biNoop();
194
+ const charts = Array.isArray(body.charts) ? body.charts : [];
195
+ const objectId = String(charts[0]?.objectId ?? "");
196
+ const dashboard = ctx.state.biDashboards.get(objectId);
197
+ if (!dashboard)
198
+ return biError("dashboard.not.exist", "The dashboard does not exist");
199
+ for (const chart of charts) {
200
+ const index = dashboard.charts.findIndex((entry) => entry.uuid === chart.uuid);
201
+ if (index < 0)
202
+ return biError("chart.not.exist", "chart does not exist");
203
+ dashboard.charts[index] = chart;
204
+ }
205
+ return biData(true);
206
+ }
207
+ case "/dashboard/v1/chart/removeChart": {
208
+ if (!hasConfig(body))
209
+ return biNoop();
210
+ const objectId = String(body.objectId ?? "");
211
+ for (const dashboard of ctx.state.biDashboards.values())
212
+ dashboard.charts = dashboard.charts.filter((chart) => chart.uuid !== objectId);
213
+ return biData(true);
214
+ }
215
+ case "/dashboard/v1/chart/removeCharts": {
216
+ if (!hasConfig(body))
217
+ return biNoop();
218
+ const ids = new Set(Array.isArray(body.objectIds) ? body.objectIds.map(String) : []);
219
+ for (const dashboard of ctx.state.biDashboards.values())
220
+ dashboard.charts = dashboard.charts.filter((chart) => !ids.has(String(chart.uuid)));
221
+ return biData(true);
222
+ }
223
+ case "/dashboard/v1/report/v3/datasourcelist":
224
+ return biData([
225
+ { dataSourceId: "model-1", displayName: "人员信息", nodeType: 0, useType: 1, objectId: "model-1", corpId: "a" },
226
+ ]);
227
+ case "/dashboard/datasource/advanced_list":
228
+ return biData([
229
+ { dataSourceId: "sql-1", displayName: "自定义查询", nodeType: 100, useType: 100, objectId: "sql-1", corpId: "a", status: 0, sort: 0, dwTableSyncFlag: false },
230
+ ]);
231
+ case "/dashboard/datasource/v2/datasource_list":
232
+ return biData([]);
233
+ case "/dashboard/v1/report/v3/datasource": {
234
+ if (!hasConfig(body))
235
+ return biNoop();
236
+ const infos = Array.isArray(body.dataSourceInfos) ? body.dataSourceInfos : [];
237
+ return biData(infos.map((info) => ({ nodeType: 1, displayName: String(info.dataSourceId), dataSourceId: info.dataSourceId, schema: { dataSourceId: info.dataSourceId, tableName: `t_${String(info.dataSourceId)}` } })));
238
+ }
239
+ case "/dashboard/v1/datalogic/v3/chartdata": {
240
+ // Live fidelity: the endpoint wants the flattened chart with an Integer
241
+ // (or null) `limit`; a raw stored chart (limit is an object) answers
242
+ // server.error. This locks the normalization in report.ts.
243
+ const chart = (body.chart ?? {});
244
+ if (chart.limit !== null && chart.limit !== undefined && typeof chart.limit === "object")
245
+ return biError("server.error", "A server error, please try again later");
246
+ const metric = Array.isArray(chart.metric) ? chart.metric : [];
247
+ return biData({ series: [], total: 0, metricFields: metric.map((entry) => entry.field) });
248
+ }
249
+ default:
250
+ return NOT_HANDLED;
251
+ }
252
+ };
@@ -0,0 +1,2 @@
1
+ import { type FakeRequestInput, type FakeRouteContext } from "./state.js";
2
+ export declare function dispatchFakeRoute(ctx: FakeRouteContext, input: FakeRequestInput): unknown;
package/dist/routes.js ADDED
@@ -0,0 +1,46 @@
1
+ import { appRoute } from "./app.js";
2
+ import { b2Route } from "./b2-registration.js";
3
+ import { businessRuleRoute } from "./businessrule.js";
4
+ import { dataRuleRoute } from "./datarule.js";
5
+ import { dictionaryRoute } from "./dictionary.js";
6
+ import { environmentRoute } from "./environment.js";
7
+ import { fieldRoute } from "./field.js";
8
+ import { formRoute } from "./form.js";
9
+ import { listViewRoute } from "./list-view.js";
10
+ import { modelRoute } from "./model.js";
11
+ import { onlineJsRoute } from "./online-js.js";
12
+ import { reportRoute } from "./report.js";
13
+ import { ruleLifecycleRoute } from "./rule-lifecycle.js";
14
+ import { runtimeDataRoute } from "./runtime-data.js";
15
+ import { NOT_HANDLED } from "./state.js";
16
+ import { workflowRoute } from "./workflow.js";
17
+ /**
18
+ * Composition root for the fake wire surface: one route module per lane,
19
+ * mirroring src/tools.ts. Every lane is tried in turn; the first that does
20
+ * not answer NOT_HANDLED owns the path.
21
+ */
22
+ const LANE_ROUTES = [
23
+ environmentRoute,
24
+ appRoute,
25
+ dictionaryRoute,
26
+ modelRoute,
27
+ fieldRoute,
28
+ dataRuleRoute,
29
+ formRoute,
30
+ businessRuleRoute,
31
+ ruleLifecycleRoute,
32
+ runtimeDataRoute,
33
+ workflowRoute,
34
+ onlineJsRoute,
35
+ b2Route,
36
+ listViewRoute,
37
+ reportRoute,
38
+ ];
39
+ export function dispatchFakeRoute(ctx, input) {
40
+ for (const route of LANE_ROUTES) {
41
+ const result = route(ctx, input);
42
+ if (result !== NOT_HANDLED)
43
+ return result;
44
+ }
45
+ return undefined;
46
+ }
@@ -0,0 +1,7 @@
1
+ import { type LaneRoute } from "./state.js";
2
+ /**
3
+ * S25-rule-lifecycle lane: enable/disable/delete mutations. Twin of
4
+ * src/lanes/rule-lifecycle.ts. The platform quirk stands: `enabled` is the
5
+ * truth; `status` may still read DISABLED.
6
+ */
7
+ export declare const ruleLifecycleRoute: LaneRoute;
@@ -0,0 +1,36 @@
1
+ import { NOT_HANDLED } from "./state.js";
2
+ /**
3
+ * S25-rule-lifecycle lane: enable/disable/delete mutations. Twin of
4
+ * src/lanes/rule-lifecycle.ts. The platform quirk stands: `enabled` is the
5
+ * truth; `status` may still read DISABLED.
6
+ */
7
+ export const ruleLifecycleRoute = (ctx, input) => {
8
+ const q = input.query ?? {};
9
+ const body = (input.body ?? {});
10
+ switch (input.path) {
11
+ case "/api/api/app/businessrule/publish": {
12
+ const targetId = q.id ?? body.id;
13
+ for (const rule of ctx.state.rules.values())
14
+ if (rule.id === targetId)
15
+ rule.enabled = true;
16
+ return null;
17
+ }
18
+ case "/api/api/app/businessrule/enable": {
19
+ // BusinessRuleKeyRequest {schemaCode, ruleCode} flips the DRAFT row's
20
+ // enable switch — the face the runtime dispatcher reads
21
+ // (getExecutableBySchemaCodeAndCode). The platform quirk stands:
22
+ // enabled is the truth; status may still read DISABLED.
23
+ const target = [...ctx.state.rules.values()].find((rule) => rule.schemaCode === body.schemaCode && rule.code === body.ruleCode);
24
+ if (target)
25
+ target.enabled = true;
26
+ return null;
27
+ }
28
+ case "/api/api/app/businessrule/delete":
29
+ for (const [code, rule] of ctx.state.rules)
30
+ if (rule.id === q.id)
31
+ ctx.state.rules.delete(code);
32
+ return null;
33
+ default:
34
+ return NOT_HANDLED;
35
+ }
36
+ };
@@ -0,0 +1,2 @@
1
+ import { type LaneRoute } from "./state.js";
2
+ export declare const runtimeDataRoute: LaneRoute;