@hatchet-dev/typescript-sdk 0.5.1 → 0.5.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.
@@ -2,6 +2,7 @@ import { DispatcherClient as PbDispatcherClient, AssignedAction } from '../../pr
2
2
  import { ClientConfig } from '../hatchet-client/client-config';
3
3
  import { Logger } from '../../util/logger';
4
4
  import { DispatcherClient } from './dispatcher-client';
5
+ import { Heartbeat } from './heartbeat/heartbeat-controller';
5
6
  declare enum ListenStrategy {
6
7
  LISTEN_STRATEGY_V1 = 1,
7
8
  LISTEN_STRATEGY_V2 = 2
@@ -31,14 +32,12 @@ export declare class ActionListener {
31
32
  retryCount: number;
32
33
  done: boolean;
33
34
  listenStrategy: ListenStrategy;
34
- heartbeatInterval: any;
35
+ heartbeat: Heartbeat;
35
36
  constructor(client: DispatcherClient, workerId: string, retryInterval?: number, retryCount?: number);
36
37
  actions: () => AsyncGenerator<Action, void, unknown>;
37
38
  setListenStrategy(strategy: ListenStrategy): Promise<void>;
38
39
  getListenStrategy(): Promise<ListenStrategy>;
39
40
  incrementRetries(): Promise<void>;
40
- heartbeat(): Promise<void>;
41
- closeHeartbeat(): void;
42
41
  getListenClient(): Promise<AsyncIterable<AssignedAction>>;
43
42
  unregister(): Promise<import("../../protoc/dispatcher").WorkerUnsubscribeResponse>;
44
43
  }
@@ -37,6 +37,7 @@ const nice_grpc_1 = require("nice-grpc");
37
37
  const sleep_1 = __importDefault(require("../../util/sleep"));
38
38
  const hatchet_error_1 = __importDefault(require("../../util/errors/hatchet-error"));
39
39
  const logger_1 = require("../../util/logger");
40
+ const heartbeat_controller_1 = require("./heartbeat/heartbeat-controller");
40
41
  const DEFAULT_ACTION_LISTENER_RETRY_INTERVAL = 5000; // milliseconds
41
42
  const DEFAULT_ACTION_LISTENER_RETRY_COUNT = 20;
42
43
  // eslint-disable-next-line no-shadow
@@ -91,8 +92,13 @@ class ActionListener {
91
92
  }
92
93
  client.incrementRetries();
93
94
  client.logger.error(`Listener encountered an error: ${e.message}`);
94
- client.logger.info(`Retrying in ${client.retryInterval}ms...`);
95
- yield __await((0, sleep_1.default)(client.retryInterval));
95
+ if (client.retries > 1) {
96
+ client.logger.info(`Retrying in ${client.retryInterval}ms...`);
97
+ yield __await((0, sleep_1.default)(client.retryInterval));
98
+ }
99
+ else {
100
+ client.logger.info(`Retrying`);
101
+ }
96
102
  }
97
103
  }
98
104
  });
@@ -103,6 +109,7 @@ class ActionListener {
103
109
  this.logger = new logger_1.Logger(`ActionListener`, this.config.log_level);
104
110
  this.retryInterval = retryInterval;
105
111
  this.retryCount = retryCount;
112
+ this.heartbeat = new heartbeat_controller_1.Heartbeat(client, workerId);
106
113
  }
