@hatchet-dev/typescript-sdk 1.15.2 → 1.17.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 (202) hide show
  1. package/README.md +14 -2
  2. package/clients/admin/admin-client.d.ts +2 -2
  3. package/clients/admin/admin-client.js +8 -9
  4. package/clients/dispatcher/action-listener.d.ts +3 -6
  5. package/clients/dispatcher/action-listener.js +54 -23
  6. package/clients/dispatcher/dispatcher-client.js +5 -8
  7. package/clients/dispatcher/heartbeat/heartbeat-worker.js +7 -4
  8. package/clients/event/event-client.d.ts +2 -2
  9. package/clients/event/event-client.js +5 -11
  10. package/clients/hatchet-client/hatchet-logger.js +8 -17
  11. package/clients/listeners/durable-listener/durable-listener-client.d.ts +115 -15
  12. package/clients/listeners/durable-listener/durable-listener-client.js +769 -19
  13. package/clients/listeners/durable-listener/pooled-durable-listener-client.js +11 -22
  14. package/clients/listeners/run-listener/child-listener-client.d.ts +1 -1
  15. package/clients/listeners/run-listener/child-listener-client.js +34 -30
  16. package/clients/listeners/run-listener/pooled-child-listener-client.js +9 -19
  17. package/clients/rest/generated/Api.d.ts +25 -1
  18. package/clients/rest/generated/Api.js +20 -0
  19. package/clients/rest/generated/data-contracts.d.ts +60 -1
  20. package/clients/rest/generated/data-contracts.js +9 -1
  21. package/legacy/examples/affinity-workers.js +2 -3
  22. package/legacy/examples/byo-logger.js +0 -2
  23. package/legacy/examples/concurrency/cancel-in-progress/concurrency-worker.js +4 -2
  24. package/legacy/examples/concurrency/group-round-robin/concurrency-event.js +0 -1
  25. package/legacy/examples/concurrency/group-round-robin/concurrency-worker-expression.js +4 -2
  26. package/legacy/examples/concurrency/group-round-robin/concurrency-worker-key-fn.js +4 -2
  27. package/legacy/examples/example-event.js +0 -3
  28. package/legacy/examples/logger.js +0 -1
  29. package/legacy/examples/sticky-worker-with-check.js +0 -1
  30. package/legacy/examples/sticky-worker.js +0 -1
  31. package/legacy/legacy-client.js +2 -2
  32. package/legacy/legacy-transformer.js +2 -4
  33. package/legacy/step.d.ts +16 -16
  34. package/legacy/step.js +8 -17
  35. package/legacy/workflow.d.ts +81 -81
  36. package/package.json +20 -29
  37. package/protoc/dispatcher/dispatcher.d.ts +20 -0
  38. package/protoc/dispatcher/dispatcher.js +136 -2
  39. package/protoc/v1/dispatcher.d.ts +168 -0
  40. package/protoc/v1/dispatcher.js +1920 -1
  41. package/protoc/v1/shared/trigger.d.ts +89 -0
  42. package/protoc/v1/shared/trigger.js +493 -0
  43. package/protoc/v1/workflows.d.ts +34 -34
  44. package/protoc/v1/workflows.js +252 -200
  45. package/protoc/workflows/workflows.d.ts +2 -75
  46. package/protoc/workflows/workflows.js +16 -491
  47. package/util/abort-error.d.ts +15 -1
  48. package/util/abort-error.js +30 -5
  49. package/util/config-loader/config-loader.js +4 -3
  50. package/util/config-loader/token.js +9 -2
  51. package/util/errors/eviction-not-supported-error.d.ts +5 -0
  52. package/util/errors/eviction-not-supported-error.js +18 -0
  53. package/util/errors/hatchet-error.d.ts +9 -1
  54. package/util/errors/hatchet-error.js +23 -2
  55. package/util/errors/non-determinism-error.d.ts +7 -0
  56. package/util/errors/non-determinism-error.js +21 -0
  57. package/util/errors/task-run-terminated-error.d.ts +6 -0
  58. package/util/errors/task-run-terminated-error.js +15 -0
  59. package/util/grpc-error.d.ts +9 -0
  60. package/util/grpc-error.js +25 -0
  61. package/util/hatchet-promise/hatchet-promise.d.ts +6 -1
  62. package/util/hatchet-promise/hatchet-promise.js +16 -2
  63. package/util/logger/logger.js +0 -1
  64. package/util/parse.d.ts +1 -1
  65. package/util/parse.js +4 -2
  66. package/util/retrier.js +2 -3
  67. package/util/sleep.d.ts +3 -2
  68. package/util/sleep.js +6 -4
  69. package/util/workflow-run-ref.js +5 -3
  70. package/v1/client/admin.d.ts +2 -2
  71. package/v1/client/admin.js +2 -6
  72. package/v1/client/client.d.ts +7 -11
  73. package/v1/client/client.interface.d.ts +5 -8
  74. package/v1/client/client.js +34 -40
  75. package/v1/client/duration.d.ts +11 -1
  76. package/v1/client/duration.js +44 -0
  77. package/v1/client/features/cel.js +1 -1
  78. package/v1/client/features/crons.js +2 -2
  79. package/v1/client/features/index.d.ts +5 -0
  80. package/v1/client/features/index.js +5 -0
  81. package/v1/client/features/logs.d.ts +37 -0
  82. package/v1/client/features/logs.js +46 -0
  83. package/v1/client/features/runs.d.ts +16 -3
  84. package/v1/client/features/runs.js +38 -4
  85. package/v1/client/features/schedules.js +4 -4
  86. package/v1/client/features/webhooks.js +4 -2
  87. package/v1/client/features/workflows.js +1 -1
  88. package/v1/client/worker/context.d.ts +101 -6
  89. package/v1/client/worker/context.js +257 -44
  90. package/v1/client/worker/deprecated/deprecation.js +8 -4
  91. package/v1/client/worker/deprecated/index.d.ts +1 -1
  92. package/v1/client/worker/deprecated/index.js +2 -1
  93. package/v1/client/worker/deprecated/legacy-worker.d.ts +5 -0
  94. package/v1/client/worker/deprecated/legacy-worker.js +33 -24
  95. package/v1/client/worker/deprecated/pre-eviction.d.ts +12 -0
  96. package/v1/client/worker/deprecated/pre-eviction.js +37 -0
  97. package/v1/client/worker/engine-version.d.ts +5 -0
  98. package/v1/client/worker/engine-version.js +14 -0
  99. package/v1/client/worker/eviction/eviction-cache.d.ts +33 -0
  100. package/v1/client/worker/eviction/eviction-cache.js +139 -0
  101. package/v1/client/worker/eviction/eviction-manager.d.ts +42 -0
  102. package/v1/client/worker/eviction/eviction-manager.js +132 -0
  103. package/v1/client/worker/eviction/eviction-policy.d.ts +19 -0
  104. package/v1/client/worker/eviction/eviction-policy.js +8 -0
  105. package/v1/client/worker/eviction/index.d.ts +3 -0
  106. package/v1/client/worker/eviction/index.js +11 -0
  107. package/v1/client/worker/health-server.js +3 -3
  108. package/v1/client/worker/slot-utils.js +0 -3
  109. package/v1/client/worker/worker-internal.d.ts +23 -4
  110. package/v1/client/worker/worker-internal.js +216 -148
  111. package/v1/client/worker/worker.d.ts +1 -0
  112. package/v1/client/worker/worker.js +34 -0
  113. package/v1/conditions/base.js +0 -1
  114. package/v1/conditions/index.js +2 -4
  115. package/v1/conditions/sleep-condition.js +2 -1
  116. package/v1/conditions/transformer.js +2 -1
  117. package/v1/declaration.d.ts +6 -4
  118. package/v1/declaration.js +20 -7
  119. package/v1/examples/__e2e__/harness.d.ts +5 -0
  120. package/v1/examples/__e2e__/harness.js +17 -3
  121. package/v1/examples/affinity/affinity-workers.js +0 -1
  122. package/v1/examples/bulk_operations/workflow.js +0 -1
  123. package/v1/examples/cancellation/run.js +0 -1
  124. package/v1/examples/cancellations/run.js +0 -1
  125. package/v1/examples/child_workflows/run.js +0 -2
  126. package/v1/examples/child_workflows/workflow.js +0 -1
  127. package/v1/examples/concurrency-rr/load.js +0 -1
  128. package/v1/examples/concurrency-rr/run.js +0 -3
  129. package/v1/examples/concurrency_limit_rr/load.js +0 -1
  130. package/v1/examples/concurrency_limit_rr/run.js +0 -3
  131. package/v1/examples/concurrency_workflow_level/workflow.d.ts +1 -1
  132. package/v1/examples/concurrency_workflow_level/workflow.js +1 -1
  133. package/v1/examples/conditions/event.js +0 -1
  134. package/v1/examples/conditions/run.js +0 -1
  135. package/v1/examples/dag/run.js +0 -1
  136. package/v1/examples/dag_match_condition/event.js +0 -1
  137. package/v1/examples/dag_match_condition/run.js +0 -1
  138. package/v1/examples/deep/run.js +0 -2
  139. package/v1/examples/durable/workflow.d.ts +57 -0
  140. package/v1/examples/durable/workflow.js +164 -10
  141. package/v1/examples/durable-event/event.js +0 -1
  142. package/v1/examples/durable-event/run.js +0 -2
  143. package/v1/examples/durable-event/workflow.js +2 -7
  144. package/v1/examples/durable-sleep/event.js +0 -1
  145. package/v1/examples/durable-sleep/run.js +0 -2
  146. package/v1/examples/durable_event/event.js +0 -1
  147. package/v1/examples/durable_event/run.js +0 -2
  148. package/v1/examples/durable_event/workflow.d.ts +1 -0
  149. package/v1/examples/durable_event/workflow.js +4 -9
  150. package/v1/examples/durable_eviction/capacity-worker.d.ts +1 -0
  151. package/v1/examples/durable_eviction/capacity-worker.js +31 -0
  152. package/v1/examples/durable_eviction/worker.d.ts +1 -0
  153. package/v1/examples/durable_eviction/worker.js +34 -0
  154. package/v1/examples/durable_eviction/workflow.d.ts +44 -0
  155. package/v1/examples/durable_eviction/workflow.js +129 -0
  156. package/v1/examples/durable_sleep/event.js +0 -1
  157. package/v1/examples/durable_sleep/run.js +0 -2
  158. package/v1/examples/e2e-worker.js +42 -19
  159. package/v1/examples/events/event.js +0 -1
  160. package/v1/examples/high-memory/run.js +0 -1
  161. package/v1/examples/inferred-typing/run.js +0 -1
  162. package/v1/examples/landing_page/durable-excution.js +0 -1
  163. package/v1/examples/landing_page/queues.js +0 -1
  164. package/v1/examples/legacy/run.js +0 -1
  165. package/v1/examples/logger/byo-logger.js +0 -2
  166. package/v1/examples/logger/logger.js +0 -1
  167. package/v1/examples/logging/byo-logger.js +0 -2
  168. package/v1/examples/logging/logger.js +0 -1
  169. package/v1/examples/middleware/recipes.js +3 -1
  170. package/v1/examples/migration-guides/mergent.js +2 -1
  171. package/v1/examples/multiple_wf_concurrency/run.js +0 -3
  172. package/v1/examples/non_retryable/run.js +0 -1
  173. package/v1/examples/on_event/event.js +0 -1
  174. package/v1/examples/on_failure/run.js +0 -1
  175. package/v1/examples/on_failure/workflow.js +0 -1
  176. package/v1/examples/on_success/run.js +0 -1
  177. package/v1/examples/on_success/workflow.js +0 -1
  178. package/v1/examples/priority/run.js +0 -1
  179. package/v1/examples/priority/workflow.js +0 -1
  180. package/v1/examples/retries/run.js +0 -1
  181. package/v1/examples/retries/workflow.js +0 -1
  182. package/v1/examples/simple/bulk.js +0 -1
  183. package/v1/examples/simple/cron.js +0 -2
  184. package/v1/examples/simple/delay.js +0 -1
  185. package/v1/examples/simple/enqueue.js +0 -2
  186. package/v1/examples/simple/run.js +0 -1
  187. package/v1/examples/simple/schedule.js +0 -1
  188. package/v1/examples/simple/workflow-with-child.js +10 -4
  189. package/v1/examples/sticky/run.js +0 -1
  190. package/v1/examples/sticky/workflow.js +0 -1
  191. package/v1/examples/streaming/nextjs-proxy.js +0 -1
  192. package/v1/examples/streaming/run.js +0 -1
  193. package/v1/examples/timeout/run.js +0 -1
  194. package/v1/examples/timeouts/run.js +0 -1
  195. package/v1/index.d.ts +5 -0
  196. package/v1/index.js +10 -0
  197. package/v1/parent-run-context-vars.d.ts +6 -0
  198. package/v1/slot-types.js +0 -1
  199. package/v1/task.d.ts +10 -2
  200. package/v1/task.js +2 -1
  201. package/version.d.ts +1 -1
  202. package/version.js +1 -1
