react-inp-chunker 0.1.2 → 0.2.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.
Files changed (37) hide show
  1. package/README.md +141 -53
  2. package/core/package.json +10 -0
  3. package/dist/core/index.cjs +1 -1
  4. package/dist/core/index.d.cts +2 -2
  5. package/dist/core/index.d.ts +2 -2
  6. package/dist/core/index.js +1 -1
  7. package/dist/deferred-task-CwjZA3K-.js +713 -0
  8. package/dist/deferred-task-CwjZA3K-.js.map +1 -0
  9. package/dist/deferred-task-EWH55ZyL.cjs +796 -0
  10. package/dist/deferred-task-EWH55ZyL.cjs.map +1 -0
  11. package/dist/{index-C6B_Nm3i.d.ts → index-DShDP2VN.d.cts} +51 -6
  12. package/dist/index-DShDP2VN.d.cts.map +1 -0
  13. package/dist/{index-C6B_Nm3i.d.cts → index-DShDP2VN.d.ts} +51 -6
  14. package/dist/index-DShDP2VN.d.ts.map +1 -0
  15. package/dist/index.cjs +47 -12
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +20 -1
  18. package/dist/index.d.cts.map +1 -1
  19. package/dist/index.d.ts +20 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +24 -10
  22. package/dist/index.js.map +1 -1
  23. package/dist/production/core/index.cjs +16 -0
  24. package/dist/production/core/index.js +1 -0
  25. package/dist/{deferred-task-5kfztF2G.js → production/deferred-task-BGsfee-L.js} +177 -86
  26. package/dist/production/deferred-task-BGsfee-L.js.map +1 -0
  27. package/dist/{deferred-task-BXVzapQg.cjs → production/deferred-task-zVnq18IN.cjs} +177 -86
  28. package/dist/production/deferred-task-zVnq18IN.cjs.map +1 -0
  29. package/dist/production/index.cjs +195 -0
  30. package/dist/production/index.cjs.map +1 -0
  31. package/dist/production/index.js +172 -0
  32. package/dist/production/index.js.map +1 -0
  33. package/package.json +65 -22
  34. package/dist/deferred-task-5kfztF2G.js.map +0 -1
  35. package/dist/deferred-task-BXVzapQg.cjs.map +0 -1
  36. package/dist/index-C6B_Nm3i.d.cts.map +0 -1
  37. package/dist/index-C6B_Nm3i.d.ts.map +0 -1
