@mikro-orm/core
Version:
TypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, PostgreSQL and SQLite databases as well as usage with vanilla JavaScript.
118 lines (117 loc) • 3.98 kB
JavaScript
/** @internal Stores managed entity instances keyed by their primary key hash, ensuring each row is loaded once. */
export class IdentityMap {
#defaultSchema;
#registry = new Map();
/** Tracks alternate key hashes for each entity so we can clean them up on delete */
#alternateKeys = new WeakMap();
constructor(defaultSchema) {
this.#defaultSchema = defaultSchema;
}
/** Stores an entity in the identity map under its primary key hash. */
store(item) {
this.getStore(item.__meta.root).set(this.getPkHash(item), item);
}
/**
* Stores an entity under an alternate key (non-PK property).
* This allows looking up entities by unique properties that are not the primary key.
*/
storeByKey(item, key, value, schema) {
const hash = this.getKeyHash(key, value, schema);
this.getStore(item.__meta.root).set(hash, item);
// Track this alternate key so we can clean it up when the entity is deleted
let keys = this.#alternateKeys.get(item);
if (!keys) {
keys = new Set();
this.#alternateKeys.set(item, keys);
}
keys.add(hash);
}
/** Removes an entity and its alternate key entries from the identity map. */
delete(item) {
const meta = item.__meta.root;
const store = this.getStore(meta);
store.delete(this.getPkHash(item));
// Also delete any alternate key entries for this entity
const altKeys = this.#alternateKeys.get(item);
if (altKeys) {
for (const hash of altKeys) {
store.delete(hash);
}
this.#alternateKeys.delete(item);
}
}
/** Retrieves an entity by its hash key from the identity map. */
getByHash(meta, hash) {
const store = this.getStore(meta);
return store.has(hash) ? store.get(hash) : undefined;
}
/** Returns (or creates) the per-entity-class store within the identity map. */
getStore(meta) {
const store = this.#registry.get(meta.class);
if (store) {
return store;
}
const newStore = new Map();
this.#registry.set(meta.class, newStore);
return newStore;
}
clear() {
this.#registry.clear();
}
/** Returns all entities currently in the identity map. */
values() {
const ret = [];
for (const store of this.#registry.values()) {
ret.push(...store.values());
}
return ret;
}
*[Symbol.iterator]() {
for (const store of this.#registry.values()) {
for (const item of store.values()) {
yield item;
}
}
}
/** Returns all hash keys currently in the identity map. */
keys() {
const ret = [];
for (const [cls, store] of this.#registry) {
ret.push(...[...store.keys()].map(hash => `${cls.name}-${hash}`));
}
return ret;
}
/**
* For back compatibility only.
*/
get(hash) {
const [name, id] = hash.split('-', 2);
const cls = [...this.#registry.keys()].find(k => k.name === name);
if (!cls) {
return undefined;
}
const store = this.#registry.get(cls);
return store.has(id) ? store.get(id) : undefined;
}
getPkHash(item) {
const wrapped = item.__helper;
const meta = wrapped.__meta;
const hash = wrapped.getSerializedPrimaryKey();
const schema = wrapped.__schema ?? meta.root.schema ?? this.#defaultSchema;
if (schema) {
return schema + ':' + hash;
}
return hash;
}
/**
* Creates a hash for an alternate key lookup.
* Format: `[key]value` or `schema:[key]value`
*/
getKeyHash(key, value, schema) {
const hash = `[${key}]${value}`;
if (schema) {
return schema + ':' + hash;
}
return hash;
}
}