metal-orm 1.0.62 → 1.0.64

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,18 +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';
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';
17
18
  import { CompiledQuery, Dialect } from '../core/dialect/abstract.js';
18
19
  import { DialectKey, resolveDialectInput } from '../core/dialect/dialect-factory.js';
19
20
 
@@ -31,7 +32,8 @@ import { ColumnSelector } from './column-selector.js';
31
32
  import { RelationIncludeOptions, RelationTargetColumns, TypedRelationIncludeOptions } from './relation-types.js';
32
33
  import { RelationKinds } from '../schema/relation.js';
33
34
  import { JOIN_KINDS, JoinKind, ORDER_DIRECTIONS, OrderDirection } from '../core/sql/sql.js';
34
- import { EntityInstance, RelationMap } from '../schema/types.js';
35
+ import { EntityInstance, RelationMap } from '../schema/types.js';
36
+ import type { ColumnToTs, InferRow } from '../schema/types.js';
35
37
  import { OrmSession } from '../orm/orm-session.ts';
36
38
  import { ExecutionContext } from '../orm/execution-context.js';
37
39
  import { HydrationContext } from '../orm/hydration-context.js';
@@ -55,8 +57,27 @@ import { SelectCTEFacet } from './select/cte-facet.js';
55
57
  import { SelectSetOpFacet } from './select/setop-facet.js';
56
58
  import { SelectRelationFacet } from './select/relation-facet.js';
57
59
 
58
- type ColumnSelectionValue = ColumnDef | FunctionNode | CaseExpressionNode | WindowFunctionNode;
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
81
  type DeepSelectEntry<TTable extends TableDef> = {
61
82
  type: 'root';
62
83
  columns: (keyof TTable['columns'] & string)[];
@@ -133,21 +154,21 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
133
154
  * @param lazyRelations - Updated lazy relations set
134
155
  * @returns New SelectQueryBuilder instance
135
156
  */
136
- private clone(
137
- context: SelectQueryBuilderContext = this.context,
138
- lazyRelations = new Set(this.lazyRelations),
139
- lazyRelationOptions = new Map(this.lazyRelationOptions)
140
- ): SelectQueryBuilder<T, TTable> {
141
- return new SelectQueryBuilder(
142
- this.env.table as TTable,
143
- context.state,
144
- context.hydration,
145
- this.env.deps,
146
- lazyRelations,
147
- lazyRelationOptions,
148
- this.entityConstructor
149
- );
150
- }
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
+ }
151
172
 
152
173
  /**
153
174
  * Applies an alias to the root FROM table.
@@ -213,32 +234,41 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
213
234
  * fullName: concat(userTable.columns.firstName, ' ', userTable.columns.lastName)
214
235
  * });
215
236
  */
216
- select<K extends keyof TTable['columns'] & string>(
217
- ...args: K[]
218
- ): SelectQueryBuilder<T, TTable>;
219
- select(columns: Record<string, ColumnSelectionValue>): SelectQueryBuilder<T, TTable>;
220
- select<K extends keyof TTable['columns'] & string>(
221
- ...args: K[] | [Record<string, ColumnSelectionValue>]
222
- ): SelectQueryBuilder<T, TTable> {
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> {
223
249
  // If first arg is an object (not a string), treat as projection map
224
250
  if (args.length === 1 && typeof args[0] === 'object' && args[0] !== null && typeof args[0] !== 'string') {
225
- const columns = args[0] as Record<string, ColumnSelectionValue>;
226
- return this.clone(this.projectionFacet.select(this.context, columns));
227
- }
251
+ const columns = args[0] as TSelection;
252
+ return this.clone<T & SelectionResult<TSelection>>(
253
+ this.projectionFacet.select(this.context, columns)
254
+ );
255
+ }
228
256
 
229
257
  // Otherwise, treat as column names
230
258
  const cols = args as K[];
231
259
  const selection: Record<string, ColumnDef> = {};
232
- for (const key of cols) {
233
- const col = this.env.table.columns[key];
234
- if (!col) {
235
- throw new Error(`Column '${key}' not found on table '${this.env.table.name}'`);
236
- }
237
- selection[key] = col;
238
- }
239
-
240
- return this.clone(this.projectionFacet.select(this.context, selection));
241
- }
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
+ }
242
272
 
243
273
  /**
244
274
  * Selects raw column expressions
@@ -354,10 +384,15 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
354
384
  * qb.select('id', 'name')
355
385
  * .selectSubquery('postCount', postCount);
356
386
  */
357
- selectSubquery<TSub extends TableDef>(alias: string, sub: SelectQueryBuilder<unknown, TSub> | SelectQueryNode): SelectQueryBuilder<T, TTable> {
358
- const query = resolveSelectQuery(sub);
359
- return this.clone(this.projectionFacet.selectSubquery(this.context, alias, query));
360
- }
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
+ }
361
396
 
362
397
  /**
363
398
  * Adds a JOIN against a derived table (subquery with alias)
@@ -675,7 +710,8 @@ export class SelectQueryBuilder<T = EntityInstance<TableDef>, TTable extends Tab
675
710
  */
676
711
  async executePlain(ctx: OrmSession): Promise<EntityInstance<TTable>[]> {
677
712
  const builder = this.ensureDefaultSelection();
678
- return executeHydratedPlain(ctx, builder) as EntityInstance<TTable>[];
713
+ const rows = await executeHydratedPlain(ctx, builder);
714
+ return rows as EntityInstance<TTable>[];
679
715
  }
680
716
 
681
717
  /**