@pylonts/dsl 1.0.6 → 1.1.1
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/README.md +2 -1
- package/dist/asset.d.ts +48 -0
- package/dist/asset.js +31 -0
- package/dist/bases.d.ts +6 -2
- package/dist/bases.js +10 -6
- package/dist/check-inheritance.js +1 -4
- package/dist/curd.d.ts +60 -0
- package/dist/curd.js +31 -0
- package/dist/db-config.d.ts +8 -0
- package/dist/db-config.js +1 -0
- package/dist/db.d.ts +52 -0
- package/dist/db.js +91 -0
- package/dist/dictionary.d.ts +26 -5
- package/dist/dictionary.js +21 -8
- package/dist/dsl.d.ts +5 -49
- package/dist/dsl.js +12 -103
- package/dist/dto.d.ts +10 -7
- package/dist/dto.js +24 -33
- package/dist/enum-driver.d.ts +1 -1
- package/dist/enum-driver.js +1 -4
- package/dist/flow.d.ts +1 -1
- package/dist/flow.js +3 -8
- package/dist/import-base.d.ts +15 -0
- package/dist/import-base.js +1 -0
- package/dist/index.d.ts +21 -17
- package/dist/index.js +21 -33
- package/dist/mermaid-driver.d.ts +2 -2
- package/dist/mermaid-driver.js +2 -6
- package/dist/mock.d.ts +30 -0
- package/dist/mock.js +18 -0
- package/dist/mysql-driver.d.ts +1 -1
- package/dist/mysql-driver.js +20 -10
- package/dist/page-flow.d.ts +2 -2
- package/dist/page-flow.js +2 -6
- package/dist/page.d.ts +6 -6
- package/dist/page.js +3 -8
- package/dist/pattern.js +2 -6
- package/dist/patterns/retry.d.ts +1 -1
- package/dist/patterns/retry.js +2 -6
- package/dist/project.d.ts +18 -13
- package/dist/project.js +41 -6
- package/dist/prototype.d.ts +1 -1
- package/dist/prototype.js +1 -4
- package/dist/typebox-driver.d.ts +3 -3
- package/dist/typebox-driver.js +24 -26
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +5 -4
- package/docs/curd.md +111 -0
- package/docs/dictionary.md +42 -31
- package/docs/driver.md +42 -42
- package/docs/dto.md +66 -66
- package/docs/enum.md +24 -24
- package/docs/project.md +2 -2
- package/docs/table.md +50 -15
- package/package.json +6 -4
- package/src/asset.ts +63 -0
- package/src/bases.ts +12 -2
- package/src/curd.ts +92 -0
- package/src/db-config.ts +8 -0
- package/src/db.ts +142 -0
- package/src/dictionary.ts +45 -19
- package/src/dsl.ts +182 -281
- package/src/dto.ts +247 -234
- package/src/enum-driver.ts +1 -1
- package/src/flow.ts +1 -1
- package/src/import-base.ts +15 -0
- package/src/index.ts +21 -17
- package/src/mermaid-driver.ts +3 -3
- package/src/mock.ts +45 -0
- package/src/mysql-driver.ts +19 -6
- package/src/page-flow.ts +3 -3
- package/src/page.ts +7 -7
- package/src/patterns/retry.ts +1 -1
- package/src/project.ts +90 -54
- package/src/prototype.ts +1 -1
- package/src/typebox-driver.ts +192 -187
- package/src/utils.ts +5 -0
- package/src/check-inheritance.ts +0 -86
package/dist/page-flow.js
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.pageEdge = pageEdge;
|
|
4
|
-
exports.definePageFlow = definePageFlow;
|
|
5
|
-
function pageEdge(start, end, when, description) {
|
|
1
|
+
export function pageEdge(start, end, when, description) {
|
|
6
2
|
// Auto name for uniformity with SchemaBase; `when` stays the branch marker.
|
|
7
3
|
return { name: `${start.name}->${end.name}`, start, end, when, description };
|
|
8
4
|
}
|
|
9
|
-
function definePageFlow(name, schema) {
|
|
5
|
+
export function definePageFlow(name, schema) {
|
|
10
6
|
const seen = new Set();
|
|
11
7
|
const pages = [];
|
|
12
8
|
for (const e of schema.edges) {
|
package/dist/page.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { SchemaBase } from './dsl';
|
|
2
|
-
import {
|
|
1
|
+
import { SchemaBase } from './dsl.js';
|
|
2
|
+
import { FrontAppSchema } from './project.js';
|
|
3
3
|
/** Standalone page definition. A page is a shared value object: it lists
|
|
4
4
|
* the actions a user can perform, and belongs to exactly one frontend app. */
|
|
5
5
|
export interface PageSchema extends SchemaBase {
|
|
6
6
|
/** The frontend app this page belongs to (shared instance from project.config). */
|
|
7
|
-
app:
|
|
7
|
+
app: FrontAppSchema;
|
|
8
8
|
/** Actions a user can perform on this page (e.g. submit, approve, reject). */
|
|
9
9
|
actions: ActionSchema[];
|
|
10
10
|
}
|
|
@@ -15,12 +15,12 @@ export declare function defineAction(name: string, description?: string): Action
|
|
|
15
15
|
export declare function definePage(schema: {
|
|
16
16
|
name: string;
|
|
17
17
|
description?: string;
|
|
18
|
-
app:
|
|
18
|
+
app: FrontAppSchema;
|
|
19
19
|
actions: ActionSchema[];
|
|
20
20
|
}): PageSchema;
|
|
21
21
|
/** A page node in a page-driven flow: every node is a page, and a page belongs to an app. */
|
|
22
22
|
export interface Page extends SchemaBase {
|
|
23
23
|
/** The frontend app this page belongs to (shared instance from project.config). */
|
|
24
|
-
app:
|
|
24
|
+
app: FrontAppSchema;
|
|
25
25
|
}
|
|
26
|
-
export declare function page(app:
|
|
26
|
+
export declare function page(app: FrontAppSchema, name: string, description?: string): Page;
|
package/dist/page.js
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.defineAction = defineAction;
|
|
4
|
-
exports.definePage = definePage;
|
|
5
|
-
exports.page = page;
|
|
6
|
-
function defineAction(name, description) {
|
|
1
|
+
export function defineAction(name, description) {
|
|
7
2
|
return { name, description };
|
|
8
3
|
}
|
|
9
|
-
function definePage(schema) {
|
|
4
|
+
export function definePage(schema) {
|
|
10
5
|
return { ...schema };
|
|
11
6
|
}
|
|
12
|
-
function page(app, name, description) {
|
|
7
|
+
export function page(app, name, description) {
|
|
13
8
|
return { name, app, description };
|
|
14
9
|
}
|
package/dist/pattern.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
// Pattern core: minimal definitions to bootstrap the Flow × Pattern DSL.
|
|
3
2
|
// A Pattern is a reusable solution ("how to guarantee success") declared as
|
|
4
3
|
// pure data. Concrete usage fills its params and action injection points.
|
|
5
|
-
|
|
6
|
-
exports.definePattern = definePattern;
|
|
7
|
-
exports.ref = ref;
|
|
8
|
-
function definePattern(def) {
|
|
4
|
+
export function definePattern(def) {
|
|
9
5
|
return def;
|
|
10
6
|
}
|
|
11
|
-
function ref(name, args) {
|
|
7
|
+
export function ref(name, args) {
|
|
12
8
|
return { ref: name, args };
|
|
13
9
|
}
|
package/dist/patterns/retry.d.ts
CHANGED
package/dist/patterns/retry.js
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.retryPattern = void 0;
|
|
4
|
-
exports.renderRetry = renderRetry;
|
|
5
1
|
// Retry pattern: blind mechanical retry for read-only operations.
|
|
6
2
|
// Valid because a read-only action is idempotent by nature: clicking a query
|
|
7
3
|
// button any number of times never changes the result, so retrying the same
|
|
8
4
|
// call is always safe. No idempotency key, no query-and-resume, no compensate.
|
|
9
|
-
|
|
5
|
+
export const retryPattern = {
|
|
10
6
|
name: 'retry',
|
|
11
7
|
params: {
|
|
12
8
|
max: { type: 'int', min: 1, default: 3 },
|
|
13
9
|
backoffMs: { type: 'int', min: 0, default: 0 },
|
|
14
10
|
},
|
|
15
11
|
};
|
|
16
|
-
function renderRetry(args) {
|
|
12
|
+
export function renderRetry(args) {
|
|
17
13
|
if (args.max !== undefined && args.max < 1)
|
|
18
14
|
throw new Error('retry: max must be >= 1');
|
|
19
15
|
if (!args.action.call)
|
package/dist/project.d.ts
CHANGED
|
@@ -1,41 +1,46 @@
|
|
|
1
|
-
import { SchemaBase } from './dsl';
|
|
1
|
+
import { SchemaBase } from './dsl.js';
|
|
2
2
|
/** Frontend form factor. Closed enum, extend when new form factors appear. */
|
|
3
3
|
export type FrontType = 'admin' | 'wxmini';
|
|
4
4
|
/** A frontend application (e.g. admin console, wechat mini program). */
|
|
5
|
-
export interface
|
|
5
|
+
export interface FrontAppSchema extends SchemaBase {
|
|
6
6
|
type: FrontType;
|
|
7
7
|
/** Source directory relative to project root, e.g. 'web-admin/'. */
|
|
8
8
|
dir: string;
|
|
9
9
|
}
|
|
10
|
-
/** A backend API service. apps references shared
|
|
11
|
-
export interface
|
|
10
|
+
/** A backend API service. apps references shared FrontAppSchema instances. */
|
|
11
|
+
export interface ProjectApiSchema extends SchemaBase {
|
|
12
12
|
/** Source directory relative to project root, e.g. 'api/'. */
|
|
13
13
|
dir: string;
|
|
14
14
|
/** Frontends this API serves. Direct instance references (see defineProject). */
|
|
15
|
-
apps:
|
|
15
|
+
apps: FrontAppSchema[];
|
|
16
16
|
/** API base URL prefix shared by all apps it serves, e.g. '/mall'. '' = no prefix. */
|
|
17
17
|
contextPath?: string;
|
|
18
|
+
/** API service base URL for node clients, e.g. 'http://127.0.0.1:3000'. */
|
|
19
|
+
baseUrl?: string;
|
|
18
20
|
}
|
|
19
21
|
/** A third-party system (e.g. wechat pay, unionpay). Owns its own
|
|
20
22
|
* implementation dir and contract (controller_types), just like an API,
|
|
21
23
|
* but is not part of this repo's served surface. */
|
|
22
|
-
export interface
|
|
24
|
+
export interface ThirdApiSchema extends SchemaBase {
|
|
23
25
|
/** Source directory relative to project root, e.g. 'wechat/'. */
|
|
24
26
|
dir: string;
|
|
25
27
|
}
|
|
26
28
|
export interface ProjectSchema extends SchemaBase {
|
|
27
|
-
apps:
|
|
28
|
-
apis:
|
|
29
|
-
thirdApis:
|
|
29
|
+
apps: FrontAppSchema[];
|
|
30
|
+
apis: ProjectApiSchema[];
|
|
31
|
+
thirdApis: ThirdApiSchema[];
|
|
30
32
|
}
|
|
31
33
|
/**
|
|
32
|
-
* Defines the project topology.
|
|
34
|
+
* Defines the project topology. FrontAppSchema instances are shared value objects:
|
|
33
35
|
* api.apps references the same instances from project.apps, so an app served
|
|
34
36
|
* by multiple APIs is defined once and referenced many times.
|
|
37
|
+
*
|
|
38
|
+
* Runtime-validates app type whitelist, unique names and api.apps reference
|
|
39
|
+
* integrity (same style as defineTable/defineCurd).
|
|
35
40
|
*/
|
|
36
41
|
export declare function defineProject(name: string, schema: {
|
|
37
42
|
description?: string;
|
|
38
|
-
apps:
|
|
39
|
-
apis:
|
|
40
|
-
thirdApis?:
|
|
43
|
+
apps: FrontAppSchema[];
|
|
44
|
+
apis: ProjectApiSchema[];
|
|
45
|
+
thirdApis?: ThirdApiSchema[];
|
|
41
46
|
}): ProjectSchema;
|
package/dist/project.js
CHANGED
|
@@ -1,11 +1,46 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.defineProject = defineProject;
|
|
4
1
|
/**
|
|
5
|
-
* Defines the project topology.
|
|
2
|
+
* Defines the project topology. FrontAppSchema instances are shared value objects:
|
|
6
3
|
* api.apps references the same instances from project.apps, so an app served
|
|
7
4
|
* by multiple APIs is defined once and referenced many times.
|
|
5
|
+
*
|
|
6
|
+
* Runtime-validates app type whitelist, unique names and api.apps reference
|
|
7
|
+
* integrity (same style as defineTable/defineCurd).
|
|
8
8
|
*/
|
|
9
|
-
function defineProject(name, schema) {
|
|
10
|
-
|
|
9
|
+
export function defineProject(name, schema) {
|
|
10
|
+
const project = { name, ...schema, thirdApis: schema.thirdApis ?? [] };
|
|
11
|
+
const appNames = new Set();
|
|
12
|
+
for (const app of project.apps) {
|
|
13
|
+
if (!app.name)
|
|
14
|
+
throw new Error(`project ${name}: app name is required`);
|
|
15
|
+
if (appNames.has(app.name))
|
|
16
|
+
throw new Error(`project ${name}: duplicate app name '${app.name}'`);
|
|
17
|
+
appNames.add(app.name);
|
|
18
|
+
if (app.type !== 'admin' && app.type !== 'wxmini') {
|
|
19
|
+
throw new Error(`project ${name}: app '${app.name}' must be type 'admin' or 'wxmini' (got '${app.type}')`);
|
|
20
|
+
}
|
|
21
|
+
if (!app.dir)
|
|
22
|
+
throw new Error(`project ${name}: app '${app.name}' dir is required`);
|
|
23
|
+
}
|
|
24
|
+
const apiNames = new Set();
|
|
25
|
+
for (const api of project.apis) {
|
|
26
|
+
if (!api.name)
|
|
27
|
+
throw new Error(`project ${name}: api name is required`);
|
|
28
|
+
if (apiNames.has(api.name))
|
|
29
|
+
throw new Error(`project ${name}: duplicate api name '${api.name}'`);
|
|
30
|
+
apiNames.add(api.name);
|
|
31
|
+
if (!api.dir)
|
|
32
|
+
throw new Error(`project ${name}: api '${api.name}' dir is required`);
|
|
33
|
+
for (const ref of api.apps) {
|
|
34
|
+
if (!project.apps.includes(ref)) {
|
|
35
|
+
throw new Error(`project ${name}: api '${api.name}' references app '${ref.name}' that is not a shared instance in project.apps (define once and reference it)`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
for (const third of project.thirdApis) {
|
|
40
|
+
if (!third.name)
|
|
41
|
+
throw new Error(`project ${name}: thirdApi name is required`);
|
|
42
|
+
if (!third.dir)
|
|
43
|
+
throw new Error(`project ${name}: thirdApi '${third.name}' dir is required`);
|
|
44
|
+
}
|
|
45
|
+
return project;
|
|
11
46
|
}
|
package/dist/prototype.d.ts
CHANGED
package/dist/prototype.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.definePrototype = definePrototype;
|
|
4
1
|
/**
|
|
5
2
|
* Defines a page prototype. The field keys become the names referenced by
|
|
6
3
|
* later DTO/table definitions; here they only carry label/description.
|
|
7
4
|
*/
|
|
8
|
-
function definePrototype(name, schema) {
|
|
5
|
+
export function definePrototype(name, schema) {
|
|
9
6
|
return { name, ...schema };
|
|
10
7
|
}
|
package/dist/typebox-driver.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { DtoMessage,
|
|
2
|
-
export type EnumResolver = (enumName: string) =>
|
|
1
|
+
import { DtoMessage, ImportBase } from './dto.js';
|
|
2
|
+
export type EnumResolver = (enumName: string) => ImportBase | undefined;
|
|
3
3
|
/** Collect all imports needed to render a DTO: include() bases + enum references. */
|
|
4
|
-
export declare function collectDtoImports(schema: DtoMessage, resolver: EnumResolver | undefined, out: Map<string,
|
|
4
|
+
export declare function collectDtoImports(schema: DtoMessage, resolver: EnumResolver | undefined, out: Map<string, ImportBase>): void;
|
|
5
5
|
/** Render one DTO export (const + type) — no file header, for file-level generation. */
|
|
6
6
|
export declare function renderDtoExport(schema: DtoMessage, resolver: EnumResolver | undefined): string;
|
|
7
7
|
/** Render the Static type export for a DTO. */
|
package/dist/typebox-driver.js
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.collectDtoImports = collectDtoImports;
|
|
4
|
-
exports.renderDtoExport = renderDtoExport;
|
|
5
|
-
exports.renderDtoTypeExport = renderDtoTypeExport;
|
|
6
|
-
exports.renderDtoMessage = renderDtoMessage;
|
|
7
|
-
const dto_1 = require("./dto");
|
|
8
1
|
function renderString(s) {
|
|
9
2
|
return `'${s.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
|
|
10
3
|
}
|
|
@@ -83,31 +76,36 @@ function renderField(f, indent, resolver) {
|
|
|
83
76
|
return f.isOptional() ? `Type.Optional(${base})` : base;
|
|
84
77
|
}
|
|
85
78
|
function renderValue(f, indent, resolver) {
|
|
86
|
-
if (f
|
|
87
|
-
const items = f.items
|
|
79
|
+
if (f.field.type === 'array') {
|
|
80
|
+
const items = f.field.items;
|
|
88
81
|
// Referenced DTO element — render by name (same-file export), not expanded.
|
|
89
|
-
if (items
|
|
82
|
+
if (isDtoMessage(items))
|
|
90
83
|
return `Type.Array(${items.name})`;
|
|
91
84
|
return `Type.Array(${renderField(items, indent + 1, resolver)})`;
|
|
92
85
|
}
|
|
93
|
-
if (f
|
|
94
|
-
return renderObject(f.properties
|
|
86
|
+
if (f.field.type === 'object') {
|
|
87
|
+
return renderObject(f.field.properties, indent + 1, resolver);
|
|
95
88
|
}
|
|
96
89
|
// DtoField only wraps a database Field; array/object defs live in the subclasses.
|
|
97
|
-
// DTO-level
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
90
|
+
// Only DTO-level defaults (setDefault) are emitted as TypeBox default
|
|
91
|
+
// annotations; DB field defaults are not carried into the API contract.
|
|
92
|
+
return renderBasic(f.field, f.pattern, f.default, resolver);
|
|
93
|
+
}
|
|
94
|
+
/** Structural check — DtoMessage instances may come from a different module copy, so instanceof is unreliable. */
|
|
95
|
+
function isDtoMessage(v) {
|
|
96
|
+
if (typeof v !== 'object' || v === null)
|
|
97
|
+
return false;
|
|
98
|
+
return v.type === 'dto';
|
|
101
99
|
}
|
|
102
100
|
function collectEnumImports(f, resolver, out) {
|
|
103
|
-
if (f
|
|
104
|
-
const items = f.items
|
|
105
|
-
if (!(items
|
|
101
|
+
if (f.field.type === 'array') {
|
|
102
|
+
const items = f.field.items;
|
|
103
|
+
if (!isDtoMessage(items))
|
|
106
104
|
collectEnumImports(items, resolver, out);
|
|
107
105
|
return;
|
|
108
106
|
}
|
|
109
|
-
if (f
|
|
110
|
-
for (const child of Object.values(f.properties
|
|
107
|
+
if (f.field.type === 'object') {
|
|
108
|
+
for (const child of Object.values(f.field.properties))
|
|
111
109
|
collectEnumImports(child, resolver, out);
|
|
112
110
|
return;
|
|
113
111
|
}
|
|
@@ -119,14 +117,14 @@ function collectEnumImports(f, resolver, out) {
|
|
|
119
117
|
}
|
|
120
118
|
}
|
|
121
119
|
/** Collect all imports needed to render a DTO: include() bases + enum references. */
|
|
122
|
-
function collectDtoImports(schema, resolver, out) {
|
|
120
|
+
export function collectDtoImports(schema, resolver, out) {
|
|
123
121
|
for (const base of schema.bases ?? [])
|
|
124
122
|
out.set(`${base.from}#${base.name}`, base);
|
|
125
123
|
for (const f of Object.values(schema.fields))
|
|
126
124
|
collectEnumImports(f, resolver, out);
|
|
127
125
|
}
|
|
128
126
|
/** Render one DTO export (const + type) — no file header, for file-level generation. */
|
|
129
|
-
function renderDtoExport(schema, resolver) {
|
|
127
|
+
export function renderDtoExport(schema, resolver) {
|
|
130
128
|
const object = renderObject(schema.fields, 1, resolver);
|
|
131
129
|
const bases = schema.bases ?? [];
|
|
132
130
|
const body = bases.length > 0
|
|
@@ -135,10 +133,10 @@ function renderDtoExport(schema, resolver) {
|
|
|
135
133
|
return `export const ${schema.name} = ${body};`;
|
|
136
134
|
}
|
|
137
135
|
/** Render the Static type export for a DTO. */
|
|
138
|
-
function renderDtoTypeExport(name) {
|
|
136
|
+
export function renderDtoTypeExport(name) {
|
|
139
137
|
return `export type ${name} = Static<typeof ${name}>;`;
|
|
140
138
|
}
|
|
141
|
-
function renderDtoMessage(schema, options = {}) {
|
|
139
|
+
export function renderDtoMessage(schema, options = {}) {
|
|
142
140
|
const { resolver, source } = options;
|
|
143
141
|
const imports = new Map();
|
|
144
142
|
collectDtoImports(schema, resolver, imports);
|
|
@@ -146,7 +144,7 @@ function renderDtoMessage(schema, options = {}) {
|
|
|
146
144
|
'// AUTO-GENERATED by typebox-driver — DO NOT EDIT',
|
|
147
145
|
...(source !== undefined ? [`// Source: ${source}`] : []),
|
|
148
146
|
"import { Type, Static } from '@sinclair/typebox';",
|
|
149
|
-
...[...imports.values()].map((r) => `import { ${r.name} } from '${r.from}';`),
|
|
147
|
+
...[...imports.values()].map((r) => `import${r.type ? ' type' : ''} { ${r.name} } from '${r.from}';`),
|
|
150
148
|
];
|
|
151
149
|
return [
|
|
152
150
|
...header,
|
package/dist/utils.d.ts
CHANGED
package/dist/utils.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
// ── naming conversions ──
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.toCamelCase = toCamelCase;
|
|
5
2
|
/** snake_case → camelCase: mer_id → merId; names without underscores are unchanged */
|
|
6
|
-
function toCamelCase(name) {
|
|
3
|
+
export function toCamelCase(name) {
|
|
7
4
|
return name.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
|
|
8
5
|
}
|
|
6
|
+
/** snake_case → PascalCase: mer_id → MerId */
|
|
7
|
+
export function toPascalCase(snake) {
|
|
8
|
+
return snake.replace(/(^|_)([a-z])/g, (_m, _p, c) => c.toUpperCase());
|
|
9
|
+
}
|
package/docs/curd.md
ADDED
|
@@ -0,0 +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-cli 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
|
+
| DTO 引用(`request` / `fields` / `DtoFields`) | 去掉(DTO 由生成器推导,页面只依赖 table) |
|
package/docs/dictionary.md
CHANGED
|
@@ -1,31 +1,42 @@
|
|
|
1
|
-
# 短语词典 (Dictionary)
|
|
2
|
-
|
|
3
|
-
词典是与团队达成共识的基础知识库:**某词代表什么**(语义/定义层面),不是物理形式。短语定了,字段命名、外键命名就都有依据——全项目只说同一种话。
|
|
4
|
-
|
|
5
|
-
- 是基础知识库,很少变更。
|
|
6
|
-
- **能引用就引用**:魔法字符串只在首次出现时使用,之后一律引用词典条目。
|
|
7
|
-
|
|
8
|
-
## 规范位置:schema/_dictionary.ts
|
|
9
|
-
|
|
10
|
-
**所有短语统一定义在 `schema/_dictionary.ts`**,一个文件一处定义;`*.table.ts` 从该文件 import 短语,禁止在表文件里内联定义短语。
|
|
11
|
-
|
|
12
|
-
##
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
1
|
+
# 短语词典 (Dictionary)
|
|
2
|
+
|
|
3
|
+
词典是与团队达成共识的基础知识库:**某词代表什么**(语义/定义层面),不是物理形式。短语定了,字段命名、外键命名就都有依据——全项目只说同一种话。
|
|
4
|
+
|
|
5
|
+
- 是基础知识库,很少变更。
|
|
6
|
+
- **能引用就引用**:魔法字符串只在首次出现时使用,之后一律引用词典条目。
|
|
7
|
+
|
|
8
|
+
## 规范位置:schema/_dictionary.ts
|
|
9
|
+
|
|
10
|
+
**所有短语统一定义在 `schema/_dictionary.ts`**,一个文件一处定义;`*.table.ts` 从该文件 import 短语,禁止在表文件里内联定义短语。
|
|
11
|
+
|
|
12
|
+
## 两种短语,两种命名规则
|
|
13
|
+
|
|
14
|
+
短语分两类,参与不同的字段命名校验:
|
|
15
|
+
|
|
16
|
+
| 类型 | 定义函数 | 含义 | 命名规则 | 例子 |
|
|
17
|
+
|---|---|---|---|---|
|
|
18
|
+
| `entity` | `defineEntityPhrase` | 实体缩写 | 字段名**首段**(实体领先) | `mer_id`、`bd_rate` |
|
|
19
|
+
| `business` | `defineBusinessPhrase` | 实体的属性 | 字段名**末段**(属性收尾) | `bd_rate`、`acquiring_rate` |
|
|
20
|
+
|
|
21
|
+
## 口径:name 即短语
|
|
22
|
+
|
|
23
|
+
两个定义函数返回的条目**就是短语本身**,不是"全名 + 缩写"两套——`name` 即短语词干(列名前缀),`label`/`description` 解释语义。**变量名与 name 一致**(小写)。短语要短(mer / bd / amt 三字母左右),**不要用长语**:引用商户实体的字段叫 `mer_id`,不叫 `merchant_id`。
|
|
24
|
+
|
|
25
|
+
## 定义
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
// schema/_dictionary.ts
|
|
29
|
+
import { defineEntityPhrase, defineBusinessPhrase } from '@pylonts/dsl';
|
|
30
|
+
|
|
31
|
+
const bd = defineEntityPhrase({ name: 'bd', label: 'BD推广员', description: '线下拓展商户、辅助入驻的推广人员' });
|
|
32
|
+
const mer = defineEntityPhrase({ name: 'mer', label: '商户', description: '入驻平台的商户' });
|
|
33
|
+
const amt = defineBusinessPhrase({ name: 'amt', label: '金额', description: '交易金额,单位分' });
|
|
34
|
+
const rate = defineBusinessPhrase({ name: 'rate', label: '费率', description: '结算费率' });
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 使用
|
|
38
|
+
|
|
39
|
+
- **表链接实体**:`TableSchema.phrase` 引用**实体短语**条目,声明本表归属哪个实体(见 [table.md](./table.md) 的外键检查链)。关联表等多实体场景不需要。
|
|
40
|
+
- 业务短语供字段命名/文档使用,跨团队对齐。
|
|
41
|
+
- 未收录短语的实体保留全名作词干(不臆造缩写),评审时再裁决收录。
|
|
42
|
+
- 字段命名校验按类型区分:实体短语必须首段、业务短语必须末段(见 [field-check.md](../../lint/docs/field-check.md))。
|