@blokjs/runner 0.6.20 → 0.7.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.d.ts +2 -0
- package/dist/Blok.js +42 -110
- package/dist/Blok.js.map +1 -1
- package/dist/DefaultLogger.d.ts +13 -0
- package/dist/DefaultLogger.js +25 -0
- package/dist/DefaultLogger.js.map +1 -1
- package/dist/RunnerSteps.d.ts +23 -0
- package/dist/RunnerSteps.js +128 -87
- package/dist/RunnerSteps.js.map +1 -1
- package/dist/SubworkflowNode.js +19 -0
- package/dist/SubworkflowNode.js.map +1 -1
- package/dist/TriggerBase.d.ts +12 -0
- package/dist/TriggerBase.js +216 -181
- package/dist/TriggerBase.js.map +1 -1
- package/dist/adapters/grpc/GrpcRuntimeAdapter.d.ts +9 -0
- package/dist/adapters/grpc/GrpcRuntimeAdapter.js +76 -6
- package/dist/adapters/grpc/GrpcRuntimeAdapter.js.map +1 -1
- package/dist/index.d.ts +4 -39
- package/dist/index.js +7 -32
- package/dist/index.js.map +1 -1
- package/dist/monitoring/JanitorMetrics.d.ts +3 -0
- package/dist/monitoring/JanitorMetrics.js +11 -0
- package/dist/monitoring/JanitorMetrics.js.map +1 -1
- package/dist/monitoring/ProcessErrorMetrics.d.ts +32 -0
- package/dist/monitoring/ProcessErrorMetrics.js +43 -0
- package/dist/monitoring/ProcessErrorMetrics.js.map +1 -0
- package/dist/monitoring/PrometheusMetricsBridge.d.ts +7 -0
- package/dist/monitoring/PrometheusMetricsBridge.js +8 -2
- package/dist/monitoring/PrometheusMetricsBridge.js.map +1 -1
- package/dist/monitoring/SubworkflowMetrics.d.ts +25 -0
- package/dist/monitoring/SubworkflowMetrics.js +38 -0
- package/dist/monitoring/SubworkflowMetrics.js.map +1 -0
- package/dist/observability/ErrorSink.d.ts +23 -0
- package/dist/observability/ErrorSink.js +32 -0
- package/dist/observability/ErrorSink.js.map +1 -0
- package/dist/observability/SentryIntegration.d.ts +9 -0
- package/dist/observability/SentryIntegration.js +31 -0
- package/dist/observability/SentryIntegration.js.map +1 -0
- package/dist/scheduling/DebounceCoordinator.d.ts +7 -53
- package/dist/scheduling/DebounceCoordinator.js +8 -207
- package/dist/scheduling/DebounceCoordinator.js.map +1 -1
- package/dist/tracing/InMemoryRunStore.d.ts +5 -1
- package/dist/tracing/InMemoryRunStore.js +14 -0
- package/dist/tracing/InMemoryRunStore.js.map +1 -1
- package/dist/tracing/Janitor.js +3 -0
- package/dist/tracing/Janitor.js.map +1 -1
- package/dist/tracing/PostgresRunStore.d.ts +4 -1
- package/dist/tracing/PostgresRunStore.js +73 -3
- package/dist/tracing/PostgresRunStore.js.map +1 -1
- package/dist/tracing/RunStore.d.ts +17 -1
- package/dist/tracing/RunTracker.d.ts +13 -34
- package/dist/tracing/RunTracker.js +62 -32
- package/dist/tracing/RunTracker.js.map +1 -1
- package/dist/tracing/SqliteRunStore.d.ts +4 -1
- package/dist/tracing/SqliteRunStore.js +60 -0
- package/dist/tracing/SqliteRunStore.js.map +1 -1
- package/dist/tracing/TraceRouter.d.ts +13 -0
- package/dist/tracing/TraceRouter.js +43 -11
- package/dist/tracing/TraceRouter.js.map +1 -1
- package/dist/tracing/TracingLogger.js +22 -0
- package/dist/tracing/TracingLogger.js.map +1 -1
- package/dist/tracing/createStore.js +51 -22
- package/dist/tracing/createStore.js.map +1 -1
- package/dist/tracing/types.d.ts +22 -0
- package/dist/types/GlobalOptions.d.ts +5 -7
- package/dist/workflow/WorkflowNormalizer.js +63 -0
- package/dist/workflow/WorkflowNormalizer.js.map +1 -1
- package/package.json +7 -4
- package/dist/cache/NodeResultCache.d.ts +0 -286
- package/dist/cache/NodeResultCache.js +0 -506
- package/dist/cache/NodeResultCache.js.map +0 -1
- package/dist/cache/index.d.ts +0 -1
- package/dist/cache/index.js +0 -2
- package/dist/cache/index.js.map +0 -1
- package/dist/concurrency/ConcurrencyBackend.d.ts +0 -61
- package/dist/concurrency/ConcurrencyBackend.js +0 -20
- package/dist/concurrency/ConcurrencyBackend.js.map +0 -1
- package/dist/concurrency/NatsKvConcurrencyBackend.d.ts +0 -64
- package/dist/concurrency/NatsKvConcurrencyBackend.js +0 -310
- package/dist/concurrency/NatsKvConcurrencyBackend.js.map +0 -1
- package/dist/concurrency/RedisConcurrencyBackend.d.ts +0 -64
- package/dist/concurrency/RedisConcurrencyBackend.js +0 -374
- package/dist/concurrency/RedisConcurrencyBackend.js.map +0 -1
- package/dist/concurrency/createConcurrencyBackend.d.ts +0 -24
- package/dist/concurrency/createConcurrencyBackend.js +0 -38
- package/dist/concurrency/createConcurrencyBackend.js.map +0 -1
- package/dist/graphql/GraphQLSchemaGenerator.d.ts +0 -129
- package/dist/graphql/GraphQLSchemaGenerator.js +0 -425
- package/dist/graphql/GraphQLSchemaGenerator.js.map +0 -1
- package/dist/integrations/APMIntegration.d.ts +0 -141
- package/dist/integrations/APMIntegration.js +0 -212
- package/dist/integrations/APMIntegration.js.map +0 -1
- package/dist/integrations/AzureMonitorIntegration.d.ts +0 -118
- package/dist/integrations/AzureMonitorIntegration.js +0 -254
- package/dist/integrations/AzureMonitorIntegration.js.map +0 -1
- package/dist/integrations/CloudWatchIntegration.d.ts +0 -135
- package/dist/integrations/CloudWatchIntegration.js +0 -293
- package/dist/integrations/CloudWatchIntegration.js.map +0 -1
- package/dist/integrations/SentryIntegration.d.ts +0 -153
- package/dist/integrations/SentryIntegration.js +0 -200
- package/dist/integrations/SentryIntegration.js.map +0 -1
- package/dist/integrations/index.d.ts +0 -19
- package/dist/integrations/index.js +0 -16
- package/dist/integrations/index.js.map +0 -1
- package/dist/marketplace/RuntimeAutoScaler.d.ts +0 -148
- package/dist/marketplace/RuntimeAutoScaler.js +0 -366
- package/dist/marketplace/RuntimeAutoScaler.js.map +0 -1
- package/dist/marketplace/RuntimeCatalog.d.ts +0 -180
- package/dist/marketplace/RuntimeCatalog.js +0 -339
- package/dist/marketplace/RuntimeCatalog.js.map +0 -1
- package/dist/marketplace/RuntimeDiscovery.d.ts +0 -86
- package/dist/marketplace/RuntimeDiscovery.js +0 -231
- package/dist/marketplace/RuntimeDiscovery.js.map +0 -1
- package/dist/marketplace/RuntimeHealthMonitor.d.ts +0 -100
- package/dist/marketplace/RuntimeHealthMonitor.js +0 -241
- package/dist/marketplace/RuntimeHealthMonitor.js.map +0 -1
- package/dist/marketplace/RuntimeMetricsDashboard.d.ts +0 -113
- package/dist/marketplace/RuntimeMetricsDashboard.js +0 -293
- package/dist/marketplace/RuntimeMetricsDashboard.js.map +0 -1
- package/dist/openapi/OpenAPIGenerator.d.ts +0 -192
- package/dist/openapi/OpenAPIGenerator.js +0 -378
- package/dist/openapi/OpenAPIGenerator.js.map +0 -1
- package/dist/openapi/index.d.ts +0 -20
- package/dist/openapi/index.js +0 -20
- package/dist/openapi/index.js.map +0 -1
- package/dist/scheduling/DebounceBackend.d.ts +0 -108
- package/dist/scheduling/DebounceBackend.js +0 -23
- package/dist/scheduling/DebounceBackend.js.map +0 -1
- package/dist/scheduling/NatsKvDebounceBackend.d.ts +0 -53
- package/dist/scheduling/NatsKvDebounceBackend.js +0 -334
- package/dist/scheduling/NatsKvDebounceBackend.js.map +0 -1
- package/dist/scheduling/RedisDebounceBackend.d.ts +0 -49
- package/dist/scheduling/RedisDebounceBackend.js +0 -356
- package/dist/scheduling/RedisDebounceBackend.js.map +0 -1
- package/dist/scheduling/createDebounceBackend.d.ts +0 -25
- package/dist/scheduling/createDebounceBackend.js +0 -39
- package/dist/scheduling/createDebounceBackend.js.map +0 -1
- package/dist/security/ABAC.d.ts +0 -224
- package/dist/security/ABAC.js +0 -380
- package/dist/security/ABAC.js.map +0 -1
- package/dist/security/AuditLogger.d.ts +0 -242
- package/dist/security/AuditLogger.js +0 -317
- package/dist/security/AuditLogger.js.map +0 -1
- package/dist/security/AuthMiddleware.d.ts +0 -162
- package/dist/security/AuthMiddleware.js +0 -289
- package/dist/security/AuthMiddleware.js.map +0 -1
- package/dist/security/EncryptionAtRest.d.ts +0 -206
- package/dist/security/EncryptionAtRest.js +0 -236
- package/dist/security/EncryptionAtRest.js.map +0 -1
- package/dist/security/OAuthProvider.d.ts +0 -334
- package/dist/security/OAuthProvider.js +0 -719
- package/dist/security/OAuthProvider.js.map +0 -1
- package/dist/security/PIIDetector.d.ts +0 -233
- package/dist/security/PIIDetector.js +0 -354
- package/dist/security/PIIDetector.js.map +0 -1
- package/dist/security/RBAC.d.ts +0 -143
- package/dist/security/RBAC.js +0 -285
- package/dist/security/RBAC.js.map +0 -1
- package/dist/security/SecretManager.d.ts +0 -652
- package/dist/security/SecretManager.js +0 -1147
- package/dist/security/SecretManager.js.map +0 -1
- package/dist/security/TLSConfig.d.ts +0 -305
- package/dist/security/TLSConfig.js +0 -550
- package/dist/security/TLSConfig.js.map +0 -1
- package/dist/security/index.d.ts +0 -81
- package/dist/security/index.js +0 -82
- package/dist/security/index.js.map +0 -1
|
@@ -1,64 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,310 +0,0 @@
|
|
|
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 { ConcurrencyMetrics } from "../monitoring/ConcurrencyMetrics";
|
|
25
|
-
const DEFAULT_BUCKET_NAME = "blok-concurrency";
|
|
26
|
-
const MAX_CAS_RETRIES = 10;
|
|
27
|
-
/**
|
|
28
|
-
* Read configuration from environment variables. Used by
|
|
29
|
-
* {@link createConcurrencyBackend} when the user opts into NATS KV.
|
|
30
|
-
*/
|
|
31
|
-
export function readNatsKvConfigFromEnv() {
|
|
32
|
-
const serversRaw = process.env.BLOK_CONCURRENCY_NATS_SERVERS ?? "nats://localhost:4222";
|
|
33
|
-
const servers = serversRaw
|
|
34
|
-
.split(",")
|
|
35
|
-
.map((s) => s.trim())
|
|
36
|
-
.filter(Boolean);
|
|
37
|
-
return {
|
|
38
|
-
servers,
|
|
39
|
-
token: process.env.BLOK_CONCURRENCY_NATS_TOKEN,
|
|
40
|
-
user: process.env.BLOK_CONCURRENCY_NATS_USER,
|
|
41
|
-
pass: process.env.BLOK_CONCURRENCY_NATS_PASS,
|
|
42
|
-
bucketName: process.env.BLOK_CONCURRENCY_NATS_KV_BUCKET ?? DEFAULT_BUCKET_NAME,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
export class NatsKvConcurrencyBackend {
|
|
46
|
-
name = "nats-kv";
|
|
47
|
-
nc = null;
|
|
48
|
-
kv = null;
|
|
49
|
-
config;
|
|
50
|
-
connected = false;
|
|
51
|
-
constructor(config) {
|
|
52
|
-
const env = readNatsKvConfigFromEnv();
|
|
53
|
-
this.config = {
|
|
54
|
-
servers: config?.servers ?? env.servers,
|
|
55
|
-
token: config?.token ?? env.token,
|
|
56
|
-
user: config?.user ?? env.user,
|
|
57
|
-
pass: config?.pass ?? env.pass,
|
|
58
|
-
bucketName: config?.bucketName ?? env.bucketName,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
async connect() {
|
|
62
|
-
if (this.connected)
|
|
63
|
-
return;
|
|
64
|
-
// Security review FW-5 — refuse to start in production with the
|
|
65
|
-
// default bucket name. Two deployments sharing a NATS server with
|
|
66
|
-
// the default would contend on the same `(workflow, key)` buckets,
|
|
67
|
-
// silently corrupting concurrency state across tenants. The fix
|
|
68
|
-
// is operator-mandatory: set BLOK_CONCURRENCY_NATS_KV_BUCKET
|
|
69
|
-
// per-deployment.
|
|
70
|
-
const blokEnv = process.env.BLOK_ENV;
|
|
71
|
-
const nodeEnv = process.env.NODE_ENV;
|
|
72
|
-
const isProd = blokEnv === "production" || nodeEnv === "production";
|
|
73
|
-
if (isProd && this.config.bucketName === DEFAULT_BUCKET_NAME) {
|
|
74
|
-
throw new Error(`[blok] NATS KV concurrency backend refuses to start in production with the default bucket name ('${DEFAULT_BUCKET_NAME}'). Set BLOK_CONCURRENCY_NATS_KV_BUCKET to a deployment-unique value (e.g. 'blok-concurrency-acme-prod') to prevent cross-deployment collision on a shared NATS server.`);
|
|
75
|
-
}
|
|
76
|
-
let natsModule;
|
|
77
|
-
try {
|
|
78
|
-
natsModule = (await import("nats"));
|
|
79
|
-
}
|
|
80
|
-
catch (err) {
|
|
81
|
-
throw new Error(`NatsKvConcurrencyBackend requires the 'nats' package. Install it: \`bun add nats\` or \`npm install nats\`. Underlying error: ${err instanceof Error ? err.message : String(err)}`);
|
|
82
|
-
}
|
|
83
|
-
const connectOpts = { servers: this.config.servers };
|
|
84
|
-
if (this.config.token)
|
|
85
|
-
connectOpts.token = this.config.token;
|
|
86
|
-
if (this.config.user)
|
|
87
|
-
connectOpts.user = this.config.user;
|
|
88
|
-
if (this.config.pass)
|
|
89
|
-
connectOpts.pass = this.config.pass;
|
|
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);
|
|
97
|
-
this.connected = true;
|
|
98
|
-
}
|
|
99
|
-
async disconnect() {
|
|
100
|
-
if (!this.connected)
|
|
101
|
-
return;
|
|
102
|
-
try {
|
|
103
|
-
await this.nc?.drain();
|
|
104
|
-
}
|
|
105
|
-
finally {
|
|
106
|
-
this.nc = null;
|
|
107
|
-
this.kv = null;
|
|
108
|
-
this.connected = false;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
bucketKey(workflowName, concurrencyKey) {
|
|
112
|
-
// Use `__` (double underscore) — KV keys cannot contain `.` or
|
|
113
|
-
// `>` per NATS subject grammar; `__` is unambiguous and allows
|
|
114
|
-
// arbitrary workflow / key strings.
|
|
115
|
-
return `${this.encodeSegment(workflowName)}__${this.encodeSegment(concurrencyKey)}`;
|
|
116
|
-
}
|
|
117
|
-
encodeSegment(s) {
|
|
118
|
-
// NATS KV keys must match `[-/_=\.a-zA-Z0-9]+`. Replace anything
|
|
119
|
-
// outside the safe set with hex escape `_HHHH_` to keep the
|
|
120
|
-
// roundtrip lossless.
|
|
121
|
-
return s.replace(/[^-_=.a-zA-Z0-9]/g, (ch) => `_${ch.codePointAt(0)?.toString(16)}_`);
|
|
122
|
-
}
|
|
123
|
-
requireKv() {
|
|
124
|
-
if (!this.kv) {
|
|
125
|
-
throw new Error("NatsKvConcurrencyBackend not connected — call connect() first.");
|
|
126
|
-
}
|
|
127
|
-
return this.kv;
|
|
128
|
-
}
|
|
129
|
-
async acquireSlot(workflowName, concurrencyKey, concurrencyLimit, runId, leaseExpiresAt) {
|
|
130
|
-
const kv = this.requireKv();
|
|
131
|
-
const bucketKey = this.bucketKey(workflowName, concurrencyKey);
|
|
132
|
-
// PR 3 D2 — record OCC retry depth + outcome on every exit path.
|
|
133
|
-
const metricAttrs = { workflow_name: workflowName, concurrency_key: concurrencyKey };
|
|
134
|
-
for (let attempt = 0; attempt < MAX_CAS_RETRIES; attempt++) {
|
|
135
|
-
const entry = await this.safeGet(kv, bucketKey);
|
|
136
|
-
// PR 2 A6 — fetch failure (broker unreachable / non-NotFound
|
|
137
|
-
// error). Spinning 10× CAS retries on a connection problem just
|
|
138
|
-
// burns latency. Fail-fast so the trigger sees the issue and
|
|
139
|
-
// can fall back / alert. Existing run continues with no slot;
|
|
140
|
-
// the gate is conservative.
|
|
141
|
-
if (entry === "fetch-failed") {
|
|
142
|
-
console.warn(`[blok][concurrency][nats-kv] acquireSlot fetch-failed for ${workflowName}:${concurrencyKey} (attempt ${attempt + 1}); failing closed`);
|
|
143
|
-
ConcurrencyMetrics.getInstance().recordOccRetries({ ...metricAttrs, outcome: "fail-closed" }, attempt);
|
|
144
|
-
return { acquired: false, currentInFlight: -1 };
|
|
145
|
-
}
|
|
146
|
-
if (!entry) {
|
|
147
|
-
// Bucket doesn't exist — create with first lease.
|
|
148
|
-
const initial = { leases: [{ runId, expiresAt: leaseExpiresAt }] };
|
|
149
|
-
try {
|
|
150
|
-
await kv.create(bucketKey, JSON.stringify(initial));
|
|
151
|
-
ConcurrencyMetrics.getInstance().recordOccRetries({ ...metricAttrs, outcome: "success" }, attempt);
|
|
152
|
-
return { acquired: true, currentInFlight: 1 };
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
155
|
-
// Race — another process created. Retry.
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
// Read current state, lazy-purge expired.
|
|
160
|
-
const current = this.parseBucket(entry);
|
|
161
|
-
const now = Date.now();
|
|
162
|
-
const active = current.leases.filter((l) => l.expiresAt > now);
|
|
163
|
-
// Idempotent re-acquire: refresh lease, don't grow count.
|
|
164
|
-
const existingIdx = active.findIndex((l) => l.runId === runId);
|
|
165
|
-
if (existingIdx >= 0) {
|
|
166
|
-
active[existingIdx] = { runId, expiresAt: leaseExpiresAt };
|
|
167
|
-
try {
|
|
168
|
-
await kv.update(bucketKey, JSON.stringify({ leases: active }), entry.revision);
|
|
169
|
-
ConcurrencyMetrics.getInstance().recordOccRetries({ ...metricAttrs, outcome: "success" }, attempt);
|
|
170
|
-
return { acquired: true, currentInFlight: active.length };
|
|
171
|
-
}
|
|
172
|
-
catch {
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
// Limit check.
|
|
177
|
-
if (active.length >= concurrencyLimit) {
|
|
178
|
-
ConcurrencyMetrics.getInstance().recordOccRetries({ ...metricAttrs, outcome: "denied" }, attempt);
|
|
179
|
-
return { acquired: false, currentInFlight: active.length };
|
|
180
|
-
}
|
|
181
|
-
// Insert + CAS.
|
|
182
|
-
const updated = { leases: [...active, { runId, expiresAt: leaseExpiresAt }] };
|
|
183
|
-
try {
|
|
184
|
-
await kv.update(bucketKey, JSON.stringify(updated), entry.revision);
|
|
185
|
-
ConcurrencyMetrics.getInstance().recordOccRetries({ ...metricAttrs, outcome: "success" }, attempt);
|
|
186
|
-
return { acquired: true, currentInFlight: updated.leases.length };
|
|
187
|
-
}
|
|
188
|
-
catch { }
|
|
189
|
-
}
|
|
190
|
-
// Retry exhausted — fail-closed.
|
|
191
|
-
console.warn(`[blok][concurrency][nats-kv] acquireSlot exhausted ${MAX_CAS_RETRIES} CAS retries for ${workflowName}:${concurrencyKey}; denying slot to runId=${runId}`);
|
|
192
|
-
ConcurrencyMetrics.getInstance().recordOccRetries({ ...metricAttrs, outcome: "fail-closed" }, MAX_CAS_RETRIES);
|
|
193
|
-
return { acquired: false, currentInFlight: -1 };
|
|
194
|
-
}
|
|
195
|
-
async releaseSlot(workflowName, concurrencyKey, runId) {
|
|
196
|
-
const kv = this.requireKv();
|
|
197
|
-
const bucketKey = this.bucketKey(workflowName, concurrencyKey);
|
|
198
|
-
for (let attempt = 0; attempt < MAX_CAS_RETRIES; attempt++) {
|
|
199
|
-
const entry = await this.safeGet(kv, bucketKey);
|
|
200
|
-
// PR 2 A6 — fetch failure on release. Lease will expire via
|
|
201
|
-
// TTL; safe to fail-fast.
|
|
202
|
-
if (entry === "fetch-failed") {
|
|
203
|
-
console.warn(`[blok][concurrency][nats-kv] releaseSlot fetch-failed for ${workflowName}:${concurrencyKey} (attempt ${attempt + 1}); lease for runId=${runId} will expire via TTL`);
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
if (!entry)
|
|
207
|
-
return; // Idempotent — bucket already gone.
|
|
208
|
-
const current = this.parseBucket(entry);
|
|
209
|
-
const next = current.leases.filter((l) => l.runId !== runId);
|
|
210
|
-
// No-op when the runId wasn't holding a slot.
|
|
211
|
-
if (next.length === current.leases.length)
|
|
212
|
-
return;
|
|
213
|
-
if (next.length === 0) {
|
|
214
|
-
try {
|
|
215
|
-
await kv.delete(bucketKey);
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
catch {
|
|
219
|
-
// Another process beat us to delete — fine.
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
try {
|
|
224
|
-
await kv.update(bucketKey, JSON.stringify({ leases: next }), entry.revision);
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
catch { }
|
|
228
|
-
}
|
|
229
|
-
console.warn(`[blok][concurrency][nats-kv] releaseSlot exhausted ${MAX_CAS_RETRIES} CAS retries for ${workflowName}:${concurrencyKey}; lease for runId=${runId} will expire via TTL`);
|
|
230
|
-
}
|
|
231
|
-
async purgeExpired(now) {
|
|
232
|
-
const kv = this.requireKv();
|
|
233
|
-
let purged = 0;
|
|
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) {
|
|
245
|
-
const entry = await this.safeGet(kv, key);
|
|
246
|
-
// Treat both legitimate misses and fetch failures as "skip
|
|
247
|
-
// this bucket" — purge is a best-effort sweep.
|
|
248
|
-
if (!entry || entry === "fetch-failed")
|
|
249
|
-
continue;
|
|
250
|
-
const current = this.parseBucket(entry);
|
|
251
|
-
const active = current.leases.filter((l) => l.expiresAt > now);
|
|
252
|
-
const expired = current.leases.length - active.length;
|
|
253
|
-
if (expired === 0)
|
|
254
|
-
continue;
|
|
255
|
-
if (active.length === 0) {
|
|
256
|
-
try {
|
|
257
|
-
await kv.delete(key);
|
|
258
|
-
purged += expired;
|
|
259
|
-
}
|
|
260
|
-
catch {
|
|
261
|
-
// best-effort
|
|
262
|
-
}
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
try {
|
|
266
|
-
await kv.update(key, JSON.stringify({ leases: active }), entry.revision);
|
|
267
|
-
purged += expired;
|
|
268
|
-
}
|
|
269
|
-
catch {
|
|
270
|
-
// CAS conflict — leave for next sweep.
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
return purged;
|
|
274
|
-
}
|
|
275
|
-
/**
|
|
276
|
-
* PR 2 A6 — distinguishes legitimate "key not found" from "broker
|
|
277
|
-
* unreachable / non-NotFound error". Returns:
|
|
278
|
-
* - `NatsKvEntry` on a successful fetch.
|
|
279
|
-
* - `null` when the key doesn't exist (NotFound code or null entry).
|
|
280
|
-
* - `"fetch-failed"` for any other error (transient broker outage,
|
|
281
|
-
* auth failure, network blip, etc.) so the OCC loop can fail-fast
|
|
282
|
-
* instead of spinning 10× before fail-closing.
|
|
283
|
-
*/
|
|
284
|
-
async safeGet(kv, key) {
|
|
285
|
-
try {
|
|
286
|
-
const e = await kv.get(key);
|
|
287
|
-
return e ?? null;
|
|
288
|
-
}
|
|
289
|
-
catch (err) {
|
|
290
|
-
// NATS surfaces "not found" via a code. Different `nats`
|
|
291
|
-
// package versions use different shapes; cover the common ones.
|
|
292
|
-
const code = err.code;
|
|
293
|
-
if (code === "NotFound" || code === "404")
|
|
294
|
-
return null;
|
|
295
|
-
return "fetch-failed";
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
parseBucket(entry) {
|
|
299
|
-
try {
|
|
300
|
-
const parsed = JSON.parse(entry.string());
|
|
301
|
-
if (!parsed || !Array.isArray(parsed.leases))
|
|
302
|
-
return { leases: [] };
|
|
303
|
-
return parsed;
|
|
304
|
-
}
|
|
305
|
-
catch {
|
|
306
|
-
return { leases: [] };
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
//# sourceMappingURL=NatsKvConcurrencyBackend.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"NatsKvConcurrencyBackend.js","sourceRoot":"","sources":["../../src/concurrency/NatsKvConcurrencyBackend.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,64 +0,0 @@
|
|
|
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
|
-
}
|