react-sync-ui 1.0.2 → 2.0.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/CHANGELOG.md ADDED
@@ -0,0 +1,111 @@
1
+ # Changelog
2
+
3
+ ## 2.0.0
4
+
5
+ ### Breaking changes
6
+
7
+ - **React peer dependency is now `^18.0.0 || ^19.0.0`** (was `>=16`). The
8
+ queue is exposed to React through `useSyncExternalStore`, which ships with
9
+ React 18.
10
+ - **ESM-only package.** `package.json` has `"type": "module"` and an `exports`
11
+ map; the only code entry is `dist/index.js` (+ `dist/index.d.ts`), next to
12
+ the `./package.json` subpath. The CJS build and `dist/react-sync-ui.esm.js`
13
+ are gone.
14
+ - **Calling a sync function before `<SyncUI />` is mounted no longer throws**
15
+ `You have to initialize <SyncUI />`. The item is queued and rendered as soon
16
+ as a host mounts. In development a `console.error` is logged if items are
17
+ still pending after 3 seconds and no `<SyncUI />` has ever mounted.
18
+ - **`resolve` / `reject` are bound to their own item and idempotent.** A second
19
+ call for the same item, or a call from a stale closure after the item has
20
+ left the queue, is a no-op instead of settling the _next_ caller's promise.
21
+ This also applies to `usePromiseQueue().head.resolve/reject`.
22
+ - **`reject()` with no reason now rejects with an `Error`** instead of
23
+ `undefined`: `new Error("react-sync-ui: rejected without a reason")`. The
24
+ idiomatic `catch (error) { toast(error.message) }` therefore no longer throws
25
+ a `TypeError` on top of the cancellation. Pass your own reason whenever the
26
+ caller has to tell one apart from another. Applies to `props.reject()` and to
27
+ `usePromiseQueue().head.reject()`.
28
+ - **`usePromiseQueue` rejects its pending items when the owning component
29
+ unmounts**, with
30
+ `new Error("react-sync-ui: usePromiseQueue unmounted with pending items")`.
31
+ That queue is created by the hook and dies with the component, so previously
32
+ every `await push(...)` stayed suspended forever. StrictMode's simulated
33
+ unmount/remount does not drain anything; only a real unmount does. The
34
+ `makeSyncUI` queue is unaffected — it lives in the factory and survives
35
+ `<SyncUI />` unmounting.
36
+ - `usePromiseQueue` is now exported from the package entry (previously only
37
+ reachable from `react-sync-ui/src/syncUI`).
38
+ - `reject` is typed as `(reason?: unknown) => void` (was `any`).
39
+ - `require("react-sync-ui")` now needs Node `>=20.19` / `>=22.12` (unflagged
40
+ `require(esm)`); on older Node use `import` or a dynamic `import()`.
41
+
42
+ ### Fixes
43
+
44
+ - Promise settlement no longer happens inside a `setState` updater. Under
45
+ StrictMode / concurrent rendering the updater could be replayed against a
46
+ different queue and settle the wrong promise with the wrong value.
47
+ - `makeSyncUI` called after `<SyncUI />` has mounted (lazy-loaded chunk, Vite
48
+ HMR re-evaluating a module) now works; previously the first call threw.
49
+ - Mounting two `<SyncUI />` of the same factory no longer breaks pushes when
50
+ either one unmounts. Only the first mounted host renders; a dev warning is
51
+ logged for the duplicate.
52
+ - Unmounting `<SyncUI />` no longer loses queued items: the queue lives in the
53
+ factory closure, so pending promises survive a remount (StrictMode, HMR,
54
+ route changes).
55
+ - `<SyncUI />` inside a React 19.2 `<Activity mode="hidden">` subtree no
56
+ longer jams the queue: going back to `visible` re-reads the store and
57
+ restores the open dialog. (While hidden the host renders nothing, so the
58
+ dialog's own local state starts over; the queue is untouched.)
59
+ - A sync component that throws during render is no longer a poison pill. An
60
+ internal error boundary rejects that call's promise with the thrown error
61
+ and renders the next queued item; the app's own error boundary is not
62
+ triggered.
63
+ - Calling a sync function during server rendering only queues the item: the
64
+ dev "no `<SyncUI />` is mounted" warning is not logged and no 3 s timer is
65
+ scheduled when `typeof window === "undefined"`, so a Node process, lambda or
66
+ test worker is no longer held open. `<SyncUI />` renders nothing on the
67
+ server and hydrates cleanly, with pushes before and after hydration.
68
+ - A queued item whose component is missing from the registry (a module graph
69
+ reset under a live queue) is rejected with
70
+ `new Error("react-sync-ui: no component registered for this sync UI")`
71
+ instead of blocking itself and everything queued behind it forever. The
72
+ development `console.error` is still logged.
73
+ - The dev-only "no `<SyncUI />` is mounted" timer is cleared as soon as a host
74
+ mounts, instead of lingering for the rest of its 3 s.
75
+ - Dev-only warnings are gated by a plain `process.env.NODE_ENV` read, the same
76
+ convention React uses. Bundlers replace it, so the warnings are active in
77
+ development and dropped from production bundles. Importing the package with
78
+ no bundler and no `process` global throws at module load, as React does.
79
+ - Item keys use a monotonic counter instead of `Math.random()`.
80
+ - `<SyncUI />` renders only the head item's component instead of one wrapper
81
+ per registered component.
82
+
83
+ ### Types
84
+
85
+ - Added `SyncUIProps`, `SyncUIComponent`, `SyncUIFactory`, `SyncUIFunction`
86
+ and `PromiseQueueAPI` type exports.
87
+ - `SyncUIFunction<InputData, ResolveValue = void>` names what `makeSyncUI`
88
+ returns — `(input: InputData) => Promise<ResolveValue>` — so wrappers,
89
+ context values and props types no longer have to re-spell the signature.
90
+ - `PromiseQueueAPI<InputData, ResolveValue = void>` defaults its second type
91
+ argument (`PromiseQueueAPI<Msg>` used to be a hard error), and its `head` is
92
+ now `SyncUIProps<InputData, ResolveValue>` instead of a structurally
93
+ identical inline type.
94
+ - The public generic parameters are uniformly named `InputData` /
95
+ `ResolveValue` (they show up verbatim in `dist/*.d.ts` and editor tooltips).
96
+ - `SyncUIComponent<Data, Result>` is `ComponentType<SyncUIProps<Data, Result>>`
97
+ instead of `(props) => ReactNode`, so `React.FC<SyncUIProps<...>>`, class
98
+ components, `memo()` and `forwardRef()` are all accepted by `makeSyncUI`.
99
+ (React 19's `FunctionComponent` returns `ReactNode | Promise<ReactNode>`,
100
+ which the old signature rejected.)
101
+
102
+ ### Tooling
103
+
104
+ - Build: Vite 8 library mode (`vite build`) with `unplugin-dts`, replacing
105
+ tsdx. Sourcemaps included, output not minified.
106
+ - Tests: Vitest 4 + Testing Library 16 + jsdom, React 19 in devDependencies.
107
+ - ESLint 10 flat config with `typescript-eslint` and
108
+ `eslint-plugin-react-hooks` v7, Prettier, husky 9 pre-commit hook, size-limit
109
+ 13, publint and are-the-types-wrong in `npm run check`.
110
+ - TypeScript 5.9, `jsx: react-jsx` (no `React` default import needed).
111
+ - The example app uses the native `<dialog>` element, no UI framework.