nanoeffects 0.10.0 → 0.12.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-BrZF6hXc.d.ts → action-creator-CQlFgQm4.d.ts} +29 -13
- package/dist/actions.d.ts +12 -5
- package/dist/actions.js +1 -1
- package/dist/bus-Cdw5ajKF.js +1 -0
- package/dist/context-CJGW6OWj.js +1 -0
- package/dist/{context-plugin--HkNN1ha.d.ts → context-plugin-Cphk8jfq.d.ts} +1 -1
- package/dist/{delay-CIHnOh_L.js → delay-BnBdGPh7.js} +1 -1
- package/dist/dev.d.ts +2 -2
- package/dist/effects.d.ts +2 -2
- package/dist/effects.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/match.d.ts +1 -1
- package/dist/match.js +1 -1
- package/dist/{module-DqKegXHt.d.ts → module-woawvR28.d.ts} +3 -3
- package/dist/notrace/{action-creator-DHAEgwuh.d.ts → action-creator-Ls3Gy3jk.d.ts} +28 -12
- package/dist/notrace/actions.d.ts +12 -5
- package/dist/notrace/actions.js +1 -1
- package/dist/notrace/bus-G2AdGObY.js +1 -0
- package/dist/notrace/{context-plugin-BapQkGwQ.d.ts → context-plugin-BQa-wtbj.d.ts} +1 -1
- 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/origin-key-BbWN3gmN.js +1 -0
- package/dist/notrace/origin-key-CxK0SkeG.d.ts +10 -0
- package/dist/notrace/{plugin-Cxadz6uU.d.ts → plugin-Cw45_-Bo.d.ts} +2 -2
- package/dist/notrace/policies.d.ts +1 -1
- package/dist/notrace/redux.d.ts +4 -3
- package/dist/notrace/redux.js +1 -1
- package/dist/observable.d.ts +1 -1
- package/dist/oop.d.ts +13 -7
- package/dist/oop.js +1 -1
- package/dist/origin-key-BbWN3gmN.js +1 -0
- package/dist/origin-key-CxK0SkeG.d.ts +10 -0
- package/dist/{plugin-DkXCvsmB.d.ts → plugin-BrTsPg2Y.d.ts} +2 -2
- package/dist/policies.d.ts +2 -2
- package/dist/policies.js +1 -1
- package/dist/react.d.ts +15 -8
- package/dist/react.js +1 -1
- package/dist/redux.d.ts +4 -3
- package/dist/redux.js +1 -1
- package/dist/scope.d.ts +2 -2
- package/dist/scope.js +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/signal.js +1 -1
- package/dist/trace.d.ts +65 -14
- package/dist/trace.js +1 -1
- package/dist/{types-KavbvHix.d.ts → types-bzocBVYc.d.ts} +2 -2
- package/dist/{with-policy-DBV9dh2F.js → with-policy-CW50Edgk.js} +1 -1
- package/dist/{with-policy-CqUS-VWW.d.ts → with-policy-_zP3OKGy.d.ts} +1 -1
- package/docs/oop.md +2 -0
- package/docs/package.md +1 -1
- package/docs/plugins.md +16 -1
- package/docs/scope.md +1 -1
- package/docs/stores.md +5 -1
- package/docs/tracing.md +184 -8
- package/llms-full.txt +210 -13
- package/llms.txt +1 -1
- package/package.json +3 -3
- package/dist/bus-CkTHUAvq.js +0 -1
- package/dist/context-COgyNlrC.js +0 -1
- package/dist/notrace/bus-BmCTo8BG.js +0 -1
- package/dist/onAbort-BgMfspK4.js +0 -1
package/docs/tracing.md
CHANGED
|
@@ -21,12 +21,14 @@ Each `TraceEvent` has:
|
|
|
21
21
|
| Field | Meaning |
|
|
22
22
|
| ----- | ------- |
|
|
23
23
|
| `id` | Unique task id within the bus |
|
|
24
|
-
| `
|
|
24
|
+
| `parentId` | Id of the task that started this one (absent for handler tasks) |
|
|
25
|
+
| `rootId` | Id of the root task of the chain; absent on the root itself, and it is the same through a listener that reports nothing |
|
|
25
26
|
| `type` | Event type the task works on |
|
|
26
|
-
| `effect` | `latest`, `debounce`, `throttle`, `leading`, `run`, `all`, `race`,
|
|
27
|
+
| `effect` | `latest`, `debounce`, `throttle`, `leading`, `run`, `all`, `race`, …; `emit` for the emit event of `tracePlugin` |
|
|
27
28
|
| `phase` | `start`, `done`, `error`, `cancel`, or `drop` |
|
|
28
29
|
| `payload` | Event payload |
|
|
29
30
|
| `error` | Thrown value, only for `error` |
|
|
31
|
+
| `traceGroupId` | The flow of the task, when it or a task above has one |
|
|
30
32
|
|
|
31
33
|
Phases:
|
|
32
34
|
|
|
@@ -51,6 +53,150 @@ bus.on("load", async (id, ctx) => {
|
|
|
51
53
|
});
|
|
52
54
|
```
|
|
53
55
|
|
|
56
|
+
## Show every emit
|
|
57
|
+
|
|
58
|
+
**When.** You want the log to say what was emitted, also when nothing listens or the listener is a plain function. Without it the trace has only the tasks that report: a policy (`latest`, ...) or a `ctx.run`.
|
|
59
|
+
|
|
60
|
+
`tracePlugin` reports one event for each emit, with `effect: "emit"` and `phase: "done"`, before the handlers run. `type` is the event name and `payload` is what was emitted. A `ctx.emit` has the task that emits as its `parentId`.
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
// bus.js
|
|
64
|
+
import { createBus } from "nanoeffects";
|
|
65
|
+
import { consoleLogger, createTracer, tracePlugin } from "nanoeffects/trace";
|
|
66
|
+
|
|
67
|
+
export const bus = createBus({ trace: createTracer(consoleLogger()) })
|
|
68
|
+
.use(tracePlugin()); // tracePlugin: reports each emit as an event
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
import { bus } from "./bus";
|
|
73
|
+
|
|
74
|
+
bus.emit("search", "nano"); // logs: search · emit done
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The event changes nothing else: the other tasks keep their `parentId`, `rootId` and `traceGroupId`. Without the plugin the trace has no `emit` events.
|
|
78
|
+
|
|
79
|
+
- **What counts as an emit.** Every `bus.emit`, also the ones a library makes for you: a job emits its `pending` and `success` (or `fail`, `cancel`), so one call of a job is several `emit` events.
|
|
80
|
+
- **Cost.** One small task per emit, about 60 ns on a bus that already has a tracer (a bus with a tracer and no plugin: about 250 ns more than a bus without one). `sample` and `filter` of `createTracer` cut it in the log, not in the work.
|
|
81
|
+
|
|
82
|
+
## Show which subscription began a chain
|
|
83
|
+
|
|
84
|
+
**Problem.** `useEmitter` and `bus.emitter` put an event on the bus from outside any task, such as a render or the callback of a store. In the trace that event has no sign of where it came from. With many such subscriptions it is hard to tell which one started a chain of commands and retries.
|
|
85
|
+
|
|
86
|
+
**When.** The bus has a tracer and the project has several `useEmitter` or `bus.emitter` calls. The names come from `tracePlugin`: use it on the bus, or the `traceId` is ignored.
|
|
87
|
+
|
|
88
|
+
Give each one a `traceId`. When the bus has a tracer, the emit is made from a root task with that `traceId` as its `type` and `run` as its effect. What the event starts is below it. Without a tracer nothing changes.
|
|
89
|
+
|
|
90
|
+
```js
|
|
91
|
+
import { createBus } from "nanoeffects";
|
|
92
|
+
import { createAction, emitterPlugin } from "nanoeffects/actions";
|
|
93
|
+
import { consoleLogger, createTracer, tracePlugin } from "nanoeffects/trace";
|
|
94
|
+
|
|
95
|
+
// createTracer: reports every task phase to the console
|
|
96
|
+
const bus = createBus({ trace: createTracer(consoleLogger()) })
|
|
97
|
+
.use(tracePlugin()) // tracePlugin: makes the root task that traceId names
|
|
98
|
+
.use(emitterPlugin());
|
|
99
|
+
|
|
100
|
+
const cartChanged = createAction("cart/changed");
|
|
101
|
+
|
|
102
|
+
// emitter: a callback for a store; traceId names it in the trace
|
|
103
|
+
const emitCartChanged = bus.emitter(cartChanged, { traceId: "cartStore" });
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
In a component, the same option goes in the options of the hook: `useEmitter(nameChanged, name, { traceId: "Profile" })`. In the console:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
[nanoeffects] Profile · run start
|
|
110
|
+
[nanoeffects] user/save · command start
|
|
111
|
+
[nanoeffects] user/save · command done
|
|
112
|
+
[nanoeffects] Profile · run done
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
A `traceId` is a string you choose and keep stable: the name of the component or of the store. Without one the root has the type of the event.
|
|
116
|
+
|
|
117
|
+
Two things to know:
|
|
118
|
+
|
|
119
|
+
- **`sample` sees the `traceId`.** The root of an emitter is a task like any other, so a `sample` function gets `{ type, payload }` with the `traceId` as the `type` and the emitted action as the `payload`. A function that matches on the type of the event, such as `event.type === "search"`, does not pick a root that has another `traceId`; match the `traceId`, or the `payload.type`.
|
|
120
|
+
- **Inside a handler, emit with `ctx.emit`.** `ctx.emit` keeps the chain: what it starts is below the handler. A plain `bus.emit` inside a handler starts a new root with no parent and no `rootId`, so the trace loses the link.
|
|
121
|
+
|
|
122
|
+
Every event of a task below another one has `rootId`, the id of the root of the chain, also when a listener, which reports nothing, stands in between. `parentId` is the task directly above, which can be such a listener.
|
|
123
|
+
|
|
124
|
+
## Follow one flow with a traceGroupId
|
|
125
|
+
|
|
126
|
+
**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.
|
|
127
|
+
|
|
128
|
+
**When.** One business flow runs through several commands, and you read the log by flow.
|
|
129
|
+
|
|
130
|
+
Give the flow a `traceGroupId` in the scope of the command, the job or the action 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.
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
import { createBus } from "nanoeffects";
|
|
134
|
+
import { createCommand } from "nanoeffects/actions";
|
|
135
|
+
import { consoleLogger, createTracer, tracePlugin } from "nanoeffects/trace";
|
|
136
|
+
|
|
137
|
+
// tracePlugin: reads traceGroupId from the scope of a call
|
|
138
|
+
const bus = createBus({ trace: createTracer(consoleLogger()) }).use(tracePlugin());
|
|
139
|
+
|
|
140
|
+
// the scope names the flow; traceGroupId is for the tracer, not a field of ctx
|
|
141
|
+
const pay = createCommand("checkout/pay", async () => {}, {
|
|
142
|
+
traceGroupId: "checkout",
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
[nanoeffects] [checkout] checkout/pay · command start
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The emitters take it too: `useEmitter(cartChanged, cart, { traceId: "Cart", traceGroupId: "checkout" })`. `traceGroupId` works without `scopePlugin`, and needs `tracePlugin`.
|
|
151
|
+
|
|
152
|
+
## Name a listener
|
|
153
|
+
|
|
154
|
+
**Problem.** The log shows what started a chain and what it started, but a listener of an event reports nothing, so you cannot tell which of several listeners ran.
|
|
155
|
+
|
|
156
|
+
**When.** An event has more than one listener and you read the trace to see which one did the work.
|
|
157
|
+
|
|
158
|
+
Give the listener a `traceId` when you register it. The plugin makes each call of it a task with that name, and what it emits with `ctx.emit` is below it. `off` works as before. It also works in a module (`m.on(event, handler, { traceId })`) and on an instance made by `extendBus`.
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
import { createBus } from "nanoeffects";
|
|
162
|
+
import { consoleLogger, createTracer, tracePlugin } from "nanoeffects/trace";
|
|
163
|
+
|
|
164
|
+
const bus = createBus({ trace: createTracer(consoleLogger()) }).use(tracePlugin());
|
|
165
|
+
|
|
166
|
+
// traceId: the name of this listener in the trace
|
|
167
|
+
bus.on("name/changed", saveName, { traceId: "saveNotifier" });
|
|
168
|
+
bus.once("name/changed", (name) => console.log(name), { traceId: "auditLog" });
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
[nanoeffects] saveNotifier · run start
|
|
173
|
+
[nanoeffects] user/save · command start
|
|
174
|
+
[nanoeffects] saveNotifier · run done
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Keep the chain: `ctx.emit`, not `bus.emit`
|
|
178
|
+
|
|
179
|
+
Inside a handler, emit with `ctx.emit`. It keeps the chain: what it starts is below the handler, and its events have the `rootId` of the chain. A plain `bus.emit` inside a handler starts a new root with no `parentId` and no `rootId`, so the trace loses the link. The bus cannot warn about it, because it does not know which handler is running.
|
|
180
|
+
|
|
181
|
+
The names work wherever an event or a listener is made:
|
|
182
|
+
|
|
183
|
+
| Where | How to name it |
|
|
184
|
+
| ----- | -------------- |
|
|
185
|
+
| `useEmitter` and `bus.emitter` | `{ traceId, traceGroupId }` in the options |
|
|
186
|
+
| A Redux dispatch | `createEffectsMiddleware(bus, { traceId, traceGroupId })` |
|
|
187
|
+
| A listener, `bus.on` and `bus.once` | `{ traceId }` as the third argument |
|
|
188
|
+
| A listener of a module or of an instance of `extendBus` | the same third argument of its `on` and `once` |
|
|
189
|
+
| A method with `@On` or `@Once` | `{ traceId }` in the decorator |
|
|
190
|
+
| A command, a job or an action | `traceGroupId` in its scope |
|
|
191
|
+
|
|
192
|
+
## Turn the names off
|
|
193
|
+
|
|
194
|
+
`tracePlugin` is optional, and none of it changes how your code runs:
|
|
195
|
+
|
|
196
|
+
- **Without the plugin**, or on a bus without a tracer, `traceId` and `traceGroupId` are ignored. Handlers run as they are, with the same results, errors and rejections, and `onError` sees the same errors.
|
|
197
|
+
- **The types are the same** with and without the plugin, so you can use it only in development: `if (isDev) bus.use(tracePlugin())` compiles in both builds.
|
|
198
|
+
- **A `traceGroupId` in a scope** is never put on `ctx`.
|
|
199
|
+
|
|
54
200
|
## Use built-in loggers
|
|
55
201
|
|
|
56
202
|
A logger is `(event) => void`. `createTracer` takes one logger or an array. A logger that throws is reported to `console.error` and never breaks the task or the other loggers.
|
|
@@ -66,7 +212,7 @@ const bus = createBus({
|
|
|
66
212
|
|
|
67
213
|
| Logger | What it does |
|
|
68
214
|
| ------ | ------------ |
|
|
69
|
-
| `reduxDevtools()` | Sends
|
|
215
|
+
| `reduxDevtools()` | Sends the emits to Redux DevTools, and with `policies` the stages of the handlers as `type/effect/phase`; state is `{ running }` |
|
|
70
216
|
| `consoleLogger()` | Prints one line per phase, indented by task depth |
|
|
71
217
|
| `performanceLogger()` | User Timing measures for the browser Performance panel |
|
|
72
218
|
| `createTraceRecorder()` | Keeps events in `recorder.events`, for tests |
|
|
@@ -89,19 +235,49 @@ createTracer(logger, {
|
|
|
89
235
|
|
|
90
236
|
Read-only (the bus owns no state, so there is no time travel). Does nothing when the extension is not installed. Has its own instance in the DevTools picker, apart from your store.
|
|
91
237
|
|
|
92
|
-
Quiet by default:
|
|
238
|
+
Quiet by default, so you see what happened before you see how:
|
|
239
|
+
|
|
240
|
+
- **One action per emit**, named after the event (`search`), with its payload. This needs `bus.use(tracePlugin())` (see [Show every emit](#show-every-emit)); without it the default log is empty, and the logger prints one `console.warn` when the first handler task ends with no emit before it.
|
|
241
|
+
- **A failed handler** is sent as `search/latest/error`.
|
|
242
|
+
- Nothing else: no start, no `done`, no policy stages, no effects.
|
|
243
|
+
|
|
244
|
+
Turn on more when you need it:
|
|
93
245
|
|
|
94
|
-
-
|
|
95
|
-
-
|
|
246
|
+
- `policies` — the stages of the handler tasks, as `event/policy/phase`. `true` shows every phase, a list shows those phases, a function gets `{ effect, phase }`.
|
|
247
|
+
- `starts: true` — also send the start of each shown task. Running tasks are in the state `{ running }`.
|
|
248
|
+
- `depth: 1` — also the effects a handler starts (`retry`, `timeout`); `depth: Infinity` — everything below.
|
|
96
249
|
- `maxAge: 200` — how many actions DevTools keeps (its default is 50).
|
|
97
250
|
|
|
251
|
+
```js
|
|
252
|
+
// bus.js
|
|
253
|
+
import { createBus } from "nanoeffects";
|
|
254
|
+
import { createTracer, reduxDevtools, tracePlugin } from "nanoeffects/trace";
|
|
255
|
+
|
|
256
|
+
// reduxDevtools: sends the trace to the extension; it has the emits and the failures only
|
|
257
|
+
const logger = reduxDevtools({ name: "app" });
|
|
258
|
+
|
|
259
|
+
export const bus = createBus({ trace: createTracer(logger) })
|
|
260
|
+
.use(tracePlugin()); // tracePlugin: makes the emit events
|
|
261
|
+
```
|
|
262
|
+
|
|
98
263
|
```js
|
|
99
264
|
import { reduxDevtools } from "nanoeffects/trace";
|
|
100
265
|
|
|
101
|
-
|
|
102
|
-
reduxDevtools({ name: "app",
|
|
266
|
+
// debugging a race: which calls did a policy cancel or drop?
|
|
267
|
+
reduxDevtools({ name: "app", policies: ["cancel", "drop"] });
|
|
268
|
+
|
|
269
|
+
// every stage of every policy, one function for the choice
|
|
270
|
+
reduxDevtools({
|
|
271
|
+
name: "app",
|
|
272
|
+
policies: ({ effect, phase }) => effect === "latest" && phase !== "start",
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// the old log: every stage, and the effects below the handlers
|
|
276
|
+
reduxDevtools({ name: "app", policies: true, depth: 1 });
|
|
103
277
|
```
|
|
104
278
|
|
|
279
|
+
With two quick `search` emits and a `latest` handler, the default list is `search`, `search`. With `policies: true` it adds the `latest` stages of each call, for example `search/latest/cancel` for the first one.
|
|
280
|
+
|
|
105
281
|
### Write your own logger
|
|
106
282
|
|
|
107
283
|
```ts
|
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`, `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`; `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
|
---
|
|
@@ -566,7 +566,7 @@ Imports are split so you only load what you use. `createBus` stays small; the re
|
|
|
566
566
|
| `nanoeffects/observable` | `createObserve` |
|
|
567
567
|
| `nanoeffects/react` | `reactPlugin`: adds the `useEmitter` hook to a bus (needs React 18 or newer) |
|
|
568
568
|
| `nanoeffects/dev` | `devPlugin` (no-op in production) |
|
|
569
|
-
| `nanoeffects/trace` | `createTracer
|
|
569
|
+
| `nanoeffects/trace` | `createTracer`, the loggers and `tracePlugin` (names for the trace) |
|
|
570
570
|
|
|
571
571
|
The `...Plugin` names above are used with `bus.use`. [Plugins](plugins.md) lists all of them, what each returns, and the order to use them in.
|
|
572
572
|
|
|
@@ -680,6 +680,7 @@ Add one of these when you meet its need. None of them is required.
|
|
|
680
680
|
| [`signalPlugin`](#tie-an-emit-to-an-outside-signal-signalplugin) | `emit(call, { signal })` that aborts its tasks when an outside signal aborts. | `nanoeffects/signal` | The same bus, typed with the new `emit` |
|
|
681
681
|
| [`matchPlugin`](#listen-to-a-group-of-events-matchplugin) | `on` that listens to a group of events which a function picks. | `nanoeffects/match` | The same bus, typed with the new overloads |
|
|
682
682
|
| [`emitterPlugin`](#hand-the-bus-to-a-state-manager-emitterplugin) | A callback that emits an action, to give to a state manager. | `nanoeffects/actions` | The same bus, with `emitter` |
|
|
683
|
+
| [`tracePlugin`](#name-the-origin-and-the-flow-of-a-trace-traceplugin) | `traceId` and `traceGroupId` for a trace: the root of an emitter, a flow, a named listener. | `nanoeffects/trace` | The same bus |
|
|
683
684
|
| [`reactPlugin`](#tell-the-bus-when-a-react-value-changes-reactplugin) | A React hook that emits when a value of a component changes. | `nanoeffects/react` | The same bus, with `useEmitter` |
|
|
684
685
|
| [`devPlugin`](#find-leaks-while-you-develop-devplugin) | Warnings about listeners that pile up, and messages about modules, while you develop. | `nanoeffects/dev` | A function that disconnects it |
|
|
685
686
|
|
|
@@ -723,6 +724,20 @@ bus.emit(getUser("1"), { signal: controller.signal }); // runs the command
|
|
|
723
724
|
controller.abort(); // ctx.signal of the handler aborts with it
|
|
724
725
|
```
|
|
725
726
|
|
|
727
|
+
### Name the origin and the flow of a trace: `tracePlugin`
|
|
728
|
+
|
|
729
|
+
With a tracer on the bus, the plugin lets a trace say which subscription began a chain (`traceId` of `useEmitter` and `bus.emitter`), follow one flow by a `traceGroupId`, and show which listener ran (`bus.on(event, handler, { traceId })`). Without the plugin the options are ignored. More in [Tracing](tracing.md#show-which-subscription-began-a-chain).
|
|
730
|
+
|
|
731
|
+
```js
|
|
732
|
+
import { createBus } from "nanoeffects";
|
|
733
|
+
import { consoleLogger, createTracer, tracePlugin } from "nanoeffects/trace";
|
|
734
|
+
|
|
735
|
+
// tracePlugin — names the roots, groups and listeners of the trace
|
|
736
|
+
const bus = createBus({ trace: createTracer(consoleLogger()) }).use(tracePlugin());
|
|
737
|
+
|
|
738
|
+
bus.on("name/changed", saveName, { traceId: "saveNotifier" }); // a listener you can find in the log
|
|
739
|
+
```
|
|
740
|
+
|
|
726
741
|
### Listen to a group of events: `matchPlugin`
|
|
727
742
|
|
|
728
743
|
`on`, `once` and `off` take a function that picks the events, such as every `user/*` event. A type guard narrows the payload. The subscription listens to every event, so for a few known events `on(creator, handler)` is cheaper. More in the [Guide](guide.md#pick-events-with-a-function).
|
|
@@ -759,7 +774,7 @@ $name.listen(bus.emitter(nameChanged)); // every change of $name emits nameChang
|
|
|
759
774
|
|
|
760
775
|
### Tell the bus when a React value changes: `reactPlugin`
|
|
761
776
|
|
|
762
|
-
`useEmitter(event, value)` emits after a render commits with a new value. It does not emit on mount, and not twice under StrictMode. It needs React 18 or newer. More in [Stores](stores.md#react-sharing-a-value-across-components).
|
|
777
|
+
`useEmitter(event, value, options?)` emits after a render commits with a new value. It does not emit on mount, and not twice under StrictMode. The options are `{ equals, traceId, traceGroupId }`; the last two name the emit in the [trace](tracing.md#show-which-subscription-began-a-chain). It needs React 18 or newer. More in [Stores](stores.md#react-sharing-a-value-across-components).
|
|
763
778
|
|
|
764
779
|
```jsx
|
|
765
780
|
import { useState } from "react";
|
|
@@ -2126,6 +2141,10 @@ String event names (`"user/requested"`) work the same. Use your usual RTK action
|
|
|
2126
2141
|
|
|
2127
2142
|
The only option is `filter`, which keeps rejected actions away from the bus. Options of the bus (`onError`, `context`, `trace`) go to `createBus`. Use one bus for one store. Values that are not plain actions (thunks) go through untouched. Nothing here depends on the `redux` package at runtime.
|
|
2128
2143
|
|
|
2144
|
+
### Show which dispatch began a chain
|
|
2145
|
+
|
|
2146
|
+
With a tracer and `tracePlugin` on the bus, each dispatched action reaches the bus from a root task of the trace. Name it with `traceId`, and put the flow in a group with `traceGroupId`: `createEffectsMiddleware(bus, { traceId: "store", traceGroupId: "checkout" })`. Without a `traceId` the root has the type of the action. Without the plugin the options are ignored. More in [Tracing](tracing.md#show-which-subscription-began-a-chain).
|
|
2147
|
+
|
|
2129
2148
|
### Type `ctx.store` (optional)
|
|
2130
2149
|
|
|
2131
2150
|
Only if you want TypeScript to know `store` on handlers. **Skip this in JavaScript** — the middleware already puts `store` on the task at runtime. These lines do not connect the store across the app.
|
|
@@ -2504,7 +2523,7 @@ export function Results() {
|
|
|
2504
2523
|
Two limits to know:
|
|
2505
2524
|
|
|
2506
2525
|
- **All consumers re-render** on any change of the context value. Split it into two contexts (`query` and `items`) if that is slow.
|
|
2507
|
-
- **A new object counts as a change.** For a value that is rebuilt with the same content, pass
|
|
2526
|
+
- **A new object counts as a change.** For a value that is rebuilt with the same content, pass `equals` in the options, the third argument, to say when two values are the same: `useEmitter(profileChanged, profile, { equals: (previous, next) => previous.id === next.id })`. The same object takes `traceId` and `traceGroupId`, which name the subscription and the flow in the [trace](tracing.md#show-which-subscription-began-a-chain).
|
|
2508
2527
|
|
|
2509
2528
|
`useEmitter` emits after the render, not at the moment of `setQuery`. To emit on every call, even with the same value, call an emitter from the event handler (`bus.emitter(action)`, see [Zustand](#zustand)) instead.
|
|
2510
2529
|
|
|
@@ -3308,7 +3327,7 @@ When several places set the same field, the strongest one wins:
|
|
|
3308
3327
|
| 3 | the scope of the module that owns the handler |
|
|
3309
3328
|
| 4 (weakest) | the bus: `createBus({ context })`, `contextPlugin`, `effectsPlugin` |
|
|
3310
3329
|
|
|
3311
|
-
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.
|
|
3330
|
+
The fields of the library (`signal`, `abort`, `emit`, `on`, `off`, `run`, `type`, `payload`) are never replaced. The name `traceGroupId` is reserved: the tracer reads it, and it is not put on `ctx` ([Follow one flow](tracing.md#follow-one-flow-with-a-tracegroupid)). A child task (`ctx.run`, an attempt of `retry`) has the fields of its parent.
|
|
3312
3331
|
|
|
3313
3332
|
## Keep a generic effect generic
|
|
3314
3333
|
|
|
@@ -3583,6 +3602,8 @@ Things to know:
|
|
|
3583
3602
|
- On a class that `extendBus` did not make, a decorator does nothing.
|
|
3584
3603
|
- Both the decorators of the language and `experimentalDecorators` work. Your compiler has to support the one you use: TypeScript 5 or newer for the first, and the `experimentalDecorators` option for the second.
|
|
3585
3604
|
|
|
3605
|
+
Name a method for the [trace](tracing.md#name-a-listener) with `{ traceId }`, as the second argument or, after a wrapper, the third: `@On(queryChanged, { traceId: "searcher" })` or `@On(queryChanged, latest, { traceId: "searcher" })`. It needs `tracePlugin` on the bus and is ignored without it.
|
|
3606
|
+
|
|
3586
3607
|
## Decorate the class with `@WithBus`
|
|
3587
3608
|
|
|
3588
3609
|
`@WithBus(bus)` does what `extendBus(bus, Base)` does, as a class decorator. `@WithBus()` makes a bus for the class.
|
|
@@ -4540,12 +4561,14 @@ Each `TraceEvent` has:
|
|
|
4540
4561
|
| Field | Meaning |
|
|
4541
4562
|
| ----- | ------- |
|
|
4542
4563
|
| `id` | Unique task id within the bus |
|
|
4543
|
-
| `
|
|
4564
|
+
| `parentId` | Id of the task that started this one (absent for handler tasks) |
|
|
4565
|
+
| `rootId` | Id of the root task of the chain; absent on the root itself, and it is the same through a listener that reports nothing |
|
|
4544
4566
|
| `type` | Event type the task works on |
|
|
4545
|
-
| `effect` | `latest`, `debounce`, `throttle`, `leading`, `run`, `all`, `race`,
|
|
4567
|
+
| `effect` | `latest`, `debounce`, `throttle`, `leading`, `run`, `all`, `race`, …; `emit` for the emit event of `tracePlugin` |
|
|
4546
4568
|
| `phase` | `start`, `done`, `error`, `cancel`, or `drop` |
|
|
4547
4569
|
| `payload` | Event payload |
|
|
4548
4570
|
| `error` | Thrown value, only for `error` |
|
|
4571
|
+
| `traceGroupId` | The flow of the task, when it or a task above has one |
|
|
4549
4572
|
|
|
4550
4573
|
Phases:
|
|
4551
4574
|
|
|
@@ -4570,6 +4593,150 @@ bus.on("load", async (id, ctx) => {
|
|
|
4570
4593
|
});
|
|
4571
4594
|
```
|
|
4572
4595
|
|
|
4596
|
+
## Show every emit
|
|
4597
|
+
|
|
4598
|
+
**When.** You want the log to say what was emitted, also when nothing listens or the listener is a plain function. Without it the trace has only the tasks that report: a policy (`latest`, ...) or a `ctx.run`.
|
|
4599
|
+
|
|
4600
|
+
`tracePlugin` reports one event for each emit, with `effect: "emit"` and `phase: "done"`, before the handlers run. `type` is the event name and `payload` is what was emitted. A `ctx.emit` has the task that emits as its `parentId`.
|
|
4601
|
+
|
|
4602
|
+
```js
|
|
4603
|
+
// bus.js
|
|
4604
|
+
import { createBus } from "nanoeffects";
|
|
4605
|
+
import { consoleLogger, createTracer, tracePlugin } from "nanoeffects/trace";
|
|
4606
|
+
|
|
4607
|
+
export const bus = createBus({ trace: createTracer(consoleLogger()) })
|
|
4608
|
+
.use(tracePlugin()); // tracePlugin: reports each emit as an event
|
|
4609
|
+
```
|
|
4610
|
+
|
|
4611
|
+
```js
|
|
4612
|
+
import { bus } from "./bus";
|
|
4613
|
+
|
|
4614
|
+
bus.emit("search", "nano"); // logs: search · emit done
|
|
4615
|
+
```
|
|
4616
|
+
|
|
4617
|
+
The event changes nothing else: the other tasks keep their `parentId`, `rootId` and `traceGroupId`. Without the plugin the trace has no `emit` events.
|
|
4618
|
+
|
|
4619
|
+
- **What counts as an emit.** Every `bus.emit`, also the ones a library makes for you: a job emits its `pending` and `success` (or `fail`, `cancel`), so one call of a job is several `emit` events.
|
|
4620
|
+
- **Cost.** One small task per emit, about 60 ns on a bus that already has a tracer (a bus with a tracer and no plugin: about 250 ns more than a bus without one). `sample` and `filter` of `createTracer` cut it in the log, not in the work.
|
|
4621
|
+
|
|
4622
|
+
## Show which subscription began a chain
|
|
4623
|
+
|
|
4624
|
+
**Problem.** `useEmitter` and `bus.emitter` put an event on the bus from outside any task, such as a render or the callback of a store. In the trace that event has no sign of where it came from. With many such subscriptions it is hard to tell which one started a chain of commands and retries.
|
|
4625
|
+
|
|
4626
|
+
**When.** The bus has a tracer and the project has several `useEmitter` or `bus.emitter` calls. The names come from `tracePlugin`: use it on the bus, or the `traceId` is ignored.
|
|
4627
|
+
|
|
4628
|
+
Give each one a `traceId`. When the bus has a tracer, the emit is made from a root task with that `traceId` as its `type` and `run` as its effect. What the event starts is below it. Without a tracer nothing changes.
|
|
4629
|
+
|
|
4630
|
+
```js
|
|
4631
|
+
import { createBus } from "nanoeffects";
|
|
4632
|
+
import { createAction, emitterPlugin } from "nanoeffects/actions";
|
|
4633
|
+
import { consoleLogger, createTracer, tracePlugin } from "nanoeffects/trace";
|
|
4634
|
+
|
|
4635
|
+
// createTracer: reports every task phase to the console
|
|
4636
|
+
const bus = createBus({ trace: createTracer(consoleLogger()) })
|
|
4637
|
+
.use(tracePlugin()) // tracePlugin: makes the root task that traceId names
|
|
4638
|
+
.use(emitterPlugin());
|
|
4639
|
+
|
|
4640
|
+
const cartChanged = createAction("cart/changed");
|
|
4641
|
+
|
|
4642
|
+
// emitter: a callback for a store; traceId names it in the trace
|
|
4643
|
+
const emitCartChanged = bus.emitter(cartChanged, { traceId: "cartStore" });
|
|
4644
|
+
```
|
|
4645
|
+
|
|
4646
|
+
In a component, the same option goes in the options of the hook: `useEmitter(nameChanged, name, { traceId: "Profile" })`. In the console:
|
|
4647
|
+
|
|
4648
|
+
```
|
|
4649
|
+
[nanoeffects] Profile · run start
|
|
4650
|
+
[nanoeffects] user/save · command start
|
|
4651
|
+
[nanoeffects] user/save · command done
|
|
4652
|
+
[nanoeffects] Profile · run done
|
|
4653
|
+
```
|
|
4654
|
+
|
|
4655
|
+
A `traceId` is a string you choose and keep stable: the name of the component or of the store. Without one the root has the type of the event.
|
|
4656
|
+
|
|
4657
|
+
Two things to know:
|
|
4658
|
+
|
|
4659
|
+
- **`sample` sees the `traceId`.** The root of an emitter is a task like any other, so a `sample` function gets `{ type, payload }` with the `traceId` as the `type` and the emitted action as the `payload`. A function that matches on the type of the event, such as `event.type === "search"`, does not pick a root that has another `traceId`; match the `traceId`, or the `payload.type`.
|
|
4660
|
+
- **Inside a handler, emit with `ctx.emit`.** `ctx.emit` keeps the chain: what it starts is below the handler. A plain `bus.emit` inside a handler starts a new root with no parent and no `rootId`, so the trace loses the link.
|
|
4661
|
+
|
|
4662
|
+
Every event of a task below another one has `rootId`, the id of the root of the chain, also when a listener, which reports nothing, stands in between. `parentId` is the task directly above, which can be such a listener.
|
|
4663
|
+
|
|
4664
|
+
## Follow one flow with a traceGroupId
|
|
4665
|
+
|
|
4666
|
+
**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.
|
|
4667
|
+
|
|
4668
|
+
**When.** One business flow runs through several commands, and you read the log by flow.
|
|
4669
|
+
|
|
4670
|
+
Give the flow a `traceGroupId` in the scope of the command, the job or the action 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.
|
|
4671
|
+
|
|
4672
|
+
```js
|
|
4673
|
+
import { createBus } from "nanoeffects";
|
|
4674
|
+
import { createCommand } from "nanoeffects/actions";
|
|
4675
|
+
import { consoleLogger, createTracer, tracePlugin } from "nanoeffects/trace";
|
|
4676
|
+
|
|
4677
|
+
// tracePlugin: reads traceGroupId from the scope of a call
|
|
4678
|
+
const bus = createBus({ trace: createTracer(consoleLogger()) }).use(tracePlugin());
|
|
4679
|
+
|
|
4680
|
+
// the scope names the flow; traceGroupId is for the tracer, not a field of ctx
|
|
4681
|
+
const pay = createCommand("checkout/pay", async () => {}, {
|
|
4682
|
+
traceGroupId: "checkout",
|
|
4683
|
+
});
|
|
4684
|
+
```
|
|
4685
|
+
|
|
4686
|
+
```
|
|
4687
|
+
[nanoeffects] [checkout] checkout/pay · command start
|
|
4688
|
+
```
|
|
4689
|
+
|
|
4690
|
+
The emitters take it too: `useEmitter(cartChanged, cart, { traceId: "Cart", traceGroupId: "checkout" })`. `traceGroupId` works without `scopePlugin`, and needs `tracePlugin`.
|
|
4691
|
+
|
|
4692
|
+
## Name a listener
|
|
4693
|
+
|
|
4694
|
+
**Problem.** The log shows what started a chain and what it started, but a listener of an event reports nothing, so you cannot tell which of several listeners ran.
|
|
4695
|
+
|
|
4696
|
+
**When.** An event has more than one listener and you read the trace to see which one did the work.
|
|
4697
|
+
|
|
4698
|
+
Give the listener a `traceId` when you register it. The plugin makes each call of it a task with that name, and what it emits with `ctx.emit` is below it. `off` works as before. It also works in a module (`m.on(event, handler, { traceId })`) and on an instance made by `extendBus`.
|
|
4699
|
+
|
|
4700
|
+
```js
|
|
4701
|
+
import { createBus } from "nanoeffects";
|
|
4702
|
+
import { consoleLogger, createTracer, tracePlugin } from "nanoeffects/trace";
|
|
4703
|
+
|
|
4704
|
+
const bus = createBus({ trace: createTracer(consoleLogger()) }).use(tracePlugin());
|
|
4705
|
+
|
|
4706
|
+
// traceId: the name of this listener in the trace
|
|
4707
|
+
bus.on("name/changed", saveName, { traceId: "saveNotifier" });
|
|
4708
|
+
bus.once("name/changed", (name) => console.log(name), { traceId: "auditLog" });
|
|
4709
|
+
```
|
|
4710
|
+
|
|
4711
|
+
```
|
|
4712
|
+
[nanoeffects] saveNotifier · run start
|
|
4713
|
+
[nanoeffects] user/save · command start
|
|
4714
|
+
[nanoeffects] saveNotifier · run done
|
|
4715
|
+
```
|
|
4716
|
+
|
|
4717
|
+
## Keep the chain: `ctx.emit`, not `bus.emit`
|
|
4718
|
+
|
|
4719
|
+
Inside a handler, emit with `ctx.emit`. It keeps the chain: what it starts is below the handler, and its events have the `rootId` of the chain. A plain `bus.emit` inside a handler starts a new root with no `parentId` and no `rootId`, so the trace loses the link. The bus cannot warn about it, because it does not know which handler is running.
|
|
4720
|
+
|
|
4721
|
+
The names work wherever an event or a listener is made:
|
|
4722
|
+
|
|
4723
|
+
| Where | How to name it |
|
|
4724
|
+
| ----- | -------------- |
|
|
4725
|
+
| `useEmitter` and `bus.emitter` | `{ traceId, traceGroupId }` in the options |
|
|
4726
|
+
| A Redux dispatch | `createEffectsMiddleware(bus, { traceId, traceGroupId })` |
|
|
4727
|
+
| A listener, `bus.on` and `bus.once` | `{ traceId }` as the third argument |
|
|
4728
|
+
| A listener of a module or of an instance of `extendBus` | the same third argument of its `on` and `once` |
|
|
4729
|
+
| A method with `@On` or `@Once` | `{ traceId }` in the decorator |
|
|
4730
|
+
| A command, a job or an action | `traceGroupId` in its scope |
|
|
4731
|
+
|
|
4732
|
+
## Turn the names off
|
|
4733
|
+
|
|
4734
|
+
`tracePlugin` is optional, and none of it changes how your code runs:
|
|
4735
|
+
|
|
4736
|
+
- **Without the plugin**, or on a bus without a tracer, `traceId` and `traceGroupId` are ignored. Handlers run as they are, with the same results, errors and rejections, and `onError` sees the same errors.
|
|
4737
|
+
- **The types are the same** with and without the plugin, so you can use it only in development: `if (isDev) bus.use(tracePlugin())` compiles in both builds.
|
|
4738
|
+
- **A `traceGroupId` in a scope** is never put on `ctx`.
|
|
4739
|
+
|
|
4573
4740
|
## Use built-in loggers
|
|
4574
4741
|
|
|
4575
4742
|
A logger is `(event) => void`. `createTracer` takes one logger or an array. A logger that throws is reported to `console.error` and never breaks the task or the other loggers.
|
|
@@ -4585,7 +4752,7 @@ const bus = createBus({
|
|
|
4585
4752
|
|
|
4586
4753
|
| Logger | What it does |
|
|
4587
4754
|
| ------ | ------------ |
|
|
4588
|
-
| `reduxDevtools()` | Sends
|
|
4755
|
+
| `reduxDevtools()` | Sends the emits to Redux DevTools, and with `policies` the stages of the handlers as `type/effect/phase`; state is `{ running }` |
|
|
4589
4756
|
| `consoleLogger()` | Prints one line per phase, indented by task depth |
|
|
4590
4757
|
| `performanceLogger()` | User Timing measures for the browser Performance panel |
|
|
4591
4758
|
| `createTraceRecorder()` | Keeps events in `recorder.events`, for tests |
|
|
@@ -4608,19 +4775,49 @@ createTracer(logger, {
|
|
|
4608
4775
|
|
|
4609
4776
|
Read-only (the bus owns no state, so there is no time travel). Does nothing when the extension is not installed. Has its own instance in the DevTools picker, apart from your store.
|
|
4610
4777
|
|
|
4611
|
-
Quiet by default:
|
|
4778
|
+
Quiet by default, so you see what happened before you see how:
|
|
4779
|
+
|
|
4780
|
+
- **One action per emit**, named after the event (`search`), with its payload. This needs `bus.use(tracePlugin())` (see [Show every emit](#show-every-emit)); without it the default log is empty, and the logger prints one `console.warn` when the first handler task ends with no emit before it.
|
|
4781
|
+
- **A failed handler** is sent as `search/latest/error`.
|
|
4782
|
+
- Nothing else: no start, no `done`, no policy stages, no effects.
|
|
4612
4783
|
|
|
4613
|
-
|
|
4614
|
-
|
|
4784
|
+
Turn on more when you need it:
|
|
4785
|
+
|
|
4786
|
+
- `policies` — the stages of the handler tasks, as `event/policy/phase`. `true` shows every phase, a list shows those phases, a function gets `{ effect, phase }`.
|
|
4787
|
+
- `starts: true` — also send the start of each shown task. Running tasks are in the state `{ running }`.
|
|
4788
|
+
- `depth: 1` — also the effects a handler starts (`retry`, `timeout`); `depth: Infinity` — everything below.
|
|
4615
4789
|
- `maxAge: 200` — how many actions DevTools keeps (its default is 50).
|
|
4616
4790
|
|
|
4791
|
+
```js
|
|
4792
|
+
// bus.js
|
|
4793
|
+
import { createBus } from "nanoeffects";
|
|
4794
|
+
import { createTracer, reduxDevtools, tracePlugin } from "nanoeffects/trace";
|
|
4795
|
+
|
|
4796
|
+
// reduxDevtools: sends the trace to the extension; it has the emits and the failures only
|
|
4797
|
+
const logger = reduxDevtools({ name: "app" });
|
|
4798
|
+
|
|
4799
|
+
export const bus = createBus({ trace: createTracer(logger) })
|
|
4800
|
+
.use(tracePlugin()); // tracePlugin: makes the emit events
|
|
4801
|
+
```
|
|
4802
|
+
|
|
4617
4803
|
```js
|
|
4618
4804
|
import { reduxDevtools } from "nanoeffects/trace";
|
|
4619
4805
|
|
|
4620
|
-
|
|
4621
|
-
reduxDevtools({ name: "app",
|
|
4806
|
+
// debugging a race: which calls did a policy cancel or drop?
|
|
4807
|
+
reduxDevtools({ name: "app", policies: ["cancel", "drop"] });
|
|
4808
|
+
|
|
4809
|
+
// every stage of every policy, one function for the choice
|
|
4810
|
+
reduxDevtools({
|
|
4811
|
+
name: "app",
|
|
4812
|
+
policies: ({ effect, phase }) => effect === "latest" && phase !== "start",
|
|
4813
|
+
});
|
|
4814
|
+
|
|
4815
|
+
// the old log: every stage, and the effects below the handlers
|
|
4816
|
+
reduxDevtools({ name: "app", policies: true, depth: 1 });
|
|
4622
4817
|
```
|
|
4623
4818
|
|
|
4819
|
+
With two quick `search` emits and a `latest` handler, the default list is `search`, `search`. With `policies: true` it adds the `latest` stages of each call, for example `search/latest/cancel` for the first one.
|
|
4820
|
+
|
|
4624
4821
|
### Write your own logger
|
|
4625
4822
|
|
|
4626
4823
|
```ts
|
package/llms.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`, `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`; `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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nanoeffects",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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",
|
|
@@ -201,7 +201,7 @@
|
|
|
201
201
|
{
|
|
202
202
|
"name": "actions (all)",
|
|
203
203
|
"path": "dist/actions.js",
|
|
204
|
-
"limit": "2.
|
|
204
|
+
"limit": "2.87 kB"
|
|
205
205
|
},
|
|
206
206
|
{
|
|
207
207
|
"name": "match (plugin)",
|
|
@@ -239,7 +239,7 @@
|
|
|
239
239
|
"ignore": [
|
|
240
240
|
"react"
|
|
241
241
|
],
|
|
242
|
-
"limit": "1.
|
|
242
|
+
"limit": "1.18 kB"
|
|
243
243
|
},
|
|
244
244
|
{
|
|
245
245
|
"name": "dev",
|
package/dist/bus-CkTHUAvq.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{n as e}from"./onAbort-BgMfspK4.js";import{a as t,n,r}from"./context-COgyNlrC.js";import{n as i}from"./promise-CSLpzwSR.js";import{n as a,t as o}from"./event-0RIBGYqS.js";const s=`*`,c=()=>{let e={},t={},n=n=>(t[n]=void 0,e[n]??=/* @__PURE__ */ new Map,e[n]),r=(e,t)=>{let r=n(e),i=r.get(t)??0;i>1?r.set(t,i-1):t?r.delete(t):r.clear()};return{get:n=>(t[n]??=[...e[n]??[]].flatMap(([e,t])=>Array(t).fill(e)),t[n]),add:(e,t)=>{let i=n(e);return i.set(t,(i.get(t)??0)+1),()=>{r(e,t)}},remove:r,clear:()=>{for(let t of Reflect.ownKeys(e))delete e[t];for(let e of Reflect.ownKeys(t))delete t[e]}}},l=Symbol(),u=(e,t,n)=>{let r={type:t,payload:n};return Object.defineProperty(r,l,{value:e,enumerable:!1}),r},d=e=>{if(typeof e==`function`)return e;if(typeof e==`object`&&e&&l in e)return e[l]},f=(e,t,n)=>Reflect.apply(e,null,[t,n]),p=(s={})=>{let l=c(),u=(e,t)=>l.add(o(e),t),p=(e,t)=>{let n=u(e,(e,r)=>(n(),t(e,r)));return n},m=(e,t)=>{e===void 0?l.clear():l.remove(o(e),t)},h=(t,a)=>{let{onError:o}=s,{ctx:c}=n({event:a,bus:g,tracer:s.trace,parent:{ref:r()}});if(!o){t(a.payload,c);return}let l=t=>{try{o(t,a)}catch(t){e(`onError failed`,t)}};try{let e=t(a.payload,c);i(e)&&Promise.resolve(e).catch(l)}catch(e){l(e)}},g={on:u,once:p,off:m,emit:(e,i)=>{let c=o(e),u=a(e,i),p={type:c,payload:u},m=d(e),_=[...l.get(c),...l.get(`*`)];_.length===0&&!m&&s.onUnhandled?.(p);for(let e of _)h(e,p);if(m){let{ctx:e}=n({event:p,bus:g,tracer:s.trace,parent:{ref:r()}}),i=g.commands?.run??f;return t(e,`command`,()=>i(m,u,e))}},use:e=>e(g),ctx:s.context,trace:s.trace};return g};export{s as a,u as i,d as n,p as r,l as t};
|
package/dist/context-COgyNlrC.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{t as e}from"./onAbort-BgMfspK4.js";const t=t=>{let n=new AbortController;return{controller:n,unlink:e({signal:t},e=>n.abort(e))}},n=Symbol(),r=typeof __NANOEFFECTS_TRACE__>`u`||__NANOEFFECTS_TRACE__,i=/* @__PURE__ */ new WeakMap;let a;const o=()=>a,s=()=>{},c=(e,t,n,a)=>{let o=r&&i.get(e);o&&o.tracer.report(o.ref,t,n,a)},l=(e,t)=>{let n=r&&i.get(e);return n?n.tracer.settler(n.ref,t):s},u=(e,t,n)=>{let a=r&&i.get(e);return a?a.tracer.run(a.ref,t,e.signal,n):n()},d=(e,t)=>typeof e==`function`?e(t):e,f=({event:o,bus:c,tracer:l,parent:p})=>{let{controller:m,unlink:h}=t(p?.signal),{signal:g}=m,_=r&&l?{tracer:l,ref:l.task(o,p?.ref)}:void 0,v=(...e)=>{if(g.aborted&&!x[n])return;let t=c?.emit,r=a;a=_?.ref;try{return t?.(...e)}finally{a=r}},y=(...t)=>{if(g.aborted)return s;let n=c?.on,r=n?.(...t);if(!r)return s;let i=e({signal:g},r);return()=>{i(),r()}},b=async(e,...t)=>{g.throwIfAborted();let n=t[0],r=f({event:{type:o.type,payload:n},bus:c,tracer:l,parent:{signal:g,ref:_?.ref}});try{return await u(r.ctx,`run`,()=>e(n,r.ctx))}finally{r.unlink()}},x={...d(c?.ctx,o),type:o.type,payload:o.payload,signal:g,abort:e=>m.abort(e),emit:v,on:y,off:c?.off??s,run:b};return c?.addContext?.(x),_&&i.set(x,_),{ctx:x,unlink:h}},p=(e,t)=>{let n=r?i.get(e):void 0;return f({event:{type:t??e.type??``,payload:e.payload},bus:{emit:e.emit,on:e.on,off:e.off,ctx:e},tracer:n?.tracer,parent:{signal:e.signal,ref:n?.ref}})};export{u as a,p as i,f as n,c as o,o as r,n as s,l as t};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a as e,l as t,n,r}from"./context-yYU3_9lq.js";import{n as i}from"./promise-CSLpzwSR.js";const a=e=>typeof e==`string`?e:e.type,o=(e,t)=>typeof e==`object`?e.payload:t,s=`*`,c=()=>{let e={},t={},n=n=>(t[n]=void 0,e[n]??=/* @__PURE__ */ new Map,e[n]),r=(e,t)=>{let r=n(e),i=r.get(t)??0;i>1?r.set(t,i-1):t?r.delete(t):r.clear()};return{get:n=>(t[n]??=[...e[n]??[]].flatMap(([e,t])=>Array(t).fill(e)),t[n]),add:(e,t)=>{let i=n(e);return i.set(t,(i.get(t)??0)+1),()=>{r(e,t)}},remove:r,clear:()=>{for(let t of Reflect.ownKeys(e))delete e[t];for(let e of Reflect.ownKeys(t))delete t[e]}}},l=Symbol(),u=(e,t,n)=>{let r={type:t,payload:n};return Object.defineProperty(r,l,{value:e,enumerable:!1}),r},d=e=>{if(typeof e==`function`)return e;if(typeof e==`object`&&e&&l in e)return e[l]},f=(e,t,n)=>Reflect.apply(e,null,[t,n]),p=(s={})=>{let l=c(),u=(e,t)=>l.add(a(e),t),p=(e,t)=>{let n=u(e,(e,r)=>(n(),t(e,r)));return n},m=(e,t)=>{e===void 0?l.clear():l.remove(a(e),t)},h=(e,a)=>{let{onError:o}=s,{ctx:c}=n({event:a,bus:g,tracer:s.trace,parent:{ref:r()}});if(!o){e(a.payload,c);return}let l=e=>{try{o(e,a)}catch(e){t(`onError failed`,e)}};try{let t=e(a.payload,c);i(t)&&Promise.resolve(t).catch(l)}catch(e){l(e)}},g={on:u,once:p,off:m,emit:(t,i)=>{let c=a(t),u=o(t,i),p={type:c,payload:u},m=d(t),_=[...l.get(c),...l.get(`*`)];_.length===0&&!m&&s.onUnhandled?.(p);for(let e of _)h(e,p);if(m){let{ctx:t}=n({event:p,bus:g,tracer:s.trace,parent:{ref:r()}}),i=g.commands?.run??f;return e(t,`command`,()=>i(m,u,t))}},use:e=>e(g),ctx:s.context,trace:s.trace};return g};export{a,s as i,p as n,u as r,l as t};
|
package/dist/onAbort-BgMfspK4.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const e=(e,t)=>{globalThis.console?.error(`[nanoeffects] ${e}`,t)},t=()=>{},n=/* @__PURE__ */ new WeakMap,r=t=>{let r=n.get(t);if(r)return r;let i=/* @__PURE__ */ new Set;return n.set(t,i),t.addEventListener(`abort`,()=>{let n=[...i];i.clear();for(let{fn:r}of n)try{r(t.reason)}catch(t){e(`abort callback failed`,t)}},{once:!0}),i},i=(e,n)=>{let{signal:i}=e;if(!i)return t;if(i.aborted)return n(i.reason),t;let a=r(i),o={fn:n};return a.add(o),()=>{a.delete(o)}};export{e as n,i as t};
|