@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.
- package/EntityManager.d.ts +34 -17
- package/EntityManager.js +95 -103
- 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 +50 -20
- 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 +89 -50
- package/entity/defineEntity.js +12 -0
- package/entity/index.d.ts +1 -0
- package/entity/index.js +1 -0
- package/entity/utils.d.ts +6 -1
- package/entity/utils.js +33 -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 +325 -110
- 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.d.ts +3 -6
- package/unit-of-work/ChangeSetComputer.js +34 -13
- package/unit-of-work/ChangeSetPersister.d.ts +12 -10
- package/unit-of-work/ChangeSetPersister.js +55 -25
- 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 +203 -56
- 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 +106 -11
- 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, PopulateHintOptions, Prefixes } 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
|
|
@@ -107,6 +107,10 @@ export interface FindOptions<Entity, Hint extends string = never, Fields extends
|
|
|
107
107
|
populateFilter?: ObjectQuery<Entity>;
|
|
108
108
|
/** Used for ordering of the populate queries. If not specified, the value of `options.orderBy` is used. */
|
|
109
109
|
populateOrderBy?: OrderDefinition<Entity>;
|
|
110
|
+
/** Per-relation overrides for populate loading behavior. Keys are populate paths (same as used in `populate`). */
|
|
111
|
+
populateHints?: [Hint] extends [never] ? never : {
|
|
112
|
+
[K in Prefixes<Hint>]?: PopulateHintOptions;
|
|
113
|
+
};
|
|
110
114
|
/** Ordering of the results.Can be an object or array of objects, keys are property names, values are ordering (asc/desc) */
|
|
111
115
|
orderBy?: OrderDefinition<Entity>;
|
|
112
116
|
/** Control result caching for this query. Result cache is by default disabled, not to be confused with the identity map. */
|
|
@@ -144,7 +148,7 @@ export interface FindOptions<Entity, Hint extends string = never, Fields extends
|
|
|
144
148
|
flags?: QueryFlag[];
|
|
145
149
|
/** sql only */
|
|
146
150
|
groupBy?: string | string[];
|
|
147
|
-
having?:
|
|
151
|
+
having?: FilterQuery<Entity>;
|
|
148
152
|
/** sql only */
|
|
149
153
|
strategy?: LoadStrategy | `${LoadStrategy}`;
|
|
150
154
|
flushMode?: FlushMode | `${FlushMode}`;
|
|
@@ -155,18 +159,24 @@ export interface FindOptions<Entity, Hint extends string = never, Fields extends
|
|
|
155
159
|
lockTableAliases?: string[];
|
|
156
160
|
ctx?: Transaction;
|
|
157
161
|
connectionType?: ConnectionType;
|
|
158
|
-
/**
|
|
159
|
-
indexHint?: string;
|
|
162
|
+
/** SQL: appended to FROM clause (e.g. `'force index(my_index)'`); MongoDB: index name or spec passed as `hint`. */
|
|
163
|
+
indexHint?: string | Dictionary;
|
|
160
164
|
/** sql only */
|
|
161
165
|
comments?: string | string[];
|
|
162
166
|
/** sql only */
|
|
163
167
|
hintComments?: string | string[];
|
|
168
|
+
/** SQL: collation name string applied as COLLATE to ORDER BY; MongoDB: CollationOptions object. */
|
|
169
|
+
collation?: CollationOptions | string;
|
|
170
|
+
/** mongodb only */
|
|
171
|
+
maxTimeMS?: number;
|
|
172
|
+
/** mongodb only */
|
|
173
|
+
allowDiskUse?: boolean;
|
|
164
174
|
loggerContext?: LogContext;
|
|
165
175
|
logging?: LoggingOptions;
|
|
166
176
|
/** @internal used to apply filters to the auto-joined relations */
|
|
167
177
|
em?: EntityManager;
|
|
168
178
|
}
|
|
169
|
-
export interface FindByCursorOptions<T extends object, P extends string = never, F extends string = '*', E extends string = never, I extends boolean = true> extends Omit<
|
|
179
|
+
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
180
|
includeCount?: I;
|
|
171
181
|
}
|
|
172
182
|
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 +199,11 @@ export interface NativeInsertUpdateManyOptions<T> extends NativeInsertUpdateOpti
|
|
|
189
199
|
processCollections?: boolean;
|
|
190
200
|
}
|
|
191
201
|
export interface UpsertOptions<Entity, Fields extends string = never> extends Omit<NativeInsertUpdateOptions<Entity>, 'upsert'> {
|
|
192
|
-
onConflictFields?: (keyof Entity)[] |
|
|
202
|
+
onConflictFields?: (keyof Entity)[] | Raw;
|
|
193
203
|
onConflictAction?: 'ignore' | 'merge';
|
|
194
204
|
onConflictMergeFields?: AutoPath<Entity, Fields, `${PopulatePath.ALL}`>[];
|
|
195
205
|
onConflictExcludeFields?: AutoPath<Entity, Fields, `${PopulatePath.ALL}`>[];
|
|
206
|
+
onConflictWhere?: FilterQuery<Entity>;
|
|
196
207
|
disableIdentityMap?: boolean;
|
|
197
208
|
}
|
|
198
209
|
export interface UpsertManyOptions<Entity, Fields extends string = never> extends UpsertOptions<Entity, Fields> {
|
|
@@ -202,7 +213,7 @@ export interface CountOptions<T extends object, P extends string = never> {
|
|
|
202
213
|
filters?: FilterOptions;
|
|
203
214
|
schema?: string;
|
|
204
215
|
groupBy?: string | readonly string[];
|
|
205
|
-
having?:
|
|
216
|
+
having?: FilterQuery<T>;
|
|
206
217
|
cache?: boolean | number | [string, number];
|
|
207
218
|
populate?: Populate<T, P>;
|
|
208
219
|
populateWhere?: ObjectQuery<T> | PopulateHint | `${PopulateHint}`;
|
|
@@ -210,12 +221,16 @@ export interface CountOptions<T extends object, P extends string = never> {
|
|
|
210
221
|
ctx?: Transaction;
|
|
211
222
|
connectionType?: ConnectionType;
|
|
212
223
|
flushMode?: FlushMode | `${FlushMode}`;
|
|
213
|
-
/**
|
|
214
|
-
indexHint?: string;
|
|
224
|
+
/** SQL: appended to FROM clause (e.g. `'force index(my_index)'`); MongoDB: index name or spec passed as `hint`. */
|
|
225
|
+
indexHint?: string | Dictionary;
|
|
215
226
|
/** sql only */
|
|
216
227
|
comments?: string | string[];
|
|
217
228
|
/** sql only */
|
|
218
229
|
hintComments?: string | string[];
|
|
230
|
+
/** SQL: collation name string applied as COLLATE; MongoDB: CollationOptions object. */
|
|
231
|
+
collation?: CollationOptions | string;
|
|
232
|
+
/** mongodb only */
|
|
233
|
+
maxTimeMS?: number;
|
|
219
234
|
loggerContext?: LogContext;
|
|
220
235
|
logging?: LoggingOptions;
|
|
221
236
|
/** @internal used to apply filters to the auto-joined relations */
|
|
@@ -243,8 +258,23 @@ export interface DriverMethodOptions {
|
|
|
243
258
|
schema?: string;
|
|
244
259
|
loggerContext?: LogContext;
|
|
245
260
|
}
|
|
261
|
+
export interface CollationOptions {
|
|
262
|
+
locale: string;
|
|
263
|
+
caseLevel?: boolean;
|
|
264
|
+
caseFirst?: string;
|
|
265
|
+
strength?: number;
|
|
266
|
+
numericOrdering?: boolean;
|
|
267
|
+
alternate?: string;
|
|
268
|
+
maxVariable?: string;
|
|
269
|
+
backwards?: boolean;
|
|
270
|
+
}
|
|
246
271
|
export interface GetReferenceOptions {
|
|
247
272
|
wrapped?: boolean;
|
|
248
273
|
convertCustomTypes?: boolean;
|
|
249
274
|
schema?: string;
|
|
275
|
+
/**
|
|
276
|
+
* Property name to use for identity map lookup instead of the primary key.
|
|
277
|
+
* This is useful for creating references by unique non-PK properties.
|
|
278
|
+
*/
|
|
279
|
+
key?: string;
|
|
250
280
|
}
|
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>>;
|