nanoeffects 0.28.0 → 0.30.1
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/README.md +6 -5
- package/dist/{action-creator-CcAEvUnw.d.ts → action-creator-6rlBTmH9.d.ts} +60 -33
- package/dist/actions.d.ts +18 -18
- package/dist/actions.js +1 -1
- package/dist/bus-DFlmNJ6h.js +1 -0
- package/dist/context-B5Z2RpJt.js +1 -0
- package/dist/context-types-DKNc5mkS.d.ts +31 -0
- package/dist/context.d.ts +8 -3
- package/dist/context.js +1 -1
- package/dist/{delay-18LJ8jSk.js → delay-5OhJZZQ0.js} +1 -1
- package/dist/dev.d.ts +44 -3
- package/dist/dev.js +2 -1
- package/dist/effects.d.ts +2 -2
- package/dist/effects.js +1 -1
- package/dist/emit-key-C6FL7Ewn.js +1 -0
- package/dist/guard.d.ts +59 -0
- package/dist/guard.js +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/match.d.ts +3 -3
- package/dist/match.js +1 -1
- package/dist/{module-C_oEXw1n.d.ts → module-G39MaVOt.d.ts} +38 -38
- package/dist/notrace/{action-creator-DxOvVo8I.d.ts → action-creator-BrMSO3RP.d.ts} +35 -32
- package/dist/notrace/actions.d.ts +52 -52
- package/dist/notrace/actions.js +1 -1
- package/dist/notrace/{bus-DmKn4SDD.js → bus-37nlVljc.js} +1 -1
- package/dist/notrace/effects.d.ts +2 -2
- package/dist/notrace/index.d.ts +2 -2
- package/dist/notrace/index.js +1 -1
- package/dist/notrace/{plugin-lJjjd-SN.d.ts → plugin-7GUL2EW5.d.ts} +1 -1
- package/dist/notrace/policies.d.ts +1 -1
- package/dist/notrace/redux.d.ts +1 -1
- package/dist/observable.d.ts +43 -1
- package/dist/observable.js +1 -1
- package/dist/onAbort-BgMfspK4.js +1 -0
- package/dist/oop.d.ts +3 -3
- package/dist/oop.js +1 -1
- package/dist/{plugin-CuvIgjnX.d.ts → plugin-BWNP23hr.d.ts} +2 -2
- package/dist/policies.d.ts +2 -2
- package/dist/policies.js +1 -1
- package/dist/production/dev.js +1 -1
- package/dist/react.d.ts +2 -2
- package/dist/react.js +1 -1
- package/dist/redux.d.ts +1 -1
- package/dist/scope.d.ts +9 -9
- package/dist/signal-field-key-CWT-3OXC.js +1 -0
- package/dist/signal.d.ts +34 -6
- package/dist/signal.js +1 -1
- package/dist/site-DLvUoXs3.js +1 -0
- package/dist/svelte.d.ts +1 -1
- package/dist/trace.d.ts +170 -6
- package/dist/trace.js +3 -1
- package/dist/{types-FV8fuvI9.d.ts → types-N_cdRr1r.d.ts} +2 -2
- package/dist/vue.d.ts +1 -1
- package/dist/{with-policy-D-SQRkPh.js → with-policy-CeM1rtES.js} +1 -1
- package/dist/{with-policy-3bhFLEXs.d.ts → with-policy-CthiBp_z.d.ts} +1 -1
- package/docs/README.md +65 -22
- package/docs/actions.md +56 -13
- package/docs/cancel-stale-requests.md +2 -0
- package/docs/chat-example.md +15 -1
- package/docs/chat-widget.md +9 -7
- package/docs/context.md +352 -8
- package/docs/debounce-and-abort.md +3 -1
- package/docs/debugging-loops.md +283 -0
- package/docs/effects.md +259 -131
- package/docs/errors.md +4 -2
- package/docs/examples.md +60 -0
- package/docs/guide.md +57 -22
- package/docs/hmr.md +35 -1
- package/docs/loop-guard.md +109 -0
- package/docs/migrating-from-effector.md +15 -2
- package/docs/migrating-from-mitt.md +6 -4
- package/docs/migrating-from-rxjs.md +2 -0
- package/docs/migrating-from-saga.md +2 -0
- package/docs/motivation.md +14 -21
- package/docs/observable.md +45 -0
- package/docs/oop.md +20 -1
- package/docs/package.md +25 -13
- package/docs/pass-the-signal.md +281 -0
- package/docs/plugins.md +90 -15
- package/docs/policies.md +73 -46
- package/docs/prevent-double-submit.md +3 -1
- package/docs/query-libraries.md +276 -6
- package/docs/react.md +50 -11
- package/docs/recipes.md +528 -0
- package/docs/ru/README.md +62 -0
- package/docs/ru/actions.md +412 -0
- package/docs/ru/cancel-stale-requests.md +106 -0
- package/docs/ru/chat-example.md +259 -0
- package/docs/ru/chat-widget.md +131 -0
- package/docs/ru/context.md +401 -0
- package/docs/ru/debounce-and-abort.md +105 -0
- package/docs/ru/debugging-loops.md +283 -0
- package/docs/ru/effects.md +538 -0
- package/docs/ru/errors.md +36 -0
- package/docs/ru/examples.md +60 -0
- package/docs/ru/guide.md +530 -0
- package/docs/ru/hmr.md +83 -0
- package/docs/ru/loop-guard.md +109 -0
- package/docs/ru/migrating-from-effector.md +339 -0
- package/docs/ru/migrating-from-mitt.md +99 -0
- package/docs/ru/migrating-from-rxjs.md +197 -0
- package/docs/ru/migrating-from-saga.md +97 -0
- package/docs/ru/motivation.md +47 -0
- package/docs/ru/observable.md +233 -0
- package/docs/ru/oop.md +431 -0
- package/docs/ru/package.md +42 -0
- package/docs/ru/pass-the-signal.md +281 -0
- package/docs/ru/plugins.md +398 -0
- package/docs/ru/policies.md +201 -0
- package/docs/ru/prevent-double-submit.md +122 -0
- package/docs/ru/query-libraries.md +392 -0
- package/docs/ru/react.md +309 -0
- package/docs/ru/recipes.md +528 -0
- package/docs/ru/semantics.md +19 -0
- package/docs/ru/server.md +204 -0
- package/docs/ru/starter.md +118 -0
- package/docs/ru/stores.md +673 -0
- package/docs/ru/svelte.md +78 -0
- package/docs/ru/tracing.md +466 -0
- package/docs/ru/typescript.md +170 -0
- package/docs/ru/vue.md +92 -0
- package/docs/semantics.md +12 -0
- package/docs/server.md +5 -3
- package/docs/starter.md +2 -0
- package/docs/stores.md +2 -0
- package/docs/svelte.md +3 -1
- package/docs/tracing.md +132 -7
- package/docs/typescript.md +3 -1
- package/docs/vue.md +3 -1
- package/llms-full.txt +2718 -545
- package/llms.txt +17 -14
- package/package.json +25 -8
- package/skills/nanoeffects/SKILL.md +2 -2
- package/dist/bus-DlpEluzd.js +0 -1
- package/dist/context-UcmrAIhP.js +0 -1
- package/dist/context-types-hCKgU8sC.d.ts +0 -14
- package/docs/scope.md +0 -266
- package/docs/stability.md +0 -90
package/dist/signal.d.ts
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { A as HandledCall, C as Emit, E as EventLike, H as EventMap, s as EmitScope, u as WithScope, v as Bus } from "./action-creator-6rlBTmH9.js";
|
|
2
|
+
import { n as FromSignal } from "./context-types-DKNc5mkS.js";
|
|
3
|
+
//#region src/signal/from-signal.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Marks a field of `contextPlugin` as a value that needs the signal of its task: a
|
|
6
|
+
* client, a database handle. `make` runs when the task first reads the field, with the
|
|
7
|
+
* `ctx.signal` of that task, and runs once for it. A child task (`ctx.run`) makes its
|
|
8
|
+
* own with its own signal. Without `contextPlugin` the marker means nothing.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const bus = createBus().use(
|
|
13
|
+
* contextPlugin({
|
|
14
|
+
* logger, // a plain field, the same for every task
|
|
15
|
+
* http: fromSignal((signal) => axios.create({ signal })), // one client per task, aborted with it
|
|
16
|
+
* }),
|
|
17
|
+
* );
|
|
18
|
+
*
|
|
19
|
+
* bus.on("user/open", (id, { http }) => http.get(`/users/${id}`));
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare const fromSignal: <T>(make: (signal: AbortSignal) => T) => FromSignal<T>;
|
|
23
|
+
//#endregion
|
|
2
24
|
//#region src/signal/plugin.d.ts
|
|
3
25
|
/** What the second argument of `emit` takes besides the fields of a scope. */
|
|
4
26
|
type SignalOption = {
|
|
@@ -7,8 +29,8 @@ type SignalOption = {
|
|
|
7
29
|
};
|
|
8
30
|
/** `emit` of a bus with `signalPlugin`: the call forms take `{ signal }` as their second argument. */
|
|
9
31
|
type SignalEmit<E extends EventMap> = Emit<E> & {
|
|
10
|
-
<P, R, F>(call:
|
|
11
|
-
<P, R>(call:
|
|
32
|
+
<P, R, F>(call: HandledCall<P, R> & WithScope<F>, options: SignalOption & EmitScope<E, F>): R;
|
|
33
|
+
<P, R>(call: HandledCall<P, R>, options: SignalOption & EmitScope<E>): R;
|
|
12
34
|
(event: EventLike<E>, options: SignalOption & EmitScope<E>): void;
|
|
13
35
|
};
|
|
14
36
|
/** A bus with `signalPlugin`: the same bus, with the `emit` that takes a signal. */
|
|
@@ -16,16 +38,22 @@ type SignalBus<E extends EventMap> = Bus<E> & {
|
|
|
16
38
|
emit: SignalEmit<E>;
|
|
17
39
|
};
|
|
18
40
|
/**
|
|
41
|
+
* Also lets `bus.on(event, handler, { signal })` and `bus.once` remove the
|
|
42
|
+
* listener when the signal aborts, as `addEventListener` does, so a component or
|
|
43
|
+
* a script that holds a signal keeps no `unbind`. A handler that already runs is
|
|
44
|
+
* not aborted; a signal that is aborted already adds no listener. `off(event, fn)`
|
|
45
|
+
* does not end the link: the returned function does.
|
|
46
|
+
*
|
|
19
47
|
* Lets `bus.emit(call, { signal })` tie the tasks that this emit starts to an
|
|
20
48
|
* outside `AbortSignal`: when it aborts, their `ctx.signal` aborts with the same
|
|
21
49
|
* reason, so a handler, `latest`, `retry` and `timeout` stop as for any abort. It
|
|
22
50
|
* is made for libraries that own the request and give you a signal, such as the
|
|
23
51
|
* `queryFn` of React Query. The tasks that count are those made while the call
|
|
24
|
-
* to `emit` runs (the
|
|
52
|
+
* to `emit` runs (the action or job and the listeners of its type, and their
|
|
25
53
|
* `ctx.run` children); a separate `emit` that a handler makes later is not
|
|
26
54
|
* linked. `emit` returns what it returns without the plugin.
|
|
27
55
|
*
|
|
28
|
-
* For
|
|
56
|
+
* For an action or a job the link ends when its promise settles; a handler
|
|
29
57
|
* that returns without a promise is finished with the call, so it is not
|
|
30
58
|
* linked after it. For an event the link stays until the signal aborts, so
|
|
31
59
|
* give each request its own signal.
|
|
@@ -48,4 +76,4 @@ type SignalBus<E extends EventMap> = Bus<E> & {
|
|
|
48
76
|
*/
|
|
49
77
|
export declare const signalPlugin: () => <E extends EventMap>(bus: Bus<E>) => SignalBus<E>;
|
|
50
78
|
//#endregion
|
|
51
|
-
export type { SignalBus, SignalEmit, SignalOption };
|
|
79
|
+
export type { FromSignal, SignalBus, SignalEmit, SignalOption };
|
package/dist/signal.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{t as e}from"./onAbort-BgMfspK4.js";import{n as t}from"./promise-CSLpzwSR.js";import{n}from"./bus-DFlmNJ6h.js";import{t as r}from"./signal-field-key-CWT-3OXC.js";const i=e=>({[r]:e}),a=(t,n)=>(r,i,a)=>{let o=a?.signal;if(!o)return t(r,i,a);let s=t(r,n?(e,t)=>(c(),i(e,t)):i,a),c=e({signal:o},s);return()=>{c(),s()}},o=e=>typeof e==`object`&&!!e,s=(e,t)=>{let n=t[0];if(!o(n)||!o(e)&&typeof e!=`function`)return;let r=Reflect.get(n,`signal`);return r instanceof AbortSignal?r:void 0},c=(r,i,a,o)=>{let s=e({signal:r},e=>{for(let{target:t}of i)t.abort(e)});return t(o)?o.then(s,s):n(a)&&s(),o},l=()=>e=>{let t,n,r=e.addContext;e.addContext=e=>{r?.(e),t?.push({target:e}),n?.aborted&&e.abort(n.reason)};let i=e.emit;e.emit=(e,...r)=>{let a=s(e,r);if(!(a||t))return i(e,...r);let o=t,l=n,u=[];t=a?u:void 0,n=a;let d;try{d=i(e,...r)}finally{t=o,n=l}return a?c(a,u,e,d):d};let o=e.on,l=e.once;return e.on=a(o,!1),e.once=a(l,!0),e};export{i as fromSignal,l as signalPlugin};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=/* @__PURE__ */ new Map,t=(t,n)=>{let r=e.get(t);e.set(t,{site:r?.site??n,count:(r?.count??0)+1});let i=!1;return()=>{if(i)return;i=!0;let r=(e.get(t)?.count??1)-1;r<=0?e.delete(t):e.set(t,{site:e.get(t)?.site??n,count:r})}},n=t=>e.get(t)?.site;export{n,t};
|
package/dist/svelte.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { H as EventMap, T as EventKey, a as FreeActionCreator, t as ActionCreator, v as Bus } from "./action-creator-6rlBTmH9.js";
|
|
2
2
|
import { t as StateOf } from "./state-of-C19H6BoV.js";
|
|
3
3
|
import { Readable } from "svelte/store";
|
|
4
4
|
//#region src/svelte/plugin.d.ts
|
package/dist/trace.d.ts
CHANGED
|
@@ -1,4 +1,141 @@
|
|
|
1
|
-
import { B as Tracer, H as EventMap, J as
|
|
1
|
+
import { B as Tracer, H as EventMap, J as TaskInfo, K as Phase, V as EffectName, X as TraceEvent, Y as Trace, Z as TraceLogger, v as Bus } from "./action-creator-6rlBTmH9.js";
|
|
2
|
+
//#region src/graph/serialize.d.ts
|
|
3
|
+
type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
4
|
+
[key: string]: JsonValue;
|
|
5
|
+
};
|
|
6
|
+
type SerializeOptions = {
|
|
7
|
+
/** How deep to go before a value becomes a marker like `"[Object]"`. Default 6. */
|
|
8
|
+
maxDepth?: number;
|
|
9
|
+
/** How many values, in all, to visit before the rest is cut. Default 1000. */
|
|
10
|
+
maxSize?: number;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Makes any value a plain JSON value: never throws, never loops, never calls
|
|
14
|
+
* a getter or `toJSON`, and cuts a value that is too deep or too big.
|
|
15
|
+
* `Error` keeps its name, message, stack and cause; a cycle is `"[Circular]"`;
|
|
16
|
+
* `Map` and `Set` become `{ $map }` and `{ $set }`; an instance of a class
|
|
17
|
+
* keeps its own fields and its name as `$class`.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* safeSerialize({ user: new User("Ann"), tags: new Set(["a"]) });
|
|
22
|
+
* // { user: { $class: "User", name: "Ann" }, tags: { $set: ["a"] } }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare const safeSerialize: (value: unknown, options?: SerializeOptions) => JsonValue;
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/graph/types.d.ts
|
|
28
|
+
/** A loop that the logger found, or an event that is emitted too often. */
|
|
29
|
+
type Finding = {
|
|
30
|
+
/** `cycle`: a node repeats in a chain; `storm`: too many emits from outside any task; `runaway`: one root started too many tasks. */
|
|
31
|
+
kind: "cycle" | "storm" | "runaway";
|
|
32
|
+
/** The names from the first turn of the loop back to it: `a`, `b`, `a`. For a storm, the event. */
|
|
33
|
+
path: string[];
|
|
34
|
+
/** How many times the repeating node was in the chain when it was found. */
|
|
35
|
+
repeats: number;
|
|
36
|
+
/** How many times the loop has gone round since it was found, the first time included. */
|
|
37
|
+
turns: number;
|
|
38
|
+
/** The first node of the chain, or the event of a storm. */
|
|
39
|
+
root: string;
|
|
40
|
+
traceGroupId?: string;
|
|
41
|
+
/** `chain`: it ran as one chain; `graph`: the edges of different runs close it. */
|
|
42
|
+
source: "chain" | "graph";
|
|
43
|
+
};
|
|
44
|
+
type GraphLoggerOptions = {
|
|
45
|
+
/** Put before every report line and in the title of the exports. */
|
|
46
|
+
name?: string;
|
|
47
|
+
/** How many times a node may be in one chain before it is a cycle. Default 3. */
|
|
48
|
+
repeats?: number;
|
|
49
|
+
/** How many tasks one root may start before the chain is called runaway. Default 5000. */
|
|
50
|
+
maxSteps?: number;
|
|
51
|
+
/** How many emits of one event from outside any task, in how many ms, make a storm. Default 100 in 1000; `false` turns it off. */
|
|
52
|
+
storm?: false | {
|
|
53
|
+
count: number;
|
|
54
|
+
perMs: number;
|
|
55
|
+
};
|
|
56
|
+
/** Names that are not counted as repeats, steps or storms (they stay in the graph). */
|
|
57
|
+
ignore?: readonly string[] | ((name: string) => boolean);
|
|
58
|
+
/** Receives each finding once, in place of `console.warn`. */
|
|
59
|
+
onCycle?: (finding: Finding) => void;
|
|
60
|
+
/** Also send each finding to Redux DevTools as `cycle/detected`. */
|
|
61
|
+
devtools?: boolean;
|
|
62
|
+
/** The clock, in ms. Default `performance.now`. */
|
|
63
|
+
now?: () => number;
|
|
64
|
+
/** Where the default report goes. Default `globalThis.console`. */
|
|
65
|
+
console?: Pick<Console, "warn">;
|
|
66
|
+
/** The Redux DevTools extension. Default `globalThis.__REDUX_DEVTOOLS_EXTENSION__`. */
|
|
67
|
+
extension?: DevtoolsExtension;
|
|
68
|
+
};
|
|
69
|
+
type DevtoolsExtension = {
|
|
70
|
+
connect: (options?: {
|
|
71
|
+
name?: string;
|
|
72
|
+
}) => {
|
|
73
|
+
init: (state: unknown) => void;
|
|
74
|
+
send: (action: unknown, state: unknown) => void;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
type NodeSnapshot = {
|
|
78
|
+
name: string;
|
|
79
|
+
count: number;
|
|
80
|
+
/** Where the listener was made, when `devPlugin` is used. */
|
|
81
|
+
site?: string;
|
|
82
|
+
last?: {
|
|
83
|
+
payload: JsonValue;
|
|
84
|
+
error?: JsonValue;
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
type EdgeSnapshot = {
|
|
88
|
+
from: string;
|
|
89
|
+
to: string;
|
|
90
|
+
count: number;
|
|
91
|
+
inCycle: boolean;
|
|
92
|
+
};
|
|
93
|
+
type GraphSnapshot = {
|
|
94
|
+
name?: string;
|
|
95
|
+
nodes: NodeSnapshot[];
|
|
96
|
+
edges: EdgeSnapshot[];
|
|
97
|
+
cycles: Finding[];
|
|
98
|
+
};
|
|
99
|
+
type ExportOptions = {
|
|
100
|
+
/** Put the nodes in groups: `"prefix"` is the part of the name before the first `/`, or a function of the name. */
|
|
101
|
+
group?: "prefix" | ((name: string) => string | undefined);
|
|
102
|
+
};
|
|
103
|
+
type JsonOptions = {
|
|
104
|
+
/** Add the last payload and error of each node, made safe. Default `false`. */
|
|
105
|
+
payload?: boolean;
|
|
106
|
+
};
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/graph/logger.d.ts
|
|
109
|
+
type GraphLogger = TraceLogger & {
|
|
110
|
+
/** The loops found so far: the ones that ran as one chain, and the cycles that the edges of different runs close. */
|
|
111
|
+
cycles: () => Finding[];
|
|
112
|
+
/** The graph as a Mermaid flowchart; the edges of a cycle are red. */
|
|
113
|
+
toMermaid: (options?: ExportOptions) => string;
|
|
114
|
+
/** The graph as a Graphviz digraph. */
|
|
115
|
+
toDot: (options?: ExportOptions) => string;
|
|
116
|
+
/** The graph as plain data that `JSON.stringify` takes. */
|
|
117
|
+
toJSON: (options?: JsonOptions) => GraphSnapshot;
|
|
118
|
+
/** Forgets the graph, the findings and the counters. */
|
|
119
|
+
clear: () => void;
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* A trace logger that builds the map of which event leads to which, finds loops
|
|
123
|
+
* while the app runs, and writes the map as Mermaid, Graphviz or JSON. Pass it to
|
|
124
|
+
* `createTracer` like any logger; it needs no other setup, and works best with
|
|
125
|
+
* `tracePlugin` (it sees every emit and the names of listeners).
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* const graph = graphLogger({ name: "cart" });
|
|
130
|
+
* const bus = createBus({ trace: createTracer(graph) }).use(tracePlugin());
|
|
131
|
+
*
|
|
132
|
+
* // a loop is written to the console once; read the rest when you want
|
|
133
|
+
* graph.cycles();
|
|
134
|
+
* graph.toMermaid();
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
export declare const graphLogger: (options?: GraphLoggerOptions) => GraphLogger;
|
|
138
|
+
//#endregion
|
|
2
139
|
//#region src/trace/console.d.ts
|
|
3
140
|
type ConsoleLoggerOptions = {
|
|
4
141
|
/** Defaults to `globalThis.console`. */
|
|
@@ -52,7 +189,7 @@ export declare const performanceLogger: (options?: PerformanceLoggerOptions) =>
|
|
|
52
189
|
/**
|
|
53
190
|
* Names and groups what the trace shows, on a bus that has a tracer:
|
|
54
191
|
*
|
|
55
|
-
* - `traceGroupId` in the scope of
|
|
192
|
+
* - `traceGroupId` in the scope of an action, a job or an action goes to every
|
|
56
193
|
* task that the call starts;
|
|
57
194
|
* - `useEmitter` and `bus.emitter` emit from a root task named by their
|
|
58
195
|
* `traceId`, so a chain says which subscription began it;
|
|
@@ -80,12 +217,26 @@ export declare const tracePlugin: () => <E extends EventMap>(bus: Bus<E>) => Bus
|
|
|
80
217
|
//#endregion
|
|
81
218
|
//#region src/trace/recorder.d.ts
|
|
82
219
|
type Recorder<E = Record<string, unknown>> = TraceLogger<E> & {
|
|
83
|
-
/**
|
|
220
|
+
/**
|
|
221
|
+
* The events received so far, oldest first. Without a `capacity` it is one
|
|
222
|
+
* live array; with one, each read is a new array that is a snapshot.
|
|
223
|
+
*/
|
|
84
224
|
readonly events: TraceEvent<E>[];
|
|
85
225
|
clear: () => void;
|
|
226
|
+
/**
|
|
227
|
+
* The events as plain data that `JSON.stringify` takes: the payload and the error are made safe, so a
|
|
228
|
+
* circular object, an `Error`, a `Map` or a class instance cannot break it. `JSON.stringify(recorder)` uses it.
|
|
229
|
+
*/
|
|
230
|
+
toJSON: () => JsonValue[];
|
|
86
231
|
};
|
|
87
232
|
/**
|
|
88
|
-
* Trace logger that keeps events in memory
|
|
233
|
+
* Trace logger that keeps events in memory. With no option it keeps all of
|
|
234
|
+
* them, for tests. With `capacity` it keeps the last events, to send when
|
|
235
|
+
* something goes wrong.
|
|
236
|
+
*
|
|
237
|
+
* `payload` and `error` are kept as they are, and can hold private data: cut
|
|
238
|
+
* what you send. An event has no time, and a `parentId` can name an event that
|
|
239
|
+
* was pushed out: read it as a root.
|
|
89
240
|
*
|
|
90
241
|
* @example
|
|
91
242
|
* ```ts
|
|
@@ -95,9 +246,15 @@ type Recorder<E = Record<string, unknown>> = TraceLogger<E> & {
|
|
|
95
246
|
* bus.emit("search", "re");
|
|
96
247
|
* bus.emit("search", "redux");
|
|
97
248
|
* recorder.events.map((e) => e.phase); // ["start", "cancel", "start", "done"]
|
|
249
|
+
*
|
|
250
|
+
* const lastEvents = createTraceRecorder<Events>({ capacity: 500 });
|
|
98
251
|
* ```
|
|
99
252
|
*/
|
|
100
|
-
export declare const createTraceRecorder: <E = Record<string, unknown>>(
|
|
253
|
+
export declare const createTraceRecorder: <E = Record<string, unknown>>(
|
|
254
|
+
/** `capacity`: keep the last N events (a ring buffer); not a positive integer, or left out: keep all. */
|
|
255
|
+
options?: {
|
|
256
|
+
capacity?: number;
|
|
257
|
+
}) => Recorder<E>;
|
|
101
258
|
//#endregion
|
|
102
259
|
//#region src/trace/redux.d.ts
|
|
103
260
|
type ReduxConnection = {
|
|
@@ -160,6 +317,13 @@ type ReduxDevtoolsOptions = {
|
|
|
160
317
|
payload?: PayloadMode;
|
|
161
318
|
/** The number of actions that DevTools keeps; its own default is 50. */
|
|
162
319
|
maxAge?: number;
|
|
320
|
+
/**
|
|
321
|
+
* Makes the payload and the error of an action plain data before they reach the extension. The default
|
|
322
|
+
* keeps any value safe: an `Error`, a `Map`, a class instance, a circular object, a function. Pass your
|
|
323
|
+
* own function to change what is sent, or `false` to send values as they are (the extension then
|
|
324
|
+
* serializes them, and can fail on a circular one).
|
|
325
|
+
*/
|
|
326
|
+
serialize?: false | ((value: unknown) => unknown);
|
|
163
327
|
};
|
|
164
328
|
type ReduxDevtoolsState = {
|
|
165
329
|
/** Tasks that started and have not settled yet, by trace id. */
|
|
@@ -224,4 +388,4 @@ type TracerOptions = {
|
|
|
224
388
|
*/
|
|
225
389
|
export declare const createTracer: <E = Record<string, unknown>>(trace: Trace<E>, options?: TracerOptions) => Tracer;
|
|
226
390
|
//#endregion
|
|
227
|
-
export type { ConsoleLoggerOptions, EffectName, PayloadMode, PerformanceLoggerOptions, Phase, PolicyStage, PolicyStages, Recorder, ReduxDevtoolsOptions, ReduxDevtoolsState, ReduxExtension, Trace, TraceEvent, TraceLogger, Tracer, TracerOptions };
|
|
391
|
+
export type { ConsoleLoggerOptions, EffectName, ExportOptions, Finding, GraphLogger, GraphLoggerOptions, GraphSnapshot, JsonOptions, JsonValue, PayloadMode, PerformanceLoggerOptions, Phase, PolicyStage, PolicyStages, Recorder, ReduxDevtoolsOptions, ReduxDevtoolsState, ReduxExtension, SerializeOptions, TaskInfo, Trace, TraceEvent, TraceLogger, Tracer, TracerOptions };
|
package/dist/trace.js
CHANGED
|
@@ -1 +1,3 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{n as e,t}from"./onAbort-BgMfspK4.js";import{a as n,c as r,n as i,o as a,r as o,s}from"./context-B5Z2RpJt.js";import{n as c}from"./promise-CSLpzwSR.js";import{t as l}from"./origin-key-BbWN3gmN.js";import{n as u}from"./site-DLvUoXs3.js";import{t as d}from"./scope-key-B7l7mUS5.js";const f=e=>{let t=/* @__PURE__ */ new Map;for(let{from:n,to:r}of e){let e=t.get(n)??[];e.push(r),t.set(n,e),t.has(r)||t.set(r,[])}let n=/* @__PURE__ */ new Map,r=/* @__PURE__ */ new Map,i=/* @__PURE__ */ new Set,a=[],o=[],s=0,c=(e,t)=>{n.set(e,s),r.set(e,s),s+=1,a.push(e),i.add(e),t.push({name:e,next:0})},l=e=>{let n=[],r;do{if(r=a.pop(),r===void 0)break;i.delete(r),n.push(r)}while(r!==e);let[s]=n,c=n.length===1&&s!==void 0&&t.get(s)?.includes(s);(n.length>1||c)&&o.push(n.reverse())},u=(e,t)=>{r.set(e,Math.min(r.get(e)??t,t))},d=(e,r)=>{let a=t.get(r.name)?.[r.next];return a!==void 0&&(r.next+=1,n.has(a)?i.has(a)&&u(r.name,n.get(a)??0):c(a,e),!0)},f=(e,t)=>{e.pop();let i=e.at(-1);i&&u(i.name,r.get(t.name)??0),r.get(t.name)===n.get(t.name)&&l(t.name)},p=e=>{let t=[];c(e,t);for(let e=t.at(-1);e;e=t.at(-1))d(t,e)||f(t,e)};for(let e of t.keys())n.has(e)||p(e);return o},p=(e,t)=>{if(t===`prefix`){let t=e.indexOf(`/`);return t>0?e.slice(0,t):void 0}return t?.(e)},m=e=>e.replaceAll(`"`,`#quot;`).replaceAll(`<`,`#lt;`).replaceAll(`>`,`#gt;`).replaceAll(/\r?\n/g,` `),h=e=>e.replaceAll(`\\`,`\\\\`).replaceAll(`"`,`\\"`).replaceAll(/\r?\n/g,`\\n`),g=e=>new Map(e.nodes.map((e,t)=>[e.name,`n${t}`])),_=(e,t)=>{let n=/* @__PURE__ */ new Map;for(let{name:r}of e.nodes){let e=p(r,t);e!==void 0&&n.set(e,[...n.get(e)??[],r])}return n},v=(e,t={})=>{let n=g(e),r=[`flowchart LR`],i=/* @__PURE__ */ new Set;for(let[a,o]of _(e,t.group)){r.push(` subgraph g_${r.length}["${m(a)}"]`);for(let e of o)i.add(e),r.push(` ${n.get(e)}["${m(e)}"]`);r.push(` end`)}for(let{name:t}of e.nodes)i.has(t)||r.push(` ${n.get(t)}["${m(t)}"]`);let a=[];for(let[t,i]of e.edges.entries())r.push(` ${n.get(i.from)} -->|x${i.count}| ${n.get(i.to)}`),i.inCycle&&a.push(t);return a.length>0&&r.push(` linkStyle ${a.join(`,`)} stroke:red,stroke-width:2px`),r.join(`
|
|
2
|
+
`)},y=(e,t={})=>{let n=g(e),r=[`digraph events {`];e.name!==void 0&&r.push(` label="${h(e.name)}";`);let i=/* @__PURE__ */ new Set;for(let[a,o]of _(e,t.group)){r.push(` subgraph "cluster_${h(a)}" {`,` label="${h(a)}";`);for(let e of o)i.add(e),r.push(` ${n.get(e)} [label="${h(e)}"];`);r.push(` }`)}for(let{name:t}of e.nodes)i.has(t)||r.push(` ${n.get(t)} [label="${h(t)}"];`);for(let t of e.edges){let e=t.inCycle?`, color=red, penwidth=2`:``;r.push(` ${n.get(t.from)} -> ${n.get(t.to)} [label="x${t.count}"${e}];`)}return r.push(`}`),r.join(`
|
|
3
|
+
`)},b=e=>e.kind===`storm`?"an emit from a component or a timer repeats: stop it with `equals` of useEmitter, debounce, or a guard":e.kind===`runaway`?`one chain started a huge number of tasks: look for event names that are made from a counter or an id`:e.path.length<=2?`a handler emits its own event: emit a different event for the result`:`handlers feed each other: break the loop with a condition, or emit a different event for the result`,x=(e,t)=>{let n=t===void 0?`[nanoeffects]`:`[nanoeffects ${t}]`,r=e.traceGroupId===void 0?``:` (flow ${e.traceGroupId})`,i=e.kind===`cycle`?`cycle: ${e.path.join(` → `)} (repeated ${e.repeats} times, started at ${e.root})`:`${e.kind}: ${e.path.join(` → `)} (${e.repeats} in a row, started at ${e.root})`,a=[...new Set(e.path)].flatMap(e=>{let t=u(e);return t===void 0?[]:[`${e} at ${t}`]});return`${n} ${i}${r}${a.length>0?`\n ${a.join(`; `)}`:``}\n ${b(e)}`},S=(e,t)=>{if(!e)return;let n=e.connect({name:t});n.init({findings:0});let r=0;return e=>{r+=1,n.send({type:`cycle/detected`,kind:e.kind,path:e.path,repeats:e.repeats,root:e.root},{findings:r})}},C=2e3,w=e=>{let t=Object.getPrototypeOf(e),n=t===null?void 0:Reflect.get(t,`constructor`),r=typeof n==`function`?n.name:``;return typeof r==`string`&&r!==``?r:`Object`},T=e=>{let t=Object.getPrototypeOf(e);return t===null||t===Object.prototype},E=e=>typeof Reflect.get(e,`nodeType`)==`number`&&typeof Reflect.get(e,`nodeName`)==`string`,D=e=>e.length>C?`${e.slice(0,C)}…`:e,O=e=>{switch(typeof e){case`string`:return D(e);case`boolean`:return e;case`number`:return Number.isFinite(e)?e:String(e);case`bigint`:return`${e}n`;case`symbol`:return String(e);case`function`:return`[Function ${e.name||`anonymous`}]`;default:return e===null?null:void 0}},k=e=>{if(e instanceof WeakMap)return`[WeakMap]`;if(e instanceof WeakSet)return`[WeakSet]`;if(e instanceof Promise)return`[Promise]`;if(E(e))return`[DOM ${String(Reflect.get(e,`nodeName`))}]`;if(e instanceof Date)return Number.isNaN(e.getTime())?`[Invalid Date]`:e.toISOString()},A=(e,t,n)=>{if(t.budget<=0)return`[Cut]`;--t.budget;let r=O(e);return r===void 0?typeof e!=`object`||!e?`[undefined]`:j(e,t,n):r},j=(e,t,n)=>{try{let r=k(e);return r===void 0?t.path.has(e)?`[Circular]`:n>=t.maxDepth?Array.isArray(e)?`[Array]`:`[${w(e)}]`:M(e,t,n):r}catch{return`[Unserializable]`}},M=(e,t,n)=>{t.path.add(e);try{return N(e,t,n)}finally{t.path.delete(e)}},N=(e,t,n)=>e instanceof Error?P(e,t,n):Array.isArray(e)?F(e,t,n):e instanceof Map?{$map:F([...e.entries()],t,n)}:e instanceof Set?{$set:F([...e.values()],t,n)}:ArrayBuffer.isView(e)?{$class:w(e),length:Number(Reflect.get(e,`length`))}:I(e,t,n,T(e)?void 0:w(e)),P=(e,t,n)=>{let r={name:D(e.name),message:D(e.message)};return typeof e.stack==`string`&&(r.stack=D(e.stack)),`cause`in e&&(r.cause=A(e.cause,t,n+1)),r},F=(e,t,n)=>{let r=[];for(let[i,a]of e.entries()){if(t.budget<=0){r.push(`[${e.length-i} more]`);break}r.push(A(a,t,n+1))}return r},I=(e,t,n,r)=>{let i={};r!==void 0&&(i.$class=r);for(let r of Object.keys(e)){let a=Object.getOwnPropertyDescriptor(e,r);a&&(i[r]=`value`in a?A(a.value,t,n+1):`[Getter]`)}return i},L=(e,t={})=>{let n={maxDepth:t.maxDepth??6,budget:t.maxSize??1e3,path:/* @__PURE__ */ new Set};try{return A(e,n,0)}catch{return`[Unserializable]`}},R=(e,t)=>`${e}\u0000${t}`,z=e=>{if(typeof e==`function`)return e;let t=new Set(e);return e=>t.has(e)},B=e=>e.event===void 0?[e.node]:[e.event,e.node],V=e=>{let t=e.slice(0,-1),n=t.reduce((e,n,r)=>n<(t[e]??n)?r:e,0);return[...t.slice(n),...t.slice(0,n)].join(`\0`)},H=(e,t)=>{let{node:n}=t,r=[],i=e;for(;i&&i.node!==n;)r.push(i),i=i.parent;let a=r.reverse();return{path:[n,...a.flatMap(B),...B(t)],names:[n,...a.map(e=>e.node),n]}},U=(e={})=>{let t=e.repeats??3,n=e.maxSteps??5e3,r=e.storm===!1?void 0:e.storm??{count:100,perMs:1e3},i=z(e.ignore),a=e.now??(()=>globalThis.performance.now()),o=e.console??globalThis.console,s=/* @__PURE__ */ new Map,c=/* @__PURE__ */ new Map,l=/* @__PURE__ */ new Map,d=/* @__PURE__ */ new WeakMap,p=/* @__PURE__ */ new Map,m=/* @__PURE__ */ new Set,h=!1,g=!1,_,b,C=!1,w=!1,T=e=>{let t=s.get(e)??{count:0};t.count+=1,s.set(e,t)},E=(e,t)=>{let n=R(e,t),r=c.get(n)??{from:e,to:t,count:0,inCycle:!1};r.count+=1,c.set(n,r)},D=t=>{if(e.devtools&&!C){C=!0;let t=e.extension??globalThis.__REDUX_DEVTOOLS_EXTENSION__;b=S(t,e.name??`nanoeffects graph`)}b?.(t),e.onCycle?e.onCycle(t):o?.warn(x(t,e.name))},O=(e,t)=>{let n=l.get(e);if(n){n.turns+=1;return}let r=t();if(l.set(e,r),!w){w=!0;try{D(r)}catch(e){globalThis.console?.error(`[nanoeffects] graph logger report failed`,e)}finally{w=!1}}},k=(e,t)=>{let n=new Map(e.counts);n.set(t,(n.get(t)??0)+1),e.counts=n},A=(e,n)=>{let r=e.counts.get(e.node)??0;if(r<t||i(e.node))return;let{path:a,names:o}=H(e.parent,e);O(`cycle:${V(o)}`,()=>{let t={kind:`cycle`,path:a,repeats:r,turns:1,root:e.root.name,source:`chain`};return n.traceGroupId!==void 0&&(t.traceGroupId=n.traceGroupId),t})},j=e=>{e.root.steps+=1,!(e.root.steps<=n||e.root.flagged)&&(e.root.flagged=!0,O(`runaway:${e.root.name}`,()=>({kind:`runaway`,path:[e.root.name],repeats:e.root.steps,turns:1,root:e.root.name,source:`chain`})))},M=e=>{if(!r||i(e))return;let t=a(),n=(p.get(e)??[]).filter(e=>t-e<r.perMs);n.push(t),p.set(e,n),n.length<r.count?m.delete(e):m.has(e)||(m.add(e),O(`storm:${e}:${t}`,()=>({kind:`storm`,path:[e],repeats:n.length,turns:1,root:e,source:`chain`})))},N=(e,t)=>{let n=_;return n!==void 0&&n.parent===e&&n.type===t},P=(e,t,n)=>({node:e.type,parent:t,counts:t?.counts??/* @__PURE__ */ new Map,root:t?.root??{name:e.type,steps:0,flagged:!1},inner:n}),F=()=>{g||(g=!0,queueMicrotask(()=>{h||o?.warn(`[nanoeffects] graphLogger got tasks but no emit event: add bus.use(tracePlugin()) for a finer graph.`)}))},I=(e,t,n,r)=>{k(t,e.type),(e.leaf===!0||r||!h)&&(T(e.type),n?E(n.node,e.type):M(e.type)),e.leaf||i(e.type)||(j(t),A(t,e))},B=e=>{let t=e.parent?d.get(e.parent):void 0;e.leaf?(h=!0,_={parent:t,type:e.type}):F();let n=e.emitted===!1,r=!e.leaf&&h&&!n&&!N(t,e.type),i=r&&!t,a=P(e,t,n||r&&!i);d.set(e.ref,a),a.inner||I(e,a,t,i)},U=(e,t)=>{let n=new Map(e.counts),r=e.node;n.set(r,Math.max(0,(n.get(r)??1)-1)),n.set(t.type,(n.get(t.type)??0)+1),e.counts=n,e.node=t.type,e.event=t.event,T(t.type),t.event!==void 0&&E(t.event,t.type),e.inner||A(e,t)},W=e=>{let t=d.get(e.ref);t?U(t,e):B(e)},G=e=>{if(e.phase!==`done`&&e.phase!==`error`)return;let t=s.get(e.type);t&&(t.last=e.phase===`error`?{payload:e.payload,error:e.error}:{payload:e.payload})},K=()=>{let t=J(),n=/* @__PURE__ */ new Set;for(let e of t)q(e,n);let r={nodes:[...s].map(([e,t])=>{let n=u(e);return n===void 0?{name:e,count:t.count}:{name:e,count:t.count,site:n}}),edges:[...c.values()].map(e=>({...e,inCycle:n.has(R(e.from,e.to))})),cycles:t};return e.name!==void 0&&(r.name=e.name),r},q=(e,t)=>{if(e.source===`chain`){for(let[n,r]of e.path.entries()){let i=e.path[n+1];i!==void 0&&t.add(R(r,i))}return}let n=new Set(e.path);for(let e of c.values())n.has(e.from)&&n.has(e.to)&&t.add(R(e.from,e.to))},J=()=>{let e=[...l.values()].filter(e=>e.kind===`cycle`),t=new Set(e.flatMap(e=>e.path)),n=[],r=[...c.values()].filter(e=>!(i(e.from)||i(e.to)));for(let e of f(r))e.every(e=>t.has(e))||n.push({kind:`cycle`,path:e,repeats:e.length,turns:0,root:e[0]??``,source:`graph`});return[...l.values(),...n.filter(t=>!e.some(e=>e.path.join()===t.path.join()))]};return Object.assign(G,{task:W,cycles:()=>J(),toMermaid:e=>v(K(),e),toDot:e=>y(K(),e),toJSON:(e={})=>{let t=K();if(!e.payload)return t;for(let e of t.nodes){let t=s.get(e.name)?.last;t&&(e.last={payload:L(t.payload),...t.error!==void 0&&{error:L(t.error)}})}return t},clear:()=>{s=/* @__PURE__ */ new Map,c=/* @__PURE__ */ new Map,l=/* @__PURE__ */ new Map,d=/* @__PURE__ */ new WeakMap,p=/* @__PURE__ */ new Map,m=/* @__PURE__ */ new Set,_=void 0}})},W=(e={})=>{let t=e.console??globalThis.console,n=e.prefix??`[nanoeffects]`,r=/* @__PURE__ */ new Map;return({id:e,parentId:i,type:a,effect:o,phase:s,payload:c,error:l,traceGroupId:u})=>{r.has(e)||r.set(e,typeof i==`number`?(r.get(i)??0)+1:0);let d=` `.repeat(r.get(e)??0);s!==`start`&&r.delete(e);let f=u===void 0?``:` [${u}]`,p=`${n}${f} ${d}${a} · ${o} ${s}`;s===`error`?t?.error(p,l):t?.debug(p,c)}},G=e=>{if(e instanceof Error)return`${e.name}: ${e.message}`;let t=L(e);return typeof t==`string`?t:JSON.stringify(t)},K=(e={})=>{let t=e.performance??globalThis.performance,n=/* @__PURE__ */ new Map;return({id:e,parentId:r,rootId:i,traceGroupId:a,type:o,effect:s,phase:c,error:l})=>{if(!t)return;if(c===`start`){n.set(e,t.now());return}let u=`${o} · ${s} (${c})`,d={id:e,parentId:r,...i!==void 0&&{rootId:i},...a!==void 0&&{traceGroupId:a},phase:c,...c===`error`&&{error:G(l)}},f=n.get(e);n.delete(e),typeof f==`number`?t.measure(u,{start:f,end:t.now(),detail:d}):t.mark(u,{detail:d})}};let q;const J=()=>q,Y=(e,t)=>{let n=q;q=e;try{return t()}finally{q=n}},X=(e,t,n={})=>{let{trace:r}=e;if(!r){e.emit(t);return}let{traceId:a,traceGroupId:o}=n,{ctx:c,unlink:l}=i({event:{type:a??t.type,payload:t,...o===void 0?{}:{traceGroupId:o}},bus:{emit:e.emit,on:e.on,off:e.off,ctx:e.ctx,addContext:e.addContext},tracer:r});s(c,`run`,`start`);try{c.emit(t),s(c,`run`,`done`)}catch(e){throw s(c,`run`,`error`,e),e}finally{l()}},Z=e=>typeof e==`string`?e:Reflect.get(Object(e),`type`),Q=(e,t)=>typeof e==`object`?Reflect.get(Object(e),`payload`):t[0],$=()=>e=>{if(!(r&&e.trace))return e;let{trace:t}=e,i=(e,n)=>{let r=t.task({type:Z(e),payload:Q(e,n),leaf:!0},o());t.report(r,`emit`,`done`)},s=e.emit;e.emit=(e,...t)=>Y(e,()=>(i(e,t),s(e,...t))),Reflect.set(e,l,(t,n)=>{X(e,t,n)});let c=/* @__PURE__ */ new WeakMap,u=(e,t,n)=>{let r=c.get(t)??/* @__PURE__ */ new Map,i=Z(e);r.set(i,[...r.get(i)??[],n]),c.set(t,r)},d=(e,t)=>(c.get(t)?.get(Z(e)))?.pop(),f=(e,t)=>(r,i)=>(n(i,t),a(i,`run`,()=>e(r,i))),p=(e,t)=>(n,r,i)=>{let a=Reflect.get(Object(i),`traceId`);if(typeof a!=`string`)return e(n,r,i);let o=t?(e,t)=>(d(n,r),f(r,a)(e,t)):f(r,a);u(n,r,o);let s=e(n,o,i);return()=>{s()}},m=e.off,h=(e,t)=>{t===void 0?m(e):m(e,d(e,t)??t)},g=e.on,_=e.once;return e.on=p(g,!1),e.once=p(_,!0),e.off=h,e},ee=e=>{let t=e?.capacity??0,n=Number.isInteger(t)&&t>0?t:0,r=[],i=0,a=e=>{n===0?r.push(e):(r[i%n]=e,i+=1)},o=()=>{r.length=0,i=0},s=()=>{let e=Math.min(i,n),t=i>n?i%n:0,a=[];for(let i=0;i<e;i+=1){let e=r[(t+i)%n];e&&a.push(e)}return a},c=Object.assign(a,{clear:o,events:r,toJSON:()=>(n>0?s():r).map(e=>L(e))});return n>0&&Object.defineProperty(c,"events",{get:s}),c},te=()=>{},ne=/* @__PURE__ */ new Set([`cancel`,`drop`,`error`]),re=()=>!1,ie=()=>!0,ae=e=>{if(typeof e==`function`)return e;if(Array.isArray(e)){let t=new Set(e);return({phase:e})=>t.has(e)}return e?ie:re},oe=(e={})=>{let t=e.extension??globalThis.__REDUX_DEVTOOLS_EXTENSION__;if(!t)return te;let{starts:n=!1,depth:r=0,maxAge:i}=e,a=ae(e.policies),o=e.payload??`essential`,s=e.serialize===!1?e=>e:e.serialize??(e=>L(e)),c=t.connect({name:e.name??`nanoeffects`,...i!==void 0&&{maxAge:i}}),l=/* @__PURE__ */ new Map,u=/* @__PURE__ */ new Map,d=()=>({running:Object.fromEntries(l)});c.init(d());let f=(e,t,n)=>n===0?a({effect:e,phase:t}):n<=r,p=(e,t)=>{e.phase===`start`?(u.set(e.id,t),f(e.effect,`start`,t)&&l.set(e.id,{type:e.type,effect:e.effect,...e.traceGroupId!==void 0&&{traceGroupId:e.traceGroupId}})):(u.delete(e.id),l.delete(e.id))},m=!1,h=!1,g=(e,t)=>{m||h||t!==0||e===`start`||(h=!0,globalThis.console?.warn(`[nanoeffects] reduxDevtools got a task but no emit event: add bus.use(tracePlugin()) to see the emits.`))},_=e=>{let{payload:t,...n}=e;return o===`all`||o===`essential`&&(e.effect===`emit`||ne.has(e.phase))?{...n,payload:s(t)}:n},v=(e,t)=>{c.send({..._(e),type:`${e.type}/${e.effect}/${e.phase}`,event:e.type,...e.phase===`error`&&{error:t instanceof Error?t.message:s(t)}},d())};return({error:e,...t})=>{if(t.effect===`emit`){m=!0,c.send({..._(t),event:t.type},d());return}let r=(t.parentId===void 0?-1:u.get(t.parentId)??-1)+1;p(t,r),g(t.phase,r),(t.phase===`error`&&r===0||f(t.effect,t.phase,r))&&(t.phase!==`start`||n)&&v(t,e)}},se=t=>{let n=typeof t==`function`?[t]:[...t??[]];return n.length===0?null:t=>{for(let r of n)try{r(t)}catch(t){e(`trace logger failed`,t)}}},ce=()=>{},le=e=>{if(typeof e.traceGroupId==`string`)return e.traceGroupId;let t=J();if(typeof t!=`object`&&typeof t!=`function`||t===null)return;let n=Reflect.get(t,d),r=typeof n==`object`&&n?Reflect.get(n,`traceGroupId`):void 0;return typeof r==`string`?r:void 0},ue=(n,r={})=>{let i=n,a=se(i),{filter:s,sample:l=1}=r,u=0,d=(Array.isArray(i)?i:[i]).flatMap(e=>e.task?[e.task]:[]),f=t=>{let n={ref:t,id:t.id,type:t.type};t.parentRef&&(n.parent=t.parentRef),t.event!==void 0&&(n.event=t.event),t.leaf&&(n.leaf=!0),n.emitted=t.emitted,t.group!==void 0&&(n.traceGroupId=t.group);for(let t of d)try{t(n)}catch(t){e(`trace logger failed`,t)}},p=e=>typeof l==`function`?l({type:e.type,payload:e.payload}):Math.random()<l,m={task:(e,t)=>{let n={id:++u,root:t?.root??u,parent:t?.id,parentRef:d.length>0?t:void 0,leaf:e.leaf,emitted:o()===t,type:e.type,payload:e.payload,sampled:t?.sampled??p(e),group:le(e)??t?.group};return d.length>0&&n.sampled&&f(n),n},rename:(e,t)=>{e.event??=e.type,e.type=t,d.length>0&&e.sampled&&f(e)},report:(e,t,n,r)=>{if(!e.sampled)return;let i={id:e.id,type:e.type,effect:t,phase:n,payload:e.payload};typeof e.parent==`number`&&(i.parentId=e.parent),e.root!==e.id&&(i.rootId=e.root),e.group!==void 0&&(i.traceGroupId=e.group),n===`error`&&(i.error=r),(!s||s(i))&&a?.(i)},settler:(e,t)=>{if(!e.sampled)return ce;let n=!1;return(r,i)=>{n||(n=!0,m.report(e,t,r,i))}},run:(e,n,r,i)=>{if(!e.sampled)return i();let a=m.settler(e,n);m.report(e,n,`start`),t({signal:r},()=>a(`cancel`));let o;try{o=i()}catch(e){throw a(`error`,e),e}return c(o)?Promise.resolve(o).then(e=>(a(`done`),e),e=>{throw a(`error`,e),e}):(a(`done`),o)}};return m};export{W as consoleLogger,ee as createTraceRecorder,ue as createTracer,U as graphLogger,K as performanceLogger,oe as reduxDevtools,L as safeSerialize,$ as tracePlugin};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { H as EventMap, c as Scope, l as WithFields, p as Effects } from "./action-creator-
|
|
2
|
-
import { t as BoundOf } from "./plugin-
|
|
1
|
+
import { H as EventMap, c as Scope, l as WithFields, p as Effects } from "./action-creator-6rlBTmH9.js";
|
|
2
|
+
import { t as BoundOf } from "./plugin-BWNP23hr.js";
|
|
3
3
|
//#region src/scope/types.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* The slots `scopePlugin` fills for `modulePlugin`. They are not on `Bus`: a bus
|
package/dist/vue.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { H as EventMap, T as EventKey, a as FreeActionCreator, t as ActionCreator, v as Bus } from "./action-creator-6rlBTmH9.js";
|
|
2
2
|
import { t as OriginOptions } from "./origin-key-CxK0SkeG.js";
|
|
3
3
|
import { t as StateOf } from "./state-of-C19H6BoV.js";
|
|
4
4
|
import { ShallowRef, WatchSource, WatchStopHandle } from "vue";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"./context-
|
|
1
|
+
import{n as e}from"./context-B5Z2RpJt.js";const t=Reflect.get(Symbol,`dispose`),n=n=>{let r=new AbortController,i=async(...t)=>{let i=t[0],{ctx:a,unlink:o}=e({event:{type:`withPolicy`,payload:i},parent:{signal:r.signal}});try{return await n(i,a)}finally{o()}},a=e=>{r.abort(e),r=new AbortController},o=Object.assign(i,{cancel:a});return t&&Reflect.set(o,t,()=>a()),o};export{n as t};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { G as PayloadOf, H as EventMap, q as Task } from "./action-creator-
|
|
1
|
+
import { G as PayloadOf, H as EventMap, q as Task } from "./action-creator-6rlBTmH9.js";
|
|
2
2
|
//#region src/policies/with-policy.d.ts
|
|
3
3
|
/** What `withPolicy` returns: the task as a plain function that can be cancelled. */
|
|
4
4
|
type PolicyFunction<P, R> = {
|
package/docs/README.md
CHANGED
|
@@ -1,14 +1,66 @@
|
|
|
1
1
|
# Documentation
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
nanoeffects moves business logic and side effects out of UI components into plain `async` functions. Policies (`latest`, `debounce`, `queue`) and effects (`retry`, `timeout`, `poll`) handle races, retries and cancellation.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
```js
|
|
6
|
+
import { createBus } from "nanoeffects";
|
|
7
|
+
import { latest } from "nanoeffects/policies";
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
const bus = createBus(); // event bus
|
|
10
|
+
|
|
11
|
+
// The search logic lives outside the component; latest aborts the previous request
|
|
12
|
+
bus.on(
|
|
13
|
+
"search",
|
|
14
|
+
latest(async (query, { signal }) => {
|
|
15
|
+
const items = await api.search(query, { signal });
|
|
16
|
+
bus.emit("found", items);
|
|
17
|
+
}),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
// The component only listens for the result and sends events
|
|
21
|
+
bus.on("found", (items) => render(items));
|
|
22
|
+
bus.emit("search", "nano");
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The same example with a UI framework: [React](react.md), [Vue](vue.md), [Svelte](svelte.md). Ready projects: [Start a project from a template](starter.md).
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install nanoeffects
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
To start a new project from a template ([Start a project from a template](starter.md)):
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm create nanoeffects@latest my-app
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Where to start
|
|
40
|
+
|
|
41
|
+
1. Follow the [Guide](guide.md#1-create-a-bus): from the first bus to modules.
|
|
42
|
+
2. Common async UI problems (stale responses, double clicks, retries) are in [Common tasks](recipes.md).
|
|
43
|
+
|
|
44
|
+
## The ladder, from A to F
|
|
45
|
+
|
|
46
|
+
Read the docs from the level of your project and stop when it has what it needs. Each level adds one idea. A page says at its top which level it is for and what to read first.
|
|
47
|
+
|
|
48
|
+
| Level | Your project | You can stop here when | Read |
|
|
49
|
+
| ----- | ------------ | ---------------------- | ---- |
|
|
50
|
+
| **A** | A small app, widget or MVP with rare races | Events and handlers do the job | [Guide](guide.md#1-create-a-bus) |
|
|
51
|
+
| **B** | A SPA with async UI: search, sending messages, unmounting components | A newer request must replace an older one | [Policies](policies.md), [Guide](guide.md#3-abort-the-previous-run-with-latest) |
|
|
52
|
+
| **C** | Complex business logic | Retries, timeouts and waiting are handled | [Effects](effects.md), [Guide](guide.md#5-add-retry-and-timeout) |
|
|
53
|
+
| **D** | Domain logic that must live off the UI | Features are actions and modules, not components | [Actions, jobs and modules](actions.md), [Guide](guide.md#7-name-events-three-ways) |
|
|
54
|
+
| **E** | An existing Redux, Zustand, MobX or Pinia app, or a data library | The bus runs the async work next to your store | [Stores](stores.md), [Query libraries](query-libraries.md) |
|
|
55
|
+
| **F** | A platform, an SDK or a long-lived app | You can see what ran and reload modules | [Tracing](tracing.md), [Hot reload](hmr.md), [Semantics](semantics.md) |
|
|
56
|
+
|
|
57
|
+
A small app never needs level B or higher. Move up when the first problem of that level appears, not before.
|
|
8
58
|
|
|
9
59
|
Code for every guide step: [`examples/guide.ts`](../examples/guide.ts) (type-checked and tested).
|
|
10
60
|
|
|
11
|
-
## Jump by
|
|
61
|
+
## Jump by need
|
|
62
|
+
|
|
63
|
+
For a problem, start from [Common tasks](recipes.md). For a project shape:
|
|
12
64
|
|
|
13
65
|
| Project shape | First page |
|
|
14
66
|
| ------------- | ---------- |
|
|
@@ -23,29 +75,20 @@ Code for every guide step: [`examples/guide.ts`](../examples/guide.ts) (type-che
|
|
|
23
75
|
| A Vue app | [Use nanoeffects in a Vue app](vue.md) |
|
|
24
76
|
| A Svelte app | [Use nanoeffects in a Svelte app](svelte.md) |
|
|
25
77
|
| Old responses overwrite new ones | [Cancel stale requests](cancel-stale-requests.md) |
|
|
26
|
-
| Debounced input that must also cancel the old request | [
|
|
78
|
+
| Debounced input that must also cancel the old request | [Pause between requests](debounce-and-abort.md) |
|
|
27
79
|
| A second click sends a second request | [Prevent a double submit](prevent-double-submit.md) |
|
|
28
|
-
| Using mitt
|
|
80
|
+
| Using mitt or nanoevents | [Coming from mitt](migrating-from-mitt.md) |
|
|
29
81
|
| Moving from redux-saga | [Coming from redux-saga](migrating-from-saga.md) |
|
|
30
82
|
| Using RxJS | [Use nanoeffects next to RxJS](migrating-from-rxjs.md) |
|
|
31
83
|
| Moving from Effector | [Coming from Effector](migrating-from-effector.md) |
|
|
32
84
|
|
|
33
85
|
## Reference
|
|
34
86
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- [
|
|
38
|
-
- [
|
|
39
|
-
- [
|
|
40
|
-
- [
|
|
41
|
-
- [
|
|
42
|
-
- [Use the bus from a class](oop.md) — `extendBus`, `@WithBus`, `@On`, and `dispose()` for the subscriptions of an instance
|
|
43
|
-
- [TypeScript in many files](typescript.md) — keep the types of the bus in commands and modules
|
|
44
|
-
- [Errors and unheard events](errors.md)
|
|
45
|
-
- [Package entries](package.md) — which import holds what
|
|
46
|
-
- [Stability](stability.md) — which entries are stable, what is breaking, supported versions
|
|
47
|
-
- [Plugins](plugins.md) — every plugin of a bus, what it returns, and the order to use them in
|
|
48
|
-
- [Tracing](tracing.md) — loggers, Redux DevTools, `notrace`
|
|
49
|
-
- [Observable](observable.md) — events as TC39 Observables
|
|
87
|
+
Look these up when you need the exact rule.
|
|
88
|
+
|
|
89
|
+
- [Package entries](package.md): which import holds what
|
|
90
|
+
- [Plugins](plugins.md): every plugin of a bus, what it returns, and the order to use them in
|
|
91
|
+
- [Semantics](semantics.md): the exact rules of emit, abort and ordering
|
|
92
|
+
- [Tracing](tracing.md): loggers, Redux DevTools, `notrace`
|
|
93
|
+
- [Observable](observable.md): events as TC39 Observables
|
|
50
94
|
- [Hot reload and dev diagnostics](hmr.md)
|
|
51
|
-
- [Semantics](semantics.md)
|