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

@niscorp/loom

v0.2.2

Published

Schema-driven editors that run on Nova — turn any schema into an editing UI

Readme

@niscorp/loom

Loom builds editing UIs from schemas. Give it a Zod schema and it produces a form that views, creates, and edits JSON matching that schema. The form is built from the schema's structure — nested objects become nested sections, arrays get add and remove controls, unions become a type picker plus the fields for the chosen type — so the form has a control for every field the schema describes. It does not validate for you: a new document, and a document mid-edit, can fail the schema, so parse it before you keep it. There is no hand-written form code; a shape Loom does not model gets a raw JSON box.

The forms render on Nova, this stack's layout engine. Loom compiles a schema into a Nova layout; Nova draws it and routes each edit back into the data.

Install

pnpm add @niscorp/loom @niscorp/nova zod

@niscorp/nova is required. The React surface (@niscorp/loom/react) also needs react. Domain plugins each need their own library — @niscorp/vex for the Vex plugin, @niscorp/prism for the Prism plugin. All three are optional peers of Loom: a consumer that only uses the compiler needs none of them for Loom itself. (Nova has required peers of its own — @niscorp/prism and @niscorp/strata — which pnpm installs with it.)

Two ways to use Loom

The compiler — turn a schema into a Nova editor

parse reads a schema into a field model. toNova turns that model into a Nova editor: an action that holds the data being edited, plus the layouts that render it.

import { parse, toNova } from '@niscorp/loom';
import { z } from 'zod';

const schema = z.object({
  name: z.string(),
  age: z.number().int(),
  tags: z.array(z.string()),
});

const { action, layouts } = toNova(parse(schema));
// `action.data` is the document the form starts from; hand `action` and
// `layouts` to a Nova runtime to render the form. Edits go to the runtime's
// copy (`shell.getRuntime(id).getData()`), not to `action.data`, and the
// document can fail `schema` at any point: parse it before you keep it.

A new document starts from each field's empty value: '', 0, false, [], the schema's default where it has one, and null for a kind Loom does not model. A required enum with no default is left out. These can fail the schema ('' is not an email, 0 is not min(1), null is not a record), and optional fields get them too. toNova(model, { empty, includeOptional }) changes both: empty supplies a kind's starting value, includeOptional: false leaves optional fields out.

This half is headless — no React, no DOM. Use it when you have your own Nova host or only need the compiled output.

The editor — a ready-made React surface

<LoomEditor> is the integrated surface. It loads a set of plugins, compiles each plugin's schemas into forms, and renders everything as one Nova shell: the forms on the left, a live preview and JSON panes alongside.

import { LoomEditor, defaultPlugins } from '@niscorp/loom/react';
import { prism } from '@niscorp/loom/plugins/prism/react';

<LoomEditor
  plugins={[...defaultPlugins(), prism({ input })]}
  artifact={{ type: 'prism', documents: { config } }}
  onChange={(documents) => console.log(documents)}
/>;
  • plugins is the loaded set. They load in order, and a later plugin can override or remove an earlier one. defaultPlugins() adds the Data and Validations JSON panes — spread it first so domain plugins load after it.
  • artifact says what to edit. type names the plugin that handles it; documents seeds the starting values (omit it to start from the schema's defaults).
  • onChange fires with the live document values on every edit.
  • To switch to a different artifact, give the element a new React key.

Two things the built-in kit draws are worth knowing before you rely on them:

  • Messages follow edits. The inline messages and the Validations pane show what the schema said after the last edit. They are empty until the first one, a problem with no field path (a .refine() on the whole object, without a path) is shown nowhere, and a problem in a list row shows in the Validations pane only. They are not the verdict on a document; the schema is (DESIGN.md, "Validation").
  • A select has no row for "none". An enum with no .default() starts with no value in the document, and the select displays its first option all the same. Give the enum a .default() and the two start out agreeing.

A plugin wires one domain into the editor. It contributes the schemas to edit (its documents), optional custom field widgets, and usually a preview. The three reference plugins:

  • prism({ input }) — edit a Prism transform config; the preview applies it to input and shows the output.
  • vex({ run, db }) — edit a Vex query; the preview runs it with run and shows the rows. Pass db to get column-aware field pickers.
  • nova({ manifest }) — edit a Nova layout built from the components the manifest lists (each { name, props, container?, render }, props a Zod schema); the preview renders the layout with them.

Entry points

| Import | What it gives you | |---|---| | @niscorp/loom | The compiler (parse, toNova), the editor controller (createLoomEditor), and the types. Headless. | | @niscorp/loom/react | <LoomEditor>, defaultPlugins(), and the widget kit. | | @niscorp/loom/plugins/{vex,nova,prism} | A domain plugin's framework-free core. | | @niscorp/loom/plugins/{vex,nova,prism}/react | A domain plugin's React surface (preview and widget components). |

Documentation

  • DESIGN.md — how Loom is built and why. Read it before the source.

License

Apache-2.0