nanoeffects 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/{action-creator-DOWcbXpy.d.ts → action-creator-c292nK7v.d.ts} +115 -33
  2. package/dist/actions.d.ts +18 -4
  3. package/dist/actions.js +1 -1
  4. package/dist/{notrace/context-plugin-B0sLWOxR.d.ts → context-plugin-1pYmSQEG.d.ts} +1 -1
  5. package/dist/dev.d.ts +2 -2
  6. package/dist/effects.d.ts +3 -202
  7. package/dist/index.d.ts +2 -2
  8. package/dist/{module-DCJl-qfi.d.ts → module-BlF2KSgy.d.ts} +19 -4
  9. package/dist/notrace/{action-creator-DOWcbXpy.d.ts → action-creator-DKG_sIia.d.ts} +115 -33
  10. package/dist/notrace/actions.d.ts +45 -5
  11. package/dist/notrace/actions.js +1 -1
  12. package/dist/{context-plugin-B0sLWOxR.d.ts → notrace/context-plugin-Dt1IgVo8.d.ts} +1 -1
  13. package/dist/notrace/effects.d.ts +3 -202
  14. package/dist/notrace/index.d.ts +2 -2
  15. package/dist/notrace/plugin-B0AXKusc.d.ts +207 -0
  16. package/dist/notrace/policies.d.ts +1 -1
  17. package/dist/notrace/redux.d.ts +1 -1
  18. package/dist/observable.d.ts +1 -1
  19. package/dist/plugin-CCYlC0XS.d.ts +207 -0
  20. package/dist/policies.d.ts +1 -1
  21. package/dist/react.d.ts +1 -1
  22. package/dist/redux.d.ts +1 -1
  23. package/dist/scope-key-B7l7mUS5.js +1 -0
  24. package/dist/scope.d.ts +58 -0
  25. package/dist/scope.js +1 -0
  26. package/dist/trace.d.ts +1 -1
  27. package/dist/types-mLaAx-EZ.d.ts +15 -0
  28. package/docs/actions.md +24 -0
  29. package/docs/context.md +2 -0
  30. package/docs/effects.md +3 -2
  31. package/docs/guide.md +22 -0
  32. package/docs/package.md +1 -0
  33. package/docs/scope.md +226 -0
  34. package/llms-full.txt +282 -2
  35. package/llms.txt +1 -0
  36. package/package.json +11 -2
