@adonis-agora/durable 0.1.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/LICENSE +21 -0
- package/README.md +29 -0
- package/dist/assets/dashboard.html +446 -0
- package/dist/commands/main.d.ts +15 -0
- package/dist/commands/main.d.ts.map +1 -0
- package/dist/commands/main.js +15 -0
- package/dist/commands/main.js.map +1 -0
- package/dist/commands/retry.d.ts +16 -0
- package/dist/commands/retry.d.ts.map +1 -0
- package/dist/commands/retry.js +34 -0
- package/dist/commands/retry.js.map +1 -0
- package/dist/commands/runs.d.ts +17 -0
- package/dist/commands/runs.d.ts.map +1 -0
- package/dist/commands/runs.js +40 -0
- package/dist/commands/runs.js.map +1 -0
- package/dist/commands/work.d.ts +18 -0
- package/dist/commands/work.d.ts.map +1 -0
- package/dist/commands/work.js +58 -0
- package/dist/commands/work.js.map +1 -0
- package/dist/configure.d.ts +15 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +26 -0
- package/dist/configure.js.map +1 -0
- package/dist/providers/dashboard_provider.d.ts +27 -0
- package/dist/providers/dashboard_provider.d.ts.map +1 -0
- package/dist/providers/dashboard_provider.js +94 -0
- package/dist/providers/dashboard_provider.js.map +1 -0
- package/dist/providers/durable_provider.d.ts +36 -0
- package/dist/providers/durable_provider.d.ts.map +1 -0
- package/dist/providers/durable_provider.js +113 -0
- package/dist/providers/durable_provider.js.map +1 -0
- package/dist/src/admission-redis/index.d.ts +2 -0
- package/dist/src/admission-redis/index.d.ts.map +1 -0
- package/dist/src/admission-redis/index.js +2 -0
- package/dist/src/admission-redis/index.js.map +1 -0
- package/dist/src/admission-redis/redis-admission-backend.d.ts +83 -0
- package/dist/src/admission-redis/redis-admission-backend.d.ts.map +1 -0
- package/dist/src/admission-redis/redis-admission-backend.js +200 -0
- package/dist/src/admission-redis/redis-admission-backend.js.map +1 -0
- package/dist/src/admission.d.ts +49 -0
- package/dist/src/admission.d.ts.map +1 -0
- package/dist/src/admission.js +30 -0
- package/dist/src/admission.js.map +1 -0
- package/dist/src/backoff.d.ts +5 -0
- package/dist/src/backoff.d.ts.map +1 -0
- package/dist/src/backoff.js +11 -0
- package/dist/src/backoff.js.map +1 -0
- package/dist/src/checkpoints.d.ts +42 -0
- package/dist/src/checkpoints.d.ts.map +1 -0
- package/dist/src/checkpoints.js +57 -0
- package/dist/src/checkpoints.js.map +1 -0
- package/dist/src/codec-state-store.d.ts +60 -0
- package/dist/src/codec-state-store.d.ts.map +1 -0
- package/dist/src/codec-state-store.js +129 -0
- package/dist/src/codec-state-store.js.map +1 -0
- package/dist/src/commands/index.d.ts +3 -0
- package/dist/src/commands/index.d.ts.map +1 -0
- package/dist/src/commands/index.js +3 -0
- package/dist/src/commands/index.js.map +1 -0
- package/dist/src/commands/runs.d.ts +24 -0
- package/dist/src/commands/runs.d.ts.map +1 -0
- package/dist/src/commands/runs.js +39 -0
- package/dist/src/commands/runs.js.map +1 -0
- package/dist/src/commands/worker.d.ts +61 -0
- package/dist/src/commands/worker.d.ts.map +1 -0
- package/dist/src/commands/worker.js +74 -0
- package/dist/src/commands/worker.js.map +1 -0
- package/dist/src/completion.d.ts +15 -0
- package/dist/src/completion.d.ts.map +1 -0
- package/dist/src/completion.js +10 -0
- package/dist/src/completion.js.map +1 -0
- package/dist/src/dashboard/define_config.d.ts +48 -0
- package/dist/src/dashboard/define_config.d.ts.map +1 -0
- package/dist/src/dashboard/define_config.js +57 -0
- package/dist/src/dashboard/define_config.js.map +1 -0
- package/dist/src/dashboard/handlers.d.ts +47 -0
- package/dist/src/dashboard/handlers.d.ts.map +1 -0
- package/dist/src/dashboard/handlers.js +163 -0
- package/dist/src/dashboard/handlers.js.map +1 -0
- package/dist/src/dashboard/html.d.ts +7 -0
- package/dist/src/dashboard/html.d.ts.map +1 -0
- package/dist/src/dashboard/html.js +24 -0
- package/dist/src/dashboard/html.js.map +1 -0
- package/dist/src/dashboard/index.d.ts +8 -0
- package/dist/src/dashboard/index.d.ts.map +1 -0
- package/dist/src/dashboard/index.js +6 -0
- package/dist/src/dashboard/index.js.map +1 -0
- package/dist/src/define_config.d.ts +72 -0
- package/dist/src/define_config.d.ts.map +1 -0
- package/dist/src/define_config.js +8 -0
- package/dist/src/define_config.js.map +1 -0
- package/dist/src/diagnostics-bridge.d.ts +22 -0
- package/dist/src/diagnostics-bridge.d.ts.map +1 -0
- package/dist/src/diagnostics-bridge.js +25 -0
- package/dist/src/diagnostics-bridge.js.map +1 -0
- package/dist/src/duration.d.ts +6 -0
- package/dist/src/duration.d.ts.map +1 -0
- package/dist/src/duration.js +39 -0
- package/dist/src/duration.js.map +1 -0
- package/dist/src/engine.d.ts +583 -0
- package/dist/src/engine.d.ts.map +1 -0
- package/dist/src/engine.js +1894 -0
- package/dist/src/engine.js.map +1 -0
- package/dist/src/entities.d.ts +44 -0
- package/dist/src/entities.d.ts.map +1 -0
- package/dist/src/entities.js +64 -0
- package/dist/src/entities.js.map +1 -0
- package/dist/src/errors.d.ts +68 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.js +102 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/event-accumulators.d.ts +39 -0
- package/dist/src/event-accumulators.d.ts.map +1 -0
- package/dist/src/event-accumulators.js +70 -0
- package/dist/src/event-accumulators.js.map +1 -0
- package/dist/src/events.d.ts +9 -0
- package/dist/src/events.d.ts.map +1 -0
- package/dist/src/events.js +35 -0
- package/dist/src/events.js.map +1 -0
- package/dist/src/index.d.ts +36 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +37 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/interfaces.d.ts +909 -0
- package/dist/src/interfaces.d.ts.map +1 -0
- package/dist/src/interfaces.js +2 -0
- package/dist/src/interfaces.js.map +1 -0
- package/dist/src/metrics.d.ts +44 -0
- package/dist/src/metrics.d.ts.map +1 -0
- package/dist/src/metrics.js +78 -0
- package/dist/src/metrics.js.map +1 -0
- package/dist/src/otel/durable-metrics.d.ts +42 -0
- package/dist/src/otel/durable-metrics.d.ts.map +1 -0
- package/dist/src/otel/durable-metrics.js +75 -0
- package/dist/src/otel/durable-metrics.js.map +1 -0
- package/dist/src/otel/durable-otel.d.ts +20 -0
- package/dist/src/otel/durable-otel.d.ts.map +1 -0
- package/dist/src/otel/durable-otel.js +74 -0
- package/dist/src/otel/durable-otel.js.map +1 -0
- package/dist/src/otel/index.d.ts +4 -0
- package/dist/src/otel/index.d.ts.map +1 -0
- package/dist/src/otel/index.js +4 -0
- package/dist/src/otel/index.js.map +1 -0
- package/dist/src/otel/traceparent.d.ts +17 -0
- package/dist/src/otel/traceparent.d.ts.map +1 -0
- package/dist/src/otel/traceparent.js +21 -0
- package/dist/src/otel/traceparent.js.map +1 -0
- package/dist/src/pollers.d.ts +33 -0
- package/dist/src/pollers.d.ts.map +1 -0
- package/dist/src/pollers.js +76 -0
- package/dist/src/pollers.js.map +1 -0
- package/dist/src/protocol.d.ts +17 -0
- package/dist/src/protocol.d.ts.map +1 -0
- package/dist/src/protocol.js +47 -0
- package/dist/src/protocol.js.map +1 -0
- package/dist/src/queue.d.ts +113 -0
- package/dist/src/queue.d.ts.map +1 -0
- package/dist/src/queue.js +128 -0
- package/dist/src/queue.js.map +1 -0
- package/dist/src/remote-step-factory.d.ts +15 -0
- package/dist/src/remote-step-factory.d.ts.map +1 -0
- package/dist/src/remote-step-factory.js +12 -0
- package/dist/src/remote-step-factory.js.map +1 -0
- package/dist/src/remote-workflow-executor.d.ts +24 -0
- package/dist/src/remote-workflow-executor.d.ts.map +1 -0
- package/dist/src/remote-workflow-executor.js +70 -0
- package/dist/src/remote-workflow-executor.js.map +1 -0
- package/dist/src/scheduler.d.ts +67 -0
- package/dist/src/scheduler.d.ts.map +1 -0
- package/dist/src/scheduler.js +111 -0
- package/dist/src/scheduler.js.map +1 -0
- package/dist/src/search-attributes.d.ts +48 -0
- package/dist/src/search-attributes.d.ts.map +1 -0
- package/dist/src/search-attributes.js +98 -0
- package/dist/src/search-attributes.js.map +1 -0
- package/dist/src/singleton-gate.d.ts +44 -0
- package/dist/src/singleton-gate.d.ts.map +1 -0
- package/dist/src/singleton-gate.js +90 -0
- package/dist/src/singleton-gate.js.map +1 -0
- package/dist/src/step-logger.d.ts +9 -0
- package/dist/src/step-logger.d.ts.map +1 -0
- package/dist/src/step-logger.js +89 -0
- package/dist/src/step-logger.js.map +1 -0
- package/dist/src/stores/factory.d.ts +42 -0
- package/dist/src/stores/factory.d.ts.map +1 -0
- package/dist/src/stores/factory.js +29 -0
- package/dist/src/stores/factory.js.map +1 -0
- package/dist/src/stores/lucid-helpers.d.ts +11 -0
- package/dist/src/stores/lucid-helpers.d.ts.map +1 -0
- package/dist/src/stores/lucid-helpers.js +38 -0
- package/dist/src/stores/lucid-helpers.js.map +1 -0
- package/dist/src/stores/lucid-mappers.d.ts +58 -0
- package/dist/src/stores/lucid-mappers.d.ts.map +1 -0
- package/dist/src/stores/lucid-mappers.js +174 -0
- package/dist/src/stores/lucid-mappers.js.map +1 -0
- package/dist/src/stores/lucid-schema.d.ts +28 -0
- package/dist/src/stores/lucid-schema.d.ts.map +1 -0
- package/dist/src/stores/lucid-schema.js +115 -0
- package/dist/src/stores/lucid-schema.js.map +1 -0
- package/dist/src/stores/lucid.d.ts +69 -0
- package/dist/src/stores/lucid.d.ts.map +1 -0
- package/dist/src/stores/lucid.js +342 -0
- package/dist/src/stores/lucid.js.map +1 -0
- package/dist/src/telescope/dashboard.d.ts +15 -0
- package/dist/src/telescope/dashboard.d.ts.map +1 -0
- package/dist/src/telescope/dashboard.js +113 -0
- package/dist/src/telescope/dashboard.js.map +1 -0
- package/dist/src/telescope/data-providers.d.ts +20 -0
- package/dist/src/telescope/data-providers.d.ts.map +1 -0
- package/dist/src/telescope/data-providers.js +322 -0
- package/dist/src/telescope/data-providers.js.map +1 -0
- package/dist/src/telescope/extension.d.ts +22 -0
- package/dist/src/telescope/extension.d.ts.map +1 -0
- package/dist/src/telescope/extension.js +38 -0
- package/dist/src/telescope/extension.js.map +1 -0
- package/dist/src/telescope/index.d.ts +5 -0
- package/dist/src/telescope/index.d.ts.map +1 -0
- package/dist/src/telescope/index.js +4 -0
- package/dist/src/telescope/index.js.map +1 -0
- package/dist/src/telescope/telescope-sdk.d.ts +123 -0
- package/dist/src/telescope/telescope-sdk.d.ts.map +1 -0
- package/dist/src/telescope/telescope-sdk.js +12 -0
- package/dist/src/telescope/telescope-sdk.js.map +1 -0
- package/dist/src/test-helpers.d.ts +11 -0
- package/dist/src/test-helpers.d.ts.map +1 -0
- package/dist/src/test-helpers.js +12 -0
- package/dist/src/test-helpers.js.map +1 -0
- package/dist/src/testing/in-memory-state-store.d.ts +64 -0
- package/dist/src/testing/in-memory-state-store.d.ts.map +1 -0
- package/dist/src/testing/in-memory-state-store.js +259 -0
- package/dist/src/testing/in-memory-state-store.js.map +1 -0
- package/dist/src/testing/in-memory-transport.d.ts +25 -0
- package/dist/src/testing/in-memory-transport.d.ts.map +1 -0
- package/dist/src/testing/in-memory-transport.js +49 -0
- package/dist/src/testing/in-memory-transport.js.map +1 -0
- package/dist/src/testing-kit/admission-backend-conformance.d.ts +18 -0
- package/dist/src/testing-kit/admission-backend-conformance.d.ts.map +1 -0
- package/dist/src/testing-kit/admission-backend-conformance.js +81 -0
- package/dist/src/testing-kit/admission-backend-conformance.js.map +1 -0
- package/dist/src/testing-kit/assertions.d.ts +10 -0
- package/dist/src/testing-kit/assertions.d.ts.map +1 -0
- package/dist/src/testing-kit/assertions.js +41 -0
- package/dist/src/testing-kit/assertions.js.map +1 -0
- package/dist/src/testing-kit/harness.d.ts +28 -0
- package/dist/src/testing-kit/harness.d.ts.map +1 -0
- package/dist/src/testing-kit/harness.js +42 -0
- package/dist/src/testing-kit/harness.js.map +1 -0
- package/dist/src/testing-kit/index.d.ts +8 -0
- package/dist/src/testing-kit/index.d.ts.map +1 -0
- package/dist/src/testing-kit/index.js +8 -0
- package/dist/src/testing-kit/index.js.map +1 -0
- package/dist/src/testing-kit/replay.d.ts +22 -0
- package/dist/src/testing-kit/replay.d.ts.map +1 -0
- package/dist/src/testing-kit/replay.js +38 -0
- package/dist/src/testing-kit/replay.js.map +1 -0
- package/dist/src/testing-kit/state-store-conformance.d.ts +51 -0
- package/dist/src/testing-kit/state-store-conformance.d.ts.map +1 -0
- package/dist/src/testing-kit/state-store-conformance.js +430 -0
- package/dist/src/testing-kit/state-store-conformance.js.map +1 -0
- package/dist/src/testing-kit/steps.d.ts +5 -0
- package/dist/src/testing-kit/steps.d.ts.map +1 -0
- package/dist/src/testing-kit/steps.js +15 -0
- package/dist/src/testing-kit/steps.js.map +1 -0
- package/dist/src/testing-kit/transport-conformance.d.ts +16 -0
- package/dist/src/testing-kit/transport-conformance.d.ts.map +1 -0
- package/dist/src/testing-kit/transport-conformance.js +64 -0
- package/dist/src/testing-kit/transport-conformance.js.map +1 -0
- package/dist/src/tokens.d.ts +18 -0
- package/dist/src/tokens.d.ts.map +1 -0
- package/dist/src/tokens.js +18 -0
- package/dist/src/tokens.js.map +1 -0
- package/dist/src/transport-pool.d.ts +30 -0
- package/dist/src/transport-pool.d.ts.map +1 -0
- package/dist/src/transport-pool.js +82 -0
- package/dist/src/transport-pool.js.map +1 -0
- package/dist/src/transports/db-helpers.d.ts +10 -0
- package/dist/src/transports/db-helpers.d.ts.map +1 -0
- package/dist/src/transports/db-helpers.js +33 -0
- package/dist/src/transports/db-helpers.js.map +1 -0
- package/dist/src/transports/db-schema.d.ts +28 -0
- package/dist/src/transports/db-schema.d.ts.map +1 -0
- package/dist/src/transports/db-schema.js +98 -0
- package/dist/src/transports/db-schema.js.map +1 -0
- package/dist/src/transports/db.d.ts +81 -0
- package/dist/src/transports/db.d.ts.map +1 -0
- package/dist/src/transports/db.js +340 -0
- package/dist/src/transports/db.js.map +1 -0
- package/dist/src/transports/factory.d.ts +79 -0
- package/dist/src/transports/factory.d.ts.map +1 -0
- package/dist/src/transports/factory.js +57 -0
- package/dist/src/transports/factory.js.map +1 -0
- package/dist/src/transports/queue-mock-adapter.d.ts +39 -0
- package/dist/src/transports/queue-mock-adapter.d.ts.map +1 -0
- package/dist/src/transports/queue-mock-adapter.js +86 -0
- package/dist/src/transports/queue-mock-adapter.js.map +1 -0
- package/dist/src/transports/queue.d.ts +84 -0
- package/dist/src/transports/queue.d.ts.map +1 -0
- package/dist/src/transports/queue.js +210 -0
- package/dist/src/transports/queue.js.map +1 -0
- package/dist/src/workflow-ctx.d.ts +64 -0
- package/dist/src/workflow-ctx.d.ts.map +1 -0
- package/dist/src/workflow-ctx.js +457 -0
- package/dist/src/workflow-ctx.js.map +1 -0
- package/dist/src/workflow-ref.d.ts +34 -0
- package/dist/src/workflow-ref.d.ts.map +1 -0
- package/dist/src/workflow-ref.js +28 -0
- package/dist/src/workflow-ref.js.map +1 -0
- package/dist/stubs/config/durable.stub +49 -0
- package/dist/stubs/config/durable_dashboard.stub +17 -0
- package/dist/stubs/database/migrations/create_durable_tables.stub +87 -0
- package/dist/stubs/database/migrations/create_durable_transport_tables.stub +75 -0
- package/dist/stubs/main.d.ts +3 -0
- package/dist/stubs/main.d.ts.map +1 -0
- package/dist/stubs/main.js +4 -0
- package/dist/stubs/main.js.map +1 -0
- package/package.json +154 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
// One atomic admit. Reclaims dead-instance slots, prunes stale waiters, enforces the rate window and
|
|
3
|
+
// the concurrency cap, then selects the rightful next waiter by (priority desc, fairness round-robin,
|
|
4
|
+
// arrival FIFO/LIFO) before taking a slot — all server-side so concurrent pods can't race past the cap.
|
|
5
|
+
// Returns {admitted (1|0), retryAt}.
|
|
6
|
+
// KEYS 1 slots(hash slotId→instanceId) 2 waiters(zset waiterId→-priority) 3 wmeta(hash waiterId→"seq|key")
|
|
7
|
+
// 4 wexpire(zset waiterId→expiry) 5 keyserved(hash key→tick) 6 rate(string)
|
|
8
|
+
// 7 seq(string) 8 servedSeq(string)
|
|
9
|
+
const ACQUIRE_LUA = `
|
|
10
|
+
local p=cjson.decode(ARGV[1])
|
|
11
|
+
local now=p.now; local instanceId=p.instanceId; local concurrency=p.concurrency
|
|
12
|
+
local rateLimit=p.rateLimit; local ratePeriodMs=p.ratePeriodMs; local waiterId=p.waiterId
|
|
13
|
+
local priority=p.priority; local retryAt=p.retryAt; local hasOrdering=p.hasOrdering
|
|
14
|
+
local fairnessOn=p.fairnessOn; local fairKey=p.fairKey; local instPrefix=p.instPrefix
|
|
15
|
+
local waiterTtl=p.waiterTtl; local instTtl=p.instTtl; local lifo=p.lifo
|
|
16
|
+
|
|
17
|
+
-- refresh own liveness atomically so this pod can never reclaim its own fresh slot
|
|
18
|
+
redis.call('SET', instPrefix..instanceId, '1', 'PX', instTtl)
|
|
19
|
+
|
|
20
|
+
-- reclaim slots whose owning instance is gone; count the live ones
|
|
21
|
+
local slotIds=redis.call('HKEYS', KEYS[1]); local inflight=0
|
|
22
|
+
for i=1,#slotIds do
|
|
23
|
+
local owner=redis.call('HGET', KEYS[1], slotIds[i])
|
|
24
|
+
if owner and redis.call('EXISTS', instPrefix..owner)==1 then inflight=inflight+1
|
|
25
|
+
else redis.call('HDEL', KEYS[1], slotIds[i]) end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
-- prune waiters that stopped retrying (cancelled/abandoned)
|
|
29
|
+
local stale=redis.call('ZRANGEBYSCORE', KEYS[4], '-inf', now)
|
|
30
|
+
for i=1,#stale do
|
|
31
|
+
redis.call('ZREM', KEYS[2], stale[i]); redis.call('HDEL', KEYS[3], stale[i]); redis.call('ZREM', KEYS[4], stale[i])
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
if rateLimit>0 then
|
|
35
|
+
local used=tonumber(redis.call('GET', KEYS[6]) or '0')
|
|
36
|
+
if used>=rateLimit then return {0, retryAt} end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
local function register()
|
|
40
|
+
if redis.call('ZSCORE', KEYS[2], waiterId)==false then
|
|
41
|
+
local seq=redis.call('INCR', KEYS[7])
|
|
42
|
+
redis.call('HSET', KEYS[3], waiterId, seq..'|'..fairKey)
|
|
43
|
+
end
|
|
44
|
+
redis.call('ZADD', KEYS[2], -priority, waiterId)
|
|
45
|
+
redis.call('ZADD', KEYS[4], now+waiterTtl, waiterId)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
if concurrency>0 and inflight>=concurrency then
|
|
49
|
+
if hasOrdering==1 then register() end
|
|
50
|
+
return {0, retryAt}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if hasOrdering==1 then
|
|
54
|
+
register()
|
|
55
|
+
local firstWS=redis.call('ZRANGE', KEYS[2], 0, 0, 'WITHSCORES')
|
|
56
|
+
local topScore=firstWS[2]
|
|
57
|
+
local tier=redis.call('ZRANGEBYSCORE', KEYS[2], topScore, topScore)
|
|
58
|
+
local bestId=nil; local bestServed=nil; local bestSeq=nil
|
|
59
|
+
for i=1,#tier do
|
|
60
|
+
local wid=tier[i]; local meta=redis.call('HGET', KEYS[3], wid)
|
|
61
|
+
if meta then
|
|
62
|
+
local sep=string.find(meta, '|', 1, true)
|
|
63
|
+
local seq=tonumber(string.sub(meta, 1, sep-1))
|
|
64
|
+
local k=string.sub(meta, sep+1)
|
|
65
|
+
local served=0
|
|
66
|
+
if fairnessOn==1 and k~='' then served=tonumber(redis.call('HGET', KEYS[5], k) or '0') end
|
|
67
|
+
local better=false
|
|
68
|
+
if bestId==nil then better=true
|
|
69
|
+
elseif served<bestServed then better=true
|
|
70
|
+
elseif served==bestServed then
|
|
71
|
+
if lifo==1 then better=(seq>bestSeq) else better=(seq<bestSeq) end
|
|
72
|
+
end
|
|
73
|
+
if better then bestId=wid; bestServed=served; bestSeq=seq end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
if bestId~=waiterId then return {0, retryAt} end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
-- admit
|
|
80
|
+
redis.call('HSET', KEYS[1], waiterId, instanceId)
|
|
81
|
+
redis.call('ZREM', KEYS[2], waiterId); redis.call('HDEL', KEYS[3], waiterId); redis.call('ZREM', KEYS[4], waiterId)
|
|
82
|
+
if fairnessOn==1 and fairKey~='' then
|
|
83
|
+
local tick=redis.call('INCR', KEYS[8]); redis.call('HSET', KEYS[5], fairKey, tick)
|
|
84
|
+
end
|
|
85
|
+
if rateLimit>0 then redis.call('INCR', KEYS[6]); redis.call('PEXPIRE', KEYS[6], ratePeriodMs) end
|
|
86
|
+
return {1, 0}
|
|
87
|
+
`;
|
|
88
|
+
/**
|
|
89
|
+
* An {@link AdmissionBackend} whose state lives in Redis, so `concurrency` / `rateLimit` / priority +
|
|
90
|
+
* fairness ordering are enforced ACROSS every engine replica instead of per-process.
|
|
91
|
+
*
|
|
92
|
+
* - **Concurrency** is a hash of slot→owning-instance; a slot is reclaimed only when its owner's
|
|
93
|
+
* liveness heartbeat lapses, so a live pod holds its slot for the full step duration (no time-lease
|
|
94
|
+
* false purge) while a crashed pod's slots free within `instanceTtlMs`.
|
|
95
|
+
* - **Rate limit** is a fixed-window counter.
|
|
96
|
+
* - **Ordering** registers blocked callers and, when a slot frees, admits the rightful next under
|
|
97
|
+
* (priority desc → fairness round-robin by `key` → arrival FIFO/LIFO). Abandoned waiters are pruned
|
|
98
|
+
* so a cancelled run can't sit as a phantom best-waiter.
|
|
99
|
+
*
|
|
100
|
+
* Targets a single (non-cluster) Redis — the atomic Lua spans multiple keys.
|
|
101
|
+
*/
|
|
102
|
+
export class RedisAdmissionBackend {
|
|
103
|
+
redis;
|
|
104
|
+
prefix;
|
|
105
|
+
instanceId;
|
|
106
|
+
instanceTtlMs;
|
|
107
|
+
waiterTtlMs;
|
|
108
|
+
retryMs;
|
|
109
|
+
clock;
|
|
110
|
+
configs = new Map();
|
|
111
|
+
heartbeat;
|
|
112
|
+
subscriber;
|
|
113
|
+
constructor(options) {
|
|
114
|
+
this.redis = options.connection;
|
|
115
|
+
this.prefix = options.prefix ?? 'durable';
|
|
116
|
+
this.instanceId = options.instanceId ?? randomUUID();
|
|
117
|
+
this.instanceTtlMs = options.instanceTtlMs ?? 30_000;
|
|
118
|
+
this.retryMs = options.retryMs ?? 1000;
|
|
119
|
+
this.waiterTtlMs = options.waiterTtlMs ?? this.retryMs * 3;
|
|
120
|
+
this.clock = options.clock ?? Date.now;
|
|
121
|
+
this.redis.defineCommand('admissionAcquire', { numberOfKeys: 8, lua: ACQUIRE_LUA });
|
|
122
|
+
// Keep this instance's liveness fresh even while idle, so slots it holds aren't reclaimed.
|
|
123
|
+
void this.refreshLiveness();
|
|
124
|
+
this.heartbeat = setInterval(() => void this.refreshLiveness().catch(() => undefined), Math.max(1000, Math.floor(this.instanceTtlMs / 3)));
|
|
125
|
+
this.heartbeat.unref?.();
|
|
126
|
+
}
|
|
127
|
+
register(config) {
|
|
128
|
+
this.configs.set(config.name, config);
|
|
129
|
+
}
|
|
130
|
+
handles(queue) {
|
|
131
|
+
return this.configs.has(queue);
|
|
132
|
+
}
|
|
133
|
+
async tryAdmit(queue, item) {
|
|
134
|
+
const config = this.configs.get(queue);
|
|
135
|
+
if (!config)
|
|
136
|
+
return { ok: true }; // unregistered queue → ungated
|
|
137
|
+
const now = this.clock();
|
|
138
|
+
const rate = config.rateLimit;
|
|
139
|
+
const fairnessOn = config.fairness === 'key' ? 1 : 0;
|
|
140
|
+
const lifo = config.order === 'lifo' ? 1 : 0;
|
|
141
|
+
const fairKey = fairnessOn === 1 ? (item.key ?? '') : '';
|
|
142
|
+
// Order-track every call that carries a stable waiterId (the engine always does), so even a plain
|
|
143
|
+
// concurrency queue admits blocked waiters by arrival FIFO — identical to the in-process backend.
|
|
144
|
+
const hasOrdering = item.waiterId != null || item.priority != null || fairnessOn === 1 || lifo === 1 ? 1 : 0;
|
|
145
|
+
const rateKey = rate
|
|
146
|
+
? this.key(queue, 'rate', Math.floor(now / rate.periodMs))
|
|
147
|
+
: this.key(queue, 'rate', 0);
|
|
148
|
+
const params = {
|
|
149
|
+
now,
|
|
150
|
+
instanceId: this.instanceId,
|
|
151
|
+
concurrency: config.concurrency ?? 0,
|
|
152
|
+
rateLimit: rate?.limit ?? 0,
|
|
153
|
+
ratePeriodMs: rate?.periodMs ?? 0,
|
|
154
|
+
waiterId: item.waiterId ?? `anon:${now}`,
|
|
155
|
+
priority: item.priority ?? 0,
|
|
156
|
+
retryAt: now + this.retryMs,
|
|
157
|
+
hasOrdering,
|
|
158
|
+
fairnessOn,
|
|
159
|
+
fairKey,
|
|
160
|
+
instPrefix: this.instPrefix(),
|
|
161
|
+
waiterTtl: this.waiterTtlMs,
|
|
162
|
+
instTtl: this.instanceTtlMs,
|
|
163
|
+
lifo,
|
|
164
|
+
};
|
|
165
|
+
const [admitted, retryAt] = await this.redis.admissionAcquire(this.key(queue, 'slots'), this.key(queue, 'waiters'), this.key(queue, 'wmeta'), this.key(queue, 'wexpire'), this.key(queue, 'keyserved'), rateKey, this.key(queue, 'seq'), this.key(queue, 'servedseq'), JSON.stringify(params));
|
|
166
|
+
return admitted === 1 ? { ok: true } : { ok: false, retryAt };
|
|
167
|
+
}
|
|
168
|
+
async release(queue, slotId) {
|
|
169
|
+
await this.redis.hdel(this.key(queue, 'slots'), slotId);
|
|
170
|
+
// Tell the fleet a slot freed so blocked waiters can re-contend now (best-effort; the retry tick
|
|
171
|
+
// is the guarantee). One channel, queue name as payload.
|
|
172
|
+
await this.redis.publish(this.freedChannel(), queue);
|
|
173
|
+
}
|
|
174
|
+
onFreed(handler) {
|
|
175
|
+
// A subscriber connection can't run normal commands, so use a dedicated dup of the connection.
|
|
176
|
+
const sub = this.redis.duplicate();
|
|
177
|
+
this.subscriber = sub;
|
|
178
|
+
void sub.subscribe(this.freedChannel());
|
|
179
|
+
sub.on('message', (_channel, queue) => handler(queue));
|
|
180
|
+
}
|
|
181
|
+
/** Stop the liveness heartbeat + subscriber (held slots lapse within `instanceTtlMs`). */
|
|
182
|
+
async close() {
|
|
183
|
+
clearInterval(this.heartbeat);
|
|
184
|
+
this.subscriber?.disconnect();
|
|
185
|
+
}
|
|
186
|
+
freedChannel() {
|
|
187
|
+
return `${this.prefix}:adm:freed`;
|
|
188
|
+
}
|
|
189
|
+
async refreshLiveness() {
|
|
190
|
+
await this.redis.set(`${this.instPrefix()}${this.instanceId}`, '1', 'PX', this.instanceTtlMs);
|
|
191
|
+
}
|
|
192
|
+
instPrefix() {
|
|
193
|
+
return `${this.prefix}:adm:inst:`;
|
|
194
|
+
}
|
|
195
|
+
key(queue, part, window) {
|
|
196
|
+
const base = `${this.prefix}:adm:${queue}:${part}`;
|
|
197
|
+
return window === undefined ? base : `${base}:${window}`;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=redis-admission-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-admission-backend.js","sourceRoot":"","sources":["../../../src/admission-redis/redis-admission-backend.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type Admission, type AdmissionItem, type QueueConfig } from './queue.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pluggable backend for the remote-step flow-control gate (`ctx.call(step, input, { queue })`). The
|
|
4
|
+
* engine asks it whether a queued call may be admitted now (concurrency / rate / ordering) and tells
|
|
5
|
+
* it when an admitted slot is released.
|
|
6
|
+
*
|
|
7
|
+
* The default {@link InMemoryAdmissionBackend} keeps per-instance counts — correct for a single
|
|
8
|
+
* orchestrator. Swap in a store/Redis-backed backend to make the caps and ordering GLOBAL across
|
|
9
|
+
* engine replicas (so `concurrency: 5` means 5 in-flight across the whole fleet, not 5 per pod).
|
|
10
|
+
*
|
|
11
|
+
* `tryAdmit`/`release` are async so a global backend can do its atomic round-trip; the in-memory one
|
|
12
|
+
* resolves synchronously. An UNREGISTERED queue name is treated as ungated (admits immediately) — the
|
|
13
|
+
* engine only tracks/releases a slot for a registered queue.
|
|
14
|
+
*/
|
|
15
|
+
export interface AdmissionBackend {
|
|
16
|
+
/** Register (or replace) a queue's config. Called from `engine.registerQueue`. */
|
|
17
|
+
register(config: QueueConfig): void;
|
|
18
|
+
/** Whether `queue` is registered (gated). The engine only tracks/releases a slot for a gated queue. */
|
|
19
|
+
handles(queue: string): boolean;
|
|
20
|
+
/** May a call on `queue` take a slot now? On `ok`, the caller holds a slot until {@link release}. */
|
|
21
|
+
tryAdmit(queue: string, item: AdmissionItem): Promise<Admission>;
|
|
22
|
+
/**
|
|
23
|
+
* Release the slot granted by {@link tryAdmit} for `queue`. `slotId` is the admitted item's
|
|
24
|
+
* `waiterId` (the engine's stepId), so a lease-based backend can drop the exact slot; an in-process
|
|
25
|
+
* counter ignores it.
|
|
26
|
+
*/
|
|
27
|
+
release(queue: string, slotId: string): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Optional: subscribe to "a slot just freed on `queue`" events (e.g. a fleet-wide pub/sub). The
|
|
30
|
+
* engine uses it to wake its admission-blocked runs early instead of waiting for their retry tick.
|
|
31
|
+
* A backend without it falls back to retry-tick polling. Fired best-effort across the fleet.
|
|
32
|
+
*/
|
|
33
|
+
onFreed?(handler: (queue: string) => void): void;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Default admission backend: one in-process {@link QueueController} per registered queue. Caps and
|
|
37
|
+
* waiter ordering are per engine instance (see {@link QueueConfig}). Preserves the exact pre-backend
|
|
38
|
+
* behaviour — the engine used to hold these controllers directly.
|
|
39
|
+
*/
|
|
40
|
+
export declare class InMemoryAdmissionBackend implements AdmissionBackend {
|
|
41
|
+
private readonly clock;
|
|
42
|
+
private readonly controllers;
|
|
43
|
+
constructor(clock: () => number);
|
|
44
|
+
register(config: QueueConfig): void;
|
|
45
|
+
handles(queue: string): boolean;
|
|
46
|
+
tryAdmit(queue: string, item: AdmissionItem): Promise<Admission>;
|
|
47
|
+
release(queue: string, _slotId: string): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=admission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admission.d.ts","sourceRoot":"","sources":["../../src/admission.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAmB,MAAM,YAAY,CAAC;AAEnG;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kFAAkF;IAClF,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IACpC,uGAAuG;IACvG,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,qGAAqG;IACrG,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACjE;;;;OAIG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD;;;;OAIG;IACH,OAAO,CAAC,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;CAClD;AAED;;;;GAIG;AACH,qBAAa,wBAAyB,YAAW,gBAAgB;IAGnD,OAAO,CAAC,QAAQ,CAAC,KAAK;IAFlC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsC;gBAErC,KAAK,EAAE,MAAM,MAAM;IAEhD,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAInC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAIzB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,SAAS,CAAC;IAOhE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAG7D"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { QueueController } from './queue.js';
|
|
2
|
+
/**
|
|
3
|
+
* Default admission backend: one in-process {@link QueueController} per registered queue. Caps and
|
|
4
|
+
* waiter ordering are per engine instance (see {@link QueueConfig}). Preserves the exact pre-backend
|
|
5
|
+
* behaviour — the engine used to hold these controllers directly.
|
|
6
|
+
*/
|
|
7
|
+
export class InMemoryAdmissionBackend {
|
|
8
|
+
clock;
|
|
9
|
+
controllers = new Map();
|
|
10
|
+
constructor(clock) {
|
|
11
|
+
this.clock = clock;
|
|
12
|
+
}
|
|
13
|
+
register(config) {
|
|
14
|
+
this.controllers.set(config.name, new QueueController(config, this.clock));
|
|
15
|
+
}
|
|
16
|
+
handles(queue) {
|
|
17
|
+
return this.controllers.has(queue);
|
|
18
|
+
}
|
|
19
|
+
async tryAdmit(queue, item) {
|
|
20
|
+
const controller = this.controllers.get(queue);
|
|
21
|
+
// Unregistered queue → ungated (mirrors the old `this.queues.get(queue)` truthiness check).
|
|
22
|
+
if (!controller)
|
|
23
|
+
return { ok: true };
|
|
24
|
+
return controller.tryAdmit(item);
|
|
25
|
+
}
|
|
26
|
+
async release(queue, _slotId) {
|
|
27
|
+
this.controllers.get(queue)?.release();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=admission.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admission.js","sourceRoot":"","sources":["../../src/admission.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwD,eAAe,EAAE,MAAM,YAAY,CAAC;AAoCnG;;;;GAIG;AACH,MAAM,OAAO,wBAAwB;IAGN;IAFZ,WAAW,GAAG,IAAI,GAAG,EAA2B,CAAC;IAElE,YAA6B,KAAmB;QAAnB,UAAK,GAAL,KAAK,CAAc;IAAG,CAAC;IAEpD,QAAQ,CAAC,MAAmB;QAC1B,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7E,CAAC;IAED,OAAO,CAAC,KAAa;QACnB,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,KAAa,EAAE,IAAmB;QAC/C,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC/C,4FAA4F;QAC5F,IAAI,CAAC,UAAU;YAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;QACrC,OAAO,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,KAAa,EAAE,OAAe;QAC1C,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IACzC,CAAC;CACF"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { StepOptions } from './interfaces.js';
|
|
2
|
+
/** Delay in ms before the next retry attempt, per a step's `StepOptions` backoff config. Shared by
|
|
3
|
+
* the local-step retry loop and the durable remote-step retry, so they stay consistent. */
|
|
4
|
+
export declare function backoffDelay(attempt: number, options?: StepOptions): number;
|
|
5
|
+
//# sourceMappingURL=backoff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backoff.d.ts","sourceRoot":"","sources":["../../src/backoff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD;4FAC4F;AAC5F,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,MAAM,CAM3E"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Delay in ms before the next retry attempt, per a step's `StepOptions` backoff config. Shared by
|
|
2
|
+
* the local-step retry loop and the durable remote-step retry, so they stay consistent. */
|
|
3
|
+
export function backoffDelay(attempt, options) {
|
|
4
|
+
const base = options?.backoffMs ?? 0;
|
|
5
|
+
if (base <= 0)
|
|
6
|
+
return 0;
|
|
7
|
+
const raw = options?.backoff === 'exp' ? base * 2 ** (attempt - 1) : base;
|
|
8
|
+
const capped = options?.backoffMaxMs ? Math.min(raw, options.backoffMaxMs) : raw;
|
|
9
|
+
return options?.jitter ? Math.round(capped * (0.5 + Math.random() * 0.5)) : capped;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=backoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backoff.js","sourceRoot":"","sources":["../../src/backoff.ts"],"names":[],"mappings":"AAEA;4FAC4F;AAC5F,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,OAAqB;IACjE,MAAM,IAAI,GAAG,OAAO,EAAE,SAAS,IAAI,CAAC,CAAC;IACrC,IAAI,IAAI,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IACxB,MAAM,GAAG,GAAG,OAAO,EAAE,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1E,MAAM,MAAM,GAAG,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IACjF,OAAO,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AACrF,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { StepCheckpoint, StepError, StepEvent, StepKind } from './interfaces.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build a *phased* step checkpoint — one with distinct enqueue/run/finish timestamps (a local step
|
|
4
|
+
* that is running/completed/failed, or a pending remote/sleep step). Computes the deterministic
|
|
5
|
+
* `stepId` and normalizes empty event arrays, so the engine's running/completed/failed/pending
|
|
6
|
+
* checkpoint literals are constructed in exactly one place instead of ~8.
|
|
7
|
+
*
|
|
8
|
+
* For instantaneous checkpoints (sleep timers, markers, delivered signals) use {@link instantCheckpoint}.
|
|
9
|
+
*/
|
|
10
|
+
export declare function stepCheckpoint(p: {
|
|
11
|
+
runId: string;
|
|
12
|
+
seq: number;
|
|
13
|
+
name: string;
|
|
14
|
+
kind: StepKind;
|
|
15
|
+
status: StepCheckpoint['status'];
|
|
16
|
+
attempts: number;
|
|
17
|
+
enqueuedAt: Date;
|
|
18
|
+
startedAt: Date;
|
|
19
|
+
finishedAt: Date;
|
|
20
|
+
input?: unknown;
|
|
21
|
+
output?: unknown;
|
|
22
|
+
error?: StepError | undefined;
|
|
23
|
+
events?: StepEvent[] | undefined;
|
|
24
|
+
workerGroup?: string | undefined;
|
|
25
|
+
wakeAt?: number | undefined;
|
|
26
|
+
}): StepCheckpoint;
|
|
27
|
+
/**
|
|
28
|
+
* Build an *instantaneous* checkpoint — one with no distinct enqueue/run/finish phases (a durable
|
|
29
|
+
* sleep timer, a breakpoint marker, a delivered signal, a published `setEvent` value, a `patched`
|
|
30
|
+
* marker). All three timestamps collapse to now and `attempts` is 1. Collapses what was five
|
|
31
|
+
* near-identical 10-field `saveCheckpoint({...})` literals into one builder.
|
|
32
|
+
*/
|
|
33
|
+
export declare function instantCheckpoint(p: {
|
|
34
|
+
runId: string;
|
|
35
|
+
seq: number;
|
|
36
|
+
name: string;
|
|
37
|
+
kind: StepKind;
|
|
38
|
+
status?: StepCheckpoint['status'];
|
|
39
|
+
output?: unknown;
|
|
40
|
+
wakeAt?: number;
|
|
41
|
+
}): StepCheckpoint;
|
|
42
|
+
//# sourceMappingURL=checkpoints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkpoints.d.ts","sourceRoot":"","sources":["../../src/checkpoints.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAQtF;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,IAAI,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;IAChB,UAAU,EAAE,IAAI,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC9B,MAAM,CAAC,EAAE,SAAS,EAAE,GAAG,SAAS,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B,GAAG,cAAc,CAmBjB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,CAAC,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC;IAClC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,cAAc,CAgBjB"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { stepId } from './protocol.js';
|
|
2
|
+
/** Drop an empty events array to `undefined` (the repeated `events?.length ? events : undefined`). */
|
|
3
|
+
function nonEmptyEvents(events) {
|
|
4
|
+
return events && events.length > 0 ? events : undefined;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Build a *phased* step checkpoint — one with distinct enqueue/run/finish timestamps (a local step
|
|
8
|
+
* that is running/completed/failed, or a pending remote/sleep step). Computes the deterministic
|
|
9
|
+
* `stepId` and normalizes empty event arrays, so the engine's running/completed/failed/pending
|
|
10
|
+
* checkpoint literals are constructed in exactly one place instead of ~8.
|
|
11
|
+
*
|
|
12
|
+
* For instantaneous checkpoints (sleep timers, markers, delivered signals) use {@link instantCheckpoint}.
|
|
13
|
+
*/
|
|
14
|
+
export function stepCheckpoint(p) {
|
|
15
|
+
return {
|
|
16
|
+
runId: p.runId,
|
|
17
|
+
seq: p.seq,
|
|
18
|
+
name: p.name,
|
|
19
|
+
kind: p.kind,
|
|
20
|
+
stepId: stepId(p.runId, p.seq),
|
|
21
|
+
status: p.status,
|
|
22
|
+
input: p.input,
|
|
23
|
+
output: p.output,
|
|
24
|
+
error: p.error,
|
|
25
|
+
events: nonEmptyEvents(p.events),
|
|
26
|
+
attempts: p.attempts,
|
|
27
|
+
workerGroup: p.workerGroup,
|
|
28
|
+
wakeAt: p.wakeAt,
|
|
29
|
+
enqueuedAt: p.enqueuedAt,
|
|
30
|
+
startedAt: p.startedAt,
|
|
31
|
+
finishedAt: p.finishedAt,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Build an *instantaneous* checkpoint — one with no distinct enqueue/run/finish phases (a durable
|
|
36
|
+
* sleep timer, a breakpoint marker, a delivered signal, a published `setEvent` value, a `patched`
|
|
37
|
+
* marker). All three timestamps collapse to now and `attempts` is 1. Collapses what was five
|
|
38
|
+
* near-identical 10-field `saveCheckpoint({...})` literals into one builder.
|
|
39
|
+
*/
|
|
40
|
+
export function instantCheckpoint(p) {
|
|
41
|
+
const at = new Date();
|
|
42
|
+
return {
|
|
43
|
+
runId: p.runId,
|
|
44
|
+
seq: p.seq,
|
|
45
|
+
name: p.name,
|
|
46
|
+
kind: p.kind,
|
|
47
|
+
stepId: stepId(p.runId, p.seq),
|
|
48
|
+
status: p.status ?? 'completed',
|
|
49
|
+
output: p.output,
|
|
50
|
+
wakeAt: p.wakeAt,
|
|
51
|
+
attempts: 1,
|
|
52
|
+
enqueuedAt: at,
|
|
53
|
+
startedAt: at,
|
|
54
|
+
finishedAt: at,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=checkpoints.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkpoints.js","sourceRoot":"","sources":["../../src/checkpoints.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC,sGAAsG;AACtG,SAAS,cAAc,CAAC,MAA+B;IACrD,OAAO,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AAC1D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,CAgB9B;IACC,OAAO;QACL,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,GAAG,EAAE,CAAC,CAAC,GAAG;QACV,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC;QAC9B,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC;QAChC,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,UAAU,EAAE,CAAC,CAAC,UAAU;KACzB,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,CAQjC;IACC,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC;IACtB,OAAO;QACL,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,GAAG,EAAE,CAAC,CAAC,GAAG;QACV,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC;QAC9B,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,WAAW;QAC/B,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,QAAQ,EAAE,CAAC;QACX,UAAU,EAAE,EAAE;QACd,SAAS,EAAE,EAAE;QACb,UAAU,EAAE,EAAE;KACf,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { RunQuery, SignalWaiter, StateStore, StepCheckpoint, WorkflowRun } from './interfaces.js';
|
|
2
|
+
/**
|
|
3
|
+
* Transforms a payload value as it crosses the store boundary — e.g. encrypt-at-rest, compress, or
|
|
4
|
+
* redact PII. `decode` must be the exact inverse of `encode`. Both are synchronous (use a local
|
|
5
|
+
* cipher / codec); wrap an async KMS yourself if you need one.
|
|
6
|
+
*/
|
|
7
|
+
export interface PayloadCodec {
|
|
8
|
+
encode(value: unknown): unknown;
|
|
9
|
+
decode(value: unknown): unknown;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A `StateStore` decorator that runs run/step **payloads** (input + output) through a
|
|
13
|
+
* {@link PayloadCodec} — encoded on write, decoded on read — so they're never stored in the clear.
|
|
14
|
+
* Adapter-agnostic: wrap any store. Searchable metadata (id, status, workflow, tags, timestamps) and
|
|
15
|
+
* the structured `error` are left untouched so the dashboard, queries, and recovery still work.
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* const store = new CodecStateStore(new TypeOrmStateStore(ds), aesCodec);
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare class CodecStateStore implements StateStore {
|
|
22
|
+
private readonly inner;
|
|
23
|
+
private readonly codec;
|
|
24
|
+
constructor(inner: StateStore, codec: PayloadCodec);
|
|
25
|
+
private enc;
|
|
26
|
+
private dec;
|
|
27
|
+
private encRun;
|
|
28
|
+
private decRun;
|
|
29
|
+
private encCp;
|
|
30
|
+
private decCp;
|
|
31
|
+
ensureSchema(): Promise<void>;
|
|
32
|
+
createRun(run: WorkflowRun): Promise<void>;
|
|
33
|
+
updateRun(runId: string, patch: Partial<WorkflowRun>): Promise<void>;
|
|
34
|
+
getRun(runId: string): Promise<WorkflowRun | null>;
|
|
35
|
+
deleteRun(runId: string): Promise<void>;
|
|
36
|
+
getCheckpoint(runId: string, seq: number): Promise<StepCheckpoint | null>;
|
|
37
|
+
saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
|
|
38
|
+
transaction<T>(work: (tx: {
|
|
39
|
+
raw: unknown;
|
|
40
|
+
saveCheckpoint: (cp: StepCheckpoint) => Promise<void>;
|
|
41
|
+
}) => Promise<T>): Promise<T>;
|
|
42
|
+
listIncompleteRuns(): Promise<WorkflowRun[]>;
|
|
43
|
+
listPendingRuns(limit: number): Promise<WorkflowRun[]>;
|
|
44
|
+
listDueTimers(nowMs: number): Promise<WorkflowRun[]>;
|
|
45
|
+
tryLockRun(runId: string, owner: string, leaseUntilMs: number, nowMs: number): Promise<boolean>;
|
|
46
|
+
releaseRunLock(runId: string): Promise<void>;
|
|
47
|
+
renewRunLock(runId: string, owner: string, leaseUntilMs: number): Promise<boolean>;
|
|
48
|
+
putSignalWaiter(waiter: SignalWaiter): Promise<void>;
|
|
49
|
+
takeSignalWaiter(token: string): Promise<SignalWaiter | null>;
|
|
50
|
+
listSignalWaiters(prefix: string): Promise<SignalWaiter[]>;
|
|
51
|
+
bufferSignal(token: string, payload: unknown): Promise<void>;
|
|
52
|
+
takeBufferedSignal(token: string): Promise<{
|
|
53
|
+
payload: unknown;
|
|
54
|
+
} | null>;
|
|
55
|
+
listRuns(query: RunQuery): Promise<WorkflowRun[]>;
|
|
56
|
+
listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
|
|
57
|
+
getLatestCheckpointByName(runId: string, name: string): Promise<StepCheckpoint | undefined>;
|
|
58
|
+
listCheckpointsByNamePrefix(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=codec-state-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codec-state-store.d.ts","sourceRoot":"","sources":["../../src/codec-state-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `StateStore` decorator that runs run/step **payloads** (input + output) through a
|
|
3
|
+
* {@link PayloadCodec} — encoded on write, decoded on read — so they're never stored in the clear.
|
|
4
|
+
* Adapter-agnostic: wrap any store. Searchable metadata (id, status, workflow, tags, timestamps) and
|
|
5
|
+
* the structured `error` are left untouched so the dashboard, queries, and recovery still work.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* const store = new CodecStateStore(new TypeOrmStateStore(ds), aesCodec);
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export class CodecStateStore {
|
|
12
|
+
inner;
|
|
13
|
+
codec;
|
|
14
|
+
constructor(inner, codec) {
|
|
15
|
+
this.inner = inner;
|
|
16
|
+
this.codec = codec;
|
|
17
|
+
}
|
|
18
|
+
enc(v) {
|
|
19
|
+
return v === undefined ? undefined : this.codec.encode(v);
|
|
20
|
+
}
|
|
21
|
+
dec(v) {
|
|
22
|
+
return v === undefined ? undefined : this.codec.decode(v);
|
|
23
|
+
}
|
|
24
|
+
encRun(run) {
|
|
25
|
+
return { ...run, input: this.enc(run.input), output: this.enc(run.output) };
|
|
26
|
+
}
|
|
27
|
+
decRun(run) {
|
|
28
|
+
return (run && { ...run, input: this.dec(run.input), output: this.dec(run.output) });
|
|
29
|
+
}
|
|
30
|
+
encCp(cp) {
|
|
31
|
+
return { ...cp, input: this.enc(cp.input), output: this.enc(cp.output) };
|
|
32
|
+
}
|
|
33
|
+
decCp(cp) {
|
|
34
|
+
return (cp && { ...cp, input: this.dec(cp.input), output: this.dec(cp.output) });
|
|
35
|
+
}
|
|
36
|
+
ensureSchema() {
|
|
37
|
+
return this.inner.ensureSchema?.() ?? Promise.resolve();
|
|
38
|
+
}
|
|
39
|
+
createRun(run) {
|
|
40
|
+
return this.inner.createRun(this.encRun(run));
|
|
41
|
+
}
|
|
42
|
+
updateRun(runId, patch) {
|
|
43
|
+
const next = { ...patch };
|
|
44
|
+
if ('input' in patch)
|
|
45
|
+
next.input = this.enc(patch.input);
|
|
46
|
+
if ('output' in patch)
|
|
47
|
+
next.output = this.enc(patch.output);
|
|
48
|
+
return this.inner.updateRun(runId, next);
|
|
49
|
+
}
|
|
50
|
+
async getRun(runId) {
|
|
51
|
+
return this.decRun(await this.inner.getRun(runId));
|
|
52
|
+
}
|
|
53
|
+
deleteRun(runId) {
|
|
54
|
+
return this.inner.deleteRun(runId);
|
|
55
|
+
}
|
|
56
|
+
async getCheckpoint(runId, seq) {
|
|
57
|
+
return this.decCp(await this.inner.getCheckpoint(runId, seq));
|
|
58
|
+
}
|
|
59
|
+
saveCheckpoint(checkpoint) {
|
|
60
|
+
return this.inner.saveCheckpoint(this.encCp(checkpoint));
|
|
61
|
+
}
|
|
62
|
+
transaction(work) {
|
|
63
|
+
if (!this.inner.transaction) {
|
|
64
|
+
throw new Error('the wrapped store does not support transactions');
|
|
65
|
+
}
|
|
66
|
+
// Encode the checkpoint inside the tx, so payloads stay encoded at rest like the normal path.
|
|
67
|
+
return this.inner.transaction((tx) => work({ raw: tx.raw, saveCheckpoint: (cp) => tx.saveCheckpoint(this.encCp(cp)) }));
|
|
68
|
+
}
|
|
69
|
+
async listIncompleteRuns() {
|
|
70
|
+
return (await this.inner.listIncompleteRuns()).map((r) => this.decRun(r));
|
|
71
|
+
}
|
|
72
|
+
async listPendingRuns(limit) {
|
|
73
|
+
return (await this.inner.listPendingRuns(limit)).map((r) => this.decRun(r));
|
|
74
|
+
}
|
|
75
|
+
async listDueTimers(nowMs) {
|
|
76
|
+
return (await this.inner.listDueTimers(nowMs)).map((r) => this.decRun(r));
|
|
77
|
+
}
|
|
78
|
+
tryLockRun(runId, owner, leaseUntilMs, nowMs) {
|
|
79
|
+
return this.inner.tryLockRun(runId, owner, leaseUntilMs, nowMs);
|
|
80
|
+
}
|
|
81
|
+
releaseRunLock(runId) {
|
|
82
|
+
return this.inner.releaseRunLock(runId);
|
|
83
|
+
}
|
|
84
|
+
renewRunLock(runId, owner, leaseUntilMs) {
|
|
85
|
+
return this.inner.renewRunLock(runId, owner, leaseUntilMs);
|
|
86
|
+
}
|
|
87
|
+
putSignalWaiter(waiter) {
|
|
88
|
+
return this.inner.putSignalWaiter(waiter);
|
|
89
|
+
}
|
|
90
|
+
takeSignalWaiter(token) {
|
|
91
|
+
return this.inner.takeSignalWaiter(token);
|
|
92
|
+
}
|
|
93
|
+
listSignalWaiters(prefix) {
|
|
94
|
+
return this.inner.listSignalWaiters(prefix);
|
|
95
|
+
}
|
|
96
|
+
bufferSignal(token, payload) {
|
|
97
|
+
return this.inner.bufferSignal(token, this.enc(payload));
|
|
98
|
+
}
|
|
99
|
+
async takeBufferedSignal(token) {
|
|
100
|
+
const buffered = await this.inner.takeBufferedSignal(token);
|
|
101
|
+
return buffered && { payload: this.dec(buffered.payload) };
|
|
102
|
+
}
|
|
103
|
+
async listRuns(query) {
|
|
104
|
+
return (await this.inner.listRuns(query)).map((r) => this.decRun(r));
|
|
105
|
+
}
|
|
106
|
+
async listCheckpoints(runId) {
|
|
107
|
+
return (await this.inner.listCheckpoints(runId)).map((c) => this.decCp(c));
|
|
108
|
+
}
|
|
109
|
+
async getLatestCheckpointByName(runId, name) {
|
|
110
|
+
// Forward the targeted read when the inner store implements it; otherwise fall back to the
|
|
111
|
+
// (always-present) listCheckpoints scan so wrapping a legacy custom store keeps working.
|
|
112
|
+
if (this.inner.getLatestCheckpointByName) {
|
|
113
|
+
const cp = await this.inner.getLatestCheckpointByName(runId, name);
|
|
114
|
+
return cp ? this.decCp(cp) : undefined;
|
|
115
|
+
}
|
|
116
|
+
let latest;
|
|
117
|
+
for (const cp of await this.inner.listCheckpoints(runId))
|
|
118
|
+
if (cp.name === name)
|
|
119
|
+
latest = cp;
|
|
120
|
+
return latest ? this.decCp(latest) : undefined;
|
|
121
|
+
}
|
|
122
|
+
async listCheckpointsByNamePrefix(runId, prefixes) {
|
|
123
|
+
const matches = this.inner.listCheckpointsByNamePrefix
|
|
124
|
+
? await this.inner.listCheckpointsByNamePrefix(runId, prefixes)
|
|
125
|
+
: (await this.inner.listCheckpoints(runId)).filter((cp) => prefixes.some((p) => cp.name.startsWith(p)));
|
|
126
|
+
return matches.map((c) => this.decCp(c));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=codec-state-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codec-state-store.js","sourceRoot":"","sources":["../../src/codec-state-store.ts"],"names":[],"mappings":"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"}
|