typeorm-extensions 1.3.5 → 2.1.0

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.
Files changed (107) hide show
  1. package/README.md +12 -10
  2. package/dist/constants/index.d.ts +1 -1
  3. package/dist/constants/index.js +2 -2
  4. package/dist/extensions/cursor-pagination.extension.d.ts +18 -0
  5. package/dist/extensions/cursor-pagination.extension.js +108 -0
  6. package/dist/extensions/index.d.ts +7 -0
  7. package/dist/extensions/index.js +7 -0
  8. package/dist/extensions/order-by-array-position.extension.d.ts +24 -0
  9. package/dist/extensions/order-by-array-position.extension.js +18 -0
  10. package/dist/extensions/order.extension.d.ts +3 -2
  11. package/dist/extensions/order.extension.js +16 -13
  12. package/dist/extensions/period-filter.extension.d.ts +29 -0
  13. package/dist/extensions/period-filter.extension.js +23 -0
  14. package/dist/extensions/typed/index.d.ts +10 -0
  15. package/dist/extensions/typed/index.js +10 -0
  16. package/dist/extensions/typed/typed-distinct/typed-distinct.extension.js +2 -2
  17. package/dist/extensions/typed/typed-joins/typed-joins.extension.js +2 -1
  18. package/dist/extensions/typed/typed-order-by/typed-order-by-array-position.extension.d.ts +20 -0
  19. package/dist/extensions/typed/typed-order-by/typed-order-by-array-position.extension.js +21 -0
  20. package/dist/extensions/typed/typed-order-by/typed-order-by.extension.d.ts +4 -3
  21. package/dist/extensions/typed/typed-order-by/typed-order-by.extension.js +7 -6
  22. package/dist/extensions/typed/typed-returning/typed-returning.extension.d.ts +27 -0
  23. package/dist/extensions/typed/typed-returning/typed-returning.extension.js +38 -0
  24. package/dist/extensions/typed/typed-select/typed-select.extension.d.ts +6 -6
  25. package/dist/extensions/typed/typed-select/typed-select.extension.js +11 -8
  26. package/dist/extensions/typed/typed-where/typed-period-filter.extension.d.ts +31 -0
  27. package/dist/extensions/typed/typed-where/typed-period-filter.extension.js +25 -0
  28. package/dist/extensions/typed/typed-where/typed-where-array.extension.d.ts +68 -0
  29. package/dist/extensions/typed/typed-where/typed-where-array.extension.js +41 -0
  30. package/dist/extensions/typed/typed-where/typed-where-ilike.extension.d.ts +62 -0
  31. package/dist/extensions/typed/typed-where/typed-where-ilike.extension.js +84 -0
  32. package/dist/extensions/typed/typed-where/typed-where-is-any.extension.d.ts +53 -0
  33. package/dist/extensions/typed/typed-where/typed-where-is-any.extension.js +38 -0
  34. package/dist/extensions/typed/typed-where/typed-where-is-distinct-from.extension.d.ts +53 -0
  35. package/dist/extensions/typed/typed-where/typed-where-is-distinct-from.extension.js +38 -0
  36. package/dist/extensions/typed/typed-where/typed-where-is-in.extension.d.ts +36 -119
  37. package/dist/extensions/typed/typed-where/typed-where-is-in.extension.js +14 -29
  38. package/dist/extensions/typed/typed-where/typed-where-json.extension.d.ts +53 -0
  39. package/dist/extensions/typed/typed-where/typed-where-json.extension.js +39 -0
  40. package/dist/extensions/typed/typed-where/typed-where-lower.extension.d.ts +59 -0
  41. package/dist/extensions/typed/typed-where/typed-where-lower.extension.js +86 -0
  42. package/dist/extensions/typed/typed-where/typed-where-similar.extension.d.ts +54 -0
  43. package/dist/extensions/typed/typed-where/typed-where-similar.extension.js +73 -0
  44. package/dist/extensions/typed/typed-where/typed-where.extension.d.ts +30 -74
  45. package/dist/extensions/typed/typed-where/typed-where.extension.js +13 -21
  46. package/dist/extensions/where-array.extension.d.ts +70 -0
  47. package/dist/extensions/where-array.extension.js +47 -0
  48. package/dist/extensions/where-is-any.extension.d.ts +142 -0
  49. package/dist/extensions/where-is-any.extension.js +44 -0
  50. package/dist/extensions/where-is-distinct-from.extension.d.ts +56 -0
  51. package/dist/extensions/where-is-distinct-from.extension.js +55 -0
  52. package/dist/extensions/where-json.extension.d.ts +55 -0
  53. package/dist/extensions/where-json.extension.js +64 -0
  54. package/dist/helpers.d.ts +2 -0
  55. package/dist/helpers.js +23 -0
  56. package/dist/index.d.ts +3 -0
  57. package/dist/index.js +4 -0
  58. package/dist/types/extensions/cursor-pagination.types.d.ts +16 -0
  59. package/dist/types/extensions/cursor-pagination.types.js +2 -0
  60. package/dist/types/extensions/get-many-with-totals.types.d.ts +3 -1
  61. package/dist/types/extensions/index.d.ts +4 -0
  62. package/dist/types/extensions/index.js +4 -0
  63. package/dist/types/extensions/order.types.d.ts +1 -1
  64. package/dist/types/extensions/period.types.d.ts +15 -0
  65. package/dist/types/extensions/period.types.js +2 -0
  66. package/dist/types/extensions/search.types.d.ts +68 -0
  67. package/dist/types/extensions/search.types.js +2 -0
  68. package/dist/types/extensions/typed-order-by.types.d.ts +3 -0
  69. package/dist/types/extensions/typed-order-by.types.js +2 -0
  70. package/dist/types/extensions/typed-where.types.d.ts +4 -4
  71. package/dist/types/extensions/where.types.d.ts +1 -0
  72. package/dist/types/interfaces/config.interface.d.ts +17 -0
  73. package/dist/types/interfaces/config.interface.js +2 -0
  74. package/dist/types/interfaces/cursor-pagination.interface.d.ts +16 -0
  75. package/dist/types/interfaces/cursor-pagination.interface.js +2 -0
  76. package/dist/types/interfaces/index.d.ts +3 -0
  77. package/dist/types/interfaces/index.js +3 -0
  78. package/dist/types/interfaces/period.interface.d.ts +12 -0
  79. package/dist/types/interfaces/period.interface.js +2 -0
  80. package/dist/types/modules/proxy-callback.types.d.ts +50 -3
  81. package/dist/types/modules/typed-params.types.d.ts +45 -0
  82. package/dist/types/modules/typed-params.types.js +2 -0
  83. package/dist/types/util-types/index.d.ts +3 -1
  84. package/dist/utils/cursor.utils.d.ts +31 -0
  85. package/dist/utils/cursor.utils.js +130 -0
  86. package/dist/utils/json-path.utils.d.ts +22 -0
  87. package/dist/utils/json-path.utils.js +57 -0
  88. package/dist/utils/order.utils.d.ts +15 -0
  89. package/dist/utils/order.utils.js +45 -0
  90. package/dist/utils/period.utils.d.ts +9 -0
  91. package/dist/utils/period.utils.js +82 -0
  92. package/dist/utils/proxy-key.utils.d.ts +19 -3
  93. package/dist/utils/proxy-key.utils.js +40 -15
  94. package/dist/utils/query-builder.utils.d.ts +48 -0
  95. package/dist/utils/query-builder.utils.js +257 -0
  96. package/dist/utils/search.utils.d.ts +27 -0
  97. package/dist/utils/search.utils.js +83 -0
  98. package/dist/utils/sql.utils.d.ts +29 -1
  99. package/dist/utils/sql.utils.js +91 -9
  100. package/dist/utils/table.utils.d.ts +86 -0
  101. package/dist/utils/table.utils.js +124 -0
  102. package/dist/utils/where.utils.d.ts +18 -1
  103. package/dist/utils/where.utils.js +62 -9
  104. package/helpers/package.json +7 -0
  105. package/package.json +5 -4
  106. package/dist/utils/identifier.utils.d.ts +0 -15
  107. package/dist/utils/identifier.utils.js +0 -45
