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