@hatchet-dev/typescript-sdk 1.31.1 → 1.33.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 +46 -0
- package/clients/dispatcher/action-listener.d.ts +3 -8
- package/clients/dispatcher/action-listener.js +7 -25
- package/clients/dispatcher/action.d.ts +19 -0
- package/clients/dispatcher/action.js +35 -0
- package/clients/dispatcher/dispatcher-client.d.ts +2 -2
- package/clients/dispatcher/heartbeat/heartbeat-controller.d.ts +1 -1
- package/clients/listeners/durable-listener/durable-events.d.ts +56 -0
- package/clients/listeners/durable-listener/durable-events.js +2 -0
- package/clients/listeners/durable-listener/durable-listener-client.d.ts +3 -52
- package/clients/listeners/durable-listener/durable-listener-client.js +3 -2
- package/clients/listeners/durable-listener/pooled-durable-listener-client.js +2 -1
- package/clients/listeners/run-listener/pooled-child-listener-client.js +2 -1
- package/clients/rest/generated/Api.d.ts +1 -62
- package/clients/rest/generated/Api.js +0 -50
- package/clients/rest/generated/data-contracts.d.ts +0 -52
- package/dist/check-edge-entry.mjs +84 -0
- package/edge/declarations.d.ts +49 -0
- package/edge/declarations.js +21 -0
- package/edge/index.d.ts +49 -0
- package/edge/index.js +115 -0
- package/package.json +7 -2
- package/scripts/check-edge-entry.mjs +84 -0
- package/util/abort-error.d.ts +0 -10
- package/util/abort-error.js +0 -15
- package/util/abort-signal.d.ts +12 -0
- package/util/abort-signal.js +19 -0
- package/util/logger/logger.d.ts +1 -1
- package/v1/client/worker/context.d.ts +58 -12
- package/v1/client/worker/context.js +79 -51
- package/v1/client/worker/deprecated/pre-eviction.d.ts +5 -2
- package/v1/client/worker/deprecated/pre-eviction.js +5 -0
- package/v1/client/worker/health-server.d.ts +2 -0
- package/v1/client/worker/health-server.js +28 -0
- package/v1/client/worker/runtime.d.ts +118 -0
- package/v1/client/worker/runtime.js +9 -0
- package/v1/client/worker/worker-internal.d.ts +3 -39
- package/v1/client/worker/worker-internal.js +22 -393
- package/v1/client/worker/worker-runtime.d.ts +8 -0
- package/v1/client/worker/worker-runtime.js +67 -0
- package/v1/client/worker/workflow-proto.d.ts +76 -0
- package/v1/client/worker/workflow-proto.js +482 -0
- package/v1/parent-run-context-storage.d.ts +11 -0
- package/v1/parent-run-context-storage.js +28 -0
- package/v1/parent-run-context-vars.d.ts +15 -1
- package/v1/parent-run-context-vars.js +13 -4
- package/version.d.ts +1 -1
- package/version.js +1 -1
|
@@ -8,20 +8,20 @@
|
|
|
8
8
|
* @module Context
|
|
9
9
|
*/
|
|
10
10
|
import { Priority, RunOpts, TaskWorkflowDeclaration, BaseWorkflowDeclaration as WorkflowV1 } from '../../declaration';
|
|
11
|
-
import type { Action } from '../../../clients/dispatcher/action
|
|
11
|
+
import type { Action } from '../../../clients/dispatcher/action';
|
|
12
12
|
import { Logger, LogLevel } from '../../../util/logger';
|
|
13
13
|
import WorkflowRunRef from '../../../util/workflow-run-ref';
|
|
14
14
|
import { Conditions } from '../../conditions';
|
|
15
15
|
import { CreateWorkflowDurableTaskOpts, CreateWorkflowTaskOpts } from '../../task';
|
|
16
16
|
import { JsonObject, OutputType } from '../../types';
|
|
17
|
-
import { HatchetClient } from '
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import { DurableListenerClient } from '../../../clients/listeners/durable-listener/durable-listener-client';
|
|
17
|
+
import type { HatchetClient } from '../client';
|
|
18
|
+
import type { NextStep } from '../../../legacy/step';
|
|
19
|
+
import type { DurableListenerClient } from '../../../clients/listeners/durable-listener/durable-listener-client';
|
|
21
20
|
import { z } from 'zod/v4';
|
|
22
|
-
import { InternalWorker } from './worker-internal';
|
|
21
|
+
import type { InternalWorker } from './worker-internal';
|
|
23
22
|
import { Duration } from '../duration';
|
|
24
|
-
import { DurableEvictionManager } from './eviction/eviction-manager';
|
|
23
|
+
import type { DurableEvictionManager } from './eviction/eviction-manager';
|
|
24
|
+
import { ContextRuntime, DurableContextOptions, DurableTransport, WorkerLabels } from './runtime';
|
|
25
25
|
type TriggerData = Record<string, Record<string, any>>;
|
|
26
26
|
type ChildRunOpts = RunOpts & {
|
|
27
27
|
key?: string;
|
|
@@ -50,11 +50,11 @@ interface ContextData<T, K> {
|
|
|
50
50
|
step_run_errors: Record<string, string>;
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
|
-
* ContextWorker is a
|
|
53
|
+
* ContextWorker is a user-friendly view of the worker running the task, from the context of a run.
|
|
54
54
|
*/
|
|
55
55
|
export declare class ContextWorker {
|
|
56
|
-
private
|
|
57
|
-
constructor(
|
|
56
|
+
private runtime;
|
|
57
|
+
constructor(runtime: ContextRuntime);
|
|
58
58
|
/**
|
|
59
59
|
* Gets the ID of the worker.
|
|
60
60
|
* @returns The ID of the worker.
|
|
@@ -83,7 +83,14 @@ export declare class Context<T, K = {}> {
|
|
|
83
83
|
input: T;
|
|
84
84
|
controller: AbortController;
|
|
85
85
|
action: Action;
|
|
86
|
+
/**
|
|
87
|
+
* The Hatchet client of the worker running the task. Only set when the context was
|
|
88
|
+
* created by a worker; a runtime without a client (see {@link ContextRuntime}) leaves
|
|
89
|
+
* it undefined.
|
|
90
|
+
*/
|
|
86
91
|
v1: HatchetClient;
|
|
92
|
+
/** The runtime the context performs engine-facing operations through. */
|
|
93
|
+
runtime: ContextRuntime;
|
|
87
94
|
worker: ContextWorker;
|
|
88
95
|
overridesData: Record<string, any>;
|
|
89
96
|
_logger: Logger;
|
|
@@ -91,6 +98,18 @@ export declare class Context<T, K = {}> {
|
|
|
91
98
|
spawnIndex: number;
|
|
92
99
|
streamIndex: number;
|
|
93
100
|
protected nextChildIndex(n?: number): number;
|
|
101
|
+
/**
|
|
102
|
+
* Creates a context on top of a runtime.
|
|
103
|
+
* @param action - The action assigned to the task.
|
|
104
|
+
* @param runtime - The runtime the context performs engine-facing operations through.
|
|
105
|
+
*/
|
|
106
|
+
constructor(action: Action, runtime: ContextRuntime);
|
|
107
|
+
/**
|
|
108
|
+
* Creates a context for a task running on a worker.
|
|
109
|
+
* @param action - The action assigned to the task.
|
|
110
|
+
* @param v1 - The worker's Hatchet client.
|
|
111
|
+
* @param worker - The worker running the task.
|
|
112
|
+
*/
|
|
94
113
|
constructor(action: Action, v1: HatchetClient, worker: InternalWorker);
|
|
95
114
|
get abortController(): AbortController;
|
|
96
115
|
get cancelled(): boolean;
|
|
@@ -247,7 +266,7 @@ export declare class Context<T, K = {}> {
|
|
|
247
266
|
value: string | number;
|
|
248
267
|
required?: boolean;
|
|
249
268
|
weight?: number;
|
|
250
|
-
comparator?: import("
|
|
269
|
+
comparator?: import("../../task").WorkerLabelComparator;
|
|
251
270
|
}>;
|
|
252
271
|
key?: string;
|
|
253
272
|
};
|
|
@@ -383,9 +402,29 @@ export declare class DurableContext<T, K = {}> extends Context<T, K> {
|
|
|
383
402
|
private _waitKey;
|
|
384
403
|
private _sendEventLock;
|
|
385
404
|
private _serializeSendEvent;
|
|
405
|
+
/**
|
|
406
|
+
* Creates a durable context on top of a runtime and a durable transport.
|
|
407
|
+
* @param action - The action assigned to the task.
|
|
408
|
+
* @param runtime - The runtime the context performs engine-facing operations through.
|
|
409
|
+
* @param transport - The transport durable events travel over.
|
|
410
|
+
* @param options - Engine version and eviction settings.
|
|
411
|
+
*/
|
|
412
|
+
constructor(action: Action, runtime: ContextRuntime, transport: DurableTransport, options?: DurableContextOptions & {
|
|
413
|
+
evictionManager?: DurableEvictionManager;
|
|
414
|
+
});
|
|
415
|
+
/**
|
|
416
|
+
* Creates a durable context for a task running on a worker.
|
|
417
|
+
* @param action - The action assigned to the task.
|
|
418
|
+
* @param v1 - The worker's Hatchet client.
|
|
419
|
+
* @param worker - The worker running the task.
|
|
420
|
+
* @param durableListener - The worker's durable listener.
|
|
421
|
+
* @param evictionManager - The worker's eviction manager, when the engine supports eviction.
|
|
422
|
+
* @param engineVersion - The engine version the worker is connected to.
|
|
423
|
+
*/
|
|
386
424
|
constructor(action: Action, v1: HatchetClient, worker: InternalWorker, durableListener: DurableListenerClient, evictionManager?: DurableEvictionManager, engineVersion?: string);
|
|
387
425
|
get supportsEviction(): boolean;
|
|
388
|
-
|
|
426
|
+
/** The transport durable events travel over. */
|
|
427
|
+
get durableListener(): DurableTransport;
|
|
389
428
|
/**
|
|
390
429
|
* The invocation count for the current durable task. Used for deduplication across replays.
|
|
391
430
|
*/
|
|
@@ -465,4 +504,11 @@ export declare class DurableContext<T, K = {}> extends Context<T, K> {
|
|
|
465
504
|
*/
|
|
466
505
|
private memo;
|
|
467
506
|
}
|
|
507
|
+
/**
|
|
508
|
+
* Derives the memo key for a task run and its dependency values: the SHA-256 of the task
|
|
509
|
+
* run id followed by the JSON-serialised dependencies, over WebCrypto so it works in
|
|
510
|
+
* every runtime. The bytes are identical to the previous Node `createHash` version, so
|
|
511
|
+
* event logs recorded by older SDKs keep replaying.
|
|
512
|
+
*/
|
|
513
|
+
export declare function computeMemoKey(taskRunExternalId: string, args: readonly unknown[]): Promise<Uint8Array>;
|
|
468
514
|
export {};
|
|
@@ -22,34 +22,35 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
22
22
|
};
|
|
23
23
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
24
|
exports.DurableContext = exports.Context = exports.ContextWorker = void 0;
|
|
25
|
+
exports.computeMemoKey = computeMemoKey;
|
|
25
26
|
const declaration_1 = require("../../declaration");
|
|
26
27
|
const hatchet_error_1 = __importDefault(require("../../../util/errors/hatchet-error"));
|
|
27
|
-
const
|
|
28
|
+
const action_1 = require("../../../clients/dispatcher/action");
|
|
28
29
|
const parse_1 = require("../../../util/parse");
|
|
29
30
|
const conditions_1 = require("../../conditions");
|
|
30
31
|
const transformer_1 = require("../../conditions/transformer");
|
|
31
32
|
const condition_1 = require("../../../protoc/v1/shared/condition");
|
|
32
|
-
const dispatcher_1 = require("../../../protoc/dispatcher");
|
|
33
33
|
const apply_namespace_1 = require("../../../util/apply-namespace");
|
|
34
34
|
const abort_error_1 = require("../../../util/abort-error");
|
|
35
35
|
const parent_run_context_vars_1 = require("../../parent-run-context-vars");
|
|
36
|
-
const crypto_1 = require("crypto");
|
|
37
36
|
const duration_1 = require("../duration");
|
|
38
37
|
const engine_version_1 = require("./engine-version");
|
|
39
38
|
const pre_eviction_1 = require("./deprecated/pre-eviction");
|
|
39
|
+
const runtime_1 = require("./runtime");
|
|
40
|
+
const worker_runtime_1 = require("./worker-runtime");
|
|
40
41
|
/**
|
|
41
|
-
* ContextWorker is a
|
|
42
|
+
* ContextWorker is a user-friendly view of the worker running the task, from the context of a run.
|
|
42
43
|
*/
|
|
43
44
|
class ContextWorker {
|
|
44
|
-
constructor(
|
|
45
|
-
this.
|
|
45
|
+
constructor(runtime) {
|
|
46
|
+
this.runtime = runtime;
|
|
46
47
|
}
|
|
47
48
|
/**
|
|
48
49
|
* Gets the ID of the worker.
|
|
49
50
|
* @returns The ID of the worker.
|
|
50
51
|
*/
|
|
51
52
|
id() {
|
|
52
|
-
return this.
|
|
53
|
+
return this.runtime.workerId();
|
|
53
54
|
}
|
|
54
55
|
/**
|
|
55
56
|
* Checks if the worker has a registered workflow.
|
|
@@ -57,14 +58,14 @@ class ContextWorker {
|
|
|
57
58
|
* @returns True if the workflow is registered, otherwise false.
|
|
58
59
|
*/
|
|
59
60
|
hasWorkflow(workflowName) {
|
|
60
|
-
return
|
|
61
|
+
return this.runtime.hasWorkflow(workflowName);
|
|
61
62
|
}
|
|
62
63
|
/**
|
|
63
64
|
* Gets the current state of the worker labels.
|
|
64
65
|
* @returns The labels of the worker.
|
|
65
66
|
*/
|
|
66
67
|
labels() {
|
|
67
|
-
return this.
|
|
68
|
+
return this.runtime.workerLabels();
|
|
68
69
|
}
|
|
69
70
|
/**
|
|
70
71
|
* Upserts the a set of labels on the worker.
|
|
@@ -72,10 +73,16 @@ class ContextWorker {
|
|
|
72
73
|
* @returns A promise that resolves when the labels have been upserted.
|
|
73
74
|
*/
|
|
74
75
|
upsertLabels(labels) {
|
|
75
|
-
return this.
|
|
76
|
+
return this.runtime.upsertWorkerLabels(labels);
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
exports.ContextWorker = ContextWorker;
|
|
80
|
+
function resolveRuntime(runtimeOrClient, worker) {
|
|
81
|
+
if ((0, runtime_1.isContextRuntime)(runtimeOrClient)) {
|
|
82
|
+
return runtimeOrClient;
|
|
83
|
+
}
|
|
84
|
+
return (0, worker_runtime_1.createWorkerContextRuntime)(runtimeOrClient, worker);
|
|
85
|
+
}
|
|
79
86
|
class Context {
|
|
80
87
|
nextChildIndex(n = 1) {
|
|
81
88
|
var _a;
|
|
@@ -85,7 +92,7 @@ class Context {
|
|
|
85
92
|
this.spawnIndex = idx + n;
|
|
86
93
|
return idx;
|
|
87
94
|
}
|
|
88
|
-
constructor(action,
|
|
95
|
+
constructor(action, runtimeOrClient, worker) {
|
|
89
96
|
// @deprecated use ctx.abortController instead
|
|
90
97
|
this.controller = new AbortController();
|
|
91
98
|
this.overridesData = {};
|
|
@@ -93,12 +100,14 @@ class Context {
|
|
|
93
100
|
this.spawnIndex = 0;
|
|
94
101
|
this.streamIndex = 0;
|
|
95
102
|
try {
|
|
103
|
+
const runtime = resolveRuntime(runtimeOrClient, worker);
|
|
96
104
|
const data = (0, parse_1.parseJSON)(action.actionPayload);
|
|
97
105
|
this.data = data;
|
|
98
106
|
this.action = action;
|
|
99
|
-
this.
|
|
100
|
-
this.
|
|
101
|
-
this.
|
|
107
|
+
this.runtime = runtime;
|
|
108
|
+
this.v1 = runtime.client;
|
|
109
|
+
this.worker = new ContextWorker(runtime);
|
|
110
|
+
this._logger = runtime.logger(`Context Logger`);
|
|
102
111
|
// if this is a getGroupKeyRunId, the data is the workflow input
|
|
103
112
|
if (action.getGroupKeyRunId !== '') {
|
|
104
113
|
this.input = data;
|
|
@@ -143,20 +152,16 @@ class Context {
|
|
|
143
152
|
// the raw batch-items map for a START_BATCH action, keyed by each member's
|
|
144
153
|
// task-run external id.
|
|
145
154
|
const memberIds = Object.keys((_a = this.data) !== null && _a !== void 0 ? _a : {});
|
|
146
|
-
yield this.
|
|
155
|
+
yield this.runtime.cancelBatch({
|
|
147
156
|
workerId: (_b = this.worker.id()) !== null && _b !== void 0 ? _b : '',
|
|
148
157
|
jobId: this.action.jobId,
|
|
149
158
|
actionId: this.action.actionId,
|
|
150
159
|
batchId: this.action.batchId,
|
|
151
|
-
|
|
152
|
-
eventType: dispatcher_1.StepActionEventType.STEP_EVENT_TYPE_CANCELLED,
|
|
153
|
-
items: memberIds.map((id) => ({ taskRunExternalId: id, eventPayload: '' })),
|
|
160
|
+
memberIds,
|
|
154
161
|
});
|
|
155
162
|
}
|
|
156
163
|
else {
|
|
157
|
-
yield this.
|
|
158
|
-
ids: [this.action.taskRunExternalId],
|
|
159
|
-
});
|
|
164
|
+
yield this.runtime.cancelRun(this.action.taskRunExternalId);
|
|
160
165
|
}
|
|
161
166
|
// optimistically abort the run
|
|
162
167
|
this.controller.abort();
|
|
@@ -242,7 +247,7 @@ class Context {
|
|
|
242
247
|
* @returns The name of the workflow.
|
|
243
248
|
*/
|
|
244
249
|
workflowNameV1() {
|
|
245
|
-
return (0,
|
|
250
|
+
return (0, action_1.workflowNameFromAction)(this.action);
|
|
246
251
|
}
|
|
247
252
|
/**
|
|
248
253
|
* Gets the user data associated with the workflow.
|
|
@@ -317,7 +322,7 @@ class Context {
|
|
|
317
322
|
this._logger.warn('cannot log from context without stepRunId');
|
|
318
323
|
return Promise.resolve();
|
|
319
324
|
}
|
|
320
|
-
const logger = this.
|
|
325
|
+
const logger = this.runtime.logger('ctx');
|
|
321
326
|
const contextExtra = Object.assign({ workflowRunId: this.action.workflowRunId, taskRunExternalId: this.action.taskRunExternalId, retryCount: this.action.retryCount, workflowName: this.workflowNameV1() }, extra === null || extra === void 0 ? void 0 : extra.extra);
|
|
322
327
|
const promises = [];
|
|
323
328
|
if (!level || level === 'INFO') {
|
|
@@ -333,7 +338,7 @@ class Context {
|
|
|
333
338
|
promises.push(logger.error(message, extra === null || extra === void 0 ? void 0 : extra.error, contextExtra));
|
|
334
339
|
}
|
|
335
340
|
// FIXME: this is a hack to get around the fact that the log level is not typed
|
|
336
|
-
promises.push(this.
|
|
341
|
+
promises.push(this.runtime.putLog(taskRunExternalId, message, level, this.retryCount(), extra === null || extra === void 0 ? void 0 : extra.extra));
|
|
337
342
|
return Promise.all(promises);
|
|
338
343
|
}
|
|
339
344
|
get logger() {
|
|
@@ -351,7 +356,7 @@ class Context {
|
|
|
351
356
|
return this.log(message, 'ERROR', extra);
|
|
352
357
|
},
|
|
353
358
|
util: (key, message, extra) => {
|
|
354
|
-
const logger = this.
|
|
359
|
+
const logger = this.runtime.logger('ctx');
|
|
355
360
|
if (!logger.util) {
|
|
356
361
|
return Promise.resolve();
|
|
357
362
|
}
|
|
@@ -372,7 +377,7 @@ class Context {
|
|
|
372
377
|
this._logger.warn('cannot refresh timeout from context without stepRunId');
|
|
373
378
|
return;
|
|
374
379
|
}
|
|
375
|
-
yield this.
|
|
380
|
+
yield this.runtime.refreshTimeout(taskRunExternalId, (0, duration_1.durationToString)(incrementBy));
|
|
376
381
|
});
|
|
377
382
|
}
|
|
378
383
|
/**
|
|
@@ -382,9 +387,7 @@ class Context {
|
|
|
382
387
|
*/
|
|
383
388
|
releaseSlot() {
|
|
384
389
|
return __awaiter(this, void 0, void 0, function* () {
|
|
385
|
-
yield this.
|
|
386
|
-
taskRunExternalId: this.action.taskRunExternalId,
|
|
387
|
-
});
|
|
390
|
+
yield this.runtime.releaseSlot(this.action.taskRunExternalId);
|
|
388
391
|
});
|
|
389
392
|
}
|
|
390
393
|
/**
|
|
@@ -401,7 +404,7 @@ class Context {
|
|
|
401
404
|
return;
|
|
402
405
|
}
|
|
403
406
|
const index = this._incrementStreamIndex();
|
|
404
|
-
yield this.
|
|
407
|
+
yield this.runtime.putStream(taskRunExternalId, data, index);
|
|
405
408
|
});
|
|
406
409
|
}
|
|
407
410
|
spawnOptions(workflow, options) {
|
|
@@ -425,7 +428,7 @@ class Context {
|
|
|
425
428
|
}
|
|
426
429
|
spawn(workflow, input, options) {
|
|
427
430
|
const { workflowName, opts } = this.spawnOptions(workflow, options);
|
|
428
|
-
return this.
|
|
431
|
+
return this.runtime.runWorkflow(workflowName, input, opts);
|
|
429
432
|
}
|
|
430
433
|
spawnBulk(children) {
|
|
431
434
|
this.throwIfCancelled();
|
|
@@ -433,7 +436,7 @@ class Context {
|
|
|
433
436
|
const { workflowName, opts } = this.spawnOptions(child.workflow, child.options);
|
|
434
437
|
return { workflowName, input: child.input, options: opts };
|
|
435
438
|
});
|
|
436
|
-
return this.
|
|
439
|
+
return this.runtime.runWorkflows(workflows);
|
|
437
440
|
}
|
|
438
441
|
/**
|
|
439
442
|
* Runs multiple children workflows in parallel without waiting for their results.
|
|
@@ -606,7 +609,7 @@ class Context {
|
|
|
606
609
|
else {
|
|
607
610
|
workflowName = workflow.name;
|
|
608
611
|
}
|
|
609
|
-
const name = (0, apply_namespace_1.applyNamespace)(workflowName, this.
|
|
612
|
+
const name = (0, apply_namespace_1.applyNamespace)(workflowName, this.runtime.namespace).toLowerCase();
|
|
610
613
|
const opts = options || {};
|
|
611
614
|
const { sticky } = opts;
|
|
612
615
|
if (sticky && !this.worker.hasWorkflow(name)) {
|
|
@@ -628,7 +631,7 @@ class Context {
|
|
|
628
631
|
let resp = [];
|
|
629
632
|
for (let i = 0; i < workflowRuns.length; i += batchSize) {
|
|
630
633
|
const batch = workflowRuns.slice(i, i + batchSize);
|
|
631
|
-
const batchResp = yield this.
|
|
634
|
+
const batchResp = yield this.runtime.runWorkflows(batch);
|
|
632
635
|
resp = resp.concat(batchResp);
|
|
633
636
|
}
|
|
634
637
|
const res = [];
|
|
@@ -661,7 +664,7 @@ class Context {
|
|
|
661
664
|
this.throwIfCancelled();
|
|
662
665
|
const { workflowRunId, taskRunExternalId } = this.action;
|
|
663
666
|
const workflowName = typeof workflow === 'string' ? workflow : workflow.name;
|
|
664
|
-
const name = (0, apply_namespace_1.applyNamespace)(workflowName, this.
|
|
667
|
+
const name = (0, apply_namespace_1.applyNamespace)(workflowName, this.runtime.namespace).toLowerCase();
|
|
665
668
|
const opts = options || {};
|
|
666
669
|
const { sticky } = opts;
|
|
667
670
|
if (sticky && !this.worker.hasWorkflow(name)) {
|
|
@@ -669,7 +672,7 @@ class Context {
|
|
|
669
672
|
}
|
|
670
673
|
try {
|
|
671
674
|
const childIndex = this.nextChildIndex();
|
|
672
|
-
const resp = yield this.
|
|
675
|
+
const resp = yield this.runtime.runWorkflow(name, input, Object.assign({ parentId: workflowRunId, parentTaskRunExternalId: taskRunExternalId, childIndex, desiredWorkerId: sticky ? this.worker.id() : undefined }, opts));
|
|
673
676
|
if (workflow instanceof declaration_1.TaskWorkflowDeclaration) {
|
|
674
677
|
resp._standaloneTaskName = workflow._standalone_task_name;
|
|
675
678
|
}
|
|
@@ -706,20 +709,31 @@ class DurableContext extends Context {
|
|
|
706
709
|
this._sendEventLock = result.then(() => undefined, () => undefined);
|
|
707
710
|
return result;
|
|
708
711
|
}
|
|
709
|
-
constructor(action,
|
|
710
|
-
|
|
712
|
+
constructor(action, runtimeOrClient, workerOrTransport, listenerOrOptions, evictionManager, engineVersion) {
|
|
713
|
+
// The base constructor tells a runtime apart from a client itself, so the worker
|
|
714
|
+
// argument is ignored on the runtime path.
|
|
715
|
+
super(action, runtimeOrClient, workerOrTransport);
|
|
711
716
|
this._waitKey = 0;
|
|
712
717
|
// Serializes sendEvent calls from concurrent coroutines in this invocation.
|
|
713
718
|
// The listener keys pending acks by (task, invocation), so overlapping sends
|
|
714
719
|
// would overwrite each other's ack and cross-wire branch/node assignments.
|
|
715
720
|
this._sendEventLock = Promise.resolve();
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
721
|
+
if ((0, runtime_1.isContextRuntime)(runtimeOrClient)) {
|
|
722
|
+
const options = listenerOrOptions;
|
|
723
|
+
this._durableListener = workerOrTransport;
|
|
724
|
+
this._evictionManager = options === null || options === void 0 ? void 0 : options.evictionManager;
|
|
725
|
+
this._engineVersion = options === null || options === void 0 ? void 0 : options.engineVersion;
|
|
726
|
+
}
|
|
727
|
+
else {
|
|
728
|
+
this._durableListener = listenerOrOptions;
|
|
729
|
+
this._evictionManager = evictionManager;
|
|
730
|
+
this._engineVersion = engineVersion;
|
|
731
|
+
}
|
|
719
732
|
}
|
|
720
733
|
get supportsEviction() {
|
|
721
734
|
return (0, engine_version_1.supportsEviction)(this._engineVersion);
|
|
722
735
|
}
|
|
736
|
+
/** The transport durable events travel over. */
|
|
723
737
|
get durableListener() {
|
|
724
738
|
return this._durableListener;
|
|
725
739
|
}
|
|
@@ -784,7 +798,7 @@ class DurableContext extends Context {
|
|
|
784
798
|
return this._waitForPreEviction(conditions);
|
|
785
799
|
}
|
|
786
800
|
const rendered = (0, conditions_1.Render)(condition_1.Action.CREATE, conditions);
|
|
787
|
-
const pbConditions = (0, transformer_1.conditionsToPb)(rendered, this.
|
|
801
|
+
const pbConditions = (0, transformer_1.conditionsToPb)(rendered, this.runtime.namespace);
|
|
788
802
|
const ack = yield this._serializeSendEvent(() => this._durableListener.sendEvent(this.action.taskRunExternalId, this.invocationCount, {
|
|
789
803
|
kind: 'waitFor',
|
|
790
804
|
waitForConditions: {
|
|
@@ -860,7 +874,10 @@ class DurableContext extends Context {
|
|
|
860
874
|
}
|
|
861
875
|
_waitForPreEviction(conditions) {
|
|
862
876
|
return __awaiter(this, void 0, void 0, function* () {
|
|
863
|
-
|
|
877
|
+
if (!(0, pre_eviction_1.isLegacyDurableTransport)(this._durableListener)) {
|
|
878
|
+
throw new hatchet_error_1.default(`Engine ${this._engineVersion || 'unknown'} does not support durable eviction and the durable transport has no legacy fallback. Upgrade the Hatchet engine.`);
|
|
879
|
+
}
|
|
880
|
+
const { result, nextWaitKey } = yield (0, pre_eviction_1.waitForPreEviction)(this._durableListener, this.action.taskRunExternalId, this._waitKey, conditions, this.runtime.namespace, this.abortController.signal);
|
|
864
881
|
this._waitKey = nextWaitKey;
|
|
865
882
|
return result;
|
|
866
883
|
});
|
|
@@ -873,7 +890,7 @@ class DurableContext extends Context {
|
|
|
873
890
|
else {
|
|
874
891
|
workflowName = workflow.name;
|
|
875
892
|
}
|
|
876
|
-
workflowName = (0, apply_namespace_1.applyNamespace)(workflowName, this.
|
|
893
|
+
workflowName = (0, apply_namespace_1.applyNamespace)(workflowName, this.runtime.namespace).toLowerCase();
|
|
877
894
|
const childIndex = this.nextChildIndex();
|
|
878
895
|
const triggerOpts = {
|
|
879
896
|
name: workflowName,
|
|
@@ -902,7 +919,7 @@ class DurableContext extends Context {
|
|
|
902
919
|
return __awaiter(this, void 0, void 0, function* () {
|
|
903
920
|
if (!this.supportsEviction) {
|
|
904
921
|
const { workflowName, opts } = this.spawnOptions(workflow, options);
|
|
905
|
-
const ref = yield this.
|
|
922
|
+
const ref = yield this.runtime.runWorkflow(workflowName, (input || {}), opts);
|
|
906
923
|
ref.defaultSignal = this.abortController.signal;
|
|
907
924
|
return ref.output;
|
|
908
925
|
}
|
|
@@ -925,7 +942,7 @@ class DurableContext extends Context {
|
|
|
925
942
|
const { workflowName, opts } = this.spawnOptions(c.workflow, c.options);
|
|
926
943
|
return { workflowName, input: c.input, options: opts };
|
|
927
944
|
});
|
|
928
|
-
const refs = yield this.
|
|
945
|
+
const refs = yield this.runtime.runWorkflows(workflows);
|
|
929
946
|
for (const r of refs) {
|
|
930
947
|
r.defaultSignal = this.abortController.signal;
|
|
931
948
|
}
|
|
@@ -962,7 +979,7 @@ class DurableContext extends Context {
|
|
|
962
979
|
if (!this.supportsEviction) {
|
|
963
980
|
return fn();
|
|
964
981
|
}
|
|
965
|
-
const memoKey = computeMemoKey(this.action.taskRunExternalId, deps);
|
|
982
|
+
const memoKey = yield computeMemoKey(this.action.taskRunExternalId, deps);
|
|
966
983
|
const ack = yield this._serializeSendEvent(() => this._durableListener.sendEvent(this.action.taskRunExternalId, this.invocationCount, {
|
|
967
984
|
kind: 'memo',
|
|
968
985
|
memoKey,
|
|
@@ -980,9 +997,20 @@ class DurableContext extends Context {
|
|
|
980
997
|
}
|
|
981
998
|
}
|
|
982
999
|
exports.DurableContext = DurableContext;
|
|
1000
|
+
/**
|
|
1001
|
+
* Derives the memo key for a task run and its dependency values: the SHA-256 of the task
|
|
1002
|
+
* run id followed by the JSON-serialised dependencies, over WebCrypto so it works in
|
|
1003
|
+
* every runtime. The bytes are identical to the previous Node `createHash` version, so
|
|
1004
|
+
* event logs recorded by older SDKs keep replaying.
|
|
1005
|
+
*/
|
|
983
1006
|
function computeMemoKey(taskRunExternalId, args) {
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1007
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1008
|
+
var _a;
|
|
1009
|
+
const { subtle } = (_a = globalThis.crypto) !== null && _a !== void 0 ? _a : {};
|
|
1010
|
+
if (!subtle) {
|
|
1011
|
+
throw new hatchet_error_1.default('WebCrypto is not available in this runtime. Durable tasks need globalThis.crypto.subtle (Node 20 or newer).');
|
|
1012
|
+
}
|
|
1013
|
+
const data = new TextEncoder().encode(taskRunExternalId + JSON.stringify(args));
|
|
1014
|
+
return new Uint8Array(yield subtle.digest('SHA-256', data));
|
|
1015
|
+
});
|
|
988
1016
|
}
|
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
* Remove this module when support for those engines is dropped.
|
|
6
6
|
*/
|
|
7
7
|
import { Conditions } from '../../../conditions';
|
|
8
|
-
import { DurableListenerClient } from '../../../../clients/listeners/durable-listener/durable-listener-client';
|
|
9
|
-
|
|
8
|
+
import type { DurableListenerClient } from '../../../../clients/listeners/durable-listener/durable-listener-client';
|
|
9
|
+
/** The unary and streaming RPCs the pre-eviction fallback needs from the listener. */
|
|
10
|
+
export type LegacyDurableTransport = Pick<DurableListenerClient, 'registerDurableEvent' | 'result'>;
|
|
11
|
+
export declare function isLegacyDurableTransport(value: unknown): value is LegacyDurableTransport;
|
|
12
|
+
export declare function waitForPreEviction(durableListener: LegacyDurableTransport, taskRunExternalId: string, waitKey: number, conditions: Conditions | Conditions[], namespace?: string, signal?: AbortSignal): Promise<{
|
|
10
13
|
result: Record<string, unknown>;
|
|
11
14
|
nextWaitKey: number;
|
|
12
15
|
}>;
|
|
@@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.isLegacyDurableTransport = isLegacyDurableTransport;
|
|
12
13
|
exports.waitForPreEviction = waitForPreEviction;
|
|
13
14
|
/**
|
|
14
15
|
* Pre-eviction fallback for DurableContext.
|
|
@@ -19,6 +20,10 @@ exports.waitForPreEviction = waitForPreEviction;
|
|
|
19
20
|
const conditions_1 = require("../../../conditions");
|
|
20
21
|
const transformer_1 = require("../../../conditions/transformer");
|
|
21
22
|
const condition_1 = require("../../../../protoc/v1/shared/condition");
|
|
23
|
+
function isLegacyDurableTransport(value) {
|
|
24
|
+
const candidate = value;
|
|
25
|
+
return (typeof (candidate === null || candidate === void 0 ? void 0 : candidate.registerDurableEvent) === 'function' && typeof (candidate === null || candidate === void 0 ? void 0 : candidate.result) === 'function');
|
|
26
|
+
}
|
|
22
27
|
function waitForPreEviction(durableListener, taskRunExternalId, waitKey, conditions, namespace, signal) {
|
|
23
28
|
return __awaiter(this, void 0, void 0, function* () {
|
|
24
29
|
const pbConditions = (0, transformer_1.conditionsToPb)((0, conditions_1.Render)(condition_1.Action.CREATE, conditions), namespace);
|
|
@@ -23,6 +23,8 @@ export declare class HealthServer {
|
|
|
23
23
|
constructor(port: number, getStatus: () => WorkerStatus, workerName: string, getSlots: () => number, getActions: () => string[], getLabels: () => Record<string, string | number>, logger: Logger);
|
|
24
24
|
private handleRequest;
|
|
25
25
|
private handleHealth;
|
|
26
|
+
private handleReadyz;
|
|
27
|
+
private handleLivez;
|
|
26
28
|
private initializeMetrics;
|
|
27
29
|
private handleMetrics;
|
|
28
30
|
start(): Promise<void>;
|
|
@@ -40,6 +40,12 @@ class HealthServer {
|
|
|
40
40
|
if (url === '/health' && req.method === 'GET') {
|
|
41
41
|
yield this.handleHealth(res);
|
|
42
42
|
}
|
|
43
|
+
else if (url === '/readyz' && req.method === 'GET') {
|
|
44
|
+
this.handleReadyz(res);
|
|
45
|
+
}
|
|
46
|
+
else if (url === '/livez' && req.method === 'GET') {
|
|
47
|
+
this.handleLivez(res);
|
|
48
|
+
}
|
|
43
49
|
else if (url === '/metrics' && req.method === 'GET') {
|
|
44
50
|
yield this.handleMetrics(res);
|
|
45
51
|
}
|
|
@@ -59,10 +65,32 @@ class HealthServer {
|
|
|
59
65
|
labels: this.getLabels(),
|
|
60
66
|
nodeVersion: process.version,
|
|
61
67
|
};
|
|
68
|
+
// Always return 200 for compatibility with consumers that inspect the JSON
|
|
69
|
+
// status. Use /readyz when the HTTP status must indicate readiness.
|
|
62
70
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
63
71
|
yield res.end(JSON.stringify(response));
|
|
64
72
|
});
|
|
65
73
|
}
|
|
74
|
+
// Kubernetes-style readiness probe: 200 only when the worker is HEALTHY
|
|
75
|
+
// (registered and holding an action listener), 503 otherwise. Unlike
|
|
76
|
+
// /health, this is a plain status-code contract a vanilla `httpGet` probe
|
|
77
|
+
// can consume directly, with no body to parse.
|
|
78
|
+
handleReadyz(res) {
|
|
79
|
+
const ready = this.getStatus() === exports.workerStatus.HEALTHY;
|
|
80
|
+
res.writeHead(ready ? 200 : 503, { 'Content-Type': 'text/plain' });
|
|
81
|
+
res.end(ready ? 'ok' : 'not ready');
|
|
82
|
+
}
|
|
83
|
+
// Kubernetes-style liveness probe: 200 whenever this handler can run at
|
|
84
|
+
// all, deliberately independent of Hatchet connectivity. A worker that has
|
|
85
|
+
// temporarily lost its action listener (UNHEALTHY) should be pulled from
|
|
86
|
+
// rotation via /readyz, not killed and restarted via /livez — restarting
|
|
87
|
+
// does nothing to fix an upstream Hatchet outage and drops in-flight work.
|
|
88
|
+
// /livez only needs to fail when the process itself is deadlocked/wedged,
|
|
89
|
+
// which this HTTP response reaching the caller already disproves.
|
|
90
|
+
handleLivez(res) {
|
|
91
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
92
|
+
res.end('ok');
|
|
93
|
+
}
|
|
66
94
|
initializeMetrics() {
|
|
67
95
|
try {
|
|
68
96
|
// THIS IS AN OPTIONAL DEPENDENCY
|