Initial commit - Event Planner application
This commit is contained in:
129
node_modules/@mikro-orm/core/entity/BaseEntity.d.ts
generated
vendored
Normal file
129
node_modules/@mikro-orm/core/entity/BaseEntity.d.ts
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
import { type Ref } from './Reference.js';
|
||||
import type {
|
||||
AutoPath,
|
||||
EntityData,
|
||||
EntityDTO,
|
||||
Loaded,
|
||||
LoadedReference,
|
||||
AddEager,
|
||||
EntityKey,
|
||||
FromEntityType,
|
||||
IsSubset,
|
||||
MergeSelected,
|
||||
SerializeDTO,
|
||||
} from '../typings.js';
|
||||
import { type AssignOptions } from './EntityAssigner.js';
|
||||
import type { EntityLoaderOptions } from './EntityLoader.js';
|
||||
import { type SerializeOptions } from '../serialization/EntitySerializer.js';
|
||||
import type { FindOneOptions } from '../drivers/IDatabaseDriver.js';
|
||||
import type { PopulatePath } from '../enums.js';
|
||||
/** Base class for entities providing convenience methods like `assign()`, `toObject()`, and `populate()`. */
|
||||
export declare abstract class BaseEntity {
|
||||
/** Returns whether the entity has been fully loaded from the database. */
|
||||
isInitialized(): boolean;
|
||||
/** Marks the entity as populated or not for serialization purposes. */
|
||||
populated(populated?: boolean): void;
|
||||
/** Loads the specified relations on this entity. */
|
||||
populate<Entity extends this = this, Hint extends string = never, Fields extends string = never>(
|
||||
populate: AutoPath<Entity, Hint, PopulatePath.ALL>[] | false,
|
||||
options?: EntityLoaderOptions<Entity, Fields>,
|
||||
): Promise<Loaded<Entity, Hint>>;
|
||||
/** Returns a Reference wrapper for this entity. */
|
||||
toReference<Entity extends this = this>(): Ref<Entity> & LoadedReference<Loaded<Entity, AddEager<Entity>>>;
|
||||
/**
|
||||
* Converts the entity to a plain object representation.
|
||||
*
|
||||
* **Note on typing with `Loaded` entities:** When called on a `Loaded<Entity, 'relation'>` type,
|
||||
* the return type will be `EntityDTO<Entity>` (with relations as primary keys), not
|
||||
* `EntityDTO<Loaded<Entity, 'relation'>>` (with loaded relations as nested objects).
|
||||
* This is a TypeScript limitation - the `this` type resolves to the class, not the `Loaded` wrapper.
|
||||
*
|
||||
* For correct typing that reflects loaded relations, use `wrap()`:
|
||||
* ```ts
|
||||
* const result = await em.find(User, {}, { populate: ['profile'] });
|
||||
* // Type: EntityDTO<User> (profile is number)
|
||||
* const obj1 = result[0].toObject();
|
||||
* // Type: EntityDTO<Loaded<User, 'profile'>> (profile is nested object)
|
||||
* const obj2 = wrap(result[0]).toObject();
|
||||
* ```
|
||||
*
|
||||
* Runtime values are correct in both cases - only the static types differ.
|
||||
*/
|
||||
toObject<Entity extends this = this>(): EntityDTO<Entity>;
|
||||
/**
|
||||
* Converts the entity to a plain object representation.
|
||||
*
|
||||
* **Note on typing with `Loaded` entities:** When called on a `Loaded<Entity, 'relation'>` type,
|
||||
* the return type will be `EntityDTO<Entity>` (with relations as primary keys), not
|
||||
* `EntityDTO<Loaded<Entity, 'relation'>>` (with loaded relations as nested objects).
|
||||
* This is a TypeScript limitation - the `this` type resolves to the class, not the `Loaded` wrapper.
|
||||
*
|
||||
* For correct typing that reflects loaded relations, use `wrap()`:
|
||||
* ```ts
|
||||
* const result = await em.find(User, {}, { populate: ['profile'] });
|
||||
* // Type: EntityDTO<User> (profile is number)
|
||||
* const obj1 = result[0].toObject();
|
||||
* // Type: EntityDTO<Loaded<User, 'profile'>> (profile is nested object)
|
||||
* const obj2 = wrap(result[0]).toObject();
|
||||
* ```
|
||||
*
|
||||
* Runtime values are correct in both cases - only the static types differ.
|
||||
*/
|
||||
toObject<Entity extends this = this>(ignoreFields: never[]): EntityDTO<Entity>;
|
||||
/**
|
||||
* Converts the entity to a plain object representation.
|
||||
*
|
||||
* **Note on typing with `Loaded` entities:** When called on a `Loaded<Entity, 'relation'>` type,
|
||||
* the return type will be `EntityDTO<Entity>` (with relations as primary keys), not
|
||||
* `EntityDTO<Loaded<Entity, 'relation'>>` (with loaded relations as nested objects).
|
||||
* This is a TypeScript limitation - the `this` type resolves to the class, not the `Loaded` wrapper.
|
||||
*
|
||||
* For correct typing that reflects loaded relations, use `wrap()`:
|
||||
* ```ts
|
||||
* const result = await em.find(User, {}, { populate: ['profile'] });
|
||||
* // Type: EntityDTO<User> (profile is number)
|
||||
* const obj1 = result[0].toObject();
|
||||
* // Type: EntityDTO<Loaded<User, 'profile'>> (profile is nested object)
|
||||
* const obj2 = wrap(result[0]).toObject();
|
||||
* ```
|
||||
*
|
||||
* Runtime values are correct in both cases - only the static types differ.
|
||||
*
|
||||
* @param ignoreFields - Array of field names to omit from the result.
|
||||
*/
|
||||
toObject<Entity extends this = this, Ignored extends EntityKey<Entity> = never>(
|
||||
ignoreFields: Ignored[],
|
||||
): Omit<EntityDTO<Entity>, Ignored>;
|
||||
/** Converts the entity to a plain object, including all properties regardless of serialization rules. */
|
||||
toPOJO<Entity extends this = this>(): EntityDTO<Entity>;
|
||||
/** Serializes the entity with control over which relations and fields to include or exclude. */
|
||||
serialize<
|
||||
Entity extends this = this,
|
||||
Naked extends FromEntityType<Entity> = FromEntityType<Entity>,
|
||||
Hint extends string = never,
|
||||
Exclude extends string = never,
|
||||
>(options?: SerializeOptions<Naked, Hint, Exclude>): SerializeDTO<Naked, Hint, Exclude>;
|
||||
/** Assigns the given data to this entity, updating its properties and relations. */
|
||||
assign<
|
||||
Entity extends this,
|
||||
Naked extends FromEntityType<Entity> = FromEntityType<Entity>,
|
||||
Convert extends boolean = false,
|
||||
Data extends EntityData<Naked, Convert> | Partial<EntityDTO<Naked>> =
|
||||
| EntityData<Naked, Convert>
|
||||
| Partial<EntityDTO<Naked>>,
|
||||
>(
|
||||
data: Data & IsSubset<EntityData<Naked>, Data>,
|
||||
options?: AssignOptions<Convert>,
|
||||
): MergeSelected<Entity, Naked, keyof Data & string>;
|
||||
/** Initializes (refreshes) the entity by reloading it from the database. Returns null if not found. */
|
||||
init<
|
||||
Entity extends this = this,
|
||||
Hint extends string = never,
|
||||
Fields extends string = '*',
|
||||
Excludes extends string = never,
|
||||
>(options?: FindOneOptions<Entity, Hint, Fields, Excludes>): Promise<Loaded<Entity, Hint, Fields, Excludes> | null>;
|
||||
/** Returns the database schema this entity belongs to. */
|
||||
getSchema(): string | undefined;
|
||||
/** Sets the database schema for this entity. */
|
||||
setSchema(schema?: string): void;
|
||||
}
|
||||
51
node_modules/@mikro-orm/core/entity/BaseEntity.js
generated
vendored
Normal file
51
node_modules/@mikro-orm/core/entity/BaseEntity.js
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
import { Reference } from './Reference.js';
|
||||
import { EntityAssigner } from './EntityAssigner.js';
|
||||
import { EntitySerializer } from '../serialization/EntitySerializer.js';
|
||||
import { helper } from './wrap.js';
|
||||
/** Base class for entities providing convenience methods like `assign()`, `toObject()`, and `populate()`. */
|
||||
export class BaseEntity {
|
||||
/** Returns whether the entity has been fully loaded from the database. */
|
||||
isInitialized() {
|
||||
return helper(this).__initialized;
|
||||
}
|
||||
/** Marks the entity as populated or not for serialization purposes. */
|
||||
populated(populated = true) {
|
||||
helper(this).populated(populated);
|
||||
}
|
||||
/** Loads the specified relations on this entity. */
|
||||
async populate(populate, options = {}) {
|
||||
return helper(this).populate(populate, options);
|
||||
}
|
||||
/** Returns a Reference wrapper for this entity. */
|
||||
toReference() {
|
||||
return Reference.create(this);
|
||||
}
|
||||
toObject(ignoreFields) {
|
||||
return helper(this).toObject(ignoreFields);
|
||||
}
|
||||
/** Converts the entity to a plain object, including all properties regardless of serialization rules. */
|
||||
toPOJO() {
|
||||
return helper(this).toPOJO();
|
||||
}
|
||||
/** Serializes the entity with control over which relations and fields to include or exclude. */
|
||||
serialize(options) {
|
||||
return EntitySerializer.serialize(this, options);
|
||||
}
|
||||
/** Assigns the given data to this entity, updating its properties and relations. */
|
||||
assign(data, options = {}) {
|
||||
return EntityAssigner.assign(this, data, options);
|
||||
}
|
||||
/** Initializes (refreshes) the entity by reloading it from the database. Returns null if not found. */
|
||||
init(options) {
|
||||
return helper(this).init(options);
|
||||
}
|
||||
/** Returns the database schema this entity belongs to. */
|
||||
getSchema() {
|
||||
return helper(this).getSchema();
|
||||
}
|
||||
/** Sets the database schema for this entity. */
|
||||
setSchema(schema) {
|
||||
helper(this).setSchema(schema);
|
||||
}
|
||||
}
|
||||
Object.defineProperty(BaseEntity.prototype, '__baseEntity', { value: true, writable: false, enumerable: false });
|
||||
227
node_modules/@mikro-orm/core/entity/Collection.d.ts
generated
vendored
Normal file
227
node_modules/@mikro-orm/core/entity/Collection.d.ts
generated
vendored
Normal file
@@ -0,0 +1,227 @@
|
||||
import type {
|
||||
EntityDTO,
|
||||
EntityKey,
|
||||
EntityProperty,
|
||||
FilterQuery,
|
||||
IPrimaryKey,
|
||||
Loaded,
|
||||
LoadedCollection,
|
||||
Populate,
|
||||
Primary,
|
||||
} from '../typings.js';
|
||||
import { Reference } from './Reference.js';
|
||||
import type { Transaction } from '../connections/Connection.js';
|
||||
import type { CountOptions, FindOptions } from '../drivers/IDatabaseDriver.js';
|
||||
import type { EntityLoaderOptions } from './EntityLoader.js';
|
||||
/** Options for the `Collection.matching()` method to query a subset of collection items from the database. */
|
||||
export interface MatchingOptions<T extends object, P extends string = never> extends FindOptions<T, P> {
|
||||
/** Additional filtering conditions for the query. */
|
||||
where?: FilterQuery<T>;
|
||||
/** Whether to store the matched items in the collection (makes it read-only). */
|
||||
store?: boolean;
|
||||
/** Transaction context for the query. */
|
||||
ctx?: Transaction;
|
||||
}
|
||||
/** Represents a to-many relation (1:m or m:n) as an iterable, managed collection of entities. */
|
||||
export declare class Collection<T extends object, O extends object = object> {
|
||||
#private;
|
||||
readonly owner: O;
|
||||
[k: number]: T;
|
||||
constructor(owner: O, items?: T[], initialized?: boolean);
|
||||
/**
|
||||
* Creates new Collection instance, assigns it to the owning entity and sets the items to it (propagating them to their inverse sides)
|
||||
*/
|
||||
static create<T extends object, O extends object = object>(
|
||||
owner: O,
|
||||
prop: EntityKey<O>,
|
||||
items: undefined | T[],
|
||||
initialized: boolean,
|
||||
): Collection<T, O>;
|
||||
/**
|
||||
* Ensures the collection is loaded first (without reloading it if it already is loaded).
|
||||
* Returns the Collection instance (itself), works the same as `Reference.load()`.
|
||||
*/
|
||||
load<TT extends T, P extends string = never>(
|
||||
options?: InitCollectionOptions<TT, P>,
|
||||
): Promise<LoadedCollection<Loaded<TT, P>>>;
|
||||
private setSerializationContext;
|
||||
/**
|
||||
* Initializes the collection and returns the items
|
||||
*/
|
||||
loadItems<TT extends T, P extends string = never>(options?: InitCollectionOptions<TT, P>): Promise<Loaded<TT, P>[]>;
|
||||
/**
|
||||
* Gets the count of collection items from database instead of counting loaded items.
|
||||
* The value is cached (unless you use the `where` option), use `refresh: true` to force reload it.
|
||||
*/
|
||||
loadCount(options?: LoadCountOptions<T> | boolean): Promise<number>;
|
||||
/** Queries a subset of the collection items from the database with custom filtering, ordering, and pagination. */
|
||||
matching<TT extends T, P extends string = never>(options: MatchingOptions<T, P>): Promise<Loaded<TT, P>[]>;
|
||||
/**
|
||||
* Returns the items (the collection must be initialized)
|
||||
*/
|
||||
getItems(check?: boolean): T[];
|
||||
/** Serializes the collection items to plain JSON objects. Returns an empty array if not initialized. */
|
||||
toJSON<TT extends T>(): EntityDTO<TT>[];
|
||||
/** Adds one or more items to the collection, propagating the change to the inverse side. Returns the number of items added. */
|
||||
add<TT extends T>(
|
||||
entity: TT | Reference<TT> | Iterable<TT | Reference<TT>>,
|
||||
...entities: (TT | Reference<TT>)[]
|
||||
): number;
|
||||
/**
|
||||
* Remove specified item(s) from the collection. Note that removing item from collection does not necessarily imply deleting the target entity,
|
||||
* it means we are disconnecting the relation - removing items from collection, not removing entities from database - `Collection.remove()`
|
||||
* is not the same as `em.remove()`. If we want to delete the entity by removing it from collection, we need to enable `orphanRemoval: true`,
|
||||
* which tells the ORM we don't want orphaned entities to exist, so we know those should be removed.
|
||||
*/
|
||||
remove<TT extends T>(
|
||||
entity: TT | Reference<TT> | Iterable<TT | Reference<TT>> | ((item: TT) => boolean),
|
||||
...entities: (TT | Reference<TT>)[]
|
||||
): number;
|
||||
/** Checks whether the collection contains the given item. */
|
||||
contains<TT extends T>(item: TT | Reference<TT>, check?: boolean): boolean;
|
||||
/** Returns the number of items in the collection. Throws if the collection is not initialized. */
|
||||
count(): number;
|
||||
/** Returns true if the collection has no items. Throws if the collection is not initialized. */
|
||||
isEmpty(): boolean;
|
||||
/** Returns whether this collection should be included in serialization based on its populated state. */
|
||||
shouldPopulate(populated?: boolean): boolean;
|
||||
/** Marks the collection as populated or not for serialization purposes. */
|
||||
populated(populated?: boolean | undefined): void;
|
||||
/** Initializes the collection by loading its items from the database. */
|
||||
init<TT extends T, P extends string = never>(
|
||||
options?: InitCollectionOptions<TT, P>,
|
||||
): Promise<LoadedCollection<Loaded<TT, P>>>;
|
||||
private getEntityManager;
|
||||
private createCondition;
|
||||
private createManyToManyCondition;
|
||||
private createLoadCountCondition;
|
||||
private checkInitialized;
|
||||
/**
|
||||
* re-orders items after searching with `$in` operator
|
||||
*/
|
||||
private reorderItems;
|
||||
private cancelOrphanRemoval;
|
||||
private validateModification;
|
||||
/** Converts all items in the collection to plain DTO objects. */
|
||||
toArray<TT extends T>(): EntityDTO<TT>[];
|
||||
/** Returns the primary key values (or a specific field) of all items in the collection. */
|
||||
getIdentifiers<U extends IPrimaryKey = Primary<T> & IPrimaryKey>(field?: string | string[]): U[];
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
addWithoutPropagation(entity: T): void;
|
||||
/** Replaces all items in the collection with the given items. */
|
||||
set(items: Iterable<T | Reference<T>>): void;
|
||||
private compare;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
hydrate(items: T[], forcePropagate?: boolean, partial?: boolean): void;
|
||||
/**
|
||||
* Remove all items from the collection. Note that removing items from collection does not necessarily imply deleting the target entity,
|
||||
* it means we are disconnecting the relation - removing items from collection, not removing entities from database - `Collection.remove()`
|
||||
* is not the same as `em.remove()`. If we want to delete the entity by removing it from collection, we need to enable `orphanRemoval: true`,
|
||||
* which tells the ORM we don't want orphaned entities to exist, so we know those should be removed.
|
||||
*/
|
||||
removeAll(): void;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
removeWithoutPropagation(entity: T): void;
|
||||
/**
|
||||
* Extracts a slice of the collection items starting at position start to end (exclusive) of the collection.
|
||||
* If end is null it returns all elements from start to the end of the collection.
|
||||
*/
|
||||
slice(start?: number, end?: number): T[];
|
||||
/**
|
||||
* Tests for the existence of an element that satisfies the given predicate.
|
||||
*/
|
||||
exists(cb: (item: T) => boolean): boolean;
|
||||
/**
|
||||
* Returns the first element of this collection that satisfies the predicate.
|
||||
*/
|
||||
find<S extends T>(cb: (item: T, index: number) => item is S): S | undefined;
|
||||
/**
|
||||
* Returns the first element of this collection that satisfies the predicate.
|
||||
*/
|
||||
find(cb: (item: T, index: number) => boolean): T | undefined;
|
||||
/**
|
||||
* Extracts a subset of the collection items.
|
||||
*/
|
||||
filter<S extends T>(cb: (item: T, index: number) => item is S): S[];
|
||||
/**
|
||||
* Extracts a subset of the collection items.
|
||||
*/
|
||||
filter(cb: (item: T, index: number) => boolean): T[];
|
||||
/**
|
||||
* Maps the collection items based on your provided mapper function.
|
||||
*/
|
||||
map<R>(mapper: (item: T, index: number) => R): R[];
|
||||
/**
|
||||
* Maps the collection items based on your provided mapper function to a single object.
|
||||
*/
|
||||
reduce<R>(cb: (obj: R, item: T, index: number) => R, initial?: R): R;
|
||||
/**
|
||||
* Maps the collection items to a dictionary, indexed by the key you specify.
|
||||
* If there are more items with the same key, only the first one will be present.
|
||||
*/
|
||||
indexBy<K1 extends keyof T, K2 extends keyof T = never>(key: K1): Record<T[K1] & PropertyKey, T>;
|
||||
/**
|
||||
* Maps the collection items to a dictionary, indexed by the key you specify.
|
||||
* If there are more items with the same key, only the first one will be present.
|
||||
*/
|
||||
indexBy<K1 extends keyof T, K2 extends keyof T = never>(key: K1, valueKey: K2): Record<T[K1] & PropertyKey, T[K2]>;
|
||||
/** Returns whether the collection has been initialized. Pass `fully = true` to also check that all items are initialized. */
|
||||
isInitialized(fully?: boolean): boolean;
|
||||
isDirty(): boolean;
|
||||
/** Returns whether the collection was partially loaded (propagation is disabled for partial collections). */
|
||||
isPartial(): boolean;
|
||||
setDirty(dirty?: boolean): void;
|
||||
get length(): number;
|
||||
[Symbol.iterator](): IterableIterator<T>;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
takeSnapshot(forcePropagate?: boolean): void;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
getSnapshot(): T[] | undefined;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
get property(): EntityProperty;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
set property(prop: EntityProperty);
|
||||
protected propagate(item: T, method: 'add' | 'remove' | 'takeSnapshot'): void;
|
||||
protected propagateToInverseSide(item: T, method: 'add' | 'remove' | 'takeSnapshot'): void;
|
||||
protected propagateToOwningSide(item: T, method: 'add' | 'remove' | 'takeSnapshot'): void;
|
||||
protected shouldPropagateToCollection(
|
||||
collection: Collection<O, T>,
|
||||
method: 'add' | 'remove' | 'takeSnapshot',
|
||||
): boolean;
|
||||
protected incrementCount(value: number): void;
|
||||
}
|
||||
/** Options for initializing a collection via `init()` or `load()`. */
|
||||
export interface InitCollectionOptions<
|
||||
T,
|
||||
P extends string = never,
|
||||
F extends string = '*',
|
||||
E extends string = never,
|
||||
> extends EntityLoaderOptions<T, F, E> {
|
||||
/** Whether to use the dataloader for batching collection loads. */
|
||||
dataloader?: boolean;
|
||||
/** Relations to populate on the loaded items. */
|
||||
populate?: Populate<T, P>;
|
||||
/** Populate only references (without loading full entities). Works only with M:N collections that use pivot table. */
|
||||
ref?: boolean;
|
||||
}
|
||||
/** Options for the `Collection.loadCount()` method. */
|
||||
export interface LoadCountOptions<T extends object> extends CountOptions<T, '*'> {
|
||||
/** Whether to reload the count from the database even if it is already cached. */
|
||||
refresh?: boolean;
|
||||
/** Additional filtering conditions for the count query. */
|
||||
where?: FilterQuery<T>;
|
||||
}
|
||||
747
node_modules/@mikro-orm/core/entity/Collection.js
generated
vendored
Normal file
747
node_modules/@mikro-orm/core/entity/Collection.js
generated
vendored
Normal file
@@ -0,0 +1,747 @@
|
||||
import { Utils } from '../utils/Utils.js';
|
||||
import { MetadataError, ValidationError } from '../errors.js';
|
||||
import { DataloaderType, ReferenceKind } from '../enums.js';
|
||||
import { Reference } from './Reference.js';
|
||||
import { helper, wrap } from './wrap.js';
|
||||
import { QueryHelper } from '../utils/QueryHelper.js';
|
||||
import { inspect } from '../logging/inspect.js';
|
||||
/** Represents a to-many relation (1:m or m:n) as an iterable, managed collection of entities. */
|
||||
export class Collection {
|
||||
owner;
|
||||
#items = new Set();
|
||||
#initialized = true;
|
||||
#dirty = false;
|
||||
#partial = false; // mark partially loaded collections, propagation is disabled for those
|
||||
#snapshot = []; // used to create a diff of the collection at commit time, undefined marks overridden values so we need to wipe when flushing
|
||||
#readonly;
|
||||
#count;
|
||||
#property;
|
||||
#populated;
|
||||
constructor(owner, items, initialized = true) {
|
||||
this.owner = owner;
|
||||
/* v8 ignore next */
|
||||
if (items) {
|
||||
let i = 0;
|
||||
this.#items = new Set(items);
|
||||
this.#items.forEach(item => (this[i++] = item));
|
||||
}
|
||||
this.#initialized = !!items || initialized;
|
||||
}
|
||||
/**
|
||||
* Creates new Collection instance, assigns it to the owning entity and sets the items to it (propagating them to their inverse sides)
|
||||
*/
|
||||
static create(owner, prop, items, initialized) {
|
||||
const coll = new Collection(owner, undefined, initialized);
|
||||
coll.property = helper(owner).__meta.properties[prop];
|
||||
owner[prop] = coll;
|
||||
if (items) {
|
||||
coll.set(items);
|
||||
}
|
||||
return coll;
|
||||
}
|
||||
/**
|
||||
* Ensures the collection is loaded first (without reloading it if it already is loaded).
|
||||
* Returns the Collection instance (itself), works the same as `Reference.load()`.
|
||||
*/
|
||||
async load(options = {}) {
|
||||
if (this.isInitialized(true) && !options.refresh) {
|
||||
const em = this.getEntityManager(this.#items, false);
|
||||
options = { ...options, filters: QueryHelper.mergePropertyFilters(this.property.filters, options.filters) };
|
||||
await em?.populate(this.#items, options.populate, options);
|
||||
this.setSerializationContext(options);
|
||||
} else {
|
||||
await this.init({ refresh: false, ...options });
|
||||
}
|
||||
return this;
|
||||
}
|
||||
setSerializationContext(options) {
|
||||
helper(this.owner).setSerializationContext({
|
||||
populate: Array.isArray(options.populate)
|
||||
? options.populate.map(hint => `${this.property.name}.${hint}`)
|
||||
: (options.populate ?? [this.property.name]),
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Initializes the collection and returns the items
|
||||
*/
|
||||
async loadItems(options) {
|
||||
await this.load(options);
|
||||
return this.getItems(false);
|
||||
}
|
||||
/**
|
||||
* Gets the count of collection items from database instead of counting loaded items.
|
||||
* The value is cached (unless you use the `where` option), use `refresh: true` to force reload it.
|
||||
*/
|
||||
async loadCount(options = {}) {
|
||||
options = typeof options === 'boolean' ? { refresh: options } : options;
|
||||
const { refresh, where, ...countOptions } = options;
|
||||
if (!refresh && !where && this.#count != null) {
|
||||
return this.#count;
|
||||
}
|
||||
const em = this.getEntityManager();
|
||||
if (
|
||||
!em.getPlatform().usesPivotTable() &&
|
||||
this.property.kind === ReferenceKind.MANY_TO_MANY &&
|
||||
this.property.owner
|
||||
) {
|
||||
return (this.#count = this.length);
|
||||
}
|
||||
const cond = this.createLoadCountCondition(where ?? {});
|
||||
const count = await em.count(this.property.targetMeta.class, cond, countOptions);
|
||||
if (!where) {
|
||||
this.#count = count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
/** Queries a subset of the collection items from the database with custom filtering, ordering, and pagination. */
|
||||
async matching(options) {
|
||||
const em = this.getEntityManager();
|
||||
const { where, ctx, ...opts } = options;
|
||||
let items;
|
||||
if (this.property.kind === ReferenceKind.MANY_TO_MANY && em.getPlatform().usesPivotTable()) {
|
||||
// M:N via pivot table bypasses em.find(), so merge all 3 levels here
|
||||
opts.orderBy = QueryHelper.mergeOrderBy(opts.orderBy, this.property.orderBy, this.property.targetMeta?.orderBy);
|
||||
options.populate = await em.preparePopulate(this.property.targetMeta.class, options);
|
||||
const cond = await em.applyFilters(this.property.targetMeta.class, where, options.filters ?? {}, 'read');
|
||||
const map = await em
|
||||
.getDriver()
|
||||
.loadFromPivotTable(this.property, [helper(this.owner).__primaryKeys], cond, opts.orderBy, ctx, options);
|
||||
items = map[helper(this.owner).getSerializedPrimaryKey()].map(item =>
|
||||
em.merge(this.property.targetMeta.class, item, { convertCustomTypes: true }),
|
||||
);
|
||||
await em.populate(items, options.populate, options);
|
||||
} else {
|
||||
// em.find() merges entity-level orderBy, so only merge runtime + relation here
|
||||
opts.orderBy = QueryHelper.mergeOrderBy(opts.orderBy, this.property.orderBy);
|
||||
items = await em.find(this.property.targetMeta.class, this.createCondition(where), opts);
|
||||
}
|
||||
if (options.store) {
|
||||
this.hydrate(items, true);
|
||||
this.setSerializationContext(options);
|
||||
this.populated();
|
||||
this.#readonly = true;
|
||||
}
|
||||
return items;
|
||||
}
|
||||
/**
|
||||
* Returns the items (the collection must be initialized)
|
||||
*/
|
||||
getItems(check = true) {
|
||||
if (check) {
|
||||
this.checkInitialized();
|
||||
}
|
||||
return [...this.#items];
|
||||
}
|
||||
/** Serializes the collection items to plain JSON objects. Returns an empty array if not initialized. */
|
||||
toJSON() {
|
||||
if (!this.isInitialized()) {
|
||||
return [];
|
||||
}
|
||||
return this.toArray();
|
||||
}
|
||||
/** Adds one or more items to the collection, propagating the change to the inverse side. Returns the number of items added. */
|
||||
add(entity, ...entities) {
|
||||
entities = Utils.asArray(entity).concat(entities);
|
||||
const unwrapped = entities.map(i => Reference.unwrapReference(i));
|
||||
this.validateModification(unwrapped);
|
||||
const em = this.getEntityManager(entities, false);
|
||||
let added = 0;
|
||||
for (const item of entities) {
|
||||
const entity = Reference.unwrapReference(item);
|
||||
if (!this.contains(entity, false)) {
|
||||
this.incrementCount(1);
|
||||
this[this.#items.size] = entity;
|
||||
this.#items.add(entity);
|
||||
added++;
|
||||
this.#dirty = true;
|
||||
this.propagate(entity, 'add');
|
||||
}
|
||||
}
|
||||
if (this.property.kind === ReferenceKind.ONE_TO_MANY && em) {
|
||||
em.persist(entities);
|
||||
}
|
||||
this.cancelOrphanRemoval(unwrapped);
|
||||
return added;
|
||||
}
|
||||
/**
|
||||
* Remove specified item(s) from the collection. Note that removing item from collection does not necessarily imply deleting the target entity,
|
||||
* it means we are disconnecting the relation - removing items from collection, not removing entities from database - `Collection.remove()`
|
||||
* is not the same as `em.remove()`. If we want to delete the entity by removing it from collection, we need to enable `orphanRemoval: true`,
|
||||
* which tells the ORM we don't want orphaned entities to exist, so we know those should be removed.
|
||||
*/
|
||||
remove(entity, ...entities) {
|
||||
if (entity instanceof Function) {
|
||||
let removed = 0;
|
||||
for (const item of this.#items) {
|
||||
if (entity(item)) {
|
||||
removed += this.remove(item);
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
this.checkInitialized();
|
||||
entities = Utils.asArray(entity).concat(entities);
|
||||
const unwrapped = entities.map(i => Reference.unwrapReference(i));
|
||||
this.validateModification(unwrapped);
|
||||
const em = this.getEntityManager(entities, false);
|
||||
let removed = 0;
|
||||
for (const item of entities) {
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
const entity = Reference.unwrapReference(item);
|
||||
if (this.#items.delete(entity)) {
|
||||
this.incrementCount(-1);
|
||||
delete this[this.#items.size]; // remove last item
|
||||
this.propagate(entity, 'remove');
|
||||
removed++;
|
||||
this.#dirty = true;
|
||||
}
|
||||
if (this.property.orphanRemoval && em) {
|
||||
em.getUnitOfWork().scheduleOrphanRemoval(entity);
|
||||
}
|
||||
}
|
||||
if (this.property.kind === ReferenceKind.ONE_TO_MANY && !this.property.orphanRemoval && em) {
|
||||
em.persist(entities);
|
||||
}
|
||||
if (removed > 0) {
|
||||
Object.assign(this, [...this.#items]); // reassign array access
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
/** Checks whether the collection contains the given item. */
|
||||
contains(item, check = true) {
|
||||
if (check) {
|
||||
this.checkInitialized();
|
||||
}
|
||||
const entity = Reference.unwrapReference(item);
|
||||
return this.#items.has(entity);
|
||||
}
|
||||
/** Returns the number of items in the collection. Throws if the collection is not initialized. */
|
||||
count() {
|
||||
this.checkInitialized();
|
||||
return this.#items.size;
|
||||
}
|
||||
/** Returns true if the collection has no items. Throws if the collection is not initialized. */
|
||||
isEmpty() {
|
||||
this.checkInitialized();
|
||||
return this.count() === 0;
|
||||
}
|
||||
/** Returns whether this collection should be included in serialization based on its populated state. */
|
||||
shouldPopulate(populated) {
|
||||
if (!this.isInitialized(true)) {
|
||||
return false;
|
||||
}
|
||||
if (this.#populated != null) {
|
||||
return this.#populated;
|
||||
}
|
||||
return !!populated;
|
||||
}
|
||||
/** Marks the collection as populated or not for serialization purposes. */
|
||||
populated(populated = true) {
|
||||
this.#populated = populated;
|
||||
}
|
||||
/** Initializes the collection by loading its items from the database. */
|
||||
async init(options = {}) {
|
||||
if (this.#dirty) {
|
||||
const items = [...this.#items];
|
||||
this.#dirty = false;
|
||||
await this.init(options);
|
||||
items.forEach(i => this.add(i));
|
||||
return this;
|
||||
}
|
||||
const em = this.getEntityManager();
|
||||
options = { ...options, filters: QueryHelper.mergePropertyFilters(this.property.filters, options.filters) };
|
||||
if (options.dataloader ?? [DataloaderType.ALL, DataloaderType.COLLECTION].includes(em.config.getDataloaderType())) {
|
||||
const order = [...this.#items]; // copy order of references
|
||||
const orderBy = QueryHelper.mergeOrderBy(
|
||||
options.orderBy,
|
||||
this.property.orderBy,
|
||||
this.property.targetMeta?.orderBy,
|
||||
);
|
||||
const customOrder = orderBy.length > 0;
|
||||
const pivotTable = this.property.kind === ReferenceKind.MANY_TO_MANY && em.getPlatform().usesPivotTable();
|
||||
const loader = await em.getDataLoader(pivotTable ? 'm:n' : '1:m');
|
||||
const items = await loader.load([this, { ...options, orderBy }]);
|
||||
if (this.property.kind === ReferenceKind.MANY_TO_MANY) {
|
||||
this.#initialized = true;
|
||||
this.#dirty = false;
|
||||
if (!customOrder) {
|
||||
this.reorderItems(items, order);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
this.#items.clear();
|
||||
let i = 0;
|
||||
for (const item of items) {
|
||||
this.#items.add(item);
|
||||
this[i++] = item;
|
||||
}
|
||||
this.#initialized = true;
|
||||
this.#dirty = false;
|
||||
return this;
|
||||
}
|
||||
const populate = Array.isArray(options.populate)
|
||||
? options.populate.map(f => (f === '*' ? f : `${this.property.name}.${f}`))
|
||||
: [`${this.property.name}${options.ref ? ':ref' : ''}`];
|
||||
const schema = this.property.targetMeta.schema === '*' ? helper(this.owner).__schema : undefined;
|
||||
await em.populate(this.owner, populate, {
|
||||
refresh: true,
|
||||
...options,
|
||||
connectionType: options.connectionType,
|
||||
schema,
|
||||
where: { [this.property.name]: options.where },
|
||||
orderBy: { [this.property.name]: options.orderBy },
|
||||
});
|
||||
return this;
|
||||
}
|
||||
getEntityManager(items = [], required = true) {
|
||||
const wrapped = helper(this.owner);
|
||||
let em = wrapped.__em;
|
||||
if (!em) {
|
||||
for (const i of items) {
|
||||
if (i && helper(i).__em) {
|
||||
em = helper(i).__em;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!em && required) {
|
||||
throw ValidationError.entityNotManaged(this.owner);
|
||||
}
|
||||
return em;
|
||||
}
|
||||
createCondition(cond = {}) {
|
||||
if (this.property.kind === ReferenceKind.ONE_TO_MANY) {
|
||||
cond[this.property.mappedBy] = helper(this.owner).getPrimaryKey();
|
||||
} else {
|
||||
// MANY_TO_MANY
|
||||
this.createManyToManyCondition(cond);
|
||||
}
|
||||
return cond;
|
||||
}
|
||||
createManyToManyCondition(cond) {
|
||||
const dict = cond;
|
||||
if (this.property.owner || this.property.pivotTable) {
|
||||
// we know there is at least one item as it was checked in load method
|
||||
const pk = this.property.targetMeta.primaryKeys[0];
|
||||
dict[pk] = { $in: [] };
|
||||
this.#items.forEach(item => dict[pk].$in.push(helper(item).getPrimaryKey()));
|
||||
} else {
|
||||
dict[this.property.mappedBy] = helper(this.owner).getPrimaryKey();
|
||||
}
|
||||
}
|
||||
createLoadCountCondition(cond) {
|
||||
const wrapped = helper(this.owner);
|
||||
const val = wrapped.__meta.compositePK ? { $in: wrapped.__primaryKeys } : wrapped.getPrimaryKey();
|
||||
const dict = cond;
|
||||
if (this.property.kind === ReferenceKind.ONE_TO_MANY) {
|
||||
dict[this.property.mappedBy] = val;
|
||||
} else {
|
||||
const key = this.property.owner ? this.property.inversedBy : this.property.mappedBy;
|
||||
dict[key] = val;
|
||||
}
|
||||
return cond;
|
||||
}
|
||||
checkInitialized() {
|
||||
if (!this.isInitialized()) {
|
||||
throw new Error(
|
||||
`Collection<${this.property.type}> of entity ${helper(this.owner).__meta.name}[${helper(this.owner).getSerializedPrimaryKey()}] not initialized`,
|
||||
);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* re-orders items after searching with `$in` operator
|
||||
*/
|
||||
reorderItems(items, order) {
|
||||
if (this.property.kind === ReferenceKind.MANY_TO_MANY && this.property.owner) {
|
||||
items.sort((a, b) => order.indexOf(a) - order.indexOf(b));
|
||||
}
|
||||
}
|
||||
cancelOrphanRemoval(items) {
|
||||
const em = this.getEntityManager(items, false);
|
||||
if (!em) {
|
||||
return;
|
||||
}
|
||||
for (const item of items) {
|
||||
em.getUnitOfWork().cancelOrphanRemoval(item);
|
||||
}
|
||||
}
|
||||
validateModification(items) {
|
||||
if (this.#readonly) {
|
||||
throw ValidationError.cannotModifyReadonlyCollection(this.owner, this.property);
|
||||
}
|
||||
const check = item => {
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
if (!Utils.isEntity(item)) {
|
||||
throw ValidationError.notEntity(this.owner, this.property, item);
|
||||
}
|
||||
// currently we allow persisting to inverse sides only in SQL drivers
|
||||
if (this.property.pivotTable || !this.property.mappedBy) {
|
||||
return false;
|
||||
}
|
||||
if (helper(item).__initialized) {
|
||||
return false;
|
||||
}
|
||||
return !item[this.property.mappedBy] && this.property.kind === ReferenceKind.MANY_TO_MANY;
|
||||
};
|
||||
// throw if we are modifying inverse side of M:N collection when owning side is initialized (would be ignored when persisting)
|
||||
if (items.some(item => check(item))) {
|
||||
throw ValidationError.cannotModifyInverseCollection(this.owner, this.property);
|
||||
}
|
||||
}
|
||||
/** Converts all items in the collection to plain DTO objects. */
|
||||
toArray() {
|
||||
if (this.#items.size === 0) {
|
||||
return [];
|
||||
}
|
||||
return this.map(item => wrap(item).toJSON());
|
||||
}
|
||||
/** Returns the primary key values (or a specific field) of all items in the collection. */
|
||||
getIdentifiers(field) {
|
||||
const items = this.getItems();
|
||||
const targetMeta = this.property.targetMeta;
|
||||
if (items.length === 0) {
|
||||
return [];
|
||||
}
|
||||
field ??= targetMeta.compositePK
|
||||
? targetMeta.primaryKeys
|
||||
: (targetMeta.serializedPrimaryKey ?? targetMeta.primaryKeys[0]);
|
||||
const cb = (i, f) => {
|
||||
if (Utils.isEntity(i[f], true)) {
|
||||
return wrap(i[f], true).getPrimaryKey();
|
||||
}
|
||||
return i[f];
|
||||
};
|
||||
return items.map(i => {
|
||||
if (Array.isArray(field)) {
|
||||
return field.map(f => cb(i, f));
|
||||
}
|
||||
return cb(i, field);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
addWithoutPropagation(entity) {
|
||||
if (!this.contains(entity, false)) {
|
||||
this.incrementCount(1);
|
||||
this[this.#items.size] = entity;
|
||||
this.#items.add(entity);
|
||||
this.#dirty = true;
|
||||
}
|
||||
}
|
||||
/** Replaces all items in the collection with the given items. */
|
||||
set(items) {
|
||||
if (!this.#initialized) {
|
||||
this.#initialized = true;
|
||||
this.#snapshot = undefined;
|
||||
}
|
||||
if (this.compare(Utils.asArray(items).map(item => Reference.unwrapReference(item)))) {
|
||||
return;
|
||||
}
|
||||
this.remove(this.#items);
|
||||
this.add(items);
|
||||
}
|
||||
compare(items) {
|
||||
if (items.length !== this.#items.size) {
|
||||
return false;
|
||||
}
|
||||
let idx = 0;
|
||||
for (const item of this.#items) {
|
||||
if (item !== items[idx++]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
hydrate(items, forcePropagate, partial) {
|
||||
for (let i = 0; i < this.#items.size; i++) {
|
||||
delete this[i];
|
||||
}
|
||||
this.#initialized = true;
|
||||
this.#partial = !!partial;
|
||||
this.#items.clear();
|
||||
this.#count = 0;
|
||||
this.add(items);
|
||||
this.takeSnapshot(forcePropagate);
|
||||
}
|
||||
/**
|
||||
* Remove all items from the collection. Note that removing items from collection does not necessarily imply deleting the target entity,
|
||||
* it means we are disconnecting the relation - removing items from collection, not removing entities from database - `Collection.remove()`
|
||||
* is not the same as `em.remove()`. If we want to delete the entity by removing it from collection, we need to enable `orphanRemoval: true`,
|
||||
* which tells the ORM we don't want orphaned entities to exist, so we know those should be removed.
|
||||
*/
|
||||
removeAll() {
|
||||
if (!this.#initialized) {
|
||||
this.#initialized = true;
|
||||
this.#snapshot = undefined;
|
||||
}
|
||||
this.remove(this.#items);
|
||||
this.#dirty = true;
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
removeWithoutPropagation(entity) {
|
||||
if (!this.#items.delete(entity)) {
|
||||
return;
|
||||
}
|
||||
this.incrementCount(-1);
|
||||
delete this[this.#items.size];
|
||||
Object.assign(this, [...this.#items]);
|
||||
this.#dirty = true;
|
||||
}
|
||||
/**
|
||||
* Extracts a slice of the collection items starting at position start to end (exclusive) of the collection.
|
||||
* If end is null it returns all elements from start to the end of the collection.
|
||||
*/
|
||||
slice(start = 0, end) {
|
||||
this.checkInitialized();
|
||||
let index = 0;
|
||||
end ??= this.#items.size;
|
||||
const items = [];
|
||||
for (const item of this.#items) {
|
||||
if (index === end) {
|
||||
break;
|
||||
}
|
||||
if (index >= start && index < end) {
|
||||
items.push(item);
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return items;
|
||||
}
|
||||
/**
|
||||
* Tests for the existence of an element that satisfies the given predicate.
|
||||
*/
|
||||
exists(cb) {
|
||||
this.checkInitialized();
|
||||
for (const item of this.#items) {
|
||||
if (cb(item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Returns the first element of this collection that satisfies the predicate.
|
||||
*/
|
||||
find(cb) {
|
||||
this.checkInitialized();
|
||||
let index = 0;
|
||||
for (const item of this.#items) {
|
||||
if (cb(item, index++)) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Extracts a subset of the collection items.
|
||||
*/
|
||||
filter(cb) {
|
||||
this.checkInitialized();
|
||||
const items = [];
|
||||
let index = 0;
|
||||
for (const item of this.#items) {
|
||||
if (cb(item, index++)) {
|
||||
items.push(item);
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
/**
|
||||
* Maps the collection items based on your provided mapper function.
|
||||
*/
|
||||
map(mapper) {
|
||||
this.checkInitialized();
|
||||
const items = [];
|
||||
let index = 0;
|
||||
for (const item of this.#items) {
|
||||
items.push(mapper(item, index++));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
/**
|
||||
* Maps the collection items based on your provided mapper function to a single object.
|
||||
*/
|
||||
reduce(cb, initial = {}) {
|
||||
this.checkInitialized();
|
||||
let index = 0;
|
||||
for (const item of this.#items) {
|
||||
initial = cb(initial, item, index++);
|
||||
}
|
||||
return initial;
|
||||
}
|
||||
/**
|
||||
* Maps the collection items to a dictionary, indexed by the key you specify.
|
||||
* If there are more items with the same key, only the first one will be present.
|
||||
*/
|
||||
indexBy(key, valueKey) {
|
||||
return this.reduce((obj, item) => {
|
||||
obj[item[key]] ??= valueKey ? item[valueKey] : item;
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
/** Returns whether the collection has been initialized. Pass `fully = true` to also check that all items are initialized. */
|
||||
isInitialized(fully = false) {
|
||||
if (!this.#initialized || !fully) {
|
||||
return this.#initialized;
|
||||
}
|
||||
for (const item of this.#items) {
|
||||
if (!helper(item).__initialized) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
isDirty() {
|
||||
return this.#dirty;
|
||||
}
|
||||
/** Returns whether the collection was partially loaded (propagation is disabled for partial collections). */
|
||||
isPartial() {
|
||||
return this.#partial;
|
||||
}
|
||||
setDirty(dirty = true) {
|
||||
this.#dirty = dirty;
|
||||
}
|
||||
get length() {
|
||||
return this.count();
|
||||
}
|
||||
*[Symbol.iterator]() {
|
||||
for (const item of this.getItems()) {
|
||||
yield item;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
takeSnapshot(forcePropagate) {
|
||||
this.#snapshot = [...this.#items];
|
||||
this.#dirty = false;
|
||||
if (this.property.owner || forcePropagate) {
|
||||
this.#items.forEach(item => {
|
||||
this.propagate(item, 'takeSnapshot');
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
getSnapshot() {
|
||||
return this.#snapshot;
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
get property() {
|
||||
// cannot be typed to `EntityProperty<O, T>` as it causes issues in assignability of `Loaded` type
|
||||
if (!this.#property) {
|
||||
const meta = wrap(this.owner, true).__meta;
|
||||
/* v8 ignore next */
|
||||
if (!meta) {
|
||||
throw MetadataError.fromUnknownEntity(
|
||||
this.owner.constructor.name,
|
||||
'Collection.property getter, maybe you just forgot to initialize the ORM?',
|
||||
);
|
||||
}
|
||||
this.#property = meta.relations.find(prop => this.owner[prop.name] === this);
|
||||
}
|
||||
return this.#property;
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
set property(prop) {
|
||||
// cannot be typed to `EntityProperty<O, T>` as it causes issues in assignability of `Loaded` type
|
||||
this.#property = prop;
|
||||
}
|
||||
propagate(item, method) {
|
||||
if (this.property.owner && this.property.inversedBy) {
|
||||
this.propagateToInverseSide(item, method);
|
||||
} else if (!this.property.owner && this.property.mappedBy) {
|
||||
this.propagateToOwningSide(item, method);
|
||||
}
|
||||
}
|
||||
propagateToInverseSide(item, method) {
|
||||
const collection = item[this.property.inversedBy];
|
||||
if (this.shouldPropagateToCollection(collection, method)) {
|
||||
method = method === 'takeSnapshot' ? method : method + 'WithoutPropagation';
|
||||
collection[method](this.owner);
|
||||
}
|
||||
}
|
||||
propagateToOwningSide(item, method) {
|
||||
const mappedBy = this.property.mappedBy;
|
||||
const collection = item[mappedBy];
|
||||
if (this.property.kind === ReferenceKind.MANY_TO_MANY) {
|
||||
if (this.shouldPropagateToCollection(collection, method)) {
|
||||
collection[method](this.owner);
|
||||
}
|
||||
} else if (this.property.kind === ReferenceKind.ONE_TO_MANY && method !== 'takeSnapshot') {
|
||||
const prop2 = this.property.targetMeta.properties[mappedBy];
|
||||
const owner = prop2.mapToPk ? helper(this.owner).getPrimaryKey() : this.owner;
|
||||
const value = method === 'add' ? owner : null;
|
||||
if (this.property.orphanRemoval && method === 'remove') {
|
||||
// cache the PK before we propagate, as its value might be needed when flushing
|
||||
helper(item).__pk = helper(item).getPrimaryKey();
|
||||
}
|
||||
if (!prop2.nullable && prop2.deleteRule !== 'cascade' && method === 'remove') {
|
||||
if (!this.property.orphanRemoval) {
|
||||
throw ValidationError.cannotRemoveFromCollectionWithoutOrphanRemoval(this.owner, this.property);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// skip if already propagated
|
||||
if (Reference.unwrapReference(item[mappedBy]) !== value) {
|
||||
item[mappedBy] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
shouldPropagateToCollection(collection, method) {
|
||||
if (!collection) {
|
||||
return false;
|
||||
}
|
||||
switch (method) {
|
||||
case 'add':
|
||||
return !collection.contains(this.owner, false);
|
||||
case 'remove':
|
||||
return collection.isInitialized() && collection.contains(this.owner, false);
|
||||
case 'takeSnapshot':
|
||||
return collection.isDirty();
|
||||
}
|
||||
}
|
||||
incrementCount(value) {
|
||||
if (typeof this.#count === 'number' && this.#initialized) {
|
||||
this.#count += value;
|
||||
}
|
||||
}
|
||||
/** @ignore */
|
||||
[Symbol.for('nodejs.util.inspect.custom')](depth = 2) {
|
||||
const object = { ...this };
|
||||
delete object.owner;
|
||||
object.initialized = this.#initialized;
|
||||
object.dirty = this.#dirty;
|
||||
const ret = inspect(object, { depth });
|
||||
const name = `${this.constructor.name}<${this.property?.type ?? 'unknown'}>`;
|
||||
return ret === '[Object]' ? `[${name}]` : name + ' ' + ret;
|
||||
}
|
||||
}
|
||||
Object.defineProperties(Collection.prototype, {
|
||||
$: {
|
||||
get() {
|
||||
return this;
|
||||
},
|
||||
},
|
||||
get: {
|
||||
get() {
|
||||
return () => this;
|
||||
},
|
||||
},
|
||||
__collection: { value: true, enumerable: false, writable: false },
|
||||
});
|
||||
97
node_modules/@mikro-orm/core/entity/EntityAssigner.d.ts
generated
vendored
Normal file
97
node_modules/@mikro-orm/core/entity/EntityAssigner.d.ts
generated
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
import type { EntityManager } from '../EntityManager.js';
|
||||
import type { EntityData, EntityDTO, EntityProperty, FromEntityType, IsSubset, MergeSelected } from '../typings.js';
|
||||
/** Handles assigning data to entities, resolving relations, and propagating changes. */
|
||||
export declare class EntityAssigner {
|
||||
/** Assigns the given data to the entity, resolving relations and handling custom types. */
|
||||
static assign<
|
||||
Entity extends object,
|
||||
Naked extends FromEntityType<Entity> = FromEntityType<Entity>,
|
||||
Convert extends boolean = false,
|
||||
Data extends EntityData<Naked, Convert> | Partial<EntityDTO<Naked>> =
|
||||
| EntityData<Naked, Convert>
|
||||
| Partial<EntityDTO<Naked>>,
|
||||
>(
|
||||
entity: Entity,
|
||||
data: Data & IsSubset<EntityData<Naked, Convert>, Data>,
|
||||
options?: AssignOptions<Convert>,
|
||||
): MergeSelected<Entity, Naked, keyof Data & string>;
|
||||
private static assignProperty;
|
||||
/**
|
||||
* auto-wire 1:1 inverse side with owner as in no-sql drivers it can't be joined
|
||||
* also makes sure the link is bidirectional when creating new entities from nested structures
|
||||
* @internal
|
||||
*/
|
||||
static autoWireOneToOne<T extends object, O extends object>(prop: EntityProperty<O, T>, entity: O): void;
|
||||
private static validateEM;
|
||||
private static assignReference;
|
||||
private static assignCollection;
|
||||
private static assignEmbeddable;
|
||||
private static createCollectionItem;
|
||||
}
|
||||
export declare const assign: typeof EntityAssigner.assign;
|
||||
/** Options controlling how data is assigned to an entity via `assign()`. */
|
||||
export interface AssignOptions<Convert extends boolean> {
|
||||
/**
|
||||
* Allows disabling processing of nested relations. When disabled, an object payload in place of a relation always
|
||||
* results in an `INSERT` query. To assign a value of the relation, use the foreign key instead of an object.
|
||||
* Defaults to `true`.
|
||||
*/
|
||||
updateNestedEntities?: boolean;
|
||||
/**
|
||||
* When assigning to a relation property with object payload and `updateNestedEntities` enabled (default), you can
|
||||
* control how a payload without a primary key is handled. By default, it is considered as a new object, resulting
|
||||
* in an `INSERT` query. Use `updateByPrimaryKey: false` to allow assigning the data on an existing relation instead.
|
||||
* Defaults to `true`.
|
||||
*/
|
||||
updateByPrimaryKey?: boolean;
|
||||
/**
|
||||
* When you have some properties in the payload that are not represented by an entity property mapping, you can skip
|
||||
* such unknown properties via `onlyProperties: true`. Defaults to `false`.
|
||||
*/
|
||||
onlyProperties?: boolean;
|
||||
/**
|
||||
* With `onlyOwnProperties` enabled, inverse sides of to-many relations are skipped, and payloads of other relations are converted
|
||||
* to foreign keys. Defaults to `false`.
|
||||
*/
|
||||
onlyOwnProperties?: boolean;
|
||||
/**
|
||||
* With `ignoreUndefined` enabled, `undefined` properties passed in the payload are skipped. Defaults to `false`.
|
||||
*/
|
||||
ignoreUndefined?: boolean;
|
||||
/**
|
||||
* `assign` excepts runtime values for properties using custom types. To be able to assign raw database values, you
|
||||
* can enable the `convertCustomTypes` option. Defaults to `false`.
|
||||
*/
|
||||
convertCustomTypes?: Convert;
|
||||
/**
|
||||
* When assigning to a JSON property, the value is replaced. Use `mergeObjectProperties: true` to enable deep merging
|
||||
* of the payload with the existing value. Defaults to `false`.
|
||||
*/
|
||||
mergeObjectProperties?: boolean;
|
||||
/**
|
||||
* When assigning to an embedded property, the values are deeply merged with the existing data.
|
||||
* Use `mergeEmbeddedProperties: false` to replace them instead. Defaults to `true`.
|
||||
*/
|
||||
mergeEmbeddedProperties?: boolean;
|
||||
/**
|
||||
* When assigning to a relation property, if the value is a POJO and `updateByPrimaryKey` is enabled, we check if
|
||||
* the target exists in the identity map based on its primary key and call `assign` on it recursively. If there is
|
||||
* no primary key provided, or the entity is not present in the context, such an entity is considered as new
|
||||
* (resulting in `INSERT` query), created via `em.create()`. You can use `merge: true` to use `em.merge()` instead,
|
||||
* which means there won't be any query used for persisting the relation. Defaults to `false`.
|
||||
*/
|
||||
merge?: boolean;
|
||||
/**
|
||||
* When assigning to a to-many relation properties (`Collection`) with `updateNestedEntities` and `updateByPrimaryKey`
|
||||
* enabled (default), you can use this option to override the relation schema. This is used only when trying to find
|
||||
* the entity reference in the current context. If it is not found, we create the relation entity using the target
|
||||
* entity schema. The value is automatically inferred from the target entity.
|
||||
*/
|
||||
schema?: string;
|
||||
/**
|
||||
* When using the static `assign()` helper, you can pass the EntityManager instance explicitly via the `em` option.
|
||||
* This is only needed when you try to assign a relation property. The value is automatically inferred from the target
|
||||
* entity when it is managed, or when you use `em.assign()` instead.
|
||||
*/
|
||||
em?: EntityManager;
|
||||
}
|
||||
251
node_modules/@mikro-orm/core/entity/EntityAssigner.js
generated
vendored
Normal file
251
node_modules/@mikro-orm/core/entity/EntityAssigner.js
generated
vendored
Normal file
@@ -0,0 +1,251 @@
|
||||
import { Collection } from './Collection.js';
|
||||
import { Utils } from '../utils/Utils.js';
|
||||
import { Reference } from './Reference.js';
|
||||
import { ReferenceKind, SCALAR_TYPES } from '../enums.js';
|
||||
import { validateProperty } from './validators.js';
|
||||
import { helper, wrap } from './wrap.js';
|
||||
import { EntityHelper } from './EntityHelper.js';
|
||||
import { ValidationError } from '../errors.js';
|
||||
/** Handles assigning data to entities, resolving relations, and propagating changes. */
|
||||
export class EntityAssigner {
|
||||
/** Assigns the given data to the entity, resolving relations and handling custom types. */
|
||||
static assign(entity, data, options = {}) {
|
||||
let opts = options;
|
||||
if (opts.visited?.has(entity)) {
|
||||
return entity;
|
||||
}
|
||||
EntityHelper.ensurePropagation(entity);
|
||||
opts.visited ??= new Set();
|
||||
opts.visited.add(entity);
|
||||
const wrapped = helper(entity);
|
||||
opts = {
|
||||
...wrapped.__config.get('assign'),
|
||||
schema: wrapped.__schema,
|
||||
...opts, // allow overriding the defaults
|
||||
};
|
||||
const meta = wrapped.__meta;
|
||||
const props = meta.properties;
|
||||
Object.keys(data).forEach(prop => {
|
||||
return EntityAssigner.assignProperty(entity, prop, props, data, {
|
||||
...opts,
|
||||
em: opts.em || wrapped.__em,
|
||||
platform: wrapped.__platform,
|
||||
});
|
||||
});
|
||||
return entity;
|
||||
}
|
||||
static assignProperty(entity, propName, props, data, options) {
|
||||
let value = data[propName];
|
||||
const onlyProperties = options.onlyProperties && !(propName in props);
|
||||
const ignoreUndefined = options.ignoreUndefined === true && value === undefined;
|
||||
if (onlyProperties || ignoreUndefined) {
|
||||
return;
|
||||
}
|
||||
const prop = { ...props[propName], name: propName };
|
||||
if (prop && options.onlyOwnProperties) {
|
||||
if ([ReferenceKind.ONE_TO_MANY].includes(prop.kind)) {
|
||||
return;
|
||||
}
|
||||
if ([ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
|
||||
if (!prop.owner) {
|
||||
return;
|
||||
} else if (value?.map) {
|
||||
value = value.map(v => Utils.extractPK(v, prop.targetMeta));
|
||||
}
|
||||
}
|
||||
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
||||
value = Utils.extractPK(value, prop.targetMeta);
|
||||
}
|
||||
}
|
||||
if (propName in props && !prop.nullable && value == null) {
|
||||
throw new Error(
|
||||
`You must pass a non-${value} value to the property ${propName} of entity ${entity.constructor.name}.`,
|
||||
);
|
||||
}
|
||||
// create collection instance if its missing so old items can be deleted with orphan removal
|
||||
if ([ReferenceKind.MANY_TO_MANY, ReferenceKind.ONE_TO_MANY].includes(prop?.kind) && entity[prop.name] == null) {
|
||||
entity[prop.name] = Collection.create(entity, prop.name, undefined, helper(entity).isInitialized());
|
||||
}
|
||||
if (prop && Utils.isCollection(entity[prop.name])) {
|
||||
return EntityAssigner.assignCollection(entity, entity[prop.name], value, prop, options.em, options);
|
||||
}
|
||||
const customType = prop?.customType;
|
||||
if (options.convertCustomTypes && customType && prop.kind === ReferenceKind.SCALAR && !Utils.isEntity(data)) {
|
||||
value = customType.convertToJSValue(value, options.platform);
|
||||
}
|
||||
if ([ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop?.kind) && value != null) {
|
||||
if (
|
||||
options.updateNestedEntities &&
|
||||
Object.hasOwn(entity, propName) &&
|
||||
Utils.isEntity(entity[propName], true) &&
|
||||
Utils.isPlainObject(value)
|
||||
) {
|
||||
const unwrappedEntity = Reference.unwrapReference(entity[propName]);
|
||||
const wrapped = helper(unwrappedEntity);
|
||||
if (options.updateByPrimaryKey) {
|
||||
const pk = Utils.extractPK(value, prop.targetMeta);
|
||||
if (pk) {
|
||||
const ref = options.em.getReference(prop.targetMeta.class, pk, options);
|
||||
// if the PK differs, we want to change the target entity, not update it
|
||||
const wrappedChild = helper(ref);
|
||||
const sameTarget = wrappedChild.getSerializedPrimaryKey() === wrapped.getSerializedPrimaryKey();
|
||||
if (wrappedChild.__managed && wrappedChild.isInitialized() && sameTarget) {
|
||||
return EntityAssigner.assign(ref, value, options);
|
||||
}
|
||||
}
|
||||
return EntityAssigner.assignReference(entity, value, prop, options.em, options);
|
||||
}
|
||||
if (wrapped.__managed && wrap(unwrappedEntity).isInitialized()) {
|
||||
return EntityAssigner.assign(unwrappedEntity, value, options);
|
||||
}
|
||||
}
|
||||
return EntityAssigner.assignReference(entity, value, prop, options.em, options);
|
||||
}
|
||||
if (prop.kind === ReferenceKind.SCALAR && SCALAR_TYPES.has(prop.runtimeType) && (prop.setter || !prop.getter)) {
|
||||
validateProperty(prop, value, entity);
|
||||
return (entity[prop.name] = value);
|
||||
}
|
||||
if (prop.kind === ReferenceKind.EMBEDDED && EntityAssigner.validateEM(options.em)) {
|
||||
return EntityAssigner.assignEmbeddable(entity, value, prop, options.em, options);
|
||||
}
|
||||
if (options.mergeObjectProperties && Utils.isPlainObject(entity[propName]) && Utils.isPlainObject(value)) {
|
||||
entity[propName] ??= {};
|
||||
entity[propName] = Utils.merge({}, entity[propName], value);
|
||||
} else if (!prop || prop.setter || !prop.getter) {
|
||||
entity[propName] = value;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* auto-wire 1:1 inverse side with owner as in no-sql drivers it can't be joined
|
||||
* also makes sure the link is bidirectional when creating new entities from nested structures
|
||||
* @internal
|
||||
*/
|
||||
static autoWireOneToOne(prop, entity) {
|
||||
const ref = entity[prop.name];
|
||||
if (prop.kind !== ReferenceKind.ONE_TO_ONE || !Utils.isEntity(ref)) {
|
||||
return;
|
||||
}
|
||||
const meta2 = helper(ref).__meta;
|
||||
const prop2 = meta2.properties[prop.inversedBy || prop.mappedBy];
|
||||
/* v8 ignore next */
|
||||
if (prop2 && !ref[prop2.name]) {
|
||||
if (Reference.isReference(ref)) {
|
||||
ref.unwrap()[prop2.name] = Reference.wrapReference(entity, prop2);
|
||||
} else {
|
||||
ref[prop2.name] = Reference.wrapReference(entity, prop2);
|
||||
}
|
||||
}
|
||||
}
|
||||
static validateEM(em) {
|
||||
if (!em) {
|
||||
throw new Error(
|
||||
`To use assign() on not managed entities, explicitly provide EM instance: wrap(entity).assign(data, { em: orm.em })`,
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static assignReference(entity, value, prop, em, options) {
|
||||
if (Utils.isEntity(value, true)) {
|
||||
entity[prop.name] = Reference.wrapReference(value, prop);
|
||||
} else if (Utils.isPrimaryKey(value, true) && EntityAssigner.validateEM(em)) {
|
||||
entity[prop.name] = prop.mapToPk
|
||||
? value
|
||||
: Reference.wrapReference(em.getReference(prop.targetMeta.class, value, options), prop);
|
||||
} else if (Utils.isPlainObject(value) && options.merge && EntityAssigner.validateEM(em)) {
|
||||
entity[prop.name] = Reference.wrapReference(em.merge(prop.targetMeta.class, value, options), prop);
|
||||
} else if (Utils.isPlainObject(value) && EntityAssigner.validateEM(em)) {
|
||||
entity[prop.name] = Reference.wrapReference(em.create(prop.targetMeta.class, value, options), prop);
|
||||
} else {
|
||||
const name = entity.constructor.name;
|
||||
throw new Error(
|
||||
`Invalid reference value provided for '${name}.${prop.name}' in ${name}.assign(): ${JSON.stringify(value)}`,
|
||||
);
|
||||
}
|
||||
EntityAssigner.autoWireOneToOne(prop, entity);
|
||||
}
|
||||
static assignCollection(entity, collection, value, prop, em, options) {
|
||||
const invalid = [];
|
||||
const items = Utils.asArray(value).map((item, idx) => {
|
||||
// try to propagate missing owning side reference to the payload first
|
||||
const prop2 = prop.targetMeta?.properties[prop.mappedBy];
|
||||
if (Utils.isPlainObject(item) && prop2 && item[prop2.name] == null) {
|
||||
item = { ...item, [prop2.name]: Reference.wrapReference(entity, prop2) };
|
||||
}
|
||||
if (options.updateNestedEntities && options.updateByPrimaryKey && Utils.isPlainObject(item)) {
|
||||
const pk = Utils.extractPK(item, prop.targetMeta);
|
||||
if (pk && EntityAssigner.validateEM(em)) {
|
||||
const ref = em.getUnitOfWork().getById(prop.targetMeta.class, pk, options.schema);
|
||||
if (ref) {
|
||||
return EntityAssigner.assign(ref, item, options);
|
||||
}
|
||||
}
|
||||
return this.createCollectionItem(item, em, prop, invalid, options);
|
||||
}
|
||||
/* v8 ignore next */
|
||||
if (
|
||||
options.updateNestedEntities &&
|
||||
!options.updateByPrimaryKey &&
|
||||
collection[idx] &&
|
||||
helper(collection[idx])?.isInitialized()
|
||||
) {
|
||||
return EntityAssigner.assign(collection[idx], item, options);
|
||||
}
|
||||
return this.createCollectionItem(item, em, prop, invalid, options);
|
||||
});
|
||||
if (invalid.length > 0) {
|
||||
const name = entity.constructor.name;
|
||||
throw ValidationError.invalidCollectionValues(name, prop.name, invalid);
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
collection.set(items);
|
||||
} else {
|
||||
// append to the collection in case of assigning a single value instead of array
|
||||
collection.add(items);
|
||||
}
|
||||
}
|
||||
static assignEmbeddable(entity, value, prop, em, options) {
|
||||
const propName = prop.embedded ? prop.embedded[1] : prop.name;
|
||||
if (value == null) {
|
||||
entity[propName] = value;
|
||||
return;
|
||||
}
|
||||
// if the value is not an array, we just push, otherwise we replace the array
|
||||
if (prop.array && (Array.isArray(value) || entity[propName] == null)) {
|
||||
entity[propName] = [];
|
||||
}
|
||||
if (prop.array) {
|
||||
return Utils.asArray(value).forEach(item => {
|
||||
const tmp = {};
|
||||
this.assignEmbeddable(tmp, item, { ...prop, array: false }, em, options);
|
||||
entity[propName].push(...Object.values(tmp));
|
||||
});
|
||||
}
|
||||
const create = () =>
|
||||
EntityAssigner.validateEM(em) &&
|
||||
em.getEntityFactory().createEmbeddable(prop.targetMeta.class, value, {
|
||||
convertCustomTypes: options.convertCustomTypes,
|
||||
newEntity: options.mergeEmbeddedProperties ? !('propName' in entity) : true,
|
||||
});
|
||||
entity[propName] = options.mergeEmbeddedProperties ? entity[propName] || create() : create();
|
||||
Object.keys(value).forEach(key => {
|
||||
EntityAssigner.assignProperty(entity[propName], key, prop.embeddedProps, value, options);
|
||||
});
|
||||
}
|
||||
static createCollectionItem(item, em, prop, invalid, options) {
|
||||
if (Utils.isEntity(item)) {
|
||||
return item;
|
||||
}
|
||||
if (Utils.isPrimaryKey(item) && EntityAssigner.validateEM(em)) {
|
||||
return em.getReference(prop.targetMeta.class, item, options);
|
||||
}
|
||||
if (Utils.isPlainObject(item) && options.merge && EntityAssigner.validateEM(em)) {
|
||||
return em.merge(prop.targetMeta.class, item, options);
|
||||
}
|
||||
if (Utils.isPlainObject(item) && EntityAssigner.validateEM(em)) {
|
||||
return em.create(prop.targetMeta.class, item, options);
|
||||
}
|
||||
invalid.push(item);
|
||||
return item;
|
||||
}
|
||||
}
|
||||
export const assign = EntityAssigner.assign;
|
||||
75
node_modules/@mikro-orm/core/entity/EntityFactory.d.ts
generated
vendored
Normal file
75
node_modules/@mikro-orm/core/entity/EntityFactory.d.ts
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
import type { EntityData, EntityMetadata, EntityName, New, Primary } from '../typings.js';
|
||||
import type { EntityManager } from '../EntityManager.js';
|
||||
import type { EntityComparator } from '../utils/EntityComparator.js';
|
||||
/** @internal Options for creating and merging entities via the EntityFactory. */
|
||||
export interface FactoryOptions {
|
||||
/** Whether the entity should be marked as initialized. */
|
||||
initialized?: boolean;
|
||||
/** Whether the entity is being newly created (uses constructor). */
|
||||
newEntity?: boolean;
|
||||
/**
|
||||
* Property `onCreate` hooks are normally executed during `flush` operation.
|
||||
* With this option, they will be processed early inside `em.create()` method.
|
||||
*/
|
||||
processOnCreateHooksEarly?: boolean;
|
||||
/** Whether to merge the entity into the identity map. */
|
||||
merge?: boolean;
|
||||
/** Whether to refresh an already loaded entity with new data. */
|
||||
refresh?: boolean;
|
||||
/** Whether to convert custom types during hydration. */
|
||||
convertCustomTypes?: boolean;
|
||||
/** Whether to recompute the entity snapshot after creation. */
|
||||
recomputeSnapshot?: boolean;
|
||||
/** Schema from FindOptions, overrides default schema. */
|
||||
schema?: string;
|
||||
/** Parent entity schema for nested entity creation. */
|
||||
parentSchema?: string;
|
||||
/** Whether to normalize accessors to the correct property names (normally handled via result mapper). */
|
||||
normalizeAccessors?: boolean;
|
||||
/**
|
||||
* Property name to use for identity map lookup instead of the primary key.
|
||||
* This is useful for creating references by unique non-PK properties.
|
||||
*/
|
||||
key?: string;
|
||||
}
|
||||
/** @internal Factory responsible for creating, merging, and hydrating entity instances. */
|
||||
export declare class EntityFactory {
|
||||
#private;
|
||||
constructor(em: EntityManager);
|
||||
/** Creates a new entity instance or returns an existing one from the identity map, hydrating it with the provided data. */
|
||||
create<T extends object, P extends string = string>(
|
||||
entityName: EntityName<T>,
|
||||
data: EntityData<T>,
|
||||
options?: FactoryOptions,
|
||||
): New<T, P>;
|
||||
/** Merges new data into an existing entity, preserving user-modified properties. */
|
||||
mergeData<T extends object>(meta: EntityMetadata<T>, entity: T, data: EntityData<T>, options?: FactoryOptions): void;
|
||||
/** Creates or retrieves an uninitialized entity reference by its primary key or alternate key. */
|
||||
createReference<T extends object>(
|
||||
entityName: EntityName<T>,
|
||||
id: Primary<T> | Primary<T>[] | Record<string, Primary<T>>,
|
||||
options?: Pick<FactoryOptions, 'merge' | 'convertCustomTypes' | 'schema' | 'key'>,
|
||||
): T;
|
||||
/** Creates an embeddable entity instance from the provided data. */
|
||||
createEmbeddable<T extends object>(
|
||||
entityName: EntityName<T>,
|
||||
data: EntityData<T>,
|
||||
options?: Pick<FactoryOptions, 'newEntity' | 'convertCustomTypes'>,
|
||||
): T;
|
||||
/** Returns the EntityComparator instance used for diffing entities. */
|
||||
getComparator(): EntityComparator;
|
||||
private createEntity;
|
||||
private assignDefaultValues;
|
||||
private hydrate;
|
||||
private findEntity;
|
||||
private processDiscriminatorColumn;
|
||||
/**
|
||||
* denormalize PK to value required by driver (e.g. ObjectId)
|
||||
*/
|
||||
private denormalizePrimaryKey;
|
||||
/**
|
||||
* returns parameters for entity constructor, creating references from plain ids
|
||||
*/
|
||||
private extractConstructorParams;
|
||||
private get unitOfWork();
|
||||
}
|
||||
458
node_modules/@mikro-orm/core/entity/EntityFactory.js
generated
vendored
Normal file
458
node_modules/@mikro-orm/core/entity/EntityFactory.js
generated
vendored
Normal file
@@ -0,0 +1,458 @@
|
||||
import { Utils } from '../utils/Utils.js';
|
||||
import { QueryHelper } from '../utils/QueryHelper.js';
|
||||
import { EventType, ReferenceKind } from '../enums.js';
|
||||
import { Reference } from './Reference.js';
|
||||
import { helper } from './wrap.js';
|
||||
import { EntityHelper } from './EntityHelper.js';
|
||||
import { JsonType } from '../types/JsonType.js';
|
||||
import { isRaw } from '../utils/RawQueryFragment.js';
|
||||
/** @internal Factory responsible for creating, merging, and hydrating entity instances. */
|
||||
export class EntityFactory {
|
||||
#driver;
|
||||
#platform;
|
||||
#config;
|
||||
#metadata;
|
||||
#hydrator;
|
||||
#eventManager;
|
||||
#comparator;
|
||||
#em;
|
||||
constructor(em) {
|
||||
this.#em = em;
|
||||
this.#driver = this.#em.getDriver();
|
||||
this.#platform = this.#driver.getPlatform();
|
||||
this.#config = this.#em.config;
|
||||
this.#metadata = this.#em.getMetadata();
|
||||
this.#hydrator = this.#config.getHydrator(this.#metadata);
|
||||
this.#eventManager = this.#em.getEventManager();
|
||||
this.#comparator = this.#em.getComparator();
|
||||
}
|
||||
/** Creates a new entity instance or returns an existing one from the identity map, hydrating it with the provided data. */
|
||||
create(entityName, data, options = {}) {
|
||||
data = Reference.unwrapReference(data);
|
||||
options.initialized ??= true;
|
||||
if (data.__entity) {
|
||||
return data;
|
||||
}
|
||||
const meta = this.#metadata.get(entityName);
|
||||
if (meta.virtual) {
|
||||
data = { ...data };
|
||||
const entity = this.createEntity(data, meta, options);
|
||||
this.hydrate(entity, meta, data, options);
|
||||
return entity;
|
||||
}
|
||||
if (meta.serializedPrimaryKey) {
|
||||
this.denormalizePrimaryKey(meta, data);
|
||||
}
|
||||
const meta2 = this.processDiscriminatorColumn(meta, data);
|
||||
const exists = this.findEntity(data, meta2, options);
|
||||
let wrapped = exists && helper(exists);
|
||||
if (wrapped && !options.refresh) {
|
||||
wrapped.__processing = true;
|
||||
Utils.dropUndefinedProperties(data);
|
||||
this.mergeData(meta2, exists, data, options);
|
||||
wrapped.__processing = false;
|
||||
if (wrapped.isInitialized()) {
|
||||
return exists;
|
||||
}
|
||||
}
|
||||
data = { ...data };
|
||||
const entity = exists ?? this.createEntity(data, meta2, options);
|
||||
wrapped = helper(entity);
|
||||
wrapped.__processing = true;
|
||||
wrapped.__initialized = options.initialized;
|
||||
if (options.newEntity || meta.forceConstructor || meta.virtual) {
|
||||
const tmp = { ...data };
|
||||
meta.constructorParams?.forEach(prop => delete tmp[prop]);
|
||||
this.hydrate(entity, meta2, tmp, options);
|
||||
// since we now process only a copy of the `data` via hydrator, but later we register the state with the full snapshot,
|
||||
// we need to go through all props with custom types that have `ensureComparable: true` and ensure they are comparable
|
||||
// even if they are not part of constructor parameters (as this is otherwise normalized during hydration, here only in `tmp`)
|
||||
if (options.convertCustomTypes) {
|
||||
for (const prop of meta.props) {
|
||||
if (prop.customType?.ensureComparable(meta, prop) && data[prop.name]) {
|
||||
if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind)) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
[ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
|
||||
Utils.isPlainObject(data[prop.name])
|
||||
) {
|
||||
data[prop.name] = Utils.getPrimaryKeyValues(data[prop.name], prop.targetMeta, true);
|
||||
}
|
||||
if (prop.customType instanceof JsonType && this.#platform.convertsJsonAutomatically()) {
|
||||
data[prop.name] = prop.customType.convertToDatabaseValue(data[prop.name], this.#platform, {
|
||||
key: prop.name,
|
||||
mode: 'hydration',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.hydrate(entity, meta2, data, options);
|
||||
}
|
||||
if (exists && meta.root.inheritanceType && !(entity instanceof meta2.class)) {
|
||||
Object.setPrototypeOf(entity, meta2.prototype);
|
||||
}
|
||||
if (options.merge && wrapped.hasPrimaryKey()) {
|
||||
this.unitOfWork.register(entity, data, {
|
||||
// Always refresh to ensure the payload is in correct shape for joined strategy. When loading nested relations,
|
||||
// they will be created early without `Type.ensureComparable` being properly handled, resulting in extra updates.
|
||||
refresh: options.initialized,
|
||||
newEntity: options.newEntity,
|
||||
loaded: options.initialized,
|
||||
});
|
||||
if (options.recomputeSnapshot) {
|
||||
wrapped.__originalEntityData = this.#comparator.prepareEntity(entity);
|
||||
}
|
||||
}
|
||||
if (this.#eventManager.hasListeners(EventType.onInit, meta2)) {
|
||||
this.#eventManager.dispatchEvent(EventType.onInit, { entity, meta: meta2, em: this.#em });
|
||||
}
|
||||
wrapped.__processing = false;
|
||||
return entity;
|
||||
}
|
||||
/** Merges new data into an existing entity, preserving user-modified properties. */
|
||||
mergeData(meta, entity, data, options = {}) {
|
||||
// merge unchanged properties automatically
|
||||
data = QueryHelper.processParams(data);
|
||||
const existsData = this.#comparator.prepareEntity(entity);
|
||||
const originalEntityData = helper(entity).__originalEntityData ?? {};
|
||||
const diff = this.#comparator.diffEntities(meta.class, originalEntityData, existsData);
|
||||
// version properties are not part of entity snapshots
|
||||
if (
|
||||
meta.versionProperty &&
|
||||
data[meta.versionProperty] &&
|
||||
data[meta.versionProperty] !== originalEntityData[meta.versionProperty]
|
||||
) {
|
||||
diff[meta.versionProperty] = data[meta.versionProperty];
|
||||
}
|
||||
const diff2 = this.#comparator.diffEntities(meta.class, existsData, data, { includeInverseSides: true });
|
||||
// do not override values changed by user
|
||||
Utils.keys(diff).forEach(key => delete diff2[key]);
|
||||
Utils.keys(diff2)
|
||||
.filter(key => {
|
||||
// ignore null values if there is already present non-null value
|
||||
if (existsData[key] != null) {
|
||||
return diff2[key] == null;
|
||||
}
|
||||
return diff2[key] === undefined;
|
||||
})
|
||||
.forEach(key => delete diff2[key]);
|
||||
// but always add collection properties and formulas if they are part of the `data`
|
||||
Utils.keys(data)
|
||||
.filter(
|
||||
key =>
|
||||
meta.properties[key]?.formula ||
|
||||
[ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(meta.properties[key]?.kind),
|
||||
)
|
||||
.forEach(key => (diff2[key] = data[key]));
|
||||
// rehydrated with the new values, skip those changed by user
|
||||
// use full hydration if the entity is already initialized, even if the caller used `initialized: false`
|
||||
// (e.g. from createReference), otherwise scalar properties in diff2 won't be applied
|
||||
const initialized = options.initialized || helper(entity).__initialized;
|
||||
this.hydrate(entity, meta, diff2, initialized ? { ...options, initialized } : options);
|
||||
// we need to update the entity data only with keys that were not present before
|
||||
const nullVal = this.#config.get('forceUndefined') ? undefined : null;
|
||||
Utils.keys(diff2).forEach(key => {
|
||||
const prop = meta.properties[key];
|
||||
if (
|
||||
[ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
|
||||
Utils.isPlainObject(data[prop.name])
|
||||
) {
|
||||
// oxfmt-ignore
|
||||
diff2[key] = entity[prop.name] ? helper(entity[prop.name]).getPrimaryKey(options.convertCustomTypes) : null;
|
||||
}
|
||||
if (
|
||||
!options.convertCustomTypes &&
|
||||
[ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE, ReferenceKind.SCALAR].includes(prop.kind) &&
|
||||
prop.customType?.ensureComparable(meta, prop) &&
|
||||
diff2[key] != null
|
||||
) {
|
||||
const converted = prop.customType.convertToJSValue(diff2[key], this.#platform, { force: true });
|
||||
diff2[key] = prop.customType.convertToDatabaseValue(converted, this.#platform, { fromQuery: true });
|
||||
}
|
||||
originalEntityData[key] = diff2[key] === null ? nullVal : diff2[key];
|
||||
helper(entity).__loadedProperties.add(key);
|
||||
});
|
||||
// in case of joined loading strategy, we need to cascade the merging to possibly loaded relations manually
|
||||
meta.relations.forEach(prop => {
|
||||
if (
|
||||
[ReferenceKind.MANY_TO_MANY, ReferenceKind.ONE_TO_MANY].includes(prop.kind) &&
|
||||
Array.isArray(data[prop.name])
|
||||
) {
|
||||
// instead of trying to match the collection items (which could easily fail if the collection was loaded with different ordering),
|
||||
// we just create the entity from scratch, which will automatically pick the right one from the identity map and call `mergeData` on it
|
||||
data[prop.name]
|
||||
.filter(child => Utils.isPlainObject(child)) // objects with prototype can be PKs (e.g. `ObjectId`)
|
||||
.forEach(child => this.create(prop.targetMeta.class, child, options)); // we can ignore the value, we just care about the `mergeData` call
|
||||
return;
|
||||
}
|
||||
if (
|
||||
[ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
|
||||
Utils.isPlainObject(data[prop.name]) &&
|
||||
entity[prop.name] &&
|
||||
helper(entity[prop.name]).__initialized
|
||||
) {
|
||||
this.create(prop.targetMeta.class, data[prop.name], options); // we can ignore the value, we just care about the `mergeData` call
|
||||
}
|
||||
});
|
||||
this.unitOfWork.normalizeEntityData(meta, originalEntityData);
|
||||
}
|
||||
/** Creates or retrieves an uninitialized entity reference by its primary key or alternate key. */
|
||||
createReference(entityName, id, options = {}) {
|
||||
options.convertCustomTypes ??= true;
|
||||
const meta = this.#metadata.get(entityName);
|
||||
const schema = this.#driver.getSchemaName(meta, options);
|
||||
// Handle alternate key lookup
|
||||
if (options.key) {
|
||||
const value = '' + (Array.isArray(id) ? id[0] : Utils.isPlainObject(id) ? id[options.key] : id);
|
||||
const exists = this.unitOfWork.getByKey(entityName, options.key, value, schema, options.convertCustomTypes);
|
||||
if (exists) {
|
||||
return exists;
|
||||
}
|
||||
// Create entity stub - storeByKey will set the alternate key property and store in identity map
|
||||
const entity = this.create(entityName, {}, { ...options, initialized: false });
|
||||
this.unitOfWork.storeByKey(entity, options.key, value, schema, options.convertCustomTypes);
|
||||
return entity;
|
||||
}
|
||||
if (meta.simplePK) {
|
||||
const exists = this.unitOfWork.getById(entityName, id, schema);
|
||||
if (exists) {
|
||||
return exists;
|
||||
}
|
||||
const data = Utils.isPlainObject(id) ? id : { [meta.primaryKeys[0]]: Array.isArray(id) ? id[0] : id };
|
||||
return this.create(entityName, data, { ...options, initialized: false });
|
||||
}
|
||||
if (Array.isArray(id)) {
|
||||
id = Utils.getPrimaryKeyCondFromArray(id, meta);
|
||||
}
|
||||
const pks = Utils.getOrderedPrimaryKeys(id, meta, this.#platform);
|
||||
const exists = this.unitOfWork.getById(entityName, pks, schema, options.convertCustomTypes);
|
||||
if (exists) {
|
||||
return exists;
|
||||
}
|
||||
if (Utils.isPrimaryKey(id)) {
|
||||
id = { [meta.primaryKeys[0]]: id };
|
||||
}
|
||||
return this.create(entityName, id, { ...options, initialized: false });
|
||||
}
|
||||
/** Creates an embeddable entity instance from the provided data. */
|
||||
createEmbeddable(entityName, data, options = {}) {
|
||||
data = { ...data };
|
||||
const meta = this.#metadata.get(entityName);
|
||||
const meta2 = this.processDiscriminatorColumn(meta, data);
|
||||
return this.createEntity(data, meta2, options);
|
||||
}
|
||||
/** Returns the EntityComparator instance used for diffing entities. */
|
||||
getComparator() {
|
||||
return this.#comparator;
|
||||
}
|
||||
createEntity(data, meta, options) {
|
||||
const schema = this.#driver.getSchemaName(meta, options);
|
||||
if (options.newEntity || meta.forceConstructor || meta.virtual) {
|
||||
if (meta.polymorphs) {
|
||||
throw new Error(`Cannot create entity ${meta.className}, class prototype is unknown`);
|
||||
}
|
||||
const params = this.extractConstructorParams(meta, data, options);
|
||||
const Entity = meta.class;
|
||||
// creates new instance via constructor as this is the new entity
|
||||
const entity = new Entity(...params);
|
||||
// creating managed entity instance when `forceEntityConstructor` is enabled,
|
||||
// we need to wipe all the values as they would cause update queries on next flush
|
||||
if (!options.newEntity && (meta.forceConstructor || this.#config.get('forceEntityConstructor'))) {
|
||||
meta.props
|
||||
.filter(prop => prop.persist !== false && !prop.primary && data[prop.name] === undefined)
|
||||
.forEach(prop => delete entity[prop.name]);
|
||||
}
|
||||
if (meta.virtual) {
|
||||
return entity;
|
||||
}
|
||||
helper(entity).__schema = schema;
|
||||
if (options.initialized) {
|
||||
EntityHelper.ensurePropagation(entity);
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
// creates new entity instance, bypassing constructor call as its already persisted entity
|
||||
const entity = Object.create(meta.class.prototype);
|
||||
helper(entity).__managed = true;
|
||||
helper(entity).__processing = !meta.embeddable && !meta.virtual;
|
||||
helper(entity).__schema = schema;
|
||||
if (options.merge && !options.newEntity) {
|
||||
this.#hydrator.hydrateReference(
|
||||
entity,
|
||||
meta,
|
||||
data,
|
||||
this,
|
||||
options.convertCustomTypes,
|
||||
options.schema,
|
||||
options.parentSchema,
|
||||
);
|
||||
this.unitOfWork.register(entity);
|
||||
}
|
||||
if (options.initialized) {
|
||||
EntityHelper.ensurePropagation(entity);
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
assignDefaultValues(entity, meta) {
|
||||
for (const prop of meta.props) {
|
||||
if (prop.embedded || [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind)) {
|
||||
continue;
|
||||
}
|
||||
if (prop.onCreate) {
|
||||
entity[prop.name] ??= prop.onCreate(entity, this.#em);
|
||||
} else if (prop.default != null && !isRaw(prop.default) && entity[prop.name] === undefined) {
|
||||
entity[prop.name] = prop.default;
|
||||
}
|
||||
if (prop.kind === ReferenceKind.EMBEDDED && entity[prop.name]) {
|
||||
const items = prop.array ? entity[prop.name] : [entity[prop.name]];
|
||||
for (const item of items) {
|
||||
this.assignDefaultValues(item, prop.targetMeta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
hydrate(entity, meta, data, options) {
|
||||
if (options.initialized) {
|
||||
this.#hydrator.hydrate(
|
||||
entity,
|
||||
meta,
|
||||
data,
|
||||
this,
|
||||
'full',
|
||||
options.newEntity,
|
||||
options.convertCustomTypes,
|
||||
options.schema,
|
||||
this.#driver.getSchemaName(meta, options),
|
||||
options.normalizeAccessors,
|
||||
);
|
||||
} else {
|
||||
this.#hydrator.hydrateReference(
|
||||
entity,
|
||||
meta,
|
||||
data,
|
||||
this,
|
||||
options.convertCustomTypes,
|
||||
options.schema,
|
||||
this.#driver.getSchemaName(meta, options),
|
||||
options.normalizeAccessors,
|
||||
);
|
||||
}
|
||||
Utils.keys(data).forEach(key => {
|
||||
helper(entity)?.__loadedProperties.add(key);
|
||||
helper(entity)?.__serializationContext.fields?.add(key);
|
||||
});
|
||||
const processOnCreateHooksEarly =
|
||||
options.processOnCreateHooksEarly ?? this.#config.get('processOnCreateHooksEarly');
|
||||
if (options.newEntity && processOnCreateHooksEarly) {
|
||||
this.assignDefaultValues(entity, meta);
|
||||
}
|
||||
}
|
||||
findEntity(data, meta, options) {
|
||||
const schema = this.#driver.getSchemaName(meta, options);
|
||||
if (meta.simplePK) {
|
||||
return this.unitOfWork.getById(meta.class, data[meta.primaryKeys[0]], schema);
|
||||
}
|
||||
if (!Array.isArray(data) && meta.primaryKeys.some(pk => data[pk] == null)) {
|
||||
return undefined;
|
||||
}
|
||||
const pks = Utils.getOrderedPrimaryKeys(data, meta, this.#platform, options.convertCustomTypes);
|
||||
return this.unitOfWork.getById(meta.class, pks, schema);
|
||||
}
|
||||
processDiscriminatorColumn(meta, data) {
|
||||
// Handle STI discriminator (persisted column)
|
||||
if (meta.root.inheritanceType === 'sti') {
|
||||
const prop = meta.properties[meta.root.discriminatorColumn];
|
||||
const value = data[prop.name];
|
||||
const type = meta.root.discriminatorMap[value];
|
||||
meta = type ? this.#metadata.get(type) : meta;
|
||||
return meta;
|
||||
}
|
||||
// Handle TPT discriminator (computed at query time)
|
||||
if (meta.root.inheritanceType === 'tpt' && meta.root.discriminatorMap) {
|
||||
const value = data[meta.root.tptDiscriminatorColumn];
|
||||
if (value) {
|
||||
const type = meta.root.discriminatorMap[value];
|
||||
meta = type ? this.#metadata.get(type) : meta;
|
||||
}
|
||||
}
|
||||
return meta;
|
||||
}
|
||||
/**
|
||||
* denormalize PK to value required by driver (e.g. ObjectId)
|
||||
*/
|
||||
denormalizePrimaryKey(meta, data) {
|
||||
const pk = meta.getPrimaryProp();
|
||||
const spk = meta.properties[meta.serializedPrimaryKey];
|
||||
if (!spk?.serializedPrimaryKey) {
|
||||
return;
|
||||
}
|
||||
if (pk.type === 'ObjectId' && (data[pk.name] != null || data[spk.name] != null)) {
|
||||
data[pk.name] = this.#platform.denormalizePrimaryKey(data[spk.name] || data[pk.name]);
|
||||
delete data[spk.name];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* returns parameters for entity constructor, creating references from plain ids
|
||||
*/
|
||||
extractConstructorParams(meta, data, options) {
|
||||
if (!meta.constructorParams) {
|
||||
return [data];
|
||||
}
|
||||
return meta.constructorParams.map(k => {
|
||||
const prop = meta.properties[k];
|
||||
const value = data[k];
|
||||
if (prop && [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) && value) {
|
||||
const pk = Reference.unwrapReference(value);
|
||||
const entity = this.unitOfWork.getById(prop.targetMeta.class, pk, options.schema, true);
|
||||
if (entity) {
|
||||
return entity;
|
||||
}
|
||||
if (Utils.isEntity(value)) {
|
||||
return value;
|
||||
}
|
||||
const nakedPk = Utils.extractPK(value, prop.targetMeta, true);
|
||||
if (Utils.isObject(value) && !nakedPk) {
|
||||
return this.create(prop.targetMeta.class, value, options);
|
||||
}
|
||||
const { newEntity, initialized, ...rest } = options;
|
||||
const target = this.createReference(prop.targetMeta.class, nakedPk, rest);
|
||||
return Reference.wrapReference(target, prop);
|
||||
}
|
||||
if (prop?.kind === ReferenceKind.EMBEDDED && value) {
|
||||
/* v8 ignore next */
|
||||
if (Utils.isEntity(value)) {
|
||||
return value;
|
||||
}
|
||||
return this.createEmbeddable(prop.targetMeta.class, value, options);
|
||||
}
|
||||
if (!prop) {
|
||||
const tmp = { ...data };
|
||||
for (const prop of meta.props) {
|
||||
if (!options.convertCustomTypes || !prop.customType || tmp[prop.name] == null) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
[ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(prop.kind) &&
|
||||
Utils.isPlainObject(tmp[prop.name]) &&
|
||||
!Utils.extractPK(tmp[prop.name], prop.targetMeta, true)
|
||||
) {
|
||||
tmp[prop.name] = Reference.wrapReference(this.create(prop.targetMeta.class, tmp[prop.name], options), prop);
|
||||
} else if (prop.kind === ReferenceKind.SCALAR) {
|
||||
tmp[prop.name] = prop.customType.convertToJSValue(tmp[prop.name], this.#platform);
|
||||
}
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
if (options.convertCustomTypes && prop.customType && value != null) {
|
||||
return prop.customType.convertToJSValue(value, this.#platform);
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
get unitOfWork() {
|
||||
return this.#em.getUnitOfWork(false);
|
||||
}
|
||||
}
|
||||
41
node_modules/@mikro-orm/core/entity/EntityHelper.d.ts
generated
vendored
Normal file
41
node_modules/@mikro-orm/core/entity/EntityHelper.d.ts
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { EntityManager } from '../EntityManager.js';
|
||||
import { type EntityMetadata, type EntityProperty, type IHydrator } from '../typings.js';
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export declare class EntityHelper {
|
||||
static decorate<T extends object>(meta: EntityMetadata<T>, em: EntityManager): void;
|
||||
/**
|
||||
* As a performance optimization, we create entity state methods lazily. We first add
|
||||
* the `null` value to the prototype to reserve space in memory. Then we define a setter on the
|
||||
* prototype that will be executed exactly once per entity instance. There we redefine the given
|
||||
* property on the entity instance, so shadowing the prototype setter.
|
||||
*/
|
||||
private static defineBaseProperties;
|
||||
/**
|
||||
* Defines getter and setter for every owning side of m:1 and 1:1 relation. This is then used for propagation of
|
||||
* changes to the inverse side of bi-directional relations. Rest of the properties are also defined this way to
|
||||
* achieve dirtiness, which is then used for fast checks whether we need to auto-flush because of managed entities.
|
||||
*
|
||||
* First defines a setter on the prototype, once called, actual get/set handlers are registered on the instance rather
|
||||
* than on its prototype. Thanks to this we still have those properties enumerable (e.g. part of `Object.keys(entity)`).
|
||||
*/
|
||||
private static defineProperties;
|
||||
static defineCustomInspect<T extends object>(meta: EntityMetadata<T>): void;
|
||||
static defineReferenceProperty<T extends object>(
|
||||
meta: EntityMetadata<T>,
|
||||
prop: EntityProperty<T>,
|
||||
ref: T,
|
||||
hydrator: IHydrator,
|
||||
): void;
|
||||
static propagate<T extends object>(
|
||||
meta: EntityMetadata<T>,
|
||||
entity: T,
|
||||
owner: T,
|
||||
prop: EntityProperty<T>,
|
||||
value?: T[keyof T & string],
|
||||
old?: T,
|
||||
): void;
|
||||
private static propagateOneToOne;
|
||||
static ensurePropagation<T extends object>(entity: T): void;
|
||||
}
|
||||
311
node_modules/@mikro-orm/core/entity/EntityHelper.js
generated
vendored
Normal file
311
node_modules/@mikro-orm/core/entity/EntityHelper.js
generated
vendored
Normal file
@@ -0,0 +1,311 @@
|
||||
import {
|
||||
EagerProps,
|
||||
EntityName,
|
||||
EntityRepositoryType,
|
||||
HiddenProps,
|
||||
OptionalProps,
|
||||
PrimaryKeyProp,
|
||||
} from '../typings.js';
|
||||
import { EntityTransformer } from '../serialization/EntityTransformer.js';
|
||||
import { Reference } from './Reference.js';
|
||||
import { Utils } from '../utils/Utils.js';
|
||||
import { WrappedEntity } from './WrappedEntity.js';
|
||||
import { ReferenceKind } from '../enums.js';
|
||||
import { helper } from './wrap.js';
|
||||
import { inspect } from '../logging/inspect.js';
|
||||
import { getEnv } from '../utils/env-vars.js';
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class EntityHelper {
|
||||
static decorate(meta, em) {
|
||||
const fork = em.fork(); // use fork so we can access `EntityFactory`
|
||||
const serializedPrimaryKey = meta.props.find(p => p.serializedPrimaryKey);
|
||||
if (serializedPrimaryKey) {
|
||||
Object.defineProperty(meta.prototype, serializedPrimaryKey.name, {
|
||||
get() {
|
||||
return this._id ? em.getPlatform().normalizePrimaryKey(this._id) : null;
|
||||
},
|
||||
set(id) {
|
||||
this._id = id ? em.getPlatform().denormalizePrimaryKey(id) : null;
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
EntityHelper.defineBaseProperties(meta, meta.prototype, fork);
|
||||
EntityHelper.defineCustomInspect(meta);
|
||||
if (em.config.get('propagationOnPrototype') && !meta.embeddable && !meta.virtual) {
|
||||
EntityHelper.defineProperties(meta, fork);
|
||||
}
|
||||
const prototype = meta.prototype;
|
||||
if (!prototype.toJSON) {
|
||||
// toJSON can be overridden
|
||||
Object.defineProperty(prototype, 'toJSON', {
|
||||
value: function (...args) {
|
||||
// Guard against being called on the prototype itself (e.g. by serializers
|
||||
// walking the object graph and calling toJSON on prototype objects)
|
||||
if (this === prototype) {
|
||||
return {};
|
||||
}
|
||||
return EntityTransformer.toObject(this, ...args);
|
||||
},
|
||||
writable: true,
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* As a performance optimization, we create entity state methods lazily. We first add
|
||||
* the `null` value to the prototype to reserve space in memory. Then we define a setter on the
|
||||
* prototype that will be executed exactly once per entity instance. There we redefine the given
|
||||
* property on the entity instance, so shadowing the prototype setter.
|
||||
*/
|
||||
static defineBaseProperties(meta, prototype, em) {
|
||||
// oxfmt-ignore
|
||||
const helperParams = meta.embeddable || meta.virtual ? [] : [em.getComparator().getPkGetter(meta), em.getComparator().getPkSerializer(meta), em.getComparator().getPkGetterConverted(meta)];
|
||||
Object.defineProperties(prototype, {
|
||||
__entity: { value: !meta.embeddable, configurable: true },
|
||||
__meta: { value: meta, configurable: true },
|
||||
__config: { value: em.config, configurable: true },
|
||||
__platform: { value: em.getPlatform(), configurable: true },
|
||||
__factory: { value: em.getEntityFactory(), configurable: true },
|
||||
__helper: {
|
||||
get() {
|
||||
Object.defineProperty(this, '__helper', {
|
||||
value: new WrappedEntity(this, em.getHydrator(), ...helperParams),
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
});
|
||||
return this.__helper;
|
||||
},
|
||||
configurable: true, // otherwise jest fails when trying to compare entities ¯\_(ツ)_/¯
|
||||
},
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Defines getter and setter for every owning side of m:1 and 1:1 relation. This is then used for propagation of
|
||||
* changes to the inverse side of bi-directional relations. Rest of the properties are also defined this way to
|
||||
* achieve dirtiness, which is then used for fast checks whether we need to auto-flush because of managed entities.
|
||||
*
|
||||
* First defines a setter on the prototype, once called, actual get/set handlers are registered on the instance rather
|
||||
* than on its prototype. Thanks to this we still have those properties enumerable (e.g. part of `Object.keys(entity)`).
|
||||
*/
|
||||
static defineProperties(meta, em) {
|
||||
Object.values(meta.properties).forEach(prop => {
|
||||
const isCollection = [ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(prop.kind);
|
||||
// oxfmt-ignore
|
||||
const isReference = [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && (prop.inversedBy || prop.mappedBy) && !prop.mapToPk;
|
||||
if (isReference) {
|
||||
Object.defineProperty(meta.prototype, prop.name, {
|
||||
set(val) {
|
||||
EntityHelper.defineReferenceProperty(meta, prop, this, em.getHydrator());
|
||||
this[prop.name] = val;
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (prop.inherited || prop.primary || prop.accessor || prop.persist === false || prop.embedded || isCollection) {
|
||||
return;
|
||||
}
|
||||
Object.defineProperty(meta.prototype, prop.name, {
|
||||
set(val) {
|
||||
Object.defineProperty(this, prop.name, {
|
||||
get() {
|
||||
return this.__helper?.__data[prop.name];
|
||||
},
|
||||
set(val) {
|
||||
this.__helper.__data[prop.name] = val;
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
});
|
||||
this.__helper.__data[prop.name] = val;
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
static defineCustomInspect(meta) {
|
||||
// @ts-ignore
|
||||
meta.prototype[Symbol.for('nodejs.util.inspect.custom')] ??= function (depth = 2) {
|
||||
const object = {};
|
||||
const keys = new Set(Utils.keys(this));
|
||||
for (const prop of meta.props) {
|
||||
if (keys.has(prop.name) || (prop.getter && prop.accessor === prop.name)) {
|
||||
object[prop.name] = this[prop.name];
|
||||
}
|
||||
}
|
||||
for (const key of keys) {
|
||||
if (!meta.properties[key]) {
|
||||
object[key] = this[key];
|
||||
}
|
||||
}
|
||||
// ensure we dont have internal symbols in the POJO
|
||||
[OptionalProps, EntityRepositoryType, PrimaryKeyProp, EagerProps, HiddenProps, EntityName].forEach(
|
||||
sym => delete object[sym],
|
||||
);
|
||||
meta.props.filter(prop => object[prop.name] === undefined).forEach(prop => delete object[prop.name]);
|
||||
const ret = inspect(object, { depth });
|
||||
let name = this.constructor.name;
|
||||
const showEM = ['true', 't', '1'].includes(getEnv('MIKRO_ORM_LOG_EM_ID')?.toLowerCase() ?? '');
|
||||
if (showEM) {
|
||||
if (helper(this).__em) {
|
||||
name += ` [managed by ${helper(this).__em.id}]`;
|
||||
} else {
|
||||
name += ` [not managed]`;
|
||||
}
|
||||
}
|
||||
// distinguish not initialized entities
|
||||
if (!helper(this).__initialized) {
|
||||
name = `(${name})`;
|
||||
}
|
||||
return ret === '[Object]' ? `[${name}]` : name + ' ' + ret;
|
||||
};
|
||||
}
|
||||
static defineReferenceProperty(meta, prop, ref, hydrator) {
|
||||
const wrapped = helper(ref);
|
||||
Object.defineProperty(ref, prop.name, {
|
||||
get() {
|
||||
return helper(ref).__data[prop.name];
|
||||
},
|
||||
set(val) {
|
||||
const entity = Reference.unwrapReference(val ?? wrapped.__data[prop.name]);
|
||||
const old = Reference.unwrapReference(wrapped.__data[prop.name]);
|
||||
// oxfmt-ignore
|
||||
if (old && old !== entity && prop.kind === ReferenceKind.MANY_TO_ONE && prop.inversedBy && old[prop.inversedBy]) {
|
||||
old[prop.inversedBy].removeWithoutPropagation(this);
|
||||
}
|
||||
wrapped.__data[prop.name] = Reference.wrapReference(val, prop);
|
||||
// when propagation from inside hydration, we set the FK to the entity data immediately
|
||||
if (val && hydrator.isRunning() && wrapped.__originalEntityData && prop.owner) {
|
||||
wrapped.__originalEntityData[prop.name] = Utils.getPrimaryKeyValues(
|
||||
wrapped.__data[prop.name],
|
||||
prop.targetMeta,
|
||||
true,
|
||||
);
|
||||
}
|
||||
EntityHelper.propagate(meta, entity, this, prop, Reference.unwrapReference(val), old);
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
static propagate(meta, entity, owner, prop, value, old) {
|
||||
// For polymorphic relations, get bidirectional relations from the actual entity's metadata
|
||||
let bidirectionalRelations;
|
||||
if (prop.polymorphic && prop.polymorphTargets?.length) {
|
||||
// For polymorphic relations, we need to get the bidirectional relations from the actual value's metadata
|
||||
if (!value) {
|
||||
return; // No value means no propagation needed
|
||||
}
|
||||
bidirectionalRelations = helper(value).__meta.bidirectionalRelations;
|
||||
} else {
|
||||
bidirectionalRelations = prop.targetMeta.bidirectionalRelations;
|
||||
}
|
||||
for (const prop2 of bidirectionalRelations) {
|
||||
if ((prop2.inversedBy || prop2.mappedBy) !== prop.name) {
|
||||
continue;
|
||||
}
|
||||
// oxfmt-ignore
|
||||
if (prop2.targetMeta.abstract ? prop2.targetMeta.root.class !== meta.root.class : prop2.targetMeta.class !== meta.class) {
|
||||
continue;
|
||||
}
|
||||
const inverse = value?.[prop2.name];
|
||||
if (prop.ref && owner[prop.name]) {
|
||||
// eslint-disable-next-line dot-notation
|
||||
owner[prop.name]['property'] = prop;
|
||||
}
|
||||
if (Utils.isCollection(inverse) && inverse.isPartial()) {
|
||||
continue;
|
||||
}
|
||||
if (prop.kind === ReferenceKind.MANY_TO_ONE && Utils.isCollection(inverse) && inverse.isInitialized()) {
|
||||
inverse.addWithoutPropagation(owner);
|
||||
helper(owner).__em?.getUnitOfWork().cancelOrphanRemoval(owner);
|
||||
}
|
||||
if (prop.kind === ReferenceKind.ONE_TO_ONE) {
|
||||
if (
|
||||
(value != null && Reference.unwrapReference(inverse) !== owner) ||
|
||||
(value == null && entity?.[prop2.name] != null)
|
||||
) {
|
||||
if (entity && (!prop.owner || helper(entity).__initialized)) {
|
||||
EntityHelper.propagateOneToOne(entity, owner, prop, prop2, value, old);
|
||||
}
|
||||
if (old && prop.orphanRemoval) {
|
||||
helper(old).__em?.getUnitOfWork().scheduleOrphanRemoval(old);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
static propagateOneToOne(entity, owner, prop, prop2, value, old) {
|
||||
helper(entity).__pk = helper(entity).getPrimaryKey();
|
||||
// the inverse side will be changed on the `value` too, so we need to clean-up and schedule orphan removal there too
|
||||
if (
|
||||
!prop.primary &&
|
||||
!prop2.mapToPk &&
|
||||
value?.[prop2.name] != null &&
|
||||
Reference.unwrapReference(value[prop2.name]) !== entity
|
||||
) {
|
||||
const other = Reference.unwrapReference(value[prop2.name]);
|
||||
delete helper(other).__data[prop.name];
|
||||
if (prop2.orphanRemoval) {
|
||||
helper(other).__em?.getUnitOfWork().scheduleOrphanRemoval(other);
|
||||
}
|
||||
}
|
||||
// Skip setting the inverse side to null if it's a primary key - the entity will be removed via orphan removal
|
||||
// Setting a primary key to null would corrupt the entity and cause validation errors
|
||||
if (value == null && prop.orphanRemoval && prop2.primary) {
|
||||
return;
|
||||
}
|
||||
if (value == null) {
|
||||
entity[prop2.name] = value;
|
||||
} else if (prop2.mapToPk) {
|
||||
entity[prop2.name] = helper(owner).getPrimaryKey();
|
||||
} else {
|
||||
entity[prop2.name] = Reference.wrapReference(owner, prop);
|
||||
}
|
||||
if (old?.[prop2.name] != null) {
|
||||
delete helper(old).__data[prop2.name];
|
||||
old[prop2.name] = null;
|
||||
}
|
||||
}
|
||||
static ensurePropagation(entity) {
|
||||
if (entity.__gettersDefined) {
|
||||
return;
|
||||
}
|
||||
const wrapped = helper(entity);
|
||||
const meta = wrapped.__meta;
|
||||
const platform = wrapped.__platform;
|
||||
const serializedPrimaryKey = meta.props.find(p => p.serializedPrimaryKey);
|
||||
const values = [];
|
||||
if (serializedPrimaryKey) {
|
||||
const pk = meta.getPrimaryProps()[0];
|
||||
const val = entity[serializedPrimaryKey.name];
|
||||
delete entity[serializedPrimaryKey.name];
|
||||
Object.defineProperty(entity, serializedPrimaryKey.name, {
|
||||
get() {
|
||||
return this[pk.name] ? platform.normalizePrimaryKey(this[pk.name]) : null;
|
||||
},
|
||||
set(id) {
|
||||
this[pk.name] = id ? platform.denormalizePrimaryKey(id) : null;
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
if (entity[pk.name] == null && val != null) {
|
||||
values.push(serializedPrimaryKey.name, val);
|
||||
}
|
||||
}
|
||||
for (const prop of meta.trackingProps) {
|
||||
if (entity[prop.name] !== undefined) {
|
||||
values.push(prop.name, entity[prop.name]);
|
||||
}
|
||||
delete entity[prop.name];
|
||||
}
|
||||
Object.defineProperties(entity, meta.definedProperties);
|
||||
for (let i = 0; i < values.length; i += 2) {
|
||||
entity[values[i]] = values[i + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
10
node_modules/@mikro-orm/core/entity/EntityIdentifier.d.ts
generated
vendored
Normal file
10
node_modules/@mikro-orm/core/entity/EntityIdentifier.d.ts
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { IPrimaryKey } from '../typings.js';
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export declare class EntityIdentifier {
|
||||
private value?;
|
||||
constructor(value?: IPrimaryKey | undefined);
|
||||
setValue(value: IPrimaryKey): void;
|
||||
getValue<T extends IPrimaryKey = IPrimaryKey>(): T;
|
||||
}
|
||||
15
node_modules/@mikro-orm/core/entity/EntityIdentifier.js
generated
vendored
Normal file
15
node_modules/@mikro-orm/core/entity/EntityIdentifier.js
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class EntityIdentifier {
|
||||
value;
|
||||
constructor(value) {
|
||||
this.value = value;
|
||||
}
|
||||
setValue(value) {
|
||||
this.value = value;
|
||||
}
|
||||
getValue() {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
112
node_modules/@mikro-orm/core/entity/EntityLoader.d.ts
generated
vendored
Normal file
112
node_modules/@mikro-orm/core/entity/EntityLoader.d.ts
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
import type {
|
||||
AnyEntity,
|
||||
AutoPath,
|
||||
ConnectionType,
|
||||
EntityName,
|
||||
EntityProperty,
|
||||
FilterQuery,
|
||||
PopulateOptions,
|
||||
} from '../typings.js';
|
||||
import type { EntityManager } from '../EntityManager.js';
|
||||
import { LoadStrategy, type LockMode, type PopulateHint, PopulatePath, type QueryOrderMap } from '../enums.js';
|
||||
import type { FilterOptions } from '../drivers/IDatabaseDriver.js';
|
||||
import type { LoggingOptions } from '../logging/Logger.js';
|
||||
/** Options for controlling how relations are loaded by the EntityLoader. */
|
||||
export interface EntityLoaderOptions<
|
||||
Entity,
|
||||
Fields extends string = PopulatePath.ALL,
|
||||
Excludes extends string = never,
|
||||
> {
|
||||
/** Select specific fields to load (partial loading). */
|
||||
fields?: readonly AutoPath<Entity, Fields, `${PopulatePath.ALL}`>[];
|
||||
/** Fields to exclude from loading. */
|
||||
exclude?: readonly AutoPath<Entity, Excludes>[];
|
||||
/** Additional filtering conditions applied to populated relations. */
|
||||
where?: FilterQuery<Entity>;
|
||||
/** Controls how `where` conditions are applied to populated relations. */
|
||||
populateWhere?: PopulateHint | `${PopulateHint}`;
|
||||
/** Ordering for populated relations. */
|
||||
orderBy?: QueryOrderMap<Entity> | QueryOrderMap<Entity>[];
|
||||
/** Whether to reload already loaded entities. */
|
||||
refresh?: boolean;
|
||||
/** Whether to validate the populate hint against the entity metadata. */
|
||||
validate?: boolean;
|
||||
/** Whether to look up eager-loaded relationships automatically. */
|
||||
lookup?: boolean;
|
||||
/** Whether to convert custom types during hydration. */
|
||||
convertCustomTypes?: boolean;
|
||||
/** Whether to skip loading lazy scalar properties. */
|
||||
ignoreLazyScalarProperties?: boolean;
|
||||
/** Filter options to apply when loading relations. */
|
||||
filters?: FilterOptions;
|
||||
/** Loading strategy to use (select-in, joined, or balanced). */
|
||||
strategy?: LoadStrategy | `${LoadStrategy}`;
|
||||
/** Lock mode for the query (pessimistic locking). */
|
||||
lockMode?: Exclude<LockMode, LockMode.OPTIMISTIC>;
|
||||
/** Database schema override. */
|
||||
schema?: string;
|
||||
/** Connection type (read or write replica). */
|
||||
connectionType?: ConnectionType;
|
||||
/** Logging options for the query. */
|
||||
logging?: LoggingOptions;
|
||||
}
|
||||
/** Responsible for batch-loading entity relations using either select-in or joined loading strategies. */
|
||||
export declare class EntityLoader {
|
||||
#private;
|
||||
constructor(em: EntityManager);
|
||||
/**
|
||||
* Loads specified relations in batch.
|
||||
* This will execute one query for each relation, that will populate it on all the specified entities.
|
||||
*/
|
||||
populate<Entity extends object, Fields extends string = PopulatePath.ALL>(
|
||||
entityName: EntityName<Entity>,
|
||||
entities: Entity[],
|
||||
populate: PopulateOptions<Entity>[] | boolean,
|
||||
options: EntityLoaderOptions<Entity, Fields>,
|
||||
): Promise<void>;
|
||||
/** Normalizes populate hints into a structured array of PopulateOptions, expanding dot paths and eager relations. */
|
||||
normalizePopulate<Entity>(
|
||||
entityName: EntityName<Entity>,
|
||||
populate: (PopulateOptions<Entity> | boolean)[] | PopulateOptions<Entity> | boolean,
|
||||
strategy?: LoadStrategy,
|
||||
lookup?: boolean,
|
||||
exclude?: string[],
|
||||
): PopulateOptions<Entity>[];
|
||||
private setSerializationContext;
|
||||
/**
|
||||
* Merge multiple populates for the same entity with different children. Also skips `*` fields, those can come from
|
||||
* partial loading hints (`fields`) that are used to infer the `populate` hint if missing.
|
||||
*/
|
||||
private mergeNestedPopulate;
|
||||
/**
|
||||
* preload everything in one call (this will update already existing references in IM)
|
||||
*/
|
||||
private populateMany;
|
||||
private populateScalar;
|
||||
private populatePolymorphic;
|
||||
private initializeCollections;
|
||||
private initializeOneToMany;
|
||||
private initializeManyToMany;
|
||||
private findChildren;
|
||||
private mergePrimaryCondition;
|
||||
private populateField;
|
||||
/** @internal */
|
||||
findChildrenFromPivotTable<Entity extends object>(
|
||||
filtered: Entity[],
|
||||
prop: EntityProperty<Entity>,
|
||||
options: Required<EntityLoaderOptions<Entity>>,
|
||||
orderBy?: QueryOrderMap<Entity>[],
|
||||
populate?: PopulateOptions<Entity>,
|
||||
pivotJoin?: boolean,
|
||||
): Promise<AnyEntity[][]>;
|
||||
private extractChildCondition;
|
||||
private buildFields;
|
||||
private getChildReferences;
|
||||
private filterCollections;
|
||||
private isPropertyLoaded;
|
||||
private filterReferences;
|
||||
private filterByReferences;
|
||||
private lookupAllRelationships;
|
||||
private getRelationName;
|
||||
private lookupEagerLoadedRelationships;
|
||||
}
|
||||
787
node_modules/@mikro-orm/core/entity/EntityLoader.js
generated
vendored
Normal file
787
node_modules/@mikro-orm/core/entity/EntityLoader.js
generated
vendored
Normal file
@@ -0,0 +1,787 @@
|
||||
import { QueryHelper } from '../utils/QueryHelper.js';
|
||||
import { Utils } from '../utils/Utils.js';
|
||||
import { ValidationError } from '../errors.js';
|
||||
import { LoadStrategy, PopulatePath, ReferenceKind } from '../enums.js';
|
||||
import { Reference } from './Reference.js';
|
||||
import { helper } from './wrap.js';
|
||||
import { expandDotPaths } from './utils.js';
|
||||
import { Raw } from '../utils/RawQueryFragment.js';
|
||||
/** Responsible for batch-loading entity relations using either select-in or joined loading strategies. */
|
||||
export class EntityLoader {
|
||||
#metadata;
|
||||
#driver;
|
||||
#em;
|
||||
constructor(em) {
|
||||
this.#em = em;
|
||||
this.#metadata = this.#em.getMetadata();
|
||||
this.#driver = this.#em.getDriver();
|
||||
}
|
||||
/**
|
||||
* Loads specified relations in batch.
|
||||
* This will execute one query for each relation, that will populate it on all the specified entities.
|
||||
*/
|
||||
async populate(entityName, entities, populate, options) {
|
||||
if (entities.length === 0 || Utils.isEmpty(populate)) {
|
||||
return this.setSerializationContext(entities, populate, options);
|
||||
}
|
||||
const meta = this.#metadata.find(entityName);
|
||||
if (entities.some(e => !e.__helper)) {
|
||||
const entity = entities.find(e => !Utils.isEntity(e));
|
||||
throw ValidationError.notDiscoveredEntity(entity, meta, 'populate');
|
||||
}
|
||||
const references = entities.filter(e => !helper(e).isInitialized());
|
||||
const visited = (options.visited ??= new Set());
|
||||
options.where ??= {};
|
||||
options.orderBy ??= {};
|
||||
options.lookup ??= true;
|
||||
options.validate ??= true;
|
||||
options.refresh ??= false;
|
||||
options.convertCustomTypes ??= true;
|
||||
if (references.length > 0) {
|
||||
await this.populateScalar(meta, references, { ...options, populateWhere: undefined });
|
||||
}
|
||||
populate = this.normalizePopulate(entityName, populate, options.strategy, options.lookup, options.exclude);
|
||||
const invalid = populate.find(({ field }) => !this.#em.canPopulate(entityName, field));
|
||||
/* v8 ignore next */
|
||||
if (options.validate && invalid) {
|
||||
throw ValidationError.invalidPropertyName(entityName, invalid.field);
|
||||
}
|
||||
this.setSerializationContext(entities, populate, options);
|
||||
for (const entity of entities) {
|
||||
visited.add(entity);
|
||||
}
|
||||
for (const pop of populate) {
|
||||
await this.populateField(entityName, entities, pop, options);
|
||||
}
|
||||
for (const entity of entities) {
|
||||
visited.delete(entity);
|
||||
}
|
||||
}
|
||||
/** Normalizes populate hints into a structured array of PopulateOptions, expanding dot paths and eager relations. */
|
||||
normalizePopulate(entityName, populate, strategy, lookup = true, exclude) {
|
||||
const meta = this.#metadata.find(entityName);
|
||||
let normalized = Utils.asArray(populate).map(field => {
|
||||
// oxfmt-ignore
|
||||
return typeof field === 'boolean' || field.field === PopulatePath.ALL ? { all: !!field, field: meta.primaryKeys[0] } : field;
|
||||
});
|
||||
if (normalized.some(p => p.all)) {
|
||||
normalized = this.lookupAllRelationships(entityName);
|
||||
}
|
||||
// convert nested `field` with dot syntax to PopulateOptions with `children` array
|
||||
expandDotPaths(meta, normalized, true);
|
||||
if (lookup && populate !== false) {
|
||||
normalized = this.lookupEagerLoadedRelationships(entityName, normalized, strategy, '', [], exclude);
|
||||
// convert nested `field` with dot syntax produced by eager relations
|
||||
expandDotPaths(meta, normalized, true);
|
||||
}
|
||||
// merge same fields
|
||||
return this.mergeNestedPopulate(normalized);
|
||||
}
|
||||
setSerializationContext(entities, populate, options) {
|
||||
for (const entity of entities) {
|
||||
helper(entity).setSerializationContext({
|
||||
populate,
|
||||
fields: options.fields,
|
||||
exclude: options.exclude,
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Merge multiple populates for the same entity with different children. Also skips `*` fields, those can come from
|
||||
* partial loading hints (`fields`) that are used to infer the `populate` hint if missing.
|
||||
*/
|
||||
mergeNestedPopulate(populate) {
|
||||
const tmp = populate.reduce((ret, item) => {
|
||||
/* v8 ignore next */
|
||||
if (item.field === PopulatePath.ALL) {
|
||||
return ret;
|
||||
}
|
||||
if (!ret[item.field]) {
|
||||
ret[item.field] = item;
|
||||
return ret;
|
||||
}
|
||||
if (!ret[item.field].children && item.children) {
|
||||
ret[item.field].children = item.children;
|
||||
} else if (ret[item.field].children && item.children) {
|
||||
ret[item.field].children.push(...item.children);
|
||||
}
|
||||
return ret;
|
||||
}, {});
|
||||
return Object.values(tmp).map(item => {
|
||||
if (item.children) {
|
||||
item.children = this.mergeNestedPopulate(item.children);
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* preload everything in one call (this will update already existing references in IM)
|
||||
*/
|
||||
async populateMany(entityName, entities, populate, options) {
|
||||
const [field, ref] = populate.field.split(':', 2);
|
||||
const meta = this.#metadata.find(entityName);
|
||||
const prop = meta.properties[field];
|
||||
if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner && !this.#driver.getPlatform().usesPivotTable()) {
|
||||
const filtered = entities.filter(e => !e[prop.name]?.isInitialized());
|
||||
if (filtered.length > 0) {
|
||||
await this.populateScalar(meta, filtered, { ...options, fields: [prop.name] });
|
||||
}
|
||||
}
|
||||
if (prop.kind === ReferenceKind.SCALAR && prop.lazy) {
|
||||
const filtered = entities.filter(
|
||||
e => options.refresh || (prop.ref ? !e[prop.name]?.isInitialized() : e[prop.name] === undefined),
|
||||
);
|
||||
if (options.ignoreLazyScalarProperties || filtered.length === 0) {
|
||||
return entities;
|
||||
}
|
||||
await this.populateScalar(meta, filtered, { ...options, fields: [prop.name] });
|
||||
return entities;
|
||||
}
|
||||
if (prop.kind === ReferenceKind.EMBEDDED) {
|
||||
return [];
|
||||
}
|
||||
const filtered = this.filterCollections(entities, field, options, ref);
|
||||
const innerOrderBy = Utils.asArray(options.orderBy)
|
||||
.filter(
|
||||
orderBy =>
|
||||
(Array.isArray(orderBy[prop.name]) && orderBy[prop.name].length > 0) || Utils.isObject(orderBy[prop.name]),
|
||||
)
|
||||
.flatMap(orderBy => orderBy[prop.name]);
|
||||
const where = await this.extractChildCondition(options, prop);
|
||||
if (prop.kind === ReferenceKind.MANY_TO_MANY && this.#driver.getPlatform().usesPivotTable()) {
|
||||
const pivotOrderBy = QueryHelper.mergeOrderBy(innerOrderBy, prop.orderBy, prop.targetMeta?.orderBy);
|
||||
const res = await this.findChildrenFromPivotTable(filtered, prop, options, pivotOrderBy, populate, !!ref);
|
||||
return Utils.flatten(res);
|
||||
}
|
||||
if (prop.polymorphic && prop.polymorphTargets) {
|
||||
return this.populatePolymorphic(entities, prop, options, !!ref);
|
||||
}
|
||||
const { items, partial } = await this.findChildren(
|
||||
options.filtered ?? entities,
|
||||
prop,
|
||||
populate,
|
||||
{
|
||||
...options,
|
||||
where,
|
||||
orderBy: innerOrderBy,
|
||||
},
|
||||
!!(ref || prop.mapToPk),
|
||||
);
|
||||
const customOrder = innerOrderBy.length > 0 || !!prop.orderBy || !!prop.targetMeta?.orderBy;
|
||||
this.initializeCollections(filtered, prop, field, items, customOrder, partial);
|
||||
return items;
|
||||
}
|
||||
async populateScalar(meta, filtered, options) {
|
||||
const pk = Utils.getPrimaryKeyHash(meta.primaryKeys);
|
||||
const ids = Utils.unique(filtered.map(e => Utils.getPrimaryKeyValues(e, meta, true)));
|
||||
const where = this.mergePrimaryCondition(ids, pk, options, meta, this.#metadata, this.#driver.getPlatform());
|
||||
const { filters, convertCustomTypes, lockMode, strategy, populateWhere, connectionType, logging, fields } = options;
|
||||
await this.#em.find(meta.class, where, {
|
||||
filters,
|
||||
convertCustomTypes,
|
||||
lockMode,
|
||||
strategy,
|
||||
populateWhere,
|
||||
connectionType,
|
||||
logging,
|
||||
fields: fields,
|
||||
populate: [],
|
||||
});
|
||||
}
|
||||
async populatePolymorphic(entities, prop, options, ref) {
|
||||
const ownerMeta = this.#metadata.get(entities[0].constructor);
|
||||
// Separate entities: those with loaded refs vs those needing FK load
|
||||
const toPopulate = [];
|
||||
const needsFkLoad = [];
|
||||
for (const entity of entities) {
|
||||
const refValue = entity[prop.name];
|
||||
if (refValue && helper(refValue).hasPrimaryKey()) {
|
||||
if (
|
||||
(ref && !options.refresh) || // :ref hint - already have reference
|
||||
(!ref && helper(refValue).__initialized && !options.refresh) // already loaded
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
toPopulate.push(entity);
|
||||
} else if (refValue == null && !helper(entity).__loadedProperties.has(prop.name)) {
|
||||
// FK columns weren't loaded (partial loading) — need to re-fetch them.
|
||||
// If the property IS in __loadedProperties, the FK was loaded and is genuinely null.
|
||||
needsFkLoad.push(entity);
|
||||
}
|
||||
}
|
||||
// Load FK columns using populateScalar pattern
|
||||
if (needsFkLoad.length > 0) {
|
||||
await this.populateScalar(ownerMeta, needsFkLoad, {
|
||||
...options,
|
||||
fields: [...ownerMeta.primaryKeys, prop.name],
|
||||
});
|
||||
// After loading FKs, add to toPopulate if not using :ref hint
|
||||
if (!ref) {
|
||||
for (const entity of needsFkLoad) {
|
||||
const refValue = entity[prop.name];
|
||||
if (refValue && helper(refValue).hasPrimaryKey()) {
|
||||
toPopulate.push(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (toPopulate.length === 0) {
|
||||
return [];
|
||||
}
|
||||
// Group references by target class for batch loading
|
||||
const groups = new Map();
|
||||
for (const entity of toPopulate) {
|
||||
const refValue = Reference.unwrapReference(entity[prop.name]);
|
||||
const discriminator = QueryHelper.findDiscriminatorValue(prop.discriminatorMap, helper(refValue).__meta.class);
|
||||
const group = groups.get(discriminator) ?? [];
|
||||
group.push(refValue);
|
||||
groups.set(discriminator, group);
|
||||
}
|
||||
// Load each group concurrently - identity map handles merging with existing references
|
||||
const allItems = [];
|
||||
await Promise.all(
|
||||
[...groups].map(async ([discriminator, children]) => {
|
||||
const targetMeta = this.#metadata.find(prop.discriminatorMap[discriminator]);
|
||||
await this.populateScalar(targetMeta, children, options);
|
||||
allItems.push(...children);
|
||||
}),
|
||||
);
|
||||
return allItems;
|
||||
}
|
||||
initializeCollections(filtered, prop, field, children, customOrder, partial) {
|
||||
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
||||
this.initializeOneToMany(filtered, children, prop, field, partial);
|
||||
}
|
||||
if (prop.kind === ReferenceKind.MANY_TO_MANY && !this.#driver.getPlatform().usesPivotTable()) {
|
||||
this.initializeManyToMany(filtered, children, prop, field, customOrder, partial);
|
||||
}
|
||||
}
|
||||
initializeOneToMany(filtered, children, prop, field, partial) {
|
||||
const mapToPk = prop.targetMeta.properties[prop.mappedBy].mapToPk;
|
||||
const map = {};
|
||||
for (const entity of filtered) {
|
||||
const key = helper(entity).getSerializedPrimaryKey();
|
||||
map[key] = [];
|
||||
}
|
||||
for (const child of children) {
|
||||
const pk = child.__helper.__data[prop.mappedBy] ?? child[prop.mappedBy];
|
||||
if (pk) {
|
||||
const key = helper(mapToPk ? this.#em.getReference(prop.targetMeta.class, pk) : pk).getSerializedPrimaryKey();
|
||||
map[key]?.push(child);
|
||||
}
|
||||
}
|
||||
for (const entity of filtered) {
|
||||
const key = helper(entity).getSerializedPrimaryKey();
|
||||
entity[field].hydrate(map[key], undefined, partial);
|
||||
}
|
||||
}
|
||||
initializeManyToMany(filtered, children, prop, field, customOrder, partial) {
|
||||
if (prop.mappedBy) {
|
||||
for (const entity of filtered) {
|
||||
const items = children.filter(child => child[prop.mappedBy].contains(entity, false));
|
||||
entity[field].hydrate(items, true, partial);
|
||||
}
|
||||
} else {
|
||||
// owning side of M:N without pivot table needs to be reordered
|
||||
for (const entity of filtered) {
|
||||
const order = !customOrder ? [...entity[prop.name].getItems(false)] : []; // copy order of references
|
||||
const items = children.filter(child => entity[prop.name].contains(child, false));
|
||||
if (!customOrder) {
|
||||
items.sort((a, b) => order.indexOf(a) - order.indexOf(b));
|
||||
}
|
||||
entity[field].hydrate(items, true, partial);
|
||||
}
|
||||
}
|
||||
}
|
||||
async findChildren(entities, prop, populate, options, ref) {
|
||||
const children = Utils.unique(this.getChildReferences(entities, prop, options, ref));
|
||||
const meta = prop.targetMeta;
|
||||
// When targetKey is set, use it for FK lookup instead of the PK
|
||||
let fk = prop.targetKey ?? Utils.getPrimaryKeyHash(meta.primaryKeys);
|
||||
let schema = options.schema;
|
||||
const partial = !Utils.isEmpty(prop.where) || !Utils.isEmpty(options.where);
|
||||
let polymorphicOwnerProp;
|
||||
if (prop.kind === ReferenceKind.ONE_TO_MANY || (prop.kind === ReferenceKind.MANY_TO_MANY && !prop.owner)) {
|
||||
const ownerProp = meta.properties[prop.mappedBy];
|
||||
if (ownerProp.polymorphic && ownerProp.fieldNames.length >= 2) {
|
||||
const idColumns = ownerProp.fieldNames.slice(1);
|
||||
fk = idColumns.length === 1 ? idColumns[0] : idColumns;
|
||||
polymorphicOwnerProp = ownerProp;
|
||||
} else {
|
||||
fk = ownerProp.name;
|
||||
}
|
||||
}
|
||||
if (prop.kind === ReferenceKind.ONE_TO_ONE && !prop.owner && !ref) {
|
||||
children.length = 0;
|
||||
fk = meta.properties[prop.mappedBy].name;
|
||||
children.push(...this.filterByReferences(entities, prop.name, options.refresh));
|
||||
}
|
||||
if (children.length === 0) {
|
||||
return { items: [], partial };
|
||||
}
|
||||
if (!schema && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
|
||||
schema = children.find(e => e.__helper.__schema)?.__helper.__schema;
|
||||
}
|
||||
const ids = Utils.unique(children.map(e => (prop.targetKey ? e[prop.targetKey] : e.__helper.getPrimaryKey())));
|
||||
let where;
|
||||
if (polymorphicOwnerProp && Array.isArray(fk)) {
|
||||
const conditions = ids.map(id => {
|
||||
const pkValues = Object.values(id);
|
||||
return Object.fromEntries(fk.map((col, idx) => [col, pkValues[idx]]));
|
||||
});
|
||||
where = conditions.length === 1 ? conditions[0] : { $or: conditions };
|
||||
} else {
|
||||
where = this.mergePrimaryCondition(ids, fk, options, meta, this.#metadata, this.#driver.getPlatform());
|
||||
}
|
||||
if (polymorphicOwnerProp) {
|
||||
const parentMeta = this.#metadata.find(entities[0].constructor);
|
||||
const discriminatorValue =
|
||||
QueryHelper.findDiscriminatorValue(polymorphicOwnerProp.discriminatorMap, parentMeta.class) ??
|
||||
parentMeta.tableName;
|
||||
const discriminatorColumn = polymorphicOwnerProp.fieldNames[0];
|
||||
where = { $and: [where, { [discriminatorColumn]: discriminatorValue }] };
|
||||
}
|
||||
const fields = this.buildFields(options.fields, prop, ref);
|
||||
/* eslint-disable prefer-const */
|
||||
let {
|
||||
refresh,
|
||||
filters,
|
||||
convertCustomTypes,
|
||||
lockMode,
|
||||
strategy,
|
||||
populateWhere = 'infer',
|
||||
connectionType,
|
||||
logging,
|
||||
} = options;
|
||||
/* eslint-enable prefer-const */
|
||||
if (typeof populateWhere === 'object') {
|
||||
populateWhere = await this.extractChildCondition({ where: populateWhere }, prop);
|
||||
}
|
||||
if (!Utils.isEmpty(prop.where) || Raw.hasObjectFragments(prop.where)) {
|
||||
where = { $and: [where, prop.where] };
|
||||
}
|
||||
const orderBy = QueryHelper.mergeOrderBy(options.orderBy, prop.orderBy);
|
||||
const items = await this.#em.find(meta.class, where, {
|
||||
filters,
|
||||
convertCustomTypes,
|
||||
lockMode,
|
||||
populateWhere,
|
||||
logging,
|
||||
orderBy,
|
||||
populate: populate.children ?? populate.all ?? [],
|
||||
exclude: Array.isArray(options.exclude)
|
||||
? Utils.extractChildElements(options.exclude, prop.name)
|
||||
: options.exclude,
|
||||
strategy,
|
||||
fields,
|
||||
schema,
|
||||
connectionType,
|
||||
// @ts-ignore not a public option, will be propagated to the populate call
|
||||
refresh: refresh && !children.every(item => options.visited.has(item)),
|
||||
// @ts-ignore not a public option, will be propagated to the populate call
|
||||
visited: options.visited,
|
||||
});
|
||||
// For targetKey relations, wire up loaded entities to parent references
|
||||
// This is needed because the references were created under alternate key,
|
||||
// but loaded entities are stored under PK, so they don't automatically merge
|
||||
if (prop.targetKey && [ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind)) {
|
||||
const itemsByKey = new Map();
|
||||
for (const item of items) {
|
||||
itemsByKey.set('' + item[prop.targetKey], item);
|
||||
}
|
||||
for (const entity of entities) {
|
||||
const ref = entity[prop.name];
|
||||
/* v8 ignore next */
|
||||
if (!ref) {
|
||||
continue;
|
||||
}
|
||||
// oxfmt-ignore
|
||||
const keyValue = '' + (Reference.isReference(ref) ? ref.unwrap()[prop.targetKey] : ref[prop.targetKey]);
|
||||
const loadedItem = itemsByKey.get(keyValue);
|
||||
if (loadedItem) {
|
||||
entity[prop.name] = Reference.isReference(ref) ? Reference.create(loadedItem) : loadedItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ([ReferenceKind.ONE_TO_ONE, ReferenceKind.MANY_TO_ONE].includes(prop.kind) && items.length !== children.length) {
|
||||
const nullVal = this.#em.config.get('forceUndefined') ? undefined : null;
|
||||
const itemsMap = new Set();
|
||||
const childrenMap = new Set();
|
||||
// Use targetKey value if set, otherwise use serialized PK
|
||||
const getKey = e => (prop.targetKey ? '' + e[prop.targetKey] : helper(e).getSerializedPrimaryKey());
|
||||
for (const item of items) {
|
||||
/* v8 ignore next */
|
||||
itemsMap.add(getKey(item));
|
||||
}
|
||||
for (const child of children) {
|
||||
childrenMap.add(getKey(child));
|
||||
}
|
||||
for (const entity of entities) {
|
||||
const ref = entity[prop.name] ?? {};
|
||||
const key = helper(ref) ? getKey(ref) : undefined;
|
||||
if (key && childrenMap.has(key) && !itemsMap.has(key)) {
|
||||
entity[prop.name] = nullVal;
|
||||
helper(entity).__originalEntityData[prop.name] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const item of items) {
|
||||
if (ref && !helper(item).__onLoadFired) {
|
||||
helper(item).__initialized = false;
|
||||
this.#em.getUnitOfWork().unmarkAsLoaded(item);
|
||||
}
|
||||
}
|
||||
return { items, partial };
|
||||
}
|
||||
mergePrimaryCondition(ids, pk, options, meta, metadata, platform) {
|
||||
const cond1 = QueryHelper.processWhere({
|
||||
where: { [pk]: { $in: ids } },
|
||||
entityName: meta.class,
|
||||
metadata,
|
||||
platform,
|
||||
convertCustomTypes: !options.convertCustomTypes,
|
||||
});
|
||||
const where = { ...options.where };
|
||||
Utils.dropUndefinedProperties(where);
|
||||
return where[pk] ? { $and: [cond1, where] } : { ...cond1, ...where };
|
||||
}
|
||||
async populateField(entityName, entities, populate, options) {
|
||||
const field = populate.field.split(':')[0];
|
||||
const prop = this.#metadata.find(entityName).properties[field];
|
||||
if (prop.kind === ReferenceKind.SCALAR && !prop.lazy) {
|
||||
return;
|
||||
}
|
||||
options = { ...options, filters: QueryHelper.mergePropertyFilters(prop.filters, options.filters) };
|
||||
const populated = await this.populateMany(entityName, entities, populate, options);
|
||||
if (!populate.children && !populate.all) {
|
||||
return;
|
||||
}
|
||||
const children = [];
|
||||
for (const entity of entities) {
|
||||
const ref = entity[field];
|
||||
if (Utils.isEntity(ref)) {
|
||||
children.push(ref);
|
||||
} else if (Reference.isReference(ref)) {
|
||||
children.push(ref.unwrap());
|
||||
} else if (Utils.isCollection(ref)) {
|
||||
children.push(...ref.getItems());
|
||||
} else if (ref && prop.kind === ReferenceKind.EMBEDDED) {
|
||||
children.push(...Utils.asArray(ref));
|
||||
}
|
||||
}
|
||||
if (populated.length === 0 && !populate.children) {
|
||||
return;
|
||||
}
|
||||
const fields = this.buildFields(options.fields, prop);
|
||||
const innerOrderBy = Utils.asArray(options.orderBy)
|
||||
.filter(orderBy => Utils.isObject(orderBy[prop.name]))
|
||||
.map(orderBy => orderBy[prop.name]);
|
||||
const { refresh, filters, ignoreLazyScalarProperties, populateWhere, connectionType, logging, schema } = options;
|
||||
// oxfmt-ignore
|
||||
const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
|
||||
const visited = options.visited;
|
||||
for (const entity of entities) {
|
||||
visited.delete(entity);
|
||||
}
|
||||
const unique = Utils.unique(children);
|
||||
const filtered = unique.filter(e => !visited.has(e));
|
||||
for (const entity of entities) {
|
||||
visited.add(entity);
|
||||
}
|
||||
if (!prop.targetMeta) {
|
||||
return;
|
||||
}
|
||||
const populateChildren = async (targetMeta, items) => {
|
||||
await this.populate(targetMeta.class, items, populate.children ?? populate.all, {
|
||||
where: await this.extractChildCondition(options, prop, false),
|
||||
orderBy: innerOrderBy,
|
||||
fields,
|
||||
exclude,
|
||||
validate: false,
|
||||
lookup: false,
|
||||
filters,
|
||||
ignoreLazyScalarProperties,
|
||||
populateWhere,
|
||||
connectionType,
|
||||
logging,
|
||||
schema,
|
||||
// @ts-ignore not a public option, will be propagated to the populate call
|
||||
refresh: refresh && !filtered.every(item => options.visited.has(item)),
|
||||
// @ts-ignore not a public option, will be propagated to the populate call
|
||||
visited: options.visited,
|
||||
// @ts-ignore not a public option
|
||||
filtered,
|
||||
});
|
||||
};
|
||||
if (prop.polymorphic && prop.polymorphTargets) {
|
||||
await Promise.all(
|
||||
prop.polymorphTargets.map(async targetMeta => {
|
||||
const targetChildren = unique.filter(child => helper(child).__meta.className === targetMeta.className);
|
||||
if (targetChildren.length > 0) {
|
||||
await populateChildren(targetMeta, targetChildren);
|
||||
}
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
await populateChildren(prop.targetMeta, unique);
|
||||
}
|
||||
}
|
||||
/** @internal */
|
||||
async findChildrenFromPivotTable(filtered, prop, options, orderBy, populate, pivotJoin) {
|
||||
const ids = filtered.map(e => e.__helper.__primaryKeys);
|
||||
const refresh = options.refresh;
|
||||
let where = await this.extractChildCondition(options, prop, true);
|
||||
const fields = this.buildFields(options.fields, prop);
|
||||
// oxfmt-ignore
|
||||
const exclude = Array.isArray(options.exclude) ? Utils.extractChildElements(options.exclude, prop.name) : options.exclude;
|
||||
const populateFilter = options.populateFilter?.[prop.name];
|
||||
const options2 = { ...options, fields, exclude, populateFilter };
|
||||
['limit', 'offset', 'first', 'last', 'before', 'after', 'overfetch'].forEach(prop => delete options2[prop]);
|
||||
options2.populate = populate?.children ?? [];
|
||||
if (!Utils.isEmpty(prop.where)) {
|
||||
where = { $and: [where, prop.where] };
|
||||
}
|
||||
const map = await this.#driver.loadFromPivotTable(
|
||||
prop,
|
||||
ids,
|
||||
where,
|
||||
orderBy,
|
||||
this.#em.getTransactionContext(),
|
||||
options2,
|
||||
pivotJoin,
|
||||
);
|
||||
const children = [];
|
||||
for (let i = 0; i < filtered.length; i++) {
|
||||
const entity = filtered[i];
|
||||
const items = map[Utils.getPrimaryKeyHash(ids[i])].map(item => {
|
||||
if (pivotJoin) {
|
||||
return this.#em.getReference(prop.targetMeta.class, item, {
|
||||
convertCustomTypes: true,
|
||||
schema: options.schema ?? this.#em.config.get('schema'),
|
||||
});
|
||||
}
|
||||
const entity = this.#em.getEntityFactory().create(prop.targetMeta.class, item, {
|
||||
refresh,
|
||||
merge: true,
|
||||
convertCustomTypes: true,
|
||||
schema: options.schema ?? this.#em.config.get('schema'),
|
||||
});
|
||||
return this.#em.getUnitOfWork().register(entity, item, { refresh, loaded: true });
|
||||
});
|
||||
entity[prop.name].hydrate(items, true);
|
||||
children.push(items);
|
||||
}
|
||||
return children;
|
||||
}
|
||||
async extractChildCondition(options, prop, filters = false) {
|
||||
const where = options.where;
|
||||
const subCond = Utils.isPlainObject(where[prop.name]) ? where[prop.name] : {};
|
||||
const meta2 = prop.targetMeta;
|
||||
const pk = Utils.getPrimaryKeyHash(meta2.primaryKeys);
|
||||
['$and', '$or'].forEach(op => {
|
||||
if (where[op]) {
|
||||
const child = where[op]
|
||||
.map(cond => cond[prop.name])
|
||||
.filter(
|
||||
sub =>
|
||||
sub != null &&
|
||||
!(Utils.isPlainObject(sub) && Utils.getObjectQueryKeys(sub).every(key => Utils.isOperator(key, false))),
|
||||
)
|
||||
.map(cond => {
|
||||
if (Utils.isPrimaryKey(cond)) {
|
||||
return { [pk]: cond };
|
||||
}
|
||||
return cond;
|
||||
});
|
||||
if (child.length > 0) {
|
||||
subCond[op] = child;
|
||||
}
|
||||
}
|
||||
});
|
||||
const operators = Object.keys(subCond).filter(key => Utils.isOperator(key, false));
|
||||
if (operators.length > 0) {
|
||||
operators.forEach(op => {
|
||||
subCond[pk] ??= {};
|
||||
subCond[pk][op] = subCond[op];
|
||||
delete subCond[op];
|
||||
});
|
||||
}
|
||||
if (filters) {
|
||||
return this.#em.applyFilters(meta2.class, subCond, options.filters, 'read', options);
|
||||
}
|
||||
return subCond;
|
||||
}
|
||||
buildFields(fields = [], prop, ref) {
|
||||
if (ref) {
|
||||
fields = prop.targetMeta.primaryKeys.map(targetPkName => `${prop.name}.${targetPkName}`);
|
||||
}
|
||||
const ret = fields.reduce((ret, f) => {
|
||||
if (Utils.isPlainObject(f)) {
|
||||
Utils.keys(f)
|
||||
.filter(ff => ff === prop.name)
|
||||
.forEach(ff => ret.push(...f[ff]));
|
||||
} else if (f.toString().includes('.')) {
|
||||
const parts = f.toString().split('.');
|
||||
const propName = parts.shift();
|
||||
const childPropName = parts.join('.');
|
||||
/* v8 ignore next */
|
||||
if (propName === prop.name) {
|
||||
ret.push(childPropName);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}, []);
|
||||
// we need to automatically select the FKs too, e.g. for 1:m relations to be able to wire them with the items
|
||||
if (prop.kind === ReferenceKind.ONE_TO_MANY || prop.kind === ReferenceKind.MANY_TO_MANY) {
|
||||
const owner = prop.targetMeta.properties[prop.mappedBy];
|
||||
// when the owning FK is lazy, we need to explicitly select it even without user-provided fields,
|
||||
// otherwise the driver will exclude it and we won't be able to map children to their parent collections
|
||||
if (owner && !ret.includes(owner.name) && (ret.length > 0 || owner.lazy)) {
|
||||
ret.push(owner.name);
|
||||
}
|
||||
}
|
||||
if (ret.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
getChildReferences(entities, prop, options, ref) {
|
||||
const filtered = this.filterCollections(entities, prop.name, options, ref);
|
||||
if (prop.kind === ReferenceKind.ONE_TO_MANY) {
|
||||
return filtered.map(e => e[prop.name].owner);
|
||||
}
|
||||
if (prop.kind === ReferenceKind.MANY_TO_MANY && prop.owner) {
|
||||
return filtered.reduce((a, b) => {
|
||||
a.push(...b[prop.name].getItems());
|
||||
return a;
|
||||
}, []);
|
||||
}
|
||||
if (prop.kind === ReferenceKind.MANY_TO_MANY) {
|
||||
// inverse side
|
||||
return filtered;
|
||||
}
|
||||
// MANY_TO_ONE or ONE_TO_ONE
|
||||
return this.filterReferences(entities, prop.name, options, ref);
|
||||
}
|
||||
filterCollections(entities, field, options, ref) {
|
||||
if (options.refresh) {
|
||||
return entities.filter(e => e[field]);
|
||||
}
|
||||
return entities.filter(e => Utils.isCollection(e[field]) && !e[field].isInitialized(!ref));
|
||||
}
|
||||
isPropertyLoaded(entity, field) {
|
||||
if (!entity || field === '*') {
|
||||
return true;
|
||||
}
|
||||
const wrapped = helper(entity);
|
||||
if (!field.includes('.')) {
|
||||
return wrapped.__loadedProperties.has(field);
|
||||
}
|
||||
const [f, ...r] = field.split('.');
|
||||
/* v8 ignore next */
|
||||
if (!wrapped.__loadedProperties.has(f) || !wrapped.__meta.properties[f]?.targetMeta) {
|
||||
return false;
|
||||
}
|
||||
if ([ReferenceKind.ONE_TO_MANY, ReferenceKind.MANY_TO_MANY].includes(wrapped.__meta.properties[f].kind)) {
|
||||
return entity[f].getItems(false).every(item => this.isPropertyLoaded(item, r.join('.')));
|
||||
}
|
||||
return this.isPropertyLoaded(entity[f], r.join('.'));
|
||||
}
|
||||
filterReferences(entities, field, options, ref) {
|
||||
if (ref) {
|
||||
return [];
|
||||
}
|
||||
const children = entities.filter(e => Utils.isEntity(e[field], true));
|
||||
if (options.refresh) {
|
||||
return children.map(e => Reference.unwrapReference(e[field]));
|
||||
}
|
||||
if (options.fields) {
|
||||
return children
|
||||
.map(e => Reference.unwrapReference(e[field]))
|
||||
.filter(target => {
|
||||
const wrapped = helper(target);
|
||||
const childFields = options.fields
|
||||
.filter(f => f.startsWith(`${field}.`))
|
||||
.map(f => f.substring(field.length + 1));
|
||||
return !wrapped.__initialized || !childFields.every(cf => this.isPropertyLoaded(target, cf));
|
||||
});
|
||||
}
|
||||
return children.filter(e => !e[field].__helper.__initialized).map(e => Reference.unwrapReference(e[field]));
|
||||
}
|
||||
filterByReferences(entities, field, refresh) {
|
||||
/* v8 ignore next */
|
||||
if (refresh) {
|
||||
return entities;
|
||||
}
|
||||
return entities.filter(e => e[field] !== null && !e[field]?.__helper?.__initialized);
|
||||
}
|
||||
lookupAllRelationships(entityName) {
|
||||
const ret = [];
|
||||
const meta = this.#metadata.find(entityName);
|
||||
meta.relations.forEach(prop => {
|
||||
ret.push({
|
||||
field: this.getRelationName(meta, prop),
|
||||
// force select-in strategy when populating all relations as otherwise we could cause infinite loops when self-referencing
|
||||
strategy: LoadStrategy.SELECT_IN,
|
||||
// no need to look up populate children recursively as we just pass `all: true` here
|
||||
all: true,
|
||||
});
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
getRelationName(meta, prop) {
|
||||
if (!prop.embedded) {
|
||||
return prop.name;
|
||||
}
|
||||
return `${this.getRelationName(meta, meta.properties[prop.embedded[0]])}.${prop.embedded[1]}`;
|
||||
}
|
||||
lookupEagerLoadedRelationships(entityName, populate, strategy, prefix = '', visited = [], exclude) {
|
||||
const meta = this.#metadata.find(entityName);
|
||||
if (!meta && !prefix) {
|
||||
return populate;
|
||||
}
|
||||
if (!meta || visited.includes(meta)) {
|
||||
return [];
|
||||
}
|
||||
visited.push(meta);
|
||||
const ret = prefix === '' ? [...populate] : [];
|
||||
meta.relations
|
||||
.filter(prop => {
|
||||
const field = this.getRelationName(meta, prop);
|
||||
const prefixed = prefix ? `${prefix}.${field}` : field;
|
||||
const isExcluded = exclude?.includes(prefixed);
|
||||
const eager = prop.eager && !populate.some(p => p.field === `${prop.name}:ref`);
|
||||
const populated = populate.some(p => p.field === prop.name);
|
||||
const disabled = populate.some(p => p.field === prop.name && p.all === false);
|
||||
return !disabled && !isExcluded && (eager || populated);
|
||||
})
|
||||
.forEach(prop => {
|
||||
const field = this.getRelationName(meta, prop);
|
||||
const prefixed = prefix ? `${prefix}.${field}` : field;
|
||||
const nestedPopulate = populate
|
||||
.filter(p => p.field === prop.name)
|
||||
.flatMap(p => p.children)
|
||||
.filter(Boolean);
|
||||
const nested = this.lookupEagerLoadedRelationships(
|
||||
prop.targetMeta.class,
|
||||
nestedPopulate,
|
||||
strategy,
|
||||
prefixed,
|
||||
visited.slice(),
|
||||
exclude,
|
||||
);
|
||||
if (nested.length > 0) {
|
||||
ret.push(...nested);
|
||||
} else {
|
||||
const selfReferencing =
|
||||
[meta.tableName, ...visited.map(m => m.tableName)].includes(prop.targetMeta.tableName) && prop.eager;
|
||||
ret.push({
|
||||
field: prefixed,
|
||||
// enforce select-in strategy for self-referencing relations
|
||||
strategy: selfReferencing ? LoadStrategy.SELECT_IN : (strategy ?? prop.strategy),
|
||||
});
|
||||
}
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
324
node_modules/@mikro-orm/core/entity/EntityRepository.d.ts
generated
vendored
Normal file
324
node_modules/@mikro-orm/core/entity/EntityRepository.d.ts
generated
vendored
Normal file
@@ -0,0 +1,324 @@
|
||||
import type { PopulatePath } from '../enums.js';
|
||||
import type { CreateOptions, EntityManager, MergeOptions } from '../EntityManager.js';
|
||||
import type { AssignOptions } from './EntityAssigner.js';
|
||||
import type {
|
||||
EntityData,
|
||||
EntityName,
|
||||
Primary,
|
||||
Loaded,
|
||||
FilterQuery,
|
||||
EntityDictionary,
|
||||
AutoPath,
|
||||
RequiredEntityData,
|
||||
Ref,
|
||||
EntityType,
|
||||
EntityDTO,
|
||||
MergeSelected,
|
||||
FromEntityType,
|
||||
IsSubset,
|
||||
MergeLoaded,
|
||||
ArrayElement,
|
||||
} from '../typings.js';
|
||||
import type {
|
||||
CountOptions,
|
||||
DeleteOptions,
|
||||
FindAllOptions,
|
||||
FindByCursorOptions,
|
||||
FindOneOptions,
|
||||
FindOneOrFailOptions,
|
||||
FindOptions,
|
||||
GetReferenceOptions,
|
||||
NativeInsertUpdateOptions,
|
||||
StreamOptions,
|
||||
UpdateOptions,
|
||||
UpsertManyOptions,
|
||||
UpsertOptions,
|
||||
} from '../drivers/IDatabaseDriver.js';
|
||||
import type { EntityLoaderOptions } from './EntityLoader.js';
|
||||
import type { Cursor } from '../utils/Cursor.js';
|
||||
/** Repository class providing a type-safe API for querying and persisting a specific entity type. */
|
||||
export declare class EntityRepository<Entity extends object> {
|
||||
protected readonly em: EntityManager;
|
||||
protected readonly entityName: EntityName<Entity>;
|
||||
constructor(em: EntityManager, entityName: EntityName<Entity>);
|
||||
/**
|
||||
* Finds first entity matching your `where` query.
|
||||
*/
|
||||
findOne<Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(
|
||||
where: FilterQuery<Entity>,
|
||||
options?: FindOneOptions<Entity, Hint, Fields, Excludes>,
|
||||
): Promise<Loaded<Entity, Hint, Fields, Excludes> | null>;
|
||||
/**
|
||||
* Finds first entity matching your `where` query. If nothing is found, it will throw an error.
|
||||
* You can override the factory for creating this method via `options.failHandler` locally
|
||||
* or via `Configuration.findOneOrFailHandler` globally.
|
||||
*/
|
||||
findOneOrFail<Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(
|
||||
where: FilterQuery<Entity>,
|
||||
options?: FindOneOrFailOptions<Entity, Hint, Fields, Excludes>,
|
||||
): Promise<Loaded<Entity, Hint, Fields, Excludes>>;
|
||||
/**
|
||||
* Creates or updates the entity, based on whether it is already present in the database.
|
||||
* This method performs an `insert on conflict merge` query ensuring the database is in sync, returning a managed
|
||||
* entity instance. The method accepts either `entityName` together with the entity `data`, or just entity instance.
|
||||
*
|
||||
* ```ts
|
||||
* // insert into "author" ("age", "email") values (33, 'foo@bar.com') on conflict ("email") do update set "age" = 41
|
||||
* const author = await em.getRepository(Author).upsert({ email: 'foo@bar.com', age: 33 });
|
||||
* ```
|
||||
*
|
||||
* The entity data needs to contain either the primary key, or any other unique property. Let's consider the following example, where `Author.email` is a unique property:
|
||||
*
|
||||
* ```ts
|
||||
* // insert into "author" ("age", "email") values (33, 'foo@bar.com') on conflict ("email") do update set "age" = 41
|
||||
* // select "id" from "author" where "email" = 'foo@bar.com'
|
||||
* const author = await em.getRepository(Author).upsert({ email: 'foo@bar.com', age: 33 });
|
||||
* ```
|
||||
*
|
||||
* Depending on the driver support, this will either use a returning query, or a separate select query, to fetch the primary key if it's missing from the `data`.
|
||||
*
|
||||
* If the entity is already present in current context, there won't be any queries - instead, the entity data will be assigned and an explicit `flush` will be required for those changes to be persisted.
|
||||
*/
|
||||
upsert<Fields extends string = any>(
|
||||
entityOrData?: EntityData<Entity> | Entity,
|
||||
options?: UpsertOptions<Entity, Fields>,
|
||||
): Promise<Entity>;
|
||||
/**
|
||||
* Creates or updates the entity, based on whether it is already present in the database.
|
||||
* This method performs an `insert on conflict merge` query ensuring the database is in sync, returning a managed
|
||||
* entity instance.
|
||||
*
|
||||
* ```ts
|
||||
* // insert into "author" ("age", "email") values (33, 'foo@bar.com') on conflict ("email") do update set "age" = 41
|
||||
* const authors = await em.getRepository(Author).upsertMany([{ email: 'foo@bar.com', age: 33 }, ...]);
|
||||
* ```
|
||||
*
|
||||
* The entity data needs to contain either the primary key, or any other unique property. Let's consider the following example, where `Author.email` is a unique property:
|
||||
*
|
||||
* ```ts
|
||||
* // insert into "author" ("age", "email") values (33, 'foo@bar.com'), (666, 'lol@lol.lol') on conflict ("email") do update set "age" = excluded."age"
|
||||
* // select "id" from "author" where "email" = 'foo@bar.com'
|
||||
* const author = await em.getRepository(Author).upsertMany([
|
||||
* { email: 'foo@bar.com', age: 33 },
|
||||
* { email: 'lol@lol.lol', age: 666 },
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* Depending on the driver support, this will either use a returning query, or a separate select query, to fetch the primary key if it's missing from the `data`.
|
||||
*
|
||||
* If the entity is already present in current context, there won't be any queries - instead, the entity data will be assigned and an explicit `flush` will be required for those changes to be persisted.
|
||||
*/
|
||||
upsertMany<Fields extends string = any>(
|
||||
entitiesOrData?: EntityData<Entity>[] | Entity[],
|
||||
options?: UpsertManyOptions<Entity, Fields>,
|
||||
): Promise<Entity[]>;
|
||||
/**
|
||||
* Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
|
||||
*/
|
||||
find<Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(
|
||||
where: FilterQuery<Entity>,
|
||||
options?: FindOptions<Entity, Hint, Fields, Excludes>,
|
||||
): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
|
||||
/**
|
||||
* Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as tuple
|
||||
* where first element is the array of entities, and the second is the count.
|
||||
*/
|
||||
findAndCount<Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(
|
||||
where: FilterQuery<Entity>,
|
||||
options?: FindOptions<Entity, Hint, Fields, Excludes>,
|
||||
): Promise<[Loaded<Entity, Hint, Fields, Excludes>[], number]>;
|
||||
/**
|
||||
* @inheritDoc EntityManager.findByCursor
|
||||
*/
|
||||
findByCursor<
|
||||
Hint extends string = never,
|
||||
Fields extends string = '*',
|
||||
Excludes extends string = never,
|
||||
IncludeCount extends boolean = true,
|
||||
>(
|
||||
options: FindByCursorOptions<Entity, Hint, Fields, Excludes, IncludeCount>,
|
||||
): Promise<Cursor<Entity, Hint, Fields, Excludes, IncludeCount>>;
|
||||
/**
|
||||
* Finds all entities of given type. You can pass additional options via the `options` parameter.
|
||||
*/
|
||||
findAll<Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(
|
||||
options?: FindAllOptions<Entity, Hint, Fields, Excludes>,
|
||||
): Promise<Loaded<Entity, Hint, Fields, Excludes>[]>;
|
||||
/**
|
||||
* @inheritDoc EntityManager.stream
|
||||
*/
|
||||
stream<Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(
|
||||
options?: StreamOptions<Entity, Hint, Fields, Excludes>,
|
||||
): AsyncIterableIterator<Loaded<Entity, Hint, Fields, Excludes>>;
|
||||
/**
|
||||
* @inheritDoc EntityManager.insert
|
||||
*/
|
||||
insert(
|
||||
data: Entity | RequiredEntityData<Entity>,
|
||||
options?: NativeInsertUpdateOptions<Entity>,
|
||||
): Promise<Primary<Entity>>;
|
||||
/**
|
||||
* @inheritDoc EntityManager.insert
|
||||
*/
|
||||
insertMany(
|
||||
data: Entity[] | RequiredEntityData<Entity>[],
|
||||
options?: NativeInsertUpdateOptions<Entity>,
|
||||
): Promise<Primary<Entity>[]>;
|
||||
/**
|
||||
* Fires native update query. Calling this has no side effects on the context (identity map).
|
||||
*/
|
||||
nativeUpdate(where: FilterQuery<Entity>, data: EntityData<Entity>, options?: UpdateOptions<Entity>): Promise<number>;
|
||||
/**
|
||||
* Fires native delete query. Calling this has no side effects on the context (identity map).
|
||||
*/
|
||||
nativeDelete(where: FilterQuery<Entity>, options?: DeleteOptions<Entity>): Promise<number>;
|
||||
/**
|
||||
* Maps raw database result to an entity and merges it to this EntityManager.
|
||||
*/
|
||||
map(
|
||||
result: EntityDictionary<Entity>,
|
||||
options?: {
|
||||
schema?: string;
|
||||
},
|
||||
): Entity;
|
||||
/**
|
||||
* Gets a reference to the entity identified by the given type and alternate key property without actually loading it.
|
||||
* The key option specifies which property to use for identity map lookup instead of the primary key.
|
||||
*/
|
||||
getReference<K extends string & keyof Entity>(
|
||||
id: Entity[K],
|
||||
options: Omit<GetReferenceOptions, 'key' | 'wrapped'> & {
|
||||
key: K;
|
||||
wrapped: true;
|
||||
},
|
||||
): Ref<Entity>;
|
||||
/**
|
||||
* Gets a reference to the entity identified by the given type and alternate key property without actually loading it.
|
||||
* The key option specifies which property to use for identity map lookup instead of the primary key.
|
||||
*/
|
||||
getReference<K extends string & keyof Entity>(
|
||||
id: Entity[K],
|
||||
options: Omit<GetReferenceOptions, 'key'> & {
|
||||
key: K;
|
||||
wrapped?: false;
|
||||
},
|
||||
): Entity;
|
||||
/**
|
||||
* Gets a reference to the entity identified by the given type and identifier without actually loading it, if the entity is not yet loaded
|
||||
*/
|
||||
getReference(
|
||||
id: Primary<Entity>,
|
||||
options: Omit<GetReferenceOptions, 'wrapped' | 'key'> & {
|
||||
wrapped: true;
|
||||
},
|
||||
): Ref<Entity>;
|
||||
/**
|
||||
* Gets a reference to the entity identified by the given type and identifier without actually loading it, if the entity is not yet loaded
|
||||
*/
|
||||
getReference(id: Primary<Entity> | Primary<Entity>[]): Entity;
|
||||
/**
|
||||
* Gets a reference to the entity identified by the given type and identifier without actually loading it, if the entity is not yet loaded
|
||||
*/
|
||||
getReference(
|
||||
id: Primary<Entity>,
|
||||
options: Omit<GetReferenceOptions, 'wrapped' | 'key'> & {
|
||||
wrapped: false;
|
||||
},
|
||||
): Entity;
|
||||
/**
|
||||
* Checks whether given property can be populated on the entity.
|
||||
*/
|
||||
canPopulate(property: string): boolean;
|
||||
/**
|
||||
* Loads specified relations in batch. This will execute one query for each relation, that will populate it on all the specified entities.
|
||||
*/
|
||||
populate<
|
||||
Ent extends Entity | Entity[],
|
||||
Hint extends string = never,
|
||||
Naked extends FromEntityType<Entity> = FromEntityType<Entity>,
|
||||
Fields extends string = never,
|
||||
Excludes extends string = never,
|
||||
>(
|
||||
entities: Ent,
|
||||
populate: AutoPath<Naked, Hint, PopulatePath.ALL>[] | false,
|
||||
options?: EntityLoaderOptions<Naked, Fields, Excludes>,
|
||||
): Promise<
|
||||
Ent extends object[]
|
||||
? MergeLoaded<ArrayElement<Ent>, Naked, Hint, Fields, Excludes>[]
|
||||
: MergeLoaded<Ent, Naked, Hint, Fields, Excludes>
|
||||
>;
|
||||
/**
|
||||
* Creates new instance of given entity and populates it with given data.
|
||||
* The entity constructor will be used unless you provide `{ managed: true }` in the `options` parameter.
|
||||
* The constructor will be given parameters based on the defined constructor of the entity. If the constructor
|
||||
* parameter matches a property name, its value will be extracted from `data`. If no matching property exists,
|
||||
* the whole `data` parameter will be passed. This means we can also define `constructor(data: Partial<T>)` and
|
||||
* `em.create()` will pass the data into it (unless we have a property named `data` too).
|
||||
*
|
||||
* The parameters are strictly checked, you need to provide all required properties. You can use `OptionalProps`
|
||||
* symbol to omit some properties from this check without making them optional. Alternatively, use `partial: true`
|
||||
* in the options to disable the strict checks for required properties. This option has no effect on runtime.
|
||||
*
|
||||
* The newly created entity will be automatically marked for persistence via `em.persist` unless you disable this
|
||||
* behavior, either locally via `persist: false` option, or globally via `persistOnCreate` ORM config option.
|
||||
*/
|
||||
create<
|
||||
Convert extends boolean = false,
|
||||
Data extends RequiredEntityData<Entity, never, Convert> = RequiredEntityData<Entity, never, Convert>,
|
||||
>(data: Data & IsSubset<RequiredEntityData<Entity, never, Convert>, Data>, options?: CreateOptions<Convert>): Entity;
|
||||
/**
|
||||
* Creates new instance of given entity and populates it with given data.
|
||||
* The entity constructor will be used unless you provide `{ managed: true }` in the `options` parameter.
|
||||
* The constructor will be given parameters based on the defined constructor of the entity. If the constructor
|
||||
* parameter matches a property name, its value will be extracted from `data`. If no matching property exists,
|
||||
* the whole `data` parameter will be pass. This means we can also define `constructor(data: Partial<T>)` and
|
||||
* `em.create()` will pass the data into it (unless we have a property named `data` too).
|
||||
*
|
||||
* The parameters are strictly checked, you need to provide all required properties. You can use `OptionalProps`
|
||||
* symbol to omit some properties from this check without making them optional. Alternatively, use `partial: true`
|
||||
* in the options to disable the strict checks for required properties. This option has no effect on runtime.
|
||||
*
|
||||
* The newly created entity will be automatically marked for persistence via `em.persist` unless you disable this
|
||||
* behavior, either locally via `persist: false` option, or globally via `persistOnCreate` ORM config option.
|
||||
*/
|
||||
create<Convert extends boolean = false, Data extends EntityData<Entity, Convert> = EntityData<Entity, Convert>>(
|
||||
data: Data & IsSubset<EntityData<Entity, Convert>, Data>,
|
||||
options: CreateOptions<Convert> & {
|
||||
partial: true;
|
||||
},
|
||||
): Entity;
|
||||
/**
|
||||
* Shortcut for `wrap(entity).assign(data, { em })`
|
||||
*/
|
||||
assign<
|
||||
Ent extends EntityType<Entity>,
|
||||
Naked extends FromEntityType<Ent> = FromEntityType<Ent>,
|
||||
Convert extends boolean = false,
|
||||
Data extends EntityData<Naked, Convert> | Partial<EntityDTO<Naked>> =
|
||||
| EntityData<Naked, Convert>
|
||||
| Partial<EntityDTO<Naked>>,
|
||||
>(
|
||||
entity: Ent | Partial<Ent>,
|
||||
data: Data & IsSubset<EntityData<Naked, Convert>, Data>,
|
||||
options?: AssignOptions<Convert>,
|
||||
): MergeSelected<Ent, Naked, keyof Data & string>;
|
||||
/**
|
||||
* Merges given entity to this EntityManager so it becomes managed. You can force refreshing of existing entities
|
||||
* via second parameter. By default it will return already loaded entities without modifying them.
|
||||
*/
|
||||
merge(data: Entity | EntityData<Entity>, options?: MergeOptions): Entity;
|
||||
/**
|
||||
* Returns total number of entities matching your `where` query.
|
||||
*/
|
||||
count<Hint extends string = never>(
|
||||
where?: FilterQuery<Entity>,
|
||||
options?: CountOptions<Entity, Hint>,
|
||||
): Promise<number>;
|
||||
/** Returns the entity class name associated with this repository. */
|
||||
getEntityName(): string;
|
||||
/**
|
||||
* Returns the underlying EntityManager instance
|
||||
*/
|
||||
getEntityManager(): EntityManager;
|
||||
protected validateRepositoryType(entities: Entity[] | Entity, method: string): void;
|
||||
}
|
||||
218
node_modules/@mikro-orm/core/entity/EntityRepository.js
generated
vendored
Normal file
218
node_modules/@mikro-orm/core/entity/EntityRepository.js
generated
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
import { ValidationError } from '../errors.js';
|
||||
import { Utils } from '../utils/Utils.js';
|
||||
/** Repository class providing a type-safe API for querying and persisting a specific entity type. */
|
||||
export class EntityRepository {
|
||||
em;
|
||||
entityName;
|
||||
constructor(em, entityName) {
|
||||
this.em = em;
|
||||
this.entityName = entityName;
|
||||
}
|
||||
/**
|
||||
* Finds first entity matching your `where` query.
|
||||
*/
|
||||
async findOne(where, options) {
|
||||
return this.getEntityManager().findOne(this.entityName, where, options);
|
||||
}
|
||||
/**
|
||||
* Finds first entity matching your `where` query. If nothing is found, it will throw an error.
|
||||
* You can override the factory for creating this method via `options.failHandler` locally
|
||||
* or via `Configuration.findOneOrFailHandler` globally.
|
||||
*/
|
||||
async findOneOrFail(where, options) {
|
||||
return this.getEntityManager().findOneOrFail(this.entityName, where, options);
|
||||
}
|
||||
/**
|
||||
* Creates or updates the entity, based on whether it is already present in the database.
|
||||
* This method performs an `insert on conflict merge` query ensuring the database is in sync, returning a managed
|
||||
* entity instance. The method accepts either `entityName` together with the entity `data`, or just entity instance.
|
||||
*
|
||||
* ```ts
|
||||
* // insert into "author" ("age", "email") values (33, 'foo@bar.com') on conflict ("email") do update set "age" = 41
|
||||
* const author = await em.getRepository(Author).upsert({ email: 'foo@bar.com', age: 33 });
|
||||
* ```
|
||||
*
|
||||
* The entity data needs to contain either the primary key, or any other unique property. Let's consider the following example, where `Author.email` is a unique property:
|
||||
*
|
||||
* ```ts
|
||||
* // insert into "author" ("age", "email") values (33, 'foo@bar.com') on conflict ("email") do update set "age" = 41
|
||||
* // select "id" from "author" where "email" = 'foo@bar.com'
|
||||
* const author = await em.getRepository(Author).upsert({ email: 'foo@bar.com', age: 33 });
|
||||
* ```
|
||||
*
|
||||
* Depending on the driver support, this will either use a returning query, or a separate select query, to fetch the primary key if it's missing from the `data`.
|
||||
*
|
||||
* If the entity is already present in current context, there won't be any queries - instead, the entity data will be assigned and an explicit `flush` will be required for those changes to be persisted.
|
||||
*/
|
||||
async upsert(entityOrData, options) {
|
||||
return this.getEntityManager().upsert(this.entityName, entityOrData, options);
|
||||
}
|
||||
/**
|
||||
* Creates or updates the entity, based on whether it is already present in the database.
|
||||
* This method performs an `insert on conflict merge` query ensuring the database is in sync, returning a managed
|
||||
* entity instance.
|
||||
*
|
||||
* ```ts
|
||||
* // insert into "author" ("age", "email") values (33, 'foo@bar.com') on conflict ("email") do update set "age" = 41
|
||||
* const authors = await em.getRepository(Author).upsertMany([{ email: 'foo@bar.com', age: 33 }, ...]);
|
||||
* ```
|
||||
*
|
||||
* The entity data needs to contain either the primary key, or any other unique property. Let's consider the following example, where `Author.email` is a unique property:
|
||||
*
|
||||
* ```ts
|
||||
* // insert into "author" ("age", "email") values (33, 'foo@bar.com'), (666, 'lol@lol.lol') on conflict ("email") do update set "age" = excluded."age"
|
||||
* // select "id" from "author" where "email" = 'foo@bar.com'
|
||||
* const author = await em.getRepository(Author).upsertMany([
|
||||
* { email: 'foo@bar.com', age: 33 },
|
||||
* { email: 'lol@lol.lol', age: 666 },
|
||||
* ]);
|
||||
* ```
|
||||
*
|
||||
* Depending on the driver support, this will either use a returning query, or a separate select query, to fetch the primary key if it's missing from the `data`.
|
||||
*
|
||||
* If the entity is already present in current context, there won't be any queries - instead, the entity data will be assigned and an explicit `flush` will be required for those changes to be persisted.
|
||||
*/
|
||||
async upsertMany(entitiesOrData, options) {
|
||||
return this.getEntityManager().upsertMany(this.entityName, entitiesOrData, options);
|
||||
}
|
||||
/**
|
||||
* Finds all entities matching your `where` query. You can pass additional options via the `options` parameter.
|
||||
*/
|
||||
async find(where, options) {
|
||||
return this.getEntityManager().find(this.entityName, where, options);
|
||||
}
|
||||
/**
|
||||
* Calls `em.find()` and `em.count()` with the same arguments (where applicable) and returns the results as tuple
|
||||
* where first element is the array of entities, and the second is the count.
|
||||
*/
|
||||
async findAndCount(where, options) {
|
||||
return this.getEntityManager().findAndCount(this.entityName, where, options);
|
||||
}
|
||||
/**
|
||||
* @inheritDoc EntityManager.findByCursor
|
||||
*/
|
||||
async findByCursor(options) {
|
||||
return this.getEntityManager().findByCursor(this.entityName, options);
|
||||
}
|
||||
/**
|
||||
* Finds all entities of given type. You can pass additional options via the `options` parameter.
|
||||
*/
|
||||
async findAll(options) {
|
||||
return this.getEntityManager().findAll(this.entityName, options);
|
||||
}
|
||||
/**
|
||||
* @inheritDoc EntityManager.stream
|
||||
*/
|
||||
async *stream(options) {
|
||||
yield* this.getEntityManager().stream(this.entityName, options);
|
||||
}
|
||||
/**
|
||||
* @inheritDoc EntityManager.insert
|
||||
*/
|
||||
async insert(data, options) {
|
||||
return this.getEntityManager().insert(this.entityName, data, options);
|
||||
}
|
||||
/**
|
||||
* @inheritDoc EntityManager.insert
|
||||
*/
|
||||
async insertMany(data, options) {
|
||||
return this.getEntityManager().insertMany(this.entityName, data, options);
|
||||
}
|
||||
/**
|
||||
* Fires native update query. Calling this has no side effects on the context (identity map).
|
||||
*/
|
||||
async nativeUpdate(where, data, options) {
|
||||
return this.getEntityManager().nativeUpdate(this.entityName, where, data, options);
|
||||
}
|
||||
/**
|
||||
* Fires native delete query. Calling this has no side effects on the context (identity map).
|
||||
*/
|
||||
async nativeDelete(where, options) {
|
||||
return this.getEntityManager().nativeDelete(this.entityName, where, options);
|
||||
}
|
||||
/**
|
||||
* Maps raw database result to an entity and merges it to this EntityManager.
|
||||
*/
|
||||
map(result, options) {
|
||||
return this.getEntityManager().map(this.entityName, result, options);
|
||||
}
|
||||
/**
|
||||
* Gets a reference to the entity identified by the given type and identifier without actually loading it, if the entity is not yet loaded
|
||||
*/
|
||||
getReference(id, options) {
|
||||
return this.getEntityManager().getReference(this.entityName, id, options);
|
||||
}
|
||||
/**
|
||||
* Checks whether given property can be populated on the entity.
|
||||
*/
|
||||
canPopulate(property) {
|
||||
return this.getEntityManager().canPopulate(this.entityName, property);
|
||||
}
|
||||
/**
|
||||
* Loads specified relations in batch. This will execute one query for each relation, that will populate it on all the specified entities.
|
||||
*/
|
||||
async populate(entities, populate, options) {
|
||||
this.validateRepositoryType(entities, 'populate');
|
||||
// @ts-ignore hard to type
|
||||
return this.getEntityManager().populate(entities, populate, options);
|
||||
}
|
||||
/**
|
||||
* Creates new instance of given entity and populates it with given data.
|
||||
* The entity constructor will be used unless you provide `{ managed: true }` in the `options` parameter.
|
||||
* The constructor will be given parameters based on the defined constructor of the entity. If the constructor
|
||||
* parameter matches a property name, its value will be extracted from `data`. If no matching property exists,
|
||||
* the whole `data` parameter will be passed. This means we can also define `constructor(data: Partial<T>)` and
|
||||
* `em.create()` will pass the data into it (unless we have a property named `data` too).
|
||||
*
|
||||
* The parameters are strictly checked, you need to provide all required properties. You can use `OptionalProps`
|
||||
* symbol to omit some properties from this check without making them optional. Alternatively, use `partial: true`
|
||||
* in the options to disable the strict checks for required properties. This option has no effect on runtime.
|
||||
*
|
||||
* The newly created entity will be automatically marked for persistence via `em.persist` unless you disable this
|
||||
* behavior, either locally via `persist: false` option, or globally via `persistOnCreate` ORM config option.
|
||||
*/
|
||||
create(data, options) {
|
||||
return this.getEntityManager().create(this.entityName, data, options);
|
||||
}
|
||||
/**
|
||||
* Shortcut for `wrap(entity).assign(data, { em })`
|
||||
*/
|
||||
assign(entity, data, options) {
|
||||
this.validateRepositoryType(entity, 'assign');
|
||||
return this.getEntityManager().assign(entity, data, options);
|
||||
}
|
||||
/**
|
||||
* Merges given entity to this EntityManager so it becomes managed. You can force refreshing of existing entities
|
||||
* via second parameter. By default it will return already loaded entities without modifying them.
|
||||
*/
|
||||
merge(data, options) {
|
||||
return this.getEntityManager().merge(this.entityName, data, options);
|
||||
}
|
||||
/**
|
||||
* Returns total number of entities matching your `where` query.
|
||||
*/
|
||||
async count(where = {}, options = {}) {
|
||||
return this.getEntityManager().count(this.entityName, where, options);
|
||||
}
|
||||
/** Returns the entity class name associated with this repository. */
|
||||
getEntityName() {
|
||||
return Utils.className(this.entityName);
|
||||
}
|
||||
/**
|
||||
* Returns the underlying EntityManager instance
|
||||
*/
|
||||
getEntityManager() {
|
||||
return this.em;
|
||||
}
|
||||
validateRepositoryType(entities, method) {
|
||||
entities = Utils.asArray(entities);
|
||||
if (entities.length === 0) {
|
||||
return;
|
||||
}
|
||||
const entityName = entities[0].constructor.name;
|
||||
const repoType = Utils.className(this.entityName);
|
||||
if (entityName && repoType !== entityName) {
|
||||
throw ValidationError.fromWrongRepositoryType(entityName, repoType, method);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
node_modules/@mikro-orm/core/entity/PolymorphicRef.d.ts
generated
vendored
Normal file
12
node_modules/@mikro-orm/core/entity/PolymorphicRef.d.ts
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Wrapper class for polymorphic relation reference data.
|
||||
* Holds the discriminator value (type identifier) and the primary key value(s).
|
||||
* Used internally to track polymorphic FK values before hydration.
|
||||
*/
|
||||
export declare class PolymorphicRef {
|
||||
readonly discriminator: string;
|
||||
id: unknown;
|
||||
constructor(discriminator: string, id: unknown);
|
||||
/** Returns `[discriminator, ...idValues]` tuple suitable for column-level expansion. */
|
||||
toTuple(): unknown[];
|
||||
}
|
||||
18
node_modules/@mikro-orm/core/entity/PolymorphicRef.js
generated
vendored
Normal file
18
node_modules/@mikro-orm/core/entity/PolymorphicRef.js
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Utils } from '../utils/Utils.js';
|
||||
/**
|
||||
* Wrapper class for polymorphic relation reference data.
|
||||
* Holds the discriminator value (type identifier) and the primary key value(s).
|
||||
* Used internally to track polymorphic FK values before hydration.
|
||||
*/
|
||||
export class PolymorphicRef {
|
||||
discriminator;
|
||||
id;
|
||||
constructor(discriminator, id) {
|
||||
this.discriminator = discriminator;
|
||||
this.id = id;
|
||||
}
|
||||
/** Returns `[discriminator, ...idValues]` tuple suitable for column-level expansion. */
|
||||
toTuple() {
|
||||
return [this.discriminator, ...Utils.asArray(this.id)];
|
||||
}
|
||||
}
|
||||
148
node_modules/@mikro-orm/core/entity/Reference.d.ts
generated
vendored
Normal file
148
node_modules/@mikro-orm/core/entity/Reference.d.ts
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
||||
import type {
|
||||
AddEager,
|
||||
AddOptional,
|
||||
Dictionary,
|
||||
EntityClass,
|
||||
EntityKey,
|
||||
EntityProperty,
|
||||
Loaded,
|
||||
LoadedReference,
|
||||
Primary,
|
||||
Ref,
|
||||
} from '../typings.js';
|
||||
import type { FindOneOptions, FindOneOrFailOptions } from '../drivers/IDatabaseDriver.js';
|
||||
/** Wrapper around an entity that provides lazy loading capabilities and identity-preserving reference semantics. */
|
||||
export declare class Reference<T extends object> {
|
||||
private entity;
|
||||
private property?;
|
||||
constructor(entity: T);
|
||||
/** Creates a Reference wrapper for the given entity, preserving identity if one already exists. */
|
||||
static create<T extends object>(entity: T | Ref<T>): Ref<T>;
|
||||
/** Creates a Reference wrapper for an entity identified by its primary key, wrapped in a Ref. */
|
||||
static createFromPK<T extends object>(
|
||||
entityType: EntityClass<T>,
|
||||
pk: Primary<T>,
|
||||
options?: {
|
||||
schema?: string;
|
||||
},
|
||||
): Ref<T>;
|
||||
/** Creates an uninitialized entity reference by primary key without wrapping it in a Reference. */
|
||||
static createNakedFromPK<T extends object>(
|
||||
entityType: EntityClass<T>,
|
||||
pk: Primary<T>,
|
||||
options?: {
|
||||
schema?: string;
|
||||
},
|
||||
): T;
|
||||
/**
|
||||
* Checks whether the argument is instance of `Reference` wrapper.
|
||||
*/
|
||||
static isReference<T extends object>(data: any): data is Reference<T>;
|
||||
/**
|
||||
* Wraps the entity in a `Reference` wrapper if the property is defined as `ref`.
|
||||
*/
|
||||
static wrapReference<T extends object, O extends object>(
|
||||
entity: T | Reference<T>,
|
||||
prop: EntityProperty<O, T>,
|
||||
): Reference<T> | T;
|
||||
/**
|
||||
* Returns wrapped entity.
|
||||
*/
|
||||
static unwrapReference<T extends object>(ref: T | Reference<T> | ScalarReference<T> | Ref<T>): T;
|
||||
/**
|
||||
* Ensures the underlying entity is loaded first (without reloading it if it already is loaded). Returns the entity.
|
||||
* If the entity is not found in the database (e.g. it was deleted in the meantime, or currently active filters disallow loading of it)
|
||||
* the method returns `null`. Use `loadOrFail()` if you want an error to be thrown in such a case.
|
||||
*/
|
||||
load<TT extends T, P extends string = never, F extends string = '*', E extends string = never>(
|
||||
options?: LoadReferenceOptions<TT, P, F, E>,
|
||||
): Promise<Loaded<TT, P, F, E> | null>;
|
||||
/**
|
||||
* Ensures the underlying entity is loaded first (without reloading it if it already is loaded).
|
||||
* Returns the entity or throws an error just like `em.findOneOrFail()` (and respects the same config options).
|
||||
*/
|
||||
loadOrFail<TT extends T, P extends string = never, F extends string = '*', E extends string = never>(
|
||||
options?: LoadReferenceOrFailOptions<TT, P, F, E>,
|
||||
): Promise<Loaded<TT, P, F, E>>;
|
||||
private set;
|
||||
/** Returns the underlying entity without checking initialization state. */
|
||||
unwrap(): T;
|
||||
/** Returns the underlying entity, throwing an error if the reference is not initialized. */
|
||||
getEntity(): T;
|
||||
/** Returns the value of a property on the underlying entity. Throws if the reference is not initialized. */
|
||||
getProperty<K extends keyof T>(prop: K): T[K];
|
||||
/** Loads the entity if needed, then returns the value of the specified property. */
|
||||
loadProperty<TT extends T, P extends string = never, K extends keyof TT = keyof TT>(
|
||||
prop: K,
|
||||
options?: LoadReferenceOrFailOptions<TT, P>,
|
||||
): Promise<Loaded<TT, P>[K]>;
|
||||
/** Returns whether the underlying entity has been fully loaded from the database. */
|
||||
isInitialized(): boolean;
|
||||
/** Marks the underlying entity as populated or not for serialization purposes. */
|
||||
populated(populated?: boolean): void;
|
||||
/** Serializes the underlying entity to a plain JSON object. */
|
||||
toJSON(...args: any[]): Dictionary;
|
||||
}
|
||||
/** Wrapper for lazy scalar properties that provides on-demand loading from the database. */
|
||||
export declare class ScalarReference<Value> {
|
||||
#private;
|
||||
private value?;
|
||||
private entity?;
|
||||
constructor(value?: Value | undefined, initialized?: boolean);
|
||||
/**
|
||||
* Ensures the underlying entity is loaded first (without reloading it if it already is loaded).
|
||||
* Returns either the whole entity, or the requested property.
|
||||
*/
|
||||
load(options?: Omit<LoadReferenceOptions<any, any>, 'populate' | 'fields' | 'exclude'>): Promise<Value | undefined>;
|
||||
/**
|
||||
* Ensures the underlying entity is loaded first (without reloading it if it already is loaded).
|
||||
* Returns the entity or throws an error just like `em.findOneOrFail()` (and respects the same config options).
|
||||
*/
|
||||
loadOrFail(options?: Omit<LoadReferenceOrFailOptions<any, any>, 'populate' | 'fields' | 'exclude'>): Promise<Value>;
|
||||
/** Sets the scalar value and marks the reference as initialized. */
|
||||
set(value: Value): void;
|
||||
/** Binds this scalar reference to a specific entity and property for lazy loading support. */
|
||||
bind<Entity extends object>(entity: Entity, property: EntityKey<Entity>): void;
|
||||
/** Returns the current scalar value, or undefined if not yet loaded. */
|
||||
unwrap(): Value | undefined;
|
||||
/** Returns whether the scalar value has been loaded. */
|
||||
isInitialized(): boolean;
|
||||
}
|
||||
/** Options for `Reference.load()` to control how the referenced entity is loaded. */
|
||||
export interface LoadReferenceOptions<
|
||||
T extends object,
|
||||
P extends string = never,
|
||||
F extends string = '*',
|
||||
E extends string = never,
|
||||
> extends FindOneOptions<T, P, F, E> {
|
||||
/** Whether to use the dataloader for batching reference loads. */
|
||||
dataloader?: boolean;
|
||||
}
|
||||
/** Options for `Reference.loadOrFail()` which throws when the entity is not found. */
|
||||
export interface LoadReferenceOrFailOptions<
|
||||
T extends object,
|
||||
P extends string = never,
|
||||
F extends string = '*',
|
||||
E extends string = never,
|
||||
> extends FindOneOrFailOptions<T, P, F, E> {
|
||||
/** Whether to use the dataloader for batching reference loads. */
|
||||
dataloader?: boolean;
|
||||
}
|
||||
/**
|
||||
* shortcut for `wrap(entity).toReference()`
|
||||
*/
|
||||
export declare function ref<I extends unknown | Ref<unknown> | undefined | null, T extends I & {}>(
|
||||
entity: I,
|
||||
): (Ref<T> & LoadedReference<Loaded<T, AddEager<T>>>) | AddOptional<typeof entity>;
|
||||
/**
|
||||
* shortcut for `Reference.createFromPK(entityType, pk)`
|
||||
*/
|
||||
export declare function ref<I extends unknown | undefined | null, T, PKV extends Primary<T> = Primary<T>>(
|
||||
entityType: EntityClass<T>,
|
||||
pk: I,
|
||||
): Ref<T> | AddOptional<typeof pk>;
|
||||
/**
|
||||
* shortcut for `Reference.createNakedFromPK(entityType, pk)`
|
||||
*/
|
||||
export declare function rel<T, PK extends Primary<T>>(entityType: EntityClass<T>, pk: T | PK): T;
|
||||
export { Reference as Ref };
|
||||
312
node_modules/@mikro-orm/core/entity/Reference.js
generated
vendored
Normal file
312
node_modules/@mikro-orm/core/entity/Reference.js
generated
vendored
Normal file
@@ -0,0 +1,312 @@
|
||||
import { DataloaderType } from '../enums.js';
|
||||
import { helper, wrap } from './wrap.js';
|
||||
import { Utils } from '../utils/Utils.js';
|
||||
import { QueryHelper } from '../utils/QueryHelper.js';
|
||||
import { NotFoundError } from '../errors.js';
|
||||
import { inspect } from '../logging/inspect.js';
|
||||
/** Wrapper around an entity that provides lazy loading capabilities and identity-preserving reference semantics. */
|
||||
export class Reference {
|
||||
entity;
|
||||
property;
|
||||
constructor(entity) {
|
||||
this.entity = entity;
|
||||
this.set(entity);
|
||||
const meta = helper(this.entity).__meta;
|
||||
meta.primaryKeys.forEach(primaryKey => {
|
||||
Object.defineProperty(this, primaryKey, {
|
||||
get() {
|
||||
return this.entity[primaryKey];
|
||||
},
|
||||
});
|
||||
});
|
||||
if (meta.serializedPrimaryKey && meta.primaryKeys[0] !== meta.serializedPrimaryKey) {
|
||||
Object.defineProperty(this, meta.serializedPrimaryKey, {
|
||||
get() {
|
||||
return helper(this.entity).getSerializedPrimaryKey();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
/** Creates a Reference wrapper for the given entity, preserving identity if one already exists. */
|
||||
static create(entity) {
|
||||
const unwrapped = Reference.unwrapReference(entity);
|
||||
const ref = helper(entity).toReference();
|
||||
if (unwrapped !== ref.unwrap()) {
|
||||
ref.set(unwrapped);
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
/** Creates a Reference wrapper for an entity identified by its primary key, wrapped in a Ref. */
|
||||
static createFromPK(entityType, pk, options) {
|
||||
const ref = this.createNakedFromPK(entityType, pk, options);
|
||||
return helper(ref)?.toReference() ?? ref;
|
||||
}
|
||||
/** Creates an uninitialized entity reference by primary key without wrapping it in a Reference. */
|
||||
static createNakedFromPK(entityType, pk, options) {
|
||||
const factory = entityType.prototype.__factory;
|
||||
if (!factory) {
|
||||
// this can happen only if `ref()` is used as a property initializer, and the value is important only for the
|
||||
// inference of defaults, so it's fine to return it directly without wrapping with `Reference` class
|
||||
return pk;
|
||||
}
|
||||
const entity = factory.createReference(entityType, pk, {
|
||||
merge: false,
|
||||
convertCustomTypes: false,
|
||||
...options,
|
||||
});
|
||||
const wrapped = helper(entity);
|
||||
wrapped.__meta.primaryKeys.forEach(key => wrapped.__loadedProperties.add(key));
|
||||
wrapped.__originalEntityData = factory.getComparator().prepareEntity(entity);
|
||||
return entity;
|
||||
}
|
||||
/**
|
||||
* Checks whether the argument is instance of `Reference` wrapper.
|
||||
*/
|
||||
static isReference(data) {
|
||||
return data && !!data.__reference;
|
||||
}
|
||||
/**
|
||||
* Wraps the entity in a `Reference` wrapper if the property is defined as `ref`.
|
||||
*/
|
||||
static wrapReference(entity, prop) {
|
||||
if (entity && prop.ref && !Reference.isReference(entity)) {
|
||||
const ref = Reference.create(entity);
|
||||
ref.property = prop;
|
||||
return ref;
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
/**
|
||||
* Returns wrapped entity.
|
||||
*/
|
||||
static unwrapReference(ref) {
|
||||
return Reference.isReference(ref) ? ref.unwrap() : ref;
|
||||
}
|
||||
/**
|
||||
* Ensures the underlying entity is loaded first (without reloading it if it already is loaded). Returns the entity.
|
||||
* If the entity is not found in the database (e.g. it was deleted in the meantime, or currently active filters disallow loading of it)
|
||||
* the method returns `null`. Use `loadOrFail()` if you want an error to be thrown in such a case.
|
||||
*/
|
||||
async load(options = {}) {
|
||||
const wrapped = helper(this.entity);
|
||||
if (!wrapped.__em) {
|
||||
return this.entity;
|
||||
}
|
||||
options = { ...options, filters: QueryHelper.mergePropertyFilters(this.property?.filters, options.filters) };
|
||||
if (this.isInitialized() && !options.refresh && options.populate) {
|
||||
await wrapped.__em.populate(this.entity, options.populate, options);
|
||||
}
|
||||
if (!this.isInitialized() || options.refresh) {
|
||||
if (
|
||||
options.dataloader ??
|
||||
[DataloaderType.ALL, DataloaderType.REFERENCE].includes(wrapped.__em.config.getDataloaderType())
|
||||
) {
|
||||
const dataLoader = await wrapped.__em.getDataLoader('ref');
|
||||
return dataLoader.load([this, options]);
|
||||
}
|
||||
return wrapped.init(options);
|
||||
}
|
||||
return this.entity;
|
||||
}
|
||||
/**
|
||||
* Ensures the underlying entity is loaded first (without reloading it if it already is loaded).
|
||||
* Returns the entity or throws an error just like `em.findOneOrFail()` (and respects the same config options).
|
||||
*/
|
||||
async loadOrFail(options = {}) {
|
||||
const ret = await this.load(options);
|
||||
if (!ret) {
|
||||
const wrapped = helper(this.entity);
|
||||
options.failHandler ??= wrapped.__em.config.get('findOneOrFailHandler');
|
||||
const entityName = this.entity.constructor.name;
|
||||
const where = wrapped.getPrimaryKey();
|
||||
throw options.failHandler(entityName, where);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
set(entity) {
|
||||
this.entity = Reference.unwrapReference(entity);
|
||||
delete helper(this.entity).__reference;
|
||||
}
|
||||
/** Returns the underlying entity without checking initialization state. */
|
||||
unwrap() {
|
||||
return this.entity;
|
||||
}
|
||||
/** Returns the underlying entity, throwing an error if the reference is not initialized. */
|
||||
getEntity() {
|
||||
if (!this.isInitialized()) {
|
||||
throw new Error(
|
||||
`Reference<${helper(this.entity).__meta.name}> ${helper(this.entity).getPrimaryKey()} not initialized`,
|
||||
);
|
||||
}
|
||||
return this.entity;
|
||||
}
|
||||
/** Returns the value of a property on the underlying entity. Throws if the reference is not initialized. */
|
||||
getProperty(prop) {
|
||||
return this.getEntity()[prop];
|
||||
}
|
||||
/** Loads the entity if needed, then returns the value of the specified property. */
|
||||
async loadProperty(prop, options) {
|
||||
await this.loadOrFail(options);
|
||||
return this.getEntity()[prop];
|
||||
}
|
||||
/** Returns whether the underlying entity has been fully loaded from the database. */
|
||||
isInitialized() {
|
||||
return helper(this.entity).__initialized;
|
||||
}
|
||||
/** Marks the underlying entity as populated or not for serialization purposes. */
|
||||
populated(populated) {
|
||||
helper(this.entity).populated(populated);
|
||||
}
|
||||
/** Serializes the underlying entity to a plain JSON object. */
|
||||
toJSON(...args) {
|
||||
return wrap(this.entity).toJSON(...args);
|
||||
}
|
||||
/** @ignore */
|
||||
[Symbol.for('nodejs.util.inspect.custom')](depth = 2) {
|
||||
const object = { ...this };
|
||||
const hidden = ['meta', 'property'];
|
||||
hidden.forEach(k => delete object[k]);
|
||||
const ret = inspect(object, { depth });
|
||||
const wrapped = helper(this.entity);
|
||||
const meta = wrapped.__meta;
|
||||
/* v8 ignore next */
|
||||
const pk = wrapped.hasPrimaryKey() ? '<' + wrapped.getSerializedPrimaryKey() + '>' : '';
|
||||
const name = `Ref<${meta.className}${pk}>`;
|
||||
return ret === '[Object]' ? `[${name}]` : name + ' ' + ret;
|
||||
}
|
||||
}
|
||||
/** Wrapper for lazy scalar properties that provides on-demand loading from the database. */
|
||||
export class ScalarReference {
|
||||
value;
|
||||
entity;
|
||||
#property;
|
||||
#initialized;
|
||||
constructor(value, initialized = value != null) {
|
||||
this.value = value;
|
||||
this.#initialized = initialized;
|
||||
}
|
||||
/**
|
||||
* Ensures the underlying entity is loaded first (without reloading it if it already is loaded).
|
||||
* Returns either the whole entity, or the requested property.
|
||||
*/
|
||||
async load(options) {
|
||||
const opts = typeof options === 'object' ? options : { prop: options };
|
||||
if (!this.#initialized || opts.refresh) {
|
||||
if (this.entity == null || this.#property == null) {
|
||||
throw new Error('Cannot load scalar reference that is not bound to an entity property.');
|
||||
}
|
||||
await helper(this.entity).populate([this.#property], opts);
|
||||
}
|
||||
return this.value;
|
||||
}
|
||||
/**
|
||||
* Ensures the underlying entity is loaded first (without reloading it if it already is loaded).
|
||||
* Returns the entity or throws an error just like `em.findOneOrFail()` (and respects the same config options).
|
||||
*/
|
||||
async loadOrFail(options = {}) {
|
||||
const ret = await this.load(options);
|
||||
if (ret == null) {
|
||||
const wrapped = helper(this.entity);
|
||||
options.failHandler ??= wrapped.__em.config.get('findOneOrFailHandler');
|
||||
const entityName = this.entity.constructor.name;
|
||||
throw NotFoundError.failedToLoadProperty(entityName, this.#property, wrapped.getPrimaryKey());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
/** Sets the scalar value and marks the reference as initialized. */
|
||||
set(value) {
|
||||
this.value = value;
|
||||
this.#initialized = true;
|
||||
}
|
||||
/** Binds this scalar reference to a specific entity and property for lazy loading support. */
|
||||
bind(entity, property) {
|
||||
this.entity = entity;
|
||||
this.#property = property;
|
||||
Object.defineProperty(this, 'entity', { enumerable: false, value: entity });
|
||||
}
|
||||
/** Returns the current scalar value, or undefined if not yet loaded. */
|
||||
unwrap() {
|
||||
return this.value;
|
||||
}
|
||||
/** Returns whether the scalar value has been loaded. */
|
||||
isInitialized() {
|
||||
return this.#initialized;
|
||||
}
|
||||
/** @ignore */
|
||||
/* v8 ignore next */
|
||||
[Symbol.for('nodejs.util.inspect.custom')]() {
|
||||
return this.#initialized ? `Ref<${inspect(this.value)}>` : `Ref<?>`;
|
||||
}
|
||||
}
|
||||
Object.defineProperties(Reference.prototype, {
|
||||
__reference: { value: true, enumerable: false },
|
||||
__meta: {
|
||||
get() {
|
||||
return this.entity.__meta;
|
||||
},
|
||||
},
|
||||
__platform: {
|
||||
get() {
|
||||
return this.entity.__platform;
|
||||
},
|
||||
},
|
||||
__helper: {
|
||||
get() {
|
||||
return this.entity.__helper;
|
||||
},
|
||||
},
|
||||
$: {
|
||||
get() {
|
||||
return this.entity;
|
||||
},
|
||||
},
|
||||
get: {
|
||||
get() {
|
||||
return () => this.entity;
|
||||
},
|
||||
},
|
||||
});
|
||||
Object.defineProperties(ScalarReference.prototype, {
|
||||
__scalarReference: { value: true, enumerable: false },
|
||||
$: {
|
||||
get() {
|
||||
return this.value;
|
||||
},
|
||||
},
|
||||
get: {
|
||||
get() {
|
||||
return () => this.value;
|
||||
},
|
||||
},
|
||||
});
|
||||
/**
|
||||
* shortcut for `wrap(entity).toReference()`
|
||||
*/
|
||||
export function ref(entityOrType, pk) {
|
||||
if (entityOrType == null) {
|
||||
return entityOrType;
|
||||
}
|
||||
if (Utils.isEntity(entityOrType, true)) {
|
||||
return helper(entityOrType).toReference();
|
||||
}
|
||||
if (Utils.isEntity(pk, true)) {
|
||||
return helper(pk).toReference();
|
||||
}
|
||||
if (arguments.length === 1) {
|
||||
return new ScalarReference(entityOrType, true);
|
||||
}
|
||||
if (pk == null) {
|
||||
return pk;
|
||||
}
|
||||
return Reference.createFromPK(entityOrType, pk);
|
||||
}
|
||||
/**
|
||||
* shortcut for `Reference.createNakedFromPK(entityType, pk)`
|
||||
*/
|
||||
export function rel(entityType, pk) {
|
||||
if (pk == null || Utils.isEntity(pk)) {
|
||||
return pk;
|
||||
}
|
||||
return Reference.createNakedFromPK(entityType, pk);
|
||||
}
|
||||
export { Reference as Ref };
|
||||
129
node_modules/@mikro-orm/core/entity/WrappedEntity.d.ts
generated
vendored
Normal file
129
node_modules/@mikro-orm/core/entity/WrappedEntity.d.ts
generated
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
import type { PopulatePath } from '../enums.js';
|
||||
import type { EntityManager } from '../EntityManager.js';
|
||||
import type {
|
||||
Dictionary,
|
||||
EntityData,
|
||||
EntityDictionary,
|
||||
EntityMetadata,
|
||||
IHydrator,
|
||||
EntityKey,
|
||||
PopulateOptions,
|
||||
Primary,
|
||||
AutoPath,
|
||||
Ref,
|
||||
AddEager,
|
||||
LoadedReference,
|
||||
EntityDTO,
|
||||
Loaded,
|
||||
SerializeDTO,
|
||||
FromEntityType,
|
||||
IsSubset,
|
||||
MergeSelected,
|
||||
} from '../typings.js';
|
||||
import { Reference } from './Reference.js';
|
||||
import { type AssignOptions } from './EntityAssigner.js';
|
||||
import type { EntityLoaderOptions } from './EntityLoader.js';
|
||||
import type { EntityIdentifier } from './EntityIdentifier.js';
|
||||
import type { SerializationContext } from '../serialization/SerializationContext.js';
|
||||
import { type SerializeOptions } from '../serialization/EntitySerializer.js';
|
||||
import type { FindOneOptions, LoadHint } from '../drivers/IDatabaseDriver.js';
|
||||
import type { Platform } from '../platforms/Platform.js';
|
||||
import type { Configuration } from '../utils/Configuration.js';
|
||||
/** @internal Wrapper attached to every managed entity, holding ORM state such as initialization flags, identity map references, and change tracking snapshots. */
|
||||
export declare class WrappedEntity<Entity extends object> {
|
||||
__initialized: boolean;
|
||||
__populated?: boolean;
|
||||
__managed?: boolean;
|
||||
__onLoadFired?: boolean;
|
||||
__schema?: string;
|
||||
__em?: EntityManager;
|
||||
__loadedProperties: Set<string>;
|
||||
__data: Dictionary;
|
||||
__processing: boolean;
|
||||
__serializationContext: {
|
||||
root?: SerializationContext<Entity>;
|
||||
populate?: PopulateOptions<Entity>[];
|
||||
fields?: Set<string>;
|
||||
exclude?: readonly string[];
|
||||
};
|
||||
/** stores last known primary key, as its current state might be broken due to propagation/orphan removal, but we need to know the PK to be able t remove the entity */
|
||||
__pk?: Primary<Entity>;
|
||||
/** holds the reference wrapper instance (if created), so we can maintain the identity on reference wrappers too */
|
||||
__reference?: Reference<Entity>;
|
||||
/** holds last entity data snapshot, so we can compute changes when persisting managed entities */
|
||||
__originalEntityData?: EntityData<Entity>;
|
||||
/** holds wrapped primary key, so we can compute change set without eager commit */
|
||||
__identifier?: EntityIdentifier;
|
||||
private readonly entity;
|
||||
private readonly hydrator;
|
||||
private readonly pkGetter?;
|
||||
private readonly pkSerializer?;
|
||||
private readonly pkGetterConverted?;
|
||||
constructor(
|
||||
entity: Entity,
|
||||
hydrator: IHydrator,
|
||||
pkGetter?: (e: Entity) => Primary<Entity>,
|
||||
pkSerializer?: (e: Entity) => string,
|
||||
pkGetterConverted?: (e: Entity) => Primary<Entity>,
|
||||
);
|
||||
/** Returns whether the entity has been fully loaded from the database. */
|
||||
isInitialized(): boolean;
|
||||
/** Returns whether the entity is managed by an EntityManager (tracked in the identity map). */
|
||||
isManaged(): boolean;
|
||||
/** Marks the entity as populated or not for serialization purposes. */
|
||||
populated(populated?: boolean | undefined): void;
|
||||
/** Sets the serialization context with populate hints, field selections, and exclusions. */
|
||||
setSerializationContext<Hint extends string = never, Fields extends string = '*', Exclude extends string = never>(
|
||||
options: LoadHint<Entity, Hint, Fields, Exclude>,
|
||||
): void;
|
||||
/** Returns a Reference wrapper for this entity, creating one if it does not already exist. */
|
||||
toReference(): Ref<Entity> & LoadedReference<Loaded<Entity, AddEager<Entity>>>;
|
||||
/** Converts the entity to a plain object representation, optionally excluding specified fields. */
|
||||
toObject<Ignored extends EntityKey<Entity> = never>(ignoreFields?: Ignored[]): Omit<EntityDTO<Entity>, Ignored>;
|
||||
/** Serializes the entity with control over which relations and fields to include or exclude. */
|
||||
serialize<Hint extends string = never, Exclude extends string = never>(
|
||||
options?: SerializeOptions<Entity, Hint, Exclude>,
|
||||
): SerializeDTO<Entity, Hint, Exclude>;
|
||||
/** Converts the entity to a plain object, including all properties regardless of serialization rules. */
|
||||
toPOJO(): EntityDTO<Entity>;
|
||||
/** Serializes the entity using its `toJSON` method (supports `JSON.stringify`). */
|
||||
toJSON(...args: any[]): EntityDictionary<Entity>;
|
||||
/** Assigns the given data to this entity, updating its properties and relations. */
|
||||
assign<
|
||||
Naked extends FromEntityType<Entity> = FromEntityType<Entity>,
|
||||
Convert extends boolean = false,
|
||||
Data extends EntityData<Naked, Convert> | Partial<EntityDTO<Naked>> =
|
||||
| EntityData<Naked, Convert>
|
||||
| Partial<EntityDTO<Naked>>,
|
||||
>(
|
||||
data: Data & IsSubset<EntityData<Naked>, Data>,
|
||||
options?: AssignOptions<Convert>,
|
||||
): MergeSelected<Entity, Naked, keyof Data & string>;
|
||||
/** Initializes (refreshes) the entity by reloading it from the database. Returns null if not found. */
|
||||
init<Hint extends string = never, Fields extends string = '*', Excludes extends string = never>(
|
||||
options?: FindOneOptions<Entity, Hint, Fields, Excludes>,
|
||||
): Promise<Loaded<Entity, Hint, Fields, Excludes> | null>;
|
||||
/** Loads the specified relations on this entity. */
|
||||
populate<Hint extends string = never, Fields extends string = never>(
|
||||
populate: AutoPath<Entity, Hint, PopulatePath.ALL>[] | false,
|
||||
options?: EntityLoaderOptions<Entity, Fields>,
|
||||
): Promise<Loaded<Entity, Hint>>;
|
||||
/** Returns whether this entity has a primary key value set. */
|
||||
hasPrimaryKey(): boolean;
|
||||
/** Returns the primary key value, optionally converting custom types to their database representation. */
|
||||
getPrimaryKey(convertCustomTypes?: boolean): Primary<Entity> | null;
|
||||
/** Returns all primary key values as an array. Used internally for composite key handling. */
|
||||
getPrimaryKeys(convertCustomTypes?: boolean): Primary<Entity>[] | null;
|
||||
/** Returns the database schema this entity belongs to. */
|
||||
getSchema(): string | undefined;
|
||||
/** Sets the database schema for this entity. */
|
||||
setSchema(schema?: string): void;
|
||||
/** Sets the primary key value on the entity. */
|
||||
setPrimaryKey(id: Primary<Entity> | null): void;
|
||||
/** Returns the primary key serialized as a string suitable for identity map lookups. */
|
||||
getSerializedPrimaryKey(): string;
|
||||
get __meta(): EntityMetadata<Entity>;
|
||||
get __platform(): Platform;
|
||||
get __config(): Configuration;
|
||||
get __primaryKeys(): Primary<Entity>[];
|
||||
}
|
||||
180
node_modules/@mikro-orm/core/entity/WrappedEntity.js
generated
vendored
Normal file
180
node_modules/@mikro-orm/core/entity/WrappedEntity.js
generated
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
import { Reference } from './Reference.js';
|
||||
import { EntityTransformer } from '../serialization/EntityTransformer.js';
|
||||
import { EntityAssigner } from './EntityAssigner.js';
|
||||
import { Utils } from '../utils/Utils.js';
|
||||
import { ValidationError } from '../errors.js';
|
||||
import { helper } from './wrap.js';
|
||||
import { EntitySerializer } from '../serialization/EntitySerializer.js';
|
||||
import { expandDotPaths } from './utils.js';
|
||||
/** @internal Wrapper attached to every managed entity, holding ORM state such as initialization flags, identity map references, and change tracking snapshots. */
|
||||
export class WrappedEntity {
|
||||
constructor(entity, hydrator, pkGetter, pkSerializer, pkGetterConverted) {
|
||||
this.entity = entity;
|
||||
this.hydrator = hydrator;
|
||||
this.pkGetter = pkGetter;
|
||||
this.pkSerializer = pkSerializer;
|
||||
this.pkGetterConverted = pkGetterConverted;
|
||||
this.__initialized = true;
|
||||
this.__serializationContext = {};
|
||||
this.__loadedProperties = new Set();
|
||||
this.__data = {};
|
||||
this.__processing = false;
|
||||
}
|
||||
/** Returns whether the entity has been fully loaded from the database. */
|
||||
isInitialized() {
|
||||
return this.__initialized;
|
||||
}
|
||||
/** Returns whether the entity is managed by an EntityManager (tracked in the identity map). */
|
||||
isManaged() {
|
||||
return !!this.__managed;
|
||||
}
|
||||
/** Marks the entity as populated or not for serialization purposes. */
|
||||
populated(populated = true) {
|
||||
this.__populated = populated;
|
||||
}
|
||||
/** Sets the serialization context with populate hints, field selections, and exclusions. */
|
||||
setSerializationContext(options) {
|
||||
const exclude = options.exclude ?? [];
|
||||
const context = this.__serializationContext;
|
||||
const populate = expandDotPaths(this.__meta, options.populate);
|
||||
context.populate = context.populate ? context.populate.concat(populate) : populate;
|
||||
context.exclude = context.exclude ? context.exclude.concat(exclude) : exclude;
|
||||
if (context.fields && options.fields) {
|
||||
options.fields.forEach(f => context.fields.add(f));
|
||||
} else if (options.fields) {
|
||||
context.fields = new Set(options.fields);
|
||||
} else {
|
||||
context.fields = new Set(['*']);
|
||||
}
|
||||
}
|
||||
/** Returns a Reference wrapper for this entity, creating one if it does not already exist. */
|
||||
toReference() {
|
||||
this.__reference ??= new Reference(this.entity);
|
||||
return this.__reference;
|
||||
}
|
||||
/** Converts the entity to a plain object representation, optionally excluding specified fields. */
|
||||
toObject(ignoreFields) {
|
||||
return EntityTransformer.toObject(this.entity, ignoreFields);
|
||||
}
|
||||
/** Serializes the entity with control over which relations and fields to include or exclude. */
|
||||
serialize(options) {
|
||||
return EntitySerializer.serialize(this.entity, options);
|
||||
}
|
||||
/** Converts the entity to a plain object, including all properties regardless of serialization rules. */
|
||||
toPOJO() {
|
||||
return EntityTransformer.toObject(this.entity, [], true);
|
||||
}
|
||||
/** Serializes the entity using its `toJSON` method (supports `JSON.stringify`). */
|
||||
toJSON(...args) {
|
||||
// toJSON methods is added to the prototype during discovery to support automatic serialization via JSON.stringify()
|
||||
return this.entity.toJSON(...args);
|
||||
}
|
||||
/** Assigns the given data to this entity, updating its properties and relations. */
|
||||
assign(data, options) {
|
||||
if ('assign' in this.entity) {
|
||||
return this.entity.assign(data, options);
|
||||
}
|
||||
return EntityAssigner.assign(this.entity, data, options);
|
||||
}
|
||||
/** Initializes (refreshes) the entity by reloading it from the database. Returns null if not found. */
|
||||
async init(options) {
|
||||
if (!this.__em) {
|
||||
throw ValidationError.entityNotManaged(this.entity);
|
||||
}
|
||||
return this.__em.findOne(this.entity.constructor, this.entity, {
|
||||
...options,
|
||||
refresh: true,
|
||||
schema: this.__schema,
|
||||
});
|
||||
}
|
||||
/** Loads the specified relations on this entity. */
|
||||
async populate(populate, options = {}) {
|
||||
if (!this.__em) {
|
||||
throw ValidationError.entityNotManaged(this.entity);
|
||||
}
|
||||
// @ts-ignore hard to type
|
||||
await this.__em.populate(this.entity, populate, options);
|
||||
return this.entity;
|
||||
}
|
||||
/** Returns whether this entity has a primary key value set. */
|
||||
hasPrimaryKey() {
|
||||
const pk = this.getPrimaryKey();
|
||||
return pk != null;
|
||||
}
|
||||
/** Returns the primary key value, optionally converting custom types to their database representation. */
|
||||
getPrimaryKey(convertCustomTypes = false) {
|
||||
const prop = this.__meta.getPrimaryProps()[0];
|
||||
if (!prop) {
|
||||
return null;
|
||||
}
|
||||
if (this.__pk != null && this.__meta.compositePK) {
|
||||
return Utils.getCompositeKeyValue(
|
||||
this.__pk,
|
||||
this.__meta,
|
||||
convertCustomTypes ? 'convertToDatabaseValue' : false,
|
||||
this.__platform,
|
||||
);
|
||||
}
|
||||
if (convertCustomTypes && this.__pk != null && prop.customType) {
|
||||
return prop.customType.convertToDatabaseValue(this.__pk, this.__platform);
|
||||
}
|
||||
if (convertCustomTypes) {
|
||||
return this.__pk ?? this.pkGetterConverted(this.entity);
|
||||
}
|
||||
return this.__pk ?? this.pkGetter(this.entity);
|
||||
}
|
||||
/** Returns all primary key values as an array. Used internally for composite key handling. */
|
||||
// TODO: currently used only in `Driver.syncCollection` — candidate for removal
|
||||
getPrimaryKeys(convertCustomTypes = false) {
|
||||
const pk = this.getPrimaryKey(convertCustomTypes);
|
||||
if (pk == null) {
|
||||
return null;
|
||||
}
|
||||
if (this.__meta.compositePK) {
|
||||
return this.__meta.primaryKeys.reduce((ret, pk) => {
|
||||
const child = this.entity[pk];
|
||||
if (Utils.isEntity(child, true)) {
|
||||
const childPk = helper(child).getPrimaryKeys(convertCustomTypes);
|
||||
ret.push(...childPk);
|
||||
} else {
|
||||
ret.push(child);
|
||||
}
|
||||
return ret;
|
||||
}, []);
|
||||
}
|
||||
return [pk];
|
||||
}
|
||||
/** Returns the database schema this entity belongs to. */
|
||||
getSchema() {
|
||||
return this.__schema;
|
||||
}
|
||||
/** Sets the database schema for this entity. */
|
||||
setSchema(schema) {
|
||||
this.__schema = schema;
|
||||
}
|
||||
/** Sets the primary key value on the entity. */
|
||||
setPrimaryKey(id) {
|
||||
this.entity[this.__meta.primaryKeys[0]] = id;
|
||||
this.__pk = id;
|
||||
}
|
||||
/** Returns the primary key serialized as a string suitable for identity map lookups. */
|
||||
getSerializedPrimaryKey() {
|
||||
return this.pkSerializer(this.entity);
|
||||
}
|
||||
get __meta() {
|
||||
return this.entity.__meta;
|
||||
}
|
||||
get __platform() {
|
||||
return this.entity.__platform;
|
||||
}
|
||||
get __config() {
|
||||
return this.__em?.config ?? this.entity.__config;
|
||||
}
|
||||
get __primaryKeys() {
|
||||
return Utils.getPrimaryKeyValues(this.entity, this.__meta);
|
||||
}
|
||||
/** @ignore */
|
||||
[Symbol.for('nodejs.util.inspect.custom')]() {
|
||||
return `[WrappedEntity<${this.__meta.className}>]`;
|
||||
}
|
||||
}
|
||||
1372
node_modules/@mikro-orm/core/entity/defineEntity.d.ts
generated
vendored
Normal file
1372
node_modules/@mikro-orm/core/entity/defineEntity.d.ts
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
547
node_modules/@mikro-orm/core/entity/defineEntity.js
generated
vendored
Normal file
547
node_modules/@mikro-orm/core/entity/defineEntity.js
generated
vendored
Normal file
@@ -0,0 +1,547 @@
|
||||
import { types } from '../types/index.js';
|
||||
import { EntitySchema } from '../metadata/EntitySchema.js';
|
||||
// Parameter-level sync assertion lives in tests/defineEntity.test.ts to avoid
|
||||
// instantiating the full builder class in production builds (~680 instantiations).
|
||||
/** @internal */
|
||||
export class UniversalPropertyOptionsBuilder {
|
||||
'~options';
|
||||
'~type';
|
||||
constructor(options) {
|
||||
this['~options'] = options;
|
||||
}
|
||||
assignOptions(options) {
|
||||
return new UniversalPropertyOptionsBuilder({ ...this['~options'], ...options });
|
||||
}
|
||||
$type() {
|
||||
return this.assignOptions({});
|
||||
}
|
||||
/**
|
||||
* Alias for `fieldName`.
|
||||
*/
|
||||
name(name) {
|
||||
return this.assignOptions({ name });
|
||||
}
|
||||
/**
|
||||
* Specify database column name for this property.
|
||||
*
|
||||
* @see https://mikro-orm.io/docs/naming-strategy
|
||||
*/
|
||||
fieldName(fieldName) {
|
||||
return this.assignOptions({ fieldName });
|
||||
}
|
||||
/**
|
||||
* Specify database column names for this property.
|
||||
* Same as `fieldName` but for composite FKs.
|
||||
*
|
||||
* @see https://mikro-orm.io/docs/naming-strategy
|
||||
*/
|
||||
fieldNames(...fieldNames) {
|
||||
return this.assignOptions({ fieldNames });
|
||||
}
|
||||
/**
|
||||
* Specify an exact database column type for {@link https://mikro-orm.io/docs/schema-generator Schema Generator}. This option is only for simple properties represented by a single column. (SQL only)
|
||||
*/
|
||||
columnType(columnType) {
|
||||
return this.assignOptions({ columnType });
|
||||
}
|
||||
/**
|
||||
* Specify an exact database column type for {@link https://mikro-orm.io/docs/schema-generator Schema Generator}. This option is suitable for composite keys, where one property is represented by multiple columns. (SQL only)
|
||||
*/
|
||||
columnTypes(...columnTypes) {
|
||||
return this.assignOptions({ columnTypes });
|
||||
}
|
||||
/**
|
||||
* Explicitly specify the runtime type.
|
||||
*
|
||||
* @see https://mikro-orm.io/docs/metadata-providers
|
||||
* @see https://mikro-orm.io/docs/custom-types
|
||||
*/
|
||||
type(type) {
|
||||
return this.assignOptions({ type });
|
||||
}
|
||||
/**
|
||||
* Runtime type of the property. This is the JS type that your property is mapped to, e.g. `string` or `number`, and is normally inferred automatically via `reflect-metadata`.
|
||||
* In some cases, the inference won't work, and you might need to specify the `runtimeType` explicitly - the most common one is when you use a union type with null like `foo: number | null`.
|
||||
*/
|
||||
runtimeType(runtimeType) {
|
||||
return this.assignOptions({ runtimeType });
|
||||
}
|
||||
/**
|
||||
* Set length of database column, used for datetime/timestamp/varchar column types for {@link https://mikro-orm.io/docs/schema-generator Schema Generator}. (SQL only)
|
||||
*/
|
||||
length(length) {
|
||||
return this.assignOptions({ length });
|
||||
}
|
||||
/**
|
||||
* Set precision of database column to represent the number of significant digits. (SQL only)
|
||||
*/
|
||||
precision(precision) {
|
||||
return this.assignOptions({ precision });
|
||||
}
|
||||
/**
|
||||
* Set scale of database column to represents the number of digits after the decimal point. (SQL only)
|
||||
*/
|
||||
scale(scale) {
|
||||
return this.assignOptions({ scale });
|
||||
}
|
||||
autoincrement(autoincrement = true) {
|
||||
return this.assignOptions({ autoincrement });
|
||||
}
|
||||
/**
|
||||
* Add the property to the `returning` statement.
|
||||
*/
|
||||
returning(returning = true) {
|
||||
return this.assignOptions({ returning });
|
||||
}
|
||||
/**
|
||||
* Automatically set the property value when entity gets created, executed during flush operation.
|
||||
*/
|
||||
onCreate(onCreate) {
|
||||
return this.assignOptions({ onCreate });
|
||||
}
|
||||
/**
|
||||
* Automatically update the property value every time entity gets updated, executed during flush operation.
|
||||
*/
|
||||
onUpdate(onUpdate) {
|
||||
return this.assignOptions({ onUpdate });
|
||||
}
|
||||
/**
|
||||
* Specify default column value for {@link https://mikro-orm.io/docs/schema-generator Schema Generator}.
|
||||
* This is a runtime value, assignable to the entity property. (SQL only)
|
||||
*/
|
||||
default(defaultValue) {
|
||||
return this.assignOptions({ default: defaultValue });
|
||||
}
|
||||
/**
|
||||
* Specify SQL functions for {@link https://mikro-orm.io/docs/schema-generator Schema Generator}. (SQL only)
|
||||
* Since v4 you should use defaultRaw for SQL functions. e.g. now()
|
||||
*/
|
||||
defaultRaw(defaultRaw) {
|
||||
return this.assignOptions({ defaultRaw });
|
||||
}
|
||||
/**
|
||||
* Allow controlling `filters` option. This will be overridden with `em.fork` or `FindOptions` if provided.
|
||||
*/
|
||||
filters(filters) {
|
||||
return this.assignOptions({ filters });
|
||||
}
|
||||
/**
|
||||
* Set to map some SQL snippet for the entity.
|
||||
*
|
||||
* @see https://mikro-orm.io/docs/defining-entities#formulas Formulas
|
||||
*/
|
||||
formula(formula) {
|
||||
return this.assignOptions({ formula });
|
||||
}
|
||||
/**
|
||||
* For generated columns. This will be appended to the column type after the `generated always` clause.
|
||||
*/
|
||||
generated(generated) {
|
||||
return this.assignOptions({ generated });
|
||||
}
|
||||
/**
|
||||
* Set column as nullable for {@link https://mikro-orm.io/docs/schema-generator Schema Generator}.
|
||||
*/
|
||||
nullable() {
|
||||
return this.assignOptions({ nullable: true });
|
||||
}
|
||||
/**
|
||||
* Set column as unsigned for {@link https://mikro-orm.io/docs/schema-generator Schema Generator}. (SQL only)
|
||||
*/
|
||||
unsigned(unsigned = true) {
|
||||
return this.assignOptions({ unsigned });
|
||||
}
|
||||
persist(persist = true) {
|
||||
return this.assignOptions({ persist });
|
||||
}
|
||||
/**
|
||||
* Set false to disable hydration of this property. Useful for persisted getters.
|
||||
*/
|
||||
hydrate(hydrate = true) {
|
||||
return this.assignOptions({ hydrate });
|
||||
}
|
||||
/**
|
||||
* Enable `ScalarReference` wrapper for lazy values. Use this in combination with `lazy: true` to have a type-safe accessor object in place of the value.
|
||||
*/
|
||||
ref() {
|
||||
return this.assignOptions({ ref: true });
|
||||
}
|
||||
/**
|
||||
* Set to true to omit the property when {@link https://mikro-orm.io/docs/serializing Serializing}.
|
||||
*/
|
||||
hidden() {
|
||||
return this.assignOptions({ hidden: true });
|
||||
}
|
||||
/**
|
||||
* Set to true to enable {@link https://mikro-orm.io/docs/transactions#optimistic-locking Optimistic Locking} via version field. (SQL only)
|
||||
*/
|
||||
version() {
|
||||
return this.assignOptions({ version: true });
|
||||
}
|
||||
/**
|
||||
* Set to true to enable {@link https://mikro-orm.io/docs/transactions#optimistic-locking Optimistic Locking} via concurrency fields.
|
||||
*/
|
||||
concurrencyCheck(concurrencyCheck = true) {
|
||||
return this.assignOptions({ concurrencyCheck });
|
||||
}
|
||||
/**
|
||||
* Explicitly specify index on a property.
|
||||
*/
|
||||
index(index = true) {
|
||||
return this.assignOptions({ index });
|
||||
}
|
||||
/**
|
||||
* Set column as unique for {@link https://mikro-orm.io/docs/schema-generator Schema Generator}. (SQL only)
|
||||
*/
|
||||
unique(unique = true) {
|
||||
return this.assignOptions({ unique });
|
||||
}
|
||||
/**
|
||||
* Specify column with check constraints. (Postgres driver only)
|
||||
*
|
||||
* @see https://mikro-orm.io/docs/defining-entities#check-constraints
|
||||
*/
|
||||
check(check) {
|
||||
return this.assignOptions({ check });
|
||||
}
|
||||
/**
|
||||
* Set to omit the property from the select clause for lazy loading.
|
||||
*
|
||||
* @see https://mikro-orm.io/docs/defining-entities#lazy-scalar-properties
|
||||
*/
|
||||
lazy() {
|
||||
return this.assignOptions({ lazy: true });
|
||||
}
|
||||
/**
|
||||
* Set true to define entity's unique primary key identifier.
|
||||
*
|
||||
* @see https://mikro-orm.io/docs/decorators#primarykey
|
||||
*/
|
||||
primary() {
|
||||
return this.assignOptions({ primary: true });
|
||||
}
|
||||
/**
|
||||
* Set true to define the properties as setter. (virtual)
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* @Property({ setter: true })
|
||||
* set address(value: string) {
|
||||
* this._address = value.toLocaleLowerCase();
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
setter(setter = true) {
|
||||
return this.assignOptions({ setter });
|
||||
}
|
||||
/**
|
||||
* Set true to define the properties as getter. (virtual)
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* @Property({ getter: true })
|
||||
* get fullName() {
|
||||
* return this.firstName + this.lastName;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
getter(getter = true) {
|
||||
return this.assignOptions({ getter });
|
||||
}
|
||||
/**
|
||||
* When defining a property over a method (not a getter, a regular function), you can use this option to point
|
||||
* to the method name.
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* @Property({ getter: true })
|
||||
* getFullName() {
|
||||
* return this.firstName + this.lastName;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
getterName(getterName) {
|
||||
return this.assignOptions({ getterName });
|
||||
}
|
||||
/**
|
||||
* Set to define serialized primary key for MongoDB. (virtual)
|
||||
* Alias for `@SerializedPrimaryKey()` decorator.
|
||||
*
|
||||
* @see https://mikro-orm.io/docs/decorators#serializedprimarykey
|
||||
*/
|
||||
serializedPrimaryKey(serializedPrimaryKey = true) {
|
||||
return this.assignOptions({ serializedPrimaryKey });
|
||||
}
|
||||
/**
|
||||
* Set to use serialize property. Allow to specify a callback that will be used when serializing a property.
|
||||
*
|
||||
* @see https://mikro-orm.io/docs/serializing#property-serializers
|
||||
*/
|
||||
serializer(serializer) {
|
||||
return this.assignOptions({ serializer });
|
||||
}
|
||||
/**
|
||||
* Specify name of key for the serialized value.
|
||||
*/
|
||||
serializedName(serializedName) {
|
||||
return this.assignOptions({ serializedName });
|
||||
}
|
||||
/**
|
||||
* Specify serialization groups for `serialize()` calls. If a property does not specify any group, it will be included,
|
||||
* otherwise only properties with a matching group are included.
|
||||
*/
|
||||
groups(...groups) {
|
||||
return this.assignOptions({ groups });
|
||||
}
|
||||
/**
|
||||
* Specify a custom order based on the values. (SQL only)
|
||||
*/
|
||||
customOrder(...customOrder) {
|
||||
return this.assignOptions({ customOrder });
|
||||
}
|
||||
/**
|
||||
* Specify comment of column for {@link https://mikro-orm.io/docs/schema-generator Schema Generator}. (SQL only)
|
||||
*/
|
||||
comment(comment) {
|
||||
return this.assignOptions({ comment });
|
||||
}
|
||||
/** mysql only */
|
||||
extra(extra) {
|
||||
return this.assignOptions({ extra });
|
||||
}
|
||||
/**
|
||||
* Set to avoid a perpetual diff from the {@link https://mikro-orm.io/docs/schema-generator Schema Generator} when columns are generated.
|
||||
*
|
||||
* @see https://mikro-orm.io/docs/defining-entities#sql-generated-columns
|
||||
*/
|
||||
ignoreSchemaChanges(...ignoreSchemaChanges) {
|
||||
return this.assignOptions({ ignoreSchemaChanges });
|
||||
}
|
||||
array() {
|
||||
return this.assignOptions({ array: true });
|
||||
}
|
||||
/** for postgres, by default it uses text column with check constraint */
|
||||
nativeEnumName(nativeEnumName) {
|
||||
return this.assignOptions({ nativeEnumName });
|
||||
}
|
||||
prefix(prefix) {
|
||||
return this.assignOptions({ prefix });
|
||||
}
|
||||
prefixMode(prefixMode) {
|
||||
return this.assignOptions({ prefixMode });
|
||||
}
|
||||
object(object = true) {
|
||||
return this.assignOptions({ object });
|
||||
}
|
||||
/** Set what actions on owning entity should be cascaded to the relationship. Defaults to [Cascade.PERSIST, Cascade.MERGE] (see {@doclink cascading}). */
|
||||
cascade(...cascade) {
|
||||
return this.assignOptions({ cascade });
|
||||
}
|
||||
/** Always load the relationship. Discouraged for use with to-many relations for performance reasons. */
|
||||
eager(eager = true) {
|
||||
return this.assignOptions({ eager });
|
||||
}
|
||||
/** Override the default loading strategy for this property. This option has precedence over the global `loadStrategy`, but can be overridden by `FindOptions.strategy`. */
|
||||
strategy(strategy) {
|
||||
return this.assignOptions({ strategy });
|
||||
}
|
||||
/** Set this side as owning. Owning side is where the foreign key is defined. This option is not required if you use `inversedBy` or `mappedBy` to distinguish owning and inverse side. */
|
||||
owner() {
|
||||
return this.assignOptions({ owner: true });
|
||||
}
|
||||
/** For polymorphic relations. Specifies the property name that stores the entity type discriminator. Defaults to the property name. */
|
||||
discriminator(discriminator) {
|
||||
return this.assignOptions({ discriminator });
|
||||
}
|
||||
/** For polymorphic relations. Custom mapping of discriminator values to entity class names. */
|
||||
discriminatorMap(discriminatorMap) {
|
||||
return this.assignOptions({ discriminatorMap });
|
||||
}
|
||||
/** Point to the inverse side property name. */
|
||||
inversedBy(inversedBy) {
|
||||
return this.assignOptions({ inversedBy });
|
||||
}
|
||||
/** Point to the owning side property name. */
|
||||
mappedBy(mappedBy) {
|
||||
return this.assignOptions({ mappedBy });
|
||||
}
|
||||
/** Condition for {@doclink collections#declarative-partial-loading | Declarative partial loading}. */
|
||||
where(...where) {
|
||||
return this.assignOptions({ where });
|
||||
}
|
||||
/** Set default ordering. */
|
||||
orderBy(...orderBy) {
|
||||
return this.assignOptions({ orderBy });
|
||||
}
|
||||
/** Force stable insertion order of items in the collection (see {@doclink collections | Collections}). */
|
||||
fixedOrder(fixedOrder = true) {
|
||||
return this.assignOptions({ fixedOrder });
|
||||
}
|
||||
/** Override default order column name (`id`) for fixed ordering. */
|
||||
fixedOrderColumn(fixedOrderColumn) {
|
||||
return this.assignOptions({ fixedOrderColumn });
|
||||
}
|
||||
/** Override default name for pivot table (see {@doclink naming-strategy | Naming Strategy}). */
|
||||
pivotTable(pivotTable) {
|
||||
return this.assignOptions({ pivotTable });
|
||||
}
|
||||
/** Set pivot entity for this relation (see {@doclink collections#custom-pivot-table-entity | Custom pivot table entity}). */
|
||||
pivotEntity(pivotEntity) {
|
||||
return this.assignOptions({ pivotEntity });
|
||||
}
|
||||
/** Override the default database column name on the owning side (see {@doclink naming-strategy | Naming Strategy}). This option is only for simple properties represented by a single column. */
|
||||
joinColumn(joinColumn) {
|
||||
return this.assignOptions({ joinColumn });
|
||||
}
|
||||
/** Override the default database column name on the owning side (see {@doclink naming-strategy | Naming Strategy}). This option is suitable for composite keys, where one property is represented by multiple columns. */
|
||||
joinColumns(...joinColumns) {
|
||||
return this.assignOptions({ joinColumns });
|
||||
}
|
||||
/** Override the default database column name on the inverse side (see {@doclink naming-strategy | Naming Strategy}). This option is only for simple properties represented by a single column. */
|
||||
inverseJoinColumn(inverseJoinColumn) {
|
||||
return this.assignOptions({ inverseJoinColumn });
|
||||
}
|
||||
/** Override the default database column name on the inverse side (see {@doclink naming-strategy | Naming Strategy}). This option is suitable for composite keys, where one property is represented by multiple columns. */
|
||||
inverseJoinColumns(...inverseJoinColumns) {
|
||||
return this.assignOptions({ inverseJoinColumns });
|
||||
}
|
||||
/** Override the default database column name on the target entity (see {@doclink naming-strategy | Naming Strategy}). This option is only for simple properties represented by a single column. */
|
||||
referenceColumnName(referenceColumnName) {
|
||||
return this.assignOptions({ referenceColumnName });
|
||||
}
|
||||
/** Override the default database column name on the target entity (see {@doclink naming-strategy | Naming Strategy}). This option is suitable for composite keys, where one property is represented by multiple columns. */
|
||||
referencedColumnNames(...referencedColumnNames) {
|
||||
return this.assignOptions({ referencedColumnNames });
|
||||
}
|
||||
/** Specify the property name on the target entity that this FK references instead of the primary key. */
|
||||
targetKey(targetKey) {
|
||||
return this.assignOptions({ targetKey });
|
||||
}
|
||||
/** What to do when the target entity gets deleted. */
|
||||
deleteRule(deleteRule) {
|
||||
return this.assignOptions({ deleteRule });
|
||||
}
|
||||
/** What to do when the reference to the target entity gets updated. */
|
||||
updateRule(updateRule) {
|
||||
return this.assignOptions({ updateRule });
|
||||
}
|
||||
/** Map this relation to the primary key value instead of an entity. */
|
||||
mapToPk() {
|
||||
return this.assignOptions({ mapToPk: true });
|
||||
}
|
||||
/** Set the constraint type. Immediate constraints are checked for each statement, while deferred ones are only checked at the end of the transaction. Only for postgres unique constraints. */
|
||||
deferMode(deferMode) {
|
||||
return this.assignOptions({ deferMode });
|
||||
}
|
||||
/** When a part of a composite column is shared in other properties, use this option to specify what columns are considered as owned by this property. This is useful when your composite property is nullable, but parts of it are not. */
|
||||
ownColumns(...ownColumns) {
|
||||
return this.assignOptions({ ownColumns });
|
||||
}
|
||||
/** Enable/disable foreign key constraint creation on this relation */
|
||||
createForeignKeyConstraint(createForeignKeyConstraint = true) {
|
||||
return this.assignOptions({ createForeignKeyConstraint });
|
||||
}
|
||||
/** Set a custom foreign key constraint name, overriding NamingStrategy.indexName(). */
|
||||
foreignKeyName(foreignKeyName) {
|
||||
return this.assignOptions({ foreignKeyName });
|
||||
}
|
||||
/** Remove the entity when it gets disconnected from the relationship (see {@doclink cascading | Cascading}). */
|
||||
orphanRemoval(orphanRemoval = true) {
|
||||
return this.assignOptions({ orphanRemoval });
|
||||
}
|
||||
accessor(accessor = true) {
|
||||
return this.assignOptions({ accessor });
|
||||
}
|
||||
}
|
||||
/** @internal */
|
||||
export class OneToManyOptionsBuilderOnlyMappedBy extends UniversalPropertyOptionsBuilder {
|
||||
/** Point to the owning side property name. */
|
||||
mappedBy(mappedBy) {
|
||||
return new UniversalPropertyOptionsBuilder({ ...this['~options'], mappedBy });
|
||||
}
|
||||
}
|
||||
function createPropertyBuilders(options) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(options).map(([key, value]) => [key, () => new UniversalPropertyOptionsBuilder({ type: value })]),
|
||||
);
|
||||
}
|
||||
const propertyBuilders = {
|
||||
...createPropertyBuilders(types),
|
||||
bigint: mode => new UniversalPropertyOptionsBuilder({ type: new types.bigint(mode) }),
|
||||
array: (toJsValue = i => i, toDbValue = i => i) =>
|
||||
new UniversalPropertyOptionsBuilder({ type: new types.array(toJsValue, toDbValue) }),
|
||||
decimal: mode => new UniversalPropertyOptionsBuilder({ type: new types.decimal(mode) }),
|
||||
json: () => new UniversalPropertyOptionsBuilder({ type: types.json }),
|
||||
formula: formula => new UniversalPropertyOptionsBuilder({ formula }),
|
||||
datetime: length => new UniversalPropertyOptionsBuilder({ type: types.datetime, length }),
|
||||
time: length => new UniversalPropertyOptionsBuilder({ type: types.time, length }),
|
||||
type: type => new UniversalPropertyOptionsBuilder({ type }),
|
||||
enum: items =>
|
||||
new UniversalPropertyOptionsBuilder({
|
||||
enum: true,
|
||||
items,
|
||||
}),
|
||||
embedded: target =>
|
||||
new UniversalPropertyOptionsBuilder({
|
||||
entity: () => target,
|
||||
kind: 'embedded',
|
||||
}),
|
||||
manyToMany: target =>
|
||||
new UniversalPropertyOptionsBuilder({
|
||||
entity: () => target,
|
||||
kind: 'm:n',
|
||||
}),
|
||||
manyToOne: target =>
|
||||
new UniversalPropertyOptionsBuilder({
|
||||
entity: () => target,
|
||||
kind: 'm:1',
|
||||
}),
|
||||
oneToMany: target =>
|
||||
new OneToManyOptionsBuilderOnlyMappedBy({
|
||||
entity: () => target,
|
||||
kind: '1:m',
|
||||
}),
|
||||
oneToOne: target =>
|
||||
new UniversalPropertyOptionsBuilder({
|
||||
entity: () => target,
|
||||
kind: '1:1',
|
||||
}),
|
||||
};
|
||||
function getBuilderOptions(builder) {
|
||||
return '~options' in builder ? builder['~options'] : builder;
|
||||
}
|
||||
export function defineEntity(meta) {
|
||||
const { properties: propertiesOrGetter, ...options } = meta;
|
||||
const propertyOptions =
|
||||
typeof propertiesOrGetter === 'function' ? propertiesOrGetter(propertyBuilders) : propertiesOrGetter;
|
||||
const properties = {};
|
||||
const values = new Map();
|
||||
for (const [key, builder] of Object.entries(propertyOptions)) {
|
||||
if (typeof builder === 'function') {
|
||||
Object.defineProperty(properties, key, {
|
||||
get: () => {
|
||||
let value = values.get(key);
|
||||
if (value === undefined) {
|
||||
value = getBuilderOptions(builder());
|
||||
values.set(key, value);
|
||||
}
|
||||
return value;
|
||||
},
|
||||
set: value => {
|
||||
values.set(key, value);
|
||||
},
|
||||
enumerable: true,
|
||||
});
|
||||
} else {
|
||||
Object.defineProperty(properties, key, {
|
||||
value: getBuilderOptions(builder),
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
return new EntitySchema({ properties, ...options });
|
||||
}
|
||||
defineEntity.properties = propertyBuilders;
|
||||
/** Shorthand alias for `defineEntity.properties` - the property builders for use in `defineEntity()`. */
|
||||
export { propertyBuilders as p };
|
||||
15
node_modules/@mikro-orm/core/entity/index.d.ts
generated
vendored
Normal file
15
node_modules/@mikro-orm/core/entity/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
export * from './EntityRepository.js';
|
||||
export * from './EntityIdentifier.js';
|
||||
export * from './PolymorphicRef.js';
|
||||
export * from './EntityAssigner.js';
|
||||
export * from './EntityHelper.js';
|
||||
export * from './EntityFactory.js';
|
||||
export * from './Collection.js';
|
||||
export * from './EntityLoader.js';
|
||||
export * from './Reference.js';
|
||||
export * from './BaseEntity.js';
|
||||
export * from './WrappedEntity.js';
|
||||
export * from './validators.js';
|
||||
export * from './wrap.js';
|
||||
export * from './defineEntity.js';
|
||||
export * from './utils.js';
|
||||
15
node_modules/@mikro-orm/core/entity/index.js
generated
vendored
Normal file
15
node_modules/@mikro-orm/core/entity/index.js
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
export * from './EntityRepository.js';
|
||||
export * from './EntityIdentifier.js';
|
||||
export * from './PolymorphicRef.js';
|
||||
export * from './EntityAssigner.js';
|
||||
export * from './EntityHelper.js';
|
||||
export * from './EntityFactory.js';
|
||||
export * from './Collection.js';
|
||||
export * from './EntityLoader.js';
|
||||
export * from './Reference.js';
|
||||
export * from './BaseEntity.js';
|
||||
export * from './WrappedEntity.js';
|
||||
export * from './validators.js';
|
||||
export * from './wrap.js';
|
||||
export * from './defineEntity.js';
|
||||
export * from './utils.js';
|
||||
27
node_modules/@mikro-orm/core/entity/utils.d.ts
generated
vendored
Normal file
27
node_modules/@mikro-orm/core/entity/utils.d.ts
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { EntityMetadata, PopulateHintOptions, PopulateOptions } from '../typings.js';
|
||||
import { LoadStrategy, ReferenceKind } from '../enums.js';
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export declare function expandDotPaths<Entity>(
|
||||
meta: EntityMetadata<Entity>,
|
||||
populate?: readonly (string | PopulateOptions<Entity>)[],
|
||||
normalized?: boolean,
|
||||
): PopulateOptions<Entity>[];
|
||||
/**
|
||||
* Returns the loading strategy based on the provided hint.
|
||||
* If `BALANCED` strategy is used, it will return JOINED if the property is a to-one relation.
|
||||
* @internal
|
||||
*/
|
||||
export declare function getLoadingStrategy(
|
||||
strategy: LoadStrategy | `${LoadStrategy}`,
|
||||
kind: ReferenceKind,
|
||||
): LoadStrategy.SELECT_IN | LoadStrategy.JOINED;
|
||||
/**
|
||||
* Applies per-relation overrides from `populateHints` to the normalized populate tree.
|
||||
* @internal
|
||||
*/
|
||||
export declare function applyPopulateHints<Entity>(
|
||||
populate: PopulateOptions<Entity>[],
|
||||
hints: Record<string, PopulateHintOptions>,
|
||||
): void;
|
||||
102
node_modules/@mikro-orm/core/entity/utils.js
generated
vendored
Normal file
102
node_modules/@mikro-orm/core/entity/utils.js
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
import { LoadStrategy, PopulatePath, ReferenceKind } from '../enums.js';
|
||||
import { Utils } from '../utils/Utils.js';
|
||||
/**
|
||||
* Expands `books.perex` like populate to use `children` array instead of the dot syntax
|
||||
*/
|
||||
function expandNestedPopulate(parentProp, parts, strategy, all) {
|
||||
const meta = parentProp.targetMeta;
|
||||
const field = parts.shift();
|
||||
const prop = meta.properties[field];
|
||||
const ret = { field, strategy, all };
|
||||
if (parts.length > 0) {
|
||||
ret.children = [expandNestedPopulate(prop, parts, strategy)];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function expandDotPaths(meta, populate, normalized = false) {
|
||||
const ret = normalized
|
||||
? populate
|
||||
: Utils.asArray(populate).map(field => {
|
||||
if (typeof field === 'string') {
|
||||
return { field };
|
||||
}
|
||||
/* v8 ignore next */
|
||||
return typeof field === 'boolean' || field.field === PopulatePath.ALL
|
||||
? { all: !!field, field: meta.primaryKeys[0] }
|
||||
: field;
|
||||
});
|
||||
for (const p of ret) {
|
||||
if (!p.field.includes('.')) {
|
||||
continue;
|
||||
}
|
||||
const [f, ...parts] = p.field.split('.');
|
||||
p.field = f;
|
||||
p.children ??= [];
|
||||
const prop = meta.properties[p.field];
|
||||
if (parts[0] === PopulatePath.ALL) {
|
||||
prop.targetMeta.props
|
||||
.filter(prop => prop.lazy || prop.kind !== ReferenceKind.SCALAR)
|
||||
.forEach(prop => p.children.push({ field: prop.name, strategy: p.strategy }));
|
||||
} else if (prop.kind === ReferenceKind.EMBEDDED) {
|
||||
const embeddedProp = Object.values(prop.embeddedProps).find(c => c.embedded[1] === parts[0]);
|
||||
ret.push({
|
||||
...p,
|
||||
field: embeddedProp.name,
|
||||
children: parts.length > 1 ? [expandNestedPopulate(embeddedProp, parts.slice(1), p.strategy, p.all)] : [],
|
||||
});
|
||||
p.children.push(expandNestedPopulate(prop, parts, p.strategy, p.all));
|
||||
} else {
|
||||
p.children.push(expandNestedPopulate(prop, parts, p.strategy, p.all));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
/**
|
||||
* Returns the loading strategy based on the provided hint.
|
||||
* If `BALANCED` strategy is used, it will return JOINED if the property is a to-one relation.
|
||||
* @internal
|
||||
*/
|
||||
export function getLoadingStrategy(strategy, kind) {
|
||||
if (strategy === LoadStrategy.BALANCED) {
|
||||
return [ReferenceKind.MANY_TO_ONE, ReferenceKind.ONE_TO_ONE].includes(kind)
|
||||
? LoadStrategy.JOINED
|
||||
: LoadStrategy.SELECT_IN;
|
||||
}
|
||||
return strategy;
|
||||
}
|
||||
function findPopulateEntry(populate, parts) {
|
||||
let current = populate;
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const entry = current.find(p => p.field.split(':')[0] === parts[i]);
|
||||
if (!entry) {
|
||||
return undefined;
|
||||
}
|
||||
if (i === parts.length - 1) {
|
||||
return entry;
|
||||
}
|
||||
current = entry.children ?? [];
|
||||
}
|
||||
/* v8 ignore next */
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Applies per-relation overrides from `populateHints` to the normalized populate tree.
|
||||
* @internal
|
||||
*/
|
||||
export function applyPopulateHints(populate, hints) {
|
||||
for (const [path, hint] of Object.entries(hints)) {
|
||||
const entry = findPopulateEntry(populate, path.split('.'));
|
||||
if (!entry) {
|
||||
continue;
|
||||
}
|
||||
if (hint.strategy != null) {
|
||||
entry.strategy = hint.strategy;
|
||||
}
|
||||
if (hint.joinType != null) {
|
||||
entry.joinType = hint.joinType;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
node_modules/@mikro-orm/core/entity/validators.d.ts
generated
vendored
Normal file
11
node_modules/@mikro-orm/core/entity/validators.d.ts
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { EntityData, EntityMetadata, EntityProperty, FilterQuery } from '../typings.js';
|
||||
/** @internal */
|
||||
export declare function validateProperty<T extends object>(prop: EntityProperty, givenValue: any, entity: T): void;
|
||||
/** @internal */
|
||||
export declare function validateEntity<T extends object>(entity: T, meta: EntityMetadata<T>): void;
|
||||
/** @internal */
|
||||
export declare function validateParams(params: any, type?: string, field?: string): void;
|
||||
/** @internal */
|
||||
export declare function validatePrimaryKey<T>(entity: EntityData<T>, meta: EntityMetadata<T>): void;
|
||||
/** @internal */
|
||||
export declare function validateEmptyWhere<T>(where: FilterQuery<T>): void;
|
||||
66
node_modules/@mikro-orm/core/entity/validators.js
generated
vendored
Normal file
66
node_modules/@mikro-orm/core/entity/validators.js
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
import { Utils } from '../utils/Utils.js';
|
||||
import { ValidationError } from '../errors.js';
|
||||
import { isRaw, Raw } from '../utils/RawQueryFragment.js';
|
||||
import { SCALAR_TYPES } from '../enums.js';
|
||||
/** @internal */
|
||||
export function validateProperty(prop, givenValue, entity) {
|
||||
if (givenValue == null || isRaw(givenValue)) {
|
||||
return;
|
||||
}
|
||||
const expectedType = prop.runtimeType;
|
||||
const propName = prop.embedded ? prop.name.replace(/~/g, '.') : prop.name;
|
||||
const givenType = Utils.getObjectType(givenValue);
|
||||
if (prop.enum && prop.items) {
|
||||
/* v8 ignore next */
|
||||
if (!prop.items.some(it => it === givenValue)) {
|
||||
throw ValidationError.fromWrongPropertyType(entity, propName, expectedType, givenType, givenValue);
|
||||
}
|
||||
} else {
|
||||
if (givenType !== expectedType && SCALAR_TYPES.has(expectedType)) {
|
||||
throw ValidationError.fromWrongPropertyType(entity, propName, expectedType, givenType, givenValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
function getValue(o, prop) {
|
||||
if (prop.embedded && prop.embedded[0] in o) {
|
||||
return o[prop.embedded[0]]?.[prop.embedded[1]];
|
||||
}
|
||||
/* v8 ignore next */
|
||||
if (prop.ref) {
|
||||
return o[prop.name]?.unwrap();
|
||||
}
|
||||
return o[prop.name];
|
||||
}
|
||||
/** @internal */
|
||||
export function validateEntity(entity, meta) {
|
||||
for (const prop of meta.validateProps) {
|
||||
validateProperty(prop, getValue(entity, prop), entity);
|
||||
}
|
||||
}
|
||||
/** @internal */
|
||||
export function validateParams(params, type = 'search condition', field) {
|
||||
if (Utils.isPrimaryKey(params) || Utils.isEntity(params)) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(params)) {
|
||||
return params.forEach(item => validateParams(item, type, field));
|
||||
}
|
||||
if (Utils.isPlainObject(params)) {
|
||||
Object.keys(params).forEach(k => validateParams(params[k], type, k));
|
||||
}
|
||||
}
|
||||
/** @internal */
|
||||
export function validatePrimaryKey(entity, meta) {
|
||||
const pkExists =
|
||||
meta.primaryKeys.every(pk => entity[pk] != null) ||
|
||||
(meta.serializedPrimaryKey && entity[meta.serializedPrimaryKey] != null);
|
||||
if (!entity || !pkExists) {
|
||||
throw ValidationError.fromMergeWithoutPK(meta);
|
||||
}
|
||||
}
|
||||
/** @internal */
|
||||
export function validateEmptyWhere(where) {
|
||||
if (Utils.isEmpty(where) && !Raw.hasObjectFragments(where)) {
|
||||
throw new Error(`You cannot call 'EntityManager.findOne()' with empty 'where' parameter`);
|
||||
}
|
||||
}
|
||||
15
node_modules/@mikro-orm/core/entity/wrap.d.ts
generated
vendored
Normal file
15
node_modules/@mikro-orm/core/entity/wrap.d.ts
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { IWrappedEntity, IWrappedEntityInternal } from '../typings.js';
|
||||
/**
|
||||
* returns WrappedEntity instance associated with this entity. This includes all the internal properties like `__meta` or `__em`.
|
||||
*/
|
||||
export declare function wrap<T extends object>(entity: T, preferHelper: true): IWrappedEntityInternal<T>;
|
||||
/**
|
||||
* wraps entity type with WrappedEntity internal properties and helpers like init/isInitialized/populated/toJSON
|
||||
*/
|
||||
export declare function wrap<T extends object>(entity: T, preferHelper?: false): IWrappedEntity<T>;
|
||||
/**
|
||||
* wraps entity type with WrappedEntity internal properties and helpers like init/isInitialized/populated/toJSON
|
||||
* use `preferHelper = true` to have access to the internal `__` properties like `__meta` or `__em`
|
||||
* @internal
|
||||
*/
|
||||
export declare function helper<T extends object>(entity: T): IWrappedEntityInternal<T>;
|
||||
21
node_modules/@mikro-orm/core/entity/wrap.js
generated
vendored
Normal file
21
node_modules/@mikro-orm/core/entity/wrap.js
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* wraps entity type with WrappedEntity internal properties and helpers like init/isInitialized/populated/toJSON
|
||||
* use `preferHelper = true` to have access to the internal `__` properties like `__meta` or `__em`
|
||||
*/
|
||||
export function wrap(entity, preferHelper = false) {
|
||||
if (!entity) {
|
||||
return entity;
|
||||
}
|
||||
if (entity.__baseEntity && !preferHelper) {
|
||||
return entity;
|
||||
}
|
||||
return entity.__helper ?? entity;
|
||||
}
|
||||
/**
|
||||
* wraps entity type with WrappedEntity internal properties and helpers like init/isInitialized/populated/toJSON
|
||||
* use `preferHelper = true` to have access to the internal `__` properties like `__meta` or `__em`
|
||||
* @internal
|
||||
*/
|
||||
export function helper(entity) {
|
||||
return entity.__helper;
|
||||
}
|
||||
Reference in New Issue
Block a user