react-flow-z 1.0.3 → 1.0.5

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 CHANGED
@@ -7,20 +7,15 @@
7
7
  ---
8
8
 
9
9
  `react-flow-z` is a **small, framework-agnostic async flow runtime**.
10
-
11
- It focuses on **how async logic runs**, not:
12
- - how state is stored
13
- - how UI renders
14
- - or how effects are magically managed
10
+ It focuses on **how async logic runs**
15
11
 
16
12
  > This library is about **orchestration**, not reactivity.
17
13
 
18
14
  ---
19
15
 
20
16
  ## Why react-flow-z
21
-
22
17
  - Typed async execution pipeline
23
- - Immutable flow composition
18
+ - Declarative flow composition
24
19
  - Abort & cancellation via `AbortController`
25
20
  - Async orchestration operators: `debounce` · `retry` · `timeout` · `switchMap` ·` parallel`...
26
21
  - Control flow: `filter` · `take` · `conditional execution`...
@@ -45,11 +40,16 @@ import { Flow } from "react-flow-z"
45
40
 
46
41
  new Flow()
47
42
  .debounce(300)
48
- .switchMap(q => fetch(`/search?q=${q}`))
49
- .tap(res => console.log(res))
43
+ .switchMap(async q => {
44
+ const res = await fetch(`/search?q=${q}`)
45
+ return res.json()
46
+ })
47
+ .tap(console.log)
50
48
  .run("hello")
51
49
  ```
52
50
 
51
+ > Avoid mutating a shared Flow instance inside React render loops.
52
+
53
53
  ---
54
54
 
55
55
  ## Cancellation
@@ -104,39 +104,85 @@ useFlow(
104
104
 
105
105
  ##### searchFlow.ts
106
106
  ```ts
107
- import { Flow } from "react-flow-z"
107
+ import { Flow, createFlow } from "react-flow-z";
108
+
109
+ export type Post = {
110
+ id: number;
111
+ title: string;
112
+ };
108
113
 
109
- export const searchFlow = new Flow()
110
- .onStart(() => console.log("loading..."))
114
+ export const searchFlow = new Flow<string, Post[]>()
111
115
  .debounce(300)
112
- .filter((q: string) => q.length > 0)
113
- .switchMap(async (q: string) => {
116
+ .filter(q => q.trim().length > 0)
117
+ .switchMap(async q => {
114
118
  const res = await fetch(
115
119
  `https://jsonplaceholder.typicode.com/posts?q=${q}`
116
- )
117
- if (!res.ok) throw new Error("network error")
118
- return res.json()
119
- })
120
- .onDone(() => console.log("done"))
121
- .onError(err => console.error(err))
120
+ );
121
+
122
+ if (!res.ok) throw new Error("network error");
123
+
124
+ return res.json();
125
+ });
122
126
 
123
127
  // searchFlow.run("r")
124
128
  // searchFlow.run("re")
125
129
  // searchFlow.run("react")
126
130
  ```
127
131
 
132
+ ##### createFlow.ts
133
+
134
+ ```ts
135
+ import { Flow, createFlow } from "react-flow-z";
136
+ export const searchFlow = createFlow<string, Post[]>()
137
+ .debounce(300)
138
+ .filter(q => q.trim().length > 0)
139
+ .switchMap(async q => {
140
+ const res = await fetch(
141
+ `https://jsonplaceholder.typicode.com/posts?q=${q}`
142
+ )
143
+ return res.json()
144
+ })
145
+ ```
146
+
128
147
  ##### React usage
129
148
  ```ts
