@pylonts/dsl 1.1.1 → 1.1.3
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/dist/action.d.ts +7 -0
- package/dist/action.js +3 -0
- package/dist/asset.d.ts +2 -2
- package/dist/asset.js +2 -2
- package/dist/component.d.ts +20 -0
- package/dist/component.js +1 -0
- package/dist/convert.d.ts +9 -0
- package/dist/convert.js +3 -0
- package/dist/curd.d.ts +3 -1
- package/dist/db.d.ts +4 -0
- package/dist/db.js +29 -0
- package/dist/dsl.d.ts +5 -0
- package/dist/dto.d.ts +2 -2
- package/dist/dto.js +1 -1
- package/dist/event.d.ts +8 -0
- package/dist/event.js +3 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/mermaid-driver.js +2 -1
- package/dist/mock.d.ts +3 -21
- package/dist/mock.js +1 -18
- package/dist/mysql-driver.d.ts +4 -0
- package/dist/mysql-driver.js +8 -3
- package/dist/navigation.d.ts +22 -0
- package/dist/navigation.js +15 -0
- package/dist/page-def.d.ts +40 -0
- package/dist/page-def.js +38 -0
- package/dist/page-flow.d.ts +4 -2
- package/dist/page-flow.js +107 -12
- package/dist/page.d.ts +32 -10
- package/dist/page.js +20 -5
- package/dist/popup.d.ts +18 -0
- package/dist/popup.js +8 -0
- package/dist/project.d.ts +7 -0
- package/dist/provider.d.ts +54 -0
- package/dist/provider.js +18 -0
- package/dist/ref.d.ts +14 -0
- package/dist/ref.js +6 -0
- package/dist/route.d.ts +8 -0
- package/dist/route.js +3 -0
- package/docs/curd.md +110 -110
- package/docs/dto.md +66 -66
- package/docs/table.md +4 -2
- package/package.json +1 -1
- package/src/action.ts +11 -0
- package/src/asset.ts +63 -63
- package/src/bases.ts +29 -29
- package/src/component.ts +22 -0
- package/src/convert.ts +13 -0
- package/src/curd.ts +93 -91
- package/src/db.ts +37 -0
- package/src/dsl.ts +188 -182
- package/src/dto.ts +247 -247
- package/src/enum-driver.ts +42 -42
- package/src/event.ts +12 -0
- package/src/flow.ts +103 -103
- package/src/index.ts +31 -21
- package/src/mermaid-driver.ts +2 -1
- package/src/mock.ts +12 -45
- package/src/mysql-driver.ts +8 -2
- package/src/navigation.ts +29 -0
- package/src/page-def.ts +80 -0
- package/src/page-flow.ts +116 -14
- package/src/page.ts +51 -14
- package/src/patterns/retry.ts +54 -54
- package/src/popup.ts +25 -0
- package/src/project.ts +97 -90
- package/src/prototype.ts +29 -29
- package/src/provider.ts +73 -0
- package/src/ref.ts +19 -0
- package/src/route.ts +12 -0
- package/src/utils.ts +10 -10
package/dist/page.d.ts
CHANGED
|
@@ -1,26 +1,48 @@
|
|
|
1
1
|
import { SchemaBase } from './dsl.js';
|
|
2
2
|
import { FrontAppSchema } from './project.js';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import type { PageDef } from './page-def.js';
|
|
4
|
+
import type { RouteDataSchema } from './route.js';
|
|
5
|
+
export declare function getRegisteredPages(): ReadonlySet<PageSchema>;
|
|
6
|
+
export declare function clearRegisteredPages(): void;
|
|
7
|
+
/** Standalone page definition. A page is a shared value object: it belongs to
|
|
8
|
+
* exactly one frontend app. Actions live in the page skeleton (PageDef.actions),
|
|
9
|
+
* not on the page itself — the page only carries what the flow/topology needs. */
|
|
5
10
|
export interface PageSchema extends SchemaBase {
|
|
11
|
+
/** Short display name for topology diagrams. */
|
|
12
|
+
label: string;
|
|
6
13
|
/** The frontend app this page belongs to (shared instance from project.config). */
|
|
7
14
|
app: FrontAppSchema;
|
|
8
|
-
/**
|
|
9
|
-
|
|
15
|
+
/** Full page skeleton definition (optional). */
|
|
16
|
+
pageDef?: PageDef;
|
|
17
|
+
/** Route params this page expects (e.g. detail page expects productId). */
|
|
18
|
+
params?: RouteDataSchema;
|
|
10
19
|
}
|
|
11
|
-
/** An action a user can perform on a page (e.g. submit, approve, reject). */
|
|
12
|
-
export interface ActionSchema extends SchemaBase {
|
|
13
|
-
}
|
|
14
|
-
export declare function defineAction(name: string, description?: string): ActionSchema;
|
|
15
20
|
export declare function definePage(schema: {
|
|
16
21
|
name: string;
|
|
22
|
+
label: string;
|
|
17
23
|
description?: string;
|
|
18
24
|
app: FrontAppSchema;
|
|
19
|
-
|
|
25
|
+
pageDef?: PageDef;
|
|
26
|
+
params?: RouteDataSchema;
|
|
20
27
|
}): PageSchema;
|
|
28
|
+
/** Page with bottom tab navigation. tabs collects the child pages reachable via tab switch. */
|
|
29
|
+
export interface TabPageSchema extends PageSchema {
|
|
30
|
+
tabs: PageSchema[];
|
|
31
|
+
}
|
|
32
|
+
export declare function defineTabPage(schema: {
|
|
33
|
+
name: string;
|
|
34
|
+
label: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
app: FrontAppSchema;
|
|
37
|
+
tabs: PageSchema[];
|
|
38
|
+
pageDef?: PageDef;
|
|
39
|
+
params?: RouteDataSchema;
|
|
40
|
+
}): TabPageSchema;
|
|
21
41
|
/** A page node in a page-driven flow: every node is a page, and a page belongs to an app. */
|
|
22
42
|
export interface Page extends SchemaBase {
|
|
43
|
+
/** Short display name for topology diagrams. */
|
|
44
|
+
label?: string;
|
|
23
45
|
/** The frontend app this page belongs to (shared instance from project.config). */
|
|
24
46
|
app: FrontAppSchema;
|
|
25
47
|
}
|
|
26
|
-
export declare function page(app: FrontAppSchema, name: string, description?: string): Page;
|
|
48
|
+
export declare function page(app: FrontAppSchema, name: string, description?: string, label?: string): Page;
|
package/dist/page.js
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Page definitions: standalone page schemas and the page node type used by
|
|
2
|
+
// page-driven flows. Kept separate from page-flow.ts (the flow graph itself).
|
|
3
|
+
// Module-level registry: every definePage/defineTabPage call registers its
|
|
4
|
+
// result so definePageFlow can check that no page is left out.
|
|
5
|
+
const _pageRegistry = new Set();
|
|
6
|
+
export function getRegisteredPages() {
|
|
7
|
+
return _pageRegistry;
|
|
8
|
+
}
|
|
9
|
+
export function clearRegisteredPages() {
|
|
10
|
+
_pageRegistry.clear();
|
|
3
11
|
}
|
|
4
12
|
export function definePage(schema) {
|
|
5
|
-
|
|
13
|
+
const p = { ...schema };
|
|
14
|
+
_pageRegistry.add(p);
|
|
15
|
+
return p;
|
|
16
|
+
}
|
|
17
|
+
export function defineTabPage(schema) {
|
|
18
|
+
const p = { ...schema };
|
|
19
|
+
_pageRegistry.add(p);
|
|
20
|
+
return p;
|
|
6
21
|
}
|
|
7
|
-
export function page(app, name, description) {
|
|
8
|
-
return { name, app, description };
|
|
22
|
+
export function page(app, name, description, label) {
|
|
23
|
+
return { name, label, app, description };
|
|
9
24
|
}
|
package/dist/popup.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ActionSchema } from './action.js';
|
|
2
|
+
import type { RefSchema } from './ref.js';
|
|
3
|
+
/** Display a toast notification. React: toast/message UI. Mini-program: wx.showToast. */
|
|
4
|
+
export interface ToastAction extends ActionSchema {
|
|
5
|
+
type: 'toast';
|
|
6
|
+
message: string | RefSchema;
|
|
7
|
+
icon?: 'success' | 'error' | 'loading' | 'none';
|
|
8
|
+
}
|
|
9
|
+
/** Display an alert dialog. React: modal. Mini-program: wx.showModal. */
|
|
10
|
+
export interface AlertAction extends ActionSchema {
|
|
11
|
+
type: 'alert';
|
|
12
|
+
title: string | RefSchema;
|
|
13
|
+
content: string | RefSchema;
|
|
14
|
+
}
|
|
15
|
+
export declare const popup: {
|
|
16
|
+
toast(message: string | RefSchema, icon?: 'success' | 'error' | 'loading' | 'none'): ToastAction;
|
|
17
|
+
alert(title: string | RefSchema, content: string | RefSchema): AlertAction;
|
|
18
|
+
};
|
package/dist/popup.js
ADDED
package/dist/project.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SchemaBase } from './dsl.js';
|
|
2
|
+
import type { TableSchema } from './db.js';
|
|
2
3
|
/** Frontend form factor. Closed enum, extend when new form factors appear. */
|
|
3
4
|
export type FrontType = 'admin' | 'wxmini';
|
|
4
5
|
/** A frontend application (e.g. admin console, wechat mini program). */
|
|
@@ -6,6 +7,12 @@ export interface FrontAppSchema extends SchemaBase {
|
|
|
6
7
|
type: FrontType;
|
|
7
8
|
/** Source directory relative to project root, e.g. 'web-admin/'. */
|
|
8
9
|
dir: string;
|
|
10
|
+
/**
|
|
11
|
+
* Tenant table for this app. When set, all tables with a foreign key
|
|
12
|
+
* pointing to this table get automatic tenant scoping: the tenant PK
|
|
13
|
+
* value is injected from `user.id` into all curd operations.
|
|
14
|
+
*/
|
|
15
|
+
tenant?: TableSchema;
|
|
9
16
|
}
|
|
10
17
|
/** A backend API service. apps references shared FrontAppSchema instances. */
|
|
11
18
|
export interface ProjectApiSchema extends SchemaBase {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ImportBase } from './import-base.js';
|
|
2
|
+
import type { ImportableSchemaBase } from './dsl.js';
|
|
3
|
+
import type { DtoField, DtoMessage, DtoArrayField, DtoObjectField } from './dto.js';
|
|
4
|
+
import type { ActionSchema } from './action.js';
|
|
5
|
+
import type { RefSchema } from './ref.js';
|
|
6
|
+
import type { ConvertSchema } from './convert.js';
|
|
7
|
+
/** Parameter data source for a call argument. */
|
|
8
|
+
export type DataRef = {
|
|
9
|
+
type: 'route';
|
|
10
|
+
key: string;
|
|
11
|
+
} | {
|
|
12
|
+
type: 'data';
|
|
13
|
+
key: string;
|
|
14
|
+
} | {
|
|
15
|
+
type: 'value';
|
|
16
|
+
value: unknown;
|
|
17
|
+
};
|
|
18
|
+
/** Create a route-parameter reference. */
|
|
19
|
+
export declare function route(key: string): DataRef;
|
|
20
|
+
/** Create a page-data reference. */
|
|
21
|
+
export declare function data(key: string): DataRef;
|
|
22
|
+
export interface CallAction extends ActionSchema {
|
|
23
|
+
type: 'call';
|
|
24
|
+
func: ProviderSchema;
|
|
25
|
+
args?: Record<string, DtoField | DtoMessage | DtoArrayField | DtoObjectField | RefSchema | string>;
|
|
26
|
+
}
|
|
27
|
+
export declare function call(func: ProviderSchema, args?: Record<string, DtoField | DtoMessage | DtoArrayField | DtoObjectField | RefSchema | string>): CallAction;
|
|
28
|
+
/** Provider function signature. The generated API client exposes one function
|
|
29
|
+
* per endpoint; ProviderSchema gives that function a name and types. */
|
|
30
|
+
export interface ProviderSchema extends ImportableSchemaBase {
|
|
31
|
+
isAsync: boolean;
|
|
32
|
+
/** Input DTO (buildInput / buildQuery / buildPk result). */
|
|
33
|
+
args: DtoMessage;
|
|
34
|
+
/** Output DTO (buildOutput result) or primitive. */
|
|
35
|
+
results: DtoMessage | number | boolean | string;
|
|
36
|
+
}
|
|
37
|
+
/** Define a provider function. */
|
|
38
|
+
export declare function defineProvider(name: string, schema: {
|
|
39
|
+
isAsync: boolean;
|
|
40
|
+
args: DtoMessage;
|
|
41
|
+
results: DtoMessage | number | boolean | string;
|
|
42
|
+
description?: string;
|
|
43
|
+
importRef?: ImportBase;
|
|
44
|
+
}): ProviderSchema;
|
|
45
|
+
/** Assign a call's result to a page data field.
|
|
46
|
+
* React: setState({ [field]: await ... }). Mini-program: this.setData({ [field]: ... }). */
|
|
47
|
+
export interface SetDataAction extends ActionSchema {
|
|
48
|
+
type: 'setData';
|
|
49
|
+
call: CallAction;
|
|
50
|
+
field: DtoField;
|
|
51
|
+
/** Optional field-level transform before assignment. */
|
|
52
|
+
convert?: ConvertSchema;
|
|
53
|
+
}
|
|
54
|
+
export declare function setData(call: CallAction, field: DtoField, convert?: ConvertSchema): SetDataAction;
|
package/dist/provider.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Create a route-parameter reference. */
|
|
2
|
+
export function route(key) {
|
|
3
|
+
return { type: 'route', key };
|
|
4
|
+
}
|
|
5
|
+
/** Create a page-data reference. */
|
|
6
|
+
export function data(key) {
|
|
7
|
+
return { type: 'data', key };
|
|
8
|
+
}
|
|
9
|
+
export function call(func, args) {
|
|
10
|
+
return { name: func.name, type: 'call', func, args };
|
|
11
|
+
}
|
|
12
|
+
/** Define a provider function. */
|
|
13
|
+
export function defineProvider(name, schema) {
|
|
14
|
+
return { name, ...schema };
|
|
15
|
+
}
|
|
16
|
+
export function setData(call, field, convert) {
|
|
17
|
+
return { name: 'setData', type: 'setData', call, field, convert };
|
|
18
|
+
}
|
package/dist/ref.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CollectionSchemaBase } from './dsl.js';
|
|
2
|
+
import type { DtoField } from './dto.js';
|
|
3
|
+
/** A reference to a field within another schema. Carries a .schema back-reference
|
|
4
|
+
* so the driver knows where the data comes from (route params, page data, etc.). */
|
|
5
|
+
export interface RefSchema {
|
|
6
|
+
name: string;
|
|
7
|
+
/** Schema back-reference — tells driver the data source. */
|
|
8
|
+
schema: CollectionSchemaBase;
|
|
9
|
+
/** The referenced field instance. */
|
|
10
|
+
field: DtoField;
|
|
11
|
+
}
|
|
12
|
+
/** Create a reference to a field in a source schema.
|
|
13
|
+
* Writes back field.schema to the source so the driver can trace origin. */
|
|
14
|
+
export declare function defineRef(source: CollectionSchemaBase, field: DtoField): RefSchema;
|
package/dist/ref.js
ADDED
package/dist/route.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CollectionSchemaBase } from './dsl.js';
|
|
2
|
+
import type { DtoField } from './dto.js';
|
|
3
|
+
/** Route params: data that arrives from the navigation URL / route. */
|
|
4
|
+
export interface RouteDataSchema extends CollectionSchemaBase {
|
|
5
|
+
type: 'route';
|
|
6
|
+
fields: Record<string, DtoField>;
|
|
7
|
+
}
|
|
8
|
+
export declare function defineRouteData(name: string, fields: RouteDataSchema['fields']): RouteDataSchema;
|
package/dist/route.js
ADDED
package/docs/curd.md
CHANGED
|
@@ -1,111 +1,111 @@
|
|
|
1
|
-
# 管理端 CRUD 页面标准(CurdSchema)
|
|
2
|
-
|
|
3
|
-
CurdSchema 是**管理端专用**(`FrontAppSchema.type === 'admin'`)的 CRUD 页面标准:绑定一张实体表 + 一个管理端 app,描述列表页与新增/编辑/详情动作页生成所需的全部页面语义。一条 CurdSchema = 列表页(+ 动作页)的生成规格。
|
|
4
|
-
|
|
5
|
-
页面定义文件按实体组织:`{project}/pages/{entity}.curd.ts`(与 `schema/*.table.ts` 平级)。
|
|
6
|
-
|
|
7
|
-
**CurdSchema 只依赖 table schema(`Field` 实例),不挂钩 DTO(`DtoMessage`)**——DTO 由生成器按标准从 `columns` 推导。
|
|
8
|
-
|
|
9
|
-
## 定义
|
|
10
|
-
|
|
11
|
-
```ts
|
|
12
|
-
import { defineCurd } from '@pylonts/dsl';
|
|
13
|
-
|
|
14
|
-
export const orderCurd = defineCurd('order-curd', {
|
|
15
|
-
description: '订单管理',
|
|
16
|
-
app: webAdmin, // 所属管理端(project.config.ts 的 FrontAppSchema 共享实例)
|
|
17
|
-
table: order, // 绑定实体表(共享实例)
|
|
18
|
-
title: '订单管理',
|
|
19
|
-
section: '订单管理', // 必填:sidebar 分组名
|
|
20
|
-
actions: [defineAction('EXPORT', '导出订单')], // 额外操作按钮
|
|
21
|
-
actionPages: {
|
|
22
|
-
add: { mode: 'modal', columns: [order.columns.order_no, order.columns.mer_id] },
|
|
23
|
-
update: { mode: 'modal', columns: [order.columns.id, order.columns.order_no] },
|
|
24
|
-
detail: { mode: 'route', columns: [order.columns.id, order.columns.order_no, order.columns.amount] },
|
|
25
|
-
},
|
|
26
|
-
list: {
|
|
27
|
-
columns: [order.columns.id, order.columns.order_no, merchant.columns.name], // 可跨表
|
|
28
|
-
keyword: { columns: [order.columns.order_no] }, // 模糊搜索(本表字段)
|
|
29
|
-
orderBy: { column: order.columns.id, direction: 'desc' },
|
|
30
|
-
searchFields: [{ field: order.columns.mer_id }, { field: order.columns.order_no, op: 'like' }],
|
|
31
|
-
columnTitles: { order_no: '订单号', name: '商户名称' }, // Field.name → 文案
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## 字段
|
|
37
|
-
|
|
38
|
-
| 字段 | 类型 | 说明 |
|
|
39
|
-
|---|---|---|
|
|
40
|
-
| `app` | `FrontAppSchema` | 所属管理端(共享实例,`type` 必须为 `'admin'`) |
|
|
41
|
-
| `table` | `TableSchema` | 绑定实体表(共享实例) |
|
|
42
|
-
| `title` | `string` | 列表页中文标题 |
|
|
43
|
-
| `section` | `string` | **必填**:sidebar 分组名(`gen
|
|
44
|
-
| `actions?` | `ActionSchema[]` | 页面额外可执行动作(标准 CRUD 之外,如导出、审核) |
|
|
45
|
-
| `actionPages?` | `{ add? / update? / detail? }` | 动作页:`{ mode: 'modal' \| 'route'; columns: Field[] }` |
|
|
46
|
-
| `list` | `CurdListConfig` | 列表页配置(必填) |
|
|
47
|
-
|
|
48
|
-
### ActionPage
|
|
49
|
-
|
|
50
|
-
| 字段 | 类型 | 说明 |
|
|
51
|
-
|---|---|---|
|
|
52
|
-
| `mode` | `'modal' \| 'route'` | 弹窗或独立路由 |
|
|
53
|
-
| `columns` | `Field[]` | 该页面渲染的字段,**必填非空**——前端要显示的字段必须全部显式列出 |
|
|
54
|
-
|
|
55
|
-
### CurdListConfig
|
|
56
|
-
|
|
57
|
-
| 字段 | 类型 | 说明 |
|
|
58
|
-
|---|---|---|
|
|
59
|
-
| `columns` | `Field[]` | 列表列,**必填非空**——前端要显示的字段必须全部显式列出;可含跨表字段(见下) |
|
|
60
|
-
| `keyword?` | `{ columns: Field[] }` | 模糊搜索,columns 必须是**本表字段实例** |
|
|
61
|
-
| `orderBy` | `{ column: Field; direction: 'asc' \| 'desc' }` | 默认排序,**必填**,column 与 direction 都必填;column 必须是**本表字段实例** |
|
|
62
|
-
| `searchFields?` | `{ field: Field; op?: Operator }[]` | 搜索条件字段,op 默认 `'eq'`,可选 `eq/gt/gte/lt/lte/like/ne` |
|
|
63
|
-
| `columnTitles?` | `Record<string, string>` | 列标题覆盖:`Field.name` → 中文文案 |
|
|
64
|
-
|
|
65
|
-
## 跨表字段
|
|
66
|
-
|
|
67
|
-
`columns` / `searchFields` 里的 `Field` 实例可指向**本表或其他表**的列——列表列与搜索条件因此可以显示关联表字段(如订单列表显示商户名称):
|
|
68
|
-
|
|
69
|
-
```ts
|
|
70
|
-
list: {
|
|
71
|
-
columns: [order.columns.id, order.columns.order_no, merchant.columns.name], // 跨表:字段指向 merchant.name
|
|
72
|
-
}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
## 默认与校验
|
|
76
|
-
|
|
77
|
-
- `list.columns` / `actionPages.*.columns` **必填非空**(不允许省略、不允许空数组)——前端显示什么必须显式定义
|
|
78
|
-
- `list.orderBy` **必填**,`column` 与 `direction` 都必填(规格:默认主键 desc 由定义方显式写出)
|
|
79
|
-
- 运行时校验(`defineCurd`,仿 `defineTable` 强校验风格):
|
|
80
|
-
- `app.type` 必须为 `'admin'`,否则抛错
|
|
81
|
-
- 所有 `columns` 非空,否则抛错
|
|
82
|
-
- `list.keyword.columns` / `list.orderBy.column` 必须属于 `table`,否则抛错
|
|
83
|
-
- `list.columns` / `list.searchFields` 允许跨表,**不校验归属**
|
|
84
|
-
- 生成时校验(curd 生成器,`DtoSchemaGen.add` / `update`):
|
|
85
|
-
- 表配置 `autoIncrement` 或 `generator`(主键由服务端生成)时,`actionPages.add.columns` **不允许包含主键字段**,否则抛错——AddRequest 不携带服务端生成的主键
|
|
86
|
-
- `actionPages.update.columns` **必须包含主键字段**,否则抛错——UpdateRequest 靠主键定位记录
|
|
87
|
-
|
|
88
|
-
## DTO 推导(生成器约定)
|
|
89
|
-
|
|
90
|
-
DTO 由 curd 生成器从 `CurdSchema` 按标准命名推导,页面语义不持有 DTO 实例:
|
|
91
|
-
|
|
92
|
-
| DTO | 命名 | 字段来源 |
|
|
93
|
-
|---|---|---|
|
|
94
|
-
| Row | `{Pascal}Row` | `list.columns` |
|
|
95
|
-
| AddRequest | `{Pascal}AddRequest` | `actionPages.add.columns` |
|
|
96
|
-
| UpdateRequest | `{Pascal}UpdateRequest` | `actionPages.update.columns` |
|
|
97
|
-
| DetailRequest | `{Pascal}DetailRequest` | 主键 |
|
|
98
|
-
| DetailResponse | `{Pascal}DetailResponse` | `actionPages.detail.columns` |
|
|
99
|
-
|
|
100
|
-
## 与旧 PageConfig 的差异
|
|
101
|
-
|
|
102
|
-
| PageConfig(旧方案,已废弃) | CurdSchema |
|
|
103
|
-
|---|---|
|
|
104
|
-
| `module: string` | 由 `app` 推导(后端模块 == app 1:1) |
|
|
105
|
-
| `schema: 'bd'` 字符串 | `table: TableSchema` 实例(类型安全) |
|
|
106
|
-
| `operations: { label, action }` | `actions: ActionSchema[]` |
|
|
107
|
-
| `detail.mode` 单例 | `actionPages.detail.mode` |
|
|
108
|
-
| `forms.add / forms.update` | `actionPages.add / actionPages.update` |
|
|
109
|
-
| `keyword` / `orderBy` / `columnTitles` | `list.keyword` / `list.orderBy` / `list.columnTitles`(列改字段实例引用) |
|
|
110
|
-
| `naming` | 去掉(DTO 命名是生成器约定,非页面语义) |
|
|
1
|
+
# 管理端 CRUD 页面标准(CurdSchema)
|
|
2
|
+
|
|
3
|
+
CurdSchema 是**管理端专用**(`FrontAppSchema.type === 'admin'`)的 CRUD 页面标准:绑定一张实体表 + 一个管理端 app,描述列表页与新增/编辑/详情动作页生成所需的全部页面语义。一条 CurdSchema = 列表页(+ 动作页)的生成规格。
|
|
4
|
+
|
|
5
|
+
页面定义文件按实体组织:`{project}/pages/{entity}.curd.ts`(与 `schema/*.table.ts` 平级)。
|
|
6
|
+
|
|
7
|
+
**CurdSchema 只依赖 table schema(`Field` 实例),不挂钩 DTO(`DtoMessage`)**——DTO 由生成器按标准从 `columns` 推导。
|
|
8
|
+
|
|
9
|
+
## 定义
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { defineCurd } from '@pylonts/dsl';
|
|
13
|
+
|
|
14
|
+
export const orderCurd = defineCurd('order-curd', {
|
|
15
|
+
description: '订单管理',
|
|
16
|
+
app: webAdmin, // 所属管理端(project.config.ts 的 FrontAppSchema 共享实例)
|
|
17
|
+
table: order, // 绑定实体表(共享实例)
|
|
18
|
+
title: '订单管理',
|
|
19
|
+
section: '订单管理', // 必填:sidebar 分组名
|
|
20
|
+
actions: [defineAction('EXPORT', '导出订单')], // 额外操作按钮
|
|
21
|
+
actionPages: {
|
|
22
|
+
add: { mode: 'modal', columns: [order.columns.order_no, order.columns.mer_id] },
|
|
23
|
+
update: { mode: 'modal', columns: [order.columns.id, order.columns.order_no] },
|
|
24
|
+
detail: { mode: 'route', columns: [order.columns.id, order.columns.order_no, order.columns.amount] },
|
|
25
|
+
},
|
|
26
|
+
list: {
|
|
27
|
+
columns: [order.columns.id, order.columns.order_no, merchant.columns.name], // 可跨表
|
|
28
|
+
keyword: { columns: [order.columns.order_no] }, // 模糊搜索(本表字段)
|
|
29
|
+
orderBy: { column: order.columns.id, direction: 'desc' },
|
|
30
|
+
searchFields: [{ field: order.columns.mer_id }, { field: order.columns.order_no, op: 'like' }],
|
|
31
|
+
columnTitles: { order_no: '订单号', name: '商户名称' }, // Field.name → 文案
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 字段
|
|
37
|
+
|
|
38
|
+
| 字段 | 类型 | 说明 |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `app` | `FrontAppSchema` | 所属管理端(共享实例,`type` 必须为 `'admin'`) |
|
|
41
|
+
| `table` | `TableSchema` | 绑定实体表(共享实例) |
|
|
42
|
+
| `title` | `string` | 列表页中文标题 |
|
|
43
|
+
| `section` | `string` | **必填**:sidebar 分组名(`pylonts gen curd` 据此生成路由注册的 `section` 字段) |
|
|
44
|
+
| `actions?` | `ActionSchema[]` | 页面额外可执行动作(标准 CRUD 之外,如导出、审核) |
|
|
45
|
+
| `actionPages?` | `{ add? / update? / detail? }` | 动作页:`{ mode: 'modal' \| 'route'; columns: Field[] }` |
|
|
46
|
+
| `list` | `CurdListConfig` | 列表页配置(必填) |
|
|
47
|
+
|
|
48
|
+
### ActionPage
|
|
49
|
+
|
|
50
|
+
| 字段 | 类型 | 说明 |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `mode` | `'modal' \| 'route'` | 弹窗或独立路由 |
|
|
53
|
+
| `columns` | `Field[]` | 该页面渲染的字段,**必填非空**——前端要显示的字段必须全部显式列出 |
|
|
54
|
+
|
|
55
|
+
### CurdListConfig
|
|
56
|
+
|
|
57
|
+
| 字段 | 类型 | 说明 |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `columns` | `Field[]` | 列表列,**必填非空**——前端要显示的字段必须全部显式列出;可含跨表字段(见下) |
|
|
60
|
+
| `keyword?` | `{ columns: Field[] }` | 模糊搜索,columns 必须是**本表字段实例** |
|
|
61
|
+
| `orderBy` | `{ column: Field; direction: 'asc' \| 'desc' }` | 默认排序,**必填**,column 与 direction 都必填;column 必须是**本表字段实例** |
|
|
62
|
+
| `searchFields?` | `{ field: Field; op?: Operator }[]` | 搜索条件字段,op 默认 `'eq'`,可选 `eq/gt/gte/lt/lte/like/ne` |
|
|
63
|
+
| `columnTitles?` | `Record<string, string>` | 列标题覆盖:`Field.name` → 中文文案 |
|
|
64
|
+
|
|
65
|
+
## 跨表字段
|
|
66
|
+
|
|
67
|
+
`columns` / `searchFields` 里的 `Field` 实例可指向**本表或其他表**的列——列表列与搜索条件因此可以显示关联表字段(如订单列表显示商户名称):
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
list: {
|
|
71
|
+
columns: [order.columns.id, order.columns.order_no, merchant.columns.name], // 跨表:字段指向 merchant.name
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## 默认与校验
|
|
76
|
+
|
|
77
|
+
- `list.columns` / `actionPages.*.columns` **必填非空**(不允许省略、不允许空数组)——前端显示什么必须显式定义
|
|
78
|
+
- `list.orderBy` **必填**,`column` 与 `direction` 都必填(规格:默认主键 desc 由定义方显式写出)
|
|
79
|
+
- 运行时校验(`defineCurd`,仿 `defineTable` 强校验风格):
|
|
80
|
+
- `app.type` 必须为 `'admin'`,否则抛错
|
|
81
|
+
- 所有 `columns` 非空,否则抛错
|
|
82
|
+
- `list.keyword.columns` / `list.orderBy.column` 必须属于 `table`,否则抛错
|
|
83
|
+
- `list.columns` / `list.searchFields` 允许跨表,**不校验归属**
|
|
84
|
+
- 生成时校验(curd 生成器,`DtoSchemaGen.add` / `update`):
|
|
85
|
+
- 表配置 `autoIncrement` 或 `generator`(主键由服务端生成)时,`actionPages.add.columns` **不允许包含主键字段**,否则抛错——AddRequest 不携带服务端生成的主键
|
|
86
|
+
- `actionPages.update.columns` **必须包含主键字段**,否则抛错——UpdateRequest 靠主键定位记录
|
|
87
|
+
|
|
88
|
+
## DTO 推导(生成器约定)
|
|
89
|
+
|
|
90
|
+
DTO 由 curd 生成器从 `CurdSchema` 按标准命名推导,页面语义不持有 DTO 实例:
|
|
91
|
+
|
|
92
|
+
| DTO | 命名 | 字段来源 |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| Row | `{Pascal}Row` | `list.columns` |
|
|
95
|
+
| AddRequest | `{Pascal}AddRequest` | `actionPages.add.columns` |
|
|
96
|
+
| UpdateRequest | `{Pascal}UpdateRequest` | `actionPages.update.columns` |
|
|
97
|
+
| DetailRequest | `{Pascal}DetailRequest` | 主键 |
|
|
98
|
+
| DetailResponse | `{Pascal}DetailResponse` | `actionPages.detail.columns` |
|
|
99
|
+
|
|
100
|
+
## 与旧 PageConfig 的差异
|
|
101
|
+
|
|
102
|
+
| PageConfig(旧方案,已废弃) | CurdSchema |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `module: string` | 由 `app` 推导(后端模块 == app 1:1) |
|
|
105
|
+
| `schema: 'bd'` 字符串 | `table: TableSchema` 实例(类型安全) |
|
|
106
|
+
| `operations: { label, action }` | `actions: ActionSchema[]` |
|
|
107
|
+
| `detail.mode` 单例 | `actionPages.detail.mode` |
|
|
108
|
+
| `forms.add / forms.update` | `actionPages.add / actionPages.update` |
|
|
109
|
+
| `keyword` / `orderBy` / `columnTitles` | `list.keyword` / `list.orderBy` / `list.columnTitles`(列改字段实例引用) |
|
|
110
|
+
| `naming` | 去掉(DTO 命名是生成器约定,非页面语义) |
|
|
111
111
|
| DTO 引用(`request` / `fields` / `DtoFields`) | 去掉(DTO 由生成器推导,页面只依赖 table) |
|
package/docs/dto.md
CHANGED
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
# 定义 DTO(四种方向)
|
|
2
|
-
|
|
3
|
-
DTO 描述接口出入参。方向决定可选性规则,由各方向工厂设置;**规则统一只在 `field.optional === undefined` 时生效**(作者已显式设置的跳过):
|
|
4
|
-
|
|
5
|
-
| 构建器 | 方向 | 可选性规则 |
|
|
6
|
-
|---|---|---|
|
|
7
|
-
| `buildInput` | input | 按 DB 列规则:主键 →
|
|
8
|
-
| `buildOutput` | output | 不设置(保持字段构建器/作者设置) |
|
|
9
|
-
| `buildQuery` | query | 全部可选 |
|
|
10
|
-
| `buildPk` | pk | 主键字段必填,其他字段可选 |
|
|
11
|
-
|
|
12
|
-
## 从表提取字段
|
|
13
|
-
|
|
14
|
-
```ts
|
|
15
|
-
import { buildInput, buildQuery, dtoField, from } from '@pylonts/dsl';
|
|
16
|
-
|
|
17
|
-
// 输入:新增订单
|
|
18
|
-
buildInput('OrderAddRequest', { ...from(order, [order.columns.mer_id, order.columns.amount]) });
|
|
19
|
-
|
|
20
|
-
// 输出:订单行
|
|
21
|
-
buildOutput('OrderRow', from(order, [order.columns.id, order.columns.order_no]));
|
|
22
|
-
|
|
23
|
-
// 查询:分页 + 过滤(query 字段恒为可选,.op() 声明比较操作符)
|
|
24
|
-
buildQuery('OrderPageQuery', {
|
|
25
|
-
keyword: dtoField(stringField({ maxLength: 32 })).setOperator('like'),
|
|
26
|
-
...from(order, [order.columns.mer_id]),
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
// 主键:按 id 取详情
|
|
30
|
-
buildPk('OrderDetailRequest', from(order, [order.columns.id]));
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
`from(table, fields)` 提取表字段包装为 DTO 字段,字段实例与表共享,`name/schema` 保持指向表。**DTO 字段名转 camelCase**(`mer_id` → `merId`),与 DB 列名(snake_case)分离。`from()` 本身不做任何可选性推断——推断在各方向工厂。
|
|
34
|
-
|
|
35
|
-
## 独立字段
|
|
36
|
-
|
|
37
|
-
不来自表的内联字段直接用 `dtoField(...)` 包装任意字段构建器,可加 `pattern`、`optional`、`operator`、`default`。
|
|
38
|
-
|
|
39
|
-
```ts
|
|
40
|
-
dtoField(stringField({ maxLength: 32 })).setOperator('like')
|
|
41
|
-
dtoField(intField()).setDefault(0) // TypeBox default 注解
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## 默认值
|
|
45
|
-
|
|
46
|
-
- `setDefault(v)` 设 DTO 层默认值,渲染为 TypeBox `default:` 注解(`Type.String({ default: 'PENDING' })`、`Type.Enum(OrderStatus, { default: OrderStatus.PENDING })`)。
|
|
47
|
-
- 枚举默认值必须是该枚举的成员值(value),渲染时解析为成员引用;非成员值直接报错。
|
|
48
|
-
- 未显式设置时,fallback 到字段构建器的 `default`(DB 默认值,string),`from()` 提取的字段自动带出。
|
|
49
|
-
|
|
50
|
-
## 继承基础 schema
|
|
51
|
-
|
|
52
|
-
```ts
|
|
53
|
-
buildQuery('OrderPageQuery', { ... })
|
|
54
|
-
.include({ from: '@pylonts/core', name: 'PageRequest' }); // 渲染 Type.Intersect([PageRequest, ...])
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## 关键语义
|
|
58
|
-
|
|
59
|
-
- **字段两层名**:`DtoField.name` 是接口字段名(DTO map key 反写);`field.name` 是数据库列名(表反写)。
|
|
60
|
-
- **可选性优先级**:DTO 层 `optional` 优先于字段层;query 方向所有字段强制可选。
|
|
61
|
-
- **HTTP 传 string**:bigint / decimal / date / time 在接口层渲染为 `Type.String()`,保证精度与序列化语义。
|
|
62
|
-
|
|
63
|
-
## 文件组织
|
|
64
|
-
|
|
65
|
-
- `dto_schema/{module}/*.dto.ts`:DTO 源文件,一个文件可导出多个 DtoMessage;`module` = `project.config.ts` 的 `apps.name`,另加 `common/` 放跨 app 共享 DTO。
|
|
66
|
-
- `gen
|
|
1
|
+
# 定义 DTO(四种方向)
|
|
2
|
+
|
|
3
|
+
DTO 描述接口出入参。方向决定可选性规则,由各方向工厂设置;**规则统一只在 `field.optional === undefined` 时生效**(作者已显式设置的跳过):
|
|
4
|
+
|
|
5
|
+
| 构建器 | 方向 | 可选性规则 |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| `buildInput` | input | 按 DB 列规则:主键 → 必填;可空(未写 `optional` 或 `optional: true`)/ 有默认 → 可选;`optional: false` 无默认 → 必填 |
|
|
8
|
+
| `buildOutput` | output | 不设置(保持字段构建器/作者设置) |
|
|
9
|
+
| `buildQuery` | query | 全部可选 |
|
|
10
|
+
| `buildPk` | pk | 主键字段必填,其他字段可选 |
|
|
11
|
+
|
|
12
|
+
## 从表提取字段
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { buildInput, buildQuery, dtoField, from } from '@pylonts/dsl';
|
|
16
|
+
|
|
17
|
+
// 输入:新增订单
|
|
18
|
+
buildInput('OrderAddRequest', { ...from(order, [order.columns.mer_id, order.columns.amount]) });
|
|
19
|
+
|
|
20
|
+
// 输出:订单行
|
|
21
|
+
buildOutput('OrderRow', from(order, [order.columns.id, order.columns.order_no]));
|
|
22
|
+
|
|
23
|
+
// 查询:分页 + 过滤(query 字段恒为可选,.op() 声明比较操作符)
|
|
24
|
+
buildQuery('OrderPageQuery', {
|
|
25
|
+
keyword: dtoField(stringField({ maxLength: 32 })).setOperator('like'),
|
|
26
|
+
...from(order, [order.columns.mer_id]),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// 主键:按 id 取详情
|
|
30
|
+
buildPk('OrderDetailRequest', from(order, [order.columns.id]));
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`from(table, fields)` 提取表字段包装为 DTO 字段,字段实例与表共享,`name/schema` 保持指向表。**DTO 字段名转 camelCase**(`mer_id` → `merId`),与 DB 列名(snake_case)分离。`from()` 本身不做任何可选性推断——推断在各方向工厂。
|
|
34
|
+
|
|
35
|
+
## 独立字段
|
|
36
|
+
|
|
37
|
+
不来自表的内联字段直接用 `dtoField(...)` 包装任意字段构建器,可加 `pattern`、`optional`、`operator`、`default`。
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
dtoField(stringField({ maxLength: 32 })).setOperator('like')
|
|
41
|
+
dtoField(intField()).setDefault(0) // TypeBox default 注解
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 默认值
|
|
45
|
+
|
|
46
|
+
- `setDefault(v)` 设 DTO 层默认值,渲染为 TypeBox `default:` 注解(`Type.String({ default: 'PENDING' })`、`Type.Enum(OrderStatus, { default: OrderStatus.PENDING })`)。
|
|
47
|
+
- 枚举默认值必须是该枚举的成员值(value),渲染时解析为成员引用;非成员值直接报错。
|
|
48
|
+
- 未显式设置时,fallback 到字段构建器的 `default`(DB 默认值,string),`from()` 提取的字段自动带出。
|
|
49
|
+
|
|
50
|
+
## 继承基础 schema
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
buildQuery('OrderPageQuery', { ... })
|
|
54
|
+
.include({ from: '@pylonts/core', name: 'PageRequest' }); // 渲染 Type.Intersect([PageRequest, ...])
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## 关键语义
|
|
58
|
+
|
|
59
|
+
- **字段两层名**:`DtoField.name` 是接口字段名(DTO map key 反写);`field.name` 是数据库列名(表反写)。
|
|
60
|
+
- **可选性优先级**:DTO 层 `optional` 优先于字段层;query 方向所有字段强制可选。
|
|
61
|
+
- **HTTP 传 string**:bigint / decimal / date / time 在接口层渲染为 `Type.String()`,保证精度与序列化语义。
|
|
62
|
+
|
|
63
|
+
## 文件组织
|
|
64
|
+
|
|
65
|
+
- `dto_schema/{module}/*.dto.ts`:DTO 源文件,一个文件可导出多个 DtoMessage;`module` = `project.config.ts` 的 `apps.name`,另加 `common/` 放跨 app 共享 DTO。
|
|
66
|
+
- `pylonts gen dto` 按 module 逐个扫描:`dto_schema/{module}/*.dto.ts` → 生成 `dto/{module}/{Name}.dto.ts`。
|
|
67
67
|
- 枚举字段引用 `enums/` 下生成的枚举源文件,DTO 文件通过 `../../enums/{JsName}.enum` 导入。
|
package/docs/table.md
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
|
|
19
19
|
通用扩展属性(构建器参数):`label`(中文标签)、`description`、`optional`、`readOnly`、`default`。
|
|
20
20
|
|
|
21
|
+
**`optional` 默认语义(MySQL 惯例)**:不写 `optional` 或写 `optional: true` → 列可空,DDL 不渲染 `NOT NULL`;写 `optional: false` → 列必填(`NOT NULL`)。业务上必填的列必须显式声明。
|
|
22
|
+
|
|
21
23
|
## 定义表
|
|
22
24
|
|
|
23
25
|
```ts
|
|
@@ -31,7 +33,7 @@ export const order = defineTable('order', {
|
|
|
31
33
|
columns: {
|
|
32
34
|
id,
|
|
33
35
|
order_no: stringField({ label: '订单号', maxLength: 32, optional: false }),
|
|
34
|
-
amount: decimalField({ precision: 18, scale: 2, label: '金额' }),
|
|
36
|
+
amount: decimalField({ precision: 18, scale: 2, label: '金额', optional: false }),
|
|
35
37
|
},
|
|
36
38
|
primaryKey: id,
|
|
37
39
|
});
|
|
@@ -127,7 +129,7 @@ export const audit = defineTable('audit', {
|
|
|
127
129
|
`foreign key bad: field must be named bd_id (phrase bd + id), got merchant_id`
|
|
128
130
|
- 关联表不需要 `phrase`。
|
|
129
131
|
|
|
130
|
-
> **外键是逻辑作用**:`foreignKeys` 用于定义期命名强校验与关系表达,**DDL 默认不渲染物理 FOREIGN KEY 约束**(`gen
|
|
132
|
+
> **外键是逻辑作用**:`foreignKeys` 用于定义期命名强校验与关系表达,**DDL 默认不渲染物理 FOREIGN KEY 约束**(`pylonts gen sql init` 不传 `generateForeignKeys`)。数据完整性由 Service/DAO 层保证;如需物理约束,调用 `buildCreateTableSql(schema, { generateForeignKeys: true })`。
|
|
131
133
|
|
|
132
134
|
## 生成 SQL
|
|
133
135
|
|