flowneer 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/FlowBuilder-Dqppgla9.d.ts +247 -0
- package/dist/index.d.ts +2 -226
- package/dist/index.js +60 -4
- package/dist/plugins/agent/index.d.ts +1 -1
- package/dist/plugins/agent/index.js +38 -3
- package/dist/plugins/dev/index.d.ts +1 -1
- package/dist/plugins/eval/index.d.ts +1 -1
- package/dist/plugins/graph/index.d.ts +1 -1
- package/dist/plugins/index.d.ts +1 -1
- package/dist/plugins/index.js +38 -3
- package/dist/plugins/llm/index.d.ts +1 -1
- package/dist/plugins/memory/index.d.ts +1 -1
- package/dist/plugins/messaging/index.d.ts +1 -1
- package/dist/plugins/observability/index.d.ts +1 -1
- package/dist/plugins/observability/index.js +1 -1
- package/dist/plugins/output/index.d.ts +1 -1
- package/dist/plugins/persistence/index.d.ts +1 -1
- package/dist/plugins/resilience/index.d.ts +1 -1
- package/dist/plugins/telemetry/index.d.ts +1 -1
- package/dist/plugins/tools/index.d.ts +1 -1
- package/dist/src/index.d.ts +71 -0
- package/dist/src/index.js +455 -0
- package/package.json +11 -7
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<img src="https://
|
|
2
|
+
<img src="https://github.com/Fanna1119/flowneer/blob/main/docs/public/flowneer_logo.png" width="500" height="auto" alt="Flowneer"/>
|
|
3
3
|
</div>
|
|
4
4
|
|
|
5
5
|
<p>
|
|
@@ -8,10 +8,14 @@
|
|
|
8
8
|
<a href="https://www.npmjs.com/package/flowneer"><img src="https://img.shields.io/npm/l/flowneer" /></a>
|
|
9
9
|
<a href="https://www.npmjs.com/package/flowneer"><img src="https://img.shields.io/npm/d18m/flowneer" /></a>
|
|
10
10
|
<a href="https://deepwiki.com/Fanna1119/flowneer"><img src="https://deepwiki.com/badge.svg" /></a>
|
|
11
|
+
<a href="https://github.com/Fanna1119/flowneer"><img src="https://img.shields.io/badge/GitHub-%23121011.svg?logo=github&logoColor=white)" /></a>
|
|
12
|
+
<a href="https://context7.com/fanna1119/flowneer"><img src="https://img.shields.io/badge/-Context7-black?style=flat&logo=data%3Aimage%2Fsvg%2Bxml%3Bbase64%2CPHN2ZyB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHZpZXdCb3g9IjAgMCAyOCAyOCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI4IiBoZWlnaHQ9IjI4IiByeD0iNCIgZmlsbD0iIzA1OTY2OSIvPgo8cGF0aCBkPSJNMTAuNTcyNCAxNS4yNTY1QzEwLjU3MjQgMTcuNTAyNSA5LjY2MTMgMTkuMzc3OCA4LjE3ODA1IDIxLjEwNDdIMTEuNjMxOUwxMS42MzE5IDIyLjc3ODZINi4zMzQ1OVYyMS4xODk1QzcuOTU1NTcgMTkuMzU2NiA4LjU4MDY1IDE3Ljg2MjggOC41ODA2NSAxNS4yNTY1TDEwLjU3MjQgMTUuMjU2NVoiIGZpbGw9IndoaXRlIi8%2BCjxwYXRoIGQ9Ik0xNy40Mjc2IDE1LjI1NjVDMTcuNDI3NiAxNy41MDI1IDE4LjMzODcgMTkuMzc3OCAxOS44MjIgMjEuMTA0N0gxNi4zNjgxVjIyLjc3ODZIMjEuNjY1NFYyMS4xODk1QzIwLjA0NDQgMTkuMzU2NiAxOS40MTk0IDE3Ljg2MjggMTkuNDE5NCAxNS4yNTY1SDE3LjQyNzZaIiBmaWxsPSJ3aGl0ZSIvPgo8cGF0aCBkPSJNMTAuNTcyNCAxMi43NDM1QzEwLjU3MjQgMTAuNDk3NSA5LjY2MTMxIDguNjIyMjQgOC4xNzgwNyA2Ljg5NTMyTDExLjYzMTkgNi44OTUzMlY1LjIyMTM3TDYuMzM0NjEgNS4yMjEzN1Y2LjgxMDU2QzcuOTU1NTggOC42NDM0MyA4LjU4MDY2IDEwLjEzNzMgOC41ODA2NiAxMi43NDM1TDEwLjU3MjQgMTIuNzQzNVoiIGZpbGw9IndoaXRlIi8%2BCjxwYXRoIGQ9Ik0xNy40Mjc2IDEyLjc0MzVDMTcuNDI3NiAxMC40OTc1IDE4LjMzODcgOC42MjIyNCAxOS44MjIgNi44OTUzMkwxNi4zNjgxIDYuODk1MzJMMTYuMzY4MSA1LjIyMTM4TDIxLjY2NTQgNS4yMjEzOFY2LjgxMDU2QzIwLjA0NDQgOC42NDM0MyAxOS40MTk0IDEwLjEzNzMgMTkuNDE5NCAxMi43NDM1SDE3LjQyNzZaIiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K" alt="Badge"></a>
|
|
11
13
|
</p>
|
|
12
14
|
|
|
13
15
|
A tiny, zero-dependency fluent flow builder for TypeScript. Chain steps, branch on conditions, loop, batch-process, and run tasks in parallel — all through a single `FlowBuilder` class. Extend it with plugins for tool calling, ReAct agent loops, human-in-the-loop, memory, structured output, streaming, graph-based flow composition, eval, and more.
|
|
14
16
|
|
|
17
|
+
> Flowneer is currently under heavy development with ongoing pattern exploration and architectural refinement. Breaking changes are expected frequently, potentially on a daily basis, as the core design is actively evolving.
|
|
18
|
+
|
|
15
19
|
## Install
|
|
16
20
|
|
|
17
21
|
```bash
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic validator interface — structurally compatible with Zod, ArkType,
|
|
3
|
+
* Valibot, or any custom implementation that exposes `.parse(input)`.
|
|
4
|
+
* Used by `withStructuredOutput` and output parsers.
|
|
5
|
+
*/
|
|
6
|
+
interface Validator<T = unknown> {
|
|
7
|
+
parse(input: unknown): T;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Events yielded by `FlowBuilder.stream()`.
|
|
11
|
+
*/
|
|
12
|
+
type StreamEvent<S = any> = {
|
|
13
|
+
type: "step:before";
|
|
14
|
+
meta: StepMeta;
|
|
15
|
+
} | {
|
|
16
|
+
type: "step:after";
|
|
17
|
+
meta: StepMeta;
|
|
18
|
+
shared: S;
|
|
19
|
+
} | {
|
|
20
|
+
type: "chunk";
|
|
21
|
+
data: unknown;
|
|
22
|
+
} | {
|
|
23
|
+
type: "error";
|
|
24
|
+
error: unknown;
|
|
25
|
+
} | {
|
|
26
|
+
type: "done";
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Function signature for all step logic.
|
|
30
|
+
* Return an action string to route, or undefined/void to continue.
|
|
31
|
+
*
|
|
32
|
+
* Steps may also be declared as `async function*` generators — each `yield`
|
|
33
|
+
* forwards its value to the active stream consumer as a `chunk` event.
|
|
34
|
+
* The generator's final `return` value is still routed normally, so
|
|
35
|
+
* `return "#anchorName"` works exactly as it does in plain steps.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* .then(async function* (s) {
|
|
39
|
+
* for await (const token of llmStream(s.prompt)) {
|
|
40
|
+
* s.response += token;
|
|
41
|
+
* yield token; // → chunk event on flow.stream()
|
|
42
|
+
* }
|
|
43
|
+
* })
|
|
44
|
+
*/
|
|
45
|
+
type NodeFn<S = any, P extends Record<string, unknown> = Record<string, unknown>> = (shared: S, params: P) => Promise<string | undefined | void> | string | undefined | void | AsyncGenerator<unknown, string | undefined | void, unknown>;
|
|
46
|
+
/**
|
|
47
|
+
* A numeric value or a function that computes it from the current shared state
|
|
48
|
+
* and params. Use functions for dynamic per-item behaviour, e.g.
|
|
49
|
+
* `retries: (s) => (s.__batchItem % 3 === 0 ? 3 : 1)`.
|
|
50
|
+
*/
|
|
51
|
+
type NumberOrFn<S = any, P extends Record<string, unknown> = Record<string, unknown>> = number | ((shared: S, params: P) => number);
|
|
52
|
+
interface NodeOptions<S = any, P extends Record<string, unknown> = Record<string, unknown>> {
|
|
53
|
+
retries?: NumberOrFn<S, P>;
|
|
54
|
+
delaySec?: NumberOrFn<S, P>;
|
|
55
|
+
timeoutMs?: NumberOrFn<S, P>;
|
|
56
|
+
}
|
|
57
|
+
interface RunOptions {
|
|
58
|
+
signal?: AbortSignal;
|
|
59
|
+
}
|
|
60
|
+
/** Metadata exposed to hooks — intentionally minimal to avoid coupling. */
|
|
61
|
+
interface StepMeta {
|
|
62
|
+
index: number;
|
|
63
|
+
type: "fn" | "branch" | "loop" | "batch" | "parallel" | "anchor";
|
|
64
|
+
label?: string;
|
|
65
|
+
}
|
|
66
|
+
/** Lifecycle hooks that plugins can register. */
|
|
67
|
+
interface FlowHooks<S = any, P extends Record<string, unknown> = Record<string, unknown>> {
|
|
68
|
+
/** Fires once before the first step runs. */
|
|
69
|
+
beforeFlow?: (shared: S, params: P) => void | Promise<void>;
|
|
70
|
+
beforeStep?: (meta: StepMeta, shared: S, params: P) => void | Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Wraps step execution — call `next()` to invoke the step body.
|
|
73
|
+
* Omitting `next()` skips execution (dry-run, mock, etc.).
|
|
74
|
+
* Multiple `wrapStep` registrations are composed innermost-first.
|
|
75
|
+
*/
|
|
76
|
+
wrapStep?: (meta: StepMeta, next: () => Promise<void>, shared: S, params: P) => Promise<void>;
|
|
77
|
+
afterStep?: (meta: StepMeta, shared: S, params: P) => void | Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Wraps individual functions within a `.parallel()` step.
|
|
80
|
+
* `fnIndex` is the position within the fns array.
|
|
81
|
+
*/
|
|
82
|
+
wrapParallelFn?: (meta: StepMeta, fnIndex: number, next: () => Promise<void>, shared: S, params: P) => Promise<void>;
|
|
83
|
+
onError?: (meta: StepMeta, error: unknown, shared: S, params: P) => void;
|
|
84
|
+
afterFlow?: (shared: S, params: P) => void | Promise<void>;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* A plugin is an object whose keys become methods on `FlowBuilder.prototype`.
|
|
88
|
+
* Each method receives the builder as `this` and should return `this` for chaining.
|
|
89
|
+
*
|
|
90
|
+
* Use declaration merging to get type-safe access:
|
|
91
|
+
* ```ts
|
|
92
|
+
* declare module "flowneer" {
|
|
93
|
+
* interface FlowBuilder<S, P> { withTracing(fn: TraceCallback): this; }
|
|
94
|
+
* }
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
type FlowneerPlugin = Record<string, (this: FlowBuilder<any, any>, ...args: any[]) => any>;
|
|
98
|
+
|
|
99
|
+
interface FnStep<S, P extends Record<string, unknown>> {
|
|
100
|
+
type: "fn";
|
|
101
|
+
fn: NodeFn<S, P>;
|
|
102
|
+
retries: NumberOrFn<S, P>;
|
|
103
|
+
delaySec: NumberOrFn<S, P>;
|
|
104
|
+
timeoutMs: NumberOrFn<S, P>;
|
|
105
|
+
}
|
|
106
|
+
interface BranchStep<S, P extends Record<string, unknown>> {
|
|
107
|
+
type: "branch";
|
|
108
|
+
router: NodeFn<S, P>;
|
|
109
|
+
branches: Record<string, NodeFn<S, P>>;
|
|
110
|
+
retries: NumberOrFn<S, P>;
|
|
111
|
+
delaySec: NumberOrFn<S, P>;
|
|
112
|
+
timeoutMs: NumberOrFn<S, P>;
|
|
113
|
+
}
|
|
114
|
+
interface LoopStep<S, P extends Record<string, unknown>> {
|
|
115
|
+
type: "loop";
|
|
116
|
+
condition: (shared: S, params: P) => Promise<boolean> | boolean;
|
|
117
|
+
body: FlowBuilder<S, P>;
|
|
118
|
+
}
|
|
119
|
+
interface BatchStep<S, P extends Record<string, unknown>> {
|
|
120
|
+
type: "batch";
|
|
121
|
+
itemsExtractor: (shared: S, params: P) => Promise<any[]> | any[];
|
|
122
|
+
processor: FlowBuilder<S, P>;
|
|
123
|
+
key: string;
|
|
124
|
+
}
|
|
125
|
+
interface ParallelStep<S, P extends Record<string, unknown>> {
|
|
126
|
+
type: "parallel";
|
|
127
|
+
fns: NodeFn<S, P>[];
|
|
128
|
+
retries: NumberOrFn<S, P>;
|
|
129
|
+
delaySec: NumberOrFn<S, P>;
|
|
130
|
+
timeoutMs: NumberOrFn<S, P>;
|
|
131
|
+
reducer?: (shared: S, drafts: S[]) => void;
|
|
132
|
+
}
|
|
133
|
+
interface AnchorStep {
|
|
134
|
+
type: "anchor";
|
|
135
|
+
name: string;
|
|
136
|
+
}
|
|
137
|
+
type Step<S, P extends Record<string, unknown>> = FnStep<S, P> | BranchStep<S, P> | LoopStep<S, P> | BatchStep<S, P> | ParallelStep<S, P> | AnchorStep;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Fluent builder for composable flows.
|
|
141
|
+
*
|
|
142
|
+
* Steps execute sequentially in the order added. Call `.run(shared)` to execute.
|
|
143
|
+
*
|
|
144
|
+
* **Shared-state safety**: all steps operate on the same shared object.
|
|
145
|
+
* Mutate it directly; avoid spreading/replacing the entire object.
|
|
146
|
+
*/
|
|
147
|
+
declare class FlowBuilder<S = any, P extends Record<string, unknown> = Record<string, unknown>> {
|
|
148
|
+
protected steps: Step<S, P>[];
|
|
149
|
+
private _hooksList;
|
|
150
|
+
/** Cached flat arrays of present hooks — invalidated whenever a hook is added. */
|
|
151
|
+
private _cachedHooks;
|
|
152
|
+
private _getHooks;
|
|
153
|
+
/** Register a plugin — copies its methods onto `FlowBuilder.prototype`. */
|
|
154
|
+
static use(plugin: FlowneerPlugin): void;
|
|
155
|
+
/** Register lifecycle hooks (called by plugin methods, not by consumers). */
|
|
156
|
+
protected _setHooks(hooks: Partial<FlowHooks<S, P>>): void;
|
|
157
|
+
/** Set the first step, resetting any prior chain. */
|
|
158
|
+
startWith(fn: NodeFn<S, P>, options?: NodeOptions<S, P>): this;
|
|
159
|
+
/** Append a sequential step. */
|
|
160
|
+
then(fn: NodeFn<S, P>, options?: NodeOptions<S, P>): this;
|
|
161
|
+
/**
|
|
162
|
+
* Splice all steps from a `Fragment` into this flow at the current position.
|
|
163
|
+
*
|
|
164
|
+
* Fragments are reusable partial flows created with the `fragment()` factory.
|
|
165
|
+
* Steps are copied by reference (same semantics as `loop` / `batch` inners).
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```ts
|
|
169
|
+
* const enrich = fragment<S>()
|
|
170
|
+
* .then(fetchUser)
|
|
171
|
+
* .then(enrichProfile);
|
|
172
|
+
*
|
|
173
|
+
* new FlowBuilder<S>()
|
|
174
|
+
* .then(init)
|
|
175
|
+
* .add(enrich)
|
|
176
|
+
* .then(finalize)
|
|
177
|
+
* .run(shared);
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
add(frag: FlowBuilder<S, P>): this;
|
|
181
|
+
/**
|
|
182
|
+
* Append a routing step.
|
|
183
|
+
* `router` returns a key; the matching branch flow executes, then the chain continues.
|
|
184
|
+
*/
|
|
185
|
+
branch(router: NodeFn<S, P>, branches: Record<string, NodeFn<S, P>>, options?: NodeOptions<S, P>): this;
|
|
186
|
+
/**
|
|
187
|
+
* Append a looping step.
|
|
188
|
+
* Repeatedly runs `body` while `condition` returns true.
|
|
189
|
+
*/
|
|
190
|
+
loop(condition: (shared: S, params: P) => Promise<boolean> | boolean, body: (b: FlowBuilder<S, P>) => void): this;
|
|
191
|
+
/**
|
|
192
|
+
* Append a batch step.
|
|
193
|
+
* Runs `processor` once per item extracted by `items`, setting
|
|
194
|
+
* `shared[key]` each time (defaults to `"__batchItem"`).
|
|
195
|
+
*
|
|
196
|
+
* Use a unique `key` when nesting batches so each level has its own
|
|
197
|
+
* namespace:
|
|
198
|
+
* ```ts
|
|
199
|
+
* .batch(s => s.users, b => b
|
|
200
|
+
* .startWith(s => { console.log(s.__batch_user); })
|
|
201
|
+
* .batch(s => s.__batch_user.posts, p => p
|
|
202
|
+
* .startWith(s => { console.log(s.__batch_post); })
|
|
203
|
+
* , { key: '__batch_post' })
|
|
204
|
+
* , { key: '__batch_user' })
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
batch(items: (shared: S, params: P) => Promise<any[]> | any[], processor: (b: FlowBuilder<S, P>) => void, options?: {
|
|
208
|
+
key?: string;
|
|
209
|
+
}): this;
|
|
210
|
+
/**
|
|
211
|
+
* Append a parallel step.
|
|
212
|
+
* Runs all `fns` concurrently against the same shared state.
|
|
213
|
+
*
|
|
214
|
+
* When `reducer` is provided each fn receives its own shallow clone of
|
|
215
|
+
* `shared`; after all fns complete the reducer merges the drafts back
|
|
216
|
+
* into the original shared object — preventing concurrent mutation races.
|
|
217
|
+
*/
|
|
218
|
+
parallel(fns: NodeFn<S, P>[], options?: NodeOptions<S, P>, reducer?: (shared: S, drafts: S[]) => void): this;
|
|
219
|
+
/**
|
|
220
|
+
* Insert a named anchor. Anchors are no-op markers that can be jumped to
|
|
221
|
+
* from any `NodeFn` by returning `"#anchorName"`.
|
|
222
|
+
*/
|
|
223
|
+
anchor(name: string): this;
|
|
224
|
+
/** Execute the flow. */
|
|
225
|
+
run(shared: S, params?: P, options?: RunOptions): Promise<void>;
|
|
226
|
+
/**
|
|
227
|
+
* Execute the flow and yield `StreamEvent`s as an async generator.
|
|
228
|
+
*
|
|
229
|
+
* Events include `step:before`, `step:after`, `chunk` (from `emit()`),
|
|
230
|
+
* `error`, and `done`. This is an additive API — `.run()` is unchanged.
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* for await (const event of flow.stream(shared)) {
|
|
234
|
+
* if (event.type === "chunk") process.stdout.write(event.data);
|
|
235
|
+
* }
|
|
236
|
+
*/
|
|
237
|
+
stream(shared: S, params?: P, options?: RunOptions): AsyncGenerator<StreamEvent<S>>;
|
|
238
|
+
protected _execute(shared: S, params: P, signal?: AbortSignal): Promise<void>;
|
|
239
|
+
private _addFn;
|
|
240
|
+
/** Resolve a NumberOrFn value against the current shared state and params. */
|
|
241
|
+
private _res;
|
|
242
|
+
private _runSub;
|
|
243
|
+
private _retry;
|
|
244
|
+
private _withTimeout;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export { FlowBuilder as F, type NodeFn as N, type RunOptions as R, type StepMeta as S, type Validator as V, type NodeOptions as a, type FlowneerPlugin as b, type FlowHooks as c, type NumberOrFn as d, type StreamEvent as e };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,226 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* Valibot, or any custom implementation that exposes `.parse(input)`.
|
|
4
|
-
* Used by `withStructuredOutput` and output parsers.
|
|
5
|
-
*/
|
|
6
|
-
interface Validator<T = unknown> {
|
|
7
|
-
parse(input: unknown): T;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Events yielded by `FlowBuilder.stream()`.
|
|
11
|
-
*/
|
|
12
|
-
type StreamEvent<S = any> = {
|
|
13
|
-
type: "step:before";
|
|
14
|
-
meta: StepMeta;
|
|
15
|
-
} | {
|
|
16
|
-
type: "step:after";
|
|
17
|
-
meta: StepMeta;
|
|
18
|
-
shared: S;
|
|
19
|
-
} | {
|
|
20
|
-
type: "chunk";
|
|
21
|
-
data: unknown;
|
|
22
|
-
} | {
|
|
23
|
-
type: "error";
|
|
24
|
-
error: unknown;
|
|
25
|
-
} | {
|
|
26
|
-
type: "done";
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* Function signature for all step logic.
|
|
30
|
-
* Return an action string to route, or undefined/void to continue.
|
|
31
|
-
*/
|
|
32
|
-
type NodeFn<S = any, P extends Record<string, unknown> = Record<string, unknown>> = (shared: S, params: P) => Promise<string | undefined | void> | string | undefined | void;
|
|
33
|
-
/**
|
|
34
|
-
* A numeric value or a function that computes it from the current shared state
|
|
35
|
-
* and params. Use functions for dynamic per-item behaviour, e.g.
|
|
36
|
-
* `retries: (s) => (s.__batchItem % 3 === 0 ? 3 : 1)`.
|
|
37
|
-
*/
|
|
38
|
-
type NumberOrFn<S = any, P extends Record<string, unknown> = Record<string, unknown>> = number | ((shared: S, params: P) => number);
|
|
39
|
-
interface NodeOptions<S = any, P extends Record<string, unknown> = Record<string, unknown>> {
|
|
40
|
-
retries?: NumberOrFn<S, P>;
|
|
41
|
-
delaySec?: NumberOrFn<S, P>;
|
|
42
|
-
timeoutMs?: NumberOrFn<S, P>;
|
|
43
|
-
}
|
|
44
|
-
interface RunOptions {
|
|
45
|
-
signal?: AbortSignal;
|
|
46
|
-
}
|
|
47
|
-
interface FnStep<S, P extends Record<string, unknown>> {
|
|
48
|
-
type: "fn";
|
|
49
|
-
fn: NodeFn<S, P>;
|
|
50
|
-
retries: NumberOrFn<S, P>;
|
|
51
|
-
delaySec: NumberOrFn<S, P>;
|
|
52
|
-
timeoutMs: NumberOrFn<S, P>;
|
|
53
|
-
}
|
|
54
|
-
interface BranchStep<S, P extends Record<string, unknown>> {
|
|
55
|
-
type: "branch";
|
|
56
|
-
router: NodeFn<S, P>;
|
|
57
|
-
branches: Record<string, NodeFn<S, P>>;
|
|
58
|
-
retries: NumberOrFn<S, P>;
|
|
59
|
-
delaySec: NumberOrFn<S, P>;
|
|
60
|
-
timeoutMs: NumberOrFn<S, P>;
|
|
61
|
-
}
|
|
62
|
-
interface LoopStep<S, P extends Record<string, unknown>> {
|
|
63
|
-
type: "loop";
|
|
64
|
-
condition: (shared: S, params: P) => Promise<boolean> | boolean;
|
|
65
|
-
body: FlowBuilder<S, P>;
|
|
66
|
-
}
|
|
67
|
-
interface BatchStep<S, P extends Record<string, unknown>> {
|
|
68
|
-
type: "batch";
|
|
69
|
-
itemsExtractor: (shared: S, params: P) => Promise<any[]> | any[];
|
|
70
|
-
processor: FlowBuilder<S, P>;
|
|
71
|
-
key: string;
|
|
72
|
-
}
|
|
73
|
-
interface ParallelStep<S, P extends Record<string, unknown>> {
|
|
74
|
-
type: "parallel";
|
|
75
|
-
fns: NodeFn<S, P>[];
|
|
76
|
-
retries: NumberOrFn<S, P>;
|
|
77
|
-
delaySec: NumberOrFn<S, P>;
|
|
78
|
-
timeoutMs: NumberOrFn<S, P>;
|
|
79
|
-
reducer?: (shared: S, drafts: S[]) => void;
|
|
80
|
-
}
|
|
81
|
-
interface AnchorStep {
|
|
82
|
-
type: "anchor";
|
|
83
|
-
name: string;
|
|
84
|
-
}
|
|
85
|
-
type Step<S, P extends Record<string, unknown>> = FnStep<S, P> | BranchStep<S, P> | LoopStep<S, P> | BatchStep<S, P> | ParallelStep<S, P> | AnchorStep;
|
|
86
|
-
/** Metadata exposed to hooks — intentionally minimal to avoid coupling. */
|
|
87
|
-
interface StepMeta {
|
|
88
|
-
index: number;
|
|
89
|
-
type: Step<any, any>["type"];
|
|
90
|
-
label?: string;
|
|
91
|
-
}
|
|
92
|
-
/** Lifecycle hooks that plugins can register. */
|
|
93
|
-
interface FlowHooks<S = any, P extends Record<string, unknown> = Record<string, unknown>> {
|
|
94
|
-
/** Fires once before the first step runs. */
|
|
95
|
-
beforeFlow?: (shared: S, params: P) => void | Promise<void>;
|
|
96
|
-
beforeStep?: (meta: StepMeta, shared: S, params: P) => void | Promise<void>;
|
|
97
|
-
/**
|
|
98
|
-
* Wraps step execution — call `next()` to invoke the step body.
|
|
99
|
-
* Omitting `next()` skips execution (dry-run, mock, etc.).
|
|
100
|
-
* Multiple `wrapStep` registrations are composed innermost-first.
|
|
101
|
-
*/
|
|
102
|
-
wrapStep?: (meta: StepMeta, next: () => Promise<void>, shared: S, params: P) => Promise<void>;
|
|
103
|
-
afterStep?: (meta: StepMeta, shared: S, params: P) => void | Promise<void>;
|
|
104
|
-
/**
|
|
105
|
-
* Wraps individual functions within a `.parallel()` step.
|
|
106
|
-
* `fnIndex` is the position within the fns array.
|
|
107
|
-
*/
|
|
108
|
-
wrapParallelFn?: (meta: StepMeta, fnIndex: number, next: () => Promise<void>, shared: S, params: P) => Promise<void>;
|
|
109
|
-
onError?: (meta: StepMeta, error: unknown, shared: S, params: P) => void;
|
|
110
|
-
afterFlow?: (shared: S, params: P) => void | Promise<void>;
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* A plugin is an object whose keys become methods on `FlowBuilder.prototype`.
|
|
114
|
-
* Each method receives the builder as `this` and should return `this` for chaining.
|
|
115
|
-
*
|
|
116
|
-
* Use declaration merging to get type-safe access:
|
|
117
|
-
* ```ts
|
|
118
|
-
* declare module "flowneer" {
|
|
119
|
-
* interface FlowBuilder<S, P> { withTracing(fn: TraceCallback): this; }
|
|
120
|
-
* }
|
|
121
|
-
* ```
|
|
122
|
-
*/
|
|
123
|
-
type FlowneerPlugin = Record<string, (this: FlowBuilder<any, any>, ...args: any[]) => any>;
|
|
124
|
-
/** Wraps step failures with context about which step failed. */
|
|
125
|
-
declare class FlowError extends Error {
|
|
126
|
-
readonly step: string;
|
|
127
|
-
readonly cause: unknown;
|
|
128
|
-
constructor(step: string, cause: unknown);
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Thrown by `interruptIf` to pause a flow.
|
|
132
|
-
* Catch this in your runner to save `savedShared` and resume later.
|
|
133
|
-
*/
|
|
134
|
-
declare class InterruptError extends Error {
|
|
135
|
-
readonly savedShared: unknown;
|
|
136
|
-
constructor(shared: unknown);
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Fluent builder for composable flows.
|
|
140
|
-
*
|
|
141
|
-
* Steps execute sequentially in the order added. Call `.run(shared)` to execute.
|
|
142
|
-
*
|
|
143
|
-
* **Shared-state safety**: all steps operate on the same shared object.
|
|
144
|
-
* Mutate it directly; avoid spreading/replacing the entire object.
|
|
145
|
-
*/
|
|
146
|
-
declare class FlowBuilder<S = any, P extends Record<string, unknown> = Record<string, unknown>> {
|
|
147
|
-
private steps;
|
|
148
|
-
private _hooksList;
|
|
149
|
-
/** Cached flat arrays of present hooks — invalidated whenever a hook is added. */
|
|
150
|
-
private _cachedHooks;
|
|
151
|
-
private _getHooks;
|
|
152
|
-
/** Register a plugin — copies its methods onto `FlowBuilder.prototype`. */
|
|
153
|
-
static use(plugin: FlowneerPlugin): void;
|
|
154
|
-
/** Register lifecycle hooks (called by plugin methods, not by consumers). */
|
|
155
|
-
protected _setHooks(hooks: Partial<FlowHooks<S, P>>): void;
|
|
156
|
-
/** Set the first step, resetting any prior chain. */
|
|
157
|
-
startWith(fn: NodeFn<S, P>, options?: NodeOptions<S, P>): this;
|
|
158
|
-
/** Append a sequential step. */
|
|
159
|
-
then(fn: NodeFn<S, P>, options?: NodeOptions<S, P>): this;
|
|
160
|
-
/**
|
|
161
|
-
* Append a routing step.
|
|
162
|
-
* `router` returns a key; the matching branch flow executes, then the chain continues.
|
|
163
|
-
*/
|
|
164
|
-
branch(router: NodeFn<S, P>, branches: Record<string, NodeFn<S, P>>, options?: NodeOptions<S, P>): this;
|
|
165
|
-
/**
|
|
166
|
-
* Append a looping step.
|
|
167
|
-
* Repeatedly runs `body` while `condition` returns true.
|
|
168
|
-
*/
|
|
169
|
-
loop(condition: (shared: S, params: P) => Promise<boolean> | boolean, body: (b: FlowBuilder<S, P>) => void): this;
|
|
170
|
-
/**
|
|
171
|
-
* Append a batch step.
|
|
172
|
-
* Runs `processor` once per item extracted by `items`, setting
|
|
173
|
-
* `shared[key]` each time (defaults to `"__batchItem"`).
|
|
174
|
-
*
|
|
175
|
-
* Use a unique `key` when nesting batches so each level has its own
|
|
176
|
-
* namespace:
|
|
177
|
-
* ```ts
|
|
178
|
-
* .batch(s => s.users, b => b
|
|
179
|
-
* .startWith(s => { console.log(s.__batch_user); })
|
|
180
|
-
* .batch(s => s.__batch_user.posts, p => p
|
|
181
|
-
* .startWith(s => { console.log(s.__batch_post); })
|
|
182
|
-
* , { key: '__batch_post' })
|
|
183
|
-
* , { key: '__batch_user' })
|
|
184
|
-
* ```
|
|
185
|
-
*/
|
|
186
|
-
batch(items: (shared: S, params: P) => Promise<any[]> | any[], processor: (b: FlowBuilder<S, P>) => void, options?: {
|
|
187
|
-
key?: string;
|
|
188
|
-
}): this;
|
|
189
|
-
/**
|
|
190
|
-
* Append a parallel step.
|
|
191
|
-
* Runs all `fns` concurrently against the same shared state.
|
|
192
|
-
*
|
|
193
|
-
* When `reducer` is provided each fn receives its own shallow clone of
|
|
194
|
-
* `shared`; after all fns complete the reducer merges the drafts back
|
|
195
|
-
* into the original shared object — preventing concurrent mutation races.
|
|
196
|
-
*/
|
|
197
|
-
parallel(fns: NodeFn<S, P>[], options?: NodeOptions<S, P>, reducer?: (shared: S, drafts: S[]) => void): this;
|
|
198
|
-
/**
|
|
199
|
-
* Insert a named anchor. Anchors are no-op markers that can be jumped to
|
|
200
|
-
* from any `NodeFn` by returning `"#anchorName"`.
|
|
201
|
-
*/
|
|
202
|
-
anchor(name: string): this;
|
|
203
|
-
/** Execute the flow. */
|
|
204
|
-
run(shared: S, params?: P, options?: RunOptions): Promise<void>;
|
|
205
|
-
/**
|
|
206
|
-
* Execute the flow and yield `StreamEvent`s as an async generator.
|
|
207
|
-
*
|
|
208
|
-
* Events include `step:before`, `step:after`, `chunk` (from `emit()`),
|
|
209
|
-
* `error`, and `done`. This is an additive API — `.run()` is unchanged.
|
|
210
|
-
*
|
|
211
|
-
* @example
|
|
212
|
-
* for await (const event of flow.stream(shared)) {
|
|
213
|
-
* if (event.type === "chunk") process.stdout.write(event.data);
|
|
214
|
-
* }
|
|
215
|
-
*/
|
|
216
|
-
stream(shared: S, params?: P, options?: RunOptions): AsyncGenerator<StreamEvent<S>>;
|
|
217
|
-
protected _execute(shared: S, params: P, signal?: AbortSignal): Promise<void>;
|
|
218
|
-
private _addFn;
|
|
219
|
-
/** Resolve a NumberOrFn value against the current shared state and params. */
|
|
220
|
-
private _res;
|
|
221
|
-
private _runSub;
|
|
222
|
-
private _retry;
|
|
223
|
-
private _withTimeout;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
export { FlowBuilder, FlowError, type FlowHooks, type FlowneerPlugin, InterruptError, type NodeFn, type NodeOptions, type NumberOrFn, type RunOptions, type StepMeta, type StreamEvent, type Validator };
|
|
1
|
+
export { F as FlowBuilder, c as FlowHooks, b as FlowneerPlugin, N as NodeFn, a as NodeOptions, d as NumberOrFn, R as RunOptions, S as StepMeta, e as StreamEvent, V as Validator } from './FlowBuilder-Dqppgla9.js';
|
|
2
|
+
export { FlowError, Fragment, InterruptError, fragment } from './src/index.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// src/errors.ts
|
|
2
2
|
var FlowError = class extends Error {
|
|
3
3
|
step;
|
|
4
4
|
cause;
|
|
@@ -19,6 +19,8 @@ var InterruptError = class extends Error {
|
|
|
19
19
|
this.savedShared = shared;
|
|
20
20
|
}
|
|
21
21
|
};
|
|
22
|
+
|
|
23
|
+
// src/FlowBuilder.ts
|
|
22
24
|
var FlowBuilder = class _FlowBuilder {
|
|
23
25
|
steps = [];
|
|
24
26
|
_hooksList = [];
|
|
@@ -64,6 +66,29 @@ var FlowBuilder = class _FlowBuilder {
|
|
|
64
66
|
then(fn, options) {
|
|
65
67
|
return this._addFn(fn, options);
|
|
66
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Splice all steps from a `Fragment` into this flow at the current position.
|
|
71
|
+
*
|
|
72
|
+
* Fragments are reusable partial flows created with the `fragment()` factory.
|
|
73
|
+
* Steps are copied by reference (same semantics as `loop` / `batch` inners).
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* const enrich = fragment<S>()
|
|
78
|
+
* .then(fetchUser)
|
|
79
|
+
* .then(enrichProfile);
|
|
80
|
+
*
|
|
81
|
+
* new FlowBuilder<S>()
|
|
82
|
+
* .then(init)
|
|
83
|
+
* .add(enrich)
|
|
84
|
+
* .then(finalize)
|
|
85
|
+
* .run(shared);
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
add(frag) {
|
|
89
|
+
for (const step of frag.steps) this.steps.push(step);
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
67
92
|
/**
|
|
68
93
|
* Append a routing step.
|
|
69
94
|
* `router` returns a key; the matching branch flow executes, then the chain continues.
|
|
@@ -192,7 +217,7 @@ var FlowBuilder = class _FlowBuilder {
|
|
|
192
217
|
push({ type: "chunk", data: chunk });
|
|
193
218
|
if (typeof prevStream === "function") prevStream(chunk);
|
|
194
219
|
};
|
|
195
|
-
|
|
220
|
+
this.run(shared, params, options).catch((err) => push({ type: "error", error: err })).then(() => push(null));
|
|
196
221
|
while (true) {
|
|
197
222
|
await pull();
|
|
198
223
|
while (queue.length > 0) {
|
|
@@ -233,8 +258,18 @@ var FlowBuilder = class _FlowBuilder {
|
|
|
233
258
|
this._res(step.delaySec, 0, shared, params),
|
|
234
259
|
() => step.fn(shared, params)
|
|
235
260
|
);
|
|
236
|
-
if (
|
|
261
|
+
if (result != null && typeof result[Symbol.asyncIterator] === "function") {
|
|
262
|
+
const gen = result;
|
|
263
|
+
let genResult = await gen.next();
|
|
264
|
+
while (!genResult.done) {
|
|
265
|
+
shared.__stream?.(genResult.value);
|
|
266
|
+
genResult = await gen.next();
|
|
267
|
+
}
|
|
268
|
+
if (typeof genResult.value === "string" && genResult.value[0] === "#")
|
|
269
|
+
gotoTarget = genResult.value.slice(1);
|
|
270
|
+
} else if (typeof result === "string" && result[0] === "#") {
|
|
237
271
|
gotoTarget = result.slice(1);
|
|
272
|
+
}
|
|
238
273
|
break;
|
|
239
274
|
}
|
|
240
275
|
case "branch": {
|
|
@@ -392,8 +427,29 @@ var FlowBuilder = class _FlowBuilder {
|
|
|
392
427
|
]);
|
|
393
428
|
}
|
|
394
429
|
};
|
|
430
|
+
|
|
431
|
+
// src/Fragment.ts
|
|
432
|
+
var Fragment = class extends FlowBuilder {
|
|
433
|
+
/** @internal Fragments cannot be run — embed them via `.add()`. */
|
|
434
|
+
async run(_shared, _params) {
|
|
435
|
+
throw new Error(
|
|
436
|
+
"Fragment cannot be run directly \u2014 use .add() to embed it in a FlowBuilder"
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
/** @internal Fragments cannot be streamed — embed them via `.add()`. */
|
|
440
|
+
async *stream(_shared, _params) {
|
|
441
|
+
throw new Error(
|
|
442
|
+
"Fragment cannot be streamed directly \u2014 use .add() to embed it in a FlowBuilder"
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
function fragment() {
|
|
447
|
+
return new Fragment();
|
|
448
|
+
}
|
|
395
449
|
export {
|
|
396
450
|
FlowBuilder,
|
|
397
451
|
FlowError,
|
|
398
|
-
|
|
452
|
+
Fragment,
|
|
453
|
+
InterruptError,
|
|
454
|
+
fragment
|
|
399
455
|
};
|
|
@@ -80,7 +80,7 @@ var withReActLoop = {
|
|
|
80
80
|
}
|
|
81
81
|
};
|
|
82
82
|
|
|
83
|
-
//
|
|
83
|
+
// src/errors.ts
|
|
84
84
|
var FlowError = class extends Error {
|
|
85
85
|
step;
|
|
86
86
|
cause;
|
|
@@ -101,6 +101,8 @@ var InterruptError = class extends Error {
|
|
|
101
101
|
this.savedShared = shared;
|
|
102
102
|
}
|
|
103
103
|
};
|
|
104
|
+
|
|
105
|
+
// src/FlowBuilder.ts
|
|
104
106
|
var FlowBuilder = class _FlowBuilder {
|
|
105
107
|
steps = [];
|
|
106
108
|
_hooksList = [];
|
|
@@ -146,6 +148,29 @@ var FlowBuilder = class _FlowBuilder {
|
|
|
146
148
|
then(fn, options) {
|
|
147
149
|
return this._addFn(fn, options);
|
|
148
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Splice all steps from a `Fragment` into this flow at the current position.
|
|
153
|
+
*
|
|
154
|
+
* Fragments are reusable partial flows created with the `fragment()` factory.
|
|
155
|
+
* Steps are copied by reference (same semantics as `loop` / `batch` inners).
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* ```ts
|
|
159
|
+
* const enrich = fragment<S>()
|
|
160
|
+
* .then(fetchUser)
|
|
161
|
+
* .then(enrichProfile);
|
|
162
|
+
*
|
|
163
|
+
* new FlowBuilder<S>()
|
|
164
|
+
* .then(init)
|
|
165
|
+
* .add(enrich)
|
|
166
|
+
* .then(finalize)
|
|
167
|
+
* .run(shared);
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
add(frag) {
|
|
171
|
+
for (const step of frag.steps) this.steps.push(step);
|
|
172
|
+
return this;
|
|
173
|
+
}
|
|
149
174
|
/**
|
|
150
175
|
* Append a routing step.
|
|
151
176
|
* `router` returns a key; the matching branch flow executes, then the chain continues.
|
|
@@ -274,7 +299,7 @@ var FlowBuilder = class _FlowBuilder {
|
|
|
274
299
|
push({ type: "chunk", data: chunk });
|
|
275
300
|
if (typeof prevStream === "function") prevStream(chunk);
|
|
276
301
|
};
|
|
277
|
-
|
|
302
|
+
this.run(shared, params, options).catch((err) => push({ type: "error", error: err })).then(() => push(null));
|
|
278
303
|
while (true) {
|
|
279
304
|
await pull();
|
|
280
305
|
while (queue.length > 0) {
|
|
@@ -315,8 +340,18 @@ var FlowBuilder = class _FlowBuilder {
|
|
|
315
340
|
this._res(step.delaySec, 0, shared, params),
|
|
316
341
|
() => step.fn(shared, params)
|
|
317
342
|
);
|
|
318
|
-
if (
|
|
343
|
+
if (result != null && typeof result[Symbol.asyncIterator] === "function") {
|
|
344
|
+
const gen = result;
|
|
345
|
+
let genResult = await gen.next();
|
|
346
|
+
while (!genResult.done) {
|
|
347
|
+
shared.__stream?.(genResult.value);
|
|
348
|
+
genResult = await gen.next();
|
|
349
|
+
}
|
|
350
|
+
if (typeof genResult.value === "string" && genResult.value[0] === "#")
|
|
351
|
+
gotoTarget = genResult.value.slice(1);
|
|
352
|
+
} else if (typeof result === "string" && result[0] === "#") {
|
|
319
353
|
gotoTarget = result.slice(1);
|
|
354
|
+
}
|
|
320
355
|
break;
|
|
321
356
|
}
|
|
322
357
|
case "branch": {
|