blokctl 0.6.21 → 1.0.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.
Files changed (81) hide show
  1. package/dist/__tests__/modular-observability.capstone.e2e.test.js +72 -0
  2. package/dist/commands/create/node.js +46 -66
  3. package/dist/commands/create/project.js +60 -9
  4. package/dist/commands/create/utils/Examples.d.ts +11 -23
  5. package/dist/commands/create/utils/Examples.js +206 -421
  6. package/dist/commands/dev/index.js +40 -1
  7. package/dist/commands/generate/NodeGenerator.d.ts +0 -2
  8. package/dist/commands/generate/NodeGenerator.js +0 -20
  9. package/dist/commands/generate/RuntimeGenerator.d.ts +0 -2
  10. package/dist/commands/generate/RuntimeGenerator.js +0 -19
  11. package/dist/commands/generate/RuntimeGenerator.test.js +0 -29
  12. package/dist/commands/generate/TriggerGenerator.d.ts +0 -2
  13. package/dist/commands/generate/TriggerGenerator.js +0 -19
  14. package/dist/commands/generate/WorkflowGenerator.d.ts +0 -2
  15. package/dist/commands/generate/WorkflowGenerator.js +4 -23
  16. package/dist/commands/generate/WorkflowGenerator.test.js +2 -2
  17. package/dist/commands/generate/e2e/NodeGenerator.e2e.test.js +0 -12
  18. package/dist/commands/generate/e2e/RuntimeGenerator.e2e.test.js +0 -12
  19. package/dist/commands/generate/e2e/TriggerGenerator.e2e.test.js +0 -14
  20. package/dist/commands/generate/prompts/create-fn-node.system.js +10 -17
  21. package/dist/commands/generate/prompts/create-workflow.system.js +114 -375
  22. package/dist/commands/generate/validators/WorkflowValidator.js +80 -5
  23. package/dist/commands/generate/validators/WorkflowValidator.test.js +66 -0
  24. package/dist/commands/monitor/monitor-component.js +5 -5
  25. package/dist/commands/observability/add.d.ts +2 -0
  26. package/dist/commands/observability/add.js +113 -0
  27. package/dist/commands/observability/alerting-module.test.js +43 -0
  28. package/dist/commands/observability/apply.d.ts +10 -0
  29. package/dist/commands/observability/apply.js +11 -0
  30. package/dist/commands/observability/descriptor.d.ts +37 -0
  31. package/dist/commands/observability/descriptor.js +203 -0
  32. package/dist/commands/observability/descriptor.test.d.ts +1 -0
  33. package/dist/commands/observability/descriptor.test.js +40 -0
  34. package/dist/commands/observability/index.d.ts +1 -0
  35. package/dist/commands/observability/index.js +53 -0
  36. package/dist/commands/observability/list.d.ts +2 -0
  37. package/dist/commands/observability/list.js +45 -0
  38. package/dist/commands/observability/logging-module.test.d.ts +1 -0
  39. package/dist/commands/observability/logging-module.test.js +43 -0
  40. package/dist/commands/observability/obs-stack-module.test.d.ts +1 -0
  41. package/dist/commands/observability/obs-stack-module.test.js +33 -0
  42. package/dist/commands/observability/remove.d.ts +2 -0
  43. package/dist/commands/observability/remove.js +62 -0
  44. package/dist/commands/observability/shared.d.ts +6 -0
  45. package/dist/commands/observability/shared.js +23 -0
  46. package/dist/commands/observability/status.d.ts +2 -0
  47. package/dist/commands/observability/status.js +36 -0
  48. package/dist/commands/observability/tracing-module.test.d.ts +1 -0
  49. package/dist/commands/observability/tracing-module.test.js +42 -0
  50. package/dist/commands/profile/index.js +7 -10
  51. package/dist/commands/watch/format.d.ts +23 -0
  52. package/dist/commands/watch/format.js +60 -0
  53. package/dist/commands/watch/index.d.ts +1 -0
  54. package/dist/commands/watch/index.js +53 -0
  55. package/dist/commands/watch/sse.d.ts +16 -0
  56. package/dist/commands/watch/sse.js +82 -0
  57. package/dist/index.d.ts +2 -0
  58. package/dist/index.js +4 -0
  59. package/dist/services/obs-setup.d.ts +5 -0
  60. package/dist/services/obs-setup.js +68 -0
  61. package/dist/services/obs-setup.test.d.ts +1 -0
  62. package/dist/services/obs-setup.test.js +71 -0
  63. package/dist/services/obs-tiers.d.ts +9 -0
  64. package/dist/services/obs-tiers.js +16 -0
  65. package/dist/services/observability-mutations.d.ts +4 -0
  66. package/dist/services/observability-mutations.js +46 -0
  67. package/dist/services/observability-mutations.test.d.ts +1 -0
  68. package/dist/services/observability-mutations.test.js +57 -0
  69. package/dist/services/runtime-setup.d.ts +12 -1
  70. package/dist/services/runtime-setup.js +274 -14
  71. package/dist/studio-dist/assets/{index-BD8_9YPN.js → index-CnFqCRQe.js} +17 -17
  72. package/dist/studio-dist/index.html +1 -1
  73. package/package.json +3 -3
  74. package/dist/commands/generate/GenerationAnalytics.d.ts +0 -61
  75. package/dist/commands/generate/GenerationAnalytics.js +0 -163
  76. package/dist/commands/generate/GenerationAnalytics.test.js +0 -407
  77. package/dist/commands/generate/PromptVersioning.d.ts +0 -25
  78. package/dist/commands/generate/PromptVersioning.js +0 -71
  79. package/dist/commands/generate/PromptVersioning.test.js +0 -120
  80. /package/dist/{commands/generate/GenerationAnalytics.test.d.ts → __tests__/modular-observability.capstone.e2e.test.d.ts} +0 -0
  81. /package/dist/commands/{generate/PromptVersioning.test.d.ts → observability/alerting-module.test.d.ts} +0 -0
