@mikro-orm/core 7.0.0-dev.99 → 7.0.0-rc.1

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 (107) hide show
  1. package/EntityManager.d.ts +34 -17
  2. package/EntityManager.js +95 -103
  3. package/MikroORM.d.ts +5 -5
  4. package/MikroORM.js +25 -20
  5. package/cache/FileCacheAdapter.js +11 -3
  6. package/connections/Connection.d.ts +3 -2
  7. package/connections/Connection.js +4 -3
  8. package/drivers/DatabaseDriver.d.ts +11 -11
  9. package/drivers/DatabaseDriver.js +91 -25
  10. package/drivers/IDatabaseDriver.d.ts +50 -20
  11. package/entity/BaseEntity.d.ts +61 -1
  12. package/entity/Collection.d.ts +8 -1
  13. package/entity/Collection.js +12 -13
  14. package/entity/EntityAssigner.js +9 -9
  15. package/entity/EntityFactory.d.ts +6 -1
  16. package/entity/EntityFactory.js +40 -22
  17. package/entity/EntityHelper.d.ts +2 -2
  18. package/entity/EntityHelper.js +27 -4
  19. package/entity/EntityLoader.d.ts +5 -4
  20. package/entity/EntityLoader.js +193 -80
  21. package/entity/EntityRepository.d.ts +27 -7
  22. package/entity/EntityRepository.js +8 -2
  23. package/entity/PolymorphicRef.d.ts +12 -0
  24. package/entity/PolymorphicRef.js +18 -0
  25. package/entity/WrappedEntity.d.ts +2 -2
  26. package/entity/WrappedEntity.js +1 -1
  27. package/entity/defineEntity.d.ts +89 -50
  28. package/entity/defineEntity.js +12 -0
  29. package/entity/index.d.ts +1 -0
  30. package/entity/index.js +1 -0
  31. package/entity/utils.d.ts +6 -1
  32. package/entity/utils.js +33 -0
  33. package/entity/validators.js +2 -2
  34. package/enums.d.ts +2 -2
  35. package/enums.js +1 -0
  36. package/errors.d.ts +16 -8
  37. package/errors.js +40 -13
  38. package/hydration/ObjectHydrator.js +63 -21
  39. package/index.d.ts +1 -1
  40. package/logging/colors.d.ts +1 -1
  41. package/logging/colors.js +7 -6
  42. package/logging/inspect.js +1 -6
  43. package/metadata/EntitySchema.d.ts +43 -13
  44. package/metadata/EntitySchema.js +82 -27
  45. package/metadata/MetadataDiscovery.d.ts +60 -3
  46. package/metadata/MetadataDiscovery.js +665 -154
  47. package/metadata/MetadataProvider.js +3 -1
  48. package/metadata/MetadataStorage.d.ts +13 -6
  49. package/metadata/MetadataStorage.js +64 -19
  50. package/metadata/MetadataValidator.d.ts +32 -2
  51. package/metadata/MetadataValidator.js +196 -31
  52. package/metadata/discover-entities.js +5 -5
  53. package/metadata/types.d.ts +111 -14
  54. package/naming-strategy/AbstractNamingStrategy.d.ts +11 -3
  55. package/naming-strategy/AbstractNamingStrategy.js +12 -0
  56. package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
  57. package/naming-strategy/EntityCaseNamingStrategy.js +6 -5
  58. package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
  59. package/naming-strategy/MongoNamingStrategy.js +6 -6
  60. package/naming-strategy/NamingStrategy.d.ts +17 -3
  61. package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
  62. package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
  63. package/package.json +2 -2
  64. package/platforms/Platform.d.ts +4 -2
  65. package/platforms/Platform.js +5 -2
  66. package/serialization/EntitySerializer.d.ts +3 -0
  67. package/serialization/EntitySerializer.js +15 -13
  68. package/serialization/EntityTransformer.js +6 -6
  69. package/serialization/SerializationContext.d.ts +6 -6
  70. package/typings.d.ts +325 -110
  71. package/typings.js +84 -17
  72. package/unit-of-work/ChangeSet.d.ts +4 -3
  73. package/unit-of-work/ChangeSet.js +2 -3
  74. package/unit-of-work/ChangeSetComputer.d.ts +3 -6
  75. package/unit-of-work/ChangeSetComputer.js +34 -13
  76. package/unit-of-work/ChangeSetPersister.d.ts +12 -10
  77. package/unit-of-work/ChangeSetPersister.js +55 -25
  78. package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
  79. package/unit-of-work/CommitOrderCalculator.js +13 -13
  80. package/unit-of-work/IdentityMap.d.ts +12 -0
  81. package/unit-of-work/IdentityMap.js +39 -1
  82. package/unit-of-work/UnitOfWork.d.ts +21 -3
  83. package/unit-of-work/UnitOfWork.js +203 -56
  84. package/utils/AbstractSchemaGenerator.js +17 -8
  85. package/utils/AsyncContext.d.ts +6 -0
  86. package/utils/AsyncContext.js +42 -0
  87. package/utils/Configuration.d.ts +52 -11
  88. package/utils/Configuration.js +12 -8
  89. package/utils/Cursor.js +21 -8
  90. package/utils/DataloaderUtils.js +13 -11
  91. package/utils/EntityComparator.d.ts +14 -7
  92. package/utils/EntityComparator.js +132 -46
  93. package/utils/QueryHelper.d.ts +16 -6
  94. package/utils/QueryHelper.js +53 -18
  95. package/utils/RawQueryFragment.d.ts +28 -23
  96. package/utils/RawQueryFragment.js +34 -56
  97. package/utils/RequestContext.js +2 -2
  98. package/utils/TransactionContext.js +2 -2
  99. package/utils/TransactionManager.js +1 -1
  100. package/utils/Utils.d.ts +7 -26
  101. package/utils/Utils.js +25 -79
  102. package/utils/clone.js +7 -21
  103. package/utils/env-vars.d.ts +4 -0
  104. package/utils/env-vars.js +13 -3
  105. package/utils/fs-utils.d.ts +21 -0
  106. package/utils/fs-utils.js +106 -11
  107. package/utils/upsert-utils.d.ts +4 -4
