@mikro-orm/core 7.2.7-dev.6 → 7.2.7-dev.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/EntityManager.js CHANGED
@@ -743,10 +743,15 @@ export class EntityManager {
743
743
  if (Utils.isEmpty(options.orderBy) && !Raw.hasObjectFragments(options.orderBy)) {
744
744
  throw new Error('Explicit `orderBy` option required');
745
745
  }
746
+ const meta = this.metadata.get(entityName);
747
+ // same merge as em.find, so the cursor and the query see the same orderBy
748
+ if (meta.orderBy) {
749
+ options.orderBy = QueryHelper.mergeOrderBy(options.orderBy, meta.orderBy);
750
+ }
746
751
  const [entities, count] = options.includeCount !== false
747
752
  ? await em.findAndCount(entityName, options.where, options)
748
753
  : [await em.find(entityName, options.where, options)];
749
- return new Cursor(entities, count, options, this.metadata.get(entityName));
754
+ return new Cursor(entities, count, options, meta);
750
755
  }
751
756
  /**
752
757
  * Refreshes the persistent state of an entity from the database, overriding any local changes that have not yet been
@@ -679,12 +679,20 @@ export class EntityLoader {
679
679
  const subCond = Utils.isPlainObject(where[prop.name]) ? { ...where[prop.name] } : {};
680
680
  const meta2 = prop.targetMeta;
681
681
  const pk = Utils.getPrimaryKeyHash(meta2.primaryKeys);
682
+ // `$not` wrapping entity conditions stays on the target entity, only operators on the relation itself map to its PK
683
+ const isRelationOperator = (key, value) => {
684
+ if (key === '$not' && Utils.isPlainObject(value)) {
685
+ return Utils.getObjectQueryKeys(value).every(k => Utils.isOperator(k, false));
686
+ }
687
+ return Utils.isOperator(key, false);
688
+ };
682
689
  ['$and', '$or'].forEach(op => {
683
690
  if (where[op]) {
684
691
  const child = where[op]
685
692
  .map((cond) => cond[prop.name])
686
693
  .filter((sub) => sub != null &&
687
- !(Utils.isPlainObject(sub) && Utils.getObjectQueryKeys(sub).every(key => Utils.isOperator(key, false))))
694
+ !(Utils.isPlainObject(sub) &&
695
+ Utils.getObjectQueryKeys(sub).every(key => isRelationOperator(key, sub[key]))))
688
696
  .map((cond) => {
689
697
  if (Utils.isPrimaryKey(cond)) {
690
698
  return { [pk]: cond };
@@ -697,7 +705,7 @@ export class EntityLoader {
697
705
  }
698
706
  }
699
707
  });
700
- const operators = Object.keys(subCond).filter(key => Utils.isOperator(key, false));
708
+ const operators = Object.keys(subCond).filter(key => isRelationOperator(key, subCond[key]));
701
709
  if (operators.length > 0) {
702
710
  subCond[pk] = Utils.isPlainObject(subCond[pk]) ? { ...subCond[pk] } : (subCond[pk] ?? {});
703
711
  operators.forEach(op => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
- "version": "7.2.7-dev.6",
3
+ "version": "7.2.7-dev.7",
4
4
  "description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
5
5
  "keywords": [
6
6
  "data-mapper",
@@ -204,6 +204,8 @@ export declare abstract class Platform {
204
204
  supportsUnionWhere(): boolean;
205
205
  /** Returns the SQL type declaration used for array storage. */
206
206
  getArrayDeclarationSQL(): string;
207
+ /** Whether array columns support the `&&` (overlap) operator. */
208
+ supportsArrayOverlap(): boolean;
207
209
  /** Serializes a string array into its database storage format. */
208
210
  marshallArray(values: string[]): string;
209
211
  /** Deserializes a database-stored array string back into a string array. */
