typeorm-extensions 2.2.0-rc.5 → 2.2.0-rc.7
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/cursor-pagination.extension.d.ts +2 -1
- 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 +2 -0
- package/dist/extensions/typed/index.d.ts +1 -0
- package/dist/extensions/typed/index.js +1 -0
- package/dist/extensions/typed/typed-distinct/typed-distinct.extension.d.ts +2 -2
- package/dist/extensions/typed/typed-group-by/typed-group-by.extension.d.ts +2 -2
- package/dist/extensions/typed/typed-having/typed-having.extension.d.ts +4 -4
- 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 +22 -0
- package/dist/extensions/typed/typed-joins/typed-joins.extension.d.ts +4 -4
- 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 +2 -2
- 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-where/typed-period-filter.extension.d.ts +5 -1
- package/dist/extensions/typed/typed-where/typed-period-filter.extension.js +2 -0
- 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 +2 -0
- 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 +2 -0
- 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 +2 -0
- 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 +2 -0
- 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 +2 -0
- 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 +2 -0
- 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 +2 -0
- 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 +2 -0
- package/dist/extensions/typed/typed-where/typed-where.extension.d.ts +7 -3
- package/dist/extensions/typed/typed-where/typed-where.extension.js +2 -0
- package/dist/extensions/where-array.extension.d.ts +4 -0
- package/dist/extensions/where-array.extension.js +2 -0
- package/dist/extensions/where-is-any.extension.d.ts +41 -6
- package/dist/extensions/where-is-any.extension.js +2 -0
- package/dist/extensions/where-is-distinct-from.extension.d.ts +4 -0
- package/dist/extensions/where-is-distinct-from.extension.js +2 -0
- package/dist/extensions/where-is-in.extension.d.ts +38 -6
- package/dist/extensions/where-is-in.extension.js +2 -0
- package/dist/extensions/where-json.extension.d.ts +4 -0
- package/dist/extensions/where-json.extension.js +2 -0
- package/dist/extensions/where-relation-exists.extension.d.ts +6 -6
- package/dist/helpers.d.ts +4 -0
- package/dist/helpers.js +11 -3
- package/dist/index.d.ts +4 -0
- package/dist/index.js +9 -1
- package/dist/types/extensions/index.d.ts +1 -0
- package/dist/types/extensions/index.js +1 -0
- 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/query-builder.utils.d.ts +4 -6
- package/dist/utils/query-builder.utils.js +82 -21
- 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 +49 -12
- 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,6 +1,7 @@
|
|
|
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");
|
|
6
7
|
/**
|
|
@@ -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
|
}
|
|
@@ -316,4 +308,49 @@ const tableHelper = (alias, options = {}) => ({
|
|
|
316
308
|
return `date_trunc('${unit}', ${toExpression(value, toColumn)}${zone})`;
|
|
317
309
|
}
|
|
318
310
|
});
|
|
311
|
+
// keys that JavaScript, Node and test tools read on any object (await, JSON.stringify, util.inspect, Jest): they
|
|
312
|
+
// must stay undefined, as must symbols. The members of Object.prototype are in the target already
|
|
313
|
+
const PROTOCOL_KEYS = new Set(['then', 'toJSON', 'inspect', 'asymmetricMatch', '$$typeof', 'nodeType']);
|
|
314
|
+
const columnAccess = {
|
|
315
|
+
get(target, key, receiver) {
|
|
316
|
+
if (typeof key === 'symbol' || key in target || PROTOCOL_KEYS.has(key)) {
|
|
317
|
+
return Reflect.get(target, key, receiver);
|
|
318
|
+
}
|
|
319
|
+
return target.column(key);
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
const databaseNamesByEntity = new WeakMap();
|
|
323
|
+
// The names set with `@Column({ name })` on the class or a parent, the nearest declaration winning. Every other
|
|
324
|
+
// column keeps its property name, as under TypeORM's default naming strategy. Columns of embedded entities are not read
|
|
325
|
+
const databaseColumnNames = (entity) => {
|
|
326
|
+
var _a;
|
|
327
|
+
const cached = databaseNamesByEntity.get(entity);
|
|
328
|
+
if (cached) {
|
|
329
|
+
return cached;
|
|
330
|
+
}
|
|
331
|
+
const lineage = [];
|
|
332
|
+
for (let target = entity; typeof target === 'function' && target !== Function.prototype; target = Object.getPrototypeOf(target)) {
|
|
333
|
+
lineage.push(target);
|
|
334
|
+
}
|
|
335
|
+
const names = new Map();
|
|
336
|
+
const depths = new Map();
|
|
337
|
+
for (const { target, propertyName, options } of (0, typeorm_1.getMetadataArgsStorage)().columns) {
|
|
338
|
+
const depth = lineage.indexOf(target);
|
|
339
|
+
if (depth !== -1 && options.name && depth < ((_a = depths.get(propertyName)) !== null && _a !== void 0 ? _a : Infinity)) {
|
|
340
|
+
names.set(propertyName, options.name);
|
|
341
|
+
depths.set(propertyName, depth);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
databaseNamesByEntity.set(entity, names);
|
|
345
|
+
return names;
|
|
346
|
+
};
|
|
347
|
+
function tableHelper(entityOrAlias, aliasOrOptions, entityOptions) {
|
|
348
|
+
if (entityOrAlias === undefined && typeof aliasOrOptions === 'string') {
|
|
349
|
+
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`);
|
|
350
|
+
}
|
|
351
|
+
const helper = typeof entityOrAlias === 'function'
|
|
352
|
+
? createTableHelper(aliasOrOptions, entityOptions, databaseColumnNames(entityOrAlias))
|
|
353
|
+
: createTableHelper(entityOrAlias, aliasOrOptions);
|
|
354
|
+
return new Proxy(helper, columnAccess);
|
|
355
|
+
}
|
|
319
356
|
exports.tableHelper = tableHelper;
|
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.7",
|
|
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",
|