/** * Type-level utilities for inferring schema types from form elements. * * These types allow TypeScript to automatically derive the form's schema type * from its nested element structure. */ import type { TextField, NumberField, BooleanField, StaticEnumField, EnumOption, EnumOptionValue, DynamicEnumField, DynamicSchemaField, ArrayField, ObjectField, AnyField, Group, Conditional, FormElement, FormSpec, DataSourceValueType } from "@formspec/core"; /** * Infers the value type from a single field. * * - TextField returns string * - NumberField returns number * - BooleanField returns boolean * - StaticEnumField returns union of option literals * - DynamicEnumField returns DataSourceValueType (usually string) * - DynamicSchemaField returns Record of string to unknown * - ArrayField returns array of inferred item schema * - ObjectField returns object of inferred property schema * * @example * ```typescript * // Simple fields * type T1 = InferFieldValue>; // string * type T2 = InferFieldValue>; // number * * // Enum fields * type T3 = InferFieldValue>; // "draft" | "sent" * * // Nested fields * type T4 = InferFieldValue]>>; // { name: string }[] * type T5 = InferFieldValue]>>; // { city: string } * ``` * * @public */ export type InferFieldValue = F extends TextField ? string : F extends NumberField ? number : F extends BooleanField ? boolean : F extends StaticEnumField ? O extends readonly EnumOption[] ? O[number]["id"] : O extends readonly string[] ? O[number] : never : F extends DynamicEnumField ? DataSourceValueType : F extends DynamicSchemaField ? Record : F extends ArrayField ? InferSchema[] : F extends ObjectField ? InferSchema : never; /** * Extracts all fields from a single element (recursively). * * - Field elements return themselves * - Groups extract fields from all child elements * - Conditionals extract fields from all child elements * * @public */ export type ExtractFields = E extends AnyField ? E : E extends Group ? ExtractFieldsFromArray : E extends Conditional ? ExtractFieldsFromArray : never; /** * Extracts fields from an array of elements. * * Recursively processes each element and unions the results. * * @public */ export type ExtractFieldsFromArray = Elements extends readonly [ infer First, ...infer Rest ] ? ExtractFields | ExtractFieldsFromArray : never; /** * Extracts fields that are NOT inside conditionals. * These fields are always visible and should be required in the inferred schema. * * @example * ```typescript * // Given a form with conditional and non-conditional fields: * const form = formspec( * field.text("name"), // non-conditional * field.number("age"), // non-conditional * when(is("type", "business"), * field.text("company"), // conditional (skipped) * ), * ); * * // ExtractNonConditionalFields extracts: TextField<"name"> | NumberField<"age"> * ``` * * @public */ export type ExtractNonConditionalFields = E extends AnyField ? E : E extends Group ? ExtractNonConditionalFieldsFromArray : E extends Conditional ? never : never; /** * Extracts non-conditional fields from an array of elements. * * @example * ```typescript * type Elements = readonly [TextField<"name">, NumberField<"age">]; * type Fields = ExtractNonConditionalFieldsFromArray; * // TextField<"name"> | NumberField<"age"> * ``` * * @public */ export type ExtractNonConditionalFieldsFromArray = Elements extends readonly [ infer First, ...infer Rest ] ? ExtractNonConditionalFields | ExtractNonConditionalFieldsFromArray : never; /** * Extracts fields that ARE inside conditionals. * These fields may or may not be visible and should be optional in the inferred schema. * * @example * ```typescript * // Given a form with conditional fields: * const form = formspec( * field.text("name"), // non-conditional (skipped) * when(is("type", "business"), * field.text("company"), // conditional (extracted) * field.text("taxId"), // conditional (extracted) * ), * ); * * // ExtractConditionalFields extracts: TextField<"company"> | TextField<"taxId"> * ``` * * @public */ export type ExtractConditionalFields = E extends AnyField ? never : E extends Group ? ExtractConditionalFieldsFromArray : E extends Conditional ? ExtractFieldsFromArray : never; /** * Extracts conditional fields from an array of elements. * * @example * ```typescript * type Elements = readonly [ * TextField<"name">, * Conditional<"type", "business", readonly [TextField<"company">]> * ]; * type Fields = ExtractConditionalFieldsFromArray; * // TextField<"company"> * ``` * * @public */ export type ExtractConditionalFieldsFromArray = Elements extends readonly [ infer First, ...infer Rest ] ? ExtractConditionalFields | ExtractConditionalFieldsFromArray : never; /** * Builds a schema type from extracted fields. * * Maps field names to their inferred value types. * * @public */ export type BuildSchema = { [F in Fields as F extends { name: infer N extends string; } ? N : never]: F extends AnyField ? InferFieldValue : never; }; /** * Utility type that flattens intersection types into a single object type. * * This improves TypeScript's display of inferred types and ensures * structural equality checks work correctly. * * @example * ```typescript * // Without FlattenIntersection: * type Messy = { a: string } & { b: number }; * // Displays as: { a: string } & { b: number } * * // With FlattenIntersection: * type Clean = FlattenIntersection<{ a: string } & { b: number }>; * // Displays as: { a: string; b: number } * ``` * * @public */ export type FlattenIntersection = { [K in keyof T]: T[K]; } & {}; /** * Infers the complete schema type from a form's elements. * * This is the main inference type that converts a form structure * into its corresponding TypeScript schema type. * * Non-conditional fields are required, conditional fields are optional. * * @example * ```typescript * const form = formspec( * field.text("name"), * field.number("age"), * field.enum("status", ["active", "inactive"] as const), * ); * * type Schema = InferSchema; * // { name: string; age: number; status: "active" | "inactive" } * * // Conditional fields become optional: * const formWithConditional = formspec( * field.enum("type", ["a", "b"] as const), * when(is("type", "a"), field.text("aField")), * ); * type ConditionalSchema = InferSchema; * // { type: "a" | "b"; aField?: string } * ``` * * @public */ export type InferSchema = FlattenIntersection> & Partial>>>; /** * Infers the schema type from a FormSpec. * * Convenience type that extracts elements and infers the schema. * * @example * ```typescript * const form = formspec(...); * type Schema = InferFormSchema; * ``` * * @public */ export type InferFormSchema> = F extends FormSpec ? InferSchema : never; //# sourceMappingURL=inference.d.ts.map