@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.
Files changed (2) hide show
  1. package/README.md +33 -309
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -1,336 +1,60 @@
1
1
  # @formspec/dsl
2
2
 
3
- Type-safe form definition using a fluent builder API. This is the recommended approach for defining forms programmatically, especially when you need runtime form construction or dynamic forms.
3
+ Chain DSL for defining FormSpec forms with TypeScript inference.
4
4
 
5
- ## Installation
5
+ ## Install
6
6
 
7
7
  ```bash
8
- npm install @formspec/dsl @formspec/build
9
- # Or use the umbrella package:
10
- npm install formspec
8
+ pnpm add @formspec/dsl @formspec/build
11
9
  ```
12
10
 
13
- ## Requirements
11
+ Or use the umbrella package:
14
12
 
15
- This package is ESM-only and requires:
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
- ```typescript
37
- import { formspec, field, group, when, is, type InferFormSchema } from "@formspec/dsl";
19
+ ```ts
20
+ import { field, formspec, group, is, type InferFormSchema, when } from "@formspec/dsl";
38
21
  import { buildFormSchemas } from "@formspec/build";
39
22
 
40
- // Define a form with full type safety
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
- "Preferences",
183
- field.enum("theme", ["light", "dark", "system"] as const, { label: "Theme" }),
184
- field.boolean("notifications", { label: "Enable notifications" })
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
- ```typescript
273
- import { buildFormSchemas, writeSchemas } from "@formspec/build";
32
+ type ProfileData = InferFormSchema<typeof ProfileForm>;
274
33
 
275
- // Get schema objects
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
- ## When to Use This Package
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
- | Builder | Description |
314
- | ----------------------------------------------------- | ------------------------- |
315
- | `field.text(name, config?)` | Text input field |
316
- | `field.number(name, config?)` | Numeric input field |
317
- | `field.boolean(name, config?)` | Checkbox/toggle field |
318
- | `field.enum(name, options, config?)` | Dropdown/select field |
319
- | `field.dynamicEnum(name, source, config?)` | API-populated dropdown |
320
- | `field.dynamicSchema(name, source, config?)` | Dynamic nested schema |
321
- | `field.array(name, ...items)` | Repeatable field array |
322
- | `field.arrayWithConfig(name, config, ...items)` | Array with configuration |
323
- | `field.object(name, ...properties)` | Nested object field |
324
- | `field.objectWithConfig(name, config, ...properties)` | Object with configuration |
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
- ### Type Utilities
53
+ ## Notes
327
54
 
328
- | Type | Description |
329
- | ----------------------- | ------------------------------------ |
330
- | `InferFormSchema<F>` | Infer data type from FormSpec |
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.17",
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.17"
21
+ "@formspec/core": "0.1.0-alpha.19"
22
22
  },
23
23
  "devDependencies": {
24
24
  "tsd": "^0.31.0",