@@ -8,23 +8,29 @@
8
8
  * @module Context
9
9
  */
10
10
  import { Priority, RunOpts, TaskWorkflowDeclaration, BaseWorkflowDeclaration as WorkflowV1 } from '../../declaration';
11
- import { JsonObject } from '@bufbuild/protobuf';
12
11
  import { Action } from '../../../clients/dispatcher/action-listener';
13
12
  import { Logger, LogLevel } from '../../../util/logger';
14
13
  import WorkflowRunRef from '../../../util/workflow-run-ref';
15
14
  import { Conditions } from '../../conditions';
16
15
  import { CreateWorkflowDurableTaskOpts, CreateWorkflowTaskOpts } from '../../task';
17
- import { OutputType } from '../../types';
16
+ import { JsonObject, OutputType } from '../../types';
18
17
  import { HatchetClient } from '../..';
19
18
  import { WorkerLabels } from '../../../clients/dispatcher/dispatcher-client';
20
19
  import { NextStep } from '../../../legacy/step';
20
+ import { DurableListenerClient } from '../../../clients/listeners/durable-listener/durable-listener-client';
21
+ import { z } from 'zod';
21
22
  import { InternalWorker } from './worker-internal';
22
23
  import { Duration } from '../duration';
24
+ import { DurableEvictionManager } from './eviction/eviction-manager';
23
25
  type TriggerData = Record<string, Record<string, any>>;
