nanoeffects 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +53 -55
  2. package/dist/{action-Bdfp-Wvd.d.ts → action-creator-DOWcbXpy.d.ts} +79 -73
  3. package/dist/actions.d.ts +75 -6
  4. package/dist/actions.js +1 -1
  5. package/dist/bus-BAs9x7IQ.js +1 -0
  6. package/dist/{context-DlA80GOA.js → context-CK0ZomWt.js} +1 -1
  7. package/dist/context-plugin-B0sLWOxR.d.ts +34 -0
  8. package/dist/dev.d.ts +2 -2
  9. package/dist/effects.d.ts +153 -56
  10. package/dist/effects.js +1 -1
  11. package/dist/index.d.ts +3 -2
  12. package/dist/index.js +1 -1
  13. package/dist/{module-I1ixyJzx.d.ts → module-DCJl-qfi.d.ts} +24 -9
  14. package/dist/notrace/{action-Bdfp-Wvd.d.ts → action-creator-DOWcbXpy.d.ts} +79 -73
  15. package/dist/notrace/actions.d.ts +97 -13
  16. package/dist/notrace/actions.js +1 -1
  17. package/dist/notrace/bus-Bdk0YyFQ.js +1 -0
  18. package/dist/notrace/{context-yAWj0BFZ.js → context-CctVmHLC.js} +1 -1
  19. package/dist/notrace/context-plugin-B0sLWOxR.d.ts +34 -0
  20. package/dist/notrace/{delay-Cgvn16oY.js → delay-B_W1tnkS.js} +1 -1
  21. package/dist/notrace/effects.d.ts +153 -56
  22. package/dist/notrace/effects.js +1 -1
  23. package/dist/notrace/index.d.ts +3 -2
  24. package/dist/notrace/index.js +1 -1
  25. package/dist/notrace/policies.d.ts +1 -1
  26. package/dist/notrace/policies.js +1 -1
  27. package/dist/notrace/redux.d.ts +18 -20
  28. package/dist/notrace/redux.js +1 -1
  29. package/dist/observable.d.ts +1 -1
  30. package/dist/policies.d.ts +1 -1
  31. package/dist/policies.js +1 -1
  32. package/dist/react.d.ts +50 -0
  33. package/dist/react.js +1 -0
  34. package/dist/redux.d.ts +18 -20
  35. package/dist/redux.js +1 -1
  36. package/dist/trace.d.ts +1 -1
  37. package/docs/README.md +30 -9
  38. package/docs/actions.md +117 -41
  39. package/docs/context.md +48 -7
  40. package/docs/effects.md +229 -73
  41. package/docs/errors.md +23 -10
  42. package/docs/guide.md +238 -129
  43. package/docs/hmr.md +25 -13
  44. package/docs/migrating-from-effector.md +293 -0
  45. package/docs/migrating-from-rxjs.md +127 -0
  46. package/docs/migrating-from-saga.md +93 -0
  47. package/docs/observable.md +11 -9
  48. package/docs/package.md +24 -0
  49. package/docs/policies.md +93 -30
  50. package/docs/react.md +157 -0
  51. package/docs/semantics.md +5 -3
  52. package/docs/stores.md +488 -25
  53. package/docs/tracing.md +69 -54
  54. package/docs/typescript.md +153 -0
  55. package/llms-full.txt +2645 -0
  56. package/llms.txt +48 -0
  57. package/package.json +39 -9
  58. package/dist/bus-CRnlwmQ4.js +0 -1
  59. package/dist/notrace/bus-Dx0l9X6h.js +0 -1
  60. package/dist/notrace/event-0RIBGYqS.js +0 -1
package/dist/effects.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { A as Keys, N as Task, k as EventMap, u as Context } from "./action-Bdfp-Wvd.js";
1
+ import { F as WithContext, S as Context, _ as Bus, o as EventMap, s as Keys, u as Task } from "./action-creator-DOWcbXpy.js";
2
+ import { n as NoClash } from "./context-plugin-B0sLWOxR.js";
2
3
  //#region src/utils/delay.d.ts
3
4
  /**
4
5
  * Resolves after `ms`, or rejects early if `ctx.signal` aborts. `ms` can be a
@@ -85,6 +86,38 @@ export declare function allSettled<const T extends readonly Starter<unknown>[]>(
85
86
  signal?: AbortSignal;
86
87
  }, effects: T): Promise<{ -readonly [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
87
88
  //#endregion
89
+ //#region src/effects/backoff.d.ts
90
+ type BackoffOptions = {
91
+ /** Pause before the second attempt, in ms. Default 100. */
92
+ base?: number;
93
+ /** How many times each pause is longer than the one before. Default 2. */
94
+ factor?: number;
95
+ /** Longest pause, in ms. Default `Infinity`. */
96
+ max?: number;
97
+ /**
98
+ * Use a random pause from 0 up to the computed one ("full jitter"), so
99
+ * clients that failed together do not retry together. Default `false`.
100
+ */
101
+ jitter?: boolean;
102
+ };
103
+ /**
104
+ * Builds the `delay` of `retry`: the pause grows as `base * factor ** (attempt - 1)`
105
+ * and never passes `max`. `retry` counts attempts from 1; `poll` counts calls from
106
+ * 0, so there the first pause is `base / factor`.
107
+ *
108
+ * @example
109
+ * ```ts
110
+ * bus.on(
111
+ * "load",
112
+ * retry(loadUser, {
113
+ * times: 5,
114
+ * delay: backoff({ base: 200, max: 5000, jitter: true }), // 200, 400, 800, ... up to 5 s, randomised
115
+ * }),
116
+ * );
117
+ * ```
118
+ */
119
+ export declare const backoff: (options?: BackoffOptions) => (attempt: number) => number;
120
+ //#endregion
88
121
  //#region src/effects/cancellable.d.ts