@@ -0,0 +1,207 @@
1
+ import { R as EventMap, c as BoundForm, j as WithContext, p as Bus, u as Effects, v as Context, z as Keys } from "./action-creator-DKG_sIia.js";
2
+ import { n as NoClash } from "./context-plugin-Dt1IgVo8.js";
3
+ //#region src/utils/delay.d.ts
4
+ /**
5
+ * Resolves after `ms`, or rejects early if `ctx.signal` aborts. `ms` can be a
6
+ * function that returns the number of ms, called once when the delay starts:
7
+ * handy for a random pause (jitter).
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * await delay({}, 500);
12
+ *
13
+ * // 200 to 400 ms
14
+ * await delay({}, () => 200 + Math.random() * 200);
15
+ *
16
+ * // inside a task: stops waiting when the task is aborted
17
+ * bus.on("retry", async (_, ctx) => {
18
+ * await delay(ctx, 1000);
19
+ * await load();
20
+ * });
21
+ * ```
22
+ */
23
+ declare const delay: (ctx: {
24
+ signal?: AbortSignal | undefined;
25
+ }, ms: number | (() => number)) => Promise<void>;
26
+ //#endregion
27
+ //#region src/effects/run.d.ts
28
+ /** Context passed into `race` / `all` effect starters. */
29
+ type EffectContext = {
30
+ signal: AbortSignal;
31
+ abort: (reason?: unknown) => void;
32
+ };
33
+ type Starter<T> = (ctx: EffectContext) => T | PromiseLike<T>;
34
+ //#endregion
35
+ //#region src/effects/all.d.ts
36
+ /**
37
+ * Runs all starters; waits for every result. On failure or parent abort, cancels the rest.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * bus.on("load", async (id, ctx) => {
42
+ * const { user, posts } = await all(ctx, {
43
+ * user: ({ signal }) => fetchUser(id, { signal }),
44
+ * posts: ({ signal }) => fetchPosts(id, { signal }),
45
+ * });
46
+ *
47
+ * // arrays work too: const [user, posts] = await all(ctx, [getUser, getPosts]);
48
+ * });
49
+ * ```
50
+ */
51
+ declare function all<T extends Record<string, Starter<unknown>>>(ctx: {
52
+ signal?: AbortSignal;
53
+ }, effects: T): Promise<{ [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
54
+ declare function all<const T extends readonly Starter<unknown>[]>(ctx: {
55
+ signal?: AbortSignal;
56
+ }, effects: T): Promise<{ -readonly [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
57
+ //#endregion
58
+ //#region src/effects/allSettled.d.ts
59
+ /**
60
+ * Runs all starters and waits for every one to settle, whether it succeeds or
61
+ * fails: nothing is cancelled when a starter fails. The result has, for each
62
+ * starter, `{ status: "fulfilled", value }` or `{ status: "rejected", reason }`.
63
+ * Only an abort of `ctx` rejects it, with the abort reason.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * bus.on("load", async (id, ctx) => {
68
+ * const { user, posts } = await allSettled(ctx, {
69
+ * user: ({ signal }) => fetchUser(id, { signal }),
70
+ * posts: ({ signal }) => fetchPosts(id, { signal }),
71
+ * });
72
+ *
73
+ * if (user.status === "fulfilled") render(user.value);
74
+ * });
75
+ * ```
76
+ */
77
+ declare function allSettled<T extends Record<string, Starter<unknown>>>(ctx: {
78
+ signal?: AbortSignal;
79
+ }, effects: T): Promise<{ [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
80
+ declare function allSettled<const T extends readonly Starter<unknown>[]>(ctx: {
81
+ signal?: AbortSignal;
82
+ }, effects: T): Promise<{ -readonly [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
83
+ //#endregion
84
+ //#region src/effects/race.d.ts
85
+ type RaceStarter<T> = Starter<T>;
86
+ /**
87
+ * Runs all starters; first to settle wins, losers are cancelled via `ctx.abort`.
88
+ * The result holds only the winner's value, under its key or index.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * bus.on("load", async (id, ctx) => {
93
+ * const result = await race(ctx, {
94
+ * data: ({ signal }) => fetchData(id, { signal }),
95
+ * timeout: ({ signal }) => delay({ signal }, 5000),
96
+ * });
97
+ *
98
+ * if ("timeout" in result) throw new Error("timed out");
99
+ * render(result.data);
100
+ * });
101
+ * ```
102
+ */
103
+ declare function race<T extends Record<string, RaceStarter<unknown>>>(ctx: {
104
+ signal?: AbortSignal;
105
+ }, effects: T): Promise<{ [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
106
+ declare function race<const T extends readonly RaceStarter<unknown>[]>(ctx: {
107
+ signal?: AbortSignal;
108
+ }, effects: T): Promise<{ -readonly [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
109
+ //#endregion
110
+ //#region src/effects/take.d.ts
111
+ type TakeOptions<P> = {
112
+ /**
113
+ * Wait for the first payload this accepts; others are skipped. A type guard
114
+ * (`(payload): payload is T`) narrows the type of the result to `T`.
115
+ */
116
+ predicate?: (payload: P) => boolean;
117
+ /** Stop waiting when this aborts, e.g. the signal of a `race` starter. */
118
+ signal?: AbortSignal | undefined;
119
+ };
120
+ /**
121
+ * Waits for the next `event` on the bus and resolves with its payload. It sees
122
+ * only events emitted after the call, takes only one, and unsubscribes when it
123
+ * is done. If `ctx` (or `options.signal`) aborts first, it unsubscribes and
124
+ * rejects with the abort reason.
125
+ *
126
+ * @example
127
+ * ```ts
128
+ * bus.on("login", async (user, ctx) => {
129
+ * const reason = await take(ctx, "logout");
130
+ * cleanup(user, reason);
131
+ * });
132
+ *
133
+ * // wait for a matching event, but not longer than 5 s
134
+ * const done = await timeout(ctx, 5000, ({ signal }) =>
135
+ * take(ctx, "saved", { signal, predicate: (id) => id === draftId }),
136
+ * );
137
+ *
138
+ * // a type guard narrows the result: `failed` is `{ ok: false }`
139
+ * const failed = await take(ctx, "result", {
140
+ * predicate: (result): result is { ok: false } => !result.ok,
141
+ * });
142
+ * ```
143
+ */
144
+ declare function take<E extends EventMap, K extends Keys<E>, T extends E[K]>(ctx: Context<E>, event: K, options: TakeOptions<E[K]> & {
145
+ predicate: (payload: E[K]) => payload is T;
146
+ }): Promise<T>;
147
+ declare function take<E extends EventMap, K extends Keys<E>>(ctx: Context<E>, event: K, options?: TakeOptions<E[K]>): Promise<E[K]>;
148
+ //#endregion
149
+ //#region src/effects/plugin.d.ts
150
+ /** The effects that `effectsPlugin` can put on `ctx`. */
151
+ type Supported = {
152
+ race: typeof race;
153
+ all: typeof all;
154
+ allSettled: typeof allSettled;
155
+ take: typeof take;
156
+ delay: typeof delay;
157
+ };
158
+ type Results = Record<string, Starter<unknown>>;
159
+ type List = readonly Starter<unknown>[];
160
+ /** Each supported effect without its leading `ctx` argument. `take` knows the events of the bus. */
161
+ type Bound<E extends EventMap> = {
162
+ race: {
163
+ <T extends Results>(effects: T): Promise<{ [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
164
+ <const T extends List>(effects: T): Promise<{ -readonly [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
165
+ };
166
+ all: {
167
+ <T extends Results>(effects: T): Promise<{ [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
168
+ <const T extends List>(effects: T): Promise<{ -readonly [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
169
+ };
170
+ allSettled: {
171
+ <T extends Results>(effects: T): Promise<{ [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
172
+ <const T extends List>(effects: T): Promise<{ -readonly [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
173
+ };
174
+ take: <K extends Keys<E>>(event: K, options?: TakeOptions<E[K]>) => Promise<E[K]>;
175
+ delay: (ms: number | (() => number)) => Promise<void>;
176
+ };
177
+ /** Any function whose first parameter takes `{ signal }` can go on the context, not only the built-in ones. */
178
+ type Helpers = Effects;
179
+ /**
180
+ * The effects as a handler sees them: the built-in ones keep their hand-written
181
+ * types, any other loses its first parameter, or has the type `Effect<F>` says.
182
+ */
183
+ type BoundOf<H extends Helpers, E extends EventMap> = { [K in keyof H]: K extends keyof Bound<E> ? H[K] extends Supported[K] ? Bound<E>[K] : BoundForm<H[K]> : BoundForm<H[K]>; };
184
+ /**
185
+ * Puts the effects you pass on the context of every task of the bus, bound to
186
+ * that task, so a handler can write `({ race })` instead of `race(ctx, …)`.
187
+ * The key is the name of the effect: `race`, `all`, `allSettled`, `take`,
188
+ * `delay`. Returns the same bus, typed with the new fields.
189
+ *
190
+ * @example
191
+ * ```ts
192
+ * import { createBus } from "nanoeffects";
193
+ * import { all, delay, effectsPlugin, race } from "nanoeffects/effects";
194
+ *
195
+ * const bus = createBus().use(effectsPlugin({ race, delay }));
196
+ *
197
+ * bus.on("load", async (id, { race, delay }) => {
198
+ * const result = await race({
199
+ * data: ({ signal }) => fetchData(id, { signal }),
200
+ * timeout: () => delay(5000),
201
+ * });
202
+ * });
203
+ * ```
204
+ */
205
+ declare const effectsPlugin: <H extends Helpers>(helpers: H) => <E extends EventMap>(bus: Bus<E> & NoClash<BoundOf<H, E>, E>) => Bus<E & WithContext<BoundOf<H, E>>>;
206
+ //#endregion
207
+ export { RaceStarter as a, all as c, delay as d, take as i, EffectContext as l, effectsPlugin as n, race as o, TakeOptions as r, allSettled as s, BoundOf as t, Starter as u };
@@ -1,4 +1,4 @@
1
- import { o as EventMap, u as Task } from "./action-creator-DOWcbXpy.js";
1
+ import { H as Task, R as EventMap } from "./action-creator-DKG_sIia.js";
2
2
  //#region src/policies/keyed.d.ts
3
3
  /** Options shared by every policy that can keep its state per key. */
4
4
  type KeyOption<P> = {
@@ -1,4 +1,4 @@
1
- import { _ as Bus, o as EventMap } from "./action-creator-DOWcbXpy.js";
1
+ import { R as EventMap, p as Bus } from "./action-creator-DKG_sIia.js";
2
2
  //#region src/store/redux/middleware.d.ts
3
3
  /** What Redux passes through a middleware: an action is a plain object with a `type`. */
4
4
  type ReduxAction = {
@@ -1,4 +1,4 @@
1
- import { _ as Bus, o as EventMap, s as Keys } from "./action-creator-DOWcbXpy.js";
1
+ import { B as EventMap, V as Keys, h as Bus } from "./action-creator-c292nK7v.js";
2
2
  //#region src/observable.d.ts
3
3
  declare global {
4
4
  interface SymbolConstructor {
@@ -0,0 +1,207 @@
1
+ import { B as EventMap, N as WithContext, V as Keys, b as Context, d as Effects, h as Bus, l as BoundForm } from "./action-creator-c292nK7v.js";
2
+ import { n as NoClash } from "./context-plugin-1pYmSQEG.js";
3
+ //#region src/utils/delay.d.ts
4
+ /**
5
+ * Resolves after `ms`, or rejects early if `ctx.signal` aborts. `ms` can be a
6
+ * function that returns the number of ms, called once when the delay starts:
7
+ * handy for a random pause (jitter).
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * await delay({}, 500);
12
+ *
13
+ * // 200 to 400 ms
14
+ * await delay({}, () => 200 + Math.random() * 200);
15
+ *
16
+ * // inside a task: stops waiting when the task is aborted
17
+ * bus.on("retry", async (_, ctx) => {
18
+ * await delay(ctx, 1000);
19
+ * await load();
20
+ * });
21
+ * ```
22
+ */
23
+ declare const delay: (ctx: {
24
+ signal?: AbortSignal | undefined;
25
+ }, ms: number | (() => number)) => Promise<void>;
26
+ //#endregion
27
+ //#region src/effects/run.d.ts
28
+ /** Context passed into `race` / `all` effect starters. */
29
+ type EffectContext = {
30
+ signal: AbortSignal;
31
+ abort: (reason?: unknown) => void;
32
+ };
33
+ type Starter<T> = (ctx: EffectContext) => T | PromiseLike<T>;
34
+ //#endregion
35
+ //#region src/effects/all.d.ts
36
+ /**
37
+ * Runs all starters; waits for every result. On failure or parent abort, cancels the rest.
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * bus.on("load", async (id, ctx) => {
42
+ * const { user, posts } = await all(ctx, {
43
+ * user: ({ signal }) => fetchUser(id, { signal }),
44
+ * posts: ({ signal }) => fetchPosts(id, { signal }),
45
+ * });
46
+ *
47
+ * // arrays work too: const [user, posts] = await all(ctx, [getUser, getPosts]);
48
+ * });
49
+ * ```
50
+ */
51
+ declare function all<T extends Record<string, Starter<unknown>>>(ctx: {
52
+ signal?: AbortSignal;
53
+ }, effects: T): Promise<{ [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
54
+ declare function all<const T extends readonly Starter<unknown>[]>(ctx: {
55
+ signal?: AbortSignal;
56
+ }, effects: T): Promise<{ -readonly [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
57
+ //#endregion
58
+ //#region src/effects/allSettled.d.ts
59
+ /**
60
+ * Runs all starters and waits for every one to settle, whether it succeeds or
61
+ * fails: nothing is cancelled when a starter fails. The result has, for each
62
+ * starter, `{ status: "fulfilled", value }` or `{ status: "rejected", reason }`.
63
+ * Only an abort of `ctx` rejects it, with the abort reason.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * bus.on("load", async (id, ctx) => {
68
+ * const { user, posts } = await allSettled(ctx, {
69
+ * user: ({ signal }) => fetchUser(id, { signal }),
70
+ * posts: ({ signal }) => fetchPosts(id, { signal }),
71
+ * });
72
+ *
73
+ * if (user.status === "fulfilled") render(user.value);
74
+ * });
75
+ * ```
76
+ */
77
+ declare function allSettled<T extends Record<string, Starter<unknown>>>(ctx: {
78
+ signal?: AbortSignal;
79
+ }, effects: T): Promise<{ [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
80
+ declare function allSettled<const T extends readonly Starter<unknown>[]>(ctx: {
81
+ signal?: AbortSignal;
82
+ }, effects: T): Promise<{ -readonly [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
83
+ //#endregion
84
+ //#region src/effects/race.d.ts
85
+ type RaceStarter<T> = Starter<T>;
86
+ /**
87
+ * Runs all starters; first to settle wins, losers are cancelled via `ctx.abort`.
88
+ * The result holds only the winner's value, under its key or index.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * bus.on("load", async (id, ctx) => {
93
+ * const result = await race(ctx, {
94
+ * data: ({ signal }) => fetchData(id, { signal }),
95
+ * timeout: ({ signal }) => delay({ signal }, 5000),
96
+ * });
97
+ *
98
+ * if ("timeout" in result) throw new Error("timed out");
99
+ * render(result.data);
100
+ * });
101
+ * ```
102
+ */
103
+ declare function race<T extends Record<string, RaceStarter<unknown>>>(ctx: {
104
+ signal?: AbortSignal;
105
+ }, effects: T): Promise<{ [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
106
+ declare function race<const T extends readonly RaceStarter<unknown>[]>(ctx: {
107
+ signal?: AbortSignal;
108
+ }, effects: T): Promise<{ -readonly [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
109
+ //#endregion
110
+ //#region src/effects/take.d.ts
111
+ type TakeOptions<P> = {
112
+ /**
113
+ * Wait for the first payload this accepts; others are skipped. A type guard
114
+ * (`(payload): payload is T`) narrows the type of the result to `T`.
115
+ */
116
+ predicate?: (payload: P) => boolean;
117
+ /** Stop waiting when this aborts, e.g. the signal of a `race` starter. */
118
+ signal?: AbortSignal | undefined;
119
+ };
120
+ /**
121
+ * Waits for the next `event` on the bus and resolves with its payload. It sees
122
+ * only events emitted after the call, takes only one, and unsubscribes when it
123
+ * is done. If `ctx` (or `options.signal`) aborts first, it unsubscribes and
124
+ * rejects with the abort reason.
125
+ *
126
+ * @example
127
+ * ```ts
128
+ * bus.on("login", async (user, ctx) => {
129
+ * const reason = await take(ctx, "logout");
130
+ * cleanup(user, reason);
131
+ * });
132
+ *
133
+ * // wait for a matching event, but not longer than 5 s
134
+ * const done = await timeout(ctx, 5000, ({ signal }) =>
135
+ * take(ctx, "saved", { signal, predicate: (id) => id === draftId }),
136
+ * );
137
+ *
138
+ * // a type guard narrows the result: `failed` is `{ ok: false }`
139
+ * const failed = await take(ctx, "result", {
140
+ * predicate: (result): result is { ok: false } => !result.ok,
141
+ * });
142
+ * ```
143
+ */
144
+ declare function take<E extends EventMap, K extends Keys<E>, T extends E[K]>(ctx: Context<E>, event: K, options: TakeOptions<E[K]> & {
145
+ predicate: (payload: E[K]) => payload is T;
146
+ }): Promise<T>;
147
+ declare function take<E extends EventMap, K extends Keys<E>>(ctx: Context<E>, event: K, options?: TakeOptions<E[K]>): Promise<E[K]>;
148
+ //#endregion
149
+ //#region src/effects/plugin.d.ts
150
+ /** The effects that `effectsPlugin` can put on `ctx`. */
151
+ type Supported = {
152
+ race: typeof race;
153
+ all: typeof all;
154
+ allSettled: typeof allSettled;
155
+ take: typeof take;
156
+ delay: typeof delay;
157
+ };
158
+ type Results = Record<string, Starter<unknown>>;
159
+ type List = readonly Starter<unknown>[];
160
+ /** Each supported effect without its leading `ctx` argument. `take` knows the events of the bus. */
161
+ type Bound<E extends EventMap> = {
162
+ race: {
163
+ <T extends Results>(effects: T): Promise<{ [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
164
+ <const T extends List>(effects: T): Promise<{ -readonly [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
165
+ };
166
+ all: {
167
+ <T extends Results>(effects: T): Promise<{ [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
168
+ <const T extends List>(effects: T): Promise<{ -readonly [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
169
+ };
170
+ allSettled: {
171
+ <T extends Results>(effects: T): Promise<{ [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
172
+ <const T extends List>(effects: T): Promise<{ -readonly [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
173
+ };
174
+ take: <K extends Keys<E>>(event: K, options?: TakeOptions<E[K]>) => Promise<E[K]>;
175
+ delay: (ms: number | (() => number)) => Promise<void>;
176
+ };
177
+ /** Any function whose first parameter takes `{ signal }` can go on the context, not only the built-in ones. */
178
+ type Helpers = Effects;
179
+ /**
180
+ * The effects as a handler sees them: the built-in ones keep their hand-written
181
+ * types, any other loses its first parameter, or has the type `Effect<F>` says.
182
+ */
183
+ type BoundOf<H extends Helpers, E extends EventMap> = { [K in keyof H]: K extends keyof Bound<E> ? H[K] extends Supported[K] ? Bound<E>[K] : BoundForm<H[K]> : BoundForm<H[K]>; };
184
+ /**
185
+ * Puts the effects you pass on the context of every task of the bus, bound to
186
+ * that task, so a handler can write `({ race })` instead of `race(ctx, …)`.
187
+ * The key is the name of the effect: `race`, `all`, `allSettled`, `take`,
188
+ * `delay`. Returns the same bus, typed with the new fields.
189
+ *
190
+ * @example
191
+ * ```ts
192
+ * import { createBus } from "nanoeffects";
193
+ * import { all, delay, effectsPlugin, race } from "nanoeffects/effects";
194
+ *
195
+ * const bus = createBus().use(effectsPlugin({ race, delay }));
196
+ *
197
+ * bus.on("load", async (id, { race, delay }) => {
198
+ * const result = await race({
199
+ * data: ({ signal }) => fetchData(id, { signal }),
200
+ * timeout: () => delay(5000),
201
+ * });
202
+ * });
203
+ * ```
204
+ */
205
+ declare const effectsPlugin: <H extends Helpers>(helpers: H) => <E extends EventMap>(bus: Bus<E> & NoClash<BoundOf<H, E>, E>) => Bus<E & WithContext<BoundOf<H, E>>>;
206
+ //#endregion
207
+ export { RaceStarter as a, all as c, delay as d, take as i, EffectContext as l, effectsPlugin as n, race as o, TakeOptions as r, allSettled as s, BoundOf as t, Starter as u };
@@ -1,4 +1,4 @@
1
- import { o as EventMap, u as Task } from "./action-creator-DOWcbXpy.js";
1
+ import { B as EventMap, W as Task } from "./action-creator-c292nK7v.js";
2
2
  //#region src/policies/keyed.d.ts
3
3
  /** Options shared by every policy that can keep its state per key. */
4
4
  type KeyOption<P> = {
package/dist/react.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { D as EventKey, _ as Bus, o as EventMap, r as FreeActionCreator, t as ActionCreator } from "./action-creator-DOWcbXpy.js";
1
+ import { B as EventMap, T as EventKey, h as Bus, r as FreeActionCreator, t as ActionCreator } from "./action-creator-c292nK7v.js";
2
2
  //#region src/react/plugin.d.ts
3
3
  /** What `reactPlugin` adds to the bus. */
4
4
  /** Tells whether two values are the same: `Object.is` unless the caller gives another. */
package/dist/redux.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as Bus, o as EventMap } from "./action-creator-DOWcbXpy.js";
1
+ import { B as EventMap, h as Bus } from "./action-creator-c292nK7v.js";
2
2
  //#region src/store/redux/middleware.d.ts
3
3
  /** What Redux passes through a middleware: an action is a plain object with a `type`. */
4
4
  type ReduxAction = {
@@ -0,0 +1 @@
1
+ const e=Symbol.for(`nanoeffects.scope`);export{e as t};
@@ -0,0 +1,58 @@
1
+ import { B as EventMap, a as EmitScope, d as Effects, f as Signaled, h as Bus, o as Scope, u as Effect } from "./action-creator-c292nK7v.js";
2
+ import { n as Scoped } from "./types-mLaAx-EZ.js";
3
+ //#region src/scope/effect.d.ts
4
+ /**
5
+ * Marks a function whose first parameter is `{ signal }` as an effect, so that
6
+ * as a value of a scope it is bound to the task: `ctx.first(items)`
7
+ * calls `first(ctx, items)`. The type is the function without that parameter,
8
+ * generics included, which `effects` cannot do: no annotation is needed, and
9
+ * JavaScript editors show the exact hint.
10
+ *
11
+ * Only `scopePlugin` binds it; the function itself is returned unchanged.
12
+ * Call it where the function is declared, not inline in the scope: TypeScript
13
+ * does not infer the fields of a scope from a generic call written there.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const first = effect(<T>(ctx: { signal: AbortSignal }, items: T[]) => items[0]);
18
+ *
19
+ * const getUser = createCommand(
20
+ * "user/get",
21
+ * (id: string, { first }) => first([id]), // first: <T>(items: T[]) => T
22
+ * { first },
23
+ * );
24
+ * ```
25
+ */
26
+ export declare const effect: <A extends unknown[], R>(fn: (ctx: Signaled, ...args: A) => R) => ((...args: A) => R);
27
+ //#endregion
28
+ //#region src/scope/plugin.d.ts
29
+ /**
30
+ * Switches scopes on for a bus: the scope given to a command, a job, an
31
+ * action or a module (an object whose keys go on `ctx`, with `effects` bound to
32
+ * the task), and the second argument of `emit`, put fields on
33
+ * the context of the tasks they are for. A stronger layer replaces a field of
34
+ * the same name: `emit`, then action / command / job, then module, then the bus.
35
+ * Fields of the library (`signal`, `emit`, `run`, …) are never replaced. Returns
36
+ * the same bus.
37
+ *
38
+ * Use it after `effectsPlugin`, which assigns its effects over those of a scope
39
+ * when it comes later, and before `modulePlugin`, which mounts its modules when
40
+ * it is used. Without this plugin a scope is ignored.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * import { createBus } from "nanoeffects";
45
+ * import { createCommand } from "nanoeffects/actions";
46
+ * import { scopePlugin } from "nanoeffects/scope";
47
+ *
48
+ * const bus = createBus().use(scopePlugin()); // scopes work on this bus
49
+ *
50
+ * const getUser = createCommand("user/get", (id: string, { http }) => http.get(id), {
51
+ * http, // a field of this command only
52
+ * });
53
+ * bus.emit(getUser("1"), { http: fakeHttp }); // this call only
54
+ * ```
55
+ */
56
+ export declare const scopePlugin: () => <E extends EventMap>(bus: Bus<E>) => Bus<E>;
57
+ //#endregion
58
+ export type { Effect, Effects, EmitScope, Scope, Scoped, Signaled };
package/dist/scope.js ADDED
@@ -0,0 +1 @@
1
+ import{t as e}from"./scope-key-B7l7mUS5.js";const t=/* @__PURE__ */ new WeakSet,n=e=>(t.add(e),e),r=/* @__PURE__ */ new Set([`signal`,`abort`,`emit`,`on`,`off`,`run`,`type`,`payload`]),i=Symbol(`nanoeffects.scope.entry`),a=[],o=/* @__PURE__ */ new WeakSet,s=(e,n)=>{if(!n)return;for(let i of Object.keys(n)){let a=n[i];if(i!==`effects`&&!r.has(i)){let n=a;e[i]=typeof a==`function`&&t.has(a)?c(e,n):a}}let i=n.effects??{};for(let t of Object.keys(i)){let n=i[t];r.has(t)||(e[t]=c(e,n))}},c=(e,t)=>(...n)=>t(e,...n),l=(e,t)=>{s(e,t.module);for(let n of t.call)s(e,n)},u=t=>{if(typeof t==`object`&&t||typeof t==`function`)return Reflect.get(t,e)},d=e=>typeof e==`object`&&!!e,f=(e,t)=>{let n=u(e),r=d(e)?t[0]:void 0;if(!(n||r))return;let i=n&&r?[n,r]:[n??r];return{type:typeof e==`string`?e:e.type,payload:d(e)?e.payload:t[0],layers:i}},p=()=>e=>{let t,n=e=>{let n=e.run;if(o.has(n))return;let a=(a,...o)=>{let s=t;t=void 0;try{return n((t,n)=>{let o=e[i];return o&&r(n,o.call,o.module),a(t,n)},...o)}finally{t=s}};o.add(a),e.run=a},r=(e,t,r)=>{let a={call:t,module:r};e[i]=a,l(e,a),n(e)},s=e.addContext;e.addContext=e=>{s?.(e);let n=e;t&&n.type===t.type&&Object.is(n.payload,t.payload)&&r(n,t.layers,void 0)},e.applyScope=(e,t)=>{let n=e,o=n[i]?.call??a;r(n,o,t)},e.fitScope=(t,n)=>(r,i)=>(e.applyScope?.(i,t),Reflect.apply(n,null,[r,i]));let c=e.emit;return e.emit=(e,...n)=>{let r=f(e,n);if(!(r||t))return c(e,...n);let i=t;t=r;try{return d(e)?c(e):c(e,...n)}finally{t=i}},e};export{n as effect,p as scopePlugin};
package/dist/trace.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { d as Trace, f as TraceEvent, p as TraceLogger, z as Tracer } from "./action-creator-DOWcbXpy.js";
1
+ import { G as Trace, K as TraceEvent, L as Tracer, q as TraceLogger } from "./action-creator-c292nK7v.js";
2
2
  //#region src/trace/console.d.ts
3
3
  type ConsoleLoggerOptions = {
4
4
  /** Defaults to `globalThis.console`. */
@@ -0,0 +1,15 @@
1
+ import { B as EventMap, d as Effects, o as Scope, s as WithFields } from "./action-creator-c292nK7v.js";
2
+ import { t as BoundOf } from "./plugin-CCYlC0XS.js";
3
+ //#region src/scope/types.d.ts
4
+ type BoundEffects<E extends EventMap, S extends Scope> = S extends {
5
+ effects: infer H extends Effects;
6
+ } ? BoundOf<H, E> : unknown;
7
+ /** The fields a scope gives a task: its values as they are and its `effects` bound; `unknown` for an empty scope. */
8
+ type FieldsOfScope<E extends EventMap, S extends Scope> = [keyof S] extends [never] ? unknown : Omit<S, "effects"> & BoundEffects<E, S>;
9
+ /**
10
+ * The event map `E` with the fields of a scope on top of its context fields.
11
+ * A field the scope sets replaces the one of the bus in the type too.
12
+ */
13
+ type Scoped<E extends EventMap, S extends Scope> = WithFields<E, FieldsOfScope<E, S>>;
14
+ //#endregion
15
+ export { Scoped as n, FieldsOfScope as t };
package/docs/actions.md CHANGED
@@ -152,3 +152,27 @@ app.dispose(); // unmounts every module
152
152
  - `use` — bring sub-modules and commands that carry a handler; the returned function undoes that call.
153
153
  - `spawn` — background task, aborted on unmount.
154
154
  - A command that carries a handler keeps answering after unmount (`use` only tracks registration). Use `handle` for a handler that must live only as long as the module.
155
+
156
+ ## Give a command, an action or a module its own fields
157
+
158
+ A command can need its own client, a module its own logger. Pass a scope, a plain object whose keys go on `ctx` (`effects` binds functions to the task), as the last argument of `createCommand`, `createAction`, `createModule` and `createJob`, and turn it on with `scopePlugin`:
159
+
160
+ ```js
161
+ import { createBus } from "nanoeffects";
162
+ import { createCommand } from "nanoeffects/actions";
163
+ import { scopePlugin } from "nanoeffects/scope";
164
+
165
+ // scopePlugin — makes the scope of a command work on this bus
166
+ const bus = createBus().use(scopePlugin());
167
+
168
+ // the third argument — fields of this command only
169
+ const getUser = createCommand("user/get", (id, { api }) => api.getUser(id), {
170
+ api,
171
+ });
172
+
173
+ // a test replaces them for one call
174
+ const fakeApi = { getUser: async (id) => ({ id, name: "test" }) };
175
+ bus.emit(getUser("1"), { api: fakeApi });
176
+ ```
177
+
178
+ The handler gets the fields typed. Without `scopePlugin` the scope is ignored. The full picture, with jobs, actions, modules and which value wins, is in [scopes](scope.md).
package/docs/context.md CHANGED
@@ -21,6 +21,8 @@ Library fields (`signal`, `emit`, `run`, …) win on name clashes. `context` may
21
21
 
22
22
  To get `race`, `all`, `take` and the like on every task without passing `ctx`, see [`effectsPlugin`](effects.md#put-selected-effects-on-ctx).
23
23
 
24
+ To give fields to **one** command, action, job or module, or to replace them for one `emit`, see [scopes](scope.md).
25
+
24
26
  ## Type the fields (optional)
25
27
 
26
28
  In TypeScript, `contextPlugin` puts the fields on every task of **this** bus and takes their types from the value you pass. The event map holds only events. It does not wire anything across the app — another bus can have different fields or none.
package/docs/effects.md CHANGED
@@ -166,8 +166,9 @@ bus.on("load", async (id, { race, all, delay }) => {
166
166
  });
167
167
  ```
168
168
 
169
- - Supported: `race`, `all`, `allSettled`, `take`, `delay`. The key is the effect name.
170
- - `retry`, `timeout` and `poll` are not supported: bound to the task they would look like their handler wrappers.
169
+ - The key is the name the effect gets on `ctx`. `race`, `all`, `allSettled`, `take` and `delay` are typed by hand.
170
+ - Your own function works too if its first parameter is `{ signal }` (an *effect*): `effectsPlugin({ fetchJson })` gives `ctx.fetchJson(url)`. TypeScript removes the first parameter from its type; for a generic function see [keep a generic effect generic](scope.md#keep-a-generic-effect-generic).
171
+ - Leave `retry`, `timeout` and `poll` out: bound to the task they would look like their handler wrappers.
171
172
  - Fields from `createBus({ context })` stay. TypeScript rejects an effect whose name is already a context field of the bus, including one added by an earlier `effectsPlugin`.
172
173
  - Free forms keep working: `race(ctx, …)`.
173
174
 
package/docs/guide.md CHANGED
@@ -47,6 +47,28 @@ bus.on("tick", () => {});
47
47
  bus.off(); // clear every subscription
48
48
  ```
49
49
 
50
+ ### Listen to every event
51
+
52
+ `bus.on("*", fn)` runs for every event of the bus. It is for logging, debugging, or forwarding events elsewhere. The handler gets the payload and the task context as usual, and `ctx.type` is the name of the event.
53
+
54
+ ```js
55
+ import { createBus } from "nanoeffects";
56
+
57
+ const bus = createBus();
58
+
59
+ // "*" — runs for every event, after the listeners of that event
60
+ const unbind = bus.on("*", (payload, ctx) => {
61
+ console.log(ctx.type, payload);
62
+ });
63
+
64
+ bus.emit("greet", "Ada"); // greet Ada
65
+ bus.emit("moved", { x: 1, y: 2 }); // moved { x: 1, y: 2 }
66
+ unbind();
67
+ ```
68
+
69
+ - An event that only `"*"` listens to counts as heard, so `onUnhandled` does not fire for it.
70
+ - `bus.off("*")` removes every `"*"` listener; `bus.off()` clears the whole bus, `"*"` included.
71
+
50
72
  ## 2. Use the handler context
51
73
 
52
74
  The second argument is the **task** context for this call. Emit further events through it. Pass `ctx.signal` to `fetch` when you cancel work.
package/docs/package.md CHANGED
@@ -8,6 +8,7 @@ Imports are split so you only load what you use. `createBus` stays small; the re
8
8
  | `nanoeffects/policies` | `latest`, `leading`, `debounce`, `throttle`, `queue` |
9
9
  | `nanoeffects/effects` | `retry`, `timeout`, `poll`, `delay`, `take`, `all`, `allSettled`, `race`, `effectsPlugin` |
10
10
  | `nanoeffects/actions` | `createAction`, `createCommand`, `createJob`, `createModule`, `modulePlugin`, `commandsPlugin` |
11
+ | `nanoeffects/scope` | `scopePlugin`, `effect`: fields for one command, action, job, module or `emit` (the types `Scope`, `Effect`) |
11
12
  | `nanoeffects/redux` | `createEffectsMiddleware` |
12
13
  | `nanoeffects/observable` | `createObserve` |
13
14
  | `nanoeffects/react` | `reactPlugin`: adds the `useEmitter` hook to a bus (needs React 18 or newer) |