react-flow-z 1.0.4 → 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 +25 -17
- package/build/flow-system/Flow.d.ts +4 -0
- package/build/flow-system/utils.d.ts +2 -0
- package/build/index.cjs.js +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.esm.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,18 +7,13 @@
|
|
|
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
18
|
- Declarative flow composition
|
|
24
19
|
- Abort & cancellation via `AbortController`
|
|
@@ -109,7 +104,7 @@ useFlow(
|
|
|
109
104
|
|
|
110
105
|
##### searchFlow.ts
|
|
111
106
|
```ts
|
|
112
|
-
import { Flow } from "react-flow-z";
|
|
107
|
+
import { Flow, createFlow } from "react-flow-z";
|
|
113
108
|
|
|
114
109
|
export type Post = {
|
|
115
110
|
id: number;
|
|
@@ -134,6 +129,21 @@ export const searchFlow = new Flow<string, Post[]>()
|
|
|
134
129
|
// searchFlow.run("react")
|
|
135
130
|
```
|
|
136
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
|
+
|
|
137
147
|
##### React usage
|
|
138
148
|
```ts
|
|
139
149
|
import { useEffect, useState } from "react";
|
|
@@ -244,13 +254,13 @@ export function runSearch(q: string) {
|
|
|
244
254
|
|
|
245
255
|
## Compare high-level
|
|
246
256
|
|
|
247
|
-
| Point | react-flow-z | RxJS | Redux-Saga | XState |
|
|
248
|
-
| ---------------------- | ------------ | ----- | ---------- | ------- |
|
|
249
|
-
| Async orchestration | ✅ | ✅ | ✅ | 🟡 |
|
|
250
|
-
| Debounce / cancel | ✅ | ✅ | 🟡 | 🟡 |
|
|
251
|
-
| Execution-first design | ✅ | ❌ | 🟡 | ❌ |
|
|
252
|
-
| Framework-agnostic | ✅ | ✅ | ❌ | 🟡 |
|
|
253
|
-
| 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 | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
|
|
254
264
|
|
|
255
265
|
---
|
|
256
266
|
|
|
@@ -271,9 +281,7 @@ If you need **explicit async execution with cancel / debounce / queue** → reac
|
|
|
271
281
|
|
|
272
282
|
- A `Flow` instance is stateful
|
|
273
283
|
- Operators like `tap`, `catch`, `onDone` mutate the instance
|
|
274
|
-
- Prefer:
|
|
275
|
-
- configuring the flow once
|
|
276
|
-
- handling React state outside the flow
|
|
284
|
+
- Prefer: configuring the flow once, handling React state outside the flow
|
|
277
285
|
|
|
278
286
|
---
|
|
279
287
|
|
|
@@ -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>;
|
|
@@ -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
|
}
|
package/build/index.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var t=require("react");const r=(t,r)=>new Promise((e
|
|
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
|
|
5
|
+
export * from "./flow-system/utils";
|
package/build/index.esm.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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")})})
|
|
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
|
+
"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",
|