UNPKG

@wiko/subscribable

Version:

Helpers for creating subscription-based event emitters

203 lines (196 loc) 6.2 kB
'use strict'; var errors = require('@wiko/errors'); // src/async-iterable.ts // ../event-target-impl/dist/index.browser.mjs var o = globalThis.AbortController; var t = globalThis.EventTarget; // src/async-iterable.ts var EXPLICIT_ABORT_TOKEN; function createExplicitAbortToken() { return Symbol( process.env.NODE_ENV !== "production" ? "This symbol is thrown from a socket's iterator when the connection is explicitly aborted by the user" : void 0 ); } var UNINITIALIZED = Symbol(); function createAsyncIterableFromDataPublisher({ abortSignal, dataChannelName, dataPublisher, errorChannelName }) { const iteratorState = /* @__PURE__ */ new Map(); function publishErrorToAllIterators(reason) { for (const [iteratorKey, state] of iteratorState.entries()) { if (state.__hasPolled) { iteratorState.delete(iteratorKey); state.onError(reason); } else { state.publishQueue.push({ __type: 1 /* ERROR */, err: reason }); } } } const abortController = new o(); abortSignal.addEventListener("abort", () => { abortController.abort(); publishErrorToAllIterators(EXPLICIT_ABORT_TOKEN ||= createExplicitAbortToken()); }); const options = { signal: abortController.signal }; let firstError = UNINITIALIZED; dataPublisher.on( errorChannelName, (err) => { if (firstError === UNINITIALIZED) { firstError = err; abortController.abort(); publishErrorToAllIterators(err); } }, options ); dataPublisher.on( dataChannelName, (data) => { iteratorState.forEach((state, iteratorKey) => { if (state.__hasPolled) { const { onData } = state; iteratorState.set(iteratorKey, { __hasPolled: false, publishQueue: [] }); onData(data); } else { state.publishQueue.push({ __type: 0 /* DATA */, data }); } }); }, options ); return { async *[Symbol.asyncIterator]() { if (abortSignal.aborted) { return; } if (firstError !== UNINITIALIZED) { throw firstError; } const iteratorKey = Symbol(); iteratorState.set(iteratorKey, { __hasPolled: false, publishQueue: [] }); try { while (true) { const state = iteratorState.get(iteratorKey); if (!state) { throw new errors.WikoError(errors.WIKO_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING); } if (state.__hasPolled) { throw new errors.WikoError( errors.WIKO_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_MUST_NOT_POLL_BEFORE_RESOLVING_EXISTING_MESSAGE_PROMISE ); } const publishQueue = state.publishQueue; try { if (publishQueue.length) { state.publishQueue = []; for (const item of publishQueue) { if (item.__type === 0 /* DATA */) { yield item.data; } else { throw item.err; } } } else { yield await new Promise((resolve, reject) => { iteratorState.set(iteratorKey, { __hasPolled: true, onData: resolve, onError: reject }); }); } } catch (e) { if (e === (EXPLICIT_ABORT_TOKEN ||= createExplicitAbortToken())) { return; } else { throw e; } } } } finally { iteratorState.delete(iteratorKey); } } }; } // src/data-publisher.ts function getDataPublisherFromEventEmitter(eventEmitter) { return { on(channelName, subscriber, options) { function innerListener(ev) { if (ev instanceof CustomEvent) { const data = ev.detail; subscriber(data); } else { subscriber(); } } eventEmitter.addEventListener(channelName, innerListener, options); return () => { eventEmitter.removeEventListener(channelName, innerListener); }; } }; } // src/demultiplex.ts function demultiplexDataPublisher(publisher, sourceChannelName, messageTransformer) { let innerPublisherState; const eventTarget = new t(); const demultiplexedDataPublisher = getDataPublisherFromEventEmitter(eventTarget); return { ...demultiplexedDataPublisher, on(channelName, subscriber, options) { if (!innerPublisherState) { const innerPublisherUnsubscribe = publisher.on(sourceChannelName, (sourceMessage) => { const transformResult = messageTransformer(sourceMessage); if (!transformResult) { return; } const [destinationChannelName, message] = transformResult; eventTarget.dispatchEvent( new CustomEvent(destinationChannelName, { detail: message }) ); }); innerPublisherState = { dispose: innerPublisherUnsubscribe, numSubscribers: 0 }; } innerPublisherState.numSubscribers++; const unsubscribe = demultiplexedDataPublisher.on(channelName, subscriber, options); let isActive = true; function handleUnsubscribe() { if (!isActive) { return; } isActive = false; options?.signal.removeEventListener("abort", handleUnsubscribe); innerPublisherState.numSubscribers--; if (innerPublisherState.numSubscribers === 0) { innerPublisherState.dispose(); innerPublisherState = void 0; } unsubscribe(); } options?.signal.addEventListener("abort", handleUnsubscribe); return handleUnsubscribe; } }; } exports.createAsyncIterableFromDataPublisher = createAsyncIterableFromDataPublisher; exports.demultiplexDataPublisher = demultiplexDataPublisher; exports.getDataPublisherFromEventEmitter = getDataPublisherFromEventEmitter; //# sourceMappingURL=index.browser.cjs.map //# sourceMappingURL=index.browser.cjs.map