metal-orm 1.0.5 → 1.0.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.
Files changed (36) hide show
  1. package/README.md +299 -113
  2. package/docs/CHANGES.md +104 -0
  3. package/docs/advanced-features.md +92 -1
  4. package/docs/api-reference.md +13 -4
  5. package/docs/dml-operations.md +156 -0
  6. package/docs/getting-started.md +122 -55
  7. package/docs/hydration.md +78 -13
  8. package/docs/index.md +19 -14
  9. package/docs/multi-dialect-support.md +25 -0
  10. package/docs/query-builder.md +60 -0
  11. package/docs/runtime.md +105 -0
  12. package/docs/schema-definition.md +52 -1
  13. package/package.json +1 -1
  14. package/src/ast/expression.ts +38 -18
  15. package/src/builder/hydration-planner.ts +74 -74
  16. package/src/builder/select.ts +427 -395
  17. package/src/constants/sql-operator-config.ts +3 -0
  18. package/src/constants/sql.ts +38 -32
  19. package/src/index.ts +16 -8
  20. package/src/playground/features/playground/data/scenarios/types.ts +18 -15
  21. package/src/playground/features/playground/data/schema.ts +10 -10
  22. package/src/playground/features/playground/services/QueryExecutionService.ts +2 -1
  23. package/src/runtime/entity-meta.ts +52 -0
  24. package/src/runtime/entity.ts +252 -0
  25. package/src/runtime/execute.ts +36 -0
  26. package/src/runtime/hydration.ts +99 -49
  27. package/src/runtime/lazy-batch.ts +205 -0
  28. package/src/runtime/orm-context.ts +539 -0
  29. package/src/runtime/relations/belongs-to.ts +92 -0
  30. package/src/runtime/relations/has-many.ts +111 -0
  31. package/src/runtime/relations/many-to-many.ts +149 -0
  32. package/src/schema/column.ts +15 -1
  33. package/src/schema/relation.ts +82 -58
  34. package/src/schema/table.ts +34 -22
  35. package/src/schema/types.ts +76 -0
  36. package/tests/orm-runtime.test.ts +254 -0
