@bsm-form/core 0.40.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 CHANGED
@@ -27,25 +27,86 @@ __export(index_exports, {
27
27
  adaptDisplay: () => adaptDisplay,
28
28
  adaptField: () => adaptField,
29
29
  adaptLayout: () => adaptLayout,
30
+ collectFormEnvReferences: () => collectFormEnvReferences,
30
31
  compileSchema: () => compileSchema,
31
32
  conditions: () => conditions,
32
33
  createFormEngineEnvironment: () => createFormEngineEnvironment,
33
34
  createRuntimeNode: () => createRuntimeNode,
34
35
  formatTagSourceValue: () => formatTagSourceValue,
35
36
  isThenable: () => isThenable,
37
+ remapFormEnvReferences: () => remapFormEnvReferences,
38
+ resolveFormEnv: () => resolveFormEnv,
36
39
  resolveLoadingProp: () => resolveLoadingProp,
37
40
  resolveTagValueMapping: () => resolveTagValueMapping,
38
41
  runConditionEngine: () => runConditionEngine,
39
42
  tagMapKey: () => tagMapKey,
40
43
  traverseNode: () => traverseNode,
41
44
  validateApiUrlTemplate: () => validateApiUrlTemplate,
45
+ validateEnvBaseUrl: () => validateEnvBaseUrl,
46
+ validateEnvDefinitions: () => validateEnvDefinitions,
42
47
  validateField: () => validateField,
48
+ validateFormEnvReferences: () => validateFormEnvReferences,
43
49
  validateSchema: () => validateSchema,
44
50
  validation: () => validation,
45
51
  validationRegistry: () => validationRegistry
46
52
  });
47
53
  module.exports = __toCommonJS(index_exports);
48
54
 
55
+ // src/utils/form-env.ts
56
+ function validateEnvBaseUrl(value) {
57
+ if (!value || /[\\\s?#]/.test(value) || value.startsWith("//")) throw new Error("Base URL cannot contain whitespace, query, fragment, or backslashes");
58
+ if (value.startsWith("/")) return;
59
+ let url;
60
+ try {
61
+ url = new URL(value);
62
+ } catch {
63
+ throw new Error("Expected an http(s) URL or path starting with /");
64
+ }
65
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) throw new Error("Base URL must use http(s) without credentials");
66
+ }
67
+ function validateValue(name, type, value) {
68
+ if (typeof value !== (type === "url" ? "string" : type) || typeof value === "number" && !Number.isFinite(value)) throw new Error(`env.${name}: expected ${String(type)}`);
69
+ if (type === "url") {
70
+ try {
71
+ validateEnvBaseUrl(value);
72
+ } catch (error) {
73
+ throw new Error(`env.${name}: ${error instanceof Error ? error.message : String(error)}`);
74
+ }
75
+ }
76
+ }
77
+ function validateEnvDefinitions(value) {
78
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Environment definitions must be an object");
79
+ for (const [name, entry] of Object.entries(value)) {
80
+ if (!/^[A-Za-z_]+$/.test(name) || ["__proto__", "prototype", "constructor"].includes(name)) throw new Error(`Invalid environment variable name: ${name}`);
81
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) throw new Error(`env.${name}: expected a definition`);
82
+ const definition = entry;
83
+ if (!["string", "number", "boolean", "url"].includes(String(definition.type))) throw new Error(`env.${name}: invalid type`);
84
+ for (const key of Object.keys(definition)) if (!["type", "label", "description", "default", "required", "allowOverride"].includes(key)) throw new Error(`env.${name}: unknown property ${key}`);
85
+ for (const key of ["required", "allowOverride"]) if (definition[key] !== void 0 && typeof definition[key] !== "boolean") throw new Error(`env.${name}.${key}: expected boolean`);
86
+ for (const key of ["label", "description"]) if (definition[key] !== void 0 && typeof definition[key] !== "string") throw new Error(`env.${name}.${key}: expected string`);
87
+ if (Object.hasOwn(definition, "default")) validateValue(name, definition.type, definition.default);
88
+ }
89
+ }
90
+ function resolveFormEnv(config = {}, requireValues = true) {
91
+ const definitions = config.definitions ?? {};
92
+ const overrides = config.overrides ?? {};
93
+ validateEnvDefinitions(definitions);
94
+ if (typeof overrides !== "object" || Array.isArray(overrides)) throw new Error("Environment overrides must be an object");
95
+ for (const name of Object.keys(overrides)) {
96
+ if (!Object.hasOwn(definitions, name)) throw new Error(`env.${name}: unknown override`);
97
+ if (definitions[name]?.allowOverride === false) throw new Error(`env.${name}: overrides are disabled`);
98
+ }
99
+ const values = {};
100
+ for (const [name, definition] of Object.entries(definitions)) {
101
+ const provided = Object.hasOwn(overrides, name);
102
+ const value = provided ? overrides[name] : definition.default;
103
+ if (provided || value !== void 0) validateValue(name, definition.type, value);
104
+ if (requireValues && definition.required && (value === void 0 || value === "")) throw new Error(`env.${name}: required value is missing`);
105
+ if (value !== void 0) values[name] = value;
106
+ }
107
+ return Object.freeze(values);
108
+ }
109
+
49
110
  // src/utils/get-path.ts
