react-inp-chunker 0.1.2 → 0.3.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 +165 -53
- package/core/package.json +10 -0
- package/dist/core/index.cjs +1 -1
- package/dist/core/index.d.cts +2 -2
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +1 -1
- package/dist/deferred-task-6oTEnHWa.js +778 -0
- package/dist/deferred-task-6oTEnHWa.js.map +1 -0
- package/dist/deferred-task-DcnC99_Y.cjs +861 -0
- package/dist/deferred-task-DcnC99_Y.cjs.map +1 -0
- package/dist/{index-C6B_Nm3i.d.ts → index-B4gKjLYR.d.cts} +117 -6
- package/dist/index-B4gKjLYR.d.cts.map +1 -0
- package/dist/{index-C6B_Nm3i.d.cts → index-B4gKjLYR.d.ts} +117 -6
- package/dist/index-B4gKjLYR.d.ts.map +1 -0
- package/dist/index.cjs +68 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +49 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +49 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +45 -10
- package/dist/index.js.map +1 -1
- package/dist/production/core/index.cjs +16 -0
- package/dist/production/core/index.js +1 -0
- package/dist/{deferred-task-5kfztF2G.js → production/deferred-task-DkHdsliy.js} +227 -99
- package/dist/production/deferred-task-DkHdsliy.js.map +1 -0
- package/dist/{deferred-task-BXVzapQg.cjs → production/deferred-task-yh5pWlSj.cjs} +227 -99
- package/dist/production/deferred-task-yh5pWlSj.cjs.map +1 -0
- package/dist/production/index.cjs +216 -0
- package/dist/production/index.cjs.map +1 -0
- package/dist/production/index.js +193 -0
- package/dist/production/index.js.map +1 -0
- package/package.json +69 -23
- package/dist/deferred-task-5kfztF2G.js.map +0 -1
- package/dist/deferred-task-BXVzapQg.cjs.map +0 -1
- package/dist/index-C6B_Nm3i.d.cts.map +0 -1
- package/dist/index-C6B_Nm3i.d.ts.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/react/task-controller.ts","../../src/react/use-effect-event.ts","../../src/react/useChunkedTask.ts","../../src/react/useDeferredTask.ts"],"sourcesContent":["/**\n * The glue between a task handle (`ChunkScheduler<R>`, `DeferredTask<T>` —\n * anything with `subscribe`/`getSnapshot`) recreated from scratch whenever a\n * hook's deps change, inside an effect, and `useSyncExternalStore`, which\n * wants one stable `subscribe` function for a component's whole lifetime.\n *\n * `useSyncExternalStore` resubscribes whenever the `subscribe` function\n * identity changes — fine for correctness, but it means the naive approach\n * (pass the current task's own `.subscribe` directly) either churns\n * subscriptions every render or, worse, drifts: the component keeps\n * `subscribe`d to whichever task was current when it last resubscribed, not\n * necessarily the one `getSnapshot` is reading from after a swap.\n *\n * A controller sidesteps that: it's created once per hook instance (lazy\n * `useState` initializer in the hook, so it's stable without touching a\n * ref), hands `useSyncExternalStore` a `subscribe`/`getSnapshot` pair that\n * never change identity, and internally forwards to whichever task is\n * \"current\" — re-pointing itself via `replace()` every time the effect\n * swaps in a new one. Generic over the task shape so both `useChunkedTask`\n * and `useDeferredTask` share this one implementation.\n *\n * Every property below is an arrow function, deliberately — not method\n * shorthand — so none of them close over `this`, and `controller.subscribe`\n * can be handed to `useSyncExternalStore` directly as a plain function\n * reference without an unbound-method footgun.\n */\nexport interface SubscribableTask<Snapshot> {\n subscribe(listener: () => void): () => void;\n getSnapshot(): Snapshot;\n}\n\nexport interface TaskController<Task extends SubscribableTask<unknown>> {\n subscribe: (listener: () => void) => () => void;\n getSnapshot: () => ReturnType<Task['getSnapshot']> | undefined;\n getCurrent: () => Task | null;\n /** Swaps in a new task (or none), detaching from the previous one first. */\n replace: (task: Task | null) => void;\n}\n\nexport function createTaskController<\n Task extends SubscribableTask<unknown>,\n>(): TaskController<Task> {\n let current: Task | null = null;\n let detachFromCurrent: (() => void) | undefined;\n const listeners = new Set<() => void>();\n\n const notify = (): void => {\n for (const listener of listeners) listener();\n };\n\n return {\n subscribe: (listener) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n getSnapshot: () =>\n current?.getSnapshot() as ReturnType<Task['getSnapshot']> | undefined,\n getCurrent: () => current,\n replace: (task) => {\n detachFromCurrent?.();\n current = task;\n detachFromCurrent = task?.subscribe(notify);\n notify();\n },\n };\n}\n","import * as React from 'react';\nimport { useCallback, useInsertionEffect, useRef } from 'react';\n\n/**\n * `useEffectEvent` only became a stable React export in 19.2. On React 18\n * and 19.0–19.1 it doesn't exist at all — and a named\n * `import { useEffectEvent } from 'react'` then fails outright: at module\n * link time under Node ESM (\"Named export 'useEffectEvent' not found\"), or\n * as \"useEffectEvent is not a function\" on first render in a bundler. Both\n * versions are inside this package's declared peer range, so that has to\n * work, not just type-check.\n *\n * So the native hook is read as a property of the namespace object, where a\n * missing export is plain `undefined` rather than a link error, and used\n * whenever the running React has it. Otherwise the fallback keeps the same\n * contract: a stable function identity that always calls the latest\n * callback. The latest callback is committed from an insertion effect —\n * after render, before any layout or passive effect of the same commit can\n * observe it — so effects always see the current closure, without the\n * render-time ref mutation the React Compiler rejects.\n */\ntype EffectEventHook = <Args extends readonly unknown[], Return>(\n callback: (...args: Args) => Return,\n) => (...args: Args) => Return;\n\nexport function useEffectEventFallback<Args extends readonly unknown[], Return>(\n callback: (...args: Args) => Return,\n): (...args: Args) => Return {\n const latest = useRef(callback);\n useInsertionEffect(() => {\n latest.current = callback;\n });\n return useCallback((...args: Args) => latest.current(...args), []);\n}\n\n// Through `unknown` on purpose: React 18's types have no `useEffectEvent`\n// member at all, so a direct assertion wouldn't compile against them.\nconst nativeUseEffectEvent = (\n React as unknown as { readonly useEffectEvent?: EffectEventHook }\n).useEffectEvent;\n\nexport const useEffectEvent: EffectEventHook =\n nativeUseEffectEvent ?? useEffectEventFallback;\n","import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from 'react';\n\nimport type { ChunkStatus } from '../core/chunk-machine.js';\nimport { createChunkScheduler } from '../core/chunk-scheduler.js';\nimport type {\n ChunkScheduler,\n ChunkSnapshot,\n SkippedChunkItem,\n} from '../core/chunk-scheduler.js';\nimport { createTaskController } from './task-controller.js';\nimport { useEffectEvent } from './use-effect-event.js';\n\n/** Stable empty array so a never-skipped task doesn't hand out a fresh `[]` every render. */\nconst NO_SKIPPED: readonly SkippedChunkItem[] = [];\n\nexport interface UseChunkedTaskOptions<T, 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. Raise it only for\n * items measured in nanoseconds, where reading the clock costs more than\n * the work; a slice can then run up to `batchSize` items past its budget.\n * See the `batchSize` note on `createChunkScheduler`.\n */\n readonly batchSize?: number;\n /**\n * Keep every result in `results`. Default true. Set false when results\n * are consumed through `onItem`, to avoid retaining one result per item;\n * `results` is then an empty array once the task settles.\n */\n readonly collectResults?: boolean;\n /** Start processing automatically when the task is (re)created. Default `true`. */\n readonly autoStart?: boolean;\n /**\n * Called synchronously after each item is processed. This is the cheap\n * way to consume results incrementally while the task is still running —\n * write into a ref-backed buffer, a canvas, a chart, whatever you need —\n * without paying for an array copy on every yield checkpoint.\n */\n readonly onItem?: (result: R, item: T, index: number) => void;\n /**\n * Called when `processItem` throws. Return `'skip'` to drop that item and\n * keep going — it shows up in `skipped` — or `'abort'` to end the task as\n * an error, carrying whatever was collected before the throw. Omitting\n * this defaults to `'abort'`, so existing callers see no behavior change.\n */\n readonly onError?: (error: unknown, item: T, index: number) => 'skip' | 'abort';\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. Ships in the\n * production build, unlike this package's own dev-only diagnostics — wire\n * it to your own telemetry to verify, on real traffic, that this task is\n * actually staying under `timeSliceMs`.\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\nexport interface UseChunkedTaskState<R> {\n readonly status: ChunkStatus;\n readonly processed: number;\n readonly total: number;\n readonly error: unknown;\n /** The full results array once the task has settled — `undefined` until then. */\n readonly results: readonly R[] | undefined;\n /** Items `onError` has skipped so far, growing live as skips happen. Empty when unused. */\n readonly skipped: readonly SkippedChunkItem[];\n readonly isIdle: boolean;\n readonly isProcessing: boolean;\n readonly isPaused: boolean;\n readonly isDone: boolean;\n readonly isCancelled: boolean;\n readonly isError: boolean;\n readonly start: () => void;\n readonly pause: () => void;\n readonly resume: () => void;\n readonly cancel: () => void;\n}\n\nconst IDLE_STATUS: ChunkStatus = 'idle';\n\n/**\n * Chunks a heavy, synchronous per-item computation over `items` so it never\n * blocks the main thread for longer than a time slice — the thing that\n * costs you Interaction to Next Paint. Starts automatically by default;\n * pass `{ autoStart: false }` to wait for an explicit `start()` call (e.g.\n * behind a button).\n *\n * `processItem` and `onItem` can be fresh closures every render — the hook\n * always calls the latest version (via `useEffectEvent`) without that\n * causing the task to restart. What *does* restart the task is `items`\n * changing identity (a new array reference), or `timeSliceMs`/`autoStart`\n * changing. That's deliberate: a new `items` array means new work, and\n * memoizing it when you don't want that is the caller's call to make, the\n * same as any other React dependency.\n */\nexport function useChunkedTask<T, R>(\n items: readonly T[],\n processItem: (item: T, index: number) => R,\n options?: UseChunkedTaskOptions<T, R>,\n): UseChunkedTaskState<R> {\n const {\n timeSliceMs,\n batchSize,\n collectResults,\n autoStart = true,\n onItem,\n onError,\n onSlice,\n onYield,\n } = options ?? {};\n\n // Stable identity, always runs the latest processItem/onItem passed this\n // render — the React-Compiler-safe replacement for a ref mutated during\n // render. Effect Events are meant to be called from inside an effect\n // (here: from the scheduler's async loop, which the effect below owns),\n // never during render itself.\n const stableProcessItem = useEffectEvent((item: T, index: number) =>\n processItem(item, index),\n );\n const stableOnItem = useEffectEvent((result: R, item: T, index: number) => {\n onItem?.(result, item, index);\n });\n const stableOnError = useEffectEvent(\n (error: unknown, item: T, index: number): 'skip' | 'abort' =>\n onError?.(error, item, index) ?? 'abort',\n );\n const stableOnSlice = useEffectEvent((durationMs: number, processed: number) => {\n onSlice?.(durationMs, processed);\n });\n const stableOnYield = useEffectEvent(() => {\n onYield?.();\n });\n\n // Created once, lazily, via useState rather than a ref mutated during\n // render — the controller itself is inert (no subscriptions, no timers)\n // until an effect calls replace() on it, so a discarded render (double\n // render in dev, or one React throws away) never leaks anything.\n const [controller] = useState(() => createTaskController<ChunkScheduler<R>>());\n\n // What getSnapshot() reports before the effect below has ever run —\n // including during SSR, where effects never run at all.\n const idleSnapshot = useMemo<ChunkSnapshot<R>>(\n () => ({ status: IDLE_STATUS, processed: 0, total: items.length }),\n [items.length],\n );\n\n const snapshot = useSyncExternalStore(\n controller.subscribe,\n () => controller.getSnapshot() ?? idleSnapshot,\n () => idleSnapshot,\n );\n\n useEffect(() => {\n const scheduler = createChunkScheduler<T, R>({\n items,\n processItem: stableProcessItem,\n onItem: stableOnItem,\n onError: stableOnError,\n onSlice: stableOnSlice,\n onYield: stableOnYield,\n ...(timeSliceMs !== undefined ? { timeSliceMs } : {}),\n ...(batchSize !== undefined ? { batchSize } : {}),\n ...(collectResults !== undefined ? { collectResults } : {}),\n });\n controller.replace(scheduler);\n if (autoStart) scheduler.start();\n\n return () => {\n // Safe even if the task already finished on its own — cancel() on a\n // terminal scheduler is a documented no-op, not an error.\n scheduler.cancel();\n controller.replace(null);\n };\n // stableProcessItem/stableOnItem/stableOnError/stableOnSlice/stableOnYield\n // are Effect Events: stable by construction, intentionally excluded\n // from the reactive dependency set\n // (that's the whole point of useEffectEvent) rather than omitted by\n // oversight.\n }, [items, timeSliceMs, batchSize, collectResults, autoStart, controller]);\n\n const start = useCallback(() => controller.getCurrent()?.start(), [controller]);\n const pause = useCallback(() => controller.getCurrent()?.pause(), [controller]);\n const resume = useCallback(() => controller.getCurrent()?.resume(), [controller]);\n const cancel = useCallback(() => controller.getCurrent()?.cancel(), [controller]);\n\n return {\n status: snapshot.status,\n processed: snapshot.processed,\n total: snapshot.total,\n error: snapshot.error,\n // From the snapshot, not a direct read of the scheduler: the engine\n // publishes results before it announces a terminal status, so the two\n // always arrive together and a concurrent render can never tear them\n // apart.\n results: snapshot.results,\n skipped: snapshot.skipped ?? NO_SKIPPED,\n isIdle: snapshot.status === 'idle',\n isProcessing: snapshot.status === 'processing',\n isPaused: snapshot.status === 'paused',\n isDone: snapshot.status === 'done',\n isCancelled: snapshot.status === 'cancelled',\n isError: snapshot.status === 'error',\n start,\n pause,\n resume,\n cancel,\n };\n}\n","import { useCallback, useEffect, useState, useSyncExternalStore } from 'react';\n\nimport type { ChunkStatus } from '../core/chunk-machine.js';\nimport { createDeferredTask } from '../core/deferred-task.js';\nimport type {\n DeferredGenerator,\n DeferredTask,\n DeferredTaskSnapshot,\n} from '../core/deferred-task.js';\nimport { createTaskController } from './task-controller.js';\nimport { useEffectEvent } from './use-effect-event.js';\n\nexport interface UseDeferredTaskOptions {\n /** Max ms of uninterrupted work before yielding. Default 5. */\n readonly timeSliceMs?: number;\n /**\n * Generator segments to run between clock checks. Default 1. Raise it\n * only for segments measured in nanoseconds; a slice can then run up to\n * `batchSize` segments past its budget.\n */\n readonly batchSize?: number;\n /** Start processing automatically when the task is (re)created. Default `true`. */\n readonly autoStart?: boolean;\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 * `useChunkedTask`/`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\nexport interface UseDeferredTaskState<T> {\n readonly status: ChunkStatus;\n readonly error: unknown;\n /** The returned value once the task settles with `done` — `undefined` until then. */\n readonly value: T | undefined;\n readonly isIdle: boolean;\n readonly isProcessing: boolean;\n readonly isPaused: boolean;\n readonly isDone: boolean;\n readonly isCancelled: boolean;\n readonly isError: boolean;\n readonly start: () => void;\n readonly pause: () => void;\n readonly resume: () => void;\n readonly cancel: () => void;\n}\n\nconst IDLE_SNAPSHOT: DeferredTaskSnapshot<never> = { status: 'idle' };\n\n/**\n * Runs an arbitrary heavy, synchronous generator function in chunks, so it\n * never produces a long task on its own. Where `useChunkedTask` chunks \"one\n * function applied to every item of an array\" automatically, this hook\n * leaves the chunk boundaries to you — put `yield` wherever your function\n * can safely pause (see `DeferredGenerator`'s doc comment for an example).\n *\n * `task` is only called when the effect (re)creates the underlying task, not\n * on every render — list whatever it closes over in `deps`, the same way\n * you would for `useMemo` or `useEffect`.\n */\nexport function useDeferredTask<T>(\n task: () => DeferredGenerator<T>,\n deps: readonly unknown[],\n options?: UseDeferredTaskOptions,\n): UseDeferredTaskState<T> {\n const { timeSliceMs, batchSize, autoStart = true, onSlice, onYield } = options ?? {};\n\n // Stable identity, always runs the latest `task` passed this render —\n // see useChunkedTask.ts for why this is useEffectEvent rather than a ref\n // mutated during render.\n const stableTask = useEffectEvent(() => task());\n const stableOnSlice = useEffectEvent((durationMs: number, segmentsDone: number) => {\n onSlice?.(durationMs, segmentsDone);\n });\n const stableOnYield = useEffectEvent(() => {\n onYield?.();\n });\n\n const [controller] = useState(() => createTaskController<DeferredTask<T>>());\n\n const snapshot = useSyncExternalStore(\n controller.subscribe,\n () => controller.getSnapshot() ?? IDLE_SNAPSHOT,\n () => IDLE_SNAPSHOT,\n );\n\n useEffect(\n () => {\n const deferredTask = createDeferredTask<T>({\n task: stableTask,\n onSlice: stableOnSlice,\n onYield: stableOnYield,\n ...(timeSliceMs !== undefined ? { timeSliceMs } : {}),\n ...(batchSize !== undefined ? { batchSize } : {}),\n });\n controller.replace(deferredTask);\n if (autoStart) deferredTask.start();\n\n return () => {\n deferredTask.cancel();\n controller.replace(null);\n };\n },\n // `deps` plays the role `items` plays for useChunkedTask: it's the\n // caller's declaration of \"this is new work\". `stableTask` is an Effect\n // Event — stable by construction, intentionally excluded from the\n // reactive dependency set rather than omitted by oversight. `deps` is a\n // caller-supplied array, the same pattern (and the same reason it can't\n // be statically verified) as useMemo/useEffect's own deps parameter.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [...deps, timeSliceMs, batchSize, autoStart, controller],\n );\n\n const start = useCallback(() => controller.getCurrent()?.start(), [controller]);\n const pause = useCallback(() => controller.getCurrent()?.pause(), [controller]);\n const resume = useCallback(() => controller.getCurrent()?.resume(), [controller]);\n const cancel = useCallback(() => controller.getCurrent()?.cancel(), [controller]);\n\n return {\n status: snapshot.status,\n error: snapshot.error,\n // From the snapshot: the engine publishes the value before it\n // announces `done`, so a concurrent render can never see one without\n // the other. See useChunkedTask.ts.\n value: snapshot.value,\n isIdle: snapshot.status === 'idle',\n isProcessing: snapshot.status === 'processing',\n isPaused: snapshot.status === 'paused',\n isDone: snapshot.status === 'done',\n isCancelled: snapshot.status === 'cancelled',\n isError: snapshot.status === 'error',\n start,\n pause,\n resume,\n cancel,\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-inp-chunker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Headless React/TypeScript primitives that chunk heavy synchronous work and yield to the main thread, so long-running updates never cost you Interaction to Next Paint (INP).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"concurrent",
|
|
16
16
|
"headless"
|
|
17
17
|
],
|
|
18
|
-
"homepage": "https://github.
|
|
18
|
+
"homepage": "https://suprabhat02.github.io/react-inp-chunker/",
|
|
19
19
|
"bugs": "https://github.com/suprabhat02/react-inp-chunker/issues",
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
@@ -26,18 +26,48 @@
|
|
|
26
26
|
"type": "module",
|
|
27
27
|
"sideEffects": false,
|
|
28
28
|
"engines": {
|
|
29
|
-
"node": ">=
|
|
29
|
+
"node": ">=18"
|
|
30
30
|
},
|
|
31
31
|
"exports": {
|
|
32
32
|
".": {
|
|
33
|
-
"
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
"production": {
|
|
34
|
+
"import": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/production/index.js"
|
|
37
|
+
},
|
|
38
|
+
"require": {
|
|
39
|
+
"types": "./dist/index.d.cts",
|
|
40
|
+
"default": "./dist/production/index.cjs"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"import": {
|
|
44
|
+
"types": "./dist/index.d.ts",
|
|
45
|
+
"default": "./dist/index.js"
|
|
46
|
+
},
|
|
47
|
+
"require": {
|
|
48
|
+
"types": "./dist/index.d.cts",
|
|
49
|
+
"default": "./dist/index.cjs"
|
|
50
|
+
}
|
|
36
51
|
},
|
|
37
52
|
"./core": {
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
53
|
+
"production": {
|
|
54
|
+
"import": {
|
|
55
|
+
"types": "./dist/core/index.d.ts",
|
|
56
|
+
"default": "./dist/production/core/index.js"
|
|
57
|
+
},
|
|
58
|
+
"require": {
|
|
59
|
+
"types": "./dist/core/index.d.cts",
|
|
60
|
+
"default": "./dist/production/core/index.cjs"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"import": {
|
|
64
|
+
"types": "./dist/core/index.d.ts",
|
|
65
|
+
"default": "./dist/core/index.js"
|
|
66
|
+
},
|
|
67
|
+
"require": {
|
|
68
|
+
"types": "./dist/core/index.d.cts",
|
|
69
|
+
"default": "./dist/core/index.cjs"
|
|
70
|
+
}
|
|
41
71
|
},
|
|
42
72
|
"./package.json": "./package.json"
|
|
43
73
|
},
|
|
@@ -46,6 +76,7 @@
|
|
|
46
76
|
"types": "./dist/index.d.ts",
|
|
47
77
|
"files": [
|
|
48
78
|
"dist",
|
|
79
|
+
"core",
|
|
49
80
|
"README.md",
|
|
50
81
|
"LICENSE"
|
|
51
82
|
],
|
|
@@ -56,6 +87,7 @@
|
|
|
56
87
|
"build": "tsdown",
|
|
57
88
|
"dev": "tsdown --watch",
|
|
58
89
|
"typecheck": "tsc --noEmit",
|
|
90
|
+
"typecheck:react18": "tsc -p tsconfig.react18.json",
|
|
59
91
|
"lint": "eslint .",
|
|
60
92
|
"format": "prettier --write .",
|
|
61
93
|
"format:check": "prettier --check .",
|
|
@@ -63,53 +95,67 @@
|
|
|
63
95
|
"test:watch": "vitest",
|
|
64
96
|
"test:coverage": "vitest run --coverage",
|
|
65
97
|
"size": "size-limit",
|
|
66
|
-
"verify": "npm run lint && npm run format:check && npm run typecheck && npm run test:coverage && npm run build && npm run size",
|
|
67
|
-
"prepublishOnly": "npm run verify"
|
|
98
|
+
"verify": "npm run lint && npm run format:check && npm run typecheck && npm run test:coverage && npm run build && npm run api:check && npm run lint:package && npm run types:resolution && npm run types:consumer && npm run size",
|
|
99
|
+
"prepublishOnly": "npm run verify",
|
|
100
|
+
"bench": "npm run build && node bench/overhead.mjs",
|
|
101
|
+
"api:check": "node scripts/api-surface.mjs",
|
|
102
|
+
"api:update": "node scripts/api-surface.mjs --update",
|
|
103
|
+
"lint:package": "publint --strict",
|
|
104
|
+
"types:resolution": "node scripts/check-type-resolution.mjs",
|
|
105
|
+
"types:consumer": "tsc -p consumer/tsconfig.bundler.json && tsc -p consumer/tsconfig.nodenext.json",
|
|
106
|
+
"test:browser": "npm run build && node tests/browser/yielding.mjs"
|
|
68
107
|
},
|
|
69
108
|
"devDependencies": {
|
|
109
|
+
"@arethetypeswrong/core": "^0.18.5",
|
|
70
110
|
"@eslint/js": "10.0.1",
|
|
111
|
+
"@size-limit/preset-small-lib": "14.3.0",
|
|
71
112
|
"@testing-library/react": "16.3.3",
|
|
72
|
-
"@types/node": "
|
|
113
|
+
"@types/node": "^26.6.5",
|
|
73
114
|
"@types/react": "19.3.0",
|
|
74
115
|
"@types/react-dom": "19.3.0",
|
|
75
116
|
"@vitest/coverage-v8": "5.0.5",
|
|
76
117
|
"eslint": "10.13.0",
|
|
77
118
|
"eslint-plugin-react-hooks": "7.1.1",
|
|
119
|
+
"fast-check": "^4.10.2",
|
|
78
120
|
"globals": "17.13.0",
|
|
79
|
-
"jsdom": "
|
|
121
|
+
"jsdom": "^30.1.3",
|
|
122
|
+
"playwright": "^1.64.1",
|
|
80
123
|
"prettier": "3.9.10",
|
|
124
|
+
"publint": "^0.3.25",
|
|
81
125
|
"react": "19.3.1",
|
|
82
126
|
"react-dom": "19.3.1",
|
|
83
127
|
"size-limit": "14.3.0",
|
|
84
|
-
"@size-limit/preset-small-lib": "14.3.0",
|
|
85
128
|
"tsdown": "0.23.2",
|
|
86
|
-
"typescript": "6.0.3",
|
|
87
|
-
"typescript-eslint": "8.72.
|
|
129
|
+
"typescript": "^6.0.3",
|
|
130
|
+
"typescript-eslint": "8.72.1",
|
|
88
131
|
"vitest": "5.0.5"
|
|
89
132
|
},
|
|
90
133
|
"size-limit": [
|
|
91
134
|
{
|
|
92
135
|
"name": "core (import { createChunkScheduler })",
|
|
93
|
-
"path": "dist/core/index.js",
|
|
136
|
+
"path": "dist/production/core/index.js",
|
|
94
137
|
"import": "{ createChunkScheduler }",
|
|
95
|
-
"limit": "1.
|
|
138
|
+
"limit": "1.55 kB"
|
|
96
139
|
},
|
|
97
140
|
{
|
|
98
141
|
"name": "useChunkedTask",
|
|
99
|
-
"path": "dist/index.js",
|
|
142
|
+
"path": "dist/production/index.js",
|
|
100
143
|
"import": "{ useChunkedTask }",
|
|
101
144
|
"limit": "2.2 kB"
|
|
102
145
|
},
|
|
103
146
|
{
|
|
104
147
|
"name": "useDeferredTask",
|
|
105
|
-
"path": "dist/index.js",
|
|
148
|
+
"path": "dist/production/index.js",
|
|
106
149
|
"import": "{ useDeferredTask }",
|
|
107
|
-
"limit": "1.
|
|
150
|
+
"limit": "1.9 kB"
|
|
108
151
|
},
|
|
109
152
|
{
|
|
110
153
|
"name": "everything",
|
|
111
|
-
"path": "dist/index.js",
|
|
154
|
+
"path": "dist/production/index.js",
|
|
112
155
|
"limit": "3.5 kB"
|
|
113
156
|
}
|
|
114
|
-
]
|
|
157
|
+
],
|
|
158
|
+
"overrides": {
|
|
159
|
+
"minimatch": "^11.0.1"
|
|
160
|
+
}
|
|
115
161
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"deferred-task-5kfztF2G.js","names":["normalizeTimeSliceMs"],"sources":["../src/core/chunk-machine.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 * 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 * 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 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\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 {\n DEFAULT_TIME_SLICE_MS,\n type YieldStrategy,\n now,\n shouldYieldNow,\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 /** Called after each item is successfully processed. */\n readonly onItem?: (result: R, item: T, index: number) => void;\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\nexport interface ChunkSchedulerResult<R> {\n readonly status: 'done' | 'cancelled' | 'error';\n readonly results: readonly R[];\n readonly error?: unknown;\n}\n\nexport interface ChunkSnapshot {\n readonly status: ChunkStatus;\n readonly processed: number;\n readonly total: number;\n readonly error?: unknown;\n}\n\nexport interface ChunkScheduler<R> {\n start(): void;\n pause(): void;\n resume(): void;\n cancel(): void;\n getSnapshot(): ChunkSnapshot;\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\nfunction 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 * 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, signal, yieldStrategy } = options;\n const timeSliceMs = normalizeTimeSliceMs(options.timeSliceMs);\n const total = items.length;\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\n let snapshot: ChunkSnapshot = { status: machineState.status, processed, total };\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 refreshSnapshot(): void {\n snapshot = {\n status: machineState.status,\n processed,\n total,\n ...(machineState.error !== undefined ? { error: machineState.error } : {}),\n };\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 refreshSnapshot();\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 * The single path every terminal transition goes through: snapshots the\n * results array exactly once (not per item), publishes it via\n * getResults(), and settles the public promise with the same reference —\n * no redundant second copy.\n */\n function finish(status: 'done' | 'cancelled' | 'error', error?: unknown): void {\n const resultsCopy = [...results];\n finalResults = resultsCopy;\n settle(\n status === 'error'\n ? { status, error, results: resultsCopy }\n : { status, results: resultsCopy },\n );\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, error } = machineState;\n if (loopStarted || !isTerminal(status)) return;\n loopStarted = true;\n // Both 'done' and 'error' are unreachable here without the loop\n // having run — and the loop having run means `loopStarted` was\n // already true, which would have short-circuited this function above\n // — so 'cancelled' is the only status this ever actually sees. Kept\n // as the real (narrowed) type rather than assumed away, in case that\n // invariant ever changes; the 'error' branch specifically is untestable\n // defense-in-depth for the same structural reason.\n /* v8 ignore next */\n if (status === 'error') finish('error', error);\n else finish(status);\n }\n\n function handleAbort(): void {\n cancel();\n }\n\n function detachSignal(): void {\n signal?.removeEventListener('abort', handleAbort);\n }\n\n if (signal?.aborted) {\n dispatch({ type: 'CANCEL' });\n } else {\n signal?.addEventListener('abort', handleAbort);\n }\n\n async function runLoop(): Promise<void> {\n let sliceStartedAt = now();\n\n itemLoop: for (const [index, item] of items.entries()) {\n // Keep re-checking status for *this same* item until it's neither\n // paused nor cancelled, instead of trusting a single wake-from-pause\n // to mean \"clear to proceed\". A resume() can be undone by a\n // *reentrant* pause() triggered synchronously from a subscriber's\n // own notification (resume() dispatches, notify() runs listeners\n // synchronously, a listener calls pause() before resume() returns)\n // — so without this, the first item after such a resume() would\n // run anyway, even though the task is genuinely paused again by\n // the time it does. A plain `continue` on the outer for-of would\n // wrongly advance to the next item instead of re-checking this one,\n // which is why this is its own inner loop with a labeled break for\n // the one case (cancelled) that must stop processing entirely.\n for (;;) {\n const status: ChunkStatus = machineState.status;\n if (status === 'cancelled') break itemLoop;\n if (status !== 'paused') break;\n await waitForWake();\n sliceStartedAt = now();\n }\n\n try {\n const result = processItem(item, index);\n results.push(result);\n processed = index + 1;\n refreshSnapshot();\n onItem?.(result, item, index);\n } catch (error) {\n dispatch({ type: 'ERROR', error });\n finish('error', error);\n return;\n }\n\n if (shouldYieldNow({ sliceStartedAt, timeSliceMs })) {\n await yieldToMain(\n yieldStrategy !== undefined ? { strategy: yieldStrategy } : undefined,\n );\n notify(); // surface the progress made during this slice\n sliceStartedAt = now();\n }\n }\n\n if (machineState.status === 'cancelled') {\n finish('cancelled');\n return;\n }\n\n dispatch({ type: 'COMPLETE' });\n finish('done');\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: () => snapshot,\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 {\n DEFAULT_TIME_SLICE_MS,\n type YieldStrategy,\n now,\n shouldYieldNow,\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 readonly signal?: AbortSignal;\n /** Testing-only: forces a specific yield strategy instead of auto-detecting one. */\n readonly yieldStrategy?: YieldStrategy;\n}\n\nexport interface DeferredTaskResult<T> {\n readonly status: 'done' | 'cancelled' | 'error';\n readonly value?: T;\n readonly error?: unknown;\n}\n\nexport interface DeferredTaskSnapshot {\n readonly status: ChunkStatus;\n readonly error?: unknown;\n}\n\nexport interface DeferredTask<T> {\n start(): void;\n pause(): void;\n resume(): void;\n cancel(): void;\n getSnapshot(): DeferredTaskSnapshot;\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\nfunction 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 * 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 } = options;\n const timeSliceMs = normalizeTimeSliceMs(options.timeSliceMs);\n\n let machineState: ChunkMachineState = initialChunkState;\n let loopStarted = false;\n let settledFlag = false;\n let value: T | undefined;\n\n // Cached, and only rebuilt on an actual transition (see refreshSnapshot())\n // — not recomputed on every getSnapshot() call. useSyncExternalStore\n // requires repeated calls between notifications to return the *same*\n // reference; a fresh object literal on every call (as this used to be)\n // reads as \"changed every render\" and sends React into an infinite\n // re-render loop. Mirrors chunk-scheduler.ts's `snapshot` exactly.\n let snapshot: DeferredTaskSnapshot = { status: machineState.status };\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 refreshSnapshot(): void {\n snapshot = {\n status: machineState.status,\n ...(machineState.error !== undefined ? { error: machineState.error } : {}),\n };\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 refreshSnapshot();\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 function finish(\n status: 'done' | 'cancelled' | 'error',\n resolvedValue?: T,\n error?: unknown,\n ): void {\n if (status === 'done') {\n value = resolvedValue;\n settle({\n status,\n ...(resolvedValue !== undefined ? { value: resolvedValue } : {}),\n });\n return;\n }\n settle(status === 'error' ? { status, error } : { status });\n }\n\n function settleIfNeverStarted(): void {\n const { status, error } = machineState;\n if (loopStarted || !isTerminal(status)) return;\n loopStarted = true;\n // See the identical comment in chunk-scheduler.ts's settleIfNeverStarted():\n // both 'done' and 'error' are unreachable here, since reaching either\n // requires the loop to have run, which requires `loopStarted` already\n // true — which would have short-circuited this function above. Only\n // 'cancelled' is ever actually observed; 'error' is kept as the real,\n // narrowed type rather than assumed away, as untestable defense-in-depth.\n /* v8 ignore next */\n if (status === 'error') finish('error', undefined, error);\n else finish(status);\n }\n\n function handleAbort(): void {\n cancel();\n }\n\n function detachSignal(): void {\n signal?.removeEventListener('abort', handleAbort);\n }\n\n if (signal?.aborted) {\n dispatch({ type: 'CANCEL' });\n } else {\n signal?.addEventListener('abort', handleAbort);\n }\n\n async function runLoop(): Promise<void> {\n let sliceStartedAt = now();\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 // 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 step = generator.next();\n if (step.done) break;\n\n if (shouldYieldNow({ sliceStartedAt, timeSliceMs })) {\n await yieldToMain(\n yieldStrategy !== undefined ? { strategy: yieldStrategy } : undefined,\n );\n notify();\n sliceStartedAt = now();\n }\n }\n } catch (error) {\n dispatch({ type: 'ERROR', error });\n finish('error', undefined, error);\n return;\n }\n\n if (machineState.status === 'cancelled') {\n finish('cancelled');\n return;\n }\n\n dispatch({ type: 'COMPLETE' });\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 `if (step.done)\n // break;` — so the \": undefined\" side of this ternary can't actually\n // run. It stays only because it's what makes TypeScript narrow\n // step.value's type correctly (see the top of this function's history\n // for the exact inference issue); the real type is kept rather than\n // cast away.\n /* v8 ignore next */\n finish('done', step.done ? step.value : undefined);\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: () => snapshot,\n getValue: () => value,\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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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"deferred-task-BXVzapQg.cjs","names":["normalizeTimeSliceMs"],"sources":["../src/core/chunk-machine.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 * 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 * 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 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\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 {\n DEFAULT_TIME_SLICE_MS,\n type YieldStrategy,\n now,\n shouldYieldNow,\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 /** Called after each item is successfully processed. */\n readonly onItem?: (result: R, item: T, index: number) => void;\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\nexport interface ChunkSchedulerResult<R> {\n readonly status: 'done' | 'cancelled' | 'error';\n readonly results: readonly R[];\n readonly error?: unknown;\n}\n\nexport interface ChunkSnapshot {\n readonly status: ChunkStatus;\n readonly processed: number;\n readonly total: number;\n readonly error?: unknown;\n}\n\nexport interface ChunkScheduler<R> {\n start(): void;\n pause(): void;\n resume(): void;\n cancel(): void;\n getSnapshot(): ChunkSnapshot;\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\nfunction 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 * 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, signal, yieldStrategy } = options;\n const timeSliceMs = normalizeTimeSliceMs(options.timeSliceMs);\n const total = items.length;\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\n let snapshot: ChunkSnapshot = { status: machineState.status, processed, total };\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 refreshSnapshot(): void {\n snapshot = {\n status: machineState.status,\n processed,\n total,\n ...(machineState.error !== undefined ? { error: machineState.error } : {}),\n };\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 refreshSnapshot();\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 * The single path every terminal transition goes through: snapshots the\n * results array exactly once (not per item), publishes it via\n * getResults(), and settles the public promise with the same reference —\n * no redundant second copy.\n */\n function finish(status: 'done' | 'cancelled' | 'error', error?: unknown): void {\n const resultsCopy = [...results];\n finalResults = resultsCopy;\n settle(\n status === 'error'\n ? { status, error, results: resultsCopy }\n : { status, results: resultsCopy },\n );\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, error } = machineState;\n if (loopStarted || !isTerminal(status)) return;\n loopStarted = true;\n // Both 'done' and 'error' are unreachable here without the loop\n // having run — and the loop having run means `loopStarted` was\n // already true, which would have short-circuited this function above\n // — so 'cancelled' is the only status this ever actually sees. Kept\n // as the real (narrowed) type rather than assumed away, in case that\n // invariant ever changes; the 'error' branch specifically is untestable\n // defense-in-depth for the same structural reason.\n /* v8 ignore next */\n if (status === 'error') finish('error', error);\n else finish(status);\n }\n\n function handleAbort(): void {\n cancel();\n }\n\n function detachSignal(): void {\n signal?.removeEventListener('abort', handleAbort);\n }\n\n if (signal?.aborted) {\n dispatch({ type: 'CANCEL' });\n } else {\n signal?.addEventListener('abort', handleAbort);\n }\n\n async function runLoop(): Promise<void> {\n let sliceStartedAt = now();\n\n itemLoop: for (const [index, item] of items.entries()) {\n // Keep re-checking status for *this same* item until it's neither\n // paused nor cancelled, instead of trusting a single wake-from-pause\n // to mean \"clear to proceed\". A resume() can be undone by a\n // *reentrant* pause() triggered synchronously from a subscriber's\n // own notification (resume() dispatches, notify() runs listeners\n // synchronously, a listener calls pause() before resume() returns)\n // — so without this, the first item after such a resume() would\n // run anyway, even though the task is genuinely paused again by\n // the time it does. A plain `continue` on the outer for-of would\n // wrongly advance to the next item instead of re-checking this one,\n // which is why this is its own inner loop with a labeled break for\n // the one case (cancelled) that must stop processing entirely.\n for (;;) {\n const status: ChunkStatus = machineState.status;\n if (status === 'cancelled') break itemLoop;\n if (status !== 'paused') break;\n await waitForWake();\n sliceStartedAt = now();\n }\n\n try {\n const result = processItem(item, index);\n results.push(result);\n processed = index + 1;\n refreshSnapshot();\n onItem?.(result, item, index);\n } catch (error) {\n dispatch({ type: 'ERROR', error });\n finish('error', error);\n return;\n }\n\n if (shouldYieldNow({ sliceStartedAt, timeSliceMs })) {\n await yieldToMain(\n yieldStrategy !== undefined ? { strategy: yieldStrategy } : undefined,\n );\n notify(); // surface the progress made during this slice\n sliceStartedAt = now();\n }\n }\n\n if (machineState.status === 'cancelled') {\n finish('cancelled');\n return;\n }\n\n dispatch({ type: 'COMPLETE' });\n finish('done');\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: () => snapshot,\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 {\n DEFAULT_TIME_SLICE_MS,\n type YieldStrategy,\n now,\n shouldYieldNow,\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 readonly signal?: AbortSignal;\n /** Testing-only: forces a specific yield strategy instead of auto-detecting one. */\n readonly yieldStrategy?: YieldStrategy;\n}\n\nexport interface DeferredTaskResult<T> {\n readonly status: 'done' | 'cancelled' | 'error';\n readonly value?: T;\n readonly error?: unknown;\n}\n\nexport interface DeferredTaskSnapshot {\n readonly status: ChunkStatus;\n readonly error?: unknown;\n}\n\nexport interface DeferredTask<T> {\n start(): void;\n pause(): void;\n resume(): void;\n cancel(): void;\n getSnapshot(): DeferredTaskSnapshot;\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\nfunction 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 * 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 } = options;\n const timeSliceMs = normalizeTimeSliceMs(options.timeSliceMs);\n\n let machineState: ChunkMachineState = initialChunkState;\n let loopStarted = false;\n let settledFlag = false;\n let value: T | undefined;\n\n // Cached, and only rebuilt on an actual transition (see refreshSnapshot())\n // — not recomputed on every getSnapshot() call. useSyncExternalStore\n // requires repeated calls between notifications to return the *same*\n // reference; a fresh object literal on every call (as this used to be)\n // reads as \"changed every render\" and sends React into an infinite\n // re-render loop. Mirrors chunk-scheduler.ts's `snapshot` exactly.\n let snapshot: DeferredTaskSnapshot = { status: machineState.status };\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 refreshSnapshot(): void {\n snapshot = {\n status: machineState.status,\n ...(machineState.error !== undefined ? { error: machineState.error } : {}),\n };\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 refreshSnapshot();\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 function finish(\n status: 'done' | 'cancelled' | 'error',\n resolvedValue?: T,\n error?: unknown,\n ): void {\n if (status === 'done') {\n value = resolvedValue;\n settle({\n status,\n ...(resolvedValue !== undefined ? { value: resolvedValue } : {}),\n });\n return;\n }\n settle(status === 'error' ? { status, error } : { status });\n }\n\n function settleIfNeverStarted(): void {\n const { status, error } = machineState;\n if (loopStarted || !isTerminal(status)) return;\n loopStarted = true;\n // See the identical comment in chunk-scheduler.ts's settleIfNeverStarted():\n // both 'done' and 'error' are unreachable here, since reaching either\n // requires the loop to have run, which requires `loopStarted` already\n // true — which would have short-circuited this function above. Only\n // 'cancelled' is ever actually observed; 'error' is kept as the real,\n // narrowed type rather than assumed away, as untestable defense-in-depth.\n /* v8 ignore next */\n if (status === 'error') finish('error', undefined, error);\n else finish(status);\n }\n\n function handleAbort(): void {\n cancel();\n }\n\n function detachSignal(): void {\n signal?.removeEventListener('abort', handleAbort);\n }\n\n if (signal?.aborted) {\n dispatch({ type: 'CANCEL' });\n } else {\n signal?.addEventListener('abort', handleAbort);\n }\n\n async function runLoop(): Promise<void> {\n let sliceStartedAt = now();\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 // 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 step = generator.next();\n if (step.done) break;\n\n if (shouldYieldNow({ sliceStartedAt, timeSliceMs })) {\n await yieldToMain(\n yieldStrategy !== undefined ? { strategy: yieldStrategy } : undefined,\n );\n notify();\n sliceStartedAt = now();\n }\n }\n } catch (error) {\n dispatch({ type: 'ERROR', error });\n finish('error', undefined, error);\n return;\n }\n\n if (machineState.status === 'cancelled') {\n finish('cancelled');\n return;\n }\n\n dispatch({ type: 'COMPLETE' });\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 `if (step.done)\n // break;` — so the \": undefined\" side of this ternary can't actually\n // run. It stays only because it's what makes TypeScript narrow\n // step.value's type correctly (see the top of this function's history\n // for the exact inference issue); the real type is kept rather than\n // cast away.\n /* v8 ignore next */\n finish('done', step.done ? step.value : undefined);\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: () => snapshot,\n getValue: () => value,\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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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-C6B_Nm3i.d.cts","names":[],"sources":["../src/core/chunk-machine.ts","../src/core/scheduler.ts","../src/core/chunk-scheduler.ts","../src/core/deferred-task.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;KAyBY;KAGA;WACG;;WACA;;WACA;;WACA;;WACA;;WACA;WAAwB;;UAEtB;WACN,QAAQ;WACR;;cAGE,mBAAmB;KAEpB;iBAQI,WAAW,QAAQ,cAAc,UAAU;;;;;iBA0D3C,WACd,OAAO,mBACP,OAAO,aACN;;;;;;;;;;;;;;;;;;;;;;;;KCxFS;;cAGC;;iBAiBG;;iBAMA;;iBAKA;;;;;;iBAUA;;iBAUA,wBAAwB;UA6BvB;;;;;;EAMf,WAAW;;;;;;;;;;iBAWS,YAAY,UAAU,qBAAqB;;iBA8BjD;UAWC;;EAEf;;EAEA;;;;;;;iBAQc,eAAe,SAAS;UC9JvB,sBAAsB,GAAG;WAC/B,gBAAgB;WAChB,cAAc,MAAM,GAAG,kBAAkB;;WAEzC;;WAEA,UAAU,QAAQ,GAAG,MAAM,GAAG;;WAE9B,SAAS;;WAET,gBAAgB;;UAGV,qBAAqB;WAC3B;WACA,kBAAkB;WAClB;;UAGM;WACN,QAAQ;WACR;WACA;WACA;;UAGM,eAAe;EAC9B;EACA;EACA;EACA;EACA,eAAe;;;;;;;;;EASf,uBAAuB;EACvB,UAAU;;WAED,SAAS,QAAQ,qBAAqB;;;;;;;;;;;;;;iBAsBjC,qBAAqB,GAAG,GACtC,SAAS,sBAAsB,GAAG,KACjC,eAAe;;;;;;;;;;;;;;;;;;;;;;KC9CN,kBAAkB,KAAK,gBAAgB;UAElC,oBAAoB;WAC1B,YAAY,kBAAkB;;WAE9B;WACA,SAAS;;WAET,gBAAgB;;UAGV,mBAAmB;WACzB;WACA,QAAQ;WACR;;UAGM;WACN,QAAQ;WACR;;UAGM,aAAa;EAC5B;EACA;EACA;EACA;EACA,eAAe;;EAEf,YAAY;EACZ,UAAU;WACD,SAAS,QAAQ,mBAAmB;;;;;;;;;iBAiB/B,mBAAmB,GAAG,SAAS,oBAAoB,KAAK,aAAa"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-C6B_Nm3i.d.ts","names":[],"sources":["../src/core/chunk-machine.ts","../src/core/scheduler.ts","../src/core/chunk-scheduler.ts","../src/core/deferred-task.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;KAyBY;KAGA;WACG;;WACA;;WACA;;WACA;;WACA;;WACA;WAAwB;;UAEtB;WACN,QAAQ;WACR;;cAGE,mBAAmB;KAEpB;iBAQI,WAAW,QAAQ,cAAc,UAAU;;;;;iBA0D3C,WACd,OAAO,mBACP,OAAO,aACN;;;;;;;;;;;;;;;;;;;;;;;;KCxFS;;cAGC;;iBAiBG;;iBAMA;;iBAKA;;;;;;iBAUA;;iBAUA,wBAAwB;UA6BvB;;;;;;EAMf,WAAW;;;;;;;;;;iBAWS,YAAY,UAAU,qBAAqB;;iBA8BjD;UAWC;;EAEf;;EAEA;;;;;;;iBAQc,eAAe,SAAS;UC9JvB,sBAAsB,GAAG;WAC/B,gBAAgB;WAChB,cAAc,MAAM,GAAG,kBAAkB;;WAEzC;;WAEA,UAAU,QAAQ,GAAG,MAAM,GAAG;;WAE9B,SAAS;;WAET,gBAAgB;;UAGV,qBAAqB;WAC3B;WACA,kBAAkB;WAClB;;UAGM;WACN,QAAQ;WACR;WACA;WACA;;UAGM,eAAe;EAC9B;EACA;EACA;EACA;EACA,eAAe;;;;;;;;;EASf,uBAAuB;EACvB,UAAU;;WAED,SAAS,QAAQ,qBAAqB;;;;;;;;;;;;;;iBAsBjC,qBAAqB,GAAG,GACtC,SAAS,sBAAsB,GAAG,KACjC,eAAe;;;;;;;;;;;;;;;;;;;;;;KC9CN,kBAAkB,KAAK,gBAAgB;UAElC,oBAAoB;WAC1B,YAAY,kBAAkB;;WAE9B;WACA,SAAS;;WAET,gBAAgB;;UAGV,mBAAmB;WACzB;WACA,QAAQ;WACR;;UAGM;WACN,QAAQ;WACR;;UAGM,aAAa;EAC5B;EACA;EACA;EACA;EACA,eAAe;;EAEf,YAAY;EACZ,UAAU;WACD,SAAS,QAAQ,mBAAmB;;;;;;;;;iBAiB/B,mBAAmB,GAAG,SAAS,oBAAoB,KAAK,aAAa"}
|