@@ -0,0 +1,257 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getTypedSelectionKey = exports.getTypedSingleColumnKey = exports.getTypedColumnKey = exports.toFieldSql = exports.getColumnDatabaseName = void 0;
4
+ const proxy_key_utils_1 = require("./proxy-key.utils");
5
+ const json_path_utils_1 = require("./json-path.utils");
6
+ const sql_utils_1 = require("./sql.utils");
7
+ /**
8
+ * Whether single-level typed keys are qualified with the main alias (`user.firstName`).
9
+ *
10
+ * UPDATE / DELETE builders turn alias prefixing off (`aliasNamePrefixingEnabled === false`)
11
+ * and render `UPDATE "users" … WHERE …` without an alias, where `"UserEntity"."firstName"`
12
+ * is invalid: their keys are just the column, `"firstName"`.
13
+ */
14
+ function isAliasPrefixingEnabled(queryBuilder) {
15
+ var _a;
16
+ return ((_a = queryBuilder.expressionMap) === null || _a === void 0 ? void 0 : _a.aliasNamePrefixingEnabled) !== false;
17
+ }
18
+ /**
19
+ * Main alias of the query for the single-level key `key`. A query created without an entity
20
+ * (`dataSource.createQueryBuilder()`, `qb.subQuery()`) has none until `from()`: throw a
21
+ * `TypeError` that says how to fix the call, instead of TypeORM's "Main alias is not set".
22
+ */
23
+ function requireMainAliasName(queryBuilder, key) {
24
+ var _a;
25
+ const mainAlias = (_a = queryBuilder.expressionMap) === null || _a === void 0 ? void 0 : _a.mainAlias;
26
+ if (!mainAlias) {
27
+ throw new TypeError(`Typed key "${key}" has no alias and the query has no main alias yet. ` +
28
+ `Call from() before this method, or address the column by its alias with an alias-keyed type: ` +
29
+ `type Q = { someAlias: SomeEntity }; (q) => q.someAlias.${key}`);
30
+ }
31
+ return mainAlias.name;
32
+ }
33
+ /** Entity metadata of a query alias (the main alias when `aliasName` is undefined), if it has any. */
34
+ function getAliasMetadata(queryBuilder, aliasName) {
35
+ const expressionMap = queryBuilder.expressionMap;
36
+ const alias = aliasName === undefined
37
+ ? expressionMap === null || expressionMap === void 0 ? void 0 : expressionMap.mainAlias
38
+ : expressionMap === null || expressionMap === void 0 ? void 0 : expressionMap.aliases.find((queryAlias) => queryAlias.name === aliasName);
39
+ return (alias === null || alias === void 0 ? void 0 : alias.hasMetadata) ? alias.metadata : undefined;
40
+ }
41
+ /**
42
+ * Database name of an entity property's column (`@Column({ name })`), looked up in the metadata of
43
+ * a query alias (the main alias when `aliasName` is undefined). `undefined` when the alias has no
44
+ * entity or the property isn't a column.
45
+ */
46
+ function getColumnDatabaseName(builder, aliasName, propertyPath) {
47
+ var _a;
48
+ const metadata = getAliasMetadata(builder, aliasName);
49
+ return (_a = metadata === null || metadata === void 0 ? void 0 : metadata.findColumnWithPropertyPath(propertyPath)) === null || _a === void 0 ? void 0 : _a.databaseName;
50
+ }
51
+ exports.getColumnDatabaseName = getColumnDatabaseName;
52
+ /** A column of the entity by its database name (`first-name`) or its property path (`firstName`, `address.city`). */
53
+ function findColumn(metadata, name) {
54
+ var _a;
55
+ return ((_a = metadata.columns.find((column) => column.databaseName === name)) !== null && _a !== void 0 ? _a : metadata.findColumnWithPropertyPath(name));
56
+ }
57
+ /**
58
+ * SQL for a field passed as a string to the untyped methods (`whereIsIn('user.first-name', …)`).
59
+ *
60
+ * A string can't tell a column from an expression: `user.first-name` may be the column `first-name`
61
+ * or `user.first` minus `name`. The query builder knows its aliases and their entities, though:
62
+ * - `alias.column`, where `alias` is an alias of the query and `column` one of its entity's columns
63
+ * (by database name or property path): the column, quoted by the driver, `"user"."first-name"`.
64
+ * Property paths are mapped to their columns (`user.createdAt` → `"user"."created_at"`).
65
+ * UPDATE / DELETE give the bare column, as they have no alias.
66
+ * - `column`, a database column name of the main entity: the quoted column, `"first-name"`. Property
67
+ * names aren't mapped without an alias, as `status` may also be a column of a joined table.
68
+ * - anything else (unknown aliases, subqueries, expressions such as `LOWER(u.email)` or
69
+ * `u.price - u.discount`): quoted only when it is a path of plain identifiers, as
70
+ * `stringToSQLIdentifier` does, in the driver's quotes.
71
+ */
72
+ function toFieldSql(builder, field) {
73
+ var _a;
74
+ const queryBuilder = builder;
75
+ const dot = field.indexOf('.');
76
+ if (dot > 0) {
77
+ const aliasName = field.slice(0, dot);
78
+ const metadata = ((_a = queryBuilder.expressionMap) === null || _a === void 0 ? void 0 : _a.aliases.some((alias) => alias.name === aliasName))
79
+ ? getAliasMetadata(queryBuilder, aliasName)
80
+ : undefined;
81
+ const column = metadata && findColumn(metadata, field.slice(dot + 1));
82
+ if (column) {
83
+ const columnSql = queryBuilder.escape(column.databaseName);
84
+ return isAliasPrefixingEnabled(queryBuilder) ? `${queryBuilder.escape(aliasName)}.${columnSql}` : columnSql;
85
+ }
86
+ }
87
+ else {
88
+ // only by database name: an unqualified property name may mean another table's column
89
+ const metadata = getAliasMetadata(queryBuilder, undefined);
90
+ const column = metadata === null || metadata === void 0 ? void 0 : metadata.columns.find((candidate) => candidate.databaseName === field);
91
+ if (column) {
92
+ return queryBuilder.escape(column.databaseName);
93
+ }
94
+ }
95
+ return (0, sql_utils_1.stringToSQLIdentifier)(field, (0, sql_utils_1.getDriverType)(builder));
96
+ }
97
+ exports.toFieldSql = toFieldSql;
98
+ /**
99
+ * The column that `keys` name in an entity: the longest prefix of the keys that is a column's property path
100
+ * (`address.city` for an embedded entity). The keys after it are a path inside the column, allowed only in a
101
+ * JSON column (`details` + `['meta', 'endTs']`). `undefined` when no prefix is a column, or a column that isn't
102
+ * JSON is followed by more keys (a relation's join column, `user` in `user.email`).
103
+ */
104
+ function findColumnPath(metadata, keys) {
105
+ for (let length = keys.length; metadata && length >= 1; length--) {
106
+ const propertyPath = keys.slice(0, length).join('.');
107
+ const column = metadata.findColumnWithPropertyPath(propertyPath);
108
+ if (!column) {
109
+ continue;
110
+ }
111
+ if (length === keys.length) {
112
+ return { propertyPath, column };
113
+ }
114
+ return (0, json_path_utils_1.isJsonColumn)(column) ? { propertyPath, column, jsonPath: keys.slice(length) } : undefined;
115
+ }
116
+ return undefined;
117
+ }
118
+ /**
119
+ * Splits typed keys into the alias and the property path they refer to:
120
+ * - `t => t.status` → the query's main alias + `status` (no alias on UPDATE / DELETE);
121
+ * - `j => j.t.status`, `u => u.images.url` → the first key, when it is an alias of the query;
122
+ * - `p => p.address.city` → when the first key isn't an alias but the main entity has a column
123
+ * with that path (an embedded entity), the main alias + `address.city`;
124
+ * - `e => e.details.meta.endTs`, `j => j.e.details.outcome` → a JSON column of the alias' entity
125
+ * followed by the keys inside it (`jsonPath`), at any depth;
126
+ * - anything else → the first key as the alias (an alias the query builder doesn't know yet,
127
+ * e.g. `q => q.base.chatRoomId` before `.from(…, 'base')`).
128
+ *
129
+ * Only single-level keys need the main alias, so `alias.column` keys work before `from()`.
130
+ * Their column names can be mapped only through aliases already known to the query, though:
131
+ * before `from()` the property name is used as the column name.
132
+ */
133
+ function resolveTypedKey(queryBuilder, keys) {
134
+ const resolved = resolveTypedKeyPath(queryBuilder, keys);
135
+ // deeper than `alias.column` only inside a JSON column: a nested relation can't be mapped to an alias
136
+ if (!resolved.jsonPath && keys.length > proxy_key_utils_1.MAX_KEY_DEPTH) {
137
+ throw (0, proxy_key_utils_1.createKeyTooDeepError)(keys);
138
+ }
139
+ return resolved;
140
+ }
141
+ function resolveTypedKeyPath(queryBuilder, keys) {
142
+ const prefixing = isAliasPrefixingEnabled(queryBuilder);
143
+ if (keys.length === 1) {
144
+ const aliasName = prefixing ? requireMainAliasName(queryBuilder, keys[0]) : undefined;
145
+ return {
146
+ aliasName,
147
+ propertyPath: keys[0],
148
+ metadata: getAliasMetadata(queryBuilder, aliasName),
149
+ };
150
+ }
151
+ const [firstKey, ...restKeys] = keys;
152
+ const expressionMap = queryBuilder.expressionMap;
153
+ const mainAlias = expressionMap === null || expressionMap === void 0 ? void 0 : expressionMap.mainAlias;
154
+ // UPDATE / DELETE have no alias to qualify with: `u => u.user.firstName` is `"firstName"` there
155
+ if (!prefixing && (mainAlias === null || mainAlias === void 0 ? void 0 : mainAlias.name) === firstKey) {
156
+ const metadata = getAliasMetadata(queryBuilder, undefined);
157
+ return {
158
+ aliasName: undefined,
159
+ propertyPath: restKeys.join('.'),
160
+ metadata,
161
+ ...findColumnPath(metadata, restKeys),
162
+ };
163
+ }
164
+ const queryAlias = expressionMap === null || expressionMap === void 0 ? void 0 : expressionMap.aliases.find((alias) => alias.name === firstKey);
165
+ if (!queryAlias && mainAlias) {
166
+ const mainMetadata = getAliasMetadata(queryBuilder, undefined);
167
+ const columnPath = findColumnPath(mainMetadata, keys);
168
+ if (columnPath) {
169
+ return {
170
+ aliasName: prefixing ? mainAlias.name : undefined,
171
+ metadata: mainMetadata,
172
+ ...columnPath,
173
+ };
174
+ }
175
+ }
176
+ const metadata = (queryAlias === null || queryAlias === void 0 ? void 0 : queryAlias.hasMetadata) ? queryAlias.metadata : undefined;
177
+ return {
178
+ aliasName: firstKey,
179
+ propertyPath: restKeys.join('.'),
180
+ metadata,
181
+ ...findColumnPath(metadata, restKeys),
182
+ };
183
+ }
184
+ /**
185
+ * `"alias"."column"` (or `"column"` without an alias) with the database column name, followed by the path
186
+ * inside a JSON column: `"exec"."details" -> 'meta' ->> 'endTs'` (see `toJsonPathSql`).
187
+ */
188
+ function toQuotedColumnKey(queryBuilder, { aliasName, propertyPath, metadata, column, jsonPath }, jsonOptions) {
189
+ var _a, _b;
190
+ 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;
191
+ const columnSql = aliasName === undefined
192
+ ? queryBuilder.escape(columnName)
193
+ : `${queryBuilder.escape(aliasName)}.${queryBuilder.escape(columnName)}`;
194
+ if (!column || !jsonPath) {
195
+ return columnSql;
196
+ }
197
+ const driver = (0, sql_utils_1.getDriverType)(queryBuilder);
198
+ return (0, json_path_utils_1.toJsonPathSql)(driver, columnSql, column, jsonPath, jsonOptions);
199
+ }
200
+ /**
201
+ * SQL key for a typed callback on `builder`: `"alias"."column"`, or `"column"` on UPDATE / DELETE.
202
+ *
203
+ * The property is mapped to its database column name (`@Column({ name })`, embedded columns)
204
+ * through the metadata of the alias it belongs to (see `resolveTypedKey`). Aliases without
205
+ * metadata (subqueries) and unknown properties keep the property name. A path into a JSON column
206
+ * gives its JSON accessor, cast on PostgreSQL to the type of `jsonOptions.value`.
207
+ */
208
+ function getTypedColumnKey(builder, callback, jsonOptions) {
209
+ const queryBuilder = builder;
210
+ const toKey = (keyHolder) => toQuotedColumnKey(queryBuilder, resolveTypedKey(queryBuilder, keyHolder.getKeys()), jsonOptions);
211
+ // the depth is checked against the query's metadata, where a JSON column may be deeper
212
+ const keyHolder = (0, proxy_key_utils_1.getKeyHolder)(callback, true);
213
+ return Array.isArray(keyHolder) ? keyHolder.map(toKey) : toKey(keyHolder);
214
+ }
215
+ exports.getTypedColumnKey = getTypedColumnKey;
216
+ /**
217
+ * Quoted column of a typed callback that must select exactly one column
218
+ * (`whereIsAnyTyped`, `whereArrayContainsTyped`, …). Throws a `TypeError` for several columns:
219
+ * one condition per column has no single obvious meaning (AND? OR?), so don't guess.
220
+ */
221
+ function getTypedSingleColumnKey(builder, callback, methodName, jsonOptions) {
222
+ const key = getTypedColumnKey(builder, callback, jsonOptions);
223
+ if (Array.isArray(key)) {
224
+ throw new TypeError(`${methodName} expects the callback to select a single column, got ${key.length}: ${key.join(', ')}`);
225
+ }
226
+ return key;
227
+ }
228
+ exports.getTypedSingleColumnKey = getTypedSingleColumnKey;
229
+ /**
230
+ * Key for typed select / order / distinct on a SelectQueryBuilder.
231
+ *
232
+ * Without quotes it is `alias.propertyPath` (`user.name`, `images.url`, `p.address.city`),
233
+ * which TypeORM maps to the column itself. With `useQuotes` it is the quoted database column
234
+ * (`"t"."ticket_status"`), since TypeORM doesn't map quoted keys. Strings are kept as is.
235
+ */
236
+ function getTypedSelectionKey(builder, selection, useQuotes) {
237
+ if (typeof selection === 'string') {
238
+ return selection;
239
+ }
240
+ const queryBuilder = builder;
241
+ const toKey = (keyHolder) => {
242
+ const resolved = resolveTypedKey(queryBuilder, keyHolder.getKeys());
243
+ if (resolved.jsonPath) {
244
+ throw new TypeError(`Typed key "${keyHolder.getKeys().join('.')}" points into the JSON column "${resolved.propertyPath}": ` +
245
+ `select, order and distinct take columns only. For an expression use tableHelper().jsonColumn()`);
246
+ }
247
+ if (useQuotes) {
248
+ return toQuotedColumnKey(queryBuilder, resolved);
249
+ }
250
+ return resolved.aliasName === undefined
251
+ ? resolved.propertyPath
252
+ : `${resolved.aliasName}.${resolved.propertyPath}`;
253
+ };
254
+ const keyHolder = (0, proxy_key_utils_1.getKeyHolder)(selection);
255
+ return Array.isArray(keyHolder) ? keyHolder.map(toKey) : toKey(keyHolder);
256
+ }
257
+ exports.getTypedSelectionKey = getTypedSelectionKey;
@@ -0,0 +1,27 @@
1
+ import type { DatabaseType } from 'typeorm';
2
+ import type { LikeMatch } from '../types/extensions/search.types';
3
+ /**
4
+ * Escapes the LIKE wildcards `%` and `_`, and the escape character `\` itself, with a backslash:
5
+ * `50%_off` → `50\%\_off`. The default escape character of PostgreSQL and MySQL;
6
+ * SQLite needs `ESCAPE '\'`, which the search methods add.
7
+ */
8
+ export declare function escapeLikePattern(value: string): string;
9
+ /** LIKE pattern for `value`: `%value%`, `value%`, `%value` or `value`. */
10
+ export declare function toLikePattern(value: string, match?: LikeMatch, escape?: boolean): string;
11
+ /**
12
+ * Trimmed search term, or `undefined` when there is nothing to search for (`null`, `undefined`,
13
+ * an empty or blank string): search methods add no condition then.
14
+ */
15
+ export declare function toSearchTerm(value: unknown, methodName: string): string | undefined;
16
+ /** Function to wrap both sides with for `unaccent`, or `undefined` for none. */
17
+ export declare function toUnaccentFunction(unaccent: boolean | string | undefined): string | undefined;
18
+ /**
19
+ * Case-insensitive LIKE of `column` against `parameter` in the syntax of the database:
20
+ * - PostgreSQL (and any other driver): `column ILIKE :p`, which `gin_trgm_ops` indexes support;
21
+ * - MySQL / MariaDB: `column LIKE :p`, case-insensitive with the default `_ci` collations;
22
+ * - SQLite: `column LIKE :p ESCAPE '\'`, case-insensitive for ASCII; SQLite has no default
23
+ * escape character.
24
+ *
25
+ * With `unaccentFunction`, both sides are wrapped with it: `unaccent(column) ILIKE unaccent(:p)`.
26
+ */
27
+ export declare function buildILikeCondition(driver: DatabaseType | undefined, column: string, parameter: string, unaccentFunction?: string): string;
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildILikeCondition = exports.toUnaccentFunction = exports.toSearchTerm = exports.toLikePattern = exports.escapeLikePattern = void 0;
4
+ const sql_utils_1 = require("./sql.utils");
5
+ /**
6
+ * Escapes the LIKE wildcards `%` and `_`, and the escape character `\` itself, with a backslash:
7
+ * `50%_off` → `50\%\_off`. The default escape character of PostgreSQL and MySQL;
8
+ * SQLite needs `ESCAPE '\'`, which the search methods add.
9
+ */
10
+ function escapeLikePattern(value) {
11
+ return value.replace(/[\\%_]/g, '\\$&');
12
+ }
13
+ exports.escapeLikePattern = escapeLikePattern;
14
+ /** LIKE pattern for `value`: `%value%`, `value%`, `%value` or `value`. */
15
+ function toLikePattern(value, match = 'contains', escape = true) {
16
+ const text = escape ? escapeLikePattern(value) : value;
17
+ switch (match) {
18
+ case 'contains':
19
+ return `%${text}%`;
20
+ case 'prefix':
21
+ return `${text}%`;
22
+ case 'suffix':
23
+ return `%${text}`;
24
+ case 'exact':
25
+ return text;
26
+ default:
27
+ throw new TypeError(`Invalid match "${match}": expected "contains", "prefix", "suffix" or "exact"`);
28
+ }
29
+ }
30
+ exports.toLikePattern = toLikePattern;
31
+ /**
32
+ * Trimmed search term, or `undefined` when there is nothing to search for (`null`, `undefined`,
33
+ * an empty or blank string): search methods add no condition then.
34
+ */
35
+ function toSearchTerm(value, methodName) {
36
+ if (value === null || value === undefined) {
37
+ return undefined;
38
+ }
39
+ if (typeof value !== 'string') {
40
+ throw new TypeError(`${methodName} expects a string to search for, got ${typeof value}`);
41
+ }
42
+ const term = value.trim();
43
+ return term.length > 0 ? term : undefined;
44
+ }
45
+ exports.toSearchTerm = toSearchTerm;
46
+ // `unaccent`, `public.f_unaccent`: a function name, optionally schema-qualified
47
+ const FUNCTION_NAME = /^[A-Za-z_][\w$]*(?:\.[A-Za-z_][\w$]*)?$/;
48
+ /** Function to wrap both sides with for `unaccent`, or `undefined` for none. */
49
+ function toUnaccentFunction(unaccent) {
50
+ if (unaccent === undefined || unaccent === false) {
51
+ return undefined;
52
+ }
53
+ if (unaccent === true) {
54
+ return 'unaccent';
55
+ }
56
+ if (typeof unaccent !== 'string' || !FUNCTION_NAME.test(unaccent)) {
57
+ // the name is inserted into the SQL as is
58
+ throw new TypeError(`Invalid unaccent function "${unaccent}": expected a function name like "unaccent"`);
59
+ }
60
+ return unaccent;
61
+ }
62
+ exports.toUnaccentFunction = toUnaccentFunction;
63
+ /**
64
+ * Case-insensitive LIKE of `column` against `parameter` in the syntax of the database:
65
+ * - PostgreSQL (and any other driver): `column ILIKE :p`, which `gin_trgm_ops` indexes support;
66
+ * - MySQL / MariaDB: `column LIKE :p`, case-insensitive with the default `_ci` collations;
67
+ * - SQLite: `column LIKE :p ESCAPE '\'`, case-insensitive for ASCII; SQLite has no default
68
+ * escape character.
69
+ *
70
+ * With `unaccentFunction`, both sides are wrapped with it: `unaccent(column) ILIKE unaccent(:p)`.
71
+ */
72
+ function buildILikeCondition(driver, column, parameter, unaccentFunction) {
73
+ const left = unaccentFunction ? `${unaccentFunction}(${column})` : column;
74
+ const right = unaccentFunction ? `${unaccentFunction}(${parameter})` : parameter;
75
+ if ((0, sql_utils_1.isMysqlLikeDriver)(driver)) {
76
+ return `${left} LIKE ${right}`;
77
+ }
78
+ if ((0, sql_utils_1.isSqliteLikeDriver)(driver)) {
79
+ return `${left} LIKE ${right} ESCAPE '\\'`;
80
+ }
81
+ return `${left} ILIKE ${right}`;
82
+ }
83
+ exports.buildILikeCondition = buildILikeCondition;
@@ -1,6 +1,34 @@
1
- import { DatabaseType } from 'typeorm';
1
+ import type { DatabaseType, WhereExpressionBuilder } from 'typeorm';
2
2
  export declare const wrapWithDoubleQoutes: (str: string) => string;
