@pylonts/dsl 1.1.4 → 1.1.6
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/controller.d.ts +27 -0
- package/dist/controller.js +11 -0
- package/dist/convert.d.ts +18 -6
- package/dist/convert.js +12 -2
- package/dist/curd.d.ts +0 -2
- package/dist/curd.js +7 -0
- package/dist/dao.d.ts +111 -2
- package/dist/dao.js +28 -2
- package/dist/db.js +3 -0
- package/dist/dsl.d.ts +24 -1
- package/dist/dsl.js +16 -0
- package/dist/dto.d.ts +6 -2
- package/dist/dto.js +20 -9
- package/dist/endpoint.d.ts +15 -0
- package/dist/endpoint.js +3 -0
- package/dist/exception.d.ts +14 -0
- package/dist/exception.js +9 -0
- package/dist/field-rule.d.ts +20 -0
- package/dist/field-rule.js +19 -0
- package/dist/flow.d.ts +24 -2
- package/dist/flow.js +16 -4
- package/dist/index.d.ts +7 -0
- package/dist/index.js +9 -0
- package/dist/mermaid-driver.js +32 -3
- package/dist/method.d.ts +11 -0
- package/dist/method.js +3 -0
- package/dist/mysql-driver.js +8 -1
- package/dist/provider.d.ts +6 -11
- package/dist/provider.js +2 -2
- package/dist/service.d.ts +16 -6
- package/dist/service.js +13 -2
- package/dist/third-service.d.ts +75 -0
- package/dist/third-service.js +96 -0
- package/dist/typebox-driver.d.ts +6 -0
- package/dist/typebox-driver.js +76 -14
- package/dist/utils.d.ts +25 -10
- package/dist/utils.js +28 -11
- package/docs/curd.md +110 -110
- package/docs/dto.md +9 -2
- package/docs/table.md +135 -135
- package/docs/third-service.md +122 -0
- package/package.json +4 -1
- package/src/action.ts +10 -10
- package/src/asset.ts +62 -62
- package/src/bases.ts +29 -29
- package/src/component.ts +21 -21
- package/src/controller.ts +40 -0
- package/src/convert.ts +34 -7
- package/src/curd.ts +7 -2
- package/src/dao.ts +162 -3
- package/src/db.ts +5 -0
- package/src/dsl.ts +49 -1
- package/src/dto.ts +25 -9
- package/src/endpoint.ts +18 -0
- package/src/event.ts +12 -12
- package/src/exception.ts +28 -0
- package/src/field-rule.ts +47 -0
- package/src/flow.ts +143 -103
- package/src/index.ts +11 -1
- package/src/mermaid-driver.ts +31 -3
- package/src/method.ts +20 -0
- package/src/mock.ts +12 -12
- package/src/mysql-driver.ts +8 -2
- package/src/navigation.ts +28 -28
- package/src/page-def.ts +79 -79
- package/src/page-flow.ts +153 -153
- package/src/page.ts +76 -76
- package/src/popup.ts +25 -25
- package/src/project.ts +97 -97
- package/src/provider.ts +7 -12
- package/src/ref.ts +18 -18
- package/src/route.ts +11 -11
- package/src/service.ts +28 -6
- package/src/third-service.ts +186 -0
- package/src/typebox-driver.ts +264 -192
- package/src/utils.ts +54 -17
- package/dist/check-inheritance.d.ts +0 -9
- package/dist/check-inheritance.js +0 -58
package/src/popup.ts
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import type { ActionSchema } from './action.js';
|
|
2
|
-
import type { RefSchema } from './ref.js';
|
|
3
|
-
|
|
4
|
-
/** Display a toast notification. React: toast/message UI. Mini-program: wx.showToast. */
|
|
5
|
-
export interface ToastAction extends ActionSchema {
|
|
6
|
-
type: 'toast';
|
|
7
|
-
message: string | RefSchema;
|
|
8
|
-
icon?: 'success' | 'error' | 'loading' | 'none';
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/** Display an alert dialog. React: modal. Mini-program: wx.showModal. */
|
|
12
|
-
export interface AlertAction extends ActionSchema {
|
|
13
|
-
type: 'alert';
|
|
14
|
-
title: string | RefSchema;
|
|
15
|
-
content: string | RefSchema;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const popup = {
|
|
19
|
-
toast(message: string | RefSchema, icon?: 'success' | 'error' | 'loading' | 'none'): ToastAction {
|
|
20
|
-
return { name: 'toast', type: 'toast', message, icon };
|
|
21
|
-
},
|
|
22
|
-
alert(title: string | RefSchema, content: string | RefSchema): AlertAction {
|
|
23
|
-
return { name: 'alert', type: 'alert', title, content };
|
|
24
|
-
},
|
|
25
|
-
};
|
|
1
|
+
import type { ActionSchema } from './action.js';
|
|
2
|
+
import type { RefSchema } from './ref.js';
|
|
3
|
+
|
|
4
|
+
/** Display a toast notification. React: toast/message UI. Mini-program: wx.showToast. */
|
|
5
|
+
export interface ToastAction extends ActionSchema {
|
|
6
|
+
type: 'toast';
|
|
7
|
+
message: string | RefSchema;
|
|
8
|
+
icon?: 'success' | 'error' | 'loading' | 'none';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Display an alert dialog. React: modal. Mini-program: wx.showModal. */
|
|
12
|
+
export interface AlertAction extends ActionSchema {
|
|
13
|
+
type: 'alert';
|
|
14
|
+
title: string | RefSchema;
|
|
15
|
+
content: string | RefSchema;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const popup = {
|
|
19
|
+
toast(message: string | RefSchema, icon?: 'success' | 'error' | 'loading' | 'none'): ToastAction {
|
|
20
|
+
return { name: 'toast', type: 'toast', message, icon };
|
|
21
|
+
},
|
|
22
|
+
alert(title: string | RefSchema, content: string | RefSchema): AlertAction {
|
|
23
|
+
return { name: 'alert', type: 'alert', title, content };
|
|
24
|
+
},
|
|
25
|
+
};
|
package/src/project.ts
CHANGED
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
import { SchemaBase } from './dsl.js';
|
|
2
|
-
import type { TableSchema } from './db.js';
|
|
3
|
-
|
|
4
|
-
// Project topology definitions: describe the applications (frontends) and
|
|
5
|
-
// backend APIs of a repository, and which frontends each API serves.
|
|
6
|
-
|
|
7
|
-
/** Frontend form factor. Closed enum, extend when new form factors appear. */
|
|
8
|
-
export type FrontType = 'admin' | 'wxmini';
|
|
9
|
-
|
|
10
|
-
/** A frontend application (e.g. admin console, wechat mini program). */
|
|
11
|
-
export interface FrontAppSchema extends SchemaBase {
|
|
12
|
-
type: FrontType;
|
|
13
|
-
/** Source directory relative to project root, e.g. 'web-admin/'. */
|
|
14
|
-
dir: string;
|
|
15
|
-
/**
|
|
16
|
-
* Tenant table for this app. When set, all tables with a foreign key
|
|
17
|
-
* pointing to this table get automatic tenant scoping: the tenant PK
|
|
18
|
-
* value is injected from `user.id` into all curd operations.
|
|
19
|
-
*/
|
|
20
|
-
tenant?: TableSchema;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** A backend API service. apps references shared FrontAppSchema instances. */
|
|
24
|
-
export interface ProjectApiSchema extends SchemaBase {
|
|
25
|
-
/** Source directory relative to project root, e.g. 'api/'. */
|
|
26
|
-
dir: string;
|
|
27
|
-
/** Frontends this API serves. Direct instance references (see defineProject). */
|
|
28
|
-
apps: FrontAppSchema[];
|
|
29
|
-
/** API base URL prefix shared by all apps it serves, e.g. '/mall'. '' = no prefix. */
|
|
30
|
-
contextPath?: string;
|
|
31
|
-
/** API service base URL for node clients, e.g. 'http://127.0.0.1:3000'. */
|
|
32
|
-
baseUrl?: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** A third-party system (e.g. wechat pay, unionpay). Owns its own
|
|
36
|
-
* implementation dir and contract (controller_types), just like an API,
|
|
37
|
-
* but is not part of this repo's served surface. */
|
|
38
|
-
export interface ThirdApiSchema extends SchemaBase {
|
|
39
|
-
/** Source directory relative to project root, e.g. 'wechat/'. */
|
|
40
|
-
dir: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface ProjectSchema extends SchemaBase {
|
|
44
|
-
apps: FrontAppSchema[];
|
|
45
|
-
apis: ProjectApiSchema[];
|
|
46
|
-
thirdApis: ThirdApiSchema[];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Defines the project topology. FrontAppSchema instances are shared value objects:
|
|
51
|
-
* api.apps references the same instances from project.apps, so an app served
|
|
52
|
-
* by multiple APIs is defined once and referenced many times.
|
|
53
|
-
*
|
|
54
|
-
* Runtime-validates app type whitelist, unique names and api.apps reference
|
|
55
|
-
* integrity (same style as defineTable/defineCurd).
|
|
56
|
-
*/
|
|
57
|
-
export function defineProject(
|
|
58
|
-
name: string,
|
|
59
|
-
schema: {
|
|
60
|
-
description?: string;
|
|
61
|
-
apps: FrontAppSchema[];
|
|
62
|
-
apis: ProjectApiSchema[];
|
|
63
|
-
thirdApis?: ThirdApiSchema[];
|
|
64
|
-
},
|
|
65
|
-
): ProjectSchema {
|
|
66
|
-
const project: ProjectSchema = { name, ...schema, thirdApis: schema.thirdApis ?? [] };
|
|
67
|
-
|
|
68
|
-
const appNames = new Set<string>();
|
|
69
|
-
for (const app of project.apps) {
|
|
70
|
-
if (!app.name) throw new Error(`project ${name}: app name is required`);
|
|
71
|
-
if (appNames.has(app.name)) throw new Error(`project ${name}: duplicate app name '${app.name}'`);
|
|
72
|
-
appNames.add(app.name);
|
|
73
|
-
if (app.type !== 'admin' && app.type !== 'wxmini') {
|
|
74
|
-
throw new Error(`project ${name}: app '${app.name}' must be type 'admin' or 'wxmini' (got '${app.type}')`);
|
|
75
|
-
}
|
|
76
|
-
if (!app.dir) throw new Error(`project ${name}: app '${app.name}' dir is required`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const apiNames = new Set<string>();
|
|
80
|
-
for (const api of project.apis) {
|
|
81
|
-
if (!api.name) throw new Error(`project ${name}: api name is required`);
|
|
82
|
-
if (apiNames.has(api.name)) throw new Error(`project ${name}: duplicate api name '${api.name}'`);
|
|
83
|
-
apiNames.add(api.name);
|
|
84
|
-
if (!api.dir) throw new Error(`project ${name}: api '${api.name}' dir is required`);
|
|
85
|
-
for (const ref of api.apps) {
|
|
86
|
-
if (!project.apps.includes(ref)) {
|
|
87
|
-
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)`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
for (const third of project.thirdApis) {
|
|
93
|
-
if (!third.name) throw new Error(`project ${name}: thirdApi name is required`);
|
|
94
|
-
if (!third.dir) throw new Error(`project ${name}: thirdApi '${third.name}' dir is required`);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return project;
|
|
1
|
+
import { SchemaBase } from './dsl.js';
|
|
2
|
+
import type { TableSchema } from './db.js';
|
|
3
|
+
|
|
4
|
+
// Project topology definitions: describe the applications (frontends) and
|
|
5
|
+
// backend APIs of a repository, and which frontends each API serves.
|
|
6
|
+
|
|
7
|
+
/** Frontend form factor. Closed enum, extend when new form factors appear. */
|
|
8
|
+
export type FrontType = 'admin' | 'wxmini';
|
|
9
|
+
|
|
10
|
+
/** A frontend application (e.g. admin console, wechat mini program). */
|
|
11
|
+
export interface FrontAppSchema extends SchemaBase {
|
|
12
|
+
type: FrontType;
|
|
13
|
+
/** Source directory relative to project root, e.g. 'web-admin/'. */
|
|
14
|
+
dir: string;
|
|
15
|
+
/**
|
|
16
|
+
* Tenant table for this app. When set, all tables with a foreign key
|
|
17
|
+
* pointing to this table get automatic tenant scoping: the tenant PK
|
|
18
|
+
* value is injected from `user.id` into all curd operations.
|
|
19
|
+
*/
|
|
20
|
+
tenant?: TableSchema;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A backend API service. apps references shared FrontAppSchema instances. */
|
|
24
|
+
export interface ProjectApiSchema extends SchemaBase {
|
|
25
|
+
/** Source directory relative to project root, e.g. 'api/'. */
|
|
26
|
+
dir: string;
|
|
27
|
+
/** Frontends this API serves. Direct instance references (see defineProject). */
|
|
28
|
+
apps: FrontAppSchema[];
|
|
29
|
+
/** API base URL prefix shared by all apps it serves, e.g. '/mall'. '' = no prefix. */
|
|
30
|
+
contextPath?: string;
|
|
31
|
+
/** API service base URL for node clients, e.g. 'http://127.0.0.1:3000'. */
|
|
32
|
+
baseUrl?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A third-party system (e.g. wechat pay, unionpay). Owns its own
|
|
36
|
+
* implementation dir and contract (controller_types), just like an API,
|
|
37
|
+
* but is not part of this repo's served surface. */
|
|
38
|
+
export interface ThirdApiSchema extends SchemaBase {
|
|
39
|
+
/** Source directory relative to project root, e.g. 'wechat/'. */
|
|
40
|
+
dir: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ProjectSchema extends SchemaBase {
|
|
44
|
+
apps: FrontAppSchema[];
|
|
45
|
+
apis: ProjectApiSchema[];
|
|
46
|
+
thirdApis: ThirdApiSchema[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Defines the project topology. FrontAppSchema instances are shared value objects:
|
|
51
|
+
* api.apps references the same instances from project.apps, so an app served
|
|
52
|
+
* by multiple APIs is defined once and referenced many times.
|
|
53
|
+
*
|
|
54
|
+
* Runtime-validates app type whitelist, unique names and api.apps reference
|
|
55
|
+
* integrity (same style as defineTable/defineCurd).
|
|
56
|
+
*/
|
|
57
|
+
export function defineProject(
|
|
58
|
+
name: string,
|
|
59
|
+
schema: {
|
|
60
|
+
description?: string;
|
|
61
|
+
apps: FrontAppSchema[];
|
|
62
|
+
apis: ProjectApiSchema[];
|
|
63
|
+
thirdApis?: ThirdApiSchema[];
|
|
64
|
+
},
|
|
65
|
+
): ProjectSchema {
|
|
66
|
+
const project: ProjectSchema = { name, ...schema, thirdApis: schema.thirdApis ?? [] };
|
|
67
|
+
|
|
68
|
+
const appNames = new Set<string>();
|
|
69
|
+
for (const app of project.apps) {
|
|
70
|
+
if (!app.name) throw new Error(`project ${name}: app name is required`);
|
|
71
|
+
if (appNames.has(app.name)) throw new Error(`project ${name}: duplicate app name '${app.name}'`);
|
|
72
|
+
appNames.add(app.name);
|
|
73
|
+
if (app.type !== 'admin' && app.type !== 'wxmini') {
|
|
74
|
+
throw new Error(`project ${name}: app '${app.name}' must be type 'admin' or 'wxmini' (got '${app.type}')`);
|
|
75
|
+
}
|
|
76
|
+
if (!app.dir) throw new Error(`project ${name}: app '${app.name}' dir is required`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const apiNames = new Set<string>();
|
|
80
|
+
for (const api of project.apis) {
|
|
81
|
+
if (!api.name) throw new Error(`project ${name}: api name is required`);
|
|
82
|
+
if (apiNames.has(api.name)) throw new Error(`project ${name}: duplicate api name '${api.name}'`);
|
|
83
|
+
apiNames.add(api.name);
|
|
84
|
+
if (!api.dir) throw new Error(`project ${name}: api '${api.name}' dir is required`);
|
|
85
|
+
for (const ref of api.apps) {
|
|
86
|
+
if (!project.apps.includes(ref)) {
|
|
87
|
+
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)`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (const third of project.thirdApis) {
|
|
93
|
+
if (!third.name) throw new Error(`project ${name}: thirdApi name is required`);
|
|
94
|
+
if (!third.dir) throw new Error(`project ${name}: thirdApi '${third.name}' dir is required`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return project;
|
|
98
98
|
}
|
package/src/provider.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { ImportableSchemaBase } from './dsl.js';
|
|
|
3
3
|
import type { DtoField, DtoMessage, DtoArrayField, DtoObjectField } from './dto.js';
|
|
4
4
|
import type { ActionSchema } from './action.js';
|
|
5
5
|
import type { RefSchema } from './ref.js';
|
|
6
|
-
import type {
|
|
6
|
+
import type { EndpointSchema } from './endpoint.js';
|
|
7
7
|
|
|
8
8
|
// ProviderSchema: an API function signature (args DTO + results DTO).
|
|
9
9
|
|
|
@@ -35,13 +35,11 @@ export function call(func: ProviderSchema, args?: Record<string, DtoField | DtoM
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/** Provider function signature. The generated API client exposes one function
|
|
38
|
-
* per endpoint; ProviderSchema gives that function a name and
|
|
38
|
+
* per endpoint; ProviderSchema gives that function a name and a shared signature. */
|
|
39
39
|
export interface ProviderSchema extends ImportableSchemaBase {
|
|
40
40
|
isAsync: boolean;
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
/** Output DTO (buildOutput result) or primitive. */
|
|
44
|
-
results: DtoMessage | number | boolean | string;
|
|
41
|
+
/** Shared API signature — same instance the backend controller method references. */
|
|
42
|
+
signature: EndpointSchema;
|
|
45
43
|
}
|
|
46
44
|
|
|
47
45
|
/** Define a provider function. */
|
|
@@ -49,8 +47,7 @@ export function defineProvider(
|
|
|
49
47
|
name: string,
|
|
50
48
|
schema: {
|
|
51
49
|
isAsync: boolean;
|
|
52
|
-
|
|
53
|
-
results: DtoMessage | number | boolean | string;
|
|
50
|
+
signature: EndpointSchema;
|
|
54
51
|
description?: string;
|
|
55
52
|
importRef?: ImportBase;
|
|
56
53
|
},
|
|
@@ -64,10 +61,8 @@ export interface SetDataAction extends ActionSchema {
|
|
|
64
61
|
type: 'setData';
|
|
65
62
|
call: CallAction;
|
|
66
63
|
field: DtoField;
|
|
67
|
-
/** Optional field-level transform before assignment. */
|
|
68
|
-
convert?: ConvertSchema;
|
|
69
64
|
}
|
|
70
65
|
|
|
71
|
-
export function setData(call: CallAction, field: DtoField
|
|
72
|
-
return { name: 'setData', type: 'setData', call, field
|
|
66
|
+
export function setData(call: CallAction, field: DtoField): SetDataAction {
|
|
67
|
+
return { name: 'setData', type: 'setData', call, field };
|
|
73
68
|
}
|
package/src/ref.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import type { CollectionSchemaBase } from './dsl.js';
|
|
2
|
-
import type { DtoField } from './dto.js';
|
|
3
|
-
|
|
4
|
-
/** A reference to a field within another schema. Carries a .schema back-reference
|
|
5
|
-
* so the driver knows where the data comes from (route params, page data, etc.). */
|
|
6
|
-
export interface RefSchema {
|
|
7
|
-
name: string;
|
|
8
|
-
/** Schema back-reference — tells driver the data source. */
|
|
9
|
-
schema: CollectionSchemaBase;
|
|
10
|
-
/** The referenced field instance. */
|
|
11
|
-
field: DtoField;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** Create a reference to a field in a source schema.
|
|
15
|
-
* Writes back field.schema to the source so the driver can trace origin. */
|
|
16
|
-
export function defineRef(source: CollectionSchemaBase, field: DtoField): RefSchema {
|
|
17
|
-
field.schema = source;
|
|
18
|
-
return { name: field.name, schema: source, field };
|
|
1
|
+
import type { CollectionSchemaBase } from './dsl.js';
|
|
2
|
+
import type { DtoField } from './dto.js';
|
|
3
|
+
|
|
4
|
+
/** A reference to a field within another schema. Carries a .schema back-reference
|
|
5
|
+
* so the driver knows where the data comes from (route params, page data, etc.). */
|
|
6
|
+
export interface RefSchema {
|
|
7
|
+
name: string;
|
|
8
|
+
/** Schema back-reference — tells driver the data source. */
|
|
9
|
+
schema: CollectionSchemaBase;
|
|
10
|
+
/** The referenced field instance. */
|
|
11
|
+
field: DtoField;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Create a reference to a field in a source schema.
|
|
15
|
+
* Writes back field.schema to the source so the driver can trace origin. */
|
|
16
|
+
export function defineRef(source: CollectionSchemaBase, field: DtoField): RefSchema {
|
|
17
|
+
field.schema = source;
|
|
18
|
+
return { name: field.name, schema: source, field };
|
|
19
19
|
}
|
package/src/route.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type { CollectionSchemaBase } from './dsl.js';
|
|
2
|
-
import type { DtoField } from './dto.js';
|
|
3
|
-
|
|
4
|
-
/** Route params: data that arrives from the navigation URL / route. */
|
|
5
|
-
export interface RouteDataSchema extends CollectionSchemaBase {
|
|
6
|
-
type: 'route';
|
|
7
|
-
fields: Record<string, DtoField>;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function defineRouteData(name: string, fields: RouteDataSchema['fields']): RouteDataSchema {
|
|
11
|
-
return { name, type: 'route', fields };
|
|
1
|
+
import type { CollectionSchemaBase } from './dsl.js';
|
|
2
|
+
import type { DtoField } from './dto.js';
|
|
3
|
+
|
|
4
|
+
/** Route params: data that arrives from the navigation URL / route. */
|
|
5
|
+
export interface RouteDataSchema extends CollectionSchemaBase {
|
|
6
|
+
type: 'route';
|
|
7
|
+
fields: Record<string, DtoField>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function defineRouteData(name: string, fields: RouteDataSchema['fields']): RouteDataSchema {
|
|
11
|
+
return { name, type: 'route', fields };
|
|
12
12
|
}
|
package/src/service.ts
CHANGED
|
@@ -2,20 +2,42 @@ import { SchemaBase } from './dsl.js';
|
|
|
2
2
|
import { FrontAppSchema } from './project.js';
|
|
3
3
|
import type { DtoArrayField, DtoField, DtoMessage, DtoObjectField } from './dto.js';
|
|
4
4
|
|
|
5
|
-
/** A
|
|
5
|
+
/** A backend service serving exactly one frontend app (1:1 module). */
|
|
6
6
|
export interface ServiceSchema extends SchemaBase {
|
|
7
7
|
type: 'service';
|
|
8
|
-
/** The frontend app this service
|
|
8
|
+
/** The frontend app this service serves (shared instance from project.config). */
|
|
9
9
|
app: FrontAppSchema;
|
|
10
|
+
/** Methods keyed by name — the map key is written back as the method name. */
|
|
11
|
+
methods: Record<string, ServiceMethodSchema>;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
/** Method input for defineService: type/schema/name are set by the builder. */
|
|
15
|
+
export type ServiceMethodDef = Omit<ServiceMethodSchema, 'type' | 'schema' | 'name'>;
|
|
16
|
+
|
|
17
|
+
export function defineService(options: {
|
|
18
|
+
name: string;
|
|
19
|
+
app: FrontAppSchema;
|
|
20
|
+
methods: Record<string, ServiceMethodDef>;
|
|
21
|
+
description?: string;
|
|
22
|
+
}): ServiceSchema {
|
|
23
|
+
const schema: ServiceSchema = {
|
|
24
|
+
type: 'service',
|
|
25
|
+
name: options.name,
|
|
26
|
+
description: options.description,
|
|
27
|
+
app: options.app,
|
|
28
|
+
methods: {},
|
|
29
|
+
};
|
|
30
|
+
for (const key of Object.keys(options.methods)) {
|
|
31
|
+
const method = options.methods[key] as ServiceMethodDef;
|
|
32
|
+
schema.methods[key] = { type: 'method', schema, ...method, name: key };
|
|
33
|
+
}
|
|
34
|
+
return schema;
|
|
14
35
|
}
|
|
15
36
|
|
|
16
37
|
/** A method exposed by a service. */
|
|
17
38
|
export interface ServiceMethodSchema extends SchemaBase {
|
|
18
39
|
type: 'method';
|
|
19
|
-
|
|
20
|
-
|
|
40
|
+
schema : ServiceSchema;
|
|
41
|
+
args: DtoMessage,
|
|
42
|
+
results : DtoMessage
|
|
21
43
|
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { CollectionSchemaBase, Field, SchemaBase } from './dsl.js';
|
|
2
|
+
import type { FieldRuleEnd, FieldRuleSchema } from './field-rule.js';
|
|
3
|
+
import type { ThirdApiSchema } from './project.js';
|
|
4
|
+
|
|
5
|
+
// Third-party integration services.
|
|
6
|
+
// A ThirdMethodSchema is the Field-collection counterpart of a DtoMessage —
|
|
7
|
+
// like TableSchema.columns but for wire-format fields instead of DB columns.
|
|
8
|
+
// DTOs project from it via from(thirdMethod), and its fields may either share
|
|
9
|
+
// a local entity column instance (same fact, same type) or declare their own
|
|
10
|
+
// wire type plus a refs link (same fact, different type/format).
|
|
11
|
+
|
|
12
|
+
/** A third-party integration service (e.g. tenpay wechat pay).
|
|
13
|
+
* Distinct from ServiceSchema (backend service bound to an app) and
|
|
14
|
+
* ThirdApiSchema (project topology: the dir the integration lives in).
|
|
15
|
+
* Describes the adapter class contract: constructor config + methods. */
|
|
16
|
+
export interface ThirdServiceSchema extends SchemaBase {
|
|
17
|
+
type: 'thirdService';
|
|
18
|
+
/** The third-party system this service integrates (topology reference). */
|
|
19
|
+
schema: ThirdApiSchema;
|
|
20
|
+
/** Methods keyed by name — the map key is written back as the method name. */
|
|
21
|
+
methods: Record<string, ThirdServiceMethodSchema>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** A method exposed by a third-party service. */
|
|
25
|
+
export interface ThirdServiceMethodSchema extends SchemaBase {
|
|
26
|
+
type: 'method';
|
|
27
|
+
schema: ThirdServiceSchema;
|
|
28
|
+
/** Input message — the Field-collection counterpart of a DtoMessage. */
|
|
29
|
+
args: ThirdMethodSchema;
|
|
30
|
+
/** Output message. */
|
|
31
|
+
results: ThirdMethodSchema;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Field binding to a rule: which end the local wire field stands on.
|
|
35
|
+
* The ref always stands on the other end — from/to carry no extra information. */
|
|
36
|
+
export interface ConvertFieldSchema {
|
|
37
|
+
/** The rule binding field and ref (rule = name + two ends). */
|
|
38
|
+
rule: FieldRuleSchema;
|
|
39
|
+
/** The end instance the local wire field stands on. */
|
|
40
|
+
end: FieldRuleEnd;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Same-fact variant link: a wire field carrying the same fact as a local
|
|
44
|
+
* entity column under a different type/format (e.g. total_fee in fen vs amount in yuan). */
|
|
45
|
+
export interface ThirdFieldRef {
|
|
46
|
+
/** Wire field defined in this message (local definition). */
|
|
47
|
+
field: Field;
|
|
48
|
+
/** Field in another schema (table column or another message). */
|
|
49
|
+
ref: Field;
|
|
50
|
+
/** Optional rule binding — omitted when the fact is merely linked, not converted. */
|
|
51
|
+
convert?: ConvertFieldSchema;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A directional message of a third-party method: a Field collection mirroring
|
|
55
|
+
* TableSchema.columns, but fields hold wire-format names/types. A field may be
|
|
56
|
+
* a shared instance of a local entity column (same fact, same type) — its
|
|
57
|
+
* name/schema keep pointing at the table and the DTO projection inherits
|
|
58
|
+
* type/semantics from the entity, exactly like from(table). */
|
|
59
|
+
export interface ThirdMethodSchema extends CollectionSchemaBase {
|
|
60
|
+
type: 'thirdMethod';
|
|
61
|
+
/** The method this message belongs to (direction implied by args/results slot). */
|
|
62
|
+
schema: ThirdServiceMethodSchema;
|
|
63
|
+
/** Wire-format fields. */
|
|
64
|
+
fields: Record<string, Field>;
|
|
65
|
+
/** Same-fact variant links: wire field -> local entity column. */
|
|
66
|
+
refs?: ThirdFieldRef[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Message input for defineThirdMethod: type/schema are set by the builder. */
|
|
70
|
+
export type ThirdMethodDef = Omit<ThirdMethodSchema, 'type' | 'schema'>;
|
|
71
|
+
|
|
72
|
+
/** Build a third-party method message. Writes back name/schema on own fields
|
|
73
|
+
* (top-level and nested); shared entity columns keep their table identity and
|
|
74
|
+
* must be keyed by their column name. */
|
|
75
|
+
export function defineThirdMethod(def: ThirdMethodDef): ThirdMethodSchema {
|
|
76
|
+
const message: ThirdMethodSchema = {
|
|
77
|
+
type: 'thirdMethod',
|
|
78
|
+
name: def.name,
|
|
79
|
+
description: def.description,
|
|
80
|
+
// Filled by defineThirdService.
|
|
81
|
+
schema: undefined as unknown as ThirdServiceMethodSchema,
|
|
82
|
+
fields: def.fields,
|
|
83
|
+
refs: def.refs,
|
|
84
|
+
};
|
|
85
|
+
for (const key of Object.keys(message.fields)) {
|
|
86
|
+
const field = message.fields[key] as Field;
|
|
87
|
+
if (field.schema === undefined) {
|
|
88
|
+
field.name = key;
|
|
89
|
+
field.schema = message;
|
|
90
|
+
} else if (field.schema.type === 'table') {
|
|
91
|
+
// Shared entity column: from() names the projection after field.name, so
|
|
92
|
+
// a mismatched key would silently rename the wire field. Same-fact fields
|
|
93
|
+
// with different names go through refs instead.
|
|
94
|
+
if (field.name !== key) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`thirdMethod '${message.name}': shared column key '${key}' must match the column name '${field.name}' — ` +
|
|
97
|
+
`same-fact fields with different names go through refs instead`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
} else if (field.schema !== message) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`thirdMethod '${message.name}': field '${key}' already belongs to ${field.schema.type} '${field.schema.name}', cannot reuse`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
writeBackNested(message, field);
|
|
106
|
+
}
|
|
107
|
+
if (message.refs !== undefined) {
|
|
108
|
+
const ownFields = Object.values(message.fields);
|
|
109
|
+
for (const link of message.refs) {
|
|
110
|
+
if (!ownFields.includes(link.field)) {
|
|
111
|
+
throw new Error(`thirdMethod '${message.name}': ref field must be one of its fields`);
|
|
112
|
+
}
|
|
113
|
+
if (link.ref.schema === undefined || link.ref.schema === message) {
|
|
114
|
+
throw new Error(`thirdMethod '${message.name}': ref target '${link.ref.name}' must be defined in another schema`);
|
|
115
|
+
}
|
|
116
|
+
if (link.convert !== undefined) {
|
|
117
|
+
const ends = Object.values(link.convert.rule.ends);
|
|
118
|
+
if (!ends.includes(link.convert.end)) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`thirdMethod '${message.name}': convert end '${link.convert.end.name}' must be one of rule '${link.convert.rule.name}' ends`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return message;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Write back name/schema on nested wire fields (array items, object properties);
|
|
130
|
+
* shared entity columns keep their table identity. */
|
|
131
|
+
function writeBackNested(message: ThirdMethodSchema, field: Field): void {
|
|
132
|
+
if (field.type === 'array') {
|
|
133
|
+
writeBackNested(message, field.items);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (field.type !== 'object') return;
|
|
137
|
+
for (const key of Object.keys(field.properties)) {
|
|
138
|
+
const child = field.properties[key] as Field;
|
|
139
|
+
if (child.schema === undefined) {
|
|
140
|
+
child.name = key;
|
|
141
|
+
child.schema = message;
|
|
142
|
+
}
|
|
143
|
+
writeBackNested(message, child);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Method input for defineThirdService: name is written back from the methods map key. */
|
|
148
|
+
export interface ThirdServiceMethodDef {
|
|
149
|
+
/** Input message. */
|
|
150
|
+
args: ThirdMethodDef;
|
|
151
|
+
/** Output message. */
|
|
152
|
+
results: ThirdMethodDef;
|
|
153
|
+
description?: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function defineThirdService(options: {
|
|
157
|
+
schema: ThirdApiSchema;
|
|
158
|
+
name: string;
|
|
159
|
+
methods: Record<string, ThirdServiceMethodDef>;
|
|
160
|
+
description?: string;
|
|
161
|
+
}): ThirdServiceSchema {
|
|
162
|
+
const schema: ThirdServiceSchema = {
|
|
163
|
+
type: 'thirdService',
|
|
164
|
+
name: options.name,
|
|
165
|
+
description: options.description,
|
|
166
|
+
schema: options.schema,
|
|
167
|
+
methods: {},
|
|
168
|
+
};
|
|
169
|
+
for (const key of Object.keys(options.methods)) {
|
|
170
|
+
const method = options.methods[key] as ThirdServiceMethodDef;
|
|
171
|
+
const methodSchema: ThirdServiceMethodSchema = {
|
|
172
|
+
type: 'method',
|
|
173
|
+
name: key,
|
|
174
|
+
description: method.description,
|
|
175
|
+
schema,
|
|
176
|
+
args: undefined as unknown as ThirdMethodSchema,
|
|
177
|
+
results: undefined as unknown as ThirdMethodSchema,
|
|
178
|
+
};
|
|
179
|
+
methodSchema.args = defineThirdMethod(method.args);
|
|
180
|
+
methodSchema.results = defineThirdMethod(method.results);
|
|
181
|
+
methodSchema.args.schema = methodSchema;
|
|
182
|
+
methodSchema.results.schema = methodSchema;
|
|
183
|
+
schema.methods[key] = methodSchema;
|
|
184
|
+
}
|
|
185
|
+
return schema;
|
|
186
|
+
}
|