nanoeffects 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +95 -2
- package/dist/action-BM-UdmJr.d.ts +302 -0
- package/dist/actions.d.ts +196 -0
- package/dist/actions.js +1 -0
- package/dist/bus-RUiw8f9p.js +1 -0
- package/dist/context-BXQ6Einm.js +1 -0
- package/dist/delay-CIHnOh_L.js +1 -0
- package/dist/effects.d.ts +244 -0
- package/dist/effects.js +1 -0
- package/dist/event-0RIBGYqS.js +1 -0
- package/dist/index-CaxrMD1A.d.ts +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/notrace/action-BM-UdmJr.d.ts +302 -0
- package/dist/notrace/actions.d.ts +196 -0
- package/dist/notrace/actions.js +1 -0
- package/dist/notrace/bus-CFxgpsRL.js +1 -0
- package/dist/notrace/context-DGPIFXxI.js +1 -0
- package/dist/notrace/delay-Cko_Q9hl.js +1 -0
- package/dist/notrace/effects.d.ts +244 -0
- package/dist/notrace/effects.js +1 -0
- package/dist/notrace/event-0RIBGYqS.js +1 -0
- package/dist/notrace/index-CaxrMD1A.d.ts +1 -0
- package/dist/notrace/index.d.ts +3 -0
- package/dist/notrace/index.js +1 -0
- package/dist/notrace/policies.d.ts +171 -0
- package/dist/notrace/policies.js +1 -0
- package/dist/notrace/promise-CSLpzwSR.js +1 -0
- package/dist/notrace/redux.d.ts +92 -0
- package/dist/notrace/redux.js +1 -0
- package/dist/observable.d.ts +20 -0
- package/dist/observable.js +1 -0
- package/dist/onAbort-BgMfspK4.js +1 -0
- package/dist/policies.d.ts +171 -0
- package/dist/policies.js +1 -0
- package/dist/promise-CSLpzwSR.js +1 -0
- package/dist/redux.d.ts +92 -0
- package/dist/redux.js +1 -0
- package/dist/trace.d.ts +144 -0
- package/dist/trace.js +1 -0
- package/docs/README.md +12 -0
- package/docs/actions.md +68 -0
- package/docs/context.md +16 -0
- package/docs/effects.md +119 -0
- package/docs/errors.md +23 -0
- package/docs/hmr.md +74 -0
- package/docs/observable.md +22 -0
- package/docs/policies.md +53 -0
- package/docs/semantics.md +7 -0
- package/docs/stores.md +50 -0
- package/docs/tracing.md +104 -0
- package/package.json +164 -5
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { L as UnsubscribeCallback, N as Task, c as Command, j as PayloadOf, k as EventMap, m as Emittable, n as FreeActionCreator, o as Bus, r as createAction, t as ActionCreator, u as Context } from "./action-BM-UdmJr.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
|
+
export 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
|
+
export 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, for `createBus({ commands })`. Each bus gets
|
|
47
|
+
* its own, so each test can have its own implementation. `handle` binds more
|
|
48
|
+
* later and returns the function that unbinds.
|
|
49
|
+
*
|
|
50
|
+
* What the bus runs for a command: the handler bound last, else the one the
|
|
51
|
+
* command was created with, else it throws `No handler for command "..."`.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* const commands = createCommands([bindHandler(sendMail, smtpSend)]);
|
|
56
|
+
* const bus = createBus<Events>({ commands });
|
|
57
|
+
*
|
|
58
|
+
* const unbind = commands.handle(getUser, async () => fakeUser);
|
|
59
|
+
* unbind(); // the command's own handler is back
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare const createCommands: <E extends EventMap = EventMap>(bindings?: readonly Binding<E>[]) => {
|
|
63
|
+
run: (command: Command, payload: unknown, ctx: unknown) => unknown;
|
|
64
|
+
handle: {
|
|
65
|
+
<P, R>(command: Command<P, R>, fn: Task<P, E, R>): UnsubscribeCallback;
|
|
66
|
+
(type: string, fn: Task<never, E, unknown>): UnsubscribeCallback;
|
|
67
|
+
(bindings: readonly Binding<E>[]): UnsubscribeCallback;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/actions/job.d.ts
|
|
72
|
+
/**
|
|
73
|
+
* Creates a command whose outcome is sent on: when the handler succeeds, its
|
|
74
|
+
* result goes to `done`, and when it fails, the error goes to `fail`. Each is
|
|
75
|
+
* anything `emit` takes as its first argument, and receives the result (or the
|
|
76
|
+
* error) as the payload: an event type, an action creator, a command, or a
|
|
77
|
+
* plain action. It is made for the case where nobody waits for the result and
|
|
78
|
+
* the bus carries it on, like `createAsyncThunk` or an effector effect.
|
|
79
|
+
*
|
|
80
|
+
* `bus.emit(job, payload)` returns a promise of the result. When `fail` is set
|
|
81
|
+
* the error counts as handled: it goes to `fail` and the promise resolves with
|
|
82
|
+
* `undefined`; without `fail` the promise rejects. The outcome is routed by the
|
|
83
|
+
* bus, also for a handler bound with `commands.handle`, and is skipped once the
|
|
84
|
+
* task is aborted. Calling the job directly runs only its handler.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* const loaded = createAction<User>("user/loaded");
|
|
89
|
+
* const failed = createAction<Error>("user/failed");
|
|
90
|
+
*
|
|
91
|
+
* const loadUser = createJob("user/load", fetchUser, { done: loaded, fail: failed });
|
|
92
|
+
*
|
|
93
|
+
* bus.on(loaded, (user) => render(user));
|
|
94
|
+
* bus.emit(loadUser, "1"); // loaded(user) or failed(error) comes later
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export declare const createJob: <P = void, R = void, T extends string = string, E extends EventMap = EventMap>(type: T, handler?: Task<P, E, R>, outcomes?: {
|
|
98
|
+
/** Gets the result when the handler succeeds. */
|
|
99
|
+
done?: Emittable<E>;
|
|
100
|
+
/** Gets the error when the handler fails. */
|
|
101
|
+
fail?: Emittable<E>;
|
|
102
|
+
}) => Command<P, Promise<Awaited<R> | undefined>> & {
|
|
103
|
+
readonly type: T;
|
|
104
|
+
};
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/actions/module.d.ts
|
|
107
|
+
/**
|
|
108
|
+
* What a module's `setup` gets: the context of the module's task, so `on`,
|
|
109
|
+
* `emit` and `run` work as in a handler, plus `handle` to bind commands, `use`
|
|
110
|
+
* to mount sub-modules and `spawn` to start a background task. Everything the
|
|
111
|
+
* module does is undone when it is unmounted.
|
|
112
|
+
*/
|
|
113
|
+
type ModuleContext<E extends EventMap = EventMap> = Context<E> & {
|
|
114
|
+
/** Binds the handler of a command, or a list of them; undone on unmount. */
|
|
115
|
+
handle<P, R>(command: Command<P, R>, fn: Task<P, E, R>): UnsubscribeCallback;
|
|
116
|
+
handle(type: string, fn: Task<never, E, unknown>): UnsubscribeCallback;
|
|
117
|
+
handle(bindings: readonly Binding<E>[]): UnsubscribeCallback;
|
|
118
|
+
/**
|
|
119
|
+
* Mounts sub-modules; they are unmounted with this one. The returned function
|
|
120
|
+
* unmounts just these, e.g. when they were added later from a listener.
|
|
121
|
+
*/
|
|
122
|
+
use(...modules: Module<E>[]): UnsubscribeCallback;
|
|
123
|
+
/**
|
|
124
|
+
* Starts a background task that lives as long as the module: it is aborted
|
|
125
|
+
* on unmount, and an error of it is left unhandled, as in any handler.
|
|
126
|
+
*/
|
|
127
|
+
spawn<P>(fn: Task<P, E, unknown>, ...payload: PayloadOf<P>): void;
|
|
128
|
+
};
|
|
129
|
+
type Module<E extends EventMap = EventMap> = {
|
|
130
|
+
readonly name: string;
|
|
131
|
+
readonly setup: (m: ModuleContext<E>) => void;
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Describes one domain of an app: the commands it handles, the events it
|
|
135
|
+
* listens to, its sub-modules and its background tasks. The `name` shows up as
|
|
136
|
+
* the type of the module's tasks in the trace.
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```ts
|
|
140
|
+
* export const userModule = createModule<Events>("user", (m) => {
|
|
141
|
+
* m.handle(getUser, fetchUser);
|
|
142
|
+
* m.handle([bindHandler(sendMail, smtpSend)]);
|
|
143
|
+
* m.use(authModule);
|
|
144
|
+
*
|
|
145
|
+
* m.on(loginRequested, (user, ctx) => ctx.emit(userLoaded(user)));
|
|
146
|
+
* m.spawn(async (_, ctx) => {
|
|
147
|
+
* for (;;) await handleNext(await take(ctx, "job"));
|
|
148
|
+
* });
|
|
149
|
+
* });
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
export declare const createModule: <E extends EventMap = EventMap>(name: string, setup: (m: ModuleContext<E>) => void) => Module<E>;
|
|
153
|
+
type Registry<E extends EventMap> = ReturnType<typeof createCommands<E>>;
|
|
154
|
+
/** What `registerModules` returns. */
|
|
155
|
+
type ModuleApi<E extends EventMap = EventMap> = {
|
|
156
|
+
/** The registry of command handlers of the bus: the one it was made with, or the app's own. */
|
|
157
|
+
commands: Registry<E>;
|
|
158
|
+
/** Mounts a module and returns the function that unmounts it; a module that is mounted already is not run again. */
|
|
159
|
+
mount(module: Module<E>): UnsubscribeCallback;
|
|
160
|
+
unmount(module: Module<E>): void;
|
|
161
|
+
isMounted(module: Module<E>): boolean;
|
|
162
|
+
/** Unmounts every module; a disposed app cannot mount modules. */
|
|
163
|
+
dispose(): void;
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Assembles an app from modules on `bus`: mounts `modules` at once, in the
|
|
167
|
+
* order given, and returns `mount`, `unmount`, `isMounted` and `dispose` for
|
|
168
|
+
* the modules that come and go later. The bus runs the commands that the
|
|
169
|
+
* modules bind: if it was made with `commands: createCommands()` that registry
|
|
170
|
+
* is used, otherwise the app brings its own, as `app.commands`.
|
|
171
|
+
*
|
|
172
|
+
* A module is known by its `name`: mounting a module whose name is taken
|
|
173
|
+
* replaces the mounted one, which is unmounted first. That is what hot reload
|
|
174
|
+
* needs (see `docs/hmr.md`): the new version of a module file mounts again and
|
|
175
|
+
* the old version goes away with its subscriptions, commands and tasks.
|
|
176
|
+
*
|
|
177
|
+
* Unmounting undoes everything the module did: its subscriptions, its bound
|
|
178
|
+
* commands, its sub-modules and its background tasks. `dispose` unmounts them
|
|
179
|
+
* all, and a disposed app cannot mount modules.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* const bus = createBus<Events>();
|
|
184
|
+
* const app = registerModules(bus, [userModule]);
|
|
185
|
+
*
|
|
186
|
+
* const unmount = app.mount(orderModule); // later, e.g. after a lazy import
|
|
187
|
+
* unmount(); // or app.unmount(orderModule)
|
|
188
|
+
*
|
|
189
|
+
* bus.emit(getUser, "1");
|
|
190
|
+
* app.commands.handle(getUser, fakeUser); // in a test
|
|
191
|
+
* app.dispose();
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
export declare const registerModules: <E extends EventMap>(bus: Bus<E>, modules?: readonly Module<E>[]) => ModuleApi<E>;
|
|
195
|
+
//#endregion
|
|
196
|
+
export { type ActionCreator, type Binding, type FreeActionCreator, type Module, type ModuleApi, type ModuleContext, createAction };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{i as e,n as t,s as n}from"./context-DGPIFXxI.js";import{t as r}from"./event-0RIBGYqS.js";function i(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 a=class extends Error{};const o=(e,n)=>Object.assign((r,i)=>{if(!n)throw new a(`No handler for command "${e}"`);return n(r,i??t({type:e,payload:r}).ctx)},{type:e}),s=(e,t)=>[e,t],c=(e=[])=>{let t=/* @__PURE__ */ new Map,n=(e,n)=>{let r={fn:n};return t.set(e,[...t.get(e)??[],r]),()=>{let n=(t.get(e)??[]).filter(e=>e!==r);n.length>0?t.set(e,n):t.delete(e)}};function i(e,t){if(t)return n(r(e),t);let i=e.map(([e,t])=>n(r(e),t));return()=>{for(let e of i)e()}}return i(e),{run:(e,n,r)=>{let i=(t.get(e.type)??[]).at(-1)?.fn,a=i&&e.wrap?e.wrap(i):i??e;return Reflect.apply(a,null,[n,r])},handle:i}},l=(e,t,n)=>{let r=e.emit;typeof t==`string`?r(t,n):typeof t==`function`?r(`match`in t?t.type:t,n):r({type:t.type,payload:n})},u=e=>{try{return Promise.resolve(e())}catch(e){if(e instanceof a)throw e;return Promise.reject(e)}},d=(e,t,n={})=>{let r=o(e,t),i=(e,t,r)=>{let i=r;return u(()=>Reflect.apply(e,null,[t,r])).then(e=>(n.done&&l(i,n.done,e),e),e=>{if(!n.fail)throw e;l(i,n.fail,e)})};return Object.assign((e,t)=>t?i(r,e,t):u(()=>r(e)),{type:e,wrap:e=>(t,n)=>i(e,t,n)})},f=(e,t)=>({name:e,setup:t}),p=(r,i=[])=>{let a=r.commands??c();r.commands??=a;let o={handle:(...e)=>{if(!a.handle)throw Error("registerModules: the `commands` of the bus is not a registry from `createCommands()`");return Reflect.apply(a.handle,null,e)}},s=t({type:`app`,payload:void 0},r,r.trace),l=t=>{let r=/* @__PURE__ */ new Map,i=/* @__PURE__ */ new Map;return{mount:a=>{let s=r.get(a);if(s)return s;let c=i.get(a.name);c&&r.get(c)?.();let u=e(t,a.name),d=u.ctx,f=l(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=(e,t)=>{let r=Reflect.apply(o.handle,null,[e,t]);return n(d,r),r},h=(...e)=>{let t=e.map(f.mount);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=l(s.ctx);for(let e of i)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{s as bindHandler,i as createAction,o as createCommand,c as createCommands,d as createJob,f as createModule,p as registerModules};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as e,c as t,n,r}from"./context-DGPIFXxI.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=/* @__PURE__ */ new WeakSet,n=n=>{let r=e[n]??[];if(!t.has(r))return e[n]=r,r;let i=[...r];return e[n]=i,i},r=(t,r)=>{if(!r){e[t]=[];return}let i=(e[t]??[]).indexOf(r);i>=0&&n(t).splice(i,1)};return{get:n=>{let r=e[n]??[];return t.add(r),r},add:(e,t)=>(n(e).push(t),()=>{r(e,t)}),remove:r}},l=(e,t,n)=>Reflect.apply(e,null,[t,n]),u=(s={})=>{let u=c(),d=(e,t)=>u.add(o(e),t),f=(e,t)=>u.remove(o(e),t),p=(e,a)=>{let{onError:o}=s,{ctx:c}=n(a,m,s.trace,{ref:r()});if(!o){e(a.payload,c);return}let l=e=>{try{o(e,a)}catch(e){t(`onError failed`,e)}};try{let t=e(a.payload,c);i(t)&&Promise.resolve(t).catch(l)}catch(e){l(e)}},m={on:d,off:f,emit:(t,i)=>{let c=o(t),d=a(t,i),f={type:c,payload:d},h=[...u.get(c),...u.get(`*`)];h.length===0&&typeof t!=`function`&&s.onUnhandled?.(f);for(let e of h)p(e,f);if(typeof t==`function`){let{ctx:i}=n(f,m,s.trace,{ref:r()}),a=m.commands?.run??l;return e(i,`command`,()=>a(t,d,i))}},commands:s.commands,context:s.context,trace:s.trace};return m};export{s as n,u as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=(e,t)=>{globalThis.console?.error(`[nanoeffects] ${e}`,t)},t=()=>{},n=/* @__PURE__ */ new WeakMap,r=t=>{let r=n.get(t);if(r)return r;let i=/* @__PURE__ */ new Set;return n.set(t,i),t.addEventListener(`abort`,()=>{let n=[...i];i.clear();for(let{fn:r}of n)try{r(t.reason)}catch(t){e(`abort callback failed`,t)}},{once:!0}),i},i=(e,n)=>{let{signal:i}=e;if(!i)return t;if(i.aborted)return n(i.reason),t;let a=r(i),o={fn:n};return a.add(o),()=>{a.delete(o)}},a=e=>{let t=new AbortController;return{controller:t,unlink:i({signal:e},e=>t.abort(e))}};let o;const s=()=>o,c=()=>{},l=(e,t,n,r)=>{},u=(e,t)=>c,d=(e,t,n)=>n(),f=(e,t)=>typeof e==`function`?e(t):e,p=e=>e,m=(e,t,n,r)=>{let{controller:s,unlink:l}=a(r?.signal),{signal:u}=s,h=(...e)=>{if(u.aborted)return;let n=t?.emit,r=o;o=void 0;try{return n?.(...e)}finally{o=r}},g=(...e)=>{if(u.aborted)return c;let n=t?.on,r=n?.(...e);if(!r)return c;let a=i({signal:u},r);return()=>{a(),r()}},_=async(r,...i)=>{u.throwIfAborted();let a=i[0],o=m({type:e.type,payload:a},t,n,{signal:u,ref:void 0});try{return await d(o.ctx,`run`,()=>r(a,o.ctx))}finally{o.unlink()}};return{ctx:p({...f(t?.context,e),type:e.type,payload:e.payload,signal:u,abort:e=>s.abort(e),emit:h,on:g,off:t?.off??c,run:_}),unlink:l}},h=(e,t)=>m({type:t??e.type??``,payload:e.payload},{emit:e.emit,on:e.on,off:e.off,context:e},void 0,{signal:e.signal,ref:void 0});export{d as a,e as c,h as i,m as n,l as o,s as r,i as s,u as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{s as e}from"./context-DGPIFXxI.js";const t=2**31-1,n=(e,n)=>{let r={id:null};if(n===1/0)return r;let i=n=>{r.id=setTimeout(()=>{n>t?i(n-t):(r.id=null,e())},Math.min(n,t))};return i(n>0?n:0),r},r=e=>{e.id!==null&&(clearTimeout(e.id),e.id=null)},i=(t,i)=>new Promise((a,o)=>{let s=n(()=>{c(),a()},typeof i==`function`?i():i),c=e(t,e=>{r(s),o(e)})});export{r as n,n as r,i as t};
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { A as Keys, k as EventMap, u as Context } from "./action-BM-UdmJr.js";
|
|
2
|
+
//#region src/utils/delay.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Resolves after `ms`, or rejects early if `ctx.signal` aborts. `ms` can be a
|
|
5
|
+
* function that returns the number of ms, called once when the delay starts:
|
|
6
|
+
* handy for a random pause (jitter).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* await delay({}, 500);
|
|
11
|
+
*
|
|
12
|
+
* // 200 to 400 ms
|
|
13
|
+
* await delay({}, () => 200 + Math.random() * 200);
|
|
14
|
+
*
|
|
15
|
+
* // inside a task: stops waiting when the task is aborted
|
|
16
|
+
* bus.on("retry", async (_, ctx) => {
|
|
17
|
+
* await delay(ctx, 1000);
|
|
18
|
+
* await load();
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare const delay: (ctx: {
|
|
23
|
+
signal?: AbortSignal | undefined;
|
|
24
|
+
}, ms: number | (() => number)) => Promise<void>;
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/utils/onAbort.d.ts
|
|
27
|
+
export declare const onAbort: (ctx: {
|
|
28
|
+
signal?: AbortSignal | undefined;
|
|
29
|
+
}, fn: (reason: unknown) => void) => (() => void);
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/effects/run.d.ts
|
|
32
|
+
/** Context passed into `race` / `all` effect starters. */
|
|
33
|
+
type EffectContext = {
|
|
34
|
+
signal: AbortSignal;
|
|
35
|
+
abort: (reason?: unknown) => void;
|
|
36
|
+
};
|
|
37
|
+
type Starter<T> = (ctx: EffectContext) => T | PromiseLike<T>;
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/effects/all.d.ts
|
|
40
|
+
/**
|
|
41
|
+
* Runs all starters; waits for every result. On failure or parent abort, cancels the rest.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* bus.on("load", async (id, ctx) => {
|
|
46
|
+
* const { user, posts } = await all(ctx, {
|
|
47
|
+
* user: ({ signal }) => fetchUser(id, { signal }),
|
|
48
|
+
* posts: ({ signal }) => fetchPosts(id, { signal }),
|
|
49
|
+
* });
|
|
50
|
+
*
|
|
51
|
+
* // arrays work too: const [user, posts] = await all(ctx, [getUser, getPosts]);
|
|
52
|
+
* });
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function all<T extends Record<string, Starter<unknown>>>(ctx: {
|
|
56
|
+
signal?: AbortSignal;
|
|
57
|
+
}, effects: T): Promise<{ [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
|
|
58
|
+
export declare function all<const T extends readonly Starter<unknown>[]>(ctx: {
|
|
59
|
+
signal?: AbortSignal;
|
|
60
|
+
}, effects: T): Promise<{ -readonly [K in keyof T]: Awaited<ReturnType<T[K]>>; }>;
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/effects/allSettled.d.ts
|
|
63
|
+
/**
|
|
64
|
+
* Runs all starters and waits for every one to settle, whether it succeeds or
|
|
65
|
+
* fails: nothing is cancelled when a starter fails. The result has, for each
|
|
66
|
+
* starter, `{ status: "fulfilled", value }` or `{ status: "rejected", reason }`.
|
|
67
|
+
* Only an abort of `ctx` rejects it, with the abort reason.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* bus.on("load", async (id, ctx) => {
|
|
72
|
+
* const { user, posts } = await allSettled(ctx, {
|
|
73
|
+
* user: ({ signal }) => fetchUser(id, { signal }),
|
|
74
|
+
* posts: ({ signal }) => fetchPosts(id, { signal }),
|
|
75
|
+
* });
|
|
76
|
+
*
|
|
77
|
+
* if (user.status === "fulfilled") render(user.value);
|
|
78
|
+
* });
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function allSettled<T extends Record<string, Starter<unknown>>>(ctx: {
|
|
82
|
+
signal?: AbortSignal;
|
|
83
|
+
}, effects: T): Promise<{ [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
|
|
84
|
+
export declare function allSettled<const T extends readonly Starter<unknown>[]>(ctx: {
|
|
85
|
+
signal?: AbortSignal;
|
|
86
|
+
}, effects: T): Promise<{ -readonly [K in keyof T]: PromiseSettledResult<Awaited<ReturnType<T[K]>>>; }>;
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/effects/cancellable.d.ts
|
|
89
|
+
type CancellableHandle<T> = {
|
|
90
|
+
resolve: (value: T | PromiseLike<T>) => void;
|
|
91
|
+
reject: (reason?: unknown) => void;
|
|
92
|
+
/** Registers a cleanup that runs if the promise is cancelled before it settles. */
|
|
93
|
+
onCancel: (cleanup: () => void) => void;
|
|
94
|
+
};
|
|
95
|
+
type CancellableExecutor<T> = (handle: CancellableHandle<T>) => void;
|
|
96
|
+
/**
|
|
97
|
+
* A promise tied to `ctx.signal`. It works like `new Promise`, but the
|
|
98
|
+
* executor gets one object, `{ resolve, reject, onCancel }`. If the signal
|
|
99
|
+
* aborts before the promise settles, the cleanups run (last registered first)
|
|
100
|
+
* and the promise rejects with the abort reason. If the signal is already
|
|
101
|
+
* aborted, the executor does not run. After the promise settles, the abort
|
|
102
|
+
* listener is removed and the cleanups are dropped.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* bus.on(
|
|
107
|
+
* "wait",
|
|
108
|
+
* latest((_, ctx) =>
|
|
109
|
+
* cancellable(ctx, ({ resolve, onCancel }) => {
|
|
110
|
+
* const timer = setTimeout(resolve, 5000);
|
|
111
|
+
* onCancel(() => clearTimeout(timer));
|
|
112
|
+
* }),
|
|
113
|
+
* ),
|
|
114
|
+
* );
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export declare const cancellable: <T = void>(ctx: {
|
|
118
|
+
signal: AbortSignal;
|
|
119
|
+
}, executor: CancellableExecutor<T>) => Promise<T>;
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/effects/poll.d.ts
|
|
122
|
+
type PollOptions = {
|
|
123
|
+
/** Also call `fn` at once, not only after the first `ms`. Default `false`. */
|
|
124
|
+
leading?: boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Start the pause after a call finishes, so calls never overlap. By default
|
|
127
|
+
* the calls come every `ms` whatever the duration of `fn`.
|
|
128
|
+
*/
|
|
129
|
+
waitForFn?: boolean;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Calls `fn` every `ms`, like patronum `interval`: the first call comes after
|
|
133
|
+
* `ms` (with `leading` also at once), and the next ticks keep a steady rate
|
|
134
|
+
* whatever the duration of `fn`, so a slow `fn` may overlap with the next call
|
|
135
|
+
* (`waitForFn` starts each pause when the call ends instead). `fn` gets the
|
|
136
|
+
* number of the call, starting at 0.
|
|
137
|
+
*
|
|
138
|
+
* `ms` can be a function of the number of the call that the pause leads to, for
|
|
139
|
+
* a growing or random pause: `(call) => Math.min(1000 * 2 ** call, 60_000)`.
|
|
140
|
+
*
|
|
141
|
+
* It stops when `ctx` aborts: the result then resolves, it does not reject, and
|
|
142
|
+
* the pending timer is cleared. If a call throws, polling stops and the result
|
|
143
|
+
* rejects.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```ts
|
|
147
|
+
* bus.on("watch", async (_, ctx) => {
|
|
148
|
+
* await poll(ctx, () => refresh(), 5000);
|
|
149
|
+
* cleanup(); // the task was aborted
|
|
150
|
+
* });
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
export declare const poll: (ctx: {
|
|
154
|
+
signal: AbortSignal;
|
|
155
|
+
}, fn: (tick: number) => unknown, ms: number | ((call: number) => number), options?: PollOptions) => Promise<void>;
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region src/effects/race.d.ts
|
|
158
|
+
type RaceStarter<T> = Starter<T>;
|
|
159
|
+
/**
|
|
160
|
+
* Runs all starters; first to settle wins, losers are cancelled via `ctx.abort`.
|
|
161
|
+
* The result holds only the winner's value, under its key or index.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```ts
|
|
165
|
+
* bus.on("load", async (id, ctx) => {
|
|
166
|
+
* const result = await race(ctx, {
|
|
167
|
+
* data: ({ signal }) => fetchData(id, { signal }),
|
|
168
|
+
* timeout: ({ signal }) => delay({ signal }, 5000),
|
|
169
|
+
* });
|
|
170
|
+
*
|
|
171
|
+
* if ("timeout" in result) throw new Error("timed out");
|
|
172
|
+
* render(result.data);
|
|
173
|
+
* });
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
export declare function race<T extends Record<string, RaceStarter<unknown>>>(ctx: {
|
|
177
|
+
signal?: AbortSignal;
|
|
178
|
+
}, effects: T): Promise<{ [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
|
|
179
|
+
export declare function race<const T extends readonly RaceStarter<unknown>[]>(ctx: {
|
|
180
|
+
signal?: AbortSignal;
|
|
181
|
+
}, effects: T): Promise<{ -readonly [K in keyof T]?: Awaited<ReturnType<T[K]>>; }>;
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region src/effects/retry.d.ts
|
|
184
|
+
type RetryOptions = {
|
|
185
|
+
/** Total number of attempts, the first one included. Default 3. */
|
|
186
|
+
times?: number;
|
|
187
|
+
/**
|
|
188
|
+
* Pause before the next attempt, in ms: a number, or a function of the
|
|
189
|
+
* number of the attempt that just failed and its error. Default 0.
|
|
190
|
+
*/
|
|
191
|
+
delay?: number | ((attempt: number, error: unknown) => number);
|
|
192
|
+
/** Return `false` to stop retrying and throw `error`. Default: always retry. */
|
|
193
|
+
shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
194
|
+
};
|
|
195
|
+
export declare const retry: <T>(ctx: {
|
|
196
|
+
signal?: AbortSignal;
|
|
197
|
+
}, fn: (attempt: number) => T | PromiseLike<T>, options?: RetryOptions) => Promise<T>;
|
|
198
|
+
//#endregion
|
|
199
|
+
//#region src/effects/take.d.ts
|
|
200
|
+
type TakeOptions<P> = {
|
|
201
|
+
/** Wait for the first payload this accepts; others are skipped. */
|
|
202
|
+
predicate?: (payload: P) => boolean;
|
|
203
|
+
/** Stop waiting when this aborts, e.g. the signal of a `race` starter. */
|
|
204
|
+
signal?: AbortSignal | undefined;
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Waits for the next `event` on the bus and resolves with its payload. It sees
|
|
208
|
+
* only events emitted after the call, takes only one, and unsubscribes when it
|
|
209
|
+
* is done. If `ctx` (or `options.signal`) aborts first, it unsubscribes and
|
|
210
|
+
* rejects with the abort reason.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```ts
|
|
214
|
+
* bus.on("login", async (user, ctx) => {
|
|
215
|
+
* const reason = await take(ctx, "logout");
|
|
216
|
+
* cleanup(user, reason);
|
|
217
|
+
* });
|
|
218
|
+
*
|
|
219
|
+
* // wait for a matching event, but not longer than 5 s
|
|
220
|
+
* const done = await timeout(ctx, 5000, ({ signal }) =>
|
|
221
|
+
* take(ctx, "saved", { signal, predicate: (id) => id === draftId }),
|
|
222
|
+
* );
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
export declare const take: <E extends EventMap, K extends Keys<E>>(ctx: Context<E>, event: K, options?: TakeOptions<E[K]>) => Promise<E[K]>;
|
|
226
|
+
//#endregion
|
|
227
|
+
//#region src/effects/timeout.d.ts
|
|
228
|
+
/**
|
|
229
|
+
* Runs `starter`; if it takes longer than `ms`, aborts its signal and rejects
|
|
230
|
+
* with a `TimeoutError` (a `DOMException`, like `AbortSignal.timeout`), even
|
|
231
|
+
* if the starter ignores the signal. If `ctx` aborts, rejects with its reason.
|
|
232
|
+
*
|
|
233
|
+
* @example
|
|
234
|
+
* ```ts
|
|
235
|
+
* bus.on("load", async (id, ctx) => {
|
|
236
|
+
* const data = await timeout(ctx, 5000, ({ signal }) => fetchData(id, { signal }));
|
|
237
|
+
* });
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
export declare const timeout: <T>(ctx: {
|
|
241
|
+
signal?: AbortSignal;
|
|
242
|
+
}, ms: number, starter: Starter<T>) => Promise<T>;
|
|
243
|
+
//#endregion
|
|
244
|
+
export type { CancellableExecutor, CancellableHandle, EffectContext, PollOptions, RaceStarter, RetryOptions, TakeOptions };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as e,c as t,i as n,o as r,s as i,t as a}from"./context-DGPIFXxI.js";import{n as o,r as s,t as c}from"./delay-Cko_Q9hl.js";const l=(e,t)=>t?.signal?new Promise((n,r)=>{let a=i(t,r);e.then(n,r).finally(a)}):e,u=e=>Array.isArray(e)?Array(e.length):{},d=(t,o,s)=>{s?.signal?.throwIfAborted();let c=n(s??{}),u=a(c.ctx,o);return r(c.ctx,o,`start`),i(c.ctx,()=>u(`cancel`)),{tasks:Object.entries(t).map(([t,r])=>{let i=n(c.ctx),a={signal:i.ctx.signal,abort:i.ctx.abort};return[t,new Promise(t=>t(e(i.ctx,`run`,()=>r(a)))).finally(i.unlink)]}),cancelAll:()=>c.ctx.abort(),unfollowParent:c.unlink,settle:u,until:e=>l(e,s)}};async function f(e,t){let n=d(t,`all`,e);try{let e=await n.until(Promise.all(n.tasks.map(([,e])=>e))),r=u(t);for(let[t,[i]]of n.tasks.entries())r[i]=e[t];return n.settle(`done`),r}catch(e){throw n.settle(`error`,e),n.cancelAll(),e}finally{n.unfollowParent()}}const p=e=>e.then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}));async function m(e,t){let n=d(t,`allSettled`,e);try{let e=await n.until(Promise.all(n.tasks.map(([,e])=>p(e)))),r=u(t);for(let[t,[i]]of n.tasks.entries())r[i]=e[t];return n.settle(`done`),r}catch(e){throw n.settle(`error`,e),n.cancelAll(),e}finally{n.unfollowParent()}}const h=(e,n)=>e.signal.aborted?Promise.reject(e.signal.reason):new Promise((r,a)=>{let o=[],s=`pending`,c=e=>{try{e()}catch(e){t(`cancel cleanup failed`,e)}},l=i(e,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{n({resolve:u(r),reject:u(a),onCancel:d})}catch(e){u(a)(e)}}),g=async(e,t,o)=>{t?.signal?.throwIfAborted();let{ctx:s,unlink:c}=n(t??{}),u=a(s,e);r(s,e,`start`),i(s,()=>u(`cancel`));try{let e=await l(o(s),t);return u(`done`),e}catch(e){throw u(`error`,e),e}finally{c()}},_=async(t,r)=>{let{ctx:i,unlink:a}=n(t);try{return await e(i,`run`,r)}finally{a()}},v=(e,t,n,r={})=>{if(e.signal.aborted)return Promise.resolve();let a=r.leading??!1,c=r.waitForFn??!1;return g(`poll`,e,e=>new Promise((r,l)=>{let u=0,d=null,f=()=>{d!==null&&(o(d),d=null)},p=e=>{try{d=s(m,typeof n==`function`?n(e):n)}catch(e){f(),l(e)}},m=()=>{d=null,c||p(u+1),h()},h=()=>{let n=u++;_(e,()=>t(n)).then(()=>{c&&!e.signal.aborted&&p(u)},e=>{f(),l(e)})};i(e,f),a?(h(),c||p(u)):p(0)})).catch(t=>{if(!e.signal.aborted)throw t})};async function y(e,t){let n=u(t);if(Object.keys(t).length===0)return n;let r=d(t,`race`,e);try{let[e,t]=await r.until(Promise.race(r.tasks.map(([e,t])=>t.then(t=>[e,t]))));return n[e]=t,r.settle(`done`),n}catch(e){throw r.settle(`error`,e),e}finally{r.unfollowParent(),r.cancelAll()}}const b=(e,t,n)=>typeof e==`function`?e(t,n):e,x=e=>({times:e.times??3,delay:e.delay??0,shouldRetry:e.shouldRetry??(()=>!0)}),S=(e,t,n={})=>g(`retry`,e,async e=>{let{times:r,delay:i,shouldRetry:a}=x(n);for(let n=1;;n++)try{return await _(e,()=>t(n))}catch(t){if(e.signal.throwIfAborted(),n>=r||!a(t,n))throw t;let o=b(i,n,t);o>0&&await c(e,o)}}),C=(e,t,n={})=>g(`take`,e,r=>new Promise((a,o)=>{let{predicate:s}=n,c=[],l=()=>{for(let e of c)e()},u=e=>{l(),o(e)};c.push(e.on(t,e=>{(!s||s(e))&&(l(),a(e))})),c.push(i(r,u)),c.push(i({signal:n.signal},u))})),w=(t,r,i)=>g(`timeout`,t,async t=>{let a=n(t),c=s(()=>a.ctx.abort(new DOMException(`Timed out after ${r}ms`,`TimeoutError`)),r);try{return await l(Promise.resolve(e(a.ctx,`run`,()=>i({signal:a.ctx.signal,abort:a.ctx.abort}))),a.ctx)}finally{o(c),a.unlink()}});export{f as all,m as allSettled,h as cancellable,c as delay,i as onAbort,v as poll,y as race,S as retry,C as take,w as timeout};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=e=>typeof e==`string`?e:e.type,t=(e,t)=>typeof e==`object`?e.payload:t;export{t as n,e as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { A as Keys, C as WithPayload, D as anyEventType, E as Tracer, F as TraceEvent, I as TraceLogger, L as UnsubscribeCallback, M as Phase, N as Task, O as EffectName, P as Trace, S as WithContext, T as createBus, _ as EventPayload, a as AnyEvent, b as Off, c as Command, d as ContextOf, f as ContextOption, g as EventLike, h as EventKey, i as Action, j as PayloadOf, k as EventMap, l as Commands, m as Emittable, o as Bus, p as Emit, s as BusOptions, u as Context, v as Handler, w as WithType, x as On, y as HandlerRef } from "./action-BM-UdmJr.js";
|
|
2
|
+
import "./index-CaxrMD1A.js";
|
|
3
|
+
export { type Action, type AnyEvent, type Bus, type BusOptions, type Command, type Commands, type Context, type ContextOf, type ContextOption, type EffectName, type Emit, type Emittable, type EventKey, type EventLike, type EventMap, type EventPayload, type Handler, type HandlerRef, type Keys, type Off, type On, type PayloadOf, type Phase, type Task, type Trace, type TraceEvent, type TraceLogger, type Tracer, type UnsubscribeCallback, type WithContext, type WithPayload, type WithType, anyEventType, createBus };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{n as anyEventType,t as createBus}from"./bus-CFxgpsRL.js";
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { N as Task, k as EventMap } from "./action-BM-UdmJr.js";
|
|
2
|
+
//#region src/policies/keyed.d.ts
|
|
3
|
+
/** Options shared by every policy that can keep its state per key. */
|
|
4
|
+
type KeyOption<P> = {
|
|
5
|
+
/**
|
|
6
|
+
* Gives each key its own state: calls with different keys do not affect
|
|
7
|
+
* each other. The state of a key is dropped when that key goes idle, so
|
|
8
|
+
* keys can be unbounded (ids, urls).
|
|
9
|
+
*/
|
|
10
|
+
key?: (payload: P) => unknown;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/policies/debounce.d.ts
|
|
14
|
+
type DebounceResult<R> = Promise<R | undefined> | Promise<Awaited<R> | undefined> | undefined;
|
|
15
|
+
type DebounceOptions<P = never> = KeyOption<P> & {
|
|
16
|
+
/** Run the first call of a burst at once. Default `false`. */
|
|
17
|
+
leading?: boolean;
|
|
18
|
+
/** Run the last call of a burst after `ms` of quiet. Default `true`. */
|
|
19
|
+
trailing?: boolean;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Waits for `ms` of quiet, then runs; a new call restarts the wait and drops
|
|
23
|
+
* the previous. With `leading` the first call of a burst runs at once, and the
|
|
24
|
+
* trailing run happens only if more calls follow it. With `trailing: false`
|
|
25
|
+
* only the leading call runs. With `options.key` each key has its own wait.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* bus.on(
|
|
30
|
+
* "input",
|
|
31
|
+
* debounce(300, async (text, { signal }) => {
|
|
32
|
+
* const res = await fetch(`/api?q=${text}`, { signal });
|
|
33
|
+
* showResults(await res.json());
|
|
34
|
+
* }),
|
|
35
|
+
* );
|
|
36
|
+
*
|
|
37
|
+
* bus.emit("input", "r");
|
|
38
|
+
* bus.emit("input", "re"); // only the last call runs, 300 ms after it
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare const debounce: <P, E extends EventMap, R>(ms: number, fn: Task<P, E, R>, options?: DebounceOptions<P>) => Task<P, E, DebounceResult<R>>;
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/policies/latest.d.ts
|
|
44
|
+
type LatestOptions<P = never> = KeyOption<P>;
|
|
45
|
+
/**
|
|
46
|
+
* Aborts the previous run when a new call arrives; stale results are dropped.
|
|
47
|
+
* With `options.key` each key has its own latest run.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* bus.on(
|
|
52
|
+
* "search",
|
|
53
|
+
* latest(async (query, { signal }) => {
|
|
54
|
+
* const res = await fetch(`/api?q=${query}`, { signal });
|
|
55
|
+
* showResults(await res.json());
|
|
56
|
+
* }),
|
|
57
|
+
* );
|
|
58
|
+
*
|
|
59
|
+
* bus.emit("search", "re"); // aborted by the next call
|
|
60
|
+
* bus.emit("search", "redux"); // only this one finishes
|
|
61
|
+
*
|
|
62
|
+
* // one latest run per user instead of one for all
|
|
63
|
+
* bus.on("load", latest(loadUser, { key: (user) => user.id }));
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare const latest: <P, E extends EventMap, R>(fn: Task<P, E, R>, options?: LatestOptions<P>) => Task<P, E, R | Promise<Awaited<R> | undefined> | undefined>;
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/policies/leading.d.ts
|
|
69
|
+
type LeadingOptions<P = never> = KeyOption<P>;
|
|
70
|
+
/**
|
|
71
|
+
* Drops new calls while the task is still running. The lock is released when
|
|
72
|
+
* the run settles or when its task is aborted, whichever comes first, so a
|
|
73
|
+
* `fn` that ignores the abort signal may overlap with the next run. With
|
|
74
|
+
* `options.key` each key has its own lock.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* bus.on(
|
|
79
|
+
* "save",
|
|
80
|
+
* leading(async (draft) => {
|
|
81
|
+
* await api.save(draft);
|
|
82
|
+
* }),
|
|
83
|
+
* );
|
|
84
|
+
*
|
|
85
|
+
* bus.emit("save", draft); // runs
|
|
86
|
+
* bus.emit("save", draft); // dropped while the first save is in flight
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export declare const leading: <P, E extends EventMap, R>(fn: Task<P, E, R>, options?: LeadingOptions<P>) => Task<P, E, R | Promise<Awaited<R>> | undefined>;
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/policies/queue.d.ts
|
|
92
|
+
type QueueOptions<P = never> = KeyOption<P> & {
|
|
93
|
+
/** How many calls may run at the same time. Default 1. */
|
|
94
|
+
concurrency?: number;
|
|
95
|
+
/** How many calls may wait for a free slot. Default: no limit. */
|
|
96
|
+
limit?: number;
|
|
97
|
+
/**
|
|
98
|
+
* What to drop when a call arrives and `limit` calls already wait: the new
|
|
99
|
+
* call (`"drop-new"`, the default) or the oldest waiting one (`"drop-old"`,
|
|
100
|
+
* which keeps the most recent calls). A dropped call resolves with `undefined`.
|
|
101
|
+
*/
|
|
102
|
+
overflow?: "drop-new" | "drop-old";
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Runs calls one after another, in the order they arrived: a call starts when
|
|
106
|
+
* fewer than `concurrency` calls are running. Every call returns a promise with
|
|
107
|
+
* the result of its own run; an error rejects only that call, and the queue
|
|
108
|
+
* goes on. The first call on an idle queue starts synchronously.
|
|
109
|
+
*
|
|
110
|
+
* With `limit` the number of waiting calls is capped. `limit: 1` with
|
|
111
|
+
* `overflow: "drop-old"` keeps only the latest call while one is running, and
|
|
112
|
+
* runs it when the current one is done.
|
|
113
|
+
*
|
|
114
|
+
* With `options.key` each key has its own line.
|
|
115
|
+
*
|
|
116
|
+
* A call whose task aborts while it waits is skipped and resolves with
|
|
117
|
+
* `undefined`. When the task of a running call aborts, its slot is freed at
|
|
118
|
+
* once and its result is dropped; a `fn` that ignores the abort signal can
|
|
119
|
+
* then overlap with the next run.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* bus.on(
|
|
124
|
+
* "save",
|
|
125
|
+
* queue(async (draft, { signal }) => {
|
|
126
|
+
* await api.save(draft, { signal });
|
|
127
|
+
* }),
|
|
128
|
+
* );
|
|
129
|
+
*
|
|
130
|
+
* bus.emit("save", a); // runs
|
|
131
|
+
* bus.emit("save", b); // waits for a, then runs
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export declare const queue: <P, E extends EventMap, R>(fn: Task<P, E, R>, options?: QueueOptions<P>) => Task<P, E, Promise<Awaited<R> | undefined>>;
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region src/policies/throttle.d.ts
|
|
137
|
+
type ThrottleOptions<P = never> = KeyOption<P> & {
|
|
138
|
+
/** Run the first call at once, when the throttle is idle. Default `true`. */
|
|
139
|
+
leading?: boolean;
|
|
140
|
+
/** Run the latest call made while waiting, at the end of the window. Default `true`. */
|
|
141
|
+
trailing?: boolean;
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* At most one run per `ms`, with the semantics of lodash `throttle` (and
|
|
145
|
+
* patronum): a call made when the throttle is idle, that is, at least `ms`
|
|
146
|
+
* after the previous call and the previous run, runs at once (`leading`); calls
|
|
147
|
+
* made while it waits are held, and the latest of them runs when the window
|
|
148
|
+
* closes (`trailing`). Under a steady stream of calls it runs every `ms`.
|
|
149
|
+
*
|
|
150
|
+
* A held call returns a promise that settles with its result; calls superseded
|
|
151
|
+
* by a newer one, or not allowed to wait (`trailing: false`), resolve with
|
|
152
|
+
* `undefined`. With `leading: false` even the first call waits for the end of
|
|
153
|
+
* its window. With `options.key` each key has its own throttle.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```ts
|
|
157
|
+
* bus.on(
|
|
158
|
+
* "scroll",
|
|
159
|
+
* throttle(200, (position) => {
|
|
160
|
+
* saveScrollPosition(position);
|
|
161
|
+
* }),
|
|
162
|
+
* );
|
|
163
|
+
*
|
|
164
|
+
* bus.emit("scroll", 10); // runs now
|
|
165
|
+
* bus.emit("scroll", 20); // superseded
|
|
166
|
+
* bus.emit("scroll", 30); // runs once the 200 ms window closes
|
|
167
|
+
* ```
|
|
168
|
+
*/
|
|
169
|
+
export declare const throttle: <P, E extends EventMap, R>(ms: number, fn: Task<P, E, R>, options?: ThrottleOptions<P>) => Task<P, E, R | Promise<R | undefined> | undefined>;
|
|
170
|
+
//#endregion
|
|
171
|
+
export type { DebounceOptions, LatestOptions, LeadingOptions, QueueOptions, ThrottleOptions };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as e,i as t,o as n,s as r,t as i}from"./context-DGPIFXxI.js";import{n as a,r as o,t as s}from"./delay-Cko_Q9hl.js";import{n as c,t as l}from"./promise-CSLpzwSR.js";const u=()=>{},d=(e,t)=>{let n=/* @__PURE__ */ new Map;return(r,i)=>{let a=e(r),o=n.get(a);if(!o){let e=t(()=>{n.get(a)===e&&n.delete(a)});o=e,n.set(a,e)}return o(r,i)}},f=(e,t={})=>{let n=t=>p(`latest`,(t,n,r)=>(r(),e(t,n)),t);return t.key?d(t.key,n):n(u)},p=(e,a,o=u)=>{let s=null;return(l,u)=>{if(s?.abort(),u.signal.aborted)return;let{ctx:d,unlink:f}=t(u);s=d;let p=i(d,e),m=!1,h=()=>{d.signal.aborted||(m=!0,n(d,e,`start`))};r(d,()=>p(m?`cancel`:`drop`));let g=()=>{f(),s===d&&(s=null,o())},_=e=>{if(!d.signal.aborted)return p(m?`done`:`drop`),e};try{let e=a(l,d,h);return c(e)?Promise.resolve(e).then(_,e=>{if(!d.signal.aborted)throw p(`error`,e),e}).finally(g):(g(),_(e))}catch(e){if(g(),d.signal.aborted)return;throw p(`error`,e),e}}},m=(e,t,n={})=>{let r=r=>g(e,t,n,r);return n.key?d(n.key,r):r(u)},h=(t,r,i,s)=>{let c=s!==u,l=null,d=null;return(u,f)=>{if(f.signal.aborted)return;let p=Date.now(),m=l===null||p-l>=t||p-l<0;return l=p,d!==null&&a(d),d=c?o(s,t):null,i&&m?Promise.resolve(e(f,`debounce`,()=>r(u,f))):(n(f,`debounce`,`drop`),Promise.resolve(void 0))}},g=(t,n,i,a)=>{let o=i.leading??!1,c=i.trailing??!0;if(!c)return h(t,n,o,a);let l=!1,u=0,d=p(`debounce`,([e,i],a,o)=>{let d=u;return r(a,()=>{u===d&&(l=!1)}),s(a,t).then(()=>{if(l=!1,c&&!i)return o(),n(e,a)})},a);return(t,r)=>{if(r.signal.aborted)return;let i=o&&!l;l=!0,u++;let a=d([t,i],r);return i?Promise.resolve(e(r,`debounce`,()=>n(t,r))):a}},_=(t,i={})=>{let a=i=>{let a=null;return(o,s)=>{if(s.signal.aborted)return;if(a){n(s,`leading`,`drop`);return}let c={};a=c;let u=()=>{d(),a===c&&(a=null,i())},d=r(s,u);try{return l(e(s,`leading`,()=>t(o,s)),u)}catch(e){throw u(),e}}};return i.key?d(i.key,a):a(u)},v=(e,t={})=>{let n=n=>y(e,t,n);return t.key?d(t.key,n):n(u)},y=(t,i,a)=>{let o=i.concurrency??1,s=i.limit??1/0,c=[],l=0,d=()=>{for(;l<o;){let e=c.shift();if(!e)return;e.start()}};return(f,p)=>new Promise((m,h)=>{if(p.signal.aborted){m(void 0);return}let g=u,_=()=>{g(),n(p,`queue`,`drop`),m(void 0)},v=()=>{if(g(),p.signal.aborted){_();return}l++;let n=!0,i=()=>{n&&(n=!1,l--,d(),l===0&&c.length===0&&a())},o=r(p,()=>{i(),m(void 0)}),s;try{s=Promise.resolve(e(p,`queue`,()=>t(f,p)))}catch(e){s=Promise.reject(e)}let u=()=>{o(),i()};s.then(e=>{u(),m(p.signal.aborted?void 0:e)},e=>{u(),p.signal.aborted?m(void 0):h(e)})};if(l<o){v();return}if(c.length>=s){if(i.overflow!==`drop-old`||s<=0){_();return}c.shift()?.drop()}let y={start:v,drop:_};c.push(y),g=r(p,()=>{let e=c.indexOf(y);e>=0&&c.splice(e,1),_()})})},b=(e,t,n={})=>{let r=r=>x(e,t,n,r);return n.key?d(n.key,r):r(u)},x=(t,r,i,s)=>{let c=i.leading??!0,l=i.trailing??!0,d=s!==u,f=null,p=0,m=null,h=null,g=null,_=!1,v=e=>f===null||e-f>=t||e-f<0||e-p>=t,y=e=>n(e,`throttle`,`drop`),b=()=>{let e=g;g=null,e&&(y(e.ctx),e.resolve())},x=(e,t)=>(b(),new Promise((n,r)=>{g={payload:e,ctx:t,resolve:n,reject:r}})),S=(t,n,i)=>{b(),p=i,_=!0;try{return e(n,`throttle`,()=>r(t,n))}finally{_=!1}},C=()=>{d&&(h=o(w,t))},w=()=>{if(h=null,!d||m!==null||g)return;let e=Date.now(),n=Math.max(t-(e-(f??-1/0)),t-(e-p));n<=0?s():h=o(w,n)},T=e=>{m=null;let t=g;if(l&&t&&!t.ctx.signal.aborted){g=null;try{t.resolve(S(t.payload,t.ctx,e))}catch(e){t.reject(e)}}else b();w()},E=()=>{let e=Date.now();v(e)?T(e):m=o(E,Math.min(t-(e-(f??e)),t-(e-p)))},D=()=>{h!==null&&(a(h),h=null)},O=(e,t)=>{if(!l)y(t);else return x(e,t)},k=(e,t,n,r)=>{if(!r)y(t);else if(p=n,C(),!c)y(t);else return S(e,t,n)},A=(e,n,r,i)=>i&&m!==null?(a(m),m=o(E,t),S(e,n,r)):m===null&&(m=o(E,t),i&&(p=r,c))?S(e,n,r):O(e,n);return(e,t)=>{if(t.signal.aborted)return;let n=Date.now(),r=!_&&v(n);return f=n,D(),l?A(e,t,n,r):k(e,t,n,r)}};export{m as debounce,f as latest,_ as leading,v as queue,b as throttle};
|