@pylonts/dsl 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/action.d.ts +7 -0
  2. package/dist/action.js +3 -0
  3. package/dist/asset.d.ts +2 -2
  4. package/dist/asset.js +2 -2
  5. package/dist/component.d.ts +20 -0
  6. package/dist/component.js +1 -0
  7. package/dist/convert.d.ts +9 -0
  8. package/dist/convert.js +3 -0
  9. package/dist/curd.d.ts +3 -1
  10. package/dist/db.d.ts +4 -0
  11. package/dist/db.js +29 -0
  12. package/dist/dsl.d.ts +5 -0
  13. package/dist/dto.d.ts +2 -2
  14. package/dist/dto.js +1 -1
  15. package/dist/event.d.ts +8 -0
  16. package/dist/event.js +3 -0
  17. package/dist/index.d.ts +10 -0
  18. package/dist/index.js +10 -0
  19. package/dist/mermaid-driver.js +2 -1
  20. package/dist/mock.d.ts +3 -21
  21. package/dist/mock.js +1 -18
  22. package/dist/mysql-driver.d.ts +4 -0
  23. package/dist/mysql-driver.js +8 -3
  24. package/dist/navigation.d.ts +22 -0
  25. package/dist/navigation.js +15 -0
  26. package/dist/page-def.d.ts +40 -0
  27. package/dist/page-def.js +38 -0
  28. package/dist/page-flow.d.ts +4 -2
  29. package/dist/page-flow.js +107 -12
  30. package/dist/page.d.ts +32 -10
  31. package/dist/page.js +20 -5
  32. package/dist/popup.d.ts +18 -0
  33. package/dist/popup.js +8 -0
  34. package/dist/project.d.ts +7 -0
  35. package/dist/provider.d.ts +54 -0
  36. package/dist/provider.js +18 -0
  37. package/dist/ref.d.ts +14 -0
  38. package/dist/ref.js +6 -0
  39. package/dist/route.d.ts +8 -0
  40. package/dist/route.js +3 -0
  41. package/docs/curd.md +110 -110
  42. package/docs/dto.md +66 -66
  43. package/docs/table.md +4 -2
  44. package/package.json +1 -1
  45. package/src/action.ts +11 -0
  46. package/src/asset.ts +63 -63
  47. package/src/bases.ts +29 -29
  48. package/src/component.ts +22 -0
  49. package/src/convert.ts +13 -0
  50. package/src/curd.ts +93 -91
  51. package/src/db.ts +37 -0
  52. package/src/dsl.ts +188 -182
  53. package/src/dto.ts +247 -247
  54. package/src/enum-driver.ts +42 -42
  55. package/src/event.ts +12 -0
  56. package/src/flow.ts +103 -103
  57. package/src/index.ts +31 -21
  58. package/src/mermaid-driver.ts +2 -1
  59. package/src/mock.ts +12 -45
  60. package/src/mysql-driver.ts +8 -2
  61. package/src/navigation.ts +29 -0
  62. package/src/page-def.ts +80 -0
  63. package/src/page-flow.ts +116 -14
  64. package/src/page.ts +51 -14
  65. package/src/patterns/retry.ts +54 -54
  66. package/src/popup.ts +25 -0
  67. package/src/project.ts +97 -90
  68. package/src/prototype.ts +29 -29
  69. package/src/provider.ts +73 -0
  70. package/src/ref.ts +19 -0
  71. package/src/route.ts +12 -0
  72. package/src/utils.ts +10 -10
package/src/page.ts CHANGED
@@ -1,40 +1,77 @@
1
1
  import { SchemaBase } from './dsl.js';
2
2
  import { FrontAppSchema } from './project.js';
3
+ import type { PageDef } from './page-def.js';
4
+ import type { RouteDataSchema } from './route.js';
3
5
 
4
6
  // Page definitions: standalone page schemas and the page node type used by
5
7
  // page-driven flows. Kept separate from page-flow.ts (the flow graph itself).
6
8
 
