typeorm-extensions 2.2.0-rc.3 → 2.2.0-rc.4
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/extensions/get-many-with-totals.extension.js +7 -3
- package/dist/extensions/typed/typed-distinct/typed-distinct.extension.js +1 -1
- package/dist/extensions/typed/typed-group-by/typed-group-by.extension.d.ts +6 -5
- package/dist/extensions/typed/typed-group-by/typed-group-by.extension.js +1 -1
- package/dist/extensions/typed/typed-order-by/typed-order-by.extension.js +2 -2
- package/dist/extensions/typed/typed-select/typed-select.extension.js +2 -2
- package/dist/utils/query-builder.utils.d.ts +6 -1
- package/dist/utils/query-builder.utils.js +78 -2
- package/dist/utils/where.utils.js +6 -0
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ const SelectQueryBuilder_1 = require("typeorm/query-builder/SelectQueryBuilder")
|
|
|
5
5
|
const pagination_utils_1 = require("../utils/pagination.utils");
|
|
6
6
|
const constants_1 = require("../constants");
|
|
7
7
|
const prototype_utils_1 = require("../utils/prototype.utils");
|
|
8
|
+
const query_builder_utils_1 = require("../utils/query-builder.utils");
|
|
8
9
|
// patching
|
|
9
10
|
const extension = {
|
|
10
11
|
prototype: {
|
|
@@ -111,9 +112,12 @@ async function readWithTotals(builder, paginationFilter, options, read) {
|
|
|
111
112
|
}
|
|
112
113
|
// allOrderBys, not orderBys: it is what the SQL is built from, so without an explicit order it
|
|
113
114
|
// falls back to the `orderBy` of @Entity, and respects the `disable-global-order` option
|
|
114
|
-
const orderBy = Object.entries((_a = builder.expressionMap.allOrderBys) !== null && _a !== void 0 ? _a : {}).map(([
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
const orderBy = Object.entries((_a = builder.expressionMap.allOrderBys) !== null && _a !== void 0 ? _a : {}).map(([key, value]) => {
|
|
116
|
+
const selector = (0, query_builder_utils_1.resolveDeferredKeys)(builder, key);
|
|
117
|
+
return typeof value === 'string'
|
|
118
|
+
? { selector, order: value }
|
|
119
|
+
: { selector, order: value.order, nulls: value.nulls };
|
|
120
|
+
});
|
|
117
121
|
return {
|
|
118
122
|
list,
|
|
119
123
|
total,
|
|
@@ -5,7 +5,7 @@ const query_builder_utils_1 = require("../../../utils/query-builder.utils");
|
|
|
5
5
|
// patching and implementation
|
|
6
6
|
typeorm_1.SelectQueryBuilder.prototype.distinctOnTyped = function (...distinctOn) {
|
|
7
7
|
if (Array.isArray(distinctOn) && distinctOn.length) {
|
|
8
|
-
const keys = distinctOn.map((callback) => (0, query_builder_utils_1.getTypedSelectionKey)(this, callback)).flat();
|
|
8
|
+
const keys = distinctOn.map((callback) => (0, query_builder_utils_1.getTypedSelectionKey)(this, callback, undefined, 'distinctOnTyped')).flat();
|
|
9
9
|
return this.distinctOn(keys);
|
|
10
10
|
}
|
|
11
11
|
return this;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { ObjectLiteral } from 'typeorm';
|
|
2
|
-
import type {
|
|
2
|
+
import type { KeyProxyCallback } from '../../../types/modules/proxy-callback.types';
|
|
3
3
|
import type { TypedGroupByOptions } from '../../../types/extensions/typed-group-by.types';
|
|
4
4
|
import type { TypedRoot } from '../../../types/modules/typed-root.types';
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
/**
|
|
6
|
+
* One column, `user => user.id`, or several, `user => [user.id, user.email]`. A JSON column groups as a whole;
|
|
7
|
+
* a relation without a join column in its table, or an embedded entity, throws a `TypeError`.
|
|
8
|
+
*/
|
|
9
|
+
type GroupBySelection<Type extends ObjectLiteral> = KeyProxyCallback<Type>;
|
|
9
10
|
export interface TypedGroupByMethods<Entity extends ObjectLiteral> {
|
|
10
11
|
/**
|
|
11
12
|
* Sets GROUP BY to the columns the callback selects: `user => user.id`, or several at once,
|
|
@@ -23,7 +23,7 @@ const extension = {
|
|
|
23
23
|
(0, prototype_utils_1.patchPrototype)(typeorm_1.SelectQueryBuilder, extension);
|
|
24
24
|
// implementation
|
|
25
25
|
function toGroupByKeys(builder, selection, options) {
|
|
26
|
-
const keys = (0, query_builder_utils_1.getTypedSelectionKey)(builder, selection, options === null || options === void 0 ? void 0 : options.useQuotes);
|
|
26
|
+
const keys = (0, query_builder_utils_1.getTypedSelectionKey)(builder, selection, options === null || options === void 0 ? void 0 : options.useQuotes, 'groupByTyped');
|
|
27
27
|
return Array.isArray(keys) ? keys : [keys];
|
|
28
28
|
}
|
|
29
29
|
function addGroupByKeys(builder, keys) {
|
|
@@ -10,7 +10,7 @@ typeorm_1.SelectQueryBuilder.prototype.orderByTyped = function (selection, order
|
|
|
10
10
|
return this.orderBy(selection.toSql(undefined, options === null || options === void 0 ? void 0 : options.useQuotes, (0, sql_utils_1.getDriverType)(this)), order, nulls);
|
|
11
11
|
}
|
|
12
12
|
if (typeof selection === 'function') {
|
|
13
|
-
const key = (0, query_builder_utils_1.getTypedSelectionKey)(this, selection, options === null || options === void 0 ? void 0 : options.useQuotes);
|
|
13
|
+
const key = (0, query_builder_utils_1.getTypedSelectionKey)(this, selection, options === null || options === void 0 ? void 0 : options.useQuotes, 'orderByTyped');
|
|
14
14
|
return this.orderBy(key, order, nulls);
|
|
15
15
|
}
|
|
16
16
|
return this;
|
|
@@ -20,7 +20,7 @@ typeorm_1.SelectQueryBuilder.prototype.addOrderByTyped = function (selection, or
|
|
|
20
20
|
return this.addOrderBy(selection.toSql(undefined, options === null || options === void 0 ? void 0 : options.useQuotes, (0, sql_utils_1.getDriverType)(this)), order, nulls);
|
|
21
21
|
}
|
|
22
22
|
if (typeof selection === 'function') {
|
|
23
|
-
const key = (0, query_builder_utils_1.getTypedSelectionKey)(this, selection, options === null || options === void 0 ? void 0 : options.useQuotes);
|
|
23
|
+
const key = (0, query_builder_utils_1.getTypedSelectionKey)(this, selection, options === null || options === void 0 ? void 0 : options.useQuotes, 'addOrderByTyped');
|
|
24
24
|
return this.addOrderBy(key, order, nulls);
|
|
25
25
|
}
|
|
26
26
|
return this;
|
|
@@ -29,11 +29,11 @@ const attachSelectMethod = (query, method, selection, selectionAliasName, useQuo
|
|
|
29
29
|
if (Array.isArray(selection)) {
|
|
30
30
|
const arrayOfKeys = selection.flatMap((select) => select instanceof proxy_key_utils_1.KeyHolder
|
|
31
31
|
? (0, proxy_key_utils_1.getSqlKeyFromProxyCallback)(select, () => query.alias, useQuotes, (0, sql_utils_1.getDriverType)(query))
|
|
32
|
-
: (0, query_builder_utils_1.getTypedSelectionKey)(query, select, useQuotes));
|
|
32
|
+
: (0, query_builder_utils_1.getTypedSelectionKey)(query, select, useQuotes, `${method}Typed`));
|
|
33
33
|
return query[method](arrayOfKeys);
|
|
34
34
|
}
|
|
35
35
|
else if (typeof selection === 'function') {
|
|
36
|
-
const keyArrayOrSingleKey = (0, query_builder_utils_1.getTypedSelectionKey)(query, selection, useQuotes);
|
|
36
|
+
const keyArrayOrSingleKey = (0, query_builder_utils_1.getTypedSelectionKey)(query, selection, useQuotes, `${method}Typed`);
|
|
37
37
|
if (Array.isArray(keyArrayOrSingleKey)) {
|
|
38
38
|
return query[method](keyArrayOrSingleKey);
|
|
39
39
|
}
|
|
@@ -2,6 +2,11 @@ import type { ObjectLiteral, WhereExpressionBuilder } from 'typeorm';
|
|
|
2
2
|
import type { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata';
|
|
3
3
|
import type { KeyProxyCallback } from '../types/modules/proxy-callback.types';
|
|
4
4
|
import { JsonPathSqlOptions } from './json-path.utils';
|
|
5
|
+
/**
|
|
6
|
+
* Replaces placeholders of `deferColumnKey` in SQL built by `builder`: the database column through the entity of
|
|
7
|
+
* the alias, quoted, `"usr"."receipts_email"`. An alias still unknown or without an entity keeps the name.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveDeferredKeys(builder: WhereExpressionBuilder, statement: string): string;
|
|
5
10
|
/**
|
|
6
11
|
* Database name of an entity property's column (`@Column({ name })`), looked up in the metadata of
|
|
7
12
|
* a query alias (the main alias when `aliasName` is undefined). `undefined` when the alias has no
|
|
@@ -56,4 +61,4 @@ export declare function getTypedSingleColumnKey<Type extends ObjectLiteral>(buil
|
|
|
56
61
|
* TypeORM leaves its keys as written, and PostgreSQL folds an unquoted `cte.firstName` to `firstname`.
|
|
57
62
|
* Strings are kept as is.
|
|
58
63
|
*/
|
|
59
|
-
export declare function getTypedSelectionKey<Type extends ObjectLiteral>(builder: WhereExpressionBuilder, selection: KeyProxyCallback<Type> | string, useQuotes?: boolean): string | string[];
|
|
64
|
+
export declare function getTypedSelectionKey<Type extends ObjectLiteral>(builder: WhereExpressionBuilder, selection: KeyProxyCallback<Type> | string, useQuotes?: boolean, columnsFor?: string): string | string[];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getTypedSelectionKey = exports.getTypedSingleColumnKey = exports.isTypedJsonPath = exports.getTypedColumnKey = exports.toFieldSql = exports.findFieldColumn = exports.getColumnDatabaseName = void 0;
|
|
3
|
+
exports.getTypedSelectionKey = exports.getTypedSingleColumnKey = exports.isTypedJsonPath = exports.getTypedColumnKey = exports.toFieldSql = exports.findFieldColumn = exports.getColumnDatabaseName = exports.resolveDeferredKeys = void 0;
|
|
4
|
+
const typeorm_1 = require("typeorm");
|
|
4
5
|
const proxy_key_utils_1 = require("./proxy-key.utils");
|
|
5
6
|
const json_path_utils_1 = require("./json-path.utils");
|
|
6
7
|
const sql_utils_1 = require("./sql.utils");
|
|
@@ -47,6 +48,55 @@ function findQueryAlias(queryBuilder, aliasName) {
|
|
|
47
48
|
}
|
|
48
49
|
return undefined;
|
|
49
50
|
}
|
|
51
|
+
// private-use characters: no identifier or SQL that TypeORM builds contains them
|
|
52
|
+
const DEFERRED_KEY_START = '\uE000';
|
|
53
|
+
const DEFERRED_PROPERTY = '\uE001';
|
|
54
|
+
const DEFERRED_FIELD = '\uE002';
|
|
55
|
+
const DEFERRED_KEY_END = '\uE003';
|
|
56
|
+
const DEFERRED_KEY = /\uE000([^\uE000-\uE003]+)([\uE001\uE002])([^\uE000-\uE003]+)\uE003/g;
|
|
57
|
+
const TWO_PART_FIELD = /^([A-Za-z_][\w$]*)\.([A-Za-z_][\w$]*)$/;
|
|
58
|
+
/**
|
|
59
|
+
* Placeholder for a column of an alias the query doesn't have yet, e.g. a join added after the call. TypeORM maps
|
|
60
|
+
* property names when it builds the SQL, and so does `resolveDeferredKeys` with these, once the alias is known.
|
|
61
|
+
*/
|
|
62
|
+
function deferColumnKey(aliasName, separator, name) {
|
|
63
|
+
return `${DEFERRED_KEY_START}${aliasName}${separator}${name}${DEFERRED_KEY_END}`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Replaces placeholders of `deferColumnKey` in SQL built by `builder`: the database column through the entity of
|
|
67
|
+
* the alias, quoted, `"usr"."receipts_email"`. An alias still unknown or without an entity keeps the name.
|
|
68
|
+
*/
|
|
69
|
+
function resolveDeferredKeys(builder, statement) {
|
|
70
|
+
if (typeof statement !== 'string' || !statement.includes(DEFERRED_KEY_START)) {
|
|
71
|
+
return statement;
|
|
72
|
+
}
|
|
73
|
+
const queryBuilder = builder;
|
|
74
|
+
return statement.replace(DEFERRED_KEY, (_match, aliasName, separator, name) => {
|
|
75
|
+
const metadata = getAliasMetadata(queryBuilder, aliasName);
|
|
76
|
+
const column = metadata && (separator === DEFERRED_FIELD ? findColumn(metadata, name) : metadata.findColumnWithPropertyPath(name));
|
|
77
|
+
const columnName = column && !column.isVirtualProperty ? column.databaseName : name;
|
|
78
|
+
return `${queryBuilder.escape(aliasName)}.${queryBuilder.escape(columnName)}`;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
exports.resolveDeferredKeys = resolveDeferredKeys;
|
|
82
|
+
const DEFERRED_KEYS_INSTALLED = Symbol.for('typeorm-extensions.deferredKeys');
|
|
83
|
+
// TypeORM 0.3.12 maps property names in each part of the query, later versions once in the whole query
|
|
84
|
+
function installDeferredKeyResolution() {
|
|
85
|
+
const prototype = typeorm_1.QueryBuilder.prototype;
|
|
86
|
+
if (prototype[DEFERRED_KEYS_INSTALLED]) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
prototype[DEFERRED_KEYS_INSTALLED] = true;
|
|
90
|
+
for (const method of ['replacePropertyNames', 'replacePropertyNamesForTheWholeQuery']) {
|
|
91
|
+
const original = prototype[method];
|
|
92
|
+
if (typeof original === 'function') {
|
|
93
|
+
prototype[method] = function (statement) {
|
|
94
|
+
return resolveDeferredKeys(this, original.call(this, statement));
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
installDeferredKeyResolution();
|
|
50
100
|
/** Entity metadata of a query alias (the main alias when `aliasName` is undefined), if it has any. */
|
|
51
101
|
function getAliasMetadata(queryBuilder, aliasName) {
|
|
52
102
|
var _a;
|
|
@@ -112,6 +162,10 @@ function toFieldSql(builder, field) {
|
|
|
112
162
|
const columnSql = queryBuilder.escape(column.databaseName);
|
|
113
163
|
return isAliasPrefixingEnabled(queryBuilder) ? `${queryBuilder.escape(aliasName)}.${columnSql}` : columnSql;
|
|
114
164
|
}
|
|
165
|
+
const parts = TWO_PART_FIELD.exec(field);
|
|
166
|
+
if (parts && !findQueryAlias(queryBuilder, aliasName)) {
|
|
167
|
+
return deferColumnKey(parts[1], DEFERRED_FIELD, parts[2]);
|
|
168
|
+
}
|
|
115
169
|
}
|
|
116
170
|
else {
|
|
117
171
|
// only by database name: an unqualified property name may mean another table's column
|
|
@@ -222,6 +276,9 @@ function resolveTypedKeyPath(queryBuilder, keys) {
|
|
|
222
276
|
*/
|
|
223
277
|
function toQuotedColumnKey(queryBuilder, { aliasName, propertyPath, metadata, column, jsonPath }, jsonOptions) {
|
|
224
278
|
var _a, _b;
|
|
279
|
+
if (aliasName !== undefined && !metadata && !findQueryAlias(queryBuilder, aliasName)) {
|
|
280
|
+
return deferColumnKey(aliasName, DEFERRED_PROPERTY, propertyPath);
|
|
281
|
+
}
|
|
225
282
|
const columnName = (_b = (_a = (column !== null && column !== void 0 ? column : metadata === null || metadata === void 0 ? void 0 : metadata.findColumnWithPropertyPath(propertyPath))) === null || _a === void 0 ? void 0 : _a.databaseName) !== null && _b !== void 0 ? _b : propertyPath;
|
|
226
283
|
const columnSql = aliasName === undefined
|
|
227
284
|
? queryBuilder.escape(columnName)
|
|
@@ -268,6 +325,22 @@ function getTypedSingleColumnKey(builder, callback, methodName, jsonOptions) {
|
|
|
268
325
|
return key;
|
|
269
326
|
}
|
|
270
327
|
exports.getTypedSingleColumnKey = getTypedSingleColumnKey;
|
|
328
|
+
/**
|
|
329
|
+
* Throws for a key that names no column of its table: a relation whose join column is on the other side
|
|
330
|
+
* (one-to-many, many-to-many, the inverse side of a one-to-one) or an embedded entity. TypeORM leaves such a key
|
|
331
|
+
* as written, and the database rejects it.
|
|
332
|
+
*/
|
|
333
|
+
function assertTableColumnKey({ metadata, propertyPath }, keys, methodName) {
|
|
334
|
+
const relation = metadata === null || metadata === void 0 ? void 0 : metadata.findRelationWithPropertyPath(propertyPath);
|
|
335
|
+
if (relation && !relation.isManyToOne && !relation.isOneToOneOwner) {
|
|
336
|
+
throw new TypeError(`${methodName}: "${keys.join('.')}" is a relation without a join column in its table. ` +
|
|
337
|
+
`Join it and address its columns by the join alias`);
|
|
338
|
+
}
|
|
339
|
+
if (!relation && (metadata === null || metadata === void 0 ? void 0 : metadata.findEmbeddedWithPropertyPath(propertyPath))) {
|
|
340
|
+
throw new TypeError(`${methodName}: "${keys.join('.')}" is an embedded entity, not a column: address its columns, e.g. ` +
|
|
341
|
+
`"${keys.join('.')}.someColumn"`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
271
344
|
// an alias the query doesn't know yet may still get metadata (a join added later), so it stays unquoted
|
|
272
345
|
function isAliasWithoutMetadata(queryBuilder, aliasName) {
|
|
273
346
|
const alias = aliasName === undefined ? undefined : findQueryAlias(queryBuilder, aliasName);
|
|
@@ -283,7 +356,7 @@ function isAliasWithoutMetadata(queryBuilder, aliasName) {
|
|
|
283
356
|
* TypeORM leaves its keys as written, and PostgreSQL folds an unquoted `cte.firstName` to `firstname`.
|
|
284
357
|
* Strings are kept as is.
|
|
285
358
|
*/
|
|
286
|
-
function getTypedSelectionKey(builder, selection, useQuotes) {
|
|
359
|
+
function getTypedSelectionKey(builder, selection, useQuotes, columnsFor) {
|
|
287
360
|
if (typeof selection === 'string') {
|
|
288
361
|
return selection;
|
|
289
362
|
}
|
|
@@ -294,6 +367,9 @@ function getTypedSelectionKey(builder, selection, useQuotes) {
|
|
|
294
367
|
throw new TypeError(`Typed key "${keyHolder.getKeys().join('.')}" points into the JSON column "${resolved.propertyPath}": ` +
|
|
295
368
|
`select, order, distinct and group by take columns only. For an expression use tableHelper().jsonColumn()`);
|
|
296
369
|
}
|
|
370
|
+
if (columnsFor !== undefined) {
|
|
371
|
+
assertTableColumnKey(resolved, keyHolder.getKeys(), columnsFor);
|
|
372
|
+
}
|
|
297
373
|
if (useQuotes !== null && useQuotes !== void 0 ? useQuotes : isAliasWithoutMetadata(queryBuilder, resolved.aliasName)) {
|
|
298
374
|
return toQuotedColumnKey(queryBuilder, resolved);
|
|
299
375
|
}
|
|
@@ -13,6 +13,8 @@ function getValuePlaceholder(value, paramName) {
|
|
|
13
13
|
return `:${paramName}`;
|
|
14
14
|
}
|
|
15
15
|
exports.getValuePlaceholder = getValuePlaceholder;
|
|
16
|
+
/** A condition that ends with an operator, `=`, `>=`, `LIKE`, `IN`, `@>`: it needs a value after it. */
|
|
17
|
+
const OPERATOR_AT_END = /(?:[=<>!~@&|?]|\b(?:LIKE|ILIKE|IN|SIMILAR TO|DISTINCT FROM|BETWEEN|ANY|ALL))\s*$/i;
|
|
16
18
|
const isDate = (value) => Object.prototype.toString.call(value) === '[object Date]';
|
|
17
19
|
/**
|
|
18
20
|
* Objects that are parameter values themselves, not `{ name: value }` params objects:
|
|
@@ -76,6 +78,10 @@ function attachWhere(builder, method, field, conditions, params, options) {
|
|
|
76
78
|
const hasParams = typeof params !== 'undefined';
|
|
77
79
|
// `addValue` defaults to `true` unless `conditions` already references one of the `params` keys
|
|
78
80
|
addValue = addValue !== null && addValue !== void 0 ? addValue : !hasParamPlaceholder(conditions, params);
|
|
81
|
+
if (addValue && !hasParams && OPERATOR_AT_END.test(conditions)) {
|
|
82
|
+
throw new TypeError(`The value after "${fieldName} ${conditions.trim()}" is undefined, which leaves the condition without ` +
|
|
83
|
+
`an operand. Pass the value, null to compare with NULL, or a condition without one ('IS NULL')`);
|
|
84
|
+
}
|
|
79
85
|
if (addValue && hasParams) {
|
|
80
86
|
assertSingleValueParams(fieldName, params);
|
|
81
87
|
// pick first value from params object or pass as is (a Date or a Buffer is a value too)
|