@mikro-orm/core 7.1.0-dev.2 → 7.1.0-dev.4

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.
@@ -307,6 +307,25 @@ export declare class EntityManager<Driver extends IDatabaseDriver = IDatabaseDri
307
307
  * Fires native insert query. Calling this has no side effects on the context (identity map).
308
308
  */
309
309
  insert<Entity extends object>(entityNameOrEntity: EntityName<Entity> | Entity, data?: RequiredEntityData<Entity> | Entity, options?: NativeInsertUpdateOptions<Entity>): Promise<Primary<Entity>>;
310
+ /**
311
+ * Clones rows matching the condition at the database level via INSERT...SELECT.
312
+ * Automatically excludes auto-increment PKs, generated columns, and resets version properties.
313
+ * Returns the cloned entity, registered in the identity map.
314
+ *
315
+ * @example
316
+ * ```ts
317
+ * // Clone by entity class + where + overrides
318
+ * const cloned = await em.clone(Author, { id: 1 }, { email: 'new@email.com' });
319
+ *
320
+ * // Pure clone (all non-PK fields copied)
321
+ * const cloned = await em.clone(Author, { id: 1 });
322
+ *
323
+ * // Clone a loaded entity
324
+ * const author = await em.findOneOrFail(Author, 1);
325
+ * const cloned = await em.clone(author, { email: 'new@email.com' });
326
+ * ```
327
+ */
328
+ clone<Entity extends object>(entityNameOrEntity: EntityName<Entity> | Entity, whereOrOverrides?: FilterQuery<NoInfer<Entity>> | EntityData<Entity>, overridesOrOptions?: EntityData<Entity> | NativeInsertUpdateOptions<Entity>, options?: NativeInsertUpdateOptions<Entity>): Promise<Entity>;
310
329
  /**
311
330
  * Fires native multi-insert query. Calling this has no side effects on the context (identity map).
312
331
  */
package/EntityManager.js CHANGED
@@ -1211,6 +1211,54 @@ export class EntityManager {
1211
1211
  });
1212
1212
  return res.insertId;
1213
1213
  }
1214
+ /**
1215
+ * Clones rows matching the condition at the database level via INSERT...SELECT.
1216
+ * Automatically excludes auto-increment PKs, generated columns, and resets version properties.
1217
+ * Returns the cloned entity, registered in the identity map.
1218
+ *
1219
+ * @example
1220
+ * ```ts
1221
+ * // Clone by entity class + where + overrides
1222
+ * const cloned = await em.clone(Author, { id: 1 }, { email: 'new@email.com' });
1223
+ *
1224
+ * // Pure clone (all non-PK fields copied)
1225
+ * const cloned = await em.clone(Author, { id: 1 });
1226
+ *
1227
+ * // Clone a loaded entity
1228
+ * const author = await em.findOneOrFail(Author, 1);
1229
+ * const cloned = await em.clone(author, { email: 'new@email.com' });
1230
+ * ```
1231
+ */
1232
+ async clone(entityNameOrEntity, whereOrOverrides, overridesOrOptions, options) {
1233
+ const em = this.getContext(false);
1234
+ let entityName;
1235
+ let where;
1236
+ let overrides;
1237
+ if (Utils.isEntity(entityNameOrEntity)) {
1238
+ // clone(entity, overrides?, options?)
1239
+ entityName = entityNameOrEntity.constructor;
1240
+ where = helper(entityNameOrEntity).getPrimaryKey();
1241
+ overrides = whereOrOverrides;
1242
+ options = overridesOrOptions;
1243
+ }
1244
+ else {
1245
+ // clone(EntityClass, where, overrides?, options?)
1246
+ entityName = entityNameOrEntity;
1247
+ where = whereOrOverrides;
1248
+ overrides = overridesOrOptions;
1249
+ }
1250
+ options ??= {};
1251
+ em.prepareOptions(options);
1252
+ const meta = em.metadata.get(entityName);
1253
+ const res = await em.driver.nativeClone(entityName, where, overrides, {
1254
+ ctx: em.#transactionContext,
1255
+ ...options,
1256
+ });
1257
+ const pk = res.insertId ?? res.row?.[meta.primaryKeys[0]];
1258
+ return em.findOneOrFail(entityName, pk, {
1259
+ schema: options.schema,
1260
+ });
1261
+ }
1214
1262
  /**
1215
1263
  * Fires native multi-insert query. Calling this has no side effects on the context (identity map).
1216
1264
  */
