nanoeffects 0.1.0 → 0.2.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 (51) hide show
  1. package/README.md +62 -61
  2. package/dist/{action-BM-UdmJr.d.ts → action-rqZEnRIp.d.ts} +12 -11
  3. package/dist/actions.d.ts +47 -185
  4. package/dist/actions.js +1 -1
  5. package/dist/bus-CRnlwmQ4.js +1 -0
  6. package/dist/context-DlA80GOA.js +1 -0
  7. package/dist/dev.d.ts +46 -0
  8. package/dist/dev.js +1 -0
  9. package/dist/effects.d.ts +61 -21
  10. package/dist/effects.js +1 -1
  11. package/dist/index.d.ts +1 -2
  12. package/dist/index.js +1 -1
  13. package/dist/module-BSCdR9D1.d.ts +167 -0
  14. package/dist/notrace/{action-BM-UdmJr.d.ts → action-rqZEnRIp.d.ts} +12 -11
  15. package/dist/notrace/actions.d.ts +71 -45
  16. package/dist/notrace/actions.js +1 -1
  17. package/dist/notrace/bus-Dx0l9X6h.js +1 -0
  18. package/dist/notrace/context-yAWj0BFZ.js +1 -0
  19. package/dist/notrace/{delay-Cko_Q9hl.js → delay-Cgvn16oY.js} +1 -1
  20. package/dist/notrace/effects.d.ts +61 -21
  21. package/dist/notrace/effects.js +1 -1
  22. package/dist/notrace/index.d.ts +1 -2
  23. package/dist/notrace/index.js +1 -1
  24. package/dist/notrace/policies.d.ts +1 -1
  25. package/dist/notrace/policies.js +1 -1
  26. package/dist/notrace/redux.d.ts +1 -1
  27. package/dist/notrace/redux.js +1 -1
  28. package/dist/observable.d.ts +1 -1
  29. package/dist/policies.d.ts +1 -1
  30. package/dist/policies.js +1 -1
  31. package/dist/production/dev.js +1 -0
  32. package/dist/redux.d.ts +1 -1
  33. package/dist/redux.js +1 -1
  34. package/dist/trace.d.ts +22 -4
  35. package/dist/trace.js +1 -1
  36. package/docs/README.md +4 -1
  37. package/docs/actions.md +21 -11
  38. package/docs/context.md +0 -1
  39. package/docs/effects.md +49 -31
  40. package/docs/errors.md +4 -4
  41. package/docs/guide.md +229 -0
  42. package/docs/hmr.md +20 -58
  43. package/docs/policies.md +4 -4
  44. package/docs/tracing.md +21 -8
  45. package/package.json +25 -5
  46. package/dist/bus-RUiw8f9p.js +0 -1
  47. package/dist/context-BXQ6Einm.js +0 -1
  48. package/dist/index-CaxrMD1A.d.ts +0 -1
  49. package/dist/notrace/bus-CFxgpsRL.js +0 -1
  50. package/dist/notrace/context-DGPIFXxI.js +0 -1
  51. package/dist/notrace/index-CaxrMD1A.d.ts +0 -1
@@ -1,4 +1,4 @@
1
- import { A as Keys, k as EventMap, u as Context } from "./action-BM-UdmJr.js";
1
+ import { A as Keys, N as Task, k as EventMap, u as Context } from "./action-rqZEnRIp.js";
2
2
  //#region src/utils/delay.d.ts
3
3
  /**
4
4
  * Resolves after `ms`, or rejects early if `ctx.signal` aborts. `ms` can be a
@@ -129,30 +129,36 @@ type PollOptions = {
129
129
  waitForFn?: boolean;
130
130
  };
131
131
  /**
132
- * Calls `fn` every `ms`, like patronum `interval`: the first call comes after
133
- * `ms` (with `leading` also at once), and the next ticks keep a steady rate
134
- * whatever the duration of `fn`, so a slow `fn` may overlap with the next call
135
- * (`waitForFn` starts each pause when the call ends instead). `fn` gets the
136
- * number of the call, starting at 0.
132
+ * Calls a function every `ms`, like patronum `interval`: the first call comes
133
+ * after `ms` (with `leading` also at once), and the next ticks keep a steady
134
+ * rate whatever the duration of the function, so a slow one may overlap with
135
+ * the next call (`waitForFn` starts each pause when the call ends instead). An
136
+ * error stops the polling and rejects; an abort of the caller ends it without
137
+ * an error and clears the timer. Two forms:
138
+ *
139
+ * - `poll(ms, task, options)` wraps a Task `(payload, ctx) => result` and
140
+ * returns a Task. Each call gets a context of its own, a child of the
141
+ * caller's: `bus.on("watch", poll(10_000, refresh))`.
142
+ * - `poll(ctx, fn, ms, options)` is for one call inside a body; `fn` gets the
143
+ * number of the call, starting at 0.
137
144
  *
138
145
  * `ms` can be a function of the number of the call that the pause leads to, for
139
146
  * a growing or random pause: `(call) => Math.min(1000 * 2 ** call, 60_000)`.
140
147
  *
141
- * It stops when `ctx` aborts: the result then resolves, it does not reject, and
142
- * the pending timer is cleared. If a call throws, polling stops and the result
143
- * rejects.
144
- *
145
148
  * @example
146
149
  * ```ts
150
+ * bus.on("watch", poll(10_000, (_, { signal }) => refresh({ signal })));
151
+ *
147
152
  * bus.on("watch", async (_, ctx) => {
148
- * await poll(ctx, () => refresh(), 5000);
149
- * cleanup(); // the task was aborted
153
+ * await poll(ctx, () => refresh(), 10_000);
150
154
  * });
151
155
  * ```
152
156
  */
153
- export declare const poll: (ctx: {
157
+ type PollMs = number | ((call: number) => number);
158
+ export declare function poll<P, E extends EventMap, R>(ms: PollMs, task: Task<P, E, R>, options?: PollOptions): Task<P, E, Promise<void>>;
159
+ export declare function poll(ctx: {
154
160
  signal: AbortSignal;
155
- }, fn: (tick: number) => unknown, ms: number | ((call: number) => number), options?: PollOptions) => Promise<void>;
161
+ }, fn: (tick: number) => unknown, ms: PollMs, options?: PollOptions): Promise<void>;
156
162
  //#endregion
157
163
  //#region src/effects/race.d.ts
158
164
  type RaceStarter<T> = Starter<T>;
@@ -192,9 +198,33 @@ type RetryOptions = {
192
198
  /** Return `false` to stop retrying and throw `error`. Default: always retry. */
193
199
  shouldRetry?: (error: unknown, attempt: number) => boolean;
194
200
  };
195
- export declare const retry: <T>(ctx: {
201
+ /**
202
+ * Calls a function until it succeeds or `times` attempts are used, then throws
203
+ * the last error. If the caller aborts, retrying stops and the result rejects
204
+ * with the abort reason. Two forms:
205
+ *
206
+ * - `retry(task, options)` wraps a Task `(payload, ctx) => result` and returns
207
+ * a Task. Each attempt gets a context of its own, a child of the caller's, so
208
+ * it can go straight into `on` and into `ctx.run`:
209
+ * `bus.on("load", latest(retry(load, { times: 4 })))`. The number of the
210
+ * attempt is not passed; use the other form when it is needed.
211
+ * - `retry(ctx, fn, options)` is for one call inside a body. `fn` gets the
212
+ * number of the attempt, starting at 1.
213
+ *
214
+ * @example
215
+ * ```ts
216
+ * bus.on("load", latest(retry(loadUser, { times: 4, delay: (attempt) => 2 ** attempt * 100 })));
217
+ *
218
+ * bus.on("load", async (id, ctx) => {
219
+ * const user = await retry(ctx, () => fetchUser(id, { signal: ctx.signal }), { times: 4 });
220
+ * });
221
+ * ```
222
+ */
223
+ type RetryFn<T> = (attempt: number) => T | PromiseLike<T>;
224
+ export declare function retry<P, E extends EventMap, R>(task: Task<P, E, R>, options?: RetryOptions): Task<P, E, Promise<Awaited<R>>>;
225
+ export declare function retry<T>(ctx: {
196
226
  signal?: AbortSignal;
197
- }, fn: (attempt: number) => T | PromiseLike<T>, options?: RetryOptions) => Promise<T>;
227
+ }, fn: RetryFn<T>, options?: RetryOptions): Promise<T>;
198
228
  //#endregion