7
- /** Standalone page definition. A page is a shared value object: it lists
8
- * the actions a user can perform, and belongs to exactly one frontend app. */
9
+ // Module-level registry: every definePage/defineTabPage call registers its
10
+ // result so definePageFlow can check that no page is left out.
11
+ const _pageRegistry = new Set<PageSchema>();
12
+
13
+ export function getRegisteredPages(): ReadonlySet<PageSchema> {
14
+ return _pageRegistry;
15
+ }
16
+
17
+ export function clearRegisteredPages(): void {
18
+ _pageRegistry.clear();
19
+ }
20
+
21
+ /** Standalone page definition. A page is a shared value object: it belongs to
22
+ * exactly one frontend app. Actions live in the page skeleton (PageDef.actions),
23
+ * not on the page itself — the page only carries what the flow/topology needs. */
9
24
  export interface PageSchema extends SchemaBase {
25
+ /** Short display name for topology diagrams. */
26
+ label: string;
10
27
  /** The frontend app this page belongs to (shared instance from project.config). */
11
28
  app: FrontAppSchema;
12
- /** Actions a user can perform on this page (e.g. submit, approve, reject). */
13
- actions: ActionSchema[];
29
+ /** Full page skeleton definition (optional). */
30
+ pageDef?: PageDef;
31
+ /** Route params this page expects (e.g. detail page expects productId). */
32
+ params?: RouteDataSchema;
14
33
  }
15
34
 
16
- /** An action a user can perform on a page (e.g. submit, approve, reject). */
17
- export interface ActionSchema extends SchemaBase {}
35
+ export function definePage(schema: {
36
+ name: string;
37
+ label: string;
38
+ description?: string;
39
+ app: FrontAppSchema;
40
+ pageDef?: PageDef;
41
+ params?: RouteDataSchema;
42
+ }): PageSchema {
43
+ const p: PageSchema = { ...schema };
44
+ _pageRegistry.add(p);
45
+ return p;
46
+ }
18
47
 
