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

@revisium/schema-toolkit

v0.23.0

Published

Universal schema toolkit with TypeScript types and utilities for JSON Schema manipulation

Downloads

1,136

Readme

@revisium/schema-toolkit

Quality Gate Status Coverage GitHub License GitHub Release

Framework-agnostic TypeScript types, system schemas, runtime stores, and utilities for working with JSON Schema in Revisium projects.

Installation

npm install @revisium/schema-toolkit

Quick Start

Schema helpers

import { obj, str, num, bool, arr } from '@revisium/schema-toolkit';

const schema = obj({
  name: str(),
  age: num(),
  active: bool(),
  tags: arr(str()),
});

RowModel

import { obj, str, num, createRowModel } from '@revisium/schema-toolkit';

const schema = obj({ name: str(), price: num() });

const row = createRowModel({
  rowId: 'row-1',
  schema,
  data: { name: 'Widget', price: 9.99 },
});

row.getValue('name');           // string (typed!)
row.setValue('price', 19.99);   // OK
row.setValue('price', 'wrong'); // TS Error!
row.getPlainValue();            // { name: string, price: number }
row.patches;                    // JSON Patch operations
row.root;                       // typed root node (InferNode<S>)
row.reset({ name: 'New', price: 0 }); // reset to new data, commit
row.reset();                    // reset to schema defaults

Array Search

Arrays expose find and findIndex for searching elements by node properties:

import { obj, str, arr, createRowModel } from '@revisium/schema-toolkit';

const schema = obj({
  sorts: arr(obj({ field: str(), direction: str() })),
});

const row = createRowModel({
  rowId: 'row-1',
  schema,
  data: { sorts: [
    { field: 'name', direction: 'asc' },
    { field: 'age', direction: 'desc' },
  ]},
});

const sortsNode = row.get('sorts');
// find returns the typed node
const ageSort = sortsNode.find(
  (node) => node.child('field').getPlainValue() === 'age',
);
// findIndex returns the index
const idx = sortsNode.findIndex(
  (node) => node.child('field').getPlainValue() === 'age',
); // 1

TableModel

import { obj, str, num, bool, createTableModel } from '@revisium/schema-toolkit';

const schema = obj({ title: str(), price: num(), inStock: bool() });

const table = createTableModel({
  tableId: 'products',
  schema,
  rows: [
    { rowId: 'p1', data: { title: 'Laptop', price: 999, inStock: true } },
  ],
});

const row = table.getRow('p1');
row?.getValue('title');         // string (typed!)
row?.getPlainValue();           // { title: string, price: number, inStock: boolean }

const newRow = table.addRow('p2', { title: 'Mouse', price: 29, inStock: true });
newRow.setValue('price', 39);   // OK
newRow.setValue('price', 'x');  // TS Error!

Typed API without helpers

When the schema is typed (via helpers or as const), createRowModel / createTableModel return typed models automatically. With plain JsonSchema they return the untyped API as before:

import { createRowModel } from '@revisium/schema-toolkit';
import type { JsonObjectSchema } from '@revisium/schema-toolkit';

// Untyped — returns plain RowModel with unknown types
const schema: JsonObjectSchema = getSchemaFromApi();
const row = createRowModel({ rowId: 'row-1', schema, data });
row.getValue('name'); // unknown

See Typed API documentation for all approaches: as const, explicit type declarations, SchemaFromValue<T>, and more.

Reactivity (MobX)

By default all models use a noop reactivity provider, which works for backend and plain scripts. To enable MobX reactivity (e.g. in a React app), configure the provider once at startup:

import * as mobx from 'mobx';
import { setReactivityProvider, createMobxProvider } from '@revisium/schema-toolkit/core';

setReactivityProvider(createMobxProvider(mobx));

After this call every model created via createRowModel, createTableModel, or createDataModel becomes fully observable.

See Reactivity Module docs for the full API, noop behaviour table, and test-setup examples.

Formulas (Computed Fields)

Fields with x-formula are automatically computed from other fields. Use readOnly: true and the formula option in helpers:

import { obj, num, numFormula, createRowModel } from '@revisium/schema-toolkit';

const schema = obj({
  price: num(),
  quantity: num(),
  subtotal: numFormula('price * quantity'),
  tax: numFormula('subtotal * 0.1'),
  total: numFormula('subtotal + tax'),
});

const row = createRowModel({
  rowId: 'order-1',
  schema,
  data: { price: 100, quantity: 5, subtotal: 0, tax: 0, total: 0 },
});

row.getPlainValue();
// { price: 100, quantity: 5, subtotal: 500, tax: 50, total: 550 }

Formulas are evaluated in dependency order. With the MobX reactivity provider configured, changing a dependency triggers automatic re-evaluation of all affected formulas.

Expression Syntax

| Syntax | Example | |--------|---------| | Field reference | price, item.quantity | | Arithmetic | price * quantity, a + b - c | | Comparison & logic | a > b && c < d, x ? y : z | | Absolute path | /rootField | | Relative path | ../siblingField | | Array access | items[0].price, items[*].price | | Array context | #index, #length, @prev, @next | | Functions | sum(items[*].price), avg(values), count(array) |

