xantiagoma 0.2.0 → 0.2.1
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 +33 -0
- package/dist/entry-sonner.cjs +57 -0
- package/dist/entry-sonner.cjs.map +1 -1
- package/dist/entry-sonner.d.cts +86 -8
- package/dist/entry-sonner.d.mts +86 -8
- package/dist/entry-sonner.mjs +57 -1
- package/dist/entry-sonner.mjs.map +1 -1
- package/docs/RELEASING.md +109 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
npm install xantiagoma
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
Release process notes live in [docs/RELEASING.md](./docs/RELEASING.md).
|
|
26
|
+
|
|
25
27
|
## Entry Points
|
|
26
28
|
|
|
27
29
|
| Import | Description | Dependencies |
|
|
@@ -172,6 +174,37 @@ Cursor tokens are produced by a two-stage codec (`serializer` + `encoder`, both
|
|
|
172
174
|
| `fetchWithProgress` | Fetch with upload/download progress | [src](./src/fetch-with-progress.ts) | [tests](./test/fetch-with-progress.test.ts) |
|
|
173
175
|
| `createHttpInterceptor` | Intercept fetch + XHR with rules | [src](./src/intercept-http.ts) | [tests](./test/intercept-http.test.tsx) |
|
|
174
176
|
|
|
177
|
+
## Sonner Utilities (`xantiagoma/sonner`)
|
|
178
|
+
|
|
179
|
+
Toast helpers for streaming iterables/generators through [Sonner](https://sonner.emilkowal.ski/).
|
|
180
|
+
|
|
181
|
+
| Export | Description | Source | Tests |
|
|
182
|
+
| ------------------ | ----------------------------------------------------------------------- | ---------------------------- | ------------------------------------ |
|
|
183
|
+
| `toastStream` | Blocking/awaitable stream toast; resolves to `{ items, returnValue }` | [src](./src/toast-stream.ts) | [tests](./test/toast-stream.test.ts) |
|
|
184
|
+
| `toastStreamAsync` | Non-blocking stream toast; returns toast id immediately with `unwrap()` | [src](./src/toast-stream.ts) | [tests](./test/toast-stream.test.ts) |
|
|
185
|
+
|
|
186
|
+
Use `toastStream` when the caller should wait for completion:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
import { toastStream } from "xantiagoma/sonner";
|
|
190
|
+
|
|
191
|
+
const { items, returnValue } = await toastStream(source, {
|
|
192
|
+
loading: "Loading...",
|
|
193
|
+
streaming: ({ count }) => `Received ${count}`,
|
|
194
|
+
success: ({ count }) => `Done: ${count} items`,
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Use `toastStreamAsync` when the caller should continue immediately, matching
|
|
199
|
+
Sonner's `toast.promise(...).unwrap()` style:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import { toastStreamAsync } from "xantiagoma/sonner";
|
|
203
|
+
|
|
204
|
+
const toastId = toastStreamAsync(source, { loading: "Loading..." });
|
|
205
|
+
const { items, returnValue } = await toastId.unwrap();
|
|
206
|
+
```
|
|
207
|
+
|
|
175
208
|
## React Utilities (`xantiagoma/react`)
|
|
176
209
|
|
|
177
210
|
| Export | Description | Source | Tests |
|
package/dist/entry-sonner.cjs
CHANGED
|
@@ -204,7 +204,64 @@ async function toastStream(source, data = {}) {
|
|
|
204
204
|
returnValue: result.returnValue
|
|
205
205
|
};
|
|
206
206
|
}
|
|
207
|
+
function toastStreamAsync(source, data = {}) {
|
|
208
|
+
const { loading, delay, ...rest } = data;
|
|
209
|
+
const delayConfig = normalizeDelay(delay);
|
|
210
|
+
const needsAsyncResolve = typeof loading === "function" || typeof loading === "object" && loading !== null && "message" in loading;
|
|
211
|
+
const initialLoadingMsg = needsAsyncResolve ? "Processing..." : loading ?? "Processing...";
|
|
212
|
+
const toastId = sonner.toast.loading(initialLoadingMsg);
|
|
213
|
+
const promise = (async () => {
|
|
214
|
+
const { streaming, success, error, finally: onFinally, onItem, ...toastOptions } = rest;
|
|
215
|
+
if (needsAsyncResolve) {
|
|
216
|
+
const { message: loadingMsg, options: loadingOptions } = await resolveLoading(loading, "Processing...");
|
|
217
|
+
sonner.toast.loading(loadingMsg, {
|
|
218
|
+
id: toastId,
|
|
219
|
+
...loadingOptions
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
const result = await consumeStream({
|
|
223
|
+
source,
|
|
224
|
+
toastId,
|
|
225
|
+
streaming,
|
|
226
|
+
onItem,
|
|
227
|
+
delayConfig
|
|
228
|
+
});
|
|
229
|
+
await onFinally?.();
|
|
230
|
+
if (delayConfig.result) await require_src.wait(delayConfig.result);
|
|
231
|
+
if (!result.ok) {
|
|
232
|
+
const partial = {
|
|
233
|
+
count: result.items.length,
|
|
234
|
+
items: result.items
|
|
235
|
+
};
|
|
236
|
+
const { message: errorMsg, options: errorOptions } = await resolveErrorResult(error, result.error, partial, "Stream failed");
|
|
237
|
+
sonner.toast.error(errorMsg, {
|
|
238
|
+
id: toastId,
|
|
239
|
+
...toastOptions,
|
|
240
|
+
...errorOptions
|
|
241
|
+
});
|
|
242
|
+
throw result.error;
|
|
243
|
+
}
|
|
244
|
+
const { message: successMsg, options: successOptions } = await resolveResult(success, {
|
|
245
|
+
count: result.items.length,
|
|
246
|
+
items: result.items,
|
|
247
|
+
returnValue: result.returnValue
|
|
248
|
+
}, `Complete! ${result.items.length} items`);
|
|
249
|
+
sonner.toast.success(successMsg, {
|
|
250
|
+
id: toastId,
|
|
251
|
+
...toastOptions,
|
|
252
|
+
...successOptions
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
items: result.items,
|
|
256
|
+
returnValue: result.returnValue
|
|
257
|
+
};
|
|
258
|
+
})();
|
|
259
|
+
const res = toastId;
|
|
260
|
+
res.unwrap = () => promise;
|
|
261
|
+
return res;
|
|
262
|
+
}
|
|
207
263
|
//#endregion
|
|
208
264
|
exports.toastStream = toastStream;
|
|
265
|
+
exports.toastStreamAsync = toastStreamAsync;
|
|
209
266
|
|
|
210
267
|
//# sourceMappingURL=entry-sonner.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry-sonner.cjs","names":["resolveStreamSource","wait","isGenerator","isAsyncGenerator","AsyncChannel","isAsyncIterable","isIterable","toAsyncIterable","toast"],"sources":["../src/toast-stream.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\n\nimport {\n AsyncChannel,\n isAsyncGenerator,\n isAsyncIterable,\n isGenerator,\n isIterable,\n resolveStreamSource,\n type StreamSource,\n toAsyncIterable,\n wait,\n} from \"./index.ts\";\nimport { type ExternalToast, toast } from \"sonner\";\n\n// Types matching Sonner's patterns\ntype StreamExternalToast = Omit<ExternalToast, \"description\">;\n\ninterface StreamExtendedResult extends ExternalToast {\n message: ReactNode;\n}\n\n/** Result can be string, ReactNode, or callback returning either (sync or async) */\ntype StreamResultT<TData = unknown> =\n | string\n | ReactNode\n | ((data: TData) => ReactNode | string | Promise<ReactNode | string>);\n\n/** Extended result includes full toast options */\ntype StreamExtendedResultT<TData = unknown> =\n | StreamExtendedResult\n | ((data: TData) => StreamExtendedResult | Promise<StreamExtendedResult>);\n\n/** Error result - callback receives error first, then partial data */\ntype StreamErrorResultT<TYield> =\n | string\n | ReactNode\n | ((\n error: unknown,\n partial: ErrorPartialData<TYield>,\n ) => ReactNode | string | Promise<ReactNode | string>);\n\n/** Extended error result with toast options */\ntype StreamExtendedErrorResultT<TYield> =\n | StreamExtendedResult\n | ((\n error: unknown,\n partial: ErrorPartialData<TYield>,\n ) => StreamExtendedResult | Promise<StreamExtendedResult>);\n\n/** Streaming progress data passed to callbacks */\ntype StreamingData<T> = {\n count: number;\n latest: T;\n /** All items received so far (including latest) */\n items: T[];\n};\n\n/** Success data passed to callbacks - includes return value if generator has one */\ntype SuccessData<TYield, TReturn = void> = {\n count: number;\n items: TYield[];\n /** The return value from the generator (if any) */\n returnValue: TReturn;\n};\n\n/** Partial data available when an error occurs */\ntype ErrorPartialData<TYield> = {\n /** Number of items received before error */\n count: number;\n /** Items received before error */\n items: TYield[];\n};\n\n/** Result of consuming a stream */\ntype StreamResult<TYield, TReturn> = {\n items: TYield[];\n returnValue: TReturn;\n};\n\n/** Internal result from consumeStream - includes partial data on error */\ntype ConsumeResult<TYield, TReturn> =\n | { ok: true; items: TYield[]; returnValue: TReturn }\n | { ok: false; items: TYield[]; error: unknown };\n\n/** Granular delay configuration */\ntype DelayConfig = {\n /** Delay between items in ms */\n items?: number;\n /** Delay after loading, before first item */\n first?: number;\n /** Delay before showing success/error */\n result?: number;\n};\n\n/** Loading result - no data, just static or callback */\ntype LoadingResultT = string | ReactNode | (() => ReactNode | string | Promise<ReactNode | string>);\n\n/** Extended loading result with toast options */\ninterface LoadingExtendedResult extends ExternalToast {\n message: ReactNode;\n}\n\ntype LoadingExtendedResultT =\n | LoadingExtendedResult\n | (() => LoadingExtendedResult | Promise<LoadingExtendedResult>);\n\n/** Options matching Sonner's PromiseData pattern */\ntype ToastStreamData<TYield = unknown, TReturn = void> = StreamExternalToast & {\n /**\n * Message shown while waiting for first item.\n * Supports string, ReactNode, callback, or extended result with toast options.\n */\n loading?: LoadingResultT | LoadingExtendedResultT;\n /** Message shown while streaming - updates on each item */\n streaming?: StreamResultT<StreamingData<TYield>> | StreamExtendedResultT<StreamingData<TYield>>;\n /** Message shown on successful completion */\n success?:\n | StreamResultT<SuccessData<TYield, TReturn>>\n | StreamExtendedResultT<SuccessData<TYield, TReturn>>;\n /** Message shown on error - callback receives (error, { count, items }) */\n error?: StreamErrorResultT<TYield> | StreamExtendedErrorResultT<TYield>;\n /** Called when stream ends (success or error) */\n finally?: () => void | Promise<void>;\n /** Called for each item received */\n onItem?: (item: TYield, count: number) => void;\n /**\n * Artificial delay for visualizing sync streams.\n * - `number`: applies to all phases (first, items, result)\n * - `object`: granular control over individual phases\n *\n * @example\n * // Simple: 500ms for all phases (first, between items, before result)\n * delay: 500\n *\n * @example\n * // Granular: different delays for different phases\n * delay: { first: 500, items: 300, result: 200 }\n */\n delay?: number | DelayConfig;\n};\n\n/** Resolve a result that can be string, ReactNode, or callback */\nasync function resolveResult<TData>(\n result: StreamResultT<TData> | StreamExtendedResultT<TData> | undefined,\n data: TData,\n fallback: string,\n): Promise<{ message: ReactNode; options?: ExternalToast }> {\n if (result === undefined) {\n return { message: fallback };\n }\n\n if (typeof result === \"function\") {\n const resolved = await result(data);\n if (typeof resolved === \"object\" && resolved !== null && \"message\" in resolved) {\n const { message, ...options } = resolved as StreamExtendedResult;\n return { message, options };\n }\n return { message: resolved as ReactNode };\n }\n\n if (typeof result === \"object\" && result !== null && \"message\" in result) {\n const { message, ...options } = result as StreamExtendedResult;\n return { message, options };\n }\n\n return { message: result };\n}\n\n/** Resolve an error result that receives (error, partialData) */\nasync function resolveErrorResult<TYield>(\n result: StreamErrorResultT<TYield> | StreamExtendedErrorResultT<TYield> | undefined,\n error: unknown,\n partial: ErrorPartialData<TYield>,\n fallback: string,\n): Promise<{ message: ReactNode; options?: ExternalToast }> {\n if (result === undefined) {\n return { message: fallback };\n }\n\n if (typeof result === \"function\") {\n const resolved = await result(error, partial);\n if (typeof resolved === \"object\" && resolved !== null && \"message\" in resolved) {\n const { message, ...options } = resolved as StreamExtendedResult;\n return { message, options };\n }\n return { message: resolved as ReactNode };\n }\n\n if (typeof result === \"object\" && result !== null && \"message\" in result) {\n const { message, ...options } = result as StreamExtendedResult;\n return { message, options };\n }\n\n return { message: result };\n}\n\n/** Resolve loading result - no data passed to callback */\nasync function resolveLoading(\n result: LoadingResultT | LoadingExtendedResultT | undefined,\n fallback: string,\n): Promise<{ message: ReactNode; options?: ExternalToast }> {\n if (result === undefined) {\n return { message: fallback };\n }\n\n if (typeof result === \"function\") {\n const resolved = await result();\n if (typeof resolved === \"object\" && resolved !== null && \"message\" in resolved) {\n const { message, ...options } = resolved as LoadingExtendedResult;\n return { message, options };\n }\n return { message: resolved as ReactNode };\n }\n\n if (typeof result === \"object\" && result !== null && \"message\" in result) {\n const { message, ...options } = result as LoadingExtendedResult;\n return { message, options };\n }\n\n return { message: result };\n}\n\n/** Normalize delay option to full DelayConfig */\nfunction normalizeDelay(delay: number | DelayConfig | undefined): DelayConfig {\n if (delay === undefined) {\n return {};\n }\n if (typeof delay === \"number\") {\n // Simple number applies to all phases: first, items, and result\n return { first: delay, items: delay, result: delay };\n }\n return delay;\n}\n\n/** Options for consumeStream */\ntype ConsumeStreamOptions<TYield, TReturn> = {\n source: StreamSource<TYield>;\n toastId: string | number;\n streaming: ToastStreamData<TYield, TReturn>[\"streaming\"];\n onItem: ToastStreamData<TYield, TReturn>[\"onItem\"];\n delayConfig: DelayConfig;\n};\n\n/** Core streaming logic - consumes source and updates toast, captures return value */\nasync function consumeStream<TYield, TReturn>(\n options: ConsumeStreamOptions<TYield, TReturn>,\n): Promise<ConsumeResult<TYield, TReturn>> {\n const { source: sourceOrFactory, toastId, streaming, onItem, delayConfig } = options;\n // Resolve factory function if needed\n const source = resolveStreamSource(sourceOrFactory);\n\n const items: TYield[] = [];\n let count = 0;\n let isFirst = true;\n\n const updateToast = async (item: TYield) => {\n // Delay before first item (after loading)\n if (isFirst && delayConfig.first) {\n await wait(delayConfig.first);\n isFirst = false;\n } else if (!isFirst && delayConfig.items) {\n // Delay between items (not before first)\n await wait(delayConfig.items);\n } else {\n isFirst = false;\n }\n\n count += 1;\n items.push(item);\n onItem?.(item, count);\n\n const streamingData: StreamingData<TYield> = {\n count,\n latest: item,\n items: [...items],\n };\n const { message: streamingMsg, options: streamingOptions } = await resolveResult(\n streaming,\n streamingData,\n `Streaming... (${count} items)`,\n );\n toast.loading(streamingMsg, { id: toastId, ...streamingOptions });\n };\n\n try {\n // Handle sync Generator specially to capture return value\n if (isGenerator<TYield, TReturn>(source)) {\n let result = source.next();\n while (!result.done) {\n await updateToast(result.value);\n result = source.next();\n }\n return { ok: true, items, returnValue: result.value };\n }\n\n // Handle AsyncGenerator specially to capture return value\n if (isAsyncGenerator<TYield, TReturn>(source)) {\n let result = await source.next();\n while (!result.done) {\n await updateToast(result.value);\n result = await source.next();\n }\n return { ok: true, items, returnValue: result.value };\n }\n\n // Handle AsyncChannel specially to capture return value\n if (source instanceof AsyncChannel) {\n const iterator = source[Symbol.asyncIterator]();\n let result = await iterator.next();\n while (!result.done) {\n await updateToast(result.value);\n result = await iterator.next();\n }\n return { ok: true, items, returnValue: result.value };\n }\n\n // For plain iterables/iterators, convert and iterate (no return value)\n const iterable =\n isAsyncIterable(source) || isIterable(source)\n ? toAsyncIterable(source)\n : toAsyncIterable(source as AsyncIterator<TYield>);\n\n for await (const item of iterable) {\n await updateToast(item);\n }\n\n return { ok: true, items, returnValue: undefined as TReturn };\n } catch (error) {\n return { ok: false, items, error };\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// toastStream overloads\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Like `toast.promise` but for iterables/generators/streams.\n *\n * Handles: loading → streaming → success/error\n *\n * Supports sync and async sources. Captures generator return values.\n * Accepts either an instance or a factory function.\n *\n * @example\n * ```tsx\n * // Pass generator function directly (recommended)\n * await toastStream(fetchData, { ... });\n *\n * // Or pass the result of calling it\n * await toastStream(fetchData(), { ... });\n * ```\n */\nexport function toastStream<TYield, TReturn>(\n source:\n | AsyncGenerator<TYield, TReturn, unknown>\n | (() => AsyncGenerator<TYield, TReturn, unknown>),\n data?: ToastStreamData<TYield, TReturn>,\n): Promise<StreamResult<TYield, TReturn>>;\n\n/** Sync Generator with return value */\nexport function toastStream<TYield, TReturn>(\n source: Generator<TYield, TReturn, unknown> | (() => Generator<TYield, TReturn, unknown>),\n data?: ToastStreamData<TYield, TReturn>,\n): Promise<StreamResult<TYield, TReturn>>;\n\n/** AsyncChannel with return type - must come before AsyncIterable overload */\nexport function toastStream<TYield, TReturn>(\n source: AsyncChannel<TYield, TReturn> | (() => AsyncChannel<TYield, TReturn>),\n data?: ToastStreamData<TYield, TReturn>,\n): Promise<StreamResult<TYield, TReturn>>;\n\n/** AsyncIterable (no return value) */\nexport function toastStream<TYield>(\n source: AsyncIterable<TYield> | (() => AsyncIterable<TYield>),\n data?: ToastStreamData<TYield, void>,\n): Promise<StreamResult<TYield, void>>;\n\n/** AsyncIterator (no return value) */\nexport function toastStream<TYield>(\n source: AsyncIterator<TYield> | (() => AsyncIterator<TYield>),\n data?: ToastStreamData<TYield, void>,\n): Promise<StreamResult<TYield, void>>;\n\n/** Sync Iterable (no return value) */\nexport function toastStream<TYield>(\n source: Iterable<TYield> | (() => Iterable<TYield>),\n data?: ToastStreamData<TYield, void>,\n): Promise<StreamResult<TYield, void>>;\n\n/** Sync Iterator (no return value) */\nexport function toastStream<TYield>(\n source: Iterator<TYield> | (() => Iterator<TYield>),\n data?: ToastStreamData<TYield, void>,\n): Promise<StreamResult<TYield, void>>;\n\n// Implementation\nexport async function toastStream<TYield, TReturn = void>(\n source: StreamSource<TYield>,\n data: ToastStreamData<TYield, TReturn> = {},\n): Promise<StreamResult<TYield, TReturn>> {\n const {\n loading,\n streaming,\n success,\n error,\n finally: onFinally,\n onItem,\n delay,\n ...toastOptions\n } = data;\n\n const delayConfig = normalizeDelay(delay);\n const { message: loadingMsg, options: loadingOptions } = await resolveLoading(\n loading,\n \"Processing...\",\n );\n const toastId = toast.loading(loadingMsg, {\n ...toastOptions,\n ...loadingOptions,\n });\n\n const result = await consumeStream<TYield, TReturn>({\n source,\n toastId,\n streaming,\n onItem,\n delayConfig,\n });\n\n await onFinally?.();\n\n // Delay before showing result\n if (delayConfig.result) {\n await wait(delayConfig.result);\n }\n\n if (!result.ok) {\n const partial: ErrorPartialData<TYield> = {\n count: result.items.length,\n items: result.items,\n };\n const { message: errorMsg, options: errorOptions } = await resolveErrorResult(\n error,\n result.error,\n partial,\n \"Stream failed\",\n );\n toast.error(errorMsg, { id: toastId, ...errorOptions });\n throw result.error;\n }\n\n const successData: SuccessData<TYield, TReturn> = {\n count: result.items.length,\n items: result.items,\n returnValue: result.returnValue,\n };\n const { message: successMsg, options: successOptions } = await resolveResult(\n success,\n successData,\n `Complete! ${result.items.length} items`,\n );\n\n toast.success(successMsg, { id: toastId, ...successOptions });\n return { items: result.items, returnValue: result.returnValue };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// toastStreamAsync overloads\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Return type matching toast.promise's unwrap pattern */\ntype ToastStreamReturn<TYield, TReturn> = (string | number) & {\n unwrap: () => Promise<StreamResult<TYield, TReturn>>;\n};\n\n/**\n * Non-blocking version that returns immediately like `toast.promise`.\n * Returns toast ID with `unwrap()` method to await the result.\n * Accepts either an instance or a factory function.\n *\n * @example\n * ```ts\n * const { unwrap } = toastStreamAsync(fetchData, { loading: \"...\" });\n * const { items, returnValue } = await unwrap();\n * ```\n */\nexport function toastStreamAsync<TYield, TReturn>(\n source:\n | AsyncGenerator<TYield, TReturn, unknown>\n | (() => AsyncGenerator<TYield, TReturn, unknown>),\n data?: ToastStreamData<TYield, TReturn>,\n): ToastStreamReturn<TYield, TReturn>;\n\n/** Sync Generator with return value */\nexport function toastStreamAsync<TYield, TReturn>(\n source: Generator<TYield, TReturn, unknown> | (() => Generator<TYield, TReturn, unknown>),\n data?: ToastStreamData<TYield, TReturn>,\n): ToastStreamReturn<TYield, TReturn>;\n\n/** AsyncChannel with return type */\nexport function toastStreamAsync<TYield, TReturn>(\n source: AsyncChannel<TYield, TReturn> | (() => AsyncChannel<TYield, TReturn>),\n data?: ToastStreamData<TYield, TReturn>,\n): ToastStreamReturn<TYield, TReturn>;\n\n/** AsyncIterable - excludes AsyncChannel which has its own overload */\nexport function toastStreamAsync<TYield>(\n source:\n | Exclude<AsyncIterable<TYield>, AsyncChannel<TYield, unknown>>\n | (() => Exclude<AsyncIterable<TYield>, AsyncChannel<TYield, unknown>>),\n data?: ToastStreamData<TYield, void>,\n): ToastStreamReturn<TYield, void>;\n\n/** AsyncIterator */\nexport function toastStreamAsync<TYield>(\n source: AsyncIterator<TYield> | (() => AsyncIterator<TYield>),\n data?: ToastStreamData<TYield, void>,\n): ToastStreamReturn<TYield, void>;\n\n/** Sync Iterable */\nexport function toastStreamAsync<TYield>(\n source: Iterable<TYield> | (() => Iterable<TYield>),\n data?: ToastStreamData<TYield, void>,\n): ToastStreamReturn<TYield, void>;\n\n/** Sync Iterator */\nexport function toastStreamAsync<TYield>(\n source: Iterator<TYield> | (() => Iterator<TYield>),\n data?: ToastStreamData<TYield, void>,\n): ToastStreamReturn<TYield, void>;\n\n// Implementation\nexport function toastStreamAsync<TYield, TReturn = void>(\n source: StreamSource<TYield>,\n data: ToastStreamData<TYield, TReturn> = {},\n): ToastStreamReturn<TYield, TReturn> {\n const { loading, delay, ...rest } = data;\n\n const delayConfig = normalizeDelay(delay);\n // For non-blocking version, show simple loading immediately\n // Callbacks and extended results are resolved inside the promise\n const needsAsyncResolve =\n typeof loading === \"function\" ||\n (typeof loading === \"object\" && loading !== null && \"message\" in loading);\n const initialLoadingMsg = needsAsyncResolve\n ? \"Processing...\"\n : ((loading as ReactNode) ?? \"Processing...\");\n const toastId = toast.loading(initialLoadingMsg);\n\n const promise = (async () => {\n const { streaming, success, error, finally: onFinally, onItem, ...toastOptions } = rest;\n\n // If loading needs async resolution (callback or extended result), resolve and update\n if (needsAsyncResolve) {\n const { message: loadingMsg, options: loadingOptions } = await resolveLoading(\n loading,\n \"Processing...\",\n );\n toast.loading(loadingMsg, { id: toastId, ...loadingOptions });\n }\n\n const result = await consumeStream<TYield, TReturn>({\n source,\n toastId,\n streaming,\n onItem,\n delayConfig,\n });\n\n await onFinally?.();\n\n // Delay before showing result\n if (delayConfig.result) {\n await wait(delayConfig.result);\n }\n\n if (!result.ok) {\n const partial: ErrorPartialData<TYield> = {\n count: result.items.length,\n items: result.items,\n };\n const { message: errorMsg, options: errorOptions } = await resolveErrorResult(\n error,\n result.error,\n partial,\n \"Stream failed\",\n );\n toast.error(errorMsg, { id: toastId, ...toastOptions, ...errorOptions });\n throw result.error;\n }\n\n const successData: SuccessData<TYield, TReturn> = {\n count: result.items.length,\n items: result.items,\n returnValue: result.returnValue,\n };\n const { message: successMsg, options: successOptions } = await resolveResult(\n success,\n successData,\n `Complete! ${result.items.length} items`,\n );\n\n toast.success(successMsg, {\n id: toastId,\n ...toastOptions,\n ...successOptions,\n });\n return { items: result.items, returnValue: result.returnValue };\n })();\n\n // Match toast.promise's return signature\n const res = toastId as ToastStreamReturn<TYield, TReturn>;\n res.unwrap = () => promise;\n return res;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"entry-sonner.cjs","names":["resolveStreamSource","wait","isGenerator","isAsyncGenerator","AsyncChannel","isAsyncIterable","isIterable","toAsyncIterable","toast"],"sources":["../src/toast-stream.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\n\nimport {\n AsyncChannel,\n isAsyncGenerator,\n isAsyncIterable,\n isGenerator,\n isIterable,\n resolveStreamSource,\n type StreamSource,\n toAsyncIterable,\n wait,\n} from \"./index.ts\";\nimport { type ExternalToast, toast } from \"sonner\";\n\n// Types matching Sonner's patterns\ntype StreamExternalToast = Omit<ExternalToast, \"description\">;\n\ninterface StreamExtendedResult extends ExternalToast {\n message: ReactNode;\n}\n\n/** Result can be string, ReactNode, or callback returning either (sync or async) */\ntype StreamResultT<TData = unknown> =\n | string\n | ReactNode\n | ((data: TData) => ReactNode | string | Promise<ReactNode | string>);\n\n/** Extended result includes full toast options */\ntype StreamExtendedResultT<TData = unknown> =\n | StreamExtendedResult\n | ((data: TData) => StreamExtendedResult | Promise<StreamExtendedResult>);\n\n/** Error result - callback receives error first, then partial data */\ntype StreamErrorResultT<TYield> =\n | string\n | ReactNode\n | ((\n error: unknown,\n partial: ErrorPartialData<TYield>,\n ) => ReactNode | string | Promise<ReactNode | string>);\n\n/** Extended error result with toast options */\ntype StreamExtendedErrorResultT<TYield> =\n | StreamExtendedResult\n | ((\n error: unknown,\n partial: ErrorPartialData<TYield>,\n ) => StreamExtendedResult | Promise<StreamExtendedResult>);\n\n/** Streaming progress data passed to callbacks */\ntype StreamingData<T> = {\n count: number;\n latest: T;\n /** All items received so far (including latest) */\n items: T[];\n};\n\n/** Success data passed to callbacks - includes return value if generator has one */\ntype SuccessData<TYield, TReturn = void> = {\n count: number;\n items: TYield[];\n /** The return value from the generator (if any) */\n returnValue: TReturn;\n};\n\n/** Partial data available when an error occurs */\ntype ErrorPartialData<TYield> = {\n /** Number of items received before error */\n count: number;\n /** Items received before error */\n items: TYield[];\n};\n\n/** Result of consuming a stream */\ntype StreamResult<TYield, TReturn> = {\n items: TYield[];\n returnValue: TReturn;\n};\n\n/** Internal result from consumeStream - includes partial data on error */\ntype ConsumeResult<TYield, TReturn> =\n | { ok: true; items: TYield[]; returnValue: TReturn }\n | { ok: false; items: TYield[]; error: unknown };\n\n/** Granular delay configuration */\ntype DelayConfig = {\n /** Delay between items in ms */\n items?: number;\n /** Delay after loading, before first item */\n first?: number;\n /** Delay before showing success/error */\n result?: number;\n};\n\n/** Loading result - no data, just static or callback */\ntype LoadingResultT = string | ReactNode | (() => ReactNode | string | Promise<ReactNode | string>);\n\n/** Extended loading result with toast options */\ninterface LoadingExtendedResult extends ExternalToast {\n message: ReactNode;\n}\n\ntype LoadingExtendedResultT =\n | LoadingExtendedResult\n | (() => LoadingExtendedResult | Promise<LoadingExtendedResult>);\n\n/** Options matching Sonner's PromiseData pattern */\ntype ToastStreamData<TYield = unknown, TReturn = void> = StreamExternalToast & {\n /**\n * Message shown while waiting for first item.\n * Supports string, ReactNode, callback, or extended result with toast options.\n */\n loading?: LoadingResultT | LoadingExtendedResultT;\n /** Message shown while streaming - updates on each item */\n streaming?: StreamResultT<StreamingData<TYield>> | StreamExtendedResultT<StreamingData<TYield>>;\n /** Message shown on successful completion */\n success?:\n | StreamResultT<SuccessData<TYield, TReturn>>\n | StreamExtendedResultT<SuccessData<TYield, TReturn>>;\n /** Message shown on error - callback receives (error, { count, items }) */\n error?: StreamErrorResultT<TYield> | StreamExtendedErrorResultT<TYield>;\n /** Called when stream ends (success or error) */\n finally?: () => void | Promise<void>;\n /** Called for each item received */\n onItem?: (item: TYield, count: number) => void;\n /**\n * Artificial delay for visualizing sync streams.\n * - `number`: applies to all phases (first, items, result)\n * - `object`: granular control over individual phases\n *\n * @example\n * // Simple: 500ms for all phases (first, between items, before result)\n * delay: 500\n *\n * @example\n * // Granular: different delays for different phases\n * delay: { first: 500, items: 300, result: 200 }\n */\n delay?: number | DelayConfig;\n};\n\n/** Resolve a result that can be string, ReactNode, or callback */\nasync function resolveResult<TData>(\n result: StreamResultT<TData> | StreamExtendedResultT<TData> | undefined,\n data: TData,\n fallback: string,\n): Promise<{ message: ReactNode; options?: ExternalToast }> {\n if (result === undefined) {\n return { message: fallback };\n }\n\n if (typeof result === \"function\") {\n const resolved = await result(data);\n if (typeof resolved === \"object\" && resolved !== null && \"message\" in resolved) {\n const { message, ...options } = resolved as StreamExtendedResult;\n return { message, options };\n }\n return { message: resolved as ReactNode };\n }\n\n if (typeof result === \"object\" && result !== null && \"message\" in result) {\n const { message, ...options } = result as StreamExtendedResult;\n return { message, options };\n }\n\n return { message: result };\n}\n\n/** Resolve an error result that receives (error, partialData) */\nasync function resolveErrorResult<TYield>(\n result: StreamErrorResultT<TYield> | StreamExtendedErrorResultT<TYield> | undefined,\n error: unknown,\n partial: ErrorPartialData<TYield>,\n fallback: string,\n): Promise<{ message: ReactNode; options?: ExternalToast }> {\n if (result === undefined) {\n return { message: fallback };\n }\n\n if (typeof result === \"function\") {\n const resolved = await result(error, partial);\n if (typeof resolved === \"object\" && resolved !== null && \"message\" in resolved) {\n const { message, ...options } = resolved as StreamExtendedResult;\n return { message, options };\n }\n return { message: resolved as ReactNode };\n }\n\n if (typeof result === \"object\" && result !== null && \"message\" in result) {\n const { message, ...options } = result as StreamExtendedResult;\n return { message, options };\n }\n\n return { message: result };\n}\n\n/** Resolve loading result - no data passed to callback */\nasync function resolveLoading(\n result: LoadingResultT | LoadingExtendedResultT | undefined,\n fallback: string,\n): Promise<{ message: ReactNode; options?: ExternalToast }> {\n if (result === undefined) {\n return { message: fallback };\n }\n\n if (typeof result === \"function\") {\n const resolved = await result();\n if (typeof resolved === \"object\" && resolved !== null && \"message\" in resolved) {\n const { message, ...options } = resolved as LoadingExtendedResult;\n return { message, options };\n }\n return { message: resolved as ReactNode };\n }\n\n if (typeof result === \"object\" && result !== null && \"message\" in result) {\n const { message, ...options } = result as LoadingExtendedResult;\n return { message, options };\n }\n\n return { message: result };\n}\n\n/** Normalize delay option to full DelayConfig */\nfunction normalizeDelay(delay: number | DelayConfig | undefined): DelayConfig {\n if (delay === undefined) {\n return {};\n }\n if (typeof delay === \"number\") {\n // Simple number applies to all phases: first, items, and result\n return { first: delay, items: delay, result: delay };\n }\n return delay;\n}\n\n/** Options for consumeStream */\ntype ConsumeStreamOptions<TYield, TReturn> = {\n source: StreamSource<TYield>;\n toastId: string | number;\n streaming: ToastStreamData<TYield, TReturn>[\"streaming\"];\n onItem: ToastStreamData<TYield, TReturn>[\"onItem\"];\n delayConfig: DelayConfig;\n};\n\n/** Core streaming logic - consumes source and updates toast, captures return value */\nasync function consumeStream<TYield, TReturn>(\n options: ConsumeStreamOptions<TYield, TReturn>,\n): Promise<ConsumeResult<TYield, TReturn>> {\n const { source: sourceOrFactory, toastId, streaming, onItem, delayConfig } = options;\n // Resolve factory function if needed\n const source = resolveStreamSource(sourceOrFactory);\n\n const items: TYield[] = [];\n let count = 0;\n let isFirst = true;\n\n const updateToast = async (item: TYield) => {\n // Delay before first item (after loading)\n if (isFirst && delayConfig.first) {\n await wait(delayConfig.first);\n isFirst = false;\n } else if (!isFirst && delayConfig.items) {\n // Delay between items (not before first)\n await wait(delayConfig.items);\n } else {\n isFirst = false;\n }\n\n count += 1;\n items.push(item);\n onItem?.(item, count);\n\n const streamingData: StreamingData<TYield> = {\n count,\n latest: item,\n items: [...items],\n };\n const { message: streamingMsg, options: streamingOptions } = await resolveResult(\n streaming,\n streamingData,\n `Streaming... (${count} items)`,\n );\n toast.loading(streamingMsg, { id: toastId, ...streamingOptions });\n };\n\n try {\n // Handle sync Generator specially to capture return value\n if (isGenerator<TYield, TReturn>(source)) {\n let result = source.next();\n while (!result.done) {\n await updateToast(result.value);\n result = source.next();\n }\n return { ok: true, items, returnValue: result.value };\n }\n\n // Handle AsyncGenerator specially to capture return value\n if (isAsyncGenerator<TYield, TReturn>(source)) {\n let result = await source.next();\n while (!result.done) {\n await updateToast(result.value);\n result = await source.next();\n }\n return { ok: true, items, returnValue: result.value };\n }\n\n // Handle AsyncChannel specially to capture return value\n if (source instanceof AsyncChannel) {\n const iterator = source[Symbol.asyncIterator]();\n let result = await iterator.next();\n while (!result.done) {\n await updateToast(result.value);\n result = await iterator.next();\n }\n return { ok: true, items, returnValue: result.value };\n }\n\n // For plain iterables/iterators, convert and iterate (no return value)\n const iterable =\n isAsyncIterable(source) || isIterable(source)\n ? toAsyncIterable(source)\n : toAsyncIterable(source as AsyncIterator<TYield>);\n\n for await (const item of iterable) {\n await updateToast(item);\n }\n\n return { ok: true, items, returnValue: undefined as TReturn };\n } catch (error) {\n return { ok: false, items, error };\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// toastStream overloads\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Like `toast.promise` but for iterables/generators/streams.\n *\n * Blocking/awaitable API: waits until the stream completes and resolves to\n * `{ items, returnValue }`. Use this when the caller should wait for all\n * streamed values before continuing.\n *\n * Handles: loading → streaming → success/error.\n *\n * Supports sync and async sources. Captures generator return values.\n * Accepts either an instance or a factory function.\n *\n * @example Async generator with progress and return value\n * ```ts\n * import { toastStream } from \"xantiagoma/sonner\";\n *\n * async function* importUsers() {\n * yield { id: 1, name: \"Ada\" };\n * yield { id: 2, name: \"Grace\" };\n * return { imported: 2 };\n * }\n *\n * const { items, returnValue } = await toastStream(importUsers, {\n * loading: \"Importing users...\",\n * streaming: ({ count, latest }) => `Imported ${count}: ${latest.name}`,\n * success: ({ count, returnValue }) =>\n * `Imported ${count} users (${returnValue.imported} confirmed)`,\n * error: (error, partial) =>\n * `Import failed after ${partial.count} users: ${String(error)}`,\n * });\n * ```\n *\n * @example Sync iterable with artificial delay\n * ```ts\n * const result = await toastStream([\"parse\", \"validate\", \"save\"], {\n * loading: \"Starting...\",\n * streaming: ({ latest }) => `Running ${latest}`,\n * success: ({ count }) => `Completed ${count} steps`,\n * delay: { first: 300, items: 200, result: 150 },\n * });\n * ```\n */\nexport function toastStream<TYield, TReturn>(\n source:\n | AsyncGenerator<TYield, TReturn, unknown>\n | (() => AsyncGenerator<TYield, TReturn, unknown>),\n data?: ToastStreamData<TYield, TReturn>,\n): Promise<StreamResult<TYield, TReturn>>;\n\n/** Sync Generator with return value */\nexport function toastStream<TYield, TReturn>(\n source: Generator<TYield, TReturn, unknown> | (() => Generator<TYield, TReturn, unknown>),\n data?: ToastStreamData<TYield, TReturn>,\n): Promise<StreamResult<TYield, TReturn>>;\n\n/** AsyncChannel with return type - must come before AsyncIterable overload */\nexport function toastStream<TYield, TReturn>(\n source: AsyncChannel<TYield, TReturn> | (() => AsyncChannel<TYield, TReturn>),\n data?: ToastStreamData<TYield, TReturn>,\n): Promise<StreamResult<TYield, TReturn>>;\n\n/** AsyncIterable (no return value) */\nexport function toastStream<TYield>(\n source: AsyncIterable<TYield> | (() => AsyncIterable<TYield>),\n data?: ToastStreamData<TYield, void>,\n): Promise<StreamResult<TYield, void>>;\n\n/** AsyncIterator (no return value) */\nexport function toastStream<TYield>(\n source: AsyncIterator<TYield> | (() => AsyncIterator<TYield>),\n data?: ToastStreamData<TYield, void>,\n): Promise<StreamResult<TYield, void>>;\n\n/** Sync Iterable (no return value) */\nexport function toastStream<TYield>(\n source: Iterable<TYield> | (() => Iterable<TYield>),\n data?: ToastStreamData<TYield, void>,\n): Promise<StreamResult<TYield, void>>;\n\n/** Sync Iterator (no return value) */\nexport function toastStream<TYield>(\n source: Iterator<TYield> | (() => Iterator<TYield>),\n data?: ToastStreamData<TYield, void>,\n): Promise<StreamResult<TYield, void>>;\n\n// Implementation\nexport async function toastStream<TYield, TReturn = void>(\n source: StreamSource<TYield>,\n data: ToastStreamData<TYield, TReturn> = {},\n): Promise<StreamResult<TYield, TReturn>> {\n const {\n loading,\n streaming,\n success,\n error,\n finally: onFinally,\n onItem,\n delay,\n ...toastOptions\n } = data;\n\n const delayConfig = normalizeDelay(delay);\n const { message: loadingMsg, options: loadingOptions } = await resolveLoading(\n loading,\n \"Processing...\",\n );\n const toastId = toast.loading(loadingMsg, {\n ...toastOptions,\n ...loadingOptions,\n });\n\n const result = await consumeStream<TYield, TReturn>({\n source,\n toastId,\n streaming,\n onItem,\n delayConfig,\n });\n\n await onFinally?.();\n\n // Delay before showing result\n if (delayConfig.result) {\n await wait(delayConfig.result);\n }\n\n if (!result.ok) {\n const partial: ErrorPartialData<TYield> = {\n count: result.items.length,\n items: result.items,\n };\n const { message: errorMsg, options: errorOptions } = await resolveErrorResult(\n error,\n result.error,\n partial,\n \"Stream failed\",\n );\n toast.error(errorMsg, { id: toastId, ...errorOptions });\n throw result.error;\n }\n\n const successData: SuccessData<TYield, TReturn> = {\n count: result.items.length,\n items: result.items,\n returnValue: result.returnValue,\n };\n const { message: successMsg, options: successOptions } = await resolveResult(\n success,\n successData,\n `Complete! ${result.items.length} items`,\n );\n\n toast.success(successMsg, { id: toastId, ...successOptions });\n return { items: result.items, returnValue: result.returnValue };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// toastStreamAsync overloads\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Return type matching toast.promise's unwrap pattern */\ntype ToastStreamReturn<TYield, TReturn> = (string | number) & {\n unwrap: () => Promise<StreamResult<TYield, TReturn>>;\n};\n\n/**\n * Non-blocking version that returns immediately like `toast.promise`.\n * Returns the toast ID with an `unwrap()` method to await the result later.\n * Accepts either an instance or a factory function.\n *\n * Use this when the UI should continue immediately while the toast tracks the\n * stream in the background.\n *\n * @example\n * ```ts\n * import { toastStreamAsync } from \"xantiagoma/sonner\";\n *\n * const toastId = toastStreamAsync(importUsers, {\n * loading: \"Importing users...\",\n * streaming: ({ count }) => `Imported ${count}`,\n * success: ({ count }) => `Imported ${count} users`,\n * });\n *\n * // The caller continues immediately; await only if/when the result matters.\n * const { items, returnValue } = await toastId.unwrap();\n * ```\n *\n * @example Fire-and-track with error handling\n * ```ts\n * const toastId = toastStreamAsync(uploadFiles(files), {\n * loading: \"Uploading...\",\n * streaming: ({ count }) => `${count} files uploaded`,\n * error: (error, partial) =>\n * `Upload failed after ${partial.count} files: ${String(error)}`,\n * });\n *\n * toastId.unwrap().catch((error) => {\n * reportError(error);\n * });\n * ```\n */\nexport function toastStreamAsync<TYield, TReturn>(\n source:\n | AsyncGenerator<TYield, TReturn, unknown>\n | (() => AsyncGenerator<TYield, TReturn, unknown>),\n data?: ToastStreamData<TYield, TReturn>,\n): ToastStreamReturn<TYield, TReturn>;\n\n/** Sync Generator with return value */\nexport function toastStreamAsync<TYield, TReturn>(\n source: Generator<TYield, TReturn, unknown> | (() => Generator<TYield, TReturn, unknown>),\n data?: ToastStreamData<TYield, TReturn>,\n): ToastStreamReturn<TYield, TReturn>;\n\n/** AsyncChannel with return type */\nexport function toastStreamAsync<TYield, TReturn>(\n source: AsyncChannel<TYield, TReturn> | (() => AsyncChannel<TYield, TReturn>),\n data?: ToastStreamData<TYield, TReturn>,\n): ToastStreamReturn<TYield, TReturn>;\n\n/** AsyncIterable - excludes AsyncChannel which has its own overload */\nexport function toastStreamAsync<TYield>(\n source:\n | Exclude<AsyncIterable<TYield>, AsyncChannel<TYield, unknown>>\n | (() => Exclude<AsyncIterable<TYield>, AsyncChannel<TYield, unknown>>),\n data?: ToastStreamData<TYield, void>,\n): ToastStreamReturn<TYield, void>;\n\n/** AsyncIterator */\nexport function toastStreamAsync<TYield>(\n source: AsyncIterator<TYield> | (() => AsyncIterator<TYield>),\n data?: ToastStreamData<TYield, void>,\n): ToastStreamReturn<TYield, void>;\n\n/** Sync Iterable */\nexport function toastStreamAsync<TYield>(\n source: Iterable<TYield> | (() => Iterable<TYield>),\n data?: ToastStreamData<TYield, void>,\n): ToastStreamReturn<TYield, void>;\n\n/** Sync Iterator */\nexport function toastStreamAsync<TYield>(\n source: Iterator<TYield> | (() => Iterator<TYield>),\n data?: ToastStreamData<TYield, void>,\n): ToastStreamReturn<TYield, void>;\n\n// Implementation\nexport function toastStreamAsync<TYield, TReturn = void>(\n source: StreamSource<TYield>,\n data: ToastStreamData<TYield, TReturn> = {},\n): ToastStreamReturn<TYield, TReturn> {\n const { loading, delay, ...rest } = data;\n\n const delayConfig = normalizeDelay(delay);\n // For non-blocking version, show simple loading immediately\n // Callbacks and extended results are resolved inside the promise\n const needsAsyncResolve =\n typeof loading === \"function\" ||\n (typeof loading === \"object\" && loading !== null && \"message\" in loading);\n const initialLoadingMsg = needsAsyncResolve\n ? \"Processing...\"\n : ((loading as ReactNode) ?? \"Processing...\");\n const toastId = toast.loading(initialLoadingMsg);\n\n const promise = (async () => {\n const { streaming, success, error, finally: onFinally, onItem, ...toastOptions } = rest;\n\n // If loading needs async resolution (callback or extended result), resolve and update\n if (needsAsyncResolve) {\n const { message: loadingMsg, options: loadingOptions } = await resolveLoading(\n loading,\n \"Processing...\",\n );\n toast.loading(loadingMsg, { id: toastId, ...loadingOptions });\n }\n\n const result = await consumeStream<TYield, TReturn>({\n source,\n toastId,\n streaming,\n onItem,\n delayConfig,\n });\n\n await onFinally?.();\n\n // Delay before showing result\n if (delayConfig.result) {\n await wait(delayConfig.result);\n }\n\n if (!result.ok) {\n const partial: ErrorPartialData<TYield> = {\n count: result.items.length,\n items: result.items,\n };\n const { message: errorMsg, options: errorOptions } = await resolveErrorResult(\n error,\n result.error,\n partial,\n \"Stream failed\",\n );\n toast.error(errorMsg, { id: toastId, ...toastOptions, ...errorOptions });\n throw result.error;\n }\n\n const successData: SuccessData<TYield, TReturn> = {\n count: result.items.length,\n items: result.items,\n returnValue: result.returnValue,\n };\n const { message: successMsg, options: successOptions } = await resolveResult(\n success,\n successData,\n `Complete! ${result.items.length} items`,\n );\n\n toast.success(successMsg, {\n id: toastId,\n ...toastOptions,\n ...successOptions,\n });\n return { items: result.items, returnValue: result.returnValue };\n })();\n\n // Match toast.promise's return signature\n const res = toastId as ToastStreamReturn<TYield, TReturn>;\n res.unwrap = () => promise;\n return res;\n}\n"],"mappings":";;;;;;AA+IA,eAAe,cACb,QACA,MACA,UAC0D;CAC1D,IAAI,WAAW,KAAA,GACb,OAAO,EAAE,SAAS,UAAU;CAG9B,IAAI,OAAO,WAAW,YAAY;EAChC,MAAM,WAAW,MAAM,OAAO,KAAK;EACnC,IAAI,OAAO,aAAa,YAAY,aAAa,QAAQ,aAAa,UAAU;GAC9E,MAAM,EAAE,SAAS,GAAG,YAAY;GAChC,OAAO;IAAE;IAAS;IAAS;;EAE7B,OAAO,EAAE,SAAS,UAAuB;;CAG3C,IAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,aAAa,QAAQ;EACxE,MAAM,EAAE,SAAS,GAAG,YAAY;EAChC,OAAO;GAAE;GAAS;GAAS;;CAG7B,OAAO,EAAE,SAAS,QAAQ;;;AAI5B,eAAe,mBACb,QACA,OACA,SACA,UAC0D;CAC1D,IAAI,WAAW,KAAA,GACb,OAAO,EAAE,SAAS,UAAU;CAG9B,IAAI,OAAO,WAAW,YAAY;EAChC,MAAM,WAAW,MAAM,OAAO,OAAO,QAAQ;EAC7C,IAAI,OAAO,aAAa,YAAY,aAAa,QAAQ,aAAa,UAAU;GAC9E,MAAM,EAAE,SAAS,GAAG,YAAY;GAChC,OAAO;IAAE;IAAS;IAAS;;EAE7B,OAAO,EAAE,SAAS,UAAuB;;CAG3C,IAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,aAAa,QAAQ;EACxE,MAAM,EAAE,SAAS,GAAG,YAAY;EAChC,OAAO;GAAE;GAAS;GAAS;;CAG7B,OAAO,EAAE,SAAS,QAAQ;;;AAI5B,eAAe,eACb,QACA,UAC0D;CAC1D,IAAI,WAAW,KAAA,GACb,OAAO,EAAE,SAAS,UAAU;CAG9B,IAAI,OAAO,WAAW,YAAY;EAChC,MAAM,WAAW,MAAM,QAAQ;EAC/B,IAAI,OAAO,aAAa,YAAY,aAAa,QAAQ,aAAa,UAAU;GAC9E,MAAM,EAAE,SAAS,GAAG,YAAY;GAChC,OAAO;IAAE;IAAS;IAAS;;EAE7B,OAAO,EAAE,SAAS,UAAuB;;CAG3C,IAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,aAAa,QAAQ;EACxE,MAAM,EAAE,SAAS,GAAG,YAAY;EAChC,OAAO;GAAE;GAAS;GAAS;;CAG7B,OAAO,EAAE,SAAS,QAAQ;;;AAI5B,SAAS,eAAe,OAAsD;CAC5E,IAAI,UAAU,KAAA,GACZ,OAAO,EAAE;CAEX,IAAI,OAAO,UAAU,UAEnB,OAAO;EAAE,OAAO;EAAO,OAAO;EAAO,QAAQ;EAAO;CAEtD,OAAO;;;AAaT,eAAe,cACb,SACyC;CACzC,MAAM,EAAE,QAAQ,iBAAiB,SAAS,WAAW,QAAQ,gBAAgB;CAE7E,MAAM,SAASA,YAAAA,oBAAoB,gBAAgB;CAEnD,MAAM,QAAkB,EAAE;CAC1B,IAAI,QAAQ;CACZ,IAAI,UAAU;CAEd,MAAM,cAAc,OAAO,SAAiB;EAE1C,IAAI,WAAW,YAAY,OAAO;GAChC,MAAMC,YAAAA,KAAK,YAAY,MAAM;GAC7B,UAAU;SACL,IAAI,CAAC,WAAW,YAAY,OAEjC,MAAMA,YAAAA,KAAK,YAAY,MAAM;OAE7B,UAAU;EAGZ,SAAS;EACT,MAAM,KAAK,KAAK;EAChB,SAAS,MAAM,MAAM;EAOrB,MAAM,EAAE,SAAS,cAAc,SAAS,qBAAqB,MAAM,cACjE,WACA;GANA;GACA,QAAQ;GACR,OAAO,CAAC,GAAG,MAAM;GAIJ,EACb,iBAAiB,MAAM,SACxB;EACD,OAAA,MAAM,QAAQ,cAAc;GAAE,IAAI;GAAS,GAAG;GAAkB,CAAC;;CAGnE,IAAI;EAEF,IAAIC,YAAAA,YAA6B,OAAO,EAAE;GACxC,IAAI,SAAS,OAAO,MAAM;GAC1B,OAAO,CAAC,OAAO,MAAM;IACnB,MAAM,YAAY,OAAO,MAAM;IAC/B,SAAS,OAAO,MAAM;;GAExB,OAAO;IAAE,IAAI;IAAM;IAAO,aAAa,OAAO;IAAO;;EAIvD,IAAIC,YAAAA,iBAAkC,OAAO,EAAE;GAC7C,IAAI,SAAS,MAAM,OAAO,MAAM;GAChC,OAAO,CAAC,OAAO,MAAM;IACnB,MAAM,YAAY,OAAO,MAAM;IAC/B,SAAS,MAAM,OAAO,MAAM;;GAE9B,OAAO;IAAE,IAAI;IAAM;IAAO,aAAa,OAAO;IAAO;;EAIvD,IAAI,kBAAkBC,YAAAA,cAAc;GAClC,MAAM,WAAW,OAAO,OAAO,gBAAgB;GAC/C,IAAI,SAAS,MAAM,SAAS,MAAM;GAClC,OAAO,CAAC,OAAO,MAAM;IACnB,MAAM,YAAY,OAAO,MAAM;IAC/B,SAAS,MAAM,SAAS,MAAM;;GAEhC,OAAO;IAAE,IAAI;IAAM;IAAO,aAAa,OAAO;IAAO;;EAIvD,MAAM,WACJC,YAAAA,gBAAgB,OAAO,IAAIC,YAAAA,WAAW,OAAO,GACzCC,YAAAA,gBAAgB,OAAO,GACvBA,YAAAA,gBAAgB,OAAgC;EAEtD,WAAW,MAAM,QAAQ,UACvB,MAAM,YAAY,KAAK;EAGzB,OAAO;GAAE,IAAI;GAAM;GAAO,aAAa,KAAA;GAAsB;UACtD,OAAO;EACd,OAAO;GAAE,IAAI;GAAO;GAAO;GAAO;;;AA8FtC,eAAsB,YACpB,QACA,OAAyC,EAAE,EACH;CACxC,MAAM,EACJ,SACA,WACA,SACA,OACA,SAAS,WACT,QACA,OACA,GAAG,iBACD;CAEJ,MAAM,cAAc,eAAe,MAAM;CACzC,MAAM,EAAE,SAAS,YAAY,SAAS,mBAAmB,MAAM,eAC7D,SACA,gBACD;CACD,MAAM,UAAUC,OAAAA,MAAM,QAAQ,YAAY;EACxC,GAAG;EACH,GAAG;EACJ,CAAC;CAEF,MAAM,SAAS,MAAM,cAA+B;EAClD;EACA;EACA;EACA;EACA;EACD,CAAC;CAEF,MAAM,aAAa;CAGnB,IAAI,YAAY,QACd,MAAMP,YAAAA,KAAK,YAAY,OAAO;CAGhC,IAAI,CAAC,OAAO,IAAI;EACd,MAAM,UAAoC;GACxC,OAAO,OAAO,MAAM;GACpB,OAAO,OAAO;GACf;EACD,MAAM,EAAE,SAAS,UAAU,SAAS,iBAAiB,MAAM,mBACzD,OACA,OAAO,OACP,SACA,gBACD;EACD,OAAA,MAAM,MAAM,UAAU;GAAE,IAAI;GAAS,GAAG;GAAc,CAAC;EACvD,MAAM,OAAO;;CAQf,MAAM,EAAE,SAAS,YAAY,SAAS,mBAAmB,MAAM,cAC7D,SACA;EANA,OAAO,OAAO,MAAM;EACpB,OAAO,OAAO;EACd,aAAa,OAAO;EAIT,EACX,aAAa,OAAO,MAAM,OAAO,QAClC;CAED,OAAA,MAAM,QAAQ,YAAY;EAAE,IAAI;EAAS,GAAG;EAAgB,CAAC;CAC7D,OAAO;EAAE,OAAO,OAAO;EAAO,aAAa,OAAO;EAAa;;AA8FjE,SAAgB,iBACd,QACA,OAAyC,EAAE,EACP;CACpC,MAAM,EAAE,SAAS,OAAO,GAAG,SAAS;CAEpC,MAAM,cAAc,eAAe,MAAM;CAGzC,MAAM,oBACJ,OAAO,YAAY,cAClB,OAAO,YAAY,YAAY,YAAY,QAAQ,aAAa;CACnE,MAAM,oBAAoB,oBACtB,kBACE,WAAyB;CAC/B,MAAM,UAAUO,OAAAA,MAAM,QAAQ,kBAAkB;CAEhD,MAAM,WAAW,YAAY;EAC3B,MAAM,EAAE,WAAW,SAAS,OAAO,SAAS,WAAW,QAAQ,GAAG,iBAAiB;EAGnF,IAAI,mBAAmB;GACrB,MAAM,EAAE,SAAS,YAAY,SAAS,mBAAmB,MAAM,eAC7D,SACA,gBACD;GACD,OAAA,MAAM,QAAQ,YAAY;IAAE,IAAI;IAAS,GAAG;IAAgB,CAAC;;EAG/D,MAAM,SAAS,MAAM,cAA+B;GAClD;GACA;GACA;GACA;GACA;GACD,CAAC;EAEF,MAAM,aAAa;EAGnB,IAAI,YAAY,QACd,MAAMP,YAAAA,KAAK,YAAY,OAAO;EAGhC,IAAI,CAAC,OAAO,IAAI;GACd,MAAM,UAAoC;IACxC,OAAO,OAAO,MAAM;IACpB,OAAO,OAAO;IACf;GACD,MAAM,EAAE,SAAS,UAAU,SAAS,iBAAiB,MAAM,mBACzD,OACA,OAAO,OACP,SACA,gBACD;GACD,OAAA,MAAM,MAAM,UAAU;IAAE,IAAI;IAAS,GAAG;IAAc,GAAG;IAAc,CAAC;GACxE,MAAM,OAAO;;EAQf,MAAM,EAAE,SAAS,YAAY,SAAS,mBAAmB,MAAM,cAC7D,SACA;GANA,OAAO,OAAO,MAAM;GACpB,OAAO,OAAO;GACd,aAAa,OAAO;GAIT,EACX,aAAa,OAAO,MAAM,OAAO,QAClC;EAED,OAAA,MAAM,QAAQ,YAAY;GACxB,IAAI;GACJ,GAAG;GACH,GAAG;GACJ,CAAC;EACF,OAAO;GAAE,OAAO,OAAO;GAAO,aAAa,OAAO;GAAa;KAC7D;CAGJ,MAAM,MAAM;CACZ,IAAI,eAAe;CACnB,OAAO"}
|
package/dist/entry-sonner.d.cts
CHANGED
|
@@ -80,18 +80,43 @@ type ToastStreamData<TYield = unknown, TReturn = void> = StreamExternalToast & {
|
|
|
80
80
|
/**
|
|
81
81
|
* Like `toast.promise` but for iterables/generators/streams.
|
|
82
82
|
*
|
|
83
|
-
*
|
|
83
|
+
* Blocking/awaitable API: waits until the stream completes and resolves to
|
|
84
|
+
* `{ items, returnValue }`. Use this when the caller should wait for all
|
|
85
|
+
* streamed values before continuing.
|
|
86
|
+
*
|
|
87
|
+
* Handles: loading → streaming → success/error.
|
|
84
88
|
*
|
|
85
89
|
* Supports sync and async sources. Captures generator return values.
|
|
86
90
|
* Accepts either an instance or a factory function.
|
|
87
91
|
*
|
|
88
|
-
* @example
|
|
89
|
-
* ```
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
+
* @example Async generator with progress and return value
|
|
93
|
+
* ```ts
|
|
94
|
+
* import { toastStream } from "xantiagoma/sonner";
|
|
95
|
+
*
|
|
96
|
+
* async function* importUsers() {
|
|
97
|
+
* yield { id: 1, name: "Ada" };
|
|
98
|
+
* yield { id: 2, name: "Grace" };
|
|
99
|
+
* return { imported: 2 };
|
|
100
|
+
* }
|
|
101
|
+
*
|
|
102
|
+
* const { items, returnValue } = await toastStream(importUsers, {
|
|
103
|
+
* loading: "Importing users...",
|
|
104
|
+
* streaming: ({ count, latest }) => `Imported ${count}: ${latest.name}`,
|
|
105
|
+
* success: ({ count, returnValue }) =>
|
|
106
|
+
* `Imported ${count} users (${returnValue.imported} confirmed)`,
|
|
107
|
+
* error: (error, partial) =>
|
|
108
|
+
* `Import failed after ${partial.count} users: ${String(error)}`,
|
|
109
|
+
* });
|
|
110
|
+
* ```
|
|
92
111
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
112
|
+
* @example Sync iterable with artificial delay
|
|
113
|
+
* ```ts
|
|
114
|
+
* const result = await toastStream(["parse", "validate", "save"], {
|
|
115
|
+
* loading: "Starting...",
|
|
116
|
+
* streaming: ({ latest }) => `Running ${latest}`,
|
|
117
|
+
* success: ({ count }) => `Completed ${count} steps`,
|
|
118
|
+
* delay: { first: 300, items: 200, result: 150 },
|
|
119
|
+
* });
|
|
95
120
|
* ```
|
|
96
121
|
*/
|
|
97
122
|
declare function toastStream<TYield, TReturn>(source: AsyncGenerator<TYield, TReturn, unknown> | (() => AsyncGenerator<TYield, TReturn, unknown>), data?: ToastStreamData<TYield, TReturn>): Promise<StreamResult<TYield, TReturn>>;
|
|
@@ -107,6 +132,59 @@ declare function toastStream<TYield>(source: AsyncIterator<TYield> | (() => Asyn
|
|
|
107
132
|
declare function toastStream<TYield>(source: Iterable<TYield> | (() => Iterable<TYield>), data?: ToastStreamData<TYield, void>): Promise<StreamResult<TYield, void>>;
|
|
108
133
|
/** Sync Iterator (no return value) */
|
|
109
134
|
declare function toastStream<TYield>(source: Iterator<TYield> | (() => Iterator<TYield>), data?: ToastStreamData<TYield, void>): Promise<StreamResult<TYield, void>>;
|
|
135
|
+
/** Return type matching toast.promise's unwrap pattern */
|
|
136
|
+
type ToastStreamReturn<TYield, TReturn> = (string | number) & {
|
|
137
|
+
unwrap: () => Promise<StreamResult<TYield, TReturn>>;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Non-blocking version that returns immediately like `toast.promise`.
|
|
141
|
+
* Returns the toast ID with an `unwrap()` method to await the result later.
|
|
142
|
+
* Accepts either an instance or a factory function.
|
|
143
|
+
*
|
|
144
|
+
* Use this when the UI should continue immediately while the toast tracks the
|
|
145
|
+
* stream in the background.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* import { toastStreamAsync } from "xantiagoma/sonner";
|
|
150
|
+
*
|
|
151
|
+
* const toastId = toastStreamAsync(importUsers, {
|
|
152
|
+
* loading: "Importing users...",
|
|
153
|
+
* streaming: ({ count }) => `Imported ${count}`,
|
|
154
|
+
* success: ({ count }) => `Imported ${count} users`,
|
|
155
|
+
* });
|
|
156
|
+
*
|
|
157
|
+
* // The caller continues immediately; await only if/when the result matters.
|
|
158
|
+
* const { items, returnValue } = await toastId.unwrap();
|
|
159
|
+
* ```
|
|
160
|
+
*
|
|
161
|
+
* @example Fire-and-track with error handling
|
|
162
|
+
* ```ts
|
|
163
|
+
* const toastId = toastStreamAsync(uploadFiles(files), {
|
|
164
|
+
* loading: "Uploading...",
|
|
165
|
+
* streaming: ({ count }) => `${count} files uploaded`,
|
|
166
|
+
* error: (error, partial) =>
|
|
167
|
+
* `Upload failed after ${partial.count} files: ${String(error)}`,
|
|
168
|
+
* });
|
|
169
|
+
*
|
|
170
|
+
* toastId.unwrap().catch((error) => {
|
|
171
|
+
* reportError(error);
|
|
172
|
+
* });
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
declare function toastStreamAsync<TYield, TReturn>(source: AsyncGenerator<TYield, TReturn, unknown> | (() => AsyncGenerator<TYield, TReturn, unknown>), data?: ToastStreamData<TYield, TReturn>): ToastStreamReturn<TYield, TReturn>;
|
|
176
|
+
/** Sync Generator with return value */
|
|
177
|
+
declare function toastStreamAsync<TYield, TReturn>(source: Generator<TYield, TReturn, unknown> | (() => Generator<TYield, TReturn, unknown>), data?: ToastStreamData<TYield, TReturn>): ToastStreamReturn<TYield, TReturn>;
|
|
178
|
+
/** AsyncChannel with return type */
|
|
179
|
+
declare function toastStreamAsync<TYield, TReturn>(source: AsyncChannel<TYield, TReturn> | (() => AsyncChannel<TYield, TReturn>), data?: ToastStreamData<TYield, TReturn>): ToastStreamReturn<TYield, TReturn>;
|
|
180
|
+
/** AsyncIterable - excludes AsyncChannel which has its own overload */
|
|
181
|
+
declare function toastStreamAsync<TYield>(source: Exclude<AsyncIterable<TYield>, AsyncChannel<TYield, unknown>> | (() => Exclude<AsyncIterable<TYield>, AsyncChannel<TYield, unknown>>), data?: ToastStreamData<TYield, void>): ToastStreamReturn<TYield, void>;
|
|
182
|
+
/** AsyncIterator */
|
|
183
|
+
declare function toastStreamAsync<TYield>(source: AsyncIterator<TYield> | (() => AsyncIterator<TYield>), data?: ToastStreamData<TYield, void>): ToastStreamReturn<TYield, void>;
|
|
184
|
+
/** Sync Iterable */
|
|
185
|
+
declare function toastStreamAsync<TYield>(source: Iterable<TYield> | (() => Iterable<TYield>), data?: ToastStreamData<TYield, void>): ToastStreamReturn<TYield, void>;
|
|
186
|
+
/** Sync Iterator */
|
|
187
|
+
declare function toastStreamAsync<TYield>(source: Iterator<TYield> | (() => Iterator<TYield>), data?: ToastStreamData<TYield, void>): ToastStreamReturn<TYield, void>;
|
|
110
188
|
//#endregion
|
|
111
|
-
export { toastStream };
|
|
189
|
+
export { toastStream, toastStreamAsync };
|
|
112
190
|
//# sourceMappingURL=entry-sonner.d.cts.map
|
package/dist/entry-sonner.d.mts
CHANGED
|
@@ -80,18 +80,43 @@ type ToastStreamData<TYield = unknown, TReturn = void> = StreamExternalToast & {
|
|
|
80
80
|
/**
|
|
81
81
|
* Like `toast.promise` but for iterables/generators/streams.
|
|
82
82
|
*
|
|
83
|
-
*
|
|
83
|
+
* Blocking/awaitable API: waits until the stream completes and resolves to
|
|
84
|
+
* `{ items, returnValue }`. Use this when the caller should wait for all
|
|
85
|
+
* streamed values before continuing.
|
|
86
|
+
*
|
|
87
|
+
* Handles: loading → streaming → success/error.
|
|
84
88
|
*
|
|
85
89
|
* Supports sync and async sources. Captures generator return values.
|
|
86
90
|
* Accepts either an instance or a factory function.
|
|
87
91
|
*
|
|
88
|
-
* @example
|
|
89
|
-
* ```
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
+
* @example Async generator with progress and return value
|
|
93
|
+
* ```ts
|
|
94
|
+
* import { toastStream } from "xantiagoma/sonner";
|
|
95
|
+
*
|
|
96
|
+
* async function* importUsers() {
|
|
97
|
+
* yield { id: 1, name: "Ada" };
|
|
98
|
+
* yield { id: 2, name: "Grace" };
|
|
99
|
+
* return { imported: 2 };
|
|
100
|
+
* }
|
|
101
|
+
*
|
|
102
|
+
* const { items, returnValue } = await toastStream(importUsers, {
|
|
103
|
+
* loading: "Importing users...",
|
|
104
|
+
* streaming: ({ count, latest }) => `Imported ${count}: ${latest.name}`,
|
|
105
|
+
* success: ({ count, returnValue }) =>
|
|
106
|
+
* `Imported ${count} users (${returnValue.imported} confirmed)`,
|
|
107
|
+
* error: (error, partial) =>
|
|
108
|
+
* `Import failed after ${partial.count} users: ${String(error)}`,
|
|
109
|
+
* });
|
|
110
|
+
* ```
|
|
92
111
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
112
|
+
* @example Sync iterable with artificial delay
|
|
113
|
+
* ```ts
|
|
114
|
+
* const result = await toastStream(["parse", "validate", "save"], {
|
|
115
|
+
* loading: "Starting...",
|
|
116
|
+
* streaming: ({ latest }) => `Running ${latest}`,
|
|
117
|
+
* success: ({ count }) => `Completed ${count} steps`,
|
|
118
|
+
* delay: { first: 300, items: 200, result: 150 },
|
|
119
|
+
* });
|
|
95
120
|
* ```
|
|
96
121
|
*/
|
|
97
122
|
declare function toastStream<TYield, TReturn>(source: AsyncGenerator<TYield, TReturn, unknown> | (() => AsyncGenerator<TYield, TReturn, unknown>), data?: ToastStreamData<TYield, TReturn>): Promise<StreamResult<TYield, TReturn>>;
|
|
@@ -107,6 +132,59 @@ declare function toastStream<TYield>(source: AsyncIterator<TYield> | (() => Asyn
|
|
|
107
132
|
declare function toastStream<TYield>(source: Iterable<TYield> | (() => Iterable<TYield>), data?: ToastStreamData<TYield, void>): Promise<StreamResult<TYield, void>>;
|
|
108
133
|
/** Sync Iterator (no return value) */
|
|
109
134
|
declare function toastStream<TYield>(source: Iterator<TYield> | (() => Iterator<TYield>), data?: ToastStreamData<TYield, void>): Promise<StreamResult<TYield, void>>;
|
|
135
|
+
/** Return type matching toast.promise's unwrap pattern */
|
|
136
|
+
type ToastStreamReturn<TYield, TReturn> = (string | number) & {
|
|
137
|
+
unwrap: () => Promise<StreamResult<TYield, TReturn>>;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* Non-blocking version that returns immediately like `toast.promise`.
|
|
141
|
+
* Returns the toast ID with an `unwrap()` method to await the result later.
|
|
142
|
+
* Accepts either an instance or a factory function.
|
|
143
|
+
*
|
|
144
|
+
* Use this when the UI should continue immediately while the toast tracks the
|
|
145
|
+
* stream in the background.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* import { toastStreamAsync } from "xantiagoma/sonner";
|
|
150
|
+
*
|
|
151
|
+
* const toastId = toastStreamAsync(importUsers, {
|
|
152
|
+
* loading: "Importing users...",
|
|
153
|
+
* streaming: ({ count }) => `Imported ${count}`,
|
|
154
|
+
* success: ({ count }) => `Imported ${count} users`,
|
|
155
|
+
* });
|
|
156
|
+
*
|
|
157
|
+
* // The caller continues immediately; await only if/when the result matters.
|
|
158
|
+
* const { items, returnValue } = await toastId.unwrap();
|
|
159
|
+
* ```
|
|
160
|
+
*
|
|
161
|
+
* @example Fire-and-track with error handling
|
|
162
|
+
* ```ts
|
|
163
|
+
* const toastId = toastStreamAsync(uploadFiles(files), {
|
|
164
|
+
* loading: "Uploading...",
|
|
165
|
+
* streaming: ({ count }) => `${count} files uploaded`,
|
|
166
|
+
* error: (error, partial) =>
|
|
167
|
+
* `Upload failed after ${partial.count} files: ${String(error)}`,
|
|
168
|
+
* });
|
|
169
|
+
*
|
|
170
|
+
* toastId.unwrap().catch((error) => {
|
|
171
|
+
* reportError(error);
|
|
172
|
+
* });
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
declare function toastStreamAsync<TYield, TReturn>(source: AsyncGenerator<TYield, TReturn, unknown> | (() => AsyncGenerator<TYield, TReturn, unknown>), data?: ToastStreamData<TYield, TReturn>): ToastStreamReturn<TYield, TReturn>;
|
|
176
|
+
/** Sync Generator with return value */
|
|
177
|
+
declare function toastStreamAsync<TYield, TReturn>(source: Generator<TYield, TReturn, unknown> | (() => Generator<TYield, TReturn, unknown>), data?: ToastStreamData<TYield, TReturn>): ToastStreamReturn<TYield, TReturn>;
|
|
178
|
+
/** AsyncChannel with return type */
|
|
179
|
+
declare function toastStreamAsync<TYield, TReturn>(source: AsyncChannel<TYield, TReturn> | (() => AsyncChannel<TYield, TReturn>), data?: ToastStreamData<TYield, TReturn>): ToastStreamReturn<TYield, TReturn>;
|
|
180
|
+
/** AsyncIterable - excludes AsyncChannel which has its own overload */
|
|
181
|
+
declare function toastStreamAsync<TYield>(source: Exclude<AsyncIterable<TYield>, AsyncChannel<TYield, unknown>> | (() => Exclude<AsyncIterable<TYield>, AsyncChannel<TYield, unknown>>), data?: ToastStreamData<TYield, void>): ToastStreamReturn<TYield, void>;
|
|
182
|
+
/** AsyncIterator */
|
|
183
|
+
declare function toastStreamAsync<TYield>(source: AsyncIterator<TYield> | (() => AsyncIterator<TYield>), data?: ToastStreamData<TYield, void>): ToastStreamReturn<TYield, void>;
|
|
184
|
+
/** Sync Iterable */
|
|
185
|
+
declare function toastStreamAsync<TYield>(source: Iterable<TYield> | (() => Iterable<TYield>), data?: ToastStreamData<TYield, void>): ToastStreamReturn<TYield, void>;
|
|
186
|
+
/** Sync Iterator */
|
|
187
|
+
declare function toastStreamAsync<TYield>(source: Iterator<TYield> | (() => Iterator<TYield>), data?: ToastStreamData<TYield, void>): ToastStreamReturn<TYield, void>;
|
|
110
188
|
//#endregion
|
|
111
|
-
export { toastStream };
|
|
189
|
+
export { toastStream, toastStreamAsync };
|
|
112
190
|
//# sourceMappingURL=entry-sonner.d.mts.map
|
package/dist/entry-sonner.mjs
CHANGED
|
@@ -202,7 +202,63 @@ async function toastStream(source, data = {}) {
|
|
|
202
202
|
returnValue: result.returnValue
|
|
203
203
|
};
|
|
204
204
|
}
|
|
205
|
+
function toastStreamAsync(source, data = {}) {
|
|
206
|
+
const { loading, delay, ...rest } = data;
|
|
207
|
+
const delayConfig = normalizeDelay(delay);
|
|
208
|
+
const needsAsyncResolve = typeof loading === "function" || typeof loading === "object" && loading !== null && "message" in loading;
|
|
209
|
+
const initialLoadingMsg = needsAsyncResolve ? "Processing..." : loading ?? "Processing...";
|
|
210
|
+
const toastId = toast.loading(initialLoadingMsg);
|
|
211
|
+
const promise = (async () => {
|
|
212
|
+
const { streaming, success, error, finally: onFinally, onItem, ...toastOptions } = rest;
|
|
213
|
+
if (needsAsyncResolve) {
|
|
214
|
+
const { message: loadingMsg, options: loadingOptions } = await resolveLoading(loading, "Processing...");
|
|
215
|
+
toast.loading(loadingMsg, {
|
|
216
|
+
id: toastId,
|
|
217
|
+
...loadingOptions
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
const result = await consumeStream({
|
|
221
|
+
source,
|
|
222
|
+
toastId,
|
|
223
|
+
streaming,
|
|
224
|
+
onItem,
|
|
225
|
+
delayConfig
|
|
226
|
+
});
|
|
227
|
+
await onFinally?.();
|
|
228
|
+
if (delayConfig.result) await wait(delayConfig.result);
|
|
229
|
+
if (!result.ok) {
|
|
230
|
+
const partial = {
|
|
231
|
+
count: result.items.length,
|
|
232
|
+
items: result.items
|
|
233
|
+
};
|
|
234
|
+
const { message: errorMsg, options: errorOptions } = await resolveErrorResult(error, result.error, partial, "Stream failed");
|
|
235
|
+
toast.error(errorMsg, {
|
|
236
|
+
id: toastId,
|
|
237
|
+
...toastOptions,
|
|
238
|
+
...errorOptions
|
|
239
|
+
});
|
|
240
|
+
throw result.error;
|
|
241
|
+
}
|
|
242
|
+
const { message: successMsg, options: successOptions } = await resolveResult(success, {
|
|
243
|
+
count: result.items.length,
|
|
244
|
+
items: result.items,
|
|
245
|
+
returnValue: result.returnValue
|
|
246
|
+
}, `Complete! ${result.items.length} items`);
|
|
247
|
+
toast.success(successMsg, {
|
|
248
|
+
id: toastId,
|
|
249
|
+
...toastOptions,
|
|
250
|
+
...successOptions
|
|
251
|
+
});
|
|
252
|
+
return {
|
|
253
|
+
items: result.items,
|
|
254
|
+
returnValue: result.returnValue
|
|
255
|
+
};
|
|
256
|
+
})();
|
|
257
|
+
const res = toastId;
|
|
258
|
+
res.unwrap = () => promise;
|
|
259
|
+
return res;
|
|
260
|
+
}
|
|
205
261
|
//#endregion
|
|
206
|
-
export { toastStream };
|
|
262
|
+
export { toastStream, toastStreamAsync };
|
|
207
263
|
|
|
208
264
|
//# sourceMappingURL=entry-sonner.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry-sonner.mjs","names":[],"sources":["../src/toast-stream.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\n\nimport {\n AsyncChannel,\n isAsyncGenerator,\n isAsyncIterable,\n isGenerator,\n isIterable,\n resolveStreamSource,\n type StreamSource,\n toAsyncIterable,\n wait,\n} from \"./index.ts\";\nimport { type ExternalToast, toast } from \"sonner\";\n\n// Types matching Sonner's patterns\ntype StreamExternalToast = Omit<ExternalToast, \"description\">;\n\ninterface StreamExtendedResult extends ExternalToast {\n message: ReactNode;\n}\n\n/** Result can be string, ReactNode, or callback returning either (sync or async) */\ntype StreamResultT<TData = unknown> =\n | string\n | ReactNode\n | ((data: TData) => ReactNode | string | Promise<ReactNode | string>);\n\n/** Extended result includes full toast options */\ntype StreamExtendedResultT<TData = unknown> =\n | StreamExtendedResult\n | ((data: TData) => StreamExtendedResult | Promise<StreamExtendedResult>);\n\n/** Error result - callback receives error first, then partial data */\ntype StreamErrorResultT<TYield> =\n | string\n | ReactNode\n | ((\n error: unknown,\n partial: ErrorPartialData<TYield>,\n ) => ReactNode | string | Promise<ReactNode | string>);\n\n/** Extended error result with toast options */\ntype StreamExtendedErrorResultT<TYield> =\n | StreamExtendedResult\n | ((\n error: unknown,\n partial: ErrorPartialData<TYield>,\n ) => StreamExtendedResult | Promise<StreamExtendedResult>);\n\n/** Streaming progress data passed to callbacks */\ntype StreamingData<T> = {\n count: number;\n latest: T;\n /** All items received so far (including latest) */\n items: T[];\n};\n\n/** Success data passed to callbacks - includes return value if generator has one */\ntype SuccessData<TYield, TReturn = void> = {\n count: number;\n items: TYield[];\n /** The return value from the generator (if any) */\n returnValue: TReturn;\n};\n\n/** Partial data available when an error occurs */\ntype ErrorPartialData<TYield> = {\n /** Number of items received before error */\n count: number;\n /** Items received before error */\n items: TYield[];\n};\n\n/** Result of consuming a stream */\ntype StreamResult<TYield, TReturn> = {\n items: TYield[];\n returnValue: TReturn;\n};\n\n/** Internal result from consumeStream - includes partial data on error */\ntype ConsumeResult<TYield, TReturn> =\n | { ok: true; items: TYield[]; returnValue: TReturn }\n | { ok: false; items: TYield[]; error: unknown };\n\n/** Granular delay configuration */\ntype DelayConfig = {\n /** Delay between items in ms */\n items?: number;\n /** Delay after loading, before first item */\n first?: number;\n /** Delay before showing success/error */\n result?: number;\n};\n\n/** Loading result - no data, just static or callback */\ntype LoadingResultT = string | ReactNode | (() => ReactNode | string | Promise<ReactNode | string>);\n\n/** Extended loading result with toast options */\ninterface LoadingExtendedResult extends ExternalToast {\n message: ReactNode;\n}\n\ntype LoadingExtendedResultT =\n | LoadingExtendedResult\n | (() => LoadingExtendedResult | Promise<LoadingExtendedResult>);\n\n/** Options matching Sonner's PromiseData pattern */\ntype ToastStreamData<TYield = unknown, TReturn = void> = StreamExternalToast & {\n /**\n * Message shown while waiting for first item.\n * Supports string, ReactNode, callback, or extended result with toast options.\n */\n loading?: LoadingResultT | LoadingExtendedResultT;\n /** Message shown while streaming - updates on each item */\n streaming?: StreamResultT<StreamingData<TYield>> | StreamExtendedResultT<StreamingData<TYield>>;\n /** Message shown on successful completion */\n success?:\n | StreamResultT<SuccessData<TYield, TReturn>>\n | StreamExtendedResultT<SuccessData<TYield, TReturn>>;\n /** Message shown on error - callback receives (error, { count, items }) */\n error?: StreamErrorResultT<TYield> | StreamExtendedErrorResultT<TYield>;\n /** Called when stream ends (success or error) */\n finally?: () => void | Promise<void>;\n /** Called for each item received */\n onItem?: (item: TYield, count: number) => void;\n /**\n * Artificial delay for visualizing sync streams.\n * - `number`: applies to all phases (first, items, result)\n * - `object`: granular control over individual phases\n *\n * @example\n * // Simple: 500ms for all phases (first, between items, before result)\n * delay: 500\n *\n * @example\n * // Granular: different delays for different phases\n * delay: { first: 500, items: 300, result: 200 }\n */\n delay?: number | DelayConfig;\n};\n\n/** Resolve a result that can be string, ReactNode, or callback */\nasync function resolveResult<TData>(\n result: StreamResultT<TData> | StreamExtendedResultT<TData> | undefined,\n data: TData,\n fallback: string,\n): Promise<{ message: ReactNode; options?: ExternalToast }> {\n if (result === undefined) {\n return { message: fallback };\n }\n\n if (typeof result === \"function\") {\n const resolved = await result(data);\n if (typeof resolved === \"object\" && resolved !== null && \"message\" in resolved) {\n const { message, ...options } = resolved as StreamExtendedResult;\n return { message, options };\n }\n return { message: resolved as ReactNode };\n }\n\n if (typeof result === \"object\" && result !== null && \"message\" in result) {\n const { message, ...options } = result as StreamExtendedResult;\n return { message, options };\n }\n\n return { message: result };\n}\n\n/** Resolve an error result that receives (error, partialData) */\nasync function resolveErrorResult<TYield>(\n result: StreamErrorResultT<TYield> | StreamExtendedErrorResultT<TYield> | undefined,\n error: unknown,\n partial: ErrorPartialData<TYield>,\n fallback: string,\n): Promise<{ message: ReactNode; options?: ExternalToast }> {\n if (result === undefined) {\n return { message: fallback };\n }\n\n if (typeof result === \"function\") {\n const resolved = await result(error, partial);\n if (typeof resolved === \"object\" && resolved !== null && \"message\" in resolved) {\n const { message, ...options } = resolved as StreamExtendedResult;\n return { message, options };\n }\n return { message: resolved as ReactNode };\n }\n\n if (typeof result === \"object\" && result !== null && \"message\" in result) {\n const { message, ...options } = result as StreamExtendedResult;\n return { message, options };\n }\n\n return { message: result };\n}\n\n/** Resolve loading result - no data passed to callback */\nasync function resolveLoading(\n result: LoadingResultT | LoadingExtendedResultT | undefined,\n fallback: string,\n): Promise<{ message: ReactNode; options?: ExternalToast }> {\n if (result === undefined) {\n return { message: fallback };\n }\n\n if (typeof result === \"function\") {\n const resolved = await result();\n if (typeof resolved === \"object\" && resolved !== null && \"message\" in resolved) {\n const { message, ...options } = resolved as LoadingExtendedResult;\n return { message, options };\n }\n return { message: resolved as ReactNode };\n }\n\n if (typeof result === \"object\" && result !== null && \"message\" in result) {\n const { message, ...options } = result as LoadingExtendedResult;\n return { message, options };\n }\n\n return { message: result };\n}\n\n/** Normalize delay option to full DelayConfig */\nfunction normalizeDelay(delay: number | DelayConfig | undefined): DelayConfig {\n if (delay === undefined) {\n return {};\n }\n if (typeof delay === \"number\") {\n // Simple number applies to all phases: first, items, and result\n return { first: delay, items: delay, result: delay };\n }\n return delay;\n}\n\n/** Options for consumeStream */\ntype ConsumeStreamOptions<TYield, TReturn> = {\n source: StreamSource<TYield>;\n toastId: string | number;\n streaming: ToastStreamData<TYield, TReturn>[\"streaming\"];\n onItem: ToastStreamData<TYield, TReturn>[\"onItem\"];\n delayConfig: DelayConfig;\n};\n\n/** Core streaming logic - consumes source and updates toast, captures return value */\nasync function consumeStream<TYield, TReturn>(\n options: ConsumeStreamOptions<TYield, TReturn>,\n): Promise<ConsumeResult<TYield, TReturn>> {\n const { source: sourceOrFactory, toastId, streaming, onItem, delayConfig } = options;\n // Resolve factory function if needed\n const source = resolveStreamSource(sourceOrFactory);\n\n const items: TYield[] = [];\n let count = 0;\n let isFirst = true;\n\n const updateToast = async (item: TYield) => {\n // Delay before first item (after loading)\n if (isFirst && delayConfig.first) {\n await wait(delayConfig.first);\n isFirst = false;\n } else if (!isFirst && delayConfig.items) {\n // Delay between items (not before first)\n await wait(delayConfig.items);\n } else {\n isFirst = false;\n }\n\n count += 1;\n items.push(item);\n onItem?.(item, count);\n\n const streamingData: StreamingData<TYield> = {\n count,\n latest: item,\n items: [...items],\n };\n const { message: streamingMsg, options: streamingOptions } = await resolveResult(\n streaming,\n streamingData,\n `Streaming... (${count} items)`,\n );\n toast.loading(streamingMsg, { id: toastId, ...streamingOptions });\n };\n\n try {\n // Handle sync Generator specially to capture return value\n if (isGenerator<TYield, TReturn>(source)) {\n let result = source.next();\n while (!result.done) {\n await updateToast(result.value);\n result = source.next();\n }\n return { ok: true, items, returnValue: result.value };\n }\n\n // Handle AsyncGenerator specially to capture return value\n if (isAsyncGenerator<TYield, TReturn>(source)) {\n let result = await source.next();\n while (!result.done) {\n await updateToast(result.value);\n result = await source.next();\n }\n return { ok: true, items, returnValue: result.value };\n }\n\n // Handle AsyncChannel specially to capture return value\n if (source instanceof AsyncChannel) {\n const iterator = source[Symbol.asyncIterator]();\n let result = await iterator.next();\n while (!result.done) {\n await updateToast(result.value);\n result = await iterator.next();\n }\n return { ok: true, items, returnValue: result.value };\n }\n\n // For plain iterables/iterators, convert and iterate (no return value)\n const iterable =\n isAsyncIterable(source) || isIterable(source)\n ? toAsyncIterable(source)\n : toAsyncIterable(source as AsyncIterator<TYield>);\n\n for await (const item of iterable) {\n await updateToast(item);\n }\n\n return { ok: true, items, returnValue: undefined as TReturn };\n } catch (error) {\n return { ok: false, items, error };\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// toastStream overloads\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Like `toast.promise` but for iterables/generators/streams.\n *\n * Handles: loading → streaming → success/error\n *\n * Supports sync and async sources. Captures generator return values.\n * Accepts either an instance or a factory function.\n *\n * @example\n * ```tsx\n * // Pass generator function directly (recommended)\n * await toastStream(fetchData, { ... });\n *\n * // Or pass the result of calling it\n * await toastStream(fetchData(), { ... });\n * ```\n */\nexport function toastStream<TYield, TReturn>(\n source:\n | AsyncGenerator<TYield, TReturn, unknown>\n | (() => AsyncGenerator<TYield, TReturn, unknown>),\n data?: ToastStreamData<TYield, TReturn>,\n): Promise<StreamResult<TYield, TReturn>>;\n\n/** Sync Generator with return value */\nexport function toastStream<TYield, TReturn>(\n source: Generator<TYield, TReturn, unknown> | (() => Generator<TYield, TReturn, unknown>),\n data?: ToastStreamData<TYield, TReturn>,\n): Promise<StreamResult<TYield, TReturn>>;\n\n/** AsyncChannel with return type - must come before AsyncIterable overload */\nexport function toastStream<TYield, TReturn>(\n source: AsyncChannel<TYield, TReturn> | (() => AsyncChannel<TYield, TReturn>),\n data?: ToastStreamData<TYield, TReturn>,\n): Promise<StreamResult<TYield, TReturn>>;\n\n/** AsyncIterable (no return value) */\nexport function toastStream<TYield>(\n source: AsyncIterable<TYield> | (() => AsyncIterable<TYield>),\n data?: ToastStreamData<TYield, void>,\n): Promise<StreamResult<TYield, void>>;\n\n/** AsyncIterator (no return value) */\nexport function toastStream<TYield>(\n source: AsyncIterator<TYield> | (() => AsyncIterator<TYield>),\n data?: ToastStreamData<TYield, void>,\n): Promise<StreamResult<TYield, void>>;\n\n/** Sync Iterable (no return value) */\nexport function toastStream<TYield>(\n source: Iterable<TYield> | (() => Iterable<TYield>),\n data?: ToastStreamData<TYield, void>,\n): Promise<StreamResult<TYield, void>>;\n\n/** Sync Iterator (no return value) */\nexport function toastStream<TYield>(\n source: Iterator<TYield> | (() => Iterator<TYield>),\n data?: ToastStreamData<TYield, void>,\n): Promise<StreamResult<TYield, void>>;\n\n// Implementation\nexport async function toastStream<TYield, TReturn = void>(\n source: StreamSource<TYield>,\n data: ToastStreamData<TYield, TReturn> = {},\n): Promise<StreamResult<TYield, TReturn>> {\n const {\n loading,\n streaming,\n success,\n error,\n finally: onFinally,\n onItem,\n delay,\n ...toastOptions\n } = data;\n\n const delayConfig = normalizeDelay(delay);\n const { message: loadingMsg, options: loadingOptions } = await resolveLoading(\n loading,\n \"Processing...\",\n );\n const toastId = toast.loading(loadingMsg, {\n ...toastOptions,\n ...loadingOptions,\n });\n\n const result = await consumeStream<TYield, TReturn>({\n source,\n toastId,\n streaming,\n onItem,\n delayConfig,\n });\n\n await onFinally?.();\n\n // Delay before showing result\n if (delayConfig.result) {\n await wait(delayConfig.result);\n }\n\n if (!result.ok) {\n const partial: ErrorPartialData<TYield> = {\n count: result.items.length,\n items: result.items,\n };\n const { message: errorMsg, options: errorOptions } = await resolveErrorResult(\n error,\n result.error,\n partial,\n \"Stream failed\",\n );\n toast.error(errorMsg, { id: toastId, ...errorOptions });\n throw result.error;\n }\n\n const successData: SuccessData<TYield, TReturn> = {\n count: result.items.length,\n items: result.items,\n returnValue: result.returnValue,\n };\n const { message: successMsg, options: successOptions } = await resolveResult(\n success,\n successData,\n `Complete! ${result.items.length} items`,\n );\n\n toast.success(successMsg, { id: toastId, ...successOptions });\n return { items: result.items, returnValue: result.returnValue };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// toastStreamAsync overloads\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Return type matching toast.promise's unwrap pattern */\ntype ToastStreamReturn<TYield, TReturn> = (string | number) & {\n unwrap: () => Promise<StreamResult<TYield, TReturn>>;\n};\n\n/**\n * Non-blocking version that returns immediately like `toast.promise`.\n * Returns toast ID with `unwrap()` method to await the result.\n * Accepts either an instance or a factory function.\n *\n * @example\n * ```ts\n * const { unwrap } = toastStreamAsync(fetchData, { loading: \"...\" });\n * const { items, returnValue } = await unwrap();\n * ```\n */\nexport function toastStreamAsync<TYield, TReturn>(\n source:\n | AsyncGenerator<TYield, TReturn, unknown>\n | (() => AsyncGenerator<TYield, TReturn, unknown>),\n data?: ToastStreamData<TYield, TReturn>,\n): ToastStreamReturn<TYield, TReturn>;\n\n/** Sync Generator with return value */\nexport function toastStreamAsync<TYield, TReturn>(\n source: Generator<TYield, TReturn, unknown> | (() => Generator<TYield, TReturn, unknown>),\n data?: ToastStreamData<TYield, TReturn>,\n): ToastStreamReturn<TYield, TReturn>;\n\n/** AsyncChannel with return type */\nexport function toastStreamAsync<TYield, TReturn>(\n source: AsyncChannel<TYield, TReturn> | (() => AsyncChannel<TYield, TReturn>),\n data?: ToastStreamData<TYield, TReturn>,\n): ToastStreamReturn<TYield, TReturn>;\n\n/** AsyncIterable - excludes AsyncChannel which has its own overload */\nexport function toastStreamAsync<TYield>(\n source:\n | Exclude<AsyncIterable<TYield>, AsyncChannel<TYield, unknown>>\n | (() => Exclude<AsyncIterable<TYield>, AsyncChannel<TYield, unknown>>),\n data?: ToastStreamData<TYield, void>,\n): ToastStreamReturn<TYield, void>;\n\n/** AsyncIterator */\nexport function toastStreamAsync<TYield>(\n source: AsyncIterator<TYield> | (() => AsyncIterator<TYield>),\n data?: ToastStreamData<TYield, void>,\n): ToastStreamReturn<TYield, void>;\n\n/** Sync Iterable */\nexport function toastStreamAsync<TYield>(\n source: Iterable<TYield> | (() => Iterable<TYield>),\n data?: ToastStreamData<TYield, void>,\n): ToastStreamReturn<TYield, void>;\n\n/** Sync Iterator */\nexport function toastStreamAsync<TYield>(\n source: Iterator<TYield> | (() => Iterator<TYield>),\n data?: ToastStreamData<TYield, void>,\n): ToastStreamReturn<TYield, void>;\n\n// Implementation\nexport function toastStreamAsync<TYield, TReturn = void>(\n source: StreamSource<TYield>,\n data: ToastStreamData<TYield, TReturn> = {},\n): ToastStreamReturn<TYield, TReturn> {\n const { loading, delay, ...rest } = data;\n\n const delayConfig = normalizeDelay(delay);\n // For non-blocking version, show simple loading immediately\n // Callbacks and extended results are resolved inside the promise\n const needsAsyncResolve =\n typeof loading === \"function\" ||\n (typeof loading === \"object\" && loading !== null && \"message\" in loading);\n const initialLoadingMsg = needsAsyncResolve\n ? \"Processing...\"\n : ((loading as ReactNode) ?? \"Processing...\");\n const toastId = toast.loading(initialLoadingMsg);\n\n const promise = (async () => {\n const { streaming, success, error, finally: onFinally, onItem, ...toastOptions } = rest;\n\n // If loading needs async resolution (callback or extended result), resolve and update\n if (needsAsyncResolve) {\n const { message: loadingMsg, options: loadingOptions } = await resolveLoading(\n loading,\n \"Processing...\",\n );\n toast.loading(loadingMsg, { id: toastId, ...loadingOptions });\n }\n\n const result = await consumeStream<TYield, TReturn>({\n source,\n toastId,\n streaming,\n onItem,\n delayConfig,\n });\n\n await onFinally?.();\n\n // Delay before showing result\n if (delayConfig.result) {\n await wait(delayConfig.result);\n }\n\n if (!result.ok) {\n const partial: ErrorPartialData<TYield> = {\n count: result.items.length,\n items: result.items,\n };\n const { message: errorMsg, options: errorOptions } = await resolveErrorResult(\n error,\n result.error,\n partial,\n \"Stream failed\",\n );\n toast.error(errorMsg, { id: toastId, ...toastOptions, ...errorOptions });\n throw result.error;\n }\n\n const successData: SuccessData<TYield, TReturn> = {\n count: result.items.length,\n items: result.items,\n returnValue: result.returnValue,\n };\n const { message: successMsg, options: successOptions } = await resolveResult(\n success,\n successData,\n `Complete! ${result.items.length} items`,\n );\n\n toast.success(successMsg, {\n id: toastId,\n ...toastOptions,\n ...successOptions,\n });\n return { items: result.items, returnValue: result.returnValue };\n })();\n\n // Match toast.promise's return signature\n const res = toastId as ToastStreamReturn<TYield, TReturn>;\n res.unwrap = () => promise;\n return res;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"entry-sonner.mjs","names":[],"sources":["../src/toast-stream.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\n\nimport {\n AsyncChannel,\n isAsyncGenerator,\n isAsyncIterable,\n isGenerator,\n isIterable,\n resolveStreamSource,\n type StreamSource,\n toAsyncIterable,\n wait,\n} from \"./index.ts\";\nimport { type ExternalToast, toast } from \"sonner\";\n\n// Types matching Sonner's patterns\ntype StreamExternalToast = Omit<ExternalToast, \"description\">;\n\ninterface StreamExtendedResult extends ExternalToast {\n message: ReactNode;\n}\n\n/** Result can be string, ReactNode, or callback returning either (sync or async) */\ntype StreamResultT<TData = unknown> =\n | string\n | ReactNode\n | ((data: TData) => ReactNode | string | Promise<ReactNode | string>);\n\n/** Extended result includes full toast options */\ntype StreamExtendedResultT<TData = unknown> =\n | StreamExtendedResult\n | ((data: TData) => StreamExtendedResult | Promise<StreamExtendedResult>);\n\n/** Error result - callback receives error first, then partial data */\ntype StreamErrorResultT<TYield> =\n | string\n | ReactNode\n | ((\n error: unknown,\n partial: ErrorPartialData<TYield>,\n ) => ReactNode | string | Promise<ReactNode | string>);\n\n/** Extended error result with toast options */\ntype StreamExtendedErrorResultT<TYield> =\n | StreamExtendedResult\n | ((\n error: unknown,\n partial: ErrorPartialData<TYield>,\n ) => StreamExtendedResult | Promise<StreamExtendedResult>);\n\n/** Streaming progress data passed to callbacks */\ntype StreamingData<T> = {\n count: number;\n latest: T;\n /** All items received so far (including latest) */\n items: T[];\n};\n\n/** Success data passed to callbacks - includes return value if generator has one */\ntype SuccessData<TYield, TReturn = void> = {\n count: number;\n items: TYield[];\n /** The return value from the generator (if any) */\n returnValue: TReturn;\n};\n\n/** Partial data available when an error occurs */\ntype ErrorPartialData<TYield> = {\n /** Number of items received before error */\n count: number;\n /** Items received before error */\n items: TYield[];\n};\n\n/** Result of consuming a stream */\ntype StreamResult<TYield, TReturn> = {\n items: TYield[];\n returnValue: TReturn;\n};\n\n/** Internal result from consumeStream - includes partial data on error */\ntype ConsumeResult<TYield, TReturn> =\n | { ok: true; items: TYield[]; returnValue: TReturn }\n | { ok: false; items: TYield[]; error: unknown };\n\n/** Granular delay configuration */\ntype DelayConfig = {\n /** Delay between items in ms */\n items?: number;\n /** Delay after loading, before first item */\n first?: number;\n /** Delay before showing success/error */\n result?: number;\n};\n\n/** Loading result - no data, just static or callback */\ntype LoadingResultT = string | ReactNode | (() => ReactNode | string | Promise<ReactNode | string>);\n\n/** Extended loading result with toast options */\ninterface LoadingExtendedResult extends ExternalToast {\n message: ReactNode;\n}\n\ntype LoadingExtendedResultT =\n | LoadingExtendedResult\n | (() => LoadingExtendedResult | Promise<LoadingExtendedResult>);\n\n/** Options matching Sonner's PromiseData pattern */\ntype ToastStreamData<TYield = unknown, TReturn = void> = StreamExternalToast & {\n /**\n * Message shown while waiting for first item.\n * Supports string, ReactNode, callback, or extended result with toast options.\n */\n loading?: LoadingResultT | LoadingExtendedResultT;\n /** Message shown while streaming - updates on each item */\n streaming?: StreamResultT<StreamingData<TYield>> | StreamExtendedResultT<StreamingData<TYield>>;\n /** Message shown on successful completion */\n success?:\n | StreamResultT<SuccessData<TYield, TReturn>>\n | StreamExtendedResultT<SuccessData<TYield, TReturn>>;\n /** Message shown on error - callback receives (error, { count, items }) */\n error?: StreamErrorResultT<TYield> | StreamExtendedErrorResultT<TYield>;\n /** Called when stream ends (success or error) */\n finally?: () => void | Promise<void>;\n /** Called for each item received */\n onItem?: (item: TYield, count: number) => void;\n /**\n * Artificial delay for visualizing sync streams.\n * - `number`: applies to all phases (first, items, result)\n * - `object`: granular control over individual phases\n *\n * @example\n * // Simple: 500ms for all phases (first, between items, before result)\n * delay: 500\n *\n * @example\n * // Granular: different delays for different phases\n * delay: { first: 500, items: 300, result: 200 }\n */\n delay?: number | DelayConfig;\n};\n\n/** Resolve a result that can be string, ReactNode, or callback */\nasync function resolveResult<TData>(\n result: StreamResultT<TData> | StreamExtendedResultT<TData> | undefined,\n data: TData,\n fallback: string,\n): Promise<{ message: ReactNode; options?: ExternalToast }> {\n if (result === undefined) {\n return { message: fallback };\n }\n\n if (typeof result === \"function\") {\n const resolved = await result(data);\n if (typeof resolved === \"object\" && resolved !== null && \"message\" in resolved) {\n const { message, ...options } = resolved as StreamExtendedResult;\n return { message, options };\n }\n return { message: resolved as ReactNode };\n }\n\n if (typeof result === \"object\" && result !== null && \"message\" in result) {\n const { message, ...options } = result as StreamExtendedResult;\n return { message, options };\n }\n\n return { message: result };\n}\n\n/** Resolve an error result that receives (error, partialData) */\nasync function resolveErrorResult<TYield>(\n result: StreamErrorResultT<TYield> | StreamExtendedErrorResultT<TYield> | undefined,\n error: unknown,\n partial: ErrorPartialData<TYield>,\n fallback: string,\n): Promise<{ message: ReactNode; options?: ExternalToast }> {\n if (result === undefined) {\n return { message: fallback };\n }\n\n if (typeof result === \"function\") {\n const resolved = await result(error, partial);\n if (typeof resolved === \"object\" && resolved !== null && \"message\" in resolved) {\n const { message, ...options } = resolved as StreamExtendedResult;\n return { message, options };\n }\n return { message: resolved as ReactNode };\n }\n\n if (typeof result === \"object\" && result !== null && \"message\" in result) {\n const { message, ...options } = result as StreamExtendedResult;\n return { message, options };\n }\n\n return { message: result };\n}\n\n/** Resolve loading result - no data passed to callback */\nasync function resolveLoading(\n result: LoadingResultT | LoadingExtendedResultT | undefined,\n fallback: string,\n): Promise<{ message: ReactNode; options?: ExternalToast }> {\n if (result === undefined) {\n return { message: fallback };\n }\n\n if (typeof result === \"function\") {\n const resolved = await result();\n if (typeof resolved === \"object\" && resolved !== null && \"message\" in resolved) {\n const { message, ...options } = resolved as LoadingExtendedResult;\n return { message, options };\n }\n return { message: resolved as ReactNode };\n }\n\n if (typeof result === \"object\" && result !== null && \"message\" in result) {\n const { message, ...options } = result as LoadingExtendedResult;\n return { message, options };\n }\n\n return { message: result };\n}\n\n/** Normalize delay option to full DelayConfig */\nfunction normalizeDelay(delay: number | DelayConfig | undefined): DelayConfig {\n if (delay === undefined) {\n return {};\n }\n if (typeof delay === \"number\") {\n // Simple number applies to all phases: first, items, and result\n return { first: delay, items: delay, result: delay };\n }\n return delay;\n}\n\n/** Options for consumeStream */\ntype ConsumeStreamOptions<TYield, TReturn> = {\n source: StreamSource<TYield>;\n toastId: string | number;\n streaming: ToastStreamData<TYield, TReturn>[\"streaming\"];\n onItem: ToastStreamData<TYield, TReturn>[\"onItem\"];\n delayConfig: DelayConfig;\n};\n\n/** Core streaming logic - consumes source and updates toast, captures return value */\nasync function consumeStream<TYield, TReturn>(\n options: ConsumeStreamOptions<TYield, TReturn>,\n): Promise<ConsumeResult<TYield, TReturn>> {\n const { source: sourceOrFactory, toastId, streaming, onItem, delayConfig } = options;\n // Resolve factory function if needed\n const source = resolveStreamSource(sourceOrFactory);\n\n const items: TYield[] = [];\n let count = 0;\n let isFirst = true;\n\n const updateToast = async (item: TYield) => {\n // Delay before first item (after loading)\n if (isFirst && delayConfig.first) {\n await wait(delayConfig.first);\n isFirst = false;\n } else if (!isFirst && delayConfig.items) {\n // Delay between items (not before first)\n await wait(delayConfig.items);\n } else {\n isFirst = false;\n }\n\n count += 1;\n items.push(item);\n onItem?.(item, count);\n\n const streamingData: StreamingData<TYield> = {\n count,\n latest: item,\n items: [...items],\n };\n const { message: streamingMsg, options: streamingOptions } = await resolveResult(\n streaming,\n streamingData,\n `Streaming... (${count} items)`,\n );\n toast.loading(streamingMsg, { id: toastId, ...streamingOptions });\n };\n\n try {\n // Handle sync Generator specially to capture return value\n if (isGenerator<TYield, TReturn>(source)) {\n let result = source.next();\n while (!result.done) {\n await updateToast(result.value);\n result = source.next();\n }\n return { ok: true, items, returnValue: result.value };\n }\n\n // Handle AsyncGenerator specially to capture return value\n if (isAsyncGenerator<TYield, TReturn>(source)) {\n let result = await source.next();\n while (!result.done) {\n await updateToast(result.value);\n result = await source.next();\n }\n return { ok: true, items, returnValue: result.value };\n }\n\n // Handle AsyncChannel specially to capture return value\n if (source instanceof AsyncChannel) {\n const iterator = source[Symbol.asyncIterator]();\n let result = await iterator.next();\n while (!result.done) {\n await updateToast(result.value);\n result = await iterator.next();\n }\n return { ok: true, items, returnValue: result.value };\n }\n\n // For plain iterables/iterators, convert and iterate (no return value)\n const iterable =\n isAsyncIterable(source) || isIterable(source)\n ? toAsyncIterable(source)\n : toAsyncIterable(source as AsyncIterator<TYield>);\n\n for await (const item of iterable) {\n await updateToast(item);\n }\n\n return { ok: true, items, returnValue: undefined as TReturn };\n } catch (error) {\n return { ok: false, items, error };\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// toastStream overloads\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Like `toast.promise` but for iterables/generators/streams.\n *\n * Blocking/awaitable API: waits until the stream completes and resolves to\n * `{ items, returnValue }`. Use this when the caller should wait for all\n * streamed values before continuing.\n *\n * Handles: loading → streaming → success/error.\n *\n * Supports sync and async sources. Captures generator return values.\n * Accepts either an instance or a factory function.\n *\n * @example Async generator with progress and return value\n * ```ts\n * import { toastStream } from \"xantiagoma/sonner\";\n *\n * async function* importUsers() {\n * yield { id: 1, name: \"Ada\" };\n * yield { id: 2, name: \"Grace\" };\n * return { imported: 2 };\n * }\n *\n * const { items, returnValue } = await toastStream(importUsers, {\n * loading: \"Importing users...\",\n * streaming: ({ count, latest }) => `Imported ${count}: ${latest.name}`,\n * success: ({ count, returnValue }) =>\n * `Imported ${count} users (${returnValue.imported} confirmed)`,\n * error: (error, partial) =>\n * `Import failed after ${partial.count} users: ${String(error)}`,\n * });\n * ```\n *\n * @example Sync iterable with artificial delay\n * ```ts\n * const result = await toastStream([\"parse\", \"validate\", \"save\"], {\n * loading: \"Starting...\",\n * streaming: ({ latest }) => `Running ${latest}`,\n * success: ({ count }) => `Completed ${count} steps`,\n * delay: { first: 300, items: 200, result: 150 },\n * });\n * ```\n */\nexport function toastStream<TYield, TReturn>(\n source:\n | AsyncGenerator<TYield, TReturn, unknown>\n | (() => AsyncGenerator<TYield, TReturn, unknown>),\n data?: ToastStreamData<TYield, TReturn>,\n): Promise<StreamResult<TYield, TReturn>>;\n\n/** Sync Generator with return value */\nexport function toastStream<TYield, TReturn>(\n source: Generator<TYield, TReturn, unknown> | (() => Generator<TYield, TReturn, unknown>),\n data?: ToastStreamData<TYield, TReturn>,\n): Promise<StreamResult<TYield, TReturn>>;\n\n/** AsyncChannel with return type - must come before AsyncIterable overload */\nexport function toastStream<TYield, TReturn>(\n source: AsyncChannel<TYield, TReturn> | (() => AsyncChannel<TYield, TReturn>),\n data?: ToastStreamData<TYield, TReturn>,\n): Promise<StreamResult<TYield, TReturn>>;\n\n/** AsyncIterable (no return value) */\nexport function toastStream<TYield>(\n source: AsyncIterable<TYield> | (() => AsyncIterable<TYield>),\n data?: ToastStreamData<TYield, void>,\n): Promise<StreamResult<TYield, void>>;\n\n/** AsyncIterator (no return value) */\nexport function toastStream<TYield>(\n source: AsyncIterator<TYield> | (() => AsyncIterator<TYield>),\n data?: ToastStreamData<TYield, void>,\n): Promise<StreamResult<TYield, void>>;\n\n/** Sync Iterable (no return value) */\nexport function toastStream<TYield>(\n source: Iterable<TYield> | (() => Iterable<TYield>),\n data?: ToastStreamData<TYield, void>,\n): Promise<StreamResult<TYield, void>>;\n\n/** Sync Iterator (no return value) */\nexport function toastStream<TYield>(\n source: Iterator<TYield> | (() => Iterator<TYield>),\n data?: ToastStreamData<TYield, void>,\n): Promise<StreamResult<TYield, void>>;\n\n// Implementation\nexport async function toastStream<TYield, TReturn = void>(\n source: StreamSource<TYield>,\n data: ToastStreamData<TYield, TReturn> = {},\n): Promise<StreamResult<TYield, TReturn>> {\n const {\n loading,\n streaming,\n success,\n error,\n finally: onFinally,\n onItem,\n delay,\n ...toastOptions\n } = data;\n\n const delayConfig = normalizeDelay(delay);\n const { message: loadingMsg, options: loadingOptions } = await resolveLoading(\n loading,\n \"Processing...\",\n );\n const toastId = toast.loading(loadingMsg, {\n ...toastOptions,\n ...loadingOptions,\n });\n\n const result = await consumeStream<TYield, TReturn>({\n source,\n toastId,\n streaming,\n onItem,\n delayConfig,\n });\n\n await onFinally?.();\n\n // Delay before showing result\n if (delayConfig.result) {\n await wait(delayConfig.result);\n }\n\n if (!result.ok) {\n const partial: ErrorPartialData<TYield> = {\n count: result.items.length,\n items: result.items,\n };\n const { message: errorMsg, options: errorOptions } = await resolveErrorResult(\n error,\n result.error,\n partial,\n \"Stream failed\",\n );\n toast.error(errorMsg, { id: toastId, ...errorOptions });\n throw result.error;\n }\n\n const successData: SuccessData<TYield, TReturn> = {\n count: result.items.length,\n items: result.items,\n returnValue: result.returnValue,\n };\n const { message: successMsg, options: successOptions } = await resolveResult(\n success,\n successData,\n `Complete! ${result.items.length} items`,\n );\n\n toast.success(successMsg, { id: toastId, ...successOptions });\n return { items: result.items, returnValue: result.returnValue };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// toastStreamAsync overloads\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Return type matching toast.promise's unwrap pattern */\ntype ToastStreamReturn<TYield, TReturn> = (string | number) & {\n unwrap: () => Promise<StreamResult<TYield, TReturn>>;\n};\n\n/**\n * Non-blocking version that returns immediately like `toast.promise`.\n * Returns the toast ID with an `unwrap()` method to await the result later.\n * Accepts either an instance or a factory function.\n *\n * Use this when the UI should continue immediately while the toast tracks the\n * stream in the background.\n *\n * @example\n * ```ts\n * import { toastStreamAsync } from \"xantiagoma/sonner\";\n *\n * const toastId = toastStreamAsync(importUsers, {\n * loading: \"Importing users...\",\n * streaming: ({ count }) => `Imported ${count}`,\n * success: ({ count }) => `Imported ${count} users`,\n * });\n *\n * // The caller continues immediately; await only if/when the result matters.\n * const { items, returnValue } = await toastId.unwrap();\n * ```\n *\n * @example Fire-and-track with error handling\n * ```ts\n * const toastId = toastStreamAsync(uploadFiles(files), {\n * loading: \"Uploading...\",\n * streaming: ({ count }) => `${count} files uploaded`,\n * error: (error, partial) =>\n * `Upload failed after ${partial.count} files: ${String(error)}`,\n * });\n *\n * toastId.unwrap().catch((error) => {\n * reportError(error);\n * });\n * ```\n */\nexport function toastStreamAsync<TYield, TReturn>(\n source:\n | AsyncGenerator<TYield, TReturn, unknown>\n | (() => AsyncGenerator<TYield, TReturn, unknown>),\n data?: ToastStreamData<TYield, TReturn>,\n): ToastStreamReturn<TYield, TReturn>;\n\n/** Sync Generator with return value */\nexport function toastStreamAsync<TYield, TReturn>(\n source: Generator<TYield, TReturn, unknown> | (() => Generator<TYield, TReturn, unknown>),\n data?: ToastStreamData<TYield, TReturn>,\n): ToastStreamReturn<TYield, TReturn>;\n\n/** AsyncChannel with return type */\nexport function toastStreamAsync<TYield, TReturn>(\n source: AsyncChannel<TYield, TReturn> | (() => AsyncChannel<TYield, TReturn>),\n data?: ToastStreamData<TYield, TReturn>,\n): ToastStreamReturn<TYield, TReturn>;\n\n/** AsyncIterable - excludes AsyncChannel which has its own overload */\nexport function toastStreamAsync<TYield>(\n source:\n | Exclude<AsyncIterable<TYield>, AsyncChannel<TYield, unknown>>\n | (() => Exclude<AsyncIterable<TYield>, AsyncChannel<TYield, unknown>>),\n data?: ToastStreamData<TYield, void>,\n): ToastStreamReturn<TYield, void>;\n\n/** AsyncIterator */\nexport function toastStreamAsync<TYield>(\n source: AsyncIterator<TYield> | (() => AsyncIterator<TYield>),\n data?: ToastStreamData<TYield, void>,\n): ToastStreamReturn<TYield, void>;\n\n/** Sync Iterable */\nexport function toastStreamAsync<TYield>(\n source: Iterable<TYield> | (() => Iterable<TYield>),\n data?: ToastStreamData<TYield, void>,\n): ToastStreamReturn<TYield, void>;\n\n/** Sync Iterator */\nexport function toastStreamAsync<TYield>(\n source: Iterator<TYield> | (() => Iterator<TYield>),\n data?: ToastStreamData<TYield, void>,\n): ToastStreamReturn<TYield, void>;\n\n// Implementation\nexport function toastStreamAsync<TYield, TReturn = void>(\n source: StreamSource<TYield>,\n data: ToastStreamData<TYield, TReturn> = {},\n): ToastStreamReturn<TYield, TReturn> {\n const { loading, delay, ...rest } = data;\n\n const delayConfig = normalizeDelay(delay);\n // For non-blocking version, show simple loading immediately\n // Callbacks and extended results are resolved inside the promise\n const needsAsyncResolve =\n typeof loading === \"function\" ||\n (typeof loading === \"object\" && loading !== null && \"message\" in loading);\n const initialLoadingMsg = needsAsyncResolve\n ? \"Processing...\"\n : ((loading as ReactNode) ?? \"Processing...\");\n const toastId = toast.loading(initialLoadingMsg);\n\n const promise = (async () => {\n const { streaming, success, error, finally: onFinally, onItem, ...toastOptions } = rest;\n\n // If loading needs async resolution (callback or extended result), resolve and update\n if (needsAsyncResolve) {\n const { message: loadingMsg, options: loadingOptions } = await resolveLoading(\n loading,\n \"Processing...\",\n );\n toast.loading(loadingMsg, { id: toastId, ...loadingOptions });\n }\n\n const result = await consumeStream<TYield, TReturn>({\n source,\n toastId,\n streaming,\n onItem,\n delayConfig,\n });\n\n await onFinally?.();\n\n // Delay before showing result\n if (delayConfig.result) {\n await wait(delayConfig.result);\n }\n\n if (!result.ok) {\n const partial: ErrorPartialData<TYield> = {\n count: result.items.length,\n items: result.items,\n };\n const { message: errorMsg, options: errorOptions } = await resolveErrorResult(\n error,\n result.error,\n partial,\n \"Stream failed\",\n );\n toast.error(errorMsg, { id: toastId, ...toastOptions, ...errorOptions });\n throw result.error;\n }\n\n const successData: SuccessData<TYield, TReturn> = {\n count: result.items.length,\n items: result.items,\n returnValue: result.returnValue,\n };\n const { message: successMsg, options: successOptions } = await resolveResult(\n success,\n successData,\n `Complete! ${result.items.length} items`,\n );\n\n toast.success(successMsg, {\n id: toastId,\n ...toastOptions,\n ...successOptions,\n });\n return { items: result.items, returnValue: result.returnValue };\n })();\n\n // Match toast.promise's return signature\n const res = toastId as ToastStreamReturn<TYield, TReturn>;\n res.unwrap = () => promise;\n return res;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Releasing
|
|
2
|
+
|
|
3
|
+
This package publishes from GitHub Actions when a `v*` tag is pushed.
|
|
4
|
+
|
|
5
|
+
## CI/CD Overview
|
|
6
|
+
|
|
7
|
+
### CI
|
|
8
|
+
|
|
9
|
+
`.github/workflows/ci.yml` runs on pushes and pull requests to `main`:
|
|
10
|
+
|
|
11
|
+
- `bun install`
|
|
12
|
+
- `bunx playwright install --with-deps chromium`
|
|
13
|
+
- `bun run lint`
|
|
14
|
+
- `bun run format:check`
|
|
15
|
+
- `npx tsc --noEmit`
|
|
16
|
+
- `bun run test`
|
|
17
|
+
- `bun run build`
|
|
18
|
+
|
|
19
|
+
### Publish
|
|
20
|
+
|
|
21
|
+
`.github/workflows/publish.yml` runs on pushed tags matching `v*`:
|
|
22
|
+
|
|
23
|
+
- `bun install`
|
|
24
|
+
- `bunx playwright install --with-deps chromium`
|
|
25
|
+
- `bun run test`
|
|
26
|
+
- `bun run build`
|
|
27
|
+
- `npm publish --provenance --access public || npm publish --access public`
|
|
28
|
+
- `gh release create <tag> --generate-notes || echo "Release already exists"`
|
|
29
|
+
|
|
30
|
+
The publish workflow requires `NPM_TOKEN` in repository secrets. GitHub release
|
|
31
|
+
creation uses the workflow `GITHUB_TOKEN`.
|
|
32
|
+
|
|
33
|
+
## Normal Release Flow
|
|
34
|
+
|
|
35
|
+
1. Ensure all feature/fix changes are already committed.
|
|
36
|
+
2. Run local validation:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
bun run format
|
|
40
|
+
bun run check
|
|
41
|
+
bun run test
|
|
42
|
+
bun run build
|
|
43
|
+
npm pack --dry-run
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
3. Create the release commit and tag with an explicit version:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
bunx changelogen --release -r 0.2.0 --push --no-open
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Replace `0.2.0` with the intended version.
|
|
53
|
+
|
|
54
|
+
4. Watch GitHub Actions:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
gh run list --limit 5
|
|
58
|
+
gh run watch <run-id>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
5. Confirm npm and GitHub release state:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm view xantiagoma version
|
|
65
|
+
gh release list --limit 5
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Important: Pre-1.0 Versions
|
|
69
|
+
|
|
70
|
+
Do **not** use `--minor` to create a `0.x` minor release. Changelogen follows
|
|
71
|
+
pre-1.0 semver behavior and may convert a minor bump such as `0.2.0` into the
|
|
72
|
+
next patch, e.g. `0.1.3`.
|
|
73
|
+
|
|
74
|
+
Use an explicit version instead:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
bunx changelogen --release -r 0.2.0 --push --no-open
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The package script `bun run release` uses automatic bump detection. Use it only
|
|
81
|
+
when the inferred version is acceptable. For planned minor releases, prefer the
|
|
82
|
+
explicit `-r <version>` command above.
|
|
83
|
+
|
|
84
|
+
## If The Wrong Tag Is Pushed
|
|
85
|
+
|
|
86
|
+
If a wrong tag is pushed but npm has **not** published it yet:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
gh run list --workflow Publish --limit 5
|
|
90
|
+
gh run cancel <publish-run-id>
|
|
91
|
+
git tag -d v0.1.3
|
|
92
|
+
git push origin :refs/tags/v0.1.3
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Then create the intended release with an explicit version.
|
|
96
|
+
|
|
97
|
+
If npm has already published the wrong version, do not delete or rewrite it.
|
|
98
|
+
Publish a new corrected version instead.
|
|
99
|
+
|
|
100
|
+
## Package Contents
|
|
101
|
+
|
|
102
|
+
`package.json` controls published files. Keep `docs` in the `files` list because
|
|
103
|
+
the README links to `docs/PAGINATION.md` and `docs/sync-async-adaptive.md`.
|
|
104
|
+
|
|
105
|
+
Before publishing, verify package contents:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npm pack --dry-run --json
|
|
109
|
+
```
|