@@ -0,0 +1,111 @@
1
+ import { HasManyCollection } from '../../schema/types';
2
+ import { OrmContext, RelationKey } from '../orm-context';
3
+ import { HasManyRelation } from '../../schema/relation';
4
+ import { TableDef } from '../../schema/table';
5
+ import { EntityMeta, getHydrationRows } from '../entity-meta';
6
+
7
+ type Rows = Record<string, any>[];
8
+
9
+ const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
10
+
11
+ export class DefaultHasManyCollection<TChild> implements HasManyCollection<TChild> {
12
+ private loaded = false;
13
+ private items: TChild[] = [];
14
+ private readonly added = new Set<TChild>();
15
+ private readonly removed = new Set<TChild>();
16
+
17
+ constructor(
18
+ private readonly ctx: OrmContext,
19
+ private readonly meta: EntityMeta<any>,
20
+ private readonly root: any,
21
+ private readonly relationName: string,
22
+ private readonly relation: HasManyRelation,
23
+ private readonly rootTable: TableDef,
24
+ private readonly loader: () => Promise<Map<string, Rows>>,
25
+ private readonly createEntity: (row: Record<string, any>) => TChild,
26
+ private readonly localKey: string
27
+ ) {
28
+ this.hydrateFromCache();
29
+ }
30
+
31
+ async load(): Promise<TChild[]> {
32
+ if (this.loaded) return this.items;
33
+ const map = await this.loader();
34
+ const key = toKey(this.root[this.localKey]);
35
+ const rows = map.get(key) ?? [];
36
+ this.items = rows.map(row => this.createEntity(row));
37
+ this.loaded = true;
38
+ return this.items;
39
+ }
40
+
41
+ getItems(): TChild[] {
42
+ return this.items;
43
+ }
44
+
45
+ add(data: Partial<TChild>): TChild {
46
+ const keyValue = this.root[this.localKey];
47
+ const childRow: Record<string, any> = {
48
+ ...data,
49
+ [this.relation.foreignKey]: keyValue
50
+ };
51
+ const entity = this.createEntity(childRow);
52
+ this.added.add(entity);
53
+ this.items.push(entity);
54
+ this.ctx.registerRelationChange(
55
+ this.root,
56
+ this.relationKey,
57
+ this.rootTable,
58
+ this.relationName,
59
+ this.relation,
60
+ { kind: 'add', entity }
61
+ );
62
+ return entity;
63
+ }
64
+
65
+ attach(entity: TChild): void {
66
+ const keyValue = this.root[this.localKey];
67
+ (entity as Record<string, any>)[this.relation.foreignKey] = keyValue;
68
+ this.ctx.markDirty(entity);
69
+ this.items.push(entity);
70
+ this.ctx.registerRelationChange(
71
+ this.root,
72
+ this.relationKey,
73
+ this.rootTable,
74
+ this.relationName,
75
+ this.relation,
76
+ { kind: 'attach', entity }
77
+ );
78
+ }
79
+
80
+ remove(entity: TChild): void {
81
+ this.items = this.items.filter(item => item !== entity);
82
+ this.removed.add(entity);
83
+ this.ctx.registerRelationChange(
84
+ this.root,
85
+ this.relationKey,
86
+ this.rootTable,
87
+ this.relationName,
88
+ this.relation,
89
+ { kind: 'remove', entity }
90
+ );
91
+ }
92
+
93
+ clear(): void {
94
+ for (const entity of [...this.items]) {
95
+ this.remove(entity);
96
+ }
97
+ }
98
+
99
+ private get relationKey(): RelationKey {
100
+ return `${this.rootTable.name}.${this.relationName}`;
101
+ }
102
+
103
+ private hydrateFromCache(): void {
104
+ const keyValue = this.root[this.localKey];
105
+ if (keyValue === undefined || keyValue === null) return;
106
+ const rows = getHydrationRows(this.meta, this.relationName, keyValue);
107
+ if (!rows?.length) return;
108
+ this.items = rows.map(row => this.createEntity(row));
109
+ this.loaded = true;
110
+ }
111
+ }
@@ -0,0 +1,149 @@
1
+ import { ManyToManyCollection } from '../../schema/types';
2
+ import { OrmContext, RelationKey } from '../orm-context';
3
+ import { BelongsToManyRelation } from '../../schema/relation';
4
+ import { TableDef } from '../../schema/table';
5
+ import { findPrimaryKey } from '../../builder/hydration-planner';
6
+ import { EntityMeta, getHydrationRows } from '../entity-meta';
7
+
8
+ type Rows = Record<string, any>[];
9
+
10
+ const toKey = (value: unknown): string => (value === null || value === undefined ? '' : String(value));
11
+
12
+ export class DefaultManyToManyCollection<TTarget> implements ManyToManyCollection<TTarget> {
13
+ private loaded = false;
14
+ private items: TTarget[] = [];
15
+
16
+ constructor(
17
+ private readonly ctx: OrmContext,
18
+ private readonly meta: EntityMeta<any>,
19
+ private readonly root: any,
20
+ private readonly relationName: string,
21
+ private readonly relation: BelongsToManyRelation,
22
+ private readonly rootTable: TableDef,
23
+ private readonly loader: () => Promise<Map<string, Rows>>,
24
+ private readonly createEntity: (row: Record<string, any>) => TTarget,
25
+ private readonly localKey: string
26
+ ) {
27
+ this.hydrateFromCache();
28
+ }
29
+
30
+ async load(): Promise<TTarget[]> {
31
+ if (this.loaded) return this.items;
32
+ const map = await this.loader();
33
+ const key = toKey(this.root[this.localKey]);
34
+ const rows = map.get(key) ?? [];
35
+ this.items = rows.map(row => {
36
+ const entity = this.createEntity(row);
37
+ if ((row as any)._pivot) {
38
+ (entity as any)._pivot = row._pivot;
39
+ }
40
+ return entity;
41
+ });
42
+ this.loaded = true;
43
+ return this.items;
44
+ }
45
+
46
+ getItems(): TTarget[] {
47
+ return this.items;
48
+ }
49
+
50
+ attach(target: TTarget | number | string): void {
51
+ const entity = this.ensureEntity(target);
52
+ const id = this.extractId(entity);
53
+ if (id == null) return;
54
+ if (this.items.some(item => this.extractId(item) === id)) {
55
+ return;
56
+ }
57
+ this.items.push(entity);
58
+ this.ctx.registerRelationChange(
59
+ this.root,
60
+ this.relationKey,
61
+ this.rootTable,
62
+ this.relationName,
63
+ this.relation,
64
+ { kind: 'attach', entity }
65
+ );
66
+ }
67
+
68
+ detach(target: TTarget | number | string): void {
69
+ const id = typeof target === 'number' || typeof target === 'string'
70
+ ? target
71
+ : this.extractId(target);
72
+
73
+ if (id == null) return;
74
+
75
+ const existing = this.items.find(item => this.extractId(item) === id);
76
+ if (!existing) return;
77
+
78
+ this.items = this.items.filter(item => this.extractId(item) !== id);
79
+ this.ctx.registerRelationChange(
80
+ this.root,
81
+ this.relationKey,
82
+ this.rootTable,
83
+ this.relationName,
84
+ this.relation,
85
+ { kind: 'detach', entity: existing }
86
+ );
87
+ }
88
+
89
+ async syncByIds(ids: (number | string)[]): Promise<void> {
90
+ await this.load();
91
+ const targetKey = this.relation.targetKey || findPrimaryKey(this.relation.target);
92
+ const normalized = new Set(ids.map(id => toKey(id)));
93
+ const currentIds = new Set(this.items.map(item => toKey(this.extractId(item))));
94
+
95
+ for (const id of normalized) {
96
+ if (!currentIds.has(id)) {
97
+ this.attach(id);
98
+ }
99
+ }
100
+
101
+ for (const item of [...this.items]) {
102
+ const itemId = toKey(this.extractId(item));
103
+ if (!normalized.has(itemId)) {
104
+ this.detach(item);
105
+ }
106
+ }
107
+ }
108
+
109
+ private ensureEntity(target: TTarget | number | string): TTarget {
110
+ if (typeof target === 'number' || typeof target === 'string') {
111
+ const stub: Record<string, any> = {
112
+ [this.targetKey]: target
113
+ };
114
+ return this.createEntity(stub);
115
+ }
116
+ return target;
117
+ }
118
+
119
+ private extractId(entity: TTarget | number | string | null | undefined): number | string | null {
120
+ if (entity === null || entity === undefined) return null;
121
+ if (typeof entity === 'number' || typeof entity === 'string') {
122
+ return entity;
123
+ }
124
+ return (entity as any)[this.targetKey] ?? null;
125
+ }
126
+
127
+ private get relationKey(): RelationKey {
128
+ return `${this.rootTable.name}.${this.relationName}`;
129
+ }
130
+
131
+ private get targetKey(): string {
132
+ return this.relation.targetKey || findPrimaryKey(this.relation.target);
133
+ }
134
+
135
+ private hydrateFromCache(): void {
136
+ const keyValue = this.root[this.localKey];
137
+ if (keyValue === undefined || keyValue === null) return;
138
+ const rows = getHydrationRows(this.meta, this.relationName, keyValue);
139
+ if (!rows?.length) return;
140
+ this.items = rows.map(row => {
141
+ const entity = this.createEntity(row);
142
+ if ((row as any)._pivot) {
143
+ (entity as any)._pivot = (row as any)._pivot;
144
+ }
145
+ return entity;
146
+ });
147
+ this.loaded = true;
148
+ }
149
+ }
@@ -1,7 +1,21 @@
1
1
  /**
2
2
  * Supported column data types for database schema definitions
3
3
  */