24
26
  type ChildRunOpts = RunOpts & {
25
27
  key?: string;
26
28
  sticky?: boolean;
27
29
  };
30
+ export interface SleepResult {
31
+ /** The sleep duration in milliseconds. */
32
+ durationMs: number;
33
+ }
28
34
  type LogExtra = {
29
35
  extra?: any;
30
36
  error?: Error;
@@ -207,7 +213,30 @@ export declare class Context<T, K = {}> {
207
213
  * @returns A promise that resolves when the data has been streamed.
208
214
  */
209
215
  putStream(data: string | Uint8Array): Promise<void>;
210
- private spawnOptions;
216
+ protected spawnOptions(workflow: string | WorkflowV1<any, any>, options?: ChildRunOpts): {
217
+ workflowName: string;
218
+ opts: {
219
+ parentId: string;
220
+ parentTaskRunExternalId: string;
221
+ childIndex: number;
222
+ childKey: string | undefined;
223
+ desiredWorkerId: string | undefined;
224
+ _standaloneTaskName: string | undefined;
225
+ additionalMetadata?: {
226
+ [x: string]: string;
227
+ };
228
+ priority?: Priority;
229
+ sticky?: boolean;
230
+ returnExceptions?: boolean;
231
+ desiredWorkerLabels?: Record<string, {
232
+ value: string | number;
233
+ required?: boolean;
234
+ weight?: number;
235
+ comparator?: import("../..").WorkerLabelComparator;
236
+ }>;
237
+ key?: string;
238
+ };
239
+ };
211
240
  private spawn;
212
241
  private spawnBulk;
213
242
  /**
@@ -323,14 +352,26 @@ export declare class Context<T, K = {}> {
323
352
  * It extends the Context class and includes additional methods for durable execution like sleepFor and waitFor.
324
353
  */
325
354
  export declare class DurableContext<T, K = {}> extends Context<T, K> {
326
- waitKey: number;
355
+ private _durableListener;
356
+ private _evictionManager;
357
+ private _engineVersion;
358
+ private _waitKey;
359
+ constructor(action: Action, v1: HatchetClient, worker: InternalWorker, durableListener: DurableListenerClient, evictionManager?: DurableEvictionManager, engineVersion?: string);
360
+ get supportsEviction(): boolean;
361
+ get durableListener(): DurableListenerClient;
362
+ /**
363
+ * The invocation count for the current durable task. Used for deduplication across replays.
364
+ */
365
+ get invocationCount(): number;
366
+ private get _actionKey();
367
+ private withEvictionWait;
327
368
  /**
328
369
  * Pauses execution for the specified duration.
329
370
  * Duration is "global" meaning it will wait in real time regardless of transient failures like worker restarts.
330
371
  * @param duration - The duration to sleep for.
331
- * @returns A promise that resolves when the sleep duration has elapsed.
372
+ * @returns A promise that resolves with a SleepResult when the sleep duration has elapsed.
332
373
  */
333
- sleepFor(duration: Duration, readableDataKey?: string): Promise<Record<string, any>>;
374
+ sleepFor(duration: Duration, readableDataKey?: string): Promise<SleepResult>;
334
375
  /**
335
376
  * Pauses execution until the specified conditions are met.
336
377
  * Conditions are "global" meaning they will wait in real time regardless of transient failures like worker restarts.
@@ -338,5 +379,59 @@ export declare class DurableContext<T, K = {}> extends Context<T, K> {
338
379
  * @returns A promise that resolves with the event that satisfied the conditions.
339
380
  */
340
381
  waitFor(conditions: Conditions | Conditions[]): Promise<Record<string, any>>;
382
+ /**
383
+ * Lightweight wrapper for waiting for a user event. Allows for shorthand usage of
384
+ * `ctx.waitFor` when specifying a user event condition.
385
+ *
386
+ * For more complicated conditions, use `ctx.waitFor` directly.
387
+ *
388
+ * @param key - The event key to wait for.
389
+ * @param expression - An optional CEL expression to filter events.
390
+ * @param payloadSchema - An optional Zod schema to validate and parse the event payload.
391
+ * @returns The event payload, validated against the schema if provided.
392
+ */
393
+ waitForEvent<T extends z.ZodTypeAny>(key: string, expression?: string, payloadSchema?: T): Promise<z.infer<T>>;
394
+ waitForEvent(key: string, expression?: string): Promise<Record<string, any>>;
395
+ /**
396
+ * Durably sleep until a specific timestamp.
397
+ * Uses the memoized `now()` to compute the remaining duration, then delegates to `sleepFor`.
398
+ *
399
+ * @param wakeAt - The timestamp to sleep until.
400
+ * @returns A SleepResult containing the actual duration slept.
401
+ */
402
+ sleepUntil(wakeAt: Date): Promise<SleepResult>;
403
+ /**
404
+ * Get the current timestamp, memoized across replays. Returns the same Date on every replay of the same task run.
405
+ * @returns The memoized current timestamp.
406
+ */
407
+ now(): Promise<Date>;
408
+ private _waitForPreEviction;
409
+ private _buildTriggerOpts;
410
+ /**
411
+ * Spawns a child workflow through the durable event log, waits for the child to complete.
412
+ * @param workflow - The workflow to spawn.
413
+ * @param input - The input data for the child workflow.
414
+ * @param options - Options for spawning the child workflow.
415
+ * @returns The result of the child workflow.
416
+ */
417
+ spawnChild<Q extends JsonObject, P extends OutputType>(workflow: string | WorkflowV1<Q, P> | TaskWorkflowDeclaration<Q, P>, input?: Q, options?: ChildRunOpts): Promise<P>;
418
+ /**
419
+ * Spawns multiple child workflows through the durable event log, waits for all to complete.
420
+ * @param children - An array of objects containing the workflow, input, and options for each child.
421
+ * @returns A list of results from the child workflows.
422
+ */
423
+ spawnChildren<Q extends JsonObject, P extends OutputType>(children: Array<{
424
+ workflow: string | WorkflowV1<Q, P> | TaskWorkflowDeclaration<Q, P>;
425
+ input: Q;
426
+ options?: ChildRunOpts;
427
+ }>): Promise<P[]>;
428
+ /**
429
+ * Memoize a function by storing its result in durable storage. Avoids recomputation on replay.
430
+ *
431
+ * @param fn - The async function to compute the value.
432
+ * @param deps - Dependency values that form the memoization key.
433
+ * @returns The memoized value, either from durable storage or freshly computed.
434
+ */
435
+ private memo;
341
436
  }
342
437
  export {};
@@ -1,4 +1,13 @@
1
1
  "use strict";
2
+ /**
3
+ * The Hatchet Context class provides helper methods and useful data to tasks at runtime. It is passed as the second argument to all tasks and durable tasks.
4
+ *
5
+ * There are two types of context classes you'll encounter:
6
+ *
7
+ * - Context - The standard context for regular tasks with methods for logging, task output retrieval, cancellation, and more.
8
+ * - DurableContext - An extended context for durable tasks that includes additional methods for durable execution.
9
+ * @module Context
10
+ */
2
11
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
12
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
13
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -13,17 +22,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
13
22
  };
14
23
  Object.defineProperty(exports, "__esModule", { value: true });
15
24
  exports.DurableContext = exports.Context = exports.ContextWorker = void 0;
16
- /**
17
- * The Hatchet Context class provides helper methods and useful data to tasks at runtime. It is passed as the second argument to all tasks and durable tasks.
18
- *
19
- * There are two types of context classes you'll encounter:
20
- *
21
- * - Context - The standard context for regular tasks with methods for logging, task output retrieval, cancellation, and more.
22
- * - DurableContext - An extended context for durable tasks that includes additional methods for durable execution.
23
- * @module Context
24
- */
25
- /* eslint-disable no-underscore-dangle */
26
- /* eslint-disable max-classes-per-file */
27
25
  const declaration_1 = require("../../declaration");
28
26
  const hatchet_error_1 = __importDefault(require("../../../util/errors/hatchet-error"));
29
27
  const parse_1 = require("../../../util/parse");
@@ -32,6 +30,10 @@ const transformer_1 = require("../../conditions/transformer");
32
30
  const condition_1 = require("../../../protoc/v1/shared/condition");
33
31
  const apply_namespace_1 = require("../../../util/apply-namespace");
34
32
  const abort_error_1 = require("../../../util/abort-error");
33
+ const crypto_1 = require("crypto");
34
+ const duration_1 = require("../duration");
35
+ const engine_version_1 = require("./engine-version");
36
+ const pre_eviction_1 = require("./deprecated/pre-eviction");
35
37
  /**
36
38
  * ContextWorker is a wrapper around the V1Worker class that provides a more user-friendly interface for the worker from the context of a run.
37
39
  */
@@ -330,7 +332,7 @@ class Context {
330
332
  this._logger.warn('cannot refresh timeout from context without stepRunId');
331
333
  return;
332
334
  }
333
- yield this.v1.dispatcher.refreshTimeout(incrementBy, taskRunExternalId);
335
+ yield this.v1.dispatcher.refreshTimeout((0, duration_1.durationToString)(incrementBy), taskRunExternalId);
334
336
  });
