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

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 (60) hide show
  1. package/EntityManager.d.ts +63 -12
  2. package/EntityManager.js +221 -40
  3. package/README.md +2 -1
  4. package/connections/Connection.d.ts +29 -0
  5. package/drivers/IDatabaseDriver.d.ts +45 -7
  6. package/entity/BaseEntity.d.ts +68 -1
  7. package/entity/BaseEntity.js +18 -0
  8. package/entity/Collection.d.ts +6 -3
  9. package/entity/Collection.js +15 -4
  10. package/entity/EntityFactory.js +20 -1
  11. package/entity/EntityLoader.d.ts +8 -1
  12. package/entity/EntityLoader.js +89 -28
  13. package/entity/EntityRepository.d.ts +27 -9
  14. package/entity/EntityRepository.js +12 -0
  15. package/entity/Reference.d.ts +42 -1
  16. package/entity/Reference.js +9 -0
  17. package/entity/defineEntity.d.ts +99 -21
  18. package/entity/defineEntity.js +17 -6
  19. package/entity/utils.js +4 -5
  20. package/enums.d.ts +8 -1
  21. package/errors.d.ts +2 -0
  22. package/errors.js +4 -0
  23. package/index.d.ts +2 -2
  24. package/index.js +1 -1
  25. package/metadata/EntitySchema.js +3 -0
  26. package/metadata/MetadataDiscovery.d.ts +12 -0
  27. package/metadata/MetadataDiscovery.js +166 -20
  28. package/metadata/MetadataValidator.d.ts +24 -0
  29. package/metadata/MetadataValidator.js +202 -1
  30. package/metadata/types.d.ts +71 -4
  31. package/naming-strategy/AbstractNamingStrategy.d.ts +1 -1
  32. package/naming-strategy/NamingStrategy.d.ts +1 -1
  33. package/package.json +1 -1
  34. package/platforms/Platform.d.ts +18 -3
  35. package/platforms/Platform.js +58 -6
  36. package/serialization/EntitySerializer.js +2 -1
  37. package/typings.d.ts +202 -22
  38. package/typings.js +51 -14
  39. package/unit-of-work/UnitOfWork.js +15 -4
  40. package/utils/AbstractMigrator.d.ts +20 -5
  41. package/utils/AbstractMigrator.js +263 -28
  42. package/utils/AbstractSchemaGenerator.d.ts +1 -1
  43. package/utils/AbstractSchemaGenerator.js +4 -1
  44. package/utils/Configuration.d.ts +25 -0
  45. package/utils/Configuration.js +1 -0
  46. package/utils/DataloaderUtils.d.ts +10 -1
  47. package/utils/DataloaderUtils.js +78 -0
  48. package/utils/EntityComparator.js +1 -1
  49. package/utils/QueryHelper.d.ts +16 -0
  50. package/utils/QueryHelper.js +15 -0
  51. package/utils/TransactionManager.js +2 -0
  52. package/utils/Utils.js +1 -1
  53. package/utils/fs-utils.d.ts +2 -0
  54. package/utils/fs-utils.js +7 -1
  55. package/utils/index.d.ts +1 -0
  56. package/utils/index.js +1 -0
  57. package/utils/partition-utils.d.ts +17 -0
  58. package/utils/partition-utils.js +79 -0
  59. package/utils/upsert-utils.d.ts +2 -0
  60. package/utils/upsert-utils.js +26 -1
@@ -1,9 +1,52 @@
1
- import type { AnyEntity, Constructor, EntityName, AnyString, CheckCallback, GeneratedColumnCallback, FormulaCallback, FilterQuery, Dictionary, AutoPath, EntityClass, IndexCallback, ObjectQuery, Raw } from '../typings.js';
1
+ import type { AnyEntity, Constructor, EntityName, AnyString, CheckCallback, GeneratedColumnCallback, FormulaCallback, FilterQuery, Dictionary, AutoPath, EntityClass, IndexCallback, ObjectQuery, Raw, SchemaColumns, TriggerDef } from '../typings.js';
2
2
  import type { Cascade, LoadStrategy, DeferMode, QueryOrderMap, EmbeddedPrefixMode } from '../enums.js';
