@hatchet-dev/typescript-sdk 1.26.1 → 1.27.0

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.
@@ -111,7 +111,7 @@ class AdminClient {
111
111
  limit,
112
112
  duration,
113
113
  });
114
- }), this.logger);
114
+ }), this.logger, this.config.retrier);
115
115
  }
116
116
  catch (e) {
117
117
  throw (0, hatchet_error_1.toHatchetError)(e);
@@ -54,14 +54,14 @@ class EventClient {
54
54
  priority: options.priority,
55
55
  scope: options.scope,
56
56
  };
57
- try {
58
- const e = this.retrier(() => __awaiter(this, void 0, void 0, function* () { return this.client.push(req); }), this.logger);
57
+ return this.retrier(() => __awaiter(this, void 0, void 0, function* () { return this.client.push(req); }), this.logger, this.config.retrier)
58
+ .then((result) => {
59
59
  this.logger.info(`Event pushed: ${namespacedType}`);
60
- return e;
61
- }
62
- catch (e) {
60
+ return result;
61
+ })
62
+ .catch((e) => {
63
63
  throw (0, hatchet_error_1.toHatchetError)(e);
64
- }
64
+ });
65
65
  }
66
66
  /**
67
67
  * @important This method is instrumented by HatchetInstrumentor._patchBulkPushEvent.
@@ -85,14 +85,14 @@ class EventClient {
85
85
  const req = {
86
86
  events,
87
87
  };
88
- try {
89
- const res = this.retrier(() => __awaiter(this, void 0, void 0, function* () { return this.client.bulkPush(req); }), this.logger);
88
+ return this.retrier(() => __awaiter(this, void 0, void 0, function* () { return this.client.bulkPush(req); }), this.logger, this.config.retrier)
89
+ .then((result) => {
90
90
  this.logger.info(`Bulk events pushed for type: ${namespacedType}`);
91
- return res;
92
- }
93
- catch (e) {
91
+ return result;
92
+ })
93
+ .catch((e) => {
94
94
  throw (0, hatchet_error_1.toHatchetError)(e);
95
- }
95
+ });
96
96
  }
97
97
  putLog(taskRunExternalId, log, level, taskRetryCount, metadata) {
98
98
  return __awaiter(this, void 0, void 0, function* () {
@@ -2,6 +2,12 @@ import { ChannelCredentials } from 'nice-grpc';
2
2
  import { z } from 'zod/v4';
3
3
  import type { Context } from '../../v1/client/worker/context';
4
4
  import { Logger, LogLevel } from '../../util/logger';
5
+ export declare const RetrierConfigSchema: z.ZodObject<{
6
+ maxAttempts: z.ZodOptional<z.ZodNumber>;
7
+ initialInterval: z.ZodOptional<z.ZodNumber>;
8
+ maxJitter: z.ZodOptional<z.ZodNumber>;
9
+ }, z.core.$strip>;
10
+ export type RetrierConfig = z.infer<typeof RetrierConfigSchema>;
5
11
  declare const ClientTLSConfigSchema: z.ZodObject<{
6
12
  tls_strategy: z.ZodOptional<z.ZodEnum<{
7
13
  tls: "tls";
@@ -60,6 +66,11 @@ export declare const ClientConfigSchema: z.ZodObject<{
60
66
  cancellation_warning_threshold: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
61
67
  grpc_max_recv_message_length: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
62
68
  grpc_max_send_message_length: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
69
+ retrier: z.ZodOptional<z.ZodObject<{
70
+ maxAttempts: z.ZodOptional<z.ZodNumber>;
71
+ initialInterval: z.ZodOptional<z.ZodNumber>;
72
+ maxJitter: z.ZodOptional<z.ZodNumber>;
73
+ }, z.core.$strip>>;
63
74
  }, z.core.$strip>;
64
75
  export type LogConstructor = (context: string, logLevel?: LogLevel) => Logger;
65
76
  /**
@@ -1,7 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ClientConfigSchema = exports.OpenTelemetryConfigSchema = void 0;
3
+ exports.ClientConfigSchema = exports.OpenTelemetryConfigSchema = exports.RetrierConfigSchema = void 0;
4
4
  const v4_1 = require("zod/v4");
5
+ exports.RetrierConfigSchema = v4_1.z.object({
6
+ maxAttempts: v4_1.z.number().int().positive().optional(),
7
+ initialInterval: v4_1.z.number().positive().optional(),
8
+ maxJitter: v4_1.z.number().nonnegative().optional(),
9
+ });
5
10
  const ClientTLSConfigSchema = v4_1.z.object({
6
11
  tls_strategy: v4_1.z.enum(['tls', 'mtls', 'none']).optional(),
7
12
  cert_file: v4_1.z.string().optional(),
@@ -65,4 +70,5 @@ exports.ClientConfigSchema = v4_1.z.object({
65
70
  .positive()
66
71
  .optional()
67
72
  .default(4 * 1024 * 1024),
73
+ retrier: exports.RetrierConfigSchema.optional(),
68
74
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hatchet-dev/typescript-sdk",
3
- "version": "1.26.1",
3
+ "version": "1.27.0",
4
4
  "description": "Background task orchestration & visibility for developers",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [
@@ -45,6 +45,13 @@ export declare enum ConcurrencyLimitStrategy {
45
45
  }
46
46
  export declare function concurrencyLimitStrategyFromJSON(object: any): ConcurrencyLimitStrategy;
47
47
  export declare function concurrencyLimitStrategyToJSON(object: ConcurrencyLimitStrategy): string;
48
+ export declare enum IdempotencyMethod {
49
+ TTL = 0,
50
+ STATUS = 1,
51
+ UNRECOGNIZED = -1
52
+ }
53
+ export declare function idempotencyMethodFromJSON(object: any): IdempotencyMethod;
54
+ export declare function idempotencyMethodToJSON(object: IdempotencyMethod): string;
48
55
  export interface CancelTasksRequest {
49
56
  /** a list of external UUIDs */
50
57
  externalIds: string[];
@@ -136,8 +143,10 @@ export interface CreateWorkflowVersionRequest {
136
143
  export interface IdempotencyConfig {
137
144
  /** a CEL expression for determining the idempotency key for workflow runs */
138
145
  expression: string;
139
- /** time-to-live for idempotency keys in milliseconds */
146
+ /** time-to-live for idempotency keys in milliseconds. if the method is `STATUS`, this is a "fallback" - the longest the key can live before it's evicted */
140
147
  ttlMs: number;
148
+ /** the method to use for idempotency, defaults to TTL */
149
+ method?: IdempotencyMethod | undefined;
141
150
  }
142
151
  export interface IdempotencyCollisionError {
143
152
  /** the external ID of the existing workflow run that caused the collision */
@@ -5,7 +5,7 @@
5
5
  // protoc v3.19.1
6
6
  // source: v1/workflows.proto
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.AdminServiceDefinition = exports.GetRunDetailsResponse_TaskRunsEntry = exports.GetRunDetailsResponse = exports.TaskRunDetail = exports.GetRunDetailsRequest = exports.CreateWorkflowVersionResponse = exports.CreateTaskRateLimit = exports.CreateTaskOpts_SlotRequestsEntry = exports.CreateTaskOpts_WorkerLabelsEntry = exports.CreateTaskOpts = exports.Concurrency = exports.DefaultFilter = exports.BulkTriggerIdempotencyCollisionError = exports.IdempotencyCollisionError = exports.IdempotencyConfig = exports.CreateWorkflowVersionRequest = exports.BranchDurableTaskResponse = exports.BranchDurableTaskRequest = exports.TriggerWorkflowRunResponse = exports.TriggerWorkflowRunRequest_DesiredWorkerLabelsEntry = exports.TriggerWorkflowRunRequest = exports.ReplayTasksResponse = exports.CancelTasksResponse = exports.TasksFilter = exports.ReplayTasksRequest = exports.CancelTasksRequest = exports.ConcurrencyLimitStrategy = exports.RunStatus = exports.RateLimitDuration = exports.StickyStrategy = exports.protobufPackage = void 0;
8
+ exports.AdminServiceDefinition = exports.GetRunDetailsResponse_TaskRunsEntry = exports.GetRunDetailsResponse = exports.TaskRunDetail = exports.GetRunDetailsRequest = exports.CreateWorkflowVersionResponse = exports.CreateTaskRateLimit = exports.CreateTaskOpts_SlotRequestsEntry = exports.CreateTaskOpts_WorkerLabelsEntry = exports.CreateTaskOpts = exports.Concurrency = exports.DefaultFilter = exports.BulkTriggerIdempotencyCollisionError = exports.IdempotencyCollisionError = exports.IdempotencyConfig = exports.CreateWorkflowVersionRequest = exports.BranchDurableTaskResponse = exports.BranchDurableTaskRequest = exports.TriggerWorkflowRunResponse = exports.TriggerWorkflowRunRequest_DesiredWorkerLabelsEntry = exports.TriggerWorkflowRunRequest = exports.ReplayTasksResponse = exports.CancelTasksResponse = exports.TasksFilter = exports.ReplayTasksRequest = exports.CancelTasksRequest = exports.IdempotencyMethod = exports.ConcurrencyLimitStrategy = exports.RunStatus = exports.RateLimitDuration = exports.StickyStrategy = exports.protobufPackage = void 0;
9
9
  exports.stickyStrategyFromJSON = stickyStrategyFromJSON;
10
10
  exports.stickyStrategyToJSON = stickyStrategyToJSON;
11
11
  exports.rateLimitDurationFromJSON = rateLimitDurationFromJSON;
@@ -14,6 +14,8 @@ exports.runStatusFromJSON = runStatusFromJSON;
14
14
  exports.runStatusToJSON = runStatusToJSON;
15
15
  exports.concurrencyLimitStrategyFromJSON = concurrencyLimitStrategyFromJSON;
16
16
  exports.concurrencyLimitStrategyToJSON = concurrencyLimitStrategyToJSON;
17
+ exports.idempotencyMethodFromJSON = idempotencyMethodFromJSON;
18
+ exports.idempotencyMethodToJSON = idempotencyMethodToJSON;
17
19
  /* eslint-disable */
18
20
  const wire_1 = require("@bufbuild/protobuf/wire");
19
21
  const timestamp_1 = require("../google/protobuf/timestamp");
@@ -218,6 +220,37 @@ function concurrencyLimitStrategyToJSON(object) {
218
220
  return 'UNRECOGNIZED';
219
221
  }
220
222
  }
223
+ var IdempotencyMethod;
224
+ (function (IdempotencyMethod) {
225
+ IdempotencyMethod[IdempotencyMethod["TTL"] = 0] = "TTL";
226
+ IdempotencyMethod[IdempotencyMethod["STATUS"] = 1] = "STATUS";
227
+ IdempotencyMethod[IdempotencyMethod["UNRECOGNIZED"] = -1] = "UNRECOGNIZED";
228
+ })(IdempotencyMethod || (exports.IdempotencyMethod = IdempotencyMethod = {}));
229
+ function idempotencyMethodFromJSON(object) {
230
+ switch (object) {
231
+ case 0:
232
+ case 'TTL':
233
+ return IdempotencyMethod.TTL;
234
+ case 1:
235
+ case 'STATUS':
236
+ return IdempotencyMethod.STATUS;
237
+ case -1:
238
+ case 'UNRECOGNIZED':
239
+ default:
240
+ return IdempotencyMethod.UNRECOGNIZED;
241
+ }
242
+ }
243
+ function idempotencyMethodToJSON(object) {
244
+ switch (object) {
245
+ case IdempotencyMethod.TTL:
246
+ return 'TTL';
247
+ case IdempotencyMethod.STATUS:
248
+ return 'STATUS';
249
+ case IdempotencyMethod.UNRECOGNIZED:
250
+ default:
251
+ return 'UNRECOGNIZED';
252
+ }
253
+ }
221
254
  function createBaseCancelTasksRequest() {
222
255
  return { externalIds: [], filter: undefined };
223
256
  }
@@ -1422,7 +1455,7 @@ exports.CreateWorkflowVersionRequest = {
1422
1455
  },
1423
1456
  };
