@wiko/subscribable
Version:
Helpers for creating subscription-based event emitters
199 lines (193 loc) • 6.22 kB
JavaScript
import { WikoError, WIKO_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING, WIKO_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_MUST_NOT_POLL_BEFORE_RESOLVING_EXISTING_MESSAGE_PROMISE } from '@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 WikoError(WIKO_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING);
}
if (state.__hasPolled) {
throw new WikoError(
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;
}
};
}
export { createAsyncIterableFromDataPublisher, demultiplexDataPublisher, getDataPublisherFromEventEmitter };
//# sourceMappingURL=index.native.mjs.map
//# sourceMappingURL=index.native.mjs.map