@solana/subscribable 7.0.0 → 7.1.0-canary-20260812151931

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -222,6 +222,40 @@ Things to note:
222
222
  - `reset()` aborts the current connection and returns the store to `idle`, clearing `data` and `error`. A follow-up `connect()` opens a fresh stream.
223
223
  - Attach a caller-provided cancellation source via `store.withSignal(signal).connect()` — the signal is composed with the per-connection controller via `AbortSignal.any`. Aborting the caller's signal transitions the store to `error` with that abort reason.
224
224
 
225
+ ### `bridgeStoreToAsyncIterable(store, signal, shouldYield?)`
226
+
227
+ Adapts a `ReactiveStreamStore` into an `AsyncIterable`, so its _push_-based `subscribe` contract can be driven by _pull_-based consumers like TanStack Query's `experimental_streamedQuery` — anything that consumes a stream by `for await`-ing it.
228
+
229
+ The bridge only _observes_ the store. Just like every other consumer in this package — a store does nothing until you `connect()` it — the caller owns the store's lifecycle: `connect()` it yourself (typically bound to the same `signal`), and `reset()` it when you're done. The bridge subscribes, yields values, and unsubscribes when iteration ends.
230
+
231
+ ```ts
232
+ const store = rpcSubscriptions.slotNotifications().reactiveStore();
233
+ const controller = new AbortController();
234
+ // The caller owns the connection — bind it to the same signal so an abort tears it down.
235
+ store.withSignal(controller.signal).connect();
236
+ try {
237
+ for await (const notification of bridgeStoreToAsyncIterable(store, controller.signal)) {
238
+ console.log('Latest slot:', notification.slot);
239
+ }
240
+ } catch (e) {
241
+ console.error('The subscription errored', e);
242
+ } finally {
243
+ store.reset();
244
+ }
245
+ // Elsewhere: controller.abort() ends the loop cleanly.
246
+ ```
247
+
248
+ This is the store-backed sibling of `createAsyncIterableFromDataPublisher`. Reach for that helper when you have a raw `DataPublisher` and want every message queued and delivered; reach for `bridgeStoreToAsyncIterable` when you already have a `ReactiveStreamStore` and want to consume its unified lifecycle as an iterable. Note this is also distinct from an RPC subscription's own iterable (`await rpcSubscriptions.someNotifications().subscribe({ abortSignal })`), which vends messages straight off the transport without a store in between.
249
+
250
+ Because it reads through a store, its semantics follow the store's snapshot model:
251
+
252
+ - **Seeds from the current snapshot.** On iteration it reads the store's current state, so a value (or error) already present when iteration begins is delivered — you don't have to create the iterable before connecting.
253
+ - **Latest-wins.** A store only ever holds the most recent value, so if several notifications land between pulls only the freshest survives — a subscription consumer wants the current state, not a backlog. (This is the key behavioural difference from `createAsyncIterableFromDataPublisher`, which queues every message.)
254
+ - **`error` throws.** A store `error` rejects the consuming `for await`. If the store errors with a nullish payload, a `SolanaError` with code `SOLANA_ERROR__SUBSCRIBABLE__STREAM_CLOSED_WITHOUT_ERROR` is substituted so the failure still surfaces. An error takes precedence over a buffered value.
255
+ - **Abort ends cleanly.** Aborting `signal` completes the iterable (`done`) rather than throwing — an abort is teardown, not failure. Because a subscription never completes on its own, this is how the iterable terminates; bind the same signal to the store's connection so the abort tears the underlying stream down too.
256
+
257
+ The optional `shouldYield` predicate gates each `loaded` value before it is yielded; return `false` to drop it (useful for e.g. deduping by slot).
258
+
225
259
  ### `demultiplexDataPublisher(publisher, sourceChannelName, messageTransformer)`
226
260
 
227
261
  Given a channel that carries messages for multiple subscribers on a single channel name, this function returns a new `DataPublisher` that splits them into multiple channel names.
@@ -196,6 +196,53 @@ function createAsyncIterableFromDataPublisher({
196
196
  }
197
197
  };
198
198
  }
199
+ function bridgeStoreToAsyncIterable(store, signal, shouldYield) {
200
+ return {
201
+ async *[Symbol.asyncIterator]() {
202
+ let latest;
203
+ let failure;
204
+ let deferred = Promise.withResolvers();
205
+ const wake = () => {
206
+ const { resolve } = deferred;
207
+ deferred = Promise.withResolvers();
208
+ resolve();
209
+ };
210
+ const onChange = () => {
211
+ const state = store.getState();
212
+ if (state.status === "loaded") {
213
+ if (shouldYield && !shouldYield(state.data)) return;
214
+ latest = { value: state.data };
215
+ wake();
216
+ } else if (state.status === "error") {
217
+ failure = {
218
+ error: state.error ?? new errors.SolanaError(errors.SOLANA_ERROR__SUBSCRIBABLE__STREAM_CLOSED_WITHOUT_ERROR)
219
+ };
220
+ wake();
221
+ }
222
+ };
223
+ const onAbort = () => wake();
224
+ signal.addEventListener("abort", onAbort, { once: true });
225
+ const unsubscribe = store.subscribe(onChange);
226
+ onChange();
227
+ try {
228
+ while (true) {
229
+ if (signal.aborted) return;
230
+ if (failure) throw failure.error;
231
+ if (latest) {
232
+ const { value } = latest;
233
+ latest = void 0;
234
+ yield value;
235
+ continue;
236
+ }
237
+ await deferred.promise;
238
+ }
239
+ } finally {
240
+ signal.removeEventListener("abort", onAbort);
241
+ unsubscribe();
242
+ }
243
+ }
244
+ };
245
+ }
199
246
 
200
247
  // src/data-publisher.ts
