@hatchet-dev/typescript-sdk 1.29.3 → 1.30.1

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 (32) hide show
  1. package/clients/dispatcher/action-listener.d.ts +1 -1
  2. package/clients/dispatcher/action-listener.js +6 -0
  3. package/clients/listeners/durable-listener/durable-listener-client.d.ts +6 -1
  4. package/clients/listeners/durable-listener/durable-listener-client.js +124 -37
  5. package/clients/rest/generated/Api.d.ts +6 -1
  6. package/clients/rest/generated/Api.js +1 -1
  7. package/clients/rest/generated/data-contracts.d.ts +9 -1
  8. package/clients/rest/generated/data-contracts.js +1 -0
  9. package/legacy/workflow.d.ts +4 -4
  10. package/legacy/workflow.js +1 -1
  11. package/package.json +1 -1
  12. package/protoc/v1/workflows.d.ts +17 -0
  13. package/protoc/v1/workflows.js +74 -2
  14. package/v1/client/worker/context.d.ts +2 -0
  15. package/v1/client/worker/context.js +26 -8
  16. package/v1/client/worker/eviction/eviction-manager.d.ts +1 -1
  17. package/v1/client/worker/eviction/eviction-manager.js +12 -5
  18. package/v1/client/worker/worker-internal.d.ts +2 -1
  19. package/v1/client/worker/worker-internal.js +52 -25
  20. package/v1/embedded.d.ts +36 -2
  21. package/v1/embedded.js +35 -8
  22. package/v1/examples/concurrency_cancel_queued_except_newest/workflow.d.ts +9 -0
  23. package/v1/examples/concurrency_cancel_queued_except_newest/workflow.js +47 -0
  24. package/v1/examples/concurrency_cancel_queued_except_oldest/workflow.d.ts +9 -0
  25. package/v1/examples/concurrency_cancel_queued_except_oldest/workflow.js +47 -0
  26. package/v1/examples/durable_callback_ordering/workflow.d.ts +42 -0
  27. package/v1/examples/durable_callback_ordering/workflow.js +95 -0
  28. package/v1/examples/durable_eviction/workflow.d.ts +5 -0
  29. package/v1/examples/durable_eviction/workflow.js +15 -1
  30. package/v1/examples/e2e-worker.js +99 -90
  31. package/version.d.ts +1 -1
  32. package/version.js +1 -1
@@ -7,7 +7,7 @@ declare enum ListenStrategy {
7
7
  LISTEN_STRATEGY_V1 = 1,
8
8
  LISTEN_STRATEGY_V2 = 2
9
9
  }
10
- export type ActionKey = `${string}/${number}`;
10
+ export type ActionKey = `${string}/${number}` | `${string}/${number}/${number}`;
11
11
  export type Action = AssignedAction & {
12
12
  readonly key: ActionKey;
13
13
  };
