nanoeffects 0.16.0 → 0.18.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 CHANGED
@@ -15,6 +15,8 @@ npm install nanoeffects
15
15
 
16
16
  ESM only, no dependencies, types included.
17
17
 
18
+ Starting a new project? `npm create nanoeffects@latest my-app` makes a Vite app with a bus ([Docs](docs/starter.md)).
19
+
18
20
  Want to try it first? [Open the playground](https://stackblitz.com/github/nanoeffects/nanoeffects/tree/main/examples/playground): a search box with `debounce` and `latest`, running in your browser, no install. More playgrounds (React) and recipes: [Examples](examples/README.md).
19
21
 
20
22
  ## Create a bus
package/dist/react.d.ts CHANGED
@@ -24,7 +24,30 @@ type ReactApi<E extends EventMap> = {
24
24
  <K extends EventKey<E>>(creator: FreeActionCreator<K>, value: E[K], options?: UseEmitterOptions<E[K]>): void;
25
25
  <P, T extends string>(creator: ActionCreator<P, T>, value: P, options?: UseEmitterOptions<P>): void;
26
26
  };
27
+ /**
28
+ * A React hook with the signature of `useState`: returns `[state, setState]`, and
29
+ * every emit of `event` sets the state to its payload while the component is
30
+ * mounted. `initial` may be a function, as in `useState`. The bus keeps no values,
31
+ * so an event emitted before the first commit is not seen: read a current value
32
+ * into `initial` when that matters. The method does not read `this`, so
33
+ * `export const { useEventState } = bus` gives a free hook.
34
+ *
35
+ * @example
36
+ * ```tsx
37
+ * const [found, setFound] = useEventState(searchLoaded, []); // [] until the first searchLoaded
38
+ * ```
39
+ */
40
+ useEventState: {
41
+ <K extends EventKey<E>, S>(event: K, initial: StateOf<E[K], S> | (() => StateOf<E[K], S>)): EventState<StateOf<E[K], S>>;
42
+ /** A creator without a payload type: the payload comes from the event map. */
43
+ <K extends EventKey<E>, S>(creator: FreeActionCreator<K>, initial: StateOf<E[K], S> | (() => StateOf<E[K], S>)): EventState<StateOf<E[K], S>>;
44
+ <P, T extends string>(creator: ActionCreator<P, T>, initial: P | (() => P)): EventState<P>;
45
+ };
27
46
  };
