blokctl 1.4.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/create/project.js +2 -2
- package/dist/scaffold-repo/sdks/php/bin/serve.php +95 -0
- package/dist/scaffold-repo/sdks/python3/bin/serve.py +174 -0
- package/dist/scaffold-repo/sdks/python3/bin/worker.py +60 -0
- package/dist/scaffold-repo/sdks/ruby/bin/serve.rb +105 -0
- package/dist/scaffold-repo/triggers/cron/package.json +4 -4
- package/dist/scaffold-repo/triggers/grpc/package.json +6 -6
- package/dist/scaffold-repo/triggers/http/package.json +11 -11
- package/dist/scaffold-repo/triggers/mcp/package.json +4 -4
- package/dist/scaffold-repo/triggers/pubsub/package.json +4 -4
- package/dist/scaffold-repo/triggers/pubsub/template/package.json +7 -7
- package/dist/scaffold-repo/triggers/sse/package.json +5 -5
- package/dist/scaffold-repo/triggers/webhook/package.json +4 -4
- package/dist/scaffold-repo/triggers/websocket/package.json +6 -6
- package/dist/scaffold-repo/triggers/worker/package.json +4 -4
- package/dist/scaffold-repo/triggers/worker/template/package.json +7 -7
- package/package.json +3 -3
|
@@ -23,7 +23,7 @@ const exec = util.promisify(child_process.exec);
|
|
|
23
23
|
const HOME_DIR = `${os.homedir()}/.blok`;
|
|
24
24
|
const GITHUB_REPO_LOCAL = `${HOME_DIR}/blok`;
|
|
25
25
|
const GITHUB_REPO_REMOTE = "https://github.com/well-prado/blok.git";
|
|
26
|
-
const GITHUB_REPO_RELEASE_TAG = "v1.4.
|
|
26
|
+
const GITHUB_REPO_RELEASE_TAG = "v1.4.1";
|
|
27
27
|
const BUNDLED_SCAFFOLD_REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../scaffold-repo");
|
|
28
28
|
const RUNTIME_HELLO_EXAMPLES = {
|
|
29
29
|
go: "runtime-go-hello.ts",
|
|
@@ -564,7 +564,7 @@ export async function createProject(opts, version, currentPath = false, localRep
|
|
|
564
564
|
"@blokjs/trigger-worker": "triggers/worker",
|
|
565
565
|
"@blokjs/core": "core/core",
|
|
566
566
|
};
|
|
567
|
-
const BLOKJS_DEP_RANGE = "^1.4.
|
|
567
|
+
const BLOKJS_DEP_RANGE = "^1.4.1";
|
|
568
568
|
for (const depGroup of ["dependencies", "devDependencies", "peerDependencies"]) {
|
|
569
569
|
const deps = packageJsonContent[depGroup];
|
|
570
570
|
if (!deps)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
declare(strict_types=1);
|
|
4
|
+
|
|
5
|
+
require_once __DIR__ . '/../vendor/autoload.php';
|
|
6
|
+
|
|
7
|
+
use Blok\Blok\Config\ServerConfig;
|
|
8
|
+
use Blok\Blok\Config\Transport;
|
|
9
|
+
use Blok\Blok\Examples\ApiCallNode;
|
|
10
|
+
use Blok\Blok\Examples\BlokErrorDemoNode;
|
|
11
|
+
use Blok\Blok\Examples\ChainTestNode;
|
|
12
|
+
use Blok\Blok\Examples\HelloWorldNode;
|
|
13
|
+
use Blok\Blok\Examples\TransformDataNode;
|
|
14
|
+
use Blok\Blok\Examples\TypedGreetNode;
|
|
15
|
+
use Blok\Blok\Node\NodeHandler;
|
|
16
|
+
use Blok\Blok\Node\NodeRegistry;
|
|
17
|
+
use Blok\Blok\Server\BlokNodeRuntimeService;
|
|
18
|
+
use Blok\Blok\Server\NodeRuntimeInterface;
|
|
19
|
+
use Blok\Blok\Server\Server;
|
|
20
|
+
use Spiral\RoadRunner\GRPC\Server as GrpcServer;
|
|
21
|
+
|
|
22
|
+
$config = ServerConfig::fromEnv();
|
|
23
|
+
|
|
24
|
+
$registry = new NodeRegistry($config->version);
|
|
25
|
+
$registry->register('hello-world', new HelloWorldNode());
|
|
26
|
+
$registry->register('api-call', new ApiCallNode());
|
|
27
|
+
$registry->register('transform-data', new TransformDataNode());
|
|
28
|
+
$registry->register('chain-test', new ChainTestNode());
|
|
29
|
+
$registry->register('blok-error-demo', new BlokErrorDemoNode());
|
|
30
|
+
$registry->register('typed-greet', new TypedGreetNode());
|
|
31
|
+
|
|
32
|
+
// Discover user nodes scaffolded under runtimes/php/nodes/ (mirrors Python's
|
|
33
|
+
// BLOK_NODES_DIR scan). The CLI sets BLOK_NODES_DIR; each node lives at
|
|
34
|
+
// <dir>/<name>/src/Nodes/<Pascal>Node.php with class
|
|
35
|
+
// Blok\Blok\Nodes\<Pascal>\<Pascal>Node implementing NodeHandler, registered
|
|
36
|
+
// under its directory name. require_once is enough — nodes use the SDK's
|
|
37
|
+
// already-autoloaded classes, so no composer-autoloader merge is needed.
|
|
38
|
+
$nodesDir = getenv('BLOK_NODES_DIR') ?: '';
|
|
39
|
+
if ($nodesDir !== '' && is_dir($nodesDir)) {
|
|
40
|
+
$discovered = 0;
|
|
41
|
+
foreach (glob($nodesDir . '/*/src/Nodes/*Node.php') ?: [] as $file) {
|
|
42
|
+
// ponytail: name = the node's top dir; convention class lives under it.
|
|
43
|
+
$name = basename(dirname($file, 3));
|
|
44
|
+
$pascal = str_replace(' ', '', ucwords(str_replace(['-', '_'], ' ', $name)));
|
|
45
|
+
$class = "Blok\\Blok\\Nodes\\{$pascal}\\{$pascal}Node";
|
|
46
|
+
|
|
47
|
+
require_once $file;
|
|
48
|
+
if (!class_exists($class)) {
|
|
49
|
+
fwrite(STDERR, sprintf("[blok] skipping user node '%s': class %s not found\n", $name, $class));
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
$handler = new $class();
|
|
53
|
+
if (!$handler instanceof NodeHandler) {
|
|
54
|
+
fwrite(STDERR, sprintf("[blok] skipping user node '%s': %s does not implement NodeHandler\n", $name, $class));
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
$registry->register($name, $handler);
|
|
58
|
+
$discovered++;
|
|
59
|
+
}
|
|
60
|
+
if ($discovered > 0) {
|
|
61
|
+
fwrite(STDERR, sprintf("Discovered %d user node(s) from %s\n", $discovered, $nodesDir));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fwrite(
|
|
66
|
+
STDERR,
|
|
67
|
+
sprintf("Registered %d nodes: %s\n", $registry->count(), implode(', ', $registry->nodeNames())),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
switch ($config->transport) {
|
|
71
|
+
case Transport::Grpc:
|
|
72
|
+
// Worker mode: the RoadRunner daemon (started via `rr serve -c .rr.yaml`)
|
|
73
|
+
// handles HTTP/2 + gRPC framing in Go and dispatches each call to this
|
|
74
|
+
// PHP worker over goridge pipes. Server::serve() blocks reading payloads
|
|
75
|
+
// from stdin until rr signals shutdown.
|
|
76
|
+
$service = new BlokNodeRuntimeService($registry, sdkVersion: $config->version);
|
|
77
|
+
$server = new GrpcServer();
|
|
78
|
+
$server->registerService(NodeRuntimeInterface::class, $service);
|
|
79
|
+
fwrite(STDERR, "Blok PHP gRPC worker entering spiral/roadrunner-grpc loop\n");
|
|
80
|
+
$server->serve();
|
|
81
|
+
break;
|
|
82
|
+
|
|
83
|
+
case Transport::Both:
|
|
84
|
+
// PHP cannot run two transports in one process without OS threads.
|
|
85
|
+
// Operators run `rr serve` (gRPC) and `php bin/serve.php` (HTTP) side
|
|
86
|
+
// by side; here we fall through to HTTP so this command still serves
|
|
87
|
+
// half the matrix on its own.
|
|
88
|
+
fwrite(STDERR, "BLOK_TRANSPORT=both: this process serves HTTP only; run `rr serve -c .rr.yaml` separately for gRPC\n");
|
|
89
|
+
// intentional fallthrough
|
|
90
|
+
case Transport::Http:
|
|
91
|
+
default:
|
|
92
|
+
$server = new Server($registry, $config);
|
|
93
|
+
$server->start();
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Entry point for the Blok blok Python3 runtime server.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python bin/serve.py
|
|
6
|
+
|
|
7
|
+
Environment variables:
|
|
8
|
+
PORT HTTP port (default: 9007)
|
|
9
|
+
HOST Bind address (default: 0.0.0.0)
|
|
10
|
+
VERSION Runtime version (default: 1.0.0)
|
|
11
|
+
GRPC_PORT gRPC port (default: 10007)
|
|
12
|
+
BLOK_TRANSPORT "http" | "grpc" | "both" (default: "http")
|
|
13
|
+
LOG_LEVEL DEBUG | INFO | WARN | ERROR (default: INFO)
|
|
14
|
+
ENABLE_CORS true / false (default: false)
|
|
15
|
+
BLOK_GRPC_MAX_MESSAGE_BYTES Max gRPC message size, send+recv (default: 16777216).
|
|
16
|
+
Must match the runner client + other sidecars.
|
|
17
|
+
BLOK_NODES_DIR Directory of user-authored nodes to discover (each node in
|
|
18
|
+
a `<name>/node.py` using the `@node` decorator). Set by
|
|
19
|
+
blokctl to the project's `runtimes/python3/nodes`. When
|
|
20
|
+
unset, only the SDK's built-in example nodes load.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import logging
|
|
24
|
+
import os
|
|
25
|
+
import signal
|
|
26
|
+
import sys
|
|
27
|
+
import threading
|
|
28
|
+
|
|
29
|
+
# Add the SDK root to the path so imports work
|
|
30
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
31
|
+
|
|
32
|
+
from blok.config.server_config import ServerConfig
|
|
33
|
+
from blok.middleware.logging_middleware import logging_middleware
|
|
34
|
+
from blok.middleware.recovery_middleware import recovery_middleware
|
|
35
|
+
from blok.node.node_registry import NodeRegistry
|
|
36
|
+
from blok.server.runtime_server import RuntimeServer
|
|
37
|
+
from examples import register_all
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _start_http(registry: NodeRegistry, config: ServerConfig) -> RuntimeServer:
|
|
41
|
+
"""Start the HTTP runtime server in the calling thread.
|
|
42
|
+
|
|
43
|
+
Returns the started server so the caller can stop it cleanly.
|
|
44
|
+
"""
|
|
45
|
+
server = RuntimeServer(registry, config)
|
|
46
|
+
server.start()
|
|
47
|
+
return server
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _start_grpc(registry: NodeRegistry, config: ServerConfig):
|
|
51
|
+
"""Start the gRPC runtime server. Lazy-imports grpcio so the base SDK
|
|
52
|
+
install doesn't pull it in unless `BLOK_TRANSPORT` selects gRPC.
|
|
53
|
+
|
|
54
|
+
Returns the started ``grpc.Server`` instance.
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
from blok.server.grpc_server import serve_grpc
|
|
58
|
+
except ImportError as exc: # pragma: no cover — depends on optional install
|
|
59
|
+
logging.error(
|
|
60
|
+
"BLOK_TRANSPORT=%s requires grpcio. Install with: pip install 'blok-blok-python3[grpc]'",
|
|
61
|
+
config.transport,
|
|
62
|
+
)
|
|
63
|
+
raise SystemExit(1) from exc
|
|
64
|
+
|
|
65
|
+
# Max gRPC message size (decode + encode), default 16 MiB. MUST match the
|
|
66
|
+
# runner client's BLOK_GRPC_MAX_MESSAGE_BYTES and the other sidecars — a
|
|
67
|
+
# client-only raise leaves this server rejecting oversized messages with
|
|
68
|
+
# RESOURCE_EXHAUSTED. Invalid / non-positive falls back to the default.
|
|
69
|
+
default_max = 16 * 1024 * 1024
|
|
70
|
+
try:
|
|
71
|
+
max_message_bytes = int(os.environ.get("BLOK_GRPC_MAX_MESSAGE_BYTES", default_max))
|
|
72
|
+
if max_message_bytes <= 0:
|
|
73
|
+
max_message_bytes = default_max
|
|
74
|
+
except (TypeError, ValueError):
|
|
75
|
+
max_message_bytes = default_max
|
|
76
|
+
|
|
77
|
+
return serve_grpc(
|
|
78
|
+
registry,
|
|
79
|
+
port=config.grpc_port,
|
|
80
|
+
host=config.host,
|
|
81
|
+
sdk_version=config.version,
|
|
82
|
+
max_message_bytes=max_message_bytes,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _load_user_nodes(registry: NodeRegistry) -> int:
|
|
87
|
+
"""Discover user nodes from ``BLOK_NODES_DIR`` (delegates to the SDK).
|
|
88
|
+
|
|
89
|
+
`@node` authoring requires pydantic; if it isn't installed there are no
|
|
90
|
+
user nodes to load, so a missing import is a no-op.
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
from blok.node.define_node import load_user_nodes
|
|
94
|
+
except ImportError:
|
|
95
|
+
return 0
|
|
96
|
+
return load_user_nodes(registry, os.environ.get("BLOK_NODES_DIR"))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def main():
|
|
100
|
+
config = ServerConfig.from_env()
|
|
101
|
+
|
|
102
|
+
log_level = getattr(logging, config.log_level, logging.INFO)
|
|
103
|
+
logging.basicConfig(
|
|
104
|
+
level=log_level,
|
|
105
|
+
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
106
|
+
stream=sys.stdout,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
registry = NodeRegistry(version=config.version)
|
|
110
|
+
register_all(registry)
|
|
111
|
+
user_node_count = _load_user_nodes(registry)
|
|
112
|
+
registry.use(recovery_middleware)
|
|
113
|
+
registry.use(logging_middleware)
|
|
114
|
+
|
|
115
|
+
transport = config.transport
|
|
116
|
+
log = logging.getLogger("blok.serve")
|
|
117
|
+
log.info(
|
|
118
|
+
"Blok Python3 SDK starting (transport=%s, http_port=%d, grpc_port=%d, %d nodes, %d user)",
|
|
119
|
+
transport,
|
|
120
|
+
config.port,
|
|
121
|
+
config.grpc_port,
|
|
122
|
+
len(registry.node_names()),
|
|
123
|
+
user_node_count,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
if transport == "http":
|
|
127
|
+
_start_http(registry, config)
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
if transport == "grpc":
|
|
131
|
+
grpc_server = _start_grpc(registry, config)
|
|
132
|
+
_block_until_signal_then_stop(grpc=grpc_server, http=None)
|
|
133
|
+
return
|
|
134
|
+
|
|
135
|
+
if transport == "both":
|
|
136
|
+
# Start gRPC in background, HTTP in foreground (HTTP server's
|
|
137
|
+
# ``start()`` blocks until shutdown). On signal we stop gRPC, then
|
|
138
|
+
# the HTTP server unwinds.
|
|
139
|
+
grpc_server = _start_grpc(registry, config)
|
|
140
|
+
http_thread = threading.Thread(
|
|
141
|
+
target=_start_http,
|
|
142
|
+
args=(registry, config),
|
|
143
|
+
daemon=True,
|
|
144
|
+
name="blok-http",
|
|
145
|
+
)
|
|
146
|
+
http_thread.start()
|
|
147
|
+
_block_until_signal_then_stop(grpc=grpc_server, http=None)
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
log.error("Unknown BLOK_TRANSPORT=%r — expected http | grpc | both", transport)
|
|
151
|
+
sys.exit(1)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _block_until_signal_then_stop(*, grpc, http) -> None:
|
|
155
|
+
"""Block on SIGINT/SIGTERM, then stop the gRPC server (and HTTP if owned)."""
|
|
156
|
+
stop_event = threading.Event()
|
|
157
|
+
|
|
158
|
+
def _handler(signum, _frame):
|
|
159
|
+
logging.getLogger("blok.serve").info("Received signal %d, shutting down…", signum)
|
|
160
|
+
stop_event.set()
|
|
161
|
+
|
|
162
|
+
signal.signal(signal.SIGINT, _handler)
|
|
163
|
+
signal.signal(signal.SIGTERM, _handler)
|
|
164
|
+
stop_event.wait()
|
|
165
|
+
|
|
166
|
+
if grpc is not None:
|
|
167
|
+
grpc.stop(grace=3.0)
|
|
168
|
+
# The HTTP server in dual-listen runs in a daemon thread; it tears down
|
|
169
|
+
# automatically on process exit. ``http`` is reserved for future shapes
|
|
170
|
+
# (e.g. promoting HTTP into a stoppable abstraction).
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
if __name__ == "__main__":
|
|
174
|
+
main()
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Entry point for the Blok NATS JetStream worker.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python bin/worker.py
|
|
6
|
+
|
|
7
|
+
Environment variables:
|
|
8
|
+
NATS_SERVERS - Comma-separated NATS server URLs (default: localhost:4222)
|
|
9
|
+
NATS_TOKEN - Authentication token (optional)
|
|
10
|
+
NATS_USER - Username (optional)
|
|
11
|
+
NATS_PASS - Password (optional)
|
|
12
|
+
NATS_STREAM_NAME - JetStream stream name (default: blok-worker)
|
|
13
|
+
WORKER_CONCURRENCY - Max concurrent jobs (default: 1)
|
|
14
|
+
WORKER_MAX_RETRIES - Max delivery attempts (default: 3)
|
|
15
|
+
WORKER_ACK_WAIT_SECS - Job timeout in seconds (default: 30)
|
|
16
|
+
WORKER_QUEUES - Comma-separated queue names
|
|
17
|
+
PORT - HTTP health server port (default: 8080)
|
|
18
|
+
VERSION - Runtime version (default: 1.0.0)
|
|
19
|
+
LOG_LEVEL - Log level: DEBUG, INFO, WARN, ERROR (default: INFO)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import logging
|
|
23
|
+
import os
|
|
24
|
+
import sys
|
|
25
|
+
|
|
26
|
+
# Add the SDK root to the path so imports work
|
|
27
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
28
|
+
|
|
29
|
+
from blok.config.server_config import ServerConfig
|
|
30
|
+
from blok.middleware.logging_middleware import logging_middleware
|
|
31
|
+
from blok.middleware.recovery_middleware import recovery_middleware
|
|
32
|
+
from blok.node.node_registry import NodeRegistry
|
|
33
|
+
from blok.worker import listen_and_serve_worker
|
|
34
|
+
from examples import register_all
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def main():
|
|
38
|
+
# Configure logging
|
|
39
|
+
config = ServerConfig.from_env()
|
|
40
|
+
log_level = getattr(logging, config.log_level, logging.INFO)
|
|
41
|
+
logging.basicConfig(
|
|
42
|
+
level=log_level,
|
|
43
|
+
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
44
|
+
stream=sys.stdout,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Create registry and register nodes
|
|
48
|
+
registry = NodeRegistry(version=config.version)
|
|
49
|
+
register_all(registry)
|
|
50
|
+
|
|
51
|
+
# Add middleware
|
|
52
|
+
registry.use(recovery_middleware)
|
|
53
|
+
registry.use(logging_middleware)
|
|
54
|
+
|
|
55
|
+
# Start worker (blocks until shutdown)
|
|
56
|
+
listen_and_serve_worker(registry)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
main()
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Entry point for the Blok Ruby runtime.
|
|
5
|
+
#
|
|
6
|
+
# Boots the configured server(s) based on +BLOK_TRANSPORT+:
|
|
7
|
+
# - "http" (default): Sinatra HTTP server only.
|
|
8
|
+
# - "grpc": gRPC server only on +GRPC_PORT+.
|
|
9
|
+
# - "both": HTTP in foreground + gRPC in a background thread (dual-listen).
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# ruby bin/serve.rb
|
|
13
|
+
#
|
|
14
|
+
# Environment:
|
|
15
|
+
# PORT HTTP port (default: 9006)
|
|
16
|
+
# GRPC_PORT gRPC port (default: 10006)
|
|
17
|
+
# BLOK_TRANSPORT "http" | "grpc" | "both" (default: "http")
|
|
18
|
+
# HOST Bind address (default: 0.0.0.0)
|
|
19
|
+
# LOG_LEVEL DEBUG | INFO | WARN | ERROR (default: INFO)
|
|
20
|
+
# BLOK_NODES_DIR Directory of user-authored nodes to discover (each in a
|
|
21
|
+
# `<name>/node.rb` subclassing Blok::Node::NodeHandler). Set by
|
|
22
|
+
# blokctl to the project's `runtimes/ruby/nodes`. When unset,
|
|
23
|
+
# only the SDK's built-in example nodes load.
|
|
24
|
+
|
|
25
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
26
|
+
|
|
27
|
+
require "blok"
|
|
28
|
+
require_relative "../examples/hello_world_node"
|
|
29
|
+
require_relative "../examples/api_call_node"
|
|
30
|
+
require_relative "../examples/transform_data_node"
|
|
31
|
+
require_relative "../examples/chain_test_node"
|
|
32
|
+
require_relative "../examples/blok_error_demo_node"
|
|
33
|
+
require_relative "../examples/typed_greet_node"
|
|
34
|
+
|
|
35
|
+
config = Blok::Config::ServerConfig.from_env
|
|
36
|
+
|
|
37
|
+
registry = Blok::Node::NodeRegistry.new(config.version)
|
|
38
|
+
registry.register("hello-world", HelloWorldNode.new)
|
|
39
|
+
registry.register("api-call", ApiCallNode.new)
|
|
40
|
+
registry.register("transform-data", TransformDataNode.new)
|
|
41
|
+
registry.register("chain-test", ChainTestNode.new)
|
|
42
|
+
registry.register("blok-error-demo", BlokErrorDemoNode.new)
|
|
43
|
+
registry.register("typed-greet", TypedGreetNode.new)
|
|
44
|
+
|
|
45
|
+
# Discover user-authored nodes from BLOK_NODES_DIR (set by blokctl to the
|
|
46
|
+
# project's runtimes/ruby/nodes). Each lives in <name>/node.rb subclassing
|
|
47
|
+
# Blok::Node::NodeHandler; no manual registration. Unset => built-ins only.
|
|
48
|
+
user_node_count = Blok::Node.load_user_nodes(registry, ENV["BLOK_NODES_DIR"])
|
|
49
|
+
|
|
50
|
+
logger = Blok::Logging::Logger.new(config.log_level)
|
|
51
|
+
registry.use(Blok::Middleware::RecoveryMiddleware.new)
|
|
52
|
+
registry.use(Blok::Middleware::LoggingMiddleware.new(logger))
|
|
53
|
+
|
|
54
|
+
warn "Blok Ruby SDK starting (transport=#{config.transport}, http_port=#{config.port}, " \
|
|
55
|
+
"grpc_port=#{config.grpc_port}, #{registry.node_names.length} nodes, #{user_node_count} user)"
|
|
56
|
+
|
|
57
|
+
# Lazy-load the gRPC server only when we actually need it. This keeps the
|
|
58
|
+
# HTTP-only path free of grpc dependency load time.
|
|
59
|
+
def start_grpc(registry, config, blocking:)
|
|
60
|
+
require_relative "../lib/blok/server/grpc_server"
|
|
61
|
+
Blok::Server::GrpcServer.new(
|
|
62
|
+
registry,
|
|
63
|
+
port: config.grpc_port,
|
|
64
|
+
host: config.host,
|
|
65
|
+
sdk_version: config.version
|
|
66
|
+
).tap { |s| s.start(blocking: blocking) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def start_http(registry, config)
|
|
70
|
+
Blok::Server::RuntimeApp.registry = registry
|
|
71
|
+
# Sinatra/Puma: bind on host:port and run in the foreground.
|
|
72
|
+
require "rackup"
|
|
73
|
+
app = Rack::Builder.new { run Blok::Server::RuntimeApp }.to_app
|
|
74
|
+
Rackup::Server.start(
|
|
75
|
+
app: app,
|
|
76
|
+
server: "puma",
|
|
77
|
+
Host: config.host,
|
|
78
|
+
Port: config.port,
|
|
79
|
+
quiet: false
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
case config.transport
|
|
84
|
+
when Blok::Config::ServerConfig::Transport::HTTP
|
|
85
|
+
start_http(registry, config)
|
|
86
|
+
when Blok::Config::ServerConfig::Transport::GRPC
|
|
87
|
+
grpc_server = start_grpc(registry, config, blocking: false)
|
|
88
|
+
# Hand the stop signal off the trap context to the main thread.
|
|
89
|
+
# `GrpcServer#stop` calls `RpcServer#stop` which acquires a Mutex,
|
|
90
|
+
# and `Mutex#synchronize` raises `ThreadError: can't be called from
|
|
91
|
+
# trap context` under Ruby 3.x. `Queue#<<` is signal-safe in MRI.
|
|
92
|
+
stop_queue = Queue.new
|
|
93
|
+
trap("INT") { stop_queue << "INT" }
|
|
94
|
+
trap("TERM") { stop_queue << "TERM" }
|
|
95
|
+
stop_queue.pop
|
|
96
|
+
grpc_server.stop
|
|
97
|
+
exit 0
|
|
98
|
+
when Blok::Config::ServerConfig::Transport::BOTH
|
|
99
|
+
grpc_server = start_grpc(registry, config, blocking: false)
|
|
100
|
+
at_exit { grpc_server.stop }
|
|
101
|
+
start_http(registry, config)
|
|
102
|
+
else
|
|
103
|
+
warn "Unknown transport: #{config.transport}"
|
|
104
|
+
exit 1
|
|
105
|
+
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blokjs/trigger-cron",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"files": ["dist"],
|
|
5
5
|
"description": "Cron/scheduled trigger for Blok workflows - supports cron expressions and interval-based scheduling",
|
|
6
6
|
"type": "module",
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
"author": "Deskree Technologies Inc.",
|
|
16
16
|
"license": "Apache-2.0",
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@blokjs/helper": "^1.4.
|
|
19
|
-
"@blokjs/runner": "^1.4.
|
|
20
|
-
"@blokjs/shared": "^1.4.
|
|
18
|
+
"@blokjs/helper": "^1.4.1",
|
|
19
|
+
"@blokjs/runner": "^1.4.1",
|
|
20
|
+
"@blokjs/shared": "^1.4.1",
|
|
21
21
|
"@opentelemetry/api": "^1.9.0",
|
|
22
22
|
"cron": "^4.4.0",
|
|
23
23
|
"uuid": "^11.1.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blokjs/trigger-grpc",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"files": ["dist"],
|
|
5
5
|
"description": "",
|
|
6
6
|
"type": "module",
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"@connectrpc/connect": "^2.1.1",
|
|
37
37
|
"@connectrpc/connect-fastify": "^2.1.1",
|
|
38
38
|
"@connectrpc/connect-node": "^2.1.1",
|
|
39
|
-
"@blokjs/api-call": "^1.4.
|
|
40
|
-
"@blokjs/helper": "^1.4.
|
|
41
|
-
"@blokjs/if-else": "^1.4.
|
|
42
|
-
"@blokjs/runner": "^1.4.
|
|
43
|
-
"@blokjs/shared": "^1.4.
|
|
39
|
+
"@blokjs/api-call": "^1.4.1",
|
|
40
|
+
"@blokjs/helper": "^1.4.1",
|
|
41
|
+
"@blokjs/if-else": "^1.4.1",
|
|
42
|
+
"@blokjs/runner": "^1.4.1",
|
|
43
|
+
"@blokjs/shared": "^1.4.1",
|
|
44
44
|
"@opentelemetry/api": "^1.9.0",
|
|
45
45
|
"@opentelemetry/exporter-prometheus": "^0.57.2",
|
|
46
46
|
"@opentelemetry/resources": "^1.30.1",
|
|
@@ -31,17 +31,17 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@ai-sdk/openai": "^1.3.22",
|
|
34
|
-
"@blokjs/api-call": "^1.4.
|
|
35
|
-
"@blokjs/core": "^1.4.
|
|
36
|
-
"@blokjs/helper": "^1.4.
|
|
37
|
-
"@blokjs/helpers": "^1.4.
|
|
38
|
-
"@blokjs/if-else": "^1.4.
|
|
39
|
-
"@blokjs/runner": "^1.4.
|
|
40
|
-
"@blokjs/shared": "^1.4.
|
|
41
|
-
"@blokjs/trigger-mcp": "^1.4.
|
|
42
|
-
"@blokjs/trigger-sse": "^1.4.
|
|
43
|
-
"@blokjs/trigger-webhook": "^1.4.
|
|
44
|
-
"@blokjs/trigger-websocket": "^1.4.
|
|
34
|
+
"@blokjs/api-call": "^1.4.1",
|
|
35
|
+
"@blokjs/core": "^1.4.1",
|
|
36
|
+
"@blokjs/helper": "^1.4.1",
|
|
37
|
+
"@blokjs/helpers": "^1.4.1",
|
|
38
|
+
"@blokjs/if-else": "^1.4.1",
|
|
39
|
+
"@blokjs/runner": "^1.4.1",
|
|
40
|
+
"@blokjs/shared": "^1.4.1",
|
|
41
|
+
"@blokjs/trigger-mcp": "^1.4.1",
|
|
42
|
+
"@blokjs/trigger-sse": "^1.4.1",
|
|
43
|
+
"@blokjs/trigger-webhook": "^1.4.1",
|
|
44
|
+
"@blokjs/trigger-websocket": "^1.4.1",
|
|
45
45
|
"@hono/node-server": "^1.19.9",
|
|
46
46
|
"@opentelemetry/api": "^1.9.0",
|
|
47
47
|
"@opentelemetry/exporter-prometheus": "^0.57.2",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blokjs/trigger-mcp",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"files": ["dist"],
|
|
5
5
|
"description": "Model Context Protocol (MCP) trigger for Blok workflows — expose workflows as MCP tools over SSE + Streamable-HTTP on the shared Hono port.",
|
|
6
6
|
"type": "module",
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
"author": "Deskree Technologies Inc.",
|
|
16
16
|
"license": "Apache-2.0",
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@blokjs/helper": "^1.4.
|
|
19
|
-
"@blokjs/runner": "^1.4.
|
|
20
|
-
"@blokjs/shared": "^1.4.
|
|
18
|
+
"@blokjs/helper": "^1.4.1",
|
|
19
|
+
"@blokjs/runner": "^1.4.1",
|
|
20
|
+
"@blokjs/shared": "^1.4.1",
|
|
21
21
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
22
22
|
"@opentelemetry/api": "^1.9.0",
|
|
23
23
|
"hono": "^4.11.7",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blokjs/trigger-pubsub",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"files": ["dist"],
|
|
5
5
|
"description": "Pub/Sub trigger for Blok workflows — supports NATS (Core + JetStream), Redis Streams, Kafka, GCP Pub/Sub, AWS SNS+SQS, and Azure Service Bus.",
|
|
6
6
|
"type": "module",
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
"author": "Deskree Technologies Inc.",
|
|
16
16
|
"license": "Apache-2.0",
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@blokjs/helper": "^1.4.
|
|
19
|
-
"@blokjs/runner": "^1.4.
|
|
20
|
-
"@blokjs/shared": "^1.4.
|
|
18
|
+
"@blokjs/helper": "^1.4.1",
|
|
19
|
+
"@blokjs/runner": "^1.4.1",
|
|
20
|
+
"@blokjs/shared": "^1.4.1",
|
|
21
21
|
"@opentelemetry/api": "^1.9.0",
|
|
22
22
|
"uuid": "^11.1.0"
|
|
23
23
|
},
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
"vitest": "^4.0.18"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@blokjs/api-call": "^1.4.
|
|
29
|
-
"@blokjs/core": "^1.4.
|
|
30
|
-
"@blokjs/helper": "^1.4.
|
|
31
|
-
"@blokjs/if-else": "^1.4.
|
|
32
|
-
"@blokjs/runner": "^1.4.
|
|
33
|
-
"@blokjs/shared": "^1.4.
|
|
34
|
-
"@blokjs/trigger-pubsub": "^1.4.
|
|
28
|
+
"@blokjs/api-call": "^1.4.1",
|
|
29
|
+
"@blokjs/core": "^1.4.1",
|
|
30
|
+
"@blokjs/helper": "^1.4.1",
|
|
31
|
+
"@blokjs/if-else": "^1.4.1",
|
|
32
|
+
"@blokjs/runner": "^1.4.1",
|
|
33
|
+
"@blokjs/shared": "^1.4.1",
|
|
34
|
+
"@blokjs/trigger-pubsub": "^1.4.1",
|
|
35
35
|
"@opentelemetry/api": "^1.9.0",
|
|
36
36
|
"@opentelemetry/exporter-prometheus": "^0.57.2",
|
|
37
37
|
"@opentelemetry/resources": "^1.30.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blokjs/trigger-sse",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"files": ["dist"],
|
|
5
5
|
"description": "Server-Sent Events (SSE) trigger for Blok workflows — real-time server push on the shared Hono port.",
|
|
6
6
|
"type": "module",
|
|
@@ -15,15 +15,15 @@
|
|
|
15
15
|
"author": "Deskree Technologies Inc.",
|
|
16
16
|
"license": "Apache-2.0",
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@blokjs/helper": "^1.4.
|
|
19
|
-
"@blokjs/runner": "^1.4.
|
|
20
|
-
"@blokjs/shared": "^1.4.
|
|
18
|
+
"@blokjs/helper": "^1.4.1",
|
|
19
|
+
"@blokjs/runner": "^1.4.1",
|
|
20
|
+
"@blokjs/shared": "^1.4.1",
|
|
21
21
|
"@opentelemetry/api": "^1.9.0",
|
|
22
22
|
"hono": "^4.11.7",
|
|
23
23
|
"uuid": "^11.1.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@blokjs/core": "^1.4.
|
|
26
|
+
"@blokjs/core": "^1.4.1",
|
|
27
27
|
"@hono/node-server": "^1.19.9",
|
|
28
28
|
"@types/node": "^22.15.21",
|
|
29
29
|
"@types/uuid": "^11.0.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blokjs/trigger-webhook",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"files": ["dist"],
|
|
5
5
|
"description": "Webhook trigger for Blok workflows - supports GitHub, Stripe, Shopify, and custom webhooks",
|
|
6
6
|
"type": "module",
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
"author": "Deskree Technologies Inc.",
|
|
16
16
|
"license": "Apache-2.0",
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@blokjs/helper": "^1.4.
|
|
19
|
-
"@blokjs/runner": "^1.4.
|
|
20
|
-
"@blokjs/shared": "^1.4.
|
|
18
|
+
"@blokjs/helper": "^1.4.1",
|
|
19
|
+
"@blokjs/runner": "^1.4.1",
|
|
20
|
+
"@blokjs/shared": "^1.4.1",
|
|
21
21
|
"@opentelemetry/api": "^1.9.0",
|
|
22
22
|
"hono": "^4.11.7",
|
|
23
23
|
"uuid": "^11.1.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blokjs/trigger-websocket",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"files": ["dist"],
|
|
5
5
|
"description": "WebSocket trigger for Blok workflows - real-time bidirectional communication",
|
|
6
6
|
"type": "module",
|
|
@@ -15,15 +15,15 @@
|
|
|
15
15
|
"author": "Deskree Technologies Inc.",
|
|
16
16
|
"license": "Apache-2.0",
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@blokjs/helper": "^1.4.
|
|
19
|
-
"@blokjs/runner": "^1.4.
|
|
20
|
-
"@blokjs/shared": "^1.4.
|
|
18
|
+
"@blokjs/helper": "^1.4.1",
|
|
19
|
+
"@blokjs/runner": "^1.4.1",
|
|
20
|
+
"@blokjs/shared": "^1.4.1",
|
|
21
21
|
"@hono/node-ws": "^1.3.1",
|
|
22
22
|
"@opentelemetry/api": "^1.9.0",
|
|
23
23
|
"uuid": "^11.1.0"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
26
|
-
"@blokjs/trigger-pubsub": "^1.4.
|
|
26
|
+
"@blokjs/trigger-pubsub": "^1.4.1",
|
|
27
27
|
"ws": "^8.19.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependenciesMeta": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@blokjs/core": "^1.4.
|
|
38
|
+
"@blokjs/core": "^1.4.1",
|
|
39
39
|
"@types/node": "^22.15.21",
|
|
40
40
|
"@types/uuid": "^11.0.0",
|
|
41
41
|
"@types/ws": "^8.18.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blokjs/trigger-worker",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"files": ["dist"],
|
|
5
5
|
"description": "Worker-based trigger for Blok workflows - supports background job processing with concurrency, retries, and scheduling",
|
|
6
6
|
"type": "module",
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
"author": "Deskree Technologies Inc.",
|
|
16
16
|
"license": "Apache-2.0",
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@blokjs/helper": "^1.4.
|
|
19
|
-
"@blokjs/runner": "^1.4.
|
|
20
|
-
"@blokjs/shared": "^1.4.
|
|
18
|
+
"@blokjs/helper": "^1.4.1",
|
|
19
|
+
"@blokjs/runner": "^1.4.1",
|
|
20
|
+
"@blokjs/shared": "^1.4.1",
|
|
21
21
|
"@opentelemetry/api": "^1.9.0",
|
|
22
22
|
"uuid": "^11.1.0"
|
|
23
23
|
},
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
"vitest": "^4.0.18"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@blokjs/api-call": "^1.4.
|
|
29
|
-
"@blokjs/core": "^1.4.
|
|
30
|
-
"@blokjs/helper": "^1.4.
|
|
31
|
-
"@blokjs/if-else": "^1.4.
|
|
32
|
-
"@blokjs/runner": "^1.4.
|
|
33
|
-
"@blokjs/shared": "^1.4.
|
|
34
|
-
"@blokjs/trigger-worker": "^1.4.
|
|
28
|
+
"@blokjs/api-call": "^1.4.1",
|
|
29
|
+
"@blokjs/core": "^1.4.1",
|
|
30
|
+
"@blokjs/helper": "^1.4.1",
|
|
31
|
+
"@blokjs/if-else": "^1.4.1",
|
|
32
|
+
"@blokjs/runner": "^1.4.1",
|
|
33
|
+
"@blokjs/shared": "^1.4.1",
|
|
34
|
+
"@blokjs/trigger-worker": "^1.4.1",
|
|
35
35
|
"@opentelemetry/api": "^1.9.0",
|
|
36
36
|
"@opentelemetry/exporter-prometheus": "^0.57.2",
|
|
37
37
|
"@opentelemetry/resources": "^1.30.1",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blokctl",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"author": "Deskree Technologies Inc.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"description": "cli for blok",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"keywords": ["blokctl", "cli", "blok", "blok"],
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@ai-sdk/openai": "^1.3.22",
|
|
33
|
-
"@blokjs/helper": "^1.4.
|
|
34
|
-
"@blokjs/runner": "^1.4.
|
|
33
|
+
"@blokjs/helper": "^1.4.1",
|
|
34
|
+
"@blokjs/runner": "^1.4.1",
|
|
35
35
|
"@clack/prompts": "^1.0.0",
|
|
36
36
|
"ai": "^4.3.16",
|
|
37
37
|
"better-sqlite3": "^12.6.2",
|