New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details
Socket
Book a DemoSign in
Socket

formisch-utils

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

formisch-utils

Utility functions for working with Formisch forms and Valibot schemas

latest
Source
npmnpm
Version
0.0.3
Version published
Maintainers
1
Created
Source

formisch-utils

AST-first utilities for building Formisch forms from Valibot schemas

[!WARNING] ⚠️ Experimental Package ⚠️: This package is designed to work with the Formisch form library.

Overview

formisch-utils derives form field configurations, HTML input constraints, and initial values from Valibot schemas. It accepts either a live Valibot schema or a pre-serialized valibot-ast node at every public API.

Three layers are provided:

LayerPurpose
Single-node utilitiesUnwrap, infer input type, constraints, metadata, initial value
buildFormFieldsRecursively build a full FormFieldConfig tree
Framework adaptersOne-call setup for React, Preact, Vue, Solid, Qwik

Installation

# pnpm
pnpm add formisch-utils

# npm
npm install formisch-utils

# yarn
yarn add formisch-utils

[!TIP] Continuous releases are also available via pkg.pr.new:

pnpm add https://pkg.pr.new/formisch-utils@main

Quick Start

import * as v from "valibot";
import { buildFormFields, generateInitialInput } from "formisch-utils";

const schema = v.object({
  name: v.pipe(v.string(), v.title("Full Name"), v.minLength(2)),
  email: v.pipe(v.string(), v.email(), v.title("Email Address")),
  age: v.optional(v.pipe(v.number(), v.minValue(0), v.maxValue(150))),
});

// Build the full form field tree
const config = buildFormFields(schema);
// → ObjectFormFieldConfig { kind: "object", fields: [...] }

// Generate type-safe initial values
const initial = generateInitialInput(schema);
// → { name: "", email: "", age: undefined }

With a Framework Adapter (React)

import * as v from "valibot";
import { useFormFields } from "formisch-utils/react";

const schema = v.object({
  name: v.pipe(v.string(), v.title("Full Name")),
  email: v.pipe(v.string(), v.email()),
});

function MyForm() {
  const { form, config } = useFormFields(schema, {
    validate: "blur",
    revalidate: "input",
  });

  // config is a FormFieldConfig tree
  // form is a Formisch FormStore
}

Entry Points

Import pathWhat it provides
formisch-utilsFramework-agnostic core (all utilities and types)
formisch-utils/reactuseFormFields + all core exports
formisch-utils/preactuseFormFields + all core exports
formisch-utils/vueuseFormFields + all core exports
formisch-utils/solidcreateFormFields + all core exports
formisch-utils/qwikuseFormFields$ + all core exports

API Reference

buildFormFields(input, options?)

Recursively traverses a schema and produces a FormFieldConfig tree describing every field.

import * as v from "valibot";
import { buildFormFields } from "formisch-utils";

const schema = v.object({
  name: v.string(),
  address: v.object({
    street: v.string(),
    city: v.string(),
  }),
  tags: v.array(v.string()),
});

const config = buildFormFields(schema);
// config.kind === "object"
// config.fields[0] → LeafFormFieldConfig { kind: "leaf", key: "name", inputType: "text" }
// config.fields[1] → ObjectFormFieldConfig { kind: "object", key: "address", fields: [...] }
// config.fields[2] → ArrayFormFieldConfig { kind: "array", key: "tags", item: {...} }

Input: accepts a GenericSchema, SchemaToASTResult, ASTDocument, or ASTNode.

Options:

OptionTypeDescription
basePathstring[]Prefix for all generated field paths

Mapping rules:

Schema typeConfig kindNotes
object (all variants)objectfields[] preserving insertion order
arrayarrayitem config for the array element
tuple (all variants)tupleitems[] indexed as "0", "1", etc.
variantvariantdiscriminatorKey + branches[]
union (all literals)leafinputType: "select" with options
union (mixed/objects)unionoptions: FormFieldConfig[][] (sub-form per option)
intersect (objects)objectEntries merged, first-wins on duplicates
recordrecordkeyField + valueField configs
enum / picklistleafinputType: "select" with options
literalleafinputType: "hidden"
Scalars (string, number, etc.)leafinputType from inferInputType, constraints from inferInputConstraints
lazy, function, map, set, instanceunsupportedWith nodeType and reason

buildObjectFields(input, options?)

Convenience wrapper around buildFormFields. If the result is kind: "object", returns root.fields directly; otherwise wraps the result in a single-element array.

generateInitialInput(input)

Derives sensible default values from a schema.

import * as v from "valibot";
import { generateInitialInput } from "formisch-utils";

const schema = v.object({
  name: v.string(),
  bio: v.optional(v.string(), "N/A"),
  age: v.optional(v.number()),
  active: v.boolean(),
});

generateInitialInput(schema);
// → { name: "", bio: "N/A", age: undefined, active: false }

Resolution order per field:

  • Explicit wrapper default (e.g. v.optional(v.string(), "hello")) → use it
  • Optional/undefinedable wrapper → undefined
  • Nullable (but required) wrapper → null
  • Type-based: string"", booleanfalse, literal → the literal value, object → recurse, array/tuple[], union/variant → first option's initial value, number/bigint/dateundefined

inferInputType(node)

Maps an AST node to an HTML <input type> string.

