@formspec/dsl 0.1.0-alpha.21 → 0.1.0-alpha.26

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.
@@ -0,0 +1,1001 @@
1
+ /**
2
+ * `@formspec/dsl` - DSL functions for defining FormSpec forms
3
+ *
4
+ * This package provides the builder functions for creating form specifications:
5
+ * - `field.*` - Field builders (text, number, boolean, enum, dynamicEnum)
6
+ * - `group()` - Visual grouping
7
+ * - `when()` + `is()` - Conditional visibility
8
+ * - `formspec()` - Top-level form definition
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { formspec, field, group, when, is } from "@formspec/dsl";
13
+ *
14
+ * const InvoiceForm = formspec(
15
+ * group("Customer",
16
+ * field.text("customerName", { label: "Customer Name" }),
17
+ * field.dynamicEnum("country", "fetch_countries", { label: "Country" }),
18
+ * ),
19
+ * group("Invoice Details",
20
+ * field.number("amount", { label: "Amount", min: 0 }),
21
+ * field.enum("status", ["draft", "sent", "paid"] as const),
22
+ * when(is("status", "draft"),
23
+ * field.text("internalNotes", { label: "Internal Notes" }),
24
+ * ),
25
+ * ),
26
+ * );
27
+ * ```
28
+ *
29
+ * @packageDocumentation
30
+ */
31
+
32
+ /**
33
+ * Union of all field types.
34
+ *
35
+ * @public
36
+ */
37
+ export declare type AnyField = TextField<string> | NumberField<string> | BooleanField<string> | StaticEnumField<string, readonly EnumOptionValue[]> | DynamicEnumField<string, string> | DynamicSchemaField<string> | ArrayField<string, readonly FormElement[]> | ObjectField<string, readonly FormElement[]>;
38
+
39
+ /**
40
+ * An array field containing repeating items.
41
+ *
42
+ * Use this for lists of values (e.g., multiple addresses, line items).
43
+ *
44
+ * @typeParam N - The field name (string literal type)
45
+ * @typeParam Items - The form elements that define each array item
46
+ *
47
+ * @public
48
+ */
49
+ export declare interface ArrayField<N extends string, Items extends readonly FormElement[]> {
50
+ /** Type discriminator for form elements */
51
+ readonly _type: "field";
52
+ /** Field type discriminator - identifies this as an array field */
53
+ readonly _field: "array";
54
+ /** Unique field identifier used as the schema key */
55
+ readonly name: N;
56
+ /** Form elements that define the schema for each array item */
57
+ readonly items: Items;
58
+ /** Display label for the field */
59
+ readonly label?: string;
60
+ /** Whether this field is required for form submission */
61
+ readonly required?: boolean;
62
+ /** Minimum number of items required */
63
+ readonly minItems?: number;
64
+ /** Maximum number of items allowed */
65
+ readonly maxItems?: number;
66
+ }
67
+
68
+ /**
69
+ * A boolean checkbox field.
70
+ *
71
+ * @typeParam N - The field name (string literal type)
72
+ *
73
+ * @public
74
+ */
75
+ export declare interface BooleanField<N extends string> {
76
+ /** Type discriminator for form elements */
77
+ readonly _type: "field";
78
+ /** Field type discriminator - identifies this as a boolean field */
79
+ readonly _field: "boolean";
80
+ /** Unique field identifier used as the schema key */
81
+ readonly name: N;
82
+ /** Display label for the field */
83
+ readonly label?: string;
84
+ /** Whether this field is required for form submission */
85
+ readonly required?: boolean;
86
+ }
87
+
88
+ /**
89
+ * Builds a schema type from extracted fields.
90
+ *
91
+ * Maps field names to their inferred value types.
92
+ *
93
+ * @public
94
+ */
95
+ export declare type BuildSchema<Fields> = {
96
+ [F in Fields as F extends {
97
+ name: infer N extends string;
98
+ } ? N : never]: F extends AnyField ? InferFieldValue<F> : never;
99
+ };
100
+
101
+ /**
102
+ * A conditional wrapper that shows/hides elements based on another field's value.
103
+ *
104
+ * @typeParam FieldName - The field to check
105
+ * @typeParam Value - The value that triggers the condition
106
+ * @typeParam Elements - Tuple of contained form elements
107
+ *
108
+ * @public
109
+ */
110
+ export declare interface Conditional<FieldName extends string, Value, Elements extends readonly FormElement[]> {
111
+ /** Type discriminator - identifies this as a conditional element */
112
+ readonly _type: "conditional";
113
+ /** Name of the field whose value determines visibility */
114
+ readonly field: FieldName;
115
+ /** Value that triggers the condition (shows nested elements) */
116
+ readonly value: Value;
117
+ /** Form elements shown when condition is met */
118
+ readonly elements: Elements;
119
+ }
120
+
121
+ /**
122
+ * Registry for dynamic data sources.
123
+ *
124
+ * Extend this interface via module augmentation to register your data sources:
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * declare module "@formspec/core" {
129
+ * interface DataSourceRegistry {
130
+ * countries: { id: string; code: string; name: string };
131
+ * templates: { id: string; name: string; category: string };
132
+ * }
133
+ * }
134
+ * ```
135
+ *
136
+ * @public
137
+ */
138
+ export declare interface DataSourceRegistry {
139
+ }
140
+
141
+ /**
142
+ * Gets the value type for a registered data source.
143
+ *
144
+ * If the source has an `id` property, that becomes the value type.
145
+ * Otherwise, defaults to `string`.
146
+ *
147
+ * @public
148
+ */
149
+ export declare type DataSourceValueType<Source extends string> = Source extends keyof DataSourceRegistry ? DataSourceRegistry[Source] extends {
150
+ id: infer ID;
151
+ } ? ID : string : string;
152
+
153
+ /**
154
+ * A field with dynamic enum options (fetched from a data source at runtime).
155
+ *
156
+ * @typeParam N - The field name (string literal type)
157
+ * @typeParam Source - The data source key (from DataSourceRegistry)
158
+ *
159
+ * @public
160
+ */
161
+ export declare interface DynamicEnumField<N extends string, Source extends string> {
162
+ /** Type discriminator for form elements */
163
+ readonly _type: "field";
164
+ /** Field type discriminator - identifies this as a dynamic enum field */
165
+ readonly _field: "dynamic_enum";
166
+ /** Unique field identifier used as the schema key */
167
+ readonly name: N;
168
+ /** Data source key for fetching options at runtime */
169
+ readonly source: Source;
170
+ /** Display label for the field */
171
+ readonly label?: string;
172
+ /** Whether this field is required for form submission */
173
+ readonly required?: boolean;
174
+ /** Field names whose values are needed to fetch options */
175
+ readonly params?: readonly string[];
176
+ }
177
+
178
+ /**
179
+ * A field that loads its schema dynamically (e.g., from an extension).
180
+ *
181
+ * @typeParam N - The field name (string literal type)
182
+ *
183
+ * @public
184
+ */
185
+ export declare interface DynamicSchemaField<N extends string> {
186
+ /** Type discriminator for form elements */
187
+ readonly _type: "field";
188
+ /** Field type discriminator - identifies this as a dynamic schema field */
189
+ readonly _field: "dynamic_schema";
190
+ /** Unique field identifier used as the schema key */
191
+ readonly name: N;
192
+ /** Identifier for the schema source */
193
+ readonly schemaSource: string;
194
+ /** Display label for the field */
195
+ readonly label?: string;
196
+ /** Whether this field is required for form submission */
197
+ readonly required?: boolean;
198
+ /** Field names whose values are needed to configure the schema */
199
+ readonly params?: readonly string[];
200
+ }
201
+
202
+ /**
203
+ * An enum option with a separate ID and display label.
204
+ *
205
+ * Use this when the stored value (id) should differ from the display text (label).
206
+ *
207
+ * @public
208
+ */
209
+ export declare interface EnumOption {
210
+ /** Stored enum value written into submitted data. */
211
+ readonly id: string;
212
+ /** Human-readable label shown to end users. */
213
+ readonly label: string;
214
+ }
215
+
216
+ /**
217
+ * Valid enum option types: either plain strings or objects with id/label.
218
+ *
219
+ * @public
220
+ */
221
+ export declare type EnumOptionValue = string | EnumOption;
222
+
223
+ /**
224
+ * Predicate types for conditional logic.
225
+ *
226
+ * Predicates are used with `when()` to define conditions in a readable way.
227
+ */
228
+ /**
229
+ * An equality predicate that checks if a field equals a specific value.
230
+ *
231
+ * @typeParam K - The field name to check
232
+ * @typeParam V - The value to compare against
233
+ *
234
+ * @public
235
+ */
236
+ export declare interface EqualsPredicate<K extends string, V> {
237
+ /** Predicate type discriminator */
238
+ readonly _predicate: "equals";
239
+ /** Name of the field to check */
240
+ readonly field: K;
241
+ /** Value that the field must equal */
242
+ readonly value: V;
243
+ }
244
+
245
+ /**
246
+ * Extracts fields that ARE inside conditionals.
247
+ * These fields may or may not be visible and should be optional in the inferred schema.
248
+ *
249
+ * @example
250
+ * ```typescript
251
+ * // Given a form with conditional fields:
252
+ * const form = formspec(
253
+ * field.text("name"), // non-conditional (skipped)
254
+ * when(is("type", "business"),
255
+ * field.text("company"), // conditional (extracted)
256
+ * field.text("taxId"), // conditional (extracted)
257
+ * ),
258
+ * );
259
+ *
260
+ * // ExtractConditionalFields extracts: TextField<"company"> | TextField<"taxId">
261
+ * ```
262
+ *
263
+ * @public
264
+ */
265
+ export declare type ExtractConditionalFields<E> = E extends AnyField ? never : E extends Group<infer Elements> ? ExtractConditionalFieldsFromArray<Elements> : E extends Conditional<string, unknown, infer Elements> ? ExtractFieldsFromArray<Elements> : never;
266
+
267
+ /**
268
+ * Extracts conditional fields from an array of elements.
269
+ *
270
+ * @example
271
+ * ```typescript
272
+ * type Elements = readonly [
273
+ * TextField<"name">,
274
+ * Conditional<"type", "business", readonly [TextField<"company">]>
275
+ * ];
276
+ * type Fields = ExtractConditionalFieldsFromArray<Elements>;
277
+ * // TextField<"company">
278
+ * ```
279
+ *
280
+ * @public
281
+ */
282
+ export declare type ExtractConditionalFieldsFromArray<Elements> = Elements extends readonly [
283
+ infer First,
284
+ ...infer Rest
285
+ ] ? ExtractConditionalFields<First> | ExtractConditionalFieldsFromArray<Rest> : never;
286
+
287
+ /**
288
+ * Extracts all fields from a single element (recursively).
289
+ *
290
+ * - Field elements return themselves
291
+ * - Groups extract fields from all child elements
292
+ * - Conditionals extract fields from all child elements
293
+ *
294
+ * @public
295
+ */
296
+ export declare type ExtractFields<E> = E extends AnyField ? E : E extends Group<infer Elements> ? ExtractFieldsFromArray<Elements> : E extends Conditional<string, unknown, infer Elements> ? ExtractFieldsFromArray<Elements> : never;
297
+
298
+ /**
299
+ * Extracts fields from an array of elements.
300
+ *
301
+ * Recursively processes each element and unions the results.
302
+ *
303
+ * @public
304
+ */
305
+ export declare type ExtractFieldsFromArray<Elements> = Elements extends readonly [
306
+ infer First,
307
+ ...infer Rest
308
+ ] ? ExtractFields<First> | ExtractFieldsFromArray<Rest> : never;
309
+
310
+ /**
311
+ * Extracts fields that are NOT inside conditionals.
312
+ * These fields are always visible and should be required in the inferred schema.
313
+ *
314
+ * @example
315
+ * ```typescript
316
+ * // Given a form with conditional and non-conditional fields:
317
+ * const form = formspec(
318
+ * field.text("name"), // non-conditional
319
+ * field.number("age"), // non-conditional
320
+ * when(is("type", "business"),
321
+ * field.text("company"), // conditional (skipped)
322
+ * ),
323
+ * );
324
+ *
325
+ * // ExtractNonConditionalFields extracts: TextField<"name"> | NumberField<"age">
326
+ * ```
327
+ *
328
+ * @public
329
+ */
330
+ export declare type ExtractNonConditionalFields<E> = E extends AnyField ? E : E extends Group<infer Elements> ? ExtractNonConditionalFieldsFromArray<Elements> : E extends Conditional<string, unknown, infer _Elements> ? never : never;
331
+
332
+ /**
333
+ * Extracts non-conditional fields from an array of elements.
334
+ *
335
+ * @example
336
+ * ```typescript
337
+ * type Elements = readonly [TextField<"name">, NumberField<"age">];
338
+ * type Fields = ExtractNonConditionalFieldsFromArray<Elements>;
339
+ * // TextField<"name"> | NumberField<"age">
340
+ * ```
341
+ *
342
+ * @public
343
+ */
344
+ export declare type ExtractNonConditionalFieldsFromArray<Elements> = Elements extends readonly [
345
+ infer First,
346
+ ...infer Rest
347
+ ] ? ExtractNonConditionalFields<First> | ExtractNonConditionalFieldsFromArray<Rest> : never;
348
+
349
+ /**
350
+ * Field builder namespace containing functions to create each field type.
351
+ *
352
+ * @example
353
+ * ```typescript
354
+ * import { field } from "@formspec/dsl";
355
+ *
356
+ * field.text("name", { label: "Full Name" });
357
+ * field.number("age", { min: 0, max: 150 });
358
+ * field.enum("status", ["draft", "sent", "paid"]);
359
+ * field.dynamicEnum("country", "countries", { label: "Country" });
360
+ * ```
361
+ *
362
+ * @public
363
+ */
364
+ export declare const field: {
365
+ /**
366
+ * Creates a text input field.
367
+ *
368
+ * @param name - The field name (used as the schema key)
369
+ * @param config - Optional configuration for label, placeholder, etc.
370
+ * @returns A TextField descriptor
371
+ */
372
+ text: <const N extends string>(name: N, config?: Omit<TextField<N>, "_type" | "_field" | "name">) => TextField<N>;
373
+ /**
374
+ * Creates a numeric input field.
375
+ *
376
+ * @param name - The field name (used as the schema key)
377
+ * @param config - Optional configuration for label, min, max, etc.
378
+ * @returns A NumberField descriptor
379
+ */
380
+ number: <const N extends string>(name: N, config?: Omit<NumberField<N>, "_type" | "_field" | "name">) => NumberField<N>;
381
+ /**
382
+ * Creates a boolean checkbox field.
383
+ *
384
+ * @param name - The field name (used as the schema key)
385
+ * @param config - Optional configuration for label, etc.
386
+ * @returns A BooleanField descriptor
387
+ */
388
+ boolean: <const N extends string>(name: N, config?: Omit<BooleanField<N>, "_type" | "_field" | "name">) => BooleanField<N>;
389
+ /**
390
+ * Creates a field with static enum options (known at compile time).
391
+ *
392
+ * Literal types are automatically inferred - no `as const` needed:
393
+ * ```typescript
394
+ * field.enum("status", ["draft", "sent", "paid"])
395
+ * // Schema type: "draft" | "sent" | "paid"
396
+ * ```
397
+ *
398
+ * Options can be strings or objects with `id` and `label`:
399
+ * ```typescript
400
+ * field.enum("priority", [
401
+ * { id: "low", label: "Low Priority" },
402
+ * { id: "high", label: "High Priority" },
403
+ * ])
404
+ * ```
405
+ *
406
+ * **Note:** All options must be of the same type (all strings OR all objects).
407
+ * Mixing strings and objects will throw a runtime error.
408
+ *
409
+ * @param name - The field name (used as the schema key)
410
+ * @param options - Array of allowed string values or objects with `id` and `label` properties
411
+ * @param config - Optional configuration for label, etc.
412
+ * @returns A StaticEnumField descriptor
413
+ * @throws Error if options array contains mixed types (strings and objects)
414
+ */
415
+ enum: <const N extends string, const O extends readonly EnumOptionValue[]>(name: N, options: O, config?: Omit<StaticEnumField<N, O>, "_type" | "_field" | "name" | "options">) => StaticEnumField<N, O>;
416
+ /**
417
+ * Creates a field with dynamic enum options (fetched from a data source at runtime).
418
+ *
419
+ * The data source must be registered in DataSourceRegistry via module augmentation:
420
+ * ```typescript
421
+ * declare module "@formspec/core" {
422
+ * interface DataSourceRegistry {
423
+ * countries: { id: string; code: string; name: string };
424
+ * }
425
+ * }
426
+ *
427
+ * field.dynamicEnum("country", "countries", { label: "Country" })
428
+ * ```
429
+ *
430
+ * @param name - The field name (used as the schema key)
431
+ * @param source - The data source key (must be in DataSourceRegistry)
432
+ * @param config - Optional configuration for label, params, etc.
433
+ * @returns A DynamicEnumField descriptor
434
+ */
435
+ dynamicEnum: <const N extends string, const Source extends string>(name: N, source: Source, config?: Omit<DynamicEnumField<N, Source>, "_type" | "_field" | "name" | "source">) => DynamicEnumField<N, Source>;
436
+ /**
437
+ * Creates a field that loads its schema dynamically (e.g., from an extension).
438
+ *
439
+ * @param name - The field name (used as the schema key)
440
+ * @param schemaSource - Identifier for the schema source
441
+ * @param config - Optional configuration for label, etc.
442
+ * @returns A DynamicSchemaField descriptor
443
+ */
444
+ dynamicSchema: <const N extends string>(name: N, schemaSource: string, config?: Omit<DynamicSchemaField<N>, "_type" | "_field" | "name" | "schemaSource">) => DynamicSchemaField<N>;
445
+ /**
446
+ * Creates an array field containing repeating items.
447
+ *
448
+ * Use this for lists of values (e.g., multiple addresses, line items).
449
+ *
450
+ * @example
451
+ * ```typescript
452
+ * field.array("addresses",
453
+ * field.text("street", { label: "Street" }),
454
+ * field.text("city", { label: "City" }),
455
+ * field.text("zip", { label: "ZIP Code" }),
456
+ * )
457
+ * ```
458
+ *
459
+ * @param name - The field name (used as the schema key)
460
+ * @param items - The form elements that define each array item
461
+ * @returns An ArrayField descriptor
462
+ */
463
+ array: <const N extends string, const Items extends readonly FormElement[]>(name: N, ...items: Items) => ArrayField<N, Items>;
464
+ /**
465
+ * Creates an array field with additional configuration options.
466
+ *
467
+ * @example
468
+ * ```typescript
469
+ * field.arrayWithConfig("lineItems", {
470
+ * label: "Line Items",
471
+ * minItems: 1,
472
+ * maxItems: 10,
473
+ * },
474
+ * field.text("description"),
475
+ * field.number("quantity"),
476
+ * )
477
+ * ```
478
+ *
479
+ * @param name - The field name (used as the schema key)
480
+ * @param config - Configuration for label, minItems, maxItems, etc.
481
+ * @param items - The form elements that define each array item
482
+ * @returns An ArrayField descriptor
483
+ */
484
+ arrayWithConfig: <const N extends string, const Items extends readonly FormElement[]>(name: N, config: Omit<ArrayField<N, Items>, "_type" | "_field" | "name" | "items">, ...items: Items) => ArrayField<N, Items>;
485
+ /**
486
+ * Creates an object field containing nested properties.
487
+ *
488
+ * Use this for grouping related fields under a single key in the schema.
489
+ *
490
+ * @example
491
+ * ```typescript
492
+ * field.object("address",
493
+ * field.text("street", { label: "Street" }),
494
+ * field.text("city", { label: "City" }),
495
+ * field.text("zip", { label: "ZIP Code" }),
496
+ * )
497
+ * ```
498
+ *
499
+ * @param name - The field name (used as the schema key)
500
+ * @param properties - The form elements that define the object's properties
501
+ * @returns An ObjectField descriptor
502
+ */
503
+ object: <const N extends string, const Properties extends readonly FormElement[]>(name: N, ...properties: Properties) => ObjectField<N, Properties>;
504
+ /**
505
+ * Creates an object field with additional configuration options.
506
+ *
507
+ * @example
508
+ * ```typescript
509
+ * field.objectWithConfig("billingAddress", { label: "Billing Address", required: true },
510
+ * field.text("street"),
511
+ * field.text("city"),
512
+ * )
513
+ * ```
514
+ *
515
+ * @param name - The field name (used as the schema key)
516
+ * @param config - Configuration for label, required, etc.
517
+ * @param properties - The form elements that define the object's properties
518
+ * @returns An ObjectField descriptor
519
+ */
520
+ objectWithConfig: <const N extends string, const Properties extends readonly FormElement[]>(name: N, config: Omit<ObjectField<N, Properties>, "_type" | "_field" | "name" | "properties">, ...properties: Properties) => ObjectField<N, Properties>;
521
+ };
522
+
523
+ /**
524
+ * Utility type that flattens intersection types into a single object type.
525
+ *
526
+ * This improves TypeScript's display of inferred types and ensures
527
+ * structural equality checks work correctly.
528
+ *
529
+ * @example
530
+ * ```typescript
531
+ * // Without FlattenIntersection:
532
+ * type Messy = { a: string } & { b: number };
533
+ * // Displays as: { a: string } & { b: number }
534
+ *
535
+ * // With FlattenIntersection:
536
+ * type Clean = FlattenIntersection<{ a: string } & { b: number }>;
537
+ * // Displays as: { a: string; b: number }
538
+ * ```
539
+ *
540
+ * @public
541
+ */
542
+ export declare type FlattenIntersection<T> = {
543
+ [K in keyof T]: T[K];
544
+ } & {};
545
+
546
+ /**
547
+ * Union of all form element types (fields and structural elements).
548
+ *
549
+ * @public
550
+ */
551
+ export declare type FormElement = AnyField | Group<readonly FormElement[]> | Conditional<string, unknown, readonly FormElement[]>;
552
+
553
+ /**
554
+ * A complete form specification.
555
+ *
556
+ * @typeParam Elements - Tuple of top-level form elements
557
+ *
558
+ * @public
559
+ */
560
+ export declare interface FormSpec<Elements extends readonly FormElement[]> {
561
+ /** Top-level form elements */
562
+ readonly elements: Elements;
563
+ }
564
+
565
+ /**
566
+ * Creates a complete form specification.
567
+ *
568
+ * The structure IS the definition:
569
+ * - Nesting with `group()` defines visual layout
570
+ * - Nesting with `when()` defines conditional visibility
571
+ * - Field type implies control type (text field → text input)
572
+ * - Array position implies field ordering
573
+ *
574
+ * Schema is automatically inferred from all fields in the structure.
575
+ *
576
+ * @example
577
+ * ```typescript
578
+ * const InvoiceForm = formspec(
579
+ * group("Customer",
580
+ * field.text("customerName", { label: "Customer Name" }),
581
+ * field.dynamicEnum("country", "fetch_countries", { label: "Country" }),
582
+ * ),
583
+ * group("Invoice Details",
584
+ * field.number("amount", { label: "Amount", min: 0 }),
585
+ * field.enum("status", ["draft", "sent", "paid"] as const),
586
+ * when(is("status", "draft"),
587
+ * field.text("internalNotes", { label: "Internal Notes" }),
588
+ * ),
589
+ * ),
590
+ * );
591
+ * ```
592
+ *
593
+ * @param elements - The top-level form elements
594
+ * @returns A FormSpec descriptor
595
+ *
596
+ * @public
597
+ */
598
+ export declare function formspec<const Elements extends readonly FormElement[]>(...elements: Elements): FormSpec<Elements>;
599
+
600
+ /**
601
+ * Options for creating a form specification.
602
+ *
603
+ * @public
604
+ */
605
+ export declare interface FormSpecOptions {
606
+ /**
607
+ * Whether to validate the form structure.
608
+ * - `true` or `"warn"`: Validate and log warnings/errors to console
609
+ * - `"throw"`: Validate and throw an error if validation fails
610
+ * - `false`: Skip validation (default in production for performance)
611
+ *
612
+ * @defaultValue false
613
+ */
614
+ validate?: boolean | "warn" | "throw";
615
+ /**
616
+ * Optional name for the form (used in validation messages).
617
+ */
618
+ name?: string;
619
+ }
620
+
621
+ /**
622
+ * Creates a complete form specification with validation options.
623
+ *
624
+ * @example
625
+ * ```typescript
626
+ * const form = formspecWithValidation(
627
+ * { validate: true, name: "MyForm" },
628
+ * field.text("name"),
629
+ * field.enum("status", ["draft", "sent"] as const),
630
+ * when(is("status", "draft"),
631
+ * field.text("notes"),
632
+ * ),
633
+ * );
634
+ * ```
635
+ *
636
+ * @param options - Validation options
637
+ * @param elements - The top-level form elements
638
+ * @returns A FormSpec descriptor
639
+ *
640
+ * @public
641
+ */
642
+ export declare function formspecWithValidation<const Elements extends readonly FormElement[]>(options: FormSpecOptions, ...elements: Elements): FormSpec<Elements>;
643
+
644
+ /**
645
+ * A visual grouping of form elements.
646
+ *
647
+ * Groups provide visual organization and can be rendered as fieldsets or sections.
648
+ *
649
+ * @typeParam Elements - Tuple of contained form elements
650
+ *
651
+ * @public
652
+ */
653
+ export declare interface Group<Elements extends readonly FormElement[]> {
654
+ /** Type discriminator - identifies this as a group element */
655
+ readonly _type: "group";
656
+ /** Display label for the group */
657
+ readonly label: string;
658
+ /** Form elements contained within this group */
659
+ readonly elements: Elements;
660
+ }
661
+
662
+ /**
663
+ * Creates a visual group of form elements.
664
+ *
665
+ * Groups provide visual organization and can be rendered as fieldsets or sections.
666
+ * The nesting of groups defines the visual hierarchy of the form.
667
+ *
668
+ * @example
669
+ * ```typescript
670
+ * group("Customer Information",
671
+ * field.text("name", { label: "Name" }),
672
+ * field.text("email", { label: "Email" }),
673
+ * )
674
+ * ```
675
+ *
676
+ * @param label - The group's display label
677
+ * @param elements - The form elements contained in this group
678
+ * @returns A Group descriptor
679
+ *
680
+ * @public
681
+ */
682
+ export declare function group<const Elements extends readonly FormElement[]>(label: string, ...elements: Elements): Group<Elements>;
683
+
684
+ /**
685
+ * Infers the value type from a single field.
686
+ *
687
+ * - TextField returns string
688
+ * - NumberField returns number
689
+ * - BooleanField returns boolean
690
+ * - StaticEnumField returns union of option literals
691
+ * - DynamicEnumField returns DataSourceValueType (usually string)
692
+ * - DynamicSchemaField returns Record of string to unknown
693
+ * - ArrayField returns array of inferred item schema
694
+ * - ObjectField returns object of inferred property schema
695
+ *
696
+ * @example
697
+ * ```typescript
698
+ * // Simple fields
699
+ * type T1 = InferFieldValue<TextField<"name">>; // string
700
+ * type T2 = InferFieldValue<NumberField<"age">>; // number
701
+ *
702
+ * // Enum fields
703
+ * type T3 = InferFieldValue<StaticEnumField<"status", ["draft", "sent"]>>; // "draft" | "sent"
704
+ *
705
+ * // Nested fields
706
+ * type T4 = InferFieldValue<ArrayField<"items", [TextField<"name">]>>; // { name: string }[]
707
+ * type T5 = InferFieldValue<ObjectField<"address", [TextField<"city">]>>; // { city: string }
708
+ * ```
709
+ *
710
+ * @public
711
+ */
712
+ export declare type InferFieldValue<F> = F extends TextField<string> ? string : F extends NumberField<string> ? number : F extends BooleanField<string> ? boolean : F extends StaticEnumField<string, infer O extends readonly EnumOptionValue[]> ? O extends readonly EnumOption[] ? O[number]["id"] : O extends readonly string[] ? O[number] : never : F extends DynamicEnumField<string, infer Source> ? DataSourceValueType<Source> : F extends DynamicSchemaField<string> ? Record<string, unknown> : F extends ArrayField<string, infer Items extends readonly FormElement[]> ? InferSchema<Items>[] : F extends ObjectField<string, infer Properties extends readonly FormElement[]> ? InferSchema<Properties> : never;
713
+
714
+ /**
715
+ * Infers the schema type from a FormSpec.
716
+ *
717
+ * Convenience type that extracts elements and infers the schema.
718
+ *
719
+ * @example
720
+ * ```typescript
721
+ * const form = formspec(...);
722
+ * type Schema = InferFormSchema<typeof form>;
723
+ * ```
724
+ *
725
+ * @public
726
+ */
727
+ export declare type InferFormSchema<F extends FormSpec<readonly FormElement[]>> = F extends FormSpec<infer Elements> ? InferSchema<Elements> : never;
728
+
729
+ /**
730
+ * Infers the complete schema type from a form's elements.
731
+ *
732
+ * This is the main inference type that converts a form structure
733
+ * into its corresponding TypeScript schema type.
734
+ *
735
+ * Non-conditional fields are required, conditional fields are optional.
736
+ *
737
+ * @example
738
+ * ```typescript
739
+ * const form = formspec(
740
+ * field.text("name"),
741
+ * field.number("age"),
742
+ * field.enum("status", ["active", "inactive"] as const),
743
+ * );
744
+ *
745
+ * type Schema = InferSchema<typeof form.elements>;
746
+ * // { name: string; age: number; status: "active" | "inactive" }
747
+ *
748
+ * // Conditional fields become optional:
749
+ * const formWithConditional = formspec(
750
+ * field.enum("type", ["a", "b"] as const),
751
+ * when(is("type", "a"), field.text("aField")),
752
+ * );
753
+ * type ConditionalSchema = InferSchema<typeof formWithConditional.elements>;
754
+ * // { type: "a" | "b"; aField?: string }
755
+ * ```
756
+ *
757
+ * @public
758
+ */
759
+ export declare type InferSchema<Elements extends readonly FormElement[]> = FlattenIntersection<BuildSchema<ExtractNonConditionalFieldsFromArray<Elements>> & Partial<BuildSchema<ExtractConditionalFieldsFromArray<Elements>>>>;
760
+
761
+ /**
762
+ * Creates an equality predicate that checks if a field equals a specific value.
763
+ *
764
+ * Use this with `when()` to create readable conditional expressions:
765
+ *
766
+ * @example
767
+ * ```typescript
768
+ * // Show cardNumber field when paymentMethod is "card"
769
+ * when(is("paymentMethod", "card"),
770
+ * field.text("cardNumber", { label: "Card Number" }),
771
+ * )
772
+ * ```
773
+ *
774
+ * @typeParam K - The field name (inferred as string literal)
775
+ * @typeParam V - The value type (inferred as literal)
776
+ * @param field - The name of the field to check
777
+ * @param value - The value the field must equal
778
+ * @returns An EqualsPredicate for use with `when()`
779
+ * @public
780
+ */
781
+ export declare function is<const K extends string, const V>(field: K, value: V): EqualsPredicate<K, V>;
782
+
783
+ /**
784
+ * Logs validation issues to the console.
785
+ *
786
+ * @param result - The validation result to log
787
+ * @param formName - Optional name for the form (for better error messages)
788
+ *
789
+ * @public
790
+ */
791
+ export declare function logValidationIssues(result: ValidationResult, formName?: string): void;
792
+
793
+ /**
794
+ * A numeric input field.
795
+ *
796
+ * @typeParam N - The field name (string literal type)
797
+ *
798
+ * @public
799
+ */
800
+ export declare interface NumberField<N extends string> {
801
+ /** Type discriminator for form elements */
802
+ readonly _type: "field";
803
+ /** Field type discriminator - identifies this as a number field */
804
+ readonly _field: "number";
805
+ /** Unique field identifier used as the schema key */
806
+ readonly name: N;
807
+ /** Display label for the field */
808
+ readonly label?: string;
809
+ /** Minimum allowed value */
810
+ readonly min?: number;
811
+ /** Maximum allowed value */
812
+ readonly max?: number;
813
+ /** Whether this field is required for form submission */
814
+ readonly required?: boolean;
815
+ /** Value must be a multiple of this number (use 1 for integer semantics) */
816
+ readonly multipleOf?: number;
817
+ }
818
+
819
+ /**
820
+ * An object field containing nested properties.
821
+ *
822
+ * Use this for grouping related fields under a single key in the schema.
823
+ *
824
+ * @typeParam N - The field name (string literal type)
825
+ * @typeParam Properties - The form elements that define the object's properties
826
+ *
827
+ * @public
828
+ */
829
+ export declare interface ObjectField<N extends string, Properties extends readonly FormElement[]> {
830
+ /** Type discriminator for form elements */
831
+ readonly _type: "field";
832
+ /** Field type discriminator - identifies this as an object field */
833
+ readonly _field: "object";
834
+ /** Unique field identifier used as the schema key */
835
+ readonly name: N;
836
+ /** Form elements that define the properties of this object */
837
+ readonly properties: Properties;
838
+ /** Display label for the field */
839
+ readonly label?: string;
840
+ /** Whether this field is required for form submission */
841
+ readonly required?: boolean;
842
+ }
843
+
844
+ /**
845
+ * Union of all predicate types.
846
+ *
847
+ * Currently only supports equality, but can be extended with:
848
+ * - `OneOfPredicate` - field value is one of several options
849
+ * - `NotPredicate` - negation of another predicate
850
+ * - `AndPredicate` / `OrPredicate` - logical combinations
851
+ *
852
+ * @public
853
+ */
854
+ export declare type Predicate<K extends string = string, V = unknown> = EqualsPredicate<K, V>;
855
+
856
+ /**
857
+ * A field with static enum options (known at compile time).
858
+ *
859
+ * Options can be plain strings or objects with `id` and `label` properties.
860
+ *
861
+ * @typeParam N - The field name (string literal type)
862
+ * @typeParam O - Tuple of option values (strings or EnumOption objects)
863
+ *
864
+ * @public
865
+ */
866
+ export declare interface StaticEnumField<N extends string, O extends readonly EnumOptionValue[]> {
867
+ /** Type discriminator for form elements */
868
+ readonly _type: "field";
869
+ /** Field type discriminator - identifies this as an enum field */
870
+ readonly _field: "enum";
871
+ /** Unique field identifier used as the schema key */
872
+ readonly name: N;
873
+ /** Array of allowed option values */
874
+ readonly options: O;
875
+ /** Display label for the field */
876
+ readonly label?: string;
877
+ /** Whether this field is required for form submission */
878
+ readonly required?: boolean;
879
+ }
880
+
881
+ /**
882
+ * Form element type definitions.
883
+ *
884
+ * These types define the structure of form specifications.
885
+ * The structure IS the definition - nesting implies layout and conditional logic.
886
+ */
887
+ /**
888
+ * A text input field.
889
+ *
890
+ * @typeParam N - The field name (string literal type)
891
+ *
892
+ * @public
893
+ */
894
+ export declare interface TextField<N extends string> {
895
+ /** Type discriminator for form elements */
896
+ readonly _type: "field";
897
+ /** Field type discriminator - identifies this as a text field */
898
+ readonly _field: "text";
899
+ /** Unique field identifier used as the schema key */
900
+ readonly name: N;
901
+ /** Display label for the field */
902
+ readonly label?: string;
903
+ /** Placeholder text shown when field is empty */
904
+ readonly placeholder?: string;
905
+ /** Whether this field is required for form submission */
906
+ readonly required?: boolean;
907
+ /** Minimum string length */
908
+ readonly minLength?: number;
909
+ /** Maximum string length */
910
+ readonly maxLength?: number;
911
+ /** Regular expression pattern the value must match */
912
+ readonly pattern?: string;
913
+ }
914
+
915
+ /**
916
+ * Validates a form specification for common issues.
917
+ *
918
+ * Checks for:
919
+ * - Duplicate field names at the root level (warning)
920
+ * - References to non-existent fields in conditionals (error)
921
+ *
922
+ * @example
923
+ * ```typescript
924
+ * const form = formspec(
925
+ * field.text("name"),
926
+ * field.text("name"), // Duplicate!
927
+ * when("nonExistent", "value", // Reference to non-existent field!
928
+ * field.text("extra"),
929
+ * ),
930
+ * );
931
+ *
932
+ * const result = validateForm(form.elements);
933
+ * // result.valid === false
934
+ * // result.issues contains duplicate and reference errors
935
+ * ```
936
+ *
937
+ * @param elements - The form elements to validate
938
+ * @returns Validation result with any issues found
939
+ *
940
+ * @public
941
+ */
942
+ export declare function validateForm(elements: readonly FormElement[]): ValidationResult;
943
+
944
+ /**
945
+ * A validation issue found in a form specification.
946
+ *
947
+ * @public
948
+ */
949
+ export declare interface ValidationIssue {
950
+ /** Severity of the issue */
951
+ severity: ValidationSeverity;
952
+ /** Human-readable message describing the issue */
953
+ message: string;
954
+ /** Path to the element with the issue (e.g., "group.fieldName") */
955
+ path: string;
956
+ }
957
+
958
+ /**
959
+ * Result of validating a form specification.
960
+ *
961
+ * @public
962
+ */
963
+ export declare interface ValidationResult {
964
+ /** Whether the form is valid (no errors, warnings are ok) */
965
+ valid: boolean;
966
+ /** List of validation issues found */
967
+ issues: ValidationIssue[];
968
+ }
969
+
970
+ /**
971
+ * Validation issue severity levels.
972
+ *
973
+ * @public
974
+ */
975
+ export declare type ValidationSeverity = "error" | "warning";
976
+
977
+ /**
978
+ * Creates a conditional wrapper that shows elements based on a predicate.
979
+ *
980
+ * When the predicate evaluates to true, the contained elements are shown.
981
+ * Otherwise, they are hidden (but still part of the schema).
982
+ *
983
+ * @example
984
+ * ```typescript
985
+ * import { is } from "@formspec/dsl";
986
+ *
987
+ * field.enum("status", ["draft", "sent", "paid"] as const),
988
+ * when(is("status", "draft"),
989
+ * field.text("internalNotes", { label: "Internal Notes" }),
990
+ * )
991
+ * ```
992
+ *
993
+ * @param predicate - The condition to evaluate (use `is()` to create)
994
+ * @param elements - The form elements to show when condition is met
995
+ * @returns A Conditional descriptor
996
+ *
997
+ * @public
998
+ */
999
+ export declare function when<const K extends string, const V, const Elements extends readonly FormElement[]>(predicate: Predicate<K, V>, ...elements: Elements): Conditional<K, V, Elements>;
1000
+
1001
+ export { }