@revisium/schema-toolkit
v0.23.0
Published
Universal schema toolkit with TypeScript types and utilities for JSON Schema manipulation
Downloads
1,136
Maintainers
Readme
@revisium/schema-toolkit
Framework-agnostic TypeScript types, system schemas, runtime stores, and utilities for working with JSON Schema in Revisium projects.
Installation
npm install @revisium/schema-toolkitQuick 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 defaultsArray 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',
); // 1TableModel
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'); // unknownSee 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
