@hotmeshio/hotmesh 0.0.29 → 0.0.30

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 CHANGED
@@ -92,19 +92,25 @@ npm install @hotmeshio/hotmesh
92
92
 
93
93
  Redis governance delivers more than just reliability. Externalizing state fundamentally changes the execution profile for your functions, allowing you to design long-running, durable workflows. The `MeshOS` base class (shown in the examples above) provides additional methods for solving the most common state management challenges.
94
94
 
95
- - `waitForSignal` | Pause your function and wait for external event(s) before continuing. The *waitForSignal* method will collate and cache the signals and only awaken your function once they've all arrived.
95
+ - `waitForSignal` | Pause your function and wait for external event(s) before continuing. The *waitForSignal* method will collate and cache the signals and only awaken your function once all signals have arrived.
96
96
  - `signal` | Send a signal (and optional payload) to any paused function.
97
97
  - `hook` | Redis governance converts your functions into 're-entrant processes'. Optionally use the *hook* method to spawn parallel execution threads to augment a running workflow.
98
- - `sleep` | Pause function execution for a ridiculous amount of time (months, years, etc). There's no risk of information loss, as Redis governs function state. When your function awakens, function state is efficiently (and automatically) restored.
98
+ - `sleep` | Pause function execution for a ridiculous amount of time (months, years, etc). There's no risk of information loss, as Redis governs function state. When your function awakens, function state is efficiently (and automatically) restored and your function will resume right where it left off.
99
99
  - `random` | Generate a deterministic random number that can be used in a reentrant process workflow (replaces `Math.random()`).
100
100
  - `executeChild` | Call another durable function and await the response. *Design sophisticated, multi-process solutions by leveraging this command.*
101
101
  - `startChild` | Call another durable function, but do not await the response.
