@hatchet-dev/typescript-sdk 1.30.0 → 1.31.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.
Files changed (32) hide show
  1. package/clients/dispatcher/action-listener.d.ts +1 -1
  2. package/clients/dispatcher/action-listener.js +6 -0
  3. package/clients/listeners/durable-listener/durable-listener-client.d.ts +6 -1
  4. package/clients/listeners/durable-listener/durable-listener-client.js +124 -37
  5. package/clients/rest/generated/Api.d.ts +6 -1
  6. package/clients/rest/generated/Api.js +1 -1
  7. package/clients/rest/generated/data-contracts.d.ts +9 -1
  8. package/clients/rest/generated/data-contracts.js +1 -0
  9. package/package.json +1 -1
  10. package/protoc/v1/workflows.d.ts +15 -0
  11. package/protoc/v1/workflows.js +62 -2
  12. package/v1/client/worker/context.d.ts +2 -0
  13. package/v1/client/worker/context.js +26 -8
  14. package/v1/client/worker/eviction/eviction-manager.d.ts +1 -1
  15. package/v1/client/worker/eviction/eviction-manager.js +12 -5
  16. package/v1/client/worker/worker-internal.d.ts +15 -0
  17. package/v1/client/worker/worker-internal.js +66 -29
  18. package/v1/declaration.d.ts +3 -1
  19. package/v1/embedded.d.ts +36 -2
  20. package/v1/embedded.js +35 -8
  21. package/v1/examples/concurrency_dynamic/workflow.d.ts +10 -0
  22. package/v1/examples/concurrency_dynamic/workflow.js +32 -0
  23. package/v1/examples/concurrency_shared/workflow.d.ts +17 -0
  24. package/v1/examples/concurrency_shared/workflow.js +70 -0
  25. package/v1/examples/durable_callback_ordering/workflow.d.ts +42 -0
  26. package/v1/examples/durable_callback_ordering/workflow.js +95 -0
  27. package/v1/examples/durable_eviction/workflow.d.ts +5 -0
  28. package/v1/examples/durable_eviction/workflow.js +15 -1
  29. package/v1/examples/e2e-worker.js +97 -88
  30. package/v1/task.d.ts +24 -3
  31. package/version.d.ts +1 -1
  32. package/version.js +1 -1
@@ -53,8 +53,15 @@ class DurableEvictionManager {
53
53
  markActive(key) {
54
54
  this._cache.markActive(key);
55
55
  }
56
- _evictRun(key) {
57
- this._cancelLocal(key);
56
+ // The invocation count pins the teardown to the invocation that was
57
+ // actually evicted: between requesting eviction and the server's ack, the
58
+ // server may restore and re-dispatch the next invocation, which registers
59
+ // under the same action key and must not be torn down.
60
+ _evictRun(key, invocationCount) {
61
+ const current = this._cache.get(key);
62
+ if (current && current.invocationCount !== invocationCount)
63
+ return;
64
+ this._cancelLocal(key, invocationCount);
58
65
  this.unregisterRun(key);
59
66
  }
60
67
  _tickSafe() {
@@ -89,7 +96,7 @@ class DurableEvictionManager {
89
96
  this._logger.debug(`DurableEvictionManager: evicting task_run_external_id=${rec.taskRunExternalId} ` +
90
97
  `wait_kind=${rec.waitKind} resource_id=${rec.waitResourceId}`);
91
98
  yield this._requestEvictionWithAck(key, rec);
92
- this._evictRun(key);
99
+ this._evictRun(key, rec.invocationCount);
93
100
  }
94
101
  });
95
102
  }
@@ -101,7 +108,7 @@ class DurableEvictionManager {
101
108
  if (rec && rec.invocationCount !== invocationCount)
102
109
  return;
103
110
  this._logger.info(`DurableEvictionManager: server-initiated eviction for task_run_external_id=${taskRunExternalId} invocation_count=${invocationCount}`);
104
- this._evictRun(key);
111
+ this._evictRun(key, invocationCount);
105
112
  }
106
113
  evictAllWaiting() {
107
114
  return __awaiter(this, void 0, void 0, function* () {
@@ -122,7 +129,7 @@ class DurableEvictionManager {
122
129
  // Always cancel locally even if the server ACK failed, so the
123
130
  // future settles and exitGracefully doesn't hang.
124
131
  // This will get resolved by the reassignment of the task.
125
- this._evictRun(rec.key);
132
+ this._evictRun(rec.key, rec.invocationCount);
126
133
  evicted++;
127
134
  }
128
135
  return evicted;
@@ -113,6 +113,21 @@ export declare function mapSlotRequestsPb(task: {
113
113
  slotCost?: number;
114
114
  }, isDurable: boolean): Record<string, number>;
115
115
  export declare function mapRateLimitPb(limits: CreateWorkflowTaskOpts<any, any>['rateLimits']): CreateStepRateLimit[];
116
+ export declare function mapConcurrencyPb(entries: Concurrency[]): {
117
+ expression: string;
118
+ maxRuns: number | undefined;
119
+ limitStrategy: import("../..").ConcurrencyLimitStrategy | undefined;
120
+ name: string | undefined;
121
+ isTenantScoped: boolean | undefined;
122
+ maxRunsExpression: string | undefined;
123
+ }[];
124
+ export declare function taskConcurrencyArr(task: {
125
+ concurrency?: Concurrency | Concurrency[];
126
+ }, workflow: {
127
+ taskDefaults?: {
128
+ concurrency?: Concurrency | Concurrency[];
129
+ };
130
+ }): Concurrency[];
116
131
  export declare function assertValidConcurrencyArr(concurrency: Concurrency[] | undefined): void;
117
132
  export declare function mapBatchConfigPb(batch: CreateWorkflowTaskOpts<any, any>['batch']): TaskBatchConfig | undefined;
118
133
  export declare function resolveExecutionTimeout(task: {
@@ -55,6 +55,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
55
55
  exports.InternalWorker = void 0;
56
56
  exports.mapSlotRequestsPb = mapSlotRequestsPb;
57
57
  exports.mapRateLimitPb = mapRateLimitPb;
58
+ exports.mapConcurrencyPb = mapConcurrencyPb;
59
+ exports.taskConcurrencyArr = taskConcurrencyArr;
58
60
  exports.assertValidConcurrencyArr = assertValidConcurrencyArr;
59
61
  exports.mapBatchConfigPb = mapBatchConfigPb;
60
62
  exports.resolveExecutionTimeout = resolveExecutionTimeout;
@@ -307,7 +309,7 @@ class InternalWorker {
307
309
  eventTriggers,
308
310
  cronTriggers,
309
311
  sticky: stickyStrategy,
310
- concurrencyArr,
312
+ concurrencyArr: mapConcurrencyPb(concurrencyArr),
311
313
  onFailureTask,
312
314
  defaultPriority: workflow.defaultPriority,
313
315
  inputJsonSchema,
@@ -333,22 +335,13 @@ class InternalWorker {
333
335
  slotRequests: mapSlotRequestsPb(task, durableTaskSet.has(task)),
334
336
  batch: mapBatchConfigPb(batchOf(task)),
335
337
  concurrency: (() => {
336
- var _a;
337
- const taskConcurrency = task.concurrency
338
- ? Array.isArray(task.concurrency)
339
- ? task.concurrency
340
- : [task.concurrency]
341
- : ((_a = workflow.taskDefaults) === null || _a === void 0 ? void 0 : _a.concurrency)
342
- ? Array.isArray(workflow.taskDefaults.concurrency)
343
- ? workflow.taskDefaults.concurrency
344
- : [workflow.taskDefaults.concurrency]
345
- : [];
338
+ const taskConcurrency = taskConcurrencyArr(task, workflow);
346
339
  assertValidConcurrencyArr(taskConcurrency);
347
- return taskConcurrency;
340
+ return mapConcurrencyPb(taskConcurrency);
348
341
  })(),
349
342
  });
350
343
  }),
351
- concurrency: concurrencySolo,
344
+ concurrency: concurrencySolo ? mapConcurrencyPb([concurrencySolo])[0] : undefined,
352
345
  defaultFilters: (_w = (_v = workflow.defaultFilters) === null || _v === void 0 ? void 0 : _v.map((f) => ({
353
346
  scope: f.scope,
354
347
  expression: f.expression,
@@ -383,28 +376,31 @@ class InternalWorker {
383
376
  const totalDurableSlots = (_c = (_b = (_a = this.slotConfig) === null || _a === void 0 ? void 0 : _a.durable) !== null && _b !== void 0 ? _b : this.durableSlots) !== null && _c !== void 0 ? _c : 0;
384
377
  this.evictionManager = new eviction_manager_1.DurableEvictionManager({
385
378
  durableSlots: totalDurableSlots,
386
- cancelLocal: (key) => {
379
+ cancelLocal: (key, invocationCount) => {
387
380
  var _a;
388
381
  const err = new task_run_terminated_error_1.TaskRunTerminatedError('evicted');
389
382
  const ctx = this.contexts[key];
383
+ // A newer invocation may already be registered under this key (the
384
+ // server restored the run before the eviction ack arrived); it must
385
+ // not be aborted for the old invocation's eviction.
386
+ const ctxMatchesEvictedInvocation = ctx && ((_a = ctx.invocationCount) !== null && _a !== void 0 ? _a : 1) === invocationCount;
390
387
  if (ctx) {
391
- const invocationCount = (_a = ctx.invocationCount) !== null && _a !== void 0 ? _a : 1;
392
- this.client.durableListener.cleanupTaskState(ctx.action.taskRunExternalId, invocationCount);
393
- if (ctx.abortController) {
388
+ // Abort before cleanup: waiters must settle as aborted (eviction),
389
+ // not with cleanup's generic rejection, which would surface as a
390
+ // task failure.
391
+ if (ctxMatchesEvictedInvocation && ctx.abortController) {
394
392
  ctx.abortController.abort(err);
395
393
  }
394
+ this.client.durableListener.cleanupTaskState(ctx.action.taskRunExternalId, invocationCount);
396
395
  }
397
396
  const future = this.futures[key];
398
- if (future) {
397
+ if (future && ctxMatchesEvictedInvocation) {
399
398
  future.promise.catch(() => undefined);
400
399
  future.cancel(hatchet_promise_1.CancellationReason.EVICTED_BY_WORKER);
401
400
  }
402
401
  },
403
402
  requestEvictionWithAck: (key, rec) => __awaiter(this, void 0, void 0, function* () {
404
- var _a;
405
- const ctx = this.contexts[key];
406
- const invocationCount = (_a = ctx === null || ctx === void 0 ? void 0 : ctx.invocationCount) !== null && _a !== void 0 ? _a : 1;
407
- yield this.client.durableListener.sendEvictInvocation(rec.taskRunExternalId, invocationCount, rec.evictionReason);
403
+ yield this.client.durableListener.sendEvictInvocation(rec.taskRunExternalId, rec.invocationCount, rec.evictionReason);
408
404
  }),
409
405
  logger: this.logger,
410
406
  });
@@ -415,12 +411,16 @@ class InternalWorker {
415
411
  this.evictionManager.start();
416
412
  return this.evictionManager;
417
413
  }
418
- cleanupRun(key) {
414
+ cleanupRun(key, attemptContext) {
419
415
  var _a;
420
- const ctx = this.contexts[key];
416
+ const ctx = attemptContext !== null && attemptContext !== void 0 ? attemptContext : this.contexts[key];
421
417
  if (ctx instanceof context_1.DurableContext) {
422
418
  this.client.durableListener.cleanupTaskState(ctx.action.taskRunExternalId, ctx.invocationCount);
423
419
  }
420
+ // A restored invocation may have re-registered under this key while this
421
+ // attempt was shutting down; the key-owned state now belongs to it.
422
+ if (attemptContext && this.contexts[key] !== attemptContext)
423
+ return;
424
424
  (_a = this.evictionManager) === null || _a === void 0 ? void 0 : _a.unregisterRun(key);
425
425
  delete this.futures[key];
426
426
  delete this.contexts[key];
@@ -456,7 +456,7 @@ class InternalWorker {
456
456
  if (!step) {
457
457
  this.logger.error(`Registered actions: '${Object.keys(this.action_registry).join(', ')}'`);
458
458
  this.logger.error(`Could not find step '${actionId}'`);
459
- this.cleanupRun(actionKey);
459
+ this.cleanupRun(actionKey, context);
460
460
  return;
461
461
  }
462
462
  const run = () => __awaiter(this, void 0, void 0, function* () {
@@ -517,7 +517,7 @@ class InternalWorker {
517
517
  this.logger.error(`Could not send action event: ${actionEventError.message || actionEventError}`);
518
518
  }
519
519
  finally {
520
- this.cleanupRun(actionKey);
520
+ this.cleanupRun(actionKey, context);
521
521
  }
522
522
  });
523
523
  const failure = (error) => __awaiter(this, void 0, void 0, function* () {
@@ -526,6 +526,11 @@ class InternalWorker {
526
526
  if (context.cancelled) {
527
527
  return;
528
528
  }
529
+ // The run was evicted or cancelled by the worker, not failed by
530
+ // user code; the server already accounts for it.
531
+ if ((0, task_run_terminated_error_1.isTaskRunTerminatedError)(error)) {
532
+ return;
533
+ }
529
534
  this.logger.error((0, logger_1.taskRunLog)(taskName, taskRunExternalId, `failed: ${error.message}`));
530
535
  if (error.stack) {
531
536
  this.logger.error(error.stack);
@@ -540,7 +545,7 @@ class InternalWorker {
540
545
  this.logger.error(`Could not send action event: ${e.message}`);
541
546
  }
542
547
  finally {
543
- this.cleanupRun(actionKey);
548
+ this.cleanupRun(actionKey, context);
544
549
  }
545
550
  });
546
551
  const future = new hatchet_promise_1.default((() => __awaiter(this, void 0, void 0, function* () {
@@ -581,7 +586,7 @@ class InternalWorker {
581
586
  }
582
587
  }
583
588
  finally {
584
- this.cleanupRun(actionKey);
589
+ this.cleanupRun(actionKey, context);
585
590
  }
586
591
  }
587
592
  catch (e) {
@@ -1104,10 +1109,42 @@ function parseBatchPayload(actionPayload) {
1104
1109
  function batchOf(task) {
1105
1110
  return 'batch' in task ? task.batch : undefined;
1106
1111
  }
1112
+ // mapConcurrencyPb maps SDK concurrency entries onto the proto shape; entries keep their
1113
+ // declared order, which is the chain order.
1114
+ function mapConcurrencyPb(entries) {
1115
+ return entries.map((c) => ({
1116
+ expression: c.expression,
1117
+ // a string maxRuns is a CEL expression; the static field then carries the default
1118
+ // of 1, which only governs slots created before the expression existed
1119
+ maxRuns: typeof c.maxRuns === 'string' ? 1 : c.maxRuns,
1120
+ limitStrategy: c.limitStrategy,
1121
+ name: c.name,
1122
+ isTenantScoped: c.isTenantScoped,
1123
+ maxRunsExpression: typeof c.maxRuns === 'string' ? c.maxRuns : undefined,
1124
+ }));
1125
+ }
1126
+ function taskConcurrencyArr(task, workflow) {
1127
+ var _a;
1128
+ if (task.concurrency) {
1129
+ return Array.isArray(task.concurrency) ? task.concurrency : [task.concurrency];
1130
+ }
1131
+ if ((_a = workflow.taskDefaults) === null || _a === void 0 ? void 0 : _a.concurrency) {
1132
+ return Array.isArray(workflow.taskDefaults.concurrency)
1133
+ ? workflow.taskDefaults.concurrency
1134
+ : [workflow.taskDefaults.concurrency];
1135
+ }
1136
+ return [];
1137
+ }
1107
1138
  function assertValidConcurrencyArr(concurrency) {
1108
1139
  concurrency === null || concurrency === void 0 ? void 0 : concurrency.forEach((c) => {
1140
+ if (typeof c.maxRuns === 'string') {
1141
+ if (!c.maxRuns.trim()) {
1142
+ throw new Error('concurrency.maxRuns expression must be non-empty');
1143
+ }
1144
+ return;
1145
+ }
1109
1146
  if (c.maxRuns !== undefined && (!Number.isInteger(c.maxRuns) || c.maxRuns <= 0)) {
1110
- throw new Error(`concurrency.maxRuns must be a positive integer when provided, got: ${c.maxRuns}`);
1147
+ throw new Error(`concurrency.maxRuns must be a positive integer or a CEL expression, got: ${c.maxRuns}`);
1111
1148
  }
1112
1149
  });
1113
1150
  }
@@ -228,7 +228,9 @@ export type TaskDefaults = {
228
228
  */
229
229
  workerLabels?: CreateWorkflowTaskOpts<any, any>['desiredWorkerLabels'];
230
230
  /**
231
- * (optional) the concurrency options for the task.
231
+ * (optional) the concurrency options for the task, processed in array order. Entries
232
+ * may be workflow-scoped strategies or tenant-scoped entries (`isTenantScoped`)
233
+ * strategies, whose definitions are upserted as part of workflow registration.
232
234
  */
233
235
  concurrency?: Concurrency | Concurrency[];
234
236
  };
package/v1/embedded.d.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  import type { AxiosRequestConfig } from 'axios';
2
2
  import type { ClientConfig, HatchetClientOptions } from '../clients/hatchet-client';
3
3
  import { HatchetClient } from './client/client';
4
+ /**
5
+ * Options for the embedded engine sidecar. All fields are optional; by default the
6
+ * latest hatchet-embedded release is downloaded and started with a bundled Postgres.
7
+ */
4
8
  export interface EmbeddedOptions {
5
9
  /**
6
10
  * hatchet-embedded release tag to download (defaults to HATCHET_CLIENT_EMBEDDED_VERSION or
@@ -20,7 +24,9 @@ export interface EmbeddedOptions {
20
24
  databaseUrl?: string;
21
25
  /** store the bundled Postgres runtime and data under this directory */
22
26
  postgresDataDir?: string;
27
+ /** bind the engine's gRPC server to this port */
23
28
  grpcPort?: number;
29
+ /** bind the REST API server to this port */
24
30
  apiPort?: number;
25
31
  /** set to false to start only the engine + gRPC, no REST API */
26
32
  startApi?: boolean;
@@ -28,14 +34,25 @@ export interface EmbeddedOptions {
28
34
  runMigrations?: boolean;
29
35
  /** use RabbitMQ instead of the Postgres message queue */
30
36
  rabbitmqUrl?: string;
37
+ /** log level for the engine's output */
31
38
  logLevel?: string;
39
+ /** how long to wait for the engine to become ready, in milliseconds (default 300000) */
32
40
  readyTimeoutMs?: number;
33
41
  }
42
+ /**
43
+ * A running embedded engine sidecar and its connection details, as returned by
44
+ * `startEmbeddedSidecar`.
45
+ */
34
46
  export interface EmbeddedSidecar {
47
+ /** API token for the sidecar's default tenant */
35
48
  token: string;
49
+ /** ID of the sidecar's default tenant */
36
50
  tenantId: string;
51
+ /** host:port of the engine's gRPC server */
37
52
  grpcAddress: string;
53
+ /** base URL of the REST API (empty when `startApi` is false) */
38
54
  apiUrl: string;
55
+ /** gracefully stops the sidecar and resolves once it has fully exited */
39
56
  stop: () => Promise<void>;
40
57
  }
41
58
  /**
@@ -46,12 +63,25 @@ export interface EmbeddedSidecar {
46
63
  * your program has returned.
47
64
  */
48
65
  export declare function stopEmbeddedSidecar(): Promise<void>;
66
+ /**
67
+ * Downloads (and caches) the hatchet-embedded sidecar binary, spawns it, and waits
68
+ * until the embedded engine is ready. The sidecar shuts down when this process exits.
69
+ * Use {@link HatchetEmbeddedClient.init} unless you need the raw connection details.
70
+ */
49
71
  export declare function startEmbeddedSidecar(opts?: EmbeddedOptions): Promise<EmbeddedSidecar>;
72
+ /**
73
+ * Entry point for embedded mode. `init()` starts a full local Hatchet engine and
74
+ * returns a regular Hatchet client connected to it. No API token or Docker is needed.
75
+ * See the [embedded mode guide](https://docs.hatchet.run/v1/embedded).
76
+ */
50
77
  export declare class HatchetEmbeddedClient {
51
78
  /**
52
79
  * Runs a full Hatchet engine locally via the hatchet-embedded sidecar (downloaded
53
- * on first use) and returns a client wired to it. By default the sidecar starts a
54
- * bundled Postgres; pass `databaseUrl` to point it at your own instead.
80
+ * on first use) and returns a client wired to it. No API token or Docker is needed,
81
+ * which makes this a good fit for local development and CI. By default the sidecar
82
+ * starts a bundled Postgres; pass `databaseUrl` to point it at your own instead.
83
+ *
84
+ * See the [embedded mode guide](https://docs.hatchet.run/v1/embedded).
55
85
  * @param embeddedOpts - Options for the embedded engine (version, ports, database, ...).
56
86
  * @param config - Optional configuration overrides for the client.
57
87
  * @param options - Optional client options.
@@ -60,6 +90,10 @@ export declare class HatchetEmbeddedClient {
60
90
  */
61
91
  static init<T extends Record<string, any> = {}, U extends Record<string, any> = {}>(embeddedOpts?: EmbeddedOptions, config?: Omit<Partial<ClientConfig>, 'middleware'>, options?: HatchetClientOptions, axiosConfig?: AxiosRequestConfig): Promise<EmbeddedClient<T, U>>;
62
92
  }
93
+ /**
94
+ * A `HatchetClient` connected to an embedded engine, extended with `stopEmbedded`.
95
+ * Returned by {@link HatchetEmbeddedClient.init}.
96
+ */
63
97
  export type EmbeddedClient<T extends Record<string, any> = {}, U extends Record<string, any> = {}> = HatchetClient<T, U> & {
64
98
  /**
65
99
  * Gracefully stops the embedded engine sidecar and resolves once it has
package/v1/embedded.js CHANGED
@@ -52,6 +52,25 @@ Object.defineProperty(exports, "__esModule", { value: true });
52
52
  exports.HatchetEmbeddedClient = void 0;
53
53
  exports.stopEmbeddedSidecar = stopEmbeddedSidecar;
54
54
  exports.startEmbeddedSidecar = startEmbeddedSidecar;
55
+ /**
56
+ * Embedded mode runs a full local Hatchet engine as a sidecar process managed by your
57
+ * application. It needs no API token, no Docker, and no external services (by default
58
+ * the sidecar starts a bundled Postgres), which makes it a good fit for local
59
+ * development and CI.
60
+ *
61
+ * ```typescript
62
+ * import { HatchetEmbeddedClient } from './embedded.js';
63
+ *
64
+ * const hatchet = await HatchetEmbeddedClient.init();
65
+ * // ... register workers and run tasks as usual ...
66
+ * await hatchet.stopEmbedded();
67
+ * ```
68
+ *
69
+ * See the [embedded mode guide](https://docs.hatchet.run/v1/embedded) for setup and
70
+ * configuration details.
71
+ *
72
+ * @module Embedded
73
+ */
55
74
  const child_process_1 = require("child_process");
56
75
  const client_1 = require("./client/client");
57
76
  const crypto_1 = require("crypto");
@@ -211,12 +230,7 @@ function waitForHandshake(child, handshakePath, timeoutMs) {
211
230
  throw new Error(`hatchet embedded sidecar did not become ready within ${timeoutMs}ms`);
212
231
  });
213
232
  }
214
- /**
215
- * Downloads (and caches) the hatchet-embedded sidecar binary, spawns it, and
216
- * waits until the embedded engine is ready. The sidecar shuts down when this
217
- * process exits. Use `HatchetEmbedded()` unless you need the raw
218
- * connection details.
219
- */
233
+ // sidecars started in this process that have not been stopped yet
220
234
  const activeSidecars = new Set();
221
235
  /**
222
236
  * Gracefully stops every sidecar started in this process by
@@ -232,6 +246,11 @@ function stopEmbeddedSidecar() {
232
246
  }
233
247
  });
234
248
  }
249
+ /**
250
+ * Downloads (and caches) the hatchet-embedded sidecar binary, spawns it, and waits
251
+ * until the embedded engine is ready. The sidecar shuts down when this process exits.
252
+ * Use {@link HatchetEmbeddedClient.init} unless you need the raw connection details.
253
+ */
235
254
  function startEmbeddedSidecar() {
236
255
  return __awaiter(this, arguments, void 0, function* (opts = {}) {
237
256
  var _a, _b;
@@ -321,11 +340,19 @@ function startEmbeddedSidecar() {
321
340
  return sidecar;
322
341
  });
323
342
  }
343
+ /**
344
+ * Entry point for embedded mode. `init()` starts a full local Hatchet engine and
345
+ * returns a regular Hatchet client connected to it. No API token or Docker is needed.
346
+ * See the [embedded mode guide](https://docs.hatchet.run/v1/embedded).
347
+ */
324
348
  class HatchetEmbeddedClient {
325
349
  /**
326
350
  * Runs a full Hatchet engine locally via the hatchet-embedded sidecar (downloaded
327
- * on first use) and returns a client wired to it. By default the sidecar starts a
328
- * bundled Postgres; pass `databaseUrl` to point it at your own instead.
351
+ * on first use) and returns a client wired to it. No API token or Docker is needed,
352
+ * which makes this a good fit for local development and CI. By default the sidecar
353
+ * starts a bundled Postgres; pass `databaseUrl` to point it at your own instead.
354
+ *
355
+ * See the [embedded mode guide](https://docs.hatchet.run/v1/embedded).
329
356
  * @param embeddedOpts - Options for the embedded engine (version, ports, database, ...).
330
357
  * @param config - Optional configuration overrides for the client.
331
358
  * @param options - Optional client options.
@@ -0,0 +1,10 @@
1
+ export type WorkflowInput = {
2
+ account: string;
3
+ tier: string;
4
+ };
5
+ export type WorkflowOutput = {
6
+ 'dynamic-task': {
7
+ account: string;
8
+ };
9
+ };
10
+ export declare const concurrencyDynamicWorkflow: import("../..").WorkflowDeclaration<WorkflowInput, WorkflowOutput, {}>;
@@ -0,0 +1,32 @@
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.concurrencyDynamicWorkflow = void 0;
13
+ const v1_1 = require("../..");
14
+ const hatchet_client_1 = require("../hatchet-client");
15
+ // > Dynamic Max Runs
16
+ // maxRuns accepts a number or a CEL expression string. With an expression, each
17
+ // concurrency group's limit is computed from the task's input.
18
+ exports.concurrencyDynamicWorkflow = hatchet_client_1.hatchet.workflow({
19
+ name: 'concurrency-dynamic',
20
+ });
21
+ exports.concurrencyDynamicWorkflow.task({
22
+ name: 'dynamic-task',
23
+ concurrency: [
24
+ {
25
+ expression: 'input.account',
26
+ maxRuns: "input.tier == 'premium' ? 10 : 1",
27
+ limitStrategy: v1_1.ConcurrencyLimitStrategy.GROUP_ROUND_ROBIN,
28
+ },
29
+ ],
30
+ fn: (input) => __awaiter(void 0, void 0, void 0, function* () { return ({ account: input.account }); }),
31
+ });
32
+ // !!
@@ -0,0 +1,17 @@
1
+ import { Concurrency } from '../..';
2
+ export declare const SLEEP_TIME_MS = 1500;
3
+ export type WorkflowInput = {
4
+ group: string;
5
+ inline?: string;
6
+ };
7
+ export type RunWindow = {
8
+ startMs: number;
9
+ endMs: number;
10
+ };
11
+ export type WorkflowOutput = {
12
+ 'shared-task': RunWindow;
13
+ };
14
+ export declare const sharedLimit: Concurrency;
15
+ export declare const concurrencySharedWorkflowA: import("../..").WorkflowDeclaration<WorkflowInput, WorkflowOutput, {}>;
16
+ export declare const concurrencySharedWorkflowB: import("../..").WorkflowDeclaration<WorkflowInput, WorkflowOutput, {}>;
17
+ export declare const concurrencySharedMixedWorkflow: import("../..").WorkflowDeclaration<WorkflowInput, WorkflowOutput, {}>;
@@ -0,0 +1,70 @@
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.concurrencySharedMixedWorkflow = exports.concurrencySharedWorkflowB = exports.concurrencySharedWorkflowA = exports.sharedLimit = exports.SLEEP_TIME_MS = void 0;
13
+ const v1_1 = require("../..");
14
+ const hatchet_client_1 = require("../hatchet-client");
15
+ const sleep = (ms) => new Promise((resolve) => {
16
+ setTimeout(resolve, ms);
17
+ });
18
+ exports.SLEEP_TIME_MS = 1500;
19
+ // > Shared Concurrency Strategy
20
+ // A tenant-scoped strategy is shared across workflows: every task declaring the same name
21
+ // consumes the same concurrency limit. The definition rides on workflow registration and
22
+ // re-registering the name updates it in place.
23
+ exports.sharedLimit = {
24
+ name: 'ts-example-shared-limit',
25
+ isTenantScoped: true,
26
+ expression: 'input.group',
27
+ maxRuns: 1,
28
+ limitStrategy: v1_1.ConcurrencyLimitStrategy.GROUP_ROUND_ROBIN,
29
+ };
30
+ const runWindowTask = () => __awaiter(void 0, void 0, void 0, function* () {
31
+ const startMs = Date.now();
32
+ yield sleep(exports.SLEEP_TIME_MS);
33
+ return { startMs, endMs: Date.now() };
34
+ });
35
+ exports.concurrencySharedWorkflowA = hatchet_client_1.hatchet.workflow({
36
+ name: 'concurrency-shared-a',
37
+ });
38
+ exports.concurrencySharedWorkflowA.task({
39
+ name: 'shared-task',
40
+ concurrency: [exports.sharedLimit],
41
+ fn: runWindowTask,
42
+ });
43
+ exports.concurrencySharedWorkflowB = hatchet_client_1.hatchet.workflow({
44
+ name: 'concurrency-shared-b',
45
+ });
46
+ exports.concurrencySharedWorkflowB.task({
47
+ name: 'shared-task',
48
+ concurrency: [exports.sharedLimit],
49
+ fn: runWindowTask,
50
+ });
51
+ // !!
52
+ // > Mixed Inline And Shared Concurrency
53
+ // A single task can combine a workflow-scoped inline strategy with a shared strategy;
54
+ // both limits apply at once.
55
+ exports.concurrencySharedMixedWorkflow = hatchet_client_1.hatchet.workflow({
56
+ name: 'concurrency-shared-mixed',
57
+ });
58
+ exports.concurrencySharedMixedWorkflow.task({
59
+ name: 'shared-task',
60
+ concurrency: [
61
+ {
62
+ expression: 'input.inline',
63
+ maxRuns: 1,
64
+ limitStrategy: v1_1.ConcurrencyLimitStrategy.GROUP_ROUND_ROBIN,
65
+ },
66
+ exports.sharedLimit,
67
+ ],
68
+ fn: runWindowTask,
69
+ });
70
+ // !!
@@ -0,0 +1,42 @@
1
+ export type LeafInput = {
2
+ mid: number;
3
+ branch: number;
4
+ delayMs: number;
5
+ generation: number;
6
+ };
7
+ export type LeafOutput = {
8
+ mid: number;
9
+ branch: number;
10
+ generation: number;
11
+ };
12
+ export type MidInput = {
13
+ mid: number;
14
+ branches: number;
15
+ childDelayMs: number;
16
+ delayStepMs: number;
17
+ };
18
+ export type MidOutput = {
19
+ mid: number;
20
+ completedBranches: number[];
21
+ invocationCount: number;
22
+ };
23
+ export type RootInput = {
24
+ durables?: number;
25
+ branches?: number;
26
+ childDelayMs?: number;
27
+ delayStepMs?: number;
28
+ };
29
+ export type RootOutput = {
30
+ rootInvocationCount: number;
31
+ midInvocationCounts: number[];
32
+ completedMids: number[];
33
+ };
34
+ export declare const ROOT_DEFAULTS: {
35
+ durables: number;
36
+ branches: number;
37
+ childDelayMs: number;
38
+ delayStepMs: number;
39
+ };
40
+ export declare const callbackOrderingLeaf: import("../..").TaskWorkflowDeclaration<LeafInput, LeafOutput, {}, {}, {}, {}>;
41
+ export declare const callbackOrderingMid: import("../..").TaskWorkflowDeclaration<MidInput, MidOutput, {}, {}, {}, {}>;
42
+ export declare const callbackOrderingRoot: import("../..").TaskWorkflowDeclaration<RootInput, RootOutput, {}, {}, {}, {}>;