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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vladimir Yazykov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,96 @@
|
|
|
1
|
-
#
|
|
1
|
+
# nanoeffects
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A typed event bus where every handler is a **task**: it gets a context with an `AbortSignal`, and the policies that decide what happens when calls overlap (`latest`, `debounce`, `throttle`, `queue`, ...) are plain functions around the handler.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { createBus } from "nanoeffects";
|
|
7
|
+
import { latest } from "nanoeffects/policies";
|
|
8
|
+
import { retry } from "nanoeffects/effects";
|
|
9
|
+
|
|
10
|
+
type Events = { search: string; results: Item[] };
|
|
11
|
+
|
|
12
|
+
const bus = createBus<Events>();
|
|
13
|
+
|
|
14
|
+
// a new search aborts the previous one; the request is retried and cancelled with the task
|
|
15
|
+
bus.on(
|
|
16
|
+
"search",
|
|
17
|
+
latest(async (query, ctx) => {
|
|
18
|
+
const items = await retry(ctx, () => api.search(query, { signal: ctx.signal }));
|
|
19
|
+
ctx.emit("results", items); // does nothing if this task was aborted meanwhile
|
|
20
|
+
}),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
bus.emit("search", "nano");
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
What it is for: the glue between events and async work in an app, where the usual answer is a pile of `AbortController`s, `debounce` from lodash, `p-queue` and a hand-written retry. Here they share one model of cancellation and one trace.
|
|
27
|
+
|
|
28
|
+
- **Tiny core.** `createBus` is about 1.2 kB gzip. Everything else is a separate entry and is bundled only when imported.
|
|
29
|
+
- **Cancellation is the base of everything.** Aborting a task aborts its children, ends its `ctx.on` subscriptions and drops its result.
|
|
30
|
+
- **Policies and effects are functions**: `latest`, `leading`, `debounce`, `throttle`, `queue`, `retry`, `timeout`, `poll`, `all`, `allSettled`, `race`, `take`.
|
|
31
|
+
- **Typed commands, actions, jobs and modules** to describe what the app does apart from the code that does it.
|
|
32
|
+
- **Tracing by design**: causal links between tasks, sampling, Redux DevTools, and a build that cuts all of it out.
|
|
33
|
+
- **Checked against the libraries it replaces**: `throttle` and `debounce` are compared with lodash on random call sequences, and known issues of lodash, RxJS, p-queue, p-retry, bottleneck and effector are regression tests.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install nanoeffects
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
ESM only, no dependencies. Types are included.
|
|
42
|
+
|
|
43
|
+
## Entries
|
|
44
|
+
|
|
45
|
+
| Import | What is in it |
|
|
46
|
+
| ----------------------- | -------------------------------------------------------------------------------------- |
|
|
47
|
+
| `nanoeffects` | `createBus`, the types (`Bus`, `Context`, `WithContext`, ...) |
|
|
48
|
+
| `nanoeffects/policies` | `latest`, `leading`, `debounce`, `throttle`, `queue` |
|
|
49
|
+
| `nanoeffects/effects` | `retry`, `timeout`, `poll`, `delay`, `take`, `all`, `allSettled`, `race`, `cancellable`, `onAbort` |
|
|
50
|
+
| `nanoeffects/actions` | `createAction`, `createCommand`, `createJob`, `createModule`, `registerModules` |
|
|
51
|
+
| `nanoeffects/redux` | `createEffectsMiddleware`, `nanoeffectsMiddleware`: Redux actions to the bus, `ctx.store` in tasks |
|
|
52
|
+
| `nanoeffects/observable` | `createObserve`: an event of the bus as an Observable (RxJS and others) |
|
|
53
|
+
| `nanoeffects/trace` | `createTracer` and the loggers (Redux DevTools, console, performance) |
|
|
54
|
+
|
|
55
|
+
## Handlers and the task context
|
|
56
|
+
|
|
57
|
+
`bus.on(event, handler)` calls `handler(payload, ctx)` for every `bus.emit(event, payload)`. Each call is a task with its own context:
|
|
58
|
+
|
|
59
|
+
| `ctx` | Meaning |
|
|
60
|
+
| ---------------- | ------------------------------------------------------------------------------ |
|
|
61
|
+
| `signal`, `abort` | the abort signal of the task, and a way to abort it (and its children) |
|
|
62
|
+
| `emit` | emit as this task; does nothing after the task is aborted |
|
|
63
|
+
| `on`, `off` | subscribe as this task; the subscription ends when the task aborts |
|
|
64
|
+
| `run(fn, ...)` | a child task: aborted with this one, its errors go to this one |
|
|
65
|
+
| `type`, `payload` | the event the task works on |
|
|
66
|
+
|
|
67
|
+
`emit` is a synchronous loop over the handlers, and `bus.on` returns the function that unsubscribes.
|
|
68
|
+
|
|
69
|
+
**The event map is optional.** Describe the events once, `type Events = { name: Payload }` (an event without data is `void`), and `createBus<Events>()` checks every `on` and `emit`. Or skip it: `createBus()` takes any string as an event and passes the payload as `unknown`. Actions and commands bring their own types, so they are typed on either kind of bus: `createAction("user/loaded")` needs no generics on a bus with an event map (the payload type comes from the map), and `createAction<User>("user/loaded")` gives it on a bus without one. On a bus with an event map, an action or a command that is emitted by its object must have its type in the map.
|
|
70
|
+
|
|
71
|
+
The event map is optional. Describe the events once, `type Events = { name: Payload }` (an event without data is `void`), and `createBus<Events>()` checks every `on` and `emit`. Or skip it: `createBus()` takes any string as an event and passes the payload as `unknown`. Actions and commands bring their own types, so they are typed on either kind of bus.
|
|
72
|
+
|
|
73
|
+
## Where to go next
|
|
74
|
+
|
|
75
|
+
- [Policies](docs/policies.md): `latest`, `leading`, `debounce`, `throttle`, `queue`, and `key` for a state per key.
|
|
76
|
+
- [Effects and cancellation](docs/effects.md): `retry`, `timeout`, `poll`, `take`, `all`, `race`, and `ctx.on`.
|
|
77
|
+
- [Actions, commands, jobs and modules](docs/actions.md): describe what an app does apart from the code that does it.
|
|
78
|
+
- [Your own fields in the context](docs/context.md): a store, a client or a logger in every task, with types.
|
|
79
|
+
- [Errors and unheard events](docs/errors.md): `onError` and `onUnhandled`.
|
|
80
|
+
- [Tracing](docs/tracing.md): causal traces, Redux DevTools, sampling, and a build without tracing.
|
|
81
|
+
- [Redux, Zustand, MobX](docs/stores.md).
|
|
82
|
+
- [Observable](docs/observable.md): events as streams for RxJS and others.
|
|
83
|
+
- [Hot reload](docs/hmr.md): modules are replaced by name, so an edit does not pile up handlers.
|
|
84
|
+
- [Semantics](docs/semantics.md): the rules that hold everywhere.
|
|
85
|
+
- [`examples/`](examples): code that is type-checked together with the project.
|
|
86
|
+
|
|
87
|
+
## Development
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pnpm verify # type check, lint, tests, build, integration tests, package checks, bundle size: what CI runs
|
|
91
|
+
pnpm test # vitest
|
|
92
|
+
pnpm test:integration # the real Redux Toolkit, Zustand, MobX, Jotai; Vite and webpack bundling the package
|
|
93
|
+
pnpm check # biome with the readability plugins in lint/
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
[`AGENTS.md`](AGENTS.md) (also read by Claude Code through `CLAUDE.md`) describes the structure, the decisions behind it and the rules of the code, for people and for AI assistants that work on the project.
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
//#region src/core/types.d.ts
|
|
2
|
+
type PayloadOf<P> = undefined extends P ? [payload?: P] : [payload: P];
|
|
3
|
+
type EventMap = Record<string, unknown>;
|
|
4
|
+
type Keys<E> = keyof E & string;
|
|
5
|
+
type Task<P, E extends EventMap, R = unknown> = (payload: P, ctx: Context<E>) => R;
|
|
6
|
+
type EffectName = "every" | "latest" | "leading" | "debounce" | "throttle" | "run" | "all" | "race" | "retry" | "timeout" | "poll" | "take" | "queue" | "allSettled" | "command";
|
|
7
|
+
type Phase = "start" | "done" | "error" | "cancel" | "drop";
|
|
8
|
+
type TraceEvent<E = Record<string, unknown>> = { [K in Keys<E>]: {
|
|
9
|
+
id: number;
|
|
10
|
+
parent?: number;
|
|
11
|
+
type: K;
|
|
12
|
+
effect: EffectName;
|
|
13
|
+
phase: Phase;
|
|
14
|
+
payload: E[K];
|
|
15
|
+
error?: unknown;
|
|
16
|
+
}; }[Keys<E>];
|
|
17
|
+
/**
|
|
18
|
+
* The contract of every trace logger: receives each task phase of a bus.
|
|
19
|
+
* Adapters in `nanoeffects/trace` are factories that return one; pass it
|
|
20
|
+
* through `createTracer` to `createBus({ trace })`.
|
|
21
|
+
*/
|
|
22
|
+
type TraceLogger<E = Record<string, unknown>> = (event: TraceEvent<E>) => void;
|
|
23
|
+
/** One logger or several; a throwing logger never breaks the task or the others. */
|
|
24
|
+
type Trace<E = Record<string, unknown>> = TraceLogger<E> | readonly TraceLogger<E>[];
|
|
25
|
+
type UnsubscribeCallback = () => void;
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/core/store.d.ts
|
|
28
|
+
declare const anyEventType = "*";
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/core/tracer.d.ts
|
|
31
|
+
/**
|
|
32
|
+
* What the bus needs from tracing. The implementation, `createTracer` in
|
|
33
|
+
* `nanoeffects/trace`, holds all the machinery (ids, events, loggers), so a
|
|
34
|
+
* bus without `trace` carries none of it. `Ref` is an opaque handle that
|
|
35
|
+
* `task` makes for each task.
|
|
36
|
+
*/
|
|
37
|
+
interface Tracer<Ref extends object = object> {
|
|
38
|
+
/** Registers a task and returns its handle; `parent` is the handle of its parent task. */
|
|
39
|
+
task(event: {
|
|
40
|
+
type: string;
|
|
41
|
+
payload?: unknown;
|
|
42
|
+
}, parent?: Ref): Ref;
|
|
43
|
+
/** Reports a phase of a task. */
|
|
44
|
+
report(ref: Ref, effect: EffectName, phase: Phase, error?: unknown): void;
|
|
45
|
+
/** Returns `settle(phase, error?)` that reports only the first phase it gets. */
|
|
46
|
+
settler(ref: Ref, effect: EffectName): (phase: Phase, error?: unknown) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Runs `fn`, reporting `start` and then `done` / `error` of its (sync or
|
|
49
|
+
* async) result, or `cancel` if `signal` aborts first.
|
|
50
|
+
*/
|
|
51
|
+
run<R>(ref: Ref, effect: EffectName, signal: AbortSignal, fn: () => R): R;
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/core/bus.d.ts
|
|
55
|
+
/** Subscribes to every event; handlers receive the payload of any of them. */
|
|
56
|
+
type AnyEvent = typeof anyEventType;
|
|
57
|
+
type EventKey<E extends EventMap> = Keys<E>;
|
|
58
|
+
type EventPayload<E extends EventMap> = E[EventKey<E>];
|
|
59
|
+
type WithType<K extends string> = {
|
|
60
|
+
type: K;
|
|
61
|
+
};
|
|
62
|
+
type HandlerRef<E extends EventMap, Response = unknown> = (action: Action<E>) => Response;
|
|
63
|
+
type Handler<E extends EventMap, Response = unknown, K extends EventKey<E> = EventKey<E>> = { [P in K]: {
|
|
64
|
+
type: P;
|
|
65
|
+
(payload: E[P]): Response;
|
|
66
|
+
}; }[K];
|
|
67
|
+
type Action<E extends EventMap> = {
|
|
68
|
+
type: EventKey<E>;
|
|
69
|
+
payload: EventPayload<E>;
|
|
70
|
+
};
|
|
71
|
+
type WithPayload<E extends EventMap, K extends EventKey<E>> = undefined extends E[K] ? {
|
|
72
|
+
type: K;
|
|
73
|
+
payload?: E[K];
|
|
74
|
+
} : {
|
|
75
|
+
type: K;
|
|
76
|
+
payload: E[K];
|
|
77
|
+
};
|
|
78
|
+
type EventLike<E extends EventMap> = { [K in EventKey<E>]: WithPayload<E, K>; }[Keys<E>];
|
|
79
|
+
/**
|
|
80
|
+
* A typed request for a result. `bus.emit(command, payload)` runs the handler
|
|
81
|
+
* of the command as a task and returns what it returns. Made by
|
|
82
|
+
* `createCommand` in `nanoeffects/actions`.
|
|
83
|
+
*/
|
|
84
|
+
type Command<P = never, R = unknown> = {
|
|
85
|
+
(payload: P, ctx?: never): R;
|
|
86
|
+
readonly type: string;
|
|
87
|
+
/** Wraps the handler that really runs, e.g. to route its outcome. */
|
|
88
|
+
readonly wrap?: (run: (payload: never, ctx: never) => unknown) => (payload: never, ctx: never) => unknown;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* What the bus needs to run commands. The implementation, `createCommands` in
|
|
92
|
+
* `nanoeffects/actions`, holds the handlers bound to commands and runs the
|
|
93
|
+
* right one, so a bus without `commands` carries none of that.
|
|
94
|
+
*/
|
|
95
|
+
interface Commands {
|
|
96
|
+
run(command: Command, payload: unknown, ctx: unknown): unknown;
|
|
97
|
+
}
|
|
98
|
+
/** Anything `emit` takes as its first argument: an event type, an action or a command. */
|
|
99
|
+
type Emittable<E extends EventMap> = EventKey<E> | Command | EventLike<E>;
|
|
100
|
+
type Emit<E extends EventMap> = {
|
|
101
|
+
<K extends EventKey<E>>(event: K, ...payload: PayloadOf<E[K]>): void;
|
|
102
|
+
<K extends EventKey<E>, R>(event: Handler<E, R, K>, ...payload: PayloadOf<E[K]>): R;
|
|
103
|
+
<P, R>(command: Command<P, R>, ...payload: PayloadOf<P>): R;
|
|
104
|
+
(event: EventLike<E>): void;
|
|
105
|
+
};
|
|
106
|
+
type On<E extends EventMap> = {
|
|
107
|
+
(event: AnyEvent, fn: Task<EventPayload<E>, E>): UnsubscribeCallback;
|
|
108
|
+
<K extends EventKey<E>>(event: K, fn: Task<E[K], E>): UnsubscribeCallback;
|
|
109
|
+
/** An action creator without a payload type: the payload comes from the event map. */
|
|
110
|
+
<K extends EventKey<E>>(creator: FreeActionCreator<K>, fn: Task<E[K], E>): UnsubscribeCallback;
|
|
111
|
+
/** An action creator: the payload comes from it, so an event map is not needed. */
|
|
112
|
+
<P>(creator: ActionCreator<P>, fn: Task<P, E>): UnsubscribeCallback;
|
|
113
|
+
/** A command: the payload comes from it too. */
|
|
114
|
+
<P>(command: Command<P, unknown>, fn: Task<P, E>): UnsubscribeCallback;
|
|
115
|
+
<K extends EventKey<E>, Event extends {
|
|
116
|
+
type: K;
|
|
117
|
+
}>(event: Event & {
|
|
118
|
+
type: K;
|
|
119
|
+
}, fn: Task<E[K], E>): UnsubscribeCallback;
|
|
120
|
+
};
|
|
121
|
+
type Off<E extends EventMap> = {
|
|
122
|
+
(event: AnyEvent): void;
|
|
123
|
+
(event: AnyEvent, fn: Task<EventPayload<E>, E>): void;
|
|
124
|
+
<K extends EventKey<E>>(event: K): void;
|
|
125
|
+
<K extends EventKey<E>>(event: K, fn: Task<E[K], E>): void;
|
|
126
|
+
<K extends EventKey<E>, Event extends {
|
|
127
|
+
type: K;
|
|
128
|
+
}>(event: Event & {
|
|
129
|
+
type: K;
|
|
130
|
+
}, fn: Task<E[K], E>): void;
|
|
131
|
+
<P>(command: Command<P, unknown>, fn?: Task<P, E>): void;
|
|
132
|
+
};
|
|
133
|
+
declare const contextKey: unique symbol;
|
|
134
|
+
/**
|
|
135
|
+
* Your own fields of every task context, in the event map: add it to the
|
|
136
|
+
* events with `&` and give the values in `createBus({ context })`. The fields
|
|
137
|
+
* are in the context of every task of that bus, and the compiler checks them.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```ts
|
|
141
|
+
* type Events = { save: Draft } & WithContext<{ store: Store }>;
|
|
142
|
+
*
|
|
143
|
+
* const bus = createBus<Events>({ context: { store } });
|
|
144
|
+
* bus.on("save", (draft, { store }) => store.put(draft));
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
type WithContext<Fields extends object> = {
|
|
148
|
+
readonly [contextKey]?: Fields;
|
|
149
|
+
};
|
|
150
|
+
/** The fields that the event map `E` adds to the context of a task. */
|
|
151
|
+
type ContextOf<E extends EventMap> = typeof contextKey extends keyof E ? NonNullable<E[typeof contextKey]> : unknown;
|
|
152
|
+
/** Fields for the context of every task, or a function that makes them for one task. */
|
|
153
|
+
type ContextOption<E extends EventMap> = ContextOf<E> | ((event: {
|
|
154
|
+
type: string;
|
|
155
|
+
payload?: unknown;
|
|
156
|
+
}) => ContextOf<E>);
|
|
157
|
+
/** Task context inside an effect. */
|
|
158
|
+
type Context<E extends EventMap> = EventLike<E> & ContextOf<E> & {
|
|
159
|
+
signal: AbortSignal;
|
|
160
|
+
/** Abort this task (and attached children). */
|
|
161
|
+
abort: (reason?: unknown) => void;
|
|
162
|
+
/** Emit as this task; no-op after the task is aborted. */
|
|
163
|
+
emit: Emit<E>;
|
|
164
|
+
/**
|
|
165
|
+
* Subscribe as this task. The subscription is removed when the task aborts;
|
|
166
|
+
* after that this is a no-op that returns a no-op.
|
|
167
|
+
*/
|
|
168
|
+
on: On<E>;
|
|
169
|
+
/** Same as the bus `off`. */
|
|
170
|
+
off: Off<E>;
|
|
171
|
+
/** Child task: aborted with the parent; errors propagate to the parent. */
|
|
172
|
+
run<P, R>(fn: Task<P, E, R>, ...args: PayloadOf<P>): Promise<Awaited<R>>;
|
|
173
|
+
};
|
|
174
|
+
interface Bus<E extends EventMap> {
|
|
175
|
+
on: On<E>;
|
|
176
|
+
off: Off<E>;
|
|
177
|
+
emit: Emit<E>;
|
|
178
|
+
/** `BusOptions.context`, `.trace` and `.commands`, for what makes tasks of its own, like `registerModules`. */
|
|
179
|
+
readonly context?: ContextOption<E> | undefined;
|
|
180
|
+
/** What runs the commands; `registerModules` sets it when the bus was made without. */
|
|
181
|
+
commands?: Commands | undefined;
|
|
182
|
+
readonly trace?: Tracer | undefined;
|
|
183
|
+
}
|
|
184
|
+
type BusOptions<E extends EventMap> = {
|
|
185
|
+
/**
|
|
186
|
+
* Runs commands: make it with `createCommands` from `nanoeffects/actions`.
|
|
187
|
+
* Without it an event that is a function runs as it is, with the payload and
|
|
188
|
+
* the task context.
|
|
189
|
+
*/
|
|
190
|
+
commands?: Commands;
|
|
191
|
+
/** Tracing of every task phase; make it with `createTracer` from `nanoeffects/trace`. */
|
|
192
|
+
trace?: Tracer;
|
|
193
|
+
/**
|
|
194
|
+
* Receives errors of handlers: thrown ones and rejections of async ones.
|
|
195
|
+
* Without it handlers run like in a plain loop: the first error stops the
|
|
196
|
+
* rest and is thrown to the caller of `emit`, and rejections of async
|
|
197
|
+
* handlers are left unhandled. With it every handler runs and errors go
|
|
198
|
+
* here; an `onError` that throws is logged to `console.error`.
|
|
199
|
+
*/
|
|
200
|
+
onError?: (error: unknown, event: Action<E>) => void;
|
|
201
|
+
/**
|
|
202
|
+
* Called when an event is emitted and nobody listens to it: the usual sign
|
|
203
|
+
* of a typo in an event name. Not called for commands, which always have a
|
|
204
|
+
* handler to run. Typically a warning in development only.
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```ts
|
|
208
|
+
* createBus<Events>({
|
|
209
|
+
* onUnhandled: import.meta.env.DEV ? ({ type }) => console.warn(`Nobody listens to "${type}"`) : void 0,
|
|
210
|
+
* });
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
onUnhandled?: (event: Action<E>) => void;
|
|
214
|
+
/**
|
|
215
|
+
* Fields added to the context of every task of this bus, its child tasks
|
|
216
|
+
* and the tasks of effects included: a store, a logger, a client. Their
|
|
217
|
+
* types come from `WithContext` in the event map. The fields of the library win over
|
|
218
|
+
* these when the names are the same. A function gets `{ type, payload }` of
|
|
219
|
+
* the task and returns its fields, for fields that depend on the task.
|
|
220
|
+
*/
|
|
221
|
+
context?: ContextOption<E>;
|
|
222
|
+
};
|
|
223
|
+
declare const createBus: <Events extends EventMap>(options?: BusOptions<Events>) => Bus<Events>;
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region src/actions/action.d.ts
|
|
226
|
+
type Absent<P> = [P] extends [void] ? undefined : P;
|
|
227
|
+
type Made<P, T extends string> = WithPayload<{ [K in T]: Absent<P>; }, T>;
|
|
228
|
+
declare const free: unique symbol;
|
|
229
|
+
type Free = typeof free;
|
|
230
|
+
/**
|
|
231
|
+
* A creator whose payload type was not given: each call has the type of what
|
|
232
|
+
* it is given, and the bus checks it against its event map, so
|
|
233
|
+
* `createAction("user/loaded")` needs no generics and keeps its literal type.
|
|
234
|
+
*/
|
|
235
|
+
interface FreeActionCreator<T extends string> {
|
|
236
|
+
(): {
|
|
237
|
+
type: T;
|
|
238
|
+
payload: undefined;
|
|
239
|
+
};
|
|
240
|
+
<P>(payload: P): {
|
|
241
|
+
type: T;
|
|
242
|
+
payload: P;
|
|
243
|
+
};
|
|
244
|
+
readonly type: T;
|
|
245
|
+
/** Tells whether an action is one of this creator. */
|
|
246
|
+
match(action: {
|
|
247
|
+
type: string;
|
|
248
|
+
payload?: unknown;
|
|
249
|
+
}): action is {
|
|
250
|
+
type: T;
|
|
251
|
+
payload: unknown;
|
|
252
|
+
};
|
|
253
|
+
toString(): T;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Makes actions of one type. It also works as the event of that type:
|
|
257
|
+
* `bus.on(creator, fn)` and `bus.emit(creator(payload))`.
|
|
258
|
+
*/
|
|
259
|
+
interface ActionCreator<P = void, T extends string = string, A extends unknown[] = PayloadOf<P>> {
|
|
260
|
+
(...args: A): Made<P, T>;
|
|
261
|
+
readonly type: T;
|
|
262
|
+
/** Tells whether an action is one of this creator, and narrows its type. */
|
|
263
|
+
match(action: {
|
|
264
|
+
type: string;
|
|
265
|
+
payload?: unknown;
|
|
266
|
+
}): action is Made<P, T>;
|
|
267
|
+
toString(): T;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Creates an action creator, as in Redux Toolkit.
|
|
271
|
+
*
|
|
272
|
+
* - `createAction("user/loaded")` needs no generics: the payload type comes
|
|
273
|
+
* from the event map of the bus that the action goes to, and the literal
|
|
274
|
+
* type of the action is kept.
|
|
275
|
+
* - `createAction<User>("user/loaded")` gives the payload type, for a bus
|
|
276
|
+
* without an event map or to check the calls of the creator. The type of
|
|
277
|
+
* the action is then `string`; to keep the literal, give it as the second
|
|
278
|
+
* generic: `createAction<User, "user/loaded">("user/loaded")`.
|
|
279
|
+
* - With `prepare` the arguments of the creator can differ from the payload,
|
|
280
|
+
* and both types are inferred.
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* ```ts
|
|
284
|
+
* const added = createAction<number>("counter/added");
|
|
285
|
+
* added(2); // { type: "counter/added", payload: 2 }
|
|
286
|
+
* added.type; // "counter/added"
|
|
287
|
+
* added.match(action); // true for actions of this type
|
|
288
|
+
*
|
|
289
|
+
* const renamed = createAction("user/renamed", (id: string, name: string) => ({
|
|
290
|
+
* payload: { id, name },
|
|
291
|
+
* }));
|
|
292
|
+
*
|
|
293
|
+
* bus.emit(added(2));
|
|
294
|
+
* bus.on(added, (n) => console.log(n));
|
|
295
|
+
* ```
|
|
296
|
+
*/
|
|
297
|
+
declare function createAction<P = Free, T extends string = string>(type: T): [P] extends [Free] ? FreeActionCreator<T> : ActionCreator<P, T>;
|
|
298
|
+
declare function createAction<A extends unknown[], P, T extends string = string>(type: T, prepare: (...args: A) => {
|
|
299
|
+
payload: P;
|
|
300
|
+
}): ActionCreator<P, T, A>;
|
|
301
|
+
//#endregion
|
|
302
|
+
export { Keys as A, WithPayload as C, anyEventType as D, Tracer as E, TraceEvent as F, TraceLogger as I, UnsubscribeCallback as L, Phase as M, Task as N, EffectName as O, Trace as P, WithContext as S, createBus as T, EventPayload as _, AnyEvent as a, Off as b, Command as c, ContextOf as d, ContextOption as f, EventLike as g, EventKey as h, Action as i, PayloadOf as j, EventMap as k, Commands as l, Emittable as m, FreeActionCreator as n, Bus as o, Emit as p, createAction as r, BusOptions as s, ActionCreator as t, Context as u, Handler as v, WithType as w, On as x, HandlerRef as y };
|
|
@@ -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 };
|
package/dist/actions.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{t as e}from"./onAbort-BgMfspK4.js";import{i as t,n}from"./context-BXQ6Einm.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,t)=>Object.assign((r,i)=>{if(!t)throw new a(`No handler for command "${e}"`);return t(r,i??n({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=n({type:`app`,payload:void 0},r,r.trace),l=n=>{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=t(n,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=(t,n)=>{let r=Reflect.apply(o.handle,null,[t,n]);return e(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{n as e}from"./onAbort-BgMfspK4.js";import{a as t,n,r}from"./context-BXQ6Einm.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=(t,a)=>{let{onError:o}=s,{ctx:c}=n(a,m,s.trace,{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)}},m={on:d,off:f,emit:(e,i)=>{let c=o(e),d=a(e,i),f={type:c,payload:d},h=[...u.get(c),...u.get(`*`)];h.length===0&&typeof e!=`function`&&s.onUnhandled?.(f);for(let e of h)p(e,f);if(typeof e==`function`){let{ctx:i}=n(f,m,s.trace,{ref:r()}),a=m.commands?.run??l;return t(i,`command`,()=>a(e,d,i))}},commands:s.commands,context:s.context,trace:s.trace};return m};export{s as n,u as t};
|
|
@@ -0,0 +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=(a,s,c,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({type:a.type,payload:n},s,c,{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({type:t??e.type??``,payload:e.payload},{emit:e.emit,on:e.on,off:e.off,context:e},i?.tracer,{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 @@
|
|
|
1
|
+
import{t as e}from"./onAbort-BgMfspK4.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};
|