89
122
  type CancellableHandle<T> = {
90
123
  resolve: (value: T | PromiseLike<T>) => void;
@@ -118,6 +151,124 @@ export declare const cancellable: <T = void>(ctx: {
118
151
  signal: AbortSignal;
119
152
  }, executor: CancellableExecutor<T>) => Promise<T>;
120
153
  //#endregion
154
+ //#region src/effects/race.d.ts
155
+ type RaceStarter<T> = Starter<T>;
156
+ /**
157
+ * Runs all starters; first to settle wins, losers are cancelled via `ctx.abort`.
158
+ * The result holds only the winner's value, under its key or index.
159
+ *
160
+ * @example
161
+ * ```ts
162
+ * bus.on("load", async (id, ctx) => {
163
+ * const result = await race(ctx, {
164
+ * data: ({ signal }) => fetchData(id, { signal }),
165
+ * timeout: ({ signal }) => delay({ signal }, 5000),
166
+ * });
167
+ *
168
+ * if ("timeout" in result) throw new Error("timed out");
169
+ * render(result.data);
170
+ * });
171
+ * ```
172
+ */
173
+ export declare function race<T extends Record<string, RaceStarter<unknown>>>(ctx: {
174
+ signal?: AbortSignal;
175
+ }, effects: T): Promise<{ [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
176
+ export declare function race<const T extends readonly RaceStarter<unknown>[]>(ctx: {
177
+ signal?: AbortSignal;
178
+ }, effects: T): Promise<{ -readonly [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
179
+ //#endregion
180
+ //#region src/effects/take.d.ts
181
+ type TakeOptions<P> = {
182
+ /**
183
+ * Wait for the first payload this accepts; others are skipped. A type guard
184
+ * (`(payload): payload is T`) narrows the type of the result to `T`.
185
+ */
186
+ predicate?: (payload: P) => boolean;
187
+ /** Stop waiting when this aborts, e.g. the signal of a `race` starter. */
188
+ signal?: AbortSignal | undefined;
189
+ };
190
+ /**
191
+ * Waits for the next `event` on the bus and resolves with its payload. It sees
192
+ * only events emitted after the call, takes only one, and unsubscribes when it
193
+ * is done. If `ctx` (or `options.signal`) aborts first, it unsubscribes and
194
+ * rejects with the abort reason.
195
+ *
196
+ * @example
197
+ * ```ts
198
+ * bus.on("login", async (user, ctx) => {
199
+ * const reason = await take(ctx, "logout");
200
+ * cleanup(user, reason);
201
+ * });
202
+ *
203
+ * // wait for a matching event, but not longer than 5 s
204
+ * const done = await timeout(ctx, 5000, ({ signal }) =>
205
+ * take(ctx, "saved", { signal, predicate: (id) => id === draftId }),
206
+ * );
207
+ *
208
+ * // a type guard narrows the result: `failed` is `{ ok: false }`
209
+ * const failed = await take(ctx, "result", {
210
+ * predicate: (result): result is { ok: false } => !result.ok,
211
+ * });
212
+ * ```
213
+ */
214
+ export declare function take<E extends EventMap, K extends Keys<E>, T extends E[K]>(ctx: Context<E>, event: K, options: TakeOptions<E[K]> & {
215
+ predicate: (payload: E[K]) => payload is T;
216
+ }): Promise<T>;
217
+ export declare function take<E extends EventMap, K extends Keys<E>>(ctx: Context<E>, event: K, options?: TakeOptions<E[K]>): Promise<E[K]>;
218
+ //#endregion
219
+ //#region src/effects/plugin.d.ts
220
+ /** The effects that `effectsPlugin` can put on `ctx`. */
221
+ type Supported = {
222
+ race: typeof race;
223
+ all: typeof all;
224
+ allSettled: typeof allSettled;
225
+ take: typeof take;
226
+ delay: typeof delay;
227
+ };
228
+ type Results = Record<string, Starter<unknown>>;
229
+ type List = readonly Starter<unknown>[];
230
+ /** Each supported effect without its leading `ctx` argument. `take` knows the events of the bus. */
231
+ type Bound<E extends EventMap> = {
232
+ race: {
233
+ <T extends Results>(effects: T): Promise<{ [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
234
+ <const T extends List>(effects: T): Promise<{ -readonly [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
235
+ };
236
+ all: {
237
+ <T extends Results>(effects: T): Promise<{ [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
238
+ <const T extends List>(effects: T): Promise<{ -readonly [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
239
+ };
240
+ allSettled: {
241
+ <T extends Results>(effects: T): Promise<{ [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
242
+ <const T extends List>(effects: T): Promise<{ -readonly [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
243
+ };
244
+ take: <K extends Keys<E>>(event: K, options?: TakeOptions<E[K]>) => Promise<E[K]>;
245
+ delay: (ms: number | (() => number)) => Promise<void>;
246
+ };
247
+ type Helpers = Partial<Supported>;
248
+ type BoundOf<H extends Helpers, E extends EventMap> = { [K in keyof H & keyof Bound<E>]: Bound<E>[K]; };
249
+ /**
250
+ * Puts the effects you pass on the context of every task of the bus, bound to
251
+ * that task, so a handler can write `({ race })` instead of `race(ctx, …)`.
252
+ * The key is the name of the effect: `race`, `all`, `allSettled`, `take`,
253
+ * `delay`. Returns the same bus, typed with the new fields.
254
+ *
255
+ * @example
256
+ * ```ts
257
+ * import { createBus } from "nanoeffects";
258
+ * import { all, delay, effectsPlugin, race } from "nanoeffects/effects";
259
+ *
260
+ * const bus = createBus().use(effectsPlugin({ race, delay }));
261
+ *
262
+ * bus.on("load", async (id, { race, delay }) => {
263
+ * const result = await race({
264
+ * data: ({ signal }) => fetchData(id, { signal }),
265
+ * timeout: () => delay(5000),
266
+ * });
267
+ * });
268
+ * ```
269
+ */
270
+ export 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>>>;
271
+ //#endregion
121
272
  //#region src/effects/poll.d.ts
122
273
  type PollOptions = {
123
274
  /** Also call `fn` at once, not only after the first `ms`. Default `false`. */
@@ -160,32 +311,6 @@ export declare function poll(ctx: {
160
311
  signal: AbortSignal;
161
312
  }, fn: (tick: number) => unknown, ms: PollMs, options?: PollOptions): Promise<void>;
162
313
  //#endregion
163
- //#region src/effects/race.d.ts
164
- type RaceStarter<T> = Starter<T>;
165
- /**
166
- * Runs all starters; first to settle wins, losers are cancelled via `ctx.abort`.
167
- * The result holds only the winner's value, under its key or index.
168
- *
169
- * @example
170
- * ```ts
171
- * bus.on("load", async (id, ctx) => {
172
- * const result = await race(ctx, {
173
- * data: ({ signal }) => fetchData(id, { signal }),
174
- * timeout: ({ signal }) => delay({ signal }, 5000),
175
- * });
176
- *
177
- * if ("timeout" in result) throw new Error("timed out");
178
- * render(result.data);
179
- * });
180
- * ```
181
- */
182
- export declare function race<T extends Record<string, RaceStarter<unknown>>>(ctx: {
183
- signal?: AbortSignal;
184
- }, effects: T): Promise<{ [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
185
- export declare function race<const T extends readonly RaceStarter<unknown>[]>(ctx: {
186
- signal?: AbortSignal;
187
- }, effects: T): Promise<{ -readonly [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
188
- //#endregion
189
314
  //#region src/effects/retry.d.ts
190
315
  type RetryOptions = {
191
316
  /** Total number of attempts, the first one included. Default 3. */
@@ -226,34 +351,6 @@ export declare function retry<T>(ctx: {
226
351
  signal?: AbortSignal;
227
352
  }, fn: RetryFn<T>, options?: RetryOptions): Promise<T>;
228
353
  //#endregion
229
- //#region src/effects/take.d.ts
230
- type TakeOptions<P> = {
231
- /** Wait for the first payload this accepts; others are skipped. */
232
- predicate?: (payload: P) => boolean;
233
- /** Stop waiting when this aborts, e.g. the signal of a `race` starter. */
234
- signal?: AbortSignal | undefined;
235
- };
236
- /**
237
- * Waits for the next `event` on the bus and resolves with its payload. It sees
238
- * only events emitted after the call, takes only one, and unsubscribes when it
239
- * is done. If `ctx` (or `options.signal`) aborts first, it unsubscribes and
240
- * rejects with the abort reason.
241
- *
242
- * @example
243
- * ```ts
244
- * bus.on("login", async (user, ctx) => {
245
- * const reason = await take(ctx, "logout");
246
- * cleanup(user, reason);
247
- * });
248
- *
249
- * // wait for a matching event, but not longer than 5 s
250
- * const done = await timeout(ctx, 5000, ({ signal }) =>
251
- * take(ctx, "saved", { signal, predicate: (id) => id === draftId }),
252
- * );
253
- * ```
254
- */
255
- export declare const take: <E extends EventMap, K extends Keys<E>>(ctx: Context<E>, event: K, options?: TakeOptions<E[K]>) => Promise<E[K]>;
256
- //#endregion
257
354
  //#region src/effects/timeout.d.ts
258
355
  /**
259
356
  * Runs a function; if it takes longer than `ms`, aborts its signal and rejects
@@ -281,4 +378,4 @@ export declare function timeout<T>(ctx: {
281
378
  signal?: AbortSignal;
282
379
  }, ms: number, starter: Starter<T>): Promise<T>;
283
380
  //#endregion
284
- export type { CancellableExecutor, CancellableHandle, EffectContext, PollOptions, RaceStarter, RetryOptions, TakeOptions };
381
+ export type { BackoffOptions, CancellableExecutor, CancellableHandle, EffectContext, PollOptions, RaceStarter, RetryOptions, TakeOptions };
package/dist/effects.js CHANGED
@@ -1 +1 @@
1
- import{n as e,t}from"./onAbort-BgMfspK4.js";import{a as n,i as r,o as i,t as a}from"./context-DlA80GOA.js";import{n as o,r as s,t as c}from"./delay-CIHnOh_L.js";const l=(e,n)=>n?.signal?new Promise((r,i)=>{let a=t(n,i);e.then(r,i).finally(a)}):e,u=async(e,n,o,s)=>{n?.signal?.throwIfAborted();let{ctx:c,unlink:u}=r(n??{}),d=a(c,e);i(c,e,`start`),t(c,()=>d(`cancel`));try{let e=await l(o(c),n);return d(`done`),s?.(!1),e}catch(e){throw d(`error`,e),s?.(!0),e}finally{u()}},d=async(e,t)=>{let{ctx:i,unlink:a}=r(e);try{return await n(i,`run`,()=>t(i))}finally{a()}},f=e=>e,p=(e,t)=>n=>e(t,f(n)),m=e=>Array.isArray(e)?Array(e.length):{},h=(e,t)=>{let i=Object.entries(t).map(([t,i])=>{let a=r(e),o={signal:a.ctx.signal,abort:a.ctx.abort},s=()=>i(o);return{key:t,task:new Promise(e=>e(n(a.ctx,`run`,s))).finally(a.unlink),cancel:a.ctx.abort}});return{tasks:i.map(({key:e,task:t})=>[e,t]),cancelAll:()=>{for(let e of i)e.cancel()}}},g=(e,t,n,r,i)=>{let a=()=>{};return u(e,t,async e=>{let t=h(e,n);return a=t.cancelAll,r(t.tasks)},e=>{(e||i===`end`)&&a()})};function _(e,t){return g(`all`,e,t,async e=>{let n=await Promise.all(e.map(([,e])=>e)),r=m(t);for(let[t,[i]]of e.entries())r[i]=n[t];return r},`failure`)}const v=e=>e.then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}));function y(e,t){return g(`allSettled`,e,t,async e=>{let n=await Promise.all(e.map(([,e])=>v(e))),r=m(t);for(let[t,[i]]of e.entries())r[i]=n[t];return r},`failure`)}const b=(n,r)=>n.signal.aborted?Promise.reject(n.signal.reason):new Promise((i,a)=>{let o=[],s=`pending`,c=t=>{try{t()}catch(t){e(`cancel cleanup failed`,t)}},l=t(n,e=>{s=`cancelled`;for(let e of o.reverse())c(e);a(e)}),u=e=>(...t)=>{s===`pending`&&(s=`settled`,l(),o.length=0,e(...t))},d=e=>{s===`pending`?o.push(e):s===`cancelled`&&c(e)};try{r({resolve:u(i),reject:u(a),onCancel:d})}catch(e){u(a)(e)}}),x=(e,n,r,i)=>{if(e.signal.aborted)return Promise.resolve();let a=i.leading??!1,c=i.waitForFn??!1;return u(`poll`,e,e=>new Promise((i,l)=>{let u=0,f=null,p=()=>{f!==null&&(o(f),f=null)},m=e=>{try{f=s(h,typeof r==`function`?r(e):r)}catch(e){p(),l(e)}},h=()=>{f=null,c||m(u+1),g()},g=()=>{let t=u++;d(e,e=>n(e,t)).then(()=>{c&&!e.signal.aborted&&m(u)},e=>{p(),l(e)})};t(e,p),a?(g(),c||m(u)):m(0)})).catch(t=>{if(!e.signal.aborted)throw t})};function S(e,t,n,r={}){if(typeof e!=`object`){let r=t,i=n??{};return(t,n)=>x(n,p(r,t),e,i)}let i=t;return x(e,(e,t)=>i(t),n,r)}function C(e,t){let n=m(t);return Object.keys(t).length===0?Promise.resolve(n):g(`race`,e,t,async e=>{let[t,r]=await Promise.race(e.map(([e,t])=>t.then(t=>[e,t])));return n[t]=r,n},`end`)}const w=(e,t,n)=>typeof e==`function`?e(t,n):e,T=e=>({times:e.times??3,delay:e.delay??0,shouldRetry:e.shouldRetry??(()=>!0)}),E=(e,t,n)=>u(`retry`,e,async e=>{let{times:r,delay:i,shouldRetry:a}=T(n);for(let n=1;;n++)try{return await d(e,e=>t(e,n))}catch(t){if(e.signal.throwIfAborted(),n>=r||!a(t,n))throw t;let o=w(i,n,t);o>0&&await c(e,o)}});function D(e,t,n={}){if(typeof e==`function`){let n=e;return(e,r)=>E(r,p(n,e),t??{})}let r=t;return E(e,(e,t)=>r(t),n)}const O=(e,n,r={})=>u(`take`,e,i=>new Promise((a,o)=>{let{predicate:s}=r,c=[],l=()=>{for(let e of c)e()},u=e=>{l(),o(e)};c.push(e.on(n,e=>{(!s||s(e))&&(l(),a(e))})),c.push(t(i,u)),c.push(t({signal:r.signal},u))})),k=(e,t,i)=>u(`timeout`,e,async e=>{let a=r(e),c=s(()=>a.ctx.abort(new DOMException(`Timed out after ${t}ms`,`TimeoutError`)),t);try{return await l(Promise.resolve(n(a.ctx,`run`,()=>i(a.ctx))),a.ctx)}finally{o(c),a.unlink()}});function A(e,t,n){if(typeof e==`number`){let n=t;return(t,r)=>k(r,e,p(n,t))}let r=t,i=n;return k(e,r,e=>i({signal:e.signal,abort:e.abort}))}export{_ as all,y as allSettled,b as cancellable,c as delay,t as onAbort,S as poll,C as race,D as retry,O as take,A as timeout};
1
+ import{n as e,t}from"./onAbort-BgMfspK4.js";import{a as n,i as r,o as i,t as a}from"./context-CK0ZomWt.js";import{n as o,r as s,t as c}from"./delay-CIHnOh_L.js";const l=(e,n)=>n?.signal?new Promise((r,i)=>{let a=t(n,i);e.then(r,i).finally(a)}):e,u=async(e,n,o,s)=>{n?.signal?.throwIfAborted();let{ctx:c,unlink:u}=r(n??{}),d=a(c,e);i(c,e,`start`),t(c,()=>d(`cancel`));try{let e=await l(o(c),n);return d(`done`),s?.(!1),e}catch(e){throw d(`error`,e),s?.(!0),e}finally{u()}},d=async(e,t)=>{let{ctx:i,unlink:a}=r(e);try{return await n(i,`run`,()=>t(i))}finally{a()}},f=e=>e,p=(e,t)=>n=>e(t,f(n)),m=e=>Array.isArray(e)?Array(e.length):{},h=(e,t)=>{let i=Object.entries(t).map(([t,i])=>{let a=r(e),o={signal:a.ctx.signal,abort:a.ctx.abort},s=()=>i(o);return{key:t,task:new Promise(e=>e(n(a.ctx,`run`,s))).finally(a.unlink),cancel:a.ctx.abort}});return{tasks:i.map(({key:e,task:t})=>[e,t]),cancelAll:()=>{for(let e of i)e.cancel()}}},g=(e,t,n,r,i)=>{let a=()=>{};return u(e,t,async e=>{let t=h(e,n);return a=t.cancelAll,r(t.tasks)},e=>{(e||i===`end`)&&a()})};function _(e,t){return g(`all`,e,t,async e=>{let n=await Promise.all(e.map(([,e])=>e)),r=m(t);for(let[t,[i]]of e.entries())r[i]=n[t];return r},`failure`)}const v=e=>e.then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}));function y(e,t){return g(`allSettled`,e,t,async e=>{let n=await Promise.all(e.map(([,e])=>v(e))),r=m(t);for(let[t,[i]]of e.entries())r[i]=n[t];return r},`failure`)}const b=e=>e>0?e:0,x=(e={})=>{let t=b(e.base??100),n=e.factor??2,r=b(e.max??1/0),i=e.jitter??!1;return e=>{let a=b(Math.min(r,t*n**(e-1)));return i?Math.random()*a||0:a}},S=(n,r)=>n.signal.aborted?Promise.reject(n.signal.reason):new Promise((i,a)=>{let o=[],s=`pending`,c=t=>{try{t()}catch(t){e(`cancel cleanup failed`,t)}},l=t(n,e=>{s=`cancelled`;for(let e of o.reverse())c(e);a(e)}),u=e=>(...t)=>{s===`pending`&&(s=`settled`,l(),o.length=0,e(...t))},d=e=>{s===`pending`?o.push(e):s===`cancelled`&&c(e)};try{r({resolve:u(i),reject:u(a),onCancel:d})}catch(e){u(a)(e)}}),C=e=>t=>{let n=t.addContext,r=Object.entries(e);return t.addContext=e=>{n?.(e);for(let[t,n]of r)Object.assign(e,{[t]:(...t)=>n(e,...t)})},t},w=(e,n,r,i)=>{if(e.signal.aborted)return Promise.resolve();let a=i.leading??!1,c=i.waitForFn??!1;return u(`poll`,e,e=>new Promise((i,l)=>{let u=0,f=null,p=()=>{f!==null&&(o(f),f=null)},m=e=>{try{f=s(h,typeof r==`function`?r(e):r)}catch(e){p(),l(e)}},h=()=>{f=null,c||m(u+1),g()},g=()=>{let t=u++;d(e,e=>n(e,t)).then(()=>{c&&!e.signal.aborted&&m(u)},e=>{p(),l(e)})};t(e,p),a?(g(),c||m(u)):m(0)})).catch(t=>{if(!e.signal.aborted)throw t})};function T(e,t,n,r={}){if(typeof e!=`object`){let r=t,i=n??{};return(t,n)=>w(n,p(r,t),e,i)}let i=t;return w(e,(e,t)=>i(t),n,r)}function E(e,t){let n=m(t);return Object.keys(t).length===0?Promise.resolve(n):g(`race`,e,t,async e=>{let[t,r]=await Promise.race(e.map(([e,t])=>t.then(t=>[e,t])));return n[t]=r,n},`end`)}const D=(e,t,n)=>typeof e==`function`?e(t,n):e,O=e=>({times:e.times??3,delay:e.delay??0,shouldRetry:e.shouldRetry??(()=>!0)}),k=(e,t,n)=>u(`retry`,e,async e=>{let{times:r,delay:i,shouldRetry:a}=O(n);for(let n=1;;n++)try{return await d(e,e=>t(e,n))}catch(t){if(e.signal.throwIfAborted(),!(n<r)||!a(t,n))throw t;let o=D(i,n,t);o>0&&await c(e,o)}});function A(e,t,n={}){if(typeof e==`function`){let n=e;return(e,r)=>k(r,p(n,e),t??{})}let r=t;return k(e,(e,t)=>r(t),n)}function j(e,n,r={}){return u(`take`,e,i=>new Promise((a,o)=>{let{predicate:s}=r,c=[],l=()=>{for(let e of c)e()},u=e=>{l(),o(e)};c.push(e.on(n,e=>{(!s||s(e))&&(l(),a(e))})),c.push(t(i,u)),c.push(t({signal:r.signal},u))}))}const M=(e,t,i)=>u(`timeout`,e,async e=>{let a=r(e),c=s(()=>a.ctx.abort(new DOMException(`Timed out after ${t}ms`,`TimeoutError`)),t);try{return await l(Promise.resolve(n(a.ctx,`run`,()=>i(a.ctx))),a.ctx)}finally{o(c),a.unlink()}});function N(e,t,n){if(typeof e==`number`){let n=t;return(t,r)=>M(r,e,p(n,t))}let r=t,i=n;return M(e,r,e=>i({signal:e.signal,abort:e.abort}))}export{_ as all,y as allSettled,x as backoff,S as cancellable,c as delay,C as effectsPlugin,t as onAbort,T as poll,E as race,A as retry,j as take,N as timeout};
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
- import { A as Keys, C as WithPayload, D as anyEventType, E as Tracer, F as TraceEvent, I as TraceLogger, L as UnsubscribeCallback, M as Phase, N as Task, O as EffectName, P as Trace, S as WithContext, T as createBus, _ as EventPayload, a as AnyEvent, b as Off, c as Command, d as ContextOf, f as ContextOption, g as EventLike, h as EventKey, i as Action, j as PayloadOf, k as EventMap, l as Commands, m as Emittable, o as Bus, p as Emit, s as BusOptions, u as Context, v as Handler, w as WithType, x as On, y as HandlerRef } from "./action-Bdfp-Wvd.js";
2
- export { type Action, type AnyEvent, type Bus, type BusOptions, type Command, type Commands, type Context, type ContextOf, type ContextOption, type EffectName, type Emit, type Emittable, type EventKey, type EventLike, type EventMap, type EventPayload, type Handler, type HandlerRef, type Keys, type Off, type On, type PayloadOf, type Phase, type Task, type Trace, type TraceEvent, type TraceLogger, type Tracer, type UnsubscribeCallback, type WithContext, type WithPayload, type WithType, anyEventType, createBus };
1
+ import { A as EventsOf, B as anyEventType, C as ContextOf, D as EventKey, E as Emittable, F as WithContext, I as WithType, L as commandCall, M as HandlerRef, N as Off, O as EventLike, P as On, R as createBus, S as Context, T as Emit, _ as Bus, a as EffectName, b as CommandCall, c as PayloadOf, d as Trace, f as TraceEvent, g as AnyEvent, h as Action, i as WithPayload, j as Handler, k as EventPayload, l as Phase, m as UnsubscribeCallback, o as EventMap, p as TraceLogger, s as Keys, u as Task, v as BusOptions, w as ContextOption, x as Commands, y as Command, z as Tracer } from "./action-creator-DOWcbXpy.js";
2
+ import { n as NoClash, r as contextPlugin, t as ContextTask } from "./context-plugin-B0sLWOxR.js";
3
+ export { type Action, type AnyEvent, type Bus, type BusOptions, type Command, type CommandCall, type Commands, type Context, type ContextOf, type ContextOption, type ContextTask, type EffectName, type Emit, type Emittable, type EventKey, type EventLike, type EventMap, type EventPayload, type EventsOf, type Handler, type HandlerRef, type Keys, type NoClash, type Off, type On, type PayloadOf, type Phase, type Task, type Trace, type TraceEvent, type TraceLogger, type Tracer, type UnsubscribeCallback, type WithContext, type WithPayload, type WithType, anyEventType, commandCall, contextPlugin, createBus };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export{n as anyEventType,t as createBus}from"./bus-CRnlwmQ4.js";
1
+ import{i as e,n as t,t as n}from"./bus-BAs9x7IQ.js";const r=e=>typeof e==`function`,i=e=>({type:Reflect.get(e,`type`),payload:Reflect.get(e,`payload`)}),a=e=>t=>{let n=t.addContext;return t.addContext=t=>{n?.(t);let a=r(e)?e(i(t)):e;for(let[e,n]of Object.entries(a))e in t||Reflect.set(t,e,n)},t};export{e as anyEventType,n as commandCall,a as contextPlugin,t as createBus};
@@ -1,4 +1,4 @@
1
- import { L as UnsubscribeCallback, N as Task, c as Command, j as PayloadOf, k as EventMap, o as Bus, u as Context } from "./action-Bdfp-Wvd.js";
1
+ import { S as Context, _ as Bus, c as PayloadOf, m as UnsubscribeCallback, o as EventMap, u as Task, y as Command } from "./action-creator-DOWcbXpy.js";
2
2
  //#region src/actions/command.d.ts
3
3
  /** What `createCommand` returns: a command that also tells whether an action is of its type. */
4
4
  type MadeCommand<P, R, T extends string> = Command<P, R> & {
@@ -13,17 +13,18 @@ type MadeCommand<P, R, T extends string> = Command<P, R> & {
13
13
  };
14
14
  };
15
15
  /**
16
- * Creates a command: a typed request for a result. `bus.emit(command, payload)`
17
- * runs its handler as a task, with a context, and returns what the handler
18
- * returns. Listeners of the command's type are told first, as for any event.
16
+ * Creates a command: a typed request for a result. Calling it builds
17
+ * `{ type, payload }` like `createAction`. `bus.emit(command(payload))` (or
18
+ * `bus.emit(command, payload)`) runs its handler as a task, with a context, and
19
+ * returns what the handler returns. Listeners of the command's type are told
20
+ * first, as for any event.
19
21
  *
20
22
  * The handler is optional. A command without one is only a contract, and the
21
23
  * bus gets its implementation from the `commands` registry (see
22
24
  * `commandsPlugin` and `bindHandler`). A handler bound there wins over the one
23
25
  * the command was created with, so a test can give a bus its own implementation.
24
26
  *
25
- * A handler can be wrapped in a policy, e.g. `latest(fn)`. Calling the command
26
- * directly, `command(payload)`, runs its own handler outside of any bus.
27
+ * A handler can be wrapped in a policy, e.g. `latest(fn)`.
27
28
  * `command.match(action)` tells whether an action has the type of the command,
28
29
  * as it does for an action creator.
29
30
  *
@@ -34,9 +35,9 @@ type MadeCommand<P, R, T extends string> = Command<P, R> & {
34
35
  * );
35
36
  * const sendMail = createCommand<Mail, void>("mail/send"); // implemented elsewhere
36
37
  *
38
+ * const bus = createBus<Events>();
37
39
  * bus.use(commandsPlugin([bindHandler(sendMail, smtpSend)]));
38
- * const bus = createBus({ commands });
39
- * const user = await bus.emit(getUser, "1");
40
+ * const user = await bus.emit(getUser("1"));
40
41
  *
41
42
  * commands.handle(getUser, async () => fakeUser); // in a test
42
43
  * ```
@@ -103,7 +104,10 @@ type ModuleContext<E extends EventMap = EventMap> = Context<E> & {
103
104
  * returned function undoes just this call, e.g. for what was added later from
104
105
  * a listener.
105
106
  */
106
- use(...items: Array<Module<E> | Command<never, unknown>>): UnsubscribeCallback;
107
+ use(...items: Array<Module<E> | {
108
+ readonly type: string;
109
+ (payload: never): unknown;
110
+ }>): UnsubscribeCallback;
107
111
  /**
108
112
  * Starts a background task that lives as long as the module: it is aborted
109
113
  * on unmount, and an error of it is left unhandled, as in any handler.
@@ -145,6 +149,12 @@ type ModuleApi<E extends EventMap = EventMap> = {
145
149
  isMounted(module: Module<E>): boolean;
146
150
  /** Unmounts every module; a disposed app cannot mount modules. */
147
151
  dispose(): void;
152
+ /**
153
+ * `createModule` with the event map of this bus: the module sees the context
154
+ * fields of the bus without a type argument. It returns a plain module, not
155
+ * mounted, with no link to this bus.
156
+ */
157
+ createModule(name: string, setup: (m: ModuleContext<E>) => void): Module<E>;
148
158
  };
149
159
  /**
150
160
  * The plugin that assembles an app from modules: `bus.use(modulePlugin(modules))`
@@ -170,6 +180,11 @@ type ModuleApi<E extends EventMap = EventMap> = {
170
180
  * const unmount = app.mount(orderModule); // later, e.g. after a lazy import
171
181
  * unmount(); // or app.unmount(orderModule)
172
182
  *
183
+ * // a module made from the app has the context fields of the bus, typed
184
+ * const userModule = app.createModule("user", (m) => {
185
+ * m.on("loaded", (user, { store }) => store.put(user));
186
+ * });
187
+ *
173
188
  * bus.emit(getUser, "1");
174
189
  * app.commands.handle(getUser, fakeUser); // in a test
175
190
  * app.dispose();
@@ -1,29 +1,3 @@
1
- //#region src/core/types.d.ts
2
- type PayloadOf<P> = undefined extends P ? [payload?: P] : [payload: P];
3
- type EventMap = Record<string, unknown>;
4
- type Keys<E> = keyof E & string;
5
- type Task<P, E extends EventMap, R = unknown> = (payload: P, ctx: Context<E>) => R;
6
- type EffectName = "every" | "latest" | "leading" | "debounce" | "throttle" | "run" | "all" | "race" | "retry" | "timeout" | "poll" | "take" | "queue" | "allSettled" | "command";
7
- type Phase = "start" | "done" | "error" | "cancel" | "drop";
8
- type TraceEvent<E = Record<string, unknown>> = { [K in Keys<E>]: {
9
- id: number;
10
- parent?: number;
11
- type: K;
12
- effect: EffectName;
13
- phase: Phase;
14
- payload: E[K];
15
- error?: unknown;
16
- }; }[Keys<E>];
17
- /**
18
- * The contract of every trace logger: receives each task phase of a bus.
19
- * Adapters in `nanoeffects/trace` are factories that return one; pass it
20
- * through `createTracer` to `createBus({ trace })`.
21
- */
22
- type TraceLogger<E = Record<string, unknown>> = (event: TraceEvent<E>) => void;
23
- /** One logger or several; a throwing logger never breaks the task or the others. */
24
- type Trace<E = Record<string, unknown>> = TraceLogger<E> | readonly TraceLogger<E>[];
25
- type UnsubscribeCallback = () => void;
26
- //#endregion
27
1
  //#region src/core/store.d.ts
28
2
  declare const anyEventType = "*";
29
3
  //#endregion
@@ -68,21 +42,29 @@ type Action<E extends EventMap> = {
68
42
  type: EventKey<E>;
69
43
  payload: EventPayload<E>;
70
44
  };
71
- type WithPayload<E extends EventMap, K extends EventKey<E>> = undefined extends E[K] ? {
72
- type: K;
73
- payload?: E[K];
74
- } : {
75
- type: K;
76
- payload: E[K];
77
- };
78
45
  type EventLike<E extends EventMap> = { [K in EventKey<E>]: WithPayload<E, K>; }[Keys<E>];
79
46
  /**
80
- * A typed request for a result. `bus.emit(command, payload)` runs the handler
81
- * of the command as a task and returns what it returns. Made by
47
+ * Brands `{ type, payload }` from a command call so `emit` can run the command.
48
+ * Not enumerable on the object.
49
+ */
50
+ declare const commandCall: unique symbol;
51
+ /**
52
+ * What `command(payload)` returns: an action-shaped value that `emit` runs as
53
+ * that command. A plain `{ type, payload }` without this brand is notify-only.
54
+ */
55
+ type CommandCall<P = unknown, R = unknown> = {
56
+ type: string;
57
+ payload: P;
58
+ readonly [commandCall]: Command<P, R>;
59
+ };
60
+ /**
61
+ * A typed request for a result. Calling it builds `{ type, payload }` like
62
+ * `createAction`; `bus.emit(command(payload))` (or `bus.emit(command, payload)`)
63
+ * runs the handler as a task and returns what it returns. Made by
82
64
  * `createCommand` in `nanoeffects/actions`.
83
65
  */
84
66
  type Command<P = never, R = unknown> = {
85
- (payload: P, ctx?: never): R;
67
+ (payload: P): CommandCall<P, R>;
86
68
  readonly type: string;
87
69
  /** Wraps the handler that really runs, e.g. to route its outcome. */
88
70
  readonly wrap?: (run: (payload: never, ctx: never) => unknown) => (payload: never, ctx: never) => unknown;
@@ -96,11 +78,13 @@ interface Commands {
96
78
  run(command: Command, payload: unknown, ctx: unknown): unknown;
97
79
  }
98
80
  /** Anything `emit` takes as its first argument: an event type, an action or a command. */
99
- type Emittable<E extends EventMap> = EventKey<E> | Command | EventLike<E>;
81
+ type Emittable<E extends EventMap> = EventKey<E> | Command | CommandCall | EventLike<E>;
100
82
  type Emit<E extends EventMap> = {
101
83
  <K extends EventKey<E>>(event: K, ...payload: PayloadOf<E[K]>): void;
102
- <K extends EventKey<E>, R>(event: Handler<E, R, K>, ...payload: PayloadOf<E[K]>): R;
84
+ /** Before Handler: a command is callable too, and must return its result type. */
103
85
  <P, R>(command: Command<P, R>, ...payload: PayloadOf<P>): R;
86
+ <P, R>(call: CommandCall<P, R>): R;
87
+ <K extends EventKey<E>, R>(event: Handler<E, R, K>, ...payload: PayloadOf<E[K]>): R;
104
88
  (event: EventLike<E>): void;
105
89
  };
106
90
  type On<E extends EventMap> = {
@@ -119,6 +103,8 @@ type On<E extends EventMap> = {
119
103
  }, fn: Task<E[K], E>): UnsubscribeCallback;
120
104
  };
121
105
  type Off<E extends EventMap> = {
106
+ /** Removes every listener of every event (including `"*"`). */
107
+ (): void;
122
108
  (event: AnyEvent): void;
123
109
  (event: AnyEvent, fn: Task<EventPayload<E>, E>): void;
124
110
  <K extends EventKey<E>>(event: K): void;
@@ -171,8 +157,25 @@ type Context<E extends EventMap> = EventLike<E> & ContextOf<E> & {
171
157
  /** Child task: aborted with the parent; errors propagate to the parent. */
172
158
  run<P, R>(fn: Task<P, E, R>, ...args: PayloadOf<P>): Promise<Awaited<R>>;
173
159
  };
160
+ /**
161
+ * The event map of a bus, with the context fields that plugins added. Use it to
162
+ * name the types of a bus in other files: `type AppEvents = EventsOf<typeof bus>`.
163
+ *
164
+ * @example
165
+ * ```ts
166
+ * const bus = createBus<Events>().use(effectsPlugin({ race }));
167
+ * type AppEvents = EventsOf<typeof bus>;
168
+ * type AppContext = Context<AppEvents>; // has `race`
169
+ * ```
170
+ */
171
+ type EventsOf<B> = B extends Bus<infer E extends EventMap> ? E : never;
174
172
  interface Bus<E extends EventMap> {
175
173
  on: On<E>;
174
+ /**
175
+ * Like `on`, but the handler runs at most once: it is removed before it is
176
+ * invoked, so a nested emit of the same event does not re-enter it.
177
+ */
178
+ once: On<E>;
176
179
  off: Off<E>;
177
180
  emit: Emit<E>;
178
181
  /**
@@ -182,10 +185,12 @@ interface Bus<E extends EventMap> {
182
185
  */
183
186
  use<R>(plugin: (bus: Bus<E>) => R): R;
184
187
  /** `BusOptions.context` and `.trace`, and `commands`, for what makes tasks of its own, like `modulePlugin`. */
185
- readonly context?: ContextOption<E> | undefined;
188
+ readonly ctx?: ContextOption<E> | undefined;
186
189
  /** What runs the commands; `commandsPlugin` and `modulePlugin` set it. Without one a command runs its own handler. */
187
190
  commands?: Commands | undefined;
188
191
  readonly trace?: Tracer | undefined;
192
+ /** Called with every new task context to add fields to it, for what needs the task, like `effectsPlugin`. Chain to the previous one. */
193
+ addContext?: ((ctx: object) => void) | undefined;
189
194
  }
190
195
  type BusOptions<E extends EventMap> = {
191
196
  /** Tracing of every task phase; make it with `createTracer` from `nanoeffects/trace`. */
@@ -223,7 +228,40 @@ type BusOptions<E extends EventMap> = {
223
228
  };
224
229
  declare const createBus: <Events extends EventMap>(options?: BusOptions<Events>) => Bus<Events>;
225
230
  //#endregion
226
- //#region src/actions/action.d.ts
231
+ //#region src/core/types.d.ts
232
+ type PayloadOf<P> = undefined extends P ? [payload?: P] : [payload: P];
233
+ type EventMap = Record<string, unknown>;
234
+ type Keys<E> = keyof E & string;
235
+ type Task<P, E extends EventMap, R = unknown> = (payload: P, ctx: Context<E>) => R;
236
+ type EffectName = "every" | "latest" | "leading" | "debounce" | "throttle" | "run" | "all" | "race" | "retry" | "timeout" | "poll" | "take" | "queue" | "allSettled" | "command";
237
+ type Phase = "start" | "done" | "error" | "cancel" | "drop";
238
+ type TraceEvent<E = Record<string, unknown>> = { [K in Keys<E>]: {
239
+ id: number;
240
+ parent?: number;
241
+ type: K;
242
+ effect: EffectName;
243
+ phase: Phase;
244
+ payload: E[K];
245
+ error?: unknown;
246
+ }; }[Keys<E>];
247
+ /**
248
+ * The contract of every trace logger: receives each task phase of a bus.
249
+ * Adapters in `nanoeffects/trace` are factories that return one; pass it
250
+ * through `createTracer` to `createBus({ trace })`.
251
+ */
252
+ type TraceLogger<E = Record<string, unknown>> = (event: TraceEvent<E>) => void;
253
+ /** One logger or several; a throwing logger never breaks the task or the others. */
254
+ type Trace<E = Record<string, unknown>> = TraceLogger<E> | readonly TraceLogger<E>[];
255
+ type UnsubscribeCallback = () => void;
256
+ //#endregion
257
+ //#region src/core/action-creator.d.ts
258
+ type WithPayload<E extends EventMap, K extends Keys<E>> = undefined extends E[K] ? {
259
+ type: K;
260
+ payload?: E[K];
261
+ } : {
262
+ type: K;
263
+ payload: E[K];
264
+ };
227
265
  type Absent<P> = [P] extends [void] ? undefined : P;
228
266
  type Made<P, T extends string> = WithPayload<{ [K in T]: Absent<P>; }, T>;
229
267
  declare const free: unique symbol;
@@ -267,37 +305,5 @@ interface ActionCreator<P = void, T extends string = string, A extends unknown[]
267
305
  }): action is Made<P, T>;
268
306
  toString(): T;
269
307
  }
270
- /**
271
- * Creates an action creator, as in Redux Toolkit.
272
- *
273
- * - `createAction("user/loaded")` needs no generics: the payload type comes
274
- * from the event map of the bus that the action goes to, and the literal
275
- * type of the action is kept.
276
- * - `createAction<User>("user/loaded")` gives the payload type, for a bus
277
- * without an event map or to check the calls of the creator. The type of
278
- * the action is then `string`; to keep the literal, give it as the second
279
- * generic: `createAction<User, "user/loaded">("user/loaded")`.
280
- * - With `prepare` the arguments of the creator can differ from the payload,
281
- * and both types are inferred.
282
- *
283
- * @example
284
- * ```ts
285
- * const added = createAction<number>("counter/added");
286
- * added(2); // { type: "counter/added", payload: 2 }
287
- * added.type; // "counter/added"
288
- * added.match(action); // true for actions of this type
289
- *
290
- * const renamed = createAction("user/renamed", (id: string, name: string) => ({
291
- * payload: { id, name },
292
- * }));
293
- *
294
- * bus.emit(added(2));
295
- * bus.on(added, (n) => console.log(n));
296
- * ```
297
- */
298
- declare function createAction<P = Free, T extends string = string>(type: T): [P] extends [Free] ? FreeActionCreator<T> : ActionCreator<P, T>;
299
- declare function createAction<A extends unknown[], P, T extends string = string>(type: T, prepare: (...args: A) => {
300
- payload: P;
301
- }): ActionCreator<P, T, A>;
302
308
  //#endregion
303
- export { Keys as A, WithPayload as C, anyEventType as D, Tracer as E, TraceEvent as F, TraceLogger as I, UnsubscribeCallback as L, Phase as M, Task as N, EffectName as O, Trace as P, WithContext as S, createBus as T, EventPayload as _, AnyEvent as a, Off as b, Command as c, ContextOf as d, ContextOption as f, EventLike as g, EventKey as h, Action as i, PayloadOf as j, EventMap as k, Commands as l, Emittable as m, FreeActionCreator as n, Bus as o, Emit as p, createAction as r, BusOptions as s, ActionCreator as t, Context as u, Handler as v, WithType as w, On as x, HandlerRef as y };
309
+ export { EventsOf as A, anyEventType as B, ContextOf as C, EventKey as D, Emittable as E, WithContext as F, WithType as I, commandCall as L, HandlerRef as M, Off as N, EventLike as O, On as P, createBus as R, Context as S, Emit as T, Bus as _, EffectName as a, CommandCall as b, PayloadOf as c, Trace as d, TraceEvent as f, AnyEvent as g, Action as h, WithPayload as i, Handler as j, EventPayload as k, Phase as l, UnsubscribeCallback as m, Free as n, EventMap as o, TraceLogger as p, FreeActionCreator as r, Keys as s, ActionCreator as t, Task as u, BusOptions as v, ContextOption as w, Commands as x, Command as y, Tracer as z };