UNPKG

@payfit/unity-components

Version:

109 lines (75 loc) 4.09 kB
# Schema adapters Schema adapters give the form-field organisms a uniform way to ask "is this path required?" across schemas authored in Zod 3, Zod 4, or any Standard Schema v1 implementation. Source: `src/adapters/`. ## Common interface ```ts // src/types/schema.ts export interface StandardSchemaField { isOptional: boolean type: string shape?: Record<string, StandardSchemaField> } export interface StandardSchema { getField(path: string): StandardSchemaField | null } ``` `getField('preferences.marketing')` returns `null` if the path does not exist, otherwise `{ isOptional, type, shape }`. `shape` is only populated when the field resolves to a nested `ZodObject` (so callers can recurse into nested forms). ## Adapters ### ZodV3SchemaAdapter Signature: ```ts new ZodV3SchemaAdapter(schema: z3.ZodObject<z3.ZodRawShape>) ``` - Reads structure from `schema.shape` and `field._def.typeName`. - Detects optional with `field instanceof z3.ZodOptional`; unwraps via `field._def.innerType`. - Source: `src/adapters/zodAdapter.ts` (lines 6–60). ### ZodV4SchemaAdapter Signature: ```ts new ZodV4SchemaAdapter(schema: z4.ZodObject<z4.ZodRawShape>) ``` - Same shape traversal as v3, but uses `field.def.innerType` and `field.def.typeName` (no underscore — Zod 4 renamed `_def` to `def`). - `field instanceof z4.ZodOptional` for optionality detection. - Source: `src/adapters/zodAdapter.ts` (lines 62–117). ### StandardSchemaAdapter Signature: ```ts new StandardSchemaAdapter(standardSchema: StandardSchemaV1) ``` - Stub implementation: `getField()` returns `{ isOptional: false, type: 'unknown', shape: undefined }` for any non-null path. Standard Schema's spec does not expose enough internal structure for richer introspection. - Use this only for schemas that aren't Zod (e.g. Valibot, ArkType) — required-field inference will degrade to "always required". - Source: `src/adapters/standardSchemaAdapter.ts`. ## How adapters auto-select `createSchemaAdapter(schema)` (in `src/utils/createSchemaAdapter.ts`) picks an adapter from the schema's structural fingerprint: ```ts import { createSchemaAdapter } from '@payfit/unity-components' const adapter = createSchemaAdapter(schema) // schema._def && 'def' in schema → ZodV4SchemaAdapter // schema._def && !('def' in schema) → ZodV3SchemaAdapter // schema['~validate'] is a function → StandardSchemaAdapter // otherwise → null ``` You rarely call this directly: every Composed field organism (e.g. `TanstackTextField`, `TanstackCheckboxField`, `TanstackToggleSwitchGroupField`) calls `createSchemaAdapter` + `isFieldRequired` internally to render the required indicator on the label. ## isFieldRequired Consumer of the adapter, used inside every Composed field to decide whether to mark the label as required. ```ts // src/components/form-field/utils/isFieldRequired.ts export function isFieldRequired( schema: StandardSchema | null | undefined, fieldPath: string, ): boolean { if (!schema) return false const field = schema.getField(fieldPath) return field ? !field.isOptional : false } ``` Behavior: - No schema → `false` (no required indicator). - Path not found in schema → `false` (treat as not required rather than crashing). - Field is `ZodOptional``false`. - Field is anything else → `true`. This is why `<form.AppField name="email">{field => <field.TextField label="Email" />}</form.AppField>` automatically gets a required asterisk when `email` is `z.email()` and no asterisk when it's `z.email().optional()` — without any prop wiring. ## When to import an adapter explicitly Almost never. The Composed field components call `createSchemaAdapter(schema)` themselves. Import an adapter directly only when: - You are writing a custom field component outside the Composed inventory and need required-state inference. - You are introspecting a schema for non-form purposes (form-derived UI, dynamic field rendering). Even then, prefer `createSchemaAdapter(schema)` so the right version is picked at runtime.