@blokjs/runner 0.2.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/dist/Blok.js +32 -3
  2. package/dist/Blok.js.map +1 -1
  3. package/dist/Configuration.d.ts +59 -5
  4. package/dist/Configuration.js +366 -96
  5. package/dist/Configuration.js.map +1 -1
  6. package/dist/ForEachNode.d.ts +59 -0
  7. package/dist/ForEachNode.js +522 -0
  8. package/dist/ForEachNode.js.map +1 -0
  9. package/dist/LoopMaxIterationsError.d.ts +11 -0
  10. package/dist/LoopMaxIterationsError.js +18 -0
  11. package/dist/LoopMaxIterationsError.js.map +1 -0
  12. package/dist/LoopNode.d.ts +36 -0
  13. package/dist/LoopNode.js +182 -0
  14. package/dist/LoopNode.js.map +1 -0
  15. package/dist/PayloadTooLargeError.d.ts +19 -0
  16. package/dist/PayloadTooLargeError.js +29 -0
  17. package/dist/PayloadTooLargeError.js.map +1 -0
  18. package/dist/RunCancelledError.d.ts +17 -0
  19. package/dist/RunCancelledError.js +25 -0
  20. package/dist/RunCancelledError.js.map +1 -0
  21. package/dist/Runner.d.ts +11 -1
  22. package/dist/Runner.js +9 -2
  23. package/dist/Runner.js.map +1 -1
  24. package/dist/RunnerSteps.js +648 -44
  25. package/dist/RunnerSteps.js.map +1 -1
  26. package/dist/RuntimeAdapterNode.d.ts +2 -1
  27. package/dist/RuntimeAdapterNode.js +2 -2
  28. package/dist/RuntimeAdapterNode.js.map +1 -1
  29. package/dist/RuntimeRegistry.d.ts +23 -2
  30. package/dist/RuntimeRegistry.js +31 -2
  31. package/dist/RuntimeRegistry.js.map +1 -1
  32. package/dist/SubworkflowNode.d.ts +181 -0
  33. package/dist/SubworkflowNode.js +479 -0
  34. package/dist/SubworkflowNode.js.map +1 -0
  35. package/dist/SwitchNode.d.ts +37 -0
  36. package/dist/SwitchNode.js +153 -0
  37. package/dist/SwitchNode.js.map +1 -0
  38. package/dist/TriggerBase.d.ts +178 -0
  39. package/dist/TriggerBase.js +1032 -5
  40. package/dist/TriggerBase.js.map +1 -1
  41. package/dist/TryCatchNode.d.ts +32 -0
  42. package/dist/TryCatchNode.js +207 -0
  43. package/dist/TryCatchNode.js.map +1 -0
  44. package/dist/WaitDispatchRequest.d.ts +38 -0
  45. package/dist/WaitDispatchRequest.js +13 -0
  46. package/dist/WaitDispatchRequest.js.map +1 -0
  47. package/dist/WaitNode.d.ts +23 -0
  48. package/dist/WaitNode.js +26 -0
  49. package/dist/WaitNode.js.map +1 -0
  50. package/dist/adapters/grpc/GrpcCodec.js +2 -2
  51. package/dist/adapters/grpc/GrpcRuntimeAdapter.d.ts +6 -4
  52. package/dist/adapters/grpc/GrpcRuntimeAdapter.js +6 -4
  53. package/dist/adapters/grpc/GrpcRuntimeAdapter.js.map +1 -1
  54. package/dist/adapters/grpc/types.d.ts +7 -5
  55. package/dist/adapters/grpc/types.js.map +1 -1
  56. package/dist/adapters/transport.d.ts +12 -41
  57. package/dist/adapters/transport.js +21 -70
  58. package/dist/adapters/transport.js.map +1 -1
  59. package/dist/cache/NodeResultCache.js +7 -0
  60. package/dist/cache/NodeResultCache.js.map +1 -1
  61. package/dist/concurrency/ConcurrencyBackend.d.ts +61 -0
  62. package/dist/concurrency/ConcurrencyBackend.js +20 -0
  63. package/dist/concurrency/ConcurrencyBackend.js.map +1 -0
  64. package/dist/concurrency/ConcurrencyLimitError.d.ts +37 -0
  65. package/dist/concurrency/ConcurrencyLimitError.js +16 -0
  66. package/dist/concurrency/ConcurrencyLimitError.js.map +1 -0
  67. package/dist/concurrency/NatsKvConcurrencyBackend.d.ts +64 -0
  68. package/dist/concurrency/NatsKvConcurrencyBackend.js +310 -0
  69. package/dist/concurrency/NatsKvConcurrencyBackend.js.map +1 -0
  70. package/dist/concurrency/QueueExpiredError.d.ts +40 -0
  71. package/dist/concurrency/QueueExpiredError.js +15 -0
  72. package/dist/concurrency/QueueExpiredError.js.map +1 -0
  73. package/dist/concurrency/RedisConcurrencyBackend.d.ts +64 -0
  74. package/dist/concurrency/RedisConcurrencyBackend.js +374 -0
  75. package/dist/concurrency/RedisConcurrencyBackend.js.map +1 -0
  76. package/dist/concurrency/createConcurrencyBackend.d.ts +24 -0
  77. package/dist/concurrency/createConcurrencyBackend.js +38 -0
  78. package/dist/concurrency/createConcurrencyBackend.js.map +1 -0
  79. package/dist/concurrency/readConcurrencyConfig.d.ts +60 -0
  80. package/dist/concurrency/readConcurrencyConfig.js +60 -0
  81. package/dist/concurrency/readConcurrencyConfig.js.map +1 -0
  82. package/dist/defineNode.d.ts +8 -0
  83. package/dist/defineNode.js +25 -5
  84. package/dist/defineNode.js.map +1 -1
  85. package/dist/graphql/GraphQLSchemaGenerator.js +1 -1
  86. package/dist/graphql/GraphQLSchemaGenerator.js.map +1 -1
  87. package/dist/idempotency/resolveIdempotencyKey.d.ts +20 -0
  88. package/dist/idempotency/resolveIdempotencyKey.js +37 -0
  89. package/dist/idempotency/resolveIdempotencyKey.js.map +1 -0
  90. package/dist/index.d.ts +30 -6
  91. package/dist/index.js +55 -6
  92. package/dist/index.js.map +1 -1
  93. package/dist/marketplace/RuntimeCatalog.d.ts +6 -0
  94. package/dist/marketplace/RuntimeCatalog.js.map +1 -1
  95. package/dist/marketplace/RuntimeDiscovery.d.ts +2 -2
  96. package/dist/marketplace/RuntimeDiscovery.js +18 -6
  97. package/dist/marketplace/RuntimeDiscovery.js.map +1 -1
  98. package/dist/monitoring/ConcurrencyMetrics.d.ts +82 -0
  99. package/dist/monitoring/ConcurrencyMetrics.js +139 -0
  100. package/dist/monitoring/ConcurrencyMetrics.js.map +1 -0
  101. package/dist/monitoring/ForEachWaitMetrics.d.ts +22 -0
  102. package/dist/monitoring/ForEachWaitMetrics.js +36 -0
  103. package/dist/monitoring/ForEachWaitMetrics.js.map +1 -0
  104. package/dist/monitoring/JanitorMetrics.d.ts +27 -0
  105. package/dist/monitoring/JanitorMetrics.js +48 -0
  106. package/dist/monitoring/JanitorMetrics.js.map +1 -0
  107. package/dist/openapi/OpenAPIGenerator.js +7 -2
  108. package/dist/openapi/OpenAPIGenerator.js.map +1 -1
  109. package/dist/runtime/PrimitiveStack.d.ts +64 -0
  110. package/dist/runtime/PrimitiveStack.js +92 -0
  111. package/dist/runtime/PrimitiveStack.js.map +1 -0
  112. package/dist/scheduling/DebounceBackend.d.ts +108 -0
  113. package/dist/scheduling/DebounceBackend.js +23 -0
  114. package/dist/scheduling/DebounceBackend.js.map +1 -0
  115. package/dist/scheduling/DebounceCoordinator.d.ts +141 -0
  116. package/dist/scheduling/DebounceCoordinator.js +362 -0
  117. package/dist/scheduling/DebounceCoordinator.js.map +1 -0
  118. package/dist/scheduling/DeferredDispatchSignal.d.ts +50 -0
  119. package/dist/scheduling/DeferredDispatchSignal.js +14 -0
  120. package/dist/scheduling/DeferredDispatchSignal.js.map +1 -0
  121. package/dist/scheduling/DeferredRunScheduler.d.ts +96 -0
  122. package/dist/scheduling/DeferredRunScheduler.js +256 -0
  123. package/dist/scheduling/DeferredRunScheduler.js.map +1 -0
  124. package/dist/scheduling/NatsKvDebounceBackend.d.ts +53 -0
  125. package/dist/scheduling/NatsKvDebounceBackend.js +334 -0
  126. package/dist/scheduling/NatsKvDebounceBackend.js.map +1 -0
  127. package/dist/scheduling/RedisDebounceBackend.d.ts +49 -0
  128. package/dist/scheduling/RedisDebounceBackend.js +356 -0
  129. package/dist/scheduling/RedisDebounceBackend.js.map +1 -0
  130. package/dist/scheduling/createDebounceBackend.d.ts +25 -0
  131. package/dist/scheduling/createDebounceBackend.js +39 -0
  132. package/dist/scheduling/createDebounceBackend.js.map +1 -0
  133. package/dist/scheduling/readSchedulingConfig.d.ts +24 -0
  134. package/dist/scheduling/readSchedulingConfig.js +52 -0
  135. package/dist/scheduling/readSchedulingConfig.js.map +1 -0
  136. package/dist/security/AuditLogger.js +1 -1
  137. package/dist/security/AuditLogger.js.map +1 -1
  138. package/dist/security/AuthMiddleware.d.ts +19 -20
  139. package/dist/security/AuthMiddleware.js +35 -20
  140. package/dist/security/AuthMiddleware.js.map +1 -1
  141. package/dist/security/OAuthProvider.js +2 -2
  142. package/dist/security/OAuthProvider.js.map +1 -1
  143. package/dist/security/SecretManager.js +14 -13
  144. package/dist/security/SecretManager.js.map +1 -1
  145. package/dist/security/index.d.ts +3 -1
  146. package/dist/security/index.js +3 -1
  147. package/dist/security/index.js.map +1 -1
  148. package/dist/testing/TestHarness.d.ts +27 -12
  149. package/dist/testing/TestHarness.js +19 -3
  150. package/dist/testing/TestHarness.js.map +1 -1
  151. package/dist/testing/WorkflowTestRunner.js +0 -7
  152. package/dist/testing/WorkflowTestRunner.js.map +1 -1
  153. package/dist/timeouts/StepTimeoutError.d.ts +22 -0
  154. package/dist/timeouts/StepTimeoutError.js +31 -0
  155. package/dist/timeouts/StepTimeoutError.js.map +1 -0
  156. package/dist/tracing/InMemoryRunStore.d.ts +41 -1
  157. package/dist/tracing/InMemoryRunStore.js +239 -0
  158. package/dist/tracing/InMemoryRunStore.js.map +1 -1
  159. package/dist/tracing/Janitor.d.ts +70 -0
  160. package/dist/tracing/Janitor.js +150 -0
  161. package/dist/tracing/Janitor.js.map +1 -0
  162. package/dist/tracing/PostgresRunStore.d.ts +57 -1
  163. package/dist/tracing/PostgresRunStore.js +711 -6
  164. package/dist/tracing/PostgresRunStore.js.map +1 -1
  165. package/dist/tracing/RoutingDiagnostics.d.ts +55 -0
  166. package/dist/tracing/RoutingDiagnostics.js +50 -0
  167. package/dist/tracing/RoutingDiagnostics.js.map +1 -0
  168. package/dist/tracing/RunStore.d.ts +181 -1
  169. package/dist/tracing/RunTracker.d.ts +244 -9
  170. package/dist/tracing/RunTracker.js +594 -1
  171. package/dist/tracing/RunTracker.js.map +1 -1
  172. package/dist/tracing/SqliteRunStore.d.ts +79 -2
  173. package/dist/tracing/SqliteRunStore.js +775 -16
  174. package/dist/tracing/SqliteRunStore.js.map +1 -1
  175. package/dist/tracing/TraceRouter.d.ts +20 -2
  176. package/dist/tracing/TraceRouter.js +612 -6
  177. package/dist/tracing/TraceRouter.js.map +1 -1
  178. package/dist/tracing/createStore.js +14 -3
  179. package/dist/tracing/createStore.js.map +1 -1
  180. package/dist/tracing/metadataFilter.d.ts +63 -0
  181. package/dist/tracing/metadataFilter.js +224 -0
  182. package/dist/tracing/metadataFilter.js.map +1 -0
  183. package/dist/tracing/sanitize.d.ts +11 -0
  184. package/dist/tracing/sanitize.js +29 -0
  185. package/dist/tracing/sanitize.js.map +1 -1
  186. package/dist/tracing/types.d.ts +672 -2
  187. package/dist/utils/createChildContext.d.ts +32 -0
  188. package/dist/utils/createChildContext.js +113 -0
  189. package/dist/utils/createChildContext.js.map +1 -0
  190. package/dist/utils/envAllowlist.d.ts +35 -0
  191. package/dist/utils/envAllowlist.js +113 -0
  192. package/dist/utils/envAllowlist.js.map +1 -0
  193. package/dist/version/RuntimeVersionValidator.d.ts +38 -0
  194. package/dist/version/RuntimeVersionValidator.js +121 -0
  195. package/dist/version/RuntimeVersionValidator.js.map +1 -0
  196. package/dist/visualization/WorkflowVisualizer.js +4 -4
  197. package/dist/visualization/WorkflowVisualizer.js.map +1 -1
  198. package/dist/workflow/PersistenceHelper.d.ts +18 -10
  199. package/dist/workflow/PersistenceHelper.js +35 -9
  200. package/dist/workflow/PersistenceHelper.js.map +1 -1
  201. package/dist/workflow/WorkflowNormalizer.d.ts +48 -42
  202. package/dist/workflow/WorkflowNormalizer.js +650 -18
  203. package/dist/workflow/WorkflowNormalizer.js.map +1 -1
  204. package/dist/workflow/WorkflowRegistry.d.ts +186 -0
  205. package/dist/workflow/WorkflowRegistry.js +202 -0
  206. package/dist/workflow/WorkflowRegistry.js.map +1 -0
  207. package/dist/workflow/sampleBody.d.ts +54 -0
  208. package/dist/workflow/sampleBody.js +320 -0
  209. package/dist/workflow/sampleBody.js.map +1 -0
  210. package/package.json +3 -8
  211. package/dist/adapters/HttpRuntimeAdapter.d.ts +0 -79
  212. package/dist/adapters/HttpRuntimeAdapter.js +0 -233
  213. package/dist/adapters/HttpRuntimeAdapter.js.map +0 -1
