nanoeffects 0.4.0 → 0.6.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 (60) hide show
  1. package/README.md +4 -1
  2. package/dist/{action-creator-D48fp13T.d.ts → action-creator-c292nK7v.d.ts} +130 -34
  3. package/dist/actions.d.ts +54 -5
  4. package/dist/actions.js +1 -1
  5. package/dist/{bus-Cho463Sq.js → bus-BAs9x7IQ.js} +1 -1
  6. package/dist/{context-DlA80GOA.js → context-CK0ZomWt.js} +1 -1
  7. package/dist/context-plugin-1pYmSQEG.d.ts +34 -0
  8. package/dist/dev.d.ts +2 -2
  9. package/dist/effects.d.ts +27 -129
  10. package/dist/effects.js +1 -1
  11. package/dist/index.d.ts +3 -2
  12. package/dist/index.js +1 -1
  13. package/dist/{module-DVX8aYlq.d.ts → module-BlF2KSgy.d.ts} +34 -5
  14. package/dist/notrace/{action-creator-D48fp13T.d.ts → action-creator-DKG_sIia.d.ts} +130 -34
  15. package/dist/notrace/actions.d.ts +96 -7
  16. package/dist/notrace/actions.js +1 -1
  17. package/dist/notrace/{bus-B12SvxLv.js → bus-Bdk0YyFQ.js} +1 -1
  18. package/dist/notrace/{context-yAWj0BFZ.js → context-CctVmHLC.js} +1 -1
  19. package/dist/notrace/context-plugin-Dt1IgVo8.d.ts +34 -0
  20. package/dist/notrace/{delay-Cgvn16oY.js → delay-B_W1tnkS.js} +1 -1
  21. package/dist/notrace/effects.d.ts +27 -129
  22. package/dist/notrace/effects.js +1 -1
  23. package/dist/notrace/index.d.ts +3 -2
  24. package/dist/notrace/index.js +1 -1
  25. package/dist/notrace/plugin-B0AXKusc.d.ts +207 -0
  26. package/dist/notrace/policies.d.ts +1 -1
  27. package/dist/notrace/policies.js +1 -1
  28. package/dist/notrace/redux.d.ts +18 -20
  29. package/dist/notrace/redux.js +1 -1
  30. package/dist/observable.d.ts +1 -1
  31. package/dist/plugin-CCYlC0XS.d.ts +207 -0
  32. package/dist/policies.d.ts +1 -1
  33. package/dist/policies.js +1 -1
  34. package/dist/react.d.ts +50 -0
  35. package/dist/react.js +1 -0
  36. package/dist/redux.d.ts +18 -20
  37. package/dist/redux.js +1 -1
  38. package/dist/scope-key-B7l7mUS5.js +1 -0
  39. package/dist/scope.d.ts +58 -0
  40. package/dist/scope.js +1 -0
  41. package/dist/trace.d.ts +1 -1
  42. package/dist/types-mLaAx-EZ.d.ts +15 -0
  43. package/docs/README.md +7 -1
  44. package/docs/actions.md +25 -1
  45. package/docs/context.md +26 -2
  46. package/docs/effects.md +83 -5
  47. package/docs/guide.md +24 -0
  48. package/docs/migrating-from-effector.md +293 -0
  49. package/docs/migrating-from-rxjs.md +127 -0
  50. package/docs/migrating-from-saga.md +93 -0
  51. package/docs/observable.md +1 -0
  52. package/docs/package.md +13 -2
  53. package/docs/policies.md +1 -0
  54. package/docs/react.md +157 -0
  55. package/docs/scope.md +226 -0
  56. package/docs/stores.md +429 -40
  57. package/docs/typescript.md +153 -0
  58. package/llms-full.txt +2925 -0
  59. package/llms.txt +49 -0
  60. package/package.json +44 -6
package/README.md CHANGED
@@ -15,6 +15,8 @@ npm install nanoeffects
15
15
 
16
16
  ESM only, no dependencies, types included.
17
17
 
