@pylonts/dsl 1.1.20 → 1.1.22

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.
Files changed (54) hide show
  1. package/README.md +8 -5
  2. package/dist/action.d.ts +76 -33
  3. package/dist/action.js +19 -17
  4. package/dist/aggregate.d.ts +3 -13
  5. package/dist/aggregate.js +19 -22
  6. package/dist/component.d.ts +3 -3
  7. package/dist/curd.d.ts +2 -2
  8. package/dist/dto.d.ts +18 -2
  9. package/dist/dto.js +75 -9
  10. package/dist/flow-script.js +73 -7
  11. package/dist/flow.d.ts +20 -3
  12. package/dist/flow.js +82 -11
  13. package/dist/index.d.ts +3 -0
  14. package/dist/index.js +3 -0
  15. package/dist/journey.d.ts +23 -0
  16. package/dist/journey.js +26 -0
  17. package/dist/mermaid-driver.js +6 -1
  18. package/dist/navigation.d.ts +2 -2
  19. package/dist/page-action.d.ts +39 -0
  20. package/dist/page-action.js +17 -0
  21. package/dist/page-def.d.ts +9 -9
  22. package/dist/page-flow.d.ts +4 -4
  23. package/dist/popup.d.ts +3 -3
  24. package/dist/repository.d.ts +2 -2
  25. package/dist/task.d.ts +20 -0
  26. package/dist/task.js +21 -0
  27. package/dist/utils.d.ts +7 -0
  28. package/dist/utils.js +12 -4
  29. package/docs/aggregate-implementation.md +174 -0
  30. package/docs/aggregate.md +147 -110
  31. package/docs/concepts.md +109 -0
  32. package/docs/dto.md +130 -106
  33. package/docs/table.md +41 -0
  34. package/docs/task.md +81 -0
  35. package/docs/utils.md +19 -12
  36. package/package.json +1 -1
  37. package/src/action.ts +87 -52
  38. package/src/aggregate.ts +94 -103
  39. package/src/component.ts +3 -3
  40. package/src/curd.ts +2 -2
  41. package/src/dto.ts +87 -8
  42. package/src/flow-script.ts +68 -6
  43. package/src/flow.ts +99 -17
  44. package/src/index.ts +3 -0
  45. package/src/journey.ts +61 -0
  46. package/src/mermaid-driver.ts +5 -1
  47. package/src/navigation.ts +2 -2
  48. package/src/page-action.ts +59 -0
  49. package/src/page-def.ts +9 -9
  50. package/src/page-flow.ts +4 -4
  51. package/src/popup.ts +3 -3
  52. package/src/repository.ts +35 -35
  53. package/src/task.ts +52 -0
  54. package/src/utils.ts +18 -4
