@fogpipe/forma-core 0.12.0-alpha.2 → 0.13.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.
Files changed (40) hide show
  1. package/README.md +12 -12
  2. package/dist/{chunk-U2OXXFEH.js → chunk-BDICNCE2.js} +1 -1
  3. package/dist/chunk-BDICNCE2.js.map +1 -0
  4. package/dist/{chunk-ZSW7NIMY.js → chunk-NKA3L2LJ.js} +64 -15
  5. package/dist/chunk-NKA3L2LJ.js.map +1 -0
  6. package/dist/engine/calculate.d.ts.map +1 -1
  7. package/dist/engine/enabled.d.ts.map +1 -1
  8. package/dist/engine/index.cjs +62 -13
  9. package/dist/engine/index.cjs.map +1 -1
  10. package/dist/engine/index.d.ts +8 -8
  11. package/dist/engine/index.d.ts.map +1 -1
  12. package/dist/engine/index.js +2 -2
  13. package/dist/engine/readonly.d.ts.map +1 -1
  14. package/dist/engine/required.d.ts.map +1 -1
  15. package/dist/engine/validate.d.ts.map +1 -1
  16. package/dist/engine/visibility.d.ts.map +1 -1
  17. package/dist/feel/index.cjs.map +1 -1
  18. package/dist/feel/index.js +1 -1
  19. package/dist/index.cjs +62 -13
  20. package/dist/index.cjs.map +1 -1
  21. package/dist/index.js +2 -2
  22. package/dist/types.d.ts +16 -0
  23. package/dist/types.d.ts.map +1 -1
  24. package/package.json +1 -1
  25. package/src/__tests__/feel.test.ts +67 -76
  26. package/src/__tests__/format.test.ts +19 -6
  27. package/src/__tests__/validate.test.ts +62 -20
  28. package/src/__tests__/visibility.test.ts +217 -85
  29. package/src/engine/calculate.ts +13 -10
  30. package/src/engine/enabled.ts +16 -6
  31. package/src/engine/index.ts +8 -28
  32. package/src/engine/readonly.ts +16 -6
  33. package/src/engine/required.ts +7 -5
  34. package/src/engine/validate.ts +43 -22
  35. package/src/engine/visibility.ts +40 -16
  36. package/src/feel/index.ts +12 -12
  37. package/src/format/index.ts +1 -1
  38. package/src/types.ts +46 -7
  39. package/dist/chunk-U2OXXFEH.js.map +0 -1
  40. package/dist/chunk-ZSW7NIMY.js.map +0 -1
