Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 63 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,41 @@ row.getValue('name'); // string (typed!)
row.setValue('price', 19.99); // OK
row.setValue('price', 'wrong'); // TS Error!
row.getPlainValue(); // { name: string, price: number }
row.getPatches(); // JSON Patch operations
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:

```typescript
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
Expand Down Expand Up @@ -215,6 +249,33 @@ See [ForeignKeyResolver docs](src/model/foreign-key-resolver/README.md) for cach
| `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 |
Expand Down Expand Up @@ -268,7 +329,7 @@ See [ForeignKeyResolver docs](src/model/foreign-key-resolver/README.md) for cach
| `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 `getValue`, `setValue`, `getPlainValue` |
| `TypedRowModel<S>` | RowModel with typed `root`, `getValue`, `setValue`, `getPlainValue`, `reset` |
| `TypedTableModel<S>` | TableModel with typed rows, `addRow`, `getRow` |

See [Typed API documentation](src/types/TYPED-API.md) for the full reference.
Expand Down
23 changes: 22 additions & 1 deletion src/model/table/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ interface RowModel {
readonly rowId: string;
readonly tableModel: TableModelLike | null;
readonly tree: ValueTreeLike;
readonly root: ValueNode; // typed root node (InferNode<S> for TypedRowModel)

// Navigation within table
readonly index: number; // -1 if not in table
Expand All @@ -147,6 +148,9 @@ interface RowModel {
commit(): void;
revert(): void;

// Reset to new data (or schema defaults) and commit
reset(data?: unknown): void; // typed as reset(data?: InferValue<S>) for TypedRowModel

// Lifecycle
dispose(): void; // cleans up FormulaEngine reactions
}
Expand Down Expand Up @@ -217,6 +221,19 @@ if (nameNode) {
row.nodeById(nameNode.id); // ValueNode
}

// Root node access
row.root; // typed ValueNode (ObjectValueNode for object schemas)
row.root.isObject(); // true

// Reset to new data (setValue + commit in one call)
row.reset({ name: 'Bob', age: 40 });
row.getPlainValue(); // { name: 'Bob', age: 40 }
row.isDirty; // false

// Reset to schema defaults
row.reset();
row.getPlainValue(); // { name: '', age: 0 }

// No table context — navigation returns defaults
row.tableModel; // null
row.index; // -1
Expand Down Expand Up @@ -495,4 +512,8 @@ None

13. **Standalone createRowModel**: `createRowModel()` provides the same row creation logic (NodeFactory + ValueTree + FormulaEngine) as `TableModel.addRow()`, but without a table context. `TableModel` uses it internally and adds `setTableModel(this)` on top.

14. **Typed Overloads**: Both `createRowModel()` and `createTableModel()` use TypeScript overloads. When schema preserves literal types (via helpers or `as const`), returns `TypedRowModel<S>` / `TypedTableModel<S>` with typed `getValue()`, `setValue()`, `getPlainValue()`. When schema is a plain `JsonSchema` / `JsonObjectSchema`, returns the untyped `RowModel` / `TableModel` as before. See `src/types/TYPED-API.md` for full documentation.
14. **Typed Overloads**: Both `createRowModel()` and `createTableModel()` use TypeScript overloads. When schema preserves literal types (via helpers or `as const`), returns `TypedRowModel<S>` / `TypedTableModel<S>` with typed `root`, `getValue()`, `setValue()`, `getPlainValue()`, `reset()`. When schema is a plain `JsonSchema` / `JsonObjectSchema`, returns the untyped `RowModel` / `TableModel` as before. See `src/types/TYPED-API.md` for full documentation.

15. **Root Accessor**: `root` provides direct access to the tree root node without going through `tree.root`. For `TypedRowModel<S>`, `root` returns `InferNode<S>` — fully typed based on the schema.

16. **Reset Method**: `reset(data?)` replaces the entire row data and commits in one call. Avoids the need to dispose and recreate the RowModel when data needs to change completely. Uses `tree.setValue('', data)` internally, which handles type narrowing for object/array/primitive roots.
17 changes: 16 additions & 1 deletion src/model/table/row/RowModelImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { JsonValuePatch } from '../../../types/json-value-patch.types.js';
import { generateDefaultValue } from '../../default-value/index.js';
import { FormulaEngine } from '../../value-formula/FormulaEngine.js';
import { createNodeFactory } from '../../value-node/NodeFactory.js';
import type { RefSchemas } from '../../value-node/NodeFactory.js';
import type { ValueNode } from '../../value-node/types.js';
import { ValueTree } from '../../value-tree/ValueTree.js';
import type { RowModel, RowModelOptions, TableModelLike, ValueTreeLike } from './types.js';
Expand All @@ -18,10 +19,14 @@ export class RowModelImpl implements RowModel {
constructor(
private readonly _rowId: string,
private readonly _tree: ValueTreeLike,
private readonly _schema: JsonSchema,
private readonly _refSchemas?: RefSchemas,
) {
makeAutoObservable(this, {
_rowId: false,
_tree: false,
_schema: false,
_refSchemas: false,
_tableModel: 'observable.ref',
});
}
Expand All @@ -38,6 +43,10 @@ export class RowModelImpl implements RowModel {
return this._tree;
}

get root(): ValueNode {
return this._tree.root;
}

get index(): number {
if (!this._tableModel) {
return UNSET_INDEX;
Expand Down Expand Up @@ -111,6 +120,12 @@ export class RowModelImpl implements RowModel {
this._tree.revert();
}

reset(data?: unknown): void {
const value = data ?? generateDefaultValue(this._schema, { refSchemas: this._refSchemas });
this._tree.setValue('', value);
this._tree.commit();
}

dispose(): void {
this._tree.dispose();
}
Expand All @@ -137,5 +152,5 @@ export function createRowModel(options: RowModelOptions): RowModel {
const formulaEngine = new FormulaEngine(valueTree);
valueTree.setFormulaEngine(formulaEngine);

return new RowModelImpl(options.rowId, valueTree);
return new RowModelImpl(options.rowId, valueTree, options.schema, options.refSchemas);
}
Loading