biz-a-cli 2.3.80-15224 → 2.3.80-15230

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/bin/hubEvent.js CHANGED
@@ -298,6 +298,7 @@ const EngineRegistry = {
298
298
  "bpm/workflowRT": () => import("../engine/bpm/workflow-runtime.js"),
299
299
  "domain/system": () => import("../engine/domain/system.js"),
300
300
  "domain/appConfig": () => import("../engine/domain/appConfig.js"),
301
+ "domain/applicationConfig": () => import("../engine/domain/applicationConfig.js"),
301
302
  };
302
303
 
303
304
  const handleCliCommand = async (data, cb, argv) => {
@@ -2,6 +2,7 @@ import {
2
2
  executeEffect,
3
3
  executeTimerJob,
4
4
  executeBackgroundTransition,
5
+ initiate,
5
6
  } from "./workflow-runtime.js";
6
7
  import {
7
8
  queryData,
@@ -65,6 +66,18 @@ export function initBpmAgent(broker, services = "") {
65
66
  }
66
67
  });
67
68
 
69
+ broker.on("bpm:initiate", async (payload) => {
70
+ try {
71
+ const { entityType, entityId } = payload || {};
72
+ await initiate({ ...payload, apiConfig: getGlobalConfig() });
73
+ console.log(
74
+ `[BPM Agent] Initiated workflow for ${entityType} ${entityId}.`,
75
+ );
76
+ } catch (error) {
77
+ console.error(`[BPM Agent] Initiate Failed:`, error.message);
78
+ }
79
+ });
80
+
68
81
  broker.on("bpm:effect", async (payload, ackCallback) => {
69
82
  if (!payload || (!payload.id && !payload.ID)) {
70
83
  pollEffects();
@@ -1227,10 +1227,25 @@ export async function executeEffect(effectData) {
1227
1227
 
1228
1228
  if (effectType === "CLI_SCRIPT") {
1229
1229
  console.log(`[BPM Engine] Bridging Script: ${eventName}`);
1230
+ // Auto-inject the effect's own instanceId so the CLI script knows which workflow
1231
+ // instance triggered it (per the BPM spec's payload-injection contract). Without
1232
+ // this, a script that keys off scriptData.instanceId has no way to identify the
1233
+ // instance when the effect carries no payloadTemplate.
1234
+ const scriptData = Object.assign(
1235
+ {},
1236
+ parsedPayload && typeof parsedPayload === "object" && !Array.isArray(parsedPayload)
1237
+ ? parsedPayload
1238
+ : {},
1239
+ {
1240
+ instanceId:
1241
+ effectData.sys$bpm_wf_instance_id ||
1242
+ effectData.SYS$BPM_WF_INSTANCE_ID,
1243
+ },
1244
+ );
1230
1245
  const scriptResult = await execCLIScriptWorker(
1231
1246
  apiConfig,
1232
1247
  eventName,
1233
- parsedPayload,
1248
+ scriptData,
1234
1249
  );
1235
1250
 
1236
1251
  // --- if cliScript of Effect need to run executeTransition ---
@@ -429,12 +429,10 @@ const isFirebird25NotNullCheckForColumn = (check, columnName) => {
429
429
  .replace(/^CHECK\s*/i, "")
430
430
  .replace(/"/g, "")
431
431
  .replace(/\s+/g, "")
432
- .toUpperCase();
432
+ .toUpperCase()
433
+ .replace(/^\(+|\)+$/g, "");
433
434
  const normalizedColumnName = toConstraintIdentifierPart(columnName);
434
- return (
435
- expression === `${normalizedColumnName}ISNOTNULL` ||
436
- expression === `(${normalizedColumnName}ISNOTNULL)`
437
- );
435
+ return expression === `${normalizedColumnName}ISNOTNULL`;
438
436
  };
439
437
 
440
438
  const resolveFirebird25NotNullCheckState = (tableName, columnName, actual) => {
@@ -451,6 +449,9 @@ const resolveFirebird25NotNullCheckState = (tableName, columnName, actual) => {
451
449
  };
452
450
  };
453
451
 
452
+ const getNotNullChecksForColumn = (actual, columnName) =>
453
+ (actual.checks ?? []).filter((check) => isFirebird25NotNullCheckForColumn(check, columnName));
454
+
454
455
  const describeColumnDependency = (dependency) => {
455
456
  const dependentName = dependency.dependentName || "unnamed object";
456
457
  return `dependency ${dependentName} (type ${dependency.dependentType})`;
@@ -588,11 +589,34 @@ export const buildTableSyncPlan = (expected, actual, options) => {
588
589
  const migrationMode = options?.migrationMode ?? "safe";
589
590
  const allowDestructiveChanges = migrationMode === "aggressive";
590
591
  const compare = compareTableStructure(expected, actual);
591
- if (compare.isSame) {
592
+ const expectedColumnsForCleanup = new Map(
593
+ (expected.columns ?? []).map((column) => [column.name.trim().toUpperCase(), column]),
594
+ );
595
+ const actualColumnsForCleanup = new Map(
596
+ (actual.columns ?? []).map((column) => [column.name.trim().toUpperCase(), column]),
597
+ );
598
+ const notNullChecksToDrop = [];
599
+ const seenNotNullChecks = new Set();
600
+ for (const [columnName, expectedColumn] of expectedColumnsForCleanup.entries()) {
601
+ if (expectedColumn.nullable === false || !actualColumnsForCleanup.has(columnName)) {
602
+ continue;
603
+ }
604
+ for (const check of getNotNullChecksForColumn(actual, expectedColumn.name)) {
605
+ const constraintName = String(check?.name ?? "").trim();
606
+ const constraintKey = constraintName.toUpperCase();
607
+ if (constraintName && !seenNotNullChecks.has(constraintKey)) {
608
+ seenNotNullChecks.add(constraintKey);
609
+ notNullChecksToDrop.push(constraintName);
610
+ }
611
+ }
612
+ }
613
+ if (compare.isSame && notNullChecksToDrop.length === 0) {
592
614
  return { statements: [], warnings: [], differences: [] };
593
615
  }
594
616
 
595
- const statements = [];
617
+ const statements = notNullChecksToDrop.map(
618
+ (constraintName) => `ALTER TABLE ${expected.name} DROP CONSTRAINT ${constraintName};`,
619
+ );
596
620
  const warnings = [];
597
621
 
598
622
  const expectedColumns = new Map();
@@ -1236,6 +1260,11 @@ const buildEntityTable = (entityName, entity, defaultDbIndex) => {
1236
1260
  const columns = attributeEntries.map((entry) => entry.column);
1237
1261
  ensureIdentityPrimaryColumn(columns, entity?.identity);
1238
1262
  const primaryKey = resolvePrimaryKey(entity, columns);
1263
+ const identityPrimaryKey = toDbName(entity?.identity?.primaryKey ?? "");
1264
+ const identityColumn = columns.find((column) => column.name === identityPrimaryKey);
1265
+ if (identityColumn) {
1266
+ identityColumn.nullable = false;
1267
+ }
1239
1268
  const uniqueKeys = buildUniqueKeys(tableName, attributeEntries, primaryKey);
1240
1269
  const indexes = buildAttributeIndexes(tableName, attributeEntries, primaryKey, uniqueKeys);
1241
1270
 
@@ -2175,7 +2204,7 @@ const syncTable = async (table, ctx) => {
2175
2204
  return executeSqlStatements(syncPlan.statements, tableDbIndex, ctx.config, ctx);
2176
2205
  };
2177
2206
 
2178
- const resolveConfigFromPayload = (payload = {}, argv = {}) => {
2207
+ export const resolveConfigFromPayload = (payload = {}, argv = {}) => {
2179
2208
  const resolvedDbIndex = toNonNegativeInteger(payload.dbIndex) ?? toNonNegativeInteger(argv.dbindex) ?? 2;
2180
2209
  return {
2181
2210
  dbindex: resolvedDbIndex,
@@ -2189,12 +2218,12 @@ const resolveConfigFromPayload = (payload = {}, argv = {}) => {
2189
2218
  };
2190
2219
  };
2191
2220
 
2192
- const buildPublishStatusConfigKey = (domainName, domainVersion) => ({
2221
+ export const buildPublishStatusConfigKey = (domainName, domainVersion) => ({
2193
2222
  name: `${domainName}__PUBLISH_STATUS`,
2194
2223
  version: domainVersion,
2195
2224
  });
2196
2225
 
2197
- const writePublishStatus = async (name, version, statusPayload, config) => {
2226
+ export const writePublishStatus = async (name, version, statusPayload, config) => {
2198
2227
  const upsertSql = getUpsertBlock(name, version, JSON.stringify(statusPayload));
2199
2228
  await executeBlock(upsertSql, config);
2200
2229
  };
@@ -0,0 +1,741 @@
1
+ import { queryData } from "../../db/db.js";
2
+ import { publishApp } from "./system.js";
3
+ import {
4
+ getConfigByNameVersionParameters,
5
+ resolveConfigFromPayload,
6
+ buildPublishStatusConfigKey,
7
+ writePublishStatus,
8
+ } from "./appConfig.js";
9
+
10
+ export const parseConfigData = (rawConfig) => {
11
+ if (rawConfig == null) {
12
+ return null;
13
+ }
14
+ if (typeof rawConfig === "object") {
15
+ return rawConfig;
16
+ }
17
+
18
+ const rawText = String(rawConfig).trim();
19
+ if (!rawText) {
20
+ return null;
21
+ }
22
+
23
+ try {
24
+ return JSON.parse(rawText);
25
+ } catch {
26
+ try {
27
+ return Function(`${rawText}; return (typeof get === 'function') ? get() : null;`)();
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+ };
33
+
34
+ export const normalizeMenuNode = (menu) => {
35
+ const caption = typeof menu?.caption === "string" ? menu.caption : typeof menu?.label === "string" ? menu.label : "";
36
+ const rawRoute = typeof menu?.route === "string" ? menu.route.trim().toLowerCase() : "";
37
+ const entryPoint = typeof menu?.entryPoint === "string" && menu.entryPoint.trim().length > 0 ? menu.entryPoint.trim() : null;
38
+
39
+ const inferredLink =
40
+ rawRoute.endsWith("/form") || entryPoint
41
+ ? ["./form", entryPoint ?? null, null]
42
+ : rawRoute.endsWith("/list")
43
+ ? typeof menu?.id === "string" && menu.id.trim().length > 0
44
+ ? ["./list", menu.id.trim()]
45
+ : ["./list"]
46
+ : [];
47
+
48
+ const sourceChildren = Array.isArray(menu?.subMenu) ? menu.subMenu : Array.isArray(menu?.menus) ? menu.menus : [];
49
+ const normalizedChildren = sourceChildren
50
+ .map((child) => normalizeMenuNode(child))
51
+ .filter(
52
+ (child) =>
53
+ String(child?.caption ?? "").trim().length > 0 ||
54
+ (Array.isArray(child?.link) && child.link.length > 0) ||
55
+ (Array.isArray(child?.subMenu) && child.subMenu.length > 0),
56
+ );
57
+
58
+ return {
59
+ menuName: typeof menu?.menuName === "string" ? menu.menuName : "",
60
+ caption,
61
+ link: Array.isArray(menu?.link) ? menu.link : inferredLink,
62
+ subMenu: normalizedChildren,
63
+ };
64
+ };
65
+
66
+ export const buildMenuStructureFromApplicationConfig = (parsedApplicationConfig) => {
67
+ const sourceMenus = Array.isArray(parsedApplicationConfig?.navigation?.menus) ? parsedApplicationConfig.navigation.menus : [];
68
+
69
+ const normalizedMenus = sourceMenus
70
+ .map((menu) => normalizeMenuNode(menu))
71
+ .filter(
72
+ (menu) =>
73
+ String(menu?.caption ?? "").trim().length > 0 ||
74
+ (Array.isArray(menu?.link) && menu.link.length > 0) ||
75
+ (Array.isArray(menu?.subMenu) && menu.subMenu.length > 0),
76
+ );
77
+
78
+ const nonHomeMenus = normalizedMenus.filter(
79
+ (menu) => !(menu?.caption === "Home" || (Array.isArray(menu?.link) && menu.link[0] === "./main")),
80
+ );
81
+
82
+ return [{ menuName: "", caption: "Home", link: ["./main"], subMenu: [] }].concat(nonHomeMenus);
83
+ };
84
+
85
+ export const normalizeTemplateFileName = (rawName) => {
86
+ const normalized = String(rawName ?? "")
87
+ .trim()
88
+ .toLowerCase()
89
+ .replace(/\s+/g, "");
90
+ return normalized.endsWith(".js") ? normalized : `${normalized}.js`;
91
+ };
92
+
93
+ export const collectFormTargets = (menuNode, inheritedDomainKey = null) => {
94
+ const targets = [];
95
+ const currentDomainKey =
96
+ typeof menuNode?.domain === "string" && menuNode.domain.trim().length > 0 ? menuNode.domain.trim() : inheritedDomainKey;
97
+ const explicitEntryPoint =
98
+ typeof menuNode?.entryPoint === "string" && menuNode.entryPoint.trim().length > 0 ? menuNode.entryPoint.trim() : null;
99
+ const menuLink = Array.isArray(menuNode?.link) ? menuNode.link : [];
100
+ const normalizedLinkTarget = String(menuLink?.[0] ?? "").trim().toLowerCase();
101
+ const linkedTemplateType =
102
+ normalizedLinkTarget === "./list" ? "list" : ["./form", "./main"].includes(normalizedLinkTarget) ? "form" : null;
103
+ const fallbackLinkedEntryPoint = normalizedLinkTarget === "./main" ? "main" : null;
104
+ const linkedEntryPoint = linkedTemplateType
105
+ ? typeof menuLink?.[1] === "string" && menuLink[1].trim().length > 0
106
+ ? menuLink[1].trim()
107
+ : fallbackLinkedEntryPoint
108
+ : null;
109
+ const resolvedEntryPoint = linkedEntryPoint ?? explicitEntryPoint;
110
+ if (resolvedEntryPoint) {
111
+ targets.push({
112
+ entryPointName: resolvedEntryPoint,
113
+ domainKey: currentDomainKey,
114
+ templateType: linkedTemplateType ?? "form",
115
+ });
116
+ }
117
+
118
+ const children = Array.isArray(menuNode?.menus) ? menuNode.menus : Array.isArray(menuNode?.subMenu) ? menuNode.subMenu : [];
119
+ for (const child of children) {
120
+ targets.push(...collectFormTargets(child, currentDomainKey));
121
+ }
122
+
123
+ return targets;
124
+ };
125
+
126
+ export const resolveDomainKeyByReference = (domainReference, domainMap) => {
127
+ const rawReference = String(domainReference ?? "").trim();
128
+ if (!rawReference) {
129
+ return null;
130
+ }
131
+ const domainKeys = Object.keys(domainMap ?? {});
132
+ if (domainKeys.length === 0) {
133
+ return null;
134
+ }
135
+
136
+ if (Object.prototype.hasOwnProperty.call(domainMap, rawReference)) {
137
+ return rawReference;
138
+ }
139
+
140
+ const normalizedReference = rawReference.toLowerCase();
141
+ const caseInsensitiveKeyMatch =
142
+ domainKeys.find((key) => String(key ?? "").trim().toLowerCase() === normalizedReference) ?? null;
143
+ if (caseInsensitiveKeyMatch) {
144
+ return caseInsensitiveKeyMatch;
145
+ }
146
+
147
+ for (const key of domainKeys) {
148
+ const domainDef = domainMap?.[key];
149
+ const source = String(domainDef?.source ?? "").trim();
150
+ const sourceName = String(source.split("@")[0] ?? "").trim();
151
+ const alias = String(domainDef?.alias ?? "").trim();
152
+ if (source && source.toLowerCase() === normalizedReference) {
153
+ return key;
154
+ }
155
+ if (sourceName && sourceName.toLowerCase() === normalizedReference) {
156
+ return key;
157
+ }
158
+ if (alias && alias.toLowerCase() === normalizedReference) {
159
+ return key;
160
+ }
161
+ }
162
+
163
+ return null;
164
+ };
165
+
166
+ export const resolveTemplateTargets = (parsedDomainConfig, parsedApplicationConfig) => {
167
+ const sourceMenus = Array.isArray(parsedApplicationConfig?.navigation?.menus) ? parsedApplicationConfig.navigation.menus : [];
168
+ const resolvedTargets = [];
169
+ const seenFileNames = new Set();
170
+
171
+ for (const menuNode of sourceMenus) {
172
+ const menuTargets = collectFormTargets(menuNode);
173
+ for (const menuTarget of menuTargets) {
174
+ const entryPointName = String(menuTarget.entryPointName ?? "").trim();
175
+ if (!entryPointName) {
176
+ continue;
177
+ }
178
+ const fileName = normalizeTemplateFileName(entryPointName);
179
+ if (seenFileNames.has(fileName)) {
180
+ continue;
181
+ }
182
+ seenFileNames.add(fileName);
183
+ resolvedTargets.push({
184
+ fileName,
185
+ entryPointName,
186
+ domainKey: menuTarget.domainKey ?? null,
187
+ templateType: menuTarget.templateType ?? "form",
188
+ });
189
+ }
190
+ }
191
+
192
+ const structures = parsedDomainConfig?.ui?.structures;
193
+ const entryPoints = parsedDomainConfig?.ui?.entryPoints;
194
+ const structureKeys =
195
+ structures && typeof structures === "object" ? Object.keys(structures).filter((key) => String(key ?? "").trim().length > 0) : [];
196
+ const entryPointKeys =
197
+ entryPoints && typeof entryPoints === "object" ? Object.keys(entryPoints).filter((key) => String(key ?? "").trim().length > 0) : [];
198
+ for (const entryPointName of entryPointKeys) {
199
+ const fileName = normalizeTemplateFileName(entryPointName);
200
+ if (seenFileNames.has(fileName)) {
201
+ continue;
202
+ }
203
+ const resolvedStructureRef = String(entryPoints?.[entryPointName]?.structureRef ?? "").trim() || entryPointName;
204
+ const resolvedStructure = structures?.[resolvedStructureRef];
205
+ seenFileNames.add(fileName);
206
+ resolvedTargets.push({
207
+ fileName,
208
+ entryPointName,
209
+ domainKey: null,
210
+ templateType: String(resolvedStructure?.type ?? "").trim().toLowerCase() === "list" ? "list" : "form",
211
+ });
212
+ }
213
+ if (resolvedTargets.length > 0) {
214
+ return resolvedTargets;
215
+ }
216
+ const entryPointName =
217
+ entryPointKeys.length > 0 ? String(entryPointKeys[0]) : structureKeys.length > 0 ? String(structureKeys[0]) : "appForm";
218
+ const resolvedStructureRef = String(entryPoints?.[entryPointName]?.structureRef ?? "").trim() || entryPointName;
219
+ const resolvedStructure = structures?.[resolvedStructureRef];
220
+ return [
221
+ {
222
+ fileName: normalizeTemplateFileName(entryPointName),
223
+ entryPointName,
224
+ domainKey: null,
225
+ templateType: String(resolvedStructure?.type ?? "").trim().toLowerCase() === "list" ? "list" : "form",
226
+ },
227
+ ];
228
+ };
229
+
230
+ export const buildTemplateDomainConfigResolverSnippet = () => {
231
+ return `const ensureDomainConfig = (onResolved) => {
232
+ const callback = typeof onResolved === "function" ? onResolved : function () {};
233
+ const configuredSource = String("__DOMAIN_SOURCE__").trim();
234
+ const domainCacheKey = configuredSource || "__default__";
235
+ if (!this._resolvedDomainConfigBySource || typeof this._resolvedDomainConfigBySource !== "object" || Array.isArray(this._resolvedDomainConfigBySource)) {
236
+ this._resolvedDomainConfigBySource = {};
237
+ }
238
+ if (!this._pendingDomainConfigCallbacksBySource || typeof this._pendingDomainConfigCallbacksBySource !== "object" || Array.isArray(this._pendingDomainConfigCallbacksBySource)) {
239
+ this._pendingDomainConfigCallbacksBySource = {};
240
+ }
241
+ if (!this._isResolvingDomainConfigBySource || typeof this._isResolvingDomainConfigBySource !== "object" || Array.isArray(this._isResolvingDomainConfigBySource)) {
242
+ this._isResolvingDomainConfigBySource = {};
243
+ }
244
+ const cachedDomainConfig = this._resolvedDomainConfigBySource[domainCacheKey];
245
+ if (cachedDomainConfig) {
246
+ this.domainConfig = cachedDomainConfig;
247
+ callback(this.domainConfig);
248
+ return;
249
+ }
250
+ if (!Array.isArray(this._pendingDomainConfigCallbacksBySource[domainCacheKey])) {
251
+ this._pendingDomainConfigCallbacksBySource[domainCacheKey] = [];
252
+ }
253
+ this._pendingDomainConfigCallbacksBySource[domainCacheKey].push(callback);
254
+ if (this._isResolvingDomainConfigBySource[domainCacheKey]) {
255
+ return;
256
+ }
257
+ this._isResolvingDomainConfigBySource[domainCacheKey] = true;
258
+ const completeResolve = (resolvedConfig) => {
259
+ if (resolvedConfig) {
260
+ this._resolvedDomainConfigBySource[domainCacheKey] = resolvedConfig;
261
+ this.domainConfig = resolvedConfig;
262
+ }
263
+ const callbackConfig = resolvedConfig || this.domainConfig;
264
+ const queuedCallbacks = Array.isArray(this._pendingDomainConfigCallbacksBySource[domainCacheKey]) ? this._pendingDomainConfigCallbacksBySource[domainCacheKey].slice() : [];
265
+ delete this._pendingDomainConfigCallbacksBySource[domainCacheKey];
266
+ this._isResolvingDomainConfigBySource[domainCacheKey] = false;
267
+ for (let index = 0; index < queuedCallbacks.length; index += 1) {
268
+ try {
269
+ queuedCallbacks[index](callbackConfig);
270
+ } catch {}
271
+ }
272
+ };
273
+ const parseConfigData = (rawConfig) => {
274
+ if (rawConfig == null) {
275
+ return null;
276
+ }
277
+ if (typeof rawConfig === "object") {
278
+ if (rawConfig.metadata && rawConfig.model && rawConfig.ui) {
279
+ return rawConfig;
280
+ }
281
+ if (rawConfig.data != null) {
282
+ return parseConfigData(rawConfig.data);
283
+ }
284
+ return rawConfig;
285
+ }
286
+ const rawText = String(rawConfig).trim();
287
+ if (!rawText) {
288
+ return null;
289
+ }
290
+ try {
291
+ return JSON.parse(rawText);
292
+ } catch {
293
+ try {
294
+ return Function(rawText + "; return (typeof get === 'function') ? get() : null;")();
295
+ } catch {
296
+ return null;
297
+ }
298
+ }
299
+ };
300
+ const apiConfig = this._configApi != null ? this._configApi : this.config && this.config.api ? this.config.api : null;
301
+ this._configApi = apiConfig;
302
+ const queryDomainConfig = (domainName, domainVersion) => {
303
+ if (!domainName || !domainVersion) {
304
+ completeResolve(this.domainConfig);
305
+ return;
306
+ }
307
+ const domainQuery = {
308
+ start: 0,
309
+ length: 1,
310
+ order: [],
311
+ filter: [
312
+ { junction: "", column: "NAME", operator: "=", value1: "'" + domainName.replace(/'/g, "''") + "'" },
313
+ { junction: "AND", column: "VERSION", operator: "=", value1: "'" + domainVersion.replace(/'/g, "''") + "'" },
314
+ ],
315
+ columns: [
316
+ { data: "SYS$CONFIG.ID", key: "id" },
317
+ { data: "SYS$CONFIG.NAME", key: "name" },
318
+ { data: "SYS$CONFIG.VERSION", key: "version" },
319
+ { data: "SYS$CONFIG.DATA", key: "data" },
320
+ ],
321
+ };
322
+ this.dataService.queryData(domainQuery, apiConfig, true, true).subscribe({
323
+ next: (domainRows) => {
324
+ const domainData = domainRows && domainRows[0] ? domainRows[0].data : null;
325
+ const domainConfig = parseConfigData(domainData);
326
+ completeResolve(domainConfig || this.domainConfig);
327
+ },
328
+ error: (error) => {
329
+ console.log("[AppConfig] Failed to query domain config:", error);
330
+ completeResolve(this.domainConfig);
331
+ },
332
+ });
333
+ };
334
+ if (configuredSource) {
335
+ const sourceParts = configuredSource.split("@");
336
+ const domainName = String(sourceParts[0] || "").trim();
337
+ const domainVersion = String(sourceParts[1] || "").trim();
338
+ queryDomainConfig(domainName, domainVersion);
339
+ return;
340
+ }
341
+ const applicationQuery = {
342
+ start: 0,
343
+ length: 1,
344
+ order: [],
345
+ filter: [{ junction: "", column: "NAME", operator: "=", value1: "'APPLICATION_CONFIG'" }],
346
+ columns: [
347
+ { data: "SYS$CONFIG.ID", key: "id" },
348
+ { data: "SYS$CONFIG.NAME", key: "name" },
349
+ { data: "SYS$CONFIG.VERSION", key: "version" },
350
+ { data: "SYS$CONFIG.DATA", key: "data" },
351
+ ],
352
+ };
353
+ this.dataService.queryData(applicationQuery, apiConfig, true, true).subscribe({
354
+ next: (applicationRows) => {
355
+ const applicationData = applicationRows && applicationRows[0] ? applicationRows[0].data : null;
356
+ const appConfig = parseConfigData(applicationData);
357
+ const domainDef = appConfig && appConfig.domains && appConfig.domains.salesOrderDomain ? appConfig.domains.salesOrderDomain : null;
358
+ const source = String(domainDef && domainDef.source ? domainDef.source : "").trim();
359
+ const sourceParts = source.split("@");
360
+ const domainName = String(sourceParts[0] || "").trim();
361
+ const domainVersion = String(sourceParts[1] || "").trim();
362
+ queryDomainConfig(domainName, domainVersion);
363
+ },
364
+ error: (error) => {
365
+ console.log("[AppConfig] Failed to query application config:", error);
366
+ completeResolve(this.domainConfig);
367
+ },
368
+ });
369
+ };
370
+ this._ensureDomainConfig = ensureDomainConfig;`;
371
+ };
372
+
373
+ export const buildHardcodedTemplateContent = (
374
+ entryPointName = "loginForm",
375
+ domainKey = null,
376
+ templateType = "form",
377
+ domainSource = null,
378
+ ) => {
379
+ const sharedDomainConfigResolverScript = buildTemplateDomainConfigResolverSnippet();
380
+ const formTemplateContent = `get = function () {
381
+ return {
382
+ model: {},
383
+ tableName: "",
384
+ fields: [],
385
+ functions: {
386
+ beforeLoadData: function (id) {
387
+ ${sharedDomainConfigResolverScript}
388
+ return new Promise((resolve) => {
389
+ ensureDomainConfig((resolvedConfig) => {
390
+ this.domainConfig = resolvedConfig || this.domainConfig;
391
+ console.log('Template Result :', this.domainConfig);
392
+ this.domainService.doBeforeLoadDataEvent({ config: this.domainConfig, entryPointName: "__ENTRY_POINT_NAME__", runtimeHost: this, id: String(id) });
393
+ resolve();
394
+ });
395
+ });
396
+ },
397
+ afterLoadData: function (id) {
398
+ const ensureDomainConfig =
399
+ typeof this._ensureDomainConfig === "function"
400
+ ? this._ensureDomainConfig
401
+ : (onResolved) => {
402
+ const callback = typeof onResolved === "function" ? onResolved : function () {};
403
+ callback(this.domainConfig);
404
+ };
405
+ return new Promise((resolve) => {
406
+ ensureDomainConfig((resolvedConfig) => {
407
+ this.domainConfig = resolvedConfig || this.domainConfig;
408
+ this.domainService.doAfterLoadDataEvent({ config: this.domainConfig, entryPointName: "__ENTRY_POINT_NAME__", runtimeHost: this, id: String(id) });
409
+ resolve();
410
+ });
411
+ });
412
+ },
413
+ onInit: function (id) {
414
+ const ensureDomainConfig =
415
+ typeof this._ensureDomainConfig === "function"
416
+ ? this._ensureDomainConfig
417
+ : (onResolved) => {
418
+ const callback = typeof onResolved === "function" ? onResolved : function () {};
419
+ callback(this.domainConfig);
420
+ };
421
+ ensureDomainConfig((resolvedConfig) => {
422
+ this.domainConfig = resolvedConfig || this.domainConfig;
423
+ if (!this.domainConfig) {
424
+ return;
425
+ }
426
+ const entryPointName = "__ENTRY_POINT_NAME__";
427
+ const uiProfile = typeof window !== "undefined" && window.matchMedia && window.matchMedia("(max-width: 767px)").matches ? "mobile" : "desktop";
428
+ const result = this.domainService.domainInit({ config: this.domainConfig, entryPointName, runtimeHost: this, uiProfile, id });
429
+ console.log('Domain Init Result :', result);
430
+ });
431
+ },
432
+ onDestroy: function () {
433
+ this.domainService.domainDestroy(this);
434
+ },
435
+ },
436
+ };
437
+ };`;
438
+ const listTemplateContent = `get = function () {
439
+ return {
440
+ caption: "",
441
+ columns: [],
442
+ filter: [],
443
+ buttons: [],
444
+ distinct: false,
445
+ functions: {
446
+ beforeLoadData: function () {
447
+ ${sharedDomainConfigResolverScript}
448
+ return new Promise((resolve) => {
449
+ ensureDomainConfig((resolvedConfig) => {
450
+ this.domainConfig = resolvedConfig || this.domainConfig;
451
+ console.log('Template Result :', this.domainConfig);
452
+ this.domainService.doBeforeLoadDataEvent({
453
+ config: this.domainConfig,
454
+ entryPointName: "__ENTRY_POINT_NAME__",
455
+ runtimeHost: this,
456
+ id: this.id,
457
+ ctx: this.contex,
458
+ });
459
+
460
+ if (this._listDomainInitialized !== true) {
461
+ const rendered = this.domainService.domainInit({
462
+ config: this.domainConfig,
463
+ entryPointName: "__ENTRY_POINT_NAME__",
464
+ runtimeHost: this,
465
+ uiProfile: "desktop",
466
+ });
467
+ console.log('Domain Init Result :', rendered);
468
+
469
+ this.caption = rendered.caption || "";
470
+ this.columns = rendered.columns || [];
471
+ this.buttons = rendered.buttons || [];
472
+ this.filter = rendered.filter || [];
473
+ this.distinct = !!rendered.distinct;
474
+ this.functions = Object.assign(this.functions || {}, rendered.functions || {});
475
+ if (this.helper && typeof this.helper.bindFunctionInObj === "function") {
476
+ this.helper.bindFunctionInObj(this.functions, this);
477
+ }
478
+ this._listDomainInitialized = true;
479
+ }
480
+ resolve();
481
+ });
482
+ });
483
+ },
484
+ onPipe: function () {
485
+ const ensureDomainConfig =
486
+ typeof this._ensureDomainConfig === "function"
487
+ ? this._ensureDomainConfig
488
+ : (onResolved) => {
489
+ const callback = typeof onResolved === "function" ? onResolved : function () {};
490
+ callback(this.domainConfig);
491
+ };
492
+ const rows = Array.prototype.slice.call(arguments);
493
+ let result = rows;
494
+ ensureDomainConfig((resolvedConfig) => {
495
+ this.domainConfig = resolvedConfig || this.domainConfig;
496
+ const uiProfile = typeof window !== "undefined" && window.matchMedia && window.matchMedia("(max-width: 767px)").matches ? "mobile" : "desktop";
497
+ result = this.domainService.domainPipe({
498
+ config: this.domainConfig,
499
+ entryPointName: "__ENTRY_POINT_NAME__",
500
+ runtimeHost: this,
501
+ uiProfile,
502
+ rows,
503
+ });
504
+ });
505
+ return result;
506
+ },
507
+ afterLoadData: function () {
508
+ const ensureDomainConfig =
509
+ typeof this._ensureDomainConfig === "function"
510
+ ? this._ensureDomainConfig
511
+ : (onResolved) => {
512
+ const callback = typeof onResolved === "function" ? onResolved : function () {};
513
+ callback(this.domainConfig);
514
+ };
515
+ return new Promise((resolve) => {
516
+ ensureDomainConfig((resolvedConfig) => {
517
+ this.domainConfig = resolvedConfig || this.domainConfig;
518
+ this.domainService.doAfterLoadDataEvent({
519
+ config: this.domainConfig,
520
+ entryPointName: "__ENTRY_POINT_NAME__",
521
+ runtimeHost: this,
522
+ id: this.id,
523
+ ctx: this.contex,
524
+ });
525
+ resolve();
526
+ });
527
+ });
528
+ },
529
+ },
530
+ };
531
+ };`;
532
+ const templateContent = templateType === "list" ? listTemplateContent : formTemplateContent;
533
+
534
+ const safeEntryPointName = String(entryPointName ?? "").trim() || "loginForm";
535
+ const safeDomainKey = typeof domainKey === "string" && domainKey.trim().length > 0 ? domainKey.trim() : "";
536
+ const safeDomainSource = typeof domainSource === "string" && domainSource.trim().length > 0 ? domainSource.trim() : "";
537
+ const domainResolverExpression = `(() => { const domains = appConfig && appConfig.domains && typeof appConfig.domains === 'object' ? appConfig.domains : null; if (!domains) { return null; } const preferredDomainKey = ${JSON.stringify(safeDomainKey)}; if (preferredDomainKey && domains[preferredDomainKey]) { return domains[preferredDomainKey]; } if (domains.salesOrderDomain) { return domains.salesOrderDomain; } const domainKeys = Object.keys(domains); return domainKeys.length > 0 ? domains[domainKeys[0]] : null; })()`;
538
+ const resolvedTemplateContent = templateContent
539
+ .replace(/"__ENTRY_POINT_NAME__"/g, JSON.stringify(safeEntryPointName))
540
+ .replace(/"__DOMAIN_SOURCE__"/g, JSON.stringify(safeDomainSource))
541
+ .replace(
542
+ "appConfig && appConfig.domains && appConfig.domains.salesOrderDomain ? appConfig.domains.salesOrderDomain : null",
543
+ domainResolverExpression,
544
+ );
545
+ const sanitizedTemplateContent = resolvedTemplateContent.replace(/\n\t+/g, " ");
546
+ return JSON.parse(JSON.stringify(sanitizedTemplateContent));
547
+ };
548
+
549
+ export const buildPublishBodyFromConfigs = (parsedApplicationConfig, parsedDomainConfig, configuredDomainName) => {
550
+ const safeApplicationConfig = parsedApplicationConfig ?? {};
551
+ const safeDomainConfig = parsedDomainConfig ?? {};
552
+
553
+ const menuStructure = buildMenuStructureFromApplicationConfig(safeApplicationConfig);
554
+ const menuContent = JSON.stringify(menuStructure);
555
+
556
+ const templateTargets = resolveTemplateTargets(safeDomainConfig, safeApplicationConfig);
557
+ const domainMap =
558
+ safeApplicationConfig?.domains && typeof safeApplicationConfig.domains === "object" ? safeApplicationConfig.domains : {};
559
+ const domainKeys = Object.keys(domainMap).filter((key) => String(key ?? "").trim().length > 0);
560
+ const safeConfiguredDomainName = String(configuredDomainName ?? "").trim();
561
+ const configuredDomainKey = safeConfiguredDomainName
562
+ ? (resolveDomainKeyByReference(safeConfiguredDomainName, domainMap) ??
563
+ domainKeys.find((key) => {
564
+ const source = String(domainMap?.[key]?.source ?? "").trim();
565
+ const sourceName = String(source.split("@")[0] ?? "").trim();
566
+ return sourceName === safeConfiguredDomainName;
567
+ }) ??
568
+ null)
569
+ : null;
570
+ const primaryDomainKey = domainKeys.find((key) => key === "salesOrderDomain") ?? (domainKeys.length > 0 ? domainKeys[0] : null);
571
+ const unresolvedDomainKeys = configuredDomainKey ? domainKeys.filter((key) => key !== configuredDomainKey) : domainKeys;
572
+ const configuredEntryPointNames = new Set(
573
+ [...Object.keys(safeDomainConfig?.ui?.entryPoints ?? {}), ...Object.keys(safeDomainConfig?.ui?.structures ?? {})].filter(
574
+ (name) => String(name ?? "").trim().length > 0,
575
+ ),
576
+ );
577
+ let unresolvedTargetIndex = 0;
578
+ const templateFiles = templateTargets.map((target, targetIndex) => {
579
+ const explicitDomainReference = String(target.domainKey ?? "").trim();
580
+ let resolvedDomainKey = explicitDomainReference.length > 0 ? resolveDomainKeyByReference(explicitDomainReference, domainMap) : null;
581
+ if (explicitDomainReference.length > 0 && !resolvedDomainKey) {
582
+ throw new Error(`Cannot resolve menu domain "${explicitDomainReference}" for entry point "${target.entryPointName}".`);
583
+ }
584
+ if (!resolvedDomainKey) {
585
+ const isConfiguredEntryPoint = configuredEntryPointNames.has(target.entryPointName);
586
+ if (isConfiguredEntryPoint && configuredDomainKey) {
587
+ resolvedDomainKey = configuredDomainKey;
588
+ } else if (configuredDomainKey) {
589
+ resolvedDomainKey =
590
+ unresolvedDomainKeys.length > 0
591
+ ? unresolvedDomainKeys[Math.min(unresolvedTargetIndex, unresolvedDomainKeys.length - 1)]
592
+ : configuredDomainKey;
593
+ unresolvedTargetIndex += 1;
594
+ } else {
595
+ resolvedDomainKey =
596
+ configuredEntryPointNames.has(target.entryPointName) && primaryDomainKey
597
+ ? primaryDomainKey
598
+ : domainKeys.length > 0
599
+ ? domainKeys[Math.min(targetIndex, domainKeys.length - 1)]
600
+ : null;
601
+ }
602
+ }
603
+ const resolvedDomainSource =
604
+ resolvedDomainKey && domainMap && typeof domainMap === "object" ? String(domainMap?.[resolvedDomainKey]?.source ?? "").trim() : "";
605
+ return {
606
+ name: target.fileName,
607
+ content: buildHardcodedTemplateContent(target.entryPointName, resolvedDomainKey, target.templateType ?? "form", resolvedDomainSource),
608
+ };
609
+ });
610
+ const fileList = [{ name: "menu.json", content: menuContent }, ...templateFiles];
611
+
612
+ return { verbose: false, fileList };
613
+ };
614
+
615
+ export const publishApplicationConfig = async (payload = {}, argv = {}) => {
616
+ const applicationConfigName = String(payload?.applicationConfigName ?? "APPLICATION_CONFIG").trim() || "APPLICATION_CONFIG";
617
+ const applicationConfigVersion = String(payload?.applicationConfigVersion ?? "").trim();
618
+ if (!applicationConfigVersion) {
619
+ throw new Error("payload.applicationConfigVersion is required.");
620
+ }
621
+ const config = resolveConfigFromPayload(payload, argv);
622
+
623
+ const appRows = await queryData(getConfigByNameVersionParameters(applicationConfigName, applicationConfigVersion), config);
624
+ const appRow = appRows?.[0];
625
+ if (!appRow?.data) {
626
+ throw new Error(`SYS$CONFIG record ${applicationConfigName}@${applicationConfigVersion} was not found.`);
627
+ }
628
+ const parsedApplicationConfig = parseConfigData(appRow.data) ?? {};
629
+
630
+ /* An app bundle publish is a full replace (addApp() rebuilds the whole bundle from scratch every call), so
631
+ template.js scaffolding must be generated for EVERY domain registered in application.js's `domains` map, not
632
+ just whichever one this particular publish call cares about -- otherwise every other domain's screens silently
633
+ drop out of the bundle on the next publish. Real incident: Supertail's own runtime-domain publish passed only
634
+ `supertailruntime` as the domain to scan, which wiped out all 21 of supertailsetup's template files (they'd
635
+ only ever been generated because the Admin's own editor happened to have supertailsetup loaded). */
636
+ const domainMap =
637
+ parsedApplicationConfig?.domains && typeof parsedApplicationConfig.domains === "object" ? parsedApplicationConfig.domains : {};
638
+ const domainsToScan = new Map();
639
+ for (const [domainKey, domainDef] of Object.entries(domainMap)) {
640
+ const source = String(domainDef?.source ?? "").trim();
641
+ const [sourceName, sourceVersion] = source.split("@").map((part) => String(part ?? "").trim());
642
+ if (sourceName && sourceVersion) {
643
+ domainsToScan.set(domainKey, { domainConfigName: sourceName, domainConfigVersion: sourceVersion });
644
+ }
645
+ }
646
+ const explicitDomainConfigName = String(payload?.domainConfigName ?? "").trim();
647
+ const explicitDomainConfigVersion = String(payload?.domainConfigVersion ?? "").trim();
648
+ if (explicitDomainConfigName && explicitDomainConfigVersion && !domainsToScan.has(explicitDomainConfigName)) {
649
+ domainsToScan.set(explicitDomainConfigName, { domainConfigName: explicitDomainConfigName, domainConfigVersion: explicitDomainConfigVersion });
650
+ }
651
+
652
+ const mergedFileList = [];
653
+ const seenFileNames = new Set();
654
+ const addFilesToMergedList = (fileList) => {
655
+ for (const file of fileList) {
656
+ if (seenFileNames.has(file.name)) {
657
+ continue;
658
+ }
659
+ seenFileNames.add(file.name);
660
+ mergedFileList.push(file);
661
+ }
662
+ };
663
+
664
+ if (domainsToScan.size === 0) {
665
+ addFilesToMergedList(buildPublishBodyFromConfigs(parsedApplicationConfig, {}, "").fileList);
666
+ } else {
667
+ for (const [domainKey, { domainConfigName, domainConfigVersion }] of domainsToScan) {
668
+ const domainRows = await queryData(getConfigByNameVersionParameters(domainConfigName, domainConfigVersion), config);
669
+ const domainRow = domainRows?.[0];
670
+ const parsedDomainConfig = domainRow?.data ? (parseConfigData(domainRow.data) ?? {}) : {};
671
+ addFilesToMergedList(buildPublishBodyFromConfigs(parsedApplicationConfig, parsedDomainConfig, domainKey).fileList);
672
+ }
673
+ }
674
+
675
+ const publishBody = { verbose: false, fileList: mergedFileList };
676
+
677
+ const response = await publishApp(
678
+ {
679
+ domainId: payload?.domainId ?? payload?.subdomain ?? undefined,
680
+ data: publishBody,
681
+ },
682
+ argv,
683
+ );
684
+ if (!response || response.success !== true) {
685
+ throw new Error(response?.error ? String(response.error) : "Application Config publish failed without an error message from the server.");
686
+ }
687
+ return response;
688
+ };
689
+
690
+ export const publishApplicationConfigAsync = async (payload = {}, argv = {}) => {
691
+ const applicationConfigName = String(payload?.applicationConfigName ?? "APPLICATION_CONFIG").trim() || "APPLICATION_CONFIG";
692
+ const applicationConfigVersion = String(payload?.applicationConfigVersion ?? "").trim();
693
+ if (!applicationConfigVersion) {
694
+ return { success: false, error: "payload.applicationConfigVersion is required." };
695
+ }
696
+
697
+ const config = resolveConfigFromPayload(payload, argv);
698
+ const { name, version } = buildPublishStatusConfigKey(applicationConfigName, applicationConfigVersion);
699
+ const startedAt = new Date().toISOString();
700
+
701
+ await writePublishStatus(name, version, { status: "IN_PROGRESS", applicationConfigName, applicationConfigVersion, startedAt }, config);
702
+
703
+ publishApplicationConfig(payload, argv)
704
+ .then((result) =>
705
+ writePublishStatus(
706
+ name,
707
+ version,
708
+ {
709
+ success: true,
710
+ status: "SUCCESS",
711
+ applicationConfigName,
712
+ applicationConfigVersion,
713
+ startedAt,
714
+ finishedAt: new Date().toISOString(),
715
+ result,
716
+ },
717
+ config,
718
+ ),
719
+ )
720
+ .catch((error) =>
721
+ writePublishStatus(
722
+ name,
723
+ version,
724
+ {
725
+ success: false,
726
+ status: "FAILED",
727
+ applicationConfigName,
728
+ applicationConfigVersion,
729
+ startedAt,
730
+ finishedAt: new Date().toISOString(),
731
+ error: error?.message ?? String(error),
732
+ },
733
+ config,
734
+ ),
735
+ )
736
+ .catch((error) => {
737
+ console.error(`[ApplicationConfig Publish] Failed to record publish status for ${applicationConfigName}@${applicationConfigVersion}:`, error);
738
+ });
739
+
740
+ return { success: true, status: "IN_PROGRESS", applicationConfigName, applicationConfigVersion, startedAt };
741
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "biz-a-cli",
3
- "version": "2.3.80-15224",
3
+ "version": "2.3.80-15230",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "type": "module",