3
3
  import type { Type, types } from '../types/index.js';
4
4
  import type { EntityManager } from '../EntityManager.js';
5
5
  import type { FilterOptions, FindOptions } from '../drivers/IDatabaseDriver.js';
6
6
  import type { SerializeOptions } from '../serialization/EntitySerializer.js';
7
+ /** Supported PostgreSQL partitioning strategies. */
8
+ export type EntityPartitionType = 'hash' | 'list' | 'range';
9
+ /**
10
+ * Partition key expression for PostgreSQL partitioned tables.
11
+ *
12
+ * You can use:
13
+ * - a property name, e.g. `'type'`
14
+ * - a list of property names for composite expressions, e.g. `['tenant', 'createdAt']`
15
+ * - a raw SQL expression string, e.g. `"date_trunc('month', created_at)"`
16
+ * - a callback that receives schema column mappings and returns SQL
17
+ */
18
+ export type EntityPartitionExpression<E = AnyEntity> = (keyof E & string) | readonly (keyof E & string)[] | AnyString | ((columns: SchemaColumns<E>) => string);
19
+ /** Explicit child partition definition for PostgreSQL `list` and `range` partitioning. */
20
+ export interface EntityPartition<E = AnyEntity> {
21
+ /**
22
+ * Optional child table name. Defaults to `<tableName>_<index>`. A single `.` is treated as a
23
+ * `schema.table` separator; names with more than one `.` are rejected to avoid ambiguity.
24
+ */
25
+ name?: string;
26
+ /** Partition bound clause, either as `for values ...` or the trailing part such as `in (...)`, `from ... to ...`, or `default`. */
27
+ values: string;
28
+ }
29
+ /**
30
+ * PostgreSQL table partitioning definition.
31
+ *
32
+ * - `hash` partitions generate child tables automatically from the partition count
33
+ * - `list` and `range` partitions require explicit child partition definitions
34
+ */
35
+ export type EntityPartitionBy<E = AnyEntity> = {
36
+ type: Extract<EntityPartitionType, 'hash'>;
37
+ expression: EntityPartitionExpression<E>;
38
+ /**
39
+ * Hash partition fan-out: either a number (auto-named as `${tableName}_N`) or an array of
40
+ * explicit partition names in remainder order. Names may be `schema.name` to create the
41
+ * child in a non-default schema.
42
+ */
43
+ partitions: number | readonly string[];
44
+ } | {
45
+ type: Exclude<EntityPartitionType, 'hash'>;
46
+ expression: EntityPartitionExpression<E>;
47
+ /** Explicit child partitions created in the order provided. */
48
+ partitions: EntityPartition<E>[];
49
+ };
7
50
  export type EntityOptions<T, E = T extends EntityClass<infer P> ? P : T> = {
8
51
  /** Override default collection/table name. Alias for `collection`. */
9
52
  tableName?: string;
@@ -57,6 +100,15 @@ export type EntityOptions<T, E = T extends EntityClass<infer P> ? P : T> = {
57
100
  };
58
101
  /** Used to make ORM aware of externally defined triggers. This is needed for MS SQL Server multi inserts, ignored in other dialects. */
59
102
  hasTriggers?: boolean;
103
+ /** Database triggers to create for this entity's table. (SQL drivers only) */
104
+ triggers?: TriggerDef<E>[];
105
+ /**
106
+ * PostgreSQL partitioning definition for this table.
107
+ *
108
+ * Partitioned tables are tracked by the schema generator and introspected back from PostgreSQL.
109
+ * Changing an existing partition layout still requires a manual migration.
110
+ */
111
+ partitionBy?: EntityPartitionBy<E>;
60
112
  /** SQL query that maps to a {@doclink virtual-entities | virtual entity}, or for view entities, the view definition. */
61
113
  expression?: string | ((em: any, where: ObjectQuery<E>, options: FindOptions<E, any, any, any>, stream?: boolean) => object);
62
114
  /** Set {@doclink repositories#custom-repository | custom repository class}. */
@@ -329,6 +381,11 @@ export interface PropertyOptions<Owner> {
329
381
  * Specify comment of column for {@link https://mikro-orm.io/docs/schema-generator Schema Generator}. (SQL only)
330
382
  */
331
383
  comment?: string;
384
+ /**
385
+ * Specify column-level collation for {@link https://mikro-orm.io/docs/schema-generator Schema Generator}. (SQL only)
386
+ * Emits a `COLLATE` clause on the column definition; the accepted collation names are dialect-specific.
387
+ */
388
+ collation?: string;
332
389
  /** mysql only */
333
390
  extra?: string;
334
391
  /**
@@ -336,11 +393,11 @@ export interface PropertyOptions<Owner> {
336
393
  *
337
394
  * @see https://mikro-orm.io/docs/defining-entities#sql-generated-columns
338
395
  */
339
- ignoreSchemaChanges?: ('type' | 'extra' | 'default')[];
396
+ ignoreSchemaChanges?: ('type' | 'extra' | 'default' | 'collation')[];
340
397
  }
341
398
  export interface ReferenceOptions<Owner, Target> extends PropertyOptions<Owner> {
342
399
  /** Set target entity type. For polymorphic relations, pass an array of entity types. */
343
- entity?: () => EntityName<Target> | EntityName<Target>[];
400
+ entity?: () => EntityName<Target> | EntityName[];
344
401
  /** Set what actions on owning entity should be cascaded to the relationship. Defaults to [Cascade.PERSIST, Cascade.MERGE] (see {@doclink cascading}). */
345
402
  cascade?: Cascade[];
346
403
  /** Always load the relationship. Discouraged for use with to-many relations for performance reasons. */
@@ -505,7 +562,7 @@ export interface EmbeddableOptions<Owner> {
505
562
  abstract?: boolean;
506
563
  }
507
564
  export interface EnumOptions<T> extends PropertyOptions<T> {
508
- items?: readonly (number | string)[] | (() => Dictionary);
565
+ items?: readonly (number | string)[] | Dictionary | (() => Dictionary);
509
566
  array?: boolean;
510
567
  /** for postgres, by default it uses text column with check constraint */
511
568
  nativeEnumName?: string;
@@ -549,6 +606,16 @@ interface BaseOptions<T, H extends string> {
549
606
  include?: T extends EntityClass<infer P> ? Properties<P, H> : Properties<T, H>;
550
607
  /** Fill factor for the index as a percentage 0-100 (PostgreSQL, MSSQL). */
551
608
  fillFactor?: number;
609
+ /**
610
+ * Predicate for partial indexes. Object form is a `FilterQuery` and is portable across
611
+ * SQL drivers and MongoDB; string form is a raw SQL fragment (SQL drivers only).
612
+ *
613
+ * Native support: PostgreSQL, SQLite, MSSQL (`WHERE`), MongoDB (`partialFilterExpression`).
614
+ * MySQL 8.0.13+ / Oracle: emulated via `CASE WHEN` functional index (uniqueness only enforced
615
+ * where the predicate holds). MariaDB is not supported — it has no inline expression indexes;
616
+ * define a virtual generated column and index that instead.
617
+ */
618
+ where?: string | FilterQuery<NoInfer<T> extends EntityClass<infer P> ? P : NoInfer<T>>;
552
619
  }
553
620
  export interface UniqueOptions<T, H extends string = string> extends BaseOptions<T, H> {
554
621
  deferMode?: DeferMode | `${DeferMode}`;
@@ -4,7 +4,7 @@ import { type ReferenceKind } from '../enums.js';
4
4
  export declare abstract class AbstractNamingStrategy implements NamingStrategy {
5
5
  getClassName(file: string, separator?: string): string;
6
6
  classToMigrationName(timestamp: string, customMigrationName?: string): string;
7
- indexName(tableName: string, columns: string[], type: 'primary' | 'foreign' | 'unique' | 'index' | 'sequence' | 'check' | 'default'): string;
7
+ indexName(tableName: string, columns: string[], type: 'primary' | 'foreign' | 'unique' | 'index' | 'sequence' | 'check' | 'default' | 'trigger'): string;
8
8
  /**
9
9
  * @inheritDoc
10
10
  */
@@ -75,7 +75,7 @@ export interface NamingStrategy {
75
75
  /**
76
76
  * Returns key/constraint name for the given type. Some drivers might not support all the types (e.g. mysql and sqlite enforce the PK name).
77
77
  */
78
- indexName(tableName: string, columns: string[], type: 'primary' | 'foreign' | 'unique' | 'index' | 'sequence' | 'check' | 'default'): string;
78
+ indexName(tableName: string, columns: string[], type: 'primary' | 'foreign' | 'unique' | 'index' | 'sequence' | 'check' | 'default' | 'trigger'): string;
79
79
  /**
80
80
  * Returns alias name for given entity. The alias needs to be unique across the query, which is by default
81
81
  * ensured via appended index parameter. It is optional to use it as long as you ensure it will be unique.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikro-orm/core",
3
- "version": "7.1.0-dev.4",
3
+ "version": "7.1.0-dev.40",
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",
@@ -43,6 +43,8 @@ export declare abstract class Platform {
43
43
  getEnumArrayCheckConstraintExpression(column: string, items: string[]): string | null;
44
44
  /** Whether this platform supports materialized views. */
45
45
  supportsMaterializedViews(): boolean;
46
+ /** Whether this platform supports declarative table partitioning in schema generation. */
47
+ supportsPartitionedTables(): boolean;
46
48
  /** Returns the schema helper instance for this platform, or undefined if not supported. */
47
49
  getSchemaHelper(): unknown;
48
50
  /** Whether the platform automatically creates indexes on foreign key columns. */
@@ -168,7 +170,7 @@ export declare abstract class Platform {
168
170
  getUuidTypeDeclarationSQL(column: {
169
171
  length?: number;
170
172
  }): string;
171
- /** Extracts the base type name from a full SQL type declaration (e.g. "varchar(255)" -> "varchar"). */
173
+ /** Extracts the base type name from a full SQL type declaration (e.g. `varchar(255)` -> `varchar`). */
172
174
  extractSimpleType(type: string): string;
173
175
  /**
174
176
  * This should be used only to compare types, it can strip some information like the length.
@@ -211,6 +213,11 @@ export declare abstract class Platform {
211
213
  getFullTextWhereClause(prop: EntityProperty): string;
212
214
  supportsCreatingFullTextIndex(): boolean;
213
215
  getFullTextIndexExpression(indexName: string, schemaName: string | undefined, tableName: string, columns: SimpleColumnMeta[]): string;
216
+ /**
217
+ * Generates the SQL index hint clause for the given index names.
218
+ * Returns `undefined` when the platform does not support index hints (e.g. PostgreSQL, SQLite).
219
+ */
220
+ formatIndexHint(indexNames: string[]): string | undefined;
214
221
  /** Whether the driver automatically parses JSON columns into JS objects. */
215
222
  convertsJsonAutomatically(): boolean;
216
223
  /** Converts a JS value to its JSON database representation (typically JSON.stringify). */
@@ -271,9 +278,17 @@ export declare abstract class Platform {
271
278
  /** Whether the platform supports unsigned integer columns. */
272
279
  supportsUnsigned(): boolean;
273
280
  /**
274
- * Returns the default name of index for the given columns
281
+ * Maximum length of identifiers (table, column, index, constraint, …) the platform supports.
282
+ * Names produced by {@link getIndexName} above this limit are hash-truncated. Defaults to
283
+ * `Infinity` (no truncation); SQL platforms override with their engine's limit
284
+ * (PG 63, MySQL/MariaDB 64, MSSQL/Oracle 128).
285
+ */
286
+ getMaxIdentifierLength(): number;
287
+ /**
288
+ * Returns the default name of index for the given columns, hash-truncated if it would
289
+ * exceed {@link getMaxIdentifierLength}.
275
290
  */
276
- getIndexName(tableName: string, columns: string[], type: 'index' | 'unique' | 'foreign' | 'primary' | 'sequence'): string;
291
+ getIndexName(tableName: string, columns: string[], type: 'index' | 'unique' | 'foreign' | 'primary' | 'sequence' | 'check'): string;
277
292
  /** Returns the default primary key constraint name. */
278
293
  getDefaultPrimaryName(tableName: string, columns: string[]): string;
279
294
  /** Whether the platform supports custom names for primary key constraints. */
@@ -2,12 +2,22 @@ import { clone } from '../utils/clone.js';
2
2
  import { EntityRepository } from '../entity/EntityRepository.js';
3
3
  import { UnderscoreNamingStrategy } from '../naming-strategy/UnderscoreNamingStrategy.js';
4
4
  import { ExceptionConverter } from './ExceptionConverter.js';
5
+ import { MetadataError } from '../errors.js';
5
6
  import { ArrayType, BigIntType, BlobType, BooleanType, CharacterType, DateTimeType, DateType, DecimalType, DoubleType, EnumType, FloatType, IntegerType, IntervalType, JsonType, MediumIntType, SmallIntType, StringType, TextType, TimeType, TinyIntType, Type, Uint8ArrayType, UnknownType, UuidType, } from '../types/index.js';
6
7
  import { parseJsonSafe, Utils } from '../utils/Utils.js';
7
8
  import { ReferenceKind } from '../enums.js';
8
9
  import { Raw } from '../utils/RawQueryFragment.js';
9
10
  /** Symbol used to tag cloned embeddable data for JSON serialization handling. */
10
11
  export const JsonProperty = Symbol('JsonProperty');
12
+ const MULTI_WORD_SQL_TYPES = [
13
+ 'timestamp with time zone',
14
+ 'timestamp without time zone',
15
+ 'time with time zone',
16
+ 'time without time zone',
17
+ 'character varying',
18
+ 'double precision',
19
+ 'bit varying',
20
+ ];
11
21
  /** Abstract base class providing database-specific behavior and SQL dialect differences. */
12
22
  export class Platform {
13
23
  exceptionConverter = new ExceptionConverter();
@@ -62,6 +72,10 @@ export class Platform {
62
72
  supportsMaterializedViews() {
63
73
  return false;
64
74
  }
75
+ /** Whether this platform supports declarative table partitioning in schema generation. */
76
+ supportsPartitionedTables() {
77
+ return false;
78
+ }
65
79
  /** Returns the schema helper instance for this platform, or undefined if not supported. */
66
80
  getSchemaHelper() {
67
81
  return undefined;
@@ -222,9 +236,15 @@ export class Platform {
222
236
  column.length ??= 36;
223
237
  return this.getVarcharTypeDeclarationSQL(column);
224
238
  }
225
- /** Extracts the base type name from a full SQL type declaration (e.g. "varchar(255)" -> "varchar"). */
239
+ /** Extracts the base type name from a full SQL type declaration (e.g. `varchar(255)` -> `varchar`). */
226
240
  extractSimpleType(type) {
227
- return /[^(), ]+/.exec(type.toLowerCase())[0];
241
+ const lower = type.toLowerCase();
242
+ for (const multiWord of MULTI_WORD_SQL_TYPES) {
243
+ if (lower.startsWith(multiWord)) {
244
+ return multiWord;
245
+ }
246
+ }
247
+ return /[^(), ]+/.exec(lower)[0];
228
248
  }
229
249
  /**
230
250
  * This should be used only to compare types, it can strip some information like the length.
@@ -248,6 +268,7 @@ export class Platform {
248
268
  return Type.getType(CharacterType);
249
269
  case 'string':
250
270
  case 'varchar':
271
+ case 'character varying':
251
272
  return Type.getType(StringType);
252
273
  case 'interval':
253
274
  return Type.getType(IntervalType);
@@ -267,6 +288,7 @@ export class Platform {
267
288
  case 'float':
268
289
  return Type.getType(FloatType);
269
290
  case 'double':
291
+ case 'double precision':
270
292
  return Type.getType(DoubleType);
271
293
  case 'integer':
272
294
  return Type.getType(IntegerType);
@@ -286,8 +308,12 @@ export class Platform {
286
308
  return Type.getType(DateType);
287
309
  case 'datetime':
288
310
  case 'timestamp':
311
+ case 'timestamp with time zone':
312
+ case 'timestamp without time zone':
289
313
  return Type.getType(DateTimeType);
290
314
  case 'time':
315
+ case 'time with time zone':
316
+ case 'time without time zone':
291
317
  return Type.getType(TimeType);
292
318
  case 'object':
293
319
  case 'json':
@@ -415,6 +441,13 @@ export class Platform {
415
441
  getFullTextIndexExpression(indexName, schemaName, tableName, columns) {
416
442
  throw new Error('Full text searching is not supported by this driver.');
417
443
  }
444
+ /**
445
+ * Generates the SQL index hint clause for the given index names.
446
+ * Returns `undefined` when the platform does not support index hints (e.g. PostgreSQL, SQLite).
447
+ */
448
+ formatIndexHint(indexNames) {
449
+ return undefined;
450
+ }
418
451
  /** Whether the driver automatically parses JSON columns into JS objects. */
419
452
  convertsJsonAutomatically() {
420
453
  return true;
@@ -506,7 +539,8 @@ export class Platform {
506
539
  if (raw) {
507
540
  return this.formatQuery(raw.sql, raw.params);
508
541
  }
509
- return `${quote}${id.toString().replace('.', `${quote}.${quote}`)}${quote}`;
542
+ const escaped = id.toString().replaceAll(quote, quote + quote);
543
+ return `${quote}${escaped.replace('.', `${quote}.${quote}`)}${quote}`;
510
544
  }
511
545
  /** Quotes a literal value for safe embedding in SQL. */
512
546
  quoteValue(value) {
@@ -596,10 +630,26 @@ export class Platform {
596
630
  return false;
597
631
  }
598
632
  /**
599
- * Returns the default name of index for the given columns
633
+ * Maximum length of identifiers (table, column, index, constraint, …) the platform supports.
634
+ * Names produced by {@link getIndexName} above this limit are hash-truncated. Defaults to
635
+ * `Infinity` (no truncation); SQL platforms override with their engine's limit
636
+ * (PG 63, MySQL/MariaDB 64, MSSQL/Oracle 128).
637
+ */
638
+ getMaxIdentifierLength() {
639
+ return Infinity;
640
+ }
641
+ /**
642
+ * Returns the default name of index for the given columns, hash-truncated if it would
643
+ * exceed {@link getMaxIdentifierLength}.
600
644
  */
601
645
  getIndexName(tableName, columns, type) {
602
- return this.namingStrategy.indexName(tableName, columns, type);
646
+ const indexName = this.namingStrategy.indexName(tableName, columns, type);
647
+ const max = this.getMaxIdentifierLength();
648
+ if (indexName.length > max) {
649
+ const suffix = type === 'primary' ? 'pkey' : type;
650
+ return `${indexName.substring(0, max - 8 - type.length)}_${Utils.hash(indexName, 5)}_${suffix}`;
651
+ }
652
+ return indexName;
603
653
  }
604
654
  /** Returns the default primary key constraint name. */
605
655
  getDefaultPrimaryName(tableName, columns) {
@@ -651,7 +701,9 @@ export class Platform {
651
701
  }
652
702
  /** Platform-specific validation of entity metadata. */
653
703
  validateMetadata(meta) {
654
- return;
704
+ if (meta.partitionBy && !this.supportsPartitionedTables()) {
705
+ throw new MetadataError(`Entity ${meta.className} uses partitionBy, but ${this.constructor.name} does not support partitioned tables`);
706
+ }
655
707
  }
656
708
  /**
657
709
  * Generates a custom order by statement given a set of in order values, eg.
@@ -16,7 +16,8 @@ function isVisible(meta, propName, options) {
16
16
  if (Array.isArray(options.fields) && options.fields.length > 0 && !matchesPath(options.fields, propName)) {
17
17
  return false;
18
18
  }
19
- if (Array.isArray(options.populate) && matchesPath(options.populate, propName)) {
19
+ if (Array.isArray(options.populate) &&
20
+ options.populate.find(item => item === propName || item.startsWith(propName + '.'))) {
20
21
  return true;
21
22
  }
22
23
  if (options.exclude?.find(item => item === propName)) {