typeorm-extensions 2.1.0 → 2.2.0-rc.1

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 (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -3
  3. package/dist/decorators/calculated-column.decorator.d.ts +30 -0
  4. package/dist/decorators/calculated-column.decorator.js +82 -0
  5. package/dist/decorators/index.d.ts +1 -0
  6. package/dist/decorators/index.js +17 -0
  7. package/dist/extensions/calculated-column.extension.d.ts +33 -0
  8. package/dist/extensions/calculated-column.extension.js +33 -0
  9. package/dist/extensions/cursor-pagination.extension.d.ts +2 -1
  10. package/dist/extensions/cursor-pagination.extension.js +4 -3
  11. package/dist/extensions/get-many-with-totals.extension.d.ts +9 -1
  12. package/dist/extensions/get-many-with-totals.extension.js +159 -10
  13. package/dist/extensions/index.d.ts +7 -0
  14. package/dist/extensions/index.js +7 -0
  15. package/dist/extensions/join-lateral.extension.d.ts +19 -0
  16. package/dist/extensions/join-lateral.extension.js +50 -0
  17. package/dist/extensions/join-unnest.extension.d.ts +19 -0
  18. package/dist/extensions/join-unnest.extension.js +40 -0
  19. package/dist/extensions/order.extension.js +18 -7
  20. package/dist/extensions/query-returning.extension.d.ts +29 -0
  21. package/dist/extensions/query-returning.extension.js +50 -0
  22. package/dist/extensions/typed/index.d.ts +3 -0
  23. package/dist/extensions/typed/index.js +3 -2
  24. package/dist/extensions/typed/typed-distinct/typed-distinct.extension.d.ts +3 -2
  25. package/dist/extensions/typed/typed-group-by/typed-group-by.extension.d.ts +24 -0
  26. package/dist/extensions/typed/typed-group-by/typed-group-by.extension.js +31 -0
  27. package/dist/extensions/typed/typed-having/typed-having.extension.d.ts +36 -0
  28. package/dist/extensions/typed/typed-having/typed-having.extension.js +42 -0
  29. package/dist/extensions/typed/typed-joins/typed-joins.extension.d.ts +5 -4
  30. package/dist/extensions/typed/typed-order-by/typed-order-by-array-position.extension.d.ts +3 -2
  31. package/dist/extensions/typed/typed-order-by/typed-order-by-array-position.extension.js +10 -2
  32. package/dist/extensions/typed/typed-order-by/typed-order-by.extension.d.ts +3 -2
  33. package/dist/extensions/typed/typed-order-by/typed-order-by.extension.js +3 -2
  34. package/dist/extensions/typed/typed-root.extension.d.ts +25 -0
  35. package/dist/extensions/typed/typed-root.extension.js +13 -0
  36. package/dist/extensions/typed/typed-select/typed-select.extension.d.ts +5 -4
  37. package/dist/extensions/typed/typed-select/typed-select.extension.js +3 -2
  38. package/dist/extensions/typed/typed-where/typed-period-filter.extension.d.ts +2 -1
  39. package/dist/extensions/typed/typed-where/typed-where-array.extension.d.ts +10 -9
  40. package/dist/extensions/typed/typed-where/typed-where-array.extension.js +32 -0
  41. package/dist/extensions/typed/typed-where/typed-where-ilike.extension.d.ts +7 -6
  42. package/dist/extensions/typed/typed-where/typed-where-is-any.extension.d.ts +7 -6
  43. package/dist/extensions/typed/typed-where/typed-where-is-distinct-from.extension.d.ts +7 -6
  44. package/dist/extensions/typed/typed-where/typed-where-is-in.extension.d.ts +7 -6
  45. package/dist/extensions/typed/typed-where/typed-where-json.extension.d.ts +7 -6
  46. package/dist/extensions/typed/typed-where/typed-where-lower.extension.d.ts +7 -6
  47. package/dist/extensions/typed/typed-where/typed-where-similar.extension.d.ts +5 -4
  48. package/dist/extensions/typed/typed-where/typed-where.extension.d.ts +4 -3
  49. package/dist/extensions/union.extension.d.ts +22 -0
  50. package/dist/extensions/union.extension.js +52 -0
  51. package/dist/extensions/where-exists.extension.d.ts +50 -0
  52. package/dist/extensions/where-exists.extension.js +64 -0
  53. package/dist/extensions/where-relation-exists.extension.d.ts +38 -0
  54. package/dist/extensions/where-relation-exists.extension.js +95 -0
  55. package/dist/index.d.ts +2 -0
  56. package/dist/index.js +1 -0
  57. package/dist/types/extensions/cursor-pagination.types.d.ts +5 -0
  58. package/dist/types/extensions/get-many-with-totals.types.d.ts +19 -0
  59. package/dist/types/extensions/index.d.ts +5 -0
  60. package/dist/types/extensions/index.js +5 -0
  61. package/dist/types/extensions/join-lateral.types.d.ts +6 -0
  62. package/dist/types/extensions/join-lateral.types.js +2 -0
  63. package/dist/types/extensions/period.types.d.ts +8 -0
  64. package/dist/types/extensions/query-returning.types.d.ts +11 -0
  65. package/dist/types/extensions/query-returning.types.js +2 -0
  66. package/dist/types/extensions/typed-group-by.types.d.ts +8 -0
  67. package/dist/types/extensions/typed-group-by.types.js +2 -0
  68. package/dist/types/extensions/typed-having.types.d.ts +2 -0
  69. package/dist/types/extensions/typed-having.types.js +2 -0
  70. package/dist/types/extensions/union.types.d.ts +12 -0
  71. package/dist/types/extensions/union.types.js +2 -0
  72. package/dist/types/extensions/where.types.d.ts +1 -0
  73. package/dist/types/modules/typed-root.types.d.ts +17 -0
  74. package/dist/types/modules/typed-root.types.js +2 -0
  75. package/dist/utils/calculated-column.utils.d.ts +21 -0
  76. package/dist/utils/calculated-column.utils.js +63 -0
  77. package/dist/utils/parameter.utils.d.ts +7 -0
  78. package/dist/utils/parameter.utils.js +21 -0
  79. package/dist/utils/period.utils.js +7 -2
  80. package/dist/utils/prototype.utils.d.ts +5 -1
  81. package/dist/utils/prototype.utils.js +4 -1
  82. package/dist/utils/proxy-key.utils.d.ts +3 -3
  83. package/dist/utils/proxy-key.utils.js +7 -7
  84. package/dist/utils/query-builder.utils.d.ts +8 -0
  85. package/dist/utils/query-builder.utils.js +30 -2
  86. package/dist/utils/table.utils.d.ts +112 -2
  87. package/dist/utils/table.utils.js +206 -11
  88. package/dist/utils/time-zone.utils.d.ts +8 -0
  89. package/dist/utils/time-zone.utils.js +102 -0
  90. package/dist/utils/where.utils.d.ts +2 -2
  91. package/dist/utils/where.utils.js +23 -2
  92. package/package.json +3 -3
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readInTimeZone = exports.assertTimeZone = void 0;
4
+ const DAY_MS = 24 * 60 * 60 * 1000;
5
+ const MAX_CACHED_FORMATTERS = 500;
6
+ const DATE_TIME = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,9}))?)?(Z|[+-]\d{2}(?::?\d{2})?)?)?$/i;
7
+ const OFFSET = /^([+-])(\d{2}):?(\d{2})?$/;
8
+ const formatters = new Map();
9
+ function getFormatter(timeZone) {
10
+ let formatter = formatters.get(timeZone);
11
+ if (formatter === undefined) {
12
+ try {
13
+ formatter = new Intl.DateTimeFormat('en-US', {
14
+ timeZone,
15
+ hour12: false,
16
+ era: 'short',
17
+ year: 'numeric',
18
+ month: 'numeric',
19
+ day: 'numeric',
20
+ hour: 'numeric',
21
+ minute: 'numeric',
22
+ second: 'numeric',
23
+ });
24
+ }
25
+ catch {
26
+ throw new TypeError(`Invalid timeZone "${timeZone}": not an IANA time zone`);
27
+ }
28
+ // the keys are caller input: Intl accepts any letter case of a zone name
29
+ if (formatters.size >= MAX_CACHED_FORMATTERS) {
30
+ formatters.clear();
31
+ }
32
+ formatters.set(timeZone, formatter);
33
+ }
34
+ return formatter;
35
+ }
36
+ /** The wall clock of an instant in a time zone, as milliseconds of the same wall clock in UTC. */
37
+ function getWallClock(instant, timeZone) {
38
+ const parts = {};
39
+ for (const { type, value } of getFormatter(timeZone).formatToParts(new Date(instant))) {
40
+ parts[type] = value;
41
+ }
42
+ const year = parts.era === 'BC' ? 1 - Number(parts.year) : Number(parts.year);
43
+ const wallClock = new Date(0);
44
+ // some ICU versions write midnight as 24 with hour12: false
45
+ wallClock.setUTCFullYear(year, Number(parts.month) - 1, Number(parts.day));
46
+ wallClock.setUTCHours(Number(parts.hour) % 24, Number(parts.minute), Number(parts.second), 0);
47
+ return wallClock.getTime() + (((instant % 1000) + 1000) % 1000);
48
+ }
49
+ /**
50
+ * The instant at which the wall clock of a time zone shows `wallClock` (milliseconds of that wall clock in
51
+ * UTC). Like PostgreSQL: a time skipped by a forward transition takes the offset before it, and a time that
52
+ * occurs twice is read with the offset after the transition, the later of the two instants.
53
+ */
54
+ function wallClockToInstant(wallClock, timeZone) {
55
+ const offsets = new Set([wallClock - DAY_MS, wallClock, wallClock + DAY_MS].map((instant) => getWallClock(instant, timeZone) - instant));
56
+ const candidates = [...offsets].map((offset) => wallClock - offset);
57
+ const matching = candidates.filter((instant) => getWallClock(instant, timeZone) === wallClock);
58
+ return Math.max(...(matching.length > 0 ? matching : candidates));
59
+ }
60
+ /** Throws a `TypeError` for a time zone the runtime doesn't know. */
61
+ function assertTimeZone(timeZone) {
62
+ getFormatter(timeZone);
63
+ }
64
+ exports.assertTimeZone = assertTimeZone;
65
+ /**
66
+ * Reads a date (`'2024-01-31'`, its midnight) or a date-time without an offset (`'2024-01-31 10:00'`) as
67
+ * the wall clock of `timeZone`, and returns that instant. A date-time with an offset (`Z`, `+02:00`) is an
68
+ * instant already: it is returned as that `Date`. Any other string throws a `TypeError` naming `name`.
69
+ */
70
+ function readInTimeZone(value, timeZone, name) {
71
+ const match = DATE_TIME.exec(value);
72
+ if (match === null) {
73
+ throw new TypeError(`Invalid ${name} "${value}": with timeZone, expected a Date, a date (YYYY-MM-DD) or a date-time ` +
74
+ '(YYYY-MM-DD HH:mm[:ss[.sss]]) with or without an offset');
75
+ }
76
+ const [, year, month, day, hour = '0', minute = '0', second = '0', fraction = '', offset] = match;
77
+ const wallClock = new Date(0);
78
+ wallClock.setUTCFullYear(Number(year), Number(month) - 1, Number(day));
79
+ // a Date keeps milliseconds: finer fractions are dropped
80
+ wallClock.setUTCHours(Number(hour), Number(minute), Number(second), Number(fraction.padEnd(3, '0').slice(0, 3)));
81
+ const roundTrip = wallClock.toISOString();
82
+ const expected = `${year}-${month}-${day}T${hour.padStart(2, '0')}:${minute.padStart(2, '0')}:${second.padStart(2, '0')}`;
83
+ if (isNaN(wallClock.getTime()) || roundTrip.slice(0, 19) !== expected) {
84
+ throw new TypeError(`Invalid ${name} "${value}": not a calendar date and time`);
85
+ }
86
+ if (offset === undefined) {
87
+ return new Date(wallClockToInstant(wallClock.getTime(), timeZone));
88
+ }
89
+ // an offset makes it an instant already; as text the databases would read it each their own way
90
+ return new Date(wallClock.getTime() - getOffsetMinutes(offset, value, name) * 60000);
91
+ }
92
+ exports.readInTimeZone = readInTimeZone;
93
+ function getOffsetMinutes(offset, value, name) {
94
+ if (offset.toUpperCase() === 'Z') {
95
+ return 0;
96
+ }
97
+ const [, sign, hours, minutes = '00'] = OFFSET.exec(offset);
98
+ if (Number(hours) > 23 || Number(minutes) > 59) {
99
+ throw new TypeError(`Invalid ${name} "${value}": not a UTC offset`);
100
+ }
101
+ return (sign === '-' ? -1 : 1) * (Number(hours) * 60 + Number(minutes));
102
+ }
@@ -1,5 +1,5 @@
1
1
  import type { ObjectLiteral, WhereExpressionBuilder } from 'typeorm';
