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.
- package/dist/__tests__/modular-observability.capstone.e2e.test.js +72 -0
- package/dist/commands/create/node.js +46 -66
- package/dist/commands/create/project.js +60 -9
- package/dist/commands/create/utils/Examples.d.ts +11 -23
- package/dist/commands/create/utils/Examples.js +206 -421
- package/dist/commands/dev/index.js +40 -1
- package/dist/commands/generate/NodeGenerator.d.ts +0 -2
- package/dist/commands/generate/NodeGenerator.js +0 -20
- package/dist/commands/generate/RuntimeGenerator.d.ts +0 -2
- package/dist/commands/generate/RuntimeGenerator.js +0 -19
- package/dist/commands/generate/RuntimeGenerator.test.js +0 -29
- package/dist/commands/generate/TriggerGenerator.d.ts +0 -2
- package/dist/commands/generate/TriggerGenerator.js +0 -19
- package/dist/commands/generate/WorkflowGenerator.d.ts +0 -2
- package/dist/commands/generate/WorkflowGenerator.js +4 -23
- package/dist/commands/generate/WorkflowGenerator.test.js +2 -2
- package/dist/commands/generate/e2e/NodeGenerator.e2e.test.js +0 -12
- package/dist/commands/generate/e2e/RuntimeGenerator.e2e.test.js +0 -12
- package/dist/commands/generate/e2e/TriggerGenerator.e2e.test.js +0 -14
- package/dist/commands/generate/prompts/create-fn-node.system.js +10 -17
- package/dist/commands/generate/prompts/create-workflow.system.js +114 -375
- package/dist/commands/generate/validators/WorkflowValidator.js +80 -5
- package/dist/commands/generate/validators/WorkflowValidator.test.js +66 -0
- package/dist/commands/monitor/monitor-component.js +5 -5
- package/dist/commands/observability/add.d.ts +2 -0
- package/dist/commands/observability/add.js +113 -0
- package/dist/commands/observability/alerting-module.test.js +43 -0
- package/dist/commands/observability/apply.d.ts +10 -0
- package/dist/commands/observability/apply.js +11 -0
- package/dist/commands/observability/descriptor.d.ts +37 -0
- package/dist/commands/observability/descriptor.js +203 -0
- package/dist/commands/observability/descriptor.test.d.ts +1 -0
- package/dist/commands/observability/descriptor.test.js +40 -0
- package/dist/commands/observability/index.d.ts +1 -0
- package/dist/commands/observability/index.js +53 -0
- package/dist/commands/observability/list.d.ts +2 -0
- package/dist/commands/observability/list.js +45 -0
- package/dist/commands/observability/logging-module.test.d.ts +1 -0
- package/dist/commands/observability/logging-module.test.js +43 -0
- package/dist/commands/observability/obs-stack-module.test.d.ts +1 -0
- package/dist/commands/observability/obs-stack-module.test.js +33 -0
- package/dist/commands/observability/remove.d.ts +2 -0
- package/dist/commands/observability/remove.js +62 -0
- package/dist/commands/observability/shared.d.ts +6 -0
- package/dist/commands/observability/shared.js +23 -0
- package/dist/commands/observability/status.d.ts +2 -0
- package/dist/commands/observability/status.js +36 -0
- package/dist/commands/observability/tracing-module.test.d.ts +1 -0
- package/dist/commands/observability/tracing-module.test.js +42 -0
- package/dist/commands/profile/index.js +7 -10
- package/dist/commands/watch/format.d.ts +23 -0
- package/dist/commands/watch/format.js +60 -0
- package/dist/commands/watch/index.d.ts +1 -0
- package/dist/commands/watch/index.js +53 -0
- package/dist/commands/watch/sse.d.ts +16 -0
- package/dist/commands/watch/sse.js +82 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/services/obs-setup.d.ts +5 -0
- package/dist/services/obs-setup.js +68 -0
- package/dist/services/obs-setup.test.d.ts +1 -0
- package/dist/services/obs-setup.test.js +71 -0
- package/dist/services/obs-tiers.d.ts +9 -0
- package/dist/services/obs-tiers.js +16 -0
- package/dist/services/observability-mutations.d.ts +4 -0
- package/dist/services/observability-mutations.js +46 -0
- package/dist/services/observability-mutations.test.d.ts +1 -0
- package/dist/services/observability-mutations.test.js +57 -0
- package/dist/services/runtime-setup.d.ts +12 -1
- package/dist/services/runtime-setup.js +274 -14
- package/dist/studio-dist/assets/{index-BD8_9YPN.js → index-CnFqCRQe.js} +17 -17
- package/dist/studio-dist/index.html +1 -1
- package/package.json +3 -3
- package/dist/commands/generate/GenerationAnalytics.d.ts +0 -61
- package/dist/commands/generate/GenerationAnalytics.js +0 -163
- package/dist/commands/generate/GenerationAnalytics.test.js +0 -407
- package/dist/commands/generate/PromptVersioning.d.ts +0 -25
- package/dist/commands/generate/PromptVersioning.js +0 -71
- package/dist/commands/generate/PromptVersioning.test.js +0 -120
- /package/dist/{commands/generate/GenerationAnalytics.test.d.ts → __tests__/modular-observability.capstone.e2e.test.d.ts} +0 -0
- /package/dist/commands/{generate/PromptVersioning.test.d.ts → observability/alerting-module.test.d.ts} +0 -0
|
@@ -32,36 +32,27 @@ const package_dev_dependencies = {
|
|
|
32
32
|
"@types/ejs": "^3.1.5",
|
|
33
33
|
"@types/pg": "^8.11.11",
|
|
34
34
|
};
|
|
35
|
-
const python3_file = `
|
|
36
|
-
|
|
37
|
-
from
|
|
38
|
-
from
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
err.setCode(500)
|
|
57
|
-
err.setName(self.name)
|
|
58
|
-
|
|
59
|
-
stack_trace = traceback.format_exc()
|
|
60
|
-
err.setStack(stack_trace)
|
|
61
|
-
response.success = False
|
|
62
|
-
response.setError(err)
|
|
63
|
-
|
|
64
|
-
return response
|
|
35
|
+
const python3_file = `from pydantic import BaseModel
|
|
36
|
+
|
|
37
|
+
from blok import node
|
|
38
|
+
from blok.types.context import Context
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Input(BaseModel):
|
|
42
|
+
"""Validated inputs for the {{NODE_NAME}} node."""
|
|
43
|
+
|
|
44
|
+
name: str = "world"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Output(BaseModel):
|
|
48
|
+
message: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@node("{{NODE_NAME}}", "Describe what {{NODE_NAME}} does")
|
|
52
|
+
def run(ctx: Context, input: Input) -> Output:
|
|
53
|
+
# \`input\` is already validated against Input; the return is validated
|
|
54
|
+
# against Output and serialized for you.
|
|
55
|
+
return Output(message=f"Hello, {input.name}!")
|
|
65
56
|
`;
|
|
66
57
|
const examples_url = `
|
|
67
58
|
Examples:
|
|
@@ -121,316 +112,151 @@ autorestart=true
|
|
|
121
112
|
stderr_logfile=/var/log/python.err.log
|
|
122
113
|
stdout_logfile=/var/log/python.out.log
|
|
123
114
|
`;
|
|
124
|
-
const go_node_file =
|
|
115
|
+
const go_node_file = `// {{NODE_NAME}} — a Blok node for the Go runtime.
|
|
116
|
+
package {{NODE_PKG}}
|
|
125
117
|
|
|
126
118
|
import (
|
|
127
|
-
"github.com/blok
|
|
119
|
+
blok "github.com/nickincloud/blok-go"
|
|
128
120
|
)
|
|
129
121
|
|
|
130
|
-
|
|
122
|
+
// {{NODE_NAME_PASCAL}}Node implements blok.NodeHandler.
|
|
123
|
+
type {{NODE_NAME_PASCAL}}Node struct{}
|
|
131
124
|
|
|
132
|
-
|
|
133
|
-
|
|
125
|
+
// Execute runs the node. Input arrives on ctx.Request; config holds the step's
|
|
126
|
+
// inputs from the workflow. Return any JSON-serialisable value (or an error).
|
|
127
|
+
func (n *{{NODE_NAME_PASCAL}}Node) Execute(ctx *blok.Context, config map[string]interface{}) (interface{}, error) {
|
|
134
128
|
name := "World"
|
|
135
|
-
if body
|
|
136
|
-
if
|
|
137
|
-
name =
|
|
129
|
+
if body := ctx.Request.BodyMap(); body != nil {
|
|
130
|
+
if v, ok := body["name"].(string); ok {
|
|
131
|
+
name = v
|
|
138
132
|
}
|
|
139
133
|
}
|
|
140
134
|
|
|
141
|
-
// Access configuration
|
|
142
135
|
prefix := "Hello"
|
|
143
|
-
if
|
|
144
|
-
prefix =
|
|
136
|
+
if v, ok := config["prefix"].(string); ok {
|
|
137
|
+
prefix = v
|
|
145
138
|
}
|
|
146
139
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// Return successful result
|
|
151
|
-
return &sdk.ExecutionResult{
|
|
152
|
-
Success: true,
|
|
153
|
-
Data: map[string]interface{}{
|
|
154
|
-
"message": prefix + ", " + name + "!",
|
|
155
|
-
"timestamp": sdk.GetCurrentTimestamp(),
|
|
156
|
-
"language": "Go",
|
|
157
|
-
},
|
|
158
|
-
Errors: nil,
|
|
140
|
+
return map[string]interface{}{
|
|
141
|
+
"message": prefix + ", " + name + "!",
|
|
142
|
+
"language": "Go",
|
|
159
143
|
}, nil
|
|
160
144
|
}
|
|
161
145
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
registry.Register("{{NODE_NAME}}", &
|
|
166
|
-
|
|
167
|
-
// Start HTTP server
|
|
168
|
-
server := sdk.NewHTTPServer(registry, ":8080")
|
|
169
|
-
if err := server.Start(); err != nil {
|
|
170
|
-
panic(err)
|
|
171
|
-
}
|
|
146
|
+
// Register wires this node into the runtime registry. The generated
|
|
147
|
+
// register_user_nodes.go calls it for every node under runtimes/go/nodes.
|
|
148
|
+
func Register(registry *blok.NodeRegistry) {
|
|
149
|
+
registry.Register("{{NODE_NAME}}", &{{NODE_NAME_PASCAL}}Node{})
|
|
172
150
|
}
|
|
173
151
|
`;
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
go 1.21
|
|
177
|
-
|
|
178
|
-
require github.com/blok/sdk v1.0.0
|
|
179
|
-
`;
|
|
180
|
-
const go_dockerfile = `FROM golang:1.21-alpine AS builder
|
|
152
|
+
const java_node_file = `package com.blok.blok.nodes;
|
|
181
153
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
RUN go mod download
|
|
185
|
-
|
|
186
|
-
COPY . .
|
|
187
|
-
RUN CGO_ENABLED=0 GOOS=linux go build -o /node main.go
|
|
188
|
-
|
|
189
|
-
FROM alpine:latest
|
|
190
|
-
RUN apk --no-cache add ca-certificates
|
|
191
|
-
WORKDIR /root/
|
|
192
|
-
COPY --from=builder /node .
|
|
193
|
-
|
|
194
|
-
EXPOSE 8080
|
|
195
|
-
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
|
|
196
|
-
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
|
|
197
|
-
|
|
198
|
-
CMD ["./node"]
|
|
199
|
-
`;
|
|
200
|
-
const java_node_file = `package com.blok.nodes;
|
|
154
|
+
import com.blok.blok.node.NodeHandler;
|
|
155
|
+
import com.blok.blok.types.Context;
|
|
201
156
|
|
|
202
|
-
import
|
|
203
|
-
import com.blok.runtime.NodeRegistry;
|
|
204
|
-
import com.blok.server.RuntimeServer;
|
|
157
|
+
import java.time.Instant;
|
|
205
158
|
import java.util.HashMap;
|
|
206
159
|
import java.util.Map;
|
|
207
160
|
|
|
208
|
-
|
|
161
|
+
/**
|
|
162
|
+
* {{NODE_NAME}} — a Blok node for the Java runtime.
|
|
163
|
+
*
|
|
164
|
+
* <p>Input arrives on {@code ctx.getRequest().bodyMap()} (a Map, or null when
|
|
165
|
+
* the body isn't an object); {@code config} holds the step's inputs from the
|
|
166
|
+
* workflow. Return any JSON-serialisable value (a Map here), or throw to fail
|
|
167
|
+
* the step. {@code blokctl dev} discovers this class under
|
|
168
|
+
* {@code runtimes/java/nodes/} and registers it into the Java runtime.
|
|
169
|
+
*/
|
|
170
|
+
public class {{NODE_NAME_PASCAL}}Node implements NodeHandler {
|
|
171
|
+
|
|
209
172
|
@Override
|
|
210
|
-
public
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
name = (String) ctx.request.body.get("name");
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Access configuration
|
|
219
|
-
String prefix = "Hello";
|
|
220
|
-
if (config != null && config.containsKey("prefix")) {
|
|
221
|
-
prefix = (String) config.get("prefix");
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Store result in context for downstream nodes
|
|
225
|
-
String greeting = prefix + ", " + name + "!";
|
|
226
|
-
ctx.vars.put("greeting", greeting);
|
|
227
|
-
|
|
228
|
-
// Build response data
|
|
229
|
-
Map<String, Object> data = new HashMap<>();
|
|
230
|
-
data.put("message", greeting);
|
|
231
|
-
data.put("timestamp", System.currentTimeMillis());
|
|
232
|
-
data.put("language", "Java");
|
|
233
|
-
|
|
234
|
-
// Return successful result
|
|
235
|
-
return new Blok.ExecutionResult(true, data, null, null, null);
|
|
236
|
-
} catch (Exception e) {
|
|
237
|
-
return new Blok.ExecutionResult(false, null, e.getMessage(), null, null);
|
|
173
|
+
public Object execute(Context ctx, Map<String, Object> config) throws Exception {
|
|
174
|
+
String name = "World";
|
|
175
|
+
Map<String, Object> body = ctx.getRequest().bodyMap();
|
|
176
|
+
if (body != null && body.get("name") instanceof String s && !s.isEmpty()) {
|
|
177
|
+
name = s;
|
|
238
178
|
}
|
|
239
|
-
}
|
|
240
179
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
NodeRegistry registry = new NodeRegistry();
|
|
245
|
-
registry.register("{{NODE_NAME}}", new HelloWorldNode());
|
|
246
|
-
|
|
247
|
-
// Start HTTP server
|
|
248
|
-
RuntimeServer server = new RuntimeServer(registry, 8080);
|
|
249
|
-
server.start();
|
|
250
|
-
} catch (Exception e) {
|
|
251
|
-
e.printStackTrace();
|
|
252
|
-
System.exit(1);
|
|
180
|
+
String prefix = "Hello";
|
|
181
|
+
if (config != null && config.get("prefix") instanceof String s && !s.isEmpty()) {
|
|
182
|
+
prefix = s;
|
|
253
183
|
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
`;
|
|
257
|
-
const java_pom_file = `<?xml version="1.0" encoding="UTF-8"?>
|
|
258
|
-
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
259
|
-
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
260
|
-
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
|
261
|
-
http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
262
|
-
<modelVersion>4.0.0</modelVersion>
|
|
263
|
-
|
|
264
|
-
<groupId>com.blok</groupId>
|
|
265
|
-
<artifactId>{{NODE_NAME}}</artifactId>
|
|
266
|
-
<version>1.0.0</version>
|
|
267
|
-
|
|
268
|
-
<properties>
|
|
269
|
-
<maven.compiler.source>17</maven.compiler.source>
|
|
270
|
-
<maven.compiler.target>17</maven.compiler.target>
|
|
271
|
-
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
272
|
-
</properties>
|
|
273
|
-
|
|
274
|
-
<dependencies>
|
|
275
|
-
<dependency>
|
|
276
|
-
<groupId>com.google.code.gson</groupId>
|
|
277
|
-
<artifactId>gson</artifactId>
|
|
278
|
-
<version>2.10.1</version>
|
|
279
|
-
</dependency>
|
|
280
|
-
</dependencies>
|
|
281
|
-
|
|
282
|
-
<build>
|
|
283
|
-
<plugins>
|
|
284
|
-
<plugin>
|
|
285
|
-
<groupId>org.apache.maven.plugins</groupId>
|
|
286
|
-
<artifactId>maven-shade-plugin</artifactId>
|
|
287
|
-
<version>3.5.0</version>
|
|
288
|
-
<executions>
|
|
289
|
-
<execution>
|
|
290
|
-
<phase>package</phase>
|
|
291
|
-
<goals>
|
|
292
|
-
<goal>shade</goal>
|
|
293
|
-
</goals>
|
|
294
|
-
<configuration>
|
|
295
|
-
<transformers>
|
|
296
|
-
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
|
297
|
-
<mainClass>com.blok.nodes.HelloWorldNode</mainClass>
|
|
298
|
-
</transformer>
|
|
299
|
-
</transformers>
|
|
300
|
-
</configuration>
|
|
301
|
-
</execution>
|
|
302
|
-
</executions>
|
|
303
|
-
</plugin>
|
|
304
|
-
</plugins>
|
|
305
|
-
</build>
|
|
306
|
-
</project>
|
|
307
|
-
`;
|
|
308
|
-
const java_dockerfile = `FROM maven:3.9-eclipse-temurin-17 AS builder
|
|
309
|
-
|
|
310
|
-
WORKDIR /app
|
|
311
|
-
COPY pom.xml .
|
|
312
|
-
RUN mvn dependency:go-offline
|
|
313
|
-
|
|
314
|
-
COPY src ./src
|
|
315
|
-
RUN mvn clean package -DskipTests
|
|
316
|
-
|
|
317
|
-
FROM eclipse-temurin:17-jre-alpine
|
|
318
184
|
|
|
319
|
-
|
|
320
|
-
COPY --from=builder /app/target/{{NODE_NAME}}-1.0.0.jar ./app.jar
|
|
185
|
+
String message = prefix + ", " + name + "!";
|
|
321
186
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
|
|
187
|
+
// Publish a value for downstream nodes via context vars.
|
|
188
|
+
ctx.setVar("greeting", message);
|
|
325
189
|
|
|
326
|
-
|
|
190
|
+
Map<String, Object> result = new HashMap<>();
|
|
191
|
+
result.put("message", message);
|
|
192
|
+
result.put("timestamp", Instant.now().toString());
|
|
193
|
+
result.put("language", "Java");
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
327
197
|
`;
|
|
328
198
|
const rust_node_file = `use async_trait::async_trait;
|
|
329
|
-
use blok::
|
|
199
|
+
use blok::registry::NodeRegistry;
|
|
200
|
+
use blok::{Context, NodeHandler};
|
|
330
201
|
use std::collections::HashMap;
|
|
331
202
|
|
|
332
|
-
/// {{
|
|
333
|
-
struct {{NODE_NAME_PASCAL}};
|
|
203
|
+
/// {{NODE_NAME_PASCAL}}Node implements blok::NodeHandler.
|
|
204
|
+
struct {{NODE_NAME_PASCAL}}Node;
|
|
334
205
|
|
|
335
206
|
#[async_trait]
|
|
336
|
-
impl NodeHandler for {{NODE_NAME_PASCAL}} {
|
|
207
|
+
impl NodeHandler for {{NODE_NAME_PASCAL}}Node {
|
|
208
|
+
/// Execute runs the node. Input arrives on ctx.request; config holds the
|
|
209
|
+
/// step's inputs from the workflow. Return any JSON value (or an error).
|
|
337
210
|
async fn execute(
|
|
338
211
|
&self,
|
|
339
212
|
ctx: &mut Context,
|
|
340
213
|
config: &HashMap<String, serde_json::Value>,
|
|
341
214
|
) -> Result<serde_json::Value, Box<dyn std::error::Error + Send + Sync>> {
|
|
342
|
-
|
|
343
|
-
|
|
215
|
+
let name = ctx
|
|
216
|
+
.request
|
|
217
|
+
.body
|
|
344
218
|
.get("name")
|
|
345
219
|
.and_then(|v| v.as_str())
|
|
346
220
|
.unwrap_or("World");
|
|
347
221
|
|
|
348
|
-
// Access configuration
|
|
349
222
|
let prefix = config
|
|
350
223
|
.get("prefix")
|
|
351
224
|
.and_then(|v| v.as_str())
|
|
352
225
|
.unwrap_or("Hello");
|
|
353
226
|
|
|
354
|
-
let message = format!("{}, {}!", prefix, name);
|
|
355
|
-
|
|
356
|
-
// Store in context vars for downstream nodes
|
|
357
|
-
ctx.vars.insert(
|
|
358
|
-
"greeting".to_string(),
|
|
359
|
-
serde_json::Value::String(message.clone()),
|
|
360
|
-
);
|
|
361
|
-
|
|
362
|
-
// Return response
|
|
363
227
|
Ok(serde_json::json!({
|
|
364
|
-
"message":
|
|
228
|
+
"message": format!("{}, {}!", prefix, name),
|
|
365
229
|
"language": "Rust"
|
|
366
230
|
}))
|
|
367
231
|
}
|
|
368
232
|
}
|
|
369
233
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
// Register nodes
|
|
376
|
-
let mut registry = NodeRegistry::new("1.0.0");
|
|
377
|
-
registry.register("{{NODE_NAME}}", {{NODE_NAME_PASCAL}});
|
|
378
|
-
|
|
379
|
-
// Start HTTP server
|
|
380
|
-
blok::server::serve(registry, 8080).await.unwrap();
|
|
234
|
+
/// register wires this node into the runtime registry. The generated
|
|
235
|
+
/// user_nodes/mod.rs calls it for every node under runtimes/rust/nodes.
|
|
236
|
+
pub fn register(registry: &mut NodeRegistry) {
|
|
237
|
+
registry.register("{{NODE_NAME}}", {{NODE_NAME_PASCAL}}Node);
|
|
381
238
|
}
|
|
382
239
|
`;
|
|
383
|
-
const rust_cargo_file = `[package]
|
|
384
|
-
name = "{{NODE_NAME}}"
|
|
385
|
-
version = "1.0.0"
|
|
386
|
-
edition = "2021"
|
|
387
|
-
|
|
388
|
-
[[bin]]
|
|
389
|
-
name = "{{NODE_NAME}}"
|
|
390
|
-
path = "src/main.rs"
|
|
391
|
-
|
|
392
|
-
[dependencies]
|
|
393
|
-
blok = { path = "../../sdk" }
|
|
394
|
-
tokio = { version = "1", features = ["full"] }
|
|
395
|
-
serde = { version = "1", features = ["derive"] }
|
|
396
|
-
serde_json = "1"
|
|
397
|
-
async-trait = "0.1"
|
|
398
|
-
tracing = "0.1"
|
|
399
|
-
tracing-subscriber = "0.3"
|
|
400
|
-
`;
|
|
401
|
-
const rust_dockerfile = `FROM rust:1.77-alpine AS builder
|
|
402
|
-
|
|
403
|
-
RUN apk add --no-cache musl-dev
|
|
404
|
-
|
|
405
|
-
WORKDIR /app
|
|
406
|
-
COPY Cargo.toml Cargo.lock ./
|
|
407
|
-
RUN mkdir -p src && echo 'fn main() {}' > src/main.rs && \\
|
|
408
|
-
cargo build --release 2>/dev/null || true && rm -rf src
|
|
409
|
-
|
|
410
|
-
COPY . .
|
|
411
|
-
RUN cargo build --release
|
|
412
|
-
|
|
413
|
-
FROM alpine:latest
|
|
414
|
-
RUN apk --no-cache add ca-certificates
|
|
415
|
-
WORKDIR /root/
|
|
416
|
-
COPY --from=builder /app/target/release/{{NODE_NAME}} .
|
|
417
|
-
|
|
418
|
-
EXPOSE 8080
|
|
419
|
-
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
|
|
420
|
-
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
|
|
421
|
-
|
|
422
|
-
CMD ["./{{NODE_NAME}}"]
|
|
423
|
-
`;
|
|
424
240
|
const csharp_node_file = `using System.Text.Json;
|
|
425
|
-
using Blok.
|
|
241
|
+
using Blok.Core.Node;
|
|
242
|
+
using Blok.Core.Types;
|
|
426
243
|
|
|
427
|
-
namespace Blok.
|
|
244
|
+
namespace Blok.Core.Nodes;
|
|
428
245
|
|
|
246
|
+
/// <summary>
|
|
247
|
+
/// {{NODE_NAME_PASCAL}}Node — a Blok node for the C# runtime. Discovered under
|
|
248
|
+
/// runtimes/csharp/nodes/ and registered into the shared gRPC server alongside
|
|
249
|
+
/// the built-in nodes (same model as the other SDKs). Run \`blokctl dev\`; no
|
|
250
|
+
/// per-node csproj/Dockerfile — it isn't a standalone service.
|
|
251
|
+
/// </summary>
|
|
429
252
|
public class {{NODE_NAME_PASCAL}}Node : INodeHandler
|
|
430
253
|
{
|
|
254
|
+
/// <summary>
|
|
255
|
+
/// Execute the node. Input arrives on ctx.Request; config holds the step's
|
|
256
|
+
/// inputs from the workflow. Return any JSON-serialisable value.
|
|
257
|
+
/// </summary>
|
|
431
258
|
public Task<JsonElement> ExecuteAsync(Context ctx, Dictionary<string, JsonElement> config)
|
|
432
259
|
{
|
|
433
|
-
// Access request body
|
|
434
260
|
var name = "World";
|
|
435
261
|
if (ctx.Request.Body.ValueKind == JsonValueKind.Object &&
|
|
436
262
|
ctx.Request.Body.TryGetProperty("name", out var nameEl) &&
|
|
@@ -439,7 +265,6 @@ public class {{NODE_NAME_PASCAL}}Node : INodeHandler
|
|
|
439
265
|
name = nameEl.GetString() ?? "World";
|
|
440
266
|
}
|
|
441
267
|
|
|
442
|
-
// Access configuration
|
|
443
268
|
var prefix = "Hello";
|
|
444
269
|
if (config.TryGetValue("prefix", out var prefixEl) &&
|
|
445
270
|
prefixEl.ValueKind == JsonValueKind.String)
|
|
@@ -448,11 +273,8 @@ public class {{NODE_NAME_PASCAL}}Node : INodeHandler
|
|
|
448
273
|
}
|
|
449
274
|
|
|
450
275
|
var message = $"{prefix}, {name}!";
|
|
276
|
+
ctx.SetVar("greeting", message);
|
|
451
277
|
|
|
452
|
-
// Store in context for downstream nodes
|
|
453
|
-
ctx.Vars["greeting"] = JsonSerializer.SerializeToElement(message);
|
|
454
|
-
|
|
455
|
-
// Return response
|
|
456
278
|
var result = JsonSerializer.SerializeToElement(new
|
|
457
279
|
{
|
|
458
280
|
message,
|
|
@@ -464,154 +286,58 @@ public class {{NODE_NAME_PASCAL}}Node : INodeHandler
|
|
|
464
286
|
}
|
|
465
287
|
}
|
|
466
288
|
`;
|
|
467
|
-
const csharp_csproj_file = `<Project Sdk="Microsoft.NET.Sdk.Web">
|
|
468
|
-
<PropertyGroup>
|
|
469
|
-
<TargetFramework>net8.0</TargetFramework>
|
|
470
|
-
<RootNamespace>Blok.Runtime</RootNamespace>
|
|
471
|
-
<ImplicitUsings>enable</ImplicitUsings>
|
|
472
|
-
<Nullable>enable</Nullable>
|
|
473
|
-
</PropertyGroup>
|
|
474
|
-
</Project>
|
|
475
|
-
`;
|
|
476
|
-
const csharp_dockerfile = `FROM mcr.microsoft.com/dotnet/sdk:8.0 AS builder
|
|
477
|
-
WORKDIR /app
|
|
478
|
-
COPY *.csproj .
|
|
479
|
-
RUN dotnet restore
|
|
480
|
-
COPY . .
|
|
481
|
-
RUN dotnet publish -c Release -o /out
|
|
482
|
-
|
|
483
|
-
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
|
|
484
|
-
WORKDIR /app
|
|
485
|
-
COPY --from=builder /out .
|
|
486
|
-
|
|
487
|
-
EXPOSE 8080
|
|
488
|
-
ENV PORT=8080
|
|
489
|
-
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
|
|
490
|
-
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
|
|
491
|
-
|
|
492
|
-
CMD ["dotnet", "BlokRuntime.dll"]
|
|
493
|
-
`;
|
|
494
289
|
const php_node_file = `<?php
|
|
495
290
|
|
|
496
|
-
|
|
291
|
+
declare(strict_types=1);
|
|
497
292
|
|
|
498
|
-
|
|
499
|
-
use Blok\\Context;
|
|
293
|
+
namespace Blok\\Blok\\Nodes\\{{NODE_NAME_PASCAL}};
|
|
500
294
|
|
|
501
|
-
|
|
295
|
+
use Blok\\Blok\\Node\\NodeHandler;
|
|
296
|
+
use Blok\\Blok\\Types\\Context;
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* {{NODE_NAME}} — a Blok node for the PHP runtime.
|
|
300
|
+
*
|
|
301
|
+
* Input arrives on \$ctx->request; \$config holds the step's inputs from the
|
|
302
|
+
* workflow. Return any JSON-serialisable value (array, scalar, etc.).
|
|
303
|
+
*/
|
|
304
|
+
final class {{NODE_NAME_PASCAL}}Node implements NodeHandler
|
|
502
305
|
{
|
|
503
306
|
public function execute(Context $ctx, array $config): mixed
|
|
504
307
|
{
|
|
505
|
-
|
|
506
|
-
$
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
$prefix = $config['prefix'] ?? 'Hello';
|
|
510
|
-
|
|
511
|
-
$message = "$prefix, $name!";
|
|
308
|
+
$name = $ctx->request->bodyStr('name') ?? 'World';
|
|
309
|
+
$prefix = isset($config['prefix']) && is_string($config['prefix'])
|
|
310
|
+
? $config['prefix']
|
|
311
|
+
: 'Hello';
|
|
512
312
|
|
|
513
|
-
// Store in context for downstream nodes
|
|
514
|
-
$ctx->vars['greeting'] = $message;
|
|
515
|
-
|
|
516
|
-
// Return response
|
|
517
313
|
return [
|
|
518
|
-
'message' => $
|
|
519
|
-
'timestamp' => date('c'),
|
|
314
|
+
'message' => sprintf('%s, %s!', $prefix, $name),
|
|
520
315
|
'language' => 'PHP',
|
|
521
316
|
];
|
|
522
317
|
}
|
|
523
318
|
}
|
|
524
319
|
`;
|
|
525
|
-
const
|
|
526
|
-
"name": "blok/{{NODE_NAME}}",
|
|
527
|
-
"type": "project",
|
|
528
|
-
"require": {
|
|
529
|
-
"php": ">=8.2",
|
|
530
|
-
"react/http": "^1.9",
|
|
531
|
-
"react/socket": "^1.15"
|
|
532
|
-
},
|
|
533
|
-
"autoload": {
|
|
534
|
-
"psr-4": {
|
|
535
|
-
"Blok\\\\": "src/"
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
`;
|
|
540
|
-
const php_dockerfile = `FROM php:8.2-cli-alpine AS builder
|
|
541
|
-
WORKDIR /app
|
|
542
|
-
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
|
|
543
|
-
COPY composer.json .
|
|
544
|
-
RUN composer install --no-dev --optimize-autoloader
|
|
545
|
-
COPY . .
|
|
546
|
-
|
|
547
|
-
FROM php:8.2-cli-alpine
|
|
548
|
-
WORKDIR /app
|
|
549
|
-
COPY --from=builder /app .
|
|
550
|
-
|
|
551
|
-
EXPOSE 8080
|
|
552
|
-
ENV PORT=8080
|
|
553
|
-
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
|
|
554
|
-
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
|
|
555
|
-
|
|
556
|
-
CMD ["php", "index.php"]
|
|
557
|
-
`;
|
|
558
|
-
const ruby_node_file = `require_relative '../../lib/blok'
|
|
559
|
-
|
|
560
|
-
module Blok
|
|
561
|
-
module Nodes
|
|
562
|
-
class {{NODE_NAME_PASCAL}}Node < Blok::NodeHandler
|
|
563
|
-
def execute(ctx, config)
|
|
564
|
-
# Access request body
|
|
565
|
-
name = ctx.request.body.is_a?(Hash) ? ctx.request.body['name'] : nil
|
|
566
|
-
name ||= 'World'
|
|
320
|
+
const ruby_node_file = `# frozen_string_literal: true
|
|
567
321
|
|
|
568
|
-
|
|
569
|
-
|
|
322
|
+
# {{NODE_NAME_PASCAL}}Node — registered as "{{NODE_NAME}}" by the Ruby runtime.
|
|
323
|
+
#
|
|
324
|
+
# Request body arrives on ctx.request (use body_str(key) for a String|nil);
|
|
325
|
+
# config holds the step's inputs from the workflow. Return any
|
|
326
|
+
# JSON-serialisable Hash. Publish to downstream steps with ctx.set_var(k, v).
|
|
327
|
+
class {{NODE_NAME_PASCAL}}Node < Blok::Node::NodeHandler
|
|
328
|
+
def execute(ctx, config)
|
|
329
|
+
name = ctx.request.body_str("name") || "World"
|
|
330
|
+
prefix = config["prefix"] || "Hello"
|
|
570
331
|
|
|
571
|
-
|
|
332
|
+
message = "#{prefix}, #{name}!"
|
|
333
|
+
ctx.set_var("greeting", message)
|
|
572
334
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
{
|
|
578
|
-
'message' => message,
|
|
579
|
-
'timestamp' => Time.now.utc.iso8601,
|
|
580
|
-
'language' => 'Ruby'
|
|
581
|
-
}
|
|
582
|
-
end
|
|
583
|
-
end
|
|
335
|
+
{
|
|
336
|
+
"message" => message,
|
|
337
|
+
"language" => "Ruby"
|
|
338
|
+
}
|
|
584
339
|
end
|
|
585
|
-
end
|
|
586
|
-
`;
|
|
587
|
-
const ruby_gemfile = `source 'https://rubygems.org'
|
|
588
|
-
|
|
589
|
-
ruby '>= 3.1'
|
|
590
|
-
|
|
591
|
-
gem 'sinatra', '~> 4.0'
|
|
592
|
-
gem 'puma', '~> 6.4'
|
|
593
|
-
gem 'rackup', '~> 2.1'
|
|
594
|
-
`;
|
|
595
|
-
const ruby_dockerfile = `FROM ruby:3.2-alpine AS builder
|
|
596
|
-
RUN apk add --no-cache build-base
|
|
597
|
-
WORKDIR /app
|
|
598
|
-
COPY Gemfile Gemfile.lock ./
|
|
599
|
-
RUN bundle install --without development test
|
|
600
|
-
|
|
601
|
-
FROM ruby:3.2-alpine
|
|
602
|
-
RUN apk --no-cache add ca-certificates wget
|
|
603
|
-
WORKDIR /app
|
|
604
|
-
COPY --from=builder /usr/local/bundle /usr/local/bundle
|
|
605
|
-
COPY . .
|
|
606
|
-
|
|
607
|
-
EXPOSE 8080
|
|
608
|
-
ENV PORT=8080
|
|
609
|
-
ENV RACK_ENV=production
|
|
610
|
-
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\
|
|
611
|
-
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
|
|
612
|
-
|
|
613
|
-
CMD ["bundle", "exec", "puma", "-b", "tcp://0.0.0.0:8080"]
|
|
614
|
-
`;
|
|
340
|
+
end`;
|
|
615
341
|
const agents_md = `
|
|
616
342
|
# AGENTS.md — Blok Framework AI Context
|
|
617
343
|
|
|
@@ -623,7 +349,52 @@ Blok is a **multi-trigger, multi-runtime workflow framework**. A workflow is a d
|
|
|
623
349
|
The 9 trigger types: \`http\`, \`worker\`, \`cron\`, \`pubsub\`, \`sse\`, \`websocket\`, \`webhook\`, \`mcp\`, \`grpc\`.
|
|
624
350
|
The 8 runtimes: \`typescript\` (in-process), \`go\`, \`rust\`, \`java\`, \`csharp\`, \`php\`, \`ruby\`, \`python3\`.
|
|
625
351
|
|
|
626
|
-
The canonical
|
|
352
|
+
The canonical TypeScript form is the **typed-handle DSL** from \`@blokjs/core\`: \`workflow(name, { version, trigger }, (entry) => { ... })\`, where each \`step()\` returns a typed handle you reference directly — **no \`$\`, no \`js/\`, no raw \`ctx\` strings.** The object-style \`workflow({ name, version, trigger, steps: [...] })\` from \`@blokjs/helper\` and JSON workflows are equivalent (all three compile to the same IR) and remain fully supported. The same shape works for all 9 triggers — only the \`trigger:\` block changes.
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## 0. THE TYPED-HANDLE DSL (\`@blokjs/core\`) — preferred for TypeScript
|
|
357
|
+
|
|
358
|
+
\`\`\`ts
|
|
359
|
+
import { workflow, step, branch, forEach, switchOn, tryCatch, http, tpl, gt } from "@blokjs/core";
|
|
360
|
+
|
|
361
|
+
export default workflow("order-intake", { version: "1.0.0", trigger: http.post("/orders") }, (req) => {
|
|
362
|
+
// step(id, node, inputs) → a TYPED handle shaped like the node's output.
|
|
363
|
+
const order = step("validate", validateOrder, { qty: req.body.qty });
|
|
364
|
+
|
|
365
|
+
// Reference a handle field anywhere downstream — it records a ref the runner resolves.
|
|
366
|
+
step("summary", summarize, { line: tpl\`order of \${order.qty} item(s)\` });
|
|
367
|
+
|
|
368
|
+
// Control flow: a comparator (gt/eq/lt/...) builds the condition; arms are callbacks.
|
|
369
|
+
branch("lane", gt(order.qty, 10), {
|
|
370
|
+
then: () => { step("bulk", routeOrder, { lane: "bulk" }); },
|
|
371
|
+
else: () => { step("standard", routeOrder, { lane: "standard" }); },
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
\`\`\`
|
|
375
|
+
|
|
376
|
+
**Trigger config:** \`http.{get,post,put,delete,patch,any}(path?, opts?)\` for HTTP (omit \`path\` for file-based routing). For any other trigger pass the raw block as \`trigger:\` — e.g. \`{ worker: { queue: "jobs" } }\`, \`{ cron: { schedule: "0 2 * * *" } }\`, \`{ webhook: { source: "stripe" } }\`.
|
|
377
|
+
|
|
378
|
+
**The entry handle** (the callback's argument) is the trigger payload — typed and conventionally named per trigger: \`http\`→\`req\`, \`webhook\`→\`event\`, \`cron\`→\`tick\`, \`worker\`→\`job\`, \`pubsub\`→\`msg\`, \`grpc\`→\`rpc\` (others get a loose handle). Read \`req.body\`, \`req.params.id\`, \`req.query.q\`, \`req.headers["x-…"]\`.
|
|
379
|
+
|
|
380
|
+
**Handles & persistence:**
|
|
381
|
+
- \`const h = step("id", node, inputs)\` — every step auto-persists to \`ctx.state["id"]\` on success; \`h\` / \`h.field\` is how you reference it later (never \`$.state.id\`).
|
|
382
|
+
- 4th arg \`opts\`: \`{ as: "name" }\` roots the handle at \`state["name"]\`; \`{ spread: true }\` flattens \`result.data\` into state (mutually exclusive with \`as\`); \`{ ephemeral: true }\` skips persistence (handle then unreadable downstream); also \`idempotencyKey\`, \`retry\`, \`maxDuration\`, \`type: "runtime.<lang>"\`.
|
|
383
|
+
- \`tpl\`…\${h.field}…\`\` builds a string with embedded handle refs.
|
|
384
|
+
- Comparators (typed): \`eq, ne, gt, gte, lt, lte, not\`. A bare boolean handle is a truthiness check.
|
|
385
|
+
- \`defineNode\` is re-exported from \`@blokjs/core\` — author nodes exactly as before; just RETURN output (never write \`ctx.state\`/\`ctx.vars\`).
|
|
386
|
+
|
|
387
|
+
**Control-flow primitives** (all from \`@blokjs/core\`):
|
|
388
|
+
- \`branch(id, cond, { then: () => {…}, else?: () => {…} })\` — \`cond\` is a comparator or a boolean handle.
|
|
389
|
+
- \`forEach(iterable, (item, index) => { step(…) }, { as?, mode?, concurrency? })\` — \`iterable\` is a handle; \`item\`/\`index\` are per-iteration handles; \`mode: "parallel"\` bounds with \`concurrency\` (default 10).
|
|
390
|
+
- \`switchOn(discriminant, { cases: [{ when: "push", do: () => {…} }], default?: () => {…} }, { id })\` — \`when\` labels are STATIC literals (a handle never matches).
|
|
391
|
+
- \`tryCatch(id, { try: () => {…}, catch: (error) => { step("log", logger, { msg: error.message }); }, finally?: () => {…} })\` — \`error\` is a typed handle (\`.message\`/\`.name\`/\`.stack\`/\`.code\`/\`.stepId\`).
|
|
392
|
+
|
|
393
|
+
**The four footguns** (full detail: \`docs/d/primitives/handles-and-footguns.mdx\`):
|
|
394
|
+
1. **Arm-scoped handles don't escape their arm.** A handle minted inside a \`branch\`/\`switchOn\`/\`tryCatch\`/\`forEach\` arm is unreadable outside it. Return a value from the control-flow step, or have both arms write one \`as:\` key.
|
|
395
|
+
2. **Ephemeral handles are unreadable.** Reading an \`{ ephemeral: true }\` step's handle resolves to \`undefined\`.
|
|
396
|
+
3. **Never reuse a step \`id\`** — including across mutually-exclusive arms. Ids are a flat per-workflow map; the runner throws at load time. Use \`as:\` when two arms must write the same downstream key.
|
|
397
|
+
4. **Never name a \`forEach\` \`as:\`/\`asIndex\` after an existing step id** (or another loop's \`as\`) — they share \`ctx.state\`; the runner throws at load time.
|
|
627
398
|
|
|
628
399
|
---
|
|
629
400
|
|
|
@@ -1132,7 +903,7 @@ Process-global middleware: \`WorkflowRegistry.getInstance().setGlobalMiddleware(
|
|
|
1132
903
|
Always \`export default defineNode(...)\`. Never class-based \`BlokService\`. Zod input/output are mandatory.
|
|
1133
904
|
|
|
1134
905
|
\`\`\`ts
|
|
1135
|
-
import { defineNode } from "@blokjs/
|
|
906
|
+
import { defineNode } from "@blokjs/core";
|
|
1136
907
|
import { z } from "zod";
|
|
1137
908
|
|
|
1138
909
|
export default defineNode({
|
|
@@ -1608,7 +1379,7 @@ Per-step reliability lives on the step: \`idempotencyKey\` (cache by \`(workflow
|
|
|
1608
1379
|
Always \`export default defineNode(...)\` (TS) — never class-based \`BlokService\`. Zod input/output are mandatory. Never write \`ctx.state\` from a node — return your output and let the runner persist it (use \`ctx.publish(name, value)\` for a true side-channel). No \`any\` types — use \`z.unknown()\`.
|
|
1609
1380
|
|
|
1610
1381
|
\`\`\`typescript
|
|
1611
|
-
import { defineNode } from "@blokjs/
|
|
1382
|
+
import { defineNode } from "@blokjs/core";
|
|
1612
1383
|
import { z } from "zod";
|
|
1613
1384
|
|
|
1614
1385
|
export default defineNode({
|
|
@@ -1662,7 +1433,21 @@ Registration is **manual** in non-TS runtimes — importing the module runs the
|
|
|
1662
1433
|
|
|
1663
1434
|
## 4. Generating Workflows
|
|
1664
1435
|
|
|
1665
|
-
|
|
1436
|
+
**Preferred (TypeScript): the typed-handle DSL from \`@blokjs/core\`.** \`workflow(name, { version, trigger }, (entry) => { ... })\` — each \`step(id, node, inputs)\` returns a typed handle you reference directly; no \`$\`, no \`js/\`, no raw \`ctx\` strings. \`http.{get,post,…}(path?)\` builds the trigger; other triggers pass the raw block (\`{ worker: { queue } }\`, \`{ cron: { schedule } }\`, …). Entry name per trigger: \`http\`→\`req\`, \`worker\`→\`job\`, \`cron\`→\`tick\`, \`webhook\`→\`event\`, \`pubsub\`→\`msg\`, \`grpc\`→\`rpc\`.
|
|
1437
|
+
|
|
1438
|
+
\`\`\`typescript
|
|
1439
|
+
import { workflow, step, branch, gt, http } from "@blokjs/core";
|
|
1440
|
+
|
|
1441
|
+
export default workflow("Process Order", { version: "1.0.0", trigger: http.post("/orders") }, (req) => {
|
|
1442
|
+
const order = step("validate", orderValidator, { order: req.body }); // typed handle
|
|
1443
|
+
step("save", orderStore, { data: order }); // reference it directly
|
|
1444
|
+
branch("big", gt(order.total, 100), { then: () => { step("vip", flagVip, { id: order.id }); } });
|
|
1445
|
+
});
|
|
1446
|
+
\`\`\`
|
|
1447
|
+
|
|
1448
|
+
Control flow (all from \`@blokjs/core\`): \`branch(id, cond, {then,else?})\`, \`forEach(iterable, (item,i)=>{}, {as?,mode?})\`, \`switchOn(disc, {cases:[{when,do}],default?}, {id})\`, \`tryCatch(id, {try,catch:(err)=>{},finally?})\`; comparators \`eq/ne/gt/gte/lt/lte/not\`; \`tpl\`…\${h.x}…\`\` for strings. The **four footguns**: arm-scoped handles don't escape their arm; ephemeral handles read \`undefined\`; never reuse a step \`id\`; never name a \`forEach\` \`as:\` after an existing id. See \`docs/d/primitives/handles-and-footguns.mdx\`.
|
|
1449
|
+
|
|
1450
|
+
**Equivalent object-style form** (\`@blokjs/helper\`, also valid; JSON mirrors it): one object literal, \`steps: [...]\`, reference outputs with \`$.state.<id>\` / \`$.req.body\`.
|
|
1666
1451
|
|
|
1667
1452
|
\`\`\`typescript
|
|
1668
1453
|
import { workflow, $ } from "@blokjs/helper";
|
|
@@ -1735,7 +1520,7 @@ branch({ id: "route",
|
|
|
1735
1520
|
- Do NOT generate class-based \`BlokService\` nodes or use \`any\` types — always \`defineNode()\` (TS) / \`@node\` (Python) with Zod/Pydantic schemas.
|
|
1736
1521
|
- Do NOT use ESLint/Prettier — this project uses Biome. Do NOT edit auto-generated files in \`.blok/runtimes/\`.
|
|
1737
1522
|
`;
|
|
1738
|
-
const function_first_node_file = `import { defineNode } from "@blokjs/
|
|
1523
|
+
const function_first_node_file = `import { defineNode } from "@blokjs/core";
|
|
1739
1524
|
import { z } from "zod";
|
|
1740
1525
|
|
|
1741
1526
|
/**
|
|
@@ -1760,13 +1545,13 @@ export default defineNode({
|
|
|
1760
1545
|
// Execute function - type-safe with inferred types from Zod schemas
|
|
1761
1546
|
async execute(ctx, input) {
|
|
1762
1547
|
// Your business logic here
|
|
1763
|
-
// - ctx.vars: Access workflow variables
|
|
1764
1548
|
// - ctx.request: Access HTTP request data
|
|
1765
1549
|
// - ctx.logger: Log messages
|
|
1766
1550
|
// - ctx.env: Access environment variables
|
|
1767
|
-
|
|
1768
|
-
//
|
|
1769
|
-
ctx.
|
|
1551
|
+
//
|
|
1552
|
+
// Just RETURN your output — the runner auto-persists it to
|
|
1553
|
+
// ctx.state["{{NODE_NAME}}"], readable from any later step's handle.
|
|
1554
|
+
// Do NOT write to ctx.state / ctx.vars inside a node.
|
|
1770
1555
|
|
|
1771
1556
|
// Return type-safe output (validated automatically)
|
|
1772
1557
|
return {
|
|
@@ -1776,4 +1561,4 @@ export default defineNode({
|
|
|
1776
1561
|
},
|
|
1777
1562
|
});
|
|
1778
1563
|
`;
|
|
1779
|
-
export { node_file, package_dependencies, package_dev_dependencies, python3_file, examples_url, workflow_template, supervisord_nodejs, supervisord_python, go_node_file,
|
|
1564
|
+
export { node_file, package_dependencies, package_dev_dependencies, python3_file, examples_url, workflow_template, supervisord_nodejs, supervisord_python, go_node_file, java_node_file, rust_node_file, csharp_node_file, php_node_file, ruby_node_file, function_first_node_file, agents_md, claude_md, };
|