typeorm-extensions 1.3.5 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +14 -10
  2. package/dist/constants/index.d.ts +1 -1
  3. package/dist/constants/index.js +2 -2
  4. package/dist/extensions/cursor-pagination.extension.d.ts +18 -0
  5. package/dist/extensions/cursor-pagination.extension.js +108 -0
  6. package/dist/extensions/index.d.ts +7 -0
  7. package/dist/extensions/index.js +7 -0
  8. package/dist/extensions/order-by-array-position.extension.d.ts +24 -0
  9. package/dist/extensions/order-by-array-position.extension.js +18 -0
  10. package/dist/extensions/order.extension.d.ts +3 -2
  11. package/dist/extensions/order.extension.js +16 -13
  12. package/dist/extensions/period-filter.extension.d.ts +29 -0
  13. package/dist/extensions/period-filter.extension.js +23 -0
  14. package/dist/extensions/typed/index.d.ts +10 -0
  15. package/dist/extensions/typed/index.js +10 -0
  16. package/dist/extensions/typed/typed-distinct/typed-distinct.extension.js +2 -2
  17. package/dist/extensions/typed/typed-joins/typed-joins.extension.js +2 -1
  18. package/dist/extensions/typed/typed-order-by/typed-order-by-array-position.extension.d.ts +20 -0
  19. package/dist/extensions/typed/typed-order-by/typed-order-by-array-position.extension.js +21 -0
  20. package/dist/extensions/typed/typed-order-by/typed-order-by.extension.d.ts +4 -3
  21. package/dist/extensions/typed/typed-order-by/typed-order-by.extension.js +7 -6
  22. package/dist/extensions/typed/typed-returning/typed-returning.extension.d.ts +27 -0
  23. package/dist/extensions/typed/typed-returning/typed-returning.extension.js +38 -0
  24. package/dist/extensions/typed/typed-select/typed-select.extension.d.ts +6 -6
  25. package/dist/extensions/typed/typed-select/typed-select.extension.js +11 -8
  26. package/dist/extensions/typed/typed-where/typed-period-filter.extension.d.ts +31 -0
  27. package/dist/extensions/typed/typed-where/typed-period-filter.extension.js +25 -0
  28. package/dist/extensions/typed/typed-where/typed-where-array.extension.d.ts +68 -0
  29. package/dist/extensions/typed/typed-where/typed-where-array.extension.js +41 -0
  30. package/dist/extensions/typed/typed-where/typed-where-ilike.extension.d.ts +62 -0
  31. package/dist/extensions/typed/typed-where/typed-where-ilike.extension.js +84 -0
  32. package/dist/extensions/typed/typed-where/typed-where-is-any.extension.d.ts +144 -0
  33. package/dist/extensions/typed/typed-where/typed-where-is-any.extension.js +37 -0
  34. package/dist/extensions/typed/typed-where/typed-where-is-distinct-from.extension.d.ts +53 -0
  35. package/dist/extensions/typed/typed-where/typed-where-is-distinct-from.extension.js +38 -0
  36. package/dist/extensions/typed/typed-where/typed-where-is-in.extension.d.ts +25 -26
  37. package/dist/extensions/typed/typed-where/typed-where-is-in.extension.js +16 -23
  38. package/dist/extensions/typed/typed-where/typed-where-json.extension.d.ts +53 -0
  39. package/dist/extensions/typed/typed-where/typed-where-json.extension.js +39 -0
  40. package/dist/extensions/typed/typed-where/typed-where-lower.extension.d.ts +59 -0
  41. package/dist/extensions/typed/typed-where/typed-where-lower.extension.js +86 -0
  42. package/dist/extensions/typed/typed-where/typed-where-similar.extension.d.ts +54 -0
  43. package/dist/extensions/typed/typed-where/typed-where-similar.extension.js +73 -0
  44. package/dist/extensions/typed/typed-where/typed-where.extension.d.ts +13 -13
  45. package/dist/extensions/typed/typed-where/typed-where.extension.js +4 -12
  46. package/dist/extensions/where-array.extension.d.ts +70 -0
  47. package/dist/extensions/where-array.extension.js +47 -0
  48. package/dist/extensions/where-is-any.extension.d.ts +142 -0
  49. package/dist/extensions/where-is-any.extension.js +44 -0
  50. package/dist/extensions/where-is-distinct-from.extension.d.ts +56 -0
  51. package/dist/extensions/where-is-distinct-from.extension.js +55 -0
  52. package/dist/extensions/where-json.extension.d.ts +55 -0
  53. package/dist/extensions/where-json.extension.js +64 -0
  54. package/dist/helpers.d.ts +2 -0
  55. package/dist/helpers.js +23 -0
  56. package/dist/index.d.ts +2 -0
  57. package/dist/index.js +4 -0
  58. package/dist/types/extensions/cursor-pagination.types.d.ts +16 -0
  59. package/dist/types/extensions/cursor-pagination.types.js +2 -0
  60. package/dist/types/extensions/get-many-with-totals.types.d.ts +3 -1
  61. package/dist/types/extensions/index.d.ts +4 -0
  62. package/dist/types/extensions/index.js +4 -0
  63. package/dist/types/extensions/order.types.d.ts +1 -1
  64. package/dist/types/extensions/period.types.d.ts +15 -0
  65. package/dist/types/extensions/period.types.js +2 -0
  66. package/dist/types/extensions/search.types.d.ts +68 -0
  67. package/dist/types/extensions/search.types.js +2 -0
  68. package/dist/types/extensions/typed-order-by.types.d.ts +3 -0
  69. package/dist/types/extensions/typed-order-by.types.js +2 -0
  70. package/dist/types/extensions/typed-where.types.d.ts +4 -4
  71. package/dist/types/extensions/where.types.d.ts +1 -0
  72. package/dist/types/interfaces/cursor-pagination.interface.d.ts +16 -0
  73. package/dist/types/interfaces/cursor-pagination.interface.js +2 -0
  74. package/dist/types/interfaces/index.d.ts +2 -0
  75. package/dist/types/interfaces/index.js +2 -0
  76. package/dist/types/interfaces/period.interface.d.ts +12 -0
  77. package/dist/types/interfaces/period.interface.js +2 -0
  78. package/dist/types/modules/proxy-callback.types.d.ts +26 -3
  79. package/dist/types/util-types/index.d.ts +3 -1
  80. package/dist/utils/cursor.utils.d.ts +31 -0
  81. package/dist/utils/cursor.utils.js +130 -0
  82. package/dist/utils/order.utils.d.ts +15 -0
  83. package/dist/utils/order.utils.js +45 -0
  84. package/dist/utils/period.utils.d.ts +9 -0
  85. package/dist/utils/period.utils.js +82 -0
  86. package/dist/utils/proxy-key.utils.d.ts +13 -2
  87. package/dist/utils/proxy-key.utils.js +32 -14
  88. package/dist/utils/query-builder.utils.d.ts +46 -0
  89. package/dist/utils/query-builder.utils.js +208 -0
  90. package/dist/utils/search.utils.d.ts +27 -0
  91. package/dist/utils/search.utils.js +83 -0
  92. package/dist/utils/sql.utils.d.ts +27 -1
  93. package/dist/utils/sql.utils.js +87 -9
  94. package/dist/utils/table.utils.d.ts +86 -0
  95. package/dist/utils/table.utils.js +135 -0
  96. package/dist/utils/where.utils.d.ts +18 -1
  97. package/dist/utils/where.utils.js +62 -9
  98. package/helpers/package.json +7 -0
  99. package/package.json +3 -3
  100. package/dist/utils/identifier.utils.d.ts +0 -15
  101. package/dist/utils/identifier.utils.js +0 -45
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const typeorm_1 = require("typeorm");
4
+ const prototype_utils_1 = require("../utils/prototype.utils");
5
+ const query_builder_utils_1 = require("../utils/query-builder.utils");
6
+ const sql_utils_1 = require("../utils/sql.utils");
7
+ /* eslint-enable @typescript-eslint/no-empty-interface, @typescript-eslint/no-unused-vars */
8
+ // patching
9
+ const createWhereJsonContainsMethod = (method) => function (field, value) {
10
+ return attachWhereJsonContains(this, method, field, value);
11
+ };
12
+ const createWhereJsonHasKeyMethod = (method) => function (field, key) {
13
+ return attachWhereJsonHasKey(this, method, field, key);
14
+ };
15
+ const extension = {
16
+ prototype: {
17
+ whereJsonContains: createWhereJsonContainsMethod('where'),
18
+ andWhereJsonContains: createWhereJsonContainsMethod('andWhere'),
19
+ orWhereJsonContains: createWhereJsonContainsMethod('orWhere'),
20
+ whereJsonHasKey: createWhereJsonHasKeyMethod('where'),
21
+ andWhereJsonHasKey: createWhereJsonHasKeyMethod('andWhere'),
22
+ orWhereJsonHasKey: createWhereJsonHasKeyMethod('orWhere'),
23
+ },
24
+ };
25
+ const queryBuilders = [
26
+ typeorm_1.SelectQueryBuilder,
27
+ typeorm_1.UpdateQueryBuilder,
28
+ typeorm_1.DeleteQueryBuilder,
29
+ ];
30
+ queryBuilders.forEach((builder) => {
31
+ (0, prototype_utils_1.patchPrototype)(builder, extension);
32
+ });
33
+ // implementation
34
+ /**
35
+ * `CAST("table"."column" AS jsonb)`: makes `json` and `simple-json` (text) columns work, and is dropped by PostgreSQL
36
+ * for `jsonb` columns, so it doesn't stop a GIN index from being used.
37
+ */
38
+ function toJsonbOperand(builder, field) {
39
+ return `CAST(${(0, query_builder_utils_1.toFieldSql)(builder, field)} AS jsonb)`;
40
+ }
41
+ function attachWhereJsonContains(builder, method, field, value) {
42
+ if (!method || typeof field !== 'string') {
43
+ return builder;
44
+ }
45
+ const json = JSON.stringify(value);
46
+ if (json === undefined) {
47
+ // `undefined` / functions have no JSON form; matching nothing or everything silently would hide the bug
48
+ throw new TypeError(`JSON condition on "${field}" expects a JSON value, got ${String(value)}`);
49
+ }
50
+ const paramName = (0, sql_utils_1.createUniqueParameterName)(method);
51
+ // CAST("table"."column" AS jsonb) @> CAST(:param AS jsonb), where :param is the JSON text
52
+ return builder[method](`${toJsonbOperand(builder, field)} @> CAST(:${paramName} AS jsonb)`, { [paramName]: json });
53
+ }
54
+ function attachWhereJsonHasKey(builder, method, field, key) {
55
+ if (!method || typeof field !== 'string') {
56
+ return builder;
57
+ }
58
+ if (typeof key !== 'string') {
59
+ throw new TypeError(`JSON key condition on "${field}" expects a string key, got ${String(key)}`);
60
+ }
61
+ const paramName = (0, sql_utils_1.createUniqueParameterName)(method);
62
+ // the `?` operator, not jsonb_exists(): only the operator can use a GIN index
63
+ return builder[method](`${toJsonbOperand(builder, field)} ? :${paramName}`, { [paramName]: key });
64
+ }
@@ -0,0 +1,2 @@
1
+ export * from './utils/table.utils';
2
+ export { escapeLikePattern } from './utils/search.utils';
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.escapeLikePattern = void 0;
18
+ // Side-effect-free entry point: `typeorm-extensions/helpers`.
19
+ // Exports helpers (tableHelper, escapeLikePattern) only and patches nothing; importing the package root
20
+ // (`typeorm-extensions`) installs every QueryBuilder extension.
21
+ __exportStar(require("./utils/table.utils"), exports);
22
+ var search_utils_1 = require("./utils/search.utils");
23
+ Object.defineProperty(exports, "escapeLikePattern", { enumerable: true, get: function () { return search_utils_1.escapeLikePattern; } });
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export * from './extensions';
2
2
  export * from './types/interfaces';