@@ -28,6 +28,7 @@ export declare abstract class DatabaseDriver<C extends Connection> implements ID
28
28
  abstract nativeUpdate<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, data: EntityDictionary<T>, options?: NativeInsertUpdateOptions<T>): Promise<QueryResult<T>>;
29
29
  nativeUpdateMany<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>[], data: EntityDictionary<T>[], options?: NativeInsertUpdateManyOptions<T>): Promise<QueryResult<T>>;
30
30
  abstract nativeDelete<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options?: DeleteOptions<T>): Promise<QueryResult<T>>;
31
+ nativeClone<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, overrides?: EntityData<T>, options?: NativeInsertUpdateOptions<T>): Promise<QueryResult<T>>;
31
32
  abstract count<T extends object, P extends string = never>(entityName: EntityName<T>, where: FilterQuery<T>, options?: CountOptions<T, P>): Promise<number>;
32
33
  /** Creates a new EntityManager instance bound to this driver. */
33
34
  createEntityManager(useContext?: boolean): this[typeof EntityManagerType];
@@ -27,6 +27,9 @@ export class DatabaseDriver {
27
27
  async nativeUpdateMany(entityName, where, data, options) {
28
28
  throw new Error(`Batch updates are not supported by ${this.constructor.name} driver`);
29
29
  }
30
+ async nativeClone(entityName, where, overrides, options) {
31
+ throw new Error(`${this.constructor.name} does not support nativeClone`);
32
+ }
30
33
  /** Creates a new EntityManager instance bound to this driver. */
31
34
  createEntityManager(useContext) {
32
35
  const EntityManagerClass = this.config.get('entityManager', EntityManager);
@@ -52,6 +52,11 @@ export interface IDatabaseDriver<C extends Connection = Connection> {
52
52
  nativeUpdateMany<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>[], data: EntityDictionary<T>[], options?: NativeInsertUpdateManyOptions<T>): Promise<QueryResult<T>>;
53
53
  /** Deletes rows matching the given condition. */
54
54
  nativeDelete<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options?: NativeDeleteOptions<T>): Promise<QueryResult<T>>;
55
+ /**
56
+ * Clones rows matching the condition at the database level via INSERT...SELECT.
57
+ * Automatically excludes auto-increment PKs, generated columns, and resets version properties.
58
+ */
59
+ nativeClone<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, overrides?: EntityData<T>, options?: NativeInsertUpdateOptions<T>): Promise<QueryResult<T>>;
55
60
  /** Persists changes to M:N collections (inserts/deletes pivot table rows). */
56
61
  syncCollections<T extends object, O extends object>(collections: Iterable<Collection<T, O>>, options?: DriverMethodOptions): Promise<void>;
57
62
  /** Counts entities matching the given condition. */
@@ -127,9 +127,13 @@ export interface PropertyChain<Value, Options> {
127
127
  pivotEntity(pivotEntity: () => EntityName): HasKind<Options, 'm:n'> extends true ? PropertyChain<Value, Options> : never;
128
128
  fixedOrder(fixedOrder?: boolean): HasKind<Options, 'm:n'> extends true ? PropertyChain<Value, Options> : never;
129
129
  fixedOrderColumn(fixedOrderColumn: string): HasKind<Options, 'm:n'> extends true ? PropertyChain<Value, Options> : never;
130
- array(): HasKind<Options, 'embedded' | 'enum'> extends true ? PropertyChain<Value, Omit<Options, 'array'> & {
130
+ array(): Options extends {
131
+ kind: infer K extends string;
132
+ } ? K extends 'embedded' | 'enum' ? PropertyChain<Value, Omit<Options, 'array'> & {
131
133
  array: true;
132
- }> : never;
134
+ }> : never : PropertyChain<Value, Omit<Options, 'array'> & {
135
+ array: true;
136
+ }>;
133
137
  prefix(prefix: string | boolean): HasKind<Options, 'embedded'> extends true ? PropertyChain<Value, Options> : never;
134
138
  prefixMode(prefixMode: EmbeddedPrefixMode): HasKind<Options, 'embedded'> extends true ? PropertyChain<Value, Options> : never;
135
139
  object(object?: boolean): HasKind<Options, 'embedded'> extends true ? PropertyChain<Value, Options> : never;
@@ -1646,7 +1646,14 @@ export class MetadataDiscovery {
1646
1646
  return (!Type.getType(t[type]).ensureComparable(meta, prop) && (prop.type === t[type] || brand === type));
1647
1647
  });
