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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -2
  3. package/dist/action-rqZEnRIp.d.ts +303 -0
  4. package/dist/actions.d.ts +58 -0
  5. package/dist/actions.js +1 -0
  6. package/dist/bus-CRnlwmQ4.js +1 -0
  7. package/dist/context-DlA80GOA.js +1 -0
  8. package/dist/delay-CIHnOh_L.js +1 -0
  9. package/dist/dev.d.ts +46 -0
  10. package/dist/dev.js +1 -0
  11. package/dist/effects.d.ts +284 -0
  12. package/dist/effects.js +1 -0
  13. package/dist/event-0RIBGYqS.js +1 -0
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.js +1 -0
  16. package/dist/module-BSCdR9D1.d.ts +167 -0
  17. package/dist/notrace/action-rqZEnRIp.d.ts +303 -0
  18. package/dist/notrace/actions.d.ts +222 -0
  19. package/dist/notrace/actions.js +1 -0
  20. package/dist/notrace/bus-Dx0l9X6h.js +1 -0
  21. package/dist/notrace/context-yAWj0BFZ.js +1 -0
  22. package/dist/notrace/delay-Cgvn16oY.js +1 -0
  23. package/dist/notrace/effects.d.ts +284 -0
  24. package/dist/notrace/effects.js +1 -0
  25. package/dist/notrace/event-0RIBGYqS.js +1 -0
  26. package/dist/notrace/index.d.ts +2 -0
  27. package/dist/notrace/index.js +1 -0
  28. package/dist/notrace/policies.d.ts +171 -0
  29. package/dist/notrace/policies.js +1 -0
  30. package/dist/notrace/promise-CSLpzwSR.js +1 -0
  31. package/dist/notrace/redux.d.ts +92 -0
  32. package/dist/notrace/redux.js +1 -0
  33. package/dist/observable.d.ts +20 -0
  34. package/dist/observable.js +1 -0
  35. package/dist/onAbort-BgMfspK4.js +1 -0
  36. package/dist/policies.d.ts +171 -0
  37. package/dist/policies.js +1 -0
  38. package/dist/production/dev.js +1 -0
  39. package/dist/promise-CSLpzwSR.js +1 -0
  40. package/dist/redux.d.ts +92 -0
  41. package/dist/redux.js +1 -0
  42. package/dist/trace.d.ts +162 -0
  43. package/dist/trace.js +1 -0
  44. package/docs/README.md +15 -0
  45. package/docs/actions.md +78 -0
  46. package/docs/context.md +15 -0
  47. package/docs/effects.md +137 -0
  48. package/docs/errors.md +23 -0
  49. package/docs/guide.md +229 -0
  50. package/docs/hmr.md +36 -0
  51. package/docs/observable.md +22 -0
  52. package/docs/policies.md +53 -0
  53. package/docs/semantics.md +7 -0
  54. package/docs/stores.md +50 -0
  55. package/docs/tracing.md +117 -0
  56. 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 };
@@ -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};
@@ -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 };