3
3
  export * from './types/extensions';
4
+ export * from './utils/table.utils';
5
+ export { escapeLikePattern } from './utils/search.utils';
package/dist/index.js CHANGED
@@ -14,6 +14,10 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.escapeLikePattern = void 0;
17
18
  __exportStar(require("./extensions"), exports);
18
19
  __exportStar(require("./types/interfaces"), exports);
19
20
  __exportStar(require("./types/extensions"), exports);
21
+ __exportStar(require("./utils/table.utils"), exports);
22
+ var search_utils_1 = require("./utils/search.utils");
23
+ Object.defineProperty(exports, "escapeLikePattern", { enumerable: true, get: function () { return search_utils_1.escapeLikePattern; } });
@@ -0,0 +1,16 @@
1
+ import type { CursorOrderParam } from '../interfaces/cursor-pagination.interface';
2
+ export type GetManyWithCursorOptions<Entity> = {
3
+ /**
4
+ * Columns that order the rows, e.g. `[{ field: 'createdAt', direction: 'DESC' }]`.
5
+ * The primary key is appended when it isn't listed, so the order is total and no row is skipped
6
+ * or repeated between pages. It replaces any ORDER BY of the query.
7
+ */
8
+ orderBy: Array<CursorOrderParam<Entity>>;
9
+ /** Use `take` instead of `limit`, as needed for queries with joined collections. Defaults to `false`. */
10
+ useTakeAndSkip?: boolean;
11
+ };
12
+ export interface ListWithCursor<Entity> {
13
+ list: Entity[];
14
+ /** Cursor of the next page, `null` on the last page. */
15
+ nextCursor: string | null;
16
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,4 +1,6 @@
1
- export type GetManyWithTotalsOptions = {
1
+ import type { ApplyPaginationOptions } from './pagination.types';
2
+ /** Also takes the pagination options, e.g. `useTakeAndSkip` for queries with joined collections. */
3
+ export type GetManyWithTotalsOptions = ApplyPaginationOptions & {
2
4
  disableTotalsCalculation?: boolean;
3
5
  loadAll?: boolean;
4
6
  };
@@ -1,4 +1,8 @@
1
1
  export * from './pagination.types';
2
2
  export * from './order.types';
3
+ export * from './typed-order-by.types';
3
4
  export * from './get-many-with-totals.types';
4
5
  export * from './where.types';
6
+ export * from './period.types';
7
+ export * from './cursor-pagination.types';
8
+ export * from './search.types';
@@ -16,5 +16,9 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./pagination.types"), exports);
18
18
  __exportStar(require("./order.types"), exports);
19
+ __exportStar(require("./typed-order-by.types"), exports);
19
20
  __exportStar(require("./get-many-with-totals.types"), exports);
20
21
  __exportStar(require("./where.types"), exports);
22
+ __exportStar(require("./period.types"), exports);
23
+ __exportStar(require("./cursor-pagination.types"), exports);
24
+ __exportStar(require("./search.types"), exports);
@@ -1,5 +1,5 @@
1
1
  export type ApplyOrderOptions = {
2
- useDoubleQuotes?: boolean;
2
+ useQuotes?: boolean;
3
3
  alias?: string;
4
4
  alwaysAliasFields?: string[];
5
5
  resetPreviousOrder?: boolean;
@@ -0,0 +1,15 @@
1
+ export type ApplyPeriodOptions = {
2
+ /**
3
+ * Include the start of the period (default `true`): `field >= periodFrom`.
4
+ * With `false`: `field > periodFrom`, and a date-only `periodFrom` (`'2024-01-01'`) excludes that
5
+ * whole day: `field >= '2024-01-02'`.
6
+ */
7
+ includeFrom?: boolean;
8
+ /**
9
+ * Include the end of the period (default `true`): `field <= periodTo`, and a date-only `periodTo`
10
+ * (`'2024-01-31'`) includes that whole day: `field < '2024-02-01'`.
11
+ * With `false`: `field < periodTo`, a half-open period such as
12
+ * `{ periodFrom: '2024-01-01', periodTo: '2024-02-01' }`.
13
+ */
14
+ includeTo?: boolean;
15
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,68 @@
1
+ import type { ObjectLiteral } from 'typeorm';
2
+ import type { KeyProxyCallback } from '../modules/proxy-callback.types';
3
+ /**
4
+ * Where the search value must be found in the column:
5
+ * - `'contains'` → `%value%`;
6
+ * - `'prefix'` → `value%`;
7
+ * - `'suffix'` → `%value`;
8
+ * - `'exact'` → `value` (a case-insensitive equality).
9
+ */
10
+ export type LikeMatch = 'contains' | 'prefix' | 'suffix' | 'exact';
11
+ /** A raw SQL expression to search in, used as is in parentheses: `{ sql: \`u."firstName" || ' ' || u."lastName"\` }`. */
12
+ export type SqlExpression = {
13
+ sql: string;
14
+ };
15
+ /** A column (typed callback, may select several columns) or a raw SQL expression to search in. */
16
+ export type SearchSelector<Type extends ObjectLiteral> = KeyProxyCallback<Type> | SqlExpression;
17
+ export type TypedILikeOptions = {
18
+ /** Where the value must be found in the column. Defaults to `'contains'`. */
19
+ match?: LikeMatch;
20
+ /**
21
+ * Escape `%`, `_` and `\` in the value with a backslash, so they match themselves.
22
+ * Defaults to `true`; `false` passes the value's own wildcards through.
23
+ */
24
+ escape?: boolean;
25
+ /**
26
+ * Compare without accents (PostgreSQL `unaccent` extension): `unaccent(col) ILIKE unaccent(:p)`.
27
+ * `true` calls `unaccent`; a string names the function to call, e.g. an IMMUTABLE wrapper
28
+ * that an expression index is built on: `'immutable_unaccent'`, `'public.f_unaccent'`.
29
+ */
30
+ unaccent?: boolean | string;
31
+ };
32
+ export type TypedSearchOptions = TypedILikeOptions & {
33
+ /** A value shorter than this (after trimming) adds no condition. Defaults to 1. */
34
+ minLength?: number;
35
+ };
36
+ export type TypedLowerOptions = {
37
+ /**
38
+ * Wraps both the column and the parameter: `(sql) => \`LOWER(TRIM(${sql}))\``.
39
+ * Defaults to `(sql) => \`LOWER(${sql})\``. Write it exactly like the expression index
40
+ * the condition should use. The result must contain `sql`.
41
+ */
42
+ normalize?: (sql: string) => string;
43
+ };
44
+ /**
45
+ * pg_trgm operator, with the column on the left:
46
+ * - `'%'` → `col % :term`: `similarity(col, term)` is above `pg_trgm.similarity_threshold`;
47
+ * - `'%>'` → `col %> :term` (same as `:term <% col`): the term is similar to a word of the column,
48
+ * `word_similarity(term, col)` is above `pg_trgm.word_similarity_threshold`;
49
+ * - `'<%'` → `col <% :term`: the column is similar to a word of the term.
50
+ *
51
+ * `%` and `%>` can use a `gin_trgm_ops` / `gist_trgm_ops` index on the column, `<%` can't.
52
+ */
53
+ export type TrigramOperator = '%' | '%>' | '<%';
54
+ export type TypedSimilarOptions = {
55
+ /** Defaults to `'%'`. */
56
+ operator?: TrigramOperator;
57
+ };
58
+ /**
59
+ * pg_trgm score function:
60
+ * - `'similarity'` → `similarity(col, :term)`, pairs with the `%` operator;
61
+ * - `'word_similarity'` → `word_similarity(:term, col)`, pairs with `%>`;
62
+ * - `'strict_word_similarity'` → `strict_word_similarity(:term, col)`.
63
+ */
64
+ export type SimilarityFunction = 'similarity' | 'word_similarity' | 'strict_word_similarity';
65
+ export type SimilarityScoreOptions = {
66
+ /** Defaults to `'similarity'`. */
67
+ function?: SimilarityFunction;
68
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,3 @@
1
+ export type TypedOrderByOptions = {
2
+ useQuotes?: boolean;
3
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,7 +1,7 @@
1
1
  export type TypedWhereOptions = {
2
- useDoubleQuotes?: boolean;
2
+ /**
3
+ * Append a placeholder for the value to the condition (`=` → `= :param`). Defaults to `true`,
4
+ * or to `false` when the condition already references a key of the parameters (`'= :id'` with `{ id }`).
5
+ */
3
6
  addValue?: boolean;
4
7
  };
5
- export type TypedWhereIsInOptions = {
6
- useDoubleQuotes?: boolean;
7
- };
@@ -1,2 +1,3 @@
1
1
  export type WhereMethods = 'where' | 'andWhere' | 'orWhere';
2
2
  export type WhereInMethods = 'whereIsIn' | 'andWhereIsIn' | 'orWhereIsIn' | 'whereIsNotIn' | 'andWhereIsNotIn' | 'orWhereIsNotIn';
3
+ export type WhereAnyMethods = 'whereIsAny' | 'andWhereIsAny' | 'orWhereIsAny' | 'whereIsNotAny' | 'andWhereIsNotAny' | 'orWhereIsNotAny';
@@ -0,0 +1,16 @@
1
+ import type { NonFunctionPropertyNames } from '../util-types';
2
+ import type { OrderDirection } from './ordering.interface';
3
+ /** The page to load with keyset (cursor) pagination, e.g. from query parameters. */
4
+ export interface CursorPaginationFilter {
5
+ /** `nextCursor` of the previous page; missing, `null` or empty for the first page. */
6
+ cursor?: string | null;
7
+ /** Rows per page, 10 by default. */
8
+ pageSize?: number;
9
+ }
10
+ /** A column that orders the rows of keyset pagination. */
11
+ export type CursorOrderParam<Entity> = {
12
+ /** Property of the main entity. The column must not be `NULL`. */
13
+ field: NonFunctionPropertyNames<Required<Entity>>;
14
+ /** `'ASC'` by default. */
15
+ direction?: OrderDirection;
16
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,2 +1,4 @@
1
1
  export * from './ordering.interface';
2
2
  export * from './pagination.interface';
3
+ export * from './period.interface';
4
+ export * from './cursor-pagination.interface';
@@ -16,3 +16,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./ordering.interface"), exports);
18
18
  __exportStar(require("./pagination.interface"), exports);
19
+ __exportStar(require("./period.interface"), exports);
20
+ __exportStar(require("./cursor-pagination.interface"), exports);
@@ -0,0 +1,12 @@
1
+ /** A period boundary: a `Date`, or a date / timestamp string the database understands (`'2024-01-31'`). */
2
+ export type PeriodBoundary = Date | string;
3
+ /**
4
+ * A period to filter rows by, e.g. from query parameters. Both ends are optional and,
5
+ * by default, inclusive: `periodFrom` → `field >= periodFrom`, `periodTo` → `field <= periodTo`.
6
+ * A date-only string (`'2024-01-31'`) stands for the whole day: `periodTo: '2024-01-31'`
7
+ * → `field < '2024-02-01'`. See `ApplyPeriodOptions` to exclude either end.
8
+ */
9
+ export interface PeriodFilter {
10
+ periodFrom?: PeriodBoundary;
11
+ periodTo?: PeriodBoundary;
12
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,7 +1,30 @@
1
1
  import { ObjectLiteral } from "typeorm";
2
2
  import { EntityOnlyWithRelations, EntityWithoutRelations } from "./relations.types";
3
3
  import { ColumnValueType } from "./column.types";
4
+ import { UnwrapArray } from "../util-types";
4
5
  export type ProxyColumnValueType = ColumnValueType | ':value';
5
- export type KeyProxyCallback<T extends ObjectLiteral> = (proxy: T) => ProxyColumnValueType | Array<ProxyColumnValueType>;
6
- export type RelationalKeyProxyCallback<T extends ObjectLiteral> = (proxy: EntityOnlyWithRelations<T>) => ProxyColumnValueType;
7
- export type NonRelationalKeyProxyCallback<T extends ObjectLiteral> = (proxy: EntityWithoutRelations<T>) => ProxyColumnValueType;
6
+ type ProxyPropertyType<V> = NonNullable<V> extends readonly (infer U)[] ? NonNullable<U> : NonNullable<V>;
7
+ /**
8
+ * End of a typed key path. Typed keys are `column` or `alias.column` (`relation.column`),
9
+ * so an object at the second level (a nested relation, a JSON sub-object) can be selected
10
+ * 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.
12
+ */
13
+ export interface KeyPathEnd {
14
+ readonly __keyPathEnd: never;
15
+ }
16
+ type ProxySecondLevel<T> = {
17
+ [K in keyof T]-?: ProxyPropertyType<T[K]> extends object ? KeyPathEnd : T[K];
18
+ };
19
+ /**
20
+ * Type of the proxy passed to typed callbacks: two levels (`column` or `alias.column`),
21
+ * with arrays unwrapped (`user.images.url` for `images: ImageEntity[]`) and optional
22
+ * properties made required.
23
+ */
24
+ export type DeepUnwrapArray<T> = {
25
+ [K in keyof T]-?: ProxyPropertyType<T[K]> extends object ? ProxySecondLevel<ProxyPropertyType<T[K]>> : T[K];
26
+ };
27
+ export type KeyProxyCallback<T extends ObjectLiteral> = (proxy: DeepUnwrapArray<T>) => ProxyColumnValueType | Array<ProxyColumnValueType>;
28
+ export type RelationalKeyProxyCallback<T extends ObjectLiteral> = (proxy: DeepUnwrapArray<EntityOnlyWithRelations<T>>) => ProxyColumnValueType;
29
+ export type NonRelationalKeyProxyCallback<T extends ObjectLiteral> = (proxy: EntityWithoutRelations<UnwrapArray<T>>) => ProxyColumnValueType;
30
+ export {};
@@ -1,4 +1,5 @@
1
- export type ArgsType = unknown[];
1
+ /** Values for IN / NOT IN conditions. Readonly so `as const` arrays are accepted; the array is only read. */
2
+ export type ArgsType = readonly unknown[];
2
3
  export type FunctionPropertyNames<T> = {
3
4
  [K in keyof T]: T[K] extends (...args: any[]) => any ? K : never;
4
5
  }[keyof T] & string;
@@ -6,3 +7,4 @@ export type NonFunctionPropertyNames<T> = {
6
7
  [K in keyof T]: T[K] extends (...args: any[]) => any ? never : K;
7
8
  }[keyof T] & string;
8
9
  export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
10
+ export type UnwrapArray<T> = T extends (infer U)[] ? U : T;
@@ -0,0 +1,31 @@
1
+ import type { DatabaseType } from 'typeorm';
2
+ /** A cursor that can't be decoded or doesn't belong to the query's order (e.g. tampered with). */
3
+ export declare class InvalidCursorError extends TypeError {
4
+ constructor(message: string);
5
+ }
6
+ /** A key value in a cursor: as read from the database, or a JS Date tagged as `{ $date }`. */
7
+ export type CursorValue = string | number | boolean;
8
+ /** base64url-encoded JSON of the key fields and the key values of the last row. */
9
+ export declare function encodeCursor(fields: string[], values: unknown[]): string;
10
+ /** Key values of a cursor made for `fields`; throws `InvalidCursorError` otherwise. */
11
+ export declare function decodeCursor(cursor: string, fields: string[]): Array<CursorValue | Date>;
12
+ /** Databases that compare row values: `(a, b) < (1, 2)`. */
13
+ export declare const supportsRowValueComparison: (driver?: DatabaseType) => boolean;
14
+ /**
15
+ * Type to read a date / time column as text with, so the cursor keeps the database's own precision
16
+ * (PostgreSQL timestamps have microseconds, a JS Date only milliseconds). `undefined` when unknown.
17
+ */
18
+ export declare const getTextCastType: (driver?: DatabaseType) => string | undefined;
19
+ export type KeysetColumn = {
20
+ /** quoted column, e.g. `"user"."createdAt"` */
21
+ column: string;
22
+ direction: 'ASC' | 'DESC';
23
+ /** parameter name holding the cursor value of this column */
24
+ paramName: string;
25
+ };
26
+ /**
27
+ * WHERE condition for the rows after the cursor:
28
+ * - same direction everywhere, on databases with row values: `(a, b) > (:a, :b)`
29
+ * - otherwise: `a >= :a AND (a > :a OR (a = :a AND b < :b))`, where the leading bound lets an index on `a` be used
30
+ */
31
+ export declare function buildKeysetCondition(keys: KeysetColumn[], useRowValues: boolean): string;
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildKeysetCondition = exports.getTextCastType = exports.supportsRowValueComparison = exports.decodeCursor = exports.encodeCursor = exports.InvalidCursorError = void 0;
4
+ const sql_utils_1 = require("./sql.utils");
5
+ /** A cursor that can't be decoded or doesn't belong to the query's order (e.g. tampered with). */
6
+ class InvalidCursorError extends TypeError {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = 'InvalidCursorError';
10
+ }
11
+ }
12
+ exports.InvalidCursorError = InvalidCursorError;
13
+ const isDate = (value) => Object.prototype.toString.call(value) === '[object Date]';
14
+ const toCursorValue = (value) => {
15
+ if (isDate(value)) {
16
+ return { $date: value.toISOString() };
17
+ }
18
+ if (typeof value === 'bigint') {
19
+ return value.toString();
20
+ }
21
+ return value;
22
+ };
23
+ // base64url of UTF-8 text, with the globals of Node 16+ and browsers (no Buffer, no @types/node)
24
+ const toBase64Url = (text) => {
25
+ let binary = '';
26
+ new TextEncoder().encode(text).forEach((byte) => (binary += String.fromCharCode(byte)));
27
+ return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
28
+ };
29
+ const fromBase64Url = (value) => {
30
+ if (!/^[A-Za-z0-9_-]*$/.test(value)) {
31
+ throw new InvalidCursorError('Invalid cursor: not base64url');
32
+ }
33
+ const base64 = value.replace(/-/g, '+').replace(/_/g, '/');
34
+ const binary = atob(base64 + '='.repeat((4 - (base64.length % 4)) % 4));
35
+ return new TextDecoder('utf-8', { fatal: true }).decode(Uint8Array.from(binary, (char) => char.charCodeAt(0)));
36
+ };
37
+ /** base64url-encoded JSON of the key fields and the key values of the last row. */
38
+ function encodeCursor(fields, values) {
39
+ const payload = { k: fields, v: values.map(toCursorValue) };
40
+ return toBase64Url(JSON.stringify(payload));
41
+ }
42
+ exports.encodeCursor = encodeCursor;
43
+ const isCursorValue = (value) => typeof value === 'string' ||
44
+ (typeof value === 'number' && Number.isFinite(value)) ||
45
+ typeof value === 'boolean' ||
46
+ (typeof value === 'object' &&
47
+ value !== null &&
48
+ typeof value.$date === 'string');
49
+ /** Key values of a cursor made for `fields`; throws `InvalidCursorError` otherwise. */
50
+ function decodeCursor(cursor, fields) {
51
+ let payload;
52
+ try {
53
+ payload = JSON.parse(fromBase64Url(cursor));
54
+ }
55
+ catch {
56
+ throw new InvalidCursorError('Invalid cursor: it could not be decoded');
57
+ }
58
+ if (!payload ||
59
+ !Array.isArray(payload.k) ||
60
+ !Array.isArray(payload.v) ||
61
+ payload.k.length !== fields.length ||
62
+ payload.v.length !== fields.length ||
63
+ payload.k.some((field, i) => field !== fields[i]) ||
64
+ !payload.v.every(isCursorValue)) {
65
+ throw new InvalidCursorError(`Invalid cursor: it was not made for the order (${fields.join(', ')})`);
66
+ }
67
+ return payload.v.map((value) => {
68
+ if (typeof value !== 'object') {
69
+ return value;
70
+ }
71
+ const date = new Date(value.$date);
72
+ if (isNaN(date.getTime())) {
73
+ throw new InvalidCursorError('Invalid cursor: invalid date');
74
+ }
75
+ return date;
76
+ });
77
+ }
78
+ exports.decodeCursor = decodeCursor;
79
+ /** Databases that compare row values: `(a, b) < (1, 2)`. */
80
+ const supportsRowValueComparison = (driver) => driver === 'postgres' ||
81
+ driver === 'aurora-postgres' ||
82
+ driver === 'cockroachdb' ||
83
+ (0, sql_utils_1.isMysqlLikeDriver)(driver) ||
84
+ (0, sql_utils_1.isSqliteLikeDriver)(driver);
85
+ exports.supportsRowValueComparison = supportsRowValueComparison;
86
+ /**
87
+ * Type to read a date / time column as text with, so the cursor keeps the database's own precision
88
+ * (PostgreSQL timestamps have microseconds, a JS Date only milliseconds). `undefined` when unknown.
89
+ */
90
+ const getTextCastType = (driver) => {
91
+ if (driver === 'postgres' || driver === 'aurora-postgres' || driver === 'cockroachdb' || (0, sql_utils_1.isSqliteLikeDriver)(driver)) {
92
+ return 'text';
93
+ }
94
+ if ((0, sql_utils_1.isMysqlLikeDriver)(driver)) {
95
+ return 'char';
96
+ }
97
+ if (driver === 'mssql') {
98
+ return 'nvarchar(max)';
99
+ }
100
+ return undefined;
101
+ };
102
+ exports.getTextCastType = getTextCastType;
103
+ /**
104
+ * WHERE condition for the rows after the cursor:
105
+ * - same direction everywhere, on databases with row values: `(a, b) > (:a, :b)`
106
+ * - otherwise: `a >= :a AND (a > :a OR (a = :a AND b < :b))`, where the leading bound lets an index on `a` be used
107
+ */
108
+ function buildKeysetCondition(keys, useRowValues) {
109
+ const operator = (key) => (key.direction === 'ASC' ? '>' : '<');
110
+ if (keys.length === 1) {
111
+ const [key] = keys;
112
+ return `${key.column} ${operator(key)} :${key.paramName}`;
113
+ }
114
+ if (useRowValues && keys.every((key) => key.direction === keys[0].direction)) {
115
+ const columns = keys.map((key) => key.column).join(', ');
116
+ const params = keys.map((key) => `:${key.paramName}`).join(', ');
117
+ return `(${columns}) ${operator(keys[0])} (${params})`;
118
+ }
119
+ const expand = (index) => {
120
+ const key = keys[index];
121
+ const after = `${key.column} ${operator(key)} :${key.paramName}`;
122
+ if (index === keys.length - 1) {
123
+ return after;
124
+ }
125
+ return `(${after} OR (${key.column} = :${key.paramName} AND ${expand(index + 1)}))`;
126
+ };
127
+ const [first] = keys;
128
+ return `(${first.column} ${operator(first)}= :${first.paramName} AND ${expand(0)})`;
129
+ }
130
+ exports.buildKeysetCondition = buildKeysetCondition;
@@ -0,0 +1,15 @@
1
+ import type { ObjectLiteral, SelectQueryBuilder } from 'typeorm';
2
+ import type { OrderDirection } from '../types/interfaces/ordering.interface';
3
+ import type { ArgsType } from '../types/util-types';
4
+ /**
5
+ * `'ASC'` / `'DESC'` for any `OrderDirection`: `'asc'` / `'desc'` in any case, `1` / `-1`,
6
+ * `true` (ASC) / `false` (DESC). Anything else is the default direction (ASC).
7
+ */
8
+ export declare function toSqlOrderDirection(direction: OrderDirection | undefined | null): 'ASC' | 'DESC';
9
+ /**
10
+ * Selects `array_position(:values, column)` under a generated alias and orders by that alias.
11
+ * TypeORM can't order by a function expression together with `take` and joins
12
+ * (`"array_position(…" alias was not found`), but it can order by a selection alias.
13
+ * `column` is already quoted.
14
+ */
15
+ export declare function attachOrderByArrayPosition<Entity extends ObjectLiteral>(builder: SelectQueryBuilder<Entity>, column: string, values: ArgsType, arrayType: string | undefined, replace: boolean): SelectQueryBuilder<Entity>;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.attachOrderByArrayPosition = exports.toSqlOrderDirection = void 0;
4
+ const constants_1 = require("../constants");
5
+ const sql_utils_1 = require("./sql.utils");
6
+ /**
7
+ * `'ASC'` / `'DESC'` for any `OrderDirection`: `'asc'` / `'desc'` in any case, `1` / `-1`,
8
+ * `true` (ASC) / `false` (DESC). Anything else is the default direction (ASC).
9
+ */
10
+ function toSqlOrderDirection(direction) {
11
+ if (typeof direction === 'boolean') {
12
+ return direction ? 'ASC' : 'DESC';
13
+ }
14
+ if (typeof direction === 'number') {
15
+ return direction === -1 ? 'DESC' : 'ASC';
16
+ }
17
+ if (typeof direction === 'string' && /asc|desc/i.test(direction)) {
18
+ return direction.toUpperCase();
19
+ }
20
+ return constants_1.defaultOrderDirection;
21
+ }
22
+ exports.toSqlOrderDirection = toSqlOrderDirection;
23
+ /**
24
+ * Selects `array_position(:values, column)` under a generated alias and orders by that alias.
25
+ * TypeORM can't order by a function expression together with `take` and joins
26
+ * (`"array_position(…" alias was not found`), but it can order by a selection alias.
27
+ * `column` is already quoted.
28
+ */
29
+ function attachOrderByArrayPosition(builder, column, values, arrayType, replace) {
30
+ if (!Array.isArray(values)) {
31
+ throw new TypeError(`orderByArrayPosition on "${column}" expects an array of values, got ${values}`);
32
+ }
33
+ const castTo = arrayType === undefined ? undefined : (0, sql_utils_1.assertArrayType)(arrayType);
34
+ const paramName = (0, sql_utils_1.createUniqueParameterName)('position');
35
+ // lower case: PostgreSQL folds the unquoted alias in ORDER BY
36
+ const selectionAlias = `array_${paramName}`.toLowerCase();
37
+ const parameter = castTo ? `CAST(:${paramName} AS ${castTo})` : `:${paramName}`;
38
+ builder
39
+ .addSelect(`array_position(${parameter}, ${column})`, selectionAlias)
40
+ .setParameter(paramName, values);
41
+ return replace
42
+ ? builder.orderBy(selectionAlias, 'ASC', 'NULLS LAST')
43
+ : builder.addOrderBy(selectionAlias, 'ASC', 'NULLS LAST');
44
+ }
45
+ exports.attachOrderByArrayPosition = attachOrderByArrayPosition;
@@ -0,0 +1,9 @@
1
+ import type { WhereExpressionBuilder } from 'typeorm';
2
+ import type { PeriodFilter } from '../types/interfaces/period.interface';
3
+ import type { ApplyPeriodOptions } from '../types/extensions/period.types';
4
+ /**
5
+ * Adds `column >= :periodFrom` and `column <= :periodTo` conditions (or their exclusive versions) with
6
+ * AND, so the filter narrows whatever WHERE conditions the query already has, OR ones included:
7
+ * `(a OR b) AND column >= …`. `column` is already quoted.
8
+ */
9
+ export declare function attachPeriodFilter<QB extends WhereExpressionBuilder>(builder: QB, column: string, periodFilter: PeriodFilter | null | undefined, options?: ApplyPeriodOptions): QB;