201
248
  function getDataPublisherFromEventEmitter(eventEmitter) {
@@ -366,6 +413,7 @@ function createReactiveStoreFromDataPublisherFactory({
366
413
  };
367
414
  }
368
415
 
416
+ exports.bridgeStoreToAsyncIterable = bridgeStoreToAsyncIterable;
369
417
  exports.createAsyncIterableFromDataPublisher = createAsyncIterableFromDataPublisher;
370
418
  exports.createReactiveActionStore = createReactiveActionStore;
371
419
  exports.createReactiveStoreFromDataPublisherFactory = createReactiveStoreFromDataPublisherFactory;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../event-target-impl/src/index.browser.ts","../src/reactive-action-store.ts","../src/async-iterable.ts","../src/data-publisher.ts","../src/demultiplex.ts","../src/reactive-stream-store.ts"],"names":["AbortController","EventTarget","getAbortablePromise","SolanaError","SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING","SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_MUST_NOT_POLL_BEFORE_RESOLVING_EXISTING_MESSAGE_PROMISE","IDLE_STATE"],"mappings":"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","file":"index.browser.cjs","sourcesContent":["export const AbortController = globalThis.AbortController;\nexport const EventTarget = globalThis.EventTarget;\n","import { AbortController } from '@solana/event-target-impl';\nimport { getAbortablePromise } from '@solana/promises';\n\n/** Lifecycle status of a {@link ReactiveActionStore}. */\nexport type ReactiveActionStatus = 'error' | 'idle' | 'running' | 'success';\n\n/**\n * Discriminated state of a {@link ReactiveActionStore}, keyed by {@link ReactiveActionStatus}.\n *\n * `data` holds the most recent successful result and `error` holds the most recent failure. Both\n * persist through subsequent `running` states so call sites can keep rendering stale content\n * while a retry is in flight. `success` clears `error`; only `reset()` clears `data`.\n */\nexport type ReactiveActionState<TResult> =\n | { readonly data: TResult | undefined; readonly error: unknown; readonly status: 'error' }\n | { readonly data: TResult | undefined; readonly error: unknown; readonly status: 'running' }\n | { readonly data: TResult; readonly error: undefined; readonly status: 'success' }\n | { readonly data: undefined; readonly error: undefined; readonly status: 'idle' };\n\n/**\n * A framework-agnostic state machine that wraps an async function and exposes a\n * `{ dispatch, getState, subscribe, reset }` contract. Bridges trivially into\n * `useSyncExternalStore`, Svelte stores, Vue's `shallowRef`, and similar reactive primitives.\n *\n * @see {@link createReactiveActionStore}\n */\nexport type ReactiveActionStore<TArgs extends readonly unknown[], TResult> = {\n /**\n * Fire-and-forget dispatch. Returns `undefined` synchronously and never throws — failures\n * surface on state as `{ status: 'error' }`, and superseded or `reset()`-aborted calls produce\n * no state update. Use from UI event handlers; there's no promise to handle or `.catch`.\n *\n * @see {@link ReactiveActionStore.dispatchAsync} when you need the resolved value or propagated errors.\n * @see {@link ReactiveActionStore.withSignal} to attach a caller-provided `AbortSignal` to a dispatch.\n */\n readonly dispatch: (...args: TArgs) => void;\n /**\n * Promise-returning dispatch for imperative callers. Resolves with the wrapped function's\n * result on success. Rejects with the thrown error on failure, and with an `AbortError` when\n * the call is superseded or `reset()` is invoked — filter those with `isAbortError` from\n * `@solana/promises`.\n */\n readonly dispatchAsync: (...args: TArgs) => Promise<TResult>;\n /**\n * Returns the current lifecycle snapshot: `{ data, error, status }`. The returned object has\n * stable identity between state changes, making it safe to pass directly as the\n * `getSnapshot` argument to React's `useSyncExternalStore`.\n *\n * @see {@link ReactiveActionState}\n */\n readonly getState: () => ReactiveActionState<TResult>;\n /** Aborts any in-flight dispatch and resets the state to `{ status: 'idle' }`. */\n readonly reset: () => void;\n /** Registers a listener called on every state change. Returns an unsubscribe function. */\n readonly subscribe: (listener: () => void) => () => void;\n /**\n * Returns a thin wrapper exposing `dispatch` / `dispatchAsync` that compose `signal` with the\n * store's internal per-dispatch controller via `AbortSignal.any` — aborting either cancels\n * the in-flight call. Aborting the caller-provided signal surfaces the abort reason on state\n * as `{ status: 'error' }`; the internal controller path (supersession by a newer dispatch or\n * `reset()`) is silent by design so the newer dispatch owns state. Use this to attach a\n * caller-provided cancellation source (per-attempt timeout, shared kill switch, parent-context\n * signal) without touching the bare `dispatch` / `dispatchAsync` API.\n *\n * - Per-attempt timeout: `store.withSignal(AbortSignal.timeout(5_000)).dispatch(args)` — fresh\n * clock per call.\n * - Permanent kill switch: hold one `AbortController`, bind the wrapper once\n * (`const killable = store.withSignal(killCtrl.signal)`), and use `killable.dispatch(...)`\n * everywhere; aborting the controller cancels in-flight and short-circuits future calls.\n *\n * The wrapper exposes only `dispatch` / `dispatchAsync` — `getState` / `subscribe` / `reset`\n * remain store-level concerns on the parent.\n */\n readonly withSignal: (signal: AbortSignal) => {\n readonly dispatch: (...args: TArgs) => void;\n readonly dispatchAsync: (...args: TArgs) => Promise<TResult>;\n };\n};\n\n/**\n * Duck-type for objects that build a {@link ReactiveActionStore} on demand via `reactiveStore()`.\n * Satisfied by `PendingRpcRequest<T>`. The `[]` argument tuple is intentional — the operation's\n * arguments are already baked into the pending request, so each `dispatch()` re-fires the same\n * call.\n *\n * The returned store is in the `idle` state — the caller is responsible for calling `dispatch()`\n * to fire the first attempt. Attach a caller-provided cancellation source per dispatch via\n * `store.withSignal(signal).dispatch(...)` — see {@link ReactiveActionStore.withSignal}.\n *\n * @typeParam T - The value type resolved by the wrapped operation.\n *\n * @example\n * ```ts\n * function bind<T>(source: ReactiveActionSource<T>) {\n * const store = source.reactiveStore();\n * // Per-attempt timeout, fresh signal per call:\n * store.withSignal(AbortSignal.timeout(30_000)).dispatch();\n * return store;\n * }\n * ```\n *\n * @see {@link ReactiveActionStore}\n * @see {@link ReactiveStreamSource}\n */\nexport type ReactiveActionSource<T> = {\n reactiveStore(): ReactiveActionStore<[], T>;\n};\n\nconst IDLE_STATE: ReactiveActionState<never> = Object.freeze({\n data: undefined,\n error: undefined,\n status: 'idle',\n});\n\n/**\n * Wraps an async function in a {@link ReactiveActionStore}. Each `dispatch` creates a fresh\n * {@link AbortController} and aborts the previous one; the superseded call's outcome is dropped,\n * so only the most recent dispatch can mutate state.\n *\n * The wrapped function receives the `AbortSignal` as its first argument, followed by whatever\n * arguments were passed to `dispatch`. Callers attach their own cancellation source per-call via\n * {@link ReactiveActionStore.withSignal} — `store.withSignal(signal).dispatch(...)`. The caller's\n * signal is composed with the per-dispatch controller via `AbortSignal.any`, so aborting it\n * cancels the in-flight call and surfaces the abort reason on state.\n *\n * @typeParam TArgs - Argument tuple forwarded from `dispatch` to `fn`.\n * @typeParam TResult - Resolved value type of `fn`.\n * @param fn - Async function to wrap. Receives an {@link AbortSignal} plus the dispatch arguments.\n * @return A {@link ReactiveActionStore} exposing `dispatch`, `dispatchAsync`, `getState`, `subscribe`,\n * `reset`, and `withSignal`.\n *\n * @example\n * ```ts\n * const store = createReactiveActionStore(async (signal, accountId: Address) => {\n * const response = await fetch(`/api/accounts/${accountId}`, { signal });\n * return response.json();\n * });\n *\n * store.subscribe(() => console.log(store.getState()));\n * store.dispatch(someAccountId); // fire-and-forget; state is the source of truth\n *\n * // Per-attempt timeout — fresh signal per call:\n * store.withSignal(AbortSignal.timeout(30_000)).dispatch(someAccountId);\n *\n * // Imperative call with the resolved value:\n * const account = await store.dispatchAsync(someAccountId);\n * ```\n *\n * @see {@link ReactiveActionStore}\n */\nexport function createReactiveActionStore<TArgs extends readonly unknown[], TResult>(\n fn: (signal: AbortSignal, ...args: TArgs) => Promise<TResult>,\n): ReactiveActionStore<TArgs, TResult> {\n let state: ReactiveActionState<TResult> = IDLE_STATE;\n let currentController: AbortController | undefined;\n const listeners = new Set<() => void>();\n\n function setState(next: ReactiveActionState<TResult>) {\n if (state.status === next.status && state.data === next.data && state.error === next.error) {\n return;\n }\n state = next;\n listeners.forEach(listener => listener());\n }\n\n const dispatchAsyncWithSignal = async (userSignal: AbortSignal | undefined, ...args: TArgs): Promise<TResult> => {\n currentController?.abort();\n // If the caller's signal is already aborted, surface as error and bail.\n if (userSignal?.aborted) {\n setState({ data: state.data, error: userSignal.reason, status: 'error' });\n throw userSignal.reason;\n }\n const controller = new AbortController();\n currentController = controller;\n const signal = userSignal ? AbortSignal.any([controller.signal, userSignal]) : controller.signal;\n const previousData = state.data;\n const previousError = state.error;\n setState({ data: previousData, error: previousError, status: 'running' });\n try {\n const result = await getAbortablePromise(fn(signal, ...args), signal);\n if (signal.aborted) {\n throw signal.reason;\n }\n setState({ data: result, error: undefined, status: 'success' });\n return result;\n } catch (error) {\n // Superseded by a newer dispatch or `reset()` — drop silently so only the most recent\n // dispatch mutates state, and reject with the abort reason rather than any underlying\n // failure that happened to race the abort.\n if (controller.signal.aborted) {\n throw controller.signal.reason;\n }\n // Real failure or the caller-provided signal firing — surface as error state.\n setState({ data: previousData, error, status: 'error' });\n throw error;\n }\n };\n\n const dispatchAsync = (...args: TArgs): Promise<TResult> => dispatchAsyncWithSignal(undefined, ...args);\n const dispatch = (...args: TArgs): void => {\n dispatchAsync(...args).catch(() => {});\n };\n\n return {\n dispatch,\n dispatchAsync,\n getState: () => state,\n reset: () => {\n currentController?.abort();\n currentController = undefined;\n setState(IDLE_STATE);\n },\n subscribe: listener => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n withSignal: (signal: AbortSignal) => ({\n dispatch: (...args: TArgs): void => {\n dispatchAsyncWithSignal(signal, ...args).catch(() => {});\n },\n dispatchAsync: (...args: TArgs): Promise<TResult> => dispatchAsyncWithSignal(signal, ...args),\n }),\n };\n}\n","import {\n SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_MUST_NOT_POLL_BEFORE_RESOLVING_EXISTING_MESSAGE_PROMISE,\n SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING,\n SolanaError,\n} from '@solana/errors';\nimport { AbortController } from '@solana/event-target-impl';\n\nimport { DataPublisher } from './data-publisher';\n\ntype Config = Readonly<{\n /**\n * Triggering this abort signal will cause all iterators spawned from this iterator to return\n * once they have published all queued messages.\n */\n abortSignal: AbortSignal;\n /**\n * Messages from this channel of `dataPublisher` will be the ones yielded through the iterators.\n *\n * Messages only begin to be queued after the first time an iterator begins to poll. Channel\n * messages published before that time will be dropped.\n */\n dataChannelName: string;\n // FIXME: It would be nice to be able to constrain the type of `dataPublisher` to one that\n // definitely supports the `dataChannelName` and `errorChannelName` channels, and\n // furthermore publishes `TData` on the `dataChannelName` channel. This is more difficult\n // than it should be: https://tsplay.dev/NlZelW\n dataPublisher: DataPublisher;\n /**\n * Messages from this channel of `dataPublisher` will be the ones thrown through the iterators.\n *\n * Any new iterators created after the first error is encountered will reject with that error\n * when polled.\n */\n errorChannelName: string;\n}>;\n\nconst enum PublishType {\n DATA,\n ERROR,\n}\n\ntype IteratorKey = symbol;\ntype IteratorState<TData> =\n | {\n __hasPolled: false;\n publishQueue: (\n | {\n __type: PublishType.DATA;\n data: TData;\n }\n | {\n __type: PublishType.ERROR;\n err: unknown;\n }\n )[];\n }\n | {\n __hasPolled: true;\n onData: (data: TData) => void;\n onError: Parameters<ConstructorParameters<typeof Promise>[0]>[1];\n };\n\nlet EXPLICIT_ABORT_TOKEN: symbol;\nfunction createExplicitAbortToken() {\n // This function is an annoying workaround to prevent `process.env.NODE_ENV` from appearing at\n // the top level of this module and thwarting an optimizing compiler's attempt to tree-shake.\n return Symbol(\n process.env.NODE_ENV !== \"production\"\n ? \"This symbol is thrown from a socket's iterator when the connection is explicitly \" +\n 'aborted by the user'\n : undefined,\n );\n}\n\nconst UNINITIALIZED = Symbol();\n\n/**\n * Returns an `AsyncIterable` given a data publisher.\n *\n * The iterable will produce iterators that vend messages published to `dataChannelName` and will\n * throw the first time a message is published to `errorChannelName`. Triggering the abort signal\n * will cause all iterators spawned from this iterator to return once they have published all queued\n * messages.\n *\n * Things to note:\n *\n * - If a message is published over a channel before the `AsyncIterator` attached to it has polled\n * for the next result, the message will be queued in memory.\n * - Messages only begin to be queued after the first time an iterator begins to poll. Channel\n * messages published before that time will be dropped.\n * - If there are messages in the queue and an error occurs, all queued messages will be vended to\n * the iterator before the error is thrown.\n * - If there are messages in the queue and the abort signal fires, all queued messages will be\n * vended to the iterator after which it will return.\n * - Any new iterators created after the first error is encountered will reject with that error when\n * polled.\n *\n * @param config\n *\n * @example\n * ```ts\n * const iterable = createAsyncIterableFromDataPublisher({\n * abortSignal: AbortSignal.timeout(10_000),\n * dataChannelName: 'message',\n * dataPublisher,\n * errorChannelName: 'error',\n * });\n * try {\n * for await (const message of iterable) {\n * console.log('Got message', message);\n * }\n * } catch (e) {\n * console.error('An error was published to the error channel', e);\n * } finally {\n * console.log(\"It's been 10 seconds; that's enough for now.\");\n * }\n * ```\n */\nexport function createAsyncIterableFromDataPublisher<TData>({\n abortSignal,\n dataChannelName,\n dataPublisher,\n errorChannelName,\n}: Config): AsyncIterable<TData> {\n const iteratorState: Map<IteratorKey, IteratorState<TData>> = new Map();\n function publishErrorToAllIterators(reason: unknown) {\n for (const [iteratorKey, state] of iteratorState.entries()) {\n if (state.__hasPolled) {\n iteratorState.delete(iteratorKey);\n state.onError(reason);\n } else {\n state.publishQueue.push({\n __type: PublishType.ERROR,\n err: reason,\n });\n }\n }\n }\n const abortController = new AbortController();\n abortSignal.addEventListener('abort', () => {\n abortController.abort();\n publishErrorToAllIterators((EXPLICIT_ABORT_TOKEN ||= createExplicitAbortToken()));\n });\n const options = { signal: abortController.signal } as const;\n let firstError: unknown = UNINITIALIZED;\n dataPublisher.on(\n errorChannelName,\n err => {\n if (firstError === UNINITIALIZED) {\n firstError = err;\n abortController.abort();\n publishErrorToAllIterators(err);\n }\n },\n options,\n );\n dataPublisher.on(\n dataChannelName,\n data => {\n iteratorState.forEach((state, iteratorKey) => {\n if (state.__hasPolled) {\n const { onData } = state;\n iteratorState.set(iteratorKey, { __hasPolled: false, publishQueue: [] });\n onData(data as TData);\n } else {\n state.publishQueue.push({\n __type: PublishType.DATA,\n data: data as TData,\n });\n }\n });\n },\n options,\n );\n return {\n async *[Symbol.asyncIterator]() {\n if (abortSignal.aborted) {\n return;\n }\n if (firstError !== UNINITIALIZED) {\n throw firstError;\n }\n const iteratorKey = Symbol();\n iteratorState.set(iteratorKey, { __hasPolled: false, publishQueue: [] });\n try {\n while (true) {\n const state = iteratorState.get(iteratorKey);\n if (!state) {\n // There should always be state by now.\n throw new SolanaError(SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING);\n }\n if (state.__hasPolled) {\n // You should never be able to poll twice in a row.\n throw new SolanaError(\n SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_MUST_NOT_POLL_BEFORE_RESOLVING_EXISTING_MESSAGE_PROMISE,\n );\n }\n const publishQueue = state.publishQueue;\n try {\n if (publishQueue.length) {\n state.publishQueue = [];\n for (const item of publishQueue) {\n if (item.__type === PublishType.DATA) {\n yield item.data;\n } else {\n throw item.err;\n }\n }\n } else {\n yield await new Promise<TData>((resolve, reject) => {\n iteratorState.set(iteratorKey, {\n __hasPolled: true,\n onData: resolve,\n onError: reject,\n });\n });\n }\n } catch (e) {\n if (e === (EXPLICIT_ABORT_TOKEN ||= createExplicitAbortToken())) {\n return;\n } else {\n throw e;\n }\n }\n }\n } finally {\n iteratorState.delete(iteratorKey);\n }\n },\n };\n}\n","import { TypedEventEmitter, TypedEventTarget } from './event-emitter';\n\ntype UnsubscribeFn = () => void;\n\n/**\n * Represents an object with an `on` function that you can call to subscribe to certain data over a\n * named channel.\n *\n * @example\n * ```ts\n * let dataPublisher: DataPublisher<{ error: SolanaError }>;\n * dataPublisher.on('data', handleData); // ERROR. `data` is not a known channel name.\n * dataPublisher.on('error', e => {\n * console.error(e);\n * }); // OK.\n * ```\n */\nexport interface DataPublisher<TDataByChannelName extends Record<string, unknown> = Record<string, unknown>> {\n /**\n * Call this to subscribe to data over a named channel.\n *\n * @param channelName The name of the channel on which to subscribe for messages\n * @param subscriber The function to call when a message becomes available\n * @param options.signal An abort signal you can fire to unsubscribe\n *\n * @returns A function that you can call to unsubscribe\n */\n on<const TChannelName extends keyof TDataByChannelName>(\n channelName: TChannelName,\n subscriber: (data: TDataByChannelName[TChannelName]) => void,\n options?: { signal: AbortSignal },\n ): UnsubscribeFn;\n}\n\n/**\n * Returns an object with an `on` function that you can call to subscribe to certain data over a\n * named channel.\n *\n * The `on` function returns an unsubscribe function.\n *\n * @example\n * ```ts\n * const socketDataPublisher = getDataPublisherFromEventEmitter(new WebSocket('wss://api.devnet.solana.com'));\n * const unsubscribe = socketDataPublisher.on('message', message => {\n * if (JSON.parse(message.data).id === 42) {\n * console.log('Got response 42');\n * unsubscribe();\n * }\n * });\n * ```\n */\nexport function getDataPublisherFromEventEmitter<TEventMap extends Record<string, Event>>(\n eventEmitter: TypedEventEmitter<TEventMap> | TypedEventTarget<TEventMap>,\n): DataPublisher<{\n [TEventType in keyof TEventMap]: TEventMap[TEventType] extends CustomEvent ? TEventMap[TEventType]['detail'] : null;\n}> {\n return {\n on(channelName, subscriber, options) {\n function innerListener(ev: Event) {\n if (ev instanceof CustomEvent) {\n const data = (ev as CustomEvent<TEventMap[typeof channelName]>).detail;\n (subscriber as unknown as (data: TEventMap[typeof channelName]) => void)(data);\n } else {\n (subscriber as () => void)();\n }\n }\n eventEmitter.addEventListener(channelName, innerListener, options);\n return () => {\n eventEmitter.removeEventListener(channelName, innerListener);\n };\n },\n };\n}\n","import { EventTarget } from '@solana/event-target-impl';\n\nimport { DataPublisher, getDataPublisherFromEventEmitter } from './data-publisher';\n\n/**\n * Given a channel that carries messages for multiple subscribers on a single channel name, this\n * function returns a new {@link DataPublisher} that splits them into multiple channel names.\n *\n * @param messageTransformer A function that receives the message as the first argument, and returns\n * a tuple of the derived channel name and the message.\n *\n * @example\n * Imagine a channel that carries multiple notifications whose destination is contained within the\n * message itself.\n *\n * ```ts\n * const demuxedDataPublisher = demultiplexDataPublisher(channel, 'message', message => {\n * const destinationChannelName = `notification-for:${message.subscriberId}`;\n * return [destinationChannelName, message];\n * });\n * ```\n *\n * Now you can subscribe to _only_ the messages you are interested in, without having to subscribe\n * to the entire `'message'` channel and filter out the messages that are not for you.\n *\n * ```ts\n * demuxedDataPublisher.on(\n * 'notification-for:123',\n * message => {\n * console.log('Got a message for subscriber 123', message);\n * },\n * { signal: AbortSignal.timeout(5_000) },\n * );\n * ```\n */\nexport function demultiplexDataPublisher<\n TDataPublisher extends DataPublisher,\n const TChannelName extends Parameters<TDataPublisher['on']>[0],\n>(\n publisher: TDataPublisher,\n sourceChannelName: TChannelName,\n messageTransformer: (\n // FIXME: Deriving the type of the message from `TDataPublisher` and `TChannelName` would\n // help callers to constrain their transform functions.\n message: unknown,\n ) => [destinationChannelName: string, message: unknown] | void,\n): DataPublisher {\n let innerPublisherState:\n | {\n readonly dispose: () => void;\n numSubscribers: number;\n }\n | undefined;\n const eventTarget = new EventTarget();\n const demultiplexedDataPublisher = getDataPublisherFromEventEmitter(eventTarget);\n return {\n ...demultiplexedDataPublisher,\n on(channelName, subscriber, options) {\n if (!innerPublisherState) {\n const innerPublisherUnsubscribe = publisher.on(sourceChannelName, sourceMessage => {\n const transformResult = messageTransformer(sourceMessage);\n if (!transformResult) {\n return;\n }\n const [destinationChannelName, message] = transformResult;\n eventTarget.dispatchEvent(\n new CustomEvent(destinationChannelName, {\n detail: message,\n }),\n );\n });\n innerPublisherState = {\n dispose: innerPublisherUnsubscribe,\n numSubscribers: 0,\n };\n }\n innerPublisherState.numSubscribers++;\n const unsubscribe = demultiplexedDataPublisher.on(channelName, subscriber, options);\n let isActive = true;\n function handleUnsubscribe() {\n if (!isActive) {\n return;\n }\n isActive = false;\n options?.signal.removeEventListener('abort', handleUnsubscribe);\n innerPublisherState!.numSubscribers--;\n if (innerPublisherState!.numSubscribers === 0) {\n innerPublisherState!.dispose();\n innerPublisherState = undefined;\n }\n unsubscribe();\n }\n options?.signal.addEventListener('abort', handleUnsubscribe);\n return handleUnsubscribe;\n },\n };\n}\n","import { AbortController } from '@solana/event-target-impl';\n\nimport { DataPublisher } from './data-publisher';\n\ntype FactoryConfig = Readonly<{\n // FIXME: It would be nice to be able to constrain the type returned by `createDataPublisher` to one that\n // definitely supports the `dataChannelName` and `errorChannelName` channels, and\n // furthermore publishes `TData` on the `dataChannelName` channel. This is more difficult\n // than it should be: https://tsplay.dev/NlZelW\n /**\n * An async factory that produces a fresh {@link DataPublisher} each time it is invoked. Called\n * on every {@link ReactiveStreamStore.connect | `connect()`}.\n *\n * Receives an {@link AbortSignal} that fires when this specific connection window should tear\n * down — composed from the per-connection inner controller and (if attached via\n * {@link ReactiveStreamStore.withSignal | `withSignal()`}) the caller-provided signal via\n * `AbortSignal.any`. Thread it into the underlying transport's own cancellation so the\n * connection itself stops on per-connection abort, not just the stream-store's listeners.\n * Rejections surface as a store error.\n */\n createDataPublisher: (signal: AbortSignal) => Promise<DataPublisher>;\n /**\n * Messages from this channel of the produced `DataPublisher` will be used to update the store's\n * state.\n */\n dataChannelName: string;\n /**\n * Messages from this channel of the produced `DataPublisher` will transition the store to an\n * error state, preserving the last known value.\n */\n errorChannelName: string;\n}>;\n\n/**\n * The lifecycle state of a {@link ReactiveStreamStore} as a single snapshot.\n *\n * - `idle`: the store has not yet been connected, or has been reset via\n * {@link ReactiveStreamStore.reset | `reset()`}. Call\n * {@link ReactiveStreamStore.connect | `connect()`} to open the underlying stream.\n * - `loading`: a connection is in progress. `data` and `error` are preserved from the previous\n * connection (if any) — stale-while-revalidate UX. A subsequent `loaded` clears `error`; a\n * subsequent `error` replaces it.\n * - `loaded`: a value has been received and no error is active.\n * - `error`: the stream failed. `data` holds the last known value (or `undefined` if none ever\n * arrived) and `error` holds the failure.\n */\nexport type ReactiveState<T> =\n | { readonly data: T | undefined; readonly error: unknown; readonly status: 'error' }\n | { readonly data: T | undefined; readonly error: unknown; readonly status: 'loading' }\n | { readonly data: T; readonly error: undefined; readonly status: 'loaded' }\n | { readonly data: undefined; readonly error: undefined; readonly status: 'idle' };\n\nconst IDLE_STATE: ReactiveState<never> = Object.freeze({\n data: undefined,\n error: undefined,\n status: 'idle',\n});\n\n/**\n * A reactive store that holds the latest value published to a data channel and allows external\n * systems to subscribe to changes. Compatible with `useSyncExternalStore`, Svelte stores, Solid's\n * `from()`, and other reactive primitives that expect a `{ subscribe, getState }` contract.\n *\n * The store starts in `status: 'idle'`. Call {@link ReactiveStreamStore.connect | `connect()`}\n * to open the underlying stream; the store transitions through `loading` → `loaded` (or `error`).\n * Subsequent `connect()` calls also pass through `loading` while preserving the last known\n * `data` and `error` (stale-while-revalidate).\n *\n * @example\n * ```ts\n * // React — the unified state snapshot has stable identity per update, making it suitable as\n * // the second argument to `useSyncExternalStore`.\n * const state = useSyncExternalStore(store.subscribe, store.getState);\n * useEffect(() => {\n * store.connect();\n * return () => store.reset();\n * }, [store]);\n * if (state.status === 'error') return <ErrorMessage error={state.error} onRetry={store.connect} />;\n * if (state.status === 'loading' || state.status === 'idle') return <Spinner />;\n * return <View data={state.data} />;\n * ```\n *\n * @see {@link createReactiveStoreFromDataPublisherFactory}\n */\nexport type ReactiveStreamStore<T> = {\n /**\n * Open the underlying stream. Aborts any currently active connection, invokes the configured\n * factory, and transitions the store to `loading` (preserving the last known `data` and\n * `error` for stale-while-revalidate) before settling into `loaded` (on data) or `error`\n * (on failure).\n */\n connect(): void;\n /**\n * Returns the current lifecycle snapshot: `{ data, error, status }`. The returned object has\n * stable identity between state changes, making it safe to pass directly as the\n * `getSnapshot` argument to React's `useSyncExternalStore`.\n *\n * @see {@link ReactiveState}\n */\n getState(): ReactiveState<T>;\n /**\n * Aborts any currently active connection and resets the store to `{ status: 'idle' }`. Both\n * `data` and `error` are cleared. Use this to tear down the connection without permanently\n * killing the store — a follow-up {@link ReactiveStreamStore.connect | `connect()`} will open\n * a fresh stream.\n */\n reset(): void;\n /**\n * Registers a callback to be called whenever the state changes or an error is received.\n * Returns an unsubscribe function. Safe to call multiple times.\n */\n subscribe(callback: () => void): () => void;\n /**\n * Returns a thin wrapper exposing `connect()` that composes `signal` with the store's internal\n * per-connection controller via `AbortSignal.any` — aborting either tears down the active\n * connection. Aborting the caller-provided signal surfaces the abort reason on state as\n * `{ status: 'error' }`; the internal controller path (supersession by a newer `connect()` or\n * `reset()`) is silent by design so the newer call owns state. Use this to attach a\n * caller-provided cancellation source (per-connection timeout, shared kill switch,\n * parent-context signal) without touching the bare `connect()` API.\n *\n * - Per-connection timeout: `store.withSignal(AbortSignal.timeout(30_000)).connect()` — fresh\n * clock per call.\n * - Permanent kill switch: hold one `AbortController`, bind the wrapper once\n * (`const killable = store.withSignal(killCtrl.signal)`), and use `killable.connect()`\n * everywhere; aborting the controller cancels the active connection and short-circuits\n * future calls through the bound wrapper.\n *\n * The wrapper exposes only `connect()` — `getState` / `subscribe` / `reset` remain\n * store-level concerns on the parent.\n */\n withSignal(signal: AbortSignal): { readonly connect: () => void };\n};\n\n/**\n * Duck-type for objects that build a {@link ReactiveStreamStore} on demand via a `reactiveStore()`\n * method. Satisfied by `PendingRpcSubscriptionsRequest<T>`. Reactive-framework bindings (e.g.\n * React's `useSubscription`) consume this duck-type so they don't have to name a concrete producer\n * type.\n *\n * The returned store is in `status: 'idle'` — the caller is responsible for invoking\n * {@link ReactiveStreamStore.connect | `connect()`} to open the underlying stream. Attach a\n * caller-provided cancellation source via {@link ReactiveStreamStore.withSignal | `withSignal()`}\n * — `store.withSignal(signal).connect()`.\n *\n * @typeParam T - The value type emitted by the resulting stream store.\n *\n * @example\n * ```ts\n * function bindWithTimeout<T>(source: ReactiveStreamSource<T>) {\n * const store = source.reactiveStore();\n * store.withSignal(AbortSignal.timeout(30_000)).connect();\n * return store;\n * }\n * ```\n *\n * @see {@link ReactiveStreamStore}\n * @see {@link ReactiveActionSource}\n */\nexport type ReactiveStreamSource<T> = {\n reactiveStore(): ReactiveStreamStore<T>;\n};\n\n/**\n * Returns a {@link ReactiveStreamStore} that wires itself to a fresh {@link DataPublisher} on\n * every {@link ReactiveStreamStore.connect | `connect()`}.\n *\n * The store accepts a `createDataPublisher` factory rather than a ready-made publisher — that\n * lets the store tear down a broken stream and open a new one without losing subscribers or the\n * last known value. The factory receives the per-connection signal so the underlying transport\n * can stop on per-connection abort, not just the stream-store's listeners.\n *\n * Things to note:\n *\n * - The returned store starts in `status: 'idle'`. Call `connect()` to open the first stream.\n * - `createDataPublisher` is invoked on every `connect()`. The store transitions through\n * `loading`, preserving the last known `data` and `error` (stale-while-revalidate).\n * - If `createDataPublisher` rejects, the store transitions to `status: 'error'` with the\n * rejection as the error. Call `connect()` to try again.\n * - `reset()` aborts the current connection and returns the store to `idle`, clearing `data`\n * and `error`. A follow-up `connect()` opens a fresh stream.\n * - Attach a caller-provided cancellation source via\n * {@link ReactiveStreamStore.withSignal | `withSignal()`} — `store.withSignal(signal).connect()`\n * composes the signal with the per-connection controller. Aborting the caller's signal\n * transitions the store to `error` with that abort reason.\n *\n * @param config\n *\n * @example\n * ```ts\n * const store = createReactiveStoreFromDataPublisherFactory({\n * createDataPublisher: signal => getDataPublisherFromEventEmitter(new WebSocket(url, { signal })),\n * dataChannelName: 'message',\n * errorChannelName: 'error',\n * });\n * const unsubscribe = store.subscribe(() => {\n * const snapshot = store.getState();\n * if (snapshot.status === 'error') console.error('Connection failed:', snapshot.error);\n * else if (snapshot.status === 'loaded') console.log('Latest:', snapshot.data);\n * });\n * // Fresh 30-second clock per connection attempt:\n * store.withSignal(AbortSignal.timeout(30_000)).connect();\n * ```\n */\nexport function createReactiveStoreFromDataPublisherFactory<TData>({\n createDataPublisher,\n dataChannelName,\n errorChannelName,\n}: FactoryConfig): ReactiveStreamStore<TData> {\n let currentState: ReactiveState<TData> = IDLE_STATE;\n let currentInnerController: AbortController | undefined;\n const subscribers = new Set<() => void>();\n\n function notify() {\n subscribers.forEach(cb => cb());\n }\n\n function setState(next: ReactiveState<TData>) {\n if (\n currentState.status === next.status &&\n currentState.data === next.data &&\n currentState.error === next.error\n ) {\n return;\n }\n currentState = next;\n notify();\n }\n\n function performConnect(callerSignal: AbortSignal | undefined) {\n // Abort any currently active connection before starting a fresh one.\n currentInnerController?.abort();\n // If the caller's signal is already aborted, surface as error and bail.\n if (callerSignal?.aborted) {\n setState({ data: currentState.data, error: callerSignal.reason, status: 'error' });\n return;\n }\n // Transition to `loading`, preserving the last known `data` and `error` for SWR. If\n // already `loading` with the same data/error, `setState` no-ops — no spurious notify.\n setState({ data: currentState.data, error: currentState.error, status: 'loading' });\n // Inner signal is passed to the data publisher (composed with caller signal if any).\n const innerController = new AbortController();\n currentInnerController = innerController;\n const signal = callerSignal ? AbortSignal.any([innerController.signal, callerSignal]) : innerController.signal;\n // Caller's signal aborting (not just supersede via the inner controller) transitions the\n // store to error with the caller's abort reason. Scoped to the inner signal so the\n // listener is removed automatically on reconnect / reset.\n if (callerSignal) {\n callerSignal.addEventListener(\n 'abort',\n () => {\n if (innerController.signal.aborted) return;\n setState({ data: currentState.data, error: callerSignal.reason, status: 'error' });\n innerController.abort(callerSignal.reason);\n },\n { signal: innerController.signal },\n );\n }\n createDataPublisher(signal).then(\n publisher => {\n if (signal.aborted) return;\n publisher.on(\n dataChannelName,\n data => {\n setState({ data: data as TData, error: undefined, status: 'loaded' });\n },\n { signal },\n );\n publisher.on(\n errorChannelName,\n err => {\n if (currentState.status === 'error') return;\n setState({ data: currentState.data, error: err, status: 'error' });\n innerController.abort(err);\n },\n { signal },\n );\n },\n err => {\n if (signal.aborted) return;\n setState({ data: currentState.data, error: err, status: 'error' });\n innerController.abort(err);\n },\n );\n }\n\n function performReset() {\n currentInnerController?.abort();\n currentInnerController = undefined;\n setState(IDLE_STATE);\n }\n\n return {\n connect(): void {\n performConnect(undefined);\n },\n getState(): ReactiveState<TData> {\n return currentState;\n },\n reset: performReset,\n subscribe(callback: () => void): () => void {\n subscribers.add(callback);\n return () => {\n subscribers.delete(callback);\n };\n },\n withSignal(signal: AbortSignal) {\n return {\n connect(): void {\n performConnect(signal);\n },\n };\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../../event-target-impl/src/index.browser.ts","../src/reactive-action-store.ts","../src/async-iterable.ts","../src/bridge-store-to-async-iterable.ts","../src/data-publisher.ts","../src/demultiplex.ts","../src/reactive-stream-store.ts"],"names":["AbortController","EventTarget","getAbortablePromise","SolanaError","SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING","SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_MUST_NOT_POLL_BEFORE_RESOLVING_EXISTING_MESSAGE_PROMISE","SOLANA_ERROR__SUBSCRIBABLE__STREAM_CLOSED_WITHOUT_ERROR","IDLE_STATE"],"mappings":"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","file":"index.browser.cjs","sourcesContent":["export const AbortController = globalThis.AbortController;\nexport const EventTarget = globalThis.EventTarget;\n","import { AbortController } from '@solana/event-target-impl';\nimport { getAbortablePromise } from '@solana/promises';\n\n/** Lifecycle status of a {@link ReactiveActionStore}. */\nexport type ReactiveActionStatus = 'error' | 'idle' | 'running' | 'success';\n\n/**\n * Discriminated state of a {@link ReactiveActionStore}, keyed by {@link ReactiveActionStatus}.\n *\n * `data` holds the most recent successful result and `error` holds the most recent failure. Both\n * persist through subsequent `running` states so call sites can keep rendering stale content\n * while a retry is in flight. `success` clears `error`; only `reset()` clears `data`.\n */\nexport type ReactiveActionState<TResult> =\n | { readonly data: TResult | undefined; readonly error: unknown; readonly status: 'error' }\n | { readonly data: TResult | undefined; readonly error: unknown; readonly status: 'running' }\n | { readonly data: TResult; readonly error: undefined; readonly status: 'success' }\n | { readonly data: undefined; readonly error: undefined; readonly status: 'idle' };\n\n/**\n * A framework-agnostic state machine that wraps an async function and exposes a\n * `{ dispatch, getState, subscribe, reset }` contract. Bridges trivially into\n * `useSyncExternalStore`, Svelte stores, Vue's `shallowRef`, and similar reactive primitives.\n *\n * @see {@link createReactiveActionStore}\n */\nexport type ReactiveActionStore<TArgs extends readonly unknown[], TResult> = {\n /**\n * Fire-and-forget dispatch. Returns `undefined` synchronously and never throws — failures\n * surface on state as `{ status: 'error' }`, and superseded or `reset()`-aborted calls produce\n * no state update. Use from UI event handlers; there's no promise to handle or `.catch`.\n *\n * @see {@link ReactiveActionStore.dispatchAsync} when you need the resolved value or propagated errors.\n * @see {@link ReactiveActionStore.withSignal} to attach a caller-provided `AbortSignal` to a dispatch.\n */\n readonly dispatch: (...args: TArgs) => void;\n /**\n * Promise-returning dispatch for imperative callers. Resolves with the wrapped function's\n * result on success. Rejects with the thrown error on failure, and with an `AbortError` when\n * the call is superseded or `reset()` is invoked — filter those with `isAbortError` from\n * `@solana/promises`.\n */\n readonly dispatchAsync: (...args: TArgs) => Promise<TResult>;\n /**\n * Returns the current lifecycle snapshot: `{ data, error, status }`. The returned object has\n * stable identity between state changes, making it safe to pass directly as the\n * `getSnapshot` argument to React's `useSyncExternalStore`.\n *\n * @see {@link ReactiveActionState}\n */\n readonly getState: () => ReactiveActionState<TResult>;\n /** Aborts any in-flight dispatch and resets the state to `{ status: 'idle' }`. */\n readonly reset: () => void;\n /** Registers a listener called on every state change. Returns an unsubscribe function. */\n readonly subscribe: (listener: () => void) => () => void;\n /**\n * Returns a thin wrapper exposing `dispatch` / `dispatchAsync` that compose `signal` with the\n * store's internal per-dispatch controller via `AbortSignal.any` — aborting either cancels\n * the in-flight call. Aborting the caller-provided signal surfaces the abort reason on state\n * as `{ status: 'error' }`; the internal controller path (supersession by a newer dispatch or\n * `reset()`) is silent by design so the newer dispatch owns state. Use this to attach a\n * caller-provided cancellation source (per-attempt timeout, shared kill switch, parent-context\n * signal) without touching the bare `dispatch` / `dispatchAsync` API.\n *\n * - Per-attempt timeout: `store.withSignal(AbortSignal.timeout(5_000)).dispatch(args)` — fresh\n * clock per call.\n * - Permanent kill switch: hold one `AbortController`, bind the wrapper once\n * (`const killable = store.withSignal(killCtrl.signal)`), and use `killable.dispatch(...)`\n * everywhere; aborting the controller cancels in-flight and short-circuits future calls.\n *\n * The wrapper exposes only `dispatch` / `dispatchAsync` — `getState` / `subscribe` / `reset`\n * remain store-level concerns on the parent.\n */\n readonly withSignal: (signal: AbortSignal) => {\n readonly dispatch: (...args: TArgs) => void;\n readonly dispatchAsync: (...args: TArgs) => Promise<TResult>;\n };\n};\n\n/**\n * Duck-type for objects that build a {@link ReactiveActionStore} on demand via `reactiveStore()`.\n * Satisfied by `PendingRpcRequest<T>`. The `[]` argument tuple is intentional — the operation's\n * arguments are already baked into the pending request, so each `dispatch()` re-fires the same\n * call.\n *\n * The returned store is in the `idle` state — the caller is responsible for calling `dispatch()`\n * to fire the first attempt. Attach a caller-provided cancellation source per dispatch via\n * `store.withSignal(signal).dispatch(...)` — see {@link ReactiveActionStore.withSignal}.\n *\n * @typeParam T - The value type resolved by the wrapped operation.\n *\n * @example\n * ```ts\n * function bind<T>(source: ReactiveActionSource<T>) {\n * const store = source.reactiveStore();\n * // Per-attempt timeout, fresh signal per call:\n * store.withSignal(AbortSignal.timeout(30_000)).dispatch();\n * return store;\n * }\n * ```\n *\n * @see {@link ReactiveActionStore}\n * @see {@link ReactiveStreamSource}\n */\nexport type ReactiveActionSource<T> = {\n reactiveStore(): ReactiveActionStore<[], T>;\n};\n\nconst IDLE_STATE: ReactiveActionState<never> = Object.freeze({\n data: undefined,\n error: undefined,\n status: 'idle',\n});\n\n/**\n * Wraps an async function in a {@link ReactiveActionStore}. Each `dispatch` creates a fresh\n * {@link AbortController} and aborts the previous one; the superseded call's outcome is dropped,\n * so only the most recent dispatch can mutate state.\n *\n * The wrapped function receives the `AbortSignal` as its first argument, followed by whatever\n * arguments were passed to `dispatch`. Callers attach their own cancellation source per-call via\n * {@link ReactiveActionStore.withSignal} — `store.withSignal(signal).dispatch(...)`. The caller's\n * signal is composed with the per-dispatch controller via `AbortSignal.any`, so aborting it\n * cancels the in-flight call and surfaces the abort reason on state.\n *\n * @typeParam TArgs - Argument tuple forwarded from `dispatch` to `fn`.\n * @typeParam TResult - Resolved value type of `fn`.\n * @param fn - Async function to wrap. Receives an {@link AbortSignal} plus the dispatch arguments.\n * @return A {@link ReactiveActionStore} exposing `dispatch`, `dispatchAsync`, `getState`, `subscribe`,\n * `reset`, and `withSignal`.\n *\n * @example\n * ```ts\n * const store = createReactiveActionStore(async (signal, accountId: Address) => {\n * const response = await fetch(`/api/accounts/${accountId}`, { signal });\n * return response.json();\n * });\n *\n * store.subscribe(() => console.log(store.getState()));\n * store.dispatch(someAccountId); // fire-and-forget; state is the source of truth\n *\n * // Per-attempt timeout — fresh signal per call:\n * store.withSignal(AbortSignal.timeout(30_000)).dispatch(someAccountId);\n *\n * // Imperative call with the resolved value:\n * const account = await store.dispatchAsync(someAccountId);\n * ```\n *\n * @see {@link ReactiveActionStore}\n */\nexport function createReactiveActionStore<TArgs extends readonly unknown[], TResult>(\n fn: (signal: AbortSignal, ...args: TArgs) => Promise<TResult>,\n): ReactiveActionStore<TArgs, TResult> {\n let state: ReactiveActionState<TResult> = IDLE_STATE;\n let currentController: AbortController | undefined;\n const listeners = new Set<() => void>();\n\n function setState(next: ReactiveActionState<TResult>) {\n if (state.status === next.status && state.data === next.data && state.error === next.error) {\n return;\n }\n state = next;\n listeners.forEach(listener => listener());\n }\n\n const dispatchAsyncWithSignal = async (userSignal: AbortSignal | undefined, ...args: TArgs): Promise<TResult> => {\n currentController?.abort();\n // If the caller's signal is already aborted, surface as error and bail.\n if (userSignal?.aborted) {\n setState({ data: state.data, error: userSignal.reason, status: 'error' });\n throw userSignal.reason;\n }\n const controller = new AbortController();\n currentController = controller;\n const signal = userSignal ? AbortSignal.any([controller.signal, userSignal]) : controller.signal;\n const previousData = state.data;\n const previousError = state.error;\n setState({ data: previousData, error: previousError, status: 'running' });\n try {\n const result = await getAbortablePromise(fn(signal, ...args), signal);\n if (signal.aborted) {\n throw signal.reason;\n }\n setState({ data: result, error: undefined, status: 'success' });\n return result;\n } catch (error) {\n // Superseded by a newer dispatch or `reset()` — drop silently so only the most recent\n // dispatch mutates state, and reject with the abort reason rather than any underlying\n // failure that happened to race the abort.\n if (controller.signal.aborted) {\n throw controller.signal.reason;\n }\n // Real failure or the caller-provided signal firing — surface as error state.\n setState({ data: previousData, error, status: 'error' });\n throw error;\n }\n };\n\n const dispatchAsync = (...args: TArgs): Promise<TResult> => dispatchAsyncWithSignal(undefined, ...args);\n const dispatch = (...args: TArgs): void => {\n dispatchAsync(...args).catch(() => {});\n };\n\n return {\n dispatch,\n dispatchAsync,\n getState: () => state,\n reset: () => {\n currentController?.abort();\n currentController = undefined;\n setState(IDLE_STATE);\n },\n subscribe: listener => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n withSignal: (signal: AbortSignal) => ({\n dispatch: (...args: TArgs): void => {\n dispatchAsyncWithSignal(signal, ...args).catch(() => {});\n },\n dispatchAsync: (...args: TArgs): Promise<TResult> => dispatchAsyncWithSignal(signal, ...args),\n }),\n };\n}\n","import {\n SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_MUST_NOT_POLL_BEFORE_RESOLVING_EXISTING_MESSAGE_PROMISE,\n SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING,\n SolanaError,\n} from '@solana/errors';\nimport { AbortController } from '@solana/event-target-impl';\n\nimport { DataPublisher } from './data-publisher';\n\ntype Config = Readonly<{\n /**\n * Triggering this abort signal will cause all iterators spawned from this iterator to return\n * once they have published all queued messages.\n */\n abortSignal: AbortSignal;\n /**\n * Messages from this channel of `dataPublisher` will be the ones yielded through the iterators.\n *\n * Messages only begin to be queued after the first time an iterator begins to poll. Channel\n * messages published before that time will be dropped.\n */\n dataChannelName: string;\n // FIXME: It would be nice to be able to constrain the type of `dataPublisher` to one that\n // definitely supports the `dataChannelName` and `errorChannelName` channels, and\n // furthermore publishes `TData` on the `dataChannelName` channel. This is more difficult\n // than it should be: https://tsplay.dev/NlZelW\n dataPublisher: DataPublisher;\n /**\n * Messages from this channel of `dataPublisher` will be the ones thrown through the iterators.\n *\n * Any new iterators created after the first error is encountered will reject with that error\n * when polled.\n */\n errorChannelName: string;\n}>;\n\nconst enum PublishType {\n DATA,\n ERROR,\n}\n\ntype IteratorKey = symbol;\ntype IteratorState<TData> =\n | {\n __hasPolled: false;\n publishQueue: (\n | {\n __type: PublishType.DATA;\n data: TData;\n }\n | {\n __type: PublishType.ERROR;\n err: unknown;\n }\n )[];\n }\n | {\n __hasPolled: true;\n onData: (data: TData) => void;\n onError: Parameters<ConstructorParameters<typeof Promise>[0]>[1];\n };\n\nlet EXPLICIT_ABORT_TOKEN: symbol;\nfunction createExplicitAbortToken() {\n // This function is an annoying workaround to prevent `process.env.NODE_ENV` from appearing at\n // the top level of this module and thwarting an optimizing compiler's attempt to tree-shake.\n return Symbol(\n process.env.NODE_ENV !== \"production\"\n ? \"This symbol is thrown from a socket's iterator when the connection is explicitly \" +\n 'aborted by the user'\n : undefined,\n );\n}\n\nconst UNINITIALIZED = Symbol();\n\n/**\n * Returns an `AsyncIterable` given a data publisher.\n *\n * The iterable will produce iterators that vend messages published to `dataChannelName` and will\n * throw the first time a message is published to `errorChannelName`. Triggering the abort signal\n * will cause all iterators spawned from this iterator to return once they have published all queued\n * messages.\n *\n * Things to note:\n *\n * - If a message is published over a channel before the `AsyncIterator` attached to it has polled\n * for the next result, the message will be queued in memory.\n * - Messages only begin to be queued after the first time an iterator begins to poll. Channel\n * messages published before that time will be dropped.\n * - If there are messages in the queue and an error occurs, all queued messages will be vended to\n * the iterator before the error is thrown.\n * - If there are messages in the queue and the abort signal fires, all queued messages will be\n * vended to the iterator after which it will return.\n * - Any new iterators created after the first error is encountered will reject with that error when\n * polled.\n *\n * @param config\n *\n * @example\n * ```ts\n * const iterable = createAsyncIterableFromDataPublisher({\n * abortSignal: AbortSignal.timeout(10_000),\n * dataChannelName: 'message',\n * dataPublisher,\n * errorChannelName: 'error',\n * });\n * try {\n * for await (const message of iterable) {\n * console.log('Got message', message);\n * }\n * } catch (e) {\n * console.error('An error was published to the error channel', e);\n * } finally {\n * console.log(\"It's been 10 seconds; that's enough for now.\");\n * }\n * ```\n */\nexport function createAsyncIterableFromDataPublisher<TData>({\n abortSignal,\n dataChannelName,\n dataPublisher,\n errorChannelName,\n}: Config): AsyncIterable<TData> {\n const iteratorState: Map<IteratorKey, IteratorState<TData>> = new Map();\n function publishErrorToAllIterators(reason: unknown) {\n for (const [iteratorKey, state] of iteratorState.entries()) {\n if (state.__hasPolled) {\n iteratorState.delete(iteratorKey);\n state.onError(reason);\n } else {\n state.publishQueue.push({\n __type: PublishType.ERROR,\n err: reason,\n });\n }\n }\n }\n const abortController = new AbortController();\n abortSignal.addEventListener('abort', () => {\n abortController.abort();\n publishErrorToAllIterators((EXPLICIT_ABORT_TOKEN ||= createExplicitAbortToken()));\n });\n const options = { signal: abortController.signal } as const;\n let firstError: unknown = UNINITIALIZED;\n dataPublisher.on(\n errorChannelName,\n err => {\n if (firstError === UNINITIALIZED) {\n firstError = err;\n abortController.abort();\n publishErrorToAllIterators(err);\n }\n },\n options,\n );\n dataPublisher.on(\n dataChannelName,\n data => {\n iteratorState.forEach((state, iteratorKey) => {\n if (state.__hasPolled) {\n const { onData } = state;\n iteratorState.set(iteratorKey, { __hasPolled: false, publishQueue: [] });\n onData(data as TData);\n } else {\n state.publishQueue.push({\n __type: PublishType.DATA,\n data: data as TData,\n });\n }\n });\n },\n options,\n );\n return {\n async *[Symbol.asyncIterator]() {\n if (abortSignal.aborted) {\n return;\n }\n if (firstError !== UNINITIALIZED) {\n throw firstError;\n }\n const iteratorKey = Symbol();\n iteratorState.set(iteratorKey, { __hasPolled: false, publishQueue: [] });\n try {\n while (true) {\n const state = iteratorState.get(iteratorKey);\n if (!state) {\n // There should always be state by now.\n throw new SolanaError(SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING);\n }\n if (state.__hasPolled) {\n // You should never be able to poll twice in a row.\n throw new SolanaError(\n SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_MUST_NOT_POLL_BEFORE_RESOLVING_EXISTING_MESSAGE_PROMISE,\n );\n }\n const publishQueue = state.publishQueue;\n try {\n if (publishQueue.length) {\n state.publishQueue = [];\n for (const item of publishQueue) {\n if (item.__type === PublishType.DATA) {\n yield item.data;\n } else {\n throw item.err;\n }\n }\n } else {\n yield await new Promise<TData>((resolve, reject) => {\n iteratorState.set(iteratorKey, {\n __hasPolled: true,\n onData: resolve,\n onError: reject,\n });\n });\n }\n } catch (e) {\n if (e === (EXPLICIT_ABORT_TOKEN ||= createExplicitAbortToken())) {\n return;\n } else {\n throw e;\n }\n }\n }\n } finally {\n iteratorState.delete(iteratorKey);\n }\n },\n };\n}\n","import { SOLANA_ERROR__SUBSCRIBABLE__STREAM_CLOSED_WITHOUT_ERROR, SolanaError } from '@solana/errors';\n\nimport { ReactiveStreamStore } from './reactive-stream-store';\n\n/**\n * Adapts a {@link ReactiveStreamStore} into an `AsyncIterable`, so a *push*-based reactive store can\n * be driven by *pull*-based code that consumes a stream by `for await`-ing it — for example TanStack\n * Query's `experimental_streamedQuery`.\n *\n * The bridge only *observes* the store; it does not open or tear down the connection. Just like every\n * other consumer in this ecosystem — a store does nothing until you `connect()` it — the caller owns\n * the store's lifecycle: `connect()` the store yourself (typically binding the same `signal` via\n * {@link ReactiveStreamStore.withSignal | `withSignal()`}), and `reset()` it when you're done if you\n * intend to reuse it. The bridge subscribes, yields the store's current and subsequent values, and\n * unsubscribes when iteration ends.\n *\n * This is the store-backed counterpart to {@link createAsyncIterableFromDataPublisher}. That helper\n * turns a raw {@link DataPublisher} directly into an `AsyncIterable` and queues every message so\n * none are dropped; use it when you have a publisher and no store. `bridgeStoreToAsyncIterable`\n * instead sits on top of a `ReactiveStreamStore`, so it reflects the store's unified\n * `idle`/`loading`/`loaded`/`error` lifecycle and its stale-while-revalidate behaviour — and,\n * because a store only ever holds the *latest* snapshot, it is latest-wins rather than fully\n * buffered. Note this is also distinct from an RPC subscription's own `AsyncIterable`\n * (`await rpcSubscriptions.someNotifications().subscribe(...)`), which vends messages straight off\n * the transport without a store in between.\n *\n * On iteration it seeds from the store's current snapshot, then yields its lifecycle:\n * - `loaded` → yields the value (the one already present when iteration begins, then each subsequent\n * update), unless an optional `shouldYield` predicate rejects it. Latest-wins: if several\n * notifications land between pulls, only the most recent unconsumed value is yielded (a\n * subscription consumer wants the freshest state, not a backlog).\n * - `error` → throws, so the consuming `for await` rejects. Substitutes a\n * {@link SOLANA_ERROR__SUBSCRIBABLE__STREAM_CLOSED_WITHOUT_ERROR} sentinel when the store reports\n * an error with a nullish payload. An error takes precedence over a buffered value: if a `loaded`\n * value is still pending when an `error` arrives, that value is dropped and the error propagates\n * (once errored, stop yielding).\n * - `signal` aborts → ends the iterable cleanly (no error). A subscription never completes on its\n * own, so `signal` is how the iterable terminates: aborting it unblocks a parked `for await` and\n * ends the loop. Bind the same signal to the store's connection\n * (`store.withSignal(signal).connect()`) so the abort tears the underlying stream down too.\n *\n * However iteration ends — value exhaustion, error, or abort — the bridge unsubscribes from the\n * store. It does not `reset()` the store; that is the caller's decision.\n *\n * @typeParam T - The notification type emitted by the store.\n *\n * @param store - A stream store to observe. Connect it yourself — the bridge does not.\n * @param signal - Terminates the iterable when aborted. Bind it to the store's connection too\n * (`store.withSignal(signal).connect()`) so an abort also tears down the underlying stream.\n * @param shouldYield - Optional gate run against each `loaded` value before it is yielded. Return\n * `false` to drop the value. When omitted, every loaded value is yielded.\n *\n * @returns An `AsyncIterable<T>` that yields each store value until the store errors or `signal`\n * aborts.\n *\n * @throws Rethrows the store's `error` payload when the store transitions to `status: 'error'`, or a\n * {@link SolanaError} with code {@link SOLANA_ERROR__SUBSCRIBABLE__STREAM_CLOSED_WITHOUT_ERROR}\n * when that payload is nullish.\n *\n * @example\n * ```ts\n * const store = rpcSubscriptions.slotNotifications().reactiveStore();\n * const controller = new AbortController();\n * // The caller owns the connection — bind it to the same signal so an abort tears it down.\n * store.withSignal(controller.signal).connect();\n * try {\n * for await (const notification of bridgeStoreToAsyncIterable(store, controller.signal)) {\n * console.log('Latest slot:', notification.slot);\n * }\n * } catch (e) {\n * console.error('The subscription errored', e);\n * } finally {\n * store.reset();\n * }\n * // Elsewhere: controller.abort() ends the loop cleanly.\n * ```\n *\n * @see {@link ReactiveStreamStore}\n * @see {@link createAsyncIterableFromDataPublisher}\n */\nexport function bridgeStoreToAsyncIterable<T>(\n store: ReactiveStreamStore<T>,\n signal: AbortSignal,\n shouldYield?: (value: T) => boolean,\n): AsyncIterable<T> {\n return {\n async *[Symbol.asyncIterator](): AsyncIterator<T> {\n // Latest-wins single-slot buffer plus a one-shot \"something changed\" deferred the loop\n // parks on. `wake()` resolves the current deferred and arms a fresh one for the next park.\n let latest: { readonly value: T } | undefined;\n let failure: { readonly error: unknown } | undefined;\n let deferred = Promise.withResolvers<void>();\n const wake = () => {\n const { resolve } = deferred;\n deferred = Promise.withResolvers<void>();\n resolve();\n };\n\n const onChange = () => {\n const state = store.getState();\n if (state.status === 'loaded') {\n // Drop a value the gate rejects. The stream parks again rather than yielding it.\n if (shouldYield && !shouldYield(state.data)) return;\n latest = { value: state.data };\n wake();\n } else if (state.status === 'error') {\n // A nullish error would otherwise surface as a value-less success; substitute a\n // sentinel so the failure propagates.\n failure = {\n error: state.error ?? new SolanaError(SOLANA_ERROR__SUBSCRIBABLE__STREAM_CLOSED_WITHOUT_ERROR),\n };\n wake();\n }\n // `idle` / `loading` carry no value and no error — nothing to yield.\n };\n\n const onAbort = () => wake();\n signal.addEventListener('abort', onAbort, { once: true });\n const unsubscribe = store.subscribe(onChange);\n // Seed from the store's current snapshot: the caller may already have connected (and a\n // value or error may already be present) before iteration began. The bridge never\n // connects the store itself.\n onChange();\n try {\n while (true) {\n // Abort wins over everything: an abort is teardown, so end cleanly without\n // surfacing the store's incidental abort-driven error state.\n if (signal.aborted) return;\n if (failure) throw failure.error;\n if (latest) {\n const { value } = latest;\n latest = undefined;\n yield value;\n continue;\n }\n await deferred.promise;\n }\n } finally {\n signal.removeEventListener('abort', onAbort);\n unsubscribe();\n }\n },\n };\n}\n","import { TypedEventEmitter, TypedEventTarget } from './event-emitter';\n\ntype UnsubscribeFn = () => void;\n\n/**\n * Represents an object with an `on` function that you can call to subscribe to certain data over a\n * named channel.\n *\n * @example\n * ```ts\n * let dataPublisher: DataPublisher<{ error: SolanaError }>;\n * dataPublisher.on('data', handleData); // ERROR. `data` is not a known channel name.\n * dataPublisher.on('error', e => {\n * console.error(e);\n * }); // OK.\n * ```\n */\nexport interface DataPublisher<TDataByChannelName extends Record<string, unknown> = Record<string, unknown>> {\n /**\n * Call this to subscribe to data over a named channel.\n *\n * @param channelName The name of the channel on which to subscribe for messages\n * @param subscriber The function to call when a message becomes available\n * @param options.signal An abort signal you can fire to unsubscribe\n *\n * @returns A function that you can call to unsubscribe\n */\n on<const TChannelName extends keyof TDataByChannelName>(\n channelName: TChannelName,\n subscriber: (data: TDataByChannelName[TChannelName]) => void,\n options?: { signal: AbortSignal },\n ): UnsubscribeFn;\n}\n\n/**\n * Returns an object with an `on` function that you can call to subscribe to certain data over a\n * named channel.\n *\n * The `on` function returns an unsubscribe function.\n *\n * @example\n * ```ts\n * const socketDataPublisher = getDataPublisherFromEventEmitter(new WebSocket('wss://api.devnet.solana.com'));\n * const unsubscribe = socketDataPublisher.on('message', message => {\n * if (JSON.parse(message.data).id === 42) {\n * console.log('Got response 42');\n * unsubscribe();\n * }\n * });\n * ```\n */\nexport function getDataPublisherFromEventEmitter<TEventMap extends Record<string, Event>>(\n eventEmitter: TypedEventEmitter<TEventMap> | TypedEventTarget<TEventMap>,\n): DataPublisher<{\n [TEventType in keyof TEventMap]: TEventMap[TEventType] extends CustomEvent ? TEventMap[TEventType]['detail'] : null;\n}> {\n return {\n on(channelName, subscriber, options) {\n function innerListener(ev: Event) {\n if (ev instanceof CustomEvent) {\n const data = (ev as CustomEvent<TEventMap[typeof channelName]>).detail;\n (subscriber as unknown as (data: TEventMap[typeof channelName]) => void)(data);\n } else {\n (subscriber as () => void)();\n }\n }\n eventEmitter.addEventListener(channelName, innerListener, options);\n return () => {\n eventEmitter.removeEventListener(channelName, innerListener);\n };\n },\n };\n}\n","import { EventTarget } from '@solana/event-target-impl';\n\nimport { DataPublisher, getDataPublisherFromEventEmitter } from './data-publisher';\n\n/**\n * Given a channel that carries messages for multiple subscribers on a single channel name, this\n * function returns a new {@link DataPublisher} that splits them into multiple channel names.\n *\n * @param messageTransformer A function that receives the message as the first argument, and returns\n * a tuple of the derived channel name and the message.\n *\n * @example\n * Imagine a channel that carries multiple notifications whose destination is contained within the\n * message itself.\n *\n * ```ts\n * const demuxedDataPublisher = demultiplexDataPublisher(channel, 'message', message => {\n * const destinationChannelName = `notification-for:${message.subscriberId}`;\n * return [destinationChannelName, message];\n * });\n * ```\n *\n * Now you can subscribe to _only_ the messages you are interested in, without having to subscribe\n * to the entire `'message'` channel and filter out the messages that are not for you.\n *\n * ```ts\n * demuxedDataPublisher.on(\n * 'notification-for:123',\n * message => {\n * console.log('Got a message for subscriber 123', message);\n * },\n * { signal: AbortSignal.timeout(5_000) },\n * );\n * ```\n */\nexport function demultiplexDataPublisher<\n TDataPublisher extends DataPublisher,\n const TChannelName extends Parameters<TDataPublisher['on']>[0],\n>(\n publisher: TDataPublisher,\n sourceChannelName: TChannelName,\n messageTransformer: (\n // FIXME: Deriving the type of the message from `TDataPublisher` and `TChannelName` would\n // help callers to constrain their transform functions.\n message: unknown,\n ) => [destinationChannelName: string, message: unknown] | void,\n): DataPublisher {\n let innerPublisherState:\n | {\n readonly dispose: () => void;\n numSubscribers: number;\n }\n | undefined;\n const eventTarget = new EventTarget();\n const demultiplexedDataPublisher = getDataPublisherFromEventEmitter(eventTarget);\n return {\n ...demultiplexedDataPublisher,\n on(channelName, subscriber, options) {\n if (!innerPublisherState) {\n const innerPublisherUnsubscribe = publisher.on(sourceChannelName, sourceMessage => {\n const transformResult = messageTransformer(sourceMessage);\n if (!transformResult) {\n return;\n }\n const [destinationChannelName, message] = transformResult;\n eventTarget.dispatchEvent(\n new CustomEvent(destinationChannelName, {\n detail: message,\n }),\n );\n });\n innerPublisherState = {\n dispose: innerPublisherUnsubscribe,\n numSubscribers: 0,\n };\n }\n innerPublisherState.numSubscribers++;\n const unsubscribe = demultiplexedDataPublisher.on(channelName, subscriber, options);\n let isActive = true;\n function handleUnsubscribe() {\n if (!isActive) {\n return;\n }\n isActive = false;\n options?.signal.removeEventListener('abort', handleUnsubscribe);\n innerPublisherState!.numSubscribers--;\n if (innerPublisherState!.numSubscribers === 0) {\n innerPublisherState!.dispose();\n innerPublisherState = undefined;\n }\n unsubscribe();\n }\n options?.signal.addEventListener('abort', handleUnsubscribe);\n return handleUnsubscribe;\n },\n };\n}\n","import { AbortController } from '@solana/event-target-impl';\n\nimport { DataPublisher } from './data-publisher';\n\ntype FactoryConfig = Readonly<{\n // FIXME: It would be nice to be able to constrain the type returned by `createDataPublisher` to one that\n // definitely supports the `dataChannelName` and `errorChannelName` channels, and\n // furthermore publishes `TData` on the `dataChannelName` channel. This is more difficult\n // than it should be: https://tsplay.dev/NlZelW\n /**\n * An async factory that produces a fresh {@link DataPublisher} each time it is invoked. Called\n * on every {@link ReactiveStreamStore.connect | `connect()`}.\n *\n * Receives an {@link AbortSignal} that fires when this specific connection window should tear\n * down — composed from the per-connection inner controller and (if attached via\n * {@link ReactiveStreamStore.withSignal | `withSignal()`}) the caller-provided signal via\n * `AbortSignal.any`. Thread it into the underlying transport's own cancellation so the\n * connection itself stops on per-connection abort, not just the stream-store's listeners.\n * Rejections surface as a store error.\n */\n createDataPublisher: (signal: AbortSignal) => Promise<DataPublisher>;\n /**\n * Messages from this channel of the produced `DataPublisher` will be used to update the store's\n * state.\n */\n dataChannelName: string;\n /**\n * Messages from this channel of the produced `DataPublisher` will transition the store to an\n * error state, preserving the last known value.\n */\n errorChannelName: string;\n}>;\n\n/**\n * The lifecycle state of a {@link ReactiveStreamStore} as a single snapshot.\n *\n * - `idle`: the store has not yet been connected, or has been reset via\n * {@link ReactiveStreamStore.reset | `reset()`}. Call\n * {@link ReactiveStreamStore.connect | `connect()`} to open the underlying stream.\n * - `loading`: a connection is in progress. `data` and `error` are preserved from the previous\n * connection (if any) — stale-while-revalidate UX. A subsequent `loaded` clears `error`; a\n * subsequent `error` replaces it.\n * - `loaded`: a value has been received and no error is active.\n * - `error`: the stream failed. `data` holds the last known value (or `undefined` if none ever\n * arrived) and `error` holds the failure.\n */\nexport type ReactiveState<T> =\n | { readonly data: T | undefined; readonly error: unknown; readonly status: 'error' }\n | { readonly data: T | undefined; readonly error: unknown; readonly status: 'loading' }\n | { readonly data: T; readonly error: undefined; readonly status: 'loaded' }\n | { readonly data: undefined; readonly error: undefined; readonly status: 'idle' };\n\nconst IDLE_STATE: ReactiveState<never> = Object.freeze({\n data: undefined,\n error: undefined,\n status: 'idle',\n});\n\n/**\n * A reactive store that holds the latest value published to a data channel and allows external\n * systems to subscribe to changes. Compatible with `useSyncExternalStore`, Svelte stores, Solid's\n * `from()`, and other reactive primitives that expect a `{ subscribe, getState }` contract.\n *\n * The store starts in `status: 'idle'`. Call {@link ReactiveStreamStore.connect | `connect()`}\n * to open the underlying stream; the store transitions through `loading` → `loaded` (or `error`).\n * Subsequent `connect()` calls also pass through `loading` while preserving the last known\n * `data` and `error` (stale-while-revalidate).\n *\n * @example\n * ```ts\n * // React — the unified state snapshot has stable identity per update, making it suitable as\n * // the second argument to `useSyncExternalStore`.\n * const state = useSyncExternalStore(store.subscribe, store.getState);\n * useEffect(() => {\n * store.connect();\n * return () => store.reset();\n * }, [store]);\n * if (state.status === 'error') return <ErrorMessage error={state.error} onRetry={store.connect} />;\n * if (state.status === 'loading' || state.status === 'idle') return <Spinner />;\n * return <View data={state.data} />;\n * ```\n *\n * @see {@link createReactiveStoreFromDataPublisherFactory}\n */\nexport type ReactiveStreamStore<T> = {\n /**\n * Open the underlying stream. Aborts any currently active connection, invokes the configured\n * factory, and transitions the store to `loading` (preserving the last known `data` and\n * `error` for stale-while-revalidate) before settling into `loaded` (on data) or `error`\n * (on failure).\n */\n connect(): void;\n /**\n * Returns the current lifecycle snapshot: `{ data, error, status }`. The returned object has\n * stable identity between state changes, making it safe to pass directly as the\n * `getSnapshot` argument to React's `useSyncExternalStore`.\n *\n * @see {@link ReactiveState}\n */\n getState(): ReactiveState<T>;\n /**\n * Aborts any currently active connection and resets the store to `{ status: 'idle' }`. Both\n * `data` and `error` are cleared. Use this to tear down the connection without permanently\n * killing the store — a follow-up {@link ReactiveStreamStore.connect | `connect()`} will open\n * a fresh stream.\n */\n reset(): void;\n /**\n * Registers a callback to be called whenever the state changes or an error is received.\n * Returns an unsubscribe function. Safe to call multiple times.\n */\n subscribe(callback: () => void): () => void;\n /**\n * Returns a thin wrapper exposing `connect()` that composes `signal` with the store's internal\n * per-connection controller via `AbortSignal.any` — aborting either tears down the active\n * connection. Aborting the caller-provided signal surfaces the abort reason on state as\n * `{ status: 'error' }`; the internal controller path (supersession by a newer `connect()` or\n * `reset()`) is silent by design so the newer call owns state. Use this to attach a\n * caller-provided cancellation source (per-connection timeout, shared kill switch,\n * parent-context signal) without touching the bare `connect()` API.\n *\n * - Per-connection timeout: `store.withSignal(AbortSignal.timeout(30_000)).connect()` — fresh\n * clock per call.\n * - Permanent kill switch: hold one `AbortController`, bind the wrapper once\n * (`const killable = store.withSignal(killCtrl.signal)`), and use `killable.connect()`\n * everywhere; aborting the controller cancels the active connection and short-circuits\n * future calls through the bound wrapper.\n *\n * The wrapper exposes only `connect()` — `getState` / `subscribe` / `reset` remain\n * store-level concerns on the parent.\n */\n withSignal(signal: AbortSignal): { readonly connect: () => void };\n};\n\n/**\n * Duck-type for objects that build a {@link ReactiveStreamStore} on demand via a `reactiveStore()`\n * method. Satisfied by `PendingRpcSubscriptionsRequest<T>`. Reactive-framework bindings (e.g.\n * React's `useSubscription`) consume this duck-type so they don't have to name a concrete producer\n * type.\n *\n * The returned store is in `status: 'idle'` — the caller is responsible for invoking\n * {@link ReactiveStreamStore.connect | `connect()`} to open the underlying stream. Attach a\n * caller-provided cancellation source via {@link ReactiveStreamStore.withSignal | `withSignal()`}\n * — `store.withSignal(signal).connect()`.\n *\n * @typeParam T - The value type emitted by the resulting stream store.\n *\n * @example\n * ```ts\n * function bindWithTimeout<T>(source: ReactiveStreamSource<T>) {\n * const store = source.reactiveStore();\n * store.withSignal(AbortSignal.timeout(30_000)).connect();\n * return store;\n * }\n * ```\n *\n * @see {@link ReactiveStreamStore}\n * @see {@link ReactiveActionSource}\n */\nexport type ReactiveStreamSource<T> = {\n reactiveStore(): ReactiveStreamStore<T>;\n};\n\n/**\n * Returns a {@link ReactiveStreamStore} that wires itself to a fresh {@link DataPublisher} on\n * every {@link ReactiveStreamStore.connect | `connect()`}.\n *\n * The store accepts a `createDataPublisher` factory rather than a ready-made publisher — that\n * lets the store tear down a broken stream and open a new one without losing subscribers or the\n * last known value. The factory receives the per-connection signal so the underlying transport\n * can stop on per-connection abort, not just the stream-store's listeners.\n *\n * Things to note:\n *\n * - The returned store starts in `status: 'idle'`. Call `connect()` to open the first stream.\n * - `createDataPublisher` is invoked on every `connect()`. The store transitions through\n * `loading`, preserving the last known `data` and `error` (stale-while-revalidate).\n * - If `createDataPublisher` rejects, the store transitions to `status: 'error'` with the\n * rejection as the error. Call `connect()` to try again.\n * - `reset()` aborts the current connection and returns the store to `idle`, clearing `data`\n * and `error`. A follow-up `connect()` opens a fresh stream.\n * - Attach a caller-provided cancellation source via\n * {@link ReactiveStreamStore.withSignal | `withSignal()`} — `store.withSignal(signal).connect()`\n * composes the signal with the per-connection controller. Aborting the caller's signal\n * transitions the store to `error` with that abort reason.\n *\n * @param config\n *\n * @example\n * ```ts\n * const store = createReactiveStoreFromDataPublisherFactory({\n * createDataPublisher: signal => getDataPublisherFromEventEmitter(new WebSocket(url, { signal })),\n * dataChannelName: 'message',\n * errorChannelName: 'error',\n * });\n * const unsubscribe = store.subscribe(() => {\n * const snapshot = store.getState();\n * if (snapshot.status === 'error') console.error('Connection failed:', snapshot.error);\n * else if (snapshot.status === 'loaded') console.log('Latest:', snapshot.data);\n * });\n * // Fresh 30-second clock per connection attempt:\n * store.withSignal(AbortSignal.timeout(30_000)).connect();\n * ```\n */\nexport function createReactiveStoreFromDataPublisherFactory<TData>({\n createDataPublisher,\n dataChannelName,\n errorChannelName,\n}: FactoryConfig): ReactiveStreamStore<TData> {\n let currentState: ReactiveState<TData> = IDLE_STATE;\n let currentInnerController: AbortController | undefined;\n const subscribers = new Set<() => void>();\n\n function notify() {\n subscribers.forEach(cb => cb());\n }\n\n function setState(next: ReactiveState<TData>) {\n if (\n currentState.status === next.status &&\n currentState.data === next.data &&\n currentState.error === next.error\n ) {\n return;\n }\n currentState = next;\n notify();\n }\n\n function performConnect(callerSignal: AbortSignal | undefined) {\n // Abort any currently active connection before starting a fresh one.\n currentInnerController?.abort();\n // If the caller's signal is already aborted, surface as error and bail.\n if (callerSignal?.aborted) {\n setState({ data: currentState.data, error: callerSignal.reason, status: 'error' });\n return;\n }\n // Transition to `loading`, preserving the last known `data` and `error` for SWR. If\n // already `loading` with the same data/error, `setState` no-ops — no spurious notify.\n setState({ data: currentState.data, error: currentState.error, status: 'loading' });\n // Inner signal is passed to the data publisher (composed with caller signal if any).\n const innerController = new AbortController();\n currentInnerController = innerController;\n const signal = callerSignal ? AbortSignal.any([innerController.signal, callerSignal]) : innerController.signal;\n // Caller's signal aborting (not just supersede via the inner controller) transitions the\n // store to error with the caller's abort reason. Scoped to the inner signal so the\n // listener is removed automatically on reconnect / reset.\n if (callerSignal) {\n callerSignal.addEventListener(\n 'abort',\n () => {\n if (innerController.signal.aborted) return;\n setState({ data: currentState.data, error: callerSignal.reason, status: 'error' });\n innerController.abort(callerSignal.reason);\n },\n { signal: innerController.signal },\n );\n }\n createDataPublisher(signal).then(\n publisher => {\n if (signal.aborted) return;\n publisher.on(\n dataChannelName,\n data => {\n setState({ data: data as TData, error: undefined, status: 'loaded' });\n },\n { signal },\n );\n publisher.on(\n errorChannelName,\n err => {\n if (currentState.status === 'error') return;\n setState({ data: currentState.data, error: err, status: 'error' });\n innerController.abort(err);\n },\n { signal },\n );\n },\n err => {\n if (signal.aborted) return;\n setState({ data: currentState.data, error: err, status: 'error' });\n innerController.abort(err);\n },\n );\n }\n\n function performReset() {\n currentInnerController?.abort();\n currentInnerController = undefined;\n setState(IDLE_STATE);\n }\n\n return {\n connect(): void {\n performConnect(undefined);\n },\n getState(): ReactiveState<TData> {\n return currentState;\n },\n reset: performReset,\n subscribe(callback: () => void): () => void {\n subscribers.add(callback);\n return () => {\n subscribers.delete(callback);\n };\n },\n withSignal(signal: AbortSignal) {\n return {\n connect(): void {\n performConnect(signal);\n },\n };\n },\n };\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { getAbortablePromise } from '@solana/promises';
2
- import { SolanaError, SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING, SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_MUST_NOT_POLL_BEFORE_RESOLVING_EXISTING_MESSAGE_PROMISE } from '@solana/errors';
2
+ import { SolanaError, SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING, SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_MUST_NOT_POLL_BEFORE_RESOLVING_EXISTING_MESSAGE_PROMISE, SOLANA_ERROR__SUBSCRIBABLE__STREAM_CLOSED_WITHOUT_ERROR } from '@solana/errors';
3
3
 
4
4
  // ../event-target-impl/dist/index.browser.mjs
5
5
  var o = globalThis.AbortController;
@@ -194,6 +194,53 @@ function createAsyncIterableFromDataPublisher({
194
194
  }
195
195
  };
196
196
  }
197
+ function bridgeStoreToAsyncIterable(store, signal, shouldYield) {
198
+ return {
199
+ async *[Symbol.asyncIterator]() {
200
+ let latest;
201
+ let failure;
202
+ let deferred = Promise.withResolvers();
203
+ const wake = () => {
204
+ const { resolve } = deferred;
205
+ deferred = Promise.withResolvers();
206
+ resolve();
207
+ };
208
+ const onChange = () => {
209
+ const state = store.getState();
210
+ if (state.status === "loaded") {
211
+ if (shouldYield && !shouldYield(state.data)) return;
212
+ latest = { value: state.data };
213
+ wake();
214
+ } else if (state.status === "error") {
215
+ failure = {
216
+ error: state.error ?? new SolanaError(SOLANA_ERROR__SUBSCRIBABLE__STREAM_CLOSED_WITHOUT_ERROR)
217
+ };
218
+ wake();
219
+ }
220
+ };
221
+ const onAbort = () => wake();
222
+ signal.addEventListener("abort", onAbort, { once: true });
223
+ const unsubscribe = store.subscribe(onChange);
224
+ onChange();
225
+ try {
226
+ while (true) {
227
+ if (signal.aborted) return;
228
+ if (failure) throw failure.error;
229
+ if (latest) {
230
+ const { value } = latest;
231
+ latest = void 0;
232
+ yield value;
233
+ continue;
234
+ }
235
+ await deferred.promise;
236
+ }
237
+ } finally {
238
+ signal.removeEventListener("abort", onAbort);
239
+ unsubscribe();
240
+ }
241
+ }
242
+ };
243
+ }
197
244
 
198
245
  // src/data-publisher.ts
199
246
  function getDataPublisherFromEventEmitter(eventEmitter) {
@@ -364,6 +411,6 @@ function createReactiveStoreFromDataPublisherFactory({
364
411
  };
365
412
  }
366
413
 
367
- export { createAsyncIterableFromDataPublisher, createReactiveActionStore, createReactiveStoreFromDataPublisherFactory, demultiplexDataPublisher, getDataPublisherFromEventEmitter };
414
+ export { bridgeStoreToAsyncIterable, createAsyncIterableFromDataPublisher, createReactiveActionStore, createReactiveStoreFromDataPublisherFactory, demultiplexDataPublisher, getDataPublisherFromEventEmitter };
368
415
  //# sourceMappingURL=index.browser.mjs.map
369
416
  //# sourceMappingURL=index.browser.mjs.map