nanoeffects 0.1.0 → 0.2.1

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 (52) 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 +2 -28
  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 +2 -28
  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/stores.md +0 -2
  45. package/docs/tracing.md +21 -8
  46. package/package.json +25 -5
  47. package/dist/bus-RUiw8f9p.js +0 -1
  48. package/dist/context-BXQ6Einm.js +0 -1
  49. package/dist/index-CaxrMD1A.d.ts +0 -1
  50. package/dist/notrace/bus-CFxgpsRL.js +0 -1
  51. package/dist/notrace/context-DGPIFXxI.js +0 -1
  52. 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 = {
@@ -9,32 +9,6 @@ type NanoeffectsMiddlewareOptions = {
9
9
  /** Return `false` for the actions that the bus should not get. Default: all of them. */
10
10
  filter?: (action: ReduxAction) => boolean;
11
11
  };
12
- /**
13
- * A Redux middleware that sends every action to the bus, after the reducers
14
- * have handled it, so a handler that reads the store sees the new state. It
15
- * does not depend on Redux: it only has the shape of a middleware. Values that
16
- * are not plain actions (the functions of redux-thunk) go through untouched.
17
- *
18
- * The other way needs no adapter: give the store to the tasks with
19
- * `createBus({ context: { store } })` and dispatch from a handler.
20
- *
21
- * Take care of loops: a handler must not dispatch the action it handles.
22
- * Errors of handlers reach the caller of `dispatch`, as for `bus.emit`; set
23
- * `onError` on the bus to isolate them.
24
- *
25
- * @example
26
- * ```ts
27
- * const store = configureStore({
28
- * reducer,
29
- * middleware: (getDefault) => getDefault().concat(nanoeffectsMiddleware(bus)),
30
- * });
31
- *
32
- * bus.on(userRequested, latest(async (id, { store, signal }) => {
33
- * store.dispatch(userLoaded(await fetchUser(id, { signal })));
34
- * }));
35
- * ```
36
- */
37
- export declare const nanoeffectsMiddleware: <E extends EventMap>(bus: Bus<E>, options?: NanoeffectsMiddlewareOptions) => () => (next: (action: unknown) => unknown) => (action: unknown) => unknown;
38
12
  //#endregion
39
13
  //#region src/store/redux/effects.d.ts
40
14
  /** What Redux gives a middleware: the parts of the store that it may use. */
@@ -89,4 +63,4 @@ export declare const createEffectsMiddleware: <E extends EventMap = EventMap>(op
89
63
  middleware: (storeApi: ReduxStoreApi) => (next: (action: unknown) => unknown) => (action: unknown) => unknown;
90
64
  };
91
65
  //#endregion
92
- export type { EffectsMiddlewareOptions, NanoeffectsMiddlewareOptions, ReduxStoreApi };
66
+ export type { EffectsMiddlewareOptions, ReduxStoreApi };
@@ -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};
@@ -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 = {
@@ -9,32 +9,6 @@ type NanoeffectsMiddlewareOptions = {
9
9
  /** Return `false` for the actions that the bus should not get. Default: all of them. */
10
10
  filter?: (action: ReduxAction) => boolean;
11
11
  };
12
- /**
13
- * A Redux middleware that sends every action to the bus, after the reducers
14
- * have handled it, so a handler that reads the store sees the new state. It
15
- * does not depend on Redux: it only has the shape of a middleware. Values that
16
- * are not plain actions (the functions of redux-thunk) go through untouched.
17
- *
18
- * The other way needs no adapter: give the store to the tasks with
19
- * `createBus({ context: { store } })` and dispatch from a handler.
20
- *
21
- * Take care of loops: a handler must not dispatch the action it handles.
22
- * Errors of handlers reach the caller of `dispatch`, as for `bus.emit`; set
23
- * `onError` on the bus to isolate them.
24
- *
25
- * @example
26
- * ```ts
27
- * const store = configureStore({
28
- * reducer,
29
- * middleware: (getDefault) => getDefault().concat(nanoeffectsMiddleware(bus)),
30
- * });
31
- *
32
- * bus.on(userRequested, latest(async (id, { store, signal }) => {
33
- * store.dispatch(userLoaded(await fetchUser(id, { signal })));
34
- * }));
35
- * ```
36
- */
37
- export declare const nanoeffectsMiddleware: <E extends EventMap>(bus: Bus<E>, options?: NanoeffectsMiddlewareOptions) => () => (next: (action: unknown) => unknown) => (action: unknown) => unknown;
38
12
  //#endregion
39
13
  //#region src/store/redux/effects.d.ts
40
14
  /** What Redux gives a middleware: the parts of the store that it may use. */
@@ -89,4 +63,4 @@ export declare const createEffectsMiddleware: <E extends EventMap = EventMap>(op
89
63
  middleware: (storeApi: ReduxStoreApi) => (next: (action: unknown) => unknown) => (action: unknown) => unknown;
90
64
  };
91
65
  //#endregion
92
- export type { EffectsMiddlewareOptions, NanoeffectsMiddlewareOptions, ReduxStoreApi };
66
+ export type { EffectsMiddlewareOptions, ReduxStoreApi };
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};
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 } }>`.