@hatchet-dev/typescript-sdk 1.25.0 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hatchet-dev/typescript-sdk",
3
- "version": "1.25.0",
3
+ "version": "1.26.0",
4
4
  "description": "Background task orchestration & visibility for developers",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [
@@ -0,0 +1,133 @@
1
+ import { BinaryReader, BinaryWriter } from '@bufbuild/protobuf/wire';
2
+ export declare const protobufPackage = "google.protobuf";
3
+ /**
4
+ * `Any` contains an arbitrary serialized protocol buffer message along with a
5
+ * URL that describes the type of the serialized message.
6
+ *
7
+ * Protobuf library provides support to pack/unpack Any values in the form
8
+ * of utility functions or additional generated methods of the Any type.
9
+ *
10
+ * Example 1: Pack and unpack a message in C++.
11
+ *
12
+ * Foo foo = ...;
13
+ * Any any;
14
+ * any.PackFrom(foo);
15
+ * ...
16
+ * if (any.UnpackTo(&foo)) {
17
+ * ...
18
+ * }
19
+ *
20
+ * Example 2: Pack and unpack a message in Java.
21
+ *
22
+ * Foo foo = ...;
23
+ * Any any = Any.pack(foo);
24
+ * ...
25
+ * if (any.is(Foo.class)) {
26
+ * foo = any.unpack(Foo.class);
27
+ * }
28
+ *
29
+ * Example 3: Pack and unpack a message in Python.
30
+ *
31
+ * foo = Foo(...)
32
+ * any = Any()
33
+ * any.Pack(foo)
34
+ * ...
35
+ * if any.Is(Foo.DESCRIPTOR):
36
+ * any.Unpack(foo)
37
+ * ...
38
+ *
39
+ * Example 4: Pack and unpack a message in Go
40
+ *
41
+ * foo := &pb.Foo{...}
42
+ * any, err := anypb.New(foo)
43
+ * if err != nil {
44
+ * ...
45
+ * }
46
+ * ...
47
+ * foo := &pb.Foo{}
48
+ * if err := any.UnmarshalTo(foo); err != nil {
49
+ * ...
50
+ * }
51
+ *
52
+ * The pack methods provided by protobuf library will by default use
53
+ * 'type.googleapis.com/full.type.name' as the type URL and the unpack
54
+ * methods only use the fully qualified type name after the last '/'
55
+ * in the type URL, for example "foo.bar.com/x/y.z" will yield type
56
+ * name "y.z".
57
+ *
58
+ * JSON
59
+ * ====
60
+ * The JSON representation of an `Any` value uses the regular
61
+ * representation of the deserialized, embedded message, with an
62
+ * additional field `@type` which contains the type URL. Example:
63
+ *
64
+ * package google.profile;
65
+ * message Person {
66
+ * string first_name = 1;
67
+ * string last_name = 2;
68
+ * }
69
+ *
70
+ * {
71
+ * "@type": "type.googleapis.com/google.profile.Person",
72
+ * "firstName": <string>,
73
+ * "lastName": <string>
74
+ * }
75
+ *
76
+ * If the embedded message type is well-known and has a custom JSON
77
+ * representation, that representation will be embedded adding a field
78
+ * `value` which holds the custom JSON in addition to the `@type`
79
+ * field. Example (for message [google.protobuf.Duration][]):
80
+ *
81
+ * {
82
+ * "@type": "type.googleapis.com/google.protobuf.Duration",
83
+ * "value": "1.212s"
84
+ * }
85
+ */
86
+ export interface Any {
87
+ /**
88
+ * A URL/resource name that uniquely identifies the type of the serialized
89
+ * protocol buffer message. This string must contain at least
90
+ * one "/" character. The last segment of the URL's path must represent
91
+ * the fully qualified name of the type (as in
92
+ * `path/google.protobuf.Duration`). The name should be in a canonical form
93
+ * (e.g., leading "." is not accepted).
94
+ *
95
+ * In practice, teams usually precompile into the binary all types that they
96
+ * expect it to use in the context of Any. However, for URLs which use the
97
+ * scheme `http`, `https`, or no scheme, one can optionally set up a type
98
+ * server that maps type URLs to message definitions as follows:
99
+ *
100
+ * * If no scheme is provided, `https` is assumed.
101
+ * * An HTTP GET on the URL must yield a [google.protobuf.Type][]
102
+ * value in binary format, or produce an error.
103
+ * * Applications are allowed to cache lookup results based on the
104
+ * URL, or have them precompiled into a binary to avoid any
105
+ * lookup. Therefore, binary compatibility needs to be preserved
106
+ * on changes to types. (Use versioned type names to manage
107
+ * breaking changes.)
108
+ *
109
+ * Note: this functionality is not currently available in the official
110
+ * protobuf release, and it is not used for type URLs beginning with
111
+ * type.googleapis.com.
112
+ *
113
+ * Schemes other than `http`, `https` (or the empty scheme) might be
114
+ * used with implementation specific semantics.
115
+ */
116
+ typeUrl: string;
117
+ /** Must be a valid serialized protocol buffer of the above specified type. */
118
+ value: Uint8Array;
119
+ }
120
+ export declare const Any: MessageFns<Any>;
121
+ type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
122
+ export type DeepPartial<T> = T extends Builtin ? T : T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>> : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>> : T extends {} ? {
123
+ [K in keyof T]?: DeepPartial<T[K]>;
124
+ } : Partial<T>;
125
+ export interface MessageFns<T> {
126
+ encode(message: T, writer?: BinaryWriter): BinaryWriter;
127
+ decode(input: BinaryReader | Uint8Array, length?: number): T;
128
+ fromJSON(object: any): T;
129
+ toJSON(message: T): unknown;
130
+ create(base?: DeepPartial<T>): T;
131
+ fromPartial(object: DeepPartial<T>): T;
132
+ }
133
+ export {};
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
3
+ // versions:
4
+ // protoc-gen-ts_proto v2.11.6
5
+ // protoc v3.19.1
6
+ // source: google/protobuf/any.proto
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.Any = exports.protobufPackage = void 0;
9
+ /* eslint-disable */
10
+ const wire_1 = require("@bufbuild/protobuf/wire");
11
+ exports.protobufPackage = 'google.protobuf';
12
+ function createBaseAny() {
13
+ return { typeUrl: '', value: new Uint8Array(0) };
14
+ }
15
+ exports.Any = {
16
+ encode(message, writer = new wire_1.BinaryWriter()) {
17
+ if (message.typeUrl !== '') {
18
+ writer.uint32(10).string(message.typeUrl);
19
+ }
20
+ if (message.value.length !== 0) {
21
+ writer.uint32(18).bytes(message.value);
22
+ }
23
+ return writer;
24
+ },
25
+ decode(input, length) {
26
+ const reader = input instanceof wire_1.BinaryReader ? input : new wire_1.BinaryReader(input);
27
+ const end = length === undefined ? reader.len : reader.pos + length;
28
+ const message = createBaseAny();
29
+ while (reader.pos < end) {
30
+ const tag = reader.uint32();
31
+ switch (tag >>> 3) {
32
+ case 1: {
33
+ if (tag !== 10) {
34
+ break;
35
+ }
36
+ message.typeUrl = reader.string();
37
+ continue;
38
+ }
39
+ case 2: {
40
+ if (tag !== 18) {
41
+ break;
42
+ }
43
+ message.value = reader.bytes();
44
+ continue;
45
+ }
46
+ }
47
+ if ((tag & 7) === 4 || tag === 0) {
48
+ break;
49
+ }
50
+ reader.skip(tag & 7);
51
+ }
52
+ return message;
53
+ },
54
+ fromJSON(object) {
55
+ return {
56
+ typeUrl: isSet(object.typeUrl)
57
+ ? globalThis.String(object.typeUrl)
58
+ : isSet(object.type_url)
59
+ ? globalThis.String(object.type_url)
60
+ : '',
61
+ value: isSet(object.value) ? bytesFromBase64(object.value) : new Uint8Array(0),
62
+ };
63
+ },
64
+ toJSON(message) {
65
+ const obj = {};
66
+ if (message.typeUrl !== '') {
67
+ obj.typeUrl = message.typeUrl;
68
+ }
69
+ if (message.value.length !== 0) {
70
+ obj.value = base64FromBytes(message.value);
71
+ }
72
+ return obj;
73
+ },
74
+ create(base) {
75
+ return exports.Any.fromPartial(base !== null && base !== void 0 ? base : {});
76
+ },
77
+ fromPartial(object) {
78
+ var _a, _b;
79
+ const message = createBaseAny();
80
+ message.typeUrl = (_a = object.typeUrl) !== null && _a !== void 0 ? _a : '';
81
+ message.value = (_b = object.value) !== null && _b !== void 0 ? _b : new Uint8Array(0);
82
+ return message;
83
+ },
84
+ };
85
+ function bytesFromBase64(b64) {
86
+ if (globalThis.Buffer) {
87
+ return Uint8Array.from(globalThis.Buffer.from(b64, 'base64'));
88
+ }
89
+ else {
90
+ const bin = globalThis.atob(b64);
91
+ const arr = new Uint8Array(bin.length);
92
+ for (let i = 0; i < bin.length; ++i) {
93
+ arr[i] = bin.charCodeAt(i);
94
+ }
95
+ return arr;
96
+ }
97
+ }
98
+ function base64FromBytes(arr) {
99
+ if (globalThis.Buffer) {
100
+ return globalThis.Buffer.from(arr).toString('base64');
101
+ }
102
+ else {
103
+ const bin = [];
104
+ arr.forEach((byte) => {
105
+ bin.push(globalThis.String.fromCharCode(byte));
106
+ });
107
+ return globalThis.btoa(bin.join(''));
108
+ }
109
+ }
110
+ function isSet(value) {
111
+ return value !== null && value !== undefined;
112
+ }
@@ -0,0 +1,45 @@
1
+ import { BinaryReader, BinaryWriter } from '@bufbuild/protobuf/wire';
2
+ import { Any } from '../protobuf/any';
3
+ export declare const protobufPackage = "google.rpc";
4
+ /**
5
+ * The `Status` type defines a logical error model that is suitable for
6
+ * different programming environments, including REST APIs and RPC APIs. It is
7
+ * used by [gRPC](https://github.com/grpc). Each `Status` message contains
8
+ * three pieces of data: error code, error message, and error details.
9
+ *
10
+ * You can find out more about this error model and how to work with it in the
11
+ * [API Design Guide](https://cloud.google.com/apis/design/errors).
12
+ */
13
+ export interface Status {
14
+ /**
15
+ * The status code, which should be an enum value of
16
+ * [google.rpc.Code][google.rpc.Code].
17
+ */
18
+ code: number;
19
+ /**
20
+ * A developer-facing error message, which should be in English. Any
21
+ * user-facing error message should be localized and sent in the
22
+ * [google.rpc.Status.details][google.rpc.Status.details] field, or localized
23
+ * by the client.
24
+ */
25
+ message: string;
26
+ /**
27
+ * A list of messages that carry the error details. There is a common set of
28
+ * message types for APIs to use.
29
+ */
30
+ details: Any[];
31
+ }
32
+ export declare const Status: MessageFns<Status>;
33
+ type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
34
+ export type DeepPartial<T> = T extends Builtin ? T : T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>> : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>> : T extends {} ? {
35
+ [K in keyof T]?: DeepPartial<T[K]>;
36
+ } : Partial<T>;
37
+ export interface MessageFns<T> {
38
+ encode(message: T, writer?: BinaryWriter): BinaryWriter;
39
+ decode(input: BinaryReader | Uint8Array, length?: number): T;
40
+ fromJSON(object: any): T;
41
+ toJSON(message: T): unknown;
42
+ create(base?: DeepPartial<T>): T;
43
+ fromPartial(object: DeepPartial<T>): T;
44
+ }
45
+ export {};
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
3
+ // versions:
4
+ // protoc-gen-ts_proto v2.11.6
5
+ // protoc v3.19.1
6
+ // source: google/rpc/status.proto
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.Status = exports.protobufPackage = void 0;
9
+ /* eslint-disable */
10
+ const wire_1 = require("@bufbuild/protobuf/wire");
11
+ const any_1 = require("../protobuf/any");
12
+ exports.protobufPackage = 'google.rpc';
13
+ function createBaseStatus() {
14
+ return { code: 0, message: '', details: [] };
15
+ }
16
+ exports.Status = {
17
+ encode(message, writer = new wire_1.BinaryWriter()) {
18
+ if (message.code !== 0) {
19
+ writer.uint32(8).int32(message.code);
20
+ }
21
+ if (message.message !== '') {
22
+ writer.uint32(18).string(message.message);
23
+ }
24
+ for (const v of message.details) {
25
+ any_1.Any.encode(v, writer.uint32(26).fork()).join();
26
+ }
27
+ return writer;
28
+ },
29
+ decode(input, length) {
30
+ const reader = input instanceof wire_1.BinaryReader ? input : new wire_1.BinaryReader(input);
31
+ const end = length === undefined ? reader.len : reader.pos + length;
32
+ const message = createBaseStatus();
33
+ while (reader.pos < end) {
34
+ const tag = reader.uint32();
35
+ switch (tag >>> 3) {
36
+ case 1: {
37
+ if (tag !== 8) {
38
+ break;
39
+ }
40
+ message.code = reader.int32();
41
+ continue;
42
+ }
43
+ case 2: {
44
+ if (tag !== 18) {
45
+ break;
46
+ }
47
+ message.message = reader.string();
48
+ continue;
49
+ }
50
+ case 3: {
51
+ if (tag !== 26) {
52
+ break;
53
+ }
54
+ message.details.push(any_1.Any.decode(reader, reader.uint32()));
55
+ continue;
56
+ }
57
+ }
58
+ if ((tag & 7) === 4 || tag === 0) {
59
+ break;
60
+ }
61
+ reader.skip(tag & 7);
62
+ }
63
+ return message;
64
+ },
65
+ fromJSON(object) {
66
+ return {
67
+ code: isSet(object.code) ? globalThis.Number(object.code) : 0,
68
+ message: isSet(object.message) ? globalThis.String(object.message) : '',
69
+ details: globalThis.Array.isArray(object === null || object === void 0 ? void 0 : object.details)
70
+ ? object.details.map((e) => any_1.Any.fromJSON(e))
71
+ : [],
72
+ };
73
+ },
74
+ toJSON(message) {
75
+ var _a;
76
+ const obj = {};
77
+ if (message.code !== 0) {
78
+ obj.code = Math.round(message.code);
79
+ }
80
+ if (message.message !== '') {
81
+ obj.message = message.message;
82
+ }
83
+ if ((_a = message.details) === null || _a === void 0 ? void 0 : _a.length) {
84
+ obj.details = message.details.map((e) => any_1.Any.toJSON(e));
85
+ }
86
+ return obj;
87
+ },
88
+ create(base) {
89
+ return exports.Status.fromPartial(base !== null && base !== void 0 ? base : {});
90
+ },
91
+ fromPartial(object) {
92
+ var _a, _b, _c;
93
+ const message = createBaseStatus();
94
+ message.code = (_a = object.code) !== null && _a !== void 0 ? _a : 0;
95
+ message.message = (_b = object.message) !== null && _b !== void 0 ? _b : '';
96
+ message.details = ((_c = object.details) === null || _c === void 0 ? void 0 : _c.map((e) => any_1.Any.fromPartial(e))) || [];
97
+ return message;
98
+ },
99
+ };
100
+ function isSet(value) {
101
+ return value !== null && value !== undefined;
102
+ }
@@ -130,6 +130,24 @@ export interface CreateWorkflowVersionRequest {
130
130
  defaultFilters: DefaultFilter[];
131
131
  /** (optional) the JSON schema for the workflow input */
132
132
  inputJsonSchema?: Uint8Array | undefined;
133
+ /** (optional) idempotency configuration for the workflow */
134
+ idempotency?: IdempotencyConfig | undefined;
135
+ }
136
+ export interface IdempotencyConfig {
137
+ /** a CEL expression for determining the idempotency key for workflow runs */
138
+ expression: string;
139
+ /** time-to-live for idempotency keys in milliseconds */
140
+ ttlMs: number;
141
+ }
142
+ export interface IdempotencyCollisionError {
143
+ /** the external ID of the existing workflow run that caused the collision */
144
+ existingRunExternalId: string;
145
+ }
146
+ export interface BulkTriggerIdempotencyCollisionError {
147
+ /** the external IDs of the successfully triggered workflow runs */
148
+ successfulWorkflowRunExternalIds: string[];
149
+ /** the idempotency collision errors */
150
+ collisions: IdempotencyCollisionError[];
133
151
  }
134
152
  export interface DefaultFilter {
135
153
  /** (required) the CEL expression for the filter */
@@ -260,6 +278,9 @@ export declare const TriggerWorkflowRunResponse: MessageFns<TriggerWorkflowRunRe
260
278
  export declare const BranchDurableTaskRequest: MessageFns<BranchDurableTaskRequest>;
261
279
  export declare const BranchDurableTaskResponse: MessageFns<BranchDurableTaskResponse>;
262
280
  export declare const CreateWorkflowVersionRequest: MessageFns<CreateWorkflowVersionRequest>;
281
+ export declare const IdempotencyConfig: MessageFns<IdempotencyConfig>;
282
+ export declare const IdempotencyCollisionError: MessageFns<IdempotencyCollisionError>;
283
+ export declare const BulkTriggerIdempotencyCollisionError: MessageFns<BulkTriggerIdempotencyCollisionError>;
263
284
  export declare const DefaultFilter: MessageFns<DefaultFilter>;
264
285
  export declare const Concurrency: MessageFns<Concurrency>;
265
286
  export declare const CreateTaskOpts: MessageFns<CreateTaskOpts>;
@@ -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
  }
@@ -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>> & {
@@ -32,17 +32,18 @@ const workflow_11 = require("./durable_sleep/workflow");
32
32
  const workflow_12 = require("./logger/workflow");
33
33
  const workflow_13 = require("./non_retryable/workflow");
34
34
  const workflow_14 = require("./on_failure/workflow");
35
- const workflow_15 = require("./on_event/workflow");
36
- const workflow_16 = require("./return_exceptions/workflow");
37
- const workflow_17 = require("./run_details/workflow");
35
+ const workflow_15 = require("./idempotency/workflow");
36
+ const workflow_16 = require("./on_event/workflow");
37
+ const workflow_17 = require("./return_exceptions/workflow");
38
+ const workflow_18 = require("./run_details/workflow");
38
39
  const e2e_workflows_1 = require("./simple/e2e-workflows");
39
- const workflow_18 = require("./streaming/workflow");
40
- const workflow_19 = require("./timeout/workflow");
41
- const workflow_20 = require("./webhooks/workflow");
42
- const workflow_21 = require("./child_index/workflow");
43
- const workflow_22 = require("./support_agent/workflow");
44
- const workflow_23 = require("./welcome_email/workflow");
45
- const workflow_24 = require("./pdf_pipeline/workflow");
40
+ const workflow_19 = require("./streaming/workflow");
41
+ const workflow_20 = require("./timeout/workflow");
42
+ const workflow_21 = require("./webhooks/workflow");
43
+ const workflow_22 = require("./child_index/workflow");
44
+ const workflow_23 = require("./support_agent/workflow");
45
+ const workflow_24 = require("./welcome_email/workflow");
46
+ const workflow_25 = require("./pdf_pipeline/workflow");
46
47
  const workflows = [
47
48
  workflow_1.bulkChild,
48
49
  workflow_1.bulkParentWorkflow,
@@ -86,25 +87,27 @@ const workflows = [
86
87
  (0, workflow_12.createLoggingWorkflow)(hatchet_client_1.hatchet),
87
88
  workflow_13.nonRetryableWorkflow,
88
89
  workflow_14.failureWorkflow,
89
- workflow_15.lower,
90
- workflow_16.returnExceptionsTask,
91
- workflow_17.runDetailTestWorkflow,
90
+ workflow_15.idempotentTask,
91
+ workflow_15.idempotentTaskShortWindow,
92
+ workflow_16.lower,
93
+ workflow_17.returnExceptionsTask,
94
+ workflow_18.runDetailTestWorkflow,
92
95
  e2e_workflows_1.helloWorld,
93
96
  e2e_workflows_1.helloWorldDurable,
94
- workflow_18.streamingTask,
95
- workflow_19.timeoutTask,
96
- workflow_19.refreshTimeoutTask,
97
- workflow_20.webhookWorkflow,
98
- workflow_21.childIndexChild,
99
- workflow_21.childIndexParent,
100
- workflow_21.scenarioTask,
101
- workflow_21.orchestratorTask,
102
- workflow_22.supportAgent,
103
- workflow_22.triageTicket,
104
- workflow_22.generateReply,
105
- workflow_22.escalateTicket,
106
- workflow_23.welcomeEmail,
107
- workflow_24.pdfPipeline,
97
+ workflow_19.streamingTask,
98
+ workflow_20.timeoutTask,
99
+ workflow_20.refreshTimeoutTask,
100
+ workflow_21.webhookWorkflow,
101
+ workflow_22.childIndexChild,
102
+ workflow_22.childIndexParent,
103
+ workflow_22.scenarioTask,
104
+ workflow_22.orchestratorTask,
105
+ workflow_23.supportAgent,
106
+ workflow_23.triageTicket,
107
+ workflow_23.generateReply,
108
+ workflow_23.escalateTicket,
109
+ workflow_24.welcomeEmail,
110
+ workflow_25.pdfPipeline,
108
111
  ];
109
112
  function main() {
110
113
  return __awaiter(this, void 0, void 0, function* () {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const v1_1 = require("../..");
13
+ const workflow_1 = require("./workflow");
14
+ function main() {
15
+ return __awaiter(this, void 0, void 0, function* () {
16
+ // > trigger
17
+ const ref1 = yield workflow_1.idempotentTask.runNoWait({ id: '123' });
18
+ let runId2;
19
+ try {
20
+ const ref2 = yield workflow_1.idempotentTask.runNoWait({ id: '123' });
21
+ runId2 = yield ref2.getWorkflowRunId();
22
+ }
23
+ catch (e) {
24
+ if (e instanceof v1_1.IdempotencyCollisionError) {
25
+ console.log(`Run with external ID ${e.existingRunExternalId} already exists for this idempotency key`);
26
+ runId2 = e.existingRunExternalId;
27
+ }
28
+ else {
29
+ throw e;
30
+ }
31
+ }
32
+ const res1 = yield ref1.result();
33
+ console.log(`Result: ${JSON.stringify(res1)}, run ID: ${runId2}`);
34
+ // !!
35
+ });
36
+ }
37
+ main().catch(console.error);
@@ -0,0 +1,10 @@
1
+ export declare const EVENT_KEY = "ts-e2e:idempotency-example";
2
+ export type IdempotencyInput = {
3
+ id: string;
4
+ };
5
+ export declare const idempotentTask: import("../..").TaskWorkflowDeclaration<IdempotencyInput, {
6
+ result: string;
7
+ }, {}, {}, {}, {}>;
8
+ export declare const idempotentTaskShortWindow: import("../..").TaskWorkflowDeclaration<IdempotencyInput, {
9
+ result: string;
10
+ }, {}, {}, {}, {}>;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.idempotentTaskShortWindow = exports.idempotentTask = exports.EVENT_KEY = void 0;
13
+ const hatchet_client_1 = require("../hatchet-client");
14
+ exports.EVENT_KEY = 'ts-e2e:idempotency-example';
15
+ // > idempotency
16
+ exports.idempotentTask = hatchet_client_1.hatchet.task({
17
+ name: 'ts-e2e-idempotent-task',
18
+ idempotency: {
19
+ strategy: 'ttl',
20
+ expression: 'input.id',
21
+ ttlMs: 60000,
22
+ },
23
+ onEvents: [exports.EVENT_KEY],
24
+ fn: (input) => __awaiter(void 0, void 0, void 0, function* () {
25
+ return { result: `Hello, world from task ${input.id}` };
26
+ }),
27
+ });
28
+ // !!
29
+ exports.idempotentTaskShortWindow = hatchet_client_1.hatchet.task({
30
+ name: 'ts-e2e-idempotent-task-short-window',
31
+ idempotency: {
32
+ strategy: 'ttl',
33
+ expression: 'input.id',
34
+ ttlMs: 2000,
35
+ },
36
+ fn: (input) => __awaiter(void 0, void 0, void 0, function* () {
37
+ return { result: `Hello, world from task ${input.id}` };
38
+ }),
39
+ });
package/v1/index.d.ts CHANGED
@@ -12,6 +12,8 @@ export * from './slot-types';
12
12
  export * from '../legacy/legacy-transformer';
13
13
  export { NonDeterminismError } from '../util/errors/non-determinism-error';
14
14
  export { EvictionNotSupportedError } from '../util/errors/eviction-not-supported-error';
15
+ export { IdempotencyCollisionError } from '../util/errors/idempotency-collision-error';
16
+ export { BulkTriggerIdempotencyCollisionError } from '../util/errors/bulk-trigger-idempotency-collision-error';
15
17
  export { EvictionPolicy, DEFAULT_DURABLE_TASK_EVICTION_POLICY, } from './client/worker/eviction/eviction-policy';
16
18
  export { DurableEvictionConfig } from './client/worker/eviction/eviction-manager';
17
19
  export { MinEngineVersion, supportsEviction } from './client/worker/engine-version';
package/v1/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.supportsEviction = exports.MinEngineVersion = exports.DEFAULT_DURABLE_TASK_EVICTION_POLICY = exports.EvictionNotSupportedError = exports.NonDeterminismError = void 0;
17
+ exports.supportsEviction = exports.MinEngineVersion = exports.DEFAULT_DURABLE_TASK_EVICTION_POLICY = exports.BulkTriggerIdempotencyCollisionError = exports.IdempotencyCollisionError = exports.EvictionNotSupportedError = exports.NonDeterminismError = void 0;
18
18
  __exportStar(require("./client/client"), exports);
19
19
  __exportStar(require("./client/features"), exports);
20
20
  __exportStar(require("./client/worker/worker"), exports);
@@ -30,6 +30,10 @@ var non_determinism_error_1 = require("../util/errors/non-determinism-error");
30
30
  Object.defineProperty(exports, "NonDeterminismError", { enumerable: true, get: function () { return non_determinism_error_1.NonDeterminismError; } });
31
31
  var eviction_not_supported_error_1 = require("../util/errors/eviction-not-supported-error");
32
32
  Object.defineProperty(exports, "EvictionNotSupportedError", { enumerable: true, get: function () { return eviction_not_supported_error_1.EvictionNotSupportedError; } });
33
+ var idempotency_collision_error_1 = require("../util/errors/idempotency-collision-error");
34
+ Object.defineProperty(exports, "IdempotencyCollisionError", { enumerable: true, get: function () { return idempotency_collision_error_1.IdempotencyCollisionError; } });
35
+ var bulk_trigger_idempotency_collision_error_1 = require("../util/errors/bulk-trigger-idempotency-collision-error");
36
+ Object.defineProperty(exports, "BulkTriggerIdempotencyCollisionError", { enumerable: true, get: function () { return bulk_trigger_idempotency_collision_error_1.BulkTriggerIdempotencyCollisionError; } });
33
37
  var eviction_policy_1 = require("./client/worker/eviction/eviction-policy");
34
38
  Object.defineProperty(exports, "DEFAULT_DURABLE_TASK_EVICTION_POLICY", { enumerable: true, get: function () { return eviction_policy_1.DEFAULT_DURABLE_TASK_EVICTION_POLICY; } });
35
39
  var engine_version_1 = require("./client/worker/engine-version");
package/v1/task.d.ts CHANGED
@@ -36,6 +36,30 @@ export type Concurrency = {
36
36
  * @deprecated use Concurrency instead
37
37
  */
38
38
  export type TaskConcurrency = Concurrency;
39
+ /**
40
+ * Base type for idempotency configurations.
41
+ */
42
+ export type BaseIdempotencyConfig = {
43
+ /**
44
+ * CEL expression to create an idempotency key from input and metadata.
45
+ * @example "input.id" // use the 'id' field from input as the key
46
+ */
47
+ expression: string;
48
+ };
49
+ /**
50
+ * TTL-based idempotency: prevents duplicate runs within a sliding time window.
51
+ */
52
+ export type TTLBasedIdempotencyConfig = BaseIdempotencyConfig & {
53
+ strategy: 'ttl';
54
+ /**
55
+ * How long the idempotency key should live (in milliseconds).
56
+ */
57
+ ttlMs: number;
58
+ };
59
+ /**
60
+ * Union of all supported idempotency configurations.
61
+ */
62
+ export type IdempotencyConfig = TTLBasedIdempotencyConfig;
39
63
  export declare class NonRetryableError extends Error {
40
64
  constructor(message?: string);
41
65
  }
package/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const HATCHET_VERSION = "1.25.0";
1
+ export declare const HATCHET_VERSION = "1.26.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.25.0';
4
+ exports.HATCHET_VERSION = '1.26.0';