typeorm-extensions 2.0.0 → 2.2.0-rc.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.
- package/LICENSE +21 -0
- package/README.md +9 -8
- package/dist/decorators/calculated-column.decorator.d.ts +30 -0
- package/dist/decorators/calculated-column.decorator.js +82 -0
- package/dist/decorators/index.d.ts +1 -0
- package/dist/decorators/index.js +17 -0
- package/dist/extensions/calculated-column.extension.d.ts +33 -0
- package/dist/extensions/calculated-column.extension.js +33 -0
- package/dist/extensions/cursor-pagination.extension.d.ts +2 -1
- package/dist/extensions/cursor-pagination.extension.js +4 -3
- package/dist/extensions/get-many-with-totals.extension.d.ts +9 -1
- package/dist/extensions/get-many-with-totals.extension.js +158 -10
- package/dist/extensions/index.d.ts +7 -0
- package/dist/extensions/index.js +7 -0
- package/dist/extensions/join-lateral.extension.d.ts +19 -0
- package/dist/extensions/join-lateral.extension.js +50 -0
- package/dist/extensions/join-unnest.extension.d.ts +19 -0
- package/dist/extensions/join-unnest.extension.js +40 -0
- package/dist/extensions/order.extension.js +18 -7
- package/dist/extensions/query-returning.extension.d.ts +29 -0
- package/dist/extensions/query-returning.extension.js +50 -0
- package/dist/extensions/typed/index.d.ts +3 -0
- package/dist/extensions/typed/index.js +3 -2
- package/dist/extensions/typed/typed-distinct/typed-distinct.extension.d.ts +3 -2
- package/dist/extensions/typed/typed-group-by/typed-group-by.extension.d.ts +24 -0
- package/dist/extensions/typed/typed-group-by/typed-group-by.extension.js +31 -0
- package/dist/extensions/typed/typed-having/typed-having.extension.d.ts +36 -0
- package/dist/extensions/typed/typed-having/typed-having.extension.js +42 -0
- package/dist/extensions/typed/typed-joins/typed-joins.extension.d.ts +5 -4
- package/dist/extensions/typed/typed-order-by/typed-order-by-array-position.extension.d.ts +3 -2
- package/dist/extensions/typed/typed-order-by/typed-order-by-array-position.extension.js +10 -2
- package/dist/extensions/typed/typed-order-by/typed-order-by.extension.d.ts +3 -2
- package/dist/extensions/typed/typed-order-by/typed-order-by.extension.js +3 -2
- package/dist/extensions/typed/typed-root.extension.d.ts +25 -0
- package/dist/extensions/typed/typed-root.extension.js +13 -0
- package/dist/extensions/typed/typed-select/typed-select.extension.d.ts +5 -4
- package/dist/extensions/typed/typed-select/typed-select.extension.js +3 -2
- package/dist/extensions/typed/typed-where/typed-period-filter.extension.d.ts +2 -1
- package/dist/extensions/typed/typed-where/typed-period-filter.extension.js +1 -1
- package/dist/extensions/typed/typed-where/typed-where-array.extension.d.ts +10 -9
- package/dist/extensions/typed/typed-where/typed-where-array.extension.js +32 -0
- package/dist/extensions/typed/typed-where/typed-where-ilike.extension.d.ts +7 -6
- package/dist/extensions/typed/typed-where/typed-where-is-any.extension.d.ts +39 -129
- package/dist/extensions/typed/typed-where/typed-where-is-any.extension.js +2 -1
- package/dist/extensions/typed/typed-where/typed-where-is-distinct-from.extension.d.ts +7 -6
- package/dist/extensions/typed/typed-where/typed-where-is-distinct-from.extension.js +1 -1
- package/dist/extensions/typed/typed-where/typed-where-is-in.extension.d.ts +36 -117
- package/dist/extensions/typed/typed-where/typed-where-is-in.extension.js +11 -19
- package/dist/extensions/typed/typed-where/typed-where-json.extension.d.ts +7 -6
- package/dist/extensions/typed/typed-where/typed-where-json.extension.js +1 -1
- package/dist/extensions/typed/typed-where/typed-where-lower.extension.d.ts +7 -6
- package/dist/extensions/typed/typed-where/typed-where-similar.extension.d.ts +5 -4
- package/dist/extensions/typed/typed-where/typed-where.extension.d.ts +30 -73
- package/dist/extensions/typed/typed-where/typed-where.extension.js +10 -10
- package/dist/extensions/union.extension.d.ts +22 -0
- package/dist/extensions/union.extension.js +52 -0
- package/dist/extensions/where-exists.extension.d.ts +50 -0
- package/dist/extensions/where-exists.extension.js +64 -0
- package/dist/extensions/where-relation-exists.extension.d.ts +38 -0
- package/dist/extensions/where-relation-exists.extension.js +95 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/types/extensions/cursor-pagination.types.d.ts +5 -0
- package/dist/types/extensions/get-many-with-totals.types.d.ts +19 -0
- package/dist/types/extensions/index.d.ts +5 -0
- package/dist/types/extensions/index.js +5 -0
- package/dist/types/extensions/join-lateral.types.d.ts +6 -0
- package/dist/types/extensions/join-lateral.types.js +2 -0
- package/dist/types/extensions/period.types.d.ts +8 -0
- package/dist/types/extensions/query-returning.types.d.ts +11 -0
- package/dist/types/extensions/query-returning.types.js +2 -0
- package/dist/types/extensions/typed-group-by.types.d.ts +8 -0
- package/dist/types/extensions/typed-group-by.types.js +2 -0
- package/dist/types/extensions/typed-having.types.d.ts +2 -0
- package/dist/types/extensions/typed-having.types.js +2 -0
- package/dist/types/extensions/union.types.d.ts +12 -0
- package/dist/types/extensions/union.types.js +2 -0
- package/dist/types/extensions/where.types.d.ts +1 -0
- package/dist/types/interfaces/config.interface.d.ts +17 -0
- package/dist/types/interfaces/config.interface.js +2 -0
- package/dist/types/interfaces/index.d.ts +1 -0
- package/dist/types/interfaces/index.js +1 -0
- package/dist/types/modules/proxy-callback.types.d.ts +28 -4
- package/dist/types/modules/typed-params.types.d.ts +45 -0
- package/dist/types/modules/typed-params.types.js +2 -0
- package/dist/types/modules/typed-root.types.d.ts +13 -0
- package/dist/types/modules/typed-root.types.js +2 -0
- package/dist/utils/calculated-column.utils.d.ts +21 -0
- package/dist/utils/calculated-column.utils.js +63 -0
- package/dist/utils/json-path.utils.d.ts +22 -0
- package/dist/utils/json-path.utils.js +57 -0
- package/dist/utils/parameter.utils.d.ts +7 -0
- package/dist/utils/parameter.utils.js +21 -0
- package/dist/utils/period.utils.js +7 -2
- package/dist/utils/prototype.utils.d.ts +5 -1
- package/dist/utils/prototype.utils.js +4 -1
- package/dist/utils/proxy-key.utils.d.ts +9 -4
- package/dist/utils/proxy-key.utils.js +21 -14
- package/dist/utils/query-builder.utils.d.ts +13 -3
- package/dist/utils/query-builder.utils.js +95 -18
- package/dist/utils/sql.utils.d.ts +2 -0
- package/dist/utils/sql.utils.js +5 -1
- package/dist/utils/table.utils.d.ts +112 -2
- package/dist/utils/table.utils.js +206 -22
- package/dist/utils/time-zone.utils.d.ts +8 -0
- package/dist/utils/time-zone.utils.js +102 -0
- package/dist/utils/where.utils.d.ts +2 -2
- package/dist/utils/where.utils.js +23 -2
- package/package.json +4 -3
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const typeorm_1 = require("typeorm");
|
|
4
|
+
const where_exists_extension_1 = require("./where-exists.extension");
|
|
5
|
+
const prototype_utils_1 = require("../utils/prototype.utils");
|
|
6
|
+
const query_builder_utils_1 = require("../utils/query-builder.utils");
|
|
7
|
+
const where_utils_1 = require("../utils/where.utils");
|
|
8
|
+
const createMethod = (method, negate) => function (relation, alias, where) {
|
|
9
|
+
return attachRelationExists(this, method, negate, relation, alias, where);
|
|
10
|
+
};
|
|
11
|
+
const extension = {
|
|
12
|
+
prototype: {
|
|
13
|
+
whereRelationExists: createMethod('where', false),
|
|
14
|
+
andWhereRelationExists: createMethod('andWhere', false),
|
|
15
|
+
orWhereRelationExists: createMethod('orWhere', false),
|
|
16
|
+
whereRelationNotExists: createMethod('where', true),
|
|
17
|
+
andWhereRelationNotExists: createMethod('andWhere', true),
|
|
18
|
+
orWhereRelationNotExists: createMethod('orWhere', true),
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
(0, prototype_utils_1.patchPrototype)(typeorm_1.SelectQueryBuilder, extension);
|
|
22
|
+
// implementation
|
|
23
|
+
function attachRelationExists(builder, method, negate, relationSelection, alias, where) {
|
|
24
|
+
const methodName = `${method}Relation${negate ? 'Not' : ''}Exists`;
|
|
25
|
+
const key = (0, query_builder_utils_1.getTypedSelectionKey)(builder, relationSelection);
|
|
26
|
+
if (Array.isArray(key)) {
|
|
27
|
+
throw new TypeError(`${methodName} expects the callback to select one relation, got ${key.length}: ${key.join(', ')}`);
|
|
28
|
+
}
|
|
29
|
+
const dot = key.indexOf('.');
|
|
30
|
+
const ownerAlias = key.slice(0, dot);
|
|
31
|
+
const propertyPath = key.slice(dot + 1);
|
|
32
|
+
const aliases = queryAliases(builder);
|
|
33
|
+
const owner = aliases.find((candidate) => candidate.name === ownerAlias);
|
|
34
|
+
const relation = (owner === null || owner === void 0 ? void 0 : owner.hasMetadata) ? owner.metadata.findRelationWithPropertyPath(propertyPath) : undefined;
|
|
35
|
+
if (!relation) {
|
|
36
|
+
throw new TypeError(`${methodName}: ${key} is not a relation of an entity of the query`);
|
|
37
|
+
}
|
|
38
|
+
// in the subquery the alias would hide the outer one the join condition refers to; SQLite matches
|
|
39
|
+
// names without case, so 'USER' would hide 'user' there
|
|
40
|
+
if (aliases.some((candidate) => candidate.name.toLowerCase() === alias.toLowerCase())) {
|
|
41
|
+
throw new TypeError(`${methodName}: the alias "${alias}" is already used by the query, choose another one`);
|
|
42
|
+
}
|
|
43
|
+
const column = (aliasName, columnName) => `${builder.escape(aliasName)}.${builder.escape(columnName)}`;
|
|
44
|
+
const subQuery = builder.connection
|
|
45
|
+
.createQueryBuilder()
|
|
46
|
+
.select('1')
|
|
47
|
+
.from(relation.inverseEntityMetadata.target, alias);
|
|
48
|
+
const correlation = joinCondition(relation, ownerAlias, alias, column, subQuery);
|
|
49
|
+
where === null || where === void 0 ? void 0 : where(subQuery);
|
|
50
|
+
// after the caller's conditions, so a `where` or an `orWhere` of theirs can't drop or bypass it
|
|
51
|
+
(0, where_utils_1.andWhereNarrowing)(subQuery, correlation);
|
|
52
|
+
return (0, where_exists_extension_1.attachExists)(builder, method, subQuery, negate);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The aliases of the query and of the queries it is part of. Inside `Brackets` the builder has the outer
|
|
56
|
+
* query's main alias but none of its joins: those are on its parent.
|
|
57
|
+
*/
|
|
58
|
+
function queryAliases(builder) {
|
|
59
|
+
const aliases = [];
|
|
60
|
+
// parentQueryBuilder is protected in TypeORM's types
|
|
61
|
+
let query = builder;
|
|
62
|
+
for (; query; query = query.parentQueryBuilder) {
|
|
63
|
+
const { aliases: own, mainAlias } = query.expressionMap;
|
|
64
|
+
aliases.push(...own, ...(mainAlias ? [mainAlias] : []));
|
|
65
|
+
}
|
|
66
|
+
return aliases;
|
|
67
|
+
}
|
|
68
|
+
const equal = (left, right) => `${left} = ${right}`;
|
|
69
|
+
/** The condition between the related rows (`alias`) and the outer row (`ownerAlias`); joins the junction table of a many-to-many. */
|
|
70
|
+
function joinCondition(relation, ownerAlias, alias, column, subQuery) {
|
|
71
|
+
var _a;
|
|
72
|
+
const referenced = (joinColumn) => joinColumn.referencedColumn.databaseName;
|
|
73
|
+
if (relation.isManyToMany) {
|
|
74
|
+
const junctionAlias = `${alias}_junction`;
|
|
75
|
+
// the owning side defines the junction table; from the other side its columns swap roles
|
|
76
|
+
const inverse = relation.inverseRelation;
|
|
77
|
+
const [ownerColumns, relatedColumns] = relation.isOwning
|
|
78
|
+
? [relation.joinColumns, relation.inverseJoinColumns]
|
|
79
|
+
: [inverse.inverseJoinColumns, inverse.joinColumns];
|
|
80
|
+
subQuery.innerJoin(((_a = relation.junctionEntityMetadata) !== null && _a !== void 0 ? _a : inverse.junctionEntityMetadata).target, junctionAlias, relatedColumns.map((joinColumn) => equal(column(alias, referenced(joinColumn)), column(junctionAlias, joinColumn.databaseName))).join(' AND '));
|
|
81
|
+
return ownerColumns
|
|
82
|
+
.map((joinColumn) => equal(column(junctionAlias, joinColumn.databaseName), column(ownerAlias, referenced(joinColumn))))
|
|
83
|
+
.join(' AND ');
|
|
84
|
+
}
|
|
85
|
+
// the join columns are on the outer entity's table
|
|
86
|
+
if (relation.isManyToOne || relation.isOneToOneOwner) {
|
|
87
|
+
return relation.joinColumns
|
|
88
|
+
.map((joinColumn) => equal(column(alias, referenced(joinColumn)), column(ownerAlias, joinColumn.databaseName)))
|
|
89
|
+
.join(' AND ');
|
|
90
|
+
}
|
|
91
|
+
// one-to-many, or the inverse side of a one-to-one: the join columns are on the related table
|
|
92
|
+
return relation.inverseRelation.joinColumns
|
|
93
|
+
.map((joinColumn) => equal(column(alias, joinColumn.databaseName), column(ownerAlias, referenced(joinColumn))))
|
|
94
|
+
.join(' AND ');
|
|
95
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export * from './extensions';
|
|
2
|
+
export * from './decorators';
|
|
2
3
|
export * from './types/interfaces';
|
|
3
4
|
export * from './types/extensions';
|
|
5
|
+
export type { Json } from './types/modules/proxy-callback.types';
|
|
6
|
+
export type { TypedRoot, WithTypedRoot } from './types/modules/typed-root.types';
|
|
4
7
|
export * from './utils/table.utils';
|
|
5
8
|
export { escapeLikePattern } from './utils/search.utils';
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
exports.escapeLikePattern = void 0;
|
|
18
18
|
__exportStar(require("./extensions"), exports);
|
|
19
|
+
__exportStar(require("./decorators"), exports);
|
|
19
20
|
__exportStar(require("./types/interfaces"), exports);
|
|
20
21
|
__exportStar(require("./types/extensions"), exports);
|
|
21
22
|
__exportStar(require("./utils/table.utils"), exports);
|
|
@@ -13,4 +13,9 @@ export interface ListWithCursor<Entity> {
|
|
|
13
13
|
list: Entity[];
|
|
14
14
|
/** Cursor of the next page, `null` on the last page. */
|
|
15
15
|
nextCursor: string | null;
|
|
16
|
+
/**
|
|
17
|
+
* Whether a next page follows. Always known, unlike the `hasMore` of `getManyWithTotals`: the
|
|
18
|
+
* keyset query loads `pageSize + 1` rows, so it needs no total. Equivalent to `nextCursor !== null`.
|
|
19
|
+
*/
|
|
20
|
+
hasMore: boolean;
|
|
16
21
|
}
|
|
@@ -1,12 +1,31 @@
|
|
|
1
|
+
import type { OrderType } from '../interfaces/ordering.interface';
|
|
1
2
|
import type { ApplyPaginationOptions } from './pagination.types';
|
|
2
3
|
/** Also takes the pagination options, e.g. `useTakeAndSkip` for queries with joined collections. */
|
|
3
4
|
export type GetManyWithTotalsOptions = ApplyPaginationOptions & {
|
|
4
5
|
disableTotalsCalculation?: boolean;
|
|
5
6
|
loadAll?: boolean;
|
|
6
7
|
};
|
|
8
|
+
/** Options of `getRawManyWithTotals`. Raw rows are paged with LIMIT / OFFSET, so there is no `useTakeAndSkip`. */
|
|
9
|
+
export type GetRawManyWithTotalsOptions = Omit<GetManyWithTotalsOptions, 'useTakeAndSkip'> & {
|
|
10
|
+
/**
|
|
11
|
+
* An expression that counts the rows of the query by itself, such as `COUNT(DISTINCT "order"."user_id")`
|
|
12
|
+
* for a query grouped by `"order"."user_id"`: cheaper than counting the query as a subquery.
|
|
13
|
+
* It replaces the select list, and GROUP BY, HAVING and DISTINCT are dropped: it can't count a query with HAVING,
|
|
14
|
+
* and counts a DISTINCT query only when it is a `COUNT(DISTINCT …)` itself.
|
|
15
|
+
*/
|
|
16
|
+
countExpression?: string;
|
|
17
|
+
};
|
|
7
18
|
export interface ListWithTotals<Entity> {
|
|
8
19
|
list: Entity[];
|
|
9
20
|
total: number | null;
|
|
10
21
|
limit: number | null;
|
|
11
22
|
offset: number | null;
|
|
23
|
+
/**
|
|
24
|
+
* Whether rows follow the page. `null` only with `loadAll`, where the page is every row.
|
|
25
|
+
* With `disableTotalsCalculation` it is still known: one row past the page is loaded and dropped,
|
|
26
|
+
* instead of counting.
|
|
27
|
+
*/
|
|
28
|
+
hasMore: boolean | null;
|
|
29
|
+
/** The ORDER BY clauses the query was built with, in the order they were applied. */
|
|
30
|
+
orderBy: OrderType[];
|
|
12
31
|
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
export * from './pagination.types';
|
|
2
2
|
export * from './order.types';
|
|
3
3
|
export * from './typed-order-by.types';
|
|
4
|
+
export * from './typed-group-by.types';
|
|
4
5
|
export * from './get-many-with-totals.types';
|
|
5
6
|
export * from './where.types';
|
|
6
7
|
export * from './period.types';
|
|
7
8
|
export * from './cursor-pagination.types';
|
|
8
9
|
export * from './search.types';
|
|
10
|
+
export * from './typed-having.types';
|
|
11
|
+
export * from './union.types';
|
|
12
|
+
export * from './join-lateral.types';
|
|
13
|
+
export * from './query-returning.types';
|
|
@@ -17,8 +17,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
__exportStar(require("./pagination.types"), exports);
|
|
18
18
|
__exportStar(require("./order.types"), exports);
|
|
19
19
|
__exportStar(require("./typed-order-by.types"), exports);
|
|
20
|
+
__exportStar(require("./typed-group-by.types"), exports);
|
|
20
21
|
__exportStar(require("./get-many-with-totals.types"), exports);
|
|
21
22
|
__exportStar(require("./where.types"), exports);
|
|
22
23
|
__exportStar(require("./period.types"), exports);
|
|
23
24
|
__exportStar(require("./cursor-pagination.types"), exports);
|
|
24
25
|
__exportStar(require("./search.types"), exports);
|
|
26
|
+
__exportStar(require("./typed-having.types"), exports);
|
|
27
|
+
__exportStar(require("./union.types"), exports);
|
|
28
|
+
__exportStar(require("./join-lateral.types"), exports);
|
|
29
|
+
__exportStar(require("./query-returning.types"), exports);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ObjectLiteral, SelectQueryBuilder } from 'typeorm';
|
|
2
|
+
/**
|
|
3
|
+
* The subquery of a LATERAL join: raw SQL (`SELECT … LIMIT 1`, without parentheses), a query
|
|
4
|
+
* builder, or a factory that builds it on the query builder it is given, as for TypeORM's joins.
|
|
5
|
+
*/
|
|
6
|
+
export type LateralSubQuery<SubEntity extends ObjectLiteral = ObjectLiteral> = string | SelectQueryBuilder<SubEntity> | ((qb: SelectQueryBuilder<ObjectLiteral>) => SelectQueryBuilder<SubEntity>);
|
|
@@ -12,4 +12,12 @@ export type ApplyPeriodOptions = {
|
|
|
12
12
|
* `{ periodFrom: '2024-01-01', periodTo: '2024-02-01' }`.
|
|
13
13
|
*/
|
|
14
14
|
includeTo?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* IANA time zone (`'America/New_York'`) in which a date (`'2024-01-31'`) and a date-time without an offset
|
|
17
|
+
* (`'2024-01-31 10:00'`) are read: `periodTo: '2024-01-31'` ends at midnight of February 1st in that zone.
|
|
18
|
+
* They become instants (`Date`), so it is for timestamp columns, not `date` ones. `Date`s and strings with
|
|
19
|
+
* an offset are instants already and stay as they are; other strings throw a `TypeError`, as does an
|
|
20
|
+
* unknown zone. Missing, `null` or `''`: the strings go to the database as they are.
|
|
21
|
+
*/
|
|
22
|
+
timeZone?: string | null;
|
|
15
23
|
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ObjectLiteral } from 'typeorm';
|
|
2
|
+
/** What `queryReturning` resolves to: the same shape for every statement and every driver. */
|
|
3
|
+
export interface QueryReturningResult<Row = ObjectLiteral> {
|
|
4
|
+
/** The rows the statement returned: those of a SELECT, or of a `RETURNING` clause. Empty when there are none. */
|
|
5
|
+
records: Row[];
|
|
6
|
+
/**
|
|
7
|
+
* The number of rows the driver reports for the statement, `null` when it reports none.
|
|
8
|
+
* SQLite reports none for a statement that returns rows, such as an UPDATE with `RETURNING`.
|
|
9
|
+
*/
|
|
10
|
+
affected: number | null;
|
|
11
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type TypedGroupByOptions = {
|
|
2
|
+
/**
|
|
3
|
+
* Group by the quoted database column (`"ticket"."ticket_status"`) instead of `ticket.status`,
|
|
4
|
+
* which TypeORM maps to the column itself. TypeORM maps only aliases with metadata, so a subquery
|
|
5
|
+
* in FROM needs it on PostgreSQL, where an unquoted `sub.firstName` is folded to `firstname`.
|
|
6
|
+
*/
|
|
7
|
+
useQuotes?: boolean;
|
|
8
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ObjectLiteral, SelectQueryBuilder } from 'typeorm';
|
|
2
|
+
/** A part of a UNION: a query builder, or raw SQL whose placeholders read the outer builder's parameters. */
|
|
3
|
+
export type UnionQuery = string | {
|
|
4
|
+
getQuery(): string;
|
|
5
|
+
getParameters(): ObjectLiteral;
|
|
6
|
+
};
|
|
7
|
+
export type UnionOptions = {
|
|
8
|
+
/** `UNION ALL`: keep duplicate rows. Defaults to `false`, `UNION`, which removes them. */
|
|
9
|
+
all?: boolean;
|
|
10
|
+
};
|
|
11
|
+
/** TypeORM's options of `addCommonTableExpression` (`materialized`, `columnNames`), and `all`. */
|
|
12
|
+
export type UnionCteOptions = UnionOptions & NonNullable<Parameters<SelectQueryBuilder<ObjectLiteral>['addCommonTableExpression']>[2]>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export type WhereMethods = 'where' | 'andWhere' | 'orWhere';
|
|
2
|
+
export type HavingMethods = 'having' | 'andHaving' | 'orHaving';
|
|
2
3
|
export type WhereInMethods = 'whereIsIn' | 'andWhereIsIn' | 'orWhereIsIn' | 'whereIsNotIn' | 'andWhereIsNotIn' | 'orWhereIsNotIn';
|
|
3
4
|
export type WhereAnyMethods = 'whereIsAny' | 'andWhereIsAny' | 'orWhereIsAny' | 'whereIsNotAny' | 'andWhereIsNotAny' | 'orWhereIsNotAny';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile-time options of the library, switched on by augmenting this interface in your project:
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* declare module 'typeorm-extensions' {
|
|
6
|
+
* interface TypeormExtensionsConfig {
|
|
7
|
+
* strictParams: true;
|
|
8
|
+
* }
|
|
9
|
+
* }
|
|
10
|
+
* ```
|
|
11
|
+
*
|
|
12
|
+
* - `strictParams`: typed where methods check their value against the type of the column the callback
|
|
13
|
+
* selects (`whereTyped(q => q.price, '>', '10')` doesn't compile for a `number` column). Applies to calls
|
|
14
|
+
* without an explicit type argument: `whereTyped<Joins>(…)` keeps unchecked parameters.
|
|
15
|
+
*/
|
|
16
|
+
export interface TypeormExtensionsConfig {
|
|
17
|
+
}
|
|
@@ -18,3 +18,4 @@ __exportStar(require("./ordering.interface"), exports);
|
|
|
18
18
|
__exportStar(require("./pagination.interface"), exports);
|
|
19
19
|
__exportStar(require("./period.interface"), exports);
|
|
20
20
|
__exportStar(require("./cursor-pagination.interface"), exports);
|
|
21
|
+
__exportStar(require("./config.interface"), exports);
|
|
@@ -4,25 +4,49 @@ import { ColumnValueType } from "./column.types";
|
|
|
4
4
|
import { UnwrapArray } from "../util-types";
|
|
5
5
|
export type ProxyColumnValueType = ColumnValueType | ':value';
|
|
6
6
|
type ProxyPropertyType<V> = NonNullable<V> extends readonly (infer U)[] ? NonNullable<U> : NonNullable<V>;
|
|
7
|
+
/** Objects that are column values, not relations or JSON objects to go into. */
|
|
8
|
+
type ProxyLeafObject = Date | ArrayBufferView | ArrayBuffer;
|
|
9
|
+
type IsProxyBranch<V> = ProxyPropertyType<V> extends ProxyLeafObject ? false : ProxyPropertyType<V> extends object ? true : false;
|
|
10
|
+
declare const jsonColumn: unique symbol;
|
|
11
|
+
/**
|
|
12
|
+
* Marks an entity property as a JSON column for typed callbacks, which then go into it at any depth:
|
|
13
|
+
* `(e) => e.details.meta.endTs` is `"e"."details" -> 'meta' ->> 'endTs'` in where methods. Without it a typed key
|
|
14
|
+
* stops at `alias.column` (`e.details.outcome`), as it does for relations. Values are assignable both ways.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* @Column({ type: 'jsonb' })
|
|
18
|
+
* details: Json<ExecutionDetails>;
|
|
19
|
+
*/
|
|
20
|
+
export type Json<T> = T & {
|
|
21
|
+
readonly [jsonColumn]?: true;
|
|
22
|
+
};
|
|
23
|
+
type IsJsonColumn<V> = typeof jsonColumn extends keyof NonNullable<V> ? true : false;
|
|
24
|
+
type IsJsonObject<V> = NonNullable<V> extends ProxyLeafObject | readonly unknown[] ? false : NonNullable<V> extends object ? true : false;
|
|
25
|
+
/** Inside a JSON column: objects to go into at any depth, while arrays and dates are values. */
|
|
26
|
+
type JsonPathProxy<T> = {
|
|
27
|
+
[K in keyof T as K extends typeof jsonColumn ? never : K]-?: IsJsonObject<T[K]> extends true ? JsonPathProxy<NonNullable<T[K]>> : T[K];
|
|
28
|
+
};
|
|
29
|
+
type JsonColumnProxy<V> = NonNullable<V> extends readonly unknown[] ? V : JsonPathProxy<NonNullable<V>>;
|
|
7
30
|
/**
|
|
8
31
|
* End of a typed key path. Typed keys are `column` or `alias.column` (`relation.column`),
|
|
9
32
|
* so an object at the second level (a nested relation, a JSON sub-object) can be selected
|
|
10
33
|
* as a whole but not dereferenced further: `u => u.images.user.email` is a compile error.
|
|
11
|
-
* Address deeper relations by their join alias with an alias-keyed type instead
|
|
34
|
+
* Address deeper relations by their join alias with an alias-keyed type instead, and mark
|
|
35
|
+
* JSON columns with {@link Json} to go deeper into them.
|
|
12
36
|
*/
|
|
13
37
|
export interface KeyPathEnd {
|
|
14
38
|
readonly __keyPathEnd: never;
|
|
15
39
|
}
|
|
16
40
|
type ProxySecondLevel<T> = {
|
|
17
|
-
[K in keyof T]-?:
|
|
41
|
+
[K in keyof T]-?: IsJsonColumn<T[K]> extends true ? JsonColumnProxy<T[K]> : IsProxyBranch<T[K]> extends true ? KeyPathEnd : T[K];
|
|
18
42
|
};
|
|
19
43
|
/**
|
|
20
44
|
* Type of the proxy passed to typed callbacks: two levels (`column` or `alias.column`),
|
|
21
45
|
* with arrays unwrapped (`user.images.url` for `images: ImageEntity[]`) and optional
|
|
22
|
-
* properties made required.
|
|
46
|
+
* properties made required. A {@link Json} column is open at any depth.
|
|
23
47
|
*/
|
|
24
48
|
export type DeepUnwrapArray<T> = {
|
|
25
|
-
[K in keyof T]-?:
|
|
49
|
+
[K in keyof T]-?: IsJsonColumn<T[K]> extends true ? JsonColumnProxy<T[K]> : IsProxyBranch<T[K]> extends true ? ProxySecondLevel<ProxyPropertyType<T[K]>> : T[K];
|
|
26
50
|
};
|
|
27
51
|
export type KeyProxyCallback<T extends ObjectLiteral> = (proxy: DeepUnwrapArray<T>) => ProxyColumnValueType | Array<ProxyColumnValueType>;
|
|
28
52
|
export type RelationalKeyProxyCallback<T extends ObjectLiteral> = (proxy: DeepUnwrapArray<EntityOnlyWithRelations<T>>) => ProxyColumnValueType;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { TypeormExtensionsConfig } from '../interfaces/config.interface';
|
|
2
|
+
import type { TypedWhereOptions } from '../extensions/typed-where.types';
|
|
3
|
+
import type { ArgsType } from '../util-types';
|
|
4
|
+
declare const unsetType: unique symbol;
|
|
5
|
+
/**
|
|
6
|
+
* Default of the `Type` argument of typed methods. Tells a call without a type argument, whose proxy is
|
|
7
|
+
* typed by the query builder's entity, from an explicit `whereTyped<Joins>(…)`.
|
|
8
|
+
*/
|
|
9
|
+
export interface UnsetType {
|
|
10
|
+
readonly [unsetType]: true;
|
|
11
|
+
}
|
|
12
|
+
/** Blocks inference from this position, so the column type comes from the callback alone (`NoInfer` before TypeScript 5.4). */
|
|
13
|
+
type NoInferType<T> = [T][T extends unknown ? 0 : never];
|
|
14
|
+
/** Whether `Type` was left out; an explicit `<any>` is assignable to `UnsetType` but was given. */
|
|
15
|
+
type IsUnset<Type> = 0 extends 1 & Type ? false : [Type] extends [UnsetType] ? true : false;
|
|
16
|
+
export type TypeOrDefault<Type, Default> = IsUnset<Type> extends true ? Default : Type;
|
|
17
|
+
type IsStrict<Type> = TypeormExtensionsConfig extends {
|
|
18
|
+
strictParams: true;
|
|
19
|
+
} ? IsUnset<Type> : false;
|
|
20
|
+
/** A column's value as a parameter: never `null` / `undefined` (they turn `= :value` into `= NULL`); a date may be a string. */
|
|
21
|
+
export type ParamValue<Value> = NonNullable<Value> extends Date ? Date | string : NonNullable<Value>;
|
|
22
|
+
type IsValueObject<P> = [P] extends [Date | ArrayBufferView | ArrayBuffer | readonly unknown[]] ? true : false;
|
|
23
|
+
/**
|
|
24
|
+
* The value itself, or a `{ name: value }` params object. A plain object value (a JSON column) can only be
|
|
25
|
+
* passed in a params object: at runtime a plain object always is one.
|
|
26
|
+
*/
|
|
27
|
+
type DirectOrField<P> = [P] extends [object] ? IsValueObject<P> extends true ? P | Readonly<Record<string, P>> : Readonly<Record<string, P>> : P | Readonly<Record<string, P>>;
|
|
28
|
+
type Trim<S extends string> = S extends ` ${infer Rest}` ? Trim<Rest> : S extends `${infer Rest} ` ? Trim<Rest> : S;
|
|
29
|
+
type NoValueOperator = 'IS NULL' | 'IS NOT NULL' | 'IS TRUE' | 'IS NOT TRUE' | 'IS FALSE' | 'IS NOT FALSE' | 'IS UNKNOWN' | 'IS NOT UNKNOWN';
|
|
30
|
+
type ComparisonOperator = '=' | '!=' | '<>' | '>' | '<' | '>=' | '<=';
|
|
31
|
+
type DistinctOperator = 'IS DISTINCT FROM' | 'IS NOT DISTINCT FROM';
|
|
32
|
+
type NullOperator = 'IS' | 'IS NOT';
|
|
33
|
+
type ListOperator = 'IN' | 'NOT IN';
|
|
34
|
+
type PatternOperator = 'LIKE' | 'NOT LIKE' | 'ILIKE' | 'NOT ILIKE';
|
|
35
|
+
type LooseWhereArgs = [parameters?: unknown, options?: TypedWhereOptions];
|
|
36
|
+
type ValueArgs<P> = [parameters: DirectOrField<P>, options?: TypedWhereOptions];
|
|
37
|
+
type StrictWhereArgs<Operator extends string, Value> = Uppercase<Trim<Operator>> extends infer Op ? Op extends NoValueOperator ? [parameters?: undefined, options?: TypedWhereOptions] : Op extends ComparisonOperator ? ValueArgs<ParamValue<Value>> : Op extends DistinctOperator ? ValueArgs<ParamValue<Value> | null> : Op extends NullOperator ? ValueArgs<null> : Op extends ListOperator ? ValueArgs<readonly ParamValue<Value>[]> : Op extends PatternOperator ? ValueArgs<string> : LooseWhereArgs : never;
|
|
38
|
+
/**
|
|
39
|
+
* Parameters and options of `whereTyped`. With `strictParams` the parameter is checked against the column type
|
|
40
|
+
* for the operators it knows; conditions written as SQL (`'BETWEEN :from AND :to'`, `'= true'`) aren't checked.
|
|
41
|
+
*/
|
|
42
|
+
export type TypedWhereArgs<Type, Operator extends string, Value> = IsStrict<Type> extends true ? StrictWhereArgs<NoInferType<Operator>, NoInferType<Value>> : LooseWhereArgs;
|
|
43
|
+
/** Values of `whereIsInTyped` / `whereIsAnyTyped`; with `strictParams`, of the column's type. */
|
|
44
|
+
export type TypedArrayArg<Type, Value> = IsStrict<Type> extends true ? readonly ParamValue<NoInferType<Value>>[] : ArgsType;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ObjectLiteral } from 'typeorm';
|
|
2
|
+
declare const typedRoot: unique symbol;
|
|
3
|
+
declare class TypedRootMark<Root> {
|
|
4
|
+
[typedRoot]?(): Root;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The entity type of a builder whose typed callbacks take another root, set with `typed<Root>()`: the entity
|
|
8
|
+
* with an optional symbol method that only types carry. Entities loaded with it are plain entities.
|
|
9
|
+
*/
|
|
10
|
+
export type WithTypedRoot<Entity, Root extends ObjectLiteral> = Entity & TypedRootMark<Root>;
|
|
11
|
+
/** The root of a builder's typed callbacks: the one set with `typed<Root>()`, or else its entity. */
|
|
12
|
+
export type TypedRoot<Entity> = Entity extends TypedRootMark<infer Root> ? Root extends ObjectLiteral ? Root : Entity : Entity;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ObjectLiteral, SelectQueryBuilder } from 'typeorm';
|
|
2
|
+
type ResolvedCalculatedColumn = {
|
|
3
|
+
/** The key TypeORM reads the column's value from when it builds the entity. */
|
|
4
|
+
key: string;
|
|
5
|
+
/** `Entity.property`, for error messages. */
|
|
6
|
+
label: string;
|
|
7
|
+
};
|
|
8
|
+
/** Whether `property` of the entity of `aliasName` (the main alias by default) is a `@CalculatedColumn`. */
|
|
9
|
+
export declare function isCalculatedColumn<Entity extends ObjectLiteral>(builder: SelectQueryBuilder<Entity>, property: string, aliasName?: string): boolean;
|
|
10
|
+
/** The `@CalculatedColumn` `property` of the entity of `aliasName`; throws a `TypeError` for anything else. */
|
|
11
|
+
export declare function resolveCalculatedColumn<Entity extends ObjectLiteral>(builder: SelectQueryBuilder<Entity>, property: string, aliasName: string | undefined, methodName: string): ResolvedCalculatedColumn;
|
|
12
|
+
/**
|
|
13
|
+
* Selects the value of a `@CalculatedColumn`, already selected with `addSelectCalculated`, a second
|
|
14
|
+
* time under a generated lower-case alias, and returns the alias to order by.
|
|
15
|
+
*
|
|
16
|
+
* The alias the entity is built from can't be ordered by: PostgreSQL folds it unquoted
|
|
17
|
+
* (`user_imagesCount` → `user_imagescount`), and quoted it breaks the DISTINCT query TypeORM pages
|
|
18
|
+
* through with `take` / `skip` and joins, which takes only select aliases it knows unquoted.
|
|
19
|
+
*/
|
|
20
|
+
export declare function selectCalculatedForOrder<Entity extends ObjectLiteral>(builder: SelectQueryBuilder<Entity>, property: string, aliasName: string | undefined, methodName: string): string;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.selectCalculatedForOrder = exports.resolveCalculatedColumn = exports.isCalculatedColumn = void 0;
|
|
4
|
+
const DriverUtils_1 = require("typeorm/driver/DriverUtils");
|
|
5
|
+
const sql_utils_1 = require("./sql.utils");
|
|
6
|
+
function findCalculatedColumn(builder, property, aliasName) {
|
|
7
|
+
const { expressionMap } = builder;
|
|
8
|
+
const alias = aliasName === undefined
|
|
9
|
+
? expressionMap.mainAlias
|
|
10
|
+
: expressionMap.aliases.find((candidate) => candidate.name === aliasName);
|
|
11
|
+
if (!(alias === null || alias === void 0 ? void 0 : alias.hasMetadata)) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
aliasName: alias.name,
|
|
16
|
+
entityName: alias.metadata.name,
|
|
17
|
+
column: alias.metadata.findColumnWithPropertyPath(property),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// @CalculatedColumn: a virtual property without the query of TypeORM's @VirtualColumn
|
|
21
|
+
const isCalculated = (column) => !!(column === null || column === void 0 ? void 0 : column.isVirtualProperty) && !column.query;
|
|
22
|
+
/** Whether `property` of the entity of `aliasName` (the main alias by default) is a `@CalculatedColumn`. */
|
|
23
|
+
function isCalculatedColumn(builder, property, aliasName) {
|
|
24
|
+
var _a;
|
|
25
|
+
return isCalculated((_a = findCalculatedColumn(builder, property, aliasName)) === null || _a === void 0 ? void 0 : _a.column);
|
|
26
|
+
}
|
|
27
|
+
exports.isCalculatedColumn = isCalculatedColumn;
|
|
28
|
+
/** The `@CalculatedColumn` `property` of the entity of `aliasName`; throws a `TypeError` for anything else. */
|
|
29
|
+
function resolveCalculatedColumn(builder, property, aliasName, methodName) {
|
|
30
|
+
var _a;
|
|
31
|
+
const found = findCalculatedColumn(builder, property, aliasName);
|
|
32
|
+
if (!found) {
|
|
33
|
+
throw new TypeError(`${methodName}: the alias "${aliasName !== null && aliasName !== void 0 ? aliasName : (_a = builder.expressionMap.mainAlias) === null || _a === void 0 ? void 0 : _a.name}" has no entity with "${property}"`);
|
|
34
|
+
}
|
|
35
|
+
const label = `${found.entityName}.${property}`;
|
|
36
|
+
if (!isCalculated(found.column)) {
|
|
37
|
+
throw new TypeError(`${methodName}: ${label} is not a @CalculatedColumn`);
|
|
38
|
+
}
|
|
39
|
+
// shortened past the driver's alias length limit, as TypeORM does it
|
|
40
|
+
const key = DriverUtils_1.DriverUtils.buildAlias(builder.connection.driver, {}, found.aliasName, found.column.databaseName);
|
|
41
|
+
return { key, label };
|
|
42
|
+
}
|
|
43
|
+
exports.resolveCalculatedColumn = resolveCalculatedColumn;
|
|
44
|
+
/**
|
|
45
|
+
* Selects the value of a `@CalculatedColumn`, already selected with `addSelectCalculated`, a second
|
|
46
|
+
* time under a generated lower-case alias, and returns the alias to order by.
|
|
47
|
+
*
|
|
48
|
+
* The alias the entity is built from can't be ordered by: PostgreSQL folds it unquoted
|
|
49
|
+
* (`user_imagesCount` → `user_imagescount`), and quoted it breaks the DISTINCT query TypeORM pages
|
|
50
|
+
* through with `take` / `skip` and joins, which takes only select aliases it knows unquoted.
|
|
51
|
+
*/
|
|
52
|
+
function selectCalculatedForOrder(builder, property, aliasName, methodName) {
|
|
53
|
+
const { key, label } = resolveCalculatedColumn(builder, property, aliasName, methodName);
|
|
54
|
+
const select = builder.expressionMap.selects.find((candidate) => candidate.aliasName === key);
|
|
55
|
+
if (!select) {
|
|
56
|
+
throw new TypeError(`${methodName}: ${label} isn't selected: select it with addSelectCalculated first`);
|
|
57
|
+
}
|
|
58
|
+
const orderAlias = (0, sql_utils_1.createUniqueParameterName)('calculated').toLowerCase();
|
|
59
|
+
// TypeORM keeps a subquery selection as its SQL, with its parameters already on the builder
|
|
60
|
+
builder.addSelect(select.selection, orderAlias);
|
|
61
|
+
return orderAlias;
|
|
62
|
+
}
|
|
63
|
+
exports.selectCalculatedForOrder = selectCalculatedForOrder;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata';
|
|
2
|
+
import type { DatabaseType } from 'typeorm';
|
|
3
|
+
/** `->>` gives the value as text (as an SQL value on SQLite), `->` as JSON. */
|
|
4
|
+
export type JsonAccessor = 'text' | 'json';
|
|
5
|
+
export declare const isJsonColumn: (column: ColumnMetadata) => boolean;
|
|
6
|
+
/** `-> 'settings' ->> 'theme'`: every key but the last one with `->`, the last one with `jsonOperator`. */
|
|
7
|
+
export declare function buildJsonPath(fieldNames: readonly string[], jsonOperator: '->>' | '->'): string;
|
|
8
|
+
export interface JsonPathSqlOptions {
|
|
9
|
+
/** Defaults to `'text'`. */
|
|
10
|
+
accessor?: JsonAccessor;
|
|
11
|
+
/** The value the path is compared with: on PostgreSQL a `'text'` path is cast to its type. */
|
|
12
|
+
value?: unknown;
|
|
13
|
+
/** Compares a `'text'` path as a timestamp whatever `value` is, since a period also takes date strings. */
|
|
14
|
+
asTimestamp?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* SQL for a path inside a JSON column: `"exec"."details" -> 'meta' ->> 'endTs'`, cast on PostgreSQL to the
|
|
18
|
+
* type of the compared value (`CAST(… AS numeric)`). A `simple-json` column is text there, so it is cast to
|
|
19
|
+
* `jsonb` first. SQLite (3.38+) has the same operators, and `->>` gives SQL values, so it needs no cast, except
|
|
20
|
+
* for a timestamp: there it is rewritten in the format TypeORM stores dates and binds `Date` parameters in.
|
|
21
|
+
*/
|
|
22
|
+
export declare function toJsonPathSql(driver: DatabaseType | undefined, columnSql: string, column: ColumnMetadata, jsonPath: readonly string[], { accessor, value, asTimestamp }?: JsonPathSqlOptions): string;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toJsonPathSql = exports.buildJsonPath = exports.isJsonColumn = void 0;
|
|
4
|
+
const sql_utils_1 = require("./sql.utils");
|
|
5
|
+
const JSON_COLUMN_TYPES = ['json', 'jsonb', 'simple-json'];
|
|
6
|
+
const isJsonColumn = (column) => JSON_COLUMN_TYPES.includes(String(column.type));
|
|
7
|
+
exports.isJsonColumn = isJsonColumn;
|
|
8
|
+
/** Wraps a JSON key in single quotes, doubling any quote inside it (standard SQL string literal). */
|
|
9
|
+
const toJsonKeyLiteral = (fieldName) => `'${String(fieldName).replace(/'/g, "''")}'`;
|
|
10
|
+
/** `-> 'settings' ->> 'theme'`: every key but the last one with `->`, the last one with `jsonOperator`. */
|
|
11
|
+
function buildJsonPath(fieldNames, jsonOperator) {
|
|
12
|
+
return fieldNames
|
|
13
|
+
.map((fieldName, index) => `${index === fieldNames.length - 1 ? jsonOperator : '->'} ${toJsonKeyLiteral(fieldName)}`)
|
|
14
|
+
.join(' ');
|
|
15
|
+
}
|
|
16
|
+
exports.buildJsonPath = buildJsonPath;
|
|
17
|
+
const isDate = (value) => Object.prototype.toString.call(value) === '[object Date]';
|
|
18
|
+
/**
|
|
19
|
+
* PostgreSQL type to compare a `->>` value (text) with `value` as: text compares `'9' >= '10'` as true.
|
|
20
|
+
* An array (IN, ANY) is typed by its first value; strings and unknown values stay text.
|
|
21
|
+
*/
|
|
22
|
+
function getJsonTextCast(value) {
|
|
23
|
+
const sample = Array.isArray(value) ? value.find((item) => item !== null && item !== undefined) : value;
|
|
24
|
+
if (typeof sample === 'number' || typeof sample === 'bigint') {
|
|
25
|
+
return 'numeric';
|
|
26
|
+
}
|
|
27
|
+
if (typeof sample === 'boolean') {
|
|
28
|
+
return 'boolean';
|
|
29
|
+
}
|
|
30
|
+
if (isDate(sample)) {
|
|
31
|
+
return 'timestamptz';
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* SQL for a path inside a JSON column: `"exec"."details" -> 'meta' ->> 'endTs'`, cast on PostgreSQL to the
|
|
37
|
+
* type of the compared value (`CAST(… AS numeric)`). A `simple-json` column is text there, so it is cast to
|
|
38
|
+
* `jsonb` first. SQLite (3.38+) has the same operators, and `->>` gives SQL values, so it needs no cast, except
|
|
39
|
+
* for a timestamp: there it is rewritten in the format TypeORM stores dates and binds `Date` parameters in.
|
|
40
|
+
*/
|
|
41
|
+
function toJsonPathSql(driver, columnSql, column, jsonPath, { accessor = 'text', value, asTimestamp = false } = {}) {
|
|
42
|
+
const path = buildJsonPath(jsonPath, accessor === 'json' ? '->' : '->>');
|
|
43
|
+
const isTimestamp = asTimestamp && accessor === 'text';
|
|
44
|
+
if ((0, sql_utils_1.isSqliteLikeDriver)(driver)) {
|
|
45
|
+
const sql = `${columnSql} ${path}`;
|
|
46
|
+
return isTimestamp ? `strftime('%Y-%m-%d %H:%M:%f', ${sql})` : sql;
|
|
47
|
+
}
|
|
48
|
+
if (!(0, sql_utils_1.isPostgresLikeDriver)(driver)) {
|
|
49
|
+
throw new TypeError(`Typed keys into a JSON column (${column.propertyPath}.${jsonPath.join('.')}) are supported on PostgreSQL and SQLite, ` +
|
|
50
|
+
`not on ${driver !== null && driver !== void 0 ? driver : 'a query without a connection'}`);
|
|
51
|
+
}
|
|
52
|
+
const source = String(column.type) === 'simple-json' ? `CAST(${columnSql} AS jsonb)` : columnSql;
|
|
53
|
+
const sql = `${source} ${path}`;
|
|
54
|
+
const cast = isTimestamp ? 'timestamptz' : accessor === 'text' ? getJsonTextCast(value) : undefined;
|
|
55
|
+
return cast ? `CAST(${sql} AS ${cast})` : sql;
|
|
56
|
+
}
|
|
57
|
+
exports.toJsonPathSql = toJsonPathSql;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ObjectLiteral } from 'typeorm';
|
|
2
|
+
/**
|
|
3
|
+
* Renames the parameters of a subquery to unique names, in its SQL and in its parameters. A query
|
|
4
|
+
* has one parameter namespace, so two subqueries with a `:status` each would otherwise both read
|
|
5
|
+
* whichever value was set last. Placeholders that aren't keys of `parameters` stay as they are.
|
|
6
|
+
*/
|
|
7
|
+
export declare function withUniqueParameterNames(sql: string, parameters: ObjectLiteral): [string, ObjectLiteral];
|