3
3
  export declare const wrapWithBackQoutes: (str: string) => string;
4
+ /** MySQL, MariaDB and Aurora MySQL: backtick identifiers, `<=>` null-safe comparison. */
5
+ export declare const isMysqlLikeDriver: (driver?: DatabaseType) => boolean;
6
+ /** PostgreSQL and the databases speaking its dialect. */
7
+ export declare const isPostgresLikeDriver: (driver?: DatabaseType) => boolean;
8
+ /** SQLite drivers: `IS` / `IS NOT` are null-safe comparisons. */
9
+ export declare const isSqliteLikeDriver: (driver?: DatabaseType) => boolean;
10
+ /** Database type of the query builder's connection, e.g. `'postgres'`; undefined without one. */
11
+ export declare function getDriverType(builder: WhereExpressionBuilder): DatabaseType | undefined;
12
+ /**
13
+ * Quotes one identifier, whatever characters it has: `first-name` → `"first-name"`, a `"` inside is
14
+ * doubled. An already quoted identifier is kept. For names known to be identifiers (columns, aliases).
15
+ */
16
+ export declare const quoteIdentifier: (name: string, driver?: DatabaseType) => string;
17
+ /**
18
+ * Quotes an alias or a table: as a path when it is one (`schema.table` → `"schema"."table"`),
19
+ * otherwise as one identifier (`user-1` → `"user-1"`).
20
+ */
21
+ export declare const quoteIdentifierPath: (path: string, driver?: DatabaseType) => string;
22
+ /**
23
+ * Quotes an identifier path: `user.id` → `"user"."id"`, `user.*` → `"user".*`.
24
+ * Already quoted segments are kept. Anything that isn't a path of identifiers
25
+ * (`COALESCE(a.x, b.x)`, `LOWER(u.email)`, `u.id::text`, `*`) is returned as is.
26
+ */
4
27
  export declare const stringToSQLIdentifier: (str: string, driver?: DatabaseType) => string;
