biz-a-cli 2.3.80-15419 → 2.3.80

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.
Files changed (80) hide show
  1. package/bin/app.js +20 -255
  2. package/bin/hub.js +0 -16
  3. package/bin/hubEvent.js +5 -98
  4. package/bin/tunnelTarget.js +0 -4
  5. package/engine/bpm/workflow.js +6 -7
  6. package/engine/cc/configValidate.js +0 -53
  7. package/engine/cc/guides/V2-SCREEN-AUTHORING.md +512 -531
  8. package/engine/cc/layer3Behavior.js +1 -2
  9. package/engine/cc/layer3Schema.js +1 -3
  10. package/engine/cc/livePipelines.js +3 -9
  11. package/engine/cc/publish.js +105 -86
  12. package/engine/domain/appConfig.js +18 -108
  13. package/engine/domain/applicationConfig.js +12 -87
  14. package/engine/domain/dialect.js +0 -138
  15. package/engine/domain/domainNavigation.js +50 -86
  16. package/engine/domain/entityConfigLoader.js +4 -16
  17. package/engine/domain/numericScale.js +0 -2
  18. package/engine/domain/system.js +0 -2
  19. package/engine/ext/publicTheme.js +1 -8
  20. package/engine/orm/apiRoute.js +61 -187
  21. package/engine/orm/entityConfig.js +2 -32
  22. package/engine/orm/finaAuth.js +0 -0
  23. package/engine/orm/finaEndpoint.js +0 -19
  24. package/engine/orm/imgQuery.js +0 -92
  25. package/engine/orm/listQuery.js +1 -6
  26. package/package.json +1 -1
  27. package/worker/cliScriptWorker.js +0 -5
  28. package/engine/attachments/store.js +0 -195
  29. package/engine/domain/domainRegistration.js +0 -65
  30. package/engine/fina/approval.js +0 -141
  31. package/engine/fina/approvalService.js +0 -59
  32. package/engine/fina/attachments.js +0 -165
  33. package/engine/fina/client.js +0 -84
  34. package/engine/fina/config.js +0 -26
  35. package/engine/fina/context.js +0 -58
  36. package/engine/fina/document.js +0 -67
  37. package/engine/fina/documentWrite.js +0 -173
  38. package/engine/fina/documents/index.js +0 -28
  39. package/engine/fina/documents/jv.js +0 -105
  40. package/engine/fina/documents/jvWrite.js +0 -260
  41. package/engine/fina/documents/si.js +0 -40
  42. package/engine/fina/errors.js +0 -17
  43. package/engine/fina/features.js +0 -86
  44. package/engine/fina/guard.js +0 -102
  45. package/engine/fina/identity.js +0 -87
  46. package/engine/fina/live.js +0 -19
  47. package/engine/fina/lock.js +0 -35
  48. package/engine/fina/master.js +0 -331
  49. package/engine/fina/masters/coa.js +0 -104
  50. package/engine/fina/masters/currency.js +0 -39
  51. package/engine/fina/masters/department.js +0 -32
  52. package/engine/fina/masters/index.js +0 -21
  53. package/engine/fina/masters/project.js +0 -53
  54. package/engine/fina/period.js +0 -56
  55. package/engine/fina/print.js +0 -136
  56. package/engine/fina/readPolicy.js +0 -67
  57. package/engine/fina/recurring.js +0 -200
  58. package/engine/fina/route.js +0 -61
  59. package/engine/fina/routes/attachment.js +0 -41
  60. package/engine/fina/routes/document.js +0 -66
  61. package/engine/fina/routes/master.js +0 -26
  62. package/engine/fina/routes/period.js +0 -24
  63. package/engine/fina/routes/print.js +0 -19
  64. package/engine/fina/routes/recurring.js +0 -27
  65. package/engine/fina/routes/session.js +0 -23
  66. package/engine/fina/routes/setup.js +0 -58
  67. package/engine/fina/routes/submission.js +0 -46
  68. package/engine/fina/routes/template.js +0 -31
  69. package/engine/fina/settings.js +0 -159
  70. package/engine/fina/sql.js +0 -34
  71. package/engine/fina/submission.js +0 -290
  72. package/engine/fina/submissionCompare.js +0 -171
  73. package/engine/fina/submissionRepo.js +0 -114
  74. package/engine/fina/template.js +0 -118
  75. package/engine/fina/userMap.js +0 -79
  76. package/engine/orm/companyLocale.js +0 -74
  77. package/engine/orm/userApiDataset.js +0 -153
  78. package/migrations/postgres/1790600000000__fina.sql +0 -33
  79. package/migrations/postgres/1790700000000__attachment.sql +0 -29
  80. package/migrations/postgres/1790800000000__fina_jv_submission.sql +0 -41
