@formspec/decorators 0.1.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +179 -0
- package/dist/index.cjs +380 -0
- package/dist/index.d.ts +464 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +694 -0
- package/dist/index.js.map +1 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# @formspec/decorators
|
|
2
|
+
|
|
3
|
+
Decorator stubs for FormSpec CLI static analysis.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @formspec/decorators
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @formspec/decorators
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { Label, Min, Max, EnumOptions, ShowWhen, Group } from '@formspec/decorators';
|
|
17
|
+
|
|
18
|
+
class UserRegistration {
|
|
19
|
+
@Group("Personal Info")
|
|
20
|
+
@Label("Full Name")
|
|
21
|
+
name!: string;
|
|
22
|
+
|
|
23
|
+
@Group("Personal Info")
|
|
24
|
+
@Label("Age")
|
|
25
|
+
@Min(18)
|
|
26
|
+
@Max(120)
|
|
27
|
+
age?: number;
|
|
28
|
+
|
|
29
|
+
@Group("Preferences")
|
|
30
|
+
@Label("Country")
|
|
31
|
+
@EnumOptions([
|
|
32
|
+
{ id: "us", label: "United States" },
|
|
33
|
+
{ id: "ca", label: "Canada" },
|
|
34
|
+
{ id: "uk", label: "United Kingdom" }
|
|
35
|
+
])
|
|
36
|
+
country!: "us" | "ca" | "uk";
|
|
37
|
+
|
|
38
|
+
@Group("Preferences")
|
|
39
|
+
@Label("Contact Method")
|
|
40
|
+
contactMethod!: "email" | "phone";
|
|
41
|
+
|
|
42
|
+
@ShowWhen({ field: "contactMethod", value: "email" })
|
|
43
|
+
@Label("Email Address")
|
|
44
|
+
email?: string;
|
|
45
|
+
|
|
46
|
+
@ShowWhen({ field: "contactMethod", value: "phone" })
|
|
47
|
+
@Label("Phone Number")
|
|
48
|
+
phone?: string;
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Generating Schemas
|
|
53
|
+
|
|
54
|
+
### Build-Time Only
|
|
55
|
+
|
|
56
|
+
Generate JSON Schema and UI Schema files at build time:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
formspec generate ./src/user-registration.ts UserRegistration -o ./generated
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
This outputs static JSON files to `./generated/`. No codegen step required.
|
|
63
|
+
|
|
64
|
+
### Runtime Schema Generation
|
|
65
|
+
|
|
66
|
+
If you need JSON Schema or UI Schema **at runtime in your program** (e.g., dynamic form rendering, server-side generation), you have two options:
|
|
67
|
+
|
|
68
|
+
1. **Chain DSL** - Works at runtime without any codegen step. See the [Chain DSL documentation](../dsl/README.md).
|
|
69
|
+
|
|
70
|
+
2. **Decorator DSL with codegen** - If you prefer to keep using decorated classes, run codegen to preserve type information:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Generate type metadata file
|
|
74
|
+
formspec codegen ./src/forms.ts -o ./src/__formspec_types__.ts
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
// Import the generated file at your application entry point
|
|
79
|
+
import './__formspec_types__.js';
|
|
80
|
+
|
|
81
|
+
// Now buildFormSchemas() has access to full type information
|
|
82
|
+
import { buildFormSchemas } from '@formspec/decorators';
|
|
83
|
+
import { UserRegistration } from './forms.js';
|
|
84
|
+
|
|
85
|
+
const { jsonSchema, uiSchema } = buildFormSchemas(UserRegistration);
|
|
86
|
+
// jsonSchema: { $schema: "...", type: "object", properties: {...}, required: [...] }
|
|
87
|
+
// uiSchema: { type: "VerticalLayout", elements: [...] }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Add `formspec codegen` to your build process to keep type metadata in sync.
|
|
91
|
+
|
|
92
|
+
> **Note:** If you need to work with **dynamically fetched schema data** (schemas not known at build time), use the Chain DSL. It's the only option for dynamic schemas.
|
|
93
|
+
|
|
94
|
+
### API Consistency
|
|
95
|
+
|
|
96
|
+
The `buildFormSchemas()` function provides the same return type as `@formspec/build`:
|
|
97
|
+
|
|
98
|
+
| DSL | Function | Returns |
|
|
99
|
+
|-----|----------|---------|
|
|
100
|
+
| Chain DSL | `buildFormSchemas(form)` | `{ jsonSchema, uiSchema }` |
|
|
101
|
+
| Decorator DSL | `buildFormSchemas(Class)` | `{ jsonSchema, uiSchema }` |
|
|
102
|
+
|
|
103
|
+
This allows you to switch between DSLs without changing how you consume the schemas.
|
|
104
|
+
|
|
105
|
+
## How It Works
|
|
106
|
+
|
|
107
|
+
These decorators are **no-ops at runtime** - they have zero overhead in your production code.
|
|
108
|
+
|
|
109
|
+
The FormSpec CLI uses TypeScript's compiler API to statically analyze your source files. It reads decorator names and arguments directly from the AST, without ever executing your code.
|
|
110
|
+
|
|
111
|
+
This means:
|
|
112
|
+
- No reflection metadata required
|
|
113
|
+
- No runtime dependencies
|
|
114
|
+
- Works with any TypeScript configuration
|
|
115
|
+
- Tree-shaking friendly
|
|
116
|
+
|
|
117
|
+
## Available Decorators
|
|
118
|
+
|
|
119
|
+
### Field Metadata
|
|
120
|
+
|
|
121
|
+
| Decorator | Purpose | Example |
|
|
122
|
+
|-----------|---------|---------|
|
|
123
|
+
| `@Label(text)` | Display label | `@Label("Full Name")` |
|
|
124
|
+
| `@Placeholder(text)` | Input placeholder | `@Placeholder("Enter name...")` |
|
|
125
|
+
| `@Description(text)` | Help text | `@Description("Your legal name")` |
|
|
126
|
+
|
|
127
|
+
### Numeric Constraints
|
|
128
|
+
|
|
129
|
+
| Decorator | Purpose | Example |
|
|
130
|
+
|-----------|---------|---------|
|
|
131
|
+
| `@Min(n)` | Minimum value | `@Min(0)` |
|
|
132
|
+
| `@Max(n)` | Maximum value | `@Max(100)` |
|
|
133
|
+
| `@Step(n)` | Step increment | `@Step(0.01)` |
|
|
134
|
+
|
|
135
|
+
### String Constraints
|
|
136
|
+
|
|
137
|
+
| Decorator | Purpose | Example |
|
|
138
|
+
|-----------|---------|---------|
|
|
139
|
+
| `@MinLength(n)` | Minimum length | `@MinLength(1)` |
|
|
140
|
+
| `@MaxLength(n)` | Maximum length | `@MaxLength(255)` |
|
|
141
|
+
| `@Pattern(regex)` | Regex pattern | `@Pattern("^[a-z]+$")` |
|
|
142
|
+
|
|
143
|
+
### Array Constraints
|
|
144
|
+
|
|
145
|
+
| Decorator | Purpose | Example |
|
|
146
|
+
|-----------|---------|---------|
|
|
147
|
+
| `@MinItems(n)` | Minimum items | `@MinItems(1)` |
|
|
148
|
+
| `@MaxItems(n)` | Maximum items | `@MaxItems(10)` |
|
|
149
|
+
|
|
150
|
+
### Enum Options
|
|
151
|
+
|
|
152
|
+
| Decorator | Purpose | Example |
|
|
153
|
+
|-----------|---------|---------|
|
|
154
|
+
| `@EnumOptions(opts)` | Custom labels | `@EnumOptions([{id: "us", label: "USA"}])` |
|
|
155
|
+
|
|
156
|
+
### Layout & Conditional
|
|
157
|
+
|
|
158
|
+
| Decorator | Purpose | Example |
|
|
159
|
+
|-----------|---------|---------|
|
|
160
|
+
| `@Group(name)` | Group fields | `@Group("Contact Info")` |
|
|
161
|
+
| `@ShowWhen(cond)` | Conditional visibility | `@ShowWhen({ field: "type", value: "other" })` |
|
|
162
|
+
|
|
163
|
+
## TypeScript Configuration
|
|
164
|
+
|
|
165
|
+
For decorator support, ensure your `tsconfig.json` includes:
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"compilerOptions": {
|
|
170
|
+
"experimentalDecorators": true
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Note: The `emitDecoratorMetadata` flag is not required since these decorators don't use reflection.
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
UNLICENSED
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// dist/index.js
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Description: () => Description,
|
|
24
|
+
EnumOptions: () => EnumOptions,
|
|
25
|
+
Group: () => Group,
|
|
26
|
+
Label: () => Label,
|
|
27
|
+
Max: () => Max,
|
|
28
|
+
MaxItems: () => MaxItems,
|
|
29
|
+
MaxLength: () => MaxLength,
|
|
30
|
+
Min: () => Min,
|
|
31
|
+
MinItems: () => MinItems,
|
|
32
|
+
MinLength: () => MinLength,
|
|
33
|
+
Pattern: () => Pattern,
|
|
34
|
+
Placeholder: () => Placeholder,
|
|
35
|
+
ShowWhen: () => ShowWhen,
|
|
36
|
+
Step: () => Step,
|
|
37
|
+
buildFormSchemas: () => buildFormSchemas,
|
|
38
|
+
getDecoratorMetadata: () => getDecoratorMetadata,
|
|
39
|
+
getTypeMetadata: () => getTypeMetadata,
|
|
40
|
+
toFormSpec: () => toFormSpec
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(index_exports);
|
|
43
|
+
var decoratorMetadata = /* @__PURE__ */ new Map();
|
|
44
|
+
function getFieldMetadata(ctor, propertyKey) {
|
|
45
|
+
if (!decoratorMetadata.has(ctor)) {
|
|
46
|
+
decoratorMetadata.set(ctor, /* @__PURE__ */ new Map());
|
|
47
|
+
}
|
|
48
|
+
const classMetadata = decoratorMetadata.get(ctor);
|
|
49
|
+
if (!classMetadata.has(propertyKey)) {
|
|
50
|
+
classMetadata.set(propertyKey, {});
|
|
51
|
+
}
|
|
52
|
+
return classMetadata.get(propertyKey);
|
|
53
|
+
}
|
|
54
|
+
function Label(text) {
|
|
55
|
+
return function(target, propertyKey) {
|
|
56
|
+
const ctor = target.constructor;
|
|
57
|
+
getFieldMetadata(ctor, propertyKey).label = text;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function Placeholder(text) {
|
|
61
|
+
return function(target, propertyKey) {
|
|
62
|
+
const ctor = target.constructor;
|
|
63
|
+
getFieldMetadata(ctor, propertyKey).placeholder = text;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function Description(text) {
|
|
67
|
+
return function(target, propertyKey) {
|
|
68
|
+
const ctor = target.constructor;
|
|
69
|
+
getFieldMetadata(ctor, propertyKey).description = text;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function Min(value) {
|
|
73
|
+
return function(target, propertyKey) {
|
|
74
|
+
const ctor = target.constructor;
|
|
75
|
+
getFieldMetadata(ctor, propertyKey).min = value;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function Max(value) {
|
|
79
|
+
return function(target, propertyKey) {
|
|
80
|
+
const ctor = target.constructor;
|
|
81
|
+
getFieldMetadata(ctor, propertyKey).max = value;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function Step(value) {
|
|
85
|
+
return function(target, propertyKey) {
|
|
86
|
+
const ctor = target.constructor;
|
|
87
|
+
getFieldMetadata(ctor, propertyKey).step = value;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function MinLength(value) {
|
|
91
|
+
return function(target, propertyKey) {
|
|
92
|
+
const ctor = target.constructor;
|
|
93
|
+
getFieldMetadata(ctor, propertyKey).minLength = value;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function MaxLength(value) {
|
|
97
|
+
return function(target, propertyKey) {
|
|
98
|
+
const ctor = target.constructor;
|
|
99
|
+
getFieldMetadata(ctor, propertyKey).maxLength = value;
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function Pattern(regex) {
|
|
103
|
+
return function(target, propertyKey) {
|
|
104
|
+
const ctor = target.constructor;
|
|
105
|
+
getFieldMetadata(ctor, propertyKey).pattern = regex;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function MinItems(value) {
|
|
109
|
+
return function(target, propertyKey) {
|
|
110
|
+
const ctor = target.constructor;
|
|
111
|
+
getFieldMetadata(ctor, propertyKey).minItems = value;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function MaxItems(value) {
|
|
115
|
+
return function(target, propertyKey) {
|
|
116
|
+
const ctor = target.constructor;
|
|
117
|
+
getFieldMetadata(ctor, propertyKey).maxItems = value;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function EnumOptions(options) {
|
|
121
|
+
return function(target, propertyKey) {
|
|
122
|
+
const ctor = target.constructor;
|
|
123
|
+
getFieldMetadata(ctor, propertyKey).options = options;
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function ShowWhen(condition) {
|
|
127
|
+
return function(target, propertyKey) {
|
|
128
|
+
const ctor = target.constructor;
|
|
129
|
+
getFieldMetadata(ctor, propertyKey).showWhen = condition;
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function Group(name) {
|
|
133
|
+
return function(target, propertyKey) {
|
|
134
|
+
const ctor = target.constructor;
|
|
135
|
+
getFieldMetadata(ctor, propertyKey).group = name;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
var FORMSPEC_TYPES_KEY = "__formspec_types__";
|
|
139
|
+
function mapTypeToFieldType(type) {
|
|
140
|
+
switch (type) {
|
|
141
|
+
case "string":
|
|
142
|
+
return "text";
|
|
143
|
+
case "number":
|
|
144
|
+
return "number";
|
|
145
|
+
case "boolean":
|
|
146
|
+
return "boolean";
|
|
147
|
+
case "enum":
|
|
148
|
+
return "enum";
|
|
149
|
+
case "array":
|
|
150
|
+
return "array";
|
|
151
|
+
case "object":
|
|
152
|
+
return "object";
|
|
153
|
+
default:
|
|
154
|
+
return "text";
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function createField(fieldName, typeInfo, decoratorInfo) {
|
|
158
|
+
const field = {
|
|
159
|
+
_field: mapTypeToFieldType(typeInfo.type),
|
|
160
|
+
id: fieldName
|
|
161
|
+
};
|
|
162
|
+
if (!typeInfo.optional && !typeInfo.nullable) {
|
|
163
|
+
field.required = true;
|
|
164
|
+
}
|
|
165
|
+
if (decoratorInfo.label)
|
|
166
|
+
field.label = decoratorInfo.label;
|
|
167
|
+
if (decoratorInfo.placeholder)
|
|
168
|
+
field.placeholder = decoratorInfo.placeholder;
|
|
169
|
+
if (decoratorInfo.description)
|
|
170
|
+
field.description = decoratorInfo.description;
|
|
171
|
+
if (decoratorInfo.min !== void 0)
|
|
172
|
+
field.min = decoratorInfo.min;
|
|
173
|
+
if (decoratorInfo.max !== void 0)
|
|
174
|
+
field.max = decoratorInfo.max;
|
|
175
|
+
if (decoratorInfo.step !== void 0)
|
|
176
|
+
field.step = decoratorInfo.step;
|
|
177
|
+
if (decoratorInfo.minLength !== void 0)
|
|
178
|
+
field.minLength = decoratorInfo.minLength;
|
|
179
|
+
if (decoratorInfo.maxLength !== void 0)
|
|
180
|
+
field.maxLength = decoratorInfo.maxLength;
|
|
181
|
+
if (decoratorInfo.minItems !== void 0)
|
|
182
|
+
field.minItems = decoratorInfo.minItems;
|
|
183
|
+
if (decoratorInfo.maxItems !== void 0)
|
|
184
|
+
field.maxItems = decoratorInfo.maxItems;
|
|
185
|
+
if (decoratorInfo.pattern)
|
|
186
|
+
field.pattern = decoratorInfo.pattern;
|
|
187
|
+
if (decoratorInfo.showWhen)
|
|
188
|
+
field.showWhen = decoratorInfo.showWhen;
|
|
189
|
+
if (decoratorInfo.group)
|
|
190
|
+
field.group = decoratorInfo.group;
|
|
191
|
+
if (decoratorInfo.options) {
|
|
192
|
+
field.options = decoratorInfo.options;
|
|
193
|
+
} else if (typeInfo.values) {
|
|
194
|
+
field.options = typeInfo.values.map((v) => String(v));
|
|
195
|
+
}
|
|
196
|
+
if (typeInfo.type === "object" && typeInfo.properties) {
|
|
197
|
+
field.fields = Object.entries(typeInfo.properties).map(([propName, propType]) => createField(propName, propType, {}));
|
|
198
|
+
}
|
|
199
|
+
return field;
|
|
200
|
+
}
|
|
201
|
+
function toFormSpec(ctor) {
|
|
202
|
+
const typeMetadata = (
|
|
203
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
204
|
+
ctor[FORMSPEC_TYPES_KEY] ?? {}
|
|
205
|
+
);
|
|
206
|
+
const classDecoratorMeta = decoratorMetadata.get(ctor) ?? /* @__PURE__ */ new Map();
|
|
207
|
+
const elements = [];
|
|
208
|
+
for (const [fieldName, typeInfo] of Object.entries(typeMetadata)) {
|
|
209
|
+
const decoratorInfo = classDecoratorMeta.get(fieldName) ?? {};
|
|
210
|
+
elements.push(createField(fieldName, typeInfo, decoratorInfo));
|
|
211
|
+
}
|
|
212
|
+
if (Object.keys(typeMetadata).length === 0) {
|
|
213
|
+
for (const [fieldName, decoratorInfo] of classDecoratorMeta.entries()) {
|
|
214
|
+
elements.push(createField(fieldName, { type: "unknown" }, decoratorInfo));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return { elements };
|
|
218
|
+
}
|
|
219
|
+
function getDecoratorMetadata(ctor) {
|
|
220
|
+
return decoratorMetadata.get(ctor) ?? /* @__PURE__ */ new Map();
|
|
221
|
+
}
|
|
222
|
+
function getTypeMetadata(ctor) {
|
|
223
|
+
return ctor[FORMSPEC_TYPES_KEY] ?? {};
|
|
224
|
+
}
|
|
225
|
+
function fieldTypeToJsonSchemaType(fieldType) {
|
|
226
|
+
switch (fieldType) {
|
|
227
|
+
case "text":
|
|
228
|
+
return "string";
|
|
229
|
+
case "number":
|
|
230
|
+
return "number";
|
|
231
|
+
case "boolean":
|
|
232
|
+
return "boolean";
|
|
233
|
+
case "enum":
|
|
234
|
+
return "string";
|
|
235
|
+
case "array":
|
|
236
|
+
return "array";
|
|
237
|
+
case "object":
|
|
238
|
+
return "object";
|
|
239
|
+
default:
|
|
240
|
+
return "string";
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function fieldToJsonSchema(field) {
|
|
244
|
+
const schema = {};
|
|
245
|
+
if (field.label) {
|
|
246
|
+
schema.title = field.label;
|
|
247
|
+
}
|
|
248
|
+
const jsonType = fieldTypeToJsonSchemaType(field._field);
|
|
249
|
+
schema.type = jsonType;
|
|
250
|
+
if (field.min !== void 0)
|
|
251
|
+
schema.minimum = field.min;
|
|
252
|
+
if (field.max !== void 0)
|
|
253
|
+
schema.maximum = field.max;
|
|
254
|
+
if (field.minLength !== void 0)
|
|
255
|
+
schema.minLength = field.minLength;
|
|
256
|
+
if (field.maxLength !== void 0)
|
|
257
|
+
schema.maxLength = field.maxLength;
|
|
258
|
+
if (field.pattern)
|
|
259
|
+
schema.pattern = field.pattern;
|
|
260
|
+
if (field._field === "enum" && field.options) {
|
|
261
|
+
const hasLabels = field.options.some((opt) => typeof opt === "object" && opt !== null && "id" in opt);
|
|
262
|
+
if (hasLabels) {
|
|
263
|
+
schema.oneOf = field.options.map((opt) => {
|
|
264
|
+
if (typeof opt === "object" && "id" in opt) {
|
|
265
|
+
return { const: opt.id, title: opt.label };
|
|
266
|
+
}
|
|
267
|
+
return { const: opt, title: String(opt) };
|
|
268
|
+
});
|
|
269
|
+
} else {
|
|
270
|
+
schema.enum = field.options.map((opt) => typeof opt === "string" ? opt : opt.id);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (field._field === "array" && field.fields) {
|
|
274
|
+
const itemProperties = {};
|
|
275
|
+
const itemRequired = [];
|
|
276
|
+
for (const nested of field.fields) {
|
|
277
|
+
itemProperties[nested.id] = fieldToJsonSchema(nested);
|
|
278
|
+
if (nested.required)
|
|
279
|
+
itemRequired.push(nested.id);
|
|
280
|
+
}
|
|
281
|
+
schema.items = {
|
|
282
|
+
type: "object",
|
|
283
|
+
properties: itemProperties,
|
|
284
|
+
...itemRequired.length > 0 && { required: itemRequired }
|
|
285
|
+
};
|
|
286
|
+
if (field.minItems !== void 0)
|
|
287
|
+
schema.minItems = field.minItems;
|
|
288
|
+
if (field.maxItems !== void 0)
|
|
289
|
+
schema.maxItems = field.maxItems;
|
|
290
|
+
}
|
|
291
|
+
if (field._field === "object" && field.fields) {
|
|
292
|
+
const objProperties = {};
|
|
293
|
+
const objRequired = [];
|
|
294
|
+
for (const nested of field.fields) {
|
|
295
|
+
objProperties[nested.id] = fieldToJsonSchema(nested);
|
|
296
|
+
if (nested.required)
|
|
297
|
+
objRequired.push(nested.id);
|
|
298
|
+
}
|
|
299
|
+
schema.properties = objProperties;
|
|
300
|
+
if (objRequired.length > 0)
|
|
301
|
+
schema.required = objRequired;
|
|
302
|
+
}
|
|
303
|
+
return schema;
|
|
304
|
+
}
|
|
305
|
+
function generateJsonSchemaFromElements(elements) {
|
|
306
|
+
const properties = {};
|
|
307
|
+
const required = [];
|
|
308
|
+
for (const element of elements) {
|
|
309
|
+
properties[element.id] = fieldToJsonSchema(element);
|
|
310
|
+
if (element.required) {
|
|
311
|
+
required.push(element.id);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return {
|
|
315
|
+
$schema: "https://json-schema.org/draft-07/schema#",
|
|
316
|
+
type: "object",
|
|
317
|
+
properties,
|
|
318
|
+
...required.length > 0 && { required }
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
function fieldToScope(fieldName) {
|
|
322
|
+
return `#/properties/${fieldName}`;
|
|
323
|
+
}
|
|
324
|
+
function elementsToUiSchema(elements) {
|
|
325
|
+
const result = [];
|
|
326
|
+
for (const element of elements) {
|
|
327
|
+
const control = {
|
|
328
|
+
type: "Control",
|
|
329
|
+
scope: fieldToScope(element.id)
|
|
330
|
+
};
|
|
331
|
+
if (element.label) {
|
|
332
|
+
control.label = element.label;
|
|
333
|
+
}
|
|
334
|
+
if (element.showWhen) {
|
|
335
|
+
control.rule = {
|
|
336
|
+
effect: "SHOW",
|
|
337
|
+
condition: {
|
|
338
|
+
scope: fieldToScope(element.showWhen.field),
|
|
339
|
+
schema: { const: element.showWhen.value }
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
result.push(control);
|
|
344
|
+
}
|
|
345
|
+
return result;
|
|
346
|
+
}
|
|
347
|
+
function generateUiSchemaFromElements(elements) {
|
|
348
|
+
return {
|
|
349
|
+
type: "VerticalLayout",
|
|
350
|
+
elements: elementsToUiSchema(elements)
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
function buildFormSchemas(ctor) {
|
|
354
|
+
const { elements } = toFormSpec(ctor);
|
|
355
|
+
return {
|
|
356
|
+
jsonSchema: generateJsonSchemaFromElements(elements),
|
|
357
|
+
uiSchema: generateUiSchemaFromElements(elements)
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
361
|
+
0 && (module.exports = {
|
|
362
|
+
Description,
|
|
363
|
+
EnumOptions,
|
|
364
|
+
Group,
|
|
365
|
+
Label,
|
|
366
|
+
Max,
|
|
367
|
+
MaxItems,
|
|
368
|
+
MaxLength,
|
|
369
|
+
Min,
|
|
370
|
+
MinItems,
|
|
371
|
+
MinLength,
|
|
372
|
+
Pattern,
|
|
373
|
+
Placeholder,
|
|
374
|
+
ShowWhen,
|
|
375
|
+
Step,
|
|
376
|
+
buildFormSchemas,
|
|
377
|
+
getDecoratorMetadata,
|
|
378
|
+
getTypeMetadata,
|
|
379
|
+
toFormSpec
|
|
380
|
+
});
|