@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/dist/mermaid-driver.js
CHANGED
|
@@ -17,24 +17,53 @@ export function renderFlowMermaid(schema) {
|
|
|
17
17
|
}
|
|
18
18
|
function renderFlow(schema, prefix, lines, ids) {
|
|
19
19
|
schema.nodes.forEach((n, i) => ids.set(n, `${prefix}${i}`));
|
|
20
|
+
const outgoing = new Map();
|
|
21
|
+
for (const n of schema.nodes)
|
|
22
|
+
outgoing.set(n, []);
|
|
23
|
+
for (const e of schema.edges)
|
|
24
|
+
outgoing.get(e.start).push(e);
|
|
20
25
|
for (const n of schema.nodes) {
|
|
21
26
|
const id = ids.get(n);
|
|
22
27
|
if (n.flow) {
|
|
23
|
-
lines.push(` subgraph ${id}["${escapeLabel(n
|
|
28
|
+
lines.push(` subgraph ${id}["${escapeLabel(renderNodeLabel(n))}"]`);
|
|
24
29
|
renderFlow(n.flow, `${id}_`, lines, ids);
|
|
25
30
|
lines.push(' end');
|
|
26
31
|
}
|
|
27
32
|
else {
|
|
28
|
-
|
|
33
|
+
const shape = renderShape(n, schema, outgoing.get(n));
|
|
34
|
+
lines.push(` ${id}${shape.open}${escapeLabel(renderNodeLabel(n))}${shape.close}`);
|
|
29
35
|
}
|
|
30
36
|
}
|
|
31
37
|
for (const e of schema.edges) {
|
|
32
38
|
lines.push(renderEdge(e, ids));
|
|
33
39
|
}
|
|
34
40
|
}
|
|
41
|
+
/** Node label: name on the first line, method references on the second. */
|
|
42
|
+
function renderNodeLabel(n) {
|
|
43
|
+
if (n.methods === undefined || n.methods.length === 0)
|
|
44
|
+
return n.name;
|
|
45
|
+
return `${n.name}\n${n.methods.map((m) => renderMethodRef(m)).join(' | ')}`;
|
|
46
|
+
}
|
|
47
|
+
/** Display form: owner.name for descriptors and container methods, bare name for converts. */
|
|
48
|
+
function renderMethodRef(m) {
|
|
49
|
+
if ('owner' in m)
|
|
50
|
+
return `${m.owner}.${m.name}`;
|
|
51
|
+
if ('schema' in m)
|
|
52
|
+
return `${m.schema.name}.${m.name}`;
|
|
53
|
+
return m.name;
|
|
54
|
+
}
|
|
55
|
+
/** Shape derived from topology: start and terminals rounded, branching nodes diamond, else rect.
|
|
56
|
+
* Exception edges do not count as branches. */
|
|
57
|
+
function renderShape(n, schema, outgoing) {
|
|
58
|
+
if (n === schema.start || outgoing.length === 0)
|
|
59
|
+
return { open: '(["', close: '"])' };
|
|
60
|
+
const normal = outgoing.filter((e) => e.exception !== true).length;
|
|
61
|
+
return normal >= 2 ? { open: '{"', close: '"}' } : { open: '["', close: '"]' };
|
|
62
|
+
}
|
|
35
63
|
function renderEdge(e, ids) {
|
|
64
|
+
const arrow = e.exception === true ? '-.->' : '-->';
|
|
36
65
|
const label = e.when ? `|"${escapeLabel(e.when)}"|` : '';
|
|
37
|
-
return ` ${ids.get(e.start)}
|
|
66
|
+
return ` ${ids.get(e.start)} ${arrow}${label} ${ids.get(e.end)}`;
|
|
38
67
|
}
|
|
39
68
|
// Page-driven flow renderer: groups pages by their app into swimlane
|
|
40
69
|
// subgraphs, then renders edges across the whole flow.
|
package/dist/method.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SchemaBase } from './dsl.js';
|
|
2
|
+
export interface MethodSchema extends SchemaBase {
|
|
3
|
+
/** Owning schema name, e.g. 'payDao'. */
|
|
4
|
+
owner: string;
|
|
5
|
+
}
|
|
6
|
+
export type SequenceSchema = MethodSchema[];
|
|
7
|
+
export declare function defineMethod(options: {
|
|
8
|
+
name: string;
|
|
9
|
+
owner: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
}): MethodSchema;
|
package/dist/method.js
ADDED
package/dist/mysql-driver.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { rateScale } from './dsl.js';
|
|
1
2
|
// MySQL driver: converts a TableSchema into a CREATE TABLE statement.
|
|
2
3
|
/** Default value constant for created_at columns. */
|
|
3
4
|
export const CURRENT_TIMESTAMP = 'CURRENT_TIMESTAMP';
|
|
@@ -17,6 +18,8 @@ function columnType(field) {
|
|
|
17
18
|
return 'BIGINT';
|
|
18
19
|
case 'decimal':
|
|
19
20
|
return `DECIMAL(${field.precision}, ${field.scale})`;
|
|
21
|
+
case 'rate':
|
|
22
|
+
return `DECIMAL(5, ${rateScale(field.unit)})`;
|
|
20
23
|
case 'boolean':
|
|
21
24
|
return 'TINYINT(1)';
|
|
22
25
|
case 'date':
|
|
@@ -30,6 +33,10 @@ function columnType(field) {
|
|
|
30
33
|
return field.enum.valueType === 'integer' ? 'TINYINT' : 'VARCHAR(20)';
|
|
31
34
|
case 'json':
|
|
32
35
|
return 'JSON';
|
|
36
|
+
case 'array':
|
|
37
|
+
case 'object':
|
|
38
|
+
// Nested fields are wire-format only (third-party messages); table columns cannot nest.
|
|
39
|
+
throw new Error(`field ${field.name} (${field.type}): nested fields are not supported on table columns`);
|
|
33
40
|
}
|
|
34
41
|
}
|
|
35
42
|
function renderDefault(field) {
|
|
@@ -40,7 +47,7 @@ function renderDefault(field) {
|
|
|
40
47
|
if (/^[A-Z]/.test(field.default))
|
|
41
48
|
return ` DEFAULT ${field.default}`;
|
|
42
49
|
// Numeric columns take a bare literal, not a quoted one.
|
|
43
|
-
if (field.type === 'integer' || field.type === 'bigint' || field.type === 'decimal' || field.type === 'boolean') {
|
|
50
|
+
if (field.type === 'integer' || field.type === 'bigint' || field.type === 'decimal' || field.type === 'rate' || field.type === 'boolean') {
|
|
44
51
|
return ` DEFAULT ${field.default}`;
|
|
45
52
|
}
|
|
46
53
|
return ` DEFAULT '${field.default}'`;
|
package/dist/provider.d.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
|
/** Parameter data source for a call argument. */
|
|
8
8
|
export type DataRef = {
|
|
9
9
|
type: 'route';
|
|
@@ -26,19 +26,16 @@ export interface CallAction extends ActionSchema {
|
|
|
26
26
|
}
|
|
27
27
|
export declare function call(func: ProviderSchema, args?: Record<string, DtoField | DtoMessage | DtoArrayField | DtoObjectField | RefSchema | string>): CallAction;
|
|
28
28
|
/** Provider function signature. The generated API client exposes one function
|
|
29
|
-
* per endpoint; ProviderSchema gives that function a name and
|
|
29
|
+
* per endpoint; ProviderSchema gives that function a name and a shared signature. */
|
|
30
30
|
export interface ProviderSchema extends ImportableSchemaBase {
|
|
31
31
|
isAsync: boolean;
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
/** Output DTO (buildOutput result) or primitive. */
|
|
35
|
-
results: DtoMessage | number | boolean | string;
|
|
32
|
+
/** Shared API signature — same instance the backend controller method references. */
|
|
33
|
+
signature: EndpointSchema;
|
|
36
34
|
}
|
|
37
35
|
/** Define a provider function. */
|
|
38
36
|
export declare function defineProvider(name: string, schema: {
|
|
39
37
|
isAsync: boolean;
|
|
40
|
-
|
|
41
|
-
results: DtoMessage | number | boolean | string;
|
|
38
|
+
signature: EndpointSchema;
|
|
42
39
|
description?: string;
|
|
43
40
|
importRef?: ImportBase;
|
|
44
41
|
}): ProviderSchema;
|
|
@@ -48,7 +45,5 @@ export interface SetDataAction extends ActionSchema {
|
|
|
48
45
|
type: 'setData';
|
|
49
46
|
call: CallAction;
|
|
50
47
|
field: DtoField;
|
|
51
|
-
/** Optional field-level transform before assignment. */
|
|
52
|
-
convert?: ConvertSchema;
|
|
53
48
|
}
|
|
54
|
-
export declare function setData(call: CallAction, field: DtoField
|
|
49
|
+
export declare function setData(call: CallAction, field: DtoField): SetDataAction;
|
package/dist/provider.js
CHANGED
|
@@ -13,6 +13,6 @@ export function call(func, args) {
|
|
|
13
13
|
export function defineProvider(name, schema) {
|
|
14
14
|
return { name, ...schema };
|
|
15
15
|
}
|
|
16
|
-
export function setData(call, field
|
|
17
|
-
return { name: 'setData', type: 'setData', call, field
|
|
16
|
+
export function setData(call, field) {
|
|
17
|
+
return { name: 'setData', type: 'setData', call, field };
|
|
18
18
|
}
|
package/dist/service.d.ts
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
import { SchemaBase } from './dsl.js';
|
|
2
2
|
import { FrontAppSchema } from './project.js';
|
|
3
|
-
import type {
|
|
4
|
-
/** A
|
|
3
|
+
import type { DtoMessage } from './dto.js';
|
|
4
|
+
/** A backend service serving exactly one frontend app (1:1 module). */
|
|
5
5
|
export interface ServiceSchema extends SchemaBase {
|
|
6
6
|
type: 'service';
|
|
7
|
-
/** The frontend app this service
|
|
7
|
+
/** The frontend app this service serves (shared instance from project.config). */
|
|
8
8
|
app: FrontAppSchema;
|
|
9
|
+
/** Methods keyed by name — the map key is written back as the method name. */
|
|
10
|
+
methods: Record<string, ServiceMethodSchema>;
|
|
9
11
|
}
|
|
10
|
-
|
|
12
|
+
/** Method input for defineService: type/schema/name are set by the builder. */
|
|
13
|
+
export type ServiceMethodDef = Omit<ServiceMethodSchema, 'type' | 'schema' | 'name'>;
|
|
14
|
+
export declare function defineService(options: {
|
|
15
|
+
name: string;
|
|
16
|
+
app: FrontAppSchema;
|
|
17
|
+
methods: Record<string, ServiceMethodDef>;
|
|
18
|
+
description?: string;
|
|
19
|
+
}): ServiceSchema;
|
|
11
20
|
/** A method exposed by a service. */
|
|
12
21
|
export interface ServiceMethodSchema extends SchemaBase {
|
|
13
22
|
type: 'method';
|
|
14
|
-
|
|
15
|
-
|
|
23
|
+
schema: ServiceSchema;
|
|
24
|
+
args: DtoMessage;
|
|
25
|
+
results: DtoMessage;
|
|
16
26
|
}
|
package/dist/service.js
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
-
export function defineService(
|
|
2
|
-
|
|
1
|
+
export function defineService(options) {
|
|
2
|
+
const schema = {
|
|
3
|
+
type: 'service',
|
|
4
|
+
name: options.name,
|
|
5
|
+
description: options.description,
|
|
6
|
+
app: options.app,
|
|
7
|
+
methods: {},
|
|
8
|
+
};
|
|
9
|
+
for (const key of Object.keys(options.methods)) {
|
|
10
|
+
const method = options.methods[key];
|
|
11
|
+
schema.methods[key] = { type: 'method', schema, ...method, name: key };
|
|
12
|
+
}
|
|
13
|
+
return schema;
|
|
3
14
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
/** A third-party integration service (e.g. tenpay wechat pay).
|
|
5
|
+
* Distinct from ServiceSchema (backend service bound to an app) and
|
|
6
|
+
* ThirdApiSchema (project topology: the dir the integration lives in).
|
|
7
|
+
* Describes the adapter class contract: constructor config + methods. */
|
|
8
|
+
export interface ThirdServiceSchema extends SchemaBase {
|
|
9
|
+
type: 'thirdService';
|
|
10
|
+
/** The third-party system this service integrates (topology reference). */
|
|
11
|
+
schema: ThirdApiSchema;
|
|
12
|
+
/** Methods keyed by name — the map key is written back as the method name. */
|
|
13
|
+
methods: Record<string, ThirdServiceMethodSchema>;
|
|
14
|
+
}
|
|
15
|
+
/** A method exposed by a third-party service. */
|
|
16
|
+
export interface ThirdServiceMethodSchema extends SchemaBase {
|
|
17
|
+
type: 'method';
|
|
18
|
+
schema: ThirdServiceSchema;
|
|
19
|
+
/** Input message — the Field-collection counterpart of a DtoMessage. */
|
|
20
|
+
args: ThirdMethodSchema;
|
|
21
|
+
/** Output message. */
|
|
22
|
+
results: ThirdMethodSchema;
|
|
23
|
+
}
|
|
24
|
+
/** Field binding to a rule: which end the local wire field stands on.
|
|
25
|
+
* The ref always stands on the other end — from/to carry no extra information. */
|
|
26
|
+
export interface ConvertFieldSchema {
|
|
27
|
+
/** The rule binding field and ref (rule = name + two ends). */
|
|
28
|
+
rule: FieldRuleSchema;
|
|
29
|
+
/** The end instance the local wire field stands on. */
|
|
30
|
+
end: FieldRuleEnd;
|
|
31
|
+
}
|
|
32
|
+
/** Same-fact variant link: a wire field carrying the same fact as a local
|
|
33
|
+
* entity column under a different type/format (e.g. total_fee in fen vs amount in yuan). */
|
|
34
|
+
export interface ThirdFieldRef {
|
|
35
|
+
/** Wire field defined in this message (local definition). */
|
|
36
|
+
field: Field;
|
|
37
|
+
/** Field in another schema (table column or another message). */
|
|
38
|
+
ref: Field;
|
|
39
|
+
/** Optional rule binding — omitted when the fact is merely linked, not converted. */
|
|
40
|
+
convert?: ConvertFieldSchema;
|
|
41
|
+
}
|
|
42
|
+
/** A directional message of a third-party method: a Field collection mirroring
|
|
43
|
+
* TableSchema.columns, but fields hold wire-format names/types. A field may be
|
|
44
|
+
* a shared instance of a local entity column (same fact, same type) — its
|
|
45
|
+
* name/schema keep pointing at the table and the DTO projection inherits
|
|
46
|
+
* type/semantics from the entity, exactly like from(table). */
|
|
47
|
+
export interface ThirdMethodSchema extends CollectionSchemaBase {
|
|
48
|
+
type: 'thirdMethod';
|
|
49
|
+
/** The method this message belongs to (direction implied by args/results slot). */
|
|
50
|
+
schema: ThirdServiceMethodSchema;
|
|
51
|
+
/** Wire-format fields. */
|
|
52
|
+
fields: Record<string, Field>;
|
|
53
|
+
/** Same-fact variant links: wire field -> local entity column. */
|
|
54
|
+
refs?: ThirdFieldRef[];
|
|
55
|
+
}
|
|
56
|
+
/** Message input for defineThirdMethod: type/schema are set by the builder. */
|
|
57
|
+
export type ThirdMethodDef = Omit<ThirdMethodSchema, 'type' | 'schema'>;
|
|
58
|
+
/** Build a third-party method message. Writes back name/schema on own fields
|
|
59
|
+
* (top-level and nested); shared entity columns keep their table identity and
|
|
60
|
+
* must be keyed by their column name. */
|
|
61
|
+
export declare function defineThirdMethod(def: ThirdMethodDef): ThirdMethodSchema;
|
|
62
|
+
/** Method input for defineThirdService: name is written back from the methods map key. */
|
|
63
|
+
export interface ThirdServiceMethodDef {
|
|
64
|
+
/** Input message. */
|
|
65
|
+
args: ThirdMethodDef;
|
|
66
|
+
/** Output message. */
|
|
67
|
+
results: ThirdMethodDef;
|
|
68
|
+
description?: string;
|
|
69
|
+
}
|
|
70
|
+
export declare function defineThirdService(options: {
|
|
71
|
+
schema: ThirdApiSchema;
|
|
72
|
+
name: string;
|
|
73
|
+
methods: Record<string, ThirdServiceMethodDef>;
|
|
74
|
+
description?: string;
|
|
75
|
+
}): ThirdServiceSchema;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** Build a third-party method message. Writes back name/schema on own fields
|
|
2
|
+
* (top-level and nested); shared entity columns keep their table identity and
|
|
3
|
+
* must be keyed by their column name. */
|
|
4
|
+
export function defineThirdMethod(def) {
|
|
5
|
+
const message = {
|
|
6
|
+
type: 'thirdMethod',
|
|
7
|
+
name: def.name,
|
|
8
|
+
description: def.description,
|
|
9
|
+
// Filled by defineThirdService.
|
|
10
|
+
schema: undefined,
|
|
11
|
+
fields: def.fields,
|
|
12
|
+
refs: def.refs,
|
|
13
|
+
};
|
|
14
|
+
for (const key of Object.keys(message.fields)) {
|
|
15
|
+
const field = message.fields[key];
|
|
16
|
+
if (field.schema === undefined) {
|
|
17
|
+
field.name = key;
|
|
18
|
+
field.schema = message;
|
|
19
|
+
}
|
|
20
|
+
else if (field.schema.type === 'table') {
|
|
21
|
+
// Shared entity column: from() names the projection after field.name, so
|
|
22
|
+
// a mismatched key would silently rename the wire field. Same-fact fields
|
|
23
|
+
// with different names go through refs instead.
|
|
24
|
+
if (field.name !== key) {
|
|
25
|
+
throw new Error(`thirdMethod '${message.name}': shared column key '${key}' must match the column name '${field.name}' — ` +
|
|
26
|
+
`same-fact fields with different names go through refs instead`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else if (field.schema !== message) {
|
|
30
|
+
throw new Error(`thirdMethod '${message.name}': field '${key}' already belongs to ${field.schema.type} '${field.schema.name}', cannot reuse`);
|
|
31
|
+
}
|
|
32
|
+
writeBackNested(message, field);
|
|
33
|
+
}
|
|
34
|
+
if (message.refs !== undefined) {
|
|
35
|
+
const ownFields = Object.values(message.fields);
|
|
36
|
+
for (const link of message.refs) {
|
|
37
|
+
if (!ownFields.includes(link.field)) {
|
|
38
|
+
throw new Error(`thirdMethod '${message.name}': ref field must be one of its fields`);
|
|
39
|
+
}
|
|
40
|
+
if (link.ref.schema === undefined || link.ref.schema === message) {
|
|
41
|
+
throw new Error(`thirdMethod '${message.name}': ref target '${link.ref.name}' must be defined in another schema`);
|
|
42
|
+
}
|
|
43
|
+
if (link.convert !== undefined) {
|
|
44
|
+
const ends = Object.values(link.convert.rule.ends);
|
|
45
|
+
if (!ends.includes(link.convert.end)) {
|
|
46
|
+
throw new Error(`thirdMethod '${message.name}': convert end '${link.convert.end.name}' must be one of rule '${link.convert.rule.name}' ends`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return message;
|
|
52
|
+
}
|
|
53
|
+
/** Write back name/schema on nested wire fields (array items, object properties);
|
|
54
|
+
* shared entity columns keep their table identity. */
|
|
55
|
+
function writeBackNested(message, field) {
|
|
56
|
+
if (field.type === 'array') {
|
|
57
|
+
writeBackNested(message, field.items);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (field.type !== 'object')
|
|
61
|
+
return;
|
|
62
|
+
for (const key of Object.keys(field.properties)) {
|
|
63
|
+
const child = field.properties[key];
|
|
64
|
+
if (child.schema === undefined) {
|
|
65
|
+
child.name = key;
|
|
66
|
+
child.schema = message;
|
|
67
|
+
}
|
|
68
|
+
writeBackNested(message, child);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export function defineThirdService(options) {
|
|
72
|
+
const schema = {
|
|
73
|
+
type: 'thirdService',
|
|
74
|
+
name: options.name,
|
|
75
|
+
description: options.description,
|
|
76
|
+
schema: options.schema,
|
|
77
|
+
methods: {},
|
|
78
|
+
};
|
|
79
|
+
for (const key of Object.keys(options.methods)) {
|
|
80
|
+
const method = options.methods[key];
|
|
81
|
+
const methodSchema = {
|
|
82
|
+
type: 'method',
|
|
83
|
+
name: key,
|
|
84
|
+
description: method.description,
|
|
85
|
+
schema,
|
|
86
|
+
args: undefined,
|
|
87
|
+
results: undefined,
|
|
88
|
+
};
|
|
89
|
+
methodSchema.args = defineThirdMethod(method.args);
|
|
90
|
+
methodSchema.results = defineThirdMethod(method.results);
|
|
91
|
+
methodSchema.args.schema = methodSchema;
|
|
92
|
+
methodSchema.results.schema = methodSchema;
|
|
93
|
+
schema.methods[key] = methodSchema;
|
|
94
|
+
}
|
|
95
|
+
return schema;
|
|
96
|
+
}
|
package/dist/typebox-driver.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DtoMessage, ImportBase } from './dto.js';
|
|
2
|
+
import type { ThirdMethodSchema } from './third-service.js';
|
|
2
3
|
export type EnumResolver = (enumName: string) => ImportBase | undefined;
|
|
3
4
|
/** Collect all imports needed to render a DTO: include() bases + enum references. */
|
|
4
5
|
export declare function collectDtoImports(schema: DtoMessage, resolver: EnumResolver | undefined, out: Map<string, ImportBase>): void;
|
|
@@ -10,3 +11,8 @@ export declare function renderDtoMessage(schema: DtoMessage, options?: {
|
|
|
10
11
|
resolver?: EnumResolver;
|
|
11
12
|
source?: string;
|
|
12
13
|
}): string;
|
|
14
|
+
/** Render one third-party method message export (const only — pair with
|
|
15
|
+
* renderDtoTypeExport for the Static type). */
|
|
16
|
+
export declare function renderThirdMethodExport(schema: ThirdMethodSchema, resolver: EnumResolver | undefined): string;
|
|
17
|
+
/** Collect all imports needed to render a third-party method message: enum references. */
|
|
18
|
+
export declare function collectThirdMethodImports(schema: ThirdMethodSchema, resolver: EnumResolver | undefined, out: Map<string, ImportBase>): void;
|
package/dist/typebox-driver.js
CHANGED
|
@@ -6,7 +6,7 @@ function renderDefault(v) {
|
|
|
6
6
|
return renderString(v);
|
|
7
7
|
return JSON.stringify(v);
|
|
8
8
|
}
|
|
9
|
-
function renderBasic(field, pattern, defaultValue, resolver) {
|
|
9
|
+
function renderBasic(field, pattern, defaultValue, resolver, indent = 0) {
|
|
10
10
|
if (pattern !== undefined && field.type !== 'string') {
|
|
11
11
|
throw new Error(`pattern is only supported on string fields, got ${field.type} (${field.name})`);
|
|
12
12
|
}
|
|
@@ -38,11 +38,12 @@ function renderBasic(field, pattern, defaultValue, resolver) {
|
|
|
38
38
|
}
|
|
39
39
|
case 'bigint':
|
|
40
40
|
case 'decimal':
|
|
41
|
+
case 'rate':
|
|
41
42
|
case 'time':
|
|
42
43
|
case 'date':
|
|
43
44
|
case 'datetime':
|
|
44
|
-
// Transmitted as string over HTTP: bigint/decimal keep full
|
|
45
|
-
// date/time serialize to string.
|
|
45
|
+
// Transmitted as string over HTTP: bigint/decimal/rate keep full
|
|
46
|
+
// precision, date/time serialize to string.
|
|
46
47
|
return def !== undefined ? `Type.String({ ${def} })` : 'Type.String()';
|
|
47
48
|
case 'boolean':
|
|
48
49
|
return def !== undefined ? `Type.Boolean({ ${def} })` : 'Type.Boolean()';
|
|
@@ -61,14 +62,32 @@ function renderBasic(field, pattern, defaultValue, resolver) {
|
|
|
61
62
|
}
|
|
62
63
|
return `Type.Enum(${ref.name})`;
|
|
63
64
|
}
|
|
65
|
+
case 'array':
|
|
66
|
+
return `Type.Array(${renderFieldValue(field.items, indent, resolver)})`;
|
|
67
|
+
case 'object':
|
|
68
|
+
return renderFieldObject(field.properties, indent + 1, resolver);
|
|
64
69
|
default:
|
|
65
70
|
// Field union is exhaustive; this branch is unreachable at runtime.
|
|
66
71
|
throw new Error(`unsupported field type: ${String(field.type)}`);
|
|
67
72
|
}
|
|
68
73
|
}
|
|
74
|
+
/** Render a plain Field value (wire-format nested fields), wrapping optional. */
|
|
75
|
+
function renderFieldValue(field, indent, resolver) {
|
|
76
|
+
const base = renderBasic(field, undefined, undefined, resolver, indent);
|
|
77
|
+
return field.optional ? `Type.Optional(${base})` : base;
|
|
78
|
+
}
|
|
79
|
+
/** Render a plain Field object (wire-format nested object). */
|
|
80
|
+
function renderFieldObject(properties, indent, resolver) {
|
|
81
|
+
const pad = ' '.repeat(indent);
|
|
82
|
+
const entries = Object.entries(properties).map(([name, f]) => `${pad}${name}: ${renderFieldValue(f, indent, resolver)}`);
|
|
83
|
+
return `Type.Object({\n${entries.join(',\n')}\n${' '.repeat(indent - 1)}})`;
|
|
84
|
+
}
|
|
69
85
|
function renderObject(fields, indent, resolver) {
|
|
70
86
|
const pad = ' '.repeat(indent);
|
|
71
|
-
const entries = Object.entries(fields).map(([name, f]) =>
|
|
87
|
+
const entries = Object.entries(fields).map(([name, f]) => {
|
|
88
|
+
const rendered = isDtoField(f) ? renderField(f, indent, resolver) : renderFieldValue(f, indent, resolver);
|
|
89
|
+
return `${pad}${name}: ${rendered}`;
|
|
90
|
+
});
|
|
72
91
|
return `Type.Object({\n${entries.join(',\n')}\n${' '.repeat(indent - 1)}})`;
|
|
73
92
|
}
|
|
74
93
|
function renderField(f, indent, resolver) {
|
|
@@ -81,7 +100,9 @@ function renderValue(f, indent, resolver) {
|
|
|
81
100
|
// Referenced DTO element — render by name (same-file export), not expanded.
|
|
82
101
|
if (isDtoMessage(items))
|
|
83
102
|
return `Type.Array(${items.name})`;
|
|
84
|
-
|
|
103
|
+
if (isDtoField(items))
|
|
104
|
+
return `Type.Array(${renderField(items, indent + 1, resolver)})`;
|
|
105
|
+
return `Type.Array(${renderFieldValue(items, indent, resolver)})`;
|
|
85
106
|
}
|
|
86
107
|
if (f.field.type === 'object') {
|
|
87
108
|
return renderObject(f.field.properties, indent + 1, resolver);
|
|
@@ -89,7 +110,7 @@ function renderValue(f, indent, resolver) {
|
|
|
89
110
|
// DtoField only wraps a database Field; array/object defs live in the subclasses.
|
|
90
111
|
// Only DTO-level defaults (setDefault) are emitted as TypeBox default
|
|
91
112
|
// annotations; DB field defaults are not carried into the API contract.
|
|
92
|
-
return renderBasic(f.field, f.pattern, f.default, resolver);
|
|
113
|
+
return renderBasic(f.field, f.pattern, f.default, resolver, indent);
|
|
93
114
|
}
|
|
94
115
|
/** Structural check — DtoMessage instances may come from a different module copy, so instanceof is unreliable. */
|
|
95
116
|
function isDtoMessage(v) {
|
|
@@ -97,24 +118,55 @@ function isDtoMessage(v) {
|
|
|
97
118
|
return false;
|
|
98
119
|
return v.type === 'dto';
|
|
99
120
|
}
|
|
121
|
+
/** Structural check — a DtoField wraps a Field in a .field property and has no .type of its own. */
|
|
122
|
+
function isDtoField(v) {
|
|
123
|
+
if (typeof v !== 'object' || v === null)
|
|
124
|
+
return false;
|
|
125
|
+
return 'field' in v && !('type' in v);
|
|
126
|
+
}
|
|
100
127
|
function collectEnumImports(f, resolver, out) {
|
|
101
128
|
if (f.field.type === 'array') {
|
|
102
129
|
const items = f.field.items;
|
|
103
|
-
if (
|
|
130
|
+
if (isDtoMessage(items))
|
|
131
|
+
return;
|
|
132
|
+
if (isDtoField(items)) {
|
|
104
133
|
collectEnumImports(items, resolver, out);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
collectFieldEnumImports(items, resolver, out);
|
|
105
137
|
return;
|
|
106
138
|
}
|
|
107
139
|
if (f.field.type === 'object') {
|
|
108
|
-
for (const child of Object.values(f.field.properties))
|
|
109
|
-
|
|
140
|
+
for (const child of Object.values(f.field.properties)) {
|
|
141
|
+
if (isDtoField(child))
|
|
142
|
+
collectEnumImports(child, resolver, out);
|
|
143
|
+
else
|
|
144
|
+
collectFieldEnumImports(child, resolver, out);
|
|
145
|
+
}
|
|
110
146
|
return;
|
|
111
147
|
}
|
|
112
|
-
if (f.field.type === 'enum')
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
148
|
+
if (f.field.type === 'enum')
|
|
149
|
+
collectEnumRef(f.field, resolver, out);
|
|
150
|
+
}
|
|
151
|
+
/** Enum import collection over a plain Field (wire-format nested fields). */
|
|
152
|
+
function collectFieldEnumImports(field, resolver, out) {
|
|
153
|
+
if (field.type === 'array') {
|
|
154
|
+
collectFieldEnumImports(field.items, resolver, out);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (field.type === 'object') {
|
|
158
|
+
for (const child of Object.values(field.properties))
|
|
159
|
+
collectFieldEnumImports(child, resolver, out);
|
|
160
|
+
return;
|
|
117
161
|
}
|
|
162
|
+
if (field.type === 'enum')
|
|
163
|
+
collectEnumRef(field, resolver, out);
|
|
164
|
+
}
|
|
165
|
+
function collectEnumRef(field, resolver, out) {
|
|
166
|
+
const ref = resolver?.(field.enum.jsName);
|
|
167
|
+
if (!ref)
|
|
168
|
+
throw new Error(`enum field ${field.name}: no import ref for ${field.enum.jsName} — pass an EnumResolver`);
|
|
169
|
+
out.set(`${ref.from}#${ref.name}`, ref);
|
|
118
170
|
}
|
|
119
171
|
/** Collect all imports needed to render a DTO: include() bases + enum references. */
|
|
120
172
|
export function collectDtoImports(schema, resolver, out) {
|
|
@@ -160,3 +212,13 @@ function renderBase(base) {
|
|
|
160
212
|
const args = base.args.map((a) => (typeof a === 'string' ? a : a.name));
|
|
161
213
|
return `${base.name}(${args.join(', ')})`;
|
|
162
214
|
}
|
|
215
|
+
/** Render one third-party method message export (const only — pair with
|
|
216
|
+
* renderDtoTypeExport for the Static type). */
|
|
217
|
+
export function renderThirdMethodExport(schema, resolver) {
|
|
218
|
+
return `export const ${schema.name} = ${renderFieldObject(schema.fields, 1, resolver)};`;
|
|
219
|
+
}
|
|
220
|
+
/** Collect all imports needed to render a third-party method message: enum references. */
|
|
221
|
+
export function collectThirdMethodImports(schema, resolver, out) {
|
|
222
|
+
for (const f of Object.values(schema.fields))
|
|
223
|
+
collectFieldEnumImports(f, resolver, out);
|
|
224
|
+
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import type { Field, SchemaBase } from './dsl.js';
|
|
2
|
+
import type { FrontAppSchema } from './project.js';
|
|
3
|
+
/** A utility method with a full signature. */
|
|
4
|
+
export interface UtilsMethodSchema extends SchemaBase {
|
|
5
|
+
type: 'utilsMethod';
|
|
6
|
+
/** The utility module this method belongs to. */
|
|
7
|
+
schema: UtilsSchema;
|
|
8
|
+
/** Input fields. */
|
|
9
|
+
args: Record<string, Field>;
|
|
10
|
+
/** Output field. */
|
|
11
|
+
result: Field;
|
|
12
|
+
}
|
|
13
|
+
/** Method input for defineUtils: type/schema/name are set by the builder. */
|
|
14
|
+
export type UtilsMethodDef = Omit<UtilsMethodSchema, 'type' | 'schema' | 'name'>;
|
|
15
|
+
/** A base utility module (e.g. DateTimeUtils). */
|
|
8
16
|
export interface UtilsSchema extends SchemaBase {
|
|
9
17
|
type: 'utils';
|
|
10
|
-
/**
|
|
11
|
-
app
|
|
18
|
+
/** Optional binding — empty means a shared public module. */
|
|
19
|
+
app?: FrontAppSchema;
|
|
20
|
+
/** Methods keyed by name — the map key is written back as the method name. */
|
|
21
|
+
methods: Record<string, UtilsMethodSchema>;
|
|
12
22
|
}
|
|
13
|
-
export declare function defineUtils(
|
|
23
|
+
export declare function defineUtils(options: {
|
|
24
|
+
name: string;
|
|
25
|
+
app?: FrontAppSchema;
|
|
26
|
+
methods: Record<string, UtilsMethodDef>;
|
|
27
|
+
description?: string;
|
|
28
|
+
}): UtilsSchema;
|
package/dist/utils.js
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
export function defineUtils(options) {
|
|
2
|
+
const schema = {
|
|
3
|
+
type: 'utils',
|
|
4
|
+
name: options.name,
|
|
5
|
+
description: options.description,
|
|
6
|
+
app: options.app,
|
|
7
|
+
methods: {},
|
|
8
|
+
};
|
|
9
|
+
for (const key of Object.keys(options.methods)) {
|
|
10
|
+
const method = options.methods[key];
|
|
11
|
+
const methodSchema = {
|
|
12
|
+
type: 'utilsMethod',
|
|
13
|
+
name: key,
|
|
14
|
+
description: method.description,
|
|
15
|
+
schema,
|
|
16
|
+
args: method.args,
|
|
17
|
+
result: method.result,
|
|
18
|
+
};
|
|
19
|
+
for (const argKey of Object.keys(methodSchema.args)) {
|
|
20
|
+
const field = methodSchema.args[argKey];
|
|
21
|
+
field.name = argKey;
|
|
22
|
+
field.schema = methodSchema;
|
|
23
|
+
}
|
|
24
|
+
methodSchema.result.name = key;
|
|
25
|
+
methodSchema.result.schema = methodSchema;
|
|
26
|
+
schema.methods[key] = methodSchema;
|
|
27
|
+
}
|
|
28
|
+
return schema;
|
|
12
29
|
}
|