5
28
  export declare const stringToSQLValue: (str: string) => string;
6
29
  export declare const createUniqueParameterName: (prefix?: string) => string;
30
+ /**
31
+ * Returns `arrayType` if it is a type name (`uuid[]`, `public.order_status[]`, `varchar(36)[]`),
32
+ * throws a `TypeError` otherwise: the type is inserted into the SQL as is.
33
+ */
34
+ export declare function assertArrayType(arrayType: string): string;
@@ -1,19 +1,85 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createUniqueParameterName = exports.stringToSQLValue = exports.stringToSQLIdentifier = exports.wrapWithBackQoutes = exports.wrapWithDoubleQoutes = void 0;
3
+ exports.assertArrayType = exports.createUniqueParameterName = exports.stringToSQLValue = exports.stringToSQLIdentifier = exports.quoteIdentifierPath = exports.quoteIdentifier = exports.getDriverType = exports.isSqliteLikeDriver = exports.isPostgresLikeDriver = exports.isMysqlLikeDriver = exports.wrapWithBackQoutes = exports.wrapWithDoubleQoutes = void 0;
4
4
  const common_utils_1 = require("./common.utils");
5
5
  const wrapWithDoubleQoutes = (str) => str.includes('"') ? str : `"${str}"`;
6
6
  exports.wrapWithDoubleQoutes = wrapWithDoubleQoutes;
