@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
package/utils/QueryHelper.js
CHANGED
|
@@ -4,6 +4,7 @@ import { GroupOperator, ReferenceKind } from '../enums.js';
|
|
|
4
4
|
import { JsonType } from '../types/JsonType.js';
|
|
5
5
|
import { helper } from '../entity/wrap.js';
|
|
6
6
|
import { RawQueryFragment, isRaw } from './RawQueryFragment.js';
|
|
7
|
+
/** @internal */
|
|
7
8
|
export class QueryHelper {
|
|
8
9
|
static SUPPORTED_OPERATORS = ['>', '<', '<=', '>=', '!', '!='];
|
|
9
10
|
static processParams(params) {
|
|
@@ -33,11 +34,52 @@ export class QueryHelper {
|
|
|
33
34
|
});
|
|
34
35
|
return params;
|
|
35
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* converts `{ account: { $or: [ [Object], [Object] ] } }`
|
|
39
|
+
* to `{ $or: [ { account: [Object] }, { account: [Object] } ] }`
|
|
40
|
+
*/
|
|
41
|
+
static liftGroupOperators(where, meta, metadata, key) {
|
|
42
|
+
if (!Utils.isPlainObject(where)) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
const keys = Object.keys(where);
|
|
46
|
+
const groupOperator = keys.find(k => {
|
|
47
|
+
return Utils.isGroupOperator(k) && Array.isArray(where[k]) && where[k].every(cond => {
|
|
48
|
+
return Utils.isPlainObject(cond) && Object.keys(cond).every(k2 => {
|
|
49
|
+
if (Utils.isOperator(k2, false)) {
|
|
50
|
+
if (k2 === '$not') {
|
|
51
|
+
return Object.keys(cond[k2]).every(k3 => meta.primaryKeys.includes(k3));
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return meta.primaryKeys.includes(k2);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
if (groupOperator) {
|
|
60
|
+
return groupOperator;
|
|
61
|
+
}
|
|
62
|
+
for (const k of keys) {
|
|
63
|
+
const value = where[k];
|
|
64
|
+
const prop = meta.properties[k];
|
|
65
|
+
if (!prop || ![ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const op = this.liftGroupOperators(value, prop.targetMeta, metadata, k);
|
|
69
|
+
if (op) {
|
|
70
|
+
delete where[k];
|
|
71
|
+
where[op] = value[op].map((v) => {
|
|
72
|
+
return { [k]: v };
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
36
78
|
static inlinePrimaryKeyObjects(where, meta, metadata, key) {
|
|
37
79
|
if (Array.isArray(where)) {
|
|
38
80
|
where.forEach((item, i) => {
|
|
39
81
|
if (this.inlinePrimaryKeyObjects(item, meta, metadata, key)) {
|
|
40
|
-
where[i] = Utils.getPrimaryKeyValues(item, meta
|
|
82
|
+
where[i] = Utils.getPrimaryKeyValues(item, meta, false);
|
|
41
83
|
}
|
|
42
84
|
});
|
|
43
85
|
}
|
|
@@ -45,9 +87,9 @@ export class QueryHelper {
|
|
|
45
87
|
return false;
|
|
46
88
|
}
|
|
47
89
|
if (meta.primaryKeys.every(pk => pk in where) && Utils.getObjectKeysSize(where) === meta.primaryKeys.length) {
|
|
48
|
-
return !!key && !GroupOperator[key] && Object.keys(where).every(k => !Utils.isPlainObject(where[k]) || Object.keys(where[k]).every(v => {
|
|
90
|
+
return !!key && !GroupOperator[key] && key !== '$not' && Object.keys(where).every(k => !Utils.isPlainObject(where[k]) || Object.keys(where[k]).every(v => {
|
|
49
91
|
if (Utils.isOperator(v, false)) {
|
|
50
|
-
return
|
|
92
|
+
return true;
|
|
51
93
|
}
|
|
52
94
|
if (meta.properties[k].primary && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(meta.properties[k].kind)) {
|
|
53
95
|
return this.inlinePrimaryKeyObjects(where[k], meta.properties[k].targetMeta, metadata, v);
|
|
@@ -58,7 +100,7 @@ export class QueryHelper {
|
|
|
58
100
|
Object.keys(where).forEach(k => {
|
|
59
101
|
const meta2 = metadata.find(meta.properties[k]?.type) || meta;
|
|
60
102
|
if (this.inlinePrimaryKeyObjects(where[k], meta2, metadata, k)) {
|
|
61
|
-
where[k] = Utils.getPrimaryKeyValues(where[k], meta2
|
|
103
|
+
where[k] = Utils.getPrimaryKeyValues(where[k], meta2, true);
|
|
62
104
|
}
|
|
63
105
|
});
|
|
64
106
|
return false;
|
|
@@ -69,6 +111,7 @@ export class QueryHelper {
|
|
|
69
111
|
const meta = metadata.find(entityName);
|
|
70
112
|
// inline PK-only objects in M:N queries, so we don't join the target entity when not needed
|
|
71
113
|
if (meta && root) {
|
|
114
|
+
QueryHelper.liftGroupOperators(where, meta, metadata);
|
|
72
115
|
QueryHelper.inlinePrimaryKeyObjects(where, meta, metadata);
|
|
73
116
|
}
|
|
74
117
|
if (platform.getConfig().get('ignoreUndefinedInQuery') && where && typeof where === 'object') {
|
|
@@ -118,7 +161,7 @@ export class QueryHelper {
|
|
|
118
161
|
value = QueryHelper.processCustomType(prop, value, platform, undefined, true);
|
|
119
162
|
}
|
|
120
163
|
const isJsonProperty = prop?.customType instanceof JsonType && Utils.isPlainObject(value) && !isRaw(value) && Object.keys(value)[0] !== '$eq';
|
|
121
|
-
if (isJsonProperty) {
|
|
164
|
+
if (isJsonProperty && prop?.kind !== ReferenceKind.EMBEDDED) {
|
|
122
165
|
return this.processJsonCondition(o, value, [prop.fieldNames[0]], platform, aliased);
|
|
123
166
|
}
|
|
124
167
|
if (Array.isArray(value) && !Utils.isOperator(key) && !QueryHelper.isSupportedOperator(key) && !key.includes('?') && options.type !== 'orderBy') {
|
|
@@ -159,6 +202,24 @@ export class QueryHelper {
|
|
|
159
202
|
return filters[f];
|
|
160
203
|
});
|
|
161
204
|
}
|
|
205
|
+
static mergePropertyFilters(propFilters, options) {
|
|
206
|
+
if (!options || !propFilters || options === true || propFilters === true) {
|
|
207
|
+
return options ?? propFilters;
|
|
208
|
+
}
|
|
209
|
+
if (Array.isArray(propFilters)) {
|
|
210
|
+
propFilters = propFilters.reduce((o, item) => {
|
|
211
|
+
o[item] = true;
|
|
212
|
+
return o;
|
|
213
|
+
}, {});
|
|
214
|
+
}
|
|
215
|
+
if (Array.isArray(options)) {
|
|
216
|
+
options = options.reduce((o, item) => {
|
|
217
|
+
o[item] = true;
|
|
218
|
+
return o;
|
|
219
|
+
}, {});
|
|
220
|
+
}
|
|
221
|
+
return Utils.mergeConfig({}, propFilters, options);
|
|
222
|
+
}
|
|
162
223
|
static isFilterActive(entityName, filterName, filter, options) {
|
|
163
224
|
if (filter.entity && !filter.entity.includes(entityName)) {
|
|
164
225
|
return false;
|
|
@@ -10,8 +10,6 @@ export declare class RawQueryFragment {
|
|
|
10
10
|
valueOf(): string;
|
|
11
11
|
toJSON(): string;
|
|
12
12
|
toString(): string;
|
|
13
|
-
/** @internal */
|
|
14
|
-
assign(): void;
|
|
15
13
|
clone(): RawQueryFragment;
|
|
16
14
|
static run<T>(cb: (...args: any[]) => Promise<T>): Promise<T>;
|
|
17
15
|
/**
|
|
@@ -72,8 +70,26 @@ export declare const ALIAS_REPLACEMENT_RE = "\\[::alias::\\]";
|
|
|
72
70
|
* ```ts
|
|
73
71
|
* @Filter({ name: 'long', cond: () => ({ [raw('length(perex)')]: { $gt: 10000 } }) })
|
|
74
72
|
* ```
|
|
73
|
+
*
|
|
74
|
+
* The `raw` helper can be used within indexes and uniques to write database-agnostic SQL expressions. In that case, you can use `'??'` to tag your database identifiers (table name, column names, index name, ...) inside your expression, and pass those identifiers as a second parameter to the `raw` helper. Internally, those will automatically be quoted according to the database in use:
|
|
75
|
+
*
|
|
76
|
+
* ```ts
|
|
77
|
+
* // On postgres, will produce: create index "index custom_idx_on_name" on "library.author" ("country")
|
|
78
|
+
* // On mysql, will produce: create index `index custom_idx_on_name` on `library.author` (`country`)
|
|
79
|
+
* @Index({ name: 'custom_idx_on_name', expression: (table, columns) => raw(`create index ?? on ?? (??)`, ['custom_idx_on_name', table, columns.name]) })
|
|
80
|
+
* @Entity({ schema: 'library' })
|
|
81
|
+
* export class Author { ... }
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* You can also use the `quote` tag function to write database-agnostic SQL expressions. The end-result is the same as using the `raw` function regarding database identifiers quoting, only to have a more elegant expression syntax:
|
|
85
|
+
*
|
|
86
|
+
* ```ts
|
|
87
|
+
* @Index({ name: 'custom_idx_on_name', expression: (table, columns) => quote`create index ${'custom_idx_on_name'} on ${table} (${columns.name})` })
|
|
88
|
+
* @Entity({ schema: 'library' })
|
|
89
|
+
* export class Author { ... }
|
|
90
|
+
* ```
|
|
75
91
|
*/
|
|
76
|
-
export declare function raw<T extends object = any, R = any>(sql: EntityKey<T> | EntityKey<T>[] | AnyString | ((alias: string) => string) | RawQueryFragment, params?: readonly unknown[] | Dictionary<unknown>): R
|
|
92
|
+
export declare function raw<T extends object = any, R = any>(sql: EntityKey<T> | EntityKey<T>[] | AnyString | ((alias: string) => string) | RawQueryFragment, params?: readonly unknown[] | Dictionary<unknown>): NoInfer<R>;
|
|
77
93
|
/**
|
|
78
94
|
* Alternative to the `raw()` helper allowing to use it as a tagged template function for the simple cases.
|
|
79
95
|
*
|
|
@@ -90,9 +106,25 @@ export declare function raw<T extends object = any, R = any>(sql: EntityKey<T> |
|
|
|
90
106
|
*/
|
|
91
107
|
export declare function sql(sql: readonly string[], ...values: unknown[]): any;
|
|
92
108
|
export declare namespace sql {
|
|
93
|
-
var ref: <T extends object>(...keys: string[]) => RawQueryFragment
|
|
109
|
+
var ref: <T extends object>(...keys: string[]) => NoInfer<RawQueryFragment>;
|
|
94
110
|
var now: (length?: number) => string;
|
|
95
111
|
var lower: <T extends object>(key: string | ((alias: string) => string)) => string;
|
|
96
112
|
var upper: <T extends object>(key: string | ((alias: string) => string)) => string;
|
|
97
113
|
}
|
|
98
114
|
export declare function createSqlFunction<T extends object, R = string>(func: string, key: string | ((alias: string) => string)): R;
|
|
115
|
+
/**
|
|
116
|
+
* Tag function providing quoting of db identifiers (table name, columns names, index names, ...).
|
|
117
|
+
*
|
|
118
|
+
* Within the template literal on which the tag function is applied, all placeholders are considered to be database identifiers, and will thus be quoted as so according to the database in use.
|
|
119
|
+
*
|
|
120
|
+
* ```ts
|
|
121
|
+
* // On postgres, will produce: create index "index custom_idx_on_name" on "library.author" ("name")
|
|
122
|
+
* // On mysql, will produce: create index `index custom_idx_on_name` on `library.author` (`name`)
|
|
123
|
+
* @Index({ name: 'custom_idx_on_name', expression: (table, columns) => quote`create index ${'custom_idx_on_name'} on ${table} (${columns.name})` })
|
|
124
|
+
* @Entity({ schema: 'library' })
|
|
125
|
+
* export class Author { ... }
|
|
126
|
+
* ```
|
|
127
|
+
*/
|
|
128
|
+
export declare function quote(expParts: readonly string[], ...values: (string | {
|
|
129
|
+
toString(): string;
|
|
130
|
+
})[]): any;
|
|
@@ -8,7 +8,6 @@ export class RawQueryFragment {
|
|
|
8
8
|
static #storage = new AsyncLocalStorage();
|
|
9
9
|
static #index = 0n;
|
|
10
10
|
static cloneRegistry;
|
|
11
|
-
#assigned = false;
|
|
12
11
|
#used = 0;
|
|
13
12
|
#key;
|
|
14
13
|
constructor(sql, params = []) {
|
|
@@ -17,11 +16,6 @@ export class RawQueryFragment {
|
|
|
17
16
|
this.#key = `[raw]: ${this.sql} (#${RawQueryFragment.#index++})`;
|
|
18
17
|
}
|
|
19
18
|
as(alias) {
|
|
20
|
-
// TODO: to be removed in v7
|
|
21
|
-
/* istanbul ignore next */
|
|
22
|
-
if (alias.startsWith('`') || alias.startsWith('"')) {
|
|
23
|
-
return new RawQueryFragment(`${this.sql} as ${alias}`, this.params);
|
|
24
|
-
}
|
|
25
19
|
return new RawQueryFragment(`${this.sql} as ??`, [...this.params, alias]);
|
|
26
20
|
}
|
|
27
21
|
valueOf() {
|
|
@@ -35,13 +29,6 @@ export class RawQueryFragment {
|
|
|
35
29
|
this.#used++;
|
|
36
30
|
return this.#key;
|
|
37
31
|
}
|
|
38
|
-
/** @internal */
|
|
39
|
-
assign() {
|
|
40
|
-
if (this.#assigned) {
|
|
41
|
-
throw new Error(`Cannot reassign already used RawQueryFragment: '${this.sql}'`);
|
|
42
|
-
}
|
|
43
|
-
this.#assigned = true;
|
|
44
|
-
}
|
|
45
32
|
clone() {
|
|
46
33
|
RawQueryFragment.cloneRegistry?.add(this.#key);
|
|
47
34
|
return new RawQueryFragment(this.sql, this.params);
|
|
@@ -147,6 +134,24 @@ export const ALIAS_REPLACEMENT_RE = '\\[::alias::\\]';
|
|
|
147
134
|
* ```ts
|
|
148
135
|
* @Filter({ name: 'long', cond: () => ({ [raw('length(perex)')]: { $gt: 10000 } }) })
|
|
149
136
|
* ```
|
|
137
|
+
*
|
|
138
|
+
* The `raw` helper can be used within indexes and uniques to write database-agnostic SQL expressions. In that case, you can use `'??'` to tag your database identifiers (table name, column names, index name, ...) inside your expression, and pass those identifiers as a second parameter to the `raw` helper. Internally, those will automatically be quoted according to the database in use:
|
|
139
|
+
*
|
|
140
|
+
* ```ts
|
|
141
|
+
* // On postgres, will produce: create index "index custom_idx_on_name" on "library.author" ("country")
|
|
142
|
+
* // On mysql, will produce: create index `index custom_idx_on_name` on `library.author` (`country`)
|
|
143
|
+
* @Index({ name: 'custom_idx_on_name', expression: (table, columns) => raw(`create index ?? on ?? (??)`, ['custom_idx_on_name', table, columns.name]) })
|
|
144
|
+
* @Entity({ schema: 'library' })
|
|
145
|
+
* export class Author { ... }
|
|
146
|
+
* ```
|
|
147
|
+
*
|
|
148
|
+
* You can also use the `quote` tag function to write database-agnostic SQL expressions. The end-result is the same as using the `raw` function regarding database identifiers quoting, only to have a more elegant expression syntax:
|
|
149
|
+
*
|
|
150
|
+
* ```ts
|
|
151
|
+
* @Index({ name: 'custom_idx_on_name', expression: (table, columns) => quote`create index ${'custom_idx_on_name'} on ${table} (${columns.name})` })
|
|
152
|
+
* @Entity({ schema: 'library' })
|
|
153
|
+
* export class Author { ... }
|
|
154
|
+
* ```
|
|
150
155
|
*/
|
|
151
156
|
export function raw(sql, params) {
|
|
152
157
|
if (sql instanceof RawQueryFragment) {
|
|
@@ -205,3 +210,19 @@ sql.ref = (...keys) => raw('??', [keys.join('.')]);
|
|
|
205
210
|
sql.now = (length) => raw('current_timestamp' + (length == null ? '' : `(${length})`));
|
|
206
211
|
sql.lower = (key) => createSqlFunction('lower', key);
|
|
207
212
|
sql.upper = (key) => createSqlFunction('upper', key);
|
|
213
|
+
/**
|
|
214
|
+
* Tag function providing quoting of db identifiers (table name, columns names, index names, ...).
|
|
215
|
+
*
|
|
216
|
+
* Within the template literal on which the tag function is applied, all placeholders are considered to be database identifiers, and will thus be quoted as so according to the database in use.
|
|
217
|
+
*
|
|
218
|
+
* ```ts
|
|
219
|
+
* // On postgres, will produce: create index "index custom_idx_on_name" on "library.author" ("name")
|
|
220
|
+
* // On mysql, will produce: create index `index custom_idx_on_name` on `library.author` (`name`)
|
|
221
|
+
* @Index({ name: 'custom_idx_on_name', expression: (table, columns) => quote`create index ${'custom_idx_on_name'} on ${table} (${columns.name})` })
|
|
222
|
+
* @Entity({ schema: 'library' })
|
|
223
|
+
* export class Author { ... }
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
export function quote(expParts, ...values) {
|
|
227
|
+
return raw(expParts.join('??'), values);
|
|
228
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { EntityManager } from '../EntityManager.js';
|
|
2
|
+
import { type TransactionOptions } from '../enums.js';
|
|
3
|
+
/**
|
|
4
|
+
* Manages transaction lifecycle and propagation for EntityManager.
|
|
5
|
+
*/
|
|
6
|
+
export declare class TransactionManager {
|
|
7
|
+
private readonly em;
|
|
8
|
+
constructor(em: EntityManager);
|
|
9
|
+
/**
|
|
10
|
+
* Main entry point for handling transactional operations with propagation support.
|
|
11
|
+
*/
|
|
12
|
+
handle<T>(cb: (em: EntityManager) => T | Promise<T>, options?: TransactionOptions): Promise<T>;
|
|
13
|
+
/**
|
|
14
|
+
* Executes the callback with the specified propagation type.
|
|
15
|
+
*/
|
|
16
|
+
private executeWithPropagation;
|
|
17
|
+
/**
|
|
18
|
+
* Suspends the current transaction and returns the suspended resources.
|
|
19
|
+
*/
|
|
20
|
+
private suspendTransaction;
|
|
21
|
+
/**
|
|
22
|
+
* Resumes a previously suspended transaction.
|
|
23
|
+
*/
|
|
24
|
+
private resumeTransaction;
|
|
25
|
+
/**
|
|
26
|
+
* Executes operation without transaction context.
|
|
27
|
+
*/
|
|
28
|
+
private executeWithoutTransaction;
|
|
29
|
+
/**
|
|
30
|
+
* Creates new independent transaction, suspending any existing one.
|
|
31
|
+
*/
|
|
32
|
+
private executeWithNewTransaction;
|
|
33
|
+
/**
|
|
34
|
+
* Creates new transaction context.
|
|
35
|
+
*/
|
|
36
|
+
private createNewTransaction;
|
|
37
|
+
/**
|
|
38
|
+
* Executes nested transaction with savepoint.
|
|
39
|
+
*/
|
|
40
|
+
private executeNestedTransaction;
|
|
41
|
+
/**
|
|
42
|
+
* Creates a fork of the EntityManager with the given options.
|
|
43
|
+
*/
|
|
44
|
+
private createFork;
|
|
45
|
+
/**
|
|
46
|
+
* Determines if changes should be propagated to the upper context.
|
|
47
|
+
*/
|
|
48
|
+
private shouldPropagateToUpperContext;
|
|
49
|
+
/**
|
|
50
|
+
* Merges entities from fork to parent EntityManager.
|
|
51
|
+
*/
|
|
52
|
+
private mergeEntitiesToParent;
|
|
53
|
+
/**
|
|
54
|
+
* Registers a deletion handler to unset entity identities after flush.
|
|
55
|
+
*/
|
|
56
|
+
private registerDeletionHandler;
|
|
57
|
+
/**
|
|
58
|
+
* Processes transaction execution.
|
|
59
|
+
*/
|
|
60
|
+
private processTransaction;
|
|
61
|
+
/**
|
|
62
|
+
* Executes transaction workflow with entity synchronization.
|
|
63
|
+
*/
|
|
64
|
+
private executeTransactionFlow;
|
|
65
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { ReferenceKind, TransactionPropagation } from '../enums.js';
|
|
2
|
+
import { TransactionEventBroadcaster } from '../events/TransactionEventBroadcaster.js';
|
|
3
|
+
import { TransactionContext } from '../utils/TransactionContext.js';
|
|
4
|
+
import { ChangeSetType } from '../unit-of-work/ChangeSet.js';
|
|
5
|
+
import { TransactionStateError } from '../errors.js';
|
|
6
|
+
import { helper } from '../entity/wrap.js';
|
|
7
|
+
/**
|
|
8
|
+
* Manages transaction lifecycle and propagation for EntityManager.
|
|
9
|
+
*/
|
|
10
|
+
export class TransactionManager {
|
|
11
|
+
em;
|
|
12
|
+
constructor(em) {
|
|
13
|
+
this.em = em;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Main entry point for handling transactional operations with propagation support.
|
|
17
|
+
*/
|
|
18
|
+
async handle(cb, options = {}) {
|
|
19
|
+
const em = this.em.getContext(false);
|
|
20
|
+
options.propagation ??= TransactionPropagation.NESTED;
|
|
21
|
+
options.ctx ??= em.getTransactionContext();
|
|
22
|
+
const hasExistingTransaction = !!em.getTransactionContext();
|
|
23
|
+
return this.executeWithPropagation(options.propagation, em, cb, options, hasExistingTransaction);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Executes the callback with the specified propagation type.
|
|
27
|
+
*/
|
|
28
|
+
async executeWithPropagation(propagation, em, cb, options, hasExistingTransaction) {
|
|
29
|
+
switch (propagation) {
|
|
30
|
+
case TransactionPropagation.NOT_SUPPORTED:
|
|
31
|
+
return this.executeWithoutTransaction(em, cb, options);
|
|
32
|
+
case TransactionPropagation.REQUIRES_NEW:
|
|
33
|
+
return this.executeWithNewTransaction(em, cb, options, hasExistingTransaction);
|
|
34
|
+
case TransactionPropagation.REQUIRED:
|
|
35
|
+
if (hasExistingTransaction) {
|
|
36
|
+
return cb(em);
|
|
37
|
+
}
|
|
38
|
+
return this.createNewTransaction(em, cb, options);
|
|
39
|
+
case TransactionPropagation.NESTED:
|
|
40
|
+
if (hasExistingTransaction) {
|
|
41
|
+
return this.executeNestedTransaction(em, cb, options);
|
|
42
|
+
}
|
|
43
|
+
return this.createNewTransaction(em, cb, options);
|
|
44
|
+
case TransactionPropagation.SUPPORTS:
|
|
45
|
+
if (hasExistingTransaction) {
|
|
46
|
+
return cb(em);
|
|
47
|
+
}
|
|
48
|
+
return this.executeWithoutTransaction(em, cb, options);
|
|
49
|
+
case TransactionPropagation.MANDATORY:
|
|
50
|
+
if (!hasExistingTransaction) {
|
|
51
|
+
throw TransactionStateError.requiredTransactionNotFound(propagation);
|
|
52
|
+
}
|
|
53
|
+
return cb(em);
|
|
54
|
+
case TransactionPropagation.NEVER:
|
|
55
|
+
if (hasExistingTransaction) {
|
|
56
|
+
throw TransactionStateError.transactionNotAllowed(propagation);
|
|
57
|
+
}
|
|
58
|
+
return this.executeWithoutTransaction(em, cb, options);
|
|
59
|
+
default:
|
|
60
|
+
throw TransactionStateError.invalidPropagation(propagation);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Suspends the current transaction and returns the suspended resources.
|
|
65
|
+
*/
|
|
66
|
+
suspendTransaction(em) {
|
|
67
|
+
const suspended = em.getTransactionContext();
|
|
68
|
+
em.resetTransactionContext();
|
|
69
|
+
return suspended;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Resumes a previously suspended transaction.
|
|
73
|
+
*/
|
|
74
|
+
resumeTransaction(em, suspended) {
|
|
75
|
+
if (suspended != null) {
|
|
76
|
+
em.setTransactionContext(suspended);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Executes operation without transaction context.
|
|
81
|
+
*/
|
|
82
|
+
async executeWithoutTransaction(em, cb, options) {
|
|
83
|
+
const suspended = this.suspendTransaction(em);
|
|
84
|
+
const fork = this.createFork(em, { ...options, disableTransactions: true });
|
|
85
|
+
const propagateToUpperContext = this.shouldPropagateToUpperContext(em);
|
|
86
|
+
try {
|
|
87
|
+
return await this.executeTransactionFlow(fork, cb, propagateToUpperContext, em);
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
this.resumeTransaction(em, suspended);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Creates new independent transaction, suspending any existing one.
|
|
95
|
+
*/
|
|
96
|
+
async executeWithNewTransaction(em, cb, options, hasExistingTransaction) {
|
|
97
|
+
const fork = this.createFork(em, options);
|
|
98
|
+
let suspended = null;
|
|
99
|
+
// Suspend existing transaction if present
|
|
100
|
+
if (hasExistingTransaction) {
|
|
101
|
+
suspended = this.suspendTransaction(em);
|
|
102
|
+
}
|
|
103
|
+
const newOptions = { ...options, ctx: undefined };
|
|
104
|
+
try {
|
|
105
|
+
return await this.processTransaction(em, fork, cb, newOptions);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
if (suspended != null) {
|
|
109
|
+
this.resumeTransaction(em, suspended);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Creates new transaction context.
|
|
115
|
+
*/
|
|
116
|
+
async createNewTransaction(em, cb, options) {
|
|
117
|
+
const fork = this.createFork(em, options);
|
|
118
|
+
return this.processTransaction(em, fork, cb, options);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Executes nested transaction with savepoint.
|
|
122
|
+
*/
|
|
123
|
+
async executeNestedTransaction(em, cb, options) {
|
|
124
|
+
const fork = this.createFork(em, options);
|
|
125
|
+
// Pass existing context to create savepoint
|
|
126
|
+
const nestedOptions = { ...options, ctx: em.getTransactionContext() };
|
|
127
|
+
return this.processTransaction(em, fork, cb, nestedOptions);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Creates a fork of the EntityManager with the given options.
|
|
131
|
+
*/
|
|
132
|
+
createFork(em, options) {
|
|
133
|
+
return em.fork({
|
|
134
|
+
clear: options.clear ?? false,
|
|
135
|
+
flushMode: options.flushMode,
|
|
136
|
+
cloneEventManager: true,
|
|
137
|
+
disableTransactions: options.ignoreNestedTransactions,
|
|
138
|
+
loggerContext: options.loggerContext,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Determines if changes should be propagated to the upper context.
|
|
143
|
+
*/
|
|
144
|
+
shouldPropagateToUpperContext(em) {
|
|
145
|
+
return !em.global || this.em.config.get('allowGlobalContext');
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Merges entities from fork to parent EntityManager.
|
|
149
|
+
*/
|
|
150
|
+
mergeEntitiesToParent(fork, parent) {
|
|
151
|
+
const parentUoW = parent.getUnitOfWork(false);
|
|
152
|
+
// perf: if parent is empty, we can just move all entities from the fork to skip the `em.merge` overhead
|
|
153
|
+
if (parentUoW.getIdentityMap().keys().length === 0) {
|
|
154
|
+
for (const entity of fork.getUnitOfWork(false).getIdentityMap()) {
|
|
155
|
+
parentUoW.getIdentityMap().store(entity);
|
|
156
|
+
helper(entity).__em = parent;
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
for (const entity of fork.getUnitOfWork(false).getIdentityMap()) {
|
|
161
|
+
const wrapped = helper(entity);
|
|
162
|
+
const meta = wrapped.__meta;
|
|
163
|
+
// eslint-disable-next-line dot-notation
|
|
164
|
+
const parentEntity = parentUoW.getById(meta.className, wrapped.getPrimaryKey(), parent['_schema'], true);
|
|
165
|
+
if (parentEntity && parentEntity !== entity) {
|
|
166
|
+
const parentWrapped = helper(parentEntity);
|
|
167
|
+
parentWrapped.__data = wrapped.__data;
|
|
168
|
+
parentWrapped.__originalEntityData = wrapped.__originalEntityData;
|
|
169
|
+
for (const prop of meta.hydrateProps) {
|
|
170
|
+
if (prop.kind === ReferenceKind.SCALAR) {
|
|
171
|
+
parentEntity[prop.name] = entity[prop.name];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
parentUoW.merge(entity, new Set([entity]));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Registers a deletion handler to unset entity identities after flush.
|
|
182
|
+
*/
|
|
183
|
+
registerDeletionHandler(fork, parent) {
|
|
184
|
+
fork.getEventManager().registerSubscriber({
|
|
185
|
+
afterFlush: (args) => {
|
|
186
|
+
const deletionChangeSets = args.uow.getChangeSets()
|
|
187
|
+
.filter(cs => cs.type === ChangeSetType.DELETE || cs.type === ChangeSetType.DELETE_EARLY);
|
|
188
|
+
for (const cs of deletionChangeSets) {
|
|
189
|
+
parent.getUnitOfWork(false).unsetIdentity(cs.entity);
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Processes transaction execution.
|
|
196
|
+
*/
|
|
197
|
+
async processTransaction(em, fork, cb, options) {
|
|
198
|
+
const propagateToUpperContext = this.shouldPropagateToUpperContext(em);
|
|
199
|
+
const eventBroadcaster = new TransactionEventBroadcaster(fork, undefined);
|
|
200
|
+
return TransactionContext.create(fork, () => fork.getConnection().transactional(async (trx) => {
|
|
201
|
+
fork.setTransactionContext(trx);
|
|
202
|
+
return this.executeTransactionFlow(fork, cb, propagateToUpperContext, em);
|
|
203
|
+
}, { ...options, eventBroadcaster }));
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Executes transaction workflow with entity synchronization.
|
|
207
|
+
*/
|
|
208
|
+
async executeTransactionFlow(fork, cb, propagateToUpperContext, parentEm) {
|
|
209
|
+
if (!propagateToUpperContext) {
|
|
210
|
+
const ret = await cb(fork);
|
|
211
|
+
await fork.flush();
|
|
212
|
+
return ret;
|
|
213
|
+
}
|
|
214
|
+
// Setup: Register deletion handler before execution
|
|
215
|
+
this.registerDeletionHandler(fork, parentEm);
|
|
216
|
+
// Execute callback and flush
|
|
217
|
+
const ret = await cb(fork);
|
|
218
|
+
await fork.flush();
|
|
219
|
+
// Synchronization: Merge entities back to the parent
|
|
220
|
+
this.mergeEntitiesToParent(fork, parentEm);
|
|
221
|
+
return ret;
|
|
222
|
+
}
|
|
223
|
+
}
|
package/utils/Utils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type GlobOptions } from 'tinyglobby';
|
|
2
2
|
import type { Dictionary, EntityData, EntityDictionary, EntityKey, EntityMetadata, EntityName, EntityProperty, IMetadataStorage, Primary } from '../typings.js';
|
|
3
3
|
import type { Collection } from '../entity/Collection.js';
|
|
4
4
|
import type { Platform } from '../platforms/Platform.js';
|
|
@@ -134,14 +134,14 @@ export declare class Utils {
|
|
|
134
134
|
static getCompositeKeyHash<T>(data: EntityData<T>, meta: EntityMetadata<T>, convertCustomTypes?: boolean, platform?: Platform, flat?: boolean): string;
|
|
135
135
|
static getPrimaryKeyHash(pks: (string | Buffer | Date)[]): string;
|
|
136
136
|
static splitPrimaryKeys<T extends object>(key: string): EntityKey<T>[];
|
|
137
|
-
static getPrimaryKeyValues<T>(entity: T, primaryKeys: string[]
|
|
137
|
+
static getPrimaryKeyValues<T>(entity: T, primaryKeys: string[] | EntityMetadata<T>, allowScalar?: boolean, convertCustomTypes?: boolean): any;
|
|
138
138
|
static getPrimaryKeyCond<T>(entity: T, primaryKeys: EntityKey<T>[]): Record<string, Primary<T>> | null;
|
|
139
139
|
/**
|
|
140
140
|
* Maps nested FKs from `[1, 2, 3]` to `[1, [2, 3]]`.
|
|
141
141
|
*/
|
|
142
142
|
static mapFlatCompositePrimaryKey(fk: Primary<any>[], prop: EntityProperty, fieldNames?: string[], idx?: number): Primary<any> | Primary<any>[];
|
|
143
143
|
static getPrimaryKeyCondFromArray<T extends object>(pks: Primary<T>[], meta: EntityMetadata<T>): Record<string, Primary<T>>;
|
|
144
|
-
static getOrderedPrimaryKeys<T>(id: Primary<T> | Record<string, Primary<T>>, meta: EntityMetadata<T>, platform?: Platform, convertCustomTypes?: boolean): Primary<T>[];
|
|
144
|
+
static getOrderedPrimaryKeys<T>(id: Primary<T> | Record<string, Primary<T>>, meta: EntityMetadata<T>, platform?: Platform, convertCustomTypes?: boolean, allowScalar?: boolean): Primary<T>[];
|
|
145
145
|
/**
|
|
146
146
|
* Checks whether given object is an entity instance.
|
|
147
147
|
*/
|
|
@@ -206,13 +206,13 @@ export declare class Utils {
|
|
|
206
206
|
* If either `path` or `baseDir` are `file:` URLs, they are converted to local paths.
|
|
207
207
|
*/
|
|
208
208
|
static absolutePath(path: string, baseDir?: string): string;
|
|
209
|
-
static hash(data: string, length?: number): string;
|
|
209
|
+
static hash(data: string, length?: number, algorithm?: 'md5' | 'sha256'): string;
|
|
210
210
|
static runIfNotEmpty(clause: () => any, data: any): void;
|
|
211
211
|
static defaultValue<T extends Dictionary>(prop: T, option: keyof T, defaultValue: any): void;
|
|
212
212
|
static findDuplicates<T>(items: T[]): T[];
|
|
213
213
|
static removeDuplicates<T>(items: T[]): T[];
|
|
214
214
|
static randomInt(min: number, max: number): number;
|
|
215
|
-
static pathExists(path: string, options?:
|
|
215
|
+
static pathExists(path: string, options?: GlobOptions): Promise<boolean>;
|
|
216
216
|
/**
|
|
217
217
|
* Extracts all possible values of a TS enum. Works with both string and numeric enums.
|
|
218
218
|
*/
|
|
@@ -230,6 +230,12 @@ export declare class Utils {
|
|
|
230
230
|
* @param [from] Location to start the node resolution
|
|
231
231
|
*/
|
|
232
232
|
static requireFrom<T extends Dictionary>(id: string, from?: string): T;
|
|
233
|
+
/**
|
|
234
|
+
* Resolve path to a module.
|
|
235
|
+
* @param id The module to require
|
|
236
|
+
* @param [from] Location to start the node resolution
|
|
237
|
+
*/
|
|
238
|
+
static resolveModulePath(id: string, from?: string): string;
|
|
233
239
|
static dynamicImport<T = any>(id: string): Promise<T>;
|
|
234
240
|
static setDynamicImportProvider(provider: (id: string) => Promise<unknown>): void;
|
|
235
241
|
static ensureDir(path: string): void;
|
|
@@ -246,19 +252,15 @@ export declare class Utils {
|
|
|
246
252
|
static setPayloadProperty<T>(entity: EntityDictionary<T>, meta: EntityMetadata<T>, prop: EntityProperty<T>, value: unknown, idx: number[]): void;
|
|
247
253
|
static tryRequire<T extends Dictionary = any>({ module, from, allowError, warning }: {
|
|
248
254
|
module: string;
|
|
249
|
-
warning
|
|
255
|
+
warning?: string;
|
|
250
256
|
from?: string;
|
|
251
257
|
allowError?: string;
|
|
252
258
|
}): T | undefined;
|
|
253
259
|
static tryImport<T extends Dictionary = any>({ module, warning }: {
|
|
254
260
|
module: string;
|
|
255
|
-
warning
|
|
261
|
+
warning?: string;
|
|
256
262
|
}): Promise<T | undefined>;
|
|
257
263
|
static stripRelativePath(str: string): string;
|
|
258
|
-
/**
|
|
259
|
-
* simple process.argv parser, supports only properties with long names, prefixed with `--`
|
|
260
|
-
*/
|
|
261
|
-
static parseArgs<T extends Dictionary = Dictionary>(): T;
|
|
262
264
|
static xor(a: boolean, b: boolean): boolean;
|
|
263
265
|
static keys<T extends object>(obj: T): (keyof T)[];
|
|
264
266
|
static values<T extends object>(obj: T): T[keyof T][];
|