@formspec/build 0.1.0-alpha.11 → 0.1.0-alpha.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +51 -15
  2. package/dist/__tests__/chain-dsl-canonicalizer.test.d.ts +2 -0
  3. package/dist/__tests__/chain-dsl-canonicalizer.test.d.ts.map +1 -0
  4. package/dist/__tests__/constraint-validator.test.d.ts +2 -0
  5. package/dist/__tests__/constraint-validator.test.d.ts.map +1 -0
  6. package/dist/__tests__/extension-api.test.d.ts +2 -0
  7. package/dist/__tests__/extension-api.test.d.ts.map +1 -0
  8. package/dist/__tests__/fixtures/example-a-builtins.d.ts +18 -0
  9. package/dist/__tests__/fixtures/example-a-builtins.d.ts.map +1 -1
  10. package/dist/__tests__/ir-analyzer.test.d.ts +11 -0
  11. package/dist/__tests__/ir-analyzer.test.d.ts.map +1 -0
  12. package/dist/__tests__/ir-jsdoc-constraints.test.d.ts +12 -0
  13. package/dist/__tests__/ir-jsdoc-constraints.test.d.ts.map +1 -0
  14. package/dist/__tests__/ir-json-schema-generator.test.d.ts +11 -0
  15. package/dist/__tests__/ir-json-schema-generator.test.d.ts.map +1 -0
  16. package/dist/__tests__/ir-ui-schema-generator.test.d.ts +2 -0
  17. package/dist/__tests__/ir-ui-schema-generator.test.d.ts.map +1 -0
  18. package/dist/__tests__/jsdoc-constraints.test.d.ts +4 -4
  19. package/dist/__tests__/parity/fixtures/address/chain-dsl.d.ts +9 -0
  20. package/dist/__tests__/parity/fixtures/address/chain-dsl.d.ts.map +1 -0
  21. package/dist/__tests__/parity/fixtures/address/expected-ir.d.ts +9 -0
  22. package/dist/__tests__/parity/fixtures/address/expected-ir.d.ts.map +1 -0
  23. package/dist/__tests__/parity/fixtures/address/tsdoc.d.ts +19 -0
  24. package/dist/__tests__/parity/fixtures/address/tsdoc.d.ts.map +1 -0
  25. package/dist/__tests__/parity/fixtures/product-config/chain-dsl.d.ts +13 -0
  26. package/dist/__tests__/parity/fixtures/product-config/chain-dsl.d.ts.map +1 -0
  27. package/dist/__tests__/parity/fixtures/product-config/expected-ir.d.ts +9 -0
  28. package/dist/__tests__/parity/fixtures/product-config/expected-ir.d.ts.map +1 -0
  29. package/dist/__tests__/parity/fixtures/product-config/tsdoc.d.ts +28 -0
  30. package/dist/__tests__/parity/fixtures/product-config/tsdoc.d.ts.map +1 -0
  31. package/dist/__tests__/parity/fixtures/user-registration/chain-dsl.d.ts +12 -0
  32. package/dist/__tests__/parity/fixtures/user-registration/chain-dsl.d.ts.map +1 -0
  33. package/dist/__tests__/parity/fixtures/user-registration/expected-ir.d.ts +9 -0
  34. package/dist/__tests__/parity/fixtures/user-registration/expected-ir.d.ts.map +1 -0
  35. package/dist/__tests__/parity/fixtures/user-registration/tsdoc.d.ts +19 -0
  36. package/dist/__tests__/parity/fixtures/user-registration/tsdoc.d.ts.map +1 -0
  37. package/dist/__tests__/parity/parity.test.d.ts +14 -0
  38. package/dist/__tests__/parity/parity.test.d.ts.map +1 -0
  39. package/dist/__tests__/parity/utils.d.ts +139 -0
  40. package/dist/__tests__/parity/utils.d.ts.map +1 -0
  41. package/dist/analyzer/class-analyzer.d.ts +54 -99
  42. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  43. package/dist/analyzer/jsdoc-constraints.d.ts +78 -30
  44. package/dist/analyzer/jsdoc-constraints.d.ts.map +1 -1
  45. package/dist/analyzer/tsdoc-parser.d.ts +61 -0
  46. package/dist/analyzer/tsdoc-parser.d.ts.map +1 -0
  47. package/dist/browser.cjs +960 -309
  48. package/dist/browser.cjs.map +1 -1
  49. package/dist/browser.d.ts +10 -6
  50. package/dist/browser.d.ts.map +1 -1
  51. package/dist/browser.js +958 -308
  52. package/dist/browser.js.map +1 -1
  53. package/dist/build.d.ts +65 -150
  54. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts +18 -0
  55. package/dist/canonicalize/chain-dsl-canonicalizer.d.ts.map +1 -0
  56. package/dist/canonicalize/index.d.ts +8 -0
  57. package/dist/canonicalize/index.d.ts.map +1 -0
  58. package/dist/canonicalize/tsdoc-canonicalizer.d.ts +34 -0
  59. package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -0
  60. package/dist/cli.cjs +1417 -1656
  61. package/dist/cli.cjs.map +1 -1
  62. package/dist/cli.js +1421 -1647
  63. package/dist/cli.js.map +1 -1
  64. package/dist/extensions/index.d.ts +8 -0
  65. package/dist/extensions/index.d.ts.map +1 -0
  66. package/dist/extensions/registry.d.ts +55 -0
  67. package/dist/extensions/registry.d.ts.map +1 -0
  68. package/dist/generators/class-schema.d.ts +23 -38
  69. package/dist/generators/class-schema.d.ts.map +1 -1
  70. package/dist/generators/method-schema.d.ts +6 -8
  71. package/dist/generators/method-schema.d.ts.map +1 -1
  72. package/dist/index.cjs +1353 -1614
  73. package/dist/index.cjs.map +1 -1
  74. package/dist/index.d.ts +6 -8
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +1365 -1610
  77. package/dist/index.js.map +1 -1
  78. package/dist/internals.cjs +1604 -824
  79. package/dist/internals.cjs.map +1 -1
  80. package/dist/internals.d.ts +12 -3
  81. package/dist/internals.d.ts.map +1 -1
  82. package/dist/internals.js +1607 -820
  83. package/dist/internals.js.map +1 -1
  84. package/dist/json-schema/generator.d.ts +10 -5
  85. package/dist/json-schema/generator.d.ts.map +1 -1
  86. package/dist/json-schema/ir-generator.d.ts +84 -0
  87. package/dist/json-schema/ir-generator.d.ts.map +1 -0
  88. package/dist/json-schema/schema.d.ts +3 -3
  89. package/dist/json-schema/types.d.ts +5 -6
  90. package/dist/json-schema/types.d.ts.map +1 -1
  91. package/dist/ui-schema/generator.d.ts +5 -15
  92. package/dist/ui-schema/generator.d.ts.map +1 -1
  93. package/dist/ui-schema/ir-generator.d.ts +53 -0
  94. package/dist/ui-schema/ir-generator.d.ts.map +1 -0
  95. package/dist/validate/constraint-validator.d.ts +66 -0
  96. package/dist/validate/constraint-validator.d.ts.map +1 -0
  97. package/dist/validate/index.d.ts +9 -0
  98. package/dist/validate/index.d.ts.map +1 -0
  99. package/package.json +5 -4
  100. package/dist/__tests__/analyzer-edge-cases.test.d.ts +0 -13
  101. package/dist/__tests__/analyzer-edge-cases.test.d.ts.map +0 -1
  102. package/dist/__tests__/analyzer.test.d.ts +0 -5
  103. package/dist/__tests__/analyzer.test.d.ts.map +0 -1
  104. package/dist/__tests__/codegen.test.d.ts +0 -5
  105. package/dist/__tests__/codegen.test.d.ts.map +0 -1
  106. package/dist/__tests__/decorator-pipeline.test.d.ts +0 -11
  107. package/dist/__tests__/decorator-pipeline.test.d.ts.map +0 -1
  108. package/dist/__tests__/fixtures/example-b-decorators.d.ts +0 -5
  109. package/dist/__tests__/fixtures/example-b-decorators.d.ts.map +0 -1
  110. package/dist/__tests__/fixtures/example-b-extended.d.ts +0 -5
  111. package/dist/__tests__/fixtures/example-b-extended.d.ts.map +0 -1
  112. package/dist/__tests__/fixtures/example-c-custom.d.ts +0 -5
  113. package/dist/__tests__/fixtures/example-c-custom.d.ts.map +0 -1
  114. package/dist/__tests__/fixtures/example-c-decorators.d.ts +0 -5
  115. package/dist/__tests__/fixtures/example-c-decorators.d.ts.map +0 -1
  116. package/dist/__tests__/fixtures/example-d-mixed-decorators.d.ts +0 -6
  117. package/dist/__tests__/fixtures/example-d-mixed-decorators.d.ts.map +0 -1
  118. package/dist/__tests__/fixtures/example-e-decorators.d.ts +0 -11
  119. package/dist/__tests__/fixtures/example-e-decorators.d.ts.map +0 -1
  120. package/dist/__tests__/fixtures/example-e-no-namespace.d.ts +0 -5
  121. package/dist/__tests__/fixtures/example-e-no-namespace.d.ts.map +0 -1
  122. package/dist/__tests__/fixtures/example-jsdoc-constraints.d.ts +0 -16
  123. package/dist/__tests__/fixtures/example-jsdoc-constraints.d.ts.map +0 -1
  124. package/dist/__tests__/fixtures/example-nested-class.d.ts +0 -45
  125. package/dist/__tests__/fixtures/example-nested-class.d.ts.map +0 -1
  126. package/dist/__tests__/interface-types.test.d.ts +0 -11
  127. package/dist/__tests__/interface-types.test.d.ts.map +0 -1
  128. package/dist/analyzer/decorator-extractor.d.ts +0 -78
  129. package/dist/analyzer/decorator-extractor.d.ts.map +0 -1
  130. package/dist/analyzer/type-converter.d.ts +0 -75
  131. package/dist/analyzer/type-converter.d.ts.map +0 -1
  132. package/dist/codegen/index.d.ts +0 -75
  133. package/dist/codegen/index.d.ts.map +0 -1
