@pylonts/dsl 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action.d.ts +7 -0
- package/dist/action.js +3 -0
- package/dist/asset.d.ts +2 -2
- package/dist/asset.js +2 -2
- package/dist/component.d.ts +20 -0
- package/dist/component.js +1 -0
- package/dist/convert.d.ts +9 -0
- package/dist/convert.js +3 -0
- package/dist/curd.d.ts +3 -1
- package/dist/db.d.ts +4 -0
- package/dist/db.js +9 -0
- package/dist/dsl.d.ts +5 -0
- package/dist/dto.d.ts +2 -2
- package/dist/dto.js +1 -1
- package/dist/event.d.ts +8 -0
- package/dist/event.js +3 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/mermaid-driver.js +2 -1
- package/dist/mock.d.ts +3 -21
- package/dist/mock.js +1 -18
- package/dist/mysql-driver.d.ts +4 -0
- package/dist/mysql-driver.js +8 -3
- package/dist/navigation.d.ts +22 -0
- package/dist/navigation.js +15 -0
- package/dist/page-def.d.ts +40 -0
- package/dist/page-def.js +38 -0
- package/dist/page-flow.d.ts +4 -2
- package/dist/page-flow.js +107 -12
- package/dist/page.d.ts +32 -10
- package/dist/page.js +20 -5
- package/dist/popup.d.ts +18 -0
- package/dist/popup.js +8 -0
- package/dist/project.d.ts +7 -0
- package/dist/provider.d.ts +54 -0
- package/dist/provider.js +18 -0
- package/dist/ref.d.ts +14 -0
- package/dist/ref.js +6 -0
- package/dist/route.d.ts +8 -0
- package/dist/route.js +3 -0
- package/docs/curd.md +110 -110
- package/docs/dto.md +66 -66
- package/docs/table.md +4 -2
- package/package.json +2 -2
- package/src/action.ts +11 -0
- package/src/asset.ts +63 -63
- package/src/bases.ts +29 -29
- package/src/component.ts +22 -0
- package/src/convert.ts +13 -0
- package/src/curd.ts +93 -91
- package/src/db.ts +11 -0
- package/src/dsl.ts +188 -182
- package/src/dto.ts +247 -247
- package/src/enum-driver.ts +42 -42
- package/src/event.ts +12 -0
- package/src/flow.ts +103 -103
- package/src/index.ts +31 -21
- package/src/mermaid-driver.ts +2 -1
- package/src/mock.ts +12 -45
- package/src/mysql-driver.ts +8 -2
- package/src/navigation.ts +29 -0
- package/src/page-def.ts +80 -0
- package/src/page-flow.ts +116 -14
- package/src/page.ts +51 -14
- package/src/patterns/retry.ts +54 -54
- package/src/popup.ts +25 -0
- package/src/project.ts +97 -90
- package/src/prototype.ts +29 -29
- package/src/provider.ts +73 -0
- package/src/ref.ts +19 -0
- package/src/route.ts +12 -0
- package/src/utils.ts +10 -10
package/dist/action.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { SchemaBase } from './dsl.js';
|
|
2
|
+
/** An action a user can perform on a page (e.g. submit, approve, reject).
|
|
3
|
+
* Subclasses use `type` as the discriminator. */
|
|
4
|
+
export interface ActionSchema extends SchemaBase {
|
|
5
|
+
type: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function defineAction(name: string, description?: string): ActionSchema;
|
package/dist/action.js
ADDED
package/dist/asset.d.ts
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* });
|
|
16
16
|
*
|
|
17
17
|
* // CLI:
|
|
18
|
-
* // gen
|
|
19
|
-
* // gen
|
|
18
|
+
* // pylonts gen asset list --tag form → all form-related assets
|
|
19
|
+
* // pylonts gen asset import formatAmt → import { formatAmt } from '@/utils/amount';
|
|
20
20
|
*/
|
|
21
21
|
export type AssetCategory = 'util' | 'component' | 'flow' | 'page' | 'hook';
|
|
22
22
|
export interface AssetImport {
|
package/dist/asset.js
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* });
|
|
16
16
|
*
|
|
17
17
|
* // CLI:
|
|
18
|
-
* // gen
|
|
19
|
-
* // gen
|
|
18
|
+
* // pylonts gen asset list --tag form → all form-related assets
|
|
19
|
+
* // pylonts gen asset import formatAmt → import { formatAmt } from '@/utils/amount';
|
|
20
20
|
*/
|
|
21
21
|
export function defineAsset(config) {
|
|
22
22
|
return {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SchemaBase } from './dsl.js';
|
|
2
|
+
import type { RefSchema } from './ref.js';
|
|
3
|
+
import type { ActionSchema } from './action.js';
|
|
4
|
+
import type { EventDataSchema } from './event.js';
|
|
5
|
+
/** A component event trigger declaration. */
|
|
6
|
+
export interface TriggerSchema extends SchemaBase {
|
|
7
|
+
/** Data the event carries (e.g. e.detail). */
|
|
8
|
+
eventData?: EventDataSchema;
|
|
9
|
+
/** Actions that fire when the event occurs. */
|
|
10
|
+
actions?: ActionSchema[];
|
|
11
|
+
}
|
|
12
|
+
/** A UI component declaration — a virtual schema that describes props and
|
|
13
|
+
* event triggers, not a real renderable component.
|
|
14
|
+
*
|
|
15
|
+
* properties: data bindings via RefSchema (or literal values).
|
|
16
|
+
* triggers: event name → TriggerSchema bindings. */
|
|
17
|
+
export interface ComponentSchema extends SchemaBase {
|
|
18
|
+
properties: Record<string, RefSchema | string | number | boolean>;
|
|
19
|
+
triggers: Record<string, TriggerSchema>;
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SchemaBase } from './dsl.js';
|
|
2
|
+
/** Declares post-call result → page data field mapping.
|
|
3
|
+
* Driver generates per-item transform (e.g. .map()) before setData. */
|
|
4
|
+
export interface ConvertSchema extends SchemaBase {
|
|
5
|
+
type: 'convert';
|
|
6
|
+
/** { targetField: sourceField } — renames or copies fields from call result. */
|
|
7
|
+
fields: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
export declare function defineConvert(name: string, fields: Record<string, string>): ConvertSchema;
|
package/dist/convert.js
ADDED
package/dist/curd.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { SchemaBase, Field, Operator } from './dsl.js';
|
|
2
2
|
import { TableSchema } from './db.js';
|
|
3
3
|
import { FrontAppSchema } from './project.js';
|
|
4
|
-
import { ActionSchema } from './
|
|
4
|
+
import { ActionSchema } from './action.js';
|
|
5
5
|
/** Mode of an action page: modal dialog or standalone route. */
|
|
6
6
|
export type ActionPageMode = 'modal' | 'route';
|
|
7
7
|
/** One CRUD action page (add / update / detail). */
|
|
8
8
|
export interface ActionPage {
|
|
9
9
|
mode: ActionPageMode;
|
|
10
|
+
/** For add/update: when true, render as modal on list page; when false/undefined, render as standalone route page. */
|
|
11
|
+
modal?: boolean;
|
|
10
12
|
/** Fields rendered on this page. Required, non-empty — every field the
|
|
11
13
|
* frontend shows must be listed explicitly. */
|
|
12
14
|
columns: Field[];
|
package/dist/db.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface TableSchemaOptions<N extends string = string, C extends Record<
|
|
|
15
15
|
actor?: boolean;
|
|
16
16
|
generator?: string;
|
|
17
17
|
autoIncrement?: Field;
|
|
18
|
+
/** 本表用于关联显示的名称字段(如 name / username)。被外键引用时,自动用该字段做 label 展示。 */
|
|
19
|
+
label?: Field;
|
|
18
20
|
primaryKey?: Field | Field[];
|
|
19
21
|
indexes?: Index[];
|
|
20
22
|
foreignKeys?: Record<string, ForeignKey>;
|
|
@@ -40,6 +42,8 @@ export declare class TableSchema<N extends string = string, C extends Record<str
|
|
|
40
42
|
indexes?: Index[];
|
|
41
43
|
/** 外键,引用其他表的字段 */
|
|
42
44
|
foreignKeys?: Record<string, ForeignKey>;
|
|
45
|
+
/** 本表用于关联显示的名称字段(如 name / username)。被外键引用时,自动用该字段做 label 展示。 */
|
|
46
|
+
label?: Field;
|
|
43
47
|
/** 引用的实体短语(词典条目):本表归属的实体;关联表等多实体场景不需要 */
|
|
44
48
|
phrase?: EntityPhrase;
|
|
45
49
|
/** 本表引用的所有枚举定义(map,key 为枚举标识),显式声明供 gen-enums 收集 */
|
package/dist/db.js
CHANGED
|
@@ -16,6 +16,8 @@ export class TableSchema {
|
|
|
16
16
|
indexes;
|
|
17
17
|
/** 外键,引用其他表的字段 */
|
|
18
18
|
foreignKeys;
|
|
19
|
+
/** 本表用于关联显示的名称字段(如 name / username)。被外键引用时,自动用该字段做 label 展示。 */
|
|
20
|
+
label;
|
|
19
21
|
/** 引用的实体短语(词典条目):本表归属的实体;关联表等多实体场景不需要 */
|
|
20
22
|
phrase;
|
|
21
23
|
/** 本表引用的所有枚举定义(map,key 为枚举标识),显式声明供 gen-enums 收集 */
|
|
@@ -34,6 +36,7 @@ export class TableSchema {
|
|
|
34
36
|
this.primaryKey = options.primaryKey;
|
|
35
37
|
this.indexes = options.indexes;
|
|
36
38
|
this.foreignKeys = options.foreignKeys;
|
|
39
|
+
this.label = options.label;
|
|
37
40
|
this.phrase = options.phrase;
|
|
38
41
|
this.enums = options.enums;
|
|
39
42
|
this.columns = options.columns;
|
|
@@ -49,6 +52,12 @@ export class TableSchema {
|
|
|
49
52
|
}
|
|
50
53
|
export function defineTable(name, schema) {
|
|
51
54
|
const table = new TableSchema(name, schema);
|
|
55
|
+
if (table.generator && table.autoIncrement) {
|
|
56
|
+
throw new Error(`table '${name}': generator and autoIncrement are mutually exclusive`);
|
|
57
|
+
}
|
|
58
|
+
if (table.label && !Object.values(table.columns).includes(table.label)) {
|
|
59
|
+
throw new Error(`table '${name}': label field '${table.label.name}' must be one of the table's columns`);
|
|
60
|
+
}
|
|
52
61
|
for (const key of Object.keys(table.columns)) {
|
|
53
62
|
const field = table.columns[key];
|
|
54
63
|
if (field.schema && field.schema !== table) {
|
package/dist/dsl.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ImportBase } from './import-base.js';
|
|
1
2
|
import type { MockDescriptor } from './mock.js';
|
|
2
3
|
/** Field query comparison operators (search field semantics). */
|
|
3
4
|
export type Operator = 'eq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'ne';
|
|
@@ -5,6 +6,10 @@ export interface SchemaBase {
|
|
|
5
6
|
name: string;
|
|
6
7
|
description?: string;
|
|
7
8
|
}
|
|
9
|
+
/** Schema base for cross-file entities. importRef locates the generating module; inline schemas omit it. */
|
|
10
|
+
export interface ImportableSchemaBase extends SchemaBase {
|
|
11
|
+
importRef?: ImportBase;
|
|
12
|
+
}
|
|
8
13
|
/** Field collection schemas (DB table vs DTO message); `type` is the discriminator */
|
|
9
14
|
export interface CollectionSchemaBase extends SchemaBase {
|
|
10
15
|
type: string;
|
package/dist/dto.d.ts
CHANGED
|
@@ -38,8 +38,8 @@ export declare class DtoField implements SchemaBase {
|
|
|
38
38
|
name: string;
|
|
39
39
|
/** 字段描述 */
|
|
40
40
|
description?: string;
|
|
41
|
-
/**
|
|
42
|
-
schema?:
|
|
41
|
+
/** 所属容器(buildMessage / defineRouteData / definePageData 反写) */
|
|
42
|
+
schema?: CollectionSchemaBase;
|
|
43
43
|
field: Field | DtoArrayFieldDef | DtoObjectFieldDef;
|
|
44
44
|
pattern?: string;
|
|
45
45
|
optional?: boolean;
|
package/dist/dto.js
CHANGED
package/dist/event.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CollectionSchemaBase } from './dsl.js';
|
|
2
|
+
import type { DtoField } from './dto.js';
|
|
3
|
+
/** Data carried by a component event (e.g. e.detail from a Tab onChange). */
|
|
4
|
+
export interface EventDataSchema extends CollectionSchemaBase {
|
|
5
|
+
type: 'event';
|
|
6
|
+
fields: Record<string, DtoField>;
|
|
7
|
+
}
|
|
8
|
+
export declare function defineEventData(name: string, fields: Record<string, DtoField>): EventDataSchema;
|
package/dist/event.js
ADDED
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,17 @@ export * from './typebox-driver.js';
|
|
|
15
15
|
export * from './pattern.js';
|
|
16
16
|
export * from './patterns/retry.js';
|
|
17
17
|
export * from './flow.js';
|
|
18
|
+
export * from './action.js';
|
|
19
|
+
export * from './event.js';
|
|
20
|
+
export * from './component.js';
|
|
21
|
+
export * from './convert.js';
|
|
22
|
+
export * from './ref.js';
|
|
23
|
+
export * from './route.js';
|
|
18
24
|
export * from './page.js';
|
|
19
25
|
export * from './curd.js';
|
|
20
26
|
export * from './page-flow.js';
|
|
27
|
+
export * from './provider.js';
|
|
28
|
+
export * from './page-def.js';
|
|
21
29
|
export * from './mermaid-driver.js';
|
|
30
|
+
export * from './navigation.js';
|
|
31
|
+
export * from './popup.js';
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,17 @@ export * from './typebox-driver.js';
|
|
|
15
15
|
export * from './pattern.js';
|
|
16
16
|
export * from './patterns/retry.js';
|
|
17
17
|
export * from './flow.js';
|
|
18
|
+
export * from './action.js';
|
|
19
|
+
export * from './event.js';
|
|
20
|
+
export * from './component.js';
|
|
21
|
+
export * from './convert.js';
|
|
22
|
+
export * from './ref.js';
|
|
23
|
+
export * from './route.js';
|
|
18
24
|
export * from './page.js';
|
|
19
25
|
export * from './curd.js';
|
|
20
26
|
export * from './page-flow.js';
|
|
27
|
+
export * from './provider.js';
|
|
28
|
+
export * from './page-def.js';
|
|
21
29
|
export * from './mermaid-driver.js';
|
|
30
|
+
export * from './navigation.js';
|
|
31
|
+
export * from './popup.js';
|
package/dist/mermaid-driver.js
CHANGED
|
@@ -56,7 +56,8 @@ export function renderPageFlowMermaid(schema) {
|
|
|
56
56
|
lines.push(` subgraph app${appIdx}["${escapeLabel(appName)}"]`);
|
|
57
57
|
pages.forEach((p, i) => ids.set(p, `app${appIdx}_p${i}`));
|
|
58
58
|
for (const p of pages) {
|
|
59
|
-
|
|
59
|
+
const display = p.label ?? p.name;
|
|
60
|
+
lines.push(` ${ids.get(p)}["${escapeLabel(display)}"]`);
|
|
60
61
|
}
|
|
61
62
|
appIdx++;
|
|
62
63
|
lines.push(' end');
|
package/dist/mock.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pure-data mock descriptor. Stored on DTO fields, consumed by
|
|
3
|
-
* or
|
|
2
|
+
* Pure-data mock descriptor. Stored on DTO fields, consumed by pylonts lint mock
|
|
3
|
+
* or MockRegistry.resolve. No function references — serializable and gen-readable.
|
|
4
4
|
*
|
|
5
5
|
* @example { fn: 'abc', count: 20 }
|
|
6
6
|
* @example { fn: 'amt', min: 10, max: 500 }
|
|
@@ -8,23 +8,5 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export interface MockDescriptor {
|
|
10
10
|
fn: string;
|
|
11
|
-
[
|
|
11
|
+
[args: string]: unknown;
|
|
12
12
|
}
|
|
13
|
-
/**
|
|
14
|
-
* Result of a descriptor factory call: pure-data descriptor + lazy generator.
|
|
15
|
-
* generate is non-enumerable — JSON.stringify / spread won't pick it up.
|
|
16
|
-
*/
|
|
17
|
-
export interface MockSchema {
|
|
18
|
-
descriptor: MockDescriptor;
|
|
19
|
-
generate: () => unknown;
|
|
20
|
-
}
|
|
21
|
-
type Params = Record<string, unknown>;
|
|
22
|
-
/**
|
|
23
|
-
* Define a mock descriptor factory.
|
|
24
|
-
*
|
|
25
|
-
* @param fn mock function name (for descriptor & dispatch)
|
|
26
|
-
* @param resolve converts descriptor params to a no-arg generator
|
|
27
|
-
* @returns (params?) => { descriptor, generate }
|
|
28
|
-
*/
|
|
29
|
-
export declare function defineMock(fn: string, resolve: (d: Params) => () => unknown): (params?: Params) => MockSchema;
|
|
30
|
-
export {};
|
package/dist/mock.js
CHANGED
|
@@ -1,18 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Define a mock descriptor factory.
|
|
3
|
-
*
|
|
4
|
-
* @param fn mock function name (for descriptor & dispatch)
|
|
5
|
-
* @param resolve converts descriptor params to a no-arg generator
|
|
6
|
-
* @returns (params?) => { descriptor, generate }
|
|
7
|
-
*/
|
|
8
|
-
export function defineMock(fn, resolve) {
|
|
9
|
-
return (params) => {
|
|
10
|
-
const d = { fn, ...params };
|
|
11
|
-
const result = { descriptor: d };
|
|
12
|
-
Object.defineProperty(result, 'generate', {
|
|
13
|
-
value: resolve(d),
|
|
14
|
-
enumerable: false,
|
|
15
|
-
});
|
|
16
|
-
return result;
|
|
17
|
-
};
|
|
18
|
-
}
|
|
1
|
+
export {};
|
package/dist/mysql-driver.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { TableSchema } from './db.js';
|
|
2
|
+
/** Default value constant for created_at columns. */
|
|
3
|
+
export declare const CURRENT_TIMESTAMP = "CURRENT_TIMESTAMP";
|
|
4
|
+
/** Default value constant for updated_at columns (with auto-update). */
|
|
5
|
+
export declare const CURRENT_TIMESTAMP_ON_UPDATE = "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP";
|
|
2
6
|
export interface BuildCreateTableSqlOptions {
|
|
3
7
|
/** 是否生成外键约束,默认 false(不生成) */
|
|
4
8
|
generateForeignKeys?: boolean;
|
package/dist/mysql-driver.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
// MySQL driver: converts a TableSchema into a CREATE TABLE statement.
|
|
2
|
+
/** Default value constant for created_at columns. */
|
|
3
|
+
export const CURRENT_TIMESTAMP = 'CURRENT_TIMESTAMP';
|
|
4
|
+
/** Default value constant for updated_at columns (with auto-update). */
|
|
5
|
+
export const CURRENT_TIMESTAMP_ON_UPDATE = 'CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP';
|
|
2
6
|
function columnType(field) {
|
|
3
7
|
switch (field.type) {
|
|
4
8
|
case 'string':
|
|
@@ -31,9 +35,10 @@ function columnType(field) {
|
|
|
31
35
|
function renderDefault(field) {
|
|
32
36
|
if (field.default === undefined)
|
|
33
37
|
return '';
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
// MySQL keyword expressions (CURRENT_TIMESTAMP, CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
39
|
+
// NULL, etc.) — render bare, no quotes.
|
|
40
|
+
if (/^[A-Z]/.test(field.default))
|
|
41
|
+
return ` DEFAULT ${field.default}`;
|
|
37
42
|
// Numeric columns take a bare literal, not a quoted one.
|
|
38
43
|
if (field.type === 'integer' || field.type === 'bigint' || field.type === 'decimal' || field.type === 'boolean') {
|
|
39
44
|
return ` DEFAULT ${field.default}`;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { RouteDataSchema } from './route.js';
|
|
2
|
+
import type { ActionSchema } from './action.js';
|
|
3
|
+
import { PageSchema } from './page.js';
|
|
4
|
+
/** Navigation primitives: front-end routing actions that are not provider calls. */
|
|
5
|
+
export interface NavigationAction extends ActionSchema {
|
|
6
|
+
type: 'navigation';
|
|
7
|
+
method: 'back' | 'push' | 'popup' | 'home';
|
|
8
|
+
/** Target page for push navigation. */
|
|
9
|
+
target?: PageSchema;
|
|
10
|
+
/** Route params type for push navigation. Auto-derived from target.params if omitted. */
|
|
11
|
+
params?: RouteDataSchema;
|
|
12
|
+
}
|
|
13
|
+
export declare const navigation: {
|
|
14
|
+
back(description?: string): NavigationAction;
|
|
15
|
+
push(options: {
|
|
16
|
+
target: PageSchema;
|
|
17
|
+
params?: RouteDataSchema;
|
|
18
|
+
description?: string;
|
|
19
|
+
}): NavigationAction;
|
|
20
|
+
popup(description?: string): NavigationAction;
|
|
21
|
+
home(description?: string): NavigationAction;
|
|
22
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const navigation = {
|
|
2
|
+
back(description) {
|
|
3
|
+
return { name: 'back', type: 'navigation', method: 'back', description };
|
|
4
|
+
},
|
|
5
|
+
push(options) {
|
|
6
|
+
const { target, params, description } = options;
|
|
7
|
+
return { name: 'push', type: 'navigation', method: 'push', target, params: params ?? target.params, description };
|
|
8
|
+
},
|
|
9
|
+
popup(description) {
|
|
10
|
+
return { name: 'popup', type: 'navigation', method: 'popup', description };
|
|
11
|
+
},
|
|
12
|
+
home(description) {
|
|
13
|
+
return { name: 'home', type: 'navigation', method: 'home', description };
|
|
14
|
+
},
|
|
15
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ImportableSchemaBase, CollectionSchemaBase, SchemaBase } from './dsl.js';
|
|
2
|
+
import { DtoField } from './dto.js';
|
|
3
|
+
import type { DtoMessage, DtoArrayField, DtoObjectField } from './dto.js';
|
|
4
|
+
import type { ComponentSchema } from './component.js';
|
|
5
|
+
import type { ActionSchema } from './action.js';
|
|
6
|
+
export type { RouteDataSchema } from './route.js';
|
|
7
|
+
export { defineRouteData } from './route.js';
|
|
8
|
+
/** Page lifecycle events that trigger data loading. */
|
|
9
|
+
export interface EventSchema extends SchemaBase {
|
|
10
|
+
type: 'onLoad' | 'onShow' | 'onHide' | 'onPullDownRefresh' | 'onReachBottom';
|
|
11
|
+
/** Actions that fire when this event occurs. */
|
|
12
|
+
actions?: ActionSchema[];
|
|
13
|
+
}
|
|
14
|
+
export declare const events: {
|
|
15
|
+
onLoad(actions?: ActionSchema[], description?: string): EventSchema;
|
|
16
|
+
onShow(actions?: ActionSchema[], description?: string): EventSchema;
|
|
17
|
+
onHide(actions?: ActionSchema[], description?: string): EventSchema;
|
|
18
|
+
onPullDownRefresh(actions?: ActionSchema[], description?: string): EventSchema;
|
|
19
|
+
onReachBottom(actions?: ActionSchema[], description?: string): EventSchema;
|
|
20
|
+
};
|
|
21
|
+
/** Page data: a named collection of fields that defines the page's data shape. */
|
|
22
|
+
export interface PageDataSchema extends CollectionSchemaBase {
|
|
23
|
+
type: 'pageData';
|
|
24
|
+
fields: Record<string, DtoField | DtoMessage | DtoArrayField | DtoObjectField>;
|
|
25
|
+
}
|
|
26
|
+
export declare function definePageData(fields: PageDataSchema['fields']): PageDataSchema;
|
|
27
|
+
/** Page skeleton definition. */
|
|
28
|
+
export interface PageDef extends ImportableSchemaBase {
|
|
29
|
+
data: PageDataSchema;
|
|
30
|
+
/** Lifecycle events that trigger data loading. */
|
|
31
|
+
events: EventSchema[];
|
|
32
|
+
/** Page actions (provider calls, navigation, etc.). */
|
|
33
|
+
actions: ActionSchema[];
|
|
34
|
+
/** UI component declarations that make up the page skeleton. */
|
|
35
|
+
components: ComponentSchema[];
|
|
36
|
+
/** Whether the driver should generate page-level loading / error / empty state wrappers. */
|
|
37
|
+
handleStates?: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** Define a page skeleton. */
|
|
40
|
+
export declare function definePageDef(name: string, schema: Omit<PageDef, 'name'>): PageDef;
|
package/dist/page-def.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { DtoField } from './dto.js';
|
|
2
|
+
export { defineRouteData } from './route.js';
|
|
3
|
+
export const events = {
|
|
4
|
+
onLoad(actions, description) {
|
|
5
|
+
return { name: 'onLoad', type: 'onLoad', actions, description };
|
|
6
|
+
},
|
|
7
|
+
onShow(actions, description) {
|
|
8
|
+
return { name: 'onShow', type: 'onShow', actions, description };
|
|
9
|
+
},
|
|
10
|
+
onHide(actions, description) {
|
|
11
|
+
return { name: 'onHide', type: 'onHide', actions, description };
|
|
12
|
+
},
|
|
13
|
+
onPullDownRefresh(actions, description) {
|
|
14
|
+
return { name: 'onPullDownRefresh', type: 'onPullDownRefresh', actions, description };
|
|
15
|
+
},
|
|
16
|
+
onReachBottom(actions, description) {
|
|
17
|
+
return { name: 'onReachBottom', type: 'onReachBottom', actions, description };
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
export function definePageData(fields) {
|
|
21
|
+
// Page data has no meaningful identity of its own; the page name already
|
|
22
|
+
// scopes it, so the schema name defaults to 'data'.
|
|
23
|
+
const data = { name: 'data', type: 'pageData', fields };
|
|
24
|
+
// Write back field names (same convention as buildMessage) so defineRef
|
|
25
|
+
// produces refs with a real field name instead of ''.
|
|
26
|
+
for (const key of Object.keys(data.fields)) {
|
|
27
|
+
const df = data.fields[key];
|
|
28
|
+
if (df instanceof DtoField) {
|
|
29
|
+
df.name = key;
|
|
30
|
+
df.schema = data;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return data;
|
|
34
|
+
}
|
|
35
|
+
/** Define a page skeleton. */
|
|
36
|
+
export function definePageDef(name, schema) {
|
|
37
|
+
return { name, ...schema };
|
|
38
|
+
}
|
package/dist/page-flow.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SchemaBase } from './dsl.js';
|
|
2
|
-
import { ActionSchema
|
|
2
|
+
import type { ActionSchema } from './action.js';
|
|
3
|
+
import { Page } from './page.js';
|
|
3
4
|
export interface PageEdge extends SchemaBase {
|
|
4
5
|
/** Trigger action; undefined = default path (success/normal). */
|
|
5
6
|
when?: ActionSchema;
|
|
@@ -9,13 +10,14 @@ export interface PageEdge extends SchemaBase {
|
|
|
9
10
|
export interface PageFlow extends SchemaBase {
|
|
10
11
|
/** Entry page. */
|
|
11
12
|
start: Page;
|
|
12
|
-
/** All pages
|
|
13
|
+
/** All pages explicitly declared by the caller. */
|
|
13
14
|
pages: Page[];
|
|
14
15
|
edges: PageEdge[];
|
|
15
16
|
}
|
|
16
17
|
export declare function pageEdge(start: Page, end: Page, when?: ActionSchema, description?: string): PageEdge;
|
|
17
18
|
export declare function definePageFlow(name: string, schema: {
|
|
18
19
|
start: Page;
|
|
20
|
+
pages: Page[];
|
|
19
21
|
edges: PageEdge[];
|
|
20
22
|
description?: string;
|
|
21
23
|
}): PageFlow;
|
package/dist/page-flow.js
CHANGED
|
@@ -1,21 +1,116 @@
|
|
|
1
|
+
import { getRegisteredPages, clearRegisteredPages } from './page.js';
|
|
1
2
|
export function pageEdge(start, end, when, description) {
|
|
2
3
|
// Auto name for uniformity with SchemaBase; `when` stays the branch marker.
|
|
3
4
|
return { name: `${start.name}->${end.name}`, start, end, when, description };
|
|
4
5
|
}
|
|
5
6
|
export function definePageFlow(name, schema) {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
const { start, pages, edges: explicitEdges, description } = schema;
|
|
8
|
+
// Resolve the back action from any page's actions list (PageSchema at runtime)
|
|
9
|
+
const backAction = findBackAction(pages);
|
|
10
|
+
// Auto-generate back edges: for every explicit edge A→B (not itself a back),
|
|
11
|
+
// if B has no explicit back edge and no existing edge B→A, add B→A with back.
|
|
12
|
+
const hasExplicitBack = new Set();
|
|
13
|
+
for (const e of explicitEdges) {
|
|
14
|
+
if (e.when && e.when.name === 'back')
|
|
15
|
+
hasExplicitBack.add(e.start);
|
|
16
|
+
}
|
|
17
|
+
const hasReverse = new Set();
|
|
18
|
+
for (const e of explicitEdges) {
|
|
19
|
+
hasReverse.add(`${e.start.name}->${e.end.name}`);
|
|
20
|
+
}
|
|
21
|
+
const generatedBackEdges = [];
|
|
22
|
+
if (backAction) {
|
|
23
|
+
for (const e of explicitEdges) {
|
|
24
|
+
if (e.when && e.when.name === 'back')
|
|
25
|
+
continue;
|
|
26
|
+
if (hasExplicitBack.has(e.end))
|
|
27
|
+
continue;
|
|
28
|
+
if (hasReverse.has(`${e.end.name}->${e.start.name}`))
|
|
29
|
+
continue;
|
|
30
|
+
generatedBackEdges.push({
|
|
31
|
+
name: `${e.end.name}->${e.start.name}`,
|
|
32
|
+
start: e.end,
|
|
33
|
+
end: e.start,
|
|
34
|
+
when: backAction,
|
|
35
|
+
description: 'auto-generated back edge',
|
|
36
|
+
});
|
|
37
|
+
hasExplicitBack.add(e.end);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const edges = [...explicitEdges, ...generatedBackEdges];
|
|
41
|
+
// Validate: every registered page must be in the flow's pages
|
|
42
|
+
const registered = getRegisteredPages();
|
|
43
|
+
const orphaned = [];
|
|
44
|
+
for (const rp of registered) {
|
|
45
|
+
// Compare by object identity — caller passes the same reference
|
|
46
|
+
if (!pages.includes(rp)) {
|
|
47
|
+
orphaned.push(rp.name);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (orphaned.length > 0) {
|
|
51
|
+
throw new Error(`[definePageFlow "${name}"] orphaned pages (defined but not in flow): ${orphaned.join(', ')}`);
|
|
52
|
+
}
|
|
53
|
+
clearRegisteredPages();
|
|
54
|
+
// Validate: start must be in pages
|
|
55
|
+
if (!pages.includes(start)) {
|
|
56
|
+
throw new Error(`[definePageFlow "${name}"] start page "${start.name}" not in pages`);
|
|
57
|
+
}
|
|
58
|
+
// Validate: every page appears in at least one edge
|
|
59
|
+
const inEdge = new Set();
|
|
60
|
+
for (const e of edges) {
|
|
61
|
+
inEdge.add(e.start);
|
|
62
|
+
inEdge.add(e.end);
|
|
63
|
+
}
|
|
64
|
+
const isolated = [];
|
|
65
|
+
for (const p of pages) {
|
|
66
|
+
if (!inEdge.has(p))
|
|
67
|
+
isolated.push(p.name);
|
|
68
|
+
}
|
|
69
|
+
if (isolated.length > 0) {
|
|
70
|
+
throw new Error(`[definePageFlow "${name}"] isolated pages (no edges): ${isolated.join(', ')}`);
|
|
71
|
+
}
|
|
72
|
+
// Validate: all pages reachable from start
|
|
73
|
+
const adj = new Map();
|
|
74
|
+
for (const p of pages)
|
|
75
|
+
adj.set(p, []);
|
|
76
|
+
for (const e of edges) {
|
|
77
|
+
const neighbors = adj.get(e.start);
|
|
78
|
+
if (neighbors)
|
|
79
|
+
neighbors.push(e.end);
|
|
80
|
+
}
|
|
81
|
+
const visited = new Set();
|
|
82
|
+
const stack = [start];
|
|
83
|
+
while (stack.length > 0) {
|
|
84
|
+
const cur = stack.pop();
|
|
85
|
+
if (visited.has(cur))
|
|
86
|
+
continue;
|
|
87
|
+
visited.add(cur);
|
|
88
|
+
for (const next of adj.get(cur) ?? []) {
|
|
89
|
+
if (!visited.has(next))
|
|
90
|
+
stack.push(next);
|
|
14
91
|
}
|
|
15
92
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
93
|
+
const unreachable = [];
|
|
94
|
+
for (const p of pages) {
|
|
95
|
+
if (!visited.has(p))
|
|
96
|
+
unreachable.push(p.name);
|
|
97
|
+
}
|
|
98
|
+
if (unreachable.length > 0) {
|
|
99
|
+
throw new Error(`[definePageFlow "${name}"] unreachable pages: ${unreachable.join(', ')}`);
|
|
100
|
+
}
|
|
101
|
+
return { name, description, start, pages, edges };
|
|
102
|
+
}
|
|
103
|
+
/** Walk all pages' pageDef.actions (PageSchema runtime shape) to find a NavigationAction with method 'back'. */
|
|
104
|
+
function findBackAction(pages) {
|
|
105
|
+
for (const p of pages) {
|
|
106
|
+
const pageDef = p.pageDef;
|
|
107
|
+
if (!pageDef?.actions)
|
|
108
|
+
continue;
|
|
109
|
+
for (const a of pageDef.actions) {
|
|
110
|
+
const na = a;
|
|
111
|
+
if (na.type === 'navigation' && na.method === 'back')
|
|
112
|
+
return na;
|
|
113
|
+
}
|
|
19
114
|
}
|
|
20
|
-
return
|
|
115
|
+
return undefined;
|
|
21
116
|
}
|