107
114
  setListenStrategy(strategy) {
108
115
  return __awaiter(this, void 0, void 0, function* () {
@@ -119,39 +126,6 @@ class ActionListener {
119
126
  this.retries += 1;
120
127
  });
121
128
  }
122
- heartbeat() {
123
- return __awaiter(this, void 0, void 0, function* () {
124
- if (this.heartbeatInterval) {
125
- return;
126
- }
127
- const beat = () => __awaiter(this, void 0, void 0, function* () {
128
- try {
129
- yield this.client.heartbeat({
130
- workerId: this.workerId,
131
- heartbeatAt: new Date(),
132
- });
133
- }
134
- catch (e) {
135
- if (e.code === nice_grpc_1.Status.UNIMPLEMENTED) {
136
- // break out of interval
137
- this.logger.error('Heartbeat not implemented, closing heartbeat');
138
- this.closeHeartbeat();
139
- return;
140
- }
141
- this.logger.error(`Failed to send heartbeat: ${e.message}`);
142
- }
143
- });
144
- // start with a heartbeat
145
- yield beat();
146
- this.heartbeatInterval = setInterval(beat, 4000);
147
- });
148
- }
149
- closeHeartbeat() {
150
- if (this.heartbeatInterval) {
151
- clearInterval(this.heartbeatInterval);
152
- this.heartbeatInterval = null;
153
- }
154
- }
155
129
  getListenClient() {
156
130
  return __awaiter(this, void 0, void 0, function* () {
157
131
  const currentTime = Math.floor(Date.now());
@@ -172,14 +146,14 @@ class ActionListener {
172
146
  const result = this.client.listen({
173
147
  workerId: this.workerId,
174
148
  });
175
- this.logger.info('Connection established using LISTEN_STRATEGY_V1');
149
+ this.logger.green('Connection established using LISTEN_STRATEGY_V1');
176
150
  return result;
177
151
  }
178
152
  const res = this.client.listenV2({
179
153
  workerId: this.workerId,
180
154
  });
181
- yield this.heartbeat();
182
- this.logger.info('Connection established using LISTEN_STRATEGY_V2');
155
+ yield this.heartbeat.start();
156
+ this.logger.green('Connection established using LISTEN_STRATEGY_V2');
183
157
  return res;
184
158
  }
185
159
  catch (e) {
@@ -187,7 +161,7 @@ class ActionListener {
187
161
  this.logger.error(`Attempt ${this.retries}: Failed to connect, retrying...`);
188
162
  if (e.code === nice_grpc_1.Status.UNAVAILABLE) {
189
163
  // Connection lost, reset heartbeat interval and retry connection
190
- this.closeHeartbeat();
164
+ this.heartbeat.stop();
191
165
  return this.getListenClient();
192
166
  }
193
167
  throw e;
@@ -197,7 +171,7 @@ class ActionListener {
197
171
  unregister() {
198
172
  return __awaiter(this, void 0, void 0, function* () {
199
173
  this.done = true;
200
- this.closeHeartbeat();
174
+ this.heartbeat.stop();
201
175
  try {
202
176
  return yield this.client.unsubscribe({
203
177
  workerId: this.workerId,
@@ -0,0 +1,16 @@
1
+ /// <reference types="node" />
2
+ import { Logger } from '../../../util/logger';
3
+ import { DispatcherClient as PbDispatcherClient } from '../../../protoc/dispatcher';
4
+ import { Worker } from 'worker_threads';
5
+ import { ClientConfig } from '../../hatchet-client';
6
+ import { DispatcherClient } from '../dispatcher-client';
7
+ export declare class Heartbeat {
8
+ config: ClientConfig;
9
+ client: PbDispatcherClient;
10
+ workerId: string;
11
+ logger: Logger;
12
+ heartbeatWorker: Worker | undefined;
13
+ constructor(client: DispatcherClient, workerId: string);
14
+ start(): Promise<void>;
15
+ stop(): Promise<void>;
16
+ }
@@ -0,0 +1,47 @@
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.Heartbeat = void 0;
16
+ const logger_1 = require("../../../util/logger");
17
+ const path_1 = __importDefault(require("path"));
18
+ const thread_helper_1 = require("../../../util/thread-helper");
19
+ class Heartbeat {
20
+ constructor(client, workerId) {
21
+ this.config = client.config;
22
+ this.client = client.client;
23
+ this.workerId = workerId;
24
+ this.logger = new logger_1.Logger(`HeartbeatController`, this.config.log_level);
25
+ }
26
+ start() {
27
+ return __awaiter(this, void 0, void 0, function* () {
28
+ if (!this.heartbeatWorker) {
29
+ this.heartbeatWorker = (0, thread_helper_1.runThreaded)(path_1.default.join(__dirname, './heartbeat-worker'), {
30
+ workerData: {
31
+ config: this.config,
32
+ workerId: this.workerId,
33
+ },
34
+ });
35
+ }
36
+ });
37
+ }
38
+ stop() {
39
+ var _a, _b;
40
+ return __awaiter(this, void 0, void 0, function* () {
41
+ (_a = this.heartbeatWorker) === null || _a === void 0 ? void 0 : _a.postMessage('stop');
42
+ (_b = this.heartbeatWorker) === null || _b === void 0 ? void 0 : _b.terminate();
43
+ this.heartbeatWorker = undefined;
44
+ });
45
+ }
46
+ }
47
+ exports.Heartbeat = Heartbeat;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,75 @@
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 worker_threads_1 = require("worker_threads");
13
+ const logger_1 = require("../../../util/logger");
14
+ const hatchet_client_1 = require("../../hatchet-client");
15
+ const config_loader_1 = require("../../../util/config-loader");
16
+ const nice_grpc_1 = require("nice-grpc");
17
+ const dispatcher_client_1 = require("../dispatcher-client");
18
+ const HEARTBEAT_INTERVAL = 4000;
19
+ class HeartbeatWorker {
20
+ constructor(config, workerId) {
21
+ this.timeLastHeartbeat = new Date().getTime();
22
+ this.workerId = workerId;
23
+ this.logger = new logger_1.Logger(`Heartbeat`, config.log_level);
24
+ const credentials = config_loader_1.ConfigLoader.createCredentials(config.tls_config);
25
+ const clientFactory = (0, nice_grpc_1.createClientFactory)().use((0, hatchet_client_1.addTokenMiddleware)(config.token));
26
+ const dispatcher = new dispatcher_client_1.DispatcherClient(config, (0, hatchet_client_1.channelFactory)(config, credentials), clientFactory);
27
+ this.client = dispatcher.client;
28
+ }
29
+ start() {
30
+ return __awaiter(this, void 0, void 0, function* () {
31
+ if (this.heartbeatInterval) {
32
+ return;
33
+ }
34
+ const beat = () => __awaiter(this, void 0, void 0, function* () {
35
+ try {
36
+ this.logger.debug('Heartbeat sending...');
37
+ yield this.client.heartbeat({
38
+ workerId: this.workerId,
39
+ heartbeatAt: new Date(),
40
+ });
41
+ const now = new Date().getTime();
42
+ const actualInterval = now - this.timeLastHeartbeat;
43
+ if (actualInterval > HEARTBEAT_INTERVAL * 1.2) {
44
+ this.logger.warn(`Heartbeat interval delay (${actualInterval}ms >> ${HEARTBEAT_INTERVAL}ms)`);
45
+ }
46
+ this.logger.debug(`Heartbeat sent ${actualInterval}ms ago`);
47
+ this.timeLastHeartbeat = now;
48
+ }
49
+ catch (e) {
50
+ if (e.code === nice_grpc_1.Status.UNIMPLEMENTED) {
51
+ // break out of interval
52
+ this.logger.error('Heartbeat not implemented, closing heartbeat');
53
+ this.stop();
54
+ return;
55
+ }
56
+ this.logger.error(`Failed to send heartbeat: ${e.message}`);
57
+ }
58
+ });
59
+ // start with a heartbeat
60
+ yield beat();
61
+ this.heartbeatInterval = setInterval(beat, HEARTBEAT_INTERVAL);
62
+ });
63
+ }
64
+ stop() {
65
+ if (this.heartbeatInterval) {
66
+ clearInterval(this.heartbeatInterval);
67
+ this.heartbeatInterval = null;
68
+ }
69
+ }
70
+ }
71
+ const heartbeat = new HeartbeatWorker(worker_threads_1.workerData.config, worker_threads_1.workerData.workerId);
72
+ heartbeat.start();
73
+ worker_threads_1.parentPort === null || worker_threads_1.parentPort === void 0 ? void 0 : worker_threads_1.parentPort.on('stop', () => {
74
+ heartbeat.stop();
75
+ });
@@ -1,7 +1,7 @@
1
1
  import { EventClient } from '../event/event-client';
2
2
  import { DispatcherClient } from '../dispatcher/dispatcher-client';
3
3
  import { AdminClient } from '../admin/admin-client';
4
- import { ChannelCredentials } from 'nice-grpc';
4
+ import { CallOptions, ChannelCredentials, ClientMiddlewareCall } from 'nice-grpc';
5
5
  import { Workflow } from '../../workflow';
6
6
  import { Worker } from '../worker';
7
7
  import Logger from '../../util/logger/logger';
@@ -13,6 +13,8 @@ export interface HatchetClientOptions {
13
13
  config_path?: string;
14
14
  credentials?: ChannelCredentials;
15
15
  }
16
+ export declare const channelFactory: (config: ClientConfig, credentials: ChannelCredentials) => import("nice-grpc").Channel;
17
+ export declare const addTokenMiddleware: (token: string) => <Request_1, Response_1>(call: ClientMiddlewareCall<Request_1, Response_1>, options: CallOptions) => AsyncGenerator<Awaited<Response_1>, Awaited<Response_1> | undefined, undefined>;
16
18
  export declare class HatchetClient {
17
19
  config: ClientConfig;
18
20
  credentials: ChannelCredentials;
@@ -37,7 +37,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  return (mod && mod.__esModule) ? mod : { "default": mod };
38
38
  };
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
- exports.HatchetClient = void 0;
40
+ exports.HatchetClient = exports.addTokenMiddleware = exports.channelFactory = void 0;
41
41
  const zod_1 = require("zod");
42
42
  const config_loader_1 = require("../../util/config-loader");
43
43
  const event_client_1 = require("../event/event-client");
@@ -49,6 +49,16 @@ const logger_1 = __importDefault(require("../../util/logger/logger"));
49
49
  const client_config_1 = require("./client-config");
50
50
  const listener_client_1 = require("../listener/listener-client");
51
51
  const rest_1 = __importDefault(require("../rest"));
52
+ const channelFactory = (config, credentials) => (0, nice_grpc_1.createChannel)(config.host_port, credentials, {
53
+ 'grpc.ssl_target_name_override': config.tls_config.server_name,
54
+ 'grpc.keepalive_timeout_ms': 60 * 1000,
55
+ 'grpc.client_idle_timeout_ms': 60 * 1000,
56
+ // Send keepalive pings every 10 seconds, default is 2 hours.
57
+ 'grpc.keepalive_time_ms': 10 * 1000,
58
+ // Allow keepalive pings when there are no gRPC calls.
59
+ 'grpc.keepalive_permit_without_calls': 1,
60
+ });
61
+ exports.channelFactory = channelFactory;
52
62
  const addTokenMiddleware = (token) => function _(call, options) {
53
63
  return __asyncGenerator(this, arguments, function* _1() {
54
64
  var _a, e_1, _b, _c;
@@ -75,6 +85,7 @@ const addTokenMiddleware = (token) => function _(call, options) {
75
85
  return yield __await(undefined);
76
86
  });
77
87
  };
88
+ exports.addTokenMiddleware = addTokenMiddleware;
78
89
  class HatchetClient {
79
90
  constructor(config, options, axiosOpts) {
80
91
  // Initializes a new Client instance.
@@ -95,22 +106,13 @@ class HatchetClient {
95
106
  }
96
107
  this.credentials =
97
108
  (_a = options === null || options === void 0 ? void 0 : options.credentials) !== null && _a !== void 0 ? _a : config_loader_1.ConfigLoader.createCredentials(this.config.tls_config);
98
- const channelFactory = () => (0, nice_grpc_1.createChannel)(this.config.host_port, this.credentials, {
99
- 'grpc.ssl_target_name_override': this.config.tls_config.server_name,
100
- 'grpc.keepalive_timeout_ms': 60 * 1000,
101
- 'grpc.client_idle_timeout_ms': 60 * 1000,
102
- // Send keepalive pings every 10 seconds, default is 2 hours.
103
- 'grpc.keepalive_time_ms': 10 * 1000,
104
- // Allow keepalive pings when there are no gRPC calls.
105
- 'grpc.keepalive_permit_without_calls': 1,
106
- });
107
- const clientFactory = (0, nice_grpc_1.createClientFactory)().use(addTokenMiddleware(this.config.token));
109
+ const clientFactory = (0, nice_grpc_1.createClientFactory)().use((0, exports.addTokenMiddleware)(this.config.token));
108
110
  this.tenantId = this.config.tenant_id;
109
111
  this.api = (0, rest_1.default)(this.config.api_url, this.config.token, axiosOpts);
110
- this.event = new event_client_1.EventClient(this.config, channelFactory(), clientFactory);
111
- this.dispatcher = new dispatcher_client_1.DispatcherClient(this.config, channelFactory(), clientFactory);
112
- this.admin = new admin_client_1.AdminClient(this.config, channelFactory(), clientFactory, this.api, this.tenantId);
113
- this.listener = new listener_client_1.ListenerClient(this.config, channelFactory(), clientFactory, this.api);
112
+ this.event = new event_client_1.EventClient(this.config, (0, exports.channelFactory)(this.config, this.credentials), clientFactory);
113
+ this.dispatcher = new dispatcher_client_1.DispatcherClient(this.config, (0, exports.channelFactory)(this.config, this.credentials), clientFactory);
114
+ this.admin = new admin_client_1.AdminClient(this.config, (0, exports.channelFactory)(this.config, this.credentials), clientFactory, this.api, this.tenantId);
115
+ this.listener = new listener_client_1.ListenerClient(this.config, (0, exports.channelFactory)(this.config, this.credentials), clientFactory, this.api);
114
116
  this.logger = new logger_1.default('HatchetClient', this.config.log_level);
115
117
  this.logger.info(`Initialized HatchetClient`);
116
118
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hatchet-dev/typescript-sdk",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Background task orchestration & visibility for developers",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [
package/step.js CHANGED
@@ -90,7 +90,8 @@ class ChildWorkflowRef {
90
90
  reject(event.results);
91
91
  return;
92
92
  }
93
- resolve(event.results);
93
+ const result = event.results.reduce((acc, r) => (Object.assign(Object.assign({}, acc), { [r.stepReadableId]: JSON.parse(r.output || '{}') })), {});
94
+ resolve(result);
94
95
  return;
95
96
  }
96
97
  }
@@ -6,6 +6,7 @@ export declare class Logger {
6
6
  private log;
7
7
  debug(message: string): void;
8
8
  info(message: string): void;
9
+ green(message: string): void;
9
10
  warn(message: string): void;
10
11
  error(message: string): void;
11
12
  }
@@ -15,22 +15,34 @@ class Logger {
15
15
  this.logLevel = logLevel;
16
16
  this.context = context;
17
17
  }
18
- log(level, message) {
18
+ log(level, message, color = '33') {
19
19
  if (LogLevelEnum[level] >= LogLevelEnum[this.logLevel]) {
20
- console.log(`🪓 [${level}/${this.context}] ${message}`);
20
+ const time = new Date().toLocaleString('en-US', {
21
+ month: '2-digit',
22
+ day: '2-digit',
23
+ year: '2-digit',
24
+ hour: '2-digit',
25
+ minute: '2-digit',
26
+ second: '2-digit',
27
+ });
28
+ // eslint-disable-next-line no-console
29
+ console.log(`🪓 ${process.pid} | ${time} \x1b[${color}m [${level}/${this.context}] ${message}\x1b[0m`);
21
30
  }
22
31
  }
23
32
  debug(message) {
24
- this.log('DEBUG', message);
33
+ this.log('DEBUG', message, '35');
25
34
  }
26
35
  info(message) {
27
- this.log('INFO', message);
36
+ this.log('INFO', message, '30');
37
+ }
38
+ green(message) {
39
+ this.log('INFO', message, '32');
28
40
  }
29
41
  warn(message) {
30
- this.log('WARN', message);
42
+ this.log('WARN', message, '93');
31
43
  }
32
44
  error(message) {
33
- this.log('ERROR', message);
45
+ this.log('ERROR', message, '91');
34
46
  }
35
47
  }
36
48
  exports.Logger = Logger;
@@ -0,0 +1,3 @@
1
+ /// <reference types="node" />
2
+ import { Worker, WorkerOptions } from 'worker_threads';
3
+ export declare function runThreaded(scriptPath: string, options: WorkerOptions): Worker;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runThreaded = void 0;
7
+ const worker_threads_1 = require("worker_threads");
8
+ const path_1 = __importDefault(require("path"));
9
+ function runThreaded(scriptPath, options) {
10
+ const resolvedPath = require.resolve(scriptPath);
11
+ const isTs = /\.ts$/.test(resolvedPath);
12
+ // NOTE: if the file is typescript, we are in the SDK dev environment and need to so some funky work.
13
+ // otherwise, we pass the file directly to the worker.
14
+ const ex = isTs
15
+ ? `
16
+ const wk = require('worker_threads');
17
+ require('tsconfig-paths/register');
18
+ require('ts-node').register({
19
+ "include": ["src/**/*.ts"],
20
+ "exclude": ["./dist"],
21
+ "compilerOptions": {
22
+ "types": ["node"],
23
+ "target": "es2016",
24
+ "esModuleInterop": true,
25
+ "module": "commonjs",
26
+ "rootDir": "${path_1.default.join(__dirname, '../../../')}",
27
+ }
28
+ });
29
+ let file = '${resolvedPath}';
30
+ require(file);
31
+ `
32
+ : resolvedPath;
33
+ return new worker_threads_1.Worker(ex, Object.assign(Object.assign({}, options), { eval: isTs }));
34
+ }
35
+ exports.runThreaded = runThreaded;
36
+ // execArgv: ? ['--require', 'ts-node/register'] : undefined,