2
- import type { WhereMethods } from '../types/extensions/where.types';
2
+ import type { HavingMethods, WhereMethods } from '../types/extensions/where.types';
3
3
  export declare function getValuePlaceholder<T>(value: T, paramName?: string): string;
4
4
  /**
5
5
  * Whether `params` is a `{ name: value }` params object (a plain object or a class instance such
@@ -12,7 +12,7 @@ export declare function isParamsObject(params: unknown): params is Record<string
12
12
  * so `::type` casts and colons inside literals don't count unless they name a `params` key.
13
13
  */
14
14
  export declare function hasParamPlaceholder(conditions: string, params?: unknown): boolean;
15
- export declare function attachWhere<QB extends WhereExpressionBuilder>(builder: QB, method: WhereMethods, field: string, conditions: string, params?: unknown, options?: {
15
+ export declare function attachWhere<QB extends WhereExpressionBuilder>(builder: QB, method: WhereMethods | HavingMethods, field: string, conditions: string, params?: unknown, options?: {
16
16
  addValue?: boolean;
17
17
  }): QB;
18
18
  /**
@@ -49,14 +49,35 @@ function hasParamPlaceholder(conditions, params) {
49
49
  return false;
50
50
  }
51
51
  exports.hasParamPlaceholder = hasParamPlaceholder;
52
+ /**
53
+ * A params object whose value goes after the condition (`'='`, `{ id: 1 }`) holds that one value. With other
54
+ * keys only the first was used and the rest dropped; with none the column was compared with NULL.
55
+ */
56
+ function assertSingleValueParams(field, params) {
57
+ if (!isParamsObject(params)) {
58
+ return;
59
+ }
60
+ const keys = Object.keys(params);
61
+ if (keys.length !== 1) {
62
+ throw new TypeError(`Condition on "${field}": a params object without a placeholder in the condition holds the one value to compare with, ` +
63
+ `got ${keys.length} keys (${keys.join(', ') || 'none'}). Pass the value itself, or name the keys in the condition: '= :id'`);
64
+ }
65
+ }
66
+ /** The builder's `where` / `having` method, bound to it: only a SelectQueryBuilder has `having`. */
67
+ function addCondition(builder, method) {
68
+ const add = builder[method];
69
+ return (condition, parameters) => add.call(builder, condition, parameters);
70
+ }
52
71
  function attachWhere(builder, method, field, conditions, params, options) {
53
72
  let { addValue } = options || {};
54
73
  if (method && typeof field === 'string' && typeof conditions === 'string') {
74
+ const fieldName = field;
55
75
  field = (0, query_builder_utils_1.toFieldSql)(builder, field);
56
76
  const hasParams = typeof params !== 'undefined';
57
77
  // `addValue` defaults to `true` unless `conditions` already references one of the `params` keys
58
78
  addValue = addValue !== null && addValue !== void 0 ? addValue : !hasParamPlaceholder(conditions, params);
59
79
  if (addValue && hasParams) {
80
+ assertSingleValueParams(fieldName, params);
60
81
  // pick first value from params object or pass as is (a Date or a Buffer is a value too)
61
82
  const firstValue = isParamsObject(params) ? Object.values(params)[0] : params;
62
83
  // 0, false, '' are valid values like any other
@@ -69,10 +90,10 @@ function attachWhere(builder, method, field, conditions, params, options) {
69
90
  : undefined;
70
91
  const queryParams = paramName && valueIsValid ? { [paramName]: firstValue } : undefined;
71
92
  const valuePlaceholder = getValuePlaceholder(firstValue, paramName);
72
- return builder[method](`${field} ${conditions} ${valuePlaceholder}`, queryParams);
93
+ return addCondition(builder, method)(`${field} ${conditions} ${valuePlaceholder}`, queryParams);
73
94
  }
74
95
  else {
75
- return builder[method](`${field} ${conditions}`, params);
96
+ return addCondition(builder, method)(`${field} ${conditions}`, params);
76
97
  }
77
98
  }
78
99
  return builder;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typeorm-extensions",
3
- "version": "2.1.0",
3
+ "version": "2.2.0-rc.1",
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"
17
+ "test:types": "tsc -p test/types && tsc -p test/types/tsconfig.loose.json"
18
18
  },
19
19
  "keywords": [
20
20
  "typeorm",
@@ -24,7 +24,7 @@
24
24
  "utils"
25
25
  ],
26
26
  "author": "koenigstag",
27
- "license": "ISC",
27
+ "license": "MIT",
28
28
  "peerDependencies": {
29
29
  "typeorm": "^0.3.12 || ^1.0.0"
30
30
  },