@hatchet-dev/typescript-sdk 1.28.0 → 1.28.2

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.
@@ -74,6 +74,7 @@ const abort_controller_x_1 = require("abort-controller-x");
74
74
  const sleep_1 = __importDefault(require("../../util/sleep"));
75
75
  const hatchet_error_1 = __importStar(require("../../util/errors/hatchet-error"));
76
76
  const heartbeat_controller_1 = require("./heartbeat/heartbeat-controller");
77
+ const listener_severity_1 = require("./listener-severity");
77
78
  const DEFAULT_ACTION_LISTENER_RETRY_INTERVAL = 5000; // milliseconds
78
79
  const DEFAULT_ACTION_LISTENER_RETRY_COUNT = 20;
79
80
  var ListenStrategy;
@@ -145,7 +146,11 @@ class ActionListener {
145
146
  client.setListenStrategy(ListenStrategy.LISTEN_STRATEGY_V1);
146
147
  }
147
148
  client.incrementRetries();
148
- client.logger.error(`Listener encountered an error: ${(0, hatchet_error_1.getErrorMessage)(e)}`);
149
+ const message = `Listener encountered an error: ${(0, hatchet_error_1.getErrorMessage)(e)}`;
150
+ const severity = (0, listener_severity_1.classifyListenerFailure)(e, client.retries);
151
+ if (severity !== 'silent') {
152
+ client.logger[severity](message);
153
+ }
149
154
  if (client.retries > 1) {
150
155
  client.logger.info(`Retrying in ${client.retryInterval}ms...`);
151
156
  yield __await((0, sleep_1.default)(client.retryInterval));
@@ -218,7 +223,11 @@ class ActionListener {
218
223
  }
219
224
  catch (e) {
220
225
  this.retries += 1;
221
- this.logger.error(`Attempt ${this.retries}: Failed to connect, retrying...`);
226
+ const message = `Attempt ${this.retries}: Failed to connect, retrying...`;
227
+ const severity = (0, listener_severity_1.classifyListenerFailure)(e, this.retries);
228
+ if (severity !== 'silent') {
229
+ this.logger[severity](message);
230
+ }
222
231
  if ((0, grpc_error_1.getGrpcErrorCode)(e) === nice_grpc_1.Status.UNAVAILABLE) {
223
232
  // Connection lost, reset heartbeat interval and retry connection
224
233
  this.heartbeat.stop();
@@ -3,10 +3,18 @@ import { DispatcherClient as PbDispatcherClient } from '../../../protoc/dispatch
3
3
  import { Worker } from 'worker_threads';
4
4
  import { ClientConfig } from '../../hatchet-client';
5
5
  import { DispatcherClient } from '../dispatcher-client';
6
- export interface HeartbeatMessage {
7
- type: 'info' | 'warn' | 'error' | 'debug';
8
- message: string;
9
- }
6
+ import { z } from 'zod/v4';
7
+ declare const HeartbeatMessageSchema: z.ZodObject<{
8
+ type: z.ZodEnum<{
9
+ error: "error";
10
+ warn: "warn";
11
+ info: "info";
12
+ debug: "debug";
13
+ }>;
14
+ message: z.ZodString;
15
+ }, z.core.$strip>;
16
+ export type HeartbeatMessage = z.infer<typeof HeartbeatMessageSchema>;
17
+ export declare const isHeartbeatMessage: (message: unknown) => message is HeartbeatMessage;
10
18
  export declare const STOP_HEARTBEAT = "stop";
11
19
  export declare class Heartbeat {
12
20
  config: ClientConfig;
@@ -18,3 +26,4 @@ export declare class Heartbeat {
18
26
  start(): Promise<void>;
19
27
  stop(): Promise<void>;
20
28
  }
29
+ export {};
@@ -23,9 +23,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
23
23
  return (mod && mod.__esModule) ? mod : { "default": mod };
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.Heartbeat = exports.STOP_HEARTBEAT = void 0;
26
+ exports.Heartbeat = exports.STOP_HEARTBEAT = exports.isHeartbeatMessage = void 0;
27
27
  const path_1 = __importDefault(require("path"));
28
28
  const thread_helper_1 = require("../../../util/thread-helper");
29
+ const v4_1 = require("zod/v4");
30
+ const HeartbeatMessageSchema = v4_1.z.object({
31
+ type: v4_1.z.enum(['info', 'warn', 'error', 'debug']),
32
+ message: v4_1.z.string(),
33
+ });
34
+ const isHeartbeatMessage = (message) => HeartbeatMessageSchema.safeParse(message).success;
35
+ exports.isHeartbeatMessage = isHeartbeatMessage;
29
36
  exports.STOP_HEARTBEAT = 'stop';
30
37
  class Heartbeat {
31
38
  constructor(client, workerId) {
@@ -45,6 +52,9 @@ class Heartbeat {
45
52
  },
46
53
  });
47
54
  this.heartbeatWorker.on('message', (message) => {
55
+ if (!(0, exports.isHeartbeatMessage)(message)) {
56
+ return;
57
+ }
48
58
  this.logger[message.type](message.message);
49
59
  });
50
60
  }
@@ -0,0 +1,3 @@
1
+ import { FailureSeverity } from '../../../util/failure-severity';
2
+ export declare const MAX_MISSED_HEARTBEATS = 3;
3
+ export declare function classifyHeartbeatFailure(code: number | undefined, missedHeartbeats: number): FailureSeverity;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_MISSED_HEARTBEATS = void 0;
4
+ exports.classifyHeartbeatFailure = classifyHeartbeatFailure;
5
+ const grpc_error_1 = require("../../../util/grpc-error");
6
+ const failure_severity_1 = require("../../../util/failure-severity");
7
+ exports.MAX_MISSED_HEARTBEATS = 3;
8
+ // determines whether to immediately error log or wait for additional errors
9
+ function classifyHeartbeatFailure(code, missedHeartbeats) {
10
+ return (0, failure_severity_1.classifyRepeatedFailure)((0, grpc_error_1.isConnectionError)(code), missedHeartbeats, exports.MAX_MISSED_HEARTBEATS);
11
+ }
@@ -18,6 +18,7 @@ const grpc_error_1 = require("../../../util/grpc-error");
18
18
  const grpc_helpers_1 = require("../../../util/grpc-helpers");
19
19
  const dispatcher_client_1 = require("../dispatcher-client");
20
20
  const heartbeat_controller_1 = require("./heartbeat-controller");
21
+ const heartbeat_severity_1 = require("./heartbeat-severity");
21
22
  const HEARTBEAT_INTERVAL = 4000;
22
23
  const postMessage = (message) => {
23
24
  worker_threads_1.parentPort === null || worker_threads_1.parentPort === void 0 ? void 0 : worker_threads_1.parentPort.postMessage(message);
@@ -25,6 +26,7 @@ const postMessage = (message) => {
25
26
  class HeartbeatWorker {
26
27
  constructor(config, workerId) {
27
28
  this.timeLastHeartbeat = new Date().getTime();
29
+ this.missedHeartbeats = 0;
28
30
  this.workerId = workerId;
29
31
  this.logger = new hatchet_client_1.HatchetLogger(`HeartbeatThread`, config.log_level);
30
32
  this.logger.debug('Heartbeat thread starting...');
@@ -69,6 +71,7 @@ class HeartbeatWorker {
69
71
  message: `Heartbeat sent ${actualInterval}ms ago`,
70
72
  });
71
73
  this.timeLastHeartbeat = now;
74
+ this.missedHeartbeats = 0;
72
75
  }
73
76
  catch (e) {
74
77
  if ((0, grpc_error_1.getGrpcErrorCode)(e) === nice_grpc_1.Status.UNIMPLEMENTED) {
@@ -82,12 +85,16 @@ class HeartbeatWorker {
82
85
  this.stop();
83
86
  return;
84
87
  }
88
+ this.missedHeartbeats += 1;
85
89
  const message = `Failed to send heartbeat: ${(0, hatchet_error_1.getErrorMessage)(e)}`;
86
90
  this.logger.debug(message);
87
- postMessage({
88
- type: 'error',
89
- message,
90
- });
91
+ const severity = (0, heartbeat_severity_1.classifyHeartbeatFailure)((0, grpc_error_1.getGrpcErrorCode)(e), this.missedHeartbeats);
92
+ if (severity !== 'silent') {
93
+ postMessage({
94
+ type: severity,
95
+ message,
96
+ });
97
+ }
91
98
  }
92
99
  });
93
100
  // start with a heartbeat
@@ -0,0 +1,3 @@
1
+ import { FailureSeverity } from '../../util/failure-severity';
2
+ export declare const MAX_TRANSIENT_LISTENER_RETRIES = 3;
3
+ export declare function classifyListenerFailure(e: unknown, retries: number): FailureSeverity;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_TRANSIENT_LISTENER_RETRIES = void 0;
4
+ exports.classifyListenerFailure = classifyListenerFailure;
5
+ const grpc_error_1 = require("../../util/grpc-error");
6
+ const failure_severity_1 = require("../../util/failure-severity");
7
+ exports.MAX_TRANSIENT_LISTENER_RETRIES = 3;
8
+ function classifyListenerFailure(e, retries) {
9
+ const isTransient = e === undefined || (0, grpc_error_1.isConnectionError)((0, grpc_error_1.getGrpcErrorCode)(e));
10
+ return (0, failure_severity_1.classifyRepeatedFailure)(isTransient, retries, exports.MAX_TRANSIENT_LISTENER_RETRIES);
11
+ }
@@ -70,6 +70,7 @@ export declare class DurableListenerClient {
70
70
  private _receiveAbort;
71
71
  private _statusInterval;
72
72
  private _startLock;
73
+ private _consecutiveFailures;
73
74
  onServerEvict: ((durableTaskExternalId: string, invocationCount: number) => void) | undefined;
74
75
  constructor(config: ClientConfig, channel: Channel, factory: ClientFactory);
75
76
  get workerId(): string | undefined;
@@ -40,6 +40,7 @@ const dispatcher_1 = require("../../../protoc/v1/dispatcher");
40
40
  const non_determinism_error_1 = require("../../../util/errors/non-determinism-error");
41
41
  const abort_error_1 = require("../../../util/abort-error");
42
42
  const sleep_1 = __importDefault(require("../../../util/sleep"));
43
+ const listener_severity_1 = require("../../dispatcher/listener-severity");
43
44
  class TTLMap {
44
45
  constructor(ttlMs) {
45
46
  this.ttlMs = ttlMs;
@@ -136,6 +137,7 @@ class DurableListenerClient {
136
137
  // (e.g. an already-satisfied sleep delivered via polling).
137
138
  this._bufferedCompletions = new TTLMap(10000);
138
139
  this._pendingEvictionAcks = new Map();
140
+ this._consecutiveFailures = 0;
139
141
  this.config = config;
140
142
  this.client = factory.create(dispatcher_1.V1DispatcherDefinition, channel);
141
143
  this.logger = config.logger(`DurableListener`, config.log_level);
@@ -159,6 +161,7 @@ class DurableListenerClient {
159
161
  return;
160
162
  this._workerId = workerId;
161
163
  this._running = true;
164
+ this._consecutiveFailures = 0;
162
165
  yield this._connect();
163
166
  this._startStatusPolling();
164
167
  });
@@ -209,11 +212,16 @@ class DurableListenerClient {
209
212
  const stream = this.client.durableTask(this._requestIterator(), {
210
213
  signal: (_d = this._receiveAbort) === null || _d === void 0 ? void 0 : _d.signal,
211
214
  });
215
+ let receivedResponse = false;
212
216
  try {
213
217
  for (var _e = true, stream_1 = (e_1 = void 0, __asyncValues(stream)), stream_1_1; stream_1_1 = yield stream_1.next(), _a = stream_1_1.done, !_a; _e = true) {
214
218
  _c = stream_1_1.value;
215
219
  _e = false;
216
220
  const response = _c;
221
+ if (!receivedResponse) {
222
+ receivedResponse = true;
223
+ this._consecutiveFailures = 0;
224
+ }
217
225
  this._handleResponse(response);
218
226
  }
219
227
  }
@@ -225,7 +233,12 @@ class DurableListenerClient {
225
233
  finally { if (e_1) throw e_1.error; }
226
234
  }
227
235
  if (this._running) {
228
- this.logger.warn(`durable event listener disconnected (EOF), reconnecting in ${DEFAULT_RECONNECT_INTERVAL}ms...`);
236
+ this._consecutiveFailures += 1;
237
+ const message = `durable event listener disconnected (EOF), reconnecting in ${DEFAULT_RECONNECT_INTERVAL}ms...`;
238
+ const eofSeverity = (0, listener_severity_1.classifyListenerFailure)(undefined, this._consecutiveFailures);
239
+ if (eofSeverity !== 'silent') {
240
+ this.logger[eofSeverity](message);
241
+ }
229
242
  this._failPendingAcks(new Error('durable stream disconnected'));
230
243
  yield (0, sleep_1.default)(DEFAULT_RECONNECT_INTERVAL);
231
244
  yield this._connect();
@@ -237,7 +250,12 @@ class DurableListenerClient {
237
250
  this.logger.debug('durable event listener aborted');
238
251
  return;
239
252
  }
240
- this.logger.error(`error in durable event listener: ${(0, hatchet_error_1.getErrorMessage)(e)}`);
253
+ this._consecutiveFailures += 1;
254
+ const message = `error in durable event listener: ${(0, hatchet_error_1.getErrorMessage)(e)}`;
255
+ const errSeverity = (0, listener_severity_1.classifyListenerFailure)(e, this._consecutiveFailures);
256
+ if (errSeverity !== 'silent') {
257
+ this.logger[errSeverity](message);
258
+ }
241
259
  if (this._running) {
242
260
  this._failPendingAcks(new Error(`durable stream error: ${(0, hatchet_error_1.getErrorMessage)(e)}`));
243
261
  yield (0, sleep_1.default)(DEFAULT_RECONNECT_INTERVAL);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hatchet-dev/typescript-sdk",
3
- "version": "1.28.0",
3
+ "version": "1.28.2",
4
4
  "description": "Background task orchestration & visibility for developers",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [
@@ -59,11 +59,11 @@
59
59
  "@anthropic-ai/claude-agent-sdk": "^0.3.148",
60
60
  "@grpc/grpc-js": "^1.14.3",
61
61
  "@modelcontextprotocol/sdk": "^1.29.0",
62
- "@openai/agents": "0.13.2",
62
+ "@openai/agents": "0.14.2",
63
63
  "@opentelemetry/api": "^1.9.0",
64
64
  "@opentelemetry/core": "^2.0.0",
65
- "@opentelemetry/exporter-trace-otlp-grpc": "^0.220.0",
66
- "@opentelemetry/instrumentation": "^0.220.0",
65
+ "@opentelemetry/exporter-trace-otlp-grpc": "^0.221.0",
66
+ "@opentelemetry/instrumentation": "^0.221.0",
67
67
  "@opentelemetry/sdk-trace-base": "^2.0.0",
68
68
  "prom-client": "^15.1.3",
69
69
  "typedoc": "^0.28.17",
@@ -91,11 +91,11 @@
91
91
  "@anthropic-ai/claude-agent-sdk": "^0.3.148",
92
92
  "@grpc/grpc-js": "^1.14.3",
93
93
  "@modelcontextprotocol/sdk": "^1.29.0",
94
- "@openai/agents": "0.13.2",
94
+ "@openai/agents": "0.14.2",
95
95
  "@opentelemetry/api": "^1.9.0",
96
96
  "@opentelemetry/core": "^2.0.0",
97
- "@opentelemetry/exporter-trace-otlp-grpc": "^0.220.0",
98
- "@opentelemetry/instrumentation": "^0.220.0",
97
+ "@opentelemetry/exporter-trace-otlp-grpc": "^0.221.0",
98
+ "@opentelemetry/instrumentation": "^0.221.0",
99
99
  "@opentelemetry/sdk-trace-base": "^2.0.0",
100
100
  "prom-client": "^15.1.3"
101
101
  },
@@ -0,0 +1,2 @@
1
+ export type FailureSeverity = 'silent' | 'warn' | 'error';
2
+ export declare function classifyRepeatedFailure(isTransient: boolean, attempt: number, threshold: number): FailureSeverity;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.classifyRepeatedFailure = classifyRepeatedFailure;
4
+ function classifyRepeatedFailure(isTransient, attempt, threshold) {
5
+ if (!isTransient) {
6
+ return 'error';
7
+ }
8
+ if (attempt >= threshold) {
9
+ return 'error';
10
+ }
11
+ if (attempt > 1) {
12
+ return 'warn';
13
+ }
14
+ return 'silent';
15
+ }
@@ -1,3 +1,9 @@
1
+ /**
2
+ * gRPC codes that typically indicate a transient connectivity problem
3
+ * (server unreachable/restarting) rather than an application-level error.
4
+ */
5
+ export declare const CONNECTION_ERROR_CODES: number[];
6
+ export declare function isConnectionError(code: number | undefined): boolean;
1
7
  /**
2
8
  * Returns the gRPC status code from an unknown value (e.g. from a catch block).
3
9
  * Used for checking Status.CANCELLED, Status.UNAVAILABLE, etc.
@@ -1,7 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CONNECTION_ERROR_CODES = void 0;
4
+ exports.isConnectionError = isConnectionError;
3
5
  exports.getGrpcErrorCode = getGrpcErrorCode;
4
6
  exports.getGrpcErrorDetails = getGrpcErrorDetails;
7
+ const nice_grpc_1 = require("nice-grpc");
8
+ /**
9
+ * gRPC codes that typically indicate a transient connectivity problem
10
+ * (server unreachable/restarting) rather than an application-level error.
11
+ */
12
+ exports.CONNECTION_ERROR_CODES = [nice_grpc_1.Status.UNAVAILABLE, nice_grpc_1.Status.FAILED_PRECONDITION];
13
+ function isConnectionError(code) {
14
+ return code !== undefined && exports.CONNECTION_ERROR_CODES.includes(code);
15
+ }
5
16
  /**
6
17
  * Returns the gRPC status code from an unknown value (e.g. from a catch block).
7
18
  * Used for checking Status.CANCELLED, Status.UNAVAILABLE, etc.
package/v1/declaration.js CHANGED
@@ -594,7 +594,12 @@ exports.WorkflowDeclaration = WorkflowDeclaration;
594
594
  */
595
595
  class TaskWorkflowDeclaration extends BaseWorkflowDeclaration {
596
596
  constructor(options, client) {
597
- super(Object.assign({}, options), client);
597
+ // strip out the concurrency options, because those do not need to be provided to the workflow options.
598
+ // if they are, it will cause the concurrency strategy to have a parentId (from the workflow that doesn't really exist)
599
+ // causing a fallback to the slow concurrency evaluation
600
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
601
+ const { concurrency } = options, workflowOpts = __rest(options, ["concurrency"]);
602
+ super(Object.assign({}, workflowOpts), client);
598
603
  this._standalone_task_name = options.name;
599
604
  this.definition._tasks.push(Object.assign({}, options));
600
605
  }
@@ -11,6 +11,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.batchChildBatchSpawn = exports.batchChildSpawn = exports.childBatch = exports.child = exports.batchCancel = exports.batchBroadcast = exports.batchOrdered = exports.batchSingle = exports.batchLarge = exports.batchKeyedInterval = exports.batchKeyedFailable = exports.batchKeyed = exports.batchSimple = void 0;
13
13
  const hatchet_client_1 = require("../hatchet-client");
14
+ // > Declaring a batch task
14
15
  exports.batchSimple = hatchet_client_1.hatchet.batchTask({
15
16
  name: 'batch-simple',
16
17
  batch: { maxSize: 3, maxInterval: 200 },
@@ -22,6 +23,8 @@ exports.batchSimple = hatchet_client_1.hatchet.batchTask({
22
23
  return out;
23
24
  }),
24
25
  });
26
+ // !!
27
+ // > Declaring a keyed batch task
25
28
  exports.batchKeyed = hatchet_client_1.hatchet.batchTask({
26
29
  name: 'batch-keyed',
27
30
  batch: { maxSize: 2, maxInterval: 200, groupKey: 'input.group' },
@@ -40,6 +43,7 @@ exports.batchKeyed = hatchet_client_1.hatchet.batchTask({
40
43
  return out;
41
44
  }),
42
45
  });
46
+ // !!
43
47
  exports.batchKeyedFailable = hatchet_client_1.hatchet.batchTask({
44
48
  name: 'batch-keyed-failable',
45
49
  batch: { maxSize: 2, maxInterval: 200, groupKey: 'input.group' },
@@ -110,6 +114,7 @@ exports.batchOrdered = hatchet_client_1.hatchet.batchTask({
110
114
  return out;
111
115
  }),
112
116
  });
117
+ // > Declaring a broadcast batch task
113
118
  exports.batchBroadcast = hatchet_client_1.hatchet.batchTask({
114
119
  name: 'batch-broadcast',
115
120
  batch: { maxSize: 10, maxInterval: 2000, broadcastOutput: true },
@@ -118,6 +123,7 @@ exports.batchBroadcast = hatchet_client_1.hatchet.batchTask({
118
123
  return { sum };
119
124
  }),
120
125
  });
126
+ // !!
121
127
  exports.batchCancel = hatchet_client_1.hatchet.batchTask({
122
128
  name: 'batch-cancel',
123
129
  batch: { maxSize: 10, maxInterval: 2000, broadcastOutput: true },
package/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const HATCHET_VERSION = "1.28.0";
1
+ export declare const HATCHET_VERSION = "1.28.2";
package/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.HATCHET_VERSION = void 0;
4
- exports.HATCHET_VERSION = '1.28.0';
4
+ exports.HATCHET_VERSION = '1.28.2';