@blokjs/runner 0.2.1 → 0.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.
Files changed (159) hide show
  1. package/dist/Blok.js +11 -11
  2. package/dist/Blok.js.map +1 -1
  3. package/dist/Configuration.d.ts +39 -2
  4. package/dist/Configuration.js +337 -28
  5. package/dist/Configuration.js.map +1 -1
  6. package/dist/ConfigurationResolver.d.ts +9 -0
  7. package/dist/ConfigurationResolver.js +17 -1
  8. package/dist/ConfigurationResolver.js.map +1 -1
  9. package/dist/PayloadTooLargeError.d.ts +19 -0
  10. package/dist/PayloadTooLargeError.js +29 -0
  11. package/dist/PayloadTooLargeError.js.map +1 -0
  12. package/dist/RunCancelledError.d.ts +17 -0
  13. package/dist/RunCancelledError.js +25 -0
  14. package/dist/RunCancelledError.js.map +1 -0
  15. package/dist/RunnerSteps.js +363 -23
  16. package/dist/RunnerSteps.js.map +1 -1
  17. package/dist/RuntimeAdapterNode.d.ts +32 -2
  18. package/dist/RuntimeAdapterNode.js +122 -27
  19. package/dist/RuntimeAdapterNode.js.map +1 -1
  20. package/dist/SubworkflowNode.d.ts +75 -0
  21. package/dist/SubworkflowNode.js +221 -0
  22. package/dist/SubworkflowNode.js.map +1 -0
  23. package/dist/TriggerBase.d.ts +128 -0
  24. package/dist/TriggerBase.js +808 -6
  25. package/dist/TriggerBase.js.map +1 -1
  26. package/dist/WaitDispatchRequest.d.ts +38 -0
  27. package/dist/WaitDispatchRequest.js +13 -0
  28. package/dist/WaitDispatchRequest.js.map +1 -0
  29. package/dist/WaitNode.d.ts +23 -0
  30. package/dist/WaitNode.js +26 -0
  31. package/dist/WaitNode.js.map +1 -0
  32. package/dist/adapters/BunRuntimeAdapter.d.ts +1 -0
  33. package/dist/adapters/BunRuntimeAdapter.js +1 -0
  34. package/dist/adapters/BunRuntimeAdapter.js.map +1 -1
  35. package/dist/adapters/DockerRuntimeAdapter.d.ts +2 -1
  36. package/dist/adapters/DockerRuntimeAdapter.js +10 -1
  37. package/dist/adapters/DockerRuntimeAdapter.js.map +1 -1
  38. package/dist/adapters/HttpRuntimeAdapter.d.ts +26 -5
  39. package/dist/adapters/HttpRuntimeAdapter.js +97 -16
  40. package/dist/adapters/HttpRuntimeAdapter.js.map +1 -1
  41. package/dist/adapters/NodeJsRuntimeAdapter.d.ts +1 -0
  42. package/dist/adapters/NodeJsRuntimeAdapter.js +1 -0
  43. package/dist/adapters/NodeJsRuntimeAdapter.js.map +1 -1
  44. package/dist/adapters/RuntimeAdapter.d.ts +17 -0
  45. package/dist/adapters/WasmRuntimeAdapter.d.ts +1 -0
  46. package/dist/adapters/WasmRuntimeAdapter.js +1 -0
  47. package/dist/adapters/WasmRuntimeAdapter.js.map +1 -1
  48. package/dist/adapters/grpc/GrpcChannelOptions.d.ts +31 -0
  49. package/dist/adapters/grpc/GrpcChannelOptions.js +68 -0
  50. package/dist/adapters/grpc/GrpcChannelOptions.js.map +1 -0
  51. package/dist/adapters/grpc/GrpcClientPool.d.ts +43 -0
  52. package/dist/adapters/grpc/GrpcClientPool.js +89 -0
  53. package/dist/adapters/grpc/GrpcClientPool.js.map +1 -0
  54. package/dist/adapters/grpc/GrpcCodec.d.ts +226 -0
  55. package/dist/adapters/grpc/GrpcCodec.js +275 -0
  56. package/dist/adapters/grpc/GrpcCodec.js.map +1 -0
  57. package/dist/adapters/grpc/GrpcErrors.d.ts +59 -0
  58. package/dist/adapters/grpc/GrpcErrors.js +190 -0
  59. package/dist/adapters/grpc/GrpcErrors.js.map +1 -0
  60. package/dist/adapters/grpc/GrpcHealthChecker.d.ts +69 -0
  61. package/dist/adapters/grpc/GrpcHealthChecker.js +96 -0
  62. package/dist/adapters/grpc/GrpcHealthChecker.js.map +1 -0
  63. package/dist/adapters/grpc/GrpcRuntimeAdapter.d.ts +98 -0
  64. package/dist/adapters/grpc/GrpcRuntimeAdapter.js +478 -0
  65. package/dist/adapters/grpc/GrpcRuntimeAdapter.js.map +1 -0
  66. package/dist/adapters/grpc/index.d.ts +13 -0
  67. package/dist/adapters/grpc/index.js +14 -0
  68. package/dist/adapters/grpc/index.js.map +1 -0
  69. package/dist/adapters/grpc/proto/blok/runtime/v1/runtime.proto +302 -0
  70. package/dist/adapters/grpc/types.d.ts +97 -0
  71. package/dist/adapters/grpc/types.js +41 -0
  72. package/dist/adapters/grpc/types.js.map +1 -0
  73. package/dist/adapters/transport.d.ts +108 -0
  74. package/dist/adapters/transport.js +196 -0
  75. package/dist/adapters/transport.js.map +1 -0
  76. package/dist/concurrency/ConcurrencyBackend.d.ts +61 -0
  77. package/dist/concurrency/ConcurrencyBackend.js +20 -0
  78. package/dist/concurrency/ConcurrencyBackend.js.map +1 -0
  79. package/dist/concurrency/ConcurrencyLimitError.d.ts +37 -0
  80. package/dist/concurrency/ConcurrencyLimitError.js +16 -0
  81. package/dist/concurrency/ConcurrencyLimitError.js.map +1 -0
  82. package/dist/concurrency/NatsKvConcurrencyBackend.d.ts +64 -0
  83. package/dist/concurrency/NatsKvConcurrencyBackend.js +297 -0
  84. package/dist/concurrency/NatsKvConcurrencyBackend.js.map +1 -0
  85. package/dist/concurrency/QueueExpiredError.d.ts +40 -0
  86. package/dist/concurrency/QueueExpiredError.js +15 -0
  87. package/dist/concurrency/QueueExpiredError.js.map +1 -0
  88. package/dist/concurrency/createConcurrencyBackend.d.ts +23 -0
  89. package/dist/concurrency/createConcurrencyBackend.js +34 -0
  90. package/dist/concurrency/createConcurrencyBackend.js.map +1 -0
  91. package/dist/concurrency/readConcurrencyConfig.d.ts +60 -0
  92. package/dist/concurrency/readConcurrencyConfig.js +60 -0
  93. package/dist/concurrency/readConcurrencyConfig.js.map +1 -0
  94. package/dist/idempotency/resolveIdempotencyKey.d.ts +20 -0
  95. package/dist/idempotency/resolveIdempotencyKey.js +37 -0
  96. package/dist/idempotency/resolveIdempotencyKey.js.map +1 -0
  97. package/dist/index.d.ts +35 -3
  98. package/dist/index.js +61 -2
  99. package/dist/index.js.map +1 -1
  100. package/dist/monitoring/ConcurrencyMetrics.d.ts +56 -0
  101. package/dist/monitoring/ConcurrencyMetrics.js +107 -0
  102. package/dist/monitoring/ConcurrencyMetrics.js.map +1 -0
  103. package/dist/monitoring/JanitorMetrics.d.ts +27 -0
  104. package/dist/monitoring/JanitorMetrics.js +48 -0
  105. package/dist/monitoring/JanitorMetrics.js.map +1 -0
  106. package/dist/scheduling/DebounceCoordinator.d.ts +88 -0
  107. package/dist/scheduling/DebounceCoordinator.js +141 -0
  108. package/dist/scheduling/DebounceCoordinator.js.map +1 -0
  109. package/dist/scheduling/DeferredDispatchSignal.d.ts +50 -0
  110. package/dist/scheduling/DeferredDispatchSignal.js +14 -0
  111. package/dist/scheduling/DeferredDispatchSignal.js.map +1 -0
  112. package/dist/scheduling/DeferredRunScheduler.d.ts +68 -0
  113. package/dist/scheduling/DeferredRunScheduler.js +154 -0
  114. package/dist/scheduling/DeferredRunScheduler.js.map +1 -0
  115. package/dist/scheduling/readSchedulingConfig.d.ts +24 -0
  116. package/dist/scheduling/readSchedulingConfig.js +52 -0
  117. package/dist/scheduling/readSchedulingConfig.js.map +1 -0
  118. package/dist/testing/WorkflowTestRunner.js +12 -0
  119. package/dist/testing/WorkflowTestRunner.js.map +1 -1
  120. package/dist/timeouts/StepTimeoutError.d.ts +22 -0
  121. package/dist/timeouts/StepTimeoutError.js +31 -0
  122. package/dist/timeouts/StepTimeoutError.js.map +1 -0
  123. package/dist/tracing/InMemoryRunStore.d.ts +28 -1
  124. package/dist/tracing/InMemoryRunStore.js +150 -0
  125. package/dist/tracing/InMemoryRunStore.js.map +1 -1
  126. package/dist/tracing/Janitor.d.ts +70 -0
  127. package/dist/tracing/Janitor.js +150 -0
  128. package/dist/tracing/Janitor.js.map +1 -0
  129. package/dist/tracing/PostgresRunStore.d.ts +30 -0
  130. package/dist/tracing/PostgresRunStore.js +435 -3
  131. package/dist/tracing/PostgresRunStore.js.map +1 -1
  132. package/dist/tracing/RunStore.d.ts +100 -1
  133. package/dist/tracing/RunTracker.d.ts +261 -11
  134. package/dist/tracing/RunTracker.js +691 -11
  135. package/dist/tracing/RunTracker.js.map +1 -1
  136. package/dist/tracing/SqliteRunStore.d.ts +23 -1
  137. package/dist/tracing/SqliteRunStore.js +421 -6
  138. package/dist/tracing/SqliteRunStore.js.map +1 -1
  139. package/dist/tracing/TraceRouter.d.ts +20 -2
  140. package/dist/tracing/TraceRouter.js +494 -9
  141. package/dist/tracing/TraceRouter.js.map +1 -1
  142. package/dist/tracing/sanitize.d.ts +11 -0
  143. package/dist/tracing/sanitize.js +29 -0
  144. package/dist/tracing/sanitize.js.map +1 -1
  145. package/dist/tracing/types.d.ts +429 -11
  146. package/dist/types/GlobalOptions.d.ts +9 -2
  147. package/dist/utils/createChildContext.d.ts +32 -0
  148. package/dist/utils/createChildContext.js +113 -0
  149. package/dist/utils/createChildContext.js.map +1 -0
  150. package/dist/workflow/PersistenceHelper.d.ts +46 -0
  151. package/dist/workflow/PersistenceHelper.js +57 -0
  152. package/dist/workflow/PersistenceHelper.js.map +1 -0
  153. package/dist/workflow/WorkflowNormalizer.d.ts +79 -0
  154. package/dist/workflow/WorkflowNormalizer.js +486 -0
  155. package/dist/workflow/WorkflowNormalizer.js.map +1 -0
  156. package/dist/workflow/WorkflowRegistry.d.ts +64 -0
  157. package/dist/workflow/WorkflowRegistry.js +81 -0
  158. package/dist/workflow/WorkflowRegistry.js.map +1 -0
  159. package/package.json +10 -7
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Resolve the transport to use for a given runtime kind from the environment.
3
+ *
4
+ * Precedence (most specific wins):
5
+ * 1. `RUNTIME_<KIND>_TRANSPORT` — per-kind override
6
+ * e.g. `RUNTIME_PYTHON3_TRANSPORT=http` to opt one SDK out of gRPC.
7
+ * 2. `RUNTIME_TRANSPORT` — global override
8
+ * e.g. `RUNTIME_TRANSPORT=http` to roll back the whole runner.
9
+ * 3. The hard-coded default — **`grpc`** as of Phase 6 (master plan §11).
10
+ * Flipped from `http` to `grpc` after the cross-language parity matrix
11
+ * (`bun run test:parity`, 33 tests across 6 SDKs × 5 workflows) and
12
+ * the per-SDK §17 BlokError E2E suites stayed green for the full
13
+ * Phase 5 observation window.
14
+ *
15
+ * Pure function — reads `process.env` once per call so tests can override.
16
+ *
17
+ * # Why the default flip is safe
18
+ *
19
+ * - HTTP transport remains available behind `RUNTIME_TRANSPORT=http` for at
20
+ * least two minor versions (master plan §11 commitment).
21
+ * - PHP without RoadRunner (Path B from §16) sets
22
+ * `RUNTIME_PHP_TRANSPORT=http` per host. The forever-supported escape
23
+ * hatch is unaffected.
24
+ * - SDKs that fail to bind their gRPC listener fall through the
25
+ * {@link GrpcHealthChecker} circuit-breaker and surface a typed
26
+ * `BlokError(category=DEPENDENCY)` per §9 — no silent failure.
27
+ *
28
+ * @param kind The runtime kind (e.g. "python3").
29
+ * @param env Optional env source (defaults to `process.env`). Tests can
30
+ * pass a stub map.
31
+ * @returns Either `"http"` or `"grpc"`. Invalid values fall back to the
32
+ * Phase 6 default (`grpc`).
33
+ */
34
+ export function resolveTransportForKind(kind, env = process.env) {
35
+ const perKindKey = `RUNTIME_${kind.toUpperCase()}_TRANSPORT`;
36
+ const perKind = env[perKindKey];
37
+ if (perKind === "grpc" || perKind === "http") {
38
+ if (perKind === "http")
39
+ warnDeprecatedHttpTransport(perKindKey);
40
+ return perKind;
41
+ }
42
+ const global = env.RUNTIME_TRANSPORT;
43
+ if (global === "grpc" || global === "http") {
44
+ if (global === "http")
45
+ warnDeprecatedHttpTransport("RUNTIME_TRANSPORT");
46
+ return global;
47
+ }
48
+ // Phase 6 default: gRPC. The flip from HTTP landed once the parity
49
+ // matrix had been green for the observation window.
50
+ return "grpc";
51
+ }
52
+ /**
53
+ * Set of env-var names that have already triggered the deprecation warning
54
+ * in this process. Prevents log spam when many runtime kinds resolve through
55
+ * the same global override on each request.
56
+ *
57
+ * Exported as a reset hook for tests — production code never touches it.
58
+ */
59
+ const _httpDeprecationWarned = new Set();
60
+ function warnDeprecatedHttpTransport(envKey) {
61
+ if (_httpDeprecationWarned.has(envKey))
62
+ return;
63
+ _httpDeprecationWarned.add(envKey);
64
+ console.warn(`[blok] ${envKey}=http is deprecated and will be removed in v0.4.0. Migrate to gRPC by dropping the env var (gRPC is the default since Phase 6) and ensuring your SDK process boots with BLOK_TRANSPORT=grpc.`);
65
+ }
66
+ /**
67
+ * Test-only — reset the per-process HTTP-transport deprecation cache so a
68
+ * test that flips `RUNTIME_TRANSPORT=http` can re-assert the warning fires.
69
+ *
70
+ * @internal
71
+ */
72
+ export function _resetHttpDeprecationCache() {
73
+ _httpDeprecationWarned.clear();
74
+ }
75
+ /**
76
+ * Whether log streaming is enabled for runtime nodes. When true, the runner
77
+ * routes runtime nodes through `GrpcRuntimeAdapter.executeStream` instead of
78
+ * the unary `execute`, and `LogLine` frames flow into `RunTracker.addLog`
79
+ * — surfacing live in Studio's `/__blok/runs/:id/stream` SSE endpoint.
80
+ *
81
+ * Streaming is a pure additive capability: when the env var is unset, the
82
+ * legacy unary path runs unchanged. When enabled but the adapter doesn't
83
+ * support streaming (e.g. HttpRuntimeAdapter), `RuntimeAdapterNode` falls
84
+ * back to unary so misconfiguration never blocks execution.
85
+ *
86
+ * Recognized as truthy: `1`, `true`, `yes`, `on` (case-insensitive). Anything
87
+ * else (including unset, empty, `0`, `false`) returns false.
88
+ */
89
+ export function isStreamLogsEnabled(env = process.env) {
90
+ return isTruthyFlag(env.BLOK_STREAM_LOGS);
91
+ }
92
+ /**
93
+ * Resolve the gRPC background health-check interval from the environment.
94
+ *
95
+ * `BLOK_GRPC_HEALTH_INTERVAL_MS` is the global override:
96
+ * - any positive integer → use that interval
97
+ * - `0` → disable the loop entirely (useful in tests)
98
+ * - unset / non-numeric → return undefined; adapter uses
99
+ * {@link GRPC_DEFAULTS.HEALTH_INTERVAL_MS}.
100
+ */
101
+ export function resolveHealthCheckIntervalMs(env = process.env) {
102
+ const raw = env.BLOK_GRPC_HEALTH_INTERVAL_MS;
103
+ if (raw === undefined || raw === "")
104
+ return undefined;
105
+ const parsed = Number.parseInt(raw, 10);
106
+ if (Number.isNaN(parsed) || parsed < 0)
107
+ return undefined;
108
+ return parsed;
109
+ }
110
+ /**
111
+ * Resolve the consecutive-failure threshold for the gRPC circuit breaker.
112
+ *
113
+ * `BLOK_GRPC_HEALTH_FAILURE_THRESHOLD` overrides the default
114
+ * ({@link GRPC_DEFAULTS.HEALTH_FAILURE_THRESHOLD}). Values < 1 are ignored
115
+ * (the checker requires ≥ 1 failure to trip).
116
+ */
117
+ export function resolveHealthCheckFailureThreshold(env = process.env) {
118
+ const raw = env.BLOK_GRPC_HEALTH_FAILURE_THRESHOLD;
119
+ if (raw === undefined || raw === "")
120
+ return undefined;
121
+ const parsed = Number.parseInt(raw, 10);
122
+ if (Number.isNaN(parsed) || parsed < 1)
123
+ return undefined;
124
+ return parsed;
125
+ }
126
+ /**
127
+ * Build a {@link TlsConfig} for a given runtime kind from environment
128
+ * variables. Returns `undefined` when nothing is configured (channel stays
129
+ * plaintext — appropriate for loopback dev).
130
+ *
131
+ * Per-kind env vars (taking precedence):
132
+ * - `RUNTIME_<KIND>_TLS_CA` CA cert path (PEM)
133
+ * - `RUNTIME_<KIND>_TLS_CLIENT_CERT` client cert path (PEM, mTLS)
134
+ * - `RUNTIME_<KIND>_TLS_CLIENT_KEY` client key path (PEM, mTLS)
135
+ * - `RUNTIME_<KIND>_TLS_SERVER_NAME` SNI override
136
+ * - `RUNTIME_<KIND>_TLS_INSECURE_SKIP_VERIFY=true` dev-only
137
+ *
138
+ * Global fallbacks (apply when the per-kind var is unset):
139
+ * - `BLOK_GRPC_TLS_CA`, `BLOK_GRPC_TLS_CLIENT_CERT`, `BLOK_GRPC_TLS_CLIENT_KEY`,
140
+ * `BLOK_GRPC_TLS_SERVER_NAME`, `BLOK_GRPC_TLS_INSECURE_SKIP_VERIFY`.
141
+ *
142
+ * If none of the relevant env vars are set, returns `undefined`.
143
+ */
144
+ export function loadTlsConfigForKind(kind, env = process.env) {
145
+ const upperKind = kind.toUpperCase();
146
+ const pick = (suffix) => env[`RUNTIME_${upperKind}_TLS_${suffix}`] ?? env[`BLOK_GRPC_TLS_${suffix}`];
147
+ const caCertPath = pick("CA");
148
+ const clientCertPath = pick("CLIENT_CERT");
149
+ const clientKeyPath = pick("CLIENT_KEY");
150
+ const serverNameOverride = pick("SERVER_NAME");
151
+ const insecureSkipVerifyRaw = pick("INSECURE_SKIP_VERIFY");
152
+ const insecureSkipVerify = isTruthyFlag(insecureSkipVerifyRaw);
153
+ const anySet = caCertPath !== undefined ||
154
+ clientCertPath !== undefined ||
155
+ clientKeyPath !== undefined ||
156
+ serverNameOverride !== undefined ||
157
+ insecureSkipVerify;
158
+ if (!anySet)
159
+ return undefined;
160
+ return {
161
+ caCertPath,
162
+ clientCertPath,
163
+ clientKeyPath,
164
+ serverNameOverride,
165
+ insecureSkipVerify,
166
+ };
167
+ }
168
+ /**
169
+ * Whether `BLOK_GRPC_REQUIRE_TLS=true` enforces TLS on non-loopback hosts.
170
+ * When true, building a gRPC adapter with no TLS config against a non-loopback
171
+ * host throws at startup. Loopback (localhost, 127.0.0.0/8, ::1) is exempted.
172
+ */
173
+ export function isStrictTlsEnabled(env = process.env) {
174
+ return isTruthyFlag(env.BLOK_GRPC_REQUIRE_TLS);
175
+ }
176
+ /**
177
+ * Returns true when the host is a loopback address that doesn't require
178
+ * TLS even under strict mode. Match is intentionally generous — covers
179
+ * `localhost`, the 127.x range, IPv6 loopback, and the wildcard 0.0.0.0
180
+ * (which dev SDKs commonly bind to).
181
+ */
182
+ export function isLoopbackHost(host) {
183
+ const normalized = host.trim().toLowerCase();
184
+ if (normalized === "localhost" || normalized === "::1" || normalized === "0.0.0.0")
185
+ return true;
186
+ if (normalized.startsWith("127."))
187
+ return true;
188
+ return false;
189
+ }
190
+ function isTruthyFlag(value) {
191
+ if (!value)
192
+ return false;
193
+ const normalized = value.trim().toLowerCase();
194
+ return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on";
195
+ }
196
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.js","sourceRoot":"","sources":["../../src/adapters/transport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Tier 2 #6 follow-up · cross-process concurrency backend.
3
+ *
4
+ * Optional capability layer that lets `RunTracker.acquireConcurrencySlot`
5
+ * delegate to a backend with cross-process semantics (NATS KV, future
6
+ * Redis) instead of the local sync `RunStore` impl.
7
+ *
8
+ * Default behavior is unchanged — when no backend is set, the tracker
9
+ * uses the existing `store.acquireConcurrencySlot` (single-process via
10
+ * SQLite locks or in-memory Map). The backend is opt-in via
11
+ * `BLOK_CONCURRENCY_BACKEND=nats-kv` and installed by trigger packages
12
+ * during `listen()`.
13
+ *
14
+ * Async-only — NATS KV operations require network round-trips. The
15
+ * sync `RunStore` interface remains untouched (no breaking change to
16
+ * existing extension points); the tracker bridges async + sync via
17
+ * `Promise.resolve()` when no backend is set.
18
+ */
19
+ import type { ConcurrencySlotResult } from "../tracing/types";
20
+ export interface ConcurrencyBackend {
21
+ /**
22
+ * Identifying string for logs/metrics. e.g. `"nats-kv"`, `"redis"`.
23
+ */
24
+ readonly name: string;
25
+ /**
26
+ * Lifecycle — open the underlying connection. Idempotent. Called
27
+ * once when the trigger installs the backend during `listen()`.
28
+ */
29
+ connect(): Promise<void>;
30
+ /**
31
+ * Lifecycle — close the underlying connection. Idempotent. Called
32
+ * on graceful process shutdown (when wired).
33
+ */
34
+ disconnect(): Promise<void>;
35
+ /**
36
+ * Atomically attempt to acquire a slot for the
37
+ * `(workflowName, concurrencyKey)` bucket against the given limit.
38
+ *
39
+ * Contract — must match {@link RunStore.acquireConcurrencySlot}:
40
+ * - Lazy-purge expired leases on the bucket before counting.
41
+ * - Idempotent re-acquire: same `runId` refreshes the lease,
42
+ * does NOT grow the count.
43
+ * - On count >= limit: return `{acquired: false, currentInFlight}`
44
+ * without inserting.
45
+ * - On grant: return `{acquired: true, currentInFlight}` where
46
+ * `currentInFlight` includes the just-acquired slot.
47
+ */
48
+ acquireSlot(workflowName: string, concurrencyKey: string, concurrencyLimit: number, runId: string, leaseExpiresAt: number): Promise<ConcurrencySlotResult>;
49
+ /**
50
+ * Release a slot. Idempotent. Safe to call on `runId`s that don't
51
+ * hold a slot (e.g. crash + restart releases via lease expiry).
52
+ */
53
+ releaseSlot(workflowName: string, concurrencyKey: string, runId: string): Promise<void>;
54
+ /**
55
+ * Janitor sweep — purge every lease whose `expiresAt <= now` across
56
+ * all buckets. Returns the count of purged leases. Cheap per-bucket
57
+ * lazy-purge happens on every acquire; this method is for global
58
+ * cleanup (e.g., periodic background task).
59
+ */
60
+ purgeExpired(now: number): Promise<number>;
61
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Tier 2 #6 follow-up · cross-process concurrency backend.
3
+ *
4
+ * Optional capability layer that lets `RunTracker.acquireConcurrencySlot`
5
+ * delegate to a backend with cross-process semantics (NATS KV, future
6
+ * Redis) instead of the local sync `RunStore` impl.
7
+ *
8
+ * Default behavior is unchanged — when no backend is set, the tracker
9
+ * uses the existing `store.acquireConcurrencySlot` (single-process via
10
+ * SQLite locks or in-memory Map). The backend is opt-in via
11
+ * `BLOK_CONCURRENCY_BACKEND=nats-kv` and installed by trigger packages
12
+ * during `listen()`.
13
+ *
14
+ * Async-only — NATS KV operations require network round-trips. The
15
+ * sync `RunStore` interface remains untouched (no breaking change to
16
+ * existing extension points); the tracker bridges async + sync via
17
+ * `Promise.resolve()` when no backend is set.
18
+ */
19
+ export {};
20
+ //# sourceMappingURL=ConcurrencyBackend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConcurrencyBackend.js","sourceRoot":"","sources":["../../src/concurrency/ConcurrencyBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Tier 2 #6 — thrown by `TriggerBase.run()` when a workflow's concurrency
3
+ * gate denies a run because the in-flight count for the resolved
4
+ * `concurrencyKey` has reached `concurrencyLimit`.
5
+ *
6
+ * Triggers catch this and translate it into the appropriate transport-level
7
+ * response:
8
+ * - HTTP trigger → `429 Too Many Requests` with `Retry-After` header.
9
+ * - Worker trigger → NACK with redelivery (existing job-queue retry handles
10
+ * spacing).
11
+ *
12
+ * Carries enough context for both observability (logs / Studio events) and
13
+ * client-facing error payloads.
14
+ */
15
+ export interface ConcurrencyLimitInfo {
16
+ /** Workflow name whose gate fired. */
17
+ workflowName: string;
18
+ /** Resolved key value (after evaluating the `concurrencyKey` expression). */
19
+ concurrencyKey: string;
20
+ /** Per-key limit from the trigger config. */
21
+ concurrencyLimit: number;
22
+ /** Number of in-flight runs observed at the moment of denial. */
23
+ currentInFlight: number;
24
+ /**
25
+ * Suggested back-off in milliseconds before retrying. A heuristic — the
26
+ * gate doesn't observe a queue, so we recommend a minimum (the default
27
+ * matches the smallest meaningful HTTP `Retry-After` precision = 1s).
28
+ */
29
+ retryAfterMs: number;
30
+ /** Run id allocated by the tracer for this denied attempt. */
31
+ runId: string;
32
+ }
33
+ export declare class ConcurrencyLimitError extends Error {
34
+ readonly info: ConcurrencyLimitInfo;
35
+ constructor(info: ConcurrencyLimitInfo);
36
+ }
37
+ export declare function isConcurrencyLimitError(err: unknown): err is ConcurrencyLimitError;
@@ -0,0 +1,16 @@
1
+ export class ConcurrencyLimitError extends Error {
2
+ info;
3
+ constructor(info) {
4
+ super(`Concurrency limit reached for workflow '${info.workflowName}' (key='${info.concurrencyKey}', ` +
5
+ `limit=${info.concurrencyLimit}, currentInFlight=${info.currentInFlight}). ` +
6
+ `Retry after ~${info.retryAfterMs}ms.`);
7
+ this.name = "ConcurrencyLimitError";
8
+ this.info = info;
9
+ // Restore prototype chain when extending Error in transpiled code.
10
+ Object.setPrototypeOf(this, ConcurrencyLimitError.prototype);
11
+ }
12
+ }
13
+ export function isConcurrencyLimitError(err) {
14
+ return err instanceof ConcurrencyLimitError;
15
+ }
16
+ //# sourceMappingURL=ConcurrencyLimitError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConcurrencyLimitError.js","sourceRoot":"","sources":["../../src/concurrency/ConcurrencyLimitError.ts"],"names":[],"mappings":"AAiCA,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC/B,IAAI,CAAuB;IAE3C,YAAY,IAA0B;QACrC,KAAK,CACJ,2CAA2C,IAAI,CAAC,YAAY,WAAW,IAAI,CAAC,cAAc,KAAK;YAC9F,SAAS,IAAI,CAAC,gBAAgB,qBAAqB,IAAI,CAAC,eAAe,KAAK;YAC5E,gBAAgB,IAAI,CAAC,YAAY,KAAK,CACvC,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;QACpC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,mEAAmE;QACnE,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,qBAAqB,CAAC,SAAS,CAAC,CAAC;IAC9D,CAAC;CACD;AAED,MAAM,UAAU,uBAAuB,CAAC,GAAY;IACnD,OAAO,GAAG,YAAY,qBAAqB,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Tier 2 #6 follow-up · NATS KV-backed concurrency backend.
3
+ *
4
+ * Coordinates per-(workflow, concurrencyKey) lease state across processes
5
+ * via a single NATS JetStream KV value per bucket using revision-based
6
+ * compare-and-swap (OCC).
7
+ *
8
+ * Storage model: one KV key per `(workflowName, concurrencyKey)` pair.
9
+ * Value is a JSON `{leases: [{runId, expiresAt}]}` document. Bounded
10
+ * cardinality assumption — typical concurrency keys hold 1-50 active
11
+ * leases (per-tenant rate limits). For higher cardinality, a per-lease
12
+ * key model would scale better; revisit when needed.
13
+ *
14
+ * Atomicity: NATS KV's only guarantee is `kv.create(key, value)` (fails
15
+ * on conflict) and `kv.update(key, value, expectedRevision)` (fails on
16
+ * concurrent modification). The acquire loop reads → filters → checks
17
+ * limit → CAS update. On CAS failure, retry up to 10 times then
18
+ * fail-closed (deny the slot).
19
+ *
20
+ * Lease leak: each lease carries an `expiresAt`. Expired leases are
21
+ * lazy-purged inside the same `acquireSlot` call that observes them;
22
+ * an explicit `purgeExpired` sweep is also exposed for janitor use.
23
+ */
24
+ import type { ConcurrencySlotResult } from "../tracing/types";
25
+ import type { ConcurrencyBackend } from "./ConcurrencyBackend";
26
+ export interface NatsKvConcurrencyConfig {
27
+ servers: string[];
28
+ token?: string;
29
+ user?: string;
30
+ pass?: string;
31
+ bucketName: string;
32
+ }
33
+ /**
34
+ * Read configuration from environment variables. Used by
35
+ * {@link createConcurrencyBackend} when the user opts into NATS KV.
36
+ */
37
+ export declare function readNatsKvConfigFromEnv(): NatsKvConcurrencyConfig;
38
+ export declare class NatsKvConcurrencyBackend implements ConcurrencyBackend {
39
+ readonly name = "nats-kv";
40
+ private nc;
41
+ private kv;
42
+ private readonly config;
43
+ private connected;
44
+ constructor(config?: Partial<NatsKvConcurrencyConfig>);
45
+ connect(): Promise<void>;
46
+ disconnect(): Promise<void>;
47
+ private bucketKey;
48
+ private encodeSegment;
49
+ private requireKv;
50
+ acquireSlot(workflowName: string, concurrencyKey: string, concurrencyLimit: number, runId: string, leaseExpiresAt: number): Promise<ConcurrencySlotResult>;
51
+ releaseSlot(workflowName: string, concurrencyKey: string, runId: string): Promise<void>;
52
+ purgeExpired(now: number): Promise<number>;
53
+ /**
54
+ * PR 2 A6 — distinguishes legitimate "key not found" from "broker
55
+ * unreachable / non-NotFound error". Returns:
56
+ * - `NatsKvEntry` on a successful fetch.
57
+ * - `null` when the key doesn't exist (NotFound code or null entry).
58
+ * - `"fetch-failed"` for any other error (transient broker outage,
59
+ * auth failure, network blip, etc.) so the OCC loop can fail-fast
60
+ * instead of spinning 10× before fail-closing.
61
+ */
62
+ private safeGet;
63
+ private parseBucket;
64
+ }