@formspec/dsl 0.1.0-alpha.14 → 0.1.0-alpha.16

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 CHANGED
@@ -214,7 +214,7 @@ export declare const field: {
214
214
  * Mixing strings and objects will throw a runtime error.
215
215
  *
216
216
  * @param name - The field name (used as the schema key)
217
- * @param options - Array of allowed string values or {id, label} objects
217
+ * @param options - Array of allowed string values or objects with `id` and `label` properties
218
218
  * @param config - Optional configuration for label, etc.
219
219
  * @returns A StaticEnumField descriptor
220
220
  * @throws Error if options array contains mixed types (strings and objects)
package/dist/field.d.ts CHANGED
@@ -64,7 +64,7 @@ export declare const field: {
64
64
  * Mixing strings and objects will throw a runtime error.
65
65
  *
66
66
  * @param name - The field name (used as the schema key)
67
- * @param options - Array of allowed string values or {id, label} objects
67
+ * @param options - Array of allowed string values or objects with `id` and `label` properties
68
68
  * @param config - Optional configuration for label, etc.
69
69
  * @returns A StaticEnumField descriptor
70
70
  * @throws Error if options array contains mixed types (strings and objects)
package/dist/index.cjs CHANGED
@@ -93,7 +93,7 @@ var field = {
93
93
  * Mixing strings and objects will throw a runtime error.
94
94
  *
95
95
  * @param name - The field name (used as the schema key)
96
- * @param options - Array of allowed string values or {id, label} objects
96
+ * @param options - Array of allowed string values or objects with `id` and `label` properties
97
97
  * @param config - Optional configuration for label, etc.
98
98
  * @returns A StaticEnumField descriptor
99
99
  * @throws Error if options array contains mixed types (strings and objects)
@@ -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 {id, label} objects\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 */\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"]}
package/dist/index.js CHANGED
@@ -60,7 +60,7 @@ var field = {
60
60
  * Mixing strings and objects will throw a runtime error.
61
61
  *
62
62
  * @param name - The field name (used as the schema key)
63
- * @param options - Array of allowed string values or {id, label} objects
63
+ * @param options - Array of allowed string values or objects with `id` and `label` properties
64
64
  * @param config - Optional configuration for label, etc.
65
65
  * @returns A StaticEnumField descriptor
66
66
  * @throws Error if options array contains mixed types (strings and objects)
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 {id, label} objects\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 */\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formspec/dsl",
3
- "version": "0.1.0-alpha.14",
3
+ "version": "0.1.0-alpha.16",
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.14"
21
+ "@formspec/core": "0.1.0-alpha.16"
22
22
  },
23
23
  "devDependencies": {
24
24
  "tsd": "^0.31.0",