biz-a-cli 2.3.78 → 2.3.79-15211
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/hub.js +86 -50
- package/bin/hubEvent.js +6 -70
- package/bin/migrate.js +12 -10
- package/db/db.js +2 -0
- package/db/ds.js +85 -3
- package/engine/bpm/bpm-agent.js +372 -0
- package/engine/bpm/workflow-runtime.js +393 -21
- package/engine/bpm/workflow.js +1 -1
- package/engine/domain/system.js +74 -0
- package/migrations/1777727892577__bpm.sql +1 -1
- package/package.json +1 -1
- package/readme.md +26 -1
- package/scheduler/datalib.js +6 -1
- package/worker/cliScriptWorker.js +85 -1
- package/worker/cliWorkerPool.js +16 -1
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
queryData,
|
|
3
|
+
save as dbSave,
|
|
4
|
+
deleteMD,
|
|
5
|
+
getGlobalConfig,
|
|
6
|
+
} from "../../db/db.js";
|
|
2
7
|
import vm from "node:vm";
|
|
8
|
+
import { execCLIScriptWorker } from "../../worker/cliWorkerPool.js";
|
|
9
|
+
import axios from "axios";
|
|
3
10
|
|
|
4
11
|
// ================
|
|
5
12
|
// INTERNAL HELPERS
|
|
@@ -243,7 +250,13 @@ export async function initiate(payload) {
|
|
|
243
250
|
}
|
|
244
251
|
|
|
245
252
|
export async function availableTransitions(payload) {
|
|
246
|
-
const {
|
|
253
|
+
const {
|
|
254
|
+
instanceId,
|
|
255
|
+
userRoles = [],
|
|
256
|
+
entityData = {},
|
|
257
|
+
triggerType = "USER_EVENT",
|
|
258
|
+
apiConfig,
|
|
259
|
+
} = payload;
|
|
247
260
|
|
|
248
261
|
const instParam = {
|
|
249
262
|
length: 1,
|
|
@@ -311,6 +324,12 @@ export async function availableTransitions(payload) {
|
|
|
311
324
|
operator: "=",
|
|
312
325
|
value1: `'${instance.current_state}'`,
|
|
313
326
|
},
|
|
327
|
+
{
|
|
328
|
+
junction: "AND",
|
|
329
|
+
column: "SYS$BPM_TRANS_DEF.TRIGGER_TYPE",
|
|
330
|
+
operator: "=",
|
|
331
|
+
value1: `'${triggerType}'`,
|
|
332
|
+
},
|
|
314
333
|
],
|
|
315
334
|
};
|
|
316
335
|
|
|
@@ -461,6 +480,10 @@ export async function executeTransition(payload) {
|
|
|
461
480
|
key: "effect_type",
|
|
462
481
|
},
|
|
463
482
|
{ data: "SYS$BPM_TRANS_EFFECT_DEF.EVENT_NAME", key: "event_name" },
|
|
483
|
+
{
|
|
484
|
+
data: "SYS$BPM_TRANS_EFFECT_DEF.PAYLOAD_TEMPLATE",
|
|
485
|
+
key: "payload_template",
|
|
486
|
+
},
|
|
464
487
|
],
|
|
465
488
|
filter: [
|
|
466
489
|
{
|
|
@@ -583,7 +606,7 @@ export async function executeTransition(payload) {
|
|
|
583
606
|
from_state_code: transition.from_state_code,
|
|
584
607
|
to_state_code: transition.to_state_code,
|
|
585
608
|
sys$bpm_trans_def_id: transition.id,
|
|
586
|
-
trigger_type: "
|
|
609
|
+
trigger_type: "USER_EVENT",
|
|
587
610
|
actor_type: "USER",
|
|
588
611
|
actor_id: userId,
|
|
589
612
|
request_id: `TRANS_${transition.transition_code}_${instanceId}_${new Date().getTime()}`,
|
|
@@ -597,6 +620,7 @@ export async function executeTransition(payload) {
|
|
|
597
620
|
sys$bpm_trans_def_id: transition.id,
|
|
598
621
|
effect_type: eff.effect_type,
|
|
599
622
|
event_name: eff.event_name,
|
|
623
|
+
payload_evaluated: eff.payload_template || null,
|
|
600
624
|
status: "PENDING",
|
|
601
625
|
retry_count: 0,
|
|
602
626
|
}));
|
|
@@ -691,7 +715,7 @@ export async function terminate(payload) {
|
|
|
691
715
|
sys$bpm_wf_instance_id: Number(instanceId),
|
|
692
716
|
from_state_code: currentStateCode,
|
|
693
717
|
to_state_code: currentStateCode,
|
|
694
|
-
trigger_type: "
|
|
718
|
+
trigger_type: "USER_EVENT",
|
|
695
719
|
actor_type: "USER",
|
|
696
720
|
actor_id: userId || null,
|
|
697
721
|
request_id: `TERMINATE_${instanceId}_${new Date().getTime()}`,
|
|
@@ -779,10 +803,6 @@ async function fetchInstanceTasksAndTimers(payload) {
|
|
|
779
803
|
length: -1,
|
|
780
804
|
columns: [
|
|
781
805
|
{ data: "SYS$BPM_TASK.ID", key: "id" },
|
|
782
|
-
{
|
|
783
|
-
data: "SYS$BPM_TASK.SYS$BPM_WF_INSTANCE_ID",
|
|
784
|
-
key: "sys$bpm_wf_instance_id",
|
|
785
|
-
},
|
|
786
806
|
{ data: "SYS$BPM_TASK.STATE_CODE", key: "state_code" },
|
|
787
807
|
{ data: "SYS$BPM_TASK.NAME", key: "name" },
|
|
788
808
|
{ data: "SYS$BPM_TASK.STATUS", key: "status" },
|
|
@@ -822,10 +842,6 @@ async function fetchInstanceTasksAndTimers(payload) {
|
|
|
822
842
|
length: -1,
|
|
823
843
|
columns: [
|
|
824
844
|
{ data: "SYS$BPM_TIMER_JOB.ID", key: "id" },
|
|
825
|
-
{
|
|
826
|
-
data: "SYS$BPM_TIMER_JOB.SYS$BPM_WF_INSTANCE_ID",
|
|
827
|
-
key: "sys$bpm_wf_instance_id",
|
|
828
|
-
},
|
|
829
845
|
{
|
|
830
846
|
data: "SYS$BPM_TIMER_JOB.SYS$BPM_TASK_ID",
|
|
831
847
|
key: "sys$bpm_task_id",
|
|
@@ -950,7 +966,7 @@ export async function instanceLogs(payload) {
|
|
|
950
966
|
value1: String(instanceId),
|
|
951
967
|
},
|
|
952
968
|
],
|
|
953
|
-
|
|
969
|
+
order: [{ column: "SYS$BPM_STATE_HISTORY.CREATED_AT", dir: "ASC" }],
|
|
954
970
|
};
|
|
955
971
|
return await queryData(historyQuery, apiConfig);
|
|
956
972
|
}
|
|
@@ -1009,7 +1025,7 @@ export async function instanceTasks(payload) {
|
|
|
1009
1025
|
{ data: "SYS$BPM_TASK.CLOSED_REASON", key: "closed_reason" },
|
|
1010
1026
|
],
|
|
1011
1027
|
filter: filters,
|
|
1012
|
-
|
|
1028
|
+
order: [{ column: "SYS$BPM_TASK.CREATED_AT", dir: "ASC" }],
|
|
1013
1029
|
};
|
|
1014
1030
|
return await queryData(taskQuery, apiConfig);
|
|
1015
1031
|
}
|
|
@@ -1093,7 +1109,7 @@ export async function instanceEffectLogs(payload) {
|
|
|
1093
1109
|
value1: String(instanceId),
|
|
1094
1110
|
},
|
|
1095
1111
|
],
|
|
1096
|
-
|
|
1112
|
+
order: [{ column: "SYS$BPM_EFFECT_LOG.CREATED_AT", dir: "ASC" }],
|
|
1097
1113
|
};
|
|
1098
1114
|
return await queryData(effectQuery, apiConfig);
|
|
1099
1115
|
}
|
|
@@ -1105,6 +1121,7 @@ export async function retryEffect(payload) {
|
|
|
1105
1121
|
length: -1,
|
|
1106
1122
|
columns: [
|
|
1107
1123
|
{ data: "SYS$BPM_EFFECT_LOG.ID", key: "id" },
|
|
1124
|
+
{ data: "SYS$BPM_EFFECT_LOG.STATUS", key: "status" },
|
|
1108
1125
|
{ data: "SYS$BPM_EFFECT_LOG.RETRY_COUNT", key: "retry_count" },
|
|
1109
1126
|
],
|
|
1110
1127
|
filter: [
|
|
@@ -1116,21 +1133,376 @@ export async function retryEffect(payload) {
|
|
|
1116
1133
|
},
|
|
1117
1134
|
],
|
|
1118
1135
|
};
|
|
1119
|
-
const effectRes = await queryData(effectQuery, apiConfig);
|
|
1120
1136
|
|
|
1121
|
-
|
|
1122
|
-
|
|
1137
|
+
const effectRes = await queryData(effectQuery, apiConfig);
|
|
1138
|
+
if (!effectRes || effectRes.length !== 1) {
|
|
1139
|
+
throw new Error("Effect not found or found duplicate effects");
|
|
1123
1140
|
}
|
|
1124
1141
|
|
|
1125
|
-
|
|
1142
|
+
if (effectRes[0].status !== "FAILED") {
|
|
1143
|
+
throw new Error("Action Denied: You can only retry FAILED effects.");
|
|
1144
|
+
}
|
|
1126
1145
|
|
|
1127
1146
|
const dbModel = {
|
|
1128
1147
|
id: effectId,
|
|
1129
|
-
status: "
|
|
1130
|
-
retry_count: currentRetry + 1,
|
|
1148
|
+
status: "RETRYING",
|
|
1131
1149
|
error_message: "",
|
|
1132
1150
|
};
|
|
1133
1151
|
|
|
1134
1152
|
const result = await dbSave({ SYS$BPM_EFFECT_LOG: dbModel });
|
|
1135
1153
|
return result.data;
|
|
1136
1154
|
}
|
|
1155
|
+
|
|
1156
|
+
// ==========================================
|
|
1157
|
+
// BACKGROUND JOB EXECUTION BOUNDARIES
|
|
1158
|
+
// ==========================================
|
|
1159
|
+
|
|
1160
|
+
export async function executeBackgroundTransition(payload, triggerType) {
|
|
1161
|
+
const { instanceId, transitionCode, entityData = {}, apiConfig } = payload;
|
|
1162
|
+
|
|
1163
|
+
// 1. Reuse availableTransitions to automatically enforce Guard Rules and Trigger Types!
|
|
1164
|
+
const validTransitions = await availableTransitions({
|
|
1165
|
+
instanceId: Number(instanceId),
|
|
1166
|
+
userRoles: ["SYSTEM"],
|
|
1167
|
+
entityData: entityData,
|
|
1168
|
+
apiConfig: apiConfig,
|
|
1169
|
+
triggerType: triggerType, // Dynamic: "SYSTEM_EVENT" or "EXTERNAL_EVENT"
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
// 2. Find the exact matching transition
|
|
1173
|
+
const target = validTransitions.find(
|
|
1174
|
+
(t) =>
|
|
1175
|
+
String(t.transition_code).toUpperCase() ===
|
|
1176
|
+
String(transitionCode).toUpperCase(),
|
|
1177
|
+
);
|
|
1178
|
+
|
|
1179
|
+
if (!target) {
|
|
1180
|
+
throw new Error(
|
|
1181
|
+
`Background Transition '${transitionCode}' failed. It is invalid, blocked by Guards, or is not a ${triggerType}.`,
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// 3. Execute natively
|
|
1186
|
+
await executeTransition({
|
|
1187
|
+
instanceId: Number(instanceId),
|
|
1188
|
+
transition: target,
|
|
1189
|
+
userRoles: ["SYSTEM"],
|
|
1190
|
+
entityData: entityData,
|
|
1191
|
+
apiConfig: apiConfig,
|
|
1192
|
+
userId: 0,
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
/**
|
|
1197
|
+
* Executes the pure business logic for a BPM Effect.
|
|
1198
|
+
* @param {Object} effectData - The raw row data from SYS$BPM_EFFECT_LOG
|
|
1199
|
+
* @returns {Object} { success: boolean, message?: string, error?: string }
|
|
1200
|
+
*/
|
|
1201
|
+
export async function executeEffect(effectData) {
|
|
1202
|
+
const effectType = String(
|
|
1203
|
+
effectData.effect_type || effectData.EFFECT_TYPE,
|
|
1204
|
+
).toUpperCase();
|
|
1205
|
+
const eventName = effectData.event_name || effectData.EVENT_NAME;
|
|
1206
|
+
|
|
1207
|
+
console.log(
|
|
1208
|
+
`[BPM Engine] Executing Effect [${effectType}] for Instance ${effectData.sys$bpm_wf_instance_id || effectData.SYS$BPM_WF_INSTANCE_ID}`,
|
|
1209
|
+
);
|
|
1210
|
+
|
|
1211
|
+
try {
|
|
1212
|
+
const rawPayload =
|
|
1213
|
+
effectData.payload_evaluated || effectData.PAYLOAD_EVALUATED;
|
|
1214
|
+
let parsedPayload = {};
|
|
1215
|
+
if (rawPayload) {
|
|
1216
|
+
try {
|
|
1217
|
+
parsedPayload =
|
|
1218
|
+
typeof rawPayload === "string"
|
|
1219
|
+
? JSON.parse(rawPayload)
|
|
1220
|
+
: rawPayload;
|
|
1221
|
+
} catch (e) {
|
|
1222
|
+
parsedPayload = { rawData: rawPayload };
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
const apiConfig = getGlobalConfig();
|
|
1227
|
+
|
|
1228
|
+
if (effectType === "CLI_SCRIPT") {
|
|
1229
|
+
console.log(`[BPM Engine] Bridging Script: ${eventName}`);
|
|
1230
|
+
const scriptResult = await execCLIScriptWorker(
|
|
1231
|
+
apiConfig,
|
|
1232
|
+
eventName,
|
|
1233
|
+
parsedPayload,
|
|
1234
|
+
);
|
|
1235
|
+
|
|
1236
|
+
// --- if cliScript of Effect need to run executeTransition ---
|
|
1237
|
+
if (scriptResult && scriptResult.executeTransition) {
|
|
1238
|
+
const targetCode = scriptResult.executeTransition;
|
|
1239
|
+
const instanceId = Number(
|
|
1240
|
+
effectData.sys$bpm_wf_instance_id ||
|
|
1241
|
+
effectData.SYS$BPM_WF_INSTANCE_ID,
|
|
1242
|
+
);
|
|
1243
|
+
delete scriptResult.executeTransition;
|
|
1244
|
+
|
|
1245
|
+
await executeBackgroundTransition(
|
|
1246
|
+
{
|
|
1247
|
+
instanceId: instanceId,
|
|
1248
|
+
transitionCode: targetCode,
|
|
1249
|
+
entityData: parsedPayload || {},
|
|
1250
|
+
apiConfig: apiConfig,
|
|
1251
|
+
},
|
|
1252
|
+
"SYSTEM_EVENT",
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
return {
|
|
1257
|
+
success: true,
|
|
1258
|
+
message: `Effect executed successfully.`,
|
|
1259
|
+
data: scriptResult,
|
|
1260
|
+
};
|
|
1261
|
+
} else if (effectType === "HTTP") {
|
|
1262
|
+
console.log(
|
|
1263
|
+
`[BPM Engine] Executing HTTP Request: ${parsedPayload.url}`,
|
|
1264
|
+
);
|
|
1265
|
+
const response = await axios({
|
|
1266
|
+
method: parsedPayload.method || "POST",
|
|
1267
|
+
url: parsedPayload.url,
|
|
1268
|
+
data: parsedPayload.data || {},
|
|
1269
|
+
headers: parsedPayload.headers || {},
|
|
1270
|
+
});
|
|
1271
|
+
if (response.status >= 400) {
|
|
1272
|
+
throw new Error(
|
|
1273
|
+
`HTTP Error: ${response.status} - ${response.statusText}`,
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
} else if (effectType === "WEBHOOK") {
|
|
1277
|
+
console.log(
|
|
1278
|
+
`[BPM Engine] Dispatching Webhook: ${parsedPayload.url}`,
|
|
1279
|
+
);
|
|
1280
|
+
axios({
|
|
1281
|
+
method: parsedPayload.method || "POST",
|
|
1282
|
+
url: parsedPayload.url,
|
|
1283
|
+
data: parsedPayload.data || {},
|
|
1284
|
+
headers: parsedPayload.headers || {},
|
|
1285
|
+
}).catch((err) => {
|
|
1286
|
+
console.log(
|
|
1287
|
+
`[BPM Engine] Webhook Silent Failure (${eventName}): ${err.message}`,
|
|
1288
|
+
);
|
|
1289
|
+
});
|
|
1290
|
+
} else {
|
|
1291
|
+
throw new Error(`Unrecognized EFFECT_TYPE: ${effectType}`);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
return { success: true, message: `Effect executed successfully.` };
|
|
1295
|
+
} catch (error) {
|
|
1296
|
+
return {
|
|
1297
|
+
success: false,
|
|
1298
|
+
error: error.message || "Unknown execution error",
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
/**
|
|
1304
|
+
* Executes the pure business logic for a BPM Timer / SLA breach.
|
|
1305
|
+
* @param {Object} timerData - The raw row data from SYS$BPM_TIMER_JOB
|
|
1306
|
+
* @returns {Object} { success: boolean, message?: string, error?: string }
|
|
1307
|
+
*/
|
|
1308
|
+
export async function executeTimerJob(timerData) {
|
|
1309
|
+
console.log(
|
|
1310
|
+
`[BPM Engine] Firing Timer [${timerData.timer_type || timerData.TIMER_TYPE}] for Instance ${timerData.sys$bpm_wf_instance_id || timerData.SYS$BPM_WF_INSTANCE_ID}`,
|
|
1311
|
+
);
|
|
1312
|
+
|
|
1313
|
+
try {
|
|
1314
|
+
const type = String(
|
|
1315
|
+
timerData.timer_type || timerData.TIMER_TYPE,
|
|
1316
|
+
).toUpperCase();
|
|
1317
|
+
const instanceId =
|
|
1318
|
+
timerData.sys$bpm_wf_instance_id ||
|
|
1319
|
+
timerData.SYS$BPM_WF_INSTANCE_ID;
|
|
1320
|
+
const stateCode = timerData.state_code || timerData.STATE_CODE;
|
|
1321
|
+
const apiConfig = getGlobalConfig();
|
|
1322
|
+
|
|
1323
|
+
if (type === "SLA_TIMEOUT") {
|
|
1324
|
+
// 1. Look up Workflow Def ID
|
|
1325
|
+
const instRes = await queryData(
|
|
1326
|
+
{
|
|
1327
|
+
length: 1,
|
|
1328
|
+
columns: [
|
|
1329
|
+
{
|
|
1330
|
+
data: "SYS$BPM_WF_INSTANCE.SYS$BPM_WORKFLOW_DEF_ID",
|
|
1331
|
+
key: "def_id",
|
|
1332
|
+
},
|
|
1333
|
+
],
|
|
1334
|
+
filter: [
|
|
1335
|
+
{
|
|
1336
|
+
junction: "",
|
|
1337
|
+
column: "SYS$BPM_WF_INSTANCE.ID",
|
|
1338
|
+
operator: "=",
|
|
1339
|
+
value1: String(instanceId),
|
|
1340
|
+
},
|
|
1341
|
+
],
|
|
1342
|
+
},
|
|
1343
|
+
apiConfig,
|
|
1344
|
+
);
|
|
1345
|
+
|
|
1346
|
+
if (!instRes || instRes.length === 0)
|
|
1347
|
+
throw new Error("Workflow Instance not found.");
|
|
1348
|
+
|
|
1349
|
+
// 2. Look up State Configuration
|
|
1350
|
+
const stateRes = await queryData(
|
|
1351
|
+
{
|
|
1352
|
+
length: 1,
|
|
1353
|
+
columns: [
|
|
1354
|
+
{
|
|
1355
|
+
data: "SYS$BPM_STATE_DEF.TIMEOUT_ACTION_TYPE",
|
|
1356
|
+
key: "timeout_action_type",
|
|
1357
|
+
},
|
|
1358
|
+
{
|
|
1359
|
+
data: "SYS$BPM_STATE_DEF.TIMEOUT_EVENT_NAME",
|
|
1360
|
+
key: "timeout_event_name",
|
|
1361
|
+
},
|
|
1362
|
+
],
|
|
1363
|
+
filter: [
|
|
1364
|
+
{
|
|
1365
|
+
junction: "",
|
|
1366
|
+
column: "SYS$BPM_STATE_DEF.SYS$BPM_WORKFLOW_DEF_ID",
|
|
1367
|
+
operator: "=",
|
|
1368
|
+
value1: String(instRes[0].def_id),
|
|
1369
|
+
},
|
|
1370
|
+
{
|
|
1371
|
+
junction: "AND",
|
|
1372
|
+
column: "SYS$BPM_STATE_DEF.STATE_CODE",
|
|
1373
|
+
operator: "=",
|
|
1374
|
+
value1: `'${stateCode}'`,
|
|
1375
|
+
},
|
|
1376
|
+
],
|
|
1377
|
+
},
|
|
1378
|
+
apiConfig,
|
|
1379
|
+
);
|
|
1380
|
+
|
|
1381
|
+
if (stateRes && stateRes.length > 0) {
|
|
1382
|
+
const actionType = String(
|
|
1383
|
+
stateRes[0].timeout_action_type || "",
|
|
1384
|
+
).toUpperCase();
|
|
1385
|
+
const eventName = stateRes[0].timeout_event_name;
|
|
1386
|
+
|
|
1387
|
+
if (actionType === "EMIT EVENT" && eventName) {
|
|
1388
|
+
const generatedPayload = JSON.stringify({
|
|
1389
|
+
instanceId: Number(instanceId),
|
|
1390
|
+
});
|
|
1391
|
+
await dbSave(
|
|
1392
|
+
{
|
|
1393
|
+
SYS$BPM_EFFECT_LOG: {
|
|
1394
|
+
id: null,
|
|
1395
|
+
sys$bpm_wf_instance_id: Number(instanceId),
|
|
1396
|
+
effect_type: "CLI_SCRIPT",
|
|
1397
|
+
event_name: eventName,
|
|
1398
|
+
payload_evaluated: generatedPayload, // INJECT IT HERE
|
|
1399
|
+
status: "PENDING",
|
|
1400
|
+
retry_count: 0,
|
|
1401
|
+
},
|
|
1402
|
+
},
|
|
1403
|
+
apiConfig,
|
|
1404
|
+
);
|
|
1405
|
+
} else if (actionType === "AUTO TRANSITION") {
|
|
1406
|
+
const transParam = {
|
|
1407
|
+
length: -1,
|
|
1408
|
+
columns: [
|
|
1409
|
+
{ data: "SYS$BPM_TRANS_DEF.ID", key: "id" },
|
|
1410
|
+
{
|
|
1411
|
+
data: "SYS$BPM_TRANS_DEF.TRANSITION_CODE",
|
|
1412
|
+
key: "transition_code",
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
data: "SYS$BPM_TRANS_DEF.FROM_STATE_CODE",
|
|
1416
|
+
key: "from_state_code",
|
|
1417
|
+
},
|
|
1418
|
+
{
|
|
1419
|
+
data: "SYS$BPM_TRANS_DEF.TO_STATE_CODE",
|
|
1420
|
+
key: "to_state_code",
|
|
1421
|
+
},
|
|
1422
|
+
{
|
|
1423
|
+
data: "SYS$BPM_TRANS_DEF.GUARD_TYPE",
|
|
1424
|
+
key: "guard_type",
|
|
1425
|
+
},
|
|
1426
|
+
{
|
|
1427
|
+
data: "SYS$BPM_TRANS_DEF.GUARD_RULE_KEY",
|
|
1428
|
+
key: "guard_rule_key",
|
|
1429
|
+
},
|
|
1430
|
+
],
|
|
1431
|
+
filter: [
|
|
1432
|
+
{
|
|
1433
|
+
junction: "",
|
|
1434
|
+
column: "SYS$BPM_TRANS_DEF.SYS$BPM_WORKFLOW_DEF_ID",
|
|
1435
|
+
operator: "=",
|
|
1436
|
+
value1: String(instRes[0].def_id),
|
|
1437
|
+
},
|
|
1438
|
+
{
|
|
1439
|
+
junction: "AND",
|
|
1440
|
+
column: "SYS$BPM_TRANS_DEF.FROM_STATE_CODE",
|
|
1441
|
+
operator: "=",
|
|
1442
|
+
value1: `'${stateCode}'`,
|
|
1443
|
+
},
|
|
1444
|
+
{
|
|
1445
|
+
junction: "AND",
|
|
1446
|
+
column: "SYS$BPM_TRANS_DEF.TRIGGER_TYPE",
|
|
1447
|
+
operator: "=",
|
|
1448
|
+
value1: "'TIMER_EVENT'",
|
|
1449
|
+
},
|
|
1450
|
+
],
|
|
1451
|
+
};
|
|
1452
|
+
|
|
1453
|
+
const timerTransitions = await queryData(
|
|
1454
|
+
transParam,
|
|
1455
|
+
apiConfig,
|
|
1456
|
+
);
|
|
1457
|
+
if (!timerTransitions || timerTransitions.length === 0) {
|
|
1458
|
+
throw new Error(
|
|
1459
|
+
`SLA Breached but no TIMER_EVENT transition found for state ${stateCode}`,
|
|
1460
|
+
);
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
let validTransition = null;
|
|
1464
|
+
for (const t of timerTransitions) {
|
|
1465
|
+
// Assuming empty entityData for timer execution; Domain updates should happen prior to timer trigger
|
|
1466
|
+
const guard = await evaluateGuardRule(
|
|
1467
|
+
t.guard_type,
|
|
1468
|
+
t.guard_rule_key,
|
|
1469
|
+
{},
|
|
1470
|
+
);
|
|
1471
|
+
if (guard.isPassed) {
|
|
1472
|
+
if (validTransition)
|
|
1473
|
+
throw new Error(
|
|
1474
|
+
"Ambiguous timer flow: Multiple valid TIMER_EVENT paths.",
|
|
1475
|
+
);
|
|
1476
|
+
validTransition = t;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
if (!validTransition) {
|
|
1481
|
+
throw new Error(
|
|
1482
|
+
"SLA Breached but no TIMER_EVENT transition passed the guard rules.",
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
await executeTransition({
|
|
1487
|
+
instanceId: Number(instanceId),
|
|
1488
|
+
transition: validTransition,
|
|
1489
|
+
userRoles: ["SYSTEM"],
|
|
1490
|
+
entityData: {},
|
|
1491
|
+
apiConfig: apiConfig,
|
|
1492
|
+
userId: null,
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
return {
|
|
1499
|
+
success: true,
|
|
1500
|
+
message: `Timer ${timerData.id || timerData.ID} fired successfully.`,
|
|
1501
|
+
};
|
|
1502
|
+
} catch (error) {
|
|
1503
|
+
return {
|
|
1504
|
+
success: false,
|
|
1505
|
+
error: error.message || "Unknown timer execution error",
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
}
|
package/engine/bpm/workflow.js
CHANGED
|
@@ -144,7 +144,7 @@ export async function publish(payload) {
|
|
|
144
144
|
`${trans.fromStateCode}_TO_${trans.toStateCode}_${index}`,
|
|
145
145
|
from_state_code: trans.fromStateCode,
|
|
146
146
|
to_state_code: trans.toStateCode,
|
|
147
|
-
trigger_type: trans.triggerType || "
|
|
147
|
+
trigger_type: trans.triggerType || "USER_EVENT",
|
|
148
148
|
label: trans.label,
|
|
149
149
|
guard_type: trans.guardType || null,
|
|
150
150
|
guard_rule_key: trans.guardRuleKey || null,
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export const publishApp = async (data, argv = {}) => {
|
|
2
|
+
try {
|
|
3
|
+
process.env.BIZA_APP_SKIP_PARSE = "1";
|
|
4
|
+
const { addApp } = await import("../../bin/app.js");
|
|
5
|
+
|
|
6
|
+
const sourcePayload = Object.prototype.hasOwnProperty.call(
|
|
7
|
+
data || {},
|
|
8
|
+
"body",
|
|
9
|
+
)
|
|
10
|
+
? data.body
|
|
11
|
+
: data?.data;
|
|
12
|
+
const requestBody =
|
|
13
|
+
typeof sourcePayload === "string"
|
|
14
|
+
? JSON.parse(sourcePayload || "{}")
|
|
15
|
+
: sourcePayload || {};
|
|
16
|
+
const hasTransportEnvelope =
|
|
17
|
+
requestBody &&
|
|
18
|
+
typeof requestBody === "object" &&
|
|
19
|
+
typeof requestBody.body === "object" &&
|
|
20
|
+
(Object.prototype.hasOwnProperty.call(requestBody, "method") ||
|
|
21
|
+
Object.prototype.hasOwnProperty.call(requestBody, "query") ||
|
|
22
|
+
Object.prototype.hasOwnProperty.call(requestBody, "headers"));
|
|
23
|
+
const unwrappedPayload = hasTransportEnvelope
|
|
24
|
+
? requestBody.body
|
|
25
|
+
: requestBody;
|
|
26
|
+
const publishPayload =
|
|
27
|
+
unwrappedPayload &&
|
|
28
|
+
typeof unwrappedPayload === "object" &&
|
|
29
|
+
unwrappedPayload.addApp &&
|
|
30
|
+
typeof unwrappedPayload.addApp === "object"
|
|
31
|
+
? unwrappedPayload.addApp
|
|
32
|
+
: unwrappedPayload;
|
|
33
|
+
const params =
|
|
34
|
+
publishPayload &&
|
|
35
|
+
typeof publishPayload === "object" &&
|
|
36
|
+
typeof publishPayload.options === "object"
|
|
37
|
+
? publishPayload.options
|
|
38
|
+
: publishPayload && typeof publishPayload === "object"
|
|
39
|
+
? publishPayload
|
|
40
|
+
: {};
|
|
41
|
+
const toNumber = (value, fallback) => {
|
|
42
|
+
const parsed = Number(value);
|
|
43
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const response = await addApp({
|
|
47
|
+
workingDir: params.workingDir || process.cwd(),
|
|
48
|
+
verbose: !!params.verbose,
|
|
49
|
+
server: argv.server || params.server || "http://localhost",
|
|
50
|
+
apiPort: toNumber(argv.port, toNumber(params.apiPort, 212)),
|
|
51
|
+
dbIndex: toNumber(argv.dbindex, toNumber(params.dbIndex, 2)),
|
|
52
|
+
sub:
|
|
53
|
+
argv.subdomain ||
|
|
54
|
+
params.sub ||
|
|
55
|
+
params.subdomain ||
|
|
56
|
+
data?.domainId,
|
|
57
|
+
files: params.files || params.fileList,
|
|
58
|
+
body: publishPayload,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return response;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
return {
|
|
64
|
+
success: false,
|
|
65
|
+
error: error?.message || error,
|
|
66
|
+
};
|
|
67
|
+
} finally {
|
|
68
|
+
delete process.env.BIZA_APP_SKIP_PARSE;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export function publish(payload, argv = {}) {
|
|
73
|
+
return publishApp(payload, argv);
|
|
74
|
+
}
|
|
@@ -61,7 +61,7 @@ BEGIN
|
|
|
61
61
|
TRANSITION_CODE VARCHAR(100) NOT NULL,
|
|
62
62
|
FROM_STATE_CODE VARCHAR(100) NOT NULL,
|
|
63
63
|
TO_STATE_CODE VARCHAR(100) NOT NULL,
|
|
64
|
-
TRIGGER_TYPE VARCHAR(30) DEFAULT ''
|
|
64
|
+
TRIGGER_TYPE VARCHAR(30) DEFAULT ''USER_EVENT'' NOT NULL,
|
|
65
65
|
LABEL VARCHAR(100),
|
|
66
66
|
GUARD_TYPE VARCHAR(30),
|
|
67
67
|
GUARD_RULE_KEY VARCHAR(100),
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -82,4 +82,29 @@
|
|
|
82
82
|
hub --server [BizA Hub Server] --sub [subdomain] --hostname [ip_API] --port [port_API] --publish [true]
|
|
83
83
|
|
|
84
84
|
Example :
|
|
85
|
-
hub --server https://server.biz-a.id --sub imamatek --hostname localhost --port 212 --publish
|
|
85
|
+
hub --server https://server.biz-a.id --sub imamatek --hostname localhost --port 212 --publish
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
## IV. Scalability
|
|
89
|
+
|
|
90
|
+
### a. Monolithic Mode (Default)
|
|
91
|
+
hub --mode monolithic --server [BizA Hub Server] --sub [subdomain]
|
|
92
|
+
|
|
93
|
+
Example :
|
|
94
|
+
hub --server https://server.biz-a.id --sub imamatek
|
|
95
|
+
|
|
96
|
+
### b. Dispatcher Mode (Load Balancer & ESB)
|
|
97
|
+
hub --mode dispatcher --server [BizA Hub Server] --sub [subdomain]
|
|
98
|
+
|
|
99
|
+
Example :
|
|
100
|
+
hub --mode dispatcher --server https://server.biz-a.id --sub imamatek
|
|
101
|
+
|
|
102
|
+
### c. Multiple Agent Mode (Worker Nodes)
|
|
103
|
+
hub --mode agent --dispatcherUrl [Dispatcher URL] --services [Services] --sp [Port]
|
|
104
|
+
|
|
105
|
+
Example :
|
|
106
|
+
hub --mode agent --dispatcherUrl http://localhost:3000 --services BPM,EMAIL --sp 3001
|
|
107
|
+
hub --mode agent --dispatcherUrl http://localhost:3000 --services BPM,EMAIL --sp 3002
|
|
108
|
+
|
|
109
|
+
> [!TIP]
|
|
110
|
+
> When running multiple Agents on the same server, always define unique ports using --sp to avoid collisions.
|
package/scheduler/datalib.js
CHANGED
|
@@ -176,7 +176,11 @@ export async function setLibrary(config, libraries, defaultLib) {
|
|
|
176
176
|
return libs;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
export async function extractFunctionScript(
|
|
179
|
+
export async function extractFunctionScript(
|
|
180
|
+
selectedConfig,
|
|
181
|
+
data,
|
|
182
|
+
extraLib = {},
|
|
183
|
+
) {
|
|
180
184
|
const config = getConfig(selectedConfig);
|
|
181
185
|
|
|
182
186
|
if (data.error) {
|
|
@@ -197,6 +201,7 @@ export async function extractFunctionScript(selectedConfig, data) {
|
|
|
197
201
|
crypto: crypto,
|
|
198
202
|
log: logger,
|
|
199
203
|
data: { sendModel, queryData, crudData, genId, getUrlAndParam },
|
|
204
|
+
...extraLib,
|
|
200
205
|
};
|
|
201
206
|
|
|
202
207
|
if (scriptFn().functions.useLibrary) {
|