metal-orm 1.0.62 → 1.0.63
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/index.cjs +58 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -8
- package/dist/index.d.ts +41 -8
- package/dist/index.js +55 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/core/ast/expression.ts +9 -0
- package/src/decorators/bootstrap.ts +23 -19
- package/src/query-builder/hydration-planner.ts +14 -16
- package/src/query-builder/select.ts +91 -55
package/package.json
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Expression AST nodes and builders.
|
|
3
3
|
* Re-exports components for building and visiting SQL expression trees.
|
|
4
4
|
*/
|
|
5
|
+
import type { CaseExpressionNode, FunctionNode, WindowFunctionNode } from './expression-nodes.js';
|
|
6
|
+
|
|
5
7
|
export * from './expression-nodes.js';
|
|
6
8
|
export * from './expression-builders.js';
|
|
7
9
|
export * from './window-functions.js';
|
|
@@ -9,3 +11,10 @@ export * from './aggregate-functions.js';
|
|
|
9
11
|
export * from './expression-visitor.js';
|
|
10
12
|
export type { ColumnRef, TableRef as AstTableRef } from './types.js';
|
|
11
13
|
export * from './adapters.js';
|
|
14
|
+
|
|
15
|
+
export type TypedExpression<T> =
|
|
16
|
+
(FunctionNode | CaseExpressionNode | WindowFunctionNode) & { __tsType: T };
|
|
17
|
+
|
|
18
|
+
export const asType = <T>(
|
|
19
|
+
expr: FunctionNode | CaseExpressionNode | WindowFunctionNode
|
|
20
|
+
): TypedExpression<T> => expr as TypedExpression<T>;
|
|
@@ -25,13 +25,14 @@ import {
|
|
|
25
25
|
|
|
26
26
|
import { tableRef, type TableRef } from '../schema/table.js';
|
|
27
27
|
import {
|
|
28
|
-
SelectableKeys,
|
|
29
|
-
ColumnDef,
|
|
30
|
-
HasManyCollection,
|
|
31
|
-
HasOneReference,
|
|
32
|
-
BelongsToReference,
|
|
33
|
-
ManyToManyCollection
|
|
34
|
-
|
|
28
|
+
SelectableKeys,
|
|
29
|
+
ColumnDef,
|
|
30
|
+
HasManyCollection,
|
|
31
|
+
HasOneReference,
|
|
32
|
+
BelongsToReference,
|
|
33
|
+
ManyToManyCollection,
|
|
34
|
+
EntityInstance
|
|
35
|
+
} from '../schema/types.js';
|
|
35
36
|
|
|
36
37
|
const unwrapTarget = (target: EntityOrTableTargetResolver): EntityOrTableTarget => {
|
|
37
38
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
@@ -196,9 +197,9 @@ type NonFunctionKeys<T> = {
|
|
|
196
197
|
type RelationKeys<TEntity extends object> =
|
|
197
198
|
Exclude<NonFunctionKeys<TEntity>, SelectableKeys<TEntity>> & string;
|
|
198
199
|
|
|
199
|
-
type EntityTable<TEntity extends object> =
|
|
200
|
-
Omit<TableDef<{ [K in SelectableKeys<TEntity>]: ColumnDef }>, 'relations'> & {
|
|
201
|
-
relations: {
|
|
200
|
+
type EntityTable<TEntity extends object> =
|
|
201
|
+
Omit<TableDef<{ [K in SelectableKeys<TEntity>]: ColumnDef }>, 'relations'> & {
|
|
202
|
+
relations: {
|
|
202
203
|
[K in RelationKeys<TEntity>]:
|
|
203
204
|
NonNullable<TEntity[K]> extends HasManyCollection<infer TChild>
|
|
204
205
|
? HasManyRelation<EntityTable<NonNullable<TChild> & object>>
|
|
@@ -214,15 +215,18 @@ type EntityTable<TEntity extends object> =
|
|
|
214
215
|
: NonNullable<TEntity[K]> extends object
|
|
215
216
|
? BelongsToRelation<EntityTable<NonNullable<TEntity[K]> & object>>
|
|
216
217
|
: never;
|
|
217
|
-
};
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
export
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
export type DecoratedEntityInstance<TEntity extends object> =
|
|
222
|
+
TEntity & EntityInstance<EntityTable<TEntity>>;
|
|
223
|
+
|
|
224
|
+
export const selectFromEntity = <TEntity extends object>(
|
|
225
|
+
ctor: EntityConstructor<TEntity>
|
|
226
|
+
): SelectQueryBuilder<DecoratedEntityInstance<TEntity>, EntityTable<TEntity>> => {
|
|
227
|
+
const table = getTableDefFromEntity(ctor);
|
|
228
|
+
if (!table) {
|
|
229
|
+
throw new Error(`Entity '${ctor.name}' is not registered with decorators or has not been bootstrapped`);
|
|
226
230
|
}
|
|
227
231
|
return new SelectQueryBuilder(
|
|
228
232
|
table as unknown as EntityTable<TEntity>,
|
|
@@ -31,22 +31,20 @@ export class HydrationPlanner {
|
|
|
31
31
|
* @param columns - Columns to capture
|
|
32
32
|
* @returns Updated HydrationPlanner with captured columns
|
|
33
33
|
*/
|
|
34
|
-
captureRootColumns(columns: ProjectionNode[]): HydrationPlanner {
|
|
35
|
-
const currentPlan = this.getPlanOrDefault();
|
|
36
|
-
const rootCols = new Set(currentPlan.rootColumns);
|
|
37
|
-
let changed = false;
|
|
38
|
-
|
|
39
|
-
columns.forEach(node => {
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
});
|
|
34
|
+
captureRootColumns(columns: ProjectionNode[]): HydrationPlanner {
|
|
35
|
+
const currentPlan = this.getPlanOrDefault();
|
|
36
|
+
const rootCols = new Set(currentPlan.rootColumns);
|
|
37
|
+
let changed = false;
|
|
38
|
+
|
|
39
|
+
columns.forEach(node => {
|
|
40
|
+
const alias = node.type === 'Column' ? (node.alias || node.name) : node.alias;
|
|
41
|
+
if (!alias || isRelationAlias(alias)) return;
|
|
42
|
+
if (node.type === 'Column' && node.table !== this.table.name) return;
|
|
43
|
+
if (!rootCols.has(alias)) {
|
|
44
|
+
rootCols.add(alias);
|
|
45
|
+
changed = true;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
50
48
|
|
|
51
49
|
if (!changed) return this;
|
|
52
50
|
return new HydrationPlanner(this.table, {
|
|
@@ -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 =
|
|
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<
|
|
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
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
|
226
|
-
return this.clone(
|
|
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
|
|
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<
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
|
|
713
|
+
const rows = await executeHydratedPlain(ctx, builder);
|
|
714
|
+
return rows as EntityInstance<TTable>[];
|
|
679
715
|
}
|
|
680
716
|
|
|
681
717
|
/**
|