nanoeffects 0.21.0 → 0.23.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 (48) hide show
  1. package/dist/actions.d.ts +2 -2
  2. package/dist/actions.js +1 -1
  3. package/dist/{bus-duCCrqrr.js → bus-hHp-T7JV.js} +1 -1
  4. package/dist/context-UcmrAIhP.js +1 -0
  5. package/dist/{notrace/delay-BosJKFjg.js → delay-mxZkTg17.js} +1 -1
  6. package/dist/dev.d.ts +1 -1
  7. package/dist/effects.d.ts +4 -4
  8. package/dist/effects.js +1 -1
  9. package/dist/index.js +1 -1
  10. package/dist/{module-B0KVDNjY.d.ts → module-D1VVKbE0.d.ts} +1 -1
  11. package/dist/notrace/actions.d.ts +1 -1
  12. package/dist/notrace/actions.js +1 -1
  13. package/dist/notrace/{bus-BkUtjPHp.js → bus-BdyvumpX.js} +1 -1
  14. package/dist/notrace/context-_HWcdjZf.js +1 -0
  15. package/dist/{delay-BnBdGPh7.js → notrace/delay-CqKplsaY.js} +1 -1
  16. package/dist/notrace/effects.d.ts +4 -4
  17. package/dist/notrace/effects.js +1 -1
  18. package/dist/notrace/index.js +1 -1
  19. package/dist/notrace/{plugin-C6KA4dX-.d.ts → plugin-BEvYzo8Q.d.ts} +52 -29
  20. package/dist/notrace/policies.js +1 -1
  21. package/dist/oop.js +1 -1
  22. package/dist/{plugin-D9UTzGRG.d.ts → plugin-BZK83Z3N.d.ts} +52 -29
  23. package/dist/policies.js +1 -1
  24. package/dist/react.js +1 -1
  25. package/dist/scope.d.ts +1 -1
  26. package/dist/scope.js +1 -1
  27. package/dist/signal.js +1 -1
  28. package/dist/svelte.d.ts +46 -0
  29. package/dist/svelte.js +1 -0
  30. package/dist/trace.js +1 -1
  31. package/dist/{types-CbdJKC2z.d.ts → types-CBoV9m7W.d.ts} +1 -1
  32. package/dist/{with-policy-CW50Edgk.js → with-policy-D-SQRkPh.js} +1 -1
  33. package/docs/README.md +3 -0
  34. package/docs/actions.md +1 -1
  35. package/docs/chat-example.md +33 -27
  36. package/docs/effects.md +36 -5
  37. package/docs/migrating-from-rxjs.md +2 -2
  38. package/docs/migrating-from-saga.md +3 -1
  39. package/docs/package.md +1 -0
  40. package/docs/server.md +205 -0
  41. package/docs/starter.md +3 -3
  42. package/docs/stores.md +1 -1
  43. package/docs/svelte.md +81 -0
  44. package/llms-full.txt +375 -41
  45. package/llms.txt +4 -2
  46. package/package.json +24 -4
  47. package/dist/context-CJGW6OWj.js +0 -1
  48. package/dist/notrace/context-yYU3_9lq.js +0 -1
@@ -1,4 +1,4 @@
1
- import { B as Keys, N as WithContext, b as Context, d as Effects, h as Bus, l as BoundForm, z as EventMap } from "./action-creator-Bpa8BUS9.js";
1
+ import { B as Keys, N as WithContext, b as Context, d as Effects, h as Bus, l as BoundForm, r as FreeActionCreator, t as ActionCreator, z as EventMap } from "./action-creator-Bpa8BUS9.js";
2
2
  import { n as NoClash } from "./context-types-BKIH5a8s.js";
3
3
  //#region src/utils/delay.d.ts
4
4
  /**
@@ -25,12 +25,18 @@ declare const delay: (ctx: {
25
25
  }, ms: number | (() => number)) => Promise<void>;
26
26
  //#endregion
27
27
  //#region src/effects/run.d.ts
28
- /** Context passed into `race` / `all` effect starters. */
28
+ /** What a starter gets when the effect was called with `{}` or a bare `{ signal }`. */
29
29
  type EffectContext = {
30
30
  signal: AbortSignal;
31
31
  abort: (reason?: unknown) => void;
32
32
  };
33
- type Starter<T> = (ctx: EffectContext) => T | PromiseLike<T>;
33
+ /**
34
+ * What a starter gets: the context of its own child task when the effect was
35
+ * called with a task context (so `take(child, event)` works), `{ signal, abort }`
36
+ * otherwise.
37
+ */
38
+ type ChildOf<C> = C extends Context<infer E> ? Context<E> : EffectContext;
39
+ type Starter<T, Child = EffectContext> = (child: Child) => T | PromiseLike<T>;
34
40
  //#endregion
35
41
  //#region src/effects/all.d.ts
36
42
  /**
@@ -48,12 +54,12 @@ type Starter<T> = (ctx: EffectContext) => T | PromiseLike<T>;
48
54
  * });
49
55
  * ```
50
56
  */
