blokctl 1.3.0 → 1.4.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/commands/create/project.js +15 -4
- package/dist/commands/create/utils/Examples.d.ts +1 -1
- package/dist/commands/create/utils/Examples.js +1 -1
- package/dist/commands/runtime/shared.js +4 -0
- package/dist/scaffold-repo/examples/ts-workflows/mcp-greeter.ts +59 -0
- package/dist/scaffold-repo/examples/ts-workflows/runtime-csharp-hello.ts +36 -0
- package/dist/scaffold-repo/examples/ts-workflows/runtime-go-hello.ts +36 -0
- package/dist/scaffold-repo/examples/ts-workflows/runtime-java-hello.ts +36 -0
- package/dist/scaffold-repo/examples/ts-workflows/runtime-php-hello.ts +36 -0
- package/dist/scaffold-repo/examples/ts-workflows/runtime-python3-hello.ts +36 -0
- package/dist/scaffold-repo/examples/ts-workflows/runtime-ruby-hello.ts +36 -0
- package/dist/scaffold-repo/examples/ts-workflows/runtime-rust-hello.ts +36 -0
- package/dist/scaffold-repo/infra/development/3055.dat +602 -0
- package/dist/scaffold-repo/infra/development/3057.dat +203 -0
- package/dist/scaffold-repo/infra/development/3059.dat +19 -0
- package/dist/scaffold-repo/infra/development/3061.dat +1003 -0
- package/dist/scaffold-repo/infra/development/3062.dat +5465 -0
- package/dist/scaffold-repo/infra/development/3063.dat +1003 -0
- package/dist/scaffold-repo/infra/development/3065.dat +606 -0
- package/dist/scaffold-repo/infra/development/3067.dat +603 -0
- package/dist/scaffold-repo/infra/development/3069.dat +112 -0
- package/dist/scaffold-repo/infra/development/3071.dat +4584 -0
- package/dist/scaffold-repo/infra/development/3073.dat +9 -0
- package/dist/scaffold-repo/infra/development/3075.dat +14599 -0
- package/dist/scaffold-repo/infra/development/3077.dat +16047 -0
- package/dist/scaffold-repo/infra/development/3079.dat +5 -0
- package/dist/scaffold-repo/infra/development/3081.dat +5 -0
- package/dist/scaffold-repo/infra/development/docker-compose.yml +106 -0
- package/dist/scaffold-repo/infra/development/schema.sql +1616 -0
- package/dist/scaffold-repo/infra/development/toc.dat +0 -0
- package/dist/scaffold-repo/infra/metrics/alertmanager.yml +43 -0
- package/dist/scaffold-repo/infra/metrics/alloy-config.alloy +75 -0
- package/dist/scaffold-repo/infra/metrics/dashboard.json +1152 -0
- package/dist/scaffold-repo/infra/metrics/dashboards/blok-overview.json +314 -0
- package/dist/scaffold-repo/infra/metrics/dashboards/blok-system.json +175 -0
- package/dist/scaffold-repo/infra/metrics/dashboards/blok-tracing.json +235 -0
- package/dist/scaffold-repo/infra/metrics/dashboards/blok-triggers.json +258 -0
- package/dist/scaffold-repo/infra/metrics/datasources.yml +43 -0
- package/dist/scaffold-repo/infra/metrics/default.yaml +8 -0
- package/dist/scaffold-repo/infra/metrics/docker-compose.yml +193 -0
- package/dist/scaffold-repo/infra/metrics/loki-config.yaml +50 -0
- package/dist/scaffold-repo/infra/metrics/nginx.conf +25 -0
- package/dist/scaffold-repo/infra/metrics/prometheus.yml +51 -0
- package/dist/scaffold-repo/infra/metrics/rules/blok-alerts.yml +114 -0
- package/dist/scaffold-repo/infra/metrics/telegraf.conf +41 -0
- package/dist/scaffold-repo/infra/metrics/tempo.yaml +81 -0
- package/dist/scaffold-repo/infra/milvus/docker-compose.yml +73 -0
- package/dist/scaffold-repo/sdk/javascript/README.md +151 -0
- package/dist/scaffold-repo/sdk/javascript/bootstrap.min.css +11660 -0
- package/dist/scaffold-repo/sdk/javascript/image-ai.html +316 -0
- package/dist/scaffold-repo/sdk/javascript/nanosdk.js +180 -0
- package/dist/scaffold-repo/sdk/javascript/nanosdk.min.js +100 -0
- package/dist/scaffold-repo/sdk/javascript/pdf.html +63 -0
- package/dist/scaffold-repo/sdk/javascript/test.html +68 -0
- package/dist/scaffold-repo/sdks/CLAUDE.md +67 -0
- package/dist/scaffold-repo/sdks/csharp/.dockerignore +7 -0
- package/dist/scaffold-repo/sdks/csharp/Blok.Core.sln +24 -0
- package/dist/scaffold-repo/sdks/csharp/Dockerfile +42 -0
- package/dist/scaffold-repo/sdks/csharp/README.md +138 -0
- package/dist/scaffold-repo/sdks/csharp/proto/blok/runtime/v1/runtime.proto +302 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Blok.Core.csproj +39 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Config/ServerConfig.cs +90 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Errors/BlokError.cs +538 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Errors/BlokErrorCategory.cs +112 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Errors/BlokErrorSeverity.cs +40 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Errors/BuildContextSnapshot.cs +168 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Errors/ErrorCategory.cs +13 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Errors/NodeException.cs +44 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Logging/LogEntry.cs +34 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Logging/LogLevel.cs +12 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Logging/Logger.cs +92 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Middleware/IMiddleware.cs +14 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Middleware/LoggingMiddleware.cs +46 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Middleware/MiddlewareChain.cs +35 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Middleware/RecoveryMiddleware.cs +63 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Node/INodeHandler.cs +17 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Node/NodeRegistry.cs +128 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Node/TypedNode.cs +140 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Nodes/ApiCallNode.cs +129 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Nodes/BlokErrorDemoNode.cs +125 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Nodes/ChainTestNode.cs +79 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Nodes/HelloWorldNode.cs +45 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Nodes/TransformDataNode.cs +119 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Nodes/TypedGreetNode.cs +23 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Program.cs +26 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Server/BlokNodeRuntimeService.cs +536 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Server/RuntimeServer.cs +183 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Testing/MockContextBuilder.cs +117 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Testing/TestNodeRunner.cs +50 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Types/Context.cs +56 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Types/ExecutionMetrics.cs +21 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Types/ExecutionRequest.cs +15 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Types/ExecutionResult.cs +104 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Types/HealthStatus.cs +18 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Types/NodeConfig.cs +59 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Types/Request.cs +57 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Types/Response.cs +24 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/UserNodeRegistry.cs +17 -0
- package/dist/scaffold-repo/sdks/csharp/src/Blok.Core/Validation/SchemaValidator.cs +198 -0
- package/dist/scaffold-repo/sdks/csharp/tests/Blok.Core.Tests/Blok.Core.Tests.csproj +25 -0
- package/dist/scaffold-repo/sdks/csharp/tests/Blok.Core.Tests/Errors/BlokErrorTests.cs +458 -0
- package/dist/scaffold-repo/sdks/csharp/tests/Blok.Core.Tests/Logging/LoggerTests.cs +129 -0
- package/dist/scaffold-repo/sdks/csharp/tests/Blok.Core.Tests/Node/NodeRegistryTests.cs +145 -0
- package/dist/scaffold-repo/sdks/csharp/tests/Blok.Core.Tests/Testing/MockContextTests.cs +153 -0
- package/dist/scaffold-repo/sdks/csharp/tests/Blok.Core.Tests/TypedNodeTests.cs +69 -0
- package/dist/scaffold-repo/sdks/csharp/tests/Blok.Core.Tests/Types/ExecutionResultTests.cs +110 -0
- package/dist/scaffold-repo/sdks/csharp/tests/Blok.Core.Tests/Validation/SchemaValidatorTests.cs +233 -0
- package/dist/scaffold-repo/sdks/go/Dockerfile +33 -0
- package/dist/scaffold-repo/sdks/go/README.md +255 -0
- package/dist/scaffold-repo/sdks/go/blok_error.go +803 -0
- package/dist/scaffold-repo/sdks/go/blok_error_test.go +433 -0
- package/dist/scaffold-repo/sdks/go/cmd/server/main.go +45 -0
- package/dist/scaffold-repo/sdks/go/cmd/server/register_user_nodes.go +12 -0
- package/dist/scaffold-repo/sdks/go/config.go +157 -0
- package/dist/scaffold-repo/sdks/go/config_test.go +129 -0
- package/dist/scaffold-repo/sdks/go/define_node.go +87 -0
- package/dist/scaffold-repo/sdks/go/define_node_test.go +75 -0
- package/dist/scaffold-repo/sdks/go/errors.go +104 -0
- package/dist/scaffold-repo/sdks/go/errors_test.go +95 -0
- package/dist/scaffold-repo/sdks/go/examples/nodes/apicall.go +117 -0
- package/dist/scaffold-repo/sdks/go/examples/nodes/blokerrordemo.go +80 -0
- package/dist/scaffold-repo/sdks/go/examples/nodes/chaintest.go +63 -0
- package/dist/scaffold-repo/sdks/go/examples/nodes/helloworld.go +41 -0
- package/dist/scaffold-repo/sdks/go/examples/nodes/transformdata.go +176 -0
- package/dist/scaffold-repo/sdks/go/examples/nodes/typedgreet.go +34 -0
- package/dist/scaffold-repo/sdks/go/genpb/blok/runtime/v1/runtime.pb.go +2070 -0
- package/dist/scaffold-repo/sdks/go/genpb/blok/runtime/v1/runtime_grpc.pb.go +273 -0
- package/dist/scaffold-repo/sdks/go/go.mod +25 -0
- package/dist/scaffold-repo/sdks/go/go.sum +66 -0
- package/dist/scaffold-repo/sdks/go/grpc_server.go +670 -0
- package/dist/scaffold-repo/sdks/go/grpc_server_test.go +474 -0
- package/dist/scaffold-repo/sdks/go/logging.go +144 -0
- package/dist/scaffold-repo/sdks/go/logging_test.go +139 -0
- package/dist/scaffold-repo/sdks/go/middleware.go +152 -0
- package/dist/scaffold-repo/sdks/go/middleware_test.go +222 -0
- package/dist/scaffold-repo/sdks/go/node.go +50 -0
- package/dist/scaffold-repo/sdks/go/proto/blok/runtime/v1/runtime.proto +302 -0
- package/dist/scaffold-repo/sdks/go/registry.go +156 -0
- package/dist/scaffold-repo/sdks/go/registry_test.go +195 -0
- package/dist/scaffold-repo/sdks/go/server.go +207 -0
- package/dist/scaffold-repo/sdks/go/server_test.go +186 -0
- package/dist/scaffold-repo/sdks/go/shutdown.go +32 -0
- package/dist/scaffold-repo/sdks/go/testutil.go +169 -0
- package/dist/scaffold-repo/sdks/go/testutil_test.go +124 -0
- package/dist/scaffold-repo/sdks/go/types.go +295 -0
- package/dist/scaffold-repo/sdks/go/types_test.go +251 -0
- package/dist/scaffold-repo/sdks/go/validation.go +210 -0
- package/dist/scaffold-repo/sdks/go/validation_test.go +262 -0
- package/dist/scaffold-repo/sdks/go/worker.go +577 -0
- package/dist/scaffold-repo/sdks/java/Dockerfile +33 -0
- package/dist/scaffold-repo/sdks/java/README.md +221 -0
- package/dist/scaffold-repo/sdks/java/pom.xml +163 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/Main.java +90 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/UserNodeRegistry.java +19 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/config/ServerConfig.java +244 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/errors/BlokError.java +576 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/errors/BlokErrorCategory.java +68 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/errors/BlokErrorSeverity.java +31 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/errors/BuildContextSnapshot.java +132 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/errors/ErrorCategory.java +22 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/errors/NodeException.java +176 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/logging/LogEntry.java +78 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/logging/LogLevel.java +48 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/logging/Logger.java +160 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/middleware/LoggingMiddleware.java +65 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/middleware/Middleware.java +29 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/middleware/MiddlewareChain.java +59 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/middleware/RecoveryMiddleware.java +53 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/node/NodeHandler.java +40 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/node/NodeReflector.java +18 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/node/NodeRegistry.java +205 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/node/TypedNode.java +143 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/nodes/ApiCallNode.java +145 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/nodes/BlokErrorDemoNode.java +109 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/nodes/ChainTestNode.java +65 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/nodes/HelloWorldNode.java +62 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/nodes/TransformDataNode.java +121 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/nodes/TypedGreetNode.java +41 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/server/BlokGrpcServer.java +103 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/server/BlokNodeRuntimeService.java +501 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/server/RuntimeServer.java +176 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/testing/MockContext.java +120 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/testing/TestRunner.java +59 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/types/Context.java +167 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/types/ExecutionMetrics.java +80 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/types/ExecutionRequest.java +61 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/types/ExecutionResult.java +188 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/types/HealthStatus.java +77 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/types/NodeConfig.java +139 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/types/Request.java +154 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/types/Response.java +77 -0
- package/dist/scaffold-repo/sdks/java/src/main/java/com/blok/blok/validation/SchemaValidator.java +178 -0
- package/dist/scaffold-repo/sdks/java/src/main/proto/blok/runtime/v1/runtime.proto +302 -0
- package/dist/scaffold-repo/sdks/java/src/test/java/com/blok/blok/errors/BlokErrorTest.java +416 -0
- package/dist/scaffold-repo/sdks/java/src/test/java/com/blok/blok/logging/LoggerTest.java +176 -0
- package/dist/scaffold-repo/sdks/java/src/test/java/com/blok/blok/node/NodeRegistryTest.java +227 -0
- package/dist/scaffold-repo/sdks/java/src/test/java/com/blok/blok/node/TypedNodeTest.java +76 -0
- package/dist/scaffold-repo/sdks/java/src/test/java/com/blok/blok/server/RuntimeServerTest.java +197 -0
- package/dist/scaffold-repo/sdks/java/src/test/java/com/blok/blok/types/ExecutionResultTest.java +194 -0
- package/dist/scaffold-repo/sdks/java/src/test/java/com/blok/blok/validation/SchemaValidatorTest.java +246 -0
- package/dist/scaffold-repo/sdks/php/.rr.yaml +29 -0
- package/dist/scaffold-repo/sdks/php/Dockerfile +61 -0
- package/dist/scaffold-repo/sdks/php/README.md +387 -0
- package/dist/scaffold-repo/sdks/php/composer.json +41 -0
- package/dist/scaffold-repo/sdks/php/examples/ApiCallNode.php +103 -0
- package/dist/scaffold-repo/sdks/php/examples/BlokErrorDemoNode.php +90 -0
- package/dist/scaffold-repo/sdks/php/examples/ChainTestNode.php +56 -0
- package/dist/scaffold-repo/sdks/php/examples/HelloWorldNode.php +39 -0
- package/dist/scaffold-repo/sdks/php/examples/TransformDataNode.php +94 -0
- package/dist/scaffold-repo/sdks/php/examples/TypedGreetNode.php +57 -0
- package/dist/scaffold-repo/sdks/php/phpunit.xml +23 -0
- package/dist/scaffold-repo/sdks/php/proto/blok/runtime/v1/runtime.proto +302 -0
- package/dist/scaffold-repo/sdks/php/src/Config/ServerConfig.php +83 -0
- package/dist/scaffold-repo/sdks/php/src/Errors/BlokError.php +467 -0
- package/dist/scaffold-repo/sdks/php/src/Errors/BlokErrorBuilder.php +121 -0
- package/dist/scaffold-repo/sdks/php/src/Errors/BlokErrorCategory.php +83 -0
- package/dist/scaffold-repo/sdks/php/src/Errors/BlokErrorSeverity.php +30 -0
- package/dist/scaffold-repo/sdks/php/src/Errors/BuildContextSnapshot.php +141 -0
- package/dist/scaffold-repo/sdks/php/src/Errors/ErrorCategory.php +17 -0
- package/dist/scaffold-repo/sdks/php/src/Errors/NodeException.php +107 -0
- package/dist/scaffold-repo/sdks/php/src/Errors/Origin.php +35 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/ErrorCategory.php +118 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/ErrorSeverity.php +72 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/ExecuteEvent.php +181 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/ExecuteOptions.php +159 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/ExecuteRequest.php +328 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/ExecuteResponse.php +288 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/HealthRequest.php +59 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/HealthResponse/Status.php +54 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/HealthResponse.php +113 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/ListNodesRequest.php +32 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/ListNodesResponse.php +154 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/LogLine.php +156 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/Metrics.php +165 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/NodeDescriptor.php +174 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/NodeError.php +627 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/NodeRef.php +134 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/NodeRuntimeClient.php +101 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/NodeStarted.php +68 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/PartialResult.php +59 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/Progress.php +85 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/RuntimeState.php +142 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/StepInfo.php +168 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/TriggerInfo.php +289 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/Blok/Runtime/V1/WorkflowInfo.php +176 -0
- package/dist/scaffold-repo/sdks/php/src/Genpb/GPBMetadata/Blok/Runtime/V1/Runtime.php +26 -0
- package/dist/scaffold-repo/sdks/php/src/Logging/LogEntry.php +47 -0
- package/dist/scaffold-repo/sdks/php/src/Logging/LogLevel.php +50 -0
- package/dist/scaffold-repo/sdks/php/src/Logging/Logger.php +133 -0
- package/dist/scaffold-repo/sdks/php/src/Node/NodeHandler.php +35 -0
- package/dist/scaffold-repo/sdks/php/src/Node/NodeReflector.php +21 -0
- package/dist/scaffold-repo/sdks/php/src/Node/NodeRegistry.php +172 -0
- package/dist/scaffold-repo/sdks/php/src/Node/TypedNode.php +159 -0
- package/dist/scaffold-repo/sdks/php/src/Server/BlokNodeRuntimeService.php +504 -0
- package/dist/scaffold-repo/sdks/php/src/Server/DecodeException.php +13 -0
- package/dist/scaffold-repo/sdks/php/src/Server/Middleware.php +18 -0
- package/dist/scaffold-repo/sdks/php/src/Server/MiddlewarePipeline.php +33 -0
- package/dist/scaffold-repo/sdks/php/src/Server/NodeRuntimeInterface.php +40 -0
- package/dist/scaffold-repo/sdks/php/src/Server/Server.php +157 -0
- package/dist/scaffold-repo/sdks/php/src/Testing/MockContext.php +161 -0
- package/dist/scaffold-repo/sdks/php/src/Testing/TestRunner.php +78 -0
- package/dist/scaffold-repo/sdks/php/src/Types/Context.php +80 -0
- package/dist/scaffold-repo/sdks/php/src/Types/ExecutionMetrics.php +48 -0
- package/dist/scaffold-repo/sdks/php/src/Types/ExecutionRequest.php +38 -0
- package/dist/scaffold-repo/sdks/php/src/Types/ExecutionResult.php +143 -0
- package/dist/scaffold-repo/sdks/php/src/Types/HealthStatus.php +42 -0
- package/dist/scaffold-repo/sdks/php/src/Types/NodeConfig.php +72 -0
- package/dist/scaffold-repo/sdks/php/src/Types/Request.php +82 -0
- package/dist/scaffold-repo/sdks/php/src/Types/Response.php +44 -0
- package/dist/scaffold-repo/sdks/php/src/Validation/SchemaValidator.php +197 -0
- package/dist/scaffold-repo/sdks/php/tests/Errors/BlokErrorTest.php +402 -0
- package/dist/scaffold-repo/sdks/php/tests/Logging/LoggerTest.php +165 -0
- package/dist/scaffold-repo/sdks/php/tests/Node/NodeRegistryTest.php +139 -0
- package/dist/scaffold-repo/sdks/php/tests/Node/TypedNodeTest.php +84 -0
- package/dist/scaffold-repo/sdks/php/tests/Server/BlokNodeRuntimeServiceTest.php +288 -0
- package/dist/scaffold-repo/sdks/php/tests/Testing/MockContextTest.php +184 -0
- package/dist/scaffold-repo/sdks/php/tests/Types/ExecutionResultTest.php +136 -0
- package/dist/scaffold-repo/sdks/php/tests/Validation/SchemaValidatorTest.php +191 -0
- package/dist/scaffold-repo/sdks/python3/Dockerfile +62 -0
- package/dist/scaffold-repo/sdks/python3/blok/__init__.py +61 -0
- package/dist/scaffold-repo/sdks/python3/blok/config/__init__.py +3 -0
- package/dist/scaffold-repo/sdks/python3/blok/config/server_config.py +116 -0
- package/dist/scaffold-repo/sdks/python3/blok/errors/__init__.py +50 -0
- package/dist/scaffold-repo/sdks/python3/blok/errors/blok_error.py +555 -0
- package/dist/scaffold-repo/sdks/python3/blok/errors/node_error.py +64 -0
- package/dist/scaffold-repo/sdks/python3/blok/logging/__init__.py +3 -0
- package/dist/scaffold-repo/sdks/python3/blok/logging/logger.py +82 -0
- package/dist/scaffold-repo/sdks/python3/blok/middleware/__init__.py +4 -0
- package/dist/scaffold-repo/sdks/python3/blok/middleware/logging_middleware.py +35 -0
- package/dist/scaffold-repo/sdks/python3/blok/middleware/recovery_middleware.py +36 -0
- package/dist/scaffold-repo/sdks/python3/blok/node/__init__.py +4 -0
- package/dist/scaffold-repo/sdks/python3/blok/node/define_node.py +239 -0
- package/dist/scaffold-repo/sdks/python3/blok/node/node_handler.py +35 -0
- package/dist/scaffold-repo/sdks/python3/blok/node/node_registry.py +136 -0
- package/dist/scaffold-repo/sdks/python3/blok/runtime/__init__.py +0 -0
- package/dist/scaffold-repo/sdks/python3/blok/runtime/v1/__init__.py +0 -0
- package/dist/scaffold-repo/sdks/python3/blok/runtime/v1/runtime_pb2.py +112 -0
- package/dist/scaffold-repo/sdks/python3/blok/runtime/v1/runtime_pb2_grpc.py +259 -0
- package/dist/scaffold-repo/sdks/python3/blok/server/__init__.py +3 -0
- package/dist/scaffold-repo/sdks/python3/blok/server/grpc_server.py +757 -0
- package/dist/scaffold-repo/sdks/python3/blok/server/runtime_server.py +165 -0
- package/dist/scaffold-repo/sdks/python3/blok/types/__init__.py +13 -0
- package/dist/scaffold-repo/sdks/python3/blok/types/context.py +162 -0
- package/dist/scaffold-repo/sdks/python3/blok/types/execution_request.py +74 -0
- package/dist/scaffold-repo/sdks/python3/blok/types/execution_result.py +103 -0
- package/dist/scaffold-repo/sdks/python3/blok/worker/__init__.py +10 -0
- package/dist/scaffold-repo/sdks/python3/blok/worker/job_message.py +34 -0
- package/dist/scaffold-repo/sdks/python3/blok/worker/worker.py +451 -0
- package/dist/scaffold-repo/sdks/python3/blok/worker/worker_config.py +129 -0
- package/dist/scaffold-repo/sdks/python3/examples/__init__.py +32 -0
- package/dist/scaffold-repo/sdks/python3/examples/api_call_node.py +85 -0
- package/dist/scaffold-repo/sdks/python3/examples/blok_error_demo_node.py +79 -0
- package/dist/scaffold-repo/sdks/python3/examples/chain_test_node.py +51 -0
- package/dist/scaffold-repo/sdks/python3/examples/hello_world_node.py +44 -0
- package/dist/scaffold-repo/sdks/python3/examples/transform_data_node.py +71 -0
- package/dist/scaffold-repo/sdks/python3/examples/typed_greet_node.py +25 -0
- package/dist/scaffold-repo/sdks/python3/proto/blok/runtime/v1/runtime.proto +302 -0
- package/dist/scaffold-repo/sdks/python3/pyproject.toml +47 -0
- package/dist/scaffold-repo/sdks/python3/requirements.txt +17 -0
- package/dist/scaffold-repo/sdks/python3/tests/__init__.py +0 -0
- package/dist/scaffold-repo/sdks/python3/tests/test_blok_error.py +341 -0
- package/dist/scaffold-repo/sdks/python3/tests/test_define_node.py +135 -0
- package/dist/scaffold-repo/sdks/python3/tests/test_discovery.py +99 -0
- package/dist/scaffold-repo/sdks/python3/tests/test_grpc_codec.py +260 -0
- package/dist/scaffold-repo/sdks/python3/tests/test_grpc_server.py +398 -0
- package/dist/scaffold-repo/sdks/ruby/Dockerfile +37 -0
- package/dist/scaffold-repo/sdks/ruby/Gemfile +5 -0
- package/dist/scaffold-repo/sdks/ruby/README.md +210 -0
- package/dist/scaffold-repo/sdks/ruby/Rakefile +11 -0
- package/dist/scaffold-repo/sdks/ruby/blok.gemspec +43 -0
- package/dist/scaffold-repo/sdks/ruby/config.ru +15 -0
- package/dist/scaffold-repo/sdks/ruby/examples/api_call_node.rb +99 -0
- package/dist/scaffold-repo/sdks/ruby/examples/blok_error_demo_node.rb +79 -0
- package/dist/scaffold-repo/sdks/ruby/examples/chain_test_node.rb +51 -0
- package/dist/scaffold-repo/sdks/ruby/examples/hello_world_node.rb +40 -0
- package/dist/scaffold-repo/sdks/ruby/examples/transform_data_node.rb +82 -0
- package/dist/scaffold-repo/sdks/ruby/examples/typed_greet_node.rb +25 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/config/server_config.rb +76 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/errors/blok_error.rb +476 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/errors/build_context_snapshot.rb +99 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/errors/error_category.rb +16 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/errors/node_error.rb +84 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/logging/log_entry.rb +46 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/logging/log_level.rb +49 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/logging/logger.rb +71 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/middleware/logging_middleware.rb +45 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/middleware/middleware.rb +36 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/middleware/recovery_middleware.rb +31 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/node/discovery.rb +59 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/node/node_handler.rb +43 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/node/node_registry.rb +107 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/node/typed_node.rb +180 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/runtime/v1/runtime_pb.rb +43 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/runtime/v1/runtime_services_pb.rb +55 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/server/grpc_server.rb +435 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/server/runtime_app.rb +86 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/testing/mock_context.rb +109 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/testing/test_runner.rb +40 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/types/context.rb +82 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/types/execution_metrics.rb +43 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/types/execution_request.rb +39 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/types/execution_result.rb +117 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/types/health_status.rb +42 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/types/node_config.rb +73 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/types/request.rb +71 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/types/response.rb +46 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/validation/schema_validator.rb +142 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok/version.rb +5 -0
- package/dist/scaffold-repo/sdks/ruby/lib/blok.rb +53 -0
- package/dist/scaffold-repo/sdks/ruby/proto/blok/runtime/v1/runtime.proto +302 -0
- package/dist/scaffold-repo/sdks/ruby/test/errors/blok_error_test.rb +339 -0
- package/dist/scaffold-repo/sdks/ruby/test/logging/logger_test.rb +155 -0
- package/dist/scaffold-repo/sdks/ruby/test/node/node_registry_test.rb +132 -0
- package/dist/scaffold-repo/sdks/ruby/test/node/typed_node_test.rb +67 -0
- package/dist/scaffold-repo/sdks/ruby/test/test_helper.rb +6 -0
- package/dist/scaffold-repo/sdks/ruby/test/testing/mock_context_test.rb +150 -0
- package/dist/scaffold-repo/sdks/ruby/test/types/execution_result_test.rb +129 -0
- package/dist/scaffold-repo/sdks/ruby/test/validation/schema_validator_test.rb +270 -0
- package/dist/scaffold-repo/sdks/rust/Cargo.lock +3044 -0
- package/dist/scaffold-repo/sdks/rust/Cargo.toml +82 -0
- package/dist/scaffold-repo/sdks/rust/Dockerfile +40 -0
- package/dist/scaffold-repo/sdks/rust/README.md +73 -0
- package/dist/scaffold-repo/sdks/rust/build.rs +32 -0
- package/dist/scaffold-repo/sdks/rust/proto/blok/runtime/v1/runtime.proto +302 -0
- package/dist/scaffold-repo/sdks/rust/src/blok_error.rs +1168 -0
- package/dist/scaffold-repo/sdks/rust/src/config.rs +158 -0
- package/dist/scaffold-repo/sdks/rust/src/errors.rs +142 -0
- package/dist/scaffold-repo/sdks/rust/src/grpc_server.rs +635 -0
- package/dist/scaffold-repo/sdks/rust/src/lib.rs +65 -0
- package/dist/scaffold-repo/sdks/rust/src/logging.rs +195 -0
- package/dist/scaffold-repo/sdks/rust/src/main.rs +79 -0
- package/dist/scaffold-repo/sdks/rust/src/middleware.rs +181 -0
- package/dist/scaffold-repo/sdks/rust/src/node.rs +172 -0
- package/dist/scaffold-repo/sdks/rust/src/nodes/api_call.rs +93 -0
- package/dist/scaffold-repo/sdks/rust/src/nodes/blok_error_demo.rs +151 -0
- package/dist/scaffold-repo/sdks/rust/src/nodes/chain_test.rs +57 -0
- package/dist/scaffold-repo/sdks/rust/src/nodes/hello_world.rs +83 -0
- package/dist/scaffold-repo/sdks/rust/src/nodes/mod.rs +18 -0
- package/dist/scaffold-repo/sdks/rust/src/nodes/transform_data.rs +142 -0
- package/dist/scaffold-repo/sdks/rust/src/nodes/typed_greet.rs +56 -0
- package/dist/scaffold-repo/sdks/rust/src/registry.rs +204 -0
- package/dist/scaffold-repo/sdks/rust/src/server.rs +63 -0
- package/dist/scaffold-repo/sdks/rust/src/testing.rs +175 -0
- package/dist/scaffold-repo/sdks/rust/src/types.rs +338 -0
- package/dist/scaffold-repo/sdks/rust/src/user_nodes/mod.rs +10 -0
- package/dist/scaffold-repo/sdks/rust/src/validation.rs +232 -0
- package/dist/scaffold-repo/sdks/rust/src/worker.rs +509 -0
- package/dist/scaffold-repo/sdks/rust/tests/grpc_integration.rs +306 -0
- package/dist/scaffold-repo/sdks/rust/tests/typed_node.rs +104 -0
- package/dist/scaffold-repo/triggers/cron/CHANGELOG.md +22 -0
- package/dist/scaffold-repo/triggers/cron/package.json +36 -0
- package/dist/scaffold-repo/triggers/cron/src/CronTrigger.ts +552 -0
- package/dist/scaffold-repo/triggers/cron/src/index.ts +77 -0
- package/dist/scaffold-repo/triggers/cron/tsconfig.json +32 -0
- package/dist/scaffold-repo/triggers/grpc/.env.example +8 -0
- package/dist/scaffold-repo/triggers/grpc/CHANGELOG.md +143 -0
- package/dist/scaffold-repo/triggers/grpc/README.md +25 -0
- package/dist/scaffold-repo/triggers/grpc/buf.gen.yaml +11 -0
- package/dist/scaffold-repo/triggers/grpc/buf.yaml +9 -0
- package/dist/scaffold-repo/triggers/grpc/localhost+2-key.pem +28 -0
- package/dist/scaffold-repo/triggers/grpc/localhost+2.pem +27 -0
- package/dist/scaffold-repo/triggers/grpc/package.json +61 -0
- package/dist/scaffold-repo/triggers/grpc/proto/workflow.proto +33 -0
- package/dist/scaffold-repo/triggers/grpc/src/GRpcTrigger.ts +297 -0
- package/dist/scaffold-repo/triggers/grpc/src/GrpcClient.ts +87 -0
- package/dist/scaffold-repo/triggers/grpc/src/GrpcServer.ts +68 -0
- package/dist/scaffold-repo/triggers/grpc/src/MessageDecode.ts +142 -0
- package/dist/scaffold-repo/triggers/grpc/src/NanoSDK.ts +146 -0
- package/dist/scaffold-repo/triggers/grpc/src/Nodes.ts +10 -0
- package/dist/scaffold-repo/triggers/grpc/src/Workflows.ts +5 -0
- package/dist/scaffold-repo/triggers/grpc/src/gen/workflow_pb.ts +142 -0
- package/dist/scaffold-repo/triggers/grpc/src/index.ts +22 -0
- package/dist/scaffold-repo/triggers/grpc/src/server.ts +8 -0
- package/dist/scaffold-repo/triggers/grpc/src/types/Message.ts +0 -0
- package/dist/scaffold-repo/triggers/grpc/src/types/RuntimeWorkflow.ts +7 -0
- package/dist/scaffold-repo/triggers/grpc/src/types/Workflows.ts +7 -0
- package/dist/scaffold-repo/triggers/grpc/tsconfig.json +34 -0
- package/dist/scaffold-repo/triggers/grpc/vitest.config.ts +29 -0
- package/dist/scaffold-repo/triggers/grpc/workflows/countries.json +27 -0
- package/dist/scaffold-repo/triggers/http/.blok/trace.db +0 -0
- package/dist/scaffold-repo/triggers/http/.blok/trace.db-shm +0 -0
- package/dist/scaffold-repo/triggers/http/.blok/trace.db-wal +0 -0
- package/dist/scaffold-repo/triggers/http/.dockerignore +23 -0
- package/dist/scaffold-repo/triggers/http/.env.example +8 -0
- package/dist/scaffold-repo/triggers/http/CHANGELOG.md +326 -0
- package/dist/scaffold-repo/triggers/http/Dockerfile +49 -0
- package/dist/scaffold-repo/triggers/http/Dockerfile.dev +34 -0
- package/dist/scaffold-repo/triggers/http/infra/docker-compose.yml +38 -0
- package/dist/scaffold-repo/triggers/http/notes/CLI_Commands/Authentication.md +74 -0
- package/dist/scaffold-repo/triggers/http/notes/CLI_Commands/Building.md +52 -0
- package/dist/scaffold-repo/triggers/http/notes/CLI_Commands/Create_Node.md +123 -0
- package/dist/scaffold-repo/triggers/http/notes/CLI_Commands/Create_Workflow.md +177 -0
- package/dist/scaffold-repo/triggers/http/notes/CLI_Commands/Deployment.md +69 -0
- package/dist/scaffold-repo/triggers/http/notes/Core_Concepts/Context.md +156 -0
- package/dist/scaffold-repo/triggers/http/notes/examples.md +104 -0
- package/dist/scaffold-repo/triggers/http/notes/index.md +51 -0
- package/dist/scaffold-repo/triggers/http/package.json +62 -0
- package/dist/scaffold-repo/triggers/http/public/favicon.ico +0 -0
- package/dist/scaffold-repo/triggers/http/public/metric/index.html +2101 -0
- package/dist/scaffold-repo/triggers/http/request.http +7 -0
- package/dist/scaffold-repo/triggers/http/src/AppRoutes.ts +64 -0
- package/dist/scaffold-repo/triggers/http/src/Nodes.ts +38 -0
- package/dist/scaffold-repo/triggers/http/src/Workflows.ts +18 -0
- package/dist/scaffold-repo/triggers/http/src/index.ts +86 -0
- package/dist/scaffold-repo/triggers/http/src/nodes/chain-init/index.ts +48 -0
- package/dist/scaffold-repo/triggers/http/src/nodes/chain-verify/index.ts +123 -0
- package/dist/scaffold-repo/triggers/http/src/nodes/eval/index.ts +69 -0
- package/dist/scaffold-repo/triggers/http/src/nodes/examples/README.md +14 -0
- package/dist/scaffold-repo/triggers/http/src/nodes/examples/chat-ui/index.html +228 -0
- package/dist/scaffold-repo/triggers/http/src/nodes/examples/chat-ui/index.ts +65 -0
- package/dist/scaffold-repo/triggers/http/src/nodes/examples/index.ts +14 -0
- package/dist/scaffold-repo/triggers/http/src/nodes/runtime-bridge/index.ts +84 -0
- package/dist/scaffold-repo/triggers/http/src/runner/HonoTraceRouterAdapter.ts +199 -0
- package/dist/scaffold-repo/triggers/http/src/runner/HttpTrigger.ts +2039 -0
- package/dist/scaffold-repo/triggers/http/src/runner/MessageDecode.ts +57 -0
- package/dist/scaffold-repo/triggers/http/src/runner/Util.ts +52 -0
- package/dist/scaffold-repo/triggers/http/src/runner/WorkflowRouter.ts +411 -0
- package/dist/scaffold-repo/triggers/http/src/runner/metrics/opentelemetry_metrics.ts +84 -0
- package/dist/scaffold-repo/triggers/http/src/runner/metrics/opentelemetry_traces.ts +30 -0
- package/dist/scaffold-repo/triggers/http/src/runner/nodeCatalog.ts +128 -0
- package/dist/scaffold-repo/triggers/http/src/runner/responseEmitter.ts +138 -0
- package/dist/scaffold-repo/triggers/http/src/runner/scanWorkflows.ts +201 -0
- package/dist/scaffold-repo/triggers/http/src/runner/types/ErrorResponse.ts +3 -0
- package/dist/scaffold-repo/triggers/http/src/runner/types/NodeTypes.ts +13 -0
- package/dist/scaffold-repo/triggers/http/src/runner/types/Nodes.ts +5 -0
- package/dist/scaffold-repo/triggers/http/src/runner/types/RuntimeWorkflow.ts +7 -0
- package/dist/scaffold-repo/triggers/http/src/runner/types/WorkflowRequest.ts +8 -0
- package/dist/scaffold-repo/triggers/http/src/runner/types/WorkflowResponse.ts +7 -0
- package/dist/scaffold-repo/triggers/http/src/runner/types/Workflows.ts +14 -0
- package/dist/scaffold-repo/triggers/http/src/workflows/countries-cats-helper.ts +30 -0
- package/dist/scaffold-repo/triggers/http/src/workflows/countries-handle-dsl.ts +25 -0
- package/dist/scaffold-repo/triggers/http/src/workflows/countries-helper.ts +18 -0
- package/dist/scaffold-repo/triggers/http/src/workflows/empty.ts +16 -0
- package/dist/scaffold-repo/triggers/http/src/workflows/eval/eval-retrieve.ts +23 -0
- package/dist/scaffold-repo/triggers/http/src/workflows/eval/eval-run.ts +39 -0
- package/dist/scaffold-repo/triggers/http/src/workflows/eval/foreign-auth.ts +24 -0
- package/dist/scaffold-repo/triggers/http/tsconfig.json +39 -0
- package/dist/scaffold-repo/triggers/http/vitest.config.ts +39 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/agent-message.json +87 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/agent-page.json +25 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/agent-stream.json +30 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/agent-tool-calculator.json +22 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/agent-tool-weather.json +22 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/chat-memory-message.json +54 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/chat-memory-page.json +25 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/chat-memory-stream.json +30 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/chat-message.json +25 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/chat-page.json +20 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/chat-stream.json +30 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/countries-py.json +27 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/countries-vs-facts.json +50 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/countries.json +27 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/cross-runtime-chain.json +89 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/dashboard-gen.json +34 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/db-manager.json +34 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/empty.json +13 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/fanout-enqueue.json +48 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/fanout-worker.json +41 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/feedback.json +34 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/films.json +27 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/image-capture.json +34 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/launches-by-year.json +27 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/loadtest.json +24 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/middleware/admin-only.json +31 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/middleware/auth-check.json +59 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/middleware/github-webhook-verify.json +34 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/middleware/jwt-auth.json +46 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/middleware/rate-limit.json +70 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/middleware/redis-rate-limit.json +71 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/mongodb.json +24 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/rentals-pdf.json +65 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-admin-delete-user.json +42 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-async-job-poller.json +45 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-csv-import.json +122 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-data-export.json +105 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-event-router.json +97 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-github-handler-issues.json +22 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-github-handler-pr.json +22 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-github-handler-push.json +22 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-github-webhook-router.json +88 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-hello-global-mw.json +22 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-hello-with-mw.json +23 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-jwt-protected.json +23 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-multi-tenant-router.json +90 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-nested-control-flow.json +207 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-order-fulfillment.json +211 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-parallel-foreach-with-wait.json +47 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-polling-with-backoff.json +55 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-protected.json +23 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-redis-protected.json +23 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-saga.json +122 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-tenant-acme.json +22 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-tenant-beta.json +22 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-tenant-gamma.json +22 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-travel-booking.json +200 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-user-signup-saga.json +186 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v05-webhook-fanout.json +49 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v06-reliability-showcase.json +80 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v07-sse-publish.json +24 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v07-sse-stream.json +26 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/v07-ws-echo.json +38 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/webhook-github-issues.json +22 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/webhook-github-pr.json +22 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/webhook-github-push.json +32 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/webhook-github.json +87 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/webhook-linear-comment.json +22 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/webhook-linear-issue.json +22 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/webhook-linear.json +74 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/webhook-stripe-customer-created.json +22 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/webhook-stripe-invoice-paid.json +32 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/webhook-stripe.json +24 -0
- package/dist/scaffold-repo/triggers/http/workflows/json/workflow-docs.json +34 -0
- package/dist/scaffold-repo/triggers/http/workflows/toml/countries.toml +21 -0
- package/dist/scaffold-repo/triggers/http/workflows/xml/countries.xml +23 -0
- package/dist/scaffold-repo/triggers/http/workflows/yaml/countries.yaml +19 -0
- package/dist/scaffold-repo/triggers/mcp/README.md +131 -0
- package/dist/scaffold-repo/triggers/mcp/package.json +39 -0
- package/dist/scaffold-repo/triggers/mcp/src/McpTrigger.ts +581 -0
- package/dist/scaffold-repo/triggers/mcp/src/index.ts +41 -0
- package/dist/scaffold-repo/triggers/mcp/tsconfig.json +32 -0
- package/dist/scaffold-repo/triggers/pubsub/CHANGELOG.md +22 -0
- package/dist/scaffold-repo/triggers/pubsub/package.json +66 -0
- package/dist/scaffold-repo/triggers/pubsub/src/PubSubTrigger.ts +424 -0
- package/dist/scaffold-repo/triggers/pubsub/src/adapters/AWSSNSAdapter.ts +352 -0
- package/dist/scaffold-repo/triggers/pubsub/src/adapters/AzureServiceBusAdapter.ts +303 -0
- package/dist/scaffold-repo/triggers/pubsub/src/adapters/GCPPubSubAdapter.ts +327 -0
- package/dist/scaffold-repo/triggers/pubsub/src/adapters/KafkaPubSubAdapter.ts +247 -0
- package/dist/scaffold-repo/triggers/pubsub/src/adapters/KafkaPubSubAdapter.ts.bak +251 -0
- package/dist/scaffold-repo/triggers/pubsub/src/adapters/NATSPubSubAdapter.ts +333 -0
- package/dist/scaffold-repo/triggers/pubsub/src/adapters/RedisStreamsPubSubAdapter.ts +256 -0
- package/dist/scaffold-repo/triggers/pubsub/src/adapters/factory.ts +88 -0
- package/dist/scaffold-repo/triggers/pubsub/src/index.ts +67 -0
- package/dist/scaffold-repo/triggers/pubsub/template/.env.example +8 -0
- package/dist/scaffold-repo/triggers/pubsub/template/package.json +45 -0
- package/dist/scaffold-repo/triggers/pubsub/template/src/Nodes.ts +20 -0
- package/dist/scaffold-repo/triggers/pubsub/template/src/Workflows.ts +8 -0
- package/dist/scaffold-repo/triggers/pubsub/template/src/index.ts +41 -0
- package/dist/scaffold-repo/triggers/pubsub/template/src/runner/PubSubServer.ts +39 -0
- package/dist/scaffold-repo/triggers/pubsub/template/src/runner/types/Workflows.ts +7 -0
- package/dist/scaffold-repo/triggers/pubsub/template/src/workflows/messages/on-message.ts +42 -0
- package/dist/scaffold-repo/triggers/pubsub/template/src/workflows/publish-order.ts +22 -0
- package/dist/scaffold-repo/triggers/pubsub/template/tsconfig.json +31 -0
- package/dist/scaffold-repo/triggers/pubsub/template/vitest.config.ts +39 -0
- package/dist/scaffold-repo/triggers/pubsub/tsconfig.json +32 -0
- package/dist/scaffold-repo/triggers/sse/.env.example +8 -0
- package/dist/scaffold-repo/triggers/sse/CHANGELOG.md +12 -0
- package/dist/scaffold-repo/triggers/sse/Dockerfile +47 -0
- package/dist/scaffold-repo/triggers/sse/Dockerfile.dev +33 -0
- package/dist/scaffold-repo/triggers/sse/package.json +37 -0
- package/dist/scaffold-repo/triggers/sse/src/SSETrigger.ts +489 -0
- package/dist/scaffold-repo/triggers/sse/src/bus.ts +208 -0
- package/dist/scaffold-repo/triggers/sse/src/index.ts +38 -0
- package/dist/scaffold-repo/triggers/sse/src/workflows/events/README.md +129 -0
- package/dist/scaffold-repo/triggers/sse/src/workflows/events/publish-demo.ts +40 -0
- package/dist/scaffold-repo/triggers/sse/src/workflows/events/stream-demo.ts +43 -0
- package/dist/scaffold-repo/triggers/sse/tsconfig.json +22 -0
- package/dist/scaffold-repo/triggers/sse/vitest.config.ts +39 -0
- package/dist/scaffold-repo/triggers/webhook/CHANGELOG.md +22 -0
- package/dist/scaffold-repo/triggers/webhook/package.json +36 -0
- package/dist/scaffold-repo/triggers/webhook/src/WebhookTrigger.ts +465 -0
- package/dist/scaffold-repo/triggers/webhook/src/index.ts +51 -0
- package/dist/scaffold-repo/triggers/webhook/src/verifiers.ts +394 -0
- package/dist/scaffold-repo/triggers/webhook/tsconfig.json +32 -0
- package/dist/scaffold-repo/triggers/websocket/.env.example +8 -0
- package/dist/scaffold-repo/triggers/websocket/CHANGELOG.md +22 -0
- package/dist/scaffold-repo/triggers/websocket/package.json +50 -0
- package/dist/scaffold-repo/triggers/websocket/src/Backplane.ts +167 -0
- package/dist/scaffold-repo/triggers/websocket/src/WebSocketTrigger.ts +910 -0
- package/dist/scaffold-repo/triggers/websocket/src/index.ts +50 -0
- package/dist/scaffold-repo/triggers/websocket/src/workflows/events/README.md +93 -0
- package/dist/scaffold-repo/triggers/websocket/src/workflows/events/echo-demo.ts +56 -0
- package/dist/scaffold-repo/triggers/websocket/tsconfig.json +22 -0
- package/dist/scaffold-repo/triggers/websocket/vitest.config.ts +39 -0
- package/dist/scaffold-repo/triggers/worker/CHANGELOG.md +22 -0
- package/dist/scaffold-repo/triggers/worker/package.json +68 -0
- package/dist/scaffold-repo/triggers/worker/src/WorkerTrigger.ts +943 -0
- package/dist/scaffold-repo/triggers/worker/src/adapters/BullMQAdapter.ts +296 -0
- package/dist/scaffold-repo/triggers/worker/src/adapters/InMemoryAdapter.ts +280 -0
- package/dist/scaffold-repo/triggers/worker/src/adapters/KafkaAdapter.ts +347 -0
- package/dist/scaffold-repo/triggers/worker/src/adapters/NATSAdapter.ts +505 -0
- package/dist/scaffold-repo/triggers/worker/src/adapters/PgBossAdapter.ts +319 -0
- package/dist/scaffold-repo/triggers/worker/src/adapters/RabbitMQAdapter.ts +318 -0
- package/dist/scaffold-repo/triggers/worker/src/adapters/RedisStreamsAdapter.ts +513 -0
- package/dist/scaffold-repo/triggers/worker/src/adapters/SQSAdapter.ts +314 -0
- package/dist/scaffold-repo/triggers/worker/src/adapters/factory.ts +111 -0
- package/dist/scaffold-repo/triggers/worker/src/index.ts +95 -0
- package/dist/scaffold-repo/triggers/worker/template/.env.example +18 -0
- package/dist/scaffold-repo/triggers/worker/template/package.json +46 -0
- package/dist/scaffold-repo/triggers/worker/template/src/Nodes.ts +20 -0
- package/dist/scaffold-repo/triggers/worker/template/src/Workflows.ts +8 -0
- package/dist/scaffold-repo/triggers/worker/template/src/index.ts +41 -0
- package/dist/scaffold-repo/triggers/worker/template/src/runner/WorkerServer.ts +34 -0
- package/dist/scaffold-repo/triggers/worker/template/src/runner/types/Workflows.ts +7 -0
- package/dist/scaffold-repo/triggers/worker/template/src/workflows/jobs/process-job.ts +40 -0
- package/dist/scaffold-repo/triggers/worker/template/tsconfig.json +31 -0
- package/dist/scaffold-repo/triggers/worker/template/vitest.config.ts +39 -0
- package/dist/scaffold-repo/triggers/worker/tsconfig.json +32 -0
- package/dist/scaffold-repo/workflows/json/agent-message.json +87 -0
- package/dist/scaffold-repo/workflows/json/agent-page.json +25 -0
- package/dist/scaffold-repo/workflows/json/agent-stream.json +30 -0
- package/dist/scaffold-repo/workflows/json/agent-tool-calculator.json +22 -0
- package/dist/scaffold-repo/workflows/json/agent-tool-weather.json +22 -0
- package/dist/scaffold-repo/workflows/json/chat-memory-message.json +54 -0
- package/dist/scaffold-repo/workflows/json/chat-memory-page.json +25 -0
- package/dist/scaffold-repo/workflows/json/chat-memory-stream.json +30 -0
- package/dist/scaffold-repo/workflows/json/chat-message.json +25 -0
- package/dist/scaffold-repo/workflows/json/chat-page.json +20 -0
- package/dist/scaffold-repo/workflows/json/chat-stream.json +30 -0
- package/dist/scaffold-repo/workflows/json/countries-vs-facts.json +50 -0
- package/dist/scaffold-repo/workflows/json/countries.json +27 -0
- package/dist/scaffold-repo/workflows/json/cron-heartbeat.json +15 -0
- package/dist/scaffold-repo/workflows/json/fanout-enqueue.json +48 -0
- package/dist/scaffold-repo/workflows/json/fanout-worker.json +41 -0
- package/dist/scaffold-repo/workflows/json/pubsub-on-order.json +24 -0
- package/dist/scaffold-repo/workflows/json/webhook-github-issues.json +22 -0
- package/dist/scaffold-repo/workflows/json/webhook-github-pr.json +22 -0
- package/dist/scaffold-repo/workflows/json/webhook-github-push.json +32 -0
- package/dist/scaffold-repo/workflows/json/webhook-github.json +87 -0
- package/dist/scaffold-repo/workflows/json/webhook-linear-comment.json +22 -0
- package/dist/scaffold-repo/workflows/json/webhook-linear-issue.json +22 -0
- package/dist/scaffold-repo/workflows/json/webhook-linear.json +74 -0
- package/dist/scaffold-repo/workflows/json/webhook-stripe-customer-created.json +22 -0
- package/dist/scaffold-repo/workflows/json/webhook-stripe-invoice-paid.json +32 -0
- package/dist/scaffold-repo/workflows/json/webhook-stripe.json +24 -0
- package/package.json +4 -4
|
@@ -0,0 +1,910 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocketTrigger — v0.7 PR 2 — Concrete WebSocket trigger that
|
|
3
|
+
* registers Hono routes on a shared `Hono<AppBindings>` app so
|
|
4
|
+
* WebSocket connections live alongside HTTP routes on the same port.
|
|
5
|
+
*
|
|
6
|
+
* **Authoring surface:**
|
|
7
|
+
*
|
|
8
|
+
* ```json
|
|
9
|
+
* {
|
|
10
|
+
* "name": "chat-room",
|
|
11
|
+
* "trigger": {
|
|
12
|
+
* "websocket": {
|
|
13
|
+
* "path": "/ws/chat/:roomId",
|
|
14
|
+
* "events": ["message", "typing"],
|
|
15
|
+
* "middleware": ["jwt-auth"]
|
|
16
|
+
* }
|
|
17
|
+
* },
|
|
18
|
+
* "steps": [
|
|
19
|
+
* { "id": "broadcast", "use": "@blokjs/ws-broadcast", "inputs": {...} }
|
|
20
|
+
* ]
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* **Lifecycle (one workflow run per event):**
|
|
25
|
+
*
|
|
26
|
+
* - `event: "connect"` — fires once on upgrade. Workflow can set
|
|
27
|
+
* `ctx.connection.attachment` and join rooms.
|
|
28
|
+
* - `event: "message"` — fires per incoming message. Author can
|
|
29
|
+
* reply via `@blokjs/ws-reply` or broadcast via `@blokjs/ws-broadcast`.
|
|
30
|
+
* - `event: "disconnect"` — fires once on close. Cleanup happens here.
|
|
31
|
+
*
|
|
32
|
+
* **Hono integration:** the trigger is constructed with the shared
|
|
33
|
+
* `Hono<AppBindings>` app (typically from `HttpTrigger`). On `listen()`,
|
|
34
|
+
* it walks `WorkflowRegistry` for workflows with `trigger.websocket`,
|
|
35
|
+
* registers one `app.get(path, upgradeWebSocket(...))` per workflow,
|
|
36
|
+
* and hooks `injectWebSocket(server)` into the `http.Server` via
|
|
37
|
+
* `httpTrigger.addServerHook(...)`. All four protocols (HTTP/WS/SSE/
|
|
38
|
+
* Webhook) end up on port 4000 via Hono's path-routing tree.
|
|
39
|
+
*
|
|
40
|
+
* See [additional-triggers-plan.mdx](../../../docs/c/devtools/additional-triggers-plan.mdx#websocket-trigger)
|
|
41
|
+
* for the full v0.7 design.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import type { Server } from "node:http";
|
|
45
|
+
import {
|
|
46
|
+
DefaultLogger,
|
|
47
|
+
type GlobalOptions as RunnerGlobalOptions,
|
|
48
|
+
TriggerBase,
|
|
49
|
+
WorkflowRegistry,
|
|
50
|
+
} from "@blokjs/runner";
|
|
51
|
+
import type { ConnectionContext, Context, RequestContext } from "@blokjs/shared";
|
|
52
|
+
import { createNodeWebSocket } from "@hono/node-ws";
|
|
53
|
+
import { type Span, SpanStatusCode, metrics, trace } from "@opentelemetry/api";
|
|
54
|
+
import type { Hono } from "hono";
|
|
55
|
+
import type { WSContext } from "hono/ws";
|
|
56
|
+
import { v4 as uuid } from "uuid";
|
|
57
|
+
import {
|
|
58
|
+
type BackplaneAdapter,
|
|
59
|
+
type BackplaneConfig,
|
|
60
|
+
type BroadcastEnvelope,
|
|
61
|
+
DEFAULT_BACKPLANE_TOPIC,
|
|
62
|
+
createBackplaneAdapter,
|
|
63
|
+
decodePayload,
|
|
64
|
+
encodeEnvelope,
|
|
65
|
+
newSenderId,
|
|
66
|
+
resolveBackplaneConfig,
|
|
67
|
+
} from "./Backplane";
|
|
68
|
+
|
|
69
|
+
// -----------------------------------------------------------------------------
|
|
70
|
+
// Types
|
|
71
|
+
// -----------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* v0.7 WebSocket trigger config — mirrors `WebSocketTriggerOptsSchema`
|
|
75
|
+
* in `core/workflow-helper/src/types/TriggerOpts.ts`. Fields are read
|
|
76
|
+
* loosely here because Configuration has already validated the schema
|
|
77
|
+
* by the time we walk the registry.
|
|
78
|
+
*/
|
|
79
|
+
interface WebSocketTriggerConfig {
|
|
80
|
+
path: string;
|
|
81
|
+
events?: string[]; // event-name allowlist; absent = accept all
|
|
82
|
+
middleware?: string[]; // trigger-level middleware chain
|
|
83
|
+
heartbeatInterval?: number; // ms; default 30000
|
|
84
|
+
maxConnections?: number; // hard cap on concurrent connections; default 10000
|
|
85
|
+
messageRateLimit?: number; // msgs/sec/connection; default 100
|
|
86
|
+
mode?: "text" | "binary"; // payload mode; default "text"
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Internal per-connection state. */
|
|
90
|
+
interface ConnectionState {
|
|
91
|
+
id: string;
|
|
92
|
+
ws: WSContext;
|
|
93
|
+
workflowName: string;
|
|
94
|
+
path: string;
|
|
95
|
+
pathParams: Record<string, string>;
|
|
96
|
+
rooms: Set<string>;
|
|
97
|
+
attachment: unknown;
|
|
98
|
+
connectedAt: number;
|
|
99
|
+
lastActivity: number;
|
|
100
|
+
tokens: number; // rate-limiter bucket
|
|
101
|
+
tokensRefilledAt: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface HttpTriggerLike {
|
|
105
|
+
addServerHook(cb: (server: Server) => void | Promise<void>): void;
|
|
106
|
+
addPreCatchAllHook(cb: () => void | Promise<void>): void;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// -----------------------------------------------------------------------------
|
|
110
|
+
// Trigger class
|
|
111
|
+
// -----------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
const DEFAULT_HEARTBEAT_INTERVAL_MS = 30_000;
|
|
114
|
+
const DEFAULT_MAX_CONNECTIONS = 10_000;
|
|
115
|
+
const DEFAULT_MESSAGE_RATE_LIMIT = 100; // msgs/sec/connection
|
|
116
|
+
const ATTACHMENT_MAX_BYTES = 2_048; // 2 KB cap per CF DO pattern
|
|
117
|
+
|
|
118
|
+
export default class WebSocketTrigger extends TriggerBase {
|
|
119
|
+
protected nodeMap: RunnerGlobalOptions = {} as RunnerGlobalOptions;
|
|
120
|
+
|
|
121
|
+
protected readonly logger = new DefaultLogger();
|
|
122
|
+
|
|
123
|
+
protected readonly tracer = trace.getTracer(
|
|
124
|
+
process.env.PROJECT_NAME || "trigger-websocket-workflow",
|
|
125
|
+
process.env.PROJECT_VERSION || "0.0.1",
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
private readonly meter = metrics.getMeter("blok");
|
|
129
|
+
private readonly counterMessagesReceived = this.meter.createCounter("blok_websocket_messages_received_total", {
|
|
130
|
+
description: "WebSocket messages received per workflow.",
|
|
131
|
+
unit: "1",
|
|
132
|
+
});
|
|
133
|
+
private readonly counterMessagesDropped = this.meter.createCounter("blok_websocket_messages_dropped_total", {
|
|
134
|
+
description: "WebSocket messages dropped (rate limit, no handler, auth failure).",
|
|
135
|
+
unit: "1",
|
|
136
|
+
});
|
|
137
|
+
private readonly counterConnections = this.meter.createCounter("blok_websocket_connections_total", {
|
|
138
|
+
description: "WebSocket connections opened (cumulative).",
|
|
139
|
+
unit: "1",
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Hono's strict-types preserve route-shape inference through middleware
|
|
143
|
+
// chains. @hono/node-ws (and most third-party middleware) accept
|
|
144
|
+
// `Hono<any, any, any>` because their own generics aren't refined enough
|
|
145
|
+
// for the propagation. We type the trigger's `app` field the same way
|
|
146
|
+
// to keep the API surface tolerant of any concrete Hono instance.
|
|
147
|
+
// biome-ignore lint/suspicious/noExplicitAny: Hono's generic propagation
|
|
148
|
+
private readonly app: Hono<any, any, any>;
|
|
149
|
+
private readonly httpTrigger: HttpTriggerLike | null;
|
|
150
|
+
|
|
151
|
+
private upgradeWebSocket: ReturnType<typeof createNodeWebSocket>["upgradeWebSocket"] | null = null;
|
|
152
|
+
private injectWebSocket: ((server: Server) => void) | null = null;
|
|
153
|
+
|
|
154
|
+
private connections: Map<string, ConnectionState> = new Map();
|
|
155
|
+
|
|
156
|
+
/** workflow name → set of connection IDs subscribed to it. */
|
|
157
|
+
private connectionsByWorkflow: Map<string, Set<string>> = new Map();
|
|
158
|
+
|
|
159
|
+
/** room name (workflow-scoped) → set of connection IDs. */
|
|
160
|
+
private rooms: Map<string, Set<string>> = new Map();
|
|
161
|
+
|
|
162
|
+
private heartbeatTimer: NodeJS.Timeout | null = null;
|
|
163
|
+
private wired = false;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* v0.7 follow-up — cross-process broadcast backplane. When
|
|
167
|
+
* configured (env var `BLOK_WS_BACKPLANE=<provider>` or constructor
|
|
168
|
+
* option), `broadcastToRoom` ALSO publishes the message envelope to
|
|
169
|
+
* a pub/sub topic. Every WS worker in the cluster subscribes to
|
|
170
|
+
* the same topic and re-runs the local fan-out, so a message sent
|
|
171
|
+
* from process A reaches connections owned by process B.
|
|
172
|
+
*
|
|
173
|
+
* Disabled by default — `backplaneAdapter === null` short-circuits
|
|
174
|
+
* the publish path and keeps single-process zero-overhead behaviour.
|
|
175
|
+
*/
|
|
176
|
+
private backplaneConfig: BackplaneConfig | null = null;
|
|
177
|
+
private backplaneAdapter: BackplaneAdapter | null = null;
|
|
178
|
+
/** Stable per-process id; used to skip self-publishes on the subscribe handler. */
|
|
179
|
+
private readonly senderId: string = newSenderId();
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @param app Shared Hono app. Constructed by an orchestrator
|
|
183
|
+
* or by HttpTrigger; WS routes are registered on
|
|
184
|
+
* the same instance so HTTP + WS multiplex on one
|
|
185
|
+
* TCP port.
|
|
186
|
+
* @param httpTrigger Optional HttpTrigger-like object exposing
|
|
187
|
+
* `addServerHook`. When provided, WebSocketTrigger
|
|
188
|
+
* hooks `injectWebSocket(server)` into the post-
|
|
189
|
+
* `serve()` callback so the upgrade listener
|
|
190
|
+
* attaches without the caller having to wire it.
|
|
191
|
+
*/
|
|
192
|
+
// biome-ignore lint/suspicious/noExplicitAny: matches `app` field's any generic
|
|
193
|
+
constructor(app: Hono<any, any, any>, httpTrigger?: HttpTriggerLike, backplane?: BackplaneConfig) {
|
|
194
|
+
super();
|
|
195
|
+
this.app = app;
|
|
196
|
+
this.httpTrigger = httpTrigger ?? null;
|
|
197
|
+
// v0.7 follow-up — resolve the cross-process broadcast backplane
|
|
198
|
+
// config from constructor arg → `BLOK_WS_BACKPLANE` env var →
|
|
199
|
+
// disabled. Adapter itself is connected lazily in `listen()` so
|
|
200
|
+
// construction stays cheap and tests that never call listen()
|
|
201
|
+
// don't open broker connections.
|
|
202
|
+
this.backplaneConfig = resolveBackplaneConfig(backplane);
|
|
203
|
+
// Register as the active trigger so helper nodes
|
|
204
|
+
// (@blokjs/ws-broadcast, @blokjs/ws-close) can look up the
|
|
205
|
+
// trigger via the singleton accessor.
|
|
206
|
+
_setActiveWebSocketTrigger(this);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Inject the runner's GlobalOptions (nodes + workflows). Called by
|
|
211
|
+
* the orchestrator AFTER constructing both triggers but BEFORE
|
|
212
|
+
* `listen()`. The HTTP trigger's `loadNodes()` + `loadWorkflows()`
|
|
213
|
+
* produces this map; we share it so both triggers see the same
|
|
214
|
+
* workflow definitions.
|
|
215
|
+
*
|
|
216
|
+
* Backward-compat: when not called, the trigger falls back to an
|
|
217
|
+
* empty map — the `listen()` walk through `WorkflowRegistry` still
|
|
218
|
+
* works because the registry is populated by HttpTrigger's workflow
|
|
219
|
+
* scan at boot.
|
|
220
|
+
*/
|
|
221
|
+
setNodeMap(nodeMap: RunnerGlobalOptions): void {
|
|
222
|
+
this.nodeMap = nodeMap;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Register Hono WebSocket routes for every workflow whose
|
|
227
|
+
* `trigger.websocket` config is present in `WorkflowRegistry`.
|
|
228
|
+
*
|
|
229
|
+
* Mounting timeline:
|
|
230
|
+
* 1. `createNodeWebSocket({ app })` — bind to the shared app.
|
|
231
|
+
* 2. For each registered WS workflow: `app.get(path, upgradeWebSocket(...))`.
|
|
232
|
+
* 3. Register `injectWebSocket` on `httpTrigger.addServerHook`.
|
|
233
|
+
* HttpTrigger calls it inside the `serve()` ready callback
|
|
234
|
+
* with the bound `http.Server` — the WS upgrade listener
|
|
235
|
+
* attaches automatically.
|
|
236
|
+
*/
|
|
237
|
+
async listen(): Promise<number> {
|
|
238
|
+
const startTime = this.startCounter();
|
|
239
|
+
|
|
240
|
+
if (this.wired) {
|
|
241
|
+
this.logger.log("[blok][ws] listen() called twice; ignoring");
|
|
242
|
+
return this.endCounter(startTime);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Wire createNodeWebSocket immediately so upgradeWebSocket /
|
|
246
|
+
// injectWebSocket are available. The actual ROUTE registration
|
|
247
|
+
// is deferred to the server hook below so it runs AFTER
|
|
248
|
+
// HttpTrigger has populated WorkflowRegistry via its workflow
|
|
249
|
+
// scan. Hono allows route addition after `serve()` because
|
|
250
|
+
// `app.fetch` is mutated in place.
|
|
251
|
+
const { upgradeWebSocket, injectWebSocket } = createNodeWebSocket({ app: this.app });
|
|
252
|
+
this.upgradeWebSocket = upgradeWebSocket;
|
|
253
|
+
this.injectWebSocket = injectWebSocket;
|
|
254
|
+
|
|
255
|
+
this.startHeartbeat();
|
|
256
|
+
this.wired = true;
|
|
257
|
+
|
|
258
|
+
// v0.7 follow-up — connect the cross-process broadcast backplane
|
|
259
|
+
// when configured. Lazy-imports the pub/sub trigger so workflows
|
|
260
|
+
// that don't use the backplane don't pull in broker SDKs. On
|
|
261
|
+
// connect failure (broker unreachable, missing peer dep), log
|
|
262
|
+
// but DON'T fail listen() — the trigger still serves single-
|
|
263
|
+
// process broadcasts; only the cross-process fan-out is lost.
|
|
264
|
+
if (this.backplaneConfig) {
|
|
265
|
+
try {
|
|
266
|
+
this.backplaneAdapter = await createBackplaneAdapter(this.backplaneConfig);
|
|
267
|
+
await this.backplaneAdapter.connect();
|
|
268
|
+
const topic = this.backplaneConfig.topic ?? DEFAULT_BACKPLANE_TOPIC;
|
|
269
|
+
await this.backplaneAdapter.subscribe(
|
|
270
|
+
{ topic, consumerGroup: this.backplaneConfig.consumerGroup },
|
|
271
|
+
async (msg) => {
|
|
272
|
+
this.onBackplaneMessage(msg.body);
|
|
273
|
+
},
|
|
274
|
+
);
|
|
275
|
+
this.logger.log(
|
|
276
|
+
`[blok][ws] backplane connected (provider=${this.backplaneConfig.provider}, topic=${topic}, senderId=${this.senderId})`,
|
|
277
|
+
);
|
|
278
|
+
} catch (err) {
|
|
279
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
280
|
+
this.logger.error(
|
|
281
|
+
`[blok][ws] backplane connect failed: ${m}. Cross-process broadcasts disabled; local fan-out still works.`,
|
|
282
|
+
);
|
|
283
|
+
this.backplaneAdapter = null;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Coordinate with HttpTrigger:
|
|
288
|
+
// 1. HttpTrigger.listen() scans workflows + populates WorkflowRegistry.
|
|
289
|
+
// 2. HttpTrigger fires preCatchAllHooks — we walk the registry and
|
|
290
|
+
// register one `app.get(path, upgradeWebSocket(...))` per WS
|
|
291
|
+
// workflow. Done BEFORE the legacy `/:workflow{.+}` catch-all
|
|
292
|
+
// so Hono dispatches `/ws/<path>` to our upgrade handler
|
|
293
|
+
// instead of treating it as a workflow lookup.
|
|
294
|
+
// 3. HttpTrigger.listen() calls serve() → http.Server is up.
|
|
295
|
+
// 4. serve() ready-callback runs our server hook → we call
|
|
296
|
+
// injectWebSocket(server) to attach the upgrade listener.
|
|
297
|
+
if (this.httpTrigger) {
|
|
298
|
+
this.httpTrigger.addPreCatchAllHook(() => {
|
|
299
|
+
const workflows = this.getWebSocketWorkflows();
|
|
300
|
+
if (workflows.length === 0) {
|
|
301
|
+
this.logger.log("[blok][ws] no workflows with trigger.websocket found");
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
this.logger.log(`[blok][ws] registering ${workflows.length} WebSocket route(s):`);
|
|
305
|
+
for (const entry of workflows) {
|
|
306
|
+
this.registerWsRoute(entry);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
this.httpTrigger.addServerHook((server) => {
|
|
310
|
+
this.injectWebSocket?.(server);
|
|
311
|
+
this.logger.log("[blok][ws] WebSocket upgrade handler attached to http.Server");
|
|
312
|
+
});
|
|
313
|
+
} else {
|
|
314
|
+
// No httpTrigger — caller is responsible for calling
|
|
315
|
+
// `getRoutes()` / `injectWebSocket()` themselves. Used by
|
|
316
|
+
// unit tests that supply their own app + server lifecycle.
|
|
317
|
+
const workflows = this.getWebSocketWorkflows();
|
|
318
|
+
if (workflows.length === 0) {
|
|
319
|
+
this.logger.log("[blok][ws] no workflows with trigger.websocket found");
|
|
320
|
+
} else {
|
|
321
|
+
this.logger.log(`[blok][ws] registering ${workflows.length} WebSocket route(s):`);
|
|
322
|
+
for (const entry of workflows) {
|
|
323
|
+
this.registerWsRoute(entry);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return this.endCounter(startTime);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async stop(): Promise<void> {
|
|
332
|
+
this.stopHeartbeat();
|
|
333
|
+
// v0.7 follow-up — disconnect the backplane. Best-effort; a
|
|
334
|
+
// failure here shouldn't block shutdown.
|
|
335
|
+
if (this.backplaneAdapter) {
|
|
336
|
+
try {
|
|
337
|
+
await this.backplaneAdapter.disconnect();
|
|
338
|
+
} catch (err) {
|
|
339
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
340
|
+
this.logger.error(`[blok][ws] backplane disconnect failed: ${m}`);
|
|
341
|
+
}
|
|
342
|
+
this.backplaneAdapter = null;
|
|
343
|
+
}
|
|
344
|
+
// Close all open connections with a "going away" code.
|
|
345
|
+
for (const conn of this.connections.values()) {
|
|
346
|
+
try {
|
|
347
|
+
conn.ws.close(1001, "Server shutting down");
|
|
348
|
+
} catch {
|
|
349
|
+
/* ignore */
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
this.connections.clear();
|
|
353
|
+
this.connectionsByWorkflow.clear();
|
|
354
|
+
this.rooms.clear();
|
|
355
|
+
this.wired = false;
|
|
356
|
+
// Clear the singleton — helper nodes should fail loudly if used
|
|
357
|
+
// after stop() rather than silently no-op against a stale handle.
|
|
358
|
+
if (_getActiveWebSocketTrigger() === this) {
|
|
359
|
+
_setActiveWebSocketTrigger(null);
|
|
360
|
+
}
|
|
361
|
+
this.destroyMonitoring();
|
|
362
|
+
this.logger.log("[blok][ws] stopped");
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* v0.7 follow-up — handle an envelope received from the backplane.
|
|
367
|
+
* Runs a LOCAL fan-out (skipping the senderId, since that's us).
|
|
368
|
+
* Does NOT re-publish — that would echo infinitely. Skips
|
|
369
|
+
* gracefully when the envelope shape doesn't validate (best-effort
|
|
370
|
+
* cross-version compat).
|
|
371
|
+
*/
|
|
372
|
+
private onBackplaneMessage(body: unknown): void {
|
|
373
|
+
if (!body || typeof body !== "object") return;
|
|
374
|
+
const env = body as BroadcastEnvelope;
|
|
375
|
+
if (!env.senderId || !env.roomKey || !env.payload) return;
|
|
376
|
+
// Skip self-publishes — local fan-out already happened on the
|
|
377
|
+
// publishing side.
|
|
378
|
+
if (env.senderId === this.senderId) return;
|
|
379
|
+
const set = this.rooms.get(env.roomKey);
|
|
380
|
+
if (!set) return;
|
|
381
|
+
const data = decodePayload(env);
|
|
382
|
+
for (const cid of set) {
|
|
383
|
+
if (env.exceptConnectionId && cid === env.exceptConnectionId) continue;
|
|
384
|
+
const conn = this.connections.get(cid);
|
|
385
|
+
if (!conn) continue;
|
|
386
|
+
try {
|
|
387
|
+
conn.ws.send(data as Parameters<typeof conn.ws.send>[0]);
|
|
388
|
+
} catch {
|
|
389
|
+
/* skip dead sockets */
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ---------------------------------------------------------------------------
|
|
395
|
+
// Route registration (one Hono route per workflow)
|
|
396
|
+
// ---------------------------------------------------------------------------
|
|
397
|
+
|
|
398
|
+
private registerWsRoute(entry: { workflowName: string; config: WebSocketTriggerConfig }): void {
|
|
399
|
+
if (!this.upgradeWebSocket) return;
|
|
400
|
+
const { workflowName, config } = entry;
|
|
401
|
+
this.logger.log(`[blok][ws] GET ${config.path} ← ${workflowName}`);
|
|
402
|
+
|
|
403
|
+
// One route per workflow; the handler factory captures workflowName
|
|
404
|
+
// and runs the workflow at each lifecycle event.
|
|
405
|
+
this.app.get(
|
|
406
|
+
config.path,
|
|
407
|
+
this.upgradeWebSocket((c) => {
|
|
408
|
+
// At upgrade time: extract path params + query so the
|
|
409
|
+
// `connect` workflow can route on roomId / userId / etc.
|
|
410
|
+
const pathParams = c.req.param() as Record<string, string>;
|
|
411
|
+
const queryParams = Object.fromEntries(new URL(c.req.url).searchParams);
|
|
412
|
+
const headers = Object.fromEntries(c.req.raw.headers);
|
|
413
|
+
|
|
414
|
+
// Check connection cap BEFORE accepting the upgrade. If
|
|
415
|
+
// over the limit, the handler factory throws so Hono
|
|
416
|
+
// closes the connection — slightly clunky API but matches
|
|
417
|
+
// the upgradeWebSocket contract (we can't easily return
|
|
418
|
+
// 503 from inside the factory).
|
|
419
|
+
const cap = typeof config.maxConnections === "number" ? config.maxConnections : DEFAULT_MAX_CONNECTIONS;
|
|
420
|
+
if (this.connections.size >= cap) {
|
|
421
|
+
this.logger.error(`[blok][ws] connection cap reached (${cap}) — refusing upgrade for ${workflowName}`);
|
|
422
|
+
this.counterMessagesDropped.add(1, { workflow_name: workflowName, reason: "max_connections" });
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const connectionId = uuid();
|
|
426
|
+
|
|
427
|
+
// `onOpen` runs synchronously on accept; we register the
|
|
428
|
+
// connection here and dispatch the `connect` workflow run
|
|
429
|
+
// asynchronously (don't block the upgrade).
|
|
430
|
+
return {
|
|
431
|
+
onOpen: (_evt, ws) => {
|
|
432
|
+
// Even if we're over-cap, we still need to register
|
|
433
|
+
// briefly so onMessage/onClose don't throw — close
|
|
434
|
+
// immediately if cap was already hit.
|
|
435
|
+
if (this.connections.size >= cap) {
|
|
436
|
+
ws.close(1013, "Server at capacity");
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const now = Date.now();
|
|
440
|
+
const state: ConnectionState = {
|
|
441
|
+
id: connectionId,
|
|
442
|
+
ws,
|
|
443
|
+
workflowName,
|
|
444
|
+
path: config.path,
|
|
445
|
+
pathParams,
|
|
446
|
+
rooms: new Set(),
|
|
447
|
+
attachment: undefined,
|
|
448
|
+
connectedAt: now,
|
|
449
|
+
lastActivity: now,
|
|
450
|
+
tokens:
|
|
451
|
+
typeof config.messageRateLimit === "number" ? config.messageRateLimit : DEFAULT_MESSAGE_RATE_LIMIT,
|
|
452
|
+
tokensRefilledAt: now,
|
|
453
|
+
};
|
|
454
|
+
this.connections.set(connectionId, state);
|
|
455
|
+
let workflowSet = this.connectionsByWorkflow.get(workflowName);
|
|
456
|
+
if (!workflowSet) {
|
|
457
|
+
workflowSet = new Set();
|
|
458
|
+
this.connectionsByWorkflow.set(workflowName, workflowSet);
|
|
459
|
+
}
|
|
460
|
+
workflowSet.add(connectionId);
|
|
461
|
+
this.counterConnections.add(1, { workflow_name: workflowName });
|
|
462
|
+
|
|
463
|
+
// Fire the `connect` workflow asynchronously — we
|
|
464
|
+
// can't await here; the upgrade callback is
|
|
465
|
+
// synchronous-ish from Hono's POV.
|
|
466
|
+
void this.dispatchEvent({
|
|
467
|
+
connectionId,
|
|
468
|
+
workflowName,
|
|
469
|
+
config,
|
|
470
|
+
eventKind: "connect",
|
|
471
|
+
payload: { event: "connect", headers, params: pathParams, query: queryParams },
|
|
472
|
+
});
|
|
473
|
+
},
|
|
474
|
+
onMessage: (evt, _ws) => {
|
|
475
|
+
const state = this.connections.get(connectionId);
|
|
476
|
+
if (!state) return;
|
|
477
|
+
state.lastActivity = Date.now();
|
|
478
|
+
this.counterMessagesReceived.add(1, { workflow_name: workflowName });
|
|
479
|
+
|
|
480
|
+
// Rate limit (per-connection token bucket).
|
|
481
|
+
if (!this.consumeRateToken(state, config)) {
|
|
482
|
+
this.counterMessagesDropped.add(1, {
|
|
483
|
+
workflow_name: workflowName,
|
|
484
|
+
reason: "rate_limit",
|
|
485
|
+
});
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Parse payload. Default mode "text" assumes JSON
|
|
490
|
+
// frames with `{event, data}` envelope; falls back
|
|
491
|
+
// to `{event: "message", data: <text>}` on parse
|
|
492
|
+
// failure. Binary mode skips JSON entirely and
|
|
493
|
+
// hands the buffer through unchanged.
|
|
494
|
+
let event = "message";
|
|
495
|
+
let payload: unknown = evt.data;
|
|
496
|
+
const mode = config.mode === "binary" ? "binary" : "text";
|
|
497
|
+
if (mode === "text" && typeof evt.data === "string") {
|
|
498
|
+
try {
|
|
499
|
+
const parsed = JSON.parse(evt.data) as { event?: unknown; data?: unknown };
|
|
500
|
+
if (typeof parsed.event === "string") event = parsed.event;
|
|
501
|
+
payload = parsed.data ?? parsed;
|
|
502
|
+
} catch {
|
|
503
|
+
/* not JSON — leave event as "message", payload as raw string */
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Allowlist check — if the trigger declares an
|
|
508
|
+
// `events` array, drop messages whose event name
|
|
509
|
+
// isn't in the list. Absent allowlist = accept all.
|
|
510
|
+
if (Array.isArray(config.events) && config.events.length > 0 && !config.events.includes(event)) {
|
|
511
|
+
this.counterMessagesDropped.add(1, {
|
|
512
|
+
workflow_name: workflowName,
|
|
513
|
+
reason: "event_not_allowed",
|
|
514
|
+
});
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
void this.dispatchEvent({
|
|
519
|
+
connectionId,
|
|
520
|
+
workflowName,
|
|
521
|
+
config,
|
|
522
|
+
eventKind: "message",
|
|
523
|
+
payload: { event, data: payload },
|
|
524
|
+
});
|
|
525
|
+
},
|
|
526
|
+
onClose: (evt, _ws) => {
|
|
527
|
+
void this.dispatchEvent({
|
|
528
|
+
connectionId,
|
|
529
|
+
workflowName,
|
|
530
|
+
config,
|
|
531
|
+
eventKind: "disconnect",
|
|
532
|
+
payload: { event: "disconnect", code: evt.code, reason: evt.reason },
|
|
533
|
+
}).finally(() => {
|
|
534
|
+
// Even if the disconnect workflow throws, free
|
|
535
|
+
// the connection record AFTER it finishes so
|
|
536
|
+
// `ctx.connection` is valid throughout the
|
|
537
|
+
// disconnect run.
|
|
538
|
+
this.removeConnection(connectionId);
|
|
539
|
+
});
|
|
540
|
+
},
|
|
541
|
+
onError: (_evt, _ws) => {
|
|
542
|
+
this.logger.error(`[blok][ws] connection error on ${workflowName} (id=${connectionId})`);
|
|
543
|
+
},
|
|
544
|
+
};
|
|
545
|
+
}),
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// ---------------------------------------------------------------------------
|
|
550
|
+
// Workflow dispatch
|
|
551
|
+
// ---------------------------------------------------------------------------
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Build a synthetic Context, attach `ctx.connection`, run the
|
|
555
|
+
* trigger's middleware chain + workflow body. One run per event.
|
|
556
|
+
*
|
|
557
|
+
* Uses TriggerBase's existing `createContext` + `applyMiddlewareChain`
|
|
558
|
+
* + `run` pipeline so all the standard primitives (concurrency,
|
|
559
|
+
* retries, idempotency, tracing, etc.) apply uniformly.
|
|
560
|
+
*/
|
|
561
|
+
private async dispatchEvent(opts: {
|
|
562
|
+
connectionId: string;
|
|
563
|
+
workflowName: string;
|
|
564
|
+
config: WebSocketTriggerConfig;
|
|
565
|
+
eventKind: "connect" | "message" | "disconnect";
|
|
566
|
+
payload: Record<string, unknown>;
|
|
567
|
+
}): Promise<void> {
|
|
568
|
+
const { connectionId, workflowName, eventKind, payload } = opts;
|
|
569
|
+
const state = this.connections.get(connectionId);
|
|
570
|
+
if (!state) return;
|
|
571
|
+
|
|
572
|
+
const requestId = uuid();
|
|
573
|
+
const triggerLabel = `websocket.${eventKind}`;
|
|
574
|
+
|
|
575
|
+
await this.tracer.startActiveSpan(`ws:${workflowName}:${eventKind}`, async (span: Span) => {
|
|
576
|
+
try {
|
|
577
|
+
// Initialize Configuration for the workflow (loads the
|
|
578
|
+
// step graph, applies the v2 normalizer, resolves nodes).
|
|
579
|
+
// Pass the workflow as the third `preloaded` argument so
|
|
580
|
+
// Configuration uses the in-registry definition directly
|
|
581
|
+
// instead of trying to load from disk — same pattern
|
|
582
|
+
// HttpTrigger uses for its file-based routing path.
|
|
583
|
+
const registry = WorkflowRegistry.getInstance();
|
|
584
|
+
const entry = registry.get(workflowName);
|
|
585
|
+
if (!entry) {
|
|
586
|
+
throw new Error(`[blok][ws] workflow "${workflowName}" not found in registry`);
|
|
587
|
+
}
|
|
588
|
+
await this.configuration.init(workflowName, this.nodeMap, entry.workflow);
|
|
589
|
+
|
|
590
|
+
const ctx: Context = this.createContext(undefined, workflowName, requestId);
|
|
591
|
+
ctx.request = {
|
|
592
|
+
body: payload,
|
|
593
|
+
headers: {} as RequestContext["headers"],
|
|
594
|
+
params: state.pathParams,
|
|
595
|
+
query: {},
|
|
596
|
+
} as unknown as RequestContext;
|
|
597
|
+
|
|
598
|
+
// Bind ctx.connection — the per-connection API. Helper
|
|
599
|
+
// nodes (@blokjs/ws-reply, @blokjs/ws-broadcast, @blokjs/ws-close)
|
|
600
|
+
// read this field directly to interact with the WS.
|
|
601
|
+
ctx.connection = this.buildConnectionContext(state);
|
|
602
|
+
|
|
603
|
+
// Run middleware chain (process-global → workflow-level →
|
|
604
|
+
// trigger-level). Identical to HttpTrigger/WorkerTrigger.
|
|
605
|
+
await this.applyMiddlewareChain(ctx, this.nodeMap);
|
|
606
|
+
|
|
607
|
+
await this.run(ctx);
|
|
608
|
+
|
|
609
|
+
span.setAttribute("workflow_name", workflowName);
|
|
610
|
+
span.setAttribute("event", eventKind);
|
|
611
|
+
span.setAttribute("connection_id", connectionId);
|
|
612
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
613
|
+
} catch (err) {
|
|
614
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
615
|
+
span.recordException(err as Error);
|
|
616
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: msg });
|
|
617
|
+
this.logger.error(`[blok][ws] ${triggerLabel} workflow ${workflowName} failed: ${msg}`);
|
|
618
|
+
} finally {
|
|
619
|
+
span.end();
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
private buildConnectionContext(state: ConnectionState): ConnectionContext {
|
|
625
|
+
const id = state.id;
|
|
626
|
+
const trigger = this;
|
|
627
|
+
return {
|
|
628
|
+
get id() {
|
|
629
|
+
return id;
|
|
630
|
+
},
|
|
631
|
+
send(data) {
|
|
632
|
+
const conn = trigger.connections.get(id);
|
|
633
|
+
if (!conn) return;
|
|
634
|
+
try {
|
|
635
|
+
// WSContext.send's strict type is Uint8Array<ArrayBuffer>;
|
|
636
|
+
// our interface uses the wider Uint8Array<ArrayBufferLike>
|
|
637
|
+
// (which TypeScript splits into ArrayBuffer | SharedArrayBuffer).
|
|
638
|
+
// SharedArrayBuffer can't legally cross WS frames anyway, so
|
|
639
|
+
// the cast is safe — we accept the strict subset at runtime.
|
|
640
|
+
conn.ws.send(data as Parameters<typeof conn.ws.send>[0]);
|
|
641
|
+
} catch (err) {
|
|
642
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
643
|
+
trigger.logger.error(`[blok][ws] send failed on ${id}: ${msg}`);
|
|
644
|
+
}
|
|
645
|
+
},
|
|
646
|
+
close(code, reason) {
|
|
647
|
+
const conn = trigger.connections.get(id);
|
|
648
|
+
if (!conn) return;
|
|
649
|
+
try {
|
|
650
|
+
conn.ws.close(code, reason);
|
|
651
|
+
} catch (err) {
|
|
652
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
653
|
+
trigger.logger.error(`[blok][ws] close failed on ${id}: ${msg}`);
|
|
654
|
+
}
|
|
655
|
+
},
|
|
656
|
+
setAttachment(value) {
|
|
657
|
+
const conn = trigger.connections.get(id);
|
|
658
|
+
if (!conn) return;
|
|
659
|
+
// Cap serialized size — per the spec's 2 KB Cloudflare-DO
|
|
660
|
+
// parity rule. Reject + warn on overflow rather than
|
|
661
|
+
// truncate (truncation produces malformed JSON on next read).
|
|
662
|
+
try {
|
|
663
|
+
const serialized = JSON.stringify(value);
|
|
664
|
+
if (serialized && Buffer.byteLength(serialized, "utf8") > ATTACHMENT_MAX_BYTES) {
|
|
665
|
+
trigger.logger.logLevel(
|
|
666
|
+
"warn",
|
|
667
|
+
`[blok][ws] attachment exceeds ${ATTACHMENT_MAX_BYTES} bytes on ${id}; rejected. Reduce attachment size.`,
|
|
668
|
+
);
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
} catch {
|
|
672
|
+
trigger.logger.logLevel("warn", `[blok][ws] attachment is not JSON-serializable on ${id}; rejected.`);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
conn.attachment = value;
|
|
676
|
+
},
|
|
677
|
+
get attachment() {
|
|
678
|
+
const conn = trigger.connections.get(id);
|
|
679
|
+
return conn?.attachment;
|
|
680
|
+
},
|
|
681
|
+
joinRoom(name) {
|
|
682
|
+
const conn = trigger.connections.get(id);
|
|
683
|
+
if (!conn) return;
|
|
684
|
+
const fullName = `${conn.workflowName}:${name}`;
|
|
685
|
+
let set = trigger.rooms.get(fullName);
|
|
686
|
+
if (!set) {
|
|
687
|
+
set = new Set();
|
|
688
|
+
trigger.rooms.set(fullName, set);
|
|
689
|
+
}
|
|
690
|
+
set.add(id);
|
|
691
|
+
conn.rooms.add(fullName);
|
|
692
|
+
},
|
|
693
|
+
leaveRoom(name) {
|
|
694
|
+
const conn = trigger.connections.get(id);
|
|
695
|
+
if (!conn) return;
|
|
696
|
+
const fullName = `${conn.workflowName}:${name}`;
|
|
697
|
+
const set = trigger.rooms.get(fullName);
|
|
698
|
+
if (set) {
|
|
699
|
+
set.delete(id);
|
|
700
|
+
if (set.size === 0) trigger.rooms.delete(fullName);
|
|
701
|
+
}
|
|
702
|
+
conn.rooms.delete(fullName);
|
|
703
|
+
},
|
|
704
|
+
get rooms() {
|
|
705
|
+
const conn = trigger.connections.get(id);
|
|
706
|
+
// Return short-name view (strip `workflowName:` prefix) so
|
|
707
|
+
// authors see what they joined.
|
|
708
|
+
const view = new Set<string>();
|
|
709
|
+
if (conn) {
|
|
710
|
+
const prefix = `${conn.workflowName}:`;
|
|
711
|
+
for (const r of conn.rooms) {
|
|
712
|
+
if (r.startsWith(prefix)) view.add(r.slice(prefix.length));
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return view;
|
|
716
|
+
},
|
|
717
|
+
broadcast(room, data, opts) {
|
|
718
|
+
const conn = trigger.connections.get(id);
|
|
719
|
+
if (!conn) return 0;
|
|
720
|
+
return trigger.broadcastToRoom({
|
|
721
|
+
workflowName: conn.workflowName,
|
|
722
|
+
room,
|
|
723
|
+
data,
|
|
724
|
+
exceptConnectionId: opts?.exceptSelf === true ? id : undefined,
|
|
725
|
+
});
|
|
726
|
+
},
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
private removeConnection(connectionId: string): void {
|
|
731
|
+
const state = this.connections.get(connectionId);
|
|
732
|
+
if (!state) return;
|
|
733
|
+
// Drain rooms.
|
|
734
|
+
for (const room of state.rooms) {
|
|
735
|
+
const set = this.rooms.get(room);
|
|
736
|
+
if (set) {
|
|
737
|
+
set.delete(connectionId);
|
|
738
|
+
if (set.size === 0) this.rooms.delete(room);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
// Drain workflow membership.
|
|
742
|
+
const wfSet = this.connectionsByWorkflow.get(state.workflowName);
|
|
743
|
+
if (wfSet) {
|
|
744
|
+
wfSet.delete(connectionId);
|
|
745
|
+
if (wfSet.size === 0) this.connectionsByWorkflow.delete(state.workflowName);
|
|
746
|
+
}
|
|
747
|
+
this.connections.delete(connectionId);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// ---------------------------------------------------------------------------
|
|
751
|
+
// Helper-node entry points (used by @blokjs/ws-broadcast et al.)
|
|
752
|
+
// ---------------------------------------------------------------------------
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Broadcast to all connections in a workflow-scoped room. Used by
|
|
756
|
+
* `@blokjs/ws-broadcast` via the singleton accessor below.
|
|
757
|
+
*/
|
|
758
|
+
broadcastToRoom(opts: {
|
|
759
|
+
workflowName: string;
|
|
760
|
+
room: string;
|
|
761
|
+
data: string | ArrayBuffer | Uint8Array;
|
|
762
|
+
exceptConnectionId?: string;
|
|
763
|
+
}): number {
|
|
764
|
+
const fullName = `${opts.workflowName}:${opts.room}`;
|
|
765
|
+
const set = this.rooms.get(fullName);
|
|
766
|
+
let count = 0;
|
|
767
|
+
if (set) {
|
|
768
|
+
for (const cid of set) {
|
|
769
|
+
if (opts.exceptConnectionId && cid === opts.exceptConnectionId) continue;
|
|
770
|
+
const conn = this.connections.get(cid);
|
|
771
|
+
if (!conn) continue;
|
|
772
|
+
try {
|
|
773
|
+
// Same Uint8Array<ArrayBufferLike> → Uint8Array<ArrayBuffer>
|
|
774
|
+
// narrowing as the per-connection `send` above. Safe at runtime.
|
|
775
|
+
conn.ws.send(opts.data as Parameters<typeof conn.ws.send>[0]);
|
|
776
|
+
count++;
|
|
777
|
+
} catch {
|
|
778
|
+
/* skip dead sockets */
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// v0.7 follow-up — also publish to the cross-process backplane
|
|
784
|
+
// so peer WS workers can fan out to their local connections in
|
|
785
|
+
// the same room. The publish path runs even when the local set
|
|
786
|
+
// was empty — there may be remote connections in this room.
|
|
787
|
+
// Fire-and-forget (broker publishes are async); failures are
|
|
788
|
+
// logged but don't break the local broadcast count.
|
|
789
|
+
if (this.backplaneAdapter && this.backplaneConfig) {
|
|
790
|
+
const envelope = encodeEnvelope(this.senderId, fullName, opts.data, opts.exceptConnectionId);
|
|
791
|
+
const topic = this.backplaneConfig.topic ?? DEFAULT_BACKPLANE_TOPIC;
|
|
792
|
+
void this.backplaneAdapter.publish(topic, envelope).catch((err: unknown) => {
|
|
793
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
794
|
+
this.logger.error(`[blok][ws] backplane publish failed: ${m}`);
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
return count;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/** Read-only stats for tests / Studio. */
|
|
802
|
+
getStats(): {
|
|
803
|
+
connections: number;
|
|
804
|
+
workflows: number;
|
|
805
|
+
rooms: number;
|
|
806
|
+
} {
|
|
807
|
+
return {
|
|
808
|
+
connections: this.connections.size,
|
|
809
|
+
workflows: this.connectionsByWorkflow.size,
|
|
810
|
+
rooms: this.rooms.size,
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// ---------------------------------------------------------------------------
|
|
815
|
+
// Internals
|
|
816
|
+
// ---------------------------------------------------------------------------
|
|
817
|
+
|
|
818
|
+
private getWebSocketWorkflows(): Array<{ workflowName: string; config: WebSocketTriggerConfig }> {
|
|
819
|
+
const registry = WorkflowRegistry.getInstance();
|
|
820
|
+
const out: Array<{ workflowName: string; config: WebSocketTriggerConfig }> = [];
|
|
821
|
+
for (const entry of registry.list()) {
|
|
822
|
+
const wf = entry.workflow as { trigger?: { websocket?: WebSocketTriggerConfig } } | undefined;
|
|
823
|
+
const wsCfg = wf?.trigger?.websocket;
|
|
824
|
+
if (!wsCfg || typeof wsCfg.path !== "string") continue;
|
|
825
|
+
out.push({ workflowName: entry.name, config: wsCfg });
|
|
826
|
+
}
|
|
827
|
+
return out;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
private consumeRateToken(state: ConnectionState, config: WebSocketTriggerConfig): boolean {
|
|
831
|
+
const limit =
|
|
832
|
+
typeof config.messageRateLimit === "number" && config.messageRateLimit > 0
|
|
833
|
+
? config.messageRateLimit
|
|
834
|
+
: DEFAULT_MESSAGE_RATE_LIMIT;
|
|
835
|
+
const now = Date.now();
|
|
836
|
+
const elapsedMs = now - state.tokensRefilledAt;
|
|
837
|
+
if (elapsedMs > 0) {
|
|
838
|
+
const refill = (elapsedMs / 1000) * limit;
|
|
839
|
+
state.tokens = Math.min(limit, state.tokens + refill);
|
|
840
|
+
state.tokensRefilledAt = now;
|
|
841
|
+
}
|
|
842
|
+
if (state.tokens < 1) return false;
|
|
843
|
+
state.tokens -= 1;
|
|
844
|
+
return true;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
private startHeartbeat(): void {
|
|
848
|
+
if (this.heartbeatTimer) return;
|
|
849
|
+
this.heartbeatTimer = setInterval(() => {
|
|
850
|
+
const now = Date.now();
|
|
851
|
+
for (const [id, state] of this.connections) {
|
|
852
|
+
const idleMs = now - state.lastActivity;
|
|
853
|
+
if (idleMs > 2 * DEFAULT_HEARTBEAT_INTERVAL_MS) {
|
|
854
|
+
// Stale connection — close it.
|
|
855
|
+
try {
|
|
856
|
+
state.ws.close(1011, "Heartbeat timeout");
|
|
857
|
+
} catch {
|
|
858
|
+
/* ignore */
|
|
859
|
+
}
|
|
860
|
+
this.removeConnection(id);
|
|
861
|
+
}
|
|
862
|
+
// `ws` library's underlying socket auto-pings via `ws.ping()`
|
|
863
|
+
// — we just need to track lastActivity which onMessage
|
|
864
|
+
// updates. No explicit ping needed from the trigger layer.
|
|
865
|
+
}
|
|
866
|
+
}, DEFAULT_HEARTBEAT_INTERVAL_MS);
|
|
867
|
+
// Don't keep the event loop alive on the heartbeat alone.
|
|
868
|
+
if (typeof this.heartbeatTimer.unref === "function") this.heartbeatTimer.unref();
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
private stopHeartbeat(): void {
|
|
872
|
+
if (this.heartbeatTimer) {
|
|
873
|
+
clearInterval(this.heartbeatTimer);
|
|
874
|
+
this.heartbeatTimer = null;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// -----------------------------------------------------------------------------
|
|
880
|
+
// Singleton accessor for helper nodes
|
|
881
|
+
// -----------------------------------------------------------------------------
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* Singleton registry — helper nodes (`@blokjs/ws-broadcast` etc.) read
|
|
885
|
+
* the active trigger via this accessor to call `broadcastToRoom(...)`.
|
|
886
|
+
* Set once at trigger construction; reset on `stop()`.
|
|
887
|
+
*
|
|
888
|
+
* Why not pass through ctx? `ctx.connection` is the per-connection API.
|
|
889
|
+
* The cross-connection `broadcast` operation needs the trigger
|
|
890
|
+
* instance, which manages all connections. A singleton is the cheapest
|
|
891
|
+
* way for helper nodes to find the trigger without threading it
|
|
892
|
+
* through every step's inputs.
|
|
893
|
+
*/
|
|
894
|
+
let activeTrigger: WebSocketTrigger | null = null;
|
|
895
|
+
|
|
896
|
+
export function _setActiveWebSocketTrigger(trigger: WebSocketTrigger | null): void {
|
|
897
|
+
activeTrigger = trigger;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
export function _getActiveWebSocketTrigger(): WebSocketTrigger | null {
|
|
901
|
+
return activeTrigger;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Re-export types kept stable from prior scaffold so any external
|
|
905
|
+
// references (Studio, docs links) keep resolving. Most are unused by
|
|
906
|
+
// the new implementation but harmless to re-export.
|
|
907
|
+
export type { ConnectionContext } from "@blokjs/shared";
|
|
908
|
+
|
|
909
|
+
// Unused but exported for backward compatibility with the prior scaffold.
|
|
910
|
+
export type WebSocketMessageType = "text" | "binary";
|