metal-orm 1.0.64 → 1.0.66

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.
@@ -2,19 +2,19 @@ import { TableDef } from '../schema/table.js';
2
2
  import { ColumnDef } from '../schema/column-types.js';
3
3
  import { OrderingTerm, SelectQueryNode, SetOperationKind } from '../core/ast/query.js';
4
4
  import { HydrationPlan } from '../core/hydration/types.js';
5
- import {
6
- ColumnNode,
7
- ExpressionNode,
8
- FunctionNode,
9
- BinaryExpressionNode,
10
- CaseExpressionNode,
11
- WindowFunctionNode,
12
- and,
13
- exists,
14
- notExists,
15
- OperandNode
16
- } from '../core/ast/expression.js';
17
- import type { TypedExpression } from '../core/ast/expression.js';
5
+ import {
6
+ ColumnNode,
7
+ ExpressionNode,
8
+ FunctionNode,
9
+ BinaryExpressionNode,
10
+ CaseExpressionNode,
11
+ WindowFunctionNode,
12
+ and,
13
+ exists,
14
+ notExists,
15
+ OperandNode
16
+ } from '../core/ast/expression.js';
17
+ import type { TypedExpression } from '../core/ast/expression.js';
18
18
  import { CompiledQuery, Dialect } from '../core/dialect/abstract.js';
19
19
  import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
20
20
 
@@ -32,12 +32,12 @@ import { ColumnSelector } from './column-selector.js';
32
32
  import { RelationIncludeOptions, RelationTargetColumns, TypedRelationIncludeOptions } from './relation-types.js';
33
33
  import { RelationKinds } from '../schema/relation.js';
34
34
  import { JOIN_KINDS, JoinKind, ORDER_DIRECTIONS, OrderDirection } from '../core/sql/sql.js';
35
- import { EntityInstance, RelationMap } from '../schema/types.js';
36
- import type { ColumnToTs, InferRow } from '../schema/types.js';
35
+ import { EntityInstance, RelationMap } from '../schema/types.js';
36
+ import type { ColumnToTs, InferRow } from '../schema/types.js';
37
37
  import { OrmSession } from '../orm/orm-session.ts';
38
38
  import { ExecutionContext } from '../orm/execution-context.js';
39
39
  import { HydrationContext } from '../orm/hydration-context.js';
40
- import { executeHydrated, executeHydratedPlain, executeHydratedWithContexts } from '../orm/execute.js';
40
+ import { executeHydrated, executeHydratedPlain, executeHydratedWithContexts } from '../orm/execute.js';
41
41
  import { EntityConstructor } from '../orm/entity-metadata.js';
42
42
  import { materializeAs } from '../orm/entity-materializer.js';
43
43
  import { resolveSelectQuery } from './query-resolution.js';
@@ -57,27 +57,27 @@ import { SelectCTEFacet } from './select/cte-facet.js';
57
57
  import { SelectSetOpFacet } from './select/setop-facet.js';
58
58
  import { SelectRelationFacet } from './select/relation-facet.js';
59
59
 