47
+ /** What `useEventState` returns: the pair of `useState`, with the payload of the event as state. */
48
+ type EventState<P> = [P, (next: P | ((previous: P) => P)) => void];
49
+ type Widen<S> = [S] extends [never[]] ? unknown[] : S;
50
+ type StateOf<P, S> = unknown extends P ? Widen<S> : P;
28
51
  /**
29
52
  * Adds `bus.useEmitter(event, value)`: a hook that tells the bus when a value of
30
53
  * a component changes, without the traps of `useEffect(() => emit(value), [value])`
package/dist/react.js CHANGED
@@ -1 +1 @@
1
- import{t as e}from"./with-policy-CW50Edgk.js";import{t}from"./origin-key-BbWN3gmN.js";import{useEffect as n,useMemo as r,useRef as i}from"react";const a=()=>e=>{function r(r,a,o){let s=i(a),c=i({event:r,options:o});n(()=>{c.current={event:r,options:o}}),n(()=>{let{event:n,options:r}=c.current,i=r?.equals??Object.is,o=s.current;if(i(o,a))return;s.current=a;let l=typeof n==`function`?n(a):{type:n,payload:a},u=Reflect.get(e,t);u?u(l,r??{}):e.emit(l)},[a])}return Object.assign(e,{useEmitter:r})},o=(t,a,o=[])=>{let s=i(t);n(()=>{s.current=t});let c=r(()=>e(a((e,t)=>s.current(e,t))),o);return n(()=>()=>c.cancel(),[c]),c};export{a as reactPlugin,o as usePolicy};
1
+ import{t as e}from"./with-policy-CW50Edgk.js";import{t}from"./origin-key-BbWN3gmN.js";import{useEffect as n,useMemo as r,useRef as i,useState as a}from"react";const o=()=>e=>{function r(r,a,o){let s=i(a),c=i({event:r,options:o});n(()=>{c.current={event:r,options:o}}),n(()=>{let{event:n,options:r}=c.current,i=r?.equals??Object.is,o=s.current;if(i(o,a))return;s.current=a;let l=typeof n==`function`?n(a):{type:n,payload:a},u=Reflect.get(e,t);u?u(l,r??{}):e.emit(l)},[a])}function o(t,r){let[i,o]=a(r);return n(()=>{let n=t;return e.on(n,e=>{let t=e;o(()=>t)})},[t]),[i,o]}return Object.assign(e,{useEmitter:r,useEventState:o})},s=(t,a,o=[])=>{let s=i(t);n(()=>{s.current=t});let c=r(()=>e(a((e,t)=>s.current(e,t))),o);return n(()=>()=>c.cancel(),[c]),c};export{o as reactPlugin,s as usePolicy};
package/docs/react.md CHANGED
@@ -162,6 +162,68 @@ const checkName = latest(async (name, ctx) => {
162
162
 
163
163
  In the component, add `bus.emit("form/closed")` to the cleanup function of the effect. This is optional: skip it when finishing a request after unmount does no harm, as for a cheap check.
164
164
 
165
+ ## Show the last value of an event as state
166
+
167
+ **Problem.** A component often needs the latest result of an event, such as the found items of a search. The hand-written form is easy to get slightly wrong:
168
+
169
+ <!-- docs-check: skip - the hand-written form that this section replaces -->
170
+ ```jsx
171
+ useEffect(() => bus.on(searchLoaded, setFound), []);
172
+ ```
173
+
174
+ It needs a `useState` next to it, the empty `[]` hides the dependency on the event, and it breaks if the payload is a function, because React calls `setFound(payload)` as an updater.
175
+
176
+ **When.** The value you show comes from the bus, not from the component. For the opposite direction, a value of the component that the bus must hear, use `useEmitter`.
177
+
178
+ `useEventState(event, initial)` has the signature of `useState`: it returns `[state, setState]`. `initial` is the state before the first event and may be a function, as in `useState`. After that, every emit of `event` sets the state to its payload. The hook unsubscribes when the component unmounts.
179
+
180
+ ```js
181
+ // bus.js
182
+ import { createBus } from "nanoeffects";
183
+ import { reactPlugin } from "nanoeffects/react";
184
+
185
+ // createBus — the event bus, created once outside React
186
+ // reactPlugin — adds the hooks bus.useEmitter and bus.useEventState
187
+ export const bus = createBus().use(reactPlugin());
188
+ export const { useEventState } = bus; // a free hook: components import it without the bus
189
+ ```
190
+
191
+ ```js
192
+ // search.js
193
+ import { createAction } from "nanoeffects/actions";
194
+
195
+ // createAction — the event name; the handlers emit it with the found titles
196
+ export const searchLoaded = createAction("search/loaded");
197
+ ```
198
+
199
+ ```jsx
200
+ // Found.jsx
201
+ import { useEventState } from "./bus";
202
+ import { searchLoaded } from "./search";
203
+
204
+ export function Found() {
205
+ // useEventState — like useState([]), but searchLoaded sets the value
206
+ const [found, setFound] = useEventState(searchLoaded, []);
207
+
208
+ return (
209
+ <>
210
+ <ul>
211
+ {found.map((title) => (
212
+ <li key={title}>{title}</li>
213
+ ))}
214
+ </ul>
215
+ <button onClick={() => setFound([])}>Clear</button>
216
+ </>
217
+ );
218
+ }
219
+ ```
220
+
221
+ `setFound` is the setter of React. It changes the state of this component only and emits nothing.
222
+
223
+ In TypeScript, the state type comes from the event: from `createAction<string[]>("search/loaded")` or from the event map of the bus. When the event has no payload type, as in the example above, it comes from `initial`: `["a"]` is `string[]`, and an empty `[]` is `unknown[]`. Give the event a type, or pass a typed `initial`, when you need more.
224
+
225
+ The bus keeps no values, so the hook cannot replay an event. An event emitted before the component mounts is not seen: if that matters, read the current value into `initial`.
226
+
165
227
  ## Keep a debounced handler between renders
166
228
 
167
229
  **Problem.** A component runs again on every render. A policy made in its body, such as `debounce(300, search)`, is made again each time and loses its timer. A policy made once keeps the callback of the first render, which reads old props and state.
@@ -0,0 +1,77 @@
1
+ # Start a project from a template
2
+
3
+ Use this page when you want a running project in one command instead of wiring Vite and the bus by hand. It makes a small app with a search box that waits for you to stop typing (`debounce`) and aborts the older request when a newer one starts (`latest`).
4
+
5
+ You need Node 18 or newer.
6
+
7
+ ## Create the project
8
+
9
+ Run the command for your package manager. Replace `my-app` with the name of the new directory.
10
+
11
+ ```bash
12
+ npm create nanoeffects@latest my-app
13
+ ```
14
+
15
+ ```bash
16
+ pnpm create nanoeffects my-app
17
+ ```
18
+
19
+ ```bash
20
+ yarn create nanoeffects my-app
21
+ ```
22
+
23
+ ```bash
24
+ bun create nanoeffects my-app
25
+ ```
26
+
27
+ The command asks for the template when you do not give one. The directory must be new or empty: the command stops instead of overwriting files.
28
+
29
+ Then follow the steps it prints:
30
+
31
+ ```bash
32
+ cd my-app
33
+ npm install
34
+ npm run dev
35
+ ```
36
+
37
+ Open the address that Vite prints and type in the box. The fake server answers after 800 ms, so you can see the older request being aborted.
38
+
39
+ ## Choose a template
40
+
41
+ Pass `--template` to skip the question. With npm, put `--` before the option.
42
+
43
+ | Template | What it shows | Use it when |
44
+ | -------- | ------------- | ----------- |
45
+ | `vanilla` | A bus, one event and a `debounce` plus `latest` handler, with plain DOM code | You use no framework, or want to see the bus alone |
46
+ | `react` | The same handler with `useEmitter` and `useEventState`, and `usePolicy` for a debounced save in a component | You build a React app |
47
+ | `vanilla-ts` | The `vanilla` app in TypeScript | You want the compiler to check events and handlers |
48
+ | `react-ts` | The `react` app in TypeScript | You build a React app in TypeScript |
49
+
50
+ ```bash
51
+ npm create nanoeffects@latest my-app -- --template react
52
+ ```
53
+
54
+ The TypeScript templates are the same apps with the same files, written in `.ts` and `.tsx`. Events are made with `createAction<Payload>`, so handlers and `useEventState` get their types from the events, with no annotation in the component. `npm run build` type-checks the project (`tsc --noEmit`) before bundling it.
55
+
56
+ ```bash
57
+ npm create nanoeffects@latest my-app -- --template react-ts
58
+ ```
59
+
60
+ ## Read the files
61
+
62
+ Every template splits the code the way [the guide](guide.md) does: one job per file. In the TypeScript templates the files end in `.ts` (and `.tsx` for components).
63
+
64
+ | File | Job |
65
+ | ---- | --- |
66
+ | `src/bus.js` | Creates the bus. It imports no events. |
67
+ | `src/search.js` | Names the events with `createAction`. It imports no bus. |
68
+ | `src/search-handlers.js` | Handles the events with `debounce` and `latest`. It knows the bus and the events. |
69
+ | `src/api.js` | A fake server. Replace it with your own requests. |
70
+
71
+ Every call of nanoeffects in these files has a comment that says what it does. To make it your own, replace `src/api.js`, then rename the events and the handler.
72
+
73
+ ## Next steps
74
+
75
+ - [Policies](policies.md): `leading`, `throttle` and `queue` next to `latest`.
76
+ - [Build a sign-up form in React](react.md): the same ideas in a bigger React example.
77
+ - [Package entries](package.md): which import holds what.
package/llms-full.txt CHANGED
@@ -9,7 +9,7 @@ Rules that generated code often gets wrong:
9
9
  - A handler gets the payload and a task context: `(payload, ctx)`. The context has `signal`, `emit`, `on`, `run`. Pass `ctx.signal` to `fetch`.
10
10
  - Effects take `ctx` as the first argument: `retry(ctx, fn, options)`, `timeout(ctx, ms, fn)`, `take(ctx, "event")`.
11
11
  - Policies wrap a handler: `bus.on("search", latest(async (query, ctx) => { ... }))`.
12
- - Each part is imported from its own path: `nanoeffects` (`createBus`), `nanoeffects/policies`, `nanoeffects/effects`, `nanoeffects/actions`, `nanoeffects/redux`, `nanoeffects/observable`, `nanoeffects/react` (`reactPlugin`, adds `bus.useEmitter`; `usePolicy`; needs React), `nanoeffects/trace` (`createTracer`, loggers, `tracePlugin`), `nanoeffects/dev`.
12
+ - Each part is imported from its own path: `nanoeffects` (`createBus`), `nanoeffects/policies`, `nanoeffects/effects`, `nanoeffects/actions`, `nanoeffects/redux`, `nanoeffects/observable`, `nanoeffects/react` (`reactPlugin`, adds `bus.useEmitter` and `bus.useEventState`; `usePolicy`; needs React), `nanoeffects/trace` (`createTracer`, loggers, `tracePlugin`), `nanoeffects/dev`.
13
13
  - `bus.emit(command(payload))` runs the command's handler and returns its result. Plain events return nothing.
14
14
 
15
15
  ---
@@ -500,6 +500,87 @@ More in [Errors](errors.md).
500
500
  | Guarantees | [Semantics](semantics.md) |
501
501
  | Which import | [Package entries](package.md) |
502
502
 
503
+ ---
504
+ Source: docs/starter.md
505
+
506
+ # Start a project from a template
507
+
508
+ Use this page when you want a running project in one command instead of wiring Vite and the bus by hand. It makes a small app with a search box that waits for you to stop typing (`debounce`) and aborts the older request when a newer one starts (`latest`).
509
+
510
+ You need Node 18 or newer.
511
+
512
+ ## Create the project
513
+
514
+ Run the command for your package manager. Replace `my-app` with the name of the new directory.
515
+
516
+ ```bash
517
+ npm create nanoeffects@latest my-app
518
+ ```
519
+
520
+ ```bash
521
+ pnpm create nanoeffects my-app
522
+ ```
523
+
524
+ ```bash
525
+ yarn create nanoeffects my-app
526
+ ```
527
+
528
+ ```bash
529
+ bun create nanoeffects my-app
530
+ ```
531
+
532
+ The command asks for the template when you do not give one. The directory must be new or empty: the command stops instead of overwriting files.
533
+
534
+ Then follow the steps it prints:
535
+
536
+ ```bash
537
+ cd my-app
538
+ npm install
539
+ npm run dev
540
+ ```
541
+
542
+ Open the address that Vite prints and type in the box. The fake server answers after 800 ms, so you can see the older request being aborted.
543
+
544
+ ## Choose a template
545
+
546
+ Pass `--template` to skip the question. With npm, put `--` before the option.
547
+
548
+ | Template | What it shows | Use it when |
549
+ | -------- | ------------- | ----------- |
550
+ | `vanilla` | A bus, one event and a `debounce` plus `latest` handler, with plain DOM code | You use no framework, or want to see the bus alone |
551
+ | `react` | The same handler with `useEmitter` and `useEventState`, and `usePolicy` for a debounced save in a component | You build a React app |
552
+ | `vanilla-ts` | The `vanilla` app in TypeScript | You want the compiler to check events and handlers |
553
+ | `react-ts` | The `react` app in TypeScript | You build a React app in TypeScript |
554
+
555
+ ```bash
556
+ npm create nanoeffects@latest my-app -- --template react
557
+ ```
558
+
559
+ The TypeScript templates are the same apps with the same files, written in `.ts` and `.tsx`. Events are made with `createAction<Payload>`, so handlers and `useEventState` get their types from the events, with no annotation in the component. `npm run build` type-checks the project (`tsc --noEmit`) before bundling it.
560
+
561
+ ```bash
562
+ npm create nanoeffects@latest my-app -- --template react-ts
563
+ ```
564
+
565
+ ## Read the files
566
+
567
+ Every template splits the code the way [the guide](guide.md) does: one job per file. In the TypeScript templates the files end in `.ts` (and `.tsx` for components).
568
+
569
+ | File | Job |
570
+ | ---- | --- |
571
+ | `src/bus.js` | Creates the bus. It imports no events. |
572
+ | `src/search.js` | Names the events with `createAction`. It imports no bus. |
573
+ | `src/search-handlers.js` | Handles the events with `debounce` and `latest`. It knows the bus and the events. |
574
+ | `src/api.js` | A fake server. Replace it with your own requests. |
575
+
576
+ Every call of nanoeffects in these files has a comment that says what it does. To make it your own, replace `src/api.js`, then rename the events and the handler.
577
+
578
+ ## Next steps
579
+
580
+ - [Policies](policies.md): `leading`, `throttle` and `queue` next to `latest`.
581
+ - [Build a sign-up form in React](react.md): the same ideas in a bigger React example.
582
+ - [Package entries](package.md): which import holds what.
583
+
503
584
  ---
504
585
  Source: docs/motivation.md
505
586
 
@@ -3041,6 +3122,68 @@ const checkName = latest(async (name, ctx) => {
3041
3122
 
3042
3123
  In the component, add `bus.emit("form/closed")` to the cleanup function of the effect. This is optional: skip it when finishing a request after unmount does no harm, as for a cheap check.
3043
3124
 
3125
+ ## Show the last value of an event as state
3126
+
3127
+ **Problem.** A component often needs the latest result of an event, such as the found items of a search. The hand-written form is easy to get slightly wrong:
3128
+
3129
+ <!-- docs-check: skip - the hand-written form that this section replaces -->
3130
+ ```jsx
3131
+ useEffect(() => bus.on(searchLoaded, setFound), []);
3132
+ ```
3133
+
3134
+ It needs a `useState` next to it, the empty `[]` hides the dependency on the event, and it breaks if the payload is a function, because React calls `setFound(payload)` as an updater.
3135
+
3136
+ **When.** The value you show comes from the bus, not from the component. For the opposite direction, a value of the component that the bus must hear, use `useEmitter`.
3137
+
3138
+ `useEventState(event, initial)` has the signature of `useState`: it returns `[state, setState]`. `initial` is the state before the first event and may be a function, as in `useState`. After that, every emit of `event` sets the state to its payload. The hook unsubscribes when the component unmounts.
3139
+
3140
+ ```js
3141
+ // bus.js
3142
+ import { createBus } from "nanoeffects";
3143
+ import { reactPlugin } from "nanoeffects/react";
3144
+
3145
+ // createBus — the event bus, created once outside React
3146
+ // reactPlugin — adds the hooks bus.useEmitter and bus.useEventState
3147
+ export const bus = createBus().use(reactPlugin());
3148
+ export const { useEventState } = bus; // a free hook: components import it without the bus
3149
+ ```
3150
+
3151
+ ```js
3152
+ // search.js
3153
+ import { createAction } from "nanoeffects/actions";
3154
+
3155
+ // createAction — the event name; the handlers emit it with the found titles
3156
+ export const searchLoaded = createAction("search/loaded");
3157
+ ```
3158
+
3159
+ ```jsx
3160
+ // Found.jsx
3161
+ import { useEventState } from "./bus";
3162
+ import { searchLoaded } from "./search";
3163
+
3164
+ export function Found() {
3165
+ // useEventState — like useState([]), but searchLoaded sets the value
3166
+ const [found, setFound] = useEventState(searchLoaded, []);
3167
+
3168
+ return (
3169
+ <>
3170
+ <ul>
3171
+ {found.map((title) => (
3172
+ <li key={title}>{title}</li>
3173
+ ))}
3174
+ </ul>
3175
+ <button onClick={() => setFound([])}>Clear</button>
3176
+ </>
3177
+ );
3178
+ }
3179
+ ```
3180
+
3181
+ `setFound` is the setter of React. It changes the state of this component only and emits nothing.
3182
+
3183
+ In TypeScript, the state type comes from the event: from `createAction<string[]>("search/loaded")` or from the event map of the bus. When the event has no payload type, as in the example above, it comes from `initial`: `["a"]` is `string[]`, and an empty `[]` is `unknown[]`. Give the event a type, or pass a typed `initial`, when you need more.
3184
+
3185
+ The bus keeps no values, so the hook cannot replay an event. An event emitted before the component mounts is not seen: if that matters, read the current value into `initial`.
3186
+
3044
3187
  ## Keep a debounced handler between renders
3045
3188
 
3046
3189
  **Problem.** A component runs again on every render. A policy made in its body, such as `debounce(300, search)`, is made again each time and loses its timer. A policy made once keeps the callback of the first render, which reads old props and state.
package/llms.txt CHANGED
@@ -9,12 +9,13 @@ Rules that generated code often gets wrong:
9
9
  - A handler gets the payload and a task context: `(payload, ctx)`. The context has `signal`, `emit`, `on`, `run`. Pass `ctx.signal` to `fetch`.
10
10
  - Effects take `ctx` as the first argument: `retry(ctx, fn, options)`, `timeout(ctx, ms, fn)`, `take(ctx, "event")`.
11
11
  - Policies wrap a handler: `bus.on("search", latest(async (query, ctx) => { ... }))`.
12
- - Each part is imported from its own path: `nanoeffects` (`createBus`), `nanoeffects/policies`, `nanoeffects/effects`, `nanoeffects/actions`, `nanoeffects/redux`, `nanoeffects/observable`, `nanoeffects/react` (`reactPlugin`, adds `bus.useEmitter`; `usePolicy`; needs React), `nanoeffects/trace` (`createTracer`, loggers, `tracePlugin`), `nanoeffects/dev`.
12
+ - Each part is imported from its own path: `nanoeffects` (`createBus`), `nanoeffects/policies`, `nanoeffects/effects`, `nanoeffects/actions`, `nanoeffects/redux`, `nanoeffects/observable`, `nanoeffects/react` (`reactPlugin`, adds `bus.useEmitter` and `bus.useEventState`; `usePolicy`; needs React), `nanoeffects/trace` (`createTracer`, loggers, `tracePlugin`), `nanoeffects/dev`.
13
13
  - `bus.emit(command(payload))` runs the command's handler and returns its result. Plain events return nothing.
14
14
 
15
15
  ## Start here
16
16
 
17
17
  - [Guide](docs/guide.md): bus, handler context, policies, effects, commands and modules, step by step
18
+ - [Start a project from a template](docs/starter.md): `npm create nanoeffects` makes a Vite project (`vanilla` or `react`) with a bus, `debounce` and `latest`
18
19
  - [Why nanoeffects exists](docs/motivation.md): no dependencies and no global state for SDKs and Module Federation, and why not Redux, Effector or RxJS
19
20
  - [Package entries](docs/package.md): which import holds what
20
21
  - [Plugins](docs/plugins.md): every plugin of a bus, what `bus.use` returns, and the order to use them in
@@ -33,7 +34,7 @@ Rules that generated code often gets wrong:
33
34
  - [Actions, commands, jobs and modules](docs/actions.md): `createAction`, `createCommand`, `createJob`, `createModule`
34
35
  - [Connect Redux, Zustand, MobX, or Pinia](docs/stores.md): the Redux middleware and callbacks of other stores
35
36
  - [Use nanoeffects with React Query and SWR](docs/query-libraries.md): call a command from `queryFn` or the SWR fetcher; `signalPlugin` passes the abort of the library to the task
36
- - [Build a sign-up form in React](docs/react.md): a form with a username check, a submit that cannot fire twice, and retry; `usePolicy` keeps a debounced handler between renders
37
+ - [Build a sign-up form in React](docs/react.md): a form with a username check, a submit that cannot fire twice, and retry; `useEventState` shows an event as state, `usePolicy` keeps a debounced handler between renders
37
38
  - [Context fields](docs/context.md): put a store or client on every task
38
39
  - [Scopes](docs/scope.md): fields for one command, action, job, module or `emit`, and a way to replace them in a test
39
40
  - [Use the bus from a class](docs/oop.md): `Emitter`, `extendBus`, `@WithBus`, `@On`, `@Once`, and `dispose()` for the subscriptions of an instance
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nanoeffects",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "Cancel stale async work and fix race conditions in event handlers: a typed event bus where every handler is a task with an AbortSignal, plus latest, debounce, throttle, queue, retry, timeout and poll. About 1.4 kB gzip.",
5
5
  "keywords": [
6
6
  "event-bus",
@@ -138,16 +138,17 @@
138
138
  "scripts": {
139
139
  "build": "tsdown && node scripts/notrace-types.mjs && node scripts/llms-full.mjs",
140
140
  "prepublishOnly": "pnpm build",
141
- "check": "biome check ./src ./examples ./integration ./scripts",
142
- "format": "npx @biomejs/biome check --write --unsafe ./src ./examples ./integration ./scripts",
141
+ "check": "biome check ./src ./examples ./integration ./scripts ./packages",
142
+ "format": "npx @biomejs/biome check --write --unsafe ./src ./examples ./integration ./scripts ./packages",
143
143
  "tsgo": "tsc --noEmit",
144
144
  "test": "vitest run",
145
145
  "test:watch": "vitest",
146
146
  "prepare": "lefthook install",
147
- "lint:package": "publint && attw --pack . --profile esm-only",
147
+ "lint:package": "publint && attw --pack . --profile esm-only && publint packages/create-nanoeffects",
148
148
  "size": "size-limit",
149
- "verify": "pnpm tsgo && pnpm check && pnpm docs:check && pnpm llms:check && pnpm test && pnpm build && pnpm test:integration && pnpm smoke && pnpm lint:package && pnpm size",
149
+ "verify": "pnpm tsgo && pnpm check && pnpm docs:check && pnpm llms:check && pnpm templates:check && pnpm test && pnpm build && pnpm test:integration && pnpm smoke && pnpm lint:package && pnpm size",
150
150
  "test:integration": "vitest run --config vitest.integration.config.ts",
151
+ "templates:check": "node scripts/templates-check.mjs",
151
152
  "docs:check": "node scripts/docs-check.mjs",
152
153
  "llms:check": "node scripts/llms-full.mjs --check",
153
154
  "smoke": "node scripts/smoke.mjs && node scripts/smoke.mjs --notrace",
@@ -163,6 +164,7 @@
163
164
  "@size-limit/file": "^14.2.0",
164
165
  "@types/react": "^19.3.0",
165
166
  "@types/react-dom": "^19.3.0",
167
+ "@vitejs/plugin-react": "^6.1.2",
166
168
  "archunit": "^2.5.4",
167
169
  "happy-dom": "^21.0.4",
168
170
  "jotai": "^3.0.1",
@@ -249,7 +251,7 @@
249
251
  "ignore": [
250
252
  "react"
251
253
  ],
252
- "limit": "1.18 kB"
254
+ "limit": "1.24 kB"
253
255
  },
254
256
  {
255
257
  "name": "dev",