nanoeffects 0.5.0 → 0.7.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/dist/{action-creator-DOWcbXpy.d.ts → action-creator-Bd4TBZgl.d.ts} +115 -33
- package/dist/actions.d.ts +18 -4
- package/dist/actions.js +1 -1
- package/dist/{notrace/context-plugin-B0sLWOxR.d.ts → context-plugin-BVNKqkIx.d.ts} +1 -1
- package/dist/dev.d.ts +2 -2
- package/dist/effects.d.ts +3 -202
- package/dist/index.d.ts +2 -2
- package/dist/{module-DCJl-qfi.d.ts → module-DZ5ngUrM.d.ts} +19 -4
- package/dist/notrace/{action-creator-DOWcbXpy.d.ts → action-creator-D-y_-xRS.d.ts} +115 -33
- package/dist/notrace/actions.d.ts +45 -5
- package/dist/notrace/actions.js +1 -1
- package/dist/{context-plugin-B0sLWOxR.d.ts → notrace/context-plugin-DDfsaErA.d.ts} +1 -1
- package/dist/notrace/effects.d.ts +3 -202
- package/dist/notrace/index.d.ts +2 -2
- package/dist/notrace/plugin-CDAngHwg.d.ts +207 -0
- package/dist/notrace/policies.d.ts +1 -1
- package/dist/notrace/redux.d.ts +1 -1
- package/dist/observable.d.ts +1 -1
- package/dist/plugin-DQNPBcxx.d.ts +207 -0
- package/dist/policies.d.ts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/redux.d.ts +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-BJ0fzV1p.d.ts +15 -0
- package/docs/README.md +1 -0
- package/docs/actions.md +24 -0
- package/docs/context.md +2 -0
- package/docs/effects.md +3 -2
- package/docs/guide.md +22 -0
- package/docs/package.md +3 -0
- package/docs/plugins.md +108 -0
- package/docs/scope.md +228 -0
- package/llms-full.txt +398 -2
- package/llms.txt +2 -0
- package/package.json +11 -2
package/docs/plugins.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Choose the plugins of a bus
|
|
2
|
+
|
|
3
|
+
Optional. A plugin adds one thing to a bus: fields on `ctx`, a method, a registry of handlers, diagnostics. Read this page when you want to know which plugins exist, what each one returns, and in what order to use them. Skip it for a small bus: `createBus()` alone is enough.
|
|
4
|
+
|
|
5
|
+
## Use a plugin
|
|
6
|
+
|
|
7
|
+
A plugin is a function of the bus. `bus.use(plugin)` calls it with the bus and returns whatever the plugin returns.
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import { createBus } from "nanoeffects";
|
|
11
|
+
|
|
12
|
+
// your own plugin: a function of the bus that returns what you want back
|
|
13
|
+
const countPlugin = (bus) => {
|
|
14
|
+
let count = 0;
|
|
15
|
+
const unbind = bus.on("*", () => {
|
|
16
|
+
count += 1;
|
|
17
|
+
});
|
|
18
|
+
return { count: () => count, stop: unbind };
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const bus = createBus();
|
|
22
|
+
// use — calls countPlugin(bus) and returns its result
|
|
23
|
+
const counter = bus.use(countPlugin);
|
|
24
|
+
|
|
25
|
+
bus.emit("greet", "Ada");
|
|
26
|
+
console.log(counter.count()); // 1
|
|
27
|
+
counter.stop();
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The bus keeps no list of plugins. A plugin that was used cannot be taken out by the bus; it returns its own `stop` or `disconnect` when it needs one.
|
|
31
|
+
|
|
32
|
+
## See every plugin
|
|
33
|
+
|
|
34
|
+
| Plugin | Import | It adds | `bus.use` returns | Page |
|
|
35
|
+
| ------ | ------ | ------- | ----------------- | ---- |
|
|
36
|
+
| `contextPlugin` | `nanoeffects` | Fields on `ctx` of every task | The same bus, typed with the fields | [Context fields](context.md) |
|
|
37
|
+
| `effectsPlugin` | `nanoeffects/effects` | `race`, `all`, `take`, `delay` and your own effects on `ctx`, bound to the task | The same bus, typed with the effects | [Effects](effects.md#put-selected-effects-on-ctx) |
|
|
38
|
+
| `scopePlugin` | `nanoeffects/scope` | Fields and effects for one command, action, job, module or `emit` | The same bus | [Scopes](scope.md) |
|
|
39
|
+
| `commandsPlugin` | `nanoeffects/actions` | The registry of command handlers, `bus.commands` | The registry `{ run, handle }` | [Actions](actions.md#describe-events-commands-and-jobs) |
|
|
40
|
+
| `modulePlugin` | `nanoeffects/actions` | Modules that mount and unmount as one unit | The app `{ mount, unmount, isMounted, dispose, commands, createModule }` | [Modules](actions.md#mount-domains-with-modules) |
|
|
41
|
+
| `emitterPlugin` | `nanoeffects/actions` | `bus.emitter(creator)`, a callback that emits an action | The same bus, with `emitter` | [Stores](stores.md#emit-from-a-state-manager) |
|
|
42
|
+
| `reactPlugin` | `nanoeffects/react` | `bus.useEmitter(event, value)`, a React hook | The same bus, with `useEmitter` | [React and stores](stores.md#react-sharing-a-value-across-components) |
|
|
43
|
+
| `devPlugin` | `nanoeffects/dev` | Warnings about listeners that pile up, and messages about modules | A function that disconnects it | [Hot reload](hmr.md#report-leaks-with-devplugin) |
|
|
44
|
+
|
|
45
|
+
"The same bus" means you can keep writing `bus` and ignore the result. Keep the result of `contextPlugin` and `effectsPlugin` in TypeScript, though: the type of the bus with its new fields is the type they return (`const bus = createBus().use(contextPlugin({ store }))`).
|
|
46
|
+
|
|
47
|
+
## Put them together
|
|
48
|
+
|
|
49
|
+
One bus with every plugin that does not need React. The order is explained in the next section.
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
import { contextPlugin, createBus } from "nanoeffects";
|
|
53
|
+
import {
|
|
54
|
+
commandsPlugin,
|
|
55
|
+
emitterPlugin,
|
|
56
|
+
modulePlugin,
|
|
57
|
+
} from "nanoeffects/actions";
|
|
58
|
+
import { devPlugin } from "nanoeffects/dev";
|
|
59
|
+
import { all, effectsPlugin, race } from "nanoeffects/effects";
|
|
60
|
+
import { scopePlugin } from "nanoeffects/scope";
|
|
61
|
+
|
|
62
|
+
const bus = createBus()
|
|
63
|
+
.use(contextPlugin({ store })) // ctx.store on every task
|
|
64
|
+
.use(effectsPlugin({ race, all })) // ctx.race and ctx.all, bound to the task
|
|
65
|
+
.use(scopePlugin()) // scopes for commands, actions, jobs, modules and emit
|
|
66
|
+
.use(emitterPlugin()); // bus.emitter(creator)
|
|
67
|
+
|
|
68
|
+
// commandsPlugin — the registry of command handlers; keep it to bind handlers later
|
|
69
|
+
const commands = bus.use(commandsPlugin());
|
|
70
|
+
|
|
71
|
+
// modulePlugin — mounts userModule now; keep app to mount and unmount later
|
|
72
|
+
const app = bus.use(modulePlugin([userModule]));
|
|
73
|
+
|
|
74
|
+
// devPlugin — reports leaks and modules while you develop; keep disconnect to remove it
|
|
75
|
+
const disconnect = bus.use(devPlugin({ app }));
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Follow the order
|
|
79
|
+
|
|
80
|
+
Most plugins do not care about each other. These pairs do:
|
|
81
|
+
|
|
82
|
+
| Rule | Why |
|
|
83
|
+
| ---- | --- |
|
|
84
|
+
| `scopePlugin` after `effectsPlugin` | `effectsPlugin` assigns its effects on `ctx`, so one used later would replace an effect with the same name that a scope put there. |
|
|
85
|
+
| `scopePlugin` before `modulePlugin` | `modulePlugin` mounts the modules when it is used, and a module with a scope needs the plugin by then. |
|
|
86
|
+
| `commandsPlugin` before `modulePlugin`, if you bind handlers yourself | `modulePlugin` uses the registry that exists. Without one it makes its own, which is `app.commands`. |
|
|
87
|
+
| `modulePlugin` before `devPlugin({ app })` | `devPlugin` reports the modules of the `app` that `modulePlugin` returned. |
|
|
88
|
+
| `emitterPlugin` and `reactPlugin` anywhere | They only call `bus.emit`, which is read when you call them. |
|
|
89
|
+
|
|
90
|
+
Two more things the checker enforces. A name that is already a context field of the bus is a type error when `contextPlugin` or `effectsPlugin` adds it again. `contextPlugin` does not replace a field that is on `ctx` already, so a scope wins over it in any order.
|
|
91
|
+
|
|
92
|
+
## Choose by need
|
|
93
|
+
|
|
94
|
+
- **A store, a client or a logger on every handler.** `contextPlugin`. Or `createBus({ context })` when you do not need the types from the value.
|
|
95
|
+
- **`race`, `all` or your own effect without passing `ctx`.** `effectsPlugin`.
|
|
96
|
+
- **Fields for one command, or a fake for one test call.** `scopePlugin`.
|
|
97
|
+
- **A request with a result, or handlers that a test can replace.** `commandsPlugin`. Add `modulePlugin` when the domain needs a mount and an unmount.
|
|
98
|
+
- **A callback for a state manager, such as `$name.listen(...)`.** `emitterPlugin`.
|
|
99
|
+
- **Tell the bus when a value of a React component changes.** `reactPlugin`.
|
|
100
|
+
- **Find listeners that were never unbound, or see modules reload.** `devPlugin`. In a production build `nanoeffects/dev` is empty, so the call can stay.
|
|
101
|
+
|
|
102
|
+
## See what is not a plugin
|
|
103
|
+
|
|
104
|
+
These connect the bus to something, but are not used with `bus.use`:
|
|
105
|
+
|
|
106
|
+
- `createEffectsMiddleware(bus, { filter })` from `nanoeffects/redux` is a Redux middleware for a bus that exists. See [Stores](stores.md#wire-redux-toolkit-to-a-bus).
|
|
107
|
+
- `createBus({ trace })` takes a tracer from `nanoeffects/trace`. See [Tracing](tracing.md).
|
|
108
|
+
- `createObserve(bus)` from `nanoeffects/observable` turns an event into an Observable. See [Observable](observable.md).
|
package/docs/scope.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# Pin context fields to a command, an action, a module or a call
|
|
2
|
+
|
|
3
|
+
Optional, for domain logic. Use it when one command needs its own client, one module its own logger, or one test its own fake, and a field on the whole bus ([context fields](context.md)) is too wide.
|
|
4
|
+
|
|
5
|
+
A **scope** is a plain object. Every key goes on `ctx` as it is (`cache`, `api`, `logger`), except one: `$effects`. Its functions take `{ signal }` as the first parameter, like `retry(ctx, fn)`, and go on `ctx` bound to the task, so you call `ctx.fetchJson(url)` instead of `fetchJson(ctx, url)`.
|
|
6
|
+
|
|
7
|
+
You give a scope to the thing that uses it. A call can replace it. Nothing else changes: handlers still get `(payload, ctx)`.
|
|
8
|
+
|
|
9
|
+
## Turn scopes on
|
|
10
|
+
|
|
11
|
+
Scopes are a plugin, so a bus that does not use them pays nothing.
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import { createBus } from "nanoeffects";
|
|
15
|
+
import { scopePlugin } from "nanoeffects/scope";
|
|
16
|
+
|
|
17
|
+
// scopePlugin — makes the scopes below work on this bus; returns the same bus
|
|
18
|
+
const bus = createBus().use(scopePlugin());
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- Without `scopePlugin` a scope is **ignored**: no error and no warning. If a field is missing from `ctx`, check this first.
|
|
22
|
+
- Use it **after** `effectsPlugin` (an effect it adds later would be assigned over an effect of a scope with the same name) and **before** `modulePlugin` (it mounts the modules when it is used). `contextPlugin` and `createBus({ context })` can come in any order: a scope wins over them.
|
|
23
|
+
|
|
24
|
+
## Give a command its own fields
|
|
25
|
+
|
|
26
|
+
```js
|
|
27
|
+
import { createBus } from "nanoeffects";
|
|
28
|
+
import { createCommand } from "nanoeffects/actions";
|
|
29
|
+
import { scopePlugin } from "nanoeffects/scope";
|
|
30
|
+
|
|
31
|
+
const bus = createBus().use(scopePlugin());
|
|
32
|
+
|
|
33
|
+
// createCommand — third argument is the scope of this command
|
|
34
|
+
const getUser = createCommand(
|
|
35
|
+
"user/get",
|
|
36
|
+
(id, { api, cache }) => {
|
|
37
|
+
// api and cache are on ctx for this command only
|
|
38
|
+
return cache.get(id) ?? api.getUser(id);
|
|
39
|
+
},
|
|
40
|
+
{ api, cache: new Map() },
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const user = await bus.emit(getUser("1")); // the handler has api and cache
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The listeners of `"user/get"` get the same fields when the command is emitted, because the fields belong to the call.
|
|
47
|
+
|
|
48
|
+
## Bind an effect to the task
|
|
49
|
+
|
|
50
|
+
An effect is a function with `{ signal }` as its first parameter. In `$effects` it is bound to the task, so it stops with the task.
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
import { createBus } from "nanoeffects";
|
|
54
|
+
import { createCommand } from "nanoeffects/actions";
|
|
55
|
+
import { delay, race } from "nanoeffects/effects";
|
|
56
|
+
import { scopePlugin } from "nanoeffects/scope";
|
|
57
|
+
|
|
58
|
+
const bus = createBus().use(scopePlugin());
|
|
59
|
+
|
|
60
|
+
// your own effect: first parameter is the task (only its signal is used)
|
|
61
|
+
const fetchJson = (ctx, url) =>
|
|
62
|
+
fetch(url, { signal: ctx.signal }).then((response) => response.json());
|
|
63
|
+
|
|
64
|
+
const loadFeed = createCommand(
|
|
65
|
+
"feed/load",
|
|
66
|
+
(url, { fetchJson, race, delay }) =>
|
|
67
|
+
// race, delay, fetchJson — bound to this task: no ctx argument
|
|
68
|
+
race({
|
|
69
|
+
feed: () => fetchJson(url),
|
|
70
|
+
timeout: () => delay(5000),
|
|
71
|
+
}),
|
|
72
|
+
// $effects — built-in and your own functions share one object
|
|
73
|
+
{ $effects: { fetchJson, race, delay } },
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const result = await bus.emit(loadFeed("/feed"));
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- Put in `$effects` only functions that take `ctx` first. A plain function (a formatter, a logger) belongs in `context`.
|
|
80
|
+
- A function under any other key is copied as it is; a function in `$effects` is bound.
|
|
81
|
+
- `$effects` is the one name with a meaning. A field called `effects` is an ordinary field: `{ effects: { retries: 3 } }` puts that object on `ctx`, and nothing in it is bound.
|
|
82
|
+
|
|
83
|
+
## Replace a field for one call
|
|
84
|
+
|
|
85
|
+
The second argument of `emit` is a scope for the tasks of **that** call. It is the usual way to give a test a fake.
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
import { createBus } from "nanoeffects";
|
|
89
|
+
import { createCommand } from "nanoeffects/actions";
|
|
90
|
+
import { scopePlugin } from "nanoeffects/scope";
|
|
91
|
+
|
|
92
|
+
const bus = createBus().use(scopePlugin());
|
|
93
|
+
const getUser = createCommand("user/get", (id, { api }) => api.getUser(id), {
|
|
94
|
+
api,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// the real api for this call
|
|
98
|
+
await bus.emit(getUser("1"));
|
|
99
|
+
|
|
100
|
+
// the second argument — a fake api, for this call only
|
|
101
|
+
const fakeApi = { getUser: async (id) => ({ id, name: "test" }) };
|
|
102
|
+
await bus.emit(getUser("1"), { api: fakeApi });
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Rules for the second argument:
|
|
106
|
+
|
|
107
|
+
- It works with an action or a command call (`emit(getUser("1"), scope)`, `ctx.emit(loaded(user), scope)`).
|
|
108
|
+
- It does not work with the string form: in `emit("user/get", payload)` the second argument is the payload.
|
|
109
|
+
- It can replace `$effects` too: `{ $effects: { delay: instantDelay } }`.
|
|
110
|
+
- Fields of the library (`signal`, `emit`, …) in it are ignored.
|
|
111
|
+
|
|
112
|
+
## Give an action, a job or a module its own fields
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
import { createAction, createJob, createModule } from "nanoeffects/actions";
|
|
116
|
+
|
|
117
|
+
// createAction — the listeners of this action's calls get the fields
|
|
118
|
+
const userLoaded = createAction("user/loaded", { store });
|
|
119
|
+
|
|
120
|
+
// createJob — the scope is the fourth argument, after the outcomes;
|
|
121
|
+
// the handler and the listeners of the outcome get its fields
|
|
122
|
+
const loadUser = createJob(
|
|
123
|
+
"user/load",
|
|
124
|
+
fetchUser,
|
|
125
|
+
{ success: userLoaded, fail: "user/failed" },
|
|
126
|
+
{ logger },
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// createModule — third argument; everything the module owns gets the fields:
|
|
130
|
+
// m.on listeners, m.handle handlers, m.spawn tasks
|
|
131
|
+
const auditModule = createModule(
|
|
132
|
+
"audit",
|
|
133
|
+
(m) => {
|
|
134
|
+
m.on(userLoaded, (user, { logger }) => logger.info(user.id));
|
|
135
|
+
},
|
|
136
|
+
{ logger },
|
|
137
|
+
);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
If the outcome action (`success` or `fail`) has a scope of its own, its listeners see the job's fields with the action's own fields over them; the handler of the job sees only the job's.
|
|
141
|
+
|
|
142
|
+
A module scope does not leak: a listener registered outside the module does not see it.
|
|
143
|
+
|
|
144
|
+
## Which value wins
|
|
145
|
+
|
|
146
|
+
When several places set the same field, the strongest one wins:
|
|
147
|
+
|
|
148
|
+
| Strength | Where it is set |
|
|
149
|
+
| -------- | --------------- |
|
|
150
|
+
| 1 (strongest) | the second argument of `emit` |
|
|
151
|
+
| 2 | the scope of the action, command or job |
|
|
152
|
+
| 3 | the scope of the module that owns the handler |
|
|
153
|
+
| 4 (weakest) | the bus: `createBus({ context })`, `contextPlugin`, `effectsPlugin` |
|
|
154
|
+
|
|
155
|
+
The fields of the library (`signal`, `abort`, `emit`, `on`, `off`, `run`, `type`, `payload`) are never replaced. A child task (`ctx.run`, an attempt of `retry`) has the fields of its parent.
|
|
156
|
+
|
|
157
|
+
## Keep a generic effect generic
|
|
158
|
+
|
|
159
|
+
In `$effects` a function loses its first parameter, and TypeScript cannot keep a generic through that step: `<T>(items: T[]) => T` becomes `(items: unknown[]) => unknown`. JavaScript code is not affected. If you want the exact type, use one of two ways.
|
|
160
|
+
|
|
161
|
+
`effect(fn)`: mark the function where you declare it, then put it in the scope like any other value. No annotation, so a JavaScript editor shows the exact hint too.
|
|
162
|
+
|
|
163
|
+
```js
|
|
164
|
+
import { createCommand } from "nanoeffects/actions";
|
|
165
|
+
import { effect } from "nanoeffects/scope";
|
|
166
|
+
|
|
167
|
+
// effect — marks the function, so a scope binds it to the task; the editor keeps its generic
|
|
168
|
+
const first = effect((ctx, items) => items[0]);
|
|
169
|
+
|
|
170
|
+
const pick = createCommand("pick", (items, { first }) => first(items), {
|
|
171
|
+
first, // a plain key of the scope, not an entry of `$effects`
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Call `effect` where you declare the function, as above, and not inline in the scope: TypeScript does not infer the scope from a generic call written there.
|
|
176
|
+
|
|
177
|
+
`Effect<F>` (TypeScript): declare how the effect looks **after** it is bound, and keep it in `$effects`.
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
import { createCommand } from "nanoeffects/actions";
|
|
181
|
+
import type { Effect } from "nanoeffects/effects";
|
|
182
|
+
|
|
183
|
+
// First — checker-only: how a handler calls the effect (no ctx)
|
|
184
|
+
type First = <T>(items: T[]) => T;
|
|
185
|
+
|
|
186
|
+
// Effect<First> — the function takes the task first and is typed as First once bound
|
|
187
|
+
const first: Effect<First> = (ctx, items) => items[0];
|
|
188
|
+
|
|
189
|
+
const pick = createCommand(
|
|
190
|
+
"pick",
|
|
191
|
+
(id: string, { first }) => first([id]), // string
|
|
192
|
+
{ $effects: { first } },
|
|
193
|
+
);
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
`Effect<F>` and `effect` change what the checker sees. At run time the function is the same.
|
|
197
|
+
|
|
198
|
+
## Type the fields (optional)
|
|
199
|
+
|
|
200
|
+
A handler of a command, job or module with a scope sees the fields of the scope in `ctx`, typed from the values you gave. You write no `WithContext`.
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import { createCommand } from "nanoeffects/actions";
|
|
204
|
+
|
|
205
|
+
type User = { id: string; name: string };
|
|
206
|
+
|
|
207
|
+
const getUser = createCommand(
|
|
208
|
+
"user/get",
|
|
209
|
+
async (id: string, ctx) => {
|
|
210
|
+
ctx.cache; // Map<string, User>, from the scope below
|
|
211
|
+
return ctx.cache.get(id);
|
|
212
|
+
},
|
|
213
|
+
{ cache: new Map<string, User>() },
|
|
214
|
+
);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
- A field of a scope replaces a field of the same name from the bus in the type too.
|
|
218
|
+
- An override in `emit` must match the type of the field it replaces: `emit(getUser("1"), { cache: 1 })` is an error.
|
|
219
|
+
- A field that no scope and no bus declares has no type to check against, so `emit(call, { unknown: 1 })` is an error in TypeScript.
|
|
220
|
+
- `bus.on(creator, fn)` for an action with a scope types `ctx` with its fields.
|
|
221
|
+
|
|
222
|
+
## Limits
|
|
223
|
+
|
|
224
|
+
- No plugin, no scope: see [Turn scopes on](#turn-scopes-on).
|
|
225
|
+
- The fields of the bus are not in the handler of a command made with `createCommand(…, scope)` unless you type `ctx` yourself; the fields of the scope are.
|
|
226
|
+
- The string form of `emit` takes no scope.
|
|
227
|
+
- `effect(fn)` works only as a value of a scope, not as an entry of `$effects`.
|
|
228
|
+
- A scope on a command, action, job or module is typed without the events of a bus, so `take` in its `$effects` takes any event name.
|