4
- export type ColumnType = 'INT' | 'VARCHAR' | 'JSON' | 'ENUM' | 'BOOLEAN';
4
+ export type ColumnType =
5
+ | 'INT'
6
+ | 'INTEGER'
7
+ | 'VARCHAR'
8
+ | 'TEXT'
9
+ | 'JSON'
10
+ | 'ENUM'
11
+ | 'BOOLEAN'
12
+ | 'int'
13
+ | 'integer'
14
+ | 'varchar'
15
+ | 'text'
16
+ | 'json'
17
+ | 'enum'
18
+ | 'boolean';
5
19
 
6
20
  /**
7
21
  * Definition of a database column
@@ -17,32 +17,36 @@ export const RelationKinds = {
17
17
  */
18
18
  export type RelationType = (typeof RelationKinds)[keyof typeof RelationKinds];
19
19
 
20
+ export type CascadeMode = 'none' | 'all' | 'persist' | 'remove' | 'link';
21
+
20
22
  /**
21
23
  * One-to-many relationship definition
22
24
  */
23
- export interface HasManyRelation {
25
+ export interface HasManyRelation<TTarget extends TableDef = TableDef> {
24
26
  type: typeof RelationKinds.HasMany;
25
- target: TableDef;
27
+ target: TTarget;
26
28
  foreignKey: string;
27
29
  localKey?: string;
30
+ cascade?: CascadeMode;
28
31
  }
29
32
 
30
33
  /**
31
34
  * Many-to-one relationship definition
32
35
  */
33
- export interface BelongsToRelation {
36
+ export interface BelongsToRelation<TTarget extends TableDef = TableDef> {
34
37
  type: typeof RelationKinds.BelongsTo;
35
- target: TableDef;
38
+ target: TTarget;
36
39
  foreignKey: string;
37
40
  localKey?: string;
41
+ cascade?: CascadeMode;
38
42
  }
39
43
 
40
44
  /**
41
45
  * Many-to-many relationship definition with rich pivot metadata
42
46
  */
43
- export interface BelongsToManyRelation {
47
+ export interface BelongsToManyRelation<TTarget extends TableDef = TableDef> {
44
48
  type: typeof RelationKinds.BelongsToMany;
45
- target: TableDef;
49
+ target: TTarget;
46
50
  pivotTable: TableDef;
47
51
  pivotForeignKeyToRoot: string;
48
52
  pivotForeignKeyToTarget: string;
@@ -50,70 +54,89 @@ export interface BelongsToManyRelation {
50
54
  targetKey?: string;
51
55
  pivotPrimaryKey?: string;
52
56
  defaultPivotColumns?: string[];
57
+ cascade?: CascadeMode;
53
58
  }
54
59
 
55
60
  /**
56
61
  * Union type representing any supported relationship definition
57
62
  */
58
- export type RelationDef = HasManyRelation | BelongsToRelation | BelongsToManyRelation;
59
-
60
- /**
61
- * Creates a one-to-many relationship definition
62
- * @param target - Target table of the relationship
63
- * @param foreignKey - Foreign key column name on the child table
64
- * @param localKey - Local key column name (optional)
65
- * @returns HasManyRelation definition
66
- *
67
- * @example
68
- * ```typescript
69
- * hasMany(usersTable, 'user_id')
70
- * ```
71
- */
72
- export const hasMany = (target: TableDef, foreignKey: string, localKey?: string): HasManyRelation => ({
63
+ export type RelationDef =
64
+ | HasManyRelation
65
+ | BelongsToRelation
66
+ | BelongsToManyRelation;
67
+
68
+ /**
69
+ * Creates a one-to-many relationship definition
70
+ * @param target - Target table of the relationship
71
+ * @param foreignKey - Foreign key column name on the child table
72
+ * @param localKey - Local key column name (optional)
73
+ * @returns HasManyRelation definition
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * hasMany(usersTable, 'user_id')
78
+ * ```
79
+ */
80
+ export const hasMany = <TTarget extends TableDef>(
81
+ target: TTarget,
82
+ foreignKey: string,
83
+ localKey?: string,
84
+ cascade?: CascadeMode
85
+ ): HasManyRelation<TTarget> => ({
73
86
  type: RelationKinds.HasMany,
74
87
  target,
75
88
  foreignKey,
76
- localKey
89
+ localKey,
90
+ cascade
77
91
  });
78
-
79
- /**
80
- * Creates a many-to-one relationship definition
81
- * @param target - Target table of the relationship
82
- * @param foreignKey - Foreign key column name on the child table
83
- * @param localKey - Local key column name (optional)
84
- * @returns BelongsToRelation definition
85
- *
86
- * @example
87
- * ```typescript
88
- * belongsTo(usersTable, 'user_id')
89
- * ```
90
- */
91
- export const belongsTo = (target: TableDef, foreignKey: string, localKey?: string): BelongsToRelation => ({
92
+
93
+ /**
94
+ * Creates a many-to-one relationship definition
95
+ * @param target - Target table of the relationship
96
+ * @param foreignKey - Foreign key column name on the child table
97
+ * @param localKey - Local key column name (optional)
98
+ * @returns BelongsToRelation definition
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * belongsTo(usersTable, 'user_id')
103
+ * ```
104
+ */
105
+ export const belongsTo = <TTarget extends TableDef>(
106
+ target: TTarget,
107
+ foreignKey: string,
108
+ localKey?: string,
109
+ cascade?: CascadeMode
110
+ ): BelongsToRelation<TTarget> => ({
92
111
  type: RelationKinds.BelongsTo,
93
112
  target,
94
113
  foreignKey,
95
- localKey
114
+ localKey,
115
+ cascade
96
116
  });
97
-
98
- /**
99
- * Creates a many-to-many relationship definition with pivot metadata
100
- * @param target - Target table
101
- * @param pivotTable - Intermediate pivot table definition
102
- * @param options - Pivot metadata configuration
103
- * @returns BelongsToManyRelation definition
104
- */
105
- export const belongsToMany = (
106
- target: TableDef,
107
- pivotTable: TableDef,
108
- options: {
109
- pivotForeignKeyToRoot: string;
110
- pivotForeignKeyToTarget: string;
111
- localKey?: string;
112
- targetKey?: string;
113
- pivotPrimaryKey?: string;
114
- defaultPivotColumns?: string[];
115
- }
116
- ): BelongsToManyRelation => ({
117
+
118
+ /**
119
+ * Creates a many-to-many relationship definition with pivot metadata
120
+ * @param target - Target table
121
+ * @param pivotTable - Intermediate pivot table definition
122
+ * @param options - Pivot metadata configuration
123
+ * @returns BelongsToManyRelation definition
124
+ */
125
+ export const belongsToMany = <
126
+ TTarget extends TableDef
127
+ >(
128
+ target: TTarget,
129
+ pivotTable: TableDef,
130
+ options: {
131
+ pivotForeignKeyToRoot: string;
132
+ pivotForeignKeyToTarget: string;
133
+ localKey?: string;
134
+ targetKey?: string;
135
+ pivotPrimaryKey?: string;
136
+ defaultPivotColumns?: string[];
137
+ cascade?: CascadeMode;
138
+ }
139
+ ): BelongsToManyRelation<TTarget> => ({
117
140
  type: RelationKinds.BelongsToMany,
118
141
  target,
119
142
  pivotTable,
@@ -122,5 +145,6 @@ export const belongsToMany = (
122
145
  localKey: options.localKey,
123
146
  targetKey: options.targetKey,
124
147
  pivotPrimaryKey: options.pivotPrimaryKey,
125
- defaultPivotColumns: options.defaultPivotColumns
148
+ defaultPivotColumns: options.defaultPivotColumns,
149
+ cascade: options.cascade
126
150
  });
@@ -1,18 +1,29 @@
1
- import { ColumnDef } from './column';
2
- import { RelationDef } from './relation';
3
-
4
- /**
5
- * Definition of a database table with its columns and relationships
6
- * @typeParam T - Type of the columns record
7
- */
8
- export interface TableDef<T extends Record<string, ColumnDef> = Record<string, ColumnDef>> {
9
- /** Name of the table */
10
- name: string;
11
- /** Record of column definitions keyed by column name */
12
- columns: T;
13
- /** Record of relationship definitions keyed by relation name */
14
- relations: Record<string, RelationDef>;
15
- }
1
+ import { ColumnDef } from './column';
2
+ import { RelationDef } from './relation';
3
+
4
+ export interface TableHooks {
5
+ beforeInsert?(ctx: unknown, entity: any): Promise<void> | void;
6
+ afterInsert?(ctx: unknown, entity: any): Promise<void> | void;
7
+ beforeUpdate?(ctx: unknown, entity: any): Promise<void> | void;
8
+ afterUpdate?(ctx: unknown, entity: any): Promise<void> | void;
9
+ beforeDelete?(ctx: unknown, entity: any): Promise<void> | void;
10
+ afterDelete?(ctx: unknown, entity: any): Promise<void> | void;
11
+ }
12
+
13
+ /**
14
+ * Definition of a database table with its columns and relationships
15
+ * @typeParam T - Type of the columns record
16
+ */
17
+ export interface TableDef<T extends Record<string, ColumnDef> = Record<string, ColumnDef>> {
18
+ /** Name of the table */
19
+ name: string;
20
+ /** Record of column definitions keyed by column name */
21
+ columns: T;
22
+ /** Record of relationship definitions keyed by relation name */
23
+ relations: Record<string, RelationDef>;
24
+ /** Optional lifecycle hooks */
25
+ hooks?: TableHooks;
26
+ }
16
27
 
17
28
  /**
18
29
  * Creates a table definition with columns and relationships
@@ -31,16 +42,17 @@ export interface TableDef<T extends Record<string, ColumnDef> = Record<string, C
31
42
  * });
32
43
  * ```
33
44
  */
34
- export const defineTable = <T extends Record<string, ColumnDef>>(
35
- name: string,
36
- columns: T,
37
- relations: Record<string, RelationDef> = {}
38
- ): TableDef<T> => {
45
+ export const defineTable = <T extends Record<string, ColumnDef>>(
46
+ name: string,
47
+ columns: T,
48
+ relations: Record<string, RelationDef> = {},
49
+ hooks?: TableHooks
50
+ ): TableDef<T> => {
39
51
  // Runtime mutability to assign names to column definitions for convenience
40
52
  const colsWithNames = Object.entries(columns).reduce((acc, [key, def]) => {
41
53
  (acc as any)[key] = { ...def, name: key, table: name };
42
54
  return acc;
43
55
  }, {} as T);
44
56
 
45
- return { name, columns: colsWithNames, relations };
46
- };
57
+ return { name, columns: colsWithNames, relations, hooks };
58
+ };
@@ -0,0 +1,76 @@
1
+ import { ColumnDef } from './column';
2
+ import { TableDef } from './table';
3
+ import {
4
+ RelationDef,
5
+ HasManyRelation,
6
+ BelongsToRelation,
7
+ BelongsToManyRelation
8
+ } from './relation';
9
+
10
+ /**
11
+ * Maps a ColumnDef to its TypeScript type representation
12
+ */
13
+ export type ColumnToTs<T extends ColumnDef> =
14
+ T['type'] extends 'INT' | 'INTEGER' | 'int' | 'integer' ? number :
15
+ T['type'] extends 'BOOLEAN' | 'boolean' ? boolean :
16
+ T['type'] extends 'JSON' | 'json' ? unknown :
17
+ string;
18
+
19
+ /**
20
+ * Infers a row shape from a table definition
21
+ */
22
+ export type InferRow<TTable extends TableDef> = {
23
+ [K in keyof TTable['columns']]: ColumnToTs<TTable['columns'][K]>;
24
+ };
25
+
26
+ type RelationResult<T extends RelationDef> =
27
+ T extends HasManyRelation<infer TTarget> ? InferRow<TTarget>[] :
28
+ T extends BelongsToRelation<infer TTarget> ? InferRow<TTarget> | null :
29
+ T extends BelongsToManyRelation<infer TTarget> ? (InferRow<TTarget> & { _pivot?: any })[] :
30
+ never;
31
+
32
+ /**
33
+ * Maps relation names to the expected row results
34
+ */
35
+ export type RelationMap<TTable extends TableDef> = {
36
+ [K in keyof TTable['relations']]: RelationResult<TTable['relations'][K]>;
37
+ };
38
+
39
+ export interface HasManyCollection<TChild> {
40
+ load(): Promise<TChild[]>;
41
+ getItems(): TChild[];
42
+ add(data: Partial<TChild>): TChild;
43
+ attach(entity: TChild): void;
44
+ remove(entity: TChild): void;
45
+ clear(): void;
46
+ }
47
+
48
+ export interface BelongsToReference<TParent> {
49
+ load(): Promise<TParent | null>;
50
+ get(): TParent | null;
51
+ set(data: Partial<TParent> | TParent | null): TParent | null;
52
+ }
53
+
54
+ export interface ManyToManyCollection<TTarget> {
55
+ load(): Promise<TTarget[]>;
56
+ getItems(): TTarget[];
57
+ attach(target: TTarget | number | string): void;
58
+ detach(target: TTarget | number | string): void;
59
+ syncByIds(ids: (number | string)[]): Promise<void>;
60
+ }
61
+
62
+ export type Entity<
63
+ TTable extends TableDef,
64
+ TRow = InferRow<TTable>
65
+ > = TRow & {
66
+ [K in keyof RelationMap<TTable>]:
67
+ TTable['relations'][K] extends HasManyRelation<infer TTarget>
68
+ ? HasManyCollection<Entity<TTarget>>
69
+ : TTable['relations'][K] extends BelongsToManyRelation<infer TTarget>
70
+ ? ManyToManyCollection<Entity<TTarget>>
71
+ : TTable['relations'][K] extends BelongsToRelation<infer TTarget>
72
+ ? BelongsToReference<Entity<TTarget>>
73
+ : never;
74
+ } & {
75
+ $load<K extends keyof RelationMap<TTable>>(relation: K): Promise<RelationMap<TTable>[K]>;
76
+ };