A lightweight utility that wraps any object with a proxy to track changes, validate, and provide Svelte-compatible reactivity.
deno add jsr:@marianmeres/modelizenpm install @marianmeres/modelizeimport { modelize } from "@marianmeres/modelize";
const user = modelize({ name: "John", age: 30 });
// Track changes
user.name = "Jane";
user.__isDirty; // true
user.__dirty; // Set { 'name' }
// Reset dirty state (keeps values)
user.__reset();
user.__isDirty; // false
// Reset to initial values
user.__resetToInitial();
user.name; // 'John'- Dirty tracking - know which properties have changed
- Validation - JSON Schema and/or custom validator functions
- Field-level errors - detailed validation error reports
- Reset to initial - restore original values
- Svelte-compatible - works with
$auto-subscription - Lightweight - minimal API, no magic
Wraps source object with a proxy.
interface ModelizeOptions<T> {
schema?: JSONSchema; // JSON Schema for validation
validate?: (model: T) => true | string; // Custom validator
strict?: boolean; // Disallow new properties (default: true)
}| Property | Type | Description |
|---|---|---|
__dirty |
Set<keyof T> |
Set of modified property keys (empty when clean) |
__isDirty |
boolean |
true if any property has been modified |
__isValid |
boolean |
true if model passes validation |
__errors |
ValidationError[] |
Last validation errors (empty if valid) |
__source |
T |
The original unwrapped source object |
__initial |
T |
Deep clone of original values (for reset) |
| Method | Description |
|---|---|
__validate() |
Throws ModelizeValidationError if invalid, returns true if valid |
__reset() |
Clears dirty state (values unchanged) |
__resetToInitial() |
Restores all properties to initial values and clears dirty state |
__hydrate(data, options?) |
Bulk update properties. Options: { resetDirty?: boolean } |
subscribe(callback) |
Svelte-compatible subscription. Returns unsubscribe function |
const model = modelize({ name: "John", age: 30 });
model.name = "Jane";
model.__isDirty; // true
model.__dirty; // Set { 'name' }
model.__dirty.has("name"); // true
model.__dirty.has("age"); // false
// Clear dirty state (values stay changed)
model.__reset();
model.__isDirty; // false
model.name; // 'Jane' (still changed)
// Or reset to initial values
model.name = "Modified";
model.__resetToInitial();
model.name; // 'John' (original value)
model.__isDirty; // falseconst user = modelize(
{ age: 25 },
{
schema: {
type: "object",
properties: {
age: { type: "number", minimum: 0, maximum: 120 },
},
},
},
);
user.age = -5;
user.__isValid; // false
user.__errors; // [{ path: '/age', message: 'must be >= 0' }]
try {
user.__validate();
} catch (e) {
// ModelizeValidationError with e.errors array
}const form = modelize(
{ password: "", confirmPassword: "" },
{
validate: (m) => m.password === m.confirmPassword ? true : "Passwords must match",
},
);
form.password = "secret";
form.confirmPassword = "different";
form.__isValid; // false
form.__errors; // [{ path: '/', message: 'Passwords must match' }]Both JSON Schema and custom validator can be used together. All errors are collected.
const user = modelize(
{ age: -5, status: "invalid" },
{
schema: {
type: "object",
properties: { age: { minimum: 0 } },
},
validate: (m) =>
["active", "inactive"].includes(m.status) ? true : "Invalid status",
},
);
user.__errors; // Contains both schema and custom validation errorsconst model = modelize({ name: "John", age: 30, city: "NYC" });
// Update multiple properties at once (single notification)
model.__hydrate({ name: "Jane", age: 25 });
// Hydrate and clear dirty state
model.__hydrate({ name: "Bob" }, { resetDirty: true });
model.__isDirty; // falseBy default, adding new properties is not allowed:
const model = modelize({ name: "John" });
model.extra = "value"; // throws Error
// Allow dynamic properties
const flexible = modelize({ name: "John" }, { strict: false });
flexible.extra = "value"; // worksimport type {
JSONSchema,
Modelized,
ModelizeOptions,
ModelizeValidationError,
ValidationError,
} from "@marianmeres/modelize";You'll notice that most methods and properties added by modelize use a double underscore
prefix (e.g., __isDirty, __validate()). This is intentional:
-
Avoid collisions: Your source object might have properties like
dirty,valid, orreset. The__prefix ensures our meta-properties never conflict with your data. -
Clear distinction: When reading code,
model.nameis obviously your data, whilemodel.__isDirtyis clearly a framework feature.
The only exception is subscribe, which has no prefix to maintain compatibility with the
Svelte store contract (allowing $model auto-subscription syntax).
- Shallow tracking: Only direct property changes are tracked. Nested object mutations
(e.g.,
model.nested.prop = x) don't trigger dirty state on the parent. - Reserved names: Properties
__dirty,__isDirty,__isValid,__source,__initial,__errors,__validate,__reset,__resetToInitial,__hydrate, andsubscribeare reserved and cannot be used in source objects. - Validation is lazy: Only runs when you access
__isValidor call__validate().
For complete API specification, see API.md.
MIT