1424
1457
  function createBaseIdempotencyConfig() {
1425
- return { expression: '', ttlMs: 0 };
1458
+ return { expression: '', ttlMs: 0, method: undefined };
1426
1459
  }
1427
1460
  exports.IdempotencyConfig = {
1428
1461
  encode(message, writer = new wire_1.BinaryWriter()) {
@@ -1432,6 +1465,9 @@ exports.IdempotencyConfig = {
1432
1465
  if (message.ttlMs !== 0) {
1433
1466
  writer.uint32(16).int64(message.ttlMs);
1434
1467
  }
1468
+ if (message.method !== undefined) {
1469
+ writer.uint32(24).int32(message.method);
1470
+ }
1435
1471
  return writer;
1436
1472
  },
1437
1473
  decode(input, length) {
@@ -1455,6 +1491,13 @@ exports.IdempotencyConfig = {
1455
1491
  message.ttlMs = longToNumber(reader.int64());
1456
1492
  continue;
1457
1493
  }
1494
+ case 3: {
1495
+ if (tag !== 24) {
1496
+ break;
1497
+ }
1498
+ message.method = reader.int32();
1499
+ continue;
1500
+ }
1458
1501
  }
1459
1502
  if ((tag & 7) === 4 || tag === 0) {
1460
1503
  break;
@@ -1471,6 +1514,7 @@ exports.IdempotencyConfig = {
1471
1514
  : isSet(object.ttl_ms)
1472
1515
  ? globalThis.Number(object.ttl_ms)
1473
1516
  : 0,
1517
+ method: isSet(object.method) ? idempotencyMethodFromJSON(object.method) : undefined,
1474
1518
  };
1475
1519
  },
1476
1520
  toJSON(message) {
@@ -1481,16 +1525,20 @@ exports.IdempotencyConfig = {
1481
1525
  if (message.ttlMs !== 0) {
1482
1526
  obj.ttlMs = Math.round(message.ttlMs);
1483
1527
  }
1528
+ if (message.method !== undefined) {
1529
+ obj.method = idempotencyMethodToJSON(message.method);
1530
+ }
1484
1531
  return obj;
1485
1532
  },
1486
1533
  create(base) {
1487
1534
  return exports.IdempotencyConfig.fromPartial(base !== null && base !== void 0 ? base : {});
1488
1535
  },
1489
1536
  fromPartial(object) {
1490
- var _a, _b;
1537
+ var _a, _b, _c;
1491
1538
  const message = createBaseIdempotencyConfig();
1492
1539
  message.expression = (_a = object.expression) !== null && _a !== void 0 ? _a : '';
1493
1540
  message.ttlMs = (_b = object.ttlMs) !== null && _b !== void 0 ? _b : 0;
1541
+ message.method = (_c = object.method) !== null && _c !== void 0 ? _c : undefined;
1494
1542
  return message;
1495
1543
  },
1496
1544
  };
@@ -6,6 +6,7 @@ interface LoadClientConfigOptions {
6
6
  export declare class ConfigLoader {
7
7
  static loadClientConfig(override?: Partial<ClientConfig>, config?: LoadClientConfigOptions): Partial<ClientConfig>;
8
8
  private static parseIntEnv;
9
+ private static parseFloatEnv;
9
10
  private static parseJsonArray;
10
11
  static get default_yaml_config_path(): string;
11
12
  static createCredentials(config: ClientConfig['tls_config']): ChannelCredentials;
@@ -44,7 +44,7 @@ const token_1 = require("./token");
44
44
  const DEFAULT_CONFIG_FILE = '.hatchet.yaml';
45
45
  class ConfigLoader {
46
46
  static loadClientConfig(override, config) {
47
- 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, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18;
47
+ 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, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30;
48
48
  const yaml = this.loadYamlConfig(config === null || config === void 0 ? void 0 : config.path);
49
49
  const tlsConfig = (_a = override === null || override === void 0 ? void 0 : override.tls_config) !== null && _a !== void 0 ? _a : {
50
50
  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',
@@ -74,7 +74,7 @@ class ConfigLoader {
74
74
  apiUrl =
75
75
  (_x = (_w = (_v = override === null || override === void 0 ? void 0 : override.api_url) !== null && _v !== void 0 ? _v : yaml === null || yaml === void 0 ? void 0 : yaml.api_url) !== null && _w !== void 0 ? _w : this.env('HATCHET_CLIENT_API_URL')) !== null && _x !== void 0 ? _x : addresses.serverUrl;
76
76
  }
77
- catch (_19) {
77
+ catch (_31) {
78
78
  grpcBroadcastAddress =
79
79
  (_z = (_y = override === null || override === void 0 ? void 0 : override.host_port) !== null && _y !== void 0 ? _y : yaml === null || yaml === void 0 ? void 0 : yaml.host_port) !== null && _z !== void 0 ? _z : this.env('HATCHET_CLIENT_HOST_PORT');
80
80
  apiUrl = (_1 = (_0 = override === null || override === void 0 ? void 0 : override.api_url) !== null && _0 !== void 0 ? _0 : yaml === null || yaml === void 0 ? void 0 : yaml.api_url) !== null && _1 !== void 0 ? _1 : this.env('HATCHET_CLIENT_API_URL');
@@ -90,21 +90,13 @@ class ConfigLoader {
90
90
  };
91
91
  const grpcMaxRecvMessageLength = (_8 = (_7 = (_6 = override === null || override === void 0 ? void 0 : override.grpc_max_recv_message_length) !== null && _6 !== void 0 ? _6 : yaml === null || yaml === void 0 ? void 0 : yaml.grpc_max_recv_message_length) !== null && _7 !== void 0 ? _7 : this.parseIntEnv('HATCHET_CLIENT_GRPC_MAX_RECV_MESSAGE_LENGTH')) !== null && _8 !== void 0 ? _8 : 4 * 1024 * 1024;
92
92
  const grpcMaxSendMessageLength = (_11 = (_10 = (_9 = override === null || override === void 0 ? void 0 : override.grpc_max_send_message_length) !== null && _9 !== void 0 ? _9 : yaml === null || yaml === void 0 ? void 0 : yaml.grpc_max_send_message_length) !== null && _10 !== void 0 ? _10 : this.parseIntEnv('HATCHET_CLIENT_GRPC_MAX_SEND_MESSAGE_LENGTH')) !== null && _11 !== void 0 ? _11 : 4 * 1024 * 1024;
93
- return {
94
- token: (_13 = (_12 = override === null || override === void 0 ? void 0 : override.token) !== null && _12 !== void 0 ? _12 : yaml === null || yaml === void 0 ? void 0 : yaml.token) !== null && _13 !== void 0 ? _13 : this.env('HATCHET_CLIENT_TOKEN'),
95
- host_port: grpcBroadcastAddress,
96
- api_url: apiUrl,
97
- tls_config: tlsConfig,
98
- healthcheck: healthCheckConfig,
99
- log_level: (_16 = (_15 = (_14 = override === null || override === void 0 ? void 0 : override.log_level) !== null && _14 !== void 0 ? _14 : yaml === null || yaml === void 0 ? void 0 : yaml.log_level) !== null && _15 !== void 0 ? _15 : this.env('HATCHET_CLIENT_LOG_LEVEL')) !== null && _16 !== void 0 ? _16 : 'INFO',
100
- tenant_id: tenantId,
101
- namespace: namespace ? `${namespace}`.toLowerCase() : '',
102
- otel: otelConfig,
103
- grpc_max_recv_message_length: grpcMaxRecvMessageLength,
104
- grpc_max_send_message_length: grpcMaxSendMessageLength,
105
- cancellation_grace_period: (_17 = override === null || override === void 0 ? void 0 : override.cancellation_grace_period) !== null && _17 !== void 0 ? _17 : yaml === null || yaml === void 0 ? void 0 : yaml.cancellation_grace_period,
106
- cancellation_warning_threshold: (_18 = override === null || override === void 0 ? void 0 : override.cancellation_warning_threshold) !== null && _18 !== void 0 ? _18 : yaml === null || yaml === void 0 ? void 0 : yaml.cancellation_warning_threshold,
93
+ const retrierConfig = {
94
+ maxAttempts: (_15 = (_13 = (_12 = override === null || override === void 0 ? void 0 : override.retrier) === null || _12 === void 0 ? void 0 : _12.maxAttempts) !== null && _13 !== void 0 ? _13 : (_14 = yaml === null || yaml === void 0 ? void 0 : yaml.retrier) === null || _14 === void 0 ? void 0 : _14.maxAttempts) !== null && _15 !== void 0 ? _15 : this.parseIntEnv('HATCHET_CLIENT_RETRIER_MAX_ATTEMPTS'),
95
+ initialInterval: (_19 = (_17 = (_16 = override === null || override === void 0 ? void 0 : override.retrier) === null || _16 === void 0 ? void 0 : _16.initialInterval) !== null && _17 !== void 0 ? _17 : (_18 = yaml === null || yaml === void 0 ? void 0 : yaml.retrier) === null || _18 === void 0 ? void 0 : _18.initialInterval) !== null && _19 !== void 0 ? _19 : this.parseFloatEnv('HATCHET_CLIENT_RETRIER_INITIAL_INTERVAL'),
96
+ maxJitter: (_23 = (_21 = (_20 = override === null || override === void 0 ? void 0 : override.retrier) === null || _20 === void 0 ? void 0 : _20.maxJitter) !== null && _21 !== void 0 ? _21 : (_22 = yaml === null || yaml === void 0 ? void 0 : yaml.retrier) === null || _22 === void 0 ? void 0 : _22.maxJitter) !== null && _23 !== void 0 ? _23 : this.parseIntEnv('HATCHET_CLIENT_RETRIER_MAX_JITTER'),
107
97
  };
98
+ const hasRetrier = Object.values(retrierConfig).some((v) => v !== undefined);
99
+ return Object.assign({ token: (_25 = (_24 = override === null || override === void 0 ? void 0 : override.token) !== null && _24 !== void 0 ? _24 : yaml === null || yaml === void 0 ? void 0 : yaml.token) !== null && _25 !== void 0 ? _25 : this.env('HATCHET_CLIENT_TOKEN'), host_port: grpcBroadcastAddress, api_url: apiUrl, tls_config: tlsConfig, healthcheck: healthCheckConfig, log_level: (_28 = (_27 = (_26 = override === null || override === void 0 ? void 0 : override.log_level) !== null && _26 !== void 0 ? _26 : yaml === null || yaml === void 0 ? void 0 : yaml.log_level) !== null && _27 !== void 0 ? _27 : this.env('HATCHET_CLIENT_LOG_LEVEL')) !== null && _28 !== void 0 ? _28 : 'INFO', tenant_id: tenantId, namespace: namespace ? `${namespace}`.toLowerCase() : '', otel: otelConfig, grpc_max_recv_message_length: grpcMaxRecvMessageLength, grpc_max_send_message_length: grpcMaxSendMessageLength, cancellation_grace_period: (_29 = override === null || override === void 0 ? void 0 : override.cancellation_grace_period) !== null && _29 !== void 0 ? _29 : yaml === null || yaml === void 0 ? void 0 : yaml.cancellation_grace_period, cancellation_warning_threshold: (_30 = override === null || override === void 0 ? void 0 : override.cancellation_warning_threshold) !== null && _30 !== void 0 ? _30 : yaml === null || yaml === void 0 ? void 0 : yaml.cancellation_warning_threshold }, (hasRetrier ? { retrier: retrierConfig } : {}));
108
100
  }
109
101
  static parseIntEnv(envName) {
110
102
  const value = this.env(envName);
@@ -115,6 +107,16 @@ class ConfigLoader {
115
107
  }
116
108
  return parseInt(value, 10);
117
109
  }
110
+ static parseFloatEnv(envName) {
111
+ const value = this.env(envName);
112
+ if (value === undefined || value === '')
113
+ return undefined;
114
+ const parsed = parseFloat(value.trim());
115
+ if (isNaN(parsed) || parsed <= 0) {
116
+ throw new Error(`Invalid value for ${envName}: "${value}". Expected a positive number.`);
117
+ }
118
+ return parsed;
119
+ }
118
120
  static parseJsonArray(value) {
119
121
  try {
120
122
  const parsed = JSON.parse(value);
package/util/retrier.d.ts CHANGED
@@ -1,2 +1,11 @@
1
1
  import { Logger } from './logger';
2
- export declare function retrier<T>(fn: () => Promise<T>, logger: Logger, retries?: number, interval?: number, shouldRetry?: (e: unknown) => boolean): Promise<T>;
2
+ export declare const DEFAULT_RETRY_INTERVAL = 0.1;
3
+ export declare const DEFAULT_RETRY_COUNT = 8;
4
+ export declare const DEFAULT_MAX_JITTER = 100;
5
+ export interface RetrierConfig {
6
+ maxAttempts?: number;
7
+ initialInterval?: number;
8
+ maxJitter?: number;
9
+ shouldRetry?: (e: unknown) => boolean;
10
+ }
11
+ export declare function retrier<T>(fn: () => Promise<T>, logger: Logger, config?: RetrierConfig): Promise<T>;
package/util/retrier.js CHANGED
@@ -12,13 +12,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.DEFAULT_MAX_JITTER = exports.DEFAULT_RETRY_COUNT = exports.DEFAULT_RETRY_INTERVAL = void 0;
15
16
  exports.retrier = retrier;
16
17
  const sleep_1 = __importDefault(require("./sleep"));
17
- const DEFAULT_RETRY_INTERVAL = 0.1; // seconds
18
- const DEFAULT_RETRY_COUNT = 8;
19
- const MAX_JITTER = 100; // milliseconds
20
- function retrier(fn_1, logger_1) {
21
- return __awaiter(this, arguments, void 0, function* (fn, logger, retries = DEFAULT_RETRY_COUNT, interval = DEFAULT_RETRY_INTERVAL, shouldRetry = () => true) {
18
+ exports.DEFAULT_RETRY_INTERVAL = 0.1; // seconds
19
+ exports.DEFAULT_RETRY_COUNT = 8;
20
+ exports.DEFAULT_MAX_JITTER = 100; // milliseconds
21
+ function retrier(fn, logger, config) {
22
+ return __awaiter(this, void 0, void 0, function* () {
23
+ var _a, _b, _c, _d;
24
+ const retries = Math.max(1, (_a = config === null || config === void 0 ? void 0 : config.maxAttempts) !== null && _a !== void 0 ? _a : exports.DEFAULT_RETRY_COUNT);
25
+ const interval = (_b = config === null || config === void 0 ? void 0 : config.initialInterval) !== null && _b !== void 0 ? _b : exports.DEFAULT_RETRY_INTERVAL;
26
+ const maxJitter = (_c = config === null || config === void 0 ? void 0 : config.maxJitter) !== null && _c !== void 0 ? _c : exports.DEFAULT_MAX_JITTER;
27
+ const shouldRetry = (_d = config === null || config === void 0 ? void 0 : config.shouldRetry) !== null && _d !== void 0 ? _d : (() => true);
22
28
  let lastError;
23
29
  for (let i = 0; i < retries; i++) {
24
30
  try {
@@ -30,11 +36,11 @@ function retrier(fn_1, logger_1) {
30
36
  }
31
37
  lastError = e instanceof Error ? e : new Error(String(e));
32
38
  logger.error(`Error: ${lastError.message}`);
33
- // Calculate exponential backoff with random jitter
34
- const exponentialDelay = interval * 2 ** i * 1000;
35
- const jitter = Math.random() * MAX_JITTER;
36
- const totalDelay = exponentialDelay + jitter;
37
- yield (0, sleep_1.default)(totalDelay);
39
+ if (i < retries - 1) {
40
+ const exponentialDelay = interval * 2 ** i * 1000;
41
+ const jitter = Math.random() * maxJitter;
42
+ yield (0, sleep_1.default)(exponentialDelay + jitter);
43
+ }
38
44
  }
39
45
  }
40
46
  throw lastError;
@@ -184,7 +184,7 @@ class AdminClient {
184
184
  trailerMetadata = trailer;
185
185
  },
186
186
  });
187
- }), this.logger, undefined, undefined, (e) => !isNiceGrpcAlreadyExists(e));
187
+ }), this.logger, Object.assign(Object.assign({}, this.config.retrier), { shouldRetry: (e) => !isNiceGrpcAlreadyExists(e) }));
188
188
  const id = resp.workflowRunId;
189
189
  const ref = new workflow_run_ref_1.default(id, this.listenerClient, this.runs, options === null || options === void 0 ? void 0 : options.parentId, options === null || options === void 0 ? void 0 : options._standaloneTaskName);
190
190
  yield ref.getWorkflowRunId();
@@ -242,8 +242,8 @@ class AdminClient {
242
242
  bulkTrailerMetadata = trailer;
243
243
  },
244
244
  });
245
- }), this.logger, undefined, undefined, (e) => !isNiceGrpcAlreadyExists(e) &&
246
- !(isGrpcServiceError(e) && e.code === grpc_js_1.status.ALREADY_EXISTS));
245
+ }), this.logger, Object.assign(Object.assign({}, this.config.retrier), { shouldRetry: (e) => !isNiceGrpcAlreadyExists(e) &&
246
+ !(isGrpcServiceError(e) && e.code === grpc_js_1.status.ALREADY_EXISTS) }));
247
247
  this.logger.debug(`batch ${batchIndex + 1} of ${batches.length}`);
248
248
  // Map the results back to their original indices
249
249
  const batchResults = bulkTriggerWorkflowResponse.workflowRunIds.map((resp, index) => {
@@ -277,7 +277,7 @@ class AdminClient {
277
277
  limit,
278
278
  duration,
279
279
  };
280
- yield (0, retrier_1.retrier)(() => __awaiter(this, void 0, void 0, function* () { return this.workflowsGrpc.putRateLimit(request); }), this.logger);
280
+ yield (0, retrier_1.retrier)(() => __awaiter(this, void 0, void 0, function* () { return this.workflowsGrpc.putRateLimit(request); }), this.logger, this.config.retrier);
281
281
  });
282
282
  }
283
283
  }
@@ -63,6 +63,7 @@ const dispatcher_1 = require("../../../protoc/dispatcher");
63
63
  const hatchet_promise_1 = __importStar(require("../../../util/hatchet-promise/hatchet-promise"));
64
64
  const workflows_1 = require("../../../protoc/workflows");
65
65
  const logger_1 = require("../../../util/logger");
66
+ const workflows_2 = require("../../../protoc/v1/workflows");
66
67
  const task_1 = require("../../task");
67
68
  const transformer_1 = require("../../conditions/transformer");
68
69
  const z = __importStar(require("zod/v4"));
@@ -338,7 +339,12 @@ class InternalWorker {
338
339
  idempotency: workflow.idempotency
339
340
  ? {
340
341
  expression: workflow.idempotency.expression,
341
- ttlMs: workflow.idempotency.ttlMs,
342
+ ttlMs: workflow.idempotency.strategy === 'status'
343
+ ? workflow.idempotency.fallbackTtlMs
344
+ : workflow.idempotency.ttlMs,
345
+ method: workflow.idempotency.strategy === 'status'
346
+ ? workflows_2.IdempotencyMethod.STATUS
347
+ : workflows_2.IdempotencyMethod.TTL,
342
348
  }
343
349
  : undefined,
344
350
  });
