@hotmeshio/hotmesh 0.0.9 → 0.0.11
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 +84 -77
- package/build/modules/errors.d.ts +17 -1
- package/build/modules/errors.js +29 -1
- package/build/modules/key.d.ts +2 -2
- package/build/modules/key.js +3 -3
- package/build/package.json +2 -1
- package/build/services/activities/activity.d.ts +1 -0
- package/build/services/activities/activity.js +13 -2
- package/build/services/activities/cycle.js +6 -1
- package/build/services/activities/trigger.js +2 -3
- package/build/services/collator/index.d.ts +8 -0
- package/build/services/collator/index.js +11 -1
- package/build/services/durable/factory.d.ts +18 -1
- package/build/services/durable/factory.js +46 -4
- package/build/services/durable/handle.js +25 -7
- package/build/services/durable/native.js +0 -1
- package/build/services/durable/worker.d.ts +3 -3
- package/build/services/durable/worker.js +16 -11
- package/build/services/durable/workflow.js +1 -1
- package/build/services/hotmesh/index.js +1 -1
- package/build/services/pipe/functions/math.d.ts +4 -0
- package/build/services/pipe/functions/math.js +73 -0
- package/build/services/pipe/functions/number.d.ts +0 -4
- package/build/services/pipe/functions/number.js +0 -73
- package/build/services/signaler/stream.js +6 -3
- package/build/services/store/index.js +2 -2
- package/build/services/stream/clients/ioredis.js +1 -1
- package/build/services/stream/clients/redis.js +1 -1
- package/build/services/sub/clients/ioredis.js +1 -1
- package/build/services/sub/clients/redis.js +1 -1
- package/build/types/durable.d.ts +8 -3
- package/build/types/index.d.ts +1 -0
- package/modules/errors.ts +42 -1
- package/modules/key.ts +2 -2
- package/package.json +2 -1
- package/services/activities/activity.ts +14 -2
- package/services/activities/cycle.ts +6 -1
- package/services/activities/trigger.ts +2 -3
- package/services/collator/index.ts +12 -1
- package/services/durable/factory.ts +46 -4
- package/services/durable/handle.ts +23 -8
- package/services/durable/native.ts +0 -1
- package/services/durable/worker.ts +27 -13
- package/services/durable/workflow.ts +1 -1
- package/services/hotmesh/index.ts +2 -2
- package/services/pipe/functions/math.ts +74 -0
- package/services/pipe/functions/number.ts +0 -75
- package/services/signaler/stream.ts +6 -3
- package/services/store/index.ts +3 -3
- package/services/stream/clients/ioredis.ts +2 -2
- package/services/stream/clients/redis.ts +2 -2
- package/services/sub/clients/ioredis.ts +2 -2
- package/services/sub/clients/redis.ts +2 -2
- package/types/durable.ts +12 -5
- package/types/index.ts +15 -0
- package/build/services/dimension/index.d.ts +0 -29
- package/build/services/dimension/index.js +0 -35
- package/services/dimension/README.md +0 -73
- package/services/dimension/index.ts +0 -39
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# HotMesh
|
|
2
2
|

