@formadapter/core 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1172 @@
1
+ //#region src/path-segment.ts
2
+ /** Names that collide with JavaScript object lookup or form-runtime state. */
3
+ const RESERVED_FORM_PATH_SEGMENTS = new Set([
4
+ ...Object.getOwnPropertyNames(Object.prototype),
5
+ "prototype",
6
+ "root"
7
+ ]);
8
+ function formPathSegmentError(segment) {
9
+ if (segment.length === 0) return "Empty property names cannot be represented in a form path";
10
+ if (RESERVED_FORM_PATH_SEGMENTS.has(segment)) return `Property name “${segment}” is reserved and cannot be represented safely in a form path`;
11
+ if (segment.startsWith("__formadapter_")) return `Property name “${segment}” uses FormAdapter's reserved internal namespace`;
12
+ if (segment.startsWith("$ACTION_")) return `Property name “${segment}” uses a reserved server-action prefix and cannot be represented safely`;
13
+ if (segment.includes(".") || segment.includes("[") || segment.includes("]") || segment.includes("'") || segment.includes("\"")) return `Property name “${segment}” contains form-path syntax and cannot be represented safely`;
14
+ if (!Number.isNaN(Number(segment))) return `Numeric-like property name “${segment}” cannot be distinguished safely from an array index`;
15
+ }
16
+ /** True when a schema property cannot be represented losslessly as one path segment. */
17
+ function isReservedFormPathSegment(segment) {
18
+ return formPathSegmentError(segment) !== void 0;
19
+ }
20
+ //#endregion
21
+ //#region src/record.ts
22
+ /** Own-property checks that are safe for null-prototype and hostile-key records. */
23
+ function hasOwn(value, key) {
24
+ return Object.prototype.hasOwnProperty.call(value, key);
25
+ }
26
+ /**
27
+ * Creates a dictionary without inherited names such as `constructor` or
28
+ * `toString`. Use this for path-indexed internal/public maps.
29
+ */
30
+ function createNullRecord() {
31
+ return Object.create(null);
32
+ }
33
+ /** Defines a normal enumerable data property without invoking `__proto__`. */
34
+ function defineOwn(target, key, value) {
35
+ Object.defineProperty(target, key, {
36
+ configurable: true,
37
+ enumerable: true,
38
+ value,
39
+ writable: true
40
+ });
41
+ }
42
+ function ownValue(value, key) {
43
+ return hasOwn(value, key) ? value[key] : void 0;
44
+ }
45
+ //#endregion
46
+ //#region src/compile.ts
47
+ var SchemaConversionError = class extends Error {
48
+ name = "SchemaConversionError";
49
+ constructor(message, options) {
50
+ super(message, options);
51
+ }
52
+ };
53
+ function isRecord$2(value) {
54
+ return typeof value === "object" && value !== null && !Array.isArray(value);
55
+ }
56
+ function isSchemaObject(schema) {
57
+ return typeof schema === "object" && schema !== null && !Array.isArray(schema);
58
+ }
59
+ function humanize(value) {
60
+ const words = value.replaceAll("[]", "").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
61
+ return words ? words.charAt(0).toLocaleUpperCase() + words.slice(1) : "Field";
62
+ }
63
+ function pointerValue(root, reference) {
64
+ if (!reference.startsWith("#/")) return void 0;
65
+ let current = root;
66
+ for (const encoded of reference.slice(2).split("/")) {
67
+ const segment = encoded.replaceAll("~1", "/").replaceAll("~0", "~");
68
+ if (!isRecord$2(current) || !hasOwn(current, segment)) return void 0;
69
+ current = current[segment];
70
+ }
71
+ return typeof current === "boolean" || isRecord$2(current) ? current : void 0;
72
+ }
73
+ function sameValue(left, right) {
74
+ if (Object.is(left, right)) return true;
75
+ if (Array.isArray(left) || Array.isArray(right)) return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => sameValue(value, right[index]));
76
+ if (!isRecord$2(left) || !isRecord$2(right)) return false;
77
+ const leftKeys = Object.keys(left).sort();
78
+ const rightKeys = Object.keys(right).sort();
79
+ return leftKeys.length === rightKeys.length && leftKeys.every((key, index) => key === rightKeys[index] && sameValue(left[key], right[key]));
80
+ }
81
+ const COMPOSED_KEYWORDS = new Set([
82
+ "$defs",
83
+ "additionalProperties",
84
+ "allOf",
85
+ "const",
86
+ "default",
87
+ "definitions",
88
+ "deprecated",
89
+ "description",
90
+ "enum",
91
+ "examples",
92
+ "exclusiveMaximum",
93
+ "exclusiveMinimum",
94
+ "format",
95
+ "items",
96
+ "maxItems",
97
+ "maxLength",
98
+ "maximum",
99
+ "minItems",
100
+ "minLength",
101
+ "minimum",
102
+ "multipleOf",
103
+ "pattern",
104
+ "patternProperties",
105
+ "properties",
106
+ "propertyNames",
107
+ "readOnly",
108
+ "required",
109
+ "title",
110
+ "type",
111
+ "uniqueItems",
112
+ "writeOnly"
113
+ ]);
114
+ function intersectSchemaValue(left, right) {
115
+ if (left === false || right === false) return false;
116
+ if (left === true) return right;
117
+ if (right === true) return left;
118
+ return { allOf: [left, right] };
119
+ }
120
+ function mergeSchemaMap(left, right) {
121
+ if (!left && !right) return void 0;
122
+ const merged = createNullRecord();
123
+ for (const [key, value] of Object.entries(left ?? {})) defineOwn(merged, key, value);
124
+ for (const [key, value] of Object.entries(right ?? {})) defineOwn(merged, key, hasOwn(merged, key) ? intersectSchemaValue(merged[key], value) : value);
125
+ return merged;
126
+ }
127
+ function mergeDefinitions(left, right, keyword) {
128
+ if (!left && !right) return {};
129
+ const merged = createNullRecord();
130
+ for (const [key, value] of Object.entries(left ?? {})) defineOwn(merged, key, value);
131
+ for (const [key, value] of Object.entries(right ?? {})) {
132
+ if (hasOwn(merged, key) && !sameValue(merged[key], value)) return { error: `Cannot safely combine conflicting ${keyword} definition “${key}”` };
133
+ defineOwn(merged, key, value);
134
+ }
135
+ return { definitions: merged };
136
+ }
137
+ function explicitTypes(schema) {
138
+ if (typeof schema.type === "string") return [schema.type];
139
+ return Array.isArray(schema.type) ? schema.type : void 0;
140
+ }
141
+ function intersectTypes(left, right) {
142
+ if (!left) return right;
143
+ if (!right) return left;
144
+ const result = [];
145
+ for (const leftType of left) for (const rightType of right) {
146
+ const intersection = leftType === rightType ? leftType : leftType === "number" && rightType === "integer" || leftType === "integer" && rightType === "number" ? "integer" : void 0;
147
+ if (intersection && !result.includes(intersection)) result.push(intersection);
148
+ }
149
+ return result;
150
+ }
151
+ function constrainedValues(schema) {
152
+ const hasConst = hasOwn(schema, "const");
153
+ const enumValues = schema.enum;
154
+ if (!hasConst) return enumValues;
155
+ if (!enumValues) return [schema.const];
156
+ return enumValues.some((value) => sameValue(value, schema.const)) ? [schema.const] : [];
157
+ }
158
+ function intersectValues(left, right) {
159
+ if (!left) return right;
160
+ if (!right) return left;
161
+ return left.filter((leftValue) => right.some((rightValue) => sameValue(leftValue, rightValue)));
162
+ }
163
+ function valueMatchesType(value, type) {
164
+ switch (type) {
165
+ case "array": return Array.isArray(value);
166
+ case "boolean": return typeof value === "boolean";
167
+ case "integer": return typeof value === "number" && Number.isInteger(value);
168
+ case "null": return value === null;
169
+ case "number": return typeof value === "number" && Number.isFinite(value);
170
+ case "object": return isRecord$2(value);
171
+ case "string": return typeof value === "string";
172
+ default: return false;
173
+ }
174
+ }
175
+ function lowerBoundary(schema) {
176
+ const candidates = [];
177
+ if (schema.minimum !== void 0) candidates.push({
178
+ exclusive: false,
179
+ value: schema.minimum
180
+ });
181
+ if (schema.exclusiveMinimum !== void 0) candidates.push({
182
+ exclusive: true,
183
+ value: schema.exclusiveMinimum
184
+ });
185
+ return candidates.sort((left, right) => right.value - left.value || Number(right.exclusive) - Number(left.exclusive))[0];
186
+ }
187
+ function upperBoundary(schema) {
188
+ const candidates = [];
189
+ if (schema.maximum !== void 0) candidates.push({
190
+ exclusive: false,
191
+ value: schema.maximum
192
+ });
193
+ if (schema.exclusiveMaximum !== void 0) candidates.push({
194
+ exclusive: true,
195
+ value: schema.exclusiveMaximum
196
+ });
197
+ return candidates.sort((left, right) => left.value - right.value || Number(right.exclusive) - Number(left.exclusive))[0];
198
+ }
199
+ function stricterLower(left, right) {
200
+ if (!left) return right;
201
+ if (!right) return left;
202
+ if (left.value !== right.value) return left.value > right.value ? left : right;
203
+ return left.exclusive ? left : right;
204
+ }
205
+ function stricterUpper(left, right) {
206
+ if (!left) return right;
207
+ if (!right) return left;
208
+ if (left.value !== right.value) return left.value < right.value ? left : right;
209
+ return left.exclusive ? left : right;
210
+ }
211
+ function stricterMultipleOf(left, right) {
212
+ if (left === void 0) return right;
213
+ if (right === void 0 || Object.is(left, right)) return left;
214
+ if (left > 0 && right > 0) {
215
+ const leftRatio = left / right;
216
+ if (Number.isInteger(leftRatio)) return left;
217
+ const rightRatio = right / left;
218
+ if (Number.isInteger(rightRatio)) return right;
219
+ }
220
+ }
221
+ function mergeSchema(left, right) {
222
+ for (const key of Object.keys(left)) if (hasOwn(right, key) && !COMPOSED_KEYWORDS.has(key) && !key.startsWith("x-formadapter") && !sameValue(left[key], right[key])) return { error: `Cannot safely combine conflicting allOf keyword “${key}”` };
223
+ const types = intersectTypes(explicitTypes(left), explicitTypes(right));
224
+ if (types?.length === 0) return { error: "The allOf branches require incompatible JSON Schema types" };
225
+ let values = intersectValues(constrainedValues(left), constrainedValues(right));
226
+ if (values && types) values = values.filter((value) => types.some((type) => valueMatchesType(value, type)));
227
+ if (values?.length === 0) return { error: "The allOf branches have no allowed values in common" };
228
+ if (left.pattern !== void 0 && right.pattern !== void 0 && left.pattern !== right.pattern) return { error: "Multiple different patterns in allOf cannot be represented by one form control" };
229
+ if (left.format !== void 0 && right.format !== void 0 && left.format !== right.format) return { error: "Conflicting formats in allOf cannot be represented by one form control" };
230
+ const multipleOf = stricterMultipleOf(left.multipleOf, right.multipleOf);
231
+ if (left.multipleOf !== void 0 && right.multipleOf !== void 0 && multipleOf === void 0) return { error: "Different multipleOf constraints in allOf cannot be represented safely" };
232
+ const minimum = stricterLower(lowerBoundary(left), lowerBoundary(right));
233
+ const maximum = stricterUpper(upperBoundary(left), upperBoundary(right));
234
+ if (minimum && maximum && (minimum.value > maximum.value || minimum.value === maximum.value && (minimum.exclusive || maximum.exclusive))) return { error: "The allOf numeric constraints cannot be satisfied" };
235
+ const minLength = Math.max(left.minLength ?? 0, right.minLength ?? 0);
236
+ const maxLength = Math.min(left.maxLength ?? Number.POSITIVE_INFINITY, right.maxLength ?? Number.POSITIVE_INFINITY);
237
+ if (minLength > maxLength) return { error: "The allOf string-length constraints cannot be satisfied" };
238
+ const minItems = Math.max(left.minItems ?? 0, right.minItems ?? 0);
239
+ const maxItems = Math.min(left.maxItems ?? Number.POSITIVE_INFINITY, right.maxItems ?? Number.POSITIVE_INFINITY);
240
+ if (minItems > maxItems) return { error: "The allOf array-length constraints cannot be satisfied" };
241
+ const definitions = mergeDefinitions(left.definitions, right.definitions, "definitions");
242
+ if (definitions.error) return { error: definitions.error };
243
+ const defs = mergeDefinitions(left.$defs, right.$defs, "$defs");
244
+ if (defs.error) return { error: defs.error };
245
+ const leftProperties = new Set(Object.keys(left.properties ?? {}));
246
+ const rightProperties = new Set(Object.keys(right.properties ?? {}));
247
+ if (left.additionalProperties === false && [...rightProperties].some((key) => !leftProperties.has(key)) || right.additionalProperties === false && [...leftProperties].some((key) => !rightProperties.has(key))) return { error: "Closed object branches in allOf declare incompatible property sets" };
248
+ const merged = {
249
+ ...left,
250
+ ...right
251
+ };
252
+ delete merged.type;
253
+ if (types) merged.type = types.length === 1 ? types[0] : types;
254
+ delete merged.const;
255
+ delete merged.enum;
256
+ if (values) if ((hasOwn(left, "const") || hasOwn(right, "const")) && values.length === 1) merged.const = values[0];
257
+ else merged.enum = values;
258
+ const properties = mergeSchemaMap(left.properties, right.properties);
259
+ if (properties) merged.properties = properties;
260
+ const patternProperties = mergeSchemaMap(left.patternProperties, right.patternProperties);
261
+ if (patternProperties) merged.patternProperties = patternProperties;
262
+ if (left.required || right.required) merged.required = [...new Set([...left.required ?? [], ...right.required ?? []])];
263
+ if (hasOwn(left, "additionalProperties") && hasOwn(right, "additionalProperties")) merged.additionalProperties = intersectSchemaValue(left.additionalProperties, right.additionalProperties);
264
+ if (hasOwn(left, "propertyNames") && hasOwn(right, "propertyNames")) merged.propertyNames = intersectSchemaValue(left.propertyNames, right.propertyNames);
265
+ if (hasOwn(left, "items") && hasOwn(right, "items")) if (Array.isArray(left.items) || Array.isArray(right.items)) {
266
+ if (!sameValue(left.items, right.items)) return { error: "Different tuple item schemas in allOf cannot be represented safely" };
267
+ } else merged.items = intersectSchemaValue(left.items, right.items);
268
+ const allOf = [...left.allOf ?? [], ...right.allOf ?? []];
269
+ if (allOf.length > 0) merged.allOf = allOf;
270
+ else delete merged.allOf;
271
+ delete merged.minimum;
272
+ delete merged.exclusiveMinimum;
273
+ if (minimum) merged[minimum.exclusive ? "exclusiveMinimum" : "minimum"] = minimum.value;
274
+ delete merged.maximum;
275
+ delete merged.exclusiveMaximum;
276
+ if (maximum) merged[maximum.exclusive ? "exclusiveMaximum" : "maximum"] = maximum.value;
277
+ delete merged.minLength;
278
+ delete merged.maxLength;
279
+ if (left.minLength !== void 0 || right.minLength !== void 0) merged.minLength = minLength;
280
+ if (maxLength !== Number.POSITIVE_INFINITY) merged.maxLength = maxLength;
281
+ delete merged.minItems;
282
+ delete merged.maxItems;
283
+ if (left.minItems !== void 0 || right.minItems !== void 0) merged.minItems = minItems;
284
+ if (maxItems !== Number.POSITIVE_INFINITY) merged.maxItems = maxItems;
285
+ if (multipleOf !== void 0) merged.multipleOf = multipleOf;
286
+ if (left.uniqueItems !== void 0 || right.uniqueItems !== void 0) merged.uniqueItems = left.uniqueItems === true || right.uniqueItems === true;
287
+ if (left.readOnly !== void 0 || right.readOnly !== void 0) merged.readOnly = left.readOnly === true || right.readOnly === true;
288
+ if (hasOwn(left, "writeOnly") || hasOwn(right, "writeOnly")) merged.writeOnly = left.writeOnly === true || right.writeOnly === true;
289
+ if (definitions.definitions) merged.definitions = definitions.definitions;
290
+ if (defs.definitions) merged.$defs = defs.definitions;
291
+ return { schema: merged };
292
+ }
293
+ function schemaChildren(schema) {
294
+ const children = [];
295
+ children.push(...Object.values(schema.properties ?? {}));
296
+ children.push(...Object.values(schema.patternProperties ?? {}));
297
+ for (const keyword of ["additionalProperties", "propertyNames"]) {
298
+ const child = schema[keyword];
299
+ if (typeof child === "boolean" || isSchemaObject(child)) children.push(child);
300
+ }
301
+ if (Array.isArray(schema.items)) children.push(...schema.items);
302
+ else if (typeof schema.items === "boolean" || isSchemaObject(schema.items)) children.push(schema.items);
303
+ children.push(...schema.prefixItems ?? []);
304
+ children.push(...schema.allOf ?? [], ...schema.anyOf ?? [], ...schema.oneOf ?? []);
305
+ return children;
306
+ }
307
+ function referenceIsRecursive(root, reference) {
308
+ const target = pointerValue(root, reference);
309
+ if (target === void 0) return false;
310
+ const visitedObjects = /* @__PURE__ */ new Set();
311
+ const visitedReferences = /* @__PURE__ */ new Set();
312
+ const visit = (schema) => {
313
+ if (!isSchemaObject(schema)) return false;
314
+ if (schema.$ref) {
315
+ if (schema.$ref === reference) return true;
316
+ if (visitedReferences.has(schema.$ref)) return false;
317
+ visitedReferences.add(schema.$ref);
318
+ const nested = pointerValue(root, schema.$ref);
319
+ if (nested !== void 0 && visit(nested)) return true;
320
+ }
321
+ if (visitedObjects.has(schema)) return false;
322
+ visitedObjects.add(schema);
323
+ return schemaChildren(schema).some(visit);
324
+ };
325
+ return visit(target);
326
+ }
327
+ function resolveSchema(schema, context) {
328
+ if (!isSchemaObject(schema) || !schema.$ref) return {
329
+ schema,
330
+ context
331
+ };
332
+ const reference = schema.$ref;
333
+ if (context.resolvingRefs.has(reference) && referenceIsRecursive(context.rootSchema, reference)) return {
334
+ schema,
335
+ context,
336
+ error: `Circular JSON Schema reference “${reference}”`
337
+ };
338
+ const target = pointerValue(context.rootSchema, reference);
339
+ if (target === void 0) return {
340
+ schema,
341
+ context,
342
+ error: `Unresolved JSON Schema reference “${reference}”`
343
+ };
344
+ const nextContext = {
345
+ ...context,
346
+ resolvingRefs: new Set([...context.resolvingRefs, reference])
347
+ };
348
+ const { $ref: _ignored, ...siblings } = schema;
349
+ if (target === false) return {
350
+ schema: false,
351
+ context: nextContext
352
+ };
353
+ if (target === true) return Object.keys(siblings).length === 0 ? {
354
+ schema: true,
355
+ context: nextContext
356
+ } : {
357
+ schema: siblings,
358
+ context: nextContext
359
+ };
360
+ const resolvedTarget = target.$ref ? resolveSchema(target, nextContext) : {
361
+ schema: target,
362
+ context: nextContext
363
+ };
364
+ if (resolvedTarget.error || resolvedTarget.schema === false) return resolvedTarget;
365
+ if (resolvedTarget.schema === true) return Object.keys(siblings).length === 0 ? resolvedTarget : {
366
+ schema: siblings,
367
+ context: resolvedTarget.context
368
+ };
369
+ if (Object.keys(siblings).length === 0) return resolvedTarget;
370
+ const merged = mergeSchema(resolvedTarget.schema, siblings);
371
+ return merged.error ? {
372
+ schema,
373
+ context,
374
+ error: merged.error
375
+ } : {
376
+ schema: merged.schema,
377
+ context: resolvedTarget.context
378
+ };
379
+ }
380
+ function collapseAllOf(schema, context) {
381
+ if (!schema.allOf || schema.allOf.length === 0) return {
382
+ schema,
383
+ context
384
+ };
385
+ const { allOf, ...base } = schema;
386
+ let merged = base;
387
+ const resolvingRefs = new Set(context.resolvingRefs);
388
+ for (const branch of allOf) {
389
+ const resolved = resolveSchema(branch, context);
390
+ if (resolved.error) return {
391
+ schema: merged,
392
+ context,
393
+ error: resolved.error
394
+ };
395
+ if (resolved.schema === false) return {
396
+ schema: merged,
397
+ context,
398
+ error: "An allOf branch can never be valid"
399
+ };
400
+ if (resolved.schema === true) continue;
401
+ const collapsed = collapseAllOf(resolved.schema, resolved.context);
402
+ if (collapsed.error) return collapsed;
403
+ const intersection = mergeSchema(merged, collapsed.schema);
404
+ if (intersection.error) return {
405
+ schema: merged,
406
+ context,
407
+ error: intersection.error
408
+ };
409
+ merged = intersection.schema;
410
+ for (const reference of collapsed.context.resolvingRefs) resolvingRefs.add(reference);
411
+ }
412
+ return {
413
+ schema: merged,
414
+ context: {
415
+ ...context,
416
+ resolvingRefs
417
+ }
418
+ };
419
+ }
420
+ function primitive(value) {
421
+ return value === null || [
422
+ "string",
423
+ "number",
424
+ "boolean"
425
+ ].includes(typeof value);
426
+ }
427
+ function optionsFromSchema(schema) {
428
+ const raw = schema.enum ?? (schema.const !== void 0 ? [schema.const] : void 0);
429
+ if (!raw || !raw.every(primitive)) return void 0;
430
+ return raw.map((value) => ({
431
+ label: humanize(String(value)),
432
+ value
433
+ }));
434
+ }
435
+ function optionsFromUnion(branches, context, exclusive) {
436
+ const options = [];
437
+ for (const branch of branches) {
438
+ const resolved = resolveSchema(branch, context);
439
+ if (resolved.error || resolved.schema === true) return void 0;
440
+ if (resolved.schema === false) continue;
441
+ const collapsed = collapseAllOf(resolved.schema, resolved.context);
442
+ if (collapsed.error) return void 0;
443
+ const branchOptions = optionsFromSchema(collapsed.schema);
444
+ if (!branchOptions) return void 0;
445
+ for (const option of branchOptions) {
446
+ if (options.some((existing) => Object.is(existing.value, option.value))) {
447
+ if (exclusive) return void 0;
448
+ continue;
449
+ }
450
+ options.push({
451
+ label: collapsed.schema.title ?? option.label,
452
+ value: option.value
453
+ });
454
+ }
455
+ }
456
+ return options;
457
+ }
458
+ function mediaTypesFromUnion(branches, context) {
459
+ const mediaTypes = [];
460
+ for (const branch of branches) {
461
+ const resolved = resolveSchema(branch, context);
462
+ if (resolved.error || !isSchemaObject(resolved.schema)) return void 0;
463
+ const collapsed = collapseAllOf(resolved.schema, resolved.context);
464
+ if (collapsed.error || typeof collapsed.schema.contentMediaType !== "string") return;
465
+ if (Object.keys(collapsed.schema).find((key) => ![
466
+ "$comment",
467
+ "contentMediaType",
468
+ "deprecated",
469
+ "description",
470
+ "readOnly",
471
+ "title",
472
+ "writeOnly"
473
+ ].includes(key))) return void 0;
474
+ if (!mediaTypes.includes(collapsed.schema.contentMediaType)) mediaTypes.push(collapsed.schema.contentMediaType);
475
+ }
476
+ return mediaTypes.length > 0 ? mediaTypes : void 0;
477
+ }
478
+ function sameSchema(left, right) {
479
+ return sameValue(left, right);
480
+ }
481
+ function normalizedObjectUnion(branches, context) {
482
+ const objects = [];
483
+ for (const branch of branches) {
484
+ const resolved = resolveSchema(branch, context);
485
+ if (resolved.error || !isSchemaObject(resolved.schema)) return void 0;
486
+ const collapsed = collapseAllOf(resolved.schema, resolved.context);
487
+ if (collapsed.error || !schemaTypes(collapsed.schema).includes("object")) return;
488
+ objects.push(collapsed.schema);
489
+ }
490
+ if (objects.length < 2) return void 0;
491
+ const discriminator = Object.keys(objects[0]?.properties ?? {}).filter((key) => objects.every((branch) => hasOwn(branch.properties ?? {}, key))).find((key) => {
492
+ const values = objects.map((branch) => {
493
+ const property = branch.properties?.[key];
494
+ if (property === void 0 || !isSchemaObject(property)) return void 0;
495
+ const options = optionsFromSchema(property);
496
+ return options?.length === 1 ? options[0]?.value : void 0;
497
+ });
498
+ return values.every((value) => value !== void 0) && new Set(values.map((value) => JSON.stringify(value))).size === values.length;
499
+ });
500
+ if (!discriminator) return void 0;
501
+ const discriminatorValues = objects.map((branch) => {
502
+ const property = branch.properties?.[discriminator];
503
+ return optionsFromSchema(property)?.[0]?.value;
504
+ });
505
+ const propertyNames = [...new Set(objects.flatMap((branch) => Object.keys(branch.properties ?? {})))];
506
+ const properties = createNullRecord();
507
+ const rules = createNullRecord();
508
+ for (const propertyName of propertyNames) {
509
+ const schemas = objects.flatMap((branch) => {
510
+ const property = branch.properties?.[propertyName];
511
+ return property === void 0 ? [] : [property];
512
+ });
513
+ if (propertyName === discriminator) {
514
+ const initial = schemas[0];
515
+ const { const: _const, ...withoutConst } = initial !== void 0 && isSchemaObject(initial) ? initial : {};
516
+ defineOwn(properties, propertyName, {
517
+ ...withoutConst,
518
+ enum: discriminatorValues
519
+ });
520
+ } else {
521
+ const first = schemas[0];
522
+ defineOwn(properties, propertyName, schemas.every((candidate) => sameSchema(first, candidate)) ? first : { anyOf: schemas });
523
+ }
524
+ const visibleFor = [];
525
+ const requiredFor = [];
526
+ for (const [index, branch] of objects.entries()) {
527
+ if (branch.properties?.[propertyName] !== void 0) visibleFor.push(discriminatorValues[index]);
528
+ if (branch.required?.includes(propertyName)) requiredFor.push(discriminatorValues[index]);
529
+ }
530
+ defineOwn(rules, propertyName, {
531
+ requiredFor,
532
+ visibleFor
533
+ });
534
+ }
535
+ return {
536
+ discriminator,
537
+ rules,
538
+ schema: {
539
+ properties,
540
+ required: propertyNames.filter((propertyName) => objects.every((branch) => branch.required?.includes(propertyName))),
541
+ type: "object"
542
+ }
543
+ };
544
+ }
545
+ function runtimePathValue(values, path) {
546
+ return path.split(".").reduce((current, segment) => {
547
+ return isRecord$2(current) ? ownValue(current, segment) : void 0;
548
+ }, values);
549
+ }
550
+ function stateValue(state, values) {
551
+ return typeof state === "function" ? state(values) : state;
552
+ }
553
+ function extensionConfig(schema) {
554
+ const nested = isRecord$2(schema["x-formadapter"]) ? schema["x-formadapter"] : {};
555
+ const flat = createNullRecord();
556
+ const prefix = "x-formadapter-";
557
+ for (const [key, value] of Object.entries(schema)) if (key.startsWith(prefix)) defineOwn(flat, key.slice(14), value);
558
+ return {
559
+ ...nested,
560
+ ...flat
561
+ };
562
+ }
563
+ function fieldOverride(path, config) {
564
+ const fields = config.fields;
565
+ return fields && hasOwn(fields, path) ? fields[path] : void 0;
566
+ }
567
+ function stringValue(value) {
568
+ return typeof value === "string" ? value : void 0;
569
+ }
570
+ function numberValue(value) {
571
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
572
+ }
573
+ function booleanValue(value) {
574
+ return typeof value === "boolean" ? value : void 0;
575
+ }
576
+ function resolvedConfig(schema, override) {
577
+ const extensions = extensionConfig(schema);
578
+ const control = override?.control ?? stringValue(extensions.control);
579
+ const placeholder = override?.placeholder ?? stringValue(extensions.placeholder);
580
+ const order = override?.order ?? numberValue(extensions.order);
581
+ const className = override?.className ?? stringValue(extensions.className ?? extensions["class-name"]);
582
+ return {
583
+ ...control !== void 0 ? { control } : {},
584
+ ...placeholder !== void 0 ? { placeholder } : {},
585
+ hidden: override?.hidden ?? booleanValue(extensions.hidden) ?? false,
586
+ disabled: override?.disabled ?? booleanValue(extensions.disabled) ?? false,
587
+ readOnly: override?.readOnly ?? booleanValue(extensions.readonly ?? extensions.readOnly) ?? schema.readOnly ?? false,
588
+ ...order !== void 0 ? { order } : {},
589
+ ...className !== void 0 ? { className } : {},
590
+ ...override?.controlProps ? { controlProps: override.controlProps } : {},
591
+ multiple: override?.multiple ?? booleanValue(extensions.multiple) ?? false,
592
+ ...override?.options ? { options: override.options } : {},
593
+ requiredWhenVisible: override?.requiredWhenVisible ?? booleanValue(extensions.requiredWhenVisible ?? extensions["required-when-visible"]) ?? false,
594
+ ...override?.requiredMessage ? { requiredMessage: override.requiredMessage } : {},
595
+ ...override?.asyncValidate !== void 0 ? { asyncValidate: override.asyncValidate } : {},
596
+ asyncValidationDebounceMs: Math.max(0, override?.asyncValidationDebounceMs ?? 250),
597
+ ...override?.array ? { array: override.array } : {},
598
+ extensions
599
+ };
600
+ }
601
+ function schemaTypes(schema) {
602
+ if (typeof schema.type === "string") return [schema.type];
603
+ if (Array.isArray(schema.type)) {
604
+ const types = [...new Set(schema.type)];
605
+ return types.includes("number") ? types.filter((type) => type !== "integer") : types;
606
+ }
607
+ if (schema.properties) return ["object"];
608
+ if (schema.items) return ["array"];
609
+ if (schema.const !== void 0) {
610
+ if (schema.const === null) return ["null"];
611
+ return [typeof schema.const];
612
+ }
613
+ if (schema.enum?.length) return [...new Set(schema.enum.map((value) => value === null ? "null" : typeof value))];
614
+ return [];
615
+ }
616
+ function inferControl(dataType, schema, options) {
617
+ if (dataType === "boolean") return "checkbox";
618
+ if (options !== void 0) return "select";
619
+ if (dataType === "number" || dataType === "integer") return "number";
620
+ if (dataType === "file") return "file";
621
+ switch (schema.format) {
622
+ case "date": return "date";
623
+ case "date-time": return "text";
624
+ case "email": return "email";
625
+ case "password": return "password";
626
+ case "tel": return "tel";
627
+ case "time": return "time";
628
+ case "uri":
629
+ case "url": return "url";
630
+ default: return "text";
631
+ }
632
+ }
633
+ const NATIVE_INPUT_TYPES = new Set([
634
+ "checkbox",
635
+ "date",
636
+ "datetime-local",
637
+ "email",
638
+ "file",
639
+ "hidden",
640
+ "number",
641
+ "password",
642
+ "range",
643
+ "search",
644
+ "tel",
645
+ "text",
646
+ "time",
647
+ "url"
648
+ ]);
649
+ function inputType(control) {
650
+ return NATIVE_INPUT_TYPES.has(control) ? control : void 0;
651
+ }
652
+ function constraints(schema, config, acceptedMediaTypes) {
653
+ return {
654
+ ...schema.format ? { format: schema.format } : {},
655
+ ...schema.minLength !== void 0 ? { minLength: schema.minLength } : {},
656
+ ...schema.maxLength !== void 0 ? { maxLength: schema.maxLength } : {},
657
+ ...schema.pattern ? { pattern: schema.pattern } : {},
658
+ ...schema.minimum !== void 0 ? { minimum: schema.minimum } : {},
659
+ ...schema.maximum !== void 0 ? { maximum: schema.maximum } : {},
660
+ ...schema.exclusiveMinimum !== void 0 ? { exclusiveMinimum: schema.exclusiveMinimum } : {},
661
+ ...schema.exclusiveMaximum !== void 0 ? { exclusiveMaximum: schema.exclusiveMaximum } : {},
662
+ ...schema.multipleOf !== void 0 ? { multipleOf: schema.multipleOf } : {},
663
+ ...acceptedMediaTypes?.length ? { accept: acceptedMediaTypes.join(",") } : schema.contentMediaType ? { accept: schema.contentMediaType } : {},
664
+ multiple: config.multiple,
665
+ ...schema.contentEncoding ? { contentEncoding: schema.contentEncoding } : {},
666
+ ...schema.contentMediaType ? { contentMediaType: schema.contentMediaType } : {}
667
+ };
668
+ }
669
+ function isFileSchema(schema) {
670
+ return schema.format === "binary" || schema.contentEncoding === "binary" || schema.contentMediaType !== void 0;
671
+ }
672
+ function nonNullUnionBranch(branch, context) {
673
+ const resolved = resolveSchema(branch, context);
674
+ if (resolved.error) return {
675
+ branch,
676
+ nullable: false
677
+ };
678
+ if (resolved.schema === false) return { nullable: false };
679
+ if (resolved.schema === true) return {
680
+ branch: true,
681
+ nullable: false
682
+ };
683
+ const collapsed = collapseAllOf(resolved.schema, resolved.context);
684
+ if (collapsed.error) return {
685
+ branch,
686
+ nullable: false
687
+ };
688
+ const schema = collapsed.schema;
689
+ const types = schemaTypes(schema);
690
+ if (!types.includes("null")) return {
691
+ branch,
692
+ nullable: false
693
+ };
694
+ if (types.filter((type) => type !== "null").length === 0) return { nullable: true };
695
+ if (Array.isArray(schema.type)) return {
696
+ branch: {
697
+ ...schema,
698
+ type: schema.type.filter((type) => type !== "null")
699
+ },
700
+ nullable: true
701
+ };
702
+ if (schema.enum) return {
703
+ branch: {
704
+ ...schema,
705
+ enum: schema.enum.filter((value) => value !== null)
706
+ },
707
+ nullable: true
708
+ };
709
+ return {
710
+ branch,
711
+ nullable: true
712
+ };
713
+ }
714
+ function unsupported(input, schema, reason) {
715
+ const objectSchema = isSchemaObject(schema) ? schema : {};
716
+ const override = fieldOverride(input.path, input.context.config);
717
+ return {
718
+ kind: "unsupported",
719
+ key: input.key,
720
+ path: input.path,
721
+ label: override?.label ?? objectSchema.title ?? input.labelFallback,
722
+ ...override?.description ?? objectSchema.description ? { description: override?.description ?? objectSchema.description } : {},
723
+ required: input.required,
724
+ nullable: input.nullable ?? false,
725
+ ...override?.defaultValue ?? objectSchema.default !== void 0 ? { defaultValue: override?.defaultValue ?? objectSchema.default } : {},
726
+ config: resolvedConfig(objectSchema, override),
727
+ source: schema,
728
+ reason
729
+ };
730
+ }
731
+ function compileNode(input) {
732
+ const resolved = resolveSchema(input.schema, input.context);
733
+ if (resolved.error) return unsupported(input, input.schema, resolved.error);
734
+ if (resolved.schema === false) return unsupported(input, false, "This field can never be valid");
735
+ if (resolved.schema === true) return unsupported(input, true, "The schema does not describe a renderable field");
736
+ const collapsed = collapseAllOf(resolved.schema, resolved.context);
737
+ if (collapsed.error) return unsupported(input, resolved.schema, collapsed.error);
738
+ let schema = collapsed.schema;
739
+ const nodeContext = collapsed.context;
740
+ if (schema.enum?.length === 0) return unsupported(input, schema, "An empty enum can never be valid");
741
+ if (Array.isArray(schema.type) && schema.type.length === 0) return unsupported(input, schema, "An empty type union can never be valid");
742
+ const union = schema.oneOf ?? schema.anyOf;
743
+ let nullable = input.nullable ?? false;
744
+ let unionOptions;
745
+ let objectUnion;
746
+ let acceptedMediaTypes;
747
+ if (union) {
748
+ if (union.length === 0) return unsupported(input, schema, "An empty union can never be valid");
749
+ const classified = union.map((branch) => nonNullUnionBranch(branch, nodeContext));
750
+ const nonNull = classified.flatMap((entry) => entry.branch === void 0 ? [] : [entry.branch]);
751
+ nullable ||= classified.some((entry) => entry.nullable);
752
+ acceptedMediaTypes = isFileSchema(schema) && schema.anyOf !== void 0 ? mediaTypesFromUnion(nonNull, nodeContext) : void 0;
753
+ objectUnion = acceptedMediaTypes ? void 0 : normalizedObjectUnion(nonNull, nodeContext);
754
+ if (acceptedMediaTypes) {
755
+ const { oneOf: _one, anyOf: _any, ...withoutUnion } = schema;
756
+ schema = withoutUnion;
757
+ } else if (objectUnion) {
758
+ if (input.path.includes("[]")) return unsupported(input, schema, "Discriminated object unions inside arrays are not supported yet");
759
+ const { oneOf: _one, anyOf: _any, ...withoutUnion } = schema;
760
+ const intersection = mergeSchema(withoutUnion, objectUnion.schema);
761
+ if (intersection.error) return unsupported(input, schema, intersection.error);
762
+ schema = intersection.schema;
763
+ } else if (nonNull.length === 1 && isSchemaObject(nonNull[0])) {
764
+ const { oneOf: _one, anyOf: _any, ...withoutUnion } = schema;
765
+ const resolvedBranch = resolveSchema(nonNull[0], nodeContext);
766
+ if (resolvedBranch.error) return unsupported(input, schema, resolvedBranch.error);
767
+ if (resolvedBranch.schema === false) return unsupported(input, schema, "This union cannot be valid");
768
+ if (resolvedBranch.schema === true) return unsupported(input, schema, "An unconstrained union cannot be represented as one form control");
769
+ const collapsedBranch = collapseAllOf(resolvedBranch.schema, resolvedBranch.context);
770
+ if (collapsedBranch.error) return unsupported(input, schema, collapsedBranch.error);
771
+ const intersection = mergeSchema(withoutUnion, collapsedBranch.schema);
772
+ if (intersection.error) return unsupported(input, schema, intersection.error);
773
+ schema = intersection.schema;
774
+ } else {
775
+ unionOptions = optionsFromUnion(nonNull, nodeContext, schema.oneOf !== void 0);
776
+ if (!unionOptions || unionOptions.length === 0) return unsupported(input, schema, "This union cannot be represented as one form control");
777
+ const { oneOf: _one, anyOf: _any, ...withoutUnion } = schema;
778
+ schema = {
779
+ ...withoutUnion,
780
+ enum: unionOptions.map((option) => option.value)
781
+ };
782
+ }
783
+ }
784
+ const types = schemaTypes(schema).filter((type) => type !== "null");
785
+ nullable ||= schemaTypes(schema).includes("null");
786
+ const type = types.length === 1 ? types[0] : void 0;
787
+ const override = fieldOverride(input.path, input.context.config);
788
+ const extensions = extensionConfig(schema);
789
+ const label = override?.label ?? schema.title ?? stringValue(extensions.label) ?? input.labelFallback;
790
+ const description = override?.description ?? schema.description ?? stringValue(extensions.description);
791
+ const defaultValue = override?.defaultValue ?? schema.default;
792
+ const config = resolvedConfig(schema, override);
793
+ const common = {
794
+ key: input.key,
795
+ path: input.path,
796
+ label,
797
+ ...description ? { description } : {},
798
+ required: input.required,
799
+ nullable,
800
+ ...defaultValue !== void 0 ? { defaultValue } : {},
801
+ config,
802
+ source: schema
803
+ };
804
+ if (type === "object") {
805
+ const hasProperties = Object.keys(schema.properties ?? {}).length > 0;
806
+ if (schema.additionalProperties === true || isSchemaObject(schema.additionalProperties ?? false) || Object.keys(schema.patternProperties ?? {}).length > 0 || !hasProperties && schema.propertyNames !== void 0) return unsupported(input, schema, "Dynamic record keys are not supported yet");
807
+ const required = new Set(schema.required ?? []);
808
+ const children = Object.entries(schema.properties ?? {}).map(([key, child]) => {
809
+ const path = input.path ? `${input.path}.${key}` : key;
810
+ const childInput = {
811
+ context: nodeContext,
812
+ key,
813
+ labelFallback: humanize(key),
814
+ path,
815
+ required: required.has(key),
816
+ schema: child
817
+ };
818
+ const invalidPath = formPathSegmentError(key);
819
+ const compiled = invalidPath ? unsupported(childInput, child, invalidPath) : compileNode(childInput);
820
+ const unionInfo = objectUnion;
821
+ const rule = unionInfo?.rules[key];
822
+ if (!unionInfo || !rule || path.includes("[]")) return compiled;
823
+ const discriminatorPath = input.path ? `${input.path}.${unionInfo.discriminator}` : unionInfo.discriminator;
824
+ const existingHidden = compiled.config.hidden;
825
+ const existingRequired = compiled.config.requiredWhenVisible;
826
+ return {
827
+ ...compiled,
828
+ config: {
829
+ ...compiled.config,
830
+ hidden: (values) => {
831
+ const selected = runtimePathValue(values, discriminatorPath);
832
+ const branchCount = unionInfo.rules[unionInfo.discriminator]?.visibleFor.length ?? 0;
833
+ const branchHidden = rule.visibleFor.length < branchCount && !rule.visibleFor.some((value) => Object.is(value, selected));
834
+ return stateValue(existingHidden, values) || branchHidden;
835
+ },
836
+ requiredWhenVisible: (values) => {
837
+ const selected = runtimePathValue(values, discriminatorPath);
838
+ const branchRequired = rule.requiredFor.some((value) => Object.is(value, selected));
839
+ return stateValue(existingRequired, values) || branchRequired;
840
+ }
841
+ }
842
+ };
843
+ }).sort((left, right) => {
844
+ return (left.config.order ?? Number.MAX_SAFE_INTEGER) - (right.config.order ?? Number.MAX_SAFE_INTEGER);
845
+ });
846
+ return {
847
+ kind: "object",
848
+ ...common,
849
+ children
850
+ };
851
+ }
852
+ if (type === "array") {
853
+ const itemSchema = schema.items;
854
+ if (Array.isArray(itemSchema) || schema.prefixItems) return unsupported(input, schema, "Tuple schemas are not supported yet");
855
+ if (!itemSchema) return unsupported(input, schema, "Array schema is missing its item schema");
856
+ const itemPath = `${input.path}[]`;
857
+ const item = compileNode({
858
+ context: nodeContext,
859
+ key: input.key,
860
+ labelFallback: `${label} item`,
861
+ path: itemPath,
862
+ required: true,
863
+ schema: itemSchema
864
+ });
865
+ return {
866
+ kind: "array",
867
+ ...common,
868
+ item,
869
+ ...schema.minItems !== void 0 ? { minItems: schema.minItems } : {},
870
+ ...schema.maxItems !== void 0 ? { maxItems: schema.maxItems } : {},
871
+ uniqueItems: schema.uniqueItems ?? false
872
+ };
873
+ }
874
+ const configuredOptions = Array.isArray(override?.options) ? override.options : void 0;
875
+ const hasDynamicOptions = typeof override?.options === "function";
876
+ let options = configuredOptions ?? unionOptions ?? optionsFromSchema(schema);
877
+ const isFile = isFileSchema(schema) || acceptedMediaTypes !== void 0;
878
+ if (isFile && config.multiple) return unsupported(input, schema, "Multiple file selection requires an array-of-files schema");
879
+ const dataType = isFile ? "file" : type === "string" || type === "number" || type === "integer" || type === "boolean" ? type : "unknown";
880
+ if (nullable && options && !options.some((option) => option.value === null)) options = [...options, {
881
+ label: "None",
882
+ value: null
883
+ }];
884
+ if (nullable && dataType === "boolean" && !options) options = [
885
+ {
886
+ label: "Yes",
887
+ value: true
888
+ },
889
+ {
890
+ label: "No",
891
+ value: false
892
+ },
893
+ {
894
+ label: "None",
895
+ value: null
896
+ }
897
+ ];
898
+ if (dataType === "unknown" && !options) return unsupported(input, schema, `Unsupported JSON Schema type “${type ?? "unknown"}”`);
899
+ const control = config.control ?? (hasDynamicOptions ? "select" : nullable && dataType === "boolean" ? "select" : inferControl(dataType, schema, options));
900
+ const nativeType = inputType(control);
901
+ return {
902
+ kind: "scalar",
903
+ ...common,
904
+ dataType,
905
+ control,
906
+ ...nativeType ? { inputType: nativeType } : {},
907
+ constraints: constraints(schema, config, acceptedMediaTypes),
908
+ ...options ? { options } : {}
909
+ };
910
+ }
911
+ function collectFields(node, target) {
912
+ if (node.path) defineOwn(target, node.path, node);
913
+ if (node.kind === "object") for (const child of node.children) collectFields(child, target);
914
+ else if (node.kind === "array") collectFields(node.item, target);
915
+ }
916
+ function arkTypeLibraryOptions(schema, config) {
917
+ const configured = config.jsonSchema?.libraryOptions;
918
+ if (schema["~standard"].vendor !== "arktype" || config.jsonSchema?.opaqueRefinements === "error") return configured;
919
+ const existingFallback = isRecord$2(configured?.fallback) ? configured.fallback : {};
920
+ return {
921
+ ...configured,
922
+ fallback: {
923
+ ...existingFallback,
924
+ predicate: (context) => context.base
925
+ }
926
+ };
927
+ }
928
+ function toInputJsonSchema(schema, config = {}) {
929
+ try {
930
+ return schema["~standard"].jsonSchema.input({
931
+ target: "draft-2020-12",
932
+ ...arkTypeLibraryOptions(schema, config) ? { libraryOptions: arkTypeLibraryOptions(schema, config) } : {}
933
+ });
934
+ } catch (error) {
935
+ throw new SchemaConversionError(`Unable to convert the ${schema["~standard"].vendor} schema to JSON Schema for form inference.`, { cause: error });
936
+ }
937
+ }
938
+ function compileForm(schema, config = {}) {
939
+ const rootSchema = toInputJsonSchema(schema, config);
940
+ const root = compileNode({
941
+ context: {
942
+ config,
943
+ rootSchema,
944
+ resolvingRefs: /* @__PURE__ */ new Set()
945
+ },
946
+ key: "root",
947
+ labelFallback: rootSchema.title ?? "Form",
948
+ path: "",
949
+ required: true,
950
+ schema: rootSchema
951
+ });
952
+ const fieldMap = createNullRecord();
953
+ collectFields(root, fieldMap);
954
+ return {
955
+ root,
956
+ fields: root.kind === "object" ? root.children : [root],
957
+ fieldMap
958
+ };
959
+ }
960
+ //#endregion
961
+ //#region src/defaults.ts
962
+ function cloneDefault(value) {
963
+ if (Array.isArray(value)) return value.map(cloneDefault);
964
+ if (typeof value !== "object" || value === null) return value;
965
+ if (value instanceof Date) return new Date(value.getTime());
966
+ const prototype = Object.getPrototypeOf(value);
967
+ if (prototype !== Object.prototype && prototype !== null) return value;
968
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, cloneDefault(child)]));
969
+ }
970
+ function defaultValueForNode(node) {
971
+ if (node.defaultValue !== void 0) return cloneDefault(node.defaultValue);
972
+ if (!node.required) return void 0;
973
+ switch (node.kind) {
974
+ case "array": {
975
+ const count = node.minItems ?? 0;
976
+ return Array.from({ length: count }, () => cloneDefault(defaultValueForNode(node.item)));
977
+ }
978
+ case "object": {
979
+ const value = {};
980
+ for (const child of node.children) {
981
+ const childDefault = defaultValueForNode(child);
982
+ if (childDefault !== void 0) defineOwn(value, child.key, childDefault);
983
+ }
984
+ return value;
985
+ }
986
+ case "scalar":
987
+ if (node.dataType === "boolean") return false;
988
+ if (node.dataType === "string" && node.required && !node.options) return "";
989
+ return;
990
+ case "unsupported": return;
991
+ }
992
+ }
993
+ function getDefaultValues(model) {
994
+ return defaultValueForNode(model.root) ?? {};
995
+ }
996
+ //#endregion
997
+ //#region src/path.ts
998
+ function pathToName(path) {
999
+ return path.map(String).join(".");
1000
+ }
1001
+ function pathToConfigPath(path) {
1002
+ let result = "";
1003
+ for (const segment of path) if (typeof segment === "number") result += "[]";
1004
+ else result += result === "" ? segment : `.${segment}`;
1005
+ return result;
1006
+ }
1007
+ //#endregion
1008
+ //#region src/rules.ts
1009
+ function isRecord$1(value) {
1010
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1011
+ }
1012
+ function resolveFieldState(state, values, fallback = false) {
1013
+ if (typeof state === "function") return Boolean(state(values));
1014
+ return typeof state === "boolean" ? state : fallback;
1015
+ }
1016
+ function isEmptyFieldValue(value) {
1017
+ if (value === void 0 || value === null || value === "") return true;
1018
+ if (Array.isArray(value)) return value.length === 0;
1019
+ if (isRecord$1(value)) return Object.values(value).every(isEmptyFieldValue);
1020
+ return false;
1021
+ }
1022
+ function visitRequiredRules(node, value, values, path, inheritedHidden, issues) {
1023
+ const hidden = inheritedHidden || resolveFieldState(node.config.hidden, values, false);
1024
+ const required = resolveFieldState(node.config.requiredWhenVisible, values, false);
1025
+ if (!hidden && required && isEmptyFieldValue(value)) issues.push({
1026
+ message: node.config.requiredMessage ?? `${node.label} is required`,
1027
+ path
1028
+ });
1029
+ if (node.kind === "object") {
1030
+ const record = isRecord$1(value) ? value : {};
1031
+ for (const child of node.children) visitRequiredRules(child, ownValue(record, child.key), values, [...path, child.key], hidden, issues);
1032
+ } else if (node.kind === "array" && Array.isArray(value)) for (const [index, item] of value.entries()) visitRequiredRules(node.item, item, values, [...path, index], hidden, issues);
1033
+ }
1034
+ /** Validates portable presentation rules that are intentionally outside the schema. */
1035
+ function validatePresentationRules(model, values) {
1036
+ const issues = [];
1037
+ const record = isRecord$1(values) ? values : {};
1038
+ visitRequiredRules(model.root, values, record, [], false, issues);
1039
+ return issues;
1040
+ }
1041
+ //#endregion
1042
+ //#region src/prepare.ts
1043
+ function isRecord(value) {
1044
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1045
+ }
1046
+ function prepareNodeValue(field, value, rootValues) {
1047
+ if (field.path !== "" && resolveFieldState(field.config.hidden, rootValues, false)) return void 0;
1048
+ if (field.kind === "scalar") {
1049
+ if (value !== "") return value;
1050
+ if (field.nullable) return null;
1051
+ if (!field.required) return void 0;
1052
+ if (field.dataType === "number" || field.dataType === "integer") return;
1053
+ return "";
1054
+ }
1055
+ if (field.kind === "array") return Array.isArray(value) ? value.map((item) => prepareNodeValue(field.item, item, rootValues)) : value;
1056
+ if (field.kind === "object") {
1057
+ const source = isRecord(value) ? value : {};
1058
+ const prepared = {};
1059
+ for (const child of field.children) {
1060
+ const next = prepareNodeValue(child, ownValue(source, child.key), rootValues);
1061
+ if (next !== void 0) defineOwn(prepared, child.key, next);
1062
+ }
1063
+ if (!field.required && Object.keys(prepared).length === 0) return void 0;
1064
+ return prepared;
1065
+ }
1066
+ return value;
1067
+ }
1068
+ /**
1069
+ * Converts browser form values to schema input: optional blanks become absent,
1070
+ * nullable blanks become null, and presentation-hidden branches are pruned.
1071
+ */
1072
+ function prepareFormValues(model, values) {
1073
+ const rootValues = isRecord(values) ? values : {};
1074
+ return prepareNodeValue(model.root, values, rootValues);
1075
+ }
1076
+ //#endregion
1077
+ //#region src/options.ts
1078
+ /** Stable DOM serialization for non-string option values. */
1079
+ function serializeOptionValue(value) {
1080
+ if (value === null) return "null:";
1081
+ return `${typeof value}:${String(value)}`;
1082
+ }
1083
+ function optionForSerializedValue(options, serialized) {
1084
+ return options.find((option) => serializeOptionValue(option.value) === serialized);
1085
+ }
1086
+ //#endregion
1087
+ //#region src/submission.ts
1088
+ const initialSubmissionState = { status: "idle" };
1089
+ const SUCCESS_KEYS = new Set([
1090
+ "data",
1091
+ "message",
1092
+ "status"
1093
+ ]);
1094
+ const ERROR_KEYS = new Set([
1095
+ "errorKind",
1096
+ "fieldErrors",
1097
+ "formErrors",
1098
+ "status"
1099
+ ]);
1100
+ function submissionFailure(options = {}) {
1101
+ const fieldErrors = createNullRecord();
1102
+ for (const [path, messages] of Object.entries(options.fieldErrors ?? {})) defineOwn(fieldErrors, path, messages);
1103
+ return {
1104
+ errorKind: options.errorKind ?? "business",
1105
+ fieldErrors,
1106
+ formErrors: options.formErrors ?? [],
1107
+ status: "error"
1108
+ };
1109
+ }
1110
+ function submissionSuccess(data, message) {
1111
+ return {
1112
+ ...data !== void 0 ? { data } : {},
1113
+ ...message ? { message } : {},
1114
+ status: "success"
1115
+ };
1116
+ }
1117
+ function isSubmissionState(value) {
1118
+ try {
1119
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
1120
+ const state = value;
1121
+ if (!hasOwn(state, "status") || Object.getOwnPropertySymbols(state).length > 0) return false;
1122
+ const keys = Object.keys(state);
1123
+ const hasOnly = (allowed) => keys.every((key) => allowed.has(key));
1124
+ if (state.status === "idle") return keys.length === 1 && keys[0] === "status";
1125
+ if (state.status === "success") return hasOnly(SUCCESS_KEYS) && (!hasOwn(state, "message") || typeof state.message === "string");
1126
+ if (state.status !== "error") return false;
1127
+ if (!hasOnly(ERROR_KEYS)) return false;
1128
+ if (!hasOwn(state, "errorKind") || !hasOwn(state, "fieldErrors") || !hasOwn(state, "formErrors")) return false;
1129
+ if (state.errorKind !== "business" && state.errorKind !== "transport" && state.errorKind !== "validation") return false;
1130
+ if (!Array.isArray(state.formErrors) || !state.formErrors.every((message) => typeof message === "string")) return false;
1131
+ if (typeof state.fieldErrors !== "object" || state.fieldErrors === null || Array.isArray(state.fieldErrors)) return false;
1132
+ return Object.values(state.fieldErrors).every((messages) => Array.isArray(messages) && messages.every((message) => typeof message === "string"));
1133
+ } catch {
1134
+ return false;
1135
+ }
1136
+ }
1137
+ //#endregion
1138
+ //#region src/validation.ts
1139
+ function issuePath(issue) {
1140
+ return (issue.path ?? []).map((segment) => {
1141
+ if (typeof segment !== "object" || segment === null) return segment;
1142
+ return hasOwn(segment, "key") ? segment.key : String(segment);
1143
+ });
1144
+ }
1145
+ function issuesToFieldErrors(issues) {
1146
+ const errors = createNullRecord();
1147
+ for (const issue of issues) {
1148
+ const path = pathToName(issuePath(issue).map((segment) => typeof segment === "number" || typeof segment === "string" ? segment : String(segment)));
1149
+ let messages = errors[path];
1150
+ if (!messages) {
1151
+ messages = [];
1152
+ defineOwn(errors, path, messages);
1153
+ }
1154
+ messages.push(issue.message);
1155
+ }
1156
+ return errors;
1157
+ }
1158
+ async function validate(schema, value) {
1159
+ const result = await schema["~standard"].validate(value);
1160
+ if (result.issues === void 0) return {
1161
+ success: true,
1162
+ data: result.value
1163
+ };
1164
+ return {
1165
+ success: false,
1166
+ issues: result.issues.length > 0 ? result.issues : [{ message: "Schema validation failed" }]
1167
+ };
1168
+ }
1169
+ //#endregion
1170
+ export { RESERVED_FORM_PATH_SEGMENTS, SchemaConversionError, compileForm, defaultValueForNode, getDefaultValues, initialSubmissionState, isEmptyFieldValue, isReservedFormPathSegment, isSubmissionState, issuePath, issuesToFieldErrors, optionForSerializedValue, pathToConfigPath, pathToName, prepareFormValues, resolveFieldState, serializeOptionValue, submissionFailure, submissionSuccess, toInputJsonSchema, validate, validatePresentationRules };
1171
+
1172
+ //# sourceMappingURL=index.js.map