ModelResource Internals Deep Dive
This guide explains the internals of ModelResource in Zova through the current public Cabloy Basic source.
Use this page when you want to understand:
- how
ModelResourcebootstraps itself - how
resourceApiis resolved - where permissions, schemas, and form-provider surfaces come from
- how query, mutation, and form helpers are organized
- how query-key prefixing and invalidation work
- why
ModelResourceis the stable resource-owner boundary under both list and entry pages
Why this page exists
This page is the third layer of a small source-reading chain around same-resource model facades:
- Model Architecture for the broader role of Zova Model
- Generated Contract Consumption: Entry Branch for the consumer-side handoff into the owner
- this page for the owner internals that make that handoff work
Several existing docs already explain the meaning and usage of the resource owner well:
- Model Resource Owner Pattern explains the architecture and why the owner boundary exists
- Using
ModelResourcein Your Module explains application-level usage patterns - Resource Model Best Practices and Resource Model Cookbook explain review and implementation guidance
- Rest Resource Under the Hood explains the module/runtime bridge around the owner
What those pages do not isolate directly is the one source-first path through ModelResource itself.
That is the gap this page fills.
The shortest accurate mental model
A practical mental model is:
ModelResourceis a selector-backed owner keyed byresource- bootstrap resolves the final
resourceApi - the owner exposes computed surfaces for permissions, schema, and form provider
- query helpers wrap the shared model/query runtime for select/view/item-style fetches
- mutation helpers wrap the shared model/query runtime for create/update/delete behavior
- form helpers let pages choose schema/data/mutation behavior from
formMeta - query keys and invalidation are centralized as part of the owner contract
That means ModelResource is not only a fetch helper. It is the stable resource semantics boundary for the frontend.
Source-confirmed reading path
When reading this topic, use this order:
zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.tszova/src/suite-vendor/a-zova/modules/a-openapi/src/model/sdk.tszova/packages-zova/zova-core/src/core/sys/util.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.query.tszova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.persister.tszova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/resource/controller.tsxzova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/entry/controller.tsx
That order moves from the owner core, to the OpenAPI model it relies on, to path/config helpers, to query/persister behavior, and finally to the clearest current consumers.
Initialization and bootstrap
The owner core lives in:
zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.tsThe first source-confirmed facts are:
@Model({ enableSelector: true })__init__(resource)requires a concreteresource- bootstrap happens before downstream consumers use the owner surfaces
This matters because ModelResource is not one global singleton.
It is a selector-backed owner whose runtime identity is the current resource name.
resourceApi resolution
The bootstrap path uses:
this.$sdk.getBootstrap(this.resource)this.sys.util.parseResourceApi(resource, api)
The path/config helper lives in:
zova/packages-zova/zova-core/src/core/sys/util.tsThis confirms the owner flow:
- bootstrap data may override the final API path
- otherwise the resource name is translated into a default controller/action API path
That is why resourceApi should be treated as an owner-level resolved surface, not as one hard-coded string duplicated across pages.
Metadata surfaces exposed by the owner
Inside ModelResource, the owner computes and exposes:
permissionsformProviderschemaViewschemaCreateschemaUpdateschemaFilterschemaRowschemaPages
These surfaces are derived from:
- the bootstrap/runtime resource context
- the OpenAPI SDK/schema helpers
This is the clearest source-confirmed reason why both list pages and entry pages reuse the same owner: schema, permission, and provider surfaces belong to one resource boundary.
Query helper layer
The query helpers are organized around:
selectGeneral(actionPath?, query?)select(query?)queryItem(...)view(id)
select(query?) is only a thin wrapper over selectGeneral(undefined, query).
The important source-confirmed detail is that the select key includes a hash of the query object:
['select', actionPath ?? '', hashkey(query)]
That means query identity is deliberate and owner-controlled.
This is the owner-side boundary for list/query fetches, not the page shell.
Mutation helper layer
The mutation helpers are organized around:
create()mutationItem(...)update(id)delete(id)
The most important source-confirmed behavior is:
- select queries are invalidated after successful mutations
- item-root queries are invalidated for the specific row identity
That means mutation helpers do not only perform network requests. They also own the default resource-level invalidation policy.
Form helper layer
The form-facing helpers are:
getFormSchema(formMeta)getFormApiSchemas(formMeta)getFormMutationSubmit(formMeta, id?)getFormData(formMeta, id?)getQueryDataDefaultValue(schemaName?)
This is the clearest source-confirmed bridge from resource owner to entry-page form runtime.
A practical reading rule is:
- list pages mainly consume
schemaFilter,schemaRow, andselect(...) - entry pages mainly consume
formMeta-driven form helpers
Query-key prefixing and invalidation behavior
The shared model/query behavior lives in:
zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useQuery.ts
zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.query.ts
zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.persister.tsThe important source-confirmed behavior is:
- owner query keys are prefixed with bean full name
- selector-backed models also prefix by selector
- persister/query behavior therefore treats one resource owner as a distinct query namespace
This is why ModelResource query identity is stable even when multiple resources or multiple selectors exist at once.
Current consumers that prove the contract
The clearest current consumers are:
rest-resource/src/page/resource/controller.tsxrest-resource/src/page/entry/controller.tsx
These page shells prove that:
- list pages consume the owner for top-level list/schema/runtime entry
- entry pages consume the owner for form/provider/schema/runtime entry
That is enough to verify that ModelResource is the shared owner beneath both branches.
What this guide does not re-explain
This page does not fully re-explain:
- why the resource owner pattern exists -> see Model Resource Owner Pattern
- application-level facade and usage guidance -> see Using
ModelResourcein Your Module - the full list-page runtime -> see Resource List Page Deep Dive
- the full entry-page runtime -> see Resource Entry Page Deep Dive
Its job is only to explain the owner internals and their current consumer contract.
Where to read next
Use these next steps depending on your question:
- if you want to step back to the broader model role, return to Model Architecture
- if you want to step back to the consumer-side handoff, return to Generated Contract Consumption: Entry Branch
- if you want the list-page runtime branch, read Resource List Page Deep Dive
- if you want the entry-page runtime branch, read Resource Entry Page Deep Dive
- if you want the owner-pattern explanation, read Model Resource Owner Pattern
- if you want application-level usage/facade rules, read Using
ModelResourcein Your Module
Final takeaway
The most accurate way to read ModelResource is not as one generic CRUD helper.
Read it as the selector-backed resource owner that:
- bootstraps and resolves
resourceApi - exposes schema, permission, and provider surfaces
- owns query, mutation, and form helper boundaries
- defines query-key and invalidation behavior
- remains the stable resource semantics layer beneath both list pages and entry pages
That is the source-confirmed role of ModelResource in the current Cabloy Basic frontend architecture.