@mikro-orm/core 7.0.0-dev.3 → 7.0.0-dev.300

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 (214) hide show
  1. package/EntityManager.d.ts +114 -63
  2. package/EntityManager.js +385 -310
  3. package/MikroORM.d.ts +44 -35
  4. package/MikroORM.js +109 -143
  5. package/README.md +3 -2
  6. package/cache/FileCacheAdapter.d.ts +1 -1
  7. package/cache/FileCacheAdapter.js +17 -8
  8. package/cache/GeneratedCacheAdapter.d.ts +0 -1
  9. package/cache/GeneratedCacheAdapter.js +0 -2
  10. package/cache/index.d.ts +0 -1
  11. package/cache/index.js +0 -1
  12. package/connections/Connection.d.ts +16 -7
  13. package/connections/Connection.js +23 -14
  14. package/drivers/DatabaseDriver.d.ts +25 -16
  15. package/drivers/DatabaseDriver.js +119 -36
  16. package/drivers/IDatabaseDriver.d.ts +125 -23
  17. package/entity/BaseEntity.d.ts +63 -4
  18. package/entity/BaseEntity.js +0 -3
  19. package/entity/Collection.d.ts +102 -31
  20. package/entity/Collection.js +446 -108
  21. package/entity/EntityAssigner.d.ts +1 -1
  22. package/entity/EntityAssigner.js +26 -18
  23. package/entity/EntityFactory.d.ts +13 -1
  24. package/entity/EntityFactory.js +106 -60
  25. package/entity/EntityHelper.d.ts +2 -2
  26. package/entity/EntityHelper.js +65 -20
  27. package/entity/EntityLoader.d.ts +13 -11
  28. package/entity/EntityLoader.js +257 -107
  29. package/entity/EntityRepository.d.ts +28 -8
  30. package/entity/EntityRepository.js +8 -2
  31. package/entity/PolymorphicRef.d.ts +12 -0
  32. package/entity/PolymorphicRef.js +18 -0
  33. package/entity/Reference.d.ts +9 -12
  34. package/entity/Reference.js +34 -9
  35. package/entity/WrappedEntity.d.ts +3 -8
  36. package/entity/WrappedEntity.js +3 -8
  37. package/entity/defineEntity.d.ts +753 -0
  38. package/entity/defineEntity.js +537 -0
  39. package/entity/index.d.ts +4 -2
  40. package/entity/index.js +4 -2
  41. package/entity/utils.d.ts +13 -1
  42. package/entity/utils.js +49 -4
  43. package/entity/validators.d.ts +11 -0
  44. package/entity/validators.js +65 -0
  45. package/enums.d.ts +23 -8
  46. package/enums.js +15 -1
  47. package/errors.d.ts +25 -9
  48. package/errors.js +67 -21
  49. package/events/EventManager.d.ts +2 -1
  50. package/events/EventManager.js +19 -11
  51. package/events/EventSubscriber.d.ts +3 -1
  52. package/hydration/Hydrator.js +1 -2
  53. package/hydration/ObjectHydrator.d.ts +4 -4
  54. package/hydration/ObjectHydrator.js +89 -36
  55. package/index.d.ts +2 -2
  56. package/index.js +1 -2
  57. package/logging/DefaultLogger.d.ts +1 -1
  58. package/logging/DefaultLogger.js +1 -0
  59. package/logging/SimpleLogger.d.ts +1 -1
  60. package/logging/colors.d.ts +1 -1
  61. package/logging/colors.js +7 -6
  62. package/logging/index.d.ts +1 -0
  63. package/logging/index.js +1 -0
  64. package/logging/inspect.d.ts +2 -0
  65. package/logging/inspect.js +11 -0
  66. package/metadata/EntitySchema.d.ts +53 -27
  67. package/metadata/EntitySchema.js +125 -52
  68. package/metadata/MetadataDiscovery.d.ts +64 -10
  69. package/metadata/MetadataDiscovery.js +823 -344
  70. package/metadata/MetadataProvider.d.ts +11 -2
  71. package/metadata/MetadataProvider.js +66 -2
  72. package/metadata/MetadataStorage.d.ts +13 -11
  73. package/metadata/MetadataStorage.js +71 -38
  74. package/metadata/MetadataValidator.d.ts +32 -9
  75. package/metadata/MetadataValidator.js +198 -42
  76. package/metadata/discover-entities.d.ts +5 -0
  77. package/metadata/discover-entities.js +40 -0
  78. package/metadata/index.d.ts +1 -1
  79. package/metadata/index.js +1 -1
  80. package/metadata/types.d.ts +577 -0
  81. package/metadata/types.js +1 -0
  82. package/naming-strategy/AbstractNamingStrategy.d.ts +16 -4
  83. package/naming-strategy/AbstractNamingStrategy.js +20 -2
  84. package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
  85. package/naming-strategy/EntityCaseNamingStrategy.js +6 -5
  86. package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
  87. package/naming-strategy/MongoNamingStrategy.js +6 -6
  88. package/naming-strategy/NamingStrategy.d.ts +28 -4
  89. package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
  90. package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
  91. package/not-supported.d.ts +2 -0
  92. package/not-supported.js +4 -0
  93. package/package.json +22 -11
  94. package/platforms/ExceptionConverter.js +1 -1
  95. package/platforms/Platform.d.ts +14 -16
  96. package/platforms/Platform.js +24 -44
  97. package/serialization/EntitySerializer.d.ts +8 -3
  98. package/serialization/EntitySerializer.js +47 -27
  99. package/serialization/EntityTransformer.js +33 -21
  100. package/serialization/SerializationContext.d.ts +6 -6
  101. package/serialization/SerializationContext.js +16 -13
  102. package/types/ArrayType.d.ts +1 -1
  103. package/types/ArrayType.js +2 -3
  104. package/types/BigIntType.d.ts +9 -6
  105. package/types/BigIntType.js +4 -1
  106. package/types/BlobType.d.ts +0 -1
  107. package/types/BlobType.js +0 -3
  108. package/types/BooleanType.d.ts +2 -1
  109. package/types/BooleanType.js +3 -0
  110. package/types/DecimalType.d.ts +6 -4
  111. package/types/DecimalType.js +3 -3
  112. package/types/DoubleType.js +2 -2
  113. package/types/EnumArrayType.js +1 -2
  114. package/types/JsonType.d.ts +1 -1
  115. package/types/JsonType.js +7 -2
  116. package/types/TinyIntType.js +1 -1
  117. package/types/Type.d.ts +2 -4
  118. package/types/Type.js +3 -3
  119. package/types/Uint8ArrayType.d.ts +0 -1
  120. package/types/Uint8ArrayType.js +1 -4
  121. package/types/index.d.ts +1 -1
  122. package/typings.d.ts +469 -175
  123. package/typings.js +120 -45
  124. package/unit-of-work/ChangeSet.d.ts +4 -6
  125. package/unit-of-work/ChangeSet.js +4 -5
  126. package/unit-of-work/ChangeSetComputer.d.ts +3 -8
  127. package/unit-of-work/ChangeSetComputer.js +44 -21
  128. package/unit-of-work/ChangeSetPersister.d.ts +15 -12
  129. package/unit-of-work/ChangeSetPersister.js +113 -45
  130. package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
  131. package/unit-of-work/CommitOrderCalculator.js +13 -13
  132. package/unit-of-work/IdentityMap.d.ts +12 -0
  133. package/unit-of-work/IdentityMap.js +39 -1
  134. package/unit-of-work/UnitOfWork.d.ts +28 -3
  135. package/unit-of-work/UnitOfWork.js +315 -110
  136. package/utils/AbstractMigrator.d.ts +101 -0
  137. package/utils/AbstractMigrator.js +305 -0
  138. package/utils/AbstractSchemaGenerator.d.ts +5 -5
  139. package/utils/AbstractSchemaGenerator.js +32 -18
  140. package/utils/AsyncContext.d.ts +6 -0
  141. package/utils/AsyncContext.js +42 -0
  142. package/utils/Configuration.d.ts +801 -207
  143. package/utils/Configuration.js +150 -191
  144. package/utils/ConfigurationLoader.d.ts +1 -54
  145. package/utils/ConfigurationLoader.js +1 -352
  146. package/utils/Cursor.d.ts +3 -6
  147. package/utils/Cursor.js +27 -11
  148. package/utils/DataloaderUtils.d.ts +15 -5
  149. package/utils/DataloaderUtils.js +65 -17
  150. package/utils/EntityComparator.d.ts +21 -10
  151. package/utils/EntityComparator.js +243 -106
  152. package/utils/QueryHelper.d.ts +24 -6
  153. package/utils/QueryHelper.js +122 -26
  154. package/utils/RawQueryFragment.d.ts +60 -32
  155. package/utils/RawQueryFragment.js +69 -66
  156. package/utils/RequestContext.js +2 -2
  157. package/utils/TransactionContext.js +2 -2
  158. package/utils/TransactionManager.d.ts +65 -0
  159. package/utils/TransactionManager.js +223 -0
  160. package/utils/Utils.d.ts +15 -122
  161. package/utils/Utils.js +108 -376
  162. package/utils/clone.js +8 -23
  163. package/utils/env-vars.d.ts +7 -0
  164. package/utils/env-vars.js +97 -0
  165. package/utils/fs-utils.d.ts +34 -0
  166. package/utils/fs-utils.js +196 -0
  167. package/utils/index.d.ts +2 -3
  168. package/utils/index.js +2 -3
  169. package/utils/upsert-utils.d.ts +9 -4
  170. package/utils/upsert-utils.js +55 -4
  171. package/decorators/Check.d.ts +0 -3
  172. package/decorators/Check.js +0 -13
  173. package/decorators/CreateRequestContext.d.ts +0 -3
  174. package/decorators/CreateRequestContext.js +0 -32
  175. package/decorators/Embeddable.d.ts +0 -8
  176. package/decorators/Embeddable.js +0 -11
  177. package/decorators/Embedded.d.ts +0 -18
  178. package/decorators/Embedded.js +0 -18
  179. package/decorators/Entity.d.ts +0 -18
  180. package/decorators/Entity.js +0 -13
  181. package/decorators/Enum.d.ts +0 -9
  182. package/decorators/Enum.js +0 -16
  183. package/decorators/Filter.d.ts +0 -2
  184. package/decorators/Filter.js +0 -8
  185. package/decorators/Formula.d.ts +0 -5
  186. package/decorators/Formula.js +0 -15
  187. package/decorators/Indexed.d.ts +0 -17
  188. package/decorators/Indexed.js +0 -20
  189. package/decorators/ManyToMany.d.ts +0 -40
  190. package/decorators/ManyToMany.js +0 -14
  191. package/decorators/ManyToOne.d.ts +0 -30
  192. package/decorators/ManyToOne.js +0 -14
  193. package/decorators/OneToMany.d.ts +0 -28
  194. package/decorators/OneToMany.js +0 -17
  195. package/decorators/OneToOne.d.ts +0 -24
  196. package/decorators/OneToOne.js +0 -7
  197. package/decorators/PrimaryKey.d.ts +0 -9
  198. package/decorators/PrimaryKey.js +0 -20
  199. package/decorators/Property.d.ts +0 -250
  200. package/decorators/Property.js +0 -32
  201. package/decorators/Transactional.d.ts +0 -13
  202. package/decorators/Transactional.js +0 -28
  203. package/decorators/hooks.d.ts +0 -16
  204. package/decorators/hooks.js +0 -47
  205. package/decorators/index.d.ts +0 -17
  206. package/decorators/index.js +0 -17
  207. package/entity/ArrayCollection.d.ts +0 -116
  208. package/entity/ArrayCollection.js +0 -395
  209. package/entity/EntityValidator.d.ts +0 -19
  210. package/entity/EntityValidator.js +0 -150
  211. package/metadata/ReflectMetadataProvider.d.ts +0 -8
  212. package/metadata/ReflectMetadataProvider.js +0 -44
  213. package/utils/resolveContextProvider.d.ts +0 -10
  214. package/utils/resolveContextProvider.js +0 -28
