react-inp-chunker 0.3.0 → 0.4.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/README.md +31 -11
- package/dist/core/index.cjs +1 -1
- package/dist/core/index.d.cts +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +1 -1
- package/dist/{deferred-task-6oTEnHWa.js → deferred-task-Cai4fF4m.js} +5 -3
- package/dist/deferred-task-Cai4fF4m.js.map +1 -0
- package/dist/{deferred-task-DcnC99_Y.cjs → deferred-task-DRigxydg.cjs} +5 -3
- package/dist/deferred-task-DRigxydg.cjs.map +1 -0
- package/dist/{index-B4gKjLYR.d.ts → index-DuUMqnsA.d.cts} +89 -45
- package/dist/index-DuUMqnsA.d.cts.map +1 -0
- package/dist/{index-B4gKjLYR.d.cts → index-DuUMqnsA.d.ts} +89 -45
- package/dist/index-DuUMqnsA.d.ts.map +1 -0
- package/dist/index.cjs +11 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -11
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +72 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -5
- package/dist/index.js.map +1 -1
- package/dist/production/core/index.cjs +1 -1
- package/dist/production/core/index.js +1 -1
- package/dist/production/{deferred-task-DkHdsliy.js → deferred-task-MsTNINAd.js} +5 -3
- package/dist/production/deferred-task-MsTNINAd.js.map +1 -0
- package/dist/production/{deferred-task-yh5pWlSj.cjs → deferred-task-j8KF7lk7.cjs} +5 -3
- package/dist/production/deferred-task-j8KF7lk7.cjs.map +1 -0
- package/dist/production/index.cjs +11 -5
- package/dist/production/index.cjs.map +1 -1
- package/dist/production/index.js +11 -5
- package/dist/production/index.js.map +1 -1
- package/package.json +6 -3
- package/dist/deferred-task-6oTEnHWa.js.map +0 -1
- package/dist/deferred-task-DcnC99_Y.cjs.map +0 -1
- package/dist/index-B4gKjLYR.d.cts.map +0 -1
- package/dist/index-B4gKjLYR.d.ts.map +0 -1
- package/dist/production/deferred-task-DkHdsliy.js.map +0 -1
- package/dist/production/deferred-task-yh5pWlSj.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ function ScoredRows({ rows }: { rows: readonly Row[] }) {
|
|
|
61
61
|
|
|
62
62
|
if (task.isError) return <p role="alert">Couldn't score the rows.</p>;
|
|
63
63
|
|
|
64
|
-
if (
|
|
64
|
+
if (task.status !== 'done') {
|
|
65
65
|
return <progress value={task.processed} max={task.total} aria-label="Scoring rows" />;
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -83,6 +83,11 @@ item. Your component re-renders with progress once per slice, not once per item.
|
|
|
83
83
|
- `results` is `undefined` until the task settles, and is then set in the same
|
|
84
84
|
render that first reports `done`, `cancelled` or `error` — never a render
|
|
85
85
|
later, so there is nothing to tear under concurrent React.
|
|
86
|
+
- The return type is discriminated on `status`: narrowing it (`task.status ===
|
|
87
|
+
'done'`, or a `switch`) narrows `results`/`error` along with it, so the
|
|
88
|
+
`=== undefined` check above is a choice, not a requirement — the `is*`
|
|
89
|
+
booleans don't narrow on their own, since they're separate fields from
|
|
90
|
+
`status` as far as the type checker is concerned.
|
|
86
91
|
|
|
87
92
|
Two options exist for work at the extremes, and most code needs neither:
|
|
88
93
|
|
|
@@ -93,11 +98,12 @@ Two options exist for work at the extremes, and most code needs neither:
|
|
|
93
98
|
|
|
94
99
|
Three more exist for running this against data you don't fully trust, or in production:
|
|
95
100
|
|
|
96
|
-
| Option | Use it when
|
|
97
|
-
| --------- |
|
|
98
|
-
| `onError` | `processItem` can throw on a bad row. Return `'skip'` to drop that item and keep going (it shows up in `skipped`), or `'abort'` to end the task as an error — the default when you omit this entirely.
|
|
99
|
-
| `onSlice` | You want to verify, on your own real traffic, that this task is actually staying under `timeSliceMs`. Called with `(durationMs, processed)` at every yield — and unlike the dev-only warnings below, it ships in production.
|
|
100
|
-
| `onYield` | Pairs with `onSlice`: called right after each yield resolves and work is about to resume.
|
|
101
|
+
| Option | Use it when |
|
|
102
|
+
| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
103
|
+
| `onError` | `processItem` can throw on a bad row. Return `'skip'` to drop that item and keep going (it shows up in `skipped`), or `'abort'` to end the task as an error — the default when you omit this entirely. |
|
|
104
|
+
| `onSlice` | You want to verify, on your own real traffic, that this task is actually staying under `timeSliceMs`. Called with `(durationMs, processed)` at every yield — and unlike the dev-only warnings below, it ships in production. |
|
|
105
|
+
| `onYield` | Pairs with `onSlice`: called right after each yield resolves and work is about to resume. |
|
|
106
|
+
| `signal` | Something else should be able to cancel this task — a parent component unmounting its own controller, a request timeout. An `AbortSignal` aborting is equivalent to calling `cancel()`, and its `reason` shows up on `task.reason`, distinguishable from a direct `cancel()` call (which leaves `reason` `undefined`). |
|
|
101
107
|
|
|
102
108
|
```ts
|
|
103
109
|
const task = useChunkedTask(rows, importRow, {
|
|
@@ -135,11 +141,22 @@ function* renderFrames(frameCount: number): DeferredGenerator<number> {
|
|
|
135
141
|
}
|
|
136
142
|
|
|
137
143
|
function FrameRenderer({ frameCount }: { frameCount: number }) {
|
|
138
|
-
const
|
|
139
|
-
return
|
|
144
|
+
const task = useDeferredTask(() => renderFrames(frameCount), [frameCount]);
|
|
145
|
+
return task.status === 'done' ? (
|
|
146
|
+
<p>Rendered {task.value} frames.</p>
|
|
147
|
+
) : (
|
|
148
|
+
<p>Rendering…</p>
|
|
149
|
+
);
|
|
140
150
|
}
|
|
141
151
|
```
|
|
142
152
|
|
|
153
|
+
Same options as `useChunkedTask` for `timeSliceMs`, `batchSize`, `autoStart`,
|
|
154
|
+
`onSlice`, `onYield` and `signal` (no `onError`: skipping one step of a
|
|
155
|
+
stateful generator mid-flight has no well-defined meaning the way skipping an
|
|
156
|
+
independent array item does). The return type is discriminated the same way:
|
|
157
|
+
narrowing `status === 'done'` narrows `value` to defined, even when the
|
|
158
|
+
generator's return type itself allows `undefined`.
|
|
159
|
+
|
|
143
160
|
### Outside React: `react-inp-chunker/core`
|
|
144
161
|
|
|
145
162
|
```ts
|
|
@@ -180,8 +197,8 @@ Dropping any of these is a breaking change; [SUPPORT.md](SUPPORT.md) has the
|
|
|
180
197
|
versioning rules.
|
|
181
198
|
|
|
182
199
|
**Size**, minified and brotli-compressed, with no dependencies of its own:
|
|
183
|
-
`useChunkedTask` 2.
|
|
184
|
-
without React 1.47 kB, everything together 2.
|
|
200
|
+
`useChunkedTask` 2.03 kB, `useDeferredTask` 1.83 kB, `createChunkScheduler`
|
|
201
|
+
without React 1.47 kB, everything together 2.61 kB. CI fails if a change pushes
|
|
185
202
|
any of them over budget.
|
|
186
203
|
|
|
187
204
|
Bundlers that set the `production` export condition — webpack, Vite, Rspack,
|
|
@@ -204,7 +221,10 @@ gh attestation verify "$(npm pack react-inp-chunker --silent)" \
|
|
|
204
221
|
```
|
|
205
222
|
|
|
206
223
|
GitHub Actions are pinned to commit SHAs, CI fails on a known vulnerability at
|
|
207
|
-
any severity, and there are no runtime dependencies to audit.
|
|
224
|
+
any severity, and there are no runtime dependencies to audit. Every
|
|
225
|
+
[release](https://github.com/suprabhat02/react-inp-chunker/releases) also
|
|
226
|
+
carries a CycloneDX SBOM covering the full build-time tree, for feeding into
|
|
227
|
+
your own vulnerability-management tooling.
|
|
208
228
|
|
|
209
229
|
## Quality
|
|
210
230
|
|
package/dist/core/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_deferred_task = require("../deferred-task-
|
|
2
|
+
const require_deferred_task = require("../deferred-task-DRigxydg.cjs");
|
|
3
3
|
exports.DEFAULT_TIME_SLICE_MS = require_deferred_task.DEFAULT_TIME_SLICE_MS;
|
|
4
4
|
exports.createChunkScheduler = require_deferred_task.createChunkScheduler;
|
|
5
5
|
exports.createDeferredTask = require_deferred_task.createDeferredTask;
|
package/dist/core/index.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as isTerminal, C as supportsSchedulerYield, D as ChunkStatus, E as ChunkMachineState, O as TerminalChunkStatus, S as supportsMessageChannel, T as ChunkEvent, _ as isInputPending, a as DeferredTaskSnapshot, b as shouldYieldNow, c as ChunkSchedulerOptions, d as SkippedChunkItem, f as createChunkScheduler, g as YieldToMainOptions, h as YieldStrategy, i as DeferredTaskResult, j as transition, k as initialChunkState, l as ChunkSchedulerResult, m as ShouldYieldOptions, n as DeferredTask, o as createDeferredTask, p as DEFAULT_TIME_SLICE_MS, r as DeferredTaskOptions, s as ChunkScheduler, t as DeferredGenerator, u as ChunkSnapshot, v as now, w as yieldToMain, x as supportsInputPending, y as resolveYieldStrategy } from "../index-
|
|
1
|
+
import { A as isTerminal, C as supportsSchedulerYield, D as ChunkStatus, E as ChunkMachineState, O as TerminalChunkStatus, S as supportsMessageChannel, T as ChunkEvent, _ as isInputPending, a as DeferredTaskSnapshot, b as shouldYieldNow, c as ChunkSchedulerOptions, d as SkippedChunkItem, f as createChunkScheduler, g as YieldToMainOptions, h as YieldStrategy, i as DeferredTaskResult, j as transition, k as initialChunkState, l as ChunkSchedulerResult, m as ShouldYieldOptions, n as DeferredTask, o as createDeferredTask, p as DEFAULT_TIME_SLICE_MS, r as DeferredTaskOptions, s as ChunkScheduler, t as DeferredGenerator, u as ChunkSnapshot, v as now, w as yieldToMain, x as supportsInputPending, y as resolveYieldStrategy } from "../index-DuUMqnsA.cjs";
|
|
2
2
|
export { type ChunkEvent, type ChunkMachineState, type ChunkScheduler, type ChunkSchedulerOptions, type ChunkSchedulerResult, type ChunkSnapshot, type ChunkStatus, DEFAULT_TIME_SLICE_MS, type DeferredGenerator, type DeferredTask, type DeferredTaskOptions, type DeferredTaskResult, type DeferredTaskSnapshot, type ShouldYieldOptions, type SkippedChunkItem, type TerminalChunkStatus, type YieldStrategy, type YieldToMainOptions, createChunkScheduler, createDeferredTask, initialChunkState, isInputPending, isTerminal, now, resolveYieldStrategy, shouldYieldNow, supportsInputPending, supportsMessageChannel, supportsSchedulerYield, transition, yieldToMain };
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as isTerminal, C as supportsSchedulerYield, D as ChunkStatus, E as ChunkMachineState, O as TerminalChunkStatus, S as supportsMessageChannel, T as ChunkEvent, _ as isInputPending, a as DeferredTaskSnapshot, b as shouldYieldNow, c as ChunkSchedulerOptions, d as SkippedChunkItem, f as createChunkScheduler, g as YieldToMainOptions, h as YieldStrategy, i as DeferredTaskResult, j as transition, k as initialChunkState, l as ChunkSchedulerResult, m as ShouldYieldOptions, n as DeferredTask, o as createDeferredTask, p as DEFAULT_TIME_SLICE_MS, r as DeferredTaskOptions, s as ChunkScheduler, t as DeferredGenerator, u as ChunkSnapshot, v as now, w as yieldToMain, x as supportsInputPending, y as resolveYieldStrategy } from "../index-
|
|
1
|
+
import { A as isTerminal, C as supportsSchedulerYield, D as ChunkStatus, E as ChunkMachineState, O as TerminalChunkStatus, S as supportsMessageChannel, T as ChunkEvent, _ as isInputPending, a as DeferredTaskSnapshot, b as shouldYieldNow, c as ChunkSchedulerOptions, d as SkippedChunkItem, f as createChunkScheduler, g as YieldToMainOptions, h as YieldStrategy, i as DeferredTaskResult, j as transition, k as initialChunkState, l as ChunkSchedulerResult, m as ShouldYieldOptions, n as DeferredTask, o as createDeferredTask, p as DEFAULT_TIME_SLICE_MS, r as DeferredTaskOptions, s as ChunkScheduler, t as DeferredGenerator, u as ChunkSnapshot, v as now, w as yieldToMain, x as supportsInputPending, y as resolveYieldStrategy } from "../index-DuUMqnsA.js";
|
|
2
2
|
export { type ChunkEvent, type ChunkMachineState, type ChunkScheduler, type ChunkSchedulerOptions, type ChunkSchedulerResult, type ChunkSnapshot, type ChunkStatus, DEFAULT_TIME_SLICE_MS, type DeferredGenerator, type DeferredTask, type DeferredTaskOptions, type DeferredTaskResult, type DeferredTaskSnapshot, type ShouldYieldOptions, type SkippedChunkItem, type TerminalChunkStatus, type YieldStrategy, type YieldToMainOptions, createChunkScheduler, createDeferredTask, initialChunkState, isInputPending, isTerminal, now, resolveYieldStrategy, shouldYieldNow, supportsInputPending, supportsMessageChannel, supportsSchedulerYield, transition, yieldToMain };
|
package/dist/core/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { r as DEFAULT_TIME_SLICE_MS, n as createChunkScheduler, t as createDeferredTask, f as initialChunkState, i as isInputPending, p as isTerminal, a as now, o as resolveYieldStrategy, s as shouldYieldNow, c as supportsInputPending, l as supportsMessageChannel, u as supportsSchedulerYield, m as transition, d as yieldToMain } from "../deferred-task-
|
|
1
|
+
export { r as DEFAULT_TIME_SLICE_MS, n as createChunkScheduler, t as createDeferredTask, f as initialChunkState, i as isInputPending, p as isTerminal, a as now, o as resolveYieldStrategy, s as shouldYieldNow, c as supportsInputPending, l as supportsMessageChannel, u as supportsSchedulerYield, m as transition, d as yieldToMain } from "../deferred-task-Cai4fF4m.js";
|
|
@@ -569,6 +569,7 @@ function createDeferredTask(options) {
|
|
|
569
569
|
let loopStarted = false;
|
|
570
570
|
let settledFlag = false;
|
|
571
571
|
let settledValue;
|
|
572
|
+
let hasSettledValue = false;
|
|
572
573
|
let cancelReason;
|
|
573
574
|
let snapshot = { status: machineState.status };
|
|
574
575
|
let snapshotStale = false;
|
|
@@ -599,7 +600,7 @@ function createDeferredTask(options) {
|
|
|
599
600
|
snapshot = {
|
|
600
601
|
status: machineState.status,
|
|
601
602
|
...machineState.error !== void 0 ? { error: machineState.error } : {},
|
|
602
|
-
...
|
|
603
|
+
...hasSettledValue ? { value: settledValue } : {},
|
|
603
604
|
...cancelReason !== void 0 ? { reason: cancelReason } : {}
|
|
604
605
|
};
|
|
605
606
|
}
|
|
@@ -628,13 +629,14 @@ function createDeferredTask(options) {
|
|
|
628
629
|
*/
|
|
629
630
|
function publishValue(resolvedValue) {
|
|
630
631
|
settledValue = resolvedValue;
|
|
632
|
+
hasSettledValue = true;
|
|
631
633
|
snapshotStale = true;
|
|
632
634
|
}
|
|
633
635
|
function settleWith(status, resolvedValue, error) {
|
|
634
636
|
if (status === "done") {
|
|
635
637
|
settle({
|
|
636
638
|
status,
|
|
637
|
-
|
|
639
|
+
value: resolvedValue
|
|
638
640
|
});
|
|
639
641
|
return;
|
|
640
642
|
}
|
|
@@ -775,4 +777,4 @@ function createDeferredTask(options) {
|
|
|
775
777
|
}
|
|
776
778
|
export { now as a, supportsInputPending as c, yieldToMain as d, initialChunkState as f, isInputPending as i, supportsMessageChannel as l, transition as m, createChunkScheduler as n, resolveYieldStrategy as o, isTerminal as p, DEFAULT_TIME_SLICE_MS as r, shouldYieldNow as s, createDeferredTask as t, supportsSchedulerYield as u };
|
|
777
779
|
|
|
778
|
-
//# sourceMappingURL=deferred-task-
|
|
780
|
+
//# sourceMappingURL=deferred-task-Cai4fF4m.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deferred-task-Cai4fF4m.js","names":[],"sources":["../src/core/chunk-machine.ts","../src/core/diagnostics.ts","../src/core/scheduler.ts","../src/core/chunk-scheduler.ts","../src/core/deferred-task.ts"],"sourcesContent":["/**\n * The state machine a chunk task moves through. Deliberately separate from\n * `chunk-scheduler.ts`'s async orchestration: this file is pure, synchronous,\n * and has nothing to fake-timer around, which is what makes it possible to\n * exhaustively test every (state, event) pair — all of them, not a sample.\n *\n * START CANCEL\n * idle ---------> processing ----------------> cancelled\n * | ^ ^ ^\n * PAUSE | | RESUME CANCEL| | CANCEL\n * v | | |\n * paused -+-------------------+ |\n * | |\n * COMPLETE | ERROR |\n * v |\n * done / error (terminal, absorb everything)\n *\n * `done`, `cancelled` and `error` are terminal: once reached, every further\n * event is a no-op that returns the same state object (referentially equal,\n * so a React reducer built on this never re-renders for a dead task). A\n * scheduler is one-shot by design — to run another task, create another\n * scheduler. That one rule is what keeps this table small enough to verify\n * completely instead of \"pretty thoroughly\".\n */\n\nexport type ChunkStatus =\n 'idle' | 'processing' | 'paused' | 'done' | 'cancelled' | 'error';\n\nexport type ChunkEvent =\n | { readonly type: 'START' }\n | { readonly type: 'PAUSE' }\n | { readonly type: 'RESUME' }\n | { readonly type: 'CANCEL' }\n | { readonly type: 'COMPLETE' }\n | { readonly type: 'ERROR'; readonly error: unknown };\n\nexport interface ChunkMachineState {\n readonly status: ChunkStatus;\n readonly error?: unknown;\n}\n\nexport const initialChunkState: ChunkMachineState = { status: 'idle' };\n\nexport type TerminalChunkStatus = 'done' | 'cancelled' | 'error';\n\nconst TERMINAL_STATUSES: ReadonlySet<ChunkStatus> = new Set<ChunkStatus>([\n 'done',\n 'cancelled',\n 'error',\n]);\n\nexport function isTerminal(status: ChunkStatus): status is TerminalChunkStatus {\n return TERMINAL_STATUSES.has(status);\n}\n\nfunction transitionIdle(state: ChunkMachineState, event: ChunkEvent): ChunkMachineState {\n switch (event.type) {\n case 'START':\n return { status: 'processing' };\n case 'CANCEL':\n return { status: 'cancelled' };\n case 'PAUSE':\n case 'RESUME':\n case 'COMPLETE':\n case 'ERROR':\n return state;\n }\n}\n\nfunction transitionProcessing(\n state: ChunkMachineState,\n event: ChunkEvent,\n): ChunkMachineState {\n switch (event.type) {\n case 'PAUSE':\n return { status: 'paused' };\n case 'CANCEL':\n return { status: 'cancelled' };\n case 'COMPLETE':\n return { status: 'done' };\n case 'ERROR':\n return { status: 'error', error: event.error };\n case 'START':\n case 'RESUME':\n return state;\n }\n}\n\nfunction transitionPaused(\n state: ChunkMachineState,\n event: ChunkEvent,\n): ChunkMachineState {\n switch (event.type) {\n case 'RESUME':\n return { status: 'processing' };\n case 'CANCEL':\n return { status: 'cancelled' };\n case 'START':\n case 'PAUSE':\n case 'COMPLETE':\n case 'ERROR':\n return state;\n }\n}\n\n/**\n * Advances the machine by one event. Pure: same inputs always produce an\n * equal output, and the input state is never mutated.\n */\nexport function transition(\n state: ChunkMachineState,\n event: ChunkEvent,\n): ChunkMachineState {\n switch (state.status) {\n case 'idle':\n return transitionIdle(state, event);\n case 'processing':\n return transitionProcessing(state, event);\n case 'paused':\n return transitionPaused(state, event);\n case 'done':\n case 'cancelled':\n case 'error':\n // Terminal: absorb every event and return the same reference, so a\n // React reducer built on this never re-renders for a dead task.\n return state;\n }\n}\n","/**\n * Development-only guidance for the two ways a chunked task quietly fails\n * to do what the caller wanted:\n *\n * 1. A single item takes longer than a long task. Chunking cannot help:\n * a slice is never shorter than one item, so the page still freezes.\n * 2. Items are so cheap that reading the clock — around 300ns in a\n * browser, where it is coarsened to 0.1ms anyway — costs more than the\n * work between reads. `batchSize` exists for exactly this.\n *\n * Neither is visible from the outside without a profiler, and both have a\n * concrete fix, which is what makes them worth a warning.\n *\n * Nothing here costs anything in production. The engines guard every call\n * with `isDevelopment`, which the production build (see tsdown.config.ts)\n * replaces with a literal `false`, so the calls and this module both\n * disappear. The checks also ride on clock reads the loop already makes and\n * keep their own baselines, so the hot loop needs no extra bookkeeping.\n */\n\nexport const isDevelopment: boolean =\n typeof process !== 'undefined' && process.env['NODE_ENV'] !== 'production';\n\n/** The Long Tasks API's definition: past this, the browser calls it a long task. */\nconst LONG_UNIT_MS = 50;\n\n/** Below this average cost, the clock read dominates the work. */\nconst CLOCK_BOUND_UNIT_MS = 0.002;\n\n/** Units per slice below which the sample is too small to judge. */\nconst MIN_UNITS_TO_JUDGE = 64;\n\n/** A suggested batch should take about this long, bounding the overshoot it buys. */\nconst TARGET_BATCH_MS = 0.1;\nconst MAX_SUGGESTED_BATCH = 512;\n\nexport interface Diagnostics {\n /** Every clock check: `at` is that reading, `unitsDone` the running total. */\n check(at: number, unitsDone: number): void;\n /** The check that ended a slice, before yielding. */\n sliceEnded(at: number, unitsDone: number): void;\n /** Work resuming after a pause, so the gap is not mistaken for slow work. */\n resumed(at: number, unitsDone: number): void;\n}\n\nfunction warn(message: string): void {\n console.warn(`[react-inp-chunker] ${message}`);\n}\n\n/**\n * The same prefixed channel as the slice-timing guidance below, exposed for\n * the one-shot misuse warnings in chunk-scheduler.ts (an async\n * `processItem`, a `collectResults: false` with no `onItem` to catch the\n * results it discards). Those checks live next to the code they're\n * guarding rather than in this file, but they go through the same\n * development-only gate: calling it has to compile away exactly like\n * `createDiagnostics` does, so `diagnostics.production.ts` carries a\n * matching no-op and the `_sameShape` check below catches drift between\n * them.\n */\nexport function warnDev(message: string): void {\n warn(message);\n}\n\n/**\n * A batch that should take about TARGET_BATCH_MS, which is also the\n * overshoot the caller takes on by accepting it. No lower bound is needed:\n * the warning only fires below CLOCK_BOUND_UNIT_MS, so the result is\n * always at least TARGET_BATCH_MS / CLOCK_BOUND_UNIT_MS.\n */\nfunction suggestedBatchSize(unitCostMs: number): number {\n const target = Math.round(TARGET_BATCH_MS / unitCostMs);\n return target > MAX_SUGGESTED_BATCH ? MAX_SUGGESTED_BATCH : target;\n}\n\n/**\n * `unit` names what one step of this task is, so the message can say\n * \"item\" for a chunked array and \"segment\" for a generator.\n */\nexport function createDiagnostics(\n unit: 'item' | 'segment',\n batchSize: number,\n): Diagnostics {\n let lastCheckAt = 0;\n let lastCheckUnits = 0;\n let sliceStartedAt = 0;\n let sliceStartUnits = 0;\n let started = false;\n let warnedAboutLongUnit = false;\n let warnedAboutClockCost = false;\n\n const rebase = (at: number, unitsDone: number): void => {\n lastCheckAt = at;\n lastCheckUnits = unitsDone;\n sliceStartedAt = at;\n sliceStartUnits = unitsDone;\n started = true;\n };\n\n return {\n check(at, unitsDone) {\n if (!started) {\n rebase(at, unitsDone);\n return;\n }\n const batchMs = at - lastCheckAt;\n const unitsInBatch = unitsDone - lastCheckUnits;\n lastCheckAt = at;\n lastCheckUnits = unitsDone;\n\n if (warnedAboutLongUnit || batchMs <= LONG_UNIT_MS) return;\n warnedAboutLongUnit = true;\n const where =\n unitsInBatch === 1\n ? `One ${unit} (index ${String(unitsDone - 1)}) took ${batchMs.toFixed(0)}ms`\n : `A batch of ${String(unitsInBatch)} ${unit}s took ${batchMs.toFixed(0)}ms`;\n const fix =\n unit === 'item'\n ? 'Split the work inside processItem into smaller items, or move it to useDeferredTask and yield part-way through.'\n : 'Add more yield points, so each segment is shorter.';\n warn(\n `${where}. A slice can never be shorter than that, so this still blocks the main thread. ${fix} (development only)`,\n );\n },\n\n sliceEnded(at, unitsDone) {\n const sliceMs = at - sliceStartedAt;\n const unitsInSlice = unitsDone - sliceStartUnits;\n sliceStartedAt = at;\n sliceStartUnits = unitsDone;\n\n if (warnedAboutClockCost || batchSize > 1) return;\n if (unitsInSlice < MIN_UNITS_TO_JUDGE || sliceMs <= 0) return;\n const unitCostMs = sliceMs / unitsInSlice;\n if (unitCostMs >= CLOCK_BOUND_UNIT_MS) return;\n warnedAboutClockCost = true;\n const micros = (unitCostMs * 1000).toFixed(2);\n warn(\n `${unit}s are averaging ${micros}µs, so checking the clock after each one costs more than the work itself. Pass batchSize: ${String(suggestedBatchSize(unitCostMs))} to check once per batch instead. (development only)`,\n );\n },\n\n resumed(at, unitsDone) {\n rebase(at, unitsDone);\n },\n };\n}\n","/**\n * The main-thread yielding primitives this package is built around.\n *\n * Framework-agnostic on purpose: nothing here imports React, so it works\n * just as well inside a Node CLI script chewing through a big array as it\n * does inside a browser tab. The React bindings in `src/react/` are a thin\n * layer on top of this.\n *\n * Strategy, in priority order:\n * 1. `scheduler.yield()` — the purpose-built API (Baseline across Chromium\n * and Firefox as of 2026; continuations resume ahead of same-priority\n * work queued elsewhere on the page).\n * 2. `MessageChannel` — posts a message to a dedicated port and resolves on\n * delivery. Not clamped to the ~4ms floor `setTimeout` gets once timers\n * are nested, so it yields sooner than (2).\n * 3. `setTimeout(0)` — universally available, the floor of the floor.\n *\n * Every feature check below is written so it can never throw on a global\n * that doesn't exist — `typeof x` on an undeclared identifier is safe in\n * JS, and every property access goes through optional chaining. That's what\n * makes this file safe to import under SSR, in a test environment with no\n * DOM, or in a browser that only half-implements one of these APIs.\n */\n\nexport type YieldStrategy = 'scheduler-yield' | 'message-channel' | 'set-timeout';\n\n/** Default time slice: roughly the granularity React's own scheduler uses. */\nexport const DEFAULT_TIME_SLICE_MS = 5;\n\ninterface SchedulerYieldGlobal {\n scheduler?: {\n yield?: () => Promise<void>;\n };\n}\n\ninterface NavigatorSchedulingGlobal {\n navigator?: {\n scheduling?: {\n isInputPending?: () => boolean;\n };\n };\n}\n\n/** True when `scheduler.yield()` is present on this runtime. */\nexport function supportsSchedulerYield(): boolean {\n const schedulerGlobal = (globalThis as SchedulerYieldGlobal).scheduler;\n return typeof schedulerGlobal?.yield === 'function';\n}\n\n/** True when the `MessageChannel` constructor is present on this runtime. */\nexport function supportsMessageChannel(): boolean {\n return typeof MessageChannel === 'function';\n}\n\n/** True when `navigator.scheduling.isInputPending()` is present. */\nexport function supportsInputPending(): boolean {\n const navigatorGlobal = (globalThis as NavigatorSchedulingGlobal).navigator;\n return typeof navigatorGlobal?.scheduling?.isInputPending === 'function';\n}\n\n/**\n * Resolves the input-pending probe once, for callers that would otherwise\n * run the feature detection on every item of a long task: walking\n * `navigator.scheduling.isInputPending` through optional calls is wasted\n * work a million times over. Returns a function that always answers\n * `false` where the API is missing, so callers need no second branch.\n */\nexport function resolveInputPending(): () => boolean {\n return supportsInputPending() ? isInputPending : returnsFalse;\n}\n\nfunction returnsFalse(): boolean {\n return false;\n}\n\n/**\n * Best-effort read of whether the browser has pending input right now.\n * Returns `false` — never throws — when the API is unavailable or errors,\n * since the caller's fallback (a plain time budget) is always safe.\n */\nexport function isInputPending(): boolean {\n try {\n const navigatorGlobal = (globalThis as NavigatorSchedulingGlobal).navigator;\n return navigatorGlobal?.scheduling?.isInputPending?.() ?? false;\n } catch {\n return false;\n }\n}\n\n/** Picks the best strategy this runtime actually supports. */\nexport function resolveYieldStrategy(): YieldStrategy {\n if (supportsSchedulerYield()) return 'scheduler-yield';\n if (supportsMessageChannel()) return 'message-channel';\n return 'set-timeout';\n}\n\nfunction yieldViaMessageChannel(): Promise<void> {\n return new Promise((resolve) => {\n // A fresh channel per call, deliberately — not a shared module-level\n // singleton. Two chunk schedulers can easily be yielding in the same\n // tick (two independent useChunkedTask calls on one page is a normal\n // app, not an edge case), and a shared port's onmessage handler would\n // get clobbered by whichever call registered second, permanently\n // hanging the first caller's promise. A new MessageChannel is a few\n // microseconds; a silently-hung promise is a production incident.\n const channel = new MessageChannel();\n channel.port1.onmessage = () => {\n // Closed the moment its one message lands. A port with a listener is\n // a live handle: in Node it keeps the process from ever exiting (a\n // CLI script would finish its work and then hang), and in a browser\n // without scheduler.yield() a long task opens hundreds of these.\n channel.port1.close();\n resolve();\n };\n channel.port2.postMessage(null);\n });\n}\n\nfunction yieldViaSetTimeout(): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, 0);\n });\n}\n\nexport interface YieldToMainOptions {\n /**\n * Force a specific strategy instead of auto-detecting one. Exists for\n * deterministic tests of each fallback path; application code should\n * leave this unset.\n */\n strategy?: YieldStrategy;\n}\n\n/**\n * Yields control back to the browser's main thread, resolving once the\n * browser has had a chance to handle any pending input, then resuming the\n * caller. Never rejects: a runtime that half-implements `scheduler.yield()`\n * (the property exists but the call throws, or the returned promise\n * rejects) falls through to the next strategy rather than propagating the\n * failure into unrelated application code.\n */\nexport async function yieldToMain(options?: YieldToMainOptions): Promise<void> {\n const strategy = options?.strategy ?? resolveYieldStrategy();\n\n if (strategy === 'scheduler-yield') {\n const schedulerGlobal = (globalThis as SchedulerYieldGlobal).scheduler;\n if (typeof schedulerGlobal?.yield === 'function') {\n try {\n await schedulerGlobal.yield();\n return;\n } catch {\n // Fall through to the next strategy below.\n }\n }\n await (supportsMessageChannel() ? yieldViaMessageChannel() : yieldViaSetTimeout());\n return;\n }\n\n if (strategy === 'message-channel') {\n if (supportsMessageChannel()) {\n await yieldViaMessageChannel();\n return;\n }\n await yieldViaSetTimeout();\n return;\n }\n\n await yieldViaSetTimeout();\n}\n\n/** `performance.now()` when available, `Date.now()` otherwise. Never throws. */\nexport function now(): number {\n try {\n if (typeof performance !== 'undefined' && typeof performance.now === 'function') {\n return performance.now();\n }\n } catch {\n // fall through\n }\n return Date.now();\n}\n\n/**\n * A time slice of zero, a negative number or NaN is meaningless, so those\n * fall back to the default rather than producing a task that yields after\n * every item (or never).\n */\nexport function normalizeTimeSliceMs(value: number | undefined): number {\n if (typeof value !== 'number' || Number.isNaN(value) || value <= 0) {\n return DEFAULT_TIME_SLICE_MS;\n }\n return value;\n}\n\n/**\n * Batch sizes are whole items, at least one. Anything else — a fraction,\n * zero, a negative, NaN — falls back to 1, the safe default.\n */\nexport function normalizeBatchSize(value: number | undefined): number {\n if (typeof value !== 'number' || !Number.isFinite(value) || value < 1) return 1;\n return Math.floor(value);\n}\n\nexport interface ShouldYieldOptions {\n /** `now()` timestamp when the current uninterrupted run of work began. */\n sliceStartedAt: number;\n /** Max ms to run before yielding regardless of input-pending state. */\n timeSliceMs?: number;\n}\n\n/**\n * Whether the caller should yield right now: either the time slice has run\n * out, or the browser is reporting pending input (a stronger, earlier\n * signal than the clock — used only when the runtime exposes it).\n */\nexport function shouldYieldNow(options: ShouldYieldOptions): boolean {\n const timeSliceMs = options.timeSliceMs ?? DEFAULT_TIME_SLICE_MS;\n const elapsed = now() - options.sliceStartedAt;\n if (elapsed >= timeSliceMs) return true;\n return isInputPending();\n}\n","import {\n type ChunkEvent,\n type ChunkMachineState,\n type ChunkStatus,\n initialChunkState,\n isTerminal,\n transition,\n} from './chunk-machine.js';\nimport { createDiagnostics, isDevelopment, warnDev } from './diagnostics.js';\nimport {\n type YieldStrategy,\n normalizeBatchSize,\n normalizeTimeSliceMs,\n now,\n resolveInputPending,\n yieldToMain,\n} from './scheduler.js';\n\nexport interface ChunkSchedulerOptions<T, R> {\n readonly items: readonly T[];\n readonly processItem: (item: T, index: number) => R;\n /** Max ms of uninterrupted work before yielding. Default 5. */\n readonly timeSliceMs?: number;\n /**\n * Items to process between clock checks. Default 1, which bounds a slice\n * at one item: the safe choice, and the right one unless profiling says\n * otherwise.\n *\n * Reading the clock costs around 300ns in a browser (where it is also\n * coarsened to 0.1ms), so for items measured in nanoseconds that read,\n * not the work, dominates. Raising this amortizes it — at the cost of\n * checking the clock, pause and cancel only at batch boundaries, so a\n * slice can run up to `batchSize` items past its budget. Pick a value\n * whose worst-case cost you know: `batchSize * your slowest item` is the\n * overshoot you are accepting.\n */\n readonly batchSize?: number;\n /**\n * Keep every result for `getResults()`. Default true. Set false when\n * results are consumed through `onItem`: the array is then never built,\n * which matters when `items` is large enough for retaining one result\n * per item to cost real memory. `getResults()` then returns an empty\n * array once the task settles.\n */\n readonly collectResults?: boolean;\n /** Called after each item is successfully processed. */\n readonly onItem?: (result: R, item: T, index: number) => void;\n /**\n * Called when `processItem` throws. Return `'skip'` to drop that one item\n * and keep going — its index and error are recorded in `skipped`, on both\n * the live snapshot and the settled result — or return `'abort'` to end\n * the task the way it always has, as an `'error'` result carrying\n * whatever was collected before the throw. Omitting `onItem` defaults to\n * `'abort'`, so existing callers see no behavior change.\n *\n * Only ever called for `processItem` throwing. An `onItem` that throws\n * still aborts unconditionally: that's the caller's own progress handler\n * misbehaving, not a per-item failure there's anything to recover from.\n */\n readonly onError?: (error: unknown, item: T, index: number) => 'skip' | 'abort';\n /** External cancellation source — aborting it is equivalent to calling cancel(). */\n readonly signal?: AbortSignal;\n /** Testing-only: forces a specific yield strategy instead of auto-detecting one. */\n readonly yieldStrategy?: YieldStrategy;\n /**\n * Called every time a slice ends and the task is about to yield, with how\n * long that slice ran and how many items are done so far. Unlike the\n * development-only diagnostics above, this ships in the production\n * build — wire it to your own telemetry so you can verify, on real\n * traffic, that this task is actually staying under `timeSliceMs`,\n * instead of trusting this package's own benchmark numbers.\n */\n readonly onSlice?: (durationMs: number, processed: number) => void;\n /** Called right after each yield resolves and work is about to resume. Pairs with `onSlice`. */\n readonly onYield?: () => void;\n}\n\n/** One item `onError` chose to skip, in the order it happened. */\nexport interface SkippedChunkItem {\n readonly index: number;\n readonly error: unknown;\n}\n\n/**\n * Fields every status shares. Split out so the union below only has to spell\n * out what actually differs per status: whether `results` is guaranteed yet,\n * and whether `error` is.\n */\ninterface ChunkResultShared {\n /** Items `onError` skipped. Omitted when nothing was skipped. */\n readonly skipped?: readonly SkippedChunkItem[];\n /**\n * The `AbortSignal`'s `reason`, present only when this cancellation came\n * from that signal aborting rather than a direct `cancel()` call.\n */\n readonly reason?: unknown;\n}\n\n/**\n * Discriminated on `status`: narrowing to `'error'` narrows `error` to\n * `unknown` (defined), and narrowing to `'done' | 'cancelled'` rules it out\n * entirely — `results` is a plain array either way, never `undefined`, since\n * a settled task has always published it by the time anyone can observe\n * this type.\n */\nexport type ChunkSchedulerResult<R> = ChunkResultShared &\n (\n | {\n readonly status: 'done' | 'cancelled';\n readonly results: readonly R[];\n readonly error?: undefined;\n }\n | {\n readonly status: 'error';\n readonly results: readonly R[];\n readonly error: unknown;\n }\n );\n\n/**\n * Discriminated on `status`: narrowing to `'done' | 'cancelled' | 'error'`\n * narrows `results` to a defined array — exactly the statuses `isTerminal()`\n * recognizes, and exactly when the engine has actually published it. Narrow\n * to `'error'` specifically to also narrow `error` itself to defined.\n */\nexport type ChunkSnapshot<R = unknown> = ChunkResultShared &\n (\n | {\n readonly status: 'idle' | 'processing' | 'paused';\n readonly processed: number;\n readonly total: number;\n readonly results?: undefined;\n readonly error?: undefined;\n }\n | {\n readonly status: 'done' | 'cancelled';\n readonly processed: number;\n readonly total: number;\n readonly results: readonly R[];\n readonly error?: undefined;\n }\n | {\n readonly status: 'error';\n readonly processed: number;\n readonly total: number;\n readonly results: readonly R[];\n readonly error: unknown;\n }\n );\n\nexport interface ChunkScheduler<R> {\n start(): void;\n pause(): void;\n resume(): void;\n cancel(): void;\n getSnapshot(): ChunkSnapshot<R>;\n /**\n * The accumulated results, but only once the task has reached a terminal\n * state — `undefined` while idle/processing/paused. Computed once, cheaply,\n * at the moment of that transition; never recomputed on every item, so\n * reading it (e.g. every time the snapshot changes) never costs an O(n)\n * array copy per item for a large task. For incremental consumption while\n * the task is still running, use the `onItem` option instead.\n */\n getResults(): readonly R[] | undefined;\n subscribe(listener: () => void): () => void;\n /** Resolves exactly once, when the task reaches a terminal state. Never rejects. */\n readonly settled: Promise<ChunkSchedulerResult<R>>;\n}\n\n/**\n * `items[index]` for an index the caller has already bounds-checked.\n *\n * The loop below only ever indexes below `items.length`, but\n * `noUncheckedIndexedAccess` widens every index read to `T | undefined`,\n * and `T` may itself include `undefined` — so no runtime check can narrow\n * it. This states the invariant in one place instead of asserting at each\n * use, and compiles to a plain index read.\n */\nfunction at<T>(items: readonly T[], index: number): T {\n return items[index] as T;\n}\n\n/**\n * Loose, `typeof`-only thenable check — the same test `await` itself uses,\n * not an `instanceof Promise` (which would miss any other Promise-like:\n * a different realm's Promise, a userland polyfill). Good enough for a\n * one-shot development warning; never throws on anything a caller could\n * plausibly have returned.\n */\nfunction isThenable(value: unknown): boolean {\n return (\n value !== null &&\n typeof value === 'object' &&\n typeof (value as { then?: unknown }).then === 'function'\n );\n}\n\n/**\n * Builds a one-shot, controllable scheduler that processes `items` through\n * `processItem`, yielding to the main thread on a time budget (and sooner,\n * if the browser reports pending input) so a large array never produces a\n * single long task.\n *\n * One-shot means exactly that: once the task reaches `done`, `cancelled` or\n * `error`, this instance is finished — start()/pause()/resume() on it are\n * no-ops from then on. To run another task, create another scheduler. For a\n * React hook, that falls out naturally: new deps → new effect → new\n * scheduler, which is the idiomatic React data-flow anyway.\n */\nexport function createChunkScheduler<T, R>(\n options: ChunkSchedulerOptions<T, R>,\n): ChunkScheduler<R> {\n const { items, processItem, onItem, onError, signal, yieldStrategy, onSlice, onYield } =\n options;\n const timeSliceMs = normalizeTimeSliceMs(options.timeSliceMs);\n const batchSize = normalizeBatchSize(options.batchSize);\n const collectResults = options.collectResults ?? true;\n const total = items.length;\n\n if (isDevelopment && !collectResults && onItem === undefined) {\n warnDev(\n 'collectResults is false and no onItem was passed — every result will be computed and then discarded, with nothing to retrieve once the task settles. Pass onItem to consume results as they finish, or drop collectResults: false if you need the final array.',\n );\n }\n\n // Resolved once per scheduler, not per item: the feature detection behind\n // it walks `navigator.scheduling.isInputPending` through optional calls,\n // which is wasted work on every item of a million-item task.\n const inputPending = resolveInputPending();\n const diagnostics = createDiagnostics('item', batchSize);\n let warnedAboutAsyncResult = false;\n\n let machineState: ChunkMachineState = initialChunkState;\n let processed = 0;\n let loopStarted = false;\n let settledFlag = false;\n const results: R[] = [];\n let finalResults: readonly R[] | undefined;\n const skipped: SkippedChunkItem[] = [];\n let cancelReason: unknown;\n\n // Built on demand rather than after every item. useSyncExternalStore\n // requires repeated calls between notifications to return the *same*\n // reference, which `snapshot` holds until something actually changes;\n // `snapshotStale` is what marks it as needing a rebuild. Allocating a\n // fresh snapshot per item (as this once did) is pure garbage for a task\n // that only publishes progress at slice boundaries.\n // `status` here is the wide `ChunkStatus`, not yet one of the three\n // terminal literals, so this literal can't be checked against the\n // discriminated union structurally — same invariant-the-type-system-\n // can't-see situation as `at()` above. True at construction: a fresh\n // machine starts idle, with no results to have published yet.\n let snapshot = { status: machineState.status, processed, total } as ChunkSnapshot<R>;\n let snapshotStale = false;\n const listeners = new Set<() => void>();\n\n let settleFn!: (result: ChunkSchedulerResult<R>) => void;\n const settledPromise = new Promise<ChunkSchedulerResult<R>>((resolve) => {\n settleFn = resolve;\n });\n\n // The gate a paused loop blocks on. Recreated per pause; opened by either\n // resume() or cancel() so a cancelled-while-paused task can never hang.\n let pauseResolve: (() => void) | undefined;\n let pausePromise: Promise<void> | undefined;\n\n function waitForWake(): Promise<void> {\n pausePromise ??= new Promise((resolve) => {\n pauseResolve = resolve;\n });\n return pausePromise;\n }\n\n function openGate(): void {\n pauseResolve?.();\n pauseResolve = undefined;\n pausePromise = undefined;\n }\n\n function notify(): void {\n for (const listener of listeners) listener();\n }\n\n function getSnapshot(): ChunkSnapshot<R> {\n if (snapshotStale) {\n snapshotStale = false;\n // Same invariant as the constructor above: `results`/`error` are\n // only ever populated together with a terminal `status` (see\n // `publishResults()` and `dispatch()`'s callers), which is what the\n // discriminated union actually requires — but it's enforced by this\n // function's own call order, not by anything the type checker can\n // read off these independent conditionals.\n snapshot = {\n status: machineState.status,\n processed,\n total,\n ...(machineState.error !== undefined ? { error: machineState.error } : {}),\n ...(finalResults !== undefined ? { results: finalResults } : {}),\n ...(skipped.length > 0 ? { skipped: skipped.slice() } : {}),\n ...(cancelReason !== undefined ? { reason: cancelReason } : {}),\n } as ChunkSnapshot<R>;\n }\n return snapshot;\n }\n\n function dispatch(event: ChunkEvent): { changed: boolean } {\n const next = transition(machineState, event);\n const changed = next !== machineState;\n if (changed) {\n machineState = next;\n snapshotStale = true;\n notify();\n }\n return { changed };\n }\n\n function settle(result: ChunkSchedulerResult<R>): void {\n // Structurally unreachable, not merely untested: `finish()` — the only\n // caller of `settle()` — is itself only ever reached from one of two\n // mutually exclusive places, each gated by `loopStarted`: runLoop()'s\n // own completion (which only runs after `loopStarted` was already set\n // true by start()) or settleIfNeverStarted() (which refuses to run\n // once `loopStarted` is true). Either way, by the time this instance\n // can ever reach a terminal state the second time, there is no longer\n // any code path left that would call settle() again. Kept anyway as\n // defense-in-depth against a future refactor breaking that invariant.\n /* v8 ignore next */\n if (settledFlag) return;\n settledFlag = true;\n detachSignal();\n settleFn(result);\n }\n\n /**\n * Publishes the accumulated results exactly once, at the moment the task\n * settles — not per item, and never as a second copy. Always called\n * *before* the terminal status is announced, so the notification that\n * reports `done` already carries the results: a subscriber can never\n * observe one without the other.\n */\n function publishResults(): readonly R[] {\n finalResults = results;\n snapshotStale = true;\n return finalResults;\n }\n\n function settleWith(\n status: 'done' | 'cancelled' | 'error',\n published: readonly R[],\n error?: unknown,\n ): void {\n // `error` is only ever passed for `status === 'error'` (see this\n // function's two call sites), which is the exact pairing the\n // discriminated union requires — invisible to the checker because it's\n // enforced by the caller, not by anything in this object literal.\n settle({\n status,\n results: published,\n ...(error !== undefined ? { error } : {}),\n ...(skipped.length > 0 ? { skipped: skipped.slice() } : {}),\n ...(status === 'cancelled' && cancelReason !== undefined\n ? { reason: cancelReason }\n : {}),\n } as ChunkSchedulerResult<R>);\n }\n\n /**\n * Settling as cancelled, for both places that can do it: the loop\n * noticing, and cancel() on a task whose loop never ran. The CANCEL\n * transition was announced earlier, before there were any results to\n * carry, so this notifies again now that there are.\n */\n function finishCancelled(): void {\n settleWith('cancelled', publishResults());\n notify();\n }\n\n // If the machine reached a terminal state without the processing loop\n // ever running (e.g. cancel() called before start(), or the signal was\n // already aborted), the loop will never call finish() itself — so this\n // does it. Guarded by !loopStarted, which this also sets, so it can never\n // race with (or double-finish against) the loop's own completion path.\n function settleIfNeverStarted(): void {\n const { status } = machineState;\n if (loopStarted || !isTerminal(status)) return;\n loopStarted = true;\n // 'cancelled' is the only status reachable here: 'done' and 'error'\n // both require the loop to have run, and that sets `loopStarted`,\n // which short-circuits this function above.\n finishCancelled();\n }\n\n function handleAbort(): void {\n cancelReason = signal?.reason;\n cancel();\n }\n\n function detachSignal(): void {\n signal?.removeEventListener('abort', handleAbort);\n }\n\n if (signal?.aborted) {\n cancelReason = signal.reason;\n dispatch({ type: 'CANCEL' });\n } else {\n signal?.addEventListener('abort', handleAbort);\n }\n\n async function runLoop(): Promise<void> {\n let sliceStartedAt = now();\n let index = 0;\n\n try {\n while (index < total) {\n // Re-read on every pass rather than trusting a single wake from the\n // pause gate: a resume() can be undone by a *reentrant* pause()\n // triggered synchronously from a subscriber's own notification\n // (resume() dispatches, notify() runs listeners synchronously, a\n // listener calls pause() before resume() returns). Continuing the\n // loop instead of falling through is what lets this see that and\n // block again on a fresh gate, without consuming an item.\n const status: ChunkStatus = machineState.status;\n if (status === 'cancelled') break;\n if (status === 'paused') {\n await waitForWake();\n sliceStartedAt = now();\n if (isDevelopment) diagnostics.resumed(sliceStartedAt, index);\n continue;\n }\n\n // One batch between clock checks. At the default batchSize of 1\n // this is exactly \"one item, then check\", and the inner loop costs\n // nothing beyond the comparison.\n const batchEnd = index + batchSize <= total ? index + batchSize : total;\n while (index < batchEnd) {\n const item = at(items, index);\n let result: R;\n try {\n result = processItem(item, index);\n } catch (caught) {\n // Default ('abort', same as omitting onError) rethrows into the\n // outer try below, so a caller with no onError sees exactly the\n // same error-and-stop behavior this always had.\n if ((onError?.(caught, item, index) ?? 'abort') !== 'skip') throw caught;\n skipped.push({ index, error: caught });\n processed = index + 1;\n snapshotStale = true;\n index += 1;\n continue;\n }\n if (isDevelopment && !warnedAboutAsyncResult && isThenable(result)) {\n warnedAboutAsyncResult = true;\n warnDev(\n 'processItem returned a Promise. createChunkScheduler calls it synchronously and never awaits the result, so results will contain unresolved Promise objects instead of the values they resolve to. Resolve the value before returning it, or chunk the async work a different way.',\n );\n }\n if (collectResults) results.push(result);\n processed = index + 1;\n onItem?.(result, item, index);\n index += 1;\n }\n snapshotStale = true;\n\n // One clock read per batch, shared by the budget check and the\n // development diagnostics. Both diagnostic calls compile away in a\n // production build, along with this module.\n const checkedAt = now();\n if (isDevelopment) diagnostics.check(checkedAt, index);\n\n if (checkedAt - sliceStartedAt >= timeSliceMs || inputPending()) {\n if (isDevelopment) diagnostics.sliceEnded(checkedAt, index);\n onSlice?.(checkedAt - sliceStartedAt, index);\n await yieldToMain(\n yieldStrategy !== undefined ? { strategy: yieldStrategy } : undefined,\n );\n notify(); // surface the progress made during this slice\n sliceStartedAt = now();\n onYield?.();\n if (isDevelopment) diagnostics.resumed(sliceStartedAt, index);\n }\n }\n } catch (error) {\n const published = publishResults();\n dispatch({ type: 'ERROR', error });\n settleWith('error', published, error);\n return;\n }\n\n if (machineState.status === 'cancelled') {\n finishCancelled();\n return;\n }\n\n const published = publishResults();\n dispatch({ type: 'COMPLETE' });\n settleWith('done', published);\n }\n\n function start(): void {\n dispatch({ type: 'START' });\n if (machineState.status === 'processing' && !loopStarted) {\n loopStarted = true;\n void runLoop();\n }\n settleIfNeverStarted();\n }\n\n function pause(): void {\n dispatch({ type: 'PAUSE' });\n }\n\n function resume(): void {\n const { changed } = dispatch({ type: 'RESUME' });\n if (changed) openGate();\n }\n\n function cancel(): void {\n dispatch({ type: 'CANCEL' });\n openGate();\n settleIfNeverStarted();\n }\n\n return {\n start,\n pause,\n resume,\n cancel,\n getSnapshot,\n getResults: () => finalResults,\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n settled: settledPromise,\n };\n}\n","import {\n type ChunkEvent,\n type ChunkMachineState,\n type ChunkStatus,\n initialChunkState,\n isTerminal,\n transition,\n} from './chunk-machine.js';\nimport { createDiagnostics, isDevelopment } from './diagnostics.js';\nimport {\n type YieldStrategy,\n normalizeBatchSize,\n normalizeTimeSliceMs,\n now,\n resolveInputPending,\n yieldToMain,\n} from './scheduler.js';\n\n/**\n * A generator is how you tell `createDeferredTask` where it's safe to yield\n * — arbitrary heavy work (a multi-phase algorithm, a big canvas render)\n * doesn't have a natural \"array of items\" shape the way `createChunkScheduler`\n * expects, so instead you mark checkpoints yourself:\n *\n * ```ts\n * function* renderFrames(frameCount: number) {\n * for (let i = 0; i < frameCount; i++) {\n * renderOneExpensiveFrame(i);\n * yield; // \"it's safe to pause here if we're over budget\"\n * }\n * return frameCount;\n * }\n * ```\n *\n * Everything between two `yield`s runs as one uninterruptible synchronous\n * segment — put them wherever your function can safely stop, the same way\n * you'd think about chunk boundaries by hand, just without writing the\n * scheduling code yourself.\n */\nexport type DeferredGenerator<T> = Generator<void, T, void>;\n\nexport interface DeferredTaskOptions<T> {\n readonly task: () => DeferredGenerator<T>;\n /** Max ms of uninterrupted work before yielding. Default 5. */\n readonly timeSliceMs?: number;\n /**\n * Segments (the code between two `yield`s) to run between clock checks.\n * Default 1, which bounds a slice at one segment.\n *\n * Reading the clock costs around 300ns in a browser, so a generator that\n * yields after a few nanoseconds of work spends most of its time asking\n * the time. Raising this amortizes that — at the cost of checking the\n * clock, pause and cancel only at batch boundaries, so a slice can run up\n * to `batchSize` segments past its budget.\n */\n readonly batchSize?: number;\n readonly signal?: AbortSignal;\n /** Testing-only: forces a specific yield strategy instead of auto-detecting one. */\n readonly yieldStrategy?: YieldStrategy;\n /**\n * Called every time a segment-batch ends and the task is about to yield,\n * with how long that slice ran and how many segments are done so far.\n * Ships in the production build — see the identical option on\n * `createChunkScheduler` for why.\n */\n readonly onSlice?: (durationMs: number, segmentsDone: number) => void;\n /** Called right after each yield resolves and work is about to resume. Pairs with `onSlice`. */\n readonly onYield?: () => void;\n}\n\n/** Fields every status shares — just `reason`, since `error` and `value` differ by status. */\ninterface DeferredResultShared {\n /**\n * The `AbortSignal`'s `reason`, present only when this cancellation came\n * from that signal aborting rather than a direct `cancel()` call.\n */\n readonly reason?: unknown;\n}\n\n/**\n * Discriminated on `status`: narrowing to `'done'` narrows `value` to `T`\n * itself, not `T | undefined` — including when `T` allows `undefined`, a\n * generator genuinely returning it is still a value, distinct from \"no\n * value yet\". Narrowing to `'error'` narrows `error` to defined instead.\n */\nexport type DeferredTaskResult<T> = DeferredResultShared &\n (\n | { readonly status: 'done'; readonly value: T; readonly error?: undefined }\n | {\n readonly status: 'cancelled';\n readonly value?: undefined;\n readonly error?: undefined;\n }\n | { readonly status: 'error'; readonly value?: undefined; readonly error: unknown }\n );\n\n/**\n * Same discrimination as `DeferredTaskResult`, plus the non-terminal\n * statuses a live snapshot can still be in, where neither `value` nor\n * `error` exists yet.\n */\nexport type DeferredTaskSnapshot<T = unknown> = DeferredResultShared &\n (\n | {\n readonly status: 'idle' | 'processing' | 'paused';\n readonly value?: undefined;\n readonly error?: undefined;\n }\n | { readonly status: 'done'; readonly value: T; readonly error?: undefined }\n | {\n readonly status: 'cancelled';\n readonly value?: undefined;\n readonly error?: undefined;\n }\n | { readonly status: 'error'; readonly value?: undefined; readonly error: unknown }\n );\n\nexport interface DeferredTask<T> {\n start(): void;\n pause(): void;\n resume(): void;\n cancel(): void;\n getSnapshot(): DeferredTaskSnapshot<T>;\n /** The returned value once the task has settled with `done` — `undefined` otherwise. */\n getValue(): T | undefined;\n subscribe(listener: () => void): () => void;\n readonly settled: Promise<DeferredTaskResult<T>>;\n}\n\n/**\n * The generator-driven sibling of `createChunkScheduler`, for heavy work\n * that isn't shaped like \"one function applied to each item of an array\".\n * Same state machine, same yield primitives, same one-shot-instance\n * contract — see that file's doc comment for the reasoning, which applies\n * here unchanged.\n */\nexport function createDeferredTask<T>(options: DeferredTaskOptions<T>): DeferredTask<T> {\n const { task, signal, yieldStrategy, onSlice, onYield } = options;\n const timeSliceMs = normalizeTimeSliceMs(options.timeSliceMs);\n const batchSize = normalizeBatchSize(options.batchSize);\n // Resolved once per task rather than per segment; see chunk-scheduler.ts.\n const inputPending = resolveInputPending();\n const diagnostics = createDiagnostics('segment', batchSize);\n\n let machineState: ChunkMachineState = initialChunkState;\n let loopStarted = false;\n let settledFlag = false;\n let settledValue: T | undefined;\n // Separate from `settledValue !== undefined` below: a generator whose `T`\n // allows `undefined` can genuinely return it, and that's still a value —\n // conflating \"returned undefined\" with \"hasn't returned yet\" is exactly\n // the bug this flag exists to avoid.\n let hasSettledValue = false;\n let cancelReason: unknown;\n\n // Built on demand, and only when something has actually changed.\n // useSyncExternalStore requires repeated calls between notifications to\n // return the *same* reference; a fresh object literal on every call (as\n // this used to be) reads as \"changed every render\" and sends React into\n // an infinite re-render loop. Mirrors chunk-scheduler.ts exactly.\n // Wide `status` at construction time, so this can't be checked\n // structurally against the discriminated union — true regardless: a\n // fresh task starts idle, before anything could have published a value.\n let snapshot = { status: machineState.status } as DeferredTaskSnapshot<T>;\n let snapshotStale = false;\n\n const listeners = new Set<() => void>();\n\n let settleFn!: (result: DeferredTaskResult<T>) => void;\n const settledPromise = new Promise<DeferredTaskResult<T>>((resolve) => {\n settleFn = resolve;\n });\n\n let pauseResolve: (() => void) | undefined;\n let pausePromise: Promise<void> | undefined;\n\n function waitForWake(): Promise<void> {\n pausePromise ??= new Promise((resolve) => {\n pauseResolve = resolve;\n });\n return pausePromise;\n }\n\n function openGate(): void {\n pauseResolve?.();\n pauseResolve = undefined;\n pausePromise = undefined;\n }\n\n function notify(): void {\n for (const listener of listeners) listener();\n }\n\n function getSnapshot(): DeferredTaskSnapshot<T> {\n if (snapshotStale) {\n snapshotStale = false;\n // Same invariant as the constructor above: `value`/`error` are only\n // ever populated together with a terminal `status`, enforced by this\n // function's own call order rather than visible to the checker here.\n snapshot = {\n status: machineState.status,\n ...(machineState.error !== undefined ? { error: machineState.error } : {}),\n ...(hasSettledValue ? { value: settledValue } : {}),\n ...(cancelReason !== undefined ? { reason: cancelReason } : {}),\n } as DeferredTaskSnapshot<T>;\n }\n return snapshot;\n }\n\n function dispatch(event: ChunkEvent): { changed: boolean } {\n const next = transition(machineState, event);\n const changed = next !== machineState;\n if (changed) {\n machineState = next;\n snapshotStale = true;\n notify();\n }\n return { changed };\n }\n\n function settle(result: DeferredTaskResult<T>): void {\n // Structurally unreachable, not merely untested — see the identical\n // guard in chunk-scheduler.ts's settle() for the full invariant: the\n // only caller, finish(), is reached from exactly one of two mutually\n // exclusive, `loopStarted`-gated paths per instance, so there is never\n // a second call left to make once this instance has settled once.\n // Kept as defense-in-depth against a future refactor breaking that.\n /* v8 ignore next */\n if (settledFlag) return;\n settledFlag = true;\n detachSignal();\n settleFn(result);\n }\n\n /**\n * Publishes the returned value before the terminal status is announced,\n * so the notification that reports `done` already carries it.\n */\n function publishValue(resolvedValue: T | undefined): void {\n settledValue = resolvedValue;\n hasSettledValue = true;\n snapshotStale = true;\n }\n\n function settleWith(\n status: 'done' | 'cancelled' | 'error',\n resolvedValue?: T,\n error?: unknown,\n ): void {\n if (status === 'done') {\n // Always include `value`, even when `resolvedValue` is `undefined`:\n // that's still a real returned value (see `hasSettledValue` above),\n // and the discriminated union's `'done'` branch requires the key\n // present — invisible to the checker since `resolvedValue`'s static\n // type is `T | undefined` regardless of which case this actually is.\n settle({ status, value: resolvedValue } as DeferredTaskResult<T>);\n return;\n }\n if (status === 'error') {\n settle({ status, error });\n return;\n }\n settle({ status, ...(cancelReason !== undefined ? { reason: cancelReason } : {}) });\n }\n\n /**\n * Settling as cancelled, for both places that can do it. The CANCEL\n * transition was announced earlier, so this notifies again now that the\n * task has actually settled.\n */\n function finishCancelled(): void {\n settleWith('cancelled');\n notify();\n }\n\n function settleIfNeverStarted(): void {\n const { status } = machineState;\n if (loopStarted || !isTerminal(status)) return;\n loopStarted = true;\n // 'cancelled' is the only status reachable here: 'done' and 'error'\n // both require the loop to have run, and that sets `loopStarted`,\n // which short-circuits this function above.\n finishCancelled();\n }\n\n function handleAbort(): void {\n cancelReason = signal?.reason;\n cancel();\n }\n\n function detachSignal(): void {\n signal?.removeEventListener('abort', handleAbort);\n }\n\n if (signal?.aborted) {\n cancelReason = signal.reason;\n dispatch({ type: 'CANCEL' });\n } else {\n signal?.addEventListener('abort', handleAbort);\n }\n\n async function runLoop(): Promise<void> {\n let sliceStartedAt = now();\n let segment = 0;\n const generator = task();\n\n // `step` always holds the result of the *last* generator.next() call —\n // including, on an early break below, a stale not-done step from before\n // that break, which the post-loop checks account for explicitly.\n let step: IteratorResult<void, T> = { done: false, value: undefined };\n try {\n for (;;) {\n // Captured once per iteration and compared via this local, not via\n // repeated `machineState.status` property reads — narrowing a\n // direct property access to a literal here would otherwise survive\n // (in TS's eyes) across the `await` below, even though the whole\n // point of that await is that `machineState` can change out from\n // under us while it's pending.\n const status: ChunkStatus = machineState.status;\n if (status === 'cancelled') {\n generator.return(undefined as unknown as T);\n break;\n }\n\n if (status === 'paused') {\n await waitForWake();\n sliceStartedAt = now();\n if (isDevelopment) diagnostics.resumed(sliceStartedAt, segment);\n // Don't fall through to generator.next() just because the gate\n // opened — re-enter the loop and read machineState.status fresh.\n // A resume() can be undone by a *reentrant* pause() triggered\n // synchronously from a subscriber's own notification (resume()\n // dispatches, notify() runs listeners synchronously, a listener\n // calls pause() before resume() returns) — so waking from one\n // gate is not proof we're actually clear to proceed. Looping\n // back here is what lets the top-of-loop checks see that and\n // either re-block on a fresh gate (status is paused again) or\n // notice cancellation, instead of blindly running one more\n // generator step first.\n continue;\n }\n\n // The cancellation check above MUST run before this call, not\n // after: calling the generator is where its actual (synchronous)\n // work for this segment happens. An earlier draft called\n // generator.next() at the *bottom* of the loop, right after the\n // yieldToMain() await below — so a cancel() landing during that\n // await would still let one more segment's worth of work run\n // before the next loop iteration finally noticed. Checking first,\n // every time, closes that race: nothing the generator does can run\n // once cancellation has been observed.\n // One batch of segments between clock checks. At the default\n // batchSize of 1 this runs exactly one segment, then checks.\n let done = false;\n for (let inBatch = 0; inBatch < batchSize; inBatch++) {\n step = generator.next();\n segment += 1;\n if (step.done) {\n done = true;\n break;\n }\n }\n if (done) break;\n\n // One clock read per batch, shared by the budget check and the\n // development diagnostics, which compile away in production.\n const checkedAt = now();\n if (isDevelopment) diagnostics.check(checkedAt, segment);\n\n if (checkedAt - sliceStartedAt >= timeSliceMs || inputPending()) {\n if (isDevelopment) diagnostics.sliceEnded(checkedAt, segment);\n onSlice?.(checkedAt - sliceStartedAt, segment);\n await yieldToMain(\n yieldStrategy !== undefined ? { strategy: yieldStrategy } : undefined,\n );\n notify();\n sliceStartedAt = now();\n onYield?.();\n if (isDevelopment) diagnostics.resumed(sliceStartedAt, segment);\n }\n }\n } catch (error) {\n dispatch({ type: 'ERROR', error });\n settleWith('error', undefined, error);\n return;\n }\n\n if (machineState.status === 'cancelled') {\n finishCancelled();\n return;\n }\n\n // step.done is always true by the time we get here: the only ways out\n // of the loop above are the top-of-loop 'cancelled' break (handled by\n // the early return just above, before this line) and the batch loop's\n // own `done` break — so the \": undefined\" side of this ternary can't\n // actually run. It stays only because it's what makes TypeScript\n // narrow step.value's type correctly; the real type is kept rather\n // than cast away.\n /* v8 ignore next */\n const returned = step.done ? step.value : undefined;\n publishValue(returned);\n dispatch({ type: 'COMPLETE' });\n settleWith('done', returned);\n }\n\n function start(): void {\n dispatch({ type: 'START' });\n if (machineState.status === 'processing' && !loopStarted) {\n loopStarted = true;\n void runLoop();\n }\n settleIfNeverStarted();\n }\n\n function pause(): void {\n dispatch({ type: 'PAUSE' });\n }\n\n function resume(): void {\n const { changed } = dispatch({ type: 'RESUME' });\n if (changed) openGate();\n }\n\n function cancel(): void {\n dispatch({ type: 'CANCEL' });\n openGate();\n settleIfNeverStarted();\n }\n\n return {\n start,\n pause,\n resume,\n cancel,\n getSnapshot,\n getValue: () => settledValue,\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n settled: settledPromise,\n };\n}\n"],"mappings":"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"}
|
|
@@ -569,6 +569,7 @@ function createDeferredTask(options) {
|
|
|
569
569
|
let loopStarted = false;
|
|
570
570
|
let settledFlag = false;
|
|
571
571
|
let settledValue;
|
|
572
|
+
let hasSettledValue = false;
|
|
572
573
|
let cancelReason;
|
|
573
574
|
let snapshot = { status: machineState.status };
|
|
574
575
|
let snapshotStale = false;
|
|
@@ -599,7 +600,7 @@ function createDeferredTask(options) {
|
|
|
599
600
|
snapshot = {
|
|
600
601
|
status: machineState.status,
|
|
601
602
|
...machineState.error !== void 0 ? { error: machineState.error } : {},
|
|
602
|
-
...
|
|
603
|
+
...hasSettledValue ? { value: settledValue } : {},
|
|
603
604
|
...cancelReason !== void 0 ? { reason: cancelReason } : {}
|
|
604
605
|
};
|
|
605
606
|
}
|
|
@@ -628,13 +629,14 @@ function createDeferredTask(options) {
|
|
|
628
629
|
*/
|
|
629
630
|
function publishValue(resolvedValue) {
|
|
630
631
|
settledValue = resolvedValue;
|
|
632
|
+
hasSettledValue = true;
|
|
631
633
|
snapshotStale = true;
|
|
632
634
|
}
|
|
633
635
|
function settleWith(status, resolvedValue, error) {
|
|
634
636
|
if (status === "done") {
|
|
635
637
|
settle({
|
|
636
638
|
status,
|
|
637
|
-
|
|
639
|
+
value: resolvedValue
|
|
638
640
|
});
|
|
639
641
|
return;
|
|
640
642
|
}
|
|
@@ -858,4 +860,4 @@ Object.defineProperty(exports, "yieldToMain", {
|
|
|
858
860
|
}
|
|
859
861
|
});
|
|
860
862
|
|
|
861
|
-
//# sourceMappingURL=deferred-task-
|
|
863
|
+
//# sourceMappingURL=deferred-task-DRigxydg.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deferred-task-DRigxydg.cjs","names":[],"sources":["../src/core/chunk-machine.ts","../src/core/diagnostics.ts","../src/core/scheduler.ts","../src/core/chunk-scheduler.ts","../src/core/deferred-task.ts"],"sourcesContent":["/**\n * The state machine a chunk task moves through. Deliberately separate from\n * `chunk-scheduler.ts`'s async orchestration: this file is pure, synchronous,\n * and has nothing to fake-timer around, which is what makes it possible to\n * exhaustively test every (state, event) pair — all of them, not a sample.\n *\n * START CANCEL\n * idle ---------> processing ----------------> cancelled\n * | ^ ^ ^\n * PAUSE | | RESUME CANCEL| | CANCEL\n * v | | |\n * paused -+-------------------+ |\n * | |\n * COMPLETE | ERROR |\n * v |\n * done / error (terminal, absorb everything)\n *\n * `done`, `cancelled` and `error` are terminal: once reached, every further\n * event is a no-op that returns the same state object (referentially equal,\n * so a React reducer built on this never re-renders for a dead task). A\n * scheduler is one-shot by design — to run another task, create another\n * scheduler. That one rule is what keeps this table small enough to verify\n * completely instead of \"pretty thoroughly\".\n */\n\nexport type ChunkStatus =\n 'idle' | 'processing' | 'paused' | 'done' | 'cancelled' | 'error';\n\nexport type ChunkEvent =\n | { readonly type: 'START' }\n | { readonly type: 'PAUSE' }\n | { readonly type: 'RESUME' }\n | { readonly type: 'CANCEL' }\n | { readonly type: 'COMPLETE' }\n | { readonly type: 'ERROR'; readonly error: unknown };\n\nexport interface ChunkMachineState {\n readonly status: ChunkStatus;\n readonly error?: unknown;\n}\n\nexport const initialChunkState: ChunkMachineState = { status: 'idle' };\n\nexport type TerminalChunkStatus = 'done' | 'cancelled' | 'error';\n\nconst TERMINAL_STATUSES: ReadonlySet<ChunkStatus> = new Set<ChunkStatus>([\n 'done',\n 'cancelled',\n 'error',\n]);\n\nexport function isTerminal(status: ChunkStatus): status is TerminalChunkStatus {\n return TERMINAL_STATUSES.has(status);\n}\n\nfunction transitionIdle(state: ChunkMachineState, event: ChunkEvent): ChunkMachineState {\n switch (event.type) {\n case 'START':\n return { status: 'processing' };\n case 'CANCEL':\n return { status: 'cancelled' };\n case 'PAUSE':\n case 'RESUME':\n case 'COMPLETE':\n case 'ERROR':\n return state;\n }\n}\n\nfunction transitionProcessing(\n state: ChunkMachineState,\n event: ChunkEvent,\n): ChunkMachineState {\n switch (event.type) {\n case 'PAUSE':\n return { status: 'paused' };\n case 'CANCEL':\n return { status: 'cancelled' };\n case 'COMPLETE':\n return { status: 'done' };\n case 'ERROR':\n return { status: 'error', error: event.error };\n case 'START':\n case 'RESUME':\n return state;\n }\n}\n\nfunction transitionPaused(\n state: ChunkMachineState,\n event: ChunkEvent,\n): ChunkMachineState {\n switch (event.type) {\n case 'RESUME':\n return { status: 'processing' };\n case 'CANCEL':\n return { status: 'cancelled' };\n case 'START':\n case 'PAUSE':\n case 'COMPLETE':\n case 'ERROR':\n return state;\n }\n}\n\n/**\n * Advances the machine by one event. Pure: same inputs always produce an\n * equal output, and the input state is never mutated.\n */\nexport function transition(\n state: ChunkMachineState,\n event: ChunkEvent,\n): ChunkMachineState {\n switch (state.status) {\n case 'idle':\n return transitionIdle(state, event);\n case 'processing':\n return transitionProcessing(state, event);\n case 'paused':\n return transitionPaused(state, event);\n case 'done':\n case 'cancelled':\n case 'error':\n // Terminal: absorb every event and return the same reference, so a\n // React reducer built on this never re-renders for a dead task.\n return state;\n }\n}\n","/**\n * Development-only guidance for the two ways a chunked task quietly fails\n * to do what the caller wanted:\n *\n * 1. A single item takes longer than a long task. Chunking cannot help:\n * a slice is never shorter than one item, so the page still freezes.\n * 2. Items are so cheap that reading the clock — around 300ns in a\n * browser, where it is coarsened to 0.1ms anyway — costs more than the\n * work between reads. `batchSize` exists for exactly this.\n *\n * Neither is visible from the outside without a profiler, and both have a\n * concrete fix, which is what makes them worth a warning.\n *\n * Nothing here costs anything in production. The engines guard every call\n * with `isDevelopment`, which the production build (see tsdown.config.ts)\n * replaces with a literal `false`, so the calls and this module both\n * disappear. The checks also ride on clock reads the loop already makes and\n * keep their own baselines, so the hot loop needs no extra bookkeeping.\n */\n\nexport const isDevelopment: boolean =\n typeof process !== 'undefined' && process.env['NODE_ENV'] !== 'production';\n\n/** The Long Tasks API's definition: past this, the browser calls it a long task. */\nconst LONG_UNIT_MS = 50;\n\n/** Below this average cost, the clock read dominates the work. */\nconst CLOCK_BOUND_UNIT_MS = 0.002;\n\n/** Units per slice below which the sample is too small to judge. */\nconst MIN_UNITS_TO_JUDGE = 64;\n\n/** A suggested batch should take about this long, bounding the overshoot it buys. */\nconst TARGET_BATCH_MS = 0.1;\nconst MAX_SUGGESTED_BATCH = 512;\n\nexport interface Diagnostics {\n /** Every clock check: `at` is that reading, `unitsDone` the running total. */\n check(at: number, unitsDone: number): void;\n /** The check that ended a slice, before yielding. */\n sliceEnded(at: number, unitsDone: number): void;\n /** Work resuming after a pause, so the gap is not mistaken for slow work. */\n resumed(at: number, unitsDone: number): void;\n}\n\nfunction warn(message: string): void {\n console.warn(`[react-inp-chunker] ${message}`);\n}\n\n/**\n * The same prefixed channel as the slice-timing guidance below, exposed for\n * the one-shot misuse warnings in chunk-scheduler.ts (an async\n * `processItem`, a `collectResults: false` with no `onItem` to catch the\n * results it discards). Those checks live next to the code they're\n * guarding rather than in this file, but they go through the same\n * development-only gate: calling it has to compile away exactly like\n * `createDiagnostics` does, so `diagnostics.production.ts` carries a\n * matching no-op and the `_sameShape` check below catches drift between\n * them.\n */\nexport function warnDev(message: string): void {\n warn(message);\n}\n\n/**\n * A batch that should take about TARGET_BATCH_MS, which is also the\n * overshoot the caller takes on by accepting it. No lower bound is needed:\n * the warning only fires below CLOCK_BOUND_UNIT_MS, so the result is\n * always at least TARGET_BATCH_MS / CLOCK_BOUND_UNIT_MS.\n */\nfunction suggestedBatchSize(unitCostMs: number): number {\n const target = Math.round(TARGET_BATCH_MS / unitCostMs);\n return target > MAX_SUGGESTED_BATCH ? MAX_SUGGESTED_BATCH : target;\n}\n\n/**\n * `unit` names what one step of this task is, so the message can say\n * \"item\" for a chunked array and \"segment\" for a generator.\n */\nexport function createDiagnostics(\n unit: 'item' | 'segment',\n batchSize: number,\n): Diagnostics {\n let lastCheckAt = 0;\n let lastCheckUnits = 0;\n let sliceStartedAt = 0;\n let sliceStartUnits = 0;\n let started = false;\n let warnedAboutLongUnit = false;\n let warnedAboutClockCost = false;\n\n const rebase = (at: number, unitsDone: number): void => {\n lastCheckAt = at;\n lastCheckUnits = unitsDone;\n sliceStartedAt = at;\n sliceStartUnits = unitsDone;\n started = true;\n };\n\n return {\n check(at, unitsDone) {\n if (!started) {\n rebase(at, unitsDone);\n return;\n }\n const batchMs = at - lastCheckAt;\n const unitsInBatch = unitsDone - lastCheckUnits;\n lastCheckAt = at;\n lastCheckUnits = unitsDone;\n\n if (warnedAboutLongUnit || batchMs <= LONG_UNIT_MS) return;\n warnedAboutLongUnit = true;\n const where =\n unitsInBatch === 1\n ? `One ${unit} (index ${String(unitsDone - 1)}) took ${batchMs.toFixed(0)}ms`\n : `A batch of ${String(unitsInBatch)} ${unit}s took ${batchMs.toFixed(0)}ms`;\n const fix =\n unit === 'item'\n ? 'Split the work inside processItem into smaller items, or move it to useDeferredTask and yield part-way through.'\n : 'Add more yield points, so each segment is shorter.';\n warn(\n `${where}. A slice can never be shorter than that, so this still blocks the main thread. ${fix} (development only)`,\n );\n },\n\n sliceEnded(at, unitsDone) {\n const sliceMs = at - sliceStartedAt;\n const unitsInSlice = unitsDone - sliceStartUnits;\n sliceStartedAt = at;\n sliceStartUnits = unitsDone;\n\n if (warnedAboutClockCost || batchSize > 1) return;\n if (unitsInSlice < MIN_UNITS_TO_JUDGE || sliceMs <= 0) return;\n const unitCostMs = sliceMs / unitsInSlice;\n if (unitCostMs >= CLOCK_BOUND_UNIT_MS) return;\n warnedAboutClockCost = true;\n const micros = (unitCostMs * 1000).toFixed(2);\n warn(\n `${unit}s are averaging ${micros}µs, so checking the clock after each one costs more than the work itself. Pass batchSize: ${String(suggestedBatchSize(unitCostMs))} to check once per batch instead. (development only)`,\n );\n },\n\n resumed(at, unitsDone) {\n rebase(at, unitsDone);\n },\n };\n}\n","/**\n * The main-thread yielding primitives this package is built around.\n *\n * Framework-agnostic on purpose: nothing here imports React, so it works\n * just as well inside a Node CLI script chewing through a big array as it\n * does inside a browser tab. The React bindings in `src/react/` are a thin\n * layer on top of this.\n *\n * Strategy, in priority order:\n * 1. `scheduler.yield()` — the purpose-built API (Baseline across Chromium\n * and Firefox as of 2026; continuations resume ahead of same-priority\n * work queued elsewhere on the page).\n * 2. `MessageChannel` — posts a message to a dedicated port and resolves on\n * delivery. Not clamped to the ~4ms floor `setTimeout` gets once timers\n * are nested, so it yields sooner than (2).\n * 3. `setTimeout(0)` — universally available, the floor of the floor.\n *\n * Every feature check below is written so it can never throw on a global\n * that doesn't exist — `typeof x` on an undeclared identifier is safe in\n * JS, and every property access goes through optional chaining. That's what\n * makes this file safe to import under SSR, in a test environment with no\n * DOM, or in a browser that only half-implements one of these APIs.\n */\n\nexport type YieldStrategy = 'scheduler-yield' | 'message-channel' | 'set-timeout';\n\n/** Default time slice: roughly the granularity React's own scheduler uses. */\nexport const DEFAULT_TIME_SLICE_MS = 5;\n\ninterface SchedulerYieldGlobal {\n scheduler?: {\n yield?: () => Promise<void>;\n };\n}\n\ninterface NavigatorSchedulingGlobal {\n navigator?: {\n scheduling?: {\n isInputPending?: () => boolean;\n };\n };\n}\n\n/** True when `scheduler.yield()` is present on this runtime. */\nexport function supportsSchedulerYield(): boolean {\n const schedulerGlobal = (globalThis as SchedulerYieldGlobal).scheduler;\n return typeof schedulerGlobal?.yield === 'function';\n}\n\n/** True when the `MessageChannel` constructor is present on this runtime. */\nexport function supportsMessageChannel(): boolean {\n return typeof MessageChannel === 'function';\n}\n\n/** True when `navigator.scheduling.isInputPending()` is present. */\nexport function supportsInputPending(): boolean {\n const navigatorGlobal = (globalThis as NavigatorSchedulingGlobal).navigator;\n return typeof navigatorGlobal?.scheduling?.isInputPending === 'function';\n}\n\n/**\n * Resolves the input-pending probe once, for callers that would otherwise\n * run the feature detection on every item of a long task: walking\n * `navigator.scheduling.isInputPending` through optional calls is wasted\n * work a million times over. Returns a function that always answers\n * `false` where the API is missing, so callers need no second branch.\n */\nexport function resolveInputPending(): () => boolean {\n return supportsInputPending() ? isInputPending : returnsFalse;\n}\n\nfunction returnsFalse(): boolean {\n return false;\n}\n\n/**\n * Best-effort read of whether the browser has pending input right now.\n * Returns `false` — never throws — when the API is unavailable or errors,\n * since the caller's fallback (a plain time budget) is always safe.\n */\nexport function isInputPending(): boolean {\n try {\n const navigatorGlobal = (globalThis as NavigatorSchedulingGlobal).navigator;\n return navigatorGlobal?.scheduling?.isInputPending?.() ?? false;\n } catch {\n return false;\n }\n}\n\n/** Picks the best strategy this runtime actually supports. */\nexport function resolveYieldStrategy(): YieldStrategy {\n if (supportsSchedulerYield()) return 'scheduler-yield';\n if (supportsMessageChannel()) return 'message-channel';\n return 'set-timeout';\n}\n\nfunction yieldViaMessageChannel(): Promise<void> {\n return new Promise((resolve) => {\n // A fresh channel per call, deliberately — not a shared module-level\n // singleton. Two chunk schedulers can easily be yielding in the same\n // tick (two independent useChunkedTask calls on one page is a normal\n // app, not an edge case), and a shared port's onmessage handler would\n // get clobbered by whichever call registered second, permanently\n // hanging the first caller's promise. A new MessageChannel is a few\n // microseconds; a silently-hung promise is a production incident.\n const channel = new MessageChannel();\n channel.port1.onmessage = () => {\n // Closed the moment its one message lands. A port with a listener is\n // a live handle: in Node it keeps the process from ever exiting (a\n // CLI script would finish its work and then hang), and in a browser\n // without scheduler.yield() a long task opens hundreds of these.\n channel.port1.close();\n resolve();\n };\n channel.port2.postMessage(null);\n });\n}\n\nfunction yieldViaSetTimeout(): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, 0);\n });\n}\n\nexport interface YieldToMainOptions {\n /**\n * Force a specific strategy instead of auto-detecting one. Exists for\n * deterministic tests of each fallback path; application code should\n * leave this unset.\n */\n strategy?: YieldStrategy;\n}\n\n/**\n * Yields control back to the browser's main thread, resolving once the\n * browser has had a chance to handle any pending input, then resuming the\n * caller. Never rejects: a runtime that half-implements `scheduler.yield()`\n * (the property exists but the call throws, or the returned promise\n * rejects) falls through to the next strategy rather than propagating the\n * failure into unrelated application code.\n */\nexport async function yieldToMain(options?: YieldToMainOptions): Promise<void> {\n const strategy = options?.strategy ?? resolveYieldStrategy();\n\n if (strategy === 'scheduler-yield') {\n const schedulerGlobal = (globalThis as SchedulerYieldGlobal).scheduler;\n if (typeof schedulerGlobal?.yield === 'function') {\n try {\n await schedulerGlobal.yield();\n return;\n } catch {\n // Fall through to the next strategy below.\n }\n }\n await (supportsMessageChannel() ? yieldViaMessageChannel() : yieldViaSetTimeout());\n return;\n }\n\n if (strategy === 'message-channel') {\n if (supportsMessageChannel()) {\n await yieldViaMessageChannel();\n return;\n }\n await yieldViaSetTimeout();\n return;\n }\n\n await yieldViaSetTimeout();\n}\n\n/** `performance.now()` when available, `Date.now()` otherwise. Never throws. */\nexport function now(): number {\n try {\n if (typeof performance !== 'undefined' && typeof performance.now === 'function') {\n return performance.now();\n }\n } catch {\n // fall through\n }\n return Date.now();\n}\n\n/**\n * A time slice of zero, a negative number or NaN is meaningless, so those\n * fall back to the default rather than producing a task that yields after\n * every item (or never).\n */\nexport function normalizeTimeSliceMs(value: number | undefined): number {\n if (typeof value !== 'number' || Number.isNaN(value) || value <= 0) {\n return DEFAULT_TIME_SLICE_MS;\n }\n return value;\n}\n\n/**\n * Batch sizes are whole items, at least one. Anything else — a fraction,\n * zero, a negative, NaN — falls back to 1, the safe default.\n */\nexport function normalizeBatchSize(value: number | undefined): number {\n if (typeof value !== 'number' || !Number.isFinite(value) || value < 1) return 1;\n return Math.floor(value);\n}\n\nexport interface ShouldYieldOptions {\n /** `now()` timestamp when the current uninterrupted run of work began. */\n sliceStartedAt: number;\n /** Max ms to run before yielding regardless of input-pending state. */\n timeSliceMs?: number;\n}\n\n/**\n * Whether the caller should yield right now: either the time slice has run\n * out, or the browser is reporting pending input (a stronger, earlier\n * signal than the clock — used only when the runtime exposes it).\n */\nexport function shouldYieldNow(options: ShouldYieldOptions): boolean {\n const timeSliceMs = options.timeSliceMs ?? DEFAULT_TIME_SLICE_MS;\n const elapsed = now() - options.sliceStartedAt;\n if (elapsed >= timeSliceMs) return true;\n return isInputPending();\n}\n","import {\n type ChunkEvent,\n type ChunkMachineState,\n type ChunkStatus,\n initialChunkState,\n isTerminal,\n transition,\n} from './chunk-machine.js';\nimport { createDiagnostics, isDevelopment, warnDev } from './diagnostics.js';\nimport {\n type YieldStrategy,\n normalizeBatchSize,\n normalizeTimeSliceMs,\n now,\n resolveInputPending,\n yieldToMain,\n} from './scheduler.js';\n\nexport interface ChunkSchedulerOptions<T, R> {\n readonly items: readonly T[];\n readonly processItem: (item: T, index: number) => R;\n /** Max ms of uninterrupted work before yielding. Default 5. */\n readonly timeSliceMs?: number;\n /**\n * Items to process between clock checks. Default 1, which bounds a slice\n * at one item: the safe choice, and the right one unless profiling says\n * otherwise.\n *\n * Reading the clock costs around 300ns in a browser (where it is also\n * coarsened to 0.1ms), so for items measured in nanoseconds that read,\n * not the work, dominates. Raising this amortizes it — at the cost of\n * checking the clock, pause and cancel only at batch boundaries, so a\n * slice can run up to `batchSize` items past its budget. Pick a value\n * whose worst-case cost you know: `batchSize * your slowest item` is the\n * overshoot you are accepting.\n */\n readonly batchSize?: number;\n /**\n * Keep every result for `getResults()`. Default true. Set false when\n * results are consumed through `onItem`: the array is then never built,\n * which matters when `items` is large enough for retaining one result\n * per item to cost real memory. `getResults()` then returns an empty\n * array once the task settles.\n */\n readonly collectResults?: boolean;\n /** Called after each item is successfully processed. */\n readonly onItem?: (result: R, item: T, index: number) => void;\n /**\n * Called when `processItem` throws. Return `'skip'` to drop that one item\n * and keep going — its index and error are recorded in `skipped`, on both\n * the live snapshot and the settled result — or return `'abort'` to end\n * the task the way it always has, as an `'error'` result carrying\n * whatever was collected before the throw. Omitting `onItem` defaults to\n * `'abort'`, so existing callers see no behavior change.\n *\n * Only ever called for `processItem` throwing. An `onItem` that throws\n * still aborts unconditionally: that's the caller's own progress handler\n * misbehaving, not a per-item failure there's anything to recover from.\n */\n readonly onError?: (error: unknown, item: T, index: number) => 'skip' | 'abort';\n /** External cancellation source — aborting it is equivalent to calling cancel(). */\n readonly signal?: AbortSignal;\n /** Testing-only: forces a specific yield strategy instead of auto-detecting one. */\n readonly yieldStrategy?: YieldStrategy;\n /**\n * Called every time a slice ends and the task is about to yield, with how\n * long that slice ran and how many items are done so far. Unlike the\n * development-only diagnostics above, this ships in the production\n * build — wire it to your own telemetry so you can verify, on real\n * traffic, that this task is actually staying under `timeSliceMs`,\n * instead of trusting this package's own benchmark numbers.\n */\n readonly onSlice?: (durationMs: number, processed: number) => void;\n /** Called right after each yield resolves and work is about to resume. Pairs with `onSlice`. */\n readonly onYield?: () => void;\n}\n\n/** One item `onError` chose to skip, in the order it happened. */\nexport interface SkippedChunkItem {\n readonly index: number;\n readonly error: unknown;\n}\n\n/**\n * Fields every status shares. Split out so the union below only has to spell\n * out what actually differs per status: whether `results` is guaranteed yet,\n * and whether `error` is.\n */\ninterface ChunkResultShared {\n /** Items `onError` skipped. Omitted when nothing was skipped. */\n readonly skipped?: readonly SkippedChunkItem[];\n /**\n * The `AbortSignal`'s `reason`, present only when this cancellation came\n * from that signal aborting rather than a direct `cancel()` call.\n */\n readonly reason?: unknown;\n}\n\n/**\n * Discriminated on `status`: narrowing to `'error'` narrows `error` to\n * `unknown` (defined), and narrowing to `'done' | 'cancelled'` rules it out\n * entirely — `results` is a plain array either way, never `undefined`, since\n * a settled task has always published it by the time anyone can observe\n * this type.\n */\nexport type ChunkSchedulerResult<R> = ChunkResultShared &\n (\n | {\n readonly status: 'done' | 'cancelled';\n readonly results: readonly R[];\n readonly error?: undefined;\n }\n | {\n readonly status: 'error';\n readonly results: readonly R[];\n readonly error: unknown;\n }\n );\n\n/**\n * Discriminated on `status`: narrowing to `'done' | 'cancelled' | 'error'`\n * narrows `results` to a defined array — exactly the statuses `isTerminal()`\n * recognizes, and exactly when the engine has actually published it. Narrow\n * to `'error'` specifically to also narrow `error` itself to defined.\n */\nexport type ChunkSnapshot<R = unknown> = ChunkResultShared &\n (\n | {\n readonly status: 'idle' | 'processing' | 'paused';\n readonly processed: number;\n readonly total: number;\n readonly results?: undefined;\n readonly error?: undefined;\n }\n | {\n readonly status: 'done' | 'cancelled';\n readonly processed: number;\n readonly total: number;\n readonly results: readonly R[];\n readonly error?: undefined;\n }\n | {\n readonly status: 'error';\n readonly processed: number;\n readonly total: number;\n readonly results: readonly R[];\n readonly error: unknown;\n }\n );\n\nexport interface ChunkScheduler<R> {\n start(): void;\n pause(): void;\n resume(): void;\n cancel(): void;\n getSnapshot(): ChunkSnapshot<R>;\n /**\n * The accumulated results, but only once the task has reached a terminal\n * state — `undefined` while idle/processing/paused. Computed once, cheaply,\n * at the moment of that transition; never recomputed on every item, so\n * reading it (e.g. every time the snapshot changes) never costs an O(n)\n * array copy per item for a large task. For incremental consumption while\n * the task is still running, use the `onItem` option instead.\n */\n getResults(): readonly R[] | undefined;\n subscribe(listener: () => void): () => void;\n /** Resolves exactly once, when the task reaches a terminal state. Never rejects. */\n readonly settled: Promise<ChunkSchedulerResult<R>>;\n}\n\n/**\n * `items[index]` for an index the caller has already bounds-checked.\n *\n * The loop below only ever indexes below `items.length`, but\n * `noUncheckedIndexedAccess` widens every index read to `T | undefined`,\n * and `T` may itself include `undefined` — so no runtime check can narrow\n * it. This states the invariant in one place instead of asserting at each\n * use, and compiles to a plain index read.\n */\nfunction at<T>(items: readonly T[], index: number): T {\n return items[index] as T;\n}\n\n/**\n * Loose, `typeof`-only thenable check — the same test `await` itself uses,\n * not an `instanceof Promise` (which would miss any other Promise-like:\n * a different realm's Promise, a userland polyfill). Good enough for a\n * one-shot development warning; never throws on anything a caller could\n * plausibly have returned.\n */\nfunction isThenable(value: unknown): boolean {\n return (\n value !== null &&\n typeof value === 'object' &&\n typeof (value as { then?: unknown }).then === 'function'\n );\n}\n\n/**\n * Builds a one-shot, controllable scheduler that processes `items` through\n * `processItem`, yielding to the main thread on a time budget (and sooner,\n * if the browser reports pending input) so a large array never produces a\n * single long task.\n *\n * One-shot means exactly that: once the task reaches `done`, `cancelled` or\n * `error`, this instance is finished — start()/pause()/resume() on it are\n * no-ops from then on. To run another task, create another scheduler. For a\n * React hook, that falls out naturally: new deps → new effect → new\n * scheduler, which is the idiomatic React data-flow anyway.\n */\nexport function createChunkScheduler<T, R>(\n options: ChunkSchedulerOptions<T, R>,\n): ChunkScheduler<R> {\n const { items, processItem, onItem, onError, signal, yieldStrategy, onSlice, onYield } =\n options;\n const timeSliceMs = normalizeTimeSliceMs(options.timeSliceMs);\n const batchSize = normalizeBatchSize(options.batchSize);\n const collectResults = options.collectResults ?? true;\n const total = items.length;\n\n if (isDevelopment && !collectResults && onItem === undefined) {\n warnDev(\n 'collectResults is false and no onItem was passed — every result will be computed and then discarded, with nothing to retrieve once the task settles. Pass onItem to consume results as they finish, or drop collectResults: false if you need the final array.',\n );\n }\n\n // Resolved once per scheduler, not per item: the feature detection behind\n // it walks `navigator.scheduling.isInputPending` through optional calls,\n // which is wasted work on every item of a million-item task.\n const inputPending = resolveInputPending();\n const diagnostics = createDiagnostics('item', batchSize);\n let warnedAboutAsyncResult = false;\n\n let machineState: ChunkMachineState = initialChunkState;\n let processed = 0;\n let loopStarted = false;\n let settledFlag = false;\n const results: R[] = [];\n let finalResults: readonly R[] | undefined;\n const skipped: SkippedChunkItem[] = [];\n let cancelReason: unknown;\n\n // Built on demand rather than after every item. useSyncExternalStore\n // requires repeated calls between notifications to return the *same*\n // reference, which `snapshot` holds until something actually changes;\n // `snapshotStale` is what marks it as needing a rebuild. Allocating a\n // fresh snapshot per item (as this once did) is pure garbage for a task\n // that only publishes progress at slice boundaries.\n // `status` here is the wide `ChunkStatus`, not yet one of the three\n // terminal literals, so this literal can't be checked against the\n // discriminated union structurally — same invariant-the-type-system-\n // can't-see situation as `at()` above. True at construction: a fresh\n // machine starts idle, with no results to have published yet.\n let snapshot = { status: machineState.status, processed, total } as ChunkSnapshot<R>;\n let snapshotStale = false;\n const listeners = new Set<() => void>();\n\n let settleFn!: (result: ChunkSchedulerResult<R>) => void;\n const settledPromise = new Promise<ChunkSchedulerResult<R>>((resolve) => {\n settleFn = resolve;\n });\n\n // The gate a paused loop blocks on. Recreated per pause; opened by either\n // resume() or cancel() so a cancelled-while-paused task can never hang.\n let pauseResolve: (() => void) | undefined;\n let pausePromise: Promise<void> | undefined;\n\n function waitForWake(): Promise<void> {\n pausePromise ??= new Promise((resolve) => {\n pauseResolve = resolve;\n });\n return pausePromise;\n }\n\n function openGate(): void {\n pauseResolve?.();\n pauseResolve = undefined;\n pausePromise = undefined;\n }\n\n function notify(): void {\n for (const listener of listeners) listener();\n }\n\n function getSnapshot(): ChunkSnapshot<R> {\n if (snapshotStale) {\n snapshotStale = false;\n // Same invariant as the constructor above: `results`/`error` are\n // only ever populated together with a terminal `status` (see\n // `publishResults()` and `dispatch()`'s callers), which is what the\n // discriminated union actually requires — but it's enforced by this\n // function's own call order, not by anything the type checker can\n // read off these independent conditionals.\n snapshot = {\n status: machineState.status,\n processed,\n total,\n ...(machineState.error !== undefined ? { error: machineState.error } : {}),\n ...(finalResults !== undefined ? { results: finalResults } : {}),\n ...(skipped.length > 0 ? { skipped: skipped.slice() } : {}),\n ...(cancelReason !== undefined ? { reason: cancelReason } : {}),\n } as ChunkSnapshot<R>;\n }\n return snapshot;\n }\n\n function dispatch(event: ChunkEvent): { changed: boolean } {\n const next = transition(machineState, event);\n const changed = next !== machineState;\n if (changed) {\n machineState = next;\n snapshotStale = true;\n notify();\n }\n return { changed };\n }\n\n function settle(result: ChunkSchedulerResult<R>): void {\n // Structurally unreachable, not merely untested: `finish()` — the only\n // caller of `settle()` — is itself only ever reached from one of two\n // mutually exclusive places, each gated by `loopStarted`: runLoop()'s\n // own completion (which only runs after `loopStarted` was already set\n // true by start()) or settleIfNeverStarted() (which refuses to run\n // once `loopStarted` is true). Either way, by the time this instance\n // can ever reach a terminal state the second time, there is no longer\n // any code path left that would call settle() again. Kept anyway as\n // defense-in-depth against a future refactor breaking that invariant.\n /* v8 ignore next */\n if (settledFlag) return;\n settledFlag = true;\n detachSignal();\n settleFn(result);\n }\n\n /**\n * Publishes the accumulated results exactly once, at the moment the task\n * settles — not per item, and never as a second copy. Always called\n * *before* the terminal status is announced, so the notification that\n * reports `done` already carries the results: a subscriber can never\n * observe one without the other.\n */\n function publishResults(): readonly R[] {\n finalResults = results;\n snapshotStale = true;\n return finalResults;\n }\n\n function settleWith(\n status: 'done' | 'cancelled' | 'error',\n published: readonly R[],\n error?: unknown,\n ): void {\n // `error` is only ever passed for `status === 'error'` (see this\n // function's two call sites), which is the exact pairing the\n // discriminated union requires — invisible to the checker because it's\n // enforced by the caller, not by anything in this object literal.\n settle({\n status,\n results: published,\n ...(error !== undefined ? { error } : {}),\n ...(skipped.length > 0 ? { skipped: skipped.slice() } : {}),\n ...(status === 'cancelled' && cancelReason !== undefined\n ? { reason: cancelReason }\n : {}),\n } as ChunkSchedulerResult<R>);\n }\n\n /**\n * Settling as cancelled, for both places that can do it: the loop\n * noticing, and cancel() on a task whose loop never ran. The CANCEL\n * transition was announced earlier, before there were any results to\n * carry, so this notifies again now that there are.\n */\n function finishCancelled(): void {\n settleWith('cancelled', publishResults());\n notify();\n }\n\n // If the machine reached a terminal state without the processing loop\n // ever running (e.g. cancel() called before start(), or the signal was\n // already aborted), the loop will never call finish() itself — so this\n // does it. Guarded by !loopStarted, which this also sets, so it can never\n // race with (or double-finish against) the loop's own completion path.\n function settleIfNeverStarted(): void {\n const { status } = machineState;\n if (loopStarted || !isTerminal(status)) return;\n loopStarted = true;\n // 'cancelled' is the only status reachable here: 'done' and 'error'\n // both require the loop to have run, and that sets `loopStarted`,\n // which short-circuits this function above.\n finishCancelled();\n }\n\n function handleAbort(): void {\n cancelReason = signal?.reason;\n cancel();\n }\n\n function detachSignal(): void {\n signal?.removeEventListener('abort', handleAbort);\n }\n\n if (signal?.aborted) {\n cancelReason = signal.reason;\n dispatch({ type: 'CANCEL' });\n } else {\n signal?.addEventListener('abort', handleAbort);\n }\n\n async function runLoop(): Promise<void> {\n let sliceStartedAt = now();\n let index = 0;\n\n try {\n while (index < total) {\n // Re-read on every pass rather than trusting a single wake from the\n // pause gate: a resume() can be undone by a *reentrant* pause()\n // triggered synchronously from a subscriber's own notification\n // (resume() dispatches, notify() runs listeners synchronously, a\n // listener calls pause() before resume() returns). Continuing the\n // loop instead of falling through is what lets this see that and\n // block again on a fresh gate, without consuming an item.\n const status: ChunkStatus = machineState.status;\n if (status === 'cancelled') break;\n if (status === 'paused') {\n await waitForWake();\n sliceStartedAt = now();\n if (isDevelopment) diagnostics.resumed(sliceStartedAt, index);\n continue;\n }\n\n // One batch between clock checks. At the default batchSize of 1\n // this is exactly \"one item, then check\", and the inner loop costs\n // nothing beyond the comparison.\n const batchEnd = index + batchSize <= total ? index + batchSize : total;\n while (index < batchEnd) {\n const item = at(items, index);\n let result: R;\n try {\n result = processItem(item, index);\n } catch (caught) {\n // Default ('abort', same as omitting onError) rethrows into the\n // outer try below, so a caller with no onError sees exactly the\n // same error-and-stop behavior this always had.\n if ((onError?.(caught, item, index) ?? 'abort') !== 'skip') throw caught;\n skipped.push({ index, error: caught });\n processed = index + 1;\n snapshotStale = true;\n index += 1;\n continue;\n }\n if (isDevelopment && !warnedAboutAsyncResult && isThenable(result)) {\n warnedAboutAsyncResult = true;\n warnDev(\n 'processItem returned a Promise. createChunkScheduler calls it synchronously and never awaits the result, so results will contain unresolved Promise objects instead of the values they resolve to. Resolve the value before returning it, or chunk the async work a different way.',\n );\n }\n if (collectResults) results.push(result);\n processed = index + 1;\n onItem?.(result, item, index);\n index += 1;\n }\n snapshotStale = true;\n\n // One clock read per batch, shared by the budget check and the\n // development diagnostics. Both diagnostic calls compile away in a\n // production build, along with this module.\n const checkedAt = now();\n if (isDevelopment) diagnostics.check(checkedAt, index);\n\n if (checkedAt - sliceStartedAt >= timeSliceMs || inputPending()) {\n if (isDevelopment) diagnostics.sliceEnded(checkedAt, index);\n onSlice?.(checkedAt - sliceStartedAt, index);\n await yieldToMain(\n yieldStrategy !== undefined ? { strategy: yieldStrategy } : undefined,\n );\n notify(); // surface the progress made during this slice\n sliceStartedAt = now();\n onYield?.();\n if (isDevelopment) diagnostics.resumed(sliceStartedAt, index);\n }\n }\n } catch (error) {\n const published = publishResults();\n dispatch({ type: 'ERROR', error });\n settleWith('error', published, error);\n return;\n }\n\n if (machineState.status === 'cancelled') {\n finishCancelled();\n return;\n }\n\n const published = publishResults();\n dispatch({ type: 'COMPLETE' });\n settleWith('done', published);\n }\n\n function start(): void {\n dispatch({ type: 'START' });\n if (machineState.status === 'processing' && !loopStarted) {\n loopStarted = true;\n void runLoop();\n }\n settleIfNeverStarted();\n }\n\n function pause(): void {\n dispatch({ type: 'PAUSE' });\n }\n\n function resume(): void {\n const { changed } = dispatch({ type: 'RESUME' });\n if (changed) openGate();\n }\n\n function cancel(): void {\n dispatch({ type: 'CANCEL' });\n openGate();\n settleIfNeverStarted();\n }\n\n return {\n start,\n pause,\n resume,\n cancel,\n getSnapshot,\n getResults: () => finalResults,\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n settled: settledPromise,\n };\n}\n","import {\n type ChunkEvent,\n type ChunkMachineState,\n type ChunkStatus,\n initialChunkState,\n isTerminal,\n transition,\n} from './chunk-machine.js';\nimport { createDiagnostics, isDevelopment } from './diagnostics.js';\nimport {\n type YieldStrategy,\n normalizeBatchSize,\n normalizeTimeSliceMs,\n now,\n resolveInputPending,\n yieldToMain,\n} from './scheduler.js';\n\n/**\n * A generator is how you tell `createDeferredTask` where it's safe to yield\n * — arbitrary heavy work (a multi-phase algorithm, a big canvas render)\n * doesn't have a natural \"array of items\" shape the way `createChunkScheduler`\n * expects, so instead you mark checkpoints yourself:\n *\n * ```ts\n * function* renderFrames(frameCount: number) {\n * for (let i = 0; i < frameCount; i++) {\n * renderOneExpensiveFrame(i);\n * yield; // \"it's safe to pause here if we're over budget\"\n * }\n * return frameCount;\n * }\n * ```\n *\n * Everything between two `yield`s runs as one uninterruptible synchronous\n * segment — put them wherever your function can safely stop, the same way\n * you'd think about chunk boundaries by hand, just without writing the\n * scheduling code yourself.\n */\nexport type DeferredGenerator<T> = Generator<void, T, void>;\n\nexport interface DeferredTaskOptions<T> {\n readonly task: () => DeferredGenerator<T>;\n /** Max ms of uninterrupted work before yielding. Default 5. */\n readonly timeSliceMs?: number;\n /**\n * Segments (the code between two `yield`s) to run between clock checks.\n * Default 1, which bounds a slice at one segment.\n *\n * Reading the clock costs around 300ns in a browser, so a generator that\n * yields after a few nanoseconds of work spends most of its time asking\n * the time. Raising this amortizes that — at the cost of checking the\n * clock, pause and cancel only at batch boundaries, so a slice can run up\n * to `batchSize` segments past its budget.\n */\n readonly batchSize?: number;\n readonly signal?: AbortSignal;\n /** Testing-only: forces a specific yield strategy instead of auto-detecting one. */\n readonly yieldStrategy?: YieldStrategy;\n /**\n * Called every time a segment-batch ends and the task is about to yield,\n * with how long that slice ran and how many segments are done so far.\n * Ships in the production build — see the identical option on\n * `createChunkScheduler` for why.\n */\n readonly onSlice?: (durationMs: number, segmentsDone: number) => void;\n /** Called right after each yield resolves and work is about to resume. Pairs with `onSlice`. */\n readonly onYield?: () => void;\n}\n\n/** Fields every status shares — just `reason`, since `error` and `value` differ by status. */\ninterface DeferredResultShared {\n /**\n * The `AbortSignal`'s `reason`, present only when this cancellation came\n * from that signal aborting rather than a direct `cancel()` call.\n */\n readonly reason?: unknown;\n}\n\n/**\n * Discriminated on `status`: narrowing to `'done'` narrows `value` to `T`\n * itself, not `T | undefined` — including when `T` allows `undefined`, a\n * generator genuinely returning it is still a value, distinct from \"no\n * value yet\". Narrowing to `'error'` narrows `error` to defined instead.\n */\nexport type DeferredTaskResult<T> = DeferredResultShared &\n (\n | { readonly status: 'done'; readonly value: T; readonly error?: undefined }\n | {\n readonly status: 'cancelled';\n readonly value?: undefined;\n readonly error?: undefined;\n }\n | { readonly status: 'error'; readonly value?: undefined; readonly error: unknown }\n );\n\n/**\n * Same discrimination as `DeferredTaskResult`, plus the non-terminal\n * statuses a live snapshot can still be in, where neither `value` nor\n * `error` exists yet.\n */\nexport type DeferredTaskSnapshot<T = unknown> = DeferredResultShared &\n (\n | {\n readonly status: 'idle' | 'processing' | 'paused';\n readonly value?: undefined;\n readonly error?: undefined;\n }\n | { readonly status: 'done'; readonly value: T; readonly error?: undefined }\n | {\n readonly status: 'cancelled';\n readonly value?: undefined;\n readonly error?: undefined;\n }\n | { readonly status: 'error'; readonly value?: undefined; readonly error: unknown }\n );\n\nexport interface DeferredTask<T> {\n start(): void;\n pause(): void;\n resume(): void;\n cancel(): void;\n getSnapshot(): DeferredTaskSnapshot<T>;\n /** The returned value once the task has settled with `done` — `undefined` otherwise. */\n getValue(): T | undefined;\n subscribe(listener: () => void): () => void;\n readonly settled: Promise<DeferredTaskResult<T>>;\n}\n\n/**\n * The generator-driven sibling of `createChunkScheduler`, for heavy work\n * that isn't shaped like \"one function applied to each item of an array\".\n * Same state machine, same yield primitives, same one-shot-instance\n * contract — see that file's doc comment for the reasoning, which applies\n * here unchanged.\n */\nexport function createDeferredTask<T>(options: DeferredTaskOptions<T>): DeferredTask<T> {\n const { task, signal, yieldStrategy, onSlice, onYield } = options;\n const timeSliceMs = normalizeTimeSliceMs(options.timeSliceMs);\n const batchSize = normalizeBatchSize(options.batchSize);\n // Resolved once per task rather than per segment; see chunk-scheduler.ts.\n const inputPending = resolveInputPending();\n const diagnostics = createDiagnostics('segment', batchSize);\n\n let machineState: ChunkMachineState = initialChunkState;\n let loopStarted = false;\n let settledFlag = false;\n let settledValue: T | undefined;\n // Separate from `settledValue !== undefined` below: a generator whose `T`\n // allows `undefined` can genuinely return it, and that's still a value —\n // conflating \"returned undefined\" with \"hasn't returned yet\" is exactly\n // the bug this flag exists to avoid.\n let hasSettledValue = false;\n let cancelReason: unknown;\n\n // Built on demand, and only when something has actually changed.\n // useSyncExternalStore requires repeated calls between notifications to\n // return the *same* reference; a fresh object literal on every call (as\n // this used to be) reads as \"changed every render\" and sends React into\n // an infinite re-render loop. Mirrors chunk-scheduler.ts exactly.\n // Wide `status` at construction time, so this can't be checked\n // structurally against the discriminated union — true regardless: a\n // fresh task starts idle, before anything could have published a value.\n let snapshot = { status: machineState.status } as DeferredTaskSnapshot<T>;\n let snapshotStale = false;\n\n const listeners = new Set<() => void>();\n\n let settleFn!: (result: DeferredTaskResult<T>) => void;\n const settledPromise = new Promise<DeferredTaskResult<T>>((resolve) => {\n settleFn = resolve;\n });\n\n let pauseResolve: (() => void) | undefined;\n let pausePromise: Promise<void> | undefined;\n\n function waitForWake(): Promise<void> {\n pausePromise ??= new Promise((resolve) => {\n pauseResolve = resolve;\n });\n return pausePromise;\n }\n\n function openGate(): void {\n pauseResolve?.();\n pauseResolve = undefined;\n pausePromise = undefined;\n }\n\n function notify(): void {\n for (const listener of listeners) listener();\n }\n\n function getSnapshot(): DeferredTaskSnapshot<T> {\n if (snapshotStale) {\n snapshotStale = false;\n // Same invariant as the constructor above: `value`/`error` are only\n // ever populated together with a terminal `status`, enforced by this\n // function's own call order rather than visible to the checker here.\n snapshot = {\n status: machineState.status,\n ...(machineState.error !== undefined ? { error: machineState.error } : {}),\n ...(hasSettledValue ? { value: settledValue } : {}),\n ...(cancelReason !== undefined ? { reason: cancelReason } : {}),\n } as DeferredTaskSnapshot<T>;\n }\n return snapshot;\n }\n\n function dispatch(event: ChunkEvent): { changed: boolean } {\n const next = transition(machineState, event);\n const changed = next !== machineState;\n if (changed) {\n machineState = next;\n snapshotStale = true;\n notify();\n }\n return { changed };\n }\n\n function settle(result: DeferredTaskResult<T>): void {\n // Structurally unreachable, not merely untested — see the identical\n // guard in chunk-scheduler.ts's settle() for the full invariant: the\n // only caller, finish(), is reached from exactly one of two mutually\n // exclusive, `loopStarted`-gated paths per instance, so there is never\n // a second call left to make once this instance has settled once.\n // Kept as defense-in-depth against a future refactor breaking that.\n /* v8 ignore next */\n if (settledFlag) return;\n settledFlag = true;\n detachSignal();\n settleFn(result);\n }\n\n /**\n * Publishes the returned value before the terminal status is announced,\n * so the notification that reports `done` already carries it.\n */\n function publishValue(resolvedValue: T | undefined): void {\n settledValue = resolvedValue;\n hasSettledValue = true;\n snapshotStale = true;\n }\n\n function settleWith(\n status: 'done' | 'cancelled' | 'error',\n resolvedValue?: T,\n error?: unknown,\n ): void {\n if (status === 'done') {\n // Always include `value`, even when `resolvedValue` is `undefined`:\n // that's still a real returned value (see `hasSettledValue` above),\n // and the discriminated union's `'done'` branch requires the key\n // present — invisible to the checker since `resolvedValue`'s static\n // type is `T | undefined` regardless of which case this actually is.\n settle({ status, value: resolvedValue } as DeferredTaskResult<T>);\n return;\n }\n if (status === 'error') {\n settle({ status, error });\n return;\n }\n settle({ status, ...(cancelReason !== undefined ? { reason: cancelReason } : {}) });\n }\n\n /**\n * Settling as cancelled, for both places that can do it. The CANCEL\n * transition was announced earlier, so this notifies again now that the\n * task has actually settled.\n */\n function finishCancelled(): void {\n settleWith('cancelled');\n notify();\n }\n\n function settleIfNeverStarted(): void {\n const { status } = machineState;\n if (loopStarted || !isTerminal(status)) return;\n loopStarted = true;\n // 'cancelled' is the only status reachable here: 'done' and 'error'\n // both require the loop to have run, and that sets `loopStarted`,\n // which short-circuits this function above.\n finishCancelled();\n }\n\n function handleAbort(): void {\n cancelReason = signal?.reason;\n cancel();\n }\n\n function detachSignal(): void {\n signal?.removeEventListener('abort', handleAbort);\n }\n\n if (signal?.aborted) {\n cancelReason = signal.reason;\n dispatch({ type: 'CANCEL' });\n } else {\n signal?.addEventListener('abort', handleAbort);\n }\n\n async function runLoop(): Promise<void> {\n let sliceStartedAt = now();\n let segment = 0;\n const generator = task();\n\n // `step` always holds the result of the *last* generator.next() call —\n // including, on an early break below, a stale not-done step from before\n // that break, which the post-loop checks account for explicitly.\n let step: IteratorResult<void, T> = { done: false, value: undefined };\n try {\n for (;;) {\n // Captured once per iteration and compared via this local, not via\n // repeated `machineState.status` property reads — narrowing a\n // direct property access to a literal here would otherwise survive\n // (in TS's eyes) across the `await` below, even though the whole\n // point of that await is that `machineState` can change out from\n // under us while it's pending.\n const status: ChunkStatus = machineState.status;\n if (status === 'cancelled') {\n generator.return(undefined as unknown as T);\n break;\n }\n\n if (status === 'paused') {\n await waitForWake();\n sliceStartedAt = now();\n if (isDevelopment) diagnostics.resumed(sliceStartedAt, segment);\n // Don't fall through to generator.next() just because the gate\n // opened — re-enter the loop and read machineState.status fresh.\n // A resume() can be undone by a *reentrant* pause() triggered\n // synchronously from a subscriber's own notification (resume()\n // dispatches, notify() runs listeners synchronously, a listener\n // calls pause() before resume() returns) — so waking from one\n // gate is not proof we're actually clear to proceed. Looping\n // back here is what lets the top-of-loop checks see that and\n // either re-block on a fresh gate (status is paused again) or\n // notice cancellation, instead of blindly running one more\n // generator step first.\n continue;\n }\n\n // The cancellation check above MUST run before this call, not\n // after: calling the generator is where its actual (synchronous)\n // work for this segment happens. An earlier draft called\n // generator.next() at the *bottom* of the loop, right after the\n // yieldToMain() await below — so a cancel() landing during that\n // await would still let one more segment's worth of work run\n // before the next loop iteration finally noticed. Checking first,\n // every time, closes that race: nothing the generator does can run\n // once cancellation has been observed.\n // One batch of segments between clock checks. At the default\n // batchSize of 1 this runs exactly one segment, then checks.\n let done = false;\n for (let inBatch = 0; inBatch < batchSize; inBatch++) {\n step = generator.next();\n segment += 1;\n if (step.done) {\n done = true;\n break;\n }\n }\n if (done) break;\n\n // One clock read per batch, shared by the budget check and the\n // development diagnostics, which compile away in production.\n const checkedAt = now();\n if (isDevelopment) diagnostics.check(checkedAt, segment);\n\n if (checkedAt - sliceStartedAt >= timeSliceMs || inputPending()) {\n if (isDevelopment) diagnostics.sliceEnded(checkedAt, segment);\n onSlice?.(checkedAt - sliceStartedAt, segment);\n await yieldToMain(\n yieldStrategy !== undefined ? { strategy: yieldStrategy } : undefined,\n );\n notify();\n sliceStartedAt = now();\n onYield?.();\n if (isDevelopment) diagnostics.resumed(sliceStartedAt, segment);\n }\n }\n } catch (error) {\n dispatch({ type: 'ERROR', error });\n settleWith('error', undefined, error);\n return;\n }\n\n if (machineState.status === 'cancelled') {\n finishCancelled();\n return;\n }\n\n // step.done is always true by the time we get here: the only ways out\n // of the loop above are the top-of-loop 'cancelled' break (handled by\n // the early return just above, before this line) and the batch loop's\n // own `done` break — so the \": undefined\" side of this ternary can't\n // actually run. It stays only because it's what makes TypeScript\n // narrow step.value's type correctly; the real type is kept rather\n // than cast away.\n /* v8 ignore next */\n const returned = step.done ? step.value : undefined;\n publishValue(returned);\n dispatch({ type: 'COMPLETE' });\n settleWith('done', returned);\n }\n\n function start(): void {\n dispatch({ type: 'START' });\n if (machineState.status === 'processing' && !loopStarted) {\n loopStarted = true;\n void runLoop();\n }\n settleIfNeverStarted();\n }\n\n function pause(): void {\n dispatch({ type: 'PAUSE' });\n }\n\n function resume(): void {\n const { changed } = dispatch({ type: 'RESUME' });\n if (changed) openGate();\n }\n\n function cancel(): void {\n dispatch({ type: 'CANCEL' });\n openGate();\n settleIfNeverStarted();\n }\n\n return {\n start,\n pause,\n resume,\n cancel,\n getSnapshot,\n getValue: () => settledValue,\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n settled: settledPromise,\n };\n}\n"],"mappings":";AAyCA,MAAa,oBAAuC,EAAE,QAAQ,OAAO;AAIrE,MAAM,oCAA8C,IAAI,IAAiB;CACvE;CACA;CACA;AACF,CAAC;AAED,SAAgB,WAAW,QAAoD;CAC7E,OAAO,kBAAkB,IAAI,MAAM;AACrC;AAEA,SAAS,eAAe,OAA0B,OAAsC;CACtF,QAAQ,MAAM,MAAd;EACE,KAAK,SACH,OAAO,EAAE,QAAQ,aAAa;EAChC,KAAK,UACH,OAAO,EAAE,QAAQ,YAAY;EAC/B,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,SACH,OAAO;CACX;AACF;AAEA,SAAS,qBACP,OACA,OACmB;CACnB,QAAQ,MAAM,MAAd;EACE,KAAK,SACH,OAAO,EAAE,QAAQ,SAAS;EAC5B,KAAK,UACH,OAAO,EAAE,QAAQ,YAAY;EAC/B,KAAK,YACH,OAAO,EAAE,QAAQ,OAAO;EAC1B,KAAK,SACH,OAAO;GAAE,QAAQ;GAAS,OAAO,MAAM;EAAM;EAC/C,KAAK;EACL,KAAK,UACH,OAAO;CACX;AACF;AAEA,SAAS,iBACP,OACA,OACmB;CACnB,QAAQ,MAAM,MAAd;EACE,KAAK,UACH,OAAO,EAAE,QAAQ,aAAa;EAChC,KAAK,UACH,OAAO,EAAE,QAAQ,YAAY;EAC/B,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,SACH,OAAO;CACX;AACF;;;;;AAMA,SAAgB,WACd,OACA,OACmB;CACnB,QAAQ,MAAM,QAAd;EACE,KAAK,QACH,OAAO,eAAe,OAAO,KAAK;EACpC,KAAK,cACH,OAAO,qBAAqB,OAAO,KAAK;EAC1C,KAAK,UACH,OAAO,iBAAiB,OAAO,KAAK;EACtC,KAAK;EACL,KAAK;EACL,KAAK,SAGH,OAAO;CACX;AACF;;;;;;;;;;;;;;;;;;;;;AC3GA,MAAa,gBACX,OAAO,YAAY,eAAe,QAAQ,IAAI,gBAAgB;;AAGhE,MAAM,eAAe;;AAGrB,MAAM,sBAAsB;;AAG5B,MAAM,qBAAqB;;AAG3B,MAAM,kBAAkB;AACxB,MAAM,sBAAsB;AAW5B,SAAS,KAAK,SAAuB;CACnC,QAAQ,KAAK,uBAAuB,SAAS;AAC/C;;;;;;;;;;;;AAaA,SAAgB,QAAQ,SAAuB;CAC7C,KAAK,OAAO;AACd;;;;;;;AAQA,SAAS,mBAAmB,YAA4B;CACtD,MAAM,SAAS,KAAK,MAAM,kBAAkB,UAAU;CACtD,OAAO,SAAS,sBAAsB,sBAAsB;AAC9D;;;;;AAMA,SAAgB,kBACd,MACA,WACa;CACb,IAAI,cAAc;CAClB,IAAI,iBAAiB;CACrB,IAAI,iBAAiB;CACrB,IAAI,kBAAkB;CACtB,IAAI,UAAU;CACd,IAAI,sBAAsB;CAC1B,IAAI,uBAAuB;CAE3B,MAAM,UAAU,IAAY,cAA4B;EACtD,cAAc;EACd,iBAAiB;EACjB,iBAAiB;EACjB,kBAAkB;EAClB,UAAU;CACZ;CAEA,OAAO;EACL,MAAM,IAAI,WAAW;GACnB,IAAI,CAAC,SAAS;IACZ,OAAO,IAAI,SAAS;IACpB;GACF;GACA,MAAM,UAAU,KAAK;GACrB,MAAM,eAAe,YAAY;GACjC,cAAc;GACd,iBAAiB;GAEjB,IAAI,uBAAuB,WAAW,cAAc;GACpD,sBAAsB;GAStB,KACE,GARA,iBAAiB,IACb,OAAO,KAAK,UAAU,OAAO,YAAY,CAAC,EAAE,SAAS,QAAQ,QAAQ,CAAC,EAAE,MACxE,cAAc,OAAO,YAAY,EAAE,GAAG,KAAK,SAAS,QAAQ,QAAQ,CAAC,EAAE,IAMlE,kFAJT,SAAS,SACL,oHACA,qDAE2F,oBACjG;EACF;EAEA,WAAW,IAAI,WAAW;GACxB,MAAM,UAAU,KAAK;GACrB,MAAM,eAAe,YAAY;GACjC,iBAAiB;GACjB,kBAAkB;GAElB,IAAI,wBAAwB,YAAY,GAAG;GAC3C,IAAI,eAAe,sBAAsB,WAAW,GAAG;GACvD,MAAM,aAAa,UAAU;GAC7B,IAAI,cAAc,qBAAqB;GACvC,uBAAuB;GAEvB,KACE,GAAG,KAAK,mBAFM,aAAa,IAAA,CAAM,QAAQ,CAEV,EAAE,4FAA4F,OAAO,mBAAmB,UAAU,CAAC,EAAE,qDACtK;EACF;EAEA,QAAQ,IAAI,WAAW;GACrB,OAAO,IAAI,SAAS;EACtB;CACF;AACF;;;ACvHA,MAAa,wBAAwB;;AAiBrC,SAAgB,yBAAkC;CAEhD,OAAO,OADkB,WAAoC,WAC9B,UAAU;AAC3C;;AAGA,SAAgB,yBAAkC;CAChD,OAAO,OAAO,mBAAmB;AACnC;;AAGA,SAAgB,uBAAgC;CAE9C,OAAO,OADkB,WAAyC,WACnC,YAAY,mBAAmB;AAChE;;;;;;;;AASA,SAAgB,sBAAqC;CACnD,OAAO,qBAAqB,IAAI,iBAAiB;AACnD;AAEA,SAAS,eAAwB;CAC/B,OAAO;AACT;;;;;;AAOA,SAAgB,iBAA0B;CACxC,IAAI;EAEF,OADyB,WAAyC,WAC1C,YAAY,iBAAiB,KAAK;CAC5D,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,SAAgB,uBAAsC;CACpD,IAAI,uBAAuB,GAAG,OAAO;CACrC,IAAI,uBAAuB,GAAG,OAAO;CACrC,OAAO;AACT;AAEA,SAAS,yBAAwC;CAC/C,OAAO,IAAI,SAAS,YAAY;EAQ9B,MAAM,UAAU,IAAI,eAAe;EACnC,QAAQ,MAAM,kBAAkB;GAK9B,QAAQ,MAAM,MAAM;GACpB,QAAQ;EACV;EACA,QAAQ,MAAM,YAAY,IAAI;CAChC,CAAC;AACH;AAEA,SAAS,qBAAoC;CAC3C,OAAO,IAAI,SAAS,YAAY;EAC9B,WAAW,SAAS,CAAC;CACvB,CAAC;AACH;;;;;;;;;AAmBA,eAAsB,YAAY,SAA6C;CAC7E,MAAM,WAAW,SAAS,YAAY,qBAAqB;CAE3D,IAAI,aAAa,mBAAmB;EAClC,MAAM,kBAAmB,WAAoC;EAC7D,IAAI,OAAO,iBAAiB,UAAU,YACpC,IAAI;GACF,MAAM,gBAAgB,MAAM;GAC5B;EACF,QAAQ,CAER;EAEF,OAAO,uBAAuB,IAAI,uBAAuB,IAAI,mBAAmB;EAChF;CACF;CAEA,IAAI,aAAa,mBAAmB;EAClC,IAAI,uBAAuB,GAAG;GAC5B,MAAM,uBAAuB;GAC7B;EACF;EACA,MAAM,mBAAmB;EACzB;CACF;CAEA,MAAM,mBAAmB;AAC3B;;AAGA,SAAgB,MAAc;CAC5B,IAAI;EACF,IAAI,OAAO,gBAAgB,eAAe,OAAO,YAAY,QAAQ,YACnE,OAAO,YAAY,IAAI;CAE3B,QAAQ,CAER;CACA,OAAO,KAAK,IAAI;AAClB;;;;;;AAOA,SAAgB,qBAAqB,OAAmC;CACtE,IAAI,OAAO,UAAU,YAAY,OAAO,MAAM,KAAK,KAAK,SAAS,GAC/D,OAAA;CAEF,OAAO;AACT;;;;;AAMA,SAAgB,mBAAmB,OAAmC;CACpE,IAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,GAAG,OAAO;CAC9E,OAAO,KAAK,MAAM,KAAK;AACzB;;;;;;AAcA,SAAgB,eAAe,SAAsC;CACnE,MAAM,cAAc,QAAQ,eAAA;CAE5B,IADgB,IAAI,IAAI,QAAQ,kBACjB,aAAa,OAAO;CACnC,OAAO,eAAe;AACxB;;;;;;;;;;;ACzCA,SAAS,GAAM,OAAqB,OAAkB;CACpD,OAAO,MAAM;AACf;;;;;;;;AASA,SAAS,WAAW,OAAyB;CAC3C,OACE,UAAU,QACV,OAAO,UAAU,YACjB,OAAQ,MAA6B,SAAS;AAElD;;;;;;;;;;;;;AAcA,SAAgB,qBACd,SACmB;CACnB,MAAM,EAAE,OAAO,aAAa,QAAQ,SAAS,QAAQ,eAAe,SAAS,YAC3E;CACF,MAAM,cAAc,qBAAqB,QAAQ,WAAW;CAC5D,MAAM,YAAY,mBAAmB,QAAQ,SAAS;CACtD,MAAM,iBAAiB,QAAQ,kBAAkB;CACjD,MAAM,QAAQ,MAAM;CAEpB,IAAI,iBAAiB,CAAC,kBAAkB,WAAW,KAAA,GACjD,QACE,gQACF;CAMF,MAAM,eAAe,oBAAoB;CACzC,MAAM,cAAc,kBAAkB,QAAQ,SAAS;CACvD,IAAI,yBAAyB;CAE7B,IAAI,eAAkC;CACtC,IAAI,YAAY;CAChB,IAAI,cAAc;CAClB,IAAI,cAAc;CAClB,MAAM,UAAe,CAAC;CACtB,IAAI;CACJ,MAAM,UAA8B,CAAC;CACrC,IAAI;CAaJ,IAAI,WAAW;EAAE,QAAQ,aAAa;EAAQ;EAAW;CAAM;CAC/D,IAAI,gBAAgB;CACpB,MAAM,4BAAY,IAAI,IAAgB;CAEtC,IAAI;CACJ,MAAM,iBAAiB,IAAI,SAAkC,YAAY;EACvE,WAAW;CACb,CAAC;CAID,IAAI;CACJ,IAAI;CAEJ,SAAS,cAA6B;EACpC,iBAAiB,IAAI,SAAS,YAAY;GACxC,eAAe;EACjB,CAAC;EACD,OAAO;CACT;CAEA,SAAS,WAAiB;EACxB,eAAe;EACf,eAAe,KAAA;EACf,eAAe,KAAA;CACjB;CAEA,SAAS,SAAe;EACtB,KAAK,MAAM,YAAY,WAAW,SAAS;CAC7C;CAEA,SAAS,cAAgC;EACvC,IAAI,eAAe;GACjB,gBAAgB;GAOhB,WAAW;IACT,QAAQ,aAAa;IACrB;IACA;IACA,GAAI,aAAa,UAAU,KAAA,IAAY,EAAE,OAAO,aAAa,MAAM,IAAI,CAAC;IACxE,GAAI,iBAAiB,KAAA,IAAY,EAAE,SAAS,aAAa,IAAI,CAAC;IAC9D,GAAI,QAAQ,SAAS,IAAI,EAAE,SAAS,QAAQ,MAAM,EAAE,IAAI,CAAC;IACzD,GAAI,iBAAiB,KAAA,IAAY,EAAE,QAAQ,aAAa,IAAI,CAAC;GAC/D;EACF;EACA,OAAO;CACT;CAEA,SAAS,SAAS,OAAyC;EACzD,MAAM,OAAO,WAAW,cAAc,KAAK;EAC3C,MAAM,UAAU,SAAS;EACzB,IAAI,SAAS;GACX,eAAe;GACf,gBAAgB;GAChB,OAAO;EACT;EACA,OAAO,EAAE,QAAQ;CACnB;CAEA,SAAS,OAAO,QAAuC;;EAWrD,IAAI,aAAa;EACjB,cAAc;EACd,aAAa;EACb,SAAS,MAAM;CACjB;;;;;;;;CASA,SAAS,iBAA+B;EACtC,eAAe;EACf,gBAAgB;EAChB,OAAO;CACT;CAEA,SAAS,WACP,QACA,WACA,OACM;EAKN,OAAO;GACL;GACA,SAAS;GACT,GAAI,UAAU,KAAA,IAAY,EAAE,MAAM,IAAI,CAAC;GACvC,GAAI,QAAQ,SAAS,IAAI,EAAE,SAAS,QAAQ,MAAM,EAAE,IAAI,CAAC;GACzD,GAAI,WAAW,eAAe,iBAAiB,KAAA,IAC3C,EAAE,QAAQ,aAAa,IACvB,CAAC;EACP,CAA4B;CAC9B;;;;;;;CAQA,SAAS,kBAAwB;EAC/B,WAAW,aAAa,eAAe,CAAC;EACxC,OAAO;CACT;CAOA,SAAS,uBAA6B;EACpC,MAAM,EAAE,WAAW;EACnB,IAAI,eAAe,CAAC,WAAW,MAAM,GAAG;EACxC,cAAc;EAId,gBAAgB;CAClB;CAEA,SAAS,cAAoB;EAC3B,eAAe,QAAQ;EACvB,OAAO;CACT;CAEA,SAAS,eAAqB;EAC5B,QAAQ,oBAAoB,SAAS,WAAW;CAClD;CAEA,IAAI,QAAQ,SAAS;EACnB,eAAe,OAAO;EACtB,SAAS,EAAE,MAAM,SAAS,CAAC;CAC7B,OACE,QAAQ,iBAAiB,SAAS,WAAW;CAG/C,eAAe,UAAyB;EACtC,IAAI,iBAAiB,IAAI;EACzB,IAAI,QAAQ;EAEZ,IAAI;GACF,OAAO,QAAQ,OAAO;IAQpB,MAAM,SAAsB,aAAa;IACzC,IAAI,WAAW,aAAa;IAC5B,IAAI,WAAW,UAAU;KACvB,MAAM,YAAY;KAClB,iBAAiB,IAAI;KACrB,IAAI,eAAe,YAAY,QAAQ,gBAAgB,KAAK;KAC5D;IACF;IAKA,MAAM,WAAW,QAAQ,aAAa,QAAQ,QAAQ,YAAY;IAClE,OAAO,QAAQ,UAAU;KACvB,MAAM,OAAO,GAAG,OAAO,KAAK;KAC5B,IAAI;KACJ,IAAI;MACF,SAAS,YAAY,MAAM,KAAK;KAClC,SAAS,QAAQ;MAIf,KAAK,UAAU,QAAQ,MAAM,KAAK,KAAK,aAAa,QAAQ,MAAM;MAClE,QAAQ,KAAK;OAAE;OAAO,OAAO;MAAO,CAAC;MACrC,YAAY,QAAQ;MACpB,gBAAgB;MAChB,SAAS;MACT;KACF;KACA,IAAI,iBAAiB,CAAC,0BAA0B,WAAW,MAAM,GAAG;MAClE,yBAAyB;MACzB,QACE,oRACF;KACF;KACA,IAAI,gBAAgB,QAAQ,KAAK,MAAM;KACvC,YAAY,QAAQ;KACpB,SAAS,QAAQ,MAAM,KAAK;KAC5B,SAAS;IACX;IACA,gBAAgB;IAKhB,MAAM,YAAY,IAAI;IACtB,IAAI,eAAe,YAAY,MAAM,WAAW,KAAK;IAErD,IAAI,YAAY,kBAAkB,eAAe,aAAa,GAAG;KAC/D,IAAI,eAAe,YAAY,WAAW,WAAW,KAAK;KAC1D,UAAU,YAAY,gBAAgB,KAAK;KAC3C,MAAM,YACJ,kBAAkB,KAAA,IAAY,EAAE,UAAU,cAAc,IAAI,KAAA,CAC9D;KACA,OAAO;KACP,iBAAiB,IAAI;KACrB,UAAU;KACV,IAAI,eAAe,YAAY,QAAQ,gBAAgB,KAAK;IAC9D;GACF;EACF,SAAS,OAAO;GACd,MAAM,YAAY,eAAe;GACjC,SAAS;IAAE,MAAM;IAAS;GAAM,CAAC;GACjC,WAAW,SAAS,WAAW,KAAK;GACpC;EACF;EAEA,IAAI,aAAa,WAAW,aAAa;GACvC,gBAAgB;GAChB;EACF;EAEA,MAAM,YAAY,eAAe;EACjC,SAAS,EAAE,MAAM,WAAW,CAAC;EAC7B,WAAW,QAAQ,SAAS;CAC9B;CAEA,SAAS,QAAc;EACrB,SAAS,EAAE,MAAM,QAAQ,CAAC;EAC1B,IAAI,aAAa,WAAW,gBAAgB,CAAC,aAAa;GACxD,cAAc;GACd,QAAa;EACf;EACA,qBAAqB;CACvB;CAEA,SAAS,QAAc;EACrB,SAAS,EAAE,MAAM,QAAQ,CAAC;CAC5B;CAEA,SAAS,SAAe;EACtB,MAAM,EAAE,YAAY,SAAS,EAAE,MAAM,SAAS,CAAC;EAC/C,IAAI,SAAS,SAAS;CACxB;CAEA,SAAS,SAAe;EACtB,SAAS,EAAE,MAAM,SAAS,CAAC;EAC3B,SAAS;EACT,qBAAqB;CACvB;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA,kBAAkB;EAClB,UAAU,UAAU;GAClB,UAAU,IAAI,QAAQ;GACtB,aAAa;IACX,UAAU,OAAO,QAAQ;GAC3B;EACF;EACA,SAAS;CACX;AACF;;;;;;;;;AClZA,SAAgB,mBAAsB,SAAkD;CACtF,MAAM,EAAE,MAAM,QAAQ,eAAe,SAAS,YAAY;CAC1D,MAAM,cAAc,qBAAqB,QAAQ,WAAW;CAC5D,MAAM,YAAY,mBAAmB,QAAQ,SAAS;CAEtD,MAAM,eAAe,oBAAoB;CACzC,MAAM,cAAc,kBAAkB,WAAW,SAAS;CAE1D,IAAI,eAAkC;CACtC,IAAI,cAAc;CAClB,IAAI,cAAc;CAClB,IAAI;CAKJ,IAAI,kBAAkB;CACtB,IAAI;CAUJ,IAAI,WAAW,EAAE,QAAQ,aAAa,OAAO;CAC7C,IAAI,gBAAgB;CAEpB,MAAM,4BAAY,IAAI,IAAgB;CAEtC,IAAI;CACJ,MAAM,iBAAiB,IAAI,SAAgC,YAAY;EACrE,WAAW;CACb,CAAC;CAED,IAAI;CACJ,IAAI;CAEJ,SAAS,cAA6B;EACpC,iBAAiB,IAAI,SAAS,YAAY;GACxC,eAAe;EACjB,CAAC;EACD,OAAO;CACT;CAEA,SAAS,WAAiB;EACxB,eAAe;EACf,eAAe,KAAA;EACf,eAAe,KAAA;CACjB;CAEA,SAAS,SAAe;EACtB,KAAK,MAAM,YAAY,WAAW,SAAS;CAC7C;CAEA,SAAS,cAAuC;EAC9C,IAAI,eAAe;GACjB,gBAAgB;GAIhB,WAAW;IACT,QAAQ,aAAa;IACrB,GAAI,aAAa,UAAU,KAAA,IAAY,EAAE,OAAO,aAAa,MAAM,IAAI,CAAC;IACxE,GAAI,kBAAkB,EAAE,OAAO,aAAa,IAAI,CAAC;IACjD,GAAI,iBAAiB,KAAA,IAAY,EAAE,QAAQ,aAAa,IAAI,CAAC;GAC/D;EACF;EACA,OAAO;CACT;CAEA,SAAS,SAAS,OAAyC;EACzD,MAAM,OAAO,WAAW,cAAc,KAAK;EAC3C,MAAM,UAAU,SAAS;EACzB,IAAI,SAAS;GACX,eAAe;GACf,gBAAgB;GAChB,OAAO;EACT;EACA,OAAO,EAAE,QAAQ;CACnB;CAEA,SAAS,OAAO,QAAqC;;EAQnD,IAAI,aAAa;EACjB,cAAc;EACd,aAAa;EACb,SAAS,MAAM;CACjB;;;;;CAMA,SAAS,aAAa,eAAoC;EACxD,eAAe;EACf,kBAAkB;EAClB,gBAAgB;CAClB;CAEA,SAAS,WACP,QACA,eACA,OACM;EACN,IAAI,WAAW,QAAQ;GAMrB,OAAO;IAAE;IAAQ,OAAO;GAAc,CAA0B;GAChE;EACF;EACA,IAAI,WAAW,SAAS;GACtB,OAAO;IAAE;IAAQ;GAAM,CAAC;GACxB;EACF;EACA,OAAO;GAAE;GAAQ,GAAI,iBAAiB,KAAA,IAAY,EAAE,QAAQ,aAAa,IAAI,CAAC;EAAG,CAAC;CACpF;;;;;;CAOA,SAAS,kBAAwB;EAC/B,WAAW,WAAW;EACtB,OAAO;CACT;CAEA,SAAS,uBAA6B;EACpC,MAAM,EAAE,WAAW;EACnB,IAAI,eAAe,CAAC,WAAW,MAAM,GAAG;EACxC,cAAc;EAId,gBAAgB;CAClB;CAEA,SAAS,cAAoB;EAC3B,eAAe,QAAQ;EACvB,OAAO;CACT;CAEA,SAAS,eAAqB;EAC5B,QAAQ,oBAAoB,SAAS,WAAW;CAClD;CAEA,IAAI,QAAQ,SAAS;EACnB,eAAe,OAAO;EACtB,SAAS,EAAE,MAAM,SAAS,CAAC;CAC7B,OACE,QAAQ,iBAAiB,SAAS,WAAW;CAG/C,eAAe,UAAyB;EACtC,IAAI,iBAAiB,IAAI;EACzB,IAAI,UAAU;EACd,MAAM,YAAY,KAAK;EAKvB,IAAI,OAAgC;GAAE,MAAM;GAAO,OAAO,KAAA;EAAU;EACpE,IAAI;GACF,SAAS;IAOP,MAAM,SAAsB,aAAa;IACzC,IAAI,WAAW,aAAa;KAC1B,UAAU,OAAO,KAAA,CAAyB;KAC1C;IACF;IAEA,IAAI,WAAW,UAAU;KACvB,MAAM,YAAY;KAClB,iBAAiB,IAAI;KACrB,IAAI,eAAe,YAAY,QAAQ,gBAAgB,OAAO;KAY9D;IACF;IAaA,IAAI,OAAO;IACX,KAAK,IAAI,UAAU,GAAG,UAAU,WAAW,WAAW;KACpD,OAAO,UAAU,KAAK;KACtB,WAAW;KACX,IAAI,KAAK,MAAM;MACb,OAAO;MACP;KACF;IACF;IACA,IAAI,MAAM;IAIV,MAAM,YAAY,IAAI;IACtB,IAAI,eAAe,YAAY,MAAM,WAAW,OAAO;IAEvD,IAAI,YAAY,kBAAkB,eAAe,aAAa,GAAG;KAC/D,IAAI,eAAe,YAAY,WAAW,WAAW,OAAO;KAC5D,UAAU,YAAY,gBAAgB,OAAO;KAC7C,MAAM,YACJ,kBAAkB,KAAA,IAAY,EAAE,UAAU,cAAc,IAAI,KAAA,CAC9D;KACA,OAAO;KACP,iBAAiB,IAAI;KACrB,UAAU;KACV,IAAI,eAAe,YAAY,QAAQ,gBAAgB,OAAO;IAChE;GACF;EACF,SAAS,OAAO;GACd,SAAS;IAAE,MAAM;IAAS;GAAM,CAAC;GACjC,WAAW,SAAS,KAAA,GAAW,KAAK;GACpC;EACF;EAEA,IAAI,aAAa,WAAW,aAAa;GACvC,gBAAgB;GAChB;EACF;;EAUA,MAAM,WAAW,KAAK,OAAO,KAAK,QAAQ,KAAA;EAC1C,aAAa,QAAQ;EACrB,SAAS,EAAE,MAAM,WAAW,CAAC;EAC7B,WAAW,QAAQ,QAAQ;CAC7B;CAEA,SAAS,QAAc;EACrB,SAAS,EAAE,MAAM,QAAQ,CAAC;EAC1B,IAAI,aAAa,WAAW,gBAAgB,CAAC,aAAa;GACxD,cAAc;GACd,QAAa;EACf;EACA,qBAAqB;CACvB;CAEA,SAAS,QAAc;EACrB,SAAS,EAAE,MAAM,QAAQ,CAAC;CAC5B;CAEA,SAAS,SAAe;EACtB,MAAM,EAAE,YAAY,SAAS,EAAE,MAAM,SAAS,CAAC;EAC/C,IAAI,SAAS,SAAS;CACxB;CAEA,SAAS,SAAe;EACtB,SAAS,EAAE,MAAM,SAAS,CAAC;EAC3B,SAAS;EACT,qBAAqB;CACvB;CAEA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA,gBAAgB;EAChB,UAAU,UAAU;GAClB,UAAU,IAAI,QAAQ;GACtB,aAAa;IACX,UAAU,OAAO,QAAQ;GAC3B;EACF;EACA,SAAS;CACX;AACF"}
|