@bsm-form/core 0.39.0 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +348 -121
- package/dist/index.d.cts +25 -3
- package/dist/index.d.ts +25 -3
- package/dist/index.js +341 -121
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,3 +1,208 @@
|
|
|
1
|
+
// src/utils/form-env.ts
|
|
2
|
+
function validateEnvBaseUrl(value) {
|
|
3
|
+
if (!value || /[\\\s?#]/.test(value) || value.startsWith("//")) throw new Error("Base URL cannot contain whitespace, query, fragment, or backslashes");
|
|
4
|
+
if (value.startsWith("/")) return;
|
|
5
|
+
let url;
|
|
6
|
+
try {
|
|
7
|
+
url = new URL(value);
|
|
8
|
+
} catch {
|
|
9
|
+
throw new Error("Expected an http(s) URL or path starting with /");
|
|
10
|
+
}
|
|
11
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) throw new Error("Base URL must use http(s) without credentials");
|
|
12
|
+
}
|
|
13
|
+
function validateValue(name, type, value) {
|
|
14
|
+
if (typeof value !== (type === "url" ? "string" : type) || typeof value === "number" && !Number.isFinite(value)) throw new Error(`env.${name}: expected ${String(type)}`);
|
|
15
|
+
if (type === "url") {
|
|
16
|
+
try {
|
|
17
|
+
validateEnvBaseUrl(value);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
throw new Error(`env.${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function validateEnvDefinitions(value) {
|
|
24
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Environment definitions must be an object");
|
|
25
|
+
for (const [name, entry] of Object.entries(value)) {
|
|
26
|
+
if (!/^[A-Za-z_]+$/.test(name) || ["__proto__", "prototype", "constructor"].includes(name)) throw new Error(`Invalid environment variable name: ${name}`);
|
|
27
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) throw new Error(`env.${name}: expected a definition`);
|
|
28
|
+
const definition = entry;
|
|
29
|
+
if (!["string", "number", "boolean", "url"].includes(String(definition.type))) throw new Error(`env.${name}: invalid type`);
|
|
30
|
+
for (const key of Object.keys(definition)) if (!["type", "label", "description", "default", "required", "allowOverride"].includes(key)) throw new Error(`env.${name}: unknown property ${key}`);
|
|
31
|
+
for (const key of ["required", "allowOverride"]) if (definition[key] !== void 0 && typeof definition[key] !== "boolean") throw new Error(`env.${name}.${key}: expected boolean`);
|
|
32
|
+
for (const key of ["label", "description"]) if (definition[key] !== void 0 && typeof definition[key] !== "string") throw new Error(`env.${name}.${key}: expected string`);
|
|
33
|
+
if (Object.hasOwn(definition, "default")) validateValue(name, definition.type, definition.default);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function resolveFormEnv(config = {}, requireValues = true) {
|
|
37
|
+
const definitions = config.definitions ?? {};
|
|
38
|
+
const overrides = config.overrides ?? {};
|
|
39
|
+
validateEnvDefinitions(definitions);
|
|
40
|
+
if (typeof overrides !== "object" || Array.isArray(overrides)) throw new Error("Environment overrides must be an object");
|
|
41
|
+
for (const name of Object.keys(overrides)) {
|
|
42
|
+
if (!Object.hasOwn(definitions, name)) throw new Error(`env.${name}: unknown override`);
|
|
43
|
+
if (definitions[name]?.allowOverride === false) throw new Error(`env.${name}: overrides are disabled`);
|
|
44
|
+
}
|
|
45
|
+
const values = {};
|
|
46
|
+
for (const [name, definition] of Object.entries(definitions)) {
|
|
47
|
+
const provided = Object.hasOwn(overrides, name);
|
|
48
|
+
const value = provided ? overrides[name] : definition.default;
|
|
49
|
+
if (provided || value !== void 0) validateValue(name, definition.type, value);
|
|
50
|
+
if (requireValues && definition.required && (value === void 0 || value === "")) throw new Error(`env.${name}: required value is missing`);
|
|
51
|
+
if (value !== void 0) values[name] = value;
|
|
52
|
+
}
|
|
53
|
+
return Object.freeze(values);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/utils/get-path.ts
|
|
57
|
+
function pathSegments(path) {
|
|
58
|
+
const segments = [];
|
|
59
|
+
let i = 0;
|
|
60
|
+
while (i < path.length) {
|
|
61
|
+
const char = path[i];
|
|
62
|
+
if (char === ".") {
|
|
63
|
+
i += 1;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (char === "[") {
|
|
67
|
+
const close = path.indexOf("]", i);
|
|
68
|
+
if (close === -1) {
|
|
69
|
+
segments.push(path.slice(i));
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
segments.push(path.slice(i + 1, close));
|
|
73
|
+
i = close + 1;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
let end = i;
|
|
77
|
+
while (end < path.length && path[end] !== "." && path[end] !== "[") {
|
|
78
|
+
end += 1;
|
|
79
|
+
}
|
|
80
|
+
if (end > i) {
|
|
81
|
+
segments.push(path.slice(i, end));
|
|
82
|
+
}
|
|
83
|
+
i = end;
|
|
84
|
+
}
|
|
85
|
+
return segments;
|
|
86
|
+
}
|
|
87
|
+
function getPath(obj, path) {
|
|
88
|
+
if (!path) {
|
|
89
|
+
return obj;
|
|
90
|
+
}
|
|
91
|
+
let current = obj;
|
|
92
|
+
for (const key of pathSegments(path)) {
|
|
93
|
+
if (typeof current !== "object" || current === null) {
|
|
94
|
+
return void 0;
|
|
95
|
+
}
|
|
96
|
+
current = current[key];
|
|
97
|
+
}
|
|
98
|
+
return current;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/utils/scope-path.ts
|
|
102
|
+
function resolveScopedPath(ctx, path) {
|
|
103
|
+
if (path === "env") return ctx.env ?? {};
|
|
104
|
+
if (path.startsWith("env.")) return getPath(ctx.env ?? {}, path.slice(4));
|
|
105
|
+
if (path === "$values" || path === "values") {
|
|
106
|
+
return ctx.values;
|
|
107
|
+
}
|
|
108
|
+
if (path === "$resources" || path === "resources") {
|
|
109
|
+
return ctx.resources;
|
|
110
|
+
}
|
|
111
|
+
if (path === "$row" || path === "row") {
|
|
112
|
+
return ctx.row;
|
|
113
|
+
}
|
|
114
|
+
if (path === "$item" || path === "item") {
|
|
115
|
+
return ctx.item;
|
|
116
|
+
}
|
|
117
|
+
if (path.startsWith("values.")) {
|
|
118
|
+
return getPath(ctx.values, path.replace("values.", ""));
|
|
119
|
+
}
|
|
120
|
+
if (path.startsWith("resources.")) {
|
|
121
|
+
return getPath(ctx.resources, path.replace("resources.", ""));
|
|
122
|
+
}
|
|
123
|
+
if (path.startsWith("row.")) {
|
|
124
|
+
return ctx.row === void 0 ? void 0 : getPath(ctx.row, path.replace("row.", ""));
|
|
125
|
+
}
|
|
126
|
+
if (path.startsWith("item.")) {
|
|
127
|
+
return ctx.item === void 0 ? void 0 : getPath(ctx.item, path.replace("item.", ""));
|
|
128
|
+
}
|
|
129
|
+
return getPath(ctx.values, path);
|
|
130
|
+
}
|
|
131
|
+
function isExplicitScopedPath(path) {
|
|
132
|
+
return path === "env" || path.startsWith("env.") || path === "$values" || path === "values" || path === "$resources" || path === "resources" || path === "$row" || path === "row" || path === "$item" || path === "item" || path.startsWith("values.") || path.startsWith("resources.") || path.startsWith("row.") || path.startsWith("item.");
|
|
133
|
+
}
|
|
134
|
+
function resolvePathStringsInData(value, ctx) {
|
|
135
|
+
if (typeof value === "string") {
|
|
136
|
+
return isExplicitScopedPath(value) ? resolveScopedPath(ctx, value) : value;
|
|
137
|
+
}
|
|
138
|
+
if (Array.isArray(value)) {
|
|
139
|
+
return value.map((item) => resolvePathStringsInData(item, ctx));
|
|
140
|
+
}
|
|
141
|
+
if (typeof value === "object" && value !== null) {
|
|
142
|
+
const result = {};
|
|
143
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
144
|
+
result[key] = resolvePathStringsInData(entry, ctx);
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
return value;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/utils/url-template.ts
|
|
152
|
+
var pathPattern = /^(values|resources|env)\.[A-Za-z_$][\w$]*(?:(?:\.(?:[A-Za-z_$][\w$]*|\d+))|(?:\[\d+\]))*$/;
|
|
153
|
+
function placeholders(url) {
|
|
154
|
+
const result = [];
|
|
155
|
+
let cursor = 0;
|
|
156
|
+
while (true) {
|
|
157
|
+
const start = url.indexOf("${", cursor);
|
|
158
|
+
if (start < 0) break;
|
|
159
|
+
const close = url.indexOf("}", start + 2);
|
|
160
|
+
if (close < 0) throw new Error("API URL template has an unclosed placeholder");
|
|
161
|
+
const path = url.slice(start + 2, close).trim();
|
|
162
|
+
if (!pathPattern.test(path)) {
|
|
163
|
+
throw new Error(`Invalid API URL placeholder: ${path}. Use a values.*, resources.*, or env.* data path`);
|
|
164
|
+
}
|
|
165
|
+
const origin = url.match(/^(?:[A-Za-z][A-Za-z0-9+.-]*:)?\/\/[^/?#]*/)?.[0];
|
|
166
|
+
if (start === 0 && !path.startsWith("env.") || origin && start < origin.length) {
|
|
167
|
+
throw new Error("API URL placeholders cannot replace the service origin; use a static base URL");
|
|
168
|
+
}
|
|
169
|
+
result.push({ start, end: close + 1, path });
|
|
170
|
+
cursor = close + 1;
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
function validateApiUrlTemplate(url) {
|
|
175
|
+
if (!url.trim()) throw new Error("API URL must not be empty");
|
|
176
|
+
placeholders(url);
|
|
177
|
+
}
|
|
178
|
+
function resolveApiUrlTemplate(url, context, definitions = {}) {
|
|
179
|
+
validateApiUrlTemplate(url);
|
|
180
|
+
let result = "";
|
|
181
|
+
let cursor = 0;
|
|
182
|
+
for (const placeholder of placeholders(url)) {
|
|
183
|
+
const value = resolveScopedPath(context, placeholder.path);
|
|
184
|
+
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean" || typeof value === "number" && !Number.isFinite(value) || value === "") {
|
|
185
|
+
throw new Error(`API URL placeholder ${placeholder.path} must resolve to a non-empty string, finite number, or boolean`);
|
|
186
|
+
}
|
|
187
|
+
if (value === "." || value === "..") {
|
|
188
|
+
throw new Error(`API URL placeholder ${placeholder.path} cannot be a dot segment`);
|
|
189
|
+
}
|
|
190
|
+
if (placeholder.start === 0 && placeholder.path.startsWith("env.")) {
|
|
191
|
+
if (definitions[placeholder.path.slice(4)]?.type !== "url" || typeof value !== "string") {
|
|
192
|
+
throw new Error(`${placeholder.path}: only an env variable of type url can supply a base URL`);
|
|
193
|
+
}
|
|
194
|
+
validateEnvBaseUrl(value);
|
|
195
|
+
const suffix = url.slice(placeholder.end);
|
|
196
|
+
if (suffix && !suffix.startsWith("/")) throw new Error("Base URL placeholder must be followed by / or end of URL");
|
|
197
|
+
result += suffix.startsWith("/") ? value.replace(/\/+$/, "") : value;
|
|
198
|
+
} else {
|
|
199
|
+
result += url.slice(cursor, placeholder.start) + encodeURIComponent(String(value));
|
|
200
|
+
}
|
|
201
|
+
cursor = placeholder.end;
|
|
202
|
+
}
|
|
203
|
+
return result + url.slice(cursor);
|
|
204
|
+
}
|
|
205
|
+
|
|
1
206
|
// src/compiler/compile-schema.ts
|
|
2
207
|
import {
|
|
3
208
|
fieldAllowsEvent,
|
|
@@ -1308,7 +1513,25 @@ function collectDrawerSteps(steps, path, context, owningDrawerId) {
|
|
|
1308
1513
|
});
|
|
1309
1514
|
});
|
|
1310
1515
|
}
|
|
1516
|
+
function validateApiUrlsInSteps(steps, path) {
|
|
1517
|
+
steps.forEach((step, index) => {
|
|
1518
|
+
const stepPath = `${path}[${index}]`;
|
|
1519
|
+
if (step.type === "when") {
|
|
1520
|
+
validateApiUrlsInSteps(step.then, `${stepPath}.then`);
|
|
1521
|
+
if (step.else) validateApiUrlsInSteps(step.else, `${stepPath}.else`);
|
|
1522
|
+
}
|
|
1523
|
+
if (step.type === "api") {
|
|
1524
|
+
try {
|
|
1525
|
+
validateApiUrlTemplate(step.request.url);
|
|
1526
|
+
} catch (error) {
|
|
1527
|
+
throw new Error(`Invalid API URL at ${stepPath}.request.url: ${error instanceof Error ? error.message : String(error)}`);
|
|
1528
|
+
}
|
|
1529
|
+
if (step.onError) validateApiUrlsInSteps(step.onError, `${stepPath}.onError`);
|
|
1530
|
+
}
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1311
1533
|
function collectDialogSteps(steps, path, context, owningDialogId) {
|
|
1534
|
+
validateApiUrlsInSteps(steps, path);
|
|
1312
1535
|
steps.forEach((step, index) => {
|
|
1313
1536
|
if (step.type !== "dialog") {
|
|
1314
1537
|
return;
|
|
@@ -1406,6 +1629,7 @@ function validateDrawerStepReferences(context) {
|
|
|
1406
1629
|
}
|
|
1407
1630
|
function validateStepConditionReferences(context) {
|
|
1408
1631
|
context.stepConditionReferences.forEach(({ path, field }) => {
|
|
1632
|
+
if (field.startsWith("env.")) return;
|
|
1409
1633
|
if (isReservedConditionField(field)) {
|
|
1410
1634
|
if (isPendingActionConditionField(field)) {
|
|
1411
1635
|
const actionId = pendingActionIdFromField(field);
|
|
@@ -1566,6 +1790,7 @@ var validationRegistry = {
|
|
|
1566
1790
|
// src/engine/environment.ts
|
|
1567
1791
|
function createFormEngineEnvironment(overrides = {}) {
|
|
1568
1792
|
return {
|
|
1793
|
+
env: overrides.env ?? Object.freeze({}),
|
|
1569
1794
|
request: overrides.request ?? defaultRequest,
|
|
1570
1795
|
log: overrides.log ?? defaultLog,
|
|
1571
1796
|
navigate: overrides.navigate ?? defaultNavigate,
|
|
@@ -1635,6 +1860,7 @@ function runInvokeValidator(rule, options) {
|
|
|
1635
1860
|
const ctx = {
|
|
1636
1861
|
values: options?.values ?? {},
|
|
1637
1862
|
resources: options?.resources ?? {},
|
|
1863
|
+
env: options?.env ?? options?.environment?.env,
|
|
1638
1864
|
...options?.row !== void 0 ? { row: options.row } : {},
|
|
1639
1865
|
...options?.item !== void 0 ? { item: options.item } : {}
|
|
1640
1866
|
};
|
|
@@ -1729,99 +1955,6 @@ function isResourcePathInFlight(sourceRelative, inFlight) {
|
|
|
1729
1955
|
);
|
|
1730
1956
|
}
|
|
1731
1957
|
|
|
1732
|
-
// src/utils/get-path.ts
|
|
1733
|
-
function pathSegments(path) {
|
|
1734
|
-
const segments = [];
|
|
1735
|
-
let i = 0;
|
|
1736
|
-
while (i < path.length) {
|
|
1737
|
-
const char = path[i];
|
|
1738
|
-
if (char === ".") {
|
|
1739
|
-
i += 1;
|
|
1740
|
-
continue;
|
|
1741
|
-
}
|
|
1742
|
-
if (char === "[") {
|
|
1743
|
-
const close = path.indexOf("]", i);
|
|
1744
|
-
if (close === -1) {
|
|
1745
|
-
segments.push(path.slice(i));
|
|
1746
|
-
break;
|
|
1747
|
-
}
|
|
1748
|
-
segments.push(path.slice(i + 1, close));
|
|
1749
|
-
i = close + 1;
|
|
1750
|
-
continue;
|
|
1751
|
-
}
|
|
1752
|
-
let end = i;
|
|
1753
|
-
while (end < path.length && path[end] !== "." && path[end] !== "[") {
|
|
1754
|
-
end += 1;
|
|
1755
|
-
}
|
|
1756
|
-
if (end > i) {
|
|
1757
|
-
segments.push(path.slice(i, end));
|
|
1758
|
-
}
|
|
1759
|
-
i = end;
|
|
1760
|
-
}
|
|
1761
|
-
return segments;
|
|
1762
|
-
}
|
|
1763
|
-
function getPath(obj, path) {
|
|
1764
|
-
if (!path) {
|
|
1765
|
-
return obj;
|
|
1766
|
-
}
|
|
1767
|
-
let current = obj;
|
|
1768
|
-
for (const key of pathSegments(path)) {
|
|
1769
|
-
if (typeof current !== "object" || current === null) {
|
|
1770
|
-
return void 0;
|
|
1771
|
-
}
|
|
1772
|
-
current = current[key];
|
|
1773
|
-
}
|
|
1774
|
-
return current;
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
|
-
// src/utils/scope-path.ts
|
|
1778
|
-
function resolveScopedPath(ctx, path) {
|
|
1779
|
-
if (path === "$values" || path === "values") {
|
|
1780
|
-
return ctx.values;
|
|
1781
|
-
}
|
|
1782
|
-
if (path === "$resources" || path === "resources") {
|
|
1783
|
-
return ctx.resources;
|
|
1784
|
-
}
|
|
1785
|
-
if (path === "$row" || path === "row") {
|
|
1786
|
-
return ctx.row;
|
|
1787
|
-
}
|
|
1788
|
-
if (path === "$item" || path === "item") {
|
|
1789
|
-
return ctx.item;
|
|
1790
|
-
}
|
|
1791
|
-
if (path.startsWith("values.")) {
|
|
1792
|
-
return getPath(ctx.values, path.replace("values.", ""));
|
|
1793
|
-
}
|
|
1794
|
-
if (path.startsWith("resources.")) {
|
|
1795
|
-
return getPath(ctx.resources, path.replace("resources.", ""));
|
|
1796
|
-
}
|
|
1797
|
-
if (path.startsWith("row.")) {
|
|
1798
|
-
return ctx.row === void 0 ? void 0 : getPath(ctx.row, path.replace("row.", ""));
|
|
1799
|
-
}
|
|
1800
|
-
if (path.startsWith("item.")) {
|
|
1801
|
-
return ctx.item === void 0 ? void 0 : getPath(ctx.item, path.replace("item.", ""));
|
|
1802
|
-
}
|
|
1803
|
-
return getPath(ctx.values, path);
|
|
1804
|
-
}
|
|
1805
|
-
function isExplicitScopedPath(path) {
|
|
1806
|
-
return path === "$values" || path === "values" || path === "$resources" || path === "resources" || path === "$row" || path === "row" || path === "$item" || path === "item" || path.startsWith("values.") || path.startsWith("resources.") || path.startsWith("row.") || path.startsWith("item.");
|
|
1807
|
-
}
|
|
1808
|
-
function resolvePathStringsInData(value, ctx) {
|
|
1809
|
-
if (typeof value === "string") {
|
|
1810
|
-
return isExplicitScopedPath(value) ? resolveScopedPath(ctx, value) : value;
|
|
1811
|
-
}
|
|
1812
|
-
if (Array.isArray(value)) {
|
|
1813
|
-
return value.map((item) => resolvePathStringsInData(item, ctx));
|
|
1814
|
-
}
|
|
1815
|
-
if (typeof value === "object" && value !== null) {
|
|
1816
|
-
const result = {};
|
|
1817
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
1818
|
-
result[key] = resolvePathStringsInData(entry, ctx);
|
|
1819
|
-
}
|
|
1820
|
-
return result;
|
|
1821
|
-
}
|
|
1822
|
-
return value;
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
1958
|
// src/conditions/evaluate.ts
|
|
1826
1959
|
function evaluateCondition(condition, values, state, options) {
|
|
1827
1960
|
if (isInvokeCondition(condition)) {
|
|
@@ -1832,6 +1965,7 @@ function evaluateCondition(condition, values, state, options) {
|
|
|
1832
1965
|
}
|
|
1833
1966
|
const target = resolveConditionTarget(condition.field, values, state, {
|
|
1834
1967
|
resources: options?.resources,
|
|
1968
|
+
env: options?.env ?? options?.environment?.env,
|
|
1835
1969
|
...options?.row !== void 0 ? { row: options.row } : {},
|
|
1836
1970
|
...options?.item !== void 0 ? { item: options.item } : {}
|
|
1837
1971
|
});
|
|
@@ -1878,6 +2012,7 @@ function evaluateInvokeCondition(name, values, options) {
|
|
|
1878
2012
|
const ctx = {
|
|
1879
2013
|
values,
|
|
1880
2014
|
resources: options?.resources ?? {},
|
|
2015
|
+
env: options?.env ?? options?.environment?.env,
|
|
1881
2016
|
...options?.row !== void 0 ? { row: options.row } : {},
|
|
1882
2017
|
...options?.item !== void 0 ? { item: options.item } : {}
|
|
1883
2018
|
};
|
|
@@ -1932,6 +2067,7 @@ function resolveConditionTarget(field, values, state, options) {
|
|
|
1932
2067
|
{
|
|
1933
2068
|
values,
|
|
1934
2069
|
resources: options?.resources ?? {},
|
|
2070
|
+
env: options?.env,
|
|
1935
2071
|
...options?.row !== void 0 ? { row: options.row } : {},
|
|
1936
2072
|
...options?.item !== void 0 ? { item: options.item } : {}
|
|
1937
2073
|
},
|
|
@@ -2231,7 +2367,7 @@ var DependencyGraph = class {
|
|
|
2231
2367
|
return ordered;
|
|
2232
2368
|
}
|
|
2233
2369
|
extractDependencies(conditions2) {
|
|
2234
|
-
return conditions2.filter(isFieldCondition2).map((condition) => condition.field).filter((field) => !isReservedConditionField3(field));
|
|
2370
|
+
return conditions2.filter(isFieldCondition2).map((condition) => condition.field).filter((field) => !isReservedConditionField3(field) && !field.startsWith("env."));
|
|
2235
2371
|
}
|
|
2236
2372
|
clear() {
|
|
2237
2373
|
this.forward.clear();
|
|
@@ -2253,10 +2389,52 @@ var EventBus = class {
|
|
|
2253
2389
|
}
|
|
2254
2390
|
};
|
|
2255
2391
|
|
|
2392
|
+
// src/utils/env-references.ts
|
|
2393
|
+
var pathKeys = /* @__PURE__ */ new Set(["from", "source", "field", "path", "bodyFrom", "queryFrom"]);
|
|
2394
|
+
function visit(value, path, key, data, transform) {
|
|
2395
|
+
if (typeof value === "string") {
|
|
2396
|
+
if (key === "url") return transform(value, path, true);
|
|
2397
|
+
if ((pathKeys.has(key) || path.endsWith(".bind.value") || data) && /^env\.[A-Za-z_]/.test(value)) return transform(value, path, false);
|
|
2398
|
+
return value;
|
|
2399
|
+
}
|
|
2400
|
+
if (Array.isArray(value)) return value.map((item, index) => visit(item, `${path}[${index}]`, key, data, transform));
|
|
2401
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([childKey, child]) => [
|
|
2402
|
+
childKey,
|
|
2403
|
+
visit(child, `${path}.${childKey}`, childKey, data || childKey === "body" || childKey === "query", transform)
|
|
2404
|
+
]));
|
|
2405
|
+
return value;
|
|
2406
|
+
}
|
|
2407
|
+
function collectFormEnvReferences(schema) {
|
|
2408
|
+
const references = [];
|
|
2409
|
+
visit(schema, "schema", "", false, (value, path, template) => {
|
|
2410
|
+
if (template) {
|
|
2411
|
+
for (const match of value.matchAll(/\$\{\s*env\.([A-Za-z_][A-Za-z0-9_]*)\s*\}/g)) {
|
|
2412
|
+
references.push({ name: match[1], path });
|
|
2413
|
+
}
|
|
2414
|
+
} else references.push({ name: value.slice(4).split(/[.[]/)[0], path });
|
|
2415
|
+
return value;
|
|
2416
|
+
});
|
|
2417
|
+
return references;
|
|
2418
|
+
}
|
|
2419
|
+
function validateFormEnvReferences(schema, definitions = {}) {
|
|
2420
|
+
for (const reference of collectFormEnvReferences(schema)) {
|
|
2421
|
+
if (!Object.hasOwn(definitions, reference.name)) throw new Error(`${reference.path}: undefined environment variable env.${reference.name}`);
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
function remapFormEnvReferences(schema, previous, next) {
|
|
2425
|
+
return visit(schema, "schema", "", false, (value, _path, template) => {
|
|
2426
|
+
if (template) return value.replace(
|
|
2427
|
+
/\$\{\s*env\.([A-Za-z_][A-Za-z0-9_]*)\s*\}/g,
|
|
2428
|
+
(original, name) => name === previous ? "${env." + next + "}" : original
|
|
2429
|
+
);
|
|
2430
|
+
return value === `env.${previous}` ? `env.${next}` : value;
|
|
2431
|
+
});
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2256
2434
|
// src/pipeline/run-conditions.ts
|
|
2257
2435
|
function runConditions(ctx) {
|
|
2258
|
-
|
|
2259
|
-
function
|
|
2436
|
+
visit2(ctx.tree, true);
|
|
2437
|
+
function visit2(node, parentVisible) {
|
|
2260
2438
|
const schema = node.schema;
|
|
2261
2439
|
const hasVisibilityConditions = schema.kind === "field" || schema.kind === "display" || schema.kind === "action" || schema.kind === "layout" && schema.type === "step";
|
|
2262
2440
|
if (!parentVisible) {
|
|
@@ -2274,7 +2452,7 @@ function runConditions(ctx) {
|
|
|
2274
2452
|
}
|
|
2275
2453
|
node.state.disabled = evaluateDisabled(schema, ctx);
|
|
2276
2454
|
const childrenVisible = node.state.visible && !(schema.kind === "layout" && (schema.type === "dialog" || schema.type === "drawer") && node.state.open !== true);
|
|
2277
|
-
node.children.forEach((child) =>
|
|
2455
|
+
node.children.forEach((child) => visit2(child, childrenVisible));
|
|
2278
2456
|
}
|
|
2279
2457
|
}
|
|
2280
2458
|
function evaluateDisabled(schema, ctx) {
|
|
@@ -2393,6 +2571,7 @@ function executeInvokeComputer(name, values, options) {
|
|
|
2393
2571
|
const ctx = {
|
|
2394
2572
|
values,
|
|
2395
2573
|
resources: options?.resources ?? {},
|
|
2574
|
+
env: options?.env ?? options?.environment?.env,
|
|
2396
2575
|
...options?.row !== void 0 ? { row: options.row } : {},
|
|
2397
2576
|
...options?.item !== void 0 ? { item: options.item } : {}
|
|
2398
2577
|
};
|
|
@@ -2741,6 +2920,7 @@ var StepRunner = class {
|
|
|
2741
2920
|
(condition) => evaluateCondition(condition, values, conditionState, {
|
|
2742
2921
|
environment: this.environment,
|
|
2743
2922
|
resources: this.engine.getResources(),
|
|
2923
|
+
env: this.engine.getEnv(),
|
|
2744
2924
|
...options?.row !== void 0 ? { row: options.row } : {},
|
|
2745
2925
|
...options?.item !== void 0 ? { item: options.item } : {}
|
|
2746
2926
|
})
|
|
@@ -2825,6 +3005,7 @@ var StepRunner = class {
|
|
|
2825
3005
|
return {
|
|
2826
3006
|
values: this.engine.getValues(),
|
|
2827
3007
|
resources: this.engine.getResources(),
|
|
3008
|
+
env: this.engine.getEnv(),
|
|
2828
3009
|
...options?.row !== void 0 ? { row: options.row } : {},
|
|
2829
3010
|
...options?.item !== void 0 ? { item: options.item } : {}
|
|
2830
3011
|
};
|
|
@@ -2833,6 +3014,7 @@ var StepRunner = class {
|
|
|
2833
3014
|
return {
|
|
2834
3015
|
values: this.engine.getValues(),
|
|
2835
3016
|
resources: this.engine.getResources(),
|
|
3017
|
+
env: this.engine.getEnv(),
|
|
2836
3018
|
...options?.row !== void 0 ? { row: options.row } : {},
|
|
2837
3019
|
...options?.item !== void 0 ? { item: options.item } : {}
|
|
2838
3020
|
};
|
|
@@ -2842,6 +3024,7 @@ function resolveApiRequestBody(step, engine, options) {
|
|
|
2842
3024
|
const ctx = {
|
|
2843
3025
|
values: engine.getValues(),
|
|
2844
3026
|
resources: engine.getResources(),
|
|
3027
|
+
env: engine.getEnv(),
|
|
2845
3028
|
...options?.row !== void 0 ? { row: options.row } : {},
|
|
2846
3029
|
...options?.item !== void 0 ? { item: options.item } : {}
|
|
2847
3030
|
};
|
|
@@ -2861,9 +3044,11 @@ function resolveApiRequestUrl(step, engine, options) {
|
|
|
2861
3044
|
const ctx = {
|
|
2862
3045
|
values: engine.getValues(),
|
|
2863
3046
|
resources: engine.getResources(),
|
|
3047
|
+
env: engine.getEnv(),
|
|
2864
3048
|
...options?.row !== void 0 ? { row: options.row } : {},
|
|
2865
3049
|
...options?.item !== void 0 ? { item: options.item } : {}
|
|
2866
3050
|
};
|
|
3051
|
+
const url = resolveApiUrlTemplate(step.request.url, ctx, engine.getEnvDefinitions());
|
|
2867
3052
|
const hasQueryFrom = "queryFrom" in step.request && step.request.queryFrom !== void 0;
|
|
2868
3053
|
const hasQuery = "query" in step.request && step.request.query !== void 0;
|
|
2869
3054
|
if (hasQueryFrom && hasQuery) {
|
|
@@ -2879,7 +3064,7 @@ function resolveApiRequestUrl(step, engine, options) {
|
|
|
2879
3064
|
} else if (hasQuery) {
|
|
2880
3065
|
queryValue = resolvePathStringsInData(step.request.query, ctx);
|
|
2881
3066
|
} else {
|
|
2882
|
-
return
|
|
3067
|
+
return url;
|
|
2883
3068
|
}
|
|
2884
3069
|
if (queryValue === void 0 || queryValue === null || typeof queryValue !== "object" || Array.isArray(queryValue)) {
|
|
2885
3070
|
throw new TypeError("api step query must resolve to an object");
|
|
@@ -2893,10 +3078,13 @@ function resolveApiRequestUrl(step, engine, options) {
|
|
|
2893
3078
|
}
|
|
2894
3079
|
const queryString = params.toString();
|
|
2895
3080
|
if (!queryString) {
|
|
2896
|
-
return
|
|
3081
|
+
return url;
|
|
2897
3082
|
}
|
|
2898
|
-
const
|
|
2899
|
-
|
|
3083
|
+
const hashIndex = url.indexOf("#");
|
|
3084
|
+
const base = hashIndex < 0 ? url : url.slice(0, hashIndex);
|
|
3085
|
+
const hash = hashIndex < 0 ? "" : url.slice(hashIndex);
|
|
3086
|
+
const separator = base.includes("?") ? "&" : "?";
|
|
3087
|
+
return `${base}${separator}${queryString}${hash}`;
|
|
2900
3088
|
}
|
|
2901
3089
|
function toRequestBody(value, headers) {
|
|
2902
3090
|
if (isBodyInit(value)) {
|
|
@@ -3153,11 +3341,11 @@ function collectTemplate(node, fields, nested) {
|
|
|
3153
3341
|
}
|
|
3154
3342
|
function collectValuesRepeatFieldIds(metas) {
|
|
3155
3343
|
const ids = /* @__PURE__ */ new Set();
|
|
3156
|
-
const
|
|
3344
|
+
const visit2 = (meta) => {
|
|
3157
3345
|
meta.fieldTemplates.forEach((node) => ids.add(node.id));
|
|
3158
|
-
meta.nested.forEach(
|
|
3346
|
+
meta.nested.forEach(visit2);
|
|
3159
3347
|
};
|
|
3160
|
-
metas.forEach(
|
|
3348
|
+
metas.forEach(visit2);
|
|
3161
3349
|
return ids;
|
|
3162
3350
|
}
|
|
3163
3351
|
function createDefaultRepeatItem(meta) {
|
|
@@ -3245,9 +3433,11 @@ function findValuesRepeatMeta(metas, repeatId) {
|
|
|
3245
3433
|
|
|
3246
3434
|
// src/engine/form-engine.ts
|
|
3247
3435
|
var FormEngine = class {
|
|
3248
|
-
constructor(runtimeTree, environment = {}) {
|
|
3436
|
+
constructor(runtimeTree, environment = {}, envConfig = {}) {
|
|
3249
3437
|
this.runtimeTree = runtimeTree;
|
|
3250
|
-
this.
|
|
3438
|
+
this.envConfig = structuredClone(envConfig);
|
|
3439
|
+
validateFormEnvReferences(this.runtimeTree.schema, this.envConfig.definitions);
|
|
3440
|
+
this.environment = createFormEngineEnvironment({ ...environment, env: resolveFormEnv(this.envConfig, false) });
|
|
3251
3441
|
this.stepRunner = new StepRunner(this, this.environment);
|
|
3252
3442
|
this.buildNodeIndexes();
|
|
3253
3443
|
this.buildDependencyGraph();
|
|
@@ -3273,6 +3463,13 @@ var FormEngine = class {
|
|
|
3273
3463
|
graph = new DependencyGraph();
|
|
3274
3464
|
stepRunner;
|
|
3275
3465
|
environment;
|
|
3466
|
+
envConfig;
|
|
3467
|
+
getEnvDefinitions() {
|
|
3468
|
+
return structuredClone(this.envConfig.definitions ?? {});
|
|
3469
|
+
}
|
|
3470
|
+
getEnv() {
|
|
3471
|
+
return this.environment.env ?? Object.freeze({});
|
|
3472
|
+
}
|
|
3276
3473
|
fieldNodes = /* @__PURE__ */ new Map();
|
|
3277
3474
|
actionNodes = /* @__PURE__ */ new Map();
|
|
3278
3475
|
displayNodes = /* @__PURE__ */ new Map();
|
|
@@ -3342,6 +3539,7 @@ var FormEngine = class {
|
|
|
3342
3539
|
this.notifyStateUpdated();
|
|
3343
3540
|
return this.batch(async () => {
|
|
3344
3541
|
try {
|
|
3542
|
+
this.environment.env = resolveFormEnv(this.envConfig, !this.skipLoadStepsOnInitialize);
|
|
3345
3543
|
this.hydrateValues(this.runtimeTree);
|
|
3346
3544
|
if (!this.skipLoadStepsOnInitialize) {
|
|
3347
3545
|
await this.runLoadSteps();
|
|
@@ -3789,7 +3987,8 @@ var FormEngine = class {
|
|
|
3789
3987
|
getPath(
|
|
3790
3988
|
{
|
|
3791
3989
|
values: this.values,
|
|
3792
|
-
resources: this.resources
|
|
3990
|
+
resources: this.resources,
|
|
3991
|
+
env: this.getEnv()
|
|
3793
3992
|
},
|
|
3794
3993
|
path
|
|
3795
3994
|
)
|
|
@@ -3880,7 +4079,8 @@ var FormEngine = class {
|
|
|
3880
4079
|
this.reevaluateConditions();
|
|
3881
4080
|
const result = runSubmitPipeline(this.getSnapshot(), {
|
|
3882
4081
|
environment: this.environment,
|
|
3883
|
-
resources: this.resources
|
|
4082
|
+
resources: this.resources,
|
|
4083
|
+
env: this.getEnv()
|
|
3884
4084
|
});
|
|
3885
4085
|
const itemErrors = {};
|
|
3886
4086
|
this.itemFieldState.forEach((state, name) => {
|
|
@@ -4271,7 +4471,8 @@ var FormEngine = class {
|
|
|
4271
4471
|
value: changedField === "__bulk__" ? void 0 : nextValues[changedField],
|
|
4272
4472
|
state: this.getConditionState(),
|
|
4273
4473
|
environment: this.environment,
|
|
4274
|
-
resources: this.resources
|
|
4474
|
+
resources: this.resources,
|
|
4475
|
+
env: this.getEnv()
|
|
4275
4476
|
});
|
|
4276
4477
|
this.validateValuesRepeatFields(
|
|
4277
4478
|
changedFields.filter((name) => name.includes("."))
|
|
@@ -4297,7 +4498,8 @@ var FormEngine = class {
|
|
|
4297
4498
|
value: void 0,
|
|
4298
4499
|
state: this.getConditionState(),
|
|
4299
4500
|
environment: this.environment,
|
|
4300
|
-
resources: this.resources
|
|
4501
|
+
resources: this.resources,
|
|
4502
|
+
env: this.getEnv()
|
|
4301
4503
|
};
|
|
4302
4504
|
}
|
|
4303
4505
|
getConditionState() {
|
|
@@ -4317,7 +4519,8 @@ var FormEngine = class {
|
|
|
4317
4519
|
value: void 0,
|
|
4318
4520
|
state: this.getConditionState(),
|
|
4319
4521
|
environment: this.environment,
|
|
4320
|
-
resources: this.resources
|
|
4522
|
+
resources: this.resources,
|
|
4523
|
+
env: this.getEnv()
|
|
4321
4524
|
});
|
|
4322
4525
|
}
|
|
4323
4526
|
syncPendingState() {
|
|
@@ -4519,7 +4722,8 @@ var FormEngine = class {
|
|
|
4519
4722
|
{
|
|
4520
4723
|
environment: this.environment,
|
|
4521
4724
|
values: this.values,
|
|
4522
|
-
resources: this.resources
|
|
4725
|
+
resources: this.resources,
|
|
4726
|
+
env: this.getEnv()
|
|
4523
4727
|
}
|
|
4524
4728
|
);
|
|
4525
4729
|
node.state.error = result.error;
|
|
@@ -4576,7 +4780,8 @@ var FormEngine = class {
|
|
|
4576
4780
|
const source = resolveScopedPath(
|
|
4577
4781
|
{
|
|
4578
4782
|
values: this.values,
|
|
4579
|
-
resources: this.resources
|
|
4783
|
+
resources: this.resources,
|
|
4784
|
+
env: this.getEnv()
|
|
4580
4785
|
},
|
|
4581
4786
|
options.source
|
|
4582
4787
|
);
|
|
@@ -4603,6 +4808,7 @@ var FormEngine = class {
|
|
|
4603
4808
|
{
|
|
4604
4809
|
values: this.values,
|
|
4605
4810
|
resources: this.resources,
|
|
4811
|
+
env: this.getEnv(),
|
|
4606
4812
|
...options?.item !== void 0 ? { item: options.item } : {}
|
|
4607
4813
|
},
|
|
4608
4814
|
data.source
|
|
@@ -4628,6 +4834,7 @@ var FormEngine = class {
|
|
|
4628
4834
|
executeComputed(schema.computed, this.values, {
|
|
4629
4835
|
environment: this.environment,
|
|
4630
4836
|
resources: this.resources,
|
|
4837
|
+
env: this.getEnv(),
|
|
4631
4838
|
...options?.item !== void 0 ? { item: options.item } : {}
|
|
4632
4839
|
})
|
|
4633
4840
|
);
|
|
@@ -4639,6 +4846,7 @@ var FormEngine = class {
|
|
|
4639
4846
|
{
|
|
4640
4847
|
values: this.values,
|
|
4641
4848
|
resources: this.resources,
|
|
4849
|
+
env: this.getEnv(),
|
|
4642
4850
|
...options?.item !== void 0 ? { item: options.item } : {}
|
|
4643
4851
|
},
|
|
4644
4852
|
props.from
|
|
@@ -4662,6 +4870,7 @@ var FormEngine = class {
|
|
|
4662
4870
|
{
|
|
4663
4871
|
values: this.values,
|
|
4664
4872
|
resources: this.resources,
|
|
4873
|
+
env: this.getEnv(),
|
|
4665
4874
|
...options?.item !== void 0 ? { item: options.item } : {}
|
|
4666
4875
|
},
|
|
4667
4876
|
props.from
|
|
@@ -4693,6 +4902,7 @@ var FormEngine = class {
|
|
|
4693
4902
|
{
|
|
4694
4903
|
values: this.values,
|
|
4695
4904
|
resources: this.resources,
|
|
4905
|
+
env: this.getEnv(),
|
|
4696
4906
|
...options?.item !== void 0 ? { item: options.item } : {}
|
|
4697
4907
|
},
|
|
4698
4908
|
props.titleFrom
|
|
@@ -4732,7 +4942,8 @@ var FormEngine = class {
|
|
|
4732
4942
|
const source = resolveScopedPath(
|
|
4733
4943
|
{
|
|
4734
4944
|
values: this.values,
|
|
4735
|
-
resources: this.resources
|
|
4945
|
+
resources: this.resources,
|
|
4946
|
+
env: this.getEnv()
|
|
4736
4947
|
},
|
|
4737
4948
|
props.source
|
|
4738
4949
|
);
|
|
@@ -4921,6 +5132,7 @@ var FormEngine = class {
|
|
|
4921
5132
|
{
|
|
4922
5133
|
values: this.values,
|
|
4923
5134
|
resources: this.resources,
|
|
5135
|
+
env: this.getEnv(),
|
|
4924
5136
|
...options.item !== void 0 ? { item: options.item } : {}
|
|
4925
5137
|
},
|
|
4926
5138
|
options.from.trim()
|
|
@@ -4939,7 +5151,7 @@ var FormEngine = class {
|
|
|
4939
5151
|
}
|
|
4940
5152
|
validateValuesRepeatFields(only) {
|
|
4941
5153
|
const targets = only ? new Set(only) : void 0;
|
|
4942
|
-
const
|
|
5154
|
+
const visit2 = (meta, absolutePath) => {
|
|
4943
5155
|
const items = readRepeatArray(this.values, absolutePath);
|
|
4944
5156
|
items.forEach((item, index) => {
|
|
4945
5157
|
meta.fieldTemplates.forEach((template, fieldName) => {
|
|
@@ -4957,20 +5169,21 @@ var FormEngine = class {
|
|
|
4957
5169
|
const result = validateField(template.schema, value, {
|
|
4958
5170
|
environment: this.environment,
|
|
4959
5171
|
values: this.values,
|
|
4960
|
-
resources: this.resources
|
|
5172
|
+
resources: this.resources,
|
|
5173
|
+
env: this.getEnv()
|
|
4961
5174
|
});
|
|
4962
5175
|
const state = this.ensureItemFieldState(scopedName);
|
|
4963
5176
|
state.error = result.error;
|
|
4964
5177
|
});
|
|
4965
5178
|
meta.nested.forEach((nested) => {
|
|
4966
|
-
|
|
5179
|
+
visit2(nested, `${absolutePath}.${index}.${nested.path}`);
|
|
4967
5180
|
});
|
|
4968
5181
|
});
|
|
4969
5182
|
};
|
|
4970
|
-
this.valuesRepeatMetas.forEach((meta) =>
|
|
5183
|
+
this.valuesRepeatMetas.forEach((meta) => visit2(meta, meta.path));
|
|
4971
5184
|
}
|
|
4972
5185
|
touchAllValuesRepeatFields() {
|
|
4973
|
-
const
|
|
5186
|
+
const visit2 = (meta, absolutePath) => {
|
|
4974
5187
|
const items = readRepeatArray(this.values, absolutePath);
|
|
4975
5188
|
items.forEach((_, index) => {
|
|
4976
5189
|
meta.fieldTemplates.forEach((_2, fieldName) => {
|
|
@@ -4978,11 +5191,11 @@ var FormEngine = class {
|
|
|
4978
5191
|
this.ensureItemFieldState(scopedName).touched = true;
|
|
4979
5192
|
});
|
|
4980
5193
|
meta.nested.forEach((nested) => {
|
|
4981
|
-
|
|
5194
|
+
visit2(nested, `${absolutePath}.${index}.${nested.path}`);
|
|
4982
5195
|
});
|
|
4983
5196
|
});
|
|
4984
5197
|
};
|
|
4985
|
-
this.valuesRepeatMetas.forEach((meta) =>
|
|
5198
|
+
this.valuesRepeatMetas.forEach((meta) => visit2(meta, meta.path));
|
|
4986
5199
|
}
|
|
4987
5200
|
pruneItemFieldState(absolutePath, removedIndex, previousLength) {
|
|
4988
5201
|
const next = /* @__PURE__ */ new Map();
|
|
@@ -5178,18 +5391,25 @@ export {
|
|
|
5178
5391
|
adaptDisplay,
|
|
5179
5392
|
adaptField,
|
|
5180
5393
|
adaptLayout,
|
|
5394
|
+
collectFormEnvReferences,
|
|
5181
5395
|
compileSchema,
|
|
5182
5396
|
conditions,
|
|
5183
5397
|
createFormEngineEnvironment,
|
|
5184
5398
|
createRuntimeNode,
|
|
5185
5399
|
formatTagSourceValue,
|
|
5186
5400
|
isThenable,
|
|
5401
|
+
remapFormEnvReferences,
|
|
5402
|
+
resolveFormEnv,
|
|
5187
5403
|
resolveLoadingProp,
|
|
5188
5404
|
resolveTagValueMapping,
|
|
5189
5405
|
runConditionEngine,
|
|
5190
5406
|
tagMapKey,
|
|
5191
5407
|
traverseNode,
|
|
5408
|
+
validateApiUrlTemplate,
|
|
5409
|
+
validateEnvBaseUrl,
|
|
5410
|
+
validateEnvDefinitions,
|
|
5192
5411
|
validateField,
|
|
5412
|
+
validateFormEnvReferences,
|
|
5193
5413
|
validateSchema,
|
|
5194
5414
|
validation,
|
|
5195
5415
|
validationRegistry
|