nanoeffects 0.3.0 → 0.5.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 (60) hide show
  1. package/README.md +53 -55
  2. package/dist/{action-Bdfp-Wvd.d.ts → action-creator-DOWcbXpy.d.ts} +79 -73
  3. package/dist/actions.d.ts +75 -6
  4. package/dist/actions.js +1 -1
  5. package/dist/bus-BAs9x7IQ.js +1 -0
  6. package/dist/{context-DlA80GOA.js → context-CK0ZomWt.js} +1 -1
  7. package/dist/context-plugin-B0sLWOxR.d.ts +34 -0
  8. package/dist/dev.d.ts +2 -2
  9. package/dist/effects.d.ts +153 -56
  10. package/dist/effects.js +1 -1
  11. package/dist/index.d.ts +3 -2
  12. package/dist/index.js +1 -1
  13. package/dist/{module-I1ixyJzx.d.ts → module-DCJl-qfi.d.ts} +24 -9
  14. package/dist/notrace/{action-Bdfp-Wvd.d.ts → action-creator-DOWcbXpy.d.ts} +79 -73
  15. package/dist/notrace/actions.d.ts +97 -13
  16. package/dist/notrace/actions.js +1 -1
  17. package/dist/notrace/bus-Bdk0YyFQ.js +1 -0
  18. package/dist/notrace/{context-yAWj0BFZ.js → context-CctVmHLC.js} +1 -1
  19. package/dist/notrace/context-plugin-B0sLWOxR.d.ts +34 -0
  20. package/dist/notrace/{delay-Cgvn16oY.js → delay-B_W1tnkS.js} +1 -1
  21. package/dist/notrace/effects.d.ts +153 -56
  22. package/dist/notrace/effects.js +1 -1
  23. package/dist/notrace/index.d.ts +3 -2
  24. package/dist/notrace/index.js +1 -1
  25. package/dist/notrace/policies.d.ts +1 -1
  26. package/dist/notrace/policies.js +1 -1
  27. package/dist/notrace/redux.d.ts +18 -20
  28. package/dist/notrace/redux.js +1 -1
  29. package/dist/observable.d.ts +1 -1
  30. package/dist/policies.d.ts +1 -1
  31. package/dist/policies.js +1 -1
  32. package/dist/react.d.ts +50 -0
  33. package/dist/react.js +1 -0
  34. package/dist/redux.d.ts +18 -20
  35. package/dist/redux.js +1 -1
  36. package/dist/trace.d.ts +1 -1
  37. package/docs/README.md +30 -9
  38. package/docs/actions.md +117 -41
  39. package/docs/context.md +48 -7
  40. package/docs/effects.md +229 -73
  41. package/docs/errors.md +23 -10
  42. package/docs/guide.md +238 -129
  43. package/docs/hmr.md +25 -13
  44. package/docs/migrating-from-effector.md +293 -0
  45. package/docs/migrating-from-rxjs.md +127 -0
  46. package/docs/migrating-from-saga.md +93 -0
  47. package/docs/observable.md +11 -9
  48. package/docs/package.md +24 -0
  49. package/docs/policies.md +93 -30
  50. package/docs/react.md +157 -0
  51. package/docs/semantics.md +5 -3
  52. package/docs/stores.md +488 -25
  53. package/docs/tracing.md +69 -54
  54. package/docs/typescript.md +153 -0
  55. package/llms-full.txt +2645 -0
  56. package/llms.txt +48 -0
  57. package/package.json +39 -9
  58. package/dist/bus-CRnlwmQ4.js +0 -1
  59. package/dist/notrace/bus-Dx0l9X6h.js +0 -1
  60. package/dist/notrace/event-0RIBGYqS.js +0 -1
