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

@modyra/core

v2.5.0

Published

Framework-agnostic type-safe form engine — typed field trees, sync/async/cross-field validation, drafts and undo/redo over a minimal reactive contract.

Downloads

780

Readme

@modyra/core

One form contract. Every framework. Any backend. The engine half of that contract: typed field trees and arrays, sync/async/cross-field validation, dirty/touched tracking, draft persistence, undo/redo and minimal-patch change tracking — with zero dependencies and no framework in sight.

npm install @modyra/core
import { createForm, field, group, required, min } from "@modyra/core";

const form = createForm({
  email: field("", [required()]),
  age: field<number | null>(null, [min(18)]),
  address: group({ city: field("Rome") }),
});

form.f.email.set("[email protected]");
form.f.email.errors(); // []
form.getValue().address.city; // typed — typos do not compile

Runs in Node, CLIs, workers and plain unit tests.

Feature tour

Typed field arrays — repeatable rows with compile-checked paths:

import { array, field, group, min } from "@modyra/core";

const form = createForm({
  items: array(group({ sku: field(""), qty: field<number>(1, [min(1)]) }), {
    initial: [{ sku: "TSHIRT-BLK-M", qty: 2 }],
  }),
});

form.f.items.push({ sku: "MUG-WHT", qty: 1 });
form.f.items.rows()[1].sku.errors();
form.f.items.move(0, 1);
form.getValue().items[0].qty; // number

Collections keyed by data — rows addressed by an entity id or a provisional key rather than by position, so a row survives sorting and filtering, and the controls of one row may be mounted apart:

import { field, group, min, record } from "@modyra/core";

const form = createForm({
  lines: record(group({ name: field(""), qty: field<number>(1, [min(1)]) })),
});

form.f.lines.upsert("a3f9", { name: "Espresso", qty: 2 });
form.f.lines.cell("a3f9", "name").set("Ristretto"); // one control of one row
form.f.lines.rename("tmp:1", "77");                 // keeps value, validity and touched
form.value().lines;                                 // { a3f9: { name: string; qty: number } }

A row exists because upsert declared it, never because something rendered it: unmounting a control keeps the value, and validity belongs to the row.

Server-side async validation, done right — cancellable, cross-field, debounced, with timeout and preconditions:

import { field, serverValidator } from "@modyra/core";

coupon: field(
  "",
  [],
  serverValidator(
    async (code, ctx) => {
      if (!code) return null;
      const res = await api.check(code, ctx.form.fieldValue("country"), {
        signal: ctx.signal, // aborted when the run is superseded
      });
      return res.valid ? null : "Coupon not valid for your country";
    },
    { dependsOn: ["country"], debounceMs: 400, timeoutMs: 5000 },
  ),
);

Drafts, history, minimal patches:

const form = createForm(schema, {
  draft: { key: "checkout", exclude: ["iban"] }, // autosave/restore, TTL'd
  history: true, // undo()/redo()
});
form.getChanges(); // → typed minimal patch for your PATCH endpoint

Cross-field validation — form-level rules over the whole typed value, attributed to fields or to the form itself (path: null):

import { crossField } from "@modyra/core";

createForm(schema, {
  validators: [
    crossField(["passwordConfirm"], (v) =>
      v.password !== v.passwordConfirm ? "Passwords do not match" : null,
    ),
  ],
});

The reactive contract

The engine is written against four primitives — signal, computed, effect (with cleanup) and untracked — the common denominator of fine-grained reactivity (Solid, Preact Signals, Vue, Angular Signals, and the TC39 Signals proposal). It is not an Angular API: Angular is just one binding of the contract.

import { createForm, vanillaReactivity } from "@modyra/core";

// Node / tests / workers: the bundled graph
const form = createForm(schema); // reactivity defaults to vanillaReactivity()

A framework adapter passes its own implementation, so form state participates natively in the host's change detection. Which adapters exist, and how completely each implements the contract, is published in the reactivity capability matrix.

Security notes

  • Drafts are versioned envelopes; expiry is opt-in via ttlMs. File/Blob/BigInt values are refused, quota errors never crash the form, and prototype-pollution paths (__proto__ & co.) in tampered storage are discarded.
  • The framework-agnostic devtools panel masks sensitive-looking paths and escapes every rendered value.
  • Zero runtime dependencies, SSR-safe (no window/document access in the engine).

Documentation

License

MIT © Lorenzo Muscherà