@hatchet-dev/typescript-sdk 1.24.3 → 1.26.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.
Files changed (35) hide show
  1. package/package.json +8 -7
  2. package/protoc/google/protobuf/any.d.ts +133 -0
  3. package/protoc/google/protobuf/any.js +112 -0
  4. package/protoc/google/rpc/status.d.ts +45 -0
  5. package/protoc/google/rpc/status.js +102 -0
  6. package/protoc/v1/workflows.d.ts +21 -0
  7. package/protoc/v1/workflows.js +231 -1
  8. package/util/errors/bulk-trigger-idempotency-collision-error.d.ts +6 -0
  9. package/util/errors/bulk-trigger-idempotency-collision-error.js +13 -0
  10. package/util/errors/idempotency-collision-error.d.ts +5 -0
  11. package/util/errors/idempotency-collision-error.js +16 -0
  12. package/util/retrier.d.ts +1 -1
  13. package/util/retrier.js +4 -1
  14. package/v1/client/admin.js +125 -9
  15. package/v1/client/worker/worker-internal.d.ts +5 -0
  16. package/v1/client/worker/worker-internal.js +25 -2
  17. package/v1/declaration.d.ts +8 -3
  18. package/v1/examples/bulk_operations/cancel.d.ts +1 -0
  19. package/v1/examples/bulk_operations/cancel.js +52 -0
  20. package/v1/examples/concurrency_cancel_in_progress/workflow.js +2 -0
  21. package/v1/examples/concurrency_cancel_newest/workflow.js +2 -0
  22. package/v1/examples/dag_match_condition/complex-workflow.js +15 -0
  23. package/v1/examples/e2e-worker.js +30 -27
  24. package/v1/examples/idempotency/run.d.ts +1 -0
  25. package/v1/examples/idempotency/run.js +37 -0
  26. package/v1/examples/idempotency/workflow.d.ts +10 -0
  27. package/v1/examples/idempotency/workflow.js +39 -0
  28. package/v1/examples/slot_cost/workflow.d.ts +2 -0
  29. package/v1/examples/slot_cost/workflow.js +29 -0
  30. package/v1/examples/timeout/worker.js +2 -0
  31. package/v1/index.d.ts +2 -0
  32. package/v1/index.js +5 -1
  33. package/v1/task.d.ts +35 -1
  34. package/version.d.ts +1 -1
  35. package/version.js +1 -1
@@ -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.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.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;
@@ -1111,6 +1111,7 @@ function createBaseCreateWorkflowVersionRequest() {
1111
1111
  concurrencyArr: [],
1112
1112
  defaultFilters: [],
1113
1113
  inputJsonSchema: undefined,
1114
+ idempotency: undefined,
1114
1115
  };
1115
1116
  }
