Model Runtime Under the Hood
This guide explains the lower-level runtime of a-model in Zova through the current public Cabloy Basic source.
Use this page when you want to understand:
- how the model module bootstraps its shared query infrastructure
- how
$queryClientbecomes available on beans - how the model inheritance stack is assembled
- how query keys are prefixed and normalized
- how
useQuery, state helpers, and persister behavior fit together - how SSR dehydrate/hydrate and stale-time rules participate in the model runtime
Why this page exists
Several existing docs already explain the role and usage of models well:
What those pages do not isolate directly is the lower-level generic a-model runtime chain.
That is the gap this page fills.
The shortest accurate mental model
A practical mental model is:
@Model()registers a model bean on the model scene- the
a-modelmodule monkey bootstraps a sharedQueryClient - the monkey injects
$queryClientonto bean instances BeanModelBaseis the public entry into a deeper inheritance stack for query, mutation, state, and persistence behavior- all model query/state operations prefix keys by model identity, and selector-enabled models add selector identity too
useQuery, state helpers, and persister behavior cooperate to apply SSR-aware stale/hydration/persistence rules
That means the model layer is not only a set of convenience helpers. It is a lower-level shared query/persistence runtime for the frontend.
Source-confirmed reading path
When reading this topic, use this order:
zova/src/suite-vendor/a-zova/modules/a-model/src/monkey.tszova/src/suite-vendor/a-zova/modules/a-model/src/service/storage.tszova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.modelBase.tszova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.last.tszova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.query.tszova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useQuery.tszova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useState.tszova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.persister.tszova/src/suite-vendor/a-zova/modules/a-model/src/config/config.ts
That order moves from module bootstrapping, to shared query storage, to the public model bean entry, then into identity, query, useQuery, state, persister, and finally default config behavior.
monkey/bootstrap and QueryClient wiring
The module monkey lives in:
zova/src/suite-vendor/a-zova/modules/a-model/src/monkey.tsThe source confirms that:
appInitialize()creates/initializes shared storagemoduleLoaded()lets the storage service finish module-level setupbeanInit(...)defines$queryClienton beans$queryClientis resolved throughuseQueryClient()and marked raw
This is the first important lower-level rule:
- the model runtime is bootstrapped at module/app level before higher model helpers are used
ServiceStorage and shared query lifecycle
The shared query-storage/runtime owner lives in:
zova/src/suite-vendor/a-zova/modules/a-model/src/service/storage.tsIts main jobs are:
- create the shared
QueryClient - create the
QueryCache - install the Vue Query plugin into the app
- on the server, dehydrate query state after render
- on the client, hydrate query state during SSR pre-hydration
- clear the query client after server rendering completes
This is the second important lower-level rule:
- the model runtime is not only about per-model helper methods
- it also owns one shared query lifecycle around SSR and client hydration
The public model bean entry and inheritance stack
The public bean entry lives in:
zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.modelBase.tsIt is intentionally thin:
BeanModelBase extends BeanModelFirst
A key lower-level identity layer appears in:
zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.last.tsThis file confirms that:
- selector identity is stored on the model when
enableSelectoris on selfandscopeSelfprovide access to the effective model bean/runtime scope
This is the rule to keep in mind when reading the rest of the runtime:
BeanModelBaseis the public entry- the lower-level behavior is assembled through the internal inheritance chain beneath it
Query-key prefixing and normalization
The core query identity logic lives in:
zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.query.tsThe most important lower-level rule is _forceQueryKeyPrefix(...).
The source confirms that query keys are prefixed with:
- bean full name
- selector, when selector mode is enabled
This matters because model query identity is not only whatever the caller typed in queryKey.
The model runtime namespaces the key automatically.
This same layer also owns:
$setQueryData(...)$invalidateQueries(...)$refetchQueries(...)$queryFind(...)- filter normalization through
$normalizeFilters(...)
That means key prefixing and invalidation are part of the same lower-level runtime layer.
useQuery runtime behavior
The query wrapper lives in:
zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useQuery.tsThe source confirms that useQuery behavior is not passed through untouched.
It adds:
- prefixed query keys
- persister integration
- default error handling
- SSR-aware stale-time branching
The important SSR-aware rule is:
- async queries use configured async stale time by default
- during client SSR pre-hydration, cached SSR data can switch to SSR stale-time behavior
That means stale-time behavior is part of the model runtime contract, not only a local query option.
State helper families as one runtime family
The state helper layer lives mainly in:
zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useState.tsThis is where the five main state families are exposed:
datamemlocalcookiedb
The important lower-level point is that these are not unrelated helpers.
They are one model-owned runtime family built on shared query/persistence identity.
A practical reading rule is:
- different state families differ in persistence/storage and SSR behavior
- but they still participate in one model runtime vocabulary and query-key system
Persister storage selection and restore/save/remove behavior
The persister layer lives in:
zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.persister.tsIts main jobs are:
- choose the storage backend
- compute storage key and prefix
- apply
buster - apply
maxAge - restore persisted state
- save persisted state
- remove persisted state
- prefix persisted query identity consistently with the model/query runtime
The storage options include:
- cookie
- local
- db
This is the clearest lower-level source for understanding why model persistence behavior differs by state family and why restored state still respects model identity.
Default config behavior
The default model-runtime config lives in:
zova/src/suite-vendor/a-zova/modules/a-model/src/config/config.tsThis file confirms default behavior such as:
- query retry/refetch defaults
- async stale time
- SSR stale time
- persistence max-age defaults
- dehydration rules for sync persister-backed queries
This is the place to check when the runtime feels surprising even though your page/model code looks correct.
What this page does not re-explain
This page does not fully re-explain:
- the architectural role of Model -> see Model Architecture
- the public helper-family usage -> see Model State Guide
- the resource-owner pattern -> see Model Resource Owner Pattern
- the resource-owner internals -> see ModelResource Internals Deep Dive
Its job is only to explain the lower-level generic a-model runtime.
Where to read next
Use these next steps depending on your question:
- if you want the architectural role of Model, read Model Architecture
- if you want state helper usage, read Model State Guide
- if you want resource-owner patterns built on this runtime, read Model Resource Owner Pattern
- if you want the resource-owner itself, read ModelResource Internals Deep Dive
- if you want the lower OpenAPI/schema substrate beneath some model behavior, read A-OpenAPI Under the Hood
Final takeaway
The most accurate way to read a-model is not as one set of convenience wrappers around TanStack Query.
Read it as the lower-level frontend model runtime that:
- bootstraps a shared query client
- injects query access into beans
- composes model identity and selector-aware query-key namespacing
- applies persistence and SSR-aware hydration/stale behavior
- supports higher-level model and resource-owner patterns on top of that lower runtime
That is the source-confirmed role of a-model in the current Cabloy Basic frontend architecture.