SchemaReturns
string + email pipe"email"
string + url"url"
string + isoDate"date"
string + isoDateTime / isoTimestamp"datetime-local"
string + isoTime"time"
string + isoWeek"week"
string + hexColor"color"
string (plain)"text"
number / bigint"number"
boolean"checkbox"
date"date"
file / blob"file"
Structural / unsupportedundefined

inferInputConstraints(node, options?)

Derives InputConstraints (HTML attributes) from pipe validations.

import * as v from "valibot";
import { schemaToAST } from "valibot-ast";
import { inferInputConstraints } from "formisch-utils";

const schema = v.pipe(v.string(), v.minLength(2), v.maxLength(100));
const { document } = schemaToAST(schema);

inferInputConstraints(document.schema);
// → { required: true, minLength: 2, maxLength: 100 }
ValidationConstraint
minLength(n)minLength: n
maxLength(n)maxLength: n
length(n)minLength + maxLength: n
nonEmpty()minLength: 1
minValue(n)min: n
maxValue(n)max: n
multipleOf(n)step: n
integer()step: 1
regex(r)pattern: r.source
mimeType([...])accept: "image/png,image/jpeg,..."

inferMeta(node, key?)

Extracts human-readable metadata from an AST node's info block.

import * as v from "valibot";
import { schemaToAST } from "valibot-ast";
import { inferMeta } from "formisch-utils";

const schema = v.pipe(
  v.string(),
  v.title("Email Address"),
  v.description("Your primary email"),
);
const { document } = schemaToAST(schema);

inferMeta(document.schema, "email");
// → { label: "Email Address", description: "Your primary email" }

// Without title metadata, falls back to titleCase of the key:
inferMeta(someNode, "firstName");
// → { label: "First Name" }

Returns FormFieldMeta:

  • label — from info.title, falls back to titleCase(key)
  • description — from info.description
  • placeholder — from info.metadata.placeholder or String(info.examples[0])

inferInitialValue(node)

Derives a default value for a single AST node. Used internally by generateInitialInput.

unwrapASTNode(node)

Alias for getWrappedASTNode from valibot-ast/utils. Peels off all wrapper layers and returns { node, required, nullable, default? }.

coerceValue(field, rawValue)

Converts a raw HTML input string value to the typed value expected by the schema.

import { coerceValue } from "formisch-utils";

// For a LeafFormFieldConfig with nodeType: "number"
coerceValue(numberField, "42");   // → 42
coerceValue(numberField, "");     // → undefined (if required) or null (if nullable)

// For fields with options, matches against option values
coerceValue(selectField, "2");    // → 2 (the number, not the string)

Coercion by nodeType:

nodeTypeEmpty stringNon-empty string
"number"fallbackNumber(raw)
"bigint"fallbackBigInt(raw)
"boolean"falsetrue for "true", "on", "1"
"date"fallbacknew Date(raw)
Othersraw stringraw string

Empty-string fallback: undefined if required and not nullable, null if required and nullable, undefined if optional.

Types

import type {
  // Field configs (discriminated union on `kind`)
  FormFieldConfig,           // Union of all config types below
  LeafFormFieldConfig,       // Scalar: inputType, constraints, options
  ObjectFormFieldConfig,     // Nested object: fields[]
  ArrayFormFieldConfig,      // Dynamic array: item config
  TupleFormFieldConfig,      // Fixed tuple: items[]
  UnionFormFieldConfig,      // Non-discriminated union: options[][]
  VariantFormFieldConfig,    // Discriminated union: discriminatorKey, branches[]
  RecordFormFieldConfig,     // Key-value: keyField, valueField
  UnsupportedFormFieldConfig,// Unmappable type: nodeType, reason

  // Shared types
  BaseFormFieldConfig,       // Common fields: key, path, label, description, required, nullable, default
  InputConstraints,          // HTML attrs: required, minLength, maxLength, min, max, step, pattern, accept
  FormFieldMeta,             // label, description, placeholder
  FormFieldOption,           // { value, label }
  UnwrappedASTNode,          // Re-export of GetWrappedASTNode

  // Build options
  BuildFormFieldsOptions,
} from "formisch-utils";

Framework Adapters

All adapters accept the same options and return { form, config }:

interface UseFormFieldsOptions<S> {
  initialInput?: DeepPartial<InferInput<S>>;  // deep-merged over auto-generated defaults
  validate?: "initial" | "blur" | "input" | "submit";
  revalidate?: "blur" | "input" | "submit";
}
// Returns: { form: FormStore<S>, config: FormFieldConfig }
AdapterImportFunction
Reactformisch-utils/reactuseFormFields(schema, options?)
Preactformisch-utils/preactuseFormFields(schema, options?)
Vueformisch-utils/vueuseFormFields(schema, options?)
SolidJSformisch-utils/solidcreateFormFields(schema, options?)
Qwikformisch-utils/qwikuseFormFields$(schema, options?)

The initialInput override is deep-merged with the auto-generated defaults, so you can partially override specific fields without losing the rest:

const { form, config } = useFormFields(schema, {
  initialInput: { name: "John" },
  // age, email, etc. still get their auto-generated defaults
});
  • valibot-ast: AST utilities for schema serialization and reconstruction
  • Formisch: Type-safe form library for modern frameworks

License

MIT

FAQs

Package last updated on 02 Mar 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts