@hatchet-dev/typescript-sdk 0.1.14 → 0.1.16

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.
@@ -1,6 +1,7 @@
1
1
  import { Channel, ClientFactory } from 'nice-grpc';
2
2
  import { CreateWorkflowVersionOpts, WorkflowServiceClient } from '../../protoc/workflows';
3
3
  import { ClientConfig } from '../hatchet-client/client-config';
4
+ import { Logger } from '../../util/logger';
4
5
  import { Api } from '../rest';
5
6
  /**
6
7
  * AdminClient is a client for interacting with the Hatchet Admin API. This allows you to configure, trigger,
@@ -23,6 +24,7 @@ export declare class AdminClient {
23
24
  client: WorkflowServiceClient;
24
25
  api: Api;
25
26
  tenantId: string;
27
+ logger: Logger;
26
28
  constructor(config: ClientConfig, channel: Channel, factory: ClientFactory, api: Api, tenantId: string);
27
29
  /**
28
30
  * Creates a new workflow or updates an existing workflow. If the workflow already exists, Hatchet will automatically
@@ -15,6 +15,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.AdminClient = void 0;
16
16
  const workflows_1 = require("../../protoc/workflows");
17
17
  const hatchet_error_1 = __importDefault(require("../../util/errors/hatchet-error"));
18
+ const logger_1 = require("../../util/logger");
19
+ const retrier_1 = require("../../util/retrier");
18
20
  /**
19
21
  * AdminClient is a client for interacting with the Hatchet Admin API. This allows you to configure, trigger,
20
22
  * and monitor workflows.
@@ -37,6 +39,7 @@ class AdminClient {
37
39
  this.client = factory.create(workflows_1.WorkflowServiceDefinition, channel);
38
40
  this.api = api;
39
41
  this.tenantId = tenantId;
42
+ this.logger = new logger_1.Logger(`Admin`, config.log_level);
40
43
  }
41
44
  /**
42
45
  * Creates a new workflow or updates an existing workflow. If the workflow already exists, Hatchet will automatically
@@ -46,9 +49,7 @@ class AdminClient {
46
49
  put_workflow(workflow) {
47
50
  return __awaiter(this, void 0, void 0, function* () {
48
51
  try {
49
- yield this.client.putWorkflow({
50
- opts: workflow,
51
- });
52
+ yield (0, retrier_1.retrier)(() => __awaiter(this, void 0, void 0, function* () { return this.client.putWorkflow({ opts: workflow }); }), this.logger);
52
53
  }
53
54
  catch (e) {
54
55
  throw new hatchet_error_1.default(e.message);
@@ -1,5 +1,6 @@
1
1
  import { DispatcherClient as PbDispatcherClient, AssignedAction } from '../../protoc/dispatcher';
2
2
  import { ClientConfig } from '../hatchet-client/client-config';
3
+ import { Logger } from '../../util/logger';
3
4
  import { DispatcherClient } from './dispatcher-client';
4
5
  export interface Action {
5
6
  tenantId: string;
@@ -17,10 +18,15 @@ export interface Action {
17
18
  export declare class ActionListener {
18
19
  config: ClientConfig;
19
20
  client: PbDispatcherClient;
20
- listener: AsyncIterable<AssignedAction>;
21
21
  workerId: string;
22
- constructor(client: DispatcherClient, listener: AsyncIterable<AssignedAction>, workerId: string);
22
+ logger: Logger;
23
+ lastConnectionAttempt: number;
24
+ retries: number;
25
+ retryInterval: number;
26
+ retryCount: number;
27
+ constructor(client: DispatcherClient, workerId: string, retryInterval?: number, retryCount?: number);
23
28
  actions: () => AsyncGenerator<Action, void, unknown>;
24
- retrySubscribe(): Promise<void>;
29
+ incrementRetries(): Promise<void>;
30
+ getListenClient(): Promise<AsyncIterable<AssignedAction>>;
25
31
  unregister(): Promise<import("../../protoc/dispatcher").WorkerUnsubscribeResponse>;
26
32
  }
@@ -8,6 +8,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
11
12
  var __asyncValues = (this && this.__asyncValues) || function (o) {
12
13
  if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
13
14
  var m = o[Symbol.asyncIterator], i;
@@ -15,7 +16,6 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
15
16
  function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
16
17
  function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
17
18
  };
18
- var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
19
19
  var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
20
20
  if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
21
21
  var g = generator.apply(thisArg, _arguments || []), i, q = [];
@@ -36,17 +36,22 @@ exports.ActionListener = void 0;
36
36
  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
- const DEFAULT_ACTION_LISTENER_RETRY_INTERVAL = 5; // seconds
39
+ const logger_1 = require("../../util/logger");
40
+ const DEFAULT_ACTION_LISTENER_RETRY_INTERVAL = 5000; // milliseconds
40
41
  const DEFAULT_ACTION_LISTENER_RETRY_COUNT = 5;
41
42
  class ActionListener {
42
- constructor(client, listener, workerId) {
43
+ constructor(client, workerId, retryInterval = DEFAULT_ACTION_LISTENER_RETRY_INTERVAL, retryCount = DEFAULT_ACTION_LISTENER_RETRY_COUNT) {
44
+ this.lastConnectionAttempt = 0;
45
+ this.retries = 0;
46
+ this.retryInterval = DEFAULT_ACTION_LISTENER_RETRY_INTERVAL;
47
+ this.retryCount = DEFAULT_ACTION_LISTENER_RETRY_COUNT;
43
48
  this.actions = () => (function gen(client) {
44
49
  return __asyncGenerator(this, arguments, function* gen_1() {
45
50
  var _a, e_1, _b, _c;
46
51
  while (true) {
47
52
  try {
48
53
  try {
49
- for (var _d = true, _e = (e_1 = void 0, __asyncValues(client.listener)), _f; _f = yield __await(_e.next()), _a = _f.done, !_a; _d = true) {
54
+ for (var _d = true, _e = (e_1 = void 0, __asyncValues(yield __await(client.getListenClient()))), _f; _f = yield __await(_e.next()), _a = _f.done, !_a; _d = true) {
50
55
  _c = _f.value;
51
56
  _d = false;
52
57
  const assignedAction = _c;
@@ -63,38 +68,55 @@ class ActionListener {
63
68
  }
64
69
  }
65
70
  catch (e) {
71
+ // if this is a HatchetError, we should throw this error
72
+ if (e instanceof hatchet_error_1.default) {
73
+ throw e;
74
+ }
66
75
  if (e.code === nice_grpc_1.Status.CANCELLED) {
67
76
  break;
68
77
  }
69
- if (e.code === nice_grpc_1.Status.UNAVAILABLE) {
70
- client.retrySubscribe();
71
- }
72
- break;
78
+ client.incrementRetries();
73
79
  }
74
80
  }
75
81
  });
76
82
  })(this);
77
83
  this.config = client.config;
78
84
  this.client = client.client;
79
- this.listener = listener;
80
85
  this.workerId = workerId;
86
+ this.logger = new logger_1.Logger(`ActionListener`, this.config.log_level);
87
+ this.retryInterval = retryInterval;
88
+ this.retryCount = retryCount;
81
89
  }
82
- retrySubscribe() {
90
+ incrementRetries() {
83
91
  return __awaiter(this, void 0, void 0, function* () {
84
- let retries = 0;
85
- while (retries < DEFAULT_ACTION_LISTENER_RETRY_COUNT) {
86
- try {
87
- yield (0, sleep_1.default)(DEFAULT_ACTION_LISTENER_RETRY_INTERVAL);
88
- this.listener = this.client.listen({
89
- workerId: this.workerId,
90
- });
91
- return;
92
- }
93
- catch (e) {
94
- retries += 1;
95
- }
92
+ this.retries += 1;
93
+ });
94
+ }
95
+ getListenClient() {
96
+ return __awaiter(this, void 0, void 0, function* () {
97
+ const currentTime = Math.floor(Date.now());
98
+ // subtract 1000 from the last connection attempt to account for the time it takes to establish the listener
99
+ if (currentTime - this.lastConnectionAttempt - 1000 > this.retryInterval) {
100
+ this.retries = 0;
101
+ }
102
+ this.lastConnectionAttempt = currentTime;
103
+ if (this.retries > DEFAULT_ACTION_LISTENER_RETRY_COUNT) {
104
+ throw new hatchet_error_1.default(`Could not subscribe to the worker after ${DEFAULT_ACTION_LISTENER_RETRY_COUNT} retries`);
105
+ }
106
+ this.logger.info(`Connecting to Hatchet to establish listener for actions... ${this.retries}/${DEFAULT_ACTION_LISTENER_RETRY_COUNT} (last attempt: ${this.lastConnectionAttempt})`);
107
+ if (this.retries >= 1) {
108
+ yield (0, sleep_1.default)(DEFAULT_ACTION_LISTENER_RETRY_INTERVAL);
109
+ }
110
+ try {
111
+ return this.client.listen({
112
+ workerId: this.workerId,
113
+ });
114
+ }
115
+ catch (e) {
116
+ this.retries += 1;
117
+ this.logger.error(`Attempt ${this.retries}: Failed to connect, retrying...`); // Optional: log retry attempt
118
+ return this.getListenClient();
96
119
  }
97
- throw new hatchet_error_1.default(`Could not subscribe to the worker after ${DEFAULT_ACTION_LISTENER_RETRY_COUNT} retries`);
98
120
  });
99
121
  }
100
122
  unregister() {
@@ -7,6 +7,7 @@ interface GetActionListenerOptions {
7
7
  workerName: string;
8
8
  services: string[];
9
9
  actions: string[];
10
+ maxRuns?: number;
10
11
  }
11
12
  export declare class DispatcherClient {
12
13
  config: ClientConfig;
@@ -16,6 +16,7 @@ exports.DispatcherClient = void 0;
16
16
  const dispatcher_1 = require("../../protoc/dispatcher");
17
17
  const hatchet_error_1 = __importDefault(require("../../util/errors/hatchet-error"));
18
18
  const logger_1 = require("../../util/logger");
19
+ const retrier_1 = require("../../util/retrier");
19
20
  const action_listener_1 = require("./action-listener");
20
21
  class DispatcherClient {
21
22
  constructor(config, channel, factory) {
@@ -27,17 +28,13 @@ class DispatcherClient {
27
28
  return __awaiter(this, void 0, void 0, function* () {
28
29
  // Register the worker
29
30
  const registration = yield this.client.register(Object.assign({}, options));
30
- // Subscribe to the worker
31
- const listener = this.client.listen({
32
- workerId: registration.workerId,
33
- });
34
- return new action_listener_1.ActionListener(this, listener, registration.workerId);
31
+ return new action_listener_1.ActionListener(this, registration.workerId);
35
32
  });
36
33
  }
37
34
  sendStepActionEvent(in_) {
38
35
  return __awaiter(this, void 0, void 0, function* () {
39
36
  try {
40
- return this.client.sendStepActionEvent(in_);
37
+ return (0, retrier_1.retrier)(() => __awaiter(this, void 0, void 0, function* () { return this.client.sendStepActionEvent(in_); }), this.logger);
41
38
  }
42
39
  catch (e) {
43
40
  throw new hatchet_error_1.default(e.message);
@@ -47,7 +44,7 @@ class DispatcherClient {
47
44
  sendGroupKeyActionEvent(in_) {
48
45
  return __awaiter(this, void 0, void 0, function* () {
49
46
  try {
50
- return this.client.sendGroupKeyActionEvent(in_);
47
+ return (0, retrier_1.retrier)(() => __awaiter(this, void 0, void 0, function* () { return this.client.sendGroupKeyActionEvent(in_); }), this.logger);
51
48
  }
52
49
  catch (e) {
53
50
  throw new hatchet_error_1.default(e.message);
@@ -25,8 +25,7 @@ export declare class HatchetClient {
25
25
  tenantId: string;
26
26
  logger: Logger;
27
27
  constructor(config?: Partial<ClientConfig>, options?: HatchetClientOptions, axiosOpts?: AxiosRequestConfig);
28
- static with_host_port(host: string, port: number, config?: Partial<ClientConfig>, options?: HatchetClientOptions): HatchetClient;
29
28
  static init(config?: Partial<ClientConfig>, options?: HatchetClientOptions, axiosConfig?: AxiosRequestConfig): HatchetClient;
30
29
  run(workflow: string | Workflow): Promise<Worker>;
31
- worker(workflow: string | Workflow): Promise<Worker>;
30
+ worker(workflow: string | Workflow, maxRuns?: number): Promise<Worker>;
32
31
  }
@@ -80,11 +80,11 @@ class HatchetClient {
80
80
  // Initializes a new Client instance.
81
81
  // Loads config in the following order: config param > yaml file > env vars
82
82
  var _a;
83
- const loaded = config_loader_1.ConfigLoader.loadClientConfig({
83
+ const loaded = config_loader_1.ConfigLoader.loadClientConfig(config, {
84
84
  path: options === null || options === void 0 ? void 0 : options.config_path,
85
85
  });
86
86
  try {
87
- const valid = client_config_1.ClientConfigSchema.parse(Object.assign(Object.assign({}, loaded), Object.assign(Object.assign({}, config), { tls_config: Object.assign(Object.assign({}, loaded.tls_config), config === null || config === void 0 ? void 0 : config.tls_config) })));
87
+ const valid = client_config_1.ClientConfigSchema.parse(loaded);
88
88
  this.config = valid;
89
89
  }
90
90
  catch (e) {
@@ -97,6 +97,12 @@ class HatchetClient {
97
97
  (_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
98
  this.channel = (0, nice_grpc_1.createChannel)(this.config.host_port, this.credentials, {
99
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,
100
106
  });
101
107
  const clientFactory = (0, nice_grpc_1.createClientFactory)().use(addTokenMiddleware(this.config.token));
102
108
  this.tenantId = this.config.tenant_id;
@@ -108,12 +114,10 @@ class HatchetClient {
108
114
  this.logger = new logger_1.default('HatchetClient', this.config.log_level);
109
115
  this.logger.info(`Initialized HatchetClient`);
110
116
  }
111
- static with_host_port(host, port, config, options) {
112
- return new HatchetClient(Object.assign(Object.assign({}, config), { host_port: `${host}:${port}` }), options);
113
- }
114
117
  static init(config, options, axiosConfig) {
115
118
  return new HatchetClient(config, options, axiosConfig);
116
119
  }
120
+ // @deprecated
117
121
  run(workflow) {
118
122
  return __awaiter(this, void 0, void 0, function* () {
119
123
  const worker = yield this.worker(workflow);
@@ -121,11 +125,12 @@ class HatchetClient {
121
125
  return worker;
122
126
  });
123
127
  }
124
- worker(workflow) {
128
+ worker(workflow, maxRuns) {
125
129
  return __awaiter(this, void 0, void 0, function* () {
126
130
  const name = typeof workflow === 'string' ? workflow : workflow.id;
127
131
  const worker = new worker_1.Worker(this, {
128
132
  name,
133
+ maxRuns,
129
134
  });
130
135
  if (typeof workflow !== 'string') {
131
136
  yield worker.registerWorkflow(workflow);
@@ -16,10 +16,12 @@ export declare class Worker {
16
16
  listener: ActionListener | undefined;
17
17
  futures: Record<Action['stepRunId'], HatchetPromise<any>>;
18
18
  contexts: Record<Action['stepRunId'], Context<any, any>>;
19
+ maxRuns?: number;
19
20
  logger: Logger;
20
21
  constructor(client: HatchetClient, options: {
21
22
  name: string;
22
23
  handleKill?: boolean;
24
+ maxRuns?: number;
23
25
  });
24
26
  registerWorkflow(workflow: Workflow): Promise<void>;
25
27
  registerAction<T, K>(actionId: string, action: StepRunFunction<T, K>): void;
@@ -25,7 +25,7 @@ const dispatcher_1 = require("../../protoc/dispatcher");
25
25
  const hatchet_promise_1 = __importDefault(require("../../util/hatchet-promise/hatchet-promise"));
26
26
  const workflows_1 = require("../../protoc/workflows");
27
27
  const logger_1 = require("../../util/logger");
28
- const sleep_1 = __importDefault(require("../../util/sleep"));
28
+ // import sleep from '../../util/sleep.js';
29
29
  const step_1 = require("../../step");
30
30
  class Worker {
31
31
  constructor(client, options) {
@@ -35,6 +35,7 @@ class Worker {
35
35
  this.client = client;
36
36
  this.name = options.name;
37
37
  this.action_registry = {};
38
+ this.maxRuns = options.maxRuns;
38
39
  process.on('SIGTERM', () => this.exitGracefully());
39
40
  process.on('SIGINT', () => this.exitGracefully());
40
41
  this.killing = false;
@@ -97,7 +98,7 @@ class Worker {
97
98
  }
98
99
  handleStartStepRun(action) {
99
100
  const { actionId } = action;
100
- const context = new step_1.Context(action.actionPayload);
101
+ const context = new step_1.Context(action);
101
102
  this.contexts[action.stepRunId] = context;
102
103
  const step = this.action_registry[actionId];
103
104
  if (!step) {
@@ -146,7 +147,7 @@ class Worker {
146
147
  }
147
148
  handleStartGroupKeyRun(action) {
148
149
  const { actionId } = action;
149
- const context = new step_1.Context(action.actionPayload);
150
+ const context = new step_1.Context(action);
150
151
  const key = action.getGroupKeyRunId;
151
152
  this.contexts[key] = context;
152
153
  this.logger.debug(`Starting group key run ${key}`);
@@ -269,57 +270,46 @@ class Worker {
269
270
  start() {
270
271
  var _a, e_1, _b, _c;
271
272
  return __awaiter(this, void 0, void 0, function* () {
272
- let retries = 0;
273
- while (retries < 5) {
273
+ try {
274
+ this.listener = yield this.client.dispatcher.getActionListener({
275
+ workerName: this.name,
276
+ services: ['default'],
277
+ actions: Object.keys(this.action_registry),
278
+ maxRuns: this.maxRuns,
279
+ });
280
+ const generator = this.listener.actions();
281
+ this.logger.info(`Worker ${this.name} listening for actions`);
274
282
  try {
275
- this.listener = yield this.client.dispatcher.getActionListener({
276
- workerName: this.name,
277
- services: ['default'],
278
- actions: Object.keys(this.action_registry),
279
- });
280
- const generator = this.listener.actions();
281
- this.logger.info(`Worker ${this.name} listening for actions`);
282
- try {
283
- for (var _d = true, generator_1 = (e_1 = void 0, __asyncValues(generator)), generator_1_1; generator_1_1 = yield generator_1.next(), _a = generator_1_1.done, !_a; _d = true) {
284
- _c = generator_1_1.value;
285
- _d = false;
286
- const action = _c;
287
- this.logger.info(`Worker ${this.name} received action ${action.actionId}:${action.actionType}`);
288
- if (action.actionType === dispatcher_1.ActionType.START_STEP_RUN) {
289
- this.handleStartStepRun(action);
290
- }
291
- else if (action.actionType === dispatcher_1.ActionType.CANCEL_STEP_RUN) {
292
- this.handleCancelStepRun(action);
293
- }
294
- else if (action.actionType === dispatcher_1.ActionType.START_GET_GROUP_KEY) {
295
- this.handleStartGroupKeyRun(action);
296
- }
297
- else {
298
- this.logger.error(`Worker ${this.name} received unknown action type ${action.actionType}`);
299
- }
283
+ for (var _d = true, generator_1 = __asyncValues(generator), generator_1_1; generator_1_1 = yield generator_1.next(), _a = generator_1_1.done, !_a; _d = true) {
284
+ _c = generator_1_1.value;
285
+ _d = false;
286
+ const action = _c;
287
+ this.logger.info(`Worker ${this.name} received action ${action.actionId}:${action.actionType}`);
288
+ if (action.actionType === dispatcher_1.ActionType.START_STEP_RUN) {
289
+ this.handleStartStepRun(action);
300
290
  }
301
- }
302
- catch (e_1_1) { e_1 = { error: e_1_1 }; }
303
- finally {
304
- try {
305
- if (!_d && !_a && (_b = generator_1.return)) yield _b.call(generator_1);
291
+ else if (action.actionType === dispatcher_1.ActionType.CANCEL_STEP_RUN) {
292
+ this.handleCancelStepRun(action);
293
+ }
294
+ else if (action.actionType === dispatcher_1.ActionType.START_GET_GROUP_KEY) {
295
+ this.handleStartGroupKeyRun(action);
296
+ }
297
+ else {
298
+ this.logger.error(`Worker ${this.name} received unknown action type ${action.actionType}`);
306
299
  }
307
- finally { if (e_1) throw e_1.error; }
308
300
  }
309
- break;
310
301
  }
311
- catch (e) {
312
- this.logger.error(`Could not start worker: ${e.message}`);
313
- retries += 1;
314
- const wait = 500;
315
- this.logger.error(`Could not start worker, retrying in ${500} seconds`);
316
- yield (0, sleep_1.default)(wait);
302
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
303
+ finally {
304
+ try {
305
+ if (!_d && !_a && (_b = generator_1.return)) yield _b.call(generator_1);
306
+ }
307
+ finally { if (e_1) throw e_1.error; }
317
308
  }
318
309
  }
319
- if (this.killing)
320
- return;
321
- if (retries > 5) {
322
- throw new hatchet_error_1.default('Could not start worker after 5 retries');
310
+ catch (e) {
311
+ this.logger.error(`Could not run worker: ${e.message}`);
312
+ throw new hatchet_error_1.default(`Could not run worker: ${e.message}`);
323
313
  }
324
314
  });
325
315
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hatchet-dev/typescript-sdk",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Background task orchestration & visibility for developers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -31,8 +31,10 @@
31
31
  "worker:simple": "npm run exec -- ./examples/simple-worker.ts",
32
32
  "manual:trigger": "npm run exec -- ./examples/manual-trigger.ts",
33
33
  "worker:dag": "npm run exec -- ./examples/dag-worker.ts",
34
- "worker:concurrency": "npm run exec -- ./examples/concurrency/concurrency-worker.ts",
35
- "event:concurrency": "npm run exec -- ./examples/concurrency/concurrency-event.ts",
34
+ "worker:concurrency": "npm run exec -- ./examples/concurrency/cancel-in-progress/concurrency-worker.ts",
35
+ "event:concurrency": "npm run exec -- ./examples/concurrency/cancel-in-progress/concurrency-event.ts",
36
+ "worker:concurrency:rr": "npm run exec -- ./examples/concurrency/group-round-robin/concurrency-worker.ts",
37
+ "event:concurrency:rr": "npm run exec -- ./examples/concurrency/group-round-robin/concurrency-event.ts",
36
38
  "worker:retries": "npm run exec -- ./examples/retries-worker.ts",
37
39
  "api": "npm run exec -- ./examples/api.ts",
38
40
  "prepublish": "cp package.json dist/package.json;",
@@ -53,6 +53,8 @@ export interface WorkerRegisterRequest {
53
53
  actions: string[];
54
54
  /** (optional) the services for this worker */
