<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Uncle Pushui</title>
    <description>The latest articles on DEV Community by Uncle Pushui (@uncle-pushui).</description>
    <link>https://dev.to/uncle-pushui</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1868785%2F82f27483-4416-487e-b0ce-5e6d741637c0.jpg</url>
      <title>DEV Community: Uncle Pushui</title>
      <link>https://dev.to/uncle-pushui</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/uncle-pushui"/>
    <language>en</language>
    <item>
      <title>Is There a CabloyJS Equivalent in the React/Next.js Ecosystem?</title>
      <dc:creator>Uncle Pushui</dc:creator>
      <pubDate>Mon, 02 Nov 2026 03:32:53 +0000</pubDate>
      <link>https://dev.to/uncle-pushui/is-there-a-cabloyjs-equivalent-in-the-reactnextjs-ecosystem-4db1</link>
      <guid>https://dev.to/uncle-pushui/is-there-a-cabloyjs-equivalent-in-the-reactnextjs-ecosystem-4db1</guid>
      <description>&lt;p&gt;In the Node.js fullstack world, CabloyJS has always occupied a rather specific position. It is not a "frontend framework with some API routes on the side," but an enterprise fullstack architecture with &lt;strong&gt;module boundaries, built-in IoC (inversion of control / dependency injection), native RBAC and Workflow, and a metadata contract shared by frontend and backend&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So a familiar question comes up: if a team's stack is React/Next.js, is there something "close enough"?&lt;/p&gt;

&lt;p&gt;The short answer: &lt;strong&gt;there is no exact equivalent to CabloyJS.&lt;/strong&gt; What the React/Next.js ecosystem offers are four &lt;em&gt;partially overlapping&lt;/em&gt; approaches. Each one hits some of CabloyJS's capabilities; none covers all of them.&lt;/p&gt;

&lt;p&gt;To see this clearly, we first need to break CabloyJS apart.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Split CabloyJS into five comparable capability layers
&lt;/h2&gt;

&lt;p&gt;Calling something a "fullstack framework" makes comparison meaningless. CabloyJS's value actually lives on five independent layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Modularity and scaffolding&lt;/strong&gt; — business capabilities are organized as modules that can be installed, composed, and removed, with a CLI that generates the standard structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend IoC/DI&lt;/strong&gt; — services, controllers, and beans collaborate through dependency injection instead of layered imports and manual wiring.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend-backend integration&lt;/strong&gt; — one framework constrains both sides, so routing, permissions, validation, and error handling follow the same conventions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RBAC and Workflow&lt;/strong&gt; — the permission model and workflow engine are built into the framework, not "install a library and wire it up."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metadata-driven design&lt;/strong&gt; — data structures, forms, lists, and permission points are described by metadata, and both sides start from the same definition.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now hold these five layers against the candidates.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Why Next.js itself is not the counterpart
&lt;/h2&gt;

&lt;p&gt;Next.js defines itself as a &lt;strong&gt;React meta-framework&lt;/strong&gt;. It solves rendering modes (SSR/CSR/RSC), routing, builds, deployment, and edge runtimes — extending the frontend all the way into the server.&lt;/p&gt;

&lt;p&gt;Its Route Handlers and Server Actions are enough to write endpoints, but they &lt;strong&gt;do not provide enterprise backend architecture constraints&lt;/strong&gt;: no dependency injection container, no module lifecycle, no built-in permission model, and no workflow engine. In Next.js, all of that belongs to the "choose your own stack and assemble it yourself" category.&lt;/p&gt;

&lt;p&gt;So the question should not be "is there a CabloyJS replacement inside Next.js," but rather: &lt;strong&gt;within the React/Next.js ecosystem, which combination fills in those CabloyJS layers?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Path one: fullstack conventions — RedwoodJS and Blitz.js
&lt;/h2&gt;

&lt;p&gt;If what appeals to you is "one framework owns both sides and tells you, with strong conventions, where business logic belongs," these two frameworks come closest.&lt;/p&gt;

&lt;h3&gt;
  
  
  RedwoodJS
&lt;/h3&gt;

&lt;p&gt;RedwoodJS is one of the most opinionated fullstack frameworks in the React ecosystem. In its classic form, it puts the React frontend, GraphQL API, Prisma data layer, and directory conventions into a single monorepo.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scaffolding&lt;/strong&gt;: &lt;code&gt;generate&lt;/code&gt; commands produce pages, Cells, Services, and GraphQL SDL in one pass, turning "where should this capability live" into a fixed answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear boundaries&lt;/strong&gt;: the frontend handles presentation and interaction, data access is confined to the service layer, and server-side validation and tests have a defined home.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One caveat&lt;/strong&gt;: Redwood has spent the past couple of years evolving toward RedwoodSDK, with changes to both its technical foundation and deployment model. When choosing it, confirm which generation you are actually adopting rather than relying on older docs describing the GraphQL + Prisma setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Blitz.js
&lt;/h3&gt;

&lt;p&gt;Blitz.js is built directly on top of Next.js, with the explicit goal of erasing the frontend-backend boundary.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero-API data layer&lt;/strong&gt;: the frontend can &lt;code&gt;import&lt;/code&gt; backend queries and mutations directly; Blitz compiles them into RPC endpoints and client calls at build time. This removes the hand-written API glue layer and is where it most resembles CabloyJS's "one integrated stack."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in auth and sessions&lt;/strong&gt;: authentication and session management work out of the box, with ready-made hooks for authorization checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boundary&lt;/strong&gt;: it provides authentication and access-control infrastructure, not a full RBAC permission-point system or workflow engine. Organization-level permission models still need to be modeled by you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Who it fits&lt;/strong&gt;: teams that want to stay entirely inside the Next.js ecosystem while getting "write the backend, call it from the frontend, with login state included." For that need, Blitz.js is the first choice. If you are not tied to Next.js but want an opinionated fullstack framework in React, look at RedwoodJS.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Path two: admin panels and metadata-driven design — Refine
&lt;/h2&gt;

&lt;p&gt;CabloyJS is often used to build complex enterprise admin systems quickly. If your core need is &lt;strong&gt;permissions, auto-generated CRUD, and direct access to a database or existing API&lt;/strong&gt;, you do not need to replace Next.js — just bring in Refine.&lt;/p&gt;

&lt;p&gt;Refine is an open-source meta-framework for React admin panels that runs seamlessly on Next.js, Remix, or Vite.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Provider-based metadata thinking&lt;/strong&gt;: &lt;code&gt;dataProvider&lt;/code&gt; connects a data source (REST, GraphQL, Supabase, Prisma, etc.), &lt;code&gt;authProvider&lt;/code&gt; handles login state, and &lt;code&gt;accessControlProvider&lt;/code&gt; carries ACL/RBAC decisions. You define &lt;em&gt;how to fetch&lt;/em&gt; and &lt;em&gt;how to authorize&lt;/em&gt;; Refine wires up CRUD pages, forms, lists, and i18n.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI-agnostic&lt;/strong&gt;: it does not bind to a component library, so you keep Next.js's navigation and rendering modes without being locked in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boundary&lt;/strong&gt;: Refine is a frontend-side meta-framework and &lt;strong&gt;does not provide a backend runtime&lt;/strong&gt;. Authorization must ultimately be enforced by your API or database, and workflows are out of its scope entirely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Who it fits&lt;/strong&gt;: teams that already have a backend (or plan to use Supabase or their own API) and whose main pain is repeated admin-panel and permission work.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Path three: backend IoC — NestJS + Next.js
&lt;/h2&gt;

&lt;p&gt;CabloyJS's backend offers Spring-like dependency injection. If your team values backend architectural rigor more than the convenience of a single all-in-one framework, the common answer is a &lt;strong&gt;combo plus a monorepo&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manage the repo with &lt;strong&gt;Nx&lt;/strong&gt; or &lt;strong&gt;Turborepo&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;Let &lt;strong&gt;Next.js&lt;/strong&gt; handle SSR, SEO, and the BFF layer;&lt;/li&gt;
&lt;li&gt;Let &lt;strong&gt;NestJS&lt;/strong&gt; own the real enterprise business backend.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;NestJS is the most mature IoC/DI backend framework in Node.js today: Module, Provider, Guard, Interceptor, and Pipe form a complete set of architectural constraints, with clear module boundaries and test isolation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The cost&lt;/strong&gt;: frontend and backend are no longer governed by one framework, so routing, permissions, DTO validation, and error codes must be explicitly aligned across two systems. In other words, the consistency CabloyJS gets from "one shared metadata contract" has to be recovered here through engineering conventions and shared packages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Who it fits&lt;/strong&gt;: large commercial projects that need to evolve for years and cannot compromise on backend architecture. This combination is not weaker than CabloyJS in rigor or modularity — but it is not "batteries included," it is "architecture included."&lt;/p&gt;

&lt;h2&gt;
  
  
  6. How the four options compare
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;CabloyJS capability&lt;/th&gt;
&lt;th&gt;React/Next.js option&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Integration and automation&lt;/td&gt;
&lt;td&gt;Modules, CLI generation, unified frontend/backend&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Blitz.js&lt;/strong&gt; / &lt;strong&gt;RedwoodJS&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Choose these to skip auth and API glue work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fast admin panels and permissions&lt;/td&gt;
&lt;td&gt;Built-in RBAC, Workflow, auto CRUD&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Refine&lt;/strong&gt; (embeddable in Next.js)&lt;/td&gt;
&lt;td&gt;Choose it when the core is admin, forms, and access control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top-tier backend architecture (IoC)&lt;/td&gt;
&lt;td&gt;Dependency injection and modularity via Egg/Vona&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;NestJS&lt;/strong&gt; + Turborepo/Nx&lt;/td&gt;
&lt;td&gt;Choose it for large, long-lived systems where backend rigor comes first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Workflow engine&lt;/td&gt;
&lt;td&gt;Built-in Workflow&lt;/td&gt;
&lt;td&gt;No direct counterpart; build or integrate one&lt;/td&gt;
&lt;td&gt;Evaluate separately for process-heavy systems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shared frontend/backend metadata&lt;/td&gt;
&lt;td&gt;One metadata source drives both sides&lt;/td&gt;
&lt;td&gt;Partial overlap (Refine providers, Redwood conventions)&lt;/td&gt;
&lt;td&gt;No complete equivalent; fill the gap with conventions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  7. Selection advice
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Staying fully inside Next.js&lt;/strong&gt;, valuing "write the backend, use it in the frontend, login included": &lt;strong&gt;Blitz.js&lt;/strong&gt; first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not tied to Next.js but wanting an opinionated React fullstack framework&lt;/strong&gt;: &lt;strong&gt;RedwoodJS&lt;/strong&gt; first — but confirm its current form and deployment model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mainly enterprise admin panels, forms, and permissions&lt;/strong&gt;: &lt;strong&gt;Refine&lt;/strong&gt; has the best return on effort, with no backend replacement required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend rigor first, system expected to live for many years&lt;/strong&gt;: the &lt;strong&gt;NestJS + Next.js&lt;/strong&gt; monorepo is the safer bet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One honest closing note: all four options can reproduce &lt;strong&gt;most of the day-to-day CabloyJS experience&lt;/strong&gt;, but none fully replicates its five-layer structure of modules + IoC + RBAC + Workflow + metadata. The React/Next.js ecosystem wins on freedom of choice and an enormous supply of components and training material; CabloyJS wins by building enterprise constraints in advance and reducing how much architectural freedom a team has to improvise.&lt;/p&gt;

&lt;p&gt;These are not substitutes for each other. They are a trade-off between &lt;strong&gt;free composition&lt;/strong&gt; and &lt;strong&gt;built-in conventions&lt;/strong&gt;. Decide which cost you fear more, and the answer follows.&lt;/p&gt;

</description>
      <category>react</category>
      <category>nextjs</category>
      <category>cabloyjs</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>AI Vibe Coding in Practice, Part 2: Product Decisions and a Revised SMS Suite Plan</title>
      <dc:creator>Uncle Pushui</dc:creator>
      <pubDate>Mon, 02 Nov 2026 01:41:42 +0000</pubDate>
      <link>https://dev.to/uncle-pushui/ai-vibe-coding-in-practice-part-2-product-decisions-and-a-revised-sms-suite-plan-38ni</link>
      <guid>https://dev.to/uncle-pushui/ai-vibe-coding-in-practice-part-2-product-decisions-and-a-revised-sms-suite-plan-38ni</guid>
      <description>&lt;p&gt;In the previous article, I shared my &lt;code&gt;prompt&lt;/code&gt; and Claude Code's &lt;code&gt;development plan&lt;/code&gt; for an SMS suite—shared infrastructure, verification codes, and authentication—for the CabloyJS fullstack framework. At the end of that plan, Claude Code asked me to confirm three groups of product decisions: registration behavior, phone-number scope, and rollout scope.&lt;/p&gt;

&lt;p&gt;I had a question about “automatically register on SMS login”: if users do not enter a username, how should &lt;code&gt;userName&lt;/code&gt; be determined? I sent that question back to Claude Code along with the other two decisions so it could refine the plan. Here is the next part of the conversation for reference.&lt;/p&gt;

&lt;p&gt;Here is the prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Registration: should SMS login automatically register a new user? I am unsure. If registration is automatic, how is &lt;code&gt;userName&lt;/code&gt; determined?&lt;/li&gt;
&lt;li&gt;Phone-number scope: mainland China only initially, or international numbers too? Support international numbers.&lt;/li&gt;
&lt;li&gt;Rollout:

&lt;ul&gt;
&lt;li&gt;Enable SMS login on both Web/Admin.&lt;/li&gt;
&lt;li&gt;Deliver the complete Mock flow in the initial release and integrate real vendors as well:

&lt;ul&gt;
&lt;li&gt;Mainland China: Alibaba Cloud / Tencent Cloud.&lt;/li&gt;
&lt;li&gt;International numbers: please recommend one or two of the most popular services.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the development plan returned by Claude Code:&lt;/p&gt;

&lt;p&gt;These clarifications let us narrow the plan further. First, the username question: automatic registration is possible, but explicit registration fits the current account model more naturally. I recommend separating SMS registration from SMS login for the initial release, without automatic registration by default.&lt;/p&gt;

&lt;p&gt;For international SMS, I recommend Twilio and Vonage. Integrate Twilio initially and keep Vonage as the second choice, rather than requiring both in the first release.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Registration: prefer explicit registration with a user-chosen &lt;code&gt;userName&lt;/code&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Why not generate a username automatically?
&lt;/h3&gt;

&lt;p&gt;After checking the source further, the current &lt;code&gt;user.name&lt;/code&gt; is more than an internal technical identifier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Registration writes it from &lt;code&gt;profile.username&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It is used for user display.&lt;/li&gt;
&lt;li&gt;Existing password login looks up users by this field.&lt;/li&gt;
&lt;li&gt;Profile editing currently allows users to change it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It cannot simply be treated as an automatically generated, immutable internal ID. There is also no separate persisted nickname field for display names.&lt;/p&gt;

&lt;p&gt;Source references:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User profile mapping: &lt;code&gt;vona/src/suite/a-home/modules/home-user/src/service/userAdapter.ts:13&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;User registration: &lt;code&gt;vona/src/suite-vendor/a-vona/modules/a-user/src/bean/bean.user.ts:27&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Password login lookup: &lt;code&gt;vona/src/suite-vendor/a-auth/modules/auth-simple/src/bean/authProvider.simple.ts:58&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Automatic registration could generate a username such as &lt;code&gt;sms_&amp;lt;random identifier&amp;gt;&lt;/code&gt;, but that raises further product questions: can it be changed, what gets displayed, and which name is used if the user later sets a password? Implementing SMS login does not require redesigning the account naming model in the initial release.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Recommended registration and login contracts
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Behavior&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SMS registration&lt;/td&gt;
&lt;td&gt;International phone number, username, SMS code&lt;/td&gt;
&lt;td&gt;Verify the number, create the user and SMS identity mapping, then sign in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMS login&lt;/td&gt;
&lt;td&gt;International phone number, SMS code&lt;/td&gt;
&lt;td&gt;Sign in only through an existing SMS identity; never create a new user&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The initial registration flow does not require a password or email, and must not create fake password credentials.&lt;/p&gt;

&lt;p&gt;Recommended username rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reuse and complete the existing username validation.&lt;/li&gt;
&lt;li&gt;Trim surrounding whitespace and apply consistent case-equivalence rules when checking names.&lt;/li&gt;
&lt;li&gt;Require uniqueness within the current instance.&lt;/li&gt;
&lt;li&gt;Reject reserved system names such as admin.&lt;/li&gt;
&lt;li&gt;Return a field error when a name is taken, rather than silently appending a random suffix.&lt;/li&gt;
&lt;li&gt;Preserve uniqueness when the same phone number or username is registered concurrently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One more detail matters: the current profile editor's “Display name” changes the same &lt;code&gt;user.name&lt;/code&gt;, without the duplicate-name checks used for registration. Registration and renaming should share the necessary username constraints. Otherwise, renaming can introduce conflicts after registration has enforced uniqueness.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Separate phone identity from username
&lt;/h3&gt;

&lt;p&gt;Keep three concepts distinct:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field/concept&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;user.id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Internal user identifier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;user.name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Account name chosen by the user&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMS identity mapping&lt;/td&gt;
&lt;td&gt;Association between a verified, normalized phone number and &lt;code&gt;userId&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Consequently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Renaming a user does not affect phone login.&lt;/li&gt;
&lt;li&gt;A phone number must not become the default username, which could expose the full number wherever the user is displayed.&lt;/li&gt;
&lt;li&gt;A username matching an old account does not trigger automatic association.&lt;/li&gt;
&lt;li&gt;A phone number matching an old account's mobile field does not claim that account either.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The existing authentication chain's automatic registration behavior also needs to be explicitly disabled. The framework can currently create users for both login and register, so passing &lt;code&gt;intention: 'login'&lt;/code&gt; alone does not make an operation login-only.&lt;/p&gt;

&lt;p&gt;For a missing SMS identity, prompt the user to register after verifying ownership of the phone number. The send-code endpoint itself must not disclose whether an account exists.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Phone-number scope: support international numbers and normalize to E.164
&lt;/h2&gt;

&lt;p&gt;Use an explicit international-number contract on both frontend and backend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provide a country/region selector on the frontend.&lt;/li&gt;
&lt;li&gt;Use a mature server-side number parser, such as a libphonenumber-based implementation.&lt;/li&gt;
&lt;li&gt;Normalize numbers to E.164 for storage, comparison, and sending, for example +86138… or +447….&lt;/li&gt;
&lt;li&gt;Use the same normalization logic for sending and verification.&lt;/li&gt;
&lt;li&gt;Do not use a simple regular expression to validate numbers from every country.&lt;/li&gt;
&lt;li&gt;Do not infer the recipient's country from the UI language, browser locale, or IP address.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Routing rules
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Destination&lt;/th&gt;
&lt;th&gt;Default sending channel&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Mainland China&lt;/td&gt;
&lt;td&gt;Alibaba Cloud or Tencent Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hong Kong, Macao, Taiwan, and other international destinations&lt;/td&gt;
&lt;td&gt;Twilio, or Vonage if configured&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explicit development/test configuration&lt;/td&gt;
&lt;td&gt;sms-mock&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Do not route Hong Kong, Macao, or Taiwan through the mainland China SMS channel, or choose a vendor based on page language.&lt;/p&gt;

&lt;p&gt;Supporting international numbers does not mean enabling worldwide sending by default. Configure allowed countries/regions, quotas, and spending limits. Destinations may have different Sender ID, number registration, template, or use-case requirements, so enable and verify them individually.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. International vendors: Twilio and Vonage
&lt;/h2&gt;

&lt;p&gt;Both are mature, widely used international communications API vendors. This is an engineering recommendation, not a claim that they rank first and second in verifiable global market share.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Vendor&lt;/th&gt;
&lt;th&gt;Recommended role&lt;/th&gt;
&lt;th&gt;Integration&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Twilio&lt;/td&gt;
&lt;td&gt;First choice for international SMS in the initial release&lt;/td&gt;
&lt;td&gt;Programmable Messaging&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vonage, formerly Nexmo&lt;/td&gt;
&lt;td&gt;Second choice or a later alternative channel&lt;/td&gt;
&lt;td&gt;SMS API&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  1. Twilio: recommended for the initial release
&lt;/h3&gt;

&lt;p&gt;It fits the proposed architecture because it provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A general-purpose SMS sending API.&lt;/li&gt;
&lt;li&gt;E.164 recipient numbers.&lt;/li&gt;
&lt;li&gt;A sending number or Messaging Service for sender configuration.&lt;/li&gt;
&lt;li&gt;Extensive documentation on destination permissions and country requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Vonage: a second choice
&lt;/h3&gt;

&lt;p&gt;Vonage SMS API provides general-purpose recipient, sender, and message-body parameters. It can support another SMS Provider following the same contract.&lt;/p&gt;

&lt;p&gt;For specific target countries, the final choice should still consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sending identities that can actually be provisioned.&lt;/li&gt;
&lt;li&gt;Delivery rate and latency tests.&lt;/li&gt;
&lt;li&gt;Unit prices and message-segment billing.&lt;/li&gt;
&lt;li&gt;Account entity and local compliance requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Do not default to managed Verify products for this plan
&lt;/h3&gt;

&lt;p&gt;Both Twilio and Vonage offer managed verification products. For this suite, use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Twilio Programmable Messaging.&lt;/li&gt;
&lt;li&gt;Vonage SMS API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The plan does not default to their Verify products because sms-captcha is already intended to manage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code generation.&lt;/li&gt;
&lt;li&gt;Expiry.&lt;/li&gt;
&lt;li&gt;Phone-number and purpose binding.&lt;/li&gt;
&lt;li&gt;Attempt limits.&lt;/li&gt;
&lt;li&gt;Single consumption.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping vendors responsible for transport lets Mock, Alibaba Cloud, Tencent Cloud, and international Providers share the same verification semantics.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The infrastructure must support two vendor sending models
&lt;/h2&gt;

&lt;p&gt;With international vendors included, the unified SMS interface cannot require a vendor template ID for every request. Their APIs differ:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Typical sending model&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alibaba Cloud&lt;/td&gt;
&lt;td&gt;Signature + template ID + named parameters as JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tencent Cloud&lt;/td&gt;
&lt;td&gt;Signature + template ID + ordered parameter array&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Twilio&lt;/td&gt;
&lt;td&gt;Sender/Messaging Service + message body&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vonage&lt;/td&gt;
&lt;td&gt;Sender + message body&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Keep the business call consistent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Proposed interface&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sms-auth:signup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;normalizedPhone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Server-side Scene configuration and Provider adapters handle the differences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alibaba Cloud: convert &lt;code&gt;{ code, minutes }&lt;/code&gt; to template parameter JSON.&lt;/li&gt;
&lt;li&gt;Tencent Cloud: convert parameters to an array in an explicitly configured order.&lt;/li&gt;
&lt;li&gt;Twilio/Vonage: render the body from localized text templates maintained on the server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Parameter order must not depend on object enumeration. Public requests must not freely supply message bodies, vendor templates, or senders.&lt;/p&gt;

&lt;p&gt;Also distinguish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recipient region: determines requirements and available channels.&lt;/li&gt;
&lt;li&gt;Message language: determined by the business language policy.&lt;/li&gt;
&lt;li&gt;Vendor account region: determined by Client configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not combine all three into one region field.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Module structure: retain four core modules and extend real Providers independently
&lt;/h2&gt;

&lt;p&gt;Since the initial scope explicitly includes multiple real vendors, retain the four core modules and create separate vendor modules:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Module&lt;/th&gt;
&lt;th&gt;Initial recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;cabloy-sms&lt;/td&gt;
&lt;td&gt;Required: transport infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sms-captcha&lt;/td&gt;
&lt;td&gt;Required: verification codes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sms-auth&lt;/td&gt;
&lt;td&gt;Required: registration and login&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sms-mock&lt;/td&gt;
&lt;td&gt;Required: development and testing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sms-aliyun&lt;/td&gt;
&lt;td&gt;Required: mainland China Provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sms-tencent&lt;/td&gt;
&lt;td&gt;Required: mainland China Provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sms-twilio&lt;/td&gt;
&lt;td&gt;Recommended for the initial release: international Provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sms-vonage&lt;/td&gt;
&lt;td&gt;Alternative; include when two international channels are needed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All belong to the cabloy-sms suite. Separate modules isolate SDKs, credential types, and vendor changes, so consumers do not have to load every SDK.&lt;/p&gt;

&lt;p&gt;If retaining exactly four modules is essential, real Providers could live in cabloy-sms. With multiple real vendor consumers now confirmed, separate Provider modules are a better fit.&lt;/p&gt;

&lt;p&gt;Default routing selects one vendor for each send. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mainland China defaults to Alibaba Cloud, with Tencent Cloud selectable through configuration.&lt;/li&gt;
&lt;li&gt;International destinations default to Twilio.&lt;/li&gt;
&lt;li&gt;Never send through multiple Providers simultaneously.&lt;/li&gt;
&lt;li&gt;Do not automatically resend through another vendor when a timeout leaves the result uncertain.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6. What enabling both Web/Admin means
&lt;/h2&gt;

