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.
Files changed (139) hide show
  1. package/README.md +6 -5
  2. package/dist/{action-creator-CcAEvUnw.d.ts → action-creator-6rlBTmH9.d.ts} +60 -33
  3. package/dist/actions.d.ts +18 -18
  4. package/dist/actions.js +1 -1
  5. package/dist/bus-DFlmNJ6h.js +1 -0
  6. package/dist/context-B5Z2RpJt.js +1 -0
  7. package/dist/context-types-DKNc5mkS.d.ts +31 -0
  8. package/dist/context.d.ts +8 -3
  9. package/dist/context.js +1 -1
  10. package/dist/{delay-18LJ8jSk.js → delay-5OhJZZQ0.js} +1 -1
  11. package/dist/dev.d.ts +44 -3
  12. package/dist/dev.js +2 -1
  13. package/dist/effects.d.ts +2 -2
  14. package/dist/effects.js +1 -1
  15. package/dist/emit-key-C6FL7Ewn.js +1 -0
  16. package/dist/guard.d.ts +59 -0
  17. package/dist/guard.js +1 -0
  18. package/dist/index.d.ts +2 -2
  19. package/dist/index.js +1 -1
  20. package/dist/match.d.ts +3 -3
  21. package/dist/match.js +1 -1
  22. package/dist/{module-C_oEXw1n.d.ts → module-G39MaVOt.d.ts} +38 -38
  23. package/dist/notrace/{action-creator-DxOvVo8I.d.ts → action-creator-BrMSO3RP.d.ts} +35 -32
  24. package/dist/notrace/actions.d.ts +52 -52
  25. package/dist/notrace/actions.js +1 -1
  26. package/dist/notrace/{bus-DmKn4SDD.js → bus-37nlVljc.js} +1 -1
  27. package/dist/notrace/effects.d.ts +2 -2
  28. package/dist/notrace/index.d.ts +2 -2
  29. package/dist/notrace/index.js +1 -1
  30. package/dist/notrace/{plugin-lJjjd-SN.d.ts → plugin-7GUL2EW5.d.ts} +1 -1
  31. package/dist/notrace/policies.d.ts +1 -1
  32. package/dist/notrace/redux.d.ts +1 -1
  33. package/dist/observable.d.ts +43 -1
  34. package/dist/observable.js +1 -1
  35. package/dist/onAbort-BgMfspK4.js +1 -0
  36. package/dist/oop.d.ts +3 -3
  37. package/dist/oop.js +1 -1
  38. package/dist/{plugin-CuvIgjnX.d.ts → plugin-BWNP23hr.d.ts} +2 -2
  39. package/dist/policies.d.ts +2 -2
  40. package/dist/policies.js +1 -1
  41. package/dist/production/dev.js +1 -1
  42. package/dist/react.d.ts +2 -2
  43. package/dist/react.js +1 -1
  44. package/dist/redux.d.ts +1 -1
  45. package/dist/scope.d.ts +9 -9
  46. package/dist/signal-field-key-CWT-3OXC.js +1 -0
  47. package/dist/signal.d.ts +34 -6
  48. package/dist/signal.js +1 -1
  49. package/dist/site-DLvUoXs3.js +1 -0
  50. package/dist/svelte.d.ts +1 -1
  51. package/dist/trace.d.ts +170 -6
  52. package/dist/trace.js +3 -1
  53. package/dist/{types-FV8fuvI9.d.ts → types-N_cdRr1r.d.ts} +2 -2
  54. package/dist/vue.d.ts +1 -1
  55. package/dist/{with-policy-D-SQRkPh.js → with-policy-CeM1rtES.js} +1 -1
  56. package/dist/{with-policy-3bhFLEXs.d.ts → with-policy-CthiBp_z.d.ts} +1 -1
  57. package/docs/README.md +65 -22
  58. package/docs/actions.md +56 -13
  59. package/docs/cancel-stale-requests.md +2 -0
  60. package/docs/chat-example.md +15 -1
  61. package/docs/chat-widget.md +9 -7
  62. package/docs/context.md +352 -8
  63. package/docs/debounce-and-abort.md +3 -1
  64. package/docs/debugging-loops.md +283 -0
  65. package/docs/effects.md +259 -131
  66. package/docs/errors.md +4 -2
  67. package/docs/examples.md +60 -0
  68. package/docs/guide.md +57 -22
  69. package/docs/hmr.md +35 -1
  70. package/docs/loop-guard.md +109 -0
  71. package/docs/migrating-from-effector.md +15 -2
  72. package/docs/migrating-from-mitt.md +6 -4
  73. package/docs/migrating-from-rxjs.md +2 -0
  74. package/docs/migrating-from-saga.md +2 -0
  75. package/docs/motivation.md +14 -21
  76. package/docs/observable.md +45 -0
  77. package/docs/oop.md +20 -1
  78. package/docs/package.md +25 -13
  79. package/docs/pass-the-signal.md +281 -0
  80. package/docs/plugins.md +90 -15
  81. package/docs/policies.md +73 -46
  82. package/docs/prevent-double-submit.md +3 -1
  83. package/docs/query-libraries.md +276 -6
  84. package/docs/react.md +50 -11
  85. package/docs/recipes.md +528 -0
  86. package/docs/ru/README.md +62 -0
  87. package/docs/ru/actions.md +412 -0
  88. package/docs/ru/cancel-stale-requests.md +106 -0
  89. package/docs/ru/chat-example.md +259 -0
  90. package/docs/ru/chat-widget.md +131 -0
  91. package/docs/ru/context.md +401 -0
  92. package/docs/ru/debounce-and-abort.md +105 -0
  93. package/docs/ru/debugging-loops.md +283 -0
  94. package/docs/ru/effects.md +538 -0
  95. package/docs/ru/errors.md +36 -0
  96. package/docs/ru/examples.md +60 -0
  97. package/docs/ru/guide.md +530 -0
  98. package/docs/ru/hmr.md +83 -0
  99. package/docs/ru/loop-guard.md +109 -0
  100. package/docs/ru/migrating-from-effector.md +339 -0
  101. package/docs/ru/migrating-from-mitt.md +99 -0
  102. package/docs/ru/migrating-from-rxjs.md +197 -0
  103. package/docs/ru/migrating-from-saga.md +97 -0
  104. package/docs/ru/motivation.md +47 -0
  105. package/docs/ru/observable.md +233 -0
  106. package/docs/ru/oop.md +431 -0
  107. package/docs/ru/package.md +42 -0
  108. package/docs/ru/pass-the-signal.md +281 -0
  109. package/docs/ru/plugins.md +398 -0
  110. package/docs/ru/policies.md +201 -0
  111. package/docs/ru/prevent-double-submit.md +122 -0
  112. package/docs/ru/query-libraries.md +392 -0
  113. package/docs/ru/react.md +309 -0
  114. package/docs/ru/recipes.md +528 -0
  115. package/docs/ru/semantics.md +19 -0
  116. package/docs/ru/server.md +204 -0
  117. package/docs/ru/starter.md +118 -0
  118. package/docs/ru/stores.md +673 -0
  119. package/docs/ru/svelte.md +78 -0
  120. package/docs/ru/tracing.md +466 -0
  121. package/docs/ru/typescript.md +170 -0
  122. package/docs/ru/vue.md +92 -0
  123. package/docs/semantics.md +12 -0
  124. package/docs/server.md +5 -3
  125. package/docs/starter.md +2 -0
  126. package/docs/stores.md +2 -0
  127. package/docs/svelte.md +3 -1
  128. package/docs/tracing.md +132 -7
  129. package/docs/typescript.md +3 -1
  130. package/docs/vue.md +3 -1
  131. package/llms-full.txt +2718 -545
  132. package/llms.txt +17 -14
  133. package/package.json +25 -8
  134. package/skills/nanoeffects/SKILL.md +2 -2
  135. package/dist/bus-DlpEluzd.js +0 -1
  136. package/dist/context-UcmrAIhP.js +0 -1
  137. package/dist/context-types-hCKgU8sC.d.ts +0 -14
  138. package/docs/scope.md +0 -266
  139. package/docs/stability.md +0 -90
