@hatchet-dev/typescript-sdk 1.25.0 → 1.26.1

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/clients/dispatcher/action-listener.d.ts +1 -0
  2. package/clients/dispatcher/action-listener.js +5 -0
  3. package/clients/rest/generated/Api.d.ts +77 -3
  4. package/clients/rest/generated/Api.js +42 -2
  5. package/clients/rest/generated/data-contracts.d.ts +126 -2
  6. package/clients/rest/generated/data-contracts.js +26 -1
  7. package/opentelemetry/instrumentor.js +2 -1
  8. package/package.json +3 -3
  9. package/protoc/google/protobuf/any.d.ts +133 -0
  10. package/protoc/google/protobuf/any.js +112 -0
  11. package/protoc/google/rpc/status.d.ts +45 -0
  12. package/protoc/google/rpc/status.js +102 -0
  13. package/protoc/v1/workflows.d.ts +21 -0
  14. package/protoc/v1/workflows.js +231 -1
  15. package/util/errors/bulk-trigger-idempotency-collision-error.d.ts +6 -0
  16. package/util/errors/bulk-trigger-idempotency-collision-error.js +13 -0
  17. package/util/errors/idempotency-collision-error.d.ts +5 -0
  18. package/util/errors/idempotency-collision-error.js +16 -0
  19. package/util/retrier.d.ts +1 -1
  20. package/util/retrier.js +4 -1
  21. package/v1/client/admin.js +125 -9
  22. package/v1/client/worker/context.d.ts +8 -1
  23. package/v1/client/worker/context.js +11 -1
  24. package/v1/client/worker/worker-internal.js +6 -0
  25. package/v1/declaration.d.ts +6 -1
  26. package/v1/examples/e2e-worker.js +30 -27
  27. package/v1/examples/idempotency/run.d.ts +1 -0
  28. package/v1/examples/idempotency/run.js +37 -0
  29. package/v1/examples/idempotency/workflow.d.ts +10 -0
  30. package/v1/examples/idempotency/workflow.js +39 -0
  31. package/v1/index.d.ts +2 -0
  32. package/v1/index.js +5 -1
  33. package/v1/task.d.ts +24 -0
  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
  }
@@ -8,7 +8,7 @@
8
8
  * @module Context
9
9
  */
10
10
  import { Priority, RunOpts, TaskWorkflowDeclaration, BaseWorkflowDeclaration as WorkflowV1 } from '../../declaration';
11
- import { Action } from '../../../clients/dispatcher/action-listener';
11
+ import type { Action } from '../../../clients/dispatcher/action-listener';
12
12
  import { Logger, LogLevel } from '../../../util/logger';
13
13
  import WorkflowRunRef from '../../../util/workflow-run-ref';
14
14
  import { Conditions } from '../../conditions';
@@ -144,8 +144,15 @@ export declare class Context<T, K = {}> {
144
144
  /**
145
145
  * Gets the name of the current workflow.
146
146
  * @returns The name of the workflow.
147
+ * @deprecated This method returns the task name. Use {@link workflowNameV1} for the workflow
148
+ * name or {@link taskName} for the task name.
147
149
  */
148
150
  workflowName(): string;
151
+ /**
152
+ * Gets the name of the current workflow.
153
+ * @returns The name of the workflow.
154
+ */
155
+ workflowNameV1(): string;
149
156
  /**
150
157
  * Gets the user data associated with the workflow.
151
158
  * @returns The user data.
@@ -24,6 +24,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
24
24
  exports.DurableContext = exports.Context = exports.ContextWorker = void 0;
25
25
  const declaration_1 = require("../../declaration");
26
26
  const hatchet_error_1 = __importDefault(require("../../../util/errors/hatchet-error"));
27
+ const action_listener_1 = require("../../../clients/dispatcher/action-listener");
27
28
  const parse_1 = require("../../../util/parse");
28
29
  const conditions_1 = require("../../conditions");
29
30
  const transformer_1 = require("../../conditions/transformer");
@@ -210,10 +211,19 @@ class Context {
210
211
  /**
211
212
  * Gets the name of the current workflow.
212
213
  * @returns The name of the workflow.
214
+ * @deprecated This method returns the task name. Use {@link workflowNameV1} for the workflow
215
+ * name or {@link taskName} for the task name.
213
216
  */
214
217
  workflowName() {
215
218
  return this.action.jobName;
216
219
  }
220
+ /**
221
+ * Gets the name of the current workflow.
222
+ * @returns The name of the workflow.
223
+ */
224
+ workflowNameV1() {
225
+ return (0, action_listener_1.workflowNameFromAction)(this.action);
226
+ }
217
227
  /**
218
228
  * Gets the user data associated with the workflow.
219
229
  * @returns The user data.
@@ -288,7 +298,7 @@ class Context {
288
298
  return Promise.resolve();
289
299
  }
290
300
  const logger = this.v1.config.logger('ctx', this.v1.config.log_level);
291
- const contextExtra = Object.assign({ workflowRunId: this.action.workflowRunId, taskRunExternalId: this.action.taskRunExternalId, retryCount: this.action.retryCount, workflowName: this.action.jobName }, extra === null || extra === void 0 ? void 0 : extra.extra);
301
+ const contextExtra = Object.assign({ workflowRunId: this.action.workflowRunId, taskRunExternalId: this.action.taskRunExternalId, retryCount: this.action.retryCount, workflowName: this.workflowNameV1() }, extra === null || extra === void 0 ? void 0 : extra.extra);
292
302
  const promises = [];
293
303
  if (!level || level === 'INFO') {
294
304
  promises.push(logger.info(message, contextExtra));
@@ -335,6 +335,12 @@ class InternalWorker {
335
335
  expression: f.expression,
336
336
  payload: f.payload ? new TextEncoder().encode(JSON.stringify(f.payload)) : undefined,
337
337
  }))) !== null && _w !== void 0 ? _w : [],
338
+ idempotency: workflow.idempotency
339
+ ? {
340
+ expression: workflow.idempotency.expression,
341
+ ttlMs: workflow.idempotency.ttlMs,
342
+ }
343
+ : undefined,
338
344
  });
339
345
  this.registeredWorkflowPromises.push(registeredWorkflow);
340
346
  yield registeredWorkflow;
@@ -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>> & {