flowcraft 1.0.0-beta.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.
Files changed (148) hide show
  1. package/.editorconfig +9 -0
  2. package/LICENSE +21 -0
  3. package/README.md +249 -0
  4. package/config/tsconfig.json +21 -0
  5. package/config/tsup.config.ts +11 -0
  6. package/config/vitest.config.ts +11 -0
  7. package/docs/.vitepress/config.ts +105 -0
  8. package/docs/api-reference/builder.md +158 -0
  9. package/docs/api-reference/fn.md +142 -0
  10. package/docs/api-reference/index.md +38 -0
  11. package/docs/api-reference/workflow.md +126 -0
  12. package/docs/guide/advanced-guides/cancellation.md +117 -0
  13. package/docs/guide/advanced-guides/composition.md +68 -0
  14. package/docs/guide/advanced-guides/custom-executor.md +180 -0
  15. package/docs/guide/advanced-guides/error-handling.md +135 -0
  16. package/docs/guide/advanced-guides/logging.md +106 -0
  17. package/docs/guide/advanced-guides/middleware.md +106 -0
  18. package/docs/guide/advanced-guides/observability.md +175 -0
  19. package/docs/guide/best-practices/debugging.md +182 -0
  20. package/docs/guide/best-practices/state-management.md +120 -0
  21. package/docs/guide/best-practices/sub-workflow-data.md +95 -0
  22. package/docs/guide/best-practices/testing.md +187 -0
  23. package/docs/guide/builders.md +157 -0
  24. package/docs/guide/functional-api.md +133 -0
  25. package/docs/guide/index.md +178 -0
  26. package/docs/guide/recipes/creating-a-loop.md +113 -0
  27. package/docs/guide/recipes/data-processing-pipeline.md +123 -0
  28. package/docs/guide/recipes/fan-out-fan-in.md +112 -0
  29. package/docs/guide/recipes/index.md +15 -0
  30. package/docs/guide/recipes/resilient-api-call.md +110 -0
  31. package/docs/guide/tooling/graph-validation.md +160 -0
  32. package/docs/guide/tooling/mermaid.md +156 -0
  33. package/docs/index.md +56 -0
  34. package/eslint.config.js +16 -0
  35. package/package.json +40 -0
  36. package/pnpm-workspace.yaml +2 -0
  37. package/sandbox/1.basic/README.md +45 -0
  38. package/sandbox/1.basic/package.json +16 -0
  39. package/sandbox/1.basic/src/flow.ts +17 -0
  40. package/sandbox/1.basic/src/main.ts +22 -0
  41. package/sandbox/1.basic/src/nodes.ts +112 -0
  42. package/sandbox/1.basic/src/utils.ts +35 -0
  43. package/sandbox/1.basic/tsconfig.json +3 -0
  44. package/sandbox/2.research/README.md +46 -0
  45. package/sandbox/2.research/package.json +16 -0
  46. package/sandbox/2.research/src/flow.ts +14 -0
  47. package/sandbox/2.research/src/main.ts +31 -0
  48. package/sandbox/2.research/src/nodes.ts +108 -0
  49. package/sandbox/2.research/src/utils.ts +45 -0
  50. package/sandbox/2.research/src/visualize.ts +29 -0
  51. package/sandbox/2.research/tsconfig.json +3 -0
  52. package/sandbox/3.parallel/README.md +65 -0
  53. package/sandbox/3.parallel/package.json +16 -0
  54. package/sandbox/3.parallel/src/main.ts +45 -0
  55. package/sandbox/3.parallel/src/nodes.ts +43 -0
  56. package/sandbox/3.parallel/src/utils.ts +25 -0
  57. package/sandbox/3.parallel/tsconfig.json +3 -0
  58. package/sandbox/4.dag/README.md +179 -0
  59. package/sandbox/4.dag/data/1.blog-post/100.json +60 -0
  60. package/sandbox/4.dag/data/1.blog-post/README.md +25 -0
  61. package/sandbox/4.dag/data/2.job-application/200.json +103 -0
  62. package/sandbox/4.dag/data/2.job-application/201.json +31 -0
  63. package/sandbox/4.dag/data/2.job-application/202.json +31 -0
  64. package/sandbox/4.dag/data/2.job-application/README.md +58 -0
  65. package/sandbox/4.dag/data/3.customer-review/300.json +141 -0
  66. package/sandbox/4.dag/data/3.customer-review/301.json +31 -0
  67. package/sandbox/4.dag/data/3.customer-review/302.json +28 -0
  68. package/sandbox/4.dag/data/3.customer-review/README.md +71 -0
  69. package/sandbox/4.dag/data/4.content-moderation/400.json +161 -0
  70. package/sandbox/4.dag/data/4.content-moderation/401.json +47 -0
  71. package/sandbox/4.dag/data/4.content-moderation/402.json +46 -0
  72. package/sandbox/4.dag/data/4.content-moderation/403.json +31 -0
  73. package/sandbox/4.dag/data/4.content-moderation/README.md +83 -0
  74. package/sandbox/4.dag/package.json +19 -0
  75. package/sandbox/4.dag/src/main.ts +73 -0
  76. package/sandbox/4.dag/src/nodes.ts +134 -0
  77. package/sandbox/4.dag/src/registry.ts +87 -0
  78. package/sandbox/4.dag/src/types.ts +25 -0
  79. package/sandbox/4.dag/src/utils.ts +42 -0
  80. package/sandbox/4.dag/tsconfig.json +3 -0
  81. package/sandbox/5.distributed/.env.example +1 -0
  82. package/sandbox/5.distributed/README.md +88 -0
  83. package/sandbox/5.distributed/data/1.blog-post/100.json +59 -0
  84. package/sandbox/5.distributed/data/1.blog-post/README.md +25 -0
  85. package/sandbox/5.distributed/data/2.job-application/200.json +103 -0
  86. package/sandbox/5.distributed/data/2.job-application/201.json +30 -0
  87. package/sandbox/5.distributed/data/2.job-application/202.json +30 -0
  88. package/sandbox/5.distributed/data/2.job-application/README.md +58 -0
  89. package/sandbox/5.distributed/data/3.customer-review/300.json +141 -0
  90. package/sandbox/5.distributed/data/3.customer-review/301.json +31 -0
  91. package/sandbox/5.distributed/data/3.customer-review/302.json +57 -0
  92. package/sandbox/5.distributed/data/3.customer-review/README.md +71 -0
  93. package/sandbox/5.distributed/data/4.content-moderation/400.json +173 -0
  94. package/sandbox/5.distributed/data/4.content-moderation/401.json +47 -0
  95. package/sandbox/5.distributed/data/4.content-moderation/402.json +46 -0
  96. package/sandbox/5.distributed/data/4.content-moderation/403.json +31 -0
  97. package/sandbox/5.distributed/data/4.content-moderation/README.md +83 -0
  98. package/sandbox/5.distributed/package.json +20 -0
  99. package/sandbox/5.distributed/src/client.ts +124 -0
  100. package/sandbox/5.distributed/src/executor.ts +69 -0
  101. package/sandbox/5.distributed/src/nodes.ts +136 -0
  102. package/sandbox/5.distributed/src/registry.ts +101 -0
  103. package/sandbox/5.distributed/src/types.ts +45 -0
  104. package/sandbox/5.distributed/src/utils.ts +69 -0
  105. package/sandbox/5.distributed/src/worker.ts +217 -0
  106. package/sandbox/5.distributed/tsconfig.json +3 -0
  107. package/sandbox/6.rag/.env.example +1 -0
  108. package/sandbox/6.rag/README.md +60 -0
  109. package/sandbox/6.rag/data/README.md +31 -0
  110. package/sandbox/6.rag/data/rag.json +58 -0
  111. package/sandbox/6.rag/documents/sample-cascade.txt +11 -0
  112. package/sandbox/6.rag/package.json +18 -0
  113. package/sandbox/6.rag/src/main.ts +52 -0
  114. package/sandbox/6.rag/src/nodes/GenerateEmbeddingsNode.ts +54 -0
  115. package/sandbox/6.rag/src/nodes/LLMProcessNode.ts +48 -0
  116. package/sandbox/6.rag/src/nodes/LoadAndChunkNode.ts +40 -0
  117. package/sandbox/6.rag/src/nodes/StoreInVectorDBNode.ts +36 -0
  118. package/sandbox/6.rag/src/nodes/VectorSearchNode.ts +53 -0
  119. package/sandbox/6.rag/src/nodes/index.ts +28 -0
  120. package/sandbox/6.rag/src/registry.ts +23 -0
  121. package/sandbox/6.rag/src/types.ts +44 -0
  122. package/sandbox/6.rag/src/utils.ts +77 -0
  123. package/sandbox/6.rag/tsconfig.json +3 -0
  124. package/sandbox/tsconfig.json +13 -0
  125. package/src/builder/collection.test.ts +287 -0
  126. package/src/builder/collection.ts +269 -0
  127. package/src/builder/graph.test.ts +406 -0
  128. package/src/builder/graph.ts +336 -0
  129. package/src/builder/graph.types.ts +104 -0
  130. package/src/builder/index.ts +3 -0
  131. package/src/context.ts +111 -0
  132. package/src/errors.ts +34 -0
  133. package/src/executor.ts +29 -0
  134. package/src/executors/in-memory.test.ts +93 -0
  135. package/src/executors/in-memory.ts +140 -0
  136. package/src/functions.test.ts +191 -0
  137. package/src/functions.ts +117 -0
  138. package/src/index.ts +5 -0
  139. package/src/logger.ts +41 -0
  140. package/src/types.ts +75 -0
  141. package/src/utils/graph.test.ts +144 -0
  142. package/src/utils/graph.ts +182 -0
  143. package/src/utils/index.ts +3 -0
  144. package/src/utils/mermaid.test.ts +239 -0
  145. package/src/utils/mermaid.ts +133 -0
  146. package/src/utils/sleep.ts +20 -0
  147. package/src/workflow.test.ts +622 -0
  148. package/src/workflow.ts +561 -0
@@ -0,0 +1,157 @@
1
+ # Builders
2
+
3
+ Builders are helper classes provided by Flowcraft to abstract away the manual construction of common and complex workflow patterns. They handle the underlying `Node` and `Flow` wiring for you, so you can focus on your application's logic.
4
+
5
+ This guide provides an overview of the available builders and helps you choose the right one for your use case.
6
+
7
+ ## Choosing the Right Builder
8
+
9
+ - **For simple, linear sequences...**
10
+ ...where one step follows the next without branching, use **[`SequenceFlow`](#sequenceflow)**. It's the simplest way to create a basic pipeline. The functional `pipeline` helper offers an even more concise syntax for the same pattern.
11
+
12
+ - **For parallel execution of different tasks...**
13
+ ...where your workflow has distinct branches that can run concurrently (a "fan-out, fan-in" pattern), use **[`ParallelFlow`](#parallelflow)**. This is ideal for structural parallelism, like fetching data from two different APIs at the same time.
14
+
15
+ - **For processing a collection of data items...**
16
+ ...where you need to run the *same* operation on many different pieces of data, use a batch processor. Choose **[`ParallelBatchFlow`](#batch-processing-batchflow-and-parallelbatchflow)** for I/O-bound tasks (like making many API calls) or **[`BatchFlow`](#batch-processing-batchflow-and-parallelbatchflow)** for tasks that must run sequentially.
17
+
18
+ - **For dynamic, data-driven graphs...**
19
+ ...where your workflow logic is defined in a declarative format like JSON, use the **[`GraphBuilder`](#graphbuilder)**. This is the most powerful and flexible option, perfect for building dynamic AI agent runtimes or systems where workflows are configuration, not code.
20
+
21
+ ---
22
+
23
+ ## `SequenceFlow`
24
+
25
+ `SequenceFlow` creates a linear `Flow` from a list of nodes, automatically chaining them together in the order they are provided.
26
+
27
+ ### Example: Class-based Builder
28
+
29
+ Instead of wiring nodes manually with `.next()`:
30
+
31
+ ```typescript
32
+ const nodeA = new NodeA()
33
+ const nodeB = new NodeB()
34
+ const nodeC = new NodeC()
35
+
36
+ nodeA.next(nodeB)
37
+ nodeB.next(nodeC)
38
+
39
+ const manualFlow = new Flow(nodeA)
40
+ ```
41
+
42
+ You can use `SequenceFlow` for a more concise definition:
43
+
44
+ ```typescript
45
+ import { SequenceFlow } from 'flowcraft'
46
+
47
+ const sequence = new SequenceFlow(
48
+ new NodeA(),
49
+ new NodeB(),
50
+ new NodeC()
51
+ )
52
+
53
+ await sequence.run(context)
54
+ ```
55
+
56
+ For an even more functional style, the `pipeline` helper provides the same functionality with a more direct syntax. See the [Functional API Reference](./functional-api.md#pipeline) for more details.
57
+
58
+ ```typescript
59
+ import { pipeline } from 'flowcraft'
60
+
61
+ const dataPipeline = pipeline(
62
+ new NodeA(),
63
+ new NodeB(),
64
+ new NodeC()
65
+ )
66
+ ```
67
+
68
+ ---
69
+
70
+ ## `ParallelFlow`
71
+
72
+ `ParallelFlow` executes a fixed set of different nodes concurrently. This is the "fan-out, fan-in" pattern, used for **structural parallelism**. After all parallel branches complete, the flow can proceed to a single, subsequent node.
73
+
74
+ ### When to Use
75
+
76
+ Use `ParallelFlow` when your workflow logic itself has distinct branches that can run simultaneously. For example, building a user dashboard by fetching profile data and activity data from two different services at the same time.
77
+
78
+ ### Example
79
+
80
+ ```typescript
81
+ import { ParallelFlow } from 'flowcraft'
82
+
83
+ // Fetch data from two different sources in parallel.
84
+ const parallelStep = new ParallelFlow([
85
+ new FetchUserProfileNode(), // Task A
86
+ new FetchUserActivityNode(), // Task B
87
+ ])
88
+
89
+ // This node will only run after both fetch nodes have completed.
90
+ const aggregateNode = new AggregateDashboardDataNode()
91
+ parallelStep.next(aggregateNode)
92
+
93
+ const dashboardFlow = new Flow(parallelStep)
94
+ ```
95
+
96
+ ---
97
+
98
+ ## `BatchFlow` (Sequential)
99
+
100
+ `BatchFlow` processes items one by one, in order - executing the **same node** many times over a dynamic collection of data items. The next item is not processed until the previous one is completely finished.
101
+
102
+ **Use Cases**: Processing items in a strict order, or interacting with a rate-limited API where you must avoid sending multiple requests at once.
103
+
104
+ ## `ParallelBatchFlow` (Concurrent)
105
+
106
+ `ParallelBatchFlow` processes all items concurrently - executing the **same node** many times over a dynamic collection of data items. This provides a massive performance boost for I/O-bound tasks.
107
+
108
+ **Use Cases**: Translating a document into 10 languages, fetching thumbnails for a list of 100 video URLs, or processing multiple user-uploaded files.
109
+
110
+ ### Example: The Document Translator
111
+
112
+ This example uses `ParallelBatchFlow` to translate a document into several languages at once.
113
+
114
+ ```typescript
115
+ import { AbstractNode, Node, ParallelBatchFlow, TypedContext } from 'flowcraft'
116
+
117
+ // The single unit of work: translates text to one language.
118
+ class TranslateNode extends Node {
119
+ async exec({ params }) { /* ... API call to translate params.text to params.language ... */ }
120
+ }
121
+
122
+ // The builder orchestrates the batch process.
123
+ class TranslateFlow extends ParallelBatchFlow {
124
+ // 1. Implement the abstract property to define which node to run for each item.
125
+ protected nodeToRun: AbstractNode = new TranslateNode()
126
+
127
+ // 2. The `prep` method provides the list of items to process.
128
+ async prep({ ctx }) {
129
+ const languages = ctx.get(LANGUAGES) || []
130
+ const text = ctx.get(DOCUMENT_TEXT)
131
+
132
+ // Return an array of parameter objects.
133
+ // Each object will be merged into the TranslateNode's params for one parallel run.
134
+ return languages.map(language => ({ language, text }))
135
+ }
136
+ }
137
+ ```
138
+
139
+ ---
140
+
141
+ ## GraphBuilder
142
+
143
+ `GraphBuilder` is the most powerful and advanced builder. It allows you to construct a fully executable `Flow` from a declarative data structure (like a JSON object). This is the key to building dynamic, data-driven systems where the workflow logic is defined as configuration, not hard-coded.
144
+
145
+ ### When to Use
146
+
147
+ - When you want to define workflow logic in a database or a set of JSON/YAML files.
148
+ - When you need to support complex, non-linear workflows with multiple start points, fan-outs, and fan-ins.
149
+ - For building modular AI Agent runtimes where different "tools" or "skills" are defined as graphs.
150
+
151
+ ### How It Works
152
+
153
+ 1. **Define a Graph**: You create a `WorkflowGraph` object containing a list of `nodes` and `edges`.
154
+ 2. **Create a Node Registry**: You map the string `type` from your graph nodes to the actual `Node` classes in your code.
155
+ 3. **Build the Flow**: You instantiate `GraphBuilder` with the registry and call `.build(graph)`.
156
+
157
+ The `GraphBuilder` intelligently analyzes the graph's structure, automatically handling parallel start nodes, mid-flow fan-outs, and fan-ins. For a complete, in-depth example, see the **[Dynamic AI Agent example (`sandbox/4.dag/`)](https://github.com/gorango/flowcraft/tree/master/sandbox/4.dag/)**.
@@ -0,0 +1,133 @@
1
+ # Functional API
2
+
3
+ While Flowcraft's core is built on composable classes like `Node` and `Flow`, it also provides a suite of functional helpers for developers who prefer a more functional programming style. These helpers allow you to define nodes and simple pipelines without explicitly creating new classes.
4
+
5
+ All helpers are imported from the main `flowcraft` package.
6
+
7
+ ```typescript
8
+ import {
9
+ composeContext,
10
+ contextNode,
11
+ lens,
12
+ mapNode,
13
+ pipeline,
14
+ transformNode
15
+ } from 'flowcraft'
16
+ ```
17
+
18
+ ## Creating Nodes from Functions
19
+
20
+ These helpers are the primary way to create `Node` instances from simple functions.
21
+
22
+ ### `mapNode`
23
+
24
+ `mapNode` creates a `Node` from a pure function that transforms an input to an output. The node's input `params` object is passed as the single argument to your function.
25
+
26
+ #### When to Use
27
+
28
+ Use `mapNode` for simple, stateless transformations where the logic doesn't need access to the shared `Context`.
29
+
30
+ #### Example
31
+
32
+ ```typescript
33
+ import { Flow, mapNode } from 'flowcraft'
34
+
35
+ // A simple function that adds two numbers from the params
36
+ const add = (params: { a: number, b: number }) => params.a + params.b
37
+
38
+ // Create a reusable Node from the function
39
+ const addNode = mapNode(add)
40
+
41
+ // Now use it in a flow
42
+ const flow = new Flow(addNode.withParams({ a: 5, b: 10 }))
43
+ ```
44
+
45
+ ### `contextNode`
46
+
47
+ `contextNode` is similar to `mapNode`, but the function you provide also receives the `Context` as its first argument.
48
+
49
+ #### When to Use
50
+
51
+ Use `contextNode` when your node's logic depends on state stored in the `Context`.
52
+
53
+ #### Example
54
+
55
+ ```typescript
56
+ import { contextKey, contextNode, Flow, TypedContext } from 'flowcraft'
57
+
58
+ const LANGUAGE = contextKey<'en' | 'es'>('language')
59
+
60
+ // A function that uses the context to determine the greeting
61
+ function greeter(ctx, params: { name: string }) {
62
+ const lang = ctx.get(LANGUAGE) || 'en'
63
+ return lang === 'es' ? `Hola, ${params.name}` : `Hello, ${params.name}`
64
+ }
65
+
66
+ // Create a Node from the context-aware function
67
+ const greetingNode = contextNode(greeter)
68
+
69
+ const context = new TypedContext([[LANGUAGE, 'es']])
70
+ const flow = new Flow(greetingNode.withParams({ name: 'Mundo' }))
71
+ ```
72
+
73
+ ## Creating Flows
74
+
75
+ ### `pipeline`
76
+
77
+ `pipeline` is a functional alias for the `SequenceFlow` builder. It takes a sequence of `Node` instances and returns a `Flow` where they are all chained together in order.
78
+
79
+ #### When to Use
80
+
81
+ Use `pipeline` as a more readable, functional-style alternative to `new SequenceFlow(...)` for creating simple linear workflows.
82
+
83
+ #### Example
84
+
85
+ ```typescript
86
+ import { contextNode, mapNode, pipeline } from 'flowcraft'
87
+
88
+ const fetchNode = mapNode(() => ({ user: 'Alice' }))
89
+ const processNode = mapNode(data => `Processed ${data.user}`)
90
+ const saveNode = contextNode((ctx, result) => { /* save result */ })
91
+
92
+ const dataPipeline = pipeline(
93
+ fetchNode,
94
+ processNode,
95
+ saveNode
96
+ )
97
+
98
+ await dataPipeline.run(context)
99
+ ```
100
+
101
+ ## Declarative Context Management
102
+
103
+ These helpers provide a functional way to manage state in the `Context`.
104
+
105
+ ### `lens` and `transformNode`
106
+
107
+ A `lens` provides a way to "focus" on a single key in the `Context`, allowing you to create reusable functions that `get`, `set`, or `update` its value.
108
+
109
+ `transformNode` is a special `Node` that takes these functions and applies them to the context. It's the ideal way to set up initial state for a workflow.
110
+
111
+ #### When to Use
112
+
113
+ Use this combination for declaratively setting up or mutating state in the `Context` as a distinct step in your workflow.
114
+
115
+ #### Example
116
+
117
+ ```typescript
118
+ import { contextKey, lens, transformNode } from 'flowcraft'
119
+
120
+ const USER_ID = contextKey<string>('user_id')
121
+ const ATTEMPTS = contextKey<number>('attempts')
122
+
123
+ // Create lenses to focus on our specific context keys
124
+ const userIdLens = lens(USER_ID)
125
+ const attemptsLens = lens(ATTEMPTS)
126
+
127
+ // A node that sets an initial user and resets the attempt counter.
128
+ // The `set` and `update` methods from the lens return `ContextTransform` functions.
129
+ const setupContextNode = transformNode(
130
+ userIdLens.set('user-123'),
131
+ attemptsLens.update(current => (current || 0) + 1) // Safely increments
132
+ )
133
+ ```
@@ -0,0 +1,178 @@
1
+ # Introduction to Flowcraft
2
+
3
+ Welcome to Flowcraft! This guide will take you from the fundamental concepts to building your first workflow, giving you a comprehensive understanding of what Flowcraft is, why it's useful, and how it works.
4
+
5
+ ## What is Flowcraft?
6
+
7
+ Flowcraft is a lightweight, zero-dependency TypeScript framework for building complex, multi-step processes. It empowers you to model everything from simple sequential tasks to dynamic, graph-driven AI agents with a clear and composable API.
8
+
9
+ At its core, Flowcraft is guided by a few key principles:
10
+
11
+ 1. **Structure for Complexity**: It provides a clear way to model asynchronous processes. By breaking logic into discrete `Node`s with a defined lifecycle, you can turn tangled promise chains and `async/await` blocks into maintainable, testable graphs.
12
+ 2. **Start Simple, Scale Gracefully**: You can start with an in-memory workflow in a single file. As your needs grow, the architecture allows you to scale up to a robust, distributed system using message queues—**without changing your core business logic**.
13
+ 3. **Composability is Key**: A `Flow` is just a specialized `Node`. This simple but powerful concept means entire workflows can be treated as building blocks, allowing you to create highly modular and reusable systems.
14
+
15
+ The best way to see the power of this structure is to build something.
16
+
17
+ ## Quick Start: Your First Workflow
18
+
19
+ This tutorial will walk you through creating a simple, three-step pipeline that takes a name as input, constructs a greeting, and assembles a final message.
20
+
21
+ ### Prerequisites
22
+
23
+ - [Node.js](https://nodejs.org/) installed on your system.
24
+ - A package manager like `npm` or `pnpm`.
25
+ - A way to run TypeScript files like `tsx` or `bun`.
26
+
27
+ ### Step 1: Project Setup
28
+
29
+ In your project directory, install Flowcraft and the necessary TypeScript tools. We'll use `tsx` to run our TypeScript file directly.
30
+
31
+ ```bash
32
+ npm install flowcraft
33
+ ```
34
+
35
+ ### Step 2: Define the Workflow Logic
36
+
37
+ Create a new file named `main.ts`. This is where we'll define and run our workflow.
38
+
39
+ ```bash
40
+ touch main.ts
41
+ ```
42
+
43
+ Inside `main.ts`, we'll start by importing the core components we need from Flowcraft.
44
+
45
+ ```typescript
46
+ // main.ts
47
+ import { contextKey, Flow, Node, TypedContext } from 'flowcraft'
48
+
49
+ // Define type-safe keys for our shared data
50
+ const NAME = contextKey<string>('name')
51
+ const GREETING = contextKey<string>('greeting')
52
+ const FINAL_MESSAGE = contextKey<string>('final_message')
53
+
54
+ // 1. nameNode: Takes a name from input `params` and stores it in the Context.
55
+ const nameNode = new Node()
56
+ .exec(async ({ params }) => params.name)
57
+ .toContext(NAME)
58
+
59
+ // 2. greetingNode: Reads the name from the Context, creates a greeting, and stores it back.
60
+ const greetingNode = new Node()
61
+ .exec(async ({ ctx }) => `Hello, ${ctx.get(NAME)}!`)
62
+ .toContext(GREETING)
63
+
64
+ // 3. finalNode: Reads the greeting from the Context and assembles the final message.
65
+ const finalNode = new Node()
66
+ .exec(async ({ ctx }) => `${ctx.get(GREETING)} Welcome to Flowcraft!`)
67
+ .toContext(FINAL_MESSAGE)
68
+ ```
69
+
70
+ ### Step 3: Orchestrate and Run the `Flow`
71
+
72
+ Now that we have our nodes, we need to wire them together into a sequence and run them.
73
+
74
+ ```typescript
75
+ // main.ts (continued)
76
+
77
+ // Chain the nodes to define the execution order.
78
+ nameNode.next(greetingNode).next(finalNode)
79
+
80
+ // Create a Flow, telling it which node to start with.
81
+ const flow = new Flow(nameNode)
82
+
83
+ // Execute the flow.
84
+ async function main() {
85
+ const context = new TypedContext()
86
+
87
+ console.log('Starting workflow...')
88
+ await flow.withParams({ name: 'Developer' }).run(context)
89
+
90
+ const result = context.get(FINAL_MESSAGE)
91
+ console.log('Workflow complete!')
92
+ console.log(`Final Result: "${result}"`)
93
+ }
94
+
95
+ main()
96
+ ```
97
+
98
+ ### Step 4: Run It
99
+
100
+ Your `main.ts` file should now contain the complete workflow. Run it from your terminal:
101
+
102
+ ```bash
103
+ npx tsx main.ts
104
+ ```
105
+
106
+ You should see the following output:
107
+
108
+ ```
109
+ Starting workflow...
110
+ Workflow complete!
111
+ Final Result: "Hello, Developer! Welcome to Flowcraft!"
112
+ ```
113
+
114
+ Congratulations! You've just built and run a Flowcraft workflow. Now, let's break down the concepts you just used.
115
+
116
+ ---
117
+
118
+ ## Core Concepts
119
+
120
+ Flowcraft is built around a few simple, powerful concepts.
121
+
122
+ ### 1. Node
123
+
124
+ The `Node` is the most fundamental building block, representing a single unit of work. In our Quick Start, `nameNode`, `greetingNode`, and `finalNode` were all created from the base `Node` class.
125
+
126
+ Every `Node` has a well-defined, three-phase lifecycle:
127
+
128
+ 1. **`prep(args)`**: **Prepare Data**. Gathers data needed for execution, usually by reading from the `Context`.
129
+ 2. **`exec(args)`**: **Execute Core Logic**. Performs the main work. This phase is designed to be isolated from the `Context` to make it pure and testable.
130
+ 3. **`post(args)`**: **Process Results**. Updates the `Context` with the results and returns an **action** to determine the next step.
131
+
132
+ For many common tasks, like in our example, you can use the fluent API (`.map`, `.toContext`, `.filter`) to create processing pipelines without writing a custom class.
133
+
134
+ ### 2. Flow
135
+
136
+ A `Flow` is a special `Node` that acts as a **container for a graph of other nodes**. It holds the starting point of a workflow and orchestrates the execution.
137
+
138
+ When you call `flow.run()`, an `Executor` starts with the flow's `startNode`, executes it, looks at the **action** it returns, and finds the next node connected via `.next()`. This repeats until the flow ends.
139
+
140
+ ### 3. Context
141
+
142
+ The `Context` is the shared memory of a running workflow. It is passed to every node, allowing them to share state. In our example, we used it to pass the `name` from `nameNode` to `greetingNode`, and the `greeting` on to `finalNode`.
143
+
144
+ - **Type-Safe by Default**: We used `contextKey<string>('name')` to create a type-safe key. This prevents you from accidentally writing a number where a string is expected.
145
+ - **Mutable**: Nodes read from the context (usually in `prep` or `exec`) and write back to it (usually in `post` or via `.toContext`).
146
+
147
+ ### 4. Actions & Branching
148
+
149
+ An **action** is a string returned by a node's `post()` method that the `Executor` uses to decide which path to take next.
150
+
151
+ - **`DEFAULT_ACTION`**: Used for simple linear sequences. `nodeA.next(nodeB)` is shorthand for branching on the default action.
152
+ - **Custom Actions**: Returning a custom string like `'success'` or `'error'` from `post()` enables conditional branching, allowing you to direct the workflow down different paths based on a node's outcome.
153
+
154
+ ---
155
+
156
+ ## When to Use Flowcraft
157
+
158
+ Flowcraft is an excellent choice for:
159
+
160
+ - **AI Agent Orchestration**: Modeling the "reasoning loop" of an AI agent is a natural fit for a graph. Conditional branching, tool use, and parallel thought processes are easily implemented. The **[Advanced RAG Agent](https://github.com/gorango/flowcraft/tree/master/sandbox/6.rag/)** is a complete, end-to-end example of this pattern.
161
+ - **Data Processing & ETL Pipelines**: Fetching data, running transformations, and saving it to a destination. The [Parallel Batch Translation](https://github.com/gorango/flowcraft/tree/master/sandbox/3.parallel/) is a great example of this task.
162
+ - **Complex Business Logic**: Any multi-step process with conditional paths, retries, and fallbacks (e.g., user onboarding, e-commerce order fulfillment). Look at some of the examples in the [DAG](https://github.com/gorango/flowcraft/tree/master/sandbox/4.dag/) sandbox.
163
+ - **Multi-Step Background Jobs**: Running tasks in the background of a web application, like generating a report or processing a file. The [Distributed](https://github.com/gorango/flowcraft/tree/master/sandbox/5.distributed/) examples showcase running all of the same DAG workflows but in the background.
164
+
165
+ ## How Flowcraft Compares
166
+
167
+ - **vs. Plain `async/await`**: Use `async/await` for simple, linear sequences. Use Flowcraft when your process starts to look like a state machine or a graph, with retries, fallbacks, or complex conditional logic.
168
+ - **vs. LangChain / LlamaIndex**: Use those frameworks for rapid prototyping with their vast ecosystem of pre-built integrations. Use Flowcraft when you want an unopinionated **runtime** for your custom AI logic, giving you full control over your prompts and business logic.
169
+ - **vs. Temporal / Airflow**: Use these heavy-duty platforms when you need **durability** (guaranteed execution that survives server crashes) out of the box. Use Flowcraft when you want to start with a lightweight, in-process library and have the *option* to build a distributed system later.
170
+
171
+ ## Next Steps
172
+
173
+ Now that you have a solid foundation, you can explore more advanced topics:
174
+
175
+ - **[Builders](./builders.md)**: Learn about creating sequential, parallel, and declarative graph-based flows.
176
+ - **[Functional API](./functional-api.md)**: Discover a more functional style of defining nodes and pipelines.
177
+ - **[Recipes](./recipes/)**: Find practical, copy-paste-friendly solutions for common workflow patterns.
178
+ - **[Advanced Guides](./advanced-guides/composition.md)**: Dive into composition, middleware, custom executors, and more.
@@ -0,0 +1,113 @@
1
+ # Recipes: Creating a Loop
2
+
3
+ A common requirement in workflows is to perform an action repeatedly until a certain condition is met. While Flowcraft does not have a dedicated "loop" node, you can easily create loops by wiring a graph of nodes into a cycle.
4
+
5
+ The key is to have a "decider" node that, based on some condition, either continues the loop or exits it.
6
+
7
+ ## The Pattern
8
+
9
+ 1. **Action Node**: A node that performs the main work of the loop's body.
10
+ 2. **Decider Node**: A node that checks a condition after the action is performed.
11
+ 3. **Cyclical Connection**: The decider node connects back to the action node to continue the loop.
12
+ 4. **Exit Connection**: The decider node connects to a different node to break the loop.
13
+
14
+ ```mermaid
15
+ graph TD
16
+ A[Start] --> B(Loop Action)
17
+ B --> C{Check Condition}
18
+ C -- "Continue Loop" --> B
19
+ C -- "Exit Loop" --> D[End]
20
+ ```
21
+
22
+ ## Example: A Counter Loop
23
+
24
+ Let's build a simple workflow that increments a counter from 0 to 3.
25
+
26
+ ### 1. Define Context and Nodes
27
+
28
+ We need a key for our counter and three nodes:
29
+
30
+ - `InitializeCounterNode`: Sets the counter to 0.
31
+ - `IncrementCounterNode`: Adds 1 to the counter. This is our "Loop Action".
32
+ - `CheckCounterNode`: Checks if the counter has reached the limit. This is our "Decider".
33
+ - `EndNode`: A final node to execute after the loop is finished.
34
+
35
+ ```typescript
36
+ import { contextKey, Flow, Node, TypedContext } from 'flowcraft'
37
+
38
+ // The value we will be incrementing
39
+ const COUNTER = contextKey<number>('counter')
40
+ const MAX_LOOPS = 3
41
+
42
+ const initializeNode = new Node().exec(async ({ ctx }) => ctx.set(COUNTER, 0))
43
+ const endNode = new Node().exec(() => console.log('Loop finished.'))
44
+
45
+ // The main work of the loop
46
+ class IncrementCounterNode extends Node {
47
+ async exec({ ctx }) {
48
+ const current = ctx.get(COUNTER)!
49
+ console.log(`Loop body: Incrementing counter from ${current} to ${current + 1}`)
50
+ ctx.set(COUNTER, current + 1)
51
+ }
52
+ }
53
+
54
+ // The decider node
55
+ class CheckCounterNode extends Node<void, void, 'continue' | 'exit'> {
56
+ async post({ ctx }) {
57
+ const current = ctx.get(COUNTER)!
58
+ if (current < MAX_LOOPS) {
59
+ console.log(`Condition check: ${current} < ${MAX_LOOPS}. Continuing loop.`)
60
+ return 'continue' // The action to continue the loop
61
+ }
62
+ else {
63
+ console.log(`Condition check: ${current} >= ${MAX_LOOPS}. Exiting loop.`)
64
+ return 'exit' // The action to break the loop
65
+ }
66
+ }
67
+ }
68
+ ```
69
+
70
+ ### 2. Wire the Loop
71
+
72
+ Now, we connect the nodes to form the cycle.
73
+
74
+ ```typescript
75
+ // Create instances
76
+ const init = initializeNode
77
+ const increment = new IncrementCounterNode()
78
+ const check = new CheckCounterNode()
79
+ const end = endNode
80
+
81
+ // Define the flow path
82
+ init.next(increment)
83
+
84
+ // The 'increment' node always goes to the 'check' node
85
+ increment.next(check)
86
+
87
+ // The 'check' node is the key to the loop
88
+ check.next(increment, 'continue') // If action is 'continue', go back to increment
89
+ check.next(end, 'exit') // If action is 'exit', go to the end
90
+
91
+ // Create the flow starting with initialization
92
+ const loopFlow = new Flow(init)
93
+ ```
94
+
95
+ ### 3. Run the Flow
96
+
97
+ ```typescript
98
+ await loopFlow.run(new TypedContext())
99
+ ```
100
+
101
+ The output will be:
102
+
103
+ ```
104
+ Loop body: Incrementing counter from 0 to 1
105
+ Condition check: 1 < 3. Continuing loop.
106
+ Loop body: Incrementing counter from 1 to 2
107
+ Condition check: 2 < 3. Continuing loop.
108
+ Loop body: Incrementing counter from 2 to 3
109
+ Condition check: 3 >= 3. Exiting loop.
110
+ Loop finished.
111
+ ```
112
+
113
+ This simple, powerful pattern is the foundation for building any kind of loop, from simple counters to complex agentic loops that decide whether to search for more information or answer a question based on the current context. The **[Research Agent (`sandbox/2.research/`)](https://github.com/gorango/flowcraft/tree/master/sandbox/2.research/)** example uses this exact pattern for its core logic.