@yss123/cage-cli 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +18 -2
- package/bin/cage.js +6 -0
- package/bin/create-cage-app.js +6 -0
- package/dist/analyze.d.ts +18 -0
- package/dist/analyze.js +26 -0
- package/dist/analyze.js.map +1 -0
- package/dist/baseline.d.ts +5 -0
- package/dist/baseline.js +50 -0
- package/dist/baseline.js.map +1 -0
- package/dist/budget.d.ts +7 -0
- package/dist/budget.js +52 -0
- package/dist/budget.js.map +1 -0
- package/dist/config.d.ts +30 -0
- package/dist/config.js +47 -0
- package/dist/config.js.map +1 -0
- package/dist/create.d.ts +1 -0
- package/dist/create.js +47 -0
- package/dist/create.js.map +1 -0
- package/dist/database.d.ts +5 -0
- package/dist/database.js +70 -0
- package/dist/database.js.map +1 -0
- package/dist/derive.d.ts +14 -0
- package/dist/derive.js +163 -0
- package/dist/derive.js.map +1 -0
- package/dist/dev.d.ts +2 -0
- package/dist/dev.js +155 -0
- package/dist/dev.js.map +1 -0
- package/dist/env.d.ts +1 -0
- package/dist/env.js +15 -0
- package/dist/env.js.map +1 -0
- package/dist/findings.d.ts +11 -0
- package/dist/findings.js +13 -0
- package/dist/findings.js.map +1 -0
- package/dist/gen/contracts.d.ts +6 -0
- package/dist/gen/contracts.js +167 -0
- package/dist/gen/contracts.js.map +1 -0
- package/dist/gen/index.d.ts +19 -0
- package/dist/gen/index.js +137 -0
- package/dist/gen/index.js.map +1 -0
- package/dist/gen/zod-code.d.ts +7 -0
- package/dist/gen/zod-code.js +161 -0
- package/dist/gen/zod-code.js.map +1 -0
- package/dist/graph-snapshot.d.ts +13 -0
- package/dist/graph-snapshot.js +46 -0
- package/dist/graph-snapshot.js.map +1 -0
- package/dist/graph.d.ts +11 -0
- package/dist/graph.js +87 -0
- package/dist/graph.js.map +1 -0
- package/dist/impact.d.ts +28 -0
- package/dist/impact.js +223 -0
- package/dist/impact.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/load.d.ts +6 -0
- package/dist/load.js +17 -0
- package/dist/load.js.map +1 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +176 -0
- package/dist/main.js.map +1 -0
- package/dist/migrate.d.ts +7 -0
- package/dist/migrate.js +69 -0
- package/dist/migrate.js.map +1 -0
- package/dist/ontology.d.ts +41 -0
- package/dist/ontology.js +270 -0
- package/dist/ontology.js.map +1 -0
- package/dist/process.d.ts +6 -0
- package/dist/process.js +39 -0
- package/dist/process.js.map +1 -0
- package/dist/rules.d.ts +23 -0
- package/dist/rules.js +251 -0
- package/dist/rules.js.map +1 -0
- package/dist/static-check.d.ts +23 -0
- package/dist/static-check.js +225 -0
- package/dist/static-check.js.map +1 -0
- package/dist/sync.d.ts +5 -0
- package/dist/sync.js +46 -0
- package/dist/sync.js.map +1 -0
- package/dist/upgrade.d.ts +10 -0
- package/dist/upgrade.js +81 -0
- package/dist/upgrade.js.map +1 -0
- package/dist/verify.d.ts +7 -0
- package/dist/verify.js +88 -0
- package/dist/verify.js.map +1 -0
- package/dist/version.d.ts +4 -0
- package/dist/version.js +6 -0
- package/dist/version.js.map +1 -0
- package/graph-ui/app.js +1065 -0
- package/graph-ui/index.html +197 -0
- package/graph-ui/model.js +141 -0
- package/graph-ui/relations.js +125 -0
- package/graph-ui/structure.js +74 -0
- package/graph-ui/style.css +1698 -0
- package/managed/errors/CAGE-APPR-001.md +32 -0
- package/managed/errors/CAGE-APPR-002.md +32 -0
- package/managed/errors/CAGE-APPR-003.md +32 -0
- package/managed/errors/CAGE-APPR-004.md +32 -0
- package/managed/errors/CAGE-APPR-005.md +32 -0
- package/managed/errors/CAGE-APPR-006.md +32 -0
- package/managed/errors/CAGE-APPR-007.md +32 -0
- package/managed/errors/CAGE-APPR-010.md +32 -0
- package/managed/errors/CAGE-AUTH-001.md +32 -0
- package/managed/errors/CAGE-BUDGET-001.md +32 -0
- package/managed/errors/CAGE-BUDGET-002.md +32 -0
- package/managed/errors/CAGE-BUDGET-003.md +32 -0
- package/managed/errors/CAGE-CMD-002.md +32 -0
- package/managed/errors/CAGE-CMD-003.md +34 -0
- package/managed/errors/CAGE-CONN-001.md +32 -0
- package/managed/errors/CAGE-CONN-002.md +32 -0
- package/managed/errors/CAGE-CONN-003.md +32 -0
- package/managed/errors/CAGE-CONN-004.md +32 -0
- package/managed/errors/CAGE-CONN-005.md +32 -0
- package/managed/errors/CAGE-CONN-006.md +32 -0
- package/managed/errors/CAGE-CONN-007.md +32 -0
- package/managed/errors/CAGE-CONN-008.md +32 -0
- package/managed/errors/CAGE-CONN-009.md +32 -0
- package/managed/errors/CAGE-CONN-010.md +32 -0
- package/managed/errors/CAGE-CONN-011.md +32 -0
- package/managed/errors/CAGE-CONN-012.md +32 -0
- package/managed/errors/CAGE-CREATE-001.md +32 -0
- package/managed/errors/CAGE-DECL-001.md +34 -0
- package/managed/errors/CAGE-DECL-002.md +34 -0
- package/managed/errors/CAGE-DECL-003.md +34 -0
- package/managed/errors/CAGE-DECL-004.md +34 -0
- package/managed/errors/CAGE-DEP-001.md +34 -0
- package/managed/errors/CAGE-DEP-002.md +34 -0
- package/managed/errors/CAGE-DEP-003.md +34 -0
- package/managed/errors/CAGE-DEP-004.md +34 -0
- package/managed/errors/CAGE-DEP-005.md +34 -0
- package/managed/errors/CAGE-DLQ-001.md +32 -0
- package/managed/errors/CAGE-ENV-001.md +32 -0
- package/managed/errors/CAGE-EVENT-001.md +32 -0
- package/managed/errors/CAGE-EVENT-002.md +32 -0
- package/managed/errors/CAGE-FIELD-001.md +32 -0
- package/managed/errors/CAGE-FIELD-002.md +32 -0
- package/managed/errors/CAGE-FIELD-003.md +32 -0
- package/managed/errors/CAGE-FIELD-004.md +32 -0
- package/managed/errors/CAGE-GEN-001.md +33 -0
- package/managed/errors/CAGE-GEN-002.md +33 -0
- package/managed/errors/CAGE-GRANT-001.md +32 -0
- package/managed/errors/CAGE-GRANT-002.md +32 -0
- package/managed/errors/CAGE-GRANT-003.md +32 -0
- package/managed/errors/CAGE-IMPACT-001.md +32 -0
- package/managed/errors/CAGE-JOB-001.md +32 -0
- package/managed/errors/CAGE-JOB-002.md +32 -0
- package/managed/errors/CAGE-JOB-003.md +32 -0
- package/managed/errors/CAGE-LINT-001.md +32 -0
- package/managed/errors/CAGE-MIG-001.md +32 -0
- package/managed/errors/CAGE-MIG-002.md +32 -0
- package/managed/errors/CAGE-MIG-003.md +32 -0
- package/managed/errors/CAGE-MIG-004.md +32 -0
- package/managed/errors/CAGE-MIG-005.md +32 -0
- package/managed/errors/CAGE-NAME-001.md +32 -0
- package/managed/errors/CAGE-NAME-002.md +32 -0
- package/managed/errors/CAGE-NAME-003.md +32 -0
- package/managed/errors/CAGE-NAME-004.md +32 -0
- package/managed/errors/CAGE-NAME-005.md +32 -0
- package/managed/errors/CAGE-NAME-006.md +32 -0
- package/managed/errors/CAGE-ORG-001.md +32 -0
- package/managed/errors/CAGE-PAIR-002.md +33 -0
- package/managed/errors/CAGE-QUERY-001.md +32 -0
- package/managed/errors/CAGE-QUERY-002.md +32 -0
- package/managed/errors/CAGE-QUERY-003.md +32 -0
- package/managed/errors/CAGE-REACT-001.md +32 -0
- package/managed/errors/CAGE-REACT-002.md +32 -0
- package/managed/errors/CAGE-READS-001.md +32 -0
- package/managed/errors/CAGE-REG-001.md +32 -0
- package/managed/errors/CAGE-REG-002.md +32 -0
- package/managed/errors/CAGE-REG-003.md +32 -0
- package/managed/errors/CAGE-ROUTE-001.md +32 -0
- package/managed/errors/CAGE-SCOPE-001.md +32 -0
- package/managed/errors/CAGE-SCOPE-002.md +32 -0
- package/managed/errors/CAGE-SECRET-001.md +32 -0
- package/managed/errors/CAGE-SECRET-002.md +32 -0
- package/managed/errors/CAGE-SENS-001.md +32 -0
- package/managed/errors/CAGE-SQL-001.md +32 -0
- package/managed/errors/CAGE-SQL-002.md +32 -0
- package/managed/errors/CAGE-STATE-001.md +32 -0
- package/managed/errors/CAGE-STATE-002.md +32 -0
- package/managed/errors/CAGE-STATE-003.md +32 -0
- package/managed/errors/CAGE-STATE-004.md +32 -0
- package/managed/errors/CAGE-STATE-005.md +32 -0
- package/managed/errors/CAGE-STATE-010.md +32 -0
- package/managed/errors/CAGE-STATE-011.md +32 -0
- package/managed/errors/CAGE-SYNC-001.md +35 -0
- package/managed/errors/CAGE-TEST-001.md +33 -0
- package/managed/errors/CAGE-UNSAFE-001.md +32 -0
- package/managed/errors/CAGE-UPGRADE-001.md +32 -0
- package/managed/errors/README.md +99 -0
- package/package.json +60 -3
- package/templates/app/.dockerignore +10 -0
- package/templates/app/.env.example +17 -0
- package/templates/app/.github/workflows/cage.yml +44 -0
- package/templates/app/AGENTS.md +13 -0
- package/templates/app/Dockerfile +23 -0
- package/templates/app/README.md +58 -0
- package/templates/app/apps/api/package.json +16 -0
- package/templates/app/apps/api/src/main.ts +10 -0
- package/templates/app/apps/api/src/worker.ts +14 -0
- package/templates/app/apps/api/tsconfig.json +5 -0
- package/templates/app/apps/web/index.html +13 -0
- package/templates/app/apps/web/package.json +22 -0
- package/templates/app/apps/web/src/main.tsx +552 -0
- package/templates/app/apps/web/src/style.css +663 -0
- package/templates/app/apps/web/tsconfig.json +11 -0
- package/templates/app/apps/web/vite.config.ts +16 -0
- package/templates/app/cage.config.json +114 -0
- package/templates/app/compose.yaml +39 -0
- package/templates/app/db/migrations/0001_init.sql +112 -0
- package/templates/app/package.json +25 -0
- package/templates/app/packages/contracts/package.json +16 -0
- package/templates/app/packages/contracts/src/dept.ts +74 -0
- package/templates/app/packages/contracts/src/index.ts +81 -0
- package/templates/app/packages/contracts/src/inventory-lock.ts +52 -0
- package/templates/app/packages/contracts/src/inventory.ts +57 -0
- package/templates/app/packages/contracts/src/purchase-order.ts +72 -0
- package/templates/app/packages/contracts/src/queries.ts +17 -0
- package/templates/app/packages/contracts/src/role.ts +85 -0
- package/templates/app/packages/contracts/src/user.ts +89 -0
- package/templates/app/packages/contracts/tsconfig.json +5 -0
- package/templates/app/packages/domain/package.json +23 -0
- package/templates/app/packages/domain/src/approvals.ts +18 -0
- package/templates/app/packages/domain/src/erp.ts +36 -0
- package/templates/app/packages/domain/src/index.ts +9 -0
- package/templates/app/packages/domain/src/inventory.ts +43 -0
- package/templates/app/packages/domain/src/jobs.ts +13 -0
- package/templates/app/packages/domain/src/purchase-order.test.ts +137 -0
- package/templates/app/packages/domain/src/purchase-order.ts +122 -0
- package/templates/app/packages/domain/src/queries.ts +10 -0
- package/templates/app/packages/domain/src/reactions.ts +24 -0
- package/templates/app/packages/domain/tsconfig.json +5 -0
- package/templates/app/pnpm-workspace.yaml +10 -0
- package/templates/app/renovate.json +5 -0
- package/templates/app/tsconfig.base.json +8 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CAGE-NAME-002
|
|
2
|
+
|
|
3
|
+
## 什么时候出现
|
|
4
|
+
|
|
5
|
+
命中以下实现中的检查时触发。终端会附具体声明位置、原因和修复方法。
|
|
6
|
+
|
|
7
|
+
来源:[packages/kernel/src/primitives/aggregate.ts:87](../../packages/kernel/src/primitives/aggregate.ts)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
}
|
|
11
|
+
if (!/^[a-z][A-Za-z0-9]*$/.test(k)) throw declError('CAGE-NAME-002', `字段名 ${name}.${k} 必须是小驼峰`);
|
|
12
|
+
fields[k] = v;
|
|
13
|
+
}
|
|
14
|
+
const states = st ?? DEFAULT_STATES;
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 怎么修
|
|
19
|
+
|
|
20
|
+
聚合、事件使用大驼峰;命令、任务和查询使用小驼峰,且同类名称唯一。
|
|
21
|
+
|
|
22
|
+
## 正例
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
aggregate('PurchaseOrder', definition);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 反例
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
aggregate('purchase-order', definition);
|
|
32
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CAGE-NAME-003
|
|
2
|
+
|
|
3
|
+
## 什么时候出现
|
|
4
|
+
|
|
5
|
+
命中以下实现中的检查时触发。终端会附具体声明位置、原因和修复方法。
|
|
6
|
+
|
|
7
|
+
来源:[packages/kernel/src/primitives/event.ts:19](../../packages/kernel/src/primitives/event.ts)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
export function event<const N extends string, F extends Fields>(name: N, fields: F, opts?: { description?: string }): Event<N, F> {
|
|
11
|
+
if (!/^[A-Z][A-Za-z0-9]*$/.test(name)) throw declError('CAGE-NAME-003', `事件名 ${name} 必须是大驼峰`);
|
|
12
|
+
return Object.freeze({
|
|
13
|
+
[EVENT]: true as const,
|
|
14
|
+
name,
|
|
15
|
+
fields: Object.freeze({ ...fields }),
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 怎么修
|
|
19
|
+
|
|
20
|
+
聚合、事件使用大驼峰;命令、任务和查询使用小驼峰,且同类名称唯一。
|
|
21
|
+
|
|
22
|
+
## 正例
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
aggregate('PurchaseOrder', definition);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 反例
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
aggregate('purchase-order', definition);
|
|
32
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CAGE-NAME-004
|
|
2
|
+
|
|
3
|
+
## 什么时候出现
|
|
4
|
+
|
|
5
|
+
命中以下实现中的检查时触发。终端会附具体声明位置、原因和修复方法。
|
|
6
|
+
|
|
7
|
+
来源:[packages/kernel/src/primitives/command.ts:145](../../packages/kernel/src/primitives/command.ts)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
if (def.name !== undefined && !/^[a-z][A-Za-z0-9]*$/.test(def.name)) {
|
|
11
|
+
throw declError('CAGE-NAME-004', `命令名 ${def.name} 必须是小驼峰`);
|
|
12
|
+
}
|
|
13
|
+
return Object.freeze({
|
|
14
|
+
[COMMAND]: true as const,
|
|
15
|
+
agg,
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 怎么修
|
|
19
|
+
|
|
20
|
+
聚合、事件使用大驼峰;命令、任务和查询使用小驼峰,且同类名称唯一。
|
|
21
|
+
|
|
22
|
+
## 正例
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
aggregate('PurchaseOrder', definition);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 反例
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
aggregate('purchase-order', definition);
|
|
32
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CAGE-NAME-005
|
|
2
|
+
|
|
3
|
+
## 什么时候出现
|
|
4
|
+
|
|
5
|
+
命中以下实现中的检查时触发。终端会附具体声明位置、原因和修复方法。
|
|
6
|
+
|
|
7
|
+
来源:[packages/kernel/src/primitives/flow.ts:88](../../packages/kernel/src/primitives/flow.ts)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
): Query<I> {
|
|
11
|
+
if (!/^[a-z][A-Za-z0-9]*$/.test(name)) throw declError('CAGE-NAME-005', `查询名 ${name} 必须是小驼峰`);
|
|
12
|
+
if (!def.sql === !def.build) throw declError('CAGE-QUERY-001', `查询 ${name} 必须二选一:sql 或 build`);
|
|
13
|
+
if (def.sql && /\bcore\.|\bsys\./i.test(def.sql)) {
|
|
14
|
+
throw declError('CAGE-QUERY-002', `查询 ${name} 引用了基表,只能引用 safe.* 安全视图`);
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 怎么修
|
|
19
|
+
|
|
20
|
+
聚合、事件使用大驼峰;命令、任务和查询使用小驼峰,且同类名称唯一。
|
|
21
|
+
|
|
22
|
+
## 正例
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
aggregate('PurchaseOrder', definition);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 反例
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
aggregate('purchase-order', definition);
|
|
32
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CAGE-NAME-006
|
|
2
|
+
|
|
3
|
+
## 什么时候出现
|
|
4
|
+
|
|
5
|
+
命中以下实现中的检查时触发。终端会附具体声明位置、原因和修复方法。
|
|
6
|
+
|
|
7
|
+
来源:[packages/kernel/src/primitives/flow.ts:189](../../packages/kernel/src/primitives/flow.ts)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
): Job {
|
|
11
|
+
if (!/^[a-z][A-Za-z0-9]*$/.test(name)) throw declError('CAGE-NAME-006', `任务名 ${name} 必须是小驼峰`);
|
|
12
|
+
if (def.cron.trim().split(/\s+/).length < 5) throw declError('CAGE-JOB-001', `任务 ${name} 的 cron 表达式不合法`);
|
|
13
|
+
return Object.freeze({
|
|
14
|
+
[JOB]: true as const,
|
|
15
|
+
name,
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 怎么修
|
|
19
|
+
|
|
20
|
+
聚合、事件使用大驼峰;命令、任务和查询使用小驼峰,且同类名称唯一。
|
|
21
|
+
|
|
22
|
+
## 正例
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
aggregate('PurchaseOrder', definition);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 反例
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
aggregate('purchase-order', definition);
|
|
32
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CAGE-ORG-001
|
|
2
|
+
|
|
3
|
+
## 什么时候出现
|
|
4
|
+
|
|
5
|
+
命中以下实现中的检查时触发。终端会附具体声明位置、原因和修复方法。
|
|
6
|
+
|
|
7
|
+
来源:[packages/cli/src/rules.ts:211](../../packages/cli/src/rules.ts)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
|
|
11
|
+
/** CAGE-ORG-001:domain 必须包含框架自带的组织模型 */
|
|
12
|
+
export function checkOrg(g: Ontology): Finding[] {
|
|
13
|
+
const has = (id: string) => g.nodes.some((n) => n.kind === 'aggregate' && n.id === id);
|
|
14
|
+
return has('User') && has('Dept') && has('Role')
|
|
15
|
+
? []
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 怎么修
|
|
19
|
+
|
|
20
|
+
从 domain 入口导出框架组织模型。
|
|
21
|
+
|
|
22
|
+
## 正例
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
export * from '@yss123/cage-admin/domain';
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 反例
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
// domain 入口只导出业务聚合,遗漏组织模型
|
|
32
|
+
```
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# CAGE-PAIR-002
|
|
2
|
+
|
|
3
|
+
## 什么时候出现
|
|
4
|
+
|
|
5
|
+
命中以下实现中的检查时触发。终端会附具体声明位置、原因和修复方法。
|
|
6
|
+
|
|
7
|
+
来源:[packages/cli/src/rules.ts:43](../../packages/cli/src/rules.ts)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
|
|
11
|
+
/** CAGE-PAIR-002:进入某状态时获得了资源,从该状态出发的每一条迁移都必须释放它,或进入一个同样持有它的状态 */
|
|
12
|
+
export function checkPairing(g: Ontology): Finding[] {
|
|
13
|
+
const { get } = index(g);
|
|
14
|
+
const findings: Finding[] = [];
|
|
15
|
+
const held = statesHoldingResources(g);
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 怎么修
|
|
19
|
+
|
|
20
|
+
离开持有资源的状态时,发出事件并声明释放该资源的反应;检查每一条离开路径。
|
|
21
|
+
|
|
22
|
+
## 正例
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
reaction(OrderCancelled, { command: releaseLock, releases: 'inventory.lock', map: e => ({ id: e.orderId }) });
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 反例
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
// approved 持有 inventory.lock,取消却没有 releases 反应
|
|
32
|
+
command(PurchaseOrder, { from: 'approved', to: 'cancelled', handler: o => o });
|
|
33
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CAGE-QUERY-001
|
|
2
|
+
|
|
3
|
+
## 什么时候出现
|
|
4
|
+
|
|
5
|
+
命中以下实现中的检查时触发。终端会附具体声明位置、原因和修复方法。
|
|
6
|
+
|
|
7
|
+
来源:[packages/kernel/src/primitives/flow.ts:89](../../packages/kernel/src/primitives/flow.ts)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
if (!/^[a-z][A-Za-z0-9]*$/.test(name)) throw declError('CAGE-NAME-005', `查询名 ${name} 必须是小驼峰`);
|
|
11
|
+
if (!def.sql === !def.build) throw declError('CAGE-QUERY-001', `查询 ${name} 必须二选一:sql 或 build`);
|
|
12
|
+
if (def.sql && /\bcore\.|\bsys\./i.test(def.sql)) {
|
|
13
|
+
throw declError('CAGE-QUERY-002', `查询 ${name} 引用了基表,只能引用 safe.* 安全视图`);
|
|
14
|
+
}
|
|
15
|
+
return Object.freeze({
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 怎么修
|
|
19
|
+
|
|
20
|
+
SQL 引用 reads 对应的 safe 视图,用命名参数绑定输入。
|
|
21
|
+
|
|
22
|
+
## 正例
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
reads: [PurchaseOrder], sql: 'select amount from safe.purchase_order where status = :status'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 反例
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
sql: 'select * from core.purchase_order'
|
|
32
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CAGE-QUERY-002
|
|
2
|
+
|
|
3
|
+
## 什么时候出现
|
|
4
|
+
|
|
5
|
+
命中以下实现中的检查时触发。终端会附具体声明位置、原因和修复方法。
|
|
6
|
+
|
|
7
|
+
来源:[packages/kernel/src/primitives/flow.ts:91](../../packages/kernel/src/primitives/flow.ts)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
if (def.sql && /\bcore\.|\bsys\./i.test(def.sql)) {
|
|
11
|
+
throw declError('CAGE-QUERY-002', `查询 ${name} 引用了基表,只能引用 safe.* 安全视图`);
|
|
12
|
+
}
|
|
13
|
+
return Object.freeze({
|
|
14
|
+
[QUERY]: true as const,
|
|
15
|
+
name,
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 怎么修
|
|
19
|
+
|
|
20
|
+
SQL 引用 reads 对应的 safe 视图,用命名参数绑定输入。
|
|
21
|
+
|
|
22
|
+
## 正例
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
reads: [PurchaseOrder], sql: 'select amount from safe.purchase_order where status = :status'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 反例
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
sql: 'select * from core.purchase_order'
|
|
32
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CAGE-QUERY-003
|
|
2
|
+
|
|
3
|
+
## 什么时候出现
|
|
4
|
+
|
|
5
|
+
命中以下实现中的检查时触发。终端会附具体声明位置、原因和修复方法。
|
|
6
|
+
|
|
7
|
+
来源:[packages/cli/src/rules.ts:168](../../packages/cli/src/rules.ts)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
|
|
11
|
+
/** CAGE-QUERY-003:手写 SQL 只能引用 reads 里聚合的安全视图 */
|
|
12
|
+
export function checkQueries(g: Ontology): Finding[] {
|
|
13
|
+
const { get } = index(g);
|
|
14
|
+
const findings: Finding[] = [];
|
|
15
|
+
for (const q of g.nodes.filter((n) => n.kind === 'query')) {
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 怎么修
|
|
19
|
+
|
|
20
|
+
SQL 引用 reads 对应的 safe 视图,用命名参数绑定输入。
|
|
21
|
+
|
|
22
|
+
## 正例
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
reads: [PurchaseOrder], sql: 'select amount from safe.purchase_order where status = :status'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 反例
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
sql: 'select * from core.purchase_order'
|
|
32
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CAGE-REACT-001
|
|
2
|
+
|
|
3
|
+
## 什么时候出现
|
|
4
|
+
|
|
5
|
+
命中以下实现中的检查时触发。终端会附具体声明位置、原因和修复方法。
|
|
6
|
+
|
|
7
|
+
来源:[packages/kernel/src/primitives/flow.ts:46](../../packages/kernel/src/primitives/flow.ts)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
if (def.target && def.target !== def.command.agg) {
|
|
11
|
+
throw declError('CAGE-REACT-001', `反应的 target 与 command 所属聚合不一致`);
|
|
12
|
+
}
|
|
13
|
+
return Object.freeze({
|
|
14
|
+
[REACTION]: true as const,
|
|
15
|
+
event: ev,
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 怎么修
|
|
19
|
+
|
|
20
|
+
反应的目标聚合必须与 command 一致,目标命令需从统一入口导出。
|
|
21
|
+
|
|
22
|
+
## 正例
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
reaction(Approved, { command: acquireLock, map: e => ({ id: e.orderId }) })
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 反例
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
reaction(Approved, { target: Order, command: acquireLock, map })
|
|
32
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CAGE-REACT-002
|
|
2
|
+
|
|
3
|
+
## 什么时候出现
|
|
4
|
+
|
|
5
|
+
命中以下实现中的检查时触发。终端会附具体声明位置、原因和修复方法。
|
|
6
|
+
|
|
7
|
+
来源:[packages/kernel/src/kernel.ts:193](../../packages/kernel/src/kernel.ts)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
const agg = cmd.agg;
|
|
11
|
+
if (depth > this.maxDepth) throw new Error(`[CAGE-REACT-002] 同事务反应嵌套超过 ${this.maxDepth} 层`);
|
|
12
|
+
|
|
13
|
+
// 第 1 关:认证
|
|
14
|
+
if (!ctx.actor || (ctx.actor.kind === 'anonymous' && !cmd.public)) throw cageError('UNAUTHENTICATED');
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 怎么修
|
|
19
|
+
|
|
20
|
+
反应的目标聚合必须与 command 一致,目标命令需从统一入口导出。
|
|
21
|
+
|
|
22
|
+
## 正例
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
reaction(Approved, { command: acquireLock, map: e => ({ id: e.orderId }) })
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 反例
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
reaction(Approved, { target: Order, command: acquireLock, map })
|
|
32
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CAGE-READS-001
|
|
2
|
+
|
|
3
|
+
## 什么时候出现
|
|
4
|
+
|
|
5
|
+
命中以下实现中的检查时触发。终端会附具体声明位置、原因和修复方法。
|
|
6
|
+
|
|
7
|
+
来源:[packages/kernel/src/pipeline/load.ts:83](../../packages/kernel/src/pipeline/load.ts)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
if (typeof prop === 'string' && !(prop in target) && prop !== 'then' && prop !== 'toJSON') {
|
|
11
|
+
throw new Error(`[CAGE-READS-001] handler 使用了未声明的读依赖 ${prop}(详见 docs/errors/CAGE-READS-001.md)`);
|
|
12
|
+
}
|
|
13
|
+
return (target as any)[prop];
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 怎么修
|
|
19
|
+
|
|
20
|
+
在 reads 中声明所有读依赖,并确认目标聚合也从入口导出。
|
|
21
|
+
|
|
22
|
+
## 正例
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
reads: { stock: Inventory }
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 反例
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
reads: {} // handler 却读取 deps.stock
|
|
32
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CAGE-REG-001
|
|
2
|
+
|
|
3
|
+
## 什么时候出现
|
|
4
|
+
|
|
5
|
+
命中以下实现中的检查时触发。终端会附具体声明位置、原因和修复方法。
|
|
6
|
+
|
|
7
|
+
来源:[packages/kernel/src/kernel.ts:109](../../packages/kernel/src/kernel.ts)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
if (!rc) {
|
|
11
|
+
throw new Error(`[CAGE-REG-001] 找不到命令 ${typeof ref === 'string' ? ref : ref.agg.name + '.?'},它是否从 domain 导出了?`);
|
|
12
|
+
}
|
|
13
|
+
return rc;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 怎么修
|
|
19
|
+
|
|
20
|
+
检查统一入口中的聚合/命令引用及名称是否重复,所有依赖都必须在注册表中。
|
|
21
|
+
|
|
22
|
+
## 正例
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
export * from './purchase-order.js';
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 反例
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
// 命令引用了没有从入口导出的聚合
|
|
32
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CAGE-REG-002
|
|
2
|
+
|
|
3
|
+
## 什么时候出现
|
|
4
|
+
|
|
5
|
+
命中以下实现中的检查时触发。终端会附具体声明位置、原因和修复方法。
|
|
6
|
+
|
|
7
|
+
来源:[packages/kernel/src/registry.ts:86](../../packages/kernel/src/registry.ts)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
if (aggregates.some((a) => a.name === (v as AnyAggregate).name)) {
|
|
11
|
+
throw declError('CAGE-REG-002', `聚合名重复:${(v as AnyAggregate).name}`);
|
|
12
|
+
}
|
|
13
|
+
aggregates.push(v as AnyAggregate);
|
|
14
|
+
break;
|
|
15
|
+
case 'COMMAND': {
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 怎么修
|
|
19
|
+
|
|
20
|
+
检查统一入口中的聚合/命令引用及名称是否重复,所有依赖都必须在注册表中。
|
|
21
|
+
|
|
22
|
+
## 正例
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
export * from './purchase-order.js';
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 反例
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
// 命令引用了没有从入口导出的聚合
|
|
32
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CAGE-REG-003
|
|
2
|
+
|
|
3
|
+
## 什么时候出现
|
|
4
|
+
|
|
5
|
+
命中以下实现中的检查时触发。终端会附具体声明位置、原因和修复方法。
|
|
6
|
+
|
|
7
|
+
来源:[packages/kernel/src/registry.ts:133](../../packages/kernel/src/registry.ts)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
if (!aggSet.has(c.cmd.agg)) throw declError('CAGE-REG-001', `命令 ${c.capability} 所属的聚合 ${c.cmd.agg.name} 没有从 domain 导出`);
|
|
11
|
+
if (caps.has(c.capability)) throw declError('CAGE-REG-003', `能力点重复:${c.capability},可用 command({ name }) 区分`);
|
|
12
|
+
caps.add(c.capability);
|
|
13
|
+
for (const [alias, a] of Object.entries(c.cmd.reads)) {
|
|
14
|
+
if (!aggSet.has(a)) throw declError('CAGE-REG-001', `命令 ${c.capability} 的 reads.${alias} 引用的聚合没有导出`);
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 怎么修
|
|
19
|
+
|
|
20
|
+
检查统一入口中的聚合/命令引用及名称是否重复,所有依赖都必须在注册表中。
|
|
21
|
+
|
|
22
|
+
## 正例
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
export * from './purchase-order.js';
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 反例
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
// 命令引用了没有从入口导出的聚合
|
|
32
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CAGE-ROUTE-001
|
|
2
|
+
|
|
3
|
+
## 什么时候出现
|
|
4
|
+
|
|
5
|
+
命中以下实现中的检查时触发。终端会附具体声明位置、原因和修复方法。
|
|
6
|
+
|
|
7
|
+
来源:[packages/lint/src/rules/no-manual-routes.ts:11](../../packages/lint/src/rules/no-manual-routes.ts)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
docs: { description: '禁止在业务项目里手写 HTTP 路由' },
|
|
11
|
+
messages: { route: '[CAGE-ROUTE-001] 不要手写路由 {{call}}:在 domain 里声明命令或查询,路由由框架生成' },
|
|
12
|
+
schema: [],
|
|
13
|
+
},
|
|
14
|
+
create(context) {
|
|
15
|
+
return {
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 怎么修
|
|
19
|
+
|
|
20
|
+
在 domain 声明 command/query,让框架生成 HTTP 路由。
|
|
21
|
+
|
|
22
|
+
## 正例
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
export const submit = command(Order, definition);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 反例
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
app.post('/api/order/submit', handler);
|
|
32
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CAGE-SCOPE-001
|
|
2
|
+
|
|
3
|
+
## 什么时候出现
|
|
4
|
+
|
|
5
|
+
命中以下实现中的检查时触发。终端会附具体声明位置、原因和修复方法。
|
|
6
|
+
|
|
7
|
+
来源:[packages/kernel/src/scope/index.ts:66](../../packages/kernel/src/scope/index.ts)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
default:
|
|
11
|
+
throw new Error('[CAGE-SCOPE-001] 未知的范围组合子');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** 多个授权取并集:用 OR 连接;外层始终加租户条件。 */
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 怎么修
|
|
19
|
+
|
|
20
|
+
选择内置范围组合子,引用聚合中真实存在的部门/创建人字段。
|
|
21
|
+
|
|
22
|
+
## 正例
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
scope: scope.deptSubtree('deptId')
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 反例
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
scope: scope.deptSubtree('missingField')
|
|
32
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CAGE-SCOPE-002
|
|
2
|
+
|
|
3
|
+
## 什么时候出现
|
|
4
|
+
|
|
5
|
+
命中以下实现中的检查时触发。终端会附具体声明位置、原因和修复方法。
|
|
6
|
+
|
|
7
|
+
来源:[packages/kernel/src/primitives/aggregate.ts:96](../../packages/kernel/src/primitives/aggregate.ts)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
for (const f of [sc.deptField, ownerField]) {
|
|
11
|
+
if (f && f !== 'id' && !(f in fields)) throw declError('CAGE-SCOPE-002', `${name} 的范围字段 ${f} 不存在`);
|
|
12
|
+
}
|
|
13
|
+
const scope: ScopeSpec = Object.freeze({ ...sc, ownerField });
|
|
14
|
+
|
|
15
|
+
return Object.freeze({
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 怎么修
|
|
19
|
+
|
|
20
|
+
选择内置范围组合子,引用聚合中真实存在的部门/创建人字段。
|
|
21
|
+
|
|
22
|
+
## 正例
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
scope: scope.deptSubtree('deptId')
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 反例
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
scope: scope.deptSubtree('missingField')
|
|
32
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CAGE-SECRET-001
|
|
2
|
+
|
|
3
|
+
## 什么时候出现
|
|
4
|
+
|
|
5
|
+
命中以下实现中的检查时触发。终端会附具体声明位置、原因和修复方法。
|
|
6
|
+
|
|
7
|
+
来源:[packages/connectors/src/runtime.ts:22](../../packages/connectors/src/runtime.ts)
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
const v = process.env[name];
|
|
11
|
+
if (!v) throw new Error(`[CAGE-SECRET-001] 缺少密钥 ${name}(由密钥管理服务或环境变量注入)`);
|
|
12
|
+
return v;
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 怎么修
|
|
19
|
+
|
|
20
|
+
配置中只写密钥名;密钥值通过运行环境注入,移除源码中的字面量。
|
|
21
|
+
|
|
22
|
+
## 正例
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
secret: 'ERP_API_TOKEN'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 反例
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
secret: 'a-real-token-in-source'
|
|
32
|
+
```
|