102
- - `set` | Set one or more name/value pairs (e.g, `set('name1', 'value1', 'name1', 'value2')`)
103
- - `get` | Get a single value by name(e.g, `get('name')`)
104
- - `mget` | Get multiple values by name (e.g, `get('name1', 'name2')`)
105
- - `del` | Delete one or more entries by name and return the number deleted (e.g, `del('name1', 'name1')`)
106
- - `incr` | Increment (or decrement) a number (e.g, `incr('name', -99)`)
107
- - `mult` | Multiply a number (e.g, `mult('name', 12)`)
102
+ - `search` | Instance a search session (e.g, `const search = MeshOS.search()`)
103
+ - `set` | Set one or more name/value pairs (e.g, `search.set('name1', 'value1', 'name2', 'value2')`)
104
+ - `get` | Get a single value by name(e.g, `search.get('name')`)
105
+ - `mget` | Get multiple values by name (e.g, `search.mget('name1', 'name2')`)
106
+ - `del` | Delete one or more entries by name and return the number deleted (e.g, `search.del('name1', 'name2')`)
107
+ - `incr` | Increment (or decrement) a number (e.g, `search.incr('name', -99)`)
108
+ - `mult` | Multiply a number (e.g, `search.mult('name', 12)`)
109
+ - `find` | Find workflows using the native Redis [FT.*](https://redis.io/commands/ft.search/) search commands
110
+ - `findWhere` | Find workflows using a simplified, JSON-based search syntax that overlays the native Redis FT.SEARCH syntax.
111
+ - `createIndex` | Create a searchable index in Redis using simplified, JSON-based syntax that overlays the native Redis FT.CREATE syntax.
112
+ - `startWorkers` | Start the workers necessary to govern your class (typically called at server startup).
113
+ - `stopWorkers` | Stop all workers (typically called at server shutdown)
108
114
 
109
115
  Refer to the [hotmeshio/samples-typescript](https://github.com/hotmeshio/samples-typescript) repo for usage examples.
110
116
 
@@ -216,31 +222,31 @@ const hotMesh = await HotMesh.init({
216
222
  ```
217
223
 
218
224
  ### Observability
219
- Workflows and activities are run according to the rules you define, offering [Graph-Oriented](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/system_lifecycle.md#telemetry) telemetry insights into your legacy function executions.
225
+ Workflows and activities are run according to the rules you define, offering [Graph-Oriented](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/system_lifecycle.md#telemetry) telemetry insights into your legacy function executions.
220
226
 
221
227
  ## FAQ
222
- Refer to the [FAQ](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/faq.md) for terminology, definitions, and an exploration of how HotMesh facilitates orchestration use cases.
228
+ Refer to the [FAQ](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/faq.md) for terminology, definitions, and an exploration of how HotMesh facilitates orchestration use cases.
223
229
 
224
230
  ## Quick Start
225
- Refer to the [Quick Start](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/quickstart.md) for sample flows you can easily copy, paste, and modify to get started.
231
+ Refer to the [Quick Start](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/quickstart.md) for sample flows you can easily copy, paste, and modify to get started.
226
232
 
227
233
  ## Developer Guide
228
- For more details on the complete development process, including information about schemas, APIs, and deployment, consult the [Developer Guide](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/developer_guide.md).
234
+ For more details on the complete development process, including information about schemas, APIs, and deployment, consult the [Developer Guide](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/developer_guide.md).
229
235
 
230
236
  ## Model Driven Development
231
- [Model Driven Development](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/model_driven_development.md) is an established strategy for managing process-oriented tasks. Check out this guide to understand its foundational principles.
237
+ [Model Driven Development](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/model_driven_development.md) is an established strategy for managing process-oriented tasks. Check out this guide to understand its foundational principles.
232
238
 
233
239
  ## Data Mapping
234
- Exchanging data between activities is central to HotMesh. For detailed information on supported functions and the functional mapping syntax (@pipes), see the [Data Mapping Overview](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/data_mapping.md).
240
+ Exchanging data between activities is central to HotMesh. For detailed information on supported functions and the functional mapping syntax (@pipes), see the [Data Mapping Overview](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/data_mapping.md).
235
241
 
236
242
  ## Composition
237
- While the simplest graphs are linear, detailing a consistent sequence of non-cyclical activities, graphs can be layered to represent intricate business scenarios. Some can even be designed to accommodate long-lasting workflows that span months. For more details, check out the [Composable Workflow Guide](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/composable_workflow.md).
243
+ While the simplest graphs are linear, detailing a consistent sequence of non-cyclical activities, graphs can be layered to represent intricate business scenarios. Some can even be designed to accommodate long-lasting workflows that span months. For more details, check out the [Composable Workflow Guide](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/composable_workflow.md).
238
244
 
239
245
  ## Distributed Orchestration
240
- HotMesh is a distributed orchestration engine. Refer to the [Distributed Orchestration Guide](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/distributed_orchestration.md) for a detailed breakdown of the approach.
246
+ HotMesh is a distributed orchestration engine. Refer to the [Distributed Orchestration Guide](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/distributed_orchestration.md) for a detailed breakdown of the approach.
241
247
 
242
248
  ## System Lifecycle
243
- Gain insight into HotMesh's monitoring, exception handling, and alarm configurations via the [System Lifecycle Guide](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/system_lifecycle.md).
249
+ Gain insight into HotMesh's monitoring, exception handling, and alarm configurations via the [System Lifecycle Guide](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/system_lifecycle.md).
244
250
 
245
251
  ## Alpha Release
246
- So what exacty is an [alpha release](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/alpha.md)?
252
+ So what exacty is an [alpha release](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/alpha.md)?
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.29",
3
+ "version": "0.0.30",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -54,7 +54,7 @@
54
54
  "test:durable:loopactivity": "NODE_ENV=test jest ./tests/durable/loopactivity/index.test.ts --detectOpenHandles --forceExit --verbose",
55
55
  "test:durable:nested": "NODE_ENV=test jest ./tests/durable/nested/index.test.ts --detectOpenHandles --forceExit --verbose"
56
56
  },
57
- "keywords": [],
57
+ "keywords": ["durable workflow", "hotmesh", "service mesh", "workflows", "operational data", "redis"],
58
58
  "author": "luke.birdeau@gmail.com",
59
59
  "license": "SEE LICENSE IN LICENSE",
60
60
  "dependencies": {
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ClientService = void 0;
4
- const nanoid_1 = require("nanoid");
5
4
  const factory_1 = require("./factory");
6
5
  const handle_1 = require("./handle");
7
6
  const hotmesh_1 = require("../hotmesh");
@@ -88,14 +87,14 @@ class ClientService {
88
87
  const payload = {
89
88
  arguments: [...options.args],
90
89
  parentWorkflowId: options.parentWorkflowId,
91
- workflowId: options.workflowId || (0, nanoid_1.nanoid)(),
90
+ workflowId: options.workflowId || hotmesh_1.HotMeshService.guid(),
92
91
  workflowTopic: workflowTopic,
93
92
  backoffCoefficient: options.config?.backoffCoefficient || factory_1.DEFAULT_COEFFICIENT,
94
93
  };
95
94
  const context = { metadata: { trc, spn }, data: {} };
96
95
  const jobId = await hotMeshClient.pub(`${options.namespace ?? factory_1.APP_ID}.execute`, payload, context);
96
+ //seed search data if present
97
97
  if (jobId && options.search?.data) {
98
- //job successfully kicked off; there is default job data to persist
99
98
  const searchSessionId = `-search-0`;
100
99
  const search = new search_1.Search(jobId, hotMeshClient, searchSessionId);
101
100
  for (const [key, value] of Object.entries(options.search.data)) {
@@ -125,8 +124,17 @@ class ClientService {
125
124
  workflowTopic,
126
125
  backoffCoefficient: options.config?.backoffCoefficient || factory_1.DEFAULT_COEFFICIENT,
127
126
  };
127
+ //seed search data if presentthe hook before entering
128
128
  const hotMeshClient = await this.getHotMeshClient(workflowTopic, options.namespace);
129
- return await hotMeshClient.hook(`${hotMeshClient.appId}.flow.signal`, payload, types_1.StreamStatus.PENDING, 202);
129
+ const msgId = await hotMeshClient.hook(`${hotMeshClient.appId}.flow.signal`, payload, types_1.StreamStatus.PENDING, 202);
130
+ if (options.search?.data) {
131
+ const searchSessionId = `-search-${hotmesh_1.HotMeshService.guid()}-0`;
132
+ const search = new search_1.Search(options.workflowId, hotMeshClient, searchSessionId);
133
+ for (const [key, value] of Object.entries(options.search.data)) {
134
+ search.set(key, value);
135
+ }
136
+ }
137
+ return msgId;
130
138
  },
131
139
  getHandle: async (taskQueue, workflowName, workflowId, namespace) => {
132
140
  const workflowTopic = `${taskQueue}-${workflowName}`;
@@ -1,12 +1,14 @@
1
1
  import { ClientService } from './client';
2
2
  import { ConnectionService } from './connection';
3
3
  import { MeshOSService } from './meshos';
4
+ import { Search } from './search';
4
5
  import { WorkerService } from './worker';
5
6
  import { WorkflowService } from './workflow';
6
7
  import { ContextType } from '../../types/durable';
7
8
  export declare const Durable: {
8
9
  Client: typeof ClientService;
9
10
  Connection: typeof ConnectionService;
11
+ Search: typeof Search;
10
12
  MeshOS: typeof MeshOSService;
11
13
  Worker: typeof WorkerService;
12
14
  workflow: typeof WorkflowService;
@@ -4,11 +4,13 @@ exports.Durable = void 0;
4
4
  const client_1 = require("./client");
5
5
  const connection_1 = require("./connection");
6
6
  const meshos_1 = require("./meshos");
7
+ const search_1 = require("./search");
7
8
  const worker_1 = require("./worker");
8
9
  const workflow_1 = require("./workflow");
9
10
  exports.Durable = {
10
11
  Client: client_1.ClientService,
11
12
  Connection: connection_1.ConnectionService,
13
+ Search: search_1.Search,
12
14
  MeshOS: meshos_1.MeshOSService,
13
15
  Worker: worker_1.WorkerService,
14
16
  workflow: workflow_1.WorkflowService,
@@ -1,52 +1,97 @@
1
1
  import { Search } from './search';
2
2
  import { HotMeshService as HotMesh } from '../hotmesh';
3
- import { ActivityConfig, HookOptions, ProxyType, WorkflowOptions } from "../../types/durable";
3
+ import { ActivityConfig, HookOptions, ProxyType, WorkflowContext, WorkflowOptions } from "../../types/durable";
4
4
  export declare class WorkflowService {
5
5
  /**
6
- * Spawn a child workflow. await and return the result.
6
+ * Spawns a child workflow. await and return the result.
7
+ * @template T - the result type
8
+ * @param {WorkflowOptions} options - the workflow options
9
+ * @returns {Promise<T>} - the result of the child workflow
7
10
  */
8
11
  static executeChild<T>(options: WorkflowOptions): Promise<T>;
9
12
  /**
10
- * spawn a child workflow. return the childJobId.
13
+ * Spawns a child workflow. return the childJobId.
14
+ * This method is used when the result of the child workflow is not needed.
15
+ * @param {WorkflowOptions} options - the workflow options
16
+ * @returns {Promise<string>} - the childJobId
11
17
  */
12
18
  static startChild<T>(options: WorkflowOptions): Promise<string>;
13
19
  /**
14
- * wrap all activities in a proxy that will durably run them
20
+ * Wraps activities in a proxy that will durably run them
21
+ * @param {ActivityConfig} options - the activity configuration
22
+ * that will be used to wrap the activities. You must pass an
23
+ * `activities` object to this configuration. The activities object
24
+ * should be a key-value pair of activity names and their respective
25
+ * functions. This is typically done by importing the activities.
26
+ *
27
+ * @returns {ProxyType<ACT>} - a proxy object with the same keys as the
28
+ * activities object, but with the values replaced by a wrapped function
29
+ * @example
30
+ * // import the activities
31
+ * import * as activities from './activities';
32
+ * const proxy = WorkflowService.proxyActivities<typeof activities>({ activities });
33
+ *
34
+ * //or destructure the proxy object, as the function names are the keys
35
+ * const { activity1, activity2 } = WorkflowService.proxyActivities<typeof activities>({ activities });
15
36
  */
16
37
  static proxyActivities<ACT>(options?: ActivityConfig): ProxyType<ACT>;
17
38
  /**
18
- * return a search session for use when reading/writing to the workflow HASH
39
+ * Returns a search session for use when reading/writing to the workflow HASH.
40
+ * The search session provides access to methods like `get`, `mget`, `set`, `del`, and `incr`.
41
+ * @returns {Promise<Search>} - a search session
19
42
  */
20
43
  static search(): Promise<Search>;
21
44
  /**
22
- * return a handle to the hotmesh client currently running the workflow
45
+ * Return a handle to the hotmesh client currently running the workflow
46
+ * @returns {Promise<HotMesh>} - a hotmesh client
23
47
  */
24
48
  static getHotMesh(): Promise<HotMesh>;
25
49
  /**
26
- * those methods that may only be called once must be protected by flagging
50
+ * Returns the current workflow context
51
+ * @returns {WorkflowContext} - the current workflow context
52
+ */
53
+ static getContext(): WorkflowContext;
54
+ /**
55
+ * Those methods that may only be called once must be protected by flagging
27
56
  * their execution with a unique key (the key is stored in the HASH alongside
28
57
  * process state and job state)
58
+ * @private
29
59
  */
30
60
  static isSideEffectAllowed(hotMeshClient: HotMesh, prefix: string): Promise<boolean>;
31
61
  /**
32
- * returns a random number between 0 and 1. This number is deterministic
62
+ * Returns a random number between 0 and 1. This number is deterministic
33
63
  * and will never vary for a given seed. This is useful for randomizing
34
64
  * pathways in a workflow that can be safely replayed.
35
- * @returns {number}
65
+ * @returns {number} - a random number between 0 and 1
36
66
  */
37
67
  static random(): number;
38
68
  /**
39
- * send signal data into any other paused thread (which is paused and
69
+ * Sends signal data into any other paused thread (which is paused and
40
70
  * awaiting the signal) from within a hook-thread or the main-thread
71
+ * @param {string} signalId - the signal id
72
+ * @param {Record<any, any>} data - the signal data
73
+ * @returns {Promise<string>} - the stream id
41
74
  */
42
75
  static signal(signalId: string, data: Record<any, any>): Promise<string>;
43
76
  /**
44
- * spawn a hook from either the main thread or a hook thread with
77
+ * Spawns a hook from either the main thread or a hook thread with
45
78
  * the provided options; worflowId/TaskQueue/Name are optional and will
46
79
  * default to the current workflowId/WorkflowTopic if not provided
80
+ * @param {HookOptions} options - the hook options
47
81
  */
48
82
  static hook(options: HookOptions): Promise<string>;
83
+ /**
84
+ * Sleeps for a duration.
85
+ * @param {string} duration - for example: '1 minute', '2 hours', '3 days'
86
+ * @returns {Promise<number>}
87
+ */
49
88
  static sleep(duration: string): Promise<number>;
89
+ /**
90
+ * Waits for a signal to awaken
91
+ * @param {string[]} signals - the signals to wait for
92
+ * @param {Record<string, string>} options - the options
93
+ * @returns {Promise<Record<any, any>[]>}
94
+ */
50
95
  static waitForSignal(signals: string[], options?: Record<string, string>): Promise<Record<any, any>[]>;
51
96
  static wrapActivity<T>(activityName: string, options?: ActivityConfig): T;
52
97
  }
@@ -17,7 +17,10 @@ const stream_1 = require("../../types/stream");
17
17
  const utils_1 = require("../../modules/utils");
18
18
  class WorkflowService {
19
19
  /**
20
- * Spawn a child workflow. await and return the result.
20
+ * Spawns a child workflow. await and return the result.
21
+ * @template T - the result type
22
+ * @param {WorkflowOptions} options - the workflow options
23
+ * @returns {Promise<T>} - the result of the child workflow
21
24
  */
22
25
  static async executeChild(options) {
23
26
  const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
@@ -28,10 +31,11 @@ class WorkflowService {
28
31
  const workflowSpan = store.get('workflowSpan');
29
32
  const COUNTER = store.get('counter');
30
33
  const execIndex = COUNTER.counter = COUNTER.counter + 1;
34
+ const prefix = options.prefix ?? '';
31
35
  //this is risky but MUST be allowed. Users MAY set the workflowId,
32
36
  //but if there is a naming collision, the data from the target entity will be used
33
37
  //as there is know way of knowing if the item was generated via a prior run of the workflow
34
- const childJobId = options.workflowId ?? `-${workflowId}-$${options.workflowName}${workflowDimension}-${execIndex}`;
38
+ const childJobId = options.workflowId ?? `${prefix}-${workflowId}-$${options.workflowName}${workflowDimension}-${execIndex}`;
35
39
  const parentWorkflowId = `${workflowId}-f`;
36
40
  const client = new client_1.ClientService({
37
41
  connection: await connection_1.ConnectionService.connect(worker_1.WorkerService.connection),
@@ -55,7 +59,10 @@ class WorkflowService {
55
59
  }
56
60
  }
57
61
  /**
58
- * spawn a child workflow. return the childJobId.
62
+ * Spawns a child workflow. return the childJobId.
63
+ * This method is used when the result of the child workflow is not needed.
64
+ * @param {WorkflowOptions} options - the workflow options
65
+ * @returns {Promise<string>} - the childJobId
59
66
  */
60
67
  static async startChild(options) {
61
68
  const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
@@ -66,7 +73,8 @@ class WorkflowService {
66
73
  const workflowSpan = store.get('workflowSpan');
67
74
  const COUNTER = store.get('counter');
68
75
  const execIndex = COUNTER.counter = COUNTER.counter + 1;
69
- const childJobId = options.workflowId ?? `-${workflowId}-$${options.workflowName}${workflowDimension}-${execIndex}`;
76
+ const prefix = options.prefix ?? '';
77
+ const childJobId = options.workflowId ?? `${prefix}-${workflowId}-$${options.workflowName}${workflowDimension}-${execIndex}`;
70
78
  const parentWorkflowId = `${workflowId}-f`;
71
79
  const workflowTopic = `${options.taskQueue}-${options.workflowName}`;
72
80
  try {
@@ -91,7 +99,22 @@ class WorkflowService {
91
99
  }
92
100
  }
93
101
  /**
94
- * wrap all activities in a proxy that will durably run them
102
+ * Wraps activities in a proxy that will durably run them
103
+ * @param {ActivityConfig} options - the activity configuration
104
+ * that will be used to wrap the activities. You must pass an
105
+ * `activities` object to this configuration. The activities object
106
+ * should be a key-value pair of activity names and their respective
107
+ * functions. This is typically done by importing the activities.
108
+ *
109
+ * @returns {ProxyType<ACT>} - a proxy object with the same keys as the
110
+ * activities object, but with the values replaced by a wrapped function
111
+ * @example
112
+ * // import the activities
113
+ * import * as activities from './activities';
114
+ * const proxy = WorkflowService.proxyActivities<typeof activities>({ activities });
115
+ *
116
+ * //or destructure the proxy object, as the function names are the keys
117
+ * const { activity1, activity2 } = WorkflowService.proxyActivities<typeof activities>({ activities });
95
118
  */
96
119
  static proxyActivities(options) {
97
120
  if (options.activities) {
@@ -108,7 +131,9 @@ class WorkflowService {
108
131
  return proxy;
109
132
  }
110
133
  /**
111
- * return a search session for use when reading/writing to the workflow HASH
134
+ * Returns a search session for use when reading/writing to the workflow HASH.
135
+ * The search session provides access to methods like `get`, `mget`, `set`, `del`, and `incr`.
136
+ * @returns {Promise<Search>} - a search session
112
137
  */
113
138
  static async search() {
114
139
  const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
@@ -124,7 +149,8 @@ class WorkflowService {
124
149
  return new search_1.Search(workflowId, hotMeshClient, searchSessionId);
125
150
  }
126
151
  /**
127
- * return a handle to the hotmesh client currently running the workflow
152
+ * Return a handle to the hotmesh client currently running the workflow
153
+ * @returns {Promise<HotMesh>} - a hotmesh client
128
154
  */
129
155
  static async getHotMesh() {
130
156
  const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
@@ -133,9 +159,33 @@ class WorkflowService {
133
159
  return await worker_1.WorkerService.getHotMesh(workflowTopic, { namespace });
134
160
  }
135
161
  /**
136
- * those methods that may only be called once must be protected by flagging
162
+ * Returns the current workflow context
163
+ * @returns {WorkflowContext} - the current workflow context
164
+ */
165
+ static getContext() {
166
+ const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
167
+ const workflowId = store.get('workflowId');
168
+ const workflowDimension = store.get('workflowDimension') ?? '';
169
+ const workflowTopic = store.get('workflowTopic');
170
+ const namespace = store.get('namespace');
171
+ const workflowTrace = store.get('workflowTrace');
172
+ const workflowSpan = store.get('workflowSpan');
173
+ const COUNTER = store.get('counter');
174
+ return {
175
+ counter: COUNTER.counter,
176
+ namespace,
177
+ workflowId,
178
+ workflowDimension,
179
+ workflowTopic,
180
+ workflowTrace,
181
+ workflowSpan,
182
+ };
183
+ }
184
+ /**
185
+ * Those methods that may only be called once must be protected by flagging
137
186
  * their execution with a unique key (the key is stored in the HASH alongside
138
187
  * process state and job state)
188
+ * @private
139
189
  */
140
190
  static async isSideEffectAllowed(hotMeshClient, prefix) {
141
191
  const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
@@ -153,10 +203,10 @@ class WorkflowService {
153
203
  return guidValue === 1;
154
204
  }
155
205
  /**
156
- * returns a random number between 0 and 1. This number is deterministic
206
+ * Returns a random number between 0 and 1. This number is deterministic
157
207
  * and will never vary for a given seed. This is useful for randomizing
158
208
  * pathways in a workflow that can be safely replayed.
159
- * @returns {number}
209
+ * @returns {number} - a random number between 0 and 1
160
210
  */
161
211
  static random() {
162
212
  const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
@@ -165,8 +215,11 @@ class WorkflowService {
165
215
  return (0, utils_1.deterministicRandom)(seed);
166
216
  }
167
217
  /**
168
- * send signal data into any other paused thread (which is paused and
218
+ * Sends signal data into any other paused thread (which is paused and
169
219
  * awaiting the signal) from within a hook-thread or the main-thread
220
+ * @param {string} signalId - the signal id
221
+ * @param {Record<any, any>} data - the signal data
222
+ * @returns {Promise<string>} - the stream id
170
223
  */
171
224
  static async signal(signalId, data) {
172
225
  const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
@@ -180,9 +233,10 @@ class WorkflowService {
180
233
  }
181
234
  }
182
235
  /**
183
- * spawn a hook from either the main thread or a hook thread with
236
+ * Spawns a hook from either the main thread or a hook thread with
184
237
  * the provided options; worflowId/TaskQueue/Name are optional and will
185
238
  * default to the current workflowId/WorkflowTopic if not provided
239
+ * @param {HookOptions} options - the hook options
186
240
  */
187
241
  static async hook(options) {
188
242
  const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
@@ -205,6 +259,11 @@ class WorkflowService {
205
259
  return await hotMeshClient.hook(`${namespace}.flow.signal`, payload, stream_1.StreamStatus.PENDING, 202);
206
260
  }
207
261
  }
262
+ /**
263
+ * Sleeps for a duration.
264
+ * @param {string} duration - for example: '1 minute', '2 hours', '3 days'
265
+ * @returns {Promise<number>}
266
+ */
208
267
  static async sleep(duration) {
209
268
  const seconds = (0, ms_1.default)(duration) / 1000;
210
269
  const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
@@ -227,6 +286,12 @@ class WorkflowService {
227
286
  throw new errors_1.DurableSleepError(workflowId, seconds, execIndex, workflowDimension);
228
287
  }
229
288
  }
289
+ /**
290
+ * Waits for a signal to awaken
291
+ * @param {string[]} signals - the signals to wait for
292
+ * @param {Record<string, string>} options - the options
293
+ * @returns {Promise<Record<any, any>[]>}
294
+ */
230
295
  static async waitForSignal(signals, options) {
231
296
  const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
232
297
  const COUNTER = store.get('counter');
@@ -5,6 +5,36 @@ type WorkflowConfig = {
5
5
  maximumInterval?: string;
6
6
  initialInterval?: string;
7
7
  };
8
+ type WorkflowContext = {
9
+ /**
10
+ * the reentrant semaphore, incremented in real-time as idempotent statements are re-traversed upon reentry. Indicates the current semaphore count.
11
+ */
12
+ counter: number;
13
+ /**
14
+ * the HotMesh App namespace. `durable` is the default.
15
+ */
16
+ namespace: string;
17
+ /**
18
+ * the workflow/job ID
19
+ */
20
+ workflowId: string;
21
+ /**
22
+ * the dimensional isolation for the reentrant hook, expressed in the format `0,0`, `0,1`, etc
23
+ */
24
+ workflowDimension: string;
25
+ /**
26
+ * a concatenation of the task queue and workflow name (e.g., `${taskQueueName}-${workflowName}`)
27
+ */
28
+ workflowTopic: string;
29
+ /**
30
+ * the open telemetry trace context for the workflow, used for logging and tracing. If a sink is enabled, this will be sent to the sink.
31
+ */
32
+ workflowTrace: string;
33
+ /**
34
+ * the open telemetry span context for the workflow, used for logging and tracing. If a sink is enabled, this will be sent to the sink.
35
+ */
36
+ workflowSpan: string;
37
+ };
8
38
  type WorkflowSearchOptions = {
9
39
  index?: string;
10
40
  prefix?: string[];
@@ -19,6 +49,7 @@ type WorkflowOptions = {
19
49
  taskQueue: string;
20
50
  args: any[];
21
51
  workflowId?: string;
52
+ prefix?: string;
22
53
  workflowName?: string;
23
54
  parentWorkflowId?: string;
24
55
  workflowTrace?: string;
@@ -142,4 +173,4 @@ type ActivityConfig = {
142
173
  maximumInterval: string;
143
174
  };
144
175
  };
145
- export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, ProxyType, Registry, SignalOptions, FindOptions, FindWhereOptions, FindWhereQuery, HookOptions, MeshOSActivityOptions, MeshOSWorkerOptions, MeshOSClassConfig, MeshOSConfig, MeshOSOptions, WorkerConfig, WorkflowConfig, WorkerOptions, WorkflowSearchOptions, WorkflowDataType, WorkflowOptions, };
176
+ export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, ProxyType, Registry, SignalOptions, FindOptions, FindWhereOptions, FindWhereQuery, HookOptions, MeshOSActivityOptions, MeshOSWorkerOptions, MeshOSClassConfig, MeshOSConfig, MeshOSOptions, WorkerConfig, WorkflowConfig, WorkerOptions, WorkflowSearchOptions, WorkflowDataType, WorkflowOptions, WorkflowContext, };
@@ -3,7 +3,7 @@ export { App, AppVID, AppTransitions, AppSubscriptions } from './app';
3
3
  export { AsyncSignal } from './async';
4
4
  export { CacheMode } from './cache';
5
5
  export { CollationFaultType, CollationStage } from './collator';
6
- export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, ProxyType, Registry, SignalOptions, FindOptions, FindWhereOptions, FindWhereQuery, HookOptions, MeshOSActivityOptions, MeshOSWorkerOptions, MeshOSClassConfig, MeshOSConfig, MeshOSOptions, WorkflowConfig, WorkerConfig, WorkerOptions, WorkflowSearchOptions, WorkflowDataType, WorkflowOptions, } from './durable';
6
+ export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, ProxyType, Registry, SignalOptions, FindOptions, FindWhereOptions, FindWhereQuery, HookOptions, MeshOSActivityOptions, MeshOSWorkerOptions, MeshOSClassConfig, MeshOSConfig, MeshOSOptions, WorkflowConfig, WorkerConfig, WorkerOptions, WorkflowContext, WorkflowSearchOptions, WorkflowDataType, WorkflowOptions, } from './durable';
7
7
  export { HookCondition, HookConditions, HookGate, HookInterface, HookRule, HookRules, HookSignal } from './hook';
8
8
  export { RedisClientType as IORedisClientType, RedisMultiType as IORedisMultiType } from './ioredisclient';
9
9
  export { ILogger } from './logger';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.29",
3
+ "version": "0.0.30",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -54,7 +54,7 @@
54
54
  "test:durable:loopactivity": "NODE_ENV=test jest ./tests/durable/loopactivity/index.test.ts --detectOpenHandles --forceExit --verbose",
55
55
  "test:durable:nested": "NODE_ENV=test jest ./tests/durable/nested/index.test.ts --detectOpenHandles --forceExit --verbose"
56
56
  },
57
- "keywords": [],
57
+ "keywords": ["durable workflow", "hotmesh", "service mesh", "workflows", "operational data", "redis"],
58
58
  "author": "luke.birdeau@gmail.com",
59
59
  "license": "SEE LICENSE IN LICENSE",
60
60
  "dependencies": {
@@ -1,4 +1,3 @@
1
- import { nanoid } from 'nanoid';
2
1
  import { APP_ID, APP_VERSION, DEFAULT_COEFFICIENT, getWorkflowYAML } from './factory';
3
2
  import { WorkflowHandleService } from './handle';
4
3
  import { HotMeshService as HotMesh } from '../hotmesh';
@@ -104,7 +103,7 @@ export class ClientService {
104
103
  const payload = {
105
104
  arguments: [...options.args],
106
105
  parentWorkflowId: options.parentWorkflowId,
107
- workflowId: options.workflowId || nanoid(),
106
+ workflowId: options.workflowId || HotMesh.guid(),
108
107
  workflowTopic: workflowTopic,
109
108
  backoffCoefficient: options.config?.backoffCoefficient || DEFAULT_COEFFICIENT,
110
109
  }
@@ -112,9 +111,10 @@ export class ClientService {
112
111
  const jobId = await hotMeshClient.pub(
113
112
  `${options.namespace ?? APP_ID}.execute`,
114
113
  payload,
115
- context as JobState);
116
- if (jobId && options.search?.data) {
117
- //job successfully kicked off; there is default job data to persist
114
+ context as JobState
115
+ );
116
+ //seed search data if present
117
+ if (jobId && options.search?.data) {
118
118
  const searchSessionId = `-search-0`;
119
119
  const search = new Search(jobId, hotMeshClient, searchSessionId);
120
120
  for (const [key, value] of Object.entries(options.search.data)) {
@@ -146,8 +146,17 @@ export class ClientService {
146
146
  workflowTopic,
147
147
  backoffCoefficient: options.config?.backoffCoefficient || DEFAULT_COEFFICIENT,
148
148
  }
149
+ //seed search data if presentthe hook before entering
149
150
  const hotMeshClient = await this.getHotMeshClient(workflowTopic, options.namespace);
150
- return await hotMeshClient.hook(`${hotMeshClient.appId}.flow.signal`, payload, StreamStatus.PENDING, 202);
151
+ const msgId = await hotMeshClient.hook(`${hotMeshClient.appId}.flow.signal`, payload, StreamStatus.PENDING, 202);
152
+ if (options.search?.data) {
153
+ const searchSessionId = `-search-${HotMesh.guid()}-0`;
154
+ const search = new Search(options.workflowId, hotMeshClient, searchSessionId);
155
+ for (const [key, value] of Object.entries(options.search.data)) {
156
+ search.set(key, value);
157
+ }
158
+ }
159
+ return msgId;
151
160
  },
152
161
 
153
162
  getHandle: async (taskQueue: string, workflowName: string, workflowId: string, namespace?: string): Promise<WorkflowHandleService> => {
@@ -1,6 +1,7 @@
1
1
  import { ClientService } from './client';
2
2
  import { ConnectionService } from './connection';
3
3
  import { MeshOSService } from './meshos';
4
+ import { Search } from './search';
4
5
  import { WorkerService } from './worker';
5
6
  import { WorkflowService } from './workflow';
6
7
  import { ContextType } from '../../types/durable';
@@ -8,6 +9,7 @@ import { ContextType } from '../../types/durable';
8
9
  export const Durable = {
9
10
  Client: ClientService,
10
11
  Connection: ConnectionService,
12
+ Search,
11
13
  MeshOS: MeshOSService,
12
14
  Worker: WorkerService,
13
15
  workflow: WorkflowService,
@@ -16,6 +16,7 @@ import {
16
16
  ActivityConfig,
17
17
  HookOptions,
18
18
  ProxyType,
19
+ WorkflowContext,
19
20
  WorkflowOptions } from "../../types/durable";
20
21
  import { JobOutput, JobState } from '../../types/job';
21
22
  import { StreamStatus } from '../../types/stream';
@@ -24,7 +25,10 @@ import { deterministicRandom } from '../../modules/utils';
24
25
  export class WorkflowService {
25
26
 
26
27
  /**
27
- * Spawn a child workflow. await and return the result.
28
+ * Spawns a child workflow. await and return the result.
29
+ * @template T - the result type
30
+ * @param {WorkflowOptions} options - the workflow options
31
+ * @returns {Promise<T>} - the result of the child workflow
28
32
  */
29
33
  static async executeChild<T>(options: WorkflowOptions): Promise<T> {
30
34
  const store = asyncLocalStorage.getStore();
@@ -35,10 +39,11 @@ export class WorkflowService {
35
39
  const workflowSpan = store.get('workflowSpan');
36
40
  const COUNTER = store.get('counter');
37
41
  const execIndex = COUNTER.counter = COUNTER.counter + 1;
42
+ const prefix = options.prefix ?? '';
38
43
  //this is risky but MUST be allowed. Users MAY set the workflowId,
39
44
  //but if there is a naming collision, the data from the target entity will be used
40
45
  //as there is know way of knowing if the item was generated via a prior run of the workflow
41
- const childJobId = options.workflowId ?? `-${workflowId}-$${options.workflowName}${workflowDimension}-${execIndex}`;
46
+ const childJobId = options.workflowId ?? `${prefix}-${workflowId}-$${options.workflowName}${workflowDimension}-${execIndex}`;
42
47
  const parentWorkflowId = `${workflowId}-f`;
43
48
 
44
49
  const client = new Client({
@@ -70,7 +75,10 @@ export class WorkflowService {
70
75
  }
71
76
 
72
77
  /**
73
- * spawn a child workflow. return the childJobId.
78
+ * Spawns a child workflow. return the childJobId.
79
+ * This method is used when the result of the child workflow is not needed.
80
+ * @param {WorkflowOptions} options - the workflow options
81
+ * @returns {Promise<string>} - the childJobId
74
82
  */
75
83
  static async startChild<T>(options: WorkflowOptions): Promise<string> {
76
84
  const store = asyncLocalStorage.getStore();
@@ -81,7 +89,8 @@ export class WorkflowService {
81
89
  const workflowSpan = store.get('workflowSpan');
82
90
  const COUNTER = store.get('counter');
83
91
  const execIndex = COUNTER.counter = COUNTER.counter + 1;
84
- const childJobId = options.workflowId ?? `-${workflowId}-$${options.workflowName}${workflowDimension}-${execIndex}`;
92
+ const prefix = options.prefix ?? '';
93
+ const childJobId = options.workflowId ?? `${prefix}-${workflowId}-$${options.workflowName}${workflowDimension}-${execIndex}`;
85
94
  const parentWorkflowId = `${workflowId}-f`;
86
95
  const workflowTopic = `${options.taskQueue}-${options.workflowName}`;
87
96
 
@@ -108,7 +117,22 @@ export class WorkflowService {
108
117
  }
109
118
 
110
119
  /**
111
- * wrap all activities in a proxy that will durably run them
120
+ * Wraps activities in a proxy that will durably run them
121
+ * @param {ActivityConfig} options - the activity configuration
122
+ * that will be used to wrap the activities. You must pass an
123
+ * `activities` object to this configuration. The activities object
124
+ * should be a key-value pair of activity names and their respective
125
+ * functions. This is typically done by importing the activities.
126
+ *
127
+ * @returns {ProxyType<ACT>} - a proxy object with the same keys as the
128
+ * activities object, but with the values replaced by a wrapped function
129
+ * @example
130
+ * // import the activities
131
+ * import * as activities from './activities';
132
+ * const proxy = WorkflowService.proxyActivities<typeof activities>({ activities });
133
+ *
134
+ * //or destructure the proxy object, as the function names are the keys
135
+ * const { activity1, activity2 } = WorkflowService.proxyActivities<typeof activities>({ activities });
112
136
  */
113
137
  static proxyActivities<ACT>(options?: ActivityConfig): ProxyType<ACT> {
114
138
  if (options.activities) {
@@ -127,7 +151,9 @@ export class WorkflowService {
127
151
  }
128
152
 
129
153
  /**
130
- * return a search session for use when reading/writing to the workflow HASH
154
+ * Returns a search session for use when reading/writing to the workflow HASH.
155
+ * The search session provides access to methods like `get`, `mget`, `set`, `del`, and `incr`.
156
+ * @returns {Promise<Search>} - a search session
131
157
  */
132
158
  static async search(): Promise<Search> {
133
159
  const store = asyncLocalStorage.getStore();
@@ -144,7 +170,8 @@ export class WorkflowService {
144
170
  }
145
171
 
146
172
  /**
147
- * return a handle to the hotmesh client currently running the workflow
173
+ * Return a handle to the hotmesh client currently running the workflow
174
+ * @returns {Promise<HotMesh>} - a hotmesh client
148
175
  */
149
176
  static async getHotMesh(): Promise<HotMesh> {
150
177
  const store = asyncLocalStorage.getStore();
@@ -154,9 +181,34 @@ export class WorkflowService {
154
181
  }
155
182
 
156
183
  /**
157
- * those methods that may only be called once must be protected by flagging
184
+ * Returns the current workflow context
185
+ * @returns {WorkflowContext} - the current workflow context
186
+ */
187
+ static getContext(): WorkflowContext {
188
+ const store = asyncLocalStorage.getStore();
189
+ const workflowId = store.get('workflowId');
190
+ const workflowDimension = store.get('workflowDimension') ?? '';
191
+ const workflowTopic = store.get('workflowTopic');
192
+ const namespace = store.get('namespace');
193
+ const workflowTrace = store.get('workflowTrace');
194
+ const workflowSpan = store.get('workflowSpan');
195
+ const COUNTER = store.get('counter');
196
+ return {
197
+ counter: COUNTER.counter,
198
+ namespace,
199
+ workflowId,
200
+ workflowDimension,
201
+ workflowTopic,
202
+ workflowTrace,
203
+ workflowSpan,
204
+ };
205
+ }
206
+
207
+ /**
208
+ * Those methods that may only be called once must be protected by flagging
158
209
  * their execution with a unique key (the key is stored in the HASH alongside
159
210
  * process state and job state)
211
+ * @private
160
212
  */
161
213
  static async isSideEffectAllowed(hotMeshClient: HotMesh, prefix:string): Promise<boolean> {
162
214
  const store = asyncLocalStorage.getStore();
@@ -175,10 +227,10 @@ export class WorkflowService {
175
227
  }
176
228
 
177
229
  /**
178
- * returns a random number between 0 and 1. This number is deterministic
230
+ * Returns a random number between 0 and 1. This number is deterministic
179
231
  * and will never vary for a given seed. This is useful for randomizing
180
232
  * pathways in a workflow that can be safely replayed.
181
- * @returns {number}
233
+ * @returns {number} - a random number between 0 and 1
182
234
  */
183
235
  static random(): number {
184
236
  const store = asyncLocalStorage.getStore();
@@ -188,8 +240,11 @@ export class WorkflowService {
188
240
  }
189
241
 
190
242
  /**
191
- * send signal data into any other paused thread (which is paused and
243
+ * Sends signal data into any other paused thread (which is paused and
192
244
  * awaiting the signal) from within a hook-thread or the main-thread
245
+ * @param {string} signalId - the signal id
246
+ * @param {Record<any, any>} data - the signal data
247
+ * @returns {Promise<string>} - the stream id
193
248
  */
194
249
  static async signal(signalId: string, data: Record<any, any>): Promise<string> {
195
250
  const store = asyncLocalStorage.getStore();
@@ -204,9 +259,10 @@ export class WorkflowService {
204
259
  }
205
260
 
206
261
  /**
207
- * spawn a hook from either the main thread or a hook thread with
262
+ * Spawns a hook from either the main thread or a hook thread with
208
263
  * the provided options; worflowId/TaskQueue/Name are optional and will
209
264
  * default to the current workflowId/WorkflowTopic if not provided
265
+ * @param {HookOptions} options - the hook options
210
266
  */
211
267
  static async hook(options: HookOptions): Promise<string> {
212
268
  const store = asyncLocalStorage.getStore();
@@ -230,6 +286,11 @@ export class WorkflowService {
230
286
  }
231
287
  }
232
288
 
289
+ /**
290
+ * Sleeps for a duration.
291
+ * @param {string} duration - for example: '1 minute', '2 hours', '3 days'
292
+ * @returns {Promise<number>}
293
+ */
233
294
  static async sleep(duration: string): Promise<number> {
234
295
  const seconds = ms(duration) / 1000;
235
296
 
@@ -254,6 +315,12 @@ export class WorkflowService {
254
315
  }
255
316
  }
256
317
 
318
+ /**
319
+ * Waits for a signal to awaken
320
+ * @param {string[]} signals - the signals to wait for
321
+ * @param {Record<string, string>} options - the options
322
+ * @returns {Promise<Record<any, any>[]>}
323
+ */
257
324
  static async waitForSignal(signals: string[], options?: Record<string, string>): Promise<Record<any, any>[]> {
258
325
  const store = asyncLocalStorage.getStore();
259
326
  const COUNTER = store.get('counter');
package/types/durable.ts CHANGED
@@ -7,6 +7,44 @@ type WorkflowConfig = {
7
7
  initialInterval?: string; //default 1s
8
8
  }
9
9
 
10
+ type WorkflowContext = {
11
+
12
+ /**
13
+ * the reentrant semaphore, incremented in real-time as idempotent statements are re-traversed upon reentry. Indicates the current semaphore count.
14
+ */
15
+ counter: number;
16
+
17
+ /**
18
+ * the HotMesh App namespace. `durable` is the default.
19
+ */
20
+ namespace: string;
21
+
22
+ /**
23
+ * the workflow/job ID
24
+ */
25
+ workflowId: string;
26
+
27
+ /**
28
+ * the dimensional isolation for the reentrant hook, expressed in the format `0,0`, `0,1`, etc
29
+ */
30
+ workflowDimension: string;
31
+
32
+ /**
33
+ * a concatenation of the task queue and workflow name (e.g., `${taskQueueName}-${workflowName}`)
34
+ */
35
+ workflowTopic: string;
36
+
37
+ /**
38
+ * the open telemetry trace context for the workflow, used for logging and tracing. If a sink is enabled, this will be sent to the sink.
39
+ */
40
+ workflowTrace: string;
41
+
42
+ /**
43
+ * the open telemetry span context for the workflow, used for logging and tracing. If a sink is enabled, this will be sent to the sink.
44
+ */
45
+ workflowSpan: string;
46
+ }
47
+
10
48
  type WorkflowSearchOptions = {
11
49
  index?: string; //FT index name (myapp:myindex)
12
50
  prefix?: string[]; //FT prefixes (['myapp:myindex:prefix1', 'myapp:myindex:prefix2'])
@@ -15,11 +53,12 @@ type WorkflowSearchOptions = {
15
53
  }
16
54
 
17
55
  type WorkflowOptions = {
18
- namespace?: string; //'durable' is the default namespace if not provided; similar to setting `appid` in the YAML
56
+ namespace?: string; //'durable' is the default namespace if not provided; similar to setting `appid` in the YAML
19
57
  taskQueue: string;
20
- args: any[]; //input arguments to pass in
21
- workflowId?: string; //execution id (the job id)
22
- workflowName?: string; //the name of the user's workflow function
58
+ args: any[]; //input arguments to pass in
59
+ workflowId?: string; //execution id (the job id)
60
+ prefix?: string; //If invoking a hook, the prefix ensures the FT.SEARCH index is properly scoped to those documents with this prefix
61
+ workflowName?: string; //the name of the user's workflow function
23
62
  parentWorkflowId?: string; //system reserved; the id of the parent; if present the flow will not self-clean until the parent that spawned it self-cleans
24
63
  workflowTrace?: string;
25
64
  workflowSpan?: string;
@@ -188,4 +227,5 @@ export {
188
227
  WorkflowSearchOptions,
189
228
  WorkflowDataType,
190
229
  WorkflowOptions,
230
+ WorkflowContext,
191
231
  };
package/types/index.ts CHANGED
@@ -49,6 +49,7 @@ export {
49
49
  WorkflowConfig,
50
50
  WorkerConfig,
51
51
  WorkerOptions,
52
+ WorkflowContext,
52
53
  WorkflowSearchOptions,
53
54
  WorkflowDataType,
54
55
  WorkflowOptions,