yummies 7.17.0 → 7.19.0
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 +72 -17
- package/async.cjs.map +1 -1
- package/async.d.ts +69 -1
- package/async.js +72 -18
- package/async.js.map +1 -1
- package/data.cjs +14 -0
- package/data.cjs.map +1 -1
- package/data.d.ts +12 -2
- package/data.js +14 -1
- package/data.js.map +1 -1
- package/package.json +1 -1
package/async.cjs
CHANGED
|
@@ -1,23 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
//#region src/async.ts
|
|
3
3
|
/**
|
|
4
|
-
* ---header-docs-section---
|
|
5
|
-
* # yummies/async
|
|
6
|
-
*
|
|
7
|
-
* ## Description
|
|
8
|
-
*
|
|
9
|
-
* Helpers for asynchronous control flow: delays, cancellable waits, scheduling on the next frame,
|
|
10
|
-
* and small utilities around `requestAnimationFrame` and `queueMicrotask`. They complement native
|
|
11
|
-
* `Promise`/`AbortSignal` patterns and keep timing logic easy to test and tree-shake per call site.
|
|
12
|
-
* Import only what you need from `yummies/async` so bundlers can drop unused helpers.
|
|
13
|
-
*
|
|
14
|
-
* ## Usage
|
|
15
|
-
*
|
|
16
|
-
* ```ts
|
|
17
|
-
* import { sleep } from "yummies/async";
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
20
|
-
/**
|
|
21
4
|
* Returns a promise that resolves after `time` milliseconds.
|
|
22
5
|
*
|
|
23
6
|
* When `signal` is passed and becomes aborted before the delay elapses, the promise
|
|
@@ -181,7 +164,79 @@ function setAbortableInterval(callback, delayInMs, signal) {
|
|
|
181
164
|
signal?.addEventListener("abort", handleAbort, { once: true });
|
|
182
165
|
timer = setInterval(callback, delayInMs);
|
|
183
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Tagged template that builds an async iterable of string chunks (like a streaming template engine).
|
|
169
|
+
*
|
|
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, or an async iterable of strings (e.g. another template or a line-by-line source).
|
|
172
|
+
*
|
|
173
|
+
* Interpolation handling matches `processTemplatePiece`: `null`, `undefined`, and `false` add nothing; any other value
|
|
174
|
+
* becomes text via `String(...)`. Collect chunks with `for await...of` or utilities like `Array.fromAsync` where available.
|
|
175
|
+
*
|
|
176
|
+
* @param strings - Cooked template segments from the tag (`strings[0]`, `strings[1]`, …).
|
|
177
|
+
* @param pieces - Values between segments (`pieces[i]` sits between `strings[i]` and `strings[i + 1]`).
|
|
178
|
+
* @returns An async generator yielding string fragments in document order.
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* Plain values and promises:
|
|
182
|
+
* ```ts
|
|
183
|
+
* const gen = asyncTemplate`Hello, ${'world'}! Status: ${Promise.resolve(200)}`;
|
|
184
|
+
* let out = '';
|
|
185
|
+
* for await (const chunk of gen) out += chunk;
|
|
186
|
+
* // out === 'Hello, world! Status: 200'
|
|
187
|
+
* ```
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* Falsy pieces are omitted (`null`, `undefined`, `false`); arrays flatten recursively:
|
|
191
|
+
* ```ts
|
|
192
|
+
* const gen = asyncTemplate`${null}${false}A${['B', ['C']]}`;
|
|
193
|
+
* const out = await Array.fromAsync(gen).then((parts) => parts.join(''));
|
|
194
|
+
* // out === 'ABC'
|
|
195
|
+
* ```
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* Stream from an async iterable (e.g. chunked upstream text):
|
|
199
|
+
* ```ts
|
|
200
|
+
* async function* lines() {
|
|
201
|
+
* yield 'line1\n';
|
|
202
|
+
* yield 'line2\n';
|
|
203
|
+
* }
|
|
204
|
+
* const gen = asyncTemplate`Header\n${lines()}Footer`;
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
async function* asyncTemplate(strings, ...pieces) {
|
|
208
|
+
for (let i = 0; i < strings.length; i++) {
|
|
209
|
+
yield strings[i];
|
|
210
|
+
if (i < pieces.length) {
|
|
211
|
+
const value = pieces[i];
|
|
212
|
+
yield* processTemplatePiece(value);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Resolves a template piece into yielded string chunks.
|
|
218
|
+
*
|
|
219
|
+
* Skips output when `value === null || value === undefined || value === false`.
|
|
220
|
+
* Otherwise awaits promises, flattens arrays, streams async iterables, and uses `String(value)` for primitives.
|
|
221
|
+
*/
|
|
222
|
+
async function* processTemplatePiece(value) {
|
|
223
|
+
if (value === null || value === void 0 || value === false) return;
|
|
224
|
+
if (value instanceof Promise) {
|
|
225
|
+
yield* processTemplatePiece(await value);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (Array.isArray(value)) {
|
|
229
|
+
for (const item of value) yield* processTemplatePiece(item);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (typeof value === "object" && typeof value[Symbol.asyncIterator] === "function") {
|
|
233
|
+
yield* value;
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
yield String(value);
|
|
237
|
+
}
|
|
184
238
|
//#endregion
|
|
239
|
+
exports.asyncTemplate = asyncTemplate;
|
|
185
240
|
exports.endlessRAF = endlessRAF;
|
|
186
241
|
exports.setAbortableInterval = setAbortableInterval;
|
|
187
242
|
exports.setAbortableTimeout = setAbortableTimeout;
|
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\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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,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"}
|
|
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"}
|
package/async.d.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents all primitive types in TypeScript.
|
|
3
|
+
*
|
|
4
|
+
* @returns Union of all primitive types
|
|
5
|
+
*/
|
|
6
|
+
type Primitive = null | undefined | string | number | boolean | symbol | bigint;
|
|
7
|
+
/**
|
|
8
|
+
* Represents a type that can be either the specified type or a Promise resolving to that type.
|
|
9
|
+
*
|
|
10
|
+
* @template T - The type to make possibly a promise
|
|
11
|
+
* @returns T or Promise<T>
|
|
12
|
+
*/
|
|
13
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
14
|
+
|
|
1
15
|
/**
|
|
2
16
|
* ---header-docs-section---
|
|
3
17
|
* # yummies/async
|
|
@@ -15,6 +29,7 @@
|
|
|
15
29
|
* import { sleep } from "yummies/async";
|
|
16
30
|
* ```
|
|
17
31
|
*/
|
|
32
|
+
|
|
18
33
|
/**
|
|
19
34
|
* Returns a promise that resolves after `time` milliseconds.
|
|
20
35
|
*
|
|
@@ -141,5 +156,58 @@ declare function setAbortableTimeout(callback: VoidFunction, delayInMs?: number,
|
|
|
141
156
|
* ```
|
|
142
157
|
*/
|
|
143
158
|
declare function setAbortableInterval(callback: VoidFunction, delayInMs?: number, signal?: AbortSignal): void;
|
|
159
|
+
/**
|
|
160
|
+
* A single interpolated segment in {@link asyncTemplate}.
|
|
161
|
+
*
|
|
162
|
+
* - **Primitives** use `String(value)` for output, except that values matching
|
|
163
|
+
* `value === null || value === undefined || value === false` yield no text (early return in `processTemplatePiece`).
|
|
164
|
+
* Other falsy values such as `0` or `''` are still stringified.
|
|
165
|
+
* - **Arrays** are flattened recursively in order.
|
|
166
|
+
* - **Promises** are awaited; the resolved value is processed the same way.
|
|
167
|
+
* - **Async iterables of strings** are streamed in order (`yield*`).
|
|
168
|
+
*/
|
|
169
|
+
type AsyncTemplatePiece = Primitive | void | AsyncIterable<string> | AsyncTemplatePiece[];
|
|
170
|
+
/**
|
|
171
|
+
* Tagged template that builds an async iterable of string chunks (like a streaming template engine).
|
|
172
|
+
*
|
|
173
|
+
* 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, or an async iterable of strings (e.g. another template or a line-by-line source).
|
|
175
|
+
*
|
|
176
|
+
* Interpolation handling matches `processTemplatePiece`: `null`, `undefined`, and `false` add nothing; any other value
|
|
177
|
+
* becomes text via `String(...)`. Collect chunks with `for await...of` or utilities like `Array.fromAsync` where available.
|
|
178
|
+
*
|
|
179
|
+
* @param strings - Cooked template segments from the tag (`strings[0]`, `strings[1]`, …).
|
|
180
|
+
* @param pieces - Values between segments (`pieces[i]` sits between `strings[i]` and `strings[i + 1]`).
|
|
181
|
+
* @returns An async generator yielding string fragments in document order.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* Plain values and promises:
|
|
185
|
+
* ```ts
|
|
186
|
+
* const gen = asyncTemplate`Hello, ${'world'}! Status: ${Promise.resolve(200)}`;
|
|
187
|
+
* let out = '';
|
|
188
|
+
* for await (const chunk of gen) out += chunk;
|
|
189
|
+
* // out === 'Hello, world! Status: 200'
|
|
190
|
+
* ```
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* Falsy pieces are omitted (`null`, `undefined`, `false`); arrays flatten recursively:
|
|
194
|
+
* ```ts
|
|
195
|
+
* const gen = asyncTemplate`${null}${false}A${['B', ['C']]}`;
|
|
196
|
+
* const out = await Array.fromAsync(gen).then((parts) => parts.join(''));
|
|
197
|
+
* // out === 'ABC'
|
|
198
|
+
* ```
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* Stream from an async iterable (e.g. chunked upstream text):
|
|
202
|
+
* ```ts
|
|
203
|
+
* async function* lines() {
|
|
204
|
+
* yield 'line1\n';
|
|
205
|
+
* yield 'line2\n';
|
|
206
|
+
* }
|
|
207
|
+
* const gen = asyncTemplate`Header\n${lines()}Footer`;
|
|
208
|
+
* ```
|
|
209
|
+
*/
|
|
210
|
+
declare function asyncTemplate(strings: TemplateStringsArray, ...pieces: MaybePromise<AsyncTemplatePiece>[]): AsyncGenerator<string, void, unknown>;
|
|
144
211
|
|
|
145
|
-
export { endlessRAF, setAbortableInterval, setAbortableTimeout, sleep, waitAsync };
|
|
212
|
+
export { asyncTemplate, endlessRAF, setAbortableInterval, setAbortableTimeout, sleep, waitAsync };
|
|
213
|
+
export type { AsyncTemplatePiece };
|
package/async.js
CHANGED
|
@@ -1,22 +1,5 @@
|
|
|
1
1
|
//#region src/async.ts
|
|
2
2
|
/**
|
|
3
|
-
* ---header-docs-section---
|
|
4
|
-
* # yummies/async
|
|
5
|
-
*
|
|
6
|
-
* ## Description
|
|
7
|
-
*
|
|
8
|
-
* Helpers for asynchronous control flow: delays, cancellable waits, scheduling on the next frame,
|
|
9
|
-
* and small utilities around `requestAnimationFrame` and `queueMicrotask`. They complement native
|
|
10
|
-
* `Promise`/`AbortSignal` patterns and keep timing logic easy to test and tree-shake per call site.
|
|
11
|
-
* Import only what you need from `yummies/async` so bundlers can drop unused helpers.
|
|
12
|
-
*
|
|
13
|
-
* ## Usage
|
|
14
|
-
*
|
|
15
|
-
* ```ts
|
|
16
|
-
* import { sleep } from "yummies/async";
|
|
17
|
-
* ```
|
|
18
|
-
*/
|
|
19
|
-
/**
|
|
20
3
|
* Returns a promise that resolves after `time` milliseconds.
|
|
21
4
|
*
|
|
22
5
|
* When `signal` is passed and becomes aborted before the delay elapses, the promise
|
|
@@ -180,7 +163,78 @@ function setAbortableInterval(callback, delayInMs, signal) {
|
|
|
180
163
|
signal?.addEventListener("abort", handleAbort, { once: true });
|
|
181
164
|
timer = setInterval(callback, delayInMs);
|
|
182
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Tagged template that builds an async iterable of string chunks (like a streaming template engine).
|
|
168
|
+
*
|
|
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, or an async iterable of strings (e.g. another template or a line-by-line source).
|
|
171
|
+
*
|
|
172
|
+
* Interpolation handling matches `processTemplatePiece`: `null`, `undefined`, and `false` add nothing; any other value
|
|
173
|
+
* becomes text via `String(...)`. Collect chunks with `for await...of` or utilities like `Array.fromAsync` where available.
|
|
174
|
+
*
|
|
175
|
+
* @param strings - Cooked template segments from the tag (`strings[0]`, `strings[1]`, …).
|
|
176
|
+
* @param pieces - Values between segments (`pieces[i]` sits between `strings[i]` and `strings[i + 1]`).
|
|
177
|
+
* @returns An async generator yielding string fragments in document order.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* Plain values and promises:
|
|
181
|
+
* ```ts
|
|
182
|
+
* const gen = asyncTemplate`Hello, ${'world'}! Status: ${Promise.resolve(200)}`;
|
|
183
|
+
* let out = '';
|
|
184
|
+
* for await (const chunk of gen) out += chunk;
|
|
185
|
+
* // out === 'Hello, world! Status: 200'
|
|
186
|
+
* ```
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* Falsy pieces are omitted (`null`, `undefined`, `false`); arrays flatten recursively:
|
|
190
|
+
* ```ts
|
|
191
|
+
* const gen = asyncTemplate`${null}${false}A${['B', ['C']]}`;
|
|
192
|
+
* const out = await Array.fromAsync(gen).then((parts) => parts.join(''));
|
|
193
|
+
* // out === 'ABC'
|
|
194
|
+
* ```
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* Stream from an async iterable (e.g. chunked upstream text):
|
|
198
|
+
* ```ts
|
|
199
|
+
* async function* lines() {
|
|
200
|
+
* yield 'line1\n';
|
|
201
|
+
* yield 'line2\n';
|
|
202
|
+
* }
|
|
203
|
+
* const gen = asyncTemplate`Header\n${lines()}Footer`;
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
async function* asyncTemplate(strings, ...pieces) {
|
|
207
|
+
for (let i = 0; i < strings.length; i++) {
|
|
208
|
+
yield strings[i];
|
|
209
|
+
if (i < pieces.length) {
|
|
210
|
+
const value = pieces[i];
|
|
211
|
+
yield* processTemplatePiece(value);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Resolves a template piece into yielded string chunks.
|
|
217
|
+
*
|
|
218
|
+
* Skips output when `value === null || value === undefined || value === false`.
|
|
219
|
+
* Otherwise awaits promises, flattens arrays, streams async iterables, and uses `String(value)` for primitives.
|
|
220
|
+
*/
|
|
221
|
+
async function* processTemplatePiece(value) {
|
|
222
|
+
if (value === null || value === void 0 || value === false) return;
|
|
223
|
+
if (value instanceof Promise) {
|
|
224
|
+
yield* processTemplatePiece(await value);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (Array.isArray(value)) {
|
|
228
|
+
for (const item of value) yield* processTemplatePiece(item);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (typeof value === "object" && typeof value[Symbol.asyncIterator] === "function") {
|
|
232
|
+
yield* value;
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
yield String(value);
|
|
236
|
+
}
|
|
183
237
|
//#endregion
|
|
184
|
-
export { endlessRAF, setAbortableInterval, setAbortableTimeout, sleep, waitAsync };
|
|
238
|
+
export { asyncTemplate, endlessRAF, setAbortableInterval, setAbortableTimeout, sleep, waitAsync };
|
|
185
239
|
|
|
186
240
|
//# sourceMappingURL=async.js.map
|
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\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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,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"}
|
|
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"}
|
package/data.cjs
CHANGED
|
@@ -31,6 +31,19 @@ var isShallowEqual = (a, b) => {
|
|
|
31
31
|
return true;
|
|
32
32
|
};
|
|
33
33
|
/**
|
|
34
|
+
* Checks whether an object has at least one enumerable key.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* hasEnumerableKeys({ id: 1 }); // true
|
|
39
|
+
* hasEnumerableKeys({}); // false
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
var hasEnumerableKeys = (input) => {
|
|
43
|
+
for (const _key in input) return true;
|
|
44
|
+
return false;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
34
47
|
* Wraps a value in an array when it is not already an array.
|
|
35
48
|
*
|
|
36
49
|
* @example
|
|
@@ -92,6 +105,7 @@ var UNSAFE_PROPERTY_KEYS = new Set([
|
|
|
92
105
|
var isUnsafeProperty = (key) => UNSAFE_PROPERTY_KEYS.has(key);
|
|
93
106
|
//#endregion
|
|
94
107
|
exports.flatMapDeep = flatMapDeep;
|
|
108
|
+
exports.hasEnumerableKeys = hasEnumerableKeys;
|
|
95
109
|
exports.isShallowEqual = isShallowEqual;
|
|
96
110
|
exports.isUnsafeProperty = isUnsafeProperty;
|
|
97
111
|
exports.safeJsonParse = safeJsonParse;
|
package/data.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data.cjs","names":[],"sources":["../src/data.ts"],"sourcesContent":["/**\n * ---header-docs-section---\n * # yummies/data\n *\n * ## Description\n *\n * General-purpose data helpers: shallow equality, normalizing values to arrays, and recursive\n * `flatMap`-style transforms. They complement `Array`/`Object` builtins when you need stable\n * comparisons for memoization or small structural utilities without pulling a large lodash-style\n * dependency into the bundle.\n *\n * ## Usage\n *\n * ```ts\n * import { isShallowEqual, toArray } from \"yummies/data\";\n * ```\n */\n\nimport type { AnyObject, Maybe } from 'yummies/types';\n\n/**\n * Performs a shallow comparison for arrays, plain objects, dates and regular expressions.\n *\n * @example\n * ```ts\n * isShallowEqual({ id: 1 }, { id: 1 }); // true\n * ```\n */\nexport const isShallowEqual = (a: unknown, b: unknown): boolean => {\n if (a === b) return true;\n\n if (\n typeof a !== 'object' ||\n typeof b !== 'object' ||\n a === null ||\n b === null\n ) {\n return false;\n }\n\n if (a.constructor !== b.constructor) return false;\n\n const isArrayA = Array.isArray(a);\n\n if (isArrayA !== Array.isArray(b)) return false;\n\n if (isArrayA) {\n const arrA = a as unknown[];\n const arrB = b as unknown[];\n if (arrA.length !== arrB.length) return false;\n\n for (const [i, element] of arrA.entries()) {\n if (element !== arrB[i]) return false;\n }\n return true;\n }\n\n if (a instanceof Date) return a.getTime() === (b as Date).getTime();\n\n if (a instanceof RegExp) return a.toString() === (b as RegExp).toString();\n\n const aKeys = Object.keys(a);\n const bKeys = Object.keys(b);\n\n if (aKeys.length !== bKeys.length) return false;\n\n const bObj = b as AnyObject;\n for (const key of aKeys) {\n if (!Object.hasOwn(bObj, key) || (a as AnyObject)[key] !== bObj[key]) {\n return false;\n }\n }\n\n return true;\n};\n\n/**\n * Wraps a value in an array when it is not already an array.\n *\n * @example\n * ```ts\n * toArray('item'); // ['item']\n * ```\n */\nexport const toArray = <TValue>(value: TValue | TValue[]): TValue[] => {\n return Array.isArray(value) ? value : [value];\n};\n\ntype DeepArray<TValue> = TValue | Array<DeepArray<TValue>>;\n\n/**\n * Recursively flattens a nested array and maps the collected values.\n *\n * @example\n * ```ts\n * flatMapDeep([1, [2, [3]]], (value) => value * 2); // [2, 4, 6]\n * ```\n */\nexport const flatMapDeep = <TSource, TNewValue>(\n arr: DeepArray<TSource>,\n fn: (value: TSource, i: number, arr: TSource[]) => TNewValue,\n): TNewValue[] => {\n const source: TSource[] = [];\n\n const collect = (value: DeepArray<TSource>): void => {\n if (!Array.isArray(value)) {\n source.push(value);\n return;\n }\n\n for (const item of value) {\n collect(item);\n }\n };\n\n collect(arr);\n\n return source.map((value, i) => fn(value, i, source));\n};\n\n/**\n * Parses JSON safely and returns a fallback value when parsing fails.\n *\n * @example\n * ```ts\n * safeJsonParse('{\"enabled\":true}', {}); // { enabled: true }\n * ```\n */\nexport const safeJsonParse = <TValue = any, TFallback = null>(\n json: Maybe<string>,\n fallback: TFallback = null as TFallback,\n): TValue | TFallback => {\n if (json == null) return fallback;\n\n try {\n return JSON.parse(json);\n } catch {\n return fallback;\n }\n};\n\nconst UNSAFE_PROPERTY_KEYS = new Set(['__proto__', 'prototype', 'constructor']);\n\n/**\n * Checks whether a property key is unsafe and can lead to prototype pollution.\n *\n * @example\n * isUnsafeProperty('__proto__'); // true\n * isUnsafeProperty('name'); // false\n */\nexport const isUnsafeProperty = (key: any) => UNSAFE_PROPERTY_KEYS.has(key);\n"],"mappings":";;;;;;;;;;AA4BA,IAAa,kBAAkB,GAAY,MAAwB;AACjE,KAAI,MAAM,EAAG,QAAO;AAEpB,KACE,OAAO,MAAM,YACb,OAAO,MAAM,YACb,MAAM,QACN,MAAM,KAEN,QAAO;AAGT,KAAI,EAAE,gBAAgB,EAAE,YAAa,QAAO;CAE5C,MAAM,WAAW,MAAM,QAAQ,EAAE;AAEjC,KAAI,aAAa,MAAM,QAAQ,EAAE,CAAE,QAAO;AAE1C,KAAI,UAAU;EACZ,MAAM,OAAO;EACb,MAAM,OAAO;AACb,MAAI,KAAK,WAAW,KAAK,OAAQ,QAAO;AAExC,OAAK,MAAM,CAAC,GAAG,YAAY,KAAK,SAAS,CACvC,KAAI,YAAY,KAAK,GAAI,QAAO;AAElC,SAAO;;AAGT,KAAI,aAAa,KAAM,QAAO,EAAE,SAAS,KAAM,EAAW,SAAS;AAEnE,KAAI,aAAa,OAAQ,QAAO,EAAE,UAAU,KAAM,EAAa,UAAU;CAEzE,MAAM,QAAQ,OAAO,KAAK,EAAE;CAC5B,MAAM,QAAQ,OAAO,KAAK,EAAE;AAE5B,KAAI,MAAM,WAAW,MAAM,OAAQ,QAAO;CAE1C,MAAM,OAAO;AACb,MAAK,MAAM,OAAO,MAChB,KAAI,CAAC,OAAO,OAAO,MAAM,IAAI,IAAK,EAAgB,SAAS,KAAK,KAC9D,QAAO;AAIX,QAAO;;;;;;;;;;AAWT,IAAa,WAAmB,UAAuC;AACrE,QAAO,MAAM,QAAQ,MAAM,GAAG,QAAQ,CAAC,MAAM;;;;;;;;;;AAa/C,IAAa,eACX,KACA,OACgB;CAChB,MAAM,SAAoB,EAAE;CAE5B,MAAM,WAAW,UAAoC;AACnD,MAAI,CAAC,MAAM,QAAQ,MAAM,EAAE;AACzB,UAAO,KAAK,MAAM;AAClB;;AAGF,OAAK,MAAM,QAAQ,MACjB,SAAQ,KAAK;;AAIjB,SAAQ,IAAI;AAEZ,QAAO,OAAO,KAAK,OAAO,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;;;;;;;;;;AAWvD,IAAa,iBACX,MACA,WAAsB,SACC;AACvB,KAAI,QAAQ,KAAM,QAAO;AAEzB,KAAI;AACF,SAAO,KAAK,MAAM,KAAK;SACjB;AACN,SAAO;;;AAIX,IAAM,uBAAuB,IAAI,IAAI;CAAC;CAAa;CAAa;CAAc,CAAC;;;;;;;;AAS/E,IAAa,oBAAoB,QAAa,qBAAqB,IAAI,IAAI"}
|
|
1
|
+
{"version":3,"file":"data.cjs","names":[],"sources":["../src/data.ts"],"sourcesContent":["/**\n * ---header-docs-section---\n * # yummies/data\n *\n * ## Description\n *\n * General-purpose data helpers: shallow equality, normalizing values to arrays, and recursive\n * `flatMap`-style transforms. They complement `Array`/`Object` builtins when you need stable\n * comparisons for memoization or small structural utilities without pulling a large lodash-style\n * dependency into the bundle.\n *\n * ## Usage\n *\n * ```ts\n * import { isShallowEqual, toArray } from \"yummies/data\";\n * ```\n */\n\nimport type { AnyObject, Maybe } from 'yummies/types';\n\n/**\n * Performs a shallow comparison for arrays, plain objects, dates and regular expressions.\n *\n * @example\n * ```ts\n * isShallowEqual({ id: 1 }, { id: 1 }); // true\n * ```\n */\nexport const isShallowEqual = (a: unknown, b: unknown): boolean => {\n if (a === b) return true;\n\n if (\n typeof a !== 'object' ||\n typeof b !== 'object' ||\n a === null ||\n b === null\n ) {\n return false;\n }\n\n if (a.constructor !== b.constructor) return false;\n\n const isArrayA = Array.isArray(a);\n\n if (isArrayA !== Array.isArray(b)) return false;\n\n if (isArrayA) {\n const arrA = a as unknown[];\n const arrB = b as unknown[];\n if (arrA.length !== arrB.length) return false;\n\n for (const [i, element] of arrA.entries()) {\n if (element !== arrB[i]) return false;\n }\n return true;\n }\n\n if (a instanceof Date) return a.getTime() === (b as Date).getTime();\n\n if (a instanceof RegExp) return a.toString() === (b as RegExp).toString();\n\n const aKeys = Object.keys(a);\n const bKeys = Object.keys(b);\n\n if (aKeys.length !== bKeys.length) return false;\n\n const bObj = b as AnyObject;\n for (const key of aKeys) {\n if (!Object.hasOwn(bObj, key) || (a as AnyObject)[key] !== bObj[key]) {\n return false;\n }\n }\n\n return true;\n};\n\n/**\n * Checks whether an object has at least one enumerable key.\n *\n * @example\n * ```ts\n * hasEnumerableKeys({ id: 1 }); // true\n * hasEnumerableKeys({}); // false\n * ```\n */\nexport const hasEnumerableKeys = (input: AnyObject): boolean => {\n for (const _key in input) {\n return true;\n }\n return false;\n};\n\n/**\n * Wraps a value in an array when it is not already an array.\n *\n * @example\n * ```ts\n * toArray('item'); // ['item']\n * ```\n */\nexport const toArray = <TValue>(value: TValue | TValue[]): TValue[] => {\n return Array.isArray(value) ? value : [value];\n};\n\ntype DeepArray<TValue> = TValue | Array<DeepArray<TValue>>;\n\n/**\n * Recursively flattens a nested array and maps the collected values.\n *\n * @example\n * ```ts\n * flatMapDeep([1, [2, [3]]], (value) => value * 2); // [2, 4, 6]\n * ```\n */\nexport const flatMapDeep = <TSource, TNewValue>(\n arr: DeepArray<TSource>,\n fn: (value: TSource, i: number, arr: TSource[]) => TNewValue,\n): TNewValue[] => {\n const source: TSource[] = [];\n\n const collect = (value: DeepArray<TSource>): void => {\n if (!Array.isArray(value)) {\n source.push(value);\n return;\n }\n\n for (const item of value) {\n collect(item);\n }\n };\n\n collect(arr);\n\n return source.map((value, i) => fn(value, i, source));\n};\n\n/**\n * Parses JSON safely and returns a fallback value when parsing fails.\n *\n * @example\n * ```ts\n * safeJsonParse('{\"enabled\":true}', {}); // { enabled: true }\n * ```\n */\nexport const safeJsonParse = <TValue = any, TFallback = null>(\n json: Maybe<string>,\n fallback: TFallback = null as TFallback,\n): TValue | TFallback => {\n if (json == null) return fallback;\n\n try {\n return JSON.parse(json);\n } catch {\n return fallback;\n }\n};\n\nconst UNSAFE_PROPERTY_KEYS = new Set(['__proto__', 'prototype', 'constructor']);\n\n/**\n * Checks whether a property key is unsafe and can lead to prototype pollution.\n *\n * @example\n * isUnsafeProperty('__proto__'); // true\n * isUnsafeProperty('name'); // false\n */\nexport const isUnsafeProperty = (key: any) => UNSAFE_PROPERTY_KEYS.has(key);\n"],"mappings":";;;;;;;;;;AA4BA,IAAa,kBAAkB,GAAY,MAAwB;AACjE,KAAI,MAAM,EAAG,QAAO;AAEpB,KACE,OAAO,MAAM,YACb,OAAO,MAAM,YACb,MAAM,QACN,MAAM,KAEN,QAAO;AAGT,KAAI,EAAE,gBAAgB,EAAE,YAAa,QAAO;CAE5C,MAAM,WAAW,MAAM,QAAQ,EAAE;AAEjC,KAAI,aAAa,MAAM,QAAQ,EAAE,CAAE,QAAO;AAE1C,KAAI,UAAU;EACZ,MAAM,OAAO;EACb,MAAM,OAAO;AACb,MAAI,KAAK,WAAW,KAAK,OAAQ,QAAO;AAExC,OAAK,MAAM,CAAC,GAAG,YAAY,KAAK,SAAS,CACvC,KAAI,YAAY,KAAK,GAAI,QAAO;AAElC,SAAO;;AAGT,KAAI,aAAa,KAAM,QAAO,EAAE,SAAS,KAAM,EAAW,SAAS;AAEnE,KAAI,aAAa,OAAQ,QAAO,EAAE,UAAU,KAAM,EAAa,UAAU;CAEzE,MAAM,QAAQ,OAAO,KAAK,EAAE;CAC5B,MAAM,QAAQ,OAAO,KAAK,EAAE;AAE5B,KAAI,MAAM,WAAW,MAAM,OAAQ,QAAO;CAE1C,MAAM,OAAO;AACb,MAAK,MAAM,OAAO,MAChB,KAAI,CAAC,OAAO,OAAO,MAAM,IAAI,IAAK,EAAgB,SAAS,KAAK,KAC9D,QAAO;AAIX,QAAO;;;;;;;;;;;AAYT,IAAa,qBAAqB,UAA8B;AAC9D,MAAK,MAAM,QAAQ,MACjB,QAAO;AAET,QAAO;;;;;;;;;;AAWT,IAAa,WAAmB,UAAuC;AACrE,QAAO,MAAM,QAAQ,MAAM,GAAG,QAAQ,CAAC,MAAM;;;;;;;;;;AAa/C,IAAa,eACX,KACA,OACgB;CAChB,MAAM,SAAoB,EAAE;CAE5B,MAAM,WAAW,UAAoC;AACnD,MAAI,CAAC,MAAM,QAAQ,MAAM,EAAE;AACzB,UAAO,KAAK,MAAM;AAClB;;AAGF,OAAK,MAAM,QAAQ,MACjB,SAAQ,KAAK;;AAIjB,SAAQ,IAAI;AAEZ,QAAO,OAAO,KAAK,OAAO,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;;;;;;;;;;AAWvD,IAAa,iBACX,MACA,WAAsB,SACC;AACvB,KAAI,QAAQ,KAAM,QAAO;AAEzB,KAAI;AACF,SAAO,KAAK,MAAM,KAAK;SACjB;AACN,SAAO;;;AAIX,IAAM,uBAAuB,IAAI,IAAI;CAAC;CAAa;CAAa;CAAc,CAAC;;;;;;;;AAS/E,IAAa,oBAAoB,QAAa,qBAAqB,IAAI,IAAI"}
|
package/data.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Maybe } from 'yummies/types';
|
|
1
|
+
import { AnyObject, Maybe } from 'yummies/types';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* ---header-docs-section---
|
|
@@ -27,6 +27,16 @@ import { Maybe } from 'yummies/types';
|
|
|
27
27
|
* ```
|
|
28
28
|
*/
|
|
29
29
|
declare const isShallowEqual: (a: unknown, b: unknown) => boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Checks whether an object has at least one enumerable key.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* hasEnumerableKeys({ id: 1 }); // true
|
|
36
|
+
* hasEnumerableKeys({}); // false
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
declare const hasEnumerableKeys: (input: AnyObject) => boolean;
|
|
30
40
|
/**
|
|
31
41
|
* Wraps a value in an array when it is not already an array.
|
|
32
42
|
*
|
|
@@ -64,4 +74,4 @@ declare const safeJsonParse: <TValue = any, TFallback = null>(json: Maybe<string
|
|
|
64
74
|
*/
|
|
65
75
|
declare const isUnsafeProperty: (key: any) => boolean;
|
|
66
76
|
|
|
67
|
-
export { flatMapDeep, isShallowEqual, isUnsafeProperty, safeJsonParse, toArray };
|
|
77
|
+
export { flatMapDeep, hasEnumerableKeys, isShallowEqual, isUnsafeProperty, safeJsonParse, toArray };
|
package/data.js
CHANGED
|
@@ -30,6 +30,19 @@ var isShallowEqual = (a, b) => {
|
|
|
30
30
|
return true;
|
|
31
31
|
};
|
|
32
32
|
/**
|
|
33
|
+
* Checks whether an object has at least one enumerable key.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* hasEnumerableKeys({ id: 1 }); // true
|
|
38
|
+
* hasEnumerableKeys({}); // false
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
var hasEnumerableKeys = (input) => {
|
|
42
|
+
for (const _key in input) return true;
|
|
43
|
+
return false;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
33
46
|
* Wraps a value in an array when it is not already an array.
|
|
34
47
|
*
|
|
35
48
|
* @example
|
|
@@ -90,6 +103,6 @@ var UNSAFE_PROPERTY_KEYS = new Set([
|
|
|
90
103
|
*/
|
|
91
104
|
var isUnsafeProperty = (key) => UNSAFE_PROPERTY_KEYS.has(key);
|
|
92
105
|
//#endregion
|
|
93
|
-
export { flatMapDeep, isShallowEqual, isUnsafeProperty, safeJsonParse, toArray };
|
|
106
|
+
export { flatMapDeep, hasEnumerableKeys, isShallowEqual, isUnsafeProperty, safeJsonParse, toArray };
|
|
94
107
|
|
|
95
108
|
//# sourceMappingURL=data.js.map
|
package/data.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data.js","names":[],"sources":["../src/data.ts"],"sourcesContent":["/**\n * ---header-docs-section---\n * # yummies/data\n *\n * ## Description\n *\n * General-purpose data helpers: shallow equality, normalizing values to arrays, and recursive\n * `flatMap`-style transforms. They complement `Array`/`Object` builtins when you need stable\n * comparisons for memoization or small structural utilities without pulling a large lodash-style\n * dependency into the bundle.\n *\n * ## Usage\n *\n * ```ts\n * import { isShallowEqual, toArray } from \"yummies/data\";\n * ```\n */\n\nimport type { AnyObject, Maybe } from 'yummies/types';\n\n/**\n * Performs a shallow comparison for arrays, plain objects, dates and regular expressions.\n *\n * @example\n * ```ts\n * isShallowEqual({ id: 1 }, { id: 1 }); // true\n * ```\n */\nexport const isShallowEqual = (a: unknown, b: unknown): boolean => {\n if (a === b) return true;\n\n if (\n typeof a !== 'object' ||\n typeof b !== 'object' ||\n a === null ||\n b === null\n ) {\n return false;\n }\n\n if (a.constructor !== b.constructor) return false;\n\n const isArrayA = Array.isArray(a);\n\n if (isArrayA !== Array.isArray(b)) return false;\n\n if (isArrayA) {\n const arrA = a as unknown[];\n const arrB = b as unknown[];\n if (arrA.length !== arrB.length) return false;\n\n for (const [i, element] of arrA.entries()) {\n if (element !== arrB[i]) return false;\n }\n return true;\n }\n\n if (a instanceof Date) return a.getTime() === (b as Date).getTime();\n\n if (a instanceof RegExp) return a.toString() === (b as RegExp).toString();\n\n const aKeys = Object.keys(a);\n const bKeys = Object.keys(b);\n\n if (aKeys.length !== bKeys.length) return false;\n\n const bObj = b as AnyObject;\n for (const key of aKeys) {\n if (!Object.hasOwn(bObj, key) || (a as AnyObject)[key] !== bObj[key]) {\n return false;\n }\n }\n\n return true;\n};\n\n/**\n * Wraps a value in an array when it is not already an array.\n *\n * @example\n * ```ts\n * toArray('item'); // ['item']\n * ```\n */\nexport const toArray = <TValue>(value: TValue | TValue[]): TValue[] => {\n return Array.isArray(value) ? value : [value];\n};\n\ntype DeepArray<TValue> = TValue | Array<DeepArray<TValue>>;\n\n/**\n * Recursively flattens a nested array and maps the collected values.\n *\n * @example\n * ```ts\n * flatMapDeep([1, [2, [3]]], (value) => value * 2); // [2, 4, 6]\n * ```\n */\nexport const flatMapDeep = <TSource, TNewValue>(\n arr: DeepArray<TSource>,\n fn: (value: TSource, i: number, arr: TSource[]) => TNewValue,\n): TNewValue[] => {\n const source: TSource[] = [];\n\n const collect = (value: DeepArray<TSource>): void => {\n if (!Array.isArray(value)) {\n source.push(value);\n return;\n }\n\n for (const item of value) {\n collect(item);\n }\n };\n\n collect(arr);\n\n return source.map((value, i) => fn(value, i, source));\n};\n\n/**\n * Parses JSON safely and returns a fallback value when parsing fails.\n *\n * @example\n * ```ts\n * safeJsonParse('{\"enabled\":true}', {}); // { enabled: true }\n * ```\n */\nexport const safeJsonParse = <TValue = any, TFallback = null>(\n json: Maybe<string>,\n fallback: TFallback = null as TFallback,\n): TValue | TFallback => {\n if (json == null) return fallback;\n\n try {\n return JSON.parse(json);\n } catch {\n return fallback;\n }\n};\n\nconst UNSAFE_PROPERTY_KEYS = new Set(['__proto__', 'prototype', 'constructor']);\n\n/**\n * Checks whether a property key is unsafe and can lead to prototype pollution.\n *\n * @example\n * isUnsafeProperty('__proto__'); // true\n * isUnsafeProperty('name'); // false\n */\nexport const isUnsafeProperty = (key: any) => UNSAFE_PROPERTY_KEYS.has(key);\n"],"mappings":";;;;;;;;;AA4BA,IAAa,kBAAkB,GAAY,MAAwB;AACjE,KAAI,MAAM,EAAG,QAAO;AAEpB,KACE,OAAO,MAAM,YACb,OAAO,MAAM,YACb,MAAM,QACN,MAAM,KAEN,QAAO;AAGT,KAAI,EAAE,gBAAgB,EAAE,YAAa,QAAO;CAE5C,MAAM,WAAW,MAAM,QAAQ,EAAE;AAEjC,KAAI,aAAa,MAAM,QAAQ,EAAE,CAAE,QAAO;AAE1C,KAAI,UAAU;EACZ,MAAM,OAAO;EACb,MAAM,OAAO;AACb,MAAI,KAAK,WAAW,KAAK,OAAQ,QAAO;AAExC,OAAK,MAAM,CAAC,GAAG,YAAY,KAAK,SAAS,CACvC,KAAI,YAAY,KAAK,GAAI,QAAO;AAElC,SAAO;;AAGT,KAAI,aAAa,KAAM,QAAO,EAAE,SAAS,KAAM,EAAW,SAAS;AAEnE,KAAI,aAAa,OAAQ,QAAO,EAAE,UAAU,KAAM,EAAa,UAAU;CAEzE,MAAM,QAAQ,OAAO,KAAK,EAAE;CAC5B,MAAM,QAAQ,OAAO,KAAK,EAAE;AAE5B,KAAI,MAAM,WAAW,MAAM,OAAQ,QAAO;CAE1C,MAAM,OAAO;AACb,MAAK,MAAM,OAAO,MAChB,KAAI,CAAC,OAAO,OAAO,MAAM,IAAI,IAAK,EAAgB,SAAS,KAAK,KAC9D,QAAO;AAIX,QAAO;;;;;;;;;;AAWT,IAAa,WAAmB,UAAuC;AACrE,QAAO,MAAM,QAAQ,MAAM,GAAG,QAAQ,CAAC,MAAM;;;;;;;;;;AAa/C,IAAa,eACX,KACA,OACgB;CAChB,MAAM,SAAoB,EAAE;CAE5B,MAAM,WAAW,UAAoC;AACnD,MAAI,CAAC,MAAM,QAAQ,MAAM,EAAE;AACzB,UAAO,KAAK,MAAM;AAClB;;AAGF,OAAK,MAAM,QAAQ,MACjB,SAAQ,KAAK;;AAIjB,SAAQ,IAAI;AAEZ,QAAO,OAAO,KAAK,OAAO,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;;;;;;;;;;AAWvD,IAAa,iBACX,MACA,WAAsB,SACC;AACvB,KAAI,QAAQ,KAAM,QAAO;AAEzB,KAAI;AACF,SAAO,KAAK,MAAM,KAAK;SACjB;AACN,SAAO;;;AAIX,IAAM,uBAAuB,IAAI,IAAI;CAAC;CAAa;CAAa;CAAc,CAAC;;;;;;;;AAS/E,IAAa,oBAAoB,QAAa,qBAAqB,IAAI,IAAI"}
|
|
1
|
+
{"version":3,"file":"data.js","names":[],"sources":["../src/data.ts"],"sourcesContent":["/**\n * ---header-docs-section---\n * # yummies/data\n *\n * ## Description\n *\n * General-purpose data helpers: shallow equality, normalizing values to arrays, and recursive\n * `flatMap`-style transforms. They complement `Array`/`Object` builtins when you need stable\n * comparisons for memoization or small structural utilities without pulling a large lodash-style\n * dependency into the bundle.\n *\n * ## Usage\n *\n * ```ts\n * import { isShallowEqual, toArray } from \"yummies/data\";\n * ```\n */\n\nimport type { AnyObject, Maybe } from 'yummies/types';\n\n/**\n * Performs a shallow comparison for arrays, plain objects, dates and regular expressions.\n *\n * @example\n * ```ts\n * isShallowEqual({ id: 1 }, { id: 1 }); // true\n * ```\n */\nexport const isShallowEqual = (a: unknown, b: unknown): boolean => {\n if (a === b) return true;\n\n if (\n typeof a !== 'object' ||\n typeof b !== 'object' ||\n a === null ||\n b === null\n ) {\n return false;\n }\n\n if (a.constructor !== b.constructor) return false;\n\n const isArrayA = Array.isArray(a);\n\n if (isArrayA !== Array.isArray(b)) return false;\n\n if (isArrayA) {\n const arrA = a as unknown[];\n const arrB = b as unknown[];\n if (arrA.length !== arrB.length) return false;\n\n for (const [i, element] of arrA.entries()) {\n if (element !== arrB[i]) return false;\n }\n return true;\n }\n\n if (a instanceof Date) return a.getTime() === (b as Date).getTime();\n\n if (a instanceof RegExp) return a.toString() === (b as RegExp).toString();\n\n const aKeys = Object.keys(a);\n const bKeys = Object.keys(b);\n\n if (aKeys.length !== bKeys.length) return false;\n\n const bObj = b as AnyObject;\n for (const key of aKeys) {\n if (!Object.hasOwn(bObj, key) || (a as AnyObject)[key] !== bObj[key]) {\n return false;\n }\n }\n\n return true;\n};\n\n/**\n * Checks whether an object has at least one enumerable key.\n *\n * @example\n * ```ts\n * hasEnumerableKeys({ id: 1 }); // true\n * hasEnumerableKeys({}); // false\n * ```\n */\nexport const hasEnumerableKeys = (input: AnyObject): boolean => {\n for (const _key in input) {\n return true;\n }\n return false;\n};\n\n/**\n * Wraps a value in an array when it is not already an array.\n *\n * @example\n * ```ts\n * toArray('item'); // ['item']\n * ```\n */\nexport const toArray = <TValue>(value: TValue | TValue[]): TValue[] => {\n return Array.isArray(value) ? value : [value];\n};\n\ntype DeepArray<TValue> = TValue | Array<DeepArray<TValue>>;\n\n/**\n * Recursively flattens a nested array and maps the collected values.\n *\n * @example\n * ```ts\n * flatMapDeep([1, [2, [3]]], (value) => value * 2); // [2, 4, 6]\n * ```\n */\nexport const flatMapDeep = <TSource, TNewValue>(\n arr: DeepArray<TSource>,\n fn: (value: TSource, i: number, arr: TSource[]) => TNewValue,\n): TNewValue[] => {\n const source: TSource[] = [];\n\n const collect = (value: DeepArray<TSource>): void => {\n if (!Array.isArray(value)) {\n source.push(value);\n return;\n }\n\n for (const item of value) {\n collect(item);\n }\n };\n\n collect(arr);\n\n return source.map((value, i) => fn(value, i, source));\n};\n\n/**\n * Parses JSON safely and returns a fallback value when parsing fails.\n *\n * @example\n * ```ts\n * safeJsonParse('{\"enabled\":true}', {}); // { enabled: true }\n * ```\n */\nexport const safeJsonParse = <TValue = any, TFallback = null>(\n json: Maybe<string>,\n fallback: TFallback = null as TFallback,\n): TValue | TFallback => {\n if (json == null) return fallback;\n\n try {\n return JSON.parse(json);\n } catch {\n return fallback;\n }\n};\n\nconst UNSAFE_PROPERTY_KEYS = new Set(['__proto__', 'prototype', 'constructor']);\n\n/**\n * Checks whether a property key is unsafe and can lead to prototype pollution.\n *\n * @example\n * isUnsafeProperty('__proto__'); // true\n * isUnsafeProperty('name'); // false\n */\nexport const isUnsafeProperty = (key: any) => UNSAFE_PROPERTY_KEYS.has(key);\n"],"mappings":";;;;;;;;;AA4BA,IAAa,kBAAkB,GAAY,MAAwB;AACjE,KAAI,MAAM,EAAG,QAAO;AAEpB,KACE,OAAO,MAAM,YACb,OAAO,MAAM,YACb,MAAM,QACN,MAAM,KAEN,QAAO;AAGT,KAAI,EAAE,gBAAgB,EAAE,YAAa,QAAO;CAE5C,MAAM,WAAW,MAAM,QAAQ,EAAE;AAEjC,KAAI,aAAa,MAAM,QAAQ,EAAE,CAAE,QAAO;AAE1C,KAAI,UAAU;EACZ,MAAM,OAAO;EACb,MAAM,OAAO;AACb,MAAI,KAAK,WAAW,KAAK,OAAQ,QAAO;AAExC,OAAK,MAAM,CAAC,GAAG,YAAY,KAAK,SAAS,CACvC,KAAI,YAAY,KAAK,GAAI,QAAO;AAElC,SAAO;;AAGT,KAAI,aAAa,KAAM,QAAO,EAAE,SAAS,KAAM,EAAW,SAAS;AAEnE,KAAI,aAAa,OAAQ,QAAO,EAAE,UAAU,KAAM,EAAa,UAAU;CAEzE,MAAM,QAAQ,OAAO,KAAK,EAAE;CAC5B,MAAM,QAAQ,OAAO,KAAK,EAAE;AAE5B,KAAI,MAAM,WAAW,MAAM,OAAQ,QAAO;CAE1C,MAAM,OAAO;AACb,MAAK,MAAM,OAAO,MAChB,KAAI,CAAC,OAAO,OAAO,MAAM,IAAI,IAAK,EAAgB,SAAS,KAAK,KAC9D,QAAO;AAIX,QAAO;;;;;;;;;;;AAYT,IAAa,qBAAqB,UAA8B;AAC9D,MAAK,MAAM,QAAQ,MACjB,QAAO;AAET,QAAO;;;;;;;;;;AAWT,IAAa,WAAmB,UAAuC;AACrE,QAAO,MAAM,QAAQ,MAAM,GAAG,QAAQ,CAAC,MAAM;;;;;;;;;;AAa/C,IAAa,eACX,KACA,OACgB;CAChB,MAAM,SAAoB,EAAE;CAE5B,MAAM,WAAW,UAAoC;AACnD,MAAI,CAAC,MAAM,QAAQ,MAAM,EAAE;AACzB,UAAO,KAAK,MAAM;AAClB;;AAGF,OAAK,MAAM,QAAQ,MACjB,SAAQ,KAAK;;AAIjB,SAAQ,IAAI;AAEZ,QAAO,OAAO,KAAK,OAAO,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;;;;;;;;;;AAWvD,IAAa,iBACX,MACA,WAAsB,SACC;AACvB,KAAI,QAAQ,KAAM,QAAO;AAEzB,KAAI;AACF,SAAO,KAAK,MAAM,KAAK;SACjB;AACN,SAAO;;;AAIX,IAAM,uBAAuB,IAAI,IAAI;CAAC;CAAa;CAAa;CAAc,CAAC;;;;;;;;AAS/E,IAAa,oBAAoB,QAAa,qBAAqB,IAAI,IAAI"}
|