@@ -8,3 +8,6 @@ export declare const idempotentTask: import("../..").TaskWorkflowDeclaration<Ide
8
8
  export declare const idempotentTaskShortWindow: import("../..").TaskWorkflowDeclaration<IdempotencyInput, {
9
9
  result: string;
10
10
  }, {}, {}, {}, {}>;
11
+ export declare const idempotentStatusBasedTask: import("../..").TaskWorkflowDeclaration<IdempotencyInput, {
12
+ result: string;
13
+ }, {}, {}, {}, {}>;
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.idempotentTaskShortWindow = exports.idempotentTask = exports.EVENT_KEY = void 0;
12
+ exports.idempotentStatusBasedTask = exports.idempotentTaskShortWindow = exports.idempotentTask = exports.EVENT_KEY = void 0;
13
13
  const hatchet_client_1 = require("../hatchet-client");
14
14
  exports.EVENT_KEY = 'ts-e2e:idempotency-example';
15
15
  // > idempotency
@@ -37,3 +37,16 @@ exports.idempotentTaskShortWindow = hatchet_client_1.hatchet.task({
37
37
  return { result: `Hello, world from task ${input.id}` };
38
38
  }),
39
39
  });
40
+ // > status-based-idempotency
41
+ exports.idempotentStatusBasedTask = hatchet_client_1.hatchet.task({
42
+ name: 'ts-e2e-idempotent-status-based-task',
43
+ idempotency: {
44
+ strategy: 'status',
45
+ expression: 'input.id',
46
+ fallbackTtlMs: 10000,
47
+ },
48
+ fn: (input) => __awaiter(void 0, void 0, void 0, function* () {
49
+ return { result: `Hello, world from task ${input.id}` };
50
+ }),
51
+ });
52
+ // !!
package/v1/task.d.ts CHANGED
@@ -56,10 +56,22 @@ export type TTLBasedIdempotencyConfig = BaseIdempotencyConfig & {
56
56
  */
57
57
  ttlMs: number;
58
58
  };
59
+ /**
60
+ * Status-based idempotency: keeps the idempotency key alive until the associated run
61
+ * reaches a terminal status. `fallbackTtlMs` caps how long the key can live before it's evicted.
62
+ */
63
+ export type StatusBasedIdempotencyConfig = BaseIdempotencyConfig & {
64
+ strategy: 'status';
65
+ /**
66
+ * Fallback time-to-live (in milliseconds): the longest the idempotency key can live
67
+ * before it's evicted, even if the run has not reached a terminal status.
68
+ */
69
+ fallbackTtlMs: number;
70
+ };
59
71
  /**
60
72
  * Union of all supported idempotency configurations.
61
73
  */
62
- export type IdempotencyConfig = TTLBasedIdempotencyConfig;
74
+ export type IdempotencyConfig = TTLBasedIdempotencyConfig | StatusBasedIdempotencyConfig;
63
75
  export declare class NonRetryableError extends Error {
64
76
  constructor(message?: string);
65
77
  }
package/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const HATCHET_VERSION = "1.26.1";
1
+ export declare const HATCHET_VERSION = "1.27.0";
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.26.1';
4
+ exports.HATCHET_VERSION = '1.27.0';