@@ -0,0 +1,713 @@
1
+ // src/core/chunk-machine.ts
2
+ const initialChunkState = { status: "idle" };
3
+ const TERMINAL_STATUSES = /* @__PURE__ */ new Set([
4
+ "done",
5
+ "cancelled",
6
+ "error"
7
+ ]);
8
+ function isTerminal(status) {
9
+ return TERMINAL_STATUSES.has(status);
10
+ }
11
+ function transitionIdle(state, event) {
12
+ switch (event.type) {
13
+ case "START": return { status: "processing" };
14
+ case "CANCEL": return { status: "cancelled" };
15
+ case "PAUSE":
16
+ case "RESUME":
17
+ case "COMPLETE":
18
+ case "ERROR": return state;
19
+ }
20
+ }
21
+ function transitionProcessing(state, event) {
22
+ switch (event.type) {
23
+ case "PAUSE": return { status: "paused" };
24
+ case "CANCEL": return { status: "cancelled" };
25
+ case "COMPLETE": return { status: "done" };
26
+ case "ERROR": return {
27
+ status: "error",
28
+ error: event.error
29
+ };
30
+ case "START":
31
+ case "RESUME": return state;
32
+ }
33
+ }
34
+ function transitionPaused(state, event) {
35
+ switch (event.type) {
36
+ case "RESUME": return { status: "processing" };
37
+ case "CANCEL": return { status: "cancelled" };
38
+ case "START":
39
+ case "PAUSE":
40
+ case "COMPLETE":
41
+ case "ERROR": return state;
42
+ }
43
+ }
44
+ /**
45
+ * Advances the machine by one event. Pure: same inputs always produce an
46
+ * equal output, and the input state is never mutated.
47
+ */
48
+ function transition(state, event) {
49
+ switch (state.status) {
50
+ case "idle": return transitionIdle(state, event);
51
+ case "processing": return transitionProcessing(state, event);
52
+ case "paused": return transitionPaused(state, event);
53
+ case "done":
54
+ case "cancelled":
55
+ case "error": return state;
56
+ }
57
+ }
58
+ // src/core/diagnostics.ts
59
+ /**
60
+ * Development-only guidance for the two ways a chunked task quietly fails
61
+ * to do what the caller wanted:
62
+ *
63
+ * 1. A single item takes longer than a long task. Chunking cannot help:
64
+ * a slice is never shorter than one item, so the page still freezes.
65
+ * 2. Items are so cheap that reading the clock — around 300ns in a
66
+ * browser, where it is coarsened to 0.1ms anyway — costs more than the
67
+ * work between reads. `batchSize` exists for exactly this.
68
+ *
69
+ * Neither is visible from the outside without a profiler, and both have a
70
+ * concrete fix, which is what makes them worth a warning.
71
+ *
72
+ * Nothing here costs anything in production. The engines guard every call
73
+ * with `isDevelopment`, which the production build (see tsdown.config.ts)
74
+ * replaces with a literal `false`, so the calls and this module both
75
+ * disappear. The checks also ride on clock reads the loop already makes and
76
+ * keep their own baselines, so the hot loop needs no extra bookkeeping.
77
+ */
78
+ const isDevelopment = typeof process !== "undefined" && process.env["NODE_ENV"] !== "production";
79
+ /** The Long Tasks API's definition: past this, the browser calls it a long task. */
80
+ const LONG_UNIT_MS = 50;
81
+ /** Below this average cost, the clock read dominates the work. */
82
+ const CLOCK_BOUND_UNIT_MS = .002;
83
+ /** Units per slice below which the sample is too small to judge. */
84
+ const MIN_UNITS_TO_JUDGE = 64;
85
+ /** A suggested batch should take about this long, bounding the overshoot it buys. */
86
+ const TARGET_BATCH_MS = .1;
87
+ const MAX_SUGGESTED_BATCH = 512;
88
+ function warn(message) {
89
+ console.warn(`[react-inp-chunker] ${message}`);
90
+ }
91
+ /**
92
+ * A batch that should take about TARGET_BATCH_MS, which is also the
93
+ * overshoot the caller takes on by accepting it. No lower bound is needed:
94
+ * the warning only fires below CLOCK_BOUND_UNIT_MS, so the result is
95
+ * always at least TARGET_BATCH_MS / CLOCK_BOUND_UNIT_MS.
96
+ */
97
+ function suggestedBatchSize(unitCostMs) {
98
+ const target = Math.round(TARGET_BATCH_MS / unitCostMs);
99
+ return target > MAX_SUGGESTED_BATCH ? MAX_SUGGESTED_BATCH : target;
100
+ }
101
+ /**
102
+ * `unit` names what one step of this task is, so the message can say
103
+ * "item" for a chunked array and "segment" for a generator.
104
+ */
105
+ function createDiagnostics(unit, batchSize) {
106
+ let lastCheckAt = 0;
107
+ let lastCheckUnits = 0;
108
+ let sliceStartedAt = 0;
109
+ let sliceStartUnits = 0;
110
+ let started = false;
111
+ let warnedAboutLongUnit = false;
112
+ let warnedAboutClockCost = false;
113
+ const rebase = (at, unitsDone) => {
114
+ lastCheckAt = at;
115
+ lastCheckUnits = unitsDone;
116
+ sliceStartedAt = at;
117
+ sliceStartUnits = unitsDone;
118
+ started = true;
119
+ };
120
+ return {
121
+ check(at, unitsDone) {
122
+ if (!started) {
123
+ rebase(at, unitsDone);
124
+ return;
125
+ }
126
+ const batchMs = at - lastCheckAt;
127
+ const unitsInBatch = unitsDone - lastCheckUnits;
128
+ lastCheckAt = at;
129
+ lastCheckUnits = unitsDone;
130
+ if (warnedAboutLongUnit || batchMs <= LONG_UNIT_MS) return;
131
+ warnedAboutLongUnit = true;
132
+ warn(`${unitsInBatch === 1 ? `One ${unit} (index ${String(unitsDone - 1)}) took ${batchMs.toFixed(0)}ms` : `A batch of ${String(unitsInBatch)} ${unit}s took ${batchMs.toFixed(0)}ms`}. A slice can never be shorter than that, so this still blocks the main thread. ${unit === "item" ? "Split the work inside processItem into smaller items, or move it to useDeferredTask and yield part-way through." : "Add more yield points, so each segment is shorter."} (development only)`);
133
+ },
134
+ sliceEnded(at, unitsDone) {
135
+ const sliceMs = at - sliceStartedAt;
136
+ const unitsInSlice = unitsDone - sliceStartUnits;
137
+ sliceStartedAt = at;
138
+ sliceStartUnits = unitsDone;
139
+ if (warnedAboutClockCost || batchSize > 1) return;
140
+ if (unitsInSlice < MIN_UNITS_TO_JUDGE || sliceMs <= 0) return;
141
+ const unitCostMs = sliceMs / unitsInSlice;
142
+ if (unitCostMs >= CLOCK_BOUND_UNIT_MS) return;
143
+ warnedAboutClockCost = true;
144
+ warn(`${unit}s are averaging ${(unitCostMs * 1e3).toFixed(2)}µ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)`);
145
+ },
146
+ resumed(at, unitsDone) {
147
+ rebase(at, unitsDone);
148
+ }
149
+ };
150
+ }
151
+ // src/core/scheduler.ts
152
+ /** Default time slice: roughly the granularity React's own scheduler uses. */
153
+ const DEFAULT_TIME_SLICE_MS = 5;
154
+ /** True when `scheduler.yield()` is present on this runtime. */
155
+ function supportsSchedulerYield() {
156
+ return typeof globalThis.scheduler?.yield === "function";
157
+ }
158
+ /** True when the `MessageChannel` constructor is present on this runtime. */
159
+ function supportsMessageChannel() {
160
+ return typeof MessageChannel === "function";
161
+ }
162
+ /** True when `navigator.scheduling.isInputPending()` is present. */
163
+ function supportsInputPending() {
164
+ return typeof globalThis.navigator?.scheduling?.isInputPending === "function";
165
+ }
166
+ /**
167
+ * Resolves the input-pending probe once, for callers that would otherwise
168
+ * run the feature detection on every item of a long task: walking
169
+ * `navigator.scheduling.isInputPending` through optional calls is wasted
170
+ * work a million times over. Returns a function that always answers
171
+ * `false` where the API is missing, so callers need no second branch.
172
+ */
173
+ function resolveInputPending() {
174
+ return supportsInputPending() ? isInputPending : returnsFalse;
175
+ }
176
+ function returnsFalse() {
177
+ return false;
178
+ }
179
+ /**
180
+ * Best-effort read of whether the browser has pending input right now.
181
+ * Returns `false` — never throws — when the API is unavailable or errors,
182
+ * since the caller's fallback (a plain time budget) is always safe.
183
+ */
184
+ function isInputPending() {
185
+ try {
186
+ return globalThis.navigator?.scheduling?.isInputPending?.() ?? false;
187
+ } catch {
188
+ return false;
189
+ }
190
+ }
191
+ /** Picks the best strategy this runtime actually supports. */
192
+ function resolveYieldStrategy() {
193
+ if (supportsSchedulerYield()) return "scheduler-yield";
194
+ if (supportsMessageChannel()) return "message-channel";
195
+ return "set-timeout";
196
+ }
197
+ function yieldViaMessageChannel() {
198
+ return new Promise((resolve) => {
199
+ const channel = new MessageChannel();
200
+ channel.port1.onmessage = () => {
201
+ channel.port1.close();
202
+ resolve();
203
+ };
204
+ channel.port2.postMessage(null);
205
+ });
206
+ }
207
+ function yieldViaSetTimeout() {
208
+ return new Promise((resolve) => {
209
+ setTimeout(resolve, 0);
210
+ });
211
+ }
212
+ /**
213
+ * Yields control back to the browser's main thread, resolving once the
214
+ * browser has had a chance to handle any pending input, then resuming the
215
+ * caller. Never rejects: a runtime that half-implements `scheduler.yield()`
216
+ * (the property exists but the call throws, or the returned promise
217
+ * rejects) falls through to the next strategy rather than propagating the
218
+ * failure into unrelated application code.
219
+ */
220
+ async function yieldToMain(options) {
221
+ const strategy = options?.strategy ?? resolveYieldStrategy();
222
+ if (strategy === "scheduler-yield") {
223
+ const schedulerGlobal = globalThis.scheduler;
224
+ if (typeof schedulerGlobal?.yield === "function") try {
225
+ await schedulerGlobal.yield();
226
+ return;
227
+ } catch {}
228
+ await (supportsMessageChannel() ? yieldViaMessageChannel() : yieldViaSetTimeout());
229
+ return;
230
+ }
231
+ if (strategy === "message-channel") {
232
+ if (supportsMessageChannel()) {
233
+ await yieldViaMessageChannel();
234
+ return;
235
+ }
236
+ await yieldViaSetTimeout();
237
+ return;
238
+ }
239
+ await yieldViaSetTimeout();
240
+ }
241
+ /** `performance.now()` when available, `Date.now()` otherwise. Never throws. */
242
+ function now() {
243
+ try {
244
+ if (typeof performance !== "undefined" && typeof performance.now === "function") return performance.now();
245
+ } catch {}
246
+ return Date.now();
247
+ }
248
+ /**
249
+ * A time slice of zero, a negative number or NaN is meaningless, so those
250
+ * fall back to the default rather than producing a task that yields after
251
+ * every item (or never).
252
+ */
253
+ function normalizeTimeSliceMs(value) {
254
+ if (typeof value !== "number" || Number.isNaN(value) || value <= 0) return 5;
255
+ return value;
256
+ }
257
+ /**
258
+ * Batch sizes are whole items, at least one. Anything else — a fraction,
259
+ * zero, a negative, NaN — falls back to 1, the safe default.
260
+ */
261
+ function normalizeBatchSize(value) {
262
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 1) return 1;
263
+ return Math.floor(value);
264
+ }
265
+ /**
266
+ * Whether the caller should yield right now: either the time slice has run
267
+ * out, or the browser is reporting pending input (a stronger, earlier
268
+ * signal than the clock — used only when the runtime exposes it).
269
+ */
270
+ function shouldYieldNow(options) {
271
+ const timeSliceMs = options.timeSliceMs ?? 5;
272
+ if (now() - options.sliceStartedAt >= timeSliceMs) return true;
273
+ return isInputPending();
274
+ }
275
+ // src/core/chunk-scheduler.ts
276
+ /**
277
+ * `items[index]` for an index the caller has already bounds-checked.
278
+ *
279
+ * The loop below only ever indexes below `items.length`, but
280
+ * `noUncheckedIndexedAccess` widens every index read to `T | undefined`,
281
+ * and `T` may itself include `undefined` — so no runtime check can narrow
282
+ * it. This states the invariant in one place instead of asserting at each
283
+ * use, and compiles to a plain index read.
284
+ */
285
+ function at(items, index) {
286
+ return items[index];
287
+ }
288
+ /**
289
+ * Builds a one-shot, controllable scheduler that processes `items` through
290
+ * `processItem`, yielding to the main thread on a time budget (and sooner,
291
+ * if the browser reports pending input) so a large array never produces a
292
+ * single long task.
293
+ *
294
+ * One-shot means exactly that: once the task reaches `done`, `cancelled` or
295
+ * `error`, this instance is finished — start()/pause()/resume() on it are
296
+ * no-ops from then on. To run another task, create another scheduler. For a
297
+ * React hook, that falls out naturally: new deps → new effect → new
298
+ * scheduler, which is the idiomatic React data-flow anyway.
299
+ */
300
+ function createChunkScheduler(options) {
301
+ const { items, processItem, onItem, signal, yieldStrategy } = options;
302
+ const timeSliceMs = normalizeTimeSliceMs(options.timeSliceMs);
303
+ const batchSize = normalizeBatchSize(options.batchSize);
304
+ const collectResults = options.collectResults ?? true;
305
+ const total = items.length;
306
+ const inputPending = resolveInputPending();
307
+ const diagnostics = createDiagnostics("item", batchSize);
308
+ let machineState = initialChunkState;
309
+ let processed = 0;
310
+ let loopStarted = false;
311
+ let settledFlag = false;
312
+ const results = [];
313
+ let finalResults;
314
+ let snapshot = {
315
+ status: machineState.status,
316
+ processed,
317
+ total
318
+ };
319
+ let snapshotStale = false;
320
+ const listeners = /* @__PURE__ */ new Set();
321
+ let settleFn;
322
+ const settledPromise = new Promise((resolve) => {
323
+ settleFn = resolve;
324
+ });
325
+ let pauseResolve;
326
+ let pausePromise;
327
+ function waitForWake() {
328
+ pausePromise ??= new Promise((resolve) => {
329
+ pauseResolve = resolve;
330
+ });
331
+ return pausePromise;
332
+ }
333
+ function openGate() {
334
+ pauseResolve?.();
335
+ pauseResolve = void 0;
336
+ pausePromise = void 0;
337
+ }
338
+ function notify() {
339
+ for (const listener of listeners) listener();
340
+ }
341
+ function getSnapshot() {
342
+ if (snapshotStale) {
343
+ snapshotStale = false;
344
+ snapshot = {
345
+ status: machineState.status,
346
+ processed,
347
+ total,
348
+ ...machineState.error !== void 0 ? { error: machineState.error } : {},
349
+ ...finalResults !== void 0 ? { results: finalResults } : {}
350
+ };
351
+ }
352
+ return snapshot;
353
+ }
354
+ function dispatch(event) {
355
+ const next = transition(machineState, event);
356
+ const changed = next !== machineState;
357
+ if (changed) {
358
+ machineState = next;
359
+ snapshotStale = true;
360
+ notify();
361
+ }
362
+ return { changed };
363
+ }
364
+ function settle(result) {
365
+ /* v8 ignore next */
366
+ if (settledFlag) return;
367
+ settledFlag = true;
368
+ detachSignal();
369
+ settleFn(result);
370
+ }
371
+ /**
372
+ * Publishes the accumulated results exactly once, at the moment the task
373
+ * settles — not per item, and never as a second copy. Always called
374
+ * *before* the terminal status is announced, so the notification that
375
+ * reports `done` already carries the results: a subscriber can never
376
+ * observe one without the other.
377
+ */
378
+ function publishResults() {
379
+ finalResults = results;
380
+ snapshotStale = true;
381
+ return finalResults;
382
+ }
383
+ function settleWith(status, published, error) {
384
+ settle(status === "error" ? {
385
+ status,
386
+ error,
387
+ results: published
388
+ } : {
389
+ status,
390
+ results: published
391
+ });
392
+ }
393
+ /**
394
+ * Settling as cancelled, for both places that can do it: the loop
395
+ * noticing, and cancel() on a task whose loop never ran. The CANCEL
396
+ * transition was announced earlier, before there were any results to
397
+ * carry, so this notifies again now that there are.
398
+ */
399
+ function finishCancelled() {
400
+ settleWith("cancelled", publishResults());
401
+ notify();
402
+ }
403
+ function settleIfNeverStarted() {
404
+ const { status } = machineState;
405
+ if (loopStarted || !isTerminal(status)) return;
406
+ loopStarted = true;
407
+ finishCancelled();
408
+ }
409
+ function handleAbort() {
410
+ cancel();
411
+ }
412
+ function detachSignal() {
413
+ signal?.removeEventListener("abort", handleAbort);
414
+ }
415
+ if (signal?.aborted) dispatch({ type: "CANCEL" });
416
+ else signal?.addEventListener("abort", handleAbort);
417
+ async function runLoop() {
418
+ let sliceStartedAt = now();
419
+ let index = 0;
420
+ try {
421
+ while (index < total) {
422
+ const status = machineState.status;
423
+ if (status === "cancelled") break;
424
+ if (status === "paused") {
425
+ await waitForWake();
426
+ sliceStartedAt = now();
427
+ if (isDevelopment) diagnostics.resumed(sliceStartedAt, index);
428
+ continue;
429
+ }
430
+ const batchEnd = index + batchSize <= total ? index + batchSize : total;
431
+ while (index < batchEnd) {
432
+ const item = at(items, index);
433
+ const result = processItem(item, index);
434
+ if (collectResults) results.push(result);
435
+ processed = index + 1;
436
+ onItem?.(result, item, index);
437
+ index += 1;
438
+ }
439
+ snapshotStale = true;
440
+ const checkedAt = now();
441
+ if (isDevelopment) diagnostics.check(checkedAt, index);
442
+ if (checkedAt - sliceStartedAt >= timeSliceMs || inputPending()) {
443
+ if (isDevelopment) diagnostics.sliceEnded(checkedAt, index);
444
+ await yieldToMain(yieldStrategy !== void 0 ? { strategy: yieldStrategy } : void 0);
445
+ notify();
446
+ sliceStartedAt = now();
447
+ if (isDevelopment) diagnostics.resumed(sliceStartedAt, index);
448
+ }
449
+ }
450
+ } catch (error) {
451
+ const published = publishResults();
452
+ dispatch({
453
+ type: "ERROR",
454
+ error
455
+ });
456
+ settleWith("error", published, error);
457
+ return;
458
+ }
459
+ if (machineState.status === "cancelled") {
460
+ finishCancelled();
461
+ return;
462
+ }
463
+ const published = publishResults();
464
+ dispatch({ type: "COMPLETE" });
465
+ settleWith("done", published);
466
+ }
467
+ function start() {
468
+ dispatch({ type: "START" });
469
+ if (machineState.status === "processing" && !loopStarted) {
470
+ loopStarted = true;
471
+ runLoop();
472
+ }
473
+ settleIfNeverStarted();
474
+ }
475
+ function pause() {
476
+ dispatch({ type: "PAUSE" });
477
+ }
478
+ function resume() {
479
+ const { changed } = dispatch({ type: "RESUME" });
480
+ if (changed) openGate();
481
+ }
482
+ function cancel() {
483
+ dispatch({ type: "CANCEL" });
484
+ openGate();
485
+ settleIfNeverStarted();
486
+ }
487
+ return {
488
+ start,
489
+ pause,
490
+ resume,
491
+ cancel,
492
+ getSnapshot,
493
+ getResults: () => finalResults,
494
+ subscribe(listener) {
495
+ listeners.add(listener);
496
+ return () => {
497
+ listeners.delete(listener);
498
+ };
499
+ },
500
+ settled: settledPromise
501
+ };
502
+ }
503
+ // src/core/deferred-task.ts
504
+ /**
505
+ * The generator-driven sibling of `createChunkScheduler`, for heavy work
506
+ * that isn't shaped like "one function applied to each item of an array".
507
+ * Same state machine, same yield primitives, same one-shot-instance
508
+ * contract — see that file's doc comment for the reasoning, which applies
509
+ * here unchanged.
510
+ */
511
+ function createDeferredTask(options) {
512
+ const { task, signal, yieldStrategy } = options;
513
+ const timeSliceMs = normalizeTimeSliceMs(options.timeSliceMs);
514
+ const batchSize = normalizeBatchSize(options.batchSize);
515
+ const inputPending = resolveInputPending();
516
+ const diagnostics = createDiagnostics("segment", batchSize);
517
+ let machineState = initialChunkState;
518
+ let loopStarted = false;
519
+ let settledFlag = false;
520
+ let settledValue;
521
+ let snapshot = { status: machineState.status };
522
+ let snapshotStale = false;
523
+ const listeners = /* @__PURE__ */ new Set();
524
+ let settleFn;
525
+ const settledPromise = new Promise((resolve) => {
526
+ settleFn = resolve;
527
+ });
528
+ let pauseResolve;
529
+ let pausePromise;
530
+ function waitForWake() {
531
+ pausePromise ??= new Promise((resolve) => {
532
+ pauseResolve = resolve;
533
+ });
534
+ return pausePromise;
535
+ }
536
+ function openGate() {
537
+ pauseResolve?.();
538
+ pauseResolve = void 0;
539
+ pausePromise = void 0;
540
+ }
541
+ function notify() {
542
+ for (const listener of listeners) listener();
543
+ }
544
+ function getSnapshot() {
545
+ if (snapshotStale) {
546
+ snapshotStale = false;
547
+ snapshot = {
548
+ status: machineState.status,
549
+ ...machineState.error !== void 0 ? { error: machineState.error } : {},
550
+ ...settledValue !== void 0 ? { value: settledValue } : {}
551
+ };
552
+ }
553
+ return snapshot;
554
+ }
555
+ function dispatch(event) {
556
+ const next = transition(machineState, event);
557
+ const changed = next !== machineState;
558
+ if (changed) {
559
+ machineState = next;
560
+ snapshotStale = true;
561
+ notify();
562
+ }
563
+ return { changed };
564
+ }
565
+ function settle(result) {
566
+ /* v8 ignore next */
567
+ if (settledFlag) return;
568
+ settledFlag = true;
569
+ detachSignal();
570
+ settleFn(result);
571
+ }
572
+ /**
573
+ * Publishes the returned value before the terminal status is announced,
574
+ * so the notification that reports `done` already carries it.
575
+ */
576
+ function publishValue(resolvedValue) {
577
+ settledValue = resolvedValue;
578
+ snapshotStale = true;
579
+ }
580
+ function settleWith(status, resolvedValue, error) {
581
+ if (status === "done") {
582
+ settle({
583
+ status,
584
+ ...resolvedValue !== void 0 ? { value: resolvedValue } : {}
585
+ });
586
+ return;
587
+ }
588
+ settle(status === "error" ? {
589
+ status,
590
+ error
591
+ } : { status });
592
+ }
593
+ /**
594
+ * Settling as cancelled, for both places that can do it. The CANCEL
595
+ * transition was announced earlier, so this notifies again now that the
596
+ * task has actually settled.
597
+ */
598
+ function finishCancelled() {
599
+ settleWith("cancelled");
600
+ notify();
601
+ }
602
+ function settleIfNeverStarted() {
603
+ const { status } = machineState;
604
+ if (loopStarted || !isTerminal(status)) return;
605
+ loopStarted = true;
606
+ finishCancelled();
607
+ }
608
+ function handleAbort() {
609
+ cancel();
610
+ }
611
+ function detachSignal() {
612
+ signal?.removeEventListener("abort", handleAbort);
613
+ }
614
+ if (signal?.aborted) dispatch({ type: "CANCEL" });
615
+ else signal?.addEventListener("abort", handleAbort);
616
+ async function runLoop() {
617
+ let sliceStartedAt = now();
618
+ let segment = 0;
619
+ const generator = task();
620
+ let step = {
621
+ done: false,
622
+ value: void 0
623
+ };
624
+ try {
625
+ for (;;) {
626
+ const status = machineState.status;
627
+ if (status === "cancelled") {
628
+ generator.return(void 0);
629
+ break;
630
+ }
631
+ if (status === "paused") {
632
+ await waitForWake();
633
+ sliceStartedAt = now();
634
+ if (isDevelopment) diagnostics.resumed(sliceStartedAt, segment);
635
+ continue;
636
+ }
637
+ let done = false;
638
+ for (let inBatch = 0; inBatch < batchSize; inBatch++) {
639
+ step = generator.next();
640
+ segment += 1;
641
+ if (step.done) {
642
+ done = true;
643
+ break;
644
+ }
645
+ }
646
+ if (done) break;
647
+ const checkedAt = now();
648
+ if (isDevelopment) diagnostics.check(checkedAt, segment);
649
+ if (checkedAt - sliceStartedAt >= timeSliceMs || inputPending()) {
650
+ if (isDevelopment) diagnostics.sliceEnded(checkedAt, segment);
651
+ await yieldToMain(yieldStrategy !== void 0 ? { strategy: yieldStrategy } : void 0);
652
+ notify();
653
+ sliceStartedAt = now();
654
+ if (isDevelopment) diagnostics.resumed(sliceStartedAt, segment);
655
+ }
656
+ }
657
+ } catch (error) {
658
+ dispatch({
659
+ type: "ERROR",
660
+ error
661
+ });
662
+ settleWith("error", void 0, error);
663
+ return;
664
+ }
665
+ if (machineState.status === "cancelled") {
666
+ finishCancelled();
667
+ return;
668
+ }
669
+ /* v8 ignore next */
670
+ const returned = step.done ? step.value : void 0;
671
+ publishValue(returned);
672
+ dispatch({ type: "COMPLETE" });
673
+ settleWith("done", returned);
674
+ }
675
+ function start() {
676
+ dispatch({ type: "START" });
677
+ if (machineState.status === "processing" && !loopStarted) {
678
+ loopStarted = true;
679
+ runLoop();
680
+ }
681
+ settleIfNeverStarted();
682
+ }
683
+ function pause() {
684
+ dispatch({ type: "PAUSE" });
685
+ }
686
+ function resume() {
687
+ const { changed } = dispatch({ type: "RESUME" });
688
+ if (changed) openGate();
689
+ }
690
+ function cancel() {
691
+ dispatch({ type: "CANCEL" });
692
+ openGate();
693
+ settleIfNeverStarted();
694
+ }
695
+ return {
696
+ start,
697
+ pause,
698
+ resume,
699
+ cancel,
700
+ getSnapshot,
701
+ getValue: () => settledValue,
702
+ subscribe(listener) {
703
+ listeners.add(listener);
704
+ return () => {
705
+ listeners.delete(listener);
706
+ };
707
+ },
708
+ settled: settledPromise
709
+ };
710
+ }
711
+ 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 };
712
+
713
+ //# sourceMappingURL=deferred-task-CwjZA3K-.js.map