1116
1117
  exports.CreateWorkflowVersionRequest = {
@@ -1157,6 +1158,9 @@ exports.CreateWorkflowVersionRequest = {
1157
1158
  if (message.inputJsonSchema !== undefined) {
1158
1159
  writer.uint32(114).bytes(message.inputJsonSchema);
1159
1160
  }
1161
+ if (message.idempotency !== undefined) {
1162
+ exports.IdempotencyConfig.encode(message.idempotency, writer.uint32(122).fork()).join();
1163
+ }
1160
1164
  return writer;
1161
1165
  },
1162
1166
  decode(input, length) {
@@ -1264,6 +1268,13 @@ exports.CreateWorkflowVersionRequest = {
1264
1268
  message.inputJsonSchema = reader.bytes();
1265
1269
  continue;
1266
1270
  }
1271
+ case 15: {
1272
+ if (tag !== 122) {
1273
+ break;
1274
+ }
1275
+ message.idempotency = exports.IdempotencyConfig.decode(reader, reader.uint32());
1276
+ continue;
1277
+ }
1267
1278
  }
1268
1279
  if ((tag & 7) === 4 || tag === 0) {
1269
1280
  break;
@@ -1322,6 +1333,9 @@ exports.CreateWorkflowVersionRequest = {
1322
1333
  : isSet(object.input_json_schema)
1323
1334
  ? bytesFromBase64(object.input_json_schema)
1324
1335
  : undefined,
1336
+ idempotency: isSet(object.idempotency)
1337
+ ? exports.IdempotencyConfig.fromJSON(object.idempotency)
1338
+ : undefined,
1325
1339
  };
1326
1340
  },
1327
1341
  toJSON(message) {
@@ -1369,6 +1383,9 @@ exports.CreateWorkflowVersionRequest = {
1369
1383
  if (message.inputJsonSchema !== undefined) {
1370
1384
  obj.inputJsonSchema = base64FromBytes(message.inputJsonSchema);
1371
1385
  }
1386
+ if (message.idempotency !== undefined) {
1387
+ obj.idempotency = exports.IdempotencyConfig.toJSON(message.idempotency);
1388
+ }
1372
1389
  return obj;
1373
1390
  },
1374
1391
  create(base) {
@@ -1397,6 +1414,219 @@ exports.CreateWorkflowVersionRequest = {
1397
1414
  message.concurrencyArr = ((_k = object.concurrencyArr) === null || _k === void 0 ? void 0 : _k.map((e) => exports.Concurrency.fromPartial(e))) || [];
1398
1415
  message.defaultFilters = ((_l = object.defaultFilters) === null || _l === void 0 ? void 0 : _l.map((e) => exports.DefaultFilter.fromPartial(e))) || [];
1399
1416
  message.inputJsonSchema = (_m = object.inputJsonSchema) !== null && _m !== void 0 ? _m : undefined;
1417
+ message.idempotency =
1418
+ object.idempotency !== undefined && object.idempotency !== null
1419
+ ? exports.IdempotencyConfig.fromPartial(object.idempotency)
1420
+ : undefined;
1421
+ return message;
1422
+ },
1423
+ };
1424
+ function createBaseIdempotencyConfig() {
1425
+ return { expression: '', ttlMs: 0 };
1426
+ }
1427
+ exports.IdempotencyConfig = {
1428
+ encode(message, writer = new wire_1.BinaryWriter()) {
1429
+ if (message.expression !== '') {
1430
+ writer.uint32(10).string(message.expression);
1431
+ }
1432
+ if (message.ttlMs !== 0) {
1433
+ writer.uint32(16).int64(message.ttlMs);
1434
+ }
1435
+ return writer;
1436
+ },
1437
+ decode(input, length) {
1438
+ const reader = input instanceof wire_1.BinaryReader ? input : new wire_1.BinaryReader(input);
1439
+ const end = length === undefined ? reader.len : reader.pos + length;
1440
+ const message = createBaseIdempotencyConfig();
1441
+ while (reader.pos < end) {
1442
+ const tag = reader.uint32();
1443
+ switch (tag >>> 3) {
1444
+ case 1: {
1445
+ if (tag !== 10) {
1446
+ break;
1447
+ }
1448
+ message.expression = reader.string();
1449
+ continue;
1450
+ }
1451
+ case 2: {
1452
+ if (tag !== 16) {
1453
+ break;
1454
+ }
1455
+ message.ttlMs = longToNumber(reader.int64());
1456
+ continue;
1457
+ }
1458
+ }
1459
+ if ((tag & 7) === 4 || tag === 0) {
1460
+ break;
1461
+ }
1462
+ reader.skip(tag & 7);
1463
+ }
1464
+ return message;
1465
+ },
1466
+ fromJSON(object) {
1467
+ return {
1468
+ expression: isSet(object.expression) ? globalThis.String(object.expression) : '',
1469
+ ttlMs: isSet(object.ttlMs)
1470
+ ? globalThis.Number(object.ttlMs)
1471
+ : isSet(object.ttl_ms)
1472
+ ? globalThis.Number(object.ttl_ms)
1473
+ : 0,
1474
+ };
1475
+ },
1476
+ toJSON(message) {
1477
+ const obj = {};
1478
+ if (message.expression !== '') {
1479
+ obj.expression = message.expression;
1480
+ }
1481
+ if (message.ttlMs !== 0) {
1482
+ obj.ttlMs = Math.round(message.ttlMs);
1483
+ }
1484
+ return obj;
1485
+ },
1486
+ create(base) {
1487
+ return exports.IdempotencyConfig.fromPartial(base !== null && base !== void 0 ? base : {});
1488
+ },
1489
+ fromPartial(object) {
1490
+ var _a, _b;
1491
+ const message = createBaseIdempotencyConfig();
1492
+ message.expression = (_a = object.expression) !== null && _a !== void 0 ? _a : '';
1493
+ message.ttlMs = (_b = object.ttlMs) !== null && _b !== void 0 ? _b : 0;
1494
+ return message;
1495
+ },
1496
+ };
1497
+ function createBaseIdempotencyCollisionError() {
1498
+ return { existingRunExternalId: '' };
1499
+ }
1500
+ exports.IdempotencyCollisionError = {
1501
+ encode(message, writer = new wire_1.BinaryWriter()) {
1502
+ if (message.existingRunExternalId !== '') {
1503
+ writer.uint32(10).string(message.existingRunExternalId);
1504
+ }
1505
+ return writer;
1506
+ },
1507
+ decode(input, length) {
1508
+ const reader = input instanceof wire_1.BinaryReader ? input : new wire_1.BinaryReader(input);
1509
+ const end = length === undefined ? reader.len : reader.pos + length;
1510
+ const message = createBaseIdempotencyCollisionError();
1511
+ while (reader.pos < end) {
1512
+ const tag = reader.uint32();
1513
+ switch (tag >>> 3) {
1514
+ case 1: {
1515
+ if (tag !== 10) {
1516
+ break;
1517
+ }
1518
+ message.existingRunExternalId = reader.string();
1519
+ continue;
1520
+ }
1521
+ }
1522
+ if ((tag & 7) === 4 || tag === 0) {
1523
+ break;
1524
+ }
1525
+ reader.skip(tag & 7);
1526
+ }
1527
+ return message;
1528
+ },
1529
+ fromJSON(object) {
1530
+ return {
1531
+ existingRunExternalId: isSet(object.existingRunExternalId)
1532
+ ? globalThis.String(object.existingRunExternalId)
1533
+ : isSet(object.existing_run_external_id)
1534
+ ? globalThis.String(object.existing_run_external_id)
1535
+ : '',
1536
+ };
1537
+ },
1538
+ toJSON(message) {
1539
+ const obj = {};
1540
+ if (message.existingRunExternalId !== '') {
1541
+ obj.existingRunExternalId = message.existingRunExternalId;
1542
+ }
1543
+ return obj;
1544
+ },
1545
+ create(base) {
1546
+ return exports.IdempotencyCollisionError.fromPartial(base !== null && base !== void 0 ? base : {});
1547
+ },
1548
+ fromPartial(object) {
1549
+ var _a;
1550
+ const message = createBaseIdempotencyCollisionError();
1551
+ message.existingRunExternalId = (_a = object.existingRunExternalId) !== null && _a !== void 0 ? _a : '';
1552
+ return message;
1553
+ },
1554
+ };
1555
+ function createBaseBulkTriggerIdempotencyCollisionError() {
1556
+ return { successfulWorkflowRunExternalIds: [], collisions: [] };
1557
+ }
1558
+ exports.BulkTriggerIdempotencyCollisionError = {
1559
+ encode(message, writer = new wire_1.BinaryWriter()) {
1560
+ for (const v of message.successfulWorkflowRunExternalIds) {
1561
+ writer.uint32(10).string(v);
1562
+ }
1563
+ for (const v of message.collisions) {
1564
+ exports.IdempotencyCollisionError.encode(v, writer.uint32(18).fork()).join();
1565
+ }
1566
+ return writer;
1567
+ },
1568
+ decode(input, length) {
1569
+ const reader = input instanceof wire_1.BinaryReader ? input : new wire_1.BinaryReader(input);
1570
+ const end = length === undefined ? reader.len : reader.pos + length;
1571
+ const message = createBaseBulkTriggerIdempotencyCollisionError();
1572
+ while (reader.pos < end) {
1573
+ const tag = reader.uint32();
1574
+ switch (tag >>> 3) {
1575
+ case 1: {
1576
+ if (tag !== 10) {
1577
+ break;
1578
+ }
1579
+ message.successfulWorkflowRunExternalIds.push(reader.string());
1580
+ continue;
1581
+ }
1582
+ case 2: {
1583
+ if (tag !== 18) {
1584
+ break;
1585
+ }
1586
+ message.collisions.push(exports.IdempotencyCollisionError.decode(reader, reader.uint32()));
1587
+ continue;
1588
+ }
1589
+ }
1590
+ if ((tag & 7) === 4 || tag === 0) {
1591
+ break;
1592
+ }
1593
+ reader.skip(tag & 7);
1594
+ }
1595
+ return message;
1596
+ },
1597
+ fromJSON(object) {
1598
+ return {
1599
+ successfulWorkflowRunExternalIds: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.successfulWorkflowRunExternalIds)
1600
+ ? object.successfulWorkflowRunExternalIds.map((e) => globalThis.String(e))
1601
+ : globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.successful_workflow_run_external_ids)
1602
+ ? object.successful_workflow_run_external_ids.map((e) => globalThis.String(e))
1603
+ : [],
1604
+ collisions: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.collisions)
1605
+ ? object.collisions.map((e) => exports.IdempotencyCollisionError.fromJSON(e))
1606
+ : [],
1607
+ };
1608
+ },
1609
+ toJSON(message) {
1610
+ var _a, _b;
1611
+ const obj = {};
1612
+ if ((_a = message.successfulWorkflowRunExternalIds) === null || _a === void 0 ? void 0 : _a.length) {
1613
+ obj.successfulWorkflowRunExternalIds = message.successfulWorkflowRunExternalIds;
1614
+ }
1615
+ if ((_b = message.collisions) === null || _b === void 0 ? void 0 : _b.length) {
1616
+ obj.collisions = message.collisions.map((e) => exports.IdempotencyCollisionError.toJSON(e));
1617
+ }
1618
+ return obj;
1619
+ },
1620
+ create(base) {
1621
+ return exports.BulkTriggerIdempotencyCollisionError.fromPartial(base !== null && base !== void 0 ? base : {});
1622
+ },
1623
+ fromPartial(object) {
1624
+ var _a, _b, _c, _d;
1625
+ const message = createBaseBulkTriggerIdempotencyCollisionError();
1626
+ message.successfulWorkflowRunExternalIds =
1627
+ (_b = (_a = object.successfulWorkflowRunExternalIds) === null || _a === void 0 ? void 0 : _a.map((e) => e)) !== null && _b !== void 0 ? _b : [];
1628
+ message.collisions =
1629
+ (_d = (_c = object.collisions) === null || _c === void 0 ? void 0 : _c.map((e) => exports.IdempotencyCollisionError.fromPartial(e))) !== null && _d !== void 0 ? _d : [];
1400
1630
  return message;
1401
1631
  },
