UNPKG

@data-client/core

Version:

Async State Management without the Management. REST, GraphQL, SSE, Websockets, Fetch

111 lines (98 loc) 2.96 kB
import { SUBSCRIBE, UNSUBSCRIBE } from '../actionTypes.js'; import Controller from '../controller/Controller.js'; import type { Manager, Middleware, UnsubscribeAction, SubscribeAction, } from '../types.js'; type Actions = UnsubscribeAction | SubscribeAction; /** Interface handling a single resource subscription */ export interface Subscription { add(frequency?: number): void; remove(frequency?: number): boolean; cleanup(): void; } /** The static class that constructs Subscription */ export interface SubscriptionConstructable { new ( action: Omit<SubscribeAction, 'type'>, controller: Controller, ): Subscription; } /** Handles subscription actions -> fetch or set actions * * Constructor takes a SubscriptionConstructable class to control how * subscriptions are handled. (e.g., polling, websockets) * * @see https://dataclient.io/docs/api/SubscriptionManager */ export default class SubscriptionManager< S extends SubscriptionConstructable = SubscriptionConstructable, > implements Manager<Actions> { protected subscriptions: { [key: string]: InstanceType<S>; } = {}; declare protected readonly Subscription: S; protected controller: Controller = new Controller(); constructor(Subscription: S) { this.Subscription = Subscription; } middleware: Middleware = controller => { this.controller = controller; return next => action => { switch (action.type) { case SUBSCRIBE: try { this.handleSubscribe(action); } catch (e) { console.error(e); } return Promise.resolve(); case UNSUBSCRIBE: this.handleUnsubscribe(action); return Promise.resolve(); default: return next(action); } }; }; /** Ensures all subscriptions are cleaned up. */ cleanup() { for (const key in this.subscriptions) { this.subscriptions[key].cleanup(); } } /** Called when middleware intercepts 'rdc/subscribe' action. * */ protected handleSubscribe(action: SubscribeAction) { const key = action.key; if (key in this.subscriptions) { const frequency = action.endpoint.pollFrequency; this.subscriptions[key].add(frequency); } else { this.subscriptions[key] = new this.Subscription( action, this.controller, ) as InstanceType<S>; } } /** Called when middleware intercepts 'rdc/unsubscribe' action. * */ protected handleUnsubscribe(action: UnsubscribeAction) { const key = action.key; /* istanbul ignore else */ if (key in this.subscriptions) { const frequency = action.endpoint.pollFrequency; const empty = this.subscriptions[key].remove(frequency); if (empty) { delete this.subscriptions[key]; } } else if (process.env.NODE_ENV !== 'production') { console.error(`Mismatched unsubscribe: ${key} is not subscribed`); } } }