335
337
  }
336
338
  /**
@@ -402,7 +404,6 @@ class Context {
402
404
  return __awaiter(this, void 0, void 0, function* () {
403
405
  const refs = yield this.spawnBulk(children);
404
406
  refs.forEach((ref) => {
405
- // eslint-disable-next-line no-param-reassign
406
407
  ref.defaultSignal = this.abortController.signal;
407
408
  });
408
409
  return refs;
@@ -431,7 +432,6 @@ class Context {
431
432
  return __awaiter(this, void 0, void 0, function* () {
432
433
  const run = yield this.spawn(workflow, input, options);
433
434
  // Ensure waiting for the child result aborts when this task is cancelled.
434
- // eslint-disable-next-line no-param-reassign
435
435
  run.defaultSignal = this.abortController.signal;
436
436
  return run.output;
437
437
  });
@@ -560,7 +560,6 @@ class Context {
560
560
  }
561
561
  // `signal` must never be sent over the wire.
562
562
  const optsWithoutSignal = Object.assign({}, opts);
563
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
564
563
  delete optsWithoutSignal.signal;
565
564
  const resp = {
566
565
  workflowName: name,
@@ -582,7 +581,6 @@ class Context {
582
581
  resp.forEach((ref, index) => {
583
582
  const wf = workflows[index].workflow;
584
583
  if (wf instanceof declaration_1.TaskWorkflowDeclaration) {
585
- // eslint-disable-next-line no-param-reassign
586
584
  ref._standaloneTaskName = wf._standalone_task_name;
587
585
  }
588
586
  res.push(ref);
@@ -608,13 +606,7 @@ class Context {
608
606
  return __awaiter(this, void 0, void 0, function* () {
609
607
  this.throwIfCancelled();
610
608
  const { workflowRunId, taskRunExternalId } = this.action;
611
- let workflowName = '';
612
- if (typeof workflow === 'string') {
613
- workflowName = workflow;
614
- }
615
- else {
616
- workflowName = workflow.name;
617
- }
609
+ const workflowName = typeof workflow === 'string' ? workflow : workflow.name;
618
610
  const name = (0, apply_namespace_1.applyNamespace)(workflowName, this.v1.config.namespace).toLowerCase();
619
611
  const opts = options || {};
620
612
  const { sticky } = opts;
@@ -646,19 +638,68 @@ exports.Context = Context;
646
638
  * It extends the Context class and includes additional methods for durable execution like sleepFor and waitFor.
647
639
  */
