UNPKG

suub

Version:

A simple pub/sub written in Typescript

297 lines (295 loc) 9.95 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/mod.ts var mod_exports = {}; __export(mod_exports, { Suub: () => Suub, SuubErreur: () => SuubErreur }); module.exports = __toCommonJS(mod_exports); var import_erreur = require("erreur"); var Suub = (() => { const DEFAULT_CHANNEL = Symbol("DEFAULT_CHANNEL"); return { createSubscription, createVoidSubscription, createMultiSubscription }; function createMultiSubscription() { const rootSub = createSubscription(); return { unsubscribeAll: rootSub.unsubscribeAll, size: rootSub.size, destroy: rootSub.destroy, isDestroyed: rootSub.isDestroyed, deferred: rootSub.deferred, createChannel, createVoidChannel }; function createChannel() { return rootSub.channel(Symbol()); } function createVoidChannel() { return createChannel(); } } function createVoidSubscription(options = {}) { return createSubscription(options); } function createSubscription(options = {}) { const { onFirstSubscription, onLastUnsubscribe, onDestroy, maxRecursiveEmit = 1e3, maxSubscriptionCount = 1e4, maxUnsubscribeAllLoop = 1e3 } = options; const subscriptions = []; let nextSubscriptions = []; const emitQueue = []; let isEmitting = false; let destroyed = false; return createChannel(DEFAULT_CHANNEL); function createChannel(channel) { const sub = { subscribe, subscribeById, unsubscribe, unsubscribeById, isSubscribed, isSubscribedById, unsubscribeAll, emit, deferred, size, destroy, isDestroyed, channel: createChannel }; return sub; function emit(newValue) { return emitInternal(channel, newValue); } function subscribe(callback, onUnsubscribe) { return subscribeInternal(channel, callback, null, onUnsubscribe); } function subscribeById(subId, callback, onUnsubscribe) { return subscribeInternal(channel, callback, subId, onUnsubscribe); } function unsubscribeAll() { return unsubscribeAllInternal(channel); } function unsubscribe(callback) { unsubscribeInternal(null, callback); } function unsubscribeById(subId) { unsubscribeInternal(subId); } function unsubscribeInternal(subId, callback) { const subscription = findSubscription(channel, subId, callback); if (subscription) { subscription.unsubscribe(); } } function isSubscribed(callback) { return isSubscribedInternal(null, callback); } function isSubscribedById(subId) { return isSubscribedInternal(subId); } function isSubscribedInternal(subId, callback) { const subscription = findSubscription(channel, subId, callback); return subscription !== void 0; } function size() { return sizeInternal(channel); } } function sizeInternal(channel) { if (channel === DEFAULT_CHANNEL) { return subscriptions.length; } return subscriptions.filter((sub) => sub.channel === channel).length; } function isDestroyed() { return destroyed; } function destroy() { if (destroyed) { return; } destroyed = true; unsubscribeAllInternal(DEFAULT_CHANNEL); if (onDestroy) { onDestroy(); } } function emitInternal(channel, newValue) { if (destroyed) { throw SuubErreur.SubscriptionDestroyed.create(); } emitQueue.push({ value: newValue, channel }); if (isEmitting) { return; } isEmitting = true; handleEmitQueue(); } function deferred(callback) { if (isEmitting) { return callback(); } isEmitting = true; const result = callback(); handleEmitQueue(); return result; } function handleEmitQueue() { let emitQueueSafe = maxRecursiveEmit + 1; while (emitQueueSafe > 0 && emitQueue.length > 0) { emitQueueSafe--; const emitItem = emitQueue.shift(); nextSubscriptions = [...subscriptions]; let safe = maxSubscriptionCount; while (safe > 0 && nextSubscriptions.length > 0) { safe--; const subItem = nextSubscriptions.shift(); if (subItem.channel === DEFAULT_CHANNEL || emitItem.channel === DEFAULT_CHANNEL || subItem.channel === emitItem.channel) { subItem.callback(emitItem.value); } } if (safe <= 0) { isEmitting = false; throw SuubErreur.MaxSubscriptionCountReached.create(); } } isEmitting = false; if (emitQueueSafe <= 0) { throw SuubErreur.MaxRecursiveEmitReached.create(maxRecursiveEmit); } } function unsubscribeAllInternal(channel) { let safe = maxUnsubscribeAllLoop + subscriptions.length; while (safe > 0) { if (subscriptions.length === 0) { break; } const nextItem = subscriptions.find((item) => channel === DEFAULT_CHANNEL || item.channel === channel); if (!nextItem) { break; } safe--; nextItem.unsubscribe(); } if (safe <= 0) { throw SuubErreur.MaxUnsubscribeAllLoopReached.create(maxUnsubscribeAllLoop); } return; } function subscribeInternal(channel, callback, subId, onUnsubscribe) { if (destroyed) { throw SuubErreur.SubscriptionDestroyed.create(); } if (typeof callback !== "function") { throw SuubErreur.InvalidCallback.create(); } const alreadySubscribed = findSubscription(channel, subId, callback); if (alreadySubscribed) { if (subId !== null) { alreadySubscribed.callback = callback; } alreadySubscribed.onUnsubscribe = onUnsubscribe; const subIndex = subscriptions.indexOf(alreadySubscribed); subscriptions.splice(subIndex, 1); subscriptions.push(alreadySubscribed); return alreadySubscribed.unsubscribe; } let isSubscribed = true; subscriptions.push({ channel, subId, callback, unsubscribe: unsubscribeCurrent, onUnsubscribe }); if (subscriptions.length === 1 && onFirstSubscription) { onFirstSubscription(); } function unsubscribeCurrent() { if (!isSubscribed) { return; } isSubscribed = false; const index = subscriptions.findIndex( (i) => (channel === DEFAULT_CHANNEL || i.channel === channel) && i.callback === callback ); if (index === -1) { console.warn( `Subscribe (isSubscribed === true) callback is not in the subscriptions list. Please report a bug.` ); return; } const item = subscriptions[index]; subscriptions.splice(index, 1); const queueIndex = nextSubscriptions.findIndex((i) => i.callback === callback); if (queueIndex >= 0) { nextSubscriptions.splice(queueIndex, 1); } if (item.onUnsubscribe) { item.onUnsubscribe(); } if (subscriptions.length === 0 && onLastUnsubscribe) { onLastUnsubscribe(); } } return unsubscribeCurrent; } function findSubscription(channel, subId, callback) { return subscriptions.find((item) => { if (channel !== DEFAULT_CHANNEL && item.channel !== channel) { return false; } return subId === null ? item.callback === callback : item.subId === subId; }); } } })(); var SuubErreur = { SubscriptionDestroyed: import_erreur.Erreur.declare( "SubscriptionDestroyed", () => `The subscription has been destroyed` ).withTransform(() => null), MaxSubscriptionCountReached: import_erreur.Erreur.declare( "MaxSubscriptionCountReached", () => `The maxSubscriptionCount has been reached. If this is expected you can use the maxSubscriptionCount option to raise the limit` ).withTransform(() => null), MaxRecursiveEmitReached: import_erreur.Erreur.declare( "MaxRecursiveEmitReached", ({ limit }) => `The maxRecursiveEmit limit (${limit}) has been reached, did you emit() in a callback ? If this is expected you can use the maxRecursiveEmit option to raise the limit` ).withTransform((limit) => ({ limit })), MaxUnsubscribeAllLoopReached: import_erreur.Erreur.declare( "MaxUnsubscribeAllLoopReached", ({ limit }) => `The maxUnsubscribeAllLoop limit (${limit}) has been reached, did you call subscribe() in the onUnsubscribe callback then called unsubscribeAll ? If this is expected you can use the maxUnsubscribeAllLoop option to raise the limit` ).withTransform((limit) => ({ limit })), InvalidCallback: import_erreur.Erreur.declare("InvalidCallback", () => `The callback is not a function`).withTransform( () => null ) }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Suub, SuubErreur });