@pylonts/dsl 1.1.3 → 1.1.5

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/table.md CHANGED
@@ -1,136 +1,136 @@
1
- # 定义表 (TableSchema)
2
-
3
- ## 字段类型
4
-
5
- | 构建器 | 类型 | jsType | MySQL 列 | 备注 |
6
- |---|---|---|---|---|
7
- | `stringField` | string | string | VARCHAR | 必填 `maxLength` |
8
- | `textField` | text | string | TEXT | |
9
- | `intField` | integer | number | INT | |
10
- | `bigintField` | bigint | string | BIGINT | 传输层走 string 保精度 |
11
- | `decimalField` | decimal | string | DECIMAL | 必填 `precision` / `scale`,传输层走 string 避免浮点误差 |
12
- | `booleanField` | boolean | boolean | TINYINT(1) | |
13
- | `dateField` | date | Date | DATE | |
14
- | `timeField` | time | string | TIME | |
15
- | `datetimeField` | datetime | Date | DATETIME | |
16
- | `enumField` | enum | string / number | VARCHAR(20) / TINYINT | 引用共享枚举定义,见 [enum.md](./enum.md) |
17
- | `jsonField` | json | object | JSON | |
18
-
19
- 通用扩展属性(构建器参数):`label`(中文标签)、`description`、`optional`、`readOnly`、`default`。
20
-
21
- **`optional` 默认语义(MySQL 惯例)**:不写 `optional` 或写 `optional: true` → 列可空,DDL 不渲染 `NOT NULL`;写 `optional: false` → 列必填(`NOT NULL`)。业务上必填的列必须显式声明。
22
-
23
- ## 定义表
24
-
25
- ```ts
26
- import { bigintField, defineTable, decimalField, stringField } from '@pylonts/dsl';
27
-
28
- const id = bigintField({ readOnly: true, label: '主键' });
29
-
30
- export const order = defineTable('order', {
31
- description: '订单',
32
- autoIncrement: id,
33
- columns: {
34
- id,
35
- order_no: stringField({ label: '订单号', maxLength: 32, optional: false }),
36
- amount: decimalField({ precision: 18, scale: 2, label: '金额', optional: false }),
37
- },
38
- primaryKey: id,
39
- });
40
- ```
41
-
42
- - 字段名从 map key 反写,`columns` 里的 key 就是列名。
43
- - 字段实例不可跨表复用(复用同一字段实例会抛错),枚举除外。
44
-
45
- ## 主键生成策略
46
-
47
- `autoIncrement` 与 `generator` 互斥,二者选一:
48
-
49
- | 属性 | 含义 | 例子 |
50
- |---|---|---|
51
- | `autoIncrement` | 引用自增主键字段,数据库负责生成值(MySQL `AUTO_INCREMENT`)。设了该属性的字段在 DTO 中自动标记为 optional(写入时不需要传) | `autoIncrement: id` |
52
- | `generator` | 主键由业务侧生成(非数据库自增),告诉下游工具用哪个 ID 生成器 | `generator: 'snowflake'` |
53
-
54
- ```ts
55
- // 数据库自增主键
56
- export const t1 = defineTable('t1', {
57
- autoIncrement: id,
58
- columns: { id: bigintField({ readOnly: true, label: '主键' }) },
59
- primaryKey: id,
60
- });
61
-
62
- // 业务生成主键(snowflake)
63
- export const t2 = defineTable('t2', {
64
- generator: 'snowflake',
65
- columns: { id: bigintField({ readOnly: true, label: '主键' }) },
66
- primaryKey: id,
67
- });
68
- ```
69
-
70
- ## 索引
71
-
72
- ```ts
73
- indexes: [
74
- { name: 'uk_uuid', columns: c_uuid, unique: true },
75
- { columns: [c_enum, c_date] }, // 名字缺省时 = 字段名 join '_'
76
- ],
77
- ```
78
-
79
- ## 外键与短语检查链
80
-
81
- 短语统一定义在 `schema/_dictionary.ts`(见 [dictionary.md](./dictionary.md)),表文件从那里 import:
82
-
83
- ```ts
84
- // schema/_dictionary.ts
85
- import { defineEntityPhrase } from '@pylonts/dsl';
86
-
87
- export const bd = defineEntityPhrase({ name: 'bd', label: 'BD推广员', description: '线下拓展商户的推广人员' });
88
- ```
89
-
90
- ```ts
91
- // schema/bd.table.ts
92
- import { bigintField, defineTable } from '@pylonts/dsl';
93
- import { bd as bdPhrase } from './_dictionary';
94
-
95
- const bdId = bigintField({ readOnly: true, label: 'BD ID' });
96
-
97
- export const bd = defineTable('bd', {
98
- description: 'BD',
99
- phrase: bdPhrase, // 链接词典条目:本表归属的实体
100
- columns: { id: bdId },
101
- primaryKey: bdId,
102
- });
103
- ```
104
-
105
- ```ts
106
- // schema/audit.table.ts
107
- import { bigintField, defineTable } from '@pylonts/dsl';
108
- import { bd } from './bd.table';
109
-
110
- const auditBdId = bigintField({ label: 'BD' });
111
-
112
- export const audit = defineTable('audit', {
113
- description: '审核',
114
- columns: {
115
- bd_id: auditBdId, // 列名必须 = 短语 + '_' + 被引用字段名
116
- },
117
- foreignKeys: {
118
- fk_audit_bd: { columns: auditBdId, references: bd.columns.id },
119
- },
120
- });
121
- ```
122
-
123
- **规则(defineTable 时强制检查)**:外键字段名必须等于 `被引用表.phrase.name + "_" + 被引用字段名`。即引用 `bd.id` 的字段必须叫 `bd_id`——`bd` 来自词典(权威短语),`id` 是 `bd` 表主键。
124
-
125
- **短语口径**:`defineEntityPhrase`(实体短语)解释的**就是短语本身**——`name` 即短语词干(如 `mer`),不是实体全名。引用 `merchant` 实体的字段用短语 `mer`(`mer_id`),**不用长语**(`merchant_id`)。短语要短(mer / bd / amt 三字母左右),语义由 `label`/`description` 解释。`TableSchema.phrase` 只接受实体短语(`defineEntityPhrase` 产物);业务短语(`defineBusinessPhrase`)用于字段命名后缀校验,见 [field-check.md](../../lint/docs/field-check.md)。
126
-
127
- - 被引用表未定义 `phrase` → 抛错(检查链要求每个被引用表都有短语)。
128
- - 命名不匹配 → 抛错并提示期望名,例如:
129
- `foreign key bad: field must be named bd_id (phrase bd + id), got merchant_id`
130
- - 关联表不需要 `phrase`。
131
-
132
- > **外键是逻辑作用**:`foreignKeys` 用于定义期命名强校验与关系表达,**DDL 默认不渲染物理 FOREIGN KEY 约束**(`pylonts gen sql init` 不传 `generateForeignKeys`)。数据完整性由 Service/DAO 层保证;如需物理约束,调用 `buildCreateTableSql(schema, { generateForeignKeys: true })`。
133
-
134
- ## 生成 SQL
135
-
1
+ # 定义表 (TableSchema)
2
+
3
+ ## 字段类型
4
+
5
+ | 构建器 | 类型 | jsType | MySQL 列 | 备注 |
6
+ |---|---|---|---|---|
7
+ | `stringField` | string | string | VARCHAR | 必填 `maxLength` |
8
+ | `textField` | text | string | TEXT | |
9
+ | `intField` | integer | number | INT | |
10
+ | `bigintField` | bigint | string | BIGINT | 传输层走 string 保精度 |
11
+ | `decimalField` | decimal | string | DECIMAL | 必填 `precision` / `scale`,传输层走 string 避免浮点误差 |
12
+ | `booleanField` | boolean | boolean | TINYINT(1) | |
13
+ | `dateField` | date | Date | DATE | |
14
+ | `timeField` | time | string | TIME | |
15
+ | `datetimeField` | datetime | Date | DATETIME | |
16
+ | `enumField` | enum | string / number | VARCHAR(20) / TINYINT | 引用共享枚举定义,见 [enum.md](./enum.md) |
17
+ | `jsonField` | json | object | JSON | |
18
+
19
+ 通用扩展属性(构建器参数):`label`(中文标签)、`description`、`optional`、`readOnly`、`default`。
20
+
21
+ **`optional` 默认语义(MySQL 惯例)**:不写 `optional` 或写 `optional: true` → 列可空,DDL 不渲染 `NOT NULL`;写 `optional: false` → 列必填(`NOT NULL`)。业务上必填的列必须显式声明。
22
+
23
+ ## 定义表
24
+
25
+ ```ts
26
+ import { bigintField, defineTable, decimalField, stringField } from '@pylonts/dsl';
27
+
28
+ const id = bigintField({ readOnly: true, label: '主键' });
29
+
30
+ export const order = defineTable('order', {
31
+ description: '订单',
32
+ autoIncrement: id,
33
+ columns: {
34
+ id,
35
+ order_no: stringField({ label: '订单号', maxLength: 32, optional: false }),
36
+ amount: decimalField({ precision: 18, scale: 2, label: '金额', optional: false }),
37
+ },
38
+ primaryKey: id,
39
+ });
40
+ ```
41
+
42
+ - 字段名从 map key 反写,`columns` 里的 key 就是列名。
43
+ - 字段实例不可跨表复用(复用同一字段实例会抛错),枚举除外。
44
+
45
+ ## 主键生成策略
46
+
47
+ `autoIncrement` 与 `generator` 互斥,二者选一:
48
+
49
+ | 属性 | 含义 | 例子 |
50
+ |---|---|---|
51
+ | `autoIncrement` | 引用自增主键字段,数据库负责生成值(MySQL `AUTO_INCREMENT`)。设了该属性的字段在 DTO 中自动标记为 optional(写入时不需要传) | `autoIncrement: id` |
52
+ | `generator` | 主键由业务侧生成(非数据库自增),告诉下游工具用哪个 ID 生成器 | `generator: 'snowflake'` |
53
+
54
+ ```ts
55
+ // 数据库自增主键
56
+ export const t1 = defineTable('t1', {
57
+ autoIncrement: id,
58
+ columns: { id: bigintField({ readOnly: true, label: '主键' }) },
59
+ primaryKey: id,
60
+ });
61
+
62
+ // 业务生成主键(snowflake)
63
+ export const t2 = defineTable('t2', {
64
+ generator: 'snowflake',
65
+ columns: { id: bigintField({ readOnly: true, label: '主键' }) },
66
+ primaryKey: id,
67
+ });
68
+ ```
69
+
70
+ ## 索引
71
+
72
+ ```ts
73
+ indexes: [
74
+ { name: 'uk_uuid', columns: c_uuid, unique: true },
75
+ { columns: [c_enum, c_date] }, // 名字缺省时 = 字段名 join '_'
76
+ ],
77
+ ```
78
+
79
+ ## 外键与短语检查链
80
+
81
+ 短语统一定义在 `schema/_dictionary.ts`(见 [dictionary.md](./dictionary.md)),表文件从那里 import:
82
+
83
+ ```ts
84
+ // schema/_dictionary.ts
85
+ import { defineEntityPhrase } from '@pylonts/dsl';
86
+
87
+ export const bd = defineEntityPhrase({ name: 'bd', label: 'BD推广员', description: '线下拓展商户的推广人员' });
88
+ ```
89
+
90
+ ```ts
91
+ // schema/bd.table.ts
92
+ import { bigintField, defineTable } from '@pylonts/dsl';
93
+ import { bd as bdPhrase } from './_dictionary';
94
+
95
+ const bdId = bigintField({ readOnly: true, label: 'BD ID' });
96
+
97
+ export const bd = defineTable('bd', {
98
+ description: 'BD',
99
+ phrase: bdPhrase, // 链接词典条目:本表归属的实体
100
+ columns: { id: bdId },
101
+ primaryKey: bdId,
102
+ });
103
+ ```
104
+
105
+ ```ts
106
+ // schema/audit.table.ts
107
+ import { bigintField, defineTable } from '@pylonts/dsl';
108
+ import { bd } from './bd.table';
109
+
110
+ const auditBdId = bigintField({ label: 'BD' });
111
+
112
+ export const audit = defineTable('audit', {
113
+ description: '审核',
114
+ columns: {
115
+ bd_id: auditBdId, // 列名必须 = 短语 + '_' + 被引用字段名
116
+ },
117
+ foreignKeys: {
118
+ fk_audit_bd: { columns: auditBdId, references: bd.columns.id },
119
+ },
120
+ });
121
+ ```
122
+
123
+ **规则(defineTable 时强制检查)**:外键字段名必须等于 `被引用表.phrase.name + "_" + 被引用字段名`。即引用 `bd.id` 的字段必须叫 `bd_id`——`bd` 来自词典(权威短语),`id` 是 `bd` 表主键。
124
+
125
+ **短语口径**:`defineEntityPhrase`(实体短语)解释的**就是短语本身**——`name` 即短语词干(如 `mer`),不是实体全名。引用 `merchant` 实体的字段用短语 `mer`(`mer_id`),**不用长语**(`merchant_id`)。短语要短(mer / bd / amt 三字母左右),语义由 `label`/`description` 解释。`TableSchema.phrase` 只接受实体短语(`defineEntityPhrase` 产物);业务短语(`defineBusinessPhrase`)用于字段命名后缀校验,见 [field-check.md](../../lint/docs/field-check.md)。
126
+
127
+ - 被引用表未定义 `phrase` → 抛错(检查链要求每个被引用表都有短语)。
128
+ - 命名不匹配 → 抛错并提示期望名,例如:
129
+ `foreign key bad: field must be named bd_id (phrase bd + id), got merchant_id`
130
+ - 关联表不需要 `phrase`。
131
+
132
+ > **外键是逻辑作用**:`foreignKeys` 用于定义期命名强校验与关系表达,**DDL 默认不渲染物理 FOREIGN KEY 约束**(`pylonts gen sql init` 不传 `generateForeignKeys`)。数据完整性由 Service/DAO 层保证;如需物理约束,调用 `buildCreateTableSql(schema, { generateForeignKeys: true })`。
133
+
134
+ ## 生成 SQL
135
+
136
136
  见 [driver.md](./driver.md)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pylonts/dsl",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "Schema definition DSL with drivers: MySQL DDL, TS enum, TypeBox schema codegen.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -28,4 +28,4 @@