&lt;p&gt;Both can offer SMS registration and login while sharing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users.&lt;/li&gt;
&lt;li&gt;SMS identity mappings.&lt;/li&gt;
&lt;li&gt;The Provider configuration mechanism.&lt;/li&gt;
&lt;li&gt;Passport and session handling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Registering through Web or Admin must not create separate users for the same phone number.&lt;/p&gt;

&lt;p&gt;Opening a login entry point and granting business permissions are separate decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SMS registration grants only ordinary registered-user permissions.&lt;/li&gt;
&lt;li&gt;Registering from an Admin page does not grant administrative permissions.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. Updated initial delivery recommendation
&lt;/h2&gt;

&lt;p&gt;Set the initial scope to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Infrastructure + Mock: establish a working unified sending contract.&lt;/li&gt;
&lt;li&gt;Alibaba Cloud, Tencent Cloud, and Twilio Providers: integrate with that contract.&lt;/li&gt;
&lt;li&gt;SMS verification: international numbers, purpose binding, rate limits and cooldowns, single consumption, and Mock autofill.&lt;/li&gt;
&lt;li&gt;Explicit SMS registration and login restricted to existing SMS identities.&lt;/li&gt;
&lt;li&gt;Web/Admin entry points and permission regression checks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For real Providers, distinguish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code integration complete: request mapping, response parsing, and error handling have been verified.&lt;/li&gt;
&lt;li&gt;Real SMS acceptance complete: accounts, signatures/templates/sending identities have been approved, and authorized test numbers have actually received messages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The former cannot replace the latter, and a complete Mock flow cannot replace real vendor acceptance.&lt;/p&gt;

&lt;p&gt;My final recommendation is explicit registration to establish &lt;code&gt;userName&lt;/code&gt;, E.164 normalization for international numbers, and initial integration with Alibaba Cloud, Tencent Cloud, and Twilio, with Vonage as the second international option. This covers the requested initial business scope without also taking on account-name redesign, worldwide channel activation, and automatic failover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.twilio.com/docs/messaging/api/message-resource" rel="noopener noreferrer"&gt;Twilio Messages API: recipient numbers, message body, and sender parameters&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.twilio.com/docs/messaging/guides/sms-geo-permissions" rel="noopener noreferrer"&gt;Twilio international SMS destination permissions and restrictions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.vonage.com/en/api/sms" rel="noopener noreferrer"&gt;Vonage SMS API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://help.aliyun.com/zh/sms/developer-reference/api-dysmsapi-2017-05-25-sendsms" rel="noopener noreferrer"&gt;Alibaba Cloud SendSms API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.tencent.com/document/product/382/55981" rel="noopener noreferrer"&gt;Tencent Cloud SendSms API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No code was changed in this round. Vendor information was checked against search results from official documentation; full-page retrieval was restricted by network access. No commitments were made about availability in specific countries, pricing, or actual delivery rates.&lt;/p&gt;




&lt;p&gt;After this exchange, Claude Code had more concrete recommendations for registration, international numbers, and real vendor integration, along with clearer boundaries for shared Web/Admin identities and permissions. I will leave it here for now to keep the article manageable. If you would like to see the implementation that follows, leave a comment and I will continue putting the material together.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>aicoding</category>
      <category>vibecoding</category>
      <category>sms</category>
    </item>
    <item>
      <title>AI Vibe Coding in Practice: An SMS Suite Prompt and Development Plan</title>
      <dc:creator>Uncle Pushui</dc:creator>
      <pubDate>Thu, 29 Oct 2026 02:55:41 +0000</pubDate>
      <link>https://dev.to/uncle-pushui/ai-vibe-coding-in-practice-an-sms-suite-prompt-and-development-plan-20g4</link>
      <guid>https://dev.to/uncle-pushui/ai-vibe-coding-in-practice-an-sms-suite-prompt-and-development-plan-20g4</guid>
      <description>&lt;p&gt;While developing an SMS suite for the CabloyJS fullstack framework—shared infrastructure, verification codes, and authentication—I found both my &lt;code&gt;prompt&lt;/code&gt; and Claude Code's &lt;code&gt;development plan&lt;/code&gt; representative of a real AI coding workflow. I have put them together here for reference.&lt;/p&gt;

&lt;p&gt;Here is the prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please carefully analyze the following development task and propose a reasonable plan:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Goal: implement shared SMS infrastructure first, then build multiple business capabilities on top of it.

&lt;ul&gt;
&lt;li&gt;Infrastructure: SMS provider / clients / scene.&lt;/li&gt;
&lt;li&gt;Business capabilities:

&lt;ul&gt;
&lt;li&gt;sms captcha: SMS verification codes.&lt;/li&gt;
&lt;li&gt;sms auth: user registration and login.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Suite / module structure:

&lt;ul&gt;
&lt;li&gt;Suite: cabloy-sms.&lt;/li&gt;
&lt;li&gt;Modules:

&lt;ul&gt;
&lt;li&gt;cabloy-sms: shared SMS infrastructure.&lt;/li&gt;
&lt;li&gt;sms-captcha: SMS verification codes.&lt;/li&gt;
&lt;li&gt;sms-auth: user registration and login.&lt;/li&gt;
&lt;li&gt;sms-mock: a Mock SMS Provider that prints verification codes to the console and sends them to the frontend so it can fill the code input automatically.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the development plan returned by Claude Code:&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Overall recommendation
&lt;/h2&gt;

&lt;p&gt;Keep the proposed cabloy-sms suite and its four modules, with these layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cabloy-sms: SMS transport infrastructure, responsible for which provider account and template to use when sending a message.&lt;/li&gt;
&lt;li&gt;sms-captcha: SMS verification, responsible for the recipient, the intended operation, and secure verification.&lt;/li&gt;
&lt;li&gt;sms-auth: phone-based authentication, responsible for which account to sign in to after verification and whether to create a new account.&lt;/li&gt;
&lt;li&gt;sms-mock: an SMS Provider for development and testing. It replaces real transport while retaining the verification and authentication flows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most important design principle is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Infrastructure does not depend on verification codes; verification codes do not depend on user authentication; Mock does not change the real business flow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The current repository is Cabloy Basic. Based on the existing source, reuse Vona's provider/client/scene extension pattern, the a-captcha verification system, and the a-auth authentication chain. There is no need to build another SMS code cache or session system.&lt;/p&gt;

&lt;p&gt;However, a-captcha currently lacks safeguards required for SMS login, including phone-number binding and atomic consumption. It needs a bounded set of backward-compatible extensions.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Suite and module structure
&lt;/h2&gt;

&lt;p&gt;The proposed names follow the current naming rules and can remain unchanged.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Suite&lt;/td&gt;
&lt;td&gt;cabloy-sms&lt;/td&gt;
&lt;td&gt;providerId=cabloy, suiteName=sms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Module&lt;/td&gt;
&lt;td&gt;cabloy-sms&lt;/td&gt;
&lt;td&gt;Shared SMS infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Module&lt;/td&gt;
&lt;td&gt;sms-captcha&lt;/td&gt;
&lt;td&gt;SMS verification codes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Module&lt;/td&gt;
&lt;td&gt;sms-auth&lt;/td&gt;
&lt;td&gt;SMS registration and login&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Module&lt;/td&gt;
&lt;td&gt;sms-mock&lt;/td&gt;
&lt;td&gt;Mock SMS Provider&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A Suite and one of its Modules can share a name. Their full package names are vona-suite-cabloy-sms and vona-module-cabloy-sms, so they remain distinct.&lt;/p&gt;

&lt;p&gt;Place backend modules under:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vona/src/suite/cabloy-sms/modules/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Place corresponding frontend modules that actually contain frontend code under:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;zova/src/suite/cabloy-sms/modules/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;There is no requirement to create four empty frontend counterparts. The initial frontend work mainly belongs to sms-captcha for verification interactions and sms-auth for login forms. Mock autofill does not require a separate frontend Mock system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dependency direction
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;sms-captcha depends on the SMS infrastructure and the existing verification system.&lt;/li&gt;
&lt;li&gt;sms-auth depends on SMS verification and the existing authentication system.&lt;/li&gt;
&lt;li&gt;sms-mock only needs the SMS infrastructure.&lt;/li&gt;
&lt;li&gt;The infrastructure, verification, and authentication modules must not have a hard dependency on sms-mock.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whether Mock participates at runtime should be determined by application composition and server configuration, rather than being enabled unconditionally by business modules.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. cabloy-sms: defining provider / clients / scene
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Provider: implementation of a vendor protocol
&lt;/h3&gt;

&lt;p&gt;A Provider implements a sending protocol, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sms-mock:mock.&lt;/li&gt;
&lt;li&gt;A future Alibaba Cloud SMS Provider.&lt;/li&gt;
&lt;li&gt;A future Tencent Cloud SMS Provider.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Provider is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Converting a unified send request into the vendor's request.&lt;/li&gt;
&lt;li&gt;Calling the SDK or HTTP API.&lt;/li&gt;
&lt;li&gt;Converting the vendor's response into a unified result.&lt;/li&gt;
&lt;li&gt;Hiding vendor errors and sensitive information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does not generate verification codes, decide between registration and login, or look up users.&lt;/p&gt;

&lt;p&gt;Introduce smsProvider and smsScene extension types using the existing decorators, type registration, onion metadata, and CLI templates. There is no need for a separate plugin manager.&lt;/p&gt;

&lt;p&gt;Existing reference:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vona/src/suite-vendor/a-pay/modules/a-pay/src/types/payProvider.ts:6&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Client: named account configuration under a Provider
&lt;/h3&gt;

&lt;p&gt;A Client is named configuration under a Provider. It is not a new business service and does not need its own bean scene.&lt;/p&gt;

&lt;p&gt;For example, one real Provider can have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;default: the default account.&lt;/li&gt;
&lt;li&gt;marketing: another vendor account or dedicated sending configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A Client holds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Credentials.&lt;/li&gt;
&lt;li&gt;Region and endpoint.&lt;/li&gt;
&lt;li&gt;Necessary vendor defaults.&lt;/li&gt;
&lt;li&gt;Settings such as whether it is enabled.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use base + clients under each Provider, retaining the type association between Provider and Client.&lt;/p&gt;

&lt;p&gt;The initial release does not need vendor account tables, a credential management console, or a persistent Client registry. Configuration is sufficient for the current consumers.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Scene: a business sending scenario
&lt;/h3&gt;

&lt;p&gt;A Scene represents a stable business intent for sending, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sms-auth:signin.&lt;/li&gt;
&lt;li&gt;Future order or delivery notifications.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A Scene configures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which Provider and Client to use.&lt;/li&gt;
&lt;li&gt;SMS signature and vendor template.&lt;/li&gt;
&lt;li&gt;Template parameter constraints.&lt;/li&gt;
&lt;li&gt;Any necessary server-side routing rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server-side call could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Proposed interface, not an existing API&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;scene&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sms-auth:signin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;normalizedPhone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The caller supplies business parameters; the infrastructure resolves transport configuration. Public requests must not directly specify the Provider, Client, template ID, signature, or credentials.&lt;/p&gt;

&lt;p&gt;Also distinguish these concepts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SMS Scene&lt;/td&gt;
&lt;td&gt;Selects the transport channel, account, and template&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Captcha Scene&lt;/td&gt;
&lt;td&gt;Defines which business operation the verification code can authorize&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The same business module may declare and associate both, but they are not interchangeable. A third registry for “SMS verification scenarios” is unnecessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Instance isolation and send results
&lt;/h3&gt;

&lt;p&gt;Providers and Scenes must resolve effective configuration for the current instance. Never cache the first request's credentials, phone number, or scene configuration in shared bean fields.&lt;/p&gt;

&lt;p&gt;The existing onion configuration merge includes instance configuration. Reading static beanOptions.options directly is not equivalent:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vona/src/suite-vendor/a-vona/modules/a-onion/src/service/onion_.ts:252&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Keep sending synchronous for the initial release and distinguish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accepted by the vendor.&lt;/li&gt;
&lt;li&gt;Explicitly rejected.&lt;/li&gt;
&lt;li&gt;An uncertain result, such as a timeout.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;“Accepted by the vendor” does not mean “received by the phone.” Defer message queues, delivery receipts, automatic failover, and automatic retries. In particular, do not blindly resend after a timeout.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. sms-captcha: reuse verification infrastructure and add SMS semantics
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Existing capabilities and gaps
&lt;/h3&gt;

&lt;p&gt;a-captcha already provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A challenge ID.&lt;/li&gt;
&lt;li&gt;Provider and Scene.&lt;/li&gt;
&lt;li&gt;Redis storage and TTL.&lt;/li&gt;
&lt;li&gt;Verification DTOs.&lt;/li&gt;
&lt;li&gt;Form validation integration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, its cache currently stores only scene/provider/token/token2, without the recipient phone number. Successful verification uses separate read, compare, and delete operations, allowing multiple concurrent attempts to succeed.&lt;/p&gt;

&lt;p&gt;Reference:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vona/src/suite-vendor/a-captcha/modules/a-captcha/src/bean/bean.captcha.ts:19, :79&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Adding a CaptchaProvider that sends SMS is therefore not enough to complete SMS verification.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Recommended responsibilities
&lt;/h3&gt;

&lt;p&gt;Add generic, optional capabilities to a-captcha:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accept trusted context at creation time.&lt;/li&gt;
&lt;li&gt;Store verification binding information.&lt;/li&gt;
&lt;li&gt;Compare it with the expected binding computed by the server at consumption time.&lt;/li&gt;
&lt;li&gt;Use atomic state transitions to guarantee single consumption.&lt;/li&gt;
&lt;li&gt;Let Provider/Scene declare policies for controlled issuance, refresh, and token exposure.&lt;/li&gt;
&lt;li&gt;Remove plaintext tokens from generic verification logs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;sms-captcha owns the SMS-specific rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Phone-number normalization and format validation.&lt;/li&gt;
&lt;li&gt;Allowed purposes and scene mapping.&lt;/li&gt;
&lt;li&gt;Cryptographically secure code generation.&lt;/li&gt;
&lt;li&gt;Binding to the phone number, purpose, and any necessary subject.&lt;/li&gt;
&lt;li&gt;Sending cooldowns, quotas, and failed verification attempt limits.&lt;/li&gt;
&lt;li&gt;Calling the SMS infrastructure.&lt;/li&gt;
&lt;li&gt;SMS-specific form interactions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This reuses the OTP system while keeping phone-number rules out of the generic verification module.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Recommended verification rules
&lt;/h3&gt;

&lt;p&gt;For the initial release:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Code&lt;/td&gt;
&lt;td&gt;Six digits, handled as a string and generated securely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validity&lt;/td&gt;
&lt;td&gt;Five minutes, configurable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resend cooldown&lt;/td&gt;
&lt;td&gt;60 seconds, configurable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failed attempts&lt;/td&gt;
&lt;td&gt;An explicit limit, such as five&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expiry&lt;/td&gt;
&lt;td&gt;Lookups and incorrect attempts never extend the original expiry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resend&lt;/td&gt;
&lt;td&gt;A new challenge replaces the old one; the old proof becomes invalid&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consumption&lt;/td&gt;
&lt;td&gt;A correct code can succeed at most once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Binding&lt;/td&gt;
&lt;td&gt;Current instance + normalized phone number + purpose; add subject binding as required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Abuse prevention&lt;/td&gt;
&lt;td&gt;Phone cooldowns/quotas + IP rate limits + verification attempt limits&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The phone number cannot be just a send parameter. A code sent to phone A must never authorize login for phone B, even when the code itself is correct.&lt;/p&gt;

&lt;p&gt;The initial release can continue using the existing Redis storage for short-lived secrets, without building another cache just for hashed storage. It must have a short TTL, access isolation, no plaintext logs, and no persistent verification-code history. If hashed storage becomes a requirement, explicitly separate stored verification material from the value returned for Mock autofill; the current interface does not inherently support that distinction.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Prevent bypasses through generic endpoints
&lt;/h3&gt;

&lt;p&gt;The existing generic captcha creation, refresh, and immediate-verification endpoints are public:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vona/src/suite-vendor/a-captcha/modules/a-captcha/src/controller/captcha.ts:15&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Security rules must therefore live at the shared service boundary, rather than only in a new SMS Controller.&lt;/p&gt;

&lt;p&gt;For the initial release:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generic create(scene) must not create an SMS challenge without trusted context.&lt;/li&gt;
&lt;li&gt;Generic refresh(id, scene) must not resend while bypassing the SMS cooldown.&lt;/li&gt;
&lt;li&gt;Refreshing an expired or missing ID must not fall back to unrestricted sending.&lt;/li&gt;
&lt;li&gt;SMS scenes should initially disable verifyImmediate → token2, avoiding another replayable proof.&lt;/li&gt;
&lt;li&gt;SMS verification must fail when the expected phone/purpose binding is absent.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. sms-mock: simulate transport while preserving business validation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Correct runtime behavior
&lt;/h3&gt;

&lt;p&gt;Mock should follow the same chain as real SMS:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;sms-captcha generates a code and establishes its binding.&lt;/li&gt;
&lt;li&gt;The SMS infrastructure resolves the sms-mock Provider.&lt;/li&gt;
&lt;li&gt;Mock makes no external vendor calls.&lt;/li&gt;
&lt;li&gt;It prints the code to the development console with the phone number masked.&lt;/li&gt;
&lt;li&gt;The verification layer returns the code to the frontend only when permitted.&lt;/li&gt;
&lt;li&gt;Submission still enforces expiry, attempt limits, binding, and single consumption.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Never use a fixed universal code or make Mock login return success directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Autofill can reuse the existing response field
&lt;/h3&gt;

&lt;p&gt;The captcha response already supports an optional token, and the current form writes it into the input:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;zova/src/suite/cabloy-basic/modules/basic-captcha/src/component/formFieldCaptcha/controller.tsx:96&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;There is no need for a second verification-response protocol.&lt;/p&gt;

&lt;p&gt;However, showToken currently defaults to enabled in all non-production environments. Real SMS must not inherit that behavior:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vona/src/suite-vendor/a-captcha/modules/a-captcha/src/config/config.ts:3&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Returning an SMS code requires all three conditions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The actual Provider used for this request is sms-mock.&lt;/li&gt;
&lt;li&gt;The environment is non-production.&lt;/li&gt;
&lt;li&gt;Development autofill is explicitly enabled.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Real SMS must never return the code because of the global showToken setting, even in dev/test. Selecting Mock in production must fail at startup or sending time, rather than silently falling back.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The frontend needs SMS-specific interactions
&lt;/h3&gt;

&lt;p&gt;The existing image-captcha component automatically creates a challenge on initialization, refreshes it after a failed submission, and renders the payload as an image. It cannot be used unchanged for paid SMS.&lt;/p&gt;

&lt;p&gt;The SMS component should provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A phone-number input.&lt;/li&gt;
&lt;li&gt;An explicit “Send code” button.&lt;/li&gt;
&lt;li&gt;A cooldown based on server time.&lt;/li&gt;
&lt;li&gt;Autofill after a successful Mock send.&lt;/li&gt;
&lt;li&gt;Clearing the challenge and code when the phone number changes.&lt;/li&gt;
&lt;li&gt;No automatic retry after a send failure.&lt;/li&gt;
&lt;li&gt;No automatic sending on page initialization or ordinary form errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep the code only in the current interaction state. Do not put it in localStorage, SSR HTML, or persistent user state.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. sms-auth: reuse authentication and establish a trusted phone identity
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Recommended product behavior
&lt;/h3&gt;

&lt;p&gt;For the initial release, I recommend:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sign in after successful phone verification; automatically register if that SMS identity does not exist.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One form can handle both registration and login, matching the basic behavior of the existing generic authentication chain. This remains a product decision requiring confirmation. If registration and login must be strictly separate, define how each operation handles existing and missing identities.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Authentication flow
&lt;/h3&gt;

&lt;p&gt;Add a non-OAuth SMS AuthProvider and reuse this chain:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Verify and consume OTP → obtain or create identity and user → existing Passport signin&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The framework already supports provider.verify → issuePassport → passport.signin:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vona/src/suite-vendor/a-auth/modules/a-auth/src/service/auth.ts:27&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Key requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consume the OTP at the Provider's verification boundary, rather than only in a new Controller.&lt;/li&gt;
&lt;li&gt;Keep Provider, Client, and intention under server control.&lt;/li&gt;
&lt;li&gt;Do not merge the entire request into authentication configuration.&lt;/li&gt;
&lt;li&gt;Do not create a separate JWT, Cookie, or session storage system.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Never claim an account automatically through an old mobile field
&lt;/h3&gt;

&lt;p&gt;The existing homeUser.mobile is only an optional string. It does not mean “verified phone number”:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;vona/src/suite/a-home/modules/home-user/src/entity/user.ts:24&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Use the existing authentication identity mapping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SMS identity namespace + normalized phone number → auth mapping → userId
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A trusted SMS mapping exists: sign in to its associated user.&lt;/li&gt;
&lt;li&gt;No mapping exists: register or reject according to the confirmed policy.&lt;/li&gt;
&lt;li&gt;An old account has the same mobile value: never automatically associate, merge, or take over that account.&lt;/li&gt;
&lt;li&gt;A mapping exists but its user is missing: return an error; do not silently recreate the account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Defer phone binding, phone changes, recovery, and account merging. Explicitly reject associate/migrate inside the Provider so generic entry points cannot trigger them.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Handle concurrent first registrations separately
&lt;/h3&gt;

&lt;p&gt;Atomic consumption of one code does not guarantee identity uniqueness: a single phone number may have two independent, valid verification requests.&lt;/p&gt;

&lt;p&gt;Serialize by current instance and SMS identity, then recheck and atomically complete these writes within a transaction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User creation.&lt;/li&gt;
&lt;li&gt;Normal role relationships.&lt;/li&gt;
&lt;li&gt;The auth mapping.&lt;/li&gt;
&lt;li&gt;Any necessary projection into the mobile profile field.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enter the existing signin flow after commit. A transaction alone, or a lock on a row that does not exist, is insufficient to prevent duplicate creation. Do not add business uniqueness constraints spanning tenants.&lt;/p&gt;

&lt;p&gt;Prefer reusing the existing identity mapping without a new identity table. If a persisted field such as mobileVerifiedAt is necessary, confirm the owning module's fileVersion strategy before implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Frontend login integration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;sms-auth provides the SMS login form and business wrapper.&lt;/li&gt;
&lt;li&gt;Keep integration with the existing Home Login page minimal.&lt;/li&gt;
&lt;li&gt;Reuse Passport's post-login handling instead of copying token writes and returnTo logic.&lt;/li&gt;
&lt;li&gt;Web/Admin share authentication identities; a different page or vendor Client must not create duplicate users.&lt;/li&gt;
&lt;li&gt;New users follow an explicit ordinary-user role policy. Successful SMS registration does not grant administrative permissions.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  7. Recommended delivery order and acceptance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Deliverables&lt;/th&gt;
&lt;th&gt;Exit criteria&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Infrastructure + Mock&lt;/td&gt;
&lt;td&gt;Provider/Client/Scene, send facade, Mock&lt;/td&gt;
&lt;td&gt;Ordinary template messages can be sent through Mock; instance configuration is isolated; failure semantics are explicit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. SMS verification&lt;/td&gt;
&lt;td&gt;Required captcha extensions, issuance/resend, abuse prevention, SMS component&lt;/td&gt;
&lt;td&gt;Autofill works; phone/purpose binding and atomic consumption are enforced; generic endpoints cannot bypass them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. SMS registration and login&lt;/td&gt;
&lt;td&gt;SMS AuthProvider, identity creation boundary, login entry&lt;/td&gt;
&lt;td&gt;Registration and subsequent login work; old mobile values are not claimed; concurrent requests do not create duplicate accounts; sessions are reused&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. First real Provider&lt;/td&gt;
&lt;td&gt;Integration with the selected vendor&lt;/td&gt;
&lt;td&gt;Mock and real transport follow the same contract; real SMS never returns or logs codes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Although Mock is fourth in the module list, implement it with the infrastructure so verification and authentication have a runnable consumer chain from the beginning.&lt;/p&gt;

&lt;p&gt;Acceptance need not become an enormous matrix, but it must cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verification fails across phone numbers, purposes, and instances, and for expired or replayed codes.&lt;/li&gt;
&lt;li&gt;At most one concurrent consumption of the same challenge succeeds.&lt;/li&gt;
&lt;li&gt;Two independent valid OTPs used concurrently for first login produce exactly one user and identity mapping.&lt;/li&gt;
&lt;li&gt;Generic captcha and authentication entry points cannot bypass security rules.&lt;/li&gt;
&lt;li&gt;Changing the phone number clears the old proof.&lt;/li&gt;
&lt;li&gt;Real SMS never exposes codes in any environment.&lt;/li&gt;
&lt;li&gt;Mock prints and autofills codes only behind explicit development gates.&lt;/li&gt;
&lt;li&gt;Disabled users cannot sign in, and existing password/GitHub login does not regress.&lt;/li&gt;
&lt;li&gt;Sending timeouts never trigger blind retries.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  8. Initial scope and decisions to confirm
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Keep out of the initial release:&lt;/strong&gt; vendor management screens, sending-history screens, bulk marketing, queues, receipts, automatic retries/failover, phone-number changes, recovery, and account merging.&lt;/p&gt;

