@mikro-orm/core 7.2.6-dev.9 → 7.2.7-dev.0
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/entity/EntityFactory.d.ts +7 -0
- package/entity/EntityFactory.js +37 -0
- package/entity/EntityLoader.js +1 -1
- package/entity/defineEntity.d.ts +16 -11
- package/metadata/MetadataDiscovery.d.ts +5 -0
- package/metadata/MetadataDiscovery.js +46 -15
- package/metadata/MetadataStorage.d.ts +1 -0
- package/metadata/MetadataStorage.js +20 -3
- package/metadata/MetadataValidator.js +2 -1
- package/package.json +1 -1
- package/platforms/Platform.d.ts +2 -0
- package/platforms/Platform.js +4 -0
- package/typings.js +5 -2
- package/utils/EntityComparator.js +9 -0
- package/utils/QueryHelper.js +7 -0
- package/utils/Utils.js +11 -6
- package/utils/clone.js +5 -7
|
@@ -38,6 +38,13 @@ export declare class EntityFactory {
|
|
|
38
38
|
constructor(em: EntityManager);
|
|
39
39
|
/** Creates a new entity instance or returns an existing one from the identity map, hydrating it with the provided data. */
|
|
40
40
|
create<T extends object, P extends string = string>(entityName: EntityName<T>, data: EntityData<T>, options?: FactoryOptions): New<T, P>;
|
|
41
|
+
/**
|
|
42
|
+
* Whether merging `data` into an initialized entity would change nothing: every key is a scalar the entity already
|
|
43
|
+
* holds by identity, and keys `mergeData` always re-applies (formula, version, generated, discriminator) match the snapshot.
|
|
44
|
+
*/
|
|
45
|
+
private isNoopMerge;
|
|
46
|
+
/** Whether hydrating `value` into `prop` would leave `current` as is, mirroring the hydrator's Date and boolean coercion. */
|
|
47
|
+
private holdsValue;
|
|
41
48
|
/** Merges new data into an existing entity, preserving user-modified properties. */
|
|
42
49
|
mergeData<T extends object>(meta: EntityMetadata<T>, entity: T, data: EntityData<T>, options?: FactoryOptions): void;
|
|
43
50
|
/** Creates or retrieves an uninitialized entity reference by its primary key or alternate key. */
|
package/entity/EntityFactory.js
CHANGED
|
@@ -149,8 +149,45 @@ export class EntityFactory {
|
|
|
149
149
|
wrapped.__processing = false;
|
|
150
150
|
return entity;
|
|
151
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Whether merging `data` into an initialized entity would change nothing: every key is a scalar the entity already
|
|
154
|
+
* holds by identity, and keys `mergeData` always re-applies (formula, version, generated, discriminator) match the snapshot.
|
|
155
|
+
*/
|
|
156
|
+
isNoopMerge(meta, entity, data, options) {
|
|
157
|
+
const wrapped = helper(entity);
|
|
158
|
+
if (!wrapped.__initialized || options.normalizeAccessors) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
for (const key of Utils.keys(data)) {
|
|
162
|
+
const prop = meta.properties[key];
|
|
163
|
+
const value = data[key];
|
|
164
|
+
// custom types are left to the full merge, as their JS and database values can differ
|
|
165
|
+
if (!prop || prop.customType || value === undefined || !this.holdsValue(prop, entity[key], value)) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
const reapplied = prop.formula || prop.version || prop.generated || key === meta.root.discriminatorColumn;
|
|
169
|
+
if (reapplied && (wrapped.__originalEntityData?.[key] !== value || !wrapped.__loadedProperties.has(key))) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
/** Whether hydrating `value` into `prop` would leave `current` as is, mirroring the hydrator's Date and boolean coercion. */
|
|
176
|
+
holdsValue(prop, current, value) {
|
|
177
|
+
if (current instanceof Date && value instanceof Date) {
|
|
178
|
+
return current.getTime() === value.getTime();
|
|
179
|
+
}
|
|
180
|
+
if (prop.runtimeType === 'boolean' && value != null) {
|
|
181
|
+
return current === !!value;
|
|
182
|
+
}
|
|
183
|
+
return current === value;
|
|
184
|
+
}
|
|
152
185
|
/** Merges new data into an existing entity, preserving user-modified properties. */
|
|
153
186
|
mergeData(meta, entity, data, options = {}) {
|
|
187
|
+
// joined rows often repeat exactly what the entity already holds, skip the snapshot and diffing then
|
|
188
|
+
if (this.isNoopMerge(meta, entity, data, options)) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
154
191
|
// merge unchanged properties automatically
|
|
155
192
|
data = QueryHelper.processParams(data);
|
|
156
193
|
const existsData = this.#comparator.prepareEntity(entity);
|
package/entity/EntityLoader.js
CHANGED
|
@@ -815,7 +815,7 @@ export class EntityLoader {
|
|
|
815
815
|
if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(wrapped.__meta.properties[f].kind)) {
|
|
816
816
|
return entity[f].getItems(false).every((item) => this.isPropertyLoaded(item, r.join('.')));
|
|
817
817
|
}
|
|
818
|
-
return this.isPropertyLoaded(entity[f], r.join('.'));
|
|
818
|
+
return this.isPropertyLoaded(Reference.unwrapReference(entity[f]), r.join('.'));
|
|
819
819
|
}
|
|
820
820
|
filterReferences(entities, field, options, ref, populate) {
|
|
821
821
|
if (ref) {
|
package/entity/defineEntity.d.ts
CHANGED
|
@@ -630,6 +630,21 @@ type AllKeys<TProperties, TBase> = keyof TProperties | (IsNever<TBase> extends t
|
|
|
630
630
|
type PartialWhere<TProperties, TBase> = string | FilterQuery<{
|
|
631
631
|
[K in AllKeys<TProperties, TBase> & string]?: unknown;
|
|
632
632
|
}>;
|
|
633
|
+
type DefineEntityFilterMap = Dictionary<{
|
|
634
|
+
name: string;
|
|
635
|
+
cond: Dictionary | ((args: Dictionary, type: 'read' | 'update' | 'delete', em: any, options?: FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any>, entityName?: string) => MaybePromise<FilterQuery<any>>);
|
|
636
|
+
default?: boolean;
|
|
637
|
+
entity?: EntityName<any> | EntityName<any>[];
|
|
638
|
+
args?: boolean;
|
|
639
|
+
strict?: boolean;
|
|
640
|
+
rls?: boolean | {
|
|
641
|
+
setting?: string;
|
|
642
|
+
};
|
|
643
|
+
}>;
|
|
644
|
+
type DefineEntityFilterMapLoose = Dictionary<{
|
|
645
|
+
name: string;
|
|
646
|
+
cond: Dictionary;
|
|
647
|
+
}>;
|
|
633
648
|
/** Metadata descriptor for `defineEntity()`, combining entity options with property definitions. */
|
|
634
649
|
export interface EntityMetadataWithProperties<TName extends string, TTableName extends string, TProperties extends Record<string, any>, TPK extends (keyof TProperties)[] | undefined = undefined, TBase = never, TRepository = never, TForceObject extends boolean = false, TDiscriminatorColumn extends string | undefined = undefined, TDiscriminatorValue extends string | number | undefined = undefined, TBaseDiscriminatorColumn extends string | undefined = undefined, TEmbeddable extends boolean = false> extends Omit<Partial<EntityMetadata<InferEntityFromProperties<TProperties, TPK, TBase, TRepository>>>, 'properties' | 'extends' | 'primaryKeys' | 'hooks' | 'discriminator' | 'discriminatorColumn' | 'discriminatorValue' | 'versionProperty' | 'concurrencyCheckKeys' | 'serializedPrimaryKey' | 'indexes' | 'uniques' | 'triggers' | 'repository' | 'filters' | 'orderBy'> {
|
|
635
650
|
name: TName;
|
|
@@ -644,17 +659,7 @@ export interface EntityMetadataWithProperties<TName extends string, TTableName e
|
|
|
644
659
|
primaryKeys?: TPK & NoInfer<InferPrimaryKeyConstraint<TProperties>>[];
|
|
645
660
|
hooks?: DefineEntityHooks;
|
|
646
661
|
repository?: () => TRepository;
|
|
647
|
-
filters?:
|
|
648
|
-
name: string;
|
|
649
|
-
cond: Dictionary | ((args: Dictionary, type: 'read' | 'update' | 'delete', em: any, options?: FindOptions<any, any, any, any> | FindOneOptions<any, any, any, any>, entityName?: string) => MaybePromise<FilterQuery<any>>);
|
|
650
|
-
default?: boolean;
|
|
651
|
-
entity?: EntityName<any> | EntityName<any>[];
|
|
652
|
-
args?: boolean;
|
|
653
|
-
strict?: boolean;
|
|
654
|
-
rls?: boolean | {
|
|
655
|
-
setting?: string;
|
|
656
|
-
};
|
|
657
|
-
}>;
|
|
662
|
+
filters?: DefineEntityFilterMap | IsNever<TRepository, never, DefineEntityFilterMapLoose>;
|
|
658
663
|
forceObject?: TForceObject;
|
|
659
664
|
embeddable?: TEmbeddable;
|
|
660
665
|
inheritance?: 'tpt';
|
|
@@ -34,6 +34,11 @@ export declare class MetadataDiscovery {
|
|
|
34
34
|
private initManyToOneFields;
|
|
35
35
|
private initOneToManyFields;
|
|
36
36
|
private processEntity;
|
|
37
|
+
/**
|
|
38
|
+
* Groups the relations of all entities by target class name in one pass, so `findReferencingProperties()`
|
|
39
|
+
* does not go over the relations of every entity for each entity. Lists keep the order of `metadata` and `relations`.
|
|
40
|
+
*/
|
|
41
|
+
private indexReferencingProperties;
|
|
37
42
|
private findReferencingProperties;
|
|
38
43
|
private initFactoryField;
|
|
39
44
|
private ensureCorrectFKOrderInPivotEntity;
|
|
@@ -183,10 +183,11 @@ export class MetadataDiscovery {
|
|
|
183
183
|
}
|
|
184
184
|
discovered.forEach(meta => meta.sync(true));
|
|
185
185
|
this.#metadataProvider.combineCache();
|
|
186
|
+
const referencingProperties = this.indexReferencingProperties(filtered);
|
|
186
187
|
return discovered.map(meta => {
|
|
187
188
|
meta = this.#metadata.get(meta.class);
|
|
188
189
|
meta.sync(true);
|
|
189
|
-
this.findReferencingProperties(meta,
|
|
190
|
+
this.findReferencingProperties(meta, referencingProperties);
|
|
190
191
|
if (meta.inheritanceType === 'tpt') {
|
|
191
192
|
this.computeTPTOwnProps(meta);
|
|
192
193
|
}
|
|
@@ -217,18 +218,20 @@ export class MetadataDiscovery {
|
|
|
217
218
|
.replace(/Array<(.*)>/, '$1') // unwrap array
|
|
218
219
|
.replace(/\[]$/, '') // remove array suffix
|
|
219
220
|
.replace(/\((.*)\)/, '$1'); // unwrap union types
|
|
221
|
+
const discoveredNames = new Set(this.#discovered.map(m => m.className));
|
|
220
222
|
// Names can be ambiguous when a minifier mangles two classes to the same name,
|
|
221
223
|
// so class references also need to be checked by identity.
|
|
224
|
+
const discoveredClasses = new Set(this.#discovered.map(m => m.class));
|
|
222
225
|
const discoveredByIdentity = (target) => {
|
|
223
226
|
const cls = EntitySchema.is(target) ? target.meta.class : target;
|
|
224
|
-
return typeof cls !== 'function' ||
|
|
227
|
+
return typeof cls !== 'function' || discoveredClasses.has(cls);
|
|
225
228
|
};
|
|
226
229
|
const missing = [];
|
|
227
230
|
this.#discovered.forEach(meta => Object.values(meta.properties).forEach(prop => {
|
|
228
231
|
const indirect = (prop.kind === ReferenceKind.MANY_TO_MANY ? prop.pivotEntity : prop.through);
|
|
229
232
|
if (indirect) {
|
|
230
233
|
const target = typeof indirect === 'function' && !indirect.prototype ? indirect() : indirect;
|
|
231
|
-
if (!
|
|
234
|
+
if (!discoveredNames.has(Utils.className(target)) || !discoveredByIdentity(target)) {
|
|
232
235
|
missing.push(target);
|
|
233
236
|
}
|
|
234
237
|
}
|
|
@@ -236,7 +239,7 @@ export class MetadataDiscovery {
|
|
|
236
239
|
const target = typeof prop.entity === 'function' && !prop.entity.prototype ? prop.entity() : prop.type;
|
|
237
240
|
if (!unwrap(prop.type)
|
|
238
241
|
.split(/ ?\| ?/)
|
|
239
|
-
.every(type =>
|
|
242
|
+
.every(type => discoveredNames.has(type)) ||
|
|
240
243
|
!Utils.asArray(target).every(discoveredByIdentity)) {
|
|
241
244
|
missing.push(...Utils.asArray(target));
|
|
242
245
|
}
|
|
@@ -302,7 +305,8 @@ export class MetadataDiscovery {
|
|
|
302
305
|
if (validate) {
|
|
303
306
|
this.#validator.validateDiscovered(this.#discovered, this.#config.get('discovery'));
|
|
304
307
|
}
|
|
305
|
-
|
|
308
|
+
const foundNames = new Set(found.map(m => m.name));
|
|
309
|
+
return this.#discovered.filter(meta => foundNames.has(meta.className));
|
|
306
310
|
}
|
|
307
311
|
reset(entityName) {
|
|
308
312
|
const exists = this.#discovered.findIndex(m => m.class === entityName || m.className === Utils.className(entityName));
|
|
@@ -668,20 +672,47 @@ export class MetadataDiscovery {
|
|
|
668
672
|
}
|
|
669
673
|
return [];
|
|
670
674
|
}
|
|
671
|
-
|
|
675
|
+
/**
|
|
676
|
+
* Groups the relations of all entities by target class name in one pass, so `findReferencingProperties()`
|
|
677
|
+
* does not go over the relations of every entity for each entity. Lists keep the order of `metadata` and `relations`.
|
|
678
|
+
*/
|
|
679
|
+
indexReferencingProperties(metadata) {
|
|
680
|
+
const index = { byType: new Map(), withTargetKey: new Map() };
|
|
681
|
+
const add = (map, key, value) => {
|
|
682
|
+
const list = map.get(key);
|
|
683
|
+
if (list) {
|
|
684
|
+
list.push(value);
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
map.set(key, [value]);
|
|
688
|
+
}
|
|
689
|
+
};
|
|
672
690
|
for (const meta2 of metadata) {
|
|
673
691
|
for (const prop2 of meta2.relations) {
|
|
674
|
-
if (prop2.kind !== ReferenceKind.SCALAR
|
|
675
|
-
|
|
692
|
+
if (prop2.kind !== ReferenceKind.SCALAR) {
|
|
693
|
+
add(index.byType, prop2.type, [meta2, prop2]);
|
|
676
694
|
}
|
|
677
|
-
if (prop2.targetKey
|
|
678
|
-
(prop2.type
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
695
|
+
if (prop2.targetKey) {
|
|
696
|
+
const targets = new Set([prop2.type, ...(prop2.polymorphTargets?.map(t => t.className) ?? [])]);
|
|
697
|
+
for (const target of targets) {
|
|
698
|
+
add(index.withTargetKey, target, prop2);
|
|
699
|
+
}
|
|
682
700
|
}
|
|
683
701
|
}
|
|
684
702
|
}
|
|
703
|
+
return index;
|
|
704
|
+
}
|
|
705
|
+
findReferencingProperties(meta, index) {
|
|
706
|
+
for (const [meta2, prop2] of index.byType.get(meta.className) ?? []) {
|
|
707
|
+
meta.referencingProperties.push({ meta: meta2, prop: prop2 });
|
|
708
|
+
}
|
|
709
|
+
for (const prop2 of index.withTargetKey.get(meta.className) ?? []) {
|
|
710
|
+
const targetKey = prop2.targetKey;
|
|
711
|
+
if (!(meta.primaryKeys.length === 1 && meta.primaryKeys[0] === targetKey) &&
|
|
712
|
+
!meta.root.targetKeys?.includes(targetKey)) {
|
|
713
|
+
(meta.root.targetKeys ??= []).push(targetKey);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
685
716
|
}
|
|
686
717
|
initFactoryField(meta, prop) {
|
|
687
718
|
['mappedBy', 'inversedBy', 'pivotEntity'].forEach(type => {
|
|
@@ -1904,9 +1935,9 @@ export class MetadataDiscovery {
|
|
|
1904
1935
|
// guard preserves the legacy convention where JSON defaults are passed pre-stringified.
|
|
1905
1936
|
// Array defaults are converted unconditionally (e.g. `default: []` on a JSON-backed embedded array).
|
|
1906
1937
|
if (prop.customType && (Array.isArray(prop.default) || prop.customType.compareAsType() !== 'any')) {
|
|
1907
|
-
val = prop.customType.convertToDatabaseValue(prop.default, this.#platform);
|
|
1938
|
+
val = prop.customType.convertToDatabaseValue(prop.default, this.#platform, { mode: 'discovery' });
|
|
1908
1939
|
}
|
|
1909
|
-
prop.defaultRaw = typeof val === 'string' ?
|
|
1940
|
+
prop.defaultRaw = typeof val === 'string' ? this.#platform.quoteDefaultValue(val) : '' + val;
|
|
1910
1941
|
}
|
|
1911
1942
|
inferTypeFromDefault(prop) {
|
|
1912
1943
|
if ((prop.defaultRaw == null && prop.default == null) || prop.type !== 'any') {
|
|
@@ -9,6 +9,7 @@ export declare class MetadataStorage {
|
|
|
9
9
|
/** Returns the global metadata dictionary, or a specific entry by entity name and path (keyed by the class reference when `target` is provided). */
|
|
10
10
|
static getMetadata(): Dictionary<EntityMetadata>;
|
|
11
11
|
static getMetadata<T = any>(entity: string, path: string, target?: EntityCtor): EntityMetadata<T>;
|
|
12
|
+
private static hashPath;
|
|
12
13
|
/** Checks whether an entity with the given class name exists in the global metadata. */
|
|
13
14
|
static isKnownEntity(name: string): boolean;
|
|
14
15
|
/** Clears all entries from the global metadata registry. */
|
|
@@ -13,6 +13,10 @@ export class MetadataStorage {
|
|
|
13
13
|
static PATH_SYMBOL = Symbol.for('@mikro-orm/core/MetadataStorage.PATH_SYMBOL');
|
|
14
14
|
static META_SYMBOL = Symbol.for('@mikro-orm/core/MetadataStorage.META_SYMBOL');
|
|
15
15
|
static #metadata = getGlobalStorage('metadata');
|
|
16
|
+
/** Lives in the global storage next to the registry, so it stays in sync when several copies of this module share it (e.g. after `vi.resetModules()`). */
|
|
17
|
+
static #classNames = getGlobalStorage('metadata-class-names');
|
|
18
|
+
/** Hashes of entity file paths, decorators call `getMetadata()` with the same path for every decorated member. */
|
|
19
|
+
static #pathHashes = new Map();
|
|
16
20
|
#metadataMap = new Map();
|
|
17
21
|
#idMap;
|
|
18
22
|
#classNameMap;
|
|
@@ -31,7 +35,7 @@ export class MetadataStorage {
|
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
static getMetadata(entity, path, target) {
|
|
34
|
-
const key = entity && path ? entity + '-' +
|
|
38
|
+
const key = entity && path ? entity + '-' + MetadataStorage.hashPath(path) : null;
|
|
35
39
|
// Key the registry by the class reference when available, so two classes minified
|
|
36
40
|
// to the same mangled name don't collide on the `className-path` key.
|
|
37
41
|
if (key && target) {
|
|
@@ -43,23 +47,36 @@ export class MetadataStorage {
|
|
|
43
47
|
}
|
|
44
48
|
// Keep the name-keyed entry in sync, the class-keyed metadata survives `MetadataStorage.clear()`.
|
|
45
49
|
MetadataStorage.#metadata[key] = target[MetadataStorage.META_SYMBOL];
|
|
50
|
+
MetadataStorage.#classNames.names?.add(target[MetadataStorage.META_SYMBOL].className);
|
|
46
51
|
return target[MetadataStorage.META_SYMBOL];
|
|
47
52
|
}
|
|
48
53
|
if (key && !MetadataStorage.#metadata[key]) {
|
|
49
|
-
|
|
54
|
+
const meta = new EntityMetadata({ className: entity, path });
|
|
55
|
+
MetadataStorage.#metadata[key] = meta;
|
|
56
|
+
MetadataStorage.#classNames.names?.add(meta.className);
|
|
50
57
|
}
|
|
51
58
|
if (key) {
|
|
52
59
|
return MetadataStorage.#metadata[key];
|
|
53
60
|
}
|
|
54
61
|
return MetadataStorage.#metadata;
|
|
55
62
|
}
|
|
63
|
+
static hashPath(path) {
|
|
64
|
+
let hash = MetadataStorage.#pathHashes.get(path);
|
|
65
|
+
if (hash === undefined) {
|
|
66
|
+
hash = Utils.hash(path);
|
|
67
|
+
MetadataStorage.#pathHashes.set(path, hash);
|
|
68
|
+
}
|
|
69
|
+
return hash;
|
|
70
|
+
}
|
|
56
71
|
/** Checks whether an entity with the given class name exists in the global metadata. */
|
|
57
72
|
static isKnownEntity(name) {
|
|
58
|
-
|
|
73
|
+
this.#classNames.names ??= new Set(Object.values(this.#metadata).map(meta => meta.className));
|
|
74
|
+
return this.#classNames.names.has(name);
|
|
59
75
|
}
|
|
60
76
|
/** Clears all entries from the global metadata registry. */
|
|
61
77
|
static clear() {
|
|
62
78
|
Object.keys(this.#metadata).forEach(k => delete this.#metadata[k]);
|
|
79
|
+
delete this.#classNames.names;
|
|
63
80
|
}
|
|
64
81
|
/** Returns the map of all registered entity metadata. */
|
|
65
82
|
getAll() {
|
|
@@ -118,12 +118,13 @@ export class MetadataValidator {
|
|
|
118
118
|
return Utils.className(p);
|
|
119
119
|
};
|
|
120
120
|
const pivotProps = new Map();
|
|
121
|
+
const discoveredNames = new Set(discovered.map(m => m.className));
|
|
121
122
|
// check for not discovered entities
|
|
122
123
|
discovered.forEach(meta => Object.values(meta.properties).forEach(prop => {
|
|
123
124
|
if (prop.kind !== ReferenceKind.SCALAR &&
|
|
124
125
|
!unwrap(prop.type)
|
|
125
126
|
.split(/ ?\| ?/)
|
|
126
|
-
.every(type =>
|
|
127
|
+
.every(type => discoveredNames.has(type))) {
|
|
127
128
|
throw MetadataError.fromUnknownEntity(prop.type, `${meta.className}.${prop.name}`);
|
|
128
129
|
}
|
|
129
130
|
if (prop.pivotEntity) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mikro-orm/core",
|
|
3
|
-
"version": "7.2.
|
|
3
|
+
"version": "7.2.7-dev.0",
|
|
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/platforms/Platform.d.ts
CHANGED
|
@@ -274,6 +274,8 @@ export declare abstract class Platform {
|
|
|
274
274
|
}, quote?: string): string;
|
|
275
275
|
/** Quotes a literal value for safe embedding in SQL. */
|
|
276
276
|
quoteValue(value: any): string;
|
|
277
|
+
/** Quotes a string default value for DDL; dialects that escape with backslashes override this. */
|
|
278
|
+
quoteDefaultValue(value: string): string;
|
|
277
279
|
escape(value: any): string;
|
|
278
280
|
/** Replaces `?` placeholders in SQL with quoted parameter values. */
|
|
279
281
|
formatQuery(sql: string, params: readonly any[]): string;
|
package/platforms/Platform.js
CHANGED
|
@@ -573,6 +573,10 @@ export class Platform {
|
|
|
573
573
|
quoteValue(value) {
|
|
574
574
|
return value;
|
|
575
575
|
}
|
|
576
|
+
/** Quotes a string default value for DDL; dialects that escape with backslashes override this. */
|
|
577
|
+
quoteDefaultValue(value) {
|
|
578
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
579
|
+
}
|
|
576
580
|
/* v8 ignore next */
|
|
577
581
|
escape(value) {
|
|
578
582
|
return value;
|
package/typings.js
CHANGED
|
@@ -304,8 +304,11 @@ export class EntityMetadata {
|
|
|
304
304
|
}, { __gettersDefined: { value: true, enumerable: false } });
|
|
305
305
|
}
|
|
306
306
|
initIndexes(prop) {
|
|
307
|
-
|
|
308
|
-
const
|
|
307
|
+
// `prop.index`/`prop.unique` keep only the name, so an index with any other option must stay in `meta.indexes`
|
|
308
|
+
const isSimple = (index) => index.properties === prop.name &&
|
|
309
|
+
Object.entries(index).every(([key, value]) => key === 'name' || key === 'properties' || value == null);
|
|
310
|
+
const simpleIndex = this.indexes.find(isSimple);
|
|
311
|
+
const simpleUnique = this.uniques.find(isSimple);
|
|
309
312
|
const owner = prop.kind === ReferenceKind.MANY_TO_ONE;
|
|
310
313
|
if (!prop.index && simpleIndex) {
|
|
311
314
|
Utils.defaultValue(simpleIndex, 'name', true);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { clone } from './clone.js';
|
|
2
2
|
import { ReferenceKind } from '../enums.js';
|
|
3
3
|
import { compareArrays, compareBooleans, compareBuffers, compareObjects, equals, parseJsonSafe, Utils, } from './Utils.js';
|
|
4
|
+
import { DateType } from '../types/DateType.js';
|
|
4
5
|
import { JsonType } from '../types/JsonType.js';
|
|
5
6
|
import { Raw } from './RawQueryFragment.js';
|
|
6
7
|
import { EntityIdentifier } from '../entity/EntityIdentifier.js';
|
|
@@ -395,6 +396,14 @@ export class EntityComparator {
|
|
|
395
396
|
lines.push(`${padding} ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
|
|
396
397
|
lines.push(`${padding} }`);
|
|
397
398
|
}
|
|
399
|
+
else if (prop.customType instanceof DateType) {
|
|
400
|
+
// tedious returns DATE columns as `Date`, map them to the string the entity holds, so the snapshot matches it
|
|
401
|
+
context.set('convertDateToJSValue', (value) => this.#platform.convertDateToJSValue(value));
|
|
402
|
+
lines.push(`${padding} if (${defined}) {`);
|
|
403
|
+
lines.push(`${padding} ret${this.wrap(prop.name)} = ${this.propName(prop.fieldNames[0])} instanceof Date ? convertDateToJSValue(${this.propName(prop.fieldNames[0])}) : ${this.propName(prop.fieldNames[0])};`);
|
|
404
|
+
lines.push(`${padding} ${this.propName(prop.fieldNames[0], 'mapped')} = true;`);
|
|
405
|
+
lines.push(`${padding} }`);
|
|
406
|
+
}
|
|
398
407
|
else if (prop.kind === ReferenceKind.EMBEDDED && (prop.object || meta.embeddable)) {
|
|
399
408
|
const idx = this.#tmpIndex++;
|
|
400
409
|
context.set(`mapEmbeddedResult_${idx}`, (data) => {
|
package/utils/QueryHelper.js
CHANGED
|
@@ -308,6 +308,13 @@ export class QueryHelper {
|
|
|
308
308
|
o[key] = { [op]: value };
|
|
309
309
|
return o;
|
|
310
310
|
}
|
|
311
|
+
// `$all` items are matched like separate `$some` payloads, so they need the same processing
|
|
312
|
+
if (key === '$all' && Array.isArray(value)) {
|
|
313
|
+
o[key] = value.map(item => Utils.isPlainObject(item)
|
|
314
|
+
? QueryHelper.processWhere({ ...options, where: item, entityName, root: false })
|
|
315
|
+
: item);
|
|
316
|
+
return o;
|
|
317
|
+
}
|
|
311
318
|
if (Utils.isPlainObject(value)) {
|
|
312
319
|
o[key] = QueryHelper.processWhere({
|
|
313
320
|
...options,
|
package/utils/Utils.js
CHANGED
|
@@ -153,7 +153,7 @@ export function parseJsonSafe(value) {
|
|
|
153
153
|
/** Collection of general-purpose utility methods used throughout the ORM. */
|
|
154
154
|
export class Utils {
|
|
155
155
|
static PK_SEPARATOR = '~~~';
|
|
156
|
-
static #ORM_VERSION = '7.2.
|
|
156
|
+
static #ORM_VERSION = '7.2.7-dev.0';
|
|
157
157
|
/** Default session variable name backing an RLS filter argument (`current_setting('mikro.<filter>.<arg>')`). */
|
|
158
158
|
static getRlsSettingName(filterName, argName) {
|
|
159
159
|
return `mikro.${filterName}.${argName}`;
|
|
@@ -651,14 +651,19 @@ export class Utils {
|
|
|
651
651
|
static isCollection(item) {
|
|
652
652
|
return Collection.isCollection(item);
|
|
653
653
|
}
|
|
654
|
-
// FNV-1a 64-bit
|
|
654
|
+
// FNV-1a 64-bit, computed on two uint32 halves instead of a BigInt. The prime is 2^40 + 0x1b3, so
|
|
655
|
+
// `h * prime mod 2^64` is `h * 0x1b3` plus the low half shifted up by 40 bits. Every partial product
|
|
656
|
+
// stays below 2^41, so the double arithmetic is exact.
|
|
655
657
|
static hash(data, length) {
|
|
656
|
-
let
|
|
658
|
+
let hi = 0xcbf29ce4;
|
|
659
|
+
let lo = 0x84222325;
|
|
657
660
|
for (let i = 0; i < data.length; i++) {
|
|
658
|
-
|
|
659
|
-
|
|
661
|
+
lo = (lo ^ data.charCodeAt(i)) >>> 0;
|
|
662
|
+
const loProduct = lo * 0x1b3;
|
|
663
|
+
hi = (((hi * 0x1b3) >>> 0) + Math.floor(loProduct / 0x100000000) + ((lo << 8) >>> 0)) >>> 0;
|
|
664
|
+
lo = loProduct >>> 0;
|
|
660
665
|
}
|
|
661
|
-
const hash =
|
|
666
|
+
const hash = hi.toString(16).padStart(8, '0') + lo.toString(16).padStart(8, '0');
|
|
662
667
|
if (length) {
|
|
663
668
|
return hash.substring(0, length);
|
|
664
669
|
}
|
package/utils/clone.js
CHANGED
|
@@ -22,8 +22,8 @@ function getPropertyDescriptor(obj, prop) {
|
|
|
22
22
|
}
|
|
23
23
|
const TypedArray = Object.getPrototypeOf(Uint8Array);
|
|
24
24
|
export function clone(parent, respectCustomCloneMethod = true) {
|
|
25
|
-
|
|
26
|
-
const
|
|
25
|
+
// already cloned objects, so shared references and cycles map to a single clone
|
|
26
|
+
const clones = new Map();
|
|
27
27
|
function _clone(parent) {
|
|
28
28
|
// cloning null always returns null
|
|
29
29
|
if (parent === null) {
|
|
@@ -92,12 +92,10 @@ export function clone(parent, respectCustomCloneMethod = true) {
|
|
|
92
92
|
proto = Object.getPrototypeOf(parent);
|
|
93
93
|
child = Object.create(proto);
|
|
94
94
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return allChildren[index];
|
|
95
|
+
if (clones.has(parent)) {
|
|
96
|
+
return clones.get(parent);
|
|
98
97
|
}
|
|
99
|
-
|
|
100
|
-
allChildren.push(child);
|
|
98
|
+
clones.set(parent, child);
|
|
101
99
|
if (parent instanceof Map) {
|
|
102
100
|
parent.forEach((value, key) => {
|
|
103
101
|
const keyChild = _clone(key);
|