@blokjs/runner 0.2.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/dist/Blok.js +32 -3
  2. package/dist/Blok.js.map +1 -1
  3. package/dist/Configuration.d.ts +59 -5
  4. package/dist/Configuration.js +366 -96
  5. package/dist/Configuration.js.map +1 -1
  6. package/dist/ForEachNode.d.ts +59 -0
  7. package/dist/ForEachNode.js +522 -0
  8. package/dist/ForEachNode.js.map +1 -0
  9. package/dist/LoopMaxIterationsError.d.ts +11 -0
  10. package/dist/LoopMaxIterationsError.js +18 -0
  11. package/dist/LoopMaxIterationsError.js.map +1 -0
  12. package/dist/LoopNode.d.ts +36 -0
  13. package/dist/LoopNode.js +182 -0
  14. package/dist/LoopNode.js.map +1 -0
  15. package/dist/PayloadTooLargeError.d.ts +19 -0
  16. package/dist/PayloadTooLargeError.js +29 -0
  17. package/dist/PayloadTooLargeError.js.map +1 -0
  18. package/dist/RunCancelledError.d.ts +17 -0
  19. package/dist/RunCancelledError.js +25 -0
  20. package/dist/RunCancelledError.js.map +1 -0
  21. package/dist/Runner.d.ts +11 -1
  22. package/dist/Runner.js +9 -2
  23. package/dist/Runner.js.map +1 -1
  24. package/dist/RunnerSteps.js +648 -44
  25. package/dist/RunnerSteps.js.map +1 -1
  26. package/dist/RuntimeAdapterNode.d.ts +2 -1
  27. package/dist/RuntimeAdapterNode.js +2 -2
  28. package/dist/RuntimeAdapterNode.js.map +1 -1
  29. package/dist/RuntimeRegistry.d.ts +23 -2
  30. package/dist/RuntimeRegistry.js +31 -2
  31. package/dist/RuntimeRegistry.js.map +1 -1
  32. package/dist/SubworkflowNode.d.ts +181 -0
  33. package/dist/SubworkflowNode.js +479 -0
  34. package/dist/SubworkflowNode.js.map +1 -0
  35. package/dist/SwitchNode.d.ts +37 -0
  36. package/dist/SwitchNode.js +153 -0
  37. package/dist/SwitchNode.js.map +1 -0
  38. package/dist/TriggerBase.d.ts +178 -0
  39. package/dist/TriggerBase.js +1032 -5
  40. package/dist/TriggerBase.js.map +1 -1
  41. package/dist/TryCatchNode.d.ts +32 -0
  42. package/dist/TryCatchNode.js +207 -0
  43. package/dist/TryCatchNode.js.map +1 -0
  44. package/dist/WaitDispatchRequest.d.ts +38 -0
  45. package/dist/WaitDispatchRequest.js +13 -0
  46. package/dist/WaitDispatchRequest.js.map +1 -0
  47. package/dist/WaitNode.d.ts +23 -0
  48. package/dist/WaitNode.js +26 -0
  49. package/dist/WaitNode.js.map +1 -0
  50. package/dist/adapters/grpc/GrpcCodec.js +2 -2
  51. package/dist/adapters/grpc/GrpcRuntimeAdapter.d.ts +6 -4
  52. package/dist/adapters/grpc/GrpcRuntimeAdapter.js +6 -4
  53. package/dist/adapters/grpc/GrpcRuntimeAdapter.js.map +1 -1
  54. package/dist/adapters/grpc/types.d.ts +7 -5
  55. package/dist/adapters/grpc/types.js.map +1 -1
  56. package/dist/adapters/transport.d.ts +12 -41
  57. package/dist/adapters/transport.js +21 -70
  58. package/dist/adapters/transport.js.map +1 -1
  59. package/dist/cache/NodeResultCache.js +7 -0
  60. package/dist/cache/NodeResultCache.js.map +1 -1
  61. package/dist/concurrency/ConcurrencyBackend.d.ts +61 -0
  62. package/dist/concurrency/ConcurrencyBackend.js +20 -0
  63. package/dist/concurrency/ConcurrencyBackend.js.map +1 -0
  64. package/dist/concurrency/ConcurrencyLimitError.d.ts +37 -0
  65. package/dist/concurrency/ConcurrencyLimitError.js +16 -0
  66. package/dist/concurrency/ConcurrencyLimitError.js.map +1 -0
  67. package/dist/concurrency/NatsKvConcurrencyBackend.d.ts +64 -0
  68. package/dist/concurrency/NatsKvConcurrencyBackend.js +310 -0
  69. package/dist/concurrency/NatsKvConcurrencyBackend.js.map +1 -0
  70. package/dist/concurrency/QueueExpiredError.d.ts +40 -0
  71. package/dist/concurrency/QueueExpiredError.js +15 -0
  72. package/dist/concurrency/QueueExpiredError.js.map +1 -0
  73. package/dist/concurrency/RedisConcurrencyBackend.d.ts +64 -0
  74. package/dist/concurrency/RedisConcurrencyBackend.js +374 -0
  75. package/dist/concurrency/RedisConcurrencyBackend.js.map +1 -0
  76. package/dist/concurrency/createConcurrencyBackend.d.ts +24 -0
  77. package/dist/concurrency/createConcurrencyBackend.js +38 -0
  78. package/dist/concurrency/createConcurrencyBackend.js.map +1 -0
  79. package/dist/concurrency/readConcurrencyConfig.d.ts +60 -0
  80. package/dist/concurrency/readConcurrencyConfig.js +60 -0
  81. package/dist/concurrency/readConcurrencyConfig.js.map +1 -0
  82. package/dist/defineNode.d.ts +8 -0
  83. package/dist/defineNode.js +25 -5
  84. package/dist/defineNode.js.map +1 -1
  85. package/dist/graphql/GraphQLSchemaGenerator.js +1 -1
  86. package/dist/graphql/GraphQLSchemaGenerator.js.map +1 -1
  87. package/dist/idempotency/resolveIdempotencyKey.d.ts +20 -0
  88. package/dist/idempotency/resolveIdempotencyKey.js +37 -0
  89. package/dist/idempotency/resolveIdempotencyKey.js.map +1 -0
  90. package/dist/index.d.ts +30 -6
  91. package/dist/index.js +55 -6
  92. package/dist/index.js.map +1 -1
  93. package/dist/marketplace/RuntimeCatalog.d.ts +6 -0
  94. package/dist/marketplace/RuntimeCatalog.js.map +1 -1
  95. package/dist/marketplace/RuntimeDiscovery.d.ts +2 -2
  96. package/dist/marketplace/RuntimeDiscovery.js +18 -6
  97. package/dist/marketplace/RuntimeDiscovery.js.map +1 -1
  98. package/dist/monitoring/ConcurrencyMetrics.d.ts +82 -0
  99. package/dist/monitoring/ConcurrencyMetrics.js +139 -0
  100. package/dist/monitoring/ConcurrencyMetrics.js.map +1 -0
  101. package/dist/monitoring/ForEachWaitMetrics.d.ts +22 -0
  102. package/dist/monitoring/ForEachWaitMetrics.js +36 -0
  103. package/dist/monitoring/ForEachWaitMetrics.js.map +1 -0
  104. package/dist/monitoring/JanitorMetrics.d.ts +27 -0
  105. package/dist/monitoring/JanitorMetrics.js +48 -0
  106. package/dist/monitoring/JanitorMetrics.js.map +1 -0
  107. package/dist/openapi/OpenAPIGenerator.js +7 -2
  108. package/dist/openapi/OpenAPIGenerator.js.map +1 -1
  109. package/dist/runtime/PrimitiveStack.d.ts +64 -0
  110. package/dist/runtime/PrimitiveStack.js +92 -0
  111. package/dist/runtime/PrimitiveStack.js.map +1 -0
  112. package/dist/scheduling/DebounceBackend.d.ts +108 -0
  113. package/dist/scheduling/DebounceBackend.js +23 -0
  114. package/dist/scheduling/DebounceBackend.js.map +1 -0
  115. package/dist/scheduling/DebounceCoordinator.d.ts +141 -0
  116. package/dist/scheduling/DebounceCoordinator.js +362 -0
  117. package/dist/scheduling/DebounceCoordinator.js.map +1 -0
  118. package/dist/scheduling/DeferredDispatchSignal.d.ts +50 -0
  119. package/dist/scheduling/DeferredDispatchSignal.js +14 -0
  120. package/dist/scheduling/DeferredDispatchSignal.js.map +1 -0
  121. package/dist/scheduling/DeferredRunScheduler.d.ts +96 -0
  122. package/dist/scheduling/DeferredRunScheduler.js +256 -0
  123. package/dist/scheduling/DeferredRunScheduler.js.map +1 -0
  124. package/dist/scheduling/NatsKvDebounceBackend.d.ts +53 -0
  125. package/dist/scheduling/NatsKvDebounceBackend.js +334 -0
  126. package/dist/scheduling/NatsKvDebounceBackend.js.map +1 -0
  127. package/dist/scheduling/RedisDebounceBackend.d.ts +49 -0
  128. package/dist/scheduling/RedisDebounceBackend.js +356 -0
  129. package/dist/scheduling/RedisDebounceBackend.js.map +1 -0
  130. package/dist/scheduling/createDebounceBackend.d.ts +25 -0
  131. package/dist/scheduling/createDebounceBackend.js +39 -0
  132. package/dist/scheduling/createDebounceBackend.js.map +1 -0
  133. package/dist/scheduling/readSchedulingConfig.d.ts +24 -0
  134. package/dist/scheduling/readSchedulingConfig.js +52 -0
  135. package/dist/scheduling/readSchedulingConfig.js.map +1 -0
  136. package/dist/security/AuditLogger.js +1 -1
  137. package/dist/security/AuditLogger.js.map +1 -1
  138. package/dist/security/AuthMiddleware.d.ts +19 -20
  139. package/dist/security/AuthMiddleware.js +35 -20
  140. package/dist/security/AuthMiddleware.js.map +1 -1
  141. package/dist/security/OAuthProvider.js +2 -2
  142. package/dist/security/OAuthProvider.js.map +1 -1
  143. package/dist/security/SecretManager.js +14 -13
  144. package/dist/security/SecretManager.js.map +1 -1
  145. package/dist/security/index.d.ts +3 -1
  146. package/dist/security/index.js +3 -1
  147. package/dist/security/index.js.map +1 -1
  148. package/dist/testing/TestHarness.d.ts +27 -12
  149. package/dist/testing/TestHarness.js +19 -3
  150. package/dist/testing/TestHarness.js.map +1 -1
  151. package/dist/testing/WorkflowTestRunner.js +0 -7
  152. package/dist/testing/WorkflowTestRunner.js.map +1 -1
  153. package/dist/timeouts/StepTimeoutError.d.ts +22 -0
  154. package/dist/timeouts/StepTimeoutError.js +31 -0
  155. package/dist/timeouts/StepTimeoutError.js.map +1 -0
  156. package/dist/tracing/InMemoryRunStore.d.ts +41 -1
  157. package/dist/tracing/InMemoryRunStore.js +239 -0
  158. package/dist/tracing/InMemoryRunStore.js.map +1 -1
  159. package/dist/tracing/Janitor.d.ts +70 -0
  160. package/dist/tracing/Janitor.js +150 -0
  161. package/dist/tracing/Janitor.js.map +1 -0
  162. package/dist/tracing/PostgresRunStore.d.ts +57 -1
  163. package/dist/tracing/PostgresRunStore.js +711 -6
  164. package/dist/tracing/PostgresRunStore.js.map +1 -1
  165. package/dist/tracing/RoutingDiagnostics.d.ts +55 -0
  166. package/dist/tracing/RoutingDiagnostics.js +50 -0
  167. package/dist/tracing/RoutingDiagnostics.js.map +1 -0
  168. package/dist/tracing/RunStore.d.ts +181 -1
  169. package/dist/tracing/RunTracker.d.ts +244 -9
  170. package/dist/tracing/RunTracker.js +594 -1
  171. package/dist/tracing/RunTracker.js.map +1 -1
  172. package/dist/tracing/SqliteRunStore.d.ts +79 -2
  173. package/dist/tracing/SqliteRunStore.js +775 -16
  174. package/dist/tracing/SqliteRunStore.js.map +1 -1
  175. package/dist/tracing/TraceRouter.d.ts +20 -2
  176. package/dist/tracing/TraceRouter.js +612 -6
  177. package/dist/tracing/TraceRouter.js.map +1 -1
  178. package/dist/tracing/createStore.js +14 -3
  179. package/dist/tracing/createStore.js.map +1 -1
  180. package/dist/tracing/metadataFilter.d.ts +63 -0
  181. package/dist/tracing/metadataFilter.js +224 -0
  182. package/dist/tracing/metadataFilter.js.map +1 -0
  183. package/dist/tracing/sanitize.d.ts +11 -0
  184. package/dist/tracing/sanitize.js +29 -0
  185. package/dist/tracing/sanitize.js.map +1 -1
  186. package/dist/tracing/types.d.ts +672 -2
  187. package/dist/utils/createChildContext.d.ts +32 -0
  188. package/dist/utils/createChildContext.js +113 -0
  189. package/dist/utils/createChildContext.js.map +1 -0
  190. package/dist/utils/envAllowlist.d.ts +35 -0
  191. package/dist/utils/envAllowlist.js +113 -0
  192. package/dist/utils/envAllowlist.js.map +1 -0
  193. package/dist/version/RuntimeVersionValidator.d.ts +38 -0
  194. package/dist/version/RuntimeVersionValidator.js +121 -0
  195. package/dist/version/RuntimeVersionValidator.js.map +1 -0
  196. package/dist/visualization/WorkflowVisualizer.js +4 -4
  197. package/dist/visualization/WorkflowVisualizer.js.map +1 -1
  198. package/dist/workflow/PersistenceHelper.d.ts +18 -10
  199. package/dist/workflow/PersistenceHelper.js +35 -9
  200. package/dist/workflow/PersistenceHelper.js.map +1 -1
  201. package/dist/workflow/WorkflowNormalizer.d.ts +48 -42
  202. package/dist/workflow/WorkflowNormalizer.js +650 -18
  203. package/dist/workflow/WorkflowNormalizer.js.map +1 -1
  204. package/dist/workflow/WorkflowRegistry.d.ts +186 -0
  205. package/dist/workflow/WorkflowRegistry.js +202 -0
  206. package/dist/workflow/WorkflowRegistry.js.map +1 -0
  207. package/dist/workflow/sampleBody.d.ts +54 -0
  208. package/dist/workflow/sampleBody.js +320 -0
  209. package/dist/workflow/sampleBody.js.map +1 -0
  210. package/package.json +3 -8
  211. package/dist/adapters/HttpRuntimeAdapter.d.ts +0 -79
  212. package/dist/adapters/HttpRuntimeAdapter.js +0 -233
  213. package/dist/adapters/HttpRuntimeAdapter.js.map +0 -1
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Tier 2 #5 + #7 — thrown by `TriggerBase.run()` when a workflow's
3
+ * scheduling gates (debounce / delay) defer the run to a future
4
+ * dispatch. Tier 2 #6 follow-up extends this to cover `onLimit:"queue"`
5
+ * (concurrency-gate-deferred runs); same transport translation.
6
+ *
7
+ * NOT a true error — a control-flow exception. Trigger transports
8
+ * catch it and translate it into the appropriate transport-level
9
+ * response:
10
+ *
11
+ * - HTTP trigger → `202 Accepted` with `Location: /__blok/runs/:id`
12
+ * header and structured JSON body.
13
+ * - Worker trigger → ACK without retry (the deferred coordinator owns
14
+ * the eventual dispatch).
15
+ *
16
+ * Carries the run-detail summary the transport needs to populate the
17
+ * response.
18
+ */
19
+ export interface DeferredDispatchInfo {
20
+ /** Run id allocated by the tracer when the run was created. */
21
+ runId: string;
22
+ /** Workflow name. */
23
+ workflowName: string;
24
+ /**
25
+ * Resolved status the run was placed in:
26
+ * - `"delayed"` — Tier 2 #5 (`trigger.delay`).
27
+ * - `"debounced"` — Tier 2 #7 (`trigger.debounce`).
28
+ * - `"queued"` — Tier 2 #6 follow-up (`trigger.onLimit:"queue"`).
29
+ */
30
+ status: "delayed" | "debounced" | "queued";
31
+ /** ms since epoch when the run is scheduled to dispatch. */
32
+ scheduledAt: number;
33
+ /** ms since epoch when the run will expire if not dispatched. Undefined when no TTL. */
34
+ expiresAt?: number;
35
+ /** True when the run was placed in `"debounced"` status (Tier 2 #7). */
36
+ debounced: boolean;
37
+ /** Pings absorbed by the run so far (always 1+). */
38
+ pingCount: number;
39
+ /**
40
+ * For leading-mode debounce, the runId of the sibling that fired
41
+ * immediately. Lets transports return `Location: /__blok/runs/<sibling>`
42
+ * so the caller can poll the actually-running run.
43
+ */
44
+ intoRunId?: string;
45
+ }
46
+ export declare class DeferredDispatchSignal extends Error {
47
+ readonly info: DeferredDispatchInfo;
48
+ constructor(info: DeferredDispatchInfo);
49
+ }
50
+ export declare function isDeferredDispatchSignal(err: unknown): err is DeferredDispatchSignal;
@@ -0,0 +1,14 @@
1
+ export class DeferredDispatchSignal extends Error {
2
+ info;
3
+ constructor(info) {
4
+ super(`Run ${info.runId} for workflow '${info.workflowName}' was deferred ` +
5
+ `(${info.status}; scheduledAt=${info.scheduledAt}; pingCount=${info.pingCount}).`);
6
+ this.name = "DeferredDispatchSignal";
7
+ this.info = info;
8
+ Object.setPrototypeOf(this, DeferredDispatchSignal.prototype);
9
+ }
10
+ }
11
+ export function isDeferredDispatchSignal(err) {
12
+ return err instanceof DeferredDispatchSignal;
13
+ }
14
+ //# sourceMappingURL=DeferredDispatchSignal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeferredDispatchSignal.js","sourceRoot":"","sources":["../../src/scheduling/DeferredDispatchSignal.ts"],"names":[],"mappings":"AA8CA,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IAChC,IAAI,CAAuB;IAE3C,YAAY,IAA0B;QACrC,KAAK,CACJ,OAAO,IAAI,CAAC,KAAK,kBAAkB,IAAI,CAAC,YAAY,iBAAiB;YACpE,IAAI,IAAI,CAAC,MAAM,iBAAiB,IAAI,CAAC,WAAW,eAAe,IAAI,CAAC,SAAS,IAAI,CAClF,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,sBAAsB,CAAC,SAAS,CAAC,CAAC;IAC/D,CAAC;CACD;AAED,MAAM,UAAU,wBAAwB,CAAC,GAAY;IACpD,OAAO,GAAG,YAAY,sBAAsB,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,96 @@
1
+ import type { ScheduledDispatchRow } from "../tracing/types";
2
+ export type DeferredDispatchFn = () => Promise<void>;
3
+ /**
4
+ * Optional persistence payload — when supplied to `schedule()`, the
5
+ * scheduler writes a `scheduled_dispatches` row before registering the
6
+ * timer, and deletes it on cancel or fire. Trigger boot recovery
7
+ * (e.g. `HttpTrigger.recoverDispatches`) reads these rows to re-register
8
+ * timers across process restarts.
9
+ */
10
+ export interface DeferredScheduleOptions {
11
+ workflowName: string;
12
+ /** Trigger type — `"http"` for v1; future triggers can opt in. */
13
+ triggerType: string;
14
+ /** TTL deadline (ms since epoch). When set, expired rows get marked `expired` on boot recovery. */
15
+ expiresAt?: number;
16
+ /** Mirrors the run record's status. */
17
+ dispatchStatus: ScheduledDispatchRow["dispatchStatus"];
18
+ /**
19
+ * JSON-serializable trigger-defined payload sufficient to reconstruct
20
+ * dispatch on boot. Trigger packages choose what to put here.
21
+ */
22
+ payload: unknown;
23
+ }
24
+ export declare class DeferredRunScheduler {
25
+ private static instance;
26
+ private entries;
27
+ /**
28
+ * Tier C #2 — stable per-process identity used for claim ownership.
29
+ * Generated once per scheduler instance; surviving across resets is
30
+ * intentional (a "process restart" in production gets a fresh
31
+ * scheduler singleton and therefore a fresh processId — peers' stale
32
+ * claims expire after the lease).
33
+ */
34
+ private readonly processId;
35
+ /** Tier C #2 — running heartbeat timer; one per scheduler instance. */
36
+ private heartbeatTimer;
37
+ /** Tier C #2 — count of persisted entries currently registered. Heartbeat is only active when > 0. */
38
+ private persistedEntryCount;
39
+ static getInstance(): DeferredRunScheduler;
40
+ /** Test-only — reset the singleton. Cancels all pending timers. */
41
+ static resetInstance(): void;
42
+ /**
43
+ * Register a deferred dispatch. The timer fires at `dispatchAt`
44
+ * (clamped to ≥ now); when it fires, the entry is removed from the
45
+ * map and `dispatchFn` is invoked. Errors thrown by `dispatchFn` are
46
+ * swallowed and logged — the scheduler is fire-and-forget by design.
47
+ *
48
+ * Re-scheduling the same `runId` cancels the previous timer and
49
+ * replaces it (used by the debounce coordinator's "reset on ping").
50
+ *
51
+ * When `persist` is provided, the scheduler also writes a
52
+ * `scheduled_dispatches` row before registering the timer (so a
53
+ * crash leaves the dispatch recoverable), and deletes the row on
54
+ * cancel or fire.
55
+ */
56
+ schedule(runId: string, dispatchAt: number, dispatchFn: DeferredDispatchFn, persist?: DeferredScheduleOptions): void;
57
+ /**
58
+ * Cancel a pending dispatch. Returns true if the entry existed and
59
+ * was cancelled; false otherwise. Idempotent. When the entry was
60
+ * persisted, also deletes the `scheduled_dispatches` row.
61
+ *
62
+ * `cancelPersistedOnly` (default false) lets callers force the
63
+ * persistence-row delete even when the in-memory timer is gone (e.g.
64
+ * recovery cleanup that knows about a row but never had a timer).
65
+ */
66
+ cancel(runId: string, cancelPersistedOnly?: boolean): boolean;
67
+ private deletePersistedRow;
68
+ /** True if `runId` has a pending timer. */
69
+ has(runId: string): boolean;
70
+ /** Number of pending timers. Useful for tests + observability. */
71
+ size(): number;
72
+ /**
73
+ * Fire ALL pending dispatches immediately and clear the queue.
74
+ * Awaits each `dispatchFn` so the caller knows when the queue is
75
+ * drained. Useful for graceful shutdown.
76
+ */
77
+ drainAll(): Promise<void>;
78
+ /** Cancel everything without dispatching. */
79
+ clear(): void;
80
+ /**
81
+ * Stable per-process identity used for the claim API. Trigger boot
82
+ * recovery passes this to `RunStore.claimDispatches(processId, …)`
83
+ * so peers can recognize each other's claims.
84
+ */
85
+ getProcessId(): string;
86
+ private maybeStartHeartbeat;
87
+ private maybeStopHeartbeat;
88
+ private runHeartbeat;
89
+ }
90
+ /**
91
+ * Public helper that returns the operator-configured claim-lease in
92
+ * ms. Used by `HttpTrigger.recoverDispatches()` to pass the lease to
93
+ * `RunStore.claimDispatches()`. Centralized so the env var name is
94
+ * defined in one place.
95
+ */
96
+ export declare function getSchedulerClaimLeaseMs(): number;
@@ -0,0 +1,256 @@
1
+ /**
2
+ * Tier 2 #5 — in-memory scheduler for deferred workflow runs. Tier 2
3
+ * #5+#7 follow-up adds optional sqlite-backed durability via the
4
+ * {@link DeferredScheduleOptions.persist} parameter. Tier C #2 adds
5
+ * cross-process claim heartbeats so multi-process deployments sharing
6
+ * a PG store don't double-fire the same dispatch.
7
+ *
8
+ * Process-wide singleton; obtained via {@link DeferredRunScheduler.getInstance}.
9
+ * Reset between tests via {@link DeferredRunScheduler.resetInstance}.
10
+ */
11
+ import { randomUUID } from "node:crypto";
12
+ import { RunTracker } from "../tracing/RunTracker";
13
+ const DEFAULT_CLAIM_LEASE_MS = 60_000;
14
+ const DEFAULT_HEARTBEAT_INTERVAL_MS = 20_000;
15
+ export class DeferredRunScheduler {
16
+ static instance = null;
17
+ entries = new Map();
18
+ /**
19
+ * Tier C #2 — stable per-process identity used for claim ownership.
20
+ * Generated once per scheduler instance; surviving across resets is
21
+ * intentional (a "process restart" in production gets a fresh
22
+ * scheduler singleton and therefore a fresh processId — peers' stale
23
+ * claims expire after the lease).
24
+ */
25
+ processId = randomUUID();
26
+ /** Tier C #2 — running heartbeat timer; one per scheduler instance. */
27
+ heartbeatTimer = null;
28
+ /** Tier C #2 — count of persisted entries currently registered. Heartbeat is only active when > 0. */
29
+ persistedEntryCount = 0;
30
+ static getInstance() {
31
+ if (!DeferredRunScheduler.instance) {
32
+ DeferredRunScheduler.instance = new DeferredRunScheduler();
33
+ }
34
+ return DeferredRunScheduler.instance;
35
+ }
36
+ /** Test-only — reset the singleton. Cancels all pending timers. */
37
+ static resetInstance() {
38
+ DeferredRunScheduler.instance?.clear();
39
+ DeferredRunScheduler.instance = null;
40
+ }
41
+ /**
42
+ * Register a deferred dispatch. The timer fires at `dispatchAt`
43
+ * (clamped to ≥ now); when it fires, the entry is removed from the
44
+ * map and `dispatchFn` is invoked. Errors thrown by `dispatchFn` are
45
+ * swallowed and logged — the scheduler is fire-and-forget by design.
46
+ *
47
+ * Re-scheduling the same `runId` cancels the previous timer and
48
+ * replaces it (used by the debounce coordinator's "reset on ping").
49
+ *
50
+ * When `persist` is provided, the scheduler also writes a
51
+ * `scheduled_dispatches` row before registering the timer (so a
52
+ * crash leaves the dispatch recoverable), and deletes the row on
53
+ * cancel or fire.
54
+ */
55
+ schedule(runId, dispatchAt, dispatchFn, persist) {
56
+ // Persist BEFORE the timer so a crash between persist + setTimeout
57
+ // still leaves the row recoverable.
58
+ const persisted = persist !== undefined;
59
+ if (persisted) {
60
+ const tracker = RunTracker.getInstance();
61
+ if (tracker.active) {
62
+ try {
63
+ tracker.getStore().upsertScheduledDispatch({
64
+ runId,
65
+ workflowName: persist.workflowName,
66
+ triggerType: persist.triggerType,
67
+ scheduledAt: dispatchAt,
68
+ expiresAt: persist.expiresAt,
69
+ dispatchStatus: persist.dispatchStatus,
70
+ payload: persist.payload,
71
+ createdAt: Date.now(),
72
+ });
73
+ }
74
+ catch (err) {
75
+ // Don't block the dispatch on persistence failure — log and continue.
76
+ console.error(`[blok][scheduling] persist failed for run ${runId}; continuing in-memory only:`, err instanceof Error ? err.stack || err.message : err);
77
+ }
78
+ }
79
+ }
80
+ // Replace any existing entry for this runId.
81
+ const existing = this.entries.get(runId);
82
+ if (existing)
83
+ clearTimeout(existing.timer);
84
+ const delay = Math.max(0, dispatchAt - Date.now());
85
+ const timer = setTimeout(() => {
86
+ this.entries.delete(runId);
87
+ // Best-effort delete the persisted row before invoking dispatchFn —
88
+ // dispatch will write the run's terminal status separately.
89
+ if (persisted) {
90
+ this.deletePersistedRow(runId);
91
+ this.persistedEntryCount = Math.max(0, this.persistedEntryCount - 1);
92
+ this.maybeStopHeartbeat();
93
+ }
94
+ void dispatchFn().catch((err) => {
95
+ console.error(`[blok][scheduling] DeferredRunScheduler dispatch failed for run ${runId}:`, err instanceof Error ? err.stack || err.message : err);
96
+ });
97
+ }, delay);
98
+ // `Node` will keep the event loop alive for pending timers — that's
99
+ // the desired behavior for delayed runs in long-running services.
100
+ // `unref()` would be wrong here.
101
+ const wasPersisted = existing?.persisted === true;
102
+ this.entries.set(runId, { runId, dispatchAt, timer, dispatchFn, persisted });
103
+ // Tier C #2 — track persisted entry count + manage heartbeat
104
+ // lifecycle. The heartbeat only runs while we have ≥1 persisted
105
+ // entry (otherwise there's nothing to keep claimed). Replace doesn't
106
+ // change the count.
107
+ if (persisted && !wasPersisted) {
108
+ this.persistedEntryCount++;
109
+ this.maybeStartHeartbeat();
110
+ }
111
+ else if (!persisted && wasPersisted) {
112
+ this.persistedEntryCount = Math.max(0, this.persistedEntryCount - 1);
113
+ this.maybeStopHeartbeat();
114
+ }
115
+ }
116
+ /**
117
+ * Cancel a pending dispatch. Returns true if the entry existed and
118
+ * was cancelled; false otherwise. Idempotent. When the entry was
119
+ * persisted, also deletes the `scheduled_dispatches` row.
120
+ *
121
+ * `cancelPersistedOnly` (default false) lets callers force the
122
+ * persistence-row delete even when the in-memory timer is gone (e.g.
123
+ * recovery cleanup that knows about a row but never had a timer).
124
+ */
125
+ cancel(runId, cancelPersistedOnly = false) {
126
+ const entry = this.entries.get(runId);
127
+ if (!entry) {
128
+ if (cancelPersistedOnly) {
129
+ return this.deletePersistedRow(runId);
130
+ }
131
+ return false;
132
+ }
133
+ clearTimeout(entry.timer);
134
+ this.entries.delete(runId);
135
+ if (entry.persisted) {
136
+ this.deletePersistedRow(runId);
137
+ this.persistedEntryCount = Math.max(0, this.persistedEntryCount - 1);
138
+ this.maybeStopHeartbeat();
139
+ }
140
+ return true;
141
+ }
142
+ deletePersistedRow(runId) {
143
+ const tracker = RunTracker.getInstance();
144
+ if (!tracker.active)
145
+ return false;
146
+ try {
147
+ return tracker.getStore().deleteScheduledDispatch(runId);
148
+ }
149
+ catch (err) {
150
+ console.error(`[blok][scheduling] persist-cleanup failed for run ${runId}:`, err instanceof Error ? err.stack || err.message : err);
151
+ return false;
152
+ }
153
+ }
154
+ /** True if `runId` has a pending timer. */
155
+ has(runId) {
156
+ return this.entries.has(runId);
157
+ }
158
+ /** Number of pending timers. Useful for tests + observability. */
159
+ size() {
160
+ return this.entries.size;
161
+ }
162
+ /**
163
+ * Fire ALL pending dispatches immediately and clear the queue.
164
+ * Awaits each `dispatchFn` so the caller knows when the queue is
165
+ * drained. Useful for graceful shutdown.
166
+ */
167
+ async drainAll() {
168
+ const toFire = Array.from(this.entries.values());
169
+ // Cancel all timers first so we don't double-dispatch.
170
+ for (const entry of toFire)
171
+ clearTimeout(entry.timer);
172
+ this.entries.clear();
173
+ // Sequential dispatch — preserves intended order if it matters.
174
+ for (const entry of toFire) {
175
+ try {
176
+ await entry.dispatchFn();
177
+ }
178
+ catch (err) {
179
+ console.error(`[blok][scheduling] drainAll dispatch failed for run ${entry.runId}:`, err instanceof Error ? err.stack || err.message : err);
180
+ }
181
+ }
182
+ }
183
+ /** Cancel everything without dispatching. */
184
+ clear() {
185
+ for (const entry of this.entries.values())
186
+ clearTimeout(entry.timer);
187
+ this.entries.clear();
188
+ this.persistedEntryCount = 0;
189
+ this.maybeStopHeartbeat();
190
+ }
191
+ // === Tier C #2 — cross-process claim heartbeat ===
192
+ /**
193
+ * Stable per-process identity used for the claim API. Trigger boot
194
+ * recovery passes this to `RunStore.claimDispatches(processId, …)`
195
+ * so peers can recognize each other's claims.
196
+ */
197
+ getProcessId() {
198
+ return this.processId;
199
+ }
200
+ maybeStartHeartbeat() {
201
+ if (this.heartbeatTimer !== null)
202
+ return;
203
+ if (this.persistedEntryCount === 0)
204
+ return;
205
+ if (process.env.BLOK_SCHEDULER_CLAIM_DISABLED === "1")
206
+ return;
207
+ const intervalMs = readEnvInt("BLOK_SCHEDULER_HEARTBEAT_INTERVAL_MS", DEFAULT_HEARTBEAT_INTERVAL_MS);
208
+ this.heartbeatTimer = setInterval(() => {
209
+ this.runHeartbeat();
210
+ }, intervalMs);
211
+ // Don't keep the event loop alive solely for the heartbeat —
212
+ // the persisted entries' timers already do that. `unref()`
213
+ // avoids blocking shutdown when no entries are pending.
214
+ this.heartbeatTimer.unref?.();
215
+ }
216
+ maybeStopHeartbeat() {
217
+ if (this.heartbeatTimer === null)
218
+ return;
219
+ if (this.persistedEntryCount > 0)
220
+ return;
221
+ clearInterval(this.heartbeatTimer);
222
+ this.heartbeatTimer = null;
223
+ }
224
+ runHeartbeat() {
225
+ const tracker = RunTracker.getInstance();
226
+ if (!tracker.active)
227
+ return;
228
+ try {
229
+ tracker.getStore().heartbeatClaims(this.processId, Date.now());
230
+ }
231
+ catch (err) {
232
+ // Heartbeat failures are non-fatal — the lease will expire if
233
+ // they continue, and a peer will take over. Log + continue.
234
+ console.warn(`[blok][scheduling] heartbeatClaims failed: ${err instanceof Error ? err.message : String(err)}`);
235
+ }
236
+ }
237
+ }
238
+ function readEnvInt(name, fallback) {
239
+ const raw = process.env[name];
240
+ if (raw && /^\d+$/.test(raw)) {
241
+ const n = Number(raw);
242
+ if (Number.isFinite(n) && n > 0)
243
+ return n;
244
+ }
245
+ return fallback;
246
+ }
247
+ /**
248
+ * Public helper that returns the operator-configured claim-lease in
249
+ * ms. Used by `HttpTrigger.recoverDispatches()` to pass the lease to
250
+ * `RunStore.claimDispatches()`. Centralized so the env var name is
251
+ * defined in one place.
252
+ */
253
+ export function getSchedulerClaimLeaseMs() {
254
+ return readEnvInt("BLOK_SCHEDULER_CLAIM_LEASE_MS", DEFAULT_CLAIM_LEASE_MS);
255
+ }
256
+ //# sourceMappingURL=DeferredRunScheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeferredRunScheduler.js","sourceRoot":"","sources":["../../src/scheduling/DeferredRunScheduler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Tier C #1 · NATS KV-backed debounce backend.
3
+ *
4
+ * Coordinates per-(workflow, debounceKey) window state across processes
5
+ * via a single NATS JetStream KV value per bucket with revision-based
6
+ * compare-and-swap (OCC). Mirrors the storage model of
7
+ * `NatsKvConcurrencyBackend` but with a different document shape (one
8
+ * window per bucket, not a leases array).
9
+ *
10
+ * Acquire / extend / coalesce: a bounded CAS loop (10 retries) that
11
+ * reads → decides ownership → atomically writes. On retry exhaustion,
12
+ * fall back to admitting the ping as a `coalesce` against the current
13
+ * owner — debounce is not a safety gate, so we'd rather over-coalesce
14
+ * than drop pings on a contention spike.
15
+ *
16
+ * Finalize: same OCC pattern. The owning process atomically reads the
17
+ * doc, confirms it still owns AND `now >= scheduledAt`, and atomically
18
+ * deletes. On lease handoff, the owner discovers it no longer owns and
19
+ * abandons silently.
20
+ *
21
+ * **Owner-local payload**: this backend tracks `pingCount`,
22
+ * `lastPingAt`, and `scheduledAt` only — not the payload. Cross-process
23
+ * latest-payload-wins is a deferred follow-up.
24
+ */
25
+ import type { DebounceBackend, DebounceFinalizeResult, DebounceRegisterBackendOpts, DebounceRegisterBackendResult } from "./DebounceBackend";
26
+ export interface NatsKvDebounceConfig {
27
+ servers: string[];
28
+ token?: string;
29
+ user?: string;
30
+ pass?: string;
31
+ bucketName: string;
32
+ }
33
+ export declare function readNatsKvDebounceConfigFromEnv(): NatsKvDebounceConfig;
34
+ export declare class NatsKvDebounceBackend implements DebounceBackend {
35
+ readonly name = "nats-kv";
36
+ private nc;
37
+ private kv;
38
+ private readonly config;
39
+ private connected;
40
+ constructor(config?: Partial<NatsKvDebounceConfig>);
41
+ connect(): Promise<void>;
42
+ disconnect(): Promise<void>;
43
+ private bucketKey;
44
+ private encodeSegment;
45
+ private requireKv;
46
+ private computeScheduledAt;
47
+ registerPing(opts: DebounceRegisterBackendOpts): Promise<DebounceRegisterBackendResult>;
48
+ finalize(workflowName: string, debounceKey: string, runId: string, now: number): Promise<DebounceFinalizeResult>;
49
+ cancel(workflowName: string, debounceKey: string): Promise<boolean>;
50
+ purgeExpired(now: number): Promise<number>;
51
+ private safeGet;
52
+ private parseDoc;
53
+ }