@formspec/constraints 0.1.0-alpha.19 → 0.1.0-alpha.21
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/browser.cjs.map +1 -1
- package/dist/browser.js.map +1 -1
- package/dist/constraints.d.ts +82 -0
- package/dist/defaults.d.ts +6 -0
- package/dist/defaults.d.ts.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loader.d.ts +10 -0
- package/dist/loader.d.ts.map +1 -1
- package/dist/types.d.ts +32 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/validators/field-options.d.ts +12 -0
- package/dist/validators/field-options.d.ts.map +1 -1
- package/dist/validators/field-types.d.ts +8 -0
- package/dist/validators/field-types.d.ts.map +1 -1
- package/dist/validators/formspec.d.ts +6 -0
- package/dist/validators/formspec.d.ts.map +1 -1
- package/dist/validators/layout.d.ts +8 -0
- package/dist/validators/layout.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/constraints.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ import type { FormSpec } from '@formspec/core';
|
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* Complete constraint configuration for a FormSpec project.
|
|
43
|
+
*
|
|
44
|
+
* @public
|
|
43
45
|
*/
|
|
44
46
|
export declare interface ConstraintConfig {
|
|
45
47
|
/** Field type constraints */
|
|
@@ -57,6 +59,8 @@ export declare interface ConstraintConfig {
|
|
|
57
59
|
/**
|
|
58
60
|
* Control options constraints - control which JSONForms Control.options are allowed.
|
|
59
61
|
* These are renderer-specific options that may not be universally supported.
|
|
62
|
+
*
|
|
63
|
+
* @public
|
|
60
64
|
*/
|
|
61
65
|
export declare interface ControlOptionConstraints {
|
|
62
66
|
/** format - renderer format hint (e.g., "radio", "textarea") */
|
|
@@ -75,12 +79,16 @@ export declare interface ControlOptionConstraints {
|
|
|
75
79
|
|
|
76
80
|
/**
|
|
77
81
|
* Default FormSpec configuration.
|
|
82
|
+
*
|
|
83
|
+
* @public
|
|
78
84
|
*/
|
|
79
85
|
export declare const DEFAULT_CONFIG: FormSpecConfig;
|
|
80
86
|
|
|
81
87
|
/**
|
|
82
88
|
* Default constraint configuration that allows all features.
|
|
83
89
|
* All constraints default to "off" (allowed).
|
|
90
|
+
*
|
|
91
|
+
* @public
|
|
84
92
|
*/
|
|
85
93
|
export declare const DEFAULT_CONSTRAINTS: ResolvedConstraintConfig;
|
|
86
94
|
|
|
@@ -103,6 +111,8 @@ export declare const DEFAULT_CONSTRAINTS: ResolvedConstraintConfig;
|
|
|
103
111
|
* },
|
|
104
112
|
* });
|
|
105
113
|
* ```
|
|
114
|
+
*
|
|
115
|
+
* @public
|
|
106
116
|
*/
|
|
107
117
|
export declare function defineConstraints(config: ConstraintConfig): ResolvedConstraintConfig;
|
|
108
118
|
|
|
@@ -112,16 +122,22 @@ export declare function defineConstraints(config: ConstraintConfig): ResolvedCon
|
|
|
112
122
|
*
|
|
113
123
|
* @param field - A field object with potential options
|
|
114
124
|
* @returns Array of present option names
|
|
125
|
+
*
|
|
126
|
+
* @public
|
|
115
127
|
*/
|
|
116
128
|
export declare function extractFieldOptions(field: Record<string, unknown>): FieldOption[];
|
|
117
129
|
|
|
118
130
|
/**
|
|
119
131
|
* Known field options that can be validated.
|
|
132
|
+
*
|
|
133
|
+
* @public
|
|
120
134
|
*/
|
|
121
135
|
export declare type FieldOption = "label" | "placeholder" | "required" | "minValue" | "maxValue" | "minItems" | "maxItems";
|
|
122
136
|
|
|
123
137
|
/**
|
|
124
138
|
* Field configuration option constraints - control which field options are allowed.
|
|
139
|
+
*
|
|
140
|
+
* @public
|
|
125
141
|
*/
|
|
126
142
|
export declare interface FieldOptionConstraints {
|
|
127
143
|
/** label - field label text */
|
|
@@ -142,6 +158,8 @@ export declare interface FieldOptionConstraints {
|
|
|
142
158
|
|
|
143
159
|
/**
|
|
144
160
|
* Context for field option validation.
|
|
161
|
+
*
|
|
162
|
+
* @public
|
|
145
163
|
*/
|
|
146
164
|
export declare interface FieldOptionsContext {
|
|
147
165
|
/** The field name */
|
|
@@ -155,6 +173,8 @@ export declare interface FieldOptionsContext {
|
|
|
155
173
|
/**
|
|
156
174
|
* Field type constraints - control which field types are allowed.
|
|
157
175
|
* Fine-grained control over each DSL field builder.
|
|
176
|
+
*
|
|
177
|
+
* @public
|
|
158
178
|
*/
|
|
159
179
|
export declare interface FieldTypeConstraints {
|
|
160
180
|
/** field.text() - basic text input */
|
|
@@ -177,6 +197,8 @@ export declare interface FieldTypeConstraints {
|
|
|
177
197
|
|
|
178
198
|
/**
|
|
179
199
|
* Context for field type validation.
|
|
200
|
+
*
|
|
201
|
+
* @public
|
|
180
202
|
*/
|
|
181
203
|
export declare interface FieldTypeContext {
|
|
182
204
|
/** The _field discriminator value (e.g., "text", "number", "enum") */
|
|
@@ -190,6 +212,8 @@ export declare interface FieldTypeContext {
|
|
|
190
212
|
/**
|
|
191
213
|
* Top-level FormSpec configuration file structure.
|
|
192
214
|
* The .formspec.yml file uses this structure.
|
|
215
|
+
*
|
|
216
|
+
* @public
|
|
193
217
|
*/
|
|
194
218
|
export declare interface FormSpecConfig {
|
|
195
219
|
/** Constraint configuration */
|
|
@@ -198,6 +222,8 @@ export declare interface FormSpecConfig {
|
|
|
198
222
|
|
|
199
223
|
/**
|
|
200
224
|
* Options for validating FormSpec elements.
|
|
225
|
+
*
|
|
226
|
+
* @public
|
|
201
227
|
*/
|
|
202
228
|
export declare interface FormSpecValidationOptions {
|
|
203
229
|
/** Constraint configuration (will be merged with defaults) */
|
|
@@ -210,6 +236,8 @@ export declare interface FormSpecValidationOptions {
|
|
|
210
236
|
* @param option - The option to check
|
|
211
237
|
* @param constraints - Field option constraints
|
|
212
238
|
* @returns Severity level, or "off" if not constrained
|
|
239
|
+
*
|
|
240
|
+
* @public
|
|
213
241
|
*/
|
|
214
242
|
export declare function getFieldOptionSeverity(option: FieldOption, constraints: FieldOptionConstraints): Severity;
|
|
215
243
|
|
|
@@ -219,6 +247,8 @@ export declare function getFieldOptionSeverity(option: FieldOption, constraints:
|
|
|
219
247
|
* @param fieldType - The _field discriminator value
|
|
220
248
|
* @param constraints - Field type constraints
|
|
221
249
|
* @returns Severity level, or "off" if not constrained
|
|
250
|
+
*
|
|
251
|
+
* @public
|
|
222
252
|
*/
|
|
223
253
|
export declare function getFieldTypeSeverity(fieldType: string, constraints: FieldTypeConstraints): Severity;
|
|
224
254
|
|
|
@@ -228,6 +258,8 @@ export declare function getFieldTypeSeverity(fieldType: string, constraints: Fie
|
|
|
228
258
|
* @param option - The option to check
|
|
229
259
|
* @param constraints - Field option constraints
|
|
230
260
|
* @returns true if allowed, false if disallowed
|
|
261
|
+
*
|
|
262
|
+
* @public
|
|
231
263
|
*/
|
|
232
264
|
export declare function isFieldOptionAllowed(option: FieldOption, constraints: FieldOptionConstraints): boolean;
|
|
233
265
|
|
|
@@ -238,6 +270,8 @@ export declare function isFieldOptionAllowed(option: FieldOption, constraints: F
|
|
|
238
270
|
* @param fieldType - The _field discriminator value
|
|
239
271
|
* @param constraints - Field type constraints
|
|
240
272
|
* @returns true if allowed, false if disallowed
|
|
273
|
+
*
|
|
274
|
+
* @public
|
|
241
275
|
*/
|
|
242
276
|
export declare function isFieldTypeAllowed(fieldType: string, constraints: FieldTypeConstraints): boolean;
|
|
243
277
|
|
|
@@ -247,6 +281,8 @@ export declare function isFieldTypeAllowed(fieldType: string, constraints: Field
|
|
|
247
281
|
* @param layoutType - The type of layout element
|
|
248
282
|
* @param constraints - Layout constraints
|
|
249
283
|
* @returns true if allowed, false if disallowed
|
|
284
|
+
*
|
|
285
|
+
* @public
|
|
250
286
|
*/
|
|
251
287
|
export declare function isLayoutTypeAllowed(layoutType: "group" | "conditional", constraints: LayoutConstraints): boolean;
|
|
252
288
|
|
|
@@ -256,11 +292,15 @@ export declare function isLayoutTypeAllowed(layoutType: "group" | "conditional",
|
|
|
256
292
|
* @param depth - Current nesting depth
|
|
257
293
|
* @param constraints - Layout constraints
|
|
258
294
|
* @returns true if allowed, false if exceeds limit
|
|
295
|
+
*
|
|
296
|
+
* @public
|
|
259
297
|
*/
|
|
260
298
|
export declare function isNestingDepthAllowed(depth: number, constraints: LayoutConstraints): boolean;
|
|
261
299
|
|
|
262
300
|
/**
|
|
263
301
|
* Layout and structure constraints - control grouping, conditionals, nesting.
|
|
302
|
+
*
|
|
303
|
+
* @public
|
|
264
304
|
*/
|
|
265
305
|
export declare interface LayoutConstraints {
|
|
266
306
|
/** group() - visual grouping of fields */
|
|
@@ -273,6 +313,8 @@ export declare interface LayoutConstraints {
|
|
|
273
313
|
|
|
274
314
|
/**
|
|
275
315
|
* Context for layout validation.
|
|
316
|
+
*
|
|
317
|
+
* @public
|
|
276
318
|
*/
|
|
277
319
|
export declare interface LayoutContext {
|
|
278
320
|
/** The type of layout element ("group" | "conditional") */
|
|
@@ -287,6 +329,8 @@ export declare interface LayoutContext {
|
|
|
287
329
|
|
|
288
330
|
/**
|
|
289
331
|
* JSONForms layout type constraints.
|
|
332
|
+
*
|
|
333
|
+
* @public
|
|
290
334
|
*/
|
|
291
335
|
export declare interface LayoutTypeConstraints {
|
|
292
336
|
/** VerticalLayout - stack elements vertically */
|
|
@@ -318,6 +362,8 @@ export declare interface LayoutTypeConstraints {
|
|
|
318
362
|
* // Load from specific file
|
|
319
363
|
* const result = await loadConfig({ configPath: '/path/to/config.yml' });
|
|
320
364
|
* ```
|
|
365
|
+
*
|
|
366
|
+
* @public
|
|
321
367
|
*/
|
|
322
368
|
export declare function loadConfig(options?: LoadConfigOptions): Promise<LoadConfigResult>;
|
|
323
369
|
|
|
@@ -327,11 +373,15 @@ export declare function loadConfig(options?: LoadConfigOptions): Promise<LoadCon
|
|
|
327
373
|
*
|
|
328
374
|
* @param yamlContent - The YAML content to parse
|
|
329
375
|
* @returns The parsed and merged configuration
|
|
376
|
+
*
|
|
377
|
+
* @public
|
|
330
378
|
*/
|
|
331
379
|
export declare function loadConfigFromString(yamlContent: string): ResolvedConstraintConfig;
|
|
332
380
|
|
|
333
381
|
/**
|
|
334
382
|
* Options for loading configuration.
|
|
383
|
+
*
|
|
384
|
+
* @public
|
|
335
385
|
*/
|
|
336
386
|
export declare interface LoadConfigOptions {
|
|
337
387
|
/**
|
|
@@ -353,6 +403,8 @@ export declare interface LoadConfigOptions {
|
|
|
353
403
|
|
|
354
404
|
/**
|
|
355
405
|
* Result of loading configuration.
|
|
406
|
+
*
|
|
407
|
+
* @public
|
|
356
408
|
*/
|
|
357
409
|
export declare interface LoadConfigResult {
|
|
358
410
|
/** The loaded and merged configuration */
|
|
@@ -365,12 +417,16 @@ export declare interface LoadConfigResult {
|
|
|
365
417
|
|
|
366
418
|
/**
|
|
367
419
|
* Merges user constraints with defaults, filling in any missing values.
|
|
420
|
+
*
|
|
421
|
+
* @public
|
|
368
422
|
*/
|
|
369
423
|
export declare function mergeWithDefaults(config: ConstraintConfig | undefined): ResolvedConstraintConfig;
|
|
370
424
|
|
|
371
425
|
/**
|
|
372
426
|
* Fully resolved constraint configuration with all properties required.
|
|
373
427
|
* This is the type returned by mergeWithDefaults().
|
|
428
|
+
*
|
|
429
|
+
* @public
|
|
374
430
|
*/
|
|
375
431
|
export declare interface ResolvedConstraintConfig {
|
|
376
432
|
fieldTypes: Required<FieldTypeConstraints>;
|
|
@@ -382,6 +438,8 @@ export declare interface ResolvedConstraintConfig {
|
|
|
382
438
|
|
|
383
439
|
/**
|
|
384
440
|
* Fully resolved rule constraints with all properties required.
|
|
441
|
+
*
|
|
442
|
+
* @public
|
|
385
443
|
*/
|
|
386
444
|
export declare interface ResolvedRuleConstraints {
|
|
387
445
|
enabled: Severity;
|
|
@@ -390,6 +448,8 @@ export declare interface ResolvedRuleConstraints {
|
|
|
390
448
|
|
|
391
449
|
/**
|
|
392
450
|
* Fully resolved UI schema constraints with all properties required.
|
|
451
|
+
*
|
|
452
|
+
* @public
|
|
393
453
|
*/
|
|
394
454
|
export declare interface ResolvedUISchemaConstraints {
|
|
395
455
|
layouts: Required<LayoutTypeConstraints>;
|
|
@@ -398,6 +458,8 @@ export declare interface ResolvedUISchemaConstraints {
|
|
|
398
458
|
|
|
399
459
|
/**
|
|
400
460
|
* JSONForms rule constraints.
|
|
461
|
+
*
|
|
462
|
+
* @public
|
|
401
463
|
*/
|
|
402
464
|
export declare interface RuleConstraints {
|
|
403
465
|
/** Whether rules are enabled at all */
|
|
@@ -408,6 +470,8 @@ export declare interface RuleConstraints {
|
|
|
408
470
|
|
|
409
471
|
/**
|
|
410
472
|
* JSONForms rule effect constraints.
|
|
473
|
+
*
|
|
474
|
+
* @public
|
|
411
475
|
*/
|
|
412
476
|
export declare interface RuleEffectConstraints {
|
|
413
477
|
/** SHOW - show element when condition is true */
|
|
@@ -425,11 +489,15 @@ export declare interface RuleEffectConstraints {
|
|
|
425
489
|
* - "error": Violation fails validation
|
|
426
490
|
* - "warn": Violation emits warning but passes
|
|
427
491
|
* - "off": Feature is allowed (no violation)
|
|
492
|
+
*
|
|
493
|
+
* @public
|
|
428
494
|
*/
|
|
429
495
|
export declare type Severity = "error" | "warn" | "off";
|
|
430
496
|
|
|
431
497
|
/**
|
|
432
498
|
* UI Schema feature constraints - control JSONForms-specific features.
|
|
499
|
+
*
|
|
500
|
+
* @public
|
|
433
501
|
*/
|
|
434
502
|
export declare interface UISchemaConstraints {
|
|
435
503
|
/** Layout type constraints */
|
|
@@ -444,6 +512,8 @@ export declare interface UISchemaConstraints {
|
|
|
444
512
|
* @param context - Information about the field and its options
|
|
445
513
|
* @param constraints - Field option constraints
|
|
446
514
|
* @returns Array of validation issues (empty if valid)
|
|
515
|
+
*
|
|
516
|
+
* @public
|
|
447
517
|
*/
|
|
448
518
|
export declare function validateFieldOptions(context: FieldOptionsContext, constraints: FieldOptionConstraints): ValidationIssue[];
|
|
449
519
|
|
|
@@ -453,6 +523,8 @@ export declare function validateFieldOptions(context: FieldOptionsContext, const
|
|
|
453
523
|
* @param context - Information about the field being validated
|
|
454
524
|
* @param constraints - Field type constraints
|
|
455
525
|
* @returns Array of validation issues (empty if valid)
|
|
526
|
+
*
|
|
527
|
+
* @public
|
|
456
528
|
*/
|
|
457
529
|
export declare function validateFieldTypes(context: FieldTypeContext, constraints: FieldTypeConstraints): ValidationIssue[];
|
|
458
530
|
|
|
@@ -462,6 +534,8 @@ export declare function validateFieldTypes(context: FieldTypeContext, constraint
|
|
|
462
534
|
* @param formSpec - The FormSpec to validate
|
|
463
535
|
* @param options - Validation options including constraints
|
|
464
536
|
* @returns Validation result with all issues found
|
|
537
|
+
*
|
|
538
|
+
* @public
|
|
465
539
|
*/
|
|
466
540
|
export declare function validateFormSpec(formSpec: FormSpec<readonly FormElement[]>, options?: FormSpecValidationOptions): ValidationResult;
|
|
467
541
|
|
|
@@ -499,6 +573,8 @@ export declare function validateFormSpec(formSpec: FormSpec<readonly FormElement
|
|
|
499
573
|
* console.error('Validation failed:', result.issues);
|
|
500
574
|
* }
|
|
501
575
|
* ```
|
|
576
|
+
*
|
|
577
|
+
* @public
|
|
502
578
|
*/
|
|
503
579
|
export declare function validateFormSpecElements(elements: readonly FormElement[], options?: FormSpecValidationOptions): ValidationResult;
|
|
504
580
|
|
|
@@ -508,11 +584,15 @@ export declare function validateFormSpecElements(elements: readonly FormElement[
|
|
|
508
584
|
* @param context - Information about the layout element
|
|
509
585
|
* @param constraints - Layout constraints
|
|
510
586
|
* @returns Array of validation issues (empty if valid)
|
|
587
|
+
*
|
|
588
|
+
* @public
|
|
511
589
|
*/
|
|
512
590
|
export declare function validateLayout(context: LayoutContext, constraints: LayoutConstraints): ValidationIssue[];
|
|
513
591
|
|
|
514
592
|
/**
|
|
515
593
|
* A single validation issue found during constraint checking.
|
|
594
|
+
*
|
|
595
|
+
* @public
|
|
516
596
|
*/
|
|
517
597
|
export declare interface ValidationIssue {
|
|
518
598
|
/** Unique code identifying the issue type */
|
|
@@ -533,6 +613,8 @@ export declare interface ValidationIssue {
|
|
|
533
613
|
|
|
534
614
|
/**
|
|
535
615
|
* Result of validating a FormSpec or schema against constraints.
|
|
616
|
+
*
|
|
617
|
+
* @public
|
|
536
618
|
*/
|
|
537
619
|
export declare interface ValidationResult {
|
|
538
620
|
/** Whether validation passed (no errors, warnings OK) */
|
package/dist/defaults.d.ts
CHANGED
|
@@ -2,14 +2,20 @@ import type { ConstraintConfig, FormSpecConfig, ResolvedConstraintConfig } from
|
|
|
2
2
|
/**
|
|
3
3
|
* Default constraint configuration that allows all features.
|
|
4
4
|
* All constraints default to "off" (allowed).
|
|
5
|
+
*
|
|
6
|
+
* @public
|
|
5
7
|
*/
|
|
6
8
|
export declare const DEFAULT_CONSTRAINTS: ResolvedConstraintConfig;
|
|
7
9
|
/**
|
|
8
10
|
* Default FormSpec configuration.
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
9
13
|
*/
|
|
10
14
|
export declare const DEFAULT_CONFIG: FormSpecConfig;
|
|
11
15
|
/**
|
|
12
16
|
* Merges user constraints with defaults, filling in any missing values.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
13
19
|
*/
|
|
14
20
|
export declare function mergeWithDefaults(config: ConstraintConfig | undefined): ResolvedConstraintConfig;
|
|
15
21
|
//# sourceMappingURL=defaults.d.ts.map
|
package/dist/defaults.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE7F
|
|
1
|
+
{"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../src/defaults.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE7F;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,EAAE,wBAmDjC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,cAE5B,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,GAAG,wBAAwB,CAwChG"}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/loader.ts","../src/defaults.ts","../src/validators/field-types.ts","../src/validators/layout.ts","../src/validators/field-options.ts","../src/validators/formspec.ts"],"sourcesContent":["/**\n * \\@formspec/constraints\n *\n * Constraint validation for FormSpec - restrict features based on target\n * environment capabilities.\n *\n * This package provides:\n * - Type definitions for constraint configuration\n * - YAML/TypeScript config file loading\n * - Core validation logic for FormSpec elements\n * - JSON Schema for .formspec.yml validation\n *\n * @example\n * ```ts\n * import { loadConfig, validateFormSpecElements } from '@formspec/constraints';\n * import { formspec, field } from '@formspec/dsl';\n *\n * // Load constraints from .formspec.yml\n * const { config } = await loadConfig();\n *\n * // Create a form\n * const form = formspec(\n * field.text(\"name\"),\n * field.dynamicEnum(\"country\", \"countries\"),\n * );\n *\n * // Validate against constraints\n * const result = validateFormSpecElements(form.elements, { constraints: config });\n *\n * if (!result.valid) {\n * console.error('Validation failed:', result.issues);\n * }\n * ```\n *\n * @packageDocumentation\n */\n\n// Types\nexport type {\n Severity,\n FieldTypeConstraints,\n LayoutConstraints,\n LayoutTypeConstraints,\n RuleEffectConstraints,\n RuleConstraints,\n UISchemaConstraints,\n FieldOptionConstraints,\n ControlOptionConstraints,\n ConstraintConfig,\n ResolvedConstraintConfig,\n ResolvedUISchemaConstraints,\n ResolvedRuleConstraints,\n FormSpecConfig,\n ValidationIssue,\n ValidationResult,\n} from \"./types.js\";\n\n// Config loading\nexport {\n loadConfig,\n loadConfigFromString,\n defineConstraints,\n type LoadConfigOptions,\n type LoadConfigResult,\n} from \"./loader.js\";\n\n// Defaults\nexport { DEFAULT_CONSTRAINTS, DEFAULT_CONFIG, mergeWithDefaults } from \"./defaults.js\";\n\n// Validators\nexport {\n validateFormSpecElements,\n validateFormSpec,\n type FormSpecValidationOptions,\n} from \"./validators/formspec.js\";\n\nexport {\n validateFieldTypes,\n isFieldTypeAllowed,\n getFieldTypeSeverity,\n type FieldTypeContext,\n} from \"./validators/field-types.js\";\n\nexport {\n validateLayout,\n isLayoutTypeAllowed,\n isNestingDepthAllowed,\n type LayoutContext,\n} from \"./validators/layout.js\";\n\nexport {\n validateFieldOptions,\n extractFieldOptions,\n isFieldOptionAllowed,\n getFieldOptionSeverity,\n type FieldOptionsContext,\n type FieldOption,\n} from \"./validators/field-options.js\";\n","import { readFile } from \"node:fs/promises\";\nimport { resolve, dirname } from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport type { FormSpecConfig, ConstraintConfig, ResolvedConstraintConfig } from \"./types.js\";\nimport { mergeWithDefaults } from \"./defaults.js\";\n\n/**\n * Default config file names to search for (in order of priority).\n */\nconst CONFIG_FILE_NAMES = [\".formspec.yml\", \".formspec.yaml\", \"formspec.yml\"];\n\n/**\n * Options for loading configuration.\n */\nexport interface LoadConfigOptions {\n /**\n * The directory to search for config files.\n * Defaults to process.cwd().\n */\n cwd?: string;\n\n /**\n * Explicit path to a config file.\n * If provided, skips searching for default config file names.\n */\n configPath?: string;\n\n /**\n * Whether to search parent directories for config files.\n * Defaults to true.\n */\n searchParents?: boolean;\n}\n\n/**\n * Result of loading configuration.\n */\nexport interface LoadConfigResult {\n /** The loaded and merged configuration */\n config: ResolvedConstraintConfig;\n /** The path to the config file that was loaded (if any) */\n configPath: string | null;\n /** Whether a config file was found */\n found: boolean;\n}\n\n/**\n * Searches for a config file in the given directory and optionally parent directories.\n */\nasync function findConfigFile(startDir: string, searchParents: boolean): Promise<string | null> {\n let currentDir = resolve(startDir);\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- intentional infinite loop with break conditions\n while (true) {\n for (const fileName of CONFIG_FILE_NAMES) {\n const filePath = resolve(currentDir, fileName);\n try {\n await readFile(filePath);\n return filePath;\n } catch {\n // File doesn't exist, continue searching\n }\n }\n\n if (!searchParents) {\n break;\n }\n\n const parentDir = dirname(currentDir);\n // Reached filesystem root when dirname returns same path\n if (parentDir === currentDir) {\n break;\n }\n currentDir = parentDir;\n }\n\n return null;\n}\n\n/**\n * Parses a YAML config file and returns the FormSpecConfig.\n */\nasync function parseConfigFile(filePath: string): Promise<FormSpecConfig> {\n const content = await readFile(filePath, \"utf-8\");\n const parsed = parseYaml(content) as unknown;\n\n if (parsed === null || parsed === undefined) {\n return {};\n }\n\n if (typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(`Invalid config file at ${filePath}: expected an object, got ${typeof parsed}`);\n }\n\n return parsed as FormSpecConfig;\n}\n\n/**\n * Loads FormSpec constraint configuration from a .formspec.yml file.\n *\n * @param options - Options for loading configuration\n * @returns The loaded configuration with defaults applied\n *\n * @example\n * ```ts\n * // Load from current directory (searches for .formspec.yml)\n * const result = await loadConfig();\n *\n * // Load from specific directory\n * const result = await loadConfig({ cwd: '/path/to/project' });\n *\n * // Load from specific file\n * const result = await loadConfig({ configPath: '/path/to/config.yml' });\n * ```\n */\nexport async function loadConfig(options: LoadConfigOptions = {}): Promise<LoadConfigResult> {\n const { cwd = process.cwd(), configPath, searchParents = true } = options;\n\n let resolvedPath: string | null = null;\n\n if (configPath) {\n resolvedPath = resolve(cwd, configPath);\n try {\n await readFile(resolvedPath);\n } catch {\n throw new Error(`Config file not found at ${resolvedPath}`);\n }\n } else {\n resolvedPath = await findConfigFile(cwd, searchParents);\n }\n\n if (!resolvedPath) {\n return {\n config: mergeWithDefaults(undefined),\n configPath: null,\n found: false,\n };\n }\n\n const fileConfig = await parseConfigFile(resolvedPath);\n const config = mergeWithDefaults(fileConfig.constraints);\n\n return {\n config,\n configPath: resolvedPath,\n found: true,\n };\n}\n\n/**\n * Synchronously loads config from a pre-parsed YAML string.\n * Useful for testing or when config is already available.\n *\n * @param yamlContent - The YAML content to parse\n * @returns The parsed and merged configuration\n */\nexport function loadConfigFromString(yamlContent: string): ResolvedConstraintConfig {\n const parsed = parseYaml(yamlContent) as FormSpecConfig | null | undefined;\n\n if (parsed === null || parsed === undefined) {\n return mergeWithDefaults(undefined);\n }\n\n if (typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(`Invalid config content: expected an object, got ${typeof parsed}`);\n }\n\n return mergeWithDefaults(parsed.constraints);\n}\n\n/**\n * Creates a constraint configuration directly from an object.\n * Useful for programmatic configuration without YAML.\n *\n * @param config - Partial constraint configuration\n * @returns Complete configuration with defaults applied\n *\n * @example\n * ```ts\n * const config = defineConstraints({\n * fieldTypes: {\n * dynamicEnum: 'error',\n * dynamicSchema: 'error',\n * },\n * layout: {\n * group: 'error',\n * },\n * });\n * ```\n */\nexport function defineConstraints(config: ConstraintConfig): ResolvedConstraintConfig {\n return mergeWithDefaults(config);\n}\n","import type { ConstraintConfig, FormSpecConfig, ResolvedConstraintConfig } from \"./types.js\";\n\n/**\n * Default constraint configuration that allows all features.\n * All constraints default to \"off\" (allowed).\n */\nexport const DEFAULT_CONSTRAINTS: ResolvedConstraintConfig = {\n fieldTypes: {\n text: \"off\",\n number: \"off\",\n boolean: \"off\",\n staticEnum: \"off\",\n dynamicEnum: \"off\",\n dynamicSchema: \"off\",\n array: \"off\",\n object: \"off\",\n },\n layout: {\n group: \"off\",\n conditionals: \"off\",\n maxNestingDepth: Infinity,\n },\n uiSchema: {\n layouts: {\n VerticalLayout: \"off\",\n HorizontalLayout: \"off\",\n Group: \"off\",\n Categorization: \"off\",\n Category: \"off\",\n },\n rules: {\n enabled: \"off\",\n effects: {\n SHOW: \"off\",\n HIDE: \"off\",\n ENABLE: \"off\",\n DISABLE: \"off\",\n },\n },\n },\n fieldOptions: {\n label: \"off\",\n placeholder: \"off\",\n required: \"off\",\n minValue: \"off\",\n maxValue: \"off\",\n minItems: \"off\",\n maxItems: \"off\",\n },\n controlOptions: {\n format: \"off\",\n readonly: \"off\",\n multi: \"off\",\n showUnfocusedDescription: \"off\",\n hideRequiredAsterisk: \"off\",\n custom: {},\n },\n};\n\n/**\n * Default FormSpec configuration.\n */\nexport const DEFAULT_CONFIG: FormSpecConfig = {\n constraints: DEFAULT_CONSTRAINTS,\n};\n\n/**\n * Merges user constraints with defaults, filling in any missing values.\n */\nexport function mergeWithDefaults(config: ConstraintConfig | undefined): ResolvedConstraintConfig {\n if (!config) {\n return DEFAULT_CONSTRAINTS;\n }\n\n return {\n fieldTypes: {\n ...DEFAULT_CONSTRAINTS.fieldTypes,\n ...config.fieldTypes,\n },\n layout: {\n ...DEFAULT_CONSTRAINTS.layout,\n ...config.layout,\n },\n uiSchema: {\n layouts: {\n ...DEFAULT_CONSTRAINTS.uiSchema.layouts,\n ...config.uiSchema?.layouts,\n },\n rules: {\n enabled: config.uiSchema?.rules?.enabled ?? DEFAULT_CONSTRAINTS.uiSchema.rules.enabled,\n effects: {\n ...DEFAULT_CONSTRAINTS.uiSchema.rules.effects,\n ...config.uiSchema?.rules?.effects,\n },\n },\n },\n fieldOptions: {\n ...DEFAULT_CONSTRAINTS.fieldOptions,\n ...config.fieldOptions,\n },\n controlOptions: {\n ...DEFAULT_CONSTRAINTS.controlOptions,\n ...config.controlOptions,\n custom: {\n ...DEFAULT_CONSTRAINTS.controlOptions.custom,\n ...config.controlOptions?.custom,\n },\n },\n };\n}\n","import type { FieldTypeConstraints, Severity, ValidationIssue } from \"../types.js\";\n\n/**\n * Maps FormSpec field._field values to constraint config keys.\n */\nconst FIELD_TYPE_MAP: Record<string, keyof FieldTypeConstraints> = {\n text: \"text\",\n number: \"number\",\n boolean: \"boolean\",\n enum: \"staticEnum\",\n dynamic_enum: \"dynamicEnum\",\n dynamic_schema: \"dynamicSchema\",\n array: \"array\",\n object: \"object\",\n};\n\n/**\n * Human-readable names for field types.\n */\nconst FIELD_TYPE_NAMES: Record<string, string> = {\n text: \"text field\",\n number: \"number field\",\n boolean: \"boolean field\",\n enum: \"static enum field\",\n dynamic_enum: \"dynamic enum field\",\n dynamic_schema: \"dynamic schema field\",\n array: \"array field\",\n object: \"object field\",\n};\n\n/**\n * Context for field type validation.\n */\nexport interface FieldTypeContext {\n /** The _field discriminator value (e.g., \"text\", \"number\", \"enum\") */\n fieldType: string;\n /** The field name */\n fieldName: string;\n /** Optional path for nested fields */\n path?: string;\n}\n\n/**\n * Validates a field type against constraints.\n *\n * @param context - Information about the field being validated\n * @param constraints - Field type constraints\n * @returns Array of validation issues (empty if valid)\n */\nexport function validateFieldTypes(\n context: FieldTypeContext,\n constraints: FieldTypeConstraints\n): ValidationIssue[] {\n const issues: ValidationIssue[] = [];\n\n const constraintKey = FIELD_TYPE_MAP[context.fieldType];\n if (!constraintKey) {\n // Unknown field type, skip validation\n return issues;\n }\n\n const severity = constraints[constraintKey];\n if (severity && severity !== \"off\") {\n const fieldTypeName = FIELD_TYPE_NAMES[context.fieldType] ?? context.fieldType;\n issues.push(createFieldTypeIssue(context, fieldTypeName, severity));\n }\n\n return issues;\n}\n\n/**\n * Creates a validation issue for a disallowed field type.\n */\nfunction createFieldTypeIssue(\n context: FieldTypeContext,\n fieldTypeName: string,\n severity: Severity\n): ValidationIssue {\n const path = context.path ?? context.fieldName;\n return {\n code: \"DISALLOWED_FIELD_TYPE\",\n message: `Field \"${context.fieldName}\" uses ${fieldTypeName}, which is not allowed in this project`,\n severity: severity === \"error\" ? \"error\" : \"warning\",\n category: \"fieldTypes\",\n path,\n fieldName: context.fieldName,\n fieldType: context.fieldType,\n };\n}\n\n/**\n * Checks if a field type is allowed by the constraints.\n * Useful for quick checks without generating issues.\n *\n * @param fieldType - The _field discriminator value\n * @param constraints - Field type constraints\n * @returns true if allowed, false if disallowed\n */\nexport function isFieldTypeAllowed(fieldType: string, constraints: FieldTypeConstraints): boolean {\n const constraintKey = FIELD_TYPE_MAP[fieldType];\n if (!constraintKey) {\n return true; // Unknown types are allowed by default\n }\n\n const severity = constraints[constraintKey];\n return !severity || severity === \"off\";\n}\n\n/**\n * Gets the severity level for a field type.\n *\n * @param fieldType - The _field discriminator value\n * @param constraints - Field type constraints\n * @returns Severity level, or \"off\" if not constrained\n */\nexport function getFieldTypeSeverity(\n fieldType: string,\n constraints: FieldTypeConstraints\n): Severity {\n const constraintKey = FIELD_TYPE_MAP[fieldType];\n if (!constraintKey) {\n return \"off\";\n }\n\n return constraints[constraintKey] ?? \"off\";\n}\n","import type { LayoutConstraints, Severity, ValidationIssue } from \"../types.js\";\n\n/**\n * Context for layout validation.\n */\nexport interface LayoutContext {\n /** The type of layout element (\"group\" | \"conditional\") */\n layoutType: \"group\" | \"conditional\";\n /** Optional label for the element (for groups) */\n label?: string;\n /** Current nesting depth */\n depth: number;\n /** Path to this element */\n path?: string;\n}\n\n/**\n * Validates a layout element against constraints.\n *\n * @param context - Information about the layout element\n * @param constraints - Layout constraints\n * @returns Array of validation issues (empty if valid)\n */\nexport function validateLayout(\n context: LayoutContext,\n constraints: LayoutConstraints\n): ValidationIssue[] {\n const issues: ValidationIssue[] = [];\n\n // Check if groups are allowed\n if (context.layoutType === \"group\") {\n const groupSeverity = constraints.group;\n if (groupSeverity && groupSeverity !== \"off\") {\n issues.push(createGroupIssue(context, groupSeverity));\n }\n }\n\n // Check if conditionals are allowed\n if (context.layoutType === \"conditional\") {\n const conditionalSeverity = constraints.conditionals;\n if (conditionalSeverity && conditionalSeverity !== \"off\") {\n issues.push(createConditionalIssue(context, conditionalSeverity));\n }\n }\n\n // Check nesting depth (applies to both groups and fields within nested structures)\n const maxDepth = constraints.maxNestingDepth;\n if (maxDepth !== undefined && context.depth > maxDepth) {\n issues.push(createNestingDepthIssue(context, maxDepth));\n }\n\n return issues;\n}\n\n/**\n * Creates a validation issue for a disallowed group.\n */\nfunction createGroupIssue(context: LayoutContext, severity: Severity): ValidationIssue {\n const labelInfo = context.label ? ` \"${context.label}\"` : \"\";\n const issue: ValidationIssue = {\n code: \"DISALLOWED_GROUP\",\n message: `Group${labelInfo} is not allowed - visual grouping is not supported in this project`,\n severity: severity === \"error\" ? \"error\" : \"warning\",\n category: \"layout\",\n };\n if (context.path !== undefined) {\n issue.path = context.path;\n }\n return issue;\n}\n\n/**\n * Creates a validation issue for a disallowed conditional.\n */\nfunction createConditionalIssue(context: LayoutContext, severity: Severity): ValidationIssue {\n const issue: ValidationIssue = {\n code: \"DISALLOWED_CONDITIONAL\",\n message: `Conditional visibility (when/is) is not allowed in this project`,\n severity: severity === \"error\" ? \"error\" : \"warning\",\n category: \"layout\",\n };\n if (context.path !== undefined) {\n issue.path = context.path;\n }\n return issue;\n}\n\n/**\n * Creates a validation issue for exceeding nesting depth.\n */\nfunction createNestingDepthIssue(context: LayoutContext, maxDepth: number): ValidationIssue {\n const issue: ValidationIssue = {\n code: \"EXCEEDED_NESTING_DEPTH\",\n message: `Nesting depth ${String(context.depth)} exceeds maximum allowed depth of ${String(maxDepth)}`,\n severity: \"error\",\n category: \"layout\",\n };\n if (context.path !== undefined) {\n issue.path = context.path;\n }\n return issue;\n}\n\n/**\n * Checks if a layout type is allowed by the constraints.\n *\n * @param layoutType - The type of layout element\n * @param constraints - Layout constraints\n * @returns true if allowed, false if disallowed\n */\nexport function isLayoutTypeAllowed(\n layoutType: \"group\" | \"conditional\",\n constraints: LayoutConstraints\n): boolean {\n if (layoutType === \"group\") {\n const severity = constraints.group;\n return !severity || severity === \"off\";\n }\n\n // layoutType === \"conditional\"\n const severity = constraints.conditionals;\n return !severity || severity === \"off\";\n}\n\n/**\n * Checks if a nesting depth is allowed.\n *\n * @param depth - Current nesting depth\n * @param constraints - Layout constraints\n * @returns true if allowed, false if exceeds limit\n */\nexport function isNestingDepthAllowed(depth: number, constraints: LayoutConstraints): boolean {\n const maxDepth = constraints.maxNestingDepth;\n if (maxDepth === undefined) {\n return true;\n }\n return depth <= maxDepth;\n}\n","import type { FieldOptionConstraints, Severity, ValidationIssue } from \"../types.js\";\n\n/**\n * Known field options that can be validated.\n */\nexport type FieldOption =\n | \"label\"\n | \"placeholder\"\n | \"required\"\n | \"minValue\"\n | \"maxValue\"\n | \"minItems\"\n | \"maxItems\";\n\n/**\n * Context for field option validation.\n */\nexport interface FieldOptionsContext {\n /** The field name */\n fieldName: string;\n /** Which options are present on this field */\n presentOptions: FieldOption[];\n /** Path to this field */\n path?: string;\n}\n\n/**\n * Validates field options against constraints.\n *\n * @param context - Information about the field and its options\n * @param constraints - Field option constraints\n * @returns Array of validation issues (empty if valid)\n */\nexport function validateFieldOptions(\n context: FieldOptionsContext,\n constraints: FieldOptionConstraints\n): ValidationIssue[] {\n const issues: ValidationIssue[] = [];\n\n for (const option of context.presentOptions) {\n const severity = constraints[option];\n if (severity && severity !== \"off\") {\n issues.push(createFieldOptionIssue(context, option, severity));\n }\n }\n\n return issues;\n}\n\n/**\n * Creates a validation issue for a disallowed field option.\n */\nfunction createFieldOptionIssue(\n context: FieldOptionsContext,\n option: FieldOption,\n severity: Severity\n): ValidationIssue {\n const path = context.path ?? context.fieldName;\n return {\n code: \"DISALLOWED_FIELD_OPTION\",\n message: `Field \"${context.fieldName}\" uses the \"${option}\" option, which is not allowed in this project`,\n severity: severity === \"error\" ? \"error\" : \"warning\",\n category: \"fieldOptions\",\n path,\n fieldName: context.fieldName,\n };\n}\n\n/**\n * Extracts which options are present on a field object.\n * Works with FormSpec field types.\n *\n * @param field - A field object with potential options\n * @returns Array of present option names\n */\nexport function extractFieldOptions(field: Record<string, unknown>): FieldOption[] {\n const options: FieldOption[] = [];\n\n if (field[\"label\"] !== undefined) options.push(\"label\");\n if (field[\"placeholder\"] !== undefined) options.push(\"placeholder\");\n if (field[\"required\"] !== undefined) options.push(\"required\");\n // NumberField uses \"min\"/\"max\" in core types, map to \"minValue\"/\"maxValue\" constraints\n if (field[\"min\"] !== undefined || field[\"minValue\"] !== undefined) options.push(\"minValue\");\n if (field[\"max\"] !== undefined || field[\"maxValue\"] !== undefined) options.push(\"maxValue\");\n if (field[\"minItems\"] !== undefined) options.push(\"minItems\");\n if (field[\"maxItems\"] !== undefined) options.push(\"maxItems\");\n\n return options;\n}\n\n/**\n * Checks if a specific field option is allowed.\n *\n * @param option - The option to check\n * @param constraints - Field option constraints\n * @returns true if allowed, false if disallowed\n */\nexport function isFieldOptionAllowed(\n option: FieldOption,\n constraints: FieldOptionConstraints\n): boolean {\n const severity = constraints[option];\n return !severity || severity === \"off\";\n}\n\n/**\n * Gets the severity level for a field option.\n *\n * @param option - The option to check\n * @param constraints - Field option constraints\n * @returns Severity level, or \"off\" if not constrained\n */\nexport function getFieldOptionSeverity(\n option: FieldOption,\n constraints: FieldOptionConstraints\n): Severity {\n return constraints[option] ?? \"off\";\n}\n","import type { FormElement, FormSpec, AnyField } from \"@formspec/core\";\nimport type {\n ConstraintConfig,\n ResolvedConstraintConfig,\n ValidationIssue,\n ValidationResult,\n} from \"../types.js\";\nimport { mergeWithDefaults } from \"../defaults.js\";\nimport { validateFieldTypes } from \"./field-types.js\";\nimport { validateLayout } from \"./layout.js\";\nimport { validateFieldOptions, extractFieldOptions } from \"./field-options.js\";\n\n/**\n * Options for validating FormSpec elements.\n */\nexport interface FormSpecValidationOptions {\n /** Constraint configuration (will be merged with defaults) */\n constraints?: ConstraintConfig;\n}\n\n/**\n * Validates FormSpec elements against constraints.\n *\n * This is the main entry point for validating a form specification\n * against a constraint configuration. It walks through all elements\n * and checks each one against the configured constraints.\n *\n * @param elements - FormSpec elements to validate\n * @param options - Validation options including constraints\n * @returns Validation result with all issues found\n *\n * @example\n * ```ts\n * import { formspec, field, group } from '@formspec/dsl';\n * import { validateFormSpecElements, defineConstraints } from '@formspec/constraints';\n *\n * const form = formspec(\n * group(\"Contact\",\n * field.text(\"name\"),\n * field.dynamicEnum(\"country\", \"countries\"),\n * ),\n * );\n *\n * const result = validateFormSpecElements(form.elements, {\n * constraints: {\n * fieldTypes: { dynamicEnum: 'error' },\n * layout: { group: 'error' },\n * },\n * });\n *\n * if (!result.valid) {\n * console.error('Validation failed:', result.issues);\n * }\n * ```\n */\nexport function validateFormSpecElements(\n elements: readonly FormElement[],\n options: FormSpecValidationOptions = {}\n): ValidationResult {\n const constraints = mergeWithDefaults(options.constraints);\n const issues: ValidationIssue[] = [];\n\n // Walk through all elements\n walkElements(elements, constraints, issues, \"\", 0);\n\n return {\n valid: !issues.some((issue) => issue.severity === \"error\"),\n issues,\n };\n}\n\n/**\n * Validates a complete FormSpec against constraints.\n *\n * @param formSpec - The FormSpec to validate\n * @param options - Validation options including constraints\n * @returns Validation result with all issues found\n */\nexport function validateFormSpec(\n formSpec: FormSpec<readonly FormElement[]>,\n options: FormSpecValidationOptions = {}\n): ValidationResult {\n return validateFormSpecElements(formSpec.elements, options);\n}\n\n/**\n * Recursively walks through FormSpec elements and validates each one.\n */\nfunction walkElements(\n elements: readonly FormElement[],\n constraints: ResolvedConstraintConfig,\n issues: ValidationIssue[],\n pathPrefix: string,\n depth: number\n): void {\n for (const element of elements) {\n const elementPath = pathPrefix;\n\n if (element._type === \"field\") {\n validateField(element, constraints, issues, elementPath, depth);\n } else if (element._type === \"group\") {\n validateGroup(element, constraints, issues, elementPath, depth);\n } else {\n // element._type === \"conditional\"\n validateConditional(element, constraints, issues, elementPath, depth);\n }\n }\n}\n\n/**\n * Validates a field element.\n */\nfunction validateField(\n field: AnyField,\n constraints: ResolvedConstraintConfig,\n issues: ValidationIssue[],\n pathPrefix: string,\n depth: number\n): void {\n const fieldPath = pathPrefix ? `${pathPrefix}/${field.name}` : field.name;\n\n // Validate field type\n const fieldTypeIssues = validateFieldTypes(\n {\n fieldType: field._field,\n fieldName: field.name,\n path: fieldPath,\n },\n constraints.fieldTypes\n );\n issues.push(...fieldTypeIssues);\n\n // Validate field options\n const presentOptions = extractFieldOptions(field as unknown as Record<string, unknown>);\n if (presentOptions.length > 0) {\n const optionIssues = validateFieldOptions(\n {\n fieldName: field.name,\n presentOptions,\n path: fieldPath,\n },\n constraints.fieldOptions\n );\n issues.push(...optionIssues);\n }\n\n // Check nesting depth for array/object fields\n if (field._field === \"array\" || field._field === \"object\") {\n const layoutIssues = validateLayout(\n {\n layoutType: \"group\", // Arrays/objects contribute to nesting depth\n depth: depth + 1,\n path: fieldPath,\n },\n constraints.layout\n );\n // Only add nesting depth issues, not group issues\n issues.push(...layoutIssues.filter((issue) => issue.code === \"EXCEEDED_NESTING_DEPTH\"));\n\n // Recursively validate nested elements\n if (field._field === \"array\" && \"items\" in field) {\n walkElements(\n field.items as readonly FormElement[],\n constraints,\n issues,\n `${fieldPath}[]`,\n depth + 1\n );\n } else if (\"properties\" in field) {\n // field._field === \"object\"\n walkElements(\n field.properties as readonly FormElement[],\n constraints,\n issues,\n fieldPath,\n depth + 1\n );\n }\n }\n}\n\n/**\n * Validates a group element.\n */\nfunction validateGroup(\n group: {\n readonly _type: \"group\";\n readonly label: string;\n readonly elements: readonly FormElement[];\n },\n constraints: ResolvedConstraintConfig,\n issues: ValidationIssue[],\n pathPrefix: string,\n depth: number\n): void {\n const groupPath = pathPrefix ? `${pathPrefix}/[group:${group.label}]` : `[group:${group.label}]`;\n\n // Validate group usage\n const layoutIssues = validateLayout(\n {\n layoutType: \"group\",\n label: group.label,\n depth,\n path: groupPath,\n },\n constraints.layout\n );\n issues.push(...layoutIssues);\n\n // Recursively validate nested elements (groups don't increase nesting depth for schema)\n walkElements(group.elements, constraints, issues, pathPrefix, depth);\n}\n\n/**\n * Validates a conditional element.\n */\nfunction validateConditional(\n conditional: {\n readonly _type: \"conditional\";\n readonly field: string;\n readonly value: unknown;\n readonly elements: readonly FormElement[];\n },\n constraints: ResolvedConstraintConfig,\n issues: ValidationIssue[],\n pathPrefix: string,\n depth: number\n): void {\n const condPath = pathPrefix\n ? `${pathPrefix}/[when:${conditional.field}=${String(conditional.value)}]`\n : `[when:${conditional.field}=${String(conditional.value)}]`;\n\n // Validate conditional usage\n const layoutIssues = validateLayout(\n {\n layoutType: \"conditional\",\n depth,\n path: condPath,\n },\n constraints.layout\n );\n issues.push(...layoutIssues);\n\n // Recursively validate nested elements\n walkElements(conditional.elements, constraints, issues, pathPrefix, depth);\n}\n"],"mappings":"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","names":["parseYaml","severity"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/loader.ts","../src/defaults.ts","../src/validators/field-types.ts","../src/validators/layout.ts","../src/validators/field-options.ts","../src/validators/formspec.ts"],"sourcesContent":["/**\n * \\@formspec/constraints\n *\n * Constraint validation for FormSpec - restrict features based on target\n * environment capabilities.\n *\n * This package provides:\n * - Type definitions for constraint configuration\n * - YAML/TypeScript config file loading\n * - Core validation logic for FormSpec elements\n * - JSON Schema for .formspec.yml validation\n *\n * @example\n * ```ts\n * import { loadConfig, validateFormSpecElements } from '@formspec/constraints';\n * import { formspec, field } from '@formspec/dsl';\n *\n * // Load constraints from .formspec.yml\n * const { config } = await loadConfig();\n *\n * // Create a form\n * const form = formspec(\n * field.text(\"name\"),\n * field.dynamicEnum(\"country\", \"countries\"),\n * );\n *\n * // Validate against constraints\n * const result = validateFormSpecElements(form.elements, { constraints: config });\n *\n * if (!result.valid) {\n * console.error('Validation failed:', result.issues);\n * }\n * ```\n *\n * @packageDocumentation\n */\n\n// Types\nexport type {\n Severity,\n FieldTypeConstraints,\n LayoutConstraints,\n LayoutTypeConstraints,\n RuleEffectConstraints,\n RuleConstraints,\n UISchemaConstraints,\n FieldOptionConstraints,\n ControlOptionConstraints,\n ConstraintConfig,\n ResolvedConstraintConfig,\n ResolvedUISchemaConstraints,\n ResolvedRuleConstraints,\n FormSpecConfig,\n ValidationIssue,\n ValidationResult,\n} from \"./types.js\";\n\n// Config loading\nexport {\n loadConfig,\n loadConfigFromString,\n defineConstraints,\n type LoadConfigOptions,\n type LoadConfigResult,\n} from \"./loader.js\";\n\n// Defaults\nexport { DEFAULT_CONSTRAINTS, DEFAULT_CONFIG, mergeWithDefaults } from \"./defaults.js\";\n\n// Validators\nexport {\n validateFormSpecElements,\n validateFormSpec,\n type FormSpecValidationOptions,\n} from \"./validators/formspec.js\";\n\nexport {\n validateFieldTypes,\n isFieldTypeAllowed,\n getFieldTypeSeverity,\n type FieldTypeContext,\n} from \"./validators/field-types.js\";\n\nexport {\n validateLayout,\n isLayoutTypeAllowed,\n isNestingDepthAllowed,\n type LayoutContext,\n} from \"./validators/layout.js\";\n\nexport {\n validateFieldOptions,\n extractFieldOptions,\n isFieldOptionAllowed,\n getFieldOptionSeverity,\n type FieldOptionsContext,\n type FieldOption,\n} from \"./validators/field-options.js\";\n","import { readFile } from \"node:fs/promises\";\nimport { resolve, dirname } from \"node:path\";\nimport { parse as parseYaml } from \"yaml\";\nimport type { FormSpecConfig, ConstraintConfig, ResolvedConstraintConfig } from \"./types.js\";\nimport { mergeWithDefaults } from \"./defaults.js\";\n\n/**\n * Default config file names to search for (in order of priority).\n */\nconst CONFIG_FILE_NAMES = [\".formspec.yml\", \".formspec.yaml\", \"formspec.yml\"];\n\n/**\n * Options for loading configuration.\n *\n * @public\n */\nexport interface LoadConfigOptions {\n /**\n * The directory to search for config files.\n * Defaults to process.cwd().\n */\n cwd?: string;\n\n /**\n * Explicit path to a config file.\n * If provided, skips searching for default config file names.\n */\n configPath?: string;\n\n /**\n * Whether to search parent directories for config files.\n * Defaults to true.\n */\n searchParents?: boolean;\n}\n\n/**\n * Result of loading configuration.\n *\n * @public\n */\nexport interface LoadConfigResult {\n /** The loaded and merged configuration */\n config: ResolvedConstraintConfig;\n /** The path to the config file that was loaded (if any) */\n configPath: string | null;\n /** Whether a config file was found */\n found: boolean;\n}\n\n/**\n * Searches for a config file in the given directory and optionally parent directories.\n */\nasync function findConfigFile(startDir: string, searchParents: boolean): Promise<string | null> {\n let currentDir = resolve(startDir);\n\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- intentional infinite loop with break conditions\n while (true) {\n for (const fileName of CONFIG_FILE_NAMES) {\n const filePath = resolve(currentDir, fileName);\n try {\n await readFile(filePath);\n return filePath;\n } catch {\n // File doesn't exist, continue searching\n }\n }\n\n if (!searchParents) {\n break;\n }\n\n const parentDir = dirname(currentDir);\n // Reached filesystem root when dirname returns same path\n if (parentDir === currentDir) {\n break;\n }\n currentDir = parentDir;\n }\n\n return null;\n}\n\n/**\n * Parses a YAML config file and returns the FormSpecConfig.\n */\nasync function parseConfigFile(filePath: string): Promise<FormSpecConfig> {\n const content = await readFile(filePath, \"utf-8\");\n const parsed = parseYaml(content) as unknown;\n\n if (parsed === null || parsed === undefined) {\n return {};\n }\n\n if (typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(`Invalid config file at ${filePath}: expected an object, got ${typeof parsed}`);\n }\n\n return parsed as FormSpecConfig;\n}\n\n/**\n * Loads FormSpec constraint configuration from a .formspec.yml file.\n *\n * @param options - Options for loading configuration\n * @returns The loaded configuration with defaults applied\n *\n * @example\n * ```ts\n * // Load from current directory (searches for .formspec.yml)\n * const result = await loadConfig();\n *\n * // Load from specific directory\n * const result = await loadConfig({ cwd: '/path/to/project' });\n *\n * // Load from specific file\n * const result = await loadConfig({ configPath: '/path/to/config.yml' });\n * ```\n *\n * @public\n */\nexport async function loadConfig(options: LoadConfigOptions = {}): Promise<LoadConfigResult> {\n const { cwd = process.cwd(), configPath, searchParents = true } = options;\n\n let resolvedPath: string | null = null;\n\n if (configPath) {\n resolvedPath = resolve(cwd, configPath);\n try {\n await readFile(resolvedPath);\n } catch {\n throw new Error(`Config file not found at ${resolvedPath}`);\n }\n } else {\n resolvedPath = await findConfigFile(cwd, searchParents);\n }\n\n if (!resolvedPath) {\n return {\n config: mergeWithDefaults(undefined),\n configPath: null,\n found: false,\n };\n }\n\n const fileConfig = await parseConfigFile(resolvedPath);\n const config = mergeWithDefaults(fileConfig.constraints);\n\n return {\n config,\n configPath: resolvedPath,\n found: true,\n };\n}\n\n/**\n * Synchronously loads config from a pre-parsed YAML string.\n * Useful for testing or when config is already available.\n *\n * @param yamlContent - The YAML content to parse\n * @returns The parsed and merged configuration\n *\n * @public\n */\nexport function loadConfigFromString(yamlContent: string): ResolvedConstraintConfig {\n const parsed = parseYaml(yamlContent) as FormSpecConfig | null | undefined;\n\n if (parsed === null || parsed === undefined) {\n return mergeWithDefaults(undefined);\n }\n\n if (typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(`Invalid config content: expected an object, got ${typeof parsed}`);\n }\n\n return mergeWithDefaults(parsed.constraints);\n}\n\n/**\n * Creates a constraint configuration directly from an object.\n * Useful for programmatic configuration without YAML.\n *\n * @param config - Partial constraint configuration\n * @returns Complete configuration with defaults applied\n *\n * @example\n * ```ts\n * const config = defineConstraints({\n * fieldTypes: {\n * dynamicEnum: 'error',\n * dynamicSchema: 'error',\n * },\n * layout: {\n * group: 'error',\n * },\n * });\n * ```\n *\n * @public\n */\nexport function defineConstraints(config: ConstraintConfig): ResolvedConstraintConfig {\n return mergeWithDefaults(config);\n}\n","import type { ConstraintConfig, FormSpecConfig, ResolvedConstraintConfig } from \"./types.js\";\n\n/**\n * Default constraint configuration that allows all features.\n * All constraints default to \"off\" (allowed).\n *\n * @public\n */\nexport const DEFAULT_CONSTRAINTS: ResolvedConstraintConfig = {\n fieldTypes: {\n text: \"off\",\n number: \"off\",\n boolean: \"off\",\n staticEnum: \"off\",\n dynamicEnum: \"off\",\n dynamicSchema: \"off\",\n array: \"off\",\n object: \"off\",\n },\n layout: {\n group: \"off\",\n conditionals: \"off\",\n maxNestingDepth: Infinity,\n },\n uiSchema: {\n layouts: {\n VerticalLayout: \"off\",\n HorizontalLayout: \"off\",\n Group: \"off\",\n Categorization: \"off\",\n Category: \"off\",\n },\n rules: {\n enabled: \"off\",\n effects: {\n SHOW: \"off\",\n HIDE: \"off\",\n ENABLE: \"off\",\n DISABLE: \"off\",\n },\n },\n },\n fieldOptions: {\n label: \"off\",\n placeholder: \"off\",\n required: \"off\",\n minValue: \"off\",\n maxValue: \"off\",\n minItems: \"off\",\n maxItems: \"off\",\n },\n controlOptions: {\n format: \"off\",\n readonly: \"off\",\n multi: \"off\",\n showUnfocusedDescription: \"off\",\n hideRequiredAsterisk: \"off\",\n custom: {},\n },\n};\n\n/**\n * Default FormSpec configuration.\n *\n * @public\n */\nexport const DEFAULT_CONFIG: FormSpecConfig = {\n constraints: DEFAULT_CONSTRAINTS,\n};\n\n/**\n * Merges user constraints with defaults, filling in any missing values.\n *\n * @public\n */\nexport function mergeWithDefaults(config: ConstraintConfig | undefined): ResolvedConstraintConfig {\n if (!config) {\n return DEFAULT_CONSTRAINTS;\n }\n\n return {\n fieldTypes: {\n ...DEFAULT_CONSTRAINTS.fieldTypes,\n ...config.fieldTypes,\n },\n layout: {\n ...DEFAULT_CONSTRAINTS.layout,\n ...config.layout,\n },\n uiSchema: {\n layouts: {\n ...DEFAULT_CONSTRAINTS.uiSchema.layouts,\n ...config.uiSchema?.layouts,\n },\n rules: {\n enabled: config.uiSchema?.rules?.enabled ?? DEFAULT_CONSTRAINTS.uiSchema.rules.enabled,\n effects: {\n ...DEFAULT_CONSTRAINTS.uiSchema.rules.effects,\n ...config.uiSchema?.rules?.effects,\n },\n },\n },\n fieldOptions: {\n ...DEFAULT_CONSTRAINTS.fieldOptions,\n ...config.fieldOptions,\n },\n controlOptions: {\n ...DEFAULT_CONSTRAINTS.controlOptions,\n ...config.controlOptions,\n custom: {\n ...DEFAULT_CONSTRAINTS.controlOptions.custom,\n ...config.controlOptions?.custom,\n },\n },\n };\n}\n","import type { FieldTypeConstraints, Severity, ValidationIssue } from \"../types.js\";\n\n/**\n * Maps FormSpec field._field values to constraint config keys.\n */\nconst FIELD_TYPE_MAP: Record<string, keyof FieldTypeConstraints> = {\n text: \"text\",\n number: \"number\",\n boolean: \"boolean\",\n enum: \"staticEnum\",\n dynamic_enum: \"dynamicEnum\",\n dynamic_schema: \"dynamicSchema\",\n array: \"array\",\n object: \"object\",\n};\n\n/**\n * Human-readable names for field types.\n */\nconst FIELD_TYPE_NAMES: Record<string, string> = {\n text: \"text field\",\n number: \"number field\",\n boolean: \"boolean field\",\n enum: \"static enum field\",\n dynamic_enum: \"dynamic enum field\",\n dynamic_schema: \"dynamic schema field\",\n array: \"array field\",\n object: \"object field\",\n};\n\n/**\n * Context for field type validation.\n *\n * @public\n */\nexport interface FieldTypeContext {\n /** The _field discriminator value (e.g., \"text\", \"number\", \"enum\") */\n fieldType: string;\n /** The field name */\n fieldName: string;\n /** Optional path for nested fields */\n path?: string;\n}\n\n/**\n * Validates a field type against constraints.\n *\n * @param context - Information about the field being validated\n * @param constraints - Field type constraints\n * @returns Array of validation issues (empty if valid)\n *\n * @public\n */\nexport function validateFieldTypes(\n context: FieldTypeContext,\n constraints: FieldTypeConstraints\n): ValidationIssue[] {\n const issues: ValidationIssue[] = [];\n\n const constraintKey = FIELD_TYPE_MAP[context.fieldType];\n if (!constraintKey) {\n // Unknown field type, skip validation\n return issues;\n }\n\n const severity = constraints[constraintKey];\n if (severity && severity !== \"off\") {\n const fieldTypeName = FIELD_TYPE_NAMES[context.fieldType] ?? context.fieldType;\n issues.push(createFieldTypeIssue(context, fieldTypeName, severity));\n }\n\n return issues;\n}\n\n/**\n * Creates a validation issue for a disallowed field type.\n */\nfunction createFieldTypeIssue(\n context: FieldTypeContext,\n fieldTypeName: string,\n severity: Severity\n): ValidationIssue {\n const path = context.path ?? context.fieldName;\n return {\n code: \"DISALLOWED_FIELD_TYPE\",\n message: `Field \"${context.fieldName}\" uses ${fieldTypeName}, which is not allowed in this project`,\n severity: severity === \"error\" ? \"error\" : \"warning\",\n category: \"fieldTypes\",\n path,\n fieldName: context.fieldName,\n fieldType: context.fieldType,\n };\n}\n\n/**\n * Checks if a field type is allowed by the constraints.\n * Useful for quick checks without generating issues.\n *\n * @param fieldType - The _field discriminator value\n * @param constraints - Field type constraints\n * @returns true if allowed, false if disallowed\n *\n * @public\n */\nexport function isFieldTypeAllowed(fieldType: string, constraints: FieldTypeConstraints): boolean {\n const constraintKey = FIELD_TYPE_MAP[fieldType];\n if (!constraintKey) {\n return true; // Unknown types are allowed by default\n }\n\n const severity = constraints[constraintKey];\n return !severity || severity === \"off\";\n}\n\n/**\n * Gets the severity level for a field type.\n *\n * @param fieldType - The _field discriminator value\n * @param constraints - Field type constraints\n * @returns Severity level, or \"off\" if not constrained\n *\n * @public\n */\nexport function getFieldTypeSeverity(\n fieldType: string,\n constraints: FieldTypeConstraints\n): Severity {\n const constraintKey = FIELD_TYPE_MAP[fieldType];\n if (!constraintKey) {\n return \"off\";\n }\n\n return constraints[constraintKey] ?? \"off\";\n}\n","import type { LayoutConstraints, Severity, ValidationIssue } from \"../types.js\";\n\n/**\n * Context for layout validation.\n *\n * @public\n */\nexport interface LayoutContext {\n /** The type of layout element (\"group\" | \"conditional\") */\n layoutType: \"group\" | \"conditional\";\n /** Optional label for the element (for groups) */\n label?: string;\n /** Current nesting depth */\n depth: number;\n /** Path to this element */\n path?: string;\n}\n\n/**\n * Validates a layout element against constraints.\n *\n * @param context - Information about the layout element\n * @param constraints - Layout constraints\n * @returns Array of validation issues (empty if valid)\n *\n * @public\n */\nexport function validateLayout(\n context: LayoutContext,\n constraints: LayoutConstraints\n): ValidationIssue[] {\n const issues: ValidationIssue[] = [];\n\n // Check if groups are allowed\n if (context.layoutType === \"group\") {\n const groupSeverity = constraints.group;\n if (groupSeverity && groupSeverity !== \"off\") {\n issues.push(createGroupIssue(context, groupSeverity));\n }\n }\n\n // Check if conditionals are allowed\n if (context.layoutType === \"conditional\") {\n const conditionalSeverity = constraints.conditionals;\n if (conditionalSeverity && conditionalSeverity !== \"off\") {\n issues.push(createConditionalIssue(context, conditionalSeverity));\n }\n }\n\n // Check nesting depth (applies to both groups and fields within nested structures)\n const maxDepth = constraints.maxNestingDepth;\n if (maxDepth !== undefined && context.depth > maxDepth) {\n issues.push(createNestingDepthIssue(context, maxDepth));\n }\n\n return issues;\n}\n\n/**\n * Creates a validation issue for a disallowed group.\n */\nfunction createGroupIssue(context: LayoutContext, severity: Severity): ValidationIssue {\n const labelInfo = context.label ? ` \"${context.label}\"` : \"\";\n const issue: ValidationIssue = {\n code: \"DISALLOWED_GROUP\",\n message: `Group${labelInfo} is not allowed - visual grouping is not supported in this project`,\n severity: severity === \"error\" ? \"error\" : \"warning\",\n category: \"layout\",\n };\n if (context.path !== undefined) {\n issue.path = context.path;\n }\n return issue;\n}\n\n/**\n * Creates a validation issue for a disallowed conditional.\n */\nfunction createConditionalIssue(context: LayoutContext, severity: Severity): ValidationIssue {\n const issue: ValidationIssue = {\n code: \"DISALLOWED_CONDITIONAL\",\n message: `Conditional visibility (when/is) is not allowed in this project`,\n severity: severity === \"error\" ? \"error\" : \"warning\",\n category: \"layout\",\n };\n if (context.path !== undefined) {\n issue.path = context.path;\n }\n return issue;\n}\n\n/**\n * Creates a validation issue for exceeding nesting depth.\n */\nfunction createNestingDepthIssue(context: LayoutContext, maxDepth: number): ValidationIssue {\n const issue: ValidationIssue = {\n code: \"EXCEEDED_NESTING_DEPTH\",\n message: `Nesting depth ${String(context.depth)} exceeds maximum allowed depth of ${String(maxDepth)}`,\n severity: \"error\",\n category: \"layout\",\n };\n if (context.path !== undefined) {\n issue.path = context.path;\n }\n return issue;\n}\n\n/**\n * Checks if a layout type is allowed by the constraints.\n *\n * @param layoutType - The type of layout element\n * @param constraints - Layout constraints\n * @returns true if allowed, false if disallowed\n *\n * @public\n */\nexport function isLayoutTypeAllowed(\n layoutType: \"group\" | \"conditional\",\n constraints: LayoutConstraints\n): boolean {\n if (layoutType === \"group\") {\n const severity = constraints.group;\n return !severity || severity === \"off\";\n }\n\n // layoutType === \"conditional\"\n const severity = constraints.conditionals;\n return !severity || severity === \"off\";\n}\n\n/**\n * Checks if a nesting depth is allowed.\n *\n * @param depth - Current nesting depth\n * @param constraints - Layout constraints\n * @returns true if allowed, false if exceeds limit\n *\n * @public\n */\nexport function isNestingDepthAllowed(depth: number, constraints: LayoutConstraints): boolean {\n const maxDepth = constraints.maxNestingDepth;\n if (maxDepth === undefined) {\n return true;\n }\n return depth <= maxDepth;\n}\n","import type { FieldOptionConstraints, Severity, ValidationIssue } from \"../types.js\";\n\n/**\n * Known field options that can be validated.\n *\n * @public\n */\nexport type FieldOption =\n | \"label\"\n | \"placeholder\"\n | \"required\"\n | \"minValue\"\n | \"maxValue\"\n | \"minItems\"\n | \"maxItems\";\n\n/**\n * Context for field option validation.\n *\n * @public\n */\nexport interface FieldOptionsContext {\n /** The field name */\n fieldName: string;\n /** Which options are present on this field */\n presentOptions: FieldOption[];\n /** Path to this field */\n path?: string;\n}\n\n/**\n * Validates field options against constraints.\n *\n * @param context - Information about the field and its options\n * @param constraints - Field option constraints\n * @returns Array of validation issues (empty if valid)\n *\n * @public\n */\nexport function validateFieldOptions(\n context: FieldOptionsContext,\n constraints: FieldOptionConstraints\n): ValidationIssue[] {\n const issues: ValidationIssue[] = [];\n\n for (const option of context.presentOptions) {\n const severity = constraints[option];\n if (severity && severity !== \"off\") {\n issues.push(createFieldOptionIssue(context, option, severity));\n }\n }\n\n return issues;\n}\n\n/**\n * Creates a validation issue for a disallowed field option.\n */\nfunction createFieldOptionIssue(\n context: FieldOptionsContext,\n option: FieldOption,\n severity: Severity\n): ValidationIssue {\n const path = context.path ?? context.fieldName;\n return {\n code: \"DISALLOWED_FIELD_OPTION\",\n message: `Field \"${context.fieldName}\" uses the \"${option}\" option, which is not allowed in this project`,\n severity: severity === \"error\" ? \"error\" : \"warning\",\n category: \"fieldOptions\",\n path,\n fieldName: context.fieldName,\n };\n}\n\n/**\n * Extracts which options are present on a field object.\n * Works with FormSpec field types.\n *\n * @param field - A field object with potential options\n * @returns Array of present option names\n *\n * @public\n */\nexport function extractFieldOptions(field: Record<string, unknown>): FieldOption[] {\n const options: FieldOption[] = [];\n\n if (field[\"label\"] !== undefined) options.push(\"label\");\n if (field[\"placeholder\"] !== undefined) options.push(\"placeholder\");\n if (field[\"required\"] !== undefined) options.push(\"required\");\n // NumberField uses \"min\"/\"max\" in core types, map to \"minValue\"/\"maxValue\" constraints\n if (field[\"min\"] !== undefined || field[\"minValue\"] !== undefined) options.push(\"minValue\");\n if (field[\"max\"] !== undefined || field[\"maxValue\"] !== undefined) options.push(\"maxValue\");\n if (field[\"minItems\"] !== undefined) options.push(\"minItems\");\n if (field[\"maxItems\"] !== undefined) options.push(\"maxItems\");\n\n return options;\n}\n\n/**\n * Checks if a specific field option is allowed.\n *\n * @param option - The option to check\n * @param constraints - Field option constraints\n * @returns true if allowed, false if disallowed\n *\n * @public\n */\nexport function isFieldOptionAllowed(\n option: FieldOption,\n constraints: FieldOptionConstraints\n): boolean {\n const severity = constraints[option];\n return !severity || severity === \"off\";\n}\n\n/**\n * Gets the severity level for a field option.\n *\n * @param option - The option to check\n * @param constraints - Field option constraints\n * @returns Severity level, or \"off\" if not constrained\n *\n * @public\n */\nexport function getFieldOptionSeverity(\n option: FieldOption,\n constraints: FieldOptionConstraints\n): Severity {\n return constraints[option] ?? \"off\";\n}\n","import type { FormElement, FormSpec, AnyField } from \"@formspec/core\";\nimport type {\n ConstraintConfig,\n ResolvedConstraintConfig,\n ValidationIssue,\n ValidationResult,\n} from \"../types.js\";\nimport { mergeWithDefaults } from \"../defaults.js\";\nimport { validateFieldTypes } from \"./field-types.js\";\nimport { validateLayout } from \"./layout.js\";\nimport { validateFieldOptions, extractFieldOptions } from \"./field-options.js\";\n\n/**\n * Options for validating FormSpec elements.\n *\n * @public\n */\nexport interface FormSpecValidationOptions {\n /** Constraint configuration (will be merged with defaults) */\n constraints?: ConstraintConfig;\n}\n\n/**\n * Validates FormSpec elements against constraints.\n *\n * This is the main entry point for validating a form specification\n * against a constraint configuration. It walks through all elements\n * and checks each one against the configured constraints.\n *\n * @param elements - FormSpec elements to validate\n * @param options - Validation options including constraints\n * @returns Validation result with all issues found\n *\n * @example\n * ```ts\n * import { formspec, field, group } from '@formspec/dsl';\n * import { validateFormSpecElements, defineConstraints } from '@formspec/constraints';\n *\n * const form = formspec(\n * group(\"Contact\",\n * field.text(\"name\"),\n * field.dynamicEnum(\"country\", \"countries\"),\n * ),\n * );\n *\n * const result = validateFormSpecElements(form.elements, {\n * constraints: {\n * fieldTypes: { dynamicEnum: 'error' },\n * layout: { group: 'error' },\n * },\n * });\n *\n * if (!result.valid) {\n * console.error('Validation failed:', result.issues);\n * }\n * ```\n *\n * @public\n */\nexport function validateFormSpecElements(\n elements: readonly FormElement[],\n options: FormSpecValidationOptions = {}\n): ValidationResult {\n const constraints = mergeWithDefaults(options.constraints);\n const issues: ValidationIssue[] = [];\n\n // Walk through all elements\n walkElements(elements, constraints, issues, \"\", 0);\n\n return {\n valid: !issues.some((issue) => issue.severity === \"error\"),\n issues,\n };\n}\n\n/**\n * Validates a complete FormSpec against constraints.\n *\n * @param formSpec - The FormSpec to validate\n * @param options - Validation options including constraints\n * @returns Validation result with all issues found\n *\n * @public\n */\nexport function validateFormSpec(\n formSpec: FormSpec<readonly FormElement[]>,\n options: FormSpecValidationOptions = {}\n): ValidationResult {\n return validateFormSpecElements(formSpec.elements, options);\n}\n\n/**\n * Recursively walks through FormSpec elements and validates each one.\n */\nfunction walkElements(\n elements: readonly FormElement[],\n constraints: ResolvedConstraintConfig,\n issues: ValidationIssue[],\n pathPrefix: string,\n depth: number\n): void {\n for (const element of elements) {\n const elementPath = pathPrefix;\n\n if (element._type === \"field\") {\n validateField(element, constraints, issues, elementPath, depth);\n } else if (element._type === \"group\") {\n validateGroup(element, constraints, issues, elementPath, depth);\n } else {\n // element._type === \"conditional\"\n validateConditional(element, constraints, issues, elementPath, depth);\n }\n }\n}\n\n/**\n * Validates a field element.\n */\nfunction validateField(\n field: AnyField,\n constraints: ResolvedConstraintConfig,\n issues: ValidationIssue[],\n pathPrefix: string,\n depth: number\n): void {\n const fieldPath = pathPrefix ? `${pathPrefix}/${field.name}` : field.name;\n\n // Validate field type\n const fieldTypeIssues = validateFieldTypes(\n {\n fieldType: field._field,\n fieldName: field.name,\n path: fieldPath,\n },\n constraints.fieldTypes\n );\n issues.push(...fieldTypeIssues);\n\n // Validate field options\n const presentOptions = extractFieldOptions(field as unknown as Record<string, unknown>);\n if (presentOptions.length > 0) {\n const optionIssues = validateFieldOptions(\n {\n fieldName: field.name,\n presentOptions,\n path: fieldPath,\n },\n constraints.fieldOptions\n );\n issues.push(...optionIssues);\n }\n\n // Check nesting depth for array/object fields\n if (field._field === \"array\" || field._field === \"object\") {\n const layoutIssues = validateLayout(\n {\n layoutType: \"group\", // Arrays/objects contribute to nesting depth\n depth: depth + 1,\n path: fieldPath,\n },\n constraints.layout\n );\n // Only add nesting depth issues, not group issues\n issues.push(...layoutIssues.filter((issue) => issue.code === \"EXCEEDED_NESTING_DEPTH\"));\n\n // Recursively validate nested elements\n if (field._field === \"array\" && \"items\" in field) {\n walkElements(\n field.items as readonly FormElement[],\n constraints,\n issues,\n `${fieldPath}[]`,\n depth + 1\n );\n } else if (\"properties\" in field) {\n // field._field === \"object\"\n walkElements(\n field.properties as readonly FormElement[],\n constraints,\n issues,\n fieldPath,\n depth + 1\n );\n }\n }\n}\n\n/**\n * Validates a group element.\n */\nfunction validateGroup(\n group: {\n readonly _type: \"group\";\n readonly label: string;\n readonly elements: readonly FormElement[];\n },\n constraints: ResolvedConstraintConfig,\n issues: ValidationIssue[],\n pathPrefix: string,\n depth: number\n): void {\n const groupPath = pathPrefix ? `${pathPrefix}/[group:${group.label}]` : `[group:${group.label}]`;\n\n // Validate group usage\n const layoutIssues = validateLayout(\n {\n layoutType: \"group\",\n label: group.label,\n depth,\n path: groupPath,\n },\n constraints.layout\n );\n issues.push(...layoutIssues);\n\n // Recursively validate nested elements (groups don't increase nesting depth for schema)\n walkElements(group.elements, constraints, issues, pathPrefix, depth);\n}\n\n/**\n * Validates a conditional element.\n */\nfunction validateConditional(\n conditional: {\n readonly _type: \"conditional\";\n readonly field: string;\n readonly value: unknown;\n readonly elements: readonly FormElement[];\n },\n constraints: ResolvedConstraintConfig,\n issues: ValidationIssue[],\n pathPrefix: string,\n depth: number\n): void {\n const condPath = pathPrefix\n ? `${pathPrefix}/[when:${conditional.field}=${String(conditional.value)}]`\n : `[when:${conditional.field}=${String(conditional.value)}]`;\n\n // Validate conditional usage\n const layoutIssues = validateLayout(\n {\n layoutType: \"conditional\",\n depth,\n path: condPath,\n },\n constraints.layout\n );\n issues.push(...layoutIssues);\n\n // Recursively validate nested elements\n walkElements(conditional.elements, constraints, issues, pathPrefix, depth);\n}\n"],"mappings":"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","names":["parseYaml","severity"]}
|