19
- export function defineAction(name: string, description?: string): ActionSchema {
20
- return { name, description };
48
+ /** Page with bottom tab navigation. tabs collects the child pages reachable via tab switch. */
49
+ export interface TabPageSchema extends PageSchema {
50
+ tabs: PageSchema[];
21
51
  }
22
52
 
23
- export function definePage(schema: {
53
+ export function defineTabPage(schema: {
24
54
  name: string;
55
+ label: string;
25
56
  description?: string;
26
57
  app: FrontAppSchema;
27
- actions: ActionSchema[];
28
- }): PageSchema {
29
- return { ...schema };
58
+ tabs: PageSchema[];
59
+ pageDef?: PageDef;
60
+ params?: RouteDataSchema;
61
+ }): TabPageSchema {
62
+ const p: TabPageSchema = { ...schema };
63
+ _pageRegistry.add(p);
64
+ return p;
30
65
  }
31
66
 
32
67
  /** A page node in a page-driven flow: every node is a page, and a page belongs to an app. */
33
68
  export interface Page extends SchemaBase {
69
+ /** Short display name for topology diagrams. */
70
+ label?: string;
34
71
  /** The frontend app this page belongs to (shared instance from project.config). */
35
72
  app: FrontAppSchema;
36
73
  }
37
74
 
38
- export function page(app: FrontAppSchema, name: string, description?: string): Page {
39
- return { name, app, description };
75
+ export function page(app: FrontAppSchema, name: string, description?: string, label?: string): Page {
76
+ return { name, label, app, description };
40
77
  }
@@ -1,55 +1,55 @@
1
- import { PatternDef } from '../pattern.js';
2
-
3
- // Retry pattern: blind mechanical retry for read-only operations.
4
- // Valid because a read-only action is idempotent by nature: clicking a query
5
- // button any number of times never changes the result, so retrying the same
6
- // call is always safe. No idempotency key, no query-and-resume, no compensate.
7
-
8
- export const retryPattern: PatternDef = {
9
- name: 'retry',
10
- params: {
11
- max: { type: 'int', min: 1, default: 3 },
12
- backoffMs: { type: 'int', min: 0, default: 0 },
13
- },
14
- };
15
-
16
- export interface RetryAction {
17
- /** Function to call, e.g. 'queryOrderList' */
18
- call: string;
19
- /** Type name of the single argument passed through, e.g. 'OrderQueryParams' */
20
- params?: string;
21
- }
22
-
23
- export interface RetryRefArgs {
24
- max?: number;
25
- backoffMs?: number;
26
- action: RetryAction;
27
- /** Generated function name; defaults to '<call>WithRetry' */
28
- fnName?: string;
29
- }
30
-
31
- export function renderRetry(args: RetryRefArgs): string {
32
- if (args.max !== undefined && args.max < 1) throw new Error('retry: max must be >= 1');
33
- if (!args.action.call) throw new Error('retry: action.call is required');
34
-
35
- const max = args.max ?? 3;
36
- const backoffMs = args.backoffMs ?? 0;
37
- const call = args.action.call;
38
- const paramType = args.action.params ?? 'unknown';
39
- const fnName = args.fnName ?? `${call}WithRetry`;
40
- const retryLine = backoffMs > 0 ? ` await sleep(${backoffMs});` : '';
41
-
42
- return [
43
- `export async function ${fnName}(params: ${paramType}) {`,
44
- ` for (let attempt = 1; ; attempt++) {`,
45
- ` try {`,
46
- ` return await ${call}(params);`,
47
- ` } catch (err) {`,
48
- ` if (attempt >= ${max}) throw err;`,
49
- retryLine,
50
- ` }`,
51
- ` }`,
52
- `}`,
53
- '',
54
- ].join('\n');
1
+ import { PatternDef } from '../pattern.js';
2
+
3
+ // Retry pattern: blind mechanical retry for read-only operations.
4
+ // Valid because a read-only action is idempotent by nature: clicking a query
5
+ // button any number of times never changes the result, so retrying the same
6
+ // call is always safe. No idempotency key, no query-and-resume, no compensate.
7
+
8
+ export const retryPattern: PatternDef = {
9
+ name: 'retry',
10
+ params: {
11
+ max: { type: 'int', min: 1, default: 3 },
12
+ backoffMs: { type: 'int', min: 0, default: 0 },
13
+ },
14
+ };
15
+
16
+ export interface RetryAction {
17
+ /** Function to call, e.g. 'queryOrderList' */
18
+ call: string;
19
+ /** Type name of the single argument passed through, e.g. 'OrderQueryParams' */
20
+ params?: string;
21
+ }
22
+
23
+ export interface RetryRefArgs {
24
+ max?: number;
25
+ backoffMs?: number;
26
+ action: RetryAction;
27
+ /** Generated function name; defaults to '<call>WithRetry' */
28
+ fnName?: string;
29
+ }
30
+
31
+ export function renderRetry(args: RetryRefArgs): string {
32
+ if (args.max !== undefined && args.max < 1) throw new Error('retry: max must be >= 1');
33
+ if (!args.action.call) throw new Error('retry: action.call is required');
34
+
35
+ const max = args.max ?? 3;
36
+ const backoffMs = args.backoffMs ?? 0;
37
+ const call = args.action.call;
38
+ const paramType = args.action.params ?? 'unknown';
39
+ const fnName = args.fnName ?? `${call}WithRetry`;
40
+ const retryLine = backoffMs > 0 ? ` await sleep(${backoffMs});` : '';
41
+
42
+ return [
43
+ `export async function ${fnName}(params: ${paramType}) {`,
44
+ ` for (let attempt = 1; ; attempt++) {`,
45
+ ` try {`,
46
+ ` return await ${call}(params);`,
47
+ ` } catch (err) {`,
48
+ ` if (attempt >= ${max}) throw err;`,
49
+ retryLine,
50
+ ` }`,
51
+ ` }`,
52
+ `}`,
53
+ '',
54
+ ].join('\n');
55
55
  }
package/src/popup.ts ADDED
@@ -0,0 +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
+ };
package/src/project.ts CHANGED
@@ -1,91 +1,98 @@
1
- import { SchemaBase } from './dsl.js';
2
-
3
- // Project topology definitions: describe the applications (frontends) and
4
- // backend APIs of a repository, and which frontends each API serves.
5
-
6
- /** Frontend form factor. Closed enum, extend when new form factors appear. */
7
- export type FrontType = 'admin' | 'wxmini';
8
-
9
- /** A frontend application (e.g. admin console, wechat mini program). */
10
- export interface FrontAppSchema extends SchemaBase {
11
- type: FrontType;
12
- /** Source directory relative to project root, e.g. 'web-admin/'. */
13
- dir: string;
14
- }
15
-
16
- /** A backend API service. apps references shared FrontAppSchema instances. */
17
- export interface ProjectApiSchema extends SchemaBase {
18
- /** Source directory relative to project root, e.g. 'api/'. */
19
- dir: string;
20
- /** Frontends this API serves. Direct instance references (see defineProject). */
21
- apps: FrontAppSchema[];
22
- /** API base URL prefix shared by all apps it serves, e.g. '/mall'. '' = no prefix. */
23
- contextPath?: string;
24
- /** API service base URL for node clients, e.g. 'http://127.0.0.1:3000'. */
25
- baseUrl?: string;
26
- }
27
-
28
- /** A third-party system (e.g. wechat pay, unionpay). Owns its own
29
- * implementation dir and contract (controller_types), just like an API,
30
- * but is not part of this repo's served surface. */
31
- export interface ThirdApiSchema extends SchemaBase {
32
- /** Source directory relative to project root, e.g. 'wechat/'. */
33
- dir: string;
34
- }
35
-
36
- export interface ProjectSchema extends SchemaBase {
37
- apps: FrontAppSchema[];
38
- apis: ProjectApiSchema[];
39
- thirdApis: ThirdApiSchema[];
40
- }
41
-
42
- /**
43
- * Defines the project topology. FrontAppSchema instances are shared value objects:
44
- * api.apps references the same instances from project.apps, so an app served
45
- * by multiple APIs is defined once and referenced many times.
46
- *
47
- * Runtime-validates app type whitelist, unique names and api.apps reference
48
- * integrity (same style as defineTable/defineCurd).
49
- */
50
- export function defineProject(
51
- name: string,
52
- schema: {
53
- description?: string;
54
- apps: FrontAppSchema[];
55
- apis: ProjectApiSchema[];
56
- thirdApis?: ThirdApiSchema[];
57
- },
58
- ): ProjectSchema {
59
- const project: ProjectSchema = { name, ...schema, thirdApis: schema.thirdApis ?? [] };
60
-
61
- const appNames = new Set<string>();
62
- for (const app of project.apps) {
63
- if (!app.name) throw new Error(`project ${name}: app name is required`);
64
- if (appNames.has(app.name)) throw new Error(`project ${name}: duplicate app name '${app.name}'`);
65
- appNames.add(app.name);
66
- if (app.type !== 'admin' && app.type !== 'wxmini') {
67
- throw new Error(`project ${name}: app '${app.name}' must be type 'admin' or 'wxmini' (got '${app.type}')`);
68
- }
69
- if (!app.dir) throw new Error(`project ${name}: app '${app.name}' dir is required`);
70
- }
71
-
72
- const apiNames = new Set<string>();
73
- for (const api of project.apis) {
74
- if (!api.name) throw new Error(`project ${name}: api name is required`);
75
- if (apiNames.has(api.name)) throw new Error(`project ${name}: duplicate api name '${api.name}'`);
76
- apiNames.add(api.name);
77
- if (!api.dir) throw new Error(`project ${name}: api '${api.name}' dir is required`);
78
- for (const ref of api.apps) {
79
- if (!project.apps.includes(ref)) {
80
- 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)`);
81
- }
82
- }
83
- }
84
-
85
- for (const third of project.thirdApis) {
86
- if (!third.name) throw new Error(`project ${name}: thirdApi name is required`);
87
- if (!third.dir) throw new Error(`project ${name}: thirdApi '${third.name}' dir is required`);
88
- }
89
-
90
- 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;
91
98
  }
package/src/prototype.ts CHANGED
@@ -1,30 +1,30 @@
1
- import { SchemaBase } from './dsl.js';
2
-
3
- // Prototype definitions: high-level design of a page. A prototype lists only
4
- // the fields a page needs — no types, no bindings to apps/APIs/tables. Field
5
- // details (DTO/table definitions) are written separately and connected later.
6
-
7
- /** Display metadata for a prototype field. */
8
- export interface PrototypeFieldMeta {
9
- label: string;
10
- description?: string;
11
- }
12
-
13
- export interface PrototypeSchema extends SchemaBase {
14
- /** Field requirements: key is the field name, value is display metadata. */
15
- fields: Record<string, PrototypeFieldMeta>;
16
- }
17
-
18
- /**
19
- * Defines a page prototype. The field keys become the names referenced by
20
- * later DTO/table definitions; here they only carry label/description.
21
- */
22
- export function definePrototype(
23
- name: string,
24
- schema: {
25
- description?: string;
26
- fields: Record<string, PrototypeFieldMeta>;
27
- },
28
- ): PrototypeSchema {
29
- return { name, ...schema };
1
+ import { SchemaBase } from './dsl.js';
2
+
3
+ // Prototype definitions: high-level design of a page. A prototype lists only
4
+ // the fields a page needs — no types, no bindings to apps/APIs/tables. Field
5
+ // details (DTO/table definitions) are written separately and connected later.
6
+
7
+ /** Display metadata for a prototype field. */
8
+ export interface PrototypeFieldMeta {
9
+ label: string;
10
+ description?: string;
11
+ }
12
+
13
+ export interface PrototypeSchema extends SchemaBase {
14
+ /** Field requirements: key is the field name, value is display metadata. */
15
+ fields: Record<string, PrototypeFieldMeta>;
16
+ }
17
+
18
+ /**
19
+ * Defines a page prototype. The field keys become the names referenced by
20
+ * later DTO/table definitions; here they only carry label/description.
21
+ */
22
+ export function definePrototype(
23
+ name: string,
24
+ schema: {
25
+ description?: string;
26
+ fields: Record<string, PrototypeFieldMeta>;
27
+ },
28
+ ): PrototypeSchema {
29
+ return { name, ...schema };
30
30
  }
@@ -0,0 +1,73 @@
1
+ import type { ImportBase } from './import-base.js';
2
+ import type { ImportableSchemaBase } from './dsl.js';
3
+ import type { DtoField, DtoMessage, DtoArrayField, DtoObjectField } from './dto.js';
4
+ import type { ActionSchema } from './action.js';
5
+ import type { RefSchema } from './ref.js';
6
+ import type { ConvertSchema } from './convert.js';
7
+
8
+ // ProviderSchema: an API function signature (args DTO + results DTO).
9
+
10
+ /** Parameter data source for a call argument. */
11
+ export type DataRef =
12
+ | { type: 'route'; key: string }
13
+ | { type: 'data'; key: string }
14
+ | { type: 'value'; value: unknown };
15
+
16
+ /** Create a route-parameter reference. */
17
+ export function route(key: string): DataRef {
18
+ return { type: 'route', key };
19
+ }
20
+
21
+ /** Create a page-data reference. */
22
+ export function data(key: string): DataRef {
23
+ return { type: 'data', key };
24
+ }
25
+
26
+ export interface CallAction extends ActionSchema {
27
+ type: 'call';
28
+ func: ProviderSchema;
29
+ args?: Record<string, DtoField | DtoMessage | DtoArrayField | DtoObjectField | RefSchema | string>;
30
+ }
31
+
32
+
33
+ export function call(func: ProviderSchema, args?: Record<string, DtoField | DtoMessage | DtoArrayField | DtoObjectField | RefSchema | string>): CallAction {
34
+ return { name: func.name, type: 'call', func, args };
35
+ }
36
+
37
+ /** Provider function signature. The generated API client exposes one function
38
+ * per endpoint; ProviderSchema gives that function a name and types. */
39
+ export interface ProviderSchema extends ImportableSchemaBase {
40
+ isAsync: boolean;
41
+ /** Input DTO (buildInput / buildQuery / buildPk result). */
42
+ args: DtoMessage;
43
+ /** Output DTO (buildOutput result) or primitive. */
44
+ results: DtoMessage | number | boolean | string;
45
+ }
46
+
47
+ /** Define a provider function. */
48
+ export function defineProvider(
49
+ name: string,
50
+ schema: {
51
+ isAsync: boolean;
52
+ args: DtoMessage;
53
+ results: DtoMessage | number | boolean | string;
54
+ description?: string;
55
+ importRef?: ImportBase;
56
+ },
57
+ ): ProviderSchema {
58
+ return { name, ...schema };
59
+ }
60
+
61
+ /** Assign a call's result to a page data field.
62
+ * React: setState({ [field]: await ... }). Mini-program: this.setData({ [field]: ... }). */
63
+ export interface SetDataAction extends ActionSchema {
64
+ type: 'setData';
65
+ call: CallAction;
66
+ field: DtoField;
67
+ /** Optional field-level transform before assignment. */
68
+ convert?: ConvertSchema;
69
+ }
70
+
71
+ export function setData(call: CallAction, field: DtoField, convert?: ConvertSchema): SetDataAction {
72
+ return { name: 'setData', type: 'setData', call, field, convert };
73
+ }
package/src/ref.ts ADDED
@@ -0,0 +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 };
19
+ }
package/src/route.ts ADDED
@@ -0,0 +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 };
12
+ }
package/src/utils.ts CHANGED
@@ -1,11 +1,11 @@
1
- // ── naming conversions ──
2
-
3
- /** snake_case → camelCase: mer_id → merId; names without underscores are unchanged */
4
- export function toCamelCase(name: string): string {
5
- return name.replace(/_([a-z])/g, (_, c: string) => c.toUpperCase());
6
- }
7
-
8
- /** snake_case → PascalCase: mer_id → MerId */
9
- export function toPascalCase(snake: string): string {
10
- return snake.replace(/(^|_)([a-z])/g, (_m, _p, c: string) => c.toUpperCase());
1
+ // ── naming conversions ──
2
+
3
+ /** snake_case → camelCase: mer_id → merId; names without underscores are unchanged */
4
+ export function toCamelCase(name: string): string {
5
+ return name.replace(/_([a-z])/g, (_, c: string) => c.toUpperCase());
6
+ }
7
+
8
+ /** snake_case → PascalCase: mer_id → MerId */
9
+ export function toPascalCase(snake: string): string {
10
+ return snake.replace(/(^|_)([a-z])/g, (_m, _p, c: string) => c.toUpperCase());
11
11
  }