nanoeffects 0.4.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/README.md +4 -1
- package/dist/{action-creator-D48fp13T.d.ts → action-creator-c292nK7v.d.ts} +130 -34
- package/dist/actions.d.ts +54 -5
- package/dist/actions.js +1 -1
- package/dist/{bus-Cho463Sq.js → bus-BAs9x7IQ.js} +1 -1
- package/dist/{context-DlA80GOA.js → context-CK0ZomWt.js} +1 -1
- package/dist/context-plugin-1pYmSQEG.d.ts +34 -0
- package/dist/dev.d.ts +2 -2
- package/dist/effects.d.ts +27 -129
- package/dist/effects.js +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -1
- package/dist/{module-DVX8aYlq.d.ts → module-BlF2KSgy.d.ts} +34 -5
- package/dist/notrace/{action-creator-D48fp13T.d.ts → action-creator-DKG_sIia.d.ts} +130 -34
- package/dist/notrace/actions.d.ts +96 -7
- package/dist/notrace/actions.js +1 -1
- package/dist/notrace/{bus-B12SvxLv.js → bus-Bdk0YyFQ.js} +1 -1
- package/dist/notrace/{context-yAWj0BFZ.js → context-CctVmHLC.js} +1 -1
- package/dist/notrace/context-plugin-Dt1IgVo8.d.ts +34 -0
- package/dist/notrace/{delay-Cgvn16oY.js → delay-B_W1tnkS.js} +1 -1
- package/dist/notrace/effects.d.ts +27 -129
- package/dist/notrace/effects.js +1 -1
- package/dist/notrace/index.d.ts +3 -2
- package/dist/notrace/index.js +1 -1
- package/dist/notrace/plugin-B0AXKusc.d.ts +207 -0
- package/dist/notrace/policies.d.ts +1 -1
- package/dist/notrace/policies.js +1 -1
- package/dist/notrace/redux.d.ts +18 -20
- package/dist/notrace/redux.js +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/policies.js +1 -1
- package/dist/react.d.ts +50 -0
- package/dist/react.js +1 -0
- package/dist/redux.d.ts +18 -20
- package/dist/redux.js +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/README.md +7 -1
- package/docs/actions.md +25 -1
- package/docs/context.md +26 -2
- package/docs/effects.md +83 -5
- package/docs/guide.md +24 -0
- package/docs/migrating-from-effector.md +293 -0
- package/docs/migrating-from-rxjs.md +127 -0
- package/docs/migrating-from-saga.md +93 -0
- package/docs/observable.md +1 -0
- package/docs/package.md +13 -2
- package/docs/policies.md +1 -0
- package/docs/react.md +157 -0
- package/docs/scope.md +226 -0
- package/docs/stores.md +429 -40
- package/docs/typescript.md +153 -0
- package/llms-full.txt +2925 -0
- package/llms.txt +49 -0
- package/package.json +44 -6
|
@@ -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/policies.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{t as e}from"./onAbort-BgMfspK4.js";import{a as t,i as n,o as r,t as i}from"./context-
|
|
1
|
+
import{t as e}from"./onAbort-BgMfspK4.js";import{a as t,i as n,o as r,t as i}from"./context-CK0ZomWt.js";import{n as a,r as o,t as s}from"./delay-CIHnOh_L.js";import{n as c,t as l}from"./promise-CSLpzwSR.js";const u=()=>{},d=(e,t)=>{let n=/* @__PURE__ */ new Map;return(r,i)=>{let a=e(r),o=n.get(a);if(!o){let e=t(()=>{n.get(a)===e&&n.delete(a)});o=e,n.set(a,e)}return o(r,i)}},f=(e,t)=>e?d(e,t):t(u),p=(e,t={})=>f(t.key,t=>m(`latest`,(t,n,r)=>(r(),e(t,n)),t)),m=(t,a,o=u)=>{let s=null;return(l,u)=>{if(s?.abort(),u.signal.aborted)return;let{ctx:d,unlink:f}=n(u);s=d;let p=i(d,t),m=!1,h=()=>{d.signal.aborted||(m=!0,r(d,t,`start`))};e(d,()=>p(m?`cancel`:`drop`));let g=()=>{f(),s===d&&(s=null,o())},_=e=>{if(!d.signal.aborted)return p(m?`done`:`drop`),e};try{let e=a(l,d,h);return c(e)?Promise.resolve(e).then(_,e=>{if(!d.signal.aborted)throw p(`error`,e),e}).finally(g):(g(),_(e))}catch(e){if(g(),d.signal.aborted)return;throw p(`error`,e),e}}},h=(e,t,n={})=>f(n.key,r=>_(e,t,n,r)),g=(e,n,i,s)=>{let c=s!==u,l=null,d=null;return(u,f)=>{if(f.signal.aborted)return;let p=Date.now(),m=l===null||p-l>=e||p-l<0;return l=p,d!==null&&a(d),d=c?o(s,e):null,i&&m?Promise.resolve(t(f,`debounce`,()=>n(u,f))):(r(f,`debounce`,`drop`),Promise.resolve(void 0))}},_=(n,r,i,a)=>{let o=i.leading??!1,c=i.trailing??!0;if(!c)return g(n,r,o,a);let l=!1,u=0,d=m(`debounce`,([t,i],a,o)=>{let d=u;return e(a,()=>{u===d&&(l=!1)}),s(a,n).then(()=>{if(l=!1,c&&!i)return o(),r(t,a)})},a);return(e,n)=>{if(n.signal.aborted)return;let i=o&&!l;l=!0,u++;let a=d([e,i],n);return i?Promise.resolve(t(n,`debounce`,()=>r(e,n))):a}},v=(n,i={})=>f(i.key,i=>{let a=null;return(o,s)=>{if(s.signal.aborted)return;if(a){r(s,`leading`,`drop`);return}let c={};a=c;let u=()=>{d(),a===c&&(a=null,i())},d=e(s,u);try{return l(t(s,`leading`,()=>n(o,s)),u)}catch(e){throw u(),e}}}),y=(e,t={})=>f(t.key,n=>b(e,t,n)),b=(n,i,a)=>{let o=i.concurrency??1,s=i.limit??1/0,c=[],l=0,d=()=>{for(;l<o;){let e=c.shift();if(!e)return;e.start()}};return(f,p)=>new Promise((m,h)=>{if(p.signal.aborted){m(void 0);return}let g=u,_=()=>{g(),r(p,`queue`,`drop`),m(void 0)},v=()=>{if(g(),p.signal.aborted){_();return}l++;let r=!0,i=()=>{r&&(r=!1,l--,d(),l===0&&c.length===0&&a())},o=e(p,()=>{i(),m(void 0)}),s;try{s=Promise.resolve(t(p,`queue`,()=>n(f,p)))}catch(e){s=Promise.reject(e)}let u=()=>{o(),i()};s.then(e=>{u(),m(p.signal.aborted?void 0:e)},e=>{u(),p.signal.aborted?m(void 0):h(e)})};if(l<o){v();return}if(c.length>=s){if(i.overflow!==`drop-old`||s<=0){_();return}c.shift()?.drop()}let y={start:v,drop:_};c.push(y),g=e(p,()=>{let e=c.indexOf(y);e>=0&&c.splice(e,1),_()})})},x=(e,t,n={})=>f(n.key,r=>S(e,t,n,r)),S=(e,n,i,s)=>{let c=i.leading??!0,l=i.trailing??!0,d=s!==u,f=null,p=0,m=null,h=null,g=null,_=!1,v=t=>f===null||t-f>=e||t-f<0||t-p>=e,y=e=>r(e,`throttle`,`drop`),b=()=>{let e=g;g=null,e&&(y(e.ctx),e.resolve())},x=(e,t)=>(b(),new Promise((n,r)=>{g={payload:e,ctx:t,resolve:n,reject:r}})),S=(e,r,i)=>{b(),p=i,_=!0;try{return t(r,`throttle`,()=>n(e,r))}finally{_=!1}},C=()=>{d&&(h=o(w,e))},w=()=>{if(h=null,!d||m!==null||g)return;let t=Date.now(),n=Math.max(e-(t-(f??-1/0)),e-(t-p));n<=0?s():h=o(w,n)},T=e=>{m=null;let t=g;if(l&&t&&!t.ctx.signal.aborted){g=null;try{t.resolve(S(t.payload,t.ctx,e))}catch(e){t.reject(e)}}else b();w()},E=()=>{let t=Date.now();v(t)?T(t):m=o(E,Math.min(e-(t-(f??t)),e-(t-p)))},D=()=>{h!==null&&(a(h),h=null)},O=(e,t)=>{if(!l)y(t);else return x(e,t)},k=(e,t,n,r)=>{if(!r)y(t);else if(p=n,C(),!c)y(t);else return S(e,t,n)},A=(t,n,r,i)=>i&&m!==null?(a(m),m=o(E,e),S(t,n,r)):m===null&&(m=o(E,e),i&&(p=r,c))?S(t,n,r):O(t,n);return(e,t)=>{if(t.signal.aborted)return;let n=Date.now(),r=!_&&v(n);return f=n,D(),l?A(e,t,n,r):k(e,t,n,r)}};export{h as debounce,p as latest,v as leading,y as queue,x as throttle};
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { B as EventMap, T as EventKey, h as Bus, r as FreeActionCreator, t as ActionCreator } from "./action-creator-c292nK7v.js";
|
|
2
|
+
//#region src/react/plugin.d.ts
|
|
3
|
+
/** What `reactPlugin` adds to the bus. */
|
|
4
|
+
/** Tells whether two values are the same: `Object.is` unless the caller gives another. */
|
|
5
|
+
type Equals<V> = (previous: V, next: V) => boolean;
|
|
6
|
+
type ReactApi<E extends EventMap> = {
|
|
7
|
+
/**
|
|
8
|
+
* A React hook: after a render commits with a `value` that differs from the
|
|
9
|
+
* previous one (`Object.is`, or the `equals` you pass), emits `event` with it. It does not emit on mount,
|
|
10
|
+
* not twice under StrictMode, and not for an equal value. Changes before one
|
|
11
|
+
* commit emit once, with the last value. The method does not read `this`, so
|
|
12
|
+
* `export const { useEmitter } = bus` gives a free hook.
|
|
13
|
+
*/
|
|
14
|
+
useEmitter: {
|
|
15
|
+
<K extends EventKey<E>>(event: K, value: E[K], equals?: Equals<E[K]>): void;
|
|
16
|
+
/** A creator without a payload type: the payload comes from the event map. */
|
|
17
|
+
<K extends EventKey<E>>(creator: FreeActionCreator<K>, value: E[K], equals?: Equals<E[K]>): void;
|
|
18
|
+
<P, T extends string>(creator: ActionCreator<P, T>, value: P, equals?: Equals<P>): void;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Adds `bus.useEmitter(event, value)`: a hook that tells the bus when a value of
|
|
23
|
+
* a component changes, without the traps of `useEffect(() => emit(value), [value])`
|
|
24
|
+
* (an emit on mount, two on mount under StrictMode, a new `emit` re-running it).
|
|
25
|
+
* Pass `equals` as the third argument for values that `Object.is` tells apart
|
|
26
|
+
* although they are the same, such as a new object with the same fields.
|
|
27
|
+
* Needs `react` 18 or newer. Returns the same bus.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* import { createBus } from "nanoeffects";
|
|
32
|
+
* import { createAction } from "nanoeffects/actions";
|
|
33
|
+
* import { reactPlugin } from "nanoeffects/react";
|
|
34
|
+
*
|
|
35
|
+
* export const bus = createBus().use(reactPlugin());
|
|
36
|
+
* export const { useEmitter } = bus; // a free hook
|
|
37
|
+
*
|
|
38
|
+
* const nameChanged = createAction<string>("name/changed");
|
|
39
|
+
* bus.on(nameChanged, (name) => console.log(name));
|
|
40
|
+
*
|
|
41
|
+
* const Profile = () => {
|
|
42
|
+
* const [name, setName] = useState("Adam");
|
|
43
|
+
* useEmitter(nameChanged, name); // nameChanged("…") on every change of name
|
|
44
|
+
* return <input value={name} onChange={(event) => setName(event.target.value)} />;
|
|
45
|
+
* };
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare const reactPlugin: () => <E extends EventMap>(bus: Bus<E>) => Bus<E> & ReactApi<E>;
|
|
49
|
+
//#endregion
|
|
50
|
+
export type { ReactApi };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useEffect as e,useRef as t}from"react";const n=()=>n=>{function r(r,i,a){let o=t(i),s=t({event:r,equals:a});e(()=>{s.current={event:r,equals:a}}),e(()=>{let{event:e,equals:t}=s.current,r=t??Object.is,a=o.current;if(r(a,i))return;o.current=i;let c=typeof e==`function`?e(i):{type:e,payload:i};n.emit(c)},[i])}return Object.assign(n,{useEmitter:r})};export{n as reactPlugin};
|
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 = {
|
|
@@ -8,6 +8,8 @@ type ReduxAction = {
|
|
|
8
8
|
type NanoeffectsMiddlewareOptions = {
|
|
9
9
|
/** Return `false` for the actions that the bus should not get. Default: all of them. */
|
|
10
10
|
filter?: (action: ReduxAction) => boolean;
|
|
11
|
+
/** Receives an error that `emit` throws, so `dispatch` does not. Default: the error is thrown. */
|
|
12
|
+
onError?: (error: unknown, action: ReduxAction) => void;
|
|
11
13
|
};
|
|
12
14
|
//#endregion
|
|
13
15
|
//#region src/store/redux/effects.d.ts
|
|
@@ -19,48 +21,44 @@ type ReduxStoreApi<State = unknown> = {
|
|
|
19
21
|
}) => unknown;
|
|
20
22
|
getState: () => State;
|
|
21
23
|
};
|
|
22
|
-
type EffectsMiddlewareOptions
|
|
23
|
-
/** More fields for the context of every task, next to `store`. */
|
|
24
|
-
context?: object;
|
|
25
|
-
};
|
|
24
|
+
type EffectsMiddlewareOptions = Pick<NanoeffectsMiddlewareOptions, "filter">;
|
|
26
25
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* `getState` of the store, as soon as
|
|
30
|
-
* in the event map:
|
|
26
|
+
* Makes the Redux middleware that feeds a bus you already have: the actions of
|
|
27
|
+
* the store reach its handlers, and every task gets `ctx.store`, the `dispatch`
|
|
28
|
+
* and `getState` of the store, as soon as Redux has installed the middleware.
|
|
29
|
+
* Type it in the event map of the bus:
|
|
31
30
|
*
|
|
32
31
|
* ```ts
|
|
33
32
|
* type Events = { userRequested: string } & WithContext<{ store: ReduxStoreApi<RootState> }>;
|
|
34
33
|
* ```
|
|
35
34
|
*
|
|
36
|
-
*
|
|
35
|
+
* Use one bus for one store: a second store would replace the first in the
|
|
36
|
+
* contexts. Two things are looked after for you:
|
|
37
37
|
*
|
|
38
38
|
* - **A handler does not loop on its own action.** An action that a handler
|
|
39
39
|
* dispatches through `ctx.store` and that has the type the handler works on
|
|
40
40
|
* does not go back to the bus (a handler of `a` that dispatches `a`). Other
|
|
41
41
|
* handlers of that type do not get it either. Longer cycles, `a` to `b` to
|
|
42
42
|
* `a`, are yours to avoid.
|
|
43
|
-
* - **A failing handler does not break `dispatch`.**
|
|
44
|
-
*
|
|
45
|
-
* still throw to
|
|
43
|
+
* - **A failing handler does not break `dispatch`.** With `onError` on the bus
|
|
44
|
+
* the bus isolates the handlers. Without it the error is written to
|
|
45
|
+
* `console.error`, as in Redux Toolkit listeners. Reducers still throw to
|
|
46
|
+
* the caller.
|
|
46
47
|
*
|
|
47
48
|
* @example
|
|
48
49
|
* ```ts
|
|
49
|
-
* const
|
|
50
|
+
* const bus = createBus<Events>();
|
|
50
51
|
*
|
|
51
52
|
* const store = configureStore({
|
|
52
53
|
* reducer,
|
|
53
|
-
* middleware: (getDefault) => getDefault().concat(
|
|
54
|
+
* middleware: (getDefault) => getDefault().concat(createEffectsMiddleware(bus)),
|
|
54
55
|
* });
|
|
55
56
|
*
|
|
56
|
-
*
|
|
57
|
+
* bus.on(userRequested, latest(async (id, { store, signal }) => {
|
|
57
58
|
* store.dispatch(userLoaded(await fetchUser(id, { signal })));
|
|
58
59
|
* }));
|
|
59
60
|
* ```
|
|
60
61
|
*/
|
|
61
|
-
export declare const createEffectsMiddleware: <E extends EventMap
|
|
62
|
-
bus: Bus<E>;
|
|
63
|
-
middleware: (storeApi: ReduxStoreApi) => (next: (action: unknown) => unknown) => (action: unknown) => unknown;
|
|
64
|
-
};
|
|
62
|
+
export declare const createEffectsMiddleware: <E extends EventMap>(bus: Bus<E>, options?: EffectsMiddlewareOptions) => (storeApi: ReduxStoreApi) => (next: (action: unknown) => unknown) => (action: unknown) => unknown;
|
|
65
63
|
//#endregion
|
|
66
64
|
export type { EffectsMiddlewareOptions, ReduxStoreApi };
|
package/dist/redux.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
const e=e=>typeof e==`object`&&!!e&&`type`in e,t=(t,n={})=>{let r=t.emit,i=e=>{try{r(e)}catch(t){if(!n.onError)throw t;n.onError(t,e)}};return()=>t=>r=>{let a=t(r);return e(r)&&(n.filter?.(r)??!0)&&i(r),a}},n=(e,n={})=>{let r,i=/* @__PURE__ */ new WeakMap,a=e.addContext;e.addContext=e=>{a?.(e);let t=r;if(!t)return;let{type:n}=e;Object.assign(e,{store:{getState:t.getState,dispatch:e=>{typeof e==`object`&&e&&i.set(e,n);let r=e;return t.dispatch(r)}}})};let o=t(e,{filter:e=>i.get(e)!==e.type&&(n.filter?.(e)??!0),onError:(e,{type:t})=>{globalThis.console?.error(`[nanoeffects] handler of "${t}" failed`,e)}});return e=>(r=e,o())};export{n as createEffectsMiddleware};
|
|
@@ -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/README.md
CHANGED
|
@@ -14,14 +14,20 @@ Code for every guide step: [`examples/guide.ts`](../examples/guide.ts) (type-che
|
|
|
14
14
|
| SPA with async UI | [Policies](policies.md), [Guide § latest](guide.md#3-abort-the-previous-run-with-latest) |
|
|
15
15
|
| Unreliable network / long flows | [Effects](effects.md) — one helper per job, then the rest as reference |
|
|
16
16
|
| Domain logic off the UI | [Actions, commands, modules](actions.md) |
|
|
17
|
-
| Already on Redux / Zustand / MobX | [Stores](stores.md) |
|
|
17
|
+
| Already on Redux / Zustand / MobX / Pinia | [Stores](stores.md) |
|
|
18
|
+
| A React form with async checks | [Build a sign-up form in React](react.md) |
|
|
19
|
+
| Moving from redux-saga | [Coming from redux-saga](migrating-from-saga.md) |
|
|
20
|
+
| Using RxJS | [Use nanoeffects next to RxJS](migrating-from-rxjs.md) |
|
|
21
|
+
| Moving from Effector | [Coming from Effector](migrating-from-effector.md) |
|
|
18
22
|
|
|
19
23
|
## Reference
|
|
20
24
|
|
|
21
25
|
- [Policies](policies.md) — `latest`, `leading`, `debounce`, `throttle`, `queue`, `key`
|
|
22
26
|
- [Effects and cancellation](effects.md) — `retry`, `timeout`, `poll`, `take`, `all`, …
|
|
23
27
|
- [Actions, commands, jobs and modules](actions.md)
|
|
28
|
+
- [Sign-up form in React](react.md) — one form: races, double submit, retry
|
|
24
29
|
- [Context fields](context.md) — store or client on every task
|
|
30
|
+
- [TypeScript in many files](typescript.md) — keep the types of the bus in commands and modules
|
|
25
31
|
- [Errors and unheard events](errors.md)
|
|
26
32
|
- [Package entries](package.md) — which import holds what
|
|
27
33
|
- [Tracing](tracing.md) — loggers, Redux DevTools, `notrace`
|
package/docs/actions.md
CHANGED
|
@@ -109,7 +109,7 @@ The registry lives in `nanoeffects/actions`, so a bus without one carries none o
|
|
|
109
109
|
|
|
110
110
|
**When.** You want one mount / one unmount for that domain. Skip modules while a few top-level `bus.on` calls are enough.
|
|
111
111
|
|
|
112
|
-
`bus.use(modulePlugin(modules))` assembles an app from modules and returns `mount`, `unmount`, `isMounted`, `dispose`, and `
|
|
112
|
+
`bus.use(modulePlugin(modules))` assembles an app from modules and returns `mount`, `unmount`, `isMounted`, `dispose`, `commands`, and `createModule`: the same as `createModule` below, with the context fields of the bus typed (see [TypeScript](typescript.md)).
|
|
113
113
|
|
|
114
114
|
```js
|
|
115
115
|
import { createBus } from "nanoeffects";
|
|
@@ -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
|
@@ -19,14 +19,38 @@ That is enough in JavaScript. The fields appear on handlers, `ctx.run`, `ctx.on`
|
|
|
19
19
|
|
|
20
20
|
Library fields (`signal`, `emit`, `run`, …) win on name clashes. `context` may be a function `(task) => fields` when the value depends on the task.
|
|
21
21
|
|
|
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
|
+
|
|
24
|
+
To give fields to **one** command, action, job or module, or to replace them for one `emit`, see [scopes](scope.md).
|
|
25
|
+
|
|
22
26
|
## Type the fields (optional)
|
|
23
27
|
|
|
24
|
-
`
|
|
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.
|
|
25
29
|
|
|
26
30
|
```ts
|
|
27
|
-
import {
|
|
31
|
+
import { contextPlugin, createBus } from "nanoeffects";
|
|
28
32
|
|
|
29
33
|
// Events — event name → payload
|
|
34
|
+
type Events = { save: Draft };
|
|
35
|
+
|
|
36
|
+
// contextPlugin — every task gets ctx.store; the type is the type of `store`
|
|
37
|
+
const bus = createBus<Events>().use(contextPlugin({ store }));
|
|
38
|
+
|
|
39
|
+
bus.on("save", (draft, { store }) => store.put(draft));
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- A function `(task) => fields` makes the fields per task. It gets `{ type, payload }`.
|
|
43
|
+
- Several `contextPlugin` calls, `effectsPlugin` and the `context` option of `createBus` all add their fields to the same context.
|
|
44
|
+
- A name that the bus already has is a type error. The fields of the library (`signal`, `emit`, `run`, …) win at runtime, and a field that the context already has is not replaced.
|
|
45
|
+
- The plugin returns the same bus, so keep the result of the chain: `const bus = createBus().use(contextPlugin({ store }))`.
|
|
46
|
+
|
|
47
|
+
### Name the fields in the event map instead
|
|
48
|
+
|
|
49
|
+
`createBus<Events>({ context: { store } })` also types the fields, but through the event map: `WithContext` is checker-only and says which fields the handlers of this bus have. Use it when you cannot call `use`, for example for the bus that `createEffectsMiddleware` makes.
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { createBus, type WithContext } from "nanoeffects";
|
|
53
|
+
|
|
30
54
|
// WithContext — checker-only: handlers also have ctx.store
|
|
31
55
|
type Events = { save: Draft } & WithContext<{ store: Store }>;
|
|
32
56
|
|