Install and requirements
- Node.js ^22.18.0 or >=24.11.0
- npm >=11
- Native Proxy support
- Native Map support when using Map state
import {Model} from 'white-label-model';
const {Model} = require('white-label-model');
State model
Model accepts a plain object, array, or Map as its root state. Mutations are synchronous: when a mutating call returns, accepted state is already visible through get() and listeners have already run.
Object update() operations are shallow merges. Array and Map update() operations address one existing member. clear() keeps the current root shape.
const profile = new Model({name: 'Ada'});
profile.on('change', state => console.log(state));
profile.update({name: 'Grace'});
Runtime validation
The validator is optional. It receives the complete proposed state before construction or an explicit set/update/push/delete mutation is committed. Return true to accept and false to reject.
TypeScript checks source code at development time; the validator checks actual runtime values. Direct nested writes through get() are observable but do not invoke the whole-state validator. clear() and destroy() intentionally bypass it so cleanup cannot be blocked.
const user = new Model({name: 'Ada'}, value =>
typeof value === 'object' && value !== null &&
typeof value.name === 'string'
);
user.set({name: 'Grace'}); // true
user.set({name: 42}); // false
Deep observation
Nested objects, arrays, and Maps are proxied lazily as accessed. Direct writes emit change and a mutate event containing operation, path, oldValue, newValue, and the complete current state.
Writes using __proto__, constructor, or prototype are rejected to avoid prototype-pollution paths.
model.on('mutate', mutation => {
console.log(mutation.path);
});
model.get().user.preferences.theme = 'dark';
Events and mediator integration
Accepted non-silent explicit mutations emit change first, followed by the matching operation event: set, update, push, delete, or clear.
Assign name and an EventEmitter-compatible mediator to relay local events as model:<name>:<event>. The package does not import Mediator directly.
Core API
new Model(data?, validator?)
Create an observable root state container.
| Parameter | Type | Default | Description |
|---|
data | object | unknown[] | Map | — | Initial root state; defaults to {}. |
validator | (state: unknown) => boolean | — | Optional whole-state runtime acceptance check. |
- Returns
- A Model instance.
- Throws / rejects
- TypeError when the initial root shape is unsupported or initial validation fails.
- Runtime
- Browser + Node.js
initialize()
Start the lifecycle for chaining and composition.
- Returns
- The same Model instance.
- Runtime
- Browser + Node.js
get() / get(key)
Read the complete observable root or one object property, array index, or Map entry.
| Parameter | Type | Default | Description |
|---|
key | unknown | — | Optional property/index/Map key. |
- Returns
- The root state, one resolved value, or undefined when a keyed lookup does not resolve.
- Runtime
- Browser + Node.js
- The returned root is observable. Direct nested writes are tracked but do not run the whole-state validator.
set(data, silent?)
Replace the complete root state with another supported root shape.
| Parameter | Type | Default | Description |
|---|
data | unknown | — | Replacement root state. |
silent | boolean | false | Suppress change/set events when true. |
- Returns
- true when accepted; false for unsupported or validator-rejected data.
- Events / side effects
- change → set unless silent.
- Runtime
- Browser + Node.js
update(partial, silent?) / update(key, value, silent?)
Shallow-merge an object root or replace/shallow-merge one existing array or Map member.
| Parameter | Type | Default | Description |
|---|
partial | object | — | Fields merged into an object root. |
key | unknown | — | Existing array index or Map key. |
value | unknown | — | Replacement or shallow-merge value. |
silent | boolean | false | Suppress emitted events. |
- Returns
- true when accepted; false for an invalid call, missing member, or validator rejection.
- Events / side effects
- change → update unless silent.
- Runtime
- Browser + Node.js
- Object updates are shallow, not recursive.
- Prototype-pollution keys are blocked from merge input.
push(valueOrValues, silent?) / push(key, value, silent?)
Append array values or insert Map entries.
| Parameter | Type | Default | Description |
|---|
valueOrValues | unknown | unknown[] | — | Value or values appended to an array root. |
key | unknown | — | Map key. |
value | unknown | — | Map value. |
- Returns
- true when accepted; false for invalid calls, object roots, or validator rejection.
- Events / side effects
- change → push unless silent.
- Runtime
- Browser + Node.js
delete(key, silent?)
Delete one object property, array index, or Map entry.
| Parameter | Type | Default | Description |
|---|
key | PropertyKey | number | unknown | — | Member to remove. |
silent | boolean | false | Suppress emitted events. |
- Returns
- true when removed; false when the member is missing/invalid or validation rejects the resulting state.
- Events / side effects
- change → delete unless silent.
- Runtime
- Browser + Node.js
clear(silent?)
Reset the root to an empty value of the same shape.
- Returns
- true.
- Events / side effects
- change → clear unless silent.
- Runtime
- Browser + Node.js
- Validation is intentionally bypassed so cleanup cannot be blocked.
destroy()
Clear state silently and release listeners.
- Returns
- The same Model instance after cleanup.
- Runtime
- Browser + Node.js
Current compatibility surface
These methods are publicly declared today but are not part of the recommended state-management workflow. They are documented here so the reference matches the shipped API exactly.
serviceGet()
Compatibility placeholder; performs no I/O in the current implementation.
- Returns
- Promise resolving to {}.
- Runtime
- Browser + Node.js
servicePatch()
Compatibility placeholder; performs no I/O in the current implementation.
- Returns
- Promise resolving to {}.
- Runtime
- Browser + Node.js
servicePost()
Compatibility placeholder; performs no I/O in the current implementation.
- Returns
- Promise resolving to {}.
- Runtime
- Browser + Node.js
servicePut()
Compatibility placeholder; performs no I/O in the current implementation.
- Returns
- Promise resolving to {}.
- Runtime
- Browser + Node.js
Inherited utility methods
Model currently inherits these public utility methods. Application code normally does not need them for ordinary Model usage.
isMap(value)
Return whether a value is a native Map, including cross-realm Maps.
- Returns
- boolean.
- Runtime
- Browser + Node.js
isFinite(value)
Accept finite numeric values without coercion.
- Returns
- boolean.
- Runtime
- Browser + Node.js
isPlainObject(value)
Return whether a value is an ordinary or null-prototype object rather than a class instance.
- Returns
- boolean.
- Runtime
- Browser + Node.js
pullAt(array, index)
Remove one array member in place.
- Returns
- The same array.
- Runtime
- Browser + Node.js
extend(object1, object2)
Create a safe shallow merge while blocking prototype-pollution keys.
- Returns
- A new merged object.
- Runtime
- Browser + Node.js
message(messages, data)
Emit local events and optional namespaced mediator events.
- Returns
- false for falsey data; true after emitting requested events.
- Runtime
- Browser + Node.js
TypeScript
Model<T> describes the supported root state and provides typed get() root access.
TypeScript does not validate runtime data; add the optional validator when an external trust boundary needs enforcement.
Security and trust boundaries
Prototype-pollution keys are blocked from direct object writes and safe merge paths.
Validate API, storage, decoded JSON, or user-controlled data before treating it as trusted application state.
Design boundaries
Model does not own rendering, routing, networking, persistence, or application-wide event orchestration.
Resolve asynchronous I/O first, then apply the result with a synchronous Model mutation.