Rest Resource Under the Hood
This guide explains the source-level runtime path behind the rest-resource module.
Use this page together with:
- Model Resource Owner Pattern
- Rest Resource Source Reading Map
- Using
ModelResourcein Your Module - Table + Resource CRUD Cookbook
- Form Guide
- Zova Source Reading Map
Use this page after Model Resource Owner Pattern when you want to move from the model-focused explanation to the internal cooperation among route records, generated page wrappers, page-shell controllers, schema-driven block rendering, selector-backed resource ownership, and downstream CRUD block runtimes.
If your next question is not “how does this runtime work?” but “which files should I read next?”, continue with Rest Resource Source Reading Map.
If your next question is specifically how a resource entry route becomes a working entry page through rest-resource, basic-pageentry, blockForm, and blockToolbarRow, continue with Resource Entry Page Deep Dive.
If your next question is specifically how the resource list route becomes a working list page through basic-page, blockFilter, blockTable, blockPager, and ZTable, continue with Resource List Page Deep Dive.
If your next question is specifically how filter state becomes query, then ModelResource.select(query), and finally list/paged data, continue with Filter to Query to Select Data Flow Guide.
If your next question is specifically how the resource owner works internally, continue with ModelResource Internals Deep Dive.
If your next question is specifically how the lower-level generic model runtime works beneath that owner, continue with Model Runtime Under the Hood.
TIP
Resource docs path
- Model Resource Owner Pattern — learn why
ModelResourceis a resource owner - Rest Resource Under the Hood — learn how the module runtime pieces cooperate
- Rest Resource Source Reading Map — learn which files to read next
- Using
ModelResourcein Your Module — learn how to reuse the owner in application code - Resource Model Best Practices — learn the review guardrails
- Resource Model Cookbook — learn the common implementation shapes
You are here: step 2. Previous page: Model Resource Owner Pattern. Next recommended page: Rest Resource Source Reading Map.
Why this page exists
The existing ModelResource pages already explain the owner pattern clearly:
- why selector identity matters
- why the model owns schema, permissions, queries, mutations, and invalidation
- how business modules should reuse the existing resource owner directly or through a thin facade
What those pages do not focus on is the larger module-level runtime around that owner:
- where the generic
/rest/resource/...routes are declared - how those routes become page-controller instances
- why the page controllers are intentionally thin
- how
rest.blocksdrives page assembly - where the deeper list-page and entry-page CRUD runtime really lives
- how commands and other consumers reuse the same owner boundary
This page is that bridge.
The shortest accurate runtime model
For a typical rest-resource page, the shortest accurate model is:
routes.tsdeclares one generic resource list route and two generic resource entry routes- generated
ZPage*wrappers bind those route records to page-controller classes throughcreateZovaComponentPage(...) - the page controller resolves a selector-backed
ModelResourceinstance from the currentresource ModelResource.__init__(resource)bootstraps the resource metadata and resolves the finalresourceApi- the model exposes resource-level computed surfaces such as permissions, form provider, and select/view/create/update schemas
- the list shell reads
schemaRow.rest.blocks, while the entry shell readsformSchema.rest.blocks - those blocks usually enter downstream generic runtimes such as
basic-page:blockPageorbasic-pageentry:blockPageEntry - those downstream runtimes resolve the same selector-backed
ModelResourceagain and own the deeper list/form behavior - commands such as delete can also reuse the same model boundary instead of inventing page-local mutation logic
That is why rest-resource is not only one reusable model bean.
It is a route-driven, schema-driven bridge that connects:
- generic resource routes
- thin page-shell controllers
- generic Basic CRUD blocks
- one stable resource-owner model
Runtime relationship map
Use this diagram when the question is:
- where does one
/rest/resource/...route actually go? - where does schema-driven block composition happen?
- where does the deeper CRUD runtime begin?
- which layer owns fetch, form, and mutation semantics?
Route record
└─ routes.ts
└─ ZPageResource / ZPageEntry / ZPageEntryCreate
└─ createZovaComponentPage(...)
└─ page-controller shell
├─ resolves current resource / id / formScene
├─ resolves selector-backed ModelResource
├─ autoloads top-level API schema surface
└─ reads schemaRow.rest.blocks or formSchema.rest.blocks
│
▼
schema-driven block composition
│
┌──────────────┴──────────────┐
▼ ▼
basic-page:blockPage basic-pageentry:blockPageEntry
│ │
├─ filter / pager / table ├─ formMeta / formData / submit
├─ query orchestration ├─ page-title / dirty-state sync
└─ deeper list runtime └─ deeper entry runtime
│
▼
selector-backed ModelResource
│
├─ bootstrap → resourceApi
├─ permissions / schemas / provider
├─ select / view queries
├─ create / update / delete mutations
└─ centralized invalidation
│
▼
OpenAPI bootstrap + fetch runtimeRead this top-down:
- the route and page shell choose the scene
- schema metadata chooses the block composition
- generic Basic blocks own the richer CRUD page behavior
ModelResourceremains the stable owner of resource semantics underneath all of them
A concrete source specimen
The smallest module-level source set is:
zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/routes.ts
zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/resource/controller.tsx
zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/entry/controller.tsx
zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.tsA deeper runtime continuation is:
zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx
zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockPageEntry/controller.tsx
zova/src/suite/cabloy-basic/modules/basic-commands/src/bean/command.delete.tsxThose files show the core architectural split clearly:
rest-resourceowns the module bridge and page shellsModelResourceowns the resource boundarybasic-pageandbasic-pageentryown the richer reusable CRUD block runtime- command beans can reuse the same resource owner outside page rendering
The core source-reading path
When you want to trace the full mechanism, read these files in order:
zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/routes.tszova/src/suite-vendor/a-cabloy/modules/rest-resource/src/.metadata/page/resource.tszova/src/suite-vendor/a-cabloy/modules/rest-resource/src/.metadata/page/entry.tszova/src/suite-vendor/a-cabloy/modules/rest-resource/src/.metadata/page/entryCreate.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.tsxzova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/entryCreate/controller.tsxzova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.tszova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsxzova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockPageEntry/controller.tsxzova/src/suite/cabloy-basic/modules/basic-commands/src/bean/command.delete.tsxzova/src/suite-vendor/a-cabloy/modules/rest-resource/src/.metadata/index.ts
A compact role map is:
routes.tsshows the public route surface.metadata/page/*.tsshows how route components entercreateZovaComponentPage(...)page/resource/controller.tsxshows the list-page shellpage/entry/controller.tsxshows the entry-page shellpage/entryCreate/controller.tsxshows virtual create-route reusemodel/resource.tsshows the owner coreblockPage/controller.tsxshows the deeper list runtimeblockPageEntry/controller.tsxshows the deeper form runtimecommand.delete.tsxshows non-page reuse of the same owner boundary.metadata/index.tsshows the generated bean/type registry surface
Step-by-step runtime path
1. Generic resource routes define the public module surface
Start with:
zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/routes.tsThis file declares three routes:
:resource:resource/create:resource/:id/:formScene?
That already reveals the module’s public role.
It is not a resource-specific module such as Student or Product.
It is a generic module whose runtime identity comes from the route params.
Why the shared tabKey matters
All three routes use the same tabKey(route) shape:
`/rest/resource/${encodeURIComponent(route.params.resource)}`;This means the workspace identity is resource-level rather than row-level.
So the list page, create page, and entry page for one resource remain grouped under one resource-oriented tab boundary.
2. Generated page wrappers enter the normal Zova page-controller path
Read next:
src/.metadata/page/resource.tssrc/.metadata/page/entry.tssrc/.metadata/page/entryCreate.ts
These files are intentionally thin.
They show that each route component is generated as a ZPage* wrapper through:
createZovaComponentPage(ControllerPageResource, ...)createZovaComponentPage(ControllerPageEntry, ...)createZovaComponentPage(ControllerPageEntryCreate, ...)
They also surface the Zod-based params schemas for each page.
That means the route record does not jump straight into generic Vue page code.
It enters the normal Zova page-controller runtime with typed params.
3. The list page controller is a shell, not the full list runtime
Read:
zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/resource/controller.tsxThe main jobs of ControllerPageResource are:
- resolve the selector-backed model from
this.$params.resource - autoload the select API schema through
this.$$modelResource.apiSchemasSelect.sdk - read
this.schemaRow?.rest?.blocks - render those blocks through
ZovaJsx
What is most important is what this controller does not own.
It does not directly own:
- filter state
- paged query state
- table refresh policy
- row-data fetching orchestration
- permission-sensitive table metadata refresh
Those deeper concerns usually appear later in the rendered block runtime, especially in basic-page:blockPage.
So the correct reading is:
ControllerPageResourceis the resource list page shell.
It resolves the current resource context, loads the schema surface, and lets metadata choose the block composition.
4. The entry page controller is also a shell
Read:
zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/entry/controller.tsxThe main jobs of ControllerPageEntry are:
- resolve
resource,id, andformScene - derive
formMeta - expose
formProviderfrom the model - expose
formSchemafrom the model - autoload the form API schemas
- read
formSchema?.rest?.blocks - render those blocks through
ZovaJsx
Again, the deeper runtime is intentionally elsewhere.
This controller does not fully own:
- row-data loading for the business entry scene
- submit mutation execution for the full CRUD flow
- page-title/page-dirty synchronization
- the final reusable block-level form orchestration
Those concerns usually appear later in basic-pageentry:blockPageEntry.
So the correct reading is:
ControllerPageEntryis the resource entry page shell.
It interprets the route and the form scene, then lets schema metadata choose the entry blocks.
5. Create-route reuse is handled through virtual subclassing
Read:
zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/entryCreate/controller.tsxThis file is intentionally tiny.
ControllerPageEntryCreate is a virtual subclass of ControllerPageEntry.
That means the create route does not get a second independently maintained runtime.
Instead, it reuses the same entry shell and lets route params plus form-scene logic choose the create branch.
This is an important Zova design clue:
- page identity can differ
- controller logic can still stay shared
6. ModelResource is the owner core behind both shells
Read:
zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.tsThis file is the stable owner boundary behind the route shells.
At the module-runtime level, the most important steps are:
6.1 Selector-backed initialization
ModelResource is decorated with:
@Model({ enableSelector: true })and initializes through:
protected async __init__(resource: string)That means one generic model class can serve many resources safely, because the runtime identity comes from the selector resource name.
6.2 Bootstrap resolves the final resourceApi
Inside initialization, _bootstrap() calls:
$QueryAutoLoad(() => this.$sdk.getBootstrap(this.resource));and then resolves:
this.resourceApi = this.sys.util.parseResourceApi(this.resource, queryBootstrap.data.apiPath);This is one of the most important architectural facts in the whole module.
The model does not assume a hardcoded final API path.
It bootstraps the resource metadata first, then derives the stable runtime API boundary from that metadata.
6.3 The model exposes resource-level metadata surfaces
Still inside initialization, the model creates computed surfaces for:
permissionsformProviderschemaViewschemaCreateschemaUpdateschemaFilterschemaRowschemaPages
That is why the page shells can stay thin.
They do not need to invent schema or permission lookup rules locally, because the owner already exposes them.
6.4 The model owns query, mutation, and form semantics
The same owner also provides:
selectGeneral(...)select(...)queryItem(...)view(id)create()update(id)delete(id)mutationItem(...)getFormSchema(...)getFormApiSchemas(...)getFormMutationSubmit(...)getFormData(...)
A practical reading takeaway is:
routes and page shells give the module its outer shape, but
ModelResourcegives it its resource truth.
7. basic-page:blockPage is the deeper list runtime
Read:
zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsxThis file shows where the richer list behavior really lives.
It resolves the same selector-backed model again, then owns:
- filter state
- paged query state
- combined
query - list loading through
this.$$modelResource.select(this.query) - permission-sensitive table-meta refresh
- page-scene JSX/CEL scope
- block rendering for filter/table/pager composition
This is the strongest runtime proof that rest-resource list pages are layered like this:
rest-resource page shell
└─ schema-driven block composition
└─ basic-page:blockPage
└─ selector-backed ModelResource select(...) ownershipSo if you are debugging list-page behavior such as paging, filtering, or table refresh, stopping at ControllerPageResource is usually too early.
8. basic-pageentry:blockPageEntry is the deeper form runtime
Read:
zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockPageEntry/controller.tsxThis file shows where the richer entry behavior really lives.
It resolves the same selector-backed model again, then owns:
formMetaformProviderformSchemaformData- existing-row view-query loading
- form submission through
getFormMutationSubmit(...) - page-title/page-dirty synchronization
- page-entry JSX/CEL scope
- rendering of the actual form block composition
This is the strongest runtime proof that rest-resource entry pages are layered like this:
rest-resource entry shell
└─ schema-driven block composition
└─ basic-pageentry:blockPageEntry
└─ selector-backed ModelResource form/query/mutation ownershipSo if you are debugging entry-page behavior such as submit, title updates, or row-data loading, stopping at ControllerPageEntry is usually too early.
9. Commands can reuse the same owner boundary
Read:
zova/src/suite/cabloy-basic/modules/basic-commands/src/bean/command.delete.tsxThis file shows an important architectural property of the module.
A command bean can resolve the same selector-backed ModelResource by resource name and call:
modelResource.delete(id)
That means the resource owner is not tied to one page controller.
It is reusable across:
- page shells
- generic blocks
- commands
- business-facing thin facades
This is one reason the owner boundary is so valuable.
The mutation policy remains centralized even when the caller is not a page.
10. Generated metadata is a registry layer, not the main runtime layer
Read last:
zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/.metadata/index.tsThis file is still useful, but use it for a different purpose.
It is best read as the module registry map for:
- model bean registration
- controller bean registration
- page-path and page-name typing
- module scope typing
- generated module augmentation surfaces
It helps answer questions like:
- what is the bean full name?
- which page names and paths are registered?
- where does typed
$paramscome from? - which scope class represents this module?
But it is not the best first file for understanding the behavioral runtime flow.
What rest-resource owns vs what downstream blocks own
This distinction is the single most useful debugging habit for this module.
rest-resource mainly owns
- generic resource route surface
- page-shell controller layer
- resource-level schema/block entry surface
- selector-backed owner resolution
- reusable resource-owner model logic
downstream generic Basic blocks mainly own
- filter/table/pager CRUD list behavior
- richer form-page orchestration
- page-title/page-dirty updates in entry scenes
- more complete list/query/form submit integration
When this distinction is clear, the module becomes much easier to extend without misplacing logic.
The Zova-native explanation
The most accurate Zova-native description is:
rest-resourceis a generic resource page moduleModelResourceis a selector-backed resource ownerControllerPageResourceandControllerPageEntryare schema-driven page shellsbasic-page:blockPageandbasic-pageentry:blockPageEntryare the deeper reusable CRUD block runtimes
An approximate Vue-style translation would be:
- this is not one page component with local fetch hooks
- it is closer to a route-driven controller shell that delegates into reusable model and block runtimes
That translation can help orientation, but the Zova meaning above is the authoritative one.
What to read next after this page
Choose the next page by the question you actually have.
If the next question is which files to read next
Continue with:
If the next question is how to reuse the owner in application code
Continue with:
- Using
ModelResourcein Your Module - Resource Model Cookbook
- Resource Model Best Practices and Anti-Patterns
If the next question is about list-page tables and resource list blocks
Continue with:
If the next question is about entry pages and forms
Continue with:
Final takeaway
The fastest accurate way to understand rest-resource is to stop reading it as only one reusable model file.
Read it as four cooperating layers:
- generic resource routes
- generated page wrappers
- thin schema-driven page shells
- one selector-backed resource owner reused by deeper CRUD block runtimes
Once those layers are clear, the module becomes much easier to debug, extend, and explain without collapsing Zova back into page-local CRUD code.