typeorm-extensions 2.2.0-rc.1 → 2.2.0-rc.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 +70 -39
- package/dist/decorators/calculated-column.decorator.d.ts +7 -3
- package/dist/decorators/calculated-column.decorator.js +35 -6
- package/dist/decorators/generated-column.decorator.d.ts +17 -0
- package/dist/decorators/generated-column.decorator.js +15 -0
- package/dist/decorators/index.d.ts +1 -0
- package/dist/decorators/index.js +1 -0
- package/dist/extensions/calculated-column.extension.d.ts +3 -3
- package/dist/extensions/calculated-column.extension.js +3 -16
- package/dist/extensions/cursor-pagination.extension.d.ts +2 -1
- package/dist/extensions/cursor-pagination.extension.js +7 -13
- package/dist/extensions/get-many-with-totals.extension.js +10 -29
- package/dist/extensions/join-lateral.extension.js +4 -6
- package/dist/extensions/order.extension.d.ts +2 -2
- package/dist/extensions/pagination.extension.d.ts +1 -1
- package/dist/extensions/period-filter.extension.d.ts +4 -0
- package/dist/extensions/period-filter.extension.js +1 -9
- package/dist/extensions/typed/index.d.ts +2 -0
- package/dist/extensions/typed/index.js +2 -0
- package/dist/extensions/typed/typed-calculated/typed-calculated-column.extension.d.ts +19 -0
- package/dist/extensions/typed/typed-calculated/typed-calculated-column.extension.js +17 -0
- package/dist/extensions/typed/typed-distinct/typed-distinct.extension.d.ts +6 -4
- package/dist/extensions/typed/typed-distinct/typed-distinct.extension.js +9 -2
- package/dist/extensions/typed/typed-group-by/typed-group-by.extension.d.ts +8 -7
- package/dist/extensions/typed/typed-group-by/typed-group-by.extension.js +1 -1
- package/dist/extensions/typed/typed-having/typed-having.extension.d.ts +4 -4
- package/dist/extensions/typed/typed-having/typed-having.extension.js +1 -1
- package/dist/extensions/typed/typed-joins/typed-join-and-map.extension.d.ts +19 -0
- package/dist/extensions/typed/typed-joins/typed-join-and-map.extension.js +23 -0
- package/dist/extensions/typed/typed-joins/typed-joins.extension.d.ts +4 -4
- package/dist/extensions/typed/typed-joins/typed-joins.extension.js +11 -22
- package/dist/extensions/typed/typed-order-by/typed-order-by-array-position.extension.d.ts +2 -2
- package/dist/extensions/typed/typed-order-by/typed-order-by.extension.d.ts +4 -2
- package/dist/extensions/typed/typed-order-by/typed-order-by.extension.js +18 -17
- package/dist/extensions/typed/typed-returning/typed-returning.extension.js +1 -9
- package/dist/extensions/typed/typed-root.extension.d.ts +26 -4
- package/dist/extensions/typed/typed-select/typed-select.extension.d.ts +6 -6
- package/dist/extensions/typed/typed-select/typed-select.extension.js +3 -9
- package/dist/extensions/typed/typed-where/typed-period-filter.extension.d.ts +5 -1
- package/dist/extensions/typed/typed-where/typed-period-filter.extension.js +1 -9
- package/dist/extensions/typed/typed-where/typed-where-array.extension.d.ts +13 -9
- package/dist/extensions/typed/typed-where/typed-where-array.extension.js +4 -16
- package/dist/extensions/typed/typed-where/typed-where-ilike.extension.d.ts +10 -6
- package/dist/extensions/typed/typed-where/typed-where-ilike.extension.js +1 -9
- package/dist/extensions/typed/typed-where/typed-where-is-any.extension.d.ts +10 -6
- package/dist/extensions/typed/typed-where/typed-where-is-any.extension.js +3 -20
- package/dist/extensions/typed/typed-where/typed-where-is-distinct-from.extension.d.ts +10 -6
- package/dist/extensions/typed/typed-where/typed-where-is-distinct-from.extension.js +3 -20
- package/dist/extensions/typed/typed-where/typed-where-is-in.extension.d.ts +10 -6
- package/dist/extensions/typed/typed-where/typed-where-is-in.extension.js +3 -21
- package/dist/extensions/typed/typed-where/typed-where-json.extension.d.ts +10 -6
- package/dist/extensions/typed/typed-where/typed-where-json.extension.js +4 -21
- package/dist/extensions/typed/typed-where/typed-where-lower.extension.d.ts +10 -6
- package/dist/extensions/typed/typed-where/typed-where-lower.extension.js +1 -9
- package/dist/extensions/typed/typed-where/typed-where-similar.extension.d.ts +8 -4
- package/dist/extensions/typed/typed-where/typed-where-similar.extension.js +1 -8
- package/dist/extensions/typed/typed-where/typed-where.extension.d.ts +7 -3
- package/dist/extensions/typed/typed-where/typed-where.extension.js +2 -10
- package/dist/extensions/union.extension.js +1 -1
- package/dist/extensions/where-array.extension.d.ts +4 -0
- package/dist/extensions/where-array.extension.js +1 -9
- package/dist/extensions/where-exists.extension.js +4 -15
- package/dist/extensions/where-is-any.extension.d.ts +41 -128
- package/dist/extensions/where-is-any.extension.js +1 -9
- package/dist/extensions/where-is-distinct-from.extension.d.ts +4 -0
- package/dist/extensions/where-is-distinct-from.extension.js +1 -9
- package/dist/extensions/where-is-in.extension.d.ts +38 -116
- package/dist/extensions/where-is-in.extension.js +11 -27
- package/dist/extensions/where-json.extension.d.ts +4 -0
- package/dist/extensions/where-json.extension.js +1 -9
- package/dist/extensions/where-relation-exists.extension.d.ts +6 -6
- package/dist/extensions/where-relation-exists.extension.js +5 -25
- package/dist/helpers.d.ts +5 -0
- package/dist/helpers.js +12 -3
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -4
- package/dist/types/extensions/cursor-pagination.types.d.ts +5 -0
- package/dist/types/extensions/get-many-with-totals.types.d.ts +4 -0
- package/dist/types/extensions/index.d.ts +1 -0
- package/dist/types/extensions/index.js +1 -0
- package/dist/types/extensions/typed-group-by.types.d.ts +2 -2
- package/dist/types/extensions/typed-join-and-map.types.d.ts +6 -0
- package/dist/types/extensions/typed-join-and-map.types.js +2 -0
- package/dist/types/modules/typed-root.types.d.ts +11 -9
- package/dist/utils/calculated-column.utils.d.ts +11 -0
- package/dist/utils/calculated-column.utils.js +33 -4
- package/dist/utils/common.utils.d.ts +2 -0
- package/dist/utils/common.utils.js +4 -1
- package/dist/utils/cursor.utils.js +4 -6
- package/dist/utils/json-path.utils.js +2 -2
- package/dist/utils/pagination.utils.d.ts +7 -0
- package/dist/utils/pagination.utils.js +20 -10
- package/dist/utils/parameter.utils.d.ts +10 -0
- package/dist/utils/parameter.utils.js +22 -1
- package/dist/utils/period.utils.js +2 -1
- package/dist/utils/prototype.utils.d.ts +9 -0
- package/dist/utils/prototype.utils.js +12 -2
- package/dist/utils/query-builder.utils.d.ts +43 -4
- package/dist/utils/query-builder.utils.js +239 -36
- package/dist/utils/reserved-qualifier-guard.utils.d.ts +36 -0
- package/dist/utils/reserved-qualifier-guard.utils.js +98 -0
- package/dist/utils/reserved-qualifier.utils.d.ts +33 -0
- package/dist/utils/reserved-qualifier.utils.js +252 -0
- package/dist/utils/table.utils.d.ts +27 -11
- package/dist/utils/table.utils.js +58 -23
- package/dist/utils/time-zone.utils.d.ts +8 -0
- package/dist/utils/time-zone.utils.js +15 -5
- package/dist/utils/typed-where.utils.d.ts +9 -0
- package/dist/utils/typed-where.utils.js +17 -0
- package/dist/utils/where.utils.d.ts +2 -0
- package/dist/utils/where.utils.js +21 -14
- package/package.json +2 -2
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findBareReservedQualifiers = exports.scanReservedQualifiers = exports.toReservedWordSet = exports.COCKROACHDB_RESERVED_KEY_WORDS = exports.POSTGRES_RESERVED_KEY_WORDS = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The reserved key words of PostgreSQL, "reserved" in Appendix C of its documentation (`SYSTEM_USER` since
|
|
6
|
+
* PostgreSQL 16). None can be a table, an alias or a schema without quotes, so none is followed by a `.` in valid
|
|
7
|
+
* SQL. The words "reserved (can be function or type)" (`LEFT`, `JOIN`, `BINARY`, …) aren't on the list: one may
|
|
8
|
+
* name the schema of a type, as in `CAST(x AS binary.my_type)`.
|
|
9
|
+
*/
|
|
10
|
+
exports.POSTGRES_RESERVED_KEY_WORDS = Object.freeze([
|
|
11
|
+
'ALL', 'ANALYSE', 'ANALYZE', 'AND', 'ANY', 'ARRAY', 'AS', 'ASC', 'ASYMMETRIC', 'BOTH', 'CASE', 'CAST', 'CHECK',
|
|
12
|
+
'COLLATE', 'COLUMN', 'CONSTRAINT', 'CREATE', 'CURRENT_CATALOG', 'CURRENT_DATE', 'CURRENT_ROLE', 'CURRENT_TIME',
|
|
13
|
+
'CURRENT_TIMESTAMP', 'CURRENT_USER', 'DEFAULT', 'DEFERRABLE', 'DESC', 'DISTINCT', 'DO', 'ELSE', 'END', 'EXCEPT',
|
|
14
|
+
'FALSE', 'FETCH', 'FOR', 'FOREIGN', 'FROM', 'GRANT', 'GROUP', 'HAVING', 'IN', 'INITIALLY', 'INTERSECT', 'INTO',
|
|
15
|
+
'LATERAL', 'LEADING', 'LIMIT', 'LOCALTIME', 'LOCALTIMESTAMP', 'NOT', 'NULL', 'OFFSET', 'ON', 'ONLY', 'OR', 'ORDER',
|
|
16
|
+
'PLACING', 'PRIMARY', 'REFERENCES', 'RETURNING', 'SELECT', 'SESSION_USER', 'SOME', 'SYMMETRIC', 'SYSTEM_USER',
|
|
17
|
+
'TABLE', 'THEN', 'TO', 'TRAILING', 'TRUE', 'UNION', 'UNIQUE', 'USER', 'USING', 'VARIADIC', 'WHEN', 'WHERE',
|
|
18
|
+
'WINDOW', 'WITH',
|
|
19
|
+
]);
|
|
20
|
+
/** The reserved key words of CockroachDB: PostgreSQL's without `SYSTEM_USER`, with `CONCURRENTLY` and `CURRENT_SCHEMA`. */
|
|
21
|
+
exports.COCKROACHDB_RESERVED_KEY_WORDS = Object.freeze([
|
|
22
|
+
...exports.POSTGRES_RESERVED_KEY_WORDS.filter((word) => word !== 'SYSTEM_USER'),
|
|
23
|
+
'CONCURRENTLY',
|
|
24
|
+
'CURRENT_SCHEMA',
|
|
25
|
+
]);
|
|
26
|
+
const KEY_WORD = /^[A-Za-z_][A-Za-z0-9_$]*$/;
|
|
27
|
+
const wordSets = new WeakMap();
|
|
28
|
+
/** The words in lower case; cached for a frozen list, such as the defaults, which can't change afterwards. */
|
|
29
|
+
function toReservedWordSet(words) {
|
|
30
|
+
const cached = wordSets.get(words);
|
|
31
|
+
if (cached) {
|
|
32
|
+
return cached;
|
|
33
|
+
}
|
|
34
|
+
if (!Array.isArray(words)) {
|
|
35
|
+
throw new TypeError(`Reserved words must be an array of key words, got ${typeof words}`);
|
|
36
|
+
}
|
|
37
|
+
for (const word of words) {
|
|
38
|
+
if (typeof word !== 'string' || !KEY_WORD.test(word)) {
|
|
39
|
+
throw new TypeError(`Invalid reserved word ${JSON.stringify(word)}: expected a key word, such as "order"`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const set = new Set(words.map((word) => word.toLowerCase()));
|
|
43
|
+
if (Object.isFrozen(words)) {
|
|
44
|
+
wordSets.set(words, set);
|
|
45
|
+
}
|
|
46
|
+
return set;
|
|
47
|
+
}
|
|
48
|
+
exports.toReservedWordSet = toReservedWordSet;
|
|
49
|
+
const TAB = 9;
|
|
50
|
+
const CARRIAGE_RETURN = 13;
|
|
51
|
+
const LINE_FEED = 10;
|
|
52
|
+
const SPACE = 32;
|
|
53
|
+
const DOUBLE_QUOTE = 34;
|
|
54
|
+
const DOLLAR = 36;
|
|
55
|
+
const QUOTE = 39;
|
|
56
|
+
const STAR = 42;
|
|
57
|
+
const DASH = 45;
|
|
58
|
+
const DOT = 46;
|
|
59
|
+
const SLASH = 47;
|
|
60
|
+
const COLON = 58;
|
|
61
|
+
const BACKSLASH = 92;
|
|
62
|
+
const isSpace = (code) => code === SPACE || (code >= TAB && code <= CARRIAGE_RETURN);
|
|
63
|
+
const isDigit = (code) => code >= 48 && code <= 57;
|
|
64
|
+
// as PostgreSQL reads identifiers: letters, `_` and every non-ASCII character; then digits and `$` too
|
|
65
|
+
const isIdentifierStart = (code) => (code >= 97 && code <= 122) || (code >= 65 && code <= 90) || code === 95 || code >= 128;
|
|
66
|
+
const isIdentifierPart = (code) => isIdentifierStart(code) || isDigit(code) || code === DOLLAR;
|
|
67
|
+
/** Index after the string literal whose opening quote is at `start`; `E'…'` strings take backslash escapes. */
|
|
68
|
+
function skipString(sql, start, backslashEscapes) {
|
|
69
|
+
for (let i = start + 1; i < sql.length; i++) {
|
|
70
|
+
const code = sql.charCodeAt(i);
|
|
71
|
+
if (backslashEscapes && code === BACKSLASH) {
|
|
72
|
+
i++;
|
|
73
|
+
}
|
|
74
|
+
else if (code === QUOTE) {
|
|
75
|
+
if (sql.charCodeAt(i + 1) !== QUOTE) {
|
|
76
|
+
return i + 1;
|
|
77
|
+
}
|
|
78
|
+
i++;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return sql.length;
|
|
82
|
+
}
|
|
83
|
+
function skipQuotedIdentifier(sql, start) {
|
|
84
|
+
for (let i = start + 1; i < sql.length; i++) {
|
|
85
|
+
if (sql.charCodeAt(i) === DOUBLE_QUOTE) {
|
|
86
|
+
if (sql.charCodeAt(i + 1) !== DOUBLE_QUOTE) {
|
|
87
|
+
return i + 1;
|
|
88
|
+
}
|
|
89
|
+
i++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return sql.length;
|
|
93
|
+
}
|
|
94
|
+
function skipLineComment(sql, start) {
|
|
95
|
+
for (let i = start + 2; i < sql.length; i++) {
|
|
96
|
+
const code = sql.charCodeAt(i);
|
|
97
|
+
if (code === LINE_FEED || code === CARRIAGE_RETURN) {
|
|
98
|
+
return i;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return sql.length;
|
|
102
|
+
}
|
|
103
|
+
// PostgreSQL nests block comments: `/* a /* b */ c */` is one comment
|
|
104
|
+
function skipBlockComment(sql, start) {
|
|
105
|
+
let depth = 0;
|
|
106
|
+
let i = start;
|
|
107
|
+
while (i < sql.length) {
|
|
108
|
+
const code = sql.charCodeAt(i);
|
|
109
|
+
const next = sql.charCodeAt(i + 1);
|
|
110
|
+
if (code === SLASH && next === STAR) {
|
|
111
|
+
depth++;
|
|
112
|
+
i += 2;
|
|
113
|
+
}
|
|
114
|
+
else if (code === STAR && next === SLASH) {
|
|
115
|
+
depth--;
|
|
116
|
+
i += 2;
|
|
117
|
+
if (depth === 0) {
|
|
118
|
+
return i;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
i++;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return sql.length;
|
|
126
|
+
}
|
|
127
|
+
/** Index after the opening tag of a dollar-quoted string at `start` (`$$`, `$tag$`); -1 for a parameter (`$1`). */
|
|
128
|
+
function dollarTagEnd(sql, start) {
|
|
129
|
+
let i = start + 1;
|
|
130
|
+
if (isIdentifierStart(sql.charCodeAt(i))) {
|
|
131
|
+
do {
|
|
132
|
+
i++;
|
|
133
|
+
} while (isIdentifierStart(sql.charCodeAt(i)) || isDigit(sql.charCodeAt(i)));
|
|
134
|
+
}
|
|
135
|
+
return sql.charCodeAt(i) === DOLLAR ? i + 1 : -1;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Whether the word from `start` to `end` qualifies a name: a dot follows it, after optional whitespace, and not a
|
|
139
|
+
* number (`THEN .5`); and it isn't itself a name after a dot (`public.order.id`), next to a quoted name or an
|
|
140
|
+
* identifier character, or a named parameter (`:order`, but `::` is a cast).
|
|
141
|
+
*/
|
|
142
|
+
function isQualifier(sql, start, end, previous) {
|
|
143
|
+
let next = end;
|
|
144
|
+
while (isSpace(sql.charCodeAt(next))) {
|
|
145
|
+
next++;
|
|
146
|
+
}
|
|
147
|
+
if (sql.charCodeAt(next) !== DOT || isDigit(sql.charCodeAt(next + 1)) || previous === DOT) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
const before = sql.charCodeAt(start - 1);
|
|
151
|
+
return (before !== DOUBLE_QUOTE &&
|
|
152
|
+
!isIdentifierPart(before) &&
|
|
153
|
+
!(before === COLON && sql.charCodeAt(start - 2) !== COLON));
|
|
154
|
+
}
|
|
155
|
+
const SNIPPET_CONTEXT = 30;
|
|
156
|
+
// cut at whitespace, not inside a word, when the context has some
|
|
157
|
+
function snippetAt(sql, start, end) {
|
|
158
|
+
let from = Math.max(0, start - SNIPPET_CONTEXT);
|
|
159
|
+
let to = Math.min(sql.length, end + SNIPPET_CONTEXT);
|
|
160
|
+
if (from > 0) {
|
|
161
|
+
const space = sql.slice(from, start).search(/\s/);
|
|
162
|
+
if (space !== -1) {
|
|
163
|
+
from += space + 1;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (to < sql.length) {
|
|
167
|
+
const space = sql.slice(end, to).search(/\s\S*$/);
|
|
168
|
+
if (space !== -1) {
|
|
169
|
+
to = end + space;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return `${from > 0 ? '…' : ''}${sql.slice(from, to).replace(/\s+/g, ' ')}${to < sql.length ? '…' : ''}`;
|
|
173
|
+
}
|
|
174
|
+
/** `findBareReservedQualifiers` with the words as a set of lower-case words (`toReservedWordSet`). */
|
|
175
|
+
function scanReservedQualifiers(sql, words) {
|
|
176
|
+
const found = [];
|
|
177
|
+
const length = sql.length;
|
|
178
|
+
// the last character outside whitespace and comments
|
|
179
|
+
let previous = -1;
|
|
180
|
+
let i = 0;
|
|
181
|
+
while (i < length) {
|
|
182
|
+
const code = sql.charCodeAt(i);
|
|
183
|
+
if (isSpace(code)) {
|
|
184
|
+
i++;
|
|
185
|
+
}
|
|
186
|
+
else if (code === DASH && sql.charCodeAt(i + 1) === DASH) {
|
|
187
|
+
i = skipLineComment(sql, i);
|
|
188
|
+
}
|
|
189
|
+
else if (code === SLASH && sql.charCodeAt(i + 1) === STAR) {
|
|
190
|
+
i = skipBlockComment(sql, i);
|
|
191
|
+
}
|
|
192
|
+
else if (code === QUOTE) {
|
|
193
|
+
i = skipString(sql, i, false);
|
|
194
|
+
previous = code;
|
|
195
|
+
}
|
|
196
|
+
else if (code === DOUBLE_QUOTE) {
|
|
197
|
+
i = skipQuotedIdentifier(sql, i);
|
|
198
|
+
previous = code;
|
|
199
|
+
}
|
|
200
|
+
else if (code === DOLLAR) {
|
|
201
|
+
const tagEnd = dollarTagEnd(sql, i);
|
|
202
|
+
if (tagEnd === -1) {
|
|
203
|
+
do {
|
|
204
|
+
i++;
|
|
205
|
+
} while (isDigit(sql.charCodeAt(i)));
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
const close = sql.indexOf(sql.slice(i, tagEnd), tagEnd);
|
|
209
|
+
i = close === -1 ? length : close + (tagEnd - i);
|
|
210
|
+
}
|
|
211
|
+
previous = code;
|
|
212
|
+
}
|
|
213
|
+
else if (isIdentifierPart(code)) {
|
|
214
|
+
const start = i;
|
|
215
|
+
do {
|
|
216
|
+
i++;
|
|
217
|
+
} while (isIdentifierPart(sql.charCodeAt(i)));
|
|
218
|
+
// E'…', the string literal with backslash escapes
|
|
219
|
+
if (i - start === 1 && (code === 69 || code === 101) && sql.charCodeAt(i) === QUOTE) {
|
|
220
|
+
i = skipString(sql, i, true);
|
|
221
|
+
previous = QUOTE;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (isIdentifierStart(code) &&
|
|
225
|
+
isQualifier(sql, start, i, previous) &&
|
|
226
|
+
words.has(sql.slice(start, i).toLowerCase())) {
|
|
227
|
+
found.push({ word: sql.slice(start, i), index: start, snippet: snippetAt(sql, start, i) });
|
|
228
|
+
}
|
|
229
|
+
previous = code;
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
previous = code;
|
|
233
|
+
i++;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return found;
|
|
237
|
+
}
|
|
238
|
+
exports.scanReservedQualifiers = scanReservedQualifiers;
|
|
239
|
+
/**
|
|
240
|
+
* Reserved words that qualify a name without quotes, as in `order."userId"` or `user.id`: PostgreSQL reads such a
|
|
241
|
+
* word as the key word, not as an alias, and rejects the statement (`syntax error at or near "order"`).
|
|
242
|
+
*
|
|
243
|
+
* Words in string literals (`E'…'` and dollar-quoted ones too), quoted identifiers, comments and parameters are
|
|
244
|
+
* skipped, and so are words after a dot (`public.order`) and a number after one (`THEN .5`). Case-insensitive;
|
|
245
|
+
* whitespace may come before the dot (`group . name`).
|
|
246
|
+
*
|
|
247
|
+
* @param words the words to look for, `POSTGRES_RESERVED_KEY_WORDS` by default
|
|
248
|
+
*/
|
|
249
|
+
function findBareReservedQualifiers(sql, words = exports.POSTGRES_RESERVED_KEY_WORDS) {
|
|
250
|
+
return scanReservedQualifiers(sql, toReservedWordSet(words));
|
|
251
|
+
}
|
|
252
|
+
exports.findBareReservedQualifiers = findBareReservedQualifiers;
|
|
@@ -74,17 +74,7 @@ export type TableHelperTimeZone<T> = string | {
|
|
|
74
74
|
/** The units of PostgreSQL's `date_trunc`. */
|
|
75
75
|
export type DateTruncUnit = 'microseconds' | 'milliseconds' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year' | 'decade' | 'century' | 'millennium';
|
|
76
76
|
export type JsonFieldRest<V> = [NonNullable<V>] extends [Record<string, unknown>] ? JsonFieldTuple<NonNullable<V>> : string[];
|
|
77
|
-
|
|
78
|
-
* Creates a type-safe SQL helper for building column references, WHERE expressions,
|
|
79
|
-
* JOIN clauses, and JSONB accessors for a given entity type.
|
|
80
|
-
*
|
|
81
|
-
* @example
|
|
82
|
-
* const t = tableHelper<UserEntity>('user');
|
|
83
|
-
* t.column('email') // → "user"."email"
|
|
84
|
-
* t.where('id', '= :id') // → "user"."id" = :id
|
|
85
|
-
* t.jsonColumn('metadata', '->>', 'role') // → "user"."metadata" ->> 'role'
|
|
86
|
-
*/
|
|
87
|
-
export declare const tableHelper: <T extends ObjectLiteral>(alias?: string, options?: TableHelperOptions) => {
|
|
77
|
+
declare const createTableHelper: <T extends ObjectLiteral>(alias?: string, options?: TableHelperOptions, databaseNames?: ReadonlyMap<string, string>) => {
|
|
88
78
|
alias: string | undefined;
|
|
89
79
|
/**
|
|
90
80
|
* Returns a quoted SQL column identifier, e.g. `"alias"."column"`. The column is always quoted as one
|
|
@@ -193,4 +183,30 @@ export declare const tableHelper: <T extends ObjectLiteral>(alias?: string, opti
|
|
|
193
183
|
*/
|
|
194
184
|
dateTrunc(unit: DateTruncUnit, value: TableHelperExpression<T>, timeZone?: TableHelperTimeZone<T> | undefined): string;
|
|
195
185
|
};
|
|
186
|
+
type TableHelperMemberName = keyof ReturnType<typeof createTableHelper>;
|
|
187
|
+
/**
|
|
188
|
+
* The helper `tableHelper` returns: its methods, and each column of the table as a read-only property,
|
|
189
|
+
* `t.email` for `t.column('email')`. A column named like a member (`alias`, `count`, …) is the member there.
|
|
190
|
+
*/
|
|
191
|
+
export type TableHelper<T extends ObjectLiteral> = ReturnType<typeof createTableHelper<T>> & {
|
|
192
|
+
readonly [K in Exclude<ColumnName<T>, TableHelperMemberName>]: string;
|
|
193
|
+
};
|
|
194
|
+
/** An entity class, abstract ones included. */
|
|
195
|
+
export type EntityClass<T> = abstract new (...args: never[]) => T;
|
|
196
|
+
/**
|
|
197
|
+
* Creates a type-safe SQL helper for building column references, WHERE expressions,
|
|
198
|
+
* JOIN clauses, and JSONB accessors for a given entity type.
|
|
199
|
+
*
|
|
200
|
+
* Given the entity class, it takes property names and writes the column names the entity maps them to:
|
|
201
|
+
* `tableHelper(UserEntity, 'user').adminEmail` is `"user"."email"` for `@Column({ name: 'email' }) adminEmail`.
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* const t = tableHelper<UserEntity>('user');
|
|
205
|
+
* t.column('email') // → "user"."email"
|
|
206
|
+
* t.email // → "user"."email", the same
|
|
207
|
+
* t.where('id', '= :id') // → "user"."id" = :id
|
|
208
|
+
* t.jsonColumn('metadata', '->>', 'role') // → "user"."metadata" ->> 'role'
|
|
209
|
+
*/
|
|
210
|
+
export declare function tableHelper<T extends ObjectLiteral>(entity: EntityClass<T>, alias?: string, options?: TableHelperOptions): TableHelper<T>;
|
|
211
|
+
export declare function tableHelper<T extends ObjectLiteral>(alias?: string, options?: TableHelperOptions): TableHelper<T>;
|
|
196
212
|
export {};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.tableHelper = void 0;
|
|
4
|
+
const typeorm_1 = require("typeorm");
|
|
4
5
|
const sql_utils_1 = require("./sql.utils");
|
|
5
6
|
const json_path_utils_1 = require("./json-path.utils");
|
|
7
|
+
const time_zone_utils_1 = require("./time-zone.utils");
|
|
6
8
|
/**
|
|
7
9
|
* The argument of an aggregate and the clause after it for a `filter` written with `syntax`:
|
|
8
10
|
* `CASE WHEN filter THEN argument END`, or `argument` and ` FILTER (WHERE filter)`.
|
|
@@ -44,9 +46,9 @@ const DATE_TRUNC_UNITS = [
|
|
|
44
46
|
'century',
|
|
45
47
|
'millennium',
|
|
46
48
|
];
|
|
47
|
-
function assertPostgres(method, driver) {
|
|
49
|
+
function assertPostgres(method, driver, hint) {
|
|
48
50
|
if (driver !== undefined && !(0, sql_utils_1.isPostgresLikeDriver)(driver)) {
|
|
49
|
-
throw new TypeError(`tableHelper.${method}(): PostgreSQL only, the driver is "${driver}"`);
|
|
51
|
+
throw new TypeError(`tableHelper.${method}(): PostgreSQL only, the driver is "${driver}"${hint ? `: ${hint}` : ''}`);
|
|
50
52
|
}
|
|
51
53
|
}
|
|
52
54
|
function toExpression(value, toColumn) {
|
|
@@ -57,7 +59,7 @@ function toTimeZone(method, timeZone, toColumn) {
|
|
|
57
59
|
if (!timeZone.trim()) {
|
|
58
60
|
throw new TypeError(`tableHelper.${method}(): the time zone is empty`);
|
|
59
61
|
}
|
|
60
|
-
return sqlString(toPostgresZone(timeZone));
|
|
62
|
+
return sqlString(toPostgresZone(method, timeZone));
|
|
61
63
|
}
|
|
62
64
|
if ('sql' in timeZone) {
|
|
63
65
|
return timeZone.sql;
|
|
@@ -65,19 +67,18 @@ function toTimeZone(method, timeZone, toColumn) {
|
|
|
65
67
|
const column = toColumn(timeZone.column);
|
|
66
68
|
return timeZone.fallback === undefined
|
|
67
69
|
? column
|
|
68
|
-
: `COALESCE(${column}, ${sqlString(toPostgresZone(timeZone.fallback))})`;
|
|
70
|
+
: `COALESCE(${column}, ${sqlString(toPostgresZone(method, timeZone.fallback))})`;
|
|
69
71
|
}
|
|
70
|
-
const ISO_OFFSET = /^([+-])(\d{2}):?(\d{2})?$/;
|
|
71
72
|
/**
|
|
72
73
|
* PostgreSQL reads `'+02:00'` as a POSIX zone, whose offsets count west: UTC−2. An ISO 8601 offset is
|
|
73
74
|
* written as the POSIX zone of the same offset, `'<+0200>-02:00'`, so `'+02:00'` is UTC+2 as everywhere else.
|
|
74
75
|
*/
|
|
75
|
-
function toPostgresZone(timeZone) {
|
|
76
|
-
const offset =
|
|
76
|
+
function toPostgresZone(method, timeZone) {
|
|
77
|
+
const offset = (0, time_zone_utils_1.parseUtcOffset)(timeZone.trim(), `tableHelper.${method}(): "${timeZone}" is not a UTC offset`);
|
|
77
78
|
if (offset === null) {
|
|
78
79
|
return timeZone;
|
|
79
80
|
}
|
|
80
|
-
const
|
|
81
|
+
const { sign, hours, minutes } = offset;
|
|
81
82
|
return `<${sign}${hours}${minutes}>${sign === '+' ? '-' : '+'}${hours}:${minutes}`;
|
|
82
83
|
}
|
|
83
84
|
/** The JSON functions of a database: PostgreSQL's unless the driver is MySQL / MariaDB or SQLite. */
|
|
@@ -102,26 +103,17 @@ function jsonFunctions(driver, jsonb) {
|
|
|
102
103
|
}
|
|
103
104
|
// MySQL reads a backslash in a string as an escape (unless NO_BACKSLASH_ESCAPES): `\'` would end the literal
|
|
104
105
|
const sqlString = (value, driver) => `'${((0, sql_utils_1.isMysqlLikeDriver)(driver) ? value.replace(/\\/g, '\\\\') : value).replace(/'/g, "''")}'`;
|
|
105
|
-
|
|
106
|
-
* Creates a type-safe SQL helper for building column references, WHERE expressions,
|
|
107
|
-
* JOIN clauses, and JSONB accessors for a given entity type.
|
|
108
|
-
*
|
|
109
|
-
* @example
|
|
110
|
-
* const t = tableHelper<UserEntity>('user');
|
|
111
|
-
* t.column('email') // → "user"."email"
|
|
112
|
-
* t.where('id', '= :id') // → "user"."id" = :id
|
|
113
|
-
* t.jsonColumn('metadata', '->>', 'role') // → "user"."metadata" ->> 'role'
|
|
114
|
-
*/
|
|
115
|
-
const tableHelper = (alias, options = {}) => ({
|
|
106
|
+
const createTableHelper = (alias, options = {}, databaseNames) => ({
|
|
116
107
|
alias,
|
|
117
108
|
/**
|
|
118
109
|
* Returns a quoted SQL column identifier, e.g. `"alias"."column"`. The column is always quoted as one
|
|
119
110
|
* identifier, so names with other characters work too: `"user"."first-name"`.
|
|
120
111
|
*/
|
|
121
112
|
column(columnName, alias) {
|
|
113
|
+
var _a;
|
|
122
114
|
const { useQuotes = true, driver } = options;
|
|
123
115
|
const tableAlias = alias !== null && alias !== void 0 ? alias : this.alias;
|
|
124
|
-
const column = String(columnName);
|
|
116
|
+
const column = (_a = databaseNames === null || databaseNames === void 0 ? void 0 : databaseNames.get(String(columnName))) !== null && _a !== void 0 ? _a : String(columnName);
|
|
125
117
|
if (!useQuotes) {
|
|
126
118
|
return [tableAlias, column].filter(Boolean).join('.');
|
|
127
119
|
}
|
|
@@ -208,9 +200,7 @@ const tableHelper = (alias, options = {}) => ({
|
|
|
208
200
|
arrayAgg(columnName, aggOptions = {}) {
|
|
209
201
|
var _a;
|
|
210
202
|
const { distinct = false, orderBy, filter } = aggOptions;
|
|
211
|
-
|
|
212
|
-
throw new TypeError(`tableHelper.arrayAgg(): arrays are PostgreSQL's, the driver is "${options.driver}": use jsonAgg`);
|
|
213
|
-
}
|
|
203
|
+
assertPostgres('arrayAgg', options.driver, 'use jsonAgg');
|
|
214
204
|
const order = orderBy ? aggregateOrder('arrayAgg', orderBy, (column) => this.column(column)) : undefined;
|
|
215
205
|
// PostgreSQL: "in an aggregate with DISTINCT, ORDER BY expressions must appear in argument list"
|
|
216
206
|
if (distinct && order && order.column !== columnName) {
|
|
@@ -316,4 +306,49 @@ const tableHelper = (alias, options = {}) => ({
|
|
|
316
306
|
return `date_trunc('${unit}', ${toExpression(value, toColumn)}${zone})`;
|
|
317
307
|
}
|
|
318
308
|
});
|
|
309
|
+
// keys that JavaScript, Node and test tools read on any object (await, JSON.stringify, util.inspect, Jest): they
|
|
310
|
+
// must stay undefined, as must symbols. The members of Object.prototype are in the target already
|
|
311
|
+
const PROTOCOL_KEYS = new Set(['then', 'toJSON', 'inspect', 'asymmetricMatch', '$$typeof', 'nodeType']);
|
|
312
|
+
const columnAccess = {
|
|
313
|
+
get(target, key, receiver) {
|
|
314
|
+
if (typeof key === 'symbol' || key in target || PROTOCOL_KEYS.has(key)) {
|
|
315
|
+
return Reflect.get(target, key, receiver);
|
|
316
|
+
}
|
|
317
|
+
return target.column(key);
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
const databaseNamesByEntity = new WeakMap();
|
|
321
|
+
// The names set with `@Column({ name })` on the class or a parent, the nearest declaration winning. Every other
|
|
322
|
+
// column keeps its property name, as under TypeORM's default naming strategy. Columns of embedded entities are not read
|
|
323
|
+
const databaseColumnNames = (entity) => {
|
|
324
|
+
var _a;
|
|
325
|
+
const cached = databaseNamesByEntity.get(entity);
|
|
326
|
+
if (cached) {
|
|
327
|
+
return cached;
|
|
328
|
+
}
|
|
329
|
+
const lineage = [];
|
|
330
|
+
for (let target = entity; typeof target === 'function' && target !== Function.prototype; target = Object.getPrototypeOf(target)) {
|
|
331
|
+
lineage.push(target);
|
|
332
|
+
}
|
|
333
|
+
const names = new Map();
|
|
334
|
+
const depths = new Map();
|
|
335
|
+
for (const { target, propertyName, options } of (0, typeorm_1.getMetadataArgsStorage)().columns) {
|
|
336
|
+
const depth = lineage.indexOf(target);
|
|
337
|
+
if (depth !== -1 && options.name && depth < ((_a = depths.get(propertyName)) !== null && _a !== void 0 ? _a : Infinity)) {
|
|
338
|
+
names.set(propertyName, options.name);
|
|
339
|
+
depths.set(propertyName, depth);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
databaseNamesByEntity.set(entity, names);
|
|
343
|
+
return names;
|
|
344
|
+
};
|
|
345
|
+
function tableHelper(entityOrAlias, aliasOrOptions, entityOptions) {
|
|
346
|
+
if (entityOrAlias === undefined && typeof aliasOrOptions === 'string') {
|
|
347
|
+
throw new TypeError(`tableHelper(entity, '${aliasOrOptions}'): the entity class is undefined, as a circular import leaves it at module load. Create the helper where it is used`);
|
|
348
|
+
}
|
|
349
|
+
const helper = typeof entityOrAlias === 'function'
|
|
350
|
+
? createTableHelper(aliasOrOptions, entityOptions, databaseColumnNames(entityOrAlias))
|
|
351
|
+
: createTableHelper(entityOrAlias, aliasOrOptions);
|
|
352
|
+
return new Proxy(helper, columnAccess);
|
|
353
|
+
}
|
|
319
354
|
exports.tableHelper = tableHelper;
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
type UtcOffset = {
|
|
2
|
+
sign: '+' | '-';
|
|
3
|
+
hours: string;
|
|
4
|
+
minutes: string;
|
|
5
|
+
};
|
|
6
|
+
/** The parts of an ISO 8601 UTC offset (`+02:00`, `-0530`, `+02`); `null` for other text, a `TypeError` past 23:59. */
|
|
7
|
+
export declare function parseUtcOffset(text: string, errorMessage: string): UtcOffset | null;
|
|
1
8
|
/** Throws a `TypeError` for a time zone the runtime doesn't know. */
|
|
2
9
|
export declare function assertTimeZone(timeZone: string): void;
|
|
3
10
|
/**
|
|
@@ -6,3 +13,4 @@ export declare function assertTimeZone(timeZone: string): void;
|
|
|
6
13
|
* instant already: it is returned as that `Date`. Any other string throws a `TypeError` naming `name`.
|
|
7
14
|
*/
|
|
8
15
|
export declare function readInTimeZone(value: string, timeZone: string, name: string): Date;
|
|
16
|
+
export {};
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.readInTimeZone = exports.assertTimeZone = void 0;
|
|
3
|
+
exports.readInTimeZone = exports.assertTimeZone = exports.parseUtcOffset = void 0;
|
|
4
4
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
5
5
|
const MAX_CACHED_FORMATTERS = 500;
|
|
6
6
|
const DATE_TIME = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,9}))?)?(Z|[+-]\d{2}(?::?\d{2})?)?)?$/i;
|
|
7
7
|
const OFFSET = /^([+-])(\d{2}):?(\d{2})?$/;
|
|
8
|
+
/** The parts of an ISO 8601 UTC offset (`+02:00`, `-0530`, `+02`); `null` for other text, a `TypeError` past 23:59. */
|
|
9
|
+
function parseUtcOffset(text, errorMessage) {
|
|
10
|
+
const offset = OFFSET.exec(text);
|
|
11
|
+
if (offset === null) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const [, sign, hours, minutes = '00'] = offset;
|
|
15
|
+
if (Number(hours) > 23 || Number(minutes) > 59) {
|
|
16
|
+
throw new TypeError(errorMessage);
|
|
17
|
+
}
|
|
18
|
+
return { sign: sign, hours, minutes };
|
|
19
|
+
}
|
|
20
|
+
exports.parseUtcOffset = parseUtcOffset;
|
|
8
21
|
const formatters = new Map();
|
|
9
22
|
function getFormatter(timeZone) {
|
|
10
23
|
let formatter = formatters.get(timeZone);
|
|
@@ -94,9 +107,6 @@ function getOffsetMinutes(offset, value, name) {
|
|
|
94
107
|
if (offset.toUpperCase() === 'Z') {
|
|
95
108
|
return 0;
|
|
96
109
|
}
|
|
97
|
-
const
|
|
98
|
-
if (Number(hours) > 23 || Number(minutes) > 59) {
|
|
99
|
-
throw new TypeError(`Invalid ${name} "${value}": not a UTC offset`);
|
|
100
|
-
}
|
|
110
|
+
const { sign, hours, minutes } = parseUtcOffset(offset, `Invalid ${name} "${value}": not a UTC offset`);
|
|
101
111
|
return (sign === '-' ? -1 : 1) * (Number(hours) * 60 + Number(minutes));
|
|
102
112
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ObjectLiteral, WhereExpressionBuilder } from 'typeorm';
|
|
2
|
+
import type { KeyProxyCallback } from '../types/modules/proxy-callback.types';
|
|
3
|
+
import type { JsonPathSqlOptions } from './json-path.utils';
|
|
4
|
+
/**
|
|
5
|
+
* Calls the untyped where `method` with the single column a typed callback picks, followed by `args`, as the typed
|
|
6
|
+
* where methods that only add the column to their untyped counterpart do. A callback that isn't a function, from
|
|
7
|
+
* untyped code, leaves the builder as it is.
|
|
8
|
+
*/
|
|
9
|
+
export declare function attachTypedColumnWhere<Type extends ObjectLiteral, QB extends WhereExpressionBuilder, Args extends unknown[] = unknown[]>(builder: QB, method: string, where: KeyProxyCallback<Type>, jsonOptions: JsonPathSqlOptions | undefined, ...args: Args): QB;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.attachTypedColumnWhere = void 0;
|
|
4
|
+
const query_builder_utils_1 = require("./query-builder.utils");
|
|
5
|
+
/**
|
|
6
|
+
* Calls the untyped where `method` with the single column a typed callback picks, followed by `args`, as the typed
|
|
7
|
+
* where methods that only add the column to their untyped counterpart do. A callback that isn't a function, from
|
|
8
|
+
* untyped code, leaves the builder as it is.
|
|
9
|
+
*/
|
|
10
|
+
function attachTypedColumnWhere(builder, method, where, jsonOptions, ...args) {
|
|
11
|
+
if (typeof where !== 'function') {
|
|
12
|
+
return builder;
|
|
13
|
+
}
|
|
14
|
+
const key = (0, query_builder_utils_1.getTypedSingleColumnKey)(builder, where, `${method}Typed`, jsonOptions);
|
|
15
|
+
return builder[method](key, ...args);
|
|
16
|
+
}
|
|
17
|
+
exports.attachTypedColumnWhere = attachTypedColumnWhere;
|
|
@@ -5,6 +5,8 @@ export declare function getValuePlaceholder<T>(value: T, paramName?: string): st
|
|
|
5
5
|
* Whether `params` is a `{ name: value }` params object (a plain object or a class instance such
|
|
6
6
|
* as a DTO), rather than the value itself (a primitive, an array, a `Date`, a `Buffer`).
|
|
7
7
|
*/
|
|
8
|
+
/** The value a condition compares with: the value of a one-key params object, or the params themselves (a Date or a Buffer is a value too). */
|
|
9
|
+
export declare function getComparedValue(params: unknown): unknown;
|
|
8
10
|
export declare function isParamsObject(params: unknown): params is Record<string, unknown>;
|
|
9
11
|
/**
|
|
10
12
|
* Checks whether `conditions` already references a key of `params` as a TypeORM
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.andWhereNarrowing = exports.attachWhere = exports.hasParamPlaceholder = exports.isParamsObject = exports.getValuePlaceholder = void 0;
|
|
3
|
+
exports.andWhereNarrowing = exports.attachWhere = exports.hasParamPlaceholder = exports.isParamsObject = exports.getComparedValue = exports.getValuePlaceholder = void 0;
|
|
4
4
|
const sql_utils_1 = require("./sql.utils");
|
|
5
5
|
const query_builder_utils_1 = require("./query-builder.utils");
|
|
6
|
+
const parameter_utils_1 = require("./parameter.utils");
|
|
7
|
+
const common_utils_1 = require("./common.utils");
|
|
6
8
|
function getValuePlaceholder(value, paramName) {
|
|
7
9
|
if (value === undefined || value === null || paramName === undefined) {
|
|
8
10
|
return 'NULL';
|
|
@@ -13,16 +15,25 @@ function getValuePlaceholder(value, paramName) {
|
|
|
13
15
|
return `:${paramName}`;
|
|
14
16
|
}
|
|
15
17
|
exports.getValuePlaceholder = getValuePlaceholder;
|
|
16
|
-
|
|
18
|
+
/**
|
|
19
|
+
* A condition that ends with an operator, `=`, `>=`, `LIKE`, `IN`, `@>`: it needs a value after it. A word after
|
|
20
|
+
* `:` or `.` is a parameter or a column (`ILIKE :like`, `= t.in`), not an operator.
|
|
21
|
+
*/
|
|
22
|
+
const OPERATOR_AT_END = /(?:[=<>!~@&|?]|(?<![\w$:.])(?:LIKE|ILIKE|IN|SIMILAR TO|DISTINCT FROM|BETWEEN|ANY|ALL))\s*$/i;
|
|
17
23
|
/**
|
|
18
24
|
* Objects that are parameter values themselves, not `{ name: value }` params objects:
|
|
19
25
|
* `Date`, and binary data (`Buffer`, typed arrays, `ArrayBuffer`).
|
|
20
26
|
*/
|
|
21
|
-
const isValueObject = (value) => isDate(value) || ArrayBuffer.isView(value) || value instanceof ArrayBuffer;
|
|
27
|
+
const isValueObject = (value) => (0, common_utils_1.isDate)(value) || ArrayBuffer.isView(value) || value instanceof ArrayBuffer;
|
|
22
28
|
/**
|
|
23
29
|
* Whether `params` is a `{ name: value }` params object (a plain object or a class instance such
|
|
24
30
|
* as a DTO), rather than the value itself (a primitive, an array, a `Date`, a `Buffer`).
|
|
25
31
|
*/
|
|
32
|
+
/** The value a condition compares with: the value of a one-key params object, or the params themselves (a Date or a Buffer is a value too). */
|
|
33
|
+
function getComparedValue(params) {
|
|
34
|
+
return isParamsObject(params) ? Object.values(params)[0] : params;
|
|
35
|
+
}
|
|
36
|
+
exports.getComparedValue = getComparedValue;
|
|
26
37
|
function isParamsObject(params) {
|
|
27
38
|
return (params !== null &&
|
|
28
39
|
typeof params === 'object' &&
|
|
@@ -39,14 +50,7 @@ function hasParamPlaceholder(conditions, params) {
|
|
|
39
50
|
if (!isParamsObject(params)) {
|
|
40
51
|
return false;
|
|
41
52
|
}
|
|
42
|
-
|
|
43
|
-
let match;
|
|
44
|
-
while ((match = placeholderRegex.exec(conditions)) !== null) {
|
|
45
|
-
if (Object.prototype.hasOwnProperty.call(params, match[1])) {
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return false;
|
|
53
|
+
return (0, parameter_utils_1.getPlaceholderNames)(conditions).some((name) => Object.prototype.hasOwnProperty.call(params, name));
|
|
50
54
|
}
|
|
51
55
|
exports.hasParamPlaceholder = hasParamPlaceholder;
|
|
52
56
|
/**
|
|
@@ -76,15 +80,18 @@ function attachWhere(builder, method, field, conditions, params, options) {
|
|
|
76
80
|
const hasParams = typeof params !== 'undefined';
|
|
77
81
|
// `addValue` defaults to `true` unless `conditions` already references one of the `params` keys
|
|
78
82
|
addValue = addValue !== null && addValue !== void 0 ? addValue : !hasParamPlaceholder(conditions, params);
|
|
83
|
+
if (addValue && !hasParams && OPERATOR_AT_END.test(conditions)) {
|
|
84
|
+
throw new TypeError(`The value after "${fieldName} ${conditions.trim()}" is undefined, which leaves the condition without ` +
|
|
85
|
+
`an operand. Pass the value, null to compare with NULL, or a condition without one ('IS NULL')`);
|
|
86
|
+
}
|
|
79
87
|
if (addValue && hasParams) {
|
|
80
88
|
assertSingleValueParams(fieldName, params);
|
|
81
|
-
|
|
82
|
-
const firstValue = isParamsObject(params) ? Object.values(params)[0] : params;
|
|
89
|
+
const firstValue = getComparedValue(params);
|
|
83
90
|
// 0, false, '' are valid values like any other
|
|
84
91
|
// null, undefined, NaN and invalid dates are equal to NULL
|
|
85
92
|
const valueIsValid = firstValue !== undefined &&
|
|
86
93
|
(typeof firstValue === 'number' ? !isNaN(firstValue) : true) &&
|
|
87
|
-
(isDate(firstValue) ? !isNaN(firstValue.getTime()) : true);
|
|
94
|
+
((0, common_utils_1.isDate)(firstValue) ? !isNaN(firstValue.getTime()) : true);
|
|
88
95
|
const paramName = valueIsValid
|
|
89
96
|
? (0, sql_utils_1.createUniqueParameterName)(method)
|
|
90
97
|
: undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typeorm-extensions",
|
|
3
|
-
"version": "2.2.0-rc.
|
|
3
|
+
"version": "2.2.0-rc.10",
|
|
4
4
|
"description": "typeorm commonly used extensions",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "tsc -p .",
|
|
16
16
|
"lint": "eslint . --ext .ts",
|
|
17
|
-
"test:types": "tsc -p test/types && tsc -p test/types/tsconfig.loose.json"
|
|
17
|
+
"test:types": "tsc -p test/types && tsc -p test/types/tsconfig.loose.json && tsc -p test/types/typed-imports && tsc -p test/types/typed-imports/tsconfig.loose.json"
|
|
18
18
|
},
|
|
19
19
|
"keywords": [
|
|
20
20
|
"typeorm",
|