@hatchet-dev/typescript-sdk 1.31.0 → 1.32.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 (46) hide show
  1. package/README.md +46 -0
  2. package/clients/dispatcher/action-listener.d.ts +3 -8
  3. package/clients/dispatcher/action-listener.js +7 -25
  4. package/clients/dispatcher/action.d.ts +19 -0
  5. package/clients/dispatcher/action.js +35 -0
  6. package/clients/dispatcher/dispatcher-client.d.ts +2 -2
  7. package/clients/dispatcher/heartbeat/heartbeat-controller.d.ts +1 -1
  8. package/clients/listeners/durable-listener/durable-events.d.ts +56 -0
  9. package/clients/listeners/durable-listener/durable-events.js +2 -0
  10. package/clients/listeners/durable-listener/durable-listener-client.d.ts +3 -52
  11. package/clients/listeners/durable-listener/durable-listener-client.js +3 -2
  12. package/clients/listeners/durable-listener/pooled-durable-listener-client.js +2 -1
  13. package/clients/listeners/run-listener/pooled-child-listener-client.js +2 -1
  14. package/clients/rest/generated/Api.d.ts +1 -62
  15. package/clients/rest/generated/Api.js +0 -50
  16. package/clients/rest/generated/data-contracts.d.ts +0 -52
  17. package/dist/check-edge-entry.mjs +84 -0
  18. package/edge/declarations.d.ts +49 -0
  19. package/edge/declarations.js +21 -0
  20. package/edge/index.d.ts +49 -0
  21. package/edge/index.js +115 -0
  22. package/package.json +21 -16
  23. package/scripts/check-edge-entry.mjs +84 -0
  24. package/util/abort-error.d.ts +0 -10
  25. package/util/abort-error.js +0 -15
  26. package/util/abort-signal.d.ts +12 -0
  27. package/util/abort-signal.js +19 -0
  28. package/util/logger/logger.d.ts +1 -1
  29. package/v1/client/worker/context.d.ts +58 -12
  30. package/v1/client/worker/context.js +79 -51
  31. package/v1/client/worker/deprecated/pre-eviction.d.ts +5 -2
  32. package/v1/client/worker/deprecated/pre-eviction.js +5 -0
  33. package/v1/client/worker/runtime.d.ts +118 -0
  34. package/v1/client/worker/runtime.js +9 -0
  35. package/v1/client/worker/worker-internal.d.ts +3 -39
  36. package/v1/client/worker/worker-internal.js +22 -393
  37. package/v1/client/worker/worker-runtime.d.ts +8 -0
  38. package/v1/client/worker/worker-runtime.js +67 -0
  39. package/v1/client/worker/workflow-proto.d.ts +76 -0
  40. package/v1/client/worker/workflow-proto.js +482 -0
  41. package/v1/parent-run-context-storage.d.ts +11 -0
  42. package/v1/parent-run-context-storage.js +28 -0
  43. package/v1/parent-run-context-vars.d.ts +15 -1
  44. package/v1/parent-run-context-vars.js +13 -4
  45. package/version.d.ts +1 -1
  46. package/version.js +1 -1
@@ -8,20 +8,20 @@
8
8
  * @module Context
9
9
  */
10
10
  import { Priority, RunOpts, TaskWorkflowDeclaration, BaseWorkflowDeclaration as WorkflowV1 } from '../../declaration';
11
- import type { Action } from '../../../clients/dispatcher/action-listener';
11
+ import type { Action } from '../../../clients/dispatcher/action';
12
12
  import { Logger, LogLevel } from '../../../util/logger';
13
13
  import WorkflowRunRef from '../../../util/workflow-run-ref';
14
14
  import { Conditions } from '../../conditions';
15
15
  import { CreateWorkflowDurableTaskOpts, CreateWorkflowTaskOpts } from '../../task';
16
16
  import { JsonObject, OutputType } from '../../types';
17
- import { HatchetClient } from '../..';
18
- import { WorkerLabels } from '../../../clients/dispatcher/dispatcher-client';
19
- import { NextStep } from '../../../legacy/step';
20
- import { DurableListenerClient } from '../../../clients/listeners/durable-listener/durable-listener-client';
17
+ import type { HatchetClient } from '../client';
18
+ import type { NextStep } from '../../../legacy/step';
19
+ import type { DurableListenerClient } from '../../../clients/listeners/durable-listener/durable-listener-client';
21
20
  import { z } from 'zod/v4';
22
- import { InternalWorker } from './worker-internal';
21
+ import type { InternalWorker } from './worker-internal';
23
22
  import { Duration } from '../duration';
24
- import { DurableEvictionManager } from './eviction/eviction-manager';
23
+ import type { DurableEvictionManager } from './eviction/eviction-manager';
24
+ import { ContextRuntime, DurableContextOptions, DurableTransport, WorkerLabels } from './runtime';
25
25
  type TriggerData = Record<string, Record<string, any>>;
