@pylonts/dsl 1.1.14 → 1.1.15
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/docs/third-service.md +100 -70
- package/package.json +2 -2
package/docs/third-service.md
CHANGED
|
@@ -2,121 +2,151 @@
|
|
|
2
2
|
|
|
3
3
|
第三方服务适配器契约(如微信支付 tenpay、短信、文件存储)。`defineThirdService` 声明适配器类契约:构造函数配置 + 方法列表。
|
|
4
4
|
|
|
5
|
+
> 完整对接流程(前置准备 → 契约化 → gen third → 填充 client → 沙箱验证)见 [methodology/third-party-integration.md](../../docs/methodology/third-party-integration.md)。
|
|
6
|
+
|
|
7
|
+
与两个相近概念区分:
|
|
8
|
+
|
|
9
|
+
- `ServiceSchema`(service_schema/)——后端业务服务;
|
|
10
|
+
- `ThirdApiSchema`(project.config.ts `thirdApis`)——项目拓扑:第三方系统的目录归属,如 `wx/`、`ble/`。
|
|
11
|
+
|
|
12
|
+
`ThirdServiceSchema.schema` 引用 `ThirdApiSchema` 实例(拓扑引用)。第三方方法实现在外部系统,仅声明契约、不建模内部流程。
|
|
13
|
+
|
|
14
|
+
## 定义
|
|
15
|
+
|
|
5
16
|
```ts
|
|
6
|
-
import {
|
|
17
|
+
import { buildInput, buildOutput, CodeException, defineThirdService, dtoField, intField, IOException, stringField } from '@pylonts/dsl';
|
|
7
18
|
import { wx } from '../project.config';
|
|
8
19
|
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
// Rule = name + two named ends. One rule per semantic (defining fenYuan twice throws).
|
|
12
|
-
const fenYuan = defineFieldRule({
|
|
13
|
-
name: 'fenYuan',
|
|
14
|
-
ends: { fen: {}, yuan: {} },
|
|
15
|
-
});
|
|
20
|
+
const totalFee = intField({ optional: false, label: '金额(分)' });
|
|
16
21
|
|
|
17
22
|
export const wxPayService = defineThirdService({
|
|
18
23
|
schema: wx,
|
|
19
24
|
name: 'WxPayService',
|
|
20
25
|
description: '微信支付服务(tenpay APIv2)',
|
|
21
|
-
methods:
|
|
22
|
-
{
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
methods: {
|
|
27
|
+
getPayParams: {
|
|
28
|
+
args: buildInput('PayParams', {
|
|
29
|
+
out_trade_no: dtoField(stringField({ maxLength: 32, optional: false, label: '订单号' })),
|
|
30
|
+
total_fee: dtoField(totalFee),
|
|
31
|
+
// Same fact and same type as the entity column — shared instance.
|
|
32
|
+
openid: dtoField(user.columns.openid),
|
|
33
|
+
}),
|
|
34
|
+
results: buildOutput('PayParamsResult', {
|
|
35
|
+
appId: dtoField(stringField({ optional: false, label: 'appId' })),
|
|
36
|
+
paySign: dtoField(stringField({ optional: false, label: '签名' })),
|
|
37
|
+
}),
|
|
38
|
+
throws: [CodeException, IOException],
|
|
39
|
+
description: '获取支付参数',
|
|
34
40
|
},
|
|
35
|
-
|
|
41
|
+
},
|
|
36
42
|
});
|
|
37
43
|
```
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
- `methods` 是 **map**:key 即方法名(构建器写回 `method.name`),value 为 `ThirdServiceMethodDef`。
|
|
46
|
+
- 每个方法的 `args` / `results` 各是一个 `DtoMessage`,用 `buildInput` / `buildOutput` 构建。`buildInput` 构建 `args`(输入消息),`buildOutput` 构建 `results`(输出消息)——与业务 DTO 同一 POJO 聚合,字段以 `dtoField(field)` 包装,map key 即线格式(wire-format)字段名,**原样保留协议拼写**(`out_trade_no`、`appId`,不做 camelCase)。
|
|
47
|
+
- `throws`(**必填**):每个方法必须声明 **`CodeException` + `IOException`** 两个异常——`CodeException`(第三方返回的业务错误码)与 `IOException`(网络/超时/不可恢复故障)。两个异常都来自 `@pylonts/core`,**不能自定义、不能替换**(`pylonts gen third` 会校验,缺失即报错拒绝生成)。原因:第三方集成有两类必然失败——业务层失败(第三方返回错误码,需转译给调用方)与传输层失败(网络/超时,需按故障重试或上报),client 骨架的异常翻译依赖这两个契约。
|
|
48
|
+
- `description`(可选):服务或方法说明。
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
|------|------|
|
|
45
|
-
| `name` | 消息名(如 `QueryBalanceResult`),生成产物的类型名 |
|
|
46
|
-
| `fields` | 线格式字段 map,key 即协议字段名(`out_trade_no`、`appId` 原样保留) |
|
|
47
|
-
| `refs` | 同事实变体链接(见下) |
|
|
48
|
-
| `schema` | 反向指针,指向所属 method(构建器写入) |
|
|
49
|
-
|
|
50
|
-
`defineThirdMethod` 写回自有字段的 `name/schema`(与 `defineTable` 同一惯例)。
|
|
51
|
-
|
|
52
|
-
## 字段与本地实体的关系
|
|
53
|
-
|
|
54
|
-
两个通道,按"同一事实"的表达方式选择:
|
|
50
|
+
字段与本地实体列/其他消息字段的关系,两个通道,按"同一事实"的表达方式选择:
|
|
55
51
|
|
|
56
52
|
### 同一概念且类型一致 → 共享实例
|
|
57
53
|
|
|
58
|
-
直接复用本地实体列实例,类型/语义/默认值自动跟随实体,DTO
|
|
54
|
+
直接复用本地实体列实例,类型/语义/默认值自动跟随实体,DTO 投影继承全部语义:
|
|
59
55
|
|
|
60
56
|
```ts
|
|
61
57
|
args: {
|
|
62
|
-
name: '
|
|
58
|
+
name: 'PayParams',
|
|
63
59
|
fields: {
|
|
64
|
-
openid: user.columns.openid, //
|
|
60
|
+
openid: dtoField(user.columns.openid), // user 是 TableSchema 实例,此处复用其 openid 列的 Field 对象
|
|
65
61
|
},
|
|
66
62
|
},
|
|
67
63
|
```
|
|
68
64
|
|
|
69
|
-
|
|
65
|
+
线格式字段名(map key)与列名无需一致——key 是协议拼写,value 是任意 Field 实例,两者解耦。协议叫 `userId`、列叫 `user_id` 照样共享:
|
|
70
66
|
|
|
71
|
-
|
|
67
|
+
```ts
|
|
68
|
+
fields: {
|
|
69
|
+
userId: dtoField(user.columns.user_id), // key 按协议拼写,value 复用列实例
|
|
70
|
+
},
|
|
71
|
+
```
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
> `user` 是 `schema/user.table.ts` 中 `export const user = defineTable('user', { ... })` 导出的 **TableSchema 实例**(`user.columns` 是它的列 map,`user.columns.openid` 是该表 `openid` 列的 Field 实例)。共享实例即把**同一个 Field 对象**放入消息字段,类型/语义/默认值全部跟随表定义。
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
// 规则 = 名称 + 两端(具名 map)。同一语义全局只允许一条(重复定义抛错)。
|
|
77
|
-
const fenYuan = defineFieldRule({
|
|
78
|
-
name: 'fenYuan',
|
|
79
|
-
ends: { fen: {}, yuan: {} },
|
|
80
|
-
});
|
|
75
|
+
### 同一概念但类型/格式不同 → 自有字段
|
|
81
76
|
|
|
77
|
+
声明自有线格式类型:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
82
80
|
const totalFee = intField({ optional: false, label: '金额(分)' });
|
|
83
81
|
|
|
84
82
|
fields: {
|
|
85
|
-
total_fee: totalFee,
|
|
83
|
+
total_fee: dtoField(totalFee),
|
|
86
84
|
},
|
|
87
|
-
refs: [
|
|
88
|
-
{
|
|
89
|
-
field: totalFee, // 本地定义(本消息的 wire 字段)
|
|
90
|
-
ref: order.columns.amount, // 其他定义(表列或其他消息字段)
|
|
91
|
-
convert: { rule: fenYuan, end: fenYuan.ends.fen },
|
|
92
|
-
},
|
|
93
|
-
],
|
|
94
85
|
```
|
|
95
86
|
|
|
96
|
-
|
|
97
|
-
- `ref`:其他 schema 的字段实例(表列或其他消息字段,构建器校验不得是本消息字段)
|
|
98
|
-
- `convert`(可选):绑定一条规则到这对字段——仅当两字段需要转化时声明,纯关联不需要
|
|
99
|
-
- `rule`:`FieldRuleSchema`——规则 = 名称 + 两端(如 `fenYuan` 的 `fen`/`yuan` 端)。加密/脱敏/换算统一为规则名维度,`defineFieldRule` 按名称查重,同一语义只声明一次
|
|
100
|
-
- `end`:`field` 所站的端——引用 `rule.ends.fen` / `rule.ends.yuan`(具名引用,无索引魔法;构建器按实例校验),`ref` 自动占另一端——不再重复声明 from/to
|
|
101
|
-
- 生成器将来为这对字段产出两个方向的函数(field 端→ref 端 与 ref 端→field 端)
|
|
87
|
+
wire 字段与本地字段之间的换算/映射(分↔元、加密、脱敏)由 convert 防腐层承载,`FieldRuleSchema` 换算规则为规划能力、尚未接入消息绑定。
|
|
102
88
|
|
|
103
89
|
## 嵌套字段
|
|
104
90
|
|
|
105
|
-
线格式字段支持递归嵌套,用 `
|
|
91
|
+
线格式字段支持递归嵌套,用 `objectField` / `arrayField`(Field 体系,非表列):
|
|
106
92
|
|
|
107
93
|
```ts
|
|
108
94
|
fields: {
|
|
109
|
-
payer_info: objectField({
|
|
95
|
+
payer_info: dtoField(objectField({
|
|
110
96
|
properties: {
|
|
111
97
|
openid: stringField({ optional: false, maxLength: 64 }),
|
|
112
98
|
},
|
|
113
|
-
}),
|
|
114
|
-
coupons: arrayField({ items: intField() }),
|
|
99
|
+
})),
|
|
100
|
+
coupons: dtoField(arrayField({ items: intField() })),
|
|
115
101
|
},
|
|
116
102
|
```
|
|
117
103
|
|
|
118
|
-
表列不支持这两个类型(`buildCreateTableSql`
|
|
104
|
+
表列不支持这两个类型(`buildCreateTableSql` 直接报错,定义期即拦截)。
|
|
105
|
+
|
|
106
|
+
## 枚举与异常
|
|
107
|
+
|
|
108
|
+
第三方消息字段可用 `enumField` 挂 `defineEnum` 枚举,两者与 `defineThirdService` 定义在同一源文件中(named export),供 `pylonts gen third` 生成枚举产物。
|
|
109
|
+
|
|
110
|
+
异常不在此处定义——方法 `throws` 固定声明 `@pylonts/core` 的 `CodeException` + `IOException`(见上文「定义」一节),`pylonts gen third` 强校验。
|
|
111
|
+
|
|
112
|
+
## 存储与生成
|
|
113
|
+
|
|
114
|
+
- 声明:`third_schema/{thirdApi.name}/*.third-service.ts`——一文件一服务(named export),目录名 = project.config.ts 的 `thirdApis` 实例名。
|
|
115
|
+
- 生成:`pylonts gen third`——对每个 thirdApi,扫描 `third_schema/{name}/`,按三步产出到 `third/{name}/`:
|
|
116
|
+
0. **throws 校验**(生成前置闸门):每个方法必须声明 `CodeException` + `IOException`,缺失即报错列出违规方法,不写任何产物;
|
|
117
|
+
1. **枚举**:模块导出的 `defineEnum` 实例 → `third/{name}/enums/{JsName}.enum.ts`(复用 enum-driver 的 `renderEnum`,与表枚举同一渲染),一 jsName 一文件;
|
|
118
|
+
2. **DTO**:每方法 args/results 用 typebox-driver 渲染 TypeBox 消息 + Static 类型,**一源文件一生成文件**,输出 `third/{name}/{stem}.third-service.gen.ts`(覆盖写);
|
|
119
|
+
3. **客户端骨架**:每服务渲染一个 class(构造配置接口 + 每方法 async 签名 + Not-implemented throw),输出 `third/{name}/{stem}.client.ts`——**已存在则跳过**(方法体是用户填充的),`--force` 覆盖。
|
|
120
|
+
- DTO 的枚举字段 import 走**相对路径** `./enums/{JsName}.enum`(DTO 与 enums/ 同处 `third/{name}/` 下,`moduleResolution: bundler` 解析 `.enum.ts`),不依赖根 `enums/` 子包。
|
|
121
|
+
- 生成物目录是子包:`third/` 目录带 `package.json`,`exports` 声明 `*.third-service.gen` 子路径,供 convert 产物 import。
|
|
122
|
+
|
|
123
|
+
## 客户端骨架是生成的,方法体是手写的
|
|
119
124
|
|
|
120
|
-
|
|
125
|
+
`defineThirdService` 只描述**契约**(构造配置 + 方法列表)。`gen third` 生成的 `{name}.client.ts` 是一个**骨架**:导出 `{Service}Config` 接口(TODO 注释标注 transport 配置——baseUrl/凭据/密钥属外部实现,不在契约内)+ `{Service}` 类(constructor 空实现),每方法带完整签名(args/results 类型从同名 `.third-service.gen` import type)与 `throw new Error('Not implemented: ...')` stub(含 `// @gen:stub` 标记,与 gen-service 骨架同一套 marker 约定)。**签名/throws 注释由生成器保证与契约同步,方法体、构造配置、签名加密等外部交互由人工填充**——已存在文件默认跳过(避免覆盖人工实现),`--force` 才重写。
|
|
126
|
+
|
|
127
|
+
## convert 防腐接线
|
|
128
|
+
|
|
129
|
+
第三方消息(`args`/`results` 是 `DtoMessage`,天然满足 `ConvertSourceSchema`)可直接作为 convert 的**源或目标**,用于 wire 消息 ↔ 本地模型的防腐翻译。```ts
|
|
130
|
+
// convert_schema/{api.name}/{app.name}/convert/wx-pay.convert.ts
|
|
131
|
+
import { wxPayService } from '../../../third_schema/wx/wxpay.third-service';
|
|
132
|
+
|
|
133
|
+
const getPayParams = wxPayService.methods.getPayParams;
|
|
134
|
+
|
|
135
|
+
export const wxPayConvert = defineConvert({
|
|
136
|
+
name: 'WxPayConvert',
|
|
137
|
+
api,
|
|
138
|
+
app: admin,
|
|
139
|
+
methods: {
|
|
140
|
+
toPayParams: {
|
|
141
|
+
sources: [order], // 本地订单表 → wire 请求
|
|
142
|
+
target: getPayParams.args,
|
|
143
|
+
},
|
|
144
|
+
toLocalPayResult: {
|
|
145
|
+
sources: [getPayParams.results], // wire 响应 → 本地 DTO
|
|
146
|
+
target: WxPayParamsResultDto,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
```
|
|
121
151
|
|
|
122
|
-
|
|
152
|
+
convert 文件绑定第三方服务身份时按 `{third-service}.convert.ts` 命名(上例 `wx-pay.convert.ts` 对应 `wxpay.third-service.ts` 的 `WxPayService`)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pylonts/dsl",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.15",
|
|
4
4
|
"description": "Schema definition DSL with drivers: MySQL DDL, TS enum, TypeBox schema codegen.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"author": "",
|
|
42
42
|
"license": "MIT",
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@pylonts/core": "^1.1.
|
|
44
|
+
"@pylonts/core": "^1.1.3"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"typescript": "^7.0.2",
|