{"id":31792727,"url":"https://github.com/revisium/schema-toolkit","last_synced_at":"2026-04-07T02:05:22.790Z","repository":{"id":317426435,"uuid":"1067354246","full_name":"revisium/schema-toolkit","owner":"revisium","description":"JSON Schema engine — validation, diff, patch, migrations","archived":false,"fork":false,"pushed_at":"2026-03-28T06:36:51.000Z","size":772,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-03-28T11:42:36.276Z","etag":null,"topics":["diff","json-schema","patch","schema-migration","typescript","validation"],"latest_commit_sha":null,"homepage":"https://revisium.io","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/revisium.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-30T18:32:17.000Z","updated_at":"2026-03-28T06:36:53.000Z","dependencies_parsed_at":"2025-10-08T08:18:30.223Z","dependency_job_id":null,"html_url":"https://github.com/revisium/schema-toolkit","commit_stats":null,"previous_names":["revisium/schema-toolkit"],"tags_count":47,"template":false,"template_full_name":null,"purl":"pkg:github/revisium/schema-toolkit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/revisium%2Fschema-toolkit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/revisium%2Fschema-toolkit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/revisium%2Fschema-toolkit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/revisium%2Fschema-toolkit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/revisium","download_url":"https://codeload.github.com/revisium/schema-toolkit/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/revisium%2Fschema-toolkit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31496770,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-06T17:22:55.647Z","status":"online","status_checked_at":"2026-04-07T02:00:07.164Z","response_time":105,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["diff","json-schema","patch","schema-migration","typescript","validation"],"created_at":"2025-10-10T17:15:59.933Z","updated_at":"2026-04-07T02:05:22.773Z","avatar_url":"https://github.com/revisium.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# @revisium/schema-toolkit\n\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=revisium_schema-toolkit\u0026metric=alert_status)](https://sonarcloud.io/summary/new_code?id=revisium_schema-toolkit)\n[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=revisium_schema-toolkit\u0026metric=coverage)](https://sonarcloud.io/summary/new_code?id=revisium_schema-toolkit)\n[![GitHub License](https://img.shields.io/badge/License-MIT-green.svg)](https://github.com/revisium/schema-toolkit/blob/master/LICENSE)\n[![GitHub Release](https://img.shields.io/github/v/release/revisium/schema-toolkit)](https://github.com/revisium/schema-toolkit/releases)\n\nFramework-agnostic TypeScript types, system schemas, runtime stores, and utilities for working with JSON Schema in [Revisium](https://revisium.io) projects.\n\n\u003c/div\u003e\n\n## Installation\n\n```bash\nnpm install @revisium/schema-toolkit\n```\n\n## Quick Start\n\n### Schema helpers\n\n```typescript\nimport { obj, str, num, bool, arr } from '@revisium/schema-toolkit';\n\nconst schema = obj({\n  name: str(),\n  age: num(),\n  active: bool(),\n  tags: arr(str()),\n});\n```\n\n### RowModel\n\n```typescript\nimport { obj, str, num, createRowModel } from '@revisium/schema-toolkit';\n\nconst schema = obj({ name: str(), price: num() });\n\nconst row = createRowModel({\n  rowId: 'row-1',\n  schema,\n  data: { name: 'Widget', price: 9.99 },\n});\n\nrow.getValue('name');           // string (typed!)\nrow.setValue('price', 19.99);   // OK\nrow.setValue('price', 'wrong'); // TS Error!\nrow.getPlainValue();            // { name: string, price: number }\nrow.patches;                    // JSON Patch operations\nrow.root;                       // typed root node (InferNode\u003cS\u003e)\nrow.reset({ name: 'New', price: 0 }); // reset to new data, commit\nrow.reset();                    // reset to schema defaults\n```\n\n### Array Search\n\nArrays expose `find` and `findIndex` for searching elements by node properties:\n\n```typescript\nimport { obj, str, arr, createRowModel } from '@revisium/schema-toolkit';\n\nconst schema = obj({\n  sorts: arr(obj({ field: str(), direction: str() })),\n});\n\nconst row = createRowModel({\n  rowId: 'row-1',\n  schema,\n  data: { sorts: [\n    { field: 'name', direction: 'asc' },\n    { field: 'age', direction: 'desc' },\n  ]},\n});\n\nconst sortsNode = row.get('sorts');\n// find returns the typed node\nconst ageSort = sortsNode.find(\n  (node) =\u003e node.child('field').getPlainValue() === 'age',\n);\n// findIndex returns the index\nconst idx = sortsNode.findIndex(\n  (node) =\u003e node.child('field').getPlainValue() === 'age',\n); // 1\n```\n\n### TableModel\n\n```typescript\nimport { obj, str, num, bool, createTableModel } from '@revisium/schema-toolkit';\n\nconst schema = obj({ title: str(), price: num(), inStock: bool() });\n\nconst table = createTableModel({\n  tableId: 'products',\n  schema,\n  rows: [\n    { rowId: 'p1', data: { title: 'Laptop', price: 999, inStock: true } },\n  ],\n});\n\nconst row = table.getRow('p1');\nrow?.getValue('title');         // string (typed!)\nrow?.getPlainValue();           // { title: string, price: number, inStock: boolean }\n\nconst newRow = table.addRow('p2', { title: 'Mouse', price: 29, inStock: true });\nnewRow.setValue('price', 39);   // OK\nnewRow.setValue('price', 'x');  // TS Error!\n```\n\n### Typed API without helpers\n\nWhen 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:\n\n```typescript\nimport { createRowModel } from '@revisium/schema-toolkit';\nimport type { JsonObjectSchema } from '@revisium/schema-toolkit';\n\n// Untyped — returns plain RowModel with unknown types\nconst schema: JsonObjectSchema = getSchemaFromApi();\nconst row = createRowModel({ rowId: 'row-1', schema, data });\nrow.getValue('name'); // unknown\n```\n\nSee [Typed API documentation](src/types/TYPED-API.md) for all approaches: `as const`, explicit type declarations, `SchemaFromValue\u003cT\u003e`, and more.\n\n## Reactivity (MobX)\n\nBy 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:\n\n```typescript\nimport * as mobx from 'mobx';\nimport { setReactivityProvider, createMobxProvider } from '@revisium/schema-toolkit/core';\n\nsetReactivityProvider(createMobxProvider(mobx));\n```\n\nAfter this call every model created via `createRowModel`, `createTableModel`, or `createDataModel` becomes fully observable.\n\nSee [Reactivity Module docs](src/core/reactivity/README.md) for the full API, noop behaviour table, and test-setup examples.\n\n## Formulas (Computed Fields)\n\nFields with `x-formula` are automatically computed from other fields. Use `readOnly: true` and the `formula` option in helpers:\n\n```typescript\nimport { obj, num, numFormula, createRowModel } from '@revisium/schema-toolkit';\n\nconst schema = obj({\n  price: num(),\n  quantity: num(),\n  subtotal: numFormula('price * quantity'),\n  tax: numFormula('subtotal * 0.1'),\n  total: numFormula('subtotal + tax'),\n});\n\nconst row = createRowModel({\n  rowId: 'order-1',\n  schema,\n  data: { price: 100, quantity: 5, subtotal: 0, tax: 0, total: 0 },\n});\n\nrow.getPlainValue();\n// { price: 100, quantity: 5, subtotal: 500, tax: 50, total: 550 }\n```\n\nFormulas are evaluated in dependency order. With the MobX reactivity provider configured, changing a dependency triggers automatic re-evaluation of all affected formulas.\n\n### Expression Syntax\n\n| Syntax | Example |\n|--------|---------|\n| Field reference | `price`, `item.quantity` |\n| Arithmetic | `price * quantity`, `a + b - c` |\n| Comparison \u0026 logic | `a \u003e b \u0026\u0026 c \u003c d`, `x ? y : z` |\n| Absolute path | `/rootField` |\n| Relative path | `../siblingField` |\n| Array access | `items[0].price`, `items[*].price` |\n| Array context | `#index`, `#length`, `@prev`, `@next` |\n| Functions | `sum(items[*].price)`, `avg(values)`, `count(array)` |\n\n### Schema-Level Integration\n\nWhen fields are renamed or moved, formula expressions are automatically updated in the generated patches:\n\n```typescript\n// rename price → cost\n// formula 'price * quantity' → 'cost * quantity' (auto-updated)\n```\n\n### Warnings\n\nThe evaluator tracks problematic results (`nan`, `infinity`, `runtime-error`) on the node's `formulaWarning` property.\n\nSee [value-formula docs](src/model/value-formula/README.md) for the runtime engine API and [schema-formula docs](src/model/schema-formula/README.md) for parsing, dependency tracking, and serialization.\n\n## Foreign Key Resolution\n\nSchemas with `foreignKey` fields (string fields referencing another table) can be resolved automatically via `ForeignKeyResolver`:\n\n```typescript\nimport { createForeignKeyResolver, createTableModel, obj, str } from '@revisium/schema-toolkit';\n\nconst resolver = createForeignKeyResolver({\n  loader: {\n    loadSchema: async (tableId) =\u003e api.getTableSchema(tableId),\n    loadRow: async (tableId, rowId) =\u003e api.getRow(tableId, rowId),\n  },\n  prefetch: true,\n});\n\nconst table = createTableModel({\n  tableId: 'products',\n  schema: obj({ name: str(), categoryId: str({ foreignKey: 'categories' }) }),\n  rows: [{ rowId: 'p1', data: { name: 'Laptop', categoryId: 'cat-1' } }],\n  fkResolver: resolver,\n});\n\n// Referenced data is prefetched in the background and available from cache\nconst category = await resolver.getRowData('categories', 'cat-1');\n```\n\nThe same `fkResolver` option is accepted by `createRowModel`. When using `createDataModel`, pass the resolver once and all tables will share it.\n\nSee [ForeignKeyResolver docs](src/model/foreign-key-resolver/README.md) for cache-only mode, prefetch control, loading state, and error handling.\n\n## API\n\n### Schema Helpers\n\n| Function | Description |\n|----------|-------------|\n| `str()` | Create string schema |\n| `num()` | Create number schema |\n| `bool()` | Create boolean schema |\n| `strFormula(expr)` | Create computed string field (`readOnly: true` + `x-formula`) |\n| `numFormula(expr)` | Create computed number field (`readOnly: true` + `x-formula`) |\n| `boolFormula(expr)` | Create computed boolean field (`readOnly: true` + `x-formula`) |\n| `obj(properties)` | Create object schema (generic — preserves property types) |\n| `arr(items)` | Create array schema (generic — preserves items type) |\n| `ref(tableName)` | Create $ref schema |\n\n### Table \u0026 Row\n\n| Function | Description |\n|----------|-------------|\n| `createRowModel(options)` | Create a row model (typed overload when schema is typed) |\n| `createTableModel(options)` | Create a table model (typed overload when schema is typed) |\n\n#### RowModel\n\n| Property / Method | Description |\n|-------------------|-------------|\n| `root` | Typed root node (`InferNode\u003cS\u003e` for typed, `ValueNode` for untyped) |\n| `get(path)` | Get node at path |\n| `getValue(path)` | Get plain value at path |\n| `setValue(path, value)` | Set value at path |\n| `getPlainValue()` | Get full plain value |\n| `patches` | JSON Patch operations (`JsonValuePatch[]`) for current changes |\n| `reset(data?)` | Reset to given data (or schema defaults) and commit |\n| `commit()` | Commit current state as base |\n| `revert()` | Revert to last committed state |\n\n#### ArrayValueNode\n\n| Method | Description |\n|--------|-------------|\n| `at(index)` | Get element at index (supports negative) |\n| `find(predicate)` | Find first element matching predicate, or `undefined` |\n| `findIndex(predicate)` | Find index of first matching element, or `-1` |\n| `push(node)` | Append element |\n| `pushValue(value?)` | Create and append element from value |\n| `removeAt(index)` | Remove element at index |\n| `move(from, to)` | Move element between positions |\n| `clear()` | Remove all elements |\n\n### Value Tree\n\n| Function | Description |\n|----------|-------------|\n| `createTypedTree(schema, data)` | Create a typed value tree with path-based access |\n| `typedNode(node)` | Cast an untyped `ValueNode` to a typed node |\n\n### Schema\n\n| Function | Description |\n|----------|-------------|\n| `createJsonSchemaStore` | Create runtime schema store |\n| `getJsonSchemaStoreByPath` | Navigate schema by path |\n| `applyPatches` | Apply JSON Patch operations to schema |\n| `resolveRefs` | Resolve $ref to inline schemas |\n| `validateJsonFieldName` | Validate field name format |\n| `getInvalidFieldNamesInSchema` | Find invalid field names in schema |\n\n### Value\n\n| Function | Description |\n|----------|-------------|\n| `createJsonValueStore` | Create runtime value store |\n| `getJsonValueByPath` | Navigate value by path |\n| `computeValueDiff` | Compute field-level diff between two values |\n| `traverseValue` | Traverse value tree |\n\n### Foreign Keys\n\n| Function | Description |\n|----------|-------------|\n| `getForeignKeysFromSchema` | Extract foreign keys from schema |\n| `getForeignKeysFromValue` | Extract foreign key values from data |\n| `getForeignKeyPatchesFromSchema` | Get patches for foreign key changes |\n| `replaceForeignKeyValue` | Replace foreign key references |\n\n### Path Utils\n\n| Function | Description |\n|----------|-------------|\n| `parsePath` | Parse dot-notation path to segments |\n| `getParentForPath` | Get parent path |\n| `getPathByStore` | Get path from store |\n| `deepEqual` | Deep equality comparison |\n\n### Type Utilities\n\n| Type | Description |\n|------|-------------|\n| `InferValue\u003cS\u003e` | Schema → plain TypeScript value type |\n| `InferNode\u003cS\u003e` | Schema → typed ValueNode interface |\n| `SchemaFromValue\u003cT\u003e` | Plain TS type → virtual schema shape |\n| `SchemaPaths\u003cS\u003e` | Union of all valid dot-separated paths |\n| `TypedRowModel\u003cS\u003e` | RowModel with typed `root`, `getValue`, `setValue`, `getPlainValue`, `reset` |\n| `TypedTableModel\u003cS\u003e` | TableModel with typed rows, `addRow`, `getRow` |\n\nSee [Typed API documentation](src/types/TYPED-API.md) for the full reference.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frevisium%2Fschema-toolkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frevisium%2Fschema-toolkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frevisium%2Fschema-toolkit/lists"}