1402
1632
  };
@@ -0,0 +1,6 @@
1
+ import { IdempotencyCollisionError } from './idempotency-collision-error';
2
+ export declare class BulkTriggerIdempotencyCollisionError extends Error {
3
+ successfulWorkflowRunExternalIds: string[];
4
+ collisions: IdempotencyCollisionError[];
5
+ constructor(successfulWorkflowRunExternalIds: string[], collisions: IdempotencyCollisionError[]);
6
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BulkTriggerIdempotencyCollisionError = void 0;
4
+ class BulkTriggerIdempotencyCollisionError extends Error {
5
+ constructor(successfulWorkflowRunExternalIds, collisions) {
6
+ super('idempotency key collision in bulk trigger');
7
+ this.name = 'BulkTriggerIdempotencyCollisionError';
8
+ this.successfulWorkflowRunExternalIds = successfulWorkflowRunExternalIds;
9
+ this.collisions = collisions;
10
+ Object.setPrototypeOf(this, new.target.prototype);
11
+ }
12
+ }
13
+ exports.BulkTriggerIdempotencyCollisionError = BulkTriggerIdempotencyCollisionError;
@@ -0,0 +1,5 @@
1
+ import HatchetError from './hatchet-error';
2
+ export declare class IdempotencyCollisionError extends HatchetError {
3
+ existingRunExternalId: string;
4
+ constructor(existingRunExternalId: string);
5
+ }
@@ -0,0 +1,16 @@
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.IdempotencyCollisionError = void 0;
7
+ const hatchet_error_1 = __importDefault(require("./hatchet-error"));
8
+ class IdempotencyCollisionError extends hatchet_error_1.default {
9
+ constructor(existingRunExternalId) {
10
+ super(`idempotency key collision: existing run ${existingRunExternalId} already exists`);
11
+ this.name = 'IdempotencyCollisionError';
12
+ this.existingRunExternalId = existingRunExternalId;
13
+ Object.setPrototypeOf(this, new.target.prototype);
14
+ }
15
+ }
16
+ exports.IdempotencyCollisionError = IdempotencyCollisionError;
package/util/retrier.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  import { Logger } from './logger';
2
- export declare function retrier<T>(fn: () => Promise<T>, logger: Logger, retries?: number, interval?: number): Promise<T>;
2
+ export declare function retrier<T>(fn: () => Promise<T>, logger: Logger, retries?: number, interval?: number, shouldRetry?: (e: unknown) => boolean): Promise<T>;
package/util/retrier.js CHANGED
@@ -18,13 +18,16 @@ const DEFAULT_RETRY_INTERVAL = 0.1; // seconds
18
18
  const DEFAULT_RETRY_COUNT = 8;
19
19
  const MAX_JITTER = 100; // milliseconds
20
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) {
21
+ return __awaiter(this, arguments, void 0, function* (fn, logger, retries = DEFAULT_RETRY_COUNT, interval = DEFAULT_RETRY_INTERVAL, shouldRetry = () => true) {
22
22
  let lastError;
23
23
  for (let i = 0; i < retries; i++) {
24
24
  try {
25
25
  return yield fn();
26
26
  }
27
27
  catch (e) {
28
+ if (!shouldRetry(e)) {
29
+ throw e;
30
+ }
28
31
  lastError = e instanceof Error ? e : new Error(String(e));
29
32
  logger.error(`Error: ${lastError.message}`);
30
33
  // Calculate exponential backoff with random jitter
@@ -25,14 +25,99 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.AdminClient = void 0;
27
27
  const hatchet_error_1 = __importDefault(require("../../util/errors/hatchet-error"));
28
+ const idempotency_collision_error_1 = require("../../util/errors/idempotency-collision-error");
29
+ const bulk_trigger_idempotency_collision_error_1 = require("../../util/errors/bulk-trigger-idempotency-collision-error");
28
30
  const workflow_run_ref_1 = __importDefault(require("../../util/workflow-run-ref"));
31
+ const grpc_js_1 = require("@grpc/grpc-js");
32
+ const status_1 = require("../../protoc/google/rpc/status");
33
+ const workflows_1 = require("../../protoc/v1/workflows");
34
+ const nice_grpc_common_1 = require("nice-grpc-common");
29
35
  const grpc_helpers_1 = require("../../util/grpc-helpers");
30
36
  const child_listener_client_1 = require("../../clients/listeners/run-listener/child-listener-client");
31
- const workflows_1 = require("../../protoc/workflows");
32
- const workflows_2 = require("../../protoc/v1/workflows");
37
+ const workflows_2 = require("../../protoc/workflows");
38
+ const workflows_3 = require("../../protoc/v1/workflows");
33
39
  const retrier_1 = require("../../util/retrier");
34
40
  const batch_1 = require("../../util/batch");
35
41
  const apply_namespace_1 = require("../../util/apply-namespace");
42
+ function isGrpcServiceError(e) {
43
+ return e instanceof Error && 'code' in e && 'metadata' in e;
44
+ }
45
+ function extractExistingRunIdFromGrpcError(e) {
46
+ var _a;
47
+ try {
48
+ const [binData] = e.metadata.get('grpc-status-details-bin');
49
+ if (!binData)
50
+ return '';
51
+ const status = status_1.Status.decode(binData instanceof Buffer ? binData : Buffer.from(binData));
52
+ for (const detail of status.details) {
53
+ if (detail.typeUrl.includes('IdempotencyCollisionError')) {
54
+ return (_a = workflows_1.IdempotencyCollisionError.decode(detail.value).existingRunExternalId) !== null && _a !== void 0 ? _a : '';
55
+ }
56
+ }
57
+ }
58
+ catch (_b) {
59
+ // ignore decoding errors
60
+ }
61
+ return '';
62
+ }
63
+ function isNiceGrpcAlreadyExists(e) {
64
+ return e instanceof nice_grpc_common_1.ClientError && e.code === nice_grpc_common_1.Status.ALREADY_EXISTS;
65
+ }
66
+ function decodeBulkTriggerCollision(status) {
67
+ for (const detail of status.details) {
68
+ if (detail.typeUrl.includes('BulkTriggerIdempotencyCollisionError')) {
69
+ const proto = workflows_1.BulkTriggerIdempotencyCollisionError.decode(detail.value);
70
+ return new bulk_trigger_idempotency_collision_error_1.BulkTriggerIdempotencyCollisionError(proto.successfulWorkflowRunExternalIds, proto.collisions.map((c) => new idempotency_collision_error_1.IdempotencyCollisionError(c.existingRunExternalId)));
71
+ }
72
+ }
73
+ return null;
74
+ }
75
+ function extractBulkTriggerCollisionFromGrpcError(e) {
76
+ try {
77
+ const [binData] = e.metadata.get('grpc-status-details-bin');
78
+ if (!binData)
79
+ return null;
80
+ const status = status_1.Status.decode(binData instanceof Buffer ? binData : Buffer.from(binData));
81
+ return decodeBulkTriggerCollision(status);
82
+ }
83
+ catch (_a) {
84
+ return null;
85
+ }
86
+ }
87
+ function extractBulkTriggerCollisionFromNiceGrpcMetadata(metadata) {
88
+ if (!metadata)
89
+ return null;
90
+ try {
91
+ const binData = metadata.get('grpc-status-details-bin');
92
+ if (!binData)
93
+ return null;
94
+ const status = status_1.Status.decode(binData);
95
+ return decodeBulkTriggerCollision(status);
96
+ }
97
+ catch (_a) {
98
+ return null;
99
+ }
100
+ }
101
+ function extractRunIdFromNiceGrpcMetadata(metadata) {
102
+ var _a;
103
+ if (!metadata)
104
+ return '';
105
+ try {
106
+ const binData = metadata.get('grpc-status-details-bin');
107
+ if (!binData)
108
+ return '';
109
+ const status = status_1.Status.decode(binData);
110
+ for (const detail of status.details) {
111
+ if (detail.typeUrl.includes('IdempotencyCollisionError')) {
112
+ return (_a = workflows_1.IdempotencyCollisionError.decode(detail.value).existingRunExternalId) !== null && _a !== void 0 ? _a : '';
113
+ }
114
+ }
115
+ }
116
+ catch (_b) {
117
+ // ignore decoding errors
118
+ }
119
+ return '';
120
+ }
36
121
  function convertDesiredWorkerLabels(labels) {
37
122
  return Object.fromEntries(Object.entries(labels).map(([key, label]) => [
38
123
  key,
@@ -49,9 +134,9 @@ class AdminClient {
49
134
  constructor(config, api, runs) {
50
135
  this.config = config;
51
136
  this.logger = config.logger(`Admin`, config.log_level);
52
- const { client, channel, factory } = (0, grpc_helpers_1.createGrpcClient)(config, workflows_1.WorkflowServiceDefinition);
137
+ const { client, channel, factory } = (0, grpc_helpers_1.createGrpcClient)(config, workflows_2.WorkflowServiceDefinition);
53
138
  this.workflowsGrpc = client;
54
- this.adminGrpc = factory.create(workflows_2.AdminServiceDefinition, channel);
139
+ this.adminGrpc = factory.create(workflows_3.AdminServiceDefinition, channel);
55
140
  this.listenerClient = new child_listener_client_1.RunListenerClient(config, channel, factory, api);
56
141
  this.runs = runs;
57
142
  }
@@ -82,6 +167,7 @@ class AdminClient {
82
167
  */
83
168
  runWorkflow(workflowName, input, options) {
84
169
  return __awaiter(this, void 0, void 0, function* () {
170
+ let trailerMetadata;
85
171
  try {
86
172
  const computedName = (0, apply_namespace_1.applyNamespace)(workflowName, this.config.namespace).toLowerCase();
87
173
  const inputStr = JSON.stringify(input);
@@ -92,14 +178,26 @@ class AdminClient {
92
178
  parentTaskRunExternalId: parentTaskRunExternalId !== null && parentTaskRunExternalId !== void 0 ? parentTaskRunExternalId : parentStepRunId, additionalMetadata: additionalMetadata ? JSON.stringify(additionalMetadata) : undefined, priority: opts.priority, desiredWorkerLabels: desiredWorkerLabels
93
179
  ? convertDesiredWorkerLabels(desiredWorkerLabels)
94
180
  : {} });
95
- const resp = yield (0, retrier_1.retrier)(() => __awaiter(this, void 0, void 0, function* () { return this.workflowsGrpc.triggerWorkflow(request); }), this.logger);
181
+ const resp = yield (0, retrier_1.retrier)(() => __awaiter(this, void 0, void 0, function* () {
182
+ return this.workflowsGrpc.triggerWorkflow(request, {
183
+ onTrailer: (trailer) => {
184
+ trailerMetadata = trailer;
185
+ },
186
+ });
187
+ }), this.logger, undefined, undefined, (e) => !isNiceGrpcAlreadyExists(e));
96
188
  const id = resp.workflowRunId;
97
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);
98
190
  yield ref.getWorkflowRunId();
99
191
  return ref;
100
192
  }
101
193
  catch (e) {
102
- throw new hatchet_error_1.default(e.message);
194
+ if (isGrpcServiceError(e) && e.code === grpc_js_1.status.ALREADY_EXISTS) {
195
+ throw new idempotency_collision_error_1.IdempotencyCollisionError(extractExistingRunIdFromGrpcError(e));
196
+ }
197
+ if (isNiceGrpcAlreadyExists(e)) {
198
+ throw new idempotency_collision_error_1.IdempotencyCollisionError(extractRunIdFromNiceGrpcMetadata(trailerMetadata));
199
+ }
200
+ throw new hatchet_error_1.default(e instanceof Error ? e.message : String(e));
103
201
  }
104
202
  });
105
203
  }
@@ -129,15 +227,23 @@ class AdminClient {
129
227
  });
130
228
  const batches = (0, batch_1.batch)(workflowRequests, batchSize, (_a = this.config.grpc_max_send_message_length) !== null && _a !== void 0 ? _a : 4 * 1024 * 1024);
131
229
  this.logger.debug(`batching ${batches.length} batches`);
230
+ let bulkTrailerMetadata;
132
231
  try {
133
232
  const results = [];
134
233
  // for loop to ensure serial execution of batches
135
234
  for (const { payloads, originalIndices, batchIndex } of batches) {
136
- const request = workflows_1.BulkTriggerWorkflowRequest.create({
235
+ const request = workflows_2.BulkTriggerWorkflowRequest.create({
137
236
  workflows: payloads,
138
237
  });
139
238
  // Call the bulk trigger workflow method for this batch
140
- const bulkTriggerWorkflowResponse = yield (0, retrier_1.retrier)(() => __awaiter(this, void 0, void 0, function* () { return this.workflowsGrpc.bulkTriggerWorkflow(request); }), this.logger);
239
+ const bulkTriggerWorkflowResponse = yield (0, retrier_1.retrier)(() => __awaiter(this, void 0, void 0, function* () {
240
+ return this.workflowsGrpc.bulkTriggerWorkflow(request, {
241
+ onTrailer: (trailer) => {
242
+ bulkTrailerMetadata = trailer;
243
+ },
244
+ });
245
+ }), this.logger, undefined, undefined, (e) => !isNiceGrpcAlreadyExists(e) &&
246
+ !(isGrpcServiceError(e) && e.code === grpc_js_1.status.ALREADY_EXISTS));
141
247
  this.logger.debug(`batch ${batchIndex + 1} of ${batches.length}`);
142
248
  // Map the results back to their original indices
143
249
  const batchResults = bulkTriggerWorkflowResponse.workflowRunIds.map((resp, index) => {
@@ -150,7 +256,17 @@ class AdminClient {
150
256
  return results;
151
257
  }
152
258
  catch (e) {
153
- throw new hatchet_error_1.default(e.message);
259
+ if (isGrpcServiceError(e) && e.code === grpc_js_1.status.ALREADY_EXISTS) {
260
+ const collision = extractBulkTriggerCollisionFromGrpcError(e);
261
+ if (collision)
262
+ throw collision;
263
+ }
264
+ if (isNiceGrpcAlreadyExists(e)) {
265
+ const collision = extractBulkTriggerCollisionFromNiceGrpcMetadata(bulkTrailerMetadata);
266
+ if (collision)
267
+ throw collision;
268
+ }
269
+ throw new hatchet_error_1.default(e instanceof Error ? e.message : String(e));
154
270
  }
155
271
  });
156
272
  }
@@ -87,6 +87,11 @@ export declare class InternalWorker {
87
87
  handleAction(action: Action): Promise<void | Error>;
88
88
  upsertLabels(labels: WorkerLabels): Promise<WorkerLabels>;
89
89
  }
90
+ /** Durable tasks stay on the durable pool; slotCost applies only to the default pool. */
91
+ export declare function mapSlotRequestsPb(task: {
92
+ slotRequests?: Record<string, number>;
93
+ slotCost?: number;
94
+ }, isDurable: boolean): Record<string, number>;
90
95
  export declare function mapRateLimitPb(limits: CreateWorkflowTaskOpts<any, any>['rateLimits']): CreateStepRateLimit[];
91
96
  export declare function resolveExecutionTimeout(task: {
92
97
  executionTimeout?: Duration;
@@ -53,6 +53,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
53
53
  };
54
54
  Object.defineProperty(exports, "__esModule", { value: true });
55
55
  exports.InternalWorker = void 0;
56
+ exports.mapSlotRequestsPb = mapSlotRequestsPb;
56
57
  exports.mapRateLimitPb = mapRateLimitPb;
57
58
  exports.resolveExecutionTimeout = resolveExecutionTimeout;
58
59
  exports.resolveScheduleTimeout = resolveScheduleTimeout;
@@ -206,7 +207,7 @@ class InternalWorker {
206
207
  backoffFactor: ((_g = onFailure.backoff) === null || _g === void 0 ? void 0 : _g.factor) || ((_j = (_h = workflow.taskDefaults) === null || _h === void 0 ? void 0 : _h.backoff) === null || _j === void 0 ? void 0 : _j.factor),
207
208
  backoffMaxSeconds: ((_k = onFailure.backoff) === null || _k === void 0 ? void 0 : _k.maxSeconds) || ((_m = (_l = workflow.taskDefaults) === null || _l === void 0 ? void 0 : _l.backoff) === null || _m === void 0 ? void 0 : _m.maxSeconds),
208
209
  isDurable: false,
209
- slotRequests: { default: 1 },
210
+ slotRequests: mapSlotRequestsPb(onFailure, false),
210
211
  };
211
212
  }
212
213
  let onSuccessTask;
@@ -316,7 +317,7 @@ class InternalWorker {
316
317
  backoffMaxSeconds: ((_j = task.backoff) === null || _j === void 0 ? void 0 : _j.maxSeconds) || ((_l = (_k = workflow.taskDefaults) === null || _k === void 0 ? void 0 : _k.backoff) === null || _l === void 0 ? void 0 : _l.maxSeconds),
317
318
  conditions: (0, transformer_1.taskConditionsToPb)(task, this.client.config.namespace),
318
319
  isDurable: durableTaskSet.has(task),
319
- slotRequests: task.slotRequests || (durableTaskSet.has(task) ? { durable: 1 } : { default: 1 }),
320
+ slotRequests: mapSlotRequestsPb(task, durableTaskSet.has(task)),
320
321
  concurrency: task.concurrency
321
322
  ? Array.isArray(task.concurrency)
322
323
  ? task.concurrency
@@ -334,6 +335,12 @@ class InternalWorker {
334
335
  expression: f.expression,
335
336
  payload: f.payload ? new TextEncoder().encode(JSON.stringify(f.payload)) : undefined,
336
337
  }))) !== null && _w !== void 0 ? _w : [],
338
+ idempotency: workflow.idempotency
339
+ ? {
340
+ expression: workflow.idempotency.expression,
341
+ ttlMs: workflow.idempotency.ttlMs,
342
+ }
343
+ : undefined,
337
344
  });
338
345
  this.registeredWorkflowPromises.push(registeredWorkflow);
339
346
  yield registeredWorkflow;
@@ -863,6 +870,22 @@ function getLeaves(tasks) {
863
870
  function isLeafTask(task, allTasks) {
864
871
  return !allTasks.some((t) => { var _a; return (_a = t.parents) === null || _a === void 0 ? void 0 : _a.some((p) => p.name === task.name); });
865
872
  }
873
+ /** Durable tasks stay on the durable pool; slotCost applies only to the default pool. */
874
+ function mapSlotRequestsPb(task, isDurable) {
875
+ if (task.slotRequests) {
876
+ return task.slotRequests;
877
+ }
878
+ if (isDurable) {
879
+ return { durable: 1 };
880
+ }
881
+ if (task.slotCost !== undefined) {
882
+ if (!Number.isInteger(task.slotCost) || task.slotCost <= 0) {
883
+ throw new Error(`slotCost must be a positive integer, got: ${task.slotCost}`);
884
+ }
885
+ return { default: task.slotCost };
886
+ }
887
+ return { default: 1 };
888
+ }
866
889
  function mapRateLimitPb(limits) {
867
890
  if (!limits) {
868
891
  return [];
@@ -9,7 +9,7 @@ import WorkflowRunRef from '../util/workflow-run-ref';
9
9
  import { CronWorkflows, ScheduledWorkflows, V1CreateFilterRequest } from '../clients/rest/generated/data-contracts';
10
10
  import * as z from 'zod/v4';
11
11
  import { IHatchetClient } from './client/client.interface';
12
- import { CreateWorkflowTaskOpts, CreateOnFailureTaskOpts, TaskFn, CreateWorkflowDurableTaskOpts, CreateBaseTaskOpts, CreateOnSuccessTaskOpts, Concurrency, DurableTaskFn, WorkerLabelComparator } from './task';
12
+ import { CreateWorkflowTaskOpts, CreateOnFailureTaskOpts, TaskFn, CreateWorkflowDurableTaskOpts, CreateBaseTaskOpts, CreateOnSuccessTaskOpts, Concurrency, DurableTaskFn, WorkerLabelComparator, IdempotencyConfig } from './task';
13
13
  import { Duration } from './client/duration';
14
14
  import { MetricsClient } from './client/features/metrics';
15
15
  import { InputType, OutputType, UnknownInputType, JsonObject, Resolved } from './types';
@@ -152,6 +152,11 @@ export type CreateBaseWorkflowOpts = {
152
152
  * can be used on the dashboard for autocomplete.
153
153
  */
154
154
  inputValidator?: z.ZodType<any>;
155
+ /**
156
+ * (optional) idempotency configuration for the workflow.
157
+ * Prevents more than one run from occurring for a given key within the TTL window.
158
+ */
159
+ idempotency?: IdempotencyConfig;
155
160
  };
156
161
  export type CreateTaskWorkflowOpts<I extends InputType = UnknownInputType, O extends OutputType = void> = CreateBaseWorkflowOpts & CreateBaseTaskOpts<I, O, TaskFn<I, O>>;
157
162
  export type CreateDurableTaskWorkflowOpts<I extends InputType = UnknownInputType, O extends OutputType = void> = CreateBaseWorkflowOpts & CreateBaseTaskOpts<I, O, DurableTaskFn<I, O>> & {
@@ -425,7 +430,7 @@ export declare class WorkflowDeclaration<I extends InputType = UnknownInputType,
425
430
  * @param options The task configuration options.
426
431
  * @returns The task options that were added.
427
432
  */
428
- task<Name extends string, Fn extends Name extends keyof O ? (input: I & MiddlewareBefore, ctx: Context<I & MiddlewareBefore>) => O[Name] extends OutputType ? O[Name] | Promise<O[Name]> : void : (input: I & MiddlewareBefore, ctx: Context<I & MiddlewareBefore>) => void, FnReturn = ReturnType<Fn> extends Promise<infer P> ? P : ReturnType<Fn>, TO extends OutputType = Name extends keyof O ? O[Name] extends OutputType ? O[Name] : never : FnReturn extends OutputType ? FnReturn : never>(options: (Omit<CreateWorkflowTaskOpts<I, TO>, 'fn'> & {
433
+ task<Name extends string, Fn extends (Name extends keyof O ? (input: I & MiddlewareBefore, ctx: Context<I & MiddlewareBefore>) => O[Name] extends OutputType ? O[Name] | Promise<O[Name]> : void : (input: I & MiddlewareBefore, ctx: Context<I & MiddlewareBefore>) => void), FnReturn = ReturnType<Fn> extends Promise<infer P> ? P : ReturnType<Fn>, TO extends OutputType = Name extends keyof O ? O[Name] extends OutputType ? O[Name] : never : FnReturn extends OutputType ? FnReturn : never>(options: (Omit<CreateWorkflowTaskOpts<I, TO>, 'fn'> & {
429
434
  name: Name;
430
435
  fn?: Fn;
431
436
  }) | TaskWorkflowDeclaration<I, TO>): CreateWorkflowTaskOpts<I, TO>;
@@ -460,7 +465,7 @@ export declare class WorkflowDeclaration<I extends InputType = UnknownInputType,
460
465
  * @param options The task configuration options.
461
466
  * @returns The task options that were added.
462
467
  */
463
- durableTask<Name extends string, Fn extends Name extends keyof O ? (input: I & MiddlewareBefore, ctx: DurableContext<I & MiddlewareBefore>) => O[Name] extends OutputType ? O[Name] | Promise<O[Name]> : void : (input: I & MiddlewareBefore, ctx: DurableContext<I & MiddlewareBefore>) => void, FnReturn = ReturnType<Fn> extends Promise<infer P> ? P : ReturnType<Fn>, TO extends OutputType = Name extends keyof O ? O[Name] extends OutputType ? O[Name] : never : FnReturn extends OutputType ? FnReturn : never>(options: Omit<CreateWorkflowTaskOpts<I, TO>, 'fn'> & {
468
+ durableTask<Name extends string, Fn extends (Name extends keyof O ? (input: I & MiddlewareBefore, ctx: DurableContext<I & MiddlewareBefore>) => O[Name] extends OutputType ? O[Name] | Promise<O[Name]> : void : (input: I & MiddlewareBefore, ctx: DurableContext<I & MiddlewareBefore>) => void), FnReturn = ReturnType<Fn> extends Promise<infer P> ? P : ReturnType<Fn>, TO extends OutputType = Name extends keyof O ? O[Name] extends OutputType ? O[Name] : never : FnReturn extends OutputType ? FnReturn : never>(options: Omit<CreateWorkflowTaskOpts<I, TO>, 'fn' | 'slotCost'> & {
464
469
  name: Name;
465
470
  fn: Fn;
466
471
  }): CreateWorkflowDurableTaskOpts<I, TO>;