nanoeffects 0.26.0 → 0.28.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 +85 -30
- package/dist/{action-creator-Bpa8BUS9.d.ts → action-creator-CcAEvUnw.d.ts} +90 -23
- package/dist/actions.d.ts +54 -21
- package/dist/actions.js +1 -1
- package/dist/bus-DlpEluzd.js +1 -0
- package/dist/{context-types-BKIH5a8s.d.ts → context-types-hCKgU8sC.d.ts} +1 -1
- package/dist/context.d.ts +2 -2
- package/dist/dev.d.ts +2 -2
- package/dist/dev.js +1 -1
- package/dist/effects.d.ts +2 -2
- package/dist/event-DVcW0mME.js +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/match.d.ts +1 -1
- package/dist/{module-B6rg5NIb.d.ts → module-C_oEXw1n.d.ts} +40 -57
- package/dist/notrace/{action-creator-D2CotugT.d.ts → action-creator-DxOvVo8I.d.ts} +90 -23
- package/dist/notrace/actions.d.ts +114 -98
- package/dist/notrace/actions.js +1 -1
- package/dist/notrace/bus-DmKn4SDD.js +1 -0
- package/dist/notrace/effects.d.ts +2 -2
- package/dist/notrace/index.d.ts +2 -2
- package/dist/notrace/index.js +1 -1
- package/dist/notrace/{plugin-Dz6hq8Jg.d.ts → plugin-lJjjd-SN.d.ts} +1 -1
- package/dist/notrace/policies.d.ts +1 -1
- package/dist/notrace/policies.js +1 -1
- package/dist/notrace/redux.d.ts +1 -1
- package/dist/observable.d.ts +6 -6
- package/dist/oop.d.ts +2 -2
- package/dist/oop.js +1 -1
- package/dist/{plugin-D-j1DarG.d.ts → plugin-CuvIgjnX.d.ts} +2 -2
- package/dist/policies.d.ts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/redux.d.ts +1 -1
- package/dist/scope.d.ts +2 -2
- package/dist/signal.d.ts +1 -1
- package/dist/signal.js +1 -1
- package/dist/svelte.d.ts +1 -1
- package/dist/trace.d.ts +1 -1
- package/dist/{types-Bkuhnkq6.d.ts → types-FV8fuvI9.d.ts} +2 -2
- package/dist/vue.d.ts +1 -1
- package/dist/{with-policy-BeH8wCkX.d.ts → with-policy-3bhFLEXs.d.ts} +1 -1
- package/docs/README.md +5 -4
- package/docs/actions.md +94 -53
- package/docs/chat-example.md +8 -8
- package/docs/chat-widget.md +135 -0
- package/docs/context.md +1 -1
- package/docs/errors.md +1 -1
- package/docs/guide.md +26 -26
- package/docs/hmr.md +1 -1
- package/docs/motivation.md +10 -4
- package/docs/oop.md +1 -1
- package/docs/package.md +2 -2
- package/docs/plugins.md +23 -23
- package/docs/query-libraries.md +14 -14
- package/docs/scope.md +22 -22
- package/docs/server.md +12 -12
- package/docs/stability.md +2 -2
- package/docs/starter.md +3 -3
- package/docs/tracing.md +5 -5
- package/docs/typescript.md +16 -16
- package/llms-full.txt +380 -194
- package/llms.txt +3 -2
- package/package.json +5 -4
- package/dist/bus-hHp-T7JV.js +0 -1
- package/dist/event-0RIBGYqS.js +0 -1
- package/dist/notrace/bus-BdyvumpX.js +0 -1
- package/dist/notrace/promise-CSLpzwSR.js +0 -1
package/docs/server.md
CHANGED
|
@@ -16,9 +16,9 @@ The fix is one `AbortController` for each unit of work that the client owns, and
|
|
|
16
16
|
| A WebSocket | the socket connects | the socket closes |
|
|
17
17
|
| A queued job | the job is accepted | the server shuts down |
|
|
18
18
|
|
|
19
|
-
## Make the bus and the
|
|
19
|
+
## Make the bus and the action
|
|
20
20
|
|
|
21
|
-
The bus and the
|
|
21
|
+
The bus and the action live in files that know nothing about HTTP, so the same action can run in a route, a test or a worker.
|
|
22
22
|
|
|
23
23
|
```js
|
|
24
24
|
// bus.js
|
|
@@ -30,15 +30,15 @@ import { signalPlugin } from "nanoeffects/signal";
|
|
|
30
30
|
export const bus = createBus().use(signalPlugin());
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
The
|
|
33
|
+
The action loads a user. It tries the request up to three times, and gives up on each try after five seconds. `ctx.signal` is the signal of the task: it aborts when the controller of the request aborts.
|
|
34
34
|
|
|
35
35
|
```js
|
|
36
36
|
// user.js
|
|
37
|
-
import {
|
|
37
|
+
import { createAction } from "nanoeffects/actions";
|
|
38
38
|
import { retry, timeout } from "nanoeffects/effects";
|
|
39
39
|
|
|
40
|
-
//
|
|
41
|
-
export const getUser =
|
|
40
|
+
// createAction — a command with a handler: emit returns what the handler returns
|
|
41
|
+
export const getUser = createAction("user/get", async (id, ctx) => {
|
|
42
42
|
// one try: the request, with the signal of timeout so it can stop it
|
|
43
43
|
const load = ({ signal }) => api.getUser(id, { signal });
|
|
44
44
|
// timeout — fail the try after 5 s
|
|
@@ -91,7 +91,7 @@ app.get("/users/:id", async (request, reply) => {
|
|
|
91
91
|
});
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
After an abort the
|
|
94
|
+
After an abort the action rejects with the abort reason. The `catch` above ignores it when the connection is gone, because there is nobody to answer.
|
|
95
95
|
|
|
96
96
|
## Stop the server
|
|
97
97
|
|
|
@@ -123,17 +123,17 @@ The route above fills `inFlight` and empties it in the `close` listener. The [ap
|
|
|
123
123
|
|
|
124
124
|
A socket lasts minutes and carries several tasks (a subscription, a heartbeat), so the controller belongs to the socket and every `emit` for it gets the same signal. Closing the socket stops all of them.
|
|
125
125
|
|
|
126
|
-
First the
|
|
126
|
+
First the action. It sends a price now and then every second, until its task is aborted.
|
|
127
127
|
|
|
128
128
|
```js
|
|
129
129
|
// prices.js
|
|
130
|
-
import {
|
|
130
|
+
import { createAction } from "nanoeffects/actions";
|
|
131
131
|
import { poll } from "nanoeffects/effects";
|
|
132
132
|
import { latest } from "nanoeffects/policies";
|
|
133
133
|
|
|
134
|
-
//
|
|
134
|
+
// createAction — a command with a handler
|
|
135
135
|
// latest — a new subscription stops the old one; key — but only the old one of the same socket
|
|
136
|
-
export const watchPrices =
|
|
136
|
+
export const watchPrices = createAction(
|
|
137
137
|
"prices/watch",
|
|
138
138
|
latest(
|
|
139
139
|
async (call, ctx) => {
|
|
@@ -147,7 +147,7 @@ export const watchPrices = createCommand(
|
|
|
147
147
|
);
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
-
Without the `key`, one policy serves the whole
|
|
150
|
+
Without the `key`, one policy serves the whole action, and a new subscription on one socket would stop the subscription on another.
|
|
151
151
|
|
|
152
152
|
Then the route. Each socket gets an id and a controller.
|
|
153
153
|
|
package/docs/stability.md
CHANGED
|
@@ -11,7 +11,7 @@ A stable entry follows the rules below. An unstable entry can change in any mino
|
|
|
11
11
|
| `nanoeffects` | Stable | `createBus` and the types of a bus |
|
|
12
12
|
| `nanoeffects/effects` | Stable | `retry`, `timeout`, `poll`, `take`, `delay`, `all`, `allSettled`, `race`, `backoff`, `cancellable`, `effectsPlugin` |
|
|
13
13
|
| `nanoeffects/policies` | Stable | `latest`, `leading`, `debounce`, `throttle`, `queue`, `withPolicy` |
|
|
14
|
-
| `nanoeffects/actions` | Stable | `createAction`, `
|
|
14
|
+
| `nanoeffects/actions` | Stable | `createAction`, `createJob`, `createModule` and their plugins |
|
|
15
15
|
| `nanoeffects/trace` | Stable: the contract | `Tracer`, `Trace`, `TraceEvent`, `TraceLogger`, `Phase`, `EffectName` and `tracePlugin` |
|
|
16
16
|
| `nanoeffects/trace` | Unstable: the tools | `createTracer`, `consoleLogger`, `performanceLogger`, `createTraceRecorder`, `reduxDevtools` and their option types |
|
|
17
17
|
| `nanoeffects/scope` | Unstable | `scopePlugin`, `effect` |
|
|
@@ -54,7 +54,7 @@ A deprecated export keeps working and is removed only in a major release. `RaceS
|
|
|
54
54
|
| ---- | --------- |
|
|
55
55
|
| Node.js, the library | 18, 20, 22 and 24 (a smoke test runs the built package on each) |
|
|
56
56
|
| Node.js, the dev tools and the trace in a browser | 22 and newer |
|
|
57
|
-
| TypeScript | The
|
|
57
|
+
| TypeScript | 5.9 and 7. The built declaration files are type-checked with TypeScript 5.9 in CI, and the source with TypeScript 7. Older versions are not tested. |
|
|
58
58
|
| React | 18 or newer (`nanoeffects/react`) |
|
|
59
59
|
| Vue | 3.3 or newer (`nanoeffects/vue`) |
|
|
60
60
|
| Svelte | 5 or newer (`nanoeffects/svelte`) |
|
package/docs/starter.md
CHANGED
|
@@ -46,7 +46,7 @@ Pass `--template` to skip the question. With npm, put `--` before the option.
|
|
|
46
46
|
| `react` | The same handler with `useEmitter` and `useEventState`, and `usePolicy` for a debounced save in a component | You build a React app |
|
|
47
47
|
| `vanilla-ts` | The `vanilla` app in TypeScript | You want the compiler to check events and handlers |
|
|
48
48
|
| `react-ts` | The `react` app in TypeScript | You build a React app in TypeScript |
|
|
49
|
-
| `domains-ts` |
|
|
49
|
+
| `domains-ts` | Actions with handlers, a job and modules, with the logic split into domain folders and React as a thin layer | You build an app with more than one feature and want the logic off the UI |
|
|
50
50
|
| `vue-ts` | The `react-ts` app in Vue 3, with `watchEmitter` and `useEventRef` from `nanoeffects/vue` | You build a Vue 3 app |
|
|
51
51
|
| `svelte-ts` | The `react-ts` app in Svelte 5, with `eventStore` from `nanoeffects/svelte` | You build a Svelte app |
|
|
52
52
|
|
|
@@ -94,13 +94,13 @@ npm create nanoeffects@latest my-app -- --template domains-ts
|
|
|
94
94
|
| ---- | --- |
|
|
95
95
|
| `src/bus.ts` | Creates the bus. It imports no events and no domain. |
|
|
96
96
|
| `src/app.ts` | Mounts the domains with `modulePlugin`. |
|
|
97
|
-
| `src/domains/catalog/` | Searching products: `catalog.ts` (events and a
|
|
97
|
+
| `src/domains/catalog/` | Searching products: `catalog.ts` (events and an action with a handler), `catalog-handlers.ts` (`debounce` and `latest`), `catalog-module.ts`. |
|
|
98
98
|
| `src/domains/checkout/` | Placing an order: `checkout.ts` (a job), `checkout-handlers.ts` (`leading` and `retry`), `checkout-store.ts` (state written from the job's outcomes), `checkout-module.ts`. |
|
|
99
99
|
| `src/ui/` | The components. None of them calls the fake server in `src/api.ts`. |
|
|
100
100
|
|
|
101
101
|
For a bigger example, a chat in which parts react to each other, see [Build a chat with entities, features, modules and a small server](chat-example.md). It has more layers than `domains-ts`: when domains must react to each other, it keeps them from importing each other by splitting them into entities and features.
|
|
102
102
|
|
|
103
|
-
The
|
|
103
|
+
The actions, jobs and modules themselves are explained in [Actions, jobs and modules](actions.md). The template keeps found products in a Context provider with one `useEventState`, and the order state in a store of its own: `useEventState` is [not a store](react.md#show-the-last-value-of-an-event-as-state).
|
|
104
104
|
|
|
105
105
|
## Read the files
|
|
106
106
|
|
package/docs/tracing.md
CHANGED
|
@@ -132,13 +132,13 @@ Every event of a task below another one has `rootId`, the id of the root of the
|
|
|
132
132
|
|
|
133
133
|
**Problem.** A flow such as a checkout has several roots (a click, a timer, a store) and many branches. Parent ids give a tree, but you want to filter the log by the flow.
|
|
134
134
|
|
|
135
|
-
**When.** One business flow runs through several
|
|
135
|
+
**When.** One business flow runs through several actions, and you read the log by flow.
|
|
136
136
|
|
|
137
|
-
Give the flow a `traceGroupId` in the scope of the
|
|
137
|
+
Give the flow a `traceGroupId` in the scope of the action or the job that starts it. Every task that it starts through `ctx.emit` or `ctx.run` has the same `traceGroupId`, and so does every trace event of those tasks. A task that declares its own `traceGroupId` uses it for itself and for what it starts.
|
|
138
138
|
|
|
139
139
|
```js
|
|
140
140
|
import { createBus } from "nanoeffects";
|
|
141
|
-
import {
|
|
141
|
+
import { createAction } from "nanoeffects/actions";
|
|
142
142
|
import { consoleLogger, createTracer, tracePlugin } from "nanoeffects/trace";
|
|
143
143
|
|
|
144
144
|
// createTracer: sends each trace event to the logger; consoleLogger: prints one line each
|
|
@@ -149,7 +149,7 @@ const bus = createBus({ trace })
|
|
|
149
149
|
.use(tracePlugin());
|
|
150
150
|
|
|
151
151
|
// the scope names the flow; traceGroupId is for the tracer, not a field of ctx
|
|
152
|
-
const pay =
|
|
152
|
+
const pay = createAction("checkout/pay", async () => {}, {
|
|
153
153
|
traceGroupId: "checkout",
|
|
154
154
|
});
|
|
155
155
|
```
|
|
@@ -202,7 +202,7 @@ The names work wherever an event or a listener is made:
|
|
|
202
202
|
| A listener, `bus.on` and `bus.once` | `{ traceId }` as the third argument |
|
|
203
203
|
| A listener of a module or of an instance of `extendBus` | the same third argument of its `on` and `once` |
|
|
204
204
|
| A method with `@On` or `@Once` | `{ traceId }` in the decorator |
|
|
205
|
-
|
|
|
205
|
+
| An action or a job | `traceGroupId` in its scope |
|
|
206
206
|
|
|
207
207
|
## Turn the names off
|
|
208
208
|
|
package/docs/typescript.md
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# Use nanoeffects in a TypeScript app with many files
|
|
2
2
|
|
|
3
|
-
Read this page if your app is in TypeScript and the
|
|
3
|
+
Read this page if your app is in TypeScript and the handlers and modules live in different files. It shows how to write an action with a handler so that TypeScript infers its payload and result, and what to add when the handlers need the fields of your bus (`ctx.store`, the effects of `effectsPlugin`). For one file or for JavaScript you do not need it.
|
|
4
4
|
|
|
5
|
-
## Write
|
|
5
|
+
## Write an action with its handler
|
|
6
6
|
|
|
7
|
-
Give `
|
|
7
|
+
Give `createAction` the handler and write nothing else. The payload comes from the annotated parameter, the result from the body. You do not write a generic.
|
|
8
8
|
|
|
9
9
|
<!-- docs-check: skip - a file of examples/typescript, type-checked there -->
|
|
10
10
|
```ts
|
|
11
11
|
// user.ts
|
|
12
|
-
import {
|
|
12
|
+
import { createAction, createModule } from "nanoeffects/actions";
|
|
13
13
|
|
|
14
14
|
export type User = { id: string; name: string };
|
|
15
15
|
|
|
16
16
|
// payload: string, from `id: string`; result: Promise<User>, from the body
|
|
17
|
-
export const getUser =
|
|
17
|
+
export const getUser = createAction(
|
|
18
18
|
"user/get",
|
|
19
19
|
async (id: string, { signal }) => {
|
|
20
20
|
const response = await fetch(`/api/users/${id}`, { signal });
|
|
@@ -56,11 +56,11 @@ A wrong payload at the call (`getUser(1)`) is an error. Annotate the parameters,
|
|
|
56
56
|
|
|
57
57
|
## Give `ctx` the fields of your bus
|
|
58
58
|
|
|
59
|
-
Types of a bus live on the bus. `contextPlugin({ store })` and `effectsPlugin({ race })` make `ctx.store` and `ctx.race` known to the handlers of **that bus**.
|
|
59
|
+
Types of a bus live on the bus. `contextPlugin({ store })` and `effectsPlugin({ race })` make `ctx.store` and `ctx.race` known to the handlers of **that bus**. An action with a handler made in another file does not see the bus, so its `ctx` has no `store`:
|
|
60
60
|
|
|
61
61
|
<!-- docs-check: skip - shows the error on purpose -->
|
|
62
62
|
```ts
|
|
63
|
-
export const getUser =
|
|
63
|
+
export const getUser = createAction("user/get", (id: string, ctx) => {
|
|
64
64
|
ctx.store; // error: Property 'store' does not exist
|
|
65
65
|
});
|
|
66
66
|
```
|
|
@@ -93,7 +93,7 @@ export type AppContext = Context<AppEvents>; // ctx of a handler of this bus: ha
|
|
|
93
93
|
| Type | What it is | Why you name it |
|
|
94
94
|
| ---- | ---------- | --------------- |
|
|
95
95
|
| `EventsOf<typeof bus>` | the event map of the bus, with the context fields of the plugins (and of `WithContext`, if you use it) | the source of `AppContext` |
|
|
96
|
-
| `Context<AppEvents>` | the `ctx` that a handler of the bus gets | annotate the `ctx` of a handler given to `
|
|
96
|
+
| `Context<AppEvents>` | the `ctx` that a handler of the bus gets | annotate the `ctx` of a handler given to `createAction` |
|
|
97
97
|
|
|
98
98
|
These types change nothing at runtime. They only tell the checker what the bus already does.
|
|
99
99
|
|
|
@@ -102,10 +102,10 @@ Annotate `ctx` with `AppContext` where the handler reads a field of the bus, and
|
|
|
102
102
|
<!-- docs-check: skip - a file of examples/typescript-context, type-checked there -->
|
|
103
103
|
```ts
|
|
104
104
|
// user.ts
|
|
105
|
-
import {
|
|
105
|
+
import { createAction } from "nanoeffects/actions";
|
|
106
106
|
import { type AppContext, createModule } from "./bus";
|
|
107
107
|
|
|
108
|
-
export const getUser =
|
|
108
|
+
export const getUser = createAction(
|
|
109
109
|
"user/get",
|
|
110
110
|
(id: string, { store, signal }: AppContext) => store.fetchUser(id, { signal }),
|
|
111
111
|
);
|
|
@@ -145,14 +145,14 @@ Pass `ctx` (or `{ signal }`) when you call it. The effects of `nanoeffects/effec
|
|
|
145
145
|
|
|
146
146
|
## Keep the contract apart from the code
|
|
147
147
|
|
|
148
|
-
When the file with the
|
|
148
|
+
When the file with the action must not import the code that implements it, make the action without a handler. Here you write the payload and the result once, as a `Call`, and the handler goes to a module, where it needs no annotation. `Call` is a promise of yours: nothing checks it against the handler, so keep the handler typed from it in the module:
|
|
149
149
|
|
|
150
150
|
<!-- docs-check: skip - fragment of the file above -->
|
|
151
151
|
```ts
|
|
152
|
-
export const getUser =
|
|
152
|
+
export const getUser = createAction<Call<string, Promise<User>>>("user/get"); // the contract
|
|
153
153
|
|
|
154
154
|
createModule("user", (m) => {
|
|
155
|
-
// payload and result come from the
|
|
155
|
+
// payload and result come from the Call, ctx from the app: no annotation
|
|
156
156
|
m.handle(getUser, (id, { store, signal }) => store.fetchUser(id, { signal }));
|
|
157
157
|
});
|
|
158
158
|
```
|
|
@@ -161,8 +161,8 @@ A wrong payload, a wrong field of `ctx` and a wrong result are errors.
|
|
|
161
161
|
|
|
162
162
|
## Check your setup
|
|
163
163
|
|
|
164
|
-
- **A field is missing on `ctx`.** The `ctx` of a handler given to `
|
|
165
|
-
- **`EventsOf<typeof bus>` is `never`.** `bus` is not a bus. `bus.use(
|
|
164
|
+
- **A field is missing on `ctx`.** The `ctx` of a handler given to `createAction` is not annotated: add `: AppContext`. A handler written inline in `m.handle` or `m.on` of a module made with the `createModule` of `bus.ts` does not need it. The default `ctx` has no fields of your bus.
|
|
165
|
+
- **`EventsOf<typeof bus>` is `never`.** `bus` is not a bus. `bus.use(actionsPlugin())` and `bus.use(modulePlugin(...))` return the registry or the app, not the bus: keep the bus in its own constant (`bus`, then `app = bus.use(modulePlugin())`).
|
|
166
166
|
- **`effectsPlugin` is not in the type.** `use` must come before you read `typeof bus`. Chain `createBus(...).use(effectsPlugin(...))` in one binding.
|
|
167
167
|
- **A policy loses the types.** It does not: `latest(handler)` keeps the payload, the result and the context of `handler`.
|
|
168
|
-
- **Do not pass the context as a generic of `
|
|
168
|
+
- **Do not pass the context as a generic of `createAction`.** It is the fourth generic of the handler form, after the payload, the result and the type string, so you would have to write all four. Annotate `ctx` with `AppContext`, or put the handler in a module made with the `createModule` of `bus.ts`.
|