yummies 7.19.0 → 7.19.2
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/async.cjs +29 -14
- package/async.cjs.map +1 -1
- package/async.d.ts +28 -4
- package/async.js +29 -14
- package/async.js.map +1 -1
- package/package.json +1 -1
package/async.cjs
CHANGED
|
@@ -168,10 +168,12 @@ function setAbortableInterval(callback, delayInMs, signal) {
|
|
|
168
168
|
* Tagged template that builds an async iterable of string chunks (like a streaming template engine).
|
|
169
169
|
*
|
|
170
170
|
* Static template parts are yielded as-is. Each interpolated “piece” can be a primitive, a nested array of pieces,
|
|
171
|
-
* a `Promise` of a piece,
|
|
171
|
+
* a `Promise` of a piece (including a resolved primitive), an async iterable of primitives, or a
|
|
172
|
+
* **zero-arg function** whose result is processed the same way (see {@link AsyncTemplatePiece} / `MaybeFn`).
|
|
172
173
|
*
|
|
173
174
|
* Interpolation handling matches `processTemplatePiece`: `null`, `undefined`, and `false` add nothing; any other value
|
|
174
|
-
* becomes text via `String(...)
|
|
175
|
+
* becomes text via `String(...)`; functions are called once with no arguments before further processing. Collect chunks
|
|
176
|
+
* with `for await...of` or utilities like `Array.fromAsync` where available.
|
|
175
177
|
*
|
|
176
178
|
* @param strings - Cooked template segments from the tag (`strings[0]`, `strings[1]`, …).
|
|
177
179
|
* @param pieces - Values between segments (`pieces[i]` sits between `strings[i]` and `strings[i + 1]`).
|
|
@@ -203,13 +205,21 @@ function setAbortableInterval(callback, delayInMs, signal) {
|
|
|
203
205
|
* }
|
|
204
206
|
* const gen = asyncTemplate`Header\n${lines()}Footer`;
|
|
205
207
|
* ```
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* Lazy piece via a thunk (invoked as `piece()`):
|
|
211
|
+
* ```ts
|
|
212
|
+
* let n = 0;
|
|
213
|
+
* const gen = asyncTemplate`count=${() => ++n}`;
|
|
214
|
+
* // first consumption yields "count=1", etc.
|
|
215
|
+
* ```
|
|
206
216
|
*/
|
|
207
217
|
async function* asyncTemplate(strings, ...pieces) {
|
|
208
218
|
for (let i = 0; i < strings.length; i++) {
|
|
209
219
|
yield strings[i];
|
|
210
220
|
if (i < pieces.length) {
|
|
211
|
-
const
|
|
212
|
-
yield* processTemplatePiece(
|
|
221
|
+
const piece = pieces[i];
|
|
222
|
+
yield* processTemplatePiece(piece);
|
|
213
223
|
}
|
|
214
224
|
}
|
|
215
225
|
}
|
|
@@ -217,23 +227,28 @@ async function* asyncTemplate(strings, ...pieces) {
|
|
|
217
227
|
* Resolves a template piece into yielded string chunks.
|
|
218
228
|
*
|
|
219
229
|
* Skips output when `value === null || value === undefined || value === false`.
|
|
220
|
-
* Otherwise awaits promises, flattens arrays, streams async iterables,
|
|
230
|
+
* Otherwise awaits promises, flattens arrays, streams async iterables, invokes **functions** with no arguments and
|
|
231
|
+
* recurses on the return value, and uses `String(value)` for primitives.
|
|
221
232
|
*/
|
|
222
|
-
async function* processTemplatePiece(
|
|
223
|
-
if (
|
|
224
|
-
if (
|
|
225
|
-
yield* processTemplatePiece(await
|
|
233
|
+
async function* processTemplatePiece(piece) {
|
|
234
|
+
if (piece === null || piece === void 0 || piece === false) return;
|
|
235
|
+
if (piece instanceof Promise) {
|
|
236
|
+
yield* processTemplatePiece(await piece);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (Array.isArray(piece)) {
|
|
240
|
+
for (const item of piece) yield* processTemplatePiece(item);
|
|
226
241
|
return;
|
|
227
242
|
}
|
|
228
|
-
if (
|
|
229
|
-
for (const item of
|
|
243
|
+
if (typeof piece === "object" && piece !== null && typeof piece[Symbol.asyncIterator] === "function") {
|
|
244
|
+
for await (const item of piece) yield* processTemplatePiece(item);
|
|
230
245
|
return;
|
|
231
246
|
}
|
|
232
|
-
if (typeof
|
|
233
|
-
yield*
|
|
247
|
+
if (typeof piece === "function") {
|
|
248
|
+
yield* processTemplatePiece(piece());
|
|
234
249
|
return;
|
|
235
250
|
}
|
|
236
|
-
yield String(
|
|
251
|
+
yield String(piece);
|
|
237
252
|
}
|
|
238
253
|
//#endregion
|
|
239
254
|
exports.asyncTemplate = asyncTemplate;
|
package/async.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"async.cjs","names":[],"sources":["../src/async.ts"],"sourcesContent":["/**\n * ---header-docs-section---\n * # yummies/async\n *\n * ## Description\n *\n * Helpers for asynchronous control flow: delays, cancellable waits, scheduling on the next frame,\n * and small utilities around `requestAnimationFrame` and `queueMicrotask`. They complement native\n * `Promise`/`AbortSignal` patterns and keep timing logic easy to test and tree-shake per call site.\n * Import only what you need from `yummies/async` so bundlers can drop unused helpers.\n *\n * ## Usage\n *\n * ```ts\n * import { sleep } from \"yummies/async\";\n * ```\n */\n\nimport type { MaybePromise, Primitive } from './types.js';\n\n/**\n * Returns a promise that resolves after `time` milliseconds.\n *\n * When `signal` is passed and becomes aborted before the delay elapses, the promise\n * rejects with `signal.reason` (same as native `fetch` / `AbortController` usage).\n * The timeout is cleared on abort so no resolve happens after cancellation.\n *\n * @param time - Delay in milliseconds. Defaults to `0` (next macrotask tick, same idea as `setTimeout(0)`).\n * @param signal - Optional `AbortSignal` to cancel the wait early.\n * @returns A promise that resolves with `void` when the delay completes, or rejects if aborted.\n *\n * @example\n * Basic pause in an async function:\n * ```ts\n * await sleep(250);\n * console.log('after 250ms');\n * ```\n *\n * @example\n * Cancellable delay tied to component unmount or user action:\n * ```ts\n * const ac = new AbortController();\n * try {\n * await sleep(5000, ac.signal);\n * } catch (e) {\n * // aborted — e is signal.reason\n * }\n * ac.abort('user cancelled');\n * ```\n */\nexport const sleep = (time: number = 0, signal?: AbortSignal) =>\n new Promise<void>((resolve, reject) => {\n if (signal) {\n const abortListener = () => {\n clearTimeout(timerId);\n reject(signal?.reason);\n };\n const timerId = setTimeout(() => {\n signal.removeEventListener('abort', abortListener);\n resolve();\n }, time);\n signal.addEventListener('abort', abortListener, { once: true });\n } else {\n setTimeout(resolve, time);\n }\n });\n\n/**\n * Creates a promise that resolves after the specified number of milliseconds.\n *\n * @deprecated Use `sleep` instead.\n * @param ms Delay in milliseconds.\n * @returns Promise\n */\nexport const waitAsync = async (ms = 1000) =>\n new Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * Runs a loop driven by `requestAnimationFrame`: on each frame, `quitFunction` is called first.\n * If it returns a truthy value, the loop stops and no further frames are scheduled.\n * If it returns falsy or nothing, the next frame is scheduled recursively.\n *\n * Use this for per-frame work (animations, layout reads after paint) without managing\n * `cancelAnimationFrame` manually — returning `true` from `quitFunction` is the exit condition.\n *\n * When `asMicrotask` is `true`, scheduling the next RAF is deferred with `queueMicrotask`\n * so other microtasks can run before the frame is requested (useful when you need to\n * batch DOM updates or let reactive frameworks flush first).\n *\n * @param quitFunction - Invoked each animation frame. Return `true` to stop the loop.\n * @param asMicrotask - If `true`, wrap the `requestAnimationFrame` call in `queueMicrotask`.\n *\n * @example\n * Stop after 60 frames (~1s at 60Hz):\n * ```ts\n * let frames = 0;\n * endlessRAF(() => {\n * frames++;\n * updateSomething(frames);\n * return frames >= 60;\n * });\n * ```\n *\n * @example\n * Run until an element is removed or a flag is set:\n * ```ts\n * let running = true;\n * endlessRAF(() => {\n * if (!running || !document.body.contains(el)) return true;\n * draw(el);\n * }, true);\n * ```\n */\nexport const endlessRAF = (\n quitFunction: () => boolean | void,\n asMicrotask?: boolean,\n) => {\n if (quitFunction()) return;\n\n const raf = () =>\n requestAnimationFrame(() => endlessRAF(quitFunction, asMicrotask));\n\n if (asMicrotask) {\n queueMicrotask(raf);\n } else {\n raf();\n }\n};\n\n/**\n * Like `setTimeout`, but if `signal` aborts before the delay fires, the timer is cleared\n * and `callback` is never run. If the callback runs normally, the abort listener is removed.\n *\n * Does nothing special if `signal` is omitted — behaves like a plain timeout.\n *\n * @param callback - Function to run once after `delayInMs` (same as `setTimeout` callback).\n * @param delayInMs - Milliseconds to wait. Passed through to `setTimeout` (browser/Node semantics apply).\n * @param signal - When aborted, clears the pending timeout so `callback` is not invoked.\n *\n * @example\n * ```ts\n * const controller = new AbortController();\n * setAbortableTimeout(() => console.log('done'), 500, controller.signal);\n * // later: controller.abort(); // timeout cancelled, log never runs\n * ```\n *\n * @example\n * Zero-delay scheduling that can still be cancelled before the macrotask runs:\n * ```ts\n * const ac = new AbortController();\n * setAbortableTimeout(() => startIntro(), 0, ac.signal);\n * // e.g. on teardown: ac.abort();\n * ```\n */\nexport function setAbortableTimeout(\n callback: VoidFunction,\n delayInMs?: number,\n signal?: AbortSignal,\n) {\n let internalTimer: number | null = null;\n\n const handleAbort = () => {\n if (internalTimer == null) {\n return;\n }\n clearTimeout(internalTimer);\n internalTimer = null;\n };\n\n signal?.addEventListener('abort', handleAbort, { once: true });\n\n internalTimer = setTimeout(() => {\n signal?.removeEventListener('abort', handleAbort);\n callback();\n }, delayInMs);\n}\n\n/**\n * Like `setInterval`, but when `signal` aborts, the interval is cleared with `clearInterval`\n * and `callback` stops being called. If `signal` is omitted, behaves like a normal interval\n * (you must clear it yourself).\n *\n * @param callback - Invoked every `delayInMs` milliseconds until aborted or cleared.\n * @param delayInMs - Interval period in milliseconds (same as `setInterval`).\n * @param signal - Aborting stops the interval and removes the abort listener path from keeping work alive.\n *\n * @example\n * ```ts\n * const controller = new AbortController();\n * setAbortableInterval(() => console.log('tick'), 1000, controller.signal);\n * // stop: controller.abort();\n * ```\n *\n * @example\n * ```ts\n * const ac = new AbortController();\n * setAbortableInterval(syncStatus, 30_000, ac.signal);\n * window.addEventListener('beforeunload', () => ac.abort());\n * ```\n */\nexport function setAbortableInterval(\n callback: VoidFunction,\n delayInMs?: number,\n signal?: AbortSignal,\n) {\n let timer: number | null = null;\n\n const handleAbort = () => {\n if (timer == null) {\n return;\n }\n clearInterval(timer);\n timer = null;\n };\n\n signal?.addEventListener('abort', handleAbort, { once: true });\n\n timer = setInterval(callback, delayInMs);\n}\n\n/**\n * A single interpolated segment in {@link asyncTemplate}.\n *\n * - **Primitives** use `String(value)` for output, except that values matching\n * `value === null || value === undefined || value === false` yield no text (early return in `processTemplatePiece`).\n * Other falsy values such as `0` or `''` are still stringified.\n * - **Arrays** are flattened recursively in order.\n * - **Promises** are awaited; the resolved value is processed the same way.\n * - **Async iterables of strings** are streamed in order (`yield*`).\n */\nexport type AsyncTemplatePiece =\n | Primitive\n | void\n | AsyncIterable<string>\n | AsyncTemplatePiece[];\n\n/**\n * Tagged template that builds an async iterable of string chunks (like a streaming template engine).\n *\n * Static template parts are yielded as-is. Each interpolated “piece” can be a primitive, a nested array of pieces,\n * a `Promise` of a piece, or an async iterable of strings (e.g. another template or a line-by-line source).\n *\n * Interpolation handling matches `processTemplatePiece`: `null`, `undefined`, and `false` add nothing; any other value\n * becomes text via `String(...)`. Collect chunks with `for await...of` or utilities like `Array.fromAsync` where available.\n *\n * @param strings - Cooked template segments from the tag (`strings[0]`, `strings[1]`, …).\n * @param pieces - Values between segments (`pieces[i]` sits between `strings[i]` and `strings[i + 1]`).\n * @returns An async generator yielding string fragments in document order.\n *\n * @example\n * Plain values and promises:\n * ```ts\n * const gen = asyncTemplate`Hello, ${'world'}! Status: ${Promise.resolve(200)}`;\n * let out = '';\n * for await (const chunk of gen) out += chunk;\n * // out === 'Hello, world! Status: 200'\n * ```\n *\n * @example\n * Falsy pieces are omitted (`null`, `undefined`, `false`); arrays flatten recursively:\n * ```ts\n * const gen = asyncTemplate`${null}${false}A${['B', ['C']]}`;\n * const out = await Array.fromAsync(gen).then((parts) => parts.join(''));\n * // out === 'ABC'\n * ```\n *\n * @example\n * Stream from an async iterable (e.g. chunked upstream text):\n * ```ts\n * async function* lines() {\n * yield 'line1\\n';\n * yield 'line2\\n';\n * }\n * const gen = asyncTemplate`Header\\n${lines()}Footer`;\n * ```\n */\nexport async function* asyncTemplate(\n strings: TemplateStringsArray,\n ...pieces: MaybePromise<AsyncTemplatePiece>[]\n) {\n for (let i = 0; i < strings.length; i++) {\n yield strings[i];\n\n if (i < pieces.length) {\n const value = pieces[i];\n yield* processTemplatePiece(value);\n }\n }\n}\n\n/**\n * Resolves a template piece into yielded string chunks.\n *\n * Skips output when `value === null || value === undefined || value === false`.\n * Otherwise awaits promises, flattens arrays, streams async iterables, and uses `String(value)` for primitives.\n */\nasync function* processTemplatePiece(\n value: MaybePromise<AsyncTemplatePiece>,\n): AsyncGenerator<string, void, unknown> {\n if (value === null || value === undefined || value === false) {\n return;\n }\n\n if (value instanceof Promise) {\n const resolved = await value;\n yield* processTemplatePiece(resolved);\n return;\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n yield* processTemplatePiece(item);\n }\n return;\n }\n\n if (\n typeof value === 'object' &&\n typeof value[Symbol.asyncIterator] === 'function'\n ) {\n yield* value;\n return;\n }\n\n yield String(value);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,IAAa,SAAS,OAAe,GAAG,WACtC,IAAI,SAAe,SAAS,WAAW;AACrC,KAAI,QAAQ;EACV,MAAM,sBAAsB;AAC1B,gBAAa,QAAQ;AACrB,UAAO,QAAQ,OAAO;;EAExB,MAAM,UAAU,iBAAiB;AAC/B,UAAO,oBAAoB,SAAS,cAAc;AAClD,YAAS;KACR,KAAK;AACR,SAAO,iBAAiB,SAAS,eAAe,EAAE,MAAM,MAAM,CAAC;OAE/D,YAAW,SAAS,KAAK;EAE3B;;;;;;;;AASJ,IAAa,YAAY,OAAO,KAAK,QACnC,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCnD,IAAa,cACX,cACA,gBACG;AACH,KAAI,cAAc,CAAE;CAEpB,MAAM,YACJ,4BAA4B,WAAW,cAAc,YAAY,CAAC;AAEpE,KAAI,YACF,gBAAe,IAAI;KAEnB,MAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BT,SAAgB,oBACd,UACA,WACA,QACA;CACA,IAAI,gBAA+B;CAEnC,MAAM,oBAAoB;AACxB,MAAI,iBAAiB,KACnB;AAEF,eAAa,cAAc;AAC3B,kBAAgB;;AAGlB,SAAQ,iBAAiB,SAAS,aAAa,EAAE,MAAM,MAAM,CAAC;AAE9D,iBAAgB,iBAAiB;AAC/B,UAAQ,oBAAoB,SAAS,YAAY;AACjD,YAAU;IACT,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;AA0Bf,SAAgB,qBACd,UACA,WACA,QACA;CACA,IAAI,QAAuB;CAE3B,MAAM,oBAAoB;AACxB,MAAI,SAAS,KACX;AAEF,gBAAc,MAAM;AACpB,UAAQ;;AAGV,SAAQ,iBAAiB,SAAS,aAAa,EAAE,MAAM,MAAM,CAAC;AAE9D,SAAQ,YAAY,UAAU,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2D1C,gBAAuB,cACrB,SACA,GAAG,QACH;AACA,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,QAAM,QAAQ;AAEd,MAAI,IAAI,OAAO,QAAQ;GACrB,MAAM,QAAQ,OAAO;AACrB,UAAO,qBAAqB,MAAM;;;;;;;;;;AAWxC,gBAAgB,qBACd,OACuC;AACvC,KAAI,UAAU,QAAQ,UAAU,KAAA,KAAa,UAAU,MACrD;AAGF,KAAI,iBAAiB,SAAS;AAE5B,SAAO,qBADU,MAAM,MACc;AACrC;;AAGF,KAAI,MAAM,QAAQ,MAAM,EAAE;AACxB,OAAK,MAAM,QAAQ,MACjB,QAAO,qBAAqB,KAAK;AAEnC;;AAGF,KACE,OAAO,UAAU,YACjB,OAAO,MAAM,OAAO,mBAAmB,YACvC;AACA,SAAO;AACP;;AAGF,OAAM,OAAO,MAAM"}
|
|
1
|
+
{"version":3,"file":"async.cjs","names":[],"sources":["../src/async.ts"],"sourcesContent":["/**\n * ---header-docs-section---\n * # yummies/async\n *\n * ## Description\n *\n * Helpers for asynchronous control flow: delays, cancellable waits, scheduling on the next frame,\n * and small utilities around `requestAnimationFrame` and `queueMicrotask`. They complement native\n * `Promise`/`AbortSignal` patterns and keep timing logic easy to test and tree-shake per call site.\n * Import only what you need from `yummies/async` so bundlers can drop unused helpers.\n *\n * ## Usage\n *\n * ```ts\n * import { sleep } from \"yummies/async\";\n * ```\n */\n\nimport type { MaybeFn, MaybePromise, Primitive } from './types.js';\n\n/**\n * Returns a promise that resolves after `time` milliseconds.\n *\n * When `signal` is passed and becomes aborted before the delay elapses, the promise\n * rejects with `signal.reason` (same as native `fetch` / `AbortController` usage).\n * The timeout is cleared on abort so no resolve happens after cancellation.\n *\n * @param time - Delay in milliseconds. Defaults to `0` (next macrotask tick, same idea as `setTimeout(0)`).\n * @param signal - Optional `AbortSignal` to cancel the wait early.\n * @returns A promise that resolves with `void` when the delay completes, or rejects if aborted.\n *\n * @example\n * Basic pause in an async function:\n * ```ts\n * await sleep(250);\n * console.log('after 250ms');\n * ```\n *\n * @example\n * Cancellable delay tied to component unmount or user action:\n * ```ts\n * const ac = new AbortController();\n * try {\n * await sleep(5000, ac.signal);\n * } catch (e) {\n * // aborted — e is signal.reason\n * }\n * ac.abort('user cancelled');\n * ```\n */\nexport const sleep = (time: number = 0, signal?: AbortSignal) =>\n new Promise<void>((resolve, reject) => {\n if (signal) {\n const abortListener = () => {\n clearTimeout(timerId);\n reject(signal?.reason);\n };\n const timerId = setTimeout(() => {\n signal.removeEventListener('abort', abortListener);\n resolve();\n }, time);\n signal.addEventListener('abort', abortListener, { once: true });\n } else {\n setTimeout(resolve, time);\n }\n });\n\n/**\n * Creates a promise that resolves after the specified number of milliseconds.\n *\n * @deprecated Use `sleep` instead.\n * @param ms Delay in milliseconds.\n * @returns Promise\n */\nexport const waitAsync = async (ms = 1000) =>\n new Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * Runs a loop driven by `requestAnimationFrame`: on each frame, `quitFunction` is called first.\n * If it returns a truthy value, the loop stops and no further frames are scheduled.\n * If it returns falsy or nothing, the next frame is scheduled recursively.\n *\n * Use this for per-frame work (animations, layout reads after paint) without managing\n * `cancelAnimationFrame` manually — returning `true` from `quitFunction` is the exit condition.\n *\n * When `asMicrotask` is `true`, scheduling the next RAF is deferred with `queueMicrotask`\n * so other microtasks can run before the frame is requested (useful when you need to\n * batch DOM updates or let reactive frameworks flush first).\n *\n * @param quitFunction - Invoked each animation frame. Return `true` to stop the loop.\n * @param asMicrotask - If `true`, wrap the `requestAnimationFrame` call in `queueMicrotask`.\n *\n * @example\n * Stop after 60 frames (~1s at 60Hz):\n * ```ts\n * let frames = 0;\n * endlessRAF(() => {\n * frames++;\n * updateSomething(frames);\n * return frames >= 60;\n * });\n * ```\n *\n * @example\n * Run until an element is removed or a flag is set:\n * ```ts\n * let running = true;\n * endlessRAF(() => {\n * if (!running || !document.body.contains(el)) return true;\n * draw(el);\n * }, true);\n * ```\n */\nexport const endlessRAF = (\n quitFunction: () => boolean | void,\n asMicrotask?: boolean,\n) => {\n if (quitFunction()) return;\n\n const raf = () =>\n requestAnimationFrame(() => endlessRAF(quitFunction, asMicrotask));\n\n if (asMicrotask) {\n queueMicrotask(raf);\n } else {\n raf();\n }\n};\n\n/**\n * Like `setTimeout`, but if `signal` aborts before the delay fires, the timer is cleared\n * and `callback` is never run. If the callback runs normally, the abort listener is removed.\n *\n * Does nothing special if `signal` is omitted — behaves like a plain timeout.\n *\n * @param callback - Function to run once after `delayInMs` (same as `setTimeout` callback).\n * @param delayInMs - Milliseconds to wait. Passed through to `setTimeout` (browser/Node semantics apply).\n * @param signal - When aborted, clears the pending timeout so `callback` is not invoked.\n *\n * @example\n * ```ts\n * const controller = new AbortController();\n * setAbortableTimeout(() => console.log('done'), 500, controller.signal);\n * // later: controller.abort(); // timeout cancelled, log never runs\n * ```\n *\n * @example\n * Zero-delay scheduling that can still be cancelled before the macrotask runs:\n * ```ts\n * const ac = new AbortController();\n * setAbortableTimeout(() => startIntro(), 0, ac.signal);\n * // e.g. on teardown: ac.abort();\n * ```\n */\nexport function setAbortableTimeout(\n callback: VoidFunction,\n delayInMs?: number,\n signal?: AbortSignal,\n) {\n let internalTimer: number | null = null;\n\n const handleAbort = () => {\n if (internalTimer == null) {\n return;\n }\n clearTimeout(internalTimer);\n internalTimer = null;\n };\n\n signal?.addEventListener('abort', handleAbort, { once: true });\n\n internalTimer = setTimeout(() => {\n signal?.removeEventListener('abort', handleAbort);\n callback();\n }, delayInMs);\n}\n\n/**\n * Like `setInterval`, but when `signal` aborts, the interval is cleared with `clearInterval`\n * and `callback` stops being called. If `signal` is omitted, behaves like a normal interval\n * (you must clear it yourself).\n *\n * @param callback - Invoked every `delayInMs` milliseconds until aborted or cleared.\n * @param delayInMs - Interval period in milliseconds (same as `setInterval`).\n * @param signal - Aborting stops the interval and removes the abort listener path from keeping work alive.\n *\n * @example\n * ```ts\n * const controller = new AbortController();\n * setAbortableInterval(() => console.log('tick'), 1000, controller.signal);\n * // stop: controller.abort();\n * ```\n *\n * @example\n * ```ts\n * const ac = new AbortController();\n * setAbortableInterval(syncStatus, 30_000, ac.signal);\n * window.addEventListener('beforeunload', () => ac.abort());\n * ```\n */\nexport function setAbortableInterval(\n callback: VoidFunction,\n delayInMs?: number,\n signal?: AbortSignal,\n) {\n let timer: number | null = null;\n\n const handleAbort = () => {\n if (timer == null) {\n return;\n }\n clearInterval(timer);\n timer = null;\n };\n\n signal?.addEventListener('abort', handleAbort, { once: true });\n\n timer = setInterval(callback, delayInMs);\n}\n\n/**\n * A single interpolated segment in {@link asyncTemplate}.\n *\n * - **Primitives** use `String(value)` for output, except that values matching\n * `value === null || value === undefined || value === false` yield no text (early return in `processTemplatePiece`).\n * Other falsy values such as `0` or `''` are still stringified.\n * - **Arrays** are flattened recursively in order.\n * - **Promises** are awaited; the resolved value is processed the same way.\n * - **Async iterables of primitives** are streamed in order; each yielded value uses the same rules\n * as a standalone primitive (`String(...)`, with `null` / `undefined` / `false` omitted).\n * - **Functions** (thunks): if the piece is a function, it is invoked with **no arguments** (`piece()`), and the return\n * value is processed recursively—so you can defer work or return any other {@link AsyncTemplatePiece} shape\n * (see {@link MaybeFn}). At runtime a thunk may also return a `Promise` of a piece; that is covered by\n * {@link MaybePromise} on each template interpolation.\n */\nexport type AsyncTemplatePiece = MaybeFn<\n | Primitive\n | void\n | AsyncIterable<Primitive>\n | Promise<Primitive>\n | AsyncTemplatePiece[]\n>;\n\n/**\n * Tagged template that builds an async iterable of string chunks (like a streaming template engine).\n *\n * Static template parts are yielded as-is. Each interpolated “piece” can be a primitive, a nested array of pieces,\n * a `Promise` of a piece (including a resolved primitive), an async iterable of primitives, or a\n * **zero-arg function** whose result is processed the same way (see {@link AsyncTemplatePiece} / `MaybeFn`).\n *\n * Interpolation handling matches `processTemplatePiece`: `null`, `undefined`, and `false` add nothing; any other value\n * becomes text via `String(...)`; functions are called once with no arguments before further processing. Collect chunks\n * with `for await...of` or utilities like `Array.fromAsync` where available.\n *\n * @param strings - Cooked template segments from the tag (`strings[0]`, `strings[1]`, …).\n * @param pieces - Values between segments (`pieces[i]` sits between `strings[i]` and `strings[i + 1]`).\n * @returns An async generator yielding string fragments in document order.\n *\n * @example\n * Plain values and promises:\n * ```ts\n * const gen = asyncTemplate`Hello, ${'world'}! Status: ${Promise.resolve(200)}`;\n * let out = '';\n * for await (const chunk of gen) out += chunk;\n * // out === 'Hello, world! Status: 200'\n * ```\n *\n * @example\n * Falsy pieces are omitted (`null`, `undefined`, `false`); arrays flatten recursively:\n * ```ts\n * const gen = asyncTemplate`${null}${false}A${['B', ['C']]}`;\n * const out = await Array.fromAsync(gen).then((parts) => parts.join(''));\n * // out === 'ABC'\n * ```\n *\n * @example\n * Stream from an async iterable (e.g. chunked upstream text):\n * ```ts\n * async function* lines() {\n * yield 'line1\\n';\n * yield 'line2\\n';\n * }\n * const gen = asyncTemplate`Header\\n${lines()}Footer`;\n * ```\n *\n * @example\n * Lazy piece via a thunk (invoked as `piece()`):\n * ```ts\n * let n = 0;\n * const gen = asyncTemplate`count=${() => ++n}`;\n * // first consumption yields \"count=1\", etc.\n * ```\n */\nexport async function* asyncTemplate(\n strings: TemplateStringsArray,\n ...pieces: MaybePromise<AsyncTemplatePiece>[]\n) {\n for (let i = 0; i < strings.length; i++) {\n yield strings[i];\n\n if (i < pieces.length) {\n const piece = pieces[i];\n yield* processTemplatePiece(piece);\n }\n }\n}\n\n/**\n * Resolves a template piece into yielded string chunks.\n *\n * Skips output when `value === null || value === undefined || value === false`.\n * Otherwise awaits promises, flattens arrays, streams async iterables, invokes **functions** with no arguments and\n * recurses on the return value, and uses `String(value)` for primitives.\n */\nasync function* processTemplatePiece(\n piece: MaybePromise<AsyncTemplatePiece>,\n): AsyncGenerator<string, void, unknown> {\n if (piece === null || piece === undefined || piece === false) {\n return;\n }\n\n if (piece instanceof Promise) {\n const resolved = await piece;\n yield* processTemplatePiece(resolved);\n return;\n }\n\n if (Array.isArray(piece)) {\n for (const item of piece) {\n yield* processTemplatePiece(item);\n }\n return;\n }\n\n if (\n typeof piece === 'object' &&\n piece !== null &&\n typeof (piece as AsyncIterable<unknown>)[Symbol.asyncIterator] ===\n 'function'\n ) {\n for await (const item of piece as AsyncIterable<AsyncTemplatePiece>) {\n yield* processTemplatePiece(item);\n }\n return;\n }\n\n if (typeof piece === 'function') {\n yield* processTemplatePiece(piece());\n return;\n }\n\n yield String(piece);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,IAAa,SAAS,OAAe,GAAG,WACtC,IAAI,SAAe,SAAS,WAAW;AACrC,KAAI,QAAQ;EACV,MAAM,sBAAsB;AAC1B,gBAAa,QAAQ;AACrB,UAAO,QAAQ,OAAO;;EAExB,MAAM,UAAU,iBAAiB;AAC/B,UAAO,oBAAoB,SAAS,cAAc;AAClD,YAAS;KACR,KAAK;AACR,SAAO,iBAAiB,SAAS,eAAe,EAAE,MAAM,MAAM,CAAC;OAE/D,YAAW,SAAS,KAAK;EAE3B;;;;;;;;AASJ,IAAa,YAAY,OAAO,KAAK,QACnC,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCnD,IAAa,cACX,cACA,gBACG;AACH,KAAI,cAAc,CAAE;CAEpB,MAAM,YACJ,4BAA4B,WAAW,cAAc,YAAY,CAAC;AAEpE,KAAI,YACF,gBAAe,IAAI;KAEnB,MAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BT,SAAgB,oBACd,UACA,WACA,QACA;CACA,IAAI,gBAA+B;CAEnC,MAAM,oBAAoB;AACxB,MAAI,iBAAiB,KACnB;AAEF,eAAa,cAAc;AAC3B,kBAAgB;;AAGlB,SAAQ,iBAAiB,SAAS,aAAa,EAAE,MAAM,MAAM,CAAC;AAE9D,iBAAgB,iBAAiB;AAC/B,UAAQ,oBAAoB,SAAS,YAAY;AACjD,YAAU;IACT,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;AA0Bf,SAAgB,qBACd,UACA,WACA,QACA;CACA,IAAI,QAAuB;CAE3B,MAAM,oBAAoB;AACxB,MAAI,SAAS,KACX;AAEF,gBAAc,MAAM;AACpB,UAAQ;;AAGV,SAAQ,iBAAiB,SAAS,aAAa,EAAE,MAAM,MAAM,CAAC;AAE9D,SAAQ,YAAY,UAAU,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4E1C,gBAAuB,cACrB,SACA,GAAG,QACH;AACA,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,QAAM,QAAQ;AAEd,MAAI,IAAI,OAAO,QAAQ;GACrB,MAAM,QAAQ,OAAO;AACrB,UAAO,qBAAqB,MAAM;;;;;;;;;;;AAYxC,gBAAgB,qBACd,OACuC;AACvC,KAAI,UAAU,QAAQ,UAAU,KAAA,KAAa,UAAU,MACrD;AAGF,KAAI,iBAAiB,SAAS;AAE5B,SAAO,qBADU,MAAM,MACc;AACrC;;AAGF,KAAI,MAAM,QAAQ,MAAM,EAAE;AACxB,OAAK,MAAM,QAAQ,MACjB,QAAO,qBAAqB,KAAK;AAEnC;;AAGF,KACE,OAAO,UAAU,YACjB,UAAU,QACV,OAAQ,MAAiC,OAAO,mBAC9C,YACF;AACA,aAAW,MAAM,QAAQ,MACvB,QAAO,qBAAqB,KAAK;AAEnC;;AAGF,KAAI,OAAO,UAAU,YAAY;AAC/B,SAAO,qBAAqB,OAAO,CAAC;AACpC;;AAGF,OAAM,OAAO,MAAM"}
|
package/async.d.ts
CHANGED
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
* @returns Union of all primitive types
|
|
5
5
|
*/
|
|
6
6
|
type Primitive = null | undefined | string | number | boolean | symbol | bigint;
|
|
7
|
+
type Fn<T, TArgs extends any[] = any[]> = (...args: TArgs) => T;
|
|
8
|
+
/**
|
|
9
|
+
* Represents a type that can be either the specified type or a function returning that type.
|
|
10
|
+
*
|
|
11
|
+
* @template T - The type to make possibly a function
|
|
12
|
+
* @template TArgs - Arguments type for the function
|
|
13
|
+
* @returns T or a function that returns T
|
|
14
|
+
*/
|
|
15
|
+
type MaybeFn<T, TArgs extends any[] = any[]> = T | Fn<T, TArgs>;
|
|
7
16
|
/**
|
|
8
17
|
* Represents a type that can be either the specified type or a Promise resolving to that type.
|
|
9
18
|
*
|
|
@@ -164,17 +173,24 @@ declare function setAbortableInterval(callback: VoidFunction, delayInMs?: number
|
|
|
164
173
|
* Other falsy values such as `0` or `''` are still stringified.
|
|
165
174
|
* - **Arrays** are flattened recursively in order.
|
|
166
175
|
* - **Promises** are awaited; the resolved value is processed the same way.
|
|
167
|
-
* - **Async iterables of
|
|
176
|
+
* - **Async iterables of primitives** are streamed in order; each yielded value uses the same rules
|
|
177
|
+
* as a standalone primitive (`String(...)`, with `null` / `undefined` / `false` omitted).
|
|
178
|
+
* - **Functions** (thunks): if the piece is a function, it is invoked with **no arguments** (`piece()`), and the return
|
|
179
|
+
* value is processed recursively—so you can defer work or return any other {@link AsyncTemplatePiece} shape
|
|
180
|
+
* (see {@link MaybeFn}). At runtime a thunk may also return a `Promise` of a piece; that is covered by
|
|
181
|
+
* {@link MaybePromise} on each template interpolation.
|
|
168
182
|
*/
|
|
169
|
-
type AsyncTemplatePiece = Primitive | void | AsyncIterable<
|
|
183
|
+
type AsyncTemplatePiece = MaybeFn<Primitive | void | AsyncIterable<Primitive> | Promise<Primitive> | AsyncTemplatePiece[]>;
|
|
170
184
|
/**
|
|
171
185
|
* Tagged template that builds an async iterable of string chunks (like a streaming template engine).
|
|
172
186
|
*
|
|
173
187
|
* Static template parts are yielded as-is. Each interpolated “piece” can be a primitive, a nested array of pieces,
|
|
174
|
-
* a `Promise` of a piece,
|
|
188
|
+
* a `Promise` of a piece (including a resolved primitive), an async iterable of primitives, or a
|
|
189
|
+
* **zero-arg function** whose result is processed the same way (see {@link AsyncTemplatePiece} / `MaybeFn`).
|
|
175
190
|
*
|
|
176
191
|
* Interpolation handling matches `processTemplatePiece`: `null`, `undefined`, and `false` add nothing; any other value
|
|
177
|
-
* becomes text via `String(...)
|
|
192
|
+
* becomes text via `String(...)`; functions are called once with no arguments before further processing. Collect chunks
|
|
193
|
+
* with `for await...of` or utilities like `Array.fromAsync` where available.
|
|
178
194
|
*
|
|
179
195
|
* @param strings - Cooked template segments from the tag (`strings[0]`, `strings[1]`, …).
|
|
180
196
|
* @param pieces - Values between segments (`pieces[i]` sits between `strings[i]` and `strings[i + 1]`).
|
|
@@ -206,6 +222,14 @@ type AsyncTemplatePiece = Primitive | void | AsyncIterable<string> | AsyncTempla
|
|
|
206
222
|
* }
|
|
207
223
|
* const gen = asyncTemplate`Header\n${lines()}Footer`;
|
|
208
224
|
* ```
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* Lazy piece via a thunk (invoked as `piece()`):
|
|
228
|
+
* ```ts
|
|
229
|
+
* let n = 0;
|
|
230
|
+
* const gen = asyncTemplate`count=${() => ++n}`;
|
|
231
|
+
* // first consumption yields "count=1", etc.
|
|
232
|
+
* ```
|
|
209
233
|
*/
|
|
210
234
|
declare function asyncTemplate(strings: TemplateStringsArray, ...pieces: MaybePromise<AsyncTemplatePiece>[]): AsyncGenerator<string, void, unknown>;
|
|
211
235
|
|
package/async.js
CHANGED
|
@@ -167,10 +167,12 @@ function setAbortableInterval(callback, delayInMs, signal) {
|
|
|
167
167
|
* Tagged template that builds an async iterable of string chunks (like a streaming template engine).
|
|
168
168
|
*
|
|
169
169
|
* Static template parts are yielded as-is. Each interpolated “piece” can be a primitive, a nested array of pieces,
|
|
170
|
-
* a `Promise` of a piece,
|
|
170
|
+
* a `Promise` of a piece (including a resolved primitive), an async iterable of primitives, or a
|
|
171
|
+
* **zero-arg function** whose result is processed the same way (see {@link AsyncTemplatePiece} / `MaybeFn`).
|
|
171
172
|
*
|
|
172
173
|
* Interpolation handling matches `processTemplatePiece`: `null`, `undefined`, and `false` add nothing; any other value
|
|
173
|
-
* becomes text via `String(...)
|
|
174
|
+
* becomes text via `String(...)`; functions are called once with no arguments before further processing. Collect chunks
|
|
175
|
+
* with `for await...of` or utilities like `Array.fromAsync` where available.
|
|
174
176
|
*
|
|
175
177
|
* @param strings - Cooked template segments from the tag (`strings[0]`, `strings[1]`, …).
|
|
176
178
|
* @param pieces - Values between segments (`pieces[i]` sits between `strings[i]` and `strings[i + 1]`).
|
|
@@ -202,13 +204,21 @@ function setAbortableInterval(callback, delayInMs, signal) {
|
|
|
202
204
|
* }
|
|
203
205
|
* const gen = asyncTemplate`Header\n${lines()}Footer`;
|
|
204
206
|
* ```
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* Lazy piece via a thunk (invoked as `piece()`):
|
|
210
|
+
* ```ts
|
|
211
|
+
* let n = 0;
|
|
212
|
+
* const gen = asyncTemplate`count=${() => ++n}`;
|
|
213
|
+
* // first consumption yields "count=1", etc.
|
|
214
|
+
* ```
|
|
205
215
|
*/
|
|
206
216
|
async function* asyncTemplate(strings, ...pieces) {
|
|
207
217
|
for (let i = 0; i < strings.length; i++) {
|
|
208
218
|
yield strings[i];
|
|
209
219
|
if (i < pieces.length) {
|
|
210
|
-
const
|
|
211
|
-
yield* processTemplatePiece(
|
|
220
|
+
const piece = pieces[i];
|
|
221
|
+
yield* processTemplatePiece(piece);
|
|
212
222
|
}
|
|
213
223
|
}
|
|
214
224
|
}
|
|
@@ -216,23 +226,28 @@ async function* asyncTemplate(strings, ...pieces) {
|
|
|
216
226
|
* Resolves a template piece into yielded string chunks.
|
|
217
227
|
*
|
|
218
228
|
* Skips output when `value === null || value === undefined || value === false`.
|
|
219
|
-
* Otherwise awaits promises, flattens arrays, streams async iterables,
|
|
229
|
+
* Otherwise awaits promises, flattens arrays, streams async iterables, invokes **functions** with no arguments and
|
|
230
|
+
* recurses on the return value, and uses `String(value)` for primitives.
|
|
220
231
|
*/
|
|
221
|
-
async function* processTemplatePiece(
|
|
222
|
-
if (
|
|
223
|
-
if (
|
|
224
|
-
yield* processTemplatePiece(await
|
|
232
|
+
async function* processTemplatePiece(piece) {
|
|
233
|
+
if (piece === null || piece === void 0 || piece === false) return;
|
|
234
|
+
if (piece instanceof Promise) {
|
|
235
|
+
yield* processTemplatePiece(await piece);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (Array.isArray(piece)) {
|
|
239
|
+
for (const item of piece) yield* processTemplatePiece(item);
|
|
225
240
|
return;
|
|
226
241
|
}
|
|
227
|
-
if (
|
|
228
|
-
for (const item of
|
|
242
|
+
if (typeof piece === "object" && piece !== null && typeof piece[Symbol.asyncIterator] === "function") {
|
|
243
|
+
for await (const item of piece) yield* processTemplatePiece(item);
|
|
229
244
|
return;
|
|
230
245
|
}
|
|
231
|
-
if (typeof
|
|
232
|
-
yield*
|
|
246
|
+
if (typeof piece === "function") {
|
|
247
|
+
yield* processTemplatePiece(piece());
|
|
233
248
|
return;
|
|
234
249
|
}
|
|
235
|
-
yield String(
|
|
250
|
+
yield String(piece);
|
|
236
251
|
}
|
|
237
252
|
//#endregion
|
|
238
253
|
export { asyncTemplate, endlessRAF, setAbortableInterval, setAbortableTimeout, sleep, waitAsync };
|
package/async.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"async.js","names":[],"sources":["../src/async.ts"],"sourcesContent":["/**\n * ---header-docs-section---\n * # yummies/async\n *\n * ## Description\n *\n * Helpers for asynchronous control flow: delays, cancellable waits, scheduling on the next frame,\n * and small utilities around `requestAnimationFrame` and `queueMicrotask`. They complement native\n * `Promise`/`AbortSignal` patterns and keep timing logic easy to test and tree-shake per call site.\n * Import only what you need from `yummies/async` so bundlers can drop unused helpers.\n *\n * ## Usage\n *\n * ```ts\n * import { sleep } from \"yummies/async\";\n * ```\n */\n\nimport type { MaybePromise, Primitive } from './types.js';\n\n/**\n * Returns a promise that resolves after `time` milliseconds.\n *\n * When `signal` is passed and becomes aborted before the delay elapses, the promise\n * rejects with `signal.reason` (same as native `fetch` / `AbortController` usage).\n * The timeout is cleared on abort so no resolve happens after cancellation.\n *\n * @param time - Delay in milliseconds. Defaults to `0` (next macrotask tick, same idea as `setTimeout(0)`).\n * @param signal - Optional `AbortSignal` to cancel the wait early.\n * @returns A promise that resolves with `void` when the delay completes, or rejects if aborted.\n *\n * @example\n * Basic pause in an async function:\n * ```ts\n * await sleep(250);\n * console.log('after 250ms');\n * ```\n *\n * @example\n * Cancellable delay tied to component unmount or user action:\n * ```ts\n * const ac = new AbortController();\n * try {\n * await sleep(5000, ac.signal);\n * } catch (e) {\n * // aborted — e is signal.reason\n * }\n * ac.abort('user cancelled');\n * ```\n */\nexport const sleep = (time: number = 0, signal?: AbortSignal) =>\n new Promise<void>((resolve, reject) => {\n if (signal) {\n const abortListener = () => {\n clearTimeout(timerId);\n reject(signal?.reason);\n };\n const timerId = setTimeout(() => {\n signal.removeEventListener('abort', abortListener);\n resolve();\n }, time);\n signal.addEventListener('abort', abortListener, { once: true });\n } else {\n setTimeout(resolve, time);\n }\n });\n\n/**\n * Creates a promise that resolves after the specified number of milliseconds.\n *\n * @deprecated Use `sleep` instead.\n * @param ms Delay in milliseconds.\n * @returns Promise\n */\nexport const waitAsync = async (ms = 1000) =>\n new Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * Runs a loop driven by `requestAnimationFrame`: on each frame, `quitFunction` is called first.\n * If it returns a truthy value, the loop stops and no further frames are scheduled.\n * If it returns falsy or nothing, the next frame is scheduled recursively.\n *\n * Use this for per-frame work (animations, layout reads after paint) without managing\n * `cancelAnimationFrame` manually — returning `true` from `quitFunction` is the exit condition.\n *\n * When `asMicrotask` is `true`, scheduling the next RAF is deferred with `queueMicrotask`\n * so other microtasks can run before the frame is requested (useful when you need to\n * batch DOM updates or let reactive frameworks flush first).\n *\n * @param quitFunction - Invoked each animation frame. Return `true` to stop the loop.\n * @param asMicrotask - If `true`, wrap the `requestAnimationFrame` call in `queueMicrotask`.\n *\n * @example\n * Stop after 60 frames (~1s at 60Hz):\n * ```ts\n * let frames = 0;\n * endlessRAF(() => {\n * frames++;\n * updateSomething(frames);\n * return frames >= 60;\n * });\n * ```\n *\n * @example\n * Run until an element is removed or a flag is set:\n * ```ts\n * let running = true;\n * endlessRAF(() => {\n * if (!running || !document.body.contains(el)) return true;\n * draw(el);\n * }, true);\n * ```\n */\nexport const endlessRAF = (\n quitFunction: () => boolean | void,\n asMicrotask?: boolean,\n) => {\n if (quitFunction()) return;\n\n const raf = () =>\n requestAnimationFrame(() => endlessRAF(quitFunction, asMicrotask));\n\n if (asMicrotask) {\n queueMicrotask(raf);\n } else {\n raf();\n }\n};\n\n/**\n * Like `setTimeout`, but if `signal` aborts before the delay fires, the timer is cleared\n * and `callback` is never run. If the callback runs normally, the abort listener is removed.\n *\n * Does nothing special if `signal` is omitted — behaves like a plain timeout.\n *\n * @param callback - Function to run once after `delayInMs` (same as `setTimeout` callback).\n * @param delayInMs - Milliseconds to wait. Passed through to `setTimeout` (browser/Node semantics apply).\n * @param signal - When aborted, clears the pending timeout so `callback` is not invoked.\n *\n * @example\n * ```ts\n * const controller = new AbortController();\n * setAbortableTimeout(() => console.log('done'), 500, controller.signal);\n * // later: controller.abort(); // timeout cancelled, log never runs\n * ```\n *\n * @example\n * Zero-delay scheduling that can still be cancelled before the macrotask runs:\n * ```ts\n * const ac = new AbortController();\n * setAbortableTimeout(() => startIntro(), 0, ac.signal);\n * // e.g. on teardown: ac.abort();\n * ```\n */\nexport function setAbortableTimeout(\n callback: VoidFunction,\n delayInMs?: number,\n signal?: AbortSignal,\n) {\n let internalTimer: number | null = null;\n\n const handleAbort = () => {\n if (internalTimer == null) {\n return;\n }\n clearTimeout(internalTimer);\n internalTimer = null;\n };\n\n signal?.addEventListener('abort', handleAbort, { once: true });\n\n internalTimer = setTimeout(() => {\n signal?.removeEventListener('abort', handleAbort);\n callback();\n }, delayInMs);\n}\n\n/**\n * Like `setInterval`, but when `signal` aborts, the interval is cleared with `clearInterval`\n * and `callback` stops being called. If `signal` is omitted, behaves like a normal interval\n * (you must clear it yourself).\n *\n * @param callback - Invoked every `delayInMs` milliseconds until aborted or cleared.\n * @param delayInMs - Interval period in milliseconds (same as `setInterval`).\n * @param signal - Aborting stops the interval and removes the abort listener path from keeping work alive.\n *\n * @example\n * ```ts\n * const controller = new AbortController();\n * setAbortableInterval(() => console.log('tick'), 1000, controller.signal);\n * // stop: controller.abort();\n * ```\n *\n * @example\n * ```ts\n * const ac = new AbortController();\n * setAbortableInterval(syncStatus, 30_000, ac.signal);\n * window.addEventListener('beforeunload', () => ac.abort());\n * ```\n */\nexport function setAbortableInterval(\n callback: VoidFunction,\n delayInMs?: number,\n signal?: AbortSignal,\n) {\n let timer: number | null = null;\n\n const handleAbort = () => {\n if (timer == null) {\n return;\n }\n clearInterval(timer);\n timer = null;\n };\n\n signal?.addEventListener('abort', handleAbort, { once: true });\n\n timer = setInterval(callback, delayInMs);\n}\n\n/**\n * A single interpolated segment in {@link asyncTemplate}.\n *\n * - **Primitives** use `String(value)` for output, except that values matching\n * `value === null || value === undefined || value === false` yield no text (early return in `processTemplatePiece`).\n * Other falsy values such as `0` or `''` are still stringified.\n * - **Arrays** are flattened recursively in order.\n * - **Promises** are awaited; the resolved value is processed the same way.\n * - **Async iterables of strings** are streamed in order (`yield*`).\n */\nexport type AsyncTemplatePiece =\n | Primitive\n | void\n | AsyncIterable<string>\n | AsyncTemplatePiece[];\n\n/**\n * Tagged template that builds an async iterable of string chunks (like a streaming template engine).\n *\n * Static template parts are yielded as-is. Each interpolated “piece” can be a primitive, a nested array of pieces,\n * a `Promise` of a piece, or an async iterable of strings (e.g. another template or a line-by-line source).\n *\n * Interpolation handling matches `processTemplatePiece`: `null`, `undefined`, and `false` add nothing; any other value\n * becomes text via `String(...)`. Collect chunks with `for await...of` or utilities like `Array.fromAsync` where available.\n *\n * @param strings - Cooked template segments from the tag (`strings[0]`, `strings[1]`, …).\n * @param pieces - Values between segments (`pieces[i]` sits between `strings[i]` and `strings[i + 1]`).\n * @returns An async generator yielding string fragments in document order.\n *\n * @example\n * Plain values and promises:\n * ```ts\n * const gen = asyncTemplate`Hello, ${'world'}! Status: ${Promise.resolve(200)}`;\n * let out = '';\n * for await (const chunk of gen) out += chunk;\n * // out === 'Hello, world! Status: 200'\n * ```\n *\n * @example\n * Falsy pieces are omitted (`null`, `undefined`, `false`); arrays flatten recursively:\n * ```ts\n * const gen = asyncTemplate`${null}${false}A${['B', ['C']]}`;\n * const out = await Array.fromAsync(gen).then((parts) => parts.join(''));\n * // out === 'ABC'\n * ```\n *\n * @example\n * Stream from an async iterable (e.g. chunked upstream text):\n * ```ts\n * async function* lines() {\n * yield 'line1\\n';\n * yield 'line2\\n';\n * }\n * const gen = asyncTemplate`Header\\n${lines()}Footer`;\n * ```\n */\nexport async function* asyncTemplate(\n strings: TemplateStringsArray,\n ...pieces: MaybePromise<AsyncTemplatePiece>[]\n) {\n for (let i = 0; i < strings.length; i++) {\n yield strings[i];\n\n if (i < pieces.length) {\n const value = pieces[i];\n yield* processTemplatePiece(value);\n }\n }\n}\n\n/**\n * Resolves a template piece into yielded string chunks.\n *\n * Skips output when `value === null || value === undefined || value === false`.\n * Otherwise awaits promises, flattens arrays, streams async iterables, and uses `String(value)` for primitives.\n */\nasync function* processTemplatePiece(\n value: MaybePromise<AsyncTemplatePiece>,\n): AsyncGenerator<string, void, unknown> {\n if (value === null || value === undefined || value === false) {\n return;\n }\n\n if (value instanceof Promise) {\n const resolved = await value;\n yield* processTemplatePiece(resolved);\n return;\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n yield* processTemplatePiece(item);\n }\n return;\n }\n\n if (\n typeof value === 'object' &&\n typeof value[Symbol.asyncIterator] === 'function'\n ) {\n yield* value;\n return;\n }\n\n yield String(value);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,IAAa,SAAS,OAAe,GAAG,WACtC,IAAI,SAAe,SAAS,WAAW;AACrC,KAAI,QAAQ;EACV,MAAM,sBAAsB;AAC1B,gBAAa,QAAQ;AACrB,UAAO,QAAQ,OAAO;;EAExB,MAAM,UAAU,iBAAiB;AAC/B,UAAO,oBAAoB,SAAS,cAAc;AAClD,YAAS;KACR,KAAK;AACR,SAAO,iBAAiB,SAAS,eAAe,EAAE,MAAM,MAAM,CAAC;OAE/D,YAAW,SAAS,KAAK;EAE3B;;;;;;;;AASJ,IAAa,YAAY,OAAO,KAAK,QACnC,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCnD,IAAa,cACX,cACA,gBACG;AACH,KAAI,cAAc,CAAE;CAEpB,MAAM,YACJ,4BAA4B,WAAW,cAAc,YAAY,CAAC;AAEpE,KAAI,YACF,gBAAe,IAAI;KAEnB,MAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BT,SAAgB,oBACd,UACA,WACA,QACA;CACA,IAAI,gBAA+B;CAEnC,MAAM,oBAAoB;AACxB,MAAI,iBAAiB,KACnB;AAEF,eAAa,cAAc;AAC3B,kBAAgB;;AAGlB,SAAQ,iBAAiB,SAAS,aAAa,EAAE,MAAM,MAAM,CAAC;AAE9D,iBAAgB,iBAAiB;AAC/B,UAAQ,oBAAoB,SAAS,YAAY;AACjD,YAAU;IACT,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;AA0Bf,SAAgB,qBACd,UACA,WACA,QACA;CACA,IAAI,QAAuB;CAE3B,MAAM,oBAAoB;AACxB,MAAI,SAAS,KACX;AAEF,gBAAc,MAAM;AACpB,UAAQ;;AAGV,SAAQ,iBAAiB,SAAS,aAAa,EAAE,MAAM,MAAM,CAAC;AAE9D,SAAQ,YAAY,UAAU,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2D1C,gBAAuB,cACrB,SACA,GAAG,QACH;AACA,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,QAAM,QAAQ;AAEd,MAAI,IAAI,OAAO,QAAQ;GACrB,MAAM,QAAQ,OAAO;AACrB,UAAO,qBAAqB,MAAM;;;;;;;;;;AAWxC,gBAAgB,qBACd,OACuC;AACvC,KAAI,UAAU,QAAQ,UAAU,KAAA,KAAa,UAAU,MACrD;AAGF,KAAI,iBAAiB,SAAS;AAE5B,SAAO,qBADU,MAAM,MACc;AACrC;;AAGF,KAAI,MAAM,QAAQ,MAAM,EAAE;AACxB,OAAK,MAAM,QAAQ,MACjB,QAAO,qBAAqB,KAAK;AAEnC;;AAGF,KACE,OAAO,UAAU,YACjB,OAAO,MAAM,OAAO,mBAAmB,YACvC;AACA,SAAO;AACP;;AAGF,OAAM,OAAO,MAAM"}
|
|
1
|
+
{"version":3,"file":"async.js","names":[],"sources":["../src/async.ts"],"sourcesContent":["/**\n * ---header-docs-section---\n * # yummies/async\n *\n * ## Description\n *\n * Helpers for asynchronous control flow: delays, cancellable waits, scheduling on the next frame,\n * and small utilities around `requestAnimationFrame` and `queueMicrotask`. They complement native\n * `Promise`/`AbortSignal` patterns and keep timing logic easy to test and tree-shake per call site.\n * Import only what you need from `yummies/async` so bundlers can drop unused helpers.\n *\n * ## Usage\n *\n * ```ts\n * import { sleep } from \"yummies/async\";\n * ```\n */\n\nimport type { MaybeFn, MaybePromise, Primitive } from './types.js';\n\n/**\n * Returns a promise that resolves after `time` milliseconds.\n *\n * When `signal` is passed and becomes aborted before the delay elapses, the promise\n * rejects with `signal.reason` (same as native `fetch` / `AbortController` usage).\n * The timeout is cleared on abort so no resolve happens after cancellation.\n *\n * @param time - Delay in milliseconds. Defaults to `0` (next macrotask tick, same idea as `setTimeout(0)`).\n * @param signal - Optional `AbortSignal` to cancel the wait early.\n * @returns A promise that resolves with `void` when the delay completes, or rejects if aborted.\n *\n * @example\n * Basic pause in an async function:\n * ```ts\n * await sleep(250);\n * console.log('after 250ms');\n * ```\n *\n * @example\n * Cancellable delay tied to component unmount or user action:\n * ```ts\n * const ac = new AbortController();\n * try {\n * await sleep(5000, ac.signal);\n * } catch (e) {\n * // aborted — e is signal.reason\n * }\n * ac.abort('user cancelled');\n * ```\n */\nexport const sleep = (time: number = 0, signal?: AbortSignal) =>\n new Promise<void>((resolve, reject) => {\n if (signal) {\n const abortListener = () => {\n clearTimeout(timerId);\n reject(signal?.reason);\n };\n const timerId = setTimeout(() => {\n signal.removeEventListener('abort', abortListener);\n resolve();\n }, time);\n signal.addEventListener('abort', abortListener, { once: true });\n } else {\n setTimeout(resolve, time);\n }\n });\n\n/**\n * Creates a promise that resolves after the specified number of milliseconds.\n *\n * @deprecated Use `sleep` instead.\n * @param ms Delay in milliseconds.\n * @returns Promise\n */\nexport const waitAsync = async (ms = 1000) =>\n new Promise((resolve) => setTimeout(resolve, ms));\n\n/**\n * Runs a loop driven by `requestAnimationFrame`: on each frame, `quitFunction` is called first.\n * If it returns a truthy value, the loop stops and no further frames are scheduled.\n * If it returns falsy or nothing, the next frame is scheduled recursively.\n *\n * Use this for per-frame work (animations, layout reads after paint) without managing\n * `cancelAnimationFrame` manually — returning `true` from `quitFunction` is the exit condition.\n *\n * When `asMicrotask` is `true`, scheduling the next RAF is deferred with `queueMicrotask`\n * so other microtasks can run before the frame is requested (useful when you need to\n * batch DOM updates or let reactive frameworks flush first).\n *\n * @param quitFunction - Invoked each animation frame. Return `true` to stop the loop.\n * @param asMicrotask - If `true`, wrap the `requestAnimationFrame` call in `queueMicrotask`.\n *\n * @example\n * Stop after 60 frames (~1s at 60Hz):\n * ```ts\n * let frames = 0;\n * endlessRAF(() => {\n * frames++;\n * updateSomething(frames);\n * return frames >= 60;\n * });\n * ```\n *\n * @example\n * Run until an element is removed or a flag is set:\n * ```ts\n * let running = true;\n * endlessRAF(() => {\n * if (!running || !document.body.contains(el)) return true;\n * draw(el);\n * }, true);\n * ```\n */\nexport const endlessRAF = (\n quitFunction: () => boolean | void,\n asMicrotask?: boolean,\n) => {\n if (quitFunction()) return;\n\n const raf = () =>\n requestAnimationFrame(() => endlessRAF(quitFunction, asMicrotask));\n\n if (asMicrotask) {\n queueMicrotask(raf);\n } else {\n raf();\n }\n};\n\n/**\n * Like `setTimeout`, but if `signal` aborts before the delay fires, the timer is cleared\n * and `callback` is never run. If the callback runs normally, the abort listener is removed.\n *\n * Does nothing special if `signal` is omitted — behaves like a plain timeout.\n *\n * @param callback - Function to run once after `delayInMs` (same as `setTimeout` callback).\n * @param delayInMs - Milliseconds to wait. Passed through to `setTimeout` (browser/Node semantics apply).\n * @param signal - When aborted, clears the pending timeout so `callback` is not invoked.\n *\n * @example\n * ```ts\n * const controller = new AbortController();\n * setAbortableTimeout(() => console.log('done'), 500, controller.signal);\n * // later: controller.abort(); // timeout cancelled, log never runs\n * ```\n *\n * @example\n * Zero-delay scheduling that can still be cancelled before the macrotask runs:\n * ```ts\n * const ac = new AbortController();\n * setAbortableTimeout(() => startIntro(), 0, ac.signal);\n * // e.g. on teardown: ac.abort();\n * ```\n */\nexport function setAbortableTimeout(\n callback: VoidFunction,\n delayInMs?: number,\n signal?: AbortSignal,\n) {\n let internalTimer: number | null = null;\n\n const handleAbort = () => {\n if (internalTimer == null) {\n return;\n }\n clearTimeout(internalTimer);\n internalTimer = null;\n };\n\n signal?.addEventListener('abort', handleAbort, { once: true });\n\n internalTimer = setTimeout(() => {\n signal?.removeEventListener('abort', handleAbort);\n callback();\n }, delayInMs);\n}\n\n/**\n * Like `setInterval`, but when `signal` aborts, the interval is cleared with `clearInterval`\n * and `callback` stops being called. If `signal` is omitted, behaves like a normal interval\n * (you must clear it yourself).\n *\n * @param callback - Invoked every `delayInMs` milliseconds until aborted or cleared.\n * @param delayInMs - Interval period in milliseconds (same as `setInterval`).\n * @param signal - Aborting stops the interval and removes the abort listener path from keeping work alive.\n *\n * @example\n * ```ts\n * const controller = new AbortController();\n * setAbortableInterval(() => console.log('tick'), 1000, controller.signal);\n * // stop: controller.abort();\n * ```\n *\n * @example\n * ```ts\n * const ac = new AbortController();\n * setAbortableInterval(syncStatus, 30_000, ac.signal);\n * window.addEventListener('beforeunload', () => ac.abort());\n * ```\n */\nexport function setAbortableInterval(\n callback: VoidFunction,\n delayInMs?: number,\n signal?: AbortSignal,\n) {\n let timer: number | null = null;\n\n const handleAbort = () => {\n if (timer == null) {\n return;\n }\n clearInterval(timer);\n timer = null;\n };\n\n signal?.addEventListener('abort', handleAbort, { once: true });\n\n timer = setInterval(callback, delayInMs);\n}\n\n/**\n * A single interpolated segment in {@link asyncTemplate}.\n *\n * - **Primitives** use `String(value)` for output, except that values matching\n * `value === null || value === undefined || value === false` yield no text (early return in `processTemplatePiece`).\n * Other falsy values such as `0` or `''` are still stringified.\n * - **Arrays** are flattened recursively in order.\n * - **Promises** are awaited; the resolved value is processed the same way.\n * - **Async iterables of primitives** are streamed in order; each yielded value uses the same rules\n * as a standalone primitive (`String(...)`, with `null` / `undefined` / `false` omitted).\n * - **Functions** (thunks): if the piece is a function, it is invoked with **no arguments** (`piece()`), and the return\n * value is processed recursively—so you can defer work or return any other {@link AsyncTemplatePiece} shape\n * (see {@link MaybeFn}). At runtime a thunk may also return a `Promise` of a piece; that is covered by\n * {@link MaybePromise} on each template interpolation.\n */\nexport type AsyncTemplatePiece = MaybeFn<\n | Primitive\n | void\n | AsyncIterable<Primitive>\n | Promise<Primitive>\n | AsyncTemplatePiece[]\n>;\n\n/**\n * Tagged template that builds an async iterable of string chunks (like a streaming template engine).\n *\n * Static template parts are yielded as-is. Each interpolated “piece” can be a primitive, a nested array of pieces,\n * a `Promise` of a piece (including a resolved primitive), an async iterable of primitives, or a\n * **zero-arg function** whose result is processed the same way (see {@link AsyncTemplatePiece} / `MaybeFn`).\n *\n * Interpolation handling matches `processTemplatePiece`: `null`, `undefined`, and `false` add nothing; any other value\n * becomes text via `String(...)`; functions are called once with no arguments before further processing. Collect chunks\n * with `for await...of` or utilities like `Array.fromAsync` where available.\n *\n * @param strings - Cooked template segments from the tag (`strings[0]`, `strings[1]`, …).\n * @param pieces - Values between segments (`pieces[i]` sits between `strings[i]` and `strings[i + 1]`).\n * @returns An async generator yielding string fragments in document order.\n *\n * @example\n * Plain values and promises:\n * ```ts\n * const gen = asyncTemplate`Hello, ${'world'}! Status: ${Promise.resolve(200)}`;\n * let out = '';\n * for await (const chunk of gen) out += chunk;\n * // out === 'Hello, world! Status: 200'\n * ```\n *\n * @example\n * Falsy pieces are omitted (`null`, `undefined`, `false`); arrays flatten recursively:\n * ```ts\n * const gen = asyncTemplate`${null}${false}A${['B', ['C']]}`;\n * const out = await Array.fromAsync(gen).then((parts) => parts.join(''));\n * // out === 'ABC'\n * ```\n *\n * @example\n * Stream from an async iterable (e.g. chunked upstream text):\n * ```ts\n * async function* lines() {\n * yield 'line1\\n';\n * yield 'line2\\n';\n * }\n * const gen = asyncTemplate`Header\\n${lines()}Footer`;\n * ```\n *\n * @example\n * Lazy piece via a thunk (invoked as `piece()`):\n * ```ts\n * let n = 0;\n * const gen = asyncTemplate`count=${() => ++n}`;\n * // first consumption yields \"count=1\", etc.\n * ```\n */\nexport async function* asyncTemplate(\n strings: TemplateStringsArray,\n ...pieces: MaybePromise<AsyncTemplatePiece>[]\n) {\n for (let i = 0; i < strings.length; i++) {\n yield strings[i];\n\n if (i < pieces.length) {\n const piece = pieces[i];\n yield* processTemplatePiece(piece);\n }\n }\n}\n\n/**\n * Resolves a template piece into yielded string chunks.\n *\n * Skips output when `value === null || value === undefined || value === false`.\n * Otherwise awaits promises, flattens arrays, streams async iterables, invokes **functions** with no arguments and\n * recurses on the return value, and uses `String(value)` for primitives.\n */\nasync function* processTemplatePiece(\n piece: MaybePromise<AsyncTemplatePiece>,\n): AsyncGenerator<string, void, unknown> {\n if (piece === null || piece === undefined || piece === false) {\n return;\n }\n\n if (piece instanceof Promise) {\n const resolved = await piece;\n yield* processTemplatePiece(resolved);\n return;\n }\n\n if (Array.isArray(piece)) {\n for (const item of piece) {\n yield* processTemplatePiece(item);\n }\n return;\n }\n\n if (\n typeof piece === 'object' &&\n piece !== null &&\n typeof (piece as AsyncIterable<unknown>)[Symbol.asyncIterator] ===\n 'function'\n ) {\n for await (const item of piece as AsyncIterable<AsyncTemplatePiece>) {\n yield* processTemplatePiece(item);\n }\n return;\n }\n\n if (typeof piece === 'function') {\n yield* processTemplatePiece(piece());\n return;\n }\n\n yield String(piece);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,IAAa,SAAS,OAAe,GAAG,WACtC,IAAI,SAAe,SAAS,WAAW;AACrC,KAAI,QAAQ;EACV,MAAM,sBAAsB;AAC1B,gBAAa,QAAQ;AACrB,UAAO,QAAQ,OAAO;;EAExB,MAAM,UAAU,iBAAiB;AAC/B,UAAO,oBAAoB,SAAS,cAAc;AAClD,YAAS;KACR,KAAK;AACR,SAAO,iBAAiB,SAAS,eAAe,EAAE,MAAM,MAAM,CAAC;OAE/D,YAAW,SAAS,KAAK;EAE3B;;;;;;;;AASJ,IAAa,YAAY,OAAO,KAAK,QACnC,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCnD,IAAa,cACX,cACA,gBACG;AACH,KAAI,cAAc,CAAE;CAEpB,MAAM,YACJ,4BAA4B,WAAW,cAAc,YAAY,CAAC;AAEpE,KAAI,YACF,gBAAe,IAAI;KAEnB,MAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BT,SAAgB,oBACd,UACA,WACA,QACA;CACA,IAAI,gBAA+B;CAEnC,MAAM,oBAAoB;AACxB,MAAI,iBAAiB,KACnB;AAEF,eAAa,cAAc;AAC3B,kBAAgB;;AAGlB,SAAQ,iBAAiB,SAAS,aAAa,EAAE,MAAM,MAAM,CAAC;AAE9D,iBAAgB,iBAAiB;AAC/B,UAAQ,oBAAoB,SAAS,YAAY;AACjD,YAAU;IACT,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;AA0Bf,SAAgB,qBACd,UACA,WACA,QACA;CACA,IAAI,QAAuB;CAE3B,MAAM,oBAAoB;AACxB,MAAI,SAAS,KACX;AAEF,gBAAc,MAAM;AACpB,UAAQ;;AAGV,SAAQ,iBAAiB,SAAS,aAAa,EAAE,MAAM,MAAM,CAAC;AAE9D,SAAQ,YAAY,UAAU,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4E1C,gBAAuB,cACrB,SACA,GAAG,QACH;AACA,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,QAAM,QAAQ;AAEd,MAAI,IAAI,OAAO,QAAQ;GACrB,MAAM,QAAQ,OAAO;AACrB,UAAO,qBAAqB,MAAM;;;;;;;;;;;AAYxC,gBAAgB,qBACd,OACuC;AACvC,KAAI,UAAU,QAAQ,UAAU,KAAA,KAAa,UAAU,MACrD;AAGF,KAAI,iBAAiB,SAAS;AAE5B,SAAO,qBADU,MAAM,MACc;AACrC;;AAGF,KAAI,MAAM,QAAQ,MAAM,EAAE;AACxB,OAAK,MAAM,QAAQ,MACjB,QAAO,qBAAqB,KAAK;AAEnC;;AAGF,KACE,OAAO,UAAU,YACjB,UAAU,QACV,OAAQ,MAAiC,OAAO,mBAC9C,YACF;AACA,aAAW,MAAM,QAAQ,MACvB,QAAO,qBAAqB,KAAK;AAEnC;;AAGF,KAAI,OAAO,UAAU,YAAY;AAC/B,SAAO,qBAAqB,OAAO,CAAC;AACpC;;AAGF,OAAM,OAAO,MAAM"}
|