&lt;p&gt;Only three groups of product decisions need confirmation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Registration: should SMS login automatically register a new user? Recommended.&lt;/li&gt;
&lt;li&gt;Phone-number scope: mainland China only initially, or international numbers too? Either way, require an explicit calling code and normalize consistently. Never prepend +86 to every input by default.&lt;/li&gt;
&lt;li&gt;Rollout: Web first, or Admin at the same time? Should the initial release deliver only the complete Mock flow and defer real vendors?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The names are already reasonable and need no change. Branding or naming preferences can still be accommodated under the same rules.&lt;/p&gt;

&lt;p&gt;After confirmation, create the infrastructure and Mock using the existing CLI rather than building directories manually, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run vona :create:suite cabloy-sms
npm run vona :create:module cabloy-sms &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--suite&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cabloy-sms
npm run vona :create:module sms-mock &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--suite&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cabloy-sms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then generate the remaining modules in stages, run focused backend tests, and verify critical E2E flows. Backend contract changes should generate frontend consumers first. For frontend reverse dependencies, build the affected Web/Admin flavors before running npm run deps:vona.&lt;/p&gt;

&lt;p&gt;This round only completed analysis and made no file changes. The CLI command implementations were confirmed in source, but attempts to run npm run vona and npm run zova both failed because @cabloy/cli was missing. Node was v24.8.0, below the repository requirement of &amp;gt;=24.11.0. Prepare the runtime and dependencies before implementation. No dependencies were installed, and no builds or tests were run in this round.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing note
&lt;/h2&gt;

&lt;p&gt;Claude Code asked me to confirm three groups of product decisions—registration behavior, phone-number scope, and rollout scope—before developing the plan further. I will leave it here for now to keep the article manageable. If you would like to see what comes next, leave a comment and I will continue putting the material together.&lt;/p&gt;

</description>
      <category>cabloy</category>
      <category>claudecode</category>
      <category>aicoding</category>
      <category>vibecoding</category>
    </item>
    <item>
      <title>Functional vs Object-Oriented: Two Cost Curves for Frontend Architecture</title>
      <dc:creator>Uncle Pushui</dc:creator>
      <pubDate>Thu, 22 Oct 2026 11:52:11 +0000</pubDate>
      <link>https://dev.to/uncle-pushui/functional-vs-object-oriented-two-cost-curves-for-frontend-architecture-5ck4</link>
      <guid>https://dev.to/uncle-pushui/functional-vs-object-oriented-two-cost-curves-for-frontend-architecture-5ck4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Abstract:&lt;/strong&gt; People habitually carve the React/Vue and Zova camps into "functional vs object-oriented." But React's Fiber nodes and Vue's component instances reveal a fact: beneath that so-called functional style, an object system is always hiding. The real dividing line is not syntax — it is who owns state identity, scope, and lifecycle: the closure, or a container-managed bean. This article re-reads the paradigm debate through two cost curves: the functional style pays "architectural entropy" afterwards, the object style pays "ceremony" up front. It then lists three hard requirements only containerization solves cleanly (SSR request scope, interceptable call sites, cross-module contracts), and three costs an object style must honestly face. The final criterion boils down to one sentence: if something needs to be found, replaced, shared, or disposed, make it an object; otherwise, let it be a function.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Tearing down a false dichotomy first
&lt;/h2&gt;

&lt;p&gt;When people discuss the difference between Vue/React and Zova, the most common framing is "functional vs object-oriented." It's a convenient label, but it conflates three distinct things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Syntactic form:&lt;/strong&gt; writing functions or classes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State host:&lt;/strong&gt; closure variables or object properties&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architectural ownership:&lt;/strong&gt; who manages identity, scope, and lifecycle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Take one step into the source code, and the classification "React is functional, Vue is functional, Zova is object-oriented" collapses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;React's "functions" are merely the expression form of rendering.&lt;/strong&gt; Hooks depend on a fixed call order — which means every function component must have a persistent Fiber node behind it holding the hooks linked list. Pure functions need no identity; React components have identity, scheduling phases, and effect-cleanup queues. React hides an object system beneath function syntax.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vue's &lt;code&gt;setup()&lt;/code&gt; is a function, but every component instance is an object.&lt;/strong&gt; The reactive host is a Proxy object, &lt;code&gt;ref&lt;/code&gt; is a box with getter/setter, &lt;code&gt;reactive&lt;/code&gt; is a proxied object graph. Vue never pretended to be purely functional.&lt;/p&gt;

&lt;p&gt;So the real watershed is not "whether to use functions" — it is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Who owns identity, scope, and lifecycle — the closure, or a container-managed bean?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the actual boundary between Zova's default mental model and Vue/React's.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Each model's "default answer"
&lt;/h2&gt;

&lt;p&gt;The same counter, written two ways — the difference looks like mere syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Vue 3 default mental model: explicit reactive primitives&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`=== &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ===`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Zova: a framework-managed reactive object&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Counter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;count2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$computed&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`=== &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ===`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Increment&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference is not that we saved a few &lt;code&gt;.value&lt;/code&gt; keystrokes. In the first snippet, state's host is the &lt;code&gt;setup()&lt;/code&gt; closure, lifecycle hangs off the component instance, and sharing requires hunting for another mechanism. In the second, state's host is a bean instance that &lt;strong&gt;inherently belongs to a container, to a scope&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This single distinction will reveal itself over and over as a system grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Cost curves: pre-paid and post-paid
&lt;/h2&gt;

&lt;p&gt;The real divergence between the two models is &lt;strong&gt;where the cost shows up&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Functional style: architectural entropy, paid after
&lt;/h3&gt;

