Apps

Model

# model — in-memory typed models generated by the Schema Builder `deft/model` is the runtime behind `<schema>.scm/model.dft`. The generated module supplies the schema's relation table, persistence refs, insert order, and id metadata; this package supplies the model handle and relation API. A generated model is not required to be persistent. It is an in-memory graph you can create, inspect, dump, and later commit to SQLite in one transaction. ## Quick start
from "shop.scm/model" import new-shop

set model [new-shop]

set c [$model create %Customer{ :id "c1" :name "Ada" }]
set a [$c create [Address --street "Main"]]
set o [$c create [Order --id "o1"]]

[$c list Address]                 # @{ %Address{...} }
[$c list Order]
[$model find Customer "c1"]
[$model list Customer]
[$model dump]

set r [await [$model persist "shop.db"]]
# r => %Ok{:value %{ :types %{ "Customer" 1 ... } :path "shop.db" }}

set loaded [new-shop]
set l [await [$loaded load "shop.db"]]
# l => %Ok{:value %{ :types %{ "Customer" 1 ... } :version 1 :seq 1 }}
## Model operations | Call | Result | |---|---| | `[$model create %Type{...}]` | Stored record, attached to the model; raises on validation failure | | `[$model find Type id]` | Record or nil (exact type) | | `[$model list Type]` / `[$model all Type]` | Vector of records | | `[$model dump]` | Snapshot `%{ :types %{ "Type" @{...} } :counts %{...} }` | | `[$model reset]` | Clears entities/ids but keeps the schema | | `[$model persist "path.db"]` | Async `%Ok`/`%Err`; snapshot-replace the model tables and record the schema version | | `[$model load "path.db"]` | Async `%Ok`/`%Err`; verify metadata, load all rows, replace the model atomically | `Type` is a bare type name (`Customer`) or keyword/string. Child operations use the same convention: `[$customer list Address]`. ## Relations - Association — child has a `:references` field to the parent. Creating the child injects the parent's referenced value into that field, and the child is stored as an entity. - Composition — parent has a field typed as another record/union. Creating the child appends/sets it inside the parent; composition children are not independent entities and are not separately findable. `create` resolves the child type against the parent's generated relations. If several relations match, it raises; `list` combines all matching relations. Inherited relations are available on subtypes. Imported parent types (for example a `Customer` from another `.scm`) also get relations/impls when a local type references them, so `[$customer create [Order ...]]` works across schema imports. Composition creation updates the parent copy held by the model, not the immutable record in your variable. Read the current children with `list` (or `find`) rather than a previously captured parent field. ## Ids - A declared `:primary-key` field is used directly and is required on create. - If the type has no PK and a field named `id`, that field is the key and is required. - Otherwise the model assigns an implicit numeric `:id` to entity records. Persisted associations require a declared primary key on the parent. Implicit ids are in-memory only; a standalone no-PK entity persisted to SQLite receives a database-assigned id. ## Dump `[$model dump]` returns the current state as data, not a printed string:
echo [deft/stringify [$model dump]]
Internal `:__model` context is stripped from the snapshot; model-assigned implicit ids remain. Association children appear under their type; composition children appear nested inside their parent. ## Persistence `persist` is a snapshot, not an append: inside one transaction it deletes the model's existing rows (children first), inserts the current entities parent-first with `persist/insert-many`, writes the schema metadata, and commits. Any failure rolls back both data and metadata. Composition fields stay nested in the parent row (persist's JSON codec); association rows are separate. Views are not created. After commit, the rows returned by `insert-many` replace the in-memory entities, so database-assigned implicit ids and `:seq` agree with the DB. `load` reads the current `_persist_meta_` blob for the model scope and verifies its version and hash against the generated descriptor before reading any rows. The dataset is staged and swapped in atomically; a mismatch or bad row leaves the existing model untouched. `load` requires the matching generated `model.dft`/`types.dft` — the metadata blob is a manifest, not executable schema.
set model [new-shop]
set r [await [$model load "shop.db"]]
# r => %Ok{:value %{ :types %{"Customer" 1 …} :version 3 :seq 1 }}
If the schema changed, run the generated schema module's `migrate` explicitly, then retry `load`; load itself never auto-migrates. For a complete normal app that saves configuration/last state this way, see `packages/todo` (`retro app deft/todo`) and the manual page `help/manual/03-Platform/10-app-state.md`. ## Limits - `load` is strict: the persisted schema/version/hash must match the generated module. Older blobs are retained for diagnostics, not for loading a different schema. - Exact-type `find`/`all` (no polymorphic find). - Persist owns its tables: snapshot replacement deletes rows in the model's mapped tables. - No persisted associations backed by implicit ids. - Persist stores each exact type in its own table. A subtype row is not materialized into its parent's table, so an association FK that targets the parent table needs a parent-table row (create the base entity separately). - Imported parent types referenced by local records are part of the model graph: `persist` ensures their tables (with default storage options) and inserts their entities before local children. If the owning schema customizes storage options, ensure/migrate it separately with matching options. - Composition children with implicit ids remain model-local; give the child a declared primary key if its identity must survive a round-trip. - One generated-model module per type namespace; duplicate type names across schemas collide as usual. - The generated module is regenerated on every Schema Builder save. ## Tests ```sh deft test packages/model/test/test_model.dft deft test packages/schema-builder/test/test_model_emit.dft deft test packages/schema-builder/test/test_model_integration.dft ````