28
28
  "typescript": "^7.0.2",
29
29
  "vitest": "^4.1.10"
30
30
  }
31
- }
31
+ }
package/src/action.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { SchemaBase } from './dsl.js';
2
-
3
- /** An action a user can perform on a page (e.g. submit, approve, reject).
4
- * Subclasses use `type` as the discriminator. */
5
- export interface ActionSchema extends SchemaBase {
6
- type: string;
7
- }
8
-
9
- export function defineAction(name: string, description?: string): ActionSchema {
10
- return { name, description, type: 'gesture' };
1
+ import { SchemaBase } from './dsl.js';
2
+
3
+ /** An action a user can perform on a page (e.g. submit, approve, reject).
4
+ * Subclasses use `type` as the discriminator. */
5
+ export interface ActionSchema extends SchemaBase {
6
+ type: string;
7
+ }
8
+
9
+ export function defineAction(name: string, description?: string): ActionSchema {
10
+ return { name, description, type: 'gesture' };
11
11
  }
package/src/asset.ts CHANGED
@@ -1,63 +1,63 @@
1
- /**
2
- * defineAsset — registry for reusable project assets (utils, components, flows, pages, hooks).
3
- *
4
- * Each asset declares its name, category, import path, tags, and optional usage example.
5
- * CLI scans all asset declarations, supports query (by tag/category) and generate (import links).
6
- *
7
- * // assets/utils.assets.ts
8
- * import { defineAsset } from '@pylonts/dsl';
9
- * export const formatAmt = defineAsset({
10
- * name: 'formatAmt',
11
- * category: 'util',
12
- * tags: ['amount', 'format'],
13
- * import: { name: 'formatAmt', from: '@/utils/amount' },
14
- * example: 'formatAmt(12345) => "12,345.00"',
15
- * });
16
- *
17
- * // CLI:
18
- * // pylonts gen asset list --tag form → all form-related assets
19
- * // pylonts gen asset import formatAmt → import { formatAmt } from '@/utils/amount';
20
- */
21
-
22
- export type AssetCategory = 'util' | 'component' | 'flow' | 'page' | 'hook';
23
-
24
- export interface AssetImport {
25
- /** Named export, e.g. 'formatAmt' */
26
- name: string;
27
- /** Module path, e.g. '@/utils/amount' */
28
- from: string;
29
- }
30
-
31
- export interface AssetConfig {
32
- name: string;
33
- category: AssetCategory;
34
- tags: string[];
35
- import: AssetImport;
36
- description?: string;
37
- /** One-liner usage example */
38
- example?: string;
39
- /** Link to detailed docs */
40
- see?: string;
41
- }
42
-
43
- export interface AssetDef {
44
- name: string;
45
- category: AssetCategory;
46
- tags: string[];
47
- import: AssetImport;
48
- description?: string;
49
- example?: string;
50
- see?: string;
51
- }
52
-
53
- export function defineAsset(config: AssetConfig): AssetDef {
54
- return {
55
- name: config.name,
56
- category: config.category,
57
- tags: config.tags,
58
- import: config.import,
59
- description: config.description,
60
- example: config.example,
61
- see: config.see,
62
- };
1
+ /**
2
+ * defineAsset — registry for reusable project assets (utils, components, flows, pages, hooks).
3
+ *
4
+ * Each asset declares its name, category, import path, tags, and optional usage example.
5
+ * CLI scans all asset declarations, supports query (by tag/category) and generate (import links).
6
+ *
7
+ * // assets/utils.assets.ts
8
+ * import { defineAsset } from '@pylonts/dsl';
9
+ * export const formatAmt = defineAsset({
10
+ * name: 'formatAmt',
11
+ * category: 'util',
12
+ * tags: ['amount', 'format'],
13
+ * import: { name: 'formatAmt', from: '@/utils/amount' },
14
+ * example: 'formatAmt(12345) => "12,345.00"',
15
+ * });
16
+ *
17
+ * // CLI:
18
+ * // pylonts gen asset list --tag form → all form-related assets
19
+ * // pylonts gen asset import formatAmt → import { formatAmt } from '@/utils/amount';
20
+ */
21
+
22
+ export type AssetCategory = 'util' | 'component' | 'flow' | 'page' | 'hook';
23
+
24
+ export interface AssetImport {
25
+ /** Named export, e.g. 'formatAmt' */
26
+ name: string;
27
+ /** Module path, e.g. '@/utils/amount' */
28
+ from: string;
29
+ }
30
+
31
+ export interface AssetConfig {
32
+ name: string;
33
+ category: AssetCategory;
34
+ tags: string[];
35
+ import: AssetImport;
36
+ description?: string;
37
+ /** One-liner usage example */
38
+ example?: string;
39
+ /** Link to detailed docs */
40
+ see?: string;
41
+ }
42
+
43
+ export interface AssetDef {
44
+ name: string;
45
+ category: AssetCategory;
46
+ tags: string[];
47
+ import: AssetImport;
48
+ description?: string;
49
+ example?: string;
50
+ see?: string;
51
+ }
52
+
53
+ export function defineAsset(config: AssetConfig): AssetDef {
54
+ return {
55
+ name: config.name,
56
+ category: config.category,
57
+ tags: config.tags,
58
+ import: config.import,
59
+ description: config.description,
60
+ example: config.example,
61
+ see: config.see,
62
+ };
63
63
  }
package/src/bases.ts CHANGED
@@ -19,8 +19,8 @@ export const PageResult = (row: DtoMessage): ImportRef => ({
19
19
  args: [row],
20
20
  });
21
21
 
22
- /** Paged rows type base without generic args — renders `import { PagedRows } from '@pylonts/core'` */
23
- export const PagedRows: ImportBase = { from: '@pylonts/core', name: 'PagedRows' };
22
+ /** Paged rows type base without generic args — renders `import type { PagedRows } from '@pylonts/core'` */
23
+ export const PagedRows: ImportBase = { from: '@pylonts/core', name: 'PagedRows', type: true };
24
24
 
25
25
  /** Paged rows type base — renders `import { PagedRows } from '@pylonts/core'` + `PagedRows(<row>)` */
26
26
  export const PageRows = (row: DtoMessage): ImportRef => ({
package/src/component.ts CHANGED
@@ -1,22 +1,22 @@
1
- import type { SchemaBase } from './dsl.js';
2
- import type { RefSchema } from './ref.js';
3
- import type { ActionSchema } from './action.js';
4
- import type { EventDataSchema } from './event.js';
5
-
6
- /** A component event trigger declaration. */
7
- export interface TriggerSchema extends SchemaBase {
8
- /** Data the event carries (e.g. e.detail). */
9
- eventData?: EventDataSchema;
10
- /** Actions that fire when the event occurs. */
11
- actions?: ActionSchema[];
12
- }
13
-
14
- /** A UI component declaration — a virtual schema that describes props and
15
- * event triggers, not a real renderable component.
16
- *
17
- * properties: data bindings via RefSchema (or literal values).
18
- * triggers: event name → TriggerSchema bindings. */
19
- export interface ComponentSchema extends SchemaBase {
20
- properties: Record<string, RefSchema | string | number | boolean>;
21
- triggers: Record<string, TriggerSchema>;
1
+ import type { SchemaBase } from './dsl.js';
2
+ import type { RefSchema } from './ref.js';
3
+ import type { ActionSchema } from './action.js';
4
+ import type { EventDataSchema } from './event.js';
5
+
6
+ /** A component event trigger declaration. */
7
+ export interface TriggerSchema extends SchemaBase {
8
+ /** Data the event carries (e.g. e.detail). */
9
+ eventData?: EventDataSchema;
10
+ /** Actions that fire when the event occurs. */
11
+ actions?: ActionSchema[];
12
+ }
13
+
14
+ /** A UI component declaration — a virtual schema that describes props and
15
+ * event triggers, not a real renderable component.
16
+ *
17
+ * properties: data bindings via RefSchema (or literal values).
18
+ * triggers: event name → TriggerSchema bindings. */
19
+ export interface ComponentSchema extends SchemaBase {
20
+ properties: Record<string, RefSchema | string | number | boolean>;
21
+ triggers: Record<string, TriggerSchema>;
22
22
  }
package/src/convert.ts CHANGED
@@ -1,13 +1,16 @@
1
- import type { SchemaBase } from './dsl.js';
2
-
3
- /** Declares post-call result → page data field mapping.
4
- * Driver generates per-item transform (e.g. .map()) before setData. */
5
- export interface ConvertSchema extends SchemaBase {
6
- type: 'convert';
7
- /** { targetField: sourceField } — renames or copies fields from call result. */
8
- fields: Record<string, string>;
9
- }
10
-
11
- export function defineConvert(name: string, fields: Record<string, string>): ConvertSchema {
12
- return { name, type: 'convert', fields };
13
- }
1
+ import type { SchemaBase } from './dsl.js';
2
+ import type { FrontAppSchema } from './project.js';
3
+
4
+ /** Declares post-call result page data field mapping.
5
+ * Driver generates per-item transform (e.g. .map()) before setData. */
6
+ export interface ConvertSchema extends SchemaBase {
7
+ type: 'convert';
8
+ /** The frontend app this convert belongs to (shared instance from project.config). */
9
+ app: FrontAppSchema;
10
+ /** { targetField: sourceField } — renames or copies fields from call result. */
11
+ fields: Record<string, string>;
12
+ }
13
+
14
+ export function defineConvert(name: string, app: FrontAppSchema, fields: Record<string, string>): ConvertSchema {
15
+ return { name, type: 'convert', app, fields };
16
+ }