@mikro-orm/core 7.1.0-dev.36 → 7.1.0-dev.38
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 +51 -11
- package/entity/EntityLoader.js +43 -10
- package/index.d.ts +1 -1
- package/metadata/MetadataDiscovery.js +12 -4
- 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';
|
|
@@ -322,13 +322,13 @@ export class EntityManager {
|
|
|
322
322
|
}
|
|
323
323
|
const types = Object.values(meta.root.discriminatorMap).map(cls => this.metadata.get(cls));
|
|
324
324
|
const children = [];
|
|
325
|
-
const lookUpChildren = (ret,
|
|
326
|
-
const children = types.filter(meta2 => meta2.extends ===
|
|
327
|
-
children.forEach(m => lookUpChildren(ret, m
|
|
325
|
+
const lookUpChildren = (ret, parent) => {
|
|
326
|
+
const children = types.filter(meta2 => meta2.extends && this.metadata.find(meta2.extends) === parent);
|
|
327
|
+
children.forEach(m => lookUpChildren(ret, m));
|
|
328
328
|
ret.push(...children.filter(c => c.discriminatorValue));
|
|
329
329
|
return children;
|
|
330
330
|
};
|
|
331
|
-
lookUpChildren(children, meta
|
|
331
|
+
lookUpChildren(children, meta);
|
|
332
332
|
/* v8 ignore next */
|
|
333
333
|
where[meta.root.discriminatorColumn] =
|
|
334
334
|
children.length > 0
|
|
@@ -363,6 +363,13 @@ export class EntityManager {
|
|
|
363
363
|
if (!joined && !hint.filter) {
|
|
364
364
|
continue;
|
|
365
365
|
}
|
|
366
|
+
// Polymorphic to-one relations are already filtered via per-target LEFT JOINs created
|
|
367
|
+
// for the `:ref filter` hint; emitting a populate-filter entry here would auto-join only
|
|
368
|
+
// the first target meta and either drop rows pointing to other targets or reference
|
|
369
|
+
// parent-table-only columns on the child sub-table alias for TPT children.
|
|
370
|
+
if (prop.polymorphic && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
366
373
|
const filters = QueryHelper.mergePropertyFilters(prop.filters, options.filters);
|
|
367
374
|
const where = await this.applyFilters(prop.targetMeta.class, {}, filters, 'read', {
|
|
368
375
|
...options,
|
|
@@ -448,7 +455,23 @@ export class EntityManager {
|
|
|
448
455
|
const prop = meta?.properties[field];
|
|
449
456
|
if (prop && !ref) {
|
|
450
457
|
hint.children ??= [];
|
|
451
|
-
|
|
458
|
+
const targets = prop.polymorphic && prop.polymorphTargets?.length ? prop.polymorphTargets : [prop.targetMeta];
|
|
459
|
+
for (const targetMeta of targets) {
|
|
460
|
+
const before = hint.children.length;
|
|
461
|
+
await this.autoJoinRefsForFilters(targetMeta, { ...options, populate: hint.children }, { class: meta.root.class, propName: prop.name });
|
|
462
|
+
// For polymorphic relations, `hint.children` is applied to every polymorph target during
|
|
463
|
+
// population, so drop any auto-added hint whose field does not exist on every target —
|
|
464
|
+
// otherwise the shared `:ref` would crash when applied to a target lacking it (GH #7722).
|
|
465
|
+
if (targets.length > 1 && hint.children.length > before) {
|
|
466
|
+
const added = hint.children.splice(before);
|
|
467
|
+
for (const h of added) {
|
|
468
|
+
const f = h.field.split(':')[0];
|
|
469
|
+
if (targets.every(t => f in t.properties)) {
|
|
470
|
+
hint.children.push(h);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
452
475
|
}
|
|
453
476
|
}
|
|
454
477
|
}
|
|
@@ -797,7 +820,10 @@ export class EntityManager {
|
|
|
797
820
|
data = QueryHelper.processObjectParams(data);
|
|
798
821
|
validateParams(data, 'insert data');
|
|
799
822
|
if (em.eventManager.hasListeners(EventType.beforeUpsert, meta)) {
|
|
800
|
-
await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity: data, em, meta }, meta);
|
|
823
|
+
await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity: entity ?? data, em, meta }, meta);
|
|
824
|
+
if (entity) {
|
|
825
|
+
data = em.#comparator.prepareEntity(entity);
|
|
826
|
+
}
|
|
801
827
|
}
|
|
802
828
|
const ret = await em.driver.nativeUpdate(entityName, where, data, {
|
|
803
829
|
ctx: em.#transactionContext,
|
|
@@ -846,6 +872,7 @@ export class EntityManager {
|
|
|
846
872
|
em.getHydrator().hydrate(entity, meta, data2, em.#entityFactory, 'full', false, true);
|
|
847
873
|
}
|
|
848
874
|
// recompute the data as there might be some values missing (e.g. those with db column defaults)
|
|
875
|
+
resetUntouchedCollections(meta, entity);
|
|
849
876
|
const snapshot = this.#comparator.prepareEntity(entity);
|
|
850
877
|
em.#unitOfWork.register(entity, snapshot, { refresh: true });
|
|
851
878
|
if (em.eventManager.hasListeners(EventType.afterUpsert, meta)) {
|
|
@@ -912,6 +939,7 @@ export class EntityManager {
|
|
|
912
939
|
const allWhere = [];
|
|
913
940
|
const entities = new Map();
|
|
914
941
|
const entitiesByData = new Map();
|
|
942
|
+
const entitiesByAllDataIdx = new Map();
|
|
915
943
|
for (let i = 0; i < data.length; i++) {
|
|
916
944
|
let row = data[i];
|
|
917
945
|
let where;
|
|
@@ -925,6 +953,7 @@ export class EntityManager {
|
|
|
925
953
|
}
|
|
926
954
|
where = helper(entity).getPrimaryKey();
|
|
927
955
|
em.#entityFactory.assignDefaultValues(entity, meta);
|
|
956
|
+
entitiesByAllDataIdx.set(allData.length, entity);
|
|
928
957
|
row = em.#comparator.prepareEntity(entity);
|
|
929
958
|
}
|
|
930
959
|
else {
|
|
@@ -972,6 +1001,9 @@ export class EntityManager {
|
|
|
972
1001
|
const entity = entitiesByData.get(dto) ?? dto;
|
|
973
1002
|
await em.eventManager.dispatchEvent(EventType.beforeUpsert, { entity, em, meta }, meta);
|
|
974
1003
|
}
|
|
1004
|
+
for (const [idx, entity] of entitiesByAllDataIdx) {
|
|
1005
|
+
allData[idx] = em.#comparator.prepareEntity(entity);
|
|
1006
|
+
}
|
|
975
1007
|
}
|
|
976
1008
|
const res = await em.driver.nativeUpdateMany(entityName, allWhere, allData, {
|
|
977
1009
|
ctx: em.#transactionContext,
|
|
@@ -1072,6 +1104,7 @@ export class EntityManager {
|
|
|
1072
1104
|
}
|
|
1073
1105
|
for (const [entity] of entities) {
|
|
1074
1106
|
// recompute the data as there might be some values missing (e.g. those with db column defaults)
|
|
1107
|
+
resetUntouchedCollections(meta, entity);
|
|
1075
1108
|
const snapshot = this.#comparator.prepareEntity(entity);
|
|
1076
1109
|
em.#unitOfWork.register(entity, snapshot, { refresh: true });
|
|
1077
1110
|
}
|
|
@@ -1864,9 +1897,13 @@ export class EntityManager {
|
|
|
1864
1897
|
return [];
|
|
1865
1898
|
}
|
|
1866
1899
|
const meta = this.metadata.find(entityName);
|
|
1867
|
-
// infer populate hints from `fields` when present; merge with explicit `populate` if both are set
|
|
1900
|
+
// infer populate hints from `fields` when present; merge with explicit `populate` if both are set.
|
|
1901
|
+
// `populateArray` is only kept when the entries are user-provided strings — when this method runs a
|
|
1902
|
+
// second time (e.g. from `lockAndPopulate`) the populate is already normalized and merging would be a no-op.
|
|
1868
1903
|
const fields = options.fields ? this.buildFields(options.fields) : undefined;
|
|
1869
|
-
const populateArray = Array.isArray(options.populate)
|
|
1904
|
+
const populateArray = Array.isArray(options.populate) && options.populate.every(p => typeof p === 'string')
|
|
1905
|
+
? options.populate
|
|
1906
|
+
: undefined;
|
|
1870
1907
|
const hasNestedFields = fields?.some(f => f.includes('.')) ?? false;
|
|
1871
1908
|
const shouldInfer = fields !== undefined && options.populate === undefined;
|
|
1872
1909
|
const shouldMerge = fields !== undefined && populateArray !== undefined && populateArray.length > 0 && hasNestedFields;
|
|
@@ -1910,8 +1947,11 @@ export class EntityManager {
|
|
|
1910
1947
|
}
|
|
1911
1948
|
else {
|
|
1912
1949
|
// with an explicit populate, only nested paths (e.g. `item.id`) need to be merged in — they
|
|
1913
|
-
// require a JOIN to access sub-fields, whereas bare names are already handled by the driver
|
|
1914
|
-
|
|
1950
|
+
// require a JOIN to access sub-fields, whereas bare names are already handled by the driver.
|
|
1951
|
+
// skip paths already in user's `populate` (regardless of `:ref` modifier) to avoid producing
|
|
1952
|
+
// both a `:ref` and a non-`:ref` entry for the same relation
|
|
1953
|
+
const existing = new Set(populateArray.map(p => p.split(':')[0]));
|
|
1954
|
+
const extras = fromFields.filter(f => f.includes('.') && !existing.has(f));
|
|
1915
1955
|
options.populate = [...populateArray, ...extras];
|
|
1916
1956
|
}
|
|
1917
1957
|
}
|
package/entity/EntityLoader.js
CHANGED
|
@@ -319,7 +319,7 @@ export class EntityLoader {
|
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
321
|
async findChildren(entities, prop, populate, options, ref) {
|
|
322
|
-
const children = Utils.unique(this.getChildReferences(entities, prop, options, ref));
|
|
322
|
+
const children = Utils.unique(this.getChildReferences(entities, prop, options, ref, populate));
|
|
323
323
|
const meta = prop.targetMeta;
|
|
324
324
|
// When targetKey is set, use it for FK lookup instead of the PK
|
|
325
325
|
let fk = prop.targetKey ?? Utils.getPrimaryKeyHash(meta.primaryKeys);
|
|
@@ -337,10 +337,10 @@ export class EntityLoader {
|
|
|
337
337
|
fk = ownerProp.name;
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
|
-
if (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && !ref) {
|
|
340
|
+
if (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && (!ref || (!prop.mapToPk && !populate.filter))) {
|
|
341
341
|
children.length = 0;
|
|
342
342
|
fk = meta.properties[prop.mappedBy].name;
|
|
343
|
-
children.push(...this.filterByReferences(entities, prop
|
|
343
|
+
children.push(...this.filterByReferences(entities, prop, options, ref));
|
|
344
344
|
}
|
|
345
345
|
if (children.length === 0) {
|
|
346
346
|
return { items: [], partial };
|
|
@@ -707,7 +707,7 @@ export class EntityLoader {
|
|
|
707
707
|
}
|
|
708
708
|
return ret;
|
|
709
709
|
}
|
|
710
|
-
getChildReferences(entities, prop, options, ref) {
|
|
710
|
+
getChildReferences(entities, prop, options, ref, populate) {
|
|
711
711
|
const filtered = this.filterCollections(entities, prop.name, options, ref);
|
|
712
712
|
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
|
713
713
|
return filtered.map(e => e[prop.name].owner);
|
|
@@ -723,7 +723,7 @@ export class EntityLoader {
|
|
|
723
723
|
return filtered;
|
|
724
724
|
}
|
|
725
725
|
// MANY_TO_ONE or ONE_TO_ONE
|
|
726
|
-
return this.filterReferences(entities, prop.name, options, ref);
|
|
726
|
+
return this.filterReferences(entities, prop.name, options, ref, populate);
|
|
727
727
|
}
|
|
728
728
|
filterCollections(entities, field, options, ref) {
|
|
729
729
|
if (options.refresh) {
|
|
@@ -749,7 +749,7 @@ export class EntityLoader {
|
|
|
749
749
|
}
|
|
750
750
|
return this.isPropertyLoaded(entity[f], r.join('.'));
|
|
751
751
|
}
|
|
752
|
-
filterReferences(entities, field, options, ref) {
|
|
752
|
+
filterReferences(entities, field, options, ref, populate) {
|
|
753
753
|
if (ref) {
|
|
754
754
|
return [];
|
|
755
755
|
}
|
|
@@ -758,13 +758,19 @@ export class EntityLoader {
|
|
|
758
758
|
return children.map(e => Reference.unwrapReference(e[field]));
|
|
759
759
|
}
|
|
760
760
|
if (options.fields) {
|
|
761
|
+
// `:ref` populate children are satisfied by FK/PK and will be loaded lazily — they don't force a parent reload
|
|
762
|
+
const refChildren = new Set((populate?.children ?? [])
|
|
763
|
+
.map(c => c.field.split(':'))
|
|
764
|
+
.filter(parts => parts[1] === 'ref')
|
|
765
|
+
.map(parts => parts[0]));
|
|
761
766
|
return children
|
|
762
767
|
.map(e => Reference.unwrapReference(e[field]))
|
|
763
768
|
.filter(target => {
|
|
764
769
|
const wrapped = helper(target);
|
|
765
770
|
const childFields = options.fields
|
|
766
771
|
.filter(f => f.startsWith(`${field}.`))
|
|
767
|
-
.map(f => f.substring(field.length + 1))
|
|
772
|
+
.map(f => f.substring(field.length + 1))
|
|
773
|
+
.filter(cf => !refChildren.has(cf.split('.')[0]));
|
|
768
774
|
return !wrapped.__initialized || !childFields.every(cf => this.isPropertyLoaded(target, cf));
|
|
769
775
|
});
|
|
770
776
|
}
|
|
@@ -772,12 +778,39 @@ export class EntityLoader {
|
|
|
772
778
|
.filter(e => !e[field].__helper.__initialized)
|
|
773
779
|
.map(e => Reference.unwrapReference(e[field]));
|
|
774
780
|
}
|
|
775
|
-
filterByReferences(entities,
|
|
781
|
+
filterByReferences(entities, prop, options, ref) {
|
|
776
782
|
/* v8 ignore next */
|
|
777
|
-
if (refresh) {
|
|
783
|
+
if (options.refresh) {
|
|
778
784
|
return entities;
|
|
779
785
|
}
|
|
780
|
-
|
|
786
|
+
const field = prop.name;
|
|
787
|
+
return entities.filter(e => {
|
|
788
|
+
const value = e[field];
|
|
789
|
+
if (value === null) {
|
|
790
|
+
return false;
|
|
791
|
+
}
|
|
792
|
+
if (value === undefined || !Utils.isEntity(value, true)) {
|
|
793
|
+
return true;
|
|
794
|
+
}
|
|
795
|
+
const target = Reference.unwrapReference(value);
|
|
796
|
+
const wrapped = helper(target);
|
|
797
|
+
if (!wrapped.__initialized) {
|
|
798
|
+
return true;
|
|
799
|
+
}
|
|
800
|
+
if (ref) {
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
if (options.fields) {
|
|
804
|
+
const childFields = options.fields
|
|
805
|
+
.filter(f => f.startsWith(`${prop.name}.`))
|
|
806
|
+
.map(f => f.substring(prop.name.length + 1));
|
|
807
|
+
return childFields.length > 0 && !childFields.every(f => this.isPropertyLoaded(target, f));
|
|
808
|
+
}
|
|
809
|
+
return prop.targetMeta.comparableProps.some(targetProp => {
|
|
810
|
+
const inlineEmbedded = targetProp.kind === ReferenceKind.EMBEDDED && !targetProp.object;
|
|
811
|
+
return !inlineEmbedded && !targetProp.lazy && !wrapped.__loadedProperties.has(targetProp.name);
|
|
812
|
+
});
|
|
813
|
+
});
|
|
781
814
|
}
|
|
782
815
|
lookupAllRelationships(entityName) {
|
|
783
816
|
const ret = [];
|
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, IndexHints, } 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, IndexFilterQuery, ExtractIndexHints, IndexName, IndexColumns, WithUsingOptions, IMigrationRunner, IEntityGenerator, ISeedManager, SeederObject, IMigratorStorage, RequiredEntityData, CheckCallback, TriggerCallback, IndexCallback, FormulaCallback, FormulaColumns, FormulaTable, SchemaTable, SchemaColumns, EntityDataPropValue, SimpleColumnMeta, Rel, Ref, LazyRef, 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, TriggerDef, 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, IndexFilterQuery, ExtractIndexHints, IndexName, IndexColumns, WithUsingOptions, IMigrationRunner, IEntityGenerator, ISeedManager, SeederObject, IMigratorStorage, RequiredEntityData, CheckCallback, TriggerCallback, IndexCallback, FormulaCallback, FormulaColumns, FormulaTable, SchemaTable, SchemaColumns, SchemaColumnRef, EntityDataPropValue, SimpleColumnMeta, Rel, Ref, LazyRef, 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, TriggerDef, 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';
|
|
@@ -605,6 +605,10 @@ export class MetadataDiscovery {
|
|
|
605
605
|
prop.pivotEntity = pivotMeta.class;
|
|
606
606
|
if (prop.inversedBy) {
|
|
607
607
|
prop.targetMeta.properties[prop.inversedBy].pivotEntity = pivotMeta.class;
|
|
608
|
+
const targetRoot = prop.targetMeta.root;
|
|
609
|
+
if (targetRoot !== prop.targetMeta && targetRoot.properties[prop.inversedBy]) {
|
|
610
|
+
targetRoot.properties[prop.inversedBy].pivotEntity = pivotMeta.class;
|
|
611
|
+
}
|
|
608
612
|
}
|
|
609
613
|
// Propagate pivotEntity to ALL inverse collections using mappedBy pointing at this
|
|
610
614
|
// owner prop. Covers three cases:
|
|
@@ -1030,10 +1034,14 @@ export class MetadataDiscovery {
|
|
|
1030
1034
|
meta.propertyOrder.set(prop.name, (order += 0.01));
|
|
1031
1035
|
});
|
|
1032
1036
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
meta.
|
|
1036
|
-
|
|
1037
|
+
// TPT children have their own tables that don't contain the parent's columns,
|
|
1038
|
+
// so propagating parent indexes/uniques/checks/triggers would target missing columns.
|
|
1039
|
+
if (meta.inheritanceType !== 'tpt' || !meta.tptParent) {
|
|
1040
|
+
meta.indexes = Utils.unique([...base.indexes, ...meta.indexes]);
|
|
1041
|
+
meta.uniques = Utils.unique([...base.uniques, ...meta.uniques]);
|
|
1042
|
+
meta.checks = Utils.unique([...base.checks, ...meta.checks]);
|
|
1043
|
+
meta.triggers = Utils.unique([...base.triggers, ...meta.triggers]);
|
|
1044
|
+
}
|
|
1037
1045
|
const pks = Object.values(meta.properties)
|
|
1038
1046
|
.filter(p => p.primary)
|
|
1039
1047
|
.map(p => p.name);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/core",
|
|
3
|
-
"version": "7.1.0-dev.
|
|
3
|
+
"version": "7.1.0-dev.38",
|
|
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
|
@@ -673,21 +673,39 @@ export type SchemaTable = {
|
|
|
673
673
|
export type FormulaColumns<T> = Record<PropertyName<T>, string> & {
|
|
674
674
|
toString(): string;
|
|
675
675
|
};
|
|
676
|
+
/**
|
|
677
|
+
* Column reference for schema callbacks. Behaves like a string (coercible via `toString()`),
|
|
678
|
+
* with arbitrary nested property access for embedded sub-columns (e.g. `cols.address.city`).
|
|
679
|
+
*/
|
|
680
|
+
export type SchemaColumnRef = string & {
|
|
681
|
+
readonly [key: string]: SchemaColumnRef;
|
|
682
|
+
};
|
|
676
683
|
/**
|
|
677
684
|
* Column mapping for schema callbacks (indexes, checks, generated columns).
|
|
678
685
|
* Maps property names to field names. For TPT entities, only includes properties
|
|
679
686
|
* that belong to the current table (not inherited properties from parent tables).
|
|
687
|
+
* Embedded properties expose their sub-columns via nested access (e.g. `cols.address.city`),
|
|
688
|
+
* while coercing to the embedded property's own column name via `toString()` (matches the
|
|
689
|
+
* pre-7712 behaviour, regardless of any custom `prefix`).
|
|
690
|
+
*/
|
|
691
|
+
export type SchemaColumns<T> = Record<PropertyName<T>, SchemaColumnRef>;
|
|
692
|
+
/**
|
|
693
|
+
* Callback for custom index expressions. Receives column mappings, table info, and the index name.
|
|
694
|
+
* The runtime `columns` object includes nested entries for embedded properties (see `SchemaColumnRef`);
|
|
695
|
+
* cast to `SchemaColumns<T>` if you need typed nested access.
|
|
680
696
|
*/
|
|
681
|
-
export type SchemaColumns<T> = Record<PropertyName<T>, string>;
|
|
682
|
-
/** Callback for custom index expressions. Receives column mappings, table info, and the index name. */
|
|
683
697
|
export type IndexCallback<T> = (columns: Record<PropertyName<T>, string>, table: SchemaTable, indexName: string) => string | Raw;
|
|
684
698
|
/** Callback for computed (formula) properties. Receives column mappings and table info, returns a SQL expression. */
|
|
685
699
|
export type FormulaCallback<T> = (columns: FormulaColumns<T>, table: FormulaTable) => string | Raw;
|
|
686
700
|
/** Callback for CHECK constraint expressions. Receives column mappings and table info. */
|
|
687
|
-
export type CheckCallback<T> = (columns:
|
|
701
|
+
export type CheckCallback<T> = (columns: SchemaColumns<T>, table: SchemaTable) => string | Raw;
|
|
688
702
|
/** Callback for trigger body expressions. Receives column mappings and table info. */
|
|
689
703
|
export type TriggerCallback<T> = (columns: Record<PropertyName<T>, string>, table: SchemaTable) => string | Raw;
|
|
690
|
-
/**
|
|
704
|
+
/**
|
|
705
|
+
* Callback for generated (computed) column expressions. Receives column mappings and table info.
|
|
706
|
+
* The runtime `columns` object includes nested entries for embedded properties (see `SchemaColumnRef`);
|
|
707
|
+
* cast to `SchemaColumns<T>` if you need typed nested access.
|
|
708
|
+
*/
|
|
691
709
|
export type GeneratedColumnCallback<T> = (columns: Record<PropertyName<T>, string>, table: SchemaTable) => string | Raw;
|
|
692
710
|
/** Definition of a CHECK constraint on a table or property. */
|
|
693
711
|
export interface CheckConstraint<T = any> {
|
|
@@ -842,8 +860,12 @@ export declare class EntityMetadata<Entity = any, Class extends EntityCtor<Entit
|
|
|
842
860
|
/**
|
|
843
861
|
* Creates a column mapping for schema callbacks (indexes, checks, generated columns).
|
|
844
862
|
* For TPT entities, only includes properties that belong to the current table (ownProps).
|
|
863
|
+
* Embedded properties expose their sub-columns via nested access (e.g. `cols.address.city`),
|
|
864
|
+
* while still coercing to the embedded column prefix when used as a string (GH #7712).
|
|
845
865
|
*/
|
|
846
866
|
createSchemaColumnMappingObject(): SchemaColumns<Entity>;
|
|
867
|
+
/** @internal Recursively builds a column mapping for an embedded property's sub-columns. */
|
|
868
|
+
private static buildEmbeddedColumnMapping;
|
|
847
869
|
get tableName(): string;
|
|
848
870
|
set tableName(name: string);
|
|
849
871
|
get uniqueName(): string;
|
package/typings.js
CHANGED
|
@@ -133,16 +133,37 @@ export class EntityMetadata {
|
|
|
133
133
|
/**
|
|
134
134
|
* Creates a column mapping for schema callbacks (indexes, checks, generated columns).
|
|
135
135
|
* For TPT entities, only includes properties that belong to the current table (ownProps).
|
|
136
|
+
* Embedded properties expose their sub-columns via nested access (e.g. `cols.address.city`),
|
|
137
|
+
* while still coercing to the embedded column prefix when used as a string (GH #7712).
|
|
136
138
|
*/
|
|
137
139
|
createSchemaColumnMappingObject() {
|
|
138
140
|
// For TPT entities, only include properties that belong to this entity's table
|
|
139
141
|
const props = this.inheritanceType === 'tpt' && this.ownProps ? this.ownProps : Object.values(this.properties);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
const result = {};
|
|
143
|
+
for (const prop of props) {
|
|
144
|
+
if (!prop.fieldNames) {
|
|
145
|
+
continue;
|
|
143
146
|
}
|
|
144
|
-
|
|
145
|
-
|
|
147
|
+
if (prop.kind === ReferenceKind.EMBEDDED && prop.embeddedProps) {
|
|
148
|
+
result[prop.name] = EntityMetadata.buildEmbeddedColumnMapping(prop);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
result[prop.name] = prop.fieldNames[0];
|
|
152
|
+
}
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
/** @internal Recursively builds a column mapping for an embedded property's sub-columns. */
|
|
156
|
+
static buildEmbeddedColumnMapping(embeddedProp) {
|
|
157
|
+
const result = {};
|
|
158
|
+
for (const [name, sub] of Object.entries(embeddedProp.embeddedProps)) {
|
|
159
|
+
result[name] =
|
|
160
|
+
sub.kind === ReferenceKind.EMBEDDED && sub.embeddedProps
|
|
161
|
+
? EntityMetadata.buildEmbeddedColumnMapping(sub)
|
|
162
|
+
: sub.fieldNames[0];
|
|
163
|
+
}
|
|
164
|
+
const prefix = embeddedProp.fieldNames[0];
|
|
165
|
+
Object.defineProperty(result, 'toString', { value: () => prefix, enumerable: false });
|
|
166
|
+
return result;
|
|
146
167
|
}
|
|
147
168
|
get tableName() {
|
|
148
169
|
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.1.0-dev.
|
|
144
|
+
static #ORM_VERSION = '7.1.0-dev.38';
|
|
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
|
+
}
|