npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@formos/kernel

v0.1.0

Published

Headless form engine for Formos - framework-agnostic state management, validation, and effects

Readme

@formos/kernel

Headless, framework-agnostic form engine for the Formos form builder.

Purpose

@formos/kernel provides the runtime execution layer for forms defined by @formos/schema. It's a completely headless engine that manages all form logic without any UI concerns - no React, no DOM, no browser APIs.

Think of it as the "brain" of the form - it handles state, validation orchestration, effect execution, and multi-step navigation, but leaves rendering entirely to UI packages.

Responsibilities

This package is responsible for:

  • State Management: Maintaining form values, errors, touched fields, and dirty state
  • Validation Orchestration: Coordinating validation execution with pluggable validator adapters
  • Effect Execution: Triggering side effects (field syncing, API calls) via effect executors
  • Conditional Logic Evaluation: Determining field visibility and required status dynamically
  • Multi-Step Flow Control: Managing step navigation with validation guards
  • Lifecycle Management: Handling submit, reset, and field value updates

What This Package Does NOT Do

❌ NO UI Rendering

  • Does not create DOM elements
  • Does not provide React components
  • Does not handle user interactions directly

❌ NO Framework Dependencies

  • No React, Vue, Svelte, or any UI framework
  • No browser-specific APIs (localStorage, fetch, etc.)
  • Completely runtime-agnostic

❌ NO Built-in Validation Libraries

  • Does not include Zod, Yup, or any validators
  • Uses adapter pattern - you provide validators
  • Same for effects - no built-in API clients or transformers

