nanoeffects 0.4.0 → 0.6.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 +4 -1
- package/dist/{action-creator-D48fp13T.d.ts → action-creator-c292nK7v.d.ts} +130 -34
- package/dist/actions.d.ts +54 -5
- package/dist/actions.js +1 -1
- package/dist/{bus-Cho463Sq.js → bus-BAs9x7IQ.js} +1 -1
- package/dist/{context-DlA80GOA.js → context-CK0ZomWt.js} +1 -1
- package/dist/context-plugin-1pYmSQEG.d.ts +34 -0
- package/dist/dev.d.ts +2 -2
- package/dist/effects.d.ts +27 -129
- package/dist/effects.js +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -1
- package/dist/{module-DVX8aYlq.d.ts → module-BlF2KSgy.d.ts} +34 -5
- package/dist/notrace/{action-creator-D48fp13T.d.ts → action-creator-DKG_sIia.d.ts} +130 -34
- package/dist/notrace/actions.d.ts +96 -7
- package/dist/notrace/actions.js +1 -1
- package/dist/notrace/{bus-B12SvxLv.js → bus-Bdk0YyFQ.js} +1 -1
- package/dist/notrace/{context-yAWj0BFZ.js → context-CctVmHLC.js} +1 -1
- package/dist/notrace/context-plugin-Dt1IgVo8.d.ts +34 -0
- package/dist/notrace/{delay-Cgvn16oY.js → delay-B_W1tnkS.js} +1 -1
- package/dist/notrace/effects.d.ts +27 -129
- package/dist/notrace/effects.js +1 -1
- package/dist/notrace/index.d.ts +3 -2
- package/dist/notrace/index.js +1 -1
- package/dist/notrace/plugin-B0AXKusc.d.ts +207 -0
- package/dist/notrace/policies.d.ts +1 -1
- package/dist/notrace/policies.js +1 -1
- package/dist/notrace/redux.d.ts +18 -20
- package/dist/notrace/redux.js +1 -1
- package/dist/observable.d.ts +1 -1
- package/dist/plugin-CCYlC0XS.d.ts +207 -0
- package/dist/policies.d.ts +1 -1
- package/dist/policies.js +1 -1
- package/dist/react.d.ts +50 -0
- package/dist/react.js +1 -0
- package/dist/redux.d.ts +18 -20
- package/dist/redux.js +1 -1
- package/dist/scope-key-B7l7mUS5.js +1 -0
- package/dist/scope.d.ts +58 -0
- package/dist/scope.js +1 -0
- package/dist/trace.d.ts +1 -1
- package/dist/types-mLaAx-EZ.d.ts +15 -0
- package/docs/README.md +7 -1
- package/docs/actions.md +25 -1
- package/docs/context.md +26 -2
- package/docs/effects.md +83 -5
- package/docs/guide.md +24 -0
- package/docs/migrating-from-effector.md +293 -0
- package/docs/migrating-from-rxjs.md +127 -0
- package/docs/migrating-from-saga.md +93 -0
- package/docs/observable.md +1 -0
- package/docs/package.md +13 -2
- package/docs/policies.md +1 -0
- package/docs/react.md +157 -0
- package/docs/scope.md +226 -0
- package/docs/stores.md +429 -40
- package/docs/typescript.md +153 -0
- package/llms-full.txt +2925 -0
- package/llms.txt +49 -0
- package/package.json +44 -6
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Use nanoeffects in a TypeScript app with many files
|
|
2
|
+
|
|
3
|
+
Read this page if your app is in TypeScript and the commands and modules live in different files. It shows how to write a command 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
|
+
|
|
5
|
+
## Write a command with its handler
|
|
6
|
+
|
|
7
|
+
Give `createCommand` 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
|
+
|
|
9
|
+
<!-- docs-check: skip - a file of examples/typescript, type-checked there -->
|
|
10
|
+
```ts
|
|
11
|
+
// user.ts
|
|
12
|
+
import { createCommand, createModule } from "nanoeffects/actions";
|
|
13
|
+
|
|
14
|
+
export type User = { id: string; name: string };
|
|
15
|
+
|
|
16
|
+
// payload: string, from `id: string`; result: Promise<User>, from the body
|
|
17
|
+
export const getUser = createCommand(
|
|
18
|
+
"user/get",
|
|
19
|
+
async (id: string, { signal }) => {
|
|
20
|
+
const response = await fetch(`/api/users/${id}`, { signal });
|
|
21
|
+
const user: User = await response.json();
|
|
22
|
+
return user;
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// createModule: m.use binds the handler the command carries, while the module is mounted
|
|
27
|
+
export const userModule = createModule("user", (m) => {
|
|
28
|
+
m.use(getUser);
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Mount the module where you create the bus. The result is typed from the handler:
|
|
33
|
+
|
|
34
|
+
<!-- docs-check: skip - a file of examples/typescript, type-checked there -->
|
|
35
|
+
```ts
|
|
36
|
+
// main.ts
|
|
37
|
+
import { createBus } from "nanoeffects";
|
|
38
|
+
import { modulePlugin } from "nanoeffects/actions";
|
|
39
|
+
import { getUser, userModule } from "./user";
|
|
40
|
+
|
|
41
|
+
const bus = createBus();
|
|
42
|
+
const app = bus.use(modulePlugin([userModule])); // mounts the modules
|
|
43
|
+
|
|
44
|
+
const user = bus.emit(getUser("1")); // Promise<User>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
What you annotate, and why:
|
|
48
|
+
|
|
49
|
+
| Part | Annotated? | Why |
|
|
50
|
+
| ---- | ---------- | --- |
|
|
51
|
+
| payload (`id: string`) | yes | a function parameter has no type to infer from |
|
|
52
|
+
| `ctx` | no, for `signal` and the fields every task has | `signal`, `emit`, `on` and `run` are on every `ctx` |
|
|
53
|
+
| result | no | inferred from the body |
|
|
54
|
+
|
|
55
|
+
A wrong payload at the call (`getUser(1)`) is an error. Annotate the parameters, not the variable: a type on the whole variable, such as `const handler: Task<string, Events> = …`, fixes the type of the function, and the result becomes `unknown`.
|
|
56
|
+
|
|
57
|
+
## Give `ctx` the fields of your bus
|
|
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**. A command made in another file does not see the bus, so its `ctx` has no `store`:
|
|
60
|
+
|
|
61
|
+
<!-- docs-check: skip - shows the error on purpose -->
|
|
62
|
+
```ts
|
|
63
|
+
export const getUser = createCommand("user/get", (id: string, ctx) => {
|
|
64
|
+
ctx.store; // error: Property 'store' does not exist
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
At runtime the bus still gives the task its fields. Only the checker does not know it. Name the types of the bus once, in the file that creates it. `contextPlugin` takes the type of each field from the value you pass, so the event map holds only events. `EventsOf` reads the event map from the bus, with the fields that the plugins added. The code of this section is in [`examples/typescript-context`](../examples/typescript-context).
|
|
69
|
+
|
|
70
|
+
<!-- docs-check: skip - a file of examples/typescript-context, type-checked there -->
|
|
71
|
+
```ts
|
|
72
|
+
// bus.ts
|
|
73
|
+
import { type Context, contextPlugin, createBus, type EventsOf } from "nanoeffects";
|
|
74
|
+
import { modulePlugin } from "nanoeffects/actions";
|
|
75
|
+
import { effectsPlugin, race } from "nanoeffects/effects";
|
|
76
|
+
|
|
77
|
+
// Events: event name → payload; nothing about ctx is written here
|
|
78
|
+
type Events = { "user/loaded": User };
|
|
79
|
+
|
|
80
|
+
export const bus = createBus<Events>()
|
|
81
|
+
.use(contextPlugin({ store })) // ctx.store, typed from the value `store`
|
|
82
|
+
.use(effectsPlugin({ race })); // ctx.race; each plugin returns the same bus, typed with its fields
|
|
83
|
+
|
|
84
|
+
// the app of the bus: mounts modules and creates them with the types of the bus
|
|
85
|
+
export const app = bus.use(modulePlugin()); // no modules yet: main.ts mounts them
|
|
86
|
+
export const { createModule } = app; // createModule with the types of the bus
|
|
87
|
+
|
|
88
|
+
export type AppEvents = EventsOf<typeof bus>; // the event map and all fields of ctx
|
|
89
|
+
export type AppContext = Context<AppEvents>; // ctx of a handler of this bus: has store and race
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
| Type | What it is | Why you name it |
|
|
93
|
+
| ---- | ---------- | --------------- |
|
|
94
|
+
| `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` |
|
|
95
|
+
| `Context<AppEvents>` | the `ctx` that a handler of the bus gets | annotate the `ctx` of a handler given to `createCommand` |
|
|
96
|
+
|
|
97
|
+
These types change nothing at runtime. They only tell the checker what the bus already does.
|
|
98
|
+
|
|
99
|
+
Annotate `ctx` with `AppContext` where the handler reads a field of the bus, and create the module with the `createModule` that `bus.ts` exports, so its `m.on` handlers see the fields too:
|
|
100
|
+
|
|
101
|
+
<!-- docs-check: skip - a file of examples/typescript-context, type-checked there -->
|
|
102
|
+
```ts
|
|
103
|
+
// user.ts
|
|
104
|
+
import { createCommand } from "nanoeffects/actions";
|
|
105
|
+
import { type AppContext, createModule } from "./bus";
|
|
106
|
+
|
|
107
|
+
export const getUser = createCommand(
|
|
108
|
+
"user/get",
|
|
109
|
+
(id: string, { store, signal }: AppContext) => store.fetchUser(id, { signal }),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// createModule from bus.ts: handlers of the module see the context fields of the bus
|
|
113
|
+
export const userModule = createModule("user", (m) => {
|
|
114
|
+
m.use(getUser);
|
|
115
|
+
m.on("user/loaded", (user, { store }) => store.remember(user));
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
<!-- docs-check: skip - a file of examples/typescript-context, type-checked there -->
|
|
120
|
+
```ts
|
|
121
|
+
// main.ts: the modules are mounted here, because the module files need `app`
|
|
122
|
+
import { app, bus } from "./bus";
|
|
123
|
+
import { getUser, userModule } from "./user";
|
|
124
|
+
|
|
125
|
+
const unmount = app.mount(userModule); // starts the module; unmount() undoes it
|
|
126
|
+
const user = bus.emit(getUser("1")); // Promise<User>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`createModule` of the app only creates the module: it does not mount it, and it keeps no link to the bus. That is why the app in `bus.ts` starts without modules. `createModule` from `nanoeffects/actions` stays for a module made without a bus: there you write the event map yourself, `createModule<AppEvents>("user", …)`.
|
|
130
|
+
|
|
131
|
+
## Keep the contract apart from the code
|
|
132
|
+
|
|
133
|
+
When the file with the command must not import the code that implements it, make the command without a handler. Here you write the payload and the result once, as generics, and the handler goes to a module, where it needs no annotation:
|
|
134
|
+
|
|
135
|
+
<!-- docs-check: skip - fragment of the file above -->
|
|
136
|
+
```ts
|
|
137
|
+
export const getUser = createCommand<string, Promise<User>>("user/get"); // the contract
|
|
138
|
+
|
|
139
|
+
createModule("user", (m) => {
|
|
140
|
+
// payload and result come from the command, ctx from the app: no annotation
|
|
141
|
+
m.handle(getUser, (id, { store, signal }) => store.fetchUser(id, { signal }));
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
A wrong payload, a wrong field of `ctx` and a wrong result are errors.
|
|
146
|
+
|
|
147
|
+
## Check your setup
|
|
148
|
+
|
|
149
|
+
- **A field is missing on `ctx`.** The `ctx` of a handler given to `createCommand` 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.
|
|
150
|
+
- **`EventsOf<typeof bus>` is `never`.** `bus` is not a bus. `bus.use(commandsPlugin())` 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())`).
|
|
151
|
+
- **`effectsPlugin` is not in the type.** `use` must come before you read `typeof bus`. Chain `createBus(...).use(effectsPlugin(...))` in one binding.
|
|
152
|
+
- **A policy loses the types.** It does not: `latest(handler)` keeps the payload, the result and the context of `handler`.
|
|
153
|
+
- **Do not pass the context as a generic of `createCommand`.** It is the fourth generic, 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`.
|