@mikro-orm/sql 7.0.0-rc.2 → 7.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 (66) hide show
  1. package/AbstractSqlConnection.d.ts +5 -4
  2. package/AbstractSqlConnection.js +20 -6
  3. package/AbstractSqlDriver.d.ts +19 -13
  4. package/AbstractSqlDriver.js +225 -47
  5. package/AbstractSqlPlatform.d.ts +35 -0
  6. package/AbstractSqlPlatform.js +51 -5
  7. package/PivotCollectionPersister.d.ts +2 -11
  8. package/PivotCollectionPersister.js +59 -59
  9. package/README.md +5 -4
  10. package/SqlEntityManager.d.ts +2 -2
  11. package/SqlEntityManager.js +5 -5
  12. package/dialects/index.d.ts +1 -0
  13. package/dialects/index.js +1 -0
  14. package/dialects/mssql/MsSqlNativeQueryBuilder.d.ts +2 -0
  15. package/dialects/mssql/MsSqlNativeQueryBuilder.js +8 -4
  16. package/dialects/mysql/BaseMySqlPlatform.d.ts +6 -0
  17. package/dialects/mysql/BaseMySqlPlatform.js +18 -2
  18. package/dialects/mysql/MySqlSchemaHelper.d.ts +1 -1
  19. package/dialects/mysql/MySqlSchemaHelper.js +25 -14
  20. package/dialects/oracledb/OracleDialect.d.ts +78 -0
  21. package/dialects/oracledb/OracleDialect.js +166 -0
  22. package/dialects/oracledb/OracleNativeQueryBuilder.d.ts +19 -0
  23. package/dialects/oracledb/OracleNativeQueryBuilder.js +249 -0
  24. package/dialects/oracledb/index.d.ts +2 -0
  25. package/dialects/oracledb/index.js +2 -0
  26. package/dialects/postgresql/BasePostgreSqlPlatform.d.ts +6 -0
  27. package/dialects/postgresql/BasePostgreSqlPlatform.js +49 -37
  28. package/dialects/postgresql/PostgreSqlSchemaHelper.js +75 -59
  29. package/dialects/sqlite/BaseSqliteConnection.js +2 -2
  30. package/dialects/sqlite/NodeSqliteDialect.js +3 -1
  31. package/dialects/sqlite/SqlitePlatform.d.ts +1 -0
  32. package/dialects/sqlite/SqlitePlatform.js +7 -1
  33. package/dialects/sqlite/SqliteSchemaHelper.js +23 -17
  34. package/index.d.ts +1 -1
  35. package/index.js +0 -1
  36. package/package.json +30 -30
  37. package/plugin/index.d.ts +1 -14
  38. package/plugin/index.js +13 -13
  39. package/plugin/transformer.d.ts +6 -22
  40. package/plugin/transformer.js +91 -82
  41. package/query/ArrayCriteriaNode.d.ts +1 -1
  42. package/query/CriteriaNode.js +28 -10
  43. package/query/CriteriaNodeFactory.js +20 -4
  44. package/query/NativeQueryBuilder.d.ts +28 -3
  45. package/query/NativeQueryBuilder.js +65 -3
  46. package/query/ObjectCriteriaNode.js +75 -31
  47. package/query/QueryBuilder.d.ts +199 -100
  48. package/query/QueryBuilder.js +544 -358
  49. package/query/QueryBuilderHelper.d.ts +18 -14
  50. package/query/QueryBuilderHelper.js +364 -147
  51. package/query/ScalarCriteriaNode.js +17 -8
  52. package/query/enums.d.ts +2 -0
  53. package/query/enums.js +2 -0
  54. package/query/raw.js +1 -1
  55. package/schema/DatabaseSchema.d.ts +7 -5
  56. package/schema/DatabaseSchema.js +68 -45
  57. package/schema/DatabaseTable.d.ts +8 -6
  58. package/schema/DatabaseTable.js +191 -107
  59. package/schema/SchemaComparator.d.ts +1 -3
  60. package/schema/SchemaComparator.js +76 -50
  61. package/schema/SchemaHelper.d.ts +2 -13
  62. package/schema/SchemaHelper.js +30 -9
  63. package/schema/SqlSchemaGenerator.d.ts +4 -14
  64. package/schema/SqlSchemaGenerator.js +26 -12
  65. package/typings.d.ts +10 -5
  66. package/tsconfig.build.tsbuildinfo +0 -1
@@ -1,44 +1,52 @@
1
1
  import { ReferenceKind, isRaw, } from '@mikro-orm/core';
2
2
  import { AliasNode, ColumnNode, ColumnUpdateNode, OperationNodeTransformer, PrimitiveValueListNode, ReferenceNode, SchemableIdentifierNode, TableNode, ValueListNode, ValueNode, ValuesNode, } from 'kysely';
