cabloy 5.1.50 → 5.1.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/cabloy-backend-scaffold/SKILL.md +207 -0
- package/.claude/skills/cabloy-backend-scaffold/evals/evals.json +29 -0
- package/.claude/skills/cabloy-backend-scaffold/references/backend-thread-map.md +52 -0
- package/.claude/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +39 -0
- package/.claude/skills/cabloy-contract-loop/SKILL.md +201 -0
- package/.claude/skills/cabloy-contract-loop/evals/evals.json +29 -0
- package/.claude/skills/cabloy-contract-loop/references/contract-loop-map.md +47 -0
- package/.claude/skills/cabloy-contract-loop/references/verification-checklist.md +32 -0
- package/.claude/skills/cabloy-frontend-scaffold/SKILL.md +230 -0
- package/.claude/skills/cabloy-frontend-scaffold/evals/evals.json +35 -0
- package/.claude/skills/cabloy-frontend-scaffold/references/follow-up-checklist.md +41 -0
- package/.claude/skills/cabloy-frontend-scaffold/references/frontend-thread-map.md +54 -0
- package/.claude/skills/cabloy-workflow/SKILL.md +288 -0
- package/.claude/skills/cabloy-workflow/evals/evals.json +35 -0
- package/.claude/skills/cabloy-workflow/references/cli-strategy.md +39 -0
- package/.claude/skills/cabloy-workflow/references/edition-detection.md +29 -0
- package/.github/workflows/docs-pages.yml +54 -0
- package/.gitignore +1 -0
- package/CHANGELOG.md +29 -0
- package/CLAUDE.md +59 -0
- package/README.md +137 -0
- package/cabloy-docs/.vitepress/config.mjs +222 -0
- package/cabloy-docs/.vitepress/public/CNAME +1 -0
- package/cabloy-docs/.vitepress/theme/custom.css +64 -0
- package/cabloy-docs/.vitepress/theme/edition-badges.md +5 -0
- package/cabloy-docs/.vitepress/theme/index.js +5 -0
- package/cabloy-docs/ai/class-placement-rule.md +138 -0
- package/cabloy-docs/ai/cli-for-agents.md +56 -0
- package/cabloy-docs/ai/cli-to-skill-map.md +165 -0
- package/cabloy-docs/ai/docs-skills-rules-mapping.md +167 -0
- package/cabloy-docs/ai/edition-detection.md +30 -0
- package/cabloy-docs/ai/future-skill-roadmap.md +135 -0
- package/cabloy-docs/ai/global-bean-lookup.md +157 -0
- package/cabloy-docs/ai/introduction.md +62 -0
- package/cabloy-docs/ai/playbook-backend-module.md +111 -0
- package/cabloy-docs/ai/playbook-contract-regeneration.md +100 -0
- package/cabloy-docs/ai/playbook-frontend-page.md +99 -0
- package/cabloy-docs/ai/playbook-metadata-refresh.md +67 -0
- package/cabloy-docs/ai/repo-guidance.md +58 -0
- package/cabloy-docs/ai/rules-and-config.md +29 -0
- package/cabloy-docs/ai/skills.md +35 -0
- package/cabloy-docs/ai/verification.md +30 -0
- package/cabloy-docs/backend/aop-overview.md +128 -0
- package/cabloy-docs/backend/auth-guide.md +151 -0
- package/cabloy-docs/backend/backend-essentials.md +128 -0
- package/cabloy-docs/backend/broadcast-guide.md +138 -0
- package/cabloy-docs/backend/cache-guide.md +70 -0
- package/cabloy-docs/backend/captcha-guide.md +162 -0
- package/cabloy-docs/backend/cli.md +173 -0
- package/cabloy-docs/backend/config-guide.md +249 -0
- package/cabloy-docs/backend/controller-aop-guide.md +270 -0
- package/cabloy-docs/backend/controller-guide.md +347 -0
- package/cabloy-docs/backend/crud-workflow.md +118 -0
- package/cabloy-docs/backend/dto-guide.md +161 -0
- package/cabloy-docs/backend/dto-infer-generation.md +153 -0
- package/cabloy-docs/backend/dynamic-datasource-guide.md +70 -0
- package/cabloy-docs/backend/election-guide.md +141 -0
- package/cabloy-docs/backend/entity-guide.md +150 -0
- package/cabloy-docs/backend/error-guide.md +108 -0
- package/cabloy-docs/backend/event-guide.md +183 -0
- package/cabloy-docs/backend/external-aop-guide.md +149 -0
- package/cabloy-docs/backend/field-indexes.md +79 -0
- package/cabloy-docs/backend/foundation.md +281 -0
- package/cabloy-docs/backend/i18n-guide.md +211 -0
- package/cabloy-docs/backend/internal-aop-guide.md +181 -0
- package/cabloy-docs/backend/introduction.md +95 -0
- package/cabloy-docs/backend/jwt-guide.md +276 -0
- package/cabloy-docs/backend/logger-guide.md +223 -0
- package/cabloy-docs/backend/mail-guide.md +189 -0
- package/cabloy-docs/backend/menu-guide.md +80 -0
- package/cabloy-docs/backend/migration-and-changes.md +192 -0
- package/cabloy-docs/backend/model-guide.md +274 -0
- package/cabloy-docs/backend/multi-database-datasource.md +171 -0
- package/cabloy-docs/backend/multi-instance-and-instance-resolution.md +196 -0
- package/cabloy-docs/backend/openapi-guide.md +118 -0
- package/cabloy-docs/backend/orm-aggregate-group-guide.md +210 -0
- package/cabloy-docs/backend/orm-configuration-guide.md +165 -0
- package/cabloy-docs/backend/orm-guide.md +109 -0
- package/cabloy-docs/backend/orm-mutation-guide.md +195 -0
- package/cabloy-docs/backend/orm-select-guide.md +243 -0
- package/cabloy-docs/backend/queue-guide.md +271 -0
- package/cabloy-docs/backend/quickstart.md +141 -0
- package/cabloy-docs/backend/redis-guide.md +70 -0
- package/cabloy-docs/backend/redlock-guide.md +60 -0
- package/cabloy-docs/backend/relations-guide.md +250 -0
- package/cabloy-docs/backend/runtime-and-flavors.md +304 -0
- package/cabloy-docs/backend/schedule-guide.md +81 -0
- package/cabloy-docs/backend/scripts.md +116 -0
- package/cabloy-docs/backend/serialization-guide.md +192 -0
- package/cabloy-docs/backend/service-guide.md +166 -0
- package/cabloy-docs/backend/sharding-guide.md +49 -0
- package/cabloy-docs/backend/startup-guide.md +326 -0
- package/cabloy-docs/backend/transaction-guide.md +82 -0
- package/cabloy-docs/backend/unit-testing.md +209 -0
- package/cabloy-docs/backend/upload-guide.md +160 -0
- package/cabloy-docs/backend/user-access-guide.md +157 -0
- package/cabloy-docs/backend/validation-guide.md +80 -0
- package/cabloy-docs/backend/worker-guide.md +59 -0
- package/cabloy-docs/editions/cabloy-basic.md +25 -0
- package/cabloy-docs/editions/cabloy-start.md +24 -0
- package/cabloy-docs/editions/detection.md +31 -0
- package/cabloy-docs/editions/overview.md +44 -0
- package/cabloy-docs/frontend/api-guide.md +93 -0
- package/cabloy-docs/frontend/api-schema-guide.md +43 -0
- package/cabloy-docs/frontend/app-startup-guide.md +185 -0
- package/cabloy-docs/frontend/cli.md +78 -0
- package/cabloy-docs/frontend/component-guide.md +105 -0
- package/cabloy-docs/frontend/component-props-guide.md +97 -0
- package/cabloy-docs/frontend/component-v-model-guide.md +110 -0
- package/cabloy-docs/frontend/css-in-js-guide.md +151 -0
- package/cabloy-docs/frontend/design-principles.md +55 -0
- package/cabloy-docs/frontend/environment-config-guide.md +250 -0
- package/cabloy-docs/frontend/foundation.md +57 -0
- package/cabloy-docs/frontend/generic-component-guide.md +35 -0
- package/cabloy-docs/frontend/icon-engine-guide.md +88 -0
- package/cabloy-docs/frontend/introduction.md +32 -0
- package/cabloy-docs/frontend/ioc-and-beans.md +211 -0
- package/cabloy-docs/frontend/mock-guide.md +109 -0
- package/cabloy-docs/frontend/model-architecture.md +87 -0
- package/cabloy-docs/frontend/model-state-guide.md +70 -0
- package/cabloy-docs/frontend/module-scope.md +168 -0
- package/cabloy-docs/frontend/modules-and-suites.md +179 -0
- package/cabloy-docs/frontend/navigation-guards-guide.md +68 -0
- package/cabloy-docs/frontend/openapi-sdk-guide.md +102 -0
- package/cabloy-docs/frontend/page-guide.md +223 -0
- package/cabloy-docs/frontend/page-params-guide.md +87 -0
- package/cabloy-docs/frontend/page-query-guide.md +96 -0
- package/cabloy-docs/frontend/page-route-guide.md +147 -0
- package/cabloy-docs/frontend/quickstart.md +201 -0
- package/cabloy-docs/frontend/route-alias-guide.md +61 -0
- package/cabloy-docs/frontend/scripts.md +86 -0
- package/cabloy-docs/frontend/sdk-guide.md +45 -0
- package/cabloy-docs/frontend/server-data.md +74 -0
- package/cabloy-docs/frontend/ssr-client-only.md +40 -0
- package/cabloy-docs/frontend/ssr-env.md +51 -0
- package/cabloy-docs/frontend/ssr-init-data.md +46 -0
- package/cabloy-docs/frontend/ssr-overview.md +48 -0
- package/cabloy-docs/frontend/ssr-seo-meta.md +52 -0
- package/cabloy-docs/frontend/system-startup-guide.md +186 -0
- package/cabloy-docs/frontend/theme-guide.md +154 -0
- package/cabloy-docs/frontend/zod-guide.md +161 -0
- package/cabloy-docs/fullstack/edition-collaboration-differences.md +61 -0
- package/cabloy-docs/fullstack/frontend-metadata-to-backend.md +64 -0
- package/cabloy-docs/fullstack/introduction.md +69 -0
- package/cabloy-docs/fullstack/openapi-to-sdk.md +116 -0
- package/cabloy-docs/fullstack/quickstart.md +86 -0
- package/cabloy-docs/fullstack/vona-zova-integration.md +86 -0
- package/cabloy-docs/index.md +73 -0
- package/cabloy-docs/package.json +16 -0
- package/cabloy-docs/pnpm-lock.yaml +1607 -0
- package/cabloy-docs/reference/backend-directory-structure.md +88 -0
- package/cabloy-docs/reference/cli-reference.md +49 -0
- package/cabloy-docs/reference/glossary.md +38 -0
- package/cabloy-docs/reference/package-map.md +105 -0
- package/cabloy-docs/reference/repo-scripts.md +36 -0
- package/package.json +4 -1
- package/scripts/init.ts +12 -0
- package/scripts/upgrade.ts +31 -3
- package/vona/README.md +3 -3
- package/vona/README.zh-CN.md +4 -4
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-datasharding/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-datasharding/src/bean/summerCache.datasourceWrite.ts +2 -2
- package/vona/src/suite-vendor/a-cabloy/package.json +1 -1
- package/vona/src/suite-vendor/a-captcha/modules/a-captcha/package.json +1 -1
- package/vona/src/suite-vendor/a-captcha/modules/a-captcha/src/bean/cacheRedis.captcha.ts +2 -2
- package/vona/src/suite-vendor/a-captcha/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-bean/cli/bean/metadata/generate.ts +5 -6
- package/vona/src/suite-vendor/a-vona/modules/a-bean/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-cache/cli/cacheMem/boilerplate/{{sceneName}}.{{beanName}}.ts_ +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-cache/cli/cacheRedis/boilerplate/{{sceneName}}.{{beanName}}.ts_ +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-cache/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-cache/src/.metadata/index.ts +13 -2
- package/vona/src/suite-vendor/a-vona/modules/a-cache/src/bean/bean.cache.ts +6 -7
- package/vona/src/suite-vendor/a-vona/modules/a-cache/src/{bean/bean.cacheMemBase.ts → service/cacheMemBase_.ts} +3 -3
- package/vona/src/suite-vendor/a-vona/modules/a-cache/src/{bean/bean.cacheRedisBase.ts → service/cacheRedisBase_.ts} +3 -3
- package/vona/src/suite-vendor/a-vona/modules/a-mailconfirm/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-mailconfirm/src/bean/cacheRedis.emailConfirm.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-mailconfirm/src/bean/cacheRedis.passwordReset.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-openapi/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-openapi/src/bean/summerCache.json.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-orm/cli/databaseDialect/boilerplate/{{sceneName}}.{{beanName}}.ts_ +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/.metadata/index.ts +4 -3
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/bean/bean.database.ts +3 -3
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/bean/bean.model.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/bean/schedule.softDeletionPrune.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/common/utils.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/index.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/{bean → lib}/bean.model/bean.model_cache.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/{bean → lib}/bean.model/bean.model_meta.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/{bean → lib}/bean.modelBase.ts +0 -5
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoAggregate.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoCreate.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoGet.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoGroup.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoMutate.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoSelectAndCount.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/dto/dtoUpdate.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/index.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/modelCacheBase.ts +3 -3
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/relations.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/relationsDynamic.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/relationsMutate.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/relationsStatic.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/cacheEntity_.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/cacheQuery_.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/database.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/{bean/bean.databaseDialectBase.ts → service/databaseDialectBase_.ts} +38 -41
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/db_.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/relations_.ts +3 -3
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/database.ts +0 -5
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/dto/dtoGet.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/dto/dtoMutate.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/dto/dtoSelectAndCount.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/model.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/modelAggr.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/modelCount.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/modelGeneral.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/modelGroup.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/modelIncrement.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/relations.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/relationsColumns.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/relationsDef.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/relationsDefDynamic.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/relationsDefMutate.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/relationsMutate.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/relationsTables.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/bean/databaseDialect.betterSqlite3.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/bean/databaseDialect.mysql.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/bean/databaseDialect.pg.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/bean.permission.ts +109 -40
- package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/summerCache.permission.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-startup/src/bean/cacheRedis.startupDebounce.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-summer/cli/boilerplate/{{sceneName}}.{{beanName}}.ts_ +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-summer/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-summer/src/.metadata/index.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-summer/src/bean/bean.summer.ts +3 -3
- package/vona/src/suite-vendor/a-vona/modules/a-summer/src/service/localMem_.ts +3 -3
- package/vona/src/suite-vendor/a-vona/modules/a-summer/src/service/localRedis_.ts +3 -3
- package/vona/src/suite-vendor/a-vona/modules/a-summer/src/{bean/bean.summerCacheBase.ts → service/summerCacheBase_.ts} +3 -3
- package/vona/src/suite-vendor/a-vona/modules/a-swagger/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-swagger/src/bean/summerCache.rapidoc.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-swagger/src/bean/summerCache.swagger.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-user/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-user/src/bean/cacheRedis.authToken.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-worker/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-worker/src/bean/cacheRedis.workerAlive.ts +2 -2
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/README.md +4 -4
- package/zova/README.zh-CN.md +4 -4
- /package/vona/src/suite-vendor/a-vona/modules/a-orm/src/{bean → lib}/bean.model/bean.model_crud.ts +0 -0
- /package/vona/src/suite-vendor/a-vona/modules/a-orm/src/{bean → lib}/bean.model/bean.model_crud_inner.ts +0 -0
- /package/vona/src/suite-vendor/a-vona/modules/a-orm/src/{bean → lib}/bean.model/bean.model_crud_table.ts +0 -0
- /package/vona/src/suite-vendor/a-vona/modules/a-orm/src/{bean → lib}/bean.model/bean.model_knex.ts +0 -0
- /package/vona/src/suite-vendor/a-vona/modules/a-orm/src/{bean → lib}/bean.model/bean.model_utils.ts +0 -0
- /package/vona/src/suite-vendor/a-vona/modules/a-orm/src/{bean → lib}/bean.model/bean.model_view.ts +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# CRUD Workflow
|
|
2
|
+
|
|
3
|
+
This guide explains the Vona CRUD generator workflow in the Cabloy monorepo.
|
|
4
|
+
|
|
5
|
+
## Why this page matters
|
|
6
|
+
|
|
7
|
+
CRUD is one of the clearest places where Cabloy’s CLI-first philosophy pays off.
|
|
8
|
+
|
|
9
|
+
Instead of creating controller, service, model, entity, DTO, metadata, locale, and test files by hand, Vona already provides generators that create the initial backend thread.
|
|
10
|
+
|
|
11
|
+
## Generate a CRUD skeleton
|
|
12
|
+
|
|
13
|
+
Example: generate a CRUD workflow for `student` in module `demo-student`.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm run vona :tools:crud student -- --module=demo-student
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
A lighter variant also exists:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm run vona :tools:crudBasic student -- --module=demo-student
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This is important because the repo already encodes both the full CRUD thread and a lighter CRUD-basic workflow in the CLI surface.
|
|
26
|
+
|
|
27
|
+
## Generated structure
|
|
28
|
+
|
|
29
|
+
The generator creates a connected set of files, typically including:
|
|
30
|
+
|
|
31
|
+
- controller
|
|
32
|
+
- service
|
|
33
|
+
- model
|
|
34
|
+
- entity
|
|
35
|
+
- create/update DTOs
|
|
36
|
+
- meta version and index files
|
|
37
|
+
- locale files
|
|
38
|
+
- tests
|
|
39
|
+
|
|
40
|
+
This is exactly why this generator should be the default starting point. It gives a consistent starting shape across the backend thread.
|
|
41
|
+
|
|
42
|
+
A practical generated-output checklist usually includes:
|
|
43
|
+
|
|
44
|
+
- controller
|
|
45
|
+
- service
|
|
46
|
+
- model
|
|
47
|
+
- entity
|
|
48
|
+
- create/update DTOs
|
|
49
|
+
- `meta.version`
|
|
50
|
+
- locale assets
|
|
51
|
+
- test file
|
|
52
|
+
- package-version update for the next schema step
|
|
53
|
+
|
|
54
|
+
That checklist is useful because it makes the generated thread easier to inspect after the CLI run instead of treating CRUD generation as a black box.
|
|
55
|
+
|
|
56
|
+
## The generated backend thread
|
|
57
|
+
|
|
58
|
+
The CRUD generator is not a shortcut around the architecture. It instantiates the same backend contract loop documented elsewhere.
|
|
59
|
+
|
|
60
|
+
A practical thread is:
|
|
61
|
+
|
|
62
|
+
1. controller exposes the HTTP contract
|
|
63
|
+
2. service owns orchestration
|
|
64
|
+
3. model owns persistence behavior
|
|
65
|
+
4. entity defines the field/data contract
|
|
66
|
+
5. DTOs define operation-specific request/response contracts
|
|
67
|
+
6. meta.version handles schema lifecycle
|
|
68
|
+
7. tests verify the resulting contract through action execution
|
|
69
|
+
|
|
70
|
+
Read this guide together with:
|
|
71
|
+
|
|
72
|
+
- [Controller Guide](/backend/controller-guide)
|
|
73
|
+
- [Service Guide](/backend/service-guide)
|
|
74
|
+
- [Model Guide](/backend/model-guide)
|
|
75
|
+
- [Entity Guide](/backend/entity-guide)
|
|
76
|
+
- [DTO Guide](/backend/dto-guide)
|
|
77
|
+
- [Migration and Changes](/backend/migration-and-changes)
|
|
78
|
+
- [Unit Testing](/backend/unit-testing)
|
|
79
|
+
|
|
80
|
+
## Recommended workflow
|
|
81
|
+
|
|
82
|
+
1. run the CRUD generator
|
|
83
|
+
2. inspect the generated files
|
|
84
|
+
3. refine entity, DTO, model, service, and controller behavior for the real business case
|
|
85
|
+
4. verify routes, model behavior, migration behavior, and tests
|
|
86
|
+
|
|
87
|
+
A practical expectation is that the generated test should already help verify the full contract thread rather than only file existence. In other words, generation should leave you with something that can immediately participate in CRUD-oriented action testing, migration verification, and later OpenAPI/frontend contract refinement.
|
|
88
|
+
|
|
89
|
+
This is the preferred path because it preserves framework conventions first, then applies domain-specific refinement second.
|
|
90
|
+
|
|
91
|
+
## When to keep generated defaults vs refine them manually
|
|
92
|
+
|
|
93
|
+
A practical rule is:
|
|
94
|
+
|
|
95
|
+
- keep generated defaults when the backend thread already matches the business shape
|
|
96
|
+
- refine the generated code when response contracts, DTO behavior, controller metadata, model behavior, or test flow need stronger domain-specific semantics
|
|
97
|
+
- avoid replacing the generated thread wholesale unless the framework shape truly does not fit the use case
|
|
98
|
+
|
|
99
|
+
## Relationship to DTO inference and OpenAPI
|
|
100
|
+
|
|
101
|
+
The generated thread is also part of the broader contract-emission path.
|
|
102
|
+
|
|
103
|
+
That means generated entity, DTO, controller, and validation structure can feed:
|
|
104
|
+
|
|
105
|
+
- backend OpenAPI output
|
|
106
|
+
- DTO inference and generation
|
|
107
|
+
- frontend SDK generation
|
|
108
|
+
|
|
109
|
+
For the cross-stack side of this loop, also see [Backend OpenAPI to Frontend SDK](/fullstack/openapi-to-sdk).
|
|
110
|
+
|
|
111
|
+
## Generated workflow checklist
|
|
112
|
+
|
|
113
|
+
When you see a request like “create a student CRUD” or “scaffold backend resources,” the correct default should be:
|
|
114
|
+
|
|
115
|
+
1. inspect the Vona CLI
|
|
116
|
+
2. use `:tools:crud` or `:tools:crudBasic` if one matches the need
|
|
117
|
+
3. modify the generated output instead of hand-building the whole thread from scratch
|
|
118
|
+
4. verify the resulting migration, controller, and test path instead of stopping at file creation
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# DTO Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how DTOs work in Vona within the Cabloy monorepo.
|
|
4
|
+
|
|
5
|
+
## Why DTOs matter
|
|
6
|
+
|
|
7
|
+
DTOs are not only transport classes. In Vona, they are part of one contract system shared across:
|
|
8
|
+
|
|
9
|
+
- validation
|
|
10
|
+
- OpenAPI metadata
|
|
11
|
+
- serializer-facing response shape
|
|
12
|
+
- model-aware DTO inference
|
|
13
|
+
- frontend-facing generated contracts
|
|
14
|
+
|
|
15
|
+
That is why DTO design should be treated as a framework concern instead of only a controller-local convenience.
|
|
16
|
+
|
|
17
|
+
## Create a DTO
|
|
18
|
+
|
|
19
|
+
Example: create a DTO named `studentCreate` in module `demo-student`.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm run vona :create:bean dto studentCreate -- --module=demo-student
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## DTO definition
|
|
26
|
+
|
|
27
|
+
Representative pattern:
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
@Dto<IDtoOptionsStudentCreate>()
|
|
31
|
+
export class DtoStudentCreate {}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## DTOs in the backend contract loop
|
|
35
|
+
|
|
36
|
+
DTOs are the most explicit named contracts in the backend contract loop.
|
|
37
|
+
|
|
38
|
+
A useful split is:
|
|
39
|
+
|
|
40
|
+
- entities define reusable field/data structure close to persistence
|
|
41
|
+
- DTOs define explicit request/response contract artifacts
|
|
42
|
+
- inferred DTOs reduce duplication when the model/query shape is already strong enough
|
|
43
|
+
|
|
44
|
+
Read this guide together with:
|
|
45
|
+
|
|
46
|
+
- [Entity Guide](/backend/entity-guide)
|
|
47
|
+
- [DTO Infer and Generation](/backend/dto-infer-generation)
|
|
48
|
+
- [Validation Guide](/backend/validation-guide)
|
|
49
|
+
- [OpenAPI Guide](/backend/openapi-guide)
|
|
50
|
+
|
|
51
|
+
## `@Api.field`
|
|
52
|
+
|
|
53
|
+
DTO field definitions use the same `@Api.field` mental model as entities.
|
|
54
|
+
|
|
55
|
+
That means DTOs can express:
|
|
56
|
+
|
|
57
|
+
- validation rules
|
|
58
|
+
- field metadata
|
|
59
|
+
- OpenAPI-facing schema information
|
|
60
|
+
- serializer-oriented response metadata when needed
|
|
61
|
+
|
|
62
|
+
For response-shaping behavior built on the same field metadata surface, see [Serialization Guide](/backend/serialization-guide).
|
|
63
|
+
|
|
64
|
+
Representative pattern:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
class DtoStudentCreate {
|
|
68
|
+
@Api.field(v.title($locale('Name')), v.min(3))
|
|
69
|
+
name: string;
|
|
70
|
+
|
|
71
|
+
@Api.field(v.title($locale('Description')), v.optional())
|
|
72
|
+
description?: string;
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## DTO options
|
|
77
|
+
|
|
78
|
+
Three especially important DTO option areas are:
|
|
79
|
+
|
|
80
|
+
- `independent`
|
|
81
|
+
- `openapi`
|
|
82
|
+
- `fields`
|
|
83
|
+
|
|
84
|
+
These options make DTOs configurable as reusable schema objects, not just local TypeScript classes.
|
|
85
|
+
|
|
86
|
+
A useful ownership rule is:
|
|
87
|
+
|
|
88
|
+
- DTO metadata defines the contract shape close to the class
|
|
89
|
+
- app config can still override broader DTO behavior
|
|
90
|
+
- inference tools can reduce how much hand-authored DTO code is needed
|
|
91
|
+
|
|
92
|
+
## App-config override support
|
|
93
|
+
|
|
94
|
+
DTO options can also be configured through app config.
|
|
95
|
+
|
|
96
|
+
That matters because the DTO layer participates in the broader framework configuration system instead of being fully hardcoded in one file.
|
|
97
|
+
|
|
98
|
+
## Mapped class tools
|
|
99
|
+
|
|
100
|
+
One of the most valuable DTO topics is reuse through mapped-class helpers.
|
|
101
|
+
|
|
102
|
+
Representative tools include:
|
|
103
|
+
|
|
104
|
+
- `$Class.pick`
|
|
105
|
+
- `$Class.partial`
|
|
106
|
+
- `$Class.omit`
|
|
107
|
+
- `$Class.mixin`
|
|
108
|
+
|
|
109
|
+
These let you derive DTOs from existing entities or DTOs instead of re-declaring the same field sets repeatedly.
|
|
110
|
+
|
|
111
|
+
## Operation-specific DTO thinking
|
|
112
|
+
|
|
113
|
+
A practical way to think about DTO families is by operation shape.
|
|
114
|
+
|
|
115
|
+
Common operation families include:
|
|
116
|
+
|
|
117
|
+
- create DTOs
|
|
118
|
+
- update DTOs
|
|
119
|
+
- get DTOs
|
|
120
|
+
- list-and-count DTOs
|
|
121
|
+
- query DTOs
|
|
122
|
+
- query-page DTOs
|
|
123
|
+
- aggregate DTOs
|
|
124
|
+
- group DTOs
|
|
125
|
+
|
|
126
|
+
Some of these are hand-authored DTO classes. Others are better expressed through Vona’s inference helpers.
|
|
127
|
+
|
|
128
|
+
The important point is not to force every operation shape into one generic DTO when the framework already distinguishes them more precisely.
|
|
129
|
+
|
|
130
|
+
## Explicit DTOs vs inferred DTOs
|
|
131
|
+
|
|
132
|
+
A practical split is:
|
|
133
|
+
|
|
134
|
+
- use explicit DTO classes when the contract needs a stable named artifact
|
|
135
|
+
- use inferred DTOs when the contract closely follows model structure or query shape
|
|
136
|
+
- wrap inferred DTOs in a named DTO class when reuse or discoverability becomes more important
|
|
137
|
+
|
|
138
|
+
For the inference side, see [DTO Infer and Generation](/backend/dto-infer-generation).
|
|
139
|
+
|
|
140
|
+
## Relationship to ORM and controller contracts
|
|
141
|
+
|
|
142
|
+
DTOs sit between backend data structure and backend API contracts.
|
|
143
|
+
|
|
144
|
+
That means DTO design should often be read together with:
|
|
145
|
+
|
|
146
|
+
- [Model Guide](/backend/model-guide)
|
|
147
|
+
- [Relations Guide](/backend/relations-guide)
|
|
148
|
+
- [ORM Aggregate and Group Guide](/backend/orm-aggregate-group-guide)
|
|
149
|
+
- [OpenAPI Guide](/backend/openapi-guide)
|
|
150
|
+
- [Controller Guide](/backend/controller-guide)
|
|
151
|
+
|
|
152
|
+
## Implementation checks for DTO changes
|
|
153
|
+
|
|
154
|
+
When creating DTOs:
|
|
155
|
+
|
|
156
|
+
1. prefer reuse through mapped-class helpers when the shape is derived from existing classes
|
|
157
|
+
2. keep DTO validation and OpenAPI concerns aligned through `@Api.field`
|
|
158
|
+
3. decide whether the contract should be an explicit DTO class or an inferred DTO
|
|
159
|
+
4. avoid re-declaring fields manually if Vona’s DTO-generation or class-derivation tools already solve the problem
|
|
160
|
+
5. treat DTO design as part of the contract between backend handlers, models, and frontend integration
|
|
161
|
+
6. choose explicit DTOs when named long-lived contracts matter, and inferred DTOs when the model/query shape already expresses the contract clearly
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# DTO Infer and Generation
|
|
2
|
+
|
|
3
|
+
This guide explains how DTO inference and generation work in Vona within the Cabloy monorepo.
|
|
4
|
+
|
|
5
|
+
## Why DTO inference matters
|
|
6
|
+
|
|
7
|
+
DTOs are essential for validation and OpenAPI metadata, but manually maintaining them becomes expensive and error-prone as models and relationships grow more complex.
|
|
8
|
+
|
|
9
|
+
Vona addresses that by dynamically inferring and generating DTOs from model structure and query shape.
|
|
10
|
+
|
|
11
|
+
## DTO tools
|
|
12
|
+
|
|
13
|
+
Several DTO-oriented tools are available, including:
|
|
14
|
+
|
|
15
|
+
- `$Dto.get`
|
|
16
|
+
- `$Dto.listAndCount`
|
|
17
|
+
- `$Dto.query`
|
|
18
|
+
- `$Dto.queryPage`
|
|
19
|
+
- `$Dto.create`
|
|
20
|
+
- `$Dto.update`
|
|
21
|
+
- `$Dto.aggregate`
|
|
22
|
+
- `$Dto.group`
|
|
23
|
+
|
|
24
|
+
These tools let DTOs emerge from model-aware structure instead of always being hand-authored from scratch.
|
|
25
|
+
|
|
26
|
+
## When each inferred DTO shape is useful
|
|
27
|
+
|
|
28
|
+
A practical mental model is:
|
|
29
|
+
|
|
30
|
+
- use `$Dto.get` for one-item read contracts
|
|
31
|
+
- use `$Dto.listAndCount` for paginated or list-plus-total contracts
|
|
32
|
+
- use `$Dto.query` and `$Dto.queryPage` for query-input or query-result patterns
|
|
33
|
+
- use `$Dto.create` and `$Dto.update` for write contracts derived from model structure
|
|
34
|
+
- use `$Dto.aggregate` and `$Dto.group` for summary-oriented result shapes
|
|
35
|
+
|
|
36
|
+
This matters because different ORM operations naturally produce different API contracts.
|
|
37
|
+
|
|
38
|
+
## When inference should replace handwritten DTOs
|
|
39
|
+
|
|
40
|
+
A practical rule is:
|
|
41
|
+
|
|
42
|
+
- prefer inferred DTOs when the contract closely follows model structure or query shape
|
|
43
|
+
- prefer explicit DTO classes when the contract is long-lived, heavily customized, or needs a strong named public identity
|
|
44
|
+
- wrap inferred DTOs into named DTO classes when reuse becomes more important than one-off convenience
|
|
45
|
+
|
|
46
|
+
This is one of the most important distinctions in the backend contract loop.
|
|
47
|
+
|
|
48
|
+
## Main-details example
|
|
49
|
+
|
|
50
|
+
A representative example uses an `Order -> Product` relation.
|
|
51
|
+
|
|
52
|
+
The key lesson is that when the return shape is richer than a simple entity array, an inferred DTO can capture the actual result shape more accurately than a hand-waved entity annotation.
|
|
53
|
+
|
|
54
|
+
Representative pattern:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
@Api.body(v.array($Dto.get(() => ModelOrder, { include: { products: true } })))
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This shows the dynamic DTO layer participating directly in controller return contracts.
|
|
61
|
+
|
|
62
|
+
Another useful inferred pattern is action-derived contract reuse:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
@Web.get('getUserDynamic')
|
|
66
|
+
@Api.body($Dto.get('test-vona:post'))
|
|
67
|
+
getPostDynamic() {}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
This is especially useful when a controller wants to expose the same contract shape that the model/action thread already defines elsewhere.
|
|
71
|
+
|
|
72
|
+
## Relation-aware inference
|
|
73
|
+
|
|
74
|
+
DTO inference becomes especially powerful when the result shape depends on relations.
|
|
75
|
+
|
|
76
|
+
Examples include:
|
|
77
|
+
|
|
78
|
+
- static relations loaded through `include`
|
|
79
|
+
- dynamic relations loaded through `with`
|
|
80
|
+
- grouped or aggregated related substructures
|
|
81
|
+
|
|
82
|
+
That means DTO inference should often be considered together with relation design rather than only after the fact.
|
|
83
|
+
|
|
84
|
+
A representative relation-aware response pattern is:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
@Api.body(v.array($Dto.get(() => ModelOrder, { include: { products: true } })))
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
A practical rule is:
|
|
91
|
+
|
|
92
|
+
- keep inference inline when one action needs one contract shape only once
|
|
93
|
+
- wrap the inferred DTO into a named DTO class when the same relation-aware shape becomes part of a reusable public contract
|
|
94
|
+
|
|
95
|
+
## Aggregate and group DTO inference
|
|
96
|
+
|
|
97
|
+
Summary-oriented ORM queries often benefit from inferred DTOs because the result shape is driven by `aggrs`, `groups`, and relation configuration.
|
|
98
|
+
|
|
99
|
+
A practical rule is:
|
|
100
|
+
|
|
101
|
+
- if the summary shape comes directly from ORM query structure, inference is often the cleanest option
|
|
102
|
+
- if the same summary contract is reused broadly, wrap the inferred DTO into a named DTO class
|
|
103
|
+
|
|
104
|
+
For the query side of this topic, also see [ORM Aggregate and Group Guide](/backend/orm-aggregate-group-guide).
|
|
105
|
+
|
|
106
|
+
## Relationship to CRUD generation
|
|
107
|
+
|
|
108
|
+
Inferred DTOs are not separate from the CRUD workflow. They sit on the same contract loop.
|
|
109
|
+
|
|
110
|
+
A useful split is:
|
|
111
|
+
|
|
112
|
+
- CRUD generation gives you the initial backend thread
|
|
113
|
+
- explicit DTOs give you stable named operation contracts
|
|
114
|
+
- inferred DTOs let the contract stay close to model and query truth when a separate handwritten class would add little value
|
|
115
|
+
|
|
116
|
+
This helps keep the generated thread productive instead of forcing redundant DTO maintenance everywhere.
|
|
117
|
+
|
|
118
|
+
## Encapsulating inferred DTOs
|
|
119
|
+
|
|
120
|
+
Inferred DTO logic can also be wrapped inside an explicit DTO class for reuse.
|
|
121
|
+
|
|
122
|
+
That is useful because it gives teams a spectrum of options:
|
|
123
|
+
|
|
124
|
+
- use inference directly for one endpoint
|
|
125
|
+
- wrap the inferred DTO into a named reusable class when the contract is important elsewhere too
|
|
126
|
+
|
|
127
|
+
## Relationship to generation and metadata refresh
|
|
128
|
+
|
|
129
|
+
DTO inference and generation are not isolated authoring tricks. They are part of a broader backend contract workflow.
|
|
130
|
+
|
|
131
|
+
When model, relation, or controller contracts change, remember to consider the downstream metadata and generated-contract path as well.
|
|
132
|
+
|
|
133
|
+
Read this guide together with:
|
|
134
|
+
|
|
135
|
+
- [DTO Guide](/backend/dto-guide)
|
|
136
|
+
- [Relations Guide](/backend/relations-guide)
|
|
137
|
+
- [ORM Select Guide](/backend/orm-select-guide)
|
|
138
|
+
- [ORM Aggregate and Group Guide](/backend/orm-aggregate-group-guide)
|
|
139
|
+
- [OpenAPI Guide](/backend/openapi-guide)
|
|
140
|
+
- [Backend OpenAPI to Frontend SDK](/fullstack/openapi-to-sdk)
|
|
141
|
+
|
|
142
|
+
## Implementation checks for DTO inference and generation changes
|
|
143
|
+
|
|
144
|
+
When evaluating a return shape or input contract that closely follows model structure, ask:
|
|
145
|
+
|
|
146
|
+
1. should this DTO be inferred instead of handwritten?
|
|
147
|
+
2. does model relationship structure already contain enough information?
|
|
148
|
+
3. is the contract get/list/query/create/update/aggregate/group oriented?
|
|
149
|
+
4. should the inferred DTO stay inline or be wrapped in a named DTO class?
|
|
150
|
+
5. does the resulting DTO also affect OpenAPI and frontend generation paths?
|
|
151
|
+
6. is CRUD generation already giving enough contract structure that another handwritten DTO would be redundant?
|
|
152
|
+
|
|
153
|
+
That helps reduce redundant type work and keeps contracts closer to the model truth.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Dynamic Datasource Guide
|
|
2
|
+
|
|
3
|
+
This guide points from the backend docs to the deeper Cabloy dynamic-datasource architecture.
|
|
4
|
+
|
|
5
|
+
## Current source of truth
|
|
6
|
+
|
|
7
|
+
This page is intentionally a backend-facing pointer page rather than a full standalone tutorial.
|
|
8
|
+
|
|
9
|
+
The older ORM page for dynamic datasource is intentionally brief because the deeper capability is provided by the `a-cabloy` suite.
|
|
10
|
+
|
|
11
|
+
In the new docs structure, treat dynamic datasource as a Cabloy-level capability with strong backend implications.
|
|
12
|
+
|
|
13
|
+
## Why this matters
|
|
14
|
+
|
|
15
|
+
Dynamic datasource selection affects:
|
|
16
|
+
|
|
17
|
+
- how model operations are routed
|
|
18
|
+
- how related queries are resolved
|
|
19
|
+
- how transactions and cache behavior should be coordinated
|
|
20
|
+
- how queue and distributed flows should remain datasource-safe
|
|
21
|
+
- how multi-tenant or multi-project data architectures can be organized
|
|
22
|
+
|
|
23
|
+
So this is not only an implementation tweak. It is part of the system architecture.
|
|
24
|
+
|
|
25
|
+
## Do not confuse three different routing layers
|
|
26
|
+
|
|
27
|
+
A useful distinction is:
|
|
28
|
+
|
|
29
|
+
- **ordinary datasource selection** can live in model metadata or app config
|
|
30
|
+
- **isolated-instance routing** can change the effective default datasource through instance config such as `isolateClient` and is primarily explained in [Multi-Database and Datasource Guide](/backend/multi-database-datasource) together with [Multi-Instance and Instance Resolution](/backend/multi-instance-and-instance-resolution)
|
|
31
|
+
- **dynamic datasource** begins when the routing decision depends on runtime context that cannot be declared once up front
|
|
32
|
+
|
|
33
|
+
This distinction matters because many backend tasks do not actually need dynamic datasource.
|
|
34
|
+
|
|
35
|
+
## When a task becomes truly dynamic
|
|
36
|
+
|
|
37
|
+
A practical rule is:
|
|
38
|
+
|
|
39
|
+
- static datasource choice can live in model metadata or app config
|
|
40
|
+
- relation-level datasource choice can live in relation metadata
|
|
41
|
+
- instance-sensitive default routing can live in instance config
|
|
42
|
+
- dynamic datasource begins when routing depends on live context beyond those declarative mechanisms
|
|
43
|
+
|
|
44
|
+
That helps avoid overusing dynamic datasource when ordinary datasource configuration is sufficient.
|
|
45
|
+
|
|
46
|
+
## Guidance for contributors and AI workflows
|
|
47
|
+
|
|
48
|
+
When a task mentions dynamic datasource behavior, do not stop at ordinary model configuration.
|
|
49
|
+
|
|
50
|
+
Instead:
|
|
51
|
+
|
|
52
|
+
1. identify whether the routing decision is static, relation-level, instance-sensitive, model-level, or truly dynamic
|
|
53
|
+
2. inspect the Cabloy-level datasource architecture and source code
|
|
54
|
+
3. verify whether transactions, cache, relation loading, and queue behavior still align with the datasource routing strategy
|
|
55
|
+
4. confirm that instance isolation or app-config defaults are not already enough for the task
|
|
56
|
+
|
|
57
|
+
## Relationship to the broader ORM family
|
|
58
|
+
|
|
59
|
+
Read this guide together with:
|
|
60
|
+
|
|
61
|
+
- [Config Guide](/backend/config-guide)
|
|
62
|
+
- [Model Guide](/backend/model-guide)
|
|
63
|
+
- [Multi-Database and Datasource Guide](/backend/multi-database-datasource)
|
|
64
|
+
- [Relations Guide](/backend/relations-guide)
|
|
65
|
+
- [Queue Guide](/backend/queue-guide)
|
|
66
|
+
- [Sharding Guide](/backend/sharding-guide)
|
|
67
|
+
|
|
68
|
+
## Documentation placement rule
|
|
69
|
+
|
|
70
|
+
Keep this page as the backend-facing pointer, but treat the Cabloy-level dynamic-datasource material as the deeper source of truth.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Election Guide
|
|
2
|
+
|
|
3
|
+
## Why election matters
|
|
4
|
+
|
|
5
|
+
In a distributed backend, multiple worker processes may all be capable of running the same logic, but some responsibilities should be owned only by one worker or by a small fixed number of workers.
|
|
6
|
+
|
|
7
|
+
Vona provides election for exactly that scenario.
|
|
8
|
+
|
|
9
|
+
This matters when backend code needs to:
|
|
10
|
+
|
|
11
|
+
- start a standalone service
|
|
12
|
+
- own a leader-like runtime responsibility
|
|
13
|
+
- recover ownership automatically when a worker exits
|
|
14
|
+
- allow limited parallel ownership instead of full fan-out execution
|
|
15
|
+
|
|
16
|
+
## Core election model
|
|
17
|
+
|
|
18
|
+
The election model works like this:
|
|
19
|
+
|
|
20
|
+
1. workers compete to obtain ownership of a named resource
|
|
21
|
+
2. one worker, or a configured number of workers, acquires that ownership
|
|
22
|
+
3. only the worker that obtains ownership starts the protected logic
|
|
23
|
+
4. if an owning worker exits, other workers compete again and ownership can move automatically
|
|
24
|
+
|
|
25
|
+
That makes election a coordination primitive, not just a boolean lock.
|
|
26
|
+
|
|
27
|
+
## Create `meta.election`
|
|
28
|
+
|
|
29
|
+
Example: create `meta.election` in module `demo-student`.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm run vona :create:bean meta election -- --module=demo-student
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Representative definition:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
export type TypeElectionObtainResource = 'echo';
|
|
39
|
+
|
|
40
|
+
@Meta()
|
|
41
|
+
export class MetaElection extends BeanElectionBase<TypeElectionObtainResource> {}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The resource type expresses which logical resources can be competed for inside the module.
|
|
45
|
+
|
|
46
|
+
## Obtain ownership from a module monkey
|
|
47
|
+
|
|
48
|
+
Election usually participates in backend lifecycle hooks rather than ordinary request handlers.
|
|
49
|
+
|
|
50
|
+
Representative pattern:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
export class Monkey extends BeanSimple implements IMonkeyAppStarted {
|
|
54
|
+
async appStarted() {
|
|
55
|
+
const scope = this.app.scope(__ThisModule__);
|
|
56
|
+
scope.election.obtain(
|
|
57
|
+
'echo',
|
|
58
|
+
() => {
|
|
59
|
+
// custom logic
|
|
60
|
+
},
|
|
61
|
+
async () => {
|
|
62
|
+
// cleanup
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
A useful mental model is:
|
|
70
|
+
|
|
71
|
+
- `appStarted()` is where the backend decides to compete for ownership
|
|
72
|
+
- `obtain(...)` starts the owned logic only on the worker that wins
|
|
73
|
+
- the cleanup callback releases or cleans up local resources when ownership ends
|
|
74
|
+
|
|
75
|
+
## Tickets: allow more than one owner
|
|
76
|
+
|
|
77
|
+
Sometimes one owner is too restrictive, but full broadcast fan-out is too broad.
|
|
78
|
+
|
|
79
|
+
In that case, election supports `tickets`:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
scope.election.obtain(
|
|
83
|
+
'echo',
|
|
84
|
+
() => {
|
|
85
|
+
// custom logic
|
|
86
|
+
},
|
|
87
|
+
async () => {
|
|
88
|
+
// cleanup
|
|
89
|
+
},
|
|
90
|
+
{ tickets: 2 },
|
|
91
|
+
);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
A practical interpretation is:
|
|
95
|
+
|
|
96
|
+
- `tickets: 1` means one owner
|
|
97
|
+
- `tickets: 2` means two workers may own the same resource simultaneously
|
|
98
|
+
|
|
99
|
+
This makes election useful for limited parallel ownership patterns.
|
|
100
|
+
|
|
101
|
+
## When to use election vs queue vs broadcast
|
|
102
|
+
|
|
103
|
+
Read this guide together with:
|
|
104
|
+
|
|
105
|
+
- [Backend Startup Guide](/backend/startup-guide)
|
|
106
|
+
- [Queue Guide](/backend/queue-guide)
|
|
107
|
+
- [Broadcast Guide](/backend/broadcast-guide)
|
|
108
|
+
- [Worker Guide](/backend/worker-guide)
|
|
109
|
+
- [Redlock Guide](/backend/redlock-guide)
|
|
110
|
+
|
|
111
|
+
A practical boundary is:
|
|
112
|
+
|
|
113
|
+
- use **election** when only one worker, or a fixed small number of workers, should own the responsibility
|
|
114
|
+
- use **queue** when work should be pushed asynchronously to background execution
|
|
115
|
+
- use **broadcast** when many workers should all receive the message
|
|
116
|
+
|
|
117
|
+
This distinction is important because these abstractions solve different distributed coordination problems.
|
|
118
|
+
|
|
119
|
+
## Relationship to startup lifecycle
|
|
120
|
+
|
|
121
|
+
Election often starts from backend startup hooks.
|
|
122
|
+
|
|
123
|
+
That means a common pattern is:
|
|
124
|
+
|
|
125
|
+
1. backend startup reaches `appStarted`
|
|
126
|
+
2. the module competes for an election resource
|
|
127
|
+
3. the winning worker starts the protected service or loop
|
|
128
|
+
4. ownership can fail over to another worker if the current owner exits
|
|
129
|
+
|
|
130
|
+
So startup answers _when lifecycle hooks fire_, while election answers _which worker should own a singleton-like responsibility_.
|
|
131
|
+
|
|
132
|
+
## Implementation checks for distributed-election changes
|
|
133
|
+
|
|
134
|
+
When editing distributed backend coordination, ask:
|
|
135
|
+
|
|
136
|
+
1. is this really a singleton-like ownership problem?
|
|
137
|
+
2. should one worker own the responsibility, or should there be multiple tickets?
|
|
138
|
+
3. does the ownership logic belong in backend startup hooks rather than a request path?
|
|
139
|
+
4. would queue or broadcast be the wrong abstraction for this job?
|
|
140
|
+
|
|
141
|
+
That helps AI choose election only when the underlying coordination problem truly matches it.
|