60
- type ColumnSelectionValue =
61
- | ColumnDef
62
- | FunctionNode
63
- | CaseExpressionNode
64
- | WindowFunctionNode
65
- | TypedExpression<unknown>;
66
-
67
- type SelectionValueType<TValue> =
68
- TValue extends TypedExpression<infer TRuntime> ? TRuntime :
69
- TValue extends ColumnDef ? ColumnToTs<TValue> :
70
- unknown;
71
-
72
- type SelectionResult<TSelection extends Record<string, ColumnSelectionValue>> = {
73
- [K in keyof TSelection]: SelectionValueType<TSelection[K]>;
74
- };
75
-
76
- type SelectionFromKeys<
77
- TTable extends TableDef,
78
- K extends keyof TTable['columns'] & string
79
- > = Pick<InferRow<TTable>, K>;
80
-
60
+ type ColumnSelectionValue =
61
+ | ColumnDef
62
+ | FunctionNode
63
+ | CaseExpressionNode
64
+ | WindowFunctionNode
65
+ | TypedExpression<unknown>;
66
+
67
+ type SelectionValueType<TValue> =
68
+ TValue extends TypedExpression<infer TRuntime> ? TRuntime :
69
+ TValue extends ColumnDef ? ColumnToTs<TValue> :
70
+ unknown;
71
+
72
+ type SelectionResult<TSelection extends Record<string, ColumnSelectionValue>> = {
73
+ [K in keyof TSelection]: SelectionValueType<TSelection[K]>;
74
+ };
75
+
76
+ type SelectionFromKeys<
77
+ TTable extends TableDef,
78
+ K extends keyof TTable['columns'] & string
79
+ > = Pick<InferRow<TTable>, K>;
80
+
81
81
  type DeepSelectEntry<TTable extends TableDef> = {
82
82
  type: 'root';
83
83
  columns: (keyof TTable['columns'] & string)[];
@@ -94,7 +94,7 @@ type DeepSelectConfig<TTable extends TableDef> = DeepSelectEntry<TTable>[];
94
94
  * @typeParam T - Result type for projections (unused)
95
95
  * @typeParam TTable - Table definition being queried
96
96
  */
97
- export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends TableDef = TableDef> {
97
+ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends TableDef = TableDef> {
98
98
  private readonly env: SelectQueryBuilderEnvironment;
99
99
  private readonly context: SelectQueryBuilderContext;
100
100
  private readonly columnSelector: ColumnSelector;
@@ -107,7 +107,7 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
107
107
  private readonly relationFacet: SelectRelationFacet;
108
108
  private readonly lazyRelations: Set<string>;
109
109
  private readonly lazyRelationOptions: Map<string, RelationIncludeOptions>;
110
- private readonly entityConstructor?: EntityConstructor;
110
+ private readonly entityConstructor?: EntityConstructor;
111
111
 
112
112
  /**
113
113
  * Creates a new SelectQueryBuilder instance
@@ -123,7 +123,7 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
123
123
  dependencies?: Partial<SelectQueryBuilderDependencies>,
124
124
  lazyRelations?: Set<string>,
125
125
  lazyRelationOptions?: Map<string, RelationIncludeOptions>,
126
- entityConstructor?: EntityConstructor
126
+ entityConstructor?: EntityConstructor
127
127
  ) {
128
128
  const deps = resolveSelectQueryBuilderDependencies(dependencies);
129
129
  this.env = { table, deps };
@@ -154,21 +154,21 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
154
154
  * @param lazyRelations - Updated lazy relations set
155
155
  * @returns New SelectQueryBuilder instance
156
156
  */
157
- private clone<TNext = T>(
158
- context: SelectQueryBuilderContext = this.context,
159
- lazyRelations = new Set(this.lazyRelations),
160
- lazyRelationOptions = new Map(this.lazyRelationOptions)
161
- ): SelectQueryBuilder<TNext, TTable> {
162
- return new SelectQueryBuilder(
163
- this.env.table as TTable,
164
- context.state,
165
- context.hydration,
166
- this.env.deps,
167
- lazyRelations,
168
- lazyRelationOptions,
169
- this.entityConstructor
170
- ) as SelectQueryBuilder<TNext, TTable>;
171
- }
157
+ private clone<TNext = T>(
158
+ context: SelectQueryBuilderContext = this.context,
159
+ lazyRelations = new Set(this.lazyRelations),
160
+ lazyRelationOptions = new Map(this.lazyRelationOptions)
161
+ ): SelectQueryBuilder<TNext, TTable> {
162
+ return new SelectQueryBuilder(
163
+ this.env.table as TTable,
164
+ context.state,
165
+ context.hydration,
166
+ this.env.deps,
167
+ lazyRelations,
168
+ lazyRelationOptions,
169
+ this.entityConstructor
170
+ ) as SelectQueryBuilder<TNext, TTable>;
171
+ }
172
172
 
173
173
  /**
174
174
  * Applies an alias to the root FROM table.
@@ -213,7 +213,8 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
213
213
  */
214
214
  private applySetOperation<TSub extends TableDef>(
215
215
  operator: SetOperationKind,
216
- query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode
216
+ query: SelectQueryBuilder<T, TSub> | SelectQueryNode
217
+
217
218
  ): SelectQueryBuilderContext {
218
219
  const subAst = resolveSelectQuery(query);
219
220
  return this.setOpFacet.applySetOperation(this.context, operator, subAst);
@@ -234,41 +235,41 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
234
235
  * fullName: concat(userTable.columns.firstName, ' ', userTable.columns.lastName)
235
236
  * });
236
237
  */
237
- select<K extends keyof TTable['columns'] & string>(
238
- ...args: K[]
239
- ): SelectQueryBuilder<T & SelectionFromKeys<TTable, K>, TTable>;
240
- select<TSelection extends Record<string, ColumnSelectionValue>>(
241
- columns: TSelection
242
- ): SelectQueryBuilder<T & SelectionResult<TSelection>, TTable>;
243
- select<
244
- K extends keyof TTable['columns'] & string,
245
- TSelection extends Record<string, ColumnSelectionValue>
246
- >(
247
- ...args: K[] | [TSelection]
248
- ): SelectQueryBuilder<T, TTable> {
238
+ select<K extends keyof TTable['columns'] & string>(
239
+ ...args: K[]
240
+ ): SelectQueryBuilder<T & SelectionFromKeys<TTable, K>, TTable>;
241
+ select<TSelection extends Record<string, ColumnSelectionValue>>(
242
+ columns: TSelection
243
+ ): SelectQueryBuilder<T & SelectionResult<TSelection>, TTable>;
244
+ select<
245
+ K extends keyof TTable['columns'] & string,
246
+ TSelection extends Record<string, ColumnSelectionValue>
247
+ >(
248
+ ...args: K[] | [TSelection]
249
+ ): SelectQueryBuilder<T, TTable> {
249
250
  // If first arg is an object (not a string), treat as projection map
250
251
  if (args.length === 1 && typeof args[0] === 'object' && args[0] !== null && typeof args[0] !== 'string') {
251
- const columns = args[0] as TSelection;
252
- return this.clone<T & SelectionResult<TSelection>>(
253
- this.projectionFacet.select(this.context, columns)
254
- );
255
- }
252
+ const columns = args[0] as TSelection;
253
+ return this.clone<T & SelectionResult<TSelection>>(
254
+ this.projectionFacet.select(this.context, columns)
255
+ );
256
+ }
256
257
 
257
258
  // Otherwise, treat as column names
258
259
  const cols = args as K[];
259
260
  const selection: Record<string, ColumnDef> = {};
260
- for (const key of cols) {
261
- const col = this.env.table.columns[key];
262
- if (!col) {
263
- throw new Error(`Column '${key}' not found on table '${this.env.table.name}'`);
264
- }
265
- selection[key] = col;
266
- }
267
-
268
- return this.clone<T & SelectionFromKeys<TTable, K>>(
269
- this.projectionFacet.select(this.context, selection)
270
- );
271
- }
261
+ for (const key of cols) {
262
+ const col = this.env.table.columns[key];
263
+ if (!col) {
264
+ throw new Error(`Column '${key}' not found on table '${this.env.table.name}'`);
265
+ }
266
+ selection[key] = col;
267
+ }
268
+
269
+ return this.clone<T & SelectionFromKeys<TTable, K>>(
270
+ this.projectionFacet.select(this.context, selection)
271
+ );
272
+ }
272
273
 
273
274
  /**
274
275
  * Selects raw column expressions
@@ -384,15 +385,15 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
384
385
  * qb.select('id', 'name')
385
386
  * .selectSubquery('postCount', postCount);
386
387
  */
387
- selectSubquery<TValue = unknown, K extends string = string, TSub extends TableDef = TableDef>(
388
- alias: K,
389
- sub: SelectQueryBuilder<unknown, TSub> | SelectQueryNode
390
- ): SelectQueryBuilder<T & Record<K, TValue>, TTable> {
391
- const query = resolveSelectQuery(sub);
392
- return this.clone<T & Record<K, TValue>>(
393
- this.projectionFacet.selectSubquery(this.context, alias, query)
394
- );
395
- }
388
+ selectSubquery<TValue = unknown, K extends string = string, TSub extends TableDef = TableDef>(
389
+ alias: K,
390
+ sub: SelectQueryBuilder<unknown, TSub> | SelectQueryNode
391
+ ): SelectQueryBuilder<T & Record<K, TValue>, TTable> {
392
+ const query = resolveSelectQuery(sub);
393
+ return this.clone<T & Record<K, TValue>>(
394
+ this.projectionFacet.selectSubquery(this.context, alias, query)
395
+ );
396
+ }
396
397
 
397
398
  /**
398
399
  * Adds a JOIN against a derived table (subquery with alias)
@@ -668,14 +669,14 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
668
669
  /**
669
670
  * Ensures that if no columns are selected, all columns from the table are selected by default.
670
671
  */
671
- private ensureDefaultSelection(): SelectQueryBuilder<T, TTable> {
672
- const columns = this.context.state.ast.columns;
673
- if (!columns || columns.length === 0) {
674
- const columnKeys = Object.keys(this.env.table.columns) as (keyof TTable['columns'] & string)[];
675
- return this.select(...columnKeys);
676
- }
677
- return this;
678
- }
672
+ private ensureDefaultSelection(): SelectQueryBuilder<T, TTable> {
673
+ const columns = this.context.state.ast.columns;
674
+ if (!columns || columns.length === 0) {
675
+ const columnKeys = Object.keys(this.env.table.columns) as (keyof TTable['columns'] & string)[];
676
+ return this.select(...columnKeys);
677
+ }
678
+ return this;
679
+ }
679
680
 
680
681
  /**
681
682
  * Executes the query and returns hydrated results.
@@ -708,11 +709,11 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
708
709
  * // rows is EntityInstance<UserTable>[] (plain objects)
709
710
  * rows[0] instanceof User; // false
710
711
  */
711
- async executePlain(ctx: OrmSession): Promise<EntityInstance<TTable>[]> {
712
- const builder = this.ensureDefaultSelection();
713
- const rows = await executeHydratedPlain(ctx, builder);
714
- return rows as EntityInstance<TTable>[];
715
- }
712
+ async executePlain(ctx: OrmSession): Promise<EntityInstance<TTable>[]> {
713
+ const builder = this.ensureDefaultSelection();
714
+ const rows = await executeHydratedPlain(ctx, builder);
715
+ return rows as EntityInstance<TTable>[];
716
+ }
716
717
 
717
718
  /**
718
719
  * Executes the query and returns results as real class instances.
@@ -894,7 +895,7 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
894
895
  * .where(eq(userTable.columns.active, false));
895
896
  * qb.union(activeUsers).union(inactiveUsers);
896
897
  */
897
- union<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
898
+ union<TSub extends TableDef>(query: SelectQueryBuilder<T, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
898
899
  return this.clone(this.applySetOperation('UNION', query));
899
900
  }
900
901
 
@@ -907,7 +908,7 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
907
908
  * const q2 = new SelectQueryBuilder(userTable).where(lt(userTable.columns.score, 20));
908
909
  * qb.unionAll(q1).unionAll(q2);
909
910
  */
910
- unionAll<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
911
+ unionAll<TSub extends TableDef>(query: SelectQueryBuilder<T, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
911
912
  return this.clone(this.applySetOperation('UNION ALL', query));
912
913
  }
913
914
 
@@ -922,7 +923,7 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
922
923
  * .where(eq(userTable.columns.premium, true));
923
924
  * qb.intersect(activeUsers).intersect(premiumUsers);
924
925
  */
925
- intersect<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
926
+ intersect<TSub extends TableDef>(query: SelectQueryBuilder<T, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
926
927
  return this.clone(this.applySetOperation('INTERSECT', query));
927
928
  }
928
929
 
@@ -936,7 +937,7 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
936
937
  * .where(eq(userTable.columns.active, false));
937
938
  * qb.except(allUsers).except(inactiveUsers); // Only active users
938
939
  */
939
- except<TSub extends TableDef>(query: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
940
+ except<TSub extends TableDef>(query: SelectQueryBuilder<T, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
940
941
  return this.clone(this.applySetOperation('EXCEPT', query));
941
942
  }
942
943
 
@@ -134,20 +134,20 @@ export const col = {
134
134
 
135
135
  /**
136
136
  * Creates a variable character column definition
137
- * @param length - Maximum length of the string
138
- * @returns ColumnDef with VARCHAR type
139
- */
140
- varchar: (length: number): ColumnDef<'VARCHAR'> => ({ name: '', type: 'VARCHAR', args: [length] }),
141
-
142
- /**
143
- * Creates a text column definition
144
- */
145
- text: (): ColumnDef<'TEXT'> => ({ name: '', type: 'TEXT' }),
146
-
147
- /**
148
- * Creates a fixed precision decimal column definition
149
- */
150
- decimal: (precision: number, scale = 0): ColumnDef<'DECIMAL'> => ({
137
+ * @param length - Maximum length of the string
138
+ * @returns ColumnDef with VARCHAR type
139
+ */
140
+ varchar: (length: number): ColumnDef<'VARCHAR'> => ({ name: '', type: 'VARCHAR', args: [length] }),
141
+
142
+ /**
143
+ * Creates a text column definition
144
+ */
145
+ text: (): ColumnDef<'TEXT'> => ({ name: '', type: 'TEXT' }),
146
+
147
+ /**
148
+ * Creates a fixed precision decimal column definition
149
+ */
150
+ decimal: (precision: number, scale = 0): ColumnDef<'DECIMAL'> => ({
151
151
  name: '',
152
152
  type: 'DECIMAL',
153
153
  args: [precision, scale]
@@ -30,20 +30,24 @@ export interface TableOptions {
30
30
  collation?: string;
31
31
  }
32
32
 
33
- export interface TableHooks {
34
- beforeInsert?(ctx: unknown, entity: unknown): Promise<void> | void;
35
- afterInsert?(ctx: unknown, entity: unknown): Promise<void> | void;
36
- beforeUpdate?(ctx: unknown, entity: unknown): Promise<void> | void;
37
- afterUpdate?(ctx: unknown, entity: unknown): Promise<void> | void;
38
- beforeDelete?(ctx: unknown, entity: unknown): Promise<void> | void;
39
- afterDelete?(ctx: unknown, entity: unknown): Promise<void> | void;
33
+ export interface TableHooks<TEntity = unknown, TContext = unknown> {
34
+ beforeInsert?(ctx: TContext, entity: TEntity): Promise<void> | void;
35
+ afterInsert?(ctx: TContext, entity: TEntity): Promise<void> | void;
36
+ beforeUpdate?(ctx: TContext, entity: TEntity): Promise<void> | void;
37
+ afterUpdate?(ctx: TContext, entity: TEntity): Promise<void> | void;
38
+ beforeDelete?(ctx: TContext, entity: TEntity): Promise<void> | void;
39
+ afterDelete?(ctx: TContext, entity: TEntity): Promise<void> | void;
40
40
  }
41
41
 
42
42
  /**
43
43
  * Definition of a database table with its columns and relationships
44
44
  * @typeParam T - Type of the columns record
45
45
  */
46
- export interface TableDef<T extends Record<string, ColumnDef> = Record<string, ColumnDef>> {
46
+ export interface TableDef<
47
+ T extends Record<string, ColumnDef> = Record<string, ColumnDef>,
48
+ TEntity = unknown,
49
+ TContext = unknown
50
+ > {
47
51
  /** Name of the table */
48
52
  name: string;
49
53
  /** Optional schema/catalog name */
@@ -53,7 +57,7 @@ export interface TableDef<T extends Record<string, ColumnDef> = Record<string, C
53
57
  /** Record of relationship definitions keyed by relation name */
54
58
  relations: Record<string, RelationDef>;
55
59
  /** Optional lifecycle hooks */
56
- hooks?: TableHooks;
60
+ hooks?: TableHooks<TEntity, TContext>;
57
61
  /** Composite primary key definition (falls back to column.primary flags) */
58
62
  primaryKey?: string[];
59
63
  /** Secondary indexes */
@@ -85,13 +89,17 @@ export interface TableDef<T extends Record<string, ColumnDef> = Record<string, C
85
89
  * });
86
90
  * ```
87
91
  */
88
- export const defineTable = <T extends Record<string, ColumnDef>>(
89
- name: string,
90
- columns: T,
91
- relations: Record<string, RelationDef> = {},
92
- hooks?: TableHooks,
93
- options: TableOptions = {}
94
- ): TableDef<T> => {
92
+ export const defineTable = <
93
+ T extends Record<string, ColumnDef>,
94
+ TEntity = unknown,
95
+ TContext = unknown
96
+ >(
97
+ name: string,
98
+ columns: T,
99
+ relations: Record<string, RelationDef> = {},
100
+ hooks?: TableHooks<TEntity, TContext>,
101
+ options: TableOptions = {}
102
+ ): TableDef<T, TEntity, TContext> => {
95
103
  // Runtime mutability to assign names to column definitions for convenience
96
104
  const colsWithNames = Object.entries(columns).reduce((acc, [key, def]) => {
97
105
  const colDef = { ...def, name: key, table: name };
@@ -112,21 +120,21 @@ export const defineTable = <T extends Record<string, ColumnDef>>(
112
120
  engine: options.engine,
113
121
  charset: options.charset,
114
122
  collation: options.collation
115
- };
116
- };
117
-
118
- /**
119
- * Assigns relations to a table definition while preserving literal typing.
120
- */
121
- export function setRelations<
122
- TTable extends TableDef,
123
- TRelations extends Record<string, RelationDef>
124
- >(
125
- table: TTable,
126
- relations: TRelations
127
- ): asserts table is TTable & { relations: TRelations } {
128
- table.relations = relations;
129
- }
123
+ };
124
+ };
125
+
126
+ /**
127
+ * Assigns relations to a table definition while preserving literal typing.
128
+ */
129
+ export function setRelations<
130
+ TTable extends TableDef,
131
+ TRelations extends Record<string, RelationDef>
132
+ >(
133
+ table: TTable,
134
+ relations: TRelations
135
+ ): asserts table is TTable & { relations: TRelations } {
136
+ table.relations = relations;
137
+ }
130
138
 
131
139
  type DirectColumnKeys<T extends TableDef> =
132
140
  Exclude<keyof T["columns"] & string, keyof T | "$">;
@@ -12,12 +12,12 @@ import {
12
12
  /**
13
13
  * Resolves a relation definition to its target table type.
14
14
  */
15
- export type RelationTargetTable<TRel extends RelationDef> =
16
- TRel extends HasManyRelation<infer TTarget> ? TTarget :
17
- TRel extends HasOneRelation<infer TTarget> ? TTarget :
18
- TRel extends BelongsToRelation<infer TTarget> ? TTarget :
19
- TRel extends BelongsToManyRelation<infer TTarget, TableDef> ? TTarget :
20
- never;
15
+ export type RelationTargetTable<TRel extends RelationDef> =
16
+ TRel extends HasManyRelation<infer TTarget> ? TTarget :
17
+ TRel extends HasOneRelation<infer TTarget> ? TTarget :
18
+ TRel extends BelongsToRelation<infer TTarget> ? TTarget :
19
+ TRel extends BelongsToManyRelation<infer TTarget, TableDef> ? TTarget :
20
+ never;
21
21
 
22
22
  type NormalizedColumnType<T extends ColumnDef> = Lowercase<T['type'] & string>;
23
23
 
@@ -43,12 +43,12 @@ export type InferRow<TTable extends TableDef> = {
43
43
  [K in keyof TTable['columns']]: ColumnToTs<TTable['columns'][K]>;
44
44
  };
45
45
 
46
- type RelationResult<T extends RelationDef> =
47
- T extends HasManyRelation<infer TTarget> ? InferRow<TTarget>[] :
48
- T extends HasOneRelation<infer TTarget> ? InferRow<TTarget> | null :
49
- T extends BelongsToRelation<infer TTarget> ? InferRow<TTarget> | null :
50
- T extends BelongsToManyRelation<infer TTarget, TableDef> ? (InferRow<TTarget> & { _pivot?: Record<string, unknown> })[] :
51
- never;
46
+ type RelationResult<T extends RelationDef> =
47
+ T extends HasManyRelation<infer TTarget> ? InferRow<TTarget>[] :
48
+ T extends HasOneRelation<infer TTarget> ? InferRow<TTarget> | null :
49
+ T extends BelongsToRelation<infer TTarget> ? InferRow<TTarget> | null :
50
+ T extends BelongsToManyRelation<infer TTarget, infer TPivot> ? (InferRow<TTarget> & { _pivot?: InferRow<TPivot> })[] :
51
+ never;
52
52
 
53
53
  /**
54
54
  * Maps relation names to the expected row results
@@ -57,26 +57,26 @@ export type RelationMap<TTable extends TableDef> = {
57
57
  [K in keyof TTable['relations']]: RelationResult<TTable['relations'][K]>;
58
58
  };
59
59
 
60
- type RelationWrapper<TRel extends RelationDef> =
61
- TRel extends HasManyRelation<infer TTarget>
62
- ? HasManyCollection<EntityInstance<TTarget>> & ReadonlyArray<EntityInstance<TTarget>>
63
- : TRel extends HasOneRelation<infer TTarget>
64
- ? HasOneReference<EntityInstance<TTarget>>
65
- : TRel extends BelongsToManyRelation<infer TTarget>
66
- ? ManyToManyCollection<EntityInstance<TTarget> & { _pivot?: Record<string, unknown> }>
67
- & ReadonlyArray<EntityInstance<TTarget> & { _pivot?: Record<string, unknown> }>
68
- : TRel extends BelongsToRelation<infer TTarget>
69
- ? BelongsToReference<EntityInstance<TTarget>>
70
- : never;
71
-
72
- export interface HasManyCollection<TChild> {
73
- length: number;
74
- [Symbol.iterator](): Iterator<TChild>;
75
- load(): Promise<TChild[]>;
76
- getItems(): TChild[];
77
- add(data: Partial<TChild>): TChild;
78
- attach(entity: TChild): void;
79
- remove(entity: TChild): void;
60
+ type RelationWrapper<TRel extends RelationDef> =
61
+ TRel extends HasManyRelation<infer TTarget>
62
+ ? HasManyCollection<EntityInstance<TTarget>> & ReadonlyArray<EntityInstance<TTarget>>
63
+ : TRel extends HasOneRelation<infer TTarget>
64
+ ? HasOneReference<EntityInstance<TTarget>>
65
+ : TRel extends BelongsToManyRelation<infer TTarget, infer TPivot>
66
+ ? ManyToManyCollection<EntityInstance<TTarget> & { _pivot?: InferRow<TPivot> }>
67
+ & ReadonlyArray<EntityInstance<TTarget> & { _pivot?: InferRow<TPivot> }>
68
+ : TRel extends BelongsToRelation<infer TTarget>
69
+ ? BelongsToReference<EntityInstance<TTarget>>
70
+ : never;
71
+
72
+ export interface HasManyCollection<TChild> {
73
+ length: number;
74
+ [Symbol.iterator](): Iterator<TChild>;
75
+ load(): Promise<TChild[]>;
76
+ getItems(): TChild[];
77
+ add(data: Partial<TChild>): TChild;
78
+ attach(entity: TChild): void;
79
+ remove(entity: TChild): void;
80
80
  clear(): void;
81
81
  }
82
82
 
@@ -96,17 +96,17 @@ export interface HasOneReferenceApi<TChild extends object = object> {
96
96
 
97
97
  export type HasOneReference<TChild extends object = object> = HasOneReferenceApi<TChild> & Partial<TChild>;
98
98
 
99
- export interface ManyToManyCollection<TTarget, TPivot extends object | undefined = undefined> {
100
- length: number;
101
- [Symbol.iterator](): Iterator<TTarget>;
102
- load(): Promise<TTarget[]>;
103
- getItems(): TTarget[];
104
- attach(target: TTarget | number | string): void;
105
- detach(target: TTarget | number | string): void;
106
- syncByIds(ids: (number | string)[]): Promise<void>;
107
- /** @internal Type-level marker for the related pivot entity */
108
- readonly __pivotType?: TPivot;
109
- }
99
+ export interface ManyToManyCollection<TTarget, TPivot extends object | undefined = undefined> {
100
+ length: number;
101
+ [Symbol.iterator](): Iterator<TTarget>;
102
+ load(): Promise<TTarget[]>;
103
+ getItems(): TTarget[];
104
+ attach(target: TTarget | number | string): void;
105
+ detach(target: TTarget | number | string): void;
106
+ syncByIds(ids: (number | string)[]): Promise<void>;
107
+ /** @internal Type-level marker for the related pivot entity */
108
+ readonly __pivotType?: TPivot;
109
+ }
110
110
 
111
111
  export type EntityInstance<
112
112
  TTable extends TableDef,
@@ -117,14 +117,14 @@ export type EntityInstance<
117
117
  $load<K extends keyof RelationMap<TTable>>(relation: K): Promise<RelationMap<TTable>[K]>;
118
118
  };
119
119
 
120
- export type Primitive = string | number | boolean | Date | bigint | Buffer | null | undefined;
121
-
122
- type IsAny<T> = 0 extends (1 & T) ? true : false;
123
-
124
- export type SelectableKeys<T> = {
125
- [K in keyof T]-?: IsAny<T[K]> extends true
126
- ? never
127
- : NonNullable<T[K]> extends Primitive
128
- ? K
129
- : never
130
- }[keyof T];
120
+ export type Primitive = string | number | boolean | Date | bigint | Buffer | null | undefined;
121
+
122
+ type IsAny<T> = 0 extends (1 & T) ? true : false;
123
+
124
+ export type SelectableKeys<T> = {
125
+ [K in keyof T]-?: IsAny<T[K]> extends true
126
+ ? never
127
+ : NonNullable<T[K]> extends Primitive
128
+ ? K
129
+ : never
130
+ }[keyof T];