@@ -1,27 +0,0 @@
1
- /*
2
- * Doc 8 M4 — `fina/recurring/*`: recurring detail lines, shared with FINA desktop (T-33–T-38, D8).
3
- *
4
- * `list { document }` and `get { document, id }`: any mapped FINA user (T-36). `save { document, name, rows }`: the
5
- * create right; the name is new (T-34). `remove { document, id }`: the edit right (D4). There is no edit (T-37).
6
- *
7
- * ⚠️ Registered as an engine target: export routes only. `remove`, because `delete` is a reserved word.
8
- */
9
- import { runFinaRoute } from "../route.js";
10
- import { createIdentity } from "../identity.js";
11
- import { createRecurringService } from "../recurring.js";
12
-
13
- const service = (deps) => createRecurringService({ client: deps.client, identity: createIdentity(deps) });
14
-
15
- export const list = (payload, _argv, context) =>
16
- runFinaRoute({ name: "recurring/list", context }, ({ deps, principal }) => service(deps).list(principal, payload?.document));
17
-
18
- export const get = (payload, _argv, context) =>
19
- runFinaRoute({ name: "recurring/get", context }, ({ deps, principal }) => service(deps).get(principal, payload?.document, payload?.id));
20
-
21
- export const save = (payload, _argv, context) =>
22
- runFinaRoute({ name: "recurring/save", context }, ({ deps, principal }) =>
23
- service(deps).save(principal, payload?.document, { name: payload?.name, rows: payload?.rows }),
24
- );
25
-
26
- export const remove = (payload, _argv, context) =>
27
- runFinaRoute({ name: "recurring/remove", context }, ({ deps, principal }) => service(deps).remove(principal, payload?.document, payload?.id));
@@ -1,23 +0,0 @@
1
- /*
2
- * Doc 8 — `fina/session/*`: for any signed-in user of a FINA Cloud company.
3
- *
4
- * `me` says who the caller is in FINA and what they may do (S-02), so screens can hide what is not allowed (S-04),
5
- * plus the date lock, the FINA features and the module settings (M4: the JV's default template, rate editable,
6
- * mandatory dimensions) the screens depend on. The server enforces all of it again on every
7
- * write; this answer is for the UI only.
8
- *
9
- * ⚠️ Registered as an engine target: export routes only.
10
- */
11
- import { runFinaRoute } from "../route.js";
12
- import { createIdentity } from "../identity.js";
13
- import { createSettingsStore } from "../settings.js";
14
- import { createFeatures } from "../features.js";
15
-
16
- export const me = (_payload, _argv, context) =>
17
- runFinaRoute({ name: "session/me", context }, async ({ deps, principal, config }) => {
18
- const identity = await createIdentity(deps).resolve(principal);
19
- const settings = await createSettingsStore(deps).read();
20
- const features = await createFeatures({ client: deps.client }).read();
21
- /* Doc 8 M3: dbIndex = the FINA database the master lists read (the operator flag), so no config hard-codes it. */
22
- return { ...identity, lockUpTo: settings.lock.upTo, features, dbIndex: config?.dbIndex ?? null, settings: { jv: settings.jv } };
23
- });
@@ -1,58 +0,0 @@
1
- /*
2
- * Doc 8 M1 — `fina/setup/*`: the server side of the FINA Cloud setup console (spec §3). ADMINISTRATORS only.
3
- *
4
- * ⚠️ Registered as an engine target (bin/hubEvent.js EngineRegistry), so EVERY export here is callable from a
5
- * browser. Export routes only; the work lives in the modules beside this folder.
6
- */
7
- import { runFinaRoute } from "../route.js";
8
- import { createFeatures } from "../features.js";
9
- import { createSettingsStore } from "../settings.js";
10
- import { createUserMap } from "../userMap.js";
11
- import { listTemplates, TEMPLATE_TYPE } from "../template.js";
12
- import { publishApproval } from "../approval.js";
13
-
14
- const admin = (name, context, work) => runFinaRoute({ name: `setup/${name}`, context, admin: true }, work);
15
-
16
- /* Everything the console shows on load (C-04, C-05, §3.4, C-14). */
17
- export const state = (_payload, _argv, context) =>
18
- admin("state", context, async ({ deps, config }) => ({
19
- finaDbIndex: config.dbIndex,
20
- features: await createFeatures({ client: deps.client }).read(),
21
- settings: await createSettingsStore(deps).read(),
22
- templates: await listTemplates(deps.client, TEMPLATE_TYPE.JV),
23
- }));
24
-
25
- /* C-05: Multi Department / Multi Project, written to FINA itself. C-12 refuses switching one off while used. */
26
- export const saveFeatures = (payload, _argv, context) =>
27
- admin("saveFeatures", context, async ({ deps }) =>
28
- createFeatures({ client: deps.client }).save({ multiDept: payload?.multiDept, multiProject: payload?.multiProject }),
29
- );
30
-
31
- /* §3.4 / C-14: `{ jv?: {...}, lock?: {...} }`, only the keys being changed. Answers every section as stored. */
32
- export const saveSettings = (payload, _argv, context) =>
33
- admin("saveSettings", context, async ({ deps, principal }) => createSettingsStore(deps).write(payload, principal.userId));
34
-
35
- /* S-01: the Biz-A users, the active FINA users and the current map. */
36
- export const users = (_payload, _argv, context) => admin("users", context, async ({ deps }) => createUserMap(deps).list());
37
-
38
- /* S-01: `{ rows: [{ bizaUserId, finaUserId | null }] }`. */
39
- export const saveUserMap = (payload, _argv, context) =>
40
- admin("saveUserMap", context, async ({ deps, principal }) => createUserMap(deps).save(payload, principal.userId));
41
-
42
- /*
43
- * Doc 8 M7 (A-02, A-04): the console Publish with Need Approval on — the JV approval workflow and its two effect
44
- * scripts, on this hub's database. A definition someone changed in the BPM module is kept (A-03); the same one is not
45
- * published again.
46
- */
47
- export const approval = (_payload, _argv, context) =>
48
- admin("approval", context, async ({ deps, principal, config }) => {
49
- const workflow = deps.workflow ?? (await import("../../bpm/workflow.js"));
50
- const apiConfig = { dbIndex: config.tenantDbIndex };
51
- return publishApproval({
52
- exec: deps.exec,
53
- execStatements: deps.execStatements,
54
- save: (p) => workflow.save({ ...p, apiConfig }),
55
- publish: (p) => workflow.publish({ ...p, apiConfig }),
56
- userId: principal.userId,
57
- });
58
- });
@@ -1,46 +0,0 @@
1
- /*
2
- * Doc 8 M7 — JV submissions (pengajuan), with Need Approval on (submission.js).
3
- *
4
- * - `fina/submission/get` `{ id }`: its JV (master, lines), number, status, the FINA JV it became, why posting failed,
5
- * and — while it waits — what would stop its posting now (A-18). Any mapped FINA user.
6
- * - `fina/submission/save` `{ id|null, master, lines, requestId }`: a new one (the create right) gets its number and
7
- * its approval workflow; a waiting one is edited (the edit right, A-17). The JV rules, the Biz-A lock and FINA's
8
- * period are checked as for a JV. Answers `{ id, number }`.
9
- * - `fina/submission/remove` `{ id, requestId }`: a waiting one only (M-20) — its workflow ended, its files and the row
10
- * gone.
11
- * - `fina/submission/compare` `{ id }` (Doc 8 M7b, L-02 / L-09 / A-16): the submission next to the JV it became in
12
- * FINA — both sides with FINA's display values, `jvState` POSTED / NOT_FOUND / NOT_POSTED, and the differences.
13
- * Any mapped FINA user.
14
- *
15
- * Setujui / Tolak are the workflow's transitions (bpm/workflowRT, the approver role); their effects post or close it.
16
- *
17
- * ⚠️ Registered as an engine target: export routes only.
18
- */
19
- import { runFinaRoute } from "../route.js";
20
- import { submissionServiceOf } from "../approvalService.js";
21
- import { compareSubmission } from "../submissionCompare.js";
22
- import { createIdentity } from "../identity.js";
23
- import { createSubmissionRepo } from "../submissionRepo.js";
24
-
25
- export const get = (payload, _argv, context) =>
26
- runFinaRoute({ name: "submission/get", context }, ({ deps, principal, config }) => submissionServiceOf(deps, config).get(principal, payload?.id));
27
-
28
- export const save = (payload, _argv, context) =>
29
- runFinaRoute({ name: "submission/save", context }, ({ deps, principal, config }) =>
30
- submissionServiceOf(deps, config).save(
31
- principal,
32
- { id: payload?.id, master: payload?.master, lines: payload?.lines, requestId: payload?.requestId },
33
- { dbIndex: config?.dbIndex },
34
- ),
35
- );
36
-
37
- /* `remove` because `delete` is a reserved word (as for documents). */
38
- export const remove = (payload, _argv, context) =>
39
- runFinaRoute({ name: "submission/remove", context }, ({ deps, principal, config }) =>
40
- submissionServiceOf(deps, config).remove(principal, { id: payload?.id, requestId: payload?.requestId }, { dbIndex: config?.dbIndex }),
41
- );
42
-
43
- export const compare = (payload, _argv, context) =>
44
- runFinaRoute({ name: "submission/compare", context }, ({ deps, principal }) =>
45
- compareSubmission({ client: deps.client, identity: createIdentity(deps), repo: createSubmissionRepo({ exec: deps.exec }) }, principal, payload?.id),
46
- );
@@ -1,31 +0,0 @@
1
- /*
2
- * Doc 8 M4 — `fina/template/get`: what a document's form is laid out from (T-01–T-16, T-22, T-23).
3
- *
4
- * Payload `{ document, templateId? }`. Answers the type's templates (for the selector), the chosen one (asked → the
5
- * console's default → FINA's default → the first), its elements, and the extended fields mapped onto the document's
6
- * tables. Any mapped FINA user (D4: view).
7
- *
8
- * ⚠️ Registered as an engine target: export routes only.
9
- */
10
- import { runFinaRoute } from "../route.js";
11
- import { createIdentity, requireRight } from "../identity.js";
12
- import { createSettingsStore } from "../settings.js";
13
- import { documentOf } from "../documents/index.js";
14
- import { listTemplates, chooseTemplateId, assertTemplateOfType, readTemplateElements, readMappings } from "../template.js";
15
-
16
- export const get = (payload, _argv, context) =>
17
- runFinaRoute({ name: "template/get", context }, async ({ deps, principal }) => {
18
- const document = documentOf(payload?.document);
19
- requireRight(await createIdentity(deps).resolve(principal), document.rights, null);
20
- const templates = await listTemplates(deps.client, document.templateType);
21
- const asked = payload?.templateId;
22
- const requested = asked === null || asked === undefined || asked === "" ? null : assertTemplateOfType(templates, asked);
23
- const settings = await createSettingsStore(deps).read();
24
- const templateId = chooseTemplateId({ templates, requested, settingDefault: settings?.[document.name]?.defaultTemplateId });
25
- return {
26
- templates,
27
- templateId,
28
- elements: await readTemplateElements(deps.client, templateId),
29
- mappings: await readMappings(deps.client, document.extended),
30
- };
31
- });
@@ -1,159 +0,0 @@
1
- /*
2
- * Doc 8 — the FINA Cloud settings the SERVER enforces, in engine/fina's own table FINA$SETTING (one JSON value per
3
- * section). The setup console writes them through `fina/setup/saveSettings`; nothing else may.
4
- *
5
- * - `lock`: the Biz-A date lock (C-14, D2a).
6
- * - `jv`: Need Approval (A-01), the default template (§3.4, T-22), rate editable (F-18), department / project
7
- * mandatory (F-09, D5), attachment limits (F-35). Biz-A only, never written to FINA (C-13).
8
- *
9
- * ⚠️ CLOSED SCHEMA. An unknown section or key is refused, never stored: a misspelt setting that was silently kept
10
- * would leave the implementor believing a rule was on.
11
- */
12
- import { finaError } from "./errors.js";
13
- import { toDateOnly } from "./lock.js";
14
- import { logger } from "../../logger.js";
15
-
16
- export const SETTING_DEFAULTS = Object.freeze({
17
- lock: Object.freeze({ upTo: null }),
18
- jv: Object.freeze({
19
- needApproval: false,
20
- /* null = FINA's own default template (TEMPLATE.ISDEFTEMPLATE). */
21
- defaultTemplateId: null,
22
- /* FINA desktop always lets the rate be edited; Biz-A matches it until the implementor says otherwise. */
23
- rateEditable: true,
24
- deptMandatory: false,
25
- projectMandatory: false,
26
- attachments: Object.freeze({ maxFiles: 5, maxFileMb: 5, extensions: Object.freeze(["pdf", "jpg", "jpeg", "png"]) }),
27
- }),
28
- });
29
-
30
- const isPlain = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
31
- const clone = (v) => JSON.parse(JSON.stringify(v));
32
-
33
- /* One level of nesting is all the schema has (jv.attachments). */
34
- const lay = (base, over) => {
35
- const out = clone(base);
36
- if (!isPlain(over)) return out;
37
- for (const [key, value] of Object.entries(over)) {
38
- out[key] = isPlain(out[key]) && isPlain(value) ? { ...out[key], ...value } : value;
39
- }
40
- return out;
41
- };
42
-
43
- const EXTENSION = /^[a-z0-9]{1,10}$/;
44
-
45
- /* Each check answers null when the value is fine, or the reason. `normalise` turns an accepted value into what is
46
- stored. */
47
- const SCHEMA = {
48
- lock: {
49
- upTo: {
50
- normalise: (v) => (v === null || v === "" ? null : toDateOnly(v)),
51
- check: (v) => (v === null || v === "" || (typeof v === "string" && toDateOnly(v) === v) ? null : "must be a date (YYYY-MM-DD) or empty"),
52
- },
53
- },
54
- jv: {
55
- needApproval: { check: (v) => (typeof v === "boolean" ? null : "must be true or false") },
56
- defaultTemplateId: {
57
- normalise: (v) => (v === null || v === "" ? null : Number(v)),
58
- check: (v) => (v === null || v === "" || (Number.isInteger(Number(v)) && Number(v) > 0 && !/\D/.test(String(v))) ? null : "must be a positive whole number or empty"),
59
- },
60
- rateEditable: { check: (v) => (typeof v === "boolean" ? null : "must be true or false") },
61
- deptMandatory: { check: (v) => (typeof v === "boolean" ? null : "must be true or false") },
62
- projectMandatory: { check: (v) => (typeof v === "boolean" ? null : "must be true or false") },
63
- attachments: {
64
- nested: {
65
- maxFiles: { check: (v) => (Number.isInteger(v) && v >= 0 && v <= 100 ? null : "must be a whole number from 0 to 100") },
66
- maxFileMb: { check: (v) => (typeof v === "number" && Number.isFinite(v) && v > 0 && v <= 100 ? null : "must be a number above 0 and at most 100") },
67
- extensions: {
68
- normalise: (v) => [...new Set(v.map((e) => String(e).trim().toLowerCase().replace(/^\./, "")))],
69
- check: (v) =>
70
- Array.isArray(v) && v.length <= 30 && v.every((e) => EXTENSION.test(String(e).trim().toLowerCase().replace(/^\./, "")))
71
- ? null
72
- : "must be a list of up to 30 file extensions (letters and digits, at most 10 each)",
73
- },
74
- },
75
- },
76
- },
77
- };
78
-
79
- /*
80
- * Validate a PATCH (only the sections and keys being changed) against the current settings. Answers
81
- * `{ ok: true, value: { <section>: <the whole merged section> } }` for the sections the patch names, or
82
- * `{ ok: false, problems: [...] }` listing every reason at once.
83
- */
84
- export const validateSettings = (patch, current = SETTING_DEFAULTS) => {
85
- const problems = [];
86
- const value = {};
87
- if (!isPlain(patch)) return { ok: false, problems: ["the settings must be an object of sections"] };
88
-
89
- const walk = (schema, input, prefix, out) => {
90
- if (!isPlain(input)) {
91
- problems.push(`${prefix} must be an object`);
92
- return;
93
- }
94
- for (const [key, raw] of Object.entries(input)) {
95
- const rule = schema[key];
96
- const name = `${prefix}.${key}`;
97
- if (!rule) {
98
- problems.push(`${name} is not a setting`);
99
- continue;
100
- }
101
- if (rule.nested) {
102
- out[key] = isPlain(out[key]) ? out[key] : {};
103
- walk(rule.nested, raw, name, out[key]);
104
- continue;
105
- }
106
- const reason = rule.check(raw);
107
- if (reason) problems.push(`${name} ${reason}`);
108
- else out[key] = rule.normalise ? rule.normalise(raw) : raw;
109
- }
110
- };
111
-
112
- for (const [section, input] of Object.entries(patch)) {
113
- if (!SCHEMA[section]) {
114
- problems.push(`unknown setting section "${section}"`);
115
- continue;
116
- }
117
- const merged = lay(current?.[section] ?? SETTING_DEFAULTS[section], {});
118
- walk(SCHEMA[section], input, section, merged);
119
- value[section] = merged;
120
- }
121
- return problems.length > 0 ? { ok: false, problems } : { ok: true, value };
122
- };
123
-
124
- const quote = (s) => `'${String(s).replace(/'/g, "''")}'`;
125
- const actorSql = (actorId) => (Number.isInteger(actorId) ? String(actorId) : /^\d+$/.test(String(actorId ?? "")) ? String(Number(actorId)) : "NULL");
126
-
127
- export const createSettingsStore = ({ exec, execStatements }) => {
128
- const read = async () => {
129
- const rows = (await exec("SELECT KEY, VALUE FROM FINA$SETTING")) ?? [];
130
- const stored = {};
131
- for (const row of rows) {
132
- const key = String(row?.key ?? "");
133
- if (!SETTING_DEFAULTS[key]) continue;
134
- try {
135
- stored[key] = JSON.parse(String(row.value));
136
- } catch {
137
- /* Never fails the read: the defaults stand for that section, and the log says why. */
138
- logger.warn(`[fina] FINA$SETTING "${key}" is not JSON; its defaults are used until it is saved again.`);
139
- }
140
- }
141
- return Object.fromEntries(Object.keys(SETTING_DEFAULTS).map((key) => [key, lay(SETTING_DEFAULTS[key], stored[key])]));
142
- };
143
-
144
- const write = async (patch, actorId) => {
145
- const current = await read();
146
- const checked = validateSettings(patch, current);
147
- if (!checked.ok) throw finaError("INVALID", checked.problems.join("; "));
148
- const statements = Object.entries(checked.value).map(
149
- ([key, value]) =>
150
- `INSERT INTO FINA$SETTING (KEY, VALUE, UPDATED_BY, UPDATED_AT) VALUES (${quote(key)}, ${quote(JSON.stringify(value))}, ${actorSql(actorId)}, now()) ` +
151
- "ON CONFLICT (KEY) DO UPDATE SET VALUE = EXCLUDED.VALUE, UPDATED_BY = EXCLUDED.UPDATED_BY, UPDATED_AT = now()",
152
- );
153
- if (statements.length > 0) await execStatements(statements);
154
- /* What is now stored: the sections written, the others as they were read. */
155
- return { ...current, ...checked.value };
156
- };
157
-
158
- return { read, write };
159
- };
@@ -1,34 +0,0 @@
1
- /*
2
- * Doc 8 M3 — Firebird literals for the statements engine/fina builds (client.executeSql / client.block).
3
- *
4
- * The FINA API takes SQL TEXT: there is no parameter binding on this path. Every value that reaches a statement goes
5
- * through here — a string quoted with its quotes doubled, a number as a finite number — so no value can carry SQL.
6
- * ⚠️ Single-line on purpose: a raw newline ends the command on the FINA transport (client.js).
7
- */
8
- import { finaError } from "./errors.js";
9
-
10
- export const sqlString = (value) => `'${String(value ?? "").replace(/[\r\n]+/g, " ").replace(/'/g, "''")}'`;
11
-
12
- export const sqlNumber = (value) => {
13
- const n = Number(value);
14
- if (value === null || value === undefined || value === "" || !Number.isFinite(n)) {
15
- throw finaError("INVALID", `${JSON.stringify(value)} is not a number.`);
16
- }
17
- return String(n);
18
- };
19
-
20
- export const sqlInteger = (value) => {
21
- const n = Number(value);
22
- if (value === null || value === undefined || value === "" || !Number.isInteger(n)) {
23
- throw finaError("INVALID", `${JSON.stringify(value)} is not a whole number.`);
24
- }
25
- return String(n);
26
- };
27
-
28
- /* A value of a column of `type` ('string' or a number type), or NULL. */
29
- export const sqlValue = (value, type) => {
30
- if (value === null || value === undefined || value === "") return "NULL";
31
- if (type === "string" || type === "date") return sqlString(value);
32
- if (type === "integer" || type === "flag") return sqlInteger(value);
33
- return sqlNumber(value);
34
- };