❌ NO Schema Definition

  • Does not define schema types (that's @formos/schema)
  • Consumes NormalizedSchemaV1 as input
  • Does not modify or mutate schemas

Think of it as: The engine that executes the schema's instructions, not the schema itself.

Stability Guarantee

FormEngine API is STABLE and FINAL ✅

The FormEngine interface is committed to backward compatibility:

  • Method signatures won't change
  • Return types remain consistent
  • Behavior is predictable and documented

This guarantee enables:

  • Safe updates: Upgrade the kernel without UI changes
  • Framework flexibility: Build UI packages for React, Vue, Svelte
  • Long-term reliability: Code written today works tomorrow

What Can Change

✅ Internal implementations can be optimized ✅ New optional methods may be added to FormEngine ✅ Performance improvements without API changes ✅ Bug fixes that don't alter public behavior

Adapter Contract

The adapter types (ValidatorExecutor, EffectExecutor) are stable:

  • Validators return string | null (error or valid)
  • Effects receive context with getValue and setValue
  • These interfaces won't have breaking changes

Installation

pnpm add @formos/kernel @formos/schema

Basic Usage

Create a Form Engine

import { createFormEngine } from '@formos/kernel';
import { normalizeSchema, type FormSchemaV1 } from '@formos/schema';

// 1. Define schema
const schema: FormSchemaV1 = {
  version: 'v1',
  fields: [
    { 
      name: 'email', 
      type: 'email',
      required: true,
      validations: [
        { 
          trigger: ['onChange', 'onBlur'],
          validator: 'email',
          rule: {},
          message: 'Invalid email'
        }
      ]
    },
    { name: 'password', type: 'password', required: true }
  ]
};

// 2. Normalize schema
const normalized = normalizeSchema(schema);

// 3. Create engine with validators
const engine = createFormEngine(normalized, {
  validators: {
    required: (value) => value ? null : 'Required',
    email: (value) => {
      const regex = /\S+@\S+\.\S+/;
      return regex.test(String(value)) ? null : 'Invalid email';
    }
  },
  onSubmit: async (values) => {
    console.log('Form submitted:', values);
  }
});

Get and Set Values

// Get field value
const email = engine.getValue('email');
console.log('Email:', email); // undefined initially

// Set field value (triggers validation + effects)
await engine.setValue('email', '[email protected]');

// Set without validation
await engine.setValue('email', '[email protected]', {
  trigger: 'manual',
  skipValidation: true
});

Validation

// Validate single field
const isEmailValid = await engine.validate('email', 'onChange');

// Validate entire form
const isFormValid = await engine.validate();

// Check validation state
const emailError = engine.getError('email');
const allErrors = engine.getErrors();
const isValid = engine.isValid();

// Manual error management
engine.setError('email', 'Custom error message');
engine.clearError('email');

Form State

// Check if field was modified
const isEmailDirty = engine.isDirty('email');
const isFormDirty = engine.isDirty(); // Any field dirty?

// Check if field was touched (focused and blurred)
const wasEmailTouched = engine.isTouched('email');

// Mark field as touched
engine.markTouched('email');

Submit and Reset

// Submit form (validates all fields first)
const success = await engine.submit();
if (success) {
  console.log('Form submitted successfully');
} else {
  console.log('Validation failed:', engine.getErrors());
}

// Reset form to initial state
engine.reset();

Multi-Step Forms

const multiStepSchema: FormSchemaV1 = {
  version: 'v1',
  fields: [
    { name: 'firstName', type: 'text' },
    { name: 'lastName', type: 'text' },
    { name: 'email', type: 'email' },
    { name: 'phone', type: 'tel' }
  ],
  steps: [
    { id: 'personal', title: 'Personal Info', fields: ['firstName', 'lastName'] },
    { id: 'contact', title: 'Contact', fields: ['email', 'phone'] }
  ]
};

const normalized = normalizeSchema(multiStepSchema);
const engine = createFormEngine(normalized, { validators });

// Step navigation (only available for multi-step forms)
const currentStep = engine.getCurrentStep?.(); // 0
const totalSteps = engine.getTotalSteps?.();   // 2

// Go to next step (validates current step first)
const canProceed = await engine.nextStep?.();
if (!canProceed) {
  console.log('Fix errors before proceeding');
}

// Go to previous step (no validation)
engine.prevStep?.();

// Jump to specific step (validates current step first)
const moved = await engine.goToStep?.(1);

// Check if can go to step
const canGo = engine.canGoToStep?.(1);

Advanced: Custom Validators

Validators use the adapter pattern for framework flexibility:

import type { ValidatorExecutor } from '@formos/kernel';

// Simple validator
const required: ValidatorExecutor = (value, params, context) => {
  return value ? null : 'Required';
};

// Cross-field validator
const matchesField: ValidatorExecutor = (value, params, context) => {
  const { targetField } = params as { targetField: string };
  const targetValue = context.getValue(targetField);
  return value === targetValue ? null : 'Fields must match';
};

// Async validator (e.g., API check)
const uniqueUsername: ValidatorExecutor = async (value, params, context) => {
  const response = await fetch(`/api/check-username?username=${value}`);
  const { available } = await response.json();
  return available ? null : 'Username already taken';
};

const engine = createFormEngine(normalized, {
  validators: {
    required,
    matchesField,
    uniqueUsername
  }
});

Advanced: Effect Executors

Effects handle side effects triggered by field changes:

import type { EffectExecutor } from '@formos/kernel';

// Sync effect: Calculate fullName from firstName + lastName
const syncFullName: EffectExecutor = (params, context) => {
  const firstName = context.getValue('firstName') as string || '';
  const lastName = context.getValue('lastName') as string || '';
  context.setValue('fullName', `${firstName} ${lastName}`.trim());
};

// API effect: Fetch cities when country changes
const fetchCities: EffectExecutor = async (params, context) => {
  const country = context.sourceValue as string;
  
  const response = await fetch(`/api/cities?country=${country}`);
  const cities = await response.json();
  
  context.setValue('cities', cities);
};

const engine = createFormEngine(normalized, {
  effectExecutors: {
    syncFullName,
    fetchCities
  }
});

Conditional Logic

The kernel evaluates conditional visibility and required logic:

import { evaluateConditional, isFieldVisible, isFieldRequired } from '@formos/kernel';

// Check field visibility
const field = schema.fieldMap?.get('ssn');
if (field && isFieldVisible(field, (name) => engine.getValue(name))) {
  // Render SSN field
}

// Check if field is dynamically required
if (field && isFieldRequired(field, (name) => engine.getValue(name))) {
  // Show required indicator
}

// Direct conditional evaluation
const condition = {
  logic: 'and',
  rules: [
    { field: 'age', operator: 'greaterThan', value: 18 },
    { field: 'country', operator: 'equals', value: 'US' }
  ]
};

const result = evaluateConditional(condition, (name) => engine.getValue(name));

API Reference

Core Function

  • createFormEngine(schema, options): Create form engine instance

FormEngine Interface

State Accessors:

  • getValue(fieldName): Get field value
  • getError(fieldName): Get field error
  • getErrors(): Get all errors
  • isValid(): Check if form is valid
  • isDirty(fieldName?): Check dirty state
  • isTouched(fieldName): Check touched state

State Mutators:

  • setValue(fieldName, value, options?): Set field value
  • setError(fieldName, error): Set field error
  • clearError(fieldName): Clear field error
  • markTouched(fieldName): Mark field as touched

Validation:

  • validate(fieldName?, trigger?): Validate field(s)

Lifecycle:

  • submit(): Submit form (validates first)
  • reset(): Reset form to initial state

Multi-Step Navigation (if applicable):

  • getCurrentStep?(): Get current step index
  • getTotalSteps?(): Get total step count
  • nextStep?(): Go to next step (validates first)
  • prevStep?(): Go to previous step
  • goToStep?(stepIndex): Jump to specific step
  • canGoToStep?(stepIndex): Check if can navigate to step

Adapter Types

  • ValidatorExecutor: (value, params, context) => string | null | Promise<string | null>
  • EffectExecutor: (params, context) => unknown | Promise<unknown>
  • ValidationContext: Read-only access to form values
  • EffectContext: Read-write access to form values

Utility Functions

  • evaluateConditional(condition, getValue): Evaluate conditional expression
  • isFieldVisible(field, getValue): Check field visibility
  • isFieldRequired(field, getValue): Check if field is required

Package Structure

@formos/kernel/
├── engine/
│   ├── factory.ts          # createFormEngine, FormEngine interface
│   ├── values.ts           # getValue, setValue operations
│   ├── validation-ops.ts   # validate, getError, isValid
│   ├── lifecycle.ts        # submit, reset
│   ├── init.ts             # Initialization helpers
│   └── helpers.ts          # Internal utilities
├── state.ts                # FormState class
├── validation.ts           # Validation orchestration
├── effects.ts              # Effect execution
├── conditionals.ts         # Conditional evaluation
├── steps.ts                # Multi-step controller
├── types.ts                # Adapter type definitions
└── index.ts                # Public API exports

TypeScript Configuration

This package uses strict mode TypeScript:

  • noUncheckedIndexedAccess: true
  • Optional chaining (?.) used everywhere for safety
  • moduleResolution: "NodeNext"
  • module: "NodeNext"

All exports are ESM only (type: "module").

Design Principles

1. Adapter Pattern

The kernel doesn't include validation libraries or HTTP clients. Instead, it defines interfaces (ValidatorExecutor, EffectExecutor) that you implement with your preferred tools.

2. Immutable Schema

The kernel never modifies the schema. It reads the normalized schema and executes its instructions, but the schema remains a pure data structure.

3. Framework Agnostic

No React hooks, no DOM manipulation, no browser APIs. This enables:

  • React bindings via @formos/react
  • Vue bindings (future)
  • Svelte bindings (future)
  • Node.js usage (server-side form validation)

4. Optional Chaining Everywhere

Since schemas are user-provided, the kernel uses optional chaining extensively for safety.

Contributing

This package is part of the Formos monorepo. See the root README for contribution guidelines.

License

MIT