106 lines
3.2 KiB
TypeScript
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];
|
|
}
|
|
}
|