199
229
  //#region src/effects/take.d.ts
200
230
  type TakeOptions<P> = {
@@ -226,19 +256,29 @@ export declare const take: <E extends EventMap, K extends Keys<E>>(ctx: Context<
226
256
  //#endregion
227
257
  //#region src/effects/timeout.d.ts
228
258
  /**
229
- * Runs `starter`; if it takes longer than `ms`, aborts its signal and rejects
230
- * with a `TimeoutError` (a `DOMException`, like `AbortSignal.timeout`), even
231
- * if the starter ignores the signal. If `ctx` aborts, rejects with its reason.
259
+ * Runs a function; if it takes longer than `ms`, aborts its signal and rejects
260
+ * with a `TimeoutError` (a `DOMException`, like `AbortSignal.timeout`), even if
261
+ * it ignores the signal. If the caller aborts, rejects with its reason. Two
262
+ * forms:
263
+ *
264
+ * - `timeout(ms, task)` wraps a Task `(payload, ctx) => result` and returns a
265
+ * Task. The Task gets a context of its own whose `signal` the time limit
266
+ * aborts: `bus.on("load", latest(timeout(5000, load)))`.
267
+ * - `timeout(ctx, ms, starter)` is for one call inside a body; `starter` gets
268
+ * `{ signal, abort }`.
232
269
  *
233
270
  * @example
234
271
  * ```ts
272
+ * bus.on("load", timeout(5000, (id, { signal }) => fetchData(id, { signal })));
273
+ *
235
274
  * bus.on("load", async (id, ctx) => {
236
275
  * const data = await timeout(ctx, 5000, ({ signal }) => fetchData(id, { signal }));
237
276
  * });
238
277
  * ```
239
278
  */
240
- export declare const timeout: <T>(ctx: {
279
+ export declare function timeout<P, E extends EventMap, R>(ms: number, task: Task<P, E, R>): Task<P, E, Promise<Awaited<R>>>;
280
+ export declare function timeout<T>(ctx: {
241
281
  signal?: AbortSignal;
242
- }, ms: number, starter: Starter<T>) => Promise<T>;
282
+ }, ms: number, starter: Starter<T>): Promise<T>;
243
283
  //#endregion
244
284
  export type { CancellableExecutor, CancellableHandle, EffectContext, PollOptions, RaceStarter, RetryOptions, TakeOptions };
@@ -1 +1 @@
1
- import{a as e,c as t,i as n,o as r,s as i,t as a}from"./context-DGPIFXxI.js";import{n as o,r as s,t as c}from"./delay-Cko_Q9hl.js";const l=(e,t)=>t?.signal?new Promise((n,r)=>{let a=i(t,r);e.then(n,r).finally(a)}):e,u=e=>Array.isArray(e)?Array(e.length):{},d=(t,o,s)=>{s?.signal?.throwIfAborted();let c=n(s??{}),u=a(c.ctx,o);return r(c.ctx,o,`start`),i(c.ctx,()=>u(`cancel`)),{tasks:Object.entries(t).map(([t,r])=>{let i=n(c.ctx),a={signal:i.ctx.signal,abort:i.ctx.abort};return[t,new Promise(t=>t(e(i.ctx,`run`,()=>r(a)))).finally(i.unlink)]}),cancelAll:()=>c.ctx.abort(),unfollowParent:c.unlink,settle:u,until:e=>l(e,s)}};async function f(e,t){let n=d(t,`all`,e);try{let e=await n.until(Promise.all(n.tasks.map(([,e])=>e))),r=u(t);for(let[t,[i]]of n.tasks.entries())r[i]=e[t];return n.settle(`done`),r}catch(e){throw n.settle(`error`,e),n.cancelAll(),e}finally{n.unfollowParent()}}const p=e=>e.then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}));async function m(e,t){let n=d(t,`allSettled`,e);try{let e=await n.until(Promise.all(n.tasks.map(([,e])=>p(e)))),r=u(t);for(let[t,[i]]of n.tasks.entries())r[i]=e[t];return n.settle(`done`),r}catch(e){throw n.settle(`error`,e),n.cancelAll(),e}finally{n.unfollowParent()}}const h=(e,n)=>e.signal.aborted?Promise.reject(e.signal.reason):new Promise((r,a)=>{let o=[],s=`pending`,c=e=>{try{e()}catch(e){t(`cancel cleanup failed`,e)}},l=i(e,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{n({resolve:u(r),reject:u(a),onCancel:d})}catch(e){u(a)(e)}}),g=async(e,t,o)=>{t?.signal?.throwIfAborted();let{ctx:s,unlink:c}=n(t??{}),u=a(s,e);r(s,e,`start`),i(s,()=>u(`cancel`));try{let e=await l(o(s),t);return u(`done`),e}catch(e){throw u(`error`,e),e}finally{c()}},_=async(t,r)=>{let{ctx:i,unlink:a}=n(t);try{return await e(i,`run`,r)}finally{a()}},v=(e,t,n,r={})=>{if(e.signal.aborted)return Promise.resolve();let a=r.leading??!1,c=r.waitForFn??!1;return g(`poll`,e,e=>new Promise((r,l)=>{let u=0,d=null,f=()=>{d!==null&&(o(d),d=null)},p=e=>{try{d=s(m,typeof n==`function`?n(e):n)}catch(e){f(),l(e)}},m=()=>{d=null,c||p(u+1),h()},h=()=>{let n=u++;_(e,()=>t(n)).then(()=>{c&&!e.signal.aborted&&p(u)},e=>{f(),l(e)})};i(e,f),a?(h(),c||p(u)):p(0)})).catch(t=>{if(!e.signal.aborted)throw t})};async function y(e,t){let n=u(t);if(Object.keys(t).length===0)return n;let r=d(t,`race`,e);try{let[e,t]=await r.until(Promise.race(r.tasks.map(([e,t])=>t.then(t=>[e,t]))));return n[e]=t,r.settle(`done`),n}catch(e){throw r.settle(`error`,e),e}finally{r.unfollowParent(),r.cancelAll()}}const b=(e,t,n)=>typeof e==`function`?e(t,n):e,x=e=>({times:e.times??3,delay:e.delay??0,shouldRetry:e.shouldRetry??(()=>!0)}),S=(e,t,n={})=>g(`retry`,e,async e=>{let{times:r,delay:i,shouldRetry:a}=x(n);for(let n=1;;n++)try{return await _(e,()=>t(n))}catch(t){if(e.signal.throwIfAborted(),n>=r||!a(t,n))throw t;let o=b(i,n,t);o>0&&await c(e,o)}}),C=(e,t,n={})=>g(`take`,e,r=>new Promise((a,o)=>{let{predicate:s}=n,c=[],l=()=>{for(let e of c)e()},u=e=>{l(),o(e)};c.push(e.on(t,e=>{(!s||s(e))&&(l(),a(e))})),c.push(i(r,u)),c.push(i({signal:n.signal},u))})),w=(t,r,i)=>g(`timeout`,t,async t=>{let a=n(t),c=s(()=>a.ctx.abort(new DOMException(`Timed out after ${r}ms`,`TimeoutError`)),r);try{return await l(Promise.resolve(e(a.ctx,`run`,()=>i({signal:a.ctx.signal,abort:a.ctx.abort}))),a.ctx)}finally{o(c),a.unlink()}});export{f as all,m as allSettled,h as cancellable,c as delay,i as onAbort,v as poll,y as race,S as retry,C as take,w as timeout};
1
+ import{a as e,c as t,i as n,o as r,s as i,t as a}from"./context-yAWj0BFZ.js";import{n as o,r as s,t as c}from"./delay-Cgvn16oY.js";const l=(e,t)=>t?.signal?new Promise((n,r)=>{let a=i(t,r);e.then(n,r).finally(a)}):e,u=async(e,t,o,s)=>{t?.signal?.throwIfAborted();let{ctx:c,unlink:u}=n(t??{}),d=a(c,e);r(c,e,`start`),i(c,()=>d(`cancel`));try{let e=await l(o(c),t);return d(`done`),s?.(!1),e}catch(e){throw d(`error`,e),s?.(!0),e}finally{u()}},d=async(t,r)=>{let{ctx:i,unlink:a}=n(t);try{return await e(i,`run`,()=>r(i))}finally{a()}},f=e=>e,p=(e,t)=>n=>e(t,f(n)),m=e=>Array.isArray(e)?Array(e.length):{},h=(t,r)=>{let i=Object.entries(r).map(([r,i])=>{let a=n(t),o={signal:a.ctx.signal,abort:a.ctx.abort},s=()=>i(o);return{key:r,task:new Promise(t=>t(e(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,n)=>e.signal.aborted?Promise.reject(e.signal.reason):new Promise((r,a)=>{let o=[],s=`pending`,c=e=>{try{e()}catch(e){t(`cancel cleanup failed`,e)}},l=i(e,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{n({resolve:u(r),reject:u(a),onCancel:d})}catch(e){u(a)(e)}}),x=(e,t,n,r)=>{if(e.signal.aborted)return Promise.resolve();let a=r.leading??!1,c=r.waitForFn??!1;return u(`poll`,e,e=>new Promise((r,l)=>{let u=0,f=null,p=()=>{f!==null&&(o(f),f=null)},m=e=>{try{f=s(h,typeof n==`function`?n(e):n)}catch(e){p(),l(e)}},h=()=>{f=null,c||m(u+1),g()},g=()=>{let n=u++;d(e,e=>t(e,n)).then(()=>{c&&!e.signal.aborted&&m(u)},e=>{p(),l(e)})};i(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,t,n={})=>u(`take`,e,r=>new Promise((a,o)=>{let{predicate:s}=n,c=[],l=()=>{for(let e of c)e()},u=e=>{l(),o(e)};c.push(e.on(t,e=>{(!s||s(e))&&(l(),a(e))})),c.push(i(r,u)),c.push(i({signal:n.signal},u))})),k=(t,r,i)=>u(`timeout`,t,async t=>{let a=n(t),c=s(()=>a.ctx.abort(new DOMException(`Timed out after ${r}ms`,`TimeoutError`)),r);try{return await l(Promise.resolve(e(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,i as onAbort,S as poll,C as race,D as retry,O as take,A as timeout};
@@ -1,3 +1,2 @@
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-BM-UdmJr.js";
2
- import "./index-CaxrMD1A.js";
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-rqZEnRIp.js";
3
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 +1 @@
1
- export{n as anyEventType,t as createBus}from"./bus-CFxgpsRL.js";
1
+ export{n as anyEventType,t as createBus}from"./bus-Dx0l9X6h.js";
@@ -1,4 +1,4 @@
1
- import { N as Task, k as EventMap } from "./action-BM-UdmJr.js";
1
+ import { N as Task, k as EventMap } from "./action-rqZEnRIp.js";
2
2
  //#region src/policies/keyed.d.ts
3
3
  /** Options shared by every policy that can keep its state per key. */
4
4
  type KeyOption<P> = {
@@ -1 +1 @@
1
- import{a as e,i as t,o as n,s as r,t as i}from"./context-DGPIFXxI.js";import{n as a,r as o,t as s}from"./delay-Cko_Q9hl.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={})=>{let n=t=>p(`latest`,(t,n,r)=>(r(),e(t,n)),t);return t.key?d(t.key,n):n(u)},p=(e,a,o=u)=>{let s=null;return(l,u)=>{if(s?.abort(),u.signal.aborted)return;let{ctx:d,unlink:f}=t(u);s=d;let p=i(d,e),m=!1,h=()=>{d.signal.aborted||(m=!0,n(d,e,`start`))};r(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}}},m=(e,t,n={})=>{let r=r=>g(e,t,n,r);return n.key?d(n.key,r):r(u)},h=(t,r,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>=t||p-l<0;return l=p,d!==null&&a(d),d=c?o(s,t):null,i&&m?Promise.resolve(e(f,`debounce`,()=>r(u,f))):(n(f,`debounce`,`drop`),Promise.resolve(void 0))}},g=(t,n,i,a)=>{let o=i.leading??!1,c=i.trailing??!0;if(!c)return h(t,n,o,a);let l=!1,u=0,d=p(`debounce`,([e,i],a,o)=>{let d=u;return r(a,()=>{u===d&&(l=!1)}),s(a,t).then(()=>{if(l=!1,c&&!i)return o(),n(e,a)})},a);return(t,r)=>{if(r.signal.aborted)return;let i=o&&!l;l=!0,u++;let a=d([t,i],r);return i?Promise.resolve(e(r,`debounce`,()=>n(t,r))):a}},_=(t,i={})=>{let a=i=>{let a=null;return(o,s)=>{if(s.signal.aborted)return;if(a){n(s,`leading`,`drop`);return}let c={};a=c;let u=()=>{d(),a===c&&(a=null,i())},d=r(s,u);try{return l(e(s,`leading`,()=>t(o,s)),u)}catch(e){throw u(),e}}};return i.key?d(i.key,a):a(u)},v=(e,t={})=>{let n=n=>y(e,t,n);return t.key?d(t.key,n):n(u)},y=(t,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(),n(p,`queue`,`drop`),m(void 0)},v=()=>{if(g(),p.signal.aborted){_();return}l++;let n=!0,i=()=>{n&&(n=!1,l--,d(),l===0&&c.length===0&&a())},o=r(p,()=>{i(),m(void 0)}),s;try{s=Promise.resolve(e(p,`queue`,()=>t(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=r(p,()=>{let e=c.indexOf(y);e>=0&&c.splice(e,1),_()})})},b=(e,t,n={})=>{let r=r=>x(e,t,n,r);return n.key?d(n.key,r):r(u)},x=(t,r,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=e=>f===null||e-f>=t||e-f<0||e-p>=t,y=e=>n(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=(t,n,i)=>{b(),p=i,_=!0;try{return e(n,`throttle`,()=>r(t,n))}finally{_=!1}},C=()=>{d&&(h=o(w,t))},w=()=>{if(h=null,!d||m!==null||g)return;let e=Date.now(),n=Math.max(t-(e-(f??-1/0)),t-(e-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 e=Date.now();v(e)?T(e):m=o(E,Math.min(t-(e-(f??e)),t-(e-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=(e,n,r,i)=>i&&m!==null?(a(m),m=o(E,t),S(e,n,r)):m===null&&(m=o(E,t),i&&(p=r,c))?S(e,n,r):O(e,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{m as debounce,f as latest,_ as leading,v as queue,b as throttle};
1
+ import{a as e,i as t,o as n,s as r,t as i}from"./context-yAWj0BFZ.js";import{n as a,r as o,t as s}from"./delay-Cgvn16oY.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=(e,a,o=u)=>{let s=null;return(l,u)=>{if(s?.abort(),u.signal.aborted)return;let{ctx:d,unlink:f}=t(u);s=d;let p=i(d,e),m=!1,h=()=>{d.signal.aborted||(m=!0,n(d,e,`start`))};r(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=(t,r,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>=t||p-l<0;return l=p,d!==null&&a(d),d=c?o(s,t):null,i&&m?Promise.resolve(e(f,`debounce`,()=>r(u,f))):(n(f,`debounce`,`drop`),Promise.resolve(void 0))}},_=(t,n,i,a)=>{let o=i.leading??!1,c=i.trailing??!0;if(!c)return g(t,n,o,a);let l=!1,u=0,d=m(`debounce`,([e,i],a,o)=>{let d=u;return r(a,()=>{u===d&&(l=!1)}),s(a,t).then(()=>{if(l=!1,c&&!i)return o(),n(e,a)})},a);return(t,r)=>{if(r.signal.aborted)return;let i=o&&!l;l=!0,u++;let a=d([t,i],r);return i?Promise.resolve(e(r,`debounce`,()=>n(t,r))):a}},v=(t,i={})=>f(i.key,i=>{let a=null;return(o,s)=>{if(s.signal.aborted)return;if(a){n(s,`leading`,`drop`);return}let c={};a=c;let u=()=>{d(),a===c&&(a=null,i())},d=r(s,u);try{return l(e(s,`leading`,()=>t(o,s)),u)}catch(e){throw u(),e}}}),y=(e,t={})=>f(t.key,n=>b(e,t,n)),b=(t,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(),n(p,`queue`,`drop`),m(void 0)},v=()=>{if(g(),p.signal.aborted){_();return}l++;let n=!0,i=()=>{n&&(n=!1,l--,d(),l===0&&c.length===0&&a())},o=r(p,()=>{i(),m(void 0)}),s;try{s=Promise.resolve(e(p,`queue`,()=>t(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=r(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=(t,r,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=e=>f===null||e-f>=t||e-f<0||e-p>=t,y=e=>n(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=(t,n,i)=>{b(),p=i,_=!0;try{return e(n,`throttle`,()=>r(t,n))}finally{_=!1}},C=()=>{d&&(h=o(w,t))},w=()=>{if(h=null,!d||m!==null||g)return;let e=Date.now(),n=Math.max(t-(e-(f??-1/0)),t-(e-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 e=Date.now();v(e)?T(e):m=o(E,Math.min(t-(e-(f??e)),t-(e-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=(e,n,r,i)=>i&&m!==null?(a(m),m=o(E,t),S(e,n,r)):m===null&&(m=o(E,t),i&&(p=r,c))?S(e,n,r):O(e,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};
@@ -1,4 +1,4 @@
1
- import { k as EventMap, o as Bus, s as BusOptions } from "./action-BM-UdmJr.js";
1
+ import { k as EventMap, o as Bus, s as BusOptions } from "./action-rqZEnRIp.js";
2
2
  //#region src/store/redux/middleware.d.ts
3
3
  /** What Redux passes through a middleware: an action is a plain object with a `type`. */
4
4
  type ReduxAction = {
@@ -1 +1 @@
1
- import{c as e}from"./context-DGPIFXxI.js";import{t}from"./bus-CFxgpsRL.js";const n=e=>typeof e==`object`&&!!e&&`type`in e,r=(e,t={})=>{let r=e.emit;return()=>e=>i=>{let a=e(i);return n(i)&&(t.filter?.(i)??!0)&&r(i),a}},i=(n={})=>{let{filter:i,context:a,onError:o,...s}=n,c,l=/* @__PURE__ */ new WeakMap,u=e=>({...a,store:c&&{getState:c.getState,dispatch:t=>{typeof t==`object`&&t&&l.set(t,e.type);let n=t;return c?.dispatch(n)}}}),d=t({...s,onError:o??((t,{type:n})=>{e(`handler of "${n}" failed`,t)}),context:u}),f=r(d,{filter:e=>l.get(e)!==e.type&&(i?.(e)??!0)});return{bus:d,middleware:e=>(c=e,f())}};export{i as createEffectsMiddleware,r as nanoeffectsMiddleware};
1
+ import{c as e}from"./context-yAWj0BFZ.js";import{t}from"./bus-Dx0l9X6h.js";const n=e=>typeof e==`object`&&!!e&&`type`in e,r=(e,t={})=>{let r=e.emit;return()=>e=>i=>{let a=e(i);return n(i)&&(t.filter?.(i)??!0)&&r(i),a}},i=(n={})=>{let{filter:i,context:a,onError:o,...s}=n,c,l=/* @__PURE__ */ new WeakMap,u=e=>({...a,store:c&&{getState:c.getState,dispatch:t=>{typeof t==`object`&&t&&l.set(t,e.type);let n=t;return c?.dispatch(n)}}}),d=t({...s,onError:o??((t,{type:n})=>{e(`handler of "${n}" failed`,t)}),context:u}),f=r(d,{filter:e=>l.get(e)!==e.type&&(i?.(e)??!0)});return{bus:d,middleware:e=>(c=e,f())}};export{i as createEffectsMiddleware,r as nanoeffectsMiddleware};
@@ -1,4 +1,4 @@
1
- import { A as Keys, k as EventMap, o as Bus } from "./action-BM-UdmJr.js";
1
+ import { A as Keys, k as EventMap, o as Bus } from "./action-rqZEnRIp.js";
2
2
  //#region src/observable.d.ts
3
3
  declare global {
4
4
  interface SymbolConstructor {
@@ -1,4 +1,4 @@
1
- import { N as Task, k as EventMap } from "./action-BM-UdmJr.js";
1
+ import { N as Task, k as EventMap } from "./action-rqZEnRIp.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-BXQ6Einm.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={})=>{let n=t=>p(`latest`,(t,n,r)=>(r(),e(t,n)),t);return t.key?d(t.key,n):n(u)},p=(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}}},m=(e,t,n={})=>{let r=r=>g(e,t,n,r);return n.key?d(n.key,r):r(u)},h=(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))}},g=(n,r,i,a)=>{let o=i.leading??!1,c=i.trailing??!0;if(!c)return h(n,r,o,a);let l=!1,u=0,d=p(`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}},_=(n,i={})=>{let a=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}}};return i.key?d(i.key,a):a(u)},v=(e,t={})=>{let n=n=>y(e,t,n);return t.key?d(t.key,n):n(u)},y=(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),_()})})},b=(e,t,n={})=>{let r=r=>x(e,t,n,r);return n.key?d(n.key,r):r(u)},x=(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{m as debounce,f as latest,_ as leading,v as queue,b as throttle};
1
+ import{t as e}from"./onAbort-BgMfspK4.js";import{a as t,i as n,o as r,t as i}from"./context-DlA80GOA.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};
@@ -0,0 +1 @@
1
+ const e=()=>{},t=()=>e,n=()=>t;export{n as devPlugin};
package/dist/redux.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { k as EventMap, o as Bus, s as BusOptions } from "./action-BM-UdmJr.js";
1
+ import { k as EventMap, o as Bus, s as BusOptions } from "./action-rqZEnRIp.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/redux.js CHANGED
@@ -1 +1 @@
1
- import{n as e}from"./onAbort-BgMfspK4.js";import{t}from"./bus-RUiw8f9p.js";const n=e=>typeof e==`object`&&!!e&&`type`in e,r=(e,t={})=>{let r=e.emit;return()=>e=>i=>{let a=e(i);return n(i)&&(t.filter?.(i)??!0)&&r(i),a}},i=(n={})=>{let{filter:i,context:a,onError:o,...s}=n,c,l=/* @__PURE__ */ new WeakMap,u=e=>({...a,store:c&&{getState:c.getState,dispatch:t=>{typeof t==`object`&&t&&l.set(t,e.type);let n=t;return c?.dispatch(n)}}}),d=t({...s,onError:o??((t,{type:n})=>{e(`handler of "${n}" failed`,t)}),context:u}),f=r(d,{filter:e=>l.get(e)!==e.type&&(i?.(e)??!0)});return{bus:d,middleware:e=>(c=e,f())}};export{i as createEffectsMiddleware,r as nanoeffectsMiddleware};
1
+ import{n as e}from"./onAbort-BgMfspK4.js";import{t}from"./bus-CRnlwmQ4.js";const n=e=>typeof e==`object`&&!!e&&`type`in e,r=(e,t={})=>{let r=e.emit;return()=>e=>i=>{let a=e(i);return n(i)&&(t.filter?.(i)??!0)&&r(i),a}},i=(n={})=>{let{filter:i,context:a,onError:o,...s}=n,c,l=/* @__PURE__ */ new WeakMap,u=e=>({...a,store:c&&{getState:c.getState,dispatch:t=>{typeof t==`object`&&t&&l.set(t,e.type);let n=t;return c?.dispatch(n)}}}),d=t({...s,onError:o??((t,{type:n})=>{e(`handler of "${n}" failed`,t)}),context:u}),f=r(d,{filter:e=>l.get(e)!==e.type&&(i?.(e)??!0)});return{bus:d,middleware:e=>(c=e,f())}};export{i as createEffectsMiddleware,r as nanoeffectsMiddleware};
package/dist/trace.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { E as Tracer, F as TraceEvent, I as TraceLogger, P as Trace } from "./action-BM-UdmJr.js";
1
+ import { E as Tracer, F as TraceEvent, I as TraceLogger, P as Trace } from "./action-rqZEnRIp.js";
2
2
  //#region src/trace/console.d.ts
3
3
  type ConsoleLoggerOptions = {
4
4
  /** Defaults to `globalThis.console`. */
@@ -77,6 +77,7 @@ type ReduxConnection = {
77
77
  type ReduxExtension = {
78
78
  connect: (options?: {
79
79
  name?: string;
80
+ maxAge?: number;
80
81
  }) => ReduxConnection;
81
82
  };
82
83
  type ReduxDevtoolsOptions = {
@@ -84,6 +85,20 @@ type ReduxDevtoolsOptions = {
84
85
  name?: string;
85
86
  /** Defaults to `globalThis.__REDUX_DEVTOOLS_EXTENSION__`. */
86
87
  extension?: ReduxExtension;
88
+ /**
89
+ * Also send the start of every task as an action. Default `false`: a task
90
+ * is one action, sent when it ends, and the tasks that are running are in
91
+ * the state.
92
+ */
93
+ starts?: boolean;
94
+ /**
95
+ * How deep in the tree of tasks to show: `0` is the handler tasks only, `1`
96
+ * also the effects they start (`retry`, `timeout`, ...), and so on. The
97
+ * attempts and the starters under them are hidden at the default, `1`.
98
+ */
99
+ depth?: number;
100
+ /** The number of actions that DevTools keeps; its own default is 50. */
101
+ maxAge?: number;
87
102
  };
88
103
  type ReduxDevtoolsState = {
89
104
  /** Tasks that started and have not settled yet, by trace id. */
@@ -93,9 +108,12 @@ type ReduxDevtoolsState = {
93
108
  }>;
94
109
  };
95
110
  /**
96
- * Trace logger that sends every task phase to Redux DevTools as an action
97
- * `type/effect/phase`, with the running tasks as state. Read-only: there is
98
- * no time travel. Does nothing when the extension is not installed.
111
+ * Trace logger that sends the tasks to Redux DevTools as actions
112
+ * `type/effect/phase`, with the running tasks as state. By default it is quiet:
113
+ * a task is one action, when it ends, and only the handler tasks and the
114
+ * effects they start are shown (see `starts` and `depth` to show more).
115
+ * Read-only: there is no time travel. Does nothing when the extension is not
116
+ * installed.
99
117
  *
100
118
  * @example
101
119
  * ```ts
package/dist/trace.js CHANGED
@@ -1 +1 @@
1
- import{n as e,t}from"./onAbort-BgMfspK4.js";import{n}from"./promise-CSLpzwSR.js";const r=(e={})=>{let t=e.console??globalThis.console,n=e.prefix??`[nanoeffects]`,r=/* @__PURE__ */ new Map;return({id:e,parent:i,type:a,effect:o,phase:s,payload:c,error:l})=>{r.has(e)||r.set(e,typeof i==`number`?(r.get(i)??0)+1:0);let u=` `.repeat(r.get(e)??0);s!==`start`&&r.delete(e);let d=`${n} ${u}${a} · ${o} ${s}`;s===`error`?t?.error(d,l):t?.debug(d,c)}},i=(e={})=>{let t=e.performance??globalThis.performance,n=/* @__PURE__ */ new Map;return({id:e,parent:r,type:i,effect:a,phase:o,error:s})=>{if(!t)return;if(o===`start`){n.set(e,t.now());return}let c=`${i} · ${a} (${o})`,l={id:e,parent:r,phase:o,...o===`error`&&{error:String(s)}},u=n.get(e);n.delete(e),typeof u==`number`?t.measure(c,{start:u,end:t.now(),detail:l}):t.mark(c,{detail:l})}},a=()=>{let e=[];return Object.assign(t=>{e.push(t)},{events:e,clear:()=>{e.length=0}})},o=()=>{},s=(e={})=>{let t=e.extension??globalThis.__REDUX_DEVTOOLS_EXTENSION__;if(!t)return o;let n=t.connect({name:e.name??`nanoeffects`}),r=/* @__PURE__ */ new Map,i=()=>({running:Object.fromEntries(r)});return n.init(i()),({error:e,...t})=>{t.phase===`start`?r.set(t.id,{type:t.type,effect:t.effect}):r.delete(t.id),n.send({...t,type:`${t.type}/${t.effect}/${t.phase}`,event:t.type,...t.phase===`error`&&{error:e instanceof Error?e.message:e}},i())}},c=t=>{let n=typeof t==`function`?[t]:[...t??[]];return n.length===0?null:t=>{for(let r of n)try{r(t)}catch(t){e(`trace logger failed`,t)}}},l=()=>{},u=(e,r={})=>{let i=c(e),{filter:a,sample:o=1}=r,s=0,u=e=>typeof o==`function`?o({type:e.type,payload:e.payload}):Math.random()<o,d={task:(e,t)=>({id:++s,parent:t?.id,type:e.type,payload:e.payload,sampled:t?.sampled??u(e)}),report:(e,t,n,r)=>{if(!e.sampled)return;let o={id:e.id,type:e.type,effect:t,phase:n,payload:e.payload};typeof e.parent==`number`&&(o.parent=e.parent),n===`error`&&(o.error=r),(!a||a(o))&&i?.(o)},settler:(e,t)=>{if(!e.sampled)return l;let n=!1;return(r,i)=>{n||(n=!0,d.report(e,t,r,i))}},run:(e,r,i,a)=>{if(!e.sampled)return a();let o=d.settler(e,r);d.report(e,r,`start`),t({signal:i},()=>o(`cancel`));let s;try{s=a()}catch(e){throw o(`error`,e),e}return n(s)?Promise.resolve(s).then(e=>(o(`done`),e),e=>{throw o(`error`,e),e}):(o(`done`),s)}};return d};export{r as consoleLogger,a as createTraceRecorder,u as createTracer,i as performanceLogger,s as reduxDevtools};
1
+ import{n as e,t}from"./onAbort-BgMfspK4.js";import{n}from"./promise-CSLpzwSR.js";const r=(e={})=>{let t=e.console??globalThis.console,n=e.prefix??`[nanoeffects]`,r=/* @__PURE__ */ new Map;return({id:e,parent:i,type:a,effect:o,phase:s,payload:c,error:l})=>{r.has(e)||r.set(e,typeof i==`number`?(r.get(i)??0)+1:0);let u=` `.repeat(r.get(e)??0);s!==`start`&&r.delete(e);let d=`${n} ${u}${a} · ${o} ${s}`;s===`error`?t?.error(d,l):t?.debug(d,c)}},i=(e={})=>{let t=e.performance??globalThis.performance,n=/* @__PURE__ */ new Map;return({id:e,parent:r,type:i,effect:a,phase:o,error:s})=>{if(!t)return;if(o===`start`){n.set(e,t.now());return}let c=`${i} · ${a} (${o})`,l={id:e,parent:r,phase:o,...o===`error`&&{error:String(s)}},u=n.get(e);n.delete(e),typeof u==`number`?t.measure(c,{start:u,end:t.now(),detail:l}):t.mark(c,{detail:l})}},a=()=>{let e=[];return Object.assign(t=>{e.push(t)},{events:e,clear:()=>{e.length=0}})},o=()=>{},s=(e={})=>{let t=e.extension??globalThis.__REDUX_DEVTOOLS_EXTENSION__;if(!t)return o;let{starts:n=!1,depth:r=1,maxAge:i}=e,a=t.connect({name:e.name??`nanoeffects`,...i!==void 0&&{maxAge:i}}),s=/* @__PURE__ */ new Map,c=/* @__PURE__ */ new Map,l=()=>({running:Object.fromEntries(s)});return a.init(l()),({error:e,...t})=>{let i=(t.parent===void 0?-1:c.get(t.parent)??-1)+1,o=i<=r;t.phase===`start`?(c.set(t.id,i),o&&s.set(t.id,{type:t.type,effect:t.effect})):(c.delete(t.id),s.delete(t.id)),o&&(t.phase!==`start`||n)&&a.send({...t,type:`${t.type}/${t.effect}/${t.phase}`,event:t.type,...t.phase===`error`&&{error:e instanceof Error?e.message:e}},l())}},c=t=>{let n=typeof t==`function`?[t]:[...t??[]];return n.length===0?null:t=>{for(let r of n)try{r(t)}catch(t){e(`trace logger failed`,t)}}},l=()=>{},u=(e,r={})=>{let i=c(e),{filter:a,sample:o=1}=r,s=0,u=e=>typeof o==`function`?o({type:e.type,payload:e.payload}):Math.random()<o,d={task:(e,t)=>({id:++s,parent:t?.id,type:e.type,payload:e.payload,sampled:t?.sampled??u(e)}),report:(e,t,n,r)=>{if(!e.sampled)return;let o={id:e.id,type:e.type,effect:t,phase:n,payload:e.payload};typeof e.parent==`number`&&(o.parent=e.parent),n===`error`&&(o.error=r),(!a||a(o))&&i?.(o)},settler:(e,t)=>{if(!e.sampled)return l;let n=!1;return(r,i)=>{n||(n=!0,d.report(e,t,r,i))}},run:(e,r,i,a)=>{if(!e.sampled)return a();let o=d.settler(e,r);d.report(e,r,`start`),t({signal:i},()=>o(`cancel`));let s;try{s=a()}catch(e){throw o(`error`,e),e}return n(s)?Promise.resolve(s).then(e=>(o(`done`),e),e=>{throw o(`error`,e),e}):(o(`done`),s)}};return d};export{r as consoleLogger,a as createTraceRecorder,u as createTracer,i as performanceLogger,s as reduxDevtools};
package/docs/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Documentation
2
2
 
3
+ Start with the [Guide](guide.md): from a bare bus to modules, one step at a time. The pages below are the reference.
4
+
5
+ - [Guide](guide.md)
3
6
  - [Policies](policies.md): `latest`, `leading`, `debounce`, `throttle`, `queue`, `key`
4
7
  - [Effects and cancellation](effects.md): `retry`, `timeout`, `poll`, `take`, `all`, `allSettled`, `race`, `ctx.on`
5
8
  - [Actions, commands, jobs and modules](actions.md)
@@ -8,5 +11,5 @@
8
11
  - [Tracing](tracing.md): loggers, Redux DevTools, sampling, the `notrace` build
9
12
  - [Redux, Zustand, MobX](stores.md)
10
13
  - [Observable: events as streams](observable.md): `createObserve` for RxJS and friends
11
- - [Hot reload](hmr.md)
14
+ - [Hot reload and dev diagnostics](hmr.md): modules replaced by name, `devPlugin`
12
15
  - [Semantics](semantics.md)
package/docs/actions.md CHANGED
@@ -18,8 +18,11 @@ const getUser = createCommand("user/get", (id: string, { signal }) => fetchUser(
18
18
  // a contract without a handler: the code that sends mail is elsewhere
19
19
  const sendMail = createCommand<Mail, void>("mail/send");
20
20
 
21
- // a command whose outcome is sent on as events
22
- const loadUser = createJob("user/load", fetchUser, { done: userLoaded, fail: loadFailed });
21
+ // a command whose outcome is sent on as actions: the command and its two actions together
22
+ const { command: loadUser, successAction, failAction } = createJob("user/load", fetchUser, {
23
+ success: userLoaded, // an action creator that exists: used and returned as it is
24
+ fail: "user/failed", // a string: the job makes the action creator
25
+ });
23
26
  ```
24
27
 
25
28
  **Actions.** `createAction(type)` returns a creator, with no generics: it keeps the literal type, and the bus checks the payload against its event map (`bus.emit(userLoaded(5))` is an error when `"user/loaded"` is a `User`). Without an event map give the payload type, `createAction<User>("user/loaded")`. The creator: `userLoaded(user)` is `{ type, payload }`, `userLoaded.type` is the type, `userLoaded.match(action)` narrows an action. `createAction(type, prepare)` builds the payload from several arguments. A creator works as the event of its type: `bus.emit(userLoaded(user))`, `bus.on(userLoaded, fn)`.
@@ -31,30 +34,36 @@ const loadUser = createJob("user/load", fetchUser, { done: userLoaded, fail: loa
31
34
  ```ts
32
35
  import { createCommands, bindHandler } from "nanoeffects/actions";
33
36
 
34
- const commands = createCommands<Events>([bindHandler(sendMail, smtpSend)]);
35
- const bus = createBus<Events>({ commands });
37
+ const bus = createBus<Events>();
38
+ const commands = createCommands(bus, [bindHandler(sendMail, smtpSend)]); // attached to the bus
36
39
 
37
40
  const unbind = commands.handle(getUser, async () => fakeUser); // later, e.g. in a test
38
41
  unbind(); // the command's own handler is back; the latest binding wins
39
42
  ```
40
43
 
41
- A command with no handler anywhere throws `No handler for command "..."` from `emit`. The registry lives in `nanoeffects/actions`, so a bus without `commands` carries none of this code; without it `emit(command, payload)` simply runs the command's own handler.
44
+ A command with no handler anywhere throws `No handler for command "..."` from `emit`. The registry lives in `nanoeffects/actions`, so a bus without one carries none of this code; without it `emit(command, payload)` simply runs the command's own handler. `createCommands(bus)` sets the registry as `bus.commands`, and a second call on the same bus replaces it. There is no `commands` option in `createBus`; a custom runner is assigned, `bus.commands = { run }`.
45
+
46
+ **Jobs.** `createJob(type, handler, { success, fail })` makes a command that sends its outcome on as actions, for when nobody waits for the result, and returns `{ command, successAction, failAction }`. When the handler succeeds, `successAction(result)` is emitted on the bus, and when it fails, `failAction(error)`.
42
47
 
43
- **Jobs.** `createJob(type, handler, { done, fail })` is a command that sends its outcome on, for when nobody waits for the result. `done` receives the result and `fail` receives the error; each is anything `emit` takes: an event type, an action creator, a command, or a plain action. `bus.emit(job, payload)` still returns a promise of the result. With `fail` set the error counts as handled: it goes to `fail` and the promise resolves with `undefined`; without it the promise rejects. The outcome is routed also for a handler bound with `commands.handle`, and is not sent once the task is aborted.
48
+ - `success` and `fail` are a string, from which the job makes an action creator with `createAction` (the payload is the result, or the error), or an action creator that exists already, which is used and returned as it is. A missing one is made with the type `<type>/success` or `<type>/fail`.
49
+ - `bus.emit(command, payload)` returns a promise of the result. An error of the handler always counts as handled: it goes to `failAction` and the promise resolves with `undefined`. For a result that must reject, use a command.
50
+ - The actions are emitted as the task that ran the job, also for a handler bound with `commands.handle`, and are not sent once the task is aborted. A missing handler is not an outcome: `emit` throws `No handler for command "..."`.
51
+ - Calling `command(payload)` directly runs only the handler.
44
52
 
45
53
  ## Modules
46
54
 
47
- `registerModules(bus, modules)` from `nanoeffects/actions` assembles an app from modules and returns `mount`, `unmount`, `isMounted`, `dispose` and `commands`, the registry of command handlers. A module is what a domain of the app needs: the commands it handles, the events it listens to, its sub-modules and its background tasks.
55
+ `bus.use(modulePlugin(modules))`, with `modulePlugin` from `nanoeffects/actions`, assembles an app from modules and returns `mount`, `unmount`, `isMounted`, `dispose` and `commands`, the registry of command handlers. A module is what a domain of the app needs: the commands it handles, the events it listens to, its sub-modules and its background tasks.
48
56
 
49
57
  ```ts
50
58
  const userModule = createModule<Events>("user", (m) => {
51
- m.handle(getUser, fetchUser);
59
+ m.handle(getUser, fetchUser); // a contract gets its handler here
60
+ m.use(saveUser, authModule); // a command that carries a handler, and a sub-module
52
61
  m.on("login", (user, ctx) => ctx.emit("loaded", user));
53
62
  m.spawn(async (_, ctx) => { /* background work, ends with the module */ });
54
63
  });
55
64
 
56
65
  const bus = createBus<Events>();
57
- const app = registerModules(bus, [userModule]);
66
+ const app = bus.use(modulePlugin([userModule]));
58
67
 
59
68
  const unmount = app.mount(orderModule); // later, e.g. after a lazy import
60
69
  app.commands.handle(getUser, fakeUser); // in a test
@@ -63,6 +72,7 @@ app.dispose(); // unmounts every module
63
72
 
64
73
  - **Unmounting undoes everything** the module did: its subscriptions, its bound commands, its sub-modules and its background tasks, which are aborted.
65
74
  - **A module is known by its name.** Mounting a module whose name is taken replaces the mounted one. That is what hot reload needs: see [hmr.md](hmr.md).
66
- - **The registry.** If the bus was made with `createBus({ commands: createCommands() })`, that registry is used; otherwise the app brings its own. A bus has `commands` as a field, so the commands that modules bind are the ones it runs.
75
+ - **The registry.** If `createCommands(bus)` was called, that registry is used; otherwise the module plugin attaches its own. Either way it is `bus.commands` and `app.commands`, so the commands that modules bind are the ones the bus runs.
67
76
  - A disposed app cannot mount modules.
68
- - A module context is a task context plus `handle` (bind a command), `use` (mount sub-modules, unmounted with this one) and `spawn` (a background task, aborted on unmount).
77
+ - A module context is a task context plus `handle` (give a command a handler: for a contract, or to override), `use` (bring sub-modules and commands that carry a handler: `m.use(getUser, saveUser, subModule)`, in any order; the returned function undoes just that call) and `spawn` (a background task, aborted on unmount).
78
+ - A command that carries a handler keeps answering after its module is unmounted: the handler is its own, and `use` only brings the command and tracks it in the registry. Use `handle` for a handler that should live only as long as the module.
package/docs/context.md CHANGED
@@ -13,4 +13,3 @@ The fields are in the context of handlers, `ctx.run`, listeners added with `ctx.
13
13
 
14
14
  - The fields of the library (`signal`, `emit`, `run`, ...) win when the names are the same.
15
15
  - `context` can also be a function, `(task) => fields`, called with `{ type, payload }` of each task, for fields that depend on the task.
16
- - Several stores are fields of one object: `WithContext<{ stores: { user: UserStore; cart: CartStore } }>`.
package/docs/effects.md CHANGED
@@ -8,13 +8,13 @@ An abort only raises a flag: your code decides to stop. `nanoeffects/effects` ha
8
8
  import { cancellable } from "nanoeffects/effects";
9
9
 
10
10
  bus.on(
11
- "wait",
12
- latest((_, ctx) =>
13
- cancellable(ctx, ({ resolve, onCancel }) => {
14
- const timer = setTimeout(resolve, 5000);
15
- onCancel(() => clearTimeout(timer));
16
- }),
17
- ),
11
+ "wait",
12
+ latest((_, ctx) =>
13
+ cancellable(ctx, ({ resolve, onCancel }) => {
14
+ const timer = setTimeout(resolve, 5000);
15
+ onCancel(() => clearTimeout(timer));
16
+ }),
17
+ ),
18
18
  );
19
19
  ```
20
20
 
@@ -28,8 +28,8 @@ bus.on(
28
28
  import { onAbort } from "nanoeffects/effects";
29
29
 
30
30
  bus.on("connect", (url, ctx) => {
31
- const socket = new WebSocket(url);
32
- onAbort(ctx, () => socket.close());
31
+ const socket = new WebSocket(url);
32
+ onAbort(ctx, () => socket.close());
33
33
  });
34
34
  ```
35
35
 
@@ -39,8 +39,8 @@ bus.on("connect", (url, ctx) => {
39
39
 
40
40
  ```ts
41
41
  const { user, posts } = await allSettled(ctx, {
42
- user: ({ signal }) => fetchUser(id, { signal }),
43
- posts: ({ signal }) => fetchPosts(id, { signal }),
42
+ user: ({ signal }) => fetchUser(id, { signal }),
43
+ posts: ({ signal }) => fetchPosts(id, { signal }),
44
44
  });
45
45
 
46
46
  if (user.status === "fulfilled") render(user.value);
@@ -52,7 +52,7 @@ A task context has `on` and `off`, like the bus. A subscription made with `ctx.o
52
52
 
53
53
  ```ts
54
54
  bus.on("watch", (_, ctx) => {
55
- ctx.on("tick", (n) => console.log(n)); // gone when this task aborts
55
+ ctx.on("tick", (n) => console.log(n)); // gone when this task aborts
56
56
  });
57
57
  ```
58
58
 
@@ -66,13 +66,13 @@ bus.on("watch", (_, ctx) => {
66
66
  import { take, timeout } from "nanoeffects/effects";
67
67
 
68
68
  bus.on("login", async (user, ctx) => {
69
- const reason = await take(ctx, "logout"); // resolves on the next logout
70
- cleanup(user, reason);
69
+ const reason = await take(ctx, "logout"); // resolves on the next logout
70
+ cleanup(user, reason);
71
71
 
72
- // wait for a matching event, but not longer than 5 s
73
- const id = await timeout(ctx, 5000, ({ signal }) =>
74
- take(ctx, "saved", { signal, predicate: (id) => id === draft.id }),
75
- );
72
+ // wait for a matching event, but not longer than 5 s
73
+ const id = await timeout(ctx, 5000, ({ signal }) =>
74
+ take(ctx, "saved", { signal, predicate: (id) => id === draft.id }),
75
+ );
76
76
  });
77
77
  ```
78
78
 
@@ -82,25 +82,43 @@ bus.on("login", async (user, ctx) => {
82
82
 
83
83
  ## retry, timeout, poll
84
84
 
85
+ These three have two forms. As a **Task wrapper** they take a function `(payload, ctx) => result` and return one, so they go straight into `on`, next to the policies, with no `ctx` in their arguments. As a **call inside a body** they take `ctx` first.
86
+
85
87
  ```ts
86
88
  import { poll, retry, timeout } from "nanoeffects/effects";
87
89
 
90
+ const loadUser = (id: string, { signal }) => fetchUser(id, { signal });
91
+
92
+ // wrappers: up to 4 attempts with a backoff, each within 5 s; a newer call supersedes the older
93
+ bus.on(
94
+ "load",
95
+ latest(
96
+ retry(timeout(5000, loadUser), {
97
+ times: 4,
98
+ delay: (attempt) => 2 ** attempt * 100,
99
+ shouldRetry: (error) => !isFatal(error),
100
+ }),
101
+ ),
102
+ );
103
+
104
+ // calls refresh every 10 s until the task is aborted
105
+ bus.on("watch", poll(10_000, (_, { signal }) => refresh({ signal })));
106
+
107
+ // a wrapped Task runs as a child of a handler with ctx.run: the only place where ctx is written
108
+ bus.on("open", async (id, ctx) => {
109
+ const user = await ctx.run(retry(timeout(5000, loadUser)), id);
110
+ });
111
+
112
+ // one call inside a body: ctx first
88
113
  bus.on("load", async (id, ctx) => {
89
- // up to 4 attempts, backoff between them; stops when the task aborts
90
- const user = await retry(ctx, () => fetchUser(id, { signal: ctx.signal }), {
91
- times: 4,
92
- delay: (attempt) => 2 ** attempt * 100,
93
- shouldRetry: (error) => !isFatal(error),
94
- });
95
-
96
- // aborts the starter's signal and rejects with a TimeoutError after 5 s
97
- const data = await timeout(ctx, 5000, ({ signal }) => fetchData(id, { signal }));
98
-
99
- // calls refresh every 10 s; resolves when the task aborts
100
- await poll(ctx, () => refresh(), 10_000);
114
+ const user = await retry(ctx, () => fetchUser(id, { signal: ctx.signal }), { times: 4 });
115
+ const data = await timeout(ctx, 5000, ({ signal }) => fetchData(id, { signal }));
116
+ await poll(ctx, () => refresh(), 10_000);
101
117
  });
102
118
  ```
103
119
 
120
+ Use the wrapper form for a whole handler or a whole `ctx.run`, and the `ctx` form for one call in the middle of a body. The forms are told apart by the first argument: a function (`retry`), a number (`timeout`), a number or a function (`poll`) is a wrapper; an object is `ctx`. Each attempt or call of a wrapper gets a context of its own, a child of the caller's, so an abort of the caller reaches it and an abort of one attempt (a timeout) does not reach the caller. The attempt number is passed only in the `ctx` form.
121
+
104
122
  - `retry`: `times` is the total number of attempts (default 3), `delay` is a number or `(attempt, error) => ms`, `shouldRetry(error, attempt)` can stop early. `fn` gets the attempt number, starting at 1. On abort it rejects with the abort reason.
105
123
  - `timeout`: the error is a `DOMException` named `TimeoutError`, like `AbortSignal.timeout`. It rejects even if the starter ignores its signal.
106
124
  - `poll`: like patronum `interval`. The first call comes after `ms` (`leading: true` also calls at once), and the next ticks keep a steady rate whatever the duration of `fn`, so a slow `fn` can overlap with the next call; `waitForFn: true` starts each pause when the call ends instead, so calls never overlap. `fn` gets the call number from 0. An error from `fn` stops polling and rejects; an abort ends it normally and clears the timer.
@@ -114,6 +132,6 @@ await delay(ctx, () => 200 + Math.random() * 200); // 200 to 400 ms
114
132
 
115
133
  - All three are traced: the effect is a group (`retry`, `timeout`, `poll`) and each attempt or call is a `run` child.
116
134
 
117
- In every effect `ctx` is the first argument: `delay(ctx, ms)`, `all(ctx, effects)`, `allSettled(ctx, effects)`, `race(ctx, effects)`, `cancellable(ctx, executor)`, `onAbort(ctx, fn)`, `retry(ctx, fn, options)`, `timeout(ctx, ms, starter)`, `poll(ctx, fn, ms, options)`, `take(ctx, event, options)`. It is required; outside a task pass `{}` or any object with a `signal`.
135
+ The other effects wait inside a body, so `ctx` is their first argument: `delay(ctx, ms)`, `all(ctx, effects)`, `allSettled(ctx, effects)`, `race(ctx, effects)`, `cancellable(ctx, executor)`, `onAbort(ctx, fn)`, `take(ctx, event, options)`, and `retry`, `timeout` and `poll` in their `ctx` form. It is required; outside a task pass `{}` or any object with a `signal`.
118
136
 
119
137
  For `fetch` and other APIs that take a signal, pass `ctx.signal`. A timeout without `race`: `AbortSignal.any([ctx.signal, AbortSignal.timeout(5000)])`.
package/docs/errors.md CHANGED
@@ -6,7 +6,7 @@ Pass `onError` to isolate handlers instead. Then every handler runs, and thrown
6
6
 
7
7
  ```ts
8
8
  const bus = createBus<Events>({
9
- onError: (error, { type, payload }) => report(error, type, payload),
9
+ onError: (error, { type, payload }) => report(error, type, payload),
10
10
  });
11
11
  ```
12
12
 
@@ -16,8 +16,8 @@ A typo in an event name, `emit("serch")`, does nothing and says nothing. `onUnha
16
16
 
17
17
  ```ts
18
18
  const bus = createBus<Events>({
19
- onUnhandled: import.meta.env.DEV
20
- ? ({ type }) => console.warn(`Nobody listens to "${type}"`)
21
- : void 0,
19
+ onUnhandled: import.meta.env.DEV
20
+ ? ({ type }) => console.warn(`Nobody listens to "${type}"`)
21
+ : void 0,
22
22
  });
23
23
  ```