@@ -1,12 +1,16 @@
1
+ // import { NodeBase } from "@blokjs/shared";
2
+ // import { z } from "zod";
3
+ import { sep as pathSep, resolve as resolvePath } from "node:path";
4
+ import { tryParseDuration } from "@blokjs/helper";
1
5
  import ConfigurationResolver from "./ConfigurationResolver";
2
6
  import RunnerNode from "./RunnerNode";
3
7
  import { RuntimeAdapterNode } from "./RuntimeAdapterNode";
4
8
  import { RuntimeRegistry } from "./RuntimeRegistry";
5
- import { HttpRuntimeAdapter } from "./adapters/HttpRuntimeAdapter";
6
9
  import { NodeJsRuntimeAdapter } from "./adapters/NodeJsRuntimeAdapter";
7
10
  import { GrpcRuntimeAdapter } from "./adapters/grpc/GrpcRuntimeAdapter";
8
11
  import { DEFAULT_GRPC_PORTS, GRPC_DEFAULTS } from "./adapters/grpc/types";
9
- import { isLoopbackHost, isStreamLogsEnabled, isStrictTlsEnabled, loadTlsConfigForKind, resolveHealthCheckFailureThreshold, resolveHealthCheckIntervalMs, resolveTransportForKind, } from "./adapters/transport";
12
+ import { assertGrpcOnlyTransport, isLoopbackHost, isStreamLogsEnabled, isStrictTlsEnabled, loadTlsConfigForKind, resolveHealthCheckFailureThreshold, resolveHealthCheckIntervalMs, } from "./adapters/transport";
13
+ import { RuntimeVersionValidator } from "./version/RuntimeVersionValidator";
10
14
  export default class Configuration {
11
15
  workflow = {};
12
16
  name;
@@ -14,6 +18,14 @@ export default class Configuration {
14
18
  steps;
15
19
  nodes;
16
20
  trigger;
21
+ /**
22
+ * v0.5.2 — workflow-level middleware chain. Populated from the
23
+ * normalized workflow's `appliedMiddleware` field. HTTP and Worker
24
+ * triggers prepend this list to their own `trigger.<kind>.middleware`
25
+ * before invoking the chain, so workflow-level entries run BEFORE
26
+ * trigger-level entries on every request.
27
+ */
28
+ appliedMiddleware;
17
29
  static loaded_nodes = {};
18
30
  globalOptions;
19
31
  constructor() {
@@ -22,87 +34,40 @@ export default class Configuration {
22
34
  this.version = "";
23
35
  this.name = "";
24
36
  this.trigger = {};
37
+ this.appliedMiddleware = [];
25
38
  this.initializeRuntimeRegistry();
26
39
  }
27
40
  /**
28
41
  * Initialize the RuntimeRegistry with built-in adapters.
29
42
  *
30
- * Registers `NodeJsRuntimeAdapter` for in-process JS nodes, then for each
31
- * SDK language picks `HttpRuntimeAdapter` or `GrpcRuntimeAdapter` based on
32
- * {@link resolveTransportForKind} (env-driven). This is the single point
33
- * of transport selection — no other file branches on transport.
43
+ * Registers `NodeJsRuntimeAdapter` for in-process JS nodes, then a
44
+ * `GrpcRuntimeAdapter` per SDK language. gRPC is the sole transport
45
+ * since v0.5 `assertGrpcOnlyTransport` throws if the operator still
46
+ * has `RUNTIME_TRANSPORT=http` set.
34
47
  */
35
48
  initializeRuntimeRegistry() {
49
+ assertGrpcOnlyTransport();
36
50
  const registry = RuntimeRegistry.getInstance();
37
51
  if (!registry.has("nodejs")) {
38
52
  registry.register(new NodeJsRuntimeAdapter());
39
53
  }
40
54
  const sdkLanguages = [
41
- {
42
- kind: "go",
43
- hostEnv: "RUNTIME_GO_HOST",
44
- httpPortEnv: "RUNTIME_GO_PORT",
45
- grpcPortEnv: "RUNTIME_GO_GRPC_PORT",
46
- defaultHttpPort: 9001,
47
- },
48
- {
49
- kind: "rust",
50
- hostEnv: "RUNTIME_RUST_HOST",
51
- httpPortEnv: "RUNTIME_RUST_PORT",
52
- grpcPortEnv: "RUNTIME_RUST_GRPC_PORT",
53
- defaultHttpPort: 9002,
54
- },
55
- {
56
- kind: "java",
57
- hostEnv: "RUNTIME_JAVA_HOST",
58
- httpPortEnv: "RUNTIME_JAVA_PORT",
59
- grpcPortEnv: "RUNTIME_JAVA_GRPC_PORT",
60
- defaultHttpPort: 9003,
61
- },
62
- {
63
- kind: "csharp",
64
- hostEnv: "RUNTIME_CSHARP_HOST",
65
- httpPortEnv: "RUNTIME_CSHARP_PORT",
66
- grpcPortEnv: "RUNTIME_CSHARP_GRPC_PORT",
67
- defaultHttpPort: 9004,
68
- },
69
- {
70
- kind: "php",
71
- hostEnv: "RUNTIME_PHP_HOST",
72
- httpPortEnv: "RUNTIME_PHP_PORT",
73
- grpcPortEnv: "RUNTIME_PHP_GRPC_PORT",
74
- defaultHttpPort: 9005,
75
- },
76
- {
77
- kind: "ruby",
78
- hostEnv: "RUNTIME_RUBY_HOST",
79
- httpPortEnv: "RUNTIME_RUBY_PORT",
80
- grpcPortEnv: "RUNTIME_RUBY_GRPC_PORT",
81
- defaultHttpPort: 9006,
82
- },
83
- {
84
- kind: "python3",
85
- hostEnv: "RUNTIME_PYTHON3_HOST",
86
- httpPortEnv: "RUNTIME_PYTHON3_PORT",
87
- grpcPortEnv: "RUNTIME_PYTHON3_GRPC_PORT",
88
- defaultHttpPort: 9007,
89
- },
55
+ { kind: "go", hostEnv: "RUNTIME_GO_HOST", grpcPortEnv: "RUNTIME_GO_GRPC_PORT" },
56
+ { kind: "rust", hostEnv: "RUNTIME_RUST_HOST", grpcPortEnv: "RUNTIME_RUST_GRPC_PORT" },
57
+ { kind: "java", hostEnv: "RUNTIME_JAVA_HOST", grpcPortEnv: "RUNTIME_JAVA_GRPC_PORT" },
58
+ { kind: "csharp", hostEnv: "RUNTIME_CSHARP_HOST", grpcPortEnv: "RUNTIME_CSHARP_GRPC_PORT" },
59
+ { kind: "php", hostEnv: "RUNTIME_PHP_HOST", grpcPortEnv: "RUNTIME_PHP_GRPC_PORT" },
60
+ { kind: "ruby", hostEnv: "RUNTIME_RUBY_HOST", grpcPortEnv: "RUNTIME_RUBY_GRPC_PORT" },
61
+ { kind: "python3", hostEnv: "RUNTIME_PYTHON3_HOST", grpcPortEnv: "RUNTIME_PYTHON3_GRPC_PORT" },
90
62
  ];
91
63
  for (const lang of sdkLanguages) {
92
64
  if (registry.has(lang.kind))
93
65
  continue;
94
- const transport = resolveTransportForKind(lang.kind);
95
66
  const host = process.env[lang.hostEnv] || "localhost";
96
- const adapter = transport === "grpc"
97
- ? this.buildGrpcAdapter(lang.kind, host, lang.grpcPortEnv)
98
- : this.buildHttpAdapter(lang.kind, host, lang.httpPortEnv, lang.defaultHttpPort);
67
+ const adapter = this.buildGrpcAdapter(lang.kind, host, lang.grpcPortEnv);
99
68
  registry.register(adapter);
100
69
  }
101
70
  }
102
- buildHttpAdapter(kind, host, portEnv, defaultPort) {
103
- const port = process.env[portEnv] ? Number.parseInt(process.env[portEnv], 10) : defaultPort;
104
- return new HttpRuntimeAdapter(kind, host, port);
105
- }
106
71
  buildGrpcAdapter(kind, host, portEnv) {
107
72
  const defaultPort = DEFAULT_GRPC_PORTS[kind];
108
73
  const port = process.env[portEnv] ? Number.parseInt(process.env[portEnv], 10) : defaultPort;
@@ -156,9 +121,17 @@ export default class Configuration {
156
121
  if (preloaded !== undefined) {
157
122
  // Boot-time scan path — workflow object already loaded, just
158
123
  // normalize it through the same v1→v2 pipeline as disk-loaded
159
- // workflows.
124
+ // workflows. **Deep-clone first** so per-request mutations
125
+ // (`NodeBase.blueprintMapper` → `mapper.replaceObjectStrings`
126
+ // resolves `js/...` expressions in place) don't bleed across
127
+ // requests by baking the first request's resolved values into
128
+ // the shared route-table workflow object. JSON-clone is safe:
129
+ // workflow definitions are pure data, and helper proxies like
130
+ // `$.req.body` serialize to their `js/...` string form via
131
+ // `Symbol.toPrimitive` / `toJSON`.
160
132
  const { normalizeWorkflow } = await import("./workflow/WorkflowNormalizer");
161
- this.workflow = normalizeWorkflow(preloaded, workflowNameInPath);
133
+ const fresh = JSON.parse(JSON.stringify(preloaded));
134
+ this.workflow = normalizeWorkflow(fresh, workflowNameInPath);
162
135
  }
163
136
  else {
164
137
  const resolver = new ConfigurationResolver(opts);
@@ -173,6 +146,11 @@ export default class Configuration {
173
146
  this.version = this.workflow.version;
174
147
  this.name = this.workflow.name;
175
148
  this.trigger = this.workflow.trigger;
149
+ // Workflow-level middleware list (v0.5.2). Lives on the normalized
150
+ // workflow as `appliedMiddleware` — see WorkflowNormalizer for the
151
+ // schema overload (`middleware: string[]` at the top level).
152
+ const wfWithApplied = this.workflow;
153
+ this.appliedMiddleware = Array.isArray(wfWithApplied.appliedMiddleware) ? wfWithApplied.appliedMiddleware : [];
176
154
  }
177
155
  async getSteps(blueprint_steps) {
178
156
  const nodes = [];
@@ -191,13 +169,6 @@ export default class Configuration {
191
169
  node.name = step.name;
192
170
  node.active = step.active !== undefined ? step.active : true;
193
171
  node.stop = step.stop !== undefined ? step.stop : false;
194
- // Pass `set_var` through verbatim — DO NOT default to `false`. The
195
- // `false` value short-circuits PersistenceHelper.applyStepOutput
196
- // and silently disables v2's default-store rule. Legacy v1
197
- // workflows that explicitly set `set_var: false` are normalized
198
- // to `ephemeral: true` upstream by WorkflowNormalizer.
199
- if (step.set_var !== undefined)
200
- node.set_var = step.set_var;
201
172
  // V2 persistence knobs — read by PersistenceHelper.applyStepOutput.
202
173
  // `as` renames the state key; `spread` flattens result.data into
203
174
  // state; `ephemeral: true` skips persistence entirely. Default
@@ -205,6 +176,27 @@ export default class Configuration {
205
176
  node.as = step.as;
206
177
  node.spread = step.spread === true;
207
178
  node.ephemeral = step.ephemeral === true;
179
+ // V2 idempotency cache + retry knobs — read by RunnerSteps before
180
+ // delegating to step.process(). Caching layers ABOVE
181
+ // PersistenceHelper; retry wraps the same call site.
182
+ const v2Idem = step;
183
+ if (v2Idem.idempotencyKey !== undefined)
184
+ node.idempotencyKey = v2Idem.idempotencyKey;
185
+ if (v2Idem.idempotencyKeyTTL !== undefined)
186
+ node.idempotencyKeyTTL = v2Idem.idempotencyKeyTTL;
187
+ if (v2Idem.retry !== undefined)
188
+ node.retry = v2Idem.retry;
189
+ // V2 sub-workflow knobs — read by SubworkflowNode at run time.
190
+ if (v2Idem.subworkflow !== undefined)
191
+ node.subworkflow = v2Idem.subworkflow;
192
+ if (v2Idem.wait !== undefined)
193
+ node.wait = v2Idem.wait;
194
+ // Tier 2 quick-wins — parse maxDuration string/number → ms.
195
+ if (v2Idem.maxDuration !== undefined) {
196
+ const parsed = tryParseDuration(v2Idem.maxDuration);
197
+ if (parsed !== null)
198
+ node.maxDurationMs = parsed;
199
+ }
208
200
  nodes.push(node);
209
201
  }
210
202
  return nodes;
@@ -222,10 +214,17 @@ export default class Configuration {
222
214
  const hasOutputs = currentNode.mapper !== undefined;
223
215
  if (isFlowWithProperties) {
224
216
  const steps = currentNode.steps;
225
- nodes[key] = await this.getFlow(steps);
226
- const copyBlueprintNode = { ...workflow_nodes[key] };
227
- copyBlueprintNode.steps = [];
228
- nodes[key] = { ...nodes[key], ...copyBlueprintNode };
217
+ const flow = await this.getFlow(steps);
218
+ // Spread the metadata FIRST, then the resolved flow — this
219
+ // keeps the resolved NodeBase[] in `flow.steps` and lets
220
+ // the metadata (e.g. forEach's in/as/mode/concurrency,
221
+ // loop's while/maxIterations) survive on the merged config.
222
+ // The earlier code spread metadata AFTER flow with a
223
+ // `copyBlueprintNode.steps = []` reset, which clobbered
224
+ // the resolved steps array — broken for any node config
225
+ // that needed both inner steps AND sibling fields.
226
+ const { steps: _drop, ...metadata } = workflow_nodes[key];
227
+ nodes[key] = { ...metadata, ...flow };
229
228
  }
230
229
  else if (isFlow) {
231
230
  const steps = currentNode.steps;
@@ -251,6 +250,47 @@ export default class Configuration {
251
250
  catch: await this.getFlow(currentNode.catch.steps),
252
251
  };
253
252
  }
253
+ else if (typeof workflow_nodes[key] === "object" &&
254
+ Array.isArray(currentNode.try) &&
255
+ Array.isArray(currentNode.catch)) {
256
+ // v0.5 · tryCatch step. `try`, `catch`, and optional `finally`
257
+ // each carry their own inner-step array (set by
258
+ // `normalizeTryCatchStep`). Resolve each block as its own Flow
259
+ // so TryCatchNode.run() can dispatch them through child Runners.
260
+ const raw = workflow_nodes[key];
261
+ const merged = {
262
+ try: (await this.getFlow(raw.try)).steps,
263
+ catch: (await this.getFlow(raw.catch)).steps,
264
+ };
265
+ if (Array.isArray(raw.finally)) {
266
+ merged.finally = (await this.getFlow(raw.finally)).steps;
267
+ }
268
+ nodes[key] = merged;
269
+ }
270
+ else if (typeof workflow_nodes[key] === "object" &&
271
+ currentNode.cases !== undefined &&
272
+ Array.isArray(currentNode.cases)) {
273
+ // v0.5 · switch step. Each case carries its own inner-step
274
+ // list at `case.steps` (set by `normalizeSwitchStep`); resolve
275
+ // each independently via getFlow. Optional `default` is its
276
+ // own resolved Flow. The merged config preserves the `on`
277
+ // expression so the blueprint mapper can rewrite it before
278
+ // SwitchNode.run() reads ctx.config[name].on at run time.
279
+ const raw = workflow_nodes[key];
280
+ const rawCases = raw.cases;
281
+ const resolvedCases = await Promise.all(rawCases.map(async (c) => ({
282
+ when: c.when,
283
+ steps: (await this.getFlow(c.steps)).steps,
284
+ })));
285
+ const merged = {
286
+ on: raw.on,
287
+ cases: resolvedCases,
288
+ };
289
+ if (Array.isArray(raw.default)) {
290
+ merged.default = (await this.getFlow(raw.default)).steps;
291
+ }
292
+ nodes[key] = merged;
293
+ }
254
294
  else {
255
295
  nodes[key] = { ...workflow_nodes[key] };
256
296
  }
@@ -276,13 +316,32 @@ export default class Configuration {
276
316
  node.name = step.name;
277
317
  node.active = step.active !== undefined ? step.active : true;
278
318
  node.stop = step.stop !== undefined ? step.stop : false;
279
- // Pass `set_var` through verbatim DO NOT default to `false`. The
280
- // `false` value short-circuits PersistenceHelper.applyStepOutput
281
- // and silently disables v2's default-store rule. Legacy v1
282
- // workflows that explicitly set `set_var: false` are normalized
283
- // to `ephemeral: true` upstream by WorkflowNormalizer.
284
- if (step.set_var !== undefined)
285
- node.set_var = step.set_var;
319
+ // V2 persistence + idempotency + retry knobs flow through nested
320
+ // flow steps too. Without this, a `branch.then[0]` step with
321
+ // `idempotencyKey` set would NOT be cached on rerun. Mirrors the
322
+ // same trio Configuration.getSteps copies onto top-level steps.
323
+ const v2Flow = step;
324
+ if (v2Flow.as !== undefined)
325
+ node.as = v2Flow.as;
326
+ node.spread = v2Flow.spread === true;
327
+ node.ephemeral = v2Flow.ephemeral === true;
328
+ if (v2Flow.idempotencyKey !== undefined)
329
+ node.idempotencyKey = v2Flow.idempotencyKey;
330
+ if (v2Flow.idempotencyKeyTTL !== undefined)
331
+ node.idempotencyKeyTTL = v2Flow.idempotencyKeyTTL;
332
+ if (v2Flow.retry !== undefined)
333
+ node.retry = v2Flow.retry;
334
+ // V2 sub-workflow knobs — also flow through nested branches so a
335
+ // `branch.then[0]` step that invokes a sub-workflow works.
336
+ if (v2Flow.subworkflow !== undefined)
337
+ node.subworkflow = v2Flow.subworkflow;
338
+ if (v2Flow.wait !== undefined)
339
+ node.wait = v2Flow.wait;
340
+ if (v2Flow.maxDuration !== undefined) {
341
+ const parsed = tryParseDuration(v2Flow.maxDuration);
342
+ if (parsed !== null)
343
+ node.maxDurationMs = parsed;
344
+ }
286
345
  // const validator = z.instanceof(NodeBase);
287
346
  // validator.parse(node);
288
347
  flows.steps.push(node);
@@ -325,6 +384,34 @@ export default class Configuration {
325
384
  "runtime.ruby": {
326
385
  resolver: async (node) => await this.runtimeResolver(node),
327
386
  },
387
+ subworkflow: {
388
+ resolver: async (node) => await this.subworkflowResolver(node),
389
+ },
390
+ // PR 4 · `wait.for(duration)` / `wait.until(date)` step. Resolves
391
+ // to a stub node — RunnerSteps intercepts before step.process()
392
+ // runs, so the stub's `run()` should never fire in practice.
393
+ // Without this entry, getSteps() throws `Node type wait not found`
394
+ // at workflow load.
395
+ wait: {
396
+ resolver: async (node) => await this.waitResolver(node),
397
+ },
398
+ // v0.5 · `forEach({...})` step — iterate a collection running
399
+ // inner steps per item. Sequential or parallel-bounded.
400
+ forEach: {
401
+ resolver: async (node) => await this.forEachResolver(node),
402
+ },
403
+ // v0.5 · `loop({...})` step — while-loop with maxIterations cap.
404
+ loop: {
405
+ resolver: async (node) => await this.loopResolver(node),
406
+ },
407
+ // v0.5 · `switchOn({...})` step — N-way branch; first matching case wins.
408
+ switch: {
409
+ resolver: async (node) => await this.switchResolver(node),
410
+ },
411
+ // v0.5 · `tryCatch({...})` step — JS-like try/catch/finally semantics.
412
+ tryCatch: {
413
+ resolver: async (node) => await this.tryCatchResolver(node),
414
+ },
328
415
  };
329
416
  }
330
417
  async runtimeResolver(node) {
@@ -354,21 +441,28 @@ export default class Configuration {
354
441
  targetNode.runtime = runtimeKind;
355
442
  targetNode.active = node.active !== undefined ? node.active : true;
356
443
  targetNode.stop = node.stop !== undefined ? node.stop : false;
357
- // Pass `set_var` through verbatim — DO NOT default to `false`. The v2
358
- // default-store rule in PersistenceHelper persists `result.data` at
359
- // `state[name]` unless `set_var === false` is explicit. Defaulting to
360
- // `false` here silently disabled persistence for every SDK step,
361
- // breaking `js/ctx.state['<id>']` reads in v2 workflows
362
- // (cross-runtime-chain regressed: `state['go']` was undefined even
363
- // though the GO step ran fine).
364
- if (node.set_var !== undefined)
365
- targetNode.set_var = node.set_var;
366
444
  // V2 persistence knobs — flow through to PersistenceHelper.
367
445
  const v2 = node;
368
446
  if (v2.as !== undefined)
369
447
  targetNode.as = v2.as;
370
448
  targetNode.spread = v2.spread === true;
371
449
  targetNode.ephemeral = v2.ephemeral === true;
450
+ // V2 idempotency cache + retry knobs — copied here so the targetNode
451
+ // surfaces them for any future code that inspects the inner SDK node
452
+ // directly. The OUTER RuntimeAdapterNode also carries them via
453
+ // getSteps/getFlow so RunnerSteps' cache-check + retry-loop wrapper
454
+ // works regardless of which side it reads.
455
+ if (v2.idempotencyKey !== undefined)
456
+ targetNode.idempotencyKey = v2.idempotencyKey;
457
+ if (v2.idempotencyKeyTTL !== undefined)
458
+ targetNode.idempotencyKeyTTL = v2.idempotencyKeyTTL;
459
+ if (v2.retry !== undefined)
460
+ targetNode.retry = v2.retry;
461
+ if (v2.maxDuration !== undefined) {
462
+ const parsed = tryParseDuration(v2.maxDuration);
463
+ if (parsed !== null)
464
+ targetNode.maxDurationMs = parsed;
465
+ }
372
466
  // Wrap in RuntimeAdapterNode to integrate with existing Runner.
373
467
  // Per-step `stream_logs: true|false` overrides the global
374
468
  // `BLOK_STREAM_LOGS` env flag (master plan §17 Phase 5 follow-up).
@@ -379,11 +473,70 @@ export default class Configuration {
379
473
  const streamLogs = stepStreamLogs !== undefined ? stepStreamLogs : isStreamLogsEnabled();
380
474
  return new RuntimeAdapterNode(adapter, targetNode, { streamLogs });
381
475
  }
476
+ /**
477
+ * Resolve a `subworkflow` step into a fully-wired `SubworkflowNode` —
478
+ * the dispatch class that looks up the named child workflow in the
479
+ * `WorkflowRegistry` and runs it inline with isolated state.
480
+ *
481
+ * The returned node carries the parent's `globalOptions` so the
482
+ * child `Configuration.init()` can resolve `module` step references
483
+ * against the same node registry.
484
+ */
485
+ async subworkflowResolver(node) {
486
+ const v2 = node;
487
+ if (typeof v2.subworkflow !== "string" || v2.subworkflow.length === 0) {
488
+ throw new Error(`[blok] subworkflowResolver: step "${node.name}" is missing the \`subworkflow\` field after normalization.`);
489
+ }
490
+ // Lazy import to avoid a circular dep (SubworkflowNode imports
491
+ // Configuration to construct the child).
492
+ const { SubworkflowNode } = await import("./SubworkflowNode");
493
+ const subworkflowNode = new SubworkflowNode();
494
+ subworkflowNode.node = node.node;
495
+ subworkflowNode.name = node.name;
496
+ subworkflowNode.type = node.type;
497
+ subworkflowNode.active = node.active !== undefined ? node.active : true;
498
+ subworkflowNode.stop = node.stop !== undefined ? node.stop : false;
499
+ subworkflowNode.subworkflow = v2.subworkflow;
500
+ // `wait: false` triggers the fire-and-forget branch in SubworkflowNode.run.
501
+ // Default to `true` (synchronous) when unset.
502
+ subworkflowNode.wait = v2.wait !== false;
503
+ // v0.7 PR 4 — polymorphic sub-workflow dispatch carries the
504
+ // parent workflow's `trigger.webhook.namespace` so a resolved
505
+ // event-type name (e.g. `"invoice.paid"`) gets prefixed into a
506
+ // full registry name (e.g. `"stripe.invoice.paid"`). Static
507
+ // names are unaffected.
508
+ const triggerCfg = this.workflow?.trigger;
509
+ if (typeof triggerCfg?.webhook?.namespace === "string" && triggerCfg.webhook.namespace.length > 0) {
510
+ subworkflowNode.namespace = triggerCfg.webhook.namespace;
511
+ }
512
+ // G3 polymorphic dispatch — pass the per-step allowList (cleaned to
513
+ // non-empty strings by `normalizeSubworkflowStep`) onto the node so
514
+ // `resolveSubworkflowName` can reject unauthorized lookups at
515
+ // dispatch time without re-walking the workflow shape.
516
+ const allowListSource = node.allowList;
517
+ if (Array.isArray(allowListSource) && allowListSource.length > 0) {
518
+ subworkflowNode.allowList = Object.freeze(allowListSource.filter((s) => typeof s === "string" && s.length > 0));
519
+ }
520
+ // G2 (v0.6) — dispatch strategy. `in-process` (default) preserves
521
+ // the v0.5 behaviour; `http-self` routes the child via a fresh
522
+ // HTTP request to the deployment's own base URL so multi-process
523
+ // deployments can isolate child execution from the parent.
524
+ const dispatchRaw = node.dispatch;
525
+ if (dispatchRaw === "http-self" || dispatchRaw === "in-process") {
526
+ subworkflowNode.dispatch = dispatchRaw;
527
+ }
528
+ // `globalOptions` is the runner's node registry — child Configuration.init
529
+ // needs it for `module:` step resolution.
530
+ subworkflowNode.globalOptions = this.globalOptions;
531
+ return subworkflowNode;
532
+ }
382
533
  async moduleResolver(node, opts) {
383
534
  const nodeHandler = opts?.nodes?.getNode(node.node);
384
535
  if (!nodeHandler) {
385
536
  throw new Error(`Node ${node.node} not found`);
386
537
  }
538
+ // Validate runtime requirements if the node declares them
539
+ this.validateNodeRuntimeRequirements(nodeHandler);
387
540
  const clone = Object.assign(Object.create(Object.getPrototypeOf(nodeHandler)), nodeHandler);
388
541
  // Copy step-level metadata from the workflow JSON onto the clone.
389
542
  // Without this, `step.type` is undefined for module nodes and
@@ -398,13 +551,130 @@ export default class Configuration {
398
551
  clone.active = node.active;
399
552
  if (node.stop !== undefined)
400
553
  clone.stop = node.stop;
401
- if (node.set_var !== undefined)
402
- clone.set_var = node.set_var;
403
554
  return clone;
404
555
  }
556
+ /**
557
+ * PR 4 · resolve a `wait` step to a stub node. The runner's wait
558
+ * primitive (`wait.for`/`wait.until`) is implemented at the
559
+ * RunnerSteps level — this resolver exists only to satisfy
560
+ * getSteps() at workflow load time so `Node type wait not found`
561
+ * doesn't fire on otherwise-valid wait steps.
562
+ */
563
+ async waitResolver(node) {
564
+ const { WaitNode } = await import("./WaitNode");
565
+ const stub = new WaitNode();
566
+ stub.node = node.node;
567
+ stub.name = node.name;
568
+ stub.type = node.type;
569
+ stub.active = node.active !== undefined ? node.active : true;
570
+ stub.stop = node.stop !== undefined ? node.stop : false;
571
+ const v2 = node;
572
+ if (v2.waitForMs !== undefined)
573
+ stub.waitForMs = v2.waitForMs;
574
+ if (v2.waitUntil !== undefined)
575
+ stub.waitUntil = v2.waitUntil;
576
+ return stub;
577
+ }
578
+ /**
579
+ * v0.5 · resolve a `forEach` step. The actual iteration logic lives
580
+ * in `ForEachNode.run()`; the inner `steps` array is pre-resolved by
581
+ * the existing isFlowWithProperties path in `getNodes()`.
582
+ */
583
+ async forEachResolver(node) {
584
+ const { ForEachNode } = await import("./ForEachNode");
585
+ const n = new ForEachNode();
586
+ n.node = node.node;
587
+ n.name = node.name;
588
+ n.type = node.type;
589
+ n.active = node.active !== undefined ? node.active : true;
590
+ n.stop = node.stop !== undefined ? node.stop : false;
591
+ return n;
592
+ }
593
+ /**
594
+ * v0.5 · resolve a `loop` step. While-loop semantics live in
595
+ * `LoopNode.run()`. Inner `steps` resolved by isFlowWithProperties.
596
+ */
597
+ async loopResolver(node) {
598
+ const { LoopNode } = await import("./LoopNode");
599
+ const n = new LoopNode();
600
+ n.node = node.node;
601
+ n.name = node.name;
602
+ n.type = node.type;
603
+ n.active = node.active !== undefined ? node.active : true;
604
+ n.stop = node.stop !== undefined ? node.stop : false;
605
+ return n;
606
+ }
607
+ /**
608
+ * v0.5 · resolve a `switch` step. The N-way match logic lives in
609
+ * `SwitchNode.run()`. Cases + default each carry their own resolved
610
+ * inner-step list — see the dedicated `cases` branch in `getNodes()`.
611
+ */
612
+ async switchResolver(node) {
613
+ const { SwitchNode } = await import("./SwitchNode");
614
+ const n = new SwitchNode();
615
+ n.node = node.node;
616
+ n.name = node.name;
617
+ n.type = node.type;
618
+ n.active = node.active !== undefined ? node.active : true;
619
+ n.stop = node.stop !== undefined ? node.stop : false;
620
+ return n;
621
+ }
622
+ /**
623
+ * v0.5 · resolve a `tryCatch` step. JS-like try/catch/finally semantics
624
+ * live in `TryCatchNode.run()`. Each block (try, catch, finally) is
625
+ * pre-resolved by the dedicated tryCatch branch in `getNodes()` so
626
+ * the runtime can dispatch them through child Runners on-demand.
627
+ */
628
+ async tryCatchResolver(node) {
629
+ const { TryCatchNode } = await import("./TryCatchNode");
630
+ const n = new TryCatchNode();
631
+ n.node = node.node;
632
+ n.name = node.name;
633
+ n.type = node.type;
634
+ n.active = node.active !== undefined ? node.active : true;
635
+ n.stop = node.stop !== undefined ? node.stop : false;
636
+ return n;
637
+ }
638
+ /**
639
+ * Check if a resolved node has runtimeRequirements and validate them
640
+ * against the currently known runtime versions in the RuntimeRegistry.
641
+ */
642
+ validateNodeRuntimeRequirements(node) {
643
+ const fnNode = node;
644
+ if (!fnNode.runtimeRequirements)
645
+ return;
646
+ const registry = RuntimeRegistry.getInstance();
647
+ const runtimeVersions = {};
648
+ for (const kind of registry.getRegisteredKinds()) {
649
+ const version = registry.getVersion(kind);
650
+ if (version) {
651
+ runtimeVersions[kind] = version;
652
+ }
653
+ }
654
+ // If no runtime versions are known yet, skip validation
655
+ // (versions are populated when health checks succeed)
656
+ if (Object.keys(runtimeVersions).length === 0)
657
+ return;
658
+ const validator = new RuntimeVersionValidator(runtimeVersions);
659
+ const results = validator.validateNode({
660
+ name: fnNode.name || "unknown",
661
+ runtimeRequirements: fnNode.runtimeRequirements,
662
+ });
663
+ const failures = results.filter((r) => !r.valid);
664
+ if (failures.length > 0) {
665
+ throw new Error(RuntimeVersionValidator.formatErrors(failures));
666
+ }
667
+ }
405
668
  async localResolver(node) {
406
- const path = `${process.env.NODES_PATH}/${node.node}`;
407
- return new (await import(path)).default();
669
+ // Security review FW-3 — canonicalize the resolved path against
670
+ // NODES_PATH so a node.node value like "../../malicious" can't
671
+ // walk the filesystem outside the configured directory.
672
+ const base = resolvePath(process.env.NODES_PATH || ".");
673
+ const target = resolvePath(base, node.node);
674
+ if (target !== base && !target.startsWith(base + pathSep)) {
675
+ throw new Error(`[blok] local node path escapes NODES_PATH: '${node.node}' resolves outside ${base}`);
676
+ }
677
+ return new (await import(target)).default();
408
678
  }
409
679
  }
410
680
  //# sourceMappingURL=Configuration.js.map