|
|
3
3
|
|
|
4
|
-
Elevate Redis from an in-memory data store to a game-changing
|
|
4
|
+
Elevate Redis from an in-memory data store 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
6
|
## Install
|
|
7
7
|
[](https://badge.fury.io/js/%40hotmeshio%2Fhotmesh)
|
|
@@ -11,85 +11,92 @@ npm install @hotmeshio/hotmesh
|
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Design
|
|
14
|
-
HotMesh
|
|
14
|
+
The HotMesh SDK is designed to keep your code front-and-center. Write functions as you normally would, then use HotMesh to make them durable.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
1. Start by defining **activities**. Activities are those functions that will be invoked by your workflow. They are commonly used to read and write to databases and invoke external services. They 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
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return `Hello, ${name}!`;
|
|
22
|
-
}
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
>Next, define your workflow. Include conditional logic, loops, etc. It's vanilla code written in your own coding style--just make sure to call `proxyActivities` to run your activities durably.
|
|
26
|
-
|
|
27
|
-
**./workflows.ts**
|
|
28
|
-
```javascript
|
|
29
|
-
import { Durable } from '@hotmeshio/hotmesh';
|
|
30
|
-
import * as activities from './activities';
|
|
31
|
-
|
|
32
|
-
const { greet } = Durable.workflow
|
|
33
|
-
.proxyActivities<typeof activities>({
|
|
34
|
-
activities
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
export async function example(name: string): Promise<string> {
|
|
38
|
-
return await greet(name);
|
|
39
|
-
}
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
>Finally, create a worker and client. The *client* triggers workflows, while the *worker* runs them, retrying as necessary until the workflow succeeds--all without the need for complicated retry logic.
|
|
43
|
-
|
|
44
|
-
**./client.ts**
|
|
45
|
-
```javascript
|
|
46
|
-
import { Durable } from '@hotmeshio/hotmesh';
|
|
47
|
-
import Redis from 'ioredis'; //OR `import * as Redis from 'redis';`
|
|
48
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
49
|
-
|
|
50
|
-
async function run() {
|
|
51
|
-
const connection = await Durable.Connection.connect({
|
|
52
|
-
class: Redis,
|
|
53
|
-
options: { host: 'localhost', port: 6379 }
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const client = new Durable.Client({
|
|
57
|
-
connection
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const handle = await client.workflow.start({
|
|
61
|
-
args: ['HotMesh'],
|
|
62
|
-
taskQueue: 'hello-world',
|
|
63
|
-
workflowName: 'example',
|
|
64
|
-
workflowId: 'workflow-' + uuidv4()
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
console.log(await handle.result());
|
|
68
|
-
}
|
|
69
|
-
```
|
|
20
|
+
export async function greet(name: string): Promise<string> {
|
|
21
|
+
return `Hello, ${name}!`;
|
|
22
|
+
}
|
|
70
23
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
24
|
+
export async function saludar(nombre: string): Promise<string> {
|
|
25
|
+
Math.random() > 0.5 && throw new Error('Random error');
|
|
26
|
+
return `¡Hola, ${nombre}!`;
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
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.
|
|
30
|
+
```javascript
|
|
31
|
+
//workflows.ts
|
|
32
|
+
|
|
33
|
+
import { Durable } from '@hotmeshio/hotmesh';
|
|
34
|
+
import * as activities from './activities';
|
|
35
|
+
|
|
36
|
+
const { greet, saludar } = Durable.workflow
|
|
37
|
+
.proxyActivities<typeof activities>({
|
|
38
|
+
activities
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export async function example(name: string, lang: string): Promise<string> {
|
|
42
|
+
if (lang === 'es') {
|
|
43
|
+
return await saludar(name);
|
|
44
|
+
} else {
|
|
45
|
+
return await greet(name);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
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. By using a HotMesh **client** to send the request, the function is guaranteed to return a result.
|
|
51
|
+
```javascript
|
|
52
|
+
//client.ts
|
|
53
|
+
|
|
54
|
+
import { Durable } from '@hotmeshio/hotmesh';
|
|
55
|
+
import Redis from 'ioredis'; //OR `import * as Redis from 'redis';`
|
|
56
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
57
|
+
|
|
58
|
+
async function run(): Promise<string> {
|
|
59
|
+
const client = new Durable.Client({
|
|
60
|
+
connection: {
|
|
61
|
+
class: Redis,
|
|
62
|
+
options: { host: 'localhost', port: 6379 }
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const handle = await client.workflow.start({
|
|
67
|
+
args: ['HotMesh', 'es'],
|
|
68
|
+
taskQueue: 'hello-world',
|
|
69
|
+
workflowName: 'example',
|
|
70
|
+
workflowId: uuidv4()
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return await handle.result();
|
|
74
|
+
//returns '¡Hola, HotMesh!'
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
4. The last step is to create a **worker** and link it to your workflow function. Workers listen for tasks on their assigned channel, executing the linked workflow function until it succeeds.
|
|
79
|
+
```javascript
|
|
80
|
+
//worker.ts
|
|
81
|
+
|
|
82
|
+
import { Durable } from '@hotmeshio/hotmesh';
|
|
83
|
+
import Redis from 'ioredis';
|
|
84
|
+
import * as workflows from './workflows';
|
|
85
|
+
|
|
86
|
+
async function run() {
|
|
87
|
+
const worker = await Durable.Worker.create({
|
|
88
|
+
connection: {
|
|
89
|
+
class: Redis,
|
|
90
|
+
options: { host: 'localhost', port: 6379 },
|
|
91
|
+
},
|
|
92
|
+
taskQueue: 'hello-world',
|
|
93
|
+
workflow: workflows.example,
|
|
94
|
+
});
|
|
95
|
+
await worker.run();
|
|
96
|
+
}
|
|
97
|
+
```
|
|
91
98
|
|
|
92
|
-
>HotMesh delivers durable
|
|
99
|
+
>HotMesh delivers durable workflows without the cost and complexity of a centralized service mesh. Refer to the [samples-typescript](https://github.com/hotmeshio/samples-typescript) Git Repo for a range of examples, including compositional workflows (where one workflow calls another) and remote execution (where calls are brokered across microservices).
|
|
93
100
|
|
|
94
101
|
## Advanced Design
|
|
95
102
|
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.
|
|
@@ -226,7 +233,7 @@ For a deep dive into HotMesh's distributed orchestration philosophy, refer to th
|
|
|
226
233
|
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.
|
|
227
234
|
|
|
228
235
|
## System Lifecycle
|
|
229
|
-
Gain insight into
|
|
236
|
+
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).
|
|
230
237
|
|
|
231
238
|
## Alpha Release
|
|
232
239
|
So what exacty is an [alpha release](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/alpha.md)?!
|
|
@@ -6,6 +6,22 @@ declare class GetStateError extends Error {
|
|
|
6
6
|
declare class SetStateError extends Error {
|
|
7
7
|
constructor();
|
|
8
8
|
}
|
|
9
|
+
declare class DurableTimeoutError extends Error {
|
|
10
|
+
code: number;
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
declare class DurableMaxedError extends Error {
|
|
14
|
+
code: number;
|
|
15
|
+
constructor(message: string);
|
|
16
|
+
}
|
|
17
|
+
declare class DurableFatalError extends Error {
|
|
18
|
+
code: number;
|
|
19
|
+
constructor(message: string);
|
|
20
|
+
}
|
|
21
|
+
declare class DurableRetryError extends Error {
|
|
22
|
+
code: number;
|
|
23
|
+
constructor(message: string);
|
|
24
|
+
}
|
|
9
25
|
declare class MapDataError extends Error {
|
|
10
26
|
constructor();
|
|
11
27
|
}
|
|
@@ -25,4 +41,4 @@ declare class CollationError extends Error {
|
|
|
25
41
|
fault: CollationFaultType;
|
|
26
42
|
constructor(status: number, leg: ActivityDuplex, stage: CollationStage, fault?: CollationFaultType);
|
|
27
43
|
}
|
|
28
|
-
export { CollationError, DuplicateJobError, GetStateError, SetStateError, MapDataError, RegisterTimeoutError, ExecActivityError };
|
|
44
|
+
export { CollationError, DurableTimeoutError, DurableMaxedError, DurableFatalError, DurableRetryError, DuplicateJobError, GetStateError, SetStateError, MapDataError, RegisterTimeoutError, ExecActivityError };
|
package/build/modules/errors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ExecActivityError = exports.RegisterTimeoutError = exports.MapDataError = exports.SetStateError = exports.GetStateError = exports.DuplicateJobError = exports.CollationError = void 0;
|
|
3
|
+
exports.ExecActivityError = exports.RegisterTimeoutError = exports.MapDataError = exports.SetStateError = exports.GetStateError = exports.DuplicateJobError = exports.DurableRetryError = exports.DurableFatalError = exports.DurableMaxedError = exports.DurableTimeoutError = exports.CollationError = void 0;
|
|
4
4
|
class GetStateError extends Error {
|
|
5
5
|
constructor() {
|
|
6
6
|
super("Error occurred while getting job state");
|
|
@@ -13,6 +13,34 @@ class SetStateError extends Error {
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
exports.SetStateError = SetStateError;
|
|
16
|
+
class DurableTimeoutError extends Error {
|
|
17
|
+
constructor(message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.code = 596;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.DurableTimeoutError = DurableTimeoutError;
|
|
23
|
+
class DurableMaxedError extends Error {
|
|
24
|
+
constructor(message) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.code = 597;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.DurableMaxedError = DurableMaxedError;
|
|
30
|
+
class DurableFatalError extends Error {
|
|
31
|
+
constructor(message) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.code = 598;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.DurableFatalError = DurableFatalError;
|
|
37
|
+
class DurableRetryError extends Error {
|
|
38
|
+
constructor(message) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.code = 599;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.DurableRetryError = DurableRetryError;
|
|
16
44
|
class MapDataError extends Error {
|
|
17
45
|
constructor() {
|
|
18
46
|
super("Error occurred while mapping data");
|
package/build/modules/key.d.ts
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* hmsh:<appid>:sym:keys:<activityid|$subscribes> -> {hash} list of symbols based upon schema enums (initially) and adaptively optimized (later) during runtime; if '$subscribes' is used as the activityid, it is a top-level `job` symbol set (for job keys)
|
|
26
26
|
* hmsh:<appid>:sym:vals: -> {hash} list of symbols for job values across all app versions
|
|
27
27
|
*/
|
|
28
|
-
declare const
|
|
28
|
+
declare const HMNS = "hmsh";
|
|
29
29
|
declare enum KeyType {
|
|
30
30
|
APP = 0,
|
|
31
31
|
ENGINE_ID = 1,
|
|
@@ -72,4 +72,4 @@ declare class KeyService {
|
|
|
72
72
|
*/
|
|
73
73
|
static mintKey(namespace: string, keyType: KeyType, params: KeyStoreParams): string;
|
|
74
74
|
}
|
|
75
|
-
export { KeyService, KeyType, KeyStoreParams,
|
|
75
|
+
export { KeyService, KeyType, KeyStoreParams, HMNS };
|
package/build/modules/key.js
CHANGED
|
@@ -27,10 +27,10 @@
|
|
|
27
27
|
* hmsh:<appid>:sym:vals: -> {hash} list of symbols for job values across all app versions
|
|
28
28
|
*/
|
|
29
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
-
exports.
|
|
30
|
+
exports.HMNS = exports.KeyType = exports.KeyService = void 0;
|
|
31
31
|
//default namespace for hotmesh
|
|
32
|
-
const
|
|
33
|
-
exports.
|
|
32
|
+
const HMNS = "hmsh";
|
|
33
|
+
exports.HMNS = HMNS;
|
|
34
34
|
//these are the entity types that are stored in the key/value store
|
|
35
35
|
var KeyType;
|
|
36
36
|
(function (KeyType) {
|
package/build/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotmeshio/hotmesh",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "Durable Workflows",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"test:durable:hello": "NODE_ENV=test jest ./tests/durable/helloworld/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
44
44
|
"test:durable:goodbye": "NODE_ENV=test jest ./tests/durable/goodbye/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
45
45
|
"test:durable:retry": "NODE_ENV=test jest ./tests/durable/retry/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
46
|
+
"test:durable:fatal": "NODE_ENV=test jest ./tests/durable/fatal/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
46
47
|
"test:durable:loopactivity": "NODE_ENV=test jest ./tests/durable/loopactivity/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
47
48
|
"test:durable:nested": "NODE_ENV=test jest ./tests/durable/nested/index.test.ts --detectOpenHandles --forceExit --verbose"
|
|
48
49
|
},
|
|
@@ -40,6 +40,7 @@ declare class Activity {
|
|
|
40
40
|
resolveStatus(multiResponse: MultiResponseFlags): number;
|
|
41
41
|
mapJobData(): void;
|
|
42
42
|
mapInputData(): void;
|
|
43
|
+
mapOutputData(): void;
|
|
43
44
|
registerTimeout(): Promise<void>;
|
|
44
45
|
bindActivityError(data: Record<string, unknown>): void;
|
|
45
46
|
getTriggerConfig(): Promise<ActivityType>;
|
|
@@ -4,7 +4,6 @@ exports.Activity = void 0;
|
|
|
4
4
|
const errors_1 = require("../../modules/errors");
|
|
5
5
|
const utils_1 = require("../../modules/utils");
|
|
6
6
|
const collator_1 = require("../collator");
|
|
7
|
-
const dimension_1 = require("../dimension");
|
|
8
7
|
const mapper_1 = require("../mapper");
|
|
9
8
|
const pipe_1 = require("../pipe");
|
|
10
9
|
const serializer_1 = require("../serializer");
|
|
@@ -43,6 +42,7 @@ class Activity {
|
|
|
43
42
|
if (this.doesHook()) {
|
|
44
43
|
//sleep and wait to awaken upon a signal
|
|
45
44
|
await this.registerHook(multi);
|
|
45
|
+
this.mapOutputData();
|
|
46
46
|
this.mapJobData();
|
|
47
47
|
await this.setState(multi);
|
|
48
48
|
await collator_1.CollatorService.authorizeReentry(this, multi);
|
|
@@ -53,6 +53,7 @@ class Activity {
|
|
|
53
53
|
else {
|
|
54
54
|
//end the activity and transition to its children
|
|
55
55
|
this.adjacencyList = await this.filterAdjacent();
|
|
56
|
+
this.mapOutputData();
|
|
56
57
|
this.mapJobData();
|
|
57
58
|
await this.setState(multi);
|
|
58
59
|
await collator_1.CollatorService.notarizeEarlyCompletion(this, multi);
|
|
@@ -265,6 +266,16 @@ class Activity {
|
|
|
265
266
|
this.context.data = mapper.mapRules();
|
|
266
267
|
}
|
|
267
268
|
}
|
|
269
|
+
mapOutputData() {
|
|
270
|
+
//activity YAML may include output map data that produces/extends activity output data.
|
|
271
|
+
if (this.config.output?.maps) {
|
|
272
|
+
const mapper = new mapper_1.MapperService(this.config.output.maps, this.context);
|
|
273
|
+
const actOutData = mapper.mapRules();
|
|
274
|
+
const activityId = this.metadata.aid;
|
|
275
|
+
const data = { ...this.context[activityId].output, ...actOutData };
|
|
276
|
+
this.context[activityId].output.data = data;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
268
279
|
async registerTimeout() {
|
|
269
280
|
//set timeout in support of hook and/or duplex
|
|
270
281
|
}
|
|
@@ -442,7 +453,7 @@ class Activity {
|
|
|
442
453
|
}
|
|
443
454
|
resolveAdjacentDad() {
|
|
444
455
|
//concat self and child dimension (all children (leg 1) begin life at 0)
|
|
445
|
-
return `${this.resolveDad()}${
|
|
456
|
+
return `${this.resolveDad()}${collator_1.CollatorService.getDimensionalSeed(0)}`;
|
|
446
457
|
}
|
|
447
458
|
;
|
|
448
459
|
async filterAdjacent() {
|
|
@@ -63,13 +63,18 @@ class Cycle extends activity_1.Activity {
|
|
|
63
63
|
* pattern allows for retries without violating the DAG.
|
|
64
64
|
*/
|
|
65
65
|
async cycleAncestorActivity(multi) {
|
|
66
|
+
//Cycle activity L1 is a standin for the target ancestor L1.
|
|
67
|
+
//Input data mapping (mapInputData) allows for the
|
|
68
|
+
//next dimensonal thread to execute with different
|
|
69
|
+
//input data than the current dimensional thread
|
|
70
|
+
this.mapInputData();
|
|
66
71
|
const streamData = {
|
|
67
72
|
metadata: {
|
|
68
73
|
dad: collator_1.CollatorService.resolveReentryDimension(this),
|
|
69
74
|
jid: this.context.metadata.jid,
|
|
70
75
|
aid: this.config.ancestor,
|
|
71
76
|
},
|
|
72
|
-
data:
|
|
77
|
+
data: this.context.data
|
|
73
78
|
};
|
|
74
79
|
return (await this.engine.streamSignaler?.publishMessage(null, streamData, multi));
|
|
75
80
|
}
|
|
@@ -6,7 +6,6 @@ const errors_1 = require("../../modules/errors");
|
|
|
6
6
|
const utils_1 = require("../../modules/utils");
|
|
7
7
|
const activity_1 = require("./activity");
|
|
8
8
|
const collator_1 = require("../collator");
|
|
9
|
-
const dimension_1 = require("../dimension");
|
|
10
9
|
const pipe_1 = require("../pipe");
|
|
11
10
|
const reporter_1 = require("../reporter");
|
|
12
11
|
const serializer_1 = require("../serializer");
|
|
@@ -80,7 +79,7 @@ class Trigger extends activity_1.Activity {
|
|
|
80
79
|
const jobKey = this.resolveJobKey(inputContext);
|
|
81
80
|
const utc = (0, utils_1.formatISODate)(new Date());
|
|
82
81
|
const { id, version } = await this.engine.getVID();
|
|
83
|
-
this.initDimensionalAddress(
|
|
82
|
+
this.initDimensionalAddress(collator_1.CollatorService.getDimensionalSeed());
|
|
84
83
|
const activityMetadata = {
|
|
85
84
|
...this.metadata,
|
|
86
85
|
jid: jobId,
|
|
@@ -100,7 +99,7 @@ class Trigger extends activity_1.Activity {
|
|
|
100
99
|
trc: this.context.metadata.trc,
|
|
101
100
|
spn: this.context.metadata.spn,
|
|
102
101
|
jid: jobId,
|
|
103
|
-
dad:
|
|
102
|
+
dad: collator_1.CollatorService.getDimensionalSeed(),
|
|
104
103
|
key: jobKey,
|
|
105
104
|
jc: utc,
|
|
106
105
|
ju: utc,
|
|
@@ -69,5 +69,13 @@ declare class CollatorService {
|
|
|
69
69
|
*/
|
|
70
70
|
static bindAncestorArray(graphs: HotMeshGraph[]): void;
|
|
71
71
|
static isActivityComplete(status: number): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* All activities exist on a dimensional plane. Zero
|
|
74
|
+
* is the default. A value of
|
|
75
|
+
* `AxY,0,0,0,0,1,0,0` would reflect that
|
|
76
|
+
* an ancestor activity was dimensionalized beyond
|
|
77
|
+
* the default.
|
|
78
|
+
*/
|
|
79
|
+
static getDimensionalSeed(index?: number): string;
|
|
72
80
|
}
|
|
73
81
|
export { CollatorService };
|
|
@@ -75,7 +75,7 @@ class CollatorService {
|
|
|
75
75
|
static async notarizeCompletion(activity, multi) {
|
|
76
76
|
//1) ALWAYS actualize leg2 dimension (+1)
|
|
77
77
|
//2) IF the activity is used in a cycle, don't close leg 2!
|
|
78
|
-
const decrement = activity.config.cycle ? 0 :
|
|
78
|
+
const decrement = activity.config.cycle ? 0 : 1000000000000;
|
|
79
79
|
return await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, 1 - decrement, this.getDimensionalAddress(activity), multi);
|
|
80
80
|
}
|
|
81
81
|
;
|
|
@@ -218,6 +218,16 @@ class CollatorService {
|
|
|
218
218
|
static isActivityComplete(status) {
|
|
219
219
|
return (status - 0) <= 0;
|
|
220
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* All activities exist on a dimensional plane. Zero
|
|
223
|
+
* is the default. A value of
|
|
224
|
+
* `AxY,0,0,0,0,1,0,0` would reflect that
|
|
225
|
+
* an ancestor activity was dimensionalized beyond
|
|
226
|
+
* the default.
|
|
227
|
+
*/
|
|
228
|
+
static getDimensionalSeed(index = 0) {
|
|
229
|
+
return `,${index}`;
|
|
230
|
+
}
|
|
221
231
|
}
|
|
222
232
|
exports.CollatorService = CollatorService;
|
|
223
233
|
//max int digit count that supports `hincrby`
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 1) `maxSystemRetries` | can be 0 to 3 and represents milliseconds;
|
|
3
|
+
* if there is an error, the workflow will retry up to `maxSystemRetries` times
|
|
4
|
+
* delaying by 10, 100, and 1000ms; this is a system level retry
|
|
5
|
+
* and is not configurable. It exists to handle intermittent network
|
|
6
|
+
* errors. (NOTE: each retry spawns a new transition stream)
|
|
7
|
+
*
|
|
8
|
+
* 2) `backoffExponent` | can be any number and represents `seconds` when applied;
|
|
9
|
+
* retries will happen indefinitely and adhere to the
|
|
10
|
+
* exponential backoff algorithm by multiplying by `backoffExponent`.
|
|
11
|
+
* For example, if `backoffExponent` is 10, the workflow will retry
|
|
12
|
+
* in 10s, 100s, 1000s, 10000s, etc.
|
|
13
|
+
*
|
|
14
|
+
* EXAMPLE | Using `maxSystemRetries = 3` and `backoffExponent = 10`, errant
|
|
15
|
+
* workflows will be retried on the following schedule (8 times in 27 hours):
|
|
16
|
+
* => 10ms, 100ms, 1000ms, 10s, 100s, 1_000s, 10_000s, 100_000s
|
|
17
|
+
*/
|
|
18
|
+
declare const getWorkflowYAML: (topic: string, version?: string, maxSystemRetries?: number, backoffExponent?: number) => string;
|
|
2
19
|
declare const getActivityYAML: (topic: string, version?: string) => string;
|
|
3
20
|
export { getActivityYAML, getWorkflowYAML };
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getWorkflowYAML = exports.getActivityYAML = void 0;
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* 1) `maxSystemRetries` | can be 0 to 3 and represents milliseconds;
|
|
6
|
+
* if there is an error, the workflow will retry up to `maxSystemRetries` times
|
|
7
|
+
* delaying by 10, 100, and 1000ms; this is a system level retry
|
|
8
|
+
* and is not configurable. It exists to handle intermittent network
|
|
9
|
+
* errors. (NOTE: each retry spawns a new transition stream)
|
|
10
|
+
*
|
|
11
|
+
* 2) `backoffExponent` | can be any number and represents `seconds` when applied;
|
|
12
|
+
* retries will happen indefinitely and adhere to the
|
|
13
|
+
* exponential backoff algorithm by multiplying by `backoffExponent`.
|
|
14
|
+
* For example, if `backoffExponent` is 10, the workflow will retry
|
|
15
|
+
* in 10s, 100s, 1000s, 10000s, etc.
|
|
16
|
+
*
|
|
17
|
+
* EXAMPLE | Using `maxSystemRetries = 3` and `backoffExponent = 10`, errant
|
|
18
|
+
* workflows will be retried on the following schedule (8 times in 27 hours):
|
|
19
|
+
* => 10ms, 100ms, 1000ms, 10s, 100s, 1_000s, 10_000s, 100_000s
|
|
20
|
+
*/
|
|
21
|
+
const getWorkflowYAML = (topic, version = '1', maxSystemRetries = 2, backoffExponent = 10) => {
|
|
5
22
|
return `app:
|
|
6
23
|
id: ${topic}
|
|
7
24
|
version: '${version}'
|
|
@@ -33,10 +50,20 @@ const getWorkflowYAML = (topic, version = '1') => {
|
|
|
33
50
|
a1:
|
|
34
51
|
type: activity
|
|
35
52
|
cycle: true
|
|
36
|
-
|
|
53
|
+
output:
|
|
54
|
+
schema:
|
|
55
|
+
type: object
|
|
56
|
+
properties:
|
|
57
|
+
duration:
|
|
58
|
+
type: number
|
|
59
|
+
maps:
|
|
60
|
+
duration: ${backoffExponent}
|
|
61
|
+
|
|
37
62
|
w1:
|
|
38
63
|
type: worker
|
|
39
64
|
topic: ${topic}
|
|
65
|
+
retry:
|
|
66
|
+
'599': [${maxSystemRetries}]
|
|
40
67
|
input:
|
|
41
68
|
schema:
|
|
42
69
|
type: object
|
|
@@ -58,18 +85,33 @@ const getWorkflowYAML = (topic, version = '1') => {
|
|
|
58
85
|
maps:
|
|
59
86
|
response: '{$self.output.data.response}'
|
|
60
87
|
|
|
88
|
+
a599:
|
|
89
|
+
title: Sleep before trying again
|
|
90
|
+
type: activity
|
|
91
|
+
sleep: "{a1.output.data.duration}"
|
|
92
|
+
|
|
61
93
|
c1:
|
|
94
|
+
title: Goto Activity a1
|
|
62
95
|
type: cycle
|
|
63
96
|
ancestor: a1
|
|
97
|
+
input:
|
|
98
|
+
maps:
|
|
99
|
+
duration:
|
|
100
|
+
'@pipe':
|
|
101
|
+
- ['{a1.output.data.duration}', ${backoffExponent}]
|
|
102
|
+
- ['{@math.multiply}']
|
|
103
|
+
|
|
64
104
|
transitions:
|
|
65
105
|
t1:
|
|
66
106
|
- to: a1
|
|
67
107
|
a1:
|
|
68
108
|
- to: w1
|
|
69
109
|
w1:
|
|
70
|
-
- to:
|
|
110
|
+
- to: a599
|
|
71
111
|
conditions:
|
|
72
|
-
code:
|
|
112
|
+
code: 599
|
|
113
|
+
a599:
|
|
114
|
+
- to: c1
|
|
73
115
|
`;
|
|
74
116
|
};
|
|
75
117
|
exports.getWorkflowYAML = getWorkflowYAML;
|
|
@@ -10,22 +10,40 @@ class WorkflowHandleService {
|
|
|
10
10
|
async result() {
|
|
11
11
|
let status = await this.hotMesh.getStatus(this.workflowId);
|
|
12
12
|
const topic = `${this.workflowTopic}.${this.workflowId}`;
|
|
13
|
-
if (status == 0) {
|
|
14
|
-
return (await this.hotMesh.getState(this.workflowTopic, this.workflowId)).data?.response;
|
|
15
|
-
}
|
|
16
13
|
return new Promise((resolve, reject) => {
|
|
17
14
|
let isResolved = false;
|
|
18
15
|
//common fulfill/unsubscribe
|
|
19
|
-
const complete = async (response) => {
|
|
16
|
+
const complete = async (response, err) => {
|
|
20
17
|
if (isResolved)
|
|
21
18
|
return;
|
|
22
19
|
isResolved = true;
|
|
23
20
|
this.hotMesh.unsub(topic);
|
|
24
|
-
|
|
21
|
+
if (err) {
|
|
22
|
+
return reject(JSON.parse(err));
|
|
23
|
+
}
|
|
24
|
+
else if (!response) {
|
|
25
|
+
const state = await this.hotMesh.getState(this.workflowTopic, this.workflowId);
|
|
26
|
+
if (!state.data && state.metadata.err) {
|
|
27
|
+
return reject(JSON.parse(state.metadata.err));
|
|
28
|
+
}
|
|
29
|
+
response = state.data?.response;
|
|
30
|
+
}
|
|
31
|
+
resolve(response);
|
|
25
32
|
};
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
//check for done
|
|
34
|
+
if (status == 0) {
|
|
35
|
+
return complete();
|
|
36
|
+
}
|
|
37
|
+
//subscribe to topic
|
|
38
|
+
this.hotMesh.sub(topic, async (topic, state) => {
|
|
39
|
+
if (!state.data && state.metadata.err) {
|
|
40
|
+
await complete(null, state.metadata.err);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
await complete(state.data?.response);
|
|
44
|
+
}
|
|
28
45
|
});
|
|
46
|
+
//resolve for race condition
|
|
29
47
|
setTimeout(async () => {
|
|
30
48
|
status = await this.hotMesh.getStatus(this.workflowId);
|
|
31
49
|
if (status == 0) {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { HotMeshService as HotMesh } from '../hotmesh';
|
|
2
|
-
import { Connection, Registry, WorkerConfig } from "../../types/durable";
|
|
2
|
+
import { Connection, Registry, WorkerConfig, WorkerOptions } from "../../types/durable";
|
|
3
3
|
export declare class WorkerService {
|
|
4
4
|
static activityRegistry: Registry;
|
|
5
5
|
static connection: Connection;
|
|
6
6
|
static instances: Map<string, HotMesh | Promise<HotMesh>>;
|
|
7
7
|
workflowRunner: HotMesh;
|
|
8
8
|
activityRunner: HotMesh;
|
|
9
|
-
static getHotMesh: (worflowTopic: string) => Promise<HotMesh>;
|
|
10
|
-
static activateWorkflow(hotMesh: HotMesh, topic: string,
|
|
9
|
+
static getHotMesh: (worflowTopic: string, options?: WorkerOptions) => Promise<HotMesh>;
|
|
10
|
+
static activateWorkflow(hotMesh: HotMesh, topic: string, dagFactory: Function, options?: WorkerOptions): Promise<void>;
|
|
11
11
|
/**
|
|
12
12
|
* The `worker` calls `registerActivities` immediately BEFORE
|
|
13
13
|
* dynamically importing the user's workflow module. That file
|