flowneer 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,6 +22,10 @@ A tiny, zero-dependency fluent flow builder for TypeScript. Chain steps, branch
22
22
  bun add flowneer
23
23
  ```
24
24
 
25
+ ## For LLM Agents
26
+ [llms.txt](https://fanna1119.github.io/flowneer/llms.txt)
27
+ [llms-full.txt](https://fanna1119.github.io/flowneer/llms-full.txt)
28
+
25
29
  ## Quick start
26
30
 
27
31
  ```typescript
@@ -53,6 +53,8 @@ interface NodeOptions<S = any, P extends Record<string, unknown> = Record<string
53
53
  retries?: NumberOrFn<S, P>;
54
54
  delaySec?: NumberOrFn<S, P>;
55
55
  timeoutMs?: NumberOrFn<S, P>;
56
+ /** Human-readable label for this step, shown in error messages and hook metadata. */
57
+ label?: string;
56
58
  }
57
59
  interface RunOptions {
58
60
  signal?: AbortSignal;
@@ -95,6 +97,31 @@ interface FlowHooks<S = any, P extends Record<string, unknown> = Record<string,
95
97
  * ```
96
98
  */
97
99
  type FlowneerPlugin = Record<string, (this: FlowBuilder<any, any>, ...args: any[]) => any>;
100
+ /**
101
+ * An instance-scoped plugin. Called immediately when passed to `flow.use()`;
102
+ * registers hooks and/or performs setup on the specific `FlowBuilder` instance.
103
+ *
104
+ * Write plugins as factories so settings are captured in a closure:
105
+ * ```ts
106
+ * function withTiming(): InstancePlugin<any> {
107
+ * return (flow) => {
108
+ * const starts = new Map<number, number>();
109
+ * flow.addHooks({
110
+ * beforeStep: (meta) => { starts.set(meta.index, Date.now()); },
111
+ * afterStep: (meta, shared) => { shared.__timings ??= {}; shared.__timings[meta.index] = Date.now() - starts.get(meta.index)!; },
112
+ * });
113
+ * };
114
+ * }
115
+ *
116
+ * const flow = new FlowBuilder<MyState>()
117
+ * .with([withTiming(), withRateLimit({ rps: 10 })])
118
+ * .then(myStep);
119
+ * ```
120
+ *
121
+ * Order matters: plugins are applied left-to-right, so earlier plugins wrap
122
+ * later ones (the first plugin's `wrapStep` runs outermost).
123
+ */
124
+ type InstancePlugin<S = any, P extends Record<string, unknown> = Record<string, unknown>> = (flow: FlowBuilder<S, P>) => void;
98
125
 
99
126
  interface FnStep<S, P extends Record<string, unknown>> {
100
127
  type: "fn";
@@ -102,6 +129,7 @@ interface FnStep<S, P extends Record<string, unknown>> {
102
129
  retries: NumberOrFn<S, P>;
103
130
  delaySec: NumberOrFn<S, P>;
104
131
  timeoutMs: NumberOrFn<S, P>;
132
+ label?: string;
105
133
  }
106
134
  interface BranchStep<S, P extends Record<string, unknown>> {
107
135
  type: "branch";
@@ -110,17 +138,20 @@ interface BranchStep<S, P extends Record<string, unknown>> {
110
138
  retries: NumberOrFn<S, P>;
111
139
  delaySec: NumberOrFn<S, P>;
112
140
  timeoutMs: NumberOrFn<S, P>;
141
+ label?: string;
113
142
  }
114
143
  interface LoopStep<S, P extends Record<string, unknown>> {
115
144
  type: "loop";
116
145
  condition: (shared: S, params: P) => Promise<boolean> | boolean;
117
- body: FlowBuilder<S, P>;
146
+ body: CoreFlowBuilder<S, P>;
147
+ label?: string;
118
148
  }
119
149
  interface BatchStep<S, P extends Record<string, unknown>> {
120
150
  type: "batch";
121
151
  itemsExtractor: (shared: S, params: P) => Promise<any[]> | any[];
122
- processor: FlowBuilder<S, P>;
152
+ processor: CoreFlowBuilder<S, P>;
123
153
  key: string;
154
+ label?: string;
124
155
  }
125
156
  interface ParallelStep<S, P extends Record<string, unknown>> {
126
157
  type: "parallel";
@@ -129,13 +160,119 @@ interface ParallelStep<S, P extends Record<string, unknown>> {
129
160
  delaySec: NumberOrFn<S, P>;
130
161
  timeoutMs: NumberOrFn<S, P>;
131
162
  reducer?: (shared: S, drafts: S[]) => void;
163
+ label?: string;
132
164
  }
133
165
  interface AnchorStep {
134
166
  type: "anchor";
135
167
  name: string;
168
+ /** Maximum number of times a goto may jump to this anchor per run. */
169
+ maxVisits?: number;
136
170
  }
137
171
  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
172
 
173
+ type ResolvedHooks<S, P extends Record<string, unknown>> = {
174
+ beforeFlow: NonNullable<FlowHooks<S, P>["beforeFlow"]>[];
175
+ beforeStep: NonNullable<FlowHooks<S, P>["beforeStep"]>[];
176
+ wrapStep: NonNullable<FlowHooks<S, P>["wrapStep"]>[];
177
+ afterStep: NonNullable<FlowHooks<S, P>["afterStep"]>[];
178
+ wrapParallelFn: NonNullable<FlowHooks<S, P>["wrapParallelFn"]>[];
179
+ onError: NonNullable<FlowHooks<S, P>["onError"]>[];
180
+ afterFlow: NonNullable<FlowHooks<S, P>["afterFlow"]>[];
181
+ };
182
+ /**
183
+ * Runtime context passed to every registered step handler.
184
+ * Full access to the builder so handlers can run sub-flows etc.
185
+ */
186
+ interface StepContext<S, P extends Record<string, unknown> = Record<string, unknown>> {
187
+ shared: S;
188
+ params: P;
189
+ signal?: AbortSignal;
190
+ hooks: ResolvedHooks<S, P>;
191
+ meta: StepMeta;
192
+ builder: CoreFlowBuilder<S, P>;
193
+ }
194
+ /**
195
+ * Signature for step type execution handlers registered via
196
+ * `CoreFlowBuilder.registerStepType()`.
197
+ *
198
+ * Return a goto anchor name (without `#`) to jump, or `undefined` to continue.
199
+ */
200
+ type StepHandler = (step: any, ctx: StepContext<any, any>) => Promise<string | undefined>;
201
+ /**
202
+ * Minimal orchestration engine. Knows nothing about specific step types —
203
+ * those are registered as plugins via `CoreFlowBuilder.registerStepType()`.
204
+ *
205
+ * Use this as a base when you want a completely fresh, zero-assumption builder.
206
+ * The exported `FlowBuilder` extends this with all primitive step types
207
+ * (fn, branch, loop, batch, parallel, anchor) pre-registered.
208
+ */
209
+ declare class CoreFlowBuilder<S = any, P extends Record<string, unknown> = Record<string, unknown>> {
210
+ /** @internal Ordered list of step descriptors. */
211
+ steps: Step<S, P>[];
212
+ /** @internal Cached anchor-name → index map; null when stale. */
213
+ protected _anchorMap: Map<string, number> | null;
214
+ private _hooksList;
215
+ private _hooksCache;
216
+ private static _stepHandlers;
217
+ /**
218
+ * Register a handler for a custom step type.
219
+ * Called once at module load time from each step plugin.
220
+ *
221
+ * @example
222
+ * CoreFlowBuilder.registerStepType("myStep", async (step, ctx) => {
223
+ * await doWork(ctx.shared);
224
+ * return undefined;
225
+ * });
226
+ */
227
+ static registerStepType(type: string, handler: StepHandler): void;
228
+ private _hooks;
229
+ /**
230
+ * Register lifecycle hooks (called by plugin methods, not by consumers).
231
+ * Returns a dispose function that removes these hooks when called.
232
+ */
233
+ protected _setHooks(hooks: Partial<FlowHooks<S, P>>): () => void;
234
+ /**
235
+ * Register a plugin — copies its methods onto `FlowBuilder.prototype`.
236
+ */
237
+ static use(plugin: FlowneerPlugin): void;
238
+ /**
239
+ * Apply one or more instance-scoped plugins to this builder.
240
+ * Each plugin is called immediately; plugins are applied in array order
241
+ * so the first plugin's hooks wrap the later ones.
242
+ *
243
+ * @example
244
+ * const flow = new FlowBuilder<MyState>()
245
+ * .with([withTiming(), withRateLimit({ rps: 10 })])
246
+ * .then(myStep);
247
+ */
248
+ with(plugins: InstancePlugin<S, P> | Array<InstancePlugin<S, P>>): this;
249
+ /**
250
+ * Register lifecycle hooks directly on this instance.
251
+ * Returns a dispose function that removes these hooks when called.
252
+ */
253
+ addHooks(hooks: Partial<FlowHooks<S, P>>): () => void;
254
+ /** Execute the flow. */
255
+ run(shared: S, params?: P, options?: RunOptions): Promise<void>;
256
+ /**
257
+ * Execute the flow and yield `StreamEvent`s as an async generator.
258
+ */
259
+ stream(shared: S, params?: P, options?: RunOptions): AsyncGenerator<StreamEvent<S>>;
260
+ /** @internal Core execution loop, bypasses beforeFlow/afterFlow hooks. */
261
+ _execute(shared: S, params: P, signal?: AbortSignal): Promise<void>;
262
+ private _runStep;
263
+ /** @internal Run a sub-flow, wrapping errors with context. */
264
+ _runSub(label: string, fn: () => Promise<void>): Promise<void>;
265
+ /** @internal Resolve NodeOptions into their stored defaults. */
266
+ _resolveOptions(options?: NodeOptions<S, P>): {
267
+ retries: NumberOrFn<S, P>;
268
+ delaySec: NumberOrFn<S, P>;
269
+ timeoutMs: NumberOrFn<S, P>;
270
+ label: string | undefined;
271
+ };
272
+ /** @internal Push a step and invalidate the anchor-map cache. */
273
+ protected _pushStep(step: Step<S, P>): void;
274
+ }
275
+
139
276
  /**
140
277
  * Fluent builder for composable flows.
141
278
  *
@@ -143,15 +280,18 @@ type Step<S, P extends Record<string, unknown>> = FnStep<S, P> | BranchStep<S, P
143
280
  *
144
281
  * **Shared-state safety**: all steps operate on the same shared object.
145
282
  * Mutate it directly; avoid spreading/replacing the entire object.
283
+ *
284
+ * Built on top of {@link CoreFlowBuilder} which exposes the raw engine for
285
+ * advanced use cases (custom step types, zero-assumption base class, etc.).
146
286
  */
147
- declare class FlowBuilder<S = any, P extends Record<string, unknown> = Record<string, unknown>> {
148
- protected steps: Step<S, P>[];
149
- private _hooksList;
150
- private _hooksCache;
151
- private _hooks;
152
- /** Register lifecycle hooks (called by plugin methods, not by consumers). */
153
- protected _setHooks(hooks: Partial<FlowHooks<S, P>>): void;
154
- /** Register a plugin — copies its methods onto `FlowBuilder.prototype`. */
287
+ declare class FlowBuilder<S = any, P extends Record<string, unknown> = Record<string, unknown>> extends CoreFlowBuilder<S, P> {
288
+ /**
289
+ * Register a plugin — copies its methods onto `FlowBuilder.prototype`.
290
+ *
291
+ * Plugins should also call `FlowBuilder.use()` (not just `CoreFlowBuilder.use()`)
292
+ * so TypeScript module-augmentation on `interface FlowBuilder` resolves
293
+ * correctly and the method appears on `FlowBuilder` instances specifically.
294
+ */
155
295
  static use(plugin: FlowneerPlugin): void;
156
296
  /** Set the first step, resetting any prior chain. */
157
297
  startWith(fn: NodeFn<S, P>, options?: NodeOptions<S, P>): this;
@@ -161,19 +301,12 @@ declare class FlowBuilder<S = any, P extends Record<string, unknown> = Record<st
161
301
  * Splice all steps from a `Fragment` into this flow at the current position.
162
302
  *
163
303
  * Fragments are reusable partial flows created with the `fragment()` factory.
164
- * Steps are copied by reference (same semantics as `loop` / `batch` inners).
165
304
  *
166
305
  * @example
167
306
  * ```ts
168
- * const enrich = fragment<S>()
169
- * .then(fetchUser)
170
- * .then(enrichProfile);
307
+ * const enrich = fragment<S>().then(fetchUser).then(enrichProfile);
171
308
  *
172
- * new FlowBuilder<S>()
173
- * .then(init)
174
- * .add(enrich)
175
- * .then(finalize)
176
- * .run(shared);
309
+ * new FlowBuilder<S>().then(init).add(enrich).then(finalize).run(shared);
177
310
  * ```
178
311
  */
179
312
  add(frag: FlowBuilder<S, P>): this;
@@ -186,25 +319,19 @@ declare class FlowBuilder<S = any, P extends Record<string, unknown> = Record<st
186
319
  * Append a looping step.
187
320
  * Repeatedly runs `body` while `condition` returns true.
188
321
  */
189
- loop(condition: (shared: S, params: P) => Promise<boolean> | boolean, body: (b: FlowBuilder<S, P>) => void): this;
322
+ loop(condition: (shared: S, params: P) => Promise<boolean> | boolean, body: (b: FlowBuilder<S, P>) => void, options?: {
323
+ label?: string;
324
+ }): this;
190
325
  /**
191
326
  * Append a batch step.
192
327
  * Runs `processor` once per item extracted by `items`, setting
193
328
  * `shared[key]` each time (defaults to `"__batchItem"`).
194
329
  *
195
- * Use a unique `key` when nesting batches so each level has its own
196
- * namespace:
197
- * ```ts
198
- * .batch(s => s.users, b => b
199
- * .startWith(s => { console.log(s.__batch_user); })
200
- * .batch(s => s.__batch_user.posts, p => p
201
- * .startWith(s => { console.log(s.__batch_post); })
202
- * , { key: '__batch_post' })
203
- * , { key: '__batch_user' })
204
- * ```
330
+ * Use a unique `key` when nesting batches so each level has its own namespace.
205
331
  */
206
332
  batch(items: (shared: S, params: P) => Promise<any[]> | any[], processor: (b: FlowBuilder<S, P>) => void, options?: {
207
333
  key?: string;
334
+ label?: string;
208
335
  }): this;
209
336
  /**
210
337
  * Append a parallel step.
@@ -218,36 +345,23 @@ declare class FlowBuilder<S = any, P extends Record<string, unknown> = Record<st
218
345
  /**
219
346
  * Insert a named anchor. Anchors are no-op markers that can be jumped to
220
347
  * from any `NodeFn` by returning `"#anchorName"`.
221
- */
222
- anchor(name: string): this;
223
- /** Execute the flow. */
224
- run(shared: S, params?: P, options?: RunOptions): Promise<void>;
225
- /**
226
- * Execute the flow and yield `StreamEvent`s as an async generator.
227
348
  *
228
- * Events include `step:before`, `step:after`, `chunk` (from `emit()`),
229
- * `error`, and `done`. This is an additive API `.run()` is unchanged.
349
+ * @param name - Anchor identifier, referenced as `"#name"` in goto returns.
350
+ * @param maxVisits - Optional cycle guard: throws after this many gotos to
351
+ * this anchor per `run()`. Replaces the need for a separate `withCycles`
352
+ * plugin call.
230
353
  *
231
354
  * @example
232
- * for await (const event of flow.stream(shared)) {
233
- * if (event.type === "chunk") process.stdout.write(event.data);
234
- * }
235
- */
236
- stream(shared: S, params?: P, options?: RunOptions): AsyncGenerator<StreamEvent<S>>;
237
- protected _execute(shared: S, params: P, signal?: AbortSignal): Promise<void>;
238
- /**
239
- * Apply timeout and `wrapStep` middleware around a single step, then
240
- * delegate to `_dispatchStep`. Returns an anchor target if the step
241
- * issued a goto, otherwise `undefined`.
242
- */
243
- private _runStep;
244
- /**
245
- * Pure step dispatch — no timeout, no `wrapStep`. Returns goto target if any.
355
+ * ```ts
356
+ * flow
357
+ * .anchor("refine", 5) // allow up to 5 refinement iterations
358
+ * .then(generateDraft)
359
+ * .then(s => s.score < 0.9 ? "#refine" : undefined)
360
+ * .then(publish);
361
+ * ```
246
362
  */
247
- private _dispatchStep;
248
- private _runParallel;
363
+ anchor(name: string, maxVisits?: number): this;
249
364
  private _addFn;
250
- private _runSub;
251
365
  }
252
366
 
253
- export { type AnchorStep as A, type BatchStep as B, FlowBuilder as F, type LoopStep as L, type NodeFn as N, type ParallelStep as P, type RunOptions as R, type StepMeta as S, type Validator as V, type FlowneerPlugin as a, type NodeOptions as b, type FlowHooks as c, type BranchStep as d, type FnStep as e, type NumberOrFn as f, type Step as g, type StreamEvent as h };
367
+ export { type AnchorStep as A, type BatchStep as B, CoreFlowBuilder as C, FlowBuilder as F, type InstancePlugin as I, type LoopStep as L, type NodeFn as N, type ParallelStep as P, type ResolvedHooks as R, type StepMeta as S, type Validator as V, type FlowneerPlugin as a, type NodeOptions as b, type FlowHooks as c, type BranchStep as d, type FnStep as e, type NumberOrFn as f, type RunOptions as g, type Step as h, type StepContext as i, type StepHandler as j, type StreamEvent as k };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { F as FlowBuilder, c as FlowHooks, a as FlowneerPlugin, N as NodeFn, b as NodeOptions, f as NumberOrFn, R as RunOptions, S as StepMeta, h as StreamEvent, V as Validator } from './FlowBuilder-C3B91Fzo.js';
1
+ export { F as FlowBuilder, c as FlowHooks, a as FlowneerPlugin, N as NodeFn, b as NodeOptions, f as NumberOrFn, g as RunOptions, S as StepMeta, k as StreamEvent, V as Validator } from './FlowBuilder-DJkzGH5l.js';
2
2
  export { FlowError, Fragment, InterruptError, fragment } from './src/index.js';