@fogpipe/forma-core 0.6.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 ADDED
@@ -0,0 +1,962 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ calculate: () => calculate,
24
+ calculateField: () => calculateField,
25
+ calculateWithErrors: () => calculateWithErrors,
26
+ evaluate: () => evaluate,
27
+ evaluateBoolean: () => evaluateBoolean,
28
+ evaluateBooleanBatch: () => evaluateBooleanBatch,
29
+ evaluateNumber: () => evaluateNumber,
30
+ evaluateString: () => evaluateString,
31
+ getEnabled: () => getEnabled,
32
+ getFormattedValue: () => getFormattedValue,
33
+ getPageVisibility: () => getPageVisibility,
34
+ getRequired: () => getRequired,
35
+ getVisibility: () => getVisibility,
36
+ isEnabled: () => isEnabled,
37
+ isFieldVisible: () => isFieldVisible,
38
+ isRequired: () => isRequired,
39
+ isValidExpression: () => isValidExpression,
40
+ validate: () => validate,
41
+ validateExpression: () => validateExpression,
42
+ validateSingleField: () => validateSingleField
43
+ });
44
+ module.exports = __toCommonJS(index_exports);
45
+
46
+ // src/feel/index.ts
47
+ var import_feelin = require("feelin");
48
+ function buildFeelContext(ctx) {
49
+ const feelContext = {
50
+ // Spread form data directly so fields are accessible by name
51
+ ...ctx.data
52
+ };
53
+ if (ctx.computed) {
54
+ feelContext["computed"] = ctx.computed;
55
+ }
56
+ if (ctx.referenceData) {
57
+ feelContext["ref"] = ctx.referenceData;
58
+ }
59
+ if (ctx.item !== void 0) {
60
+ feelContext["item"] = ctx.item;
61
+ }
62
+ if (ctx.itemIndex !== void 0) {
63
+ feelContext["itemIndex"] = ctx.itemIndex;
64
+ }
65
+ if (ctx.value !== void 0) {
66
+ feelContext["value"] = ctx.value;
67
+ }
68
+ return feelContext;
69
+ }
70
+ function evaluate(expression, context) {
71
+ try {
72
+ const feelContext = buildFeelContext(context);
73
+ const result = (0, import_feelin.evaluate)(expression, feelContext);
74
+ return {
75
+ success: true,
76
+ value: result
77
+ };
78
+ } catch (error) {
79
+ return {
80
+ success: false,
81
+ error: error instanceof Error ? error.message : String(error),
82
+ expression
83
+ };
84
+ }
85
+ }
86
+ function evaluateBoolean(expression, context) {
87
+ const result = evaluate(expression, context);
88
+ if (!result.success) {
89
+ console.warn(
90
+ `FEEL expression error: ${result.error}
91
+ Expression: ${result.expression}`
92
+ );
93
+ return false;
94
+ }
95
+ if (result.value === null || result.value === void 0) {
96
+ return false;
97
+ }
98
+ if (typeof result.value !== "boolean") {
99
+ console.warn(
100
+ `FEEL expression did not return boolean: ${expression}
101
+ Got: ${typeof result.value}`
102
+ );
103
+ return false;
104
+ }
105
+ return result.value;
106
+ }
107
+ function evaluateNumber(expression, context) {
108
+ const result = evaluate(expression, context);
109
+ if (!result.success) {
110
+ console.warn(
111
+ `FEEL expression error: ${result.error}
112
+ Expression: ${result.expression}`
113
+ );
114
+ return null;
115
+ }
116
+ if (typeof result.value !== "number") {
117
+ console.warn(
118
+ `FEEL expression did not return number: ${expression}
119
+ Got: ${typeof result.value}`
120
+ );
121
+ return null;
122
+ }
123
+ return result.value;
124
+ }
125
+ function evaluateString(expression, context) {
126
+ const result = evaluate(expression, context);
127
+ if (!result.success) {
128
+ console.warn(
129
+ `FEEL expression error: ${result.error}
130
+ Expression: ${result.expression}`
131
+ );
132
+ return null;
133
+ }
134
+ if (typeof result.value !== "string") {
135
+ console.warn(
136
+ `FEEL expression did not return string: ${expression}
137
+ Got: ${typeof result.value}`
138
+ );
139
+ return null;
140
+ }
141
+ return result.value;
142
+ }
143
+ function evaluateBooleanBatch(expressions, context) {
144
+ const results = {};
145
+ for (const [key, expression] of Object.entries(expressions)) {
146
+ results[key] = evaluateBoolean(expression, context);
147
+ }
148
+ return results;
149
+ }
150
+ function isValidExpression(expression) {
151
+ try {
152
+ (0, import_feelin.evaluate)(expression, {});
153
+ return true;
154
+ } catch {
155
+ return false;
156
+ }
157
+ }
158
+ function validateExpression(expression) {
159
+ try {
160
+ (0, import_feelin.evaluate)(expression, {});
161
+ return null;
162
+ } catch (error) {
163
+ const message = error instanceof Error ? error.message : String(error);
164
+ if (message.includes("parse") || message.includes("syntax")) {
165
+ return message;
166
+ }
167
+ return null;
168
+ }
169
+ }
170
+
171
+ // src/engine/calculate.ts
172
+ function calculate(data, spec) {
173
+ const result = calculateWithErrors(data, spec);
174
+ return result.values;
175
+ }
176
+ function calculateWithErrors(data, spec) {
177
+ if (!spec.computed) {
178
+ return { values: {}, errors: [] };
179
+ }
180
+ const values = {};
181
+ const errors = [];
182
+ const orderedFields = getComputationOrder(spec.computed);
183
+ for (const fieldName of orderedFields) {
184
+ const fieldDef = spec.computed[fieldName];
185
+ if (!fieldDef) continue;
186
+ const result = evaluateComputedField(
187
+ fieldName,
188
+ fieldDef,
189
+ data,
190
+ values,
191
+ // Pass already-computed values for dependencies
192
+ spec.referenceData
193
+ // Pass reference data for lookups
194
+ );
195
+ if (result.success) {
196
+ values[fieldName] = result.value;
197
+ } else {
198
+ errors.push({
199
+ field: fieldName,
200
+ message: result.error,
201
+ expression: fieldDef.expression
202
+ });
203
+ values[fieldName] = null;
204
+ }
205
+ }
206
+ return { values, errors };
207
+ }
208
+ function evaluateComputedField(_name, fieldDef, data, computedSoFar, referenceData) {
209
+ const referencedComputed = findComputedReferences(fieldDef.expression);
210
+ for (const ref of referencedComputed) {
211
+ if (computedSoFar[ref] === null) {
212
+ return {
213
+ success: true,
214
+ value: null
215
+ };
216
+ }
217
+ }
218
+ const context = {
219
+ data,
220
+ computed: computedSoFar,
221
+ referenceData
222
+ };
223
+ const result = evaluate(fieldDef.expression, context);
224
+ if (!result.success) {
225
+ return {
226
+ success: false,
227
+ error: result.error
228
+ };
229
+ }
230
+ if (typeof result.value === "number" && !Number.isFinite(result.value)) {
231
+ return {
232
+ success: true,
233
+ value: null
234
+ };
235
+ }
236
+ return {
237
+ success: true,
238
+ value: result.value
239
+ };
240
+ }
241
+ function findComputedReferences(expression) {
242
+ const refs = [];
243
+ const regex = /computed\.(\w+)/g;
244
+ let match;
245
+ while ((match = regex.exec(expression)) !== null) {
246
+ refs.push(match[1]);
247
+ }
248
+ return refs;
249
+ }
250
+ function getComputationOrder(computed) {
251
+ const fieldNames = Object.keys(computed);
252
+ const deps = /* @__PURE__ */ new Map();
253
+ for (const name of fieldNames) {
254
+ deps.set(name, findComputedDependencies(computed[name].expression, fieldNames));
255
+ }
256
+ const sorted = [];
257
+ const visited = /* @__PURE__ */ new Set();
258
+ const visiting = /* @__PURE__ */ new Set();
259
+ function visit(name) {
260
+ if (visited.has(name)) return;
261
+ if (visiting.has(name)) {
262
+ console.warn(`Circular dependency detected in computed field: ${name}`);
263
+ sorted.push(name);
264
+ visited.add(name);
265
+ return;
266
+ }
267
+ visiting.add(name);
268
+ const fieldDeps = deps.get(name) ?? /* @__PURE__ */ new Set();
269
+ for (const dep of fieldDeps) {
270
+ visit(dep);
271
+ }
272
+ visiting.delete(name);
273
+ visited.add(name);
274
+ sorted.push(name);
275
+ }
276
+ for (const name of fieldNames) {
277
+ visit(name);
278
+ }
279
+ return sorted;
280
+ }
281
+ function findComputedDependencies(expression, availableFields) {
282
+ const deps = /* @__PURE__ */ new Set();
283
+ const regex = /computed\.(\w+)/g;
284
+ let match;
285
+ while ((match = regex.exec(expression)) !== null) {
286
+ const fieldName = match[1];
287
+ if (availableFields.includes(fieldName)) {
288
+ deps.add(fieldName);
289
+ }
290
+ }
291
+ return deps;
292
+ }
293
+ function getFormattedValue(fieldName, data, spec) {
294
+ var _a;
295
+ if (!((_a = spec.computed) == null ? void 0 : _a[fieldName])) {
296
+ return null;
297
+ }
298
+ const fieldDef = spec.computed[fieldName];
299
+ const computed = calculate(data, spec);
300
+ const value = computed[fieldName];
301
+ if (value === null || value === void 0) {
302
+ return null;
303
+ }
304
+ return formatValue(value, fieldDef.format);
305
+ }
306
+ function formatValue(value, format) {
307
+ if (!format) {
308
+ return String(value);
309
+ }
310
+ const decimalMatch = format.match(/^decimal\((\d+)\)$/);
311
+ if (decimalMatch) {
312
+ const decimals = parseInt(decimalMatch[1], 10);
313
+ return typeof value === "number" ? value.toFixed(decimals) : String(value);
314
+ }
315
+ if (format === "currency") {
316
+ return typeof value === "number" ? new Intl.NumberFormat("en-US", {
317
+ style: "currency",
318
+ currency: "USD"
319
+ }).format(value) : String(value);
320
+ }
321
+ if (format === "percent") {
322
+ return typeof value === "number" ? new Intl.NumberFormat("en-US", {
323
+ style: "percent",
324
+ minimumFractionDigits: 1
325
+ }).format(value) : String(value);
326
+ }
327
+ return String(value);
328
+ }
329
+ function calculateField(fieldName, data, spec) {
330
+ const computed = calculate(data, spec);
331
+ return computed[fieldName] ?? null;
332
+ }
333
+
334
+ // src/engine/visibility.ts
335
+ function getVisibility(data, spec, options = {}) {
336
+ const computed = options.computed ?? calculate(data, spec);
337
+ const baseContext = {
338
+ data,
339
+ computed,
340
+ referenceData: spec.referenceData
341
+ };
342
+ const result = {};
343
+ for (const fieldPath of spec.fieldOrder) {
344
+ const fieldDef = spec.fields[fieldPath];
345
+ if (fieldDef) {
346
+ evaluateFieldVisibility(fieldPath, fieldDef, data, baseContext, result);
347
+ }
348
+ }
349
+ return result;
350
+ }
351
+ function evaluateFieldVisibility(path, fieldDef, data, context, result) {
352
+ if (fieldDef.visibleWhen) {
353
+ result[path] = evaluateBoolean(fieldDef.visibleWhen, context);
354
+ } else {
355
+ result[path] = true;
356
+ }
357
+ if (!result[path]) {
358
+ return;
359
+ }
360
+ if (fieldDef.itemFields) {
361
+ const arrayData = data[path];
362
+ if (Array.isArray(arrayData)) {
363
+ evaluateArrayItemVisibility(path, fieldDef, arrayData, context, result);
364
+ }
365
+ }
366
+ }
367
+ function evaluateArrayItemVisibility(arrayPath, fieldDef, arrayData, baseContext, result) {
368
+ if (!fieldDef.itemFields) return;
369
+ for (let i = 0; i < arrayData.length; i++) {
370
+ const item = arrayData[i];
371
+ const itemContext = {
372
+ ...baseContext,
373
+ item,
374
+ itemIndex: i
375
+ };
376
+ for (const [fieldName, itemFieldDef] of Object.entries(fieldDef.itemFields)) {
377
+ const itemFieldPath = `${arrayPath}[${i}].${fieldName}`;
378
+ if (itemFieldDef.visibleWhen) {
379
+ result[itemFieldPath] = evaluateBoolean(itemFieldDef.visibleWhen, itemContext);
380
+ } else {
381
+ result[itemFieldPath] = true;
382
+ }
383
+ }
384
+ }
385
+ }
386
+ function isFieldVisible(fieldPath, data, spec, options = {}) {
387
+ const fieldDef = spec.fields[fieldPath];
388
+ if (!fieldDef) {
389
+ return true;
390
+ }
391
+ if (!fieldDef.visibleWhen) {
392
+ return true;
393
+ }
394
+ const computed = options.computed ?? calculate(data, spec);
395
+ const context = {
396
+ data,
397
+ computed,
398
+ referenceData: spec.referenceData
399
+ };
400
+ return evaluateBoolean(fieldDef.visibleWhen, context);
401
+ }
402
+ function getPageVisibility(data, spec, options = {}) {
403
+ if (!spec.pages) {
404
+ return {};
405
+ }
406
+ const computed = options.computed ?? calculate(data, spec);
407
+ const context = {
408
+ data,
409
+ computed,
410
+ referenceData: spec.referenceData
411
+ };
412
+ const result = {};
413
+ for (const page of spec.pages) {
414
+ if (page.visibleWhen) {
415
+ result[page.id] = evaluateBoolean(page.visibleWhen, context);
416
+ } else {
417
+ result[page.id] = true;
418
+ }
419
+ }
420
+ return result;
421
+ }
422
+
423
+ // src/engine/required.ts
424
+ function getRequired(data, spec, options = {}) {
425
+ const computed = options.computed ?? calculate(data, spec);
426
+ const context = {
427
+ data,
428
+ computed,
429
+ referenceData: spec.referenceData
430
+ };
431
+ const result = {};
432
+ for (const fieldPath of spec.fieldOrder) {
433
+ const fieldDef = spec.fields[fieldPath];
434
+ if (fieldDef) {
435
+ result[fieldPath] = isFieldRequired(fieldPath, fieldDef, spec, context);
436
+ }
437
+ }
438
+ for (const [fieldPath, fieldDef] of Object.entries(spec.fields)) {
439
+ if (fieldDef.itemFields) {
440
+ const arrayData = data[fieldPath];
441
+ if (Array.isArray(arrayData)) {
442
+ for (let i = 0; i < arrayData.length; i++) {
443
+ const item = arrayData[i];
444
+ const itemContext = {
445
+ data,
446
+ computed,
447
+ referenceData: spec.referenceData,
448
+ item,
449
+ itemIndex: i
450
+ };
451
+ for (const [itemFieldName, itemFieldDef] of Object.entries(fieldDef.itemFields)) {
452
+ const itemFieldPath = `${fieldPath}[${i}].${itemFieldName}`;
453
+ result[itemFieldPath] = isFieldRequired(
454
+ itemFieldPath,
455
+ itemFieldDef,
456
+ spec,
457
+ itemContext
458
+ );
459
+ }
460
+ }
461
+ }
462
+ }
463
+ }
464
+ return result;
465
+ }
466
+ function isFieldRequired(fieldPath, fieldDef, spec, context) {
467
+ var _a;
468
+ if (fieldDef.requiredWhen) {
469
+ return evaluateBoolean(fieldDef.requiredWhen, context);
470
+ }
471
+ return ((_a = spec.schema.required) == null ? void 0 : _a.includes(fieldPath)) ?? false;
472
+ }
473
+ function isRequired(fieldPath, data, spec) {
474
+ var _a;
475
+ const fieldDef = spec.fields[fieldPath];
476
+ if (!fieldDef) {
477
+ return ((_a = spec.schema.required) == null ? void 0 : _a.includes(fieldPath)) ?? false;
478
+ }
479
+ const computed = calculate(data, spec);
480
+ const context = {
481
+ data,
482
+ computed,
483
+ referenceData: spec.referenceData
484
+ };
485
+ return isFieldRequired(fieldPath, fieldDef, spec, context);
486
+ }
487
+
488
+ // src/engine/enabled.ts
489
+ function getEnabled(data, spec, options = {}) {
490
+ const computed = options.computed ?? calculate(data, spec);
491
+ const context = {
492
+ data,
493
+ computed,
494
+ referenceData: spec.referenceData
495
+ };
496
+ const result = {};
497
+ for (const fieldPath of spec.fieldOrder) {
498
+ const fieldDef = spec.fields[fieldPath];
499
+ if (fieldDef) {
500
+ result[fieldPath] = isFieldEnabled(fieldDef, context);
501
+ }
502
+ }
503
+ for (const [fieldPath, fieldDef] of Object.entries(spec.fields)) {
504
+ if (fieldDef.itemFields) {
505
+ const arrayData = data[fieldPath];
506
+ if (Array.isArray(arrayData)) {
507
+ for (let i = 0; i < arrayData.length; i++) {
508
+ const item = arrayData[i];
509
+ const itemContext = {
510
+ data,
511
+ computed,
512
+ referenceData: spec.referenceData,
513
+ item,
514
+ itemIndex: i
515
+ };
516
+ for (const [itemFieldName, itemFieldDef] of Object.entries(fieldDef.itemFields)) {
517
+ const itemFieldPath = `${fieldPath}[${i}].${itemFieldName}`;
518
+ result[itemFieldPath] = isFieldEnabled(itemFieldDef, itemContext);
519
+ }
520
+ }
521
+ }
522
+ }
523
+ }
524
+ return result;
525
+ }
526
+ function isFieldEnabled(fieldDef, context) {
527
+ if (fieldDef.enabledWhen) {
528
+ return evaluateBoolean(fieldDef.enabledWhen, context);
529
+ }
530
+ return true;
531
+ }
532
+ function isEnabled(fieldPath, data, spec) {
533
+ const fieldDef = spec.fields[fieldPath];
534
+ if (!fieldDef) {
535
+ return true;
536
+ }
537
+ if (!fieldDef.enabledWhen) {
538
+ return true;
539
+ }
540
+ const computed = calculate(data, spec);
541
+ const context = {
542
+ data,
543
+ computed,
544
+ referenceData: spec.referenceData
545
+ };
546
+ return evaluateBoolean(fieldDef.enabledWhen, context);
547
+ }
548
+
549
+ // src/engine/validate.ts
550
+ function validate(data, spec, options = {}) {
551
+ const { onlyVisible = true } = options;
552
+ const computed = options.computed ?? calculate(data, spec);
553
+ const visibility = options.visibility ?? getVisibility(data, spec, { computed });
554
+ const errors = [];
555
+ for (const fieldPath of spec.fieldOrder) {
556
+ const fieldDef = spec.fields[fieldPath];
557
+ if (!fieldDef) continue;
558
+ if (onlyVisible && visibility[fieldPath] === false) {
559
+ continue;
560
+ }
561
+ const schemaProperty = spec.schema.properties[fieldPath];
562
+ const fieldErrors = validateField(
563
+ fieldPath,
564
+ data[fieldPath],
565
+ fieldDef,
566
+ schemaProperty,
567
+ spec,
568
+ data,
569
+ computed,
570
+ visibility,
571
+ onlyVisible
572
+ );
573
+ errors.push(...fieldErrors);
574
+ }
575
+ return {
576
+ valid: errors.filter((e) => e.severity === "error").length === 0,
577
+ errors
578
+ };
579
+ }
580
+ function validateField(path, value, fieldDef, schemaProperty, spec, data, computed, visibility, onlyVisible) {
581
+ const errors = [];
582
+ const context = {
583
+ data,
584
+ computed,
585
+ referenceData: spec.referenceData,
586
+ value
587
+ };
588
+ const required = isFieldRequired(path, fieldDef, spec, context);
589
+ if (required && isEmpty(value)) {
590
+ errors.push({
591
+ field: path,
592
+ message: fieldDef.label ? `${fieldDef.label} is required` : "This field is required",
593
+ severity: "error"
594
+ });
595
+ }
596
+ if (!isEmpty(value) && schemaProperty) {
597
+ const typeError = validateType(path, value, schemaProperty, fieldDef);
598
+ if (typeError) {
599
+ errors.push(typeError);
600
+ }
601
+ }
602
+ if (fieldDef.validations && !isEmpty(value)) {
603
+ const customErrors = validateCustomRules(path, fieldDef.validations, context);
604
+ errors.push(...customErrors);
605
+ }
606
+ if (Array.isArray(value) && fieldDef.itemFields) {
607
+ const arrayErrors = validateArray(
608
+ path,
609
+ value,
610
+ fieldDef,
611
+ spec,
612
+ data,
613
+ computed,
614
+ visibility,
615
+ onlyVisible
616
+ );
617
+ errors.push(...arrayErrors);
618
+ }
619
+ return errors;
620
+ }
621
+ function isEmpty(value) {
622
+ if (value === null || value === void 0) return true;
623
+ if (typeof value === "string" && value.trim() === "") return true;
624
+ if (Array.isArray(value) && value.length === 0) return true;
625
+ return false;
626
+ }
627
+ function validateType(path, value, schema, fieldDef) {
628
+ const label = fieldDef.label ?? path;
629
+ switch (schema.type) {
630
+ case "string": {
631
+ if (typeof value !== "string") {
632
+ return {
633
+ field: path,
634
+ message: `${label} must be a string`,
635
+ severity: "error"
636
+ };
637
+ }
638
+ if ("minLength" in schema && schema.minLength !== void 0) {
639
+ if (value.length < schema.minLength) {
640
+ return {
641
+ field: path,
642
+ message: `${label} must be at least ${schema.minLength} characters`,
643
+ severity: "error"
644
+ };
645
+ }
646
+ }
647
+ if ("maxLength" in schema && schema.maxLength !== void 0) {
648
+ if (value.length > schema.maxLength) {
649
+ return {
650
+ field: path,
651
+ message: `${label} must be no more than ${schema.maxLength} characters`,
652
+ severity: "error"
653
+ };
654
+ }
655
+ }
656
+ if ("pattern" in schema && schema.pattern) {
657
+ const regex = new RegExp(schema.pattern);
658
+ if (!regex.test(value)) {
659
+ return {
660
+ field: path,
661
+ message: `${label} format is invalid`,
662
+ severity: "error"
663
+ };
664
+ }
665
+ }
666
+ if ("enum" in schema && schema.enum) {
667
+ if (!schema.enum.includes(value)) {
668
+ return {
669
+ field: path,
670
+ message: `${label} must be one of: ${schema.enum.join(", ")}`,
671
+ severity: "error"
672
+ };
673
+ }
674
+ }
675
+ if ("format" in schema && schema.format) {
676
+ const formatError = validateFormat(path, value, schema.format, label);
677
+ if (formatError) return formatError;
678
+ }
679
+ return null;
680
+ }
681
+ case "number":
682
+ case "integer": {
683
+ if (typeof value !== "number") {
684
+ return {
685
+ field: path,
686
+ message: `${label} must be a number`,
687
+ severity: "error"
688
+ };
689
+ }
690
+ if (schema.type === "integer" && !Number.isInteger(value)) {
691
+ return {
692
+ field: path,
693
+ message: `${label} must be a whole number`,
694
+ severity: "error"
695
+ };
696
+ }
697
+ if ("minimum" in schema && schema.minimum !== void 0) {
698
+ if (value < schema.minimum) {
699
+ return {
700
+ field: path,
701
+ message: `${label} must be at least ${schema.minimum}`,
702
+ severity: "error"
703
+ };
704
+ }
705
+ }
706
+ if ("maximum" in schema && schema.maximum !== void 0) {
707
+ if (value > schema.maximum) {
708
+ return {
709
+ field: path,
710
+ message: `${label} must be no more than ${schema.maximum}`,
711
+ severity: "error"
712
+ };
713
+ }
714
+ }
715
+ if ("exclusiveMinimum" in schema && schema.exclusiveMinimum !== void 0) {
716
+ if (value <= schema.exclusiveMinimum) {
717
+ return {
718
+ field: path,
719
+ message: `${label} must be greater than ${schema.exclusiveMinimum}`,
720
+ severity: "error"
721
+ };
722
+ }
723
+ }
724
+ if ("exclusiveMaximum" in schema && schema.exclusiveMaximum !== void 0) {
725
+ if (value >= schema.exclusiveMaximum) {
726
+ return {
727
+ field: path,
728
+ message: `${label} must be less than ${schema.exclusiveMaximum}`,
729
+ severity: "error"
730
+ };
731
+ }
732
+ }
733
+ return null;
734
+ }
735
+ case "boolean": {
736
+ if (typeof value !== "boolean") {
737
+ return {
738
+ field: path,
739
+ message: `${label} must be true or false`,
740
+ severity: "error"
741
+ };
742
+ }
743
+ return null;
744
+ }
745
+ case "array": {
746
+ if (!Array.isArray(value)) {
747
+ return {
748
+ field: path,
749
+ message: `${label} must be a list`,
750
+ severity: "error"
751
+ };
752
+ }
753
+ return null;
754
+ }
755
+ case "object": {
756
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
757
+ return {
758
+ field: path,
759
+ message: `${label} must be an object`,
760
+ severity: "error"
761
+ };
762
+ }
763
+ return null;
764
+ }
765
+ default:
766
+ return null;
767
+ }
768
+ }
769
+ function validateFormat(path, value, format, label) {
770
+ switch (format) {
771
+ case "email": {
772
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
773
+ if (!emailRegex.test(value)) {
774
+ return {
775
+ field: path,
776
+ message: `${label} must be a valid email address`,
777
+ severity: "error"
778
+ };
779
+ }
780
+ return null;
781
+ }
782
+ case "date": {
783
+ const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
784
+ if (!dateRegex.test(value)) {
785
+ return {
786
+ field: path,
787
+ message: `${label} must be a valid date`,
788
+ severity: "error"
789
+ };
790
+ }
791
+ const parsed = /* @__PURE__ */ new Date(value + "T00:00:00Z");
792
+ if (isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== value) {
793
+ return {
794
+ field: path,
795
+ message: `${label} must be a valid date`,
796
+ severity: "error"
797
+ };
798
+ }
799
+ return null;
800
+ }
801
+ case "date-time": {
802
+ if (isNaN(Date.parse(value))) {
803
+ return {
804
+ field: path,
805
+ message: `${label} must be a valid date and time`,
806
+ severity: "error"
807
+ };
808
+ }
809
+ return null;
810
+ }
811
+ case "uri": {
812
+ try {
813
+ new URL(value);
814
+ return null;
815
+ } catch {
816
+ return {
817
+ field: path,
818
+ message: `${label} must be a valid URL`,
819
+ severity: "error"
820
+ };
821
+ }
822
+ }
823
+ case "uuid": {
824
+ 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;
825
+ if (!uuidRegex.test(value)) {
826
+ return {
827
+ field: path,
828
+ message: `${label} must be a valid UUID`,
829
+ severity: "error"
830
+ };
831
+ }
832
+ return null;
833
+ }
834
+ default:
835
+ return null;
836
+ }
837
+ }
838
+ function validateCustomRules(path, rules, context) {
839
+ const errors = [];
840
+ for (const rule of rules) {
841
+ const isValid = evaluateBoolean(rule.rule, context);
842
+ if (!isValid) {
843
+ errors.push({
844
+ field: path,
845
+ message: rule.message,
846
+ severity: rule.severity ?? "error"
847
+ });
848
+ }
849
+ }
850
+ return errors;
851
+ }
852
+ function validateArray(path, value, fieldDef, spec, data, computed, visibility, onlyVisible) {
853
+ const errors = [];
854
+ const label = fieldDef.label ?? path;
855
+ if (fieldDef.minItems !== void 0 && value.length < fieldDef.minItems) {
856
+ errors.push({
857
+ field: path,
858
+ message: `${label} must have at least ${fieldDef.minItems} items`,
859
+ severity: "error"
860
+ });
861
+ }
862
+ if (fieldDef.maxItems !== void 0 && value.length > fieldDef.maxItems) {
863
+ errors.push({
864
+ field: path,
865
+ message: `${label} must have no more than ${fieldDef.maxItems} items`,
866
+ severity: "error"
867
+ });
868
+ }
869
+ if (fieldDef.itemFields) {
870
+ for (let i = 0; i < value.length; i++) {
871
+ const item = value[i];
872
+ const itemErrors = validateArrayItem(
873
+ path,
874
+ i,
875
+ item,
876
+ fieldDef.itemFields,
877
+ spec,
878
+ data,
879
+ computed,
880
+ visibility,
881
+ onlyVisible
882
+ );
883
+ errors.push(...itemErrors);
884
+ }
885
+ }
886
+ return errors;
887
+ }
888
+ function validateArrayItem(arrayPath, index, item, itemFields, spec, data, computed, visibility, onlyVisible) {
889
+ const errors = [];
890
+ for (const [fieldName, fieldDef] of Object.entries(itemFields)) {
891
+ const itemFieldPath = `${arrayPath}[${index}].${fieldName}`;
892
+ if (onlyVisible && visibility[itemFieldPath] === false) {
893
+ continue;
894
+ }
895
+ const value = item[fieldName];
896
+ const context = {
897
+ data,
898
+ computed,
899
+ referenceData: spec.referenceData,
900
+ item,
901
+ itemIndex: index,
902
+ value
903
+ };
904
+ const isRequired2 = fieldDef.requiredWhen ? evaluateBoolean(fieldDef.requiredWhen, context) : false;
905
+ if (isRequired2 && isEmpty(value)) {
906
+ errors.push({
907
+ field: itemFieldPath,
908
+ message: fieldDef.label ? `${fieldDef.label} is required` : "This field is required",
909
+ severity: "error"
910
+ });
911
+ }
912
+ if (fieldDef.validations && !isEmpty(value)) {
913
+ const customErrors = validateCustomRules(itemFieldPath, fieldDef.validations, context);
914
+ errors.push(...customErrors);
915
+ }
916
+ }
917
+ return errors;
918
+ }
919
+ function validateSingleField(fieldPath, data, spec) {
920
+ const fieldDef = spec.fields[fieldPath];
921
+ if (!fieldDef) {
922
+ return [];
923
+ }
924
+ const computed = calculate(data, spec);
925
+ const visibility = getVisibility(data, spec, { computed });
926
+ const schemaProperty = spec.schema.properties[fieldPath];
927
+ return validateField(
928
+ fieldPath,
929
+ data[fieldPath],
930
+ fieldDef,
931
+ schemaProperty,
932
+ spec,
933
+ data,
934
+ computed,
935
+ visibility,
936
+ true
937
+ );
938
+ }
939
+ // Annotate the CommonJS export names for ESM import in node:
940
+ 0 && (module.exports = {
941
+ calculate,
942
+ calculateField,
943
+ calculateWithErrors,
944
+ evaluate,
945
+ evaluateBoolean,
946
+ evaluateBooleanBatch,
947
+ evaluateNumber,
948
+ evaluateString,
949
+ getEnabled,
950
+ getFormattedValue,
951
+ getPageVisibility,
952
+ getRequired,
953
+ getVisibility,
954
+ isEnabled,
955
+ isFieldVisible,
956
+ isRequired,
957
+ isValidExpression,
958
+ validate,
959
+ validateExpression,
960
+ validateSingleField
961
+ });
962
+ //# sourceMappingURL=index.cjs.map