&lt;p&gt;A single component costs almost nothing extra — write a function, declare a few &lt;code&gt;ref&lt;/code&gt;s, done. But Vue officially ships four mutually orthogonal sharing mechanisms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;props / emits&lt;/li&gt;
&lt;li&gt;composables&lt;/li&gt;
&lt;li&gt;&lt;code&gt;provide / inject&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;store layer (Pinia, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a system grows to dozens of modules and multiple teams, all four mechanisms coexist in one codebase. Which state should use which? There is no enforced answer, only team conventions. Convention density grows linearly with headcount, while execution drift grows exponentially. That is architectural entropy — it never shows up on day one, but it never fails to show up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Object style: ceremony, paid up front
&lt;/h3&gt;

&lt;p&gt;In Zova, every bean must declare its role, scope, and lifecycle. Even a state that only serves a single component's internals has to answer first: "which bean are you, and which container do you live in?" That is the up-front ceremony.&lt;/p&gt;

&lt;p&gt;What it buys in return is the convergence of sharing into one mechanism:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ControllerPage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;$$localCounterState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// ctx container: component-instance scope&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;injectionScope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;host&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;$$hostCounterState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;// hierarchical lookup: parent–child sharing&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;injectionScope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;$$appCounterState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// app container: app / request scope&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;injectionScope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sys&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="nx"&gt;$$sysCounterState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// sys container: system scope&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Component-internal, between-components, app-global, system-global — four sharing scopes, one IoC semantics. &lt;code&gt;provide/inject&lt;/code&gt; stops being an independent subsystem and becomes the natural corollary of &lt;code&gt;host&lt;/code&gt; / &lt;code&gt;skipSelf&lt;/code&gt; injection scopes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So "which is better" is a pseudo-question.&lt;/strong&gt; The right question is: where does your system's complexity primarily land? If it lands on "rendering composition," the functional style's flexibility is pure gain. If it lands on "domain ownership, modularity, long-term evolution," the object style's uniformity pays back.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Three things only containerization does cleanly
&lt;/h2&gt;

&lt;p&gt;Some questions are not "style choices" — they are "does this mechanism exist?" questions.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. SSR request scope
&lt;/h3&gt;

&lt;p&gt;SSR has a hard constraint: state must be isolated across requests; module-level singletons leak user data.&lt;/p&gt;

&lt;p&gt;In the functional model, there is no addressable container called "the request." You only have two choices: module-level singletons (leak) or rebuilding the entire module graph per request (expensive and error-prone). Zova's &lt;code&gt;app&lt;/code&gt; container is natively the request boundary, and the &lt;code&gt;sys&lt;/code&gt; container is natively cross-request — this is not syntax sugar; it is a first-class SSR requirement modeled directly into the architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Interceptable call sites
&lt;/h3&gt;

&lt;p&gt;AOP, permission injection, logging, behavior extension — all of these require "call sites" to have stable identity. Pure function calls have no identity; to intercept you have to wrap functions in layers, and the wrapper layers themselves are anonymous and unaddressable. A bean's method call naturally passes through the container, and the container is naturally the interception point.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Cross-module contracts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ControllerLayout&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a-routertabs.model.tabs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;$$modelTabs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ModelTabs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bean identifiers let consumers depend on a contract name, not the provider's file path. Modules can be reorganized, relocated, have their implementations replaced — and consumers do not move.&lt;/p&gt;

&lt;p&gt;Functional cross-module sharing eventually converges onto some global store key or context key — essentially a string contract too, except no container is managing its registration, resolution, and lifecycle for you. Anyone can reinvent the mechanism; the difference is whether it is part of the architecture or part of the scaffolding.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Honestly listing three costs of the object style
&lt;/h2&gt;

&lt;p&gt;If you only see the benefits, that is marketing, not architecture. The object style carries three real costs:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Decorators + metadata = runtime magic.&lt;/strong&gt; Behind &lt;code&gt;@Use()&lt;/code&gt; is a conspiracy of build-time transforms and runtime resolution. Static readability drops, call stacks go deeper, and debugging means piercing one extra layer of framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Granularity gets forced coarser.&lt;/strong&gt; When "everything should be a bean" becomes a habit, you end up with an input box that still needs a role and a scope — over-modeling. The object style needs discipline to fight its own ceremony.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Structural conflict with React's evolution direction.&lt;/strong&gt; RSC and concurrent rendering push state toward serializable pure data, while a bean is a non-serializable live object graph. Zova can only grow on Vue, not on React — and this is not an aesthetic choice, it is determined by the runtime substrate: Vue's component instance is already an object with a reactive scope, naturally suited to host beans; React's Fiber substrate is heading in the opposite direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Convergence: objects own identity, functions own expression
&lt;/h2&gt;

&lt;p&gt;The interesting thing is that both camps are moving into each other's core territory.&lt;/p&gt;

&lt;p&gt;Zova itself is a hybrid: underneath runs Vue reactivity, and &lt;code&gt;$computed&lt;/code&gt; and &lt;code&gt;$watch&lt;/code&gt; are thoroughly functional APIs. Objects only carry identity, scope, and lifecycle — &lt;strong&gt;not computation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The React ecosystem is also moving toward identity: the signals revival, external state containers like &lt;code&gt;QueryClient&lt;/code&gt;, stores like Zustand that "pull state out of the closure" — all essentially admitting that pure closures cannot carry all state, and that an addressable host is needed.&lt;/p&gt;

&lt;p&gt;From this we get an actionable judgment criterion:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If something needs to be "found, replaced, shared across scopes, disposed with a lifecycle," it is an object; otherwise it should be a function.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Wrapping a pure function in an object is over-design; using a closure to carry something that needs identity is architectural debt.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  7. Conclusion: a debate not on the same abstraction layer
&lt;/h2&gt;

&lt;p&gt;Let us return to the comparison in the title.&lt;/p&gt;

&lt;p&gt;The functional style solves "how to compose rendering." The object style solves "how to organize a long-lived system." The former is a programming paradigm; the latter is an architectural paradigm — &lt;strong&gt;they are not on the same abstraction layer&lt;/strong&gt;. Comparing their elegance directly often leads to "FP is more advanced," but that conclusion is wrong: React's hooks are precisely effectful closures with order constraints, side effects, and Fiber scheduling — they are no closer to pure functions than classes are.&lt;/p&gt;

&lt;p&gt;The questions worth asking are always:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In my system, who does state identity belong to?&lt;/li&gt;
&lt;li&gt;Is the state-sharing mechanism one set or four?&lt;/li&gt;
&lt;li&gt;Is the SSR request boundary modeled?&lt;/li&gt;
&lt;li&gt;When a newcomer takes over an unfamiliar module and needs to change some stateful behavior: do they have to rummage through composables, stores, and provide/inject one by one to find the state's host, or do they just ask "which bean is this?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Zova chooses to pay ceremony up front and reap order later. Whether that choice is right depends not on how elegant the syntax is, but on how long your system plans to live and how big it plans to grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/cabloy/cabloy" rel="noopener noreferrer"&gt;https://github.com/cabloy/cabloy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://cabloy.com" rel="noopener noreferrer"&gt;https://cabloy.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Demo: &lt;a href="https://cabloy.com/demo" rel="noopener noreferrer"&gt;https://cabloy.com/demo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>vue3</category>
      <category>react</category>
      <category>zova</category>
      <category>cabloy</category>
    </item>
    <item>
      <title>CabloyJS’s AI Moment: From a TypeScript Fullstack Framework to AI-Spec-Driven Development</title>
      <dc:creator>Uncle Pushui</dc:creator>
      <pubDate>Fri, 16 Oct 2026 00:52:11 +0000</pubDate>
      <link>https://dev.to/uncle-pushui/cabloyjss-ai-moment-from-a-typescript-fullstack-framework-to-ai-spec-driven-development-5h7o</link>
      <guid>https://dev.to/uncle-pushui/cabloyjss-ai-moment-from-a-typescript-fullstack-framework-to-ai-spec-driven-development-5h7o</guid>
      <description>&lt;p&gt;AI coding assistants have turned "having AI write code" into a mainstream conversation over the past two years. From Cursor's tab completion, to Claude Code's agent mode, to the more radical "Vibe Coding" — describing an idea in natural language and letting AI turn it into a running app.&lt;/p&gt;

&lt;p&gt;But the real difficulty appears when a Vibe Coding project outgrows "hello world" and becomes an actual business system.&lt;/p&gt;

&lt;p&gt;AI "hallucinates." It modifies modules you didn't ask it to touch, assumes interfaces and data structures that don't exist, and turns an entire repository into a mess. This is not about prompts being imprecise enough — it is a deeper problem: &lt;strong&gt;AI lacks explicit, machine-readable global constraints&lt;/strong&gt;. It can only guess from context what the correct boundaries are, and once that guess drifts outside the business scope, the cost of repair grows exponentially.&lt;/p&gt;

&lt;p&gt;The series of pivotal iterations CabloyJS made in the second half of 2026 is a systematic answer to exactly this pain point. No longer satisfied with being a TypeScript fullstack framework that helps developers scaffolding CRUD faster, CabloyJS has been proactively restructuring itself into an &lt;strong&gt;AI Spec-Driven Development (AI-SDD) framework&lt;/strong&gt; purpose-built for AI Vibe Coding.&lt;/p&gt;

&lt;p&gt;This evolution can be organized into three clear threads.&lt;/p&gt;




&lt;h2&gt;
  
  
  Thread One: Turn "having AI write code" into "having AI deliver against a specification"
&lt;/h2&gt;

&lt;p&gt;Most AI coding assistants follow this model: give a description, AI generates a chunk of code based on context. This model works well for local tasks, but inside complex systems that span multiple modules and both frontend and backend, AI faces a fundamental dilemma — it does not know where its boundaries are.&lt;/p&gt;

&lt;p&gt;CabloyJS's answer is a set of built-in Claude Code Skills that splits the development workflow into two strict phases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase One: Establish "specification authority" inside the repository
&lt;/h3&gt;

&lt;p&gt;The first Skill, &lt;code&gt;cabloy-spec-generation&lt;/code&gt;, does not write code. Its job is to &lt;strong&gt;establish explicit specification files inside the repository so that both AI and humans read the same source of truth&lt;/strong&gt;. A typical output is a set of mutually referencing Spec files covering product requirements, technical contracts, delivery plans, acceptance criteria, and architecture decision records.&lt;/p&gt;

&lt;p&gt;The key is not merely "having a pile of documents on disk." It is &lt;strong&gt;separated authority&lt;/strong&gt;: each file owns one kind of fact, and other files refer to it through stable IDs. Product requirements do not prescribe table names; technical contracts do not redefine business rules; delivery plans do not rewrite upstream authority. AI never has to guess "what is the boundary of this module?" — it goes and reads the Spec that is already there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase Two: Let AI execute bounded increments
&lt;/h3&gt;

&lt;p&gt;Only after the specification is in place does the second Skill, &lt;code&gt;cabloy-spec-execution&lt;/code&gt;, start touching code. It does not "look at a prompt and change things randomly." It follows a strict execution chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read specs → confirm boundaries → generate execution dossier → make source changes → run verification → save evidence → update progress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every step has a confirmation gate. AI does not automatically expand the task scope, does not skip verification steps, and does not pretend "it's done" when verification fails. The final deliverable is not just a chunk of code — it is &lt;strong&gt;source changes plus an auditable Evidence record plus updated progress state&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Putting these two phases together, CabloyJS is doing something deliberately simple: &lt;strong&gt;it prevents AI from jumping straight from a vague prompt to writing code. Instead, it inserts a Spec-based middle layer that gives AI clear behavioral boundaries within that layer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;What distinguishes this from traditional "spec-driven development" is that in the traditional model, specs are documents written for humans. In CabloyJS, specs are &lt;strong&gt;machine-readable authority written for both AI and humans&lt;/strong&gt;. AI does not merely "consult" the Spec — its entire execution flow is driven by it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Thread Two: A side effect of AI-SDD — charts are automatically derived from Specs
&lt;/h2&gt;

&lt;p&gt;When every AI operation is constrained by the Specs and updating the same progress file, a natural question emerges: &lt;strong&gt;can we directly derive agile visualization charts from this structured data?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer is yes. CabloyJS's Spec system ships with a small toolchain that automatically derives two charts from the WBS (Work Breakdown Structure) and progress files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gantt&lt;/strong&gt;: shows delivery phases, task order, dependencies, and current status&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Burndown&lt;/strong&gt;: shows the approved scope task counts and remaining items&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These charts are not hand-drawn pictures — they are direct visualizations of the Spec data. When AI finishes a WBS task and updates the progress file, running the generation script once more updates the charts automatically.&lt;/p&gt;

&lt;p&gt;On the surface this looks like a "cool small feature," but in the AI-SDD context its significance is completely different. It means: &lt;strong&gt;while AI is writing business code, it is also automatically maintaining the agile progress visualization of the entire project&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You ask AI to build a CRM system. AI is not just helping you write the customer-management CRUD — it is simultaneously updating task dependencies on the Gantt chart and marking completed work items on the Burndown. The entire development pipeline becomes one continuous flow instead of the split state where "AI writes code, humans manually update Trello/Jira."&lt;/p&gt;

&lt;p&gt;One sober expectation is worth keeping: &lt;strong&gt;auto-generated charts are structural views, not calendar schedules or velocity forecasts&lt;/strong&gt;. They tell you what delivery packages exist, their approximate ordering, and which ones have been verified — but they will not tell you whether everything can ship by next Wednesday. That is intentional. AI should not be held responsible for calendar commitments until it truly has business understanding. Until then, treating the charts as "real-time snapshots of structure and state" is the more pragmatic choice.&lt;/p&gt;




&lt;h2&gt;
  
  
  Thread Three: Dual-Engine Architecture — why Cabloy's Vona + Zova is naturally suited for AI-SDD
&lt;/h2&gt;

&lt;p&gt;The foundation of CabloyJS is two independently evolving engines: &lt;strong&gt;Vona&lt;/strong&gt; for the backend and &lt;strong&gt;Zova&lt;/strong&gt; for the frontend. They are not "a backend folder and a frontend folder inside one big project." Each has its own IoC containers, Bean systems, and CLI. They mesh together through &lt;strong&gt;build artifacts&lt;/strong&gt; and a &lt;strong&gt;bidirectional contract loop&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This architecture gives CabloyJS a tremendous natural advantage in AI-SDD: &lt;strong&gt;backend truth and frontend truth are separate, so AI cannot randomly mess with both sides at once&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend truth: Vona's OpenAPI contract
&lt;/h3&gt;

&lt;p&gt;Vona's Controllers, DTOs, and Zod schemas automatically produce an OpenAPI document through decorator metadata. That OpenAPI document is the single source of truth for frontend SDK generation. When changing a backend interface, the correct order is always:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Change Vona's DTO/Controller/Zod → emit new OpenAPI → regenerate Zova's $api SDK and $apiSchema helper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AI will not "invent" a backend interface on the frontend, because it knows the contract truth lives on the Vona side — the frontend is only a consumer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend truth: Zova's routes, components, and metadata
&lt;/h3&gt;

&lt;p&gt;Conversely, Zova's routes, components, icons, and type definitions are the frontend truth. After building, this metadata flows back into Vona's tooling through a &lt;strong&gt;reverse chain&lt;/strong&gt;, giving the backend tooling perfect IDE type hints. The correct order here is likewise enforced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Change Zova's routes/components → build Zova (emit rest artifacts) → run deps:vona to sync into Vona
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository itself wires a contract-loop-gate into git hooks, which automatically checks whether this chain has been broken at commit time. That means "bidirectional contract" is not a documentation slogan — it is an engineering guardrail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this matters for AI
&lt;/h3&gt;

&lt;p&gt;When AI knows that backend truth lives in Vona and frontend truth lives in Zova, and that syncing them has explicit commands and guardrails, it will not freely add fields on both sides, guess interface names, or copy-paste schema definitions. AI only needs to answer three questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this capability backend or frontend?&lt;/li&gt;
&lt;li&gt;Which side's truth do I need to change?&lt;/li&gt;
&lt;li&gt;Which command do I run to propagate the change to the other side?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three answers are &lt;strong&gt;defined by the framework&lt;/strong&gt;. AI does not have to guess.&lt;/p&gt;




&lt;h2&gt;
  
  
  A new edition split: two MIT baselines
&lt;/h2&gt;

&lt;p&gt;Another important change in 2026 is that CabloyJS's open-source editions have been formally split into two V5+ architecture MIT baselines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cabloy Basic (reference edition)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UI layer&lt;/strong&gt;: DaisyUI + Tailwind CSS, a modern lightweight frontend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in demo&lt;/strong&gt;: A complete e-commerce suite (&lt;code&gt;a-commerce&lt;/code&gt;) including Admin back-office, Web storefront, shopping cart, and payment flows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Positioning&lt;/strong&gt;: Learn the fullstack pattern, build prototypes, or start projects that need a lightweight UI layer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License&lt;/strong&gt;: MIT&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cabloy Start (full-featured edition)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UI layer&lt;/strong&gt;: Vuetify, a UI component library&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Built-in demo&lt;/strong&gt;: Complete system management, department management, menu authorization, and data-scope-based RBAC&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Positioning&lt;/strong&gt;: Starting point for projects that need enterprise-grade permissions, menus, and department structures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;License&lt;/strong&gt;: MIT&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This split itself is an extension of the AI-SDD philosophy: &lt;strong&gt;choose a baseline that fits your business starting point first, then let AI deliver bounded increments within that boundary&lt;/strong&gt; — instead of starting from an empty project and asking AI to guess everything from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  So what is CabloyJS now?
&lt;/h2&gt;

&lt;p&gt;Putting the 2026 changes together:&lt;/p&gt;

&lt;p&gt;It is no longer just a TypeScript fullstack framework that helps you scaffolding frontend and backend faster. It is a working system that makes AI Vibe Coding &lt;strong&gt;traceable, bounded, and verifiable&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;AI-SDD two-phase workflow&lt;/strong&gt; makes AI work under specification authority instead of guessing under vague prompts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-derived charts&lt;/strong&gt; visualize every step of AI progress, so the development pipeline is no longer split in two&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;dual-engine plus contract loop&lt;/strong&gt; architecture gives frontend and backend each their own source of truth, so AI will not mess with both sides simultaneously&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two MIT baselines&lt;/strong&gt; let you start from the right point for your business instead of building everything from zero&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Its ambition is clear: be the best fullstack Vibe Coding framework in the AI era — not by "layering prompt engineering on top," but by using &lt;strong&gt;strict typing, repository-internal specification authority, and engineering guardrails&lt;/strong&gt; to turn the previously uncontrolled "blind AI code generation" into an industrial pipeline that is specification-driven, evidence-backed, and automatically reportable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Start with a small increment
&lt;/h2&gt;

&lt;p&gt;If you want to feel this workflow yourself, begin from the public scaffolding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create cabloy
npm run dev
npm run dev:zova:admin
npm run dev:zova:web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then pick a clearly bounded requirement — say, "add a category filter to products" — and run through the full &lt;code&gt;cabloy-spec-generation&lt;/code&gt; → &lt;code&gt;cabloy-spec-execution&lt;/code&gt; flow. You'll notice that when AI no longer has to guess boundaries, contracts, or the definition of done, its behavior quality jumps up visibly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/ai/ai-spec-driven-development" rel="noopener noreferrer"&gt;AI Spec-Driven Development&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/fullstack/vona-zova-integration" rel="noopener noreferrer"&gt;Inside Cabloy's Dual-Engine Architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/fullstack/contract-loop-playbook" rel="noopener noreferrer"&gt;Contract Loop Playbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/ai/playbook-spec-generation" rel="noopener noreferrer"&gt;Generate a Cabloy Suite Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/ai/playbook-spec-execution" rel="noopener noreferrer"&gt;Execute an Approved Cabloy Specification Increment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/fullstack/tutorials-overview" rel="noopener noreferrer"&gt;Cabloy Fullstack Quick Start Tutorials&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aicoding</category>
      <category>vibecoding</category>
      <category>cabloy</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>NestJS Dynamic Providers vs Cabloy/Vona Provider Scene: Two Ways to Bind One Interface to Many Implementations</title>
      <dc:creator>Uncle Pushui</dc:creator>
      <pubDate>Wed, 14 Oct 2026 16:06:31 +0000</pubDate>
      <link>https://dev.to/uncle-pushui/nestjs-dynamic-providers-vs-cabloyvona-provider-scene-two-ways-to-bind-one-interface-to-many-2gkh</link>
      <guid>https://dev.to/uncle-pushui/nestjs-dynamic-providers-vs-cabloyvona-provider-scene-two-ways-to-bind-one-interface-to-many-2gkh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Both frameworks answer "one interface, many implementations, pick one at runtime". They disagree not on the implementation technique, but on &lt;strong&gt;when the binding happens&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;"One interface, several implementations, choose one by configuration or context" is a question every backend framework has to answer. Payments must switch between Stripe, PayPal, and a mock. File storage must switch between local disk and object storage. Captcha must switch between an image puzzle and an SMS code.&lt;/p&gt;

&lt;p&gt;NestJS answers it with &lt;strong&gt;Dynamic Providers&lt;/strong&gt;; Cabloy/Vona answers it with a &lt;strong&gt;Provider Scene&lt;/strong&gt;. Both work, but the assembly models are fundamentally different — and that difference propagates all the way to runtime switching, module decoupling, and the more fundamental question of what a consumer actually holds a reference to.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Short Conclusion
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;NestJS Dynamic Providers&lt;/th&gt;
&lt;th&gt;Vona Provider Scene&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Binding timing&lt;/td&gt;
&lt;td&gt;Once, during module initialization&lt;/td&gt;
&lt;td&gt;On every resolution, by name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expression&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{ provide: TOKEN, useClass: X }&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@PayProvider()&lt;/code&gt; registers into a scene&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Many implementations per interface&lt;/td&gt;
&lt;td&gt;Aggregated via &lt;code&gt;multi: true&lt;/code&gt;, or one token each&lt;/td&gt;
&lt;td&gt;N registered under one scene, peers by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where implementations live&lt;/td&gt;
&lt;td&gt;Usually in one module's &lt;code&gt;providers&lt;/code&gt; array&lt;/td&gt;
&lt;td&gt;Each implementation is a &lt;strong&gt;separate module&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What the consumer references&lt;/td&gt;
&lt;td&gt;An injected token&lt;/td&gt;
&lt;td&gt;A &lt;strong&gt;name&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime switching&lt;/td&gt;
&lt;td&gt;Effectively fixed after init (&lt;code&gt;overrideProvider&lt;/code&gt; only applies while compiling a test module)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;VonaConfig.onions&lt;/code&gt; + per-request &lt;code&gt;ctx.onionsDynamic&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contribution to dependency structure&lt;/td&gt;
&lt;td&gt;Participates in the assembly graph&lt;/td&gt;
&lt;td&gt;Produces no static dependency edges&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In one sentence: NestJS binds a token to an implementation in the &lt;strong&gt;single assembly phase&lt;/strong&gt;; Vona defers "which implementation" to &lt;strong&gt;every resolution&lt;/strong&gt;, so it never has an assembly order that could be broken.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: One Interface, Many Implementations
&lt;/h2&gt;

&lt;p&gt;First, the shared problem. Business code wants to be written like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The consumer depends only on the abstraction&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;payService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startPayment&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;stripe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should not care how Stripe's SDK, PayPal's OAuth flow, or the mock's fixtures are implemented, and adding a new payment method should not force a change here.&lt;/p&gt;

&lt;p&gt;The divergence appears at one step: how &lt;code&gt;payService&lt;/code&gt; obtains the &lt;code&gt;stripe&lt;/code&gt; implementation object.&lt;/p&gt;




&lt;h2&gt;
  
  
  NestJS's Answer: Bind During Assembly
&lt;/h2&gt;

&lt;p&gt;NestJS expresses this relationship as a provider definition, most commonly &lt;code&gt;useClass&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pay.module.ts&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PAY_PROVIDER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;useClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StripePayProvider&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;PAY_PROVIDER&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PayModule&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When several implementations must coexist, &lt;code&gt;multi: true&lt;/code&gt; aggregates them into an array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Module&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PAY_PROVIDER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;useClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StripePayProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;multi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PAY_PROVIDER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;useClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PaypalPayProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;multi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PAY_PROVIDER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;useClass&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MockPayProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;multi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PayModule&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The consumer injects the token and receives the bound instance or array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;Inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PAY_PROVIDER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;pay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IPayProvider&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;useFactory&lt;/code&gt; can also express "pick one by configuration":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;provide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PAY_PROVIDER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useFactory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ConfigService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PAY_DRIVER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paypal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PaypalPayProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StripePayProvider&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="nx"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ConfigService&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Key Constraint: Binding Happens Once
&lt;/h3&gt;

&lt;p&gt;Whether you use &lt;code&gt;useClass&lt;/code&gt;, &lt;code&gt;useValue&lt;/code&gt;, or &lt;code&gt;useFactory&lt;/code&gt;, that binding logic runs &lt;strong&gt;once, during module assembly&lt;/strong&gt;: when &lt;code&gt;NestFactory.create()&lt;/code&gt; bootstraps the application, the container resolves every module's providers into instances and wires them, and the same instances are reused for the rest of the process lifetime.&lt;/p&gt;

&lt;p&gt;Three practical consequences follow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The set of implementations is fixed at startup.&lt;/strong&gt; You cannot "register" a new payment implementation at runtime — module assembly is not a runtime behaviour, and the length of a &lt;code&gt;multi: true&lt;/code&gt; array is likewise determined during assembly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Changing the selection at runtime requires a detour.&lt;/strong&gt; Reading config inside &lt;code&gt;useFactory&lt;/code&gt; is fine, but it computes its result during assembly; to change it later you must inject &lt;code&gt;ModuleRef&lt;/code&gt;, wrap a mutable factory object, or make the whole provider request-scoped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implementation and module boundaries tend to blur.&lt;/strong&gt; Three payment implementations often end up in the same module's &lt;code&gt;providers&lt;/code&gt; array, so "add a payment method" becomes "edit the existing payment module". Separate modules are possible, but that requires deliberate effort rather than being the framework's default shape.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;overrideProvider(TOKEN).useClass(...)&lt;/code&gt; is available only while compiling a testing module with &lt;code&gt;Test.createTestingModule()&lt;/code&gt;. That is an assembly-time behaviour, not a runtime capability.&lt;/p&gt;




&lt;h2&gt;
  
  
  Vona's Answer: Select at Resolution Time
&lt;/h2&gt;

&lt;p&gt;Vona expresses this relationship as a &lt;strong&gt;scene&lt;/strong&gt;: each business area owns a named provider scene and a matching decorator. For payments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// a-pay/src/lib/payProvider.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PayProvider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBeanDecorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payProvider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Implementations then live in separate modules, siblings of the domain module:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Scene&lt;/th&gt;
&lt;th&gt;Implementations (each its own module)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;&lt;code&gt;authProvider&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;auth-simple&lt;/code&gt;, &lt;code&gt;auth-oauth&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Captcha&lt;/td&gt;
&lt;td&gt;&lt;code&gt;captchaProvider&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;captcha-simple&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pay&lt;/td&gt;
&lt;td&gt;&lt;code&gt;payProvider&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;pay-stripe&lt;/code&gt;, &lt;code&gt;pay-paypal&lt;/code&gt;, &lt;code&gt;pay-mock&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image&lt;/td&gt;
&lt;td&gt;&lt;code&gt;imageProvider&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;image-native&lt;/code&gt;, &lt;code&gt;image-cloudflare&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fileProvider&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;file-native&lt;/code&gt;, &lt;code&gt;file-cloudflare&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each implementation is simply "a bean registered under a scene":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pay-stripe/src/bean/payProvider.stripe.ts&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;PayProvider&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PayProviderStripe&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BeanBase&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;IPayProviderExecute&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;startPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* Stripe SDK */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What the Consumer Holds Is a Name, Not a Reference
&lt;/h3&gt;

&lt;p&gt;The pay facade obtains implementations by &lt;strong&gt;asking the container for a name&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// a-pay/src/bean/bean.payProvider.ts&lt;/span&gt;
&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;N&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;IPayProviderRecord&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;providerName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;beanFullName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;beanFullNameFromOnionName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;providerName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;payProvider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_getBean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;beanFullName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the name translation is a single string replacement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// onionName = 'pay-stripe:stripe'&lt;/span&gt;
&lt;span class="c1"&gt;// → beanFullName = 'pay-stripe.payProvider.stripe'&lt;/span&gt;
&lt;span class="nx"&gt;onionName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.payProvider.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So &lt;code&gt;pay-stripe:stripe&lt;/code&gt; becomes a container key only at the &lt;strong&gt;moment of the call&lt;/strong&gt;, and the container resolves the instance on demand. The consuming module never imports any concrete implementation — it knows a name.&lt;/p&gt;

&lt;h3&gt;
  
  
  Selection Happens at Resolution Time, Not Assembly Time
&lt;/h3&gt;

&lt;p&gt;Which implementation is active is decided by three layers, all read &lt;strong&gt;at resolution time&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Configuration — &lt;code&gt;VonaConfig.onions&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each scene keeps its own enabled entries and options in config, and the framework uses them to decide whether an implementation is usable in the current context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Request scope — &lt;code&gt;ctx.onionsDynamic&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A single request can override the selection, for example to disable one aspect in a test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;performAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;onions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;interceptor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a-ratelimit:rateLimit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;enable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Scene resolver&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For the captcha scene, a scene can select one provider, rotate across several, or vary the choice by user state and business context.&lt;/p&gt;

&lt;p&gt;On top of that, each implementation carries &lt;code&gt;enable&lt;/code&gt; / &lt;code&gt;match&lt;/code&gt; / &lt;code&gt;ignore&lt;/code&gt; metadata, filtered at resolution time by &lt;code&gt;checkOnionOptionsEnabled()&lt;/code&gt; to decide whether it is active in the current runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Unit of Extension Is a Module
&lt;/h3&gt;

&lt;p&gt;Adding a payment method means adding a module (scaffolded for you): one new implementation registered into the &lt;code&gt;payProvider&lt;/code&gt; scene, with no change to existing implementations or to the consumer.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Comparison: How Binding Timing Decides Everything
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Angle&lt;/th&gt;
&lt;th&gt;NestJS assembly-time binding&lt;/th&gt;
&lt;th&gt;Vona resolution-time selection&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;When the token becomes an implementation&lt;/td&gt;
&lt;td&gt;At module initialization&lt;/td&gt;
&lt;td&gt;On every &lt;code&gt;get()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who changes when adding the Nth implementation&lt;/td&gt;
&lt;td&gt;Usually the existing module's providers&lt;/td&gt;
&lt;td&gt;A new independent module&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime switching&lt;/td&gt;
&lt;td&gt;Needs &lt;code&gt;ModuleRef&lt;/code&gt; / request scope / mutable factory&lt;/td&gt;
&lt;td&gt;A config entry or a request-level override&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What the consumer holds&lt;/td&gt;
&lt;td&gt;An already-resolved instance&lt;/td&gt;
&lt;td&gt;A name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can an implementation form a dependency cycle with its consumer&lt;/td&gt;
&lt;td&gt;Possible (recursive resolution during assembly)&lt;/td&gt;
&lt;td&gt;No (resolved by name at runtime)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last row deserves attention. In Vona, the only path from consumer to implementation is &lt;strong&gt;"name → container lookup" at runtime&lt;/strong&gt;: registering the Nth implementation adds &lt;strong&gt;zero edges&lt;/strong&gt; to the dependency structure.&lt;/p&gt;

&lt;p&gt;That is exactly why Vona needs no cycle-breaking mechanism at all: there is no recursive resolution during assembly, and no static edge whose number changes with the number of implementations.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Use Which
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;When N implementations must coexist and rotate within one request path&lt;/strong&gt; (risk-control strategy chains, notification channel rotation): NestJS's &lt;code&gt;multi&lt;/code&gt; aggregation works well; on the Vona side, a scene resolver takes on "rotate / choose by user state" logic of this kind.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When the implementation is long-lived, replaceable infrastructure&lt;/strong&gt; (payment, storage, captcha, auth methods): Vona's Provider Scene fits better, because each implementation is naturally an independent module and switching is a configuration change rather than an edit to an existing module.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When you only need to swap one dependency in a test&lt;/strong&gt;: NestJS's &lt;code&gt;overrideProvider&lt;/code&gt; is more direct; Vona reaches the same end with a per-request &lt;code&gt;onionsDynamic&lt;/code&gt; or a config override.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Neither model is strictly better: NestJS's assembly-time binding makes relationships explicit and statically checkable; Vona's resolution-time selection makes extension and switching looser. What actually matters is whether &lt;strong&gt;your extension point is determined at startup or variable at runtime&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;NestJS Dynamic Providers and Vona Provider Scene both answer "one interface, many implementations", but they land in different places:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;NestJS binds a token to an implementation &lt;strong&gt;during assembly&lt;/strong&gt; — explicit relationships, but the implementation set and binding result are fixed after startup;&lt;/li&gt;
&lt;li&gt;Vona selects an implementation by name &lt;strong&gt;at resolution&lt;/strong&gt; — the consumer holds only a name, and implementations register as peers in independent modules;&lt;/li&gt;
&lt;li&gt;That difference in timing decides runtime switching, module decoupling, and the more fundamental conclusion about whether an implementation can form a dependency cycle with its consumer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Put another way, Vona did not make Dynamic Providers smarter; it moved the binding later. &lt;strong&gt;As long as the binding happens at resolution time, many implementations cost the dependency structure nothing.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  References and Source Code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  NestJS
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.nestjs.com/fundamentals/custom-providers" rel="noopener noreferrer"&gt;NestJS Providers / Custom Providers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.nestjs.com/fundamentals/circular-dependency" rel="noopener noreferrer"&gt;NestJS Circular Dependency Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  CabloyJS / Vona
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/main/vona/src/suite-vendor/a-vona/modules/a-onion/src/types/onion.ts" rel="noopener noreferrer"&gt;a-onion scene types (VonaConfig.onions / VonaContext.onionsDynamic)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/main/vona/src/suite-vendor/a-vona/modules/a-onion/src/bean/bean.onion.ts" rel="noopener noreferrer"&gt;a-onion scene checks and execution&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/main/vona/src/suite-vendor/a-pay/modules/a-pay/src/bean/bean.payProvider.ts" rel="noopener noreferrer"&gt;a-pay facade: resolving a provider by name&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/main/vona/src/suite-vendor/a-pay/modules/a-pay/src/lib/payProvider.ts" rel="noopener noreferrer"&gt;a-pay scene decorator definition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/main/vona/packages-vona/vona-core/src/lib/utils/util.ts" rel="noopener noreferrer"&gt;onionName to bean full name utility&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/main/vona/packages-vona/vona-core/src/lib/bean/beanContainer.ts" rel="noopener noreferrer"&gt;Vona BeanContainer source code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/main/repo-docs/backend/captcha-guide.md" rel="noopener noreferrer"&gt;Captcha scene documentation (selection, rotation, context variation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/main/repo-docs/backend/file-guide.md" rel="noopener noreferrer"&gt;Built-in file providers and their defaults&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/main/repo-docs/backend/bean-scene-authoring.md" rel="noopener noreferrer"&gt;Bean scene authoring guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nestjs</category>
      <category>cabloy</category>
      <category>vona</category>
      <category>dependencyinjection</category>
    </item>
    <item>
      <title>How Cabloy/Vona Avoids NestJS Circular Dependencies: A Source-Code Walkthrough of Lazy Property Injection</title>
      <dc:creator>Uncle Pushui</dc:creator>
      <pubDate>Wed, 14 Oct 2026 02:36:05 +0000</pubDate>
      <link>https://dev.to/uncle-pushui/how-cabloyvona-avoids-nestjs-circular-dependencies-a-source-code-walkthrough-of-lazy-property-4jk5</link>
      <guid>https://dev.to/uncle-pushui/how-cabloyvona-avoids-nestjs-circular-dependencies-a-source-code-walkthrough-of-lazy-property-4jk5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Circular dependency is not about "can it be resolved?" — it is about "when does resolution happen?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;NestJS circular dependencies are a frequently discussed topic: two Services inject each other through their constructors, and the instantiation moment falls into a "chicken-or-egg" deadlock that can only be broken with &lt;code&gt;forwardRef()&lt;/code&gt;. Cabloy/Vona does not have this problem at the architectural level — not because it implements smarter cycle detection, but because it defers dependency resolution to first access.&lt;/p&gt;

&lt;p&gt;This article uses Cabloy/Vona's &lt;code&gt;BeanContainer&lt;/code&gt; and &lt;code&gt;@Use&lt;/code&gt; decorator source code to break down the complete implementation of lazy property injection.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Short Conclusion
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;NestJS Constructor Injection&lt;/th&gt;
&lt;th&gt;Vona Lazy Property Injection&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Injection timing&lt;/td&gt;
&lt;td&gt;Synchronous during instantiation&lt;/td&gt;
&lt;td&gt;Deferred to first property access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Circular dependency&lt;/td&gt;
&lt;td&gt;Requires &lt;code&gt;forwardRef()&lt;/code&gt; + deferred cycle resolution&lt;/td&gt;
&lt;td&gt;Naturally safe, no special handling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Constructor parameters&lt;/td&gt;
&lt;td&gt;Dependencies passed as constructor arguments&lt;/td&gt;
&lt;td&gt;Zero constructor dependencies, only getters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resolution entry&lt;/td&gt;
&lt;td&gt;Container recursively resolves during &lt;code&gt;resolve()&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Getter triggers container cache lookup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caching strategy&lt;/td&gt;
&lt;td&gt;Stored in container after instantiation&lt;/td&gt;
&lt;td&gt;Entered into container cache before &lt;code&gt;__init__&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In one sentence: NestJS is "inject at construction time, resolve eagerly" — cycles deadlock during instantiation. Vona is "register at declaration time, resolve on access" — the instantiation phase never touches dependencies, and circular dependencies are dissolved by timing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Root of NestJS Circular Dependencies
&lt;/h2&gt;

&lt;p&gt;First, let us understand where NestJS's problem lies.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Timing Dilemma of Constructor Injection
&lt;/h3&gt;

&lt;p&gt;NestJS dependency injection happens during the constructor phase. When the container resolves &lt;code&gt;A&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ServiceA&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ServiceB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;  &lt;span class="c1"&gt;// Constructor parameter = dependency&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The container needs to create &lt;code&gt;B&lt;/code&gt; before it can create &lt;code&gt;A&lt;/code&gt;. But if &lt;code&gt;B&lt;/code&gt;'s constructor also depends on &lt;code&gt;A&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ServiceB&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ServiceA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resolution chain becomes: &lt;code&gt;new A()&lt;/code&gt; → needs &lt;code&gt;B&lt;/code&gt; → &lt;code&gt;new B()&lt;/code&gt; → needs &lt;code&gt;A&lt;/code&gt; → &lt;code&gt;A&lt;/code&gt; is not yet constructed → deadlock.&lt;/p&gt;

&lt;h3&gt;
  
  
  forwardRef Is a Workaround, Not an Elimination
&lt;/h3&gt;

&lt;p&gt;NestJS uses &lt;code&gt;forwardRef()&lt;/code&gt; and &lt;code&gt;InjectionToken&lt;/code&gt; to defer the resolution order, letting one side complete instantiation first, then injecting the other side afterward. But this is merely a workaround:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developers must manually identify cycles and annotate with &lt;code&gt;forwardRef&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Over-long cycle chains may still encounter "instance not ready" problems;&lt;/li&gt;
&lt;li&gt;It adds implicit complexity to the code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vona's design chose a different path: it does not give circular dependencies any opportunity to arise in the first place.&lt;/p&gt;




&lt;h2&gt;
  
  
  Vona's Three Lines of Defense
&lt;/h2&gt;

&lt;p&gt;Vona structurally does not have an "instantiation-phase" circular dependency problem. The core difference is that dependency resolution is moved from &lt;strong&gt;instantiation time&lt;/strong&gt; to &lt;strong&gt;first access time&lt;/strong&gt;. Three mechanisms ensure this:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. No Constructor Injection
&lt;/h3&gt;

&lt;p&gt;Vona's Service/Bean always extends &lt;code&gt;BeanBase&lt;/code&gt;, and the constructor accepts no dependencies. After instance creation, the container fills in &lt;code&gt;app&lt;/code&gt; / &lt;code&gt;ctx&lt;/code&gt; references, and dependencies are declared on properties via the &lt;code&gt;@Use(...)&lt;/code&gt; decorator.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Lazy Property Injection (the Key Mechanism)
&lt;/h3&gt;

&lt;p&gt;Dependencies declared with &lt;code&gt;@Use&lt;/code&gt; are not injected at Bean creation time. Instead, a getter is installed using &lt;code&gt;Object.defineProperty&lt;/code&gt;. On first property access, the getter resolves the target Bean from the container and caches the result.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Container Itself Is Runtime Lookup
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;BeanContainer.create&lt;/code&gt; returns a lazy Proxy. &lt;code&gt;this.bean.xxxService&lt;/code&gt;, &lt;code&gt;this.scope('module-x')&lt;/code&gt; all resolve from the container by bean full name at access time, producing no static wiring.&lt;/p&gt;




&lt;h2&gt;
  
  
  Source Code Breakdown: Five Stages
&lt;/h2&gt;

&lt;p&gt;Below, we break down the source code across five stages: declaration → storage → mounting → resolution → caching.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 1: Declaration — &lt;code&gt;@Use&lt;/code&gt; Decorator Does Only Two Things
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// use.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;IDecoratorUseOptions&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;PropertyDecorator&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;MethodDecorator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MetadataKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;descriptor&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;PropertyDescriptor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ① Read property type from TS design:type metadata&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;beanClass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;appMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDesignType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Constructable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// ② Class → bean full name mapping&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;beanFullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beanFullName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;beanFullName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;beanFullName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;appResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBeanFullName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;beanClass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// ③ Register metadata — no instantiation happens here&lt;/span&gt;
    &lt;span class="nx"&gt;appResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addUse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;beanFullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;descriptor&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;It reads the property type from the &lt;code&gt;design:type&lt;/code&gt; metadata generated by TypeScript's &lt;code&gt;emitDecoratorMetadata&lt;/code&gt;, so &lt;code&gt;@Use()&lt;/code&gt; without arguments can still determine the target Bean;&lt;/li&gt;
&lt;li&gt;The class → bean full name (&lt;code&gt;module-name.scene.name&lt;/code&gt;) mapping must exist, otherwise it throws directly;&lt;/li&gt;
&lt;li&gt;It calls &lt;code&gt;appResource.addUse&lt;/code&gt; to register metadata — &lt;strong&gt;no instantiation occurs&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stage 2: Storage — Metadata Map Keyed by Property Name, Inherited Along the Prototype Chain
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// resource.ts&lt;/span&gt;
&lt;span class="nf"&gt;addUse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IDecoratorUseOptionsBase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;registerMappedClassMetadataKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SymbolDecoratorUse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;appMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getOwnMetadataMap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SymbolDecoratorUse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;getUses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;MetadataKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;IDecoratorUseOptionsBase&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;appMetadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SymbolDecoratorUse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Note: getMetadata&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Writing uses &lt;code&gt;getOwnMetadataMap&lt;/code&gt; (attached to the class prototype, keyed by &lt;code&gt;prop&lt;/code&gt;), reading uses &lt;code&gt;getMetadata&lt;/code&gt; (traverses up the prototype chain) — this means &lt;strong&gt;subclasses automatically inherit all &lt;code&gt;@Use&lt;/code&gt; dependencies declared by the parent class&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 3: Mounting — Only Install Getter After Instantiation, No Resolution
&lt;/h3&gt;

&lt;p&gt;The strict ordering in the Bean instance creation flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// beanContainer.ts&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;_createBeanInstance&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;beanInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_prepareBeanInstance&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;          &lt;span class="c1"&gt;// new + attach app/ctx&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SymbolBeanContainerInstances&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;beanInstance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// ① Enter container cache first&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_initBeanInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;beanFullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;beanInstance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// ② Inject getter → __init__&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ordering ensures: even if a Bean accesses a dependency property during &lt;code&gt;__init__&lt;/code&gt;, triggering reverse resolution, the container can directly hit the cache and return the already-registered instance.&lt;/p&gt;

&lt;p&gt;The core implementation of injection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// beanContainer.ts&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;_injectBeanInstancePropLazy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;beanInstance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetBeanFullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nb"&gt;self&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;beanInstance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;enumerable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// Property is enumerable (visible in serialization/spread)&lt;/span&gt;
    &lt;span class="na"&gt;configurable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// Redefinable for HMR&lt;/span&gt;
    &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;beanInstance&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SymbolBeanInstancePropsLazy&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="nx"&gt;beanInstance&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SymbolBeanInstancePropsLazy&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;beanInstance&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SymbolBeanInstancePropsLazy&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// First access: actually resolve, then cache to instance-private slot&lt;/span&gt;
        &lt;span class="nx"&gt;beanInstance&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SymbolBeanInstancePropsLazy&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
          &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_injectBeanInstanceProp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;beanInstance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetBeanFullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// App-level container additionally registers this dependency edge to HMR&lt;/span&gt;
        &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;containerType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
          &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hmr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addBeanInstanceProp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;beanInstance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetBeanFullName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;beanInstance&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SymbolBeanInstancePropsLazy&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three implementation highlights:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The getter closure captures the container that installed it&lt;/strong&gt; (&lt;code&gt;self&lt;/code&gt;) — properties installed by the app container always go through app resolution, while those installed by the ctx (request-level) container go through ctx resolution;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cache slot is an instance-level plain object&lt;/strong&gt; &lt;code&gt;SymbolBeanInstancePropsLazy&lt;/code&gt; — after first access, the property degrades to "pseudo-static" with zero lookup cost thereafter;&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;enumerable: true / configurable: true&lt;/code&gt; combination is deliberate: enumerable ensures consistent instance export behavior, configurable lets HMR tear down and reinstall.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Stage 4: Resolution — Three Injection Scopes
&lt;/h3&gt;

&lt;p&gt;First access triggers &lt;code&gt;_injectBeanInstanceProp&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// beanContainer.ts&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;_injectBeanInstanceProp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;beanInstance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;targetBeanFullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IDecoratorUseOptionsBase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;injectionScope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;injectionScope&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;injectionScope&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;targetInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_getBeanSelectorInner&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;  &lt;span class="c1"&gt;// Application-level singleton&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;injectionScope&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ctx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;targetInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_getBeanSelectorInner&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;           &lt;span class="c1"&gt;// Request-level singleton&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;injectionScope&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;new&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;targetInstance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_newBeanInner&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;                   &lt;span class="c1"&gt;// New instance each time&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;targetInstance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;'app'&lt;/code&gt; / &lt;code&gt;'ctx'&lt;/code&gt; fall into &lt;code&gt;_getBeanSelectorInner&lt;/code&gt;: the container instance cache key is &lt;code&gt;beanFullName&lt;/code&gt; or &lt;code&gt;beanFullName#selector&lt;/code&gt;, creating only if &lt;code&gt;=== undefined&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;'new'&lt;/code&gt; creates a new instance each time, but since the outer getter already caches, &lt;strong&gt;only one resolution happens per property lifecycle&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;The selector parameter is taken from three sources by priority: the decorated property's descriptor getter → &lt;code&gt;useOptions.init&lt;/code&gt; static parameters → &lt;code&gt;useOptions.selector&lt;/code&gt; string expression (evaluation context includes &lt;code&gt;self/app/ctx&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stage 5: Caching — Container Instance Cache
&lt;/h3&gt;

&lt;p&gt;The caching logic of &lt;code&gt;_getBeanSelectorInner&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// beanContainer.ts&lt;/span&gt;
&lt;span class="nx"&gt;_getBeanSelectorInner&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;beanFullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Constructable&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;withSelector&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;beanOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;appResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;beanFullName&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;beanOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beanFullName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;__getSelectorKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;withSelector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SymbolBeanContainerInstances&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_newBeanInner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;withSelector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;SymbolBeanContainerInstances&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cache key generation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;__getSelectorKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;beanFullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;withSelector&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;withSelector&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;beanFullName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isSelectorValid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isNilOrEmptyString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;isSelectorValid&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;beanFullName&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;beanFullName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;#&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Complete Timing of A↔B Circular Dependency
&lt;/h2&gt;

&lt;p&gt;Using two Services A and B that &lt;code&gt;@Use&lt;/code&gt; each other as an example, here is the complete timing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Moment&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Declaration&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@Use&lt;/code&gt; registers A→B, B→A mappings&lt;/td&gt;
&lt;td&gt;No instance interaction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Business triggers &lt;code&gt;_getBean('A')&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;new A()&lt;/code&gt;, install lazy getter, enter container cache&lt;/td&gt;
&lt;td&gt;A fully exists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First access &lt;code&gt;a.serviceB&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Getter executes → container creates B (B's construction and getter installation also do not touch A), enters cache&lt;/td&gt;
&lt;td&gt;B fully exists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B internally accesses &lt;code&gt;b.serviceA&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Getter executes → container cache hit → returns existing A&lt;/td&gt;
&lt;td&gt;Cycle closes, no reentrance&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two invariants guarantee cycle safety:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Getters never execute during the instantiation phase&lt;/strong&gt; (no mutual touching);&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The dependency target is guaranteed to be registered in the container cache at resolution time&lt;/strong&gt; (the "enter cache before init" ordering covers even the aggressive case of accessing dependencies during &lt;code&gt;__init__&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Real Code Example
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Vona's &lt;code&gt;@Use&lt;/code&gt; Usage
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BeanBase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Use&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zova&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Service&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zova-module-a-bean&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BeanRouter&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BeanBase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;$$sysRouter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SysRouter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;$$modelPageData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ModelPageData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: zero constructor parameters; all dependencies are declared on properties via &lt;code&gt;@Use&lt;/code&gt;. Resolution is triggered only when &lt;code&gt;this.$$sysRouter&lt;/code&gt; is accessed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Equivalent Path in Vona Backend Business Code
&lt;/h3&gt;

&lt;p&gt;In the current repository, backend business code more commonly uses &lt;code&gt;this.scope('module-x').service.yyy&lt;/code&gt; and &lt;code&gt;this.bean.xxx&lt;/code&gt; instead of property-level &lt;code&gt;@Use&lt;/code&gt; — these go through the same container's runtime lookup path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// BeanContainer.create returns a Proxy&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VonaApplication&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VonaContext&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;beanContainer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BeanContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Proxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;beanContainer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;symbol&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_getBean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// Resolves on access&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is essentially the "method-call version" of the same lazy resolution mechanism. The two paths differ only in declaration style (decorator metadata vs string lookup); the resolution and caching mechanisms are fully shared.&lt;/p&gt;




&lt;h2&gt;
  
  
  Failure Boundary and HMR
&lt;/h2&gt;

&lt;p&gt;Lazy property injection is not without boundaries to be aware of:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;_removeBean&lt;/code&gt; only destroys the instance in the container cache and calls its &lt;code&gt;__dispose__&lt;/code&gt;; the cached reference in other Bean instances' &lt;code&gt;SymbolBeanInstancePropsLazy&lt;/code&gt; is not automatically cleared — this is exactly why HMR needs &lt;code&gt;addBeanInstanceProp&lt;/code&gt; to register dependency edges for rewiring.&lt;/p&gt;

&lt;p&gt;So in hot-reload scenarios, the framework can use the registered dependency edges to find all properties that reference the replaced Bean, tear down old getters, install new ones, clear cache slots, and complete dependency rewiring.&lt;/p&gt;




&lt;h2&gt;
  
  
  Module-Level Circular Dependencies
&lt;/h2&gt;

&lt;p&gt;Beyond instance-level circular dependencies, Vona also makes design-level provisions at the module level. Per the documentation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Using &lt;code&gt;this.$scope.&amp;lt;fixedModule&amp;gt;&lt;/code&gt;, &lt;code&gt;app.scope(...)&lt;/code&gt;, or &lt;code&gt;this.app.scope(...)&lt;/code&gt; does not by itself require adding the target module to the caller's &lt;code&gt;vonaModule.dependencies&lt;/code&gt;. Lookup does not create a module dependency edge, so lookup alone cannot create a circular dependency.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This means module-level circular dependencies are also avoided by design: cross-module lookups do not produce dependency declarations and do not constitute module dependency edges, so modules cannot form circular dependencies merely by "using each other's services."&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The root cause of NestJS's circular dependency problem lies in its design choice of "constructor injection + eager resolution": dependencies must be resolved at instantiation time, circular references inevitably lead to deadlock, and &lt;code&gt;forwardRef&lt;/code&gt; can only defer the resolution order.&lt;/p&gt;

&lt;p&gt;Vona's lazy property injection mechanism dissolves this problem through three design decisions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Zero constructor dependencies&lt;/strong&gt; — the instantiation phase touches no dependencies;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lazy property getter&lt;/strong&gt; — dependency resolution is deferred to first access;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enter cache before init&lt;/strong&gt; — guarantees the dependency target is always ready at resolution time.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Circular dependencies in Vona require no &lt;code&gt;forwardRef&lt;/code&gt;, no special annotation, no developer identification of cycles — they are naturally safe by timing. This is not smarter cycle detection; it is an architectural choice that gives circular dependencies no opportunity to arise.&lt;/p&gt;




&lt;h2&gt;
  
  
  References and Source Code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  NestJS
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.nestjs.com/fundamentals/circular-dependency" rel="noopener noreferrer"&gt;NestJS Circular Dependency Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nestjs/nest/blob/master/packages/common/index.ts" rel="noopener noreferrer"&gt;NestJS &lt;code&gt;forwardRef&lt;/code&gt; Source Code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  CabloyJS / Vona
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/main/vona/packages-vona/vona-core/src/lib/bean/beanContainer.ts" rel="noopener noreferrer"&gt;Vona BeanContainer Source Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/main/vona/packages-vona/vona-core/src/lib/decorator/class/use.ts" rel="noopener noreferrer"&gt;Vona &lt;code&gt;@Use&lt;/code&gt; Decorator Source Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/main/vona/packages-vona/vona-core/src/lib/core/resource.ts" rel="noopener noreferrer"&gt;Vona AppResource Source Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/backend/foundation" rel="noopener noreferrer"&gt;Vona Foundation Documentation: Module Scope and Dependencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/backend/module-dependencies" rel="noopener noreferrer"&gt;Vona Module Dependencies Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>nestjs</category>
      <category>dependencyinjection</category>
      <category>circulardependency</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Inside Cabloy's Dual-Engine Architecture: How Vona and Zova Work Together</title>
      <dc:creator>Uncle Pushui</dc:creator>
      <pubDate>Sun, 11 Oct 2026 14:43:33 +0000</pubDate>
      <link>https://dev.to/uncle-pushui/inside-cabloys-dual-engine-architecture-how-vona-and-zova-work-together-fn0</link>
      <guid>https://dev.to/uncle-pushui/inside-cabloys-dual-engine-architecture-how-vona-and-zova-work-together-fn0</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Vona provides the backend framework and runtime; Zova provides the frontend framework and application layer. They are not a "backend folder + frontend folder" inside one big package. They are two engines that evolve independently, meshed together by &lt;strong&gt;generated artifacts&lt;/strong&gt;, &lt;strong&gt;runtime integration&lt;/strong&gt;, and &lt;strong&gt;bidirectional type contracts&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first thing people notice when opening the Cabloy repository is usually the two large top-level directories: &lt;code&gt;vona/&lt;/code&gt; and &lt;code&gt;zova/&lt;/code&gt;. Each has its own &lt;code&gt;package.json&lt;/code&gt;, CLI, and workspace configuration, and each even carries its own &lt;code&gt;__CABLOY_BASIC__&lt;/code&gt; edition marker. This is not simply a "backend directory plus frontend directory" — it is the core design of Cabloy's fullstack architecture: &lt;strong&gt;one fullstack system, two engines&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This article walks through that dual-engine architecture end to end, using the Cabloy Basic repository (version 5.1.202, MIT licensed) as the baseline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Big Picture First
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                     ┌──────────────────────────────────────────┐
   Browser  ───────▶ │  Vona (backend runtime, Koa, port 7102)  │
                     │   ├─ SSR Site dispatch (Web/Admin/...)   │
                     │   └─ Renders with Zova-built SSR output  │
                     └───────────▲──────────────────┬───────────┘
       Frontend metadata         │                  │  OpenAPI contracts
       (routes/components/icons) │                  ▼  flow downstream
                     ┌───────────┴──────────────────────────────┐
                     │  Zova (frontend, Vue3 + Vite + TSX)      │
                     │  Standalone SSR dev server (port 9000)   │
                     └──────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key principle: frontend and backend concerns are separated and can evolve independently — but they do not collaborate by hand-copying interfaces or manually copying build files. They collaborate through generated artifacts, runtime integration, and shared conventions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backend technology base: Koa, Knex, Redis, and multiple database drivers (CI covers MySQL and PostgreSQL)&lt;/li&gt;
&lt;li&gt;Frontend technology base: Vue, Vite, the Quasar toolchain, and TanStack libraries&lt;/li&gt;
&lt;li&gt;Edition UI layer: Cabloy Basic uses DaisyUI + Tailwind CSS; Cabloy Start uses Vuetify. Quasar is part of the engineering toolchain, not the edition's UI component library&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Part 1. Vona: The Backend Engine
&lt;/h2&gt;

&lt;p&gt;Vona is the backend half of Cabloy. Its goal is to support SSR, SPA, Web, and Admin experiences within one system while keeping frontend and backend decoupled enough to evolve independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. IoC Containers and the Bean System
&lt;/h3&gt;

&lt;p&gt;Every capability in Vona is organized as a framework-managed &lt;strong&gt;Bean&lt;/strong&gt;. All Beans inherit from &lt;code&gt;BeanBase&lt;/code&gt;, whose working surface already includes &lt;code&gt;app&lt;/code&gt;, &lt;code&gt;ctx&lt;/code&gt;, &lt;code&gt;bean&lt;/code&gt;, &lt;code&gt;scope&lt;/code&gt;, &lt;code&gt;$scope&lt;/code&gt;, &lt;code&gt;$logger&lt;/code&gt;, &lt;code&gt;$loggerChild&lt;/code&gt;, &lt;code&gt;$text&lt;/code&gt;, and more — business code never needs to reassemble these access points by hand.&lt;/p&gt;

&lt;p&gt;There are two container levels:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Container&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;app container&lt;/td&gt;
&lt;td&gt;Global backend Beans (singleton-style)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ctx container&lt;/td&gt;
&lt;td&gt;Request-scoped Beans&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A Bean has three forms of identity, and naming is not a style preference — it directly affects injection, config override paths, and how the CLI organizes generated code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bean identifier&lt;/strong&gt;: &lt;code&gt;{moduleName}.{sceneName}.{beanName}&lt;/code&gt;, e.g. &lt;code&gt;training-student.service.student&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Onion name&lt;/strong&gt;: &lt;code&gt;{moduleName}:{beanName}&lt;/code&gt;, e.g. &lt;code&gt;training-student:student&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bean scene&lt;/strong&gt;: the operational family a Bean belongs to — service, model, entity, dto, controller, startup, queue, broadcast, and so on. The built-in global &lt;code&gt;bean&lt;/code&gt; scene is a deliberate exception with a plain shorthand surface&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are four styles of accessing a Bean:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Access style&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;th&gt;Representative shape&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Dependency injection&lt;/td&gt;
&lt;td&gt;Explicit wiring in a class&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@Use('training-student.service.student') serviceStudent&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependency lookup (the recommended concise default)&lt;/td&gt;
&lt;td&gt;Ordinary module business code&lt;/td&gt;
&lt;td&gt;&lt;code&gt;this.scope.service.student.findOne()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Direct bean access&lt;/td&gt;
&lt;td&gt;Container-level control&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;this.bean._getBean(...)&lt;/code&gt; / &lt;code&gt;this.ctx.bean._getBean(...)&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fresh bean creation&lt;/td&gt;
&lt;td&gt;Flows that must not reuse the default instance&lt;/td&gt;
&lt;td&gt;&lt;code&gt;this.bean._newBean(...)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note that &lt;code&gt;this.bean._getBean(...)&lt;/code&gt; goes through the app-level container, while &lt;code&gt;this.ctx.bean._getBean(...)&lt;/code&gt; goes through the request-scoped container.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Scope: The Module Resource Facade
&lt;/h3&gt;

&lt;p&gt;The reason Vona backend code stays concise instead of degrading into arbitrary-path imports everywhere is &lt;strong&gt;Scope&lt;/strong&gt;. Each module's service, model, entity, config, constant, locale, and error resources all hang behind one structured facade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Resources of the current module&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stockBalance&lt;/span&gt;

&lt;span class="c1"&gt;// A cross-module resource statically known in source&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commerceCatalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sku&lt;/span&gt;

&lt;span class="c1"&gt;// In tests or standalone scripts, where you hold an app reference&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;commerce-catalog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sku&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One important design decision: &lt;strong&gt;Scope lookup does not create a module dependency edge&lt;/strong&gt;. &lt;code&gt;this.$scope.xxx&lt;/code&gt; merely resolves a resource from the module set already composed into the application; it neither installs nor loads the target module, and it cannot cause circular dependencies. Only when you genuinely need availability guarantees, dependency-first ordering, or a minimum compatible version do you declare &lt;code&gt;vonaModule.dependencies&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. AOP and the Structured Request Pipeline
&lt;/h3&gt;

&lt;p&gt;Vona provides AOP across three aspect families — controller, internal, and external — plus a structured request path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;middleware → guard → interceptor → pipe (Zod validation) → filter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;@Core.transaction(...)&lt;/code&gt; and &lt;code&gt;@Core.retryable(...)&lt;/code&gt; decorators you see in everyday development are both applications of this AOP:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@Core.transaction(...)&lt;/code&gt; defaults to &lt;code&gt;REQUIRED&lt;/code&gt; propagation: start a transaction if none exists, otherwise join the current one; never escalate isolation casually. Use &lt;code&gt;REQUIRES_NEW&lt;/code&gt; only when an independently committed boundary is explicitly required&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@Core.retryable(...)&lt;/code&gt; retries only safely replayable transient failures through an explicit &lt;code&gt;errorCodes&lt;/code&gt; allowlist. It retries the downstream AOP suffix and never blindly retries external side effects&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Contracts and the Data Layer: Source of Truth for the Forward Chain
&lt;/h3&gt;

&lt;p&gt;The main backend request-handling chain is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Controller → Service → Model → Entity
             (DTO inference/generation + Zod validation)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Controller, DTO, and Zod schemas — together with decorator metadata such as &lt;code&gt;@Api.field(...)&lt;/code&gt; — ultimately produce the &lt;strong&gt;OpenAPI document&lt;/strong&gt;. That OpenAPI output is the single source of contract truth for frontend SDK generation, which becomes crucial in the contract loop below.&lt;/p&gt;

&lt;p&gt;Vona also ships a broad set of long-term capabilities: CRUD-oriented workflows, multi-tenancy (a tenant is an instance; ordinary resource CRUD is automatically instance-scoped), multi-database/multi-datasource with sharding, two-layer caching (including the cross-Model &lt;code&gt;modelsClear&lt;/code&gt; / &lt;code&gt;modelsClearedBy&lt;/code&gt; dependency graph), transactions, authentication/captcha/RBAC/menus, file and image uploads, mail, queues, workers, schedules, election, broadcast, Redlock, WebSocket, metrics, and telemetry.&lt;/p&gt;

&lt;p&gt;In terms of code organization, the framework core lives in &lt;code&gt;vona/packages-vona/&lt;/code&gt;, the CLI in &lt;code&gt;vona/packages-cli/cli/&lt;/code&gt;, and business code in &lt;code&gt;vona/src/suite/&lt;/code&gt; and &lt;code&gt;vona/src/module/&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 2. Zova: The Frontend Engine
&lt;/h2&gt;

&lt;p&gt;Zova is the frontend half of Cabloy. It combines the strengths of three mature frameworks: &lt;strong&gt;Vue3 reactivity, React-style TSX rendering, and Angular-style IoC&lt;/strong&gt;. When reading Zova code, do not degrade it into "plain Vue plus a pile of utilities" — enter through its controller / bean / IoC architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Three IoC Containers Unify All State Sharing
&lt;/h3&gt;

&lt;p&gt;Many Vue 3 projects mix component-local state, composables, provide/inject, and Pinia depending on where state must be shared. Zova asks a different first question: &lt;strong&gt;which Bean owns this state or behavior, and which container scope should own that Bean?&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Container&lt;/th&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Rough Vue-world equivalent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;sys&lt;/td&gt;
&lt;td&gt;System-wide singleton; survives across SSR requests&lt;/td&gt;
&lt;td&gt;Module-level singletons / global long-lived state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;app&lt;/td&gt;
&lt;td&gt;Application / request level&lt;/td&gt;
&lt;td&gt;App stores, globally provided state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ctx&lt;/td&gt;
&lt;td&gt;Component-instance local&lt;/td&gt;
&lt;td&gt;Component-local state, in-component composables&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The four typical sharing scopes therefore all fold into one model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ControllerPage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;$$localCounterState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                          &lt;span class="c1"&gt;// within a component&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;injectionScope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;host&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="nx"&gt;$$hostCounterState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// between components (hierarchical injection)&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;injectionScope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;  &lt;span class="nx"&gt;$$appCounterState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// app-global&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;injectionScope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sys&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;  &lt;span class="nx"&gt;$$sysCounterState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CounterState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// system-level&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under SSR, the distinction between &lt;code&gt;app&lt;/code&gt; and &lt;code&gt;sys&lt;/code&gt; is especially critical: app-level behavior can bind to a single application instance or a single request, while sys-level behavior is not request-bound and survives across SSR requests.&lt;/p&gt;

&lt;p&gt;All Beans likewise inherit from &lt;code&gt;BeanBase&lt;/code&gt;, whose core members include &lt;code&gt;sys&lt;/code&gt;, &lt;code&gt;app&lt;/code&gt;, &lt;code&gt;ctx&lt;/code&gt;, &lt;code&gt;bean&lt;/code&gt;, &lt;code&gt;scope&lt;/code&gt;, &lt;code&gt;$el&lt;/code&gt;, and &lt;code&gt;$event&lt;/code&gt;; SSR modules extend it with &lt;code&gt;$ssr&lt;/code&gt; and &lt;code&gt;$useMeta&lt;/code&gt;, page-controller bases provide &lt;code&gt;$params&lt;/code&gt; and &lt;code&gt;$query&lt;/code&gt;, and component-controller bases provide &lt;code&gt;$props&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Controller / Page / Component: TSX Rendering
&lt;/h3&gt;

&lt;p&gt;Every Zova page and component has a &lt;strong&gt;controller bean&lt;/strong&gt; as its logic carrier, with rendering described in TSX. The surrounding frontend engineering capabilities include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a-router routing: routes with dynamic &lt;code&gt;params&lt;/code&gt; must explicitly define &lt;code&gt;route.name&lt;/code&gt;; static routes generate canonical URLs via &lt;code&gt;$router.getPagePath(...)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Page meta code generation&lt;/li&gt;
&lt;li&gt;Two-layer tabs navigation, router stack, routed dialog&lt;/li&gt;
&lt;li&gt;Explicit &lt;code&gt;requiresAuth&lt;/code&gt; and &lt;code&gt;ssrProfile&lt;/code&gt; declarations (Web defaults to &lt;code&gt;public&lt;/code&gt;; cookie-backed or protected first paint uses &lt;code&gt;session&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Unified State Management with Models and ModelResource
&lt;/h3&gt;

&lt;p&gt;Async state that drives rendering is owned by Models, not scattered across fetch/cache variables in individual controllers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Establish query state during render with &lt;code&gt;$useStateData(...)&lt;/code&gt;, and read the reactive surface owned by the current query (&lt;code&gt;query.data&lt;/code&gt; or a model-derived projection)&lt;/li&gt;
&lt;li&gt;Resource-style business data is owned by a &lt;strong&gt;ModelResource&lt;/strong&gt;, which uniformly handles queries, caching, and mutations; &lt;code&gt;$apiSchema&lt;/code&gt; or the owning resource's schema directly drives automatic rendering of ZForm and ZTable&lt;/li&gt;
&lt;li&gt;Only low-reuse, one-off page actions that need no shared state or cache ownership may call the generated &lt;code&gt;$api&lt;/code&gt; directly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The purpose of these rules is clear: there is exactly one copy of schema metadata (on the backend); the frontend neither duplicates schemas nor hand-edits generated code.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Multi-Flavor Output: One Codebase, Many SSR Sites
&lt;/h3&gt;

&lt;p&gt;The same Zova business source code can be built into multiple flavors (i.e., SSR Sites). The actual build artifacts in this repository (&lt;code&gt;zova/dist/&lt;/code&gt;) include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ssr-cabloyBasicWeb&lt;/code&gt; / &lt;code&gt;ssr-cabloyBasicAdmin&lt;/code&gt;: the Web and Admin sites of Cabloy Basic&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ssr-cabloyCommerce&lt;/code&gt; / &lt;code&gt;ssr-cabloyCommerceAdmin&lt;/code&gt;: the e-commerce demo sites&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rest-cabloyBasicWeb&lt;/code&gt; and other rest artifacts: frontend metadata and type packages consumed by Vona&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each flavor has its own env files (&lt;code&gt;zova/env/.env.*&lt;/code&gt;), and at build time the SSR bundle and rest output must be produced as a pair and moved together.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 3. How the Two Engines Mesh: Two Channels
&lt;/h2&gt;

&lt;p&gt;Between Vona and Zova there are two channels of an entirely different nature.&lt;/p&gt;

&lt;h3&gt;
  
  
  Channel 1: Vona Integrated SSR (the Runtime Channel)
&lt;/h3&gt;

&lt;p&gt;The same frontend SSR application has two entry points during development. The difference is not "whether it is SSR" but &lt;strong&gt;who receives the browser request and owns the response&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vona integrated SSR:   Browser → Vona(7102) → SSR Site matching → Zova build artifacts → browser hydration
Zova standalone SSR:   Browser → Zova dev server(9000) → frontend SSR (APIs still point to Vona)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;npm run dev&lt;/code&gt; starts Vona integrated SSR: Web entry &lt;code&gt;http://localhost:7102/&lt;/code&gt;, Admin entry &lt;code&gt;http://localhost:7102/admin/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm run dev:zova:web&lt;/code&gt; / &lt;code&gt;npm run dev:zova:admin&lt;/code&gt; start Zova standalone SSR (port 9000 by default) for fast hot-reload iteration on pages, routes, and hydration&lt;/li&gt;
&lt;li&gt;Opening port 9000 directly &lt;strong&gt;cannot replace&lt;/strong&gt; integrated acceptance at 7102: it bypasses Vona's SSR Site dispatch and the built-artifact handoff&lt;/li&gt;
&lt;li&gt;The release/acceptance path is: build the SSR and rest artifacts → sync them into Vona with &lt;code&gt;npm run deps:vona&lt;/code&gt; → run full acceptance from the Vona entry&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that "standalone" only means you can open the Zova development server directly; it does not mean it is an independently deployable SSR application. Adding a mountable site requires a matching flavor, a paired set of SSR/rest artifacts, and an &lt;code&gt;@SsrSite&lt;/code&gt; registration on the Vona side.&lt;/p&gt;

&lt;h3&gt;
  
  
  Channel 2: The Bidirectional Contract Loop (the Type and Codegen Channel)
&lt;/h3&gt;

&lt;p&gt;Type information flows in both directions, and each side has its own source of truth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forward chain (backend → frontend)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Controller / DTO / Zod (contract truth)
        │  Vona emits OpenAPI
        ▼
Zova generates SDK ($api) and schema helpers ($apiSchema)
        │  generated code must never be hand-edited
        ▼
Frontend adds only thin semantic wrappers, reusing the same resource owner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When changing a backend contract, change the backend truth first, then regenerate the frontend consumers — never hand-patch generated files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reverse chain (frontend → backend)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Zova's route, component, and icon metadata is the truth; after building, it flows back into Vona's tooling and type hints. The correct order is to build Zova first, then sync dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build:zova:admin   &lt;span class="c"&gt;# Admin-side changes&lt;/span&gt;
npm run build:zova:web     &lt;span class="c"&gt;# also required when the Web flavor is affected — SSR bundle and rest must move as a pair&lt;/span&gt;
npm run deps:vona          &lt;span class="c"&gt;# then sync the .zova-rest artifacts into Vona&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The contract loop also has two typical exception branches, handled in entirely different ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consumer drift&lt;/strong&gt;: fixed by regenerating; do not hand-edit generated output&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local dependency drift&lt;/strong&gt;: the &lt;code&gt;.zova-rest&lt;/code&gt; artifacts already contain the expected changes, but Vona-side types remain stale — delete &lt;code&gt;vona/node_modules&lt;/code&gt; and reinstall dependencies instead of continuing to debug or hand-patching dependency links&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repository itself also wires a contract-loop-gate into git hooks, which automatically checks whether this chain has been broken at commit time. That means the "bidirectional contract" is not a documentation slogan — it is a workflow enforced by engineering guardrails.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 4. The Shared Organizational Skeleton: Suite / Module / Package
&lt;/h2&gt;

&lt;p&gt;Both engines organize business capability by the same structural boundaries: &lt;strong&gt;suite → module → package&lt;/strong&gt;. This is an architectural boundary, not just directory placement. Frontend and backend suites of the same name correspond one to one:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Suite&lt;/th&gt;
&lt;th&gt;Backend&lt;/th&gt;
&lt;th&gt;Frontend&lt;/th&gt;
&lt;th&gt;What it demonstrates&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;a-commerce&lt;/td&gt;
&lt;td&gt;&lt;code&gt;vona/src/suite/a-commerce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;zova/src/suite/a-commerce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A complete e-commerce Admin, Web site, personal center, and payment flows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;a-training&lt;/td&gt;
&lt;td&gt;&lt;code&gt;vona/src/suite/a-training&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;zova/src/suite/a-training&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Master–detail forms, nested detail records, image and file uploads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;a-demo / a-home&lt;/td&gt;
&lt;td&gt;Mirrored&lt;/td&gt;
&lt;td&gt;Mirrored&lt;/td&gt;
&lt;td&gt;Demo and home pages&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Business consumers are also explicitly split into two kinds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Admin Resource&lt;/strong&gt;: back-office management, reusing the generic Resource owner and resource pages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Web self-service&lt;/strong&gt;: the self-service Web frontend, with its own state, pages, and server-side scope&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When one persisted resource serves both kinds of consumers, the domain and persistence boundary stays single, while the API/DTO contracts, server-side scope, frontend state ownership, and page architecture are separated.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 5. One Diagram to Remember It All
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Zova (frontend truth)                     Vona (backend truth)
──────────────────────                    ──────────────────────
Page/Component/Controller                 Controller → Service → Model → Entity
sys/app/ctx containers + Bean             app/ctx containers + Bean (scene-based)
ModelResource + TanStack Query            DTO + Zod validation + OpenAPI output
flavors: Basic Web/Admin, Commerce        SSR Site registration, multi-tenant instances
route/component/icon metadata  ─reverse──▶  consumed via deps:vona
$api / $apiSchema generated SDK ◀─forward──  OpenAPI contracts
        │                                         │
        └───────── build artifacts: ssr-* / rest-* ┘
               Vona integrated SSR (7102) faces the world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you understand this architecture, many Cabloy working conventions fall into place naturally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why regenerating the frontend SDK — not hand-editing &lt;code&gt;$api&lt;/code&gt; — is the response to backend API changes&lt;/li&gt;
&lt;li&gt;Why frontend route changes must be followed by a Zova build and then &lt;code&gt;deps:vona&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Why debugging fullstack issues requires distinguishing 9000 (frontend iteration) from 7102 (integrated truth)&lt;/li&gt;
&lt;li&gt;Why schema-driven forms (ZForm/ZTable) never need field definitions duplicated on the frontend&lt;/li&gt;
&lt;li&gt;Why business code is organized along suite/module boundaries instead of technical-type directories&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vona and Zova are each complete, independent, independently evolvable frameworks. Cabloy's value is precisely in assembling them — through the SSR runtime channel and the bidirectional contract loop — into one complete fullstack system built for AI Spec-Driven Development.&lt;/p&gt;




&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/backend/foundation" rel="noopener noreferrer"&gt;Backend Foundation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/frontend/foundation" rel="noopener noreferrer"&gt;Frontend Foundation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/frontend/ioc-and-beans" rel="noopener noreferrer"&gt;IoC and Beans&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/fullstack/ssr-entry-modes" rel="noopener noreferrer"&gt;Vona Integrated SSR and Zova Standalone SSR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/fullstack/vona-zova-integration" rel="noopener noreferrer"&gt;Vona + Zova Integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/fullstack/contract-loop-playbook" rel="noopener noreferrer"&gt;Contract Loop Playbook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>fullstack</category>
      <category>ioc</category>
      <category>ssr</category>
      <category>openapi</category>
    </item>
    <item>
      <title>AI Coding in Action: One Prompt for CRUD, SSR &amp; Multitasking</title>
      <dc:creator>Uncle Pushui</dc:creator>
      <pubDate>Sat, 10 Oct 2026 03:09:23 +0000</pubDate>
      <link>https://dev.to/uncle-pushui/ai-coding-in-action-one-prompt-for-crud-ssr-multitasking-1b3l</link>
      <guid>https://dev.to/uncle-pushui/ai-coding-in-action-one-prompt-for-crud-ssr-multitasking-1b3l</guid>
      <description>&lt;p&gt;If you ask AI to build a book management feature, what do you expect to get back?&lt;/p&gt;

&lt;p&gt;A set of APIs and a few source files—or an Admin page you can open in a browser and actually use?&lt;/p&gt;

&lt;p&gt;Let's try it in &lt;strong&gt;CabloyJS&lt;/strong&gt;. We'll give Codex one prompt to develop and test a Book CRUD module. Then we'll start the servers, open the Admin interface, create a book, and keep edit, detail, and create tasks open at the same time.&lt;/p&gt;

&lt;p&gt;You'll see more than how AI writes code. You'll get a feel for the finished feature: &lt;strong&gt;what happens when you refresh the list, how you switch between tasks, and whether an unsubmitted form keeps its input.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start with One Prompt
&lt;/h2&gt;

&lt;p&gt;Open Codex in an existing, configured CabloyJS project and enter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a standalone demo-book module to implement CRUD operations for books.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives Codex two clear goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a standalone &lt;code&gt;demo-book&lt;/code&gt; module.&lt;/li&gt;
&lt;li&gt;Implement CRUD—create, read, update, and delete—for books.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We're using a Cabloy Basic project, with &lt;strong&gt;Vona&lt;/strong&gt; on the backend and &lt;strong&gt;Zova&lt;/strong&gt; on the frontend. The project already has a module structure, development commands, and an Admin interface. Our job is to add a business feature on top of that foundation.&lt;/p&gt;

&lt;p&gt;The prompt doesn't need to specify every controller, data model, form component, or page layout. Codex first reads the project conventions, examines the existing structure and tools, and follows the project's development workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Does AI Actually Do?
&lt;/h3&gt;

&lt;p&gt;For this feature, Codex's work follows four steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Understand the project → Run the CRUD generator → Refine fields and integration → Run tests and checks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It examines the project and generator commands, then uses Vona's CRUD generator to create the module. Next, it refines the book fields, validation, menu integration, and supporting code so the resource becomes available in the existing Admin interface.&lt;/p&gt;

&lt;p&gt;The resulting book form has three fields:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Title&lt;/td&gt;
&lt;td&gt;Book title&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Author&lt;/td&gt;
&lt;td&gt;Book author&lt;/td&gt;
&lt;td&gt;Required&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Description&lt;/td&gt;
&lt;td&gt;Book description&lt;/td&gt;
&lt;td&gt;Optional&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One useful part of the development experience is immediately apparent: &lt;strong&gt;once the business resource is defined, the list, detail view, and forms can reuse CabloyJS's existing schema-driven CRUD pages. There is no need to build a new management interface from scratch.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI adds the business module; the framework supplies the reusable capabilities. Together, they let you focus on the fields and operations a book needs rather than rebuilding page structures and basic interactions for every resource.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Development Includes Tests
&lt;/h2&gt;

&lt;p&gt;After creating the module, Codex continues with tests, TypeScript checks, linting, and formatting checks.&lt;/p&gt;

&lt;p&gt;This development task took &lt;strong&gt;13 minutes and 57 seconds&lt;/strong&gt;. Codex's completion summary reports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All five Book module tests passed.&lt;/li&gt;
&lt;li&gt;The backend test suite had 269 passed, ten skipped, and zero failed.&lt;/li&gt;
&lt;li&gt;TypeScript, lint, and formatting checks passed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhx5mlf58mpfcmk1qasx7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhx5mlf58mpfcmk1qasx7.jpg" alt="Codex's completion summary, including task duration, implemented capabilities, and test results" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The implemented capabilities include create, paginated listing, detail viewing, update, delete, and transactional bulk deletion.&lt;/p&gt;

&lt;p&gt;At this point, a single business request has led to a module implementation and automated checks. Now let's start the servers and see what the feature feels like in the interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Two Commands to Open Book Management
&lt;/h2&gt;

&lt;p&gt;In the project root, open the first terminal and start the backend development server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open a second terminal and start the Admin frontend development server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev:zova:admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the servers are ready, open the Admin interface and select &lt;strong&gt;Book&lt;/strong&gt; from the menu on the left.&lt;/p&gt;

&lt;p&gt;The book list is already part of the existing Admin layout: the management menu on the left, search filters and records in the main area, and page navigation above. Creating, searching, and working with records all happen in the same interface.&lt;/p&gt;

&lt;p&gt;That's the first tangible result of adding the module: &lt;strong&gt;you haven't just gained source files. You have a new business feature you can open and use.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Refresh the List and Experience SSR
&lt;/h3&gt;

&lt;p&gt;The book list supports SSR, or server-side rendering.&lt;/p&gt;

&lt;p&gt;Refresh the browser while on the list page. The layout shows no obvious flicker during the refresh and retains the familiar Admin structure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe2tq46s5s2jz5b8nbdo2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe2tq46s5s2jz5b8nbdo2.jpg" alt="The book list, with search filters, a records area, and the management menu" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For someone using the application, the first impression is not the implementation details of SSR—it's how the page appears when opened or refreshed. For the developer, the Book module is now part of CabloyJS's SSR-enabled Admin interface, without having to build an SSR mechanism specifically for this CRUD feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Create a Book: From Form to List
&lt;/h2&gt;

&lt;p&gt;Click &lt;strong&gt;Create&lt;/strong&gt; to open the book form and enter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Title: Book One
Author: Kevin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Description is optional, so leave it empty for now and click &lt;strong&gt;Submit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Back in the list, you'll see Book One with Kevin as its author.&lt;/p&gt;

&lt;p&gt;It's a simple operation, but it brings the frontend and backend together: open a form, enter the fields, submit the data, and see the result in the list. The module AI just created can now carry out a real business operation.&lt;/p&gt;

&lt;p&gt;Next, instead of finishing with this book, let's keep working with it as an open task.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Keep Three Tasks Open Without Losing Input
&lt;/h2&gt;

&lt;p&gt;In an Admin application, it's common to start editing a record and then need to check its details. Or perhaps you've begun filling out a new form but need to deal with something else before submitting it.&lt;/p&gt;

&lt;p&gt;If leaving the page means reopening it—or entering everything again—your work gets interrupted.&lt;/p&gt;

&lt;p&gt;CabloyJS's Admin interface provides a second row of task tabs. Let's use Book One and Book Two to see how they work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open Book One for Editing
&lt;/h3&gt;

&lt;p&gt;From the list, open Book One's edit page. Its title and author are already filled in.&lt;/p&gt;

&lt;p&gt;Now return to the book list. Look at the tabs above: &lt;strong&gt;Book One's edit task is still open.&lt;/strong&gt; Returning to the list doesn't mean you have to close the edit page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open Book One's Details as Another Task
&lt;/h3&gt;

&lt;p&gt;Next, open Book One's detail page from the list.&lt;/p&gt;

&lt;p&gt;You now have two tasks for the same book: one for editing and one for viewing details. You can choose whichever you need without going back to the list and finding the record each time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start Book Two, but Don't Submit It
&lt;/h3&gt;

&lt;p&gt;Return to the list, click Create, and enter a second book:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Title: Book Two
Author: Tom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't submit this time. Leave the form as a draft.&lt;/p&gt;

&lt;p&gt;The task tabs now contain three open tasks:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Content&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Edit&lt;/td&gt;
&lt;td&gt;Book One's edit form&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Detail&lt;/td&gt;
&lt;td&gt;Book One's detail view&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Create&lt;/td&gt;
&lt;td&gt;Book Two's unsubmitted form&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Switch Back and Continue Where You Left Off
&lt;/h3&gt;

&lt;p&gt;Switch to Book One's edit task, then its detail task, and finally back to Book Two's create task.&lt;/p&gt;

&lt;p&gt;The title is still &lt;code&gt;Book Two&lt;/code&gt;, and the author is still &lt;code&gt;Tom&lt;/code&gt;. The edit and detail tasks also remain open, so you can continue switching among all three.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu5ct3qrfot71zvp31oyd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu5ct3qrfot71zvp31oyd.jpg" alt="Returning to Book Two's create task: the title and author are preserved, with all three task tabs still open above" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Book Two hasn't been submitted, but switching tasks hasn't lost the input.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That makes the task tabs more than navigation shortcuts. Each task keeps its current working context. You can check another piece of information and then return to finish the form, without submitting early just to leave the page or retyping everything when you come back.&lt;/p&gt;

&lt;p&gt;The experience feels closer to multitasking in a desktop application: several pieces of work can stay open together, rather than forcing you to finish one before moving to the next.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. A Practical Introduction to Developing with CabloyJS
&lt;/h2&gt;

&lt;p&gt;Looking back, we started with just one business request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a standalone demo-book module to implement CRUD operations for books.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AI developed and checked the module. After starting the servers, we could open the book list, create a record, and use the SSR-enabled pages and task tabs.&lt;/p&gt;

&lt;p&gt;This walkthrough gives you a direct feel for several aspects of CabloyJS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Modular development:&lt;/strong&gt; the new Book feature is organized as a standalone &lt;code&gt;demo-book&lt;/code&gt; module.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI working with existing tools:&lt;/strong&gt; AI examines the project and uses its generators and commands instead of introducing a separate implementation approach.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema-driven management pages:&lt;/strong&gt; business fields become part of the list, detail view, and forms, with reusable page capabilities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSR page experience:&lt;/strong&gt; the new resource joins the existing SSR-enabled Admin interface.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multitasking:&lt;/strong&gt; edit, detail, and create tasks stay open together, preserving form input when you switch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CRUD is the business feature at the center of the walkthrough. SSR and multitasking show what it's like to use that feature inside an actual Admin application.&lt;/p&gt;

&lt;p&gt;These aren't infrastructure capabilities AI has rewritten from scratch. They are capabilities CabloyJS already provides. &lt;strong&gt;AI develops the business feature within the framework, and the new feature benefits from the framework's existing pages and interactions.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Give AI a Request, Get a Feature You Can Use
&lt;/h2&gt;

&lt;p&gt;If you'd like to explore CabloyJS, a small feature like this is a good place to start.&lt;/p&gt;

&lt;p&gt;Give AI a clear request. See how it creates the module, refines the fields, and runs checks. Then open the browser, create a record yourself, refresh the list, and keep a few tasks open at once.&lt;/p&gt;

&lt;p&gt;Moving from “What code did it generate?” to “What does this feature feel like to use?” turns modules, schemas, and SSR from abstract terms into a concrete development and application experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's the point of hands-on AI coding: turn a request into a feature you can operate, and make the framework's capabilities something you can see and use.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  GitHub: &lt;a href="https://github.com/cabloy/cabloy" rel="noopener noreferrer"&gt;https://github.com/cabloy/cabloy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  Docs: &lt;a href="https://cabloy.com" rel="noopener noreferrer"&gt;https://cabloy.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  Demo: &lt;a href="https://cabloy.com/demo" rel="noopener noreferrer"&gt;https://cabloy.com/demo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aicoding</category>
      <category>codex</category>
      <category>crud</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>Vona Integrated SSR vs Zova Standalone SSR</title>
      <dc:creator>Uncle Pushui</dc:creator>
      <pubDate>Mon, 28 Sep 2026 02:55:47 +0000</pubDate>
      <link>https://dev.to/uncle-pushui/vona-integrated-ssr-vs-zova-standalone-ssr-4n2c</link>
      <guid>https://dev.to/uncle-pushui/vona-integrated-ssr-vs-zova-standalone-ssr-4n2c</guid>
      <description>&lt;p&gt;When you first run Cabloy Basic, you will usually encounter two kinds of commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start the complete Cabloy service&lt;/span&gt;
npm run dev

&lt;span class="c"&gt;# Start the Zova frontend SSR development service&lt;/span&gt;
npm run dev:zova:web
&lt;span class="c"&gt;# or&lt;/span&gt;
npm run dev:zova:admin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both can open an SSR page, but they serve different goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;npm run dev&lt;/code&gt; normally serves the project at &lt;code&gt;http://localhost:7102&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm run dev:zova:*&lt;/code&gt; normally serves the Web or Admin page at &lt;code&gt;http://localhost:9000&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A newcomer does not need every SSR detail at the start. Remember this first: &lt;strong&gt;use &lt;code&gt;9000&lt;/code&gt; to iterate on the frontend quickly; use &lt;code&gt;7102&lt;/code&gt; to confirm that the project runs through its complete production-style path.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwvh3pmqmskz4nx9ky84n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwvh3pmqmskz4nx9ky84n.png" alt="Vona Integrated SSR vs Zova Standalone SSR" width="800" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Two ports, two working rhythms
&lt;/h2&gt;

&lt;h3&gt;
  
  
  7102: Vona integrated SSR
&lt;/h3&gt;

&lt;p&gt;When you visit &lt;code&gt;7102&lt;/code&gt;, the request first reaches the &lt;strong&gt;Vona backend service&lt;/strong&gt;. Vona identifies the SSR Site for the URL, loads the corresponding Zova SSR build output, and returns the final HTML response to the browser.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;7102&lt;/code&gt; represents the complete runtime path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
  → Vona backend service
  → Zova SSR build output renders the page
  → browser takes over the page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matches the core production-style model: &lt;strong&gt;Vona carries the HTTP request and SSR integration, while Zova renders the frontend page.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;7102&lt;/code&gt; when you need to confirm that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Web or Admin path is correct;&lt;/li&gt;
&lt;li&gt;Vona can find and load the right frontend build output;&lt;/li&gt;
&lt;li&gt;backend APIs, SSR pages, and the final HTTP response work together;&lt;/li&gt;
&lt;li&gt;the project runs correctly through its complete path before delivery.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  9000: Zova standalone SSR
&lt;/h3&gt;

&lt;p&gt;When you visit &lt;code&gt;9000&lt;/code&gt;, the browser enters the &lt;strong&gt;Zova frontend development service&lt;/strong&gt; directly. It still server-renders the page, but its purpose is faster frontend work: edits to user code can hot-reload, and page, route, first render, and hydration issues are easier to inspect quickly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
  → Zova frontend development service
  → SSR-rendered page + user-code hot reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;9000&lt;/code&gt; first while you are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;changing page layout or interactions;&lt;/li&gt;
&lt;li&gt;editing components, routes, or frontend state;&lt;/li&gt;
&lt;li&gt;checking the SSR first paint and the behavior after browser hydration;&lt;/li&gt;
&lt;li&gt;shortening the edit-to-feedback loop with hot reload.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;7102&lt;/code&gt; and &lt;code&gt;9000&lt;/code&gt; are the default Cabloy Basic development ports. Environment configuration can change the numbers, but not the responsibilities of the two entry points.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  How a Cabloy project works as a fullstack system
&lt;/h2&gt;

&lt;p&gt;Cabloy’s fullstack model is built around two simple principles.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Frontend build output participates directly in backend SSR
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zova&lt;/strong&gt; owns frontend application source: pages, components, routes, and frontend state.&lt;/li&gt;
&lt;li&gt;Zova’s frontend bundle and SSR-related output are loaded and used by the &lt;strong&gt;Vona&lt;/strong&gt; SSR flow.&lt;/li&gt;
&lt;li&gt;Server rendering and browser hydration therefore stay on one coordinated delivery path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A complete page visit can be understood as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser request
  → Vona receives the request and finds the matching site
  → Zova SSR build output renders the page and prepares initial state
  → Vona returns HTML
  → the browser hydrates and continues the page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the basic meaning of integrated SSR: frontend SSR is not an isolated page prerender. It is one fullstack request completed together by Vona and Zova.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Type information flows in both directions
&lt;/h3&gt;

&lt;p&gt;Cabloy uses a frontend-backend separation architecture. It does not simply place one shared &lt;code&gt;types.ts&lt;/code&gt; file between the two sides. Instead, bidirectional contracts enable frontend and backend types to be generated and shared automatically.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend → Frontend:&lt;/strong&gt; Vona emits Swagger / OpenAPI contracts; Zova uses them to generate SDKs, types, and schema helpers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend → Backend:&lt;/strong&gt; Zova generates structural metadata and typing surfaces for routes, components, icons, renderers, and related resources; Vona tooling and type hints can consume them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next section uses one small example to make both directions concrete. For now, the key idea is enough: &lt;strong&gt;Vona owns backend entry and SSR integration, Zova owns the frontend application and rendering, and build output plus contracts let them collaborate.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why do types need to synchronize in both directions?
&lt;/h2&gt;

&lt;p&gt;One common fullstack problem is that the backend and frontend maintain two definitions that look alike: the backend changes a field, but the frontend forgets to update it; the frontend adds a renderable resource, but the backend cannot safely reference it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vona → Zova: when a backend API changes
&lt;/h3&gt;

&lt;p&gt;When a Controller, DTO, validation rule, or entity field changes, Vona owns the business fact. Vona expresses it as Swagger / OpenAPI, then Zova generates the matching API, types, and schema helpers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vona API / DTO / validation
  → Swagger / OpenAPI
  → generated Zova API and types
  → frontend Model / page consumes them
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The existing &lt;code&gt;summary/:id&lt;/code&gt; endpoint in &lt;code&gt;training-student&lt;/code&gt; shows the whole process. It returns a student summary, including a level title, summary text, and description length.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Define the endpoint and response DTO on the backend
&lt;/h4&gt;

&lt;p&gt;The Vona Controller declares the URL, parameter, and response DTO:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vona/.../training-student/src/controller/student.ts&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;summary/:id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;$locale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;StudentSummary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DtoStudentSummary&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Arg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tableIdentity&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TableIdentity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DtoStudentSummary&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;student&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response DTO declares the actual fields. If the backend adds &lt;code&gt;summaryText&lt;/code&gt;, this DTO is the contract source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vona/.../training-student/src/dto/studentSummary.tsx&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Dto&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;IDtoOptionsStudentSummary&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DtoStudentSummary&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;$Dto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ModelStudent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mobile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;level&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;$locale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;LevelTitle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="nx"&gt;levelTitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;$locale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Summary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="nx"&gt;summaryText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Regenerate the Zova API and types
&lt;/h4&gt;

&lt;p&gt;First make sure Vona’s Swagger output contains the new field, then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run zova :openapi:generate training-student
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This refreshes the generated Zova API method, OpenAPI response type, and schema facade. Do not edit generated files directly; the next generation will overwrite them.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Consume the generated API in the frontend
&lt;/h4&gt;

&lt;p&gt;After generation, the Zova API surface provides &lt;code&gt;trainingStudent.summary(...)&lt;/code&gt; with its generated response type. A frontend Model can wrap it with a thin semantic method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// zova/.../training-student/src/model/student.ts&lt;/span&gt;
&lt;span class="nf"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TableIdentity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$$modelResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;queryItem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;summary&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;queryFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trainingStudent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A page or table action only needs to call &lt;code&gt;student.summary(id)&lt;/code&gt; to receive fields such as &lt;code&gt;summaryText&lt;/code&gt; and &lt;code&gt;levelTitle&lt;/code&gt;. The frontend does not need to handwrite a second &lt;code&gt;StudentSummary&lt;/code&gt; interface: when the backend DTO changes, regenerate the consumer and keep using the updated type.&lt;/p&gt;

&lt;p&gt;The complete flow is: &lt;strong&gt;Vona DTO → Swagger / OpenAPI → &lt;code&gt;openapi:generate&lt;/code&gt; → Zova API → Model / page.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Zova → Vona: when a frontend resource changes
&lt;/h3&gt;

&lt;p&gt;Some facts belong to the frontend. A custom form field, table cell, route, or icon is implemented by Zova. Vona needs a stable resource identity to reference it, but it does not execute the frontend component source itself.&lt;/p&gt;

&lt;p&gt;The current &lt;code&gt;training-student&lt;/code&gt; module provides a compact example: Vona defines what a Student Level means and which values are allowed; Zova implements the matching level selector and level badge.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vona: what Level means, its allowed values, and the renderer key to use
  → Zova: implements the form field and table cell behind that key
  → build and synchronize the handoff
  → Vona can safely reference the refreshed frontend resource
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a Zova resource like this changes, use the complete build and synchronization flow for the relevant flavor. For Admin, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build:zova:admin
npm run deps:vona
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The role of these two steps becomes concrete in the &lt;code&gt;training-student&lt;/code&gt; level renderer. Zova first declares a stable renderer key and implements the actual form control:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// zova/.../training-student/src/component/formFieldLevel/controller.tsx&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zova-module-a-openapi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IResourceFormFieldRecord&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;training-student:formFieldLevel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;IResourceFormFieldLevelOptions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ControllerFormFieldLevel&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BeanControllerBase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;itemValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;itemTitle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;itemValue&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;itemTitle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;npm run build:zova:admin&lt;/code&gt; produces the Admin SSR and REST handoff artifacts. &lt;code&gt;npm run deps:vona&lt;/code&gt; then synchronizes that handoff to Vona. After synchronization, Vona DTO or field metadata can reference the key and pass typed options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// vona/.../training-student/src/entity/student.tsx&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;$locale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Level&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="nx"&gt;ZovaRender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;training-student:formFieldLevel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;studentLevelItems&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;$locale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Level&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nx"&gt;ZovaRender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;training-student:level&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;studentLevelItems&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;union&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;literal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;literal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;literal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)]),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the backend, &lt;code&gt;DtoStudentSelectResItem&lt;/code&gt; inherits from &lt;code&gt;ModelStudent&lt;/code&gt; and uses DTO field metadata to define how the list and form should be presented. After receiving the DTO, the frontend dynamically renders it from the renderer key and options: Zova still executes the actual JSX component, while the DTO only describes which renderer to use and which arguments to pass. It does not import or execute the frontend component source directly.&lt;/p&gt;

&lt;p&gt;You do not need to memorize every command. The important direction is: &lt;strong&gt;the backend owns APIs and business rules; the frontend owns pages and renderers. When something changes, pass the contract from the side that owns the fact to the side that consumes it.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Where to explore next
&lt;/h2&gt;

&lt;p&gt;If you are new to Cabloy, this is a useful learning sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Change a page through &lt;code&gt;9000&lt;/code&gt; to experience Zova development and hot reload.&lt;/li&gt;
&lt;li&gt;Visit the same page through &lt;code&gt;7102&lt;/code&gt; to see how Vona carries the complete SSR request.&lt;/li&gt;
&lt;li&gt;Change an API field and inspect how generated frontend types change from OpenAPI.&lt;/li&gt;
&lt;li&gt;Add a frontend renderer and learn why it needs a build and synchronization handoff to Vona.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As a project grows, this division makes problems easier to locate: is it a page-development issue, a Vona integration issue, or a contract-synchronization issue? You no longer have to describe every mismatch as simply “frontend and backend are out of sync.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/cabloy/cabloy" rel="noopener noreferrer"&gt;https://github.com/cabloy/cabloy&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://cabloy.com" rel="noopener noreferrer"&gt;https://cabloy.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Demo: &lt;a href="https://cabloy.com/demo" rel="noopener noreferrer"&gt;https://cabloy.com/demo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Create fast feedback at &lt;code&gt;9000&lt;/code&gt;, then prove the complete runtime path at &lt;code&gt;7102&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>cabloy</category>
      <category>ssr</category>
      <category>fullstack</category>
      <category>typescript</category>
    </item>
    <item>
      <title>CabloyJS AI Spec-Driven Development Automatically Generates Gantt and Burndown Charts</title>
      <dc:creator>Uncle Pushui</dc:creator>
      <pubDate>Fri, 11 Sep 2026 07:43:42 +0000</pubDate>
      <link>https://dev.to/uncle-pushui/cabloyjs-ai-spec-driven-development-automatically-generates-gantt-and-burndown-charts-5dem</link>
      <guid>https://dev.to/uncle-pushui/cabloyjs-ai-spec-driven-development-automatically-generates-gantt-and-burndown-charts-5dem</guid>
      <description>&lt;p&gt;The easiest way to start AI coding is to describe a page or an API in a chat window. But once a project has multiple modules, multiple audiences, and an evolving frontend-backend contract, the genuinely difficult questions become: where does this change belong? Which boundaries have already been confirmed? How can we prove that it is actually complete?&lt;/p&gt;

&lt;p&gt;CabloyJS includes two complementary Claude Code Skills that divide the work into two phases: first establish specification authority inside the repository, then execute a bounded delivery increment. This article introduces the two Skills, explains the Spec files they produce and consume, and uses real charts from the built-in &lt;code&gt;a-commerce&lt;/code&gt; suite to show the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Meet the two Skills first
&lt;/h2&gt;

&lt;p&gt;The two Skills are invoked as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cabloy-spec-generation&lt;/code&gt;: creates or maintains the suite planning records under &lt;code&gt;repo-specs/&amp;lt;suite&amp;gt;/&lt;/code&gt;, establishing traceability among product, technical, delivery, and acceptance concerns.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cabloy-spec-execution&lt;/code&gt;: works from an existing specification to execute one explicit &lt;code&gt;WBS-*&lt;/code&gt; item—or a finite, approved phase with a closed boundary—and records the actual verification results.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following table summarizes the difference:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;&lt;code&gt;cabloy-spec-generation&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;cabloy-spec-execution&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Problem addressed&lt;/td&gt;
&lt;td&gt;What should be built, and how should boundaries and acceptance be recorded?&lt;/td&gt;
&lt;td&gt;How should one approved item be implemented and proven complete?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Input&lt;/td&gt;
&lt;td&gt;A new suite description or a request to maintain an existing specification&lt;/td&gt;
&lt;td&gt;An explicit &lt;code&gt;WBS-*&lt;/code&gt; item, or a finite and approved phase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Primary outputs&lt;/td&gt;
&lt;td&gt;PRD, SRS, PDP/WBS, ATP, ADRs, derived status, and charts&lt;/td&gt;
&lt;td&gt;Source changes, test results, Evidence, derived progress, and charts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Confirmation gate&lt;/td&gt;
&lt;td&gt;Confirms identity, scope, topology, site strategy, and more before planning records are written&lt;/td&gt;
&lt;td&gt;Confirms the execution dossier, dependencies, scope, commands, and evidence requirements before implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What it does not do&lt;/td&gt;
&lt;td&gt;Does not write business source code or treat planning as implementation&lt;/td&gt;
&lt;td&gt;Does not invent requirements, replace specification generation, or automatically expand into adjacent tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  1. Establish a specification baseline with generation
&lt;/h3&gt;

&lt;p&gt;In Claude Code, begin with a business description:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/cabloy-spec-generation Plan an equipment-maintenance suite for field technicians and operations managers: include equipment records, work orders, asset history, role-based access control, and an Admin dashboard.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Skill first performs read-only discovery and asks for missing inputs. Its typical steps are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;inspect the repository root, &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;CLAUDE.md&lt;/code&gt;, and the edition marker;&lt;/li&gt;
&lt;li&gt;distinguish a new suite from an extension of an existing suite, and check whether &lt;code&gt;repo-specs/&amp;lt;suite&amp;gt;/&lt;/code&gt; already exists;&lt;/li&gt;
&lt;li&gt;independently evaluate whether Web and Admin should reuse an existing site or use a dedicated site;&lt;/li&gt;
&lt;li&gt;collect product goals, roles, scope, module topology, tenancy and authorization, states, transactions, verification, and release constraints;&lt;/li&gt;
&lt;li&gt;show a generation confirmation gate before writing planning records;&lt;/li&gt;
&lt;li&gt;generate files in authority order, run exact-ID traceability checks, and produce two derived charts.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2. Execute a bounded increment with execution
&lt;/h3&gt;

&lt;p&gt;Once the specification is ready and a task’s dependencies are satisfied, invoke execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/cabloy-spec-execution WBS-40-03
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;WBS-40-03&lt;/code&gt; is only an example. A real project should use an ID that actually exists in the target suite. The AI then follows this process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;read &lt;code&gt;README.md&lt;/code&gt;, the PRD, SRS, applicable ADRs, the complete WBS, ATP/test plan, progress, Evidence, and derived charts;&lt;/li&gt;
&lt;li&gt;check the edition, current revision, workspace state, dependencies, blockers, unresolved &lt;code&gt;TODO&lt;/code&gt;s, and whether previous evidence is still valid;&lt;/li&gt;
&lt;li&gt;prepare an execution dossier listing the objective, scope, exclusions, authority IDs, source ownership, specialist-Skill routing, verification commands, and evidence format;&lt;/li&gt;
&lt;li&gt;obtain explicit confirmation before routing into backend, frontend, or Contract Loop specialist workflows;&lt;/li&gt;
&lt;li&gt;run the narrowest meaningful check first, followed by the ATP-required verification;&lt;/li&gt;
&lt;li&gt;save Evidence containing the revision, environment, steps, results, and locations of sanitized artifacts;&lt;/li&gt;
&lt;li&gt;update derived &lt;code&gt;progress.md&lt;/code&gt;, regenerate and check the charts, and leave one unambiguous next action.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/cabloy-spec-execution Execute the next task in the equipment-maintenance suite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the next incremental WBS ID is unknown, ask the AI to first read the suite’s &lt;code&gt;progress.md&lt;/code&gt; and combine it with the WBS, ATP, and existing Evidence to list executable &lt;code&gt;WBS-*&lt;/code&gt; IDs whose dependencies are satisfied. The user can then confirm the specific item to execute next.&lt;/p&gt;

&lt;h2&gt;
  
  
  What generation produces: a connected set of Spec files
&lt;/h2&gt;

&lt;p&gt;For a long-lived business suite, the default core output of &lt;code&gt;cabloy-spec-generation&lt;/code&gt; is located at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;repo-specs/&amp;lt;suite&amp;gt;/
├── README.md
├── prd.md
├── srs.md
├── pdp-wbs.md
├── test-plan.md
├── progress.md
├── implementation-gantt.svg
├── implementation-burndown.svg
└── decisions/
    └── 0001-&amp;lt;suite-boundary-slug&amp;gt;.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not simply one large document split into several files. It is a model of &lt;strong&gt;separated authority&lt;/strong&gt;: each record owns one kind of fact, while the others refer to it through stable IDs and links.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Facts it owns&lt;/th&gt;
&lt;th&gt;What it should not replace&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Index, reading order, suite baseline, topology summary, and authority map&lt;/td&gt;
&lt;td&gt;Detailed PRD, SRS, or WBS content&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;prd.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Product goals, roles, scope, journeys, business rules, product requirements, and product acceptance&lt;/td&gt;
&lt;td&gt;Table names, DTO syntax, routes, or test commands&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;srs.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Technical contracts; data and capability ownership; tenancy; identity; authorization; states; transactions; concurrency; APIs/DTOs; SSR; and non-functional boundaries&lt;/td&gt;
&lt;td&gt;API authority or authorization determined by a UI layout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pdp-wbs.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delivery phases, WBS tasks, dependencies, completion checks, and delivery traceability&lt;/td&gt;
&lt;td&gt;Upstream product requirements or technical contracts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;test-plan.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ATP acceptance scenarios, test levels, fixtures, verification procedures, Evidence format, and release gates&lt;/td&gt;
&lt;td&gt;Planned commands presented as results that have already passed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;progress.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;WBS state, blockers, Evidence pointers, decisions, and the next proof&lt;/td&gt;
&lt;td&gt;Requirements, contracts, or acceptance rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;decisions/*.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Long-lived scope, architecture, security, ownership, or integration decisions&lt;/td&gt;
&lt;td&gt;A duplicate SRS or status log&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;implementation-gantt.svg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A WBS-derived view of phases, task order, dependencies, and status&lt;/td&gt;
&lt;td&gt;Planning authority, date commitments, or duration commitments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;implementation-burndown.svg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A scope-and-status-derived count of remaining items&lt;/td&gt;
&lt;td&gt;A velocity trend or forecast when no historical snapshots exist&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  From business intent to evidence: how the files connect
&lt;/h2&gt;

&lt;p&gt;The core traceability chain is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PRD requirement
      ↓
SRS contract
      ↓
PDP/WBS task
      ↓
ATP scenario
      ↓
observed Evidence
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can also be written more compactly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PRD → SRS → WBS → ATP → Evidence
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, a product requirement that “an authorized operator can view orders in the current tenant” must be made concrete in the SRS as identity, tenancy, authorization, data ownership, and API contracts. The WBS then defines the implementation boundary and dependencies; the ATP provides executable happy-path, unauthorized, and cross-tenant scenarios; finally, the actual runtime observations are saved as sanitized evidence.&lt;/p&gt;

&lt;p&gt;When a task is executed, the document-state update order is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Upstream authority changes
  → traceability matrix
  → WBS dependencies and completion checks
  → ATP procedures and expected Evidence
  → progress / Evidence
  → two derived charts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;progress.md&lt;/code&gt; and both SVGs are downstream derived outputs. They help people and AI understand the current state quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to read the two charts
&lt;/h2&gt;

&lt;p&gt;The charts are automatically generated by built-in scripts from &lt;code&gt;pdp-wbs.md&lt;/code&gt;, &lt;code&gt;test-plan.md&lt;/code&gt;, and &lt;code&gt;progress.md&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gantt: inspect delivery order, not a calendar schedule
&lt;/h3&gt;

&lt;p&gt;The Gantt chart reads the formal phases, WBS tasks, dependencies, and progress statuses. It is useful for answering: what delivery packages exist? What is their approximate order? Which tasks depend on preceding phases?&lt;/p&gt;

&lt;p&gt;The current chart for the built-in &lt;code&gt;a-commerce&lt;/code&gt; suite contains 23 formal WBS tasks, all shown as 23/23 &lt;code&gt;verified&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffjtoblate1691f7hhzap.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffjtoblate1691f7hhzap.png" alt="A-Commerce implementation roadmap: WBS phases, dependencies, and derived status" width="800" height="831"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Burndown: inspect scope counts, not velocity forecasts
&lt;/h3&gt;

&lt;p&gt;In the current &lt;code&gt;a-commerce&lt;/code&gt; snapshot, the Burndown chart shows 23 active WBS items, 23 &lt;code&gt;verified&lt;/code&gt;, 0 remaining, and 0 deferred.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa604xq2vkgan301ibpki.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa604xq2vkgan301ibpki.png" alt="A-Commerce WBS scope-count burndown: approved scope and remaining-item counts" width="800" height="525"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  When this approach is worthwhile
&lt;/h2&gt;

&lt;p&gt;This approach is best suited to enterprise systems that evolve over time, span modules, involve multiple collaborators, and include both Vona backend and Zova Admin/Web/SSR work—especially where tenancy, authorization, transactions, concurrency, auditability, or external integrations are important.&lt;/p&gt;

&lt;p&gt;For a one-off page, a simple website, or a very small utility, a lightweight prompt-to-code workflow may be more economical.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a small increment
&lt;/h2&gt;

&lt;p&gt;Choose one clearly bounded capability rather than trying to plan an entire system at once:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use generation to plan the requirement.

&lt;ul&gt;
&lt;li&gt;Record goals, scope, and exclusions.&lt;/li&gt;
&lt;li&gt;Confirm technical contracts, ownership, dependencies, and ATP.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Use execution to deliver one WBS ID.

&lt;ul&gt;
&lt;li&gt;Run applicable verification and save sanitized Evidence.&lt;/li&gt;
&lt;li&gt;Update progress and the derived charts.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Proceed to the next increment.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Create a Cabloy project from the public scaffolding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm create cabloy
npm run dev
npm run dev:zova:admin
npm run dev:zova:web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;AI Spec-Driven Development in CabloyJS is not another way of saying “prompts become code.” It is a more concrete delivery path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Business intent → specification authority → bounded WBS → acceptance procedure → observed Evidence
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cabloy-spec-generation&lt;/code&gt; organizes intent into a traceable planning chain. &lt;code&gt;cabloy-spec-execution&lt;/code&gt; advances one increment within confirmed boundaries. Together, they reduce the AI’s need to guess about business scope, technical ownership, and the definition of done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/ai/ai-spec-driven-development" rel="noopener noreferrer"&gt;AI Spec-Driven Development&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/ai/playbook-spec-generation" rel="noopener noreferrer"&gt;Generate a Cabloy Suite Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/ai/playbook-spec-execution" rel="noopener noreferrer"&gt;Execute an Approved Cabloy Specification Increment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/ai/verification" rel="noopener noreferrer"&gt;Verification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/fullstack/contract-loop-playbook" rel="noopener noreferrer"&gt;Contract Loop Playbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/fullstack/vona-zova-integration" rel="noopener noreferrer"&gt;Vona + Zova Integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cabloy.com/fullstack/tutorials-overview" rel="noopener noreferrer"&gt;Cabloy Fullstack Quick Start Tutorials&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>specdrivendevelopment</category>
      <category>vibecoding</category>
      <category>architecture</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>Beyond IoC: CabloyJS as a Full-Stack Resource Addressing System</title>
      <dc:creator>Uncle Pushui</dc:creator>
      <pubDate>Sun, 16 Aug 2026 06:44:07 +0000</pubDate>
      <link>https://dev.to/uncle-pushui/beyond-ioc-cabloyjs-as-a-full-stack-resource-addressing-system-ohl</link>
      <guid>https://dev.to/uncle-pushui/beyond-ioc-cabloyjs-as-a-full-stack-resource-addressing-system-ohl</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;IoC answers “how a runtime collaborator is obtained.” Full-stack resource addressing answers “which business capability this is, where it enters the system, how each layer locates it, and how it retains one business meaning.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In a new application, tracing a feature is usually straightforward: find a component, follow an import to its request function, then follow that function to a backend Controller. As a system grows into multiple business domains and modules—with admin resource pages, Web self-service pages, SSR, OpenAPI contracts, and frontend rendering resources—the harder question is no longer simply whether a Service can be injected.&lt;/p&gt;

&lt;p&gt;Consider a Student capability. Which business domain owns it? Which module implements it? How does the backend expose it? How does a menu enter it? How does a generic Resource page know what it is displaying? And how do frontend requests, Schemas, permissions, forms, caches, and custom rendering continue to refer to the same business meaning?&lt;/p&gt;

&lt;p&gt;IoC matters to all of those questions: it provides object ownership, lifecycle, scope, and dependency resolution. But Cabloy becomes more interesting when IoC is not viewed in isolation. Together with suites, modules, Bean identities, business Resources, OpenAPI, route parameters, selector-backed Models, and rendering metadata, it forms a set of &lt;strong&gt;constrained, mappable addressing coordinates&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is not one universal string that travels through every layer, nor is it zero-configuration magic. Vona and Zova have independent runtime containers. Backend and frontend cooperate through contracts, metadata, generated artifacts, and build outputs. This article uses “full-stack resource addressing system” as a lens for understanding Cabloy architecture—not as the name of an official API that replaces existing terminology.&lt;/p&gt;




&lt;h2&gt;
  
  
  Separate IoC from addressing first
&lt;/h2&gt;

&lt;p&gt;IoC asks a focused question: &lt;strong&gt;who creates, owns, disposes of, and supplies a runtime capability?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In Zova, a Bean may be held in the &lt;code&gt;sys&lt;/code&gt;, &lt;code&gt;app&lt;/code&gt;, or &lt;code&gt;ctx&lt;/code&gt; container scope. Code can use &lt;code&gt;@Use&lt;/code&gt; for injection or use containers and Scope for dependency lookup. This single model keeps state sharing, lifecycle behavior, and cross-module collaboration from drifting into unrelated mechanisms. &lt;a href="https://docs.cabloy.com/frontend/ioc-and-beans" rel="noopener noreferrer"&gt;IoC and Beans&lt;/a&gt; explains that model in detail.&lt;/p&gt;

&lt;p&gt;Addressing asks a broader question: &lt;strong&gt;when one layer needs something, which identity expresses the target, who resolves it, and what kind of resource results?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The current repository’s Student sample includes related but non-interchangeable values:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Question answered&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Business domain / suite&lt;/td&gt;
&lt;td&gt;&lt;code&gt;a-training&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which business domain owns this family of capabilities?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feature module&lt;/td&gt;
&lt;td&gt;&lt;code&gt;training-student&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which capability boundary owns Student?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bean full name&lt;/td&gt;
&lt;td&gt;&lt;code&gt;training-student.service.student&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which runtime Service should the container resolve?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Business Resource identity (onion name)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;training-student:student&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which module-qualified business Resource is under discussion?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Route entry&lt;/td&gt;
&lt;td&gt;&lt;code&gt;:resource/:id/:formScene?&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which page scene is being entered?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP API path&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/training/student&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Where should the request be sent?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resource Model selector&lt;/td&gt;
&lt;td&gt;&lt;code&gt;training-student:student&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which Resource is the generic owner maintaining state for?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Render resource&lt;/td&gt;
&lt;td&gt;&lt;code&gt;training-student:formFieldLevel&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which frontend scene capability renders the field?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Think of these as different coordinate systems for the same place: an administrative region, street, building number, navigation entry, delivery address, and room number are related, but they solve different problems. Calling them all an “ID” hides the boundaries that matter.&lt;/p&gt;

&lt;p&gt;Let us follow &lt;code&gt;training-student:student&lt;/code&gt; through the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coordinate one: suites and modules answer “who owns it?”
&lt;/h2&gt;

&lt;p&gt;Cabloy defines a &lt;strong&gt;suite&lt;/strong&gt; as a business-domain composition boundary and a &lt;strong&gt;module&lt;/strong&gt; as a feature implementation boundary within that domain. In other words:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a suite answers, “which business domain does this group of capabilities belong to?”&lt;/li&gt;
&lt;li&gt;a module answers, “within that domain, which capability owns this implementation?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the Student sample, the domain is &lt;code&gt;a-training&lt;/code&gt; and the module is &lt;code&gt;training-student&lt;/code&gt;. Vona and Zova both have corresponding suite/module structures, so backend Controllers, Services, Models, Entities, and DTOs can evolve along the same business boundary as frontend pages, APIs, Models, and metadata. What is shared first is a &lt;strong&gt;business coordinate&lt;/strong&gt;, not a block of memory shared by Node.js and the browser.&lt;/p&gt;

&lt;p&gt;That is why a module name matters more than a directory position. Directories can be reorganized and implementation files can move, while a stable module namespace can continue to carry the logical identity of Services, Models, APIs, locales, errors, Resources, and frontend render resources. See &lt;a href="https://docs.cabloy.com/fullstack/suites-and-modules" rel="noopener noreferrer"&gt;Suites and Modules&lt;/a&gt; for the boundary between suites and modules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coordinate two: Bean full names and the Scope facade
&lt;/h2&gt;

&lt;p&gt;At runtime, one module still needs to locate different families of capabilities. Most scene-based Vona Beans use this full identity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{moduleName}.{sceneName}.{beanName}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;training-student.controller.student
training-student.service.student
training-student.model.student
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;sceneName&lt;/code&gt; distinguishes capability families such as controllers, services, and models. Two related facts must therefore remain separate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;training-student:student&lt;/code&gt; is a module-qualified onion name;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;training-student.service.student&lt;/code&gt; is a Bean full name whose scene has already been specified.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An onion name is not merely a global Bean key with some characters omitted. A consumer must identify a scene such as &lt;code&gt;service&lt;/code&gt;, &lt;code&gt;model&lt;/code&gt;, or &lt;code&gt;controller&lt;/code&gt; before the framework can resolve the corresponding full Bean name.&lt;/p&gt;

&lt;p&gt;Business code usually does not need to write full names everywhere. Scope supplies a typed facade over module resources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Business orchestration in the current module&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;students&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;student&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// A known cross-module target&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sku&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commerceCatalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scope does not create another identity system. It raises a container-level full name into an everyday way to navigate a module’s resource catalog. Backend Scope can organize services, models, entities, configuration, locales, and errors; frontend Scope commonly exposes facades for configuration, constants, locales, errors, APIs, and API Schemas. Both &lt;a href="https://docs.cabloy.com/backend/foundation" rel="noopener noreferrer"&gt;Vona Backend Foundation&lt;/a&gt; and &lt;a href="https://docs.cabloy.com/frontend/module-scope" rel="noopener noreferrer"&gt;Zova Module Scope&lt;/a&gt; describe that division of responsibility.&lt;/p&gt;

&lt;p&gt;There is an important limit: Scope lookup can resolve only resources from modules that have already been composed into the active application. A correct string does not install, load, or order an absent module. Module dependencies, suite/application composition, and runtime lookup are related but different concerns. The first establishes availability, ordering, and versioning; only then can the latter retrieve a resource from the composed modules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coordinate three: &lt;code&gt;@Resource()&lt;/code&gt; connects business identity to an HTTP contract
&lt;/h2&gt;

&lt;p&gt;The Student backend Controller uses &lt;code&gt;@Controller('student')&lt;/code&gt; and &lt;code&gt;@Resource()&lt;/code&gt;. This is a trimmed version of the current implementation, retaining only the roles relevant here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;student&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ControllerStudent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BeanBase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DtoStudentSelectRes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Arg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DtoStudentSelectReq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IQueryParams&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ModelStudent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DtoStudentSelectRes&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;student&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not just a class with two decorators. It connects at least three responsibilities:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the Controller exposes an HTTP-facing action;&lt;/li&gt;
&lt;li&gt;DTOs, Entity fields, and validation shape a machine-readable contract;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@Resource()&lt;/code&gt; registers the Controller’s onion identity as a business Resource.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When Vona processes the Controller decorator, it derives the onion name from the Bean full name, combines module and controller-path metadata to compute the API path, and records the resource-to-route mapping. &lt;code&gt;training-student:student&lt;/code&gt; can therefore remain a stable business Resource identity, while the HTTP path is a transport address derived from backend route metadata.&lt;/p&gt;

&lt;p&gt;Avoid saying:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;training-student:student === /api/training/student
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A more accurate relationship is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;training-student:student
  ──resolved through Resource registration and bootstrap metadata──▶
/api/training/student
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first expresses &lt;em&gt;which business Resource&lt;/em&gt; is involved; the second expresses &lt;em&gt;where the request goes&lt;/em&gt;. This is one reason a Resource identity is often safer than scattering HTTP strings when frontend state, permissions, Schemas, or route context need to carry business meaning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coordinate four: one field contract does not need to be copied into separate knowledge
&lt;/h2&gt;

&lt;p&gt;The value of resource addressing is not limited to finding an endpoint. It also allows one business contract to be recognized and projected by different consumers.&lt;/p&gt;

&lt;p&gt;A backend field can participate in:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;validation;&lt;/li&gt;
&lt;li&gt;OpenAPI generation;&lt;/li&gt;
&lt;li&gt;form and table rendering;&lt;/li&gt;
&lt;li&gt;serialization or desensitization.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Take &lt;code&gt;mobile&lt;/code&gt; from the Student Entity in &lt;code&gt;training-student&lt;/code&gt;. This is a trimmed version of the current source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;$locale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mobile&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="c1"&gt;// OpenAPI field title&lt;/span&gt;
  &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;               &lt;span class="c1"&gt;// validation&lt;/span&gt;
  &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;                  &lt;span class="c1"&gt;// validation&lt;/span&gt;
  &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serializerReplace&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;       &lt;span class="c1"&gt;// response-time desensitization&lt;/span&gt;
    &lt;span class="na"&gt;patternFrom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(\d{3})\d{4}(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;patternTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$1****$2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nx"&gt;ZovaRender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;        &lt;span class="c1"&gt;// display order for generic table and field surfaces&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;mobile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This declaration does not merely describe an isolated string. It is projected through DTOs and the Resource Schema: create, update, and view DTO layouts place &lt;code&gt;{ type: 'field', name: 'mobile' }&lt;/code&gt; in Student Profile; the list-row Schema inherits the field and orders it through &lt;code&gt;order(3)&lt;/code&gt;. With no specialized Renderer, the generic form uses the default &lt;code&gt;Input&lt;/code&gt; and the generic table uses a plain-text Cell. A query response with &lt;code&gt;@Core.serializer()&lt;/code&gt; returns &lt;code&gt;13812345678&lt;/code&gt; as &lt;code&gt;138****5678&lt;/code&gt;, while the persisted raw value remains unchanged.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;mobile&lt;/code&gt; declaration or projection&lt;/th&gt;
&lt;th&gt;Consumer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Validation&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;v.required()&lt;/code&gt; and &lt;code&gt;v.min(11)&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Request/DTO validation boundary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenAPI generation&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@Api.field(...)&lt;/code&gt;, title, and string Schema&lt;/td&gt;
&lt;td&gt;OpenAPI and downstream SDK/Schema consumers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Form and table rendering&lt;/td&gt;
&lt;td&gt;DTO layout’s &lt;code&gt;field: mobile&lt;/code&gt; plus &lt;code&gt;ZovaRender.order(3)&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Generic Resource UI’s default Input and text Cell&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serialization / desensitization&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v.serializerReplace(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Response serialization before data reaches the client&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If this field needs specialized controls instead of the default Renderer, it can additionally declare &lt;code&gt;ZovaRender.field('training-student:formFieldLevel', ...)&lt;/code&gt; and &lt;code&gt;ZovaRender.cell('training-student:level', ...)&lt;/code&gt;, as &lt;code&gt;level&lt;/code&gt; does in the same Entity. The point is not that every property must carry all four concerns or use a custom component. The point is that these concerns can be composed around the same field contract when they apply.&lt;/p&gt;

&lt;p&gt;That does not mean each field automatically grows a complete UI, nor that every presentation decision belongs in the backend. It means the field’s business meaning, data contract, and permitted exposure do not have to be copied into backend DTOs, frontend request types, form rules, table columns, and response post-processing—and then manually kept aligned.&lt;/p&gt;

&lt;p&gt;In this &lt;strong&gt;forward contract chain&lt;/strong&gt;, backend Controllers, DTOs, Entities, and validation rules are the source of truth. Vona generates OpenAPI; Zova then generates or consumes SDK/Schema contract material. When a backend contract changes, the recommended path is to propagate that truth forward rather than hand-edit multiple frontend copies. &lt;a href="https://docs.cabloy.com/fullstack/openapi-to-sdk" rel="noopener noreferrer"&gt;Backend OpenAPI to Frontend SDK&lt;/a&gt; and &lt;a href="https://docs.cabloy.com/fullstack/tutorial-6-one-contract-four-uses" rel="noopener noreferrer"&gt;One Contract Surface, Four Uses&lt;/a&gt; describe the boundary of that chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coordinate five: routes choose a UI scene; Resource identities choose business context
&lt;/h2&gt;

&lt;p&gt;The generic Resource page makes clear why a page address and a business Resource identity need to remain separate.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;rest-resource&lt;/code&gt; defines generic routes with dynamic parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;IModuleRoute&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;resource&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:resource&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ZPageResource&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;entryCreate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:resource/create&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ZPageEntryCreate&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;entry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:resource/:id/:formScene?&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ZPageEntry&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dynamic-parameter routes explicitly declare &lt;code&gt;name&lt;/code&gt;, which establishes a type contract for page parameters. The route answers whether the active scene is a resource list, a create form, or a form scene for one record. The route itself does not yet know the business meaning of Student, Course, or any other Resource.&lt;/p&gt;

&lt;p&gt;The Student SSR menu supplies that context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;SsrMenu&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;student&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;presetResource&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;training-student:student&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SsrMenuStudent&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BeanBase&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The menu does not directly render a Student page, nor does it grant authorization. It passes the &lt;code&gt;resource&lt;/code&gt; parameter to the predefined generic Resource route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SSR menu
  → presetResource
  → :resource route parameter
  → Page Controller’s $params.resource
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The URL and route locate a page scene; &lt;code&gt;$params.resource&lt;/code&gt; brings in business Resource context. Route names, route parameters, menu metadata, and permissions are still four separate objects that must each be verified.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coordinate six: the Resource identity becomes a selector-backed Model owner
&lt;/h2&gt;

&lt;p&gt;Once the generic page receives its parameter, it does not duplicate a page Controller for every Resource. It injects the current identity into one generic Model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ControllerPageResource&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BeanControllerPageBase&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;beanFullName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rest-resource.model.resource&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="nf"&gt;$modelResource&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;ModelResource&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;usePrepareArg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nc"&gt;$QueryEnsureLoaded&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$$modelResource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiSchemasSelect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two addresses at different levels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generic Model Bean: rest-resource.model.resource
Current selector: training-student:student
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ModelResource&lt;/code&gt; is registered with &lt;code&gt;enableSelector: true&lt;/code&gt;. It is not a vague global CRUD singleton; it is a selector-backed owner. &lt;code&gt;ModelResource(training-student:student)&lt;/code&gt; and &lt;code&gt;ModelResource(other-module:other-resource)&lt;/code&gt; can share an implementation while owning distinct Resource contexts.&lt;/p&gt;

&lt;p&gt;During initialization, the owner requests bootstrap information with the Resource identity and stores the resolved &lt;code&gt;resourceApi&lt;/code&gt;. It then centralizes permissions, form providers, view/create/update/filter/row/page Schemas, and list/item query and mutation capabilities in the same owner.&lt;/p&gt;

&lt;p&gt;The conceptual chain is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$params.resource
  → ModelResource(resource selector)
  → bootstrap(resource)
  → resourceApi
  → schemas / permissions / forms / queries / mutations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This has two practical benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a page does not need to copy the same API path, Schema-loading rule, permission lookup, and cache-invalidation policy into multiple Controllers;&lt;/li&gt;
&lt;li&gt;a custom operation for the same Resource can be a thin semantic facade that reuses the existing Resource owner instead of creating a competing query-cache owner.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, a &lt;code&gt;training-student&lt;/code&gt; frontend Model can initialize the generic owner with &lt;code&gt;training-student:student&lt;/code&gt;, then add semantic entry points for business actions such as &lt;code&gt;summary(id)&lt;/code&gt; or &lt;code&gt;deleteForce(id)&lt;/code&gt; while reusing that owner’s item query, mutation, and invalidation boundary.&lt;/p&gt;

&lt;p&gt;One more distinction matters: a call-site value such as &lt;code&gt;['select', ...]&lt;/code&gt; or &lt;code&gt;['item', id, action]&lt;/code&gt; is only a logical query key. The effective cache identity also incorporates Model Bean identity and selector. A short query key is therefore not an address that is globally unique across the application. &lt;a href="https://docs.cabloy.com/frontend/model-resource-internals-deep-dive" rel="noopener noreferrer"&gt;Model Resource Internals Deep Dive&lt;/a&gt; and &lt;a href="https://docs.cabloy.com/frontend/model-state-guide" rel="noopener noreferrer"&gt;Model State Guide&lt;/a&gt; explain this owner/state identity layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coordinate seven: Schema metadata selects concrete UI capabilities
&lt;/h2&gt;

&lt;p&gt;A generic Resource Page stays deliberately thin: it loads the current Resource Schema, reads &lt;code&gt;schemaRow?.rest?.blocks&lt;/code&gt;, and renders what the block metadata specifies. The page shell owns the question, “what is the current context?” Schema and metadata answer, “which blocks should this Resource compose in this scene?”&lt;/p&gt;

&lt;p&gt;Where does &lt;code&gt;schemaRow?.rest?.blocks&lt;/code&gt; come from? It is not an array assembled ad hoc by a frontend page. For the list page, the current Student sample defines it in the &lt;strong&gt;list-row DTO&lt;/strong&gt; through &lt;code&gt;@Dto({ blocks })&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;IDecoratorDtoOptions&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vona-module-a-web&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;$Dto&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vona-module-a-orm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Dto&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vona-module-a-web&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ZovaRender&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zova-rest-cabloy-basic-admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ModelStudent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../model/student.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Dto&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IDecoratorDtoOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;(&lt;span class="si"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;ZovaRender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;basic-page:blockPage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nx"&gt;ZovaRender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;basic-page:blockFilter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;formFieldLayout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;inline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="c1"&gt;// The full sample also declares the filter form layout here.&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="nx"&gt;ZovaRender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;basic-page:blockToolbarBulk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ZovaRender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tableActionBulk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;basic-table:actionCreate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="nx"&gt;ZovaRender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;basic-page:blockTable&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nx"&gt;ZovaRender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;basic-page:blockPager&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;)
export class DtoStudentSelectResItem extends $Dto.get(() =&amp;gt; ModelStudent) &lt;span class="si"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// List-row fields and each field’s ZovaRender.* metadata.&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code is deliberately abbreviated. It retains the important structure: the root &lt;code&gt;basic-page:blockPage&lt;/code&gt; composes registered frontend block resources for filtering, bulk toolbar actions, a table, and a pager. It does not mean every DTO should own these blocks, nor that the frontend implementations of those blocks live in Vona. The DTO declares, in backend-generated contract metadata, which rendering capabilities the current Resource list page should select.&lt;/p&gt;

&lt;p&gt;The full chain that explains &lt;code&gt;blocks&lt;/code&gt; is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DtoStudentSelectResItem’s @Dto({ blocks })
  → Vona merges blocks into the DTO OpenAPI metadata as rest.blocks
  → the GET select response’s data.list.items references the row DTO Schema
  → ModelResource.apiSchemasSelect.row resolves as schemaRow
  → schemaRow?.rest?.blocks
  → ControllerPageResource calls ZovaJsx.render(...) for each block
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words, &lt;code&gt;schemaRow&lt;/code&gt; is the current select-response row Schema, and &lt;code&gt;rest.blocks&lt;/code&gt; is the page-composition declaration attached to that Schema. &lt;code&gt;basic-page:blockTable&lt;/code&gt; in &lt;code&gt;ZovaRender.block('basic-page:blockTable')&lt;/code&gt; is another module-qualified render-resource identity, resolved by an implementation registered on the Zova side. The backend selects it; the frontend executes it. Neither side directly calls the other side’s objects across processes.&lt;/p&gt;

&lt;p&gt;Cabloy resource addressing therefore does not stop at an API. Table cells, form fields, image scenes, and Behaviors can also be selected through module-qualified, scene-aware frontend resource identities. Field metadata can, for example, refer to &lt;code&gt;training-student:formFieldLevel&lt;/code&gt;, which a registered frontend capability resolves and renders.&lt;/p&gt;

&lt;p&gt;This does not mean backend code executes frontend components. More precisely, backend contracts or metadata can declare which frontend rendering resource should be selected at a point; the frontend still owns the renderer implementation and runtime. Frontend-owned facts such as routes, components, icons, table cells, and form fields also have a reverse contract handoff: refresh the frontend metadata/build output first, then let Vona consume the synchronized local dependency result. &lt;a href="https://docs.cabloy.com/fullstack/frontend-metadata-to-backend" rel="noopener noreferrer"&gt;Frontend Metadata Back to Backend&lt;/a&gt; describes that reverse chain.&lt;/p&gt;

&lt;p&gt;The two directions are distinct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Forward: Vona Controller / DTO / Entity
  → OpenAPI
  → Zova SDK / Schema / API consumers

Reverse: Zova route / component / renderer / metadata
  → frontend metadata or build output
  → Vona-side metadata, tooling, and type consumers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Together they support full-stack collaboration, but neither means that saving arbitrary source on one side automatically teaches the other side everything. Each chain has its own source of truth, generation step, shared handoff, and verification points.&lt;/p&gt;

&lt;h2&gt;
  
  
  IoC’s actual position on this map
&lt;/h2&gt;

&lt;p&gt;Saying Cabloy is “more than IoC” does not diminish IoC. On the contrary, IoC is the runtime foundation that lets many of these coordinates work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a Bean’s container scope determines lifecycle and sharing boundaries;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@Use&lt;/code&gt; and &lt;code&gt;beanFullName&lt;/code&gt; support explicit, resolvable collaboration;&lt;/li&gt;
&lt;li&gt;Scope lets everyday business code access capabilities through a module-resource facade;&lt;/li&gt;
&lt;li&gt;parameterized injection lets generic &lt;code&gt;ModelResource&lt;/code&gt; be reused with the current Resource selector;&lt;/li&gt;
&lt;li&gt;lifecycle and Model query/cache mechanisms turn “a resource was found” into “this owner is responsible for its state.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But treating this only as convenient dependency injection misses the larger collaboration structure. Business domains and modules assign capabilities an owner first; Bean/Scope gives runtime capabilities a resolution boundary; Resource and OpenAPI project a data contract for transport; route parameters bring context into a page; selectors and effective query keys isolate state to the current Resource.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;IoC makes a capability obtainable. Resource addressing makes that capability retain its business meaning after it crosses modules, contracts, routes, state, and rendering.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  When this is useful—and when it should not be forced
&lt;/h2&gt;

&lt;p&gt;A multi-layer addressing system has a learning cost. Developers need to distinguish suites, modules, Beans, scenes, Resources, routes, selectors, Schemas, and generated artifacts. When debugging, they must begin from the correct coordinate instead of only searching for a component file.&lt;/p&gt;

&lt;p&gt;It is usually most valuable for systems that evolve over time, have many modules or consumers, or need a clear contract loop:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Prefer&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One-off page-local UI state&lt;/td&gt;
&lt;td&gt;Page/controller-local state&lt;/td&gt;
&lt;td&gt;There is no need to introduce reusable Resource semantics.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reusable asynchronous, persistent, or cross-page state&lt;/td&gt;
&lt;td&gt;Model Bean&lt;/td&gt;
&lt;td&gt;The state needs an explicit identity, lifecycle, and cache/persistence boundary.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An Admin-style entity with Schemas, permissions, forms, and list/item lifecycle&lt;/td&gt;
&lt;td&gt;Existing &lt;code&gt;ModelResource&lt;/code&gt; or a thin facade&lt;/td&gt;
&lt;td&gt;Reuse one Resource owner and a unified invalidation policy.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A custom operation on the same Resource&lt;/td&gt;
&lt;td&gt;A semantic facade on the existing owner&lt;/td&gt;
&lt;td&gt;Avoid competing request, state, and cache owners.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A distinct Web self-service experience&lt;/td&gt;
&lt;td&gt;Dedicated API/DTOs, Model, and page state&lt;/td&gt;
&lt;td&gt;One persisted Resource does not require one frontend experience.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A cross-cutting rule around an existing rendering target&lt;/td&gt;
&lt;td&gt;Behavior&lt;/td&gt;
&lt;td&gt;This is a rendering-boundary concern, not business Resource ownership.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The final two rows are especially important. One business Resource can share a domain and persistence boundary between Admin and Web while using different API/DTO contracts, server-side scopes, state owners, and page architecture. Good resource addressing does not force everything into one generic CRUD page. It lets each boundary state clearly who it serves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trace one Resource with seven questions
&lt;/h2&gt;

&lt;p&gt;To validate this mental model in an existing project, choose any registered Resource and follow it through these questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which suite/module owns it?&lt;/li&gt;
&lt;li&gt;What is its business Resource identity?&lt;/li&gt;
&lt;li&gt;Which Controller/DTO/Entity brings it into the backend contract?&lt;/li&gt;
&lt;li&gt;Which menu, route name, and parameters bring it into a page?&lt;/li&gt;
&lt;li&gt;Which Bean/Model owner receives it with which selector?&lt;/li&gt;
&lt;li&gt;After bootstrap, where do &lt;code&gt;resourceApi&lt;/code&gt;, Schemas, permissions, and render metadata come from?&lt;/li&gt;
&lt;li&gt;Should this change follow the forward chain, the reverse chain, or first investigate generated/local dependency drift?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The point is not to memorize more names. It is to break “why does this page work?” into a sequence of verifiable resolutions. Instead of asking only, “where is this component?”, you can ask: &lt;strong&gt;what is this Resource’s business identity, how is it mapped at this layer, and who owns its state and meaning?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Cabloy does not rule every layer with one global ID. A more accurate description is that it uses the module as an important naming anchor and connects multiple address spaces—suite/module ownership, Bean scenes, Scope, business Resources, OpenAPI, route parameters, Model selectors, query caches, and render metadata—into a traceable full-stack chain.&lt;/p&gt;

&lt;p&gt;IoC is one runtime foundation of that chain, but not the whole chain. The engineering benefit is that a business capability can cross backend and frontend, generated artifacts, and distinct runtimes without degrading into scattered URLs, repeated imports, hand-written requests, and disconnected state. Each layer retains its own responsibility and address while still mapping to the same business meaning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Concepts and workflows
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.cabloy.com/fullstack/suites-and-modules" rel="noopener noreferrer"&gt;Cabloy: Suites and Modules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.cabloy.com/backend/foundation" rel="noopener noreferrer"&gt;Cabloy: Vona Backend Foundation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.cabloy.com/frontend/ioc-and-beans" rel="noopener noreferrer"&gt;Cabloy: Zova IoC and Beans&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.cabloy.com/frontend/module-scope" rel="noopener noreferrer"&gt;Cabloy: Zova Module Scope&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.cabloy.com/fullstack/openapi-to-sdk" rel="noopener noreferrer"&gt;Cabloy: Backend OpenAPI to Frontend SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.cabloy.com/fullstack/frontend-metadata-to-backend" rel="noopener noreferrer"&gt;Cabloy: Frontend Metadata Back to Backend&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.cabloy.com/frontend/model-resource-internals-deep-dive" rel="noopener noreferrer"&gt;Cabloy: Model Resource Internals Deep Dive&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Source samples verified for this article
&lt;/h3&gt;

&lt;p&gt;The implementation-level statements in this article were verified against Cabloy Basic revision &lt;a href="https://github.com/cabloy/cabloy/tree/e8994f63dfffc7be2657cc4c7cb6687a89b63dec" rel="noopener noreferrer"&gt;&lt;code&gt;e8994f63dfffc7be2657cc4c7cb6687a89b63dec&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/e8994f63dfffc7be2657cc4c7cb6687a89b63dec/vona/src/suite/a-training/modules/training-student/src/controller/student.ts" rel="noopener noreferrer"&gt;Student Resource Controller&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/e8994f63dfffc7be2657cc4c7cb6687a89b63dec/vona/src/suite/a-training/modules/training-student/src/entity/student.tsx" rel="noopener noreferrer"&gt;Student Entity field-contract sample&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/e8994f63dfffc7be2657cc4c7cb6687a89b63dec/vona/src/suite/a-training/modules/training-student/src/bean/ssrMenu.student.ts" rel="noopener noreferrer"&gt;Student SSR menu&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/e8994f63dfffc7be2657cc4c7cb6687a89b63dec/vona/src/suite-vendor/a-vona/modules/a-web/src/lib/decorator/bean.ts" rel="noopener noreferrer"&gt;Vona Resource-to-route mapping&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/e8994f63dfffc7be2657cc4c7cb6687a89b63dec/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/routes.ts" rel="noopener noreferrer"&gt;Generic Resource routes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/e8994f63dfffc7be2657cc4c7cb6687a89b63dec/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/resource/controller.tsx" rel="noopener noreferrer"&gt;Generic Resource page controller&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/e8994f63dfffc7be2657cc4c7cb6687a89b63dec/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts" rel="noopener noreferrer"&gt;ModelResource owner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/e8994f63dfffc7be2657cc4c7cb6687a89b63dec/vona/src/suite/a-training/modules/training-student/src/dto/studentSelectResItem.tsx" rel="noopener noreferrer"&gt;Student select-row DTO blocks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/cabloy/cabloy/blob/e8994f63dfffc7be2657cc4c7cb6687a89b63dec/vona/src/suite-vendor/a-vona/modules/a-web/src/lib/decorator/bean.ts" rel="noopener noreferrer"&gt;Vona DTO blocks to OpenAPI metadata bridge&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ioc</category>
      <category>fullstack</category>
      <category>openapi</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
