@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.
- package/dist/Blok.js +11 -11
- package/dist/Blok.js.map +1 -1
- package/dist/Configuration.d.ts +39 -2
- package/dist/Configuration.js +337 -28
- package/dist/Configuration.js.map +1 -1
- package/dist/ConfigurationResolver.d.ts +9 -0
- package/dist/ConfigurationResolver.js +17 -1
- package/dist/ConfigurationResolver.js.map +1 -1
- package/dist/PayloadTooLargeError.d.ts +19 -0
- package/dist/PayloadTooLargeError.js +29 -0
- package/dist/PayloadTooLargeError.js.map +1 -0
- package/dist/RunCancelledError.d.ts +17 -0
- package/dist/RunCancelledError.js +25 -0
- package/dist/RunCancelledError.js.map +1 -0
- package/dist/RunnerSteps.js +363 -23
- package/dist/RunnerSteps.js.map +1 -1
- package/dist/RuntimeAdapterNode.d.ts +32 -2
- package/dist/RuntimeAdapterNode.js +122 -27
- package/dist/RuntimeAdapterNode.js.map +1 -1
- package/dist/SubworkflowNode.d.ts +75 -0
- package/dist/SubworkflowNode.js +221 -0
- package/dist/SubworkflowNode.js.map +1 -0
- package/dist/TriggerBase.d.ts +128 -0
- package/dist/TriggerBase.js +808 -6
- package/dist/TriggerBase.js.map +1 -1
- package/dist/WaitDispatchRequest.d.ts +38 -0
- package/dist/WaitDispatchRequest.js +13 -0
- package/dist/WaitDispatchRequest.js.map +1 -0
- package/dist/WaitNode.d.ts +23 -0
- package/dist/WaitNode.js +26 -0
- package/dist/WaitNode.js.map +1 -0
- package/dist/adapters/BunRuntimeAdapter.d.ts +1 -0
- package/dist/adapters/BunRuntimeAdapter.js +1 -0
- package/dist/adapters/BunRuntimeAdapter.js.map +1 -1
- package/dist/adapters/DockerRuntimeAdapter.d.ts +2 -1
- package/dist/adapters/DockerRuntimeAdapter.js +10 -1
- package/dist/adapters/DockerRuntimeAdapter.js.map +1 -1
- package/dist/adapters/HttpRuntimeAdapter.d.ts +26 -5
- package/dist/adapters/HttpRuntimeAdapter.js +97 -16
- package/dist/adapters/HttpRuntimeAdapter.js.map +1 -1
- package/dist/adapters/NodeJsRuntimeAdapter.d.ts +1 -0
- package/dist/adapters/NodeJsRuntimeAdapter.js +1 -0
- package/dist/adapters/NodeJsRuntimeAdapter.js.map +1 -1
- package/dist/adapters/RuntimeAdapter.d.ts +17 -0
- package/dist/adapters/WasmRuntimeAdapter.d.ts +1 -0
- package/dist/adapters/WasmRuntimeAdapter.js +1 -0
- package/dist/adapters/WasmRuntimeAdapter.js.map +1 -1
- package/dist/adapters/grpc/GrpcChannelOptions.d.ts +31 -0
- package/dist/adapters/grpc/GrpcChannelOptions.js +68 -0
- package/dist/adapters/grpc/GrpcChannelOptions.js.map +1 -0
- package/dist/adapters/grpc/GrpcClientPool.d.ts +43 -0
- package/dist/adapters/grpc/GrpcClientPool.js +89 -0
- package/dist/adapters/grpc/GrpcClientPool.js.map +1 -0
- package/dist/adapters/grpc/GrpcCodec.d.ts +226 -0
- package/dist/adapters/grpc/GrpcCodec.js +275 -0
- package/dist/adapters/grpc/GrpcCodec.js.map +1 -0
- package/dist/adapters/grpc/GrpcErrors.d.ts +59 -0
- package/dist/adapters/grpc/GrpcErrors.js +190 -0
- package/dist/adapters/grpc/GrpcErrors.js.map +1 -0
- package/dist/adapters/grpc/GrpcHealthChecker.d.ts +69 -0
- package/dist/adapters/grpc/GrpcHealthChecker.js +96 -0
- package/dist/adapters/grpc/GrpcHealthChecker.js.map +1 -0
- package/dist/adapters/grpc/GrpcRuntimeAdapter.d.ts +98 -0
- package/dist/adapters/grpc/GrpcRuntimeAdapter.js +478 -0
- package/dist/adapters/grpc/GrpcRuntimeAdapter.js.map +1 -0
- package/dist/adapters/grpc/index.d.ts +13 -0
- package/dist/adapters/grpc/index.js +14 -0
- package/dist/adapters/grpc/index.js.map +1 -0
- package/dist/adapters/grpc/proto/blok/runtime/v1/runtime.proto +302 -0
- package/dist/adapters/grpc/types.d.ts +97 -0
- package/dist/adapters/grpc/types.js +41 -0
- package/dist/adapters/grpc/types.js.map +1 -0
- package/dist/adapters/transport.d.ts +108 -0
- package/dist/adapters/transport.js +196 -0
- package/dist/adapters/transport.js.map +1 -0
- package/dist/concurrency/ConcurrencyBackend.d.ts +61 -0
- package/dist/concurrency/ConcurrencyBackend.js +20 -0
- package/dist/concurrency/ConcurrencyBackend.js.map +1 -0
- package/dist/concurrency/ConcurrencyLimitError.d.ts +37 -0
- package/dist/concurrency/ConcurrencyLimitError.js +16 -0
- package/dist/concurrency/ConcurrencyLimitError.js.map +1 -0
- package/dist/concurrency/NatsKvConcurrencyBackend.d.ts +64 -0
- package/dist/concurrency/NatsKvConcurrencyBackend.js +297 -0
- package/dist/concurrency/NatsKvConcurrencyBackend.js.map +1 -0
- package/dist/concurrency/QueueExpiredError.d.ts +40 -0
- package/dist/concurrency/QueueExpiredError.js +15 -0
- package/dist/concurrency/QueueExpiredError.js.map +1 -0
- package/dist/concurrency/createConcurrencyBackend.d.ts +23 -0
- package/dist/concurrency/createConcurrencyBackend.js +34 -0
- package/dist/concurrency/createConcurrencyBackend.js.map +1 -0
- package/dist/concurrency/readConcurrencyConfig.d.ts +60 -0
- package/dist/concurrency/readConcurrencyConfig.js +60 -0
- package/dist/concurrency/readConcurrencyConfig.js.map +1 -0
- package/dist/idempotency/resolveIdempotencyKey.d.ts +20 -0
- package/dist/idempotency/resolveIdempotencyKey.js +37 -0
- package/dist/idempotency/resolveIdempotencyKey.js.map +1 -0
- package/dist/index.d.ts +35 -3
- package/dist/index.js +61 -2
- package/dist/index.js.map +1 -1
- package/dist/monitoring/ConcurrencyMetrics.d.ts +56 -0
- package/dist/monitoring/ConcurrencyMetrics.js +107 -0
- package/dist/monitoring/ConcurrencyMetrics.js.map +1 -0
- package/dist/monitoring/JanitorMetrics.d.ts +27 -0
- package/dist/monitoring/JanitorMetrics.js +48 -0
- package/dist/monitoring/JanitorMetrics.js.map +1 -0
- package/dist/scheduling/DebounceCoordinator.d.ts +88 -0
- package/dist/scheduling/DebounceCoordinator.js +141 -0
- package/dist/scheduling/DebounceCoordinator.js.map +1 -0
- package/dist/scheduling/DeferredDispatchSignal.d.ts +50 -0
- package/dist/scheduling/DeferredDispatchSignal.js +14 -0
- package/dist/scheduling/DeferredDispatchSignal.js.map +1 -0
- package/dist/scheduling/DeferredRunScheduler.d.ts +68 -0
- package/dist/scheduling/DeferredRunScheduler.js +154 -0
- package/dist/scheduling/DeferredRunScheduler.js.map +1 -0
- package/dist/scheduling/readSchedulingConfig.d.ts +24 -0
- package/dist/scheduling/readSchedulingConfig.js +52 -0
- package/dist/scheduling/readSchedulingConfig.js.map +1 -0
- package/dist/testing/WorkflowTestRunner.js +12 -0
- package/dist/testing/WorkflowTestRunner.js.map +1 -1
- package/dist/timeouts/StepTimeoutError.d.ts +22 -0
- package/dist/timeouts/StepTimeoutError.js +31 -0
- package/dist/timeouts/StepTimeoutError.js.map +1 -0
- package/dist/tracing/InMemoryRunStore.d.ts +28 -1
- package/dist/tracing/InMemoryRunStore.js +150 -0
- package/dist/tracing/InMemoryRunStore.js.map +1 -1
- package/dist/tracing/Janitor.d.ts +70 -0
- package/dist/tracing/Janitor.js +150 -0
- package/dist/tracing/Janitor.js.map +1 -0
- package/dist/tracing/PostgresRunStore.d.ts +30 -0
- package/dist/tracing/PostgresRunStore.js +435 -3
- package/dist/tracing/PostgresRunStore.js.map +1 -1
- package/dist/tracing/RunStore.d.ts +100 -1
- package/dist/tracing/RunTracker.d.ts +261 -11
- package/dist/tracing/RunTracker.js +691 -11
- package/dist/tracing/RunTracker.js.map +1 -1
- package/dist/tracing/SqliteRunStore.d.ts +23 -1
- package/dist/tracing/SqliteRunStore.js +421 -6
- package/dist/tracing/SqliteRunStore.js.map +1 -1
- package/dist/tracing/TraceRouter.d.ts +20 -2
- package/dist/tracing/TraceRouter.js +494 -9
- package/dist/tracing/TraceRouter.js.map +1 -1
- package/dist/tracing/sanitize.d.ts +11 -0
- package/dist/tracing/sanitize.js +29 -0
- package/dist/tracing/sanitize.js.map +1 -1
- package/dist/tracing/types.d.ts +429 -11
- package/dist/types/GlobalOptions.d.ts +9 -2
- package/dist/utils/createChildContext.d.ts +32 -0
- package/dist/utils/createChildContext.js +113 -0
- package/dist/utils/createChildContext.js.map +1 -0
- package/dist/workflow/PersistenceHelper.d.ts +46 -0
- package/dist/workflow/PersistenceHelper.js +57 -0
- package/dist/workflow/PersistenceHelper.js.map +1 -0
- package/dist/workflow/WorkflowNormalizer.d.ts +79 -0
- package/dist/workflow/WorkflowNormalizer.js +486 -0
- package/dist/workflow/WorkflowNormalizer.js.map +1 -0
- package/dist/workflow/WorkflowRegistry.d.ts +64 -0
- package/dist/workflow/WorkflowRegistry.js +81 -0
- package/dist/workflow/WorkflowRegistry.js.map +1 -0
- 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
|
+
}
|