26
26
  type ChildRunOpts = RunOpts & {
27
27
  key?: string;
@@ -50,11 +50,11 @@ interface ContextData<T, K> {
50
50
  step_run_errors: Record<string, string>;
51
51
  }
52
52
  /**
53
- * ContextWorker is a wrapper around the V1Worker class that provides a more user-friendly interface for the worker from the context of a run.
53
+ * ContextWorker is a user-friendly view of the worker running the task, from the context of a run.
54
54
  */
55
55
  export declare class ContextWorker {
56
- private worker;
57
- constructor(worker: InternalWorker);
56
+ private runtime;
57
+ constructor(runtime: ContextRuntime);
58
58
  /**
59
59
  * Gets the ID of the worker.
60
60
  * @returns The ID of the worker.
@@ -83,7 +83,14 @@ export declare class Context<T, K = {}> {
83
83
  input: T;
84
84
  controller: AbortController;
85
85
  action: Action;
86
+ /**
87
+ * The Hatchet client of the worker running the task. Only set when the context was
88
+ * created by a worker; a runtime without a client (see {@link ContextRuntime}) leaves
89
+ * it undefined.
90
+ */
86
91
  v1: HatchetClient;
92
+ /** The runtime the context performs engine-facing operations through. */
93
+ runtime: ContextRuntime;
87
94
  worker: ContextWorker;
88
95
  overridesData: Record<string, any>;
89
96
  _logger: Logger;
@@ -91,6 +98,18 @@ export declare class Context<T, K = {}> {
91
98
  spawnIndex: number;
92
99
  streamIndex: number;
93
100
  protected nextChildIndex(n?: number): number;
101
+ /**
102
+ * Creates a context on top of a runtime.
103
+ * @param action - The action assigned to the task.
104
+ * @param runtime - The runtime the context performs engine-facing operations through.
105
+ */
106
+ constructor(action: Action, runtime: ContextRuntime);
107
+ /**
108
+ * Creates a context for a task running on a worker.
109
+ * @param action - The action assigned to the task.
110
+ * @param v1 - The worker's Hatchet client.
111
+ * @param worker - The worker running the task.
112
+ */
94
113
  constructor(action: Action, v1: HatchetClient, worker: InternalWorker);
95
114
  get abortController(): AbortController;
96
115
  get cancelled(): boolean;
@@ -247,7 +266,7 @@ export declare class Context<T, K = {}> {
247
266
  value: string | number;
248
267
  required?: boolean;
249
268
  weight?: number;
250
- comparator?: import("../..").WorkerLabelComparator;
269
+ comparator?: import("../../task").WorkerLabelComparator;
251
270
  }>;
252
271
  key?: string;
253
272
  };
@@ -383,9 +402,29 @@ export declare class DurableContext<T, K = {}> extends Context<T, K> {
383
402
  private _waitKey;
384
403
  private _sendEventLock;
385
404
  private _serializeSendEvent;
405
+ /**
406
+ * Creates a durable context on top of a runtime and a durable transport.
407
+ * @param action - The action assigned to the task.
408
+ * @param runtime - The runtime the context performs engine-facing operations through.
409
+ * @param transport - The transport durable events travel over.
410
+ * @param options - Engine version and eviction settings.
411
+ */
412
+ constructor(action: Action, runtime: ContextRuntime, transport: DurableTransport, options?: DurableContextOptions & {
413
+ evictionManager?: DurableEvictionManager;
414
+ });
415
+ /**
416
+ * Creates a durable context for a task running on a worker.
417
+ * @param action - The action assigned to the task.
418
+ * @param v1 - The worker's Hatchet client.
419
+ * @param worker - The worker running the task.
420
+ * @param durableListener - The worker's durable listener.
421
+ * @param evictionManager - The worker's eviction manager, when the engine supports eviction.
422
+ * @param engineVersion - The engine version the worker is connected to.
423
+ */
386
424
  constructor(action: Action, v1: HatchetClient, worker: InternalWorker, durableListener: DurableListenerClient, evictionManager?: DurableEvictionManager, engineVersion?: string);
387
425
  get supportsEviction(): boolean;
388
- get durableListener(): DurableListenerClient;
426
+ /** The transport durable events travel over. */
427
+ get durableListener(): DurableTransport;
389
428
  /**
390
429
  * The invocation count for the current durable task. Used for deduplication across replays.
391
430
  */
@@ -465,4 +504,11 @@ export declare class DurableContext<T, K = {}> extends Context<T, K> {
465
504
  */
466
505
  private memo;
467
506
  }
507
+ /**
508
+ * Derives the memo key for a task run and its dependency values: the SHA-256 of the task
509
+ * run id followed by the JSON-serialised dependencies, over WebCrypto so it works in
510
+ * every runtime. The bytes are identical to the previous Node `createHash` version, so
511
+ * event logs recorded by older SDKs keep replaying.
512
+ */
513
+ export declare function computeMemoKey(taskRunExternalId: string, args: readonly unknown[]): Promise<Uint8Array>;
468
514
  export {};
@@ -22,34 +22,35 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
22
22
  };
23
23
  Object.defineProperty(exports, "__esModule", { value: true });
24
24
  exports.DurableContext = exports.Context = exports.ContextWorker = void 0;
25
+ exports.computeMemoKey = computeMemoKey;
25
26
  const declaration_1 = require("../../declaration");
26
27
  const hatchet_error_1 = __importDefault(require("../../../util/errors/hatchet-error"));
27
- const action_listener_1 = require("../../../clients/dispatcher/action-listener");
28
+ const action_1 = require("../../../clients/dispatcher/action");
28
29
  const parse_1 = require("../../../util/parse");
29
30
  const conditions_1 = require("../../conditions");
30
31
  const transformer_1 = require("../../conditions/transformer");
31
32
  const condition_1 = require("../../../protoc/v1/shared/condition");
32
- const dispatcher_1 = require("../../../protoc/dispatcher");
33
33
  const apply_namespace_1 = require("../../../util/apply-namespace");
34
34
  const abort_error_1 = require("../../../util/abort-error");
35
35
  const parent_run_context_vars_1 = require("../../parent-run-context-vars");
36
- const crypto_1 = require("crypto");
37
36
  const duration_1 = require("../duration");
38
37
  const engine_version_1 = require("./engine-version");
39
38
  const pre_eviction_1 = require("./deprecated/pre-eviction");
39
+ const runtime_1 = require("./runtime");
40
+ const worker_runtime_1 = require("./worker-runtime");
40
41
  /**
41
- * ContextWorker is a wrapper around the V1Worker class that provides a more user-friendly interface for the worker from the context of a run.
42
+ * ContextWorker is a user-friendly view of the worker running the task, from the context of a run.
42
43
  */
43
44
  class ContextWorker {
44
- constructor(worker) {
45
- this.worker = worker;
45
+ constructor(runtime) {
46
+ this.runtime = runtime;
46
47
  }
47
48
  /**
48
49
  * Gets the ID of the worker.
49
50
  * @returns The ID of the worker.
50
51
  */
51
52
  id() {
52
- return this.worker.workerId;
53
+ return this.runtime.workerId();
53
54
  }
54
55
  /**
55
56
  * Checks if the worker has a registered workflow.
@@ -57,14 +58,14 @@ class ContextWorker {
57
58
  * @returns True if the workflow is registered, otherwise false.
58
59
  */
59
60
  hasWorkflow(workflowName) {
60
- return !!this.worker.workflow_registry.find((workflow) => 'id' in workflow ? workflow.id === workflowName : workflow.name === workflowName);
61
+ return this.runtime.hasWorkflow(workflowName);
61
62
  }
62
63
  /**
63
64
  * Gets the current state of the worker labels.
64
65
  * @returns The labels of the worker.
65
66
  */
66
67
  labels() {
67
- return this.worker.labels;
68
+ return this.runtime.workerLabels();
68
69
  }
69
70
  /**
70
71
  * Upserts the a set of labels on the worker.
@@ -72,10 +73,16 @@ class ContextWorker {
72
73
  * @returns A promise that resolves when the labels have been upserted.
73
74
  */
74
75
  upsertLabels(labels) {
75
- return this.worker.upsertLabels(labels);
76
+ return this.runtime.upsertWorkerLabels(labels);
76
77
  }
77
78
  }
78
79
  exports.ContextWorker = ContextWorker;
80
+ function resolveRuntime(runtimeOrClient, worker) {
81
+ if ((0, runtime_1.isContextRuntime)(runtimeOrClient)) {
82
+ return runtimeOrClient;
83
+ }
84
+ return (0, worker_runtime_1.createWorkerContextRuntime)(runtimeOrClient, worker);
85
+ }
79
86
  class Context {
80
87
  nextChildIndex(n = 1) {
81
88
  var _a;
@@ -85,7 +92,7 @@ class Context {
85
92
  this.spawnIndex = idx + n;
86
93
  return idx;
87
94
  }
88
- constructor(action, v1, worker) {
95
+ constructor(action, runtimeOrClient, worker) {
89
96
  // @deprecated use ctx.abortController instead
90
97
  this.controller = new AbortController();
91
98
  this.overridesData = {};
@@ -93,12 +100,14 @@ class Context {
93
100
  this.spawnIndex = 0;
94
101
  this.streamIndex = 0;
95
102
  try {
103
+ const runtime = resolveRuntime(runtimeOrClient, worker);
96
104
  const data = (0, parse_1.parseJSON)(action.actionPayload);
97
105
  this.data = data;
98
106
  this.action = action;
99
- this.v1 = v1;
100
- this.worker = new ContextWorker(worker);
101
- this._logger = v1.config.logger(`Context Logger`, v1.config.log_level);
107
+ this.runtime = runtime;
108
+ this.v1 = runtime.client;
109
+ this.worker = new ContextWorker(runtime);
110
+ this._logger = runtime.logger(`Context Logger`);
102
111
  // if this is a getGroupKeyRunId, the data is the workflow input
103
112
  if (action.getGroupKeyRunId !== '') {
104
113
  this.input = data;
@@ -143,20 +152,16 @@ class Context {
143
152
  // the raw batch-items map for a START_BATCH action, keyed by each member's
144
153
  // task-run external id.
145
154
  const memberIds = Object.keys((_a = this.data) !== null && _a !== void 0 ? _a : {});
146
- yield this.v1.dispatcher.sendBatchActionEvent({
155
+ yield this.runtime.cancelBatch({
147
156
  workerId: (_b = this.worker.id()) !== null && _b !== void 0 ? _b : '',
148
157
  jobId: this.action.jobId,
149
158
  actionId: this.action.actionId,
150
159
  batchId: this.action.batchId,
151
- eventTimestamp: new Date(),
152
- eventType: dispatcher_1.StepActionEventType.STEP_EVENT_TYPE_CANCELLED,
153
- items: memberIds.map((id) => ({ taskRunExternalId: id, eventPayload: '' })),
160
+ memberIds,
154
161
  });
155
162
  }
156
163
  else {
157
- yield this.v1.runs.cancel({
158
- ids: [this.action.taskRunExternalId],
159
- });
164
+ yield this.runtime.cancelRun(this.action.taskRunExternalId);
160
165
  }
161
166
  // optimistically abort the run
162
167
  this.controller.abort();
@@ -242,7 +247,7 @@ class Context {
242
247
  * @returns The name of the workflow.
243
248
  */
244
249
  workflowNameV1() {
245
- return (0, action_listener_1.workflowNameFromAction)(this.action);
250
+ return (0, action_1.workflowNameFromAction)(this.action);
246
251
  }
247
252
  /**
248
253
  * Gets the user data associated with the workflow.
@@ -317,7 +322,7 @@ class Context {
317
322
  this._logger.warn('cannot log from context without stepRunId');
318
323
  return Promise.resolve();
319
324
  }
320
- const logger = this.v1.config.logger('ctx', this.v1.config.log_level);
325
+ const logger = this.runtime.logger('ctx');
321
326
  const contextExtra = Object.assign({ workflowRunId: this.action.workflowRunId, taskRunExternalId: this.action.taskRunExternalId, retryCount: this.action.retryCount, workflowName: this.workflowNameV1() }, extra === null || extra === void 0 ? void 0 : extra.extra);
322
327
  const promises = [];
323
328
  if (!level || level === 'INFO') {
@@ -333,7 +338,7 @@ class Context {
333
338
  promises.push(logger.error(message, extra === null || extra === void 0 ? void 0 : extra.error, contextExtra));
334
339
  }
335
340
  // FIXME: this is a hack to get around the fact that the log level is not typed
336
- promises.push(this.v1.event.putLog(taskRunExternalId, message, level, this.retryCount(), extra === null || extra === void 0 ? void 0 : extra.extra));
341
+ promises.push(this.runtime.putLog(taskRunExternalId, message, level, this.retryCount(), extra === null || extra === void 0 ? void 0 : extra.extra));
337
342
  return Promise.all(promises);
338
343
  }
339
344
  get logger() {
@@ -351,7 +356,7 @@ class Context {
351
356
  return this.log(message, 'ERROR', extra);
352
357
  },
353
358
  util: (key, message, extra) => {
354
- const logger = this.v1.config.logger('ctx', this.v1.config.log_level);
359
+ const logger = this.runtime.logger('ctx');
355
360
  if (!logger.util) {
356
361
  return Promise.resolve();
357
362
  }
@@ -372,7 +377,7 @@ class Context {
372
377
  this._logger.warn('cannot refresh timeout from context without stepRunId');
373
378
  return;
374
379
  }
375
- yield this.v1.dispatcher.refreshTimeout((0, duration_1.durationToString)(incrementBy), taskRunExternalId);
380
+ yield this.runtime.refreshTimeout(taskRunExternalId, (0, duration_1.durationToString)(incrementBy));
376
381
  });
377
382
  }
378
383
  /**
@@ -382,9 +387,7 @@ class Context {
382
387
  */
383
388
  releaseSlot() {
384
389
  return __awaiter(this, void 0, void 0, function* () {
385
- yield this.v1.dispatcher.client.releaseSlot({
386
- taskRunExternalId: this.action.taskRunExternalId,
387
- });
390
+ yield this.runtime.releaseSlot(this.action.taskRunExternalId);
388
391
  });
389
392
  }
390
393
  /**
@@ -401,7 +404,7 @@ class Context {
401
404
  return;
402
405
  }
403
406
  const index = this._incrementStreamIndex();
404
- yield this.v1.events.putStream(taskRunExternalId, data, index);
407
+ yield this.runtime.putStream(taskRunExternalId, data, index);
405
408
  });
406
409
  }
407
410
  spawnOptions(workflow, options) {
@@ -425,7 +428,7 @@ class Context {
425
428
  }
426
429
  spawn(workflow, input, options) {
427
430
  const { workflowName, opts } = this.spawnOptions(workflow, options);
428
- return this.v1.admin.runWorkflow(workflowName, input, opts);
431
+ return this.runtime.runWorkflow(workflowName, input, opts);
429
432
  }
430
433
  spawnBulk(children) {
431
434
  this.throwIfCancelled();
@@ -433,7 +436,7 @@ class Context {
433
436
  const { workflowName, opts } = this.spawnOptions(child.workflow, child.options);
434
437
  return { workflowName, input: child.input, options: opts };
435
438
  });
436
- return this.v1.admin.runWorkflows(workflows);
439
+ return this.runtime.runWorkflows(workflows);
437
440
  }
438
441
  /**
439
442
  * Runs multiple children workflows in parallel without waiting for their results.
@@ -606,7 +609,7 @@ class Context {
606
609
  else {
607
610
  workflowName = workflow.name;
608
611
  }
609
- const name = (0, apply_namespace_1.applyNamespace)(workflowName, this.v1.config.namespace).toLowerCase();
612
+ const name = (0, apply_namespace_1.applyNamespace)(workflowName, this.runtime.namespace).toLowerCase();
610
613
  const opts = options || {};
611
614
  const { sticky } = opts;
612
615
  if (sticky && !this.worker.hasWorkflow(name)) {
@@ -628,7 +631,7 @@ class Context {
628
631
  let resp = [];
629
632
  for (let i = 0; i < workflowRuns.length; i += batchSize) {
630
633
  const batch = workflowRuns.slice(i, i + batchSize);
631
- const batchResp = yield this.v1.admin.runWorkflows(batch);
634
+ const batchResp = yield this.runtime.runWorkflows(batch);
632
635
  resp = resp.concat(batchResp);
633
636
  }
634
637
  const res = [];
@@ -661,7 +664,7 @@ class Context {
661
664
  this.throwIfCancelled();
662
665
  const { workflowRunId, taskRunExternalId } = this.action;
663
666
  const workflowName = typeof workflow === 'string' ? workflow : workflow.name;
664
- const name = (0, apply_namespace_1.applyNamespace)(workflowName, this.v1.config.namespace).toLowerCase();
667
+ const name = (0, apply_namespace_1.applyNamespace)(workflowName, this.runtime.namespace).toLowerCase();
665
668
  const opts = options || {};
666
669
  const { sticky } = opts;
667
670
  if (sticky && !this.worker.hasWorkflow(name)) {
@@ -669,7 +672,7 @@ class Context {
669
672
  }
670
673
  try {
671
674
  const childIndex = this.nextChildIndex();
672
- const resp = yield this.v1.admin.runWorkflow(name, input, Object.assign({ parentId: workflowRunId, parentTaskRunExternalId: taskRunExternalId, childIndex, desiredWorkerId: sticky ? this.worker.id() : undefined }, opts));
675
+ const resp = yield this.runtime.runWorkflow(name, input, Object.assign({ parentId: workflowRunId, parentTaskRunExternalId: taskRunExternalId, childIndex, desiredWorkerId: sticky ? this.worker.id() : undefined }, opts));
673
676
  if (workflow instanceof declaration_1.TaskWorkflowDeclaration) {
674
677
  resp._standaloneTaskName = workflow._standalone_task_name;
675
678
  }
@@ -706,20 +709,31 @@ class DurableContext extends Context {
706
709
  this._sendEventLock = result.then(() => undefined, () => undefined);
707
710
  return result;
708
711
  }
709
- constructor(action, v1, worker, durableListener, evictionManager, engineVersion) {
710
- super(action, v1, worker);
712
+ constructor(action, runtimeOrClient, workerOrTransport, listenerOrOptions, evictionManager, engineVersion) {
713
+ // The base constructor tells a runtime apart from a client itself, so the worker
714
+ // argument is ignored on the runtime path.
715
+ super(action, runtimeOrClient, workerOrTransport);
711
716
  this._waitKey = 0;
712
717
  // Serializes sendEvent calls from concurrent coroutines in this invocation.
713
718
  // The listener keys pending acks by (task, invocation), so overlapping sends
714
719
  // would overwrite each other's ack and cross-wire branch/node assignments.
715
720
  this._sendEventLock = Promise.resolve();
716
- this._durableListener = durableListener;
717
- this._evictionManager = evictionManager;
718
- this._engineVersion = engineVersion;
721
+ if ((0, runtime_1.isContextRuntime)(runtimeOrClient)) {
722
+ const options = listenerOrOptions;
723
+ this._durableListener = workerOrTransport;
724
+ this._evictionManager = options === null || options === void 0 ? void 0 : options.evictionManager;
725
+ this._engineVersion = options === null || options === void 0 ? void 0 : options.engineVersion;
726
+ }
727
+ else {
728
+ this._durableListener = listenerOrOptions;
729
+ this._evictionManager = evictionManager;
730
+ this._engineVersion = engineVersion;
731
+ }
719
732
  }
720
733
  get supportsEviction() {
721
734
  return (0, engine_version_1.supportsEviction)(this._engineVersion);
722
735
  }
736
+ /** The transport durable events travel over. */
723
737
  get durableListener() {
724
738
  return this._durableListener;
725
739
  }
@@ -784,7 +798,7 @@ class DurableContext extends Context {
784
798
  return this._waitForPreEviction(conditions);
785
799
  }
786
800
  const rendered = (0, conditions_1.Render)(condition_1.Action.CREATE, conditions);
787
- const pbConditions = (0, transformer_1.conditionsToPb)(rendered, this.v1.config.namespace);
801
+ const pbConditions = (0, transformer_1.conditionsToPb)(rendered, this.runtime.namespace);
788
802
  const ack = yield this._serializeSendEvent(() => this._durableListener.sendEvent(this.action.taskRunExternalId, this.invocationCount, {
789
803
  kind: 'waitFor',
790
804
  waitForConditions: {
@@ -860,7 +874,10 @@ class DurableContext extends Context {
860
874
  }
861
875
  _waitForPreEviction(conditions) {
862
876
  return __awaiter(this, void 0, void 0, function* () {
863
- const { result, nextWaitKey } = yield (0, pre_eviction_1.waitForPreEviction)(this._durableListener, this.action.taskRunExternalId, this._waitKey, conditions, this.v1.config.namespace, this.abortController.signal);
877
+ if (!(0, pre_eviction_1.isLegacyDurableTransport)(this._durableListener)) {
878
+ throw new hatchet_error_1.default(`Engine ${this._engineVersion || 'unknown'} does not support durable eviction and the durable transport has no legacy fallback. Upgrade the Hatchet engine.`);
879
+ }
880
+ const { result, nextWaitKey } = yield (0, pre_eviction_1.waitForPreEviction)(this._durableListener, this.action.taskRunExternalId, this._waitKey, conditions, this.runtime.namespace, this.abortController.signal);
864
881
  this._waitKey = nextWaitKey;
865
882
  return result;
866
883
  });
@@ -873,7 +890,7 @@ class DurableContext extends Context {
873
890
  else {
874
891
  workflowName = workflow.name;
875
892
  }
876
- workflowName = (0, apply_namespace_1.applyNamespace)(workflowName, this.v1.config.namespace).toLowerCase();
893
+ workflowName = (0, apply_namespace_1.applyNamespace)(workflowName, this.runtime.namespace).toLowerCase();
877
894
  const childIndex = this.nextChildIndex();
878
895
  const triggerOpts = {
879
896
  name: workflowName,
@@ -902,7 +919,7 @@ class DurableContext extends Context {
902
919
  return __awaiter(this, void 0, void 0, function* () {
903
920
  if (!this.supportsEviction) {
904
921
  const { workflowName, opts } = this.spawnOptions(workflow, options);
905
- const ref = yield this.v1.admin.runWorkflow(workflowName, (input || {}), opts);
922
+ const ref = yield this.runtime.runWorkflow(workflowName, (input || {}), opts);
906
923
  ref.defaultSignal = this.abortController.signal;
907
924
  return ref.output;
908
925
  }
@@ -925,7 +942,7 @@ class DurableContext extends Context {
925
942
  const { workflowName, opts } = this.spawnOptions(c.workflow, c.options);
926
943
  return { workflowName, input: c.input, options: opts };
927
944
  });
928
- const refs = yield this.v1.admin.runWorkflows(workflows);
945
+ const refs = yield this.runtime.runWorkflows(workflows);
929
946
  for (const r of refs) {
930
947
  r.defaultSignal = this.abortController.signal;
931
948
  }
@@ -962,7 +979,7 @@ class DurableContext extends Context {
962
979
  if (!this.supportsEviction) {
963
980
  return fn();
964
981
  }
965
- const memoKey = computeMemoKey(this.action.taskRunExternalId, deps);
982
+ const memoKey = yield computeMemoKey(this.action.taskRunExternalId, deps);
966
983
  const ack = yield this._serializeSendEvent(() => this._durableListener.sendEvent(this.action.taskRunExternalId, this.invocationCount, {
967
984
  kind: 'memo',
968
985
  memoKey,
@@ -980,9 +997,20 @@ class DurableContext extends Context {
980
997
  }
981
998
  }
982
999
  exports.DurableContext = DurableContext;
1000
+ /**
1001
+ * Derives the memo key for a task run and its dependency values: the SHA-256 of the task
1002
+ * run id followed by the JSON-serialised dependencies, over WebCrypto so it works in
1003
+ * every runtime. The bytes are identical to the previous Node `createHash` version, so
1004
+ * event logs recorded by older SDKs keep replaying.
1005
+ */
983
1006
  function computeMemoKey(taskRunExternalId, args) {
984
- const h = (0, crypto_1.createHash)('sha256');
985
- h.update(taskRunExternalId);
986
- h.update(JSON.stringify(args));
987
- return new Uint8Array(h.digest());
1007
+ return __awaiter(this, void 0, void 0, function* () {
1008
+ var _a;
1009
+ const { subtle } = (_a = globalThis.crypto) !== null && _a !== void 0 ? _a : {};
1010
+ if (!subtle) {
1011
+ throw new hatchet_error_1.default('WebCrypto is not available in this runtime. Durable tasks need globalThis.crypto.subtle (Node 20 or newer).');
1012
+ }
1013
+ const data = new TextEncoder().encode(taskRunExternalId + JSON.stringify(args));
1014
+ return new Uint8Array(yield subtle.digest('SHA-256', data));
1015
+ });
988
1016
  }
@@ -5,8 +5,11 @@
5
5
  * Remove this module when support for those engines is dropped.
6
6
  */
7
7
  import { Conditions } from '../../../conditions';
8
- import { DurableListenerClient } from '../../../../clients/listeners/durable-listener/durable-listener-client';
9
- export declare function waitForPreEviction(durableListener: DurableListenerClient, taskRunExternalId: string, waitKey: number, conditions: Conditions | Conditions[], namespace?: string, signal?: AbortSignal): Promise<{
8
+ import type { DurableListenerClient } from '../../../../clients/listeners/durable-listener/durable-listener-client';
9
+ /** The unary and streaming RPCs the pre-eviction fallback needs from the listener. */
10
+ export type LegacyDurableTransport = Pick<DurableListenerClient, 'registerDurableEvent' | 'result'>;
11
+ export declare function isLegacyDurableTransport(value: unknown): value is LegacyDurableTransport;
12
+ export declare function waitForPreEviction(durableListener: LegacyDurableTransport, taskRunExternalId: string, waitKey: number, conditions: Conditions | Conditions[], namespace?: string, signal?: AbortSignal): Promise<{
10
13
  result: Record<string, unknown>;
11
14
  nextWaitKey: number;
12
15
  }>;
@@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.isLegacyDurableTransport = isLegacyDurableTransport;
12
13
  exports.waitForPreEviction = waitForPreEviction;
13
14
  /**
14
15
  * Pre-eviction fallback for DurableContext.
@@ -19,6 +20,10 @@ exports.waitForPreEviction = waitForPreEviction;
19
20
  const conditions_1 = require("../../../conditions");
20
21
  const transformer_1 = require("../../../conditions/transformer");
21
22
  const condition_1 = require("../../../../protoc/v1/shared/condition");
23
+ function isLegacyDurableTransport(value) {
24
+ const candidate = value;
25
+ return (typeof (candidate === null || candidate === void 0 ? void 0 : candidate.registerDurableEvent) === 'function' && typeof (candidate === null || candidate === void 0 ? void 0 : candidate.result) === 'function');
26
+ }
22
27
  function waitForPreEviction(durableListener, taskRunExternalId, waitKey, conditions, namespace, signal) {
23
28
  return __awaiter(this, void 0, void 0, function* () {
24
29
  const pbConditions = (0, transformer_1.conditionsToPb)((0, conditions_1.Render)(condition_1.Action.CREATE, conditions), namespace);
@@ -0,0 +1,118 @@
1
+ /**
2
+ * The seams between a task's `Context` / `DurableContext` and whatever runs the task.
3
+ *
4
+ * A worker process implements them over its `HatchetClient` and durable listener (see
5
+ * `worker-runtime.ts`); a serverless handler implements them over the operator's request
6
+ * and durable socket. Nothing in this module imports from Node, so the interfaces are
7
+ * usable from the edge entry point.
8
+ * @module Runtime
9
+ */
10
+ import type { Logger, LogLevel } from '../../../util/logger';
11
+ import type WorkflowRunRef from '../../../util/workflow-run-ref';
12
+ import type { WorkerLabelComparator } from '../../../protoc/v1/shared/trigger';
13
+ import type { Priority } from '../../declaration';
14
+ import type { DurableTaskEventLogEntryResult, DurableTaskEventMemoAck, DurableTaskEventRunAck, DurableTaskEventWaitForAck, MemoEvent, RunChildrenEvent, WaitForEvent } from '../../../clients/listeners/durable-listener/durable-events';
15
+ /** Labels a worker advertises for affinity-based assignment. */
16
+ export type WorkerLabels = Record<string, string | number | undefined>;
17
+ /** A worker label a spawned child run asks for. */
18
+ export type DesiredWorkerLabel = {
19
+ value: string | number;
20
+ required?: boolean;
21
+ weight?: number;
22
+ comparator?: WorkerLabelComparator;
23
+ };
24
+ /**
25
+ * Options a context passes when it spawns a child run. Mirrors the options accepted by
26
+ * `AdminClient.runWorkflow`.
27
+ */
28
+ export type SpawnRunOptions = {
29
+ parentId?: string | undefined;
30
+ parentTaskRunExternalId?: string | undefined;
31
+ /** @deprecated Use `parentTaskRunExternalId` instead. */
32
+ parentStepRunId?: string | undefined;
33
+ childIndex?: number | undefined;
34
+ childKey?: string | undefined;
35
+ /** Alias of `childKey` kept for the child run APIs on `Context`. */
36
+ key?: string | undefined;
37
+ additionalMetadata?: Record<string, string> | undefined;
38
+ desiredWorkerId?: string | undefined;
39
+ priority?: Priority;
40
+ sticky?: boolean;
41
+ returnExceptions?: boolean;
42
+ desiredWorkerLabels?: Record<string, DesiredWorkerLabel>;
43
+ _standaloneTaskName?: string | undefined;
44
+ };
45
+ export type SpawnRunRequest<Q = object> = {
46
+ workflowName: string;
47
+ input: Q;
48
+ options?: SpawnRunOptions;
49
+ };
50
+ /** Identifies every member of a batch task run when the whole batch is cancelled. */
51
+ export type CancelBatchRequest = {
52
+ workerId: string;
53
+ jobId: string;
54
+ actionId: string;
55
+ batchId: string;
56
+ memberIds: string[];
57
+ };
58
+ /**
59
+ * Everything a `Context` needs from the process running the task: a logger factory,
60
+ * the namespace, the engine-facing operations, and the worker facts `ctx.worker` reports.
61
+ *
62
+ * Implementations that cannot honour an operation (for example a serverless runtime
63
+ * without a Hatchet client) throw from it; the context does not guard against that.
64
+ */
65
+ export interface ContextRuntime {
66
+ /** The namespace applied to workflow names and event keys, if any. */
67
+ readonly namespace?: string;
68
+ /** Creates a logger for the given component, at the runtime's configured level. */
69
+ logger(name: string): Logger;
70
+ /** Cancels a single task run. */
71
+ cancelRun(taskRunExternalId: string): Promise<void>;
72
+ /** Cancels every member of a batch task run. */
73
+ cancelBatch(request: CancelBatchRequest): Promise<void>;
74
+ /** Writes a log line for a task run to the engine. */
75
+ putLog(taskRunExternalId: string, message: string, level: LogLevel | undefined, retryCount: number, extra?: Record<string, unknown>): Promise<void>;
76
+ /** Extends the execution timeout of a task run by `incrementBy` (Go duration string). */
77
+ refreshTimeout(taskRunExternalId: string, incrementBy: string): Promise<void>;
78
+ /** Releases the worker slot held by a task run. */
79
+ releaseSlot(taskRunExternalId: string): Promise<void>;
80
+ /** Streams a chunk of data from a task run. */
81
+ putStream(taskRunExternalId: string, data: string | Uint8Array, index: number): Promise<void>;
82
+ /** Spawns one run. */
83
+ runWorkflow<Q = object, P = object>(workflowName: string, input: Q, options?: SpawnRunOptions): Promise<WorkflowRunRef<P>>;
84
+ /** Spawns many runs in one call. */
85
+ runWorkflows<Q = object, P = object>(runs: SpawnRunRequest<Q>[]): Promise<WorkflowRunRef<P>[]>;
86
+ /** The id the engine assigned to the worker, once registered. */
87
+ workerId(): string | undefined;
88
+ /** Whether the worker serves the given workflow (used by sticky child runs). */
89
+ hasWorkflow(workflowName: string): boolean;
90
+ /** The worker's current labels. */
91
+ workerLabels(): WorkerLabels;
92
+ /** Replaces the worker's labels. */
93
+ upsertWorkerLabels(labels: WorkerLabels): Promise<WorkerLabels>;
94
+ }
95
+ /**
96
+ * The seam a `DurableContext` talks to for durable events: the worker implements it with
97
+ * `DurableListenerClient` over gRPC, a serverless handler with frames over a websocket.
98
+ */
99
+ export interface DurableTransport {
100
+ sendEvent(durableTaskExternalId: string, invocationCount: number, event: RunChildrenEvent): Promise<DurableTaskEventRunAck>;
101
+ sendEvent(durableTaskExternalId: string, invocationCount: number, event: WaitForEvent): Promise<DurableTaskEventWaitForAck>;
102
+ sendEvent(durableTaskExternalId: string, invocationCount: number, event: MemoEvent): Promise<DurableTaskEventMemoAck>;
103
+ waitForCallback(durableTaskExternalId: string, invocationCount: number, branchId: number, nodeId: number, opts?: {
104
+ signal?: AbortSignal;
105
+ }): Promise<DurableTaskEventLogEntryResult>;
106
+ consumeCallbackWithoutBlocking(durableTaskExternalId: string, invocationCount: number, branchId: number, nodeId: number): void;
107
+ sendMemoCompletedNotification(durableTaskExternalId: string, nodeId: number, branchId: number, invocationCount: number, memoKey: Uint8Array, memoResultPayload?: Uint8Array): Promise<void>;
108
+ cleanupTaskState(durableTaskExternalId: string, invocationCount: number): void;
109
+ sendEvictInvocation(durableTaskExternalId: string, invocationCount: number, reason?: string): Promise<void>;
110
+ }
111
+ /**
112
+ * Options for constructing a `DurableContext` on top of a `ContextRuntime`.
113
+ */
114
+ export interface DurableContextOptions {
115
+ /** The engine version the runtime is talking to; decides whether eviction is supported. */
116
+ engineVersion?: string;
117
+ }
118
+ export declare function isContextRuntime(value: unknown): value is ContextRuntime;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isContextRuntime = isContextRuntime;
4
+ function isContextRuntime(value) {
5
+ if (typeof value !== 'object' || value === null)
6
+ return false;
7
+ const candidate = value;
8
+ return typeof candidate.logger === 'function' && typeof candidate.workerId === 'function';
9
+ }