3
3
  export class MikroTransformer extends OperationNodeTransformer {
4
- em;
5
- options;
6
4
  /**
7
5
  * Context stack to support nested queries (subqueries, CTEs)
8
6
  * Each level of query scope has its own Map of table aliases/names to EntityMetadata
9
7
  * Top of stack (highest index) is the current scope
10
8
  */
11
- contextStack = [];
9
+ #contextStack = [];
12
10
  /**
13
11
  * Subquery alias map: maps subquery/CTE alias to its source table metadata
14
12
  * Used to resolve columns from subqueries/CTEs to their original table definitions
15
13
  */
16
- subqueryAliasMap = new Map();
17
- metadata;
18
- platform;
14
+ #subqueryAliasMap = new Map();
15
+ #metadata;
16
+ #platform;
19
17
  /**
20
18
  * Global map of all entities involved in the query.
21
19
  * Populated during AST transformation and used for result transformation.
22
20
  */
23
- entityMap = new Map();
21
+ #entityMap = new Map();
22
+ #em;
23
+ #options;
24
24
  constructor(em, options = {}) {
25
25
  super();
26
- this.em = em;
27
- this.options = options;
28
- this.metadata = em.getMetadata();
29
- this.platform = em.getDriver().getPlatform();
26
+ this.#em = em;
27
+ this.#options = options;
28
+ this.#metadata = em.getMetadata();
29
+ this.#platform = em.getDriver().getPlatform();
30
30
  }
31
31
  reset() {
32
- this.subqueryAliasMap.clear();
33
- this.entityMap.clear();
32
+ this.#subqueryAliasMap.clear();
33
+ this.#entityMap.clear();
34
34
  }
35
35
  getOutputEntityMap() {
36
- return this.entityMap;
36
+ return this.#entityMap;
37
+ }
38
+ /** @internal */
39
+ getContextStack() {
40
+ return this.#contextStack;
41
+ }
42
+ /** @internal */
43
+ getSubqueryAliasMap() {
44
+ return this.#subqueryAliasMap;
37
45
  }
38
46
  transformSelectQuery(node, queryId) {
39
47
  // Push a new context for this query scope (starts with inherited parent context)
40
48
  const currentContext = new Map();
41
- this.contextStack.push(currentContext);
49
+ this.#contextStack.push(currentContext);
42
50
  try {
43
51
  // Process WITH clause (CTEs) first - they define names available in this scope
44
52
  if (node.with) {
@@ -60,12 +68,12 @@ export class MikroTransformer extends OperationNodeTransformer {
60
68
  }
61
69
  finally {
62
70
  // Pop the context when exiting this query scope
63
- this.contextStack.pop();
71
+ this.#contextStack.pop();
64
72
  }
65
73
  }
66
74
  transformInsertQuery(node, queryId) {
67
75
  const currentContext = new Map();
68
- this.contextStack.push(currentContext);
76
+ this.#contextStack.push(currentContext);
69
77
  try {
70
78
  let entityMeta;
71
79
  if (node.into) {
@@ -75,16 +83,12 @@ export class MikroTransformer extends OperationNodeTransformer {
75
83
  if (meta) {
76
84
  entityMeta = meta;
77
85
  currentContext.set(meta.tableName, meta);
78
- this.entityMap.set(meta.tableName, meta);
86
+ this.#entityMap.set(meta.tableName, meta);
79
87
  }
80
88
  }
81
89
  }
82
- const nodeWithHooks = this.options.processOnCreateHooks && entityMeta
83
- ? this.processOnCreateHooks(node, entityMeta)
84
- : node;
85
- const nodeWithConvertedValues = this.options.convertValues && entityMeta
86
- ? this.processInsertValues(nodeWithHooks, entityMeta)
87
- : nodeWithHooks;
90
+ const nodeWithHooks = this.#options.processOnCreateHooks && entityMeta ? this.processOnCreateHooks(node, entityMeta) : node;
91
+ const nodeWithConvertedValues = this.#options.convertValues && entityMeta ? this.processInsertValues(nodeWithHooks, entityMeta) : nodeWithHooks;
88
92
  // Handle ON CONFLICT clause
89
93
  let finalNode = nodeWithConvertedValues;
90
94
  if (node.onConflict?.updates && entityMeta) {
@@ -95,14 +99,14 @@ export class MikroTransformer extends OperationNodeTransformer {
95
99
  table: node.into, // Dummy table
96
100
  updates: node.onConflict.updates,
97
101
  };
98
- const updatesWithHooks = this.options.processOnUpdateHooks
102
+ const updatesWithHooks = this.#options.processOnUpdateHooks
99
103
  ? this.processOnUpdateHooks(tempUpdateNode, entityMeta).updates
100
104
  : node.onConflict.updates;
101
105
  const tempUpdateNodeWithHooks = {
102
106
  ...tempUpdateNode,
103
107
  updates: updatesWithHooks,
104
108
  };
105
- const updatesWithConvertedValues = this.options.convertValues
109
+ const updatesWithConvertedValues = this.#options.convertValues
106
110
  ? this.processUpdateValues(tempUpdateNodeWithHooks, entityMeta).updates
107
111
  : updatesWithHooks;
108
112
  if (updatesWithConvertedValues && updatesWithConvertedValues !== node.onConflict.updates) {
@@ -119,12 +123,12 @@ export class MikroTransformer extends OperationNodeTransformer {
119
123
  return super.transformInsertQuery(finalNode, queryId);
120
124
  }
121
125
  finally {
122
- this.contextStack.pop();
126
+ this.#contextStack.pop();
123
127
  }
124
128
  }
125
129
  transformUpdateQuery(node, queryId) {
126
130
  const currentContext = new Map();
127
- this.contextStack.push(currentContext);
131
+ this.#contextStack.push(currentContext);
128
132
  try {
129
133
  let entityMeta;
130
134
  if (node.table && TableNode.is(node.table)) {
@@ -134,7 +138,7 @@ export class MikroTransformer extends OperationNodeTransformer {
134
138
  if (meta) {
135
139
  entityMeta = meta;
136
140
  currentContext.set(meta.tableName, meta);
137
- this.entityMap.set(meta.tableName, meta);
141
+ this.#entityMap.set(meta.tableName, meta);
138
142
  }
139
143
  }
140
144
  }
@@ -150,21 +154,17 @@ export class MikroTransformer extends OperationNodeTransformer {
150
154
  this.processJoinNode(join, currentContext);
151
155
  }
152
156
  }
153
- const nodeWithHooks = this.options.processOnUpdateHooks && entityMeta
154
- ? this.processOnUpdateHooks(node, entityMeta)
155
- : node;
156
- const nodeWithConvertedValues = this.options.convertValues && entityMeta
157
- ? this.processUpdateValues(nodeWithHooks, entityMeta)
158
- : nodeWithHooks;
157
+ const nodeWithHooks = this.#options.processOnUpdateHooks && entityMeta ? this.processOnUpdateHooks(node, entityMeta) : node;
158
+ const nodeWithConvertedValues = this.#options.convertValues && entityMeta ? this.processUpdateValues(nodeWithHooks, entityMeta) : nodeWithHooks;
159
159
  return super.transformUpdateQuery(nodeWithConvertedValues, queryId);
160
160
  }
161
161
  finally {
162
- this.contextStack.pop();
162
+ this.#contextStack.pop();
163
163
  }
164
164
  }
165
165
  transformDeleteQuery(node, queryId) {
166
166
  const currentContext = new Map();
167
- this.contextStack.push(currentContext);
167
+ this.#contextStack.push(currentContext);
168
168
  try {
169
169
  const froms = node.from?.froms;
170
170
  if (froms && froms.length > 0) {
@@ -175,7 +175,7 @@ export class MikroTransformer extends OperationNodeTransformer {
175
175
  const meta = this.findEntityMetadata(tableName);
176
176
  if (meta) {
177
177
  currentContext.set(meta.tableName, meta);
178
- this.entityMap.set(meta.tableName, meta);
178
+ this.#entityMap.set(meta.tableName, meta);
179
179
  }
180
180
  }
181
181
  }
@@ -189,24 +189,24 @@ export class MikroTransformer extends OperationNodeTransformer {
189
189
  return super.transformDeleteQuery(node, queryId);
190
190
  }
191
191
  finally {
192
- this.contextStack.pop();
192
+ this.#contextStack.pop();
193
193
  }
194
194
  }
195
195
  transformMergeQuery(node, queryId) {
196
196
  const currentContext = new Map();
197
- this.contextStack.push(currentContext);
197
+ this.#contextStack.push(currentContext);
198
198
  try {
199
199
  return super.transformMergeQuery(node, queryId);
200
200
  }
201
201
  finally {
202
- this.contextStack.pop();
202
+ this.#contextStack.pop();
203
203
  }
204
204
  }
205
205
  transformIdentifier(node, queryId) {
206
206
  node = super.transformIdentifier(node, queryId);
207
207
  const parent = this.nodeStack[this.nodeStack.length - 2];
208
208
  // Transform table names when tableNamingStrategy is 'entity'
209
- if (this.options.tableNamingStrategy === 'entity' && parent && SchemableIdentifierNode.is(parent)) {
209
+ if (this.#options.tableNamingStrategy === 'entity' && parent && SchemableIdentifierNode.is(parent)) {
210
210
  const meta = this.findEntityMetadata(node.name);
211
211
  if (meta) {
212
212
  return {
@@ -217,7 +217,9 @@ export class MikroTransformer extends OperationNodeTransformer {
217
217
  }
218
218
  // Transform column names when columnNamingStrategy is 'property'
219
219
  // Support ColumnNode, ColumnUpdateNode, and ReferenceNode (for JOIN conditions)
220
- if (this.options.columnNamingStrategy === 'property' && parent && (ColumnNode.is(parent) || ColumnUpdateNode.is(parent) || ReferenceNode.is(parent))) {
220
+ if (this.#options.columnNamingStrategy === 'property' &&
221
+ parent &&
222
+ (ColumnNode.is(parent) || ColumnUpdateNode.is(parent) || ReferenceNode.is(parent))) {
221
223
  const ownerMeta = this.findOwnerEntityInContext();
222
224
  if (ownerMeta) {
223
225
  const prop = ownerMeta.properties[node.name];
@@ -245,8 +247,8 @@ export class MikroTransformer extends OperationNodeTransformer {
245
247
  const tableName = this.getTableName(reference.table);
246
248
  if (tableName) {
247
249
  // First, check in subquery alias map (for CTE/subquery columns)
248
- if (this.subqueryAliasMap.has(tableName)) {
249
- return this.subqueryAliasMap.get(tableName);
250
+ if (this.#subqueryAliasMap.has(tableName)) {
251
+ return this.#subqueryAliasMap.get(tableName);
250
252
  }
251
253
  // Find entity metadata to get the actual table name
252
254
  // Context uses table names (meta.tableName) as keys, not entity names
@@ -273,16 +275,16 @@ export class MikroTransformer extends OperationNodeTransformer {
273
275
  }
274
276
  }
275
277
  // If no explicit table reference, use the first entity in current context
276
- if (this.contextStack.length > 0) {
277
- const currentContext = this.contextStack[this.contextStack.length - 1];
278
+ if (this.#contextStack.length > 0) {
279
+ const currentContext = this.#contextStack[this.#contextStack.length - 1];
278
280
  for (const [alias, meta] of currentContext.entries()) {
279
281
  if (meta) {
280
282
  return meta;
281
283
  }
282
284
  // If the context value is undefined but the alias is in subqueryAliasMap,
283
285
  // use the mapped metadata (for CTE/subquery cases)
284
- if (!meta && this.subqueryAliasMap.has(alias)) {
285
- const mappedMeta = this.subqueryAliasMap.get(alias);
286
+ if (!meta && this.#subqueryAliasMap.has(alias)) {
287
+ const mappedMeta = this.#subqueryAliasMap.get(alias);
286
288
  if (mappedMeta) {
287
289
  return mappedMeta;
288
290
  }
@@ -312,7 +314,7 @@ export class MikroTransformer extends OperationNodeTransformer {
312
314
  }
313
315
  const newRows = node.values.values.map(row => {
314
316
  const valuesToAdd = missingProps.map(prop => {
315
- const val = prop.onCreate(undefined, this.em);
317
+ const val = prop.onCreate(undefined, this.#em);
316
318
  return val;
317
319
  });
318
320
  if (ValueListNode.is(row)) {
@@ -350,7 +352,7 @@ export class MikroTransformer extends OperationNodeTransformer {
350
352
  }
351
353
  const newUpdates = [...node.updates];
352
354
  for (const prop of missingProps) {
353
- const val = prop.onUpdate(undefined, this.em);
355
+ const val = prop.onUpdate(undefined, this.#em);
354
356
  newUpdates.push(ColumnUpdateNode.create(ColumnNode.create(prop.name), ValueNode.create(val)));
355
357
  }
356
358
  return {
@@ -465,7 +467,7 @@ export class MikroTransformer extends OperationNodeTransformer {
465
467
  return meta.props.find(prop => prop.fieldNames?.includes(columnName));
466
468
  }
467
469
  shouldConvertValues() {
468
- return !!this.options.convertValues;
470
+ return !!this.#options.convertValues;
469
471
  }
470
472
  prepareInputValue(prop, value, enabled) {
471
473
  if (!enabled || !prop || value == null) {
@@ -480,10 +482,14 @@ export class MikroTransformer extends OperationNodeTransformer {
480
482
  }
481
483
  }
482
484
  if (prop.customType && !isRaw(value)) {
483
- return prop.customType.convertToDatabaseValue(value, this.platform, { fromQuery: true, key: prop.name, mode: 'query-data' });
485
+ return prop.customType.convertToDatabaseValue(value, this.#platform, {
486
+ fromQuery: true,
487
+ key: prop.name,
488
+ mode: 'query-data',
489
+ });
484
490
  }
485
491
  if (value instanceof Date) {
486
- return this.platform.processDateProperty(value);
492
+ return this.#platform.processDateProperty(value);
487
493
  }
488
494
  return value;
489
495
  }
@@ -494,8 +500,8 @@ export class MikroTransformer extends OperationNodeTransformer {
494
500
  */
495
501
  lookupInContextStack(tableNameOrAlias) {
496
502
  // Search from top of stack (current scope) to bottom (parent scopes)
497
- for (let i = this.contextStack.length - 1; i >= 0; i--) {
498
- const context = this.contextStack[i];
503
+ for (let i = this.#contextStack.length - 1; i >= 0; i--) {
504
+ const context = this.#contextStack[i];
499
505
  if (context.has(tableNameOrAlias)) {
500
506
  return context.get(tableNameOrAlias);
501
507
  }
@@ -517,10 +523,10 @@ export class MikroTransformer extends OperationNodeTransformer {
517
523
  if (cte.expression?.kind === 'SelectQueryNode') {
518
524
  const sourceMeta = this.extractSourceTableFromSelectQuery(cte.expression);
519
525
  if (sourceMeta) {
520
- this.subqueryAliasMap.set(cteName, sourceMeta);
526
+ this.#subqueryAliasMap.set(cteName, sourceMeta);
521
527
  // Add CTE to entityMap so it can be used for result transformation if needed
522
528
  // (though CTEs usually don't appear in result rows directly, but their columns might)
523
- this.entityMap.set(cteName, sourceMeta);
529
+ this.#entityMap.set(cteName, sourceMeta);
524
530
  }
525
531
  }
526
532
  }
@@ -550,11 +556,11 @@ export class MikroTransformer extends OperationNodeTransformer {
550
556
  if (aliasName) {
551
557
  context.set(aliasName, meta);
552
558
  if (meta) {
553
- this.entityMap.set(aliasName, meta);
559
+ this.#entityMap.set(aliasName, meta);
554
560
  }
555
561
  // Also map the alias in subqueryAliasMap if the table name is a CTE
556
- if (this.subqueryAliasMap.has(tableName)) {
557
- this.subqueryAliasMap.set(aliasName, this.subqueryAliasMap.get(tableName));
562
+ if (this.#subqueryAliasMap.has(tableName)) {
563
+ this.#subqueryAliasMap.set(aliasName, this.#subqueryAliasMap.get(tableName));
558
564
  }
559
565
  }
560
566
  }
@@ -567,7 +573,7 @@ export class MikroTransformer extends OperationNodeTransformer {
567
573
  // Try to extract the source table from the subquery
568
574
  const sourceMeta = this.extractSourceTableFromSelectQuery(from.node);
569
575
  if (sourceMeta) {
570
- this.subqueryAliasMap.set(aliasName, sourceMeta);
576
+ this.#subqueryAliasMap.set(aliasName, sourceMeta);
571
577
  }
572
578
  }
573
579
  }
@@ -586,7 +592,7 @@ export class MikroTransformer extends OperationNodeTransformer {
586
592
  const meta = this.findEntityMetadata(tableName);
587
593
  context.set(tableName, meta);
588
594
  if (meta) {
589
- this.entityMap.set(tableName, meta);
595
+ this.#entityMap.set(tableName, meta);
590
596
  }
591
597
  }
592
598
  }
@@ -606,11 +612,11 @@ export class MikroTransformer extends OperationNodeTransformer {
606
612
  if (aliasName) {
607
613
  context.set(aliasName, meta);
608
614
  if (meta) {
609
- this.entityMap.set(aliasName, meta);
615
+ this.#entityMap.set(aliasName, meta);
610
616
  }
611
617
  // Also map the alias in subqueryAliasMap if the table name is a CTE
612
- if (this.subqueryAliasMap.has(tableName)) {
613
- this.subqueryAliasMap.set(aliasName, this.subqueryAliasMap.get(tableName));
618
+ if (this.#subqueryAliasMap.has(tableName)) {
619
+ this.#subqueryAliasMap.set(aliasName, this.#subqueryAliasMap.get(tableName));
614
620
  }
615
621
  }
616
622
  }
@@ -623,7 +629,7 @@ export class MikroTransformer extends OperationNodeTransformer {
623
629
  // Try to extract the source table from the subquery
624
630
  const sourceMeta = this.extractSourceTableFromSelectQuery(joinTable.node);
625
631
  if (sourceMeta) {
626
- this.subqueryAliasMap.set(aliasName, sourceMeta);
632
+ this.#subqueryAliasMap.set(aliasName, sourceMeta);
627
633
  }
628
634
  }
629
635
  }
@@ -643,7 +649,7 @@ export class MikroTransformer extends OperationNodeTransformer {
643
649
  // Use table name (meta.tableName) as key to match transformUpdateQuery behavior
644
650
  if (meta) {
645
651
  context.set(meta.tableName, meta);
646
- this.entityMap.set(meta.tableName, meta);
652
+ this.#entityMap.set(meta.tableName, meta);
647
653
  // Also set with entity name for backward compatibility
648
654
  context.set(tableName, meta);
649
655
  }
@@ -706,11 +712,11 @@ export class MikroTransformer extends OperationNodeTransformer {
706
712
  * Find entity metadata by table name or entity name
707
713
  */
708
714
  findEntityMetadata(name) {
709
- const byEntity = this.metadata.getByClassName(name, false);
715
+ const byEntity = this.#metadata.getByClassName(name, false);
710
716
  if (byEntity) {
711
717
  return byEntity;
712
718
  }
713
- const allMetadata = Array.from(this.metadata);
719
+ const allMetadata = Array.from(this.#metadata);
714
720
  const byTable = allMetadata.find(m => m.tableName === name);
715
721
  if (byTable) {
716
722
  return byTable;
@@ -723,7 +729,9 @@ export class MikroTransformer extends OperationNodeTransformer {
723
729
  */
724
730
  transformResult(rows, entityMap) {
725
731
  // Only transform if columnNamingStrategy is 'property' or convertValues is true, and we have data
726
- if ((this.options.columnNamingStrategy !== 'property' && !this.options.convertValues) || !rows || rows.length === 0) {
732
+ if ((this.#options.columnNamingStrategy !== 'property' && !this.#options.convertValues) ||
733
+ !rows ||
734
+ rows.length === 0) {
727
735
  return rows;
728
736
  }
729
737
  // If no entities found (e.g. raw query without known tables), return rows as is
@@ -818,7 +826,7 @@ export class MikroTransformer extends OperationNodeTransformer {
818
826
  continue;
819
827
  }
820
828
  const converted = this.prepareOutputValue(prop, transformed[fieldName]);
821
- if (this.options.columnNamingStrategy === 'property' && prop.name !== fieldName) {
829
+ if (this.#options.columnNamingStrategy === 'property' && prop.name !== fieldName) {
822
830
  if (!(prop.name in transformed)) {
823
831
  transformed[prop.name] = converted;
824
832
  }
@@ -828,13 +836,13 @@ export class MikroTransformer extends OperationNodeTransformer {
828
836
  delete transformed[fieldName];
829
837
  continue;
830
838
  }
831
- if (this.options.convertValues) {
839
+ if (this.#options.convertValues) {
832
840
  transformed[fieldName] = converted;
833
841
  }
834
842
  }
835
843
  // Second pass: handle relation fields
836
844
  // Only run if columnNamingStrategy is 'property', as we don't want to rename FKs otherwise
837
- if (this.options.columnNamingStrategy === 'property') {
845
+ if (this.#options.columnNamingStrategy === 'property') {
838
846
  for (const [fieldName, relationPropertyName] of Object.entries(relationFieldMap)) {
839
847
  if (fieldName in transformed && !(relationPropertyName in transformed)) {
840
848
  // Move the foreign key value to the relation property name
@@ -846,35 +854,36 @@ export class MikroTransformer extends OperationNodeTransformer {
846
854
  return transformed;
847
855
  }
848
856
  prepareOutputValue(prop, value) {
849
- if (!this.options.convertValues || !prop || value == null) {
857
+ if (!this.#options.convertValues || !prop || value == null) {
850
858
  return value;
851
859
  }
852
860
  if (prop.customType) {
853
- return prop.customType.convertToJSValue(value, this.platform);
861
+ return prop.customType.convertToJSValue(value, this.#platform);
854
862
  }
855
863
  // Aligned with EntityComparator.getResultMapper logic
856
864
  if (prop.runtimeType === 'boolean') {
857
865
  // Use !! conversion like EntityComparator: value == null ? value : !!value
858
866
  return value == null ? value : !!value;
859
867
  }
860
- if (prop.runtimeType === 'Date' && !this.platform.isNumericProperty(prop)) {
868
+ if (prop.runtimeType === 'Date' && !this.#platform.isNumericProperty(prop)) {
861
869
  // Aligned with EntityComparator: exclude numeric timestamp properties
862
870
  // If already Date instance or null, return as is
863
871
  if (value == null || value instanceof Date) {
864
872
  return value;
865
873
  }
866
874
  // Handle timezone like EntityComparator.parseDate
867
- const tz = this.platform.getTimezone();
875
+ const tz = this.#platform.getTimezone();
868
876
  if (!tz || tz === 'local') {
869
- return this.platform.parseDate(value);
877
+ return this.#platform.parseDate(value);
870
878
  }
871
879
  // For non-local timezone, check if value already has timezone info
872
880
  // Number (timestamp) doesn't need timezone handling, string needs check
873
- if (typeof value === 'number' || (typeof value === 'string' && (value.includes('+') || value.lastIndexOf('-') > 10 || value.endsWith('Z')))) {
874
- return this.platform.parseDate(value);
881
+ if (typeof value === 'number' ||
882
+ (typeof value === 'string' && (value.includes('+') || value.lastIndexOf('-') > 10 || value.endsWith('Z')))) {
883
+ return this.#platform.parseDate(value);
875
884
  }
876
885
  // Append timezone if not present (only for string values)
877
- return this.platform.parseDate(value + tz);
886
+ return this.#platform.parseDate(value + tz);
878
887
  }
879
888
  // For all other runtimeTypes (number, string, bigint, Buffer, object, any, etc.)
880
889
  // EntityComparator just assigns directly without conversion
@@ -6,6 +6,6 @@ import type { IQueryBuilder, ICriteriaNodeProcessOptions } from '../typings.js';
6
6
  export declare class ArrayCriteriaNode<T extends object> extends CriteriaNode<T> {
7
7
  process(qb: IQueryBuilder<T>, options?: ICriteriaNodeProcessOptions): any;
8
8
  unwrap(): any;
9
- willAutoJoin(qb: IQueryBuilder<T>, alias?: string, options?: ICriteriaNodeProcessOptions): any;
9
+ willAutoJoin(qb: IQueryBuilder<T>, alias?: string, options?: ICriteriaNodeProcessOptions): boolean;
10
10
  isStrict(): boolean;
11
11
  }
@@ -53,22 +53,34 @@ export class CriteriaNode {
53
53
  const type = this.prop ? this.prop.kind : null;
54
54
  const composite = this.prop?.joinColumns ? this.prop.joinColumns.length > 1 : false;
55
55
  const rawField = RawQueryFragment.isKnownFragmentSymbol(this.key);
56
- const scalar = payload === null || Utils.isPrimaryKey(payload) || payload instanceof RegExp || payload instanceof Date || rawField;
56
+ const scalar = payload === null ||
57
+ Utils.isPrimaryKey(payload) ||
58
+ payload instanceof RegExp ||
59
+ payload instanceof Date ||
60
+ rawField;
57
61
  const operator = Utils.isPlainObject(payload) && Utils.getObjectQueryKeys(payload).every(k => Utils.isOperator(k, false));
58
62
  if (composite) {
59
63
  return true;
60
64
  }
61
65
  switch (type) {
62
- case ReferenceKind.MANY_TO_ONE: return false;
63
- case ReferenceKind.ONE_TO_ONE: return !this.prop.owner;
64
- case ReferenceKind.ONE_TO_MANY: return scalar || operator;
65
- case ReferenceKind.MANY_TO_MANY: return scalar || operator;
66
- default: return false;
66
+ case ReferenceKind.MANY_TO_ONE:
67
+ return false;
68
+ case ReferenceKind.ONE_TO_ONE:
69
+ return !this.prop.owner;
70
+ case ReferenceKind.ONE_TO_MANY:
71
+ return scalar || operator;
72
+ case ReferenceKind.MANY_TO_MANY:
73
+ return scalar || operator;
74
+ default:
75
+ return false;
67
76
  }
68
77
  }
69
78
  renameFieldToPK(qb, ownerAlias) {
70
79
  const joinAlias = qb.getAliasForJoinPath(this.getPath(), { matchPopulateJoins: true });
71
- if (!joinAlias && this.parent && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind) && this.prop.owner) {
80
+ if (!joinAlias &&
81
+ this.parent &&
82
+ [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(this.prop.kind) &&
83
+ this.prop.owner) {
72
84
  const alias = qb.getAliasForJoinPath(this.parent.getPath()) ?? ownerAlias ?? qb.alias;
73
85
  return Utils.getPrimaryKeyHash(this.prop.joinColumns.map(col => `${alias}.${col}`));
74
86
  }
@@ -84,7 +96,9 @@ export class CriteriaNode {
84
96
  const parentPath = opts?.parentPath ?? this.parent?.getPath({ addIndex: addParentIndex }) ?? Utils.className(this.entityName);
85
97
  const index = opts?.addIndex && this.index != null ? `[${this.index}]` : '';
86
98
  // ignore group operators to allow easier mapping (e.g. for orderBy)
87
- const key = this.key && !RawQueryFragment.isKnownFragmentSymbol(this.key) && !['$and', '$or', '$not'].includes(this.key) ? '.' + this.key : '';
99
+ const key = this.key && !RawQueryFragment.isKnownFragmentSymbol(this.key) && !['$and', '$or', '$not'].includes(this.key)
100
+ ? '.' + this.key
101
+ : '';
88
102
  const ret = parentPath + index + key;
89
103
  if (this.isPivotJoin()) {
90
104
  // distinguish pivot table join from target entity join
@@ -97,7 +111,11 @@ export class CriteriaNode {
97
111
  return false;
98
112
  }
99
113
  const rawField = RawQueryFragment.isKnownFragmentSymbol(this.key);
100
- const scalar = this.payload === null || Utils.isPrimaryKey(this.payload) || this.payload instanceof RegExp || this.payload instanceof Date || rawField;
114
+ const scalar = this.payload === null ||
115
+ Utils.isPrimaryKey(this.payload) ||
116
+ this.payload instanceof RegExp ||
117
+ this.payload instanceof Date ||
118
+ rawField;
101
119
  const operator = Utils.isObject(this.payload) && Utils.getObjectQueryKeys(this.payload).every(k => Utils.isOperator(k, false));
102
120
  return this.prop.kind === ReferenceKind.MANY_TO_MANY && (scalar || operator);
103
121
  }
@@ -116,7 +134,7 @@ export class CriteriaNode {
116
134
  const o = {};
117
135
  ['entityName', 'key', 'index', 'payload']
118
136
  .filter(k => this[k] !== undefined)
119
- .forEach(k => o[k] = this[k]);
137
+ .forEach(k => (o[k] = this[k]));
120
138
  return `${this.constructor.name} ${inspect(o)}`;
121
139
  }
122
140
  }
@@ -2,13 +2,18 @@ import { GroupOperator, isRaw, JsonType, RawQueryFragment, ReferenceKind, Utils,
2
2
  import { ObjectCriteriaNode } from './ObjectCriteriaNode.js';
3
3
  import { ArrayCriteriaNode } from './ArrayCriteriaNode.js';
4
4
  import { ScalarCriteriaNode } from './ScalarCriteriaNode.js';
5
+ import { EMBEDDABLE_ARRAY_OPS } from './enums.js';
5
6
  /**
6
7
  * @internal
7
8
  */
8
9
  export class CriteriaNodeFactory {
9
10
  static createNode(metadata, entityName, payload, parent, key, validate = true) {
10
11
  const rawField = RawQueryFragment.isKnownFragmentSymbol(key);
11
- const scalar = Utils.isPrimaryKey(payload) || isRaw(payload) || payload instanceof RegExp || payload instanceof Date || rawField;
12
+ const scalar = Utils.isPrimaryKey(payload) ||
13
+ isRaw(payload) ||
14
+ payload instanceof RegExp ||
15
+ payload instanceof Date ||
16
+ rawField;
12
17
  if (Array.isArray(payload) && !scalar) {
13
18
  return this.createArrayNode(metadata, entityName, payload, parent, key, validate);
14
19
  }
@@ -65,15 +70,26 @@ export class CriteriaNodeFactory {
65
70
  }, {});
66
71
  return this.createNode(metadata, entityName, map, node, key, validate);
67
72
  }
73
+ // For array embeddeds stored as real columns, route property-level queries
74
+ // as scalar nodes so QueryBuilderHelper generates EXISTS subqueries with
75
+ // JSON array iteration. Keys containing `~` indicate the property lives
76
+ // inside a parent's object-mode JSON column (MetadataDiscovery uses `~` as
77
+ // the glue for object embeds), where JSON path access is used instead.
78
+ if (prop.array && !String(key).includes('~')) {
79
+ const keys = Object.keys(val);
80
+ const hasOnlyArrayOps = keys.every(k => EMBEDDABLE_ARRAY_OPS.includes(k));
81
+ if (!hasOnlyArrayOps) {
82
+ return this.createScalarNode(metadata, entityName, val, node, key, validate);
83
+ }
84
+ }
68
85
  // array operators can be used on embedded properties
69
- const allowedOperators = ['$contains', '$contained', '$overlap'];
70
- const operator = Object.keys(val).some(f => Utils.isOperator(f) && !allowedOperators.includes(f));
86
+ const operator = Object.keys(val).some(f => Utils.isOperator(f) && !EMBEDDABLE_ARRAY_OPS.includes(f));
71
87
  if (operator) {
72
88
  throw ValidationError.cannotUseOperatorsInsideEmbeddables(entityName, prop.name, payload);
73
89
  }
74
90
  const map = Object.keys(val).reduce((oo, k) => {
75
91
  const embeddedProp = prop.embeddedProps[k] ?? Object.values(prop.embeddedProps).find(p => p.name === k);
76
- if (!embeddedProp && !allowedOperators.includes(k)) {
92
+ if (!embeddedProp && !EMBEDDABLE_ARRAY_OPS.includes(k)) {
77
93
  throw ValidationError.invalidEmbeddableQuery(entityName, k, prop.type);
78
94
  }
79
95
  if (embeddedProp) {