@hotmeshio/hotmesh 0.0.22 → 0.0.24
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 +66 -67
- package/build/index.d.ts +2 -1
- package/build/index.js +3 -1
- package/build/package.json +2 -1
- package/build/services/durable/connection.js +0 -39
- package/build/services/durable/factory.d.ts +6 -4
- package/build/services/durable/factory.js +12 -10
- package/build/services/durable/handle.d.ts +3 -0
- package/build/services/durable/handle.js +15 -5
- package/build/services/durable/index.d.ts +2 -45
- package/build/services/durable/index.js +2 -45
- package/build/services/durable/meshos.d.ts +108 -0
- package/build/services/durable/meshos.js +289 -0
- package/build/services/durable/search.d.ts +9 -0
- package/build/services/durable/search.js +34 -2
- package/build/services/durable/worker.d.ts +2 -10
- package/build/services/durable/worker.js +10 -39
- package/build/services/durable/workflow.d.ts +12 -1
- package/build/services/durable/workflow.js +32 -16
- package/build/services/engine/index.d.ts +2 -0
- package/build/services/engine/index.js +3 -0
- package/build/services/hotmesh/index.d.ts +3 -1
- package/build/services/hotmesh/index.js +5 -2
- package/build/services/signaler/stream.js +1 -2
- package/build/services/store/clients/ioredis.js +2 -2
- package/build/services/store/clients/redis.js +1 -1
- package/build/services/store/index.d.ts +5 -0
- package/build/services/store/index.js +14 -0
- package/build/types/durable.d.ts +34 -4
- package/build/types/index.d.ts +1 -1
- package/index.ts +2 -1
- package/package.json +2 -1
- package/services/durable/connection.ts +0 -40
- package/services/durable/factory.ts +12 -10
- package/services/durable/handle.ts +18 -5
- package/services/durable/index.ts +2 -46
- package/services/durable/meshos.ts +344 -0
- package/services/durable/search.ts +35 -2
- package/services/durable/worker.ts +11 -42
- package/services/durable/workflow.ts +34 -17
- package/services/engine/index.ts +4 -1
- package/services/hotmesh/index.ts +6 -2
- package/services/signaler/stream.ts +1 -2
- package/services/store/clients/ioredis.ts +2 -3
- package/services/store/clients/redis.ts +1 -1
- package/services/store/index.ts +15 -0
- package/types/durable.ts +40 -4
- package/types/index.ts +6 -1
- package/build/services/durable/native.d.ts +0 -4
- package/build/services/durable/native.js +0 -46
- package/services/durable/native.ts +0 -45
package/README.md
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
|
|
4
4
|
Elevate Redis from an in-memory data cache to a game-changing [service mesh](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/faq.md#what-is-hotmesh). Turn your unpredictable functions into unbreakable workflows.
|
|
5
5
|
|
|
6
|
+
## What is HotMesh?
|
|
7
|
+
HotMesh is a wrapper for Redis that exposes a higher level set of domain constructs like ‘activities’, ‘workflows’, 'jobs', etc. Behind the scenes, it uses *Redis Data* (Hash, ZSet, and List); *Redis Streams* (XReadGroup, XAdd, XLen); and *Redis Publish/Subscribe*.
|
|
8
|
+
|
|
9
|
+
The ultimate goal is to resurface Redis as a *Durable Service Mesh*, capable of running unbreakable workflows that span your microservices. The technical term for this type of durability is *Reentrant Process Engine*.
|
|
10
|
+
|
|
11
|
+
It's still Redis in the background, but the information flow is fundamentally different. Your functions no longer call Redis (e.g., to cache a document) and instead are called by Redis.
|
|
12
|
+
|
|
6
13
|
## Install
|
|
7
14
|
[](https://badge.fury.io/js/%40hotmeshio%2Fhotmesh)
|
|
8
15
|
|
|
@@ -11,92 +18,84 @@ npm install @hotmeshio/hotmesh
|
|
|
11
18
|
```
|
|
12
19
|
|
|
13
20
|
## Design
|
|
14
|
-
The HotMesh SDK is designed to keep your code front-and-center. Write
|
|
21
|
+
The HotMesh SDK is designed to keep your code front-and-center. Write code as you normally would, then use HotMesh to make it durable.
|
|
15
22
|
|
|
16
|
-
1. Start
|
|
23
|
+
1. Start with any ordinary class. Pay attention to unpredictable functions: those that execute slowly, cause problems at scale, or simply fail to return. *Note how the `saludar` function throws an error 50% of the time. This is exactly the type of function to fix.*
|
|
17
24
|
```javascript
|
|
18
|
-
//
|
|
25
|
+
//myworkflow.ts
|
|
19
26
|
|
|
20
|
-
export
|
|
21
|
-
return `Hello, ${name}!`;
|
|
22
|
-
}
|
|
27
|
+
export class MyWorkflow {
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
```javascript
|
|
31
|
-
//workflows.ts
|
|
32
|
-
|
|
33
|
-
import { Durable } from '@hotmeshio/hotmesh';
|
|
34
|
-
import * as activities from './activities';
|
|
29
|
+
//main method
|
|
30
|
+
async run(name: string): Promise<string> {
|
|
31
|
+
const salud = await this.saludar(name);
|
|
32
|
+
const hello = await this.greet(name);
|
|
33
|
+
return `${hello} ${salud}`;
|
|
34
|
+
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
//this function only succeeds 50% of the time!
|
|
37
|
+
async saludar(nombre: string): Promise<string> {
|
|
38
|
+
if (Math.random() < 0.5) {
|
|
39
|
+
throw new Error('¡No hablo español!');
|
|
40
|
+
}
|
|
41
|
+
return `¡Hola, ${nombre}!`;
|
|
42
|
+
}
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return
|
|
44
|
-
} else {
|
|
45
|
-
return await greet(name);
|
|
44
|
+
//this function always succeeds
|
|
45
|
+
async greet(name: string): Promise<string> {
|
|
46
|
+
return `Hello, ${name}!`;
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
```
|
|
49
|
-
|
|
50
|
-
3. Although you could call your workflow directly (it's just a vanilla function), it's only durable when invoked and orchestrated via HotMesh.
|
|
50
|
+
2. Import `MeshOS` and subclass it as shown. Configure `host`, `port`, etc., and list those functions that should run durably like `run` and `saludar`. Your class is now a durable workflow! *Additional helper methods include `waitForSignal`, `signal`, `hook`, `sleep` (months, years, etc), `executeChild`, `startChild`, `get`, `set`, `incr` (increment), and `mult` (multiply).*
|
|
51
51
|
```javascript
|
|
52
|
-
//
|
|
52
|
+
//myworkflow.ts
|
|
53
53
|
|
|
54
|
-
import { Durable } from '@hotmeshio/hotmesh';
|
|
55
54
|
import Redis from 'ioredis'; //OR `import * as Redis from 'redis';`
|
|
56
|
-
import {
|
|
55
|
+
import { MeshOS } from '@hotmeshio/hotmesh';
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
const client = new Durable.Client({
|
|
60
|
-
connection: {
|
|
61
|
-
class: Redis,
|
|
62
|
-
options: { host: 'localhost', port: 6379 }
|
|
63
|
-
}
|
|
64
|
-
});
|
|
57
|
+
export class MyWorkflow extends MeshOS {
|
|
65
58
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
taskQueue: 'hello-world',
|
|
69
|
-
workflowName: 'example',
|
|
70
|
-
workflowId: nanoid()
|
|
71
|
-
});
|
|
59
|
+
redisClass = Redis;
|
|
60
|
+
redisOptions = { host: 'localhost', port: 6379 };
|
|
72
61
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
```
|
|
62
|
+
//list functions to run as durable workflows
|
|
63
|
+
workflowFunctions = ['run'];
|
|
77
64
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
65
|
+
//list functions to retry and cache
|
|
66
|
+
proxyFunctions = ['saludar'];
|
|
67
|
+
|
|
68
|
+
//main method (Redis will now govern its execution)
|
|
69
|
+
async run(name: string): Promise<string> {
|
|
70
|
+
const salud = await this.saludar(name);
|
|
71
|
+
const hello = await this.greet(name);
|
|
72
|
+
return `${hello} ${salud}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
//this function will now be retried until it succeeds;
|
|
76
|
+
async saludar(nombre: string): Promise<string> {
|
|
77
|
+
if (Math.random() < 0.5) {
|
|
78
|
+
throw new Error('¡No hablo español!');
|
|
79
|
+
}
|
|
80
|
+
return `¡Hola, ${nombre}!`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
//this vanilla function is unchanged
|
|
84
|
+
async greet(name: string): Promise<string> {
|
|
85
|
+
return `Hello, ${name}!`;
|
|
86
|
+
}
|
|
96
87
|
}
|
|
97
88
|
```
|
|
89
|
+
3. Invoke your class, providing a unique GUID (it's now a workflow and it's idempotent). Nothing changes from the outside, *but Redis now governs its end-to-end execution.* It's guaranteed to succeed and return regardless of catastrophic network failure. Redis will simply inflate like a balloon and deflate as things come back online.
|
|
90
|
+
```javascript
|
|
91
|
+
//mycaller.ts
|
|
92
|
+
|
|
93
|
+
//...import MyWorkflow, etc...
|
|
98
94
|
|
|
99
|
-
|
|
95
|
+
const workflow = new MyWorkflow('unique123', { await: true }); //await the response
|
|
96
|
+
const response = await workflow.run('John');
|
|
97
|
+
//Hello, John! ¡Hola, John!
|
|
98
|
+
```
|
|
100
99
|
|
|
101
100
|
## Advanced Design
|
|
102
101
|
HotMesh's TypeScript SDK is the easiest way to make your functions durable. But if you need full control over your function lifecycles (including high-volume, high-speed use cases), you can use HotMesh's underlying YAML models to optimize your durable workflows. The following model depicts a sequence of activities orchestrated by HotMesh. Any function you associate with a `topic` in your YAML definition is guaranteed to be durable.
|
package/build/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Durable } from './services/durable';
|
|
2
|
+
import { MeshOSService as MeshOS } from './services/durable/meshos';
|
|
2
3
|
import { HotMeshService as HotMesh } from './services/hotmesh';
|
|
3
4
|
import { HotMeshConfig } from './types/hotmesh';
|
|
4
|
-
export { Durable, HotMesh, HotMeshConfig };
|
|
5
|
+
export { Durable, HotMesh, HotMeshConfig, MeshOS };
|
package/build/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.HotMesh = exports.Durable = void 0;
|
|
3
|
+
exports.MeshOS = exports.HotMesh = exports.Durable = void 0;
|
|
4
4
|
const durable_1 = require("./services/durable");
|
|
5
5
|
Object.defineProperty(exports, "Durable", { enumerable: true, get: function () { return durable_1.Durable; } });
|
|
6
|
+
const meshos_1 = require("./services/durable/meshos");
|
|
7
|
+
Object.defineProperty(exports, "MeshOS", { enumerable: true, get: function () { return meshos_1.MeshOSService; } });
|
|
6
8
|
const hotmesh_1 = require("./services/hotmesh");
|
|
7
9
|
Object.defineProperty(exports, "HotMesh", { enumerable: true, get: function () { return hotmesh_1.HotMeshService; } });
|
package/build/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotmeshio/hotmesh",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
4
|
"description": "Unbreakable Workflows",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"test:sub:redis": "NODE_ENV=test jest ./tests/functional/sub/clients/redis.test.ts --detectOpenHandles --forceExit --verbose",
|
|
44
44
|
"test:sub:ioredis": "NODE_ENV=test jest ./tests/functional/sub/clients/ioredis.test.ts --detectOpenHandles --forceExit --verbose",
|
|
45
45
|
"test:durable": "NODE_ENV=test jest ./tests/durable/*/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
46
|
+
"test:durable:meshos": "NODE_ENV=test jest ./tests/durable/meshos/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
46
47
|
"test:durable:hello": "NODE_ENV=test jest ./tests/durable/helloworld/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
47
48
|
"test:durable:goodbye": "NODE_ENV=test jest ./tests/durable/goodbye/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
48
49
|
"test:durable:hook": "NODE_ENV=test jest ./tests/durable/hook/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
@@ -1,45 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ConnectionService = void 0;
|
|
4
|
-
/*
|
|
5
|
-
Here is an example of how the methods in this file are used:
|
|
6
|
-
|
|
7
|
-
./client.ts
|
|
8
|
-
|
|
9
|
-
import { Durable } from '@hotmeshio/hotmesh';
|
|
10
|
-
import Redis from 'ioredis';
|
|
11
|
-
import { nanoid } from 'nanoid';
|
|
12
|
-
|
|
13
|
-
async function run() {
|
|
14
|
-
const connection = await Durable.Connection.connect({
|
|
15
|
-
class: Redis,
|
|
16
|
-
options: {
|
|
17
|
-
host: 'localhost',
|
|
18
|
-
port: 6379,
|
|
19
|
-
},
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
const client = new Durable.Client({
|
|
23
|
-
connection,
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
const handle = await client.workflow.start(example, {
|
|
27
|
-
taskQueue: 'hello-world',
|
|
28
|
-
args: ['HotMesh'],
|
|
29
|
-
workflowName: 'example',
|
|
30
|
-
workflowId: nanoid(),
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
console.log(`Started workflow ${handle.workflowId}`);
|
|
34
|
-
console.log(await handle.result());
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
run().catch((err) => {
|
|
38
|
-
console.error(err);
|
|
39
|
-
process.exit(1);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
*/
|
|
43
4
|
class ConnectionService {
|
|
44
5
|
static async connect(config) {
|
|
45
6
|
return {
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
* NOTE: Using `maxSystemRetries = 3` and `backoffCoefficient = 10`, errant
|
|
3
3
|
* workflows will be retried on the following schedule (8 times in 27 hours):
|
|
4
4
|
* => 10ms, 100ms, 1000ms, 10s, 100s, 1_000s, 10_000s, 100_000s
|
|
5
|
+
* TODO: Max Interval, Min Interval, Initial Interval
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* ERROR CODES:
|
|
8
|
+
* 594: waitforsignal
|
|
9
|
+
* 595: sleep
|
|
10
|
+
* 596, 597, 598: fatal
|
|
11
|
+
* 599: retry
|
|
10
12
|
*/
|
|
11
13
|
declare const getWorkflowYAML: (app: string, version: string) => string;
|
|
12
14
|
declare const APP_VERSION = "1";
|
|
@@ -5,11 +5,13 @@ exports.DEFAULT_COEFFICIENT = exports.APP_ID = exports.APP_VERSION = exports.get
|
|
|
5
5
|
* NOTE: Using `maxSystemRetries = 3` and `backoffCoefficient = 10`, errant
|
|
6
6
|
* workflows will be retried on the following schedule (8 times in 27 hours):
|
|
7
7
|
* => 10ms, 100ms, 1000ms, 10s, 100s, 1_000s, 10_000s, 100_000s
|
|
8
|
+
* TODO: Max Interval, Min Interval, Initial Interval
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* ERROR CODES:
|
|
11
|
+
* 594: waitforsignal
|
|
12
|
+
* 595: sleep
|
|
13
|
+
* 596, 597, 598: fatal
|
|
14
|
+
* 599: retry
|
|
13
15
|
*/
|
|
14
16
|
const getWorkflowYAML = (app, version) => {
|
|
15
17
|
return `app:
|
|
@@ -265,11 +267,11 @@ const getWorkflowYAML = (app, version) => {
|
|
|
265
267
|
- ['{@string.concat}']
|
|
266
268
|
cycleWorkflowId:
|
|
267
269
|
'@pipe':
|
|
268
|
-
- ['{$job.metadata.jid}', '-$wfc', '{sig.output.metadata.dad}', '-', '{sigw1.output.data.index}']
|
|
270
|
+
- ['-', '{$job.metadata.jid}', '-$wfc', '{sig.output.metadata.dad}', '-', '{sigw1.output.data.index}']
|
|
269
271
|
- ['{@string.concat}']
|
|
270
272
|
baseWorkflowId:
|
|
271
273
|
'@pipe':
|
|
272
|
-
- ['{$job.metadata.jid}', '-$wfs', '{sig.output.metadata.dad}', '-']
|
|
274
|
+
- ['-', '{$job.metadata.jid}', '-$wfs', '{sig.output.metadata.dad}', '-']
|
|
273
275
|
- ['{@string.concat}']
|
|
274
276
|
output:
|
|
275
277
|
schema:
|
|
@@ -313,7 +315,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
313
315
|
- ['{@string.concat}']
|
|
314
316
|
workflowId:
|
|
315
317
|
'@pipe':
|
|
316
|
-
- ['{$job.metadata.jid}', '-$sleep', '{sig.output.metadata.dad}', '-', '{sigw1.output.data.index}']
|
|
318
|
+
- ['-', '{$job.metadata.jid}', '-$sleep', '{sig.output.metadata.dad}', '-', '{sigw1.output.data.index}']
|
|
317
319
|
- ['{@string.concat}']
|
|
318
320
|
output:
|
|
319
321
|
schema:
|
|
@@ -383,11 +385,11 @@ const getWorkflowYAML = (app, version) => {
|
|
|
383
385
|
- ['{@string.concat}']
|
|
384
386
|
cycleWorkflowId:
|
|
385
387
|
'@pipe':
|
|
386
|
-
- ['{$job.metadata.jid}', '-$wfc-', '{w1.output.data.index}']
|
|
388
|
+
- ['-', '{$job.metadata.jid}', '-$wfc-', '{w1.output.data.index}']
|
|
387
389
|
- ['{@string.concat}']
|
|
388
390
|
baseWorkflowId:
|
|
389
391
|
'@pipe':
|
|
390
|
-
- ['{$job.metadata.jid}', '-$wfs-']
|
|
392
|
+
- ['-', '{$job.metadata.jid}', '-$wfs-']
|
|
391
393
|
- ['{@string.concat}']
|
|
392
394
|
output:
|
|
393
395
|
schema:
|
|
@@ -431,7 +433,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
431
433
|
- ['{@string.concat}']
|
|
432
434
|
workflowId:
|
|
433
435
|
'@pipe':
|
|
434
|
-
- ['{$job.metadata.jid}', '-$sleep-', '{w1.output.data.index}']
|
|
436
|
+
- ['-', '{$job.metadata.jid}', '-$sleep-', '{w1.output.data.index}']
|
|
435
437
|
- ['{@string.concat}']
|
|
436
438
|
output:
|
|
437
439
|
schema:
|
|
@@ -5,5 +5,8 @@ export declare class WorkflowHandleService {
|
|
|
5
5
|
workflowId: string;
|
|
6
6
|
constructor(hotMesh: HotMesh, workflowTopic: string, workflowId: string);
|
|
7
7
|
signal(signalId: string, data: Record<any, any>): Promise<void>;
|
|
8
|
+
state(metadata?: boolean): Promise<Record<string, any>>;
|
|
9
|
+
queryState(fields: string[]): Promise<Record<string, any>>;
|
|
10
|
+
status(): Promise<number>;
|
|
8
11
|
result(loadState?: boolean): Promise<any>;
|
|
9
12
|
}
|
|
@@ -10,6 +10,19 @@ class WorkflowHandleService {
|
|
|
10
10
|
async signal(signalId, data) {
|
|
11
11
|
await this.hotMesh.hook(`${this.hotMesh.appId}.wfs.signal`, { id: signalId, data });
|
|
12
12
|
}
|
|
13
|
+
async state(metadata = false) {
|
|
14
|
+
const state = await this.hotMesh.getState(`${this.hotMesh.appId}.execute`, this.workflowId);
|
|
15
|
+
if (!state.data && state.metadata.err) {
|
|
16
|
+
throw new Error(JSON.parse(state.metadata.err));
|
|
17
|
+
}
|
|
18
|
+
return metadata ? state : state.data;
|
|
19
|
+
}
|
|
20
|
+
async queryState(fields) {
|
|
21
|
+
return await this.hotMesh.getQueryState(this.workflowId, fields);
|
|
22
|
+
}
|
|
23
|
+
async status() {
|
|
24
|
+
return await this.hotMesh.getStatus(this.workflowId);
|
|
25
|
+
}
|
|
13
26
|
async result(loadState) {
|
|
14
27
|
if (loadState) {
|
|
15
28
|
const state = await this.hotMesh.getState(`${this.hotMesh.appId}.execute`, this.workflowId);
|
|
@@ -17,11 +30,8 @@ class WorkflowHandleService {
|
|
|
17
30
|
throw new Error(JSON.parse(state.metadata.err));
|
|
18
31
|
}
|
|
19
32
|
if (state?.data?.done) {
|
|
20
|
-
//child flows are never
|
|
21
|
-
//that
|
|
22
|
-
//the 'done' flag on the child flow's payload (not the 'js' metadata field
|
|
23
|
-
//which is typically used); the loadState parameter ensures this
|
|
24
|
-
//check happens early
|
|
33
|
+
//child flows are never 'done'; they use a hook
|
|
34
|
+
//that only closes upon parent flow completion.
|
|
25
35
|
return state.data.response;
|
|
26
36
|
}
|
|
27
37
|
}
|
|
@@ -1,56 +1,13 @@
|
|
|
1
1
|
import { ClientService } from './client';
|
|
2
2
|
import { ConnectionService } from './connection';
|
|
3
|
-
import {
|
|
3
|
+
import { MeshOSService } from './meshos';
|
|
4
4
|
import { WorkerService } from './worker';
|
|
5
5
|
import { WorkflowService } from './workflow';
|
|
6
6
|
import { ContextType } from '../../types/durable';
|
|
7
|
-
/**
|
|
8
|
-
* As a durable integration platform, HotMesh
|
|
9
|
-
* can model and emulate other durable systems
|
|
10
|
-
* (like Temporal). As you review the code in
|
|
11
|
-
* this file, note the following:
|
|
12
|
-
*
|
|
13
|
-
* 1) There is no central governing server.
|
|
14
|
-
* HotMesh is a client-side SDK that connects to Redis
|
|
15
|
-
* using CQRS principles to implicitly drive
|
|
16
|
-
* orchestrations using a headless quorum. Stream
|
|
17
|
-
* semantics guarantee that all events are
|
|
18
|
-
* processed by the quorum of connected clients.
|
|
19
|
-
*
|
|
20
|
-
* 2) Every developer-defined `workflow` function
|
|
21
|
-
* is assigned a HotMesh workflow (which runs in
|
|
22
|
-
* the background) to support it.
|
|
23
|
-
*
|
|
24
|
-
* If the HotMesh workflow is not yet defined,
|
|
25
|
-
* it will be deployed and activated on-the-fly.
|
|
26
|
-
* (The generated DAG will have one Trigger Activity
|
|
27
|
-
* and one Worker Activity.) The Worker Activity
|
|
28
|
-
* is configured to catch execution errors and
|
|
29
|
-
* return them, using a 'pending' status to
|
|
30
|
-
* indicate that the workflow is still running.
|
|
31
|
-
* It is possible for workflow activities to throw
|
|
32
|
-
* errors that will force the entire workflow to
|
|
33
|
-
* fail. This is not the case here. The workflow will
|
|
34
|
-
* continue to run until it is completed or cancelled.
|
|
35
|
-
*
|
|
36
|
-
* 2) Every developer-defined `activity` function
|
|
37
|
-
* is assigned a HotMesh workflow (which runs in
|
|
38
|
-
* the background) to support it.
|
|
39
|
-
*
|
|
40
|
-
* (The generated DAG will have one Trigger Activity and one
|
|
41
|
-
* Worker Activity.) The JOB ID for Worker Activity executions
|
|
42
|
-
* is derived from the containing JOB ID,
|
|
43
|
-
* allowing Activity state to be 'replayed' when the workflow
|
|
44
|
-
* is run again. The Activity Function Runner (activity proxy)
|
|
45
|
-
* is configured similar to how the workflow worker is and will
|
|
46
|
-
* catch execution errors and return them to the caller, using a
|
|
47
|
-
* 'pending' status to indicate that the activity is still running.
|
|
48
|
-
* This allows the activity to be retried until it succeeds.
|
|
49
|
-
*/
|
|
50
7
|
export declare const Durable: {
|
|
51
8
|
Client: typeof ClientService;
|
|
52
9
|
Connection: typeof ConnectionService;
|
|
53
|
-
|
|
10
|
+
MeshOS: typeof MeshOSService;
|
|
54
11
|
Worker: typeof WorkerService;
|
|
55
12
|
workflow: typeof WorkflowService;
|
|
56
13
|
};
|
|
@@ -3,56 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Durable = void 0;
|
|
4
4
|
const client_1 = require("./client");
|
|
5
5
|
const connection_1 = require("./connection");
|
|
6
|
-
const
|
|
6
|
+
const meshos_1 = require("./meshos");
|
|
7
7
|
const worker_1 = require("./worker");
|
|
8
8
|
const workflow_1 = require("./workflow");
|
|
9
|
-
/**
|
|
10
|
-
* As a durable integration platform, HotMesh
|
|
11
|
-
* can model and emulate other durable systems
|
|
12
|
-
* (like Temporal). As you review the code in
|
|
13
|
-
* this file, note the following:
|
|
14
|
-
*
|
|
15
|
-
* 1) There is no central governing server.
|
|
16
|
-
* HotMesh is a client-side SDK that connects to Redis
|
|
17
|
-
* using CQRS principles to implicitly drive
|
|
18
|
-
* orchestrations using a headless quorum. Stream
|
|
19
|
-
* semantics guarantee that all events are
|
|
20
|
-
* processed by the quorum of connected clients.
|
|
21
|
-
*
|
|
22
|
-
* 2) Every developer-defined `workflow` function
|
|
23
|
-
* is assigned a HotMesh workflow (which runs in
|
|
24
|
-
* the background) to support it.
|
|
25
|
-
*
|
|
26
|
-
* If the HotMesh workflow is not yet defined,
|
|
27
|
-
* it will be deployed and activated on-the-fly.
|
|
28
|
-
* (The generated DAG will have one Trigger Activity
|
|
29
|
-
* and one Worker Activity.) The Worker Activity
|
|
30
|
-
* is configured to catch execution errors and
|
|
31
|
-
* return them, using a 'pending' status to
|
|
32
|
-
* indicate that the workflow is still running.
|
|
33
|
-
* It is possible for workflow activities to throw
|
|
34
|
-
* errors that will force the entire workflow to
|
|
35
|
-
* fail. This is not the case here. The workflow will
|
|
36
|
-
* continue to run until it is completed or cancelled.
|
|
37
|
-
*
|
|
38
|
-
* 2) Every developer-defined `activity` function
|
|
39
|
-
* is assigned a HotMesh workflow (which runs in
|
|
40
|
-
* the background) to support it.
|
|
41
|
-
*
|
|
42
|
-
* (The generated DAG will have one Trigger Activity and one
|
|
43
|
-
* Worker Activity.) The JOB ID for Worker Activity executions
|
|
44
|
-
* is derived from the containing JOB ID,
|
|
45
|
-
* allowing Activity state to be 'replayed' when the workflow
|
|
46
|
-
* is run again. The Activity Function Runner (activity proxy)
|
|
47
|
-
* is configured similar to how the workflow worker is and will
|
|
48
|
-
* catch execution errors and return them to the caller, using a
|
|
49
|
-
* 'pending' status to indicate that the activity is still running.
|
|
50
|
-
* This allows the activity to be retried until it succeeds.
|
|
51
|
-
*/
|
|
52
9
|
exports.Durable = {
|
|
53
10
|
Client: client_1.ClientService,
|
|
54
11
|
Connection: connection_1.ConnectionService,
|
|
55
|
-
|
|
12
|
+
MeshOS: meshos_1.MeshOSService,
|
|
56
13
|
Worker: worker_1.WorkerService,
|
|
57
14
|
workflow: workflow_1.WorkflowService,
|
|
58
15
|
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { WorkflowHandleService } from './handle';
|
|
2
|
+
import { FindOptions, MeshOSActivityOptions, MeshOSOptions, WorkflowSearchOptions } from '../../types/durable';
|
|
3
|
+
import { RedisOptions, RedisClass } from '../../types/redis';
|
|
4
|
+
import { StringAnyType } from '../../types';
|
|
5
|
+
import { WorkflowService } from './workflow';
|
|
6
|
+
/**
|
|
7
|
+
* The base class for running MeshOS workflows.
|
|
8
|
+
* Extend and register subclass methods by name to
|
|
9
|
+
* execute as durable workflows, backed by Redis.
|
|
10
|
+
*/
|
|
11
|
+
export declare class MeshOSService {
|
|
12
|
+
/**
|
|
13
|
+
* The top-level Redis isolation. All workflow data is
|
|
14
|
+
* isolated within this namespace. Values should be
|
|
15
|
+
* lower-case with no spaces (e.g, 'staging', 'prod', 'test',
|
|
16
|
+
* 'routing-stagig', 'reporting-prod', etc.).
|
|
17
|
+
* 1) only url-safe values are allowed;
|
|
18
|
+
* 2) the 'a' symbol is reserved by HotMesh for indexing apps
|
|
19
|
+
*/
|
|
20
|
+
namespace: string;
|
|
21
|
+
/**
|
|
22
|
+
* Data is routed to workers that specify this task queue.
|
|
23
|
+
* Setting the task queue when the worker is created will
|
|
24
|
+
* ensure that the worker only receives messages destined
|
|
25
|
+
* for the queue. Callers can specify the taskQue to when
|
|
26
|
+
* starting a job to call those workers.
|
|
27
|
+
*/
|
|
28
|
+
taskQueue: string;
|
|
29
|
+
/**
|
|
30
|
+
* These methods run as durable workflows
|
|
31
|
+
*/
|
|
32
|
+
workflowFunctions: Array<MeshOSOptions | string>;
|
|
33
|
+
/**
|
|
34
|
+
* These methods run as hooks (hook into a running workflow)
|
|
35
|
+
*/
|
|
36
|
+
hookFunctions: Array<MeshOSOptions | string>;
|
|
37
|
+
/**
|
|
38
|
+
* These methods run as proxied activities (and are safely memoized)
|
|
39
|
+
*/
|
|
40
|
+
proxyFunctions: Array<MeshOSActivityOptions | string>;
|
|
41
|
+
/**
|
|
42
|
+
* The workflow GUID. Workflows will be persisted to
|
|
43
|
+
* Redis using the pattern hmsh:<namespace>:j:<id>.
|
|
44
|
+
*/
|
|
45
|
+
id: string;
|
|
46
|
+
/**
|
|
47
|
+
* The Redis connection options. NOTE: Redis and IORedis
|
|
48
|
+
* use different formats for their connection config.
|
|
49
|
+
*/
|
|
50
|
+
redisOptions: RedisOptions;
|
|
51
|
+
/**
|
|
52
|
+
* The Redis connection class.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* import Redis from 'ioredis';
|
|
56
|
+
* import * as Redis from 'redis';
|
|
57
|
+
*/
|
|
58
|
+
redisClass: RedisClass;
|
|
59
|
+
/**
|
|
60
|
+
* Optional model declaration (custom workflow state)
|
|
61
|
+
*/
|
|
62
|
+
model: StringAnyType;
|
|
63
|
+
/**
|
|
64
|
+
* Optional configuration for Redis FT search
|
|
65
|
+
*/
|
|
66
|
+
search: WorkflowSearchOptions;
|
|
67
|
+
static MeshOS: typeof WorkflowService;
|
|
68
|
+
static getHotMeshClient(redisClass: RedisClass, redisOptions: RedisOptions, namespace: string, taskQueue: string): Promise<import("../hotmesh").HotMeshService>;
|
|
69
|
+
/**
|
|
70
|
+
* mints a workflow ID, using the search prefix.
|
|
71
|
+
* NOTE: The prefix is necesary when indexing
|
|
72
|
+
* HASHes when FT search is enabled.
|
|
73
|
+
* @returns {string}
|
|
74
|
+
*/
|
|
75
|
+
static mintGuid(): string;
|
|
76
|
+
/**
|
|
77
|
+
* Creates an FT search index
|
|
78
|
+
*/
|
|
79
|
+
static createIndex(): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Initialize the worker(s) for the entity. This is a static
|
|
82
|
+
* method that allows for optional task Queue targeting.
|
|
83
|
+
* NOTE: Allow List may be optionally used to only wrap
|
|
84
|
+
* specific methods in this class.
|
|
85
|
+
* @param {string} taskQueue
|
|
86
|
+
* @param {string[]} allowList
|
|
87
|
+
*/
|
|
88
|
+
static startWorkers(taskQueue?: string, allowList?: Array<MeshOSOptions | string>): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* executes the redis FT search query
|
|
91
|
+
* @example '@_quantity:[89 89]'
|
|
92
|
+
* @param {any[]} args
|
|
93
|
+
* @returns {string}
|
|
94
|
+
*/
|
|
95
|
+
static find(options: FindOptions, ...args: string[]): Promise<string[] | [number]>;
|
|
96
|
+
/**
|
|
97
|
+
* returns the workflow handle. The handle can then be
|
|
98
|
+
* used to query for status, state, custom state, etc.
|
|
99
|
+
* @param {string} id
|
|
100
|
+
* @returns {Promise<WorkflowHandleService>}
|
|
101
|
+
*/
|
|
102
|
+
static get(id: string): Promise<WorkflowHandleService>;
|
|
103
|
+
/**
|
|
104
|
+
* Optionally include a target taskQueue to exec the
|
|
105
|
+
* workflow's call on a specific worker queue.
|
|
106
|
+
*/
|
|
107
|
+
constructor(id?: string, options?: Record<string, any>);
|
|
108
|
+
}
|