@yss123/cage-cli 0.1.0 → 0.2.0
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/README.md +23 -1
- package/dist/analyze.js +2 -0
- package/dist/analyze.js.map +1 -1
- package/dist/config.d.ts +2 -2
- package/dist/config.js +0 -1
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +72 -0
- package/dist/context.js +202 -0
- package/dist/context.js.map +1 -0
- package/dist/create-prompt.d.ts +5 -0
- package/dist/create-prompt.js +50 -0
- package/dist/create-prompt.js.map +1 -0
- package/dist/create.d.ts +18 -1
- package/dist/create.js +30 -13
- package/dist/create.js.map +1 -1
- package/dist/dev.js +12 -51
- package/dist/dev.js.map +1 -1
- package/dist/gen/contracts.js +34 -13
- package/dist/gen/contracts.js.map +1 -1
- package/dist/gen/index.js +43 -3
- package/dist/gen/index.js.map +1 -1
- package/dist/gen/zod-code.d.ts +1 -1
- package/dist/gen/zod-code.js +3 -1
- package/dist/gen/zod-code.js.map +1 -1
- package/dist/graph-command.d.ts +11 -0
- package/dist/graph-command.js +67 -0
- package/dist/graph-command.js.map +1 -0
- package/dist/graph-model.d.ts +21 -0
- package/dist/graph-model.js +101 -0
- package/dist/graph-model.js.map +1 -0
- package/dist/graph-presentation.d.ts +11 -0
- package/dist/graph-presentation.js +25 -0
- package/dist/graph-presentation.js.map +1 -0
- package/dist/graph-query.d.ts +171 -0
- package/dist/graph-query.js +242 -0
- package/dist/graph-query.js.map +1 -0
- package/dist/graph-relations.d.ts +14 -0
- package/dist/graph-relations.js +142 -0
- package/dist/graph-relations.js.map +1 -0
- package/dist/graph-search.d.ts +21 -0
- package/dist/graph-search.js +63 -0
- package/dist/graph-search.js.map +1 -0
- package/dist/graph-snapshot.d.ts +2 -4
- package/dist/graph-snapshot.js +2 -19
- package/dist/graph-snapshot.js.map +1 -1
- package/dist/graph.js +19 -1
- package/dist/graph.js.map +1 -1
- package/dist/impact.d.ts +6 -0
- package/dist/impact.js +28 -16
- package/dist/impact.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +6 -0
- package/dist/init.js +131 -0
- package/dist/init.js.map +1 -0
- package/dist/main.js +77 -32
- package/dist/main.js.map +1 -1
- package/dist/migrate.d.ts +3 -6
- package/dist/migrate.js +14 -53
- package/dist/migrate.js.map +1 -1
- package/dist/ontology.d.ts +3 -1
- package/dist/ontology.js +13 -3
- package/dist/ontology.js.map +1 -1
- package/dist/project-structure.d.ts +8 -0
- package/dist/project-structure.js +94 -0
- package/dist/project-structure.js.map +1 -0
- package/dist/static-check.js +1 -4
- package/dist/static-check.js.map +1 -1
- package/dist/sync.js +82 -10
- package/dist/sync.js.map +1 -1
- package/graph-ui/app.js +46 -8
- package/graph-ui/graph-model.js +100 -0
- package/graph-ui/graph-relations.js +141 -0
- package/graph-ui/graph-search.js +62 -0
- package/graph-ui/model.js +19 -77
- package/graph-ui/relations.js +2 -125
- package/graph-ui/structure.js +4 -1
- package/graph-ui/style.css +21 -0
- package/package.json +8 -8
- package/templates/app/.env.example +58 -14
- package/templates/app/AGENTS.md +95 -12
- package/templates/app/Dockerfile +2 -0
- package/templates/app/README.md +46 -16
- package/templates/app/_gitignore +8 -0
- package/templates/app/apps/api/package.json +2 -2
- package/templates/app/apps/web/src/app/app.tsx +18 -0
- package/templates/app/apps/web/src/app/workspace-layout.tsx +70 -0
- package/templates/app/apps/web/src/main.tsx +4 -543
- package/templates/app/apps/web/src/modules/approval/hooks/use-approval-tasks.ts +20 -0
- package/templates/app/apps/web/src/modules/approval/index.ts +2 -0
- package/templates/app/apps/web/src/modules/approval/pages/approval-tasks-page.tsx +54 -0
- package/templates/app/apps/web/src/modules/auth/index.ts +1 -0
- package/templates/app/apps/web/src/modules/auth/pages/login-page.tsx +59 -0
- package/templates/app/apps/web/src/modules/inventory/index.ts +1 -0
- package/templates/app/apps/web/src/modules/inventory/pages/inventory-page.tsx +74 -0
- package/templates/app/apps/web/src/modules/purchase/components/purchase-order-form.tsx +60 -0
- package/templates/app/apps/web/src/modules/purchase/index.ts +2 -0
- package/templates/app/apps/web/src/modules/purchase/pages/purchase-orders-page.tsx +210 -0
- package/templates/app/apps/web/src/shared/hooks/use-command.ts +29 -0
- package/templates/app/apps/web/src/shared/lib/error-message.ts +15 -0
- package/templates/app/apps/web/src/shared/ui/notice.tsx +13 -0
- package/templates/app/apps/web/src/shared/ui/page-heading.tsx +28 -0
- package/templates/app/apps/web/vite.config.ts +3 -0
- package/templates/app/cage.config.json +1 -1
- package/templates/app/docs/ai/README.md +77 -0
- package/templates/app/docs/ai/architecture.md +122 -0
- package/templates/app/docs/ai/ontology-context.md +117 -0
- package/templates/app/docs/ai/primitives.md +249 -0
- package/templates/app/docs/ai/project-structure.md +123 -0
- package/templates/app/docs/ai/purchase-example.md +97 -0
- package/templates/app/docs/ai/recipes.md +81 -0
- package/templates/app/docs/ai/relations.md +160 -0
- package/templates/app/docs/ai/transfers.md +92 -0
- package/templates/app/docs/ai/troubleshooting.md +60 -0
- package/templates/app/docs/ai/workflow.md +139 -0
- package/templates/app/docs/errors/CAGE-APPR-001.md +47 -0
- package/templates/app/docs/errors/CAGE-APPR-002.md +47 -0
- package/templates/app/docs/errors/CAGE-APPR-003.md +47 -0
- package/templates/app/docs/errors/CAGE-APPR-004.md +47 -0
- package/templates/app/docs/errors/CAGE-APPR-005.md +47 -0
- package/templates/app/docs/errors/CAGE-APPR-006.md +47 -0
- package/templates/app/docs/errors/CAGE-APPR-007.md +50 -0
- package/templates/app/docs/errors/CAGE-APPR-010.md +48 -0
- package/templates/app/docs/errors/CAGE-AUTH-001.md +47 -0
- package/templates/app/docs/errors/CAGE-BUDGET-001.md +47 -0
- package/templates/app/docs/errors/CAGE-BUDGET-002.md +47 -0
- package/templates/app/docs/errors/CAGE-BUDGET-003.md +47 -0
- package/templates/app/docs/errors/CAGE-CMD-002.md +47 -0
- package/templates/app/docs/errors/CAGE-CMD-003.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-001.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-002.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-003.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-004.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-005.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-006.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-007.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-008.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-009.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-010.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-011.md +47 -0
- package/templates/app/docs/errors/CAGE-CONN-012.md +47 -0
- package/templates/app/docs/errors/CAGE-CREATE-001.md +52 -0
- package/templates/app/docs/errors/CAGE-DECL-001.md +48 -0
- package/templates/app/docs/errors/CAGE-DECL-002.md +47 -0
- package/templates/app/docs/errors/CAGE-DECL-003.md +47 -0
- package/templates/app/docs/errors/CAGE-DECL-004.md +47 -0
- package/templates/app/docs/errors/CAGE-DEP-001.md +48 -0
- package/templates/app/docs/errors/CAGE-DEP-002.md +47 -0
- package/templates/app/docs/errors/CAGE-DEP-003.md +47 -0
- package/templates/app/docs/errors/CAGE-DEP-004.md +47 -0
- package/templates/app/docs/errors/CAGE-DEP-005.md +47 -0
- package/templates/app/docs/errors/CAGE-DLQ-001.md +48 -0
- package/templates/app/docs/errors/CAGE-ENV-001.md +52 -0
- package/templates/app/docs/errors/CAGE-EVENT-001.md +47 -0
- package/templates/app/docs/errors/CAGE-EVENT-002.md +50 -0
- package/templates/app/docs/errors/CAGE-FIELD-001.md +47 -0
- package/templates/app/docs/errors/CAGE-FIELD-002.md +47 -0
- package/templates/app/docs/errors/CAGE-FIELD-003.md +47 -0
- package/templates/app/docs/errors/CAGE-FIELD-004.md +47 -0
- package/templates/app/docs/errors/CAGE-GEN-001.md +48 -0
- package/templates/app/docs/errors/CAGE-GEN-002.md +47 -0
- package/templates/app/docs/errors/CAGE-GRANT-001.md +49 -0
- package/templates/app/docs/errors/CAGE-GRANT-002.md +48 -0
- package/templates/app/docs/errors/CAGE-GRANT-003.md +47 -0
- package/templates/app/docs/errors/CAGE-IMPACT-001.md +48 -0
- package/templates/app/docs/errors/CAGE-INIT-001.md +47 -0
- package/templates/app/docs/errors/CAGE-INIT-002.md +49 -0
- package/templates/app/docs/errors/CAGE-INIT-003.md +48 -0
- package/templates/app/docs/errors/CAGE-INIT-004.md +48 -0
- package/templates/app/docs/errors/CAGE-JOB-001.md +47 -0
- package/templates/app/docs/errors/CAGE-JOB-002.md +47 -0
- package/templates/app/docs/errors/CAGE-JOB-003.md +47 -0
- package/templates/app/docs/errors/CAGE-LINT-001.md +47 -0
- package/templates/app/docs/errors/CAGE-MIG-001.md +47 -0
- package/templates/app/docs/errors/CAGE-MIG-002.md +47 -0
- package/templates/app/docs/errors/CAGE-MIG-003.md +5 -0
- package/templates/app/docs/errors/CAGE-MIG-004.md +5 -0
- package/templates/app/docs/errors/CAGE-MIG-005.md +47 -0
- package/templates/app/docs/errors/CAGE-NAME-001.md +47 -0
- package/templates/app/docs/errors/CAGE-NAME-002.md +47 -0
- package/templates/app/docs/errors/CAGE-NAME-003.md +47 -0
- package/templates/app/docs/errors/CAGE-NAME-004.md +47 -0
- package/templates/app/docs/errors/CAGE-NAME-005.md +47 -0
- package/templates/app/docs/errors/CAGE-NAME-006.md +47 -0
- package/templates/app/docs/errors/CAGE-ORG-001.md +49 -0
- package/templates/app/docs/errors/CAGE-PAIR-002.md +50 -0
- package/templates/app/docs/errors/CAGE-QUERY-001.md +47 -0
- package/templates/app/docs/errors/CAGE-QUERY-002.md +47 -0
- package/templates/app/docs/errors/CAGE-QUERY-003.md +49 -0
- package/templates/app/docs/errors/CAGE-REACT-001.md +47 -0
- package/templates/app/docs/errors/CAGE-REACT-002.md +47 -0
- package/templates/app/docs/errors/CAGE-READS-001.md +47 -0
- package/templates/app/docs/errors/CAGE-REG-001.md +53 -0
- package/templates/app/docs/errors/CAGE-REG-002.md +48 -0
- package/templates/app/docs/errors/CAGE-REG-003.md +47 -0
- package/templates/app/docs/errors/CAGE-REL-001.md +64 -0
- package/templates/app/docs/errors/CAGE-ROUTE-001.md +47 -0
- package/templates/app/docs/errors/CAGE-SCOPE-001.md +47 -0
- package/templates/app/docs/errors/CAGE-SCOPE-002.md +47 -0
- package/templates/app/docs/errors/CAGE-SECRET-001.md +48 -0
- package/templates/app/docs/errors/CAGE-SECRET-002.md +47 -0
- package/templates/app/docs/errors/CAGE-SENS-001.md +48 -0
- package/templates/app/docs/errors/CAGE-SQL-001.md +47 -0
- package/templates/app/docs/errors/CAGE-SQL-002.md +47 -0
- package/templates/app/docs/errors/CAGE-STATE-001.md +49 -0
- package/templates/app/docs/errors/CAGE-STATE-002.md +47 -0
- package/templates/app/docs/errors/CAGE-STATE-003.md +47 -0
- package/templates/app/docs/errors/CAGE-STATE-004.md +47 -0
- package/templates/app/docs/errors/CAGE-STATE-005.md +48 -0
- package/templates/app/docs/errors/CAGE-STATE-010.md +48 -0
- package/templates/app/docs/errors/CAGE-STATE-011.md +48 -0
- package/templates/app/docs/errors/CAGE-STRUCT-001.md +51 -0
- package/templates/app/docs/errors/CAGE-STRUCT-002.md +51 -0
- package/templates/app/docs/errors/CAGE-SYNC-001.md +48 -0
- package/templates/app/docs/errors/CAGE-TEST-001.md +47 -0
- package/templates/app/docs/errors/CAGE-UNSAFE-001.md +48 -0
- package/templates/app/docs/errors/CAGE-UPGRADE-001.md +48 -0
- package/templates/app/docs/errors/README.md +307 -0
- package/templates/app/docs/errors/runtime.md +50 -0
- package/templates/app/package.json +4 -7
- package/templates/app/packages/contracts/package.json +1 -1
- package/templates/app/packages/contracts/src/{dept.ts → aggregates/dept/index.ts} +3 -3
- package/templates/app/packages/contracts/src/{inventory.ts → aggregates/inventory/index.ts} +3 -3
- package/templates/app/packages/contracts/src/{inventory-lock.ts → aggregates/inventory-lock/index.ts} +3 -3
- package/templates/app/packages/contracts/src/{purchase-order.ts → aggregates/purchase-order/index.ts} +3 -3
- package/templates/app/packages/contracts/src/{role.ts → aggregates/role/index.ts} +3 -3
- package/templates/app/packages/contracts/src/{user.ts → aggregates/user/index.ts} +3 -3
- package/templates/app/packages/contracts/src/index.ts +14 -14
- package/templates/app/packages/domain/package.json +4 -4
- package/templates/app/packages/domain/src/index.ts +4 -7
- package/templates/app/packages/domain/src/{erp.ts → integrations/erp.ts} +1 -1
- package/templates/app/packages/domain/src/modules/inventory/index.ts +2 -0
- package/templates/app/packages/domain/src/modules/purchase/index.ts +6 -0
- package/templates/app/packages/domain/src/{purchase-order.test.ts → modules/purchase/purchase-order.test.ts} +2 -2
- package/templates/app/packages/domain/src/{purchase-order.ts → modules/purchase/purchase-order.ts} +1 -1
- package/templates/app/packages/domain/src/{reactions.ts → workflows/purchase-inventory.ts} +2 -2
- package/templates/blank/.dockerignore +10 -0
- package/templates/blank/.env.example +61 -0
- package/templates/blank/.github/workflows/cage.yml +44 -0
- package/templates/blank/AGENTS.md +96 -0
- package/templates/blank/Dockerfile +25 -0
- package/templates/blank/README.md +37 -0
- package/templates/blank/_gitignore +8 -0
- package/templates/blank/apps/api/package.json +15 -0
- package/templates/blank/apps/api/src/main.ts +11 -0
- package/templates/blank/apps/api/src/worker.ts +8 -0
- package/templates/blank/apps/api/tsconfig.json +5 -0
- package/templates/blank/apps/web/index.html +13 -0
- package/templates/blank/apps/web/package.json +22 -0
- package/templates/blank/apps/web/src/app/app.tsx +44 -0
- package/templates/blank/apps/web/src/app/styles/global.css +516 -0
- package/templates/blank/apps/web/src/app/workspace-layout.tsx +70 -0
- package/templates/blank/apps/web/src/main.tsx +13 -0
- package/templates/blank/apps/web/src/modules/auth/index.ts +1 -0
- package/templates/blank/apps/web/src/modules/auth/pages/login-page.tsx +87 -0
- package/templates/blank/apps/web/src/modules/workspace/index.ts +1 -0
- package/templates/blank/apps/web/src/modules/workspace/pages/home-page.tsx +45 -0
- package/templates/blank/apps/web/src/shared/lib/error-message.ts +11 -0
- package/templates/blank/apps/web/src/shared/ui/brand.tsx +11 -0
- package/templates/blank/apps/web/tsconfig.json +11 -0
- package/templates/blank/apps/web/vite.config.ts +19 -0
- package/templates/blank/cage.config.json +14 -0
- package/templates/blank/db/migrations/0001_init.sql +63 -0
- package/templates/blank/docs/ai/README.md +77 -0
- package/templates/blank/docs/ai/architecture.md +122 -0
- package/templates/blank/docs/ai/ontology-context.md +117 -0
- package/templates/blank/docs/ai/primitives.md +249 -0
- package/templates/blank/docs/ai/project-structure.md +123 -0
- package/templates/blank/docs/ai/purchase-example.md +97 -0
- package/templates/blank/docs/ai/recipes.md +81 -0
- package/templates/blank/docs/ai/relations.md +160 -0
- package/templates/blank/docs/ai/transfers.md +92 -0
- package/templates/blank/docs/ai/troubleshooting.md +60 -0
- package/templates/blank/docs/ai/workflow.md +139 -0
- package/templates/blank/docs/errors/CAGE-APPR-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-APPR-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-APPR-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-APPR-004.md +47 -0
- package/templates/blank/docs/errors/CAGE-APPR-005.md +47 -0
- package/templates/blank/docs/errors/CAGE-APPR-006.md +47 -0
- package/templates/blank/docs/errors/CAGE-APPR-007.md +50 -0
- package/templates/blank/docs/errors/CAGE-APPR-010.md +48 -0
- package/templates/blank/docs/errors/CAGE-AUTH-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-BUDGET-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-BUDGET-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-BUDGET-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-CMD-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-CMD-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-004.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-005.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-006.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-007.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-008.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-009.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-010.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-011.md +47 -0
- package/templates/blank/docs/errors/CAGE-CONN-012.md +47 -0
- package/templates/blank/docs/errors/CAGE-CREATE-001.md +52 -0
- package/templates/blank/docs/errors/CAGE-DECL-001.md +48 -0
- package/templates/blank/docs/errors/CAGE-DECL-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-DECL-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-DECL-004.md +47 -0
- package/templates/blank/docs/errors/CAGE-DEP-001.md +48 -0
- package/templates/blank/docs/errors/CAGE-DEP-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-DEP-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-DEP-004.md +47 -0
- package/templates/blank/docs/errors/CAGE-DEP-005.md +47 -0
- package/templates/blank/docs/errors/CAGE-DLQ-001.md +48 -0
- package/templates/blank/docs/errors/CAGE-ENV-001.md +52 -0
- package/templates/blank/docs/errors/CAGE-EVENT-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-EVENT-002.md +50 -0
- package/templates/blank/docs/errors/CAGE-FIELD-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-FIELD-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-FIELD-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-FIELD-004.md +47 -0
- package/templates/blank/docs/errors/CAGE-GEN-001.md +48 -0
- package/templates/blank/docs/errors/CAGE-GEN-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-GRANT-001.md +49 -0
- package/templates/blank/docs/errors/CAGE-GRANT-002.md +48 -0
- package/templates/blank/docs/errors/CAGE-GRANT-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-IMPACT-001.md +48 -0
- package/templates/blank/docs/errors/CAGE-INIT-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-INIT-002.md +49 -0
- package/templates/blank/docs/errors/CAGE-INIT-003.md +48 -0
- package/templates/blank/docs/errors/CAGE-INIT-004.md +48 -0
- package/templates/blank/docs/errors/CAGE-JOB-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-JOB-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-JOB-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-LINT-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-MIG-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-MIG-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-MIG-003.md +5 -0
- package/templates/blank/docs/errors/CAGE-MIG-004.md +5 -0
- package/templates/blank/docs/errors/CAGE-MIG-005.md +47 -0
- package/templates/blank/docs/errors/CAGE-NAME-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-NAME-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-NAME-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-NAME-004.md +47 -0
- package/templates/blank/docs/errors/CAGE-NAME-005.md +47 -0
- package/templates/blank/docs/errors/CAGE-NAME-006.md +47 -0
- package/templates/blank/docs/errors/CAGE-ORG-001.md +49 -0
- package/templates/blank/docs/errors/CAGE-PAIR-002.md +50 -0
- package/templates/blank/docs/errors/CAGE-QUERY-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-QUERY-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-QUERY-003.md +49 -0
- package/templates/blank/docs/errors/CAGE-REACT-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-REACT-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-READS-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-REG-001.md +53 -0
- package/templates/blank/docs/errors/CAGE-REG-002.md +48 -0
- package/templates/blank/docs/errors/CAGE-REG-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-REL-001.md +64 -0
- package/templates/blank/docs/errors/CAGE-ROUTE-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-SCOPE-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-SCOPE-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-SECRET-001.md +48 -0
- package/templates/blank/docs/errors/CAGE-SECRET-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-SENS-001.md +48 -0
- package/templates/blank/docs/errors/CAGE-SQL-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-SQL-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-STATE-001.md +49 -0
- package/templates/blank/docs/errors/CAGE-STATE-002.md +47 -0
- package/templates/blank/docs/errors/CAGE-STATE-003.md +47 -0
- package/templates/blank/docs/errors/CAGE-STATE-004.md +47 -0
- package/templates/blank/docs/errors/CAGE-STATE-005.md +48 -0
- package/templates/blank/docs/errors/CAGE-STATE-010.md +48 -0
- package/templates/blank/docs/errors/CAGE-STATE-011.md +48 -0
- package/templates/blank/docs/errors/CAGE-STRUCT-001.md +51 -0
- package/templates/blank/docs/errors/CAGE-STRUCT-002.md +51 -0
- package/templates/blank/docs/errors/CAGE-SYNC-001.md +48 -0
- package/templates/blank/docs/errors/CAGE-TEST-001.md +47 -0
- package/templates/blank/docs/errors/CAGE-UNSAFE-001.md +48 -0
- package/templates/blank/docs/errors/CAGE-UPGRADE-001.md +48 -0
- package/templates/blank/docs/errors/README.md +307 -0
- package/templates/blank/docs/errors/runtime.md +50 -0
- package/templates/blank/docs/examples/purchase-admin/README.md +5 -0
- package/templates/blank/docs/examples/purchase-admin/apps/api/src/main.ts +10 -0
- package/templates/blank/docs/examples/purchase-admin/apps/api/src/worker.ts +14 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/app/app.tsx +18 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/app/styles/global.css +663 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/app/workspace-layout.tsx +70 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/main.tsx +13 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/approval/hooks/use-approval-tasks.ts +20 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/approval/index.ts +2 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/approval/pages/approval-tasks-page.tsx +54 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/auth/index.ts +1 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/auth/pages/login-page.tsx +59 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/inventory/index.ts +1 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/inventory/pages/inventory-page.tsx +74 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/components/purchase-order-form.tsx +60 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/index.ts +2 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/pages/purchase-orders-page.tsx +210 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/hooks/use-command.ts +29 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/lib/error-message.ts +15 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/notice.tsx +13 -0
- package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/page-heading.tsx +28 -0
- package/templates/blank/docs/examples/purchase-admin/cage.config.json +114 -0
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/dept/index.ts +74 -0
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/inventory/index.ts +57 -0
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/inventory-lock/index.ts +52 -0
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/purchase-order/index.ts +72 -0
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/role/index.ts +85 -0
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/user/index.ts +89 -0
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/index.ts +81 -0
- package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/queries/index.ts +17 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/index.ts +6 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/integrations/erp.ts +36 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/inventory/index.ts +2 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/inventory/inventory.ts +43 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/approvals.ts +18 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/index.ts +6 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/jobs.ts +13 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/purchase-order.test.ts +137 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/purchase-order.ts +122 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/queries.ts +10 -0
- package/templates/blank/docs/examples/purchase-admin/packages/domain/src/workflows/purchase-inventory.ts +24 -0
- package/templates/blank/package.json +22 -0
- package/templates/blank/packages/contracts/package.json +16 -0
- package/templates/blank/packages/contracts/src/aggregates/dept/index.ts +74 -0
- package/templates/blank/packages/contracts/src/aggregates/role/index.ts +85 -0
- package/templates/blank/packages/contracts/src/aggregates/user/index.ts +89 -0
- package/templates/blank/packages/contracts/src/index.ts +55 -0
- package/templates/blank/packages/contracts/src/queries/index.ts +13 -0
- package/templates/blank/packages/contracts/tsconfig.json +5 -0
- package/templates/blank/packages/domain/package.json +23 -0
- package/templates/blank/packages/domain/src/index.ts +4 -0
- package/templates/blank/packages/domain/tsconfig.json +5 -0
- package/templates/blank/pnpm-workspace.yaml +10 -0
- package/templates/blank/renovate.json +5 -0
- package/templates/blank/tsconfig.base.json +8 -0
- package/dist/database.d.ts +0 -5
- package/dist/database.js +0 -70
- package/dist/database.js.map +0 -1
- package/managed/errors/CAGE-APPR-001.md +0 -32
- package/managed/errors/CAGE-APPR-002.md +0 -32
- package/managed/errors/CAGE-APPR-003.md +0 -32
- package/managed/errors/CAGE-APPR-004.md +0 -32
- package/managed/errors/CAGE-APPR-005.md +0 -32
- package/managed/errors/CAGE-APPR-006.md +0 -32
- package/managed/errors/CAGE-APPR-007.md +0 -32
- package/managed/errors/CAGE-APPR-010.md +0 -32
- package/managed/errors/CAGE-AUTH-001.md +0 -32
- package/managed/errors/CAGE-BUDGET-001.md +0 -32
- package/managed/errors/CAGE-BUDGET-002.md +0 -32
- package/managed/errors/CAGE-BUDGET-003.md +0 -32
- package/managed/errors/CAGE-CMD-002.md +0 -32
- package/managed/errors/CAGE-CMD-003.md +0 -34
- package/managed/errors/CAGE-CONN-001.md +0 -32
- package/managed/errors/CAGE-CONN-002.md +0 -32
- package/managed/errors/CAGE-CONN-003.md +0 -32
- package/managed/errors/CAGE-CONN-004.md +0 -32
- package/managed/errors/CAGE-CONN-005.md +0 -32
- package/managed/errors/CAGE-CONN-006.md +0 -32
- package/managed/errors/CAGE-CONN-007.md +0 -32
- package/managed/errors/CAGE-CONN-008.md +0 -32
- package/managed/errors/CAGE-CONN-009.md +0 -32
- package/managed/errors/CAGE-CONN-010.md +0 -32
- package/managed/errors/CAGE-CONN-011.md +0 -32
- package/managed/errors/CAGE-CONN-012.md +0 -32
- package/managed/errors/CAGE-CREATE-001.md +0 -32
- package/managed/errors/CAGE-DECL-001.md +0 -34
- package/managed/errors/CAGE-DECL-002.md +0 -34
- package/managed/errors/CAGE-DECL-003.md +0 -34
- package/managed/errors/CAGE-DECL-004.md +0 -34
- package/managed/errors/CAGE-DEP-001.md +0 -34
- package/managed/errors/CAGE-DEP-002.md +0 -34
- package/managed/errors/CAGE-DEP-003.md +0 -34
- package/managed/errors/CAGE-DEP-004.md +0 -34
- package/managed/errors/CAGE-DEP-005.md +0 -34
- package/managed/errors/CAGE-DLQ-001.md +0 -32
- package/managed/errors/CAGE-ENV-001.md +0 -32
- package/managed/errors/CAGE-EVENT-001.md +0 -32
- package/managed/errors/CAGE-EVENT-002.md +0 -32
- package/managed/errors/CAGE-FIELD-001.md +0 -32
- package/managed/errors/CAGE-FIELD-002.md +0 -32
- package/managed/errors/CAGE-FIELD-003.md +0 -32
- package/managed/errors/CAGE-FIELD-004.md +0 -32
- package/managed/errors/CAGE-GEN-001.md +0 -33
- package/managed/errors/CAGE-GEN-002.md +0 -33
- package/managed/errors/CAGE-GRANT-001.md +0 -32
- package/managed/errors/CAGE-GRANT-002.md +0 -32
- package/managed/errors/CAGE-GRANT-003.md +0 -32
- package/managed/errors/CAGE-IMPACT-001.md +0 -32
- package/managed/errors/CAGE-JOB-001.md +0 -32
- package/managed/errors/CAGE-JOB-002.md +0 -32
- package/managed/errors/CAGE-JOB-003.md +0 -32
- package/managed/errors/CAGE-LINT-001.md +0 -32
- package/managed/errors/CAGE-MIG-001.md +0 -32
- package/managed/errors/CAGE-MIG-002.md +0 -32
- package/managed/errors/CAGE-MIG-003.md +0 -32
- package/managed/errors/CAGE-MIG-004.md +0 -32
- package/managed/errors/CAGE-MIG-005.md +0 -32
- package/managed/errors/CAGE-NAME-001.md +0 -32
- package/managed/errors/CAGE-NAME-002.md +0 -32
- package/managed/errors/CAGE-NAME-003.md +0 -32
- package/managed/errors/CAGE-NAME-004.md +0 -32
- package/managed/errors/CAGE-NAME-005.md +0 -32
- package/managed/errors/CAGE-NAME-006.md +0 -32
- package/managed/errors/CAGE-ORG-001.md +0 -32
- package/managed/errors/CAGE-PAIR-002.md +0 -33
- package/managed/errors/CAGE-QUERY-001.md +0 -32
- package/managed/errors/CAGE-QUERY-002.md +0 -32
- package/managed/errors/CAGE-QUERY-003.md +0 -32
- package/managed/errors/CAGE-REACT-001.md +0 -32
- package/managed/errors/CAGE-REACT-002.md +0 -32
- package/managed/errors/CAGE-READS-001.md +0 -32
- package/managed/errors/CAGE-REG-001.md +0 -32
- package/managed/errors/CAGE-REG-002.md +0 -32
- package/managed/errors/CAGE-REG-003.md +0 -32
- package/managed/errors/CAGE-ROUTE-001.md +0 -32
- package/managed/errors/CAGE-SCOPE-001.md +0 -32
- package/managed/errors/CAGE-SCOPE-002.md +0 -32
- package/managed/errors/CAGE-SECRET-001.md +0 -32
- package/managed/errors/CAGE-SECRET-002.md +0 -32
- package/managed/errors/CAGE-SENS-001.md +0 -32
- package/managed/errors/CAGE-SQL-001.md +0 -32
- package/managed/errors/CAGE-SQL-002.md +0 -32
- package/managed/errors/CAGE-STATE-001.md +0 -32
- package/managed/errors/CAGE-STATE-002.md +0 -32
- package/managed/errors/CAGE-STATE-003.md +0 -32
- package/managed/errors/CAGE-STATE-004.md +0 -32
- package/managed/errors/CAGE-STATE-005.md +0 -32
- package/managed/errors/CAGE-STATE-010.md +0 -32
- package/managed/errors/CAGE-STATE-011.md +0 -32
- package/managed/errors/CAGE-SYNC-001.md +0 -35
- package/managed/errors/CAGE-TEST-001.md +0 -33
- package/managed/errors/CAGE-UNSAFE-001.md +0 -32
- package/managed/errors/CAGE-UPGRADE-001.md +0 -32
- package/managed/errors/README.md +0 -99
- package/templates/app/compose.yaml +0 -39
- /package/templates/app/apps/web/src/{style.css → app/styles/global.css} +0 -0
- /package/templates/app/packages/contracts/src/{queries.ts → queries/index.ts} +0 -0
- /package/templates/app/packages/domain/src/{inventory.ts → modules/inventory/inventory.ts} +0 -0
- /package/templates/app/packages/domain/src/{approvals.ts → modules/purchase/approvals.ts} +0 -0
- /package/templates/app/packages/domain/src/{jobs.ts → modules/purchase/jobs.ts} +0 -0
- /package/templates/app/packages/domain/src/{queries.ts → modules/purchase/queries.ts} +0 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# 架构与执行模型
|
|
2
|
+
|
|
3
|
+
[返回学习入口](README.md) · [下一篇:原语](primitives.md)
|
|
4
|
+
|
|
5
|
+
## 业务声明是一份可执行规格
|
|
6
|
+
|
|
7
|
+
Cage 把业务字段、状态、写操作、读依赖和跨对象关系写成 TypeScript 声明。声明函数返回描述对象,本身不连接数据库;API 和 worker 启动时从 domain 入口构建注册表,CLI 则结合 TypeScript 源码位置抽取本体图。
|
|
8
|
+
|
|
9
|
+
同一份声明产生以下结果:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
domain/index.ts 导出的声明
|
|
13
|
+
├─ 注册表 → 能力目录 → HTTP 路由 → 内核与读侧
|
|
14
|
+
├─ 本体图 → 图规则 / 中文图谱 / 影响分析 / 派生测试
|
|
15
|
+
└─ 生成器 → 前端 contracts / 状态图 / SQL / 迁移草稿
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
因此新增业务操作应先写领域命令,不再为该操作手写路由、权限拦截器、审计 SQL 和前端请求封装。新增声明还必须从 domain 入口导出;仅创建文件不会被自动注册。
|
|
19
|
+
|
|
20
|
+
## 聚合、命令、事件分别是什么
|
|
21
|
+
|
|
22
|
+
用“采购单”举例:
|
|
23
|
+
|
|
24
|
+
| 概念 | 例子 | 它负责的事 |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| 聚合 aggregate | `PurchaseOrder` | 把金额、明细、部门、状态和不变量放在同一业务边界内 |
|
|
27
|
+
| 命令 command | `PurchaseOrder.submit` | 请求把草稿提交审批;执行时可拒绝,不是直接改字段 |
|
|
28
|
+
| 事件 event | `OrderSubmitted` | 表示提交已经成功发生,供审批等后续处理消费 |
|
|
29
|
+
| 反应 reaction | `lockOnApprove` | 收到已批准事件后调用库存锁命令 |
|
|
30
|
+
| 约束 constraint | `sod.notSameAs('createdBy')` | 执行前判断当前主体是否能进行这个业务动作 |
|
|
31
|
+
| 不变量 invariant | 金额必须大于零 | 执行后的对象在任何允许状态下都必须满足的条件 |
|
|
32
|
+
|
|
33
|
+
聚合通常对应 `core.purchase_order` 一张业务主表。系统列包括 `id / version / status / createdAt / updatedAt`,数据库还维护租户列;业务不要重复定义它们。`createdBy` 是业务字段,声明为 `ref('User')` 后内核会在新建时填入当前用户,并可作为本人范围与职责分离依据。
|
|
34
|
+
|
|
35
|
+
一个采购单可以有明细列表,也可以引用库存等其他聚合。跨聚合写入要调用对方的命令,通过反应表达关系,不能拿到对方数据库表直接更新。
|
|
36
|
+
|
|
37
|
+
对象关联统一写在 fields:ref 为本表外键,hasMany 依据目标外键反查,manyToMany 自动生成中间表。多对多链接属于来源对象的命令事务,但不授权修改目标内容;公开关联通过两侧安全视图分页读取。完整模式见[对象关系](relations.md)。
|
|
38
|
+
|
|
39
|
+
## 一次写入的完整路径
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
浏览器 contracts → HTTP 会话与 CSRF → Kernel.execute
|
|
43
|
+
→ 校验输入、上下文、调用身份与能力
|
|
44
|
+
→ 幂等检查
|
|
45
|
+
→ 按范围加载并锁定记录,检查状态与版本
|
|
46
|
+
→ 硬约束(职责分离、额度、二次认证等)
|
|
47
|
+
→ 绑定 reads,只运行 handler 的业务计算
|
|
48
|
+
→ 合并结果,内核设置目标状态,校验字段及不变量
|
|
49
|
+
→ 写记录、版本、审计和 outbox,执行同事务反应
|
|
50
|
+
→ 提交事务 → 返回结果
|
|
51
|
+
worker → 消费 outbox → 异步反应 / 审批 / 连接器
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- `handler(draft, input, deps, ctx)` 描述变化,可读显式声明的依赖,不直接写数据库、发 HTTP 或读写文件。
|
|
55
|
+
- 业务拒绝返回 `fail('业务码', detail?)`。不要抛普通异常来表示可预期的业务拒绝,也不要吞异常后返回成功。
|
|
56
|
+
- `from/to` 决定生命周期。handler 不能靠写 `status` 绕过状态机;已有记录的 `to: null` 明确保留当前状态,新建必须给真实目标状态。
|
|
57
|
+
- 乐观版本冲突用 `CONFLICT` 告知客户端重新加载;幂等用于重复投递同一个请求,二者解决不同问题。
|
|
58
|
+
- SDK 每次调用默认产生新的幂等键,同一次调用内部重试复用。业务要让双击或用户手动重试视为同一次提交时,需要保留并复用该次操作的键,不能假设两次独立调用自动合并。
|
|
59
|
+
|
|
60
|
+
## 同事务与异步的选择
|
|
61
|
+
|
|
62
|
+
| 声明 | 适合用途 | 失败影响 |
|
|
63
|
+
| --- | --- | --- |
|
|
64
|
+
| `reaction(..., { tx: 'same' })` | 主动作与资源登记必须同时成功,例如批准与建库存锁 | 反应失败,主命令和审计/outbox 一起回滚 |
|
|
65
|
+
| 默认 `tx: 'outbox'` | 可稍后完成的后续动作,例如取消后的释放 | 主动作已提交;worker 重试,耗尽或永久错误进入死信 |
|
|
66
|
+
| `outbound(...)` | ERP 等事务外的网络写入 | 无法回滚远端;必须设计幂等与补偿 |
|
|
67
|
+
|
|
68
|
+
同事务链不能无限递归,外部 IO 不能放进同事务 handler。异步消息按聚合顺序投递;某条进入死信后,后续同一聚合消息可能被阻塞。先修正原因,再通过死信接口重放或经业务判断丢弃,不直接修改 outbox/业务表。
|
|
69
|
+
|
|
70
|
+
资源标签 `acquires: 'inventory.lock'`、`releases: 'inventory.lock'` 为图规则提供证据。它们不会自动加锁或释放,真正效果来自 `acquireLock/releaseLock` 命令。
|
|
71
|
+
|
|
72
|
+
## 权限是几层判断
|
|
73
|
+
|
|
74
|
+
| 层 | 例子 | 说明 |
|
|
75
|
+
| --- | --- | --- |
|
|
76
|
+
| 登录与租户 | 当前用户与租户上下文 | Cookie 会话、OIDC 等由 HTTP 层装配 |
|
|
77
|
+
| 调用身份 | `user/process/job/external` | `process` 命令不给浏览器生成直接写接口;这不是角色名 |
|
|
78
|
+
| 能力点 | `PurchaseOrder.submit`、`PurchaseOrder.read` | 代码声明能力目录,管理员在目录范围内授权 |
|
|
79
|
+
| 数据范围 | 本人、本部门、部门及下级、指定部门、全部 | 决定能触及哪些记录,越范围详情通常表现为 404 |
|
|
80
|
+
| 业务硬约束 | 不是提单人、额度不超过 ceiling | 即使被选为审批人也必须通过 |
|
|
81
|
+
| 明文能力 | `PurchaseOrder.unmask.phone` | 没有该能力时,安全读侧对电话掩码 |
|
|
82
|
+
|
|
83
|
+
`scope.deptSubtree('deptId')` 描述聚合的部门字段和建议范围;**实际范围取决于角色授权数据**。`scope.none()` 表示没有部门/创建人范围字段,仍然需要能力和适当的 `all` 范围授权,不能理解成“任何人能读”。多角色范围合并为并集,缺少显式范围时默认本人范围,不是全部。
|
|
84
|
+
|
|
85
|
+
`useCan()`、`can()` 只决定按钮呈现;前端隐藏按钮不能代替后端授权。新增命令不会自动授给已有生产角色,角色分配和额度调整也受防提权约束。
|
|
86
|
+
|
|
87
|
+
## 数据库连接属于框架核心能力
|
|
88
|
+
|
|
89
|
+
项目提供连接配置,框架管理连接池、事务、上下文变量和 SQL。普通业务不需要自己编写 `pg.Pool`、表仓储或连接释放逻辑。
|
|
90
|
+
|
|
91
|
+
| 配置/对象 | 职责 | 何时使用 |
|
|
92
|
+
| --- | --- | --- |
|
|
93
|
+
| `CAGE_ADMIN_DATABASE_URL` | 已有数据库的管理员连接 | 首次 `cage init` 准备账号;成功后从本地配置移除 |
|
|
94
|
+
| `CAGE_APP_DATABASE_URL` / `cage_app` | API 与 worker 业务写入、系统处理 | 日常运行 |
|
|
95
|
+
| `CAGE_READ_DATABASE_URL` / `cage_read` | 只能查询安全层 | 列表、详情、自定义查询、导出 |
|
|
96
|
+
| `CAGE_MIGRATE_DATABASE_URL` / `cage_migrate` | 结构变更及安全层重建 | 显式 `cage migrate` |
|
|
97
|
+
| `cage_view_owner` | 不可登录的视图所有者 | 安全视图内部使用,无密码配置 |
|
|
98
|
+
|
|
99
|
+
APP、READ、MIGRATE 指向**同一个已经存在的数据库**,用不同账号隔离能力。PostgreSQL 角色属于集群,不能因另一个项目初始化而重置已有角色密码。
|
|
100
|
+
|
|
101
|
+
| 命令 | 改什么 | 不负责什么 |
|
|
102
|
+
| --- | --- | --- |
|
|
103
|
+
| `init` | 角色、schema、授权、随机凭证及 `.env` | 安装 PG、启动 Docker、创建数据库、创建业务表 |
|
|
104
|
+
| `gen` | 本地 contracts、SQL、状态图、迁移草稿 | 应用数据库迁移 |
|
|
105
|
+
| `migrate` | 已有库的表、索引、安全视图、迁移历史 | 每次启动新建数据库 |
|
|
106
|
+
| `dev` | 连接已有库,启动 API、worker、Web,监听声明重新生成 | 自动迁移或内嵌临时数据库兜底 |
|
|
107
|
+
|
|
108
|
+
读路径使用 `safe.*`,业务表位于 `core.*`,组织和运行数据位于 `sys.*`。安全视图结合会话上下文执行范围过滤与掩码;手写报表也必须走这层。连接现有库不意味着自动把历史任意表变成 Cage 聚合,需要专门的映射与迁移方案。
|
|
109
|
+
|
|
110
|
+
## 包之间如何依赖
|
|
111
|
+
|
|
112
|
+
业务 domain 依赖 kernel 的声明 API,按需要引用 approval/connectors 声明;API 与 worker 入口依赖 domain 并装配框架。contracts 由 domain 生成,Web 依赖 contracts 和 client。domain 不能反过来依赖 contracts,否则产生生成循环。
|
|
113
|
+
|
|
114
|
+
框架的 `testkit` 提供真实 PostgreSQL 测试环境与 handler 测试工具;`lint` 检查依赖和危险写法;CLI 负责分析、生成和汇总门禁。生产程序不需要携带整个框架仓库。
|
|
115
|
+
|
|
116
|
+
## 本体图为什么有这些连线
|
|
117
|
+
|
|
118
|
+
图谱抽取命令状态迁移与事件、反应调用、审批 start/cancelOn、约束/路由字段、查询读依赖和外部映射;另以虚线标记按标识推导的所属聚合。关系解释区分显式声明、规则推导与文本匹配,箭头要按各自含义阅读。开发前可用[本体上下文工具](ontology-context.md)检索相关业务与源码。
|
|
119
|
+
|
|
120
|
+
`cage analyze` 将图写到 `.cage/ontology.json`;`cage graph` 提供浏览器工作台。中文显示名写在 `cage.config.json` 的 `graph.labels`,例如 `"command:PurchaseOrder.submit": "提交审批"`。显示名不更改能力标识、源码符号或表名。
|
|
121
|
+
|
|
122
|
+
图规则能检查“离开持有状态是否声明了释放”等结构问题,无法证明 `map` 的任意分支或 handler 计算一定正确。因此重要的资源释放条件、权限拒绝、额度边界仍需对应业务回归用例。
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# 用本体图辅助 AI 开发业务
|
|
2
|
+
|
|
3
|
+
[返回学习入口](README.md) · [开发步骤](workflow.md)
|
|
4
|
+
|
|
5
|
+
本页命令在**应用根目录**运行,使用项目安装的 CLI。工具直接分析当前注册的 domain,无需启动 API、worker 或数据库。不要把 Web 截图作为 AI 理解业务的唯一材料:优先取得结构化上下文,再按源码位置阅读实现。
|
|
6
|
+
|
|
7
|
+
## 开发一项需求的入口
|
|
8
|
+
|
|
9
|
+
以采购模板为例:
|
|
10
|
+
|
|
11
|
+
```powershell
|
|
12
|
+
# 根据中文需求取得一份可阅读的上下文,默认 Markdown。
|
|
13
|
+
pnpm exec cage context "修改采购单取消逻辑"
|
|
14
|
+
# 用完整节点身份缩小入口;保存 JSON 供编程工具读取。
|
|
15
|
+
pnpm exec cage context command:PurchaseOrder.cancel --format json --out .cage/cancel-context.json
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
报告提供中文名称、稳定 key、候选命中依据、相关业务事实、连接原因与用途、源码位置、契约、前端引用和测试候选、迁移及授权审查提示。没有执行其中的命令或测试,也没有写入数据库。
|
|
19
|
+
|
|
20
|
+
中文检索按名称、description、标识和所属对象匹配,支持相邻中文词片段;它没有调用语言模型,不保证理解任意自然语言需求。比如“采购单取消”和“取消采购单”可以匹配,但业务同义词、否定句、复杂规则仍需要 AI 阅读源码并结合用户需求确认。
|
|
21
|
+
|
|
22
|
+
- `selection.mode=exact`:完整 key、唯一 ID 或唯一中文全称定位到一个入口。
|
|
23
|
+
- `selection.mode=keyword-candidates`:选取得分接近的最多三个入口生成子图,同时返回其他候选和命中原因。先确认入口符合需求;可复制候选 key 再运行一次。
|
|
24
|
+
- 没有命中不表示框架不支持该业务。尝试较短关键词或代码标识。空白模板只注册组织模型,`docs/examples/purchase-admin` 是参考资料,不会被提取为当前项目的采购业务。
|
|
25
|
+
|
|
26
|
+
## 四个命令怎样配合
|
|
27
|
+
|
|
28
|
+
```powershell
|
|
29
|
+
# 1. 按中文或英文找业务;--kind 可限定节点类型。
|
|
30
|
+
pnpm exec cage graph search "取消" --kind command --limit 8
|
|
31
|
+
# 2. 精确查看节点属性、所有直接关联及声明位置。
|
|
32
|
+
pnpm exec cage graph inspect command:PurchaseOrder.cancel
|
|
33
|
+
# 3. 查看两个节点之间的最短关系路径。
|
|
34
|
+
pnpm exec cage graph path command:PurchaseOrder.cancel command:InventoryLock.release
|
|
35
|
+
# 4. 组织为本次开发所需的上下文。
|
|
36
|
+
pnpm exec cage context command:PurchaseOrder.cancel --depth 3 --limit 40
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
这些查询均支持 `--format md|json` 和 `--out <文件>`,默认 Markdown。输出路径相对应用根目录;可通过 `--root <目录>` 指定另一个应用。`cage graph` 不带子命令时仍启动 Web 工作台。
|
|
40
|
+
|
|
41
|
+
| 查询 | 结果与边界 |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| `graph search` | 返回排序候选、score、matches 和源码位置;默认最多 10 项,truncated 表示候选被截断 |
|
|
44
|
+
| `graph inspect` | 只接受完整 key、唯一 ID 或唯一中文全称;有歧义或只有模糊匹配时返回候选,请使用完整 key 重试 |
|
|
45
|
+
| `graph path` | 默认沿箭头,深度最多 6;用 `--direction both` 探索双向依赖,每一步明确标出逆向遍历 |
|
|
46
|
+
| `context` | 默认深度 3、最多 40 个节点;附带的表、字段和状态不继续展开,避免扩大为整张系统图 |
|
|
47
|
+
|
|
48
|
+
节点身份由类型与 ID 共同构成,例如 `command:PurchaseOrder.cancel`。命令、状态、能力点可以同名,不能只用 ID 合并节点。审批路由的 ID 自带 `route:`,因此完整 key 可能是 `route:route:po-approval`;直接复制工具返回的 key。
|
|
49
|
+
|
|
50
|
+
路径展示的是**关系链**。读取箭头指向数据来源;双向遍历不改变声明方向。归属虚线不会参与路径搜索,避免经同一所属对象制造两个命令之间的调用关系。没有在深度内找到路径,不代表任意代码之间绝无联系;可提高 `--depth`,或使用双向探索。状态迁移路径会保留 `via` 命令。
|
|
51
|
+
|
|
52
|
+
## 采购单取消的实际阅读方式
|
|
53
|
+
|
|
54
|
+
工具可以追踪以下声明:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
command:PurchaseOrder.cancel
|
|
58
|
+
→ event:OrderCancelled
|
|
59
|
+
→ reaction:releaseOnCancel
|
|
60
|
+
→ command:InventoryLock.release
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
同时会看到 `OrderCancelled → poApproval` 的 `cancels` 关系,依据是审批绑定的 `cancelOn`。这条关系负责取消审批;释放库存锁由另一条业务反应完成,两者分别检查。
|
|
64
|
+
|
|
65
|
+
接着阅读报告指向的 `workflows/purchase-inventory.ts`:`releaseOnCancel.map` 仅在事件 `previousStatus === 'approved'` 时返回释放输入,其余返回 null。图能解释订阅、目标和资源标签,不能仅凭存在一条连线就判定每次取消都会释放资源。
|
|
66
|
+
|
|
67
|
+
源码位置有两个精度:
|
|
68
|
+
|
|
69
|
+
- `declaration`:定位到顶层声明的文件与行号。
|
|
70
|
+
- `aggregate`:字段、状态等只定位到所属聚合声明;继续在该声明内查找具体成员。
|
|
71
|
+
|
|
72
|
+
`source=null` 表示当前项目静态索引没有位置,常见于来自框架依赖的组织模型;应查依赖的公开 API 和本地指南。报告不会伪造字段的精确行号或复制依赖内部实现。
|
|
73
|
+
|
|
74
|
+
## 判断证据强度和覆盖范围
|
|
75
|
+
|
|
76
|
+
每条关系都带 `why`、`purpose` 和 `evidence`:
|
|
77
|
+
|
|
78
|
+
| evidence.basis | 可以据此判断什么 |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| `declared` | 来自 reads、emits、reaction、cancelOn、约束字段等显式声明;仍需阅读执行条件 |
|
|
81
|
+
| `inferred` | 从标识推导归属,或由默认事件映射、出站同名字段规则建立关联;核对映射实现 |
|
|
82
|
+
| `text-match` | 从 SQL/build 文本匹配字段名;可能命中注释或同名内容,只作为查询依赖线索 |
|
|
83
|
+
|
|
84
|
+
前端候选按聚合/查询代码名称匹配,项目测试候选按源码标识匹配,均不是完整依赖分析。派生测试按 subject 选择,不意味着这些用例一定能构造有效输入或已经运行成功。
|
|
85
|
+
|
|
86
|
+
每份报告都带 `extractedAt`、`graphHash`、`freshness`、当前 `findings` 和使用边界。CLI 每次独立进程重新抽取,不读取旧图缓存;Web 复制使用当前图谱快照,明确标为 `displayed-snapshot`,改代码后先点“重新抽取”。`graph.labels` 的显示覆盖独立保存,不改变本体节点 ID 和语义哈希;声明内的 description 仍可能影响语义比较,特别是字段口径说明。
|
|
87
|
+
|
|
88
|
+
`coverage.truncated` 表示节点上限造成遗漏;`depthBoundary` 指出因深度限制未继续展开的节点。扩大 `--limit` / `--depth` 或改用更精确入口;不能把未出现在子图中的对象当成没有影响。测试、前端候选还分别报告自己的截断情况。
|
|
89
|
+
|
|
90
|
+
图规则有错误时,只要仍能抽取出图,就输出报告和问题,CLI 返回非零退出码。静态检查阻止 domain 求值时,先按诊断修复,再重新请求上下文。未命中、inspect 歧义、深度内无路径也返回非零;不要因为已经生成文件就当作成功。
|
|
91
|
+
|
|
92
|
+
## 修改前后形成闭环
|
|
93
|
+
|
|
94
|
+
```powershell
|
|
95
|
+
# 开发前:先取得上下文、阅读实际实现,再保存独立基线。
|
|
96
|
+
pnpm exec cage context "采购单取消"
|
|
97
|
+
pnpm exec cage analyze
|
|
98
|
+
Copy-Item .cage/ontology.json .cage/before-change.json
|
|
99
|
+
# 在源码修改业务声明和实现……
|
|
100
|
+
pnpm exec cage check
|
|
101
|
+
pnpm gen
|
|
102
|
+
pnpm exec cage impact --base-file .cage/before-change.json
|
|
103
|
+
# 按影响和实际风险选择必要测试,审查迁移与管理员授权。
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
查询也会更新 `.cage/ontology.json`。基线必须在改代码前另存,保留到验收结束,不能把每次被覆盖的当前图当作旧图。`context` 用于开发前定位,`impact` 用于修改后比较;二者都不能替代业务验收。
|
|
107
|
+
|
|
108
|
+
可直接交给编程工具的要求:
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
先读 AGENTS.md 和 docs/ai/README.md。
|
|
112
|
+
需求:修改……
|
|
113
|
+
先运行 cage context 查询需求相关业务,再用 graph inspect/path 消除入口和关系歧义。
|
|
114
|
+
按报告的源码位置阅读条件与实现;说明选中的业务入口、影响范围和图的覆盖边界。
|
|
115
|
+
修改前另存本体图基线,修改后 check、gen、impact,按风险运行相关验证。
|
|
116
|
+
不要只凭名称、连线或测试候选判断需求已完成。
|
|
117
|
+
```
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# 原语与最小业务
|
|
2
|
+
|
|
3
|
+
[返回学习入口](README.md) · [开发步骤](workflow.md)
|
|
4
|
+
|
|
5
|
+
本页代码使用当前公开导出。完整文件示例可直接放入应用;标为“片段”的代码需要结合采购范例已有符号使用。不要把片段中的变量名当成新的框架 API。
|
|
6
|
+
|
|
7
|
+
## 最小的增、查、改、归档业务
|
|
8
|
+
|
|
9
|
+
下面是完整的 `packages/domain/src/modules/note/note.ts`。它演示带部门范围的便笺,不影响采购范例原有业务;需要此业务时才添加文件。
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { aggregate, command, ref, scope, states, text } from '@yss123/cage-kernel';
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
|
|
15
|
+
export const Note = aggregate('Note', {
|
|
16
|
+
description: '部门便笺',
|
|
17
|
+
fields: {
|
|
18
|
+
title: text(120),
|
|
19
|
+
deptId: ref('Dept'),
|
|
20
|
+
createdBy: ref('User'),
|
|
21
|
+
},
|
|
22
|
+
states: states('active', 'archived', { terminal: ['archived'] }),
|
|
23
|
+
// 指明部门字段。最终能看哪个部门,由角色的数据范围决定。
|
|
24
|
+
scope: scope.deptSubtree('deptId'),
|
|
25
|
+
invariants: { titleNotBlank: (o) => o.title.trim().length > 0 },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const createNote = command(Note, {
|
|
29
|
+
// 使用独立导出名避免多个文件 export * 时 create 重名;能力仍叫 Note.create。
|
|
30
|
+
name: 'create',
|
|
31
|
+
from: 'new',
|
|
32
|
+
to: 'active',
|
|
33
|
+
input: z.object({ title: z.string().trim().min(1).max(120) }),
|
|
34
|
+
// 新建草稿已有输入字段;deptId 和 createdBy 由内核按当前主体填入。
|
|
35
|
+
handler: (draft) => draft,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export const updateNote = command(Note, {
|
|
39
|
+
name: 'update',
|
|
40
|
+
from: 'active',
|
|
41
|
+
to: null,
|
|
42
|
+
input: z.object({ id: z.string().uuid(), title: z.string().trim().min(1).max(120) }),
|
|
43
|
+
// 只返回业务变化,状态、版本及审计仍由内核维护。
|
|
44
|
+
handler: (_draft, input) => ({ title: input.title }),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const archiveNote = command(Note, {
|
|
48
|
+
name: 'archive',
|
|
49
|
+
from: 'active',
|
|
50
|
+
to: 'archived',
|
|
51
|
+
// 省略 input 时,已有记录命令默认使用 { id: uuid }。
|
|
52
|
+
handler: (draft) => draft,
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
先在 `packages/domain/src/modules/note/index.ts` 写 `export * from './note.js';`,再在 `packages/domain/src/index.ts` 保留组织模型及已有模块,加入新模块:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
export * from '@yss123/cage-admin/domain';
|
|
60
|
+
export * from './modules/note/index.js';
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
随后 `pnpm exec cage check`、`pnpm gen`,审查迁移后 `pnpm migrate`。管理员给目标角色配置 `Note.create / Note.update / Note.archive / Note.read` 和 `Note` 数据范围;前端使用生成的 `note.create({ title })`、`note.update(id, { title }, version)`、`note.archive(id, version)`、`note.list()`、`note.get(id)`。
|
|
64
|
+
|
|
65
|
+
这里的删除选择“归档”这一业务命令,没有硬删表行。读侧列表默认行为以生成契约与实际过滤参数为准,页面要按需求选择是否展示归档记录。系统默认状态并不意味着自动拥有完整 CRUD 命令。
|
|
66
|
+
|
|
67
|
+
## 字段速查
|
|
68
|
+
|
|
69
|
+
| 字段工厂 | TypeScript 值/用途 | 注意 |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
| `text(max?)` | 字符串 | 最大长度;非空、格式等业务输入规则用 Zod 明确表达 |
|
|
72
|
+
| `int()`、`number()` | 整数、普通数值 | 金额不要用普通浮点运算代替业务金额规则 |
|
|
73
|
+
| `money()` | 十进制字符串 | 输入如 `'1200.00'`,PG numeric 读出仍是字符串 |
|
|
74
|
+
| `id()` | UUID 字符串 | 业务引用没有声明关系时的 UUID 字段 |
|
|
75
|
+
| `ref('Dept')` | 引用聚合的 UUID | 用字符串名可避免循环导入;目标聚合需从入口导出 |
|
|
76
|
+
| `ref('User').unique()` | 租户内至多一对一 | 外键加租户复合唯一索引;不要求目标一定被引用 |
|
|
77
|
+
| `hasMany('NoteComment', { mappedBy: 'noteId' })` | 一对多反查 | 只读集合,外键在目标上;不占主表列 |
|
|
78
|
+
| `manyToMany('User', { description: '协作成员' })` | 多对多关联 | 命令草稿为 ID 数组;中间表由框架生成,读取独立分页 |
|
|
79
|
+
| `date()`、`datetime()` | 日期/时间字符串 | 遵守字段 schema,计算时明确时区 |
|
|
80
|
+
| `bool()`、`enumOf(...)` | 布尔、枚举 | 有状态迁移语义时用 `states` 而非普通枚举代替 |
|
|
81
|
+
| `list({ sku: text(), qty: int() })` | 对象列表 | 可以表达单据明细;不是自动生成独立子表 |
|
|
82
|
+
| `json()` | 结构不固定的 JSON 对象 | 用于结构化扩展;输入规则用命令 Zod 表达,不要把全部领域结构藏进任意 JSON |
|
|
83
|
+
|
|
84
|
+
字段可链式使用 `.optional()`、`.unique()`、`.sensitive('phone')`、`.hidden()` 等。可用敏感种类以包导出的 `SENSITIVE_KINDS` 为准;hidden 字段不会作为普通公开读字段,sensitive 字段按权限掩码。不要通过前端隐藏来替代它们。
|
|
85
|
+
|
|
86
|
+
集合关系也放在 `fields` 中,但不是普通 SQL 列,不支持上述四种修饰。完整声明、成员命令、分页接口、权限与迁移见[对象关系指南](relations.md)。纯关联不手写 NoteMember 聚合,具有职位、状态等业务属性的关联仍应建业务聚合。
|
|
87
|
+
|
|
88
|
+
不要定义 `id/version/status/createdAt/updatedAt/tenantId` 同名业务字段。聚合名和事件名用大驼峰,字段、命令、查询和任务名用小驼峰,状态名用小写下划线。
|
|
89
|
+
|
|
90
|
+
## 命令的几个关键选项
|
|
91
|
+
|
|
92
|
+
| 选项 | 当前用法 | AI 常见错误 |
|
|
93
|
+
| --- | --- | --- |
|
|
94
|
+
| `from` | `'new'` 或已有状态/状态数组 | 直接在 handler 改状态,绕过生命周期 |
|
|
95
|
+
| `to` | 声明的状态;已有记录可用 `null` 保留状态 | 新建用 `null`,或误认为 null 不检查状态 |
|
|
96
|
+
| `input` | Zod schema;已有记录必须包含 id | 用聚合字段 schema 当成任意命令输入 |
|
|
97
|
+
| `name` | 能力和接口中的命令名覆盖 | 混淆导出变量名与最终 `Aggregate.command` |
|
|
98
|
+
| `invokedBy` | 默认 user;可声明 process/job/external 或数组 | 为了从前端审批而给内部结论加 user |
|
|
99
|
+
| `reads` | `{ 别名: 聚合 }` | 不声明依赖,直接读仓库或连接数据库 |
|
|
100
|
+
| `constraints` | 执行前的硬约束 | 在页面上判断一次就认为后端安全 |
|
|
101
|
+
| `emits` | 事件或 `emit(事件, 映射)` | 已提交后另写消息,失去事务一致性 |
|
|
102
|
+
|
|
103
|
+
handler 参数依次是 `draft, input, deps, { now, actorUserId }`。可返回草稿、部分业务字段或 void;`return fail('STOCK_SHORT', detail)` 表示业务拒绝,整个事务回滚。
|
|
104
|
+
|
|
105
|
+
片段:采购审批读取已声明的库存依赖。
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
reads: { stock: Inventory },
|
|
109
|
+
handler: async (order, _input, { stock }) => {
|
|
110
|
+
for (const item of order.items) {
|
|
111
|
+
const row = await stock.findOne({ sku: item.sku });
|
|
112
|
+
if (!row || row.available < item.qty) return fail('STOCK_SHORT', [item.sku]);
|
|
113
|
+
}
|
|
114
|
+
return order;
|
|
115
|
+
},
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
使用 `deps.stock.get/findOne/find` 读取声明的依赖;不要将其扩展为写仓库。这个片段只是单次库存检查,不保证多订单并发扣减安全;生产扣减需要专门设计库存命令和并发控制。
|
|
119
|
+
|
|
120
|
+
## 约束与不变量
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
// 片段:放在采购 approve 的配置中。
|
|
124
|
+
invokedBy: 'process',
|
|
125
|
+
constraints: [sod.notSameAs('createdBy'), ceiling('amount')],
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`sod` 防止提单人审批自己;`ceiling` 读取审批人该能力上的额度参数,没有额度也拒绝。管理员在 `/admin` 给角色配置 `PurchaseOrder.approve` 的 `ceiling`,不是把额度写死进页面。
|
|
129
|
+
|
|
130
|
+
不变量写在 `aggregate(..., { invariants: { 名称: 对象 => boolean } })`,在最终结果合并和目标状态确定后检查。例如 `Number(o.amount) > 0`。约束回答“这次操作允许吗”,不变量回答“结果对象合法吗”。可用 `Constraint` 类型定义自定义约束对象;先核对公开导出,不假设所有内部 helper 都是公共 API。
|
|
131
|
+
|
|
132
|
+
## 事件与资源反应
|
|
133
|
+
|
|
134
|
+
片段来自采购范例,完整版本见 `modules/purchase/purchase-order.ts`、`workflows/purchase-inventory.ts`(相对 domain src)。
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
// 事件中只放下游真正需要的信息;不要拷贝整个对象。
|
|
138
|
+
export const OrderCancelled = event('OrderCancelled', {
|
|
139
|
+
orderId: id(),
|
|
140
|
+
previousStatus: text(),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// 命令中通过 emit 显式提供旧状态,用它判断此前是否持有资源。
|
|
144
|
+
emits: [emit(OrderCancelled, (order, _input, previous) => ({
|
|
145
|
+
orderId: order.id,
|
|
146
|
+
previousStatus: previous.status,
|
|
147
|
+
}))],
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
export const lockOnApprove = reaction(OrderApproved, {
|
|
152
|
+
command: acquireLock,
|
|
153
|
+
tx: 'same',
|
|
154
|
+
acquires: 'inventory.lock',
|
|
155
|
+
map: (e) => ({ id: e.orderId, orderId: e.orderId, items: e.items }),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
export const releaseOnCancel = reaction(OrderCancelled, {
|
|
159
|
+
command: releaseLock,
|
|
160
|
+
releases: 'inventory.lock',
|
|
161
|
+
// 草稿取消时从未建锁,不应对不存在的锁调用释放命令。
|
|
162
|
+
map: (e) => e.previousStatus === 'approved' ? { id: e.orderId } : null,
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`map` 是纯映射,可返回 null 表示此次不执行;不能在映射中调用 HTTP。每条离开持有状态的迁移都应释放资源,或进入同样持有资源的状态。新增“撤回”时别只复制状态迁移,还要检查释放路径。
|
|
167
|
+
|
|
168
|
+
事件默认载荷映射的具体命名规则见真实 `event.ts` 与范例;映射不明确时使用 `emit` 显式声明。已部署事件只做兼容扩展,新增字段设 optional;不兼容含义用新事件并考虑旧 outbox 消费,不能只改消费者。
|
|
169
|
+
|
|
170
|
+
## 两种查询写法
|
|
171
|
+
|
|
172
|
+
下面两段均为完整声明,可选择需要的写法。它们都走只读连接与安全视图;调用人需要 `PurchaseOrder.read`,并受其范围与掩码限制。
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import { query } from '@yss123/cage-kernel';
|
|
176
|
+
import { z } from 'zod';
|
|
177
|
+
import { PurchaseOrder } from './purchase-order.js';
|
|
178
|
+
|
|
179
|
+
export const poByStatus = query('poByStatus', {
|
|
180
|
+
reads: [PurchaseOrder],
|
|
181
|
+
input: z.object({ status: z.string() }),
|
|
182
|
+
sql: `select dept_id, count(*)::int as n
|
|
183
|
+
from safe.purchase_order where status = :status group by dept_id`,
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
import { query } from '@yss123/cage-kernel';
|
|
189
|
+
import { z } from 'zod';
|
|
190
|
+
import { PurchaseOrder } from './purchase-order.js';
|
|
191
|
+
|
|
192
|
+
export const recentOrders = query('recentOrders', {
|
|
193
|
+
reads: [PurchaseOrder],
|
|
194
|
+
input: z.object({ status: z.string() }),
|
|
195
|
+
build: (db, input) => db.selectFrom('safe.purchase_order')
|
|
196
|
+
.select(['id', 'amount', 'status'])
|
|
197
|
+
.where('status', '=', input.status)
|
|
198
|
+
.orderBy('created_at', 'desc')
|
|
199
|
+
.limit(20),
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
`sql` 与 `build` 必须二选一。SQL 用数据库的 snake_case 列名和 `:参数名`;不要拼接输入,也不要查询 `core.*`/`sys.*` 基表。`reads` 列出所有涉及的聚合,不能漏掉 join 的对象。业务前端调用生成的 `queries.*`,不复制查询 SQL。
|
|
204
|
+
|
|
205
|
+
## 审批绑定与选人
|
|
206
|
+
|
|
207
|
+
下面是完整的采购路由文件形状,使用同目录现有的采购符号:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
import { approval } from '@yss123/cage-kernel';
|
|
211
|
+
import { approver, gt, route, step, when } from '@yss123/cage-approval';
|
|
212
|
+
import { PurchaseOrder, OrderSubmitted, OrderCancelled, approve, reject } from './purchase-order.js';
|
|
213
|
+
|
|
214
|
+
export const poApproval = approval(PurchaseOrder, {
|
|
215
|
+
process: 'po-approval',
|
|
216
|
+
start: OrderSubmitted,
|
|
217
|
+
cancelOn: [OrderCancelled],
|
|
218
|
+
routable: ['amount', 'deptId'],
|
|
219
|
+
onApproved: approve,
|
|
220
|
+
onRejected: reject,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
export const poRoute = route('po-approval', [
|
|
224
|
+
step('部门经理审批', approver.deptLeader('deptId')),
|
|
225
|
+
when(gt('amount', 50000), step('财务审批', approver.role('finance'), { mode: 'any' })),
|
|
226
|
+
]);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
`approval` 绑定事件与结论命令;`route` 决定步骤与人;`routable` 限制交给路由的数据字段。`onApproved/onRejected` 必须属于同一聚合,且允许 process 调用。
|
|
230
|
+
|
|
231
|
+
内置选人器是 `approver.deptLeader(字段名)`、`approver.role(角色code)`、`approver.user(用户id)`。条件使用 `gt/gte/lt/lte/eq/ne/isIn` 等封闭表达式。`mode: 'any'/'all'` 描述一步内的人如何完成,不表示任意并行分支。可以在代码组合这些能力;任意函数选人、转签和加签需框架扩展或外部审批适配器,当前不能直接写成现成 API。
|
|
232
|
+
|
|
233
|
+
被选中的审批人还要具备结论命令所需能力、范围和额度。部门负责人为空会产生 `CAGE-APPR-007`,详见[排错](troubleshooting.md)。
|
|
234
|
+
|
|
235
|
+
## 连接器、定时任务和逃生口
|
|
236
|
+
|
|
237
|
+
| 能力 | 声明结构 | 标准范例 |
|
|
238
|
+
| --- | --- | --- |
|
|
239
|
+
| 连接器 | `connector(name, { hosts, secret: 密钥名, operations: { 操作: op(...) } })` | `integrations/erp.ts` |
|
|
240
|
+
| 出站 | `outbound(事件, 操作, { map, idempotencyKey, onSuccess })` | ERP 推单,成功用 external 命令回写 |
|
|
241
|
+
| 入站 | `inbound(连接器, 事件名, { verify, payload, command, map })` | HMAC 验签的收货回调 |
|
|
242
|
+
| 定时任务 | `job(name, { cron, reads, calls, run })` | `jobs.ts` 过期草稿取消 |
|
|
243
|
+
| 逃生口 | `unsafe({ reason, owner }, () => ...)` | 仅用于声明无法表达且评审过的特殊情况 |
|
|
244
|
+
|
|
245
|
+
连接器 host 写主机名而非完整 URL,操作 path 从 `/` 开始,request/response 均用 schema 白名单,密钥只写环境变量名。`effect: 'retryableWrite'` 仍需要第三方幂等协议,不是承诺远端恰好一次。
|
|
246
|
+
|
|
247
|
+
job 的 `run({ query, call, now })` 只能读 `reads` 和调用 `calls` 中的命令。用户命令可以被显式声明了它的系统任务调用;仅允许内部身份的命令还需包含 job 身份。最终仍走写流水线。不要通过新增脚本直接扫表改业务状态。
|
|
248
|
+
|
|
249
|
+
`unsafe` 只记录原因和负责人并进入预算,不自动授予数据库、网络或用户权限,也不让 lint 边界失效。能用声明表达的普通业务不应使用它。
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# 工程目录与代码组织
|
|
2
|
+
|
|
3
|
+
本规范针对 create-cage-app 生成的业务项目。目录首先表达业务归属,其次才是页面、组件、hooks 等技术用途。新增报销功能时,应在报销模块内找到它的页面和表单,而不是在整个 src 中搜索一批散落文件。
|
|
4
|
+
|
|
5
|
+
两种模板使用同一规则:采购模板提供完整业务,空白模板保留认证和工作台。不要为小功能创建空目录;文件确实承担独立职责时再拆分。
|
|
6
|
+
|
|
7
|
+
## 项目分工
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
项目根目录/
|
|
11
|
+
├─ apps/
|
|
12
|
+
│ ├─ api/src/
|
|
13
|
+
│ │ ├─ main.ts # 启动 HTTP,装配 domain,处理退出
|
|
14
|
+
│ │ └─ worker.ts # 启动异步任务,按环境装配连接器 fake
|
|
15
|
+
│ └─ web/src/
|
|
16
|
+
│ ├─ main.tsx # 挂载 React、全局 Provider 和样式
|
|
17
|
+
│ ├─ app/ # 会话恢复、布局、导航、模块组合
|
|
18
|
+
│ │ ├─ app.tsx
|
|
19
|
+
│ │ ├─ workspace-layout.tsx
|
|
20
|
+
│ │ └─ styles/global.css
|
|
21
|
+
│ ├─ modules/ # 按业务命名,一个模块内管理它的页面和交互
|
|
22
|
+
│ │ ├─ auth/
|
|
23
|
+
│ │ │ ├─ index.ts
|
|
24
|
+
│ │ │ └─ pages/login-page.tsx
|
|
25
|
+
│ │ └─ purchase/
|
|
26
|
+
│ │ ├─ index.ts # 模块对应用壳公开的入口
|
|
27
|
+
│ │ ├─ pages/purchase-orders-page.tsx
|
|
28
|
+
│ │ └─ components/purchase-order-form.tsx
|
|
29
|
+
│ └─ shared/ # 已被复用、与具体业务无关的前端代码
|
|
30
|
+
│ ├─ ui/ # 提示容器等通用组件
|
|
31
|
+
│ ├─ hooks/ # 通用交互 hooks
|
|
32
|
+
│ └─ lib/ # 格式化、通用错误展示等纯辅助函数
|
|
33
|
+
├─ packages/
|
|
34
|
+
│ ├─ domain/src/
|
|
35
|
+
│ │ ├─ index.ts # 全部声明的统一导出,仅负责注册组合
|
|
36
|
+
│ │ ├─ modules/
|
|
37
|
+
│ │ │ ├─ purchase/
|
|
38
|
+
│ │ │ │ ├─ index.ts
|
|
39
|
+
│ │ │ │ ├─ purchase-order.ts # 聚合、状态、事件、命令
|
|
40
|
+
│ │ │ │ ├─ approvals.ts # 本业务审批绑定和代码路由
|
|
41
|
+
│ │ │ │ ├─ queries.ts # 本业务自定义安全查询
|
|
42
|
+
│ │ │ │ ├─ jobs.ts # 本业务定时任务
|
|
43
|
+
│ │ │ │ └─ purchase-order.test.ts
|
|
44
|
+
│ │ │ └─ inventory/
|
|
45
|
+
│ │ │ ├─ index.ts
|
|
46
|
+
│ │ │ └─ inventory.ts
|
|
47
|
+
│ │ ├─ workflows/ # 跨模块事件反应,例如采购通过后锁库存
|
|
48
|
+
│ │ │ └─ purchase-inventory.ts
|
|
49
|
+
│ │ ├─ integrations/ # 第三方协议、出站映射和入站验签声明
|
|
50
|
+
│ │ │ └─ erp.ts
|
|
51
|
+
│ │ └─ shared/ # 按需创建:跨领域模块的纯校验或业务值定义
|
|
52
|
+
│ └─ contracts/src/ # 全部由 cage gen 维护
|
|
53
|
+
│ ├─ index.ts # @app/contracts 统一公共入口
|
|
54
|
+
│ ├─ aggregates/
|
|
55
|
+
│ │ ├─ purchase-order/index.ts
|
|
56
|
+
│ │ ├─ inventory/index.ts
|
|
57
|
+
│ │ └─ user/index.ts
|
|
58
|
+
│ └─ queries/index.ts
|
|
59
|
+
├─ db/migrations/
|
|
60
|
+
└─ docs/ai/、docs/errors/
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
树中的采购文件可在采购模板中运行;空白模板把它们作为离线参考放在 `docs/examples/purchase-admin`,真实源码只有认证、工作台和组织模型。空白项目没有需要用到的目录可以不创建。
|
|
64
|
+
|
|
65
|
+
## 新需求具体写到哪里
|
|
66
|
+
|
|
67
|
+
以“报销单列表、新建报销、经理审批”为例:
|
|
68
|
+
|
|
69
|
+
1. 在 `packages/domain/src/modules/expense/expense.ts` 声明 Expense、状态、事件和命令;小模块可先放在这个文件,不强制把每条命令拆成一个文件。
|
|
70
|
+
2. 审批声明放同模块 `approvals.ts`;查询、定时任务有实际需求再增加 `queries.ts`、`jobs.ts`。通过模块 `index.ts` 导出,再从 domain 根入口转导出。导出名全局唯一,命令重名时用显式 `name` 保留能力点语义。
|
|
71
|
+
3. 需要驱动其他模块时,把 reaction 放在 `workflows/expense-xxx.ts`,从双方模块公开入口取声明。第三方接口放 `integrations/<系统名>.ts`,声明 hosts、schema、secret 名、outbound/inbound,不直接发网络请求。
|
|
72
|
+
4. 运行 `pnpm gen`,审查 `contracts/src/aggregates/expense/index.ts` 和迁移草稿。根 `@app/contracts` 自动导出 Expense API/类型。不要在 contracts 里手工建“报销 service”。
|
|
73
|
+
5. 在 `apps/web/src/modules/expense/pages/expense-list-page.tsx` 使用生成 hooks。新建表单放该模块 `components/expense-form.tsx`,模块专用 hooks 放自己的 `hooks`,专用接口封装按需放自己的 `api`,专用常量留在模块内。
|
|
74
|
+
6. 从前端模块 `index.ts` 导出页面,应用壳从 `../modules/expense` 引入并接入导航。页面自己拥有筛选、分页、弹窗和提交状态;不要把它们全移到 App 或 Workspace。
|
|
75
|
+
|
|
76
|
+
前后端可以有相同的业务模块名,但文件不混放:领域定义授权和行为,前端管理交互,通过生成契约连接。API 启动入口维持薄壳,普通业务无需新增 controller/service/repository。
|
|
77
|
+
|
|
78
|
+
## 依赖方向与模块入口
|
|
79
|
+
|
|
80
|
+
| 来源 | 可以依赖 | 不应依赖 |
|
|
81
|
+
| --- | --- | --- |
|
|
82
|
+
| Web `main.tsx` | app、共享基础设施、框架前端 Provider | 各项业务页面与表单 |
|
|
83
|
+
| Web `app` | 模块公开入口、shared、应用内部布局 | 模块内部表单/hooks 文件 |
|
|
84
|
+
| Web `modules/<业务>` | 自己的内部文件、shared、contracts/client | 其他前端模块、app、domain/数据库包 |
|
|
85
|
+
| Web `shared` | shared 内部文件、通用前端库与通用 client 类型 | app、具体业务模块 |
|
|
86
|
+
| Domain `modules/<业务>` | 自己内部文件、其他领域模块的公开声明、纯 shared、框架声明 API | workflows、integrations、domain 总入口、IO、contracts |
|
|
87
|
+
| Domain `workflows` | 领域模块公开声明、纯 shared、框架 reaction API | HTTP 实现、页面、数据库写入 |
|
|
88
|
+
| Domain `integrations` | 领域模块公开声明、纯 shared、连接器 API | workflows、HTTP 实现、直接网络请求 |
|
|
89
|
+
| Domain `shared` | 纯 shared、基础值/校验库 | 具体模块和组合层、IO |
|
|
90
|
+
|
|
91
|
+
前端两项业务需要一起展示时,由 app 同时取它们的公开组件/hooks,通过 props 和回调传数据。采购页的“我的待办”数量就是由应用壳传入的,不让采购模块导入审批页面。遇到真实复用的业务组件,先明确归属和输入输出,不把整页挪到 shared 来绕过边界。
|
|
92
|
+
|
|
93
|
+
领域模块允许单向业务依赖,例如采购审核声明 `reads: { stock: Inventory }`,通过 `../inventory/index.js` 导入。不要从 domain 总入口反向导入,否则容易形成循环注册。双向依赖优先改为 workflows 组合或共享值,不用层层转导出掩盖循环。
|
|
94
|
+
|
|
95
|
+
`index.ts` 只列出对外导出。禁止把模块的全部内部实现都公开来规避边界;不在 index 中启动服务、读环境变量或初始化业务。
|
|
96
|
+
|
|
97
|
+
## 什么放 shared
|
|
98
|
+
|
|
99
|
+
一个辅助函数只被报销使用,就先留在报销模块。多个模块实际复用、且没有绑定某项业务时,再移到 shared。例如通用提示容器、命令结果反馈 hook、金额格式化可以共享;报销状态文案、采购请求、审批选人规则属于各自模块。
|
|
100
|
+
|
|
101
|
+
不要新增包罗万象的 `utils.ts`、`common.tsx`、`lists.tsx`、`api.ts` 收集所有业务。用能说明职责的文件名,例如 `error-message.ts`、`purchase-order-form.tsx`、`use-approval-tasks.ts`。
|
|
102
|
+
|
|
103
|
+
## 命名、文件与注释
|
|
104
|
+
|
|
105
|
+
- 目录和源码文件用 `kebab-case`;React 组件/类型用 `PascalCase`,函数、变量与 hook 用 `camelCase`,hook 以 `use` 开头。聚合、命令、事件名是稳定的业务标识,目录改名不顺便改能力点、API 或表名。
|
|
106
|
+
- 页面放 `pages/*-page.tsx`,模块专用组件放 `components`,专用 hooks 放 `hooks`。小模块只有一个页面时可以直接 `modules/<业务>/<业务>-page.tsx` 并配公开 index;不强制创建所有子目录。
|
|
107
|
+
- 入口只挂载或组合;页面主要描述页面流程。表单、表格或交互已承担独立职责时再抽取,避免一个大组件处理所有业务,也避免每个按钮都新建文件。
|
|
108
|
+
- 全局主题、reset、基础布局放 `app/styles`;业务专用样式与组件就近放置,可用 CSS Modules。不要把所有新增业务样式长期追加进一个全局文件。当前范例保留既有全局主题,结构调整不重新设计界面。
|
|
109
|
+
- 单测与被测代码相邻,使用 `*.test.ts(x)`。需要真实业务链路的集成用例与所属业务模块相邻;框架的测试发现递归查找,不必为它回到 src 根目录。
|
|
110
|
+
- 新代码写清职责、关键流程和设计原因,特别是事务边界、状态迁移、缓存刷新和权限展示。不要给每行语法写翻译,不加无业务价值的防御分支和静默兜底;脚本使用 TypeScript,不新增 `.mjs`。
|
|
111
|
+
|
|
112
|
+
## 自动约束与整理已有项目
|
|
113
|
+
|
|
114
|
+
新模板在 `cage.config.json` 设置 `"structure": "modular"`。`pnpm exec cage check` 与 `pnpm verify` 自动检查:
|
|
115
|
+
|
|
116
|
+
- `CAGE-STRUCT-001`:domain src 根目录只保留领域入口,web src 根目录只保留 `main.tsx`;其他运行源码使用上述顶层目录。`*.d.ts` 类型声明与相邻测试不受入口限制。
|
|
117
|
+
- `CAGE-STRUCT-002`:阻止共享层反向依赖、前端模块直接互相导入、领域模块依赖流程/连接器、跨模块穿透内部文件。相对路径、tsconfig 别名和转导出都参与模块解析。
|
|
118
|
+
|
|
119
|
+
这些检查约束目录和可见导入,不能替代评审,也不按行数强制拆文件。通过错误码定位文件,再按职责整理,不要关闭门禁或把文件塞进 shared 来消除提示。框架已有的依赖门禁继续约束 IO、前端依赖和生成物边界。
|
|
120
|
+
|
|
121
|
+
已有项目可以先移动文件并修正导入,再启用该配置;CLI 升级不会自动搬动客户代码。生成契约布局由新版 `cage gen` 更新:保留 `@app/contracts` 公共 API,清理带 Cage 生成签名的过期文件,用户手写文件保留。不要依赖 contracts 内部路径导入。
|
|
122
|
+
|
|
123
|
+
目录调整后运行类型检查、`cage check`、`pnpm gen` 和生成一致性检查;有改动页面时检查相应操作。目录整理不应产生数据库结构变化,若出现迁移草稿应核实原因,已执行的迁移不要改写。
|