Skip to content

Refactor fields() to return raw schema data, move form logic to @b9g/forms #15

@brainkim

Description

@brainkim

Problem

fields() currently returns FieldMeta which mixes three concerns:

  1. Database metadata (primaryKey, autoIncrement, inserted, etc.) - from .db.*() methods
  2. Form interpretation (type: "email" | "textarea", required, etc.) - computed/inferred
  3. Zod constraints (min, max, minLength, maxLength, options) - extracted from schema internals

This creates problems:

  • Form logic doesn't belong in a database library
  • HTML input types (FieldType) are opinionated and leak into core
  • Consumers can't easily access raw db metadata without the "cooking"
  • The flattened structure loses information

Solution

1. Simplify fields() to return raw data

interface FieldInfo {
  name: string;
  schema: ZodType;        // raw Zod schema
  db: FieldDBMeta;        // raw db metadata from .db.*()
}

interface Relation {
  table: Table;
  fields(): TableFields;  // navigate to related table
}

type TableFields = Record<string, FieldInfo | Relation>;

2. Remove from Zen

  • FieldType (HTML input types)
  • FieldMeta (the cooked/flattened type)
  • extractFieldMeta() (the cooking function)
  • Computed properties: type, required, min, max, minLength, maxLength, options

3. Create @b9g/forms (separate package)

Headless schema introspection for form generation:

  • Zod type inference → field types
  • Constraint extraction → validation hints
  • Db-awareness → skip auto-generated fields, handle relations
  • Works with any form library (TanStack Form, RHF, etc.)

Breaking Changes

  • fields() return type changes from FieldMeta to FieldInfo
  • FieldType and FieldMeta exports removed
  • Consumers needing form metadata should use @b9g/forms (or access schema.meta() directly)

Migration

// Before
const fields = Users.fields();
fields.email.type;        // "email"
fields.email.required;    // true

// After  
const fields = Users.fields();
fields.email.schema;              // ZodString (use Zod APIs)
fields.email.db.autoIncrement;    // boolean
fields.email.schema.isOptional(); // use Zod directly

// Or use @b9g/forms for form-specific metadata
import { formFields } from "@b9g/forms";
const form = formFields(Users);
form.email.type;          // "email"
form.email.required;      // true

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions