@hatchet-dev/typescript-sdk 1.34.0-alpha.2 → 1.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,7 +19,7 @@ import { BatchTaskFn } from '../task';
19
19
  import type { LegacyWorkflow } from '../../legacy/legacy-transformer';
20
20
  import { IHatchetClient } from './client.interface';
21
21
  import { CreateWorkerOpts, Worker } from './worker/worker';
22
- 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';
23
23
  import { InputType, OutputType, UnknownInputType, StrictWorkflowOutputType, Resolved } from '../types';
24
24
  import { AdminClient } from './admin';
25
25
  import { DurableContext } from './worker/context';
@@ -294,6 +294,13 @@ export declare class HatchetClient<GlobalInput extends Record<string, any> = {},
294
294
  * @returns A runs client instance
295
295
  */
296
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;
297
304
  private _workflows;
298
305
  /**
299
306
  * Get the workflows client for creating and managing workflows
@@ -405,6 +405,17 @@ class HatchetClient {
405
405
  }
406
406
  return this._runs;
407
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
+ }
408
419
  /**
409
420
  * Get the workflows client for creating and managing workflows
410
421
  * @returns A workflows client instance
@@ -10,3 +10,4 @@ export * from './filters';
10
10
  export * from './tenant';
11
11
  export * from './webhooks';
12
12
  export * from './cel';
13
+ export * from './streams';
@@ -26,3 +26,4 @@ __exportStar(require("./filters"), exports);
26
26
  __exportStar(require("./tenant"), exports);
27
27
  __exportStar(require("./webhooks"), exports);
28
28
  __exportStar(require("./cel"), exports);
29
+ __exportStar(require("./streams"), exports);
@@ -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;
@@ -45,6 +45,7 @@ export declare class InternalWorker {
45
45
  enableHealthServer: boolean;
46
46
  private healthServer;
47
47
  private status;
48
+ private shutdownPromise;
48
49
  constructor(client: HatchetClient, options: {
49
50
  name: string;
50
51
  handleKill?: boolean;
@@ -96,6 +97,7 @@ export declare class InternalWorker {
96
97
  handleCancelStepRun(action: Action): Promise<void>;
97
98
  stop(): Promise<void>;
98
99
  exitGracefully(handleKill: boolean): Promise<void>;
100
+ private shutdown;
99
101
  /**
100
102
  * Creates an action listener by registering the worker with the dispatcher.
101
103
  * Override in subclasses to change registration behavior (e.g. legacy engines).
@@ -628,6 +628,18 @@ class InternalWorker {
628
628
  exitGracefully(handleKill) {
629
629
  return __awaiter(this, void 0, void 0, function* () {
630
630
  var _a;
631
+ // Signals and an explicit stop must await the same eviction and listener teardown.
632
+ (_a = this.shutdownPromise) !== null && _a !== void 0 ? _a : (this.shutdownPromise = this.shutdown());
633
+ yield this.shutdownPromise;
634
+ if (handleKill) {
635
+ this.logger.info('Exiting hatchet worker...');
636
+ process.exit(0);
637
+ }
638
+ });
639
+ }
640
+ shutdown() {
641
+ return __awaiter(this, void 0, void 0, function* () {
642
+ var _a, _b;
631
643
  this.killing = true;
632
644
  this.setStatus(health_server_1.workerStatus.UNHEALTHY);
633
645
  this.logger.info('Starting to exit...');
@@ -658,16 +670,19 @@ class InternalWorker {
658
670
  catch (e) {
659
671
  this.logger.error(`Could not stop durable listener: ${e.message}`);
660
672
  }
673
+ (_a = this.listener) === null || _a === void 0 ? void 0 : _a.stopStream();
674
+ this.logger.info('Gracefully exiting hatchet worker, running tasks will attempt to finish...');
675
+ // attempt to wait for futures to finish
676
+ yield Promise.all(Object.values(this.futures).map(({ promise }) => promise));
677
+ this.logger.info('Successfully finished pending tasks.');
678
+ // Unregistering stops the heartbeat, so it has to wait until the running tasks are done:
679
+ // otherwise the engine sees a dead worker and reassigns tasks that are about to complete.
661
680
  try {
662
- yield ((_a = this.listener) === null || _a === void 0 ? void 0 : _a.unregister());
681
+ yield ((_b = this.listener) === null || _b === void 0 ? void 0 : _b.unregister());
663
682
  }
664
683
  catch (e) {
665
684
  this.logger.error(`Could not unregister listener: ${e.message}`);
666
685
  }
667
- this.logger.info('Gracefully exiting hatchet worker, running tasks will attempt to finish...');
668
- // attempt to wait for futures to finish
669
- yield Promise.all(Object.values(this.futures).map(({ promise }) => promise));
670
- this.logger.info('Successfully finished pending tasks.');
671
686
  if (this.healthServer) {
672
687
  try {
673
688
  yield this.healthServer.stop();
@@ -676,10 +691,6 @@ class InternalWorker {
676
691
  this.logger.error(`Could not stop health server: ${e.message}`);
677
692
  }
678
693
  }
679
- if (handleKill) {
680
- this.logger.info('Exiting hatchet worker...');
681
- process.exit(0);
682
- }
683
694
  });
684
695
  }
685
696
  /**
@@ -700,10 +711,18 @@ class InternalWorker {
700
711
  start() {
701
712
  return __awaiter(this, void 0, void 0, function* () {
702
713
  var _a, e_1, _b, _c;
714
+ if (this.killing) {
715
+ return;
716
+ }
703
717
  this.setStatus(health_server_1.workerStatus.STARTING);
704
718
  if (this.healthServer) {
705
719
  try {
706
720
  yield this.healthServer.start();
721
+ if (this.killing) {
722
+ yield this.shutdownPromise;
723
+ yield this.healthServer.stop();
724
+ return;
725
+ }
707
726
  }
708
727
  catch (e) {
709
728
  this.logger.error(`Could not start health server: ${e.message}`);
@@ -713,11 +732,17 @@ class InternalWorker {
713
732
  }
714
733
  // ensure all workflows are registered
715
734
  yield Promise.all(this.registeredWorkflowPromises);
716
- if (Object.keys(this.action_registry).length === 0) {
735
+ if (this.killing || Object.keys(this.action_registry).length === 0) {
717
736
  return;
718
737
  }
719
738
  try {
720
- this.listener = yield this.createListener();
739
+ const listener = yield this.createListener();
740
+ if (this.killing) {
741
+ // Shutdown cannot clean up a listener that registration has not returned yet.
742
+ yield listener.unregister();
743
+ return;
744
+ }
745
+ this.listener = listener;
721
746
  this.workerId = this.listener.workerId;
722
747
  this.setStatus(health_server_1.workerStatus.HEALTHY);
723
748
  const generator = this.listener.actions();
@@ -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;
@@ -0,0 +1,50 @@
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.measurePublishLatency = measurePublishLatency;
13
+ exports.formatPublishLatency = formatPublishLatency;
14
+ const perf_hooks_1 = require("perf_hooks");
15
+ function percentile(sorted, p) {
16
+ return sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * p))];
17
+ }
18
+ /**
19
+ * Times `count` sequential publishes, after `warmupCount` unmeasured ones
20
+ * absorb connection setup and topic creation.
21
+ */
22
+ function measurePublishLatency(hatchet_1, topic_1, payload_1, count_1) {
23
+ return __awaiter(this, arguments, void 0, function* (hatchet, topic, payload, count, warmupCount = 5) {
24
+ for (let i = 0; i < warmupCount; i += 1) {
25
+ yield hatchet.streams.publish(topic, payload);
26
+ }
27
+ const durationsMs = [];
28
+ const start = perf_hooks_1.performance.now();
29
+ for (let i = 0; i < count; i += 1) {
30
+ const before = perf_hooks_1.performance.now();
31
+ yield hatchet.streams.publish(topic, payload);
32
+ durationsMs.push(perf_hooks_1.performance.now() - before);
33
+ }
34
+ const totalMs = perf_hooks_1.performance.now() - start;
35
+ const sorted = [...durationsMs].sort((a, b) => a - b);
36
+ return {
37
+ count,
38
+ avgMs: durationsMs.reduce((sum, d) => sum + d, 0) / count,
39
+ p50Ms: percentile(sorted, 0.5),
40
+ p99Ms: percentile(sorted, 0.99),
41
+ maxMs: sorted[sorted.length - 1],
42
+ throughputPerSec: (count / totalMs) * 1000,
43
+ };
44
+ });
45
+ }
46
+ function formatPublishLatency(stats) {
47
+ const fmt = (ms) => ms.toFixed(2);
48
+ return (`avg=${fmt(stats.avgMs)}ms p50=${fmt(stats.p50Ms)}ms p99=${fmt(stats.p99Ms)}ms ` +
49
+ `max=${fmt(stats.maxMs)}ms throughput=${fmt(stats.throughputPerSec)}/s`);
50
+ }
@@ -69,11 +69,15 @@ exports.callbackOrderingMid = hatchet_client_1.hatchet.durableTask({
69
69
  };
70
70
  }),
71
71
  });
72
+ // The root is never evicted. The ordering property under test lives in the
73
+ // mids, and the worker evicts one waiting run per server round trip, oldest
74
+ // wait first: evictable roots register before every mid and would consume the
75
+ // sweep for as long as it takes to ack all of them before any mid is reached.
72
76
  exports.callbackOrderingRoot = hatchet_client_1.hatchet.durableTask({
73
77
  name: `${WORKFLOW_PREFIX}-root`,
74
78
  executionTimeout: '10m',
75
79
  retries: 0,
76
- evictionPolicy: EVICTION_POLICY,
80
+ evictionPolicy: { ttl: undefined, allowCapacityEviction: false, priority: 0 },
77
81
  fn: (input, ctx) => __awaiter(void 0, void 0, void 0, function* () {
78
82
  var _a;
79
83
  const durables = (_a = input.durables) !== null && _a !== void 0 ? _a : exports.ROOT_DEFAULTS.durables;
package/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const HATCHET_VERSION = "1.34.0-alpha.2";
1
+ export declare const HATCHET_VERSION = "1.34.1";
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.34.0-alpha.2';
4
+ exports.HATCHET_VERSION = '1.34.1';