@formspec/dsl 0.1.0-alpha.19 → 0.1.0-alpha.21
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/dist/dsl.d.ts +44 -0
- package/dist/field.d.ts +2 -0
- package/dist/field.d.ts.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/inference.d.ts +22 -0
- package/dist/inference.d.ts.map +1 -1
- package/dist/structure.d.ts +10 -0
- package/dist/structure.d.ts.map +1 -1
- package/dist/validation.d.ts +10 -0
- package/dist/validation.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/dsl.d.ts
CHANGED
|
@@ -52,6 +52,8 @@ import type { TextField } from '@formspec/core';
|
|
|
52
52
|
* Builds a schema type from extracted fields.
|
|
53
53
|
*
|
|
54
54
|
* Maps field names to their inferred value types.
|
|
55
|
+
*
|
|
56
|
+
* @public
|
|
55
57
|
*/
|
|
56
58
|
export declare type BuildSchema<Fields> = {
|
|
57
59
|
[F in Fields as F extends {
|
|
@@ -80,6 +82,8 @@ export { EnumOptionValue }
|
|
|
80
82
|
*
|
|
81
83
|
* // ExtractConditionalFields extracts: TextField<"company"> | TextField<"taxId">
|
|
82
84
|
* ```
|
|
85
|
+
*
|
|
86
|
+
* @public
|
|
83
87
|
*/
|
|
84
88
|
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;
|
|
85
89
|
|
|
@@ -95,6 +99,8 @@ export declare type ExtractConditionalFields<E> = E extends AnyField ? never : E
|
|
|
95
99
|
* type Fields = ExtractConditionalFieldsFromArray<Elements>;
|
|
96
100
|
* // TextField<"company">
|
|
97
101
|
* ```
|
|
102
|
+
*
|
|
103
|
+
* @public
|
|
98
104
|
*/
|
|
99
105
|
export declare type ExtractConditionalFieldsFromArray<Elements> = Elements extends readonly [
|
|
100
106
|
infer First,
|
|
@@ -107,6 +113,8 @@ infer First,
|
|
|
107
113
|
* - Field elements return themselves
|
|
108
114
|
* - Groups extract fields from all child elements
|
|
109
115
|
* - Conditionals extract fields from all child elements
|
|
116
|
+
*
|
|
117
|
+
* @public
|
|
110
118
|
*/
|
|
111
119
|
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;
|
|
112
120
|
|
|
@@ -114,6 +122,8 @@ export declare type ExtractFields<E> = E extends AnyField ? E : E extends Group<
|
|
|
114
122
|
* Extracts fields from an array of elements.
|
|
115
123
|
*
|
|
116
124
|
* Recursively processes each element and unions the results.
|
|
125
|
+
*
|
|
126
|
+
* @public
|
|
117
127
|
*/
|
|
118
128
|
export declare type ExtractFieldsFromArray<Elements> = Elements extends readonly [
|
|
119
129
|
infer First,
|
|
@@ -137,6 +147,8 @@ infer First,
|
|
|
137
147
|
*
|
|
138
148
|
* // ExtractNonConditionalFields extracts: TextField<"name"> | NumberField<"age">
|
|
139
149
|
* ```
|
|
150
|
+
*
|
|
151
|
+
* @public
|
|
140
152
|
*/
|
|
141
153
|
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;
|
|
142
154
|
|
|
@@ -149,6 +161,8 @@ export declare type ExtractNonConditionalFields<E> = E extends AnyField ? E : E
|
|
|
149
161
|
* type Fields = ExtractNonConditionalFieldsFromArray<Elements>;
|
|
150
162
|
* // TextField<"name"> | NumberField<"age">
|
|
151
163
|
* ```
|
|
164
|
+
*
|
|
165
|
+
* @public
|
|
152
166
|
*/
|
|
153
167
|
export declare type ExtractNonConditionalFieldsFromArray<Elements> = Elements extends readonly [
|
|
154
168
|
infer First,
|
|
@@ -167,6 +181,8 @@ infer First,
|
|
|
167
181
|
* field.enum("status", ["draft", "sent", "paid"]);
|
|
168
182
|
* field.dynamicEnum("country", "countries", { label: "Country" });
|
|
169
183
|
* ```
|
|
184
|
+
*
|
|
185
|
+
* @public
|
|
170
186
|
*/
|
|
171
187
|
export declare const field: {
|
|
172
188
|
/**
|
|
@@ -343,6 +359,8 @@ export declare const field: {
|
|
|
343
359
|
* type Clean = FlattenIntersection<{ a: string } & { b: number }>;
|
|
344
360
|
* // Displays as: { a: string; b: number }
|
|
345
361
|
* ```
|
|
362
|
+
*
|
|
363
|
+
* @public
|
|
346
364
|
*/
|
|
347
365
|
export declare type FlattenIntersection<T> = {
|
|
348
366
|
[K in keyof T]: T[K];
|
|
@@ -378,11 +396,15 @@ export declare type FlattenIntersection<T> = {
|
|
|
378
396
|
*
|
|
379
397
|
* @param elements - The top-level form elements
|
|
380
398
|
* @returns A FormSpec descriptor
|
|
399
|
+
*
|
|
400
|
+
* @public
|
|
381
401
|
*/
|
|
382
402
|
export declare function formspec<const Elements extends readonly FormElement[]>(...elements: Elements): FormSpec<Elements>;
|
|
383
403
|
|
|
384
404
|
/**
|
|
385
405
|
* Options for creating a form specification.
|
|
406
|
+
*
|
|
407
|
+
* @public
|
|
386
408
|
*/
|
|
387
409
|
export declare interface FormSpecOptions {
|
|
388
410
|
/**
|
|
@@ -418,6 +440,8 @@ export declare interface FormSpecOptions {
|
|
|
418
440
|
* @param options - Validation options
|
|
419
441
|
* @param elements - The top-level form elements
|
|
420
442
|
* @returns A FormSpec descriptor
|
|
443
|
+
*
|
|
444
|
+
* @public
|
|
421
445
|
*/
|
|
422
446
|
export declare function formspecWithValidation<const Elements extends readonly FormElement[]>(options: FormSpecOptions, ...elements: Elements): FormSpec<Elements>;
|
|
423
447
|
|
|
@@ -438,6 +462,8 @@ export declare function formspecWithValidation<const Elements extends readonly F
|
|
|
438
462
|
* @param label - The group's display label
|
|
439
463
|
* @param elements - The form elements contained in this group
|
|
440
464
|
* @returns A Group descriptor
|
|
465
|
+
*
|
|
466
|
+
* @public
|
|
441
467
|
*/
|
|
442
468
|
export declare function group<const Elements extends readonly FormElement[]>(label: string, ...elements: Elements): Group<Elements>;
|
|
443
469
|
|
|
@@ -466,6 +492,8 @@ export declare function group<const Elements extends readonly FormElement[]>(lab
|
|
|
466
492
|
* type T4 = InferFieldValue<ArrayField<"items", [TextField<"name">]>>; // { name: string }[]
|
|
467
493
|
* type T5 = InferFieldValue<ObjectField<"address", [TextField<"city">]>>; // { city: string }
|
|
468
494
|
* ```
|
|
495
|
+
*
|
|
496
|
+
* @public
|
|
469
497
|
*/
|
|
470
498
|
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;
|
|
471
499
|
|
|
@@ -479,6 +507,8 @@ export declare type InferFieldValue<F> = F extends TextField<string> ? string :
|
|
|
479
507
|
* const form = formspec(...);
|
|
480
508
|
* type Schema = InferFormSchema<typeof form>;
|
|
481
509
|
* ```
|
|
510
|
+
*
|
|
511
|
+
* @public
|
|
482
512
|
*/
|
|
483
513
|
export declare type InferFormSchema<F extends FormSpec<readonly FormElement[]>> = F extends FormSpec<infer Elements> ? InferSchema<Elements> : never;
|
|
484
514
|
|
|
@@ -509,6 +539,8 @@ export declare type InferFormSchema<F extends FormSpec<readonly FormElement[]>>
|
|
|
509
539
|
* type ConditionalSchema = InferSchema<typeof formWithConditional.elements>;
|
|
510
540
|
* // { type: "a" | "b"; aField?: string }
|
|
511
541
|
* ```
|
|
542
|
+
*
|
|
543
|
+
* @public
|
|
512
544
|
*/
|
|
513
545
|
export declare type InferSchema<Elements extends readonly FormElement[]> = FlattenIntersection<BuildSchema<ExtractNonConditionalFieldsFromArray<Elements>> & Partial<BuildSchema<ExtractConditionalFieldsFromArray<Elements>>>>;
|
|
514
546
|
|
|
@@ -539,6 +571,8 @@ export declare function is<const K extends string, const V>(field: K, value: V):
|
|
|
539
571
|
*
|
|
540
572
|
* @param result - The validation result to log
|
|
541
573
|
* @param formName - Optional name for the form (for better error messages)
|
|
574
|
+
*
|
|
575
|
+
* @public
|
|
542
576
|
*/
|
|
543
577
|
export declare function logValidationIssues(result: ValidationResult, formName?: string): void;
|
|
544
578
|
|
|
@@ -566,11 +600,15 @@ export declare function logValidationIssues(result: ValidationResult, formName?:
|
|
|
566
600
|
*
|
|
567
601
|
* @param elements - The form elements to validate
|
|
568
602
|
* @returns Validation result with any issues found
|
|
603
|
+
*
|
|
604
|
+
* @public
|
|
569
605
|
*/
|
|
570
606
|
export declare function validateForm(elements: readonly FormElement[]): ValidationResult;
|
|
571
607
|
|
|
572
608
|
/**
|
|
573
609
|
* A validation issue found in a form specification.
|
|
610
|
+
*
|
|
611
|
+
* @public
|
|
574
612
|
*/
|
|
575
613
|
export declare interface ValidationIssue {
|
|
576
614
|
/** Severity of the issue */
|
|
@@ -583,6 +621,8 @@ export declare interface ValidationIssue {
|
|
|
583
621
|
|
|
584
622
|
/**
|
|
585
623
|
* Result of validating a form specification.
|
|
624
|
+
*
|
|
625
|
+
* @public
|
|
586
626
|
*/
|
|
587
627
|
export declare interface ValidationResult {
|
|
588
628
|
/** Whether the form is valid (no errors, warnings are ok) */
|
|
@@ -593,6 +633,8 @@ export declare interface ValidationResult {
|
|
|
593
633
|
|
|
594
634
|
/**
|
|
595
635
|
* Validation issue severity levels.
|
|
636
|
+
*
|
|
637
|
+
* @public
|
|
596
638
|
*/
|
|
597
639
|
export declare type ValidationSeverity = "error" | "warning";
|
|
598
640
|
|
|
@@ -615,6 +657,8 @@ export declare type ValidationSeverity = "error" | "warning";
|
|
|
615
657
|
* @param predicate - The condition to evaluate (use `is()` to create)
|
|
616
658
|
* @param elements - The form elements to show when condition is met
|
|
617
659
|
* @returns A Conditional descriptor
|
|
660
|
+
*
|
|
661
|
+
* @public
|
|
618
662
|
*/
|
|
619
663
|
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>;
|
|
620
664
|
|
package/dist/field.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ import type { TextField, NumberField, BooleanField, StaticEnumField, EnumOptionV
|
|
|
17
17
|
* field.enum("status", ["draft", "sent", "paid"]);
|
|
18
18
|
* field.dynamicEnum("country", "countries", { label: "Country" });
|
|
19
19
|
* ```
|
|
20
|
+
*
|
|
21
|
+
* @public
|
|
20
22
|
*/
|
|
21
23
|
export declare const field: {
|
|
22
24
|
/**
|
package/dist/field.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../src/field.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACZ,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,UAAU,EACV,WAAW,EACX,WAAW,EACZ,MAAM,gBAAgB,CAAC;AAExB
|
|
1
|
+
{"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../src/field.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACZ,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,UAAU,EACV,WAAW,EACX,WAAW,EACZ,MAAM,gBAAgB,CAAC;AAExB;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,KAAK;IAChB;;;;;;OAMG;iBACU,CAAC,SAAS,MAAM,QACrB,CAAC,WACE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC,KACvD,SAAS,CAAC,CAAC,CAAC;IAOf;;;;;;OAMG;mBACY,CAAC,SAAS,MAAM,QACvB,CAAC,WACE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC,KACzD,WAAW,CAAC,CAAC,CAAC;IAOjB;;;;;;OAMG;oBACa,CAAC,SAAS,MAAM,QACxB,CAAC,WACE,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC,KAC1D,YAAY,CAAC,CAAC,CAAC;IAOlB;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;iBACU,CAAC,SAAS,MAAM,QAAQ,CAAC,SAAS,SAAS,eAAe,EAAE,QACjE,CAAC,WACE,CAAC,WACD,IAAI,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC,KAC5E,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC;IA2CxB;;;;;;;;;;;;;;;;;;OAkBG;wBACiB,CAAC,SAAS,MAAM,QAAQ,MAAM,SAAS,MAAM,QACzD,CAAC,UACC,MAAM,WACL,IAAI,CAAC,gBAAgB,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAC,KACjF,gBAAgB,CAAC,CAAC,EAAE,MAAM,CAAC;IAQ9B;;;;;;;OAOG;0BACmB,CAAC,SAAS,MAAM,QAC9B,CAAC,gBACO,MAAM,WACX,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,cAAc,CAAC,KACjF,kBAAkB,CAAC,CAAC,CAAC;IAQxB;;;;;;;;;;;;;;;;;OAiBG;kBACW,CAAC,SAAS,MAAM,QAAQ,KAAK,SAAS,SAAS,WAAW,EAAE,QAClE,CAAC,YACG,KAAK,KACd,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC;IAOvB;;;;;;;;;;;;;;;;;;;OAmBG;4BACqB,CAAC,SAAS,MAAM,QAAQ,KAAK,SAAS,SAAS,WAAW,EAAE,QAC5E,CAAC,UACC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC,YAC/D,KAAK,KACd,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC;IAQvB;;;;;;;;;;;;;;;;;OAiBG;mBACY,CAAC,SAAS,MAAM,QAAQ,UAAU,SAAS,SAAS,WAAW,EAAE,QACxE,CAAC,iBACQ,UAAU,KACxB,WAAW,CAAC,CAAC,EAAE,UAAU,CAAC;IAO7B;;;;;;;;;;;;;;;OAeG;6BACsB,CAAC,SAAS,MAAM,QAAQ,UAAU,SAAS,SAAS,WAAW,EAAE,QAClF,CAAC,UACC,IAAI,CAAC,WAAW,CAAC,CAAC,EAAE,UAAU,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,YAAY,CAAC,iBACrE,UAAU,KACxB,WAAW,CAAC,CAAC,EAAE,UAAU,CAAC;CAO9B,CAAC"}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/field.ts","../src/predicate.ts","../src/validation.ts","../src/structure.ts"],"sourcesContent":["/**\n * `@formspec/dsl` - DSL functions for defining FormSpec forms\n *\n * This package provides the builder functions for creating form specifications:\n * - `field.*` - Field builders (text, number, boolean, enum, dynamicEnum)\n * - `group()` - Visual grouping\n * - `when()` + `is()` - Conditional visibility\n * - `formspec()` - Top-level form definition\n *\n * @example\n * ```typescript\n * import { formspec, field, group, when, is } from \"@formspec/dsl\";\n *\n * const InvoiceForm = formspec(\n * group(\"Customer\",\n * field.text(\"customerName\", { label: \"Customer Name\" }),\n * field.dynamicEnum(\"country\", \"fetch_countries\", { label: \"Country\" }),\n * ),\n * group(\"Invoice Details\",\n * field.number(\"amount\", { label: \"Amount\", min: 0 }),\n * field.enum(\"status\", [\"draft\", \"sent\", \"paid\"] as const),\n * when(is(\"status\", \"draft\"),\n * field.text(\"internalNotes\", { label: \"Internal Notes\" }),\n * ),\n * ),\n * );\n * ```\n *\n * @packageDocumentation\n */\n\n// Field builders\nexport { field } from \"./field.js\";\n\n// Predicate builders\nexport { is } from \"./predicate.js\";\n\n// Structure builders\nexport { group, when, formspec, formspecWithValidation } from \"./structure.js\";\nexport type { FormSpecOptions } from \"./structure.js\";\n\n// Validation\nexport { validateForm, logValidationIssues } from \"./validation.js\";\nexport type { ValidationSeverity, ValidationIssue, ValidationResult } from \"./validation.js\";\n\n// Type inference utilities\nexport type {\n InferFieldValue,\n ExtractFields,\n ExtractFieldsFromArray,\n ExtractNonConditionalFields,\n ExtractNonConditionalFieldsFromArray,\n ExtractConditionalFields,\n ExtractConditionalFieldsFromArray,\n BuildSchema,\n FlattenIntersection,\n InferSchema,\n InferFormSchema,\n} from \"./inference.js\";\n\n// Re-export enum option types from core for convenience\nexport type { EnumOption, EnumOptionValue } from \"@formspec/core\";\n","/**\n * Field builder functions for creating form field definitions.\n *\n * Each function creates a field descriptor that captures both schema information\n * (name, type) and UI hints (label, placeholder, etc.).\n */\n\nimport type {\n TextField,\n NumberField,\n BooleanField,\n StaticEnumField,\n EnumOptionValue,\n DynamicEnumField,\n DynamicSchemaField,\n ArrayField,\n ObjectField,\n FormElement,\n} from \"@formspec/core\";\n\n/**\n * Field builder namespace containing functions to create each field type.\n *\n * @example\n * ```typescript\n * import { field } from \"@formspec/dsl\";\n *\n * field.text(\"name\", { label: \"Full Name\" });\n * field.number(\"age\", { min: 0, max: 150 });\n * field.enum(\"status\", [\"draft\", \"sent\", \"paid\"]);\n * field.dynamicEnum(\"country\", \"countries\", { label: \"Country\" });\n * ```\n */\nexport const field = {\n /**\n * Creates a text input field.\n *\n * @param name - The field name (used as the schema key)\n * @param config - Optional configuration for label, placeholder, etc.\n * @returns A TextField descriptor\n */\n text: <const N extends string>(\n name: N,\n config?: Omit<TextField<N>, \"_type\" | \"_field\" | \"name\">\n ): TextField<N> => ({\n _type: \"field\",\n _field: \"text\",\n name,\n ...config,\n }),\n\n /**\n * Creates a numeric input field.\n *\n * @param name - The field name (used as the schema key)\n * @param config - Optional configuration for label, min, max, etc.\n * @returns A NumberField descriptor\n */\n number: <const N extends string>(\n name: N,\n config?: Omit<NumberField<N>, \"_type\" | \"_field\" | \"name\">\n ): NumberField<N> => ({\n _type: \"field\",\n _field: \"number\",\n name,\n ...config,\n }),\n\n /**\n * Creates a boolean checkbox field.\n *\n * @param name - The field name (used as the schema key)\n * @param config - Optional configuration for label, etc.\n * @returns A BooleanField descriptor\n */\n boolean: <const N extends string>(\n name: N,\n config?: Omit<BooleanField<N>, \"_type\" | \"_field\" | \"name\">\n ): BooleanField<N> => ({\n _type: \"field\",\n _field: \"boolean\",\n name,\n ...config,\n }),\n\n /**\n * Creates a field with static enum options (known at compile time).\n *\n * Literal types are automatically inferred - no `as const` needed:\n * ```typescript\n * field.enum(\"status\", [\"draft\", \"sent\", \"paid\"])\n * // Schema type: \"draft\" | \"sent\" | \"paid\"\n * ```\n *\n * Options can be strings or objects with `id` and `label`:\n * ```typescript\n * field.enum(\"priority\", [\n * { id: \"low\", label: \"Low Priority\" },\n * { id: \"high\", label: \"High Priority\" },\n * ])\n * ```\n *\n * **Note:** All options must be of the same type (all strings OR all objects).\n * Mixing strings and objects will throw a runtime error.\n *\n * @param name - The field name (used as the schema key)\n * @param options - Array of allowed string values or objects with `id` and `label` properties\n * @param config - Optional configuration for label, etc.\n * @returns A StaticEnumField descriptor\n * @throws Error if options array contains mixed types (strings and objects)\n */\n enum: <const N extends string, const O extends readonly EnumOptionValue[]>(\n name: N,\n options: O,\n config?: Omit<StaticEnumField<N, O>, \"_type\" | \"_field\" | \"name\" | \"options\">\n ): StaticEnumField<N, O> => {\n // Validate that all options are of the same type (all strings or all objects)\n if (options.length > 0) {\n const first = options[0];\n // Runtime check: TypeScript allows mixed arrays, but we enforce homogeneity\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const firstIsObject = typeof first === \"object\" && first !== null;\n\n // Check all items match the type of the first item\n for (const opt of options) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const optIsObject = typeof opt === \"object\" && opt !== null;\n if (optIsObject !== firstIsObject) {\n throw new Error(\n `field.enum(\"${name}\"): options must be all strings or all objects with {id, label}, not mixed. ` +\n `Received mixed types in options array.`\n );\n }\n }\n\n // Validate object options have required properties\n if (firstIsObject) {\n for (const opt of options) {\n const obj = opt as { id?: unknown; label?: unknown };\n if (typeof obj.id !== \"string\" || typeof obj.label !== \"string\") {\n throw new Error(\n `field.enum(\"${name}\"): object options must have string \"id\" and \"label\" properties. ` +\n `Received: ${JSON.stringify(opt)}`\n );\n }\n }\n }\n }\n\n return {\n _type: \"field\",\n _field: \"enum\",\n name,\n options,\n ...config,\n };\n },\n\n /**\n * Creates a field with dynamic enum options (fetched from a data source at runtime).\n *\n * The data source must be registered in DataSourceRegistry via module augmentation:\n * ```typescript\n * declare module \"@formspec/core\" {\n * interface DataSourceRegistry {\n * countries: { id: string; code: string; name: string };\n * }\n * }\n *\n * field.dynamicEnum(\"country\", \"countries\", { label: \"Country\" })\n * ```\n *\n * @param name - The field name (used as the schema key)\n * @param source - The data source key (must be in DataSourceRegistry)\n * @param config - Optional configuration for label, params, etc.\n * @returns A DynamicEnumField descriptor\n */\n dynamicEnum: <const N extends string, const Source extends string>(\n name: N,\n source: Source,\n config?: Omit<DynamicEnumField<N, Source>, \"_type\" | \"_field\" | \"name\" | \"source\">\n ): DynamicEnumField<N, Source> => ({\n _type: \"field\",\n _field: \"dynamic_enum\",\n name,\n source,\n ...config,\n }),\n\n /**\n * Creates a field that loads its schema dynamically (e.g., from an extension).\n *\n * @param name - The field name (used as the schema key)\n * @param schemaSource - Identifier for the schema source\n * @param config - Optional configuration for label, etc.\n * @returns A DynamicSchemaField descriptor\n */\n dynamicSchema: <const N extends string>(\n name: N,\n schemaSource: string,\n config?: Omit<DynamicSchemaField<N>, \"_type\" | \"_field\" | \"name\" | \"schemaSource\">\n ): DynamicSchemaField<N> => ({\n _type: \"field\",\n _field: \"dynamic_schema\",\n name,\n schemaSource,\n ...config,\n }),\n\n /**\n * Creates an array field containing repeating items.\n *\n * Use this for lists of values (e.g., multiple addresses, line items).\n *\n * @example\n * ```typescript\n * field.array(\"addresses\",\n * field.text(\"street\", { label: \"Street\" }),\n * field.text(\"city\", { label: \"City\" }),\n * field.text(\"zip\", { label: \"ZIP Code\" }),\n * )\n * ```\n *\n * @param name - The field name (used as the schema key)\n * @param items - The form elements that define each array item\n * @returns An ArrayField descriptor\n */\n array: <const N extends string, const Items extends readonly FormElement[]>(\n name: N,\n ...items: Items\n ): ArrayField<N, Items> => ({\n _type: \"field\",\n _field: \"array\",\n name,\n items,\n }),\n\n /**\n * Creates an array field with additional configuration options.\n *\n * @example\n * ```typescript\n * field.arrayWithConfig(\"lineItems\", {\n * label: \"Line Items\",\n * minItems: 1,\n * maxItems: 10,\n * },\n * field.text(\"description\"),\n * field.number(\"quantity\"),\n * )\n * ```\n *\n * @param name - The field name (used as the schema key)\n * @param config - Configuration for label, minItems, maxItems, etc.\n * @param items - The form elements that define each array item\n * @returns An ArrayField descriptor\n */\n arrayWithConfig: <const N extends string, const Items extends readonly FormElement[]>(\n name: N,\n config: Omit<ArrayField<N, Items>, \"_type\" | \"_field\" | \"name\" | \"items\">,\n ...items: Items\n ): ArrayField<N, Items> => ({\n _type: \"field\",\n _field: \"array\",\n name,\n items,\n ...config,\n }),\n\n /**\n * Creates an object field containing nested properties.\n *\n * Use this for grouping related fields under a single key in the schema.\n *\n * @example\n * ```typescript\n * field.object(\"address\",\n * field.text(\"street\", { label: \"Street\" }),\n * field.text(\"city\", { label: \"City\" }),\n * field.text(\"zip\", { label: \"ZIP Code\" }),\n * )\n * ```\n *\n * @param name - The field name (used as the schema key)\n * @param properties - The form elements that define the object's properties\n * @returns An ObjectField descriptor\n */\n object: <const N extends string, const Properties extends readonly FormElement[]>(\n name: N,\n ...properties: Properties\n ): ObjectField<N, Properties> => ({\n _type: \"field\",\n _field: \"object\",\n name,\n properties,\n }),\n\n /**\n * Creates an object field with additional configuration options.\n *\n * @example\n * ```typescript\n * field.objectWithConfig(\"billingAddress\", { label: \"Billing Address\", required: true },\n * field.text(\"street\"),\n * field.text(\"city\"),\n * )\n * ```\n *\n * @param name - The field name (used as the schema key)\n * @param config - Configuration for label, required, etc.\n * @param properties - The form elements that define the object's properties\n * @returns An ObjectField descriptor\n */\n objectWithConfig: <const N extends string, const Properties extends readonly FormElement[]>(\n name: N,\n config: Omit<ObjectField<N, Properties>, \"_type\" | \"_field\" | \"name\" | \"properties\">,\n ...properties: Properties\n ): ObjectField<N, Properties> => ({\n _type: \"field\",\n _field: \"object\",\n name,\n properties,\n ...config,\n }),\n};\n","/**\n * Predicate builder functions for conditional logic.\n *\n * These functions create predicates for use with `when()`:\n * - `is()` - Check if a field equals a specific value\n *\n * @example\n * ```typescript\n * when(is(\"status\", \"draft\"),\n * field.text(\"notes\"),\n * )\n * ```\n */\n\nimport type { EqualsPredicate } from \"@formspec/core\";\n\n/**\n * Creates an equality predicate that checks if a field equals a specific value.\n *\n * Use this with `when()` to create readable conditional expressions:\n *\n * @example\n * ```typescript\n * // Show cardNumber field when paymentMethod is \"card\"\n * when(is(\"paymentMethod\", \"card\"),\n * field.text(\"cardNumber\", { label: \"Card Number\" }),\n * )\n * ```\n *\n * @typeParam K - The field name (inferred as string literal)\n * @typeParam V - The value type (inferred as literal)\n * @param field - The name of the field to check\n * @param value - The value the field must equal\n * @returns An EqualsPredicate for use with `when()`\n * @public\n */\nexport function is<const K extends string, const V>(field: K, value: V): EqualsPredicate<K, V> {\n return {\n _predicate: \"equals\",\n field,\n value,\n };\n}\n","/**\n * Runtime validation for form specifications.\n *\n * Validates:\n * - No duplicate field names at the same scope level\n * - All field references in conditionals point to existing fields\n */\n\nimport type { FormElement, Group, Conditional, ArrayField, ObjectField } from \"@formspec/core\";\n\n/**\n * Validation issue severity levels.\n */\nexport type ValidationSeverity = \"error\" | \"warning\";\n\n/**\n * A validation issue found in a form specification.\n */\nexport interface ValidationIssue {\n /** Severity of the issue */\n severity: ValidationSeverity;\n /** Human-readable message describing the issue */\n message: string;\n /** Path to the element with the issue (e.g., \"group.fieldName\") */\n path: string;\n}\n\n/**\n * Result of validating a form specification.\n */\nexport interface ValidationResult {\n /** Whether the form is valid (no errors, warnings are ok) */\n valid: boolean;\n /** List of validation issues found */\n issues: ValidationIssue[];\n}\n\n/**\n * Collects all field names from a list of form elements.\n * Returns a Map of field name to count (for duplicate detection).\n */\nfunction collectFieldNames(\n elements: readonly FormElement[],\n path = \"\"\n): Map<string, { count: number; paths: string[] }> {\n const fieldNames = new Map<string, { count: number; paths: string[] }>();\n\n function visit(elements: readonly FormElement[], currentPath: string): void {\n for (const element of elements) {\n switch (element._type) {\n case \"field\": {\n // After type narrowing, element is known to be AnyField\n const field = element;\n const fieldPath = currentPath ? `${currentPath}.${field.name}` : field.name;\n const existing = fieldNames.get(field.name);\n if (existing !== undefined) {\n existing.count++;\n existing.paths.push(fieldPath);\n } else {\n fieldNames.set(field.name, { count: 1, paths: [fieldPath] });\n }\n\n // Recurse into array items and object properties\n if (field._field === \"array\") {\n const arrayField = field as ArrayField<string, readonly FormElement[]>;\n visit(arrayField.items, `${fieldPath}[]`);\n } else if (field._field === \"object\") {\n const objectField = field as ObjectField<string, readonly FormElement[]>;\n visit(objectField.properties, fieldPath);\n }\n break;\n }\n\n case \"group\": {\n const group = element as Group<readonly FormElement[]>;\n const groupPath = currentPath ? `${currentPath}.[${group.label}]` : `[${group.label}]`;\n visit(group.elements, groupPath);\n break;\n }\n\n case \"conditional\": {\n const conditional = element as Conditional<string, unknown, readonly FormElement[]>;\n const conditionalPath = currentPath\n ? `${currentPath}.when(${conditional.field})`\n : `when(${conditional.field})`;\n visit(conditional.elements, conditionalPath);\n break;\n }\n }\n }\n }\n\n visit(elements, path);\n return fieldNames;\n}\n\n/**\n * Collects all field references from conditionals.\n * Returns a list of { fieldName, path } for each reference.\n */\nfunction collectConditionalReferences(\n elements: readonly FormElement[],\n path = \"\"\n): { fieldName: string; path: string }[] {\n const references: { fieldName: string; path: string }[] = [];\n\n function visit(elements: readonly FormElement[], currentPath: string): void {\n for (const element of elements) {\n switch (element._type) {\n case \"field\": {\n // After type narrowing, element is known to be AnyField\n const field = element;\n const fieldPath = currentPath ? `${currentPath}.${field.name}` : field.name;\n\n // Recurse into array items and object properties\n if (field._field === \"array\") {\n const arrayField = field as ArrayField<string, readonly FormElement[]>;\n visit(arrayField.items, `${fieldPath}[]`);\n } else if (field._field === \"object\") {\n const objectField = field as ObjectField<string, readonly FormElement[]>;\n visit(objectField.properties, fieldPath);\n }\n break;\n }\n\n case \"group\": {\n const group = element as Group<readonly FormElement[]>;\n const groupPath = currentPath ? `${currentPath}.[${group.label}]` : `[${group.label}]`;\n visit(group.elements, groupPath);\n break;\n }\n\n case \"conditional\": {\n const conditional = element as Conditional<string, unknown, readonly FormElement[]>;\n const conditionalPath = currentPath\n ? `${currentPath}.when(${conditional.field})`\n : `when(${conditional.field})`;\n\n // Record this reference\n references.push({\n fieldName: conditional.field,\n path: conditionalPath,\n });\n\n // Continue visiting children\n visit(conditional.elements, conditionalPath);\n break;\n }\n }\n }\n }\n\n visit(elements, path);\n return references;\n}\n\n/**\n * Validates a form specification for common issues.\n *\n * Checks for:\n * - Duplicate field names at the root level (warning)\n * - References to non-existent fields in conditionals (error)\n *\n * @example\n * ```typescript\n * const form = formspec(\n * field.text(\"name\"),\n * field.text(\"name\"), // Duplicate!\n * when(\"nonExistent\", \"value\", // Reference to non-existent field!\n * field.text(\"extra\"),\n * ),\n * );\n *\n * const result = validateForm(form.elements);\n * // result.valid === false\n * // result.issues contains duplicate and reference errors\n * ```\n *\n * @param elements - The form elements to validate\n * @returns Validation result with any issues found\n */\nexport function validateForm(elements: readonly FormElement[]): ValidationResult {\n const issues: ValidationIssue[] = [];\n\n // Collect all field names\n const fieldNames = collectFieldNames(elements);\n\n // Check for duplicates at root level - duplicates are errors because they cause data loss\n for (const [name, info] of fieldNames) {\n if (info.count > 1 && info.paths[0] !== undefined) {\n issues.push({\n severity: \"error\",\n message: `Duplicate field name \"${name}\" found ${String(info.count)} times at: ${info.paths.join(\", \")}`,\n path: info.paths[0],\n });\n }\n }\n\n // Collect conditional references\n const references = collectConditionalReferences(elements);\n\n // Check that all referenced fields exist\n for (const ref of references) {\n if (!fieldNames.has(ref.fieldName)) {\n issues.push({\n severity: \"error\",\n message: `Conditional references non-existent field \"${ref.fieldName}\"`,\n path: ref.path,\n });\n }\n }\n\n return {\n valid: issues.every((issue) => issue.severity !== \"error\"),\n issues,\n };\n}\n\n/**\n * Logs validation issues to the console.\n *\n * @param result - The validation result to log\n * @param formName - Optional name for the form (for better error messages)\n */\nexport function logValidationIssues(result: ValidationResult, formName?: string): void {\n if (result.issues.length === 0) {\n return;\n }\n\n const prefix = formName ? `FormSpec \"${formName}\"` : \"FormSpec\";\n\n for (const issue of result.issues) {\n const location = issue.path ? ` at ${issue.path}` : \"\";\n const message = `${prefix}: ${issue.message}${location}`;\n\n if (issue.severity === \"error\") {\n console.error(message);\n } else {\n console.warn(message);\n }\n }\n}\n","/**\n * Structure builder functions for organizing form elements.\n *\n * These functions create layout and conditional structures:\n * - `group()` - Visual grouping of fields\n * - `when()` - Conditional visibility based on field values\n * - `formspec()` - Top-level form specification\n */\n\nimport type { FormElement, Group, Conditional, FormSpec, Predicate } from \"@formspec/core\";\nimport { validateForm, logValidationIssues } from \"./validation.js\";\n\n/**\n * Options for creating a form specification.\n */\nexport interface FormSpecOptions {\n /**\n * Whether to validate the form structure.\n * - `true` or `\"warn\"`: Validate and log warnings/errors to console\n * - `\"throw\"`: Validate and throw an error if validation fails\n * - `false`: Skip validation (default in production for performance)\n *\n * @defaultValue false\n */\n validate?: boolean | \"warn\" | \"throw\";\n\n /**\n * Optional name for the form (used in validation messages).\n */\n name?: string;\n}\n\n/**\n * Creates a visual group of form elements.\n *\n * Groups provide visual organization and can be rendered as fieldsets or sections.\n * The nesting of groups defines the visual hierarchy of the form.\n *\n * @example\n * ```typescript\n * group(\"Customer Information\",\n * field.text(\"name\", { label: \"Name\" }),\n * field.text(\"email\", { label: \"Email\" }),\n * )\n * ```\n *\n * @param label - The group's display label\n * @param elements - The form elements contained in this group\n * @returns A Group descriptor\n */\nexport function group<const Elements extends readonly FormElement[]>(\n label: string,\n ...elements: Elements\n): Group<Elements> {\n return { _type: \"group\", label, elements };\n}\n\n/**\n * Creates a conditional wrapper that shows elements based on a predicate.\n *\n * When the predicate evaluates to true, the contained elements are shown.\n * Otherwise, they are hidden (but still part of the schema).\n *\n * @example\n * ```typescript\n * import { is } from \"@formspec/dsl\";\n *\n * field.enum(\"status\", [\"draft\", \"sent\", \"paid\"] as const),\n * when(is(\"status\", \"draft\"),\n * field.text(\"internalNotes\", { label: \"Internal Notes\" }),\n * )\n * ```\n *\n * @param predicate - The condition to evaluate (use `is()` to create)\n * @param elements - The form elements to show when condition is met\n * @returns A Conditional descriptor\n */\nexport function when<\n const K extends string,\n const V,\n const Elements extends readonly FormElement[],\n>(predicate: Predicate<K, V>, ...elements: Elements): Conditional<K, V, Elements> {\n return {\n _type: \"conditional\",\n field: predicate.field,\n value: predicate.value,\n elements,\n };\n}\n\n/**\n * Creates a complete form specification.\n *\n * The structure IS the definition:\n * - Nesting with `group()` defines visual layout\n * - Nesting with `when()` defines conditional visibility\n * - Field type implies control type (text field → text input)\n * - Array position implies field ordering\n *\n * Schema is automatically inferred from all fields in the structure.\n *\n * @example\n * ```typescript\n * const InvoiceForm = formspec(\n * group(\"Customer\",\n * field.text(\"customerName\", { label: \"Customer Name\" }),\n * field.dynamicEnum(\"country\", \"fetch_countries\", { label: \"Country\" }),\n * ),\n * group(\"Invoice Details\",\n * field.number(\"amount\", { label: \"Amount\", min: 0 }),\n * field.enum(\"status\", [\"draft\", \"sent\", \"paid\"] as const),\n * when(is(\"status\", \"draft\"),\n * field.text(\"internalNotes\", { label: \"Internal Notes\" }),\n * ),\n * ),\n * );\n * ```\n *\n * @param elements - The top-level form elements\n * @returns A FormSpec descriptor\n */\nexport function formspec<const Elements extends readonly FormElement[]>(\n ...elements: Elements\n): FormSpec<Elements> {\n return { elements };\n}\n\n/**\n * Creates a complete form specification with validation options.\n *\n * @example\n * ```typescript\n * const form = formspecWithValidation(\n * { validate: true, name: \"MyForm\" },\n * field.text(\"name\"),\n * field.enum(\"status\", [\"draft\", \"sent\"] as const),\n * when(is(\"status\", \"draft\"),\n * field.text(\"notes\"),\n * ),\n * );\n * ```\n *\n * @param options - Validation options\n * @param elements - The top-level form elements\n * @returns A FormSpec descriptor\n */\nexport function formspecWithValidation<const Elements extends readonly FormElement[]>(\n options: FormSpecOptions,\n ...elements: Elements\n): FormSpec<Elements> {\n // Run validation if requested\n if (options.validate) {\n const result = validateForm(elements);\n\n if (options.validate === \"throw\" && !result.valid) {\n const errors = result.issues\n .filter((i) => i.severity === \"error\")\n .map((i) => i.message)\n .join(\"; \");\n throw new Error(`Form validation failed: ${errors}`);\n }\n\n if (options.validate === true || options.validate === \"warn\") {\n logValidationIssues(result, options.name);\n }\n }\n\n return { elements };\n}\n"],"mappings":"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","names":["field","elements","field","group"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/field.ts","../src/predicate.ts","../src/validation.ts","../src/structure.ts"],"sourcesContent":["/**\n * `@formspec/dsl` - DSL functions for defining FormSpec forms\n *\n * This package provides the builder functions for creating form specifications:\n * - `field.*` - Field builders (text, number, boolean, enum, dynamicEnum)\n * - `group()` - Visual grouping\n * - `when()` + `is()` - Conditional visibility\n * - `formspec()` - Top-level form definition\n *\n * @example\n * ```typescript\n * import { formspec, field, group, when, is } from \"@formspec/dsl\";\n *\n * const InvoiceForm = formspec(\n * group(\"Customer\",\n * field.text(\"customerName\", { label: \"Customer Name\" }),\n * field.dynamicEnum(\"country\", \"fetch_countries\", { label: \"Country\" }),\n * ),\n * group(\"Invoice Details\",\n * field.number(\"amount\", { label: \"Amount\", min: 0 }),\n * field.enum(\"status\", [\"draft\", \"sent\", \"paid\"] as const),\n * when(is(\"status\", \"draft\"),\n * field.text(\"internalNotes\", { label: \"Internal Notes\" }),\n * ),\n * ),\n * );\n * ```\n *\n * @packageDocumentation\n */\n\n// Field builders\nexport { field } from \"./field.js\";\n\n// Predicate builders\nexport { is } from \"./predicate.js\";\n\n// Structure builders\nexport { group, when, formspec, formspecWithValidation } from \"./structure.js\";\nexport type { FormSpecOptions } from \"./structure.js\";\n\n// Validation\nexport { validateForm, logValidationIssues } from \"./validation.js\";\nexport type { ValidationSeverity, ValidationIssue, ValidationResult } from \"./validation.js\";\n\n// Type inference utilities\nexport type {\n InferFieldValue,\n ExtractFields,\n ExtractFieldsFromArray,\n ExtractNonConditionalFields,\n ExtractNonConditionalFieldsFromArray,\n ExtractConditionalFields,\n ExtractConditionalFieldsFromArray,\n BuildSchema,\n FlattenIntersection,\n InferSchema,\n InferFormSchema,\n} from \"./inference.js\";\n\n// Re-export enum option types from core for convenience\nexport type { EnumOption, EnumOptionValue } from \"@formspec/core\";\n","/**\n * Field builder functions for creating form field definitions.\n *\n * Each function creates a field descriptor that captures both schema information\n * (name, type) and UI hints (label, placeholder, etc.).\n */\n\nimport type {\n TextField,\n NumberField,\n BooleanField,\n StaticEnumField,\n EnumOptionValue,\n DynamicEnumField,\n DynamicSchemaField,\n ArrayField,\n ObjectField,\n FormElement,\n} from \"@formspec/core\";\n\n/**\n * Field builder namespace containing functions to create each field type.\n *\n * @example\n * ```typescript\n * import { field } from \"@formspec/dsl\";\n *\n * field.text(\"name\", { label: \"Full Name\" });\n * field.number(\"age\", { min: 0, max: 150 });\n * field.enum(\"status\", [\"draft\", \"sent\", \"paid\"]);\n * field.dynamicEnum(\"country\", \"countries\", { label: \"Country\" });\n * ```\n *\n * @public\n */\nexport const field = {\n /**\n * Creates a text input field.\n *\n * @param name - The field name (used as the schema key)\n * @param config - Optional configuration for label, placeholder, etc.\n * @returns A TextField descriptor\n */\n text: <const N extends string>(\n name: N,\n config?: Omit<TextField<N>, \"_type\" | \"_field\" | \"name\">\n ): TextField<N> => ({\n _type: \"field\",\n _field: \"text\",\n name,\n ...config,\n }),\n\n /**\n * Creates a numeric input field.\n *\n * @param name - The field name (used as the schema key)\n * @param config - Optional configuration for label, min, max, etc.\n * @returns A NumberField descriptor\n */\n number: <const N extends string>(\n name: N,\n config?: Omit<NumberField<N>, \"_type\" | \"_field\" | \"name\">\n ): NumberField<N> => ({\n _type: \"field\",\n _field: \"number\",\n name,\n ...config,\n }),\n\n /**\n * Creates a boolean checkbox field.\n *\n * @param name - The field name (used as the schema key)\n * @param config - Optional configuration for label, etc.\n * @returns A BooleanField descriptor\n */\n boolean: <const N extends string>(\n name: N,\n config?: Omit<BooleanField<N>, \"_type\" | \"_field\" | \"name\">\n ): BooleanField<N> => ({\n _type: \"field\",\n _field: \"boolean\",\n name,\n ...config,\n }),\n\n /**\n * Creates a field with static enum options (known at compile time).\n *\n * Literal types are automatically inferred - no `as const` needed:\n * ```typescript\n * field.enum(\"status\", [\"draft\", \"sent\", \"paid\"])\n * // Schema type: \"draft\" | \"sent\" | \"paid\"\n * ```\n *\n * Options can be strings or objects with `id` and `label`:\n * ```typescript\n * field.enum(\"priority\", [\n * { id: \"low\", label: \"Low Priority\" },\n * { id: \"high\", label: \"High Priority\" },\n * ])\n * ```\n *\n * **Note:** All options must be of the same type (all strings OR all objects).\n * Mixing strings and objects will throw a runtime error.\n *\n * @param name - The field name (used as the schema key)\n * @param options - Array of allowed string values or objects with `id` and `label` properties\n * @param config - Optional configuration for label, etc.\n * @returns A StaticEnumField descriptor\n * @throws Error if options array contains mixed types (strings and objects)\n */\n enum: <const N extends string, const O extends readonly EnumOptionValue[]>(\n name: N,\n options: O,\n config?: Omit<StaticEnumField<N, O>, \"_type\" | \"_field\" | \"name\" | \"options\">\n ): StaticEnumField<N, O> => {\n // Validate that all options are of the same type (all strings or all objects)\n if (options.length > 0) {\n const first = options[0];\n // Runtime check: TypeScript allows mixed arrays, but we enforce homogeneity\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const firstIsObject = typeof first === \"object\" && first !== null;\n\n // Check all items match the type of the first item\n for (const opt of options) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const optIsObject = typeof opt === \"object\" && opt !== null;\n if (optIsObject !== firstIsObject) {\n throw new Error(\n `field.enum(\"${name}\"): options must be all strings or all objects with {id, label}, not mixed. ` +\n `Received mixed types in options array.`\n );\n }\n }\n\n // Validate object options have required properties\n if (firstIsObject) {\n for (const opt of options) {\n const obj = opt as { id?: unknown; label?: unknown };\n if (typeof obj.id !== \"string\" || typeof obj.label !== \"string\") {\n throw new Error(\n `field.enum(\"${name}\"): object options must have string \"id\" and \"label\" properties. ` +\n `Received: ${JSON.stringify(opt)}`\n );\n }\n }\n }\n }\n\n return {\n _type: \"field\",\n _field: \"enum\",\n name,\n options,\n ...config,\n };\n },\n\n /**\n * Creates a field with dynamic enum options (fetched from a data source at runtime).\n *\n * The data source must be registered in DataSourceRegistry via module augmentation:\n * ```typescript\n * declare module \"@formspec/core\" {\n * interface DataSourceRegistry {\n * countries: { id: string; code: string; name: string };\n * }\n * }\n *\n * field.dynamicEnum(\"country\", \"countries\", { label: \"Country\" })\n * ```\n *\n * @param name - The field name (used as the schema key)\n * @param source - The data source key (must be in DataSourceRegistry)\n * @param config - Optional configuration for label, params, etc.\n * @returns A DynamicEnumField descriptor\n */\n dynamicEnum: <const N extends string, const Source extends string>(\n name: N,\n source: Source,\n config?: Omit<DynamicEnumField<N, Source>, \"_type\" | \"_field\" | \"name\" | \"source\">\n ): DynamicEnumField<N, Source> => ({\n _type: \"field\",\n _field: \"dynamic_enum\",\n name,\n source,\n ...config,\n }),\n\n /**\n * Creates a field that loads its schema dynamically (e.g., from an extension).\n *\n * @param name - The field name (used as the schema key)\n * @param schemaSource - Identifier for the schema source\n * @param config - Optional configuration for label, etc.\n * @returns A DynamicSchemaField descriptor\n */\n dynamicSchema: <const N extends string>(\n name: N,\n schemaSource: string,\n config?: Omit<DynamicSchemaField<N>, \"_type\" | \"_field\" | \"name\" | \"schemaSource\">\n ): DynamicSchemaField<N> => ({\n _type: \"field\",\n _field: \"dynamic_schema\",\n name,\n schemaSource,\n ...config,\n }),\n\n /**\n * Creates an array field containing repeating items.\n *\n * Use this for lists of values (e.g., multiple addresses, line items).\n *\n * @example\n * ```typescript\n * field.array(\"addresses\",\n * field.text(\"street\", { label: \"Street\" }),\n * field.text(\"city\", { label: \"City\" }),\n * field.text(\"zip\", { label: \"ZIP Code\" }),\n * )\n * ```\n *\n * @param name - The field name (used as the schema key)\n * @param items - The form elements that define each array item\n * @returns An ArrayField descriptor\n */\n array: <const N extends string, const Items extends readonly FormElement[]>(\n name: N,\n ...items: Items\n ): ArrayField<N, Items> => ({\n _type: \"field\",\n _field: \"array\",\n name,\n items,\n }),\n\n /**\n * Creates an array field with additional configuration options.\n *\n * @example\n * ```typescript\n * field.arrayWithConfig(\"lineItems\", {\n * label: \"Line Items\",\n * minItems: 1,\n * maxItems: 10,\n * },\n * field.text(\"description\"),\n * field.number(\"quantity\"),\n * )\n * ```\n *\n * @param name - The field name (used as the schema key)\n * @param config - Configuration for label, minItems, maxItems, etc.\n * @param items - The form elements that define each array item\n * @returns An ArrayField descriptor\n */\n arrayWithConfig: <const N extends string, const Items extends readonly FormElement[]>(\n name: N,\n config: Omit<ArrayField<N, Items>, \"_type\" | \"_field\" | \"name\" | \"items\">,\n ...items: Items\n ): ArrayField<N, Items> => ({\n _type: \"field\",\n _field: \"array\",\n name,\n items,\n ...config,\n }),\n\n /**\n * Creates an object field containing nested properties.\n *\n * Use this for grouping related fields under a single key in the schema.\n *\n * @example\n * ```typescript\n * field.object(\"address\",\n * field.text(\"street\", { label: \"Street\" }),\n * field.text(\"city\", { label: \"City\" }),\n * field.text(\"zip\", { label: \"ZIP Code\" }),\n * )\n * ```\n *\n * @param name - The field name (used as the schema key)\n * @param properties - The form elements that define the object's properties\n * @returns An ObjectField descriptor\n */\n object: <const N extends string, const Properties extends readonly FormElement[]>(\n name: N,\n ...properties: Properties\n ): ObjectField<N, Properties> => ({\n _type: \"field\",\n _field: \"object\",\n name,\n properties,\n }),\n\n /**\n * Creates an object field with additional configuration options.\n *\n * @example\n * ```typescript\n * field.objectWithConfig(\"billingAddress\", { label: \"Billing Address\", required: true },\n * field.text(\"street\"),\n * field.text(\"city\"),\n * )\n * ```\n *\n * @param name - The field name (used as the schema key)\n * @param config - Configuration for label, required, etc.\n * @param properties - The form elements that define the object's properties\n * @returns An ObjectField descriptor\n */\n objectWithConfig: <const N extends string, const Properties extends readonly FormElement[]>(\n name: N,\n config: Omit<ObjectField<N, Properties>, \"_type\" | \"_field\" | \"name\" | \"properties\">,\n ...properties: Properties\n ): ObjectField<N, Properties> => ({\n _type: \"field\",\n _field: \"object\",\n name,\n properties,\n ...config,\n }),\n};\n","/**\n * Predicate builder functions for conditional logic.\n *\n * These functions create predicates for use with `when()`:\n * - `is()` - Check if a field equals a specific value\n *\n * @example\n * ```typescript\n * when(is(\"status\", \"draft\"),\n * field.text(\"notes\"),\n * )\n * ```\n */\n\nimport type { EqualsPredicate } from \"@formspec/core\";\n\n/**\n * Creates an equality predicate that checks if a field equals a specific value.\n *\n * Use this with `when()` to create readable conditional expressions:\n *\n * @example\n * ```typescript\n * // Show cardNumber field when paymentMethod is \"card\"\n * when(is(\"paymentMethod\", \"card\"),\n * field.text(\"cardNumber\", { label: \"Card Number\" }),\n * )\n * ```\n *\n * @typeParam K - The field name (inferred as string literal)\n * @typeParam V - The value type (inferred as literal)\n * @param field - The name of the field to check\n * @param value - The value the field must equal\n * @returns An EqualsPredicate for use with `when()`\n * @public\n */\nexport function is<const K extends string, const V>(field: K, value: V): EqualsPredicate<K, V> {\n return {\n _predicate: \"equals\",\n field,\n value,\n };\n}\n","/**\n * Runtime validation for form specifications.\n *\n * Validates:\n * - No duplicate field names at the same scope level\n * - All field references in conditionals point to existing fields\n */\n\nimport type { FormElement, Group, Conditional, ArrayField, ObjectField } from \"@formspec/core\";\n\n/**\n * Validation issue severity levels.\n *\n * @public\n */\nexport type ValidationSeverity = \"error\" | \"warning\";\n\n/**\n * A validation issue found in a form specification.\n *\n * @public\n */\nexport interface ValidationIssue {\n /** Severity of the issue */\n severity: ValidationSeverity;\n /** Human-readable message describing the issue */\n message: string;\n /** Path to the element with the issue (e.g., \"group.fieldName\") */\n path: string;\n}\n\n/**\n * Result of validating a form specification.\n *\n * @public\n */\nexport interface ValidationResult {\n /** Whether the form is valid (no errors, warnings are ok) */\n valid: boolean;\n /** List of validation issues found */\n issues: ValidationIssue[];\n}\n\n/**\n * Collects all field names from a list of form elements.\n * Returns a Map of field name to count (for duplicate detection).\n */\nfunction collectFieldNames(\n elements: readonly FormElement[],\n path = \"\"\n): Map<string, { count: number; paths: string[] }> {\n const fieldNames = new Map<string, { count: number; paths: string[] }>();\n\n function visit(elements: readonly FormElement[], currentPath: string): void {\n for (const element of elements) {\n switch (element._type) {\n case \"field\": {\n // After type narrowing, element is known to be AnyField\n const field = element;\n const fieldPath = currentPath ? `${currentPath}.${field.name}` : field.name;\n const existing = fieldNames.get(field.name);\n if (existing !== undefined) {\n existing.count++;\n existing.paths.push(fieldPath);\n } else {\n fieldNames.set(field.name, { count: 1, paths: [fieldPath] });\n }\n\n // Recurse into array items and object properties\n if (field._field === \"array\") {\n const arrayField = field as ArrayField<string, readonly FormElement[]>;\n visit(arrayField.items, `${fieldPath}[]`);\n } else if (field._field === \"object\") {\n const objectField = field as ObjectField<string, readonly FormElement[]>;\n visit(objectField.properties, fieldPath);\n }\n break;\n }\n\n case \"group\": {\n const group = element as Group<readonly FormElement[]>;\n const groupPath = currentPath ? `${currentPath}.[${group.label}]` : `[${group.label}]`;\n visit(group.elements, groupPath);\n break;\n }\n\n case \"conditional\": {\n const conditional = element as Conditional<string, unknown, readonly FormElement[]>;\n const conditionalPath = currentPath\n ? `${currentPath}.when(${conditional.field})`\n : `when(${conditional.field})`;\n visit(conditional.elements, conditionalPath);\n break;\n }\n }\n }\n }\n\n visit(elements, path);\n return fieldNames;\n}\n\n/**\n * Collects all field references from conditionals.\n * Returns a list of { fieldName, path } for each reference.\n */\nfunction collectConditionalReferences(\n elements: readonly FormElement[],\n path = \"\"\n): { fieldName: string; path: string }[] {\n const references: { fieldName: string; path: string }[] = [];\n\n function visit(elements: readonly FormElement[], currentPath: string): void {\n for (const element of elements) {\n switch (element._type) {\n case \"field\": {\n // After type narrowing, element is known to be AnyField\n const field = element;\n const fieldPath = currentPath ? `${currentPath}.${field.name}` : field.name;\n\n // Recurse into array items and object properties\n if (field._field === \"array\") {\n const arrayField = field as ArrayField<string, readonly FormElement[]>;\n visit(arrayField.items, `${fieldPath}[]`);\n } else if (field._field === \"object\") {\n const objectField = field as ObjectField<string, readonly FormElement[]>;\n visit(objectField.properties, fieldPath);\n }\n break;\n }\n\n case \"group\": {\n const group = element as Group<readonly FormElement[]>;\n const groupPath = currentPath ? `${currentPath}.[${group.label}]` : `[${group.label}]`;\n visit(group.elements, groupPath);\n break;\n }\n\n case \"conditional\": {\n const conditional = element as Conditional<string, unknown, readonly FormElement[]>;\n const conditionalPath = currentPath\n ? `${currentPath}.when(${conditional.field})`\n : `when(${conditional.field})`;\n\n // Record this reference\n references.push({\n fieldName: conditional.field,\n path: conditionalPath,\n });\n\n // Continue visiting children\n visit(conditional.elements, conditionalPath);\n break;\n }\n }\n }\n }\n\n visit(elements, path);\n return references;\n}\n\n/**\n * Validates a form specification for common issues.\n *\n * Checks for:\n * - Duplicate field names at the root level (warning)\n * - References to non-existent fields in conditionals (error)\n *\n * @example\n * ```typescript\n * const form = formspec(\n * field.text(\"name\"),\n * field.text(\"name\"), // Duplicate!\n * when(\"nonExistent\", \"value\", // Reference to non-existent field!\n * field.text(\"extra\"),\n * ),\n * );\n *\n * const result = validateForm(form.elements);\n * // result.valid === false\n * // result.issues contains duplicate and reference errors\n * ```\n *\n * @param elements - The form elements to validate\n * @returns Validation result with any issues found\n *\n * @public\n */\nexport function validateForm(elements: readonly FormElement[]): ValidationResult {\n const issues: ValidationIssue[] = [];\n\n // Collect all field names\n const fieldNames = collectFieldNames(elements);\n\n // Check for duplicates at root level - duplicates are errors because they cause data loss\n for (const [name, info] of fieldNames) {\n if (info.count > 1 && info.paths[0] !== undefined) {\n issues.push({\n severity: \"error\",\n message: `Duplicate field name \"${name}\" found ${String(info.count)} times at: ${info.paths.join(\", \")}`,\n path: info.paths[0],\n });\n }\n }\n\n // Collect conditional references\n const references = collectConditionalReferences(elements);\n\n // Check that all referenced fields exist\n for (const ref of references) {\n if (!fieldNames.has(ref.fieldName)) {\n issues.push({\n severity: \"error\",\n message: `Conditional references non-existent field \"${ref.fieldName}\"`,\n path: ref.path,\n });\n }\n }\n\n return {\n valid: issues.every((issue) => issue.severity !== \"error\"),\n issues,\n };\n}\n\n/**\n * Logs validation issues to the console.\n *\n * @param result - The validation result to log\n * @param formName - Optional name for the form (for better error messages)\n *\n * @public\n */\nexport function logValidationIssues(result: ValidationResult, formName?: string): void {\n if (result.issues.length === 0) {\n return;\n }\n\n const prefix = formName ? `FormSpec \"${formName}\"` : \"FormSpec\";\n\n for (const issue of result.issues) {\n const location = issue.path ? ` at ${issue.path}` : \"\";\n const message = `${prefix}: ${issue.message}${location}`;\n\n if (issue.severity === \"error\") {\n console.error(message);\n } else {\n console.warn(message);\n }\n }\n}\n","/**\n * Structure builder functions for organizing form elements.\n *\n * These functions create layout and conditional structures:\n * - `group()` - Visual grouping of fields\n * - `when()` - Conditional visibility based on field values\n * - `formspec()` - Top-level form specification\n */\n\nimport type { FormElement, Group, Conditional, FormSpec, Predicate } from \"@formspec/core\";\nimport { validateForm, logValidationIssues } from \"./validation.js\";\n\n/**\n * Options for creating a form specification.\n *\n * @public\n */\nexport interface FormSpecOptions {\n /**\n * Whether to validate the form structure.\n * - `true` or `\"warn\"`: Validate and log warnings/errors to console\n * - `\"throw\"`: Validate and throw an error if validation fails\n * - `false`: Skip validation (default in production for performance)\n *\n * @defaultValue false\n */\n validate?: boolean | \"warn\" | \"throw\";\n\n /**\n * Optional name for the form (used in validation messages).\n */\n name?: string;\n}\n\n/**\n * Creates a visual group of form elements.\n *\n * Groups provide visual organization and can be rendered as fieldsets or sections.\n * The nesting of groups defines the visual hierarchy of the form.\n *\n * @example\n * ```typescript\n * group(\"Customer Information\",\n * field.text(\"name\", { label: \"Name\" }),\n * field.text(\"email\", { label: \"Email\" }),\n * )\n * ```\n *\n * @param label - The group's display label\n * @param elements - The form elements contained in this group\n * @returns A Group descriptor\n *\n * @public\n */\nexport function group<const Elements extends readonly FormElement[]>(\n label: string,\n ...elements: Elements\n): Group<Elements> {\n return { _type: \"group\", label, elements };\n}\n\n/**\n * Creates a conditional wrapper that shows elements based on a predicate.\n *\n * When the predicate evaluates to true, the contained elements are shown.\n * Otherwise, they are hidden (but still part of the schema).\n *\n * @example\n * ```typescript\n * import { is } from \"@formspec/dsl\";\n *\n * field.enum(\"status\", [\"draft\", \"sent\", \"paid\"] as const),\n * when(is(\"status\", \"draft\"),\n * field.text(\"internalNotes\", { label: \"Internal Notes\" }),\n * )\n * ```\n *\n * @param predicate - The condition to evaluate (use `is()` to create)\n * @param elements - The form elements to show when condition is met\n * @returns A Conditional descriptor\n *\n * @public\n */\nexport function when<\n const K extends string,\n const V,\n const Elements extends readonly FormElement[],\n>(predicate: Predicate<K, V>, ...elements: Elements): Conditional<K, V, Elements> {\n return {\n _type: \"conditional\",\n field: predicate.field,\n value: predicate.value,\n elements,\n };\n}\n\n/**\n * Creates a complete form specification.\n *\n * The structure IS the definition:\n * - Nesting with `group()` defines visual layout\n * - Nesting with `when()` defines conditional visibility\n * - Field type implies control type (text field → text input)\n * - Array position implies field ordering\n *\n * Schema is automatically inferred from all fields in the structure.\n *\n * @example\n * ```typescript\n * const InvoiceForm = formspec(\n * group(\"Customer\",\n * field.text(\"customerName\", { label: \"Customer Name\" }),\n * field.dynamicEnum(\"country\", \"fetch_countries\", { label: \"Country\" }),\n * ),\n * group(\"Invoice Details\",\n * field.number(\"amount\", { label: \"Amount\", min: 0 }),\n * field.enum(\"status\", [\"draft\", \"sent\", \"paid\"] as const),\n * when(is(\"status\", \"draft\"),\n * field.text(\"internalNotes\", { label: \"Internal Notes\" }),\n * ),\n * ),\n * );\n * ```\n *\n * @param elements - The top-level form elements\n * @returns A FormSpec descriptor\n *\n * @public\n */\nexport function formspec<const Elements extends readonly FormElement[]>(\n ...elements: Elements\n): FormSpec<Elements> {\n return { elements };\n}\n\n/**\n * Creates a complete form specification with validation options.\n *\n * @example\n * ```typescript\n * const form = formspecWithValidation(\n * { validate: true, name: \"MyForm\" },\n * field.text(\"name\"),\n * field.enum(\"status\", [\"draft\", \"sent\"] as const),\n * when(is(\"status\", \"draft\"),\n * field.text(\"notes\"),\n * ),\n * );\n * ```\n *\n * @param options - Validation options\n * @param elements - The top-level form elements\n * @returns A FormSpec descriptor\n *\n * @public\n */\nexport function formspecWithValidation<const Elements extends readonly FormElement[]>(\n options: FormSpecOptions,\n ...elements: Elements\n): FormSpec<Elements> {\n // Run validation if requested\n if (options.validate) {\n const result = validateForm(elements);\n\n if (options.validate === \"throw\" && !result.valid) {\n const errors = result.issues\n .filter((i) => i.severity === \"error\")\n .map((i) => i.message)\n .join(\"; \");\n throw new Error(`Form validation failed: ${errors}`);\n }\n\n if (options.validate === true || options.validate === \"warn\") {\n logValidationIssues(result, options.name);\n }\n }\n\n return { elements };\n}\n"],"mappings":"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","names":["field","elements","field","group"]}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/field.ts","../src/predicate.ts","../src/validation.ts","../src/structure.ts"],"sourcesContent":["/**\n * Field builder functions for creating form field definitions.\n *\n * Each function creates a field descriptor that captures both schema information\n * (name, type) and UI hints (label, placeholder, etc.).\n */\n\nimport type {\n TextField,\n NumberField,\n BooleanField,\n StaticEnumField,\n EnumOptionValue,\n DynamicEnumField,\n DynamicSchemaField,\n ArrayField,\n ObjectField,\n FormElement,\n} from \"@formspec/core\";\n\n/**\n * Field builder namespace containing functions to create each field type.\n *\n * @example\n * ```typescript\n * import { field } from \"@formspec/dsl\";\n *\n * field.text(\"name\", { label: \"Full Name\" });\n * field.number(\"age\", { min: 0, max: 150 });\n * field.enum(\"status\", [\"draft\", \"sent\", \"paid\"]);\n * field.dynamicEnum(\"country\", \"countries\", { label: \"Country\" });\n * ```\n */\nexport const field = {\n /**\n * Creates a text input field.\n *\n * @param name - The field name (used as the schema key)\n * @param config - Optional configuration for label, placeholder, etc.\n * @returns A TextField descriptor\n */\n text: <const N extends string>(\n name: N,\n config?: Omit<TextField<N>, \"_type\" | \"_field\" | \"name\">\n ): TextField<N> => ({\n _type: \"field\",\n _field: \"text\",\n name,\n ...config,\n }),\n\n /**\n * Creates a numeric input field.\n *\n * @param name - The field name (used as the schema key)\n * @param config - Optional configuration for label, min, max, etc.\n * @returns A NumberField descriptor\n */\n number: <const N extends string>(\n name: N,\n config?: Omit<NumberField<N>, \"_type\" | \"_field\" | \"name\">\n ): NumberField<N> => ({\n _type: \"field\",\n _field: \"number\",\n name,\n ...config,\n }),\n\n /**\n * Creates a boolean checkbox field.\n *\n * @param name - The field name (used as the schema key)\n * @param config - Optional configuration for label, etc.\n * @returns A BooleanField descriptor\n */\n boolean: <const N extends string>(\n name: N,\n config?: Omit<BooleanField<N>, \"_type\" | \"_field\" | \"name\">\n ): BooleanField<N> => ({\n _type: \"field\",\n _field: \"boolean\",\n name,\n ...config,\n }),\n\n /**\n * Creates a field with static enum options (known at compile time).\n *\n * Literal types are automatically inferred - no `as const` needed:\n * ```typescript\n * field.enum(\"status\", [\"draft\", \"sent\", \"paid\"])\n * // Schema type: \"draft\" | \"sent\" | \"paid\"\n * ```\n *\n * Options can be strings or objects with `id` and `label`:\n * ```typescript\n * field.enum(\"priority\", [\n * { id: \"low\", label: \"Low Priority\" },\n * { id: \"high\", label: \"High Priority\" },\n * ])\n * ```\n *\n * **Note:** All options must be of the same type (all strings OR all objects).\n * Mixing strings and objects will throw a runtime error.\n *\n * @param name - The field name (used as the schema key)\n * @param options - Array of allowed string values or objects with `id` and `label` properties\n * @param config - Optional configuration for label, etc.\n * @returns A StaticEnumField descriptor\n * @throws Error if options array contains mixed types (strings and objects)\n */\n enum: <const N extends string, const O extends readonly EnumOptionValue[]>(\n name: N,\n options: O,\n config?: Omit<StaticEnumField<N, O>, \"_type\" | \"_field\" | \"name\" | \"options\">\n ): StaticEnumField<N, O> => {\n // Validate that all options are of the same type (all strings or all objects)\n if (options.length > 0) {\n const first = options[0];\n // Runtime check: TypeScript allows mixed arrays, but we enforce homogeneity\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const firstIsObject = typeof first === \"object\" && first !== null;\n\n // Check all items match the type of the first item\n for (const opt of options) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const optIsObject = typeof opt === \"object\" && opt !== null;\n if (optIsObject !== firstIsObject) {\n throw new Error(\n `field.enum(\"${name}\"): options must be all strings or all objects with {id, label}, not mixed. ` +\n `Received mixed types in options array.`\n );\n }\n }\n\n // Validate object options have required properties\n if (firstIsObject) {\n for (const opt of options) {\n const obj = opt as { id?: unknown; label?: unknown };\n if (typeof obj.id !== \"string\" || typeof obj.label !== \"string\") {\n throw new Error(\n `field.enum(\"${name}\"): object options must have string \"id\" and \"label\" properties. ` +\n `Received: ${JSON.stringify(opt)}`\n );\n }\n }\n }\n }\n\n return {\n _type: \"field\",\n _field: \"enum\",\n name,\n options,\n ...config,\n };\n },\n\n /**\n * Creates a field with dynamic enum options (fetched from a data source at runtime).\n *\n * The data source must be registered in DataSourceRegistry via module augmentation:\n * ```typescript\n * declare module \"@formspec/core\" {\n * interface DataSourceRegistry {\n * countries: { id: string; code: string; name: string };\n * }\n * }\n *\n * field.dynamicEnum(\"country\", \"countries\", { label: \"Country\" })\n * ```\n *\n * @param name - The field name (used as the schema key)\n * @param source - The data source key (must be in DataSourceRegistry)\n * @param config - Optional configuration for label, params, etc.\n * @returns A DynamicEnumField descriptor\n */\n dynamicEnum: <const N extends string, const Source extends string>(\n name: N,\n source: Source,\n config?: Omit<DynamicEnumField<N, Source>, \"_type\" | \"_field\" | \"name\" | \"source\">\n ): DynamicEnumField<N, Source> => ({\n _type: \"field\",\n _field: \"dynamic_enum\",\n name,\n source,\n ...config,\n }),\n\n /**\n * Creates a field that loads its schema dynamically (e.g., from an extension).\n *\n * @param name - The field name (used as the schema key)\n * @param schemaSource - Identifier for the schema source\n * @param config - Optional configuration for label, etc.\n * @returns A DynamicSchemaField descriptor\n */\n dynamicSchema: <const N extends string>(\n name: N,\n schemaSource: string,\n config?: Omit<DynamicSchemaField<N>, \"_type\" | \"_field\" | \"name\" | \"schemaSource\">\n ): DynamicSchemaField<N> => ({\n _type: \"field\",\n _field: \"dynamic_schema\",\n name,\n schemaSource,\n ...config,\n }),\n\n /**\n * Creates an array field containing repeating items.\n *\n * Use this for lists of values (e.g., multiple addresses, line items).\n *\n * @example\n * ```typescript\n * field.array(\"addresses\",\n * field.text(\"street\", { label: \"Street\" }),\n * field.text(\"city\", { label: \"City\" }),\n * field.text(\"zip\", { label: \"ZIP Code\" }),\n * )\n * ```\n *\n * @param name - The field name (used as the schema key)\n * @param items - The form elements that define each array item\n * @returns An ArrayField descriptor\n */\n array: <const N extends string, const Items extends readonly FormElement[]>(\n name: N,\n ...items: Items\n ): ArrayField<N, Items> => ({\n _type: \"field\",\n _field: \"array\",\n name,\n items,\n }),\n\n /**\n * Creates an array field with additional configuration options.\n *\n * @example\n * ```typescript\n * field.arrayWithConfig(\"lineItems\", {\n * label: \"Line Items\",\n * minItems: 1,\n * maxItems: 10,\n * },\n * field.text(\"description\"),\n * field.number(\"quantity\"),\n * )\n * ```\n *\n * @param name - The field name (used as the schema key)\n * @param config - Configuration for label, minItems, maxItems, etc.\n * @param items - The form elements that define each array item\n * @returns An ArrayField descriptor\n */\n arrayWithConfig: <const N extends string, const Items extends readonly FormElement[]>(\n name: N,\n config: Omit<ArrayField<N, Items>, \"_type\" | \"_field\" | \"name\" | \"items\">,\n ...items: Items\n ): ArrayField<N, Items> => ({\n _type: \"field\",\n _field: \"array\",\n name,\n items,\n ...config,\n }),\n\n /**\n * Creates an object field containing nested properties.\n *\n * Use this for grouping related fields under a single key in the schema.\n *\n * @example\n * ```typescript\n * field.object(\"address\",\n * field.text(\"street\", { label: \"Street\" }),\n * field.text(\"city\", { label: \"City\" }),\n * field.text(\"zip\", { label: \"ZIP Code\" }),\n * )\n * ```\n *\n * @param name - The field name (used as the schema key)\n * @param properties - The form elements that define the object's properties\n * @returns An ObjectField descriptor\n */\n object: <const N extends string, const Properties extends readonly FormElement[]>(\n name: N,\n ...properties: Properties\n ): ObjectField<N, Properties> => ({\n _type: \"field\",\n _field: \"object\",\n name,\n properties,\n }),\n\n /**\n * Creates an object field with additional configuration options.\n *\n * @example\n * ```typescript\n * field.objectWithConfig(\"billingAddress\", { label: \"Billing Address\", required: true },\n * field.text(\"street\"),\n * field.text(\"city\"),\n * )\n * ```\n *\n * @param name - The field name (used as the schema key)\n * @param config - Configuration for label, required, etc.\n * @param properties - The form elements that define the object's properties\n * @returns An ObjectField descriptor\n */\n objectWithConfig: <const N extends string, const Properties extends readonly FormElement[]>(\n name: N,\n config: Omit<ObjectField<N, Properties>, \"_type\" | \"_field\" | \"name\" | \"properties\">,\n ...properties: Properties\n ): ObjectField<N, Properties> => ({\n _type: \"field\",\n _field: \"object\",\n name,\n properties,\n ...config,\n }),\n};\n","/**\n * Predicate builder functions for conditional logic.\n *\n * These functions create predicates for use with `when()`:\n * - `is()` - Check if a field equals a specific value\n *\n * @example\n * ```typescript\n * when(is(\"status\", \"draft\"),\n * field.text(\"notes\"),\n * )\n * ```\n */\n\nimport type { EqualsPredicate } from \"@formspec/core\";\n\n/**\n * Creates an equality predicate that checks if a field equals a specific value.\n *\n * Use this with `when()` to create readable conditional expressions:\n *\n * @example\n * ```typescript\n * // Show cardNumber field when paymentMethod is \"card\"\n * when(is(\"paymentMethod\", \"card\"),\n * field.text(\"cardNumber\", { label: \"Card Number\" }),\n * )\n * ```\n *\n * @typeParam K - The field name (inferred as string literal)\n * @typeParam V - The value type (inferred as literal)\n * @param field - The name of the field to check\n * @param value - The value the field must equal\n * @returns An EqualsPredicate for use with `when()`\n * @public\n */\nexport function is<const K extends string, const V>(field: K, value: V): EqualsPredicate<K, V> {\n return {\n _predicate: \"equals\",\n field,\n value,\n };\n}\n","/**\n * Runtime validation for form specifications.\n *\n * Validates:\n * - No duplicate field names at the same scope level\n * - All field references in conditionals point to existing fields\n */\n\nimport type { FormElement, Group, Conditional, ArrayField, ObjectField } from \"@formspec/core\";\n\n/**\n * Validation issue severity levels.\n */\nexport type ValidationSeverity = \"error\" | \"warning\";\n\n/**\n * A validation issue found in a form specification.\n */\nexport interface ValidationIssue {\n /** Severity of the issue */\n severity: ValidationSeverity;\n /** Human-readable message describing the issue */\n message: string;\n /** Path to the element with the issue (e.g., \"group.fieldName\") */\n path: string;\n}\n\n/**\n * Result of validating a form specification.\n */\nexport interface ValidationResult {\n /** Whether the form is valid (no errors, warnings are ok) */\n valid: boolean;\n /** List of validation issues found */\n issues: ValidationIssue[];\n}\n\n/**\n * Collects all field names from a list of form elements.\n * Returns a Map of field name to count (for duplicate detection).\n */\nfunction collectFieldNames(\n elements: readonly FormElement[],\n path = \"\"\n): Map<string, { count: number; paths: string[] }> {\n const fieldNames = new Map<string, { count: number; paths: string[] }>();\n\n function visit(elements: readonly FormElement[], currentPath: string): void {\n for (const element of elements) {\n switch (element._type) {\n case \"field\": {\n // After type narrowing, element is known to be AnyField\n const field = element;\n const fieldPath = currentPath ? `${currentPath}.${field.name}` : field.name;\n const existing = fieldNames.get(field.name);\n if (existing !== undefined) {\n existing.count++;\n existing.paths.push(fieldPath);\n } else {\n fieldNames.set(field.name, { count: 1, paths: [fieldPath] });\n }\n\n // Recurse into array items and object properties\n if (field._field === \"array\") {\n const arrayField = field as ArrayField<string, readonly FormElement[]>;\n visit(arrayField.items, `${fieldPath}[]`);\n } else if (field._field === \"object\") {\n const objectField = field as ObjectField<string, readonly FormElement[]>;\n visit(objectField.properties, fieldPath);\n }\n break;\n }\n\n case \"group\": {\n const group = element as Group<readonly FormElement[]>;\n const groupPath = currentPath ? `${currentPath}.[${group.label}]` : `[${group.label}]`;\n visit(group.elements, groupPath);\n break;\n }\n\n case \"conditional\": {\n const conditional = element as Conditional<string, unknown, readonly FormElement[]>;\n const conditionalPath = currentPath\n ? `${currentPath}.when(${conditional.field})`\n : `when(${conditional.field})`;\n visit(conditional.elements, conditionalPath);\n break;\n }\n }\n }\n }\n\n visit(elements, path);\n return fieldNames;\n}\n\n/**\n * Collects all field references from conditionals.\n * Returns a list of { fieldName, path } for each reference.\n */\nfunction collectConditionalReferences(\n elements: readonly FormElement[],\n path = \"\"\n): { fieldName: string; path: string }[] {\n const references: { fieldName: string; path: string }[] = [];\n\n function visit(elements: readonly FormElement[], currentPath: string): void {\n for (const element of elements) {\n switch (element._type) {\n case \"field\": {\n // After type narrowing, element is known to be AnyField\n const field = element;\n const fieldPath = currentPath ? `${currentPath}.${field.name}` : field.name;\n\n // Recurse into array items and object properties\n if (field._field === \"array\") {\n const arrayField = field as ArrayField<string, readonly FormElement[]>;\n visit(arrayField.items, `${fieldPath}[]`);\n } else if (field._field === \"object\") {\n const objectField = field as ObjectField<string, readonly FormElement[]>;\n visit(objectField.properties, fieldPath);\n }\n break;\n }\n\n case \"group\": {\n const group = element as Group<readonly FormElement[]>;\n const groupPath = currentPath ? `${currentPath}.[${group.label}]` : `[${group.label}]`;\n visit(group.elements, groupPath);\n break;\n }\n\n case \"conditional\": {\n const conditional = element as Conditional<string, unknown, readonly FormElement[]>;\n const conditionalPath = currentPath\n ? `${currentPath}.when(${conditional.field})`\n : `when(${conditional.field})`;\n\n // Record this reference\n references.push({\n fieldName: conditional.field,\n path: conditionalPath,\n });\n\n // Continue visiting children\n visit(conditional.elements, conditionalPath);\n break;\n }\n }\n }\n }\n\n visit(elements, path);\n return references;\n}\n\n/**\n * Validates a form specification for common issues.\n *\n * Checks for:\n * - Duplicate field names at the root level (warning)\n * - References to non-existent fields in conditionals (error)\n *\n * @example\n * ```typescript\n * const form = formspec(\n * field.text(\"name\"),\n * field.text(\"name\"), // Duplicate!\n * when(\"nonExistent\", \"value\", // Reference to non-existent field!\n * field.text(\"extra\"),\n * ),\n * );\n *\n * const result = validateForm(form.elements);\n * // result.valid === false\n * // result.issues contains duplicate and reference errors\n * ```\n *\n * @param elements - The form elements to validate\n * @returns Validation result with any issues found\n */\nexport function validateForm(elements: readonly FormElement[]): ValidationResult {\n const issues: ValidationIssue[] = [];\n\n // Collect all field names\n const fieldNames = collectFieldNames(elements);\n\n // Check for duplicates at root level - duplicates are errors because they cause data loss\n for (const [name, info] of fieldNames) {\n if (info.count > 1 && info.paths[0] !== undefined) {\n issues.push({\n severity: \"error\",\n message: `Duplicate field name \"${name}\" found ${String(info.count)} times at: ${info.paths.join(\", \")}`,\n path: info.paths[0],\n });\n }\n }\n\n // Collect conditional references\n const references = collectConditionalReferences(elements);\n\n // Check that all referenced fields exist\n for (const ref of references) {\n if (!fieldNames.has(ref.fieldName)) {\n issues.push({\n severity: \"error\",\n message: `Conditional references non-existent field \"${ref.fieldName}\"`,\n path: ref.path,\n });\n }\n }\n\n return {\n valid: issues.every((issue) => issue.severity !== \"error\"),\n issues,\n };\n}\n\n/**\n * Logs validation issues to the console.\n *\n * @param result - The validation result to log\n * @param formName - Optional name for the form (for better error messages)\n */\nexport function logValidationIssues(result: ValidationResult, formName?: string): void {\n if (result.issues.length === 0) {\n return;\n }\n\n const prefix = formName ? `FormSpec \"${formName}\"` : \"FormSpec\";\n\n for (const issue of result.issues) {\n const location = issue.path ? ` at ${issue.path}` : \"\";\n const message = `${prefix}: ${issue.message}${location}`;\n\n if (issue.severity === \"error\") {\n console.error(message);\n } else {\n console.warn(message);\n }\n }\n}\n","/**\n * Structure builder functions for organizing form elements.\n *\n * These functions create layout and conditional structures:\n * - `group()` - Visual grouping of fields\n * - `when()` - Conditional visibility based on field values\n * - `formspec()` - Top-level form specification\n */\n\nimport type { FormElement, Group, Conditional, FormSpec, Predicate } from \"@formspec/core\";\nimport { validateForm, logValidationIssues } from \"./validation.js\";\n\n/**\n * Options for creating a form specification.\n */\nexport interface FormSpecOptions {\n /**\n * Whether to validate the form structure.\n * - `true` or `\"warn\"`: Validate and log warnings/errors to console\n * - `\"throw\"`: Validate and throw an error if validation fails\n * - `false`: Skip validation (default in production for performance)\n *\n * @defaultValue false\n */\n validate?: boolean | \"warn\" | \"throw\";\n\n /**\n * Optional name for the form (used in validation messages).\n */\n name?: string;\n}\n\n/**\n * Creates a visual group of form elements.\n *\n * Groups provide visual organization and can be rendered as fieldsets or sections.\n * The nesting of groups defines the visual hierarchy of the form.\n *\n * @example\n * ```typescript\n * group(\"Customer Information\",\n * field.text(\"name\", { label: \"Name\" }),\n * field.text(\"email\", { label: \"Email\" }),\n * )\n * ```\n *\n * @param label - The group's display label\n * @param elements - The form elements contained in this group\n * @returns A Group descriptor\n */\nexport function group<const Elements extends readonly FormElement[]>(\n label: string,\n ...elements: Elements\n): Group<Elements> {\n return { _type: \"group\", label, elements };\n}\n\n/**\n * Creates a conditional wrapper that shows elements based on a predicate.\n *\n * When the predicate evaluates to true, the contained elements are shown.\n * Otherwise, they are hidden (but still part of the schema).\n *\n * @example\n * ```typescript\n * import { is } from \"@formspec/dsl\";\n *\n * field.enum(\"status\", [\"draft\", \"sent\", \"paid\"] as const),\n * when(is(\"status\", \"draft\"),\n * field.text(\"internalNotes\", { label: \"Internal Notes\" }),\n * )\n * ```\n *\n * @param predicate - The condition to evaluate (use `is()` to create)\n * @param elements - The form elements to show when condition is met\n * @returns A Conditional descriptor\n */\nexport function when<\n const K extends string,\n const V,\n const Elements extends readonly FormElement[],\n>(predicate: Predicate<K, V>, ...elements: Elements): Conditional<K, V, Elements> {\n return {\n _type: \"conditional\",\n field: predicate.field,\n value: predicate.value,\n elements,\n };\n}\n\n/**\n * Creates a complete form specification.\n *\n * The structure IS the definition:\n * - Nesting with `group()` defines visual layout\n * - Nesting with `when()` defines conditional visibility\n * - Field type implies control type (text field → text input)\n * - Array position implies field ordering\n *\n * Schema is automatically inferred from all fields in the structure.\n *\n * @example\n * ```typescript\n * const InvoiceForm = formspec(\n * group(\"Customer\",\n * field.text(\"customerName\", { label: \"Customer Name\" }),\n * field.dynamicEnum(\"country\", \"fetch_countries\", { label: \"Country\" }),\n * ),\n * group(\"Invoice Details\",\n * field.number(\"amount\", { label: \"Amount\", min: 0 }),\n * field.enum(\"status\", [\"draft\", \"sent\", \"paid\"] as const),\n * when(is(\"status\", \"draft\"),\n * field.text(\"internalNotes\", { label: \"Internal Notes\" }),\n * ),\n * ),\n * );\n * ```\n *\n * @param elements - The top-level form elements\n * @returns A FormSpec descriptor\n */\nexport function formspec<const Elements extends readonly FormElement[]>(\n ...elements: Elements\n): FormSpec<Elements> {\n return { elements };\n}\n\n/**\n * Creates a complete form specification with validation options.\n *\n * @example\n * ```typescript\n * const form = formspecWithValidation(\n * { validate: true, name: \"MyForm\" },\n * field.text(\"name\"),\n * field.enum(\"status\", [\"draft\", \"sent\"] as const),\n * when(is(\"status\", \"draft\"),\n * field.text(\"notes\"),\n * ),\n * );\n * ```\n *\n * @param options - Validation options\n * @param elements - The top-level form elements\n * @returns A FormSpec descriptor\n */\nexport function formspecWithValidation<const Elements extends readonly FormElement[]>(\n options: FormSpecOptions,\n ...elements: Elements\n): FormSpec<Elements> {\n // Run validation if requested\n if (options.validate) {\n const result = validateForm(elements);\n\n if (options.validate === \"throw\" && !result.valid) {\n const errors = result.issues\n .filter((i) => i.severity === \"error\")\n .map((i) => i.message)\n .join(\"; \");\n throw new Error(`Form validation failed: ${errors}`);\n }\n\n if (options.validate === true || options.validate === \"warn\") {\n logValidationIssues(result, options.name);\n }\n }\n\n return { elements };\n}\n"],"mappings":"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","names":["field","elements","field","group"]}
|
|
1
|
+
{"version":3,"sources":["../src/field.ts","../src/predicate.ts","../src/validation.ts","../src/structure.ts"],"sourcesContent":["/**\n * Field builder functions for creating form field definitions.\n *\n * Each function creates a field descriptor that captures both schema information\n * (name, type) and UI hints (label, placeholder, etc.).\n */\n\nimport type {\n TextField,\n NumberField,\n BooleanField,\n StaticEnumField,\n EnumOptionValue,\n DynamicEnumField,\n DynamicSchemaField,\n ArrayField,\n ObjectField,\n FormElement,\n} from \"@formspec/core\";\n\n/**\n * Field builder namespace containing functions to create each field type.\n *\n * @example\n * ```typescript\n * import { field } from \"@formspec/dsl\";\n *\n * field.text(\"name\", { label: \"Full Name\" });\n * field.number(\"age\", { min: 0, max: 150 });\n * field.enum(\"status\", [\"draft\", \"sent\", \"paid\"]);\n * field.dynamicEnum(\"country\", \"countries\", { label: \"Country\" });\n * ```\n *\n * @public\n */\nexport const field = {\n /**\n * Creates a text input field.\n *\n * @param name - The field name (used as the schema key)\n * @param config - Optional configuration for label, placeholder, etc.\n * @returns A TextField descriptor\n */\n text: <const N extends string>(\n name: N,\n config?: Omit<TextField<N>, \"_type\" | \"_field\" | \"name\">\n ): TextField<N> => ({\n _type: \"field\",\n _field: \"text\",\n name,\n ...config,\n }),\n\n /**\n * Creates a numeric input field.\n *\n * @param name - The field name (used as the schema key)\n * @param config - Optional configuration for label, min, max, etc.\n * @returns A NumberField descriptor\n */\n number: <const N extends string>(\n name: N,\n config?: Omit<NumberField<N>, \"_type\" | \"_field\" | \"name\">\n ): NumberField<N> => ({\n _type: \"field\",\n _field: \"number\",\n name,\n ...config,\n }),\n\n /**\n * Creates a boolean checkbox field.\n *\n * @param name - The field name (used as the schema key)\n * @param config - Optional configuration for label, etc.\n * @returns A BooleanField descriptor\n */\n boolean: <const N extends string>(\n name: N,\n config?: Omit<BooleanField<N>, \"_type\" | \"_field\" | \"name\">\n ): BooleanField<N> => ({\n _type: \"field\",\n _field: \"boolean\",\n name,\n ...config,\n }),\n\n /**\n * Creates a field with static enum options (known at compile time).\n *\n * Literal types are automatically inferred - no `as const` needed:\n * ```typescript\n * field.enum(\"status\", [\"draft\", \"sent\", \"paid\"])\n * // Schema type: \"draft\" | \"sent\" | \"paid\"\n * ```\n *\n * Options can be strings or objects with `id` and `label`:\n * ```typescript\n * field.enum(\"priority\", [\n * { id: \"low\", label: \"Low Priority\" },\n * { id: \"high\", label: \"High Priority\" },\n * ])\n * ```\n *\n * **Note:** All options must be of the same type (all strings OR all objects).\n * Mixing strings and objects will throw a runtime error.\n *\n * @param name - The field name (used as the schema key)\n * @param options - Array of allowed string values or objects with `id` and `label` properties\n * @param config - Optional configuration for label, etc.\n * @returns A StaticEnumField descriptor\n * @throws Error if options array contains mixed types (strings and objects)\n */\n enum: <const N extends string, const O extends readonly EnumOptionValue[]>(\n name: N,\n options: O,\n config?: Omit<StaticEnumField<N, O>, \"_type\" | \"_field\" | \"name\" | \"options\">\n ): StaticEnumField<N, O> => {\n // Validate that all options are of the same type (all strings or all objects)\n if (options.length > 0) {\n const first = options[0];\n // Runtime check: TypeScript allows mixed arrays, but we enforce homogeneity\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const firstIsObject = typeof first === \"object\" && first !== null;\n\n // Check all items match the type of the first item\n for (const opt of options) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition\n const optIsObject = typeof opt === \"object\" && opt !== null;\n if (optIsObject !== firstIsObject) {\n throw new Error(\n `field.enum(\"${name}\"): options must be all strings or all objects with {id, label}, not mixed. ` +\n `Received mixed types in options array.`\n );\n }\n }\n\n // Validate object options have required properties\n if (firstIsObject) {\n for (const opt of options) {\n const obj = opt as { id?: unknown; label?: unknown };\n if (typeof obj.id !== \"string\" || typeof obj.label !== \"string\") {\n throw new Error(\n `field.enum(\"${name}\"): object options must have string \"id\" and \"label\" properties. ` +\n `Received: ${JSON.stringify(opt)}`\n );\n }\n }\n }\n }\n\n return {\n _type: \"field\",\n _field: \"enum\",\n name,\n options,\n ...config,\n };\n },\n\n /**\n * Creates a field with dynamic enum options (fetched from a data source at runtime).\n *\n * The data source must be registered in DataSourceRegistry via module augmentation:\n * ```typescript\n * declare module \"@formspec/core\" {\n * interface DataSourceRegistry {\n * countries: { id: string; code: string; name: string };\n * }\n * }\n *\n * field.dynamicEnum(\"country\", \"countries\", { label: \"Country\" })\n * ```\n *\n * @param name - The field name (used as the schema key)\n * @param source - The data source key (must be in DataSourceRegistry)\n * @param config - Optional configuration for label, params, etc.\n * @returns A DynamicEnumField descriptor\n */\n dynamicEnum: <const N extends string, const Source extends string>(\n name: N,\n source: Source,\n config?: Omit<DynamicEnumField<N, Source>, \"_type\" | \"_field\" | \"name\" | \"source\">\n ): DynamicEnumField<N, Source> => ({\n _type: \"field\",\n _field: \"dynamic_enum\",\n name,\n source,\n ...config,\n }),\n\n /**\n * Creates a field that loads its schema dynamically (e.g., from an extension).\n *\n * @param name - The field name (used as the schema key)\n * @param schemaSource - Identifier for the schema source\n * @param config - Optional configuration for label, etc.\n * @returns A DynamicSchemaField descriptor\n */\n dynamicSchema: <const N extends string>(\n name: N,\n schemaSource: string,\n config?: Omit<DynamicSchemaField<N>, \"_type\" | \"_field\" | \"name\" | \"schemaSource\">\n ): DynamicSchemaField<N> => ({\n _type: \"field\",\n _field: \"dynamic_schema\",\n name,\n schemaSource,\n ...config,\n }),\n\n /**\n * Creates an array field containing repeating items.\n *\n * Use this for lists of values (e.g., multiple addresses, line items).\n *\n * @example\n * ```typescript\n * field.array(\"addresses\",\n * field.text(\"street\", { label: \"Street\" }),\n * field.text(\"city\", { label: \"City\" }),\n * field.text(\"zip\", { label: \"ZIP Code\" }),\n * )\n * ```\n *\n * @param name - The field name (used as the schema key)\n * @param items - The form elements that define each array item\n * @returns An ArrayField descriptor\n */\n array: <const N extends string, const Items extends readonly FormElement[]>(\n name: N,\n ...items: Items\n ): ArrayField<N, Items> => ({\n _type: \"field\",\n _field: \"array\",\n name,\n items,\n }),\n\n /**\n * Creates an array field with additional configuration options.\n *\n * @example\n * ```typescript\n * field.arrayWithConfig(\"lineItems\", {\n * label: \"Line Items\",\n * minItems: 1,\n * maxItems: 10,\n * },\n * field.text(\"description\"),\n * field.number(\"quantity\"),\n * )\n * ```\n *\n * @param name - The field name (used as the schema key)\n * @param config - Configuration for label, minItems, maxItems, etc.\n * @param items - The form elements that define each array item\n * @returns An ArrayField descriptor\n */\n arrayWithConfig: <const N extends string, const Items extends readonly FormElement[]>(\n name: N,\n config: Omit<ArrayField<N, Items>, \"_type\" | \"_field\" | \"name\" | \"items\">,\n ...items: Items\n ): ArrayField<N, Items> => ({\n _type: \"field\",\n _field: \"array\",\n name,\n items,\n ...config,\n }),\n\n /**\n * Creates an object field containing nested properties.\n *\n * Use this for grouping related fields under a single key in the schema.\n *\n * @example\n * ```typescript\n * field.object(\"address\",\n * field.text(\"street\", { label: \"Street\" }),\n * field.text(\"city\", { label: \"City\" }),\n * field.text(\"zip\", { label: \"ZIP Code\" }),\n * )\n * ```\n *\n * @param name - The field name (used as the schema key)\n * @param properties - The form elements that define the object's properties\n * @returns An ObjectField descriptor\n */\n object: <const N extends string, const Properties extends readonly FormElement[]>(\n name: N,\n ...properties: Properties\n ): ObjectField<N, Properties> => ({\n _type: \"field\",\n _field: \"object\",\n name,\n properties,\n }),\n\n /**\n * Creates an object field with additional configuration options.\n *\n * @example\n * ```typescript\n * field.objectWithConfig(\"billingAddress\", { label: \"Billing Address\", required: true },\n * field.text(\"street\"),\n * field.text(\"city\"),\n * )\n * ```\n *\n * @param name - The field name (used as the schema key)\n * @param config - Configuration for label, required, etc.\n * @param properties - The form elements that define the object's properties\n * @returns An ObjectField descriptor\n */\n objectWithConfig: <const N extends string, const Properties extends readonly FormElement[]>(\n name: N,\n config: Omit<ObjectField<N, Properties>, \"_type\" | \"_field\" | \"name\" | \"properties\">,\n ...properties: Properties\n ): ObjectField<N, Properties> => ({\n _type: \"field\",\n _field: \"object\",\n name,\n properties,\n ...config,\n }),\n};\n","/**\n * Predicate builder functions for conditional logic.\n *\n * These functions create predicates for use with `when()`:\n * - `is()` - Check if a field equals a specific value\n *\n * @example\n * ```typescript\n * when(is(\"status\", \"draft\"),\n * field.text(\"notes\"),\n * )\n * ```\n */\n\nimport type { EqualsPredicate } from \"@formspec/core\";\n\n/**\n * Creates an equality predicate that checks if a field equals a specific value.\n *\n * Use this with `when()` to create readable conditional expressions:\n *\n * @example\n * ```typescript\n * // Show cardNumber field when paymentMethod is \"card\"\n * when(is(\"paymentMethod\", \"card\"),\n * field.text(\"cardNumber\", { label: \"Card Number\" }),\n * )\n * ```\n *\n * @typeParam K - The field name (inferred as string literal)\n * @typeParam V - The value type (inferred as literal)\n * @param field - The name of the field to check\n * @param value - The value the field must equal\n * @returns An EqualsPredicate for use with `when()`\n * @public\n */\nexport function is<const K extends string, const V>(field: K, value: V): EqualsPredicate<K, V> {\n return {\n _predicate: \"equals\",\n field,\n value,\n };\n}\n","/**\n * Runtime validation for form specifications.\n *\n * Validates:\n * - No duplicate field names at the same scope level\n * - All field references in conditionals point to existing fields\n */\n\nimport type { FormElement, Group, Conditional, ArrayField, ObjectField } from \"@formspec/core\";\n\n/**\n * Validation issue severity levels.\n *\n * @public\n */\nexport type ValidationSeverity = \"error\" | \"warning\";\n\n/**\n * A validation issue found in a form specification.\n *\n * @public\n */\nexport interface ValidationIssue {\n /** Severity of the issue */\n severity: ValidationSeverity;\n /** Human-readable message describing the issue */\n message: string;\n /** Path to the element with the issue (e.g., \"group.fieldName\") */\n path: string;\n}\n\n/**\n * Result of validating a form specification.\n *\n * @public\n */\nexport interface ValidationResult {\n /** Whether the form is valid (no errors, warnings are ok) */\n valid: boolean;\n /** List of validation issues found */\n issues: ValidationIssue[];\n}\n\n/**\n * Collects all field names from a list of form elements.\n * Returns a Map of field name to count (for duplicate detection).\n */\nfunction collectFieldNames(\n elements: readonly FormElement[],\n path = \"\"\n): Map<string, { count: number; paths: string[] }> {\n const fieldNames = new Map<string, { count: number; paths: string[] }>();\n\n function visit(elements: readonly FormElement[], currentPath: string): void {\n for (const element of elements) {\n switch (element._type) {\n case \"field\": {\n // After type narrowing, element is known to be AnyField\n const field = element;\n const fieldPath = currentPath ? `${currentPath}.${field.name}` : field.name;\n const existing = fieldNames.get(field.name);\n if (existing !== undefined) {\n existing.count++;\n existing.paths.push(fieldPath);\n } else {\n fieldNames.set(field.name, { count: 1, paths: [fieldPath] });\n }\n\n // Recurse into array items and object properties\n if (field._field === \"array\") {\n const arrayField = field as ArrayField<string, readonly FormElement[]>;\n visit(arrayField.items, `${fieldPath}[]`);\n } else if (field._field === \"object\") {\n const objectField = field as ObjectField<string, readonly FormElement[]>;\n visit(objectField.properties, fieldPath);\n }\n break;\n }\n\n case \"group\": {\n const group = element as Group<readonly FormElement[]>;\n const groupPath = currentPath ? `${currentPath}.[${group.label}]` : `[${group.label}]`;\n visit(group.elements, groupPath);\n break;\n }\n\n case \"conditional\": {\n const conditional = element as Conditional<string, unknown, readonly FormElement[]>;\n const conditionalPath = currentPath\n ? `${currentPath}.when(${conditional.field})`\n : `when(${conditional.field})`;\n visit(conditional.elements, conditionalPath);\n break;\n }\n }\n }\n }\n\n visit(elements, path);\n return fieldNames;\n}\n\n/**\n * Collects all field references from conditionals.\n * Returns a list of { fieldName, path } for each reference.\n */\nfunction collectConditionalReferences(\n elements: readonly FormElement[],\n path = \"\"\n): { fieldName: string; path: string }[] {\n const references: { fieldName: string; path: string }[] = [];\n\n function visit(elements: readonly FormElement[], currentPath: string): void {\n for (const element of elements) {\n switch (element._type) {\n case \"field\": {\n // After type narrowing, element is known to be AnyField\n const field = element;\n const fieldPath = currentPath ? `${currentPath}.${field.name}` : field.name;\n\n // Recurse into array items and object properties\n if (field._field === \"array\") {\n const arrayField = field as ArrayField<string, readonly FormElement[]>;\n visit(arrayField.items, `${fieldPath}[]`);\n } else if (field._field === \"object\") {\n const objectField = field as ObjectField<string, readonly FormElement[]>;\n visit(objectField.properties, fieldPath);\n }\n break;\n }\n\n case \"group\": {\n const group = element as Group<readonly FormElement[]>;\n const groupPath = currentPath ? `${currentPath}.[${group.label}]` : `[${group.label}]`;\n visit(group.elements, groupPath);\n break;\n }\n\n case \"conditional\": {\n const conditional = element as Conditional<string, unknown, readonly FormElement[]>;\n const conditionalPath = currentPath\n ? `${currentPath}.when(${conditional.field})`\n : `when(${conditional.field})`;\n\n // Record this reference\n references.push({\n fieldName: conditional.field,\n path: conditionalPath,\n });\n\n // Continue visiting children\n visit(conditional.elements, conditionalPath);\n break;\n }\n }\n }\n }\n\n visit(elements, path);\n return references;\n}\n\n/**\n * Validates a form specification for common issues.\n *\n * Checks for:\n * - Duplicate field names at the root level (warning)\n * - References to non-existent fields in conditionals (error)\n *\n * @example\n * ```typescript\n * const form = formspec(\n * field.text(\"name\"),\n * field.text(\"name\"), // Duplicate!\n * when(\"nonExistent\", \"value\", // Reference to non-existent field!\n * field.text(\"extra\"),\n * ),\n * );\n *\n * const result = validateForm(form.elements);\n * // result.valid === false\n * // result.issues contains duplicate and reference errors\n * ```\n *\n * @param elements - The form elements to validate\n * @returns Validation result with any issues found\n *\n * @public\n */\nexport function validateForm(elements: readonly FormElement[]): ValidationResult {\n const issues: ValidationIssue[] = [];\n\n // Collect all field names\n const fieldNames = collectFieldNames(elements);\n\n // Check for duplicates at root level - duplicates are errors because they cause data loss\n for (const [name, info] of fieldNames) {\n if (info.count > 1 && info.paths[0] !== undefined) {\n issues.push({\n severity: \"error\",\n message: `Duplicate field name \"${name}\" found ${String(info.count)} times at: ${info.paths.join(\", \")}`,\n path: info.paths[0],\n });\n }\n }\n\n // Collect conditional references\n const references = collectConditionalReferences(elements);\n\n // Check that all referenced fields exist\n for (const ref of references) {\n if (!fieldNames.has(ref.fieldName)) {\n issues.push({\n severity: \"error\",\n message: `Conditional references non-existent field \"${ref.fieldName}\"`,\n path: ref.path,\n });\n }\n }\n\n return {\n valid: issues.every((issue) => issue.severity !== \"error\"),\n issues,\n };\n}\n\n/**\n * Logs validation issues to the console.\n *\n * @param result - The validation result to log\n * @param formName - Optional name for the form (for better error messages)\n *\n * @public\n */\nexport function logValidationIssues(result: ValidationResult, formName?: string): void {\n if (result.issues.length === 0) {\n return;\n }\n\n const prefix = formName ? `FormSpec \"${formName}\"` : \"FormSpec\";\n\n for (const issue of result.issues) {\n const location = issue.path ? ` at ${issue.path}` : \"\";\n const message = `${prefix}: ${issue.message}${location}`;\n\n if (issue.severity === \"error\") {\n console.error(message);\n } else {\n console.warn(message);\n }\n }\n}\n","/**\n * Structure builder functions for organizing form elements.\n *\n * These functions create layout and conditional structures:\n * - `group()` - Visual grouping of fields\n * - `when()` - Conditional visibility based on field values\n * - `formspec()` - Top-level form specification\n */\n\nimport type { FormElement, Group, Conditional, FormSpec, Predicate } from \"@formspec/core\";\nimport { validateForm, logValidationIssues } from \"./validation.js\";\n\n/**\n * Options for creating a form specification.\n *\n * @public\n */\nexport interface FormSpecOptions {\n /**\n * Whether to validate the form structure.\n * - `true` or `\"warn\"`: Validate and log warnings/errors to console\n * - `\"throw\"`: Validate and throw an error if validation fails\n * - `false`: Skip validation (default in production for performance)\n *\n * @defaultValue false\n */\n validate?: boolean | \"warn\" | \"throw\";\n\n /**\n * Optional name for the form (used in validation messages).\n */\n name?: string;\n}\n\n/**\n * Creates a visual group of form elements.\n *\n * Groups provide visual organization and can be rendered as fieldsets or sections.\n * The nesting of groups defines the visual hierarchy of the form.\n *\n * @example\n * ```typescript\n * group(\"Customer Information\",\n * field.text(\"name\", { label: \"Name\" }),\n * field.text(\"email\", { label: \"Email\" }),\n * )\n * ```\n *\n * @param label - The group's display label\n * @param elements - The form elements contained in this group\n * @returns A Group descriptor\n *\n * @public\n */\nexport function group<const Elements extends readonly FormElement[]>(\n label: string,\n ...elements: Elements\n): Group<Elements> {\n return { _type: \"group\", label, elements };\n}\n\n/**\n * Creates a conditional wrapper that shows elements based on a predicate.\n *\n * When the predicate evaluates to true, the contained elements are shown.\n * Otherwise, they are hidden (but still part of the schema).\n *\n * @example\n * ```typescript\n * import { is } from \"@formspec/dsl\";\n *\n * field.enum(\"status\", [\"draft\", \"sent\", \"paid\"] as const),\n * when(is(\"status\", \"draft\"),\n * field.text(\"internalNotes\", { label: \"Internal Notes\" }),\n * )\n * ```\n *\n * @param predicate - The condition to evaluate (use `is()` to create)\n * @param elements - The form elements to show when condition is met\n * @returns A Conditional descriptor\n *\n * @public\n */\nexport function when<\n const K extends string,\n const V,\n const Elements extends readonly FormElement[],\n>(predicate: Predicate<K, V>, ...elements: Elements): Conditional<K, V, Elements> {\n return {\n _type: \"conditional\",\n field: predicate.field,\n value: predicate.value,\n elements,\n };\n}\n\n/**\n * Creates a complete form specification.\n *\n * The structure IS the definition:\n * - Nesting with `group()` defines visual layout\n * - Nesting with `when()` defines conditional visibility\n * - Field type implies control type (text field → text input)\n * - Array position implies field ordering\n *\n * Schema is automatically inferred from all fields in the structure.\n *\n * @example\n * ```typescript\n * const InvoiceForm = formspec(\n * group(\"Customer\",\n * field.text(\"customerName\", { label: \"Customer Name\" }),\n * field.dynamicEnum(\"country\", \"fetch_countries\", { label: \"Country\" }),\n * ),\n * group(\"Invoice Details\",\n * field.number(\"amount\", { label: \"Amount\", min: 0 }),\n * field.enum(\"status\", [\"draft\", \"sent\", \"paid\"] as const),\n * when(is(\"status\", \"draft\"),\n * field.text(\"internalNotes\", { label: \"Internal Notes\" }),\n * ),\n * ),\n * );\n * ```\n *\n * @param elements - The top-level form elements\n * @returns A FormSpec descriptor\n *\n * @public\n */\nexport function formspec<const Elements extends readonly FormElement[]>(\n ...elements: Elements\n): FormSpec<Elements> {\n return { elements };\n}\n\n/**\n * Creates a complete form specification with validation options.\n *\n * @example\n * ```typescript\n * const form = formspecWithValidation(\n * { validate: true, name: \"MyForm\" },\n * field.text(\"name\"),\n * field.enum(\"status\", [\"draft\", \"sent\"] as const),\n * when(is(\"status\", \"draft\"),\n * field.text(\"notes\"),\n * ),\n * );\n * ```\n *\n * @param options - Validation options\n * @param elements - The top-level form elements\n * @returns A FormSpec descriptor\n *\n * @public\n */\nexport function formspecWithValidation<const Elements extends readonly FormElement[]>(\n options: FormSpecOptions,\n ...elements: Elements\n): FormSpec<Elements> {\n // Run validation if requested\n if (options.validate) {\n const result = validateForm(elements);\n\n if (options.validate === \"throw\" && !result.valid) {\n const errors = result.issues\n .filter((i) => i.severity === \"error\")\n .map((i) => i.message)\n .join(\"; \");\n throw new Error(`Form validation failed: ${errors}`);\n }\n\n if (options.validate === true || options.validate === \"warn\") {\n logValidationIssues(result, options.name);\n }\n }\n\n return { elements };\n}\n"],"mappings":"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","names":["field","elements","field","group"]}
|
package/dist/inference.d.ts
CHANGED
|
@@ -30,6 +30,8 @@ import type { TextField, NumberField, BooleanField, StaticEnumField, EnumOption,
|
|
|
30
30
|
* type T4 = InferFieldValue<ArrayField<"items", [TextField<"name">]>>; // { name: string }[]
|
|
31
31
|
* type T5 = InferFieldValue<ObjectField<"address", [TextField<"city">]>>; // { city: string }
|
|
32
32
|
* ```
|
|
33
|
+
*
|
|
34
|
+
* @public
|
|
33
35
|
*/
|
|
34
36
|
export 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;
|
|
35
37
|
/**
|
|
@@ -38,12 +40,16 @@ export type InferFieldValue<F> = F extends TextField<string> ? string : F extend
|
|
|
38
40
|
* - Field elements return themselves
|
|
39
41
|
* - Groups extract fields from all child elements
|
|
40
42
|
* - Conditionals extract fields from all child elements
|
|
43
|
+
*
|
|
44
|
+
* @public
|
|
41
45
|
*/
|
|
42
46
|
export type ExtractFields<E> = E extends AnyField ? E : E extends Group<infer Elements> ? ExtractFieldsFromArray<Elements> : E extends Conditional<string, unknown, infer Elements> ? ExtractFieldsFromArray<Elements> : never;
|
|
43
47
|
/**
|
|
44
48
|
* Extracts fields from an array of elements.
|
|
45
49
|
*
|
|
46
50
|
* Recursively processes each element and unions the results.
|
|
51
|
+
*
|
|
52
|
+
* @public
|
|
47
53
|
*/
|
|
48
54
|
export type ExtractFieldsFromArray<Elements> = Elements extends readonly [
|
|
49
55
|
infer First,
|
|
@@ -66,6 +72,8 @@ export type ExtractFieldsFromArray<Elements> = Elements extends readonly [
|
|
|
66
72
|
*
|
|
67
73
|
* // ExtractNonConditionalFields extracts: TextField<"name"> | NumberField<"age">
|
|
68
74
|
* ```
|
|
75
|
+
*
|
|
76
|
+
* @public
|
|
69
77
|
*/
|
|
70
78
|
export type ExtractNonConditionalFields<E> = E extends AnyField ? E : E extends Group<infer Elements> ? ExtractNonConditionalFieldsFromArray<Elements> : E extends Conditional<string, unknown, infer _Elements> ? never : never;
|
|
71
79
|
/**
|
|
@@ -77,6 +85,8 @@ export type ExtractNonConditionalFields<E> = E extends AnyField ? E : E extends
|
|
|
77
85
|
* type Fields = ExtractNonConditionalFieldsFromArray<Elements>;
|
|
78
86
|
* // TextField<"name"> | NumberField<"age">
|
|
79
87
|
* ```
|
|
88
|
+
*
|
|
89
|
+
* @public
|
|
80
90
|
*/
|
|
81
91
|
export type ExtractNonConditionalFieldsFromArray<Elements> = Elements extends readonly [
|
|
82
92
|
infer First,
|
|
@@ -99,6 +109,8 @@ export type ExtractNonConditionalFieldsFromArray<Elements> = Elements extends re
|
|
|
99
109
|
*
|
|
100
110
|
* // ExtractConditionalFields extracts: TextField<"company"> | TextField<"taxId">
|
|
101
111
|
* ```
|
|
112
|
+
*
|
|
113
|
+
* @public
|
|
102
114
|
*/
|
|
103
115
|
export type ExtractConditionalFields<E> = E extends AnyField ? never : E extends Group<infer Elements> ? ExtractConditionalFieldsFromArray<Elements> : E extends Conditional<string, unknown, infer Elements> ? ExtractFieldsFromArray<Elements> : never;
|
|
104
116
|
/**
|
|
@@ -113,6 +125,8 @@ export type ExtractConditionalFields<E> = E extends AnyField ? never : E extends
|
|
|
113
125
|
* type Fields = ExtractConditionalFieldsFromArray<Elements>;
|
|
114
126
|
* // TextField<"company">
|
|
115
127
|
* ```
|
|
128
|
+
*
|
|
129
|
+
* @public
|
|
116
130
|
*/
|
|
117
131
|
export type ExtractConditionalFieldsFromArray<Elements> = Elements extends readonly [
|
|
118
132
|
infer First,
|
|
@@ -122,6 +136,8 @@ export type ExtractConditionalFieldsFromArray<Elements> = Elements extends reado
|
|
|
122
136
|
* Builds a schema type from extracted fields.
|
|
123
137
|
*
|
|
124
138
|
* Maps field names to their inferred value types.
|
|
139
|
+
*
|
|
140
|
+
* @public
|
|
125
141
|
*/
|
|
126
142
|
export type BuildSchema<Fields> = {
|
|
127
143
|
[F in Fields as F extends {
|
|
@@ -144,6 +160,8 @@ export type BuildSchema<Fields> = {
|
|
|
144
160
|
* type Clean = FlattenIntersection<{ a: string } & { b: number }>;
|
|
145
161
|
* // Displays as: { a: string; b: number }
|
|
146
162
|
* ```
|
|
163
|
+
*
|
|
164
|
+
* @public
|
|
147
165
|
*/
|
|
148
166
|
export type FlattenIntersection<T> = {
|
|
149
167
|
[K in keyof T]: T[K];
|
|
@@ -175,6 +193,8 @@ export type FlattenIntersection<T> = {
|
|
|
175
193
|
* type ConditionalSchema = InferSchema<typeof formWithConditional.elements>;
|
|
176
194
|
* // { type: "a" | "b"; aField?: string }
|
|
177
195
|
* ```
|
|
196
|
+
*
|
|
197
|
+
* @public
|
|
178
198
|
*/
|
|
179
199
|
export type InferSchema<Elements extends readonly FormElement[]> = FlattenIntersection<BuildSchema<ExtractNonConditionalFieldsFromArray<Elements>> & Partial<BuildSchema<ExtractConditionalFieldsFromArray<Elements>>>>;
|
|
180
200
|
/**
|
|
@@ -187,6 +207,8 @@ export type InferSchema<Elements extends readonly FormElement[]> = FlattenInters
|
|
|
187
207
|
* const form = formspec(...);
|
|
188
208
|
* type Schema = InferFormSchema<typeof form>;
|
|
189
209
|
* ```
|
|
210
|
+
*
|
|
211
|
+
* @public
|
|
190
212
|
*/
|
|
191
213
|
export type InferFormSchema<F extends FormSpec<readonly FormElement[]>> = F extends FormSpec<infer Elements> ? InferSchema<Elements> : never;
|
|
192
214
|
//# sourceMappingURL=inference.d.ts.map
|
package/dist/inference.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inference.d.ts","sourceRoot":"","sources":["../src/inference.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACZ,eAAe,EACf,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,UAAU,EACV,WAAW,EACX,QAAQ,EACR,KAAK,EACL,WAAW,EACX,WAAW,EACX,QAAQ,EACR,mBAAmB,EACpB,MAAM,gBAAgB,CAAC;AAExB
|
|
1
|
+
{"version":3,"file":"inference.d.ts","sourceRoot":"","sources":["../src/inference.ts"],"names":[],"mappings":"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"}
|
package/dist/structure.d.ts
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
import type { FormElement, Group, Conditional, FormSpec, Predicate } from "@formspec/core";
|
|
10
10
|
/**
|
|
11
11
|
* Options for creating a form specification.
|
|
12
|
+
*
|
|
13
|
+
* @public
|
|
12
14
|
*/
|
|
13
15
|
export interface FormSpecOptions {
|
|
14
16
|
/**
|
|
@@ -42,6 +44,8 @@ export interface FormSpecOptions {
|
|
|
42
44
|
* @param label - The group's display label
|
|
43
45
|
* @param elements - The form elements contained in this group
|
|
44
46
|
* @returns A Group descriptor
|
|
47
|
+
*
|
|
48
|
+
* @public
|
|
45
49
|
*/
|
|
46
50
|
export declare function group<const Elements extends readonly FormElement[]>(label: string, ...elements: Elements): Group<Elements>;
|
|
47
51
|
/**
|
|
@@ -63,6 +67,8 @@ export declare function group<const Elements extends readonly FormElement[]>(lab
|
|
|
63
67
|
* @param predicate - The condition to evaluate (use `is()` to create)
|
|
64
68
|
* @param elements - The form elements to show when condition is met
|
|
65
69
|
* @returns A Conditional descriptor
|
|
70
|
+
*
|
|
71
|
+
* @public
|
|
66
72
|
*/
|
|
67
73
|
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>;
|
|
68
74
|
/**
|
|
@@ -95,6 +101,8 @@ export declare function when<const K extends string, const V, const Elements ext
|
|
|
95
101
|
*
|
|
96
102
|
* @param elements - The top-level form elements
|
|
97
103
|
* @returns A FormSpec descriptor
|
|
104
|
+
*
|
|
105
|
+
* @public
|
|
98
106
|
*/
|
|
99
107
|
export declare function formspec<const Elements extends readonly FormElement[]>(...elements: Elements): FormSpec<Elements>;
|
|
100
108
|
/**
|
|
@@ -115,6 +123,8 @@ export declare function formspec<const Elements extends readonly FormElement[]>(
|
|
|
115
123
|
* @param options - Validation options
|
|
116
124
|
* @param elements - The top-level form elements
|
|
117
125
|
* @returns A FormSpec descriptor
|
|
126
|
+
*
|
|
127
|
+
* @public
|
|
118
128
|
*/
|
|
119
129
|
export declare function formspecWithValidation<const Elements extends readonly FormElement[]>(options: FormSpecOptions, ...elements: Elements): FormSpec<Elements>;
|
|
120
130
|
//# sourceMappingURL=structure.d.ts.map
|
package/dist/structure.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"structure.d.ts","sourceRoot":"","sources":["../src/structure.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG3F
|
|
1
|
+
{"version":3,"file":"structure.d.ts","sourceRoot":"","sources":["../src/structure.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG3F;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;IAEtC;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,KAAK,CAAC,KAAK,CAAC,QAAQ,SAAS,SAAS,WAAW,EAAE,EACjE,KAAK,EAAE,MAAM,EACb,GAAG,QAAQ,EAAE,QAAQ,GACpB,KAAK,CAAC,QAAQ,CAAC,CAEjB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,IAAI,CAClB,KAAK,CAAC,CAAC,SAAS,MAAM,EACtB,KAAK,CAAC,CAAC,EACP,KAAK,CAAC,QAAQ,SAAS,SAAS,WAAW,EAAE,EAC7C,SAAS,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,QAAQ,GAAG,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAOhF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,QAAQ,CAAC,KAAK,CAAC,QAAQ,SAAS,SAAS,WAAW,EAAE,EACpE,GAAG,QAAQ,EAAE,QAAQ,GACpB,QAAQ,CAAC,QAAQ,CAAC,CAEpB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,CAAC,QAAQ,SAAS,SAAS,WAAW,EAAE,EAClF,OAAO,EAAE,eAAe,EACxB,GAAG,QAAQ,EAAE,QAAQ,GACpB,QAAQ,CAAC,QAAQ,CAAC,CAmBpB"}
|
package/dist/validation.d.ts
CHANGED
|
@@ -8,10 +8,14 @@
|
|
|
8
8
|
import type { FormElement } from "@formspec/core";
|
|
9
9
|
/**
|
|
10
10
|
* Validation issue severity levels.
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
11
13
|
*/
|
|
12
14
|
export type ValidationSeverity = "error" | "warning";
|
|
13
15
|
/**
|
|
14
16
|
* A validation issue found in a form specification.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
15
19
|
*/
|
|
16
20
|
export interface ValidationIssue {
|
|
17
21
|
/** Severity of the issue */
|
|
@@ -23,6 +27,8 @@ export interface ValidationIssue {
|
|
|
23
27
|
}
|
|
24
28
|
/**
|
|
25
29
|
* Result of validating a form specification.
|
|
30
|
+
*
|
|
31
|
+
* @public
|
|
26
32
|
*/
|
|
27
33
|
export interface ValidationResult {
|
|
28
34
|
/** Whether the form is valid (no errors, warnings are ok) */
|
|
@@ -54,6 +60,8 @@ export interface ValidationResult {
|
|
|
54
60
|
*
|
|
55
61
|
* @param elements - The form elements to validate
|
|
56
62
|
* @returns Validation result with any issues found
|
|
63
|
+
*
|
|
64
|
+
* @public
|
|
57
65
|
*/
|
|
58
66
|
export declare function validateForm(elements: readonly FormElement[]): ValidationResult;
|
|
59
67
|
/**
|
|
@@ -61,6 +69,8 @@ export declare function validateForm(elements: readonly FormElement[]): Validati
|
|
|
61
69
|
*
|
|
62
70
|
* @param result - The validation result to log
|
|
63
71
|
* @param formName - Optional name for the form (for better error messages)
|
|
72
|
+
*
|
|
73
|
+
* @public
|
|
64
74
|
*/
|
|
65
75
|
export declare function logValidationIssues(result: ValidationResult, formName?: string): void;
|
|
66
76
|
//# sourceMappingURL=validation.d.ts.map
|
package/dist/validation.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAA+C,MAAM,gBAAgB,CAAC;AAE/F
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,WAAW,EAA+C,MAAM,gBAAgB,CAAC;AAE/F;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,SAAS,CAAC;AAErD;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,4BAA4B;IAC5B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6DAA6D;IAC7D,KAAK,EAAE,OAAO,CAAC;IACf,sCAAsC;IACtC,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AAyHD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,gBAAgB,CAmC/E;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,gBAAgB,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAiBrF"}
|
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.21",
|
|
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.21"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"tsd": "^0.31.0",
|