@hatchet-dev/typescript-sdk 1.29.1 → 1.29.3
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.
- package/clients/dispatcher/heartbeat/heartbeat-severity.d.ts +1 -1
- package/clients/dispatcher/heartbeat/heartbeat-severity.js +4 -2
- package/clients/dispatcher/heartbeat/heartbeat-worker.js +6 -2
- package/package.json +1 -1
- package/util/config-loader/config-loader.js +1 -1
- package/util/grpc-error.d.ts +7 -0
- package/util/grpc-error.js +10 -0
- package/v1/examples/e2e-worker.js +22 -19
- package/v1/examples/subscribe_to_stream/worker.d.ts +1 -0
- package/v1/examples/subscribe_to_stream/worker.js +25 -0
- package/v1/examples/subscribe_to_stream/workflow.d.ts +5 -0
- package/v1/examples/subscribe_to_stream/workflow.js +49 -0
- package/version.d.ts +1 -1
- package/version.js +1 -1
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { FailureSeverity } from '../../../util/failure-severity';
|
|
2
2
|
export declare const MAX_MISSED_HEARTBEATS = 3;
|
|
3
|
-
export declare function classifyHeartbeatFailure(
|
|
3
|
+
export declare function classifyHeartbeatFailure(e: unknown, missedHeartbeats: number): FailureSeverity;
|
|
@@ -6,6 +6,8 @@ const grpc_error_1 = require("../../../util/grpc-error");
|
|
|
6
6
|
const failure_severity_1 = require("../../../util/failure-severity");
|
|
7
7
|
exports.MAX_MISSED_HEARTBEATS = 3;
|
|
8
8
|
// determines whether to immediately error log or wait for additional errors
|
|
9
|
-
function classifyHeartbeatFailure(
|
|
10
|
-
|
|
9
|
+
function classifyHeartbeatFailure(e, missedHeartbeats) {
|
|
10
|
+
const code = (0, grpc_error_1.getGrpcErrorCode)(e);
|
|
11
|
+
const isTransient = (0, grpc_error_1.isConnectionError)(code) || (0, grpc_error_1.isTimeoutOrAbortError)(e);
|
|
12
|
+
return (0, failure_severity_1.classifyRepeatedFailure)(isTransient, missedHeartbeats, exports.MAX_MISSED_HEARTBEATS);
|
|
11
13
|
}
|
|
@@ -20,6 +20,10 @@ const dispatcher_client_1 = require("../dispatcher-client");
|
|
|
20
20
|
const heartbeat_controller_1 = require("./heartbeat-controller");
|
|
21
21
|
const heartbeat_severity_1 = require("./heartbeat-severity");
|
|
22
22
|
const HEARTBEAT_INTERVAL = 4000;
|
|
23
|
+
// Fail fast on a stalled connection: without a deadline, a heartbeat RPC on a dead
|
|
24
|
+
// TCP connection can hang for 50+ seconds, well past the engine's 30s reassignment
|
|
25
|
+
// window, while setInterval stacks overlapping in-flight requests behind it.
|
|
26
|
+
const HEARTBEAT_TIMEOUT = 5000;
|
|
23
27
|
const postMessage = (message) => {
|
|
24
28
|
worker_threads_1.parentPort === null || worker_threads_1.parentPort === void 0 ? void 0 : worker_threads_1.parentPort.postMessage(message);
|
|
25
29
|
};
|
|
@@ -54,7 +58,7 @@ class HeartbeatWorker {
|
|
|
54
58
|
yield this.client.heartbeat({
|
|
55
59
|
workerId: this.workerId,
|
|
56
60
|
heartbeatAt: new Date(),
|
|
57
|
-
});
|
|
61
|
+
}, { signal: AbortSignal.timeout(HEARTBEAT_TIMEOUT) });
|
|
58
62
|
const now = new Date().getTime();
|
|
59
63
|
const actualInterval = now - this.timeLastHeartbeat;
|
|
60
64
|
if (actualInterval > HEARTBEAT_INTERVAL * 1.2) {
|
|
@@ -88,7 +92,7 @@ class HeartbeatWorker {
|
|
|
88
92
|
this.missedHeartbeats += 1;
|
|
89
93
|
const message = `Failed to send heartbeat: ${(0, hatchet_error_1.getErrorMessage)(e)}`;
|
|
90
94
|
this.logger.debug(message);
|
|
91
|
-
const severity = (0, heartbeat_severity_1.classifyHeartbeatFailure)(
|
|
95
|
+
const severity = (0, heartbeat_severity_1.classifyHeartbeatFailure)(e, this.missedHeartbeats);
|
|
92
96
|
if (severity !== 'silent') {
|
|
93
97
|
postMessage({
|
|
94
98
|
type: severity,
|
package/package.json
CHANGED
|
@@ -59,7 +59,7 @@ class ConfigLoader {
|
|
|
59
59
|
port: parseInt(this.env('HATCHET_CLIENT_WORKER_HEALTHCHECK_PORT') || '8001', 10),
|
|
60
60
|
};
|
|
61
61
|
if (!token) {
|
|
62
|
-
throw new Error('
|
|
62
|
+
throw new Error('API token is required. Set it via the HATCHET_CLIENT_TOKEN environment variable. For local development, you can run Hatchet embedded via HatchetEmbeddedClient.init() (imported from @hatchet-dev/typescript-sdk/v1/embedded). More docs here: https://docs.hatchet.run/v1/embedded');
|
|
63
63
|
}
|
|
64
64
|
let grpcBroadcastAddress;
|
|
65
65
|
let apiUrl;
|
package/util/grpc-error.d.ts
CHANGED
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export declare const CONNECTION_ERROR_CODES: number[];
|
|
6
6
|
export declare function isConnectionError(code: number | undefined): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Returns true if `e` is the DOMException thrown when an AbortSignal (e.g. from
|
|
9
|
+
* AbortSignal.timeout()) fires. This rejects the underlying call directly rather than
|
|
10
|
+
* surfacing as a gRPC status code, so it needs to be classified as transient separately
|
|
11
|
+
* from isConnectionError.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isTimeoutOrAbortError(e: unknown): boolean;
|
|
7
14
|
/**
|
|
8
15
|
* Returns the gRPC status code from an unknown value (e.g. from a catch block).
|
|
9
16
|
* Used for checking Status.CANCELLED, Status.UNAVAILABLE, etc.
|
package/util/grpc-error.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CONNECTION_ERROR_CODES = void 0;
|
|
4
4
|
exports.isConnectionError = isConnectionError;
|
|
5
|
+
exports.isTimeoutOrAbortError = isTimeoutOrAbortError;
|
|
5
6
|
exports.getGrpcErrorCode = getGrpcErrorCode;
|
|
6
7
|
exports.getGrpcErrorDetails = getGrpcErrorDetails;
|
|
7
8
|
const nice_grpc_1 = require("nice-grpc");
|
|
@@ -13,6 +14,15 @@ exports.CONNECTION_ERROR_CODES = [nice_grpc_1.Status.UNAVAILABLE, nice_grpc_1.St
|
|
|
13
14
|
function isConnectionError(code) {
|
|
14
15
|
return code !== undefined && exports.CONNECTION_ERROR_CODES.includes(code);
|
|
15
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Returns true if `e` is the DOMException thrown when an AbortSignal (e.g. from
|
|
19
|
+
* AbortSignal.timeout()) fires. This rejects the underlying call directly rather than
|
|
20
|
+
* surfacing as a gRPC status code, so it needs to be classified as transient separately
|
|
21
|
+
* from isConnectionError.
|
|
22
|
+
*/
|
|
23
|
+
function isTimeoutOrAbortError(e) {
|
|
24
|
+
return e instanceof DOMException && (e.name === 'TimeoutError' || e.name === 'AbortError');
|
|
25
|
+
}
|
|
16
26
|
/**
|
|
17
27
|
* Returns the gRPC status code from an unknown value (e.g. from a catch block).
|
|
18
28
|
* Used for checking Status.CANCELLED, Status.UNAVAILABLE, etc.
|
|
@@ -39,12 +39,13 @@ const workflow_18 = require("./run_details/workflow");
|
|
|
39
39
|
const e2e_workflows_1 = require("./simple/e2e-workflows");
|
|
40
40
|
const workflow_19 = require("./batch_assign/workflow");
|
|
41
41
|
const workflow_20 = require("./streaming/workflow");
|
|
42
|
-
const workflow_21 = require("./
|
|
43
|
-
const workflow_22 = require("./
|
|
44
|
-
const workflow_23 = require("./
|
|
45
|
-
const workflow_24 = require("./
|
|
46
|
-
const workflow_25 = require("./
|
|
47
|
-
const workflow_26 = require("./
|
|
42
|
+
const workflow_21 = require("./subscribe_to_stream/workflow");
|
|
43
|
+
const workflow_22 = require("./timeout/workflow");
|
|
44
|
+
const workflow_23 = require("./webhooks/workflow");
|
|
45
|
+
const workflow_24 = require("./child_index/workflow");
|
|
46
|
+
const workflow_25 = require("./support_agent/workflow");
|
|
47
|
+
const workflow_26 = require("./welcome_email/workflow");
|
|
48
|
+
const workflow_27 = require("./pdf_pipeline/workflow");
|
|
48
49
|
const workflows = [
|
|
49
50
|
workflow_1.bulkChild,
|
|
50
51
|
workflow_1.bulkParentWorkflow,
|
|
@@ -96,19 +97,21 @@ const workflows = [
|
|
|
96
97
|
e2e_workflows_1.helloWorld,
|
|
97
98
|
e2e_workflows_1.helloWorldDurable,
|
|
98
99
|
workflow_20.streamingTask,
|
|
99
|
-
workflow_21.
|
|
100
|
-
workflow_21.
|
|
101
|
-
workflow_22.
|
|
102
|
-
|
|
103
|
-
workflow_23.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
workflow_24.
|
|
107
|
-
workflow_24.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
workflow_25.
|
|
111
|
-
|
|
100
|
+
workflow_21.dagStream,
|
|
101
|
+
workflow_21.longStream,
|
|
102
|
+
workflow_22.timeoutTask,
|
|
103
|
+
workflow_22.refreshTimeoutTask,
|
|
104
|
+
workflow_23.webhookWorkflow,
|
|
105
|
+
workflow_24.childIndexChild,
|
|
106
|
+
workflow_24.childIndexParent,
|
|
107
|
+
workflow_24.scenarioTask,
|
|
108
|
+
workflow_24.orchestratorTask,
|
|
109
|
+
workflow_25.supportAgent,
|
|
110
|
+
workflow_25.triageTicket,
|
|
111
|
+
workflow_25.generateReply,
|
|
112
|
+
workflow_25.escalateTicket,
|
|
113
|
+
workflow_26.welcomeEmail,
|
|
114
|
+
workflow_27.pdfPipeline,
|
|
112
115
|
workflow_19.batchSimple,
|
|
113
116
|
workflow_19.batchKeyed,
|
|
114
117
|
workflow_19.batchKeyedFailable,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const hatchet_client_1 = require("../hatchet-client");
|
|
13
|
+
const workflow_1 = require("./workflow");
|
|
14
|
+
function main() {
|
|
15
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
16
|
+
const worker = yield hatchet_client_1.hatchet.worker('subscribe-stream-worker', {
|
|
17
|
+
workflows: [workflow_1.longStream, workflow_1.dagStream],
|
|
18
|
+
slots: 8,
|
|
19
|
+
});
|
|
20
|
+
yield worker.start();
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
if (require.main === module) {
|
|
24
|
+
main();
|
|
25
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const STREAM_CHUNKS: string[];
|
|
2
|
+
export declare const PRE_STREAM_SLEEP_MS = 2000;
|
|
3
|
+
export declare const INTER_CHUNK_MS = 50;
|
|
4
|
+
export declare const longStream: import("../..").TaskWorkflowDeclaration<import("../..").UnknownInputType, void, {}, {}, {}, {}>;
|
|
5
|
+
export declare const dagStream: import("../..").WorkflowDeclaration<import("../..").UnknownInputType, {}, {}>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.dagStream = exports.longStream = exports.INTER_CHUNK_MS = exports.PRE_STREAM_SLEEP_MS = exports.STREAM_CHUNKS = void 0;
|
|
16
|
+
const sleep_1 = __importDefault(require("../../../util/sleep"));
|
|
17
|
+
const hatchet_client_1 = require("../hatchet-client");
|
|
18
|
+
exports.STREAM_CHUNKS = Array.from({ length: 20 }, (_, i) => `c${String(i).padStart(2, '0')}|`);
|
|
19
|
+
exports.PRE_STREAM_SLEEP_MS = 2000;
|
|
20
|
+
exports.INTER_CHUNK_MS = 50;
|
|
21
|
+
exports.longStream = hatchet_client_1.hatchet.task({
|
|
22
|
+
name: 'subscribe-stream-long',
|
|
23
|
+
fn: (_, ctx) => __awaiter(void 0, void 0, void 0, function* () {
|
|
24
|
+
yield (0, sleep_1.default)(exports.PRE_STREAM_SLEEP_MS);
|
|
25
|
+
for (const chunk of exports.STREAM_CHUNKS) {
|
|
26
|
+
yield ctx.putStream(chunk);
|
|
27
|
+
yield (0, sleep_1.default)(exports.INTER_CHUNK_MS);
|
|
28
|
+
}
|
|
29
|
+
}),
|
|
30
|
+
});
|
|
31
|
+
// An onFailure handler makes the run a DAG. DAG QUEUED->RUNNING used to be
|
|
32
|
+
// published as workflow-run-finished, which hung up stream subscribers as
|
|
33
|
+
// soon as the run started.
|
|
34
|
+
exports.dagStream = hatchet_client_1.hatchet.workflow({
|
|
35
|
+
name: 'subscribe-stream-dag',
|
|
36
|
+
});
|
|
37
|
+
exports.dagStream.task({
|
|
38
|
+
name: 'subscribe-stream-dag',
|
|
39
|
+
fn: (_, ctx) => __awaiter(void 0, void 0, void 0, function* () {
|
|
40
|
+
yield (0, sleep_1.default)(exports.PRE_STREAM_SLEEP_MS);
|
|
41
|
+
for (const chunk of exports.STREAM_CHUNKS) {
|
|
42
|
+
yield ctx.putStream(chunk);
|
|
43
|
+
}
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
exports.dagStream.onFailure({
|
|
47
|
+
name: 'subscribe-stream-dag-on-failure',
|
|
48
|
+
fn: () => __awaiter(void 0, void 0, void 0, function* () { }),
|
|
49
|
+
});
|
package/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const HATCHET_VERSION = "1.29.
|
|
1
|
+
export declare const HATCHET_VERSION = "1.29.3";
|
package/version.js
CHANGED