Schema-Level Integration

When fields are renamed or moved, formula expressions are automatically updated in the generated patches:

// rename price → cost
// formula 'price * quantity' → 'cost * quantity' (auto-updated)

Warnings

The evaluator tracks problematic results (nan, infinity, runtime-error) on the node's formulaWarning property.

See value-formula docs for the runtime engine API and schema-formula docs for parsing, dependency tracking, and serialization.

Foreign Key Resolution

Schemas with foreignKey fields (string fields referencing another table) can be resolved automatically via ForeignKeyResolver:

import { createForeignKeyResolver, createTableModel, obj, str } from '@revisium/schema-toolkit';

const resolver = createForeignKeyResolver({
  loader: {
    loadSchema: async (tableId) => api.getTableSchema(tableId),
    loadRow: async (tableId, rowId) => api.getRow(tableId, rowId),
  },
  prefetch: true,
});

const table = createTableModel({
  tableId: 'products',
  schema: obj({ name: str(), categoryId: str({ foreignKey: 'categories' }) }),
  rows: [{ rowId: 'p1', data: { name: 'Laptop', categoryId: 'cat-1' } }],
  fkResolver: resolver,
});

// Referenced data is prefetched in the background and available from cache
const category = await resolver.getRowData('categories', 'cat-1');

The same fkResolver option is accepted by createRowModel. When using createDataModel, pass the resolver once and all tables will share it.

See ForeignKeyResolver docs for cache-only mode, prefetch control, loading state, and error handling.

API

Schema Helpers

| Function | Description | |----------|-------------| | str() | Create string schema | | num() | Create number schema | | bool() | Create boolean schema | | strFormula(expr) | Create computed string field (readOnly: true + x-formula) | | numFormula(expr) | Create computed number field (readOnly: true + x-formula) | | boolFormula(expr) | Create computed boolean field (readOnly: true + x-formula) | | obj(properties) | Create object schema (generic — preserves property types) | | arr(items) | Create array schema (generic — preserves items type) | | ref(tableName) | Create $ref schema |

Table & Row

| Function | Description | |----------|-------------| | createRowModel(options) | Create a row model (typed overload when schema is typed) | | createTableModel(options) | Create a table model (typed overload when schema is typed) |

RowModel

| Property / Method | Description | |-------------------|-------------| | root | Typed root node (InferNode<S> for typed, ValueNode for untyped) | | get(path) | Get node at path | | getValue(path) | Get plain value at path | | setValue(path, value) | Set value at path | | getPlainValue() | Get full plain value | | patches | JSON Patch operations (JsonValuePatch[]) for current changes | | reset(data?) | Reset to given data (or schema defaults) and commit | | commit() | Commit current state as base | | revert() | Revert to last committed state |

ArrayValueNode

| Method | Description | |--------|-------------| | at(index) | Get element at index (supports negative) | | find(predicate) | Find first element matching predicate, or undefined | | findIndex(predicate) | Find index of first matching element, or -1 | | push(node) | Append element | | pushValue(value?) | Create and append element from value | | removeAt(index) | Remove element at index | | move(from, to) | Move element between positions | | clear() | Remove all elements |

Value Tree

| Function | Description | |----------|-------------| | createTypedTree(schema, data) | Create a typed value tree with path-based access | | typedNode(node) | Cast an untyped ValueNode to a typed node |

Schema

| Function | Description | |----------|-------------| | createJsonSchemaStore | Create runtime schema store | | getJsonSchemaStoreByPath | Navigate schema by path | | applyPatches | Apply JSON Patch operations to schema | | resolveRefs | Resolve $ref to inline schemas | | validateJsonFieldName | Validate field name format | | getInvalidFieldNamesInSchema | Find invalid field names in schema |

Value

| Function | Description | |----------|-------------| | createJsonValueStore | Create runtime value store | | getJsonValueByPath | Navigate value by path | | computeValueDiff | Compute field-level diff between two values | | traverseValue | Traverse value tree |

Foreign Keys

| Function | Description | |----------|-------------| | getForeignKeysFromSchema | Extract foreign keys from schema | | getForeignKeysFromValue | Extract foreign key values from data | | getForeignKeyPatchesFromSchema | Get patches for foreign key changes | | replaceForeignKeyValue | Replace foreign key references |

Path Utils

| Function | Description | |----------|-------------| | parsePath | Parse dot-notation path to segments | | getParentForPath | Get parent path | | getPathByStore | Get path from store | | deepEqual | Deep equality comparison |

Type Utilities

| Type | Description | |------|-------------| | InferValue<S> | Schema → plain TypeScript value type | | InferNode<S> | Schema → typed ValueNode interface | | SchemaFromValue<T> | Plain TS type → virtual schema shape | | SchemaPaths<S> | Union of all valid dot-separated paths | | TypedRowModel<S> | RowModel with typed root, getValue, setValue, getPlainValue, reset | | TypedTableModel<S> | TableModel with typed rows, addRow, getRow |

See Typed API documentation for the full reference.

License

MIT