@fogpipe/forma-core 0.10.3 → 0.11.1
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/{chunk-RG7JYFQ6.js → chunk-XS3BLVIZ.js} +103 -13
- package/dist/chunk-XS3BLVIZ.js.map +1 -0
- package/dist/engine/index.cjs +104 -12
- package/dist/engine/index.cjs.map +1 -1
- package/dist/engine/index.d.ts +2 -2
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +5 -1
- package/dist/engine/validate.d.ts.map +1 -1
- package/dist/engine/visibility.d.ts +61 -9
- package/dist/engine/visibility.d.ts.map +1 -1
- package/dist/index.cjs +104 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -1
- package/package.json +1 -1
- package/src/__tests__/validate.test.ts +345 -0
- package/src/__tests__/visibility.test.ts +530 -0
- package/src/engine/index.ts +3 -0
- package/src/engine/validate.ts +54 -15
- package/src/engine/visibility.ts +183 -35
- package/dist/chunk-RG7JYFQ6.js.map +0 -1
|
@@ -210,6 +210,33 @@ function calculateField(fieldName, data, spec) {
|
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
// src/engine/visibility.ts
|
|
213
|
+
function filterOptionsByContext(options, context) {
|
|
214
|
+
return options.filter((option) => {
|
|
215
|
+
if (!option.visibleWhen) return true;
|
|
216
|
+
try {
|
|
217
|
+
return evaluateBoolean(option.visibleWhen, context);
|
|
218
|
+
} catch {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
function processArrayItemOptions(arrayPath, fieldDef, arrayData, baseContext, result) {
|
|
224
|
+
if (!fieldDef.itemFields) return;
|
|
225
|
+
for (let i = 0; i < arrayData.length; i++) {
|
|
226
|
+
const item = arrayData[i] ?? {};
|
|
227
|
+
const itemContext = {
|
|
228
|
+
...baseContext,
|
|
229
|
+
item,
|
|
230
|
+
itemIndex: i
|
|
231
|
+
};
|
|
232
|
+
for (const [itemFieldName, itemFieldDef] of Object.entries(fieldDef.itemFields)) {
|
|
233
|
+
if (itemFieldDef.options && itemFieldDef.options.length > 0) {
|
|
234
|
+
const itemFieldPath = `${arrayPath}[${i}].${itemFieldName}`;
|
|
235
|
+
result[itemFieldPath] = filterOptionsByContext(itemFieldDef.options, itemContext);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
213
240
|
function getVisibility(data, spec, options = {}) {
|
|
214
241
|
const computed = options.computed ?? calculate(data, spec);
|
|
215
242
|
const baseContext = {
|
|
@@ -297,6 +324,41 @@ function getPageVisibility(data, spec, options = {}) {
|
|
|
297
324
|
}
|
|
298
325
|
return result;
|
|
299
326
|
}
|
|
327
|
+
function getOptionsVisibility(data, spec, options = {}) {
|
|
328
|
+
const computed = options.computed ?? calculate(data, spec);
|
|
329
|
+
const result = {};
|
|
330
|
+
const baseContext = {
|
|
331
|
+
data,
|
|
332
|
+
computed,
|
|
333
|
+
referenceData: spec.referenceData
|
|
334
|
+
};
|
|
335
|
+
for (const fieldPath of spec.fieldOrder) {
|
|
336
|
+
const fieldDef = spec.fields[fieldPath];
|
|
337
|
+
if (!fieldDef) continue;
|
|
338
|
+
if (fieldDef.options && fieldDef.options.length > 0) {
|
|
339
|
+
result[fieldPath] = filterOptionsByContext(fieldDef.options, baseContext);
|
|
340
|
+
}
|
|
341
|
+
if (fieldDef.itemFields) {
|
|
342
|
+
const arrayData = data[fieldPath];
|
|
343
|
+
if (Array.isArray(arrayData)) {
|
|
344
|
+
processArrayItemOptions(fieldPath, fieldDef, arrayData, baseContext, result);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return result;
|
|
349
|
+
}
|
|
350
|
+
function getVisibleOptions(options, data, spec, context = {}) {
|
|
351
|
+
if (!options || options.length === 0) return [];
|
|
352
|
+
const computed = context.computed ?? calculate(data, spec);
|
|
353
|
+
const evalContext = {
|
|
354
|
+
data,
|
|
355
|
+
computed,
|
|
356
|
+
referenceData: spec.referenceData,
|
|
357
|
+
item: context.item,
|
|
358
|
+
itemIndex: context.itemIndex
|
|
359
|
+
};
|
|
360
|
+
return filterOptionsByContext(options, evalContext);
|
|
361
|
+
}
|
|
300
362
|
|
|
301
363
|
// src/engine/required.ts
|
|
302
364
|
function getRequired(data, spec, options = {}) {
|
|
@@ -481,11 +543,12 @@ function validateField(path, value, fieldDef, schemaProperty, spec, data, comput
|
|
|
481
543
|
const customErrors = validateCustomRules(path, fieldDef.validations, context);
|
|
482
544
|
errors.push(...customErrors);
|
|
483
545
|
}
|
|
484
|
-
if (Array.isArray(value)
|
|
546
|
+
if (Array.isArray(value)) {
|
|
485
547
|
const arrayErrors = validateArray(
|
|
486
548
|
path,
|
|
487
549
|
value,
|
|
488
550
|
fieldDef,
|
|
551
|
+
schemaProperty,
|
|
489
552
|
spec,
|
|
490
553
|
data,
|
|
491
554
|
computed,
|
|
@@ -739,24 +802,28 @@ function validateCustomRules(path, rules, context) {
|
|
|
739
802
|
}
|
|
740
803
|
return errors;
|
|
741
804
|
}
|
|
742
|
-
function validateArray(path, value, fieldDef, spec, data, computed, visibility, onlyVisible) {
|
|
805
|
+
function validateArray(path, value, fieldDef, schemaProperty, spec, data, computed, visibility, onlyVisible) {
|
|
743
806
|
const errors = [];
|
|
744
807
|
const label = fieldDef.label ?? path;
|
|
745
|
-
|
|
808
|
+
const arraySchema = (schemaProperty == null ? void 0 : schemaProperty.type) === "array" ? schemaProperty : void 0;
|
|
809
|
+
const minItems = fieldDef.minItems ?? (arraySchema == null ? void 0 : arraySchema.minItems);
|
|
810
|
+
const maxItems = fieldDef.maxItems ?? (arraySchema == null ? void 0 : arraySchema.maxItems);
|
|
811
|
+
if (minItems !== void 0 && value.length < minItems) {
|
|
746
812
|
errors.push({
|
|
747
813
|
field: path,
|
|
748
|
-
message: `${label} must have at least ${
|
|
814
|
+
message: `${label} must have at least ${minItems} items`,
|
|
749
815
|
severity: "error"
|
|
750
816
|
});
|
|
751
817
|
}
|
|
752
|
-
if (
|
|
818
|
+
if (maxItems !== void 0 && value.length > maxItems) {
|
|
753
819
|
errors.push({
|
|
754
820
|
field: path,
|
|
755
|
-
message: `${label} must have no more than ${
|
|
821
|
+
message: `${label} must have no more than ${maxItems} items`,
|
|
756
822
|
severity: "error"
|
|
757
823
|
});
|
|
758
824
|
}
|
|
759
|
-
|
|
825
|
+
const itemSchema = arraySchema == null ? void 0 : arraySchema.items;
|
|
826
|
+
if (fieldDef.itemFields || itemSchema) {
|
|
760
827
|
for (let i = 0; i < value.length; i++) {
|
|
761
828
|
const item = value[i];
|
|
762
829
|
const itemErrors = validateArrayItem(
|
|
@@ -764,6 +831,7 @@ function validateArray(path, value, fieldDef, spec, data, computed, visibility,
|
|
|
764
831
|
i,
|
|
765
832
|
item,
|
|
766
833
|
fieldDef.itemFields,
|
|
834
|
+
itemSchema,
|
|
767
835
|
spec,
|
|
768
836
|
data,
|
|
769
837
|
computed,
|
|
@@ -775,9 +843,18 @@ function validateArray(path, value, fieldDef, spec, data, computed, visibility,
|
|
|
775
843
|
}
|
|
776
844
|
return errors;
|
|
777
845
|
}
|
|
778
|
-
function validateArrayItem(arrayPath, index, item, itemFields, spec, data, computed, visibility, onlyVisible) {
|
|
846
|
+
function validateArrayItem(arrayPath, index, item, itemFields, itemSchema, spec, data, computed, visibility, onlyVisible) {
|
|
779
847
|
const errors = [];
|
|
780
|
-
|
|
848
|
+
const objectSchema = (itemSchema == null ? void 0 : itemSchema.type) === "object" ? itemSchema : void 0;
|
|
849
|
+
const schemaProperties = (objectSchema == null ? void 0 : objectSchema.properties) ?? {};
|
|
850
|
+
const schemaRequired = new Set((objectSchema == null ? void 0 : objectSchema.required) ?? []);
|
|
851
|
+
const allFieldNames = /* @__PURE__ */ new Set([
|
|
852
|
+
...Object.keys(itemFields ?? {}),
|
|
853
|
+
...Object.keys(schemaProperties)
|
|
854
|
+
]);
|
|
855
|
+
for (const fieldName of allFieldNames) {
|
|
856
|
+
const fieldDef = itemFields == null ? void 0 : itemFields[fieldName];
|
|
857
|
+
const fieldSchema = schemaProperties[fieldName];
|
|
781
858
|
const itemFieldPath = `${arrayPath}[${index}].${fieldName}`;
|
|
782
859
|
if (onlyVisible && visibility[itemFieldPath] === false) {
|
|
783
860
|
continue;
|
|
@@ -791,15 +868,26 @@ function validateArrayItem(arrayPath, index, item, itemFields, spec, data, compu
|
|
|
791
868
|
itemIndex: index,
|
|
792
869
|
value
|
|
793
870
|
};
|
|
794
|
-
const isRequired2 = fieldDef.requiredWhen ? evaluateBoolean(fieldDef.requiredWhen, context) :
|
|
871
|
+
const isRequired2 = (fieldDef == null ? void 0 : fieldDef.requiredWhen) ? evaluateBoolean(fieldDef.requiredWhen, context) : schemaRequired.has(fieldName);
|
|
795
872
|
if (isRequired2 && isEmpty(value)) {
|
|
796
873
|
errors.push({
|
|
797
874
|
field: itemFieldPath,
|
|
798
|
-
message: fieldDef.label ? `${fieldDef.label} is required` : "This field is required",
|
|
875
|
+
message: (fieldDef == null ? void 0 : fieldDef.label) ? `${fieldDef.label} is required` : "This field is required",
|
|
799
876
|
severity: "error"
|
|
800
877
|
});
|
|
801
878
|
}
|
|
802
|
-
if (
|
|
879
|
+
if (!isEmpty(value) && fieldSchema) {
|
|
880
|
+
const typeError = validateType(
|
|
881
|
+
itemFieldPath,
|
|
882
|
+
value,
|
|
883
|
+
fieldSchema,
|
|
884
|
+
fieldDef ?? { label: fieldName }
|
|
885
|
+
);
|
|
886
|
+
if (typeError) {
|
|
887
|
+
errors.push(typeError);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
if ((fieldDef == null ? void 0 : fieldDef.validations) && !isEmpty(value)) {
|
|
803
891
|
const customErrors = validateCustomRules(itemFieldPath, fieldDef.validations, context);
|
|
804
892
|
errors.push(...customErrors);
|
|
805
893
|
}
|
|
@@ -840,6 +928,8 @@ export {
|
|
|
840
928
|
getVisibility,
|
|
841
929
|
isFieldVisible,
|
|
842
930
|
getPageVisibility,
|
|
931
|
+
getOptionsVisibility,
|
|
932
|
+
getVisibleOptions,
|
|
843
933
|
getRequired,
|
|
844
934
|
isRequired,
|
|
845
935
|
getEnabled,
|
|
@@ -847,4 +937,4 @@ export {
|
|
|
847
937
|
validate,
|
|
848
938
|
validateSingleField
|
|
849
939
|
};
|
|
850
|
-
//# sourceMappingURL=chunk-
|
|
940
|
+
//# sourceMappingURL=chunk-XS3BLVIZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/format/index.ts","../src/engine/calculate.ts","../src/engine/visibility.ts","../src/engine/required.ts","../src/engine/enabled.ts","../src/engine/validate.ts"],"sourcesContent":["/**\n * Format utilities for computed field values\n *\n * Provides constants, validation helpers, and formatting functions\n * for computed field format specifications.\n */\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * Supported format specifiers (excluding decimal which uses pattern)\n */\nexport const SUPPORTED_FORMATS = [\n \"currency\",\n \"percent\",\n \"date\",\n \"datetime\",\n] as const;\n\n/**\n * Pattern for decimal format: decimal(N) where N is number of decimal places\n */\nexport const DECIMAL_FORMAT_PATTERN = /^decimal\\((\\d+)\\)$/;\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Supported format type (excluding decimal pattern)\n */\nexport type SupportedFormat = (typeof SUPPORTED_FORMATS)[number];\n\n/**\n * Options for value formatting\n */\nexport interface FormatOptions {\n /** Locale for number/date formatting (default: \"en-US\") */\n locale?: string;\n /** Currency code for currency format (default: \"USD\") */\n currency?: string;\n /** String to display for null/undefined values (default: returns String(value)) */\n nullDisplay?: string;\n}\n\n// ============================================================================\n// Validation Helpers\n// ============================================================================\n\n/**\n * Check if a format string is valid\n *\n * Valid formats:\n * - \"currency\"\n * - \"percent\"\n * - \"date\"\n * - \"datetime\"\n * - \"decimal(N)\" where N is a non-negative integer\n *\n * @param format - Format string to validate\n * @returns True if format is valid\n */\nexport function isValidFormat(format: string): boolean {\n if (SUPPORTED_FORMATS.includes(format as SupportedFormat)) {\n return true;\n }\n return DECIMAL_FORMAT_PATTERN.test(format);\n}\n\n/**\n * Parse decimal format and extract precision\n *\n * @param format - Format string (e.g., \"decimal(2)\")\n * @returns Number of decimal places, or null if not a decimal format\n */\nexport function parseDecimalFormat(format: string): number | null {\n const match = format.match(DECIMAL_FORMAT_PATTERN);\n if (match) {\n return parseInt(match[1], 10);\n }\n return null;\n}\n\n// ============================================================================\n// Formatting\n// ============================================================================\n\n/**\n * Format a value according to a format specification\n *\n * Supported formats:\n * - decimal(n) - Number with n decimal places\n * - currency - Number formatted as currency\n * - percent - Number formatted as percentage\n * - date - Date formatted as local date string\n * - datetime - Date formatted as local date/time string\n * - (none/unknown) - Default string conversion\n *\n * @param value - Value to format\n * @param format - Format specification\n * @param options - Formatting options\n * @returns Formatted string\n *\n * @example\n * formatValue(1234.567, \"decimal(2)\") // \"1234.57\"\n * formatValue(1234.5, \"currency\") // \"$1,234.50\"\n * formatValue(0.156, \"percent\") // \"15.6%\"\n * formatValue(null, undefined, { nullDisplay: \"—\" }) // \"—\"\n */\nexport function formatValue(\n value: unknown,\n format?: string,\n options?: FormatOptions\n): string {\n const { locale = \"en-US\", currency = \"USD\", nullDisplay } = options ?? {};\n\n // Handle null/undefined\n if (value === null || value === undefined) {\n if (nullDisplay !== undefined) {\n return nullDisplay;\n }\n return String(value);\n }\n\n // No format specified - default string conversion\n if (!format) {\n return String(value);\n }\n\n // Handle decimal(n) format\n const decimals = parseDecimalFormat(format);\n if (decimals !== null) {\n return typeof value === \"number\" ? value.toFixed(decimals) : String(value);\n }\n\n // Handle currency format\n if (format === \"currency\") {\n return typeof value === \"number\"\n ? new Intl.NumberFormat(locale, {\n style: \"currency\",\n currency: currency,\n }).format(value)\n : String(value);\n }\n\n // Handle percent format\n if (format === \"percent\") {\n return typeof value === \"number\"\n ? new Intl.NumberFormat(locale, {\n style: \"percent\",\n minimumFractionDigits: 0,\n maximumFractionDigits: 2,\n }).format(value)\n : String(value);\n }\n\n // Handle date format\n if (format === \"date\") {\n const date = value instanceof Date ? value : new Date(String(value));\n return !isNaN(date.getTime())\n ? new Intl.DateTimeFormat(locale).format(date)\n : String(value);\n }\n\n // Handle datetime format\n if (format === \"datetime\") {\n const date = value instanceof Date ? value : new Date(String(value));\n return !isNaN(date.getTime())\n ? new Intl.DateTimeFormat(locale, {\n dateStyle: \"short\",\n timeStyle: \"short\",\n }).format(date)\n : String(value);\n }\n\n // Unknown format - fallback to string conversion\n return String(value);\n}\n","/**\n * Calculation Engine\n *\n * Evaluates computed fields based on form data.\n * Computed values are derived from form data using FEEL expressions.\n */\n\nimport { evaluate } from \"../feel/index.js\";\nimport { formatValue, type FormatOptions } from \"../format/index.js\";\nimport type {\n Forma,\n ComputedField,\n EvaluationContext,\n CalculationResult,\n CalculationError,\n} from \"../types.js\";\n\n// ============================================================================\n// Main Function\n// ============================================================================\n\n/**\n * Calculate all computed values from form data\n *\n * Evaluates each computed field's FEEL expression and returns the results.\n * Errors are collected rather than thrown, allowing partial results.\n *\n * @param data - Current form data\n * @param spec - Form specification with computed fields\n * @returns Computed values and any calculation errors\n *\n * @example\n * const spec = {\n * computed: {\n * bmi: {\n * expression: \"weight / (height / 100) ** 2\",\n * label: \"BMI\",\n * format: \"decimal(1)\"\n * },\n * isObese: {\n * expression: \"$computed.bmi >= 30\"\n * }\n * }\n * };\n *\n * const result = calculate({ weight: 85, height: 175 }, spec);\n * // => { values: { bmi: 27.76, isObese: false }, errors: [] }\n */\nexport function calculate(\n data: Record<string, unknown>,\n spec: Forma\n): Record<string, unknown> {\n const result = calculateWithErrors(data, spec);\n return result.values;\n}\n\n/**\n * Calculate computed values with error reporting\n *\n * Same as calculate() but also returns any errors that occurred.\n *\n * @param data - Current form data\n * @param spec - Form specification\n * @returns Values and errors\n */\nexport function calculateWithErrors(\n data: Record<string, unknown>,\n spec: Forma\n): CalculationResult {\n if (!spec.computed) {\n return { values: {}, errors: [] };\n }\n\n const values: Record<string, unknown> = {};\n const errors: CalculationError[] = [];\n\n // Get computation order (handles dependencies)\n const orderedFields = getComputationOrder(spec.computed);\n\n // Evaluate each computed field in dependency order\n for (const fieldName of orderedFields) {\n const fieldDef = spec.computed[fieldName];\n if (!fieldDef) continue;\n\n const result = evaluateComputedField(\n fieldName,\n fieldDef,\n data,\n values, // Pass already-computed values for dependencies\n spec.referenceData // Pass reference data for lookups\n );\n\n if (result.success) {\n values[fieldName] = result.value;\n } else {\n errors.push({\n field: fieldName,\n message: result.error,\n expression: fieldDef.expression,\n });\n // Set to null so dependent fields can still evaluate\n values[fieldName] = null;\n }\n }\n\n return { values, errors };\n}\n\n// ============================================================================\n// Field Evaluation\n// ============================================================================\n\ninterface ComputeSuccess {\n success: true;\n value: unknown;\n}\n\ninterface ComputeFailure {\n success: false;\n error: string;\n}\n\ntype ComputeResult = ComputeSuccess | ComputeFailure;\n\n/**\n * Evaluate a single computed field\n */\nfunction evaluateComputedField(\n _name: string,\n fieldDef: ComputedField,\n data: Record<string, unknown>,\n computedSoFar: Record<string, unknown>,\n referenceData?: Record<string, unknown>\n): ComputeResult {\n // Check if any referenced computed field is null - propagate null to dependents\n // This prevents issues like: bmi is null, but bmiCategory still evaluates to \"obese\"\n // because `null < 18.5` is false in comparisons\n const referencedComputed = findComputedReferences(fieldDef.expression);\n for (const ref of referencedComputed) {\n if (computedSoFar[ref] === null) {\n return {\n success: true,\n value: null,\n };\n }\n }\n\n const context: EvaluationContext = {\n data,\n computed: computedSoFar,\n referenceData,\n };\n\n const result = evaluate(fieldDef.expression, context);\n\n if (!result.success) {\n return {\n success: false,\n error: result.error,\n };\n }\n\n // Treat NaN and Infinity as null - prevents unexpected behavior in conditional expressions\n // (e.g., NaN < 18.5 is false, causing fallthrough in if-else chains)\n if (typeof result.value === \"number\" && (!Number.isFinite(result.value))) {\n return {\n success: true,\n value: null,\n };\n }\n\n return {\n success: true,\n value: result.value,\n };\n}\n\n/**\n * Find computed field references in an expression (e.g., computed.bmi)\n */\nfunction findComputedReferences(expression: string): string[] {\n const refs: string[] = [];\n const regex = /computed\\.(\\w+)/g;\n let match;\n while ((match = regex.exec(expression)) !== null) {\n refs.push(match[1]);\n }\n return refs;\n}\n\n// ============================================================================\n// Dependency Resolution\n// ============================================================================\n\n/**\n * Determine the order to evaluate computed fields based on dependencies\n *\n * Computed fields can reference other computed fields via $computed.name.\n * This function performs topological sort to ensure dependencies are\n * evaluated first.\n */\nfunction getComputationOrder(\n computed: Record<string, ComputedField>\n): string[] {\n const fieldNames = Object.keys(computed);\n\n // Build dependency graph\n const deps = new Map<string, Set<string>>();\n for (const name of fieldNames) {\n deps.set(name, findComputedDependencies(computed[name].expression, fieldNames));\n }\n\n // Topological sort\n const sorted: string[] = [];\n const visited = new Set<string>();\n const visiting = new Set<string>();\n\n function visit(name: string): void {\n if (visited.has(name)) return;\n if (visiting.has(name)) {\n // Circular dependency - just add it and let evaluation fail gracefully\n console.warn(`Circular dependency detected in computed field: ${name}`);\n sorted.push(name);\n visited.add(name);\n return;\n }\n\n visiting.add(name);\n\n const fieldDeps = deps.get(name) ?? new Set();\n for (const dep of fieldDeps) {\n visit(dep);\n }\n\n visiting.delete(name);\n visited.add(name);\n sorted.push(name);\n }\n\n for (const name of fieldNames) {\n visit(name);\n }\n\n return sorted;\n}\n\n/**\n * Find which computed fields are referenced in an expression\n */\nfunction findComputedDependencies(\n expression: string,\n availableFields: string[]\n): Set<string> {\n const deps = new Set<string>();\n\n // Look for computed.fieldName patterns (without $ prefix)\n const regex = /computed\\.(\\w+)/g;\n let match;\n while ((match = regex.exec(expression)) !== null) {\n const fieldName = match[1];\n if (availableFields.includes(fieldName)) {\n deps.add(fieldName);\n }\n }\n\n return deps;\n}\n\n// ============================================================================\n// Formatted Output\n// ============================================================================\n\n/**\n * Get a computed value formatted according to its format specification\n *\n * @param fieldName - Name of the computed field\n * @param data - Current form data\n * @param spec - Form specification\n * @param options - Formatting options (locale, currency, nullDisplay)\n * @returns Formatted string or null if field not found or value is null/undefined\n */\nexport function getFormattedValue(\n fieldName: string,\n data: Record<string, unknown>,\n spec: Forma,\n options?: FormatOptions\n): string | null {\n if (!spec.computed?.[fieldName]) {\n return null;\n }\n\n const fieldDef = spec.computed[fieldName];\n const computed = calculate(data, spec);\n const value = computed[fieldName];\n\n if (value === null || value === undefined) {\n // If nullDisplay is specified, use formatValue to get it\n if (options?.nullDisplay !== undefined) {\n return formatValue(value, fieldDef.format, options);\n }\n return null;\n }\n\n return formatValue(value, fieldDef.format, options);\n}\n\n// ============================================================================\n// Single Field Calculation\n// ============================================================================\n\n/**\n * Calculate a single computed field\n *\n * @param fieldName - Name of the computed field\n * @param data - Current form data\n * @param spec - Form specification\n * @returns Computed value or null if calculation failed\n */\nexport function calculateField(\n fieldName: string,\n data: Record<string, unknown>,\n spec: Forma\n): unknown {\n const computed = calculate(data, spec);\n return computed[fieldName] ?? null;\n}\n","/**\n * Visibility Engine\n *\n * Determines which fields should be visible based on form data\n * and Forma visibility rules.\n */\n\nimport { evaluateBoolean } from \"../feel/index.js\";\nimport type {\n Forma,\n FieldDefinition,\n EvaluationContext,\n VisibilityResult,\n SelectOption,\n} from \"../types.js\";\nimport { calculate } from \"./calculate.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface VisibilityOptions {\n /** Pre-calculated computed values (avoids recalculation) */\n computed?: Record<string, unknown>;\n}\n\n/**\n * Result of option visibility computation.\n * Maps field paths to their visible options.\n *\n * For array items, paths are like \"items[0].category\", \"items[1].category\", etc.\n */\nexport interface OptionsVisibilityResult {\n readonly [fieldPath: string]: readonly SelectOption[];\n}\n\n// ============================================================================\n// Internal Helpers\n// ============================================================================\n\n/**\n * Filter options by evaluating visibleWhen expressions against a context.\n * This is the core filtering logic used by both batch and individual computation.\n */\nfunction filterOptionsByContext(\n options: readonly SelectOption[],\n context: EvaluationContext\n): SelectOption[] {\n return options.filter((option) => {\n if (!option.visibleWhen) return true;\n try {\n return evaluateBoolean(option.visibleWhen, context);\n } catch {\n // Invalid expression - hide the option (fail closed)\n return false;\n }\n });\n}\n\n/**\n * Process array item fields and compute their option visibility.\n */\nfunction processArrayItemOptions(\n arrayPath: string,\n fieldDef: FieldDefinition,\n arrayData: readonly unknown[],\n baseContext: EvaluationContext,\n result: Record<string, SelectOption[]>\n): void {\n if (!fieldDef.itemFields) return;\n\n for (let i = 0; i < arrayData.length; i++) {\n const item = (arrayData[i] ?? {}) as Record<string, unknown>;\n const itemContext: EvaluationContext = {\n ...baseContext,\n item,\n itemIndex: i,\n };\n\n for (const [itemFieldName, itemFieldDef] of Object.entries(fieldDef.itemFields)) {\n if (itemFieldDef.options && itemFieldDef.options.length > 0) {\n const itemFieldPath = `${arrayPath}[${i}].${itemFieldName}`;\n result[itemFieldPath] = filterOptionsByContext(itemFieldDef.options, itemContext);\n }\n }\n }\n}\n\n// ============================================================================\n// Field Visibility\n// ============================================================================\n\n/**\n * Determine visibility for all fields in a form\n *\n * Returns a map of field paths to boolean visibility states.\n * Fields without visibleWhen expressions are always visible.\n *\n * @param data - Current form data\n * @param spec - Form specification\n * @param options - Optional pre-calculated computed values\n * @returns Map of field paths to visibility states\n *\n * @example\n * const visibility = getVisibility(\n * { age: 21, hasLicense: true },\n * forma\n * );\n * // => { age: true, hasLicense: true, vehicleType: true, ... }\n */\nexport function getVisibility(\n data: Record<string, unknown>,\n spec: Forma,\n options: VisibilityOptions = {}\n): VisibilityResult {\n const computed = options.computed ?? calculate(data, spec);\n\n const baseContext: EvaluationContext = {\n data,\n computed,\n referenceData: spec.referenceData,\n };\n\n const result: VisibilityResult = {};\n\n for (const fieldPath of spec.fieldOrder) {\n const fieldDef = spec.fields[fieldPath];\n if (fieldDef) {\n evaluateFieldVisibility(fieldPath, fieldDef, data, baseContext, result);\n }\n }\n\n return result;\n}\n\n/**\n * Evaluate visibility for a single field and its nested fields\n */\nfunction evaluateFieldVisibility(\n path: string,\n fieldDef: FieldDefinition,\n data: Record<string, unknown>,\n context: EvaluationContext,\n result: VisibilityResult\n): void {\n if (fieldDef.visibleWhen) {\n result[path] = evaluateBoolean(fieldDef.visibleWhen, context);\n } else {\n result[path] = true;\n }\n\n if (!result[path]) {\n return;\n }\n\n if (fieldDef.itemFields) {\n const arrayData = data[path];\n if (Array.isArray(arrayData)) {\n evaluateArrayItemVisibility(path, fieldDef, arrayData, context, result);\n }\n }\n}\n\n/**\n * Evaluate visibility for array item fields\n */\nfunction evaluateArrayItemVisibility(\n arrayPath: string,\n fieldDef: FieldDefinition,\n arrayData: unknown[],\n baseContext: EvaluationContext,\n result: VisibilityResult\n): void {\n if (!fieldDef.itemFields) return;\n\n for (let i = 0; i < arrayData.length; i++) {\n const item = arrayData[i] as Record<string, unknown>;\n const itemContext: EvaluationContext = {\n ...baseContext,\n item,\n itemIndex: i,\n };\n\n for (const [fieldName, itemFieldDef] of Object.entries(fieldDef.itemFields)) {\n const itemFieldPath = `${arrayPath}[${i}].${fieldName}`;\n\n if (itemFieldDef.visibleWhen) {\n result[itemFieldPath] = evaluateBoolean(itemFieldDef.visibleWhen, itemContext);\n } else {\n result[itemFieldPath] = true;\n }\n }\n }\n}\n\n/**\n * Check if a single field is visible\n *\n * Useful for checking visibility of one field without computing all.\n */\nexport function isFieldVisible(\n fieldPath: string,\n data: Record<string, unknown>,\n spec: Forma,\n options: VisibilityOptions = {}\n): boolean {\n const fieldDef = spec.fields[fieldPath];\n if (!fieldDef) {\n return true;\n }\n\n if (!fieldDef.visibleWhen) {\n return true;\n }\n\n const computed = options.computed ?? calculate(data, spec);\n const context: EvaluationContext = {\n data,\n computed,\n referenceData: spec.referenceData,\n };\n\n return evaluateBoolean(fieldDef.visibleWhen, context);\n}\n\n// ============================================================================\n// Page Visibility\n// ============================================================================\n\n/**\n * Determine which pages are visible in a wizard form\n */\nexport function getPageVisibility(\n data: Record<string, unknown>,\n spec: Forma,\n options: VisibilityOptions = {}\n): Record<string, boolean> {\n if (!spec.pages) {\n return {};\n }\n\n const computed = options.computed ?? calculate(data, spec);\n const context: EvaluationContext = {\n data,\n computed,\n referenceData: spec.referenceData,\n };\n\n const result: Record<string, boolean> = {};\n\n for (const page of spec.pages) {\n if (page.visibleWhen) {\n result[page.id] = evaluateBoolean(page.visibleWhen, context);\n } else {\n result[page.id] = true;\n }\n }\n\n return result;\n}\n\n// ============================================================================\n// Option Visibility - Batch Computation\n// ============================================================================\n\n/**\n * Compute visible options for ALL select/multiselect fields in a form.\n *\n * This is the primary API for option visibility - designed to be called once\n * and memoized (e.g., in a useMemo hook). Returns a map of field paths to\n * their visible options.\n *\n * Handles both top-level fields and array item fields. For array items,\n * paths include the index: \"items[0].category\", \"items[1].category\", etc.\n *\n * @param data - Current form data\n * @param spec - Form specification\n * @param options - Optional pre-calculated computed values\n * @returns Map of field paths to visible SelectOption arrays\n *\n * @example\n * // In a React component:\n * const optionsVisibility = useMemo(\n * () => getOptionsVisibility(data, spec, { computed }),\n * [data, spec, computed]\n * );\n *\n * // Access visible options for a field:\n * const departmentOptions = optionsVisibility[\"department\"] ?? [];\n * const itemCategoryOptions = optionsVisibility[\"items[0].category\"] ?? [];\n */\nexport function getOptionsVisibility(\n data: Record<string, unknown>,\n spec: Forma,\n options: VisibilityOptions = {}\n): OptionsVisibilityResult {\n const computed = options.computed ?? calculate(data, spec);\n const result: Record<string, SelectOption[]> = {};\n\n const baseContext: EvaluationContext = {\n data,\n computed,\n referenceData: spec.referenceData,\n };\n\n for (const fieldPath of spec.fieldOrder) {\n const fieldDef = spec.fields[fieldPath];\n if (!fieldDef) continue;\n\n // Top-level fields with options\n if (fieldDef.options && fieldDef.options.length > 0) {\n result[fieldPath] = filterOptionsByContext(fieldDef.options, baseContext);\n }\n\n // Array item fields with options\n if (fieldDef.itemFields) {\n const arrayData = data[fieldPath];\n if (Array.isArray(arrayData)) {\n processArrayItemOptions(fieldPath, fieldDef, arrayData, baseContext, result);\n }\n }\n }\n\n return result;\n}\n\n// ============================================================================\n// Option Visibility - Individual Computation (Utility)\n// ============================================================================\n\n/**\n * Filter select options for a single field.\n *\n * This is a utility function for ad-hoc option filtering. For form rendering,\n * prefer using `getOptionsVisibility()` which computes all options at once\n * and can be memoized.\n *\n * @param options - Select options to filter\n * @param data - Current form data\n * @param spec - Form specification (for referenceData)\n * @param context - Optional computed values and array item context\n * @returns Filtered array of visible options\n *\n * @example\n * // Ad-hoc filtering for a single field\n * const visibleOptions = getVisibleOptions(\n * fieldDef.options,\n * formData,\n * spec,\n * { computed, item: arrayItem, itemIndex: 0 }\n * );\n */\nexport function getVisibleOptions(\n options: SelectOption[] | undefined,\n data: Record<string, unknown>,\n spec: Forma,\n context: {\n computed?: Record<string, unknown>;\n item?: Record<string, unknown>;\n itemIndex?: number;\n } = {}\n): SelectOption[] {\n if (!options || options.length === 0) return [];\n\n const computed = context.computed ?? calculate(data, spec);\n\n const evalContext: EvaluationContext = {\n data,\n computed,\n referenceData: spec.referenceData,\n item: context.item,\n itemIndex: context.itemIndex,\n };\n\n return filterOptionsByContext(options, evalContext);\n}\n","/**\n * Required Fields Engine\n *\n * Determines which fields are currently required based on\n * conditional requiredWhen expressions and schema required array.\n */\n\nimport { evaluateBoolean } from \"../feel/index.js\";\nimport type {\n Forma,\n FieldDefinition,\n EvaluationContext,\n RequiredFieldsResult,\n} from \"../types.js\";\nimport { calculate } from \"./calculate.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface RequiredOptions {\n /** Pre-calculated computed values */\n computed?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Main Function\n// ============================================================================\n\n/**\n * Determine which fields are currently required\n *\n * Returns a map of field paths to boolean required states.\n * Evaluates requiredWhen expressions for conditional requirements.\n *\n * @param data - Current form data\n * @param spec - Form specification\n * @param options - Optional pre-calculated computed values\n * @returns Map of field paths to required states\n *\n * @example\n * const required = getRequired(\n * { hasInsurance: true },\n * forma\n * );\n * // => { hasInsurance: true, insuranceProvider: true, policyNumber: true }\n */\nexport function getRequired(\n data: Record<string, unknown>,\n spec: Forma,\n options: RequiredOptions = {}\n): RequiredFieldsResult {\n const computed = options.computed ?? calculate(data, spec);\n const context: EvaluationContext = {\n data,\n computed,\n referenceData: spec.referenceData,\n };\n\n const result: RequiredFieldsResult = {};\n\n // Evaluate each field's required status\n for (const fieldPath of spec.fieldOrder) {\n const fieldDef = spec.fields[fieldPath];\n if (fieldDef) {\n result[fieldPath] = isFieldRequired(fieldPath, fieldDef, spec, context);\n }\n }\n\n // Also check array item fields\n for (const [fieldPath, fieldDef] of Object.entries(spec.fields)) {\n if (fieldDef.itemFields) {\n const arrayData = data[fieldPath];\n if (Array.isArray(arrayData)) {\n for (let i = 0; i < arrayData.length; i++) {\n const item = arrayData[i] as Record<string, unknown>;\n const itemContext: EvaluationContext = {\n data,\n computed,\n referenceData: spec.referenceData,\n item,\n itemIndex: i,\n };\n\n for (const [itemFieldName, itemFieldDef] of Object.entries(fieldDef.itemFields)) {\n const itemFieldPath = `${fieldPath}[${i}].${itemFieldName}`;\n result[itemFieldPath] = isFieldRequired(\n itemFieldPath,\n itemFieldDef,\n spec,\n itemContext\n );\n }\n }\n }\n }\n }\n\n return result;\n}\n\n// ============================================================================\n// Field Required Check\n// ============================================================================\n\n/**\n * Check if a single field is required based on requiredWhen or schema\n * @internal Exported for use by validate.ts\n */\nexport function isFieldRequired(\n fieldPath: string,\n fieldDef: FieldDefinition,\n spec: Forma,\n context: EvaluationContext\n): boolean {\n // If field has requiredWhen, evaluate it\n if (fieldDef.requiredWhen) {\n return evaluateBoolean(fieldDef.requiredWhen, context);\n }\n\n // Otherwise, check schema required array\n return spec.schema.required?.includes(fieldPath) ?? false;\n}\n\n/**\n * Check if a single field is currently required\n *\n * @param fieldPath - Path to the field\n * @param data - Current form data\n * @param spec - Form specification\n * @returns True if the field is required\n */\nexport function isRequired(\n fieldPath: string,\n data: Record<string, unknown>,\n spec: Forma\n): boolean {\n const fieldDef = spec.fields[fieldPath];\n if (!fieldDef) {\n return spec.schema.required?.includes(fieldPath) ?? false;\n }\n\n const computed = calculate(data, spec);\n const context: EvaluationContext = {\n data,\n computed,\n referenceData: spec.referenceData,\n };\n\n return isFieldRequired(fieldPath, fieldDef, spec, context);\n}\n","/**\n * Enabled Fields Engine\n *\n * Determines which fields are currently enabled (editable) based on\n * conditional enabledWhen expressions.\n */\n\nimport { evaluateBoolean } from \"../feel/index.js\";\nimport type {\n Forma,\n FieldDefinition,\n EvaluationContext,\n EnabledResult,\n} from \"../types.js\";\nimport { calculate } from \"./calculate.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface EnabledOptions {\n /** Pre-calculated computed values */\n computed?: Record<string, unknown>;\n}\n\n// ============================================================================\n// Main Function\n// ============================================================================\n\n/**\n * Determine which fields are currently enabled (editable)\n *\n * Returns a map of field paths to boolean enabled states.\n * Fields without enabledWhen expressions are always enabled.\n *\n * @param data - Current form data\n * @param spec - Form specification\n * @param options - Optional pre-calculated computed values\n * @returns Map of field paths to enabled states\n *\n * @example\n * const enabled = getEnabled(\n * { isLocked: true },\n * forma\n * );\n * // => { isLocked: true, lockedField: false, ... }\n */\nexport function getEnabled(\n data: Record<string, unknown>,\n spec: Forma,\n options: EnabledOptions = {}\n): EnabledResult {\n const computed = options.computed ?? calculate(data, spec);\n const context: EvaluationContext = {\n data,\n computed,\n referenceData: spec.referenceData,\n };\n\n const result: EnabledResult = {};\n\n // Evaluate each field's enabled status\n for (const fieldPath of spec.fieldOrder) {\n const fieldDef = spec.fields[fieldPath];\n if (fieldDef) {\n result[fieldPath] = isFieldEnabled(fieldDef, context);\n }\n }\n\n // Also check array item fields\n for (const [fieldPath, fieldDef] of Object.entries(spec.fields)) {\n if (fieldDef.itemFields) {\n const arrayData = data[fieldPath];\n if (Array.isArray(arrayData)) {\n for (let i = 0; i < arrayData.length; i++) {\n const item = arrayData[i] as Record<string, unknown>;\n const itemContext: EvaluationContext = {\n data,\n computed,\n referenceData: spec.referenceData,\n item,\n itemIndex: i,\n };\n\n for (const [itemFieldName, itemFieldDef] of Object.entries(fieldDef.itemFields)) {\n const itemFieldPath = `${fieldPath}[${i}].${itemFieldName}`;\n result[itemFieldPath] = isFieldEnabled(itemFieldDef, itemContext);\n }\n }\n }\n }\n }\n\n return result;\n}\n\n// ============================================================================\n// Field Enabled Check\n// ============================================================================\n\n/**\n * Check if a field is enabled based on its definition\n */\nfunction isFieldEnabled(\n fieldDef: FieldDefinition,\n context: EvaluationContext\n): boolean {\n // If field has enabledWhen, evaluate it\n if (fieldDef.enabledWhen) {\n return evaluateBoolean(fieldDef.enabledWhen, context);\n }\n\n // No condition = always enabled\n return true;\n}\n\n/**\n * Check if a single field is currently enabled\n *\n * @param fieldPath - Path to the field\n * @param data - Current form data\n * @param spec - Form specification\n * @returns True if the field is enabled\n */\nexport function isEnabled(\n fieldPath: string,\n data: Record<string, unknown>,\n spec: Forma\n): boolean {\n const fieldDef = spec.fields[fieldPath];\n if (!fieldDef) {\n return true; // Unknown fields are enabled by default\n }\n\n if (!fieldDef.enabledWhen) {\n return true; // No condition = always enabled\n }\n\n const computed = calculate(data, spec);\n const context: EvaluationContext = {\n data,\n computed,\n referenceData: spec.referenceData,\n };\n\n return evaluateBoolean(fieldDef.enabledWhen, context);\n}\n","/**\n * Validation Engine\n *\n * Validates form data against Forma rules including:\n * - JSON Schema type validation\n * - Required field validation (with conditional requiredWhen)\n * - Custom FEEL validation rules\n * - Array item validation\n */\n\nimport { evaluateBoolean } from \"../feel/index.js\";\nimport type {\n Forma,\n FieldDefinition,\n ValidationRule,\n EvaluationContext,\n ValidationResult,\n FieldError,\n JSONSchemaProperty,\n} from \"../types.js\";\nimport { calculate } from \"./calculate.js\";\nimport { getVisibility } from \"./visibility.js\";\nimport { isFieldRequired } from \"./required.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ValidateOptions {\n /** Pre-calculated computed values */\n computed?: Record<string, unknown>;\n /** Pre-calculated visibility */\n visibility?: Record<string, boolean>;\n /** Only validate visible fields (default: true) */\n onlyVisible?: boolean;\n}\n\n// ============================================================================\n// Main Function\n// ============================================================================\n\n/**\n * Validate form data against a Forma\n *\n * Performs comprehensive validation including:\n * - Required field checks (respecting conditional requiredWhen)\n * - JSON Schema type validation\n * - Custom FEEL validation rules\n * - Array min/max items validation\n * - Array item field validation\n *\n * By default, only visible fields are validated.\n *\n * @param data - Current form data\n * @param spec - Form specification\n * @param options - Validation options\n * @returns Validation result with valid flag and errors array\n *\n * @example\n * const result = validate(\n * { name: \"\", age: 15 },\n * forma\n * );\n * // => {\n * // valid: false,\n * // errors: [\n * // { field: \"name\", message: \"Name is required\", severity: \"error\" },\n * // { field: \"age\", message: \"Must be 18 or older\", severity: \"error\" }\n * // ]\n * // }\n */\nexport function validate(\n data: Record<string, unknown>,\n spec: Forma,\n options: ValidateOptions = {}\n): ValidationResult {\n const { onlyVisible = true } = options;\n\n // Calculate computed values\n const computed = options.computed ?? calculate(data, spec);\n\n // Calculate visibility\n const visibility = options.visibility ?? getVisibility(data, spec, { computed });\n\n // Collect errors\n const errors: FieldError[] = [];\n\n // Validate each field\n for (const fieldPath of spec.fieldOrder) {\n const fieldDef = spec.fields[fieldPath];\n if (!fieldDef) continue;\n\n // Skip hidden fields if onlyVisible is true\n if (onlyVisible && visibility[fieldPath] === false) {\n continue;\n }\n\n // Get schema property for type validation\n const schemaProperty = spec.schema.properties[fieldPath];\n\n // Validate this field\n const fieldErrors = validateField(\n fieldPath,\n data[fieldPath],\n fieldDef,\n schemaProperty,\n spec,\n data,\n computed,\n visibility,\n onlyVisible\n );\n\n errors.push(...fieldErrors);\n }\n\n return {\n valid: errors.filter((e) => e.severity === \"error\").length === 0,\n errors,\n };\n}\n\n// ============================================================================\n// Field Validation\n// ============================================================================\n\n/**\n * Validate a single field and its nested fields\n */\nfunction validateField(\n path: string,\n value: unknown,\n fieldDef: FieldDefinition,\n schemaProperty: JSONSchemaProperty | undefined,\n spec: Forma,\n data: Record<string, unknown>,\n computed: Record<string, unknown>,\n visibility: Record<string, boolean>,\n onlyVisible: boolean\n): FieldError[] {\n const errors: FieldError[] = [];\n const context: EvaluationContext = {\n data,\n computed,\n referenceData: spec.referenceData,\n value,\n };\n\n // 1. Required validation\n const required = isFieldRequired(path, fieldDef, spec, context);\n if (required && isEmpty(value)) {\n errors.push({\n field: path,\n message: fieldDef.label\n ? `${fieldDef.label} is required`\n : \"This field is required\",\n severity: \"error\",\n });\n }\n\n // 2. Type validation (only if value is present)\n if (!isEmpty(value) && schemaProperty) {\n const typeError = validateType(path, value, schemaProperty, fieldDef);\n if (typeError) {\n errors.push(typeError);\n }\n }\n\n // 3. Custom FEEL validation rules\n if (fieldDef.validations && !isEmpty(value)) {\n const customErrors = validateCustomRules(path, fieldDef.validations, context);\n errors.push(...customErrors);\n }\n\n // 4. Array validation\n if (Array.isArray(value)) {\n const arrayErrors = validateArray(\n path,\n value,\n fieldDef,\n schemaProperty,\n spec,\n data,\n computed,\n visibility,\n onlyVisible\n );\n errors.push(...arrayErrors);\n }\n\n return errors;\n}\n\n/**\n * Check if a value is empty\n */\nfunction isEmpty(value: unknown): boolean {\n if (value === null || value === undefined) return true;\n if (typeof value === \"string\" && value.trim() === \"\") return true;\n if (Array.isArray(value) && value.length === 0) return true;\n return false;\n}\n\n// ============================================================================\n// Type Validation\n// ============================================================================\n\n/**\n * Validate value against JSON Schema type\n */\nfunction validateType(\n path: string,\n value: unknown,\n schema: JSONSchemaProperty,\n fieldDef: FieldDefinition\n): FieldError | null {\n const label = fieldDef.label ?? path;\n\n switch (schema.type) {\n case \"string\": {\n if (typeof value !== \"string\") {\n return {\n field: path,\n message: `${label} must be a string`,\n severity: \"error\",\n };\n }\n\n // String-specific validations\n if (\"minLength\" in schema && schema.minLength !== undefined) {\n if (value.length < schema.minLength) {\n return {\n field: path,\n message: `${label} must be at least ${schema.minLength} characters`,\n severity: \"error\",\n };\n }\n }\n\n if (\"maxLength\" in schema && schema.maxLength !== undefined) {\n if (value.length > schema.maxLength) {\n return {\n field: path,\n message: `${label} must be no more than ${schema.maxLength} characters`,\n severity: \"error\",\n };\n }\n }\n\n if (\"pattern\" in schema && schema.pattern) {\n const regex = new RegExp(schema.pattern);\n if (!regex.test(value)) {\n return {\n field: path,\n message: `${label} format is invalid`,\n severity: \"error\",\n };\n }\n }\n\n if (\"enum\" in schema && schema.enum) {\n if (!schema.enum.includes(value)) {\n return {\n field: path,\n message: `${label} must be one of: ${schema.enum.join(\", \")}`,\n severity: \"error\",\n };\n }\n }\n\n if (\"format\" in schema && schema.format) {\n const formatError = validateFormat(path, value, schema.format, label);\n if (formatError) return formatError;\n }\n\n return null;\n }\n\n case \"number\":\n case \"integer\": {\n if (typeof value !== \"number\") {\n return {\n field: path,\n message: `${label} must be a number`,\n severity: \"error\",\n };\n }\n\n if (schema.type === \"integer\" && !Number.isInteger(value)) {\n return {\n field: path,\n message: `${label} must be a whole number`,\n severity: \"error\",\n };\n }\n\n if (\"minimum\" in schema && schema.minimum !== undefined) {\n if (value < schema.minimum) {\n return {\n field: path,\n message: `${label} must be at least ${schema.minimum}`,\n severity: \"error\",\n };\n }\n }\n\n if (\"maximum\" in schema && schema.maximum !== undefined) {\n if (value > schema.maximum) {\n return {\n field: path,\n message: `${label} must be no more than ${schema.maximum}`,\n severity: \"error\",\n };\n }\n }\n\n if (\"exclusiveMinimum\" in schema && schema.exclusiveMinimum !== undefined) {\n if (value <= schema.exclusiveMinimum) {\n return {\n field: path,\n message: `${label} must be greater than ${schema.exclusiveMinimum}`,\n severity: \"error\",\n };\n }\n }\n\n if (\"exclusiveMaximum\" in schema && schema.exclusiveMaximum !== undefined) {\n if (value >= schema.exclusiveMaximum) {\n return {\n field: path,\n message: `${label} must be less than ${schema.exclusiveMaximum}`,\n severity: \"error\",\n };\n }\n }\n\n if (\"multipleOf\" in schema && schema.multipleOf !== undefined) {\n const multipleOf = schema.multipleOf;\n // Use epsilon comparison to handle floating point precision issues\n const remainder = Math.abs(value % multipleOf);\n const isValid = remainder < 1e-10 || Math.abs(remainder - multipleOf) < 1e-10;\n if (!isValid) {\n return {\n field: path,\n message: `${label} must be a multiple of ${multipleOf}`,\n severity: \"error\",\n };\n }\n }\n\n return null;\n }\n\n case \"boolean\": {\n if (typeof value !== \"boolean\") {\n return {\n field: path,\n message: `${label} must be true or false`,\n severity: \"error\",\n };\n }\n return null;\n }\n\n case \"array\": {\n if (!Array.isArray(value)) {\n return {\n field: path,\n message: `${label} must be a list`,\n severity: \"error\",\n };\n }\n return null;\n }\n\n case \"object\": {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return {\n field: path,\n message: `${label} must be an object`,\n severity: \"error\",\n };\n }\n return null;\n }\n\n default:\n return null;\n }\n}\n\n/**\n * Validate string format\n */\nfunction validateFormat(\n path: string,\n value: string,\n format: string,\n label: string\n): FieldError | null {\n switch (format) {\n case \"email\": {\n // Simple email regex\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n if (!emailRegex.test(value)) {\n return {\n field: path,\n message: `${label} must be a valid email address`,\n severity: \"error\",\n };\n }\n return null;\n }\n\n case \"date\": {\n // ISO date format YYYY-MM-DD\n const dateRegex = /^\\d{4}-\\d{2}-\\d{2}$/;\n if (!dateRegex.test(value)) {\n return {\n field: path,\n message: `${label} must be a valid date`,\n severity: \"error\",\n };\n }\n // Verify the date is actually valid (e.g., not Feb 30)\n const parsed = new Date(value + \"T00:00:00Z\");\n if (isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== value) {\n return {\n field: path,\n message: `${label} must be a valid date`,\n severity: \"error\",\n };\n }\n return null;\n }\n\n case \"date-time\": {\n if (isNaN(Date.parse(value))) {\n return {\n field: path,\n message: `${label} must be a valid date and time`,\n severity: \"error\",\n };\n }\n return null;\n }\n\n case \"uri\": {\n try {\n new URL(value);\n return null;\n } catch {\n return {\n field: path,\n message: `${label} must be a valid URL`,\n severity: \"error\",\n };\n }\n }\n\n case \"uuid\": {\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n if (!uuidRegex.test(value)) {\n return {\n field: path,\n message: `${label} must be a valid UUID`,\n severity: \"error\",\n };\n }\n return null;\n }\n\n default:\n return null;\n }\n}\n\n// ============================================================================\n// Custom Rule Validation\n// ============================================================================\n\n/**\n * Validate custom FEEL validation rules\n */\nfunction validateCustomRules(\n path: string,\n rules: ValidationRule[],\n context: EvaluationContext\n): FieldError[] {\n const errors: FieldError[] = [];\n\n for (const rule of rules) {\n const isValid = evaluateBoolean(rule.rule, context);\n\n if (!isValid) {\n errors.push({\n field: path,\n message: rule.message,\n severity: rule.severity ?? \"error\",\n });\n }\n }\n\n return errors;\n}\n\n// ============================================================================\n// Array Validation\n// ============================================================================\n\n/**\n * Validate array field including items\n */\nfunction validateArray(\n path: string,\n value: unknown[],\n fieldDef: FieldDefinition,\n schemaProperty: JSONSchemaProperty | undefined,\n spec: Forma,\n data: Record<string, unknown>,\n computed: Record<string, unknown>,\n visibility: Record<string, boolean>,\n onlyVisible: boolean\n): FieldError[] {\n const errors: FieldError[] = [];\n const label = fieldDef.label ?? path;\n\n // Get array schema for minItems/maxItems fallback\n const arraySchema = schemaProperty?.type === \"array\" ? schemaProperty : undefined;\n\n // Check min/max items - fieldDef overrides schema\n const minItems = fieldDef.minItems ?? arraySchema?.minItems;\n const maxItems = fieldDef.maxItems ?? arraySchema?.maxItems;\n\n if (minItems !== undefined && value.length < minItems) {\n errors.push({\n field: path,\n message: `${label} must have at least ${minItems} items`,\n severity: \"error\",\n });\n }\n\n if (maxItems !== undefined && value.length > maxItems) {\n errors.push({\n field: path,\n message: `${label} must have no more than ${maxItems} items`,\n severity: \"error\",\n });\n }\n\n // Get item schema for nested validation\n const itemSchema = arraySchema?.items;\n\n // Validate each item's fields\n if (fieldDef.itemFields || itemSchema) {\n for (let i = 0; i < value.length; i++) {\n const item = value[i] as Record<string, unknown>;\n const itemErrors = validateArrayItem(\n path,\n i,\n item,\n fieldDef.itemFields,\n itemSchema,\n spec,\n data,\n computed,\n visibility,\n onlyVisible\n );\n errors.push(...itemErrors);\n }\n }\n\n return errors;\n}\n\n/**\n * Validate fields within a single array item\n */\nfunction validateArrayItem(\n arrayPath: string,\n index: number,\n item: Record<string, unknown>,\n itemFields: Record<string, FieldDefinition> | undefined,\n itemSchema: JSONSchemaProperty | undefined,\n spec: Forma,\n data: Record<string, unknown>,\n computed: Record<string, unknown>,\n visibility: Record<string, boolean>,\n onlyVisible: boolean\n): FieldError[] {\n const errors: FieldError[] = [];\n\n // Get object schema for item if available\n const objectSchema = itemSchema?.type === \"object\" ? itemSchema : undefined;\n const schemaProperties = objectSchema?.properties ?? {};\n const schemaRequired = new Set(objectSchema?.required ?? []);\n\n // Determine which fields to validate - union of itemFields and schema properties\n const allFieldNames = new Set([\n ...Object.keys(itemFields ?? {}),\n ...Object.keys(schemaProperties),\n ]);\n\n for (const fieldName of allFieldNames) {\n const fieldDef = itemFields?.[fieldName];\n const fieldSchema = schemaProperties[fieldName];\n const itemFieldPath = `${arrayPath}[${index}].${fieldName}`;\n\n // Skip hidden fields\n if (onlyVisible && visibility[itemFieldPath] === false) {\n continue;\n }\n\n const value = item[fieldName];\n const context: EvaluationContext = {\n data,\n computed,\n referenceData: spec.referenceData,\n item,\n itemIndex: index,\n value,\n };\n\n // Required check - fieldDef.requiredWhen overrides schema.required\n const isRequired = fieldDef?.requiredWhen\n ? evaluateBoolean(fieldDef.requiredWhen, context)\n : schemaRequired.has(fieldName);\n\n if (isRequired && isEmpty(value)) {\n errors.push({\n field: itemFieldPath,\n message: fieldDef?.label\n ? `${fieldDef.label} is required`\n : \"This field is required\",\n severity: \"error\",\n });\n }\n\n // Type validation from schema (only if value is present)\n if (!isEmpty(value) && fieldSchema) {\n const typeError = validateType(\n itemFieldPath,\n value,\n fieldSchema,\n fieldDef ?? { label: fieldName }\n );\n if (typeError) {\n errors.push(typeError);\n }\n }\n\n // Custom validations from fieldDef\n if (fieldDef?.validations && !isEmpty(value)) {\n const customErrors = validateCustomRules(itemFieldPath, fieldDef.validations, context);\n errors.push(...customErrors);\n }\n }\n\n return errors;\n}\n\n// ============================================================================\n// Single Field Validation\n// ============================================================================\n\n/**\n * Validate a single field\n *\n * @param fieldPath - Path to the field\n * @param data - Current form data\n * @param spec - Form specification\n * @returns Array of errors for this field\n */\nexport function validateSingleField(\n fieldPath: string,\n data: Record<string, unknown>,\n spec: Forma\n): FieldError[] {\n const fieldDef = spec.fields[fieldPath];\n if (!fieldDef) {\n return [];\n }\n\n const computed = calculate(data, spec);\n const visibility = getVisibility(data, spec, { computed });\n const schemaProperty = spec.schema.properties[fieldPath];\n\n return validateField(\n fieldPath,\n data[fieldPath],\n fieldDef,\n schemaProperty,\n spec,\n data,\n computed,\n visibility,\n true\n );\n}\n"],"mappings":"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","names":["isRequired"]}
|
package/dist/engine/index.cjs
CHANGED
|
@@ -28,9 +28,11 @@ __export(engine_exports, {
|
|
|
28
28
|
formatValue: () => formatValue,
|
|
29
29
|
getEnabled: () => getEnabled,
|
|
30
30
|
getFormattedValue: () => getFormattedValue,
|
|
31
|
+
getOptionsVisibility: () => getOptionsVisibility,
|
|
31
32
|
getPageVisibility: () => getPageVisibility,
|
|
32
33
|
getRequired: () => getRequired,
|
|
33
34
|
getVisibility: () => getVisibility,
|
|
35
|
+
getVisibleOptions: () => getVisibleOptions,
|
|
34
36
|
isEnabled: () => isEnabled,
|
|
35
37
|
isFieldVisible: () => isFieldVisible,
|
|
36
38
|
isRequired: () => isRequired,
|
|
@@ -314,6 +316,33 @@ function calculateField(fieldName, data, spec) {
|
|
|
314
316
|
}
|
|
315
317
|
|
|
316
318
|
// src/engine/visibility.ts
|
|
319
|
+
function filterOptionsByContext(options, context) {
|
|
320
|
+
return options.filter((option) => {
|
|
321
|
+
if (!option.visibleWhen) return true;
|
|
322
|
+
try {
|
|
323
|
+
return evaluateBoolean(option.visibleWhen, context);
|
|
324
|
+
} catch {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
function processArrayItemOptions(arrayPath, fieldDef, arrayData, baseContext, result) {
|
|
330
|
+
if (!fieldDef.itemFields) return;
|
|
331
|
+
for (let i = 0; i < arrayData.length; i++) {
|
|
332
|
+
const item = arrayData[i] ?? {};
|
|
333
|
+
const itemContext = {
|
|
334
|
+
...baseContext,
|
|
335
|
+
item,
|
|
336
|
+
itemIndex: i
|
|
337
|
+
};
|
|
338
|
+
for (const [itemFieldName, itemFieldDef] of Object.entries(fieldDef.itemFields)) {
|
|
339
|
+
if (itemFieldDef.options && itemFieldDef.options.length > 0) {
|
|
340
|
+
const itemFieldPath = `${arrayPath}[${i}].${itemFieldName}`;
|
|
341
|
+
result[itemFieldPath] = filterOptionsByContext(itemFieldDef.options, itemContext);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
317
346
|
function getVisibility(data, spec, options = {}) {
|
|
318
347
|
const computed = options.computed ?? calculate(data, spec);
|
|
319
348
|
const baseContext = {
|
|
@@ -401,6 +430,41 @@ function getPageVisibility(data, spec, options = {}) {
|
|
|
401
430
|
}
|
|
402
431
|
return result;
|
|
403
432
|
}
|
|
433
|
+
function getOptionsVisibility(data, spec, options = {}) {
|
|
434
|
+
const computed = options.computed ?? calculate(data, spec);
|
|
435
|
+
const result = {};
|
|
436
|
+
const baseContext = {
|
|
437
|
+
data,
|
|
438
|
+
computed,
|
|
439
|
+
referenceData: spec.referenceData
|
|
440
|
+
};
|
|
441
|
+
for (const fieldPath of spec.fieldOrder) {
|
|
442
|
+
const fieldDef = spec.fields[fieldPath];
|
|
443
|
+
if (!fieldDef) continue;
|
|
444
|
+
if (fieldDef.options && fieldDef.options.length > 0) {
|
|
445
|
+
result[fieldPath] = filterOptionsByContext(fieldDef.options, baseContext);
|
|
446
|
+
}
|
|
447
|
+
if (fieldDef.itemFields) {
|
|
448
|
+
const arrayData = data[fieldPath];
|
|
449
|
+
if (Array.isArray(arrayData)) {
|
|
450
|
+
processArrayItemOptions(fieldPath, fieldDef, arrayData, baseContext, result);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return result;
|
|
455
|
+
}
|
|
456
|
+
function getVisibleOptions(options, data, spec, context = {}) {
|
|
457
|
+
if (!options || options.length === 0) return [];
|
|
458
|
+
const computed = context.computed ?? calculate(data, spec);
|
|
459
|
+
const evalContext = {
|
|
460
|
+
data,
|
|
461
|
+
computed,
|
|
462
|
+
referenceData: spec.referenceData,
|
|
463
|
+
item: context.item,
|
|
464
|
+
itemIndex: context.itemIndex
|
|
465
|
+
};
|
|
466
|
+
return filterOptionsByContext(options, evalContext);
|
|
467
|
+
}
|
|
404
468
|
|
|
405
469
|
// src/engine/required.ts
|
|
406
470
|
function getRequired(data, spec, options = {}) {
|
|
@@ -585,11 +649,12 @@ function validateField(path, value, fieldDef, schemaProperty, spec, data, comput
|
|
|
585
649
|
const customErrors = validateCustomRules(path, fieldDef.validations, context);
|
|
586
650
|
errors.push(...customErrors);
|
|
587
651
|
}
|
|
588
|
-
if (Array.isArray(value)
|
|
652
|
+
if (Array.isArray(value)) {
|
|
589
653
|
const arrayErrors = validateArray(
|
|
590
654
|
path,
|
|
591
655
|
value,
|
|
592
656
|
fieldDef,
|
|
657
|
+
schemaProperty,
|
|
593
658
|
spec,
|
|
594
659
|
data,
|
|
595
660
|
computed,
|
|
@@ -843,24 +908,28 @@ function validateCustomRules(path, rules, context) {
|
|
|
843
908
|
}
|
|
844
909
|
return errors;
|
|
845
910
|
}
|
|
846
|
-
function validateArray(path, value, fieldDef, spec, data, computed, visibility, onlyVisible) {
|
|
911
|
+
function validateArray(path, value, fieldDef, schemaProperty, spec, data, computed, visibility, onlyVisible) {
|
|
847
912
|
const errors = [];
|
|
848
913
|
const label = fieldDef.label ?? path;
|
|
849
|
-
|
|
914
|
+
const arraySchema = (schemaProperty == null ? void 0 : schemaProperty.type) === "array" ? schemaProperty : void 0;
|
|
915
|
+
const minItems = fieldDef.minItems ?? (arraySchema == null ? void 0 : arraySchema.minItems);
|
|
916
|
+
const maxItems = fieldDef.maxItems ?? (arraySchema == null ? void 0 : arraySchema.maxItems);
|
|
917
|
+
if (minItems !== void 0 && value.length < minItems) {
|
|
850
918
|
errors.push({
|
|
851
919
|
field: path,
|
|
852
|
-
message: `${label} must have at least ${
|
|
920
|
+
message: `${label} must have at least ${minItems} items`,
|
|
853
921
|
severity: "error"
|
|
854
922
|
});
|
|
855
923
|
}
|
|
856
|
-
if (
|
|
924
|
+
if (maxItems !== void 0 && value.length > maxItems) {
|
|
857
925
|
errors.push({
|
|
858
926
|
field: path,
|
|
859
|
-
message: `${label} must have no more than ${
|
|
927
|
+
message: `${label} must have no more than ${maxItems} items`,
|
|
860
928
|
severity: "error"
|
|
861
929
|
});
|
|
862
930
|
}
|
|
863
|
-
|
|
931
|
+
const itemSchema = arraySchema == null ? void 0 : arraySchema.items;
|
|
932
|
+
if (fieldDef.itemFields || itemSchema) {
|
|
864
933
|
for (let i = 0; i < value.length; i++) {
|
|
865
934
|
const item = value[i];
|
|
866
935
|
const itemErrors = validateArrayItem(
|
|
@@ -868,6 +937,7 @@ function validateArray(path, value, fieldDef, spec, data, computed, visibility,
|
|
|
868
937
|
i,
|
|
869
938
|
item,
|
|
870
939
|
fieldDef.itemFields,
|
|
940
|
+
itemSchema,
|
|
871
941
|
spec,
|
|
872
942
|
data,
|
|
873
943
|
computed,
|
|
@@ -879,9 +949,18 @@ function validateArray(path, value, fieldDef, spec, data, computed, visibility,
|
|
|
879
949
|
}
|
|
880
950
|
return errors;
|
|
881
951
|
}
|
|
882
|
-
function validateArrayItem(arrayPath, index, item, itemFields, spec, data, computed, visibility, onlyVisible) {
|
|
952
|
+
function validateArrayItem(arrayPath, index, item, itemFields, itemSchema, spec, data, computed, visibility, onlyVisible) {
|
|
883
953
|
const errors = [];
|
|
884
|
-
|
|
954
|
+
const objectSchema = (itemSchema == null ? void 0 : itemSchema.type) === "object" ? itemSchema : void 0;
|
|
955
|
+
const schemaProperties = (objectSchema == null ? void 0 : objectSchema.properties) ?? {};
|
|
956
|
+
const schemaRequired = new Set((objectSchema == null ? void 0 : objectSchema.required) ?? []);
|
|
957
|
+
const allFieldNames = /* @__PURE__ */ new Set([
|
|
958
|
+
...Object.keys(itemFields ?? {}),
|
|
959
|
+
...Object.keys(schemaProperties)
|
|
960
|
+
]);
|
|
961
|
+
for (const fieldName of allFieldNames) {
|
|
962
|
+
const fieldDef = itemFields == null ? void 0 : itemFields[fieldName];
|
|
963
|
+
const fieldSchema = schemaProperties[fieldName];
|
|
885
964
|
const itemFieldPath = `${arrayPath}[${index}].${fieldName}`;
|
|
886
965
|
if (onlyVisible && visibility[itemFieldPath] === false) {
|
|
887
966
|
continue;
|
|
@@ -895,15 +974,26 @@ function validateArrayItem(arrayPath, index, item, itemFields, spec, data, compu
|
|
|
895
974
|
itemIndex: index,
|
|
896
975
|
value
|
|
897
976
|
};
|
|
898
|
-
const isRequired2 = fieldDef.requiredWhen ? evaluateBoolean(fieldDef.requiredWhen, context) :
|
|
977
|
+
const isRequired2 = (fieldDef == null ? void 0 : fieldDef.requiredWhen) ? evaluateBoolean(fieldDef.requiredWhen, context) : schemaRequired.has(fieldName);
|
|
899
978
|
if (isRequired2 && isEmpty(value)) {
|
|
900
979
|
errors.push({
|
|
901
980
|
field: itemFieldPath,
|
|
902
|
-
message: fieldDef.label ? `${fieldDef.label} is required` : "This field is required",
|
|
981
|
+
message: (fieldDef == null ? void 0 : fieldDef.label) ? `${fieldDef.label} is required` : "This field is required",
|
|
903
982
|
severity: "error"
|
|
904
983
|
});
|
|
905
984
|
}
|
|
906
|
-
if (
|
|
985
|
+
if (!isEmpty(value) && fieldSchema) {
|
|
986
|
+
const typeError = validateType(
|
|
987
|
+
itemFieldPath,
|
|
988
|
+
value,
|
|
989
|
+
fieldSchema,
|
|
990
|
+
fieldDef ?? { label: fieldName }
|
|
991
|
+
);
|
|
992
|
+
if (typeError) {
|
|
993
|
+
errors.push(typeError);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
if ((fieldDef == null ? void 0 : fieldDef.validations) && !isEmpty(value)) {
|
|
907
997
|
const customErrors = validateCustomRules(itemFieldPath, fieldDef.validations, context);
|
|
908
998
|
errors.push(...customErrors);
|
|
909
999
|
}
|
|
@@ -940,9 +1030,11 @@ function validateSingleField(fieldPath, data, spec) {
|
|
|
940
1030
|
formatValue,
|
|
941
1031
|
getEnabled,
|
|
942
1032
|
getFormattedValue,
|
|
1033
|
+
getOptionsVisibility,
|
|
943
1034
|
getPageVisibility,
|
|
944
1035
|
getRequired,
|
|
945
1036
|
getVisibility,
|
|
1037
|
+
getVisibleOptions,
|
|
946
1038
|
isEnabled,
|
|
947
1039
|
isFieldVisible,
|
|
948
1040
|
isRequired,
|