149
+ import { useEffect, useState } from "react";
150
+ import { searchFlow, Post } from "./searchFlow";
151
+ import "./styles.css";
152
+
130
153
  function SearchExample() {
131
- const [q, setQ] = useState("")
132
- const [posts, setPosts] = useState<any[]>([])
154
+ const [q, setQ] = useState("");
155
+ const [posts, setPosts] = useState<Post[]>([]);
156
+ const [loading, setLoading] = useState(false);
157
+ const [error, setError] = useState<string | null>(null);
133
158
 
134
159
  useEffect(() => {
160
+ if (!q) {
161
+ setPosts([]);
162
+ return;
163
+ }
164
+
165
+ setLoading(true);
166
+ setError(null);
167
+
135
168
  searchFlow
136
- .tap(setPosts)
137
- .catch(() => [])
138
169
  .run(q)
139
- }, [q])
170
+ .then(result => {
171
+ setPosts(Array.isArray(result) ? result : []);
172
+ })
173
+ .catch(err => {
174
+ console.error(err);
175
+ setError("Something went wrong");
176
+ setPosts([]);
177
+ })
178
+ .finally(() => {
179
+ setLoading(false);
180
+ });
181
+
182
+ return () => {
183
+ searchFlow.cancel();
184
+ };
185
+ }, [q]);
140
186
 
141
187
  return (
142
188
  <>
@@ -145,13 +191,26 @@ function SearchExample() {
145
191
  onChange={e => setQ(e.target.value)}
146
192
  placeholder="Search posts..."
147
193
  />
194
+
195
+ {loading && <p>Loading...</p>}
196
+ {error && <p style={{ color: "red" }}>{error}</p>}
197
+
148
198
  <ul>
149
199
  {posts.map(p => (
150
200
  <li key={p.id}>{p.title}</li>
151
201
  ))}
152
202
  </ul>
153
203
  </>
154
- )
204
+ );
205
+ }
206
+
207
+ export default function App() {
208
+ return (
209
+ <div className="App">
210
+ <h2>react-flow-z + React</h2>
211
+ <SearchExample />
212
+ </div>
213
+ );
155
214
  }
156
215
 