648
640
  class DurableContext extends Context {
649
- constructor() {
650
- super(...arguments);
651
- this.waitKey = 0;
641
+ constructor(action, v1, worker, durableListener, evictionManager, engineVersion) {
642
+ super(action, v1, worker);
643
+ this._waitKey = 0;
644
+ this._durableListener = durableListener;
645
+ this._evictionManager = evictionManager;
646
+ this._engineVersion = engineVersion;
647
+ }
648
+ get supportsEviction() {
649
+ return (0, engine_version_1.supportsEviction)(this._engineVersion);
650
+ }
651
+ get durableListener() {
652
+ return this._durableListener;
653
+ }
654
+ /**
655
+ * The invocation count for the current durable task. Used for deduplication across replays.
656
+ */
657
+ get invocationCount() {
658
+ var _a;
659
+ return (_a = this.action.durableTaskInvocationCount) !== null && _a !== void 0 ? _a : 1;
660
+ }
661
+ get _actionKey() {
662
+ return this.action.key;
663
+ }
664
+ withEvictionWait(waitKind, resourceId, fn) {
665
+ return __awaiter(this, void 0, void 0, function* () {
666
+ var _a, _b;
667
+ (_a = this._evictionManager) === null || _a === void 0 ? void 0 : _a.markWaiting(this._actionKey, waitKind, resourceId);
668
+ try {
669
+ return yield fn();
670
+ }
671
+ finally {
672
+ (_b = this._evictionManager) === null || _b === void 0 ? void 0 : _b.markActive(this._actionKey);
673
+ }
674
+ });
652
675
  }
653
676
  /**
654
677
  * Pauses execution for the specified duration.
655
678
  * Duration is "global" meaning it will wait in real time regardless of transient failures like worker restarts.
656
679
  * @param duration - The duration to sleep for.
657
- * @returns A promise that resolves when the sleep duration has elapsed.
680
+ * @returns A promise that resolves with a SleepResult when the sleep duration has elapsed.
658
681
  */
659
682
  sleepFor(duration, readableDataKey) {
660
683
  return __awaiter(this, void 0, void 0, function* () {
661
- return this.waitFor({ sleepFor: duration, readableDataKey });
684
+ const res = yield this.waitFor({ sleepFor: duration, readableDataKey });
685
+ const matches = res['CREATE'] || {};
686
+ const [firstMatch] = Object.values(matches);
687
+ if (!firstMatch || firstMatch.length === 0) {
688
+ return { durationMs: (0, duration_1.durationToMs)(duration) };
689
+ }
690
+ const [sleep] = firstMatch;
691
+ const sleepDuration = sleep === null || sleep === void 0 ? void 0 : sleep.sleep_duration;
692
+ if (sleepDuration) {
693
+ const DURATION_RE = /^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/;
694
+ const match = sleepDuration.match(DURATION_RE);
695
+ if (match) {
696
+ const [, h, m, s] = match;
697
+ const ms = (parseInt(h !== null && h !== void 0 ? h : '0', 10) * 3600 + parseInt(m !== null && m !== void 0 ? m : '0', 10) * 60 + parseInt(s !== null && s !== void 0 ? s : '0', 10)) *
698
+ 1000;
699
+ return { durationMs: ms };
700
+ }
701
+ }
702
+ return { durationMs: (0, duration_1.durationToMs)(duration) };
662
703
  });
663
704
  }
664
705
  /**
@@ -670,24 +711,196 @@ class DurableContext extends Context {
670
711
  waitFor(conditions) {
671
712
  return __awaiter(this, void 0, void 0, function* () {
672
713
  this.throwIfCancelled();
673
- const pbConditions = (0, transformer_1.conditionsToPb)((0, conditions_1.Render)(condition_1.Action.CREATE, conditions), this.v1.config.namespace);
674
- // eslint-disable-next-line no-plusplus
675
- const key = `waitFor-${this.waitKey++}`;
676
- yield this.v1.durableListener.registerDurableEvent({
677
- taskId: this.action.taskRunExternalId,
678
- signalKey: key,
679
- sleepConditions: pbConditions.sleepConditions,
680
- userEventConditions: pbConditions.userEventConditions,
714
+ if (!this.supportsEviction) {
715
+ return this._waitForPreEviction(conditions);
716
+ }
717
+ const rendered = (0, conditions_1.Render)(condition_1.Action.CREATE, conditions);
718
+ const pbConditions = (0, transformer_1.conditionsToPb)(rendered, this.v1.config.namespace);
719
+ const ack = yield this._durableListener.sendEvent(this.action.taskRunExternalId, this.invocationCount, {
720
+ kind: 'waitFor',
721
+ waitForConditions: {
722
+ sleepConditions: pbConditions.sleepConditions,
723
+ userEventConditions: pbConditions.userEventConditions,
724
+ },
681
725
  });
682
- const event = yield this.v1.durableListener.result({
683
- taskId: this.action.taskRunExternalId,
684
- signalKey: key,
685
- }, { signal: this.abortController.signal });
686
- // Convert event.data from Uint8Array to string if needed
687
- const eventData = event.data instanceof Uint8Array ? new TextDecoder().decode(event.data) : event.data;
688
- const res = JSON.parse(eventData);
689
- return res.CREATE;
726
+ const resourceId = rendered
727
+ .map((c) => c.base.readableDataKey)
728
+ .filter(Boolean)
729
+ .join(',') || `node:${ack.nodeId}`;
730
+ return this.withEvictionWait('waitFor', resourceId, () => __awaiter(this, void 0, void 0, function* () {
731
+ const result = yield this._durableListener.waitForCallback(this.action.taskRunExternalId, this.invocationCount, ack.branchId, ack.nodeId, { signal: this.abortController.signal });
732
+ return result.payload || {};
733
+ }));
734
+ });
735
+ }
736
+ waitForEvent(key, expression, payloadSchema) {
737
+ return __awaiter(this, void 0, void 0, function* () {
738
+ const res = yield this.waitFor({ eventKey: key, expression });
739
+ // The engine returns an object like:
740
+ // {"CREATE": {"signal_key_1": [{"id": ..., "data": {...}}]}}
741
+ // Since we have a single match, the list will only have one item.
742
+ const matches = res['CREATE'] || {};
743
+ const [firstMatch] = Object.values(matches);
744
+ if (!firstMatch || firstMatch.length === 0) {
745
+ if (payloadSchema) {
746
+ return payloadSchema.parse({});
747
+ }
748
+ return {};
749
+ }
750
+ const [rawPayload] = firstMatch;
751
+ if (payloadSchema) {
752
+ return payloadSchema.parse(rawPayload);
753
+ }
754
+ return rawPayload;
755
+ });
756
+ }
757
+ /**
758
+ * Durably sleep until a specific timestamp.
759
+ * Uses the memoized `now()` to compute the remaining duration, then delegates to `sleepFor`.
760
+ *
761
+ * @param wakeAt - The timestamp to sleep until.
762
+ * @returns A SleepResult containing the actual duration slept.
763
+ */
764
+ sleepUntil(wakeAt) {
765
+ return __awaiter(this, void 0, void 0, function* () {
766
+ const now = yield this.now();
767
+ const remainingMs = wakeAt.getTime() - now.getTime();
768
+ return this.sleepFor(`${Math.max(0, Math.ceil(remainingMs / 1000))}s`);
769
+ });
770
+ }
771
+ /**
772
+ * Get the current timestamp, memoized across replays. Returns the same Date on every replay of the same task run.
773
+ * @returns The memoized current timestamp.
774
+ */
775
+ now() {
776
+ return __awaiter(this, void 0, void 0, function* () {
777
+ const result = yield this.memo(() => __awaiter(this, void 0, void 0, function* () {
778
+ return { ts: new Date().toISOString() };
779
+ }), ['now']);
780
+ return new Date(result.ts);
781
+ });
782
+ }
783
+ _waitForPreEviction(conditions) {
784
+ return __awaiter(this, void 0, void 0, function* () {
785
+ const { result, nextWaitKey } = yield (0, pre_eviction_1.waitForPreEviction)(this._durableListener, this.action.taskRunExternalId, this._waitKey, conditions, this.v1.config.namespace, this.abortController.signal);
786
+ this._waitKey = nextWaitKey;
787
+ return result;
788
+ });
789
+ }
790
+ _buildTriggerOpts(workflow, input, options) {
791
+ let workflowName;
792
+ if (typeof workflow === 'string') {
793
+ workflowName = workflow;
794
+ }
795
+ else {
796
+ workflowName = workflow.name;
797
+ }
798
+ workflowName = (0, apply_namespace_1.applyNamespace)(workflowName, this.v1.config.namespace).toLowerCase();
799
+ const triggerOpts = {
800
+ name: workflowName,
801
+ input: JSON.stringify(input || {}),
802
+ parentId: this.action.workflowRunId,
803
+ parentTaskRunExternalId: this.action.taskRunExternalId,
804
+ childIndex: this.spawnIndex,
805
+ childKey: options === null || options === void 0 ? void 0 : options.key,
806
+ additionalMetadata: (options === null || options === void 0 ? void 0 : options.additionalMetadata)
807
+ ? JSON.stringify(options.additionalMetadata)
808
+ : undefined,
809
+ desiredWorkerId: (options === null || options === void 0 ? void 0 : options.sticky) ? this.worker.id() : undefined,
810
+ priority: options === null || options === void 0 ? void 0 : options.priority,
811
+ desiredWorkerLabels: {},
812
+ };
813
+ this.spawnIndex += 1;
814
+ return { workflowName, triggerOpts };
815
+ }
816
+ /**
817
+ * Spawns a child workflow through the durable event log, waits for the child to complete.
818
+ * @param workflow - The workflow to spawn.
819
+ * @param input - The input data for the child workflow.
820
+ * @param options - Options for spawning the child workflow.
821
+ * @returns The result of the child workflow.
822
+ */
823
+ spawnChild(workflow, input, options) {
824
+ return __awaiter(this, void 0, void 0, function* () {
825
+ if (!this.supportsEviction) {
826
+ const { workflowName, opts } = this.spawnOptions(workflow, options);
827
+ const ref = yield this.v1.admin.runWorkflow(workflowName, (input || {}), opts);
828
+ ref.defaultSignal = this.abortController.signal;
829
+ return ref.output;
830
+ }
831
+ const results = yield this.spawnChildren([
832
+ { workflow, input: (input || {}), options },
833
+ ]);
834
+ return results[0];
835
+ });
836
+ }
837
+ /**
838
+ * Spawns multiple child workflows through the durable event log, waits for all to complete.
839
+ * @param children - An array of objects containing the workflow, input, and options for each child.
840
+ * @returns A list of results from the child workflows.
841
+ */
842
+ spawnChildren(children) {
843
+ return __awaiter(this, void 0, void 0, function* () {
844
+ this.throwIfCancelled();
845
+ if (!this.supportsEviction) {
846
+ const workflows = children.map((c) => {
847
+ const { workflowName, opts } = this.spawnOptions(c.workflow, c.options);
848
+ return { workflowName, input: c.input, options: opts };
849
+ });
850
+ const refs = yield this.v1.admin.runWorkflows(workflows);
851
+ for (const r of refs) {
852
+ r.defaultSignal = this.abortController.signal;
853
+ }
854
+ return Promise.all(refs.map((r) => r.output));
855
+ }
856
+ const triggerOptsList = children.map((child) => {
857
+ const { triggerOpts } = this._buildTriggerOpts(child.workflow, child.input, child.options);
858
+ return triggerOpts;
859
+ });
860
+ const ack = yield this._durableListener.sendEvent(this.action.taskRunExternalId, this.invocationCount, {
861
+ kind: 'runChildren',
862
+ triggerOpts: triggerOptsList,
863
+ });
864
+ const results = yield Promise.all(ack.runEntries.map((entry) => this.withEvictionWait('runChild', `workflow:bulk-child`, () => __awaiter(this, void 0, void 0, function* () {
865
+ const result = yield this._durableListener.waitForCallback(this.action.taskRunExternalId, this.invocationCount, entry.branchId, entry.nodeId, { signal: this.abortController.signal });
866
+ return (result.payload || {});
867
+ }))));
868
+ return results;
869
+ });
870
+ }
871
+ /**
872
+ * Memoize a function by storing its result in durable storage. Avoids recomputation on replay.
873
+ *
874
+ * @param fn - The async function to compute the value.
875
+ * @param deps - Dependency values that form the memoization key.
876
+ * @returns The memoized value, either from durable storage or freshly computed.
877
+ */
878
+ memo(fn, deps) {
879
+ return __awaiter(this, void 0, void 0, function* () {
880
+ this.throwIfCancelled();
881
+ if (!this.supportsEviction) {
882
+ return fn();
883
+ }
884
+ const memoKey = computeMemoKey(this.action.taskRunExternalId, deps);
885
+ const ack = yield this._durableListener.sendEvent(this.action.taskRunExternalId, this.invocationCount, {
886
+ kind: 'memo',
887
+ memoKey,
888
+ });
889
+ if (ack.memoAlreadyExisted && ack.memoResultPayload && ack.memoResultPayload.length > 0) {
890
+ const serialized = new TextDecoder().decode(ack.memoResultPayload);
891
+ return JSON.parse(serialized);
892
+ }
893
+ const result = yield fn();
894
+ const serializedResult = new TextEncoder().encode(JSON.stringify(result));
895
+ yield this._durableListener.sendMemoCompletedNotification(this.action.taskRunExternalId, ack.nodeId, ack.branchId, this.invocationCount, memoKey, serializedResult);
896
+ return result;
690
897
  });
691
898
  }
692
899
  }
693
900
  exports.DurableContext = DurableContext;
901
+ function computeMemoKey(taskRunExternalId, args) {
902
+ const h = (0, crypto_1.createHash)('sha256');
903
+ h.update(taskRunExternalId);
904
+ h.update(JSON.stringify(args));
905
+ return new Uint8Array(h.digest());
906
+ }
@@ -43,11 +43,13 @@ exports.DeprecationError = DeprecationError;
43
43
  function parseSemver(v) {
44
44
  let s = v.startsWith('v') ? v.slice(1) : v;
45
45
  const dashIdx = s.indexOf('-');
46
- if (dashIdx !== -1)
46
+ if (dashIdx !== -1) {
47
47
  s = s.slice(0, dashIdx);
48
+ }
48
49
  const parts = s.split('.');
49
- if (parts.length !== 3)
50
+ if (parts.length !== 3) {
50
51
  return [0, 0, 0];
52
+ }
51
53
  return [parseInt(parts[0], 10) || 0, parseInt(parts[1], 10) || 0, parseInt(parts[2], 10) || 0];
52
54
  }
53
55
  /**
@@ -56,10 +58,12 @@ function parseSemver(v) {
56
58
  function semverLessThan(a, b) {
57
59
  const [aMaj, aMin, aPat] = parseSemver(a);
58
60
  const [bMaj, bMin, bPat] = parseSemver(b);
59
- if (aMaj !== bMaj)
61
+ if (aMaj !== bMaj) {
60
62
  return aMaj < bMaj;
61
- if (aMin !== bMin)
63
+ }
64
+ if (aMin !== bMin) {
62
65
  return aMin < bMin;
66
+ }
63
67
  return aPat < bPat;
64
68
  }
65
69
  function emitDeprecationNotice(feature, message, start, logger, opts) {
@@ -1,4 +1,4 @@
1
- export { isLegacyEngine, LegacyDualWorker } from './legacy-worker';
1
+ export { isLegacyEngine, fetchEngineVersion, LegacyDualWorker } from './legacy-worker';
2
2
  export { LegacyV1Worker } from './legacy-v1-worker';
3
3
  export { legacyGetActionListener } from './legacy-registration';
4
4
  export { emitDeprecationNotice, DeprecationError, parseSemver, semverLessThan, } from './deprecation';
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.semverLessThan = exports.parseSemver = exports.DeprecationError = exports.emitDeprecationNotice = exports.legacyGetActionListener = exports.LegacyV1Worker = exports.LegacyDualWorker = exports.isLegacyEngine = void 0;
3
+ exports.semverLessThan = exports.parseSemver = exports.DeprecationError = exports.emitDeprecationNotice = exports.legacyGetActionListener = exports.LegacyV1Worker = exports.LegacyDualWorker = exports.fetchEngineVersion = exports.isLegacyEngine = void 0;
4
4
  var legacy_worker_1 = require("./legacy-worker");
5
5
  Object.defineProperty(exports, "isLegacyEngine", { enumerable: true, get: function () { return legacy_worker_1.isLegacyEngine; } });
6
+ Object.defineProperty(exports, "fetchEngineVersion", { enumerable: true, get: function () { return legacy_worker_1.fetchEngineVersion; } });
6
7
  Object.defineProperty(exports, "LegacyDualWorker", { enumerable: true, get: function () { return legacy_worker_1.LegacyDualWorker; } });
7
8
  var legacy_v1_worker_1 = require("./legacy-v1-worker");
8
9
  Object.defineProperty(exports, "LegacyV1Worker", { enumerable: true, get: function () { return legacy_v1_worker_1.LegacyV1Worker; } });
@@ -8,6 +8,11 @@
8
8
  import { HatchetClient } from '../../..';
9
9
  import { CreateWorkerOpts } from '../worker';
10
10
  import { LegacyV1Worker } from './legacy-v1-worker';
11
+ /**
12
+ * Fetches the engine version from the dispatcher.
13
+ * Returns the semver string, or undefined if the engine is too old to support GetVersion.
14
+ */
15
+ export declare function fetchEngineVersion(v1: HatchetClient): Promise<string | undefined>;
11
16
  /**
12
17
  * Checks if the connected engine is legacy by comparing its semantic version
13
18
  * against the minimum required version for slot_config support.