@mikro-orm/core 7.0.16-dev.9 → 7.0.16
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.js +19 -2
- package/index.d.ts +1 -1
- package/metadata/MetadataDiscovery.js +7 -3
- package/package.json +1 -1
- package/typings.d.ts +26 -4
- package/typings.js +26 -5
- package/utils/Utils.js +1 -1
- package/utils/upsert-utils.d.ts +2 -0
- package/utils/upsert-utils.js +26 -1
package/EntityManager.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getOnConflictReturningFields, getWhereCondition } from './utils/upsert-utils.js';
|
|
1
|
+
import { getOnConflictReturningFields, getWhereCondition, resetUntouchedCollections } from './utils/upsert-utils.js';
|
|
2
2
|
import { Utils } from './utils/Utils.js';
|
|
3
3
|
import { Cursor } from './utils/Cursor.js';
|
|
4
4
|
import { QueryHelper } from './utils/QueryHelper.js';
|
|
@@ -360,6 +360,13 @@ export class EntityManager {
|
|
|
360
360
|
if (!joined && !hint.filter) {
|
|
361
361
|
continue;
|
|
362
362
|
}
|
|
363
|
+
// Polymorphic to-one relations are already filtered via per-target LEFT JOINs created
|
|
364
|
+
// for the `:ref filter` hint; emitting a populate-filter entry here would auto-join only
|
|
365
|
+
// the first target meta and either drop rows pointing to other targets or reference
|
|
366
|
+
// parent-table-only columns on the child sub-table alias for TPT children.
|
|
367
|
+
if (prop.polymorphic && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
363
370
|
const filters = QueryHelper.mergePropertyFilters(prop.filters, options.filters);
|
|
364
371
|
const where = await this.applyFilters(prop.targetMeta.class, {}, filters, 'read', {
|
|
365
372
|
...options,
|
|
@@ -806,7 +813,10 @@ export class EntityManager {
|
|
|
806
813
|
data = QueryHelper.processObjectParams(data);
|
|
807
814
|
validateParams(data, 'insert data');
|
|
808
815
|
if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
|
|
809
|
-
await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity: data, em, meta }, meta);
|
|
816
|
+
await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity: entity ?? data, em, meta }, meta);
|
|
817
|
+
if (entity) {
|
|
818
|
+
data = em.#comparator.prepareEntity(entity);
|
|
819
|
+
}
|
|
810
820
|
}
|
|
811
821
|
const ret = await em.driver.nativeUpdate(entityName, where, data, {
|
|
812
822
|
ctx: em.#transactionContext,
|
|
@@ -855,6 +865,7 @@ export class EntityManager {
|
|
|
855
865
|
em.getHydrator().hydrate(entity, meta, data2, em.#entityFactory, 'full', false, true);
|
|
856
866
|
}
|
|
857
867
|
// recompute the data as there might be some values missing (e.g. those with db column defaults)
|
|
868
|
+
resetUntouchedCollections(meta, entity);
|
|
858
869
|
const snapshot = this.#comparator.prepareEntity(entity);
|
|
859
870
|
em.#unitOfWork.register(entity, snapshot, { refresh: true });
|
|
860
871
|
if (em.eventManager.hasListeners(EventType.afterUpsert, meta)) {
|
|
@@ -921,6 +932,7 @@ export class EntityManager {
|
|
|
921
932
|
const allWhere = [];
|
|
922
933
|
const entities = new Map();
|
|
923
934
|
const entitiesByData = new Map();
|
|
935
|
+
const entitiesByAllDataIdx = new Map();
|
|
924
936
|
for (let i = 0; i < data.length; i++) {
|
|
925
937
|
let row = data[i];
|
|
926
938
|
let where;
|
|
@@ -934,6 +946,7 @@ export class EntityManager {
|
|
|
934
946
|
}
|
|
935
947
|
where = helper(entity).getPrimaryKey();
|
|
936
948
|
em.#entityFactory.assignDefaultValues(entity, meta);
|
|
949
|
+
entitiesByAllDataIdx.set(allData.length, entity);
|
|
937
950
|
row = em.#comparator.prepareEntity(entity);
|
|
938
951
|
}
|
|
939
952
|
else {
|
|
@@ -981,6 +994,9 @@ export class EntityManager {
|
|
|
981
994
|
const entity = entitiesByData.get(dto) ?? dto;
|
|
982
995
|
await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity, em, meta }, meta);
|
|
983
996
|
}
|
|
997
|
+
for (const [idx, entity] of entitiesByAllDataIdx) {
|
|
998
|
+
allData[idx] = em.#comparator.prepareEntity(entity);
|
|
999
|
+
}
|
|
984
1000
|
}
|
|
985
1001
|
const res = await em.driver.nativeUpdateMany(entityName, allWhere, allData, {
|
|
986
1002
|
ctx: em.#transactionContext,
|
|
@@ -1081,6 +1097,7 @@ export class EntityManager {
|
|
|
1081
1097
|
}
|
|
1082
1098
|
for (const [entity] of entities) {
|
|
1083
1099
|
// recompute the data as there might be some values missing (e.g. those with db column defaults)
|
|
1100
|
+
resetUntouchedCollections(meta, entity);
|
|
1084
1101
|
const snapshot = this.#comparator.prepareEntity(entity);
|
|
1085
1102
|
em.#unitOfWork.register(entity, snapshot, { refresh: true });
|
|
1086
1103
|
}
|
package/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @module core
|
|
4
4
|
*/
|
|
5
5
|
export { EntityMetadata, PrimaryKeyProp, EntityRepositoryType, OptionalProps, EagerProps, HiddenProps, Config, EntityName, } from './typings.js';
|
|
6
|
-
export type { CompiledFunctions, Constructor, ConnectionType, Dictionary, Primary, IPrimaryKey, ObjectQuery, FilterQuery, IWrappedEntity, InferEntityName, EntityData, Highlighter, MaybePromise, AnyEntity, EntityClass, EntityProperty, PopulateOptions, Populate, Loaded, New, LoadedReference, LoadedCollection, IMigrator, IMigrationGenerator, MigratorEvent, GetRepository, MigrationObject, DeepPartial, PrimaryProperty, Cast, IsUnknown, EntityDictionary, EntityDTO, EntityDTOFlat, EntityDTOProp, SerializeDTO, MigrationDiff, GenerateOptions, FilterObject, IMigrationRunner, IEntityGenerator, ISeedManager, SeederObject, IMigratorStorage, RequiredEntityData, CheckCallback, IndexCallback, FormulaCallback, FormulaColumns, FormulaTable, SchemaTable, SchemaColumns, EntityDataPropValue, SimpleColumnMeta, Rel, Ref, ScalarRef, EntityRef, ISchemaGenerator, MigrationInfo, MigrateOptions, MigrationResult, MigrationRow, EntityKey, EntityValue, EntityDataValue, FilterKey, EntityType, FromEntityType, Selected, IsSubset, EntityProps, ExpandProperty, ExpandScalar, FilterItemValue, ExpandQuery, Scalar, ExpandHint, FilterValue, MergeLoaded, MergeSelected, TypeConfig, AnyString, ClearDatabaseOptions, CreateSchemaOptions, EnsureDatabaseOptions, UpdateSchemaOptions, DropSchemaOptions, RefreshDatabaseOptions, AutoPath, UnboxArray, MetadataProcessor, ImportsResolver, RequiredNullable, DefineConfig, Opt, Hidden, EntitySchemaWithMeta, InferEntity, CheckConstraint, GeneratedColumnCallback, FilterDef, EntityCtor, Subquery, PopulateHintOptions, Prefixes, } from './typings.js';
|
|
6
|
+
export type { CompiledFunctions, Constructor, ConnectionType, Dictionary, Primary, IPrimaryKey, ObjectQuery, FilterQuery, IWrappedEntity, InferEntityName, EntityData, Highlighter, MaybePromise, AnyEntity, EntityClass, EntityProperty, PopulateOptions, Populate, Loaded, New, LoadedReference, LoadedCollection, IMigrator, IMigrationGenerator, MigratorEvent, GetRepository, MigrationObject, DeepPartial, PrimaryProperty, Cast, IsUnknown, EntityDictionary, EntityDTO, EntityDTOFlat, EntityDTOProp, SerializeDTO, MigrationDiff, GenerateOptions, FilterObject, IMigrationRunner, IEntityGenerator, ISeedManager, SeederObject, IMigratorStorage, RequiredEntityData, CheckCallback, IndexCallback, FormulaCallback, FormulaColumns, FormulaTable, SchemaTable, SchemaColumns, SchemaColumnRef, EntityDataPropValue, SimpleColumnMeta, Rel, Ref, ScalarRef, EntityRef, ISchemaGenerator, MigrationInfo, MigrateOptions, MigrationResult, MigrationRow, EntityKey, EntityValue, EntityDataValue, FilterKey, EntityType, FromEntityType, Selected, IsSubset, EntityProps, ExpandProperty, ExpandScalar, FilterItemValue, ExpandQuery, Scalar, ExpandHint, FilterValue, MergeLoaded, MergeSelected, TypeConfig, AnyString, ClearDatabaseOptions, CreateSchemaOptions, EnsureDatabaseOptions, UpdateSchemaOptions, DropSchemaOptions, RefreshDatabaseOptions, AutoPath, UnboxArray, MetadataProcessor, ImportsResolver, RequiredNullable, DefineConfig, Opt, Hidden, EntitySchemaWithMeta, InferEntity, CheckConstraint, GeneratedColumnCallback, FilterDef, EntityCtor, Subquery, PopulateHintOptions, Prefixes, } from './typings.js';
|
|
7
7
|
export * from './enums.js';
|
|
8
8
|
export * from './errors.js';
|
|
9
9
|
export * from './exceptions.js';
|
|
@@ -963,9 +963,13 @@ export class MetadataDiscovery {
|
|
|
963
963
|
meta.propertyOrder.set(prop.name, (order += 0.01));
|
|
964
964
|
});
|
|
965
965
|
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
meta.
|
|
966
|
+
// TPT children have their own tables that don't contain the parent's columns,
|
|
967
|
+
// so propagating parent indexes/uniques/checks would target missing columns.
|
|
968
|
+
if (meta.inheritanceType !== 'tpt' || !meta.tptParent) {
|
|
969
|
+
meta.indexes = Utils.unique([...base.indexes, ...meta.indexes]);
|
|
970
|
+
meta.uniques = Utils.unique([...base.uniques, ...meta.uniques]);
|
|
971
|
+
meta.checks = Utils.unique([...base.checks, ...meta.checks]);
|
|
972
|
+
}
|
|
969
973
|
const pks = Object.values(meta.properties)
|
|
970
974
|
.filter(p => p.primary)
|
|
971
975
|
.map(p => p.name);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/core",
|
|
3
|
-
"version": "7.0.16
|
|
3
|
+
"version": "7.0.16",
|
|
4
4
|
"description": "TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"data-mapper",
|
package/typings.d.ts
CHANGED
|
@@ -579,19 +579,37 @@ export type SchemaTable = {
|
|
|
579
579
|
export type FormulaColumns<T> = Record<PropertyName<T>, string> & {
|
|
580
580
|
toString(): string;
|
|
581
581
|
};
|
|
582
|
+
/**
|
|
583
|
+
* Column reference for schema callbacks. Behaves like a string (coercible via `toString()`),
|
|
584
|
+
* with arbitrary nested property access for embedded sub-columns (e.g. `cols.address.city`).
|
|
585
|
+
*/
|
|
586
|
+
export type SchemaColumnRef = string & {
|
|
587
|
+
readonly [key: string]: SchemaColumnRef;
|
|
588
|
+
};
|
|
582
589
|
/**
|
|
583
590
|
* Column mapping for schema callbacks (indexes, checks, generated columns).
|
|
584
591
|
* Maps property names to field names. For TPT entities, only includes properties
|
|
585
592
|
* that belong to the current table (not inherited properties from parent tables).
|
|
593
|
+
* Embedded properties expose their sub-columns via nested access (e.g. `cols.address.city`),
|
|
594
|
+
* while coercing to the embedded property's own column name via `toString()` (matches the
|
|
595
|
+
* pre-7712 behaviour, regardless of any custom `prefix`).
|
|
596
|
+
*/
|
|
597
|
+
export type SchemaColumns<T> = Record<PropertyName<T>, SchemaColumnRef>;
|
|
598
|
+
/**
|
|
599
|
+
* Callback for custom index expressions. Receives column mappings, table info, and the index name.
|
|
600
|
+
* The runtime `columns` object includes nested entries for embedded properties (see `SchemaColumnRef`);
|
|
601
|
+
* cast to `SchemaColumns<T>` if you need typed nested access.
|
|
586
602
|
*/
|
|
587
|
-
export type SchemaColumns<T> = Record<PropertyName<T>, string>;
|
|
588
|
-
/** Callback for custom index expressions. Receives column mappings, table info, and the index name. */
|
|
589
603
|
export type IndexCallback<T> = (columns: Record<PropertyName<T>, string>, table: SchemaTable, indexName: string) => string | Raw;
|
|
590
604
|
/** Callback for computed (formula) properties. Receives column mappings and table info, returns a SQL expression. */
|
|
591
605
|
export type FormulaCallback<T> = (columns: FormulaColumns<T>, table: FormulaTable) => string | Raw;
|
|
592
606
|
/** Callback for CHECK constraint expressions. Receives column mappings and table info. */
|
|
593
|
-
export type CheckCallback<T> = (columns:
|
|
594
|
-
/**
|
|
607
|
+
export type CheckCallback<T> = (columns: SchemaColumns<T>, table: SchemaTable) => string | Raw;
|
|
608
|
+
/**
|
|
609
|
+
* Callback for generated (computed) column expressions. Receives column mappings and table info.
|
|
610
|
+
* The runtime `columns` object includes nested entries for embedded properties (see `SchemaColumnRef`);
|
|
611
|
+
* cast to `SchemaColumns<T>` if you need typed nested access.
|
|
612
|
+
*/
|
|
595
613
|
export type GeneratedColumnCallback<T> = (columns: Record<PropertyName<T>, string>, table: SchemaTable) => string | Raw;
|
|
596
614
|
/** Definition of a CHECK constraint on a table or property. */
|
|
597
615
|
export interface CheckConstraint<T = any> {
|
|
@@ -728,8 +746,12 @@ export declare class EntityMetadata<Entity = any, Class extends EntityCtor<Entit
|
|
|
728
746
|
/**
|
|
729
747
|
* Creates a column mapping for schema callbacks (indexes, checks, generated columns).
|
|
730
748
|
* For TPT entities, only includes properties that belong to the current table (ownProps).
|
|
749
|
+
* Embedded properties expose their sub-columns via nested access (e.g. `cols.address.city`),
|
|
750
|
+
* while still coercing to the embedded column prefix when used as a string (GH #7712).
|
|
731
751
|
*/
|
|
732
752
|
createSchemaColumnMappingObject(): SchemaColumns<Entity>;
|
|
753
|
+
/** @internal Recursively builds a column mapping for an embedded property's sub-columns. */
|
|
754
|
+
private static buildEmbeddedColumnMapping;
|
|
733
755
|
get tableName(): string;
|
|
734
756
|
set tableName(name: string);
|
|
735
757
|
get uniqueName(): string;
|
package/typings.js
CHANGED
|
@@ -116,16 +116,37 @@ export class EntityMetadata {
|
|
|
116
116
|
/**
|
|
117
117
|
* Creates a column mapping for schema callbacks (indexes, checks, generated columns).
|
|
118
118
|
* For TPT entities, only includes properties that belong to the current table (ownProps).
|
|
119
|
+
* Embedded properties expose their sub-columns via nested access (e.g. `cols.address.city`),
|
|
120
|
+
* while still coercing to the embedded column prefix when used as a string (GH #7712).
|
|
119
121
|
*/
|
|
120
122
|
createSchemaColumnMappingObject() {
|
|
121
123
|
// For TPT entities, only include properties that belong to this entity's table
|
|
122
124
|
const props = this.inheritanceType === 'tpt' && this.ownProps ? this.ownProps : Object.values(this.properties);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
const result = {};
|
|
126
|
+
for (const prop of props) {
|
|
127
|
+
if (!prop.fieldNames) {
|
|
128
|
+
continue;
|
|
126
129
|
}
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
if (prop.kind === ReferenceKind.EMBEDDED && prop.embeddedProps) {
|
|
131
|
+
result[prop.name] = EntityMetadata.buildEmbeddedColumnMapping(prop);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
result[prop.name] = prop.fieldNames[0];
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
/** @internal Recursively builds a column mapping for an embedded property's sub-columns. */
|
|
139
|
+
static buildEmbeddedColumnMapping(embeddedProp) {
|
|
140
|
+
const result = {};
|
|
141
|
+
for (const [name, sub] of Object.entries(embeddedProp.embeddedProps)) {
|
|
142
|
+
result[name] =
|
|
143
|
+
sub.kind === ReferenceKind.EMBEDDED && sub.embeddedProps
|
|
144
|
+
? EntityMetadata.buildEmbeddedColumnMapping(sub)
|
|
145
|
+
: sub.fieldNames[0];
|
|
146
|
+
}
|
|
147
|
+
const prefix = embeddedProp.fieldNames[0];
|
|
148
|
+
Object.defineProperty(result, 'toString', { value: () => prefix, enumerable: false });
|
|
149
|
+
return result;
|
|
129
150
|
}
|
|
130
151
|
get tableName() {
|
|
131
152
|
return this.collection;
|
package/utils/Utils.js
CHANGED
|
@@ -141,7 +141,7 @@ export function parseJsonSafe(value) {
|
|
|
141
141
|
/** Collection of general-purpose utility methods used throughout the ORM. */
|
|
142
142
|
export class Utils {
|
|
143
143
|
static PK_SEPARATOR = '~~~';
|
|
144
|
-
static #ORM_VERSION = '7.0.16
|
|
144
|
+
static #ORM_VERSION = '7.0.16';
|
|
145
145
|
/**
|
|
146
146
|
* Checks if the argument is instance of `Object`. Returns false for arrays.
|
|
147
147
|
*/
|
package/utils/upsert-utils.d.ts
CHANGED
|
@@ -10,3 +10,5 @@ export declare function getWhereCondition<T extends object>(meta: EntityMetadata
|
|
|
10
10
|
where: FilterQuery<T>;
|
|
11
11
|
propIndex: number | false;
|
|
12
12
|
};
|
|
13
|
+
/** @internal */
|
|
14
|
+
export declare function resetUntouchedCollections<Entity extends object>(meta: EntityMetadata<Entity>, entity: Entity): void;
|
package/utils/upsert-utils.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { isRaw } from '../utils/RawQueryFragment.js';
|
|
2
2
|
import { Utils } from './Utils.js';
|
|
3
|
+
import { ReferenceKind } from '../enums.js';
|
|
4
|
+
import { Collection } from '../entity/Collection.js';
|
|
5
|
+
import { helper } from '../entity/wrap.js';
|
|
3
6
|
function expandEmbeddedProperties(prop, key) {
|
|
4
7
|
if (prop.object) {
|
|
5
8
|
return [prop.name];
|
|
@@ -52,7 +55,9 @@ export function getOnConflictFields(meta, data, uniqueFields, options) {
|
|
|
52
55
|
});
|
|
53
56
|
}
|
|
54
57
|
const keys = Object.keys(data).flatMap(f => {
|
|
55
|
-
|
|
58
|
+
// skip explicitly listed unique fields; for raw onConflictFields we can't introspect the
|
|
59
|
+
// fragment, so merge all data keys (the user is responsible for not corrupting them).
|
|
60
|
+
if (Array.isArray(uniqueFields) && uniqueFields.includes(f)) {
|
|
56
61
|
return [];
|
|
57
62
|
}
|
|
58
63
|
const prop = meta?.properties[f];
|
|
@@ -142,3 +147,23 @@ export function getWhereCondition(meta, onConflictFields, data, where) {
|
|
|
142
147
|
}
|
|
143
148
|
return { where, propIndex };
|
|
144
149
|
}
|
|
150
|
+
/** @internal */
|
|
151
|
+
export function resetUntouchedCollections(meta, entity) {
|
|
152
|
+
// for entities passed in via `em.create()` and then upserted, collection-kind relations
|
|
153
|
+
// were initialized as empty-but-initialized by the hydrator (newEntity=true). once the
|
|
154
|
+
// upsert resolves the entity to a possibly existing row, that state is stale and would
|
|
155
|
+
// cause `em.populate()` to skip those collections. replace them with a fresh
|
|
156
|
+
// uninitialized collection so populate can load them.
|
|
157
|
+
if (helper(entity).__originalEntityData) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
for (const prop of meta.relations) {
|
|
161
|
+
if (prop.kind !== ReferenceKind.MANY_TO_MANY && prop.kind !== ReferenceKind.ONE_TO_MANY) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const collection = entity[prop.name];
|
|
165
|
+
if (Utils.isCollection(collection) && collection.isInitialized() && !collection.isDirty()) {
|
|
166
|
+
Collection.create(entity, prop.name, undefined, false);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|