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

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