7
7
  const wrapWithBackQoutes = (str) => str.includes('`') ? str : '`' + str + '`';
8
8
  exports.wrapWithBackQoutes = wrapWithBackQoutes;
9
- const stringToSQLIdentifier = (str, driver) => str.includes('.')
10
- ? str
11
- .split('.')
12
- .map((val) => (driver === null || driver === void 0 ? void 0 : driver.includes('mysql'))
13
- ? (0, exports.wrapWithBackQoutes)(val)
14
- : (0, exports.wrapWithDoubleQoutes)(val))
15
- .join('.')
16
- : (0, exports.wrapWithDoubleQoutes)(str);
9
+ const BACKTICK = '`';
10
+ // one identifier: unquoted (user, firstName) or already quoted ("user", `user`, "a""b")
11
+ const UNQUOTED_IDENTIFIER = '[\\p{L}_][\\p{L}\\p{N}_$]*';
12
+ const DOUBLE_QUOTED_IDENTIFIER = '"(?:[^"]|"")+"';
13
+ const BACK_QUOTED_IDENTIFIER = `${BACKTICK}(?:[^${BACKTICK}]|${BACKTICK}${BACKTICK})+${BACKTICK}`;
14
+ const IDENTIFIER = `(?:${UNQUOTED_IDENTIFIER}|${DOUBLE_QUOTED_IDENTIFIER}|${BACK_QUOTED_IDENTIFIER})`;
15
+ // `id`, `user.id`, `"user"."id"`, `schema.table.column`, optionally ending with `.*`
16
+ const IDENTIFIER_PATH = new RegExp(`^${IDENTIFIER}(?:\\.${IDENTIFIER})*(?:\\.\\*)?$`, 'u');
17
+ const IDENTIFIER_PATH_SEGMENT = new RegExp(`${IDENTIFIER}|\\*`, 'gu');
18
+ /** MySQL, MariaDB and Aurora MySQL: backtick identifiers, `<=>` null-safe comparison. */
19
+ const isMysqlLikeDriver = (driver) => driver === 'mysql' || driver === 'mariadb' || driver === 'aurora-mysql';
20
+ exports.isMysqlLikeDriver = isMysqlLikeDriver;
21
+ // `string[]`: TypeORM 1.0 removed the `sqlite` driver from `DatabaseType`, 0.3.x still has it
22
+ const SQLITE_LIKE_DRIVERS = [
23
+ 'sqlite',
24
+ 'better-sqlite3',
25
+ 'sqljs',
26
+ 'capacitor',
27
+ 'cordova',
28
+ 'expo',
29
+ 'nativescript',
30
+ 'react-native',
31
+ ];
32
+ const POSTGRES_LIKE_DRIVERS = ['postgres', 'aurora-postgres', 'cockroachdb'];
33
+ /** PostgreSQL and the databases speaking its dialect. */
34
+ const isPostgresLikeDriver = (driver) => driver !== undefined && POSTGRES_LIKE_DRIVERS.includes(driver);
35
+ exports.isPostgresLikeDriver = isPostgresLikeDriver;
36
+ /** SQLite drivers: `IS` / `IS NOT` are null-safe comparisons. */
37
+ const isSqliteLikeDriver = (driver) => driver !== undefined && SQLITE_LIKE_DRIVERS.includes(driver);
38
+ exports.isSqliteLikeDriver = isSqliteLikeDriver;
39
+ /** Database type of the query builder's connection, e.g. `'postgres'`; undefined without one. */
40
+ function getDriverType(builder) {
41
+ var _a, _b, _c;
42
+ return (_c = (_b = (_a = builder.connection) === null || _a === void 0 ? void 0 : _a.driver) === null || _b === void 0 ? void 0 : _b.options) === null || _c === void 0 ? void 0 : _c.type;
43
+ }
44
+ exports.getDriverType = getDriverType;
45
+ /**
46
+ * Quotes one identifier, whatever characters it has: `first-name` → `"first-name"`, a `"` inside is
47
+ * doubled. An already quoted identifier is kept. For names known to be identifiers (columns, aliases).
48
+ */
49
+ const quoteIdentifier = (name, driver) => {
50
+ const mysql = (0, exports.isMysqlLikeDriver)(driver);
51
+ const quote = mysql ? BACKTICK : '"';
52
+ if (name.length >= 2 && name.startsWith(quote) && name.endsWith(quote)) {
53
+ return name;
54
+ }
55
+ return `${quote}${name.split(quote).join(quote + quote)}${quote}`;
56
+ };
57
+ exports.quoteIdentifier = quoteIdentifier;
58
+ /**
59
+ * Quotes an alias or a table: as a path when it is one (`schema.table` → `"schema"."table"`),
60
+ * otherwise as one identifier (`user-1` → `"user-1"`).
61
+ */
62
+ const quoteIdentifierPath = (path, driver) => IDENTIFIER_PATH.test(path) ? (0, exports.stringToSQLIdentifier)(path, driver) : (0, exports.quoteIdentifier)(path, driver);
63
+ exports.quoteIdentifierPath = quoteIdentifierPath;
64
+ /**
65
+ * Quotes an identifier path: `user.id` → `"user"."id"`, `user.*` → `"user".*`.
66
+ * Already quoted segments are kept. Anything that isn't a path of identifiers
67
+ * (`COALESCE(a.x, b.x)`, `LOWER(u.email)`, `u.id::text`, `*`) is returned as is.
68
+ */
69
+ const stringToSQLIdentifier = (str, driver) => {
70
+ var _a;
71
+ if (!IDENTIFIER_PATH.test(str)) {
72
+ return str;
73
+ }
74
+ const quote = (0, exports.isMysqlLikeDriver)(driver)
75
+ ? exports.wrapWithBackQoutes
76
+ : exports.wrapWithDoubleQoutes;
77
+ return ((_a = str.match(IDENTIFIER_PATH_SEGMENT)) !== null && _a !== void 0 ? _a : [])
78
+ .map((segment) => segment === '*' || segment.startsWith('"') || segment.startsWith(BACKTICK)
79
+ ? segment
80
+ : quote(segment))
81
+ .join('.');
82
+ };
17
83
  exports.stringToSQLIdentifier = stringToSQLIdentifier;
