@blokjs/runner 0.4.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Blok.js +32 -3
- package/dist/Blok.js.map +1 -1
- package/dist/Configuration.d.ts +41 -5
- package/dist/Configuration.js +215 -92
- package/dist/Configuration.js.map +1 -1
- package/dist/ForEachNode.d.ts +59 -0
- package/dist/ForEachNode.js +522 -0
- package/dist/ForEachNode.js.map +1 -0
- package/dist/LoopMaxIterationsError.d.ts +11 -0
- package/dist/LoopMaxIterationsError.js +18 -0
- package/dist/LoopMaxIterationsError.js.map +1 -0
- package/dist/LoopNode.d.ts +36 -0
- package/dist/LoopNode.js +182 -0
- package/dist/LoopNode.js.map +1 -0
- package/dist/Runner.d.ts +11 -1
- package/dist/Runner.js +9 -2
- package/dist/Runner.js.map +1 -1
- package/dist/RunnerSteps.js +419 -112
- package/dist/RunnerSteps.js.map +1 -1
- package/dist/RuntimeAdapterNode.d.ts +2 -1
- package/dist/RuntimeAdapterNode.js +2 -2
- package/dist/RuntimeAdapterNode.js.map +1 -1
- package/dist/RuntimeRegistry.d.ts +23 -2
- package/dist/RuntimeRegistry.js +31 -2
- package/dist/RuntimeRegistry.js.map +1 -1
- package/dist/SubworkflowNode.d.ts +106 -0
- package/dist/SubworkflowNode.js +261 -3
- package/dist/SubworkflowNode.js.map +1 -1
- package/dist/SwitchNode.d.ts +37 -0
- package/dist/SwitchNode.js +153 -0
- package/dist/SwitchNode.js.map +1 -0
- package/dist/TriggerBase.d.ts +50 -0
- package/dist/TriggerBase.js +262 -4
- package/dist/TriggerBase.js.map +1 -1
- package/dist/TryCatchNode.d.ts +32 -0
- package/dist/TryCatchNode.js +207 -0
- package/dist/TryCatchNode.js.map +1 -0
- package/dist/adapters/grpc/GrpcCodec.js +2 -2
- package/dist/adapters/grpc/GrpcRuntimeAdapter.d.ts +6 -4
- package/dist/adapters/grpc/GrpcRuntimeAdapter.js +6 -4
- package/dist/adapters/grpc/GrpcRuntimeAdapter.js.map +1 -1
- package/dist/adapters/grpc/types.d.ts +7 -5
- package/dist/adapters/grpc/types.js.map +1 -1
- package/dist/adapters/transport.d.ts +12 -41
- package/dist/adapters/transport.js +21 -70
- package/dist/adapters/transport.js.map +1 -1
- package/dist/cache/NodeResultCache.js +7 -0
- package/dist/cache/NodeResultCache.js.map +1 -1
- package/dist/concurrency/NatsKvConcurrencyBackend.js +18 -5
- package/dist/concurrency/NatsKvConcurrencyBackend.js.map +1 -1
- package/dist/concurrency/RedisConcurrencyBackend.d.ts +64 -0
- package/dist/concurrency/RedisConcurrencyBackend.js +374 -0
- package/dist/concurrency/RedisConcurrencyBackend.js.map +1 -0
- package/dist/concurrency/createConcurrencyBackend.d.ts +1 -0
- package/dist/concurrency/createConcurrencyBackend.js +5 -1
- package/dist/concurrency/createConcurrencyBackend.js.map +1 -1
- package/dist/defineNode.d.ts +8 -0
- package/dist/defineNode.js +25 -5
- package/dist/defineNode.js.map +1 -1
- package/dist/graphql/GraphQLSchemaGenerator.js +1 -1
- package/dist/graphql/GraphQLSchemaGenerator.js.map +1 -1
- package/dist/index.d.ts +10 -6
- package/dist/index.js +13 -9
- package/dist/index.js.map +1 -1
- package/dist/marketplace/RuntimeCatalog.d.ts +6 -0
- package/dist/marketplace/RuntimeCatalog.js.map +1 -1
- package/dist/marketplace/RuntimeDiscovery.d.ts +2 -2
- package/dist/marketplace/RuntimeDiscovery.js +18 -6
- package/dist/marketplace/RuntimeDiscovery.js.map +1 -1
- package/dist/monitoring/ConcurrencyMetrics.d.ts +26 -0
- package/dist/monitoring/ConcurrencyMetrics.js +36 -4
- package/dist/monitoring/ConcurrencyMetrics.js.map +1 -1
- package/dist/monitoring/ForEachWaitMetrics.d.ts +22 -0
- package/dist/monitoring/ForEachWaitMetrics.js +36 -0
- package/dist/monitoring/ForEachWaitMetrics.js.map +1 -0
- package/dist/openapi/OpenAPIGenerator.js +7 -2
- package/dist/openapi/OpenAPIGenerator.js.map +1 -1
- package/dist/runtime/PrimitiveStack.d.ts +64 -0
- package/dist/runtime/PrimitiveStack.js +92 -0
- package/dist/runtime/PrimitiveStack.js.map +1 -0
- package/dist/scheduling/DebounceBackend.d.ts +108 -0
- package/dist/scheduling/DebounceBackend.js +23 -0
- package/dist/scheduling/DebounceBackend.js.map +1 -0
- package/dist/scheduling/DebounceCoordinator.d.ts +65 -12
- package/dist/scheduling/DebounceCoordinator.js +234 -13
- package/dist/scheduling/DebounceCoordinator.js.map +1 -1
- package/dist/scheduling/DeferredRunScheduler.d.ts +28 -0
- package/dist/scheduling/DeferredRunScheduler.js +105 -3
- package/dist/scheduling/DeferredRunScheduler.js.map +1 -1
- package/dist/scheduling/NatsKvDebounceBackend.d.ts +53 -0
- package/dist/scheduling/NatsKvDebounceBackend.js +334 -0
- package/dist/scheduling/NatsKvDebounceBackend.js.map +1 -0
- package/dist/scheduling/RedisDebounceBackend.d.ts +49 -0
- package/dist/scheduling/RedisDebounceBackend.js +356 -0
- package/dist/scheduling/RedisDebounceBackend.js.map +1 -0
- package/dist/scheduling/createDebounceBackend.d.ts +25 -0
- package/dist/scheduling/createDebounceBackend.js +39 -0
- package/dist/scheduling/createDebounceBackend.js.map +1 -0
- package/dist/security/AuditLogger.js +1 -1
- package/dist/security/AuditLogger.js.map +1 -1
- package/dist/security/AuthMiddleware.d.ts +19 -20
- package/dist/security/AuthMiddleware.js +35 -20
- package/dist/security/AuthMiddleware.js.map +1 -1
- package/dist/security/OAuthProvider.js +2 -2
- package/dist/security/OAuthProvider.js.map +1 -1
- package/dist/security/SecretManager.js +14 -13
- package/dist/security/SecretManager.js.map +1 -1
- package/dist/security/index.d.ts +3 -1
- package/dist/security/index.js +3 -1
- package/dist/security/index.js.map +1 -1
- package/dist/testing/TestHarness.d.ts +27 -12
- package/dist/testing/TestHarness.js +19 -3
- package/dist/testing/TestHarness.js.map +1 -1
- package/dist/testing/WorkflowTestRunner.js +0 -7
- package/dist/testing/WorkflowTestRunner.js.map +1 -1
- package/dist/tracing/InMemoryRunStore.d.ts +14 -1
- package/dist/tracing/InMemoryRunStore.js +95 -6
- package/dist/tracing/InMemoryRunStore.js.map +1 -1
- package/dist/tracing/PostgresRunStore.d.ts +28 -2
- package/dist/tracing/PostgresRunStore.js +276 -3
- package/dist/tracing/PostgresRunStore.js.map +1 -1
- package/dist/tracing/RoutingDiagnostics.d.ts +55 -0
- package/dist/tracing/RoutingDiagnostics.js +50 -0
- package/dist/tracing/RoutingDiagnostics.js.map +1 -0
- package/dist/tracing/RunStore.d.ts +82 -1
- package/dist/tracing/RunTracker.d.ts +7 -1
- package/dist/tracing/RunTracker.js +23 -0
- package/dist/tracing/RunTracker.js.map +1 -1
- package/dist/tracing/SqliteRunStore.d.ts +57 -2
- package/dist/tracing/SqliteRunStore.js +408 -48
- package/dist/tracing/SqliteRunStore.js.map +1 -1
- package/dist/tracing/TraceRouter.js +380 -18
- package/dist/tracing/TraceRouter.js.map +1 -1
- package/dist/tracing/createStore.js +14 -3
- package/dist/tracing/createStore.js.map +1 -1
- package/dist/tracing/metadataFilter.d.ts +63 -0
- package/dist/tracing/metadataFilter.js +224 -0
- package/dist/tracing/metadataFilter.js.map +1 -0
- package/dist/tracing/types.d.ts +331 -7
- package/dist/utils/envAllowlist.d.ts +35 -0
- package/dist/utils/envAllowlist.js +113 -0
- package/dist/utils/envAllowlist.js.map +1 -0
- package/dist/version/RuntimeVersionValidator.d.ts +38 -0
- package/dist/version/RuntimeVersionValidator.js +121 -0
- package/dist/version/RuntimeVersionValidator.js.map +1 -0
- package/dist/visualization/WorkflowVisualizer.js +4 -4
- package/dist/visualization/WorkflowVisualizer.js.map +1 -1
- package/dist/workflow/PersistenceHelper.d.ts +18 -10
- package/dist/workflow/PersistenceHelper.js +35 -9
- package/dist/workflow/PersistenceHelper.js.map +1 -1
- package/dist/workflow/WorkflowNormalizer.d.ts +19 -1
- package/dist/workflow/WorkflowNormalizer.js +469 -19
- package/dist/workflow/WorkflowNormalizer.js.map +1 -1
- package/dist/workflow/WorkflowRegistry.d.ts +122 -0
- package/dist/workflow/WorkflowRegistry.js +121 -0
- package/dist/workflow/WorkflowRegistry.js.map +1 -1
- package/dist/workflow/sampleBody.d.ts +54 -0
- package/dist/workflow/sampleBody.js +320 -0
- package/dist/workflow/sampleBody.js.map +1 -0
- package/package.json +3 -8
- package/dist/adapters/HttpRuntimeAdapter.d.ts +0 -79
- package/dist/adapters/HttpRuntimeAdapter.js +0 -233
- package/dist/adapters/HttpRuntimeAdapter.js.map +0 -1
|
@@ -87,9 +87,13 @@ export class NatsKvConcurrencyBackend {
|
|
|
87
87
|
connectOpts.user = this.config.user;
|
|
88
88
|
if (this.config.pass)
|
|
89
89
|
connectOpts.pass = this.config.pass;
|
|
90
|
-
this.nc =
|
|
91
|
-
//
|
|
92
|
-
|
|
90
|
+
this.nc = await natsModule.connect(connectOpts);
|
|
91
|
+
// nats.js v2.x — KV lives at `nc.jetstream().views.kv(name)`. The
|
|
92
|
+
// returned `KV` auto-creates the bucket on first use given the
|
|
93
|
+
// connection has KV bucket-create permission. (Earlier versions
|
|
94
|
+
// exposed `nc.kv(name)` directly; that API was removed.)
|
|
95
|
+
const js = this.nc.jetstream();
|
|
96
|
+
this.kv = await js.views.kv(this.config.bucketName);
|
|
93
97
|
this.connected = true;
|
|
94
98
|
}
|
|
95
99
|
async disconnect() {
|
|
@@ -227,8 +231,17 @@ export class NatsKvConcurrencyBackend {
|
|
|
227
231
|
async purgeExpired(now) {
|
|
228
232
|
const kv = this.requireKv();
|
|
229
233
|
let purged = 0;
|
|
230
|
-
//
|
|
231
|
-
|
|
234
|
+
// **DRAIN THE ITERATOR FIRST.** nats.js v2.x `kv.keys()` returns a
|
|
235
|
+
// `QueuedIterator` backed by a JetStream watch consumer. Calling
|
|
236
|
+
// `kv.get()` mid-iteration interferes with the iterator's internal
|
|
237
|
+
// state — observed in practice that subsequent yields silently
|
|
238
|
+
// drop. Collect every key into an array before doing per-key
|
|
239
|
+
// reads, then operate on the array.
|
|
240
|
+
const allKeys = [];
|
|
241
|
+
for await (const key of await kv.keys()) {
|
|
242
|
+
allKeys.push(key);
|
|
243
|
+
}
|
|
244
|
+
for (const key of allKeys) {
|
|
232
245
|
const entry = await this.safeGet(kv, key);
|
|
233
246
|
// Treat both legitimate misses and fetch failures as "skip
|
|
234
247
|
// this bucket" — purge is a best-effort sweep.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NatsKvConcurrencyBackend.js","sourceRoot":"","sources":["../../src/concurrency/NatsKvConcurrencyBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAqBtE,MAAM,mBAAmB,GAAG,kBAAkB,CAAC;AAC/C,MAAM,eAAe,GAAG,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"NatsKvConcurrencyBackend.js","sourceRoot":"","sources":["../../src/concurrency/NatsKvConcurrencyBackend.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier C #4 follow-up · Redis-backed concurrency backend.
|
|
3
|
+
*
|
|
4
|
+
* Coordinates per-(workflow, concurrencyKey) lease state across processes
|
|
5
|
+
* via a single Redis key per bucket. Atomicity comes from server-side Lua
|
|
6
|
+
* scripts — `EVAL` runs single-threaded against the keyspace, so the
|
|
7
|
+
* read → filter → check-limit → write sequence is a single round-trip
|
|
8
|
+
* with no OCC retry loop (the headline win over the NATS KV backend's
|
|
9
|
+
* `WATCH`/`MULTI`/`EXEC`-style optimistic concurrency).
|
|
10
|
+
*
|
|
11
|
+
* Storage model: one Redis string key per `(workflowName, concurrencyKey)`
|
|
12
|
+
* bucket. Value is a JSON-encoded `{leases: [{runId, expiresAt}]}`
|
|
13
|
+
* document. Bounded-cardinality assumption identical to NATS KV — typical
|
|
14
|
+
* concurrency keys hold 1-50 active leases.
|
|
15
|
+
*
|
|
16
|
+
* Lease leak: each lease carries an `expiresAt`. Expired leases are
|
|
17
|
+
* lazy-purged inside the Lua script that observes them; an explicit
|
|
18
|
+
* `purgeExpired` SCAN sweep is also exposed for janitor use.
|
|
19
|
+
*
|
|
20
|
+
* Connection: ioredis is loaded via dynamic `import("ioredis")` so the
|
|
21
|
+
* dependency stays optional. Matches the existing pattern used by
|
|
22
|
+
* `triggers/worker`'s `RedisStreamsAdapter` and
|
|
23
|
+
* `triggers/pubsub`'s `RedisStreamsPubSubAdapter`.
|
|
24
|
+
*/
|
|
25
|
+
import type { ConcurrencySlotResult } from "../tracing/types";
|
|
26
|
+
import type { ConcurrencyBackend } from "./ConcurrencyBackend";
|
|
27
|
+
export interface RedisConcurrencyConfig {
|
|
28
|
+
/** Full Redis connection URL (e.g. `redis://[user:pass@]host:port[/db]`). Takes precedence over host/port. */
|
|
29
|
+
url?: string;
|
|
30
|
+
host?: string;
|
|
31
|
+
port?: number;
|
|
32
|
+
password?: string;
|
|
33
|
+
username?: string;
|
|
34
|
+
db?: number;
|
|
35
|
+
tls?: boolean;
|
|
36
|
+
/** Namespace prefix for every Redis key the backend touches. */
|
|
37
|
+
keyPrefix: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Read configuration from environment variables. Used by
|
|
41
|
+
* {@link createConcurrencyBackend} when the operator opts into Redis.
|
|
42
|
+
*/
|
|
43
|
+
export declare function readRedisConfigFromEnv(): RedisConcurrencyConfig;
|
|
44
|
+
export declare class RedisConcurrencyBackend implements ConcurrencyBackend {
|
|
45
|
+
readonly name = "redis";
|
|
46
|
+
private client;
|
|
47
|
+
private readonly config;
|
|
48
|
+
private connected;
|
|
49
|
+
constructor(config?: Partial<RedisConcurrencyConfig>);
|
|
50
|
+
connect(): Promise<void>;
|
|
51
|
+
disconnect(): Promise<void>;
|
|
52
|
+
private bucketKey;
|
|
53
|
+
private encodeSegment;
|
|
54
|
+
private requireClient;
|
|
55
|
+
acquireSlot(workflowName: string, concurrencyKey: string, concurrencyLimit: number, runId: string, leaseExpiresAt: number): Promise<ConcurrencySlotResult>;
|
|
56
|
+
releaseSlot(workflowName: string, concurrencyKey: string, runId: string): Promise<void>;
|
|
57
|
+
purgeExpired(now: number): Promise<number>;
|
|
58
|
+
/**
|
|
59
|
+
* Decode the `{acquired, currentInFlight}` pair from a Lua eval result.
|
|
60
|
+
* ioredis returns Redis arrays as plain JS arrays of (string | number)
|
|
61
|
+
* — the script returns integers, so both elements should be numbers.
|
|
62
|
+
*/
|
|
63
|
+
private parsePair;
|
|
64
|
+
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier C #4 follow-up · Redis-backed concurrency backend.
|
|
3
|
+
*
|
|
4
|
+
* Coordinates per-(workflow, concurrencyKey) lease state across processes
|
|
5
|
+
* via a single Redis key per bucket. Atomicity comes from server-side Lua
|
|
6
|
+
* scripts — `EVAL` runs single-threaded against the keyspace, so the
|
|
7
|
+
* read → filter → check-limit → write sequence is a single round-trip
|
|
8
|
+
* with no OCC retry loop (the headline win over the NATS KV backend's
|
|
9
|
+
* `WATCH`/`MULTI`/`EXEC`-style optimistic concurrency).
|
|
10
|
+
*
|
|
11
|
+
* Storage model: one Redis string key per `(workflowName, concurrencyKey)`
|
|
12
|
+
* bucket. Value is a JSON-encoded `{leases: [{runId, expiresAt}]}`
|
|
13
|
+
* document. Bounded-cardinality assumption identical to NATS KV — typical
|
|
14
|
+
* concurrency keys hold 1-50 active leases.
|
|
15
|
+
*
|
|
16
|
+
* Lease leak: each lease carries an `expiresAt`. Expired leases are
|
|
17
|
+
* lazy-purged inside the Lua script that observes them; an explicit
|
|
18
|
+
* `purgeExpired` SCAN sweep is also exposed for janitor use.
|
|
19
|
+
*
|
|
20
|
+
* Connection: ioredis is loaded via dynamic `import("ioredis")` so the
|
|
21
|
+
* dependency stays optional. Matches the existing pattern used by
|
|
22
|
+
* `triggers/worker`'s `RedisStreamsAdapter` and
|
|
23
|
+
* `triggers/pubsub`'s `RedisStreamsPubSubAdapter`.
|
|
24
|
+
*/
|
|
25
|
+
import { ConcurrencyMetrics } from "../monitoring/ConcurrencyMetrics";
|
|
26
|
+
const DEFAULT_KEY_PREFIX = "blok-concurrency";
|
|
27
|
+
/**
|
|
28
|
+
* Read configuration from environment variables. Used by
|
|
29
|
+
* {@link createConcurrencyBackend} when the operator opts into Redis.
|
|
30
|
+
*/
|
|
31
|
+
export function readRedisConfigFromEnv() {
|
|
32
|
+
const url = process.env.BLOK_CONCURRENCY_REDIS_URL?.trim() || undefined;
|
|
33
|
+
const host = process.env.BLOK_CONCURRENCY_REDIS_HOST?.trim() || undefined;
|
|
34
|
+
const portRaw = process.env.BLOK_CONCURRENCY_REDIS_PORT?.trim();
|
|
35
|
+
const port = portRaw && /^\d+$/.test(portRaw) ? Number(portRaw) : undefined;
|
|
36
|
+
const dbRaw = process.env.BLOK_CONCURRENCY_REDIS_DB?.trim();
|
|
37
|
+
const db = dbRaw && /^\d+$/.test(dbRaw) ? Number(dbRaw) : undefined;
|
|
38
|
+
const tls = process.env.BLOK_CONCURRENCY_REDIS_TLS === "1" || process.env.BLOK_CONCURRENCY_REDIS_TLS === "true";
|
|
39
|
+
return {
|
|
40
|
+
url,
|
|
41
|
+
host,
|
|
42
|
+
port,
|
|
43
|
+
password: process.env.BLOK_CONCURRENCY_REDIS_PASSWORD,
|
|
44
|
+
username: process.env.BLOK_CONCURRENCY_REDIS_USERNAME,
|
|
45
|
+
db,
|
|
46
|
+
tls,
|
|
47
|
+
keyPrefix: process.env.BLOK_CONCURRENCY_REDIS_KEY_PREFIX?.trim() || DEFAULT_KEY_PREFIX,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Atomic acquire. Returns `{acquired, currentInFlight}` as a 2-element array.
|
|
52
|
+
*
|
|
53
|
+
* Storage shape: the bucket is either MISSING (no active leases) or a
|
|
54
|
+
* JSON string `{"leases":[{"runId":"...","expiresAt":<ms>}, ...]}`.
|
|
55
|
+
* When the leases array would become empty we DEL the key — we never
|
|
56
|
+
* encode the empty array (sidesteps the cjson empty-table-as-object trap).
|
|
57
|
+
*
|
|
58
|
+
* KEYS[1] = bucket key
|
|
59
|
+
* ARGV[1] = limit (int as string)
|
|
60
|
+
* ARGV[2] = runId
|
|
61
|
+
* ARGV[3] = leaseExpiresAt (ms as string)
|
|
62
|
+
* ARGV[4] = now (ms as string)
|
|
63
|
+
*
|
|
64
|
+
* Returns: {acquired, currentInFlight}
|
|
65
|
+
* - acquired: 1 = granted, 0 = denied
|
|
66
|
+
* - currentInFlight: in-flight count INCLUDING the granted slot on success,
|
|
67
|
+
* count at denial on rejection.
|
|
68
|
+
*/
|
|
69
|
+
const ACQUIRE_LUA = `
|
|
70
|
+
local raw = redis.call('GET', KEYS[1])
|
|
71
|
+
local leases = {}
|
|
72
|
+
if raw and raw ~= '' then
|
|
73
|
+
local ok, parsed = pcall(cjson.decode, raw)
|
|
74
|
+
if ok and type(parsed) == 'table' and type(parsed.leases) == 'table' then
|
|
75
|
+
leases = parsed.leases
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
local now = tonumber(ARGV[4])
|
|
80
|
+
local active = {}
|
|
81
|
+
for i = 1, #leases do
|
|
82
|
+
local l = leases[i]
|
|
83
|
+
if type(l) == 'table' and tonumber(l.expiresAt) and tonumber(l.expiresAt) > now then
|
|
84
|
+
active[#active + 1] = { runId = tostring(l.runId), expiresAt = tonumber(l.expiresAt) }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
local runId = ARGV[2]
|
|
89
|
+
local newExpires = tonumber(ARGV[3])
|
|
90
|
+
|
|
91
|
+
-- Idempotent re-acquire: refresh lease, don't grow count.
|
|
92
|
+
for i = 1, #active do
|
|
93
|
+
if active[i].runId == runId then
|
|
94
|
+
active[i] = { runId = runId, expiresAt = newExpires }
|
|
95
|
+
redis.call('SET', KEYS[1], cjson.encode({ leases = active }))
|
|
96
|
+
return { 1, #active }
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
local limit = tonumber(ARGV[1])
|
|
101
|
+
if #active >= limit then
|
|
102
|
+
-- Persist the purge of expired entries (if any) so the bucket stays clean.
|
|
103
|
+
if #active < #leases then
|
|
104
|
+
if #active == 0 then
|
|
105
|
+
redis.call('DEL', KEYS[1])
|
|
106
|
+
else
|
|
107
|
+
redis.call('SET', KEYS[1], cjson.encode({ leases = active }))
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
return { 0, #active }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
active[#active + 1] = { runId = runId, expiresAt = newExpires }
|
|
114
|
+
redis.call('SET', KEYS[1], cjson.encode({ leases = active }))
|
|
115
|
+
return { 1, #active }
|
|
116
|
+
`;
|
|
117
|
+
/**
|
|
118
|
+
* Atomic release. Removes a lease by runId. DELs the bucket when empty.
|
|
119
|
+
*
|
|
120
|
+
* KEYS[1] = bucket key
|
|
121
|
+
* ARGV[1] = runId
|
|
122
|
+
*
|
|
123
|
+
* Returns: 1 if a lease was removed, 0 if no-op (bucket missing or runId not present).
|
|
124
|
+
*/
|
|
125
|
+
const RELEASE_LUA = `
|
|
126
|
+
local raw = redis.call('GET', KEYS[1])
|
|
127
|
+
if not raw or raw == '' then return 0 end
|
|
128
|
+
|
|
129
|
+
local ok, parsed = pcall(cjson.decode, raw)
|
|
130
|
+
if not ok or type(parsed) ~= 'table' or type(parsed.leases) ~= 'table' then return 0 end
|
|
131
|
+
|
|
132
|
+
local target = ARGV[1]
|
|
133
|
+
local next_leases = {}
|
|
134
|
+
local removed = 0
|
|
135
|
+
for i = 1, #parsed.leases do
|
|
136
|
+
local l = parsed.leases[i]
|
|
137
|
+
if type(l) == 'table' and tostring(l.runId) == target then
|
|
138
|
+
removed = 1
|
|
139
|
+
else
|
|
140
|
+
next_leases[#next_leases + 1] = { runId = tostring(l.runId), expiresAt = tonumber(l.expiresAt) }
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
if removed == 0 then return 0 end
|
|
145
|
+
|
|
146
|
+
if #next_leases == 0 then
|
|
147
|
+
redis.call('DEL', KEYS[1])
|
|
148
|
+
else
|
|
149
|
+
redis.call('SET', KEYS[1], cjson.encode({ leases = next_leases }))
|
|
150
|
+
end
|
|
151
|
+
return 1
|
|
152
|
+
`;
|
|
153
|
+
/**
|
|
154
|
+
* Purge expired leases from a single bucket. Atomic.
|
|
155
|
+
*
|
|
156
|
+
* KEYS[1] = bucket key
|
|
157
|
+
* ARGV[1] = now (ms as string)
|
|
158
|
+
*
|
|
159
|
+
* Returns: number of leases purged.
|
|
160
|
+
*/
|
|
161
|
+
const PURGE_BUCKET_LUA = `
|
|
162
|
+
local raw = redis.call('GET', KEYS[1])
|
|
163
|
+
if not raw or raw == '' then return 0 end
|
|
164
|
+
|
|
165
|
+
local ok, parsed = pcall(cjson.decode, raw)
|
|
166
|
+
if not ok or type(parsed) ~= 'table' or type(parsed.leases) ~= 'table' then return 0 end
|
|
167
|
+
|
|
168
|
+
local now = tonumber(ARGV[1])
|
|
169
|
+
local active = {}
|
|
170
|
+
for i = 1, #parsed.leases do
|
|
171
|
+
local l = parsed.leases[i]
|
|
172
|
+
if type(l) == 'table' and tonumber(l.expiresAt) and tonumber(l.expiresAt) > now then
|
|
173
|
+
active[#active + 1] = { runId = tostring(l.runId), expiresAt = tonumber(l.expiresAt) }
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
local purged = #parsed.leases - #active
|
|
178
|
+
if purged == 0 then return 0 end
|
|
179
|
+
|
|
180
|
+
if #active == 0 then
|
|
181
|
+
redis.call('DEL', KEYS[1])
|
|
182
|
+
else
|
|
183
|
+
redis.call('SET', KEYS[1], cjson.encode({ leases = active }))
|
|
184
|
+
end
|
|
185
|
+
return purged
|
|
186
|
+
`;
|
|
187
|
+
export class RedisConcurrencyBackend {
|
|
188
|
+
name = "redis";
|
|
189
|
+
client = null;
|
|
190
|
+
config;
|
|
191
|
+
connected = false;
|
|
192
|
+
constructor(config) {
|
|
193
|
+
const env = readRedisConfigFromEnv();
|
|
194
|
+
this.config = {
|
|
195
|
+
url: config?.url ?? env.url,
|
|
196
|
+
host: config?.host ?? env.host,
|
|
197
|
+
port: config?.port ?? env.port,
|
|
198
|
+
password: config?.password ?? env.password,
|
|
199
|
+
username: config?.username ?? env.username,
|
|
200
|
+
db: config?.db ?? env.db,
|
|
201
|
+
tls: config?.tls ?? env.tls,
|
|
202
|
+
keyPrefix: config?.keyPrefix ?? env.keyPrefix,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
async connect() {
|
|
206
|
+
if (this.connected)
|
|
207
|
+
return;
|
|
208
|
+
// Security review FW-5 parity — refuse to start in production with
|
|
209
|
+
// the default key prefix. Two deployments sharing a Redis instance
|
|
210
|
+
// would silently contend on the same `(workflow, key)` buckets,
|
|
211
|
+
// corrupting concurrency state across tenants.
|
|
212
|
+
const blokEnv = process.env.BLOK_ENV;
|
|
213
|
+
const nodeEnv = process.env.NODE_ENV;
|
|
214
|
+
const isProd = blokEnv === "production" || nodeEnv === "production";
|
|
215
|
+
if (isProd && this.config.keyPrefix === DEFAULT_KEY_PREFIX) {
|
|
216
|
+
throw new Error(`[blok] Redis concurrency backend refuses to start in production with the default key prefix ('${DEFAULT_KEY_PREFIX}'). Set BLOK_CONCURRENCY_REDIS_KEY_PREFIX to a deployment-unique value (e.g. 'blok-concurrency-acme-prod') to prevent cross-deployment collision on a shared Redis instance.`);
|
|
217
|
+
}
|
|
218
|
+
let ioredisModule;
|
|
219
|
+
try {
|
|
220
|
+
ioredisModule = (await import("ioredis"));
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
throw new Error(`RedisConcurrencyBackend requires the 'ioredis' package. Install it: \`bun add ioredis\` or \`npm install ioredis\`. Underlying error: ${err instanceof Error ? err.message : String(err)}`);
|
|
224
|
+
}
|
|
225
|
+
const IORedisCtor = ioredisModule.default ?? ioredisModule.Redis;
|
|
226
|
+
if (!IORedisCtor) {
|
|
227
|
+
throw new Error("RedisConcurrencyBackend could not locate the ioredis constructor on the imported module. Reinstall ioredis or report this issue.");
|
|
228
|
+
}
|
|
229
|
+
// Production-friendly defaults: fail fast on connection trouble
|
|
230
|
+
// rather than hanging triggers on broker outage. Operators who
|
|
231
|
+
// want different semantics can layer a wrapper or fork the
|
|
232
|
+
// backend — these are intentional opinions matching the "trigger
|
|
233
|
+
// startup should not block indefinitely on broker reachability"
|
|
234
|
+
// posture of the rest of the runner.
|
|
235
|
+
const failFastDefaults = {
|
|
236
|
+
connectTimeout: 5_000,
|
|
237
|
+
maxRetriesPerRequest: 0,
|
|
238
|
+
enableOfflineQueue: false,
|
|
239
|
+
lazyConnect: true,
|
|
240
|
+
};
|
|
241
|
+
if (this.config.url) {
|
|
242
|
+
this.client = new IORedisCtor(this.config.url);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
const opts = { ...failFastDefaults };
|
|
246
|
+
if (this.config.host)
|
|
247
|
+
opts.host = this.config.host;
|
|
248
|
+
if (this.config.port)
|
|
249
|
+
opts.port = this.config.port;
|
|
250
|
+
if (this.config.username)
|
|
251
|
+
opts.username = this.config.username;
|
|
252
|
+
if (this.config.password)
|
|
253
|
+
opts.password = this.config.password;
|
|
254
|
+
if (typeof this.config.db === "number")
|
|
255
|
+
opts.db = this.config.db;
|
|
256
|
+
if (this.config.tls)
|
|
257
|
+
opts.tls = {};
|
|
258
|
+
this.client = new IORedisCtor(opts);
|
|
259
|
+
}
|
|
260
|
+
// Surface async errors instead of crashing the process.
|
|
261
|
+
this.client.on("error", (err) => {
|
|
262
|
+
console.warn(`[blok][concurrency][redis] client error: ${err.message}`);
|
|
263
|
+
});
|
|
264
|
+
await this.client.ping();
|
|
265
|
+
this.connected = true;
|
|
266
|
+
}
|
|
267
|
+
async disconnect() {
|
|
268
|
+
if (!this.connected)
|
|
269
|
+
return;
|
|
270
|
+
try {
|
|
271
|
+
await this.client?.quit();
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
// quit() can reject if the connection is already torn down; ignore.
|
|
275
|
+
}
|
|
276
|
+
finally {
|
|
277
|
+
this.client = null;
|
|
278
|
+
this.connected = false;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
bucketKey(workflowName, concurrencyKey) {
|
|
282
|
+
// Mirror NATS KV's hex-escape scheme so workflow/key strings with
|
|
283
|
+
// special characters (`:`, `>`, etc.) round-trip without collision.
|
|
284
|
+
// Cross-backend portability matters when operators migrate between
|
|
285
|
+
// backends: the same `(workflow, key)` pair maps to the same bucket
|
|
286
|
+
// identity modulo the prefix.
|
|
287
|
+
return `${this.config.keyPrefix}:${this.encodeSegment(workflowName)}__${this.encodeSegment(concurrencyKey)}`;
|
|
288
|
+
}
|
|
289
|
+
encodeSegment(s) {
|
|
290
|
+
// Same regex as NATS KV — replace anything outside the safe set
|
|
291
|
+
// with hex escape `_HHHH_` so the encoding is lossless and matches
|
|
292
|
+
// the NATS backend byte-for-byte modulo prefix.
|
|
293
|
+
return s.replace(/[^-_=.a-zA-Z0-9]/g, (ch) => `_${ch.codePointAt(0)?.toString(16)}_`);
|
|
294
|
+
}
|
|
295
|
+
requireClient() {
|
|
296
|
+
if (!this.client) {
|
|
297
|
+
throw new Error("RedisConcurrencyBackend not connected — call connect() first.");
|
|
298
|
+
}
|
|
299
|
+
return this.client;
|
|
300
|
+
}
|
|
301
|
+
async acquireSlot(workflowName, concurrencyKey, concurrencyLimit, runId, leaseExpiresAt) {
|
|
302
|
+
const client = this.requireClient();
|
|
303
|
+
const key = this.bucketKey(workflowName, concurrencyKey);
|
|
304
|
+
const metricAttrs = { workflow_name: workflowName, concurrency_key: concurrencyKey };
|
|
305
|
+
try {
|
|
306
|
+
const raw = await client.eval(ACQUIRE_LUA, 1, key, String(concurrencyLimit), runId, String(leaseExpiresAt), String(Date.now()));
|
|
307
|
+
const [acquiredFlag, currentInFlight] = this.parsePair(raw);
|
|
308
|
+
const outcome = acquiredFlag === 1 ? "success" : "denied";
|
|
309
|
+
// Lua is single-shot — attempt depth is always 0.
|
|
310
|
+
ConcurrencyMetrics.getInstance().recordOccRetries({ ...metricAttrs, outcome }, 0);
|
|
311
|
+
return { acquired: acquiredFlag === 1, currentInFlight };
|
|
312
|
+
}
|
|
313
|
+
catch (err) {
|
|
314
|
+
console.warn(`[blok][concurrency][redis] acquireSlot eval failed for ${workflowName}:${concurrencyKey}: ${err instanceof Error ? err.message : String(err)}; failing closed`);
|
|
315
|
+
ConcurrencyMetrics.getInstance().recordOccRetries({ ...metricAttrs, outcome: "fail-closed" }, 0);
|
|
316
|
+
return { acquired: false, currentInFlight: -1 };
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async releaseSlot(workflowName, concurrencyKey, runId) {
|
|
320
|
+
const client = this.requireClient();
|
|
321
|
+
const key = this.bucketKey(workflowName, concurrencyKey);
|
|
322
|
+
try {
|
|
323
|
+
await client.eval(RELEASE_LUA, 1, key, runId);
|
|
324
|
+
}
|
|
325
|
+
catch (err) {
|
|
326
|
+
// Lease will expire via TTL — release is best-effort. Surface
|
|
327
|
+
// the error so operators can see broker outages.
|
|
328
|
+
console.warn(`[blok][concurrency][redis] releaseSlot eval failed for ${workflowName}:${concurrencyKey} runId=${runId}: ${err instanceof Error ? err.message : String(err)}; lease will expire via TTL`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
async purgeExpired(now) {
|
|
332
|
+
const client = this.requireClient();
|
|
333
|
+
const pattern = `${this.config.keyPrefix}:*`;
|
|
334
|
+
let cursor = "0";
|
|
335
|
+
let purged = 0;
|
|
336
|
+
do {
|
|
337
|
+
let res;
|
|
338
|
+
try {
|
|
339
|
+
res = await client.scan(cursor, "MATCH", pattern, "COUNT", 100);
|
|
340
|
+
}
|
|
341
|
+
catch (err) {
|
|
342
|
+
console.warn(`[blok][concurrency][redis] purgeExpired SCAN failed: ${err instanceof Error ? err.message : String(err)}; aborting sweep`);
|
|
343
|
+
return purged;
|
|
344
|
+
}
|
|
345
|
+
const [nextCursor, keys] = res;
|
|
346
|
+
cursor = nextCursor;
|
|
347
|
+
for (const key of keys) {
|
|
348
|
+
try {
|
|
349
|
+
const raw = await client.eval(PURGE_BUCKET_LUA, 1, key, String(now));
|
|
350
|
+
const count = typeof raw === "number" ? raw : Number(raw);
|
|
351
|
+
if (!Number.isNaN(count))
|
|
352
|
+
purged += count;
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
// Best-effort — skip this bucket; janitor will retry on next sweep.
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
} while (cursor !== "0");
|
|
359
|
+
return purged;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Decode the `{acquired, currentInFlight}` pair from a Lua eval result.
|
|
363
|
+
* ioredis returns Redis arrays as plain JS arrays of (string | number)
|
|
364
|
+
* — the script returns integers, so both elements should be numbers.
|
|
365
|
+
*/
|
|
366
|
+
parsePair(raw) {
|
|
367
|
+
if (!Array.isArray(raw) || raw.length < 2)
|
|
368
|
+
return [0, -1];
|
|
369
|
+
const acquired = typeof raw[0] === "number" ? raw[0] : Number(raw[0]);
|
|
370
|
+
const current = typeof raw[1] === "number" ? raw[1] : Number(raw[1]);
|
|
371
|
+
return [Number.isFinite(acquired) ? acquired : 0, Number.isFinite(current) ? current : -1];
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
//# sourceMappingURL=RedisConcurrencyBackend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RedisConcurrencyBackend.js","sourceRoot":"","sources":["../../src/concurrency/RedisConcurrencyBackend.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,6 +15,7 @@ import type { ConcurrencyBackend } from "./ConcurrencyBackend";
|
|
|
15
15
|
* Recognized values:
|
|
16
16
|
* - unset / `""` / `"memory"` — null (use default in-process via RunStore)
|
|
17
17
|
* - `"nats-kv"` — NATS KV backend (requires `nats` package + reachable NATS server)
|
|
18
|
+
* - `"redis"` — Redis backend (requires `ioredis` package + reachable Redis server)
|
|
18
19
|
*
|
|
19
20
|
* Unknown values throw at startup with a clear error message — silently
|
|
20
21
|
* falling back would be dangerous (operator thinks they configured cross-
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* `RunTracker.getInstance().setConcurrencyBackend(backend)`.
|
|
9
9
|
*/
|
|
10
10
|
import { NatsKvConcurrencyBackend } from "./NatsKvConcurrencyBackend";
|
|
11
|
+
import { RedisConcurrencyBackend } from "./RedisConcurrencyBackend";
|
|
11
12
|
/**
|
|
12
13
|
* Returns a configured `ConcurrencyBackend` based on
|
|
13
14
|
* `BLOK_CONCURRENCY_BACKEND`, or `null` for the default in-process backend.
|
|
@@ -15,6 +16,7 @@ import { NatsKvConcurrencyBackend } from "./NatsKvConcurrencyBackend";
|
|
|
15
16
|
* Recognized values:
|
|
16
17
|
* - unset / `""` / `"memory"` — null (use default in-process via RunStore)
|
|
17
18
|
* - `"nats-kv"` — NATS KV backend (requires `nats` package + reachable NATS server)
|
|
19
|
+
* - `"redis"` — Redis backend (requires `ioredis` package + reachable Redis server)
|
|
18
20
|
*
|
|
19
21
|
* Unknown values throw at startup with a clear error message — silently
|
|
20
22
|
* falling back would be dangerous (operator thinks they configured cross-
|
|
@@ -27,8 +29,10 @@ export function createConcurrencyBackend() {
|
|
|
27
29
|
switch (kind) {
|
|
28
30
|
case "nats-kv":
|
|
29
31
|
return new NatsKvConcurrencyBackend();
|
|
32
|
+
case "redis":
|
|
33
|
+
return new RedisConcurrencyBackend();
|
|
30
34
|
default:
|
|
31
|
-
throw new Error(`Unknown BLOK_CONCURRENCY_BACKEND='${kind}'. Expected one of: 'memory' (default), 'nats-kv'.`);
|
|
35
|
+
throw new Error(`Unknown BLOK_CONCURRENCY_BACKEND='${kind}'. Expected one of: 'memory' (default), 'nats-kv', 'redis'.`);
|
|
32
36
|
}
|
|
33
37
|
}
|
|
34
38
|
//# sourceMappingURL=createConcurrencyBackend.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createConcurrencyBackend.js","sourceRoot":"","sources":["../../src/concurrency/createConcurrencyBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"createConcurrencyBackend.js","sourceRoot":"","sources":["../../src/concurrency/createConcurrencyBackend.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AAEpE;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,wBAAwB;IACvC,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC/E,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,YAAY;QAAE,OAAO,IAAI,CAAC;IAErE,QAAQ,IAAI,EAAE,CAAC;QACd,KAAK,SAAS;YACb,OAAO,IAAI,wBAAwB,EAAE,CAAC;QACvC,KAAK,OAAO;YACX,OAAO,IAAI,uBAAuB,EAAE,CAAC;QACtC;YACC,MAAM,IAAI,KAAK,CACd,qCAAqC,IAAI,6DAA6D,CACtG,CAAC;IACJ,CAAC;AACF,CAAC"}
|
package/dist/defineNode.d.ts
CHANGED
|
@@ -45,6 +45,12 @@ export interface FnNodeDefinition<TInput extends z.ZodTypeAny = z.ZodTypeAny, TO
|
|
|
45
45
|
contentType?: string;
|
|
46
46
|
/** Whether this is a flow control node (e.g. if-else) that returns sub-steps to execute */
|
|
47
47
|
flow?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Minimum runtime versions required by this node.
|
|
50
|
+
* Keys are runtime kind identifiers, values are semver constraints.
|
|
51
|
+
* @example { python3: ">=3.11.0", go: ">=1.21.0" }
|
|
52
|
+
*/
|
|
53
|
+
runtimeRequirements?: Partial<Record<string, string>>;
|
|
48
54
|
/**
|
|
49
55
|
* Node execution logic
|
|
50
56
|
* @param ctx - Workflow context
|
|
@@ -65,6 +71,8 @@ export interface FnNodeDefinition<TInput extends z.ZodTypeAny = z.ZodTypeAny, TO
|
|
|
65
71
|
*/
|
|
66
72
|
export declare class FunctionNode<TInput extends z.ZodTypeAny, TOutput extends z.ZodTypeAny> extends BlokService<z.infer<TInput>> {
|
|
67
73
|
private definition;
|
|
74
|
+
/** Minimum runtime versions required by this node (if any) */
|
|
75
|
+
runtimeRequirements?: Partial<Record<string, string>>;
|
|
68
76
|
constructor(definition: FnNodeDefinition<TInput, TOutput>);
|
|
69
77
|
/**
|
|
70
78
|
* Implementation of the abstract handle() method required by BlokService
|