metal-orm 1.1.9 → 1.1.10
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/README.md +769 -764
- package/dist/index.cjs +2147 -239
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +559 -39
- package/dist/index.d.ts +559 -39
- package/dist/index.js +2119 -239
- package/dist/index.js.map +1 -1
- package/package.json +17 -12
- package/src/bulk/bulk-context.ts +83 -0
- package/src/bulk/bulk-delete-executor.ts +89 -0
- package/src/bulk/bulk-executor.base.ts +73 -0
- package/src/bulk/bulk-insert-executor.ts +74 -0
- package/src/bulk/bulk-types.ts +70 -0
- package/src/bulk/bulk-update-executor.ts +192 -0
- package/src/bulk/bulk-upsert-executor.ts +95 -0
- package/src/bulk/bulk-utils.ts +91 -0
- package/src/bulk/index.ts +18 -0
- package/src/codegen/typescript.ts +30 -21
- package/src/core/ast/expression-builders.ts +107 -10
- package/src/core/ast/expression-nodes.ts +52 -22
- package/src/core/ast/expression-visitor.ts +23 -13
- package/src/core/dialect/abstract.ts +30 -17
- package/src/core/dialect/mysql/index.ts +20 -5
- package/src/core/execution/db-executor.ts +96 -64
- package/src/core/execution/executors/better-sqlite3-executor.ts +94 -0
- package/src/core/execution/executors/mssql-executor.ts +66 -34
- package/src/core/execution/executors/mysql-executor.ts +98 -66
- package/src/core/execution/executors/postgres-executor.ts +33 -11
- package/src/core/execution/executors/sqlite-executor.ts +86 -30
- package/src/decorators/bootstrap.ts +482 -398
- package/src/decorators/column-decorator.ts +87 -96
- package/src/decorators/decorator-metadata.ts +100 -24
- package/src/decorators/entity.ts +27 -24
- package/src/decorators/relations.ts +231 -149
- package/src/decorators/transformers/transformer-decorators.ts +26 -29
- package/src/decorators/validators/country-validators-decorators.ts +9 -15
- package/src/dto/apply-filter.ts +568 -551
- package/src/index.ts +16 -9
- package/src/orm/entity-hydration.ts +116 -72
- package/src/orm/entity-metadata.ts +347 -301
- package/src/orm/entity-relations.ts +264 -207
- package/src/orm/entity.ts +199 -199
- package/src/orm/execute.ts +13 -13
- package/src/orm/lazy-batch/morph-many.ts +70 -0
- package/src/orm/lazy-batch/morph-one.ts +69 -0
- package/src/orm/lazy-batch/morph-to.ts +59 -0
- package/src/orm/lazy-batch.ts +4 -1
- package/src/orm/orm-session.ts +170 -104
- package/src/orm/pooled-executor-factory.ts +99 -58
- package/src/orm/query-logger.ts +49 -40
- package/src/orm/relation-change-processor.ts +198 -96
- package/src/orm/relations/belongs-to.ts +143 -143
- package/src/orm/relations/has-many.ts +204 -204
- package/src/orm/relations/has-one.ts +174 -174
- package/src/orm/relations/many-to-many.ts +288 -288
- package/src/orm/relations/morph-many.ts +156 -0
- package/src/orm/relations/morph-one.ts +151 -0
- package/src/orm/relations/morph-to.ts +162 -0
- package/src/orm/save-graph.ts +116 -1
- package/src/query-builder/expression-table-mapper.ts +5 -0
- package/src/query-builder/hydration-manager.ts +345 -345
- package/src/query-builder/hydration-planner.ts +178 -148
- package/src/query-builder/relation-conditions.ts +171 -151
- package/src/query-builder/relation-cte-builder.ts +5 -1
- package/src/query-builder/relation-filter-utils.ts +9 -6
- package/src/query-builder/relation-include-strategies.ts +44 -2
- package/src/query-builder/relation-join-strategies.ts +8 -1
- package/src/query-builder/relation-service.ts +250 -241
- package/src/query-builder/select/select-operations.ts +110 -105
- package/src/query-builder/update-include.ts +4 -0
- package/src/schema/relation.ts +296 -188
- package/src/schema/types.ts +138 -123
- package/src/tree/tree-decorator.ts +127 -137
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { TableDef } from '../../schema/table.js';
|
|
2
|
+
import { isSingleTargetRelation } from '../../schema/relation.js';
|
|
2
3
|
import { ColumnDef } from '../../schema/column-types.js';
|
|
3
4
|
import { OrderingTerm, SelectQueryNode } from '../../core/ast/query.js';
|
|
4
5
|
import { ColumnNode, FunctionNode, ExpressionNode, exists, notExists } from '../../core/ast/expression.js';
|
|
@@ -12,35 +13,35 @@ import { OrmSession } from '../../orm/orm-session.js';
|
|
|
12
13
|
import type { SelectQueryBuilder } from '../select.js';
|
|
13
14
|
import { findPrimaryKey } from '../hydration-planner.js';
|
|
14
15
|
import { payloadResultSets } from '../../core/execution/db-executor.js';
|
|
15
|
-
|
|
16
|
-
export type WhereHasOptions = {
|
|
17
|
-
correlate?: ExpressionNode;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export type RelationCallback = <TChildTable extends TableDef>(
|
|
21
|
-
qb: SelectQueryBuilder<unknown, TChildTable>
|
|
22
|
-
) => SelectQueryBuilder<unknown, TChildTable>;
|
|
23
|
-
|
|
24
|
-
type ChildBuilderFactory = <R, TChild extends TableDef>(table: TChild) => SelectQueryBuilder<R, TChild>;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Builds a new query context with an ORDER BY clause applied.
|
|
28
|
-
*/
|
|
29
|
-
export function applyOrderBy(
|
|
30
|
-
context: SelectQueryBuilderContext,
|
|
31
|
-
predicateFacet: SelectPredicateFacet,
|
|
32
|
-
term: ColumnDef | OrderingTerm,
|
|
33
|
-
directionOrOptions: OrderDirection | { direction?: OrderDirection; nulls?: 'FIRST' | 'LAST'; collation?: string }
|
|
34
|
-
): SelectQueryBuilderContext {
|
|
35
|
-
const options =
|
|
36
|
-
typeof directionOrOptions === 'string' ? { direction: directionOrOptions } : directionOrOptions;
|
|
37
|
-
const dir = options.direction ?? ORDER_DIRECTIONS.ASC;
|
|
38
|
-
return predicateFacet.orderBy(context, term, dir, options.nulls, options.collation);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Runs the count query for the provided context and session.
|
|
43
|
-
*/
|
|
16
|
+
|
|
17
|
+
export type WhereHasOptions = {
|
|
18
|
+
correlate?: ExpressionNode;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type RelationCallback = <TChildTable extends TableDef>(
|
|
22
|
+
qb: SelectQueryBuilder<unknown, TChildTable>
|
|
23
|
+
) => SelectQueryBuilder<unknown, TChildTable>;
|
|
24
|
+
|
|
25
|
+
type ChildBuilderFactory = <R, TChild extends TableDef>(table: TChild) => SelectQueryBuilder<R, TChild>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Builds a new query context with an ORDER BY clause applied.
|
|
29
|
+
*/
|
|
30
|
+
export function applyOrderBy(
|
|
31
|
+
context: SelectQueryBuilderContext,
|
|
32
|
+
predicateFacet: SelectPredicateFacet,
|
|
33
|
+
term: ColumnDef | OrderingTerm,
|
|
34
|
+
directionOrOptions: OrderDirection | { direction?: OrderDirection; nulls?: 'FIRST' | 'LAST'; collation?: string }
|
|
35
|
+
): SelectQueryBuilderContext {
|
|
36
|
+
const options =
|
|
37
|
+
typeof directionOrOptions === 'string' ? { direction: directionOrOptions } : directionOrOptions;
|
|
38
|
+
const dir = options.direction ?? ORDER_DIRECTIONS.ASC;
|
|
39
|
+
return predicateFacet.orderBy(context, term, dir, options.nulls, options.collation);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Runs the count query for the provided context and session.
|
|
44
|
+
*/
|
|
44
45
|
export async function executeCount(
|
|
45
46
|
context: SelectQueryBuilderContext,
|
|
46
47
|
env: SelectQueryBuilderEnvironment,
|
|
@@ -103,10 +104,10 @@ export async function executeCount(
|
|
|
103
104
|
const payload = await execCtx.interceptors.run({ sql: compiled.sql, params: compiled.params }, execCtx.executor);
|
|
104
105
|
const results = payloadResultSets(payload);
|
|
105
106
|
const value = results[0]?.values?.[0]?.[0];
|
|
106
|
-
|
|
107
|
-
if (typeof value === 'number') return value;
|
|
108
|
-
if (typeof value === 'bigint') return Number(value);
|
|
109
|
-
if (typeof value === 'string') return Number(value);
|
|
107
|
+
|
|
108
|
+
if (typeof value === 'number') return value;
|
|
109
|
+
if (typeof value === 'bigint') return Number(value);
|
|
110
|
+
if (typeof value === 'string') return Number(value);
|
|
110
111
|
return value === null || value === undefined ? 0 : Number(value);
|
|
111
112
|
}
|
|
112
113
|
|
|
@@ -156,75 +157,79 @@ export async function executeCountRows(
|
|
|
156
157
|
if (typeof value === 'string') return Number(value);
|
|
157
158
|
return value === null || value === undefined ? 0 : Number(value);
|
|
158
159
|
}
|
|
159
|
-
|
|
160
|
-
export interface PaginatedResult<T> {
|
|
161
|
-
items: T[];
|
|
162
|
-
totalItems: number;
|
|
163
|
-
page: number;
|
|
164
|
-
pageSize: number;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Executes paged queries using the provided builder helpers.
|
|
169
|
-
*/
|
|
170
|
-
export async function executePagedQuery<T, TTable extends TableDef>(
|
|
171
|
-
builder: SelectQueryBuilder<T, TTable>,
|
|
172
|
-
session: OrmSession,
|
|
173
|
-
options: { page: number; pageSize: number },
|
|
174
|
-
countCallback: (session: OrmSession) => Promise<number>
|
|
175
|
-
): Promise<PaginatedResult<T>> {
|
|
176
|
-
const { page, pageSize } = options;
|
|
177
|
-
|
|
178
|
-
if (!Number.isInteger(page) || page < 1) {
|
|
179
|
-
throw new Error('executePaged: page must be an integer >= 1');
|
|
180
|
-
}
|
|
181
|
-
if (!Number.isInteger(pageSize) || pageSize < 1) {
|
|
182
|
-
throw new Error('executePaged: pageSize must be an integer >= 1');
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const offset = (page - 1) * pageSize;
|
|
186
|
-
|
|
187
|
-
const totalItems = await countCallback(session);
|
|
188
|
-
const items = await builder.limit(pageSize).offset(offset).execute(session);
|
|
189
|
-
|
|
190
|
-
return { items, totalItems, page, pageSize };
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Builds an EXISTS or NOT EXISTS predicate for a related table.
|
|
195
|
-
*/
|
|
196
|
-
export function buildWhereHasPredicate<TTable extends TableDef>(
|
|
197
|
-
env: SelectQueryBuilderEnvironment,
|
|
198
|
-
context: SelectQueryBuilderContext,
|
|
199
|
-
relationFacet: SelectRelationFacet,
|
|
200
|
-
createChildBuilder: ChildBuilderFactory,
|
|
201
|
-
relationName: keyof TTable['relations'] & string,
|
|
202
|
-
callbackOrOptions?: RelationCallback | WhereHasOptions,
|
|
203
|
-
maybeOptions?: WhereHasOptions,
|
|
204
|
-
negate = false
|
|
205
|
-
): ExpressionNode {
|
|
206
|
-
const relation = env.table.relations[relationName as string];
|
|
207
|
-
if (!relation) {
|
|
208
|
-
throw new Error(`Relation '${relationName}' not found on table '${env.table.name}'`);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const callback = typeof callbackOrOptions === 'function' ? callbackOrOptions : undefined;
|
|
212
|
-
const options = (typeof callbackOrOptions === 'function' ? maybeOptions : callbackOrOptions) as
|
|
213
|
-
| WhereHasOptions
|
|
214
|
-
| undefined;
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
160
|
+
|
|
161
|
+
export interface PaginatedResult<T> {
|
|
162
|
+
items: T[];
|
|
163
|
+
totalItems: number;
|
|
164
|
+
page: number;
|
|
165
|
+
pageSize: number;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Executes paged queries using the provided builder helpers.
|
|
170
|
+
*/
|
|
171
|
+
export async function executePagedQuery<T, TTable extends TableDef>(
|
|
172
|
+
builder: SelectQueryBuilder<T, TTable>,
|
|
173
|
+
session: OrmSession,
|
|
174
|
+
options: { page: number; pageSize: number },
|
|
175
|
+
countCallback: (session: OrmSession) => Promise<number>
|
|
176
|
+
): Promise<PaginatedResult<T>> {
|
|
177
|
+
const { page, pageSize } = options;
|
|
178
|
+
|
|
179
|
+
if (!Number.isInteger(page) || page < 1) {
|
|
180
|
+
throw new Error('executePaged: page must be an integer >= 1');
|
|
181
|
+
}
|
|
182
|
+
if (!Number.isInteger(pageSize) || pageSize < 1) {
|
|
183
|
+
throw new Error('executePaged: pageSize must be an integer >= 1');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const offset = (page - 1) * pageSize;
|
|
187
|
+
|
|
188
|
+
const totalItems = await countCallback(session);
|
|
189
|
+
const items = await builder.limit(pageSize).offset(offset).execute(session);
|
|
190
|
+
|
|
191
|
+
return { items, totalItems, page, pageSize };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Builds an EXISTS or NOT EXISTS predicate for a related table.
|
|
196
|
+
*/
|
|
197
|
+
export function buildWhereHasPredicate<TTable extends TableDef>(
|
|
198
|
+
env: SelectQueryBuilderEnvironment,
|
|
199
|
+
context: SelectQueryBuilderContext,
|
|
200
|
+
relationFacet: SelectRelationFacet,
|
|
201
|
+
createChildBuilder: ChildBuilderFactory,
|
|
202
|
+
relationName: keyof TTable['relations'] & string,
|
|
203
|
+
callbackOrOptions?: RelationCallback | WhereHasOptions,
|
|
204
|
+
maybeOptions?: WhereHasOptions,
|
|
205
|
+
negate = false
|
|
206
|
+
): ExpressionNode {
|
|
207
|
+
const relation = env.table.relations[relationName as string];
|
|
208
|
+
if (!relation) {
|
|
209
|
+
throw new Error(`Relation '${relationName}' not found on table '${env.table.name}'`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const callback = typeof callbackOrOptions === 'function' ? callbackOrOptions : undefined;
|
|
213
|
+
const options = (typeof callbackOrOptions === 'function' ? maybeOptions : callbackOrOptions) as
|
|
214
|
+
| WhereHasOptions
|
|
215
|
+
| undefined;
|
|
216
|
+
|
|
217
|
+
if (!isSingleTargetRelation(relation)) {
|
|
218
|
+
throw new Error(`Polymorphic relation '${relationName}' does not support whereHas/whereHasNot`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
let subQb = createChildBuilder<unknown, TableDef>(relation.target);
|
|
222
|
+
if (callback) {
|
|
223
|
+
subQb = callback(subQb);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const subAst = subQb.getAST();
|
|
227
|
+
const finalSubAst = relationFacet.applyRelationCorrelation(
|
|
228
|
+
context,
|
|
229
|
+
relationName,
|
|
230
|
+
subAst,
|
|
231
|
+
options?.correlate
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
return negate ? notExists(finalSubAst) : exists(finalSubAst);
|
|
235
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { JOIN_KINDS } from '../core/sql/sql.js';
|
|
2
2
|
import type { TableDef } from '../schema/table.js';
|
|
3
|
+
import { isSingleTargetRelation } from '../schema/relation.js';
|
|
3
4
|
import { findJoinByRelationKey, findJoinIndexByRelationKey } from './join-utils.js';
|
|
4
5
|
import { addRelationJoin, updateRelationJoin } from './relation-join-strategies.js';
|
|
5
6
|
import { getExposedName } from './table-alias-utils.js';
|
|
@@ -92,6 +93,9 @@ export const updateInclude = <T, TTable extends TableDef>(
|
|
|
92
93
|
});
|
|
93
94
|
}
|
|
94
95
|
|
|
96
|
+
if (!isSingleTargetRelation(relation)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
95
99
|
const joinForSegment = findJoinByRelationKey(state.ast.joins, relationKey);
|
|
96
100
|
currentAlias = joinForSegment ? (getExposedName(joinForSegment.table) ?? relation.target.name) : relation.target.name;
|
|
97
101
|
currentTable = relation.target;
|