nanoeffects 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +95 -2
- package/dist/action-BM-UdmJr.d.ts +302 -0
- package/dist/actions.d.ts +196 -0
- package/dist/actions.js +1 -0
- package/dist/bus-RUiw8f9p.js +1 -0
- package/dist/context-BXQ6Einm.js +1 -0
- package/dist/delay-CIHnOh_L.js +1 -0
- package/dist/effects.d.ts +244 -0
- package/dist/effects.js +1 -0
- package/dist/event-0RIBGYqS.js +1 -0
- package/dist/index-CaxrMD1A.d.ts +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/notrace/action-BM-UdmJr.d.ts +302 -0
- package/dist/notrace/actions.d.ts +196 -0
- package/dist/notrace/actions.js +1 -0
- package/dist/notrace/bus-CFxgpsRL.js +1 -0
- package/dist/notrace/context-DGPIFXxI.js +1 -0
- package/dist/notrace/delay-Cko_Q9hl.js +1 -0
- package/dist/notrace/effects.d.ts +244 -0
- package/dist/notrace/effects.js +1 -0
- package/dist/notrace/event-0RIBGYqS.js +1 -0
- package/dist/notrace/index-CaxrMD1A.d.ts +1 -0
- package/dist/notrace/index.d.ts +3 -0
- package/dist/notrace/index.js +1 -0
- package/dist/notrace/policies.d.ts +171 -0
- package/dist/notrace/policies.js +1 -0
- package/dist/notrace/promise-CSLpzwSR.js +1 -0
- package/dist/notrace/redux.d.ts +92 -0
- package/dist/notrace/redux.js +1 -0
- package/dist/observable.d.ts +20 -0
- package/dist/observable.js +1 -0
- package/dist/onAbort-BgMfspK4.js +1 -0
- package/dist/policies.d.ts +171 -0
- package/dist/policies.js +1 -0
- package/dist/promise-CSLpzwSR.js +1 -0
- package/dist/redux.d.ts +92 -0
- package/dist/redux.js +1 -0
- package/dist/trace.d.ts +144 -0
- package/dist/trace.js +1 -0
- package/docs/README.md +12 -0
- package/docs/actions.md +68 -0
- package/docs/context.md +16 -0
- package/docs/effects.md +119 -0
- package/docs/errors.md +23 -0
- package/docs/hmr.md +74 -0
- package/docs/observable.md +22 -0
- package/docs/policies.md +53 -0
- package/docs/semantics.md +7 -0
- package/docs/stores.md +50 -0
- package/docs/tracing.md +104 -0
- package/package.json +164 -5
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { A as Keys, k as EventMap, u as Context } from "./action-BM-UdmJr.js";
|
|
2
|
+
//#region src/utils/delay.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Resolves after `ms`, or rejects early if `ctx.signal` aborts. `ms` can be a
|
|
5
|
+
* function that returns the number of ms, called once when the delay starts:
|
|
6
|
+
* handy for a random pause (jitter).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* await delay({}, 500);
|
|
11
|
+
*
|
|
12
|
+
* // 200 to 400 ms
|
|
13
|
+
* await delay({}, () => 200 + Math.random() * 200);
|
|
14
|
+
*
|
|
15
|
+
* // inside a task: stops waiting when the task is aborted
|
|
16
|
+
* bus.on("retry", async (_, ctx) => {
|
|
17
|
+
* await delay(ctx, 1000);
|
|
18
|
+
* await load();
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare const delay: (ctx: {
|
|
23
|
+
signal?: AbortSignal | undefined;
|
|
24
|
+
}, ms: number | (() => number)) => Promise<void>;
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/utils/onAbort.d.ts
|
|
27
|
+
export declare const onAbort: (ctx: {
|
|
28
|
+
signal?: AbortSignal | undefined;
|
|
29
|
+
}, fn: (reason: unknown) => void) => (() => void);
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/effects/run.d.ts
|
|
32
|
+
/** Context passed into `race` / `all` effect starters. */
|
|
33
|
+
type EffectContext = {
|
|
34
|
+
signal: AbortSignal;
|
|
35
|
+
abort: (reason?: unknown) => void;
|
|
36
|
+
};
|
|
37
|
+
type Starter<T> = (ctx: EffectContext) => T | PromiseLike<T>;
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/effects/all.d.ts
|
|
40
|
+
/**
|
|
41
|
+
* Runs all starters; waits for every result. On failure or parent abort, cancels the rest.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* bus.on("load", async (id, ctx) => {
|
|
46
|
+
* const { user, posts } = await all(ctx, {
|
|
47
|
+
* user: ({ signal }) => fetchUser(id, { signal }),
|
|
48
|
+
* posts: ({ signal }) => fetchPosts(id, { signal }),
|
|
49
|
+
* });
|
|
50
|
+
*
|
|
51
|
+
* // arrays work too: const [user, posts] = await all(ctx, [getUser, getPosts]);
|
|
52
|
+
* });
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function all<T extends Record<string, Starter<unknown>>>(ctx: {
|
|
56
|
+
signal?: AbortSignal;
|
|
57
|
+
}, effects: T): Promise<{ [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
|
|
58
|
+
export declare function all<const T extends readonly Starter<unknown>[]>(ctx: {
|
|
59
|
+
signal?: AbortSignal;
|
|
60
|
+
}, effects: T): Promise<{ -readonly [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/effects/allSettled.d.ts
|
|
63
|
+
/**
|
|
64
|
+
* Runs all starters and waits for every one to settle, whether it succeeds or
|
|
65
|
+
* fails: nothing is cancelled when a starter fails. The result has, for each
|
|
66
|
+
* starter, `{ status: "fulfilled", value }` or `{ status: "rejected", reason }`.
|
|
67
|
+
* Only an abort of `ctx` rejects it, with the abort reason.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* bus.on("load", async (id, ctx) => {
|
|
72
|
+
* const { user, posts } = await allSettled(ctx, {
|
|
73
|
+
* user: ({ signal }) => fetchUser(id, { signal }),
|
|
74
|
+
* posts: ({ signal }) => fetchPosts(id, { signal }),
|
|
75
|
+
* });
|
|
76
|
+
*
|
|
77
|
+
* if (user.status === "fulfilled") render(user.value);
|
|
78
|
+
* });
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function allSettled<T extends Record<string, Starter<unknown>>>(ctx: {
|
|
82
|
+
signal?: AbortSignal;
|
|
83
|
+
}, effects: T): Promise<{ [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
|
|
84
|
+
export declare function allSettled<const T extends readonly Starter<unknown>[]>(ctx: {
|
|
85
|
+
signal?: AbortSignal;
|
|
86
|
+
}, effects: T): Promise<{ -readonly [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/effects/cancellable.d.ts
|
|
89
|
+
type CancellableHandle<T> = {
|
|
90
|
+
resolve: (value: T | PromiseLike<T>) => void;
|
|
91
|
+
reject: (reason?: unknown) => void;
|
|
92
|
+
/** Registers a cleanup that runs if the promise is cancelled before it settles. */
|
|
93
|
+
onCancel: (cleanup: () => void) => void;
|
|
94
|
+
};
|
|
95
|
+
type CancellableExecutor<T> = (handle: CancellableHandle<T>) => void;
|
|
96
|
+
/**
|
|
97
|
+
* A promise tied to `ctx.signal`. It works like `new Promise`, but the
|
|
98
|
+
* executor gets one object, `{ resolve, reject, onCancel }`. If the signal
|
|
99
|
+
* aborts before the promise settles, the cleanups run (last registered first)
|
|
100
|
+
* and the promise rejects with the abort reason. If the signal is already
|
|
101
|
+
* aborted, the executor does not run. After the promise settles, the abort
|
|
102
|
+
* listener is removed and the cleanups are dropped.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* bus.on(
|
|
107
|
+
* "wait",
|
|
108
|
+
* latest((_, ctx) =>
|
|
109
|
+
* cancellable(ctx, ({ resolve, onCancel }) => {
|
|
110
|
+
* const timer = setTimeout(resolve, 5000);
|
|
111
|
+
* onCancel(() => clearTimeout(timer));
|
|
112
|
+
* }),
|
|
113
|
+
* ),
|
|
114
|
+
* );
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export declare const cancellable: <T = void>(ctx: {
|
|
118
|
+
signal: AbortSignal;
|
|
119
|
+
}, executor: CancellableExecutor<T>) => Promise<T>;
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/effects/poll.d.ts
|
|
122
|
+
type PollOptions = {
|
|
123
|
+
/** Also call `fn` at once, not only after the first `ms`. Default `false`. */
|
|
124
|
+
leading?: boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Start the pause after a call finishes, so calls never overlap. By default
|
|
127
|
+
* the calls come every `ms` whatever the duration of `fn`.
|
|
128
|
+
*/
|
|
129
|
+
waitForFn?: boolean;
|
|
130
|
+
};
|
|
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.
|
|
137
|
+
*
|
|
138
|
+
* `ms` can be a function of the number of the call that the pause leads to, for
|
|
139
|
+
* a growing or random pause: `(call) => Math.min(1000 * 2 ** call, 60_000)`.
|
|
140
|
+
*
|
|
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
|
+
* @example
|
|
146
|
+
* ```ts
|
|
147
|
+
* bus.on("watch", async (_, ctx) => {
|
|
148
|
+
* await poll(ctx, () => refresh(), 5000);
|
|
149
|
+
* cleanup(); // the task was aborted
|
|
150
|
+
* });
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
export declare const poll: (ctx: {
|
|
154
|
+
signal: AbortSignal;
|
|
155
|
+
}, fn: (tick: number) => unknown, ms: number | ((call: number) => number), options?: PollOptions) => Promise<void>;
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region src/effects/race.d.ts
|
|
158
|
+
type RaceStarter<T> = Starter<T>;
|
|
159
|
+
/**
|
|
160
|
+
* Runs all starters; first to settle wins, losers are cancelled via `ctx.abort`.
|
|
161
|
+
* The result holds only the winner's value, under its key or index.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```ts
|
|
165
|
+
* bus.on("load", async (id, ctx) => {
|
|
166
|
+
* const result = await race(ctx, {
|
|
167
|
+
* data: ({ signal }) => fetchData(id, { signal }),
|
|
168
|
+
* timeout: ({ signal }) => delay({ signal }, 5000),
|
|
169
|
+
* });
|
|
170
|
+
*
|
|
171
|
+
* if ("timeout" in result) throw new Error("timed out");
|
|
172
|
+
* render(result.data);
|
|
173
|
+
* });
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
export declare function race<T extends Record<string, RaceStarter<unknown>>>(ctx: {
|
|
177
|
+
signal?: AbortSignal;
|
|
178
|
+
}, effects: T): Promise<{ [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
|
|
179
|
+
export declare function race<const T extends readonly RaceStarter<unknown>[]>(ctx: {
|
|
180
|
+
signal?: AbortSignal;
|
|
181
|
+
}, effects: T): Promise<{ -readonly [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region src/effects/retry.d.ts
|
|
184
|
+
type RetryOptions = {
|
|
185
|
+
/** Total number of attempts, the first one included. Default 3. */
|
|
186
|
+
times?: number;
|
|
187
|
+
/**
|
|
188
|
+
* Pause before the next attempt, in ms: a number, or a function of the
|
|
189
|
+
* number of the attempt that just failed and its error. Default 0.
|
|
190
|
+
*/
|
|
191
|
+
delay?: number | ((attempt: number, error: unknown) => number);
|
|
192
|
+
/** Return `false` to stop retrying and throw `error`. Default: always retry. */
|
|
193
|
+
shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
194
|
+
};
|
|
195
|
+
export declare const retry: <T>(ctx: {
|
|
196
|
+
signal?: AbortSignal;
|
|
197
|
+
}, fn: (attempt: number) => T | PromiseLike<T>, options?: RetryOptions) => Promise<T>;
|
|
198
|
+
//#endregion
|
|
199
|
+
//#region src/effects/take.d.ts
|
|
200
|
+
type TakeOptions<P> = {
|
|
201
|
+
/** Wait for the first payload this accepts; others are skipped. */
|
|
202
|
+
predicate?: (payload: P) => boolean;
|
|
203
|
+
/** Stop waiting when this aborts, e.g. the signal of a `race` starter. */
|
|
204
|
+
signal?: AbortSignal | undefined;
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Waits for the next `event` on the bus and resolves with its payload. It sees
|
|
208
|
+
* only events emitted after the call, takes only one, and unsubscribes when it
|
|
209
|
+
* is done. If `ctx` (or `options.signal`) aborts first, it unsubscribes and
|
|
210
|
+
* rejects with the abort reason.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```ts
|
|
214
|
+
* bus.on("login", async (user, ctx) => {
|
|
215
|
+
* const reason = await take(ctx, "logout");
|
|
216
|
+
* cleanup(user, reason);
|
|
217
|
+
* });
|
|
218
|
+
*
|
|
219
|
+
* // wait for a matching event, but not longer than 5 s
|
|
220
|
+
* const done = await timeout(ctx, 5000, ({ signal }) =>
|
|
221
|
+
* take(ctx, "saved", { signal, predicate: (id) => id === draftId }),
|
|
222
|
+
* );
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
export declare const take: <E extends EventMap, K extends Keys<E>>(ctx: Context<E>, event: K, options?: TakeOptions<E[K]>) => Promise<E[K]>;
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region src/effects/timeout.d.ts
|
|
228
|
+
/**
|
|
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.
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```ts
|
|
235
|
+
* bus.on("load", async (id, ctx) => {
|
|
236
|
+
* const data = await timeout(ctx, 5000, ({ signal }) => fetchData(id, { signal }));
|
|
237
|
+
* });
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
export declare const timeout: <T>(ctx: {
|
|
241
|
+
signal?: AbortSignal;
|
|
242
|
+
}, ms: number, starter: Starter<T>) => Promise<T>;
|
|
243
|
+
//#endregion
|
|
244
|
+
export type { CancellableExecutor, CancellableHandle, EffectContext, PollOptions, RaceStarter, RetryOptions, TakeOptions };
|
package/dist/effects.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{n as e,t}from"./onAbort-BgMfspK4.js";import{a as n,i as r,o as i,t as a}from"./context-BXQ6Einm.js";import{n as o,r as s,t as c}from"./delay-CIHnOh_L.js";const l=(e,n)=>n?.signal?new Promise((r,i)=>{let a=t(n,i);e.then(r,i).finally(a)}):e,u=e=>Array.isArray(e)?Array(e.length):{},d=(e,o,s)=>{s?.signal?.throwIfAborted();let c=r(s??{}),u=a(c.ctx,o);return i(c.ctx,o,`start`),t(c.ctx,()=>u(`cancel`)),{tasks:Object.entries(e).map(([e,t])=>{let i=r(c.ctx),a={signal:i.ctx.signal,abort:i.ctx.abort};return[e,new Promise(e=>e(n(i.ctx,`run`,()=>t(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=(n,r)=>n.signal.aborted?Promise.reject(n.signal.reason):new Promise((i,a)=>{let o=[],s=`pending`,c=t=>{try{t()}catch(t){e(`cancel cleanup failed`,t)}},l=t(n,e=>{s=`cancelled`;for(let e of o.reverse())c(e);a(e)}),u=e=>(...t)=>{s===`pending`&&(s=`settled`,l(),o.length=0,e(...t))},d=e=>{s===`pending`?o.push(e):s===`cancelled`&&c(e)};try{r({resolve:u(i),reject:u(a),onCancel:d})}catch(e){u(a)(e)}}),g=async(e,n,o)=>{n?.signal?.throwIfAborted();let{ctx:s,unlink:c}=r(n??{}),u=a(s,e);i(s,e,`start`),t(s,()=>u(`cancel`));try{let e=await l(o(s),n);return u(`done`),e}catch(e){throw u(`error`,e),e}finally{c()}},_=async(e,t)=>{let{ctx:i,unlink:a}=r(e);try{return await n(i,`run`,t)}finally{a()}},v=(e,n,r,i={})=>{if(e.signal.aborted)return Promise.resolve();let a=i.leading??!1,c=i.waitForFn??!1;return g(`poll`,e,e=>new Promise((i,l)=>{let u=0,d=null,f=()=>{d!==null&&(o(d),d=null)},p=e=>{try{d=s(m,typeof r==`function`?r(e):r)}catch(e){f(),l(e)}},m=()=>{d=null,c||p(u+1),h()},h=()=>{let t=u++;_(e,()=>n(t)).then(()=>{c&&!e.signal.aborted&&p(u)},e=>{f(),l(e)})};t(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,n,r={})=>g(`take`,e,i=>new Promise((a,o)=>{let{predicate:s}=r,c=[],l=()=>{for(let e of c)e()},u=e=>{l(),o(e)};c.push(e.on(n,e=>{(!s||s(e))&&(l(),a(e))})),c.push(t(i,u)),c.push(t({signal:r.signal},u))})),w=(e,t,i)=>g(`timeout`,e,async e=>{let a=r(e),c=s(()=>a.ctx.abort(new DOMException(`Timed out after ${t}ms`,`TimeoutError`)),t);try{return await l(Promise.resolve(n(a.ctx,`run`,()=>i({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,t as onAbort,v as poll,y as race,S as retry,C as take,w as timeout};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=e=>typeof e==`string`?e:e.type,t=(e,t)=>typeof e==`object`?e.payload:t;export{t as n,e as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { A as Keys, C as WithPayload, D as anyEventType, E as Tracer, F as TraceEvent, I as TraceLogger, L as UnsubscribeCallback, M as Phase, N as Task, O as EffectName, P as Trace, S as WithContext, T as createBus, _ as EventPayload, a as AnyEvent, b as Off, c as Command, d as ContextOf, f as ContextOption, g as EventLike, h as EventKey, i as Action, j as PayloadOf, k as EventMap, l as Commands, m as Emittable, o as Bus, p as Emit, s as BusOptions, u as Context, v as Handler, w as WithType, x as On, y as HandlerRef } from "./action-BM-UdmJr.js";
|
|
2
|
+
import "./index-CaxrMD1A.js";
|
|
3
|
+
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 };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{n as anyEventType,t as createBus}from"./bus-RUiw8f9p.js";
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
//#region src/core/types.d.ts
|
|
2
|
+
type PayloadOf<P> = undefined extends P ? [payload?: P] : [payload: P];
|
|
3
|
+
type EventMap = Record<string, unknown>;
|
|
4
|
+
type Keys<E> = keyof E & string;
|
|
5
|
+
type Task<P, E extends EventMap, R = unknown> = (payload: P, ctx: Context<E>) => R;
|
|
6
|
+
type EffectName = "every" | "latest" | "leading" | "debounce" | "throttle" | "run" | "all" | "race" | "retry" | "timeout" | "poll" | "take" | "queue" | "allSettled" | "command";
|
|
7
|
+
type Phase = "start" | "done" | "error" | "cancel" | "drop";
|
|
8
|
+
type TraceEvent<E = Record<string, unknown>> = { [K in Keys<E>]: {
|
|
9
|
+
id: number;
|
|
10
|
+
parent?: number;
|
|
11
|
+
type: K;
|
|
12
|
+
effect: EffectName;
|
|
13
|
+
phase: Phase;
|
|
14
|
+
payload: E[K];
|
|
15
|
+
error?: unknown;
|
|
16
|
+
}; }[Keys<E>];
|
|
17
|
+
/**
|
|
18
|
+
* The contract of every trace logger: receives each task phase of a bus.
|
|
19
|
+
* Adapters in `nanoeffects/trace` are factories that return one; pass it
|
|
20
|
+
* through `createTracer` to `createBus({ trace })`.
|
|
21
|
+
*/
|
|
22
|
+
type TraceLogger<E = Record<string, unknown>> = (event: TraceEvent<E>) => void;
|
|
23
|
+
/** One logger or several; a throwing logger never breaks the task or the others. */
|
|
24
|
+
type Trace<E = Record<string, unknown>> = TraceLogger<E> | readonly TraceLogger<E>[];
|
|
25
|
+
type UnsubscribeCallback = () => void;
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/core/store.d.ts
|
|
28
|
+
declare const anyEventType = "*";
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/core/tracer.d.ts
|
|
31
|
+
/**
|
|
32
|
+
* What the bus needs from tracing. The implementation, `createTracer` in
|
|
33
|
+
* `nanoeffects/trace`, holds all the machinery (ids, events, loggers), so a
|
|
34
|
+
* bus without `trace` carries none of it. `Ref` is an opaque handle that
|
|
35
|
+
* `task` makes for each task.
|
|
36
|
+
*/
|
|
37
|
+
interface Tracer<Ref extends object = object> {
|
|
38
|
+
/** Registers a task and returns its handle; `parent` is the handle of its parent task. */
|
|
39
|
+
task(event: {
|
|
40
|
+
type: string;
|
|
41
|
+
payload?: unknown;
|
|
42
|
+
}, parent?: Ref): Ref;
|
|
43
|
+
/** Reports a phase of a task. */
|
|
44
|
+
report(ref: Ref, effect: EffectName, phase: Phase, error?: unknown): void;
|
|
45
|
+
/** Returns `settle(phase, error?)` that reports only the first phase it gets. */
|
|
46
|
+
settler(ref: Ref, effect: EffectName): (phase: Phase, error?: unknown) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Runs `fn`, reporting `start` and then `done` / `error` of its (sync or
|
|
49
|
+
* async) result, or `cancel` if `signal` aborts first.
|
|
50
|
+
*/
|
|
51
|
+
run<R>(ref: Ref, effect: EffectName, signal: AbortSignal, fn: () => R): R;
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/core/bus.d.ts
|
|
55
|
+
/** Subscribes to every event; handlers receive the payload of any of them. */
|
|
56
|
+
type AnyEvent = typeof anyEventType;
|
|
57
|
+
type EventKey<E extends EventMap> = Keys<E>;
|
|
58
|
+
type EventPayload<E extends EventMap> = E[EventKey<E>];
|
|
59
|
+
type WithType<K extends string> = {
|
|
60
|
+
type: K;
|
|
61
|
+
};
|
|
62
|
+
type HandlerRef<E extends EventMap, Response = unknown> = (action: Action<E>) => Response;
|
|
63
|
+
type Handler<E extends EventMap, Response = unknown, K extends EventKey<E> = EventKey<E>> = { [P in K]: {
|
|
64
|
+
type: P;
|
|
65
|
+
(payload: E[P]): Response;
|
|
66
|
+
}; }[K];
|
|
67
|
+
type Action<E extends EventMap> = {
|
|
68
|
+
type: EventKey<E>;
|
|
69
|
+
payload: EventPayload<E>;
|
|
70
|
+
};
|
|
71
|
+
type WithPayload<E extends EventMap, K extends EventKey<E>> = undefined extends E[K] ? {
|
|
72
|
+
type: K;
|
|
73
|
+
payload?: E[K];
|
|
74
|
+
} : {
|
|
75
|
+
type: K;
|
|
76
|
+
payload: E[K];
|
|
77
|
+
};
|
|
78
|
+
type EventLike<E extends EventMap> = { [K in EventKey<E>]: WithPayload<E, K>; }[Keys<E>];
|
|
79
|
+
/**
|
|
80
|
+
* A typed request for a result. `bus.emit(command, payload)` runs the handler
|
|
81
|
+
* of the command as a task and returns what it returns. Made by
|
|
82
|
+
* `createCommand` in `nanoeffects/actions`.
|
|
83
|
+
*/
|
|
84
|
+
type Command<P = never, R = unknown> = {
|
|
85
|
+
(payload: P, ctx?: never): R;
|
|
86
|
+
readonly type: string;
|
|
87
|
+
/** Wraps the handler that really runs, e.g. to route its outcome. */
|
|
88
|
+
readonly wrap?: (run: (payload: never, ctx: never) => unknown) => (payload: never, ctx: never) => unknown;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* What the bus needs to run commands. The implementation, `createCommands` in
|
|
92
|
+
* `nanoeffects/actions`, holds the handlers bound to commands and runs the
|
|
93
|
+
* right one, so a bus without `commands` carries none of that.
|
|
94
|
+
*/
|
|
95
|
+
interface Commands {
|
|
96
|
+
run(command: Command, payload: unknown, ctx: unknown): unknown;
|
|
97
|
+
}
|
|
98
|
+
/** Anything `emit` takes as its first argument: an event type, an action or a command. */
|
|
99
|
+
type Emittable<E extends EventMap> = EventKey<E> | Command | EventLike<E>;
|
|
100
|
+
type Emit<E extends EventMap> = {
|
|
101
|
+
<K extends EventKey<E>>(event: K, ...payload: PayloadOf<E[K]>): void;
|
|
102
|
+
<K extends EventKey<E>, R>(event: Handler<E, R, K>, ...payload: PayloadOf<E[K]>): R;
|
|
103
|
+
<P, R>(command: Command<P, R>, ...payload: PayloadOf<P>): R;
|
|
104
|
+
(event: EventLike<E>): void;
|
|
105
|
+
};
|
|
106
|
+
type On<E extends EventMap> = {
|
|
107
|
+
(event: AnyEvent, fn: Task<EventPayload<E>, E>): UnsubscribeCallback;
|
|
108
|
+
<K extends EventKey<E>>(event: K, fn: Task<E[K], E>): UnsubscribeCallback;
|
|
109
|
+
/** An action creator without a payload type: the payload comes from the event map. */
|
|
110
|
+
<K extends EventKey<E>>(creator: FreeActionCreator<K>, fn: Task<E[K], E>): UnsubscribeCallback;
|
|
111
|
+
/** An action creator: the payload comes from it, so an event map is not needed. */
|
|
112
|
+
<P>(creator: ActionCreator<P>, fn: Task<P, E>): UnsubscribeCallback;
|
|
113
|
+
/** A command: the payload comes from it too. */
|
|
114
|
+
<P>(command: Command<P, unknown>, fn: Task<P, E>): UnsubscribeCallback;
|
|
115
|
+
<K extends EventKey<E>, Event extends {
|
|
116
|
+
type: K;
|
|
117
|
+
}>(event: Event & {
|
|
118
|
+
type: K;
|
|
119
|
+
}, fn: Task<E[K], E>): UnsubscribeCallback;
|
|
120
|
+
};
|
|
121
|
+
type Off<E extends EventMap> = {
|
|
122
|
+
(event: AnyEvent): void;
|
|
123
|
+
(event: AnyEvent, fn: Task<EventPayload<E>, E>): void;
|
|
124
|
+
<K extends EventKey<E>>(event: K): void;
|
|
125
|
+
<K extends EventKey<E>>(event: K, fn: Task<E[K], E>): void;
|
|
126
|
+
<K extends EventKey<E>, Event extends {
|
|
127
|
+
type: K;
|
|
128
|
+
}>(event: Event & {
|
|
129
|
+
type: K;
|
|
130
|
+
}, fn: Task<E[K], E>): void;
|
|
131
|
+
<P>(command: Command<P, unknown>, fn?: Task<P, E>): void;
|
|
132
|
+
};
|
|
133
|
+
declare const contextKey: unique symbol;
|
|
134
|
+
/**
|
|
135
|
+
* Your own fields of every task context, in the event map: add it to the
|
|
136
|
+
* events with `&` and give the values in `createBus({ context })`. The fields
|
|
137
|
+
* are in the context of every task of that bus, and the compiler checks them.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```ts
|
|
141
|
+
* type Events = { save: Draft } & WithContext<{ store: Store }>;
|
|
142
|
+
*
|
|
143
|
+
* const bus = createBus<Events>({ context: { store } });
|
|
144
|
+
* bus.on("save", (draft, { store }) => store.put(draft));
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
type WithContext<Fields extends object> = {
|
|
148
|
+
readonly [contextKey]?: Fields;
|
|
149
|
+
};
|
|
150
|
+
/** The fields that the event map `E` adds to the context of a task. */
|
|
151
|
+
type ContextOf<E extends EventMap> = typeof contextKey extends keyof E ? NonNullable<E[typeof contextKey]> : unknown;
|
|
152
|
+
/** Fields for the context of every task, or a function that makes them for one task. */
|
|
153
|
+
type ContextOption<E extends EventMap> = ContextOf<E> | ((event: {
|
|
154
|
+
type: string;
|
|
155
|
+
payload?: unknown;
|
|
156
|
+
}) => ContextOf<E>);
|
|
157
|
+
/** Task context inside an effect. */
|
|
158
|
+
type Context<E extends EventMap> = EventLike<E> & ContextOf<E> & {
|
|
159
|
+
signal: AbortSignal;
|
|
160
|
+
/** Abort this task (and attached children). */
|
|
161
|
+
abort: (reason?: unknown) => void;
|
|
162
|
+
/** Emit as this task; no-op after the task is aborted. */
|
|
163
|
+
emit: Emit<E>;
|
|
164
|
+
/**
|
|
165
|
+
* Subscribe as this task. The subscription is removed when the task aborts;
|
|
166
|
+
* after that this is a no-op that returns a no-op.
|
|
167
|
+
*/
|
|
168
|
+
on: On<E>;
|
|
169
|
+
/** Same as the bus `off`. */
|
|
170
|
+
off: Off<E>;
|
|
171
|
+
/** Child task: aborted with the parent; errors propagate to the parent. */
|
|
172
|
+
run<P, R>(fn: Task<P, E, R>, ...args: PayloadOf<P>): Promise<Awaited<R>>;
|
|
173
|
+
};
|
|
174
|
+
interface Bus<E extends EventMap> {
|
|
175
|
+
on: On<E>;
|
|
176
|
+
off: Off<E>;
|
|
177
|
+
emit: Emit<E>;
|
|
178
|
+
/** `BusOptions.context`, `.trace` and `.commands`, for what makes tasks of its own, like `registerModules`. */
|
|
179
|
+
readonly context?: ContextOption<E> | undefined;
|
|
180
|
+
/** What runs the commands; `registerModules` sets it when the bus was made without. */
|
|
181
|
+
commands?: Commands | undefined;
|
|
182
|
+
readonly trace?: Tracer | undefined;
|
|
183
|
+
}
|
|
184
|
+
type BusOptions<E extends EventMap> = {
|
|
185
|
+
/**
|
|
186
|
+
* Runs commands: make it with `createCommands` from `nanoeffects/actions`.
|
|
187
|
+
* Without it an event that is a function runs as it is, with the payload and
|
|
188
|
+
* the task context.
|
|
189
|
+
*/
|
|
190
|
+
commands?: Commands;
|
|
191
|
+
/** Tracing of every task phase; make it with `createTracer` from `nanoeffects/trace`. */
|
|
192
|
+
trace?: never;
|
|
193
|
+
/**
|
|
194
|
+
* Receives errors of handlers: thrown ones and rejections of async ones.
|
|
195
|
+
* Without it handlers run like in a plain loop: the first error stops the
|
|
196
|
+
* rest and is thrown to the caller of `emit`, and rejections of async
|
|
197
|
+
* handlers are left unhandled. With it every handler runs and errors go
|
|
198
|
+
* here; an `onError` that throws is logged to `console.error`.
|
|
199
|
+
*/
|
|
200
|
+
onError?: (error: unknown, event: Action<E>) => void;
|
|
201
|
+
/**
|
|
202
|
+
* Called when an event is emitted and nobody listens to it: the usual sign
|
|
203
|
+
* of a typo in an event name. Not called for commands, which always have a
|
|
204
|
+
* handler to run. Typically a warning in development only.
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```ts
|
|
208
|
+
* createBus<Events>({
|
|
209
|
+
* onUnhandled: import.meta.env.DEV ? ({ type }) => console.warn(`Nobody listens to "${type}"`) : void 0,
|
|
210
|
+
* });
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
onUnhandled?: (event: Action<E>) => void;
|
|
214
|
+
/**
|
|
215
|
+
* Fields added to the context of every task of this bus, its child tasks
|
|
216
|
+
* and the tasks of effects included: a store, a logger, a client. Their
|
|
217
|
+
* types come from `WithContext` in the event map. The fields of the library win over
|
|
218
|
+
* these when the names are the same. A function gets `{ type, payload }` of
|
|
219
|
+
* the task and returns its fields, for fields that depend on the task.
|
|
220
|
+
*/
|
|
221
|
+
context?: ContextOption<E>;
|
|
222
|
+
};
|
|
223
|
+
declare const createBus: <Events extends EventMap>(options?: BusOptions<Events>) => Bus<Events>;
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region src/actions/action.d.ts
|
|
226
|
+
type Absent<P> = [P] extends [void] ? undefined : P;
|
|
227
|
+
type Made<P, T extends string> = WithPayload<{ [K in T]: Absent<P>; }, T>;
|
|
228
|
+
declare const free: unique symbol;
|
|
229
|
+
type Free = typeof free;
|
|
230
|
+
/**
|
|
231
|
+
* A creator whose payload type was not given: each call has the type of what
|
|
232
|
+
* it is given, and the bus checks it against its event map, so
|
|
233
|
+
* `createAction("user/loaded")` needs no generics and keeps its literal type.
|
|
234
|
+
*/
|
|
235
|
+
interface FreeActionCreator<T extends string> {
|
|
236
|
+
(): {
|
|
237
|
+
type: T;
|
|
238
|
+
payload: undefined;
|
|
239
|
+
};
|
|
240
|
+
<P>(payload: P): {
|
|
241
|
+
type: T;
|
|
242
|
+
payload: P;
|
|
243
|
+
};
|
|
244
|
+
readonly type: T;
|
|
245
|
+
/** Tells whether an action is one of this creator. */
|
|
246
|
+
match(action: {
|
|
247
|
+
type: string;
|
|
248
|
+
payload?: unknown;
|
|
249
|
+
}): action is {
|
|
250
|
+
type: T;
|
|
251
|
+
payload: unknown;
|
|
252
|
+
};
|
|
253
|
+
toString(): T;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Makes actions of one type. It also works as the event of that type:
|
|
257
|
+
* `bus.on(creator, fn)` and `bus.emit(creator(payload))`.
|
|
258
|
+
*/
|
|
259
|
+
interface ActionCreator<P = void, T extends string = string, A extends unknown[] = PayloadOf<P>> {
|
|
260
|
+
(...args: A): Made<P, T>;
|
|
261
|
+
readonly type: T;
|
|
262
|
+
/** Tells whether an action is one of this creator, and narrows its type. */
|
|
263
|
+
match(action: {
|
|
264
|
+
type: string;
|
|
265
|
+
payload?: unknown;
|
|
266
|
+
}): action is Made<P, T>;
|
|
267
|
+
toString(): T;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Creates an action creator, as in Redux Toolkit.
|
|
271
|
+
*
|
|
272
|
+
* - `createAction("user/loaded")` needs no generics: the payload type comes
|
|
273
|
+
* from the event map of the bus that the action goes to, and the literal
|
|
274
|
+
* type of the action is kept.
|
|
275
|
+
* - `createAction<User>("user/loaded")` gives the payload type, for a bus
|
|
276
|
+
* without an event map or to check the calls of the creator. The type of
|
|
277
|
+
* the action is then `string`; to keep the literal, give it as the second
|
|
278
|
+
* generic: `createAction<User, "user/loaded">("user/loaded")`.
|
|
279
|
+
* - With `prepare` the arguments of the creator can differ from the payload,
|
|
280
|
+
* and both types are inferred.
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* ```ts
|
|
284
|
+
* const added = createAction<number>("counter/added");
|
|
285
|
+
* added(2); // { type: "counter/added", payload: 2 }
|
|
286
|
+
* added.type; // "counter/added"
|
|
287
|
+
* added.match(action); // true for actions of this type
|
|
288
|
+
*
|
|
289
|
+
* const renamed = createAction("user/renamed", (id: string, name: string) => ({
|
|
290
|
+
* payload: { id, name },
|
|
291
|
+
* }));
|
|
292
|
+
*
|
|
293
|
+
* bus.emit(added(2));
|
|
294
|
+
* bus.on(added, (n) => console.log(n));
|
|
295
|
+
* ```
|
|
296
|
+
*/
|
|
297
|
+
declare function createAction<P = Free, T extends string = string>(type: T): [P] extends [Free] ? FreeActionCreator<T> : ActionCreator<P, T>;
|
|
298
|
+
declare function createAction<A extends unknown[], P, T extends string = string>(type: T, prepare: (...args: A) => {
|
|
299
|
+
payload: P;
|
|
300
|
+
}): ActionCreator<P, T, A>;
|
|
301
|
+
//#endregion
|
|
302
|
+
export { Keys as A, WithPayload as C, anyEventType as D, Tracer as E, TraceEvent as F, TraceLogger as I, UnsubscribeCallback as L, Phase as M, Task as N, EffectName as O, Trace as P, WithContext as S, createBus as T, EventPayload as _, AnyEvent as a, Off as b, Command as c, ContextOf as d, ContextOption as f, EventLike as g, EventKey as h, Action as i, PayloadOf as j, EventMap as k, Commands as l, Emittable as m, FreeActionCreator as n, Bus as o, Emit as p, createAction as r, BusOptions as s, ActionCreator as t, Context as u, Handler as v, WithType as w, On as x, HandlerRef as y };
|