50
111
  function pathSegments(path) {
51
112
  const segments = [];
@@ -93,6 +154,8 @@ function getPath(obj, path) {
93
154
 
94
155
  // src/utils/scope-path.ts
95
156
  function resolveScopedPath(ctx, path) {
157
+ if (path === "env") return ctx.env ?? {};
158
+ if (path.startsWith("env.")) return getPath(ctx.env ?? {}, path.slice(4));
96
159
  if (path === "$values" || path === "values") {
97
160
  return ctx.values;
98
161
  }
@@ -120,7 +183,7 @@ function resolveScopedPath(ctx, path) {
120
183
  return getPath(ctx.values, path);
121
184
  }
122
185
  function isExplicitScopedPath(path) {
123
- 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.");
186
+ 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.");
124
187
  }
125
188
  function resolvePathStringsInData(value, ctx) {
126
189
  if (typeof value === "string") {
@@ -140,7 +203,7 @@ function resolvePathStringsInData(value, ctx) {
140
203
  }
141
204
 
142
205
  // src/utils/url-template.ts
143
- var pathPattern = /^(values|resources)\.[A-Za-z_$][\w$]*(?:(?:\.(?:[A-Za-z_$][\w$]*|\d+))|(?:\[\d+\]))*$/;
206
+ var pathPattern = /^(values|resources|env)\.[A-Za-z_$][\w$]*(?:(?:\.(?:[A-Za-z_$][\w$]*|\d+))|(?:\[\d+\]))*$/;
144
207
  function placeholders(url) {
145
208
  const result = [];
146
209
  let cursor = 0;
@@ -151,10 +214,10 @@ function placeholders(url) {
151
214
  if (close < 0) throw new Error("API URL template has an unclosed placeholder");
152
215
  const path = url.slice(start + 2, close).trim();
153
216
  if (!pathPattern.test(path)) {
154
- throw new Error(`Invalid API URL placeholder: ${path}. Use a values.* or resources.* data path`);
217
+ throw new Error(`Invalid API URL placeholder: ${path}. Use a values.*, resources.*, or env.* data path`);
155
218
  }
156
219
  const origin = url.match(/^(?:[A-Za-z][A-Za-z0-9+.-]*:)?\/\/[^/?#]*/)?.[0];
157
- if (start === 0 || origin && start < origin.length) {
220
+ if (start === 0 && !path.startsWith("env.") || origin && start < origin.length) {
158
221
  throw new Error("API URL placeholders cannot replace the service origin; use a static base URL");
159
222
  }
160
223
  result.push({ start, end: close + 1, path });
@@ -166,7 +229,7 @@ function validateApiUrlTemplate(url) {
166
229
  if (!url.trim()) throw new Error("API URL must not be empty");
167
230
  placeholders(url);
168
231
  }
169
- function resolveApiUrlTemplate(url, context) {
232
+ function resolveApiUrlTemplate(url, context, definitions = {}) {
170
233
  validateApiUrlTemplate(url);
171
234
  let result = "";
172
235
  let cursor = 0;
@@ -178,7 +241,17 @@ function resolveApiUrlTemplate(url, context) {
178
241
  if (value === "." || value === "..") {
179
242
  throw new Error(`API URL placeholder ${placeholder.path} cannot be a dot segment`);
180
243
  }
181
- result += url.slice(cursor, placeholder.start) + encodeURIComponent(String(value));
244
+ if (placeholder.start === 0 && placeholder.path.startsWith("env.")) {
245
+ if (definitions[placeholder.path.slice(4)]?.type !== "url" || typeof value !== "string") {
246
+ throw new Error(`${placeholder.path}: only an env variable of type url can supply a base URL`);
247
+ }
248
+ validateEnvBaseUrl(value);
249
+ const suffix = url.slice(placeholder.end);
250
+ if (suffix && !suffix.startsWith("/")) throw new Error("Base URL placeholder must be followed by / or end of URL");
251
+ result += suffix.startsWith("/") ? value.replace(/\/+$/, "") : value;
252
+ } else {
253
+ result += url.slice(cursor, placeholder.start) + encodeURIComponent(String(value));
254
+ }
182
255
  cursor = placeholder.end;
183
256
  }
184
257
  return result + url.slice(cursor);
@@ -1602,6 +1675,7 @@ function validateDrawerStepReferences(context) {
1602
1675
  }
1603
1676
  function validateStepConditionReferences(context) {
1604
1677
  context.stepConditionReferences.forEach(({ path, field }) => {
1678
+ if (field.startsWith("env.")) return;
1605
1679
  if ((0, import_schema.isReservedConditionField)(field)) {
1606
1680
  if ((0, import_schema.isPendingActionConditionField)(field)) {
1607
1681
  const actionId = (0, import_schema.pendingActionIdFromField)(field);
@@ -1762,6 +1836,7 @@ var validationRegistry = {
1762
1836
  // src/engine/environment.ts
1763
1837
  function createFormEngineEnvironment(overrides = {}) {
1764
1838
  return {
1839
+ env: overrides.env ?? Object.freeze({}),
1765
1840
  request: overrides.request ?? defaultRequest,
1766
1841
  log: overrides.log ?? defaultLog,
1767
1842
  navigate: overrides.navigate ?? defaultNavigate,
@@ -1831,6 +1906,7 @@ function runInvokeValidator(rule, options) {
1831
1906
  const ctx = {
1832
1907
  values: options?.values ?? {},
1833
1908
  resources: options?.resources ?? {},
1909
+ env: options?.env ?? options?.environment?.env,
1834
1910
  ...options?.row !== void 0 ? { row: options.row } : {},
1835
1911
  ...options?.item !== void 0 ? { item: options.item } : {}
1836
1912
  };
@@ -1926,6 +2002,7 @@ function evaluateCondition(condition, values, state, options) {
1926
2002
  }
1927
2003
  const target = resolveConditionTarget(condition.field, values, state, {
1928
2004
  resources: options?.resources,
2005
+ env: options?.env ?? options?.environment?.env,
1929
2006
  ...options?.row !== void 0 ? { row: options.row } : {},
1930
2007
  ...options?.item !== void 0 ? { item: options.item } : {}
1931
2008
  });
@@ -1972,6 +2049,7 @@ function evaluateInvokeCondition(name, values, options) {
1972
2049
  const ctx = {
1973
2050
  values,
1974
2051
  resources: options?.resources ?? {},
2052
+ env: options?.env ?? options?.environment?.env,
1975
2053
  ...options?.row !== void 0 ? { row: options.row } : {},
1976
2054
  ...options?.item !== void 0 ? { item: options.item } : {}
1977
2055
  };
@@ -2026,6 +2104,7 @@ function resolveConditionTarget(field, values, state, options) {
2026
2104
  {
2027
2105
  values,
2028
2106
  resources: options?.resources ?? {},
2107
+ env: options?.env,
2029
2108
  ...options?.row !== void 0 ? { row: options.row } : {},
2030
2109
  ...options?.item !== void 0 ? { item: options.item } : {}
2031
2110
  },
@@ -2319,7 +2398,7 @@ var DependencyGraph = class {
2319
2398
  return ordered;
2320
2399
  }
2321
2400
  extractDependencies(conditions2) {
2322
- return conditions2.filter(import_schema4.isFieldCondition).map((condition) => condition.field).filter((field) => !(0, import_schema4.isReservedConditionField)(field));
2401
+ return conditions2.filter(import_schema4.isFieldCondition).map((condition) => condition.field).filter((field) => !(0, import_schema4.isReservedConditionField)(field) && !field.startsWith("env."));
2323
2402
  }
2324
2403
  clear() {
2325
2404
  this.forward.clear();
@@ -2341,10 +2420,52 @@ var EventBus = class {
2341
2420
  }
2342
2421
  };
2343
2422
 
2423
+ // src/utils/env-references.ts
2424
+ var pathKeys = /* @__PURE__ */ new Set(["from", "source", "field", "path", "bodyFrom", "queryFrom"]);
2425
+ function visit(value, path, key, data, transform) {
2426
+ if (typeof value === "string") {
2427
+ if (key === "url") return transform(value, path, true);
2428
+ if ((pathKeys.has(key) || path.endsWith(".bind.value") || data) && /^env\.[A-Za-z_]/.test(value)) return transform(value, path, false);
2429
+ return value;
2430
+ }
2431
+ if (Array.isArray(value)) return value.map((item, index) => visit(item, `${path}[${index}]`, key, data, transform));
2432
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([childKey, child]) => [
2433
+ childKey,
2434
+ visit(child, `${path}.${childKey}`, childKey, data || childKey === "body" || childKey === "query", transform)
2435
+ ]));
2436
+ return value;
2437
+ }
2438
+ function collectFormEnvReferences(schema) {
2439
+ const references = [];
2440
+ visit(schema, "schema", "", false, (value, path, template) => {
2441
+ if (template) {
2442
+ for (const match of value.matchAll(/\$\{\s*env\.([A-Za-z_][A-Za-z0-9_]*)\s*\}/g)) {
2443
+ references.push({ name: match[1], path });
2444
+ }
2445
+ } else references.push({ name: value.slice(4).split(/[.[]/)[0], path });
2446
+ return value;
2447
+ });
2448
+ return references;
2449
+ }
2450
+ function validateFormEnvReferences(schema, definitions = {}) {
2451
+ for (const reference of collectFormEnvReferences(schema)) {
2452
+ if (!Object.hasOwn(definitions, reference.name)) throw new Error(`${reference.path}: undefined environment variable env.${reference.name}`);
2453
+ }
2454
+ }
2455
+ function remapFormEnvReferences(schema, previous, next) {
2456
+ return visit(schema, "schema", "", false, (value, _path, template) => {
2457
+ if (template) return value.replace(
2458
+ /\$\{\s*env\.([A-Za-z_][A-Za-z0-9_]*)\s*\}/g,
2459
+ (original, name) => name === previous ? "${env." + next + "}" : original
2460
+ );
2461
+ return value === `env.${previous}` ? `env.${next}` : value;
2462
+ });
2463
+ }
2464
+
2344
2465
  // src/pipeline/run-conditions.ts
2345
2466
  function runConditions(ctx) {
2346
- visit(ctx.tree, true);
2347
- function visit(node, parentVisible) {
2467
+ visit2(ctx.tree, true);
2468
+ function visit2(node, parentVisible) {
2348
2469
  const schema = node.schema;
2349
2470
  const hasVisibilityConditions = schema.kind === "field" || schema.kind === "display" || schema.kind === "action" || schema.kind === "layout" && schema.type === "step";
2350
2471
  if (!parentVisible) {
@@ -2362,7 +2483,7 @@ function runConditions(ctx) {
2362
2483
  }
2363
2484
  node.state.disabled = evaluateDisabled(schema, ctx);
2364
2485
  const childrenVisible = node.state.visible && !(schema.kind === "layout" && (schema.type === "dialog" || schema.type === "drawer") && node.state.open !== true);
2365
- node.children.forEach((child) => visit(child, childrenVisible));
2486
+ node.children.forEach((child) => visit2(child, childrenVisible));
2366
2487
  }
2367
2488
  }
2368
2489
  function evaluateDisabled(schema, ctx) {
@@ -2481,6 +2602,7 @@ function executeInvokeComputer(name, values, options) {
2481
2602
  const ctx = {
2482
2603
  values,
2483
2604
  resources: options?.resources ?? {},
2605
+ env: options?.env ?? options?.environment?.env,
2484
2606
  ...options?.row !== void 0 ? { row: options.row } : {},
2485
2607
  ...options?.item !== void 0 ? { item: options.item } : {}
2486
2608
  };
@@ -2829,6 +2951,7 @@ var StepRunner = class {
2829
2951
  (condition) => evaluateCondition(condition, values, conditionState, {
2830
2952
  environment: this.environment,
2831
2953
  resources: this.engine.getResources(),
2954
+ env: this.engine.getEnv(),
2832
2955
  ...options?.row !== void 0 ? { row: options.row } : {},
2833
2956
  ...options?.item !== void 0 ? { item: options.item } : {}
2834
2957
  })
@@ -2913,6 +3036,7 @@ var StepRunner = class {
2913
3036
  return {
2914
3037
  values: this.engine.getValues(),
2915
3038
  resources: this.engine.getResources(),
3039
+ env: this.engine.getEnv(),
2916
3040
  ...options?.row !== void 0 ? { row: options.row } : {},
2917
3041
  ...options?.item !== void 0 ? { item: options.item } : {}
2918
3042
  };
@@ -2921,6 +3045,7 @@ var StepRunner = class {
2921
3045
  return {
2922
3046
  values: this.engine.getValues(),
2923
3047
  resources: this.engine.getResources(),
3048
+ env: this.engine.getEnv(),
2924
3049
  ...options?.row !== void 0 ? { row: options.row } : {},
2925
3050
  ...options?.item !== void 0 ? { item: options.item } : {}
2926
3051
  };
@@ -2930,6 +3055,7 @@ function resolveApiRequestBody(step, engine, options) {
2930
3055
  const ctx = {
2931
3056
  values: engine.getValues(),
2932
3057
  resources: engine.getResources(),
3058
+ env: engine.getEnv(),
2933
3059
  ...options?.row !== void 0 ? { row: options.row } : {},
2934
3060
  ...options?.item !== void 0 ? { item: options.item } : {}
2935
3061
  };
@@ -2949,10 +3075,11 @@ function resolveApiRequestUrl(step, engine, options) {
2949
3075
  const ctx = {
2950
3076
  values: engine.getValues(),
2951
3077
  resources: engine.getResources(),
3078
+ env: engine.getEnv(),
2952
3079
  ...options?.row !== void 0 ? { row: options.row } : {},
2953
3080
  ...options?.item !== void 0 ? { item: options.item } : {}
2954
3081
  };
2955
- const url = resolveApiUrlTemplate(step.request.url, ctx);
3082
+ const url = resolveApiUrlTemplate(step.request.url, ctx, engine.getEnvDefinitions());
2956
3083
  const hasQueryFrom = "queryFrom" in step.request && step.request.queryFrom !== void 0;
2957
3084
  const hasQuery = "query" in step.request && step.request.query !== void 0;
2958
3085
  if (hasQueryFrom && hasQuery) {
@@ -3245,11 +3372,11 @@ function collectTemplate(node, fields, nested) {
3245
3372
  }
3246
3373
  function collectValuesRepeatFieldIds(metas) {
3247
3374
  const ids = /* @__PURE__ */ new Set();
3248
- const visit = (meta) => {
3375
+ const visit2 = (meta) => {
3249
3376
  meta.fieldTemplates.forEach((node) => ids.add(node.id));
3250
- meta.nested.forEach(visit);
3377
+ meta.nested.forEach(visit2);
3251
3378
  };
3252
- metas.forEach(visit);
3379
+ metas.forEach(visit2);
3253
3380
  return ids;
3254
3381
  }
3255
3382
  function createDefaultRepeatItem(meta) {
@@ -3337,9 +3464,11 @@ function findValuesRepeatMeta(metas, repeatId) {
3337
3464
 
3338
3465
  // src/engine/form-engine.ts
3339
3466
  var FormEngine = class {
3340
- constructor(runtimeTree, environment = {}) {
3467
+ constructor(runtimeTree, environment = {}, envConfig = {}) {
3341
3468
  this.runtimeTree = runtimeTree;
3342
- this.environment = createFormEngineEnvironment(environment);
3469
+ this.envConfig = structuredClone(envConfig);
3470
+ validateFormEnvReferences(this.runtimeTree.schema, this.envConfig.definitions);
3471
+ this.environment = createFormEngineEnvironment({ ...environment, env: resolveFormEnv(this.envConfig, false) });
3343
3472
  this.stepRunner = new StepRunner(this, this.environment);
3344
3473
  this.buildNodeIndexes();
3345
3474
  this.buildDependencyGraph();
@@ -3365,6 +3494,13 @@ var FormEngine = class {
3365
3494
  graph = new DependencyGraph();
3366
3495
  stepRunner;
3367
3496
  environment;
3497
+ envConfig;
3498
+ getEnvDefinitions() {
3499
+ return structuredClone(this.envConfig.definitions ?? {});
3500
+ }
3501
+ getEnv() {
3502
+ return this.environment.env ?? Object.freeze({});
3503
+ }
3368
3504
  fieldNodes = /* @__PURE__ */ new Map();
3369
3505
  actionNodes = /* @__PURE__ */ new Map();
3370
3506
  displayNodes = /* @__PURE__ */ new Map();
@@ -3434,6 +3570,7 @@ var FormEngine = class {
3434
3570
  this.notifyStateUpdated();
3435
3571
  return this.batch(async () => {
3436
3572
  try {
3573
+ this.environment.env = resolveFormEnv(this.envConfig, !this.skipLoadStepsOnInitialize);
3437
3574
  this.hydrateValues(this.runtimeTree);
3438
3575
  if (!this.skipLoadStepsOnInitialize) {
3439
3576
  await this.runLoadSteps();
@@ -3881,7 +4018,8 @@ var FormEngine = class {
3881
4018
  getPath(
3882
4019
  {
3883
4020
  values: this.values,
3884
- resources: this.resources
4021
+ resources: this.resources,
4022
+ env: this.getEnv()
3885
4023
  },
3886
4024
  path
3887
4025
  )
@@ -3972,7 +4110,8 @@ var FormEngine = class {
3972
4110
  this.reevaluateConditions();
3973
4111
  const result = runSubmitPipeline(this.getSnapshot(), {
3974
4112
  environment: this.environment,
3975
- resources: this.resources
4113
+ resources: this.resources,
4114
+ env: this.getEnv()
3976
4115
  });
3977
4116
  const itemErrors = {};
3978
4117
  this.itemFieldState.forEach((state, name) => {
@@ -4363,7 +4502,8 @@ var FormEngine = class {
4363
4502
  value: changedField === "__bulk__" ? void 0 : nextValues[changedField],
4364
4503
  state: this.getConditionState(),
4365
4504
  environment: this.environment,
4366
- resources: this.resources
4505
+ resources: this.resources,
4506
+ env: this.getEnv()
4367
4507
  });
4368
4508
  this.validateValuesRepeatFields(
4369
4509
  changedFields.filter((name) => name.includes("."))
@@ -4389,7 +4529,8 @@ var FormEngine = class {
4389
4529
  value: void 0,
4390
4530
  state: this.getConditionState(),
4391
4531
  environment: this.environment,
4392
- resources: this.resources
4532
+ resources: this.resources,
4533
+ env: this.getEnv()
4393
4534
  };
4394
4535
  }
4395
4536
  getConditionState() {
@@ -4409,7 +4550,8 @@ var FormEngine = class {
4409
4550
  value: void 0,
4410
4551
  state: this.getConditionState(),
4411
4552
  environment: this.environment,
4412
- resources: this.resources
4553
+ resources: this.resources,
4554
+ env: this.getEnv()
4413
4555
  });
4414
4556
  }
4415
4557
  syncPendingState() {
@@ -4611,7 +4753,8 @@ var FormEngine = class {
4611
4753
  {
4612
4754
  environment: this.environment,
4613
4755
  values: this.values,
4614
- resources: this.resources
4756
+ resources: this.resources,
4757
+ env: this.getEnv()
4615
4758
  }
4616
4759
  );
4617
4760
  node.state.error = result.error;
@@ -4668,7 +4811,8 @@ var FormEngine = class {
4668
4811
  const source = resolveScopedPath(
4669
4812
  {
4670
4813
  values: this.values,
4671
- resources: this.resources
4814
+ resources: this.resources,
4815
+ env: this.getEnv()
4672
4816
  },
4673
4817
  options.source
4674
4818
  );
@@ -4695,6 +4839,7 @@ var FormEngine = class {
4695
4839
  {
4696
4840
  values: this.values,
4697
4841
  resources: this.resources,
4842
+ env: this.getEnv(),
4698
4843
  ...options?.item !== void 0 ? { item: options.item } : {}
4699
4844
  },
4700
4845
  data.source
@@ -4720,6 +4865,7 @@ var FormEngine = class {
4720
4865
  executeComputed(schema.computed, this.values, {
4721
4866
  environment: this.environment,
4722
4867
  resources: this.resources,
4868
+ env: this.getEnv(),
4723
4869
  ...options?.item !== void 0 ? { item: options.item } : {}
4724
4870
  })
4725
4871
  );
@@ -4731,6 +4877,7 @@ var FormEngine = class {
4731
4877
  {
4732
4878
  values: this.values,
4733
4879
  resources: this.resources,
4880
+ env: this.getEnv(),
4734
4881
  ...options?.item !== void 0 ? { item: options.item } : {}
4735
4882
  },
4736
4883
  props.from
@@ -4754,6 +4901,7 @@ var FormEngine = class {
4754
4901
  {
4755
4902
  values: this.values,
4756
4903
  resources: this.resources,
4904
+ env: this.getEnv(),
4757
4905
  ...options?.item !== void 0 ? { item: options.item } : {}
4758
4906
  },
4759
4907
  props.from
@@ -4785,6 +4933,7 @@ var FormEngine = class {
4785
4933
  {
4786
4934
  values: this.values,
4787
4935
  resources: this.resources,
4936
+ env: this.getEnv(),
4788
4937
  ...options?.item !== void 0 ? { item: options.item } : {}
4789
4938
  },
4790
4939
  props.titleFrom
@@ -4824,7 +4973,8 @@ var FormEngine = class {
4824
4973
  const source = resolveScopedPath(
4825
4974
  {
4826
4975
  values: this.values,
4827
- resources: this.resources
4976
+ resources: this.resources,
4977
+ env: this.getEnv()
4828
4978
  },
4829
4979
  props.source
4830
4980
  );
@@ -5013,6 +5163,7 @@ var FormEngine = class {
5013
5163
  {
5014
5164
  values: this.values,
5015
5165
  resources: this.resources,
5166
+ env: this.getEnv(),
5016
5167
  ...options.item !== void 0 ? { item: options.item } : {}
5017
5168
  },
5018
5169
  options.from.trim()
@@ -5031,7 +5182,7 @@ var FormEngine = class {
5031
5182
  }
5032
5183
  validateValuesRepeatFields(only) {
5033
5184
  const targets = only ? new Set(only) : void 0;
5034
- const visit = (meta, absolutePath) => {
5185
+ const visit2 = (meta, absolutePath) => {
5035
5186
  const items = readRepeatArray(this.values, absolutePath);
5036
5187
  items.forEach((item, index) => {
5037
5188
  meta.fieldTemplates.forEach((template, fieldName) => {
@@ -5049,20 +5200,21 @@ var FormEngine = class {
5049
5200
  const result = validateField(template.schema, value, {
5050
5201
  environment: this.environment,
5051
5202
  values: this.values,
5052
- resources: this.resources
5203
+ resources: this.resources,
5204
+ env: this.getEnv()
5053
5205
  });
5054
5206
  const state = this.ensureItemFieldState(scopedName);
5055
5207
  state.error = result.error;
5056
5208
  });
5057
5209
  meta.nested.forEach((nested) => {
5058
- visit(nested, `${absolutePath}.${index}.${nested.path}`);
5210
+ visit2(nested, `${absolutePath}.${index}.${nested.path}`);
5059
5211
  });
5060
5212
  });
5061
5213
  };
5062
- this.valuesRepeatMetas.forEach((meta) => visit(meta, meta.path));
5214
+ this.valuesRepeatMetas.forEach((meta) => visit2(meta, meta.path));
5063
5215
  }
5064
5216
  touchAllValuesRepeatFields() {
5065
- const visit = (meta, absolutePath) => {
5217
+ const visit2 = (meta, absolutePath) => {
5066
5218
  const items = readRepeatArray(this.values, absolutePath);
5067
5219
  items.forEach((_, index) => {
5068
5220
  meta.fieldTemplates.forEach((_2, fieldName) => {
@@ -5070,11 +5222,11 @@ var FormEngine = class {
5070
5222
  this.ensureItemFieldState(scopedName).touched = true;
5071
5223
  });
5072
5224
  meta.nested.forEach((nested) => {
5073
- visit(nested, `${absolutePath}.${index}.${nested.path}`);
5225
+ visit2(nested, `${absolutePath}.${index}.${nested.path}`);
5074
5226
  });
5075
5227
  });
5076
5228
  };
5077
- this.valuesRepeatMetas.forEach((meta) => visit(meta, meta.path));
5229
+ this.valuesRepeatMetas.forEach((meta) => visit2(meta, meta.path));
5078
5230
  }
5079
5231
  pruneItemFieldState(absolutePath, removedIndex, previousLength) {
5080
5232
  const next = /* @__PURE__ */ new Map();
@@ -5271,19 +5423,25 @@ function findNode(node, name) {
5271
5423
  adaptDisplay,
5272
5424
  adaptField,
5273
5425
  adaptLayout,
5426
+ collectFormEnvReferences,
5274
5427
  compileSchema,
5275
5428
  conditions,
5276
5429
  createFormEngineEnvironment,
5277
5430
  createRuntimeNode,
5278
5431
  formatTagSourceValue,
5279
5432
  isThenable,
5433
+ remapFormEnvReferences,
5434
+ resolveFormEnv,
5280
5435
  resolveLoadingProp,
5281
5436
  resolveTagValueMapping,
5282
5437
  runConditionEngine,
5283
5438
  tagMapKey,
5284
5439
  traverseNode,
5285
5440
  validateApiUrlTemplate,
5441
+ validateEnvBaseUrl,
5442
+ validateEnvDefinitions,
5286
5443
  validateField,
5444
+ validateFormEnvReferences,
5287
5445
  validateSchema,
5288
5446
  validation,
5289
5447
  validationRegistry
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _bsm_form_schema from '@bsm-form/schema';
2
- import { ToastVariant, ToastPosition, Condition, DisplayEventType, FieldEventType, TableInteraction, DialogEventType, DrawerEventType, RepeatOperation, Node, ActionNode, DisplayNode, FieldNode, LayoutNode, ValidationRule, FormSchema, TableTagValueMapping } from '@bsm-form/schema';
2
+ import { FormEnvValues, ToastVariant, ToastPosition, Condition, FormEnvConfig, DisplayEventType, FieldEventType, TableInteraction, DialogEventType, DrawerEventType, RepeatOperation, Node, ActionNode, DisplayNode, FieldNode, LayoutNode, ValidationRule, FormSchema, TableTagValueMapping, FormEnvDefinitions } from '@bsm-form/schema';
3
3
 
4
4
  type FormEngineNotification = {
5
5
  message: string;
@@ -9,6 +9,7 @@ type FormEngineNotification = {
9
9
  duration?: number;
10
10
  };
11
11
  type InvokeContext = {
12
+ env?: FormEnvValues;
12
13
  values: Record<string, unknown>;
13
14
  resources: Record<string, unknown>;
14
15
  row?: Record<string, unknown>;
@@ -25,6 +26,7 @@ type ValidatorHandler = (ctx: InvokeContext) => true | string;
25
26
  /** Sync computed value producer. Must not return a Promise. */
26
27
  type ComputerHandler = (ctx: InvokeContext) => unknown;
27
28
  type FormEngineEnvironment = {
29
+ env?: FormEnvValues;
28
30
  request: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
29
31
  log: (message: string) => void;
30
32
  navigate: (to: string) => void;
@@ -85,6 +87,9 @@ declare class FormEngine {
85
87
  private graph;
86
88
  private stepRunner;
87
89
  private environment;
90
+ private envConfig;
91
+ getEnvDefinitions(): _bsm_form_schema.FormEnvDefinitions;
92
+ getEnv(): FormEnvValues;
88
93
  private fieldNodes;
89
94
  private actionNodes;
90
95
  private displayNodes;
@@ -110,7 +115,7 @@ declare class FormEngine {
110
115
  private hasPendingStateUpdate;
111
116
  private initializationPromise?;
112
117
  private skipLoadStepsOnInitialize;
113
- constructor(runtimeTree: RuntimeNode, environment?: Partial<FormEngineEnvironment>);
118
+ constructor(runtimeTree: RuntimeNode, environment?: Partial<FormEngineEnvironment>, envConfig?: FormEnvConfig);
114
119
  runLoadSteps(): Promise<void>;
115
120
  initialize(options?: {
116
121
  skipLoadSteps?: boolean;
@@ -335,6 +340,7 @@ type FormSnapshot = {
335
340
  nodes: Map<string, RuntimeNode>;
336
341
  };
337
342
  type PipelineContext = {
343
+ env?: Readonly<Record<string, string | number | boolean>>;
338
344
  tree: RuntimeNode;
339
345
  values: FormValues;
340
346
  changedField: string;
@@ -509,6 +515,7 @@ declare const validationRegistry: ValidationRegistry;
509
515
 
510
516
  type ValidateFieldOptions = {
511
517
  environment?: FormEngineEnvironment;
518
+ env?: Readonly<Record<string, string | number | boolean>>;
512
519
  values?: Record<string, unknown>;
513
520
  resources?: Record<string, unknown>;
514
521
  row?: Record<string, unknown>;
@@ -591,4 +598,16 @@ declare function formatTagSourceValue(value: unknown): string;
591
598
  /** Validates syntax without reading values or running author code. */
592
599
  declare function validateApiUrlTemplate(url: string): void;
593
600
 
594
- export { type AccordionChangeReason, type AccordionChangeResult, type AccordionState, type ActionAdapterInput, type ActionContext, type ActionHandler, type ComputerHandler, DependencyGraph, type DependencyMap, type DialogState, type DialogTransitionReason, type DialogTransitionResult, type DisplayAdapterInput, type DrawerState, type DrawerTransitionReason, type DrawerTransitionResult, EventBus, type FieldAdapterInput, FormEngine, type FormEngineConfig, type FormEngineEnvironment, type FormEngineNotification, type FormEngineState, type FormErrors, type FormEvent, type FormSnapshot, type FormState, type FormValues, type InitializationStatus, type InvokeContext, type InvokeHandler, type LayoutAdapterInput, type Listener, type OptionsConfig, type PaginationChangeReason, type PaginationChangeResult, type PaginationState, type PipelineContext, type PredicateHandler, type RepeatMutationReason, type RepeatMutationResult, type RuntimeNode, type RuntimeNodeState, type RuntimeTree, type StepRunOptions, type StepperNavigationReason, type StepperNavigationResult, type StepperState, type TabsChangeReason, type TabsChangeResult, type TabsState, type ValidateFieldOptions, type ValidationResult, type Validator, type ValidatorHandler, adaptAction, adaptDisplay, adaptField, adaptLayout, compileSchema, conditions, createFormEngineEnvironment, createRuntimeNode, formatTagSourceValue, isThenable, resolveLoadingProp, resolveTagValueMapping, runConditionEngine, tagMapKey, traverseNode, validateApiUrlTemplate, validateField, validateSchema, validation, validationRegistry };
601
+ type FormEnvReference = {
602
+ name: string;
603
+ path: string;
604
+ };
605
+ declare function collectFormEnvReferences(schema: unknown): FormEnvReference[];
606
+ declare function validateFormEnvReferences(schema: unknown, definitions?: FormEnvDefinitions): void;
607
+ declare function remapFormEnvReferences<T>(schema: T, previous: string, next: string): T;
608
+
609
+ declare function validateEnvBaseUrl(value: string): void;
610
+ declare function validateEnvDefinitions(value: unknown): asserts value is FormEnvDefinitions;
611
+ declare function resolveFormEnv(config?: FormEnvConfig, requireValues?: boolean): FormEnvValues;
612
+
613
+ export { type AccordionChangeReason, type AccordionChangeResult, type AccordionState, type ActionAdapterInput, type ActionContext, type ActionHandler, type ComputerHandler, DependencyGraph, type DependencyMap, type DialogState, type DialogTransitionReason, type DialogTransitionResult, type DisplayAdapterInput, type DrawerState, type DrawerTransitionReason, type DrawerTransitionResult, EventBus, type FieldAdapterInput, FormEngine, type FormEngineConfig, type FormEngineEnvironment, type FormEngineNotification, type FormEngineState, type FormErrors, type FormEvent, type FormSnapshot, type FormState, type FormValues, type InitializationStatus, type InvokeContext, type InvokeHandler, type LayoutAdapterInput, type Listener, type OptionsConfig, type PaginationChangeReason, type PaginationChangeResult, type PaginationState, type PipelineContext, type PredicateHandler, type RepeatMutationReason, type RepeatMutationResult, type RuntimeNode, type RuntimeNodeState, type RuntimeTree, type StepRunOptions, type StepperNavigationReason, type StepperNavigationResult, type StepperState, type TabsChangeReason, type TabsChangeResult, type TabsState, type ValidateFieldOptions, type ValidationResult, type Validator, type ValidatorHandler, adaptAction, adaptDisplay, adaptField, adaptLayout, collectFormEnvReferences, compileSchema, conditions, createFormEngineEnvironment, createRuntimeNode, formatTagSourceValue, isThenable, remapFormEnvReferences, resolveFormEnv, resolveLoadingProp, resolveTagValueMapping, runConditionEngine, tagMapKey, traverseNode, validateApiUrlTemplate, validateEnvBaseUrl, validateEnvDefinitions, validateField, validateFormEnvReferences, validateSchema, validation, validationRegistry };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _bsm_form_schema from '@bsm-form/schema';
2
- import { ToastVariant, ToastPosition, Condition, DisplayEventType, FieldEventType, TableInteraction, DialogEventType, DrawerEventType, RepeatOperation, Node, ActionNode, DisplayNode, FieldNode, LayoutNode, ValidationRule, FormSchema, TableTagValueMapping } from '@bsm-form/schema';
2
+ import { FormEnvValues, ToastVariant, ToastPosition, Condition, FormEnvConfig, DisplayEventType, FieldEventType, TableInteraction, DialogEventType, DrawerEventType, RepeatOperation, Node, ActionNode, DisplayNode, FieldNode, LayoutNode, ValidationRule, FormSchema, TableTagValueMapping, FormEnvDefinitions } from '@bsm-form/schema';
3
3
 
4
4
  type FormEngineNotification = {
5
5
  message: string;
@@ -9,6 +9,7 @@ type FormEngineNotification = {
9
9
  duration?: number;
10
10
  };
11
11
  type InvokeContext = {
12
+ env?: FormEnvValues;
12
13
  values: Record<string, unknown>;
13
14
  resources: Record<string, unknown>;
14
15
  row?: Record<string, unknown>;
@@ -25,6 +26,7 @@ type ValidatorHandler = (ctx: InvokeContext) => true | string;
25
26
  /** Sync computed value producer. Must not return a Promise. */
26
27
  type ComputerHandler = (ctx: InvokeContext) => unknown;
27
28
  type FormEngineEnvironment = {
29
+ env?: FormEnvValues;
28
30
  request: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
29
31
  log: (message: string) => void;
30
32
  navigate: (to: string) => void;
@@ -85,6 +87,9 @@ declare class FormEngine {
85
87
  private graph;
86
88
  private stepRunner;
87
89
  private environment;
90
+ private envConfig;
91
+ getEnvDefinitions(): _bsm_form_schema.FormEnvDefinitions;
92
+ getEnv(): FormEnvValues;
88
93
  private fieldNodes;
89
94
  private actionNodes;
90
95
  private displayNodes;
@@ -110,7 +115,7 @@ declare class FormEngine {
110
115
  private hasPendingStateUpdate;
111
116
  private initializationPromise?;
112
117
  private skipLoadStepsOnInitialize;
113
- constructor(runtimeTree: RuntimeNode, environment?: Partial<FormEngineEnvironment>);
118
+ constructor(runtimeTree: RuntimeNode, environment?: Partial<FormEngineEnvironment>, envConfig?: FormEnvConfig);
114
119
  runLoadSteps(): Promise<void>;
115
120
  initialize(options?: {
116
121
  skipLoadSteps?: boolean;
@@ -335,6 +340,7 @@ type FormSnapshot = {
335
340
  nodes: Map<string, RuntimeNode>;
336
341
  };
337
342
  type PipelineContext = {
343
+ env?: Readonly<Record<string, string | number | boolean>>;
338
344
  tree: RuntimeNode;
339
345
  values: FormValues;
340
346
  changedField: string;
@@ -509,6 +515,7 @@ declare const validationRegistry: ValidationRegistry;
509
515
 
510
516
  type ValidateFieldOptions = {
511
517
  environment?: FormEngineEnvironment;
518
+ env?: Readonly<Record<string, string | number | boolean>>;
512
519
  values?: Record<string, unknown>;
513
520
  resources?: Record<string, unknown>;
514
521
  row?: Record<string, unknown>;
@@ -591,4 +598,16 @@ declare function formatTagSourceValue(value: unknown): string;
591
598
  /** Validates syntax without reading values or running author code. */
592
599
  declare function validateApiUrlTemplate(url: string): void;
593
600
 
594
- export { type AccordionChangeReason, type AccordionChangeResult, type AccordionState, type ActionAdapterInput, type ActionContext, type ActionHandler, type ComputerHandler, DependencyGraph, type DependencyMap, type DialogState, type DialogTransitionReason, type DialogTransitionResult, type DisplayAdapterInput, type DrawerState, type DrawerTransitionReason, type DrawerTransitionResult, EventBus, type FieldAdapterInput, FormEngine, type FormEngineConfig, type FormEngineEnvironment, type FormEngineNotification, type FormEngineState, type FormErrors, type FormEvent, type FormSnapshot, type FormState, type FormValues, type InitializationStatus, type InvokeContext, type InvokeHandler, type LayoutAdapterInput, type Listener, type OptionsConfig, type PaginationChangeReason, type PaginationChangeResult, type PaginationState, type PipelineContext, type PredicateHandler, type RepeatMutationReason, type RepeatMutationResult, type RuntimeNode, type RuntimeNodeState, type RuntimeTree, type StepRunOptions, type StepperNavigationReason, type StepperNavigationResult, type StepperState, type TabsChangeReason, type TabsChangeResult, type TabsState, type ValidateFieldOptions, type ValidationResult, type Validator, type ValidatorHandler, adaptAction, adaptDisplay, adaptField, adaptLayout, compileSchema, conditions, createFormEngineEnvironment, createRuntimeNode, formatTagSourceValue, isThenable, resolveLoadingProp, resolveTagValueMapping, runConditionEngine, tagMapKey, traverseNode, validateApiUrlTemplate, validateField, validateSchema, validation, validationRegistry };
601
+ type FormEnvReference = {
602
+ name: string;
603
+ path: string;
604
+ };
605
+ declare function collectFormEnvReferences(schema: unknown): FormEnvReference[];
606
+ declare function validateFormEnvReferences(schema: unknown, definitions?: FormEnvDefinitions): void;
607
+ declare function remapFormEnvReferences<T>(schema: T, previous: string, next: string): T;
608
+
609
+ declare function validateEnvBaseUrl(value: string): void;
610
+ declare function validateEnvDefinitions(value: unknown): asserts value is FormEnvDefinitions;
611
+ declare function resolveFormEnv(config?: FormEnvConfig, requireValues?: boolean): FormEnvValues;
612
+
613
+ export { type AccordionChangeReason, type AccordionChangeResult, type AccordionState, type ActionAdapterInput, type ActionContext, type ActionHandler, type ComputerHandler, DependencyGraph, type DependencyMap, type DialogState, type DialogTransitionReason, type DialogTransitionResult, type DisplayAdapterInput, type DrawerState, type DrawerTransitionReason, type DrawerTransitionResult, EventBus, type FieldAdapterInput, FormEngine, type FormEngineConfig, type FormEngineEnvironment, type FormEngineNotification, type FormEngineState, type FormErrors, type FormEvent, type FormSnapshot, type FormState, type FormValues, type InitializationStatus, type InvokeContext, type InvokeHandler, type LayoutAdapterInput, type Listener, type OptionsConfig, type PaginationChangeReason, type PaginationChangeResult, type PaginationState, type PipelineContext, type PredicateHandler, type RepeatMutationReason, type RepeatMutationResult, type RuntimeNode, type RuntimeNodeState, type RuntimeTree, type StepRunOptions, type StepperNavigationReason, type StepperNavigationResult, type StepperState, type TabsChangeReason, type TabsChangeResult, type TabsState, type ValidateFieldOptions, type ValidationResult, type Validator, type ValidatorHandler, adaptAction, adaptDisplay, adaptField, adaptLayout, collectFormEnvReferences, compileSchema, conditions, createFormEngineEnvironment, createRuntimeNode, formatTagSourceValue, isThenable, remapFormEnvReferences, resolveFormEnv, resolveLoadingProp, resolveTagValueMapping, runConditionEngine, tagMapKey, traverseNode, validateApiUrlTemplate, validateEnvBaseUrl, validateEnvDefinitions, validateField, validateFormEnvReferences, validateSchema, validation, validationRegistry };
package/dist/index.js CHANGED
@@ -1,3 +1,58 @@
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
+
1
56
  // src/utils/get-path.ts
2
57
  function pathSegments(path) {
3
58
  const segments = [];
@@ -45,6 +100,8 @@ function getPath(obj, path) {
45
100
 
46
101
  // src/utils/scope-path.ts
47
102
  function resolveScopedPath(ctx, path) {
103
+ if (path === "env") return ctx.env ?? {};
104
+ if (path.startsWith("env.")) return getPath(ctx.env ?? {}, path.slice(4));
48
105
  if (path === "$values" || path === "values") {
49
106
  return ctx.values;
50
107
  }
@@ -72,7 +129,7 @@ function resolveScopedPath(ctx, path) {
72
129
  return getPath(ctx.values, path);
73
130
  }
74
131
  function isExplicitScopedPath(path) {
75
- 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.");
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.");
76
133
  }
77
134
  function resolvePathStringsInData(value, ctx) {
78
135
  if (typeof value === "string") {
@@ -92,7 +149,7 @@ function resolvePathStringsInData(value, ctx) {
92
149
  }
93
150
 
94
151
  // src/utils/url-template.ts
95
- var pathPattern = /^(values|resources)\.[A-Za-z_$][\w$]*(?:(?:\.(?:[A-Za-z_$][\w$]*|\d+))|(?:\[\d+\]))*$/;
152
+ var pathPattern = /^(values|resources|env)\.[A-Za-z_$][\w$]*(?:(?:\.(?:[A-Za-z_$][\w$]*|\d+))|(?:\[\d+\]))*$/;
96
153
  function placeholders(url) {
97
154
  const result = [];
98
155
  let cursor = 0;
@@ -103,10 +160,10 @@ function placeholders(url) {
103
160
  if (close < 0) throw new Error("API URL template has an unclosed placeholder");
104
161
  const path = url.slice(start + 2, close).trim();
105
162
  if (!pathPattern.test(path)) {
106
- throw new Error(`Invalid API URL placeholder: ${path}. Use a values.* or resources.* data path`);
163
+ throw new Error(`Invalid API URL placeholder: ${path}. Use a values.*, resources.*, or env.* data path`);
107
164
  }
108
165
  const origin = url.match(/^(?:[A-Za-z][A-Za-z0-9+.-]*:)?\/\/[^/?#]*/)?.[0];
109
- if (start === 0 || origin && start < origin.length) {
166
+ if (start === 0 && !path.startsWith("env.") || origin && start < origin.length) {
110
167
  throw new Error("API URL placeholders cannot replace the service origin; use a static base URL");
111
168
  }
112
169
  result.push({ start, end: close + 1, path });
@@ -118,7 +175,7 @@ function validateApiUrlTemplate(url) {
118
175
  if (!url.trim()) throw new Error("API URL must not be empty");
119
176
  placeholders(url);
120
177
  }
121
- function resolveApiUrlTemplate(url, context) {
178
+ function resolveApiUrlTemplate(url, context, definitions = {}) {
122
179
  validateApiUrlTemplate(url);
123
180
  let result = "";
124
181
  let cursor = 0;
@@ -130,7 +187,17 @@ function resolveApiUrlTemplate(url, context) {
130
187
  if (value === "." || value === "..") {
131
188
  throw new Error(`API URL placeholder ${placeholder.path} cannot be a dot segment`);
132
189
  }
133
- result += url.slice(cursor, placeholder.start) + encodeURIComponent(String(value));
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
+ }
134
201
  cursor = placeholder.end;
135
202
  }
136
203
  return result + url.slice(cursor);
@@ -1562,6 +1629,7 @@ function validateDrawerStepReferences(context) {
1562
1629
  }
1563
1630
  function validateStepConditionReferences(context) {
1564
1631
  context.stepConditionReferences.forEach(({ path, field }) => {
1632
+ if (field.startsWith("env.")) return;
1565
1633
  if (isReservedConditionField(field)) {
1566
1634
  if (isPendingActionConditionField(field)) {
1567
1635
  const actionId = pendingActionIdFromField(field);
@@ -1722,6 +1790,7 @@ var validationRegistry = {
1722
1790
  // src/engine/environment.ts
1723
1791
  function createFormEngineEnvironment(overrides = {}) {
1724
1792
  return {
1793
+ env: overrides.env ?? Object.freeze({}),
1725
1794
  request: overrides.request ?? defaultRequest,
1726
1795
  log: overrides.log ?? defaultLog,
1727
1796
  navigate: overrides.navigate ?? defaultNavigate,
@@ -1791,6 +1860,7 @@ function runInvokeValidator(rule, options) {
1791
1860
  const ctx = {
1792
1861
  values: options?.values ?? {},
1793
1862
  resources: options?.resources ?? {},
1863
+ env: options?.env ?? options?.environment?.env,
1794
1864
  ...options?.row !== void 0 ? { row: options.row } : {},
1795
1865
  ...options?.item !== void 0 ? { item: options.item } : {}
1796
1866
  };
@@ -1895,6 +1965,7 @@ function evaluateCondition(condition, values, state, options) {
1895
1965
  }
1896
1966
  const target = resolveConditionTarget(condition.field, values, state, {
1897
1967
  resources: options?.resources,
1968
+ env: options?.env ?? options?.environment?.env,
1898
1969
  ...options?.row !== void 0 ? { row: options.row } : {},
1899
1970
  ...options?.item !== void 0 ? { item: options.item } : {}
1900
1971
  });
@@ -1941,6 +2012,7 @@ function evaluateInvokeCondition(name, values, options) {
1941
2012
  const ctx = {
1942
2013
  values,
1943
2014
  resources: options?.resources ?? {},
2015
+ env: options?.env ?? options?.environment?.env,
1944
2016
  ...options?.row !== void 0 ? { row: options.row } : {},
1945
2017
  ...options?.item !== void 0 ? { item: options.item } : {}
1946
2018
  };
@@ -1995,6 +2067,7 @@ function resolveConditionTarget(field, values, state, options) {
1995
2067
  {
1996
2068
  values,
1997
2069
  resources: options?.resources ?? {},
2070
+ env: options?.env,
1998
2071
  ...options?.row !== void 0 ? { row: options.row } : {},
1999
2072
  ...options?.item !== void 0 ? { item: options.item } : {}
2000
2073
  },
@@ -2294,7 +2367,7 @@ var DependencyGraph = class {
2294
2367
  return ordered;
2295
2368
  }
2296
2369
  extractDependencies(conditions2) {
2297
- 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."));
2298
2371
  }
2299
2372
  clear() {
2300
2373
  this.forward.clear();
@@ -2316,10 +2389,52 @@ var EventBus = class {
2316
2389
  }
2317
2390
  };
2318
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
+
2319
2434
  // src/pipeline/run-conditions.ts
2320
2435
  function runConditions(ctx) {
2321
- visit(ctx.tree, true);
2322
- function visit(node, parentVisible) {
2436
+ visit2(ctx.tree, true);
2437
+ function visit2(node, parentVisible) {
2323
2438
  const schema = node.schema;
2324
2439
  const hasVisibilityConditions = schema.kind === "field" || schema.kind === "display" || schema.kind === "action" || schema.kind === "layout" && schema.type === "step";
2325
2440
  if (!parentVisible) {
@@ -2337,7 +2452,7 @@ function runConditions(ctx) {
2337
2452
  }
2338
2453
  node.state.disabled = evaluateDisabled(schema, ctx);
2339
2454
  const childrenVisible = node.state.visible && !(schema.kind === "layout" && (schema.type === "dialog" || schema.type === "drawer") && node.state.open !== true);
2340
- node.children.forEach((child) => visit(child, childrenVisible));
2455
+ node.children.forEach((child) => visit2(child, childrenVisible));
2341
2456
  }
2342
2457
  }
2343
2458
  function evaluateDisabled(schema, ctx) {
@@ -2456,6 +2571,7 @@ function executeInvokeComputer(name, values, options) {
2456
2571
  const ctx = {
2457
2572
  values,
2458
2573
  resources: options?.resources ?? {},
2574
+ env: options?.env ?? options?.environment?.env,
2459
2575
  ...options?.row !== void 0 ? { row: options.row } : {},
2460
2576
  ...options?.item !== void 0 ? { item: options.item } : {}
2461
2577
  };
@@ -2804,6 +2920,7 @@ var StepRunner = class {
2804
2920
  (condition) => evaluateCondition(condition, values, conditionState, {
2805
2921
  environment: this.environment,
2806
2922
  resources: this.engine.getResources(),
2923
+ env: this.engine.getEnv(),
2807
2924
  ...options?.row !== void 0 ? { row: options.row } : {},
2808
2925
  ...options?.item !== void 0 ? { item: options.item } : {}
2809
2926
  })
@@ -2888,6 +3005,7 @@ var StepRunner = class {
2888
3005
  return {
2889
3006
  values: this.engine.getValues(),
2890
3007
  resources: this.engine.getResources(),
3008
+ env: this.engine.getEnv(),
2891
3009
  ...options?.row !== void 0 ? { row: options.row } : {},
2892
3010
  ...options?.item !== void 0 ? { item: options.item } : {}
2893
3011
  };
@@ -2896,6 +3014,7 @@ var StepRunner = class {
2896
3014
  return {
2897
3015
  values: this.engine.getValues(),
2898
3016
  resources: this.engine.getResources(),
3017
+ env: this.engine.getEnv(),
2899
3018
  ...options?.row !== void 0 ? { row: options.row } : {},
2900
3019
  ...options?.item !== void 0 ? { item: options.item } : {}
2901
3020
  };
@@ -2905,6 +3024,7 @@ function resolveApiRequestBody(step, engine, options) {
2905
3024
  const ctx = {
2906
3025
  values: engine.getValues(),
2907
3026
  resources: engine.getResources(),
3027
+ env: engine.getEnv(),
2908
3028
  ...options?.row !== void 0 ? { row: options.row } : {},
2909
3029
  ...options?.item !== void 0 ? { item: options.item } : {}
2910
3030
  };
@@ -2924,10 +3044,11 @@ function resolveApiRequestUrl(step, engine, options) {
2924
3044
  const ctx = {
2925
3045
  values: engine.getValues(),
2926
3046
  resources: engine.getResources(),
3047
+ env: engine.getEnv(),
2927
3048
  ...options?.row !== void 0 ? { row: options.row } : {},
2928
3049
  ...options?.item !== void 0 ? { item: options.item } : {}
2929
3050
  };
2930
- const url = resolveApiUrlTemplate(step.request.url, ctx);
3051
+ const url = resolveApiUrlTemplate(step.request.url, ctx, engine.getEnvDefinitions());
2931
3052
  const hasQueryFrom = "queryFrom" in step.request && step.request.queryFrom !== void 0;
2932
3053
  const hasQuery = "query" in step.request && step.request.query !== void 0;
2933
3054
  if (hasQueryFrom && hasQuery) {
@@ -3220,11 +3341,11 @@ function collectTemplate(node, fields, nested) {
3220
3341
  }
3221
3342
  function collectValuesRepeatFieldIds(metas) {
3222
3343
  const ids = /* @__PURE__ */ new Set();
3223
- const visit = (meta) => {
3344
+ const visit2 = (meta) => {
3224
3345
  meta.fieldTemplates.forEach((node) => ids.add(node.id));
3225
- meta.nested.forEach(visit);
3346
+ meta.nested.forEach(visit2);
3226
3347
  };
3227
- metas.forEach(visit);
3348
+ metas.forEach(visit2);
3228
3349
  return ids;
3229
3350
  }
3230
3351
  function createDefaultRepeatItem(meta) {
@@ -3312,9 +3433,11 @@ function findValuesRepeatMeta(metas, repeatId) {
3312
3433
 
3313
3434
  // src/engine/form-engine.ts
3314
3435
  var FormEngine = class {
3315
- constructor(runtimeTree, environment = {}) {
3436
+ constructor(runtimeTree, environment = {}, envConfig = {}) {
3316
3437
  this.runtimeTree = runtimeTree;
3317
- this.environment = createFormEngineEnvironment(environment);
3438
+ this.envConfig = structuredClone(envConfig);
3439
+ validateFormEnvReferences(this.runtimeTree.schema, this.envConfig.definitions);
3440
+ this.environment = createFormEngineEnvironment({ ...environment, env: resolveFormEnv(this.envConfig, false) });
3318
3441
  this.stepRunner = new StepRunner(this, this.environment);
3319
3442
  this.buildNodeIndexes();
3320
3443
  this.buildDependencyGraph();
@@ -3340,6 +3463,13 @@ var FormEngine = class {
3340
3463
  graph = new DependencyGraph();
3341
3464
  stepRunner;
3342
3465
  environment;
3466
+ envConfig;
3467
+ getEnvDefinitions() {
3468
+ return structuredClone(this.envConfig.definitions ?? {});
3469
+ }
3470
+ getEnv() {
3471
+ return this.environment.env ?? Object.freeze({});
3472
+ }
3343
3473
  fieldNodes = /* @__PURE__ */ new Map();
3344
3474
  actionNodes = /* @__PURE__ */ new Map();
3345
3475
  displayNodes = /* @__PURE__ */ new Map();
@@ -3409,6 +3539,7 @@ var FormEngine = class {
3409
3539
  this.notifyStateUpdated();
3410
3540
  return this.batch(async () => {
3411
3541
  try {
3542
+ this.environment.env = resolveFormEnv(this.envConfig, !this.skipLoadStepsOnInitialize);
3412
3543
  this.hydrateValues(this.runtimeTree);
3413
3544
  if (!this.skipLoadStepsOnInitialize) {
3414
3545
  await this.runLoadSteps();
@@ -3856,7 +3987,8 @@ var FormEngine = class {
3856
3987
  getPath(
3857
3988
  {
3858
3989
  values: this.values,
3859
- resources: this.resources
3990
+ resources: this.resources,
3991
+ env: this.getEnv()
3860
3992
  },
3861
3993
  path
3862
3994
  )
@@ -3947,7 +4079,8 @@ var FormEngine = class {
3947
4079
  this.reevaluateConditions();
3948
4080
  const result = runSubmitPipeline(this.getSnapshot(), {
3949
4081
  environment: this.environment,
3950
- resources: this.resources
4082
+ resources: this.resources,
4083
+ env: this.getEnv()
3951
4084
  });
3952
4085
  const itemErrors = {};
3953
4086
  this.itemFieldState.forEach((state, name) => {
@@ -4338,7 +4471,8 @@ var FormEngine = class {
4338
4471
  value: changedField === "__bulk__" ? void 0 : nextValues[changedField],
4339
4472
  state: this.getConditionState(),
4340
4473
  environment: this.environment,
4341
- resources: this.resources
4474
+ resources: this.resources,
4475
+ env: this.getEnv()
4342
4476
  });
4343
4477
  this.validateValuesRepeatFields(
4344
4478
  changedFields.filter((name) => name.includes("."))
@@ -4364,7 +4498,8 @@ var FormEngine = class {
4364
4498
  value: void 0,
4365
4499
  state: this.getConditionState(),
4366
4500
  environment: this.environment,
4367
- resources: this.resources
4501
+ resources: this.resources,
4502
+ env: this.getEnv()
4368
4503
  };
4369
4504
  }
4370
4505
  getConditionState() {
@@ -4384,7 +4519,8 @@ var FormEngine = class {
4384
4519
  value: void 0,
4385
4520
  state: this.getConditionState(),
4386
4521
  environment: this.environment,
4387
- resources: this.resources
4522
+ resources: this.resources,
4523
+ env: this.getEnv()
4388
4524
  });
4389
4525
  }
4390
4526
  syncPendingState() {
@@ -4586,7 +4722,8 @@ var FormEngine = class {
4586
4722
  {
4587
4723
  environment: this.environment,
4588
4724
  values: this.values,
4589
- resources: this.resources
4725
+ resources: this.resources,
4726
+ env: this.getEnv()
4590
4727
  }
4591
4728
  );
4592
4729
  node.state.error = result.error;
@@ -4643,7 +4780,8 @@ var FormEngine = class {
4643
4780
  const source = resolveScopedPath(
4644
4781
  {
4645
4782
  values: this.values,
4646
- resources: this.resources
4783
+ resources: this.resources,
4784
+ env: this.getEnv()
4647
4785
  },
4648
4786
  options.source
4649
4787
  );
@@ -4670,6 +4808,7 @@ var FormEngine = class {
4670
4808
  {
4671
4809
  values: this.values,
4672
4810
  resources: this.resources,
4811
+ env: this.getEnv(),
4673
4812
  ...options?.item !== void 0 ? { item: options.item } : {}
4674
4813
  },
4675
4814
  data.source
@@ -4695,6 +4834,7 @@ var FormEngine = class {
4695
4834
  executeComputed(schema.computed, this.values, {
4696
4835
  environment: this.environment,
4697
4836
  resources: this.resources,
4837
+ env: this.getEnv(),
4698
4838
  ...options?.item !== void 0 ? { item: options.item } : {}
4699
4839
  })
4700
4840
  );
@@ -4706,6 +4846,7 @@ var FormEngine = class {
4706
4846
  {
4707
4847
  values: this.values,
4708
4848
  resources: this.resources,
4849
+ env: this.getEnv(),
4709
4850
  ...options?.item !== void 0 ? { item: options.item } : {}
4710
4851
  },
4711
4852
  props.from
@@ -4729,6 +4870,7 @@ var FormEngine = class {
4729
4870
  {
4730
4871
  values: this.values,
4731
4872
  resources: this.resources,
4873
+ env: this.getEnv(),
4732
4874
  ...options?.item !== void 0 ? { item: options.item } : {}
4733
4875
  },
4734
4876
  props.from
@@ -4760,6 +4902,7 @@ var FormEngine = class {
4760
4902
  {
4761
4903
  values: this.values,
4762
4904
  resources: this.resources,
4905
+ env: this.getEnv(),
4763
4906
  ...options?.item !== void 0 ? { item: options.item } : {}
4764
4907
  },
4765
4908
  props.titleFrom
@@ -4799,7 +4942,8 @@ var FormEngine = class {
4799
4942
  const source = resolveScopedPath(
4800
4943
  {
4801
4944
  values: this.values,
4802
- resources: this.resources
4945
+ resources: this.resources,
4946
+ env: this.getEnv()
4803
4947
  },
4804
4948
  props.source
4805
4949
  );
@@ -4988,6 +5132,7 @@ var FormEngine = class {
4988
5132
  {
4989
5133
  values: this.values,
4990
5134
  resources: this.resources,
5135
+ env: this.getEnv(),
4991
5136
  ...options.item !== void 0 ? { item: options.item } : {}
4992
5137
  },
4993
5138
  options.from.trim()
@@ -5006,7 +5151,7 @@ var FormEngine = class {
5006
5151
  }
5007
5152
  validateValuesRepeatFields(only) {
5008
5153
  const targets = only ? new Set(only) : void 0;
5009
- const visit = (meta, absolutePath) => {
5154
+ const visit2 = (meta, absolutePath) => {
5010
5155
  const items = readRepeatArray(this.values, absolutePath);
5011
5156
  items.forEach((item, index) => {
5012
5157
  meta.fieldTemplates.forEach((template, fieldName) => {
@@ -5024,20 +5169,21 @@ var FormEngine = class {
5024
5169
  const result = validateField(template.schema, value, {
5025
5170
  environment: this.environment,
5026
5171
  values: this.values,
5027
- resources: this.resources
5172
+ resources: this.resources,
5173
+ env: this.getEnv()
5028
5174
  });
5029
5175
  const state = this.ensureItemFieldState(scopedName);
5030
5176
  state.error = result.error;
5031
5177
  });
5032
5178
  meta.nested.forEach((nested) => {
5033
- visit(nested, `${absolutePath}.${index}.${nested.path}`);
5179
+ visit2(nested, `${absolutePath}.${index}.${nested.path}`);
5034
5180
  });
5035
5181
  });
5036
5182
  };
5037
- this.valuesRepeatMetas.forEach((meta) => visit(meta, meta.path));
5183
+ this.valuesRepeatMetas.forEach((meta) => visit2(meta, meta.path));
5038
5184
  }
5039
5185
  touchAllValuesRepeatFields() {
5040
- const visit = (meta, absolutePath) => {
5186
+ const visit2 = (meta, absolutePath) => {
5041
5187
  const items = readRepeatArray(this.values, absolutePath);
5042
5188
  items.forEach((_, index) => {
5043
5189
  meta.fieldTemplates.forEach((_2, fieldName) => {
@@ -5045,11 +5191,11 @@ var FormEngine = class {
5045
5191
  this.ensureItemFieldState(scopedName).touched = true;
5046
5192
  });
5047
5193
  meta.nested.forEach((nested) => {
5048
- visit(nested, `${absolutePath}.${index}.${nested.path}`);
5194
+ visit2(nested, `${absolutePath}.${index}.${nested.path}`);
5049
5195
  });
5050
5196
  });
5051
5197
  };
5052
- this.valuesRepeatMetas.forEach((meta) => visit(meta, meta.path));
5198
+ this.valuesRepeatMetas.forEach((meta) => visit2(meta, meta.path));
5053
5199
  }
5054
5200
  pruneItemFieldState(absolutePath, removedIndex, previousLength) {
5055
5201
  const next = /* @__PURE__ */ new Map();
@@ -5245,19 +5391,25 @@ export {
5245
5391
  adaptDisplay,
5246
5392
  adaptField,
5247
5393
  adaptLayout,
5394
+ collectFormEnvReferences,
5248
5395
  compileSchema,
5249
5396
  conditions,
5250
5397
  createFormEngineEnvironment,
5251
5398
  createRuntimeNode,
5252
5399
  formatTagSourceValue,
5253
5400
  isThenable,
5401
+ remapFormEnvReferences,
5402
+ resolveFormEnv,
5254
5403
  resolveLoadingProp,
5255
5404
  resolveTagValueMapping,
5256
5405
  runConditionEngine,
5257
5406
  tagMapKey,
5258
5407
  traverseNode,
5259
5408
  validateApiUrlTemplate,
5409
+ validateEnvBaseUrl,
5410
+ validateEnvDefinitions,
5260
5411
  validateField,
5412
+ validateFormEnvReferences,
5261
5413
  validateSchema,
5262
5414
  validation,
5263
5415
  validationRegistry
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsm-form/core",
3
- "version": "0.40.0",
3
+ "version": "0.41.0",
4
4
  "description": "Framework-independent form engine for BSM FormSchema",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -19,7 +19,7 @@
19
19
  ],
20
20
  "sideEffects": false,
21
21
  "dependencies": {
22
- "@bsm-form/schema": "0.40.0"
22
+ "@bsm-form/schema": "0.41.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "vitest": "^4.1.10"