@@ -1,443 +1,183 @@
1
1
  const createWorkflowSystemPrompt = {
2
- prompt: `You are a senior backend engineer specializing in the Blok (blok) workflow framework. Your task is to generate a fully working **Workflow JSON configuration file** that implements the described logic.
2
+ prompt: `You are a senior backend engineer specializing in the Blok (blok) workflow framework. Your task is to generate a fully working **v2 Workflow JSON configuration file** that implements the described logic.
3
3
 
4
4
  What to return:
5
5
 
6
6
  * Return only a complete JSON object representing a workflow configuration, ready to be saved directly into \`workflows/json/<workflow-name>.json\`.
7
7
  * The JSON object MUST include:
8
8
 
9
- 1. \`name\`: A descriptive name for the workflow
9
+ 1. \`name\`: A descriptive name for the workflow (3+ characters)
10
10
  2. \`description\`: A short human-readable description of what the workflow does
11
11
  3. \`version\`: Semantic version string (e.g., "1.0.0")
12
12
  4. \`trigger\`: An object with exactly ONE trigger type and its configuration
13
- 5. \`steps\`: An ordered array of entry step objects (the workflow execution plan)
14
- 6. \`nodes\`: A map of node configurations keyed by step names
13
+ 5. \`steps\`: An ordered array of step objects each step carries its own \`inputs\` INLINE
15
14
 
16
- ## Trigger Types
15
+ **There is NO \`nodes\` map in v2.** Inputs live directly on each step. (Old v1 workflows used a separate \`nodes{}\` map keyed by step name — do NOT generate that shape.)
17
16
 
18
- The workflow can be triggered by one of these types:
17
+ ## Step Shape (v2)
19
18
 
20
- ### HTTP Trigger
21
19
  \`\`\`json
22
- "trigger": {
23
- "http": {
24
- "method": "GET",
25
- "path": "/api/resource",
26
- "accept": "application/json"
27
- }
20
+ {
21
+ "id": "fetch-user",
22
+ "use": "@blokjs/api-call",
23
+ "inputs": { "url": "https://api.example.com/users/1", "method": "GET" }
28
24
  }
29
25
  \`\`\`
30
- - \`method\`: "GET", "POST", "PUT", "DELETE", "PATCH", or "*" (any method)
31
- - \`path\`: Express-style route with optional params (e.g., "/:id", "/:function?/:id?")
32
- - \`accept\`: Content type accepted
33
26
 
34
- ### Queue Trigger
35
- \`\`\`json
36
- "trigger": {
37
- "queue": {
38
- "provider": "kafka",
39
- "topic": "user-events",
40
- "consumerGroup": "my-consumer-group",
41
- "ack": true,
42
- "batchSize": 1,
43
- "concurrency": 1
44
- }
45
- }
46
- \`\`\`
47
- - \`provider\`: "kafka", "rabbitmq", "sqs", or "redis"
48
- - \`topic\`: Queue/topic name to consume from
49
- - \`consumerGroup\`: Consumer group ID (for Kafka)
50
- - \`ack\`: Whether to acknowledge after processing (default: true)
27
+ - \`id\` (required): unique identifier for the step. Every step's output is auto-persisted to \`ctx.state[<id>]\` on success — reference it later as \`"$.state.<id>"\`.
28
+ - \`use\` (required): the node to run — a package name (\`"@blokjs/api-call"\`) or a local node name (\`"fetch-user"\`).
29
+ - \`inputs\` (optional): the node's input object (see Input Value Patterns).
30
+ - \`type\` (optional): inferred from \`use\` when omitted. Use \`"runtime.<lang>"\` (e.g. \`"runtime.go"\`, \`"runtime.python3"\`) for cross-runtime nodes.
31
+
32
+ ### Persistence knobs (optional, per-step)
33
+
34
+ - \`"as": "name"\` — store the output at \`ctx.state[name]\` instead of \`ctx.state[id]\`.
35
+ - \`"spread": true\` — shallow-merge the node's \`result.data\` keys into \`ctx.state\` (multi-output nodes). Mutually exclusive with \`as\`.
36
+ - \`"ephemeral": true\` — skip persistence (only the immediately next step can read it via \`$.prev\`). Use for logging / response-only steps.
37
+
38
+ ## Conditional Routing (branch)
39
+
40
+ A conditional is a SINGLE step with a \`branch\` object NOT a \`conditions\` array, NOT an \`@blokjs/if-else\` node.
51
41
 
52
- ### Pub/Sub Trigger
53
42
  \`\`\`json
54
- "trigger": {
55
- "pubsub": {
56
- "provider": "gcp",
57
- "topic": "user-notifications",
58
- "subscription": "notification-worker",
59
- "ack": true,
60
- "maxMessages": 10
43
+ {
44
+ "id": "route",
45
+ "branch": {
46
+ "when": "ctx.req.method === 'POST'",
47
+ "then": [{ "id": "create", "use": "@blokjs/api-call", "inputs": { "url": "..." } }],
48
+ "else": [{ "id": "read", "use": "@blokjs/api-call", "inputs": { "url": "..." } }]
61
49
  }
62
50
  }
63
51
  \`\`\`
64
- - \`provider\`: "gcp", "aws", or "azure"
65
- - \`topic\`: Topic name
66
- - \`subscription\`: Subscription name
67
52
 
68
- ### Cron Trigger
53
+ - \`when\` is a RAW JavaScript expression over \`ctx.*\` — e.g. \`"ctx.state.user.active === true"\`, \`"ctx.req.body.amount > 100"\`. **Never** prefix it with \`js/\` or \`$.\` (those throw at runtime).
54
+ - \`then\` (required) and \`else\` (optional) are arrays of steps, same shape as top-level steps.
55
+ - Step ids are FLAT across the whole workflow, including branch arms — every id must be unique.
56
+
57
+ ## Input Value Patterns
58
+
59
+ 1. **Static values**: \`"message": "Hello World"\`, \`"retries": 3\`, \`"headers": { "Accept": "application/json" }\`
60
+ 2. **Reference another step's output**: \`"$.state.<step-id>"\` — e.g. \`"body": "$.state.fetch-user"\`. Access a field with dot paths: \`"$.state.fetch-user.user.id"\`.
61
+ 3. **Request data**: \`"$.req.body"\`, \`"$.req.params.id"\`, \`"$.req.query.search"\`, \`"$.req.headers"\`, \`"$.req.method"\`.
62
+ 4. **Previous step (adjacent only)**: \`"$.prev"\` — the immediately previous step's output. For non-adjacent reads always use \`"$.state.<id>"\`.
63
+ 5. **Inline JavaScript** (when you need logic): a \`"js/..."\` string — e.g. \`"url": "js/\\\`https://api/users/\\\${ctx.req.params.id}\\\`"\`. \`$.\` strings compile to \`js/ctx.\` automatically; \`js/\` lets you write the expression by hand.
64
+
65
+ (\`$.request\` = \`$.req\`, \`$.response\` = \`$.prev\`, \`$.vars\` = \`$.state\` are legacy aliases — prefer the canonical \`$.req\` / \`$.prev\` / \`$.state\`.)
66
+
67
+ ## Controlling the HTTP response
68
+
69
+ End an HTTP workflow with a \`@blokjs/respond\` step for custom status / headers / cookies / redirect / binary. Mark it \`"ephemeral": true\`.
70
+
69
71
  \`\`\`json
70
- "trigger": {
71
- "cron": {
72
- "schedule": "0 * * * *",
73
- "timezone": "America/New_York",
74
- "overlap": false
75
- }
76
- }
72
+ { "id": "send", "use": "@blokjs/respond", "inputs": { "status": 201, "body": "$.state.create" }, "ephemeral": true }
77
73
  \`\`\`
78
- - \`schedule\`: Standard cron expression
79
- - \`timezone\`: IANA timezone name (default: "UTC")
80
- - \`overlap\`: Allow overlapping executions (default: false)
81
74
 
82
- ### Webhook Trigger
75
+ ## Trigger Types (exactly one)
76
+
77
+ ### HTTP
83
78
  \`\`\`json
84
- "trigger": {
85
- "webhook": {
86
- "source": "github",
87
- "events": ["push", "pull_request.*"],
88
- "secret": "\${process.env.GITHUB_WEBHOOK_SECRET}",
89
- "path": "/webhooks/github"
90
- }
91
- }
79
+ "trigger": { "http": { "method": "GET", "path": "/api/resource", "accept": "application/json" } }
92
80
  \`\`\`
93
- - \`source\`: "github", "stripe", "shopify", or "custom"
94
- - \`events\`: Array of event types to listen for (supports wildcards)
95
- - \`secret\`: Webhook secret for signature verification
81
+ - \`method\`: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "ANY" (use \`"ANY"\` for any method — NOT \`"*"\`).
82
+ - \`path\`: optional Express-style route (e.g. "/users/:id"). Omit to derive the URL from the file path.
96
83
 
97
- ### WebSocket Trigger
84
+ ### Worker (background jobs / queues)
98
85
  \`\`\`json
99
- "trigger": {
100
- "websocket": {
101
- "events": ["message", "join", "leave"],
102
- "path": "/ws",
103
- "maxConnections": 10000,
104
- "heartbeatInterval": 30000
105
- }
106
- }
86
+ "trigger": { "worker": { "queue": "background-jobs", "provider": "nats", "concurrency": 5, "retries": 3 } }
107
87
  \`\`\`
88
+ - This is the trigger for ANY queued / async / background work. **Never use a \`queue\` trigger — it is dead and throws at construction.**
89
+ - Job payload arrives as \`$.req.body\`; metadata as \`$.req.params.{queue,jobId,attempt}\`.
108
90
 
109
- ### SSE Trigger
91
+ ### Cron (scheduled)
110
92
  \`\`\`json
111
- "trigger": {
112
- "sse": {
113
- "events": ["update", "notification"],
114
- "channels": ["feed", "alerts"],
115
- "path": "/events",
116
- "heartbeatInterval": 30000
117
- }
118
- }
93
+ "trigger": { "cron": { "schedule": "0 8 * * *", "timezone": "America/New_York", "overlap": false } }
119
94
  \`\`\`
120
95
 
121
- ## Steps Structure
122
-
123
- Steps are an ordered array of step objects:
124
-
96
+ ### Webhook (signed provider callbacks)
125
97
  \`\`\`json
126
- "steps": [
127
- {
128
- "name": "step-key-name",
129
- "node": "@blokjs/api-call",
130
- "type": "module"
131
- }
132
- ]
98
+ "trigger": { "webhook": { "source": "stripe", "events": ["payment_intent.succeeded"], "secret": "\${process.env.STRIPE_WEBHOOK_SECRET}", "path": "/webhooks/stripe" } }
133
99
  \`\`\`
100
+ - \`source\`: "github" | "stripe" | "shopify" | "custom".
134
101
 
135
- - \`name\`: Unique identifier for this step (used as key in \`nodes\` map)
136
- - \`node\`: Node package/module name (e.g., "@blokjs/api-call" for module types, or custom node names for local types)
137
- - \`type\`: "module" (from node_modules), "local" (from src/nodes/), or "runtime.python3" (Python runtime)
138
-
139
- ## Nodes Configuration
140
-
141
- Each step MUST have a corresponding entry in the \`nodes\` map.
142
-
143
- ### Simple Node (with inputs)
102
+ ### Pub/Sub (cloud topics)
144
103
  \`\`\`json
145
- "nodes": {
146
- "step-name": {
147
- "inputs": {
148
- "url": "https://api.example.com/data",
149
- "method": "GET",
150
- "headers": { "Authorization": "Bearer \${ctx.env.API_KEY}" }
151
- }
152
- }
153
- }
104
+ "trigger": { "pubsub": { "provider": "gcp", "topic": "user-notifications", "subscription": "notification-worker" } }
154
105
  \`\`\`
155
106
 
156
- ### Conditional Node (if-else routing)
107
+ ### SSE (one-way live stream) / WebSocket (two-way realtime)
157
108
  \`\`\`json
158
- "nodes": {
159
- "filter-request": {
160
- "conditions": [
161
- {
162
- "type": "if",
163
- "condition": "ctx.request.method.toLowerCase() === \\"get\\" && ctx.request.params.function === undefined",
164
- "steps": [
165
- { "name": "get-data", "node": "fetch-data", "type": "module" }
166
- ]
167
- },
168
- {
169
- "type": "if",
170
- "condition": "ctx.request.method.toLowerCase() === \\"post\\"",
171
- "steps": [
172
- { "name": "create-data", "node": "save-data", "type": "module" }
173
- ]
174
- },
175
- {
176
- "type": "else",
177
- "steps": [
178
- { "name": "not-allowed", "node": "error", "type": "module" }
179
- ]
180
- }
181
- ]
182
- }
183
- }
109
+ "trigger": { "sse": { "events": ["update"], "channels": ["feed"], "path": "/events" } }
110
+ "trigger": { "websocket": { "events": ["message"], "path": "/ws" } }
184
111
  \`\`\`
185
112
 
186
- ## Input Value Patterns
187
-
188
- Node inputs support these patterns:
189
-
190
- 1. **Static values**: Direct strings, numbers, objects, arrays
191
- \`\`\`json
192
- "message": "Hello World"
193
- \`\`\`
194
-
195
- 2. **Context interpolation**: Use \${ctx.*} to read from the workflow context
196
- \`\`\`json
197
- "userId": "\${ctx.request.params.id}"
198
- "query": "\${ctx.request.query.search}"
199
- "body": "\${ctx.request.body}"
200
- \`\`\`
201
-
202
- 3. **JavaScript expressions**: Prefix with "js/" for dynamic evaluation
203
- \`\`\`json
204
- "path": "js/process.env.DATA_PATH + '/files'"
205
- "data": "js/ctx.response.data"
206
- "value": "js/JSON.stringify(ctx.request.body)"
207
- \`\`\`
208
-
209
- 4. **Previous node output**: Use ctx.vars to access outputs from previous steps
210
- \`\`\`json
211
- "input": "js/ctx.vars['previous-step-name']"
212
- \`\`\`
213
-
214
- ## Context Properties Available in Conditions and Inputs
215
-
216
- - \`ctx.request.method\`: HTTP method (GET, POST, etc.)
217
- - \`ctx.request.params\`: URL path parameters (e.g., :id, :function)
218
- - \`ctx.request.query\`: URL query parameters
219
- - \`ctx.request.body\`: Request body
220
- - \`ctx.request.headers\`: Request headers
221
- - \`ctx.response.data\`: Current response data (set by previous nodes)
222
- - \`ctx.vars['node-name']\`: Output from a specific previous node
223
- - \`ctx.env.VARIABLE_NAME\`: Environment variables (via process.env)
224
- - \`ctx.id\`: Unique request ID
225
- - \`ctx.workflow_name\`: Workflow name
226
-
227
113
  ## Available Built-in Nodes
228
114
 
229
- - \`@blokjs/api-call\`: Makes HTTP API calls (inputs: url, method, headers, body, responseType)
230
- - \`@blokjs/if-else\`: Conditional routing (uses conditions array instead of inputs)
231
- - \`@blokjs/react\`: Server-side React rendering (inputs: template, props)
232
- - \`error\`: Returns error response (inputs: message, code)
115
+ - \`@blokjs/api-call\`: HTTP requests (inputs: url, method, headers, body, responseType)
116
+ - \`@blokjs/respond\`: control the HTTP response (inputs: body, status, headers, cookies, contentType)
117
+ - \`@blokjs/react\`: server-side React rendering (inputs: template, props)
118
+
119
+ (For domain logic, reference a local node by name — e.g. \`"use": "validate-order"\` — and assume it exists or will be created.)
233
120
 
234
121
  ## Constraints
235
122
 
236
- * The JSON MUST be valid and well-formed
237
- * Every step name in \`steps\` MUST have a matching key in \`nodes\`
238
- * Every step referenced in conditional branches MUST also have a matching key in \`nodes\`
239
- * Condition expressions MUST be valid JavaScript using only ctx.* properties
123
+ * The JSON MUST be valid and well-formed (2-space indentation, no trailing commas, no comments)
124
+ * Every step MUST have a unique \`id\` and a \`use\` (or a \`branch\`)
125
+ * Do NOT emit a \`nodes\` object, a \`conditions\` array, \`set_var\`, \`ctx.vars[...] =\`, or \`js/ctx.response\`
126
+ * Branch \`when\` MUST be a raw \`ctx.*\` expression (never \`js/\` or \`$.\`)
127
+ * Use \`"ANY"\` for the wildcard HTTP method (never \`"*"\`)
128
+ * Reference earlier outputs with \`"$.state.<id>"\` and request data with \`"$.req.*"\`
240
129
  * The workflow MUST have exactly ONE trigger type
241
- * Always include a fallback "else" branch in conditional routing for error handling
242
- * Use descriptive step names in kebab-case (e.g., "fetch-user", "validate-input")
243
- * Use descriptive workflow names
244
- * Reference environment variables with ctx.env.VARIABLE_NAME (not process.env directly in inputs)
130
+ * Use descriptive kebab-case step ids (e.g. "fetch-user", "validate-input")
245
131
 
246
- ## Real-World Examples
132
+ ## Examples
247
133
 
248
- ### Example 1: Simple API Proxy
134
+ ### Example 1: Simple API proxy
249
135
  \`\`\`json
250
136
  {
251
137
  "name": "Country Data API",
252
- "description": "Fetches country data from external API",
138
+ "description": "Fetches country data from an external API",
253
139
  "version": "1.0.0",
254
- "trigger": {
255
- "http": {
256
- "method": "GET",
257
- "path": "/",
258
- "accept": "application/json"
259
- }
260
- },
140
+ "trigger": { "http": { "method": "GET", "path": "/", "accept": "application/json" } },
261
141
  "steps": [
262
- {
263
- "name": "get-countries",
264
- "node": "@blokjs/api-call",
265
- "type": "module"
266
- }
267
- ],
268
- "nodes": {
269
- "get-countries": {
270
- "inputs": {
271
- "url": "https://countriesnow.space/api/v0.1/countries/capital",
272
- "method": "GET",
273
- "headers": { "Content-Type": "application/json" },
274
- "responseType": "application/json"
275
- }
276
- }
277
- }
142
+ { "id": "get-countries", "use": "@blokjs/api-call",
143
+ "inputs": { "url": "https://countriesnow.space/api/v0.1/countries/capital", "method": "GET" } },
144
+ { "id": "respond", "use": "@blokjs/respond", "inputs": { "body": "$.state.get-countries" }, "ephemeral": true }
145
+ ]
278
146
  }
279
147
  \`\`\`
280
148
 
281
- ### Example 2: CRUD with Conditional Routing
149
+ ### Example 2: Validate then route (branch)
282
150
  \`\`\`json
283
151
  {
284
- "name": "Feedback Manager",
285
- "description": "Manages user feedback with CRUD operations",
152
+ "name": "Create Order",
153
+ "description": "Validates an order and routes by total",
286
154
  "version": "1.0.0",
287
- "trigger": {
288
- "http": {
289
- "method": "*",
290
- "path": "/:function?/:id?",
291
- "accept": "application/json"
292
- }
293
- },
155
+ "trigger": { "http": { "method": "POST", "path": "/orders" } },
294
156
  "steps": [
295
- {
296
- "name": "filter-request",
297
- "node": "@blokjs/if-else",
298
- "type": "module"
299
- }
300
- ],
301
- "nodes": {
302
- "filter-request": {
303
- "conditions": [
304
- {
305
- "type": "if",
306
- "condition": "ctx.request.method.toLowerCase() === \\"get\\" && ctx.request.params.function === ''",
307
- "steps": [
308
- { "name": "list-view", "node": "feedback-ui", "type": "module" }
309
- ]
310
- },
311
- {
312
- "type": "if",
313
- "condition": "ctx.request.method.toLowerCase() === \\"post\\" && ctx.request.params.function === \\"create\\"",
314
- "steps": [
315
- { "name": "process-data", "node": "data-processor", "type": "module" },
316
- { "name": "save-data", "node": "storage", "type": "module" }
317
- ]
318
- },
319
- {
320
- "type": "if",
321
- "condition": "ctx.request.method.toLowerCase() === \\"get\\" && ctx.request.params.function === \\"all\\"",
322
- "steps": [
323
- { "name": "get-all", "node": "storage", "type": "module" }
324
- ]
325
- },
326
- {
327
- "type": "else",
328
- "steps": [
329
- { "name": "not-allowed", "node": "error", "type": "module" }
330
- ]
331
- }
332
- ]
333
- },
334
- "list-view": { "inputs": {} },
335
- "process-data": {
336
- "inputs": {
337
- "id": "\${ctx.request.body.id}",
338
- "data": "\${ctx.request.body}"
157
+ { "id": "validate", "use": "order-validator", "inputs": { "order": "$.req.body" } },
158
+ { "id": "route",
159
+ "branch": {
160
+ "when": "ctx.state.validate.total > 100",
161
+ "then": [{ "id": "vip-save", "use": "order-store", "inputs": { "data": "$.state.validate", "tier": "vip" } }],
162
+ "else": [{ "id": "std-save", "use": "order-store", "inputs": { "data": "$.state.validate", "tier": "std" } }]
339
163
  }
340
- },
341
- "save-data": {
342
- "inputs": {
343
- "action": "set",
344
- "key": "\${ctx.request.body.id}",
345
- "value": "js/ctx.response.data"
346
- }
347
- },
348
- "get-all": {
349
- "inputs": { "action": "get-all" }
350
164
  }
351
- }
165
+ ]
352
166
  }
353
167
  \`\`\`
354
168
 
355
- ### Example 3: Queue-Triggered Workflow
169
+ ### Example 3: Worker (background job)
356
170
  \`\`\`json
357
171
  {
358
- "name": "User Event Processor",
359
- "description": "Processes user events from Kafka queue",
172
+ "name": "Process Upload Job",
173
+ "description": "Processes an uploaded file from the queue",
360
174
  "version": "1.0.0",
361
- "trigger": {
362
- "queue": {
363
- "provider": "kafka",
364
- "topic": "user-events",
365
- "consumerGroup": "event-processor",
366
- "ack": true
367
- }
368
- },
175
+ "trigger": { "worker": { "queue": "uploads", "provider": "nats", "retries": 3 } },
369
176
  "steps": [
370
- {
371
- "name": "process-event",
372
- "node": "event-handler",
373
- "type": "module"
374
- },
375
- {
376
- "name": "notify-user",
377
- "node": "@blokjs/api-call",
378
- "type": "module"
379
- }
380
- ],
381
- "nodes": {
382
- "process-event": {
383
- "inputs": {
384
- "eventType": "\${ctx.request.body.type}",
385
- "payload": "\${ctx.request.body.data}"
386
- }
387
- },
388
- "notify-user": {
389
- "inputs": {
390
- "url": "https://api.notifications.com/send",
391
- "method": "POST",
392
- "headers": { "Authorization": "Bearer \${ctx.env.NOTIFICATION_API_KEY}" },
393
- "body": "js/ctx.vars['process-event']"
394
- }
395
- }
396
- }
397
- }
398
- \`\`\`
399
-
400
- ### Example 4: Cron-Triggered Workflow
401
- \`\`\`json
402
- {
403
- "name": "Daily Report Generator",
404
- "description": "Generates and emails daily reports every morning",
405
- "version": "1.0.0",
406
- "trigger": {
407
- "cron": {
408
- "schedule": "0 8 * * *",
409
- "timezone": "America/New_York",
410
- "overlap": false
411
- }
412
- },
413
- "steps": [
414
- {
415
- "name": "fetch-metrics",
416
- "node": "@blokjs/api-call",
417
- "type": "module"
418
- },
419
- {
420
- "name": "generate-report",
421
- "node": "report-generator",
422
- "type": "module"
423
- }
424
- ],
425
- "nodes": {
426
- "fetch-metrics": {
427
- "inputs": {
428
- "url": "\${ctx.env.METRICS_API_URL}",
429
- "method": "GET",
430
- "headers": { "Authorization": "Bearer \${ctx.env.METRICS_API_KEY}" }
431
- }
432
- },
433
- "generate-report": {
434
- "inputs": {
435
- "data": "js/ctx.vars['fetch-metrics']",
436
- "format": "html",
437
- "recipients": ["admin@example.com"]
438
- }
439
- }
440
- }
177
+ { "id": "process", "use": "file-processor", "inputs": { "payload": "$.req.body", "jobId": "$.req.params.jobId" } },
178
+ { "id": "notify", "use": "@blokjs/api-call",
179
+ "inputs": { "url": "https://hooks.example.com/done", "method": "POST", "body": "$.state.process" } }
180
+ ]
441
181
  }
442
182
  \`\`\`
443
183
 
@@ -448,22 +188,21 @@ Node inputs support these patterns:
448
188
  * Use 2-space indentation
449
189
  * All string values must be properly escaped
450
190
  * No trailing commas`,
451
- updatePrompt: `You are a senior backend engineer specializing in the Blok (blok) workflow framework. Your task is to update an existing workflow JSON configuration with new functionality while preserving its core structure.
191
+ updatePrompt: `You are a senior backend engineer specializing in the Blok (blok) workflow framework. Your task is to update an existing v2 workflow JSON configuration with new functionality while preserving its core structure.
452
192
 
453
193
  Given the existing workflow JSON below, enhance or modify it according to the user's requirements while maintaining:
454
194
 
455
- 1. Valid JSON structure with name, description, version, trigger, steps, nodes
456
- 2. Consistent trigger configuration
457
- 3. All step names matching their nodes entries
458
- 4. Valid condition expressions using only ctx.* properties
459
- 5. Proper input value patterns (\${ctx.*}, js/*, static values)
195
+ 1. Valid v2 JSON structure: name, description, version, trigger, steps (inline inputs, NO nodes map)
196
+ 2. Consistent trigger configuration (exactly one trigger type)
197
+ 3. Unique \`id\` on every step (flat across branch arms)
198
+ 4. Branch conditions as raw \`ctx.*\` expressions (never \`js/\` or \`$.\`)
199
+ 5. References via \`"$.state.<id>"\` / \`"$.req.*"\` and persistence knobs (\`as\`/\`spread\`/\`ephemeral\`)
460
200
 
461
201
  What to return:
462
202
  * Return only the full updated workflow JSON
463
203
  * Preserve existing functionality unless explicitly asked to change it
464
204
  * Add new functionality as requested
465
- * Ensure all step references remain consistent
466
- * Keep input patterns comprehensive and accurate
205
+ * If the input is an old v1 workflow (\`name\`/\`node\` + a \`nodes{}\` map), MIGRATE it to v2 (\`id\`/\`use\` + inline \`inputs\`, no nodes map)
467
206
 
468
207
  Format:
469
208
  * No explanations or comments outside the JSON