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.
Files changed (63) hide show
  1. package/dist/{action-creator-BrZF6hXc.d.ts → action-creator-CQlFgQm4.d.ts} +29 -13
  2. package/dist/actions.d.ts +12 -5
  3. package/dist/actions.js +1 -1
  4. package/dist/bus-Cdw5ajKF.js +1 -0
  5. package/dist/context-CJGW6OWj.js +1 -0
  6. package/dist/{context-plugin--HkNN1ha.d.ts → context-plugin-Cphk8jfq.d.ts} +1 -1
  7. package/dist/{delay-CIHnOh_L.js → delay-BnBdGPh7.js} +1 -1
  8. package/dist/dev.d.ts +2 -2
  9. package/dist/effects.d.ts +2 -2
  10. package/dist/effects.js +1 -1
  11. package/dist/index.d.ts +2 -2
  12. package/dist/index.js +1 -1
  13. package/dist/match.d.ts +1 -1
  14. package/dist/match.js +1 -1
  15. package/dist/{module-DqKegXHt.d.ts → module-woawvR28.d.ts} +3 -3
  16. package/dist/notrace/{action-creator-DHAEgwuh.d.ts → action-creator-Ls3Gy3jk.d.ts} +28 -12
  17. package/dist/notrace/actions.d.ts +12 -5
  18. package/dist/notrace/actions.js +1 -1
  19. package/dist/notrace/bus-G2AdGObY.js +1 -0
  20. package/dist/notrace/{context-plugin-BapQkGwQ.d.ts → context-plugin-BQa-wtbj.d.ts} +1 -1
  21. package/dist/notrace/effects.d.ts +2 -2
  22. package/dist/notrace/index.d.ts +2 -2
  23. package/dist/notrace/index.js +1 -1
  24. package/dist/notrace/origin-key-BbWN3gmN.js +1 -0
  25. package/dist/notrace/origin-key-CxK0SkeG.d.ts +10 -0
  26. package/dist/notrace/{plugin-Cxadz6uU.d.ts → plugin-Cw45_-Bo.d.ts} +2 -2
  27. package/dist/notrace/policies.d.ts +1 -1
  28. package/dist/notrace/redux.d.ts +4 -3
  29. package/dist/notrace/redux.js +1 -1
  30. package/dist/observable.d.ts +1 -1
  31. package/dist/oop.d.ts +13 -7
  32. package/dist/oop.js +1 -1
  33. package/dist/origin-key-BbWN3gmN.js +1 -0
  34. package/dist/origin-key-CxK0SkeG.d.ts +10 -0
  35. package/dist/{plugin-DkXCvsmB.d.ts → plugin-BrTsPg2Y.d.ts} +2 -2
  36. package/dist/policies.d.ts +2 -2
  37. package/dist/policies.js +1 -1
  38. package/dist/react.d.ts +15 -8
  39. package/dist/react.js +1 -1
  40. package/dist/redux.d.ts +4 -3
  41. package/dist/redux.js +1 -1
  42. package/dist/scope.d.ts +2 -2
  43. package/dist/scope.js +1 -1
  44. package/dist/signal.d.ts +1 -1
  45. package/dist/signal.js +1 -1
  46. package/dist/trace.d.ts +65 -14
  47. package/dist/trace.js +1 -1
  48. package/dist/{types-KavbvHix.d.ts → types-bzocBVYc.d.ts} +2 -2
  49. package/dist/{with-policy-DBV9dh2F.js → with-policy-CW50Edgk.js} +1 -1
  50. package/dist/{with-policy-CqUS-VWW.d.ts → with-policy-_zP3OKGy.d.ts} +1 -1
  51. package/docs/oop.md +2 -0
  52. package/docs/package.md +1 -1
  53. package/docs/plugins.md +16 -1
  54. package/docs/scope.md +1 -1
  55. package/docs/stores.md +5 -1
  56. package/docs/tracing.md +184 -8
  57. package/llms-full.txt +210 -13
  58. package/llms.txt +1 -1
  59. package/package.json +3 -3
  60. package/dist/bus-CkTHUAvq.js +0 -1
  61. package/dist/context-COgyNlrC.js +0 -1
  62. package/dist/notrace/bus-BmCTo8BG.js +0 -1
  63. 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
- | `parent` | Id of the task that started this one (absent for handler tasks) |
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 tasks to Redux DevTools as `type/effect/phase`; state is `{ running }` |
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
- - **A task is one action**, sent when it ends (`done`, `error`, `cancel`, `drop`). Running tasks are in `{ running }`. `starts: true` also sends every start.
95
- - **Only the top is shown**: handler tasks and the effects they start. Attempts and starters under them are hidden. `depth: 0` — handlers only; `depth: 3` — more; `Infinity` — everything.
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
- reduxDevtools({ name: "app", depth: 0 }); // one line per handler call
102
- reduxDevtools({ name: "app", starts: true, depth: Infinity }); // everything
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` and the loggers |
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 a third argument that says when two values are the same: `useEmitter(profileChanged, profile, (previous, next) => previous.id === next.id)`.
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
- | `parent` | Id of the task that started this one (absent for handler tasks) |
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 tasks to Redux DevTools as `type/effect/phase`; state is `{ running }` |
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
- - **A task is one action**, sent when it ends (`done`, `error`, `cancel`, `drop`). Running tasks are in `{ running }`. `starts: true` also sends every start.
4614
- - **Only the top is shown**: handler tasks and the effects they start. Attempts and starters under them are hidden. `depth: 0` — handlers only; `depth: 3` — more; `Infinity` — everything.
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
- reduxDevtools({ name: "app", depth: 0 }); // one line per handler call
4621
- reduxDevtools({ name: "app", starts: true, depth: Infinity }); // everything
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.10.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.86 kB"
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.15 kB"
242
+ "limit": "1.18 kB"
243
243
  },
244
244
  {
245
245
  "name": "dev",
@@ -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};
@@ -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};
@@ -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};