157
216
  ```
@@ -195,13 +254,13 @@ export function runSearch(q: string) {
195
254
 
196
255
  ## Compare high-level
197
256
 
198
- | Point | react-flow-z | RxJS | Redux-Saga | XState | React Query |
199
- | ---------------------- | ------------ | ----- | ---------- | ------- | ----------- |
200
- | Async orchestration | ✅ | ✅ | ✅ | 🟡 | ❌ |
201
- | Debounce / cancel | ✅ | ✅ | 🟡 | 🟡 | ❌ |
202
- | Execution-first design | ✅ | ❌ | 🟡 | ❌ | ❌ |
203
- | Framework-agnostic | ✅ | ✅ | ❌ | 🟡 | ❌ |
204
- | Learning curve | ⭐ easy | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ | ⭐ |
257
+ | Point | react-flow-z | RxJS | Redux-Saga | XState |
258
+ | ---------------------- | ------------ | ----- | ---------- | ------- |
259
+ | Async orchestration | ✅ | ✅ | ✅ | 🟡 |
260
+ | Debounce / cancel | ✅ | ✅ | 🟡 | 🟡 |
261
+ | Execution-first design | ✅ | ❌ | 🟡 | ❌ |
262
+ | Framework-agnostic | ✅ | ✅ | ❌ | 🟡 |
263
+ | Learning curve | ⭐ easy | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
205
264
 
206
265
  ---
207
266
 
@@ -218,6 +277,14 @@ If you need **explicit async execution with cancel / debounce / queue** → reac
218
277
 
219
278
  ---
220
279
 
280
+ ## Flow lifecycle notes
281
+
282
+ - A `Flow` instance is stateful
283
+ - Operators like `tap`, `catch`, `onDone` mutate the instance
284
+ - Prefer: configuring the flow once, handling React state outside the flow
285
+
286
+ ---
287
+
221
288
  ## License
222
289
 
223
290
  MIT
@@ -7,6 +7,7 @@ export type TypedFlow<I, O, Context> = Flow<Context> & {
7
7
  tap(fn: (v: O, context: Context) => void): TypedFlow<I, O, Context>;
8
8
  filter(cond: Condition<O>): TypedFlow<I, O, Context>;
9
9
  debounce(ms: number): TypedFlow<I, O, Context>;
10
+ delay(ms: number): TypedFlow<I, O, Context>;
10
11
  throttle(ms: number): TypedFlow<I, O, Context>;
11
12
  leading(ms: number): TypedFlow<I, O, Context>;
12
13
  map<N>(fn: (v: O, context: Context) => N): TypedFlow<I, N, Context>;
@@ -23,6 +24,7 @@ export type TypedFlow<I, O, Context> = Flow<Context> & {
23
24
  max?: number;
24
25
  }): TypedFlow<I, O, Context>;
25
26
  timeout(ms: number): TypedFlow<I, O, Context>;
27
+ tapError(fn: (v: O, context: Context) => void): TypedFlow<I, O, Context>;
26
28
  catch(fn: (e: any, context: Context) => O | Promise<O>): TypedFlow<I, O, Context>;
27
29
  take(n: number): TypedFlow<I, O, Context>;
28
30
  finally(fn: () => void): TypedFlow<I, O, Context>;
@@ -32,7 +34,7 @@ export declare class Flow<Context = {}> {
32
34
  static SKIP: symbol;
33
35
  private ops;
34
36
  private paused?;
35
- private resume;
37
+ private resume?;
36
38
  private controller;
37
39
  private cancelHandlers;
38
40
  private onStartHandlers;
@@ -56,6 +58,7 @@ export declare class Flow<Context = {}> {
56
58
  tap(fn: (v: any, context: Context) => void): any;
57
59
  filter(cond: Condition<any>): any;
58
60
  debounce(ms: number): any;
61
+ delay(ms: number): any;
59
62
  leading(ms: number): any;
60
63
  throttle(ms: number): any;
61
64
  take(n: number): any;
@@ -73,6 +76,7 @@ export declare class Flow<Context = {}> {
73
76
  max?: number;
74
77
  }): this;
75
78
  timeout(ms: number): this;
79
+ tapError(fn: (e: any, context: Context) => void): this;
76
80
  catch(fn: (e: any, context: Context) => any): this;
77
81
  finally(fn: () => void): this;
78
82
  }
@@ -1 +1,3 @@
1
+ import { TypedFlow } from "./Flow";
1
2
  export declare const sleep: (ms: number, signal: AbortSignal) => Promise<void>;
3
+ export declare function createFlow<I, O = I, Context = {}>(context?: Context): TypedFlow<I, O, Context>;
@@ -1 +1 @@
1
- "use strict";var t=require("react");const r=(t,r)=>new Promise((e,s)=>{const n=setTimeout(e,t);r.addEventListener("abort",()=>{clearTimeout(n),s("aborted")})});class e{constructor(t={}){this.ctx=t,this.ops=[],this.controller=null,this.cancelHandlers=[],this.onStartHandlers=[],this.onDoneHandlers=[],this.onErrorHandlers=[],this.finallyHandlers=[]}static from(t,r){const s=new e(r);return s.initialInput=t,s}async run(t){var r;const s=void 0!==t?t:this.initialInput;if(void 0===s)throw Error("Flow.run: missing input");null===(r=this.controller)||void 0===r||r.abort(),this.controller=new AbortController,this.onStartHandlers.forEach(t=>t());let n=s;try{for(const t of this.ops){if(this.controller.signal.aborted)return;await this.waitIfPaused(),n=await t(n,this.ctx,this.controller.signal)}return this.onDoneHandlers.forEach(t=>t()),n}catch(t){if(t===e.SKIP)return;throw this.onErrorHandlers.forEach(r=>r(t)),t}finally{this.finallyHandlers.forEach(t=>t())}}onStart(t){return this.onStartHandlers.push(t),this}onDone(t){return this.onDoneHandlers.push(t),this}onError(t){return this.onErrorHandlers.push(t),this}onCancel(t){return this.cancelHandlers.push(t),this}cancel(){var t;null===(t=this.controller)||void 0===t||t.abort(),this.cancelHandlers.forEach(t=>t())}pause(){return this.paused||(this.paused=new Promise(t=>this.resume=t)),this}resumeFlow(){return this.paused&&(this.resume(),this.paused=void 0),this}async waitIfPaused(){this.paused&&await this.paused}context(){return this.ctx}step(t){return this.ops.push(t),this}tap(t){return this.step((r,e)=>(t(r,e),r))}filter(t){return this.step(r=>{if(!t(r))throw e.SKIP;return r})}debounce(t){return this.step(async(e,s,n)=>(await r(t,n),e))}leading(t){let r=!1;return this.step(async s=>{if(r)throw e.SKIP;return r=!0,setTimeout(()=>r=!1,t),s})}throttle(t){return this.leading(t)}take(t){let r=0;return this.step(s=>{if(++r>t)throw e.SKIP;return s})}map(t){return this.step((r,e)=>t(r,e))}switchMap(t){let r=null;return this.step(async(e,s,n)=>(null==r||r.abort(),r=new AbortController,n.addEventListener("abort",()=>null==r?void 0:r.abort()),t(e,s,r.signal)))}exhaustMap(t){let r=!1;return this.step(async(s,n,i)=>{if(r)throw e.SKIP;r=!0;try{return await t(s,n,i)}finally{r=!1}})}distinct(t=Object.is){let r,s=!1;return this.step(n=>{if(s&&t(r,n))throw e.SKIP;return s=!0,r=n,n})}retry(t){const e=this.ops.pop();if(!e)return this;const s="number"==typeof t?{times:t}:t,{times:n,delay:i=0,backoff:o}=s;return this.ops.push(async(t,s,a)=>{let l=0;for(;;)try{return await e(t,s,a)}catch(t){if(l++,l>n)throw t;if(i>0){const t="exponential"===o?i*Math.pow(2,l-1):"linear"===o?i*l:i;await r(t,a)}}}),this}poll(t,e){const s=this.ops.pop();if(!s)return this;const{until:n,max:i}=e||{};return this.ops.push(async(e,o,a)=>{let l,u=0;for(;;){if(l=await s(e,o,a),null==n?void 0:n(l))return l;if(i&&++u>=i)throw Error("poll max reached");await r(t,a)}}),this}timeout(t){const e=this.ops.pop();if(!e)return this;return this.ops.push((s,n,i)=>Promise.race([e(s,n,i),r(t,i).then(()=>{throw Error("timeout")})])),this}catch(t){const r=this.ops.pop();if(!r)return this;return this.ops.push(async(e,s,n)=>{try{return await r(e,s,n)}catch(r){return t(r,s)}}),this}finally(t){return this.finallyHandlers.push(t),this}}e.SKIP=Symbol("FLOW_SKIP"),exports.Flow=e,exports.sleep=r,exports.useFlow=function(r,s,n){const i=t.useRef(null),o=t.useRef(null!=n?n:{});return o.current=null!=n?n:o.current,t.useEffect(()=>{var t;null===(t=i.current)||void 0===t||t.cancel();const n=e.from(r,o.current),a=s(n);return i.current=a,a.run(),()=>a.cancel()},[r,s]),{cancel:t.useCallback(()=>{var t;null===(t=i.current)||void 0===t||t.cancel()},[]),pause:t.useCallback(()=>{var t;null===(t=i.current)||void 0===t||t.pause()},[]),resume:t.useCallback(()=>{var t;null===(t=i.current)||void 0===t||t.resumeFlow()},[]),flow:i.current}};
1
+ "use strict";var t=require("react");const r=(t,r)=>new Promise((s,e)=>{const n=setTimeout(s,t);r.addEventListener("abort",()=>{clearTimeout(n),e("aborted")})});const s=Symbol("FLOW_HANDLED");class e{constructor(t={}){this.ctx=t,this.ops=[],this.controller=null,this.cancelHandlers=[],this.onStartHandlers=[],this.onDoneHandlers=[],this.onErrorHandlers=[],this.finallyHandlers=[]}static from(t,r){const s=new e(r);return s.initialInput=t,s}async run(t){const r=void 0!==t?t:this.initialInput;if(void 0===r)throw Error("Flow.run: missing input");this.cancel(),this.controller=new AbortController,this.onStartHandlers.forEach(t=>t());let n=r,i=!1;try{for(const t of this.ops){if(this.controller.signal.aborted)return;await this.waitIfPaused(),n=await t(n,this.ctx,this.controller.signal)}return this.onDoneHandlers.forEach(t=>t()),n}catch(t){if(t===e.SKIP)return void(i=!0);throw(null==t?void 0:t[s])||this.onErrorHandlers.forEach(r=>r(t)),t}finally{i||this.finallyHandlers.forEach(t=>t())}}onStart(t){return this.onStartHandlers.push(t),this}onDone(t){return this.onDoneHandlers.push(t),this}onError(t){return this.onErrorHandlers.push(t),this}onCancel(t){return this.cancelHandlers.push(t),this}cancel(){var t;null===(t=this.controller)||void 0===t||t.abort(),this.controller=null,this.resume&&(this.resume(),this.resume=void 0,this.paused=void 0),this.cancelHandlers.forEach(t=>t())}pause(){return this.paused||(this.paused=new Promise(t=>{this.resume=t})),this}resumeFlow(){return this.resume&&(this.resume(),this.resume=void 0,this.paused=void 0),this}async waitIfPaused(){this.paused&&await this.paused}context(){return this.ctx}step(t){return this.ops.push(t),this}tap(t){return this.step((r,s)=>(t(r,s),r))}filter(t){return this.step(r=>{if(!t(r))throw e.SKIP;return r})}debounce(t){return this.step(async(s,e,n)=>(await r(t,n),s))}delay(t){return this.step(async(s,e,n)=>(await r(t,n),s))}leading(t){let r=!1;return this.step(async s=>{if(r)throw e.SKIP;return r=!0,setTimeout(()=>r=!1,t),s})}throttle(t){return this.leading(t)}take(t){let r=0;return this.step(s=>{if(++r>t)throw e.SKIP;return s})}map(t){return this.step((r,s)=>t(r,s))}switchMap(t){let r=null;return this.step(async(s,e,n)=>(null==r||r.abort(),r=new AbortController,n.addEventListener("abort",()=>null==r?void 0:r.abort()),t(s,e,r.signal)))}exhaustMap(t){let r=!1;return this.step(async(s,n,i)=>{if(r)throw e.SKIP;r=!0;try{return await t(s,n,i)}finally{r=!1}})}distinct(t=Object.is){let r,s=!1;return this.step(n=>{if(s&&t(r,n))throw e.SKIP;return s=!0,r=n,n})}retry(t){const s=this.ops.pop();if(!s)return this;const e="number"==typeof t?{times:t}:t,{times:n,delay:i=0,backoff:o}=e;return this.ops.push(async(t,e,a)=>{let u=0;for(;;)try{return await s(t,e,a)}catch(t){if(u++,u>n)throw t;if(i>0){const t="exponential"===o?i*Math.pow(2,u-1):"linear"===o?i*u:i;await r(t,a)}}}),this}poll(t,s){const e=this.ops.pop();if(!e)return this;const{until:n,max:i}=s||{};return this.ops.push(async(s,o,a)=>{let u,l=0;for(;;){if(u=await e(s,o,a),null==n?void 0:n(u))return u;if(i&&++l>=i)throw Error("poll max reached");await r(t,a)}}),this}timeout(t){const s=this.ops.pop();if(!s)return this;return this.ops.push((e,n,i)=>Promise.race([s(e,n,i),r(t,i).then(()=>{throw Error("timeout")})])),this}tapError(t){const r=this.ops.pop();if(!r)return this;return this.ops.push(async(s,e,n)=>{try{return await r(s,e,n)}catch(r){throw t(r,e),r}}),this}catch(t){const r=this.ops.pop();if(!r)return this;return this.ops.push(async(e,n,i)=>{try{return await r(e,n,i)}catch(r){return r[s]=!0,t(r,n)}}),this}finally(t){return this.finallyHandlers.push(t),this}}e.SKIP=Symbol("FLOW_SKIP"),exports.Flow=e,exports.createFlow=function(t){return new e(t)},exports.sleep=r,exports.useFlow=function(r,s,n){const i=t.useRef(null),o=t.useRef(null!=n?n:{});return o.current=null!=n?n:o.current,t.useEffect(()=>{var t;null===(t=i.current)||void 0===t||t.cancel();const n=e.from(r,o.current),a=s(n);return i.current=a,a.run(),()=>a.cancel()},[r,s]),{cancel:t.useCallback(()=>{var t;null===(t=i.current)||void 0===t||t.cancel()},[]),pause:t.useCallback(()=>{var t;null===(t=i.current)||void 0===t||t.pause()},[]),resume:t.useCallback(()=>{var t;null===(t=i.current)||void 0===t||t.resumeFlow()},[]),flow:i.current}};
package/build/index.d.ts CHANGED
@@ -2,4 +2,4 @@ export { Flow } from "./flow-system/Flow";
2
2
  export type { TypedFlow } from "./flow-system/Flow";
3
3
  export { useFlow } from "./flow-system/useFlow";
4
4
  export type { Step, Condition, CancelHandler } from "./flow-system/types";
5
- export { sleep } from "./flow-system/utils";
5
+ export * from "./flow-system/utils";
@@ -1 +1 @@
1
- import{useRef as t,useEffect as r,useCallback as n}from"react";const s=(t,r)=>new Promise((n,s)=>{const e=setTimeout(n,t);r.addEventListener("abort",()=>{clearTimeout(e),s("aborted")})});class e{constructor(t={}){this.ctx=t,this.ops=[],this.controller=null,this.cancelHandlers=[],this.onStartHandlers=[],this.onDoneHandlers=[],this.onErrorHandlers=[],this.finallyHandlers=[]}static from(t,r){const n=new e(r);return n.initialInput=t,n}async run(t){var r;const n=void 0!==t?t:this.initialInput;if(void 0===n)throw Error("Flow.run: missing input");null===(r=this.controller)||void 0===r||r.abort(),this.controller=new AbortController,this.onStartHandlers.forEach(t=>t());let s=n;try{for(const t of this.ops){if(this.controller.signal.aborted)return;await this.waitIfPaused(),s=await t(s,this.ctx,this.controller.signal)}return this.onDoneHandlers.forEach(t=>t()),s}catch(t){if(t===e.SKIP)return;throw this.onErrorHandlers.forEach(r=>r(t)),t}finally{this.finallyHandlers.forEach(t=>t())}}onStart(t){return this.onStartHandlers.push(t),this}onDone(t){return this.onDoneHandlers.push(t),this}onError(t){return this.onErrorHandlers.push(t),this}onCancel(t){return this.cancelHandlers.push(t),this}cancel(){var t;null===(t=this.controller)||void 0===t||t.abort(),this.cancelHandlers.forEach(t=>t())}pause(){return this.paused||(this.paused=new Promise(t=>this.resume=t)),this}resumeFlow(){return this.paused&&(this.resume(),this.paused=void 0),this}async waitIfPaused(){this.paused&&await this.paused}context(){return this.ctx}step(t){return this.ops.push(t),this}tap(t){return this.step((r,n)=>(t(r,n),r))}filter(t){return this.step(r=>{if(!t(r))throw e.SKIP;return r})}debounce(t){return this.step(async(r,n,e)=>(await s(t,e),r))}leading(t){let r=!1;return this.step(async n=>{if(r)throw e.SKIP;return r=!0,setTimeout(()=>r=!1,t),n})}throttle(t){return this.leading(t)}take(t){let r=0;return this.step(n=>{if(++r>t)throw e.SKIP;return n})}map(t){return this.step((r,n)=>t(r,n))}switchMap(t){let r=null;return this.step(async(n,s,e)=>(null==r||r.abort(),r=new AbortController,e.addEventListener("abort",()=>null==r?void 0:r.abort()),t(n,s,r.signal)))}exhaustMap(t){let r=!1;return this.step(async(n,s,i)=>{if(r)throw e.SKIP;r=!0;try{return await t(n,s,i)}finally{r=!1}})}distinct(t=Object.is){let r,n=!1;return this.step(s=>{if(n&&t(r,s))throw e.SKIP;return n=!0,r=s,s})}retry(t){const r=this.ops.pop();if(!r)return this;const n="number"==typeof t?{times:t}:t,{times:e,delay:i=0,backoff:o}=n;return this.ops.push(async(t,n,a)=>{let l=0;for(;;)try{return await r(t,n,a)}catch(t){if(l++,l>e)throw t;if(i>0){const t="exponential"===o?i*Math.pow(2,l-1):"linear"===o?i*l:i;await s(t,a)}}}),this}poll(t,r){const n=this.ops.pop();if(!n)return this;const{until:e,max:i}=r||{};return this.ops.push(async(r,o,a)=>{let l,u=0;for(;;){if(l=await n(r,o,a),null==e?void 0:e(l))return l;if(i&&++u>=i)throw Error("poll max reached");await s(t,a)}}),this}timeout(t){const r=this.ops.pop();if(!r)return this;return this.ops.push((n,e,i)=>Promise.race([r(n,e,i),s(t,i).then(()=>{throw Error("timeout")})])),this}catch(t){const r=this.ops.pop();if(!r)return this;return this.ops.push(async(n,s,e)=>{try{return await r(n,s,e)}catch(r){return t(r,s)}}),this}finally(t){return this.finallyHandlers.push(t),this}}function i(s,i,o){const a=t(null),l=t(null!=o?o:{});l.current=null!=o?o:l.current,r(()=>{var t;null===(t=a.current)||void 0===t||t.cancel();const r=e.from(s,l.current),n=i(r);return a.current=n,n.run(),()=>n.cancel()},[s,i]);return{cancel:n(()=>{var t;null===(t=a.current)||void 0===t||t.cancel()},[]),pause:n(()=>{var t;null===(t=a.current)||void 0===t||t.pause()},[]),resume:n(()=>{var t;null===(t=a.current)||void 0===t||t.resumeFlow()},[]),flow:a.current}}e.SKIP=Symbol("FLOW_SKIP");export{e as Flow,s as sleep,i as useFlow};
1
+ import{useRef as t,useEffect as r,useCallback as s}from"react";const e=(t,r)=>new Promise((s,e)=>{const n=setTimeout(s,t);r.addEventListener("abort",()=>{clearTimeout(n),e("aborted")})});function n(t){return new o(t)}const i=Symbol("FLOW_HANDLED");class o{constructor(t={}){this.ctx=t,this.ops=[],this.controller=null,this.cancelHandlers=[],this.onStartHandlers=[],this.onDoneHandlers=[],this.onErrorHandlers=[],this.finallyHandlers=[]}static from(t,r){const s=new o(r);return s.initialInput=t,s}async run(t){const r=void 0!==t?t:this.initialInput;if(void 0===r)throw Error("Flow.run: missing input");this.cancel(),this.controller=new AbortController,this.onStartHandlers.forEach(t=>t());let s=r,e=!1;try{for(const t of this.ops){if(this.controller.signal.aborted)return;await this.waitIfPaused(),s=await t(s,this.ctx,this.controller.signal)}return this.onDoneHandlers.forEach(t=>t()),s}catch(t){if(t===o.SKIP)return void(e=!0);throw(null==t?void 0:t[i])||this.onErrorHandlers.forEach(r=>r(t)),t}finally{e||this.finallyHandlers.forEach(t=>t())}}onStart(t){return this.onStartHandlers.push(t),this}onDone(t){return this.onDoneHandlers.push(t),this}onError(t){return this.onErrorHandlers.push(t),this}onCancel(t){return this.cancelHandlers.push(t),this}cancel(){var t;null===(t=this.controller)||void 0===t||t.abort(),this.controller=null,this.resume&&(this.resume(),this.resume=void 0,this.paused=void 0),this.cancelHandlers.forEach(t=>t())}pause(){return this.paused||(this.paused=new Promise(t=>{this.resume=t})),this}resumeFlow(){return this.resume&&(this.resume(),this.resume=void 0,this.paused=void 0),this}async waitIfPaused(){this.paused&&await this.paused}context(){return this.ctx}step(t){return this.ops.push(t),this}tap(t){return this.step((r,s)=>(t(r,s),r))}filter(t){return this.step(r=>{if(!t(r))throw o.SKIP;return r})}debounce(t){return this.step(async(r,s,n)=>(await e(t,n),r))}delay(t){return this.step(async(r,s,n)=>(await e(t,n),r))}leading(t){let r=!1;return this.step(async s=>{if(r)throw o.SKIP;return r=!0,setTimeout(()=>r=!1,t),s})}throttle(t){return this.leading(t)}take(t){let r=0;return this.step(s=>{if(++r>t)throw o.SKIP;return s})}map(t){return this.step((r,s)=>t(r,s))}switchMap(t){let r=null;return this.step(async(s,e,n)=>(null==r||r.abort(),r=new AbortController,n.addEventListener("abort",()=>null==r?void 0:r.abort()),t(s,e,r.signal)))}exhaustMap(t){let r=!1;return this.step(async(s,e,n)=>{if(r)throw o.SKIP;r=!0;try{return await t(s,e,n)}finally{r=!1}})}distinct(t=Object.is){let r,s=!1;return this.step(e=>{if(s&&t(r,e))throw o.SKIP;return s=!0,r=e,e})}retry(t){const r=this.ops.pop();if(!r)return this;const s="number"==typeof t?{times:t}:t,{times:n,delay:i=0,backoff:o}=s;return this.ops.push(async(t,s,a)=>{let u=0;for(;;)try{return await r(t,s,a)}catch(t){if(u++,u>n)throw t;if(i>0){const t="exponential"===o?i*Math.pow(2,u-1):"linear"===o?i*u:i;await e(t,a)}}}),this}poll(t,r){const s=this.ops.pop();if(!s)return this;const{until:n,max:i}=r||{};return this.ops.push(async(r,o,a)=>{let u,h=0;for(;;){if(u=await s(r,o,a),null==n?void 0:n(u))return u;if(i&&++h>=i)throw Error("poll max reached");await e(t,a)}}),this}timeout(t){const r=this.ops.pop();if(!r)return this;return this.ops.push((s,n,i)=>Promise.race([r(s,n,i),e(t,i).then(()=>{throw Error("timeout")})])),this}tapError(t){const r=this.ops.pop();if(!r)return this;return this.ops.push(async(s,e,n)=>{try{return await r(s,e,n)}catch(r){throw t(r,e),r}}),this}catch(t){const r=this.ops.pop();if(!r)return this;return this.ops.push(async(s,e,n)=>{try{return await r(s,e,n)}catch(r){return r[i]=!0,t(r,e)}}),this}finally(t){return this.finallyHandlers.push(t),this}}function a(e,n,i){const a=t(null),u=t(null!=i?i:{});u.current=null!=i?i:u.current,r(()=>{var t;null===(t=a.current)||void 0===t||t.cancel();const r=o.from(e,u.current),s=n(r);return a.current=s,s.run(),()=>s.cancel()},[e,n]);return{cancel:s(()=>{var t;null===(t=a.current)||void 0===t||t.cancel()},[]),pause:s(()=>{var t;null===(t=a.current)||void 0===t||t.pause()},[]),resume:s(()=>{var t;null===(t=a.current)||void 0===t||t.resumeFlow()},[]),flow:a.current}}o.SKIP=Symbol("FLOW_SKIP");export{o as Flow,n as createFlow,e as sleep,a as useFlow};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-flow-z",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "A lightweight async flow runtime for orchestrating side effects with explicit control over cancellation, debounce, throttling, and execution order.",
5
5
  "license": "MIT",
6
6
  "author": "Delpi.Kye",