@@ -360,6 +360,10 @@ export class Platform {
360
360
  getArrayDeclarationSQL() {
361
361
  return 'text';
362
362
  }
363
+ /** Whether array columns support the `&&` (overlap) operator. */
364
+ supportsArrayOverlap() {
365
+ return false;
366
+ }
363
367
  /** Serializes a string array into its database storage format. */
364
368
  marshallArray(values) {
365
369
  return values.map(v => (/[,",\\]/.test(v) ? JSON.stringify(v) : v)).join(',');
package/typings.d.ts CHANGED
@@ -356,12 +356,15 @@ type ElemMatchCondition<T extends Record<string, any>> = {
356
356
  $and?: ElemMatchCondition<T>[];
357
357
  $not?: ElemMatchCondition<T>;
358
358
  };
359
- type ElemMatchFilter<T> = T extends readonly (infer E)[] ? E extends Record<string, any> ? {
359
+ type ArrayFilter<T> = T extends readonly (infer E)[] ? [E] extends [Scalar] ? {
360
+ $in?: readonly (readonly ExpandScalar<E>[])[];
361
+ $nin?: readonly (readonly ExpandScalar<E>[])[];
362
+ } : E extends Record<string, any> ? {
360
363
  $elemMatch: ElemMatchCondition<E>;
361
364
  } : never : never;
362
365
  /** Object form of a filter query, mapping entity keys to their filter conditions. */
363
366
  export type FilterObject<T> = {
364
- -readonly [K in EntityKey<T>]?: ExpandQuery<ExpandProperty<FilterObjectProp<T, K>>> | ExpandQueryMerged<ExpandProperty<FilterObjectProp<T, K>>> | FilterValue<ExpandProperty<FilterObjectProp<T, K>>> | ElemMatchFilter<FilterObjectProp<T, K>> | null;
367
+ -readonly [K in EntityKey<T>]?: ExpandQuery<ExpandProperty<FilterObjectProp<T, K>>> | ExpandQueryMerged<ExpandProperty<FilterObjectProp<T, K>>> | FilterValue<ExpandProperty<FilterObjectProp<T, K>>> | ArrayFilter<FilterObjectProp<T, K>> | null;
365
368
  };
366
369
  /** Recursively expands a type into its `FilterQuery` form for nested object filtering. */
367
370
  export type ExpandQuery<T> = T extends object ? (T extends Scalar ? never : FilterQuery<T>) : FilterValue<T>;
@@ -381,7 +384,7 @@ export type FilterQuery<T> = ObjectQuery<T> | NonNullable<ExpandScalar<Primary<T
381
384
  * Used when `using` option is set in `FindOptions` to enforce type-safe index usage.
382
385
  */
383
386
  export type IndexFilterQuery<T, Using extends string> = [Using] extends [never] ? FilterQuery<T> : (OperatorMap<T> & {
384
- -readonly [K in Extract<EntityKey<T>, IndexColumns<T, Using>>]?: ExpandQuery<ExpandProperty<FilterObjectProp<T, K>>> | ExpandQueryMerged<ExpandProperty<FilterObjectProp<T, K>>> | FilterValue<ExpandProperty<FilterObjectProp<T, K>>> | ElemMatchFilter<FilterObjectProp<T, K>> | null;
387
+ -readonly [K in Extract<EntityKey<T>, IndexColumns<T, Using>>]?: ExpandQuery<ExpandProperty<FilterObjectProp<T, K>>> | ExpandQueryMerged<ExpandProperty<FilterObjectProp<T, K>>> | FilterValue<ExpandProperty<FilterObjectProp<T, K>>> | ArrayFilter<FilterObjectProp<T, K>> | null;
385
388
  }) | NonNullable<ExpandScalar<Primary<T>>> | IndexFilterQuery<T, Using>[];
386
389
  /** Replaces `where` and `using` on an options type with index-aware variants when `Using` is specified. */
387
390
  export type WithUsingOptions<Opts, Entity, Using extends string> = Omit<Opts, 'where' | 'using'> & {
@@ -600,19 +600,27 @@ export class UnitOfWork {
600
600
  this.findEarlyUpdates(cs, inserts[cs.meta.uniqueName]);
601
601
  }
602
602
  }
603
+ // hashes of the inserts' PKs and unique values per type, built lazily, so matching a removed entity is a set lookup
604
+ const insertHashes = {};
603
605
  for (const entity of this.#removeStack) {
604
606
  const wrapped = helper(entity);
605
607
  /* v8 ignore next */
606
608
  if (wrapped.__processing) {
607
609
  continue;
608
610
  }
609
- const deletePkHash = [wrapped.getSerializedPrimaryKey(), ...this.expandUniqueProps(entity)];
610
- let type = ChangeSetType.DELETE;
611
- for (const cs of inserts[wrapped.__meta.uniqueName] ?? []) {
612
- if (deletePkHash.some(hash => hash === cs.getSerializedPrimaryKey() || this.expandUniqueProps(cs.entity).find(child => hash === child))) {
613
- type = ChangeSetType.DELETE_EARLY;
611
+ const uniqueName = wrapped.__meta.uniqueName;
612
+ let hashes = insertHashes[uniqueName];
613
+ if (!hashes) {
614
+ hashes = insertHashes[uniqueName] = new Set();
615
+ for (const cs of inserts[uniqueName] ?? []) {
616
+ hashes.add(cs.getSerializedPrimaryKey());
617
+ for (const hash of this.expandUniqueProps(cs.entity)) {
618
+ hashes.add(hash);
619
+ }
614
620
  }
615
621
  }
622
+ const deletePkHash = [wrapped.getSerializedPrimaryKey(), ...this.expandUniqueProps(entity)];
623
+ const type = deletePkHash.some(hash => hashes.has(hash)) ? ChangeSetType.DELETE_EARLY : ChangeSetType.DELETE;
616
624
  this.computeChangeSet(entity, type);
617
625
  }
618
626
  }
package/utils/Utils.js CHANGED
@@ -153,7 +153,7 @@ export function parseJsonSafe(value) {
153
153
  /** Collection of general-purpose utility methods used throughout the ORM. */
154
154
  export class Utils {
155
155
  static PK_SEPARATOR = '~~~';
156
- static #ORM_VERSION = '7.2.7-dev.6';
156
+ static #ORM_VERSION = '7.2.7-dev.7';
157
157
  /** Default session variable name backing an RLS filter argument (`current_setting('mikro.<filter>.<arg>')`). */
158
158
  static getRlsSettingName(filterName, argName) {
159
159
  return `mikro.${filterName}.${argName}`;