1648
1648
  if (type) {
1649
- prop.type = type === 'datetime' ? 'Date' : type;
1649
+ if (prop.array) {
1650
+ // built-in type + array: true — force-create instance for ArrayType wrapping
1651
+ prop.customType = new prop.type();
1652
+ prop.type = prop.customType.constructor.name;
1653
+ }
1654
+ else {
1655
+ prop.type = type === 'datetime' ? 'Date' : type;
1656
+ }
1650
1657
  }
1651
1658
  else {
1652
1659
  prop.customType = new prop.type();
@@ -1656,6 +1663,30 @@ export class MetadataDiscovery {
1656
1663
  if (simple) {
1657
1664
  return;
1658
1665
  }
1666
+ if (prop.array &&
1667
+ prop.customType &&
1668
+ !(prop.customType instanceof t.array) &&
1669
+ !(prop.customType instanceof t.enumArray) &&
1670
+ prop.kind === ReferenceKind.SCALAR) {
1671
+ const innerType = prop.customType;
1672
+ innerType.platform = this.#platform;
1673
+ innerType.meta = meta;
1674
+ innerType.prop = prop;
1675
+ if (!prop.columnTypes) {
1676
+ const arrayDecl = this.#platform.getArrayDeclarationSQL();
1677
+ if (arrayDecl.endsWith('[]')) {
1678
+ // native array support (e.g. postgres) — use inner type's column type with [] suffix
1679
+ prop.columnTypes = [innerType.getColumnType(prop, this.#platform) + '[]'];
1680
+ }
1681
+ else {
1682
+ // non-native (e.g. mysql, sqlite) — store as text/clob
1683
+ prop.columnTypes = [arrayDecl];
1684
+ }
1685
+ }
1686
+ const platform = this.#platform;
1687
+ prop.customType = new t.array(v => innerType.convertToJSValue(v, platform), v => innerType.convertToDatabaseValue(v, platform));
1688
+ prop.type = prop.customType.constructor.name;
1689
+ }
1659
1690
  if (!prop.customType && ['json', 'jsonb'].includes(prop.type?.toLowerCase())) {
1660
1691
  prop.customType = new t.json();
1661
1692
  }
@@ -202,6 +202,12 @@ export interface PropertyOptions<Owner> {
202
202
  * @see https://mikro-orm.io/docs/defining-entities#lazy-scalar-properties
203
203
  */
204
204
  lazy?: boolean;
205
+ /**
206
+ * Marks this property as an array. When used with a custom type, the type is automatically
207
+ * wrapped in an `ArrayType` that delegates element conversion to the inner type.
208
+ * The column type is inferred as `innerType[]` (e.g. `int[]`, `time[]`).
209
+ */
210
+ array?: boolean;
205
211
  /**
206
212
  * Set true to define entity's unique primary key identifier.
207
213
  * Alias for `@PrimaryKey()` decorator
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
- "version": "7.1.0-dev.2",
3
+ "version": "7.1.0-dev.4",
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",
package/utils/Utils.js CHANGED
@@ -141,7 +141,7 @@ export function parseJsonSafe(value) {
141
141
  /** Collection of general-purpose utility methods used throughout the ORM. */
142
142
  export class Utils {
143
143
  static PK_SEPARATOR = '~~~';
144
- static #ORM_VERSION = '7.1.0-dev.2';
144
+ static #ORM_VERSION = '7.1.0-dev.4';
145
145
  /**
146
146
  * Checks if the argument is instance of `Object`. Returns false for arrays.
147
147
  */