@formspec/dsl 0.1.0-alpha.17 → 0.1.0-alpha.19
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 +33 -309
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,336 +1,60 @@
|
|
|
1
1
|
# @formspec/dsl
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Chain DSL for defining FormSpec forms with TypeScript inference.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
9
|
-
# Or use the umbrella package:
|
|
10
|
-
npm install formspec
|
|
8
|
+
pnpm add @formspec/dsl @formspec/build
|
|
11
9
|
```
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
Or use the umbrella package:
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
```json
|
|
18
|
-
// package.json
|
|
19
|
-
{
|
|
20
|
-
"type": "module"
|
|
21
|
-
}
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
```json
|
|
25
|
-
// tsconfig.json
|
|
26
|
-
{
|
|
27
|
-
"compilerOptions": {
|
|
28
|
-
"module": "NodeNext",
|
|
29
|
-
"moduleResolution": "NodeNext"
|
|
30
|
-
}
|
|
31
|
-
}
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add formspec
|
|
32
15
|
```
|
|
33
16
|
|
|
34
17
|
## Quick Start
|
|
35
18
|
|
|
36
|
-
```
|
|
37
|
-
import {
|
|
19
|
+
```ts
|
|
20
|
+
import { field, formspec, group, is, type InferFormSchema, when } from "@formspec/dsl";
|
|
38
21
|
import { buildFormSchemas } from "@formspec/build";
|
|
39
22
|
|
|
40
|
-
|
|
41
|
-
const ContactForm = formspec(
|
|
42
|
-
field.text("name", { label: "Name", required: true }),
|
|
43
|
-
field.text("email", { label: "Email", required: true }),
|
|
44
|
-
field.enum("subject", ["general", "support", "sales"] as const, {
|
|
45
|
-
label: "Subject",
|
|
46
|
-
required: true,
|
|
47
|
-
}),
|
|
48
|
-
field.text("message", { label: "Message", required: true }),
|
|
49
|
-
field.boolean("subscribe", { label: "Subscribe to newsletter" })
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
// Infer TypeScript types from the form definition
|
|
53
|
-
type ContactData = InferFormSchema<typeof ContactForm>;
|
|
54
|
-
// Result: { name: string; email: string; subject: "general" | "support" | "sales"; message: string; subscribe: boolean }
|
|
55
|
-
|
|
56
|
-
// Generate JSON Schema and UI Schema
|
|
57
|
-
const { jsonSchema, uiSchema } = buildFormSchemas(ContactForm);
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Field Types
|
|
61
|
-
|
|
62
|
-
### Text Field
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
field.text("fieldName", {
|
|
66
|
-
label: "Display Label",
|
|
67
|
-
description: "Help text",
|
|
68
|
-
required: true,
|
|
69
|
-
minLength: 1,
|
|
70
|
-
maxLength: 100,
|
|
71
|
-
pattern: "^[a-zA-Z]+$", // Regex pattern
|
|
72
|
-
});
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### Number Field
|
|
76
|
-
|
|
77
|
-
```typescript
|
|
78
|
-
field.number("age", {
|
|
79
|
-
label: "Age",
|
|
80
|
-
required: true,
|
|
81
|
-
min: 0,
|
|
82
|
-
max: 120,
|
|
83
|
-
});
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### Boolean Field
|
|
87
|
-
|
|
88
|
-
```typescript
|
|
89
|
-
field.boolean("acceptTerms", {
|
|
90
|
-
label: "I accept the terms and conditions",
|
|
91
|
-
required: true,
|
|
92
|
-
});
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
### Enum Field (Dropdown/Select)
|
|
96
|
-
|
|
97
|
-
Use `as const` to preserve literal types for type inference:
|
|
98
|
-
|
|
99
|
-
```typescript
|
|
100
|
-
// Simple string options
|
|
101
|
-
field.enum("status", ["draft", "published", "archived"] as const, {
|
|
102
|
-
label: "Status",
|
|
103
|
-
required: true,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// Options with separate IDs and labels
|
|
107
|
-
field.enum(
|
|
108
|
-
"country",
|
|
109
|
-
[
|
|
110
|
-
{ id: "us", label: "United States" },
|
|
111
|
-
{ id: "ca", label: "Canada" },
|
|
112
|
-
{ id: "uk", label: "United Kingdom" },
|
|
113
|
-
] as const,
|
|
114
|
-
{
|
|
115
|
-
label: "Country",
|
|
116
|
-
}
|
|
117
|
-
);
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
**Note:** Use `as const` when passing enum options from a variable. For inline array literals, the `const` type parameter preserves literal types automatically.
|
|
121
|
-
|
|
122
|
-
### Dynamic Enum Field
|
|
123
|
-
|
|
124
|
-
For dropdowns populated at runtime from an API:
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
field.dynamicEnum("customerId", "fetch_customers", {
|
|
128
|
-
label: "Customer",
|
|
129
|
-
required: true,
|
|
130
|
-
});
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
The second argument is a source identifier used with `@formspec/runtime` resolvers.
|
|
134
|
-
|
|
135
|
-
### Array Field
|
|
136
|
-
|
|
137
|
-
For repeatable field groups:
|
|
138
|
-
|
|
139
|
-
```typescript
|
|
140
|
-
field.array(
|
|
141
|
-
"lineItems",
|
|
142
|
-
field.text("description", { label: "Description", required: true }),
|
|
143
|
-
field.number("quantity", { label: "Quantity", min: 1 }),
|
|
144
|
-
field.number("price", { label: "Unit Price", min: 0 })
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
// With configuration
|
|
148
|
-
field.arrayWithConfig(
|
|
149
|
-
"contacts",
|
|
150
|
-
{ label: "Contact List", minItems: 1, maxItems: 5 },
|
|
151
|
-
field.text("name", { label: "Name" }),
|
|
152
|
-
field.text("phone", { label: "Phone" })
|
|
153
|
-
);
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### Object Field
|
|
157
|
-
|
|
158
|
-
For nested field groups:
|
|
159
|
-
|
|
160
|
-
```typescript
|
|
161
|
-
field.object(
|
|
162
|
-
"address",
|
|
163
|
-
field.text("street", { label: "Street", required: true }),
|
|
164
|
-
field.text("city", { label: "City", required: true }),
|
|
165
|
-
field.text("zipCode", { label: "ZIP Code", required: true })
|
|
166
|
-
);
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
## Grouping
|
|
170
|
-
|
|
171
|
-
Use `group()` to visually organize fields:
|
|
172
|
-
|
|
173
|
-
```typescript
|
|
174
|
-
const UserForm = formspec(
|
|
175
|
-
group(
|
|
176
|
-
"Personal Information",
|
|
177
|
-
field.text("firstName", { label: "First Name", required: true }),
|
|
178
|
-
field.text("lastName", { label: "Last Name", required: true }),
|
|
179
|
-
field.text("email", { label: "Email", required: true })
|
|
180
|
-
),
|
|
23
|
+
const ProfileForm = formspec(
|
|
181
24
|
group(
|
|
182
|
-
"
|
|
183
|
-
field.
|
|
184
|
-
field.
|
|
185
|
-
)
|
|
186
|
-
);
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
## Conditional Fields
|
|
190
|
-
|
|
191
|
-
Use `when()` and `is()` to show/hide fields based on other field values:
|
|
192
|
-
|
|
193
|
-
```typescript
|
|
194
|
-
const OrderForm = formspec(
|
|
195
|
-
field.enum("shippingMethod", ["standard", "express", "pickup"] as const, {
|
|
196
|
-
label: "Shipping Method",
|
|
197
|
-
required: true,
|
|
198
|
-
}),
|
|
199
|
-
|
|
200
|
-
// Only show address fields when shipping method is not "pickup"
|
|
201
|
-
when(
|
|
202
|
-
is("shippingMethod", "standard"),
|
|
203
|
-
field.text("address", { label: "Shipping Address", required: true }),
|
|
204
|
-
field.text("city", { label: "City", required: true })
|
|
25
|
+
"Profile",
|
|
26
|
+
field.text("displayName", { required: true }),
|
|
27
|
+
field.enum("role", ["admin", "member"] as const, { required: true })
|
|
205
28
|
),
|
|
206
|
-
when(
|
|
207
|
-
is("shippingMethod", "express"),
|
|
208
|
-
field.text("address", { label: "Shipping Address", required: true }),
|
|
209
|
-
field.text("city", { label: "City", required: true }),
|
|
210
|
-
field.text("phone", { label: "Phone for courier", required: true })
|
|
211
|
-
)
|
|
212
|
-
);
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
## Type Inference
|
|
216
|
-
|
|
217
|
-
The library provides powerful type inference utilities:
|
|
218
|
-
|
|
219
|
-
```typescript
|
|
220
|
-
import { type InferFormSchema, type InferFieldValue } from "@formspec/dsl";
|
|
221
|
-
|
|
222
|
-
const MyForm = formspec(
|
|
223
|
-
field.text("name"),
|
|
224
|
-
field.number("age"),
|
|
225
|
-
field.enum("role", ["admin", "user", "guest"] as const)
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
// Infer the complete form data type
|
|
229
|
-
type FormData = InferFormSchema<typeof MyForm>;
|
|
230
|
-
// { name: string; age: number; role: "admin" | "user" | "guest" }
|
|
231
|
-
|
|
232
|
-
// Access form elements at runtime
|
|
233
|
-
for (const element of MyForm.elements) {
|
|
234
|
-
if (element._type === "field") {
|
|
235
|
-
console.log(element.name, element._field);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
## Validation
|
|
241
|
-
|
|
242
|
-
Validate form definitions at runtime:
|
|
243
|
-
|
|
244
|
-
```typescript
|
|
245
|
-
import { formspec, field, validateForm, logValidationIssues } from "@formspec/dsl";
|
|
246
|
-
|
|
247
|
-
const form = formspec(
|
|
248
|
-
field.text("email"),
|
|
249
|
-
field.text("email") // Duplicate field name!
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
const result = validateForm(form.elements);
|
|
253
|
-
if (!result.valid) {
|
|
254
|
-
logValidationIssues(result, "MyForm");
|
|
255
|
-
// Logs: [MyForm] ERROR at email: Duplicate field name "email"
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// Or use formspecWithValidation for automatic checking
|
|
259
|
-
import { formspecWithValidation } from "@formspec/dsl";
|
|
260
|
-
|
|
261
|
-
const validatedForm = formspecWithValidation(
|
|
262
|
-
{ name: "MyForm", validate: "throw" },
|
|
263
|
-
field.text("email"),
|
|
264
|
-
field.text("email") // Throws error!
|
|
29
|
+
when(is("role", "admin"), field.boolean("superUser"))
|
|
265
30
|
);
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
## Schema Generation
|
|
269
|
-
|
|
270
|
-
Use `@formspec/build` to generate JSON Schema and UI Schema:
|
|
271
31
|
|
|
272
|
-
|
|
273
|
-
import { buildFormSchemas, writeSchemas } from "@formspec/build";
|
|
32
|
+
type ProfileData = InferFormSchema<typeof ProfileForm>;
|
|
274
33
|
|
|
275
|
-
|
|
276
|
-
const { jsonSchema, uiSchema } = buildFormSchemas(MyForm);
|
|
277
|
-
|
|
278
|
-
// Or write to files
|
|
279
|
-
writeSchemas(MyForm, {
|
|
280
|
-
outDir: "./generated",
|
|
281
|
-
name: "MyForm",
|
|
282
|
-
});
|
|
283
|
-
// Creates:
|
|
284
|
-
// ./generated/MyForm-schema.json
|
|
285
|
-
// ./generated/MyForm-uischema.json
|
|
34
|
+
const { jsonSchema, uiSchema } = buildFormSchemas(ProfileForm);
|
|
286
35
|
```
|
|
287
36
|
|
|
288
|
-
##
|
|
289
|
-
|
|
290
|
-
Use `@formspec/dsl` when:
|
|
291
|
-
|
|
292
|
-
- **Forms are defined programmatically** - Building forms from configuration or code
|
|
293
|
-
- **Runtime form construction** - Creating forms dynamically based on user input or API data
|
|
294
|
-
- **Full type inference needed** - Deriving TypeScript types from form definitions
|
|
295
|
-
- **No build step preferred** - Works directly at runtime without CLI codegen
|
|
296
|
-
|
|
297
|
-
## API Reference
|
|
298
|
-
|
|
299
|
-
### Functions
|
|
300
|
-
|
|
301
|
-
| Function | Description |
|
|
302
|
-
| ---------------------------------------------- | -------------------------------- |
|
|
303
|
-
| `formspec(...elements)` | Create a form specification |
|
|
304
|
-
| `formspecWithValidation(options, ...elements)` | Create a form with validation |
|
|
305
|
-
| `group(label, ...elements)` | Create a visual field group |
|
|
306
|
-
| `when(predicate, ...elements)` | Create conditional fields |
|
|
307
|
-
| `is(fieldName, value)` | Create an equality predicate |
|
|
308
|
-
| `validateForm(elements)` | Validate form elements |
|
|
309
|
-
| `logValidationIssues(result)` | Log validation issues to console |
|
|
310
|
-
|
|
311
|
-
### Field Builders
|
|
37
|
+
## Main Builders
|
|
312
38
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
39
|
+
- `formspec(...elements)`
|
|
40
|
+
- `field.text(name, config?)`
|
|
41
|
+
- `field.number(name, config?)`
|
|
42
|
+
- `field.boolean(name, config?)`
|
|
43
|
+
- `field.enum(name, options, config?)`
|
|
44
|
+
- `field.dynamicEnum(name, source, config?)`
|
|
45
|
+
- `field.array(name, ...elements)`
|
|
46
|
+
- `field.arrayWithConfig(name, config, ...elements)`
|
|
47
|
+
- `field.object(name, ...elements)`
|
|
48
|
+
- `field.objectWithConfig(name, config, ...elements)`
|
|
49
|
+
- `group(label, ...elements)`
|
|
50
|
+
- `when(predicate, ...elements)`
|
|
51
|
+
- `is(fieldName, value)`
|
|
325
52
|
|
|
326
|
-
|
|
53
|
+
## Notes
|
|
327
54
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
| `InferSchema<Elements>` | Infer data type from element array |
|
|
332
|
-
| `InferFieldValue<F>` | Infer value type from a single field |
|
|
333
|
-
| `ExtractFields<E>` | Extract all fields from an element |
|
|
55
|
+
- Use `as const` for enum option arrays when you want literal inference from variables.
|
|
56
|
+
- `group()` is layout-only; it does not change the data shape.
|
|
57
|
+
- `when()` affects UI behavior, not whether a field exists in the JSON Schema.
|
|
334
58
|
|
|
335
59
|
## License
|
|
336
60
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formspec/dsl",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.19",
|
|
4
4
|
"description": "DSL functions for defining FormSpec forms",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"README.md"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@formspec/core": "0.1.0-alpha.
|
|
21
|
+
"@formspec/core": "0.1.0-alpha.19"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"tsd": "^0.31.0",
|