@hotmeshio/hotmesh 0.0.31 → 0.0.32

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
@@ -1,13 +1,9 @@
1
1
  # HotMesh
2
2
  ![alpha release](https://img.shields.io/badge/release-alpha-yellow)
3
3
 
4
- Elevate Redis from an in-memory data cache, and turn your unpredictable functions into unbreakable workflows.
4
+ Elevate Redis from an in-memory data cache, and turn your unpredictable functions into unbreakable workflows. HotMesh is a distributed orchestration engine that governs the execution of your functions, ensuring they always complete successfully.
5
5
 
6
- **HotMesh** is a wrapper for Redis that exposes concepts like ‘activities’, ‘workflows’, and 'jobs'. Behind the scenes, it uses *Redis Data* (Hash, ZSet, List); *Redis Streams* (XReadGroup, XAdd, XLen, etc); and *Redis Publish/Subscribe*.
7
-
8
- It's still Redis in the background, but your functions are run as *reentrant processes* and are executed in a distributed environment, with all the benefits of a distributed system, including fault tolerance, scalability, and high availability.
9
-
10
- Write functions in your own preferred style, and let Redis govern their execution with its unmatched performance.
6
+ *Write functions in your own preferred style, and let Redis govern their execution.*
11
7
 
12
8
  ## Install
13
9
  [![npm version](https://badge.fury.io/js/%40hotmeshio%2Fhotmesh.svg)](https://badge.fury.io/js/%40hotmeshio%2Fhotmesh)
@@ -15,82 +11,89 @@ Write functions in your own preferred style, and let Redis govern their executio
15
11
  ```sh
16
12
  npm install @hotmeshio/hotmesh
17
13
  ```
18
-
19
14
  ## Design
20
- 1. Start with any ordinary class. Pay attention to unpredictable functions: those that execute slowly, cause problems at scale, or simply fail to return now and then. *Note how the `flaky` function throws an error 50% of the time. This is exactly the type of function that can be fixed using HotMesh.*
21
- ```javascript
22
- //myworkflow.ts
23
-
24
- export class MyWorkflow {
25
-
26
- async run(name: string): Promise<string> {
27
- const hi = await this.flaky(name);
28
- const hello = await this.greet(name);
29
- return `${hi} ${hello}`;
30
- }
31
15
 
32
- //this function is unpredictable and will fail 50% of the time
33
- async flaky(name: string): Promise<string> {
34
- if (Math.random() < 0.5) {
35
- throw new Error('Ooops!');
36
- }
37
- return `Hi, ${name}!`;
38
- }
16
+ 1. Start by defining **activities**. Activities can be written in any style, using any framework, and can even be legacy functions you've already written. The only requirement is that they return a Promise. *Note how the `saludar` example throws an error 50% of the time. It doesn't matter how unpredictable your functions are, HotMesh will retry as necessary until they succeed.*
17
+ ```javascript
18
+ //activities.ts
19
+ export async function greet(name: string): Promise<string> {
20
+ return `Hello, ${name}!`;
21
+ }
39
22
 
40
- async greet(name: string): Promise<string> {
41
- return `Hello, ${name}!`;
23
+ export async function saludar(nombre: string): Promise<string> {
24
+ if (Math.random() > 0.5) throw new Error('Random error');
25
+ return `¡Hola, ${nombre}!`;
26
+ }
27
+ ```
28
+ 2. Define your **workflow** logic. Include conditional branching, loops, etc to control activity execution. It's vanilla code written in your own coding style. The only requirement is to use `proxyActivities`, ensuring your activities are executed with HotMesh's durability guarantee.
29
+ ```javascript
30
+ //workflows.ts
31
+ import { Durable } from '@hotmeshio/hotmesh';
32
+ import * as activities from './activities';
33
+
34
+ const { greet, saludar } = Durable.workflow
35
+ .proxyActivities<typeof activities>({
36
+ activities
37
+ });
38
+
39
+ export async function example(name: string, lang: string): Promise<string> {
40
+ if (lang === 'es') {
41
+ return await saludar(name);
42
+ } else {
43
+ return await greet(name);
42
44
  }
43
45
  }
44
46
  ```
45
- 2. Import and configure `Redis` and `MeshOS` as shown. List those functions that Redis should govern as durable workflows (like `run` and `flaky`). And that's it! *Your functions don't actually change; rather, their governance does.*
47
+ 3. Although you could call your workflow directly (it's just a vanilla function), it's only durable when invoked and orchestrated via HotMesh.
46
48
  ```javascript
47
- //myworkflow.ts
48
-
49
+ //client.ts
50
+ import { Durable } from '@hotmeshio/hotmesh';
49
51
  import Redis from 'ioredis'; //OR `import * as Redis from 'redis';`
50
- import { MeshOS } from '@hotmeshio/hotmesh';
51
-
52
- export class MyWorkflow extends MeshOS {
53
-
54
- //configure Redis
55
- redisClass = Redis;
56
- redisOptions = { host: 'localhost', port: 6379 };
57
-
58
- //list functions to run as durable workflows
59
- workflowFunctions = ['run'];
60
-
61
- //list functions to retry and cache
62
- proxyFunctions = ['flaky'];
63
-
64
- //no need to change anything else!
65
-
66
- async run(name: string): Promise<string> {
67
- const hi = await this.flaky(name);
68
- const hello = await this.greet(name);
69
- return `${hi} ${hello}`;
70
- }
52
+ import { nanoid } from 'nanoid';
71
53
 
72
- async flaky(name: string): Promise<string> {
73
- if (Math.random() < 0.5) {
74
- throw new Error('Ooops!');
54
+ async function run(): Promise<string> {
55
+ const client = new Durable.Client({
56
+ connection: {
57
+ class: Redis,
58
+ options: { host: 'localhost', port: 6379 }
75
59
  }
76
- return `Hi, ${name}!`;
77
- }
60
+ });
78
61
 
79
- async greet(name: string): Promise<string> {
80
- return `Hello, ${name}!`;
81
- }
62
+ const handle = await client.workflow.start({
63
+ args: ['HotMesh', 'es'],
64
+ taskQueue: 'hello-world',
65
+ workflowName: 'example',
66
+ workflowId: nanoid()
67
+ });
68
+
69
+ return await handle.result();
70
+ //returns '¡Hola, HotMesh!'
82
71
  }
83
72
  ```
84
- 3. Invoke your class, providing a unique id (it's now an idempotent workflow and needs a GUID). Nothing changes from the outside, *but Redis now governs the end-to-end execution.* It's guaranteed to succeed, even if it breaks a few times along the way.
73
+ 4. Finally, create a **worker** and link your workflow function. Workers listen for tasks on their assigned Redis stream and invoke your workflow function each time they receive an event.
85
74
  ```javascript
86
- //mycaller.ts
87
-
88
- const workflow = new MyWorkflow({ id: 'my123', await: true });
89
- const response = await workflow.run('World');
90
- //Hi, World! Hello, World!
75
+ //worker.ts
76
+ import { Durable } from '@hotmeshio/hotmesh';
77
+ import Redis from 'ioredis';
78
+ import * as workflows from './workflows';
79
+
80
+ async function run() {
81
+ const worker = await Durable.Worker.create({
82
+ connection: {
83
+ class: Redis,
84
+ options: { host: 'localhost', port: 6379 },
85
+ },
86
+ taskQueue: 'hello-world',
87
+ workflow: workflows.example,
88
+ });
89
+
90
+ await worker.run();
91
+ }
91
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
+ 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 `Durable.workflow` base class (shown in the examples above) provides additional methods for solving the most common state management challenges.
95
+
96
+ Include statements like `await Durable.workflow.sleep('1 month')` to pause your workflow function for a month, or `await Durable.workflow.waitForSignal('signal1', 'signal2')` to pause your function until all signals have arrived.
94
97
 
95
98
  - `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
99
  - `signal` | Send a signal (and optional payload) to any paused function.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.31",
3
+ "version": "0.0.32",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -76,8 +76,8 @@ class ClientService {
76
76
  };
77
77
  this.workflow = {
78
78
  start: async (options) => {
79
- const taskQueueName = options.taskQueue;
80
- const workflowName = options.workflowName;
79
+ const taskQueueName = options.entity ?? options.taskQueue;
80
+ const workflowName = options.entity ?? options.workflowName;
81
81
  const trc = options.workflowTrace;
82
82
  const spn = options.workflowSpan;
83
83
  //topic is concat of taskQueue and workflowName
@@ -225,7 +225,7 @@ class StreamSignaler {
225
225
  this.shouldConsume = false;
226
226
  this.logger.info(`stream-consumer-stopping`, this.topic ? { topic: this.topic } : undefined);
227
227
  this.cancelThrottle();
228
- await (0, utils_1.sleepFor)(BLOCK_TIME_MS);
228
+ //await sleepFor(BLOCK_TIME_MS);
229
229
  }
230
230
  cancelThrottle() {
231
231
  if (this.currentTimerId !== undefined) {
@@ -46,7 +46,7 @@ type WorkflowSearchOptions = {
46
46
  };
47
47
  type WorkflowOptions = {
48
48
  namespace?: string;
49
- taskQueue: string;
49
+ taskQueue?: string;
50
50
  args: any[];
51
51
  workflowId?: string;
52
52
  entity?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.31",
3
+ "version": "0.0.32",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -92,8 +92,8 @@ export class ClientService {
92
92
 
93
93
  workflow = {
94
94
  start: async (options: WorkflowOptions): Promise<WorkflowHandleService> => {
95
- const taskQueueName = options.taskQueue;
96
- const workflowName = options.workflowName;
95
+ const taskQueueName = options.entity ?? options.taskQueue;
96
+ const workflowName = options.entity ?? options.workflowName;
97
97
  const trc = options.workflowTrace;
98
98
  const spn = options.workflowSpan;
99
99
  //topic is concat of taskQueue and workflowName
@@ -260,7 +260,7 @@ class StreamSignaler {
260
260
  this.shouldConsume = false;
261
261
  this.logger.info(`stream-consumer-stopping`, this.topic ? { topic: this.topic } : undefined);
262
262
  this.cancelThrottle();
263
- await sleepFor(BLOCK_TIME_MS);
263
+ //await sleepFor(BLOCK_TIME_MS);
264
264
  }
265
265
 
266
266
  cancelThrottle() {
package/types/durable.ts CHANGED
@@ -54,11 +54,11 @@ type WorkflowSearchOptions = {
54
54
 
55
55
  type WorkflowOptions = {
56
56
  namespace?: string; //'durable' is the default namespace if not provided; similar to setting `appid` in the YAML
57
- taskQueue: string;
57
+ taskQueue?: string; //optional if entity is provided
58
58
  args: any[]; //input arguments to pass in
59
59
  workflowId?: string; //execution id (the job id)
60
60
  entity?: string; //If invoking a workflow, passing 'entity' will apply the value as the workflowName, taskQueue, and prefix, ensuring the FT.SEARCH index is properly scoped. This is a convenience method but limits options.
61
- workflowName?: string; //the name of the user's workflow function
61
+ workflowName?: string; //the name of the user's workflow function; optional if 'entity' is provided
62
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
63
63
  workflowTrace?: string;
64
64
  workflowSpan?: string;
@@ -68,7 +68,7 @@ type WorkflowOptions = {
68
68
 
69
69
  type HookOptions = {
70
70
  namespace?: string; //'durable' is the default namespace if not provided; similar to setting `appid` in the YAML
71
- taskQueue?: string;
71
+ taskQueue?: string; //optional if 'entity' is provided
72
72
  args: any[]; //input arguments to pass into the hook
73
73
  entity?: string; //If invoking a hook, passing 'entity' will apply the value as the workflowName, taskQueue, and prefix, ensuring the FT.SEARCH index is properly scoped. This is a convenience method but limits options.
74
74
  workflowId?: string; //execution id (the job id to hook into)