51
- declare function all<T extends Record<string, Starter<unknown>>>(ctx: {
57
+ declare function all<C extends {
52
58
  signal?: AbortSignal;
53
- }, effects: T): Promise<{ [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
54
- declare function all<const T extends readonly Starter<unknown>[]>(ctx: {
59
+ }, T extends Record<string, Starter<unknown, ChildOf<C>>>>(ctx: C, effects: T): Promise<{ [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
60
+ declare function all<C extends {
55
61
  signal?: AbortSignal;
56
- }, effects: T): Promise<{ -readonly [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
62
+ }, const T extends readonly Starter<unknown, ChildOf<C>>[]>(ctx: C, effects: T): Promise<{ -readonly [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
57
63
  //#endregion
58
64
  //#region src/effects/allSettled.d.ts
59
65
  /**
@@ -74,15 +80,15 @@ declare function all<const T extends readonly Starter<unknown>[]>(ctx: {
74
80
  * });
75
81
  * ```
76
82
  */
77
- declare function allSettled<T extends Record<string, Starter<unknown>>>(ctx: {
83
+ declare function allSettled<C extends {
78
84
  signal?: AbortSignal;
79
- }, effects: T): Promise<{ [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
80
- declare function allSettled<const T extends readonly Starter<unknown>[]>(ctx: {
85
+ }, T extends Record<string, Starter<unknown, ChildOf<C>>>>(ctx: C, effects: T): Promise<{ [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
86
+ declare function allSettled<C extends {
81
87
  signal?: AbortSignal;
82
- }, effects: T): Promise<{ -readonly [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
88
+ }, const T extends readonly Starter<unknown, ChildOf<C>>[]>(ctx: C, effects: T): Promise<{ -readonly [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
83
89
  //#endregion
84
90
  //#region src/effects/race.d.ts
85
- type RaceStarter<T> = Starter<T>;
91
+ type RaceStarter<T, Child = EffectContext> = Starter<T, Child>;
86
92
  /**
87
93
  * Runs all starters; first to settle wins, losers are cancelled via `ctx.abort`.
88
94
  * The result holds only the winner's value, under its key or index.
@@ -100,12 +106,12 @@ type RaceStarter<T> = Starter<T>;
100
106
  * });
101
107
  * ```
102
108
  */
103
- declare function race<T extends Record<string, RaceStarter<unknown>>>(ctx: {
109
+ declare function race<C extends {
104
110
  signal?: AbortSignal;
105
- }, effects: T): Promise<{ [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
106
- declare function race<const T extends readonly RaceStarter<unknown>[]>(ctx: {
111
+ }, T extends Record<string, RaceStarter<unknown, ChildOf<C>>>>(ctx: C, effects: T): Promise<{ [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
112
+ declare function race<C extends {
107
113
  signal?: AbortSignal;
108
- }, effects: T): Promise<{ -readonly [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
114
+ }, const T extends readonly RaceStarter<unknown, ChildOf<C>>[]>(ctx: C, effects: T): Promise<{ -readonly [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
109
115
  //#endregion
110
116
  //#region src/effects/take.d.ts
111
117
  type TakeOptions<P> = {
@@ -118,7 +124,8 @@ type TakeOptions<P> = {
118
124
  signal?: AbortSignal | undefined;
119
125
  };
120
126
  /**
121
- * Waits for the next `event` on the bus and resolves with its payload. It sees
127
+ * Waits for the next `event` on the bus and resolves with its payload. `event` is the name
128
+ * of an event or an action creator, as for `bus.on`. It sees
122
129
  * only events emitted after the call, takes only one, and unsubscribes when it
123
130
  * is done. If `ctx` (or `options.signal`) aborts first, it unsubscribes and
124
131
  * rejects with the abort reason.
@@ -135,6 +142,9 @@ type TakeOptions<P> = {
135
142
  * take(ctx, "saved", { signal, predicate: (id) => id === draftId }),
136
143
  * );
137
144
  *
145
+ * // an action creator works as the name does, and gives its payload type
146
+ * const stopped = await take(ctx, stopRequested);
147
+ *
138
148
  * // a type guard narrows the result: `failed` is `{ ok: false }`
139
149
  * const failed = await take(ctx, "result", {
140
150
  * predicate: (result): result is { ok: false } => !result.ok,
@@ -145,6 +155,14 @@ declare function take<E extends EventMap, K extends Keys<E>, T extends E[K]>(ctx
145
155
  predicate: (payload: E[K]) => payload is T;
146
156
  }): Promise<T>;
147
157
  declare function take<E extends EventMap, K extends Keys<E>>(ctx: Context<E>, event: K, options?: TakeOptions<E[K]>): Promise<E[K]>;
158
+ declare function take<E extends EventMap, K extends Keys<E>, T extends E[K]>(ctx: Context<E>, creator: FreeActionCreator<K>, options: TakeOptions<E[K]> & {
159
+ predicate: (payload: E[K]) => payload is T;
160
+ }): Promise<T>;
161
+ declare function take<E extends EventMap, K extends Keys<E>>(ctx: Context<E>, creator: FreeActionCreator<K>, options?: TakeOptions<E[K]>): Promise<E[K]>;
162
+ declare function take<E extends EventMap, P, T extends P>(ctx: Context<E>, creator: ActionCreator<P>, options: TakeOptions<P> & {
163
+ predicate: (payload: P) => payload is T;
164
+ }): Promise<T>;
165
+ declare function take<E extends EventMap, P>(ctx: Context<E>, creator: ActionCreator<P>, options?: TakeOptions<P>): Promise<P>;
148
166
  //#endregion
149
167
  //#region src/effects/plugin.d.ts
150
168
  /** The effects that `effectsPlugin` can put on `ctx`. */
@@ -155,23 +173,26 @@ type Supported = {
155
173
  take: typeof take;
156
174
  delay: typeof delay;
157
175
  };
158
- type Results = Record<string, Starter<unknown>>;
159
- type List = readonly Starter<unknown>[];
160
176
  /** Each supported effect without its leading `ctx` argument. `take` knows the events of the bus. */
161
- type Bound<E extends EventMap> = {
177
+ type Bound<E extends EventMap, Child> = {
162
178
  race: {
163
- <T extends Results>(effects: T): Promise<{ [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
164
- <const T extends List>(effects: T): Promise<{ -readonly [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
179
+ <T extends Record<string, Starter<unknown, Child>>>(effects: T): Promise<{ [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
180
+ <const T extends readonly Starter<unknown, Child>[]>(effects: T): Promise<{ -readonly [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
165
181
  };
166
182
  all: {
167
- <T extends Results>(effects: T): Promise<{ [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
168
- <const T extends List>(effects: T): Promise<{ -readonly [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
183
+ <T extends Record<string, Starter<unknown, Child>>>(effects: T): Promise<{ [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
184
+ <const T extends readonly Starter<unknown, Child>[]>(effects: T): Promise<{ -readonly [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
169
185
  };
170
186
  allSettled: {
171
- <T extends Results>(effects: T): Promise<{ [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
172
- <const T extends List>(effects: T): Promise<{ -readonly [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
187
+ <T extends Record<string, Starter<unknown, Child>>>(effects: T): Promise<{ [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
188
+ <const T extends readonly Starter<unknown, Child>[]>(effects: T): Promise<{ -readonly [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
189
+ };
190
+ take: {
191
+ <K extends Keys<E>>(event: K, options?: TakeOptions<E[K]>): Promise<E[K]>;
192
+ /** A creator without a payload type: the payload comes from the event map. */
193
+ <K extends Keys<E>>(creator: FreeActionCreator<K>, options?: TakeOptions<E[K]>): Promise<E[K]>;
194
+ <P>(creator: ActionCreator<P>, options?: TakeOptions<P>): Promise<P>;
173
195
  };
174
- take: <K extends Keys<E>>(event: K, options?: TakeOptions<E[K]>) => Promise<E[K]>;
175
196
  delay: (ms: number | (() => number)) => Promise<void>;
176
197
  };
177
198
  /** Any function whose first parameter takes `{ signal }` can go on the context, not only the built-in ones. */
@@ -180,7 +201,9 @@ type Helpers = Effects;
180
201
  * The effects as a handler sees them: the built-in ones keep their hand-written
181
202
  * types, any other loses its first parameter, or has the type `Effect<F>` says.
182
203
  */
183
- type BoundOf<H extends Helpers, E extends EventMap> = { [K in keyof H]: K extends keyof Bound<E> ? H[K] extends Supported[K] ? Bound<E>[K] : BoundForm<H[K]> : BoundForm<H[K]>; };
204
+ type BoundOf<H extends Helpers, E extends EventMap> = { [K in keyof H]: K extends keyof Bound<E, StarterOf<H, E>> ? H[K] extends Supported[K] ? Bound<E, StarterOf<H, E>>[K] : BoundForm<H[K]> : BoundForm<H[K]>; };
205
+ /** What a starter of a bound `race` / `all` / `allSettled` gets: the context of its child, with the same bound effects. */
206
+ type StarterOf<H extends Helpers, E extends EventMap> = Context<E> & BoundOf<H, E>;
184
207
  /**
185
208
  * Puts the effects you pass on the context of every task of the bus, bound to
186
209
  * that task, so a handler can write `({ race })` instead of `race(ctx, …)`.
@@ -204,4 +227,4 @@ type BoundOf<H extends Helpers, E extends EventMap> = { [K in keyof H]: K extend
204
227
  */
205
228
  declare const effectsPlugin: <H extends Helpers>(helpers: H) => <E extends EventMap>(bus: Bus<E> & NoClash<BoundOf<H, E>, E>) => Bus<E & WithContext<BoundOf<H, E>>>;
206
229
  //#endregion
207
- export { RaceStarter as a, all as c, delay as d, take as i, EffectContext as l, effectsPlugin as n, race as o, TakeOptions as r, allSettled as s, BoundOf as t, Starter as u };
230
+ export { RaceStarter as a, all as c, Starter as d, delay as f, take as i, ChildOf as l, effectsPlugin as n, race as o, TakeOptions as r, allSettled as s, BoundOf as t, EffectContext as u };
package/dist/policies.js CHANGED
@@ -1 +1 @@
1
- import{i as e,o as t,s as n,t as r,u as i}from"./context-CJGW6OWj.js";import{n as a,r as o,t as s}from"./delay-BnBdGPh7.js";import{n as c,t as l}from"./promise-CSLpzwSR.js";import{n as u,t as d}from"./lifecycle-key-8iX8RUVD.js";import{t as f}from"./with-policy-CW50Edgk.js";const p=e=>Reflect.get(e,d),m=e=>{p(e)?.start()},h=(e,n,r)=>(m(e),t(e,n,r)),g=(e,t)=>{n(e,t,`drop`),p(e)?.drop()},_=(e,t)=>{p(e)?.done(t)},v=(e,t,n)=>{let r=p(e);t?r?.cancel(n):r?.drop()},y=e=>(Reflect.set(e,u,!0),e),b=()=>{},x=(e,t,n)=>typeof e==`function`?n(e,t??{}):t=>n(t,e??{}),S=(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)}},C=(e,t)=>y(e?S(e,t):t(b));function w(e,t){return x(e,t,T)}const T=(e,t)=>C(t.key,t=>E(`latest`,(t,n,r)=>(r(),e(t,n)),t)),E=(t,a,o=b)=>{let s=null;return(l,u)=>{if(s?.abort(),u.signal.aborted)return;let{ctx:d,unlink:f}=e(u);s=d;let p=r(d,t),h=!1,g=()=>{d.signal.aborted||(h=!0,m(d),n(d,t,`start`))};i(d,e=>{v(d,h,e),p(h?`cancel`:`drop`)});let _=()=>{f(),s===d&&(s=null,o())},y=e=>{if(!d.signal.aborted)return p(h?`done`:`drop`),e};try{let e=a(l,d,g);return c(e)?Promise.resolve(e).then(y,e=>{if(!d.signal.aborted)throw p(`error`,e),e}).finally(_):(_(),y(e))}catch(e){if(_(),d.signal.aborted)return;throw p(`error`,e),e}}};function D(e,t,n){return x(t,n,(t,n)=>O(e,t,n))}const O=(e,t,n)=>C(n.key,r=>A(e,t,n,r)),k=(e,t,n,r)=>{let i=r!==b,s=null,c=null;return(l,u)=>{if(u.signal.aborted)return;let d=Date.now(),f=s===null||d-s>=e||d-s<0;return s=d,c!==null&&a(c),c=i?o(r,e):null,n&&f?Promise.resolve(h(u,`debounce`,()=>t(l,u))):(g(u,`debounce`),Promise.resolve(void 0))}},A=(e,t,n,r)=>{let a=n.leading??!1,o=n.trailing??!0;if(!o)return k(e,t,a,r);let c=!1,l=0,u=E(`debounce`,([n,r],a,u)=>{let d=l;return i(a,()=>{l===d&&(c=!1)}),s(a,e).then(()=>{if(c=!1,o&&!r)return u(),t(n,a)})},r);return(e,n)=>{if(n.signal.aborted)return;let r=a&&!c;c=!0,l++;let i=u([e,r],n);return r?Promise.resolve(h(n,`debounce`,()=>t(e,n))):i}};function j(e,t){return x(e,t,M)}const M=(e,t)=>C(t.key,t=>{let n=null;return(r,a)=>{if(a.signal.aborted)return;if(n){g(a,`leading`);return}let o={};n=o;let s=()=>{c(),n===o&&(n=null,t())},c=i(a,s);try{return l(h(a,`leading`,()=>e(r,a)),s)}catch(e){throw s(),e}}});function N(e,t){return x(e,t,P)}const P=(e,t)=>C(t.key,n=>F(e,t,n)),F=(e,t,n)=>{let r=t.concurrency??1,a=t.limit??1/0,o=[],s=0,c=()=>{for(;s<r;){let e=o.shift();if(!e)return;e.start()}};return(l,u)=>new Promise((d,f)=>{if(u.signal.aborted){d(void 0);return}let p=b,m=()=>{p(),g(u,`queue`),d(void 0)},v=()=>{if(p(),u.signal.aborted){m();return}s++;let t=!0,r=()=>{t&&(t=!1,s--,c(),s===0&&o.length===0&&n())},a=i(u,()=>{r(),d(void 0)}),g;try{g=Promise.resolve(h(u,`queue`,()=>e(l,u)))}catch(e){g=Promise.reject(e)}let v=()=>{a(),r()};g.then(e=>{_(u,{value:e}),v(),d(u.signal.aborted?void 0:e)},e=>{_(u,{error:e}),v(),u.signal.aborted?d(void 0):f(e)})};if(s<r){v();return}if(o.length>=a){if(t.overflow!==`drop-old`||a<=0){m();return}o.shift()?.drop()}let y={start:v,drop:m};o.push(y),p=i(u,()=>{let e=o.indexOf(y);e>=0&&o.splice(e,1),m()})})};function I(e,t,n){return x(t,n,(t,n)=>L(e,t,n))}const L=(e,t,n)=>C(n.key,r=>R(e,t,n,r)),R=(e,t,n,r)=>{let i=n.leading??!0,s=n.trailing??!0,c=r!==b,l=null,u=0,d=null,f=null,p=null,m=!1,_=t=>l===null||t-l>=e||t-l<0||t-u>=e,v=e=>g(e,`throttle`),y=()=>{let e=p;p=null,e&&(v(e.ctx),e.resolve())},x=(e,t)=>(y(),new Promise((n,r)=>{p={payload:e,ctx:t,resolve:n,reject:r}})),S=(e,n,r)=>{y(),u=r,m=!0;try{return h(n,`throttle`,()=>t(e,n))}finally{m=!1}},C=()=>{c&&(f=o(w,e))},w=()=>{if(f=null,!c||d!==null||p)return;let t=Date.now(),n=Math.max(e-(t-(l??-1/0)),e-(t-u));n<=0?r():f=o(w,n)},T=e=>{d=null;let t=p;if(s&&t&&!t.ctx.signal.aborted){p=null;try{t.resolve(S(t.payload,t.ctx,e))}catch(e){t.reject(e)}}else y();w()},E=()=>{let t=Date.now();_(t)?T(t):d=o(E,Math.min(e-(t-(l??t)),e-(t-u)))},D=()=>{f!==null&&(a(f),f=null)},O=(e,t)=>{if(!s)v(t);else return x(e,t)},k=(e,t,n,r)=>{if(!r)v(t);else if(u=n,C(),!i)v(t);else return S(e,t,n)},A=(t,n,r,s)=>s&&d!==null?(a(d),d=o(E,e),S(t,n,r)):d===null&&(d=o(E,e),s&&(u=r,i))?S(t,n,r):O(t,n);return(e,t)=>{if(t.signal.aborted)return;let n=Date.now(),r=!m&&_(n);return l=n,D(),s?A(e,t,n,r):k(e,t,n,r)}};export{D as debounce,w as latest,j as leading,N as queue,I as throttle,f as withPolicy};
1
+ import{i as e,o as t,s as n,t as r,u as i}from"./context-UcmrAIhP.js";import{n as a,r as o,t as s}from"./delay-mxZkTg17.js";import{n as c,t as l}from"./promise-CSLpzwSR.js";import{n as u,t as d}from"./lifecycle-key-8iX8RUVD.js";import{t as f}from"./with-policy-D-SQRkPh.js";const p=e=>Reflect.get(e,d),m=e=>{p(e)?.start()},h=(e,n,r)=>(m(e),t(e,n,r)),g=(e,t)=>{n(e,t,`drop`),p(e)?.drop()},_=(e,t)=>{p(e)?.done(t)},v=(e,t,n)=>{let r=p(e);t?r?.cancel(n):r?.drop()},y=e=>(Reflect.set(e,u,!0),e),b=()=>{},x=(e,t,n)=>typeof e==`function`?n(e,t??{}):t=>n(t,e??{}),S=(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)}},C=(e,t)=>y(e?S(e,t):t(b));function w(e,t){return x(e,t,T)}const T=(e,t)=>C(t.key,t=>E(`latest`,(t,n,r)=>(r(),e(t,n)),t)),E=(t,a,o=b)=>{let s=null;return(l,u)=>{if(s?.abort(),u.signal.aborted)return;let{ctx:d,unlink:f}=e(u);s=d;let p=r(d,t),h=!1,g=()=>{d.signal.aborted||(h=!0,m(d),n(d,t,`start`))};i(d,e=>{v(d,h,e),p(h?`cancel`:`drop`)});let _=()=>{f(),s===d&&(s=null,o())},y=e=>{if(!d.signal.aborted)return p(h?`done`:`drop`),e};try{let e=a(l,d,g);return c(e)?Promise.resolve(e).then(y,e=>{if(!d.signal.aborted)throw p(`error`,e),e}).finally(_):(_(),y(e))}catch(e){if(_(),d.signal.aborted)return;throw p(`error`,e),e}}};function D(e,t,n){return x(t,n,(t,n)=>O(e,t,n))}const O=(e,t,n)=>C(n.key,r=>A(e,t,n,r)),k=(e,t,n,r)=>{let i=r!==b,s=null,c=null;return(l,u)=>{if(u.signal.aborted)return;let d=Date.now(),f=s===null||d-s>=e||d-s<0;return s=d,c!==null&&a(c),c=i?o(r,e):null,n&&f?Promise.resolve(h(u,`debounce`,()=>t(l,u))):(g(u,`debounce`),Promise.resolve(void 0))}},A=(e,t,n,r)=>{let a=n.leading??!1,o=n.trailing??!0;if(!o)return k(e,t,a,r);let c=!1,l=0,u=E(`debounce`,([n,r],a,u)=>{let d=l;return i(a,()=>{l===d&&(c=!1)}),s(a,e).then(()=>{if(c=!1,o&&!r)return u(),t(n,a)})},r);return(e,n)=>{if(n.signal.aborted)return;let r=a&&!c;c=!0,l++;let i=u([e,r],n);return r?Promise.resolve(h(n,`debounce`,()=>t(e,n))):i}};function j(e,t){return x(e,t,M)}const M=(e,t)=>C(t.key,t=>{let n=null;return(r,a)=>{if(a.signal.aborted)return;if(n){g(a,`leading`);return}let o={};n=o;let s=()=>{c(),n===o&&(n=null,t())},c=i(a,s);try{return l(h(a,`leading`,()=>e(r,a)),s)}catch(e){throw s(),e}}});function N(e,t){return x(e,t,P)}const P=(e,t)=>C(t.key,n=>F(e,t,n)),F=(e,t,n)=>{let r=t.concurrency??1,a=t.limit??1/0,o=[],s=0,c=()=>{for(;s<r;){let e=o.shift();if(!e)return;e.start()}};return(l,u)=>new Promise((d,f)=>{if(u.signal.aborted){d(void 0);return}let p=b,m=()=>{p(),g(u,`queue`),d(void 0)},v=()=>{if(p(),u.signal.aborted){m();return}s++;let t=!0,r=()=>{c(),s===0&&o.length===0&&n()},a=(e=!1)=>{t&&(t=!1,s--,e?queueMicrotask(r):r())},g=i(u,()=>{a(!0),d(void 0)}),v;try{v=Promise.resolve(h(u,`queue`,()=>e(l,u)))}catch(e){v=Promise.reject(e)}let y=()=>{g(),a()};v.then(e=>{_(u,{value:e}),y(),d(u.signal.aborted?void 0:e)},e=>{_(u,{error:e}),y(),u.signal.aborted?d(void 0):f(e)})};if(s<r){v();return}if(o.length>=a){if(t.overflow!==`drop-old`||a<=0){m();return}o.shift()?.drop()}let y={start:v,drop:m};o.push(y),p=i(u,()=>{let e=o.indexOf(y);e>=0&&o.splice(e,1),m()})})};function I(e,t,n){return x(t,n,(t,n)=>L(e,t,n))}const L=(e,t,n)=>C(n.key,r=>R(e,t,n,r)),R=(e,t,n,r)=>{let i=n.leading??!0,s=n.trailing??!0,c=r!==b,l=null,u=0,d=null,f=null,p=null,m=!1,_=t=>l===null||t-l>=e||t-l<0||t-u>=e,v=e=>g(e,`throttle`),y=()=>{let e=p;p=null,e&&(v(e.ctx),e.resolve())},x=(e,t)=>(y(),new Promise((n,r)=>{p={payload:e,ctx:t,resolve:n,reject:r}})),S=(e,n,r)=>{y(),u=r,m=!0;try{return h(n,`throttle`,()=>t(e,n))}finally{m=!1}},C=()=>{c&&(f=o(w,e))},w=()=>{if(f=null,!c||d!==null||p)return;let t=Date.now(),n=Math.max(e-(t-(l??-1/0)),e-(t-u));n<=0?r():f=o(w,n)},T=e=>{d=null;let t=p;if(s&&t&&!t.ctx.signal.aborted){p=null;try{t.resolve(S(t.payload,t.ctx,e))}catch(e){t.reject(e)}}else y();w()},E=()=>{let t=Date.now();_(t)?T(t):d=o(E,Math.min(e-(t-(l??t)),e-(t-u)))},D=()=>{f!==null&&(a(f),f=null)},O=(e,t)=>{if(!s)v(t);else return x(e,t)},k=(e,t,n,r)=>{if(!r)v(t);else if(u=n,C(),!i)v(t);else return S(e,t,n)},A=(t,n,r,s)=>s&&d!==null?(a(d),d=o(E,e),S(t,n,r)):d===null&&(d=o(E,e),s&&(u=r,i))?S(t,n,r):O(t,n);return(e,t)=>{if(t.signal.aborted)return;let n=Date.now(),r=!m&&_(n);return l=n,D(),s?A(e,t,n,r):k(e,t,n,r)}};export{D as debounce,w as latest,j as leading,N as queue,I as throttle,f as withPolicy};
package/dist/react.js CHANGED
@@ -1 +1 @@
1
- import{t as e}from"./with-policy-CW50Edgk.js";import{t}from"./origin-key-BbWN3gmN.js";import{useEffect as n,useMemo as r,useRef as i,useState as a}from"react";const o=()=>e=>{function r(r,a,o){let s=i(a),c=i({event:r,options:o});n(()=>{c.current={event:r,options:o}}),n(()=>{let{event:n,options:r}=c.current,i=r?.equals??Object.is,o=s.current;if(i(o,a))return;s.current=a;let l=typeof n==`function`?n(a):{type:n,payload:a},u=Reflect.get(e,t);u?u(l,r??{}):e.emit(l)},[a])}function o(t,r){let[i,o]=a(r);return n(()=>{let n=t;return e.on(n,e=>{let t=e;o(()=>t)})},[t]),[i,o]}return Object.assign(e,{useEmitter:r,useEventState:o})},s=(t,a,o=[])=>{let s=i(t);n(()=>{s.current=t});let c=r(()=>e(a((e,t)=>s.current(e,t))),o);return n(()=>()=>c.cancel(),[c]),c};export{o as reactPlugin,s as usePolicy};
1
+ import{t as e}from"./with-policy-D-SQRkPh.js";import{t}from"./origin-key-BbWN3gmN.js";import{useEffect as n,useMemo as r,useRef as i,useState as a}from"react";const o=()=>e=>{function r(r,a,o){let s=i(a),c=i({event:r,options:o});n(()=>{c.current={event:r,options:o}}),n(()=>{let{event:n,options:r}=c.current,i=r?.equals??Object.is,o=s.current;if(i(o,a))return;s.current=a;let l=typeof n==`function`?n(a):{type:n,payload:a},u=Reflect.get(e,t);u?u(l,r??{}):e.emit(l)},[a])}function o(t,r){let[i,o]=a(r);return n(()=>{let n=t;return e.on(n,e=>{let t=e;o(()=>t)})},[t]),[i,o]}return Object.assign(e,{useEmitter:r,useEventState:o})},s=(t,a,o=[])=>{let s=i(t);n(()=>{s.current=t});let c=r(()=>e(a((e,t)=>s.current(e,t))),o);return n(()=>()=>c.cancel(),[c]),c};export{o as reactPlugin,s as usePolicy};
package/dist/scope.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { P as WithSignal, a as EmitScope, d as Effects, f as Signaled, h as Bus, o as Scope, u as Effect, z as EventMap } from "./action-creator-Bpa8BUS9.js";
2
- import { n as ScopeSlots, r as Scoped } from "./types-CbdJKC2z.js";
2
+ import { n as ScopeSlots, r as Scoped } from "./types-CBoV9m7W.js";
3
3
  //#region src/scope/effect.d.ts
4
4
  /**
5
5
  * Marks a function whose first parameter is `{ signal }` as an effect, so that
package/dist/scope.js CHANGED
@@ -1 +1 @@
1
- import{t as e}from"./scope-key-B7l7mUS5.js";const t=/* @__PURE__ */ new WeakSet;function n(e){return t.add(e),e}const r=/* @__PURE__ */ new Set([`signal`,`abort`,`emit`,`on`,`off`,`run`,`type`,`payload`,`traceGroupId`]),i=[],a=Symbol(`nanoeffects.scope.call`),o=Symbol(`nanoeffects.scope.module`),s=Symbol(`nanoeffects.scope.watched`),c=e=>e,l=e=>typeof e==`function`&&t.has(e),u=(e,t)=>(...n)=>Reflect.apply(t,null,[e,...n]),d=(e,t)=>{if(!t)return;for(let n in t)if(n!==`$effects`&&!r.has(n)){let r=t[n];e[n]=l(r)?u(e,r):r}let n=t.$effects;if(n)for(let t in n){let i=n[t];i&&!r.has(t)&&(e[t]=u(e,i))}},f=(e,t,n)=>{d(e,n);for(let n of t)d(e,n)},p=e=>typeof e==`object`&&!!e,m=e=>p(e),h=e=>(p(e)||typeof e==`function`)&&`type`in e,g=e=>h(e)?e.type:e,_=(e,t)=>p(e)&&h(e)?e.payload:t[0],v=t=>p(t)||typeof t==`function`?Reflect.get(t,e):void 0,y=(e,t)=>{let n=v(e),r=p(e)&&m(t[0])?t[0]:void 0;if(n&&r)return[n,r];let i=n??r;return i?[i]:void 0},b=()=>e=>{let t,n,r,l=e=>{let n=e.run;if(n[s])return;let r=(r,...i)=>{let s=t;t=void 0;try{return n((t,n)=>{let i=e[a];return i&&u(n,i,e[o]),r(t,n)},...i)}finally{t=s}};r[s]=!0,e.run=r},u=(e,t,n)=>{e[a]=t,n&&(e[o]=n),f(e,t,n),l(e)},d=e.addContext;e.addContext=e=>{d?.(e);let i=c(e);t&&i.type===n&&Object.is(i.payload,r)&&u(i,t,void 0)},e.applyScope=(e,t)=>{let n=c(e);u(n,n[a]??i,t)},e.fitScope=(t,n)=>typeof n==`function`?(r,i)=>(e.applyScope?.(i,t),Reflect.apply(n,null,[r,i])):n;let p=e.emit;return e.emit=(e,...i)=>{let a=y(e,i);if(!(a||t))return p(e,...i);let o=t,s=n,c=r;t=a,n=g(e),r=_(e,i);try{return p(e,...i)}finally{t=o,n=s,r=c}},e};export{n as effect,b as scopePlugin};
1
+ import{t as e}from"./scope-key-B7l7mUS5.js";const t=/* @__PURE__ */ new WeakSet;function n(e){return t.add(e),e}const r=/* @__PURE__ */ new Set([`signal`,`abort`,`emit`,`on`,`off`,`run`,`type`,`payload`,`traceGroupId`]),i=[],a=Symbol(`nanoeffects.scope.call`),o=Symbol(`nanoeffects.scope.module`),s=Symbol(`nanoeffects.scope.watched`),c=e=>e,l=e=>typeof e==`function`&&t.has(e),u=(e,t)=>(...n)=>Reflect.apply(t,null,[e,...n]),d=(e,t)=>{if(!t)return;for(let n in t)if(n!==`$effects`&&!r.has(n)){let r=t[n];e[n]=l(r)?u(e,r):r}let n=t.$effects;if(n)for(let t in n){let i=n[t];i&&!r.has(t)&&(e[t]=u(e,i))}},f=(e,t,n)=>{d(e,n);for(let n of t)d(e,n)},p=e=>typeof e==`object`&&!!e,m=e=>p(e),h=e=>(p(e)||typeof e==`function`)&&`type`in e,g=e=>h(e)?e.type:e,_=(e,t)=>p(e)&&h(e)?e.payload:t[0],v=t=>p(t)||typeof t==`function`?Reflect.get(t,e):void 0,y=(e,t)=>{let n=v(e),r=p(e)&&m(t[0])?t[0]:void 0;if(n&&r)return[n,r];let i=n??r;return i?[i]:void 0},b=()=>e=>{let t,n,r,l=e=>{let n=e.run;if(n[s])return;let r=(r,...i)=>{let s=t;t=void 0;try{return n((t,n)=>{let i=e[a];return i&&u(n,i,e[o]),r(t,n)},...i)}finally{t=s}};r[s]=!0,e.run=r},u=(e,t,n)=>{e[a]=t,n&&(e[o]=n),f(e,t,n),l(e)},d=e.addContext;e.addContext=e=>{d?.(e);let i=c(e);t&&i.type===n&&Object.is(i.payload,r)?u(i,t,i[o]):i[a]&&u(i,i[a],i[o])},e.applyScope=(e,t)=>{let n=c(e);u(n,n[a]??i,t)},e.fitScope=(t,n)=>typeof n==`function`?(r,i)=>(e.applyScope?.(i,t),Reflect.apply(n,null,[r,i])):n;let p=e.emit;return e.emit=(e,...i)=>{let a=y(e,i);if(!(a||t))return p(e,...i);let o=t,s=n,c=r;t=a,n=g(e),r=_(e,i);try{return p(e,...i)}finally{t=o,n=s,r=c}},e};export{n as effect,b as scopePlugin};
package/dist/signal.js CHANGED
@@ -1 +1 @@
1
- import{u as e}from"./context-CJGW6OWj.js";import{n as t}from"./promise-CSLpzwSR.js";import{t as n}from"./bus-duCCrqrr.js";const r=e=>typeof e==`object`&&!!e,i=(e,t)=>{let n=t[0];if(!r(n)||!r(e)&&typeof e!=`function`)return;let i=Reflect.get(n,`signal`);return i instanceof AbortSignal?i:void 0},a=(r,i,a,o)=>{let s=e({signal:r},e=>{for(let{target:t}of i)t.abort(e)});return t(o)?o.then(s,s):n(a)&&s(),o},o=()=>e=>{let t,n,r=e.addContext;e.addContext=e=>{r?.(e),t?.push({target:e}),n?.aborted&&e.abort(n.reason)};let o=e.emit;return e.emit=(e,...r)=>{let s=i(e,r);if(!(s||t))return o(e,...r);let c=t,l=n,u=[];t=s?u:void 0,n=s;let d;try{d=o(e,...r)}finally{t=c,n=l}return s?a(s,u,e,d):d},e};export{o as signalPlugin};
1
+ import{u as e}from"./context-UcmrAIhP.js";import{n as t}from"./promise-CSLpzwSR.js";import{t as n}from"./bus-hHp-T7JV.js";const r=e=>typeof e==`object`&&!!e,i=(e,t)=>{let n=t[0];if(!r(n)||!r(e)&&typeof e!=`function`)return;let i=Reflect.get(n,`signal`);return i instanceof AbortSignal?i:void 0},a=(r,i,a,o)=>{let s=e({signal:r},e=>{for(let{target:t}of i)t.abort(e)});return t(o)?o.then(s,s):n(a)&&s(),o},o=()=>e=>{let t,n,r=e.addContext;e.addContext=e=>{r?.(e),t?.push({target:e}),n?.aborted&&e.abort(n.reason)};let o=e.emit;return e.emit=(e,...r)=>{let s=i(e,r);if(!(s||t))return o(e,...r);let c=t,l=n,u=[];t=s?u:void 0,n=s;let d;try{d=o(e,...r)}finally{t=c,n=l}return s?a(s,u,e,d):d},e};export{o as signalPlugin};
@@ -0,0 +1,46 @@
1
+ import { T as EventKey, h as Bus, r as FreeActionCreator, t as ActionCreator, z as EventMap } from "./action-creator-Bpa8BUS9.js";
2
+ import { t as StateOf } from "./state-of-C19H6BoV.js";
3
+ import { Readable } from "svelte/store";
4
+ //#region src/svelte/plugin.d.ts
5
+ /** What `sveltePlugin` adds to the bus. */
6
+ type SvelteApi<E extends EventMap> = {
7
+ /**
8
+ * A readable Svelte store: `initial` at first, then the payload of every emit of `event`.
9
+ * The bus listens only while something subscribes (the first subscriber starts it, the last
10
+ * one stops it), and the bus keeps no values, so an event emitted while nothing reads the
11
+ * store is lost. It is state of the components that read it, not a store of the app. A
12
+ * function payload is stored, not called. The method does not read `this`, so
13
+ * `export const { eventStore } = bus` gives a free function.
14
+ */
15
+ eventStore: {
16
+ <K extends EventKey<E>, S>(event: K, initial: StateOf<E[K], S>): Readable<StateOf<E[K], S>>;
17
+ /** A creator without a payload type: the payload comes from the event map. */
18
+ <K extends EventKey<E>, S>(creator: FreeActionCreator<K>, initial: StateOf<E[K], S>): Readable<StateOf<E[K], S>>;
19
+ <P, T extends string>(creator: ActionCreator<P, T>, initial: P): Readable<P>;
20
+ };
21
+ };
22
+ /**
23
+ * Adds `bus.eventStore(event, initial)`: an event of the bus as a Svelte store, so `$found` in
24
+ * a component shows the last payload and Svelte subscribes and unsubscribes for you. The store
25
+ * is not a store of the app: it keeps the last payload only while something reads it, and an
26
+ * event that came before is lost. Keep a value that must outlive the component in a store of
27
+ * your own. Needs `svelte` 5 or newer. Returns the same bus.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * import { createBus } from "nanoeffects";
32
+ * import { createAction } from "nanoeffects/actions";
33
+ * import { sveltePlugin } from "nanoeffects/svelte";
34
+ *
35
+ * export const bus = createBus().use(sveltePlugin());
36
+ * export const { eventStore } = bus; // a free function
37
+ *
38
+ * const searchLoaded = createAction<string[]>("search/loaded");
39
+ *
40
+ * // in a component: `$found` is string[], set by every searchLoaded
41
+ * const found = eventStore(searchLoaded, []);
42
+ * ```
43
+ */
44
+ export declare const sveltePlugin: () => <E extends EventMap>(bus: Bus<E>) => Bus<E> & SvelteApi<E>;
45
+ //#endregion
46
+ export type { SvelteApi };
package/dist/svelte.js ADDED
@@ -0,0 +1 @@
1
+ import{readable as e}from"svelte/store";const t=()=>t=>{function n(n,r){let i=n;return e(r,e=>t.on(i,e))}return Object.assign(t,{eventStore:n})};export{t as sveltePlugin};
package/dist/trace.js CHANGED
@@ -1 +1 @@
1
- import{a as e,c as t,d as n,n as r,o as i,r as a,s as o,u as s}from"./context-CJGW6OWj.js";import{n as c}from"./promise-CSLpzwSR.js";import{t as l}from"./origin-key-BbWN3gmN.js";import{t as u}from"./scope-key-B7l7mUS5.js";const d=(e={})=>{let t=e.console??globalThis.console,n=e.prefix??`[nanoeffects]`,r=/* @__PURE__ */ new Map;return({id:e,parentId:i,type:a,effect:o,phase:s,payload:c,error:l,traceGroupId:u})=>{r.has(e)||r.set(e,typeof i==`number`?(r.get(i)??0)+1:0);let d=` `.repeat(r.get(e)??0);s!==`start`&&r.delete(e);let f=u===void 0?``:` [${u}]`,p=`${n}${f} ${d}${a} · ${o} ${s}`;s===`error`?t?.error(p,l):t?.debug(p,c)}},f=(e={})=>{let t=e.performance??globalThis.performance,n=/* @__PURE__ */ new Map;return({id:e,parentId:r,rootId:i,traceGroupId:a,type:o,effect:s,phase:c,error:l})=>{if(!t)return;if(c===`start`){n.set(e,t.now());return}let u=`${o} · ${s} (${c})`,d={id:e,parentId:r,...i!==void 0&&{rootId:i},...a!==void 0&&{traceGroupId:a},phase:c,...c===`error`&&{error:String(l)}},f=n.get(e);n.delete(e),typeof f==`number`?t.measure(u,{start:f,end:t.now(),detail:d}):t.mark(u,{detail:d})}};let p;const m=()=>p,h=(e,t)=>{let n=p;p=e;try{return t()}finally{p=n}},g=(e,t,n={})=>{let{trace:i}=e;if(!i){e.emit(t);return}let{traceId:a,traceGroupId:s}=n,{ctx:c,unlink:l}=r({event:{type:a??t.type,payload:t,...s===void 0?{}:{traceGroupId:s}},bus:{emit:e.emit,on:e.on,off:e.off,ctx:e.ctx,addContext:e.addContext},tracer:i});o(c,`run`,`start`);try{c.emit(t),o(c,`run`,`done`)}catch(e){throw o(c,`run`,`error`,e),e}finally{l()}},_=e=>typeof e==`string`?e:Reflect.get(Object(e),`type`),v=(e,t)=>typeof e==`object`?Reflect.get(Object(e),`payload`):t[0],y=()=>n=>{if(!(t&&n.trace))return n;let{trace:r}=n,o=(e,t)=>{let n=r.task({type:_(e),payload:v(e,t)},a());r.report(n,`emit`,`done`)},s=n.emit;n.emit=(e,...t)=>h(e,()=>(o(e,t),s(e,...t))),Reflect.set(n,l,(e,t)=>{g(n,e,t)});let c=/* @__PURE__ */ new WeakMap,u=(e,t,n)=>{let r=c.get(t)??/* @__PURE__ */ new Map,i=_(e);r.set(i,[...r.get(i)??[],n]),c.set(t,r)},d=(e,t)=>(c.get(t)?.get(_(e)))?.pop(),f=(t,n)=>(r,a)=>(e(a,n),i(a,`run`,()=>t(r,a))),p=(e,t)=>(n,r,i)=>{let a=Reflect.get(Object(i),`traceId`);if(typeof a!=`string`)return e(n,r);let o=t?(e,t)=>(d(n,r),f(r,a)(e,t)):f(r,a);u(n,r,o);let s=e(n,o);return()=>{s()}},m=n.off,y=(e,t)=>{t===void 0?m(e):m(e,d(e,t)??t)},b=n.on,x=n.once;return n.on=p(b,!1),n.once=p(x,!0),n.off=y,n},b=()=>{let e=[];return Object.assign(t=>{e.push(t)},{events:e,clear:()=>{e.length=0}})},x=()=>{},S=/* @__PURE__ */ new Set([`cancel`,`drop`,`error`]),C=()=>!1,w=()=>!0,T=e=>{if(typeof e==`function`)return e;if(Array.isArray(e)){let t=new Set(e);return({phase:e})=>t.has(e)}return e?w:C},E=(e={})=>{let t=e.extension??globalThis.__REDUX_DEVTOOLS_EXTENSION__;if(!t)return x;let{starts:n=!1,depth:r=0,maxAge:i}=e,a=T(e.policies),o=e.payload??`essential`,s=t.connect({name:e.name??`nanoeffects`,...i!==void 0&&{maxAge:i}}),c=/* @__PURE__ */ new Map,l=/* @__PURE__ */ new Map,u=()=>({running:Object.fromEntries(c)});s.init(u());let d=(e,t,n)=>n===0?a({effect:e,phase:t}):n<=r,f=(e,t)=>{e.phase===`start`?(l.set(e.id,t),d(e.effect,`start`,t)&&c.set(e.id,{type:e.type,effect:e.effect,...e.traceGroupId!==void 0&&{traceGroupId:e.traceGroupId}})):(l.delete(e.id),c.delete(e.id))},p=!1,m=!1,h=(e,t)=>{p||m||t!==0||e===`start`||(m=!0,globalThis.console?.warn(`[nanoeffects] reduxDevtools got a task but no emit event: add bus.use(tracePlugin()) to see the emits.`))},g=e=>{let{payload:t,...n}=e;return o===`all`||o===`essential`&&(e.effect===`emit`||S.has(e.phase))?{...n,payload:t}:n},_=(e,t)=>{s.send({...g(e),type:`${e.type}/${e.effect}/${e.phase}`,event:e.type,...e.phase===`error`&&{error:t instanceof Error?t.message:t}},u())};return({error:e,...t})=>{if(t.effect===`emit`){p=!0,s.send({...g(t),event:t.type},u());return}let r=(t.parentId===void 0?-1:l.get(t.parentId)??-1)+1;f(t,r),h(t.phase,r),(t.phase===`error`&&r===0||d(t.effect,t.phase,r))&&(t.phase!==`start`||n)&&_(t,e)}},D=e=>{let t=typeof e==`function`?[e]:[...e??[]];return t.length===0?null:e=>{for(let r of t)try{r(e)}catch(e){n(`trace logger failed`,e)}}},O=()=>{},k=e=>{if(typeof e.traceGroupId==`string`)return e.traceGroupId;let t=m();if(typeof t!=`object`&&typeof t!=`function`||t===null)return;let n=Reflect.get(t,u),r=typeof n==`object`&&n?Reflect.get(n,`traceGroupId`):void 0;return typeof r==`string`?r:void 0},A=(e,t={})=>{let n=D(e),{filter:r,sample:i=1}=t,a=0,o=e=>typeof i==`function`?i({type:e.type,payload:e.payload}):Math.random()<i,l={task:(e,t)=>({id:++a,root:t?.root??a,parent:t?.id,type:e.type,payload:e.payload,sampled:t?.sampled??o(e),group:k(e)??t?.group}),rename:(e,t)=>{e.type=t},report:(e,t,i,a)=>{if(!e.sampled)return;let o={id:e.id,type:e.type,effect:t,phase:i,payload:e.payload};typeof e.parent==`number`&&(o.parentId=e.parent),e.root!==e.id&&(o.rootId=e.root),e.group!==void 0&&(o.traceGroupId=e.group),i===`error`&&(o.error=a),(!r||r(o))&&n?.(o)},settler:(e,t)=>{if(!e.sampled)return O;let n=!1;return(r,i)=>{n||(n=!0,l.report(e,t,r,i))}},run:(e,t,n,r)=>{if(!e.sampled)return r();let i=l.settler(e,t);l.report(e,t,`start`),s({signal:n},()=>i(`cancel`));let a;try{a=r()}catch(e){throw i(`error`,e),e}return c(a)?Promise.resolve(a).then(e=>(i(`done`),e),e=>{throw i(`error`,e),e}):(i(`done`),a)}};return l};export{d as consoleLogger,b as createTraceRecorder,A as createTracer,f as performanceLogger,E as reduxDevtools,y as tracePlugin};
1
+ import{a as e,c as t,d as n,n as r,o as i,r as a,s as o,u as s}from"./context-UcmrAIhP.js";import{n as c}from"./promise-CSLpzwSR.js";import{t as l}from"./origin-key-BbWN3gmN.js";import{t as u}from"./scope-key-B7l7mUS5.js";const d=(e={})=>{let t=e.console??globalThis.console,n=e.prefix??`[nanoeffects]`,r=/* @__PURE__ */ new Map;return({id:e,parentId:i,type:a,effect:o,phase:s,payload:c,error:l,traceGroupId:u})=>{r.has(e)||r.set(e,typeof i==`number`?(r.get(i)??0)+1:0);let d=` `.repeat(r.get(e)??0);s!==`start`&&r.delete(e);let f=u===void 0?``:` [${u}]`,p=`${n}${f} ${d}${a} · ${o} ${s}`;s===`error`?t?.error(p,l):t?.debug(p,c)}},f=(e={})=>{let t=e.performance??globalThis.performance,n=/* @__PURE__ */ new Map;return({id:e,parentId:r,rootId:i,traceGroupId:a,type:o,effect:s,phase:c,error:l})=>{if(!t)return;if(c===`start`){n.set(e,t.now());return}let u=`${o} · ${s} (${c})`,d={id:e,parentId:r,...i!==void 0&&{rootId:i},...a!==void 0&&{traceGroupId:a},phase:c,...c===`error`&&{error:String(l)}},f=n.get(e);n.delete(e),typeof f==`number`?t.measure(u,{start:f,end:t.now(),detail:d}):t.mark(u,{detail:d})}};let p;const m=()=>p,h=(e,t)=>{let n=p;p=e;try{return t()}finally{p=n}},g=(e,t,n={})=>{let{trace:i}=e;if(!i){e.emit(t);return}let{traceId:a,traceGroupId:s}=n,{ctx:c,unlink:l}=r({event:{type:a??t.type,payload:t,...s===void 0?{}:{traceGroupId:s}},bus:{emit:e.emit,on:e.on,off:e.off,ctx:e.ctx,addContext:e.addContext},tracer:i});o(c,`run`,`start`);try{c.emit(t),o(c,`run`,`done`)}catch(e){throw o(c,`run`,`error`,e),e}finally{l()}},_=e=>typeof e==`string`?e:Reflect.get(Object(e),`type`),v=(e,t)=>typeof e==`object`?Reflect.get(Object(e),`payload`):t[0],y=()=>n=>{if(!(t&&n.trace))return n;let{trace:r}=n,o=(e,t)=>{let n=r.task({type:_(e),payload:v(e,t)},a());r.report(n,`emit`,`done`)},s=n.emit;n.emit=(e,...t)=>h(e,()=>(o(e,t),s(e,...t))),Reflect.set(n,l,(e,t)=>{g(n,e,t)});let c=/* @__PURE__ */ new WeakMap,u=(e,t,n)=>{let r=c.get(t)??/* @__PURE__ */ new Map,i=_(e);r.set(i,[...r.get(i)??[],n]),c.set(t,r)},d=(e,t)=>(c.get(t)?.get(_(e)))?.pop(),f=(t,n)=>(r,a)=>(e(a,n),i(a,`run`,()=>t(r,a))),p=(e,t)=>(n,r,i)=>{let a=Reflect.get(Object(i),`traceId`);if(typeof a!=`string`)return e(n,r);let o=t?(e,t)=>(d(n,r),f(r,a)(e,t)):f(r,a);u(n,r,o);let s=e(n,o);return()=>{s()}},m=n.off,y=(e,t)=>{t===void 0?m(e):m(e,d(e,t)??t)},b=n.on,x=n.once;return n.on=p(b,!1),n.once=p(x,!0),n.off=y,n},b=()=>{let e=[];return Object.assign(t=>{e.push(t)},{events:e,clear:()=>{e.length=0}})},x=()=>{},S=/* @__PURE__ */ new Set([`cancel`,`drop`,`error`]),C=()=>!1,w=()=>!0,T=e=>{if(typeof e==`function`)return e;if(Array.isArray(e)){let t=new Set(e);return({phase:e})=>t.has(e)}return e?w:C},E=(e={})=>{let t=e.extension??globalThis.__REDUX_DEVTOOLS_EXTENSION__;if(!t)return x;let{starts:n=!1,depth:r=0,maxAge:i}=e,a=T(e.policies),o=e.payload??`essential`,s=t.connect({name:e.name??`nanoeffects`,...i!==void 0&&{maxAge:i}}),c=/* @__PURE__ */ new Map,l=/* @__PURE__ */ new Map,u=()=>({running:Object.fromEntries(c)});s.init(u());let d=(e,t,n)=>n===0?a({effect:e,phase:t}):n<=r,f=(e,t)=>{e.phase===`start`?(l.set(e.id,t),d(e.effect,`start`,t)&&c.set(e.id,{type:e.type,effect:e.effect,...e.traceGroupId!==void 0&&{traceGroupId:e.traceGroupId}})):(l.delete(e.id),c.delete(e.id))},p=!1,m=!1,h=(e,t)=>{p||m||t!==0||e===`start`||(m=!0,globalThis.console?.warn(`[nanoeffects] reduxDevtools got a task but no emit event: add bus.use(tracePlugin()) to see the emits.`))},g=e=>{let{payload:t,...n}=e;return o===`all`||o===`essential`&&(e.effect===`emit`||S.has(e.phase))?{...n,payload:t}:n},_=(e,t)=>{s.send({...g(e),type:`${e.type}/${e.effect}/${e.phase}`,event:e.type,...e.phase===`error`&&{error:t instanceof Error?t.message:t}},u())};return({error:e,...t})=>{if(t.effect===`emit`){p=!0,s.send({...g(t),event:t.type},u());return}let r=(t.parentId===void 0?-1:l.get(t.parentId)??-1)+1;f(t,r),h(t.phase,r),(t.phase===`error`&&r===0||d(t.effect,t.phase,r))&&(t.phase!==`start`||n)&&_(t,e)}},D=e=>{let t=typeof e==`function`?[e]:[...e??[]];return t.length===0?null:e=>{for(let r of t)try{r(e)}catch(e){n(`trace logger failed`,e)}}},O=()=>{},k=e=>{if(typeof e.traceGroupId==`string`)return e.traceGroupId;let t=m();if(typeof t!=`object`&&typeof t!=`function`||t===null)return;let n=Reflect.get(t,u),r=typeof n==`object`&&n?Reflect.get(n,`traceGroupId`):void 0;return typeof r==`string`?r:void 0},A=(e,t={})=>{let n=D(e),{filter:r,sample:i=1}=t,a=0,o=e=>typeof i==`function`?i({type:e.type,payload:e.payload}):Math.random()<i,l={task:(e,t)=>({id:++a,root:t?.root??a,parent:t?.id,type:e.type,payload:e.payload,sampled:t?.sampled??o(e),group:k(e)??t?.group}),rename:(e,t)=>{e.type=t},report:(e,t,i,a)=>{if(!e.sampled)return;let o={id:e.id,type:e.type,effect:t,phase:i,payload:e.payload};typeof e.parent==`number`&&(o.parentId=e.parent),e.root!==e.id&&(o.rootId=e.root),e.group!==void 0&&(o.traceGroupId=e.group),i===`error`&&(o.error=a),(!r||r(o))&&n?.(o)},settler:(e,t)=>{if(!e.sampled)return O;let n=!1;return(r,i)=>{n||(n=!0,l.report(e,t,r,i))}},run:(e,t,n,r)=>{if(!e.sampled)return r();let i=l.settler(e,t);l.report(e,t,`start`),s({signal:n},()=>i(`cancel`));let a;try{a=r()}catch(e){throw i(`error`,e),e}return c(a)?Promise.resolve(a).then(e=>(i(`done`),e),e=>{throw i(`error`,e),e}):(i(`done`),a)}};return l};export{d as consoleLogger,b as createTraceRecorder,A as createTracer,f as performanceLogger,E as reduxDevtools,y as tracePlugin};
@@ -1,5 +1,5 @@
1
1
  import { d as Effects, o as Scope, s as WithFields, z as EventMap } from "./action-creator-Bpa8BUS9.js";
2
- import { t as BoundOf } from "./plugin-D9UTzGRG.js";
2
+ import { t as BoundOf } from "./plugin-BZK83Z3N.js";
3
3
  //#region src/scope/types.d.ts
4
4
  /**
5
5
  * The slots `scopePlugin` fills for `modulePlugin`. They are not on `Bus`: a bus
@@ -1 +1 @@
1
- import{n as e}from"./context-CJGW6OWj.js";const t=Reflect.get(Symbol,`dispose`),n=n=>{let r=new AbortController,i=async(...t)=>{let i=t[0],{ctx:a,unlink:o}=e({event:{type:`withPolicy`,payload:i},parent:{signal:r.signal}});try{return await n(i,a)}finally{o()}},a=e=>{r.abort(e),r=new AbortController},o=Object.assign(i,{cancel:a});return t&&Reflect.set(o,t,()=>a()),o};export{n as t};
1
+ import{n as e}from"./context-UcmrAIhP.js";const t=Reflect.get(Symbol,`dispose`),n=n=>{let r=new AbortController,i=async(...t)=>{let i=t[0],{ctx:a,unlink:o}=e({event:{type:`withPolicy`,payload:i},parent:{signal:r.signal}});try{return await n(i,a)}finally{o()}},a=e=>{r.abort(e),r=new AbortController},o=Object.assign(i,{cancel:a});return t&&Reflect.set(o,t,()=>a()),o};export{n as t};
package/docs/README.md CHANGED
@@ -17,8 +17,10 @@ Code for every guide step: [`examples/guide.ts`](../examples/guide.ts) (type-che
17
17
  | Unreliable network / long flows | [Effects](effects.md) — one helper per job, then the rest as reference |
18
18
  | Domain logic off the UI | [Actions, commands, modules](actions.md) |
19
19
  | Already on Redux / Zustand / MobX / Pinia | [Stores](stores.md) |
20
+ | A Node server (Fastify) whose work must stop when the client leaves | [Stop work on a server](server.md) |
20
21
  | A React form with async checks | [Build a sign-up form in React](react.md) |
21
22
  | A Vue app | [Use nanoeffects in a Vue app](vue.md) |
23
+ | A Svelte app | [Use nanoeffects in a Svelte app](svelte.md) |
22
24
  | Old responses overwrite new ones | [Cancel stale requests](cancel-stale-requests.md) |
23
25
  | Debounced input that must also cancel the old request | [Debounce and abort](debounce-and-abort.md) |
24
26
  | A second click sends a second request | [Prevent a double submit](prevent-double-submit.md) |
@@ -34,6 +36,7 @@ Code for every guide step: [`examples/guide.ts`](../examples/guide.ts) (type-che
34
36
  - [Actions, commands, jobs and modules](actions.md)
35
37
  - [Sign-up form in React](react.md) — one form: races, double submit, retry
36
38
  - [nanoeffects in a Vue app](vue.md) — `watchEmitter` and `useEventRef`
39
+ - [nanoeffects in a Svelte app](svelte.md) — `eventStore`
37
40
  - [Context fields](context.md) — store or client on every task
38
41
  - [Use the bus from a class](oop.md) — `extendBus`, `@WithBus`, `@On`, and `dispose()` for the subscriptions of an instance
39
42
  - [TypeScript in many files](typescript.md) — keep the types of the bus in commands and modules
package/docs/actions.md CHANGED
@@ -6,7 +6,7 @@ If you only need `on` / `emit` or policies, stay in the [Guide](guide.md) until
6
6
 
7
7
  Recipe without a UI framework: [`examples/modules.ts`](../examples/modules.ts).
8
8
 
9
- A chat with four domains, jobs under policies and effects, and a stream tied to a module: [Build a chat with domains, modules and a small server](chat-example.md).
9
+ A chat with two entities and three features, jobs under policies and effects, and a stream tied to a module: [Build a chat with entities, features, modules and a small server](chat-example.md).
10
10
 
11
11
  A whole project with two domains, a job and React as a thin layer: `npm create nanoeffects@latest my-app -- --template domains-ts` (see [Start a project from a template](starter.md#start-with-domains)).
12
12
 
@@ -1,4 +1,4 @@
1
- # Build a chat with domains, modules and a small server
1
+ # Build a chat with entities, features, modules and a small server
2
2
 
3
3
  Use this page when your app has several features that react to each other and you want to see where each piece of logic goes. The example is a chat with rooms, a history that loads, messages that can fail to send, a search across all rooms, and new messages that arrive on their own. A small Fastify server in the example answers every request, and it misbehaves on a known schedule, so each problem on this page really happens.
4
4
 
@@ -30,21 +30,27 @@ Open the Network panel of the browser while you do this. A request that the page
30
30
 
31
31
  ## See how the files are split
32
32
 
33
- Each domain is a folder. The components only emit events and read values; they never call the server.
33
+ The code is in layers, and a file imports only from the layers below it. The components only emit events and read values; they never call the server.
34
34
 
35
- | Path | Job |
36
- | ---- | --- |
37
- | `src/bus.ts` | Creates the bus. It imports no events and no domain. |
38
- | `src/app.ts` | Mounts the four domains. |
39
- | `src/api.ts` | The client of the server: one function per request. |
40
- | `server/` | The Fastify server and the script that starts it with Vite. |
41
- | `src/domains/rooms/` | The list of rooms and the selected room. |
42
- | `src/domains/messages/` | The history of the open room and sending a message. |
43
- | `src/domains/search/` | Searching every room and opening a result. |
44
- | `src/domains/incoming/` | New messages, asked for at an interval. |
45
- | `src/ui/` | The components. |
35
+ | Path | Layer | Job |
36
+ | ---- | ----- | --- |
37
+ | `src/shared/bus.ts` | shared | Creates the bus. It imports no events, no entity and no feature. |
38
+ | `src/shared/api.ts` | shared | The client of the server: one function per request. |
39
+ | `src/entities/room/` | entity | The list of rooms and the selected room. |
40
+ | `src/entities/message/` | entity | The history of the open room, sending a message, and a message that arrives. |
41
+ | `src/features/show-history/` | feature | A selected room loads its history. |
42
+ | `src/features/search-messages/` | feature | Searching every room and opening a result. |
43
+ | `src/features/receive-messages/` | feature | New messages, asked for at an interval. |
44
+ | `src/ui/`, `src/app.ts` | top | The components, and the file that mounts the modules. |
45
+ | `server/` | | The Fastify server and the script that starts it with Vite. |
46
46
 
47
- A domain folder has its events (`rooms.ts`, which imports no bus), its work (`rooms-handlers.ts`), the state it keeps (`rooms-store.ts`) and a module (`rooms-module.ts`) that wires them. A domain reacts to another domain by listening to its events, never by importing its handlers or its store. Events and commands both live in the events file. An event (`createAction`) says that something happened, and any domain may listen: `rooms/selected` is heard by three. A command (`createCommand`) is an order with exactly one handler, as `search/openResult` and `messages/mark` are. The handler is given to `createCommand`, so the command works wherever it is called and can return a result, and the module binds it with `module.use` for as long as it is mounted. A job is a command that also sends its outcomes, but its handler is bound in the module (`module.handle`): a job usually runs under a policy, and the type of its outcomes is exact only when the handler is not part of the type.
47
+ **The rule.** An entity is a thing with its own state and orders (a room, a message). It imports `shared` and nothing else. A feature is a scenario that connects things: it imports `shared` and entities, and never another feature. The top imports anything below. No two entities import each other and no two features import each other, and a test in the repository (`src/tests/architecture.test.ts`) fails when one does.
48
+
49
+ That rule is why `show-history` exists. Selecting a room must load its history, but `room` does not know `message` and `message` does not know `room`. The feature `show-history` is the one place that knows both: it listens to `rooms/selected` and starts the job `messages/loadHistory`. The same goes for a message that arrives: `receive-messages` emits `incoming/received`, an event that the `message` entity owns and listens to.
50
+
51
+ A folder has its events (`room.ts`, which imports no bus), its work (`room-handlers.ts`), the state it keeps (`room-store.ts`) and a module (`room-module.ts`) that wires them. Another layer reaches an entity through its events and commands, never through its handlers or its store. Events and commands both live in the events file. An event (`createAction`) says that something happened, and any feature may listen: `rooms/selected` is heard by two. A command (`createCommand`) is an order with exactly one handler, as `search/openResult` and `messages/mark` are. The handler is given to `createCommand`, so the command works wherever it is called and can return a result, and the module binds it with `module.use` for as long as it is mounted. A job is a command that also sends its outcomes, but its handler is bound in the module (`module.handle`): a job usually runs under a policy, and the type of its outcomes is exact only when the handler is not part of the type.
52
+
53
+ The layers come from [Feature-Sliced Design](https://feature-sliced.design/), reduced to the three that an app of this size needs.
48
54
 
49
55
  ## Load a history that must not race
50
56
 
@@ -55,7 +61,7 @@ A domain folder has its events (`rooms.ts`, which imports no bus), its work (`ro
55
61
  A job sends `pending` when its handler starts and exactly one of `success`, `fail` and `cancel` when it ends. `latest` around the handler aborts the older load when a newer one starts, so only the newer result is delivered, and the older one sends `cancel`:
56
62
 
57
63
  ```js
58
- // messages.js
64
+ // message.js
59
65
  import { createJob } from "nanoeffects/actions";
60
66
 
61
67
  // createJob — a command that also sends pending, success, fail and cancel
@@ -63,7 +69,7 @@ export const loadHistory = createJob("messages/loadHistory");
63
69
  ```
64
70
 
65
71
  ```js
66
- // messages-handlers.js
72
+ // message-handlers.js
67
73
  import { latest } from "nanoeffects/policies";
68
74
 
69
75
  // latest — a load for a newer room aborts the older one, and its result is never delivered
@@ -73,12 +79,12 @@ export const fetchHistory = latest((roomId, { signal }) => api.loadHistory(roomI
73
79
  The module gives the job its work and keeps the state from the outcomes, so an aborted load clears the flag too (see [Show a loading state with a job](actions.md#show-a-loading-state-with-a-job)):
74
80
 
75
81
  ```js
76
- // messages-module.js
82
+ // message-module.js
77
83
  import { createModule } from "nanoeffects/actions";
78
- import { loadHistory } from "./messages";
79
- import { fetchHistory } from "./messages-handlers";
84
+ import { loadHistory } from "./message";
85
+ import { fetchHistory } from "./message-handlers";
80
86
 
81
- export const messagesModule = createModule("messages", (module) => {
87
+ export const messageModule = createModule("message", (module) => {
82
88
  // handle — this module does the work of loadHistory while it is mounted
83
89
  module.handle(loadHistory, fetchHistory);
84
90
  module.on(loadHistory.pending, () => console.log("show the spinner"));
@@ -168,7 +174,7 @@ export const runSearch = debounce(
168
174
 
169
175
  **Problem.** The reader opens a search result from another room. The message cannot be marked until that room's history has loaded, and the handler must not guess how long that takes.
170
176
 
171
- **When.** A handler starts work in another domain and needs to continue after its outcome.
177
+ **When.** A feature starts work in an entity and needs to continue after its outcome.
172
178
 
173
179
  `take` waits for the next event of a name. Start it before the emit that causes the event, so nothing is missed:
174
180
 
@@ -176,7 +182,7 @@ export const runSearch = debounce(
176
182
  import { createCommand } from "nanoeffects/actions";
177
183
  import { take } from "nanoeffects/effects";
178
184
 
179
- // createCommand — an order for the messages domain, with the handler that does it
185
+ // createCommand — an order for the message entity, with the handler that does it
180
186
  const markMessage = createCommand("messages/mark", (messageId) => console.log("mark", messageId));
181
187
 
182
188
  // the room that is open, kept from the rooms/selected event
@@ -189,7 +195,7 @@ export const goToResult = async (hit, ctx) => {
189
195
  ctx.emit("rooms/selected", hit.roomId);
190
196
  await loaded;
191
197
  }
192
- // ctx.emit with a command — the messages domain marks the message
198
+ // ctx.emit with a command — the message entity marks the message
193
199
  ctx.emit(markMessage(hit.id));
194
200
  };
195
201
  ```
@@ -227,20 +233,20 @@ const watchUpdates = async (_, ctx) => {
227
233
  await poll(ctx, check, 3000, { leading: true });
228
234
  };
229
235
 
230
- export const incomingModule = createModule("incoming", (module) => {
236
+ export const receiveMessagesModule = createModule("receive-messages", (module) => {
231
237
  // spawn — a background task that lives as long as this module
232
238
  module.spawn(watchUpdates);
233
239
  });
234
240
  ```
235
241
 
236
- In the example a button calls `app.unmount(incomingModule)` and `app.mount(incomingModule)`, so you can watch the polling stop (the Network panel shows no more `updates` requests) and start.
242
+ In the example a button calls `app.unmount(receiveMessagesModule)` and `app.mount(receiveMessagesModule)`, so you can watch the polling stop (the Network panel shows no more `updates` requests) and start.
237
243
 
238
244
  ## Know where each value lives
239
245
 
240
- Each domain keeps its state in a [Zustand](https://zustand.docs.pmnd.rs/) store that the module writes and a component reads with `useStore`. Each store lists its mutations, such as `startLoading(roomId)` and `markFailed(localId)`, and a module calls them with the data of an event: `module.on(loadHistory.pending, startLoading)`. A mutation that sets plain values gives `set` an object, as in `set({ searching: true })`. The store of the messages uses the `immer` middleware, so a mutation that changes a list can change it in place, as in `state.messages.push(message)`, and the store still gives React a new object. Nothing else writes the state. The list of rooms is the exception: it is read with one `useEventState` in a provider at the root. `useEventState` is not a store: an event that came before the provider mounted is lost, and a second component gets its own copy. See [Show the last value of an event as state](react.md#show-the-last-value-of-an-event-as-state).
246
+ Each entity and each feature that has state keeps it in a [Zustand](https://zustand.docs.pmnd.rs/) store that the module writes and a component reads with `useStore`. Each store lists its mutations, such as `startLoading(roomId)` and `markFailed(localId)`, and a module calls them with the data of an event: `module.on(loadHistory.pending, startLoading)`. A mutation that sets plain values gives `set` an object, as in `set({ searching: true })`. The store of the message entity uses the `immer` middleware, so a mutation that changes a list can change it in place, as in `state.messages.push(message)`, and the store still gives React a new object. Nothing else writes the state. The list of rooms is the exception: it is read with one `useEventState` in a provider at the root. `useEventState` is not a store: an event that came before the provider mounted is lost, and a second component gets its own copy. See [Show the last value of an event as state](react.md#show-the-last-value-of-an-event-as-state).
241
247
 
242
248
  ## Where to go next
243
249
 
244
250
  - The API of commands, jobs and modules: [Actions, commands, jobs and modules](actions.md).
245
251
  - Policies such as `latest` and `queue`: [Policies](policies.md). Effects such as `allSettled`, `take` and `poll`: [Effects](effects.md).
246
- - A smaller project with two domains: [Start with domains](starter.md#start-with-domains).
252
+ - A smaller project with two domains: [Start with domains](starter.md#start-with-domains). Its two domains do not talk to each other, so it needs no layers.
package/docs/effects.md CHANGED
@@ -161,7 +161,8 @@ bus.on("load", async (id, { race, all, delay }) => {
161
161
  // race — same as race(ctx, …)
162
162
  const result = await race({
163
163
  data: ({ signal }) => fetchData(id, { signal }),
164
- timeout: () => delay(5000), // delay — same as delay(ctx, 5000)
164
+ // a starter gets its own child with the same bound effects, so delay(5000) stops with the race
165
+ timeout: ({ delay }) => delay(5000),
165
166
  });
166
167
  if ("timeout" in result) throw new Error("timed out");
167
168
 
@@ -244,28 +245,35 @@ bus.on("watch", (_, ctx) => {
244
245
  - Under `latest`, subscriptions of a superseded run end with that run.
245
246
  - `ctx.off(event)` clears every handler of the event on the bus, not only the task’s.
246
247
 
247
- `take(ctx, event, options?)` waits for the next `event` and resolves with its payload:
248
+ `take(ctx, event, options?)` waits for the next `event` and resolves with its payload. `event` is the name of an event or an action creator, as for `bus.on`:
248
249
 
249
250
  ```js
250
251
  import { createBus } from "nanoeffects";
252
+ import { createAction } from "nanoeffects/actions";
251
253
  import { take, timeout } from "nanoeffects/effects";
252
254
 
253
255
  const bus = createBus();
256
+ const stopRequested = createAction("stop"); // an event with a name; calling it builds the action
254
257
 
255
258
  bus.on("login", async (user, ctx) => {
256
259
  const reason = await take(ctx, "logout"); // resolves on the next logout
257
260
  cleanup(user, reason);
258
261
 
262
+ // an action creator works the same, and its payload type is the type of the result
263
+ const stopped = await take(ctx, stopRequested);
264
+ console.log(stopped);
265
+
259
266
  // wait for a matching event, but not longer than 5 s
260
- const id = await timeout(ctx, 5000, ({ signal }) =>
261
- take(ctx, "saved", { signal, predicate: (savedId) => savedId === draft.id }),
267
+ const id = await timeout(ctx, 5000, (child) =>
268
+ take(child, "saved", { predicate: (savedId) => savedId === draft.id }),
262
269
  );
263
270
  });
264
271
  ```
265
272
 
266
273
  - Sees only events emitted after the call, takes one, and unsubscribes.
274
+ - With an action creator the result has the payload type of the creator. A creator made without a payload type (`createAction("saved")`) takes it from the event map of the bus.
267
275
  - `options.predicate` skips payloads it rejects. In TypeScript, a type guard (`(payload): payload is Admin`) narrows the type of the result.
268
- - `options.signal` stops the wait (how a `race` or `timeout` starter cancels it).
276
+ - `options.signal` stops the wait. In a starter of `race`, `all` or `timeout` pass `child` as the first argument instead: it already carries the signal.
269
277
  - On abort of the task or of `options.signal`, unsubscribes and rejects with the abort reason.
270
278
 
271
279
  ## Run several starters together
@@ -294,6 +302,29 @@ bus.on("open", async (id, ctx) => {
294
302
 
295
303
  `race(ctx, effects)` settles with the first starter that finishes and cancels the others.
296
304
 
305
+ ```js
306
+ import { createBus } from "nanoeffects";
307
+ import { delay, race, take } from "nanoeffects/effects";
308
+
309
+ const bus = createBus();
310
+
311
+ bus.on("save", async (draft, ctx) => {
312
+ // race — the first starter to finish wins, the other is cancelled
313
+ const result = await race(ctx, {
314
+ waited: (child) => delay(child, 1000),
315
+ cancelled: (child) => take(child, "cancel"),
316
+ });
317
+
318
+ if ("waited" in result) {
319
+ ctx.emit("saved", draft);
320
+ }
321
+ });
322
+ ```
323
+
324
+ **A starter gets the context of its own child task.** Pass `child` as the first argument to any effect, as a handler passes `ctx`: when the race ends, `child` is aborted and stops exactly this effect. The outer `ctx` is only for `race` itself. Your own API takes the signal in its options: `({ signal }) => fetchUser(id, { signal })`. The same holds for `all`, `allSettled` and `timeout`.
325
+
326
+ If `ctx` is `{}` or a bare `{ signal }` (outside a task), `child` has only `signal` and `abort`, so it fits `delay` and `poll` but not `take`, which needs a bus.
327
+
297
328
  ## Pass `ctx` outside a task
298
329
 
299
330
  Effects that wait inside a body take `ctx` first: `delay`, `all`, `allSettled`, `race`, `cancellable`, `onAbort`, `take`, and the `ctx` forms of `retry` / `timeout` / `poll`. Outside a task, pass `{}` or any object with a `signal`.
@@ -122,9 +122,9 @@ bus.on("watch", async (_, ctx) => {
122
122
 
123
123
  await race(ctx, {
124
124
  // poll: a check every 5 s; waitForFn: the next pause starts after the check ends
125
- polling: ({ signal }) => poll({ signal }, checkStatus, 5000, { waitForFn: true }),
125
+ polling: (child) => poll(child, checkStatus, 5000, { waitForFn: true }),
126
126
  // take: resolves at the next "leave", and race stops the polling
127
- leave: ({ signal }) => take(ctx, "leave", { signal }),
127
+ leave: (child) => take(child, "leave"),
128
128
  });
129
129
  });
130
130
 
@@ -14,7 +14,7 @@ Read this page if you know redux-saga and want to find the same job in nanoeffec
14
14
  | `fork(fn, x)` | `ctx.run(task, x)` without `await` |
15
15
  | `spawn(fn, x)` | `ctx.emit(event, x)` or `bus.emit(event, x)` |
16
16
  | `put(action)` | `ctx.emit(action)` |
17
- | `take(pattern)` | [`take(ctx, event, { predicate })`](effects.md) |
17
+ | `take(pattern)` | [`take(ctx, event, { predicate })`](effects.md), where `event` is a name or an action creator |
18
18
  | `select` | a [context field](context.md), such as `store` |
19
19
  | `all`, `race` | [`all`, `race`](effects.md) |
20
20
  | `retry`, `delay` | [`retry`, `delay`](effects.md) |
@@ -62,6 +62,8 @@ bus.on(
62
62
 
63
63
  Tasks are plain `async` functions. `ctx.signal` replaces `cancel`: pass it to `fetch` or to your client, and the request stops.
64
64
 
65
+ To see the counter, `takeLatest` with `delay`, a cancel and a failing call running, open the [counter example](../examples/counter): it has a page without a store and a page with Redux Toolkit.
66
+
65
67
  ## Start a task without waiting
66
68
 
67
69
  `fork` and `spawn` differ in what happens when the caller is aborted. Pick by that.