@hotmeshio/hotmesh 0.0.10 → 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 +5 -4
- package/build/modules/errors.d.ts +17 -1
- package/build/modules/errors.js +29 -1
- 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/worker.d.ts +3 -3
- package/build/services/durable/worker.js +16 -10
- package/build/services/durable/workflow.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/types/durable.d.ts +7 -2
- package/build/types/index.d.ts +1 -0
- package/modules/errors.ts +42 -1
- 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/worker.ts +27 -12
- package/services/durable/workflow.ts +1 -1
- package/services/pipe/functions/math.ts +74 -0
- package/services/pipe/functions/number.ts +0 -75
- package/services/signaler/stream.ts +6 -3
- package/types/durable.ts +11 -4
- 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
|
@@ -11,9 +11,9 @@ npm install @hotmeshio/hotmesh
|
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Design
|
|
14
|
-
The HotMesh SDK is designed to keep your code front-and-center. Write functions as you normally would, then use
|
|
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
|
-
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.
|
|
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
17
|
```javascript
|
|
18
18
|
//activities.ts
|
|
19
19
|
|
|
@@ -22,6 +22,7 @@ The HotMesh SDK is designed to keep your code front-and-center. Write functions
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export async function saludar(nombre: string): Promise<string> {
|
|
25
|
+
Math.random() > 0.5 && throw new Error('Random error');
|
|
25
26
|
return `¡Hola, ${nombre}!`;
|
|
26
27
|
}
|
|
27
28
|
```
|
|
@@ -74,7 +75,7 @@ The HotMesh SDK is designed to keep your code front-and-center. Write functions
|
|
|
74
75
|
}
|
|
75
76
|
```
|
|
76
77
|
|
|
77
|
-
4. The last step is to create a **worker**
|
|
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.
|
|
78
79
|
```javascript
|
|
79
80
|
//worker.ts
|
|
80
81
|
|
|
@@ -232,7 +233,7 @@ For a deep dive into HotMesh's distributed orchestration philosophy, refer to th
|
|
|
232
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.
|
|
233
234
|
|
|
234
235
|
## System Lifecycle
|
|
235
|
-
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).
|
|
236
237
|
|
|
237
238
|
## Alpha Release
|
|
238
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/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
|
|
@@ -6,6 +6,7 @@ const asyncLocalStorage_1 = require("./asyncLocalStorage");
|
|
|
6
6
|
const hotmesh_1 = require("../hotmesh");
|
|
7
7
|
const stream_1 = require("../../types/stream");
|
|
8
8
|
const factory_1 = require("./factory");
|
|
9
|
+
const errors_1 = require("../../modules/errors");
|
|
9
10
|
/*
|
|
10
11
|
Here is an example of how the methods in this file are used:
|
|
11
12
|
|
|
@@ -39,13 +40,13 @@ run().catch((err) => {
|
|
|
39
40
|
});
|
|
40
41
|
*/
|
|
41
42
|
class WorkerService {
|
|
42
|
-
static async activateWorkflow(hotMesh, topic,
|
|
43
|
+
static async activateWorkflow(hotMesh, topic, dagFactory, options = {}) {
|
|
43
44
|
const version = '1';
|
|
44
45
|
const app = await hotMesh.engine.store.getApp(topic);
|
|
45
46
|
const appVersion = app?.version;
|
|
46
47
|
if (!appVersion) {
|
|
47
48
|
try {
|
|
48
|
-
await hotMesh.deploy(
|
|
49
|
+
await hotMesh.deploy(dagFactory(topic, version, options.maxSystemRetries, options.backoffExponent));
|
|
49
50
|
await hotMesh.activate(version);
|
|
50
51
|
}
|
|
51
52
|
catch (err) {
|
|
@@ -99,7 +100,7 @@ class WorkerService {
|
|
|
99
100
|
worker.activityRunner = await worker.initActivityWorkflow(config, activityTopic);
|
|
100
101
|
await WorkerService.activateWorkflow(worker.activityRunner, activityTopic, factory_1.getActivityYAML);
|
|
101
102
|
worker.workflowRunner = await worker.initWorkerWorkflow(config, workflowTopic, workflowFunction);
|
|
102
|
-
await WorkerService.activateWorkflow(worker.workflowRunner, workflowTopic, factory_1.getWorkflowYAML);
|
|
103
|
+
await WorkerService.activateWorkflow(worker.workflowRunner, workflowTopic, factory_1.getWorkflowYAML, config.options);
|
|
103
104
|
return worker;
|
|
104
105
|
}
|
|
105
106
|
static resolveWorkflowTarget(workflow) {
|
|
@@ -151,12 +152,16 @@ class WorkerService {
|
|
|
151
152
|
}
|
|
152
153
|
catch (err) {
|
|
153
154
|
this.activityRunner.engine.logger.error('durable-worker-activity-err', err);
|
|
155
|
+
if (!(err instanceof errors_1.DurableTimeoutError) &&
|
|
156
|
+
!(err instanceof errors_1.DurableMaxedError) &&
|
|
157
|
+
!(err instanceof errors_1.DurableFatalError)) {
|
|
158
|
+
err = new errors_1.DurableRetryError(err.message);
|
|
159
|
+
}
|
|
154
160
|
return {
|
|
155
161
|
status: stream_1.StreamStatus.ERROR,
|
|
156
|
-
code:
|
|
157
|
-
message: err.message,
|
|
162
|
+
code: err.code,
|
|
158
163
|
metadata: { ...data.metadata },
|
|
159
|
-
data: {
|
|
164
|
+
data: { message: err.message }
|
|
160
165
|
};
|
|
161
166
|
}
|
|
162
167
|
};
|
|
@@ -227,11 +232,12 @@ class WorkerService {
|
|
|
227
232
|
};
|
|
228
233
|
}
|
|
229
234
|
catch (err) {
|
|
235
|
+
// 59* - Durable*Error
|
|
230
236
|
return {
|
|
231
237
|
status: stream_1.StreamStatus.ERROR,
|
|
232
|
-
code:
|
|
238
|
+
code: err.code || new errors_1.DurableRetryError(err.message).code,
|
|
233
239
|
metadata: { ...data.metadata },
|
|
234
|
-
data: {
|
|
240
|
+
data: { message: err.message, type: err.name }
|
|
235
241
|
};
|
|
236
242
|
}
|
|
237
243
|
};
|
|
@@ -246,7 +252,7 @@ class WorkerService {
|
|
|
246
252
|
_a = WorkerService;
|
|
247
253
|
WorkerService.activityRegistry = {}; //user's activities
|
|
248
254
|
WorkerService.instances = new Map();
|
|
249
|
-
WorkerService.getHotMesh = async (worflowTopic) => {
|
|
255
|
+
WorkerService.getHotMesh = async (worflowTopic, options) => {
|
|
250
256
|
if (WorkerService.instances.has(worflowTopic)) {
|
|
251
257
|
return await WorkerService.instances.get(worflowTopic);
|
|
252
258
|
}
|
|
@@ -255,7 +261,7 @@ WorkerService.getHotMesh = async (worflowTopic) => {
|
|
|
255
261
|
engine: { redis: { ...WorkerService.connection } }
|
|
256
262
|
});
|
|
257
263
|
WorkerService.instances.set(worflowTopic, hotMesh);
|
|
258
|
-
await WorkerService.activateWorkflow(await hotMesh, worflowTopic, factory_1.getWorkflowYAML);
|
|
264
|
+
await WorkerService.activateWorkflow(await hotMesh, worflowTopic, factory_1.getWorkflowYAML, options);
|
|
259
265
|
return hotMesh;
|
|
260
266
|
};
|
|
261
267
|
WorkerService.Context = {
|
|
@@ -62,7 +62,7 @@ class WorkflowService {
|
|
|
62
62
|
const client = new client_1.ClientService({
|
|
63
63
|
connection: await connection_1.ConnectionService.connect(worker_1.WorkerService.connection),
|
|
64
64
|
});
|
|
65
|
-
//todo:
|
|
65
|
+
//todo: allow cross/app callback (pj:'@DURABLE@hello-world@<pjid>'/pa: <paid>/pd: <pdad>)
|
|
66
66
|
const handle = await client.workflow.start({
|
|
67
67
|
...options,
|
|
68
68
|
workflowId: `${workflowId}${options.workflowId}`,
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
declare class MathHandler {
|
|
2
|
+
add(...operands: (number | number[])[]): number;
|
|
3
|
+
subtract(...operands: (number | number[])[]): number;
|
|
4
|
+
multiply(...operands: (number | number[])[]): number;
|
|
5
|
+
divide(...operands: (number | number[])[]): number;
|
|
2
6
|
abs(x: number): number;
|
|
3
7
|
acos(x: number): number;
|
|
4
8
|
acosh(x: number): number;
|
|
@@ -2,6 +2,79 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MathHandler = void 0;
|
|
4
4
|
class MathHandler {
|
|
5
|
+
add(...operands) {
|
|
6
|
+
// @ts-ignore
|
|
7
|
+
return operands.reduce((a, b) => {
|
|
8
|
+
if (Array.isArray(b)) {
|
|
9
|
+
return a + this.add(...b);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
return a + b;
|
|
13
|
+
}
|
|
14
|
+
}, 0);
|
|
15
|
+
}
|
|
16
|
+
subtract(...operands) {
|
|
17
|
+
if (operands.length === 0) {
|
|
18
|
+
throw new Error('At least one operand is required.');
|
|
19
|
+
}
|
|
20
|
+
let flatOperands = [];
|
|
21
|
+
operands.forEach((op) => {
|
|
22
|
+
if (Array.isArray(op)) {
|
|
23
|
+
flatOperands = [...flatOperands, ...op];
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
flatOperands.push(op);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
if (flatOperands.length === 0) {
|
|
30
|
+
throw new Error('At least one operand is required after flattening.');
|
|
31
|
+
}
|
|
32
|
+
const result = flatOperands.reduce((a, b, i) => {
|
|
33
|
+
return i === 0 ? a : a - b;
|
|
34
|
+
});
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
multiply(...operands) {
|
|
38
|
+
if (operands.length === 0) {
|
|
39
|
+
throw new Error('At least one operand is required.');
|
|
40
|
+
}
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
return operands.reduce((a, b) => {
|
|
43
|
+
if (Array.isArray(b)) {
|
|
44
|
+
return a * this.multiply(...b);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
return a * b;
|
|
48
|
+
}
|
|
49
|
+
}, 1);
|
|
50
|
+
}
|
|
51
|
+
divide(...operands) {
|
|
52
|
+
if (operands.length === 0) {
|
|
53
|
+
throw new Error('At least one operand is required.');
|
|
54
|
+
}
|
|
55
|
+
let flatOperands = [];
|
|
56
|
+
operands.forEach((op) => {
|
|
57
|
+
if (Array.isArray(op)) {
|
|
58
|
+
flatOperands = [...flatOperands, ...op];
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
flatOperands.push(op);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
if (flatOperands.length === 0) {
|
|
65
|
+
throw new Error('At least one operand is required after flattening.');
|
|
66
|
+
}
|
|
67
|
+
const result = flatOperands.reduce((a, b, i) => {
|
|
68
|
+
if (b === 0) {
|
|
69
|
+
return NaN;
|
|
70
|
+
}
|
|
71
|
+
return i === 0 ? a : a / b;
|
|
72
|
+
});
|
|
73
|
+
if (isNaN(result)) {
|
|
74
|
+
return NaN;
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
5
78
|
abs(x) {
|
|
6
79
|
return Math.abs(x);
|
|
7
80
|
}
|
|
@@ -17,9 +17,5 @@ declare class NumberHandler {
|
|
|
17
17
|
min(...values: number[]): number;
|
|
18
18
|
pow(base: number, exponent: number): number;
|
|
19
19
|
round(input: number): number;
|
|
20
|
-
add(...operands: (number | number[])[]): number;
|
|
21
|
-
subtract(...operands: (number | number[])[]): number;
|
|
22
|
-
multiply(...operands: (number | number[])[]): number;
|
|
23
|
-
divide(...operands: (number | number[])[]): number;
|
|
24
20
|
}
|
|
25
21
|
export { NumberHandler };
|