package/dist/browser.js CHANGED
@@ -1,204 +1,532 @@
1
- // src/json-schema/schema.ts
2
- import { z } from "zod";
3
- var jsonSchemaTypeSchema = z.enum([
4
- "string",
5
- "number",
6
- "integer",
7
- "boolean",
8
- "object",
9
- "array",
10
- "null"
11
- ]);
12
- var jsonSchema7Schema = z.lazy(
13
- () => z.object({
14
- $schema: z.string().optional(),
15
- $id: z.string().optional(),
16
- $ref: z.string().optional(),
17
- // Metadata
18
- title: z.string().optional(),
19
- description: z.string().optional(),
20
- deprecated: z.boolean().optional(),
21
- // Type
22
- type: z.union([jsonSchemaTypeSchema, z.array(jsonSchemaTypeSchema)]).optional(),
23
- // String validation
24
- minLength: z.number().optional(),
25
- maxLength: z.number().optional(),
26
- pattern: z.string().optional(),
27
- // Number validation
28
- minimum: z.number().optional(),
29
- maximum: z.number().optional(),
30
- exclusiveMinimum: z.number().optional(),
31
- exclusiveMaximum: z.number().optional(),
32
- // Enum
33
- enum: z.array(z.union([z.string(), z.number(), z.boolean(), z.null()])).readonly().optional(),
34
- const: z.union([z.string(), z.number(), z.boolean(), z.null()]).optional(),
35
- // Object
36
- properties: z.record(z.string(), jsonSchema7Schema).optional(),
37
- required: z.array(z.string()).optional(),
38
- additionalProperties: z.union([z.boolean(), jsonSchema7Schema]).optional(),
39
- // Array
40
- items: z.union([jsonSchema7Schema, z.array(jsonSchema7Schema)]).optional(),
41
- minItems: z.number().optional(),
42
- maxItems: z.number().optional(),
43
- // Composition
44
- allOf: z.array(jsonSchema7Schema).optional(),
45
- anyOf: z.array(jsonSchema7Schema).optional(),
46
- oneOf: z.array(jsonSchema7Schema).optional(),
47
- not: jsonSchema7Schema.optional(),
48
- // Conditional
49
- if: jsonSchema7Schema.optional(),
50
- then: jsonSchema7Schema.optional(),
51
- else: jsonSchema7Schema.optional(),
52
- // Format
53
- format: z.string().optional(),
54
- // Default
55
- default: z.unknown().optional(),
56
- // FormSpec extensions
57
- "x-formspec-source": z.string().optional(),
58
- "x-formspec-params": z.array(z.string()).readonly().optional(),
59
- "x-formspec-schemaSource": z.string().optional()
60
- }).passthrough()
61
- );
62
-
63
- // src/json-schema/generator.ts
64
- import { z as z2 } from "zod";
65
- function parseOrThrow(schema, value, label) {
66
- try {
67
- return schema.parse(value);
68
- } catch (error) {
69
- if (error instanceof z2.ZodError) {
70
- throw new Error(
71
- `Generated ${label} failed validation:
72
- ${error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n")}`
73
- );
74
- }
75
- throw error;
76
- }
1
+ // src/canonicalize/chain-dsl-canonicalizer.ts
2
+ import { IR_VERSION } from "@formspec/core";
3
+ var CHAIN_DSL_PROVENANCE = {
4
+ surface: "chain-dsl",
5
+ file: "",
6
+ line: 0,
7
+ column: 0
8
+ };
9
+ function isGroup(el) {
10
+ return el._type === "group";
77
11
  }
78
- function generateNestedSchema(elements) {
79
- const properties = {};
80
- const required = [];
81
- collectFields(elements, properties, required);
82
- const uniqueRequired = [...new Set(required)];
12
+ function isConditional(el) {
13
+ return el._type === "conditional";
14
+ }
15
+ function isField(el) {
16
+ return el._type === "field";
17
+ }
18
+ function canonicalizeChainDSL(form) {
83
19
  return {
84
- type: "object",
85
- properties,
86
- ...uniqueRequired.length > 0 && { required: uniqueRequired }
20
+ kind: "form-ir",
21
+ irVersion: IR_VERSION,
22
+ elements: canonicalizeElements(form.elements),
23
+ typeRegistry: {},
24
+ provenance: CHAIN_DSL_PROVENANCE
87
25
  };
88
26
  }
89
- function fieldToJsonSchema(field) {
90
- const base = {};
91
- if (field.label !== void 0) {
92
- base.title = field.label;
27
+ function canonicalizeElements(elements) {
28
+ return elements.map(canonicalizeElement);
29
+ }
30
+ function canonicalizeElement(element) {
31
+ if (isField(element)) {
32
+ return canonicalizeField(element);
33
+ }
34
+ if (isGroup(element)) {
35
+ return canonicalizeGroup(element);
93
36
  }
37
+ if (isConditional(element)) {
38
+ return canonicalizeConditional(element);
39
+ }
40
+ const _exhaustive = element;
41
+ throw new Error(`Unknown element type: ${JSON.stringify(_exhaustive)}`);
42
+ }
43
+ function canonicalizeField(field) {
94
44
  switch (field._field) {
95
45
  case "text":
96
- return { ...base, type: "string" };
46
+ return canonicalizeTextField(field);
97
47
  case "number":
98
- return {
99
- ...base,
100
- type: "number",
101
- ...field.min !== void 0 && { minimum: field.min },
102
- ...field.max !== void 0 && { maximum: field.max }
103
- };
48
+ return canonicalizeNumberField(field);
104
49
  case "boolean":
105
- return { ...base, type: "boolean" };
106
- case "enum": {
107
- const opts = field.options;
108
- const isObjectOptions = opts.length > 0 && opts.every(
109
- (opt) => typeof opt === "object" && "id" in opt && "label" in opt
110
- );
111
- if (isObjectOptions) {
112
- return {
113
- ...base,
114
- type: "string",
115
- oneOf: opts.map((o) => ({
116
- const: o.id,
117
- title: o.label
118
- }))
119
- };
120
- }
121
- return { ...base, type: "string", enum: opts };
122
- }
50
+ return canonicalizeBooleanField(field);
51
+ case "enum":
52
+ return canonicalizeStaticEnumField(field);
123
53
  case "dynamic_enum":
124
- return {
125
- ...base,
126
- type: "string",
127
- "x-formspec-source": field.source,
128
- ...field.params !== void 0 && field.params.length > 0 && { "x-formspec-params": field.params }
129
- };
54
+ return canonicalizeDynamicEnumField(field);
130
55
  case "dynamic_schema":
131
- return {
132
- ...base,
133
- type: "object",
134
- additionalProperties: true,
135
- "x-formspec-schemaSource": field.schemaSource
136
- };
137
- case "array": {
138
- const arrayField = field;
139
- return {
140
- ...base,
141
- type: "array",
142
- items: generateNestedSchema(arrayField.items),
143
- ...arrayField.minItems !== void 0 && { minItems: arrayField.minItems },
144
- ...arrayField.maxItems !== void 0 && { maxItems: arrayField.maxItems }
145
- };
146
- }
147
- case "object": {
148
- const objectField = field;
149
- const nestedSchema = generateNestedSchema(objectField.properties);
150
- return {
151
- ...base,
152
- ...nestedSchema
153
- };
154
- }
56
+ return canonicalizeDynamicSchemaField(field);
57
+ case "array":
58
+ return canonicalizeArrayField(field);
59
+ case "object":
60
+ return canonicalizeObjectField(field);
155
61
  default: {
156
62
  const _exhaustive = field;
157
- return _exhaustive;
63
+ throw new Error(`Unknown field type: ${JSON.stringify(_exhaustive)}`);
158
64
  }
159
65
  }
160
66
  }
161
- function collectFields(elements, properties, required) {
67
+ function canonicalizeTextField(field) {
68
+ const type = { kind: "primitive", primitiveKind: "string" };
69
+ return buildFieldNode(
70
+ field.name,
71
+ type,
72
+ field.required,
73
+ buildAnnotations(field.label, field.placeholder)
74
+ );
75
+ }
76
+ function canonicalizeNumberField(field) {
77
+ const type = { kind: "primitive", primitiveKind: "number" };
78
+ const constraints = [];
79
+ if (field.min !== void 0) {
80
+ const c = {
81
+ kind: "constraint",
82
+ constraintKind: "minimum",
83
+ value: field.min,
84
+ provenance: CHAIN_DSL_PROVENANCE
85
+ };
86
+ constraints.push(c);
87
+ }
88
+ if (field.max !== void 0) {
89
+ const c = {
90
+ kind: "constraint",
91
+ constraintKind: "maximum",
92
+ value: field.max,
93
+ provenance: CHAIN_DSL_PROVENANCE
94
+ };
95
+ constraints.push(c);
96
+ }
97
+ return buildFieldNode(
98
+ field.name,
99
+ type,
100
+ field.required,
101
+ buildAnnotations(field.label),
102
+ constraints
103
+ );
104
+ }
105
+ function canonicalizeBooleanField(field) {
106
+ const type = { kind: "primitive", primitiveKind: "boolean" };
107
+ return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
108
+ }
109
+ function canonicalizeStaticEnumField(field) {
110
+ const members = field.options.map((opt) => {
111
+ if (typeof opt === "string") {
112
+ return { value: opt };
113
+ }
114
+ return { value: opt.id, displayName: opt.label };
115
+ });
116
+ const type = { kind: "enum", members };
117
+ return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
118
+ }
119
+ function canonicalizeDynamicEnumField(field) {
120
+ const type = {
121
+ kind: "dynamic",
122
+ dynamicKind: "enum",
123
+ sourceKey: field.source,
124
+ parameterFields: field.params ? [...field.params] : []
125
+ };
126
+ return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
127
+ }
128
+ function canonicalizeDynamicSchemaField(field) {
129
+ const type = {
130
+ kind: "dynamic",
131
+ dynamicKind: "schema",
132
+ sourceKey: field.schemaSource,
133
+ parameterFields: []
134
+ };
135
+ return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
136
+ }
137
+ function canonicalizeArrayField(field) {
138
+ const itemProperties = buildObjectProperties(field.items);
139
+ const itemsType = {
140
+ kind: "object",
141
+ properties: itemProperties,
142
+ additionalProperties: false
143
+ };
144
+ const type = { kind: "array", items: itemsType };
145
+ const constraints = [];
146
+ if (field.minItems !== void 0) {
147
+ const c = {
148
+ kind: "constraint",
149
+ constraintKind: "minItems",
150
+ value: field.minItems,
151
+ provenance: CHAIN_DSL_PROVENANCE
152
+ };
153
+ constraints.push(c);
154
+ }
155
+ if (field.maxItems !== void 0) {
156
+ const c = {
157
+ kind: "constraint",
158
+ constraintKind: "maxItems",
159
+ value: field.maxItems,
160
+ provenance: CHAIN_DSL_PROVENANCE
161
+ };
162
+ constraints.push(c);
163
+ }
164
+ return buildFieldNode(
165
+ field.name,
166
+ type,
167
+ field.required,
168
+ buildAnnotations(field.label),
169
+ constraints
170
+ );
171
+ }
172
+ function canonicalizeObjectField(field) {
173
+ const properties = buildObjectProperties(field.properties);
174
+ const type = {
175
+ kind: "object",
176
+ properties,
177
+ additionalProperties: false
178
+ };
179
+ return buildFieldNode(field.name, type, field.required, buildAnnotations(field.label));
180
+ }
181
+ function canonicalizeGroup(g) {
182
+ return {
183
+ kind: "group",
184
+ label: g.label,
185
+ elements: canonicalizeElements(g.elements),
186
+ provenance: CHAIN_DSL_PROVENANCE
187
+ };
188
+ }
189
+ function canonicalizeConditional(c) {
190
+ return {
191
+ kind: "conditional",
192
+ fieldName: c.field,
193
+ // Conditional values from the chain DSL are JSON-serializable primitives
194
+ // (strings, numbers, booleans) produced by the `is()` predicate helper.
195
+ value: assertJsonValue(c.value),
196
+ elements: canonicalizeElements(c.elements),
197
+ provenance: CHAIN_DSL_PROVENANCE
198
+ };
199
+ }
200
+ function assertJsonValue(v) {
201
+ if (v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
202
+ return v;
203
+ }
204
+ if (Array.isArray(v)) {
205
+ return v.map(assertJsonValue);
206
+ }
207
+ if (typeof v === "object") {
208
+ const result = {};
209
+ for (const [key, val] of Object.entries(v)) {
210
+ result[key] = assertJsonValue(val);
211
+ }
212
+ return result;
213
+ }
214
+ throw new TypeError(`Conditional value is not a valid JsonValue: ${typeof v}`);
215
+ }
216
+ function buildFieldNode(name, type, required, annotations, constraints = []) {
217
+ return {
218
+ kind: "field",
219
+ name,
220
+ type,
221
+ required: required === true,
222
+ constraints,
223
+ annotations,
224
+ provenance: CHAIN_DSL_PROVENANCE
225
+ };
226
+ }
227
+ function buildAnnotations(label, placeholder) {
228
+ const annotations = [];
229
+ if (label !== void 0) {
230
+ const a = {
231
+ kind: "annotation",
232
+ annotationKind: "displayName",
233
+ value: label,
234
+ provenance: CHAIN_DSL_PROVENANCE
235
+ };
236
+ annotations.push(a);
237
+ }
238
+ if (placeholder !== void 0) {
239
+ const a = {
240
+ kind: "annotation",
241
+ annotationKind: "placeholder",
242
+ value: placeholder,
243
+ provenance: CHAIN_DSL_PROVENANCE
244
+ };
245
+ annotations.push(a);
246
+ }
247
+ return annotations;
248
+ }
249
+ function buildObjectProperties(elements, insideConditional = false) {
250
+ const properties = [];
251
+ for (const el of elements) {
252
+ if (isField(el)) {
253
+ const fieldNode = canonicalizeField(el);
254
+ properties.push({
255
+ name: fieldNode.name,
256
+ type: fieldNode.type,
257
+ // Fields inside a conditional branch are always optional in the
258
+ // data schema, regardless of their `required` flag — the condition
259
+ // may not be met, so the field may be absent.
260
+ optional: insideConditional || !fieldNode.required,
261
+ constraints: fieldNode.constraints,
262
+ annotations: fieldNode.annotations,
263
+ provenance: CHAIN_DSL_PROVENANCE
264
+ });
265
+ } else if (isGroup(el)) {
266
+ properties.push(...buildObjectProperties(el.elements, insideConditional));
267
+ } else if (isConditional(el)) {
268
+ properties.push(...buildObjectProperties(el.elements, true));
269
+ }
270
+ }
271
+ return properties;
272
+ }
273
+
274
+ // src/canonicalize/tsdoc-canonicalizer.ts
275
+ import { IR_VERSION as IR_VERSION2 } from "@formspec/core";
276
+
277
+ // src/json-schema/ir-generator.ts
278
+ function makeContext() {
279
+ return { defs: {} };
280
+ }
281
+ function generateJsonSchemaFromIR(ir) {
282
+ const ctx = makeContext();
283
+ for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
284
+ ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
285
+ }
286
+ const properties = {};
287
+ const required = [];
288
+ collectFields(ir.elements, properties, required, ctx);
289
+ const uniqueRequired = [...new Set(required)];
290
+ const result = {
291
+ $schema: "https://json-schema.org/draft/2020-12/schema",
292
+ type: "object",
293
+ properties,
294
+ ...uniqueRequired.length > 0 && { required: uniqueRequired }
295
+ };
296
+ if (Object.keys(ctx.defs).length > 0) {
297
+ result.$defs = ctx.defs;
298
+ }
299
+ return result;
300
+ }
301
+ function collectFields(elements, properties, required, ctx) {
162
302
  for (const element of elements) {
163
- switch (element._type) {
303
+ switch (element.kind) {
164
304
  case "field":
165
- properties[element.name] = fieldToJsonSchema(element);
166
- if (element.required === true) {
305
+ properties[element.name] = generateFieldSchema(element, ctx);
306
+ if (element.required) {
167
307
  required.push(element.name);
168
308
  }
169
309
  break;
170
310
  case "group":
171
- collectFields(element.elements, properties, required);
311
+ collectFields(element.elements, properties, required, ctx);
172
312
  break;
173
313
  case "conditional":
174
- collectFields(
175
- element.elements,
176
- properties,
177
- required
178
- );
314
+ collectFields(element.elements, properties, required, ctx);
179
315
  break;
316
+ default: {
317
+ const _exhaustive = element;
318
+ void _exhaustive;
319
+ }
180
320
  }
181
321
  }
182
322
  }
183
- function generateJsonSchema(form) {
323
+ function generateFieldSchema(field, ctx) {
324
+ const schema = generateTypeNode(field.type, ctx);
325
+ applyConstraints(schema, field.constraints);
326
+ applyAnnotations(schema, field.annotations);
327
+ return schema;
328
+ }
329
+ function generateTypeNode(type, ctx) {
330
+ switch (type.kind) {
331
+ case "primitive":
332
+ return generatePrimitiveType(type);
333
+ case "enum":
334
+ return generateEnumType(type);
335
+ case "array":
336
+ return generateArrayType(type, ctx);
337
+ case "object":
338
+ return generateObjectType(type, ctx);
339
+ case "union":
340
+ return generateUnionType(type, ctx);
341
+ case "reference":
342
+ return generateReferenceType(type);
343
+ case "dynamic":
344
+ return generateDynamicType(type);
345
+ case "custom":
346
+ return generateCustomType(type);
347
+ default: {
348
+ const _exhaustive = type;
349
+ return _exhaustive;
350
+ }
351
+ }
352
+ }
353
+ function generatePrimitiveType(type) {
354
+ return { type: type.primitiveKind };
355
+ }
356
+ function generateEnumType(type) {
357
+ const hasDisplayNames = type.members.some((m) => m.displayName !== void 0);
358
+ if (hasDisplayNames) {
359
+ return {
360
+ oneOf: type.members.map((m) => {
361
+ const entry = { const: m.value };
362
+ if (m.displayName !== void 0) {
363
+ entry.title = m.displayName;
364
+ }
365
+ return entry;
366
+ })
367
+ };
368
+ }
369
+ return { enum: type.members.map((m) => m.value) };
370
+ }
371
+ function generateArrayType(type, ctx) {
372
+ return {
373
+ type: "array",
374
+ items: generateTypeNode(type.items, ctx)
375
+ };
376
+ }
377
+ function generateObjectType(type, ctx) {
184
378
  const properties = {};
185
379
  const required = [];
186
- collectFields(form.elements, properties, required);
187
- const uniqueRequired = [...new Set(required)];
188
- const result = {
189
- $schema: "https://json-schema.org/draft-07/schema#",
380
+ for (const prop of type.properties) {
381
+ properties[prop.name] = generatePropertySchema(prop, ctx);
382
+ if (!prop.optional) {
383
+ required.push(prop.name);
384
+ }
385
+ }
386
+ const schema = { type: "object", properties };
387
+ if (required.length > 0) {
388
+ schema.required = required;
389
+ }
390
+ if (!type.additionalProperties) {
391
+ schema.additionalProperties = false;
392
+ }
393
+ return schema;
394
+ }
395
+ function generatePropertySchema(prop, ctx) {
396
+ const schema = generateTypeNode(prop.type, ctx);
397
+ applyConstraints(schema, prop.constraints);
398
+ applyAnnotations(schema, prop.annotations);
399
+ return schema;
400
+ }
401
+ function generateUnionType(type, ctx) {
402
+ if (isBooleanUnion(type)) {
403
+ return { type: "boolean" };
404
+ }
405
+ return {
406
+ anyOf: type.members.map((m) => generateTypeNode(m, ctx))
407
+ };
408
+ }
409
+ function isBooleanUnion(type) {
410
+ if (type.members.length !== 2) return false;
411
+ const kinds = type.members.map((m) => m.kind);
412
+ return kinds.every((k) => k === "primitive") && type.members.every((m) => m.kind === "primitive" && m.primitiveKind === "boolean");
413
+ }
414
+ function generateReferenceType(type) {
415
+ return { $ref: `#/$defs/${type.name}` };
416
+ }
417
+ function generateDynamicType(type) {
418
+ if (type.dynamicKind === "enum") {
419
+ const schema = {
420
+ type: "string",
421
+ "x-formspec-source": type.sourceKey
422
+ };
423
+ if (type.parameterFields.length > 0) {
424
+ schema["x-formspec-params"] = [...type.parameterFields];
425
+ }
426
+ return schema;
427
+ }
428
+ return {
190
429
  type: "object",
191
- properties,
192
- ...uniqueRequired.length > 0 && { required: uniqueRequired }
430
+ additionalProperties: true,
431
+ "x-formspec-schemaSource": type.sourceKey
193
432
  };
194
- return parseOrThrow(jsonSchema7Schema, result, "JSON Schema");
433
+ }
434
+ function generateCustomType(_type) {
435
+ return { type: "object" };
436
+ }
437
+ function applyConstraints(schema, constraints) {
438
+ for (const constraint of constraints) {
439
+ switch (constraint.constraintKind) {
440
+ case "minimum":
441
+ schema.minimum = constraint.value;
442
+ break;
443
+ case "maximum":
444
+ schema.maximum = constraint.value;
445
+ break;
446
+ case "exclusiveMinimum":
447
+ schema.exclusiveMinimum = constraint.value;
448
+ break;
449
+ case "exclusiveMaximum":
450
+ schema.exclusiveMaximum = constraint.value;
451
+ break;
452
+ case "multipleOf": {
453
+ const { value } = constraint;
454
+ if (value === 1 && schema.type === "number") {
455
+ schema.type = "integer";
456
+ } else {
457
+ schema.multipleOf = value;
458
+ }
459
+ break;
460
+ }
461
+ case "minLength":
462
+ schema.minLength = constraint.value;
463
+ break;
464
+ case "maxLength":
465
+ schema.maxLength = constraint.value;
466
+ break;
467
+ case "minItems":
468
+ schema.minItems = constraint.value;
469
+ break;
470
+ case "maxItems":
471
+ schema.maxItems = constraint.value;
472
+ break;
473
+ case "pattern":
474
+ schema.pattern = constraint.pattern;
475
+ break;
476
+ case "uniqueItems":
477
+ schema.uniqueItems = constraint.value;
478
+ break;
479
+ case "allowedMembers":
480
+ break;
481
+ case "custom":
482
+ break;
483
+ default: {
484
+ const _exhaustive = constraint;
485
+ void _exhaustive;
486
+ }
487
+ }
488
+ }
489
+ }
490
+ function applyAnnotations(schema, annotations) {
491
+ for (const annotation of annotations) {
492
+ switch (annotation.annotationKind) {
493
+ case "displayName":
494
+ schema.title = annotation.value;
495
+ break;
496
+ case "description":
497
+ schema.description = annotation.value;
498
+ break;
499
+ case "defaultValue":
500
+ schema.default = annotation.value;
501
+ break;
502
+ case "deprecated":
503
+ schema.deprecated = true;
504
+ break;
505
+ case "placeholder":
506
+ break;
507
+ case "formatHint":
508
+ break;
509
+ case "custom":
510
+ break;
511
+ default: {
512
+ const _exhaustive = annotation;
513
+ void _exhaustive;
514
+ }
515
+ }
516
+ }
517
+ }
518
+
519
+ // src/json-schema/generator.ts
520
+ function generateJsonSchema(form) {
521
+ const ir = canonicalizeChainDSL(form);
522
+ return generateJsonSchemaFromIR(ir);
195
523
  }
196
524
 
197
525
  // src/ui-schema/schema.ts
198
- import { z as z3 } from "zod";
199
- var jsonPointerSchema = z3.string();
200
- var ruleEffectSchema = z3.enum(["SHOW", "HIDE", "ENABLE", "DISABLE"]);
201
- var uiSchemaElementTypeSchema = z3.enum([
526
+ import { z } from "zod";
527
+ var jsonPointerSchema = z.string();
528
+ var ruleEffectSchema = z.enum(["SHOW", "HIDE", "ENABLE", "DISABLE"]);
529
+ var uiSchemaElementTypeSchema = z.enum([
202
530
  "Control",
203
531
  "VerticalLayout",
204
532
  "HorizontalLayout",
@@ -207,32 +535,32 @@ var uiSchemaElementTypeSchema = z3.enum([
207
535
  "Category",
208
536
  "Label"
209
537
  ]);
210
- var ruleConditionSchema = z3.lazy(
211
- () => z3.object({
212
- const: z3.unknown().optional(),
213
- enum: z3.array(z3.unknown()).readonly().optional(),
214
- type: z3.string().optional(),
538
+ var ruleConditionSchema = z.lazy(
539
+ () => z.object({
540
+ const: z.unknown().optional(),
541
+ enum: z.array(z.unknown()).readonly().optional(),
542
+ type: z.string().optional(),
215
543
  not: ruleConditionSchema.optional(),
216
- minimum: z3.number().optional(),
217
- maximum: z3.number().optional(),
218
- exclusiveMinimum: z3.number().optional(),
219
- exclusiveMaximum: z3.number().optional(),
220
- minLength: z3.number().optional(),
221
- properties: z3.record(z3.string(), ruleConditionSchema).optional(),
222
- required: z3.array(z3.string()).optional(),
223
- allOf: z3.array(ruleConditionSchema).optional()
544
+ minimum: z.number().optional(),
545
+ maximum: z.number().optional(),
546
+ exclusiveMinimum: z.number().optional(),
547
+ exclusiveMaximum: z.number().optional(),
548
+ minLength: z.number().optional(),
549
+ properties: z.record(z.string(), ruleConditionSchema).optional(),
550
+ required: z.array(z.string()).optional(),
551
+ allOf: z.array(ruleConditionSchema).optional()
224
552
  }).strict()
225
553
  );
226
- var schemaBasedConditionSchema = z3.object({
554
+ var schemaBasedConditionSchema = z.object({
227
555
  scope: jsonPointerSchema,
228
556
  schema: ruleConditionSchema
229
557
  }).strict();
230
- var ruleSchema = z3.object({
558
+ var ruleSchema = z.object({
231
559
  effect: ruleEffectSchema,
232
560
  condition: schemaBasedConditionSchema
233
561
  }).strict();
234
- var uiSchemaElementSchema = z3.lazy(
235
- () => z3.union([
562
+ var uiSchemaElementSchema = z.lazy(
563
+ () => z.union([
236
564
  controlSchema,
237
565
  verticalLayoutSchema,
238
566
  horizontalLayoutSchema,
@@ -242,73 +570,73 @@ var uiSchemaElementSchema = z3.lazy(
242
570
  labelElementSchema
243
571
  ])
244
572
  );
245
- var controlSchema = z3.object({
246
- type: z3.literal("Control"),
573
+ var controlSchema = z.object({
574
+ type: z.literal("Control"),
247
575
  scope: jsonPointerSchema,
248
- label: z3.union([z3.string(), z3.literal(false)]).optional(),
576
+ label: z.union([z.string(), z.literal(false)]).optional(),
249
577
  rule: ruleSchema.optional(),
250
- options: z3.record(z3.string(), z3.unknown()).optional()
578
+ options: z.record(z.string(), z.unknown()).optional()
251
579
  }).passthrough();
252
- var verticalLayoutSchema = z3.lazy(
253
- () => z3.object({
254
- type: z3.literal("VerticalLayout"),
255
- elements: z3.array(uiSchemaElementSchema),
580
+ var verticalLayoutSchema = z.lazy(
581
+ () => z.object({
582
+ type: z.literal("VerticalLayout"),
583
+ elements: z.array(uiSchemaElementSchema),
256
584
  rule: ruleSchema.optional(),
257
- options: z3.record(z3.string(), z3.unknown()).optional()
585
+ options: z.record(z.string(), z.unknown()).optional()
258
586
  }).passthrough()
259
587
  );
260
- var horizontalLayoutSchema = z3.lazy(
261
- () => z3.object({
262
- type: z3.literal("HorizontalLayout"),
263
- elements: z3.array(uiSchemaElementSchema),
588
+ var horizontalLayoutSchema = z.lazy(
589
+ () => z.object({
590
+ type: z.literal("HorizontalLayout"),
591
+ elements: z.array(uiSchemaElementSchema),
264
592
  rule: ruleSchema.optional(),
265
- options: z3.record(z3.string(), z3.unknown()).optional()
593
+ options: z.record(z.string(), z.unknown()).optional()
266
594
  }).passthrough()
267
595
  );
268
- var groupLayoutSchema = z3.lazy(
269
- () => z3.object({
270
- type: z3.literal("Group"),
271
- label: z3.string(),
272
- elements: z3.array(uiSchemaElementSchema),
596
+ var groupLayoutSchema = z.lazy(
597
+ () => z.object({
598
+ type: z.literal("Group"),
599
+ label: z.string(),
600
+ elements: z.array(uiSchemaElementSchema),
273
601
  rule: ruleSchema.optional(),
274
- options: z3.record(z3.string(), z3.unknown()).optional()
602
+ options: z.record(z.string(), z.unknown()).optional()
275
603
  }).passthrough()
276
604
  );
277
- var categorySchema = z3.lazy(
278
- () => z3.object({
279
- type: z3.literal("Category"),
280
- label: z3.string(),
281
- elements: z3.array(uiSchemaElementSchema),
605
+ var categorySchema = z.lazy(
606
+ () => z.object({
607
+ type: z.literal("Category"),
608
+ label: z.string(),
609
+ elements: z.array(uiSchemaElementSchema),
282
610
  rule: ruleSchema.optional(),
283
- options: z3.record(z3.string(), z3.unknown()).optional()
611
+ options: z.record(z.string(), z.unknown()).optional()
284
612
  }).passthrough()
285
613
  );
286
- var categorizationSchema = z3.lazy(
287
- () => z3.object({
288
- type: z3.literal("Categorization"),
289
- elements: z3.array(categorySchema),
290
- label: z3.string().optional(),
614
+ var categorizationSchema = z.lazy(
615
+ () => z.object({
616
+ type: z.literal("Categorization"),
617
+ elements: z.array(categorySchema),
618
+ label: z.string().optional(),
291
619
  rule: ruleSchema.optional(),
292
- options: z3.record(z3.string(), z3.unknown()).optional()
620
+ options: z.record(z.string(), z.unknown()).optional()
293
621
  }).passthrough()
294
622
  );
295
- var labelElementSchema = z3.object({
296
- type: z3.literal("Label"),
297
- text: z3.string(),
623
+ var labelElementSchema = z.object({
624
+ type: z.literal("Label"),
625
+ text: z.string(),
298
626
  rule: ruleSchema.optional(),
299
- options: z3.record(z3.string(), z3.unknown()).optional()
627
+ options: z.record(z.string(), z.unknown()).optional()
300
628
  }).passthrough();
301
- var uiSchema = z3.lazy(
302
- () => z3.union([verticalLayoutSchema, horizontalLayoutSchema, groupLayoutSchema, categorizationSchema])
629
+ var uiSchema = z.lazy(
630
+ () => z.union([verticalLayoutSchema, horizontalLayoutSchema, groupLayoutSchema, categorizationSchema])
303
631
  );
304
632
 
305
- // src/ui-schema/generator.ts
306
- import { z as z4 } from "zod";
307
- function parseOrThrow2(schema, value, label) {
633
+ // src/ui-schema/ir-generator.ts
634
+ import { z as z2 } from "zod";
635
+ function parseOrThrow(schema, value, label) {
308
636
  try {
309
637
  return schema.parse(value);
310
638
  } catch (error) {
311
- if (error instanceof z4.ZodError) {
639
+ if (error instanceof z2.ZodError) {
312
640
  throw new Error(
313
641
  `Generated ${label} failed validation:
314
642
  ${error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n")}`
@@ -353,110 +681,64 @@ function combineRules(parentRule, childRule) {
353
681
  }
354
682
  };
355
683
  }
356
- function elementsToUiSchema(elements, parentRule) {
684
+ function fieldNodeToControl(field, parentRule) {
685
+ const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
686
+ const control = {
687
+ type: "Control",
688
+ scope: fieldToScope(field.name),
689
+ ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
690
+ ...parentRule !== void 0 && { rule: parentRule }
691
+ };
692
+ return control;
693
+ }
694
+ function groupNodeToLayout(group, parentRule) {
695
+ return {
696
+ type: "Group",
697
+ label: group.label,
698
+ elements: irElementsToUiSchema(group.elements, parentRule),
699
+ ...parentRule !== void 0 && { rule: parentRule }
700
+ };
701
+ }
702
+ function irElementsToUiSchema(elements, parentRule) {
357
703
  const result = [];
358
704
  for (const element of elements) {
359
- switch (element._type) {
705
+ switch (element.kind) {
360
706
  case "field": {
361
- const control = {
362
- type: "Control",
363
- scope: fieldToScope(element.name),
364
- ...element.label !== void 0 && { label: element.label },
365
- ...parentRule !== void 0 && { rule: parentRule }
366
- };
367
- result.push(control);
707
+ result.push(fieldNodeToControl(element, parentRule));
368
708
  break;
369
709
  }
370
710
  case "group": {
371
- const groupElement = element;
372
- const group = {
373
- type: "Group",
374
- label: groupElement.label,
375
- elements: elementsToUiSchema(groupElement.elements, parentRule),
376
- ...parentRule !== void 0 && { rule: parentRule }
377
- };
378
- result.push(group);
711
+ result.push(groupNodeToLayout(element, parentRule));
379
712
  break;
380
713
  }
381
714
  case "conditional": {
382
- const conditionalElement = element;
383
- const newRule = createShowRule(conditionalElement.field, conditionalElement.value);
715
+ const newRule = createShowRule(element.fieldName, element.value);
384
716
  const combinedRule = parentRule !== void 0 ? combineRules(parentRule, newRule) : newRule;
385
- const childElements = elementsToUiSchema(conditionalElement.elements, combinedRule);
717
+ const childElements = irElementsToUiSchema(element.elements, combinedRule);
386
718
  result.push(...childElements);
387
719
  break;
388
720
  }
721
+ default: {
722
+ const _exhaustive = element;
723
+ void _exhaustive;
724
+ throw new Error("Unhandled IR element kind");
725
+ }
389
726
  }
390
727
  }
391
728
  return result;
392
729
  }
393
- function formSpecFieldToElement(field, scopePrefix = "#/properties") {
394
- const control = {
395
- type: "Control",
396
- scope: `${scopePrefix}/${field.id}`
397
- };
398
- if (field.label !== void 0) {
399
- control.label = field.label;
400
- }
401
- if (field.showWhen !== void 0 && typeof field.showWhen === "object" && "field" in field.showWhen && "value" in field.showWhen) {
402
- const sw = field.showWhen;
403
- control.rule = {
404
- effect: "SHOW",
405
- condition: {
406
- scope: `#/properties/${sw.field}`,
407
- schema: { const: sw.value }
408
- }
409
- };
410
- }
411
- return control;
412
- }
413
- function generateUiSchemaFromFields(fields) {
414
- const groupMap = /* @__PURE__ */ new Map();
415
- const orderedKeys = [];
416
- const ungrouped = [];
417
- for (const field of fields) {
418
- const element = formSpecFieldToElement(field);
419
- if (field.group !== void 0) {
420
- if (!groupMap.has(field.group)) {
421
- groupMap.set(field.group, []);
422
- orderedKeys.push(field.group);
423
- }
424
- groupMap.get(field.group).push(element);
425
- } else {
426
- orderedKeys.push(null);
427
- ungrouped.push(element);
428
- }
429
- }
430
- const elements = [];
431
- let ungroupedIndex = 0;
432
- for (const key of orderedKeys) {
433
- if (key === null) {
434
- const el = ungrouped[ungroupedIndex++];
435
- if (el !== void 0) {
436
- elements.push(el);
437
- }
438
- } else {
439
- const groupElements = groupMap.get(key) ?? [];
440
- const groupLayout = {
441
- type: "Group",
442
- label: key,
443
- elements: groupElements
444
- };
445
- elements.push(groupLayout);
446
- }
447
- }
730
+ function generateUiSchemaFromIR(ir) {
448
731
  const result = {
449
732
  type: "VerticalLayout",
450
- elements
733
+ elements: irElementsToUiSchema(ir.elements)
451
734
  };
452
- return parseOrThrow2(uiSchema, result, "UI Schema");
735
+ return parseOrThrow(uiSchema, result, "UI Schema");
453
736
  }
737
+
738
+ // src/ui-schema/generator.ts
454
739
  function generateUiSchema(form) {
455
- const result = {
456
- type: "VerticalLayout",
457
- elements: elementsToUiSchema(form.elements)
458
- };
459
- return parseOrThrow2(uiSchema, result, "UI Schema");
740
+ const ir = canonicalizeChainDSL(form);
741
+ return generateUiSchemaFromIR(ir);
460
742
  }
461
743
 
462
744
  // src/json-schema/types.ts
@@ -467,6 +749,373 @@ function getSchemaExtension(schema, key) {
467
749
  return schema[key];
468
750
  }
469
751
 
752
+ // src/json-schema/schema.ts
753
+ import { z as z3 } from "zod";
754
+ var jsonSchemaTypeSchema = z3.enum([
755
+ "string",
756
+ "number",
757
+ "integer",
758
+ "boolean",
759
+ "object",
760
+ "array",
761
+ "null"
762
+ ]);
763
+ var jsonSchema7Schema = z3.lazy(
764
+ () => z3.object({
765
+ $schema: z3.string().optional(),
766
+ $id: z3.string().optional(),
767
+ $ref: z3.string().optional(),
768
+ // Metadata
769
+ title: z3.string().optional(),
770
+ description: z3.string().optional(),
771
+ deprecated: z3.boolean().optional(),
772
+ // Type
773
+ type: z3.union([jsonSchemaTypeSchema, z3.array(jsonSchemaTypeSchema)]).optional(),
774
+ // String validation
775
+ minLength: z3.number().optional(),
776
+ maxLength: z3.number().optional(),
777
+ pattern: z3.string().optional(),
778
+ // Number validation
779
+ minimum: z3.number().optional(),
780
+ maximum: z3.number().optional(),
781
+ exclusiveMinimum: z3.number().optional(),
782
+ exclusiveMaximum: z3.number().optional(),
783
+ // Enum
784
+ enum: z3.array(z3.union([z3.string(), z3.number(), z3.boolean(), z3.null()])).readonly().optional(),
785
+ const: z3.union([z3.string(), z3.number(), z3.boolean(), z3.null()]).optional(),
786
+ // Object
787
+ properties: z3.record(z3.string(), jsonSchema7Schema).optional(),
788
+ required: z3.array(z3.string()).optional(),
789
+ additionalProperties: z3.union([z3.boolean(), jsonSchema7Schema]).optional(),
790
+ // Array
791
+ items: z3.union([jsonSchema7Schema, z3.array(jsonSchema7Schema)]).optional(),
792
+ minItems: z3.number().optional(),
793
+ maxItems: z3.number().optional(),
794
+ // Composition
795
+ allOf: z3.array(jsonSchema7Schema).optional(),
796
+ anyOf: z3.array(jsonSchema7Schema).optional(),
797
+ oneOf: z3.array(jsonSchema7Schema).optional(),
798
+ not: jsonSchema7Schema.optional(),
799
+ // Conditional
800
+ if: jsonSchema7Schema.optional(),
801
+ then: jsonSchema7Schema.optional(),
802
+ else: jsonSchema7Schema.optional(),
803
+ // Format
804
+ format: z3.string().optional(),
805
+ // Default
806
+ default: z3.unknown().optional(),
807
+ // FormSpec extensions
808
+ "x-formspec-source": z3.string().optional(),
809
+ "x-formspec-params": z3.array(z3.string()).readonly().optional(),
810
+ "x-formspec-schemaSource": z3.string().optional()
811
+ }).passthrough()
812
+ );
813
+
814
+ // src/validate/constraint-validator.ts
815
+ function makeCode(ctx, category, number) {
816
+ return `${ctx.vendorPrefix}-${category}-${String(number).padStart(3, "0")}`;
817
+ }
818
+ function addContradiction(ctx, message, primary, related) {
819
+ ctx.diagnostics.push({
820
+ code: makeCode(ctx, "CONTRADICTION", 1),
821
+ message,
822
+ severity: "error",
823
+ primaryLocation: primary,
824
+ relatedLocations: [related]
825
+ });
826
+ }
827
+ function addTypeMismatch(ctx, message, primary) {
828
+ ctx.diagnostics.push({
829
+ code: makeCode(ctx, "TYPE_MISMATCH", 1),
830
+ message,
831
+ severity: "error",
832
+ primaryLocation: primary,
833
+ relatedLocations: []
834
+ });
835
+ }
836
+ function addUnknownExtension(ctx, message, primary) {
837
+ ctx.diagnostics.push({
838
+ code: makeCode(ctx, "UNKNOWN_EXTENSION", 1),
839
+ message,
840
+ severity: "warning",
841
+ primaryLocation: primary,
842
+ relatedLocations: []
843
+ });
844
+ }
845
+ function findNumeric(constraints, constraintKind) {
846
+ return constraints.find(
847
+ (c) => c.constraintKind === constraintKind
848
+ );
849
+ }
850
+ function findLength(constraints, constraintKind) {
851
+ return constraints.find(
852
+ (c) => c.constraintKind === constraintKind
853
+ );
854
+ }
855
+ function findAllowedMembers(constraints) {
856
+ return constraints.filter(
857
+ (c) => c.constraintKind === "allowedMembers"
858
+ );
859
+ }
860
+ function checkNumericContradictions(ctx, fieldName, constraints) {
861
+ const min = findNumeric(constraints, "minimum");
862
+ const max = findNumeric(constraints, "maximum");
863
+ const exMin = findNumeric(constraints, "exclusiveMinimum");
864
+ const exMax = findNumeric(constraints, "exclusiveMaximum");
865
+ if (min !== void 0 && max !== void 0 && min.value > max.value) {
866
+ addContradiction(
867
+ ctx,
868
+ `Field "${fieldName}": minimum (${String(min.value)}) is greater than maximum (${String(max.value)})`,
869
+ min.provenance,
870
+ max.provenance
871
+ );
872
+ }
873
+ if (exMin !== void 0 && max !== void 0 && exMin.value >= max.value) {
874
+ addContradiction(
875
+ ctx,
876
+ `Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to maximum (${String(max.value)})`,
877
+ exMin.provenance,
878
+ max.provenance
879
+ );
880
+ }
881
+ if (min !== void 0 && exMax !== void 0 && min.value >= exMax.value) {
882
+ addContradiction(
883
+ ctx,
884
+ `Field "${fieldName}": minimum (${String(min.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
885
+ min.provenance,
886
+ exMax.provenance
887
+ );
888
+ }
889
+ if (exMin !== void 0 && exMax !== void 0 && exMin.value >= exMax.value) {
890
+ addContradiction(
891
+ ctx,
892
+ `Field "${fieldName}": exclusiveMinimum (${String(exMin.value)}) is greater than or equal to exclusiveMaximum (${String(exMax.value)})`,
893
+ exMin.provenance,
894
+ exMax.provenance
895
+ );
896
+ }
897
+ }
898
+ function checkLengthContradictions(ctx, fieldName, constraints) {
899
+ const minLen = findLength(constraints, "minLength");
900
+ const maxLen = findLength(constraints, "maxLength");
901
+ if (minLen !== void 0 && maxLen !== void 0 && minLen.value > maxLen.value) {
902
+ addContradiction(
903
+ ctx,
904
+ `Field "${fieldName}": minLength (${String(minLen.value)}) is greater than maxLength (${String(maxLen.value)})`,
905
+ minLen.provenance,
906
+ maxLen.provenance
907
+ );
908
+ }
909
+ const minItems = findLength(constraints, "minItems");
910
+ const maxItems = findLength(constraints, "maxItems");
911
+ if (minItems !== void 0 && maxItems !== void 0 && minItems.value > maxItems.value) {
912
+ addContradiction(
913
+ ctx,
914
+ `Field "${fieldName}": minItems (${String(minItems.value)}) is greater than maxItems (${String(maxItems.value)})`,
915
+ minItems.provenance,
916
+ maxItems.provenance
917
+ );
918
+ }
919
+ }
920
+ function checkAllowedMembersContradiction(ctx, fieldName, constraints) {
921
+ const members = findAllowedMembers(constraints);
922
+ if (members.length < 2) return;
923
+ const firstSet = new Set(members[0]?.members ?? []);
924
+ for (let i = 1; i < members.length; i++) {
925
+ const current = members[i];
926
+ if (current === void 0) continue;
927
+ for (const m of firstSet) {
928
+ if (!current.members.includes(m)) {
929
+ firstSet.delete(m);
930
+ }
931
+ }
932
+ }
933
+ if (firstSet.size === 0) {
934
+ const first = members[0];
935
+ const second = members[1];
936
+ if (first !== void 0 && second !== void 0) {
937
+ addContradiction(
938
+ ctx,
939
+ `Field "${fieldName}": allowedMembers constraints have an empty intersection (no valid values remain)`,
940
+ first.provenance,
941
+ second.provenance
942
+ );
943
+ }
944
+ }
945
+ }
946
+ function typeLabel(type) {
947
+ switch (type.kind) {
948
+ case "primitive":
949
+ return type.primitiveKind;
950
+ case "enum":
951
+ return "enum";
952
+ case "array":
953
+ return "array";
954
+ case "object":
955
+ return "object";
956
+ case "union":
957
+ return "union";
958
+ case "reference":
959
+ return `reference(${type.name})`;
960
+ case "dynamic":
961
+ return `dynamic(${type.dynamicKind})`;
962
+ case "custom":
963
+ return `custom(${type.typeId})`;
964
+ default: {
965
+ const _exhaustive = type;
966
+ return String(_exhaustive);
967
+ }
968
+ }
969
+ }
970
+ function checkTypeApplicability(ctx, fieldName, type, constraints) {
971
+ const isNumber = type.kind === "primitive" && type.primitiveKind === "number";
972
+ const isString = type.kind === "primitive" && type.primitiveKind === "string";
973
+ const isArray = type.kind === "array";
974
+ const isEnum = type.kind === "enum";
975
+ const label = typeLabel(type);
976
+ for (const constraint of constraints) {
977
+ const ck = constraint.constraintKind;
978
+ switch (ck) {
979
+ case "minimum":
980
+ case "maximum":
981
+ case "exclusiveMinimum":
982
+ case "exclusiveMaximum":
983
+ case "multipleOf": {
984
+ if (!isNumber) {
985
+ addTypeMismatch(
986
+ ctx,
987
+ `Field "${fieldName}": constraint "${ck}" is only valid on number fields, but field type is "${label}"`,
988
+ constraint.provenance
989
+ );
990
+ }
991
+ break;
992
+ }
993
+ case "minLength":
994
+ case "maxLength":
995
+ case "pattern": {
996
+ if (!isString) {
997
+ addTypeMismatch(
998
+ ctx,
999
+ `Field "${fieldName}": constraint "${ck}" is only valid on string fields, but field type is "${label}"`,
1000
+ constraint.provenance
1001
+ );
1002
+ }
1003
+ break;
1004
+ }
1005
+ case "minItems":
1006
+ case "maxItems":
1007
+ case "uniqueItems": {
1008
+ if (!isArray) {
1009
+ addTypeMismatch(
1010
+ ctx,
1011
+ `Field "${fieldName}": constraint "${ck}" is only valid on array fields, but field type is "${label}"`,
1012
+ constraint.provenance
1013
+ );
1014
+ }
1015
+ break;
1016
+ }
1017
+ case "allowedMembers": {
1018
+ if (!isEnum) {
1019
+ addTypeMismatch(
1020
+ ctx,
1021
+ `Field "${fieldName}": constraint "allowedMembers" is only valid on enum fields, but field type is "${label}"`,
1022
+ constraint.provenance
1023
+ );
1024
+ }
1025
+ break;
1026
+ }
1027
+ case "custom": {
1028
+ checkCustomConstraint(ctx, fieldName, type, constraint);
1029
+ break;
1030
+ }
1031
+ default: {
1032
+ const _exhaustive = constraint;
1033
+ throw new Error(
1034
+ `Unhandled constraint kind: ${_exhaustive.constraintKind}`
1035
+ );
1036
+ }
1037
+ }
1038
+ }
1039
+ }
1040
+ function checkCustomConstraint(ctx, fieldName, type, constraint) {
1041
+ if (ctx.extensionRegistry === void 0) return;
1042
+ const registration = ctx.extensionRegistry.findConstraint(constraint.constraintId);
1043
+ if (registration === void 0) {
1044
+ addUnknownExtension(
1045
+ ctx,
1046
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not registered in the extension registry`,
1047
+ constraint.provenance
1048
+ );
1049
+ return;
1050
+ }
1051
+ if (registration.applicableTypes === null) return;
1052
+ if (!registration.applicableTypes.includes(type.kind)) {
1053
+ addTypeMismatch(
1054
+ ctx,
1055
+ `Field "${fieldName}": custom constraint "${constraint.constraintId}" is not applicable to type "${typeLabel(type)}"`,
1056
+ constraint.provenance
1057
+ );
1058
+ }
1059
+ }
1060
+ function validateFieldNode(ctx, field) {
1061
+ validateConstraints(ctx, field.name, field.type, field.constraints);
1062
+ if (field.type.kind === "object") {
1063
+ for (const prop of field.type.properties) {
1064
+ validateObjectProperty(ctx, field.name, prop);
1065
+ }
1066
+ }
1067
+ }
1068
+ function validateObjectProperty(ctx, parentName, prop) {
1069
+ const qualifiedName = `${parentName}.${prop.name}`;
1070
+ validateConstraints(ctx, qualifiedName, prop.type, prop.constraints);
1071
+ if (prop.type.kind === "object") {
1072
+ for (const nestedProp of prop.type.properties) {
1073
+ validateObjectProperty(ctx, qualifiedName, nestedProp);
1074
+ }
1075
+ }
1076
+ }
1077
+ function validateConstraints(ctx, name, type, constraints) {
1078
+ checkNumericContradictions(ctx, name, constraints);
1079
+ checkLengthContradictions(ctx, name, constraints);
1080
+ checkAllowedMembersContradiction(ctx, name, constraints);
1081
+ checkTypeApplicability(ctx, name, type, constraints);
1082
+ }
1083
+ function validateElement(ctx, element) {
1084
+ switch (element.kind) {
1085
+ case "field":
1086
+ validateFieldNode(ctx, element);
1087
+ break;
1088
+ case "group":
1089
+ for (const child of element.elements) {
1090
+ validateElement(ctx, child);
1091
+ }
1092
+ break;
1093
+ case "conditional":
1094
+ for (const child of element.elements) {
1095
+ validateElement(ctx, child);
1096
+ }
1097
+ break;
1098
+ default: {
1099
+ const _exhaustive = element;
1100
+ throw new Error(`Unhandled element kind: ${_exhaustive.kind}`);
1101
+ }
1102
+ }
1103
+ }
1104
+ function validateIR(ir, options) {
1105
+ const ctx = {
1106
+ diagnostics: [],
1107
+ vendorPrefix: options?.vendorPrefix ?? "FORMSPEC",
1108
+ extensionRegistry: options?.extensionRegistry
1109
+ };
1110
+ for (const element of ir.elements) {
1111
+ validateElement(ctx, element);
1112
+ }
1113
+ return {
1114
+ diagnostics: ctx.diagnostics,
1115
+ valid: ctx.diagnostics.every((d) => d.severity !== "error")
1116
+ };
1117
+ }
1118
+
470
1119
  // src/browser.ts
471
1120
  function buildFormSchemas(form) {
472
1121
  return {
@@ -476,12 +1125,12 @@ function buildFormSchemas(form) {
476
1125
  }
477
1126
  export {
478
1127
  buildFormSchemas,
1128
+ canonicalizeChainDSL,
479
1129
  categorizationSchema,
480
1130
  categorySchema,
481
1131
  controlSchema,
482
1132
  generateJsonSchema,
483
1133
  generateUiSchema,
484
- generateUiSchemaFromFields,
485
1134
  getSchemaExtension,
486
1135
  groupLayoutSchema,
487
1136
  horizontalLayoutSchema,
@@ -496,6 +1145,7 @@ export {
496
1145
  uiSchemaElementSchema,
497
1146
  uiSchemaElementTypeSchema,
498
1147
  uiSchema as uiSchemaSchema,
1148
+ validateIR,
499
1149
  verticalLayoutSchema
500
1150
  };
501
1151
  //# sourceMappingURL=browser.js.map