Data In And Out
Instances parse from JSON in add and serialize back in json() through the same field declarations. Use this page when wiring models to an API: what goes on the wire, how server-assigned ids replace temporary ones, and why nothing breaks in between.
Examples on this page run inside an active scope - see Collections and instances.
Serializing
const t = todos.add({ id: "1", title: "write docs" });
t.json(); // { id: "1", title: "write docs", done: false }
JSON.stringify(t); // same - toJSON is wired to json()What goes out:
- bound fields under their JSON keys, through their output transforms;
- references as ids, children embedded as objects;
- local fields, inverse views, and members - never.
Temporary Ids
An instance created without an id gets an autogenerated, temporary one. It works everywhere locally but never serializes:
const t = todos.add({ title: "new" });
t.id; // "~tmp1" - usable in routes, get(), references
t.json(); // { title: "new", done: false } - no id: the server assigns oneOnly ids that came from outside - add input or rebind - appear in json().
Changing An Id: rebind
The optimistic-creation flow end to end:
const t = todos.add({ name }); // temporary id
navigate(`/todo/${t.id}`); // URL with the temporary id
const saved = await api.save(t.json()); // payload has no id
t.rebind(saved.id); // now it has the server oneWhat happens on rebind:
- every relation pointing at the instance is rewritten to the new id;
- the old id keeps resolving:
todos.get("~tmp1")returns the same instance, so the open route and any component holding the old id keep working; - that forwarding is lookup-only - it never appears in indexes, query results, or
json(); - the forwarding entry dies with the instance.
Id Aliases
Every rebind leaves an alias: the old id keeps finding the instance. That is the only alias mechanism - there is no addAlias, an alias is always the trace of a rename:
const t = todos.add({ id: "draft-7", title: "x" });
t.rebind("42");
todos.get("draft-7") === t; // true - the alias resolves
todos.get("42") === t; // true - the real id, always wins over aliases
t.json().id; // "42" - aliases never serializeThe alias rules:
- resolution is fallback-only: a real id in the collection always wins over an alias with the same spelling;
- aliases live in
get(anduseModel(todos.get(...))) only - never in indexes, query results, or relations; - an alias dies with its instance, so the table holds live renames, not history;
addmatches real ids only; an input whose id equals a live alias takes that spelling over as a real id and drops the alias with a dev warning;- aliases chain flat: after
rebind("A")thenrebind("B"), both"A"and the original id point straight at"B"- no chain walks.
Looking an entity up by several identifiers - a slug and a uuid - is not an alias and not a second id. It is a second field:
const Article = staticModel({
data: {
title: f.string(),
slug: f.string().indexed().unique(),
},
});
articles.where(Article.slug.eq("intro")).first; // lookup by the secondary keyStable List Keys
instance.key is the identity for UI lists:
{todos.items.map((t) => <Row key={t.key} todo={t} />)}It is not the id: it survives rebind (the row does not remount when the server renames the entity) and never repeats when instance slots are reused (rows of different entities are never glued together).
Loading In Any Order
Server payloads rarely arrive in dependency order, and they do not have to:
- a reference id loaded before its target reads as
null, then resolves once the target is added; - re-
adding a fetched list merges by id - existing instances update in place, references to them stay valid; - a children array is authoritative: known ids merge, new entries are created, absent children are disposed, order is taken from the array.
Adapting Foreign Formats
The model keeps one canonical JSON shape. A legacy or secondary format is a function at the edge:
function fromLegacy(row: LegacyRow) {
return todos.add({ name: row.title_text, done: row.state === 2 });
}Contract
instance.json(): Record<string, unknown>; // omits a temporary id
instance.toJSON(); // alias used by JSON.stringify
instance.rebind(newId: string): void; // rewrites references, keeps forwarding
instance.key: string; // stable UI identity
collection.get(oldId); // resolves forwarding after rebindCommon Cases
Use this flow for:
- optimistic creation - add, navigate, save,
rebind; - PATCH payloads -
json()of an instance the user edited; - cache refresh - re-
addthe fetched list over the live collection; - multi-request loading - entities and their references in any order.
Related
- Fields and data - keys, defaults, transforms, local fields.
- Relations - how references and children serialize.
- UI bindings -
instance.keyandget(id)in components.