suub
Version:
A simple pub/sub written in Typescript
297 lines (295 loc) • 9.95 kB
JavaScript
;
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
});