@formspec/dsl 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Predicate builder functions for conditional logic.
3
+ *
4
+ * These functions create predicates for use with `when()`:
5
+ * - `is()` - Check if a field equals a specific value
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * when(is("status", "draft"),
10
+ * field.text("notes"),
11
+ * )
12
+ * ```
13
+ */
14
+ /**
15
+ * Creates an equality predicate that checks if a field equals a specific value.
16
+ *
17
+ * Use this with `when()` to create readable conditional expressions:
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * // Show cardNumber field when paymentMethod is "card"
22
+ * when(is("paymentMethod", "card"),
23
+ * field.text("cardNumber", { label: "Card Number" }),
24
+ * )
25
+ * ```
26
+ *
27
+ * @typeParam K - The field name (inferred as string literal)
28
+ * @typeParam V - The value type (inferred as literal)
29
+ * @param field - The name of the field to check
30
+ * @param value - The value the field must equal
31
+ * @returns An EqualsPredicate for use with `when()`
32
+ */
33
+ export function is(field, value) {
34
+ return {
35
+ _predicate: "equals",
36
+ field,
37
+ value,
38
+ };
39
+ }
40
+ //# sourceMappingURL=predicate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predicate.js","sourceRoot":"","sources":["../src/predicate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,EAAE,CAChB,KAAQ,EACR,KAAQ;IAER,OAAO;QACL,UAAU,EAAE,QAAQ;QACpB,KAAK;QACL,KAAK;KACN,CAAC;AACJ,CAAC"}
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Structure builder functions for organizing form elements.
3
+ *
4
+ * These functions create layout and conditional structures:
5
+ * - `group()` - Visual grouping of fields
6
+ * - `when()` - Conditional visibility based on field values
7
+ * - `formspec()` - Top-level form specification
8
+ */
9
+ import type { FormElement, Group, Conditional, FormSpec, Predicate } from "@formspec/core";
10
+ /**
11
+ * Options for creating a form specification.
12
+ */
13
+ export interface FormSpecOptions {
14
+ /**
15
+ * Whether to validate the form structure.
16
+ * - `true` or `"warn"`: Validate and log warnings/errors to console
17
+ * - `"throw"`: Validate and throw an error if validation fails
18
+ * - `false`: Skip validation (default in production for performance)
19
+ *
20
+ * @defaultValue false
21
+ */
22
+ validate?: boolean | "warn" | "throw";
23
+ /**
24
+ * Optional name for the form (used in validation messages).
25
+ */
26
+ name?: string;
27
+ }
28
+ /**
29
+ * Creates a visual group of form elements.
30
+ *
31
+ * Groups provide visual organization and can be rendered as fieldsets or sections.
32
+ * The nesting of groups defines the visual hierarchy of the form.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * group("Customer Information",
37
+ * field.text("name", { label: "Name" }),
38
+ * field.text("email", { label: "Email" }),
39
+ * )
40
+ * ```
41
+ *
42
+ * @param label - The group's display label
43
+ * @param elements - The form elements contained in this group
44
+ * @returns A Group descriptor
45
+ */
46
+ export declare function group<const Elements extends readonly FormElement[]>(label: string, ...elements: Elements): Group<Elements>;
47
+ /**
48
+ * Creates a conditional wrapper that shows elements based on a predicate.
49
+ *
50
+ * When the predicate evaluates to true, the contained elements are shown.
51
+ * Otherwise, they are hidden (but still part of the schema).
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * import { is } from "@formspec/dsl";
56
+ *
57
+ * field.enum("status", ["draft", "sent", "paid"] as const),
58
+ * when(is("status", "draft"),
59
+ * field.text("internalNotes", { label: "Internal Notes" }),
60
+ * )
61
+ * ```
62
+ *
63
+ * @param predicate - The condition to evaluate (use `is()` to create)
64
+ * @param elements - The form elements to show when condition is met
65
+ * @returns A Conditional descriptor
66
+ */
67
+ 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
+ /**
69
+ * Creates a complete form specification.
70
+ *
71
+ * The structure IS the definition:
72
+ * - Nesting with `group()` defines visual layout
73
+ * - Nesting with `when()` defines conditional visibility
74
+ * - Field type implies control type (text field → text input)
75
+ * - Array position implies field ordering
76
+ *
77
+ * Schema is automatically inferred from all fields in the structure.
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * const InvoiceForm = formspec(
82
+ * group("Customer",
83
+ * field.text("customerName", { label: "Customer Name" }),
84
+ * field.dynamicEnum("country", "fetch_countries", { label: "Country" }),
85
+ * ),
86
+ * group("Invoice Details",
87
+ * field.number("amount", { label: "Amount", min: 0 }),
88
+ * field.enum("status", ["draft", "sent", "paid"] as const),
89
+ * when(is("status", "draft"),
90
+ * field.text("internalNotes", { label: "Internal Notes" }),
91
+ * ),
92
+ * ),
93
+ * );
94
+ * ```
95
+ *
96
+ * @param elements - The top-level form elements
97
+ * @returns A FormSpec descriptor
98
+ */
99
+ export declare function formspec<const Elements extends readonly FormElement[]>(...elements: Elements): FormSpec<Elements>;
100
+ /**
101
+ * Creates a complete form specification with validation options.
102
+ *
103
+ * @example
104
+ * ```typescript
105
+ * const form = formspecWithValidation(
106
+ * { validate: true, name: "MyForm" },
107
+ * field.text("name"),
108
+ * field.enum("status", ["draft", "sent"] as const),
109
+ * when(is("status", "draft"),
110
+ * field.text("notes"),
111
+ * ),
112
+ * );
113
+ * ```
114
+ *
115
+ * @param options - Validation options
116
+ * @param elements - The top-level form elements
117
+ * @returns A FormSpec descriptor
118
+ */
119
+ export declare function formspecWithValidation<const Elements extends readonly FormElement[]>(options: FormSpecOptions, ...elements: Elements): FormSpec<Elements>;
120
+ //# sourceMappingURL=structure.d.ts.map
@@ -0,0 +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;;GAEG;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;;;;;;;;;;;;;;;;;GAiBG;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;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,IAAI,CAClB,KAAK,CAAC,CAAC,SAAS,MAAM,EACtB,KAAK,CAAC,CAAC,EACP,KAAK,CAAC,QAAQ,SAAS,SAAS,WAAW,EAAE,EAE7C,SAAS,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,GAAG,QAAQ,EAAE,QAAQ,GACpB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAO7B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,QAAQ,CAAC,KAAK,CAAC,QAAQ,SAAS,SAAS,WAAW,EAAE,EACpE,GAAG,QAAQ,EAAE,QAAQ,GACpB,QAAQ,CAAC,QAAQ,CAAC,CAEpB;AAED;;;;;;;;;;;;;;;;;;GAkBG;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"}
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Structure builder functions for organizing form elements.
3
+ *
4
+ * These functions create layout and conditional structures:
5
+ * - `group()` - Visual grouping of fields
6
+ * - `when()` - Conditional visibility based on field values
7
+ * - `formspec()` - Top-level form specification
8
+ */
9
+ import { validateForm, logValidationIssues } from "./validation.js";
10
+ /**
11
+ * Creates a visual group of form elements.
12
+ *
13
+ * Groups provide visual organization and can be rendered as fieldsets or sections.
14
+ * The nesting of groups defines the visual hierarchy of the form.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * group("Customer Information",
19
+ * field.text("name", { label: "Name" }),
20
+ * field.text("email", { label: "Email" }),
21
+ * )
22
+ * ```
23
+ *
24
+ * @param label - The group's display label
25
+ * @param elements - The form elements contained in this group
26
+ * @returns A Group descriptor
27
+ */
28
+ export function group(label, ...elements) {
29
+ return { _type: "group", label, elements };
30
+ }
31
+ /**
32
+ * Creates a conditional wrapper that shows elements based on a predicate.
33
+ *
34
+ * When the predicate evaluates to true, the contained elements are shown.
35
+ * Otherwise, they are hidden (but still part of the schema).
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * import { is } from "@formspec/dsl";
40
+ *
41
+ * field.enum("status", ["draft", "sent", "paid"] as const),
42
+ * when(is("status", "draft"),
43
+ * field.text("internalNotes", { label: "Internal Notes" }),
44
+ * )
45
+ * ```
46
+ *
47
+ * @param predicate - The condition to evaluate (use `is()` to create)
48
+ * @param elements - The form elements to show when condition is met
49
+ * @returns A Conditional descriptor
50
+ */
51
+ export function when(predicate, ...elements) {
52
+ return {
53
+ _type: "conditional",
54
+ field: predicate.field,
55
+ value: predicate.value,
56
+ elements,
57
+ };
58
+ }
59
+ /**
60
+ * Creates a complete form specification.
61
+ *
62
+ * The structure IS the definition:
63
+ * - Nesting with `group()` defines visual layout
64
+ * - Nesting with `when()` defines conditional visibility
65
+ * - Field type implies control type (text field → text input)
66
+ * - Array position implies field ordering
67
+ *
68
+ * Schema is automatically inferred from all fields in the structure.
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * const InvoiceForm = formspec(
73
+ * group("Customer",
74
+ * field.text("customerName", { label: "Customer Name" }),
75
+ * field.dynamicEnum("country", "fetch_countries", { label: "Country" }),
76
+ * ),
77
+ * group("Invoice Details",
78
+ * field.number("amount", { label: "Amount", min: 0 }),
79
+ * field.enum("status", ["draft", "sent", "paid"] as const),
80
+ * when(is("status", "draft"),
81
+ * field.text("internalNotes", { label: "Internal Notes" }),
82
+ * ),
83
+ * ),
84
+ * );
85
+ * ```
86
+ *
87
+ * @param elements - The top-level form elements
88
+ * @returns A FormSpec descriptor
89
+ */
90
+ export function formspec(...elements) {
91
+ return { elements };
92
+ }
93
+ /**
94
+ * Creates a complete form specification with validation options.
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * const form = formspecWithValidation(
99
+ * { validate: true, name: "MyForm" },
100
+ * field.text("name"),
101
+ * field.enum("status", ["draft", "sent"] as const),
102
+ * when(is("status", "draft"),
103
+ * field.text("notes"),
104
+ * ),
105
+ * );
106
+ * ```
107
+ *
108
+ * @param options - Validation options
109
+ * @param elements - The top-level form elements
110
+ * @returns A FormSpec descriptor
111
+ */
112
+ export function formspecWithValidation(options, ...elements) {
113
+ // Run validation if requested
114
+ if (options.validate) {
115
+ const result = validateForm(elements);
116
+ if (options.validate === "throw" && !result.valid) {
117
+ const errors = result.issues
118
+ .filter((i) => i.severity === "error")
119
+ .map((i) => i.message)
120
+ .join("; ");
121
+ throw new Error(`Form validation failed: ${errors}`);
122
+ }
123
+ if (options.validate === true || options.validate === "warn") {
124
+ logValidationIssues(result, options.name);
125
+ }
126
+ }
127
+ return { elements };
128
+ }
129
+ //# sourceMappingURL=structure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structure.js","sourceRoot":"","sources":["../src/structure.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAsBpE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,KAAK,CACnB,KAAa,EACb,GAAG,QAAkB;IAErB,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AAC7C,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,IAAI,CAKlB,SAA0B,EAC1B,GAAG,QAAkB;IAErB,OAAO;QACL,KAAK,EAAE,aAAa;QACpB,KAAK,EAAE,SAAS,CAAC,KAAK;QACtB,KAAK,EAAE,SAAS,CAAC,KAAK;QACtB,QAAQ;KACT,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,UAAU,QAAQ,CACtB,GAAG,QAAkB;IAErB,OAAO,EAAE,QAAQ,EAAE,CAAC;AACtB,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAwB,EACxB,GAAG,QAAkB;IAErB,8BAA8B;IAC9B,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;QAEtC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAClD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM;iBACzB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC;iBACrC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;iBACrB,IAAI,CAAC,IAAI,CAAC,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,2BAA2B,MAAM,EAAE,CAAC,CAAC;QACvD,CAAC;QAED,IAAI,OAAO,CAAC,QAAQ,KAAK,IAAI,IAAI,OAAO,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;YAC7D,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,CAAC;AACtB,CAAC"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Runtime validation for form specifications.
3
+ *
4
+ * Validates:
5
+ * - No duplicate field names at the same scope level
6
+ * - All field references in conditionals point to existing fields
7
+ */
8
+ import type { FormElement } from "@formspec/core";
9
+ /**
10
+ * Validation issue severity levels.
11
+ */
12
+ export type ValidationSeverity = "error" | "warning";
13
+ /**
14
+ * A validation issue found in a form specification.
15
+ */
16
+ export interface ValidationIssue {
17
+ /** Severity of the issue */
18
+ severity: ValidationSeverity;
19
+ /** Human-readable message describing the issue */
20
+ message: string;
21
+ /** Path to the element with the issue (e.g., "group.fieldName") */
22
+ path: string;
23
+ }
24
+ /**
25
+ * Result of validating a form specification.
26
+ */
27
+ export interface ValidationResult {
28
+ /** Whether the form is valid (no errors, warnings are ok) */
29
+ valid: boolean;
30
+ /** List of validation issues found */
31
+ issues: ValidationIssue[];
32
+ }
33
+ /**
34
+ * Validates a form specification for common issues.
35
+ *
36
+ * Checks for:
37
+ * - Duplicate field names at the root level (warning)
38
+ * - References to non-existent fields in conditionals (error)
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const form = formspec(
43
+ * field.text("name"),
44
+ * field.text("name"), // Duplicate!
45
+ * when("nonExistent", "value", // Reference to non-existent field!
46
+ * field.text("extra"),
47
+ * ),
48
+ * );
49
+ *
50
+ * const result = validateForm(form.elements);
51
+ * // result.valid === false
52
+ * // result.issues contains duplicate and reference errors
53
+ * ```
54
+ *
55
+ * @param elements - The form elements to validate
56
+ * @returns Validation result with any issues found
57
+ */
58
+ export declare function validateForm(elements: readonly FormElement[]): ValidationResult;
59
+ /**
60
+ * Logs validation issues to the console.
61
+ *
62
+ * @param result - The validation result to log
63
+ * @param formName - Optional name for the form (for better error messages)
64
+ */
65
+ export declare function logValidationIssues(result: ValidationResult, formName?: string): void;
66
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,WAAW,EAMZ,MAAM,gBAAgB,CAAC;AAExB;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,SAAS,CAAC;AAErD;;GAEG;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;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6DAA6D;IAC7D,KAAK,EAAE,OAAO,CAAC;IACf,sCAAsC;IACtC,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AAuHD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,GAAG,gBAAgB,CAmC/E;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,gBAAgB,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAiBrF"}
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Runtime validation for form specifications.
3
+ *
4
+ * Validates:
5
+ * - No duplicate field names at the same scope level
6
+ * - All field references in conditionals point to existing fields
7
+ */
8
+ /**
9
+ * Collects all field names from a list of form elements.
10
+ * Returns a Map of field name to count (for duplicate detection).
11
+ */
12
+ function collectFieldNames(elements, path = "") {
13
+ const fieldNames = new Map();
14
+ function visit(elements, currentPath) {
15
+ for (const element of elements) {
16
+ switch (element._type) {
17
+ case "field": {
18
+ const field = element;
19
+ const fieldPath = currentPath ? `${currentPath}.${field.name}` : field.name;
20
+ const existing = fieldNames.get(field.name);
21
+ if (existing) {
22
+ existing.count++;
23
+ existing.paths.push(fieldPath);
24
+ }
25
+ else {
26
+ fieldNames.set(field.name, { count: 1, paths: [fieldPath] });
27
+ }
28
+ // Recurse into array items and object properties
29
+ if (field._field === "array") {
30
+ const arrayField = field;
31
+ visit(arrayField.items, `${fieldPath}[]`);
32
+ }
33
+ else if (field._field === "object") {
34
+ const objectField = field;
35
+ visit(objectField.properties, fieldPath);
36
+ }
37
+ break;
38
+ }
39
+ case "group": {
40
+ const group = element;
41
+ const groupPath = currentPath ? `${currentPath}.[${group.label}]` : `[${group.label}]`;
42
+ visit(group.elements, groupPath);
43
+ break;
44
+ }
45
+ case "conditional": {
46
+ const conditional = element;
47
+ const conditionalPath = currentPath
48
+ ? `${currentPath}.when(${conditional.field})`
49
+ : `when(${conditional.field})`;
50
+ visit(conditional.elements, conditionalPath);
51
+ break;
52
+ }
53
+ }
54
+ }
55
+ }
56
+ visit(elements, path);
57
+ return fieldNames;
58
+ }
59
+ /**
60
+ * Collects all field references from conditionals.
61
+ * Returns a list of { fieldName, path } for each reference.
62
+ */
63
+ function collectConditionalReferences(elements, path = "") {
64
+ const references = [];
65
+ function visit(elements, currentPath) {
66
+ for (const element of elements) {
67
+ switch (element._type) {
68
+ case "field": {
69
+ const field = element;
70
+ const fieldPath = currentPath ? `${currentPath}.${field.name}` : field.name;
71
+ // Recurse into array items and object properties
72
+ if (field._field === "array") {
73
+ const arrayField = field;
74
+ visit(arrayField.items, `${fieldPath}[]`);
75
+ }
76
+ else if (field._field === "object") {
77
+ const objectField = field;
78
+ visit(objectField.properties, fieldPath);
79
+ }
80
+ break;
81
+ }
82
+ case "group": {
83
+ const group = element;
84
+ const groupPath = currentPath ? `${currentPath}.[${group.label}]` : `[${group.label}]`;
85
+ visit(group.elements, groupPath);
86
+ break;
87
+ }
88
+ case "conditional": {
89
+ const conditional = element;
90
+ const conditionalPath = currentPath
91
+ ? `${currentPath}.when(${conditional.field})`
92
+ : `when(${conditional.field})`;
93
+ // Record this reference
94
+ references.push({
95
+ fieldName: conditional.field,
96
+ path: conditionalPath,
97
+ });
98
+ // Continue visiting children
99
+ visit(conditional.elements, conditionalPath);
100
+ break;
101
+ }
102
+ }
103
+ }
104
+ }
105
+ visit(elements, path);
106
+ return references;
107
+ }
108
+ /**
109
+ * Validates a form specification for common issues.
110
+ *
111
+ * Checks for:
112
+ * - Duplicate field names at the root level (warning)
113
+ * - References to non-existent fields in conditionals (error)
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * const form = formspec(
118
+ * field.text("name"),
119
+ * field.text("name"), // Duplicate!
120
+ * when("nonExistent", "value", // Reference to non-existent field!
121
+ * field.text("extra"),
122
+ * ),
123
+ * );
124
+ *
125
+ * const result = validateForm(form.elements);
126
+ * // result.valid === false
127
+ * // result.issues contains duplicate and reference errors
128
+ * ```
129
+ *
130
+ * @param elements - The form elements to validate
131
+ * @returns Validation result with any issues found
132
+ */
133
+ export function validateForm(elements) {
134
+ const issues = [];
135
+ // Collect all field names
136
+ const fieldNames = collectFieldNames(elements);
137
+ // Check for duplicates at root level
138
+ for (const [name, info] of fieldNames) {
139
+ if (info.count > 1 && info.paths[0] !== undefined) {
140
+ issues.push({
141
+ severity: "warning",
142
+ message: `Duplicate field name "${name}" found ${info.count} times at: ${info.paths.join(", ")}`,
143
+ path: info.paths[0],
144
+ });
145
+ }
146
+ }
147
+ // Collect conditional references
148
+ const references = collectConditionalReferences(elements);
149
+ // Check that all referenced fields exist
150
+ for (const ref of references) {
151
+ if (!fieldNames.has(ref.fieldName)) {
152
+ issues.push({
153
+ severity: "error",
154
+ message: `Conditional references non-existent field "${ref.fieldName}"`,
155
+ path: ref.path,
156
+ });
157
+ }
158
+ }
159
+ return {
160
+ valid: issues.every((issue) => issue.severity !== "error"),
161
+ issues,
162
+ };
163
+ }
164
+ /**
165
+ * Logs validation issues to the console.
166
+ *
167
+ * @param result - The validation result to log
168
+ * @param formName - Optional name for the form (for better error messages)
169
+ */
170
+ export function logValidationIssues(result, formName) {
171
+ if (result.issues.length === 0) {
172
+ return;
173
+ }
174
+ const prefix = formName ? `FormSpec "${formName}"` : "FormSpec";
175
+ for (const issue of result.issues) {
176
+ const location = issue.path ? ` at ${issue.path}` : "";
177
+ const message = `${prefix}: ${issue.message}${location}`;
178
+ if (issue.severity === "error") {
179
+ console.error(message);
180
+ }
181
+ else {
182
+ console.warn(message);
183
+ }
184
+ }
185
+ }
186
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@formspec/dsl",
3
+ "version": "0.1.0-alpha.0",
4
+ "description": "DSL functions for defining FormSpec forms",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/dsl.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/dsl.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "dependencies": {
18
+ "@formspec/core": "0.1.0-alpha.0"
19
+ },
20
+ "devDependencies": {
21
+ "tsd": "^0.31.0",
22
+ "vitest": "^3.0.0"
23
+ },
24
+ "tsd": {
25
+ "directory": "src/__tests__"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "keywords": [],
31
+ "license": "UNLICENSED",
32
+ "scripts": {
33
+ "build": "tsc",
34
+ "clean": "rm -rf dist temp",
35
+ "typecheck": "tsc --noEmit",
36
+ "test": "vitest run",
37
+ "test:types": "tsd",
38
+ "api-extractor": "api-extractor run",
39
+ "api-extractor:local": "api-extractor run --local",
40
+ "api-documenter": "api-documenter markdown -i temp -o docs"
41
+ }
42
+ }