package/README.md CHANGED
@@ -5,93 +5,91 @@
5
5
  [![size](https://img.shields.io/bundlephobia/minzip/nanoeffects)](https://bundlephobia.com/package/nanoeffects)
6
6
  [![license](https://img.shields.io/npm/l/nanoeffects)](LICENSE)
7
7
 
8
- A small typed event bus. Handlers are tasks with an `AbortSignal`, and policies (`latest`, `debounce`, `throttle`, `queue`) and effects (`retry`, `timeout`) are plain functions that go straight into `on`.
8
+ A small event bus for JavaScript apps. Each handler runs as a **task** with an `AbortSignal`, so you can cancel in-flight work. Policies (`latest`, `debounce`, …) and effects (`retry`, `timeout`, …) are plain functions you pass to `on`.
9
9
 
10
- ```ts
11
- import { createBus } from "nanoeffects";
10
+ ## Install
12
11
 
13
- type Events = { greet: string };
12
+ ```bash
13
+ npm install nanoeffects
14
+ ```
15
+
16
+ ESM only, no dependencies, types included.
14
17
 
15
- const bus = createBus<Events>();
18
+ Want to try it first? [Open the playground](https://stackblitz.com/github/nanoeffects/nanoeffects/tree/main/examples/playground): a search box with `debounce` and `latest`, running in your browser, no install. More playgrounds (React) and recipes: [Examples](examples/README.md).
16
19
 
17
- const unbind = bus.on("greet", (name) => console.log(`hello ${name}`));
20
+ ## Create a bus
21
+
22
+ Subscribe with `on`, send with `emit`. `on` returns a function that unsubscribes.
23
+
24
+ ```js
25
+ import { createBus } from "nanoeffects";
26
+
27
+ const bus = createBus(); // event bus; each handler call is a task
28
+
29
+ const unbind = bus.on("greet", (name) => {
30
+ console.log(`hello ${name}`);
31
+ });
18
32
 
19
33
  bus.emit("greet", "Ada"); // hello Ada
20
- unbind(); // `on` returns the unsubscribe function, as in nanoevents
34
+ unbind();
21
35
  ```
22
36
 
23
- `emit` takes the event as a string, as an object `{ type, payload }` (a Redux action fits as it is), or as a function with a `type`, a command that runs and returns its result:
37
+ That is enough for a small app or a widget. The handler’s second argument is a task context (`signal`, `emit`, …). See the [Guide](docs/guide.md).
24
38
 
25
- ```ts
26
- bus.emit("greet", "Ada");
27
- bus.emit({ type: "greet", payload: "Ada" });
28
- bus.emit(double, 21); // a command: runs, returns 42
29
- ```
39
+ ## Abort overlapping work
30
40
 
31
- Then the same bus with a policy, no other change:
41
+ In a SPA, a newer search should abort the older one. Wrap the handler in `latest` and pass `ctx.signal` to `fetch` (and similar APIs).
32
42
 
33
- ```ts
43
+ ```js
44
+ import { createBus } from "nanoeffects";
34
45
  import { latest } from "nanoeffects/policies";
35
46
 
36
- // a newer search aborts the older one
47
+ const bus = createBus();
48
+
49
+ // latest — a newer run aborts the older one
37
50
  bus.on(
38
51
  "search",
39
52
  latest(async (query, ctx) => {
53
+ // signal — lets latest cancel this request, not only drop its result
40
54
  const items = await api.search(query, { signal: ctx.signal });
41
55
  ctx.emit("results", items);
42
56
  }),
43
57
  );
44
58
  ```
45
59
 
46
- - **Tiny.** `createBus` is about 1.2 kB gzip; every other entry is bundled only when imported.
47
- - **Cancellation first.** Aborting a task aborts its children and `ctx.on` subscriptions and drops its result.
48
- - **One model** instead of `AbortController`, lodash `debounce`, `p-queue` and a hand-written retry.
49
- - **Typed** commands, actions, jobs and modules; **traced** with Redux DevTools; the trace can be cut out of the build.
50
- - **Checked** against lodash on random call sequences, and against known issues of RxJS, p-queue, p-retry and effector.
60
+ Same `on` / `emit`. Replace `api.search` with your own request.
51
61
 
52
- ## Install
62
+ ## Get a result from `emit`
53
63
 
54
- ```bash
55
- npm install nanoeffects
56
- ```
64
+ A **command** is like an event, but `emit` runs its handler and returns the result (a promise if the handler is async).
57
65
 
58
- ESM only, no dependencies, types included.
59
-
60
- ## Entries
61
-
62
- | Import | What is in it |
63
- | ------------------------ | ----------------------------------------------------------------------------- |
64
- | `nanoeffects` | `createBus`, the types |
65
- | `nanoeffects/policies` | `latest`, `leading`, `debounce`, `throttle`, `queue` |
66
- | `nanoeffects/effects` | `retry`, `timeout`, `poll`, `delay`, `take`, `all`, `allSettled`, `race` |
67
- | `nanoeffects/actions` | `createAction`, `createCommand`, `createJob`, `createModule`, `modulePlugin` |
68
- | `nanoeffects/redux` | `createEffectsMiddleware` |
69
- | `nanoeffects/observable` | `createObserve`: an event as an Observable (RxJS) |
70
- | `nanoeffects/dev` | `devPlugin`: warnings about listeners that pile up and messages about modules; a no-op in production |
71
- | `nanoeffects/trace` | `createTracer` and the loggers |
66
+ ```js
67
+ import { createBus } from "nanoeffects";
68
+ import { createCommand } from "nanoeffects/actions";
72
69
 
73
- ## The task context
70
+ const bus = createBus();
74
71
 
75
- `bus.on(event, handler)` calls `handler(payload, ctx)`. Each call is a task:
72
+ // createCommand — emit runs the handler and returns what it returns
73
+ const double = createCommand("math/double", (value) => value * 2);
76
74
 
77
- | `ctx` | Meaning |
78
- | ----------------- | -------------------------------------------------------- |
79
- | `signal`, `abort` | the signal of the task, and a way to abort it |
80
- | `emit` | emit as this task; does nothing after an abort |
81
- | `on`, `off` | subscribe; the subscription ends when the task aborts |
82
- | `run(fn, ...)` | a child task, aborted with this one |
83
- | `type`, `payload` | the event the task works on |
75
+ const answer = bus.emit(double(21)); // 42
76
+ ```
84
77
 
85
- The event map (`type Events = { name: Payload }`) is optional: `createBus()` takes any string event with an `unknown` payload. `createAction("user/loaded")` takes its payload type from the map, and `createAction<User>("user/loaded")` gives it when there is none.
78
+ More (contracts, jobs, modules): [Actions](docs/actions.md).
86
79
 
87
80
  ## Docs
88
81
 
89
- [**Guide**](docs/guide.md) · [Policies](docs/policies.md) · [Effects](docs/effects.md) · [Actions, commands, modules](docs/actions.md) · [Context fields](docs/context.md) · [Errors](docs/errors.md) · [Tracing](docs/tracing.md) · [Redux, Zustand, MobX](docs/stores.md) · [Observable](docs/observable.md) · [Hot reload and dev diagnostics](docs/hmr.md) · [Semantics](docs/semantics.md) · [Examples](examples)
82
+ Start with the [Guide](docs/guide.md) (bus → policies → effects → modules). Or jump by need:
90
83
 
91
- ## Development
84
+ | Need | Page |
85
+ | ---- | ---- |
86
+ | Async UI races (search, submit) | [Policies](docs/policies.md) |
87
+ | Retry, timeout, wait for events | [Effects](docs/effects.md) |
88
+ | Domain logic off the UI | [Actions, commands, modules](docs/actions.md) |
89
+ | Redux / Zustand / MobX | [Stores](docs/stores.md) |
92
90
 
93
- ```bash
94
- pnpm verify # what CI runs: types, lint, tests, build, integration, package checks, size
95
- ```
91
+ [Full index, package entries, tracing](docs/README.md) · [Examples](examples/README.md)
96
92
 
97
- [`AGENTS.md`](AGENTS.md) describes the structure and the rules of the code, for people and AI assistants.
93
+ - **Tiny.** `createBus` is about 1.3 kB gzip; other entries load only when imported.
94
+ - **Cancellation first.** Aborting a task aborts its children and drops its result.
95
+ - **One model** instead of hand-rolled `AbortController`, lodash debounce, and retry glue.
@@ -1,29 +1,3 @@
1
- //#region src/core/types.d.ts
2
- type PayloadOf<P> = undefined extends P ? [payload?: P] : [payload: P];
3
- type EventMap = Record<string, unknown>;
4
- type Keys<E> = keyof E & string;
5
- type Task<P, E extends EventMap, R = unknown> = (payload: P, ctx: Context<E>) => R;
6
- type EffectName = "every" | "latest" | "leading" | "debounce" | "throttle" | "run" | "all" | "race" | "retry" | "timeout" | "poll" | "take" | "queue" | "allSettled" | "command";
7
- type Phase = "start" | "done" | "error" | "cancel" | "drop";
8
- type TraceEvent<E = Record<string, unknown>> = { [K in Keys<E>]: {
9
- id: number;
10
- parent?: number;
11
- type: K;
12
- effect: EffectName;
13
- phase: Phase;
14
- payload: E[K];
15
- error?: unknown;
16
- }; }[Keys<E>];
17
- /**
18
- * The contract of every trace logger: receives each task phase of a bus.
19
- * Adapters in `nanoeffects/trace` are factories that return one; pass it
20
- * through `createTracer` to `createBus({ trace })`.
21
- */
22
- type TraceLogger<E = Record<string, unknown>> = (event: TraceEvent<E>) => void;
23
- /** One logger or several; a throwing logger never breaks the task or the others. */
24
- type Trace<E = Record<string, unknown>> = TraceLogger<E> | readonly TraceLogger<E>[];
25
- type UnsubscribeCallback = () => void;
26
- //#endregion
27
1
  //#region src/core/store.d.ts
28
2
  declare const anyEventType = "*";
29
3
  //#endregion
@@ -68,21 +42,29 @@ type Action<E extends EventMap> = {
68
42
  type: EventKey<E>;
69
43
  payload: EventPayload<E>;
70
44
  };
71
- type WithPayload<E extends EventMap, K extends EventKey<E>> = undefined extends E[K] ? {
72
- type: K;
73
- payload?: E[K];
74
- } : {
75
- type: K;
76
- payload: E[K];
77
- };
78
45
  type EventLike<E extends EventMap> = { [K in EventKey<E>]: WithPayload<E, K>; }[Keys<E>];
79
46
  /**
80
- * A typed request for a result. `bus.emit(command, payload)` runs the handler
81
- * of the command as a task and returns what it returns. Made by
47
+ * Brands `{ type, payload }` from a command call so `emit` can run the command.
48
+ * Not enumerable on the object.
49
+ */
50
+ declare const commandCall: unique symbol;
51
+ /**
52
+ * What `command(payload)` returns: an action-shaped value that `emit` runs as
53
+ * that command. A plain `{ type, payload }` without this brand is notify-only.
54
+ */
55
+ type CommandCall<P = unknown, R = unknown> = {
56
+ type: string;
57
+ payload: P;
58
+ readonly [commandCall]: Command<P, R>;
59
+ };
60
+ /**
61
+ * A typed request for a result. Calling it builds `{ type, payload }` like
62
+ * `createAction`; `bus.emit(command(payload))` (or `bus.emit(command, payload)`)
63
+ * runs the handler as a task and returns what it returns. Made by
82
64
  * `createCommand` in `nanoeffects/actions`.
83
65
  */
84
66
  type Command<P = never, R = unknown> = {
85
- (payload: P, ctx?: never): R;
67
+ (payload: P): CommandCall<P, R>;
86
68
  readonly type: string;
87
69
  /** Wraps the handler that really runs, e.g. to route its outcome. */
88
70
  readonly wrap?: (run: (payload: never, ctx: never) => unknown) => (payload: never, ctx: never) => unknown;
@@ -96,11 +78,13 @@ interface Commands {
96
78
  run(command: Command, payload: unknown, ctx: unknown): unknown;
97
79
  }
98
80
  /** Anything `emit` takes as its first argument: an event type, an action or a command. */
99
- type Emittable<E extends EventMap> = EventKey<E> | Command | EventLike<E>;
81
+ type Emittable<E extends EventMap> = EventKey<E> | Command | CommandCall | EventLike<E>;
100
82
  type Emit<E extends EventMap> = {
101
83
  <K extends EventKey<E>>(event: K, ...payload: PayloadOf<E[K]>): void;
102
- <K extends EventKey<E>, R>(event: Handler<E, R, K>, ...payload: PayloadOf<E[K]>): R;
84
+ /** Before Handler: a command is callable too, and must return its result type. */
103
85
  <P, R>(command: Command<P, R>, ...payload: PayloadOf<P>): R;
86
+ <P, R>(call: CommandCall<P, R>): R;
87
+ <K extends EventKey<E>, R>(event: Handler<E, R, K>, ...payload: PayloadOf<E[K]>): R;
104
88
  (event: EventLike<E>): void;
105
89
  };
106
90
  type On<E extends EventMap> = {
@@ -119,6 +103,8 @@ type On<E extends EventMap> = {
119
103
  }, fn: Task<E[K], E>): UnsubscribeCallback;
120
104
  };
121
105
  type Off<E extends EventMap> = {
106
+ /** Removes every listener of every event (including `"*"`). */
107
+ (): void;
122
108
  (event: AnyEvent): void;
123
109
  (event: AnyEvent, fn: Task<EventPayload<E>, E>): void;
124
110
  <K extends EventKey<E>>(event: K): void;
@@ -171,8 +157,25 @@ type Context<E extends EventMap> = EventLike<E> & ContextOf<E> & {
171
157
  /** Child task: aborted with the parent; errors propagate to the parent. */
172
158
  run<P, R>(fn: Task<P, E, R>, ...args: PayloadOf<P>): Promise<Awaited<R>>;
173
159
  };
160
+ /**
161
+ * The event map of a bus, with the context fields that plugins added. Use it to
162
+ * name the types of a bus in other files: `type AppEvents = EventsOf<typeof bus>`.
163
+ *
164
+ * @example
165
+ * ```ts
166
+ * const bus = createBus<Events>().use(effectsPlugin({ race }));
167
+ * type AppEvents = EventsOf<typeof bus>;
168
+ * type AppContext = Context<AppEvents>; // has `race`
169
+ * ```
170
+ */
171
+ type EventsOf<B> = B extends Bus<infer E extends EventMap> ? E : never;
174
172
  interface Bus<E extends EventMap> {
175
173
  on: On<E>;
174
+ /**
175
+ * Like `on`, but the handler runs at most once: it is removed before it is
176
+ * invoked, so a nested emit of the same event does not re-enter it.
177
+ */
178
+ once: On<E>;
176
179
  off: Off<E>;
177
180
  emit: Emit<E>;
178
181
  /**
@@ -182,10 +185,12 @@ interface Bus<E extends EventMap> {
182
185
  */
183
186
  use<R>(plugin: (bus: Bus<E>) => R): R;
184
187
  /** `BusOptions.context` and `.trace`, and `commands`, for what makes tasks of its own, like `modulePlugin`. */
185
- readonly context?: ContextOption<E> | undefined;
188
+ readonly ctx?: ContextOption<E> | undefined;
186
189
  /** What runs the commands; `commandsPlugin` and `modulePlugin` set it. Without one a command runs its own handler. */
187
190
  commands?: Commands | undefined;
188
191
  readonly trace?: Tracer | undefined;
192
+ /** Called with every new task context to add fields to it, for what needs the task, like `effectsPlugin`. Chain to the previous one. */
193
+ addContext?: ((ctx: object) => void) | undefined;
189
194
  }
190
195
  type BusOptions<E extends EventMap> = {
191
196
  /** Tracing of every task phase; make it with `createTracer` from `nanoeffects/trace`. */
@@ -223,7 +228,40 @@ type BusOptions<E extends EventMap> = {
223
228
  };
224
229
  declare const createBus: <Events extends EventMap>(options?: BusOptions<Events>) => Bus<Events>;
225
230
  //#endregion
226
- //#region src/actions/action.d.ts
231
+ //#region src/core/types.d.ts
232
+ type PayloadOf<P> = undefined extends P ? [payload?: P] : [payload: P];
233
+ type EventMap = Record<string, unknown>;
234
+ type Keys<E> = keyof E & string;
235
+ type Task<P, E extends EventMap, R = unknown> = (payload: P, ctx: Context<E>) => R;
236
+ type EffectName = "every" | "latest" | "leading" | "debounce" | "throttle" | "run" | "all" | "race" | "retry" | "timeout" | "poll" | "take" | "queue" | "allSettled" | "command";
237
+ type Phase = "start" | "done" | "error" | "cancel" | "drop";
238
+ type TraceEvent<E = Record<string, unknown>> = { [K in Keys<E>]: {
239
+ id: number;
240
+ parent?: number;
241
+ type: K;
242
+ effect: EffectName;
243
+ phase: Phase;
244
+ payload: E[K];
245
+ error?: unknown;
246
+ }; }[Keys<E>];
247
+ /**
248
+ * The contract of every trace logger: receives each task phase of a bus.
249
+ * Adapters in `nanoeffects/trace` are factories that return one; pass it
250
+ * through `createTracer` to `createBus({ trace })`.
251
+ */
252
+ type TraceLogger<E = Record<string, unknown>> = (event: TraceEvent<E>) => void;
253
+ /** One logger or several; a throwing logger never breaks the task or the others. */
254
+ type Trace<E = Record<string, unknown>> = TraceLogger<E> | readonly TraceLogger<E>[];
255
+ type UnsubscribeCallback = () => void;
256
+ //#endregion
257
+ //#region src/core/action-creator.d.ts
258
+ type WithPayload<E extends EventMap, K extends Keys<E>> = undefined extends E[K] ? {
259
+ type: K;
260
+ payload?: E[K];
261
+ } : {
262
+ type: K;
263
+ payload: E[K];
264
+ };
227
265
  type Absent<P> = [P] extends [void] ? undefined : P;
228
266
  type Made<P, T extends string> = WithPayload<{ [K in T]: Absent<P>; }, T>;
229
267
  declare const free: unique symbol;
@@ -267,37 +305,5 @@ interface ActionCreator<P = void, T extends string = string, A extends unknown[]
267
305
  }): action is Made<P, T>;
268
306
  toString(): T;
269
307
  }
270
- /**
271
- * Creates an action creator, as in Redux Toolkit.
272
- *
273
- * - `createAction("user/loaded")` needs no generics: the payload type comes
274
- * from the event map of the bus that the action goes to, and the literal
275
- * type of the action is kept.
276
- * - `createAction<User>("user/loaded")` gives the payload type, for a bus
277
- * without an event map or to check the calls of the creator. The type of
278
- * the action is then `string`; to keep the literal, give it as the second
279
- * generic: `createAction<User, "user/loaded">("user/loaded")`.
280
- * - With `prepare` the arguments of the creator can differ from the payload,
281
- * and both types are inferred.
282
- *
283
- * @example
284
- * ```ts
285
- * const added = createAction<number>("counter/added");
286
- * added(2); // { type: "counter/added", payload: 2 }
287
- * added.type; // "counter/added"
288
- * added.match(action); // true for actions of this type
289
- *
290
- * const renamed = createAction("user/renamed", (id: string, name: string) => ({
291
- * payload: { id, name },
292
- * }));
293
- *
294
- * bus.emit(added(2));
295
- * bus.on(added, (n) => console.log(n));
296
- * ```
297
- */
298
- declare function createAction<P = Free, T extends string = string>(type: T): [P] extends [Free] ? FreeActionCreator<T> : ActionCreator<P, T>;
299
- declare function createAction<A extends unknown[], P, T extends string = string>(type: T, prepare: (...args: A) => {
300
- payload: P;
301
- }): ActionCreator<P, T, A>;
302
308
  //#endregion
303
- export { Keys as A, WithPayload as C, anyEventType as D, Tracer as E, TraceEvent as F, TraceLogger as I, UnsubscribeCallback as L, Phase as M, Task as N, EffectName as O, Trace as P, WithContext as S, createBus as T, EventPayload as _, AnyEvent as a, Off as b, Command as c, ContextOf as d, ContextOption as f, EventLike as g, EventKey as h, Action as i, PayloadOf as j, EventMap as k, Commands as l, Emittable as m, FreeActionCreator as n, Bus as o, Emit as p, createAction as r, BusOptions as s, ActionCreator as t, Context as u, Handler as v, WithType as w, On as x, HandlerRef as y };
309
+ export { EventsOf as A, anyEventType as B, ContextOf as C, EventKey as D, Emittable as E, WithContext as F, WithType as I, commandCall as L, HandlerRef as M, Off as N, EventLike as O, On as P, createBus as R, Context as S, Emit as T, Bus as _, EffectName as a, CommandCall as b, PayloadOf as c, Trace as d, TraceEvent as f, AnyEvent as g, Action as h, WithPayload as i, Handler as j, EventPayload as k, Phase as l, UnsubscribeCallback as m, Free as n, EventMap as o, TraceLogger as p, FreeActionCreator as r, Keys as s, ActionCreator as t, Task as u, BusOptions as v, ContextOption as w, Commands as x, Command as y, Tracer as z };
package/dist/actions.d.ts CHANGED
@@ -1,5 +1,74 @@
1
- import { N as Task, k as EventMap, n as FreeActionCreator, r as createAction, t as ActionCreator } from "./action-Bdfp-Wvd.js";
2
- import { a as modulePlugin, c as bindHandler, i as createModule, l as commandsPlugin, n as ModuleApi, o as Binding, r as ModuleContext, s as MadeCommand, t as Module, u as createCommand } from "./module-I1ixyJzx.js";
1
+ import { D as EventKey, _ as Bus, c as PayloadOf, n as Free, o as EventMap, r as FreeActionCreator, t as ActionCreator, u as Task } from "./action-creator-DOWcbXpy.js";
2
+ import { a as modulePlugin, c as bindHandler, i as createModule, l as commandsPlugin, n as ModuleApi, o as Binding, r as ModuleContext, s as MadeCommand, t as Module, u as createCommand } from "./module-DCJl-qfi.js";
3
+ //#region src/actions/action.d.ts
4
+ /**
5
+ * Creates an action creator, as in Redux Toolkit.
6
+ *
7
+ * - `createAction("user/loaded")` needs no generics: the payload type comes
8
+ * from the event map of the bus that the action goes to, and the literal
9
+ * type of the action is kept.
10
+ * - `createAction<User>("user/loaded")` gives the payload type, for a bus
11
+ * without an event map or to check the calls of the creator. The type of
12
+ * the action is then `string`; to keep the literal, give it as the second
13
+ * generic: `createAction<User, "user/loaded">("user/loaded")`.
14
+ * - With `prepare` the arguments of the creator can differ from the payload,
15
+ * and both types are inferred.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const added = createAction<number>("counter/added");
20
+ * added(2); // { type: "counter/added", payload: 2 }
21
+ * added.type; // "counter/added"
22
+ * added.match(action); // true for actions of this type
23
+ *
24
+ * const renamed = createAction("user/renamed", (id: string, name: string) => ({
25
+ * payload: { id, name },
26
+ * }));
27
+ *
28
+ * bus.emit(added(2));
29
+ * bus.on(added, (n) => console.log(n));
30
+ * ```
31
+ */
32
+ export declare function createAction<P = Free, T extends string = string>(type: T): [P] extends [Free] ? FreeActionCreator<T> : ActionCreator<P, T>;
33
+ export declare function createAction<A extends unknown[], P, T extends string = string>(type: T, prepare: (...args: A) => {
34
+ payload: P;
35
+ }): ActionCreator<P, T, A>;
36
+ //#endregion
37
+ //#region src/actions/emitter.d.ts
38
+ /** What `emitterPlugin` adds to the bus. */
39
+ type EmitterApi<E extends EventMap> = {
40
+ /**
41
+ * Turns an action creator into a function that emits its action on this
42
+ * bus, to pass as a callback. The function takes what the creator takes.
43
+ */
44
+ emitter: {
45
+ /** A creator without a payload type: the payload comes from the event map. */
46
+ <K extends EventKey<E>>(creator: FreeActionCreator<K>): (...payload: PayloadOf<E[K]>) => void;
47
+ <P, T extends string, A extends unknown[]>(creator: ActionCreator<P, T, A>): (...args: A) => void;
48
+ };
49
+ };
50
+ /**
51
+ * Adds `bus.emitter(creator)`: a function that emits the action of `creator`
52
+ * on this bus. Use it where something wants a callback, such as the
53
+ * subscription of a state manager, instead of `(value) => bus.emit(creator(value))`.
54
+ * The creator stays a plain `createAction`, so `bus.emit(creator(value))` and
55
+ * `bus.on(creator, …)` work as before. Returns the same bus.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * import { createBus } from "nanoeffects";
60
+ * import { createAction, emitterPlugin } from "nanoeffects/actions";
61
+ *
62
+ * const bus = createBus().use(emitterPlugin());
63
+ * const nameChanged = createAction<string>("name/changed");
64
+ * const emitNameChanged = bus.emitter(nameChanged);
65
+ *
66
+ * // nanostores: the store calls it with the new value
67
+ * $name.listen(emitNameChanged);
68
+ * ```
69
+ */
70
+ export declare const emitterPlugin: () => <E extends EventMap>(bus: Bus<E>) => Bus<E> & EmitterApi<E>;
71
+ //#endregion
3
72
  //#region src/actions/job.d.ts
4
73
  type Creator = {
5
74
  (...args: never[]): {
@@ -29,13 +98,13 @@ type Job<P, R, T extends string, S extends Outcome, F extends Outcome> = MadeCom
29
98
  * is used and returned as it is. A missing one is made with the type
30
99
  * `<type>/success` or `<type>/fail`.
31
100
  *
32
- * `bus.emit(command, payload)` returns a promise of the result. An error of the
101
+ * `bus.emit(job(payload))` returns a promise of the result. An error of the
33
102
  * handler always counts as handled: it goes to `job.fail` and the promise
34
103
  * resolves with `undefined`; a command is the tool for a result that must reject.
35
104
  * The actions are emitted as the task that ran the job, also for a handler bound
36
105
  * with `commands.handle`, and are skipped once the task is aborted. A missing
37
106
  * handler is not an outcome: `emit` throws `MissingHandlerError`. Calling the
38
- * command directly runs only its handler.
107
+ * job builds `{ type, payload }` like a command; it does not run the handler.
39
108
  *
40
109
  * @example
41
110
  * ```ts
@@ -45,7 +114,7 @@ type Job<P, R, T extends string, S extends Outcome, F extends Outcome> = MadeCom
45
114
  * });
46
115
  *
47
116
  * bus.on(loadUser.success, (user) => render(user));
48
- * bus.emit(loadUser, "1"); // loadUser.success(user) or loadUser.fail(error) comes later
117
+ * bus.emit(loadUser("1")); // loadUser.success(user) or loadUser.fail(error) comes later
49
118
  * ```
50
119
  */
51
120
  export declare const createJob: <P = void, R = void, T extends string = string, E extends EventMap = EventMap, S extends Outcome = `${T}/success`, F extends Outcome = `${T}/fail`>(type: T, handler?: Task<P, E, R>, outcomes?: {
@@ -55,4 +124,4 @@ export declare const createJob: <P = void, R = void, T extends string = string,
55
124
  fail?: F;
56
125
  }) => Job<P, R, T, S, F>;
57
126
  //#endregion
58
- export { type ActionCreator, type Binding, type FreeActionCreator, type Job, type Module, type ModuleApi, type ModuleContext, bindHandler, commandsPlugin, createAction, createCommand, createModule, modulePlugin };
127
+ export { type ActionCreator, type Binding, type EmitterApi, type FreeActionCreator, type Job, type Module, type ModuleApi, type ModuleContext, bindHandler, commandsPlugin, createCommand, createModule, modulePlugin };
package/dist/actions.js CHANGED
@@ -1 +1 @@
1
- import{t as e}from"./onAbort-BgMfspK4.js";import{i as t,n}from"./context-DlA80GOA.js";import{t as r}from"./event-0RIBGYqS.js";function i(e,t){return Object.assign((...n)=>({type:e,payload:t?t(...n).payload:n[0]}),{type:e,match:t=>t.type===e,toString:()=>e})}var a=class extends Error{};const o=(e,t)=>Object.assign((r,i)=>{if(!t)throw new a(`No handler for command "${e}"`);return t(r,i??n({event:{type:e,payload:r}}).ctx)},{type:e,match:t=>t.type===e}),s=(e,t)=>[e,t],c=(e=[])=>t=>{let n=/* @__PURE__ */ new Map,i=(e,t)=>{let r={fn:t};return n.set(e,[...n.get(e)??[],r]),()=>{let t=(n.get(e)??[]).filter(e=>e!==r);t.length>0?n.set(e,t):n.delete(e)}};function a(e,t){if(t)return i(r(e),t);let n=e.map(([e,t])=>i(r(e),t));return()=>{for(let e of n)e()}}a(e);let o={run:(e,t,r)=>{let i=(n.get(e.type)??[]).at(-1)?.fn,a=i&&e.wrap?e.wrap(i):i??e;return Reflect.apply(a,null,[t,r])},handle:a};return t.commands=o,o},l=e=>typeof e==`string`?i(e):e,u=(e,t,n)=>{let r=e.emit;r(Reflect.apply(t,null,[n]))},d=e=>{try{return Promise.resolve(e())}catch(e){if(e instanceof a)throw e;return Promise.reject(e)}},f=(e,t,n={})=>{let r=o(e,t),i=l(n.success??`${e}/success`),a=l(n.fail??`${e}/fail`),s=(e,t,n)=>{let r=n;return d(()=>Reflect.apply(e,null,[t,n])).then(e=>(u(r,i,e),e),e=>{u(r,a,e)})};return Object.assign((e,t)=>t?s(r,e,t):d(()=>r(e)),{type:e,match:r.match,wrap:e=>(t,n)=>s(e,t,n),success:i,fail:a})},p=(e,t)=>({name:e,setup:t}),m=(r=[])=>i=>{let a=i.commands??c()(i),o={handle:(...e)=>{if(!a.handle)throw Error("modulePlugin: the `commands` of the bus is not a registry from `commandsPlugin()`");return Reflect.apply(a.handle,null,e)}},s=n({event:{type:`app`,payload:void 0},bus:i,tracer:i.trace}),l=n=>{let r=/* @__PURE__ */ new Map,i=/* @__PURE__ */ new Map;return{mount:a=>{let s=r.get(a);if(s)return s;let c=i.get(a.name);c&&r.get(c)?.();let u=t(n,a.name),d=u.ctx,f=l(d),p=()=>{r.delete(a),i.get(a.name)===a&&i.delete(a.name),d.abort(),u.unlink()};r.set(a,p),i.set(a.name,a);let m=(t,n)=>{let r=Reflect.apply(o.handle,null,[t,n]);return e(d,r),r},h=(...e)=>{let t=e.map(e=>typeof e==`function`?m(e,e):f.mount(e));return()=>{for(let e of t)e()}},g=(e,...t)=>{d.run(e,...t).catch(e=>{if(!d.signal.aborted)throw e})};try{a.setup(Object.assign(d,{handle:m,use:h,spawn:g}))}catch(e){throw p(),e}return p},unmount:e=>r.get(e)?.(),isMounted:e=>r.has(e)}},u=l(s.ctx);for(let e of r)u.mount(e);return{commands:a,mount:e=>(s.ctx.signal.throwIfAborted(),u.mount(e)),unmount:u.unmount,isMounted:u.isMounted,dispose:()=>s.ctx.abort()}};export{s as bindHandler,c as commandsPlugin,i as createAction,o as createCommand,f as createJob,p as createModule,m as modulePlugin};
1
+ import{t as e}from"./onAbort-BgMfspK4.js";import{i as t,n}from"./context-CK0ZomWt.js";import{t as r}from"./event-0RIBGYqS.js";import{r as i}from"./bus-BAs9x7IQ.js";function a(e,t){return Object.assign((...n)=>({type:e,payload:t?t(...n).payload:n[0]}),{type:e,match:t=>t.type===e,toString:()=>e})}var o=class extends Error{};const s=(e,t)=>{let n=t=>t.type===e;function r(n,r){if(typeof r==`object`){if(!t)throw new o(`No handler for command "${e}"`);return t(n,r)}return i(a,e,n)}let a=Object.assign(r,{type:e,match:n});return a},c=(e,t)=>[e,t],l=(e=[])=>t=>{let n=/* @__PURE__ */ new Map,i=(e,t)=>{let r={fn:t};return n.set(e,[...n.get(e)??[],r]),()=>{let t=(n.get(e)??[]).filter(e=>e!==r);t.length>0?n.set(e,t):n.delete(e)}};function a(e,t){if(t)return i(r(e),t);let n=e.map(([e,t])=>i(r(e),t));return()=>{for(let e of n)e()}}a(e);let o={run:(e,t,r)=>{let i=(n.get(e.type)??[]).at(-1)?.fn,a=i&&e.wrap?e.wrap(i):i??e;return Reflect.apply(a,null,[t,r])},handle:a};return t.commands=o,o},u=()=>e=>{function t(t){return(...n)=>{let r=t(...n);e.emit(r)}}return Object.assign(e,{emitter:t})},d=e=>typeof e==`string`?a(e):e,f=(e,t,n)=>{let r=e.emit;r(Reflect.apply(t,null,[n]))},p=e=>{try{return Promise.resolve(e())}catch(e){if(e instanceof o)throw e;return Promise.reject(e)}},m=(e,t,n={})=>{let r=s(e,t),a=d(n.success??`${e}/success`),o=d(n.fail??`${e}/fail`),c=(e,t,n)=>{let r=n;return p(()=>Reflect.apply(e,null,[t,n])).then(e=>(f(r,a,e),e),e=>{f(r,o,e)})},l=e=>(t,n)=>c(e,t,n);function u(t,n){return typeof n==`object`?c(r,t,n):i(m,e,t)}let m=Object.assign(u,{type:e,match:r.match,wrap:l,success:a,fail:o});return m},h=(e,t)=>({name:e,setup:t}),g=(r=[])=>i=>{let a=i.commands??l()(i),o={handle:(...e)=>{if(!a.handle)throw Error("modulePlugin: the `commands` of the bus is not a registry from `commandsPlugin()`");return Reflect.apply(a.handle,null,e)}},s=n({event:{type:`app`,payload:void 0},bus:i,tracer:i.trace}),c=n=>{let r=/* @__PURE__ */ new Map,i=/* @__PURE__ */ new Map;return{mount:a=>{let s=r.get(a);if(s)return s;let l=i.get(a.name);l&&r.get(l)?.();let u=t(n,a.name),d=u.ctx,f=c(d),p=()=>{r.delete(a),i.get(a.name)===a&&i.delete(a.name),d.abort(),u.unlink()};r.set(a,p),i.set(a.name,a);let m=(t,n)=>{let r=Reflect.apply(o.handle,null,[t,n]);return e(d,r),r},h=(...e)=>{let t=e.map(e=>typeof e==`function`?m(e,e):f.mount(e));return()=>{for(let e of t)e()}},g=(e,...t)=>{d.run(e,...t).catch(e=>{if(!d.signal.aborted)throw e})};try{a.setup(Object.assign(d,{handle:m,use:h,spawn:g}))}catch(e){throw p(),e}return p},unmount:e=>r.get(e)?.(),isMounted:e=>r.has(e)}},u=c(s.ctx);for(let e of r)u.mount(e);return{commands:a,mount:e=>(s.ctx.signal.throwIfAborted(),u.mount(e)),unmount:u.unmount,isMounted:u.isMounted,dispose:()=>s.ctx.abort(),createModule:h}};export{c as bindHandler,l as commandsPlugin,a as createAction,s as createCommand,m as createJob,h as createModule,u as emitterPlugin,g as modulePlugin};
@@ -0,0 +1 @@
1
+ import{n as e}from"./onAbort-BgMfspK4.js";import{a as t,n,r}from"./context-CK0ZomWt.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(`nanoeffects.commandCall`),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 i,p as n,u as r,l as t};
@@ -1 +1 @@
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=typeof __NANOEFFECTS_TRACE__>`u`||__NANOEFFECTS_TRACE__,r=/* @__PURE__ */ new WeakMap;let i;const a=()=>i,o=()=>{},s=(e,t,i,a)=>{let o=n&&r.get(e);o&&o.tracer.report(o.ref,t,i,a)},c=(e,t)=>{let i=n&&r.get(e);return i?i.tracer.settler(i.ref,t):o},l=(e,t,i)=>{let a=n&&r.get(e);return a?a.tracer.run(a.ref,t,e.signal,i):i()},u=(e,t)=>typeof e==`function`?e(t):e,d=e=>e,f=({event:a,bus:s,tracer:c,parent:p})=>{let{controller:m,unlink:h}=t(p?.signal),{signal:g}=m,_=n&&c?{tracer:c,ref:c.task(a,p?.ref)}:void 0,v=(...e)=>{if(g.aborted)return;let t=s?.emit,n=i;i=_?.ref;try{return t?.(...e)}finally{i=n}},y=(...t)=>{if(g.aborted)return o;let n=s?.on,r=n?.(...t);if(!r)return o;let i=e({signal:g},r);return()=>{i(),r()}},b=async(e,...t)=>{g.throwIfAborted();let n=t[0],r=f({event:{type:a.type,payload:n},bus:s,tracer:c,parent:{signal:g,ref:_?.ref}});try{return await l(r.ctx,`run`,()=>e(n,r.ctx))}finally{r.unlink()}},x=d({...u(s?.context,a),type:a.type,payload:a.payload,signal:g,abort:e=>m.abort(e),emit:v,on:y,off:s?.off??o,run:b});return _&&r.set(x,_),{ctx:x,unlink:h}},p=(e,t)=>{let i=n?r.get(e):void 0;return f({event:{type:t??e.type??``,payload:e.payload},bus:{emit:e.emit,on:e.on,off:e.off,context:e},tracer:i?.tracer,parent:{signal:e.signal,ref:i?.ref}})};export{l as a,p as i,f as n,s as o,a as r,c as t};
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=typeof __NANOEFFECTS_TRACE__>`u`||__NANOEFFECTS_TRACE__,r=/* @__PURE__ */ new WeakMap;let i;const a=()=>i,o=()=>{},s=(e,t,i,a)=>{let o=n&&r.get(e);o&&o.tracer.report(o.ref,t,i,a)},c=(e,t)=>{let i=n&&r.get(e);return i?i.tracer.settler(i.ref,t):o},l=(e,t,i)=>{let a=n&&r.get(e);return a?a.tracer.run(a.ref,t,e.signal,i):i()},u=(e,t)=>typeof e==`function`?e(t):e,d=e=>e,f=({event:a,bus:s,tracer:c,parent:p})=>{let{controller:m,unlink:h}=t(p?.signal),{signal:g}=m,_=n&&c?{tracer:c,ref:c.task(a,p?.ref)}:void 0,v=(...e)=>{if(g.aborted)return;let t=s?.emit,n=i;i=_?.ref;try{return t?.(...e)}finally{i=n}},y=(...t)=>{if(g.aborted)return o;let n=s?.on,r=n?.(...t);if(!r)return o;let i=e({signal:g},r);return()=>{i(),r()}},b=async(e,...t)=>{g.throwIfAborted();let n=t[0],r=f({event:{type:a.type,payload:n},bus:s,tracer:c,parent:{signal:g,ref:_?.ref}});try{return await l(r.ctx,`run`,()=>e(n,r.ctx))}finally{r.unlink()}},x=d({...u(s?.ctx,a),type:a.type,payload:a.payload,signal:g,abort:e=>m.abort(e),emit:v,on:y,off:s?.off??o,run:b});return s?.addContext?.(x),_&&r.set(x,_),{ctx:x,unlink:h}},p=(e,t)=>{let i=n?r.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:i?.tracer,parent:{signal:e.signal,ref:i?.ref}})};export{l as a,p as i,f as n,s as o,a as r,c as t};
@@ -0,0 +1,34 @@
1
+ import { C as ContextOf, F as WithContext, _ as Bus, o as EventMap } from "./action-creator-DOWcbXpy.js";
2
+ //#region src/core/context-plugin.d.ts
3
+ /** What a function that makes the fields of a task gets to see. */
4
+ type ContextTask = {
5
+ type: string;
6
+ payload?: unknown;
7
+ };
8
+ /**
9
+ * `unknown` when none of the `fields` is already a context field of the bus,
10
+ * else `never`: the call does not compile. Used as `bus: Bus<E> & NoClash<…>`.
11
+ */
12
+ type NoClash<Fields extends object, E extends EventMap> = [keyof Fields & keyof ContextOf<E>] extends [never] ? unknown : never;
13
+ /**
14
+ * Puts `fields` on the context of every task of the bus: a store, a client, a
15
+ * logger. The types come from the value, so the event map needs no
16
+ * `WithContext`. Returns the same bus, typed with the fields. A function gets
17
+ * `{ type, payload }` of the task and returns its fields.
18
+ *
19
+ * Several uses merge with each other, with the `context` option of `createBus`
20
+ * and with `effectsPlugin`. A name that the bus already has is a type error. The
21
+ * fields of the library (`signal`, `emit`, `run`, …) win, and a field that the
22
+ * context already has is not replaced.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * type Events = { "user/loaded": User };
27
+ *
28
+ * const bus = createBus<Events>().use(contextPlugin({ store }));
29
+ * bus.on("user/loaded", (user, { store }) => store.put(user)); // ctx.store: Store
30
+ * ```
31
+ */
32
+ declare const contextPlugin: <Fields extends object>(fields: Fields | ((task: ContextTask) => Fields)) => <E extends EventMap>(bus: Bus<E> & NoClash<Fields, E>) => Bus<E & WithContext<Fields>>;
33
+ //#endregion
34
+ export { NoClash as n, contextPlugin as r, ContextTask as t };
package/dist/dev.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { L as UnsubscribeCallback, k as EventMap, o as Bus } from "./action-Bdfp-Wvd.js";
2
- import { n as ModuleApi } from "./module-I1ixyJzx.js";
1
+ import { _ as Bus, m as UnsubscribeCallback, o as EventMap } from "./action-creator-DOWcbXpy.js";
2
+ import { n as ModuleApi } from "./module-DCJl-qfi.js";
3
3
  //#region src/dev/types.d.ts
4
4
  /** The parts of what `modulePlugin` returned that the diagnostics report. */
5
5
  type ReportedApp<E extends EventMap> = Pick<ModuleApi<E>, "mount" | "unmount" | "isMounted">;