@mikro-orm/core 7.0.0-dev.99 → 7.0.0-rc.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.
- package/EntityManager.d.ts +34 -17
- package/EntityManager.js +88 -99
- package/MikroORM.d.ts +5 -5
- package/MikroORM.js +25 -20
- package/cache/FileCacheAdapter.js +11 -3
- package/connections/Connection.d.ts +3 -2
- package/connections/Connection.js +4 -3
- package/drivers/DatabaseDriver.d.ts +11 -11
- package/drivers/DatabaseDriver.js +91 -25
- package/drivers/IDatabaseDriver.d.ts +22 -16
- package/entity/BaseEntity.d.ts +61 -1
- package/entity/Collection.d.ts +8 -1
- package/entity/Collection.js +12 -13
- package/entity/EntityAssigner.js +9 -9
- package/entity/EntityFactory.d.ts +6 -1
- package/entity/EntityFactory.js +40 -22
- package/entity/EntityHelper.d.ts +2 -2
- package/entity/EntityHelper.js +27 -4
- package/entity/EntityLoader.d.ts +5 -4
- package/entity/EntityLoader.js +193 -80
- package/entity/EntityRepository.d.ts +27 -7
- package/entity/EntityRepository.js +8 -2
- package/entity/PolymorphicRef.d.ts +12 -0
- package/entity/PolymorphicRef.js +18 -0
- package/entity/WrappedEntity.d.ts +2 -2
- package/entity/WrappedEntity.js +1 -1
- package/entity/defineEntity.d.ts +88 -49
- package/entity/defineEntity.js +12 -0
- package/entity/index.d.ts +1 -0
- package/entity/index.js +1 -0
- package/entity/validators.js +2 -2
- package/enums.d.ts +2 -2
- package/enums.js +1 -0
- package/errors.d.ts +16 -8
- package/errors.js +40 -13
- package/hydration/ObjectHydrator.js +63 -21
- package/index.d.ts +1 -1
- package/logging/colors.d.ts +1 -1
- package/logging/colors.js +7 -6
- package/logging/inspect.js +1 -6
- package/metadata/EntitySchema.d.ts +43 -13
- package/metadata/EntitySchema.js +82 -27
- package/metadata/MetadataDiscovery.d.ts +60 -3
- package/metadata/MetadataDiscovery.js +665 -154
- package/metadata/MetadataProvider.js +3 -1
- package/metadata/MetadataStorage.d.ts +13 -6
- package/metadata/MetadataStorage.js +64 -19
- package/metadata/MetadataValidator.d.ts +32 -2
- package/metadata/MetadataValidator.js +196 -31
- package/metadata/discover-entities.js +5 -5
- package/metadata/types.d.ts +111 -14
- package/naming-strategy/AbstractNamingStrategy.d.ts +11 -3
- package/naming-strategy/AbstractNamingStrategy.js +12 -0
- package/naming-strategy/EntityCaseNamingStrategy.d.ts +3 -3
- package/naming-strategy/EntityCaseNamingStrategy.js +6 -5
- package/naming-strategy/MongoNamingStrategy.d.ts +3 -3
- package/naming-strategy/MongoNamingStrategy.js +6 -6
- package/naming-strategy/NamingStrategy.d.ts +17 -3
- package/naming-strategy/UnderscoreNamingStrategy.d.ts +3 -3
- package/naming-strategy/UnderscoreNamingStrategy.js +6 -6
- package/package.json +2 -2
- package/platforms/Platform.d.ts +4 -2
- package/platforms/Platform.js +5 -2
- package/serialization/EntitySerializer.d.ts +3 -0
- package/serialization/EntitySerializer.js +15 -13
- package/serialization/EntityTransformer.js +6 -6
- package/serialization/SerializationContext.d.ts +6 -6
- package/typings.d.ts +320 -109
- package/typings.js +84 -17
- package/unit-of-work/ChangeSet.d.ts +4 -3
- package/unit-of-work/ChangeSet.js +2 -3
- package/unit-of-work/ChangeSetComputer.js +27 -6
- package/unit-of-work/ChangeSetPersister.d.ts +5 -0
- package/unit-of-work/ChangeSetPersister.js +45 -15
- package/unit-of-work/CommitOrderCalculator.d.ts +12 -10
- package/unit-of-work/CommitOrderCalculator.js +13 -13
- package/unit-of-work/IdentityMap.d.ts +12 -0
- package/unit-of-work/IdentityMap.js +39 -1
- package/unit-of-work/UnitOfWork.d.ts +21 -3
- package/unit-of-work/UnitOfWork.js +201 -54
- package/utils/AbstractSchemaGenerator.js +17 -8
- package/utils/AsyncContext.d.ts +6 -0
- package/utils/AsyncContext.js +42 -0
- package/utils/Configuration.d.ts +52 -11
- package/utils/Configuration.js +12 -8
- package/utils/Cursor.js +21 -8
- package/utils/DataloaderUtils.js +13 -11
- package/utils/EntityComparator.d.ts +14 -7
- package/utils/EntityComparator.js +132 -46
- package/utils/QueryHelper.d.ts +16 -6
- package/utils/QueryHelper.js +53 -18
- package/utils/RawQueryFragment.d.ts +28 -23
- package/utils/RawQueryFragment.js +34 -56
- package/utils/RequestContext.js +2 -2
- package/utils/TransactionContext.js +2 -2
- package/utils/TransactionManager.js +1 -1
- package/utils/Utils.d.ts +7 -26
- package/utils/Utils.js +25 -79
- package/utils/clone.js +7 -21
- package/utils/env-vars.d.ts +4 -0
- package/utils/env-vars.js +13 -3
- package/utils/fs-utils.d.ts +21 -0
- package/utils/fs-utils.js +104 -9
- package/utils/upsert-utils.d.ts +4 -4
package/MikroORM.js
CHANGED
|
@@ -4,31 +4,35 @@ import { Configuration } from './utils/Configuration.js';
|
|
|
4
4
|
import { loadEnvironmentVars } from './utils/env-vars.js';
|
|
5
5
|
import { Utils } from './utils/Utils.js';
|
|
6
6
|
import { colors } from './logging/colors.js';
|
|
7
|
-
async function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
async function tryRegisterExtension(name, pkg, extensions) {
|
|
8
|
+
try {
|
|
9
|
+
const url = import.meta.resolve(pkg);
|
|
10
|
+
const mod = await import(url);
|
|
11
|
+
if (mod[name]) {
|
|
12
|
+
extensions.push(mod[name]);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// not installed
|
|
14
17
|
}
|
|
15
18
|
}
|
|
16
19
|
/** @internal */
|
|
17
|
-
export async function
|
|
20
|
+
export async function loadOptionalDependencies(options) {
|
|
21
|
+
await import('@mikro-orm/core/fs-utils').then(m => m.fs.init()).catch(() => null);
|
|
18
22
|
const extensions = options.extensions ?? [];
|
|
19
23
|
const exists = (name) => extensions.some(ext => ext.name === name);
|
|
20
24
|
if (!exists('SeedManager')) {
|
|
21
|
-
await
|
|
25
|
+
await tryRegisterExtension('SeedManager', '@mikro-orm/seeder', extensions);
|
|
22
26
|
}
|
|
23
27
|
if (!exists('Migrator')) {
|
|
24
|
-
await
|
|
28
|
+
await tryRegisterExtension('Migrator', '@mikro-orm/migrations', extensions);
|
|
25
29
|
}
|
|
26
30
|
/* v8 ignore if */
|
|
27
31
|
if (!exists('Migrator')) {
|
|
28
|
-
await
|
|
32
|
+
await tryRegisterExtension('Migrator', '@mikro-orm/migrations-mongodb', extensions);
|
|
29
33
|
}
|
|
30
34
|
if (!exists('EntityGenerator')) {
|
|
31
|
-
await
|
|
35
|
+
await tryRegisterExtension('EntityGenerator', '@mikro-orm/entity-generator', extensions);
|
|
32
36
|
}
|
|
33
37
|
options.extensions = extensions;
|
|
34
38
|
const metadataCacheEnabled = options.metadataCache?.enabled || options.metadataProvider?.useCache?.();
|
|
@@ -85,7 +89,7 @@ export class MikroORM {
|
|
|
85
89
|
options = { ...options };
|
|
86
90
|
options.discovery ??= {};
|
|
87
91
|
options.discovery.skipSyncDiscovery ??= true;
|
|
88
|
-
await
|
|
92
|
+
await loadOptionalDependencies(options);
|
|
89
93
|
const orm = new this(options);
|
|
90
94
|
const preferTs = orm.config.get('preferTs', Utils.detectTypeScriptSupport());
|
|
91
95
|
orm.metadata = await orm.discovery.discover(preferTs);
|
|
@@ -94,13 +98,15 @@ export class MikroORM {
|
|
|
94
98
|
}
|
|
95
99
|
/**
|
|
96
100
|
* Synchronous variant of the `init` method with some limitations:
|
|
97
|
-
* -
|
|
98
|
-
* -
|
|
99
|
-
* -
|
|
101
|
+
* - folder-based discovery not supported
|
|
102
|
+
* - ORM extensions are not autoloaded
|
|
103
|
+
* - when metadata cache is enabled, `FileCacheAdapter` needs to be explicitly set in the config
|
|
100
104
|
*/
|
|
101
105
|
constructor(options) {
|
|
102
106
|
const env = loadEnvironmentVars();
|
|
103
|
-
options =
|
|
107
|
+
options = options.preferEnvVars
|
|
108
|
+
? Utils.merge(options, env)
|
|
109
|
+
: Utils.merge(env, options);
|
|
104
110
|
this.config = new Configuration(options);
|
|
105
111
|
const discovery = this.config.get('discovery');
|
|
106
112
|
this.driver = this.config.getDriver();
|
|
@@ -158,7 +164,6 @@ export class MikroORM {
|
|
|
158
164
|
*/
|
|
159
165
|
getMetadata(entityName) {
|
|
160
166
|
if (entityName) {
|
|
161
|
-
entityName = Utils.className(entityName);
|
|
162
167
|
return this.metadata.get(entityName);
|
|
163
168
|
}
|
|
164
169
|
return this.metadata;
|
|
@@ -181,8 +186,8 @@ export class MikroORM {
|
|
|
181
186
|
const tmp = this.discovery.discoverReferences(Utils.asArray(entities));
|
|
182
187
|
const metadata = this.discovery.processDiscoveredEntities(tmp);
|
|
183
188
|
for (const meta of metadata) {
|
|
184
|
-
this.metadata.set(meta.
|
|
185
|
-
meta.root = this.metadata.get(meta.root.
|
|
189
|
+
this.metadata.set(meta.class, meta);
|
|
190
|
+
meta.root = this.metadata.get(meta.root.class);
|
|
186
191
|
}
|
|
187
192
|
this.metadata.decorate(this.em);
|
|
188
193
|
}
|
|
@@ -53,7 +53,15 @@ export class FileCacheAdapter {
|
|
|
53
53
|
clear() {
|
|
54
54
|
const path = this.path('*');
|
|
55
55
|
const files = fs.glob(path);
|
|
56
|
-
|
|
56
|
+
for (const file of files) {
|
|
57
|
+
/* v8 ignore next */
|
|
58
|
+
try {
|
|
59
|
+
unlinkSync(file);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// ignore if file is already gone
|
|
63
|
+
}
|
|
64
|
+
}
|
|
57
65
|
this.cache = {};
|
|
58
66
|
}
|
|
59
67
|
combine() {
|
|
@@ -63,7 +71,7 @@ export class FileCacheAdapter {
|
|
|
63
71
|
let path = typeof this.options.combined === 'string'
|
|
64
72
|
? this.options.combined
|
|
65
73
|
: './metadata.json';
|
|
66
|
-
path =
|
|
74
|
+
path = fs.normalizePath(this.options.cacheDir, path);
|
|
67
75
|
this.options.combined = path; // override in the options, so we can log it from the CLI in `cache:generate` command
|
|
68
76
|
writeFileSync(path, JSON.stringify(this.cache, null, this.pretty ? 2 : undefined));
|
|
69
77
|
return path;
|
|
@@ -73,7 +81,7 @@ export class FileCacheAdapter {
|
|
|
73
81
|
return `${this.options.cacheDir}/${name}.json`;
|
|
74
82
|
}
|
|
75
83
|
getHash(origin) {
|
|
76
|
-
origin =
|
|
84
|
+
origin = fs.absolutePath(origin, this.baseDir);
|
|
77
85
|
if (!existsSync(origin)) {
|
|
78
86
|
return null;
|
|
79
87
|
}
|
|
@@ -43,9 +43,10 @@ export declare abstract class Connection {
|
|
|
43
43
|
*/
|
|
44
44
|
ensureConnection(): Promise<void>;
|
|
45
45
|
/**
|
|
46
|
-
*
|
|
46
|
+
* Execute raw SQL queries, handy from running schema dump loaded from a file.
|
|
47
|
+
* This method doesn't support transactions, as opposed to `orm.schema.execute()`, which is used internally.
|
|
47
48
|
*/
|
|
48
|
-
|
|
49
|
+
executeDump(dump: string): Promise<void>;
|
|
49
50
|
protected onConnect(): Promise<void>;
|
|
50
51
|
transactional<T>(cb: (trx: Transaction) => Promise<T>, options?: {
|
|
51
52
|
isolationLevel?: IsolationLevel | `${IsolationLevel}`;
|
|
@@ -40,10 +40,11 @@ export class Connection {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
|
-
*
|
|
43
|
+
* Execute raw SQL queries, handy from running schema dump loaded from a file.
|
|
44
|
+
* This method doesn't support transactions, as opposed to `orm.schema.execute()`, which is used internally.
|
|
44
45
|
*/
|
|
45
|
-
async
|
|
46
|
-
throw new Error(`
|
|
46
|
+
async executeDump(dump) {
|
|
47
|
+
throw new Error(`Executing SQL dumps is not supported by current driver`);
|
|
47
48
|
}
|
|
48
49
|
async onConnect() {
|
|
49
50
|
const schemaGenerator = this.config.getExtension('@mikro-orm/schema-generator');
|
|
@@ -22,18 +22,18 @@ export declare abstract class DatabaseDriver<C extends Connection> implements ID
|
|
|
22
22
|
protected comparator: EntityComparator;
|
|
23
23
|
protected metadata: MetadataStorage;
|
|
24
24
|
protected constructor(config: Configuration, dependencies: string[]);
|
|
25
|
-
abstract find<T extends object, P extends string = never, F extends string = '*', E extends string = never>(entityName:
|
|
26
|
-
abstract findOne<T extends object, P extends string = never, F extends string = '*', E extends string = never>(entityName:
|
|
27
|
-
abstract nativeInsert<T extends object>(entityName:
|
|
28
|
-
abstract nativeInsertMany<T extends object>(entityName:
|
|
29
|
-
abstract nativeUpdate<T extends object>(entityName:
|
|
30
|
-
nativeUpdateMany<T extends object>(entityName:
|
|
31
|
-
abstract nativeDelete<T extends object>(entityName:
|
|
32
|
-
abstract count<T extends object, P extends string = never>(entityName:
|
|
25
|
+
abstract find<T extends object, P extends string = never, F extends string = '*', E extends string = never>(entityName: EntityName<T>, where: FilterQuery<T>, options?: FindOptions<T, P, F, E>): Promise<EntityData<T>[]>;
|
|
26
|
+
abstract findOne<T extends object, P extends string = never, F extends string = '*', E extends string = never>(entityName: EntityName<T>, where: FilterQuery<T>, options?: FindOneOptions<T, P, F, E>): Promise<EntityData<T> | null>;
|
|
27
|
+
abstract nativeInsert<T extends object>(entityName: EntityName<T>, data: EntityDictionary<T>, options?: NativeInsertUpdateOptions<T>): Promise<QueryResult<T>>;
|
|
28
|
+
abstract nativeInsertMany<T extends object>(entityName: EntityName<T>, data: EntityDictionary<T>[], options?: NativeInsertUpdateManyOptions<T>, transform?: (sql: string) => string): Promise<QueryResult<T>>;
|
|
29
|
+
abstract nativeUpdate<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, data: EntityDictionary<T>, options?: NativeInsertUpdateOptions<T>): Promise<QueryResult<T>>;
|
|
30
|
+
nativeUpdateMany<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>[], data: EntityDictionary<T>[], options?: NativeInsertUpdateManyOptions<T>): Promise<QueryResult<T>>;
|
|
31
|
+
abstract nativeDelete<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options?: DeleteOptions<T>): Promise<QueryResult<T>>;
|
|
32
|
+
abstract count<T extends object, P extends string = never>(entityName: EntityName<T>, where: FilterQuery<T>, options?: CountOptions<T, P>): Promise<number>;
|
|
33
33
|
createEntityManager(useContext?: boolean): this[typeof EntityManagerType];
|
|
34
34
|
findVirtual<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options: FindOptions<T, any, any, any>): Promise<EntityData<T>[]>;
|
|
35
|
-
countVirtual<T extends object>(entityName:
|
|
36
|
-
aggregate(entityName:
|
|
35
|
+
countVirtual<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options: CountOptions<T, any>): Promise<number>;
|
|
36
|
+
aggregate(entityName: EntityName, pipeline: any[]): Promise<any[]>;
|
|
37
37
|
loadFromPivotTable<T extends object, O extends object>(prop: EntityProperty, owners: Primary<O>[][], where?: FilterQuery<any>, orderBy?: OrderDefinition<T>, ctx?: Transaction, options?: FindOptions<T, any, any, any>, pivotJoin?: boolean): Promise<Dictionary<T[]>>;
|
|
38
38
|
syncCollections<T extends object, O extends object>(collections: Iterable<Collection<T, O>>, options?: DriverMethodOptions): Promise<void>;
|
|
39
39
|
mapResult<T extends object>(result: EntityDictionary<T>, meta?: EntityMetadata<T>, populate?: PopulateOptions<T>[]): EntityData<T> | null;
|
|
@@ -58,7 +58,7 @@ export declare abstract class DatabaseDriver<C extends Connection> implements ID
|
|
|
58
58
|
/** @internal */
|
|
59
59
|
mapDataToFieldNames(data: Dictionary, stringifyJsonArrays: boolean, properties?: Record<string, EntityProperty>, convertCustomTypes?: boolean, object?: boolean): Dictionary;
|
|
60
60
|
protected inlineEmbeddables<T extends object>(meta: EntityMetadata<T>, data: T, where?: boolean): void;
|
|
61
|
-
protected getPrimaryKeyFields(
|
|
61
|
+
protected getPrimaryKeyFields<T>(meta: EntityMetadata<T>): string[];
|
|
62
62
|
protected createReplicas(cb: (c: ConnectionOptions) => C): C[];
|
|
63
63
|
lockPessimistic<T extends object>(entity: T, options: LockOptions): Promise<void>;
|
|
64
64
|
abstract stream<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options: StreamOptions<T>): AsyncIterableIterator<T>;
|
|
@@ -8,6 +8,7 @@ import { EntityManager } from '../EntityManager.js';
|
|
|
8
8
|
import { CursorError, ValidationError } from '../errors.js';
|
|
9
9
|
import { DriverException } from '../exceptions.js';
|
|
10
10
|
import { helper } from '../entity/wrap.js';
|
|
11
|
+
import { PolymorphicRef } from '../entity/PolymorphicRef.js';
|
|
11
12
|
import { JsonType } from '../types/JsonType.js';
|
|
12
13
|
import { MikroORM } from '../MikroORM.js';
|
|
13
14
|
export class DatabaseDriver {
|
|
@@ -59,7 +60,7 @@ export class DatabaseDriver {
|
|
|
59
60
|
{
|
|
60
61
|
const pk = coll.property.targetMeta.primaryKeys[0];
|
|
61
62
|
const data = { [coll.property.name]: coll.getIdentifiers(pk) };
|
|
62
|
-
await this.nativeUpdate(coll.owner.constructor
|
|
63
|
+
await this.nativeUpdate(coll.owner.constructor, helper(coll.owner).getPrimaryKey(), data, options);
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
}
|
|
@@ -67,7 +68,7 @@ export class DatabaseDriver {
|
|
|
67
68
|
if (!result || !meta) {
|
|
68
69
|
return result ?? null;
|
|
69
70
|
}
|
|
70
|
-
return this.comparator.mapResult(meta
|
|
71
|
+
return this.comparator.mapResult(meta, result);
|
|
71
72
|
}
|
|
72
73
|
async connect(options) {
|
|
73
74
|
await this.connection.connect(options);
|
|
@@ -152,7 +153,7 @@ export class DatabaseDriver {
|
|
|
152
153
|
}
|
|
153
154
|
const createOrderBy = (prop, direction) => {
|
|
154
155
|
if (Utils.isPlainObject(direction)) {
|
|
155
|
-
const value = Utils.
|
|
156
|
+
const value = Utils.getObjectQueryKeys(direction).reduce((o, key) => {
|
|
156
157
|
Object.assign(o, createOrderBy(key, direction[key]));
|
|
157
158
|
return o;
|
|
158
159
|
}, {});
|
|
@@ -168,22 +169,51 @@ export class DatabaseDriver {
|
|
|
168
169
|
};
|
|
169
170
|
}
|
|
170
171
|
createCursorCondition(definition, offsets, inverse, meta) {
|
|
171
|
-
const createCondition = (prop, direction, offset, eq = false) => {
|
|
172
|
-
if (offset === null) {
|
|
173
|
-
throw CursorError.missingValue(meta.className, prop);
|
|
174
|
-
}
|
|
172
|
+
const createCondition = (prop, direction, offset, eq = false, path = prop) => {
|
|
175
173
|
if (Utils.isPlainObject(direction)) {
|
|
174
|
+
if (offset === undefined) {
|
|
175
|
+
throw CursorError.missingValue(meta.className, path);
|
|
176
|
+
}
|
|
176
177
|
const value = Utils.keys(direction).reduce((o, key) => {
|
|
177
|
-
|
|
178
|
-
throw CursorError.missingValue(meta.className, `${prop}.${key}`);
|
|
179
|
-
}
|
|
180
|
-
Object.assign(o, createCondition(key, direction[key], offset[key], eq));
|
|
178
|
+
Object.assign(o, createCondition(key, direction[key], offset?.[key], eq, `${path}.${key}`));
|
|
181
179
|
return o;
|
|
182
180
|
}, {});
|
|
183
|
-
return
|
|
181
|
+
return { [prop]: value };
|
|
184
182
|
}
|
|
185
|
-
const
|
|
186
|
-
const
|
|
183
|
+
const isDesc = direction === QueryOrderNumeric.DESC || direction.toString().toLowerCase() === 'desc';
|
|
184
|
+
const dirStr = direction.toString().toLowerCase();
|
|
185
|
+
let nullsFirst;
|
|
186
|
+
if (dirStr.includes('nulls first')) {
|
|
187
|
+
nullsFirst = true;
|
|
188
|
+
}
|
|
189
|
+
else if (dirStr.includes('nulls last')) {
|
|
190
|
+
nullsFirst = false;
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
// Default: NULLS LAST for ASC, NULLS FIRST for DESC (matches most databases)
|
|
194
|
+
nullsFirst = isDesc;
|
|
195
|
+
}
|
|
196
|
+
const operator = Utils.xor(isDesc, inverse) ? '$lt' : '$gt';
|
|
197
|
+
// For leaf-level properties, undefined means missing value
|
|
198
|
+
if (offset === undefined) {
|
|
199
|
+
throw CursorError.missingValue(meta.className, path);
|
|
200
|
+
}
|
|
201
|
+
// Handle null offset (intentional null cursor value)
|
|
202
|
+
if (offset === null) {
|
|
203
|
+
if (eq) {
|
|
204
|
+
// Equal to null
|
|
205
|
+
return { [prop]: null };
|
|
206
|
+
}
|
|
207
|
+
// Strict comparison with null cursor value
|
|
208
|
+
// hasItemsAfterNull: forward + nullsFirst, or backward + nullsLast
|
|
209
|
+
const hasItemsAfterNull = Utils.xor(nullsFirst, inverse);
|
|
210
|
+
if (hasItemsAfterNull) {
|
|
211
|
+
return { [prop]: { $ne: null } };
|
|
212
|
+
}
|
|
213
|
+
// No items after null in this direction, return impossible condition
|
|
214
|
+
return { [prop]: [] };
|
|
215
|
+
}
|
|
216
|
+
// Non-null offset
|
|
187
217
|
return { [prop]: { [operator + (eq ? 'e' : '')]: offset } };
|
|
188
218
|
};
|
|
189
219
|
const [order, ...otherOrders] = definition;
|
|
@@ -231,6 +261,43 @@ export class DatabaseDriver {
|
|
|
231
261
|
}
|
|
232
262
|
return;
|
|
233
263
|
}
|
|
264
|
+
// Handle polymorphic relations - convert tuple or PolymorphicRef to separate columns
|
|
265
|
+
// Tuple format: ['discriminator', id] or ['discriminator', id1, id2] for composite keys
|
|
266
|
+
// Must be checked BEFORE joinColumns array handling since polymorphic uses fieldNames (includes discriminator)
|
|
267
|
+
if (prop.polymorphic && prop.fieldNames && prop.fieldNames.length >= 2) {
|
|
268
|
+
let discriminator;
|
|
269
|
+
let ids;
|
|
270
|
+
if (Array.isArray(data[k]) && typeof data[k][0] === 'string' && prop.discriminatorMap?.[data[k][0]]) {
|
|
271
|
+
// Tuple format: ['discriminator', ...ids]
|
|
272
|
+
const [disc, ...rest] = data[k];
|
|
273
|
+
discriminator = disc;
|
|
274
|
+
ids = rest;
|
|
275
|
+
}
|
|
276
|
+
else if (data[k] instanceof PolymorphicRef) {
|
|
277
|
+
// PolymorphicRef wrapper (internal use)
|
|
278
|
+
discriminator = data[k].discriminator;
|
|
279
|
+
const polyId = data[k].id;
|
|
280
|
+
// Handle object-style composite key IDs like { tenantId: 1, orgId: 100 }
|
|
281
|
+
if (polyId && typeof polyId === 'object' && !Array.isArray(polyId)) {
|
|
282
|
+
const targetEntity = prop.discriminatorMap?.[discriminator];
|
|
283
|
+
const targetMeta = this.metadata.get(targetEntity);
|
|
284
|
+
ids = targetMeta.primaryKeys.map(pk => polyId[pk]);
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
ids = Utils.asArray(polyId);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (discriminator) {
|
|
291
|
+
const discriminatorColumn = prop.fieldNames[0];
|
|
292
|
+
const idColumns = prop.fieldNames.slice(1);
|
|
293
|
+
delete data[k];
|
|
294
|
+
data[discriminatorColumn] = discriminator;
|
|
295
|
+
idColumns.forEach((col, idx) => {
|
|
296
|
+
data[col] = ids[idx];
|
|
297
|
+
});
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
234
301
|
if (prop.joinColumns && Array.isArray(data[k])) {
|
|
235
302
|
const copy = Utils.flatten(data[k]);
|
|
236
303
|
delete data[k];
|
|
@@ -277,38 +344,38 @@ export class DatabaseDriver {
|
|
|
277
344
|
// explicitly allow `$exists`, `$eq` and `$ne` operators here as they can't be misused this way
|
|
278
345
|
const operator = Object.keys(data[prop.name]).some(f => Utils.isOperator(f) && !['$exists', '$ne', '$eq'].includes(f));
|
|
279
346
|
if (operator) {
|
|
280
|
-
throw ValidationError.cannotUseOperatorsInsideEmbeddables(meta.
|
|
347
|
+
throw ValidationError.cannotUseOperatorsInsideEmbeddables(meta.class, prop.name, data);
|
|
281
348
|
}
|
|
282
349
|
if (prop.object && where) {
|
|
283
350
|
const inline = (payload, sub, path) => {
|
|
284
351
|
if (sub.kind === ReferenceKind.EMBEDDED && Utils.isObject(payload[sub.embedded[1]])) {
|
|
285
352
|
return Object.keys(payload[sub.embedded[1]]).forEach(kkk => {
|
|
286
353
|
if (!sub.embeddedProps[kkk]) {
|
|
287
|
-
throw ValidationError.invalidEmbeddableQuery(meta.
|
|
354
|
+
throw ValidationError.invalidEmbeddableQuery(meta.class, kkk, sub.type);
|
|
288
355
|
}
|
|
289
|
-
inline(payload[sub.embedded[1]], sub.embeddedProps[kkk], [...path, sub.
|
|
356
|
+
inline(payload[sub.embedded[1]], sub.embeddedProps[kkk], [...path, sub.fieldNames[0]]);
|
|
290
357
|
});
|
|
291
358
|
}
|
|
292
|
-
data[`${path.join('.')}.${sub.
|
|
359
|
+
data[`${path.join('.')}.${sub.fieldNames[0]}`] = payload[sub.embedded[1]];
|
|
293
360
|
};
|
|
294
361
|
const parentPropName = kk.substring(0, kk.indexOf('.'));
|
|
295
362
|
// we might be using some native JSON operator, e.g. with mongodb's `$geoWithin` or `$exists`
|
|
296
363
|
if (props[kk]) {
|
|
297
364
|
/* v8 ignore next */
|
|
298
|
-
inline(data[prop.name], props[kk] || props[parentPropName], [prop.
|
|
365
|
+
inline(data[prop.name], props[kk] || props[parentPropName], [prop.fieldNames[0]]);
|
|
299
366
|
}
|
|
300
367
|
else if (props[parentPropName]) {
|
|
301
|
-
data[`${prop.
|
|
368
|
+
data[`${prop.fieldNames[0]}.${kk}`] = data[prop.name][kk];
|
|
302
369
|
}
|
|
303
370
|
else {
|
|
304
371
|
unknownProp = true;
|
|
305
372
|
}
|
|
306
373
|
}
|
|
307
374
|
else if (props[kk]) {
|
|
308
|
-
data[props[kk].
|
|
375
|
+
data[props[kk].fieldNames[0]] = data[prop.name][props[kk].embedded[1]];
|
|
309
376
|
}
|
|
310
377
|
else {
|
|
311
|
-
throw ValidationError.invalidEmbeddableQuery(meta.
|
|
378
|
+
throw ValidationError.invalidEmbeddableQuery(meta.class, kk, prop.type);
|
|
312
379
|
}
|
|
313
380
|
});
|
|
314
381
|
if (!unknownProp) {
|
|
@@ -317,9 +384,8 @@ export class DatabaseDriver {
|
|
|
317
384
|
}
|
|
318
385
|
});
|
|
319
386
|
}
|
|
320
|
-
getPrimaryKeyFields(
|
|
321
|
-
|
|
322
|
-
return meta ? Utils.flatten(meta.getPrimaryProps().map(pk => pk.fieldNames)) : [this.config.getNamingStrategy().referenceColumnName()];
|
|
387
|
+
getPrimaryKeyFields(meta) {
|
|
388
|
+
return meta.getPrimaryProps().flatMap(pk => pk.fieldNames);
|
|
323
389
|
}
|
|
324
390
|
createReplicas(cb) {
|
|
325
391
|
const replicas = this.config.get('replicas', []);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConnectionType, EntityData, EntityMetadata, EntityProperty, FilterQuery, Primary, Dictionary,
|
|
1
|
+
import type { ConnectionType, EntityData, EntityMetadata, EntityProperty, FilterQuery, Primary, Dictionary, IPrimaryKey, PopulateOptions, EntityDictionary, AutoPath, ObjectQuery, FilterObject, Populate, EntityName } from '../typings.js';
|
|
2
2
|
import type { Connection, QueryResult, Transaction } from '../connections/Connection.js';
|
|
3
3
|
import type { FlushMode, LockMode, QueryOrderMap, QueryFlag, LoadStrategy, PopulateHint, PopulatePath } from '../enums.js';
|
|
4
4
|
import type { Platform } from '../platforms/Platform.js';
|
|
@@ -8,7 +8,7 @@ import type { EntityManager } from '../EntityManager.js';
|
|
|
8
8
|
import type { DriverException } from '../exceptions.js';
|
|
9
9
|
import type { Configuration } from '../utils/Configuration.js';
|
|
10
10
|
import type { LoggingOptions, LogContext } from '../logging/Logger.js';
|
|
11
|
-
import type {
|
|
11
|
+
import type { Raw } from '../utils/RawQueryFragment.js';
|
|
12
12
|
export declare const EntityManagerType: unique symbol;
|
|
13
13
|
export interface IDatabaseDriver<C extends Connection = Connection> {
|
|
14
14
|
[EntityManagerType]: EntityManager<this>;
|
|
@@ -25,21 +25,21 @@ export interface IDatabaseDriver<C extends Connection = Connection> {
|
|
|
25
25
|
/**
|
|
26
26
|
* Finds selection of entities
|
|
27
27
|
*/
|
|
28
|
-
find<T extends object, P extends string = never, F extends string = '*', E extends string = never>(entityName:
|
|
28
|
+
find<T extends object, P extends string = never, F extends string = '*', E extends string = never>(entityName: EntityName<T>, where: FilterQuery<T>, options?: FindOptions<T, P, F, E>): Promise<EntityData<T>[]>;
|
|
29
29
|
/**
|
|
30
30
|
* Finds single entity (table row, document)
|
|
31
31
|
*/
|
|
32
|
-
findOne<T extends object, P extends string = never, F extends string = '*', E extends string = never>(entityName:
|
|
33
|
-
findVirtual<T extends object>(entityName:
|
|
32
|
+
findOne<T extends object, P extends string = never, F extends string = '*', E extends string = never>(entityName: EntityName<T>, where: FilterQuery<T>, options?: FindOneOptions<T, P, F, E>): Promise<EntityData<T> | null>;
|
|
33
|
+
findVirtual<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options: FindOptions<T, any, any, any>): Promise<EntityData<T>[]>;
|
|
34
34
|
stream<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options: StreamOptions<T>): AsyncIterableIterator<T>;
|
|
35
|
-
nativeInsert<T extends object>(entityName:
|
|
36
|
-
nativeInsertMany<T extends object>(entityName:
|
|
37
|
-
nativeUpdate<T extends object>(entityName:
|
|
38
|
-
nativeUpdateMany<T extends object>(entityName:
|
|
39
|
-
nativeDelete<T extends object>(entityName:
|
|
35
|
+
nativeInsert<T extends object>(entityName: EntityName<T>, data: EntityDictionary<T>, options?: NativeInsertUpdateOptions<T>): Promise<QueryResult<T>>;
|
|
36
|
+
nativeInsertMany<T extends object>(entityName: EntityName<T>, data: EntityDictionary<T>[], options?: NativeInsertUpdateManyOptions<T>, transform?: (sql: string) => string): Promise<QueryResult<T>>;
|
|
37
|
+
nativeUpdate<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, data: EntityDictionary<T>, options?: NativeInsertUpdateOptions<T>): Promise<QueryResult<T>>;
|
|
38
|
+
nativeUpdateMany<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>[], data: EntityDictionary<T>[], options?: NativeInsertUpdateManyOptions<T>): Promise<QueryResult<T>>;
|
|
39
|
+
nativeDelete<T extends object>(entityName: EntityName<T>, where: FilterQuery<T>, options?: NativeDeleteOptions<T>): Promise<QueryResult<T>>;
|
|
40
40
|
syncCollections<T extends object, O extends object>(collections: Iterable<Collection<T, O>>, options?: DriverMethodOptions): Promise<void>;
|
|
41
|
-
count<T extends object, P extends string = never>(entityName:
|
|
42
|
-
aggregate(entityName:
|
|
41
|
+
count<T extends object, P extends string = never>(entityName: EntityName<T>, where: FilterQuery<T>, options?: CountOptions<T, P>): Promise<number>;
|
|
42
|
+
aggregate(entityName: EntityName, pipeline: any[]): Promise<any[]>;
|
|
43
43
|
mapResult<T extends object>(result: EntityDictionary<T>, meta: EntityMetadata<T>, populate?: PopulateOptions<T>[]): EntityData<T> | null;
|
|
44
44
|
/**
|
|
45
45
|
* When driver uses pivot tables for M:N, this method will load identifiers for given collections from them
|
|
@@ -144,7 +144,7 @@ export interface FindOptions<Entity, Hint extends string = never, Fields extends
|
|
|
144
144
|
flags?: QueryFlag[];
|
|
145
145
|
/** sql only */
|
|
146
146
|
groupBy?: string | string[];
|
|
147
|
-
having?:
|
|
147
|
+
having?: FilterQuery<Entity>;
|
|
148
148
|
/** sql only */
|
|
149
149
|
strategy?: LoadStrategy | `${LoadStrategy}`;
|
|
150
150
|
flushMode?: FlushMode | `${FlushMode}`;
|
|
@@ -166,7 +166,7 @@ export interface FindOptions<Entity, Hint extends string = never, Fields extends
|
|
|
166
166
|
/** @internal used to apply filters to the auto-joined relations */
|
|
167
167
|
em?: EntityManager;
|
|
168
168
|
}
|
|
169
|
-
export interface FindByCursorOptions<T extends object, P extends string = never, F extends string = '*', E extends string = never, I extends boolean = true> extends Omit<
|
|
169
|
+
export interface FindByCursorOptions<T extends object, P extends string = never, F extends string = '*', E extends string = never, I extends boolean = true> extends Omit<FindAllOptions<T, P, F, E>, 'limit' | 'offset'> {
|
|
170
170
|
includeCount?: I;
|
|
171
171
|
}
|
|
172
172
|
export interface FindOneOptions<T, P extends string = never, F extends string = '*', E extends string = never> extends Omit<FindOptions<T, P, F, E>, 'limit' | 'lockMode'> {
|
|
@@ -189,10 +189,11 @@ export interface NativeInsertUpdateManyOptions<T> extends NativeInsertUpdateOpti
|
|
|
189
189
|
processCollections?: boolean;
|
|
190
190
|
}
|
|
191
191
|
export interface UpsertOptions<Entity, Fields extends string = never> extends Omit<NativeInsertUpdateOptions<Entity>, 'upsert'> {
|
|
192
|
-
onConflictFields?: (keyof Entity)[] |
|
|
192
|
+
onConflictFields?: (keyof Entity)[] | Raw;
|
|
193
193
|
onConflictAction?: 'ignore' | 'merge';
|
|
194
194
|
onConflictMergeFields?: AutoPath<Entity, Fields, `${PopulatePath.ALL}`>[];
|
|
195
195
|
onConflictExcludeFields?: AutoPath<Entity, Fields, `${PopulatePath.ALL}`>[];
|
|
196
|
+
onConflictWhere?: FilterQuery<Entity>;
|
|
196
197
|
disableIdentityMap?: boolean;
|
|
197
198
|
}
|
|
198
199
|
export interface UpsertManyOptions<Entity, Fields extends string = never> extends UpsertOptions<Entity, Fields> {
|
|
@@ -202,7 +203,7 @@ export interface CountOptions<T extends object, P extends string = never> {
|
|
|
202
203
|
filters?: FilterOptions;
|
|
203
204
|
schema?: string;
|
|
204
205
|
groupBy?: string | readonly string[];
|
|
205
|
-
having?:
|
|
206
|
+
having?: FilterQuery<T>;
|
|
206
207
|
cache?: boolean | number | [string, number];
|
|
207
208
|
populate?: Populate<T, P>;
|
|
208
209
|
populateWhere?: ObjectQuery<T> | PopulateHint | `${PopulateHint}`;
|
|
@@ -247,4 +248,9 @@ export interface GetReferenceOptions {
|
|
|
247
248
|
wrapped?: boolean;
|
|
248
249
|
convertCustomTypes?: boolean;
|
|
249
250
|
schema?: string;
|
|
251
|
+
/**
|
|
252
|
+
* Property name to use for identity map lookup instead of the primary key.
|
|
253
|
+
* This is useful for creating references by unique non-PK properties.
|
|
254
|
+
*/
|
|
255
|
+
key?: string;
|
|
250
256
|
}
|
package/entity/BaseEntity.d.ts
CHANGED
|
@@ -4,13 +4,73 @@ import { type AssignOptions } from './EntityAssigner.js';
|
|
|
4
4
|
import type { EntityLoaderOptions } from './EntityLoader.js';
|
|
5
5
|
import { type SerializeOptions } from '../serialization/EntitySerializer.js';
|
|
6
6
|
import type { FindOneOptions } from '../drivers/IDatabaseDriver.js';
|
|
7
|
+
import type { PopulatePath } from '../enums.js';
|
|
7
8
|
export declare abstract class BaseEntity {
|
|
8
9
|
isInitialized(): boolean;
|
|
9
10
|
populated(populated?: boolean): void;
|
|
10
|
-
populate<Entity extends this = this, Hint extends string = never>(populate: AutoPath<Entity, Hint>[] | false, options?: EntityLoaderOptions<Entity>): Promise<Loaded<Entity, Hint>>;
|
|
11
|
+
populate<Entity extends this = this, Hint extends string = never>(populate: AutoPath<Entity, Hint, PopulatePath.ALL>[] | false, options?: EntityLoaderOptions<Entity>): Promise<Loaded<Entity, Hint>>;
|
|
11
12
|
toReference<Entity extends this = this>(): Ref<Entity> & LoadedReference<Loaded<Entity, AddEager<Entity>>>;
|
|
13
|
+
/**
|
|
14
|
+
* Converts the entity to a plain object representation.
|
|
15
|
+
*
|
|
16
|
+
* **Note on typing with `Loaded` entities:** When called on a `Loaded<Entity, 'relation'>` type,
|
|
17
|
+
* the return type will be `EntityDTO<Entity>` (with relations as primary keys), not
|
|
18
|
+
* `EntityDTO<Loaded<Entity, 'relation'>>` (with loaded relations as nested objects).
|
|
19
|
+
* This is a TypeScript limitation - the `this` type resolves to the class, not the `Loaded` wrapper.
|
|
20
|
+
*
|
|
21
|
+
* For correct typing that reflects loaded relations, use `wrap()`:
|
|
22
|
+
* ```ts
|
|
23
|
+
* const result = await em.find(User, {}, { populate: ['profile'] });
|
|
24
|
+
* // Type: EntityDTO<User> (profile is number)
|
|
25
|
+
* const obj1 = result[0].toObject();
|
|
26
|
+
* // Type: EntityDTO<Loaded<User, 'profile'>> (profile is nested object)
|
|
27
|
+
* const obj2 = wrap(result[0]).toObject();
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* Runtime values are correct in both cases - only the static types differ.
|
|
31
|
+
*/
|
|
12
32
|
toObject<Entity extends this = this>(): EntityDTO<Entity>;
|
|
33
|
+
/**
|
|
34
|
+
* Converts the entity to a plain object representation.
|
|
35
|
+
*
|
|
36
|
+
* **Note on typing with `Loaded` entities:** When called on a `Loaded<Entity, 'relation'>` type,
|
|
37
|
+
* the return type will be `EntityDTO<Entity>` (with relations as primary keys), not
|
|
38
|
+
* `EntityDTO<Loaded<Entity, 'relation'>>` (with loaded relations as nested objects).
|
|
39
|
+
* This is a TypeScript limitation - the `this` type resolves to the class, not the `Loaded` wrapper.
|
|
40
|
+
*
|
|
41
|
+
* For correct typing that reflects loaded relations, use `wrap()`:
|
|
42
|
+
* ```ts
|
|
43
|
+
* const result = await em.find(User, {}, { populate: ['profile'] });
|
|
44
|
+
* // Type: EntityDTO<User> (profile is number)
|
|
45
|
+
* const obj1 = result[0].toObject();
|
|
46
|
+
* // Type: EntityDTO<Loaded<User, 'profile'>> (profile is nested object)
|
|
47
|
+
* const obj2 = wrap(result[0]).toObject();
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* Runtime values are correct in both cases - only the static types differ.
|
|
51
|
+
*/
|
|
13
52
|
toObject<Entity extends this = this>(ignoreFields: never[]): EntityDTO<Entity>;
|
|
53
|
+
/**
|
|
54
|
+
* Converts the entity to a plain object representation.
|
|
55
|
+
*
|
|
56
|
+
* **Note on typing with `Loaded` entities:** When called on a `Loaded<Entity, 'relation'>` type,
|
|
57
|
+
* the return type will be `EntityDTO<Entity>` (with relations as primary keys), not
|
|
58
|
+
* `EntityDTO<Loaded<Entity, 'relation'>>` (with loaded relations as nested objects).
|
|
59
|
+
* This is a TypeScript limitation - the `this` type resolves to the class, not the `Loaded` wrapper.
|
|
60
|
+
*
|
|
61
|
+
* For correct typing that reflects loaded relations, use `wrap()`:
|
|
62
|
+
* ```ts
|
|
63
|
+
* const result = await em.find(User, {}, { populate: ['profile'] });
|
|
64
|
+
* // Type: EntityDTO<User> (profile is number)
|
|
65
|
+
* const obj1 = result[0].toObject();
|
|
66
|
+
* // Type: EntityDTO<Loaded<User, 'profile'>> (profile is nested object)
|
|
67
|
+
* const obj2 = wrap(result[0]).toObject();
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* Runtime values are correct in both cases - only the static types differ.
|
|
71
|
+
*
|
|
72
|
+
* @param ignoreFields - Array of field names to omit from the result.
|
|
73
|
+
*/
|
|
14
74
|
toObject<Entity extends this = this, Ignored extends EntityKey<Entity> = never>(ignoreFields: Ignored[]): Omit<EntityDTO<Entity>, Ignored>;
|
|
15
75
|
toPOJO<Entity extends this = this>(): EntityDTO<Entity>;
|
|
16
76
|
serialize<Entity extends this = this, Naked extends FromEntityType<Entity> = FromEntityType<Entity>, Hint extends string = never, Exclude extends string = never>(options?: SerializeOptions<Naked, Hint, Exclude>): EntityDTO<Loaded<Naked, Hint>>;
|
package/entity/Collection.d.ts
CHANGED
|
@@ -62,7 +62,6 @@ export declare class Collection<T extends object, O extends object = object> {
|
|
|
62
62
|
init<TT extends T, P extends string = never>(options?: InitCollectionOptions<TT, P>): Promise<LoadedCollection<Loaded<TT, P>>>;
|
|
63
63
|
private getEntityManager;
|
|
64
64
|
private createCondition;
|
|
65
|
-
private createOrderBy;
|
|
66
65
|
private createManyToManyCondition;
|
|
67
66
|
private createLoadCountCondition;
|
|
68
67
|
private checkInitialized;
|
|
@@ -104,10 +103,18 @@ export declare class Collection<T extends object, O extends object = object> {
|
|
|
104
103
|
* Tests for the existence of an element that satisfies the given predicate.
|
|
105
104
|
*/
|
|
106
105
|
exists(cb: (item: T) => boolean): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Returns the first element of this collection that satisfies the predicate.
|
|
108
|
+
*/
|
|
109
|
+
find<S extends T>(cb: (item: T, index: number) => item is S): S | undefined;
|
|
107
110
|
/**
|
|
108
111
|
* Returns the first element of this collection that satisfies the predicate.
|
|
109
112
|
*/
|
|
110
113
|
find(cb: (item: T, index: number) => boolean): T | undefined;
|
|
114
|
+
/**
|
|
115
|
+
* Extracts a subset of the collection items.
|
|
116
|
+
*/
|
|
117
|
+
filter<S extends T>(cb: (item: T, index: number) => item is S): S[];
|
|
111
118
|
/**
|
|
112
119
|
* Extracts a subset of the collection items.
|
|
113
120
|
*/
|