@hatchet-dev/typescript-sdk 1.34.0-alpha.1 → 1.34.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/README.md +1 -0
- package/clients/admin/rpc.d.ts +1 -1
- package/clients/admin/rpc.js +3 -3
- package/clients/admin/trigger-request.d.ts +18 -0
- package/clients/admin/trigger-request.js +66 -0
- package/clients/event/event-client.d.ts +4 -17
- package/clients/event/event-client.js +5 -43
- package/clients/event/rpc.d.ts +22 -3
- package/clients/event/rpc.js +66 -3
- package/clients/transport/fetch-transport.d.ts +73 -0
- package/clients/transport/fetch-transport.js +152 -0
- package/clients/transport/grpc-target.js +48 -3
- package/clients/transport/index.d.ts +2 -1
- package/clients/transport/index.js +6 -1
- package/clients/transport/node-transport.js +1 -2
- package/clients/transport/transport.d.ts +6 -0
- package/clients/transport/transport.js +7 -0
- package/clients/transport/ts-proto-client.js +16 -2
- package/core/client.d.ts +102 -0
- package/core/client.js +250 -0
- package/core/features/events.d.ts +25 -0
- package/core/features/events.js +57 -0
- package/core/features/logs.d.ts +26 -0
- package/core/features/logs.js +51 -0
- package/core/features/rate-limits.d.ts +19 -0
- package/core/features/rate-limits.js +30 -0
- package/core/features/runs.d.ts +33 -0
- package/core/features/runs.js +84 -0
- package/core/features/streams.d.ts +11 -0
- package/core/features/streams.js +23 -0
- package/core/features/workflows.d.ts +22 -0
- package/core/features/workflows.js +46 -0
- package/core/index.d.ts +42 -4
- package/core/index.js +79 -5
- package/core/logger.d.ts +19 -0
- package/core/logger.js +45 -0
- package/core/run-detail.d.ts +14 -0
- package/core/run-detail.js +79 -0
- package/core/run-ref.d.ts +63 -0
- package/core/run-ref.js +217 -0
- package/core/types.d.ts +182 -0
- package/core/types.js +2 -0
- package/dist/check-edge-entry.mjs +72 -14
- package/dist/check-edge-entry.test.ts +57 -0
- package/package.json +5 -2
- package/protoc/v1/streams.d.ts +79 -0
- package/protoc/v1/streams.js +511 -0
- package/protoc-es/v1/streams_pb.d.ts +133 -0
- package/protoc-es/v1/streams_pb.js +41 -0
- package/scripts/check-edge-entry.mjs +72 -14
- package/scripts/check-edge-entry.test.ts +57 -0
- package/util/batch.js +2 -1
- package/util/config-loader/token.d.ts +5 -0
- package/util/config-loader/token.js +8 -0
- package/util/errors/bulk-trigger-partial-error.d.ts +17 -0
- package/util/errors/bulk-trigger-partial-error.js +55 -0
- package/v1/client/admin.d.ts +4 -54
- package/v1/client/admin.js +9 -72
- package/v1/client/client.d.ts +14 -1
- package/v1/client/client.js +18 -0
- package/v1/client/features/index.d.ts +1 -0
- package/v1/client/features/index.js +1 -0
- package/v1/client/features/runs.d.ts +9 -27
- package/v1/client/features/runs.js +10 -55
- package/v1/client/features/streams.d.ts +47 -0
- package/v1/client/features/streams.js +210 -0
- package/v1/examples/durable-streams/publish-latency.d.ts +15 -0
- package/v1/examples/durable-streams/publish-latency.js +50 -0
- package/v1/examples/durable_callback_ordering/workflow.js +5 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
package/v1/client/admin.js
CHANGED
|
@@ -8,17 +8,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
-
var __rest = (this && this.__rest) || function (s, e) {
|
|
12
|
-
var t = {};
|
|
13
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
14
|
-
t[p] = s[p];
|
|
15
|
-
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
16
|
-
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
17
|
-
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
18
|
-
t[p[i]] = s[p[i]];
|
|
19
|
-
}
|
|
20
|
-
return t;
|
|
21
|
-
};
|
|
22
11
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
23
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
24
13
|
};
|
|
@@ -26,51 +15,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
26
15
|
exports.AdminClient = void 0;
|
|
27
16
|
const hatchet_error_1 = __importDefault(require("../../util/errors/hatchet-error"));
|
|
28
17
|
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");
|
|
30
18
|
const workflow_run_ref_1 = __importDefault(require("../../util/workflow-run-ref"));
|
|
31
|
-
const connect_1 = require("@connectrpc/connect");
|
|
32
|
-
const workflows_pb_1 = require("../../protoc-es/v1/workflows_pb");
|
|
33
19
|
const nice_grpc_1 = require("nice-grpc");
|
|
34
20
|
const grpc_helpers_1 = require("../../util/grpc-helpers");
|
|
35
21
|
const config_loader_1 = require("../../util/config-loader");
|
|
36
22
|
const child_listener_client_1 = require("../../clients/listeners/run-listener/child-listener-client");
|
|
37
23
|
const workflows_1 = require("../../protoc/workflows");
|
|
38
24
|
const rpc_1 = require("../../clients/admin/rpc");
|
|
25
|
+
const trigger_request_1 = require("../../clients/admin/trigger-request");
|
|
39
26
|
const transport_1 = require("../../clients/transport");
|
|
40
27
|
const retrier_1 = require("../../util/retrier");
|
|
41
28
|
const batch_1 = require("../../util/batch");
|
|
42
|
-
const apply_namespace_1 = require("../../util/apply-namespace");
|
|
43
|
-
/**
|
|
44
|
-
* The engine answers a trigger whose idempotency key is already taken with `ALREADY_EXISTS`
|
|
45
|
-
* and attaches the collision details to the status. The Connect client decodes those details
|
|
46
|
-
* from the trailer, so the error carries everything needed to build the SDK's collision errors.
|
|
47
|
-
*/
|
|
48
|
-
function isAlreadyExists(e) {
|
|
49
|
-
return e instanceof connect_1.ConnectError && e.code === connect_1.Code.AlreadyExists;
|
|
50
|
-
}
|
|
51
|
-
function extractExistingRunId(e) {
|
|
52
|
-
var _a;
|
|
53
|
-
const [detail] = e.findDetails(workflows_pb_1.IdempotencyCollisionErrorSchema);
|
|
54
|
-
return (_a = detail === null || detail === void 0 ? void 0 : detail.existingRunExternalId) !== null && _a !== void 0 ? _a : '';
|
|
55
|
-
}
|
|
56
|
-
function extractBulkTriggerCollision(e) {
|
|
57
|
-
const [detail] = e.findDetails(workflows_pb_1.BulkTriggerIdempotencyCollisionErrorSchema);
|
|
58
|
-
if (!detail)
|
|
59
|
-
return null;
|
|
60
|
-
return new bulk_trigger_idempotency_collision_error_1.BulkTriggerIdempotencyCollisionError(detail.successfulWorkflowRunExternalIds, detail.collisions.map((c) => new idempotency_collision_error_1.IdempotencyCollisionError(c.existingRunExternalId)));
|
|
61
|
-
}
|
|
62
|
-
function convertDesiredWorkerLabels(labels) {
|
|
63
|
-
return Object.fromEntries(Object.entries(labels).map(([key, label]) => [
|
|
64
|
-
key,
|
|
65
|
-
{
|
|
66
|
-
strValue: typeof label.value === 'string' ? label.value : undefined,
|
|
67
|
-
intValue: typeof label.value === 'number' ? label.value : undefined,
|
|
68
|
-
required: label.required,
|
|
69
|
-
weight: label.weight,
|
|
70
|
-
comparator: label.comparator,
|
|
71
|
-
},
|
|
72
|
-
]));
|
|
73
|
-
}
|
|
74
29
|
class AdminClient {
|
|
75
30
|
constructor(config, api, runs, transport = (0, transport_1.createNodeTransport)(config)) {
|
|
76
31
|
this.config = config;
|
|
@@ -111,24 +66,16 @@ class AdminClient {
|
|
|
111
66
|
runWorkflow(workflowName, input, options) {
|
|
112
67
|
return __awaiter(this, void 0, void 0, function* () {
|
|
113
68
|
try {
|
|
114
|
-
const
|
|
115
|
-
const
|
|
116
|
-
const opts = options !== null && options !== void 0 ? options : {};
|
|
117
|
-
const { additionalMetadata, parentStepRunId, parentTaskRunExternalId, desiredWorkerLabels } = opts, rest = __rest(opts, ["additionalMetadata", "parentStepRunId", "parentTaskRunExternalId", "desiredWorkerLabels"]);
|
|
118
|
-
const request = Object.assign(Object.assign({ name: computedName, input: inputStr }, rest), {
|
|
119
|
-
// API expects `parentTaskRunExternalId`; accept old names as aliases.
|
|
120
|
-
parentTaskRunExternalId: parentTaskRunExternalId !== null && parentTaskRunExternalId !== void 0 ? parentTaskRunExternalId : parentStepRunId, additionalMetadata: additionalMetadata ? JSON.stringify(additionalMetadata) : undefined, priority: opts.priority, desiredWorkerLabels: desiredWorkerLabels
|
|
121
|
-
? convertDesiredWorkerLabels(desiredWorkerLabels)
|
|
122
|
-
: {} });
|
|
123
|
-
const resp = yield (0, retrier_1.retrier)(() => __awaiter(this, void 0, void 0, function* () { return this.workflowsGrpc.triggerWorkflow(request); }), this.logger, Object.assign(Object.assign({}, this.config.retrier), { shouldRetry: (e) => !isAlreadyExists(e) }));
|
|
69
|
+
const request = (0, trigger_request_1.buildTriggerWorkflowRequest)(workflowName, input, options, this.config.namespace);
|
|
70
|
+
const resp = yield (0, retrier_1.retrier)(() => __awaiter(this, void 0, void 0, function* () { return this.workflowsGrpc.triggerWorkflow(request); }), this.logger, Object.assign(Object.assign({}, this.config.retrier), { shouldRetry: (e) => !(0, trigger_request_1.isAlreadyExists)(e) }));
|
|
124
71
|
const id = resp.workflowRunId;
|
|
125
72
|
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);
|
|
126
73
|
yield ref.getWorkflowRunId();
|
|
127
74
|
return ref;
|
|
128
75
|
}
|
|
129
76
|
catch (e) {
|
|
130
|
-
if (isAlreadyExists(e)) {
|
|
131
|
-
throw new idempotency_collision_error_1.IdempotencyCollisionError(extractExistingRunId(e));
|
|
77
|
+
if ((0, trigger_request_1.isAlreadyExists)(e)) {
|
|
78
|
+
throw new idempotency_collision_error_1.IdempotencyCollisionError((0, trigger_request_1.extractExistingRunId)(e));
|
|
132
79
|
}
|
|
133
80
|
throw new hatchet_error_1.default(e instanceof Error ? e.message : String(e));
|
|
134
81
|
}
|
|
@@ -147,17 +94,7 @@ class AdminClient {
|
|
|
147
94
|
return __awaiter(this, arguments, void 0, function* (workflowRuns, batchSize = 500) {
|
|
148
95
|
var _a;
|
|
149
96
|
// Prepare workflows to be triggered in bulk
|
|
150
|
-
const workflowRequests = workflowRuns.map(({ workflowName, input, options }) =>
|
|
151
|
-
const computedName = (0, apply_namespace_1.applyNamespace)(workflowName, this.config.namespace).toLowerCase();
|
|
152
|
-
const inputStr = JSON.stringify(input);
|
|
153
|
-
const opts = options !== null && options !== void 0 ? options : {};
|
|
154
|
-
const { additionalMetadata, parentStepRunId, parentTaskRunExternalId, desiredWorkerLabels } = opts, rest = __rest(opts, ["additionalMetadata", "parentStepRunId", "parentTaskRunExternalId", "desiredWorkerLabels"]);
|
|
155
|
-
return Object.assign(Object.assign({ name: computedName, input: inputStr }, rest), {
|
|
156
|
-
// API expects `parentTaskRunExternalId`; accept old names as aliases.
|
|
157
|
-
parentTaskRunExternalId: parentTaskRunExternalId !== null && parentTaskRunExternalId !== void 0 ? parentTaskRunExternalId : parentStepRunId, additionalMetadata: additionalMetadata ? JSON.stringify(additionalMetadata) : undefined, desiredWorkerLabels: desiredWorkerLabels
|
|
158
|
-
? convertDesiredWorkerLabels(desiredWorkerLabels)
|
|
159
|
-
: {} });
|
|
160
|
-
});
|
|
97
|
+
const workflowRequests = workflowRuns.map(({ workflowName, input, options }) => (0, trigger_request_1.buildTriggerWorkflowRequest)(workflowName, input, options, this.config.namespace));
|
|
161
98
|
const batches = (0, batch_1.batch)(workflowRequests, batchSize, (_a = this.config.grpc_max_send_message_length) !== null && _a !== void 0 ? _a : 4 * 1024 * 1024);
|
|
162
99
|
this.logger.debug(`batching ${batches.length} batches`);
|
|
163
100
|
try {
|
|
@@ -168,7 +105,7 @@ class AdminClient {
|
|
|
168
105
|
workflows: payloads,
|
|
169
106
|
});
|
|
170
107
|
// Call the bulk trigger workflow method for this batch
|
|
171
|
-
const bulkTriggerWorkflowResponse = yield (0, retrier_1.retrier)(() => __awaiter(this, void 0, void 0, function* () { return this.workflowsGrpc.bulkTriggerWorkflow(request); }), this.logger, Object.assign(Object.assign({}, this.config.retrier), { shouldRetry: (e) => !isAlreadyExists(e) }));
|
|
108
|
+
const bulkTriggerWorkflowResponse = yield (0, retrier_1.retrier)(() => __awaiter(this, void 0, void 0, function* () { return this.workflowsGrpc.bulkTriggerWorkflow(request); }), this.logger, Object.assign(Object.assign({}, this.config.retrier), { shouldRetry: (e) => !(0, trigger_request_1.isAlreadyExists)(e) }));
|
|
172
109
|
this.logger.debug(`batch ${batchIndex + 1} of ${batches.length}`);
|
|
173
110
|
// Map the results back to their original indices
|
|
174
111
|
const batchResults = bulkTriggerWorkflowResponse.workflowRunIds.map((resp, index) => {
|
|
@@ -181,8 +118,8 @@ class AdminClient {
|
|
|
181
118
|
return results;
|
|
182
119
|
}
|
|
183
120
|
catch (e) {
|
|
184
|
-
if (isAlreadyExists(e)) {
|
|
185
|
-
const collision = extractBulkTriggerCollision(e);
|
|
121
|
+
if ((0, trigger_request_1.isAlreadyExists)(e)) {
|
|
122
|
+
const collision = (0, trigger_request_1.extractBulkTriggerCollision)(e);
|
|
186
123
|
if (collision)
|
|
187
124
|
throw collision;
|
|
188
125
|
}
|
package/v1/client/client.d.ts
CHANGED
|
@@ -13,12 +13,13 @@ import { DispatcherClient } from '../../clients/dispatcher/dispatcher-client';
|
|
|
13
13
|
import { Logger } from '../../util/logger';
|
|
14
14
|
import { RunListenerClient } from '../../clients/listeners/run-listener/child-listener-client';
|
|
15
15
|
import { DurableListenerClient } from '../../clients/listeners/durable-listener/durable-listener-client';
|
|
16
|
+
import { type Transport } from '../../clients/transport';
|
|
16
17
|
import { CreateTaskWorkflowOpts, CreateWorkflowOpts, RunOpts, BaseWorkflowDeclaration, WorkflowDeclaration, TaskWorkflowDeclaration, CreateDurableTaskWorkflowOpts, CreateBatchTaskWorkflowOpts } from '../declaration';
|
|
17
18
|
import { BatchTaskFn } from '../task';
|
|
18
19
|
import type { LegacyWorkflow } from '../../legacy/legacy-transformer';
|
|
19
20
|
import { IHatchetClient } from './client.interface';
|
|
20
21
|
import { CreateWorkerOpts, Worker } from './worker/worker';
|
|
21
|
-
import { CELClient, CronClient, FiltersClient, LogsClient, MetricsClient, RatelimitsClient, RunsClient, ScheduleClient, TenantClient, WebhooksClient, WorkersClient, WorkflowsClient } from './features';
|
|
22
|
+
import { CELClient, CronClient, FiltersClient, LogsClient, MetricsClient, RatelimitsClient, RunsClient, ScheduleClient, StreamsClient, TenantClient, WebhooksClient, WorkersClient, WorkflowsClient } from './features';
|
|
22
23
|
import { InputType, OutputType, UnknownInputType, StrictWorkflowOutputType, Resolved } from '../types';
|
|
23
24
|
import { AdminClient } from './admin';
|
|
24
25
|
import { DurableContext } from './worker/context';
|
|
@@ -241,6 +242,11 @@ export declare class HatchetClient<GlobalInput extends Record<string, any> = {},
|
|
|
241
242
|
* @internal
|
|
242
243
|
*/
|
|
243
244
|
get listener(): RunListenerClient;
|
|
245
|
+
/**
|
|
246
|
+
* The Connect transport the client's unary RPCs go through
|
|
247
|
+
* @internal
|
|
248
|
+
*/
|
|
249
|
+
get transport(): Transport;
|
|
244
250
|
/**
|
|
245
251
|
* @deprecated use client.events instead
|
|
246
252
|
* @hidden
|
|
@@ -288,6 +294,13 @@ export declare class HatchetClient<GlobalInput extends Record<string, any> = {},
|
|
|
288
294
|
* @returns A runs client instance
|
|
289
295
|
*/
|
|
290
296
|
get runs(): RunsClient;
|
|
297
|
+
private _streams;
|
|
298
|
+
/**
|
|
299
|
+
* Get the streams client for publishing to and reading from durable,
|
|
300
|
+
* topic-based streams
|
|
301
|
+
* @returns A streams client instance
|
|
302
|
+
*/
|
|
303
|
+
get streams(): StreamsClient;
|
|
291
304
|
private _workflows;
|
|
292
305
|
/**
|
|
293
306
|
* Get the workflows client for creating and managing workflows
|
package/v1/client/client.js
CHANGED
|
@@ -321,6 +321,13 @@ class HatchetClient {
|
|
|
321
321
|
get listener() {
|
|
322
322
|
return this._listener;
|
|
323
323
|
}
|
|
324
|
+
/**
|
|
325
|
+
* The Connect transport the client's unary RPCs go through
|
|
326
|
+
* @internal
|
|
327
|
+
*/
|
|
328
|
+
get transport() {
|
|
329
|
+
return this._transport;
|
|
330
|
+
}
|
|
324
331
|
/**
|
|
325
332
|
* @deprecated use client.events instead
|
|
326
333
|
* @hidden
|
|
@@ -398,6 +405,17 @@ class HatchetClient {
|
|
|
398
405
|
}
|
|
399
406
|
return this._runs;
|
|
400
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* Get the streams client for publishing to and reading from durable,
|
|
410
|
+
* topic-based streams
|
|
411
|
+
* @returns A streams client instance
|
|
412
|
+
*/
|
|
413
|
+
get streams() {
|
|
414
|
+
if (!this._streams) {
|
|
415
|
+
this._streams = new features_1.StreamsClient(this);
|
|
416
|
+
}
|
|
417
|
+
return this._streams;
|
|
418
|
+
}
|
|
401
419
|
/**
|
|
402
420
|
* Get the workflows client for creating and managing workflows
|
|
403
421
|
* @returns A workflows client instance
|
|
@@ -1,32 +1,13 @@
|
|
|
1
1
|
import WorkflowRunRef from '../../../util/workflow-run-ref';
|
|
2
|
-
import { V1TaskStatus } from '../../../clients/rest/generated/data-contracts';
|
|
3
2
|
import { RunListenerClient } from '../../../clients/listeners/run-listener/child-listener-client';
|
|
4
3
|
import { WorkflowsClient } from './workflows';
|
|
5
4
|
import { HatchetClient } from '../client';
|
|
6
|
-
import { runStatusToJSON } from '../../../
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
done: boolean;
|
|
10
|
-
input: unknown;
|
|
11
|
-
additionalMetadata: unknown;
|
|
12
|
-
isEvicted: boolean;
|
|
13
|
-
taskRuns: Record<string, TaskRunDetail>;
|
|
14
|
-
};
|
|
15
|
-
export type TaskRunDetail = {
|
|
16
|
-
externalId: string;
|
|
17
|
-
readableId: string;
|
|
18
|
-
status: V1TaskStatus;
|
|
19
|
-
output: unknown;
|
|
20
|
-
error?: string;
|
|
21
|
-
isEvicted: boolean;
|
|
22
|
-
};
|
|
5
|
+
import { runStatusToJSON } from '../../../core/run-detail';
|
|
6
|
+
import type { RunDetail, RunFilterBase, TaskRunDetail } from '../../../core/types';
|
|
7
|
+
export type { RunDetail, TaskRunDetail };
|
|
23
8
|
export { runStatusToJSON };
|
|
24
|
-
export type RunFilter = {
|
|
25
|
-
since?: Date;
|
|
26
|
-
until?: Date;
|
|
27
|
-
statuses?: V1TaskStatus[];
|
|
9
|
+
export type RunFilter = RunFilterBase & {
|
|
28
10
|
workflowNames?: string[];
|
|
29
|
-
additionalMetadata?: Record<string, string>;
|
|
30
11
|
};
|
|
31
12
|
export type CancelRunOpts = {
|
|
32
13
|
ids?: string[];
|
|
@@ -90,10 +71,11 @@ export declare class RunsClient {
|
|
|
90
71
|
tenantId: string;
|
|
91
72
|
workflows: WorkflowsClient;
|
|
92
73
|
listener: RunListenerClient;
|
|
93
|
-
private
|
|
94
|
-
private
|
|
74
|
+
private _transport;
|
|
75
|
+
private _adminRpc;
|
|
95
76
|
constructor(client: HatchetClient);
|
|
96
|
-
|
|
77
|
+
/** The v1 admin RPCs on the client's Connect transport, the same path the core client uses. */
|
|
78
|
+
private get adminRpc();
|
|
97
79
|
/**
|
|
98
80
|
* Gets a task or workflow run by its ID.
|
|
99
81
|
* @param run - The ID of the run to get.
|
|
@@ -105,7 +87,7 @@ export declare class RunsClient {
|
|
|
105
87
|
* @param run - The ID of the run to get the status of.
|
|
106
88
|
* @returns A promise that resolves to the status of the run.
|
|
107
89
|
*/
|
|
108
|
-
get_status<T = any>(run: string | WorkflowRunRef<T>): Promise<V1TaskStatus>;
|
|
90
|
+
get_status<T = any>(run: string | WorkflowRunRef<T>): Promise<import("../../../clients/rest/generated/data-contracts").V1TaskStatus>;
|
|
109
91
|
/**
|
|
110
92
|
* Gets run details
|
|
111
93
|
* @param run - The workflow run ID (string) or a WorkflowRunRef.
|
|
@@ -34,55 +34,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
34
34
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
35
|
exports.RunsClient = exports.runStatusToJSON = void 0;
|
|
36
36
|
const workflow_run_ref_1 = __importDefault(require("../../../util/workflow-run-ref"));
|
|
37
|
-
const data_contracts_1 = require("../../../clients/rest/generated/data-contracts");
|
|
38
37
|
const child_listener_client_1 = require("../../../clients/listeners/run-listener/child-listener-client");
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// EVICTED is not in V1TaskStatus; treat as RUNNING per the proto comment.
|
|
43
|
-
const PROTO_STATUS_MAP = {
|
|
44
|
-
[workflows_1.RunStatus.QUEUED]: data_contracts_1.V1TaskStatus.QUEUED,
|
|
45
|
-
[workflows_1.RunStatus.RUNNING]: data_contracts_1.V1TaskStatus.RUNNING,
|
|
46
|
-
[workflows_1.RunStatus.COMPLETED]: data_contracts_1.V1TaskStatus.COMPLETED,
|
|
47
|
-
[workflows_1.RunStatus.FAILED]: data_contracts_1.V1TaskStatus.FAILED,
|
|
48
|
-
[workflows_1.RunStatus.CANCELLED]: data_contracts_1.V1TaskStatus.CANCELLED,
|
|
49
|
-
[workflows_1.RunStatus.EVICTED]: data_contracts_1.V1TaskStatus.RUNNING,
|
|
50
|
-
[workflows_1.RunStatus.UNRECOGNIZED]: data_contracts_1.V1TaskStatus.RUNNING,
|
|
51
|
-
};
|
|
52
|
-
function decodeBytes(b) {
|
|
53
|
-
if (!(b === null || b === void 0 ? void 0 : b.length))
|
|
54
|
-
return null;
|
|
55
|
-
try {
|
|
56
|
-
return JSON.parse(new TextDecoder().decode(b));
|
|
57
|
-
}
|
|
58
|
-
catch (_a) {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
function toRunDetail(raw) {
|
|
63
|
-
var _a;
|
|
64
|
-
return {
|
|
65
|
-
status: (_a = PROTO_STATUS_MAP[raw.status]) !== null && _a !== void 0 ? _a : data_contracts_1.V1TaskStatus.RUNNING,
|
|
66
|
-
done: raw.done,
|
|
67
|
-
input: decodeBytes(raw.input),
|
|
68
|
-
additionalMetadata: decodeBytes(raw.additionalMetadata),
|
|
69
|
-
isEvicted: raw.isEvicted,
|
|
70
|
-
taskRuns: Object.fromEntries(Object.entries(raw.taskRuns).map(([id, tr]) => {
|
|
71
|
-
var _a;
|
|
72
|
-
return [
|
|
73
|
-
id,
|
|
74
|
-
{
|
|
75
|
-
externalId: tr.externalId,
|
|
76
|
-
readableId: tr.readableId,
|
|
77
|
-
status: (_a = PROTO_STATUS_MAP[tr.status]) !== null && _a !== void 0 ? _a : data_contracts_1.V1TaskStatus.RUNNING,
|
|
78
|
-
output: decodeBytes(tr.output),
|
|
79
|
-
error: tr.error,
|
|
80
|
-
isEvicted: tr.isEvicted,
|
|
81
|
-
},
|
|
82
|
-
];
|
|
83
|
-
})),
|
|
84
|
-
};
|
|
85
|
-
}
|
|
38
|
+
const rpc_1 = require("../../../clients/admin/rpc");
|
|
39
|
+
const run_detail_1 = require("../../../core/run-detail");
|
|
40
|
+
Object.defineProperty(exports, "runStatusToJSON", { enumerable: true, get: function () { return run_detail_1.runStatusToJSON; } });
|
|
86
41
|
/**
|
|
87
42
|
* The runs client is a client for interacting with task and workflow runs within Hatchet.
|
|
88
43
|
*/
|
|
@@ -92,14 +47,14 @@ class RunsClient {
|
|
|
92
47
|
this.tenantId = client.tenantId;
|
|
93
48
|
this.workflows = client.workflows;
|
|
94
49
|
this.listener = client._listener;
|
|
95
|
-
this.
|
|
50
|
+
this._transport = client.transport;
|
|
96
51
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
this.
|
|
52
|
+
/** The v1 admin RPCs on the client's Connect transport, the same path the core client uses. */
|
|
53
|
+
get adminRpc() {
|
|
54
|
+
if (!this._adminRpc) {
|
|
55
|
+
this._adminRpc = (0, rpc_1.createV1AdminRpc)(this._transport);
|
|
101
56
|
}
|
|
102
|
-
return this.
|
|
57
|
+
return this._adminRpc;
|
|
103
58
|
}
|
|
104
59
|
/**
|
|
105
60
|
* Gets a task or workflow run by its ID.
|
|
@@ -133,7 +88,7 @@ class RunsClient {
|
|
|
133
88
|
getDetails(run) {
|
|
134
89
|
return __awaiter(this, void 0, void 0, function* () {
|
|
135
90
|
const runId = typeof run === 'string' ? run : yield run.getWorkflowRunId();
|
|
136
|
-
return toRunDetail(yield this.
|
|
91
|
+
return (0, run_detail_1.toRunDetail)(yield this.adminRpc.getRunDetails({ externalId: runId }));
|
|
137
92
|
});
|
|
138
93
|
}
|
|
139
94
|
/**
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { HatchetClient } from '../client';
|
|
2
|
+
export type StreamEvent = {
|
|
3
|
+
/** the message payload */
|
|
4
|
+
payload: Uint8Array;
|
|
5
|
+
/** resumes events() right after this event */
|
|
6
|
+
cursor: string;
|
|
7
|
+
/** when the message was stored */
|
|
8
|
+
createdAt?: Date;
|
|
9
|
+
};
|
|
10
|
+
export type StreamCallOptions = {
|
|
11
|
+
namespace?: string;
|
|
12
|
+
cursor?: string;
|
|
13
|
+
/** ends an events() iteration cleanly, without throwing */
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* StreamsClient publishes to and reads from durable topics. Unlike
|
|
18
|
+
* `runs.subscribeToStream`, topics are stored, independent of any run, and
|
|
19
|
+
* readers can resume from a cursor.
|
|
20
|
+
*/
|
|
21
|
+
export declare class StreamsClient {
|
|
22
|
+
private _config;
|
|
23
|
+
private _grpc;
|
|
24
|
+
private producers;
|
|
25
|
+
private publishChains;
|
|
26
|
+
constructor(client: HatchetClient);
|
|
27
|
+
private get grpc();
|
|
28
|
+
private publishOrdered;
|
|
29
|
+
/**
|
|
30
|
+
* Resolves once the message is stored. A client's messages to a topic are
|
|
31
|
+
* delivered in the order publish() was called.
|
|
32
|
+
* @param topic - the topic to publish to
|
|
33
|
+
* @param message - the message payload
|
|
34
|
+
* @param options - optional namespace override
|
|
35
|
+
*/
|
|
36
|
+
publish(topic: string, message: Uint8Array | string, options?: StreamCallOptions): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Yields topic's messages after options.cursor, or from the oldest retained.
|
|
39
|
+
* If the connection drops, it resubscribes from the last message yielded, so
|
|
40
|
+
* nothing is skipped or repeated. Ends when the server hangs up an idle
|
|
41
|
+
* subscription, options.signal aborts, or the caller stops iterating; throws
|
|
42
|
+
* on errors a retry can't fix, such as an expired cursor.
|
|
43
|
+
* @param topic - the topic to read from
|
|
44
|
+
* @param options - optional namespace override, resume cursor, and abort signal
|
|
45
|
+
*/
|
|
46
|
+
events(topic: string, options?: StreamCallOptions): AsyncIterable<StreamEvent>;
|
|
47
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
12
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
13
|
+
var m = o[Symbol.asyncIterator], i;
|
|
14
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
15
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
16
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
17
|
+
};
|
|
18
|
+
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
|
|
19
|
+
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
|
20
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
21
|
+
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
|
22
|
+
return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
|
|
23
|
+
function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
|
|
24
|
+
function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
|
|
25
|
+
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
|
26
|
+
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
|
27
|
+
function fulfill(value) { resume("next", value); }
|
|
28
|
+
function reject(value) { resume("throw", value); }
|
|
29
|
+
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
|
30
|
+
};
|
|
31
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
32
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
33
|
+
};
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
exports.StreamsClient = void 0;
|
|
36
|
+
const crypto_1 = require("crypto");
|
|
37
|
+
const nice_grpc_1 = require("nice-grpc");
|
|
38
|
+
const grpc_helpers_1 = require("../../../util/grpc-helpers");
|
|
39
|
+
const grpc_error_1 = require("../../../util/grpc-error");
|
|
40
|
+
const sleep_1 = __importDefault(require("../../../util/sleep"));
|
|
41
|
+
const streams_1 = require("../../../protoc/v1/streams");
|
|
42
|
+
// the server stored nothing, so the seq is unused
|
|
43
|
+
function publishRejectedBeforeStoring(err) {
|
|
44
|
+
const code = (0, grpc_error_1.getGrpcErrorCode)(err);
|
|
45
|
+
return (code === nice_grpc_1.Status.INVALID_ARGUMENT ||
|
|
46
|
+
code === nice_grpc_1.Status.RESOURCE_EXHAUSTED ||
|
|
47
|
+
code === nice_grpc_1.Status.UNAUTHENTICATED ||
|
|
48
|
+
code === nice_grpc_1.Status.PERMISSION_DENIED);
|
|
49
|
+
}
|
|
50
|
+
// retrying a subscribe that failed with these can't succeed
|
|
51
|
+
const permanentSubscribeErrors = new Set([
|
|
52
|
+
nice_grpc_1.Status.INVALID_ARGUMENT,
|
|
53
|
+
nice_grpc_1.Status.PERMISSION_DENIED,
|
|
54
|
+
nice_grpc_1.Status.UNAUTHENTICATED,
|
|
55
|
+
nice_grpc_1.Status.OUT_OF_RANGE,
|
|
56
|
+
nice_grpc_1.Status.FAILED_PRECONDITION,
|
|
57
|
+
nice_grpc_1.Status.NOT_FOUND,
|
|
58
|
+
]);
|
|
59
|
+
const resubscribeMinDelayMs = 250;
|
|
60
|
+
const resubscribeMaxDelayMs = 10000;
|
|
61
|
+
function shouldResubscribe(err, attempt) {
|
|
62
|
+
const code = (0, grpc_error_1.getGrpcErrorCode)(err);
|
|
63
|
+
if (code === undefined || permanentSubscribeErrors.has(code)) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
// proxies answer 404 while an engine restarts, but on a first attempt it means no streams support
|
|
67
|
+
if (code === nice_grpc_1.Status.UNIMPLEMENTED) {
|
|
68
|
+
return attempt > 0;
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* StreamsClient publishes to and reads from durable topics. Unlike
|
|
74
|
+
* `runs.subscribeToStream`, topics are stored, independent of any run, and
|
|
75
|
+
* readers can resume from a cursor.
|
|
76
|
+
*/
|
|
77
|
+
class StreamsClient {
|
|
78
|
+
constructor(client) {
|
|
79
|
+
// per (namespace, topic); seq only advances after a publish succeeds
|
|
80
|
+
this.producers = new Map();
|
|
81
|
+
// serializes publishes per (namespace, topic) so two never share a seq; the
|
|
82
|
+
// stored promise always resolves, so one failure doesn't block later calls
|
|
83
|
+
this.publishChains = new Map();
|
|
84
|
+
this._config = client.config;
|
|
85
|
+
}
|
|
86
|
+
get grpc() {
|
|
87
|
+
if (!this._grpc) {
|
|
88
|
+
const { client } = (0, grpc_helpers_1.createGrpcClient)(this._config, streams_1.V1StreamsDefinition);
|
|
89
|
+
this._grpc = client;
|
|
90
|
+
}
|
|
91
|
+
return this._grpc;
|
|
92
|
+
}
|
|
93
|
+
publishOrdered(key_1, namespace_1, topic_1, payload_1) {
|
|
94
|
+
return __awaiter(this, arguments, void 0, function* (key, namespace, topic, payload, retryGap = true) {
|
|
95
|
+
let producer = this.producers.get(key);
|
|
96
|
+
if (!producer) {
|
|
97
|
+
producer = { producerId: (0, crypto_1.randomUUID)(), seq: 0 };
|
|
98
|
+
this.producers.set(key, producer);
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
yield this.grpc.publish({
|
|
102
|
+
namespace,
|
|
103
|
+
topic,
|
|
104
|
+
payload,
|
|
105
|
+
producerId: producer.producerId,
|
|
106
|
+
producerSeq: producer.seq,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
// it may still land, and a different payload under its seq would be dropped as a duplicate
|
|
111
|
+
if (!publishRejectedBeforeStoring(err)) {
|
|
112
|
+
this.producers.set(key, { producerId: (0, crypto_1.randomUUID)(), seq: 0 });
|
|
113
|
+
}
|
|
114
|
+
// a gap stored nothing (e.g. the watermark passed cursor retention), so resend as the new producer
|
|
115
|
+
if (retryGap && (0, grpc_error_1.getGrpcErrorCode)(err) === nice_grpc_1.Status.FAILED_PRECONDITION) {
|
|
116
|
+
return this.publishOrdered(key, namespace, topic, payload, false);
|
|
117
|
+
}
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
120
|
+
producer.seq += 1;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Resolves once the message is stored. A client's messages to a topic are
|
|
125
|
+
* delivered in the order publish() was called.
|
|
126
|
+
* @param topic - the topic to publish to
|
|
127
|
+
* @param message - the message payload
|
|
128
|
+
* @param options - optional namespace override
|
|
129
|
+
*/
|
|
130
|
+
publish(topic, message, options) {
|
|
131
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
132
|
+
var _a, _b;
|
|
133
|
+
const payload = typeof message === 'string' ? new TextEncoder().encode(message) : message;
|
|
134
|
+
const namespace = (_a = options === null || options === void 0 ? void 0 : options.namespace) !== null && _a !== void 0 ? _a : '';
|
|
135
|
+
const key = `${namespace}\u0000${topic}`;
|
|
136
|
+
const previous = (_b = this.publishChains.get(key)) !== null && _b !== void 0 ? _b : Promise.resolve();
|
|
137
|
+
const publishPromise = previous.then(() => this.publishOrdered(key, namespace, topic, payload));
|
|
138
|
+
this.publishChains.set(key, publishPromise.catch(() => { }));
|
|
139
|
+
return publishPromise;
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Yields topic's messages after options.cursor, or from the oldest retained.
|
|
144
|
+
* If the connection drops, it resubscribes from the last message yielded, so
|
|
145
|
+
* nothing is skipped or repeated. Ends when the server hangs up an idle
|
|
146
|
+
* subscription, options.signal aborts, or the caller stops iterating; throws
|
|
147
|
+
* on errors a retry can't fix, such as an expired cursor.
|
|
148
|
+
* @param topic - the topic to read from
|
|
149
|
+
* @param options - optional namespace override, resume cursor, and abort signal
|
|
150
|
+
*/
|
|
151
|
+
events(topic, options) {
|
|
152
|
+
return __asyncGenerator(this, arguments, function* events_1() {
|
|
153
|
+
var _a, e_1, _b, _c;
|
|
154
|
+
var _d;
|
|
155
|
+
const namespace = (_d = options === null || options === void 0 ? void 0 : options.namespace) !== null && _d !== void 0 ? _d : '';
|
|
156
|
+
const signal = options === null || options === void 0 ? void 0 : options.signal;
|
|
157
|
+
let { cursor } = options !== null && options !== void 0 ? options : {};
|
|
158
|
+
let failures = 0;
|
|
159
|
+
for (let attempt = 0;; attempt += 1) {
|
|
160
|
+
try {
|
|
161
|
+
const stream = this.grpc.subscribe({ namespace, topic, cursor }, { signal });
|
|
162
|
+
try {
|
|
163
|
+
for (var _e = true, stream_1 = (e_1 = void 0, __asyncValues(stream)), stream_1_1; stream_1_1 = yield __await(stream_1.next()), _a = stream_1_1.done, !_a; _e = true) {
|
|
164
|
+
_c = stream_1_1.value;
|
|
165
|
+
_e = false;
|
|
166
|
+
const msg = _c;
|
|
167
|
+
if (msg.hangup) {
|
|
168
|
+
return yield __await(void 0);
|
|
169
|
+
}
|
|
170
|
+
failures = 0;
|
|
171
|
+
for (const entry of msg.entries) {
|
|
172
|
+
({ cursor } = entry);
|
|
173
|
+
yield yield __await({
|
|
174
|
+
payload: entry.payload,
|
|
175
|
+
cursor: entry.cursor,
|
|
176
|
+
createdAt: entry.createdAt,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
182
|
+
finally {
|
|
183
|
+
try {
|
|
184
|
+
if (!_e && !_a && (_b = stream_1.return)) yield __await(_b.call(stream_1));
|
|
185
|
+
}
|
|
186
|
+
finally { if (e_1) throw e_1.error; }
|
|
187
|
+
}
|
|
188
|
+
// ending without a hangup means the engine went away, e.g. it is shutting down
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
192
|
+
return yield __await(void 0);
|
|
193
|
+
}
|
|
194
|
+
if (!shouldResubscribe(err, attempt)) {
|
|
195
|
+
throw err;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const delayMs = Math.min(resubscribeMinDelayMs * 2 ** failures, resubscribeMaxDelayMs);
|
|
199
|
+
failures += 1;
|
|
200
|
+
try {
|
|
201
|
+
yield __await((0, sleep_1.default)(delayMs * (0.5 + Math.random() / 2), signal));
|
|
202
|
+
}
|
|
203
|
+
catch (_f) {
|
|
204
|
+
return yield __await(void 0);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
exports.StreamsClient = StreamsClient;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { HatchetClient } from '../..';
|
|
2
|
+
export type PublishLatencyStats = {
|
|
3
|
+
count: number;
|
|
4
|
+
avgMs: number;
|
|
5
|
+
p50Ms: number;
|
|
6
|
+
p99Ms: number;
|
|
7
|
+
maxMs: number;
|
|
8
|
+
throughputPerSec: number;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Times `count` sequential publishes, after `warmupCount` unmeasured ones
|
|
12
|
+
* absorb connection setup and topic creation.
|
|
13
|
+
*/
|
|
14
|
+
export declare function measurePublishLatency(hatchet: HatchetClient, topic: string, payload: Uint8Array | string, count: number, warmupCount?: number): Promise<PublishLatencyStats>;
|
|
15
|
+
export declare function formatPublishLatency(stats: PublishLatencyStats): string;
|