@mikro-orm/core 7.0.0-dev.5 → 7.0.0-dev.50
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 +81 -27
- package/EntityManager.js +287 -175
- package/MikroORM.d.ts +6 -6
- package/MikroORM.js +31 -74
- package/README.md +3 -2
- package/cache/FileCacheAdapter.d.ts +2 -1
- package/cache/FileCacheAdapter.js +6 -4
- package/connections/Connection.d.ts +9 -5
- package/connections/Connection.js +16 -13
- package/decorators/Embedded.d.ts +5 -11
- package/decorators/Entity.d.ts +18 -3
- package/decorators/Indexed.d.ts +2 -2
- package/decorators/ManyToMany.d.ts +2 -0
- package/decorators/ManyToOne.d.ts +4 -0
- package/decorators/OneToOne.d.ts +4 -0
- package/decorators/Property.d.ts +53 -9
- package/decorators/Transactional.d.ts +1 -0
- package/decorators/Transactional.js +3 -3
- package/decorators/index.d.ts +1 -1
- package/drivers/DatabaseDriver.d.ts +10 -5
- package/drivers/DatabaseDriver.js +4 -4
- package/drivers/IDatabaseDriver.d.ts +28 -4
- package/entity/ArrayCollection.d.ts +6 -4
- package/entity/ArrayCollection.js +26 -9
- package/entity/BaseEntity.d.ts +0 -1
- package/entity/BaseEntity.js +0 -3
- package/entity/Collection.d.ts +3 -4
- package/entity/Collection.js +37 -17
- package/entity/EntityAssigner.d.ts +1 -1
- package/entity/EntityAssigner.js +9 -1
- package/entity/EntityFactory.d.ts +7 -0
- package/entity/EntityFactory.js +29 -11
- package/entity/EntityHelper.js +25 -8
- package/entity/EntityLoader.d.ts +5 -4
- package/entity/EntityLoader.js +69 -36
- package/entity/EntityRepository.d.ts +1 -1
- package/entity/EntityValidator.js +1 -1
- package/entity/Reference.d.ts +9 -7
- package/entity/Reference.js +30 -3
- package/entity/WrappedEntity.d.ts +0 -2
- package/entity/WrappedEntity.js +1 -5
- package/entity/defineEntity.d.ts +555 -0
- package/entity/defineEntity.js +529 -0
- package/entity/index.d.ts +2 -0
- package/entity/index.js +2 -0
- package/entity/utils.d.ts +7 -0
- package/entity/utils.js +15 -3
- package/enums.d.ts +16 -3
- package/enums.js +13 -0
- package/errors.d.ts +6 -1
- package/errors.js +14 -4
- package/events/EventSubscriber.d.ts +3 -1
- package/hydration/ObjectHydrator.d.ts +4 -4
- package/hydration/ObjectHydrator.js +35 -24
- package/index.d.ts +2 -1
- package/index.js +1 -1
- package/logging/DefaultLogger.d.ts +1 -1
- package/logging/SimpleLogger.d.ts +1 -1
- package/metadata/EntitySchema.d.ts +8 -4
- package/metadata/EntitySchema.js +39 -19
- package/metadata/MetadataDiscovery.d.ts +4 -4
- package/metadata/MetadataDiscovery.js +139 -122
- package/metadata/MetadataStorage.js +1 -1
- package/metadata/MetadataValidator.js +4 -3
- package/naming-strategy/AbstractNamingStrategy.d.ts +5 -1
- package/naming-strategy/AbstractNamingStrategy.js +7 -1
- package/naming-strategy/NamingStrategy.d.ts +11 -1
- package/package.json +5 -5
- package/platforms/Platform.d.ts +5 -3
- package/platforms/Platform.js +4 -8
- package/serialization/EntitySerializer.d.ts +2 -0
- package/serialization/EntitySerializer.js +23 -5
- package/serialization/EntityTransformer.js +16 -6
- package/serialization/SerializationContext.js +14 -11
- package/types/BigIntType.d.ts +9 -6
- package/types/BigIntType.js +3 -0
- package/types/BooleanType.d.ts +1 -1
- package/types/DecimalType.d.ts +6 -4
- package/types/DecimalType.js +1 -1
- package/types/DoubleType.js +1 -1
- package/types/JsonType.d.ts +1 -1
- package/types/JsonType.js +7 -2
- package/types/Type.d.ts +2 -1
- package/types/Type.js +1 -1
- package/types/index.d.ts +1 -1
- package/typings.d.ts +89 -49
- package/typings.js +31 -31
- package/unit-of-work/ChangeSetComputer.js +8 -3
- package/unit-of-work/ChangeSetPersister.d.ts +4 -2
- package/unit-of-work/ChangeSetPersister.js +37 -16
- package/unit-of-work/UnitOfWork.d.ts +8 -1
- package/unit-of-work/UnitOfWork.js +110 -53
- package/utils/AbstractSchemaGenerator.js +3 -1
- package/utils/Configuration.d.ts +29 -16
- package/utils/Configuration.js +17 -18
- package/utils/ConfigurationLoader.d.ts +9 -22
- package/utils/ConfigurationLoader.js +49 -72
- package/utils/Cursor.d.ts +3 -3
- package/utils/Cursor.js +3 -0
- package/utils/DataloaderUtils.d.ts +7 -2
- package/utils/DataloaderUtils.js +38 -7
- package/utils/EntityComparator.d.ts +6 -2
- package/utils/EntityComparator.js +104 -58
- package/utils/QueryHelper.d.ts +9 -1
- package/utils/QueryHelper.js +66 -5
- package/utils/RawQueryFragment.d.ts +36 -4
- package/utils/RawQueryFragment.js +34 -13
- package/utils/TransactionManager.d.ts +65 -0
- package/utils/TransactionManager.js +223 -0
- package/utils/Utils.d.ts +13 -11
- package/utils/Utils.js +82 -55
- package/utils/index.d.ts +1 -0
- package/utils/index.js +1 -0
- package/utils/upsert-utils.d.ts +7 -2
- package/utils/upsert-utils.js +52 -1
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import dotenv from 'dotenv';
|
|
2
2
|
import { realpathSync } from 'node:fs';
|
|
3
|
-
import { platform } from 'node:os';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
3
|
import { colors } from '../logging/colors.js';
|
|
6
4
|
import { Configuration } from './Configuration.js';
|
|
7
5
|
import { Utils } from './Utils.js';
|
|
@@ -9,31 +7,14 @@ import { Utils } from './Utils.js';
|
|
|
9
7
|
* @internal
|
|
10
8
|
*/
|
|
11
9
|
export class ConfigurationLoader {
|
|
10
|
+
/**
|
|
11
|
+
* Gets a named configuration
|
|
12
|
+
*
|
|
13
|
+
* @param contextName Load a config with the given `contextName` value. Used when config file exports array or factory function. Setting it to "default" matches also config objects without `contextName` set.
|
|
14
|
+
* @param paths Array of possible paths for a configuration file. Files will be checked in order, and the first existing one will be used. Defaults to the output of {@link ConfigurationLoader.getConfigPaths}.
|
|
15
|
+
* @param options Additional options to augment the final configuration with.
|
|
16
|
+
*/
|
|
12
17
|
static async getConfiguration(contextName = 'default', paths = ConfigurationLoader.getConfigPaths(), options = {}) {
|
|
13
|
-
// Backwards compatibility layer
|
|
14
|
-
if (typeof contextName === 'boolean' || !Array.isArray(paths)) {
|
|
15
|
-
this.commonJSCompat(options);
|
|
16
|
-
this.registerDotenv(options);
|
|
17
|
-
const configPathFromArg = ConfigurationLoader.configPathsFromArg();
|
|
18
|
-
const configPaths = configPathFromArg ?? (Array.isArray(paths) ? paths : ConfigurationLoader.getConfigPaths());
|
|
19
|
-
const config = contextName
|
|
20
|
-
? (await ConfigurationLoader.getConfiguration(process.env.MIKRO_ORM_CONTEXT_NAME ?? 'default', configPaths, Array.isArray(paths) ? {} : paths))
|
|
21
|
-
: await (async () => {
|
|
22
|
-
const env = await this.loadEnvironmentVars();
|
|
23
|
-
const [path, tmp] = await this.getConfigFile(configPaths);
|
|
24
|
-
if (!path) {
|
|
25
|
-
if (Utils.hasObjectKeys(env)) {
|
|
26
|
-
return new Configuration(Utils.mergeConfig({}, options, env), false);
|
|
27
|
-
}
|
|
28
|
-
throw new Error(`MikroORM config file not found in ['${configPaths.join(`', '`)}']`);
|
|
29
|
-
}
|
|
30
|
-
return new Configuration(Utils.mergeConfig(tmp, options, env), false);
|
|
31
|
-
})();
|
|
32
|
-
if (configPathFromArg) {
|
|
33
|
-
config.getLogger().warn('deprecated', 'Path for config file was inferred from the command line arguments. Instead, you should set the MIKRO_ORM_CLI_CONFIG environment variable to specify the path, or if you really must use the command line arguments, import the config manually based on them, and pass it to init.', { label: 'D0001' });
|
|
34
|
-
}
|
|
35
|
-
return config;
|
|
36
|
-
}
|
|
37
18
|
const env = await this.loadEnvironmentVars();
|
|
38
19
|
const configFinder = (cfg) => {
|
|
39
20
|
return typeof cfg === 'object' && cfg !== null && ('contextName' in cfg ? cfg.contextName === contextName : (contextName === 'default'));
|
|
@@ -129,31 +110,23 @@ export class ConfigurationLoader {
|
|
|
129
110
|
const settings = { ...config['mikro-orm'] };
|
|
130
111
|
const bool = (v) => ['true', 't', '1'].includes(v.toLowerCase());
|
|
131
112
|
settings.preferTs = process.env.MIKRO_ORM_CLI_PREFER_TS != null ? bool(process.env.MIKRO_ORM_CLI_PREFER_TS) : settings.preferTs;
|
|
113
|
+
settings.tsLoader = process.env.MIKRO_ORM_CLI_TS_LOADER ?? settings.tsLoader;
|
|
132
114
|
settings.tsConfigPath = process.env.MIKRO_ORM_CLI_TS_CONFIG_PATH ?? settings.tsConfigPath;
|
|
133
|
-
settings.alwaysAllowTs = process.env.MIKRO_ORM_CLI_ALWAYS_ALLOW_TS != null ? bool(process.env.MIKRO_ORM_CLI_ALWAYS_ALLOW_TS) : settings.alwaysAllowTs;
|
|
134
115
|
settings.verbose = process.env.MIKRO_ORM_CLI_VERBOSE != null ? bool(process.env.MIKRO_ORM_CLI_VERBOSE) : settings.verbose;
|
|
135
116
|
if (process.env.MIKRO_ORM_CLI_CONFIG?.endsWith('.ts')) {
|
|
136
117
|
settings.preferTs = true;
|
|
137
118
|
}
|
|
138
119
|
return settings;
|
|
139
120
|
}
|
|
140
|
-
static configPathsFromArg() {
|
|
141
|
-
const options = Utils.parseArgs();
|
|
142
|
-
const configArgName = process.env.MIKRO_ORM_CONFIG_ARG_NAME ?? 'config';
|
|
143
|
-
if (options[configArgName]) {
|
|
144
|
-
return [options[configArgName]];
|
|
145
|
-
}
|
|
146
|
-
return undefined;
|
|
147
|
-
}
|
|
148
121
|
static getConfigPaths() {
|
|
149
|
-
const paths = [];
|
|
150
122
|
const settings = ConfigurationLoader.getSettings();
|
|
123
|
+
const typeScriptSupport = settings.preferTs ?? Utils.detectTypeScriptSupport();
|
|
124
|
+
const paths = [];
|
|
151
125
|
if (process.env.MIKRO_ORM_CLI_CONFIG) {
|
|
152
126
|
paths.push(process.env.MIKRO_ORM_CLI_CONFIG);
|
|
153
127
|
}
|
|
154
128
|
paths.push(...(settings.configPaths || []));
|
|
155
|
-
|
|
156
|
-
if (settings.preferTs !== false || alwaysAllowTs) {
|
|
129
|
+
if (typeScriptSupport) {
|
|
157
130
|
paths.push('./src/mikro-orm.config.ts');
|
|
158
131
|
paths.push('./mikro-orm.config.ts');
|
|
159
132
|
}
|
|
@@ -163,36 +136,59 @@ export class ConfigurationLoader {
|
|
|
163
136
|
const path = distDir ? 'dist' : (buildDir ? 'build' : 'src');
|
|
164
137
|
paths.push(`./${path}/mikro-orm.config.js`);
|
|
165
138
|
paths.push('./mikro-orm.config.js');
|
|
166
|
-
const typeScriptSupport = Utils.detectTypeScriptSupport();
|
|
167
139
|
/* v8 ignore next */
|
|
168
|
-
return Utils.unique(paths).filter(p => p.
|
|
140
|
+
return Utils.unique(paths).filter(p => !p.match(/\.[mc]?ts$/) || typeScriptSupport);
|
|
169
141
|
}
|
|
170
142
|
static isESM() {
|
|
171
143
|
const config = ConfigurationLoader.getPackageConfig();
|
|
172
144
|
const type = config?.type ?? '';
|
|
173
145
|
return type === 'module';
|
|
174
146
|
}
|
|
175
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Tries to register TS support in the following order: swc, tsx, jiti, tsimp
|
|
149
|
+
* Use `MIKRO_ORM_CLI_TS_LOADER` env var to set the loader explicitly.
|
|
150
|
+
* This method is used only in CLI context.
|
|
151
|
+
*/
|
|
152
|
+
static async registerTypeScriptSupport(configPath = 'tsconfig.json', tsLoader) {
|
|
176
153
|
/* v8 ignore next 3 */
|
|
177
154
|
if (process.versions.bun) {
|
|
178
155
|
return true;
|
|
179
156
|
}
|
|
180
157
|
process.env.SWC_NODE_PROJECT ??= configPath;
|
|
158
|
+
process.env.TSIMP_PROJECT ??= configPath;
|
|
181
159
|
process.env.MIKRO_ORM_CLI_ALWAYS_ALLOW_TS ??= '1';
|
|
182
|
-
const
|
|
183
|
-
/* v8 ignore next
|
|
184
|
-
const importMethod =
|
|
185
|
-
const
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
160
|
+
const isEsm = this.isESM();
|
|
161
|
+
/* v8 ignore next */
|
|
162
|
+
const importMethod = isEsm ? 'tryImport' : 'tryRequire';
|
|
163
|
+
const explicitLoader = tsLoader ?? process.env.MIKRO_ORM_CLI_TS_LOADER ?? 'auto';
|
|
164
|
+
const loaders = {
|
|
165
|
+
swc: { esm: '@swc-node/register/esm-register', cjs: '@swc-node/register' },
|
|
166
|
+
tsx: { esm: 'tsx/esm/api', cjs: 'tsx/cjs/api', cb: (tsx) => tsx.register({ tsconfig: configPath }) },
|
|
167
|
+
jiti: { esm: 'jiti/register', cjs: 'jiti/register', cb: () => Utils.setDynamicImportProvider(id => import(id).then(mod => mod?.default ?? mod)) },
|
|
168
|
+
tsimp: { esm: 'tsimp/import', cjs: 'tsimp/import' },
|
|
169
|
+
};
|
|
170
|
+
for (const loader of Utils.keys(loaders)) {
|
|
171
|
+
if (explicitLoader !== 'auto' && loader !== explicitLoader) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const { esm, cjs, cb } = loaders[loader];
|
|
175
|
+
/* v8 ignore next */
|
|
176
|
+
const module = isEsm ? esm : cjs;
|
|
177
|
+
const mod = await Utils[importMethod]({ module });
|
|
178
|
+
if (mod) {
|
|
179
|
+
cb?.(mod);
|
|
180
|
+
process.env.MIKRO_ORM_CLI_TS_LOADER = loader;
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// eslint-disable-next-line no-console
|
|
185
|
+
console.warn('Neither `swc`, `tsx`, `jiti` nor `tsimp` found in the project dependencies, support for working with TypeScript files might not work. To use `swc`, you need to install both `@swc-node/register` and `@swc/core`.');
|
|
186
|
+
return false;
|
|
191
187
|
}
|
|
192
188
|
static registerDotenv(options) {
|
|
193
|
-
const path = process.env.MIKRO_ORM_ENV ?? ((options
|
|
189
|
+
const path = process.env.MIKRO_ORM_ENV ?? ((options.baseDir ?? process.cwd()) + '/.env');
|
|
194
190
|
const env = {};
|
|
195
|
-
dotenv.config({ path, processEnv: env });
|
|
191
|
+
dotenv.config({ path, processEnv: env, quiet: true });
|
|
196
192
|
// only propagate known env vars
|
|
197
193
|
for (const key of Object.keys(env)) {
|
|
198
194
|
if (key.startsWith('MIKRO_ORM_')) {
|
|
@@ -300,25 +296,6 @@ export class ConfigurationLoader {
|
|
|
300
296
|
...Object.keys(pkg.devDependencies ?? {}),
|
|
301
297
|
]);
|
|
302
298
|
}
|
|
303
|
-
/** @internal */
|
|
304
|
-
static commonJSCompat(options) {
|
|
305
|
-
if (this.isESM()) {
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
/* v8 ignore next 11 */
|
|
309
|
-
options.dynamicImportProvider ??= id => {
|
|
310
|
-
if (platform() === 'win32') {
|
|
311
|
-
try {
|
|
312
|
-
id = fileURLToPath(id);
|
|
313
|
-
}
|
|
314
|
-
catch {
|
|
315
|
-
// ignore
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
return Utils.requireFrom(id);
|
|
319
|
-
};
|
|
320
|
-
Utils.setDynamicImportProvider(options.dynamicImportProvider);
|
|
321
|
-
}
|
|
322
299
|
static getORMPackageVersion(name) {
|
|
323
300
|
try {
|
|
324
301
|
const pkg = Utils.requireFrom(`${name}/package.json`);
|
|
@@ -332,7 +309,7 @@ export class ConfigurationLoader {
|
|
|
332
309
|
// inspired by https://github.com/facebook/docusaurus/pull/3386
|
|
333
310
|
static checkPackageVersion() {
|
|
334
311
|
const coreVersion = Utils.getORMVersion();
|
|
335
|
-
if (process.env.MIKRO_ORM_ALLOW_VERSION_MISMATCH) {
|
|
312
|
+
if (process.env.MIKRO_ORM_ALLOW_VERSION_MISMATCH || coreVersion === 'N/A') {
|
|
336
313
|
return coreVersion;
|
|
337
314
|
}
|
|
338
315
|
const deps = this.getORMPackages();
|
package/utils/Cursor.d.ts
CHANGED
|
@@ -49,13 +49,13 @@ import { type QueryOrder } from '../enums.js';
|
|
|
49
49
|
* }
|
|
50
50
|
* ```
|
|
51
51
|
*/
|
|
52
|
-
export declare class Cursor<Entity extends object, Hint extends string = never, Fields extends string = '*', Excludes extends string = never> {
|
|
52
|
+
export declare class Cursor<Entity extends object, Hint extends string = never, Fields extends string = '*', Excludes extends string = never, IncludeCount extends boolean = true> {
|
|
53
53
|
readonly items: Loaded<Entity, Hint, Fields, Excludes>[];
|
|
54
|
-
readonly totalCount: number;
|
|
54
|
+
readonly totalCount: IncludeCount extends true ? number : undefined;
|
|
55
55
|
readonly hasPrevPage: boolean;
|
|
56
56
|
readonly hasNextPage: boolean;
|
|
57
57
|
private readonly definition;
|
|
58
|
-
constructor(items: Loaded<Entity, Hint, Fields, Excludes>[], totalCount: number, options: FindByCursorOptions<Entity, Hint, Fields, Excludes>, meta: EntityMetadata<Entity>);
|
|
58
|
+
constructor(items: Loaded<Entity, Hint, Fields, Excludes>[], totalCount: IncludeCount extends true ? number : undefined, options: FindByCursorOptions<Entity, Hint, Fields, Excludes, IncludeCount>, meta: EntityMetadata<Entity>);
|
|
59
59
|
get startCursor(): string | null;
|
|
60
60
|
get endCursor(): string | null;
|
|
61
61
|
/**
|
package/utils/Cursor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import type DataLoader from 'dataloader';
|
|
1
2
|
import type { Primary, Ref } from '../typings.js';
|
|
2
3
|
import { Collection, type InitCollectionOptions } from '../entity/Collection.js';
|
|
3
4
|
import { type EntityManager } from '../EntityManager.js';
|
|
4
|
-
import type DataLoader from 'dataloader';
|
|
5
5
|
import { type LoadReferenceOptions } from '../entity/Reference.js';
|
|
6
6
|
export declare class DataloaderUtils {
|
|
7
7
|
/**
|
|
@@ -34,8 +34,13 @@ export declare class DataloaderUtils {
|
|
|
34
34
|
*/
|
|
35
35
|
static getColFilter<T, S extends T>(collection: Collection<any>): (result: T) => result is S;
|
|
36
36
|
/**
|
|
37
|
-
* Returns the collection dataloader batchLoadFn, which aggregates collections by entity,
|
|
37
|
+
* Returns the 1:M collection dataloader batchLoadFn, which aggregates collections by entity,
|
|
38
38
|
* makes one query per entity and maps each input collection to the corresponding result.
|
|
39
39
|
*/
|
|
40
40
|
static getColBatchLoadFn(em: EntityManager): DataLoader.BatchLoadFn<[Collection<any>, Omit<InitCollectionOptions<any, any>, 'dataloader'>?], any>;
|
|
41
|
+
/**
|
|
42
|
+
* Returns the M:N collection dataloader batchLoadFn, which aggregates collections by entity,
|
|
43
|
+
* makes one query per entity and maps each input collection to the corresponding result.
|
|
44
|
+
*/
|
|
45
|
+
static getManyToManyColBatchLoadFn(em: EntityManager): DataLoader.BatchLoadFn<[Collection<any>, Omit<InitCollectionOptions<any, any>, 'dataloader'>?], any>;
|
|
41
46
|
}
|
package/utils/DataloaderUtils.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Collection } from '../entity/Collection.js';
|
|
2
2
|
import { helper } from '../entity/wrap.js';
|
|
3
|
-
import { ReferenceKind } from '../enums.js';
|
|
4
3
|
import { Reference } from '../entity/Reference.js';
|
|
4
|
+
import { Utils } from './Utils.js';
|
|
5
5
|
export class DataloaderUtils {
|
|
6
6
|
/**
|
|
7
7
|
* Groups identified references by entity and returns a Map with the
|
|
@@ -118,7 +118,6 @@ export class DataloaderUtils {
|
|
|
118
118
|
// We need to populate the inverse side of the relationship in order to be able to later retrieve the PK(s) from its item(s)
|
|
119
119
|
populate: [
|
|
120
120
|
...(opts.populate === false ? [] : opts.populate ?? []),
|
|
121
|
-
...(opts.ref ? [':ref'] : []),
|
|
122
121
|
...Array.from(filterMap.keys()).filter(
|
|
123
122
|
// 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
123
|
prop => em.getMetadata(className).properties[prop]?.ref !== true),
|
|
@@ -149,15 +148,11 @@ export class DataloaderUtils {
|
|
|
149
148
|
else if (target) {
|
|
150
149
|
return target === collection.owner;
|
|
151
150
|
}
|
|
152
|
-
// FIXME https://github.com/mikro-orm/mikro-orm/issues/6031
|
|
153
|
-
if (!target && collection.property.kind === ReferenceKind.MANY_TO_MANY) {
|
|
154
|
-
throw new Error(`Inverse side is required for M:N relations with dataloader: ${collection.owner.constructor.name}.${collection.property.name}`);
|
|
155
|
-
}
|
|
156
151
|
return false;
|
|
157
152
|
};
|
|
158
153
|
}
|
|
159
154
|
/**
|
|
160
|
-
* Returns the collection dataloader batchLoadFn, which aggregates collections by entity,
|
|
155
|
+
* Returns the 1:M collection dataloader batchLoadFn, which aggregates collections by entity,
|
|
161
156
|
* makes one query per entity and maps each input collection to the corresponding result.
|
|
162
157
|
*/
|
|
163
158
|
static getColBatchLoadFn(em) {
|
|
@@ -179,4 +174,40 @@ export class DataloaderUtils {
|
|
|
179
174
|
});
|
|
180
175
|
};
|
|
181
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Returns the M:N collection dataloader batchLoadFn, which aggregates collections by entity,
|
|
179
|
+
* makes one query per entity and maps each input collection to the corresponding result.
|
|
180
|
+
*/
|
|
181
|
+
static getManyToManyColBatchLoadFn(em) {
|
|
182
|
+
return async (collsWithOpts) => {
|
|
183
|
+
const groups = new Map();
|
|
184
|
+
for (const [col, opts] of collsWithOpts) {
|
|
185
|
+
const key = `${col.property.targetMeta.className}.${col.property.name}|${JSON.stringify(opts ?? {})}`;
|
|
186
|
+
const value = groups.get(key) ?? [];
|
|
187
|
+
value.push([col, opts ?? {}]);
|
|
188
|
+
groups.set(key, value);
|
|
189
|
+
}
|
|
190
|
+
const ret = [];
|
|
191
|
+
for (const group of groups.values()) {
|
|
192
|
+
const prop = group[0][0].property;
|
|
193
|
+
const options = {};
|
|
194
|
+
const wrap = (cond) => ({ [prop.name]: cond });
|
|
195
|
+
const orderBy = Utils.asArray(group[0][1]?.orderBy).map(o => wrap(o));
|
|
196
|
+
const populate = wrap(group[0][1]?.populate);
|
|
197
|
+
const owners = group.map(c => c[0].owner);
|
|
198
|
+
const $or = [];
|
|
199
|
+
// a bit of a hack, but we need to prefix the key, since we have only a column name, not a property name
|
|
200
|
+
const alias = em.config.getNamingStrategy().aliasName(prop.pivotEntity, 0);
|
|
201
|
+
const fk = `${alias}.${Utils.getPrimaryKeyHash(prop.joinColumns)}`;
|
|
202
|
+
for (const c of group) {
|
|
203
|
+
$or.push({ $and: [c[1]?.where ?? {}, { [fk]: c[0].owner }] });
|
|
204
|
+
options.refresh ??= c[1]?.refresh;
|
|
205
|
+
}
|
|
206
|
+
options.where = wrap({ $or });
|
|
207
|
+
const r = await em.getEntityLoader().findChildrenFromPivotTable(owners, prop, options, orderBy, populate, group[0][1]?.ref);
|
|
208
|
+
ret.push(...r);
|
|
209
|
+
}
|
|
210
|
+
return ret;
|
|
211
|
+
};
|
|
212
|
+
}
|
|
182
213
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { EntityData, EntityDictionary, EntityMetadata, EntityProperty, IMetadataStorage } from '../typings.js';
|
|
2
2
|
import type { Platform } from '../platforms/Platform.js';
|
|
3
|
-
type Comparator<T> = (a: T, b: T
|
|
3
|
+
type Comparator<T> = (a: T, b: T, options?: {
|
|
4
|
+
includeInverseSides?: boolean;
|
|
5
|
+
}) => EntityData<T>;
|
|
4
6
|
type ResultMapper<T> = (result: EntityData<T>) => EntityData<T> | null;
|
|
5
7
|
type SnapshotGenerator<T> = (entity: T) => EntityData<T>;
|
|
6
8
|
type CompositeKeyPart = string | CompositeKeyPart[];
|
|
@@ -18,7 +20,9 @@ export declare class EntityComparator {
|
|
|
18
20
|
/**
|
|
19
21
|
* Computes difference between two entities.
|
|
20
22
|
*/
|
|
21
|
-
diffEntities<T>(entityName: string, a: EntityData<T>, b: EntityData<T
|
|
23
|
+
diffEntities<T>(entityName: string, a: EntityData<T>, b: EntityData<T>, options?: {
|
|
24
|
+
includeInverseSides?: boolean;
|
|
25
|
+
}): EntityData<T>;
|
|
22
26
|
matching<T>(entityName: string, a: EntityData<T>, b: EntityData<T>): boolean;
|
|
23
27
|
/**
|
|
24
28
|
* Removes ORM specific code from entities and prepares it for serializing. Used before change set computation.
|
|
@@ -20,9 +20,9 @@ export class EntityComparator {
|
|
|
20
20
|
/**
|
|
21
21
|
* Computes difference between two entities.
|
|
22
22
|
*/
|
|
23
|
-
diffEntities(entityName, a, b) {
|
|
23
|
+
diffEntities(entityName, a, b, options) {
|
|
24
24
|
const comparator = this.getEntityComparator(entityName);
|
|
25
|
-
return Utils.callCompiledFunction(comparator, a, b);
|
|
25
|
+
return Utils.callCompiledFunction(comparator, a, b, options);
|
|
26
26
|
}
|
|
27
27
|
matching(entityName, a, b) {
|
|
28
28
|
const diff = this.diffEntities(entityName, a, b);
|
|
@@ -155,6 +155,7 @@ export class EntityComparator {
|
|
|
155
155
|
const lines = [];
|
|
156
156
|
const context = new Map();
|
|
157
157
|
context.set('getCompositeKeyValue', (val) => Utils.flatten(Utils.getCompositeKeyValue(val, meta, 'convertToDatabaseValue', this.platform)));
|
|
158
|
+
context.set('getPrimaryKeyHash', (val) => Utils.getPrimaryKeyHash(Utils.asArray(val)));
|
|
158
159
|
if (meta.primaryKeys.length > 1) {
|
|
159
160
|
lines.push(` const pks = entity.__helper.__pk ? getCompositeKeyValue(entity.__helper.__pk) : [`);
|
|
160
161
|
meta.primaryKeys.forEach(pk => {
|
|
@@ -170,14 +171,23 @@ export class EntityComparator {
|
|
|
170
171
|
}
|
|
171
172
|
else {
|
|
172
173
|
const pk = meta.primaryKeys[0];
|
|
173
|
-
|
|
174
|
+
const prop = meta.properties[pk];
|
|
175
|
+
if (prop.kind !== ReferenceKind.SCALAR) {
|
|
174
176
|
lines.push(` if (entity${this.wrap(pk)} != null && (entity${this.wrap(pk)}.__entity || entity${this.wrap(pk)}.__reference)) return entity${this.wrap(pk)}.__helper.getSerializedPrimaryKey();`);
|
|
175
177
|
}
|
|
176
178
|
const serializedPrimaryKey = meta.props.find(p => p.serializedPrimaryKey);
|
|
177
179
|
if (serializedPrimaryKey) {
|
|
178
180
|
lines.push(` return '' + entity.${serializedPrimaryKey.name};`);
|
|
179
181
|
}
|
|
180
|
-
|
|
182
|
+
else if (prop.customType) {
|
|
183
|
+
const convertorKey = this.registerCustomType(meta.properties[pk], context);
|
|
184
|
+
const idx = this.tmpIndex++;
|
|
185
|
+
lines.push(` const val_${idx} = convertToDatabaseValue_${convertorKey}(entity${this.wrap(pk)});`);
|
|
186
|
+
lines.push(` return getPrimaryKeyHash(val_${idx});`);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
lines.push(` return '' + entity${this.wrap(pk)};`);
|
|
190
|
+
}
|
|
181
191
|
}
|
|
182
192
|
const code = `// compiled pk serializer for entity ${meta.className}\n`
|
|
183
193
|
+ `return function(entity) {\n${lines.join('\n')}\n}`;
|
|
@@ -286,57 +296,73 @@ export class EntityComparator {
|
|
|
286
296
|
lines.push(`${padding} }`);
|
|
287
297
|
};
|
|
288
298
|
lines.push(` const mapped = {};`);
|
|
289
|
-
meta
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
299
|
+
const mapEntityProperties = (meta, padding = '') => {
|
|
300
|
+
for (const prop of meta.props) {
|
|
301
|
+
if (!prop.fieldNames) {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (prop.targetMeta && prop.fieldNames.length > 1) {
|
|
305
|
+
lines.push(`${padding} if (${prop.fieldNames.map(field => `typeof ${this.propName(field)} === 'undefined'`).join(' && ')}) {`);
|
|
306
|
+
lines.push(`${padding} } else if (${prop.fieldNames.map(field => `${this.propName(field)} != null`).join(' && ')}) {`);
|
|
307
|
+
lines.push(`${padding} ret${this.wrap(prop.name)} = ${this.createCompositeKeyArray(prop)};`);
|
|
308
|
+
lines.push(...prop.fieldNames.map(field => `${padding} ${this.propName(field, 'mapped')} = true;`));
|
|
309
|
+
lines.push(`${padding} } else if (${prop.fieldNames.map(field => `${this.propName(field)} == null`).join(' && ')}) {\n ret${this.wrap(prop.name)} = null;`);
|
|
310
|
+
lines.push(...prop.fieldNames.map(field => `${padding} ${this.propName(field, 'mapped')} = true;`), ' }');
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (prop.embedded && (meta.embeddable || meta.properties[prop.embedded[0]].object)) {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (prop.runtimeType === 'boolean') {
|
|
317
|
+
lines.push(`${padding} if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
|
|
318
|
+
lines.push(`${padding} ret${this.wrap(prop.name)} = ${this.propName(prop.fieldNames[0])} == null ? ${this.propName(prop.fieldNames[0])} : !!${this.propName(prop.fieldNames[0])};`);
|
|
319
|
+
lines.push(`${padding} ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
|
|
320
|
+
lines.push(`${padding} }`);
|
|
321
|
+
}
|
|
322
|
+
else if (prop.runtimeType === 'Date' && !this.platform.isNumericProperty(prop)) {
|
|
323
|
+
lines.push(`${padding} if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
|
|
324
|
+
context.set('parseDate', (value) => this.platform.parseDate(value));
|
|
325
|
+
parseDate('ret' + this.wrap(prop.name), this.propName(prop.fieldNames[0]), padding);
|
|
326
|
+
lines.push(`${padding} ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
|
|
327
|
+
lines.push(`${padding} }`);
|
|
328
|
+
}
|
|
329
|
+
else if (prop.kind === ReferenceKind.EMBEDDED && (prop.object || meta.embeddable)) {
|
|
330
|
+
const idx = this.tmpIndex++;
|
|
331
|
+
context.set(`mapEmbeddedResult_${idx}`, (data) => {
|
|
332
|
+
const item = parseJsonSafe(data);
|
|
333
|
+
if (Array.isArray(item)) {
|
|
334
|
+
return item.map(row => row == null ? row : this.getResultMapper(prop.type)(row));
|
|
335
|
+
}
|
|
336
|
+
return item == null ? item : this.getResultMapper(prop.type)(item);
|
|
337
|
+
});
|
|
338
|
+
lines.push(`${padding} if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
|
|
339
|
+
lines.push(`${padding} ret${this.wrap(prop.name)} = ${this.propName(prop.fieldNames[0])} == null ? ${this.propName(prop.fieldNames[0])} : mapEmbeddedResult_${idx}(${this.propName(prop.fieldNames[0])});`);
|
|
340
|
+
lines.push(`${padding} ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
|
|
341
|
+
lines.push(`${padding} }`);
|
|
342
|
+
}
|
|
343
|
+
else if (prop.kind !== ReferenceKind.EMBEDDED) {
|
|
344
|
+
lines.push(`${padding} if (typeof ${this.propName(prop.fieldNames[0])} !== 'undefined') {`);
|
|
345
|
+
lines.push(`${padding} ret${this.wrap(prop.name)} = ${this.propName(prop.fieldNames[0])};`);
|
|
346
|
+
lines.push(`${padding} ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
|
|
347
|
+
lines.push(`${padding} }`);
|
|
348
|
+
}
|
|
331
349
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
350
|
+
};
|
|
351
|
+
if (meta.polymorphs && meta.discriminatorColumn) {
|
|
352
|
+
for (const polymorph of meta.polymorphs) {
|
|
353
|
+
const first = polymorph === meta.polymorphs[0];
|
|
354
|
+
lines.push(` ${first ? '' : 'else '}if (${this.propName(meta.discriminatorColumn)} == '${polymorph.discriminatorValue}') {`);
|
|
355
|
+
mapEntityProperties(polymorph, ' ');
|
|
336
356
|
lines.push(` }`);
|
|
337
357
|
}
|
|
338
|
-
|
|
339
|
-
|
|
358
|
+
lines.push(` else {`);
|
|
359
|
+
mapEntityProperties(meta, ' ');
|
|
360
|
+
lines.push(` }`);
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
mapEntityProperties(meta);
|
|
364
|
+
}
|
|
365
|
+
lines.push(` for (let k in result) { if (Object.hasOwn(result, k) && !mapped[k] && ret[k] === undefined) ret[k] = result[k]; }`);
|
|
340
366
|
const code = `// compiled mapper for entity ${meta.className}\n`
|
|
341
367
|
+ `return function(result) {\n const ret = {};\n${lines.join('\n')}\n return ret;\n}`;
|
|
342
368
|
const resultMapper = Utils.createFunction(context, code);
|
|
@@ -391,11 +417,21 @@ export class EntityComparator {
|
|
|
391
417
|
}
|
|
392
418
|
getEmbeddedPropertySnapshot(meta, prop, context, level, path, dataKey, object = prop.object) {
|
|
393
419
|
const padding = ' '.repeat(level * 2);
|
|
420
|
+
const nullCond = `entity${path.map(k => this.wrap(k)).join('')} === null`;
|
|
394
421
|
let ret = `${level === 1 ? '' : '\n'}`;
|
|
395
422
|
if (object) {
|
|
396
|
-
const nullCond = `entity${path.map(k => this.wrap(k)).join('')} === null`;
|
|
397
423
|
ret += `${padding}if (${nullCond}) ret${dataKey} = null;\n`;
|
|
398
424
|
}
|
|
425
|
+
else {
|
|
426
|
+
ret += `${padding}if (${nullCond}) {\n`;
|
|
427
|
+
ret += meta.props.filter(p => p.embedded?.[0] === prop.name
|
|
428
|
+
// object for JSON embeddable
|
|
429
|
+
&& (p.object || (p.persist !== false))).map(childProp => {
|
|
430
|
+
const childDataKey = meta.embeddable || prop.object ? dataKey + this.wrap(childProp.embedded[1]) : this.wrap(childProp.name);
|
|
431
|
+
return `${padding} ret${childDataKey} = null;`;
|
|
432
|
+
}).join('\n') + `\n`;
|
|
433
|
+
ret += `${padding}}\n`;
|
|
434
|
+
}
|
|
399
435
|
const cond = `entity${path.map(k => this.wrap(k)).join('')} != null`;
|
|
400
436
|
ret += `${padding}if (${cond}) {\n`;
|
|
401
437
|
if (object) {
|
|
@@ -515,17 +551,27 @@ export class EntityComparator {
|
|
|
515
551
|
context.set('compareBuffers', compareBuffers);
|
|
516
552
|
context.set('compareObjects', compareObjects);
|
|
517
553
|
context.set('equals', equals);
|
|
518
|
-
meta.comparableProps
|
|
554
|
+
for (const prop of meta.comparableProps) {
|
|
519
555
|
lines.push(this.getPropertyComparator(prop, context));
|
|
520
|
-
}
|
|
556
|
+
}
|
|
557
|
+
// also compare 1:1 inverse sides, important for `factory.mergeData`
|
|
558
|
+
lines.push(`if (options?.includeInverseSides) {`);
|
|
559
|
+
for (const prop of meta.bidirectionalRelations) {
|
|
560
|
+
if (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && prop.hydrate !== false) {
|
|
561
|
+
lines.push(this.getPropertyComparator(prop, context));
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
lines.push(`}`);
|
|
521
565
|
const code = `// compiled comparator for entity ${meta.className}\n`
|
|
522
|
-
+ `return function(last, current) {\n const diff = {};\n${lines.join('\n')}\n return diff;\n}`;
|
|
566
|
+
+ `return function(last, current, options) {\n const diff = {};\n${lines.join('\n')}\n return diff;\n}`;
|
|
523
567
|
const comparator = Utils.createFunction(context, code);
|
|
524
568
|
this.comparators.set(entityName, comparator);
|
|
525
569
|
return comparator;
|
|
526
570
|
}
|
|
527
571
|
getGenericComparator(prop, cond) {
|
|
528
|
-
return ` if (current${prop}
|
|
572
|
+
return ` if (current${prop} === null && last${prop} === undefined) {\n` +
|
|
573
|
+
` diff${prop} = current${prop};\n` +
|
|
574
|
+
` } else if (current${prop} == null && last${prop} == null) {\n\n` +
|
|
529
575
|
` } else if ((current${prop} != null && last${prop} == null) || (current${prop} == null && last${prop} != null)) {\n` +
|
|
530
576
|
` diff${prop} = current${prop};\n` +
|
|
531
577
|
` } else if (${cond}) {\n` +
|
|
@@ -591,7 +637,7 @@ export class EntityComparator {
|
|
|
591
637
|
* perf: used to generate list of comparable properties during discovery, so we speed up the runtime comparison
|
|
592
638
|
*/
|
|
593
639
|
static isComparable(prop, root) {
|
|
594
|
-
const virtual = prop.persist === false || prop.generated;
|
|
640
|
+
const virtual = prop.persist === false || (prop.generated && !prop.primary);
|
|
595
641
|
const inverse = prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner;
|
|
596
642
|
const discriminator = prop.name === root.discriminatorColumn;
|
|
597
643
|
const collection = prop.kind === ReferenceKind.ONE_TO_MANY || prop.kind === ReferenceKind.MANY_TO_MANY;
|
package/utils/QueryHelper.d.ts
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import type { Dictionary, EntityMetadata, EntityProperty, FilterDef, FilterQuery } from '../typings.js';
|
|
2
2
|
import type { Platform } from '../platforms/Platform.js';
|
|
3
3
|
import type { MetadataStorage } from '../metadata/MetadataStorage.js';
|
|
4
|
+
import type { FilterOptions } from '../drivers/IDatabaseDriver.js';
|
|
5
|
+
/** @internal */
|
|
4
6
|
export declare class QueryHelper {
|
|
5
7
|
static readonly SUPPORTED_OPERATORS: string[];
|
|
6
8
|
static processParams(params: unknown): any;
|
|
7
9
|
static processObjectParams<T extends object>(params?: T): T;
|
|
10
|
+
/**
|
|
11
|
+
* converts `{ account: { $or: [ [Object], [Object] ] } }`
|
|
12
|
+
* to `{ $or: [ { account: [Object] }, { account: [Object] } ] }`
|
|
13
|
+
*/
|
|
14
|
+
static liftGroupOperators<T extends object>(where: Dictionary, meta: EntityMetadata<T>, metadata: MetadataStorage, key?: string): string | undefined;
|
|
8
15
|
static inlinePrimaryKeyObjects<T extends object>(where: Dictionary, meta: EntityMetadata<T>, metadata: MetadataStorage, key?: string): boolean;
|
|
9
16
|
static processWhere<T extends object>(options: ProcessWhereOptions<T>): FilterQuery<T>;
|
|
10
|
-
static getActiveFilters(entityName: string, options:
|
|
17
|
+
static getActiveFilters(entityName: string, options: FilterOptions | undefined, filters: Dictionary<FilterDef>): FilterDef[];
|
|
18
|
+
static mergePropertyFilters(propFilters: FilterOptions | undefined, options: FilterOptions | undefined): FilterOptions | undefined;
|
|
11
19
|
static isFilterActive(entityName: string, filterName: string, filter: FilterDef, options: Dictionary<boolean | Dictionary>): boolean;
|
|
12
20
|
static processCustomType<T extends object>(prop: EntityProperty<T>, cond: FilterQuery<T>, platform: Platform, key?: string, fromQuery?: boolean): FilterQuery<T>;
|
|
13
21
|
private static isSupportedOperator;
|