@@ -1,11 +1,18 @@
1
1
  import { Reference } from '../entity/Reference.js';
2
2
  import { Utils } from './Utils.js';
3
- import { GroupOperator, ReferenceKind } from '../enums.js';
3
+ import { ARRAY_OPERATORS, GroupOperator, JSON_KEY_OPERATORS, ReferenceKind } from '../enums.js';
4
4
  import { JsonType } from '../types/JsonType.js';
5
5
  import { helper } from '../entity/wrap.js';
6
- import { RawQueryFragment, isRaw } from './RawQueryFragment.js';
6
+ import { isRaw, Raw } from './RawQueryFragment.js';
7
+ /** @internal */
7
8
  export class QueryHelper {
8
9
  static SUPPORTED_OPERATORS = ['>', '<', '<=', '>=', '!', '!='];
10
+ /**
11
+ * Finds the discriminator value (key) for a given entity class in a discriminator map.
12
+ */
13
+ static findDiscriminatorValue(discriminatorMap, targetClass) {
14
+ return Object.entries(discriminatorMap).find(([, cls]) => cls === targetClass)?.[0];
15
+ }
9
16
  static processParams(params) {
10
17
  if (Reference.isReference(params)) {
11
18
  params = params.unwrap();
@@ -28,16 +35,60 @@ export class QueryHelper {
28
35
  return params;
29
36
  }
30
37
  static processObjectParams(params = {}) {
31
- Utils.keys(params).forEach(k => {
38
+ Utils.getObjectQueryKeys(params).forEach(k => {
32
39
  params[k] = QueryHelper.processParams(params[k]);
33
40
  });
34
41
  return params;
35
42
  }
43
+ /**
44
+ * converts `{ account: { $or: [ [Object], [Object] ] } }`
45
+ * to `{ $or: [ { account: [Object] }, { account: [Object] } ] }`
46
+ */
47
+ static liftGroupOperators(where, meta, metadata, key) {
48
+ if (!Utils.isPlainObject(where)) {
49
+ return undefined;
50
+ }
51
+ const keys = Object.keys(where);
52
+ const groupOperator = keys.find(k => {
53
+ return k in GroupOperator && Array.isArray(where[k]) && where[k].every(cond => {
54
+ return Utils.isPlainObject(cond) && Object.keys(cond).every(k2 => {
55
+ if (Utils.isOperator(k2, false)) {
56
+ if (k2 === '$not') {
57
+ return Object.keys(cond[k2]).every(k3 => meta.primaryKeys.includes(k3));
58
+ }
59
+ return true;
60
+ }
61
+ return meta.primaryKeys.includes(k2);
62
+ });
63
+ });
64
+ });
65
+ if (groupOperator) {
66
+ return groupOperator;
67
+ }
68
+ for (const k of keys) {
69
+ const value = where[k];
70
+ const prop = meta.properties[k];
71
+ // Polymorphic relations use multiple columns (discriminator + FK), so they cannot
72
+ // participate in the standard single-column FK expansion. Query by discriminator
73
+ // column directly instead, e.g. { likeableType: 'post', likeableId: 1 }.
74
+ if (!prop || ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) || prop.polymorphic) {
75
+ continue;
76
+ }
77
+ const op = this.liftGroupOperators(value, prop.targetMeta, metadata, k);
78
+ if (op) {
79
+ delete where[k];
80
+ where[op] = value[op].map((v) => {
81
+ return { [k]: v };
82
+ });
83
+ }
84
+ }
85
+ return undefined;
86
+ }
36
87
  static inlinePrimaryKeyObjects(where, meta, metadata, key) {
37
88
  if (Array.isArray(where)) {
38
89
  where.forEach((item, i) => {
39
90
  if (this.inlinePrimaryKeyObjects(item, meta, metadata, key)) {
40
- where[i] = Utils.getPrimaryKeyValues(item, meta.primaryKeys, false);
91
+ where[i] = Utils.getPrimaryKeyValues(item, meta, false);
41
92
  }
42
93
  });
43
94
  }
@@ -45,9 +96,9 @@ export class QueryHelper {
45
96
  return false;
46
97
  }
47
98
  if (meta.primaryKeys.every(pk => pk in where) && Utils.getObjectKeysSize(where) === meta.primaryKeys.length) {
48
- return !!key && !GroupOperator[key] && Object.keys(where).every(k => !Utils.isPlainObject(where[k]) || Object.keys(where[k]).every(v => {
99
+ return !!key && !GroupOperator[key] && key !== '$not' && Object.keys(where).every(k => !Utils.isPlainObject(where[k]) || Object.keys(where[k]).every(v => {
49
100
  if (Utils.isOperator(v, false)) {
50
- return false;
101
+ return true;
51
102
  }
52
103
  if (meta.properties[k].primary && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(meta.properties[k].kind)) {
53
104
  return this.inlinePrimaryKeyObjects(where[k], meta.properties[k].targetMeta, metadata, v);
@@ -56,9 +107,9 @@ export class QueryHelper {
56
107
  }));
57
108
  }
58
109
  Object.keys(where).forEach(k => {
59
- const meta2 = metadata.find(meta.properties[k]?.type) || meta;
110
+ const meta2 = metadata.find(meta.properties[k]?.targetMeta?.class) || meta;
60
111
  if (this.inlinePrimaryKeyObjects(where[k], meta2, metadata, k)) {
61
- where[k] = Utils.getPrimaryKeyValues(where[k], meta2.primaryKeys, true);
112
+ where[k] = Utils.getPrimaryKeyValues(where[k], meta2, true);
62
113
  }
63
114
  });
64
115
  return false;
@@ -69,13 +120,14 @@ export class QueryHelper {
69
120
  const meta = metadata.find(entityName);
70
121
  // inline PK-only objects in M:N queries, so we don't join the target entity when not needed
71
122
  if (meta && root) {
123
+ QueryHelper.liftGroupOperators(where, meta, metadata);
72
124
  QueryHelper.inlinePrimaryKeyObjects(where, meta, metadata);
73
125
  }
74
126
  if (platform.getConfig().get('ignoreUndefinedInQuery') && where && typeof where === 'object') {
75
127
  Utils.dropUndefinedProperties(where);
76
128
  }
77
129
  where = QueryHelper.processParams(where) ?? {};
78
- /* v8 ignore next 3 */
130
+ /* v8 ignore next */
79
131
  if (!root && Utils.isPrimaryKey(where)) {
80
132
  return where;
81
133
  }
@@ -83,7 +135,7 @@ export class QueryHelper {
83
135
  where = { [Utils.getPrimaryKeyHash(meta.primaryKeys)]: where };
84
136
  }
85
137
  if (Array.isArray(where) && root) {
86
- const rootPrimaryKey = meta ? Utils.getPrimaryKeyHash(meta.primaryKeys) : entityName;
138
+ const rootPrimaryKey = meta ? Utils.getPrimaryKeyHash(meta.primaryKeys) : Utils.className(entityName);
87
139
  let cond = { [rootPrimaryKey]: { $in: where } };
88
140
  // @ts-ignore
89
141
  // detect tuple comparison, use `$or` in case the number of constituents don't match
@@ -95,12 +147,10 @@ export class QueryHelper {
95
147
  if (!Utils.isPlainObject(where)) {
96
148
  return where;
97
149
  }
98
- return Object.keys(where).reduce((o, key) => {
150
+ return Utils.getObjectQueryKeys(where).reduce((o, key) => {
99
151
  let value = where[key];
100
- const prop = this.findProperty(key, options);
101
- const keys = prop?.joinColumns?.length ?? 0;
102
- const composite = keys > 1;
103
- if (Array.isArray(value) && value.length === 0 && RawQueryFragment.isKnownFragment(key)) {
152
+ const customExpression = Raw.isKnownFragmentSymbol(key);
153
+ if (Array.isArray(value) && value.length === 0 && customExpression) {
104
154
  o[key] = value;
105
155
  return o;
106
156
  }
@@ -114,14 +164,17 @@ export class QueryHelper {
114
164
  o[rootPrimaryKey] = { [key]: QueryHelper.processWhere({ ...options, where: value, root: false }) };
115
165
  return o;
116
166
  }
167
+ const prop = customExpression ? null : this.findProperty(key, options);
168
+ const keys = prop?.joinColumns?.length ?? 0;
169
+ const composite = keys > 1;
117
170
  if (prop?.customType && convertCustomTypes && !isRaw(value)) {
118
171
  value = QueryHelper.processCustomType(prop, value, platform, undefined, true);
119
172
  }
120
173
  const isJsonProperty = prop?.customType instanceof JsonType && Utils.isPlainObject(value) && !isRaw(value) && Object.keys(value)[0] !== '$eq';
121
- if (isJsonProperty) {
174
+ if (isJsonProperty && prop?.kind !== ReferenceKind.EMBEDDED) {
122
175
  return this.processJsonCondition(o, value, [prop.fieldNames[0]], platform, aliased);
123
176
  }
124
- if (Array.isArray(value) && !Utils.isOperator(key) && !QueryHelper.isSupportedOperator(key) && !key.includes('?') && options.type !== 'orderBy') {
177
+ if (Array.isArray(value) && !Utils.isOperator(key) && !QueryHelper.isSupportedOperator(key) && !(customExpression && Raw.getKnownFragment(key).params.length > 0) && options.type !== 'orderBy') {
125
178
  // comparing single composite key - use $eq instead of $in
126
179
  const op = composite && !value.every(v => Array.isArray(v)) ? '$eq' : '$in';
127
180
  o[key] = { [op]: value };
@@ -131,7 +184,7 @@ export class QueryHelper {
131
184
  o[key] = QueryHelper.processWhere({
132
185
  ...options,
133
186
  where: value,
134
- entityName: prop?.type ?? entityName,
187
+ entityName: prop?.targetMeta?.class ?? entityName,
135
188
  root: false,
136
189
  });
137
190
  }
@@ -141,7 +194,7 @@ export class QueryHelper {
141
194
  return o;
142
195
  }, {});
143
196
  }
144
- static getActiveFilters(entityName, options, filters) {
197
+ static getActiveFilters(meta, options, filters) {
145
198
  if (options === false) {
146
199
  return [];
147
200
  }
@@ -153,14 +206,32 @@ export class QueryHelper {
153
206
  Object.keys(options).forEach(filter => opts[filter] = options[filter]);
154
207
  }
155
208
  return Object.keys(filters)
156
- .filter(f => QueryHelper.isFilterActive(entityName, f, filters[f], opts))
209
+ .filter(f => QueryHelper.isFilterActive(meta, f, filters[f], opts))
157
210
  .map(f => {
158
211
  filters[f].name = f;
159
212
  return filters[f];
160
213
  });
161
214
  }
162
- static isFilterActive(entityName, filterName, filter, options) {
163
- if (filter.entity && !filter.entity.includes(entityName)) {
215
+ static mergePropertyFilters(propFilters, options) {
216
+ if (!options || !propFilters || options === true || propFilters === true) {
217
+ return options ?? propFilters;
218
+ }
219
+ if (Array.isArray(propFilters)) {
220
+ propFilters = propFilters.reduce((o, item) => {
221
+ o[item] = true;
222
+ return o;
223
+ }, {});
224
+ }
225
+ if (Array.isArray(options)) {
226
+ options = options.reduce((o, item) => {
227
+ o[item] = true;
228
+ return o;
229
+ }, {});
230
+ }
231
+ return Utils.mergeConfig({}, propFilters, options);
232
+ }
233
+ static isFilterActive(meta, filterName, filter, options) {
234
+ if (filter.entity && !filter.entity.includes(meta.className)) {
164
235
  return false;
165
236
  }
166
237
  if (options[filterName] === false) {
@@ -170,8 +241,8 @@ export class QueryHelper {
170
241
  }
171
242
  static processCustomType(prop, cond, platform, key, fromQuery) {
172
243
  if (Utils.isPlainObject(cond)) {
173
- return Utils.keys(cond).reduce((o, k) => {
174
- if (Utils.isOperator(k, true) || prop.referencedPKs?.includes(k)) {
244
+ return Utils.getObjectQueryKeys(cond).reduce((o, k) => {
245
+ if (!Raw.isKnownFragmentSymbol(k) && (Utils.isOperator(k, true) || prop.referencedPKs?.includes(k))) {
175
246
  o[k] = QueryHelper.processCustomType(prop, cond[k], platform, k, fromQuery);
176
247
  }
177
248
  else {
@@ -180,12 +251,12 @@ export class QueryHelper {
180
251
  return o;
181
252
  }, {});
182
253
  }
183
- if (key && Utils.isJsonKeyOperator(key)) {
254
+ if (key && JSON_KEY_OPERATORS.includes(key)) {
184
255
  return Array.isArray(cond)
185
256
  ? platform.marshallArray(cond)
186
257
  : cond;
187
258
  }
188
- if (Array.isArray(cond) && !(key && Utils.isArrayOperator(key))) {
259
+ if (Array.isArray(cond) && !(key && ARRAY_OPERATORS.includes(key))) {
189
260
  return cond.map(v => QueryHelper.processCustomType(prop, v, platform, key, fromQuery));
190
261
  }
191
262
  if (isRaw(cond)) {
@@ -229,4 +300,29 @@ export class QueryHelper {
229
300
  const meta = entityName ? options.metadata.find(entityName) : undefined;
230
301
  return meta?.properties[propName];
231
302
  }
303
+ /**
304
+ * Merges multiple orderBy sources with key-level deduplication (first-seen key wins).
305
+ * RawQueryFragment symbol keys are never deduped (each is unique).
306
+ */
307
+ static mergeOrderBy(...sources) {
308
+ const result = [];
309
+ const seenKeys = new Set();
310
+ for (const source of sources) {
311
+ if (source == null) {
312
+ continue;
313
+ }
314
+ for (const item of Utils.asArray(source)) {
315
+ for (const key of Utils.getObjectQueryKeys(item)) {
316
+ if (typeof key === 'symbol') {
317
+ result.push({ [key]: item[key] });
318
+ }
319
+ else if (!seenKeys.has(key)) {
320
+ seenKeys.add(key);
321
+ result.push({ [key]: item[key] });
322
+ }
323
+ }
324
+ }
325
+ }
326
+ return result;
327
+ }
232
328
  }
@@ -1,34 +1,25 @@
1
- import { inspect } from 'node:util';
2
1
  import type { AnyString, Dictionary, EntityKey } from '../typings.js';
3
- export declare class RawQueryFragment {
2
+ declare const rawFragmentSymbolBrand: unique symbol;
3
+ export type RawQueryFragmentSymbol = symbol & {
4
+ readonly [rawFragmentSymbolBrand]: true;
5
+ };
6
+ export declare class RawQueryFragment<Alias extends string = string> {
4
7
  #private;
5
8
  readonly sql: string;
6
9
  readonly params: unknown[];
7
- static cloneRegistry?: Set<string>;
10
+ /** @internal Type-level only - used to track the alias for type inference */
11
+ private readonly __alias?;
8
12
  constructor(sql: string, params?: unknown[]);
9
- as(alias: string): RawQueryFragment;
10
- valueOf(): string;
13
+ get key(): RawQueryFragmentSymbol;
14
+ as<A extends string>(alias: A): RawQueryFragment<A>;
15
+ [Symbol.toPrimitive](hint: 'string'): RawQueryFragmentSymbol;
16
+ get [Symbol.toStringTag](): string;
11
17
  toJSON(): string;
12
- toString(): string;
13
- /** @internal */
14
- assign(): void;
15
- clone(): RawQueryFragment;
16
- static run<T>(cb: (...args: any[]) => Promise<T>): Promise<T>;
17
- /**
18
- * @internal allows testing we don't leak memory, as the raw fragments cache needs to be cleared automatically
19
- */
20
- static checkCacheSize(): number;
21
- static isKnownFragment(key: string | RawQueryFragment): boolean;
22
- static getKnownFragment(key: string | RawQueryFragment, cleanup?: boolean): RawQueryFragment | undefined;
23
- static remove(key: string): void;
24
- /** @ignore */
25
- [inspect.custom](): {
26
- sql: string;
27
- params: unknown[];
28
- } | {
29
- sql: string;
30
- params?: undefined;
31
- };
18
+ clone(): this;
19
+ static isKnownFragmentSymbol(key: unknown): key is RawQueryFragmentSymbol;
20
+ static hasObjectFragments(object: unknown): boolean;
21
+ static isKnownFragment(key: unknown): key is RawQueryFragment | symbol;
22
+ static getKnownFragment(key: unknown): RawQueryFragment<any> | undefined;
32
23
  }
33
24
  export { RawQueryFragment as Raw };
34
25
  export declare function isRaw(value: unknown): value is RawQueryFragment;
@@ -72,8 +63,26 @@ export declare const ALIAS_REPLACEMENT_RE = "\\[::alias::\\]";
72
63
  * ```ts
73
64
  * @Filter({ name: 'long', cond: () => ({ [raw('length(perex)')]: { $gt: 10000 } }) })
74
65
  * ```
66
+ *
67
+ * The `raw` helper can be used within indexes and uniques to write database-agnostic SQL expressions. In that case, you can use `'??'` to tag your database identifiers (table name, column names, index name, ...) inside your expression, and pass those identifiers as a second parameter to the `raw` helper. Internally, those will automatically be quoted according to the database in use:
68
+ *
69
+ * ```ts
70
+ * // On postgres, will produce: create index "index custom_idx_on_name" on "library.author" ("country")
71
+ * // On mysql, will produce: create index `index custom_idx_on_name` on `library.author` (`country`)
72
+ * @Index({ name: 'custom_idx_on_name', expression: (table, columns) => raw(`create index ?? on ?? (??)`, ['custom_idx_on_name', table, columns.name]) })
73
+ * @Entity({ schema: 'library' })
74
+ * export class Author { ... }
75
+ * ```
76
+ *
77
+ * You can also use the `quote` tag function to write database-agnostic SQL expressions. The end-result is the same as using the `raw` function regarding database identifiers quoting, only to have a more elegant expression syntax:
78
+ *
79
+ * ```ts
80
+ * @Index({ name: 'custom_idx_on_name', expression: (table, columns) => quote`create index ${'custom_idx_on_name'} on ${table} (${columns.name})` })
81
+ * @Entity({ schema: 'library' })
82
+ * export class Author { ... }
83
+ * ```
75
84
  */
76
- export declare function raw<T extends object = any, R = any>(sql: EntityKey<T> | EntityKey<T>[] | AnyString | ((alias: string) => string) | RawQueryFragment, params?: readonly unknown[] | Dictionary<unknown>): R;
85
+ export declare function raw<R = RawQueryFragment & symbol, T extends object = any>(sql: EntityKey<T> | EntityKey<T>[] | AnyString | ((alias: string) => string) | RawQueryFragment, params?: readonly unknown[] | Dictionary<unknown>): R;
77
86
  /**
78
87
  * Alternative to the `raw()` helper allowing to use it as a tagged template function for the simple cases.
79
88
  *
@@ -86,13 +95,32 @@ export declare function raw<T extends object = any, R = any>(sql: EntityKey<T> |
86
95
  *
87
96
  * // value can be empty array
88
97
  * await em.find(User, { [sql`(select 1 = 1)`]: [] });
98
+ *
99
+ * // with type parameter for assignment without casting
100
+ * entity.date = sql<Date>`now()`;
89
101
  * ```
90
102
  */
91
- export declare function sql(sql: readonly string[], ...values: unknown[]): any;
103
+ export declare function sql<R = RawQueryFragment & symbol>(sql: readonly string[], ...values: unknown[]): R;
92
104
  export declare namespace sql {
93
- var ref: <T extends object>(...keys: string[]) => RawQueryFragment;
94
- var now: (length?: number) => string;
95
- var lower: <T extends object>(key: string | ((alias: string) => string)) => string;
96
- var upper: <T extends object>(key: string | ((alias: string) => string)) => string;
105
+ var ref: <T extends object = any>(...keys: string[]) => RawQueryFragment<string> & symbol;
106
+ var now: (length?: number) => RawQueryFragment<string> & symbol;
107
+ var lower: <R = RawQueryFragment<string> & symbol, T extends object = any>(key: string | ((alias: string) => string)) => R;
108
+ var upper: <R = RawQueryFragment<string> & symbol, T extends object = any>(key: string | ((alias: string) => string)) => R;
97
109
  }
98
- export declare function createSqlFunction<T extends object, R = string>(func: string, key: string | ((alias: string) => string)): R;
110
+ export declare function createSqlFunction<R = RawQueryFragment & symbol, T extends object = any>(func: string, key: string | ((alias: string) => string)): R;
111
+ /**
112
+ * Tag function providing quoting of db identifiers (table name, columns names, index names, ...).
113
+ *
114
+ * Within the template literal on which the tag function is applied, all placeholders are considered to be database identifiers, and will thus be quoted as so according to the database in use.
115
+ *
116
+ * ```ts
117
+ * // On postgres, will produce: create index "index custom_idx_on_name" on "library.author" ("name")
118
+ * // On mysql, will produce: create index `index custom_idx_on_name` on `library.author` (`name`)
119
+ * @Index({ name: 'custom_idx_on_name', expression: (table, columns, indexName) => quote`create index ${indexName} on ${table} (${columns.name})` })
120
+ * @Entity({ schema: 'library' })
121
+ * export class Author { ... }
122
+ * ```
123
+ */
124
+ export declare function quote(expParts: readonly string[], ...values: (string | {
125
+ toString(): string;
126
+ })[]): RawQueryFragment<string> & symbol;
@@ -1,94 +1,64 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks';
2
- import { inspect } from 'node:util';
3
1
  import { Utils } from './Utils.js';
4
2
  export class RawQueryFragment {
5
3
  sql;
6
4
  params;
7
- static #rawQueryCache = new Map();
8
- static #storage = new AsyncLocalStorage();
9
- static #index = 0n;
10
- static cloneRegistry;
11
- #assigned = false;
12
- #used = 0;
5
+ static #rawQueryReferences = new WeakMap();
13
6
  #key;
14
7
  constructor(sql, params = []) {
15
8
  this.sql = sql;
16
9
  this.params = params;
17
- this.#key = `[raw]: ${this.sql} (#${RawQueryFragment.#index++})`;
10
+ }
11
+ get key() {
12
+ if (!this.#key) {
13
+ this.#key = Symbol(this.toJSON());
14
+ RawQueryFragment.#rawQueryReferences.set(this.#key, this);
15
+ }
16
+ return this.#key;
18
17
  }
19
18
  as(alias) {
20
- return new RawQueryFragment(`${this.sql} as ${alias}`, this.params);
19
+ return new RawQueryFragment(`${this.sql} as ??`, [...this.params, alias]);
21
20
  }
22
- valueOf() {
21
+ [Symbol.toPrimitive](hint) {
22
+ // if a fragment is converted to string (used as an object key), return a unique symbol
23
+ // and save a weak reference to map so we can retrieve it when compiling the query
24
+ if (hint === 'string') {
25
+ return this.key;
26
+ }
23
27
  throw new Error(`Trying to modify raw SQL fragment: '${this.sql}'`);
24
28
  }
29
+ get [Symbol.toStringTag]() {
30
+ return this.toJSON();
31
+ }
25
32
  toJSON() {
26
- return this.#key;
33
+ return `raw('${this.sql}')`;
27
34
  }
28
- toString() {
29
- RawQueryFragment.#rawQueryCache.set(this.#key, this);
30
- this.#used++;
31
- return this.#key;
35
+ clone() {
36
+ return this;
32
37
  }
33
- /** @internal */
34
- assign() {
35
- if (this.#assigned) {
36
- throw new Error(`Cannot reassign already used RawQueryFragment: '${this.sql}'`);
37
- }
38
- this.#assigned = true;
38
+ static isKnownFragmentSymbol(key) {
39
+ return typeof key === 'symbol' && this.#rawQueryReferences.has(key);
39
40
  }
40
- clone() {
41
- RawQueryFragment.cloneRegistry?.add(this.#key);
42
- return new RawQueryFragment(this.sql, this.params);
43
- }
44
- static async run(cb) {
45
- const removeStack = new Set();
46
- const res = await this.#storage.run(removeStack, cb);
47
- removeStack.forEach(key => RawQueryFragment.remove(key));
48
- removeStack.clear();
49
- return res;
50
- }
51
- /**
52
- * @internal allows testing we don't leak memory, as the raw fragments cache needs to be cleared automatically
53
- */
54
- static checkCacheSize() {
55
- return this.#rawQueryCache.size;
41
+ static hasObjectFragments(object) {
42
+ return Utils.isPlainObject(object) && Object.getOwnPropertySymbols(object).some(symbol => this.isKnownFragmentSymbol(symbol));
56
43
  }
57
44
  static isKnownFragment(key) {
58
45
  if (key instanceof RawQueryFragment) {
59
46
  return true;
60
47
  }
61
- return this.#rawQueryCache.has(key);
48
+ return this.isKnownFragmentSymbol(key);
62
49
  }
63
- static getKnownFragment(key, cleanup = true) {
50
+ static getKnownFragment(key) {
64
51
  if (key instanceof RawQueryFragment) {
65
52
  return key;
66
53
  }
67
- const raw = this.#rawQueryCache.get(key);
68
- if (raw && cleanup) {
69
- this.remove(key);
70
- }
71
- return raw;
72
- }
73
- static remove(key) {
74
- const raw = this.#rawQueryCache.get(key);
75
- if (!raw) {
54
+ if (typeof key !== 'symbol') {
76
55
  return;
77
56
  }
78
- raw.#used--;
79
- if (raw.#used <= 0) {
80
- const removeStack = this.#storage.getStore();
81
- if (removeStack) {
82
- removeStack.add(key);
83
- }
84
- else {
85
- this.#rawQueryCache.delete(key);
86
- }
87
- }
57
+ return this.#rawQueryReferences.get(key);
88
58
  }
89
- /* v8 ignore next 8 */
90
59
  /** @ignore */
91
- [inspect.custom]() {
60
+ /* v8 ignore next */
61
+ [Symbol.for('nodejs.util.inspect.custom')]() {
92
62
  if (this.params) {
93
63
  return { sql: this.sql, params: this.params };
94
64
  }
@@ -142,6 +112,24 @@ export const ALIAS_REPLACEMENT_RE = '\\[::alias::\\]';
142
112
  * ```ts
143
113
  * @Filter({ name: 'long', cond: () => ({ [raw('length(perex)')]: { $gt: 10000 } }) })
144
114
  * ```
115
+ *
116
+ * The `raw` helper can be used within indexes and uniques to write database-agnostic SQL expressions. In that case, you can use `'??'` to tag your database identifiers (table name, column names, index name, ...) inside your expression, and pass those identifiers as a second parameter to the `raw` helper. Internally, those will automatically be quoted according to the database in use:
117
+ *
118
+ * ```ts
119
+ * // On postgres, will produce: create index "index custom_idx_on_name" on "library.author" ("country")
120
+ * // On mysql, will produce: create index `index custom_idx_on_name` on `library.author` (`country`)
121
+ * @Index({ name: 'custom_idx_on_name', expression: (table, columns) => raw(`create index ?? on ?? (??)`, ['custom_idx_on_name', table, columns.name]) })
122
+ * @Entity({ schema: 'library' })
123
+ * export class Author { ... }
124
+ * ```
125
+ *
126
+ * You can also use the `quote` tag function to write database-agnostic SQL expressions. The end-result is the same as using the `raw` function regarding database identifiers quoting, only to have a more elegant expression syntax:
127
+ *
128
+ * ```ts
129
+ * @Index({ name: 'custom_idx_on_name', expression: (table, columns) => quote`create index ${'custom_idx_on_name'} on ${table} (${columns.name})` })
130
+ * @Entity({ schema: 'library' })
131
+ * export class Author { ... }
132
+ * ```
145
133
  */
146
134
  export function raw(sql, params) {
147
135
  if (sql instanceof RawQueryFragment) {
@@ -181,14 +169,13 @@ export function raw(sql, params) {
181
169
  *
182
170
  * // value can be empty array
183
171
  * await em.find(User, { [sql`(select 1 = 1)`]: [] });
172
+ *
173
+ * // with type parameter for assignment without casting
174
+ * entity.date = sql<Date>`now()`;
184
175
  * ```
185
176
  */
186
177
  export function sql(sql, ...values) {
187
- return raw(sql.reduce((query, queryPart, i) => {
188
- const valueExists = i < values.length;
189
- const text = query + queryPart;
190
- return valueExists ? text + '?' : text;
191
- }, ''), values);
178
+ return raw(sql.join('?'), values);
192
179
  }
193
180
  export function createSqlFunction(func, key) {
194
181
  if (typeof key === 'string') {
@@ -200,3 +187,19 @@ sql.ref = (...keys) => raw('??', [keys.join('.')]);
200
187
  sql.now = (length) => raw('current_timestamp' + (length == null ? '' : `(${length})`));
201
188
  sql.lower = (key) => createSqlFunction('lower', key);
202
189
  sql.upper = (key) => createSqlFunction('upper', key);
190
+ /**
191
+ * Tag function providing quoting of db identifiers (table name, columns names, index names, ...).
192
+ *
193
+ * Within the template literal on which the tag function is applied, all placeholders are considered to be database identifiers, and will thus be quoted as so according to the database in use.
194
+ *
195
+ * ```ts
196
+ * // On postgres, will produce: create index "index custom_idx_on_name" on "library.author" ("name")
197
+ * // On mysql, will produce: create index `index custom_idx_on_name` on `library.author` (`name`)
198
+ * @Index({ name: 'custom_idx_on_name', expression: (table, columns, indexName) => quote`create index ${indexName} on ${table} (${columns.name})` })
199
+ * @Entity({ schema: 'library' })
200
+ * export class Author { ... }
201
+ * ```
202
+ */
203
+ export function quote(expParts, ...values) {
204
+ return raw(expParts.join('??'), values);
205
+ }
@@ -1,10 +1,10 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks';
1
+ import { createAsyncContext } from './AsyncContext.js';
2
2
  /**
3
3
  * Uses `AsyncLocalStorage` to create async context that holds the current EM fork.
4
4
  */
5
5
  export class RequestContext {
6
6
  map;
7
- static storage = new AsyncLocalStorage();
7
+ static storage = createAsyncContext();
8
8
  static counter = 1;
9
9
  id = RequestContext.counter++;
10
10
  constructor(map) {
@@ -1,7 +1,7 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks';
1
+ import { createAsyncContext } from './AsyncContext.js';
2
2
  export class TransactionContext {
3
3
  em;
4
- static storage = new AsyncLocalStorage();
4
+ static storage = createAsyncContext();
5
5
  id;
6
6
  constructor(em) {
7
7
  this.em = em;
@@ -0,0 +1,65 @@
1
+ import type { EntityManager } from '../EntityManager.js';
2
+ import { type TransactionOptions } from '../enums.js';
3
+ /**
4
+ * Manages transaction lifecycle and propagation for EntityManager.
5
+ */
6
+ export declare class TransactionManager {
7
+ private readonly em;
8
+ constructor(em: EntityManager);
9
+ /**
10
+ * Main entry point for handling transactional operations with propagation support.
11
+ */
12
+ handle<T>(cb: (em: EntityManager) => T | Promise<T>, options?: TransactionOptions): Promise<T>;
13
+ /**
14
+ * Executes the callback with the specified propagation type.
15
+ */
16
+ private executeWithPropagation;
17
+ /**
18
+ * Suspends the current transaction and returns the suspended resources.
19
+ */
20
+ private suspendTransaction;
21
+ /**
22
+ * Resumes a previously suspended transaction.
23
+ */
24
+ private resumeTransaction;
25
+ /**
26
+ * Executes operation without transaction context.
27
+ */
28
+ private executeWithoutTransaction;
29
+ /**
30
+ * Creates new independent transaction, suspending any existing one.
31
+ */
32
+ private executeWithNewTransaction;
33
+ /**
34
+ * Creates new transaction context.
35
+ */
36
+ private createNewTransaction;
37
+ /**
38
+ * Executes nested transaction with savepoint.
39
+ */
40
+ private executeNestedTransaction;
41
+ /**
42
+ * Creates a fork of the EntityManager with the given options.
43
+ */
44
+ private createFork;
45
+ /**
46
+ * Determines if changes should be propagated to the upper context.
47
+ */
48
+ private shouldPropagateToUpperContext;
49
+ /**
50
+ * Merges entities from fork to parent EntityManager.
51
+ */
52
+ private mergeEntitiesToParent;
53
+ /**
54
+ * Registers a deletion handler to unset entity identities after flush.
55
+ */
56
+ private registerDeletionHandler;
57
+ /**
58
+ * Processes transaction execution.
59
+ */
60
+ private processTransaction;
61
+ /**
62
+ * Executes transaction workflow with entity synchronization.
63
+ */
64
+ private executeTransactionFlow;
65
+ }