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 CHANGED
@@ -1,5 +1,5 @@
1
1
  <div align="center">
2
- <img src="https://raw.githubusercontent.com/Fanna1119/flowneer/feature/extras/docs/images/flowneer_logo.png" width="500" height="auto" alt="Flowneer"/>
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
- * 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
- 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
- // Flowneer.ts
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
- const flowDone = this.run(shared, params, options).catch((err) => push({ type: "error", error: err })).then(() => push(null));
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 (typeof result === "string" && result[0] === "#")
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
- InterruptError
452
+ Fragment,
453
+ InterruptError,
454
+ fragment
399
455
  };
@@ -1,4 +1,4 @@
1
- import { FlowneerPlugin, FlowBuilder, NodeFn } from '../../index.js';
1
+ import { b as FlowneerPlugin, F as FlowBuilder, N as NodeFn } from '../../FlowBuilder-Dqppgla9.js';
2
2
  import { ToolCall, ToolResult } from '../tools/index.js';
3
3
 
4
4
  /**
@@ -80,7 +80,7 @@ var withReActLoop = {
80
80
  }
81
81
  };
82
82
 
83
- // Flowneer.ts
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
- const flowDone = this.run(shared, params, options).catch((err) => push({ type: "error", error: err })).then(() => push(null));
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 (typeof result === "string" && result[0] === "#")
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": {