@obsidize/rx-map
Version:
ES6 Map with rxjs extensions for change detection
94 lines (93 loc) • 3.45 kB
JavaScript
import { filter, map, share, startWith, tap } from 'rxjs/operators';
import { isUndefined } from '../common/utility';
import { pluckValueChanges } from '../common/operators';
import { OneToManyContext } from './one-to-many-context';
/**
* Cache map for tracking model foreign key relationships so that we have rapid lookup speed for associated models.
*
* @example
*
* ```typescript
* // Has many orders
* interface Product {
* id: number; // is the primary key
* }
*
* // Has exactly one product
* interface ProductOrder {
* id: number;
* productId: number; // is the foreign key
* }
* ```
*/
export class OneToManyRelationship {
constructor(entityMap, selectForeignKey) {
this.entityMap = entityMap;
this.selectForeignKey = selectForeignKey;
this.store = new Map();
/**
* Changes for foriegn entity relations usually happen on the foreign entity itself, rather than the primary entity.
*
* For example:
* ProductOrder has 'id' and 'productId' - a Product can have many orders associated with it, but the value
* being changed is 'productId' on ProductOrder, not 'id' on Product.
*/
this.changes = this.entityMap.changes.pipe(pluckValueChanges(ev => this.selectForeignKey(ev)), filter(ev => ev.currentValue !== ev.previousValue), tap(ev => this.consume(ev)), share());
}
getPrimaryKeys() {
return Array.from(this.store.keys());
}
clear() {
this.store.forEach(context => context.clear());
this.store.clear();
}
associate(id, fk) {
const context = this.getPrimaryKeyContext(id);
context === null || context === void 0 ? void 0 : context.foreignKeySet.add(fk);
}
disassociate(id, fk) {
const context = this.getPrimaryKeyContext(id);
context === null || context === void 0 ? void 0 : context.foreignKeySet.delete(fk);
}
hasAssociation(id, fk) {
const context = this.getPrimaryKeyContext(id);
return !!context && context.foreignKeySet.has(fk);
}
getRelatedKeys(id) {
const context = this.getPrimaryKeyContext(id);
return context ? context.getForeignKeys() : [];
}
getRelatedKeyCount(id) {
const context = this.getPrimaryKeyContext(id);
return context ? context.foreignKeySet.size : 0;
}
hasAnyAssociation(id) {
return this.getRelatedKeyCount(id) > 0;
}
getRelatedValues(id) {
return this.entityMap.getManyExisting(this.getRelatedKeys(id));
}
consume(ev) {
if (!ev)
return;
this.disassociate(ev.previousValue, ev.entityId);
this.associate(ev.currentValue, ev.entityId);
}
watchPrimaryKey(id) {
return this.changes.pipe(filter(ev => !!ev && (ev.previousValue === id || ev.currentValue === id)), map(() => this.getRelatedValues(id)), startWith(this.getRelatedValues(id)));
}
deletePrimaryKeyContext(id) {
const context = this.store.get(id);
if (context)
context.clear();
return this.store.delete(id);
}
getPrimaryKeyContext(id) {
let result = this.store.get(id);
if (!result && !isUndefined(id)) {
result = new OneToManyContext(id);
this.store.set(id, result);
}
return result;
}
}