@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.
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 +9 -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 +2 -2
  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 +11 -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pylonts/dsl",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Schema definition DSL with drivers: MySQL DDL, TS enum, TypeBox schema codegen.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -28,4 +28,4 @@
28
28
  "typescript": "^7.0.2",
29
29
  "vitest": "^4.1.10"
30
30
  }
31
- }
31
+ }
package/src/action.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { SchemaBase } from './dsl.js';
2
+
3
+ /** An action a user can perform on a page (e.g. submit, approve, reject).
4
+ * Subclasses use `type` as the discriminator. */
5
+ export interface ActionSchema extends SchemaBase {
6
+ type: string;
7
+ }
8
+
9
+ export function defineAction(name: string, description?: string): ActionSchema {
10
+ return { name, description, type: 'gesture' };
11
+ }
package/src/asset.ts CHANGED
@@ -1,63 +1,63 @@
1
- /**
2
- * defineAsset — registry for reusable project assets (utils, components, flows, pages, hooks).
3
- *
4
- * Each asset declares its name, category, import path, tags, and optional usage example.
5
- * CLI scans all asset declarations, supports query (by tag/category) and generate (import links).
6
- *
7
- * // assets/utils.assets.ts
8
- * import { defineAsset } from '@pylonts/dsl';
9
- * export const formatAmt = defineAsset({
10
- * name: 'formatAmt',
11
- * category: 'util',
12
- * tags: ['amount', 'format'],
13
- * import: { name: 'formatAmt', from: '@/utils/amount' },
14
- * example: 'formatAmt(12345) => "12,345.00"',
15
- * });
16
- *
17
- * // CLI:
18
- * // gen-cli asset list --tag form → all form-related assets
19
- * // gen-cli asset import formatAmt → import { formatAmt } from '@/utils/amount';
20
- */
21
-
22
- export type AssetCategory = 'util' | 'component' | 'flow' | 'page' | 'hook';
23
-
24
- export interface AssetImport {
25
- /** Named export, e.g. 'formatAmt' */
26
- name: string;
27
- /** Module path, e.g. '@/utils/amount' */
28
- from: string;
29
- }
30
-
31
- export interface AssetConfig {
32
- name: string;
33
- category: AssetCategory;
34
- tags: string[];
35
- import: AssetImport;
36
- description?: string;
37
- /** One-liner usage example */
38
- example?: string;
39
- /** Link to detailed docs */
40
- see?: string;
41
- }
42
-
43
- export interface AssetDef {
44
- name: string;
45
- category: AssetCategory;
46
- tags: string[];
47
- import: AssetImport;
48
- description?: string;
49
- example?: string;
50
- see?: string;
51
- }
52
-
53
- export function defineAsset(config: AssetConfig): AssetDef {
54
- return {
55
- name: config.name,
56
- category: config.category,
57
- tags: config.tags,
58
- import: config.import,
59
- description: config.description,
60
- example: config.example,
61
- see: config.see,
62
- };
63
- }
1
+ /**
2
+ * defineAsset — registry for reusable project assets (utils, components, flows, pages, hooks).
3
+ *
4
+ * Each asset declares its name, category, import path, tags, and optional usage example.
5
+ * CLI scans all asset declarations, supports query (by tag/category) and generate (import links).
6
+ *
7
+ * // assets/utils.assets.ts
8
+ * import { defineAsset } from '@pylonts/dsl';
9
+ * export const formatAmt = defineAsset({
10
+ * name: 'formatAmt',
11
+ * category: 'util',
12
+ * tags: ['amount', 'format'],
13
+ * import: { name: 'formatAmt', from: '@/utils/amount' },
14
+ * example: 'formatAmt(12345) => "12,345.00"',
15
+ * });
16
+ *
17
+ * // CLI:
18
+ * // pylonts gen asset list --tag form → all form-related assets
19
+ * // pylonts gen asset import formatAmt → import { formatAmt } from '@/utils/amount';
20
+ */
21
+
22
+ export type AssetCategory = 'util' | 'component' | 'flow' | 'page' | 'hook';
23
+
24
+ export interface AssetImport {
25
+ /** Named export, e.g. 'formatAmt' */
26
+ name: string;
27
+ /** Module path, e.g. '@/utils/amount' */
28
+ from: string;
29
+ }
30
+
31
+ export interface AssetConfig {
32
+ name: string;
33
+ category: AssetCategory;
34
+ tags: string[];
35
+ import: AssetImport;
36
+ description?: string;
37
+ /** One-liner usage example */
38
+ example?: string;
39
+ /** Link to detailed docs */
40
+ see?: string;
41
+ }
42
+
43
+ export interface AssetDef {
44
+ name: string;
45
+ category: AssetCategory;
46
+ tags: string[];
47
+ import: AssetImport;
48
+ description?: string;
49
+ example?: string;
50
+ see?: string;
51
+ }
52
+
53
+ export function defineAsset(config: AssetConfig): AssetDef {
54
+ return {
55
+ name: config.name,
56
+ category: config.category,
57
+ tags: config.tags,
58
+ import: config.import,
59
+ description: config.description,
60
+ example: config.example,
61
+ see: config.see,
62
+ };
63
+ }
package/src/bases.ts CHANGED
@@ -1,30 +1,30 @@
1
- import type { DtoMessage, ImportBase, ImportRef } from './dto.js';
2
-
3
- // Named base-schema references for common protocol DTOs.
4
- //
5
- // These are ImportRef metadata (not re-exports of the actual TypeBox schemas):
6
- // the DSL stores { from, name } so the generator can emit the import line and
7
- // the identifier — the runtime schema object itself is never loaded by the DSL.
8
- //
9
- // `import { PageRequest } from '@pylonts/dsl'` therefore gives .include() an
10
- // already-resolved reference — no static analysis or name lookup needed.
11
-
12
- /** Paginated query request base — renders `import { PageRequest } from '@pylonts/core'` + Intersect */
13
- export const PageRequest: ImportBase = { from: '@pylonts/core', name: 'PageRequest' };
14
-
15
- /** Paginated list response base — renders `import { PageResult } from '@pylonts/core'` + `PageResult(<row>)` */
16
- export const PageResult = (row: DtoMessage): ImportRef => ({
17
- from: '@pylonts/core',
18
- name: 'PageResult',
19
- args: [row],
20
- });
21
-
22
- /** Paged rows type base without generic args — renders `import { PagedRows } from '@pylonts/core'` */
23
- export const PagedRows: ImportBase = { from: '@pylonts/core', name: 'PagedRows' };
24
-
25
- /** Paged rows type base — renders `import { PagedRows } from '@pylonts/core'` + `PagedRows(<row>)` */
26
- export const PageRows = (row: DtoMessage): ImportRef => ({
27
- from: '@pylonts/core',
28
- name: 'PagedRows',
29
- args: [row],
1
+ import type { DtoMessage, ImportBase, ImportRef } from './dto.js';
2
+
3
+ // Named base-schema references for common protocol DTOs.
4
+ //
5
+ // These are ImportRef metadata (not re-exports of the actual TypeBox schemas):
6
+ // the DSL stores { from, name } so the generator can emit the import line and
7
+ // the identifier — the runtime schema object itself is never loaded by the DSL.
8
+ //
9
+ // `import { PageRequest } from '@pylonts/dsl'` therefore gives .include() an
10
+ // already-resolved reference — no static analysis or name lookup needed.
11
+
12
+ /** Paginated query request base — renders `import { PageRequest } from '@pylonts/core'` + Intersect */
13
+ export const PageRequest: ImportBase = { from: '@pylonts/core', name: 'PageRequest' };
14
+
15
+ /** Paginated list response base — renders `import { PageResult } from '@pylonts/core'` + `PageResult(<row>)` */
16
+ export const PageResult = (row: DtoMessage): ImportRef => ({
17
+ from: '@pylonts/core',
18
+ name: 'PageResult',
19
+ args: [row],
20
+ });
21
+
22
+ /** Paged rows type base without generic args — renders `import { PagedRows } from '@pylonts/core'` */
23
+ export const PagedRows: ImportBase = { from: '@pylonts/core', name: 'PagedRows' };
24
+
25
+ /** Paged rows type base — renders `import { PagedRows } from '@pylonts/core'` + `PagedRows(<row>)` */
26
+ export const PageRows = (row: DtoMessage): ImportRef => ({
27
+ from: '@pylonts/core',
28
+ name: 'PagedRows',
29
+ args: [row],
30
30
  });
@@ -0,0 +1,22 @@
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
+
6
+ /** A component event trigger declaration. */
7
+ export interface TriggerSchema extends SchemaBase {
8
+ /** Data the event carries (e.g. e.detail). */
9
+ eventData?: EventDataSchema;
10
+ /** Actions that fire when the event occurs. */
11
+ actions?: ActionSchema[];
12
+ }
13
+
14
+ /** A UI component declaration — a virtual schema that describes props and
15
+ * event triggers, not a real renderable component.
16
+ *
17
+ * properties: data bindings via RefSchema (or literal values).
18
+ * triggers: event name → TriggerSchema bindings. */
19
+ export interface ComponentSchema extends SchemaBase {
20
+ properties: Record<string, RefSchema | string | number | boolean>;
21
+ triggers: Record<string, TriggerSchema>;
22
+ }
package/src/convert.ts ADDED
@@ -0,0 +1,13 @@
1
+ import type { SchemaBase } from './dsl.js';
2
+
3
+ /** Declares post-call result → page data field mapping.
4
+ * Driver generates per-item transform (e.g. .map()) before setData. */
5
+ export interface ConvertSchema extends SchemaBase {
6
+ type: 'convert';
7
+ /** { targetField: sourceField } — renames or copies fields from call result. */
8
+ fields: Record<string, string>;
9
+ }
10
+
11
+ export function defineConvert(name: string, fields: Record<string, string>): ConvertSchema {
12
+ return { name, type: 'convert', fields };
13
+ }
package/src/curd.ts CHANGED
@@ -1,92 +1,94 @@
1
- import { SchemaBase, Field, Operator } from './dsl.js';
2
- import { TableSchema } from './db.js';
3
- import { FrontAppSchema } from './project.js';
4
- import { ActionSchema } from './page.js';
5
-
6
- // Admin-only CRUD page standard: binds one entity table to a frontend admin
7
- // app, describing everything needed to generate the list page plus optional
8
- // add/update/detail pages. Field-level columns are plain Field instances
9
- // (table fields, cross-table refs allowed) — DTOs are derived by the generator,
10
- // the schema itself never references DtoMessage.
11
-
12
- /** Mode of an action page: modal dialog or standalone route. */
13
- export type ActionPageMode = 'modal' | 'route';
14
-
15
- /** One CRUD action page (add / update / detail). */
16
- export interface ActionPage {
17
- mode: ActionPageMode;
18
- /** Fields rendered on this page. Required, non-empty every field the
19
- * frontend shows must be listed explicitly. */
20
- columns: Field[];
21
- }
22
-
23
- /** List page configuration. */
24
- export interface CurdListConfig {
25
- /** List columns; required, non-empty. Every field the list shows must be
26
- * listed explicitly. May include cross-table fields via foreign refs. */
27
- columns: Field[];
28
- /** Fuzzy keyword search on this table's columns. */
29
- keyword?: { columns: Field[] };
30
- /** Default sort. Required column and direction are both mandatory. */
31
- orderBy: { column: Field; direction: 'asc' | 'desc' };
32
- /** Search condition fields; op defaults to 'eq'. */
33
- searchFields?: { field: Field; op?: Operator }[];
34
- /** Column header text overrides: Field.name header text. */
35
- columnTitles?: Record<string, string>;
36
- }
37
-
38
- /** Admin-only CRUD page standard: binds one entity table to a frontend
39
- * admin app. Drives generation of the list page plus optional
40
- * add/update/detail pages. */
41
- export interface CurdSchema extends SchemaBase {
42
- /** The admin frontend app this CRUD belongs to (shared instance, type must be 'admin'). */
43
- app: FrontAppSchema;
44
- /** The bound entity table (shared instance). */
45
- table: TableSchema;
46
- /** List page Chinese title. */
47
- title: string;
48
- /** Sidebar menu section (group) this CRUD page belongs to. */
49
- section: string;
50
- /** Extra user actions on this page (beyond the standard CRUD). */
51
- actions?: ActionSchema[];
52
- /** Add/update/detail action pages. */
53
- actionPages?: {
54
- add?: ActionPage;
55
- update?: ActionPage;
56
- detail?: ActionPage;
57
- };
58
- list: CurdListConfig;
59
- }
60
-
61
- function assertColumns(curd: CurdSchema, pageName: string, columns: Field[]): void {
62
- if (columns.length === 0) {
63
- throw new Error(`curd ${curd.name}: ${pageName}.columns must be non-empty`);
64
- }
65
- }
66
-
67
- function assertFieldsOwnTable(curd: CurdSchema, label: string, fields: Field[]): void {
68
- for (const f of fields) {
69
- if (f.schema !== curd.table) {
70
- throw new Error(`curd ${curd.name}: ${label} field ${f.name} does not belong to table ${curd.table.name}`);
71
- }
72
- }
73
- }
74
-
75
- /** Defines an admin CRUD page standard. Runtime-validates admin app binding,
76
- * non-empty columns and table field ownership (same style as defineTable). */
77
- export function defineCurd(name: string, schema: Omit<CurdSchema, 'name'>): CurdSchema {
78
- const curd: CurdSchema = { name, ...schema };
79
- if (curd.app.type !== 'admin') {
80
- throw new Error(`curd ${name}: app ${curd.app.name} must be type 'admin' (got '${curd.app.type}')`);
81
- }
82
- if (!curd.section) {
83
- throw new Error(`curd ${name}: section is required (sidebar menu group, e.g. '商户管理')`);
84
- }
85
- assertColumns(curd, 'list', curd.list.columns);
86
- for (const [pageName, page] of Object.entries(curd.actionPages ?? {})) {
87
- if (page) assertColumns(curd, `actionPages.${pageName}`, page.columns);
88
- }
89
- assertFieldsOwnTable(curd, 'keyword', curd.list.keyword?.columns ?? []);
90
- assertFieldsOwnTable(curd, 'orderBy', [curd.list.orderBy.column]);
91
- return curd;
1
+ import { SchemaBase, Field, Operator } from './dsl.js';
2
+ import { TableSchema } from './db.js';
3
+ import { FrontAppSchema } from './project.js';
4
+ import { ActionSchema } from './action.js';
5
+
6
+ // Admin-only CRUD page standard: binds one entity table to a frontend admin
7
+ // app, describing everything needed to generate the list page plus optional
8
+ // add/update/detail pages. Field-level columns are plain Field instances
9
+ // (table fields, cross-table refs allowed) — DTOs are derived by the generator,
10
+ // the schema itself never references DtoMessage.
11
+
12
+ /** Mode of an action page: modal dialog or standalone route. */
13
+ export type ActionPageMode = 'modal' | 'route';
14
+
15
+ /** One CRUD action page (add / update / detail). */
16
+ export interface ActionPage {
17
+ mode: ActionPageMode;
18
+ /** For add/update: when true, render as modal on list page; when false/undefined, render as standalone route page. */
19
+ modal?: boolean;
20
+ /** Fields rendered on this page. Required, non-empty — every field the
21
+ * frontend shows must be listed explicitly. */
22
+ columns: Field[];
23
+ }
24
+
25
+ /** List page configuration. */
26
+ export interface CurdListConfig {
27
+ /** List columns; required, non-empty. Every field the list shows must be
28
+ * listed explicitly. May include cross-table fields via foreign refs. */
29
+ columns: Field[];
30
+ /** Fuzzy keyword search on this table's columns. */
31
+ keyword?: { columns: Field[] };
32
+ /** Default sort. Required column and direction are both mandatory. */
33
+ orderBy: { column: Field; direction: 'asc' | 'desc' };
34
+ /** Search condition fields; op defaults to 'eq'. */
35
+ searchFields?: { field: Field; op?: Operator }[];
36
+ /** Column header text overrides: Field.name → header text. */
37
+ columnTitles?: Record<string, string>;
38
+ }
39
+
40
+ /** Admin-only CRUD page standard: binds one entity table to a frontend
41
+ * admin app. Drives generation of the list page plus optional
42
+ * add/update/detail pages. */
43
+ export interface CurdSchema extends SchemaBase {
44
+ /** The admin frontend app this CRUD belongs to (shared instance, type must be 'admin'). */
45
+ app: FrontAppSchema;
46
+ /** The bound entity table (shared instance). */
47
+ table: TableSchema;
48
+ /** List page Chinese title. */
49
+ title: string;
50
+ /** Sidebar menu section (group) this CRUD page belongs to. */
51
+ section: string;
52
+ /** Extra user actions on this page (beyond the standard CRUD). */
53
+ actions?: ActionSchema[];
54
+ /** Add/update/detail action pages. */
55
+ actionPages?: {
56
+ add?: ActionPage;
57
+ update?: ActionPage;
58
+ detail?: ActionPage;
59
+ };
60
+ list: CurdListConfig;
61
+ }
62
+
63
+ function assertColumns(curd: CurdSchema, pageName: string, columns: Field[]): void {
64
+ if (columns.length === 0) {
65
+ throw new Error(`curd ${curd.name}: ${pageName}.columns must be non-empty`);
66
+ }
67
+ }
68
+
69
+ function assertFieldsOwnTable(curd: CurdSchema, label: string, fields: Field[]): void {
70
+ for (const f of fields) {
71
+ if (f.schema !== curd.table) {
72
+ throw new Error(`curd ${curd.name}: ${label} field ${f.name} does not belong to table ${curd.table.name}`);
73
+ }
74
+ }
75
+ }
76
+
77
+ /** Defines an admin CRUD page standard. Runtime-validates admin app binding,
78
+ * non-empty columns and table field ownership (same style as defineTable). */
79
+ export function defineCurd(name: string, schema: Omit<CurdSchema, 'name'>): CurdSchema {
80
+ const curd: CurdSchema = { name, ...schema };
81
+ if (curd.app.type !== 'admin') {
82
+ throw new Error(`curd ${name}: app ${curd.app.name} must be type 'admin' (got '${curd.app.type}')`);
83
+ }
84
+ if (!curd.section) {
85
+ throw new Error(`curd ${name}: section is required (sidebar menu group, e.g. '商户管理')`);
86
+ }
87
+ assertColumns(curd, 'list', curd.list.columns);
88
+ for (const [pageName, page] of Object.entries(curd.actionPages ?? {})) {
89
+ if (page) assertColumns(curd, `actionPages.${pageName}`, page.columns);
90
+ }
91
+ assertFieldsOwnTable(curd, 'keyword', curd.list.keyword?.columns ?? []);
92
+ assertFieldsOwnTable(curd, 'orderBy', [curd.list.orderBy.column]);
93
+ return curd;
92
94
  }
package/src/db.ts CHANGED
@@ -25,6 +25,8 @@ export interface TableSchemaOptions<
25
25
  actor?: boolean;
26
26
  generator?: string;
27
27
  autoIncrement?: Field;
28
+ /** 本表用于关联显示的名称字段(如 name / username)。被外键引用时,自动用该字段做 label 展示。 */
29
+ label?: Field;
28
30
  primaryKey?: Field | Field[];
29
31
  indexes?: Index[];
30
32
  foreignKeys?: Record<string, ForeignKey>;
@@ -55,6 +57,8 @@ export class TableSchema<
55
57
  indexes?: Index[];
56
58
  /** 外键,引用其他表的字段 */
57
59
  foreignKeys?: Record<string, ForeignKey>;
60
+ /** 本表用于关联显示的名称字段(如 name / username)。被外键引用时,自动用该字段做 label 展示。 */
61
+ label?: Field;
58
62
  /** 引用的实体短语(词典条目):本表归属的实体;关联表等多实体场景不需要 */
59
63
  phrase?: EntityPhrase;
60
64
  /** 本表引用的所有枚举定义(map,key 为枚举标识),显式声明供 gen-enums 收集 */
@@ -74,6 +78,7 @@ export class TableSchema<
74
78
  this.primaryKey = options.primaryKey;
75
79
  this.indexes = options.indexes;
76
80
  this.foreignKeys = options.foreignKeys;
81
+ this.label = options.label;
77
82
  this.phrase = options.phrase;
78
83
  this.enums = options.enums;
79
84
  this.columns = options.columns;
@@ -97,6 +102,12 @@ export function defineTable<
97
102
  schema: TableSchemaOptions<N, C, E>,
98
103
  ): TableSchema<N, C, E> {
99
104
  const table = new TableSchema<N, C, E>(name, schema);
105
+ if (table.generator && table.autoIncrement) {
106
+ throw new Error(`table '${name}': generator and autoIncrement are mutually exclusive`);
107
+ }
108
+ if (table.label && !Object.values(table.columns).includes(table.label)) {
109
+ throw new Error(`table '${name}': label field '${table.label.name}' must be one of the table's columns`);
110
+ }
100
111
  for (const key of Object.keys(table.columns)) {
101
112
  const field = table.columns[key] as Field;
102
113
  if (field.schema && field.schema !== table) {