55
55
  services: string[];
56
+ /** (optional) the max number of runs this worker can handle */
57
+ maxRuns?: number | undefined;
56
58
  }
57
59
  export interface WorkerRegisterResponse {
58
60
  /** the tenant id */
@@ -253,7 +253,7 @@ function resourceEventTypeToJSON(object) {
253
253
  }
254
254
  exports.resourceEventTypeToJSON = resourceEventTypeToJSON;
255
255
  function createBaseWorkerRegisterRequest() {
256
- return { workerName: "", actions: [], services: [] };
256
+ return { workerName: "", actions: [], services: [], maxRuns: undefined };
257
257
  }
258
258
  exports.WorkerRegisterRequest = {
259
259
  encode(message, writer = _m0.Writer.create()) {
@@ -266,6 +266,9 @@ exports.WorkerRegisterRequest = {
266
266
  for (const v of message.services) {
267
267
  writer.uint32(26).string(v);
268
268
  }
269
+ if (message.maxRuns !== undefined) {
270
+ writer.uint32(32).int32(message.maxRuns);
271
+ }
269
272
  return writer;
270
273
  },
271
274
  decode(input, length) {
@@ -293,6 +296,12 @@ exports.WorkerRegisterRequest = {
293
296
  }
294
297
  message.services.push(reader.string());
295
298
  continue;
299
+ case 4:
300
+ if (tag !== 32) {
301
+ break;
302
+ }
303
+ message.maxRuns = reader.int32();
304
+ continue;
296
305
  }
297
306
  if ((tag & 7) === 4 || tag === 0) {
298
307
  break;
@@ -306,6 +315,7 @@ exports.WorkerRegisterRequest = {
306
315
  workerName: isSet(object.workerName) ? globalThis.String(object.workerName) : "",
307
316
  actions: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.actions) ? object.actions.map((e) => globalThis.String(e)) : [],
308
317
  services: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.services) ? object.services.map((e) => globalThis.String(e)) : [],
318
+ maxRuns: isSet(object.maxRuns) ? globalThis.Number(object.maxRuns) : undefined,
309
319
  };
310
320
  },
311
321
  toJSON(message) {
@@ -320,17 +330,21 @@ exports.WorkerRegisterRequest = {
320
330
  if ((_b = message.services) === null || _b === void 0 ? void 0 : _b.length) {
321
331
  obj.services = message.services;
322
332
  }
333
+ if (message.maxRuns !== undefined) {
334
+ obj.maxRuns = Math.round(message.maxRuns);
335
+ }
323
336
  return obj;
324
337
  },
325
338
  create(base) {
326
339
  return exports.WorkerRegisterRequest.fromPartial(base !== null && base !== void 0 ? base : {});
327
340
  },
328
341
  fromPartial(object) {
329
- var _a, _b, _c;
342
+ var _a, _b, _c, _d;
330
343
  const message = createBaseWorkerRegisterRequest();
331
344
  message.workerName = (_a = object.workerName) !== null && _a !== void 0 ? _a : "";
332
345
  message.actions = ((_b = object.actions) === null || _b === void 0 ? void 0 : _b.map((e) => e)) || [];
333
346
  message.services = ((_c = object.services) === null || _c === void 0 ? void 0 : _c.map((e) => e)) || [];
347
+ message.maxRuns = (_d = object.maxRuns) !== null && _d !== void 0 ? _d : undefined;
334
348
  return message;
335
349
  },
336
350
  };
@@ -5,6 +5,7 @@ export declare enum ConcurrencyLimitStrategy {
5
5
  CANCEL_IN_PROGRESS = 0,
6
6
  DROP_NEWEST = 1,
7
7
  QUEUE_NEWEST = 2,
8
+ GROUP_ROUND_ROBIN = 3,
8
9
  UNRECOGNIZED = -1
9
10
  }
10
11
  export declare function concurrencyLimitStrategyFromJSON(object: any): ConcurrencyLimitStrategy;
@@ -33,6 +33,7 @@ var ConcurrencyLimitStrategy;
33
33
  ConcurrencyLimitStrategy[ConcurrencyLimitStrategy["CANCEL_IN_PROGRESS"] = 0] = "CANCEL_IN_PROGRESS";
34
34
  ConcurrencyLimitStrategy[ConcurrencyLimitStrategy["DROP_NEWEST"] = 1] = "DROP_NEWEST";
35
35
  ConcurrencyLimitStrategy[ConcurrencyLimitStrategy["QUEUE_NEWEST"] = 2] = "QUEUE_NEWEST";
36
+ ConcurrencyLimitStrategy[ConcurrencyLimitStrategy["GROUP_ROUND_ROBIN"] = 3] = "GROUP_ROUND_ROBIN";
36
37
  ConcurrencyLimitStrategy[ConcurrencyLimitStrategy["UNRECOGNIZED"] = -1] = "UNRECOGNIZED";
37
38
  })(ConcurrencyLimitStrategy || (exports.ConcurrencyLimitStrategy = ConcurrencyLimitStrategy = {}));