@@ -49,7 +49,7 @@ export interface EnabledOptions {
49
49
  export function getEnabled(
50
50
  data: Record<string, unknown>,
51
51
  spec: Forma,
52
- options: EnabledOptions = {}
52
+ options: EnabledOptions = {},
53
53
  ): EnabledResult {
54
54
  const computed = options.computed ?? calculate(data, spec);
55
55
  const context: EvaluationContext = {
@@ -73,7 +73,15 @@ export function getEnabled(
73
73
  if (fieldDef.type === "array" && fieldDef.itemFields) {
74
74
  const arrayData = data[fieldPath];
75
75
  if (Array.isArray(arrayData)) {
76
- evaluateArrayItemEnabled(fieldPath, fieldDef, arrayData, data, computed, spec, result);
76
+ evaluateArrayItemEnabled(
77
+ fieldPath,
78
+ fieldDef,
79
+ arrayData,
80
+ data,
81
+ computed,
82
+ spec,
83
+ result,
84
+ );
77
85
  }
78
86
  }
79
87
  }
@@ -90,7 +98,7 @@ export function getEnabled(
90
98
  */
91
99
  function isFieldEnabled(
92
100
  fieldDef: FieldDefinition,
93
- context: EvaluationContext
101
+ context: EvaluationContext,
94
102
  ): boolean {
95
103
  // If field has enabledWhen, evaluate it
96
104
  if (fieldDef.enabledWhen) {
@@ -111,7 +119,7 @@ function evaluateArrayItemEnabled(
111
119
  data: Record<string, unknown>,
112
120
  computed: Record<string, unknown>,
113
121
  spec: Forma,
114
- result: EnabledResult
122
+ result: EnabledResult,
115
123
  ): void {
116
124
  if (!fieldDef.itemFields) return;
117
125
 
@@ -125,7 +133,9 @@ function evaluateArrayItemEnabled(
125
133
  itemIndex: i,
126
134
  };
127
135
 
128
- for (const [itemFieldName, itemFieldDef] of Object.entries(fieldDef.itemFields)) {
136
+ for (const [itemFieldName, itemFieldDef] of Object.entries(
137
+ fieldDef.itemFields,
138
+ )) {
129
139
  const itemFieldPath = `${arrayPath}[${i}].${itemFieldName}`;
130
140
  result[itemFieldPath] = isFieldEnabled(itemFieldDef, itemContext);
131
141
  }
@@ -143,7 +153,7 @@ function evaluateArrayItemEnabled(
143
153
  export function isEnabled(
144
154
  fieldPath: string,
145
155
  data: Record<string, unknown>,
146
- spec: Forma
156
+ spec: Forma,
147
157
  ): boolean {
148
158
  const fieldDef = spec.fields[fieldPath];
149
159
  if (!fieldDef) {
@@ -38,41 +38,21 @@ export type {
38
38
  } from "./visibility.js";
39
39
 
40
40
  // Required
41
- export {
42
- getRequired,
43
- isRequired,
44
- } from "./required.js";
41
+ export { getRequired, isRequired } from "./required.js";
45
42
 
46
- export type {
47
- RequiredOptions,
48
- } from "./required.js";
43
+ export type { RequiredOptions } from "./required.js";
49
44
 
50
45
  // Enabled
51
- export {
52
- getEnabled,
53
- isEnabled,
54
- } from "./enabled.js";
46
+ export { getEnabled, isEnabled } from "./enabled.js";
55
47
 
56
- export type {
57
- EnabledOptions,
58
- } from "./enabled.js";
48
+ export type { EnabledOptions } from "./enabled.js";
59
49
 
60
50
  // Readonly
61
- export {
62
- getReadonly,
63
- isReadonly,
64
- } from "./readonly.js";
51
+ export { getReadonly, isReadonly } from "./readonly.js";
65
52
 
66
- export type {
67
- ReadonlyOptions,
68
- } from "./readonly.js";
53
+ export type { ReadonlyOptions } from "./readonly.js";
69
54
 
70
55
  // Validate
71
- export {
72
- validate,
73
- validateSingleField,
74
- } from "./validate.js";
56
+ export { validate, validateSingleField } from "./validate.js";
75
57
 
76
- export type {
77
- ValidateOptions,
78
- } from "./validate.js";
58
+ export type { ValidateOptions } from "./validate.js";
@@ -53,7 +53,7 @@ export interface ReadonlyOptions {
53
53
  export function getReadonly(
54
54
  data: Record<string, unknown>,
55
55
  spec: Forma,
56
- options: ReadonlyOptions = {}
56
+ options: ReadonlyOptions = {},
57
57
  ): ReadonlyResult {
58
58
  const computed = options.computed ?? calculate(data, spec);
59
59
  const context: EvaluationContext = {
@@ -77,7 +77,15 @@ export function getReadonly(
77
77
  if (fieldDef.type === "array" && fieldDef.itemFields) {
78
78
  const arrayData = data[fieldPath];
79
79
  if (Array.isArray(arrayData)) {
80
- evaluateArrayItemReadonly(fieldPath, fieldDef, arrayData, data, computed, spec, result);
80
+ evaluateArrayItemReadonly(
81
+ fieldPath,
82
+ fieldDef,
83
+ arrayData,
84
+ data,
85
+ computed,
86
+ spec,
87
+ result,
88
+ );
81
89
  }
82
90
  }
83
91
  }
@@ -94,7 +102,7 @@ export function getReadonly(
94
102
  */
95
103
  function isFieldReadonly(
96
104
  fieldDef: FieldDefinition,
97
- context: EvaluationContext
105
+ context: EvaluationContext,
98
106
  ): boolean {
99
107
  // If field has readonlyWhen, evaluate it
100
108
  if (fieldDef.readonlyWhen) {
@@ -115,7 +123,7 @@ function evaluateArrayItemReadonly(
115
123
  data: Record<string, unknown>,
116
124
  computed: Record<string, unknown>,
117
125
  spec: Forma,
118
- result: ReadonlyResult
126
+ result: ReadonlyResult,
119
127
  ): void {
120
128
  if (!fieldDef.itemFields) return;
121
129
 
@@ -129,7 +137,9 @@ function evaluateArrayItemReadonly(
129
137
  itemIndex: i,
130
138
  };
131
139
 
132
- for (const [itemFieldName, itemFieldDef] of Object.entries(fieldDef.itemFields)) {
140
+ for (const [itemFieldName, itemFieldDef] of Object.entries(
141
+ fieldDef.itemFields,
142
+ )) {
133
143
  const itemFieldPath = `${arrayPath}[${i}].${itemFieldName}`;
134
144
  result[itemFieldPath] = isFieldReadonly(itemFieldDef, itemContext);
135
145
  }
@@ -147,7 +157,7 @@ function evaluateArrayItemReadonly(
147
157
  export function isReadonly(
148
158
  fieldPath: string,
149
159
  data: Record<string, unknown>,
150
- spec: Forma
160
+ spec: Forma,
151
161
  ): boolean {
152
162
  const fieldDef = spec.fields[fieldPath];
153
163
  if (!fieldDef) {
@@ -48,7 +48,7 @@ export interface RequiredOptions {
48
48
  export function getRequired(
49
49
  data: Record<string, unknown>,
50
50
  spec: Forma,
51
- options: RequiredOptions = {}
51
+ options: RequiredOptions = {},
52
52
  ): RequiredFieldsResult {
53
53
  const computed = options.computed ?? calculate(data, spec);
54
54
  const context: EvaluationContext = {
@@ -82,13 +82,15 @@ export function getRequired(
82
82
  itemIndex: i,
83
83
  };
84
84
 
85
- for (const [itemFieldName, itemFieldDef] of Object.entries(fieldDef.itemFields)) {
85
+ for (const [itemFieldName, itemFieldDef] of Object.entries(
86
+ fieldDef.itemFields,
87
+ )) {
86
88
  const itemFieldPath = `${fieldPath}[${i}].${itemFieldName}`;
87
89
  result[itemFieldPath] = isFieldRequired(
88
90
  itemFieldPath,
89
91
  itemFieldDef,
90
92
  spec,
91
- itemContext
93
+ itemContext,
92
94
  );
93
95
  }
94
96
  }
@@ -111,7 +113,7 @@ export function isFieldRequired(
111
113
  fieldPath: string,
112
114
  fieldDef: FieldDefinition,
113
115
  spec: Forma,
114
- context: EvaluationContext
116
+ context: EvaluationContext,
115
117
  ): boolean {
116
118
  // If field has requiredWhen, evaluate it
117
119
  if (fieldDef.requiredWhen) {
@@ -133,7 +135,7 @@ export function isFieldRequired(
133
135
  export function isRequired(
134
136
  fieldPath: string,
135
137
  data: Record<string, unknown>,
136
- spec: Forma
138
+ spec: Forma,
137
139
  ): boolean {
138
140
  const fieldDef = spec.fields[fieldPath];
139
141
  if (!fieldDef) {
@@ -73,7 +73,7 @@ export interface ValidateOptions {
73
73
  export function validate(
74
74
  data: Record<string, unknown>,
75
75
  spec: Forma,
76
- options: ValidateOptions = {}
76
+ options: ValidateOptions = {},
77
77
  ): ValidationResult {
78
78
  const { onlyVisible = true } = options;
79
79
 
@@ -81,7 +81,8 @@ export function validate(
81
81
  const computed = options.computed ?? calculate(data, spec);
82
82
 
83
83
  // Calculate visibility
84
- const visibility = options.visibility ?? getVisibility(data, spec, { computed });
84
+ const visibility =
85
+ options.visibility ?? getVisibility(data, spec, { computed });
85
86
 
86
87
  // Collect errors
87
88
  const errors: FieldError[] = [];
@@ -114,7 +115,7 @@ export function validate(
114
115
  data,
115
116
  computed,
116
117
  visibility,
117
- onlyVisible
118
+ onlyVisible,
118
119
  );
119
120
 
120
121
  errors.push(...fieldErrors);
@@ -142,7 +143,7 @@ function validateField(
142
143
  data: Record<string, unknown>,
143
144
  computed: Record<string, unknown>,
144
145
  visibility: Record<string, boolean>,
145
- onlyVisible: boolean
146
+ onlyVisible: boolean,
146
147
  ): FieldError[] {
147
148
  const errors: FieldError[] = [];
148
149
  const context: EvaluationContext = {
@@ -174,7 +175,11 @@ function validateField(
174
175
 
175
176
  // 3. Custom FEEL validation rules
176
177
  if (fieldDef.validations && !isEmpty(value)) {
177
- const customErrors = validateCustomRules(path, fieldDef.validations, context);
178
+ const customErrors = validateCustomRules(
179
+ path,
180
+ fieldDef.validations,
181
+ context,
182
+ );
178
183
  errors.push(...customErrors);
179
184
  }
180
185
 
@@ -189,7 +194,7 @@ function validateField(
189
194
  data,
190
195
  computed,
191
196
  visibility,
192
- onlyVisible
197
+ onlyVisible,
193
198
  );
194
199
  errors.push(...arrayErrors);
195
200
  }
@@ -218,7 +223,7 @@ function validateType(
218
223
  path: string,
219
224
  value: unknown,
220
225
  schema: JSONSchemaProperty,
221
- fieldDef: { label?: string }
226
+ fieldDef: { label?: string },
222
227
  ): FieldError | null {
223
228
  const label = fieldDef.label ?? path;
224
229
 
@@ -320,7 +325,10 @@ function validateType(
320
325
  }
321
326
  }
322
327
 
323
- if ("exclusiveMinimum" in schema && schema.exclusiveMinimum !== undefined) {
328
+ if (
329
+ "exclusiveMinimum" in schema &&
330
+ schema.exclusiveMinimum !== undefined
331
+ ) {
324
332
  if (value <= schema.exclusiveMinimum) {
325
333
  return {
326
334
  field: path,
@@ -330,7 +338,10 @@ function validateType(
330
338
  }
331
339
  }
332
340
 
333
- if ("exclusiveMaximum" in schema && schema.exclusiveMaximum !== undefined) {
341
+ if (
342
+ "exclusiveMaximum" in schema &&
343
+ schema.exclusiveMaximum !== undefined
344
+ ) {
334
345
  if (value >= schema.exclusiveMaximum) {
335
346
  return {
336
347
  field: path,
@@ -344,7 +355,8 @@ function validateType(
344
355
  const multipleOf = schema.multipleOf;
345
356
  // Use epsilon comparison to handle floating point precision issues
346
357
  const remainder = Math.abs(value % multipleOf);
347
- const isValid = remainder < 1e-10 || Math.abs(remainder - multipleOf) < 1e-10;
358
+ const isValid =
359
+ remainder < 1e-10 || Math.abs(remainder - multipleOf) < 1e-10;
348
360
  if (!isValid) {
349
361
  return {
350
362
  field: path,
@@ -402,7 +414,7 @@ function validateFormat(
402
414
  path: string,
403
415
  value: string,
404
416
  format: string,
405
- label: string
417
+ label: string,
406
418
  ): FieldError | null {
407
419
  switch (format) {
408
420
  case "email": {
@@ -430,7 +442,10 @@ function validateFormat(
430
442
  }
431
443
  // Verify the date is actually valid (e.g., not Feb 30)
432
444
  const parsed = new Date(value + "T00:00:00Z");
433
- if (isNaN(parsed.getTime()) || parsed.toISOString().slice(0, 10) !== value) {
445
+ if (
446
+ isNaN(parsed.getTime()) ||
447
+ parsed.toISOString().slice(0, 10) !== value
448
+ ) {
434
449
  return {
435
450
  field: path,
436
451
  message: `${label} must be a valid date`,
@@ -465,7 +480,8 @@ function validateFormat(
465
480
  }
466
481
 
467
482
  case "uuid": {
468
- 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;
483
+ const uuidRegex =
484
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
469
485
  if (!uuidRegex.test(value)) {
470
486
  return {
471
487
  field: path,
@@ -491,7 +507,7 @@ function validateFormat(
491
507
  function validateCustomRules(
492
508
  path: string,
493
509
  rules: ValidationRule[],
494
- context: EvaluationContext
510
+ context: EvaluationContext,
495
511
  ): FieldError[] {
496
512
  const errors: FieldError[] = [];
497
513
 
@@ -526,13 +542,14 @@ function validateArray(
526
542
  data: Record<string, unknown>,
527
543
  computed: Record<string, unknown>,
528
544
  visibility: Record<string, boolean>,
529
- onlyVisible: boolean
545
+ onlyVisible: boolean,
530
546
  ): FieldError[] {
531
547
  const errors: FieldError[] = [];
532
548
  const label = fieldDef.label ?? path;
533
549
 
534
550
  // Get array schema for minItems/maxItems fallback
535
- const arraySchema = schemaProperty?.type === "array" ? schemaProperty : undefined;
551
+ const arraySchema =
552
+ schemaProperty?.type === "array" ? schemaProperty : undefined;
536
553
 
537
554
  // Check min/max items - fieldDef overrides schema
538
555
  const minItems = fieldDef.minItems ?? arraySchema?.minItems;
@@ -571,7 +588,7 @@ function validateArray(
571
588
  data,
572
589
  computed,
573
590
  visibility,
574
- onlyVisible
591
+ onlyVisible,
575
592
  );
576
593
  errors.push(...itemErrors);
577
594
  }
@@ -593,7 +610,7 @@ function validateArrayItem(
593
610
  data: Record<string, unknown>,
594
611
  computed: Record<string, unknown>,
595
612
  visibility: Record<string, boolean>,
596
- onlyVisible: boolean
613
+ onlyVisible: boolean,
597
614
  ): FieldError[] {
598
615
  const errors: FieldError[] = [];
599
616
 
@@ -649,7 +666,7 @@ function validateArrayItem(
649
666
  itemFieldPath,
650
667
  value,
651
668
  fieldSchema,
652
- fieldDef ?? { label: fieldName }
669
+ fieldDef ?? { label: fieldName },
653
670
  );
654
671
  if (typeError) {
655
672
  errors.push(typeError);
@@ -658,7 +675,11 @@ function validateArrayItem(
658
675
 
659
676
  // Custom validations from fieldDef
660
677
  if (fieldDef?.validations && !isEmpty(value)) {
661
- const customErrors = validateCustomRules(itemFieldPath, fieldDef.validations, context);
678
+ const customErrors = validateCustomRules(
679
+ itemFieldPath,
680
+ fieldDef.validations,
681
+ context,
682
+ );
662
683
  errors.push(...customErrors);
663
684
  }
664
685
  }
@@ -681,7 +702,7 @@ function validateArrayItem(
681
702
  export function validateSingleField(
682
703
  fieldPath: string,
683
704
  data: Record<string, unknown>,
684
- spec: Forma
705
+ spec: Forma,
685
706
  ): FieldError[] {
686
707
  const fieldDef = spec.fields[fieldPath];
687
708
  if (!fieldDef) {
@@ -701,6 +722,6 @@ export function validateSingleField(
701
722
  data,
702
723
  computed,
703
724
  visibility,
704
- true
725
+ true,
705
726
  );
706
727
  }
@@ -46,7 +46,7 @@ export interface OptionsVisibilityResult {
46
46
  */
47
47
  function filterOptionsByContext(
48
48
  options: readonly SelectOption[],
49
- context: EvaluationContext
49
+ context: EvaluationContext,
50
50
  ): SelectOption[] {
51
51
  return options.filter((option) => {
52
52
  if (!option.visibleWhen) return true;
@@ -67,7 +67,7 @@ function processArrayItemOptions(
67
67
  fieldDef: ArrayFieldDefinition,
68
68
  arrayData: readonly unknown[],
69
69
  baseContext: EvaluationContext,
70
- result: Record<string, SelectOption[]>
70
+ result: Record<string, SelectOption[]>,
71
71
  ): void {
72
72
  if (!fieldDef.itemFields) return;
73
73
 
@@ -79,10 +79,19 @@ function processArrayItemOptions(
79
79
  itemIndex: i,
80
80
  };
81
81
 
82
- for (const [itemFieldName, itemFieldDef] of Object.entries(fieldDef.itemFields)) {
83
- if (isSelectionField(itemFieldDef) && itemFieldDef.options && itemFieldDef.options.length > 0) {
82
+ for (const [itemFieldName, itemFieldDef] of Object.entries(
83
+ fieldDef.itemFields,
84
+ )) {
85
+ if (
86
+ isSelectionField(itemFieldDef) &&
87
+ itemFieldDef.options &&
88
+ itemFieldDef.options.length > 0
89
+ ) {
84
90
  const itemFieldPath = `${arrayPath}[${i}].${itemFieldName}`;
85
- result[itemFieldPath] = filterOptionsByContext(itemFieldDef.options, itemContext);
91
+ result[itemFieldPath] = filterOptionsByContext(
92
+ itemFieldDef.options,
93
+ itemContext,
94
+ );
86
95
  }
87
96
  }
88
97
  }
@@ -113,7 +122,7 @@ function processArrayItemOptions(
113
122
  export function getVisibility(
114
123
  data: Record<string, unknown>,
115
124
  spec: Forma,
116
- options: VisibilityOptions = {}
125
+ options: VisibilityOptions = {},
117
126
  ): VisibilityResult {
118
127
  const computed = options.computed ?? calculate(data, spec);
119
128
 
@@ -143,7 +152,7 @@ function evaluateFieldVisibility(
143
152
  fieldDef: FieldDefinition,
144
153
  data: Record<string, unknown>,
145
154
  context: EvaluationContext,
146
- result: VisibilityResult
155
+ result: VisibilityResult,
147
156
  ): void {
148
157
  if (fieldDef.visibleWhen) {
149
158
  result[path] = evaluateBoolean(fieldDef.visibleWhen, context);
@@ -171,7 +180,7 @@ function evaluateArrayItemVisibility(
171
180
  fieldDef: ArrayFieldDefinition,
172
181
  arrayData: unknown[],
173
182
  baseContext: EvaluationContext,
174
- result: VisibilityResult
183
+ result: VisibilityResult,
175
184
  ): void {
176
185
  if (!fieldDef.itemFields) return;
177
186
 
@@ -183,11 +192,16 @@ function evaluateArrayItemVisibility(
183
192
  itemIndex: i,
184
193
  };
185
194
 
186
- for (const [fieldName, itemFieldDef] of Object.entries(fieldDef.itemFields)) {
195
+ for (const [fieldName, itemFieldDef] of Object.entries(
196
+ fieldDef.itemFields,
197
+ )) {
187
198
  const itemFieldPath = `${arrayPath}[${i}].${fieldName}`;
188
199
 
189
200
  if (itemFieldDef.visibleWhen) {
190
- result[itemFieldPath] = evaluateBoolean(itemFieldDef.visibleWhen, itemContext);
201
+ result[itemFieldPath] = evaluateBoolean(
202
+ itemFieldDef.visibleWhen,
203
+ itemContext,
204
+ );
191
205
  } else {
192
206
  result[itemFieldPath] = true;
193
207
  }
@@ -204,7 +218,7 @@ export function isFieldVisible(
204
218
  fieldPath: string,
205
219
  data: Record<string, unknown>,
206
220
  spec: Forma,
207
- options: VisibilityOptions = {}
221
+ options: VisibilityOptions = {},
208
222
  ): boolean {
209
223
  const fieldDef = spec.fields[fieldPath];
210
224
  if (!fieldDef) {
@@ -235,7 +249,7 @@ export function isFieldVisible(
235
249
  export function getPageVisibility(
236
250
  data: Record<string, unknown>,
237
251
  spec: Forma,
238
- options: VisibilityOptions = {}
252
+ options: VisibilityOptions = {},
239
253
  ): Record<string, boolean> {
240
254
  if (!spec.pages) {
241
255
  return {};
@@ -294,7 +308,7 @@ export function getPageVisibility(
294
308
  export function getOptionsVisibility(
295
309
  data: Record<string, unknown>,
296
310
  spec: Forma,
297
- options: VisibilityOptions = {}
311
+ options: VisibilityOptions = {},
298
312
  ): OptionsVisibilityResult {
299
313
  const computed = options.computed ?? calculate(data, spec);
300
314
  const result: Record<string, SelectOption[]> = {};
@@ -310,7 +324,11 @@ export function getOptionsVisibility(
310
324
  if (!fieldDef) continue;
311
325
 
312
326
  // Top-level fields with options
313
- if (isSelectionField(fieldDef) && fieldDef.options && fieldDef.options.length > 0) {
327
+ if (
328
+ isSelectionField(fieldDef) &&
329
+ fieldDef.options &&
330
+ fieldDef.options.length > 0
331
+ ) {
314
332
  result[fieldPath] = filterOptionsByContext(fieldDef.options, baseContext);
315
333
  }
316
334
 
@@ -318,7 +336,13 @@ export function getOptionsVisibility(
318
336
  if (isArrayField(fieldDef) && fieldDef.itemFields) {
319
337
  const arrayData = data[fieldPath];
320
338
  if (Array.isArray(arrayData)) {
321
- processArrayItemOptions(fieldPath, fieldDef, arrayData, baseContext, result);
339
+ processArrayItemOptions(
340
+ fieldPath,
341
+ fieldDef,
342
+ arrayData,
343
+ baseContext,
344
+ result,
345
+ );
322
346
  }
323
347
  }
324
348
  }
@@ -360,7 +384,7 @@ export function getVisibleOptions(
360
384
  computed?: Record<string, unknown>;
361
385
  item?: Record<string, unknown>;
362
386
  itemIndex?: number;
363
- } = {}
387
+ } = {},
364
388
  ): SelectOption[] {
365
389
  if (!options || options.length === 0) return [];
366
390
 
package/src/feel/index.ts CHANGED
@@ -110,7 +110,7 @@ function buildFeelContext(ctx: EvaluationContext): Record<string, unknown> {
110
110
  */
111
111
  export function evaluate<T = unknown>(
112
112
  expression: FEELExpression,
113
- context: EvaluationContext
113
+ context: EvaluationContext,
114
114
  ): EvaluationOutcome<T> {
115
115
  try {
116
116
  const feelContext = buildFeelContext(context);
@@ -140,13 +140,13 @@ export function evaluate<T = unknown>(
140
140
  */
141
141
  export function evaluateBoolean(
142
142
  expression: FEELExpression,
143
- context: EvaluationContext
143
+ context: EvaluationContext,
144
144
  ): boolean {
145
145
  const result = evaluate<boolean>(expression, context);
146
146
 
147
147
  if (!result.success) {
148
148
  console.warn(
149
- `FEEL expression error: ${result.error}\nExpression: ${result.expression}`
149
+ `FEEL expression error: ${result.error}\nExpression: ${result.expression}`,
150
150
  );
151
151
  return false;
152
152
  }
@@ -166,14 +166,14 @@ export function evaluateBoolean(
166
166
  if (result.value === null || result.value === undefined) {
167
167
  console.warn(
168
168
  `[forma] FEEL expression returned null (treating as false): "${expression}"\n` +
169
- `This often means a referenced field is undefined. See docs for null-safe patterns.`
169
+ `This often means a referenced field is undefined. See docs for null-safe patterns.`,
170
170
  );
171
171
  return false;
172
172
  }
173
173
 
174
174
  if (typeof result.value !== "boolean") {
175
175
  console.warn(
176
- `FEEL expression did not return boolean: ${expression}\nGot: ${typeof result.value}`
176
+ `FEEL expression did not return boolean: ${expression}\nGot: ${typeof result.value}`,
177
177
  );
178
178
  return false;
179
179
  }
@@ -192,20 +192,20 @@ export function evaluateBoolean(
192
192
  */
193
193
  export function evaluateNumber(
194
194
  expression: FEELExpression,
195
- context: EvaluationContext
195
+ context: EvaluationContext,
196
196
  ): number | null {
197
197
  const result = evaluate<number>(expression, context);
198
198
 
199
199
  if (!result.success) {
200
200
  console.warn(
201
- `FEEL expression error: ${result.error}\nExpression: ${result.expression}`
201
+ `FEEL expression error: ${result.error}\nExpression: ${result.expression}`,
202
202
  );
203
203
  return null;
204
204
  }
205
205
 
206
206
  if (typeof result.value !== "number") {
207
207
  console.warn(
208
- `FEEL expression did not return number: ${expression}\nGot: ${typeof result.value}`
208
+ `FEEL expression did not return number: ${expression}\nGot: ${typeof result.value}`,
209
209
  );
210
210
  return null;
211
211
  }
@@ -222,20 +222,20 @@ export function evaluateNumber(
222
222
  */
223
223
  export function evaluateString(
224
224
  expression: FEELExpression,
225
- context: EvaluationContext
225
+ context: EvaluationContext,
226
226
  ): string | null {
227
227
  const result = evaluate<string>(expression, context);
228
228
 
229
229
  if (!result.success) {
230
230
  console.warn(
231
- `FEEL expression error: ${result.error}\nExpression: ${result.expression}`
231
+ `FEEL expression error: ${result.error}\nExpression: ${result.expression}`,
232
232
  );
233
233
  return null;
234
234
  }
235
235
 
236
236
  if (typeof result.value !== "string") {
237
237
  console.warn(
238
- `FEEL expression did not return string: ${expression}\nGot: ${typeof result.value}`
238
+ `FEEL expression did not return string: ${expression}\nGot: ${typeof result.value}`,
239
239
  );
240
240
  return null;
241
241
  }
@@ -258,7 +258,7 @@ export function evaluateString(
258
258
  */
259
259
  export function evaluateBooleanBatch(
260
260
  expressions: Record<string, FEELExpression>,
261
- context: EvaluationContext
261
+ context: EvaluationContext,
262
262
  ): Record<string, boolean> {
263
263
  const results: Record<string, boolean> = {};
264
264
 
@@ -112,7 +112,7 @@ export function parseDecimalFormat(format: string): number | null {
112
112
  export function formatValue(
113
113
  value: unknown,
114
114
  format?: string,
115
- options?: FormatOptions
115
+ options?: FormatOptions,
116
116
  ): string {
117
117
  const { locale = "en-US", currency = "USD", nullDisplay } = options ?? {};
118
118