@formspec/constraints 0.1.0-alpha.10 → 0.1.0-alpha.11
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 +458 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.js +413 -61
- package/dist/browser.js.map +1 -1
- package/dist/index.cjs +526 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +481 -45
- package/dist/index.js.map +1 -1
- package/package.json +10 -7
- package/dist/__tests__/loader.test.js +0 -133
- package/dist/__tests__/loader.test.js.map +0 -1
- package/dist/__tests__/validators.test.js +0 -404
- package/dist/__tests__/validators.test.js.map +0 -1
- package/dist/defaults.js +0 -106
- package/dist/defaults.js.map +0 -1
- package/dist/loader.js +0 -140
- package/dist/loader.js.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/validators/field-options.js +0 -79
- package/dist/validators/field-options.js.map +0 -1
- package/dist/validators/field-types.js +0 -93
- package/dist/validators/field-types.js.map +0 -1
- package/dist/validators/formspec.js +0 -152
- package/dist/validators/formspec.js.map +0 -1
- package/dist/validators/index.js +0 -5
- package/dist/validators/index.js.map +0 -1
- package/dist/validators/layout.js +0 -107
- package/dist/validators/layout.js.map +0 -1
package/dist/browser.cjs
ADDED
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/browser.ts
|
|
21
|
+
var browser_exports = {};
|
|
22
|
+
__export(browser_exports, {
|
|
23
|
+
DEFAULT_CONFIG: () => DEFAULT_CONFIG,
|
|
24
|
+
DEFAULT_CONSTRAINTS: () => DEFAULT_CONSTRAINTS,
|
|
25
|
+
defineConstraints: () => defineConstraints,
|
|
26
|
+
extractFieldOptions: () => extractFieldOptions,
|
|
27
|
+
getFieldOptionSeverity: () => getFieldOptionSeverity,
|
|
28
|
+
getFieldTypeSeverity: () => getFieldTypeSeverity,
|
|
29
|
+
isFieldOptionAllowed: () => isFieldOptionAllowed,
|
|
30
|
+
isFieldTypeAllowed: () => isFieldTypeAllowed,
|
|
31
|
+
isLayoutTypeAllowed: () => isLayoutTypeAllowed,
|
|
32
|
+
isNestingDepthAllowed: () => isNestingDepthAllowed,
|
|
33
|
+
loadConfigFromString: () => loadConfigFromString,
|
|
34
|
+
mergeWithDefaults: () => mergeWithDefaults,
|
|
35
|
+
validateFieldOptions: () => validateFieldOptions,
|
|
36
|
+
validateFieldTypes: () => validateFieldTypes,
|
|
37
|
+
validateFormSpec: () => validateFormSpec,
|
|
38
|
+
validateFormSpecElements: () => validateFormSpecElements,
|
|
39
|
+
validateLayout: () => validateLayout
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(browser_exports);
|
|
42
|
+
var import_yaml = require("yaml");
|
|
43
|
+
|
|
44
|
+
// src/defaults.ts
|
|
45
|
+
var DEFAULT_CONSTRAINTS = {
|
|
46
|
+
fieldTypes: {
|
|
47
|
+
text: "off",
|
|
48
|
+
number: "off",
|
|
49
|
+
boolean: "off",
|
|
50
|
+
staticEnum: "off",
|
|
51
|
+
dynamicEnum: "off",
|
|
52
|
+
dynamicSchema: "off",
|
|
53
|
+
array: "off",
|
|
54
|
+
object: "off"
|
|
55
|
+
},
|
|
56
|
+
layout: {
|
|
57
|
+
group: "off",
|
|
58
|
+
conditionals: "off",
|
|
59
|
+
maxNestingDepth: Infinity
|
|
60
|
+
},
|
|
61
|
+
uiSchema: {
|
|
62
|
+
layouts: {
|
|
63
|
+
VerticalLayout: "off",
|
|
64
|
+
HorizontalLayout: "off",
|
|
65
|
+
Group: "off",
|
|
66
|
+
Categorization: "off",
|
|
67
|
+
Category: "off"
|
|
68
|
+
},
|
|
69
|
+
rules: {
|
|
70
|
+
enabled: "off",
|
|
71
|
+
effects: {
|
|
72
|
+
SHOW: "off",
|
|
73
|
+
HIDE: "off",
|
|
74
|
+
ENABLE: "off",
|
|
75
|
+
DISABLE: "off"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
fieldOptions: {
|
|
80
|
+
label: "off",
|
|
81
|
+
placeholder: "off",
|
|
82
|
+
required: "off",
|
|
83
|
+
minValue: "off",
|
|
84
|
+
maxValue: "off",
|
|
85
|
+
minItems: "off",
|
|
86
|
+
maxItems: "off"
|
|
87
|
+
},
|
|
88
|
+
controlOptions: {
|
|
89
|
+
format: "off",
|
|
90
|
+
readonly: "off",
|
|
91
|
+
multi: "off",
|
|
92
|
+
showUnfocusedDescription: "off",
|
|
93
|
+
hideRequiredAsterisk: "off",
|
|
94
|
+
custom: {}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
var DEFAULT_CONFIG = {
|
|
98
|
+
constraints: DEFAULT_CONSTRAINTS
|
|
99
|
+
};
|
|
100
|
+
function mergeWithDefaults(config) {
|
|
101
|
+
if (!config) {
|
|
102
|
+
return DEFAULT_CONSTRAINTS;
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
fieldTypes: {
|
|
106
|
+
...DEFAULT_CONSTRAINTS.fieldTypes,
|
|
107
|
+
...config.fieldTypes
|
|
108
|
+
},
|
|
109
|
+
layout: {
|
|
110
|
+
...DEFAULT_CONSTRAINTS.layout,
|
|
111
|
+
...config.layout
|
|
112
|
+
},
|
|
113
|
+
uiSchema: {
|
|
114
|
+
layouts: {
|
|
115
|
+
...DEFAULT_CONSTRAINTS.uiSchema.layouts,
|
|
116
|
+
...config.uiSchema?.layouts
|
|
117
|
+
},
|
|
118
|
+
rules: {
|
|
119
|
+
enabled: config.uiSchema?.rules?.enabled ?? DEFAULT_CONSTRAINTS.uiSchema.rules.enabled,
|
|
120
|
+
effects: {
|
|
121
|
+
...DEFAULT_CONSTRAINTS.uiSchema.rules.effects,
|
|
122
|
+
...config.uiSchema?.rules?.effects
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
fieldOptions: {
|
|
127
|
+
...DEFAULT_CONSTRAINTS.fieldOptions,
|
|
128
|
+
...config.fieldOptions
|
|
129
|
+
},
|
|
130
|
+
controlOptions: {
|
|
131
|
+
...DEFAULT_CONSTRAINTS.controlOptions,
|
|
132
|
+
...config.controlOptions,
|
|
133
|
+
custom: {
|
|
134
|
+
...DEFAULT_CONSTRAINTS.controlOptions.custom,
|
|
135
|
+
...config.controlOptions?.custom
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/validators/field-types.ts
|
|
142
|
+
var FIELD_TYPE_MAP = {
|
|
143
|
+
text: "text",
|
|
144
|
+
number: "number",
|
|
145
|
+
boolean: "boolean",
|
|
146
|
+
enum: "staticEnum",
|
|
147
|
+
dynamic_enum: "dynamicEnum",
|
|
148
|
+
dynamic_schema: "dynamicSchema",
|
|
149
|
+
array: "array",
|
|
150
|
+
object: "object"
|
|
151
|
+
};
|
|
152
|
+
var FIELD_TYPE_NAMES = {
|
|
153
|
+
text: "text field",
|
|
154
|
+
number: "number field",
|
|
155
|
+
boolean: "boolean field",
|
|
156
|
+
enum: "static enum field",
|
|
157
|
+
dynamic_enum: "dynamic enum field",
|
|
158
|
+
dynamic_schema: "dynamic schema field",
|
|
159
|
+
array: "array field",
|
|
160
|
+
object: "object field"
|
|
161
|
+
};
|
|
162
|
+
function validateFieldTypes(context, constraints) {
|
|
163
|
+
const issues = [];
|
|
164
|
+
const constraintKey = FIELD_TYPE_MAP[context.fieldType];
|
|
165
|
+
if (!constraintKey) {
|
|
166
|
+
return issues;
|
|
167
|
+
}
|
|
168
|
+
const severity = constraints[constraintKey];
|
|
169
|
+
if (severity && severity !== "off") {
|
|
170
|
+
const fieldTypeName = FIELD_TYPE_NAMES[context.fieldType] ?? context.fieldType;
|
|
171
|
+
issues.push(createFieldTypeIssue(context, fieldTypeName, severity));
|
|
172
|
+
}
|
|
173
|
+
return issues;
|
|
174
|
+
}
|
|
175
|
+
function createFieldTypeIssue(context, fieldTypeName, severity) {
|
|
176
|
+
const path = context.path ?? context.fieldName;
|
|
177
|
+
return {
|
|
178
|
+
code: "DISALLOWED_FIELD_TYPE",
|
|
179
|
+
message: `Field "${context.fieldName}" uses ${fieldTypeName}, which is not allowed in this project`,
|
|
180
|
+
severity: severity === "error" ? "error" : "warning",
|
|
181
|
+
category: "fieldTypes",
|
|
182
|
+
path,
|
|
183
|
+
fieldName: context.fieldName,
|
|
184
|
+
fieldType: context.fieldType
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function isFieldTypeAllowed(fieldType, constraints) {
|
|
188
|
+
const constraintKey = FIELD_TYPE_MAP[fieldType];
|
|
189
|
+
if (!constraintKey) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
const severity = constraints[constraintKey];
|
|
193
|
+
return !severity || severity === "off";
|
|
194
|
+
}
|
|
195
|
+
function getFieldTypeSeverity(fieldType, constraints) {
|
|
196
|
+
const constraintKey = FIELD_TYPE_MAP[fieldType];
|
|
197
|
+
if (!constraintKey) {
|
|
198
|
+
return "off";
|
|
199
|
+
}
|
|
200
|
+
return constraints[constraintKey] ?? "off";
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/validators/layout.ts
|
|
204
|
+
function validateLayout(context, constraints) {
|
|
205
|
+
const issues = [];
|
|
206
|
+
if (context.layoutType === "group") {
|
|
207
|
+
const groupSeverity = constraints.group;
|
|
208
|
+
if (groupSeverity && groupSeverity !== "off") {
|
|
209
|
+
issues.push(createGroupIssue(context, groupSeverity));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (context.layoutType === "conditional") {
|
|
213
|
+
const conditionalSeverity = constraints.conditionals;
|
|
214
|
+
if (conditionalSeverity && conditionalSeverity !== "off") {
|
|
215
|
+
issues.push(createConditionalIssue(context, conditionalSeverity));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const maxDepth = constraints.maxNestingDepth;
|
|
219
|
+
if (maxDepth !== void 0 && context.depth > maxDepth) {
|
|
220
|
+
issues.push(createNestingDepthIssue(context, maxDepth));
|
|
221
|
+
}
|
|
222
|
+
return issues;
|
|
223
|
+
}
|
|
224
|
+
function createGroupIssue(context, severity) {
|
|
225
|
+
const labelInfo = context.label ? ` "${context.label}"` : "";
|
|
226
|
+
const issue = {
|
|
227
|
+
code: "DISALLOWED_GROUP",
|
|
228
|
+
message: `Group${labelInfo} is not allowed - visual grouping is not supported in this project`,
|
|
229
|
+
severity: severity === "error" ? "error" : "warning",
|
|
230
|
+
category: "layout"
|
|
231
|
+
};
|
|
232
|
+
if (context.path !== void 0) {
|
|
233
|
+
issue.path = context.path;
|
|
234
|
+
}
|
|
235
|
+
return issue;
|
|
236
|
+
}
|
|
237
|
+
function createConditionalIssue(context, severity) {
|
|
238
|
+
const issue = {
|
|
239
|
+
code: "DISALLOWED_CONDITIONAL",
|
|
240
|
+
message: `Conditional visibility (when/is) is not allowed in this project`,
|
|
241
|
+
severity: severity === "error" ? "error" : "warning",
|
|
242
|
+
category: "layout"
|
|
243
|
+
};
|
|
244
|
+
if (context.path !== void 0) {
|
|
245
|
+
issue.path = context.path;
|
|
246
|
+
}
|
|
247
|
+
return issue;
|
|
248
|
+
}
|
|
249
|
+
function createNestingDepthIssue(context, maxDepth) {
|
|
250
|
+
const issue = {
|
|
251
|
+
code: "EXCEEDED_NESTING_DEPTH",
|
|
252
|
+
message: `Nesting depth ${String(context.depth)} exceeds maximum allowed depth of ${String(maxDepth)}`,
|
|
253
|
+
severity: "error",
|
|
254
|
+
category: "layout"
|
|
255
|
+
};
|
|
256
|
+
if (context.path !== void 0) {
|
|
257
|
+
issue.path = context.path;
|
|
258
|
+
}
|
|
259
|
+
return issue;
|
|
260
|
+
}
|
|
261
|
+
function isLayoutTypeAllowed(layoutType, constraints) {
|
|
262
|
+
if (layoutType === "group") {
|
|
263
|
+
const severity2 = constraints.group;
|
|
264
|
+
return !severity2 || severity2 === "off";
|
|
265
|
+
}
|
|
266
|
+
const severity = constraints.conditionals;
|
|
267
|
+
return !severity || severity === "off";
|
|
268
|
+
}
|
|
269
|
+
function isNestingDepthAllowed(depth, constraints) {
|
|
270
|
+
const maxDepth = constraints.maxNestingDepth;
|
|
271
|
+
if (maxDepth === void 0) {
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
return depth <= maxDepth;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/validators/field-options.ts
|
|
278
|
+
function validateFieldOptions(context, constraints) {
|
|
279
|
+
const issues = [];
|
|
280
|
+
for (const option of context.presentOptions) {
|
|
281
|
+
const severity = constraints[option];
|
|
282
|
+
if (severity && severity !== "off") {
|
|
283
|
+
issues.push(createFieldOptionIssue(context, option, severity));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return issues;
|
|
287
|
+
}
|
|
288
|
+
function createFieldOptionIssue(context, option, severity) {
|
|
289
|
+
const path = context.path ?? context.fieldName;
|
|
290
|
+
return {
|
|
291
|
+
code: "DISALLOWED_FIELD_OPTION",
|
|
292
|
+
message: `Field "${context.fieldName}" uses the "${option}" option, which is not allowed in this project`,
|
|
293
|
+
severity: severity === "error" ? "error" : "warning",
|
|
294
|
+
category: "fieldOptions",
|
|
295
|
+
path,
|
|
296
|
+
fieldName: context.fieldName
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function extractFieldOptions(field) {
|
|
300
|
+
const options = [];
|
|
301
|
+
if (field["label"] !== void 0) options.push("label");
|
|
302
|
+
if (field["placeholder"] !== void 0) options.push("placeholder");
|
|
303
|
+
if (field["required"] !== void 0) options.push("required");
|
|
304
|
+
if (field["min"] !== void 0 || field["minValue"] !== void 0) options.push("minValue");
|
|
305
|
+
if (field["max"] !== void 0 || field["maxValue"] !== void 0) options.push("maxValue");
|
|
306
|
+
if (field["minItems"] !== void 0) options.push("minItems");
|
|
307
|
+
if (field["maxItems"] !== void 0) options.push("maxItems");
|
|
308
|
+
return options;
|
|
309
|
+
}
|
|
310
|
+
function isFieldOptionAllowed(option, constraints) {
|
|
311
|
+
const severity = constraints[option];
|
|
312
|
+
return !severity || severity === "off";
|
|
313
|
+
}
|
|
314
|
+
function getFieldOptionSeverity(option, constraints) {
|
|
315
|
+
return constraints[option] ?? "off";
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// src/validators/formspec.ts
|
|
319
|
+
function validateFormSpecElements(elements, options = {}) {
|
|
320
|
+
const constraints = mergeWithDefaults(options.constraints);
|
|
321
|
+
const issues = [];
|
|
322
|
+
walkElements(elements, constraints, issues, "", 0);
|
|
323
|
+
return {
|
|
324
|
+
valid: !issues.some((issue) => issue.severity === "error"),
|
|
325
|
+
issues
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
function validateFormSpec(formSpec, options = {}) {
|
|
329
|
+
return validateFormSpecElements(formSpec.elements, options);
|
|
330
|
+
}
|
|
331
|
+
function walkElements(elements, constraints, issues, pathPrefix, depth) {
|
|
332
|
+
for (const element of elements) {
|
|
333
|
+
const elementPath = pathPrefix;
|
|
334
|
+
if (element._type === "field") {
|
|
335
|
+
validateField(element, constraints, issues, elementPath, depth);
|
|
336
|
+
} else if (element._type === "group") {
|
|
337
|
+
validateGroup(element, constraints, issues, elementPath, depth);
|
|
338
|
+
} else {
|
|
339
|
+
validateConditional(element, constraints, issues, elementPath, depth);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function validateField(field, constraints, issues, pathPrefix, depth) {
|
|
344
|
+
const fieldPath = pathPrefix ? `${pathPrefix}/${field.name}` : field.name;
|
|
345
|
+
const fieldTypeIssues = validateFieldTypes(
|
|
346
|
+
{
|
|
347
|
+
fieldType: field._field,
|
|
348
|
+
fieldName: field.name,
|
|
349
|
+
path: fieldPath
|
|
350
|
+
},
|
|
351
|
+
constraints.fieldTypes
|
|
352
|
+
);
|
|
353
|
+
issues.push(...fieldTypeIssues);
|
|
354
|
+
const presentOptions = extractFieldOptions(field);
|
|
355
|
+
if (presentOptions.length > 0) {
|
|
356
|
+
const optionIssues = validateFieldOptions(
|
|
357
|
+
{
|
|
358
|
+
fieldName: field.name,
|
|
359
|
+
presentOptions,
|
|
360
|
+
path: fieldPath
|
|
361
|
+
},
|
|
362
|
+
constraints.fieldOptions
|
|
363
|
+
);
|
|
364
|
+
issues.push(...optionIssues);
|
|
365
|
+
}
|
|
366
|
+
if (field._field === "array" || field._field === "object") {
|
|
367
|
+
const layoutIssues = validateLayout(
|
|
368
|
+
{
|
|
369
|
+
layoutType: "group",
|
|
370
|
+
// Arrays/objects contribute to nesting depth
|
|
371
|
+
depth: depth + 1,
|
|
372
|
+
path: fieldPath
|
|
373
|
+
},
|
|
374
|
+
constraints.layout
|
|
375
|
+
);
|
|
376
|
+
issues.push(...layoutIssues.filter((issue) => issue.code === "EXCEEDED_NESTING_DEPTH"));
|
|
377
|
+
if (field._field === "array" && "items" in field) {
|
|
378
|
+
walkElements(
|
|
379
|
+
field.items,
|
|
380
|
+
constraints,
|
|
381
|
+
issues,
|
|
382
|
+
`${fieldPath}[]`,
|
|
383
|
+
depth + 1
|
|
384
|
+
);
|
|
385
|
+
} else if ("properties" in field) {
|
|
386
|
+
walkElements(
|
|
387
|
+
field.properties,
|
|
388
|
+
constraints,
|
|
389
|
+
issues,
|
|
390
|
+
fieldPath,
|
|
391
|
+
depth + 1
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
function validateGroup(group, constraints, issues, pathPrefix, depth) {
|
|
397
|
+
const groupPath = pathPrefix ? `${pathPrefix}/[group:${group.label}]` : `[group:${group.label}]`;
|
|
398
|
+
const layoutIssues = validateLayout(
|
|
399
|
+
{
|
|
400
|
+
layoutType: "group",
|
|
401
|
+
label: group.label,
|
|
402
|
+
depth,
|
|
403
|
+
path: groupPath
|
|
404
|
+
},
|
|
405
|
+
constraints.layout
|
|
406
|
+
);
|
|
407
|
+
issues.push(...layoutIssues);
|
|
408
|
+
walkElements(group.elements, constraints, issues, pathPrefix, depth);
|
|
409
|
+
}
|
|
410
|
+
function validateConditional(conditional, constraints, issues, pathPrefix, depth) {
|
|
411
|
+
const condPath = pathPrefix ? `${pathPrefix}/[when:${conditional.field}=${String(conditional.value)}]` : `[when:${conditional.field}=${String(conditional.value)}]`;
|
|
412
|
+
const layoutIssues = validateLayout(
|
|
413
|
+
{
|
|
414
|
+
layoutType: "conditional",
|
|
415
|
+
depth,
|
|
416
|
+
path: condPath
|
|
417
|
+
},
|
|
418
|
+
constraints.layout
|
|
419
|
+
);
|
|
420
|
+
issues.push(...layoutIssues);
|
|
421
|
+
walkElements(conditional.elements, constraints, issues, pathPrefix, depth);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// src/browser.ts
|
|
425
|
+
function loadConfigFromString(yamlContent) {
|
|
426
|
+
const parsed = (0, import_yaml.parse)(yamlContent);
|
|
427
|
+
if (parsed === null || parsed === void 0) {
|
|
428
|
+
return mergeWithDefaults(void 0);
|
|
429
|
+
}
|
|
430
|
+
if (typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
431
|
+
throw new Error(`Invalid config content: expected an object, got ${typeof parsed}`);
|
|
432
|
+
}
|
|
433
|
+
return mergeWithDefaults(parsed.constraints);
|
|
434
|
+
}
|
|
435
|
+
function defineConstraints(config) {
|
|
436
|
+
return mergeWithDefaults(config);
|
|
437
|
+
}
|
|
438
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
439
|
+
0 && (module.exports = {
|
|
440
|
+
DEFAULT_CONFIG,
|
|
441
|
+
DEFAULT_CONSTRAINTS,
|
|
442
|
+
defineConstraints,
|
|
443
|
+
extractFieldOptions,
|
|
444
|
+
getFieldOptionSeverity,
|
|
445
|
+
getFieldTypeSeverity,
|
|
446
|
+
isFieldOptionAllowed,
|
|
447
|
+
isFieldTypeAllowed,
|
|
448
|
+
isLayoutTypeAllowed,
|
|
449
|
+
isNestingDepthAllowed,
|
|
450
|
+
loadConfigFromString,
|
|
451
|
+
mergeWithDefaults,
|
|
452
|
+
validateFieldOptions,
|
|
453
|
+
validateFieldTypes,
|
|
454
|
+
validateFormSpec,
|
|
455
|
+
validateFormSpecElements,
|
|
456
|
+
validateLayout
|
|
457
|
+
});
|
|
458
|
+
//# sourceMappingURL=browser.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/browser.ts","../src/defaults.ts","../src/validators/field-types.ts","../src/validators/layout.ts","../src/validators/field-options.ts","../src/validators/formspec.ts"],"sourcesContent":["/**\n * Browser-safe exports from @formspec/constraints.\n *\n * This entry point excludes the file-based config loader (loadConfig)\n * which requires Node.js APIs. Use this entry point for browser builds.\n *\n * For browser use:\n * - Use loadConfigFromString() to parse YAML config strings\n * - Use defineConstraints() for programmatic configuration\n * - Use validateFormSpec() for validation\n *\n * @packageDocumentation\n */\n\n// Re-export 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// Re-export browser-safe loader functions\n// These are defined inline to avoid pulling in Node.js imports from loader.ts\nimport { parse as parseYaml } from \"yaml\";\nimport type { FormSpecConfig, ConstraintConfig, ResolvedConstraintConfig } from \"./types.js\";\nimport { mergeWithDefaults, DEFAULT_CONSTRAINTS, DEFAULT_CONFIG } from \"./defaults.js\";\n\n// Re-export defaults\nexport { DEFAULT_CONSTRAINTS, DEFAULT_CONFIG, mergeWithDefaults };\n\n/**\n * Synchronously loads config from a pre-parsed YAML string.\n * Useful for browser environments 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\n// Re-export validators (these are all browser-safe)\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 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":["severity","parseYaml"]}
|