38
39
  function concurrencyLimitStrategyFromJSON(object) {
@@ -46,6 +47,9 @@ function concurrencyLimitStrategyFromJSON(object) {
46
47
  case 2:
47
48
  case "QUEUE_NEWEST":
48
49
  return ConcurrencyLimitStrategy.QUEUE_NEWEST;
50
+ case 3:
51
+ case "GROUP_ROUND_ROBIN":
52
+ return ConcurrencyLimitStrategy.GROUP_ROUND_ROBIN;
49
53
  case -1:
50
54
  case "UNRECOGNIZED":
51
55
  default:
@@ -61,6 +65,8 @@ function concurrencyLimitStrategyToJSON(object) {
61
65
  return "DROP_NEWEST";
62
66
  case ConcurrencyLimitStrategy.QUEUE_NEWEST:
63
67
  return "QUEUE_NEWEST";
68
+ case ConcurrencyLimitStrategy.GROUP_ROUND_ROBIN:
69
+ return "GROUP_ROUND_ROBIN";
64
70
  case ConcurrencyLimitStrategy.UNRECOGNIZED:
65
71
  default:
66
72
  return "UNRECOGNIZED";
package/step.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as z from 'zod';
2
+ import { Action } from './clients/dispatcher/action-listener';
2
3
  export declare const CreateStepSchema: z.ZodObject<{
3
4
  name: z.ZodString;
4
5
  parents: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
@@ -26,8 +27,9 @@ interface ContextData<T, K> {
26
27
  }
27
28
  export declare class Context<T, K> {
28
29
  data: ContextData<T, K>;
30
+ input: T;
29
31
  controller: AbortController;
30
- constructor(payload: string);
32
+ constructor(action: Action);
31
33
  stepOutput(step: string): string;
32
34
  triggeredByEvent(): boolean;
33
35
  workflowInput(): T;
package/step.js CHANGED
@@ -37,10 +37,18 @@ exports.CreateStepSchema = z.object({
37
37
  retries: z.number().optional(),
38
38
  });
39
39
  class Context {
40
- constructor(payload) {
40
+ constructor(action) {
41
41
  this.controller = new AbortController();
42
42
  try {
43
- this.data = JSON.parse(JSON.parse(payload));
43
+ const data = JSON.parse(JSON.parse(action.actionPayload));
44
+ this.data = data;
45
+ // if this is a getGroupKeyRunId, the data is the workflow input
46
+ if (action.getGroupKeyRunId !== '') {
47
+ this.input = data;
48
+ }
49
+ else {
50
+ this.input = data.input;
51
+ }
44
52
  }
45
53
  catch (e) {
46
54
  throw new hatchet_error_1.default(`Could not parse payload: ${e.message}`);
@@ -60,8 +68,7 @@ class Context {
60
68
  return ((_a = this.data) === null || _a === void 0 ? void 0 : _a.triggered_by) === 'event';
61
69
  }
62
70
  workflowInput() {
63
- var _a;
64
- return (_a = this.data) === null || _a === void 0 ? void 0 : _a.input;
71
+ return this.input;
65
72
  }
66
73
  userData() {
67
74
  var _a;
@@ -4,7 +4,7 @@ interface LoadClientConfigOptions {
4
4
  path?: string;
5
5
  }
6
6
  export declare class ConfigLoader {
7
- static loadClientConfig(config?: LoadClientConfigOptions): Partial<ClientConfig>;
7
+ static loadClientConfig(override?: Partial<ClientConfig>, config?: LoadClientConfigOptions): Partial<ClientConfig>;
8
8
  static get default_yaml_config_path(): string;
9
9
  static createCredentials(config: ClientConfig['tls_config']): ChannelCredentials;
10
10
  static loadYamlConfig(path?: string): ClientConfig | undefined;
@@ -33,17 +33,17 @@ const nice_grpc_1 = require("nice-grpc");
33
33
  const token_1 = require("./token");
34
34
  const DEFAULT_CONFIG_FILE = '.hatchet.yaml';
35
35
  class ConfigLoader {
36
- static loadClientConfig(config) {
37
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w;
36
+ static loadClientConfig(override, config) {
37
+ 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;
38
38
  const yaml = this.loadYamlConfig(config === null || config === void 0 ? void 0 : config.path);
39
- const tlsConfig = {
40
- tls_strategy: (_c = (_b = (_a = yaml === null || yaml === void 0 ? void 0 : yaml.tls_config) === null || _a === void 0 ? void 0 : _a.tls_strategy) !== null && _b !== void 0 ? _b : this.env('HATCHET_CLIENT_TLS_STRATEGY')) !== null && _c !== void 0 ? _c : 'tls',
41
- cert_file: (_e = (_d = yaml === null || yaml === void 0 ? void 0 : yaml.tls_config) === null || _d === void 0 ? void 0 : _d.cert_file) !== null && _e !== void 0 ? _e : this.env('HATCHET_CLIENT_TLS_CERT_FILE'),
42
- key_file: (_g = (_f = yaml === null || yaml === void 0 ? void 0 : yaml.tls_config) === null || _f === void 0 ? void 0 : _f.key_file) !== null && _g !== void 0 ? _g : this.env('HATCHET_CLIENT_TLS_KEY_FILE'),
43
- ca_file: (_j = (_h = yaml === null || yaml === void 0 ? void 0 : yaml.tls_config) === null || _h === void 0 ? void 0 : _h.ca_file) !== null && _j !== void 0 ? _j : this.env('HATCHET_CLIENT_TLS_ROOT_CA_FILE'),
44
- server_name: (_l = (_k = yaml === null || yaml === void 0 ? void 0 : yaml.tls_config) === null || _k === void 0 ? void 0 : _k.server_name) !== null && _l !== void 0 ? _l : this.env('HATCHET_CLIENT_TLS_SERVER_NAME'),
39
+ const tlsConfig = (_a = override === null || override === void 0 ? void 0 : override.tls_config) !== null && _a !== void 0 ? _a : {
40
+ tls_strategy: (_d = (_c = (_b = yaml === null || yaml === void 0 ? void 0 : yaml.tls_config) === null || _b === void 0 ? void 0 : _b.tls_strategy) !== null && _c !== void 0 ? _c : this.env('HATCHET_CLIENT_TLS_STRATEGY')) !== null && _d !== void 0 ? _d : 'tls',
41
+ cert_file: (_f = (_e = yaml === null || yaml === void 0 ? void 0 : yaml.tls_config) === null || _e === void 0 ? void 0 : _e.cert_file) !== null && _f !== void 0 ? _f : this.env('HATCHET_CLIENT_TLS_CERT_FILE'),
42
+ key_file: (_h = (_g = yaml === null || yaml === void 0 ? void 0 : yaml.tls_config) === null || _g === void 0 ? void 0 : _g.key_file) !== null && _h !== void 0 ? _h : this.env('HATCHET_CLIENT_TLS_KEY_FILE'),
43
+ ca_file: (_k = (_j = yaml === null || yaml === void 0 ? void 0 : yaml.tls_config) === null || _j === void 0 ? void 0 : _j.ca_file) !== null && _k !== void 0 ? _k : this.env('HATCHET_CLIENT_TLS_ROOT_CA_FILE'),
44
+ server_name: (_m = (_l = yaml === null || yaml === void 0 ? void 0 : yaml.tls_config) === null || _l === void 0 ? void 0 : _l.server_name) !== null && _m !== void 0 ? _m : this.env('HATCHET_CLIENT_TLS_SERVER_NAME'),
45
45
  };
46
- const token = (_m = yaml === null || yaml === void 0 ? void 0 : yaml.token) !== null && _m !== void 0 ? _m : this.env('HATCHET_CLIENT_TOKEN');
46
+ const token = (_p = (_o = override === null || override === void 0 ? void 0 : override.token) !== null && _o !== void 0 ? _o : yaml === null || yaml === void 0 ? void 0 : yaml.token) !== null && _p !== void 0 ? _p : this.env('HATCHET_CLIENT_TOKEN');
47
47
  let grpcBroadcastAddress;
48
48
  let apiUrl;
49
49
  const tenantId = (0, token_1.getTenantIdFromJWT)(token);
@@ -53,19 +53,20 @@ class ConfigLoader {
53
53
  try {
54
54
  const addresses = (0, token_1.getAddressesFromJWT)(token);
55
55
  grpcBroadcastAddress =
56
- (_p = (_o = yaml === null || yaml === void 0 ? void 0 : yaml.host_port) !== null && _o !== void 0 ? _o : this.env('HATCHET_CLIENT_HOST_PORT')) !== null && _p !== void 0 ? _p : addresses.grpcBroadcastAddress;
57
- apiUrl = (_r = (_q = yaml === null || yaml === void 0 ? void 0 : yaml.api_url) !== null && _q !== void 0 ? _q : this.env('HATCHET_CLIENT_API_URL')) !== null && _r !== void 0 ? _r : addresses.serverUrl;
56
+ (_r = (_q = yaml === null || yaml === void 0 ? void 0 : yaml.host_port) !== null && _q !== void 0 ? _q : this.env('HATCHET_CLIENT_HOST_PORT')) !== null && _r !== void 0 ? _r : addresses.grpcBroadcastAddress;
57
+ apiUrl =
58
+ (_u = (_t = (_s = override === null || override === void 0 ? void 0 : override.api_url) !== null && _s !== void 0 ? _s : yaml === null || yaml === void 0 ? void 0 : yaml.api_url) !== null && _t !== void 0 ? _t : this.env('HATCHET_CLIENT_API_URL')) !== null && _u !== void 0 ? _u : addresses.serverUrl;
58
59
  }
59
60
  catch (e) {
60
- grpcBroadcastAddress = (_s = yaml === null || yaml === void 0 ? void 0 : yaml.host_port) !== null && _s !== void 0 ? _s : this.env('HATCHET_CLIENT_HOST_PORT');
61
- apiUrl = (_t = yaml === null || yaml === void 0 ? void 0 : yaml.api_url) !== null && _t !== void 0 ? _t : this.env('HATCHET_CLIENT_API_URL');
61
+ grpcBroadcastAddress = (_v = yaml === null || yaml === void 0 ? void 0 : yaml.host_port) !== null && _v !== void 0 ? _v : this.env('HATCHET_CLIENT_HOST_PORT');
62
+ apiUrl = (_x = (_w = override === null || override === void 0 ? void 0 : override.api_url) !== null && _w !== void 0 ? _w : yaml === null || yaml === void 0 ? void 0 : yaml.api_url) !== null && _x !== void 0 ? _x : this.env('HATCHET_CLIENT_API_URL');
62
63
  }
63
64
  return {
64
- token: (_u = yaml === null || yaml === void 0 ? void 0 : yaml.token) !== null && _u !== void 0 ? _u : this.env('HATCHET_CLIENT_TOKEN'),
65
+ token: (_z = (_y = override === null || override === void 0 ? void 0 : override.token) !== null && _y !== void 0 ? _y : yaml === null || yaml === void 0 ? void 0 : yaml.token) !== null && _z !== void 0 ? _z : this.env('HATCHET_CLIENT_TOKEN'),
65
66
  host_port: grpcBroadcastAddress,
66
67
  api_url: apiUrl,
67
68
  tls_config: tlsConfig,
68
- log_level: (_w = (_v = yaml === null || yaml === void 0 ? void 0 : yaml.log_level) !== null && _v !== void 0 ? _v : this.env('HATCHET_CLIENT_LOG_LEVEL')) !== null && _w !== void 0 ? _w : 'INFO',
69
+ log_level: (_2 = (_1 = (_0 = override === null || override === void 0 ? void 0 : override.log_level) !== null && _0 !== void 0 ? _0 : yaml === null || yaml === void 0 ? void 0 : yaml.log_level) !== null && _1 !== void 0 ? _1 : this.env('HATCHET_CLIENT_LOG_LEVEL')) !== null && _2 !== void 0 ? _2 : 'INFO',
69
70
  tenant_id: tenantId,
70
71
  };
71
72
  }
@@ -0,0 +1,2 @@
1
+ import { Logger } from './logger';
2
+ export declare function retrier<T>(fn: () => Promise<T>, logger: Logger, retries?: number, interval?: number): Promise<T>;
@@ -0,0 +1,36 @@
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.retrier = void 0;
16
+ const sleep_1 = __importDefault(require("./sleep"));
17
+ const DEFAULT_RETRY_INTERVAL = 5;
18
+ const DEFAULT_RETRY_COUNT = 5;
19
+ function retrier(fn, logger, retries = DEFAULT_RETRY_COUNT, interval = DEFAULT_RETRY_INTERVAL) {
20
+ return __awaiter(this, void 0, void 0, function* () {
21
+ let lastError;
22
+ // eslint-disable-next-line no-plusplus
23
+ for (let i = 0; i < retries; i++) {
24
+ try {
25
+ return yield fn();
26
+ }
27
+ catch (e) {
28
+ lastError = e;
29
+ logger.error(`Error: ${e.message}`);
30
+ yield (0, sleep_1.default)(interval * 1000);
31
+ }
32
+ }
33
+ throw lastError;
34
+ });
35
+ }
36
+ exports.retrier = retrier;