nanoeffects 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +96 -2
- package/dist/action-rqZEnRIp.d.ts +303 -0
- package/dist/actions.d.ts +58 -0
- package/dist/actions.js +1 -0
- package/dist/bus-CRnlwmQ4.js +1 -0
- package/dist/context-DlA80GOA.js +1 -0
- package/dist/delay-CIHnOh_L.js +1 -0
- package/dist/dev.d.ts +46 -0
- package/dist/dev.js +1 -0
- package/dist/effects.d.ts +284 -0
- package/dist/effects.js +1 -0
- package/dist/event-0RIBGYqS.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/module-BSCdR9D1.d.ts +167 -0
- package/dist/notrace/action-rqZEnRIp.d.ts +303 -0
- package/dist/notrace/actions.d.ts +222 -0
- package/dist/notrace/actions.js +1 -0
- package/dist/notrace/bus-Dx0l9X6h.js +1 -0
- package/dist/notrace/context-yAWj0BFZ.js +1 -0
- package/dist/notrace/delay-Cgvn16oY.js +1 -0
- package/dist/notrace/effects.d.ts +284 -0
- package/dist/notrace/effects.js +1 -0
- package/dist/notrace/event-0RIBGYqS.js +1 -0
- package/dist/notrace/index.d.ts +2 -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/production/dev.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 +162 -0
- package/dist/trace.js +1 -0
- package/docs/README.md +15 -0
- package/docs/actions.md +78 -0
- package/docs/context.md +15 -0
- package/docs/effects.md +137 -0
- package/docs/errors.md +23 -0
- package/docs/guide.md +229 -0
- package/docs/hmr.md +36 -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 +117 -0
- package/package.json +184 -5
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { A as Keys, N as Task, k as EventMap, u as Context } from "./action-rqZEnRIp.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 a function every `ms`, like patronum `interval`: the first call comes
|
|
133
|
+
* after `ms` (with `leading` also at once), and the next ticks keep a steady
|
|
134
|
+
* rate whatever the duration of the function, so a slow one may overlap with
|
|
135
|
+
* the next call (`waitForFn` starts each pause when the call ends instead). An
|
|
136
|
+
* error stops the polling and rejects; an abort of the caller ends it without
|
|
137
|
+
* an error and clears the timer. Two forms:
|
|
138
|
+
*
|
|
139
|
+
* - `poll(ms, task, options)` wraps a Task `(payload, ctx) => result` and
|
|
140
|
+
* returns a Task. Each call gets a context of its own, a child of the
|
|
141
|
+
* caller's: `bus.on("watch", poll(10_000, refresh))`.
|
|
142
|
+
* - `poll(ctx, fn, ms, options)` is for one call inside a body; `fn` gets the
|
|
143
|
+
* number of the call, starting at 0.
|
|
144
|
+
*
|
|
145
|
+
* `ms` can be a function of the number of the call that the pause leads to, for
|
|
146
|
+
* a growing or random pause: `(call) => Math.min(1000 * 2 ** call, 60_000)`.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```ts
|
|
150
|
+
* bus.on("watch", poll(10_000, (_, { signal }) => refresh({ signal })));
|
|
151
|
+
*
|
|
152
|
+
* bus.on("watch", async (_, ctx) => {
|
|
153
|
+
* await poll(ctx, () => refresh(), 10_000);
|
|
154
|
+
* });
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
type PollMs = number | ((call: number) => number);
|
|
158
|
+
export declare function poll<P, E extends EventMap, R>(ms: PollMs, task: Task<P, E, R>, options?: PollOptions): Task<P, E, Promise<void>>;
|
|
159
|
+
export declare function poll(ctx: {
|
|
160
|
+
signal: AbortSignal;
|
|
161
|
+
}, fn: (tick: number) => unknown, ms: PollMs, options?: PollOptions): Promise<void>;
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/effects/race.d.ts
|
|
164
|
+
type RaceStarter<T> = Starter<T>;
|
|
165
|
+
/**
|
|
166
|
+
* Runs all starters; first to settle wins, losers are cancelled via `ctx.abort`.
|
|
167
|
+
* The result holds only the winner's value, under its key or index.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```ts
|
|
171
|
+
* bus.on("load", async (id, ctx) => {
|
|
172
|
+
* const result = await race(ctx, {
|
|
173
|
+
* data: ({ signal }) => fetchData(id, { signal }),
|
|
174
|
+
* timeout: ({ signal }) => delay({ signal }, 5000),
|
|
175
|
+
* });
|
|
176
|
+
*
|
|
177
|
+
* if ("timeout" in result) throw new Error("timed out");
|
|
178
|
+
* render(result.data);
|
|
179
|
+
* });
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
export declare function race<T extends Record<string, RaceStarter<unknown>>>(ctx: {
|
|
183
|
+
signal?: AbortSignal;
|
|
184
|
+
}, effects: T): Promise<{ [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
|
|
185
|
+
export declare function race<const T extends readonly RaceStarter<unknown>[]>(ctx: {
|
|
186
|
+
signal?: AbortSignal;
|
|
187
|
+
}, effects: T): Promise<{ -readonly [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/effects/retry.d.ts
|
|
190
|
+
type RetryOptions = {
|
|
191
|
+
/** Total number of attempts, the first one included. Default 3. */
|
|
192
|
+
times?: number;
|
|
193
|
+
/**
|
|
194
|
+
* Pause before the next attempt, in ms: a number, or a function of the
|
|
195
|
+
* number of the attempt that just failed and its error. Default 0.
|
|
196
|
+
*/
|
|
197
|
+
delay?: number | ((attempt: number, error: unknown) => number);
|
|
198
|
+
/** Return `false` to stop retrying and throw `error`. Default: always retry. */
|
|
199
|
+
shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* Calls a function until it succeeds or `times` attempts are used, then throws
|
|
203
|
+
* the last error. If the caller aborts, retrying stops and the result rejects
|
|
204
|
+
* with the abort reason. Two forms:
|
|
205
|
+
*
|
|
206
|
+
* - `retry(task, options)` wraps a Task `(payload, ctx) => result` and returns
|
|
207
|
+
* a Task. Each attempt gets a context of its own, a child of the caller's, so
|
|
208
|
+
* it can go straight into `on` and into `ctx.run`:
|
|
209
|
+
* `bus.on("load", latest(retry(load, { times: 4 })))`. The number of the
|
|
210
|
+
* attempt is not passed; use the other form when it is needed.
|
|
211
|
+
* - `retry(ctx, fn, options)` is for one call inside a body. `fn` gets the
|
|
212
|
+
* number of the attempt, starting at 1.
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* ```ts
|
|
216
|
+
* bus.on("load", latest(retry(loadUser, { times: 4, delay: (attempt) => 2 ** attempt * 100 })));
|
|
217
|
+
*
|
|
218
|
+
* bus.on("load", async (id, ctx) => {
|
|
219
|
+
* const user = await retry(ctx, () => fetchUser(id, { signal: ctx.signal }), { times: 4 });
|
|
220
|
+
* });
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
type RetryFn<T> = (attempt: number) => T | PromiseLike<T>;
|
|
224
|
+
export declare function retry<P, E extends EventMap, R>(task: Task<P, E, R>, options?: RetryOptions): Task<P, E, Promise<Awaited<R>>>;
|
|
225
|
+
export declare function retry<T>(ctx: {
|
|
226
|
+
signal?: AbortSignal;
|
|
227
|
+
}, fn: RetryFn<T>, options?: RetryOptions): Promise<T>;
|
|
228
|
+
//#endregion
|
|
229
|
+
//#region src/effects/take.d.ts
|
|
230
|
+
type TakeOptions<P> = {
|
|
231
|
+
/** Wait for the first payload this accepts; others are skipped. */
|
|
232
|
+
predicate?: (payload: P) => boolean;
|
|
233
|
+
/** Stop waiting when this aborts, e.g. the signal of a `race` starter. */
|
|
234
|
+
signal?: AbortSignal | undefined;
|
|
235
|
+
};
|
|
236
|
+
/**
|
|
237
|
+
* Waits for the next `event` on the bus and resolves with its payload. It sees
|
|
238
|
+
* only events emitted after the call, takes only one, and unsubscribes when it
|
|
239
|
+
* is done. If `ctx` (or `options.signal`) aborts first, it unsubscribes and
|
|
240
|
+
* rejects with the abort reason.
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* ```ts
|
|
244
|
+
* bus.on("login", async (user, ctx) => {
|
|
245
|
+
* const reason = await take(ctx, "logout");
|
|
246
|
+
* cleanup(user, reason);
|
|
247
|
+
* });
|
|
248
|
+
*
|
|
249
|
+
* // wait for a matching event, but not longer than 5 s
|
|
250
|
+
* const done = await timeout(ctx, 5000, ({ signal }) =>
|
|
251
|
+
* take(ctx, "saved", { signal, predicate: (id) => id === draftId }),
|
|
252
|
+
* );
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
255
|
+
export declare const take: <E extends EventMap, K extends Keys<E>>(ctx: Context<E>, event: K, options?: TakeOptions<E[K]>) => Promise<E[K]>;
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region src/effects/timeout.d.ts
|
|
258
|
+
/**
|
|
259
|
+
* Runs a function; if it takes longer than `ms`, aborts its signal and rejects
|
|
260
|
+
* with a `TimeoutError` (a `DOMException`, like `AbortSignal.timeout`), even if
|
|
261
|
+
* it ignores the signal. If the caller aborts, rejects with its reason. Two
|
|
262
|
+
* forms:
|
|
263
|
+
*
|
|
264
|
+
* - `timeout(ms, task)` wraps a Task `(payload, ctx) => result` and returns a
|
|
265
|
+
* Task. The Task gets a context of its own whose `signal` the time limit
|
|
266
|
+
* aborts: `bus.on("load", latest(timeout(5000, load)))`.
|
|
267
|
+
* - `timeout(ctx, ms, starter)` is for one call inside a body; `starter` gets
|
|
268
|
+
* `{ signal, abort }`.
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* ```ts
|
|
272
|
+
* bus.on("load", timeout(5000, (id, { signal }) => fetchData(id, { signal })));
|
|
273
|
+
*
|
|
274
|
+
* bus.on("load", async (id, ctx) => {
|
|
275
|
+
* const data = await timeout(ctx, 5000, ({ signal }) => fetchData(id, { signal }));
|
|
276
|
+
* });
|
|
277
|
+
* ```
|
|
278
|
+
*/
|
|
279
|
+
export declare function timeout<P, E extends EventMap, R>(ms: number, task: Task<P, E, R>): Task<P, E, Promise<Awaited<R>>>;
|
|
280
|
+
export declare function timeout<T>(ctx: {
|
|
281
|
+
signal?: AbortSignal;
|
|
282
|
+
}, ms: number, starter: Starter<T>): Promise<T>;
|
|
283
|
+
//#endregion
|
|
284
|
+
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-DlA80GOA.js";import{n as o,r as s,t as c}from"./delay-CIHnOh_L.js";const l=(e,n)=>n?.signal?new Promise((r,i)=>{let a=t(n,i);e.then(r,i).finally(a)}):e,u=async(e,n,o,s)=>{n?.signal?.throwIfAborted();let{ctx:c,unlink:u}=r(n??{}),d=a(c,e);i(c,e,`start`),t(c,()=>d(`cancel`));try{let e=await l(o(c),n);return d(`done`),s?.(!1),e}catch(e){throw d(`error`,e),s?.(!0),e}finally{u()}},d=async(e,t)=>{let{ctx:i,unlink:a}=r(e);try{return await n(i,`run`,()=>t(i))}finally{a()}},f=e=>e,p=(e,t)=>n=>e(t,f(n)),m=e=>Array.isArray(e)?Array(e.length):{},h=(e,t)=>{let i=Object.entries(t).map(([t,i])=>{let a=r(e),o={signal:a.ctx.signal,abort:a.ctx.abort},s=()=>i(o);return{key:t,task:new Promise(e=>e(n(a.ctx,`run`,s))).finally(a.unlink),cancel:a.ctx.abort}});return{tasks:i.map(({key:e,task:t})=>[e,t]),cancelAll:()=>{for(let e of i)e.cancel()}}},g=(e,t,n,r,i)=>{let a=()=>{};return u(e,t,async e=>{let t=h(e,n);return a=t.cancelAll,r(t.tasks)},e=>{(e||i===`end`)&&a()})};function _(e,t){return g(`all`,e,t,async e=>{let n=await Promise.all(e.map(([,e])=>e)),r=m(t);for(let[t,[i]]of e.entries())r[i]=n[t];return r},`failure`)}const v=e=>e.then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}));function y(e,t){return g(`allSettled`,e,t,async e=>{let n=await Promise.all(e.map(([,e])=>v(e))),r=m(t);for(let[t,[i]]of e.entries())r[i]=n[t];return r},`failure`)}const b=(n,r)=>n.signal.aborted?Promise.reject(n.signal.reason):new Promise((i,a)=>{let o=[],s=`pending`,c=t=>{try{t()}catch(t){e(`cancel cleanup failed`,t)}},l=t(n,e=>{s=`cancelled`;for(let e of o.reverse())c(e);a(e)}),u=e=>(...t)=>{s===`pending`&&(s=`settled`,l(),o.length=0,e(...t))},d=e=>{s===`pending`?o.push(e):s===`cancelled`&&c(e)};try{r({resolve:u(i),reject:u(a),onCancel:d})}catch(e){u(a)(e)}}),x=(e,n,r,i)=>{if(e.signal.aborted)return Promise.resolve();let a=i.leading??!1,c=i.waitForFn??!1;return u(`poll`,e,e=>new Promise((i,l)=>{let u=0,f=null,p=()=>{f!==null&&(o(f),f=null)},m=e=>{try{f=s(h,typeof r==`function`?r(e):r)}catch(e){p(),l(e)}},h=()=>{f=null,c||m(u+1),g()},g=()=>{let t=u++;d(e,e=>n(e,t)).then(()=>{c&&!e.signal.aborted&&m(u)},e=>{p(),l(e)})};t(e,p),a?(g(),c||m(u)):m(0)})).catch(t=>{if(!e.signal.aborted)throw t})};function S(e,t,n,r={}){if(typeof e!=`object`){let r=t,i=n??{};return(t,n)=>x(n,p(r,t),e,i)}let i=t;return x(e,(e,t)=>i(t),n,r)}function C(e,t){let n=m(t);return Object.keys(t).length===0?Promise.resolve(n):g(`race`,e,t,async e=>{let[t,r]=await Promise.race(e.map(([e,t])=>t.then(t=>[e,t])));return n[t]=r,n},`end`)}const w=(e,t,n)=>typeof e==`function`?e(t,n):e,T=e=>({times:e.times??3,delay:e.delay??0,shouldRetry:e.shouldRetry??(()=>!0)}),E=(e,t,n)=>u(`retry`,e,async e=>{let{times:r,delay:i,shouldRetry:a}=T(n);for(let n=1;;n++)try{return await d(e,e=>t(e,n))}catch(t){if(e.signal.throwIfAborted(),n>=r||!a(t,n))throw t;let o=w(i,n,t);o>0&&await c(e,o)}});function D(e,t,n={}){if(typeof e==`function`){let n=e;return(e,r)=>E(r,p(n,e),t??{})}let r=t;return E(e,(e,t)=>r(t),n)}const O=(e,n,r={})=>u(`take`,e,i=>new Promise((a,o)=>{let{predicate:s}=r,c=[],l=()=>{for(let e of c)e()},u=e=>{l(),o(e)};c.push(e.on(n,e=>{(!s||s(e))&&(l(),a(e))})),c.push(t(i,u)),c.push(t({signal:r.signal},u))})),k=(e,t,i)=>u(`timeout`,e,async e=>{let a=r(e),c=s(()=>a.ctx.abort(new DOMException(`Timed out after ${t}ms`,`TimeoutError`)),t);try{return await l(Promise.resolve(n(a.ctx,`run`,()=>i(a.ctx))),a.ctx)}finally{o(c),a.unlink()}});function A(e,t,n){if(typeof e==`number`){let n=t;return(t,r)=>k(r,e,p(n,t))}let r=t,i=n;return k(e,r,e=>i({signal:e.signal,abort:e.abort}))}export{_ as all,y as allSettled,b as cancellable,c as delay,t as onAbort,S as poll,C as race,D as retry,O as take,A as timeout};
|
|
@@ -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};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { A as Keys, C as WithPayload, D as anyEventType, E as Tracer, F as TraceEvent, I as TraceLogger, L as UnsubscribeCallback, M as Phase, N as Task, O as EffectName, P as Trace, S as WithContext, T as createBus, _ as EventPayload, a as AnyEvent, b as Off, c as Command, d as ContextOf, f as ContextOption, g as EventLike, h as EventKey, i as Action, j as PayloadOf, k as EventMap, l as Commands, m as Emittable, o as Bus, p as Emit, s as BusOptions, u as Context, v as Handler, w as WithType, x as On, y as HandlerRef } from "./action-rqZEnRIp.js";
|
|
2
|
+
export { type Action, type AnyEvent, type Bus, type BusOptions, type Command, type Commands, type Context, type ContextOf, type ContextOption, type EffectName, type Emit, type Emittable, type EventKey, type EventLike, type EventMap, type EventPayload, type Handler, type HandlerRef, type Keys, type Off, type On, type PayloadOf, type Phase, type Task, type Trace, type TraceEvent, type TraceLogger, type Tracer, type UnsubscribeCallback, type WithContext, type WithPayload, type WithType, anyEventType, createBus };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{n as anyEventType,t as createBus}from"./bus-CRnlwmQ4.js";
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { L as UnsubscribeCallback, N as Task, c as Command, j as PayloadOf, k as EventMap, o as Bus, u as Context } from "./action-rqZEnRIp.js";
|
|
2
|
+
//#region src/actions/command.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Creates a command: a typed request for a result. `bus.emit(command, payload)`
|
|
5
|
+
* runs its handler as a task, with a context, and returns what the handler
|
|
6
|
+
* returns. Listeners of the command's type are told first, as for any event.
|
|
7
|
+
*
|
|
8
|
+
* The handler is optional. A command without one is only a contract, and the
|
|
9
|
+
* bus gets its implementation from the `commands` registry (see
|
|
10
|
+
* `createCommands` and `bindHandler`). A handler bound there wins over the one
|
|
11
|
+
* the command was created with, so a test can give a bus its own implementation.
|
|
12
|
+
*
|
|
13
|
+
* A handler can be wrapped in a policy, e.g. `latest(fn)`. Calling the command
|
|
14
|
+
* directly, `command(payload)`, runs its own handler outside of any bus.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const getUser = createCommand("user/get", (id: string, { signal }) =>
|
|
19
|
+
* fetchUser(id, { signal }),
|
|
20
|
+
* );
|
|
21
|
+
* const sendMail = createCommand<Mail, void>("mail/send"); // implemented elsewhere
|
|
22
|
+
*
|
|
23
|
+
* const commands = createCommands([bindHandler(sendMail, smtpSend)]);
|
|
24
|
+
* const bus = createBus({ commands });
|
|
25
|
+
* const user = await bus.emit(getUser, "1");
|
|
26
|
+
*
|
|
27
|
+
* commands.handle(getUser, async () => fakeUser); // in a test
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare const createCommand: <P = void, R = void, T extends string = string, E extends EventMap = EventMap>(type: T, handler?: Task<P, E, R>) => Command<P, R> & {
|
|
31
|
+
readonly type: T;
|
|
32
|
+
};
|
|
33
|
+
/** A command (or its type) with the handler to bind to it; `bindHandler` makes one. */
|
|
34
|
+
type Binding<E extends EventMap = EventMap> = readonly [Command<never, unknown> | string, Task<never, E, unknown>];
|
|
35
|
+
/**
|
|
36
|
+
* Pairs a command with the handler to bind to it, for `createCommands`. It
|
|
37
|
+
* checks that the handler fits the command.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* createCommands([bindHandler(sendMail, smtpSend)]);
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
declare const bindHandler: <P, R, E extends EventMap>(command: Command<P, R>, fn: Task<P, E, R>) => Binding<E>;
|
|
45
|
+
/**
|
|
46
|
+
* The handlers bound to commands of `bus`: it makes the registry, sets it as
|
|
47
|
+
* the commands of the bus and returns it. Each bus has its own, so each test
|
|
48
|
+
* can have its own implementation. `handle` binds more later and returns the
|
|
49
|
+
* function that unbinds. A second `createCommands(bus)` replaces the registry
|
|
50
|
+
* of that bus.
|
|
51
|
+
*
|
|
52
|
+
* What the bus runs for a command: the handler bound last, else the one the
|
|
53
|
+
* command was created with, else it throws `No handler for command "..."`.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* const bus = createBus<Events>();
|
|
58
|
+
* const commands = createCommands(bus, [bindHandler(sendMail, smtpSend)]);
|
|
59
|
+
*
|
|
60
|
+
* const unbind = commands.handle(getUser, async () => fakeUser);
|
|
61
|
+
* unbind(); // the command's own handler is back
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
declare const createCommands: <E extends EventMap>(bus: Bus<E>, bindings?: readonly Binding<E>[]) => {
|
|
65
|
+
run: (command: Command, payload: unknown, ctx: unknown) => unknown;
|
|
66
|
+
handle: {
|
|
67
|
+
<P, R>(command: Command<P, R>, fn: Task<P, E, R>): UnsubscribeCallback;
|
|
68
|
+
(type: string, fn: Task<never, E, unknown>): UnsubscribeCallback;
|
|
69
|
+
(bindings: readonly Binding<E>[]): UnsubscribeCallback;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/actions/module.d.ts
|
|
74
|
+
/**
|
|
75
|
+
* What a module's `setup` gets: the context of the module's task, so `on`,
|
|
76
|
+
* `emit` and `run` work as in a handler, plus `handle` to bind commands, `use`
|
|
77
|
+
* to mount sub-modules and `spawn` to start a background task. Everything the
|
|
78
|
+
* module does is undone when it is unmounted.
|
|
79
|
+
*/
|
|
80
|
+
type ModuleContext<E extends EventMap = EventMap> = Context<E> & {
|
|
81
|
+
/** Binds the handler of a command, or a list of them; undone on unmount. */
|
|
82
|
+
handle<P, R>(command: Command<P, R>, fn: Task<P, E, R>): UnsubscribeCallback;
|
|
83
|
+
handle(type: string, fn: Task<never, E, unknown>): UnsubscribeCallback;
|
|
84
|
+
handle(bindings: readonly Binding<E>[]): UnsubscribeCallback;
|
|
85
|
+
/**
|
|
86
|
+
* Brings sub-modules and commands, in any order. A sub-module is mounted and
|
|
87
|
+
* unmounted with this one. A command is bound to the handler it carries, for
|
|
88
|
+
* as long as this module is mounted; a command that answers by itself keeps
|
|
89
|
+
* answering after the module is gone, since the handler is its own. The
|
|
90
|
+
* returned function undoes just this call, e.g. for what was added later from
|
|
91
|
+
* a listener.
|
|
92
|
+
*/
|
|
93
|
+
use(...items: Array<Module<E> | Command<never, unknown>>): UnsubscribeCallback;
|
|
94
|
+
/**
|
|
95
|
+
* Starts a background task that lives as long as the module: it is aborted
|
|
96
|
+
* on unmount, and an error of it is left unhandled, as in any handler.
|
|
97
|
+
*/
|
|
98
|
+
spawn<P>(fn: Task<P, E, unknown>, ...payload: PayloadOf<P>): void;
|
|
99
|
+
};
|
|
100
|
+
type Module<E extends EventMap = EventMap> = {
|
|
101
|
+
readonly name: string;
|
|
102
|
+
readonly setup: (m: ModuleContext<E>) => void;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Describes one domain of an app: the commands it handles, the events it
|
|
106
|
+
* listens to, its sub-modules and its background tasks. The `name` shows up as
|
|
107
|
+
* the type of the module's tasks in the trace.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* export const userModule = createModule<Events>("user", (m) => {
|
|
112
|
+
* m.handle(getUser, fetchUser);
|
|
113
|
+
* m.handle([bindHandler(sendMail, smtpSend)]);
|
|
114
|
+
* m.use(authModule);
|
|
115
|
+
*
|
|
116
|
+
* m.on(loginRequested, (user, ctx) => ctx.emit(userLoaded(user)));
|
|
117
|
+
* m.spawn(async (_, ctx) => {
|
|
118
|
+
* for (;;) await handleNext(await take(ctx, "job"));
|
|
119
|
+
* });
|
|
120
|
+
* });
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
declare const createModule: <E extends EventMap = EventMap>(name: string, setup: (m: ModuleContext<E>) => void) => Module<E>;
|
|
124
|
+
type Registry<E extends EventMap> = ReturnType<typeof createCommands<E>>;
|
|
125
|
+
/** What `modulePlugin` returns. */
|
|
126
|
+
type ModuleApi<E extends EventMap = EventMap> = {
|
|
127
|
+
/** The registry of command handlers of the bus: the one it was made with, or the app's own. */
|
|
128
|
+
commands: Registry<E>;
|
|
129
|
+
/** Mounts a module and returns the function that unmounts it; a module that is mounted already is not run again. */
|
|
130
|
+
mount(module: Module<E>): UnsubscribeCallback;
|
|
131
|
+
unmount(module: Module<E>): void;
|
|
132
|
+
isMounted(module: Module<E>): boolean;
|
|
133
|
+
/** Unmounts every module; a disposed app cannot mount modules. */
|
|
134
|
+
dispose(): void;
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* The plugin that assembles an app from modules: `bus.use(modulePlugin(modules))`
|
|
138
|
+
* mounts `modules` at once, in the order given, and returns `mount`, `unmount`,
|
|
139
|
+
* `isMounted`, `dispose` and `commands` for the modules that come and go later. The bus runs the commands that the
|
|
140
|
+
* modules bind: if `createCommands(bus)` was called before, that registry is
|
|
141
|
+
* used, otherwise the app attaches its own, as `app.commands`.
|
|
142
|
+
*
|
|
143
|
+
* A module is known by its `name`: mounting a module whose name is taken
|
|
144
|
+
* replaces the mounted one, which is unmounted first. That is what hot reload
|
|
145
|
+
* needs (see `docs/hmr.md`): the new version of a module file mounts again and
|
|
146
|
+
* the old version goes away with its subscriptions, commands and tasks.
|
|
147
|
+
*
|
|
148
|
+
* Unmounting undoes everything the module did: its subscriptions, its bound
|
|
149
|
+
* commands, its sub-modules and its background tasks. `dispose` unmounts them
|
|
150
|
+
* all, and a disposed app cannot mount modules.
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```ts
|
|
154
|
+
* const bus = createBus<Events>();
|
|
155
|
+
* const app = bus.use(modulePlugin([userModule]));
|
|
156
|
+
*
|
|
157
|
+
* const unmount = app.mount(orderModule); // later, e.g. after a lazy import
|
|
158
|
+
* unmount(); // or app.unmount(orderModule)
|
|
159
|
+
*
|
|
160
|
+
* bus.emit(getUser, "1");
|
|
161
|
+
* app.commands.handle(getUser, fakeUser); // in a test
|
|
162
|
+
* app.dispose();
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
declare const modulePlugin: <E extends EventMap>(modules?: readonly Module<E>[]) => (bus: Bus<E>) => ModuleApi<E>;
|
|
166
|
+
//#endregion
|
|
167
|
+
export { modulePlugin as a, createCommand as c, createModule as i, createCommands as l, ModuleApi as n, Binding as o, ModuleContext as r, bindHandler as s, Module as t };
|