package/dist/signal.d.ts CHANGED
@@ -1,4 +1,26 @@
1
- import { H as EventMap, O as EventLike, T as Emit, s as EmitScope, u as WithScope, v as Bus, x as CommandCall } from "./action-creator-CcAEvUnw.js";
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: CommandCall<P, R> & WithScope<F>, options: SignalOption & EmitScope<E, F>): R;
11
- <P, R>(call: CommandCall<P, R>, options: SignalOption & EmitScope<E>): R;
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 command or job and the listeners of its type, and their
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 a command or a job the link ends when its promise settles; a handler
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{u as e}from"./context-UcmrAIhP.js";import{n as t}from"./promise-CSLpzwSR.js";import{t as n}from"./bus-DlpEluzd.js";const r=e=>typeof e==`object`&&!!e,i=(e,t)=>{let n=t[0];if(!r(n)||!r(e)&&typeof e!=`function`)return;let i=Reflect.get(n,`signal`);return i instanceof AbortSignal?i:void 0},a=(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},o=()=>e=>{let t,n,r=e.addContext;e.addContext=e=>{r?.(e),t?.push({target:e}),n?.aborted&&e.abort(n.reason)};let o=e.emit;return e.emit=(e,...r)=>{let s=i(e,r);if(!(s||t))return o(e,...r);let c=t,l=n,u=[];t=s?u:void 0,n=s;let d;try{d=o(e,...r)}finally{t=c,n=l}return s?a(s,u,e,d):d},e};export{o as signalPlugin};
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 { D as EventKey, H as EventMap, a as FreeActionCreator, t as ActionCreator, v as Bus } from "./action-creator-CcAEvUnw.js";
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 Trace, K as Phase, V as EffectName, X as TraceLogger, Y as TraceEvent, v as Bus } from "./action-creator-CcAEvUnw.js";
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 a command, a job or an action goes to every
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
- /** Every event received so far, in order. */
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; meant for tests.
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>>() => Recorder<E>;
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{a as e,c as t,d as n,n as r,o as i,r as a,s as o,u as s}from"./context-UcmrAIhP.js";import{n as c}from"./promise-CSLpzwSR.js";import{t as l}from"./origin-key-BbWN3gmN.js";import{t as u}from"./scope-key-B7l7mUS5.js";const d=(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)}},f=(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:String(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 p;const m=()=>p,h=(e,t)=>{let n=p;p=e;try{return t()}finally{p=n}},g=(e,t,n={})=>{let{trace:i}=e;if(!i){e.emit(t);return}let{traceId:a,traceGroupId:s}=n,{ctx:c,unlink:l}=r({event:{type:a??t.type,payload:t,...s===void 0?{}:{traceGroupId:s}},bus:{emit:e.emit,on:e.on,off:e.off,ctx:e.ctx,addContext:e.addContext},tracer:i});o(c,`run`,`start`);try{c.emit(t),o(c,`run`,`done`)}catch(e){throw o(c,`run`,`error`,e),e}finally{l()}},_=e=>typeof e==`string`?e:Reflect.get(Object(e),`type`),v=(e,t)=>typeof e==`object`?Reflect.get(Object(e),`payload`):t[0],y=()=>n=>{if(!(t&&n.trace))return n;let{trace:r}=n,o=(e,t)=>{let n=r.task({type:_(e),payload:v(e,t)},a());r.report(n,`emit`,`done`)},s=n.emit;n.emit=(e,...t)=>h(e,()=>(o(e,t),s(e,...t))),Reflect.set(n,l,(e,t)=>{g(n,e,t)});let c=/* @__PURE__ */ new WeakMap,u=(e,t,n)=>{let r=c.get(t)??/* @__PURE__ */ new Map,i=_(e);r.set(i,[...r.get(i)??[],n]),c.set(t,r)},d=(e,t)=>(c.get(t)?.get(_(e)))?.pop(),f=(t,n)=>(r,a)=>(e(a,n),i(a,`run`,()=>t(r,a))),p=(e,t)=>(n,r,i)=>{let a=Reflect.get(Object(i),`traceId`);if(typeof a!=`string`)return e(n,r);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);return()=>{s()}},m=n.off,y=(e,t)=>{t===void 0?m(e):m(e,d(e,t)??t)},b=n.on,x=n.once;return n.on=p(b,!1),n.once=p(x,!0),n.off=y,n},b=()=>{let e=[];return Object.assign(t=>{e.push(t)},{events:e,clear:()=>{e.length=0}})},x=()=>{},S=/* @__PURE__ */ new Set([`cancel`,`drop`,`error`]),C=()=>!1,w=()=>!0,T=e=>{if(typeof e==`function`)return e;if(Array.isArray(e)){let t=new Set(e);return({phase:e})=>t.has(e)}return e?w:C},E=(e={})=>{let t=e.extension??globalThis.__REDUX_DEVTOOLS_EXTENSION__;if(!t)return x;let{starts:n=!1,depth:r=0,maxAge:i}=e,a=T(e.policies),o=e.payload??`essential`,s=t.connect({name:e.name??`nanoeffects`,...i!==void 0&&{maxAge:i}}),c=/* @__PURE__ */ new Map,l=/* @__PURE__ */ new Map,u=()=>({running:Object.fromEntries(c)});s.init(u());let d=(e,t,n)=>n===0?a({effect:e,phase:t}):n<=r,f=(e,t)=>{e.phase===`start`?(l.set(e.id,t),d(e.effect,`start`,t)&&c.set(e.id,{type:e.type,effect:e.effect,...e.traceGroupId!==void 0&&{traceGroupId:e.traceGroupId}})):(l.delete(e.id),c.delete(e.id))},p=!1,m=!1,h=(e,t)=>{p||m||t!==0||e===`start`||(m=!0,globalThis.console?.warn(`[nanoeffects] reduxDevtools got a task but no emit event: add bus.use(tracePlugin()) to see the emits.`))},g=e=>{let{payload:t,...n}=e;return o===`all`||o===`essential`&&(e.effect===`emit`||S.has(e.phase))?{...n,payload:t}:n},_=(e,t)=>{s.send({...g(e),type:`${e.type}/${e.effect}/${e.phase}`,event:e.type,...e.phase===`error`&&{error:t instanceof Error?t.message:t}},u())};return({error:e,...t})=>{if(t.effect===`emit`){p=!0,s.send({...g(t),event:t.type},u());return}let r=(t.parentId===void 0?-1:l.get(t.parentId)??-1)+1;f(t,r),h(t.phase,r),(t.phase===`error`&&r===0||d(t.effect,t.phase,r))&&(t.phase!==`start`||n)&&_(t,e)}},D=e=>{let t=typeof e==`function`?[e]:[...e??[]];return t.length===0?null:e=>{for(let r of t)try{r(e)}catch(e){n(`trace logger failed`,e)}}},O=()=>{},k=e=>{if(typeof e.traceGroupId==`string`)return e.traceGroupId;let t=m();if(typeof t!=`object`&&typeof t!=`function`||t===null)return;let n=Reflect.get(t,u),r=typeof n==`object`&&n?Reflect.get(n,`traceGroupId`):void 0;return typeof r==`string`?r:void 0},A=(e,t={})=>{let n=D(e),{filter:r,sample:i=1}=t,a=0,o=e=>typeof i==`function`?i({type:e.type,payload:e.payload}):Math.random()<i,l={task:(e,t)=>({id:++a,root:t?.root??a,parent:t?.id,type:e.type,payload:e.payload,sampled:t?.sampled??o(e),group:k(e)??t?.group}),rename:(e,t)=>{e.type=t},report:(e,t,i,a)=>{if(!e.sampled)return;let o={id:e.id,type:e.type,effect:t,phase:i,payload:e.payload};typeof e.parent==`number`&&(o.parentId=e.parent),e.root!==e.id&&(o.rootId=e.root),e.group!==void 0&&(o.traceGroupId=e.group),i===`error`&&(o.error=a),(!r||r(o))&&n?.(o)},settler:(e,t)=>{if(!e.sampled)return O;let n=!1;return(r,i)=>{n||(n=!0,l.report(e,t,r,i))}},run:(e,t,n,r)=>{if(!e.sampled)return r();let i=l.settler(e,t);l.report(e,t,`start`),s({signal:n},()=>i(`cancel`));let a;try{a=r()}catch(e){throw i(`error`,e),e}return c(a)?Promise.resolve(a).then(e=>(i(`done`),e),e=>{throw i(`error`,e),e}):(i(`done`),a)}};return l};export{d as consoleLogger,b as createTraceRecorder,A as createTracer,f as performanceLogger,E as reduxDevtools,y as tracePlugin};
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-CcAEvUnw.js";
2
- import { t as BoundOf } from "./plugin-CuvIgjnX.js";
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 { D as EventKey, H as EventMap, a as FreeActionCreator, t as ActionCreator, v as Bus } from "./action-creator-CcAEvUnw.js";
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-UcmrAIhP.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
+ 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-CcAEvUnw.js";
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
- ## Start here
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
- The [Guide](guide.md) is the ladder: small bus → policies for async UI → effects when you need them → handlers and modules for domain logic off the UI.
5
+ ```js
6
+ import { createBus } from "nanoeffects";
7
+ import { latest } from "nanoeffects/policies";
6
8
 
7
- Why the library exists (logic and side effects outside React, Vue and Svelte components, as plain `async` functions), and when to pick it over redux-saga, RxJS, Redux or Effector (including SDKs and Module Federation): [Why nanoeffects exists](motivation.md).
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 project need
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 | [Debounce and abort](debounce-and-abort.md) |
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, nanoevents or EventEmitter | [Coming from mitt](migrating-from-mitt.md) |
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
- - [Policies](policies.md) — `latest`, `leading`, `debounce`, `throttle`, `queue`, `key`
36
- - [Effects and cancellation](effects.md) — `retry`, `timeout`, `poll`, `take`, `all`, …
37
- - [Actions, jobs and modules](actions.md)
38
- - [Sign-up form in React](react.md) — one form: races, double submit, retry
39
- - [nanoeffects in a Vue app](vue.md) — `watchEmitter` and `useEventRef`
40
- - [nanoeffects in a Svelte app](svelte.md) — `eventStore`
41
- - [Context fields](context.md) — store or client on every task
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)