18
+ Want to try it first? [Open the playground](https://stackblitz.com/github/nanoeffects/nanoeffects/tree/main/examples/playground): a search box with `debounce` and `latest`, running in your browser, no install. More playgrounds (React) and recipes: [Examples](examples/README.md).
19
+
18
20
  ## Create a bus
19
21
 
20
22
  Subscribe with `on`, send with `emit`. `on` returns a function that unsubscribes.
@@ -48,6 +50,7 @@ const bus = createBus();
48
50
  bus.on(
49
51
  "search",
50
52
  latest(async (query, ctx) => {
53
+ // signal — lets latest cancel this request, not only drop its result
51
54
  const items = await api.search(query, { signal: ctx.signal });
52
55
  ctx.emit("results", items);
53
56
  }),
@@ -85,7 +88,7 @@ Start with the [Guide](docs/guide.md) (bus → policies → effects → modules)
85
88
  | Domain logic off the UI | [Actions, commands, modules](docs/actions.md) |
86
89
  | Redux / Zustand / MobX | [Stores](docs/stores.md) |
87
90
 
88
- [Full index, package entries, tracing](docs/README.md) · [Examples](examples)
91
+ [Full index, package entries, tracing](docs/README.md) · [Examples](examples/README.md)
89
92
 
90
93
  - **Tiny.** `createBus` is about 1.3 kB gzip; other entries load only when imported.
91
94
  - **Cancellation first.** Aborting a task aborts its children and drops its result.
@@ -1,3 +1,29 @@
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
1
27
  //#region src/core/store.d.ts
2
28
  declare const anyEventType = "*";
3
29
  //#endregion
@@ -83,14 +109,21 @@ type Emit<E extends EventMap> = {
83
109
  <K extends EventKey<E>>(event: K, ...payload: PayloadOf<E[K]>): void;
84
110
  /** Before Handler: a command is callable too, and must return its result type. */
85
111
  <P, R>(command: Command<P, R>, ...payload: PayloadOf<P>): R;
86
- <P, R>(call: CommandCall<P, R>): R;
112
+ /** A scope as the second argument replaces context fields for the tasks of this emit; see `scopePlugin`. */
113
+ <P, R, F>(call: CommandCall<P, R> & WithScope<F>, scope?: EmitScope<E, F>): R;
114
+ <P, R>(call: CommandCall<P, R>, scope?: EmitScope<E>): R;
87
115
  <K extends EventKey<E>, R>(event: Handler<E, R, K>, ...payload: PayloadOf<E[K]>): R;
88
- (event: EventLike<E>): void;
116
+ <F>(event: EventLike<E> & WithScope<F>, scope?: EmitScope<E, F>): void;
117
+ (event: EventLike<E>, scope?: EmitScope<E>): void;
89
118
  };
90
119
  type On<E extends EventMap> = {
91
120
  (event: AnyEvent, fn: Task<EventPayload<E>, E>): UnsubscribeCallback;
92
121
  <K extends EventKey<E>>(event: K, fn: Task<E[K], E>): UnsubscribeCallback;
93
122
  /** An action creator without a payload type: the payload comes from the event map. */
123
+ /** The creators and commands that carry a scope come first: the handler sees their fields. */
124
+ <K extends EventKey<E>, F>(creator: FreeActionCreator<K, F> & WithScope<F>, fn: Task<E[K], WithFields<E, F>>): UnsubscribeCallback;
125
+ <P, F>(creator: ActionCreator<P, string, PayloadOf<P>, F> & WithScope<F>, fn: Task<P, WithFields<E, F>>): UnsubscribeCallback;
126
+ <P, F>(command: Command<P, unknown> & WithScope<F>, fn: Task<P, WithFields<E, F>>): UnsubscribeCallback;
94
127
  <K extends EventKey<E>>(creator: FreeActionCreator<K>, fn: Task<E[K], E>): UnsubscribeCallback;
95
128
  /** An action creator: the payload comes from it, so an event map is not needed. */
96
129
  <P>(creator: ActionCreator<P>, fn: Task<P, E>): UnsubscribeCallback;
@@ -157,6 +190,18 @@ type Context<E extends EventMap> = EventLike<E> & ContextOf<E> & {
157
190
  /** Child task: aborted with the parent; errors propagate to the parent. */
158
191
  run<P, R>(fn: Task<P, E, R>, ...args: PayloadOf<P>): Promise<Awaited<R>>;
159
192
  };
193
+ /**
194
+ * The event map of a bus, with the context fields that plugins added. Use it to
195
+ * name the types of a bus in other files: `type AppEvents = EventsOf<typeof bus>`.
196
+ *
197
+ * @example
198
+ * ```ts
199
+ * const bus = createBus<Events>().use(effectsPlugin({ race }));
200
+ * type AppEvents = EventsOf<typeof bus>;
201
+ * type AppContext = Context<AppEvents>; // has `race`
202
+ * ```
203
+ */
204
+ type EventsOf<B> = B extends Bus<infer E extends EventMap> ? E : never;
160
205
  interface Bus<E extends EventMap> {
161
206
  on: On<E>;
162
207
  /**
@@ -173,10 +218,16 @@ interface Bus<E extends EventMap> {
173
218
  */
174
219
  use<R>(plugin: (bus: Bus<E>) => R): R;
175
220
  /** `BusOptions.context` and `.trace`, and `commands`, for what makes tasks of its own, like `modulePlugin`. */
176
- readonly context?: ContextOption<E> | undefined;
221
+ readonly ctx?: ContextOption<E> | undefined;
177
222
  /** What runs the commands; `commandsPlugin` and `modulePlugin` set it. Without one a command runs its own handler. */
178
223
  commands?: Commands | undefined;
179
224
  readonly trace?: Tracer | undefined;
225
+ /** Called with every new task context to add fields to it, for what needs the task, like `effectsPlugin`. Chain to the previous one. */
226
+ addContext?: ((ctx: object) => void) | undefined;
227
+ /** Puts the fields of a module's scope on the context of one of its tasks; `scopePlugin` sets it. Without it a module's scope is ignored. */
228
+ applyScope?: ((ctx: object, scope: Scope) => void) | undefined;
229
+ /** Wraps a handler so it runs with the fields of a module's scope; `scopePlugin` sets it. */
230
+ fitScope?: ((scope: Scope, fn: (payload: never, ctx: never) => unknown) => unknown) | undefined;
180
231
  }
181
232
  type BusOptions<E extends EventMap> = {
182
233
  /** Tracing of every task phase; make it with `createTracer` from `nanoeffects/trace`. */
@@ -214,31 +265,76 @@ type BusOptions<E extends EventMap> = {
214
265
  };
215
266
  declare const createBus: <Events extends EventMap>(options?: BusOptions<Events>) => Bus<Events>;
216
267
  //#endregion
217
- //#region src/core/types.d.ts
218
- type PayloadOf<P> = undefined extends P ? [payload?: P] : [payload: P];
219
- type EventMap = Record<string, unknown>;
220
- type Keys<E> = keyof E & string;
221
- type Task<P, E extends EventMap, R = unknown> = (payload: P, ctx: Context<E>) => R;
222
- type EffectName = "every" | "latest" | "leading" | "debounce" | "throttle" | "run" | "all" | "race" | "retry" | "timeout" | "poll" | "take" | "queue" | "allSettled" | "command";
223
- type Phase = "start" | "done" | "error" | "cancel" | "drop";
224
- type TraceEvent<E = Record<string, unknown>> = { [K in Keys<E>]: {
225
- id: number;
226
- parent?: number;
227
- type: K;
228
- effect: EffectName;
229
- phase: Phase;
230
- payload: E[K];
231
- error?: unknown;
232
- }; }[Keys<E>];
268
+ //#region src/core/effect-types.d.ts
269
+ /** What an effect needs of the task it runs for: its signal. A task context fits, and so does `{ signal }`. */
270
+ type Signaled = {
271
+ signal: AbortSignal;
272
+ };
273
+ /** An effect: a function whose first parameter takes `{ signal }`, like `retry(ctx, fn)`. */
274
+ type SignalFirst = (ctx: Signaled, ...args: never[]) => unknown;
275
+ /** The effects of a scope or of `effectsPlugin`, by the name they get on the context. */
276
+ type Effects = Record<string, SignalFirst>;
277
+ declare const bound: unique symbol;
233
278
  /**
234
- * The contract of every trace logger: receives each task phase of a bus.
235
- * Adapters in `nanoeffects/trace` are factories that return one; pass it
236
- * through `createTracer` to `createBus({ trace })`.
279
+ * An effect that says what it looks like once it is bound to a task (without
280
+ * the `ctx` parameter). A plain effect has its first parameter removed, which
281
+ * erases its generics; this type keeps them, because `F` is carried as it is.
282
+ * It changes the checker only: the function is the same at run time.
283
+ *
284
+ * @example
285
+ * ```ts
286
+ * type First = <T>(items: T[]) => T; // how a handler calls it
287
+ * const first: Effect<First> = (ctx, items) => items[0];
288
+ * ```
237
289
  */
238
- type TraceLogger<E = Record<string, unknown>> = (event: TraceEvent<E>) => void;
239
- /** One logger or several; a throwing logger never breaks the task or the others. */
240
- type Trace<E = Record<string, unknown>> = TraceLogger<E> | readonly TraceLogger<E>[];
241
- type UnsubscribeCallback = () => void;
290
+ type Effect<F extends (...args: never[]) => unknown> = ((ctx: Signaled, ...args: Parameters<F>) => ReturnType<F>) & {
291
+ readonly [bound]?: F;
292
+ };
293
+ /** The bound form of a plain effect: the first parameter is gone, and so are its generics. */
294
+ type Strip<F> = F extends ((ctx: never, ...args: infer A) => infer R) ? (...args: A) => R : never;
295
+ /** The bound type an `Effect<F>` carries, or `Strip` of a plain function. */
296
+ type BoundForm<F> = typeof bound extends keyof F ? NonNullable<F[typeof bound & keyof F]> : Strip<F>;
297
+ //#endregion
298
+ //#region src/core/scope-types.d.ts
299
+ declare const scopeFields: unique symbol;
300
+ /** What an action creator, a command or a call knows of the scope it was made with: its fields, for the checker. */
301
+ type WithScope<F> = {
302
+ readonly [scopeFields]: F;
303
+ };
304
+ /** `X`, marked with the scope fields `F` it carries; `X` itself when there are none, so unscoped types stay as they were. */
305
+ type Carrying<X, F> = X & ([unknown] extends [F] ? unknown : WithScope<F>);
306
+ /**
307
+ * What a task gets on top of the context of its bus, for one place: every key
308
+ * is a context value and goes on `ctx` as it is, except `effects`, whose
309
+ * functions (each takes `{ signal }` first) are bound to the task. Made work by
310
+ * `scopePlugin` from `nanoeffects/scope`; without it a scope is ignored.
311
+ *
312
+ * @example
313
+ * ```ts
314
+ * const scope = { cache, effects: { race } }; // ctx.cache, and ctx.race bound to the task
315
+ * ```
316
+ */
317
+ type Scope = {
318
+ readonly effects?: Effects;
319
+ readonly [field: string]: unknown;
320
+ };
321
+ /**
322
+ * What a call may replace of what it already has: the fields of the bus and
323
+ * those of its own scope, and its effects. The types must match the ones they
324
+ * replace; a field that neither declares has no type to check against, so it is
325
+ * an error.
326
+ */
327
+ type EmitScope<E extends EventMap, F = unknown> = ([unknown] extends [ContextOf<E> & F] ? unknown : Partial<ContextOf<E> & F>) & {
328
+ effects?: Partial<Effects>;
329
+ };
330
+ /** `A` with the fields of `B` replacing those of the same name, not intersected with them. */
331
+ type Merge<A, B> = Omit<A, keyof B> & B;
332
+ /**
333
+ * The event map `E` with the fields `F` of a scope on top of its context
334
+ * fields; `E` itself when there are none. A field of the scope replaces the
335
+ * one of the bus in the type too.
336
+ */
337
+ type WithFields<E extends EventMap, F> = [unknown] extends [F] ? E : Omit<E, keyof WithContext<object>> & WithContext<Merge<ContextOf<E>, F>>;
242
338
  //#endregion
243
339
  //#region src/core/action-creator.d.ts
244
340
  type WithPayload<E extends EventMap, K extends Keys<E>> = undefined extends E[K] ? {
@@ -257,15 +353,15 @@ type Free = typeof free;
257
353
  * it is given, and the bus checks it against its event map, so
258
354
  * `createAction("user/loaded")` needs no generics and keeps its literal type.
259
355
  */
260
- interface FreeActionCreator<T extends string> {
261
- (): {
356
+ interface FreeActionCreator<T extends string, F = unknown> {
357
+ (): Carrying<{
262
358
  type: T;
263
359
  payload: undefined;
264
- };
265
- <P>(payload: P): {
360
+ }, F>;
361
+ <P>(payload: P): Carrying<{
266
362
  type: T;
267
363
  payload: P;
268
- };
364
+ }, F>;
269
365
  readonly type: T;
270
366
  /** Tells whether an action is one of this creator. */
271
367
  match(action: {
@@ -281,8 +377,8 @@ interface FreeActionCreator<T extends string> {
281
377
  * Makes actions of one type. It also works as the event of that type:
282
378
  * `bus.on(creator, fn)` and `bus.emit(creator(payload))`.
283
379
  */
284
- interface ActionCreator<P = void, T extends string = string, A extends unknown[] = PayloadOf<P>> {
285
- (...args: A): Made<P, T>;
380
+ interface ActionCreator<P = void, T extends string = string, A extends unknown[] = PayloadOf<P>, F = unknown> {
381
+ (...args: A): Carrying<Made<P, T>, F>;
286
382
  readonly type: T;
287
383
  /** Tells whether an action is one of this creator, and narrows its type. */
288
384
  match(action: {
@@ -292,4 +388,4 @@ interface ActionCreator<P = void, T extends string = string, A extends unknown[]
292
388
  toString(): T;
293
389
  }
294
390
  //#endregion
295
- export { Handler as A, ContextOf as C, EventKey as D, Emittable as E, WithType as F, commandCall as I, createBus as L, Off as M, On as N, EventLike as O, WithContext as P, Tracer as R, Context as S, Emit as T, Bus as _, EffectName as a, CommandCall as b, PayloadOf as c, Trace as d, TraceEvent as f, AnyEvent as g, Action as h, WithPayload as i, HandlerRef as j, EventPayload as k, Phase as l, UnsubscribeCallback as m, Free as n, EventMap as o, TraceLogger as p, FreeActionCreator as r, Keys as s, ActionCreator as t, Task as u, BusOptions as v, ContextOption as w, Commands as x, Command as y, anyEventType as z };
391
+ export { HandlerRef as A, EventMap as B, Emit as C, EventPayload as D, EventLike as E, commandCall as F, Trace as G, PayloadOf as H, createBus as I, UnsubscribeCallback as J, TraceEvent as K, Tracer as L, On as M, WithContext as N, EventsOf as O, WithType as P, anyEventType as R, ContextOption as S, EventKey as T, Phase as U, Keys as V, Task as W, Command as _, EmitScope as a, Context as b, WithScope as c, Effects as d, Signaled as f, BusOptions as g, Bus as h, WithPayload as i, Off as j, Handler as k, BoundForm as l, AnyEvent as m, Free as n, Scope as o, Action as p, TraceLogger as q, FreeActionCreator as r, WithFields as s, ActionCreator as t, Effect as u, CommandCall as v, Emittable as w, ContextOf as x, Commands as y, EffectName as z };
package/dist/actions.d.ts CHANGED
@@ -1,6 +1,8 @@
1
- import { n as Free, o as EventMap, r as FreeActionCreator, t as ActionCreator, u as Task } from "./action-creator-D48fp13T.js";
2
- import { a as modulePlugin, c as bindHandler, i as createModule, l as commandsPlugin, n as ModuleApi, o as Binding, r as ModuleContext, s as MadeCommand, t as Module, u as createCommand } from "./module-DVX8aYlq.js";
1
+ import { B as EventMap, H as PayloadOf, T as EventKey, W as Task, c as WithScope, h as Bus, n as Free, o as Scope, r as FreeActionCreator, t as ActionCreator } from "./action-creator-c292nK7v.js";
2
+ import { n as Scoped, t as FieldsOfScope } from "./types-mLaAx-EZ.js";
3
+ import { a as modulePlugin, c as MadeCommand, d as createCommand, i as createModule, l as bindHandler, n as ModuleApi, o as Binding, r as ModuleContext, s as CarryingCalls, t as Module, u as commandsPlugin } from "./module-BlF2KSgy.js";
3
4
  //#region src/actions/action.d.ts
5
+ type ScopedCreator<P, T extends string, F> = [P] extends [Free] ? FreeActionCreator<T, F> & WithScope<F> : ActionCreator<P, T, PayloadOf<P>, F> & WithScope<F>;
4
6
  /**
5
7
  * Creates an action creator, as in Redux Toolkit.
6
8
  *
@@ -13,6 +15,9 @@ import { a as modulePlugin, c as bindHandler, i as createModule, l as commandsPl
13
15
  * generic: `createAction<User, "user/loaded">("user/loaded")`.
14
16
  * - With `prepare` the arguments of the creator can differ from the payload,
15
17
  * and both types are inferred.
18
+ * - A scope as the last argument, `{ cache, effects: { race } }`, gives the
19
+ * listeners of its calls those context fields; it works on a bus that uses `scopePlugin`
20
+ * from `nanoeffects/scope` and is ignored on any other.
16
21
  *
17
22
  * @example
18
23
  * ```ts
@@ -33,6 +38,45 @@ export declare function createAction<P = Free, T extends string = string>(type:
33
38
  export declare function createAction<A extends unknown[], P, T extends string = string>(type: T, prepare: (...args: A) => {
34
39
  payload: P;
35
40
  }): ActionCreator<P, T, A>;
41
+ export declare function createAction<P = Free, T extends string = string, S extends Scope = Record<never, never>>(type: T, scope: S): ScopedCreator<P, T, FieldsOfScope<EventMap, S>>;
42
+ export declare function createAction<A extends unknown[], P, T extends string, S extends Scope>(type: T, prepare: (...args: A) => {
43
+ payload: P;
44
+ }, scope: S): ActionCreator<P, T, A, FieldsOfScope<EventMap, S>> & WithScope<FieldsOfScope<EventMap, S>>;
45
+ //#endregion
46
+ //#region src/actions/emitter.d.ts
47
+ /** What `emitterPlugin` adds to the bus. */
48
+ type EmitterApi<E extends EventMap> = {
49
+ /**
50
+ * Turns an action creator into a function that emits its action on this
51
+ * bus, to pass as a callback. The function takes what the creator takes.
52
+ */
53
+ emitter: {
54
+ /** A creator without a payload type: the payload comes from the event map. */
55
+ <K extends EventKey<E>>(creator: FreeActionCreator<K>): (...payload: PayloadOf<E[K]>) => void;
56
+ <P, T extends string, A extends unknown[]>(creator: ActionCreator<P, T, A>): (...args: A) => void;
57
+ };
58
+ };
59
+ /**
60
+ * Adds `bus.emitter(creator)`: a function that emits the action of `creator`
61
+ * on this bus. Use it where something wants a callback, such as the
62
+ * subscription of a state manager, instead of `(value) => bus.emit(creator(value))`.
63
+ * The creator stays a plain `createAction`, so `bus.emit(creator(value))` and
64
+ * `bus.on(creator, …)` work as before. Returns the same bus.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * import { createBus } from "nanoeffects";
69
+ * import { createAction, emitterPlugin } from "nanoeffects/actions";
70
+ *
71
+ * const bus = createBus().use(emitterPlugin());
72
+ * const nameChanged = createAction<string>("name/changed");
73
+ * const emitNameChanged = bus.emitter(nameChanged);
74
+ *
75
+ * // nanostores: the store calls it with the new value
76
+ * $name.listen(emitNameChanged);
77
+ * ```
78
+ */
79
+ export declare const emitterPlugin: () => <E extends EventMap>(bus: Bus<E>) => Bus<E> & EmitterApi<E>;
36
80
  //#endregion
37
81
  //#region src/actions/job.d.ts
38
82
  type Creator = {
@@ -58,6 +102,9 @@ type Job<P, R, T extends string, S extends Outcome, F extends Outcome> = MadeCom
58
102
  * effect. It returns the command, with the two actions on it: `job.success` and
59
103
  * `job.fail`.
60
104
  *
105
+ * A scope as the last argument, `{ cache, effects: { race } }`, gives the handler
106
+ * and the listeners of the outcome those context fields; see `scopePlugin`.
107
+ *
61
108
  * `success` and `fail` are a type string, from which the job makes an action
62
109
  * creator with `createAction`, or an action creator that exists already, which
63
110
  * is used and returned as it is. A missing one is made with the type
@@ -82,11 +129,13 @@ type Job<P, R, T extends string, S extends Outcome, F extends Outcome> = MadeCom
82
129
  * bus.emit(loadUser("1")); // loadUser.success(user) or loadUser.fail(error) comes later
83
130
  * ```
84
131
  */
85
- export declare const createJob: <P = void, R = void, T extends string = string, E extends EventMap = EventMap, S extends Outcome = `${T}/success`, F extends Outcome = `${T}/fail`>(type: T, handler?: Task<P, E, R>, outcomes?: {
132
+ export declare const createJob: <P = void, R = void, T extends string = string, E extends EventMap = EventMap, S extends Outcome = `${T}/success`, F extends Outcome = `${T}/fail`, X extends Scope = Record<never, never>>(type: T, handler?: Task<P, Scoped<E, X>, R>, outcomes?: {
86
133
  /** Gets the result when the handler succeeds: a type to make an action creator of, or an action creator. */
87
134
  success?: S;
88
135
  /** Gets the error when the handler fails: a type to make an action creator of, or an action creator. */
89
136
  fail?: F;
90
- }) => Job<P, R, T, S, F>;
137
+ },
138
+ /** Context values and `effects` for the handler and for the listeners of the outcome; see `scopePlugin`. */
139
+ scope?: X) => CarryingCalls<Job<P, R, T, S, F>, P, Promise<Awaited<R> | undefined>, FieldsOfScope<E, X>>;
91
140
  //#endregion
92
- export { type ActionCreator, type Binding, type FreeActionCreator, type Job, type Module, type ModuleApi, type ModuleContext, bindHandler, commandsPlugin, createCommand, createModule, modulePlugin };
141
+ export { type ActionCreator, type Binding, type EmitterApi, type FreeActionCreator, type Job, type Module, type ModuleApi, type ModuleContext, bindHandler, commandsPlugin, createCommand, createModule, modulePlugin };
package/dist/actions.js CHANGED
@@ -1 +1 @@
1
- import{t as e}from"./onAbort-BgMfspK4.js";import{i as t,n}from"./context-DlA80GOA.js";import{t as r}from"./event-0RIBGYqS.js";import{r as i}from"./bus-Cho463Sq.js";function a(e,t){return Object.assign((...n)=>({type:e,payload:t?t(...n).payload:n[0]}),{type:e,match:t=>t.type===e,toString:()=>e})}var o=class extends Error{};const s=(e,t)=>{let n=t=>t.type===e;function r(n,r){if(typeof r==`object`){if(!t)throw new o(`No handler for command "${e}"`);return t(n,r)}return i(a,e,n)}let a=Object.assign(r,{type:e,match:n});return a},c=(e,t)=>[e,t],l=(e=[])=>t=>{let n=/* @__PURE__ */ new Map,i=(e,t)=>{let r={fn:t};return n.set(e,[...n.get(e)??[],r]),()=>{let t=(n.get(e)??[]).filter(e=>e!==r);t.length>0?n.set(e,t):n.delete(e)}};function a(e,t){if(t)return i(r(e),t);let n=e.map(([e,t])=>i(r(e),t));return()=>{for(let e of n)e()}}a(e);let o={run:(e,t,r)=>{let i=(n.get(e.type)??[]).at(-1)?.fn,a=i&&e.wrap?e.wrap(i):i??e;return Reflect.apply(a,null,[t,r])},handle:a};return t.commands=o,o},u=e=>typeof e==`string`?a(e):e,d=(e,t,n)=>{let r=e.emit;r(Reflect.apply(t,null,[n]))},f=e=>{try{return Promise.resolve(e())}catch(e){if(e instanceof o)throw e;return Promise.reject(e)}},p=(e,t,n={})=>{let r=s(e,t),a=u(n.success??`${e}/success`),o=u(n.fail??`${e}/fail`),c=(e,t,n)=>{let r=n;return f(()=>Reflect.apply(e,null,[t,n])).then(e=>(d(r,a,e),e),e=>{d(r,o,e)})},l=e=>(t,n)=>c(e,t,n);function p(t,n){return typeof n==`object`?c(r,t,n):i(m,e,t)}let m=Object.assign(p,{type:e,match:r.match,wrap:l,success:a,fail:o});return m},m=(e,t)=>({name:e,setup:t}),h=(r=[])=>i=>{let a=i.commands??l()(i),o={handle:(...e)=>{if(!a.handle)throw Error("modulePlugin: the `commands` of the bus is not a registry from `commandsPlugin()`");return Reflect.apply(a.handle,null,e)}},s=n({event:{type:`app`,payload:void 0},bus:i,tracer:i.trace}),c=n=>{let r=/* @__PURE__ */ new Map,i=/* @__PURE__ */ new Map;return{mount:a=>{let s=r.get(a);if(s)return s;let l=i.get(a.name);l&&r.get(l)?.();let u=t(n,a.name),d=u.ctx,f=c(d),p=()=>{r.delete(a),i.get(a.name)===a&&i.delete(a.name),d.abort(),u.unlink()};r.set(a,p),i.set(a.name,a);let m=(t,n)=>{let r=Reflect.apply(o.handle,null,[t,n]);return e(d,r),r},h=(...e)=>{let t=e.map(e=>typeof e==`function`?m(e,e):f.mount(e));return()=>{for(let e of t)e()}},g=(e,...t)=>{d.run(e,...t).catch(e=>{if(!d.signal.aborted)throw e})};try{a.setup(Object.assign(d,{handle:m,use:h,spawn:g}))}catch(e){throw p(),e}return p},unmount:e=>r.get(e)?.(),isMounted:e=>r.has(e)}},u=c(s.ctx);for(let e of r)u.mount(e);return{commands:a,mount:e=>(s.ctx.signal.throwIfAborted(),u.mount(e)),unmount:u.unmount,isMounted:u.isMounted,dispose:()=>s.ctx.abort()}};export{c as bindHandler,l as commandsPlugin,a as createAction,s as createCommand,p as createJob,m as createModule,h as modulePlugin};
1
+ import{t as e}from"./onAbort-BgMfspK4.js";import{i as t,n}from"./context-CK0ZomWt.js";import{t as r}from"./event-0RIBGYqS.js";import{r as i}from"./bus-BAs9x7IQ.js";import{t as a}from"./scope-key-B7l7mUS5.js";const o=(e,t)=>(t&&Object.defineProperty(e,a,{value:t}),e);function s(e,t,n){let r=typeof t==`function`?t:void 0,i=typeof t==`function`?n:t;return Object.assign(o((...t)=>o({type:e,payload:r?r(...t).payload:t[0]},i),i),{type:e,match:t=>t.type===e,toString:()=>e})}var c=class extends Error{};function l(e,t,n){let r=t=>t.type===e;function a(r,a){if(typeof a==`object`){if(!t)throw new c(`No handler for command "${e}"`);return t(r,a)}return o(i(l,e,r),n)}let s=Object.assign(a,{type:e,match:r}),l=o(s,n);return l}const u=(e,t)=>[e,t],d=(e=[])=>t=>{let n=/* @__PURE__ */ new Map,i=(e,t)=>{let r={fn:t};return n.set(e,[...n.get(e)??[],r]),()=>{let t=(n.get(e)??[]).filter(e=>e!==r);t.length>0?n.set(e,t):n.delete(e)}};function a(e,t){if(t)return i(r(e),t);let n=e.map(([e,t])=>i(r(e),t));return()=>{for(let e of n)e()}}a(e);let o={run:(e,t,r)=>{let i=(n.get(e.type)??[]).at(-1)?.fn,a=i&&e.wrap?e.wrap(i):i??e;return Reflect.apply(a,null,[t,r])},handle:a};return t.commands=o,o},f=()=>e=>{function t(t){return(...n)=>{let r=t(...n);e.emit(r)}}return Object.assign(e,{emitter:t})},p=e=>typeof e==`string`?s(e):e,m=(e,t,n,r)=>{let i=e.emit;i(Reflect.apply(t,null,[n]),r)},h=e=>{try{return Promise.resolve(e())}catch(e){if(e instanceof c)throw e;return Promise.reject(e)}},g=(e,t,n={},r)=>{let a=l(e,t),s=p(n.success??`${e}/success`),c=p(n.fail??`${e}/fail`),u=(e,t,n)=>{let i=n;return h(()=>Reflect.apply(e,null,[t,n])).then(e=>(m(i,s,e,r),e),e=>{m(i,c,e,r)})},d=e=>(t,n)=>u(e,t,n);function f(t,n){return typeof n==`object`?u(a,t,n):o(i(_,e,t),r)}let g=Object.assign(f,{type:e,match:a.match,wrap:d,success:s,fail:c}),_=o(g,r);return _};function _(e,t,n){return n?{name:e,setup:t,scope:n}:{name:e,setup:t}}const v=(e,t)=>n=>t&&typeof n==`function`?e.fitScope?.(t,n)??n:n,y=(e,t,n)=>{n&&e.applyScope?.(t,n)},b=(e,t,n)=>Array.isArray(t)?[t.map(([t,n])=>[t,e(n)]),n]:[t,e(n)],x=(e,t)=>{let n=e.on;return(...e)=>{let[r,i]=e;return Reflect.apply(n,null,[r,t(i)])}},S=e=>(t,...n)=>{e.run(t,...n).catch(t=>{if(!e.signal.aborted)throw t})},C=(r=[])=>i=>{let a=i.commands??d()(i),o={handle:(...e)=>{if(!a.handle)throw Error("modulePlugin: the `commands` of the bus is not a registry from `commandsPlugin()`");return Reflect.apply(a.handle,null,e)}},s=n({event:{type:`app`,payload:void 0},bus:i,tracer:i.trace}),c=n=>{let r=/* @__PURE__ */ new Map,a=/* @__PURE__ */ new Map;return{mount:s=>{let l=r.get(s);if(l)return l;let u=a.get(s.name);u&&r.get(u)?.();let d=t(n,s.name),f=d.ctx,p=c(f),m=()=>{r.delete(s),a.get(s.name)===s&&a.delete(s.name),f.abort(),d.unlink()};r.set(s,m),a.set(s.name,s);let h=s.scope,g=v(i,h),_=(t,n)=>{let r=Reflect.apply(o.handle,null,b(g,t,n));return e(f,r),r},C=(...e)=>{let t=e.map(e=>typeof e==`function`?_(e,e):p.mount(e));return()=>{for(let e of t)e()}},w=S(f),T=x(f,g);try{y(i,f,h),s.setup(Object.assign(f,{handle:_,use:C,spawn:w,on:T}))}catch(e){throw m(),e}return m},unmount:e=>r.get(e)?.(),isMounted:e=>r.has(e)}},l=c(s.ctx);for(let e of r)l.mount(e);return{commands:a,mount:e=>(s.ctx.signal.throwIfAborted(),l.mount(e)),unmount:l.unmount,isMounted:l.isMounted,dispose:()=>s.ctx.abort(),createModule:_}};export{u as bindHandler,d as commandsPlugin,s as createAction,l as createCommand,g as createJob,_ as createModule,f as emitterPlugin,C as modulePlugin};
@@ -1 +1 @@
1
- import{n as e}from"./onAbort-BgMfspK4.js";import{a as t,n,r}from"./context-DlA80GOA.js";import{n as i}from"./promise-CSLpzwSR.js";import{n as a,t as o}from"./event-0RIBGYqS.js";const s=`*`,c=()=>{let e={},t={},n=n=>(t[n]=void 0,e[n]??=/* @__PURE__ */ new Map,e[n]),r=(e,t)=>{let r=n(e),i=r.get(t)??0;i>1?r.set(t,i-1):t?r.delete(t):r.clear()};return{get:n=>(t[n]??=[...e[n]??[]].flatMap(([e,t])=>Array(t).fill(e)),t[n]),add:(e,t)=>{let i=n(e);return i.set(t,(i.get(t)??0)+1),()=>{r(e,t)}},remove:r,clear:()=>{for(let t of Reflect.ownKeys(e))delete e[t];for(let e of Reflect.ownKeys(t))delete t[e]}}},l=Symbol(`nanoeffects.commandCall`),u=(e,t,n)=>{let r={type:t,payload:n};return Object.defineProperty(r,l,{value:e,enumerable:!1}),r},d=e=>{if(typeof e==`function`)return e;if(typeof e==`object`&&e&&l in e)return e[l]},f=(e,t,n)=>Reflect.apply(e,null,[t,n]),p=(s={})=>{let l=c(),u=(e,t)=>l.add(o(e),t),p=(e,t)=>{let n=u(e,(e,r)=>(n(),t(e,r)));return n},m=(e,t)=>{e===void 0?l.clear():l.remove(o(e),t)},h=(t,a)=>{let{onError:o}=s,{ctx:c}=n({event:a,bus:g,tracer:s.trace,parent:{ref:r()}});if(!o){t(a.payload,c);return}let l=t=>{try{o(t,a)}catch(t){e(`onError failed`,t)}};try{let e=t(a.payload,c);i(e)&&Promise.resolve(e).catch(l)}catch(e){l(e)}},g={on:u,once:p,off:m,emit:(e,i)=>{let c=o(e),u=a(e,i),p={type:c,payload:u},m=d(e),_=[...l.get(c),...l.get(`*`)];_.length===0&&!m&&s.onUnhandled?.(p);for(let e of _)h(e,p);if(m){let{ctx:e}=n({event:p,bus:g,tracer:s.trace,parent:{ref:r()}}),i=g.commands?.run??f;return t(e,`command`,()=>i(m,u,e))}},use:e=>e(g),context:s.context,trace:s.trace};return g};export{s as i,p as n,u as r,l as t};
1
+ import{n as e}from"./onAbort-BgMfspK4.js";import{a as t,n,r}from"./context-CK0ZomWt.js";import{n as i}from"./promise-CSLpzwSR.js";import{n as a,t as o}from"./event-0RIBGYqS.js";const s=`*`,c=()=>{let e={},t={},n=n=>(t[n]=void 0,e[n]??=/* @__PURE__ */ new Map,e[n]),r=(e,t)=>{let r=n(e),i=r.get(t)??0;i>1?r.set(t,i-1):t?r.delete(t):r.clear()};return{get:n=>(t[n]??=[...e[n]??[]].flatMap(([e,t])=>Array(t).fill(e)),t[n]),add:(e,t)=>{let i=n(e);return i.set(t,(i.get(t)??0)+1),()=>{r(e,t)}},remove:r,clear:()=>{for(let t of Reflect.ownKeys(e))delete e[t];for(let e of Reflect.ownKeys(t))delete t[e]}}},l=Symbol(`nanoeffects.commandCall`),u=(e,t,n)=>{let r={type:t,payload:n};return Object.defineProperty(r,l,{value:e,enumerable:!1}),r},d=e=>{if(typeof e==`function`)return e;if(typeof e==`object`&&e&&l in e)return e[l]},f=(e,t,n)=>Reflect.apply(e,null,[t,n]),p=(s={})=>{let l=c(),u=(e,t)=>l.add(o(e),t),p=(e,t)=>{let n=u(e,(e,r)=>(n(),t(e,r)));return n},m=(e,t)=>{e===void 0?l.clear():l.remove(o(e),t)},h=(t,a)=>{let{onError:o}=s,{ctx:c}=n({event:a,bus:g,tracer:s.trace,parent:{ref:r()}});if(!o){t(a.payload,c);return}let l=t=>{try{o(t,a)}catch(t){e(`onError failed`,t)}};try{let e=t(a.payload,c);i(e)&&Promise.resolve(e).catch(l)}catch(e){l(e)}},g={on:u,once:p,off:m,emit:(e,i)=>{let c=o(e),u=a(e,i),p={type:c,payload:u},m=d(e),_=[...l.get(c),...l.get(`*`)];_.length===0&&!m&&s.onUnhandled?.(p);for(let e of _)h(e,p);if(m){let{ctx:e}=n({event:p,bus:g,tracer:s.trace,parent:{ref:r()}}),i=g.commands?.run??f;return t(e,`command`,()=>i(m,u,e))}},use:e=>e(g),ctx:s.context,trace:s.trace};return g};export{s as i,p as n,u as r,l as t};
@@ -1 +1 @@
1
- import{t as e}from"./onAbort-BgMfspK4.js";const t=t=>{let n=new AbortController;return{controller:n,unlink:e({signal:t},e=>n.abort(e))}},n=typeof __NANOEFFECTS_TRACE__>`u`||__NANOEFFECTS_TRACE__,r=/* @__PURE__ */ new WeakMap;let i;const a=()=>i,o=()=>{},s=(e,t,i,a)=>{let o=n&&r.get(e);o&&o.tracer.report(o.ref,t,i,a)},c=(e,t)=>{let i=n&&r.get(e);return i?i.tracer.settler(i.ref,t):o},l=(e,t,i)=>{let a=n&&r.get(e);return a?a.tracer.run(a.ref,t,e.signal,i):i()},u=(e,t)=>typeof e==`function`?e(t):e,d=e=>e,f=({event:a,bus:s,tracer:c,parent:p})=>{let{controller:m,unlink:h}=t(p?.signal),{signal:g}=m,_=n&&c?{tracer:c,ref:c.task(a,p?.ref)}:void 0,v=(...e)=>{if(g.aborted)return;let t=s?.emit,n=i;i=_?.ref;try{return t?.(...e)}finally{i=n}},y=(...t)=>{if(g.aborted)return o;let n=s?.on,r=n?.(...t);if(!r)return o;let i=e({signal:g},r);return()=>{i(),r()}},b=async(e,...t)=>{g.throwIfAborted();let n=t[0],r=f({event:{type:a.type,payload:n},bus:s,tracer:c,parent:{signal:g,ref:_?.ref}});try{return await l(r.ctx,`run`,()=>e(n,r.ctx))}finally{r.unlink()}},x=d({...u(s?.context,a),type:a.type,payload:a.payload,signal:g,abort:e=>m.abort(e),emit:v,on:y,off:s?.off??o,run:b});return _&&r.set(x,_),{ctx:x,unlink:h}},p=(e,t)=>{let i=n?r.get(e):void 0;return f({event:{type:t??e.type??``,payload:e.payload},bus:{emit:e.emit,on:e.on,off:e.off,context:e},tracer:i?.tracer,parent:{signal:e.signal,ref:i?.ref}})};export{l as a,p as i,f as n,s as o,a as r,c as t};
1
+ import{t as e}from"./onAbort-BgMfspK4.js";const t=t=>{let n=new AbortController;return{controller:n,unlink:e({signal:t},e=>n.abort(e))}},n=typeof __NANOEFFECTS_TRACE__>`u`||__NANOEFFECTS_TRACE__,r=/* @__PURE__ */ new WeakMap;let i;const a=()=>i,o=()=>{},s=(e,t,i,a)=>{let o=n&&r.get(e);o&&o.tracer.report(o.ref,t,i,a)},c=(e,t)=>{let i=n&&r.get(e);return i?i.tracer.settler(i.ref,t):o},l=(e,t,i)=>{let a=n&&r.get(e);return a?a.tracer.run(a.ref,t,e.signal,i):i()},u=(e,t)=>typeof e==`function`?e(t):e,d=e=>e,f=({event:a,bus:s,tracer:c,parent:p})=>{let{controller:m,unlink:h}=t(p?.signal),{signal:g}=m,_=n&&c?{tracer:c,ref:c.task(a,p?.ref)}:void 0,v=(...e)=>{if(g.aborted)return;let t=s?.emit,n=i;i=_?.ref;try{return t?.(...e)}finally{i=n}},y=(...t)=>{if(g.aborted)return o;let n=s?.on,r=n?.(...t);if(!r)return o;let i=e({signal:g},r);return()=>{i(),r()}},b=async(e,...t)=>{g.throwIfAborted();let n=t[0],r=f({event:{type:a.type,payload:n},bus:s,tracer:c,parent:{signal:g,ref:_?.ref}});try{return await l(r.ctx,`run`,()=>e(n,r.ctx))}finally{r.unlink()}},x=d({...u(s?.ctx,a),type:a.type,payload:a.payload,signal:g,abort:e=>m.abort(e),emit:v,on:y,off:s?.off??o,run:b});return s?.addContext?.(x),_&&r.set(x,_),{ctx:x,unlink:h}},p=(e,t)=>{let i=n?r.get(e):void 0;return f({event:{type:t??e.type??``,payload:e.payload},bus:{emit:e.emit,on:e.on,off:e.off,ctx:e},tracer:i?.tracer,parent:{signal:e.signal,ref:i?.ref}})};export{l as a,p as i,f as n,s as o,a as r,c as t};
@@ -0,0 +1,34 @@
1
+ import { B as EventMap, N as WithContext, h as Bus, x as ContextOf } from "./action-creator-c292nK7v.js";
2
+ //#region src/core/context-plugin.d.ts
3
+ /** What a function that makes the fields of a task gets to see. */
4
+ type ContextTask = {
5
+ type: string;
6
+ payload?: unknown;
7
+ };
8
+ /**
9
+ * `unknown` when none of the `fields` is already a context field of the bus,
10
+ * else `never`: the call does not compile. Used as `bus: Bus<E> & NoClash<…>`.
11
+ */
12
+ type NoClash<Fields extends object, E extends EventMap> = [keyof Fields & keyof ContextOf<E>] extends [never] ? unknown : never;
13
+ /**
14
+ * Puts `fields` on the context of every task of the bus: a store, a client, a
15
+ * logger. The types come from the value, so the event map needs no
16
+ * `WithContext`. Returns the same bus, typed with the fields. A function gets
17
+ * `{ type, payload }` of the task and returns its fields.
18
+ *
19
+ * Several uses merge with each other, with the `context` option of `createBus`
20
+ * and with `effectsPlugin`. A name that the bus already has is a type error. The
21
+ * fields of the library (`signal`, `emit`, `run`, …) win, and a field that the
22
+ * context already has is not replaced.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * type Events = { "user/loaded": User };
27
+ *
28
+ * const bus = createBus<Events>().use(contextPlugin({ store }));
29
+ * bus.on("user/loaded", (user, { store }) => store.put(user)); // ctx.store: Store
30
+ * ```
31
+ */
32
+ declare const contextPlugin: <Fields extends object>(fields: Fields | ((task: ContextTask) => Fields)) => <E extends EventMap>(bus: Bus<E> & NoClash<Fields, E>) => Bus<E & WithContext<Fields>>;
33
+ //#endregion
34
+ export { NoClash as n, contextPlugin as r, ContextTask as t };
package/dist/dev.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { _ as Bus, m as UnsubscribeCallback, o as EventMap } from "./action-creator-D48fp13T.js";
2
- import { n as ModuleApi } from "./module-DVX8aYlq.js";
1
+ import { B as EventMap, J as UnsubscribeCallback, h as Bus } from "./action-creator-c292nK7v.js";
2
+ import { n as ModuleApi } from "./module-BlF2KSgy.js";
3
3
  //#region src/dev/types.d.ts
4
4
  /** The parts of what `modulePlugin` returned that the diagnostics report. */
5
5
  type ReportedApp<E extends EventMap> = Pick<ModuleApi<E>, "mount" | "unmount" | "isMounted">;
package/dist/effects.d.ts CHANGED
@@ -1,89 +1,41 @@
1
- import { S as Context, o as EventMap, s as Keys, u as Task } from "./action-creator-D48fp13T.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
1
+ import { B as EventMap, W as Task, u as Effect } from "./action-creator-c292nK7v.js";
2
+ import { a as RaceStarter, c as all, d as delay, i as take, l as EffectContext, n as effectsPlugin, o as race, r as TakeOptions, s as allSettled, u as Starter } from "./plugin-CCYlC0XS.js";
26
3
  //#region src/utils/onAbort.d.ts
27
4
  export declare const onAbort: (ctx: {
28
5
  signal?: AbortSignal | undefined;
29
6
  }, fn: (reason: unknown) => void) => (() => void);
30
7
  //#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;
8
+ //#region src/effects/backoff.d.ts
9
+ type BackoffOptions = {
10
+ /** Pause before the second attempt, in ms. Default 100. */
11
+ base?: number;
12
+ /** How many times each pause is longer than the one before. Default 2. */
13
+ factor?: number;
14
+ /** Longest pause, in ms. Default `Infinity`. */
15
+ max?: number;
16
+ /**
17
+ * Use a random pause from 0 up to the computed one ("full jitter"), so
18
+ * clients that failed together do not retry together. Default `false`.
19
+ */
20
+ jitter?: boolean;
36
21
  };
37
- type Starter<T> = (ctx: EffectContext) => T | PromiseLike<T>;
38
- //#endregion
39
- //#region src/effects/all.d.ts
40
22
  /**
41
- * Runs all starters; waits for every result. On failure or parent abort, cancels the rest.
23
+ * Builds the `delay` of `retry`: the pause grows as `base * factor ** (attempt - 1)`
24
+ * and never passes `max`. `retry` counts attempts from 1; `poll` counts calls from
25
+ * 0, so there the first pause is `base / factor`.
42
26
  *
43
27
  * @example
44
28
  * ```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
- * });
29
+ * bus.on(
30
+ * "load",
31
+ * retry(loadUser, {
32
+ * times: 5,
33
+ * delay: backoff({ base: 200, max: 5000, jitter: true }), // 200, 400, 800, ... up to 5 s, randomised
34
+ * }),
35
+ * );
79
36
  * ```
80
37
  */
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]>>>; }>;
38
+ export declare const backoff: (options?: BackoffOptions) => (attempt: number) => number;
87
39
  //#endregion
88
40
  //#region src/effects/cancellable.d.ts
89
41
  type CancellableHandle<T> = {
@@ -160,32 +112,6 @@ export declare function poll(ctx: {
160
112
  signal: AbortSignal;
161
113
  }, fn: (tick: number) => unknown, ms: PollMs, options?: PollOptions): Promise<void>;
162
114
  //#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
115
  //#region src/effects/retry.d.ts
190
116
  type RetryOptions = {
191
117
  /** Total number of attempts, the first one included. Default 3. */
@@ -226,34 +152,6 @@ export declare function retry<T>(ctx: {
226
152
  signal?: AbortSignal;
227
153
  }, fn: RetryFn<T>, options?: RetryOptions): Promise<T>;
228
154
  //#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
155
  //#region src/effects/timeout.d.ts
258
156
  /**
259
157
  * Runs a function; if it takes longer than `ms`, aborts its signal and rejects
@@ -281,4 +179,4 @@ export declare function timeout<T>(ctx: {
281
179
  signal?: AbortSignal;
282
180
  }, ms: number, starter: Starter<T>): Promise<T>;
283
181
  //#endregion
284
- export type { CancellableExecutor, CancellableHandle, EffectContext, PollOptions, RaceStarter, RetryOptions, TakeOptions };
182
+ export { type BackoffOptions, type CancellableExecutor, type CancellableHandle, type Effect, type EffectContext, type PollOptions, type RaceStarter, type RetryOptions, type TakeOptions, all, allSettled, delay, effectsPlugin, race, take };