RenaiApp/src/renderer/store/repositories/entity-repository.ts

106 lines
3.2 KiB
TypeScript

type EntityRepositorySubscriber<T> = (entity: T | undefined) => void;
type EntityRepositoryUnsubscriber = () => void;
export interface EntityRepositoryInterface<
Serialized extends IdentifiableInterface<Id>,
Id extends Identifier = number,
> {
/**
* get the current entity with the specified identifier from the state
*
* if the entity is not in the state, the entity is got from the API
*
* @param identifier - identifies the entity
*/
read(identifier: Id): Promise<Serialized | undefined>;
/**
* get the entity with the specified identifier from the API
* and call all subscribers with the refreshed entity
*
* @param identifier - identifies the entity
*/
refresh(identifier: Id): Promise<void>;
/**
* subscribes to changes of the entity with the specified identifier
*
* always remember to unsubscribe when the subscription is not needed anymore
*
* @param identifier - identifies the entity
* @param subscriber - this function is called on changes to the entity, with the entity as parameter
*
* @return EntityRepositoryUnsubscriber - the returned function must be called to unsubscribe from entity changes
*/
subscribe(identifier: Id, subscriber: EntityRepositorySubscriber<Serialized>): EntityRepositoryUnsubscriber;
}
export class EntityRepository<Serialized extends IdentifiableInterface<Id>, Id extends Identifier = number>
implements EntityRepositoryInterface<Serialized, Id>
{
protected state: {
[identifier in Id]?: Serialized;
} = {};
protected subscribers: {
[identifier in Id]?: Set<EntityRepositorySubscriber<Serialized>>;
} = {};
protected readonly apiRead: (identifier: Id) => Promise<Serialized>;
/**
* @param apiRead - get the fresh serialized entity from the API
*/
public constructor(apiRead: (identifier: Id) => Promise<Serialized>) {
this.apiRead = apiRead;
}
public async read(identifier: Id): Promise<Serialized | undefined> {
return this.load(identifier);
}
public async refresh(identifier: Id): Promise<void> {
await this.load(identifier, true);
this.run(identifier);
}
public subscribe(identifier: Id, subscriber: EntityRepositorySubscriber<Serialized>): EntityRepositoryUnsubscriber {
void this.read(identifier).then((serialized) => {
subscriber(serialized);
});
if (!this.subscribers[identifier]) {
this.subscribers[identifier] = new Set();
}
this.subscribers[identifier]?.add(subscriber);
return (): void => {
this.subscribers[identifier]?.delete(subscriber);
};
}
/**
* calls load current subscribers of the entity with the specified identifier
*
* this method is to be called after the state of that entity has changed
*/
protected run(identifier: Id): void {
const serialized = this.state[identifier];
if (serialized) {
this.subscribers[identifier]?.forEach((subscriber) => subscriber(serialized));
}
}
/**
* loads the entity, deserializes and adds it to the state if not already present
*/
private async load(identifier: Id, loadFresh = false): Promise<Serialized | undefined> {
if (!this.state[identifier] || loadFresh) {
this.state[identifier] = await this.apiRead(identifier);
}
return this.state[identifier];
}
}