18
84
  const stringToSQLValue = (str) => `'${str}'`;
19
85
  exports.stringToSQLValue = stringToSQLValue;
@@ -22,3 +88,19 @@ const createUniqueParameterName = (prefix) => {
22
88
  return `${prefix || 'param'}_${uniqueCode}`;
23
89
  };
24
90
  exports.createUniqueParameterName = createUniqueParameterName;
91
+ // a type name, optionally schema-qualified, multi-word (`character varying`), with a
92
+ // modifier (`varchar(255)`) and array brackets: `uuid[]`, `public.order_status[]`, `"MyEnum"[]`
93
+ const TYPE_NAME_PART = '(?:[A-Za-z_][\\w$]*|"(?:[^"]|"")+")';
94
+ const ARRAY_TYPE_PATTERN = new RegExp(`^${TYPE_NAME_PART}(?:\\.${TYPE_NAME_PART})*(?: ${TYPE_NAME_PART})*` +
95
+ `(?:\\(\\d+(?:, ?\\d+)?\\))?(?:\\[\\d*\\])*$`);
96
+ /**
97
+ * Returns `arrayType` if it is a type name (`uuid[]`, `public.order_status[]`, `varchar(36)[]`),
98
+ * throws a `TypeError` otherwise: the type is inserted into the SQL as is.
99
+ */
100
+ function assertArrayType(arrayType) {
101
+ if (typeof arrayType !== 'string' || !ARRAY_TYPE_PATTERN.test(arrayType)) {
102
+ throw new TypeError(`Invalid array type "${arrayType}": expected a type name like "uuid[]"`);
103
+ }
104
+ return arrayType;
105
+ }
106
+ exports.assertArrayType = assertArrayType;
@@ -0,0 +1,86 @@
1
+ import { ObjectLiteral } from "typeorm";
2
+ export type TableHelperOptions = {
3
+ useQuotes?: boolean;
4
+ };
5
+ type JsonFieldTuple<T, Depth extends 0[] = []> = Depth['length'] extends 4 ? [string, ...string[]] : {
6
+ [K in keyof T & string]: [NonNullable<T[K]>] extends [Record<string, unknown>] ? [K, ...JsonFieldTuple<NonNullable<T[K]>, [...Depth, 0]>] | [K] : [K];
7
+ }[keyof T & string];
8
+ export type TableHelperCountOptions = {
9
+ /** `COUNT(DISTINCT column)`: count the different values of the column. Needs a column. */
10
+ distinct?: boolean;
11
+ /** Count only the rows that match this condition, e.g. `t.where('status', "= 'active'")`. */
12
+ filter?: string;
13
+ /**
14
+ * How `filter` is written:
15
+ * - `'case'` (default): `COUNT(CASE WHEN filter THEN column END)`, in every database;
16
+ * - `'filter'`: `COUNT(column) FILTER (WHERE filter)`, PostgreSQL and SQLite 3.30+ only, a few percent faster on PostgreSQL.
17
+ */
18
+ syntax?: 'case' | 'filter';
19
+ };
20
+ export type JsonFieldRest<V> = [NonNullable<V>] extends [Record<string, unknown>] ? JsonFieldTuple<NonNullable<V>> : string[];
21
+ /**
22
+ * Creates a type-safe SQL helper for building column references, WHERE expressions,
23
+ * JOIN clauses, and JSONB accessors for a given entity type.
24
+ *
25
+ * @example
26
+ * const t = tableHelper<UserEntity>('user');
27
+ * t.column('email') // → "user"."email"
28
+ * t.where('id', '= :id') // → "user"."id" = :id
29
+ * t.jsonColumn('metadata', '->>', 'role') // → "user"."metadata" ->> 'role'
30
+ */
31
+ export declare const tableHelper: <T extends ObjectLiteral>(alias?: string, options?: TableHelperOptions) => {
32
+ alias: string | undefined;
33
+ /**
34
+ * Returns a quoted SQL column identifier, e.g. `"alias"."column"`. The column is always quoted as one
35
+ * identifier, so names with other characters work too: `"user"."first-name"`.
36
+ */
37
+ column(columnName: keyof T, alias?: string): string;
38
+ /** Returns a column identifier optionally followed by an expression, e.g. `"user"."id" = :id`. */
39
+ where(columnName: keyof T, expression?: string): string;
40
+ /** Returns an unquoted join path, e.g. `user.images`. Needs the helper's alias, or `alias`. */
41
+ join(relation: keyof T, alias?: string): string;
42
+ /**
43
+ * Returns a column with a JOIN ON expression, e.g. `"user"."id" = "images"."userId"`.
44
+ * The expression is inserted as is: build its columns with another `tableHelper`
45
+ * (`t.joinOn('id', '= ' + images.column('userId'))`), since unquoted camelCase names
46
+ * like `images.userId` are folded to lowercase by PostgreSQL unless TypeORM rewrites them.
47
+ */
48
+ joinOn(thisColumn: keyof T, expression: string): string;
49
+ /** Returns `"alias"."column" IS NULL`. */
50
+ isNull(columnName: keyof T): string;
51
+ /** Returns `"alias"."column" IS NOT NULL`. */
52
+ isNotNull(columnName: keyof T): string;
53
+ /**
54
+ * Returns `CAST(COUNT("alias"."column") AS integer)`: the rows where the column isn't `NULL`.
55
+ * Without a column it is `COUNT(*)`, all the rows of the result, in every database. To count the rows
56
+ * of a table joined with LEFT JOIN, count one of its non-null columns: `images.count('id')`.
57
+ * @example
58
+ * t.count('email', { distinct: true }) // CAST(COUNT(DISTINCT "user"."email") AS integer)
59
+ * t.count(undefined, { filter: t.where('status', "= 'active'") })
60
+ * // CAST(COUNT(CASE WHEN "user"."status" = 'active' THEN 1 END) AS integer)
61
+ * t.count(undefined, { filter: t.where('status', "= 'active'"), syntax: 'filter' })
62
+ * // CAST(COUNT(*) FILTER (WHERE "user"."status" = 'active') AS integer)
63
+ */
64
+ count(columnName?: keyof T | undefined, countOptions?: TableHelperCountOptions): string;
65
+ /** Returns `CAST(expression AS type)`. */
66
+ cast(expression: string, type: string): string;
67
+ /**
68
+ * Returns a full JSONB column access expression.
69
+ * @example
70
+ * t.jsonColumn('metadata', '->>', 'role') // → "user"."metadata" ->> 'role'
71
+ * t.jsonColumn('metadata', '->>', 'settings', 'theme') // → "user"."metadata" -> 'settings' ->> 'theme'
72
+ */
73
+ jsonColumn<K extends keyof T>(columnName: K, jsonOperator: '->>' | '->', ...fieldNames: JsonFieldRest<T[K]>): string;
74
+ /**
75
+ * Returns only the JSONB operator fragment (without the column).
76
+ * Useful inside `whereTyped` conditions strings.
77
+ * @example
78
+ * t.jsonPath('metadata', '->>', 'role') // → ->> 'role'
79
+ * t.jsonPath('metadata', '->>', 'settings', 'theme') // → -> 'settings' ->> 'theme'
80
+ *
81
+ * // Combined with whereTyped:
82
+ * qb.whereTyped(u => u.metadata, `${t.jsonPath('metadata', '->>', 'role')} =`, 'admin')
83
+ */
84
+ jsonPath<K_1 extends keyof T>(_columnName: K_1, jsonOperator: '->>' | '->', ...fieldNames: JsonFieldRest<T[K_1]>): string;
85
+ };
86
+ export {};