@@ -40,7 +40,7 @@ export class AbstractSchemaGenerator {
40
40
  }
41
41
  async clear(options) {
42
42
  for (const meta of this.getOrderedMetadata(options?.schema).reverse()) {
43
- await this.driver.nativeDelete(meta.className, {}, options);
43
+ await this.driver.nativeDelete(meta.class, {}, options);
44
44
  }
45
45
  if (options?.clearIdentityMap ?? true) {
46
46
  this.clearIdentityMap();
@@ -90,21 +90,30 @@ export class AbstractSchemaGenerator {
90
90
  this.notImplemented();
91
91
  }
92
92
  getOrderedMetadata(schema) {
93
- const metadata = Object.values(this.metadata.getAll()).filter(meta => {
94
- const isRootEntity = meta.root.className === meta.className;
95
- return isRootEntity && !meta.embeddable && !meta.virtual;
93
+ const metadata = [...this.metadata.getAll().values()].filter(meta => {
94
+ const isRootEntity = meta.root.class === meta.class;
95
+ const isTPTChild = meta.inheritanceType === 'tpt' && meta.tptParent;
96
+ return (isRootEntity || isTPTChild) && !meta.embeddable && !meta.virtual;
96
97
  });
97
98
  const calc = new CommitOrderCalculator();
98
- metadata.forEach(meta => calc.addNode(meta.root.className));
99
+ metadata.forEach(meta => {
100
+ const nodeId = meta.inheritanceType === 'tpt' && meta.tptParent ? meta._id : meta.root._id;
101
+ calc.addNode(nodeId);
102
+ });
99
103
  let meta = metadata.pop();
100
104
  while (meta) {
101
- for (const prop of meta.props) {
102
- calc.discoverProperty(prop, meta.root.className);
105
+ const nodeId = meta.inheritanceType === 'tpt' && meta.tptParent ? meta._id : meta.root._id;
106
+ for (const prop of meta.relations) {
107
+ calc.discoverProperty(prop, nodeId);
108
+ }
109
+ if (meta.inheritanceType === 'tpt' && meta.tptParent) {
110
+ const parentId = meta.tptParent._id;
111
+ calc.addDependency(parentId, nodeId, 1);
103
112
  }
104
113
  meta = metadata.pop();
105
114
  }
106
115
  return calc.sort()
107
- .map(cls => this.metadata.find(cls))
116
+ .map(cls => this.metadata.getById(cls))
108
117
  .filter(meta => {
109
118
  const targetSchema = meta.schema ?? this.config.get('schema', this.platform.getDefaultSchemaName());
110
119
  return schema ? [schema, '*'].includes(targetSchema) : meta.schema !== '*';
@@ -0,0 +1,6 @@
1
+ export interface AsyncContext<T> {
2
+ getStore(): T | undefined;
3
+ run<R>(store: T, callback: () => R): R;
4
+ enterWith(store: T): void;
5
+ }
6
+ export declare function createAsyncContext<T>(): AsyncContext<T>;
@@ -0,0 +1,42 @@
1
+ function getNodeAsyncContext() {
2
+ const mod = globalThis.process?.getBuiltinModule?.('node:async_hooks');
3
+ /* v8 ignore next */
4
+ if (!mod?.AsyncLocalStorage) {
5
+ throw new Error('AsyncLocalStorage not available');
6
+ }
7
+ return new mod.AsyncLocalStorage();
8
+ }
9
+ /* v8 ignore next */
10
+ function createFallbackAsyncContext() {
11
+ let store;
12
+ // eslint-disable-next-line no-console
13
+ console.warn('AsyncLocalStorage not available');
14
+ return {
15
+ getStore: () => store,
16
+ enterWith: value => store = value,
17
+ run: (value, cb) => {
18
+ const prev = store;
19
+ store = value;
20
+ try {
21
+ return cb();
22
+ }
23
+ finally {
24
+ store = prev;
25
+ }
26
+ },
27
+ };
28
+ }
29
+ export function createAsyncContext() {
30
+ /* v8 ignore next */
31
+ const ALS = globalThis.AsyncLocalStorage;
32
+ /* v8 ignore next */
33
+ if (typeof ALS === 'function' && ALS.prototype?.run) {
34
+ return new ALS();
35
+ }
36
+ /* v8 ignore else */
37
+ if (globalThis.process?.versions?.node) {
38
+ return getNodeAsyncContext();
39
+ }
40
+ /* v8 ignore next */
41
+ return createFallbackAsyncContext();
42
+ }
@@ -1,7 +1,7 @@
1
1
  import type { NamingStrategy } from '../naming-strategy/NamingStrategy.js';
2
2
  import { type CacheAdapter, type SyncCacheAdapter } from '../cache/CacheAdapter.js';
3
3
  import type { EntityRepository } from '../entity/EntityRepository.js';
4
- import type { AnyEntity, Constructor, Dictionary, EnsureDatabaseOptions, EntityClass, EntityMetadata, FilterDef, GenerateOptions, Highlighter, HydratorConstructor, IHydrator, IMigrationGenerator, IPrimaryKey, MaybePromise, Migration, MigrationObject } from '../typings.js';
4
+ import type { AnyEntity, CompiledFunctions, Constructor, Dictionary, EnsureDatabaseOptions, EntityClass, EntityMetadata, FilterDef, GenerateOptions, Highlighter, HydratorConstructor, IHydrator, IMigrationGenerator, IPrimaryKey, MaybePromise, Migration, MigrationObject } from '../typings.js';
5
5
  import { ObjectHydrator } from '../hydration/ObjectHydrator.js';
6
6
  import { NullHighlighter } from '../utils/NullHighlighter.js';
7
7
  import { type Logger, type LoggerNamespace, type LoggerOptions } from '../logging/Logger.js';
@@ -35,7 +35,7 @@ declare const DEFAULTS: {
35
35
  readonly inferDefaultValues: true;
36
36
  };
37
37
  readonly validateRequired: true;
38
- readonly context: (name: string) => EntityManager<IDatabaseDriver<import("@mikro-orm/sql").Connection>> | undefined;
38
+ readonly context: (name: string) => EntityManager<IDatabaseDriver<import("../index.js").Connection>> | undefined;
39
39
  readonly contextName: "default";
40
40
  readonly allowGlobalContext: false;
41
41
  readonly logger: (message?: any, ...optionalParams: any[]) => void;
@@ -69,6 +69,7 @@ declare const DEFAULTS: {
69
69
  readonly upsertManaged: true;
70
70
  readonly forceEntityConstructor: false;
71
71
  readonly forceUndefined: false;
72
+ readonly forceUtcTimezone: true;
72
73
  readonly processOnCreateHooksEarly: true;
73
74
  readonly ensureDatabase: true;
74
75
  readonly ensureIndexes: false;
@@ -82,7 +83,6 @@ declare const DEFAULTS: {
82
83
  readonly glob: "!(*.d).{js,ts,cjs}";
83
84
  readonly silent: false;
84
85
  readonly transactional: true;
85
- readonly disableForeignKeys: false;
86
86
  readonly allOrNothing: true;
87
87
  readonly dropTables: true;
88
88
  readonly safe: false;
@@ -91,10 +91,10 @@ declare const DEFAULTS: {
91
91
  readonly fileName: (timestamp: string, name?: string) => string;
92
92
  };
93
93
  readonly schemaGenerator: {
94
- readonly disableForeignKeys: false;
95
94
  readonly createForeignKeyConstraints: true;
96
95
  readonly ignoreSchema: readonly [];
97
96
  readonly skipTables: readonly [];
97
+ readonly skipViews: readonly [];
98
98
  readonly skipColumns: {};
99
99
  };
100
100
  readonly embeddables: {
@@ -256,6 +256,21 @@ export interface ConnectionOptions {
256
256
  driverOptions?: Dictionary;
257
257
  /** Callback to execute when a new connection is created. */
258
258
  onCreateConnection?: (connection: unknown) => Promise<void>;
259
+ /**
260
+ * SQLite/libSQL: databases to attach on connection.
261
+ * Each attached database acts as a schema, accessible via `schema.table` syntax.
262
+ * Entities can reference attached databases via `@Entity({ schema: 'db_name' })`.
263
+ * Note: Not supported for remote libSQL connections.
264
+ * @example
265
+ * attachDatabases: [
266
+ * { name: 'users_db', path: './users.db' },
267
+ * { name: 'logs_db', path: '/var/data/logs.db' },
268
+ * ]
269
+ */
270
+ attachDatabases?: {
271
+ name: string;
272
+ path: string;
273
+ }[];
259
274
  }
260
275
  /**
261
276
  * Configuration options for database migrations.
@@ -402,11 +417,6 @@ export interface MetadataDiscoveryOptions {
402
417
  * @default true
403
418
  */
404
419
  checkDuplicateFieldNames?: boolean;
405
- /**
406
- * Check for duplicate entities and throw an error if found.
407
- * @default true
408
- */
409
- checkDuplicateEntities?: boolean;
410
420
  /**
411
421
  * Check for composite primary keys marked as `persist: false` and throw an error if found.
412
422
  * @default true
@@ -628,9 +638,9 @@ export interface Options<Driver extends IDatabaseDriver = IDatabaseDriver, EM ex
628
638
  processOnCreateHooksEarly?: boolean;
629
639
  /**
630
640
  * Force `Date` values to be stored in UTC for datetime columns without timezone.
631
- * Works for MySQL (`datetime` type) and PostgreSQL (`timestamp` type).
641
+ * Works for MySQL (`datetime` type), PostgreSQL (`timestamp` type), and MSSQL (`datetime`/`datetime2` types).
632
642
  * SQLite does this by default.
633
- * @default false
643
+ * @default true
634
644
  */
635
645
  forceUtcTimezone?: boolean;
636
646
  /**
@@ -670,6 +680,12 @@ export interface Options<Driver extends IDatabaseDriver = IDatabaseDriver, EM ex
670
680
  * @default ObjectHydrator
671
681
  */
672
682
  hydrator?: HydratorConstructor;
683
+ /**
684
+ * Pre-generated compiled functions for hydration and comparison.
685
+ * Use the `compile` CLI command to create these functions.
686
+ * Enables deployment to runtimes that prohibit `new Function`/eval (e.g. Cloudflare Workers).
687
+ */
688
+ compiledFunctions?: CompiledFunctions;
673
689
  /**
674
690
  * Default loading strategy for relations.
675
691
  * - `'joined'`: Use SQL JOINs (single query, may cause cartesian product)
@@ -741,6 +757,12 @@ export interface Options<Driver extends IDatabaseDriver = IDatabaseDriver, EM ex
741
757
  * @default false
742
758
  */
743
759
  allowGlobalContext?: boolean;
760
+ /**
761
+ * When enabled, environment variables take precedence over explicitly provided config options.
762
+ * By default, explicit options win over env vars.
763
+ * @default false
764
+ */
765
+ preferEnvVars?: boolean;
744
766
  /**
745
767
  * Disable the identity map.
746
768
  * When disabled, each query returns new entity instances.
@@ -824,6 +846,10 @@ export interface Options<Driver extends IDatabaseDriver = IDatabaseDriver, EM ex
824
846
  * @default false
825
847
  */
826
848
  disableForeignKeys?: boolean;
849
+ /**
850
+ * Try to disable foreign key checks during `schema.clear()`. Enabled by default for MySQL/MariaDB.
851
+ */
852
+ disableForeignKeysForClear?: boolean;
827
853
  /**
828
854
  * Generate foreign key constraints.
829
855
  * @default true
@@ -839,6 +865,11 @@ export interface Options<Driver extends IDatabaseDriver = IDatabaseDriver, EM ex
839
865
  * @default []
840
866
  */
841
867
  skipTables?: (string | RegExp)[];
868
+ /**
869
+ * View names or patterns to skip during schema generation (e.g. PostGIS system views).
870
+ * @default []
871
+ */
872
+ skipViews?: (string | RegExp)[];
842
873
  /**
843
874
  * Column names or patterns to skip during schema generation, keyed by table name.
844
875
  * @default {}
@@ -848,6 +879,16 @@ export interface Options<Driver extends IDatabaseDriver = IDatabaseDriver, EM ex
848
879
  * Database name to use for management operations (e.g., creating/dropping databases).
849
880
  */
850
881
  managementDbName?: string;
882
+ /**
883
+ * Default ON UPDATE rule for foreign keys.
884
+ * When not set, no rule is emitted and the database uses its native default (NO ACTION/RESTRICT).
885
+ */
886
+ defaultUpdateRule?: 'cascade' | 'no action' | 'set null' | 'set default' | 'restrict';
887
+ /**
888
+ * Default ON DELETE rule for foreign keys.
889
+ * When not set, no rule is emitted and the database uses its native default (NO ACTION/RESTRICT).
890
+ */
891
+ defaultDeleteRule?: 'cascade' | 'no action' | 'set null' | 'set default' | 'restrict';
851
892
  };
852
893
  /**
853
894
  * Embeddable entity configuration options.
@@ -10,6 +10,7 @@ import { RequestContext } from './RequestContext.js';
10
10
  import { DataloaderType, FlushMode, LoadStrategy, PopulateHint } from '../enums.js';
11
11
  import { MemoryCacheAdapter } from '../cache/MemoryCacheAdapter.js';
12
12
  import { EntityComparator } from './EntityComparator.js';
13
+ import { setEnv } from './env-vars.js';
13
14
  const DEFAULTS = {
14
15
  pool: {},
15
16
  entities: [],
@@ -34,7 +35,7 @@ const DEFAULTS = {
34
35
  colors: true,
35
36
  findOneOrFailHandler: (entityName, where) => NotFoundError.findOneFailed(entityName, where),
36
37
  findExactlyOneOrFailHandler: (entityName, where) => NotFoundError.findExactlyOneFailed(entityName, where),
37
- baseDir: process.cwd(),
38
+ baseDir: globalThis.process?.cwd?.(),
38
39
  hydrator: ObjectHydrator,
39
40
  flushMode: FlushMode.AUTO,
40
41
  loadStrategy: LoadStrategy.BALANCED,
@@ -61,6 +62,7 @@ const DEFAULTS = {
61
62
  upsertManaged: true,
62
63
  forceEntityConstructor: false,
63
64
  forceUndefined: false,
65
+ forceUtcTimezone: true,
64
66
  processOnCreateHooksEarly: true,
65
67
  ensureDatabase: true,
66
68
  ensureIndexes: false,
@@ -74,7 +76,6 @@ const DEFAULTS = {
74
76
  glob: '!(*.d).{js,ts,cjs}',
75
77
  silent: false,
76
78
  transactional: true,
77
- disableForeignKeys: false,
78
79
  allOrNothing: true,
79
80
  dropTables: true,
80
81
  safe: false,
@@ -83,10 +84,10 @@ const DEFAULTS = {
83
84
  fileName: (timestamp, name) => `Migration${timestamp}${name ? '_' + name : ''}`,
84
85
  },
85
86
  schemaGenerator: {
86
- disableForeignKeys: false,
87
87
  createForeignKeyConstraints: true,
88
88
  ignoreSchema: [],
89
89
  skipTables: [],
90
+ skipViews: [],
90
91
  skipColumns: {},
91
92
  },
92
93
  embeddables: {
@@ -135,10 +136,9 @@ export class Configuration {
135
136
  extensions = new Map();
136
137
  constructor(options, validate = true) {
137
138
  if (options.dynamicImportProvider) {
138
- Utils.dynamicImportProvider = options.dynamicImportProvider;
139
+ globalThis.dynamicImportProvider = options.dynamicImportProvider;
139
140
  }
140
141
  this.options = Utils.mergeConfig({}, DEFAULTS, options);
141
- this.options.baseDir = Utils.absolutePath(this.options.baseDir);
142
142
  if (validate) {
143
143
  this.validateOptions();
144
144
  }
@@ -150,6 +150,10 @@ export class Configuration {
150
150
  highlighter: this.options.highlighter,
151
151
  writer: this.options.logger,
152
152
  });
153
+ const cf = this.options.compiledFunctions;
154
+ if (cf && cf.__version !== Utils.getORMVersion()) {
155
+ this.logger.warn('discovery', `Compiled functions were generated with MikroORM v${cf.__version ?? 'unknown'}, but the current version is v${Utils.getORMVersion()}. Please regenerate with \`npx mikro-orm compile\`.`);
156
+ }
153
157
  if (this.options.driver) {
154
158
  this.driver = new this.options.driver(this);
155
159
  this.platform = this.driver.getPlatform();
@@ -240,7 +244,7 @@ export class Configuration {
240
244
  * Gets instance of Comparator. (cached)
241
245
  */
242
246
  getComparator(metadata) {
243
- return this.getCachedService(EntityComparator, metadata, this.platform);
247
+ return this.getCachedService(EntityComparator, metadata, this.platform, this);
244
248
  }
245
249
  /**
246
250
  * Gets instance of MetadataProvider. (cached)
@@ -293,7 +297,7 @@ export class Configuration {
293
297
  metadataCache.enabled ??= useCache;
294
298
  this.options.clientUrl ??= this.platform.getDefaultClientUrl();
295
299
  this.options.implicitTransactions ??= this.platform.usesImplicitTransactions();
296
- if (metadataCache.enabled && !metadataCache.adapter) {
300
+ if (validate && metadataCache.enabled && !metadataCache.adapter) {
297
301
  throw new Error('No metadata cache adapter specified, please fill in `metadataCache.adapter` option or use the async MikroORM.init() method which can autoload it.');
298
302
  }
299
303
  try {
@@ -328,7 +332,7 @@ export class Configuration {
328
332
  }
329
333
  }
330
334
  sync() {
331
- process.env.MIKRO_ORM_COLORS = '' + this.options.colors;
335
+ setEnv('MIKRO_ORM_COLORS', this.options.colors);
332
336
  this.logger.setDebugMode(this.options.debug);
333
337
  }
334
338
  validateOptions() {
package/utils/Cursor.js CHANGED
@@ -2,7 +2,7 @@ import { Utils } from './Utils.js';
2
2
  import { ReferenceKind } from '../enums.js';
3
3
  import { Reference } from '../entity/Reference.js';
4
4
  import { helper } from '../entity/wrap.js';
5
- import { RawQueryFragment } from '../utils/RawQueryFragment.js';
5
+ import { Raw } from '../utils/RawQueryFragment.js';
6
6
  import { CursorError } from '../errors.js';
7
7
  import { inspect } from '../logging/inspect.js';
8
8
  /**
@@ -95,15 +95,22 @@ export class Cursor {
95
95
  from(entity) {
96
96
  const processEntity = (entity, prop, direction, object = false) => {
97
97
  if (Utils.isPlainObject(direction)) {
98
+ const unwrapped = Reference.unwrapReference(entity[prop]);
99
+ // Check if the relation is loaded - for nested properties, undefined means not populated
100
+ if (Utils.isEntity(unwrapped) && !helper(unwrapped).isInitialized()) {
101
+ throw CursorError.entityNotPopulated(entity, prop);
102
+ }
98
103
  return Utils.keys(direction).reduce((o, key) => {
99
- Object.assign(o, processEntity(Reference.unwrapReference(entity[prop]), key, direction[key], true));
104
+ Object.assign(o, processEntity(unwrapped, key, direction[key], true));
100
105
  return o;
101
106
  }, {});
102
107
  }
103
- if (entity[prop] == null) {
104
- throw CursorError.entityNotPopulated(entity, prop);
105
- }
106
108
  let value = entity[prop];
109
+ // Allow null/undefined values in cursor - they will be handled in createCursorCondition
110
+ // undefined can occur with forceUndefined config option which converts null to undefined
111
+ if (value == null) {
112
+ return object ? { [prop]: null } : null;
113
+ }
107
114
  if (Utils.isEntity(value, true)) {
108
115
  value = helper(value).getPrimaryKey();
109
116
  }
@@ -131,7 +138,13 @@ export class Cursor {
131
138
  */
132
139
  static for(meta, entity, orderBy) {
133
140
  const definition = this.getDefinition(meta, orderBy);
134
- return Cursor.encode(definition.map(([key]) => entity[key]));
141
+ return Cursor.encode(definition.map(([key]) => {
142
+ const value = entity[key];
143
+ if (value === undefined) {
144
+ throw CursorError.missingValue(meta.className, key);
145
+ }
146
+ return value;
147
+ }));
135
148
  }
136
149
  static encode(value) {
137
150
  return Buffer.from(JSON.stringify(value)).toString('base64url');
@@ -147,8 +160,8 @@ export class Cursor {
147
160
  static getDefinition(meta, orderBy) {
148
161
  return Utils.asArray(orderBy).flatMap(order => {
149
162
  const ret = [];
150
- for (const key of Utils.keys(order)) {
151
- if (RawQueryFragment.isKnownFragment(key)) {
163
+ for (const key of Utils.getObjectQueryKeys(order)) {
164
+ if (Raw.isKnownFragmentSymbol(key)) {
152
165
  ret.push([key, order[key]]);
153
166
  continue;
154
167
  }
@@ -11,7 +11,7 @@ export class DataloaderUtils {
11
11
  static groupPrimaryKeysByEntityAndOpts(refsWithOpts) {
12
12
  const map = new Map();
13
13
  for (const [ref, opts] of refsWithOpts) {
14
- /* The key is a combination of the className and a stringified version if the load options because we want
14
+ /* The key is a combination of the uniqueName (a unique table name based identifier) and a stringified version if the load options because we want
15
15
  to map each combination of entities/options into separate find queries in order to return accurate results.
16
16
  This could be further optimized finding the "lowest common denominator" among the different options
17
17
  for each Entity and firing a single query for each Entity instead of Entity+options combination.
@@ -24,7 +24,7 @@ export class DataloaderUtils {
24
24
  Thus such approach should probably be configurable, if not opt-in.
25
25
  NOTE: meta + opts multi maps (https://github.com/martian17/ds-js) might be a more elegant way
26
26
  to implement this but not necessarily faster. */
27
- const key = `${helper(ref).__meta.className}|${JSON.stringify(opts ?? {})}`;
27
+ const key = `${helper(ref).__meta.uniqueName}|${JSON.stringify(opts ?? {})}`;
28
28
  let primaryKeysSet = map.get(key);
29
29
  if (primaryKeysSet == null) {
30
30
  primaryKeysSet = new Set();
@@ -42,9 +42,10 @@ export class DataloaderUtils {
42
42
  return async (refsWithOpts) => {
43
43
  const groupedIdsMap = DataloaderUtils.groupPrimaryKeysByEntityAndOpts(refsWithOpts);
44
44
  const promises = Array.from(groupedIdsMap).map(([key, idsSet]) => {
45
- const className = key.substring(0, key.indexOf('|'));
45
+ const uniqueName = key.substring(0, key.indexOf('|'));
46
46
  const opts = JSON.parse(key.substring(key.indexOf('|') + 1));
47
- return em.find(className, Array.from(idsSet), opts);
47
+ const meta = em.getMetadata().getByUniqueName(uniqueName);
48
+ return em.find(meta.class, Array.from(idsSet), opts);
48
49
  });
49
50
  await Promise.all(promises);
50
51
  /* Instead of assigning each find result to the original reference we use a shortcut
@@ -70,7 +71,7 @@ export class DataloaderUtils {
70
71
  The value is another Map which we can use to filter the find query to get results pertaining to the collections that have been dataloaded:
71
72
  its keys are the props we are going to filter to and its values are the corresponding PKs.
72
73
  */
73
- const key = `${col.property.targetMeta.className}|${JSON.stringify(opts ?? {})}`;
74
+ const key = `${col.property.targetMeta.uniqueName}|${JSON.stringify(opts ?? {})}`;
74
75
  let filterMap = entitiesMap.get(key); // We are going to use this map to filter the entities pertaining to the collections that have been dataloaded.
75
76
  if (filterMap == null) {
76
77
  filterMap = new Map();
@@ -97,9 +98,10 @@ export class DataloaderUtils {
97
98
  */
98
99
  static entitiesAndOptsMapToQueries(entitiesAndOptsMap, em) {
99
100
  return Array.from(entitiesAndOptsMap, async ([key, filterMap]) => {
100
- const className = key.substring(0, key.indexOf('|'));
101
+ const uniqueName = key.substring(0, key.indexOf('|'));
101
102
  const opts = JSON.parse(key.substring(key.indexOf('|') + 1));
102
- const res = await em.find(className, opts?.where != null && Object.keys(opts.where).length > 0 ?
103
+ const meta = em.getMetadata().getByUniqueName(uniqueName);
104
+ const res = await em.find(meta.class, opts?.where != null && Object.keys(opts.where).length > 0 ?
103
105
  {
104
106
  $and: [
105
107
  {
@@ -121,7 +123,7 @@ export class DataloaderUtils {
121
123
  ...(opts.populate === false ? [] : opts.populate ?? []),
122
124
  ...Array.from(filterMap.keys()).filter(
123
125
  // We need to do so only if the inverse side is a collection, because we can already retrieve the PK from a reference without having to load it
124
- prop => em.getMetadata(className).properties[prop]?.ref !== true),
126
+ prop => meta.properties[prop]?.ref !== true),
125
127
  ],
126
128
  });
127
129
  return [key, res];
@@ -164,7 +166,7 @@ export class DataloaderUtils {
164
166
  // We need to filter the results in order to map each input collection
165
167
  // to a subset of each query matching the collection items.
166
168
  return collsWithOpts.map(([col, opts]) => {
167
- const key = `${col.property.targetMeta.className}|${JSON.stringify(opts ?? {})}`;
169
+ const key = `${col.property.targetMeta.uniqueName}|${JSON.stringify(opts ?? {})}`;
168
170
  const entities = resultsMap.get(key);
169
171
  if (entities == null) {
170
172
  // Should never happen
@@ -183,7 +185,7 @@ export class DataloaderUtils {
183
185
  return async (collsWithOpts) => {
184
186
  const groups = new Map();
185
187
  for (const [col, opts] of collsWithOpts) {
186
- const key = `${col.property.targetMeta.className}.${col.property.name}|${JSON.stringify(opts ?? {})}`;
188
+ const key = `${col.property.targetMeta.uniqueName}.${col.property.name}|${JSON.stringify(opts ?? {})}`;
187
189
  const value = groups.get(key) ?? [];
188
190
  value.push([col, opts ?? {}]);
189
191
  groups.set(key, value);
@@ -198,7 +200,7 @@ export class DataloaderUtils {
198
200
  const owners = group.map(c => c[0].owner);
199
201
  const $or = [];
200
202
  // a bit of a hack, but we need to prefix the key, since we have only a column name, not a property name
201
- const alias = em.config.getNamingStrategy().aliasName(prop.pivotEntity, 0);
203
+ const alias = em.config.getNamingStrategy().aliasName(Utils.className(prop.pivotEntity), 0);
202
204
  const fk = `${alias}.${Utils.getPrimaryKeyHash(prop.joinColumns)}`;
203
205
  for (const c of group) {
204
206
  $or.push({ $and: [c[1]?.where ?? {}, { [fk]: c[0].owner }] });
@@ -1,5 +1,6 @@
1
1
  import type { EntityData, EntityDictionary, EntityMetadata, EntityName, EntityProperty, IMetadataStorage } from '../typings.js';
2
2
  import type { Platform } from '../platforms/Platform.js';
3
+ import type { Configuration } from './Configuration.js';
3
4
  type Comparator<T> = (a: T, b: T, options?: {
4
5
  includeInverseSides?: boolean;
5
6
  }) => EntityData<T>;
@@ -9,6 +10,7 @@ type CompositeKeyPart = string | CompositeKeyPart[];
9
10
  export declare class EntityComparator {
10
11
  private readonly metadata;
11
12
  private readonly platform;
13
+ private readonly config?;
12
14
  private readonly comparators;
13
15
  private readonly mappers;
14
16
  private readonly snapshotGenerators;
@@ -16,23 +18,23 @@ export declare class EntityComparator {
16
18
  private readonly pkGettersConverted;
17
19
  private readonly pkSerializers;
18
20
  private tmpIndex;
19
- constructor(metadata: IMetadataStorage, platform: Platform);
21
+ constructor(metadata: IMetadataStorage, platform: Platform, config?: Configuration | undefined);
20
22
  /**
21
23
  * Computes difference between two entities.
22
24
  */
23
- diffEntities<T>(entityName: string, a: EntityData<T>, b: EntityData<T>, options?: {
25
+ diffEntities<T extends object>(entityName: EntityName<T>, a: EntityData<T>, b: EntityData<T>, options?: {
24
26
  includeInverseSides?: boolean;
25
27
  }): EntityData<T>;
26
- matching<T>(entityName: string, a: EntityData<T>, b: EntityData<T>): boolean;
28
+ matching<T extends object>(entityName: EntityName<T>, a: EntityData<T>, b: EntityData<T>): boolean;
27
29
  /**
28
30
  * Removes ORM specific code from entities and prepares it for serializing. Used before change set computation.
29
31
  * References will be mapped to primary keys, collections to arrays of primary keys.
30
32
  */
31
- prepareEntity<T>(entity: T): EntityData<T>;
33
+ prepareEntity<T extends object>(entity: T): EntityData<T>;
32
34
  /**
33
35
  * Maps database columns to properties.
34
36
  */
35
- mapResult<T>(entityName: string, result: EntityDictionary<T>): EntityData<T>;
37
+ mapResult<T>(meta: EntityMetadata<T>, result: EntityDictionary<T>): EntityData<T>;
36
38
  /**
37
39
  * @internal Highly performance-sensitive method.
38
40
  */
@@ -64,7 +66,7 @@ export declare class EntityComparator {
64
66
  /**
65
67
  * @internal Highly performance-sensitive method.
66
68
  */
67
- getResultMapper<T>(entityName: string): ResultMapper<T>;
69
+ getResultMapper<T>(meta: EntityMetadata<T>): ResultMapper<T>;
68
70
  private getPropertyCondition;
69
71
  private getEmbeddedArrayPropertySnapshot;
70
72
  /**
@@ -78,11 +80,16 @@ export declare class EntityComparator {
78
80
  /**
79
81
  * @internal Highly performance-sensitive method.
80
82
  */
81
- getEntityComparator<T extends object>(entityName: string): Comparator<T>;
83
+ getEntityComparator<T extends object>(entityName: EntityName<T>): Comparator<T>;
82
84
  private getGenericComparator;
83
85
  private getPropertyComparator;
84
86
  private wrap;
85
87
  private safeKey;
88
+ /**
89
+ * Sets the toArray helper in the context if not already set.
90
+ * Used for converting composite PKs to arrays.
91
+ */
92
+ private setToArrayHelper;
86
93
  /**
87
94
  * perf: used to generate list of comparable properties during discovery, so we speed up the runtime comparison
88
95
  */