@@ -90,6 +90,12 @@ function createAction(assignedAction) {
90
90
  const action = assignedAction;
91
91
  Object.defineProperty(action, 'key', {
92
92
  get() {
93
+ // Durable task invocations each get a distinct key so a restored
94
+ // invocation never collides with the one it replaces in contexts,
95
+ // futures, or eviction state.
96
+ if (this.durableTaskInvocationCount !== undefined) {
97
+ return `${this.taskRunExternalId}/${this.retryCount}/${this.durableTaskInvocationCount}`;
98
+ }
93
99
  return `${this.taskRunExternalId}/${this.retryCount}`;
94
100
  },
95
101
  enumerable: true,
@@ -65,7 +65,7 @@ export declare class DurableListenerClient {
65
65
  private _requestNotify;
66
66
  private _pendingEventAcks;
67
67
  private _pendingCallbacks;
68
- private _bufferedCompletions;
68
+ private _orderedCompletions;
69
69
  private _pendingEvictionAcks;
70
70
  private _receiveAbort;
71
71
  private _statusInterval;
@@ -90,6 +90,11 @@ export declare class DurableListenerClient {
90
90
  sendEvent(durableTaskExternalId: string, invocationCount: number, event: RunChildrenEvent): Promise<DurableTaskEventRunAck>;
91
91
  sendEvent(durableTaskExternalId: string, invocationCount: number, event: WaitForEvent): Promise<DurableTaskEventWaitForAck>;
92
92
  sendEvent(durableTaskExternalId: string, invocationCount: number, event: MemoEvent): Promise<DurableTaskEventMemoAck>;
93
+ private _drainsInProgress;
94
+ private _drainRerunRequests;
95
+ private _drainOrderedCompletions;
96
+ private _drainLoop;
97
+ consumeCallbackWithoutBlocking(durableTaskExternalId: string, invocationCount: number, branchId: number, nodeId: number): void;
93
98
  waitForCallback(durableTaskExternalId: string, invocationCount: number, branchId: number, nodeId: number, opts?: {
94
99
  signal?: AbortSignal;
95
100
  }): Promise<DurableTaskEventLogEntryResult>;
@@ -38,6 +38,7 @@ const abort_controller_x_1 = require("abort-controller-x");
38
38
  const hatchet_error_1 = require("../../../util/errors/hatchet-error");
39
39
  const dispatcher_1 = require("../../../protoc/v1/dispatcher");
40
40
  const non_determinism_error_1 = require("../../../util/errors/non-determinism-error");
41
+ const task_run_terminated_error_1 = require("../../../util/errors/task-run-terminated-error");
41
42
  const abort_error_1 = require("../../../util/abort-error");
42
43
  const sleep_1 = __importDefault(require("../../../util/sleep"));
43
44
  const listener_severity_1 = require("../../dispatcher/listener-severity");
@@ -110,6 +111,9 @@ function eventLogEntryResultFromProto(proto) {
110
111
  function ackKey(taskExtId, invocationCount) {
111
112
  return `${taskExtId}:${invocationCount}`;
112
113
  }
114
+ function completionOrderKey(taskExtId, invocationCount) {
115
+ return `${taskExtId}:${invocationCount}`;
116
+ }
113
117
  function callbackKey(taskExtId, invocationCount, branchId, nodeId) {
114
118
  return `${taskExtId}:${invocationCount}:${branchId}:${nodeId}`;
115
119
  }
@@ -131,13 +135,15 @@ class DurableListenerClient {
131
135
  this._requestQueue = [];
132
136
  this._pendingEventAcks = new Map();
133
137
  this._pendingCallbacks = new Map();
134
- // Completions that arrived before waitForCallback() registered a deferred
135
- // in _pendingCallbacks. This happens when the server delivers an
136
- // entryCompleted between the event ack and the waitForCallback call
137
- // (e.g. an already-satisfied sleep delivered via polling).
138
- this._bufferedCompletions = new TTLMap(10000);
138
+ // Completions held in server delivery order until their waiters can
139
+ // consume them without overtaking an earlier-delivered completion.
140
+ // The TTL only garbage-collects queues for invocations that died
141
+ // without consuming everything; the server stall-evicts long before.
142
+ this._orderedCompletions = new TTLMap(300000);
139
143
  this._pendingEvictionAcks = new Map();
140
144
  this._consecutiveFailures = 0;
145
+ this._drainsInProgress = new Set();
146
+ this._drainRerunRequests = new Set();
141
147
  this.config = config;
142
148
  this.client = factory.create(dispatcher_1.V1DispatcherDefinition, channel);
143
149
  this.logger = config.logger(`DurableListener`, config.log_level);
@@ -185,7 +191,7 @@ class DurableListenerClient {
185
191
  this._receiveAbort.abort();
186
192
  }
187
193
  this._failPendingAcks(new Error('DurableListener stopped'));
188
- this._bufferedCompletions.destroy();
194
+ this._orderedCompletions.destroy();
189
195
  });
190
196
  }
191
197
  _connect() {
@@ -332,10 +338,10 @@ class DurableListenerClient {
332
338
  d.reject(exc);
333
339
  }
334
340
  this._pendingCallbacks.clear();
335
- this._bufferedCompletions.clear();
341
+ this._orderedCompletions.clear();
336
342
  }
337
343
  _handleResponse(response) {
338
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2;
344
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5;
339
345
  if (response.registerWorker) {
340
346
  // registration acknowledged
341
347
  }
@@ -396,14 +402,28 @@ class DurableListenerClient {
396
402
  const { ref } = completed;
397
403
  const key = callbackKey((_o = ref === null || ref === void 0 ? void 0 : ref.durableTaskExternalId) !== null && _o !== void 0 ? _o : '', (_p = ref === null || ref === void 0 ? void 0 : ref.invocationCount) !== null && _p !== void 0 ? _p : 0, (_q = ref === null || ref === void 0 ? void 0 : ref.branchId) !== null && _q !== void 0 ? _q : 0, (_r = ref === null || ref === void 0 ? void 0 : ref.nodeId) !== null && _r !== void 0 ? _r : 0);
398
404
  const result = eventLogEntryResultFromProto(completed);
399
- const pending = this._pendingCallbacks.get(key);
400
- if (pending) {
401
- pending.resolve(result);
402
- this._pendingCallbacks.delete(key);
403
- }
404
- else {
405
- this._bufferedCompletions.set(key, result);
405
+ const orderKey = completionOrderKey((_s = ref === null || ref === void 0 ? void 0 : ref.durableTaskExternalId) !== null && _s !== void 0 ? _s : '', (_t = ref === null || ref === void 0 ? void 0 : ref.invocationCount) !== null && _t !== void 0 ? _t : 0);
406
+ const queue = (_u = this._orderedCompletions.get(orderKey)) !== null && _u !== void 0 ? _u : {
407
+ pending: [],
408
+ delivered: new Set(),
409
+ };
410
+ if (!queue.delivered.has(key)) {
411
+ queue.delivered.add(key);
412
+ queue.pending.push({ key, result });
413
+ }
414
+ else if (queue.pending.every((entry) => entry.key !== key)) {
415
+ // Re-delivery of a completion that already drained (reconnect,
416
+ // worker-status re-send, or a repeated wait on a node deduped by
417
+ // child key). Its satisfied order was released before anything
418
+ // still queued, so handing it straight to a waiter keeps order.
419
+ const redelivered = this._pendingCallbacks.get(key);
420
+ if (redelivered) {
421
+ this._pendingCallbacks.delete(key);
422
+ redelivered.resolve(result);
423
+ }
406
424
  }
425
+ this._orderedCompletions.set(orderKey, queue);
426
+ this._drainOrderedCompletions(orderKey);
407
427
  }
408
428
  else if (response.evictionAck) {
409
429
  const ack = response.evictionAck;
@@ -418,34 +438,36 @@ class DurableListenerClient {
418
438
  const evict = response.serverEvict;
419
439
  this.logger.info(`received server eviction notification for task ${evict.durableTaskExternalId} ` +
420
440
  `invocation ${evict.invocationCount}: ${evict.reason}`);
421
- this.cleanupTaskState(evict.durableTaskExternalId, evict.invocationCount);
441
+ // onServerEvict aborts the run first so waiters settle as aborted
442
+ // (eviction) rather than with cleanup's generic rejection.
422
443
  if (this.onServerEvict) {
423
444
  this.onServerEvict(evict.durableTaskExternalId, evict.invocationCount);
424
445
  }
446
+ this.cleanupTaskState(evict.durableTaskExternalId, evict.invocationCount);
425
447
  }
426
448
  else if (response.error) {
427
449
  const { error } = response;
428
450
  const { ref } = error;
429
451
  let exc;
430
452
  if (error.errorType === dispatcher_1.DurableTaskErrorType.DURABLE_TASK_ERROR_TYPE_NONDETERMINISM) {
431
- exc = new non_determinism_error_1.NonDeterminismError((_s = ref === null || ref === void 0 ? void 0 : ref.durableTaskExternalId) !== null && _s !== void 0 ? _s : '', (_t = ref === null || ref === void 0 ? void 0 : ref.invocationCount) !== null && _t !== void 0 ? _t : 0, (_u = ref === null || ref === void 0 ? void 0 : ref.nodeId) !== null && _u !== void 0 ? _u : 0, error.errorMessage);
453
+ exc = new non_determinism_error_1.NonDeterminismError((_v = ref === null || ref === void 0 ? void 0 : ref.durableTaskExternalId) !== null && _v !== void 0 ? _v : '', (_w = ref === null || ref === void 0 ? void 0 : ref.invocationCount) !== null && _w !== void 0 ? _w : 0, (_x = ref === null || ref === void 0 ? void 0 : ref.nodeId) !== null && _x !== void 0 ? _x : 0, error.errorMessage);
432
454
  }
433
455
  else {
434
456
  exc = new Error(`Unspecified durable task error: ${error.errorMessage} (type: ${error.errorType})`);
435
457
  }
436
- const eAckKey = ackKey((_v = ref === null || ref === void 0 ? void 0 : ref.durableTaskExternalId) !== null && _v !== void 0 ? _v : '', (_w = ref === null || ref === void 0 ? void 0 : ref.invocationCount) !== null && _w !== void 0 ? _w : 0);
458
+ const eAckKey = ackKey((_y = ref === null || ref === void 0 ? void 0 : ref.durableTaskExternalId) !== null && _y !== void 0 ? _y : '', (_z = ref === null || ref === void 0 ? void 0 : ref.invocationCount) !== null && _z !== void 0 ? _z : 0);
437
459
  const pendingAck = this._pendingEventAcks.get(eAckKey);
438
460
  if (pendingAck) {
439
461
  pendingAck.reject(exc);
440
462
  this._pendingEventAcks.delete(eAckKey);
441
463
  }
442
- const eCbKey = callbackKey((_x = ref === null || ref === void 0 ? void 0 : ref.durableTaskExternalId) !== null && _x !== void 0 ? _x : '', (_y = ref === null || ref === void 0 ? void 0 : ref.invocationCount) !== null && _y !== void 0 ? _y : 0, (_z = ref === null || ref === void 0 ? void 0 : ref.branchId) !== null && _z !== void 0 ? _z : 0, (_0 = ref === null || ref === void 0 ? void 0 : ref.nodeId) !== null && _0 !== void 0 ? _0 : 0);
464
+ const eCbKey = callbackKey((_0 = ref === null || ref === void 0 ? void 0 : ref.durableTaskExternalId) !== null && _0 !== void 0 ? _0 : '', (_1 = ref === null || ref === void 0 ? void 0 : ref.invocationCount) !== null && _1 !== void 0 ? _1 : 0, (_2 = ref === null || ref === void 0 ? void 0 : ref.branchId) !== null && _2 !== void 0 ? _2 : 0, (_3 = ref === null || ref === void 0 ? void 0 : ref.nodeId) !== null && _3 !== void 0 ? _3 : 0);
443
465
  const pendingCb = this._pendingCallbacks.get(eCbKey);
444
466
  if (pendingCb) {
445
467
  pendingCb.reject(exc);
446
468
  this._pendingCallbacks.delete(eCbKey);
447
469
  }
448
- const eEvKey = evictionKey((_1 = ref === null || ref === void 0 ? void 0 : ref.durableTaskExternalId) !== null && _1 !== void 0 ? _1 : '', (_2 = ref === null || ref === void 0 ? void 0 : ref.invocationCount) !== null && _2 !== void 0 ? _2 : 0);
470
+ const eEvKey = evictionKey((_4 = ref === null || ref === void 0 ? void 0 : ref.durableTaskExternalId) !== null && _4 !== void 0 ? _4 : '', (_5 = ref === null || ref === void 0 ? void 0 : ref.invocationCount) !== null && _5 !== void 0 ? _5 : 0);
449
471
  const pendingEv = this._pendingEvictionAcks.get(eEvKey);
450
472
  if (pendingEv) {
451
473
  pendingEv.reject(exc);
@@ -498,26 +520,87 @@ class DurableListenerClient {
498
520
  return d.promise;
499
521
  });
500
522
  }
523
+ // Hands queued completions to their waiters strictly in delivery order.
524
+ // Stops at the first completion whose waiter has not registered yet:
525
+ // releasing a later completion first would resume its coroutine ahead of
526
+ // the recorded order and diverge the re-emitted event sequence.
527
+ _drainOrderedCompletions(orderKey) {
528
+ if (this._drainsInProgress.has(orderKey)) {
529
+ this._drainRerunRequests.add(orderKey);
530
+ return;
531
+ }
532
+ this._drainsInProgress.add(orderKey);
533
+ void this._drainLoop(orderKey).finally(() => {
534
+ this._drainsInProgress.delete(orderKey);
535
+ if (this._drainRerunRequests.delete(orderKey)) {
536
+ this._drainOrderedCompletions(orderKey);
537
+ }
538
+ });
539
+ }
540
+ _drainLoop(orderKey) {
541
+ return __awaiter(this, void 0, void 0, function* () {
542
+ let released = false;
543
+ while (true) {
544
+ if (released) {
545
+ // A macrotask boundary between releases lets the just-resumed waiter
546
+ // run to its next suspension point (emitting its next durable event)
547
+ // before the following completion is released. Resolving several
548
+ // deferreds back-to-back would instead resume them in the order their
549
+ // continuations happened to be attached, not in release order.
550
+ yield new Promise((resolve) => {
551
+ setImmediate(resolve);
552
+ });
553
+ }
554
+ const queue = this._orderedCompletions.get(orderKey);
555
+ if (!queue || queue.pending.length === 0)
556
+ return;
557
+ const [head] = queue.pending;
558
+ const waiter = this._pendingCallbacks.get(head.key);
559
+ if (!waiter)
560
+ return;
561
+ queue.pending.shift();
562
+ this._pendingCallbacks.delete(head.key);
563
+ waiter.resolve(head.result);
564
+ released = true;
565
+ }
566
+ });
567
+ }
568
+ // A memo's value arrives in its ack and its completion is never awaited,
569
+ // but the server still delivers that completion on replay, in recorded
570
+ // order. Register a waiter that nothing blocks on so the drain can hand the
571
+ // completion through instead of stalling the queue at it forever.
572
+ consumeCallbackWithoutBlocking(durableTaskExternalId, invocationCount, branchId, nodeId) {
573
+ const key = callbackKey(durableTaskExternalId, invocationCount, branchId, nodeId);
574
+ if (this._pendingCallbacks.has(key))
575
+ return;
576
+ const d = deferred();
577
+ d.promise.catch(() => { });
578
+ this._pendingCallbacks.set(key, d);
579
+ this._drainOrderedCompletions(completionOrderKey(durableTaskExternalId, invocationCount));
580
+ }
501
581
  waitForCallback(durableTaskExternalId, invocationCount, branchId, nodeId, opts) {
502
582
  return __awaiter(this, void 0, void 0, function* () {
503
- const key = callbackKey(durableTaskExternalId, invocationCount, branchId, nodeId);
504
- const early = this._bufferedCompletions.get(key);
505
- if (early) {
506
- this._bufferedCompletions.delete(key);
507
- return early;
583
+ const signal = opts === null || opts === void 0 ? void 0 : opts.signal;
584
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
585
+ return Promise.reject((0, abort_error_1.createAbortError)('Operation cancelled by AbortSignal'));
508
586
  }
509
- if (!this._pendingCallbacks.has(key)) {
510
- this._pendingCallbacks.set(key, deferred());
511
- this._pollWorkerStatus();
587
+ const key = callbackKey(durableTaskExternalId, invocationCount, branchId, nodeId);
588
+ let d = this._pendingCallbacks.get(key);
589
+ if (!d) {
590
+ d = deferred();
591
+ // A deferred can outlive its waiters (eviction aborts the caller while
592
+ // the entry stays registered); a later cleanupTaskState rejection must
593
+ // not crash the process as an unhandled rejection.
594
+ d.promise.catch(() => { });
595
+ this._pendingCallbacks.set(key, d);
596
+ this._drainOrderedCompletions(completionOrderKey(durableTaskExternalId, invocationCount));
597
+ if (this._pendingCallbacks.has(key)) {
598
+ this._pollWorkerStatus();
599
+ }
512
600
  }
513
- const d = this._pendingCallbacks.get(key);
514
- const signal = opts === null || opts === void 0 ? void 0 : opts.signal;
515
601
  if (!signal) {
516
602
  return d.promise;
517
603
  }
518
- if (signal.aborted) {
519
- return Promise.reject((0, abort_error_1.createAbortError)('Operation cancelled by AbortSignal'));
520
- }
521
604
  return new Promise((resolve, reject) => {
522
605
  let settled = false;
523
606
  const onAbort = () => {
@@ -544,24 +627,28 @@ class DurableListenerClient {
544
627
  });
545
628
  }
546
629
  cleanupTaskState(durableTaskExternalId, invocationCount) {
630
+ // Rejecting with TaskRunTerminatedError marks any coroutine still blocked
631
+ // on this invocation's state as evicted rather than failed, matching
632
+ // Python's future cancellation on cleanup.
633
+ const evicted = () => new task_run_terminated_error_1.TaskRunTerminatedError('evicted', 'task state cleaned up');
547
634
  for (const [k, d] of this._pendingCallbacks) {
548
635
  const parts = k.split(':');
549
636
  if (parts[0] === durableTaskExternalId && parseInt(parts[1], 10) <= invocationCount) {
550
- d.reject(new Error('task state cleaned up'));
637
+ d.reject(evicted());
551
638
  this._pendingCallbacks.delete(k);
552
639
  }
553
640
  }
554
641
  for (const [k, d] of this._pendingEventAcks) {
555
642
  const parts = k.split(':');
556
643
  if (parts[0] === durableTaskExternalId && parseInt(parts[1], 10) <= invocationCount) {
557
- d.reject(new Error('task state cleaned up'));
644
+ d.reject(evicted());
558
645
  this._pendingEventAcks.delete(k);
559
646
  }
560
647
  }
561
- for (const k of this._bufferedCompletions.keys()) {
648
+ for (const k of this._orderedCompletions.keys()) {
562
649
  const parts = k.split(':');
563
650
  if (parts[0] === durableTaskExternalId && parseInt(parts[1], 10) <= invocationCount) {
564
- this._bufferedCompletions.delete(k);
651
+ this._orderedCompletions.delete(k);
565
652
  }
566
653
  }
567
654
  }
@@ -350,7 +350,10 @@ export declare class Api<SecurityDataType = unknown> extends HttpClient<Security
350
350
  * @request GET:/api/v1/stable/workflow-runs/{v1-workflow-run}
351
351
  * @secure
352
352
  */
353
- v1WorkflowRunGet: (v1WorkflowRun: string, params?: RequestParams) => Promise<import("axios").AxiosResponse<V1WorkflowRunDetails, any, {}>>;
353
+ v1WorkflowRunGet: (v1WorkflowRun: string, query?: {
354
+ /** Whether to include the DAG orchestrator's task events, which are hidden by default */
355
+ includeOrchestratorEvents?: boolean;
356
+ }, params?: RequestParams) => Promise<import("axios").AxiosResponse<V1WorkflowRunDetails, any, {}>>;
354
357
  /**
355
358
  * @description Get the status of a workflow run.
356
359
  *
@@ -2024,6 +2027,8 @@ export declare class Api<SecurityDataType = unknown> extends HttpClient<Security
2024
2027
  statuses?: WorkerStatus[];
2025
2028
  /** Filter by worker labels */
2026
2029
  labels?: string[];
2030
+ /** Whether to include engine-managed operator workers, which are hidden by default */
2031
+ includeOperators?: boolean;
2027
2032
  }, params?: RequestParams) => Promise<import("axios").AxiosResponse<WorkerList, any, {}>>;
2028
2033
  /**
2029
2034
  * @description Update a worker
@@ -175,7 +175,7 @@ class Api extends http_client_1.HttpClient {
175
175
  * @request GET:/api/v1/stable/workflow-runs/{v1-workflow-run}
176
176
  * @secure
177
177
  */
178
- this.v1WorkflowRunGet = (v1WorkflowRun, params = {}) => this.request(Object.assign({ path: `/api/v1/stable/workflow-runs/${v1WorkflowRun}`, method: 'GET', secure: true, format: 'json' }, params));
178
+ this.v1WorkflowRunGet = (v1WorkflowRun, query, params = {}) => this.request(Object.assign({ path: `/api/v1/stable/workflow-runs/${v1WorkflowRun}`, method: 'GET', query: query, secure: true, format: 'json' }, params));
179
179
  /**
180
180
  * @description Get the status of a workflow run.
181
181
  *
@@ -25,7 +25,8 @@ export declare enum PullRequestState {
25
25
  export declare enum FeatureFlagId {
26
26
  TenantLogWorkflowFilterEnabled = "tenant-log-workflow-filter-enabled",
27
27
  TraceMinimapEnabled = "trace-minimap-enabled",
28
- OrganizationSsoEnabled = "organization-sso-enabled"
28
+ OrganizationSsoEnabled = "organization-sso-enabled",
29
+ OperatorDetailsEnabled = "operator-details-enabled"
29
30
  }
30
31
  export declare enum WebhookWorkerRequestMethod {
31
32
  GET = "GET",
@@ -2328,6 +2329,13 @@ export interface Worker {
2328
2329
  */
2329
2330
  webhookId?: string;
2330
2331
  runtimeInfo?: WorkerRuntimeInfo;
2332
+ /**
2333
+ * The id of the operator that owns this worker, if it is an engine-managed operator worker.
2334
+ * @format uuid
2335
+ */
2336
+ operatorId?: string;
2337
+ /** The number of durable task runs owned by this operator that are currently evicted while waiting on durable events. Evicted runs hold no slots. Only set for operator workers. */
2338
+ evictedDurableTaskCount?: number;
2331
2339
  }
2332
2340
  export interface WorkerList {
2333
2341
  pagination?: PaginationResponse;
@@ -46,6 +46,7 @@ var FeatureFlagId;
46
46
  FeatureFlagId["TenantLogWorkflowFilterEnabled"] = "tenant-log-workflow-filter-enabled";
47
47
  FeatureFlagId["TraceMinimapEnabled"] = "trace-minimap-enabled";
48
48
  FeatureFlagId["OrganizationSsoEnabled"] = "organization-sso-enabled";
49
+ FeatureFlagId["OperatorDetailsEnabled"] = "operator-details-enabled";
49
50
  })(FeatureFlagId || (exports.FeatureFlagId = FeatureFlagId = {}));
50
51
  var WebhookWorkerRequestMethod;
51
52
  (function (WebhookWorkerRequestMethod) {
@@ -1,6 +1,6 @@
1
1
  import * as z from 'zod/v4';
2
2
  import { CreateStep } from '../step';
3
- import { ConcurrencyLimitStrategy as PbConcurrencyLimitStrategy, StickyStrategy as PbStickyStrategy } from '../protoc/workflows';
3
+ import { ConcurrencyLimitStrategy as PbConcurrencyLimitStrategy, StickyStrategy as PbStickyStrategy } from '../protoc/v1/workflows';
4
4
  declare const StepsSchema: z.ZodArray<z.ZodObject<{
5
5
  name: z.ZodString;
6
6
  parents: z.ZodOptional<z.ZodArray<z.ZodString>>;
@@ -12,7 +12,7 @@ declare const StepsSchema: z.ZodArray<z.ZodObject<{
12
12
  dynamicKey: z.ZodOptional<z.ZodString>;
13
13
  units: z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>;
14
14
  limit: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
15
- duration: z.ZodOptional<z.ZodEnum<typeof import("../protoc/workflows").RateLimitDuration>>;
15
+ duration: z.ZodOptional<z.ZodEnum<typeof import("..").RateLimitDuration>>;
16
16
  }, z.core.$strip>>>;
17
17
  worker_labels: z.ZodOptional<z.ZodRecord<z.ZodAny, z.ZodLazy<z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodObject<{
18
18
  value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
@@ -63,7 +63,7 @@ export declare const CreateWorkflowSchema: z.ZodObject<{
63
63
  dynamicKey: z.ZodOptional<z.ZodString>;
64
64
  units: z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>;
65
65
  limit: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
66
- duration: z.ZodOptional<z.ZodEnum<typeof import("../protoc/workflows").RateLimitDuration>>;
66
+ duration: z.ZodOptional<z.ZodEnum<typeof import("..").RateLimitDuration>>;
67
67
  }, z.core.$strip>>>;
68
68
  worker_labels: z.ZodOptional<z.ZodRecord<z.ZodAny, z.ZodLazy<z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodObject<{
69
69
  value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
@@ -87,7 +87,7 @@ export declare const CreateWorkflowSchema: z.ZodObject<{
87
87
  dynamicKey: z.ZodOptional<z.ZodString>;
88
88
  units: z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>;
89
89
  limit: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
90
- duration: z.ZodOptional<z.ZodEnum<typeof import("../protoc/workflows").RateLimitDuration>>;
90
+ duration: z.ZodOptional<z.ZodEnum<typeof import("..").RateLimitDuration>>;
91
91
  }, z.core.$strip>>>;
92
92
  worker_labels: z.ZodOptional<z.ZodRecord<z.ZodAny, z.ZodLazy<z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodObject<{
93
93
  value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
@@ -36,7 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.CreateWorkflowSchema = exports.StickyStrategy = exports.HatchetTimeoutSchema = exports.WorkflowConcurrency = exports.ConcurrencyLimitStrategy = void 0;
37
37
  const z = __importStar(require("zod/v4"));
38
38
  const step_1 = require("../step");
39
- const workflows_1 = require("../protoc/workflows");
39
+ const workflows_1 = require("../protoc/v1/workflows");
40
40
  const CronConfigSchema = z.object({
41
41
  cron: z.string(),
42
42
  event: z.undefined().optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hatchet-dev/typescript-sdk",
3
- "version": "1.29.3",
3
+ "version": "1.30.1",
4
4
  "description": "Background task orchestration & visibility for developers",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [
@@ -48,6 +48,8 @@ export declare enum ConcurrencyLimitStrategy {
48
48
  QUEUE_NEWEST = 2,
49
49
  GROUP_ROUND_ROBIN = 3,
50
50
  CANCEL_NEWEST = 4,
51
+ CANCEL_QUEUED_EXCEPT_NEWEST = 5,
52
+ CANCEL_QUEUED_EXCEPT_OLDEST = 6,
51
53
  UNRECOGNIZED = -1
52
54
  }
53
55
  export declare function concurrencyLimitStrategyFromJSON(object: any): ConcurrencyLimitStrategy;
@@ -168,6 +170,15 @@ export interface DefaultFilter {
168
170
  /** (optional) the payload for the filter, if any. A JSON object as a string. */
169
171
  payload?: Uint8Array | undefined;
170
172
  }
173
+ /**
174
+ * Concurrency declares one entry in a concurrency chain. Entries are processed in array
175
+ * order, so a tenant-scoped entry may come before or after a workflow-scoped one.
176
+ *
177
+ * A tenant-scoped entry (is_tenant_scoped = true) defines (or updates in place) a strategy
178
+ * shared across workflows, keyed by name: every task declaring the same name consumes the
179
+ * same concurrency limit. Registrations whose chains order the same tenant-scoped
180
+ * strategies inconsistently are rejected, since inconsistent orders can deadlock.
181
+ */
171
182
  export interface Concurrency {
172
183
  /** (required) the expression to use for concurrency */
173
184
  expression: string;
@@ -175,6 +186,12 @@ export interface Concurrency {
175
186
  maxRuns?: number | undefined;
176
187
  /** (optional) the strategy to use when the concurrency limit is reached, default CANCEL_IN_PROGRESS */
177
188
  limitStrategy?: ConcurrencyLimitStrategy | undefined;
189
+ /** (required when is_tenant_scoped) the strategy name; unique per tenant for tenant-scoped strategies */
190
+ name?: string | undefined;
191
+ /** (optional) when true, the entry is a tenant-scoped strategy shared across workflows, default false */
192
+ isTenantScoped?: boolean | undefined;
193
+ /** (optional) CEL expression over task input returning the max runs for that task's concurrency group; the group's effective limit is the value from its most recently created task. Overrides max_runs per group; a non-integer or negative result fails the task, and 0 holds the group until a newer task raises the limit */
194
+ maxRunsExpression?: string | undefined;
178
195
  }
179
196
  export interface TaskBatchConfig {
180
197
  /** (required) maximum items per batch */
@@ -209,6 +209,8 @@ var ConcurrencyLimitStrategy;
209
209
  ConcurrencyLimitStrategy[ConcurrencyLimitStrategy["QUEUE_NEWEST"] = 2] = "QUEUE_NEWEST";
210
210
  ConcurrencyLimitStrategy[ConcurrencyLimitStrategy["GROUP_ROUND_ROBIN"] = 3] = "GROUP_ROUND_ROBIN";
211
211
  ConcurrencyLimitStrategy[ConcurrencyLimitStrategy["CANCEL_NEWEST"] = 4] = "CANCEL_NEWEST";
212
+ ConcurrencyLimitStrategy[ConcurrencyLimitStrategy["CANCEL_QUEUED_EXCEPT_NEWEST"] = 5] = "CANCEL_QUEUED_EXCEPT_NEWEST";
213
+ ConcurrencyLimitStrategy[ConcurrencyLimitStrategy["CANCEL_QUEUED_EXCEPT_OLDEST"] = 6] = "CANCEL_QUEUED_EXCEPT_OLDEST";
212
214
  ConcurrencyLimitStrategy[ConcurrencyLimitStrategy["UNRECOGNIZED"] = -1] = "UNRECOGNIZED";
213
215
  })(ConcurrencyLimitStrategy || (exports.ConcurrencyLimitStrategy = ConcurrencyLimitStrategy = {}));
214
216
  function concurrencyLimitStrategyFromJSON(object) {
@@ -228,6 +230,12 @@ function concurrencyLimitStrategyFromJSON(object) {
228
230
  case 4:
229
231
  case 'CANCEL_NEWEST':
230
232
  return ConcurrencyLimitStrategy.CANCEL_NEWEST;
233
+ case 5:
234
+ case 'CANCEL_QUEUED_EXCEPT_NEWEST':
235
+ return ConcurrencyLimitStrategy.CANCEL_QUEUED_EXCEPT_NEWEST;
236
+ case 6:
237
+ case 'CANCEL_QUEUED_EXCEPT_OLDEST':
238
+ return ConcurrencyLimitStrategy.CANCEL_QUEUED_EXCEPT_OLDEST;
231
239
  case -1:
232
240
  case 'UNRECOGNIZED':
233
241
  default:
@@ -246,6 +254,10 @@ function concurrencyLimitStrategyToJSON(object) {
246
254
  return 'GROUP_ROUND_ROBIN';
247
255
  case ConcurrencyLimitStrategy.CANCEL_NEWEST:
248
256
  return 'CANCEL_NEWEST';
257
+ case ConcurrencyLimitStrategy.CANCEL_QUEUED_EXCEPT_NEWEST:
258
+ return 'CANCEL_QUEUED_EXCEPT_NEWEST';
259
+ case ConcurrencyLimitStrategy.CANCEL_QUEUED_EXCEPT_OLDEST:
260
+ return 'CANCEL_QUEUED_EXCEPT_OLDEST';
249
261
  case ConcurrencyLimitStrategy.UNRECOGNIZED:
250
262
  default:
251
263
  return 'UNRECOGNIZED';
@@ -1782,7 +1794,14 @@ exports.DefaultFilter = {
1782
1794
  },
1783
1795
  };
1784
1796
  function createBaseConcurrency() {
1785
- return { expression: '', maxRuns: undefined, limitStrategy: undefined };
1797
+ return {
1798
+ expression: '',
1799
+ maxRuns: undefined,
1800
+ limitStrategy: undefined,
1801
+ name: undefined,
1802
+ isTenantScoped: undefined,
1803
+ maxRunsExpression: undefined,
1804
+ };
1786
1805
  }
1787
1806
  exports.Concurrency = {
1788
1807
  encode(message, writer = new wire_1.BinaryWriter()) {
@@ -1795,6 +1814,15 @@ exports.Concurrency = {
1795
1814
  if (message.limitStrategy !== undefined) {
1796
1815
  writer.uint32(24).int32(message.limitStrategy);
1797
1816
  }
1817
+ if (message.name !== undefined) {
1818
+ writer.uint32(34).string(message.name);
1819
+ }
1820
+ if (message.isTenantScoped !== undefined) {
1821
+ writer.uint32(40).bool(message.isTenantScoped);
1822
+ }
1823
+ if (message.maxRunsExpression !== undefined) {
1824
+ writer.uint32(50).string(message.maxRunsExpression);
1825
+ }
1798
1826
  return writer;
1799
1827
  },
1800
1828
  decode(input, length) {
@@ -1825,6 +1853,27 @@ exports.Concurrency = {
1825
1853
  message.limitStrategy = reader.int32();
1826
1854
  continue;
1827
1855
  }
1856
+ case 4: {
1857
+ if (tag !== 34) {
1858
+ break;
1859
+ }
1860
+ message.name = reader.string();
1861
+ continue;
1862
+ }
1863
+ case 5: {
1864
+ if (tag !== 40) {
1865
+ break;
1866
+ }
1867
+ message.isTenantScoped = reader.bool();
1868
+ continue;
1869
+ }
1870
+ case 6: {
1871
+ if (tag !== 50) {
1872
+ break;
1873
+ }
1874
+ message.maxRunsExpression = reader.string();
1875
+ continue;
1876
+ }
1828
1877
  }
1829
1878
  if ((tag & 7) === 4 || tag === 0) {
1830
1879
  break;
@@ -1846,6 +1895,17 @@ exports.Concurrency = {
1846
1895
  : isSet(object.limit_strategy)
1847
1896
  ? concurrencyLimitStrategyFromJSON(object.limit_strategy)
1848
1897
  : undefined,
1898
+ name: isSet(object.name) ? globalThis.String(object.name) : undefined,
1899
+ isTenantScoped: isSet(object.isTenantScoped)
1900
+ ? globalThis.Boolean(object.isTenantScoped)
1901
+ : isSet(object.is_tenant_scoped)
1902
+ ? globalThis.Boolean(object.is_tenant_scoped)
1903
+ : undefined,
1904
+ maxRunsExpression: isSet(object.maxRunsExpression)
1905
+ ? globalThis.String(object.maxRunsExpression)
1906
+ : isSet(object.max_runs_expression)
1907
+ ? globalThis.String(object.max_runs_expression)
1908
+ : undefined,
1849
1909
  };
1850
1910
  },
1851
1911
  toJSON(message) {
@@ -1859,17 +1919,29 @@ exports.Concurrency = {
1859
1919
  if (message.limitStrategy !== undefined) {
1860
1920
  obj.limitStrategy = concurrencyLimitStrategyToJSON(message.limitStrategy);
1861
1921
  }
1922
+ if (message.name !== undefined) {
1923
+ obj.name = message.name;
1924
+ }
1925
+ if (message.isTenantScoped !== undefined) {
1926
+ obj.isTenantScoped = message.isTenantScoped;
1927
+ }
1928
+ if (message.maxRunsExpression !== undefined) {
1929
+ obj.maxRunsExpression = message.maxRunsExpression;
1930
+ }
1862
1931
  return obj;
1863
1932
  },
1864
1933
  create(base) {
1865
1934
  return exports.Concurrency.fromPartial(base !== null && base !== void 0 ? base : {});
1866
1935
  },
1867
1936
  fromPartial(object) {
1868
- var _a, _b, _c;
1937
+ var _a, _b, _c, _d, _e, _f;
1869
1938
  const message = createBaseConcurrency();
1870
1939
  message.expression = (_a = object.expression) !== null && _a !== void 0 ? _a : '';
1871
1940
  message.maxRuns = (_b = object.maxRuns) !== null && _b !== void 0 ? _b : undefined;
1872
1941
  message.limitStrategy = (_c = object.limitStrategy) !== null && _c !== void 0 ? _c : undefined;
1942
+ message.name = (_d = object.name) !== null && _d !== void 0 ? _d : undefined;
1943
+ message.isTenantScoped = (_e = object.isTenantScoped) !== null && _e !== void 0 ? _e : undefined;
1944
+ message.maxRunsExpression = (_f = object.maxRunsExpression) !== null && _f !== void 0 ? _f : undefined;
1873
1945
  return message;
1874
1946
  },
1875
1947
  };
@@ -381,6 +381,8 @@ export declare class DurableContext<T, K = {}> extends Context<T, K> {
381
381
  private _evictionManager;
382
382
  private _engineVersion;
383
383
  private _waitKey;
384
+ private _sendEventLock;
385
+ private _serializeSendEvent;
384
386
  constructor(action: Action, v1: HatchetClient, worker: InternalWorker, durableListener: DurableListenerClient, evictionManager?: DurableEvictionManager, engineVersion?: string);
385
387
  get supportsEviction(): boolean;
386
388
  get durableListener(): DurableListenerClient;