biz-a-cli 2.3.73 → 2.3.75

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/db/ds.js ADDED
@@ -0,0 +1,189 @@
1
+ import axios from "axios";
2
+ import { IDLE_SOCKET_TIMEOUT_MILLISECONDS } from "../bin/hubEvent.js";
3
+
4
+ let injectedConfig = {};
5
+
6
+ export const setGlobalConfig = (cfg) => {
7
+ injectedConfig = cfg;
8
+ };
9
+
10
+ // ===============
11
+ // PRIVATE HELPERS
12
+ // ===============
13
+
14
+ /**
15
+ * Configuration object for DataSnap requests.
16
+ * All properties are OPTIONAL.
17
+ * Fallback Priority: 1. Caller Config -> 2. CLI ARGV -> 3. Hardcoded Defaults
18
+ *
19
+ * @typedef {Object} DsConfig
20
+ * @property {string} [url] - Override full base URL (e.g., "http://localhost:212").
21
+ * @property {boolean} [secure] - Use HTTPS? Fallback: argv.secure or false.
22
+ * @property {string} [hostname] - FINA API Hostname. Fallback: argv.hostname or "127.0.0.1".
23
+ * @property {number} [port] - FINA API Port. Fallback: argv.port or 212.
24
+ * @property {number} [dbindex] - Firebird Database Index. Fallback: argv.dbindex or 2.
25
+ * @property {string} [methodClass] - DataSnap Method Class. Default: "TOrmMethod".
26
+ * @property {number} [timeout] - Axios timeout in ms. Default: IDLE_SOCKET_TIMEOUT_MILLISECONDS.
27
+ */
28
+
29
+ function resolveConfig(config = {}) {
30
+ // 3-Tier Fallback: 1. Caller Config -> 2. Global Defaults (hub.js argv) -> 3. Hardcoded Default
31
+ const isSecure = config.secure ?? injectedConfig.secure ?? false;
32
+ const protocol = isSecure ? "https://" : "http://";
33
+ const hostname = config.hostname ?? injectedConfig.hostname ?? "127.0.0.1";
34
+ const port = config.port ?? injectedConfig.port ?? 212;
35
+
36
+ return {
37
+ url: config.url || `${protocol}${hostname}:${port}`,
38
+ dbindex: config.dbindex ?? injectedConfig.dbindex ?? 2,
39
+ methodClass: config.methodClass ?? "TOrmMethod",
40
+ timeout: config.timeout ?? IDLE_SOCKET_TIMEOUT_MILLISECONDS,
41
+ };
42
+ }
43
+
44
+ function buildDataSnapUrl(resolvedConfig, dsPath, isPost = true) {
45
+ const encodedPath = isPost ? `%22${dsPath}%22` : dsPath;
46
+ return `${resolvedConfig.url}/fina/rest/${resolvedConfig.methodClass}/${encodedPath}`;
47
+ }
48
+
49
+ function buildDataSnapPayload(resolvedConfig, ormMethod, payloadObject) {
50
+ const paramObj = {
51
+ dbIndex: resolvedConfig.dbindex, // Critical for MultiDBServerMethod.pas, if payloadObject not have dbIndex
52
+ method: ormMethod,
53
+ object: payloadObject,
54
+ };
55
+
56
+ return JSON.stringify({
57
+ _parameters: [JSON.stringify(paramObj)],
58
+ });
59
+ }
60
+
61
+ const dsReq = async (url, data, timeoutMs, httpMethod = "POST") => {
62
+ // Universal DataSnap Requester using axios.request
63
+ const res = await axios.request({
64
+ method: httpMethod,
65
+ url: url,
66
+ data: data,
67
+ timeout: timeoutMs,
68
+ // MANDATORY: Prevents DataSnap from parsing JSON into multiple parameters
69
+ headers: { "content-type": "text/plain" },
70
+ });
71
+
72
+ // Normalize response (Bypass the JSON stringify tax if DataSnap double-stringified)
73
+ const parsedRes =
74
+ typeof res.data === "string" ? JSON.parse(res.data) : res.data;
75
+
76
+ // GATEKEEPER: Catch Delphi's `Result.AddPair(KEY_ERROR, E.Message);`
77
+ if (parsedRes && parsedRes.error) {
78
+ throw new Error(parsedRes.error);
79
+ }
80
+
81
+ return parsedRes;
82
+ };
83
+
84
+ // ============================================================================
85
+ // PUBLIC API (Zero-Config Needed by Caller, but worker-safe via config param)
86
+ // ============================================================================
87
+
88
+ export const queryData = async (payload, config = {}) => {
89
+ const cfg = resolveConfig(config);
90
+ const url = buildDataSnapUrl(cfg, "list");
91
+ const data = buildDataSnapPayload(cfg, "list", payload);
92
+
93
+ const res = await dsReq(url, data, cfg.timeout);
94
+ let rawData = res;
95
+ if (res && res.data) {
96
+ rawData =
97
+ typeof res.data === "string" ? JSON.parse(res.data) : res.data;
98
+ }
99
+ if (payload && Array.isArray(payload.columns) && Array.isArray(rawData)) {
100
+ return rawData.map((row) => {
101
+ const mappedObj = {};
102
+
103
+ payload.columns.forEach((col, index) => {
104
+ if (!col.key) return;
105
+
106
+ if (Array.isArray(row)) {
107
+ mappedObj[col.key] = row[index];
108
+ } else {
109
+ const exactDataMatch = row[col.data];
110
+ const upperDataMatch = row[String(col.data).toUpperCase()];
111
+ const aliasMatch = col.alias ? row[col.alias] : undefined;
112
+ const upperAliasMatch = col.alias
113
+ ? row[String(col.alias).toUpperCase()]
114
+ : undefined;
115
+
116
+ if (exactDataMatch !== undefined)
117
+ mappedObj[col.key] = exactDataMatch;
118
+ else if (upperDataMatch !== undefined)
119
+ mappedObj[col.key] = upperDataMatch;
120
+ else if (aliasMatch !== undefined)
121
+ mappedObj[col.key] = aliasMatch;
122
+ else if (upperAliasMatch !== undefined)
123
+ mappedObj[col.key] = upperAliasMatch;
124
+ }
125
+ });
126
+ return mappedObj;
127
+ });
128
+ }
129
+ return rawData;
130
+ };
131
+
132
+ export const save = async (payload, config = {}) => {
133
+ const cfg = resolveConfig(config);
134
+ const url = buildDataSnapUrl(cfg, "orm");
135
+ const data = buildDataSnapPayload(cfg, "put", payload);
136
+ return await dsReq(url, data, cfg.timeout);
137
+ };
138
+
139
+ export const remove = async (payload, config = {}) => {
140
+ const cfg = resolveConfig(config);
141
+ const url = buildDataSnapUrl(cfg, "orm");
142
+ const data = buildDataSnapPayload(cfg, "delete", payload);
143
+ return await dsReq(url, data, cfg.timeout);
144
+ };
145
+
146
+ export const removeMD = async (payload, config = {}) => {
147
+ const cfg = resolveConfig(config);
148
+ const url = buildDataSnapUrl(cfg, "orm");
149
+
150
+ const { id, master, detail } = payload;
151
+ let delphiObj = { [master]: { id: Number(id) } };
152
+
153
+ const buildNestedDetail = (details) => {
154
+ let result = {};
155
+ for (const item of details) {
156
+ if (typeof item === "string") {
157
+ result[item] = [];
158
+ } else if (typeof item === "object" && item !== null) {
159
+ for (const key of Object.keys(item)) {
160
+ if (Array.isArray(item[key]) && item[key].length > 0) {
161
+ result[key] = [buildNestedDetail(item[key])];
162
+ }
163
+ }
164
+ }
165
+ }
166
+ return result;
167
+ };
168
+
169
+ if (detail && detail.length > 0) {
170
+ Object.assign(delphiObj[master], buildNestedDetail(detail));
171
+ }
172
+
173
+ const data = buildDataSnapPayload(cfg, "deletemd", delphiObj);
174
+ return await dsReq(url, data, cfg.timeout);
175
+ };
176
+
177
+ export const list = async (payload, config = {}) => {
178
+ const cfg = resolveConfig(config);
179
+ const url = buildDataSnapUrl(cfg, "list");
180
+ const data = buildDataSnapPayload(cfg, "list", payload);
181
+ return await dsReq(url, data, cfg.timeout);
182
+ };
183
+
184
+ export const executeBlock = async (sqlString, config = {}) => {
185
+ const cfg = resolveConfig(config);
186
+ const url = buildDataSnapUrl(cfg, "block");
187
+ const data = buildDataSnapPayload(cfg, "block", sqlString);
188
+ return await dsReq(url, data, cfg.timeout);
189
+ };
@@ -0,0 +1,61 @@
1
+ import express from "express";
2
+ import * as workflow from "./workflow.js";
3
+ import * as workflowRT from "./workflow-runtime.js";
4
+
5
+ const router = express.Router();
6
+
7
+ // The Wrapper: Translates HTTP requests into pure Engine payloads
8
+ const expressWrapper = (engineFunction) => async (req, res) => {
9
+ try {
10
+ const result = await engineFunction(req.body);
11
+ res.json({ success: true, data: result });
12
+ } catch (error) {
13
+ res.status(400).json({ success: false, error: error.message });
14
+ }
15
+ };
16
+
17
+ // ==========================================
18
+ // DESIGN TIME: /workflow/ Prefix
19
+ // ==========================================
20
+ router.post("/workflow/save", expressWrapper(workflow.save));
21
+ router.post("/workflow/publish", expressWrapper(workflow.publish));
22
+ router.post("/workflow/remove", expressWrapper(workflow.remove));
23
+
24
+ // ==========================================
25
+ // RUNTIME EXECUTION: /workflowRT/ Prefix
26
+ // ==========================================
27
+ router.post("/workflowRT/initiate", expressWrapper(workflowRT.initiate));
28
+ router.post(
29
+ "/workflowRT/availableTransitions",
30
+ expressWrapper(workflowRT.availableTransitions),
31
+ );
32
+ router.post(
33
+ "/workflowRT/executeTransition",
34
+ expressWrapper(workflowRT.executeTransition),
35
+ );
36
+ router.post("/workflowRT/terminate", expressWrapper(workflowRT.terminate));
37
+ router.post("/workflowRT/remove", expressWrapper(workflowRT.remove));
38
+ router.post("/workflowRT/myTasks", expressWrapper(workflowRT.myTasks));
39
+ router.post(
40
+ "/workflowRT/instanceState",
41
+ expressWrapper(workflowRT.instanceState),
42
+ );
43
+ router.post(
44
+ "/workflowRT/instanceLogs",
45
+ expressWrapper(workflowRT.instanceLogs),
46
+ );
47
+ router.post(
48
+ "/workflowRT/instanceTasks",
49
+ expressWrapper(workflowRT.instanceTasks),
50
+ );
51
+ router.post(
52
+ "/workflowRT/instanceTimers",
53
+ expressWrapper(workflowRT.instanceTimers),
54
+ );
55
+ router.post(
56
+ "/workflowRT/instanceEffectLogs",
57
+ expressWrapper(workflowRT.instanceEffectLogs),
58
+ );
59
+ router.post("/workflowRT/retryEffect", expressWrapper(workflowRT.retryEffect));
60
+
61
+ export default router;