@@ -0,0 +1,174 @@
1
+ # 聚合 DSL 极简落盘记录
2
+
3
+ > 日期:本次会话
4
+ > 范围:`@pylonts/dsl` 聚合声明层
5
+ > 目标:让“多对一 / 一对多”先落地,扩展表(一对一)只设计不实现。
6
+
7
+ ## 1. 背景
8
+
9
+ 聚合判断标准已收敛为:
10
+
11
+ > 创建 / 保存 / 删除时,是否必须一起做?
12
+ > - 是 → 聚合成员
13
+ > - 否 → 不聚合(引用)
14
+
15
+ 本次落盘只实现最常用的“一对多”聚合成员:
16
+
17
+ ```ts
18
+ defineAggregate({
19
+ root: order,
20
+ members: {
21
+ items: [orderItem],
22
+ },
23
+ });
24
+ ```
25
+
26
+ - 数组成员 `[orderItem]` = 一对多;
27
+ - 非数组成员(扩展表 / 一对一)暂不落代码,保留设计。
28
+
29
+ ## 2. 本次落盘改了什么
30
+
31
+ ### 2.1 源码
32
+
33
+ | 文件 | 改动 |
34
+ |---|---|
35
+ | `dsl/src/aggregate.ts` | 极简化 `defineAggregate` |
36
+ | `dsl/src/repository.ts` | 注释同步为 `FK / extends` |
37
+
38
+ `dsl/src/aggregate.ts` 关键变化:
39
+
40
+ - 去掉 `name` 必填,聚合名自动取 `root.name`;
41
+ - `members` 类型简化为:
42
+
43
+ ```ts
44
+ Record<string, TableSchema | TableSchema[]>
45
+ ```
46
+
47
+ - 删除 `via` / `one` 复杂配置;
48
+ - 数组成员校验:
49
+ - 必须恰好包含一张表;
50
+ - 该表必须有且仅有一个外键指向 root 主键;
51
+ - 多个外键指向 root 时明确报错;
52
+ - 非数组成员(扩展表)暂不支持,明确报错。
53
+
54
+ ### 2.2 测试
55
+
56
+ | 文件 | 改动 |
57
+ |---|---|
58
+ | `dsl/test/aggregate.test.ts` | 改为新极简 API,并增加“非数组成员暂不支持”测试 |
59
+ | `dsl/test/repository.test.ts` | 改为新极简 API |
60
+
61
+ ### 2.3 文档
62
+
63
+ | 文件 | 改动 |
64
+ |---|---|
65
+ | `dsl/docs/aggregate.md` | 示例去掉 `name`,成员表达改为数组/扩展表,同步扩展表规则 |
66
+ | `dsl/docs/table.md` | 新增“扩展表(extends)”设计说明,标注暂不落代码 |
67
+ | `dsl/docs/aggregate-implementation.md` | 本文档 |
68
+
69
+ ## 3. 当前 API 形态
70
+
71
+ ```ts
72
+ import { defineAggregate } from '@pylonts/dsl';
73
+
74
+ const orderAggregate = defineAggregate({
75
+ root: order,
76
+ members: {
77
+ items: [orderItem],
78
+ },
79
+ });
80
+ ```
81
+
82
+ 约束:
83
+
84
+ - `root` 必须有主键;
85
+ - `members` 的数组元素必须是普通表;
86
+ - 数组元素必须有外键指向 `root` 的主键;
87
+ - 如果有多条外键指向 root,必须收敛为一条,否则无法自动推导。
88
+
89
+ ## 4. 如何验证
90
+
91
+ 在仓库根目录或 `dsl/` 目录执行。
92
+
93
+ ### 4.1 运行 DSL 测试
94
+
95
+ ```bash
96
+ # 方式一:workspace
97
+ npm test --workspace @pylonts/dsl
98
+
99
+ # 方式二:进入 dsl 目录
100
+ cd dsl
101
+ npm test
102
+ ```
103
+
104
+ 可只跑聚合相关测试:
105
+
106
+ ```bash
107
+ cd dsl
108
+ npx vitest run test/aggregate.test.ts test/repository.test.ts
109
+ ```
110
+
111
+ 预期结果:
112
+
113
+ - `aggregate.test.ts` 全部通过;
114
+ - `repository.test.ts` 全部通过;
115
+ - 新增的“非数组成员暂不支持”用例通过。
116
+
117
+ ### 4.2 类型检查
118
+
119
+ ```bash
120
+ npm run typecheck --workspace @pylonts/dsl
121
+ ```
122
+
123
+ 或:
124
+
125
+ ```bash
126
+ cd dsl
127
+ npm run typecheck
128
+ ```
129
+
130
+ 预期结果:无 TypeScript 错误。
131
+
132
+ ### 4.3 构建
133
+
134
+ ```bash
135
+ npm run build --workspace @pylonts/dsl
136
+ ```
137
+
138
+ 或:
139
+
140
+ ```bash
141
+ cd dsl
142
+ npm run build
143
+ ```
144
+
145
+ 预期结果:`dist/` 正常生成。
146
+
147
+ ### 4.4 全量回归(如项目要求)
148
+
149
+ 在仓库根按项目现有脚本执行:
150
+
151
+ ```bash
152
+ npm run lint all
153
+ npm run typecheck
154
+ ```
155
+
156
+ 或按仓库实际脚本执行 `api tsc + lint all`。
157
+
158
+ ## 5. 验证重点
159
+
160
+ 1. 新的极简 `defineAggregate` 能正常声明聚合;
161
+ 2. 不再需要传 `name`;
162
+ 3. `members.items` 直接是 `[orderItem]`,不再有 `via` / `one`;
163
+ 4. 没有外键指向 root 时会报错;
164
+ 5. 非数组成员会报“扩展表未实现”;
165
+ 6. 旧测试已全部迁移到新 API。
166
+
167
+ ## 6. 暂未实现(保留设计)
168
+
169
+ - 扩展表 `extends: root`:一对一成员;
170
+ - 非数组成员:当前会抛错;
171
+ - Repository 生成器:仍未实现;
172
+ - flow 调用 repository:仍未实现。
173
+
174
+ 这些内容在 `dsl/docs/aggregate.md` 和 `dsl/docs/table.md` 中保留设计。
package/docs/aggregate.md CHANGED
@@ -1,110 +1,147 @@
1
- # 聚合与仓储 DSL 扩展规划(Aggregate / Repository)
2
-
3
- > 状态:**规划中(未实现)**
4
- > 关联代码:`dsl/src/db.ts`(TableSchema)、`dsl/src/dao.ts`(DaoSchema)、`dsl/src/service.ts`(ServiceSchema)
5
- > 背景对话:ts-libs 会话「dd DDD 扩展讨论」(商城下单用例)
6
-
7
- ## 1. 背景:pylon 现状与 DDD 缺口
8
-
9
- **现状**:
10
-
11
- - `TableSchema` 是全局扁平数据字典("表无物理归属"),orders / order_items 是两张互相独立声明、无聚合关系的表;
12
- - `DaoSchema` 是单表单 SQL(knex 透明代理),粒度 = 表,无跨表能力;
13
- - 多表读写由 service flow 手工编排:`dao.insert(orders) → dao.insert(order_items)` + 手工 `@Trans()`,"总额 = Σ明细"这类一致性靠开发者自觉,无强制约束。
14
-
15
- **DDD 缺口对照**(战术模式):
16
-
17
- | DDD 概念 | dsl 现状 | 缺口 |
18
- |---------|---------|------|
19
- | 聚合 / 聚合根 | 无 | **全新概念** |
20
- | 值对象 | mock 识别"金额/手机号"语义 | 无声明层 |
21
- | 领域事件 | 只有 UI 组件事件(event.ts) | **全新概念** |
22
- | 领域服务 | 全混在应用服务(service_schema) | 需拆分 |
23
- | 仓储 | 无(DAO 粒度是表) | **全新概念** |
24
- | 应用服务编排 | service_schema + flow ✅ | 已具备 |
25
- | 防腐层 | ThirdServiceSchema 只隔离 | 缺模型映射 |
26
-
27
- ## 2. 扩展一:AggregateSchema(核心)
28
-
29
- **作用**:显式声明"哪些表属于同一个聚合、谁是聚合根、成员如何挂载、跨成员不变式、聚合间引用规则"。这是把"多表一致性从约定变约束"的落点。
30
-
31
- ```ts
32
- defineAggregate({
33
- name: 'Order',
34
- root: ordersTable, // 聚合根表
35
- members: { // 成员表("包含几个 table schema" 的声明)
36
- items: { table: orderItemsTable, via: 'order_no' }, // 1:N,外键挂根
37
- address: { table: orderAddressTable, via: 'order_no', one: true }, // 1:1
38
- },
39
- invariants: [ // 跨成员表不变式,挂聚合上,可被生成代码消费
40
- { name: 'total = sum(items.price * items.qty)',
41
- check: 'totalAmount == sum(items.price * items.qty)' },
42
- ],
43
- references: { productId: 'ProductAggregate' }, // 聚合间只按 ID 引用
44
- });
45
- ```
46
-
47
- | 声明项 | 含义 | 缺了会怎样 |
48
- |--------|------|-----------|
49
- | `root` | 谁是聚合根 | 分不清一致性入口 |
50
- | `members` | 包含哪几个 table schema | "包含"只是列表,无结构 |
51
- | `members[i].via` | 成员表靠哪个外键挂根 | 工具无法推导 join / 级联关系 |
52
- | `invariants` | 跨成员一致性规则 | "总额=Σ明细"又回到 flow 里手工写 |
53
- | `references` | 聚合间只按 ID 引用 | 无法 lint 跨聚合直接持表引用 |
54
-
55
- **消费方**(声明一旦存在即可自动推导):
56
-
57
- 1. **生成 Repository**:按"加载 / 保存 / 删除"三套固定骨架自动产出(见下),`orders + order_items` 自动同事务,不再手工 `@Trans()`;
58
- 2. **lint 约束**:禁止聚合外代码直接 `insert/update/delete` 成员表(只准经 root 走);
59
- 3. **不变式挂载**:save 前后强制校验。
60
-
61
- **多表映射三种模式**(聚合↔表):A 单表=单聚合(1:1,几乎透明);B 一聚合=多表(1:N,最常见,Order 案例);C 多聚合共享表(N:1,DDD 不推荐)。
62
-
63
- ## 3. 扩展二:RepositorySchema(可推导,也可显式声明)
64
-
65
- **作用**:聚合粒度的存储入口,把"哪些表一起查、怎么拼成聚合"的知识从 service flow 下沉到仓储。调用方只面对领域概念(Order),不面对表。
66
-
67
- ```ts
68
- defineRepository({
69
- name: 'OrderRepository',
70
- aggregate: orderAggregate, // 绑定聚合
71
- // 内部如何落到 DAO 由 generator 按 aggregate 结构自动展开:
72
- // save(order) = tx { ordersDao.upsert + orderItemsDao 级联 }
73
- // findByOrderNo() = ordersDao.get + orderItemsDao 按 order_no 查
74
- });
75
- ```
76
-
77
- **聚合内 join 下沉、聚合间禁止 join**:
78
-
79
- - 聚合内:加载整个 Order(根 + 明细 + 地址)由 Repository 内部完成,调用方一行,join 知识声明在 `members[].via`;
80
- - 聚合间:只按 ID 引用,跨聚合 join DDD 禁止的;展示商品名这类信息走读模型 / 查询服务(CQRS 的 Q 侧),或应用服务分步查 + 内存组装。
81
-
82
- **分层对照**:
83
-
84
- | | 操作单元 | 一次操作覆盖 | dsl |
85
- |----|---------|------------|-----|
86
- | Service | 用例 | 跨多个聚合/服务 | service_schema ✅ |
87
- | Repository | **聚合** | orders + order_items 一个事务 | **本扩展** |
88
- | DAO | **表** | 单表一条 SQL | dao_schema |
89
-
90
- ## 4. 扩展三:引入支持 DDD TS 库(选型,待决策)
91
-
92
- dsl 是**声明期**(`defineAggregate` 声明结构),引入的库是**运行期**(代码跑起来时持久化/发事件)。两者互补,不冲突——声明可翻译为运行期库的配置。
93
-
94
- | | 类型 | 聚合能力 | 与本扩展关系 |
95
- |----|------|---------|-------------|
96
- | **MikroORM** | ORM | Entity / Repository / Unit of Work / Identity Map / cascade persist | **首选参考**:`@OneToMany(cascade, orphanRemoval)` + `em.persist(order)` 就是"orders + order_items 同事务整体落库"的标准实现;AggregateSchema 声明可翻译成它的映射配置 |
97
- | **Remesh** | DDD 框架 | CQRS + 领域事件 + Command/Query 分离 | 领域事件 / CQRS 参考 |
98
- | **Emmett** | 事件溯源 | 聚合状态由事件重建 | 事件溯源聚合参考 |
99
- | TypeORM | ORM | Repository + cascade,无 UoW / Identity Map | 弱支持,不推荐 |
100
- | Prisma / Drizzle | 查询构建器 | 不支持聚合 | 需手工包 Repository |
101
-
102
- **建议**:运行时持久化参考/选用 **MikroORM**(聚合持久化设计最完整);领域事件参考 **Remesh**。dsl 的 `AggregateSchema` 声明层本身无现成开源,属本项目的增量设计空间。
103
-
104
- ## 5. 落地步骤(待办,未开工)
105
-
106
- 1. `AggregateSchema` 类型 + `defineAggregate` + 定义期校验(root 必须在其表内、via 外键存在、成员表不能是其他聚合的 root 等);
107
- 2. `RepositorySchema` 类型 + `defineRepository`(或从 aggregate 自动推导生成);
108
- 3. `gen` 生成 Repository 代码:加载/保存/删除三套骨架 + 同事务包装(`@Trans()` 从声明推导);
109
- 4. `lint` 聚合边界检查:聚合外禁止直接改成员表、聚合间禁止跨表引用;
110
- 5. 运行期库选型落地(MikroORM 或保持 DAO 同事务包装)。
1
+ # 聚合与仓储 DSL 扩展规划(Aggregate / Repository)
2
+
3
+ > 状态:**规划中(未实现)**
4
+ > 关联代码:`dsl/src/db.ts`(TableSchema)、`dsl/src/dao.ts`(DaoSchema)、`dsl/src/service.ts`(ServiceSchema)
5
+ > 背景对话:ts-libs 会话「dd DDD 扩展讨论」(商城下单用例)
6
+
7
+ ## 1. 背景:pylon 现状与 DDD 缺口
8
+
9
+ **现状**:
10
+
11
+ - `TableSchema` 是全局扁平数据字典("表无物理归属"),orders / order_items 是两张互相独立声明、无聚合关系的表;
12
+ - `DaoSchema` 是单表单 SQL(knex 透明代理),粒度 = 表,无跨表能力;
13
+ - 多表读写由 service flow 手工编排:`dao.insert(orders) → dao.insert(order_items)` + 手工 `@Trans()`,"总额 = Σ明细"这类一致性靠开发者自觉,无强制约束。
14
+
15
+ **DDD 缺口对照**(战术模式):
16
+
17
+ | DDD 概念 | dsl 现状 | 缺口 |
18
+ |---------|---------|------|
19
+ | 聚合 / 聚合根 | 无 | **全新概念** |
20
+ | 值对象 | mock 识别"金额/手机号"语义 | 无声明层 |
21
+ | 领域事件 | 只有 UI 组件事件(event.ts) | **全新概念** |
22
+ | 领域服务 | 全混在应用服务(service_schema) | 需拆分 |
23
+ | 仓储 | 无(DAO 粒度是表) | **全新概念** |
24
+ | 应用服务编排 | service_schema + flow ✅ | 已具备 |
25
+ | 防腐层 | ThirdServiceSchema 只隔离 | 缺模型映射 |
26
+
27
+ ## 2. 什么情况下需要聚合?
28
+
29
+ **核心判断标准**:
30
+
31
+ > 创建 / 保存 / 删除时,是否必须一起做?
32
+ > - 是 → 聚合(成员)
33
+ > - 否 → 不聚合(引用)
34
+
35
+ ### 2.1 成员 vs 引用
36
+
37
+ | 维度 | 聚合成员 | 聚合间引用 |
38
+ |------|---------|-----------|
39
+ | 生命周期 | 与根同生共死 | 独立生命周期 |
40
+ | 创建 | 随根一起创建 | 不随根创建 |
41
+ | 保存 | 随根一起保存 | 不随根保存 |
42
+ | 删除 | 随根级联删除 | 不随根删除 |
43
+ | 关联 | 成员表 FK 指向根 / 扩展表 extends 根 | 本表 FK 指向其他聚合根 |
44
+ | 示例 | order_item / order_address 快照 | order.user_id / order.address_id |
45
+
46
+ ### 2.2 案例:地址可复用 vs 地址快照
47
+
48
+ **场景**:一个订单对应一个地址,但地址可以被多个订单复用。
49
+
50
+ - 如果地址可复用 不是聚合成员,是引用:
51
+ - 创建订单不会创建地址;
52
+ - 删除订单不会删除地址;
53
+ - 多个订单可以指向同一个地址;
54
+ - 建模为 `references: { address: 'AddressAggregate' }`,外键不需要 unique。
55
+
56
+ - 如果地址是下单时的快照(copy)→ 是聚合成员:
57
+ - 下单时复制一份地址到订单;
58
+ - 之后修改地址簿不影响已下单订单;
59
+ - 删除订单时该快照一起删除;
60
+ - 建模为 `members: { address: orderAddress }`;
61
+ - 此时 `orderAddress` 应为**扩展表**(`extends: order`),主键与根主键同义、类型一致,天然保证一对一。
62
+
63
+ **lint 规则**:声明为一对一成员时,成员表必须是扩展表(`extends` 指向根),主键与根主键同义且类型一致;否则它要么是一对多,要么应改为 references。
64
+
65
+ ## 3. 扩展一:AggregateSchema(核心)
66
+
67
+ **作用**:显式声明"哪些表属于同一个聚合、谁是聚合根、成员如何挂载、跨成员不变式、聚合间引用规则"。这是把"多表一致性从约定变约束"的落点。
68
+
69
+ ```ts
70
+ defineAggregate({
71
+ root: ordersTable, // 聚合根表
72
+ members: { // 成员表:数组=一对多,非数组=一对一
73
+ items: [orderItemsTable], // 1:N,外键自动推导
74
+ address: orderAddressTable, // 1:1,扩展表
75
+ },
76
+ invariants: [ // 跨成员表不变式,挂聚合上,可被生成代码消费
77
+ { name: 'total = sum(items.price * items.qty)',
78
+ check: 'totalAmount == sum(items.price * items.qty)' },
79
+ ],
80
+ references: { productId: 'ProductAggregate' }, // 聚合间只按 ID 引用
81
+ });
82
+ ```
83
+
84
+ | 声明项 | 含义 | 缺了会怎样 |
85
+ |--------|------|-----------|
86
+ | `root` | 谁是聚合根 | 分不清一致性入口 |
87
+ | `members` | 包含哪几个 table schema;数组=普通表=1:N,非数组=扩展表=1:1 | 无法推导成员关系 |
88
+ | 成员外键 | TableSchema.foreignKeys / extends 自动推导 | 工具无法推导 join / 级联关系 |
89
+ | `invariants` | 跨成员一致性规则 | "总额=Σ明细"又回到 flow 里手工写 |
90
+ | `references` | 聚合间只按 ID 引用 | 无法 lint 跨聚合直接持表引用 |
91
+
92
+ **消费方**(声明一旦存在即可自动推导):
93
+
94
+ 1. **生成 Repository**:按"加载 / 保存 / 删除"三套固定骨架自动产出(见下),`orders + order_items` 自动同事务,不再手工 `@Trans()`;
95
+ 2. **lint 约束**:禁止聚合外代码直接 `insert/update/delete` 成员表(只准经 root 走);
96
+ 3. **不变式挂载**:save 前后强制校验。
97
+
98
+ **多表映射三种模式**(聚合↔表):A 单表=单聚合(1:1,几乎透明);B 一聚合=多表(1:N,最常见,Order 案例);C 多聚合共享表(N:1,DDD 不推荐)。
99
+
100
+ ## 4. 扩展二:RepositorySchema(可推导,也可显式声明)
101
+
102
+ **作用**:聚合粒度的存储入口,把"哪些表一起查、怎么拼成聚合"的知识从 service flow 下沉到仓储。调用方只面对领域概念(Order),不面对表。
103
+
104
+ ```ts
105
+ defineRepository({
106
+ name: 'OrderRepository',
107
+ aggregate: orderAggregate, // 绑定聚合
108
+ // 内部如何落到 DAO generator aggregate 结构自动展开:
109
+ // save(order) = tx { ordersDao.upsert + orderItemsDao 级联 }
110
+ // findByOrderNo() = ordersDao.get + orderItemsDao order_no 查
111
+ });
112
+ ```
113
+
114
+ **聚合内 join 下沉、聚合间禁止 join**:
115
+
116
+ - 聚合内:加载整个 Order(根 + 明细 + 地址)由 Repository 内部完成,调用方一行,join 知识由成员表 `foreignKeys` / `extends` 自动推导;
117
+ - 聚合间:只按 ID 引用,跨聚合 join 是 DDD 禁止的;展示商品名这类信息走读模型 / 查询服务(CQRS 的 Q 侧),或应用服务分步查 + 内存组装。
118
+
119
+ **分层对照**:
120
+
121
+ | 层 | 操作单元 | 一次操作覆盖 | dsl |
122
+ |----|---------|------------|-----|
123
+ | Service | 用例 | 跨多个聚合/服务 | service_schema ✅ |
124
+ | Repository | **聚合** | orders + order_items 一个事务 | **本扩展** |
125
+ | DAO | **表** | 单表一条 SQL | dao_schema ✅ |
126
+
127
+ ## 5. 扩展三:引入支持 DDD 的 TS 库(选型,待决策)
128
+
129
+ dsl 是**声明期**(`defineAggregate` 声明结构),引入的库是**运行期**(代码跑起来时持久化/发事件)。两者互补,不冲突——声明可翻译为运行期库的配置。
130
+
131
+ | 库 | 类型 | 聚合能力 | 与本扩展关系 |
132
+ |----|------|---------|-------------|
133
+ | **MikroORM** | ORM | Entity / Repository / Unit of Work / Identity Map / cascade persist | **首选参考**:`@OneToMany(cascade, orphanRemoval)` + `em.persist(order)` 就是"orders + order_items 同事务整体落库"的标准实现;AggregateSchema 声明可翻译成它的映射配置 |
134
+ | **Remesh** | DDD 框架 | CQRS + 领域事件 + Command/Query 分离 | 领域事件 / CQRS 参考 |
135
+ | **Emmett** | 事件溯源 | 聚合状态由事件重建 | 事件溯源聚合参考 |
136
+ | TypeORM | ORM | Repository + cascade,无 UoW / Identity Map | 弱支持,不推荐 |
137
+ | Prisma / Drizzle | 查询构建器 | 不支持聚合 | 需手工包 Repository |
138
+
139
+ **建议**:运行时持久化参考/选用 **MikroORM**(聚合持久化设计最完整);领域事件参考 **Remesh**。dsl 的 `AggregateSchema` 声明层本身无现成开源,属本项目的增量设计空间。
140
+
141
+ ## 6. 落地步骤(待办,未开工)
142
+
143
+ 1. `AggregateSchema` 类型 + `defineAggregate` + 定义期校验(root 必须在其表内、成员表必须有外键指向 root 或 extends root、成员表不能是其他聚合的 root 等);
144
+ 2. `RepositorySchema` 类型 + `defineRepository`(或从 aggregate 自动推导生成);
145
+ 3. `gen` 生成 Repository 代码:加载/保存/删除三套骨架 + 同事务包装(`@Trans()` 从声明推导);
146
+ 4. `lint` 聚合边界检查:聚合外禁止直接改成员表、聚合间禁止跨表引用;
147
+ 5. 运行期库选型落地(MikroORM 或保持 DAO 同事务包装)。
@@ -0,0 +1,109 @@
1
+ # 概念层 (Concepts)
2
+
3
+ 概念层是**名字层**:每个业务名词先以"名字 + 名词解释"存在,先于一切结构(表、页面、接口、旅程)。它是蓝图体系的根——所有跨层引用(journey / table / PageFlow / DTO)的身份都从概念出发。
4
+
5
+ ## 认知顺序:名词在先,结构在后
6
+
7
+ ```
8
+ 名词(概念):商户 —— 业务先说这个词,先解释它是什么
9
+ ↓ 派生
10
+ 词根(短语):mer —— 为了字段名发明缩写
11
+ ↓ 长出
12
+ 结构:merchant 表 / 商户列表页 / 入驻旅程 —— 各维度引用"商户"
13
+ ```
14
+
15
+ - **概念是本源**:业务人员嘴里只有"商户",`mer` 是工程师后来为字段名发明的代号。
16
+ - **短语(词根)是派生**:概念引用短语,不是短语解释概念。
17
+ - **结构是生长**:表、页面、journey 都是概念"长出"的结构,各自引用概念名。
18
+
19
+ ## 与词典(_dictionary.ts)的关系
20
+
21
+ | | 词典(dictionary) | 概念(concepts) |
22
+ |---|---|---|
23
+ | 单位 | 词根(mer / rate) | 完整名词(商户 / 银联报文) |
24
+ | 主键 | 短语本身 | 名词标识 |
25
+ | 内容 | 短语 + label + 描述 | 名词 + 经典段落解释 |
26
+ | 服务对象 | 字段命名校验(lint field) | 所有维度的引用身份 |
27
+ | 认知顺序 | 后于概念存在(缩写是派生的) | **先于一切存在** |
28
+
29
+ **两者并存,互不反转**:`_dictionary.ts` 维持现状(词根字典,服务字段命名);`_concepts.ts` 是新的概念清单(服务跨层身份)。概念通过 `phrase` 字段引用词根,把"名词 → 缩写"的派生关系显式化。
30
+
31
+ ## 规范位置:schema/_concepts.ts
32
+
33
+ **所有概念统一定义在 `schema/_concepts.ts`**,一个文件一处定义(与 `_dictionary.ts` 同规则)。`table.ts` / journey / PageFlow 引用概念,禁止内联。
34
+
35
+ ## 定义形态
36
+
37
+ ```ts
38
+ // schema/_concepts.ts
39
+ import { defineConcept } from '@pylonts/dsl';
40
+
41
+ export const merchant = defineConcept('merchant', {
42
+ title: '商户',
43
+ description: '入驻平台并签约收单的商家。由 BD 录入,平台审核,提交银联开通后获得登录资格,可登录商户端进行订单核销。',
44
+ phrase: mer, // 引用词根(_dictionary.ts 的实体短语)
45
+ });
46
+
47
+ export const unionpayReport = defineConcept('unionpay-report', {
48
+ title: '银联报文',
49
+ description: '提交给银联的商户资料报文,银联审核后返回审核结果。',
50
+ });
51
+ ```
52
+
53
+ - `name`:概念标识(kebab-case,跨层身份契约——journey / 表 / 页面都叫这个名字)。
54
+ - `title`:中文名。
55
+ - `description`:**经典段落**——一段话讲清楚"这是什么、干什么用的",像文档术语表里的一条。
56
+ - `phrase?`:引用 `_dictionary.ts` 的词根条目(实体短语),表达"这个概念在字段命名里缩写为什么"。
57
+ - 一个概念可以还没有任何结构(没有表、没有页面)——**概念本身就是一个完整的存在**。
58
+
59
+ ## 蓝图态 → 锚定态:概念是跳板
60
+
61
+ 概念层解决"journey 不能等一切都好了才串起来"的矛盾:
62
+
63
+ ```
64
+ journey 步骤:"商户入驻"(名字)
65
+ │ ① 引用概念(只需名字存在)
66
+
67
+ 概念:merchant(名词 + 解释) ← 跳板,先于一切存在
68
+ │ ② 概念长出结构
69
+
70
+ 表 / 页面 / 接口(引用概念名)
71
+ │ ③ 结构生成实现
72
+
73
+ DDL / 路由 / 契约产物
74
+ ```
75
+
76
+ - **蓝图态**:journey 引用概念名即可串线——概念不需要表、不需要页面,只需要名字存在。
77
+ - **锚定态**:概念长出结构后,同一引用自然升级(引用依然有效,只是"对象"变厚了)。
78
+ - **名字未长出结构的比例 = 细化度**:概念清单可统计"系统共 N 个概念,M 个已落表"——这是蓝图完成度的天然度量。
79
+
80
+ ## 身份契约
81
+
82
+ 概念名是**跨层身份**的唯一来源:
83
+
84
+ - journey 说"商户入驻" → 引用 `merchant` 概念
85
+ - 表说 `merchant.table.ts` → 引用 `merchant` 概念
86
+ - 页面说"商户列表页" → 引用 `merchant` 概念
87
+ - lint 检查字段 `mer_id` → 查概念的 `phrase`(词根 `mer`)
88
+
89
+ 所有维度说同一个词,指同一个概念;校验"引用的概念是否存在"是各维度的第一道闸门。
90
+
91
+ ## 校验草案
92
+
93
+ | 规则 | 检查 |
94
+ |------|------|
95
+ | C1 | `name` 唯一、kebab-case;一文件一概念清单(`_concepts.ts` 专用名) |
96
+ | C2 | `description` 必填(经典段落,非空) |
97
+ | C3 | `phrase` 引用必须指向 `_dictionary.ts` 中已定义的短语条目 |
98
+ | C4 | 下游引用(journey / table / PageFlow / DTO)引用的概念必须存在于 `_concepts.ts` |
99
+ | C5 | 概念引用词根时,词根必须与该概念的中文语义一致(人工评审裁决) |
100
+
101
+ ## 消费者(未来)
102
+
103
+ | 消费者 | 用途 |
104
+ |--------|------|
105
+ | journey(蓝图/旅程) | 步骤引用概念名——跨 app 业务线的身份锚点 |
106
+ | table.ts | 表归属概念(现有 `phrase` 链接的上一级) |
107
+ | PageFlow | 页面归属概念 |
108
+ | lint | 跨层身份校验(C4) |
109
+ | 文档/评审 | 概念清单渲染为术语表(markdown) |