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.
- package/dist/{action-creator-DOWcbXpy.d.ts → action-creator-c292nK7v.d.ts} +115 -33
- package/dist/actions.d.ts +18 -4
- package/dist/actions.js +1 -1
- package/dist/{notrace/context-plugin-B0sLWOxR.d.ts → context-plugin-1pYmSQEG.d.ts} +1 -1
- package/dist/dev.d.ts +2 -2
- package/dist/effects.d.ts +3 -202
- package/dist/index.d.ts +2 -2
- package/dist/{module-DCJl-qfi.d.ts → module-BlF2KSgy.d.ts} +19 -4
- package/dist/notrace/{action-creator-DOWcbXpy.d.ts → action-creator-DKG_sIia.d.ts} +115 -33
- package/dist/notrace/actions.d.ts +45 -5
- package/dist/notrace/actions.js +1 -1
- package/dist/{context-plugin-B0sLWOxR.d.ts → notrace/context-plugin-Dt1IgVo8.d.ts} +1 -1
- package/dist/notrace/effects.d.ts +3 -202
- package/dist/notrace/index.d.ts +2 -2
- package/dist/notrace/plugin-B0AXKusc.d.ts +207 -0
- package/dist/notrace/policies.d.ts +1 -1
- package/dist/notrace/redux.d.ts +1 -1
- package/dist/observable.d.ts +1 -1
- package/dist/plugin-CCYlC0XS.d.ts +207 -0
- package/dist/policies.d.ts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/redux.d.ts +1 -1
- package/dist/scope-key-B7l7mUS5.js +1 -0
- package/dist/scope.d.ts +58 -0
- package/dist/scope.js +1 -0
- package/dist/trace.d.ts +1 -1
- package/dist/types-mLaAx-EZ.d.ts +15 -0
- package/docs/actions.md +24 -0
- package/docs/context.md +2 -0
- package/docs/effects.md +3 -2
- package/docs/guide.md +22 -0
- package/docs/package.md +1 -0
- package/docs/scope.md +226 -0
- package/llms-full.txt +282 -2
- package/llms.txt +1 -0
- 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 {
|
|
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> = {
|
package/dist/notrace/redux.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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 = {
|
package/dist/observable.d.ts
CHANGED
|
@@ -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 };
|
package/dist/policies.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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 {
|
|
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};
|
package/dist/scope.d.ts
ADDED
|
@@ -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 {
|
|
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
|
-
-
|
|
170
|
-
- `
|
|
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) |
|