@hotmeshio/hotmesh 0.0.12 → 0.0.14
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 +2 -2
- package/build/modules/errors.d.ts +22 -1
- package/build/modules/errors.js +28 -1
- package/build/modules/utils.d.ts +2 -1
- package/build/modules/utils.js +5 -1
- package/build/package.json +7 -2
- package/build/services/activities/activity.d.ts +2 -0
- package/build/services/activities/activity.js +16 -10
- package/build/services/activities/await.d.ts +2 -6
- package/build/services/activities/await.js +12 -75
- package/build/services/activities/cycle.js +2 -2
- package/build/services/activities/index.d.ts +2 -2
- package/build/services/activities/index.js +2 -2
- package/build/services/activities/signal.d.ts +16 -0
- package/build/services/activities/signal.js +94 -0
- package/build/services/activities/trigger.js +4 -3
- package/build/services/activities/worker.d.ts +2 -1
- package/build/services/activities/worker.js +11 -6
- package/build/services/compiler/deployer.js +3 -1
- package/build/services/durable/client.d.ts +3 -2
- package/build/services/durable/client.js +39 -21
- package/build/services/durable/factory.d.ts +22 -18
- package/build/services/durable/factory.js +722 -50
- package/build/services/durable/handle.d.ts +1 -0
- package/build/services/durable/handle.js +5 -1
- package/build/services/durable/worker.d.ts +3 -8
- package/build/services/durable/worker.js +75 -73
- package/build/services/durable/workflow.d.ts +5 -0
- package/build/services/durable/workflow.js +93 -24
- package/build/services/engine/index.d.ts +6 -6
- package/build/services/engine/index.js +25 -15
- package/build/services/hotmesh/index.d.ts +2 -1
- package/build/services/hotmesh/index.js +3 -1
- package/build/services/mapper/index.js +1 -1
- package/build/services/pipe/functions/array.d.ts +1 -0
- package/build/services/pipe/functions/array.js +3 -0
- package/build/services/reporter/index.js +9 -2
- package/build/services/signaler/store.js +8 -3
- package/build/services/signaler/stream.js +3 -3
- package/build/services/store/clients/ioredis.js +15 -15
- package/build/services/store/clients/redis.js +18 -18
- package/build/services/store/index.d.ts +1 -1
- package/build/services/store/index.js +11 -3
- package/build/services/task/index.js +3 -3
- package/build/types/activity.d.ts +15 -6
- package/build/types/durable.d.ts +15 -2
- package/build/types/index.d.ts +2 -2
- package/build/types/stats.d.ts +1 -0
- package/modules/errors.ts +35 -0
- package/modules/utils.ts +5 -1
- package/package.json +7 -2
- package/services/activities/activity.ts +19 -9
- package/services/activities/await.ts +14 -90
- package/services/activities/cycle.ts +2 -2
- package/services/activities/index.ts +2 -2
- package/services/activities/signal.ts +124 -0
- package/services/activities/trigger.ts +4 -3
- package/services/activities/worker.ts +13 -13
- package/services/compiler/deployer.ts +3 -1
- package/services/durable/client.ts +48 -23
- package/services/durable/factory.ts +723 -49
- package/services/durable/handle.ts +6 -1
- package/services/durable/worker.ts +92 -79
- package/services/durable/workflow.ts +95 -25
- package/services/engine/index.ts +33 -24
- package/services/hotmesh/index.ts +7 -4
- package/services/mapper/index.ts +1 -1
- package/services/pipe/functions/array.ts +4 -0
- package/services/reporter/index.ts +10 -2
- package/services/signaler/store.ts +8 -3
- package/services/signaler/stream.ts +3 -3
- package/services/store/clients/ioredis.ts +15 -15
- package/services/store/clients/redis.ts +18 -18
- package/services/store/index.ts +12 -3
- package/services/task/index.ts +3 -3
- package/types/activity.ts +16 -7
- package/types/durable.ts +18 -1
- package/types/index.ts +2 -1
- package/types/stats.ts +1 -0
- package/build/services/activities/emit.d.ts +0 -9
- package/build/services/activities/emit.js +0 -13
- package/services/activities/emit.ts +0 -25
|
@@ -5,6 +5,7 @@ const errors_1 = require("../../modules/errors");
|
|
|
5
5
|
const activity_1 = require("./activity");
|
|
6
6
|
const collator_1 = require("../collator");
|
|
7
7
|
const telemetry_1 = require("../telemetry");
|
|
8
|
+
const pipe_1 = require("../pipe");
|
|
8
9
|
class Worker extends activity_1.Activity {
|
|
9
10
|
constructor(config, data, metadata, hook, engine, context) {
|
|
10
11
|
super(config, data, metadata, hook, engine, context);
|
|
@@ -14,21 +15,24 @@ class Worker extends activity_1.Activity {
|
|
|
14
15
|
this.logger.debug('worker-process', { jid: this.context.metadata.jid, aid: this.metadata.aid });
|
|
15
16
|
let telemetry;
|
|
16
17
|
try {
|
|
18
|
+
//confirm entry is allowed and restore state
|
|
17
19
|
this.setLeg(1);
|
|
18
20
|
await collator_1.CollatorService.notarizeEntry(this);
|
|
19
21
|
await this.getState();
|
|
20
22
|
telemetry = new telemetry_1.TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
21
23
|
telemetry.startActivitySpan(this.leg);
|
|
22
24
|
this.mapInputData();
|
|
25
|
+
//save state and authorize reentry
|
|
23
26
|
const multi = this.store.getMulti();
|
|
24
27
|
//todo: await this.registerTimeout();
|
|
28
|
+
const messageId = await this.execActivity(multi);
|
|
25
29
|
await collator_1.CollatorService.authorizeReentry(this, multi);
|
|
26
30
|
await this.setState(multi);
|
|
27
31
|
await this.setStatus(0, multi);
|
|
28
32
|
const multiResponse = await multi.exec();
|
|
33
|
+
//telemetry
|
|
29
34
|
telemetry.mapActivityAttributes();
|
|
30
35
|
const jobStatus = this.resolveStatus(multiResponse);
|
|
31
|
-
const messageId = await this.execActivity();
|
|
32
36
|
telemetry.setActivityAttributes({
|
|
33
37
|
'app.activity.mid': messageId,
|
|
34
38
|
'app.job.jss': jobStatus
|
|
@@ -37,10 +41,10 @@ class Worker extends activity_1.Activity {
|
|
|
37
41
|
}
|
|
38
42
|
catch (error) {
|
|
39
43
|
if (error instanceof errors_1.GetStateError) {
|
|
40
|
-
this.logger.error('worker-get-state-error', error);
|
|
44
|
+
this.logger.error('worker-get-state-error', { error });
|
|
41
45
|
}
|
|
42
46
|
else {
|
|
43
|
-
this.logger.error('worker-process-error', error);
|
|
47
|
+
this.logger.error('worker-process-error', { error });
|
|
44
48
|
}
|
|
45
49
|
telemetry.setActivityError(error.message);
|
|
46
50
|
throw error;
|
|
@@ -50,13 +54,14 @@ class Worker extends activity_1.Activity {
|
|
|
50
54
|
this.logger.debug('worker-process-end', { jid: this.context.metadata.jid, aid: this.metadata.aid });
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
|
-
async execActivity() {
|
|
57
|
+
async execActivity(multi) {
|
|
58
|
+
const topic = pipe_1.Pipe.resolve(this.config.subtype, this.context);
|
|
54
59
|
const streamData = {
|
|
55
60
|
metadata: {
|
|
56
61
|
jid: this.context.metadata.jid,
|
|
57
62
|
dad: this.metadata.dad,
|
|
58
63
|
aid: this.metadata.aid,
|
|
59
|
-
topic
|
|
64
|
+
topic,
|
|
60
65
|
spn: this.context['$self'].output.metadata.l1s,
|
|
61
66
|
trc: this.context.metadata.trc,
|
|
62
67
|
},
|
|
@@ -67,7 +72,7 @@ class Worker extends activity_1.Activity {
|
|
|
67
72
|
retry: this.config.retry
|
|
68
73
|
};
|
|
69
74
|
}
|
|
70
|
-
return (await this.engine.streamSignaler?.publishMessage(
|
|
75
|
+
return (await this.engine.streamSignaler?.publishMessage(topic, streamData, multi));
|
|
71
76
|
}
|
|
72
77
|
}
|
|
73
78
|
exports.Worker = Worker;
|
|
@@ -5,6 +5,7 @@ const key_1 = require("../../modules/key");
|
|
|
5
5
|
const utils_1 = require("../../modules/utils");
|
|
6
6
|
const collator_1 = require("../collator");
|
|
7
7
|
const serializer_1 = require("../serializer");
|
|
8
|
+
const pipe_1 = require("../pipe");
|
|
8
9
|
const DEFAULT_METADATA_RANGE_SIZE = 26; //metadata is 26 slots ([a-z] * 1)
|
|
9
10
|
const DEFAULT_DATA_RANGE_SIZE = 260; //data is 260 slots ([a-zA-Z] * 5)
|
|
10
11
|
const DEFAULT_RANGE_SIZE = DEFAULT_METADATA_RANGE_SIZE + DEFAULT_DATA_RANGE_SIZE;
|
|
@@ -405,7 +406,8 @@ class Deployer {
|
|
|
405
406
|
const activities = graph.activities;
|
|
406
407
|
for (const activityKey in activities) {
|
|
407
408
|
const activity = activities[activityKey];
|
|
408
|
-
if
|
|
409
|
+
//only precreate if the topic is concrete and not `mappable`
|
|
410
|
+
if (activity.type === 'worker' && pipe_1.Pipe.resolve(activity.subtype, {}) === activity.subtype) {
|
|
409
411
|
params.topic = activity.subtype;
|
|
410
412
|
const key = this.store.mintKey(key_1.KeyType.STREAMS, params);
|
|
411
413
|
//create one worker group per unique activity subtype (the topic)
|
|
@@ -6,10 +6,11 @@ export declare class ClientService {
|
|
|
6
6
|
options: WorkflowOptions;
|
|
7
7
|
static instances: Map<string, HotMesh | Promise<HotMesh>>;
|
|
8
8
|
constructor(config: ClientConfig);
|
|
9
|
-
|
|
9
|
+
getHotMeshClient: (worflowTopic: string) => Promise<HotMesh>;
|
|
10
10
|
workflow: {
|
|
11
11
|
start: (options: WorkflowOptions) => Promise<WorkflowHandleService>;
|
|
12
|
+
signal: (signalId: string, data: Record<any, any>) => Promise<string>;
|
|
12
13
|
};
|
|
13
|
-
activateWorkflow(hotMesh: HotMesh,
|
|
14
|
+
activateWorkflow(hotMesh: HotMesh, appId?: string, version?: string): Promise<void>;
|
|
14
15
|
static shutdown(): Promise<void>;
|
|
15
16
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ClientService = void 0;
|
|
4
|
+
const nanoid_1 = require("nanoid");
|
|
5
|
+
const factory_1 = require("./factory");
|
|
4
6
|
const handle_1 = require("./handle");
|
|
5
7
|
const hotmesh_1 = require("../hotmesh");
|
|
6
|
-
const
|
|
8
|
+
const key_1 = require("../../modules/key");
|
|
7
9
|
/*
|
|
8
10
|
Here is an example of how the methods in this file are used:
|
|
9
11
|
|
|
@@ -46,12 +48,13 @@ run().catch((err) => {
|
|
|
46
48
|
*/
|
|
47
49
|
class ClientService {
|
|
48
50
|
constructor(config) {
|
|
49
|
-
this.
|
|
51
|
+
this.getHotMeshClient = async (worflowTopic) => {
|
|
52
|
+
//NOTE: every unique topic inits a new engine
|
|
50
53
|
if (ClientService.instances.has(worflowTopic)) {
|
|
51
54
|
return await ClientService.instances.get(worflowTopic);
|
|
52
55
|
}
|
|
53
|
-
const
|
|
54
|
-
appId:
|
|
56
|
+
const hotMeshClient = hotmesh_1.HotMeshService.init({
|
|
57
|
+
appId: factory_1.APP_ID,
|
|
55
58
|
engine: {
|
|
56
59
|
redis: {
|
|
57
60
|
class: this.connection.class,
|
|
@@ -59,9 +62,19 @@ class ClientService {
|
|
|
59
62
|
}
|
|
60
63
|
}
|
|
61
64
|
});
|
|
62
|
-
ClientService.instances.set(worflowTopic,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
ClientService.instances.set(worflowTopic, hotMeshClient);
|
|
66
|
+
//since the YAML topic is dynamic, it MUST be manually created before use
|
|
67
|
+
const store = (await hotMeshClient).engine.store;
|
|
68
|
+
const params = { appId: factory_1.APP_ID, topic: worflowTopic };
|
|
69
|
+
const streamKey = store.mintKey(key_1.KeyType.STREAMS, params);
|
|
70
|
+
try {
|
|
71
|
+
await store.xgroup('CREATE', streamKey, 'WORKER', '$', 'MKSTREAM');
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
//ignore if already exists
|
|
75
|
+
}
|
|
76
|
+
await this.activateWorkflow(await hotMeshClient);
|
|
77
|
+
return hotMeshClient;
|
|
65
78
|
};
|
|
66
79
|
this.workflow = {
|
|
67
80
|
start: async (options) => {
|
|
@@ -69,40 +82,45 @@ class ClientService {
|
|
|
69
82
|
const workflowName = options.workflowName;
|
|
70
83
|
const trc = options.workflowTrace;
|
|
71
84
|
const spn = options.workflowSpan;
|
|
85
|
+
//topic is concat of taskQueue and workflowName
|
|
72
86
|
const workflowTopic = `${taskQueueName}-${workflowName}`;
|
|
73
|
-
const
|
|
87
|
+
const hotMeshClient = await this.getHotMeshClient(workflowTopic);
|
|
74
88
|
const payload = {
|
|
75
89
|
arguments: [...options.args],
|
|
76
|
-
workflowId: options.workflowId,
|
|
90
|
+
workflowId: options.workflowId || (0, nanoid_1.nanoid)(),
|
|
91
|
+
workflowTopic: workflowTopic,
|
|
92
|
+
backoffCoefficient: options.config?.backoffCoefficient || factory_1.DEFAULT_COEFFICIENT,
|
|
77
93
|
};
|
|
78
94
|
const context = { metadata: { trc, spn }, data: {} };
|
|
79
|
-
const jobId = await
|
|
80
|
-
return new handle_1.WorkflowHandleService(
|
|
95
|
+
const jobId = await hotMeshClient.pub(factory_1.SUBSCRIBES_TOPIC, payload, context);
|
|
96
|
+
return new handle_1.WorkflowHandleService(hotMeshClient, workflowTopic, jobId);
|
|
81
97
|
},
|
|
98
|
+
signal: async (signalId, data) => {
|
|
99
|
+
return await (await this.getHotMeshClient('durable.wfs.signal')).hook('durable.wfs.signal', { id: signalId, data });
|
|
100
|
+
}
|
|
82
101
|
};
|
|
83
102
|
this.connection = config.connection;
|
|
84
103
|
}
|
|
85
|
-
async activateWorkflow(hotMesh,
|
|
86
|
-
const
|
|
87
|
-
const app = await hotMesh.engine.store.getApp(workflowTopic);
|
|
104
|
+
async activateWorkflow(hotMesh, appId = factory_1.APP_ID, version = factory_1.APP_VERSION) {
|
|
105
|
+
const app = await hotMesh.engine.store.getApp(appId);
|
|
88
106
|
const appVersion = app?.version;
|
|
89
107
|
if (isNaN(appVersion)) {
|
|
90
108
|
try {
|
|
91
|
-
await hotMesh.deploy((0, factory_1.getWorkflowYAML)(
|
|
109
|
+
await hotMesh.deploy((0, factory_1.getWorkflowYAML)(appId, version));
|
|
92
110
|
await hotMesh.activate(version);
|
|
93
111
|
}
|
|
94
|
-
catch (
|
|
95
|
-
hotMesh.engine.logger.error('durable-client-deploy-activate-err',
|
|
96
|
-
throw
|
|
112
|
+
catch (error) {
|
|
113
|
+
hotMesh.engine.logger.error('durable-client-deploy-activate-err', { error });
|
|
114
|
+
throw error;
|
|
97
115
|
}
|
|
98
116
|
}
|
|
99
117
|
else if (app && !app.active) {
|
|
100
118
|
try {
|
|
101
119
|
await hotMesh.activate(version);
|
|
102
120
|
}
|
|
103
|
-
catch (
|
|
104
|
-
hotMesh.engine.logger.error('durable-client-activate-err',
|
|
105
|
-
throw
|
|
121
|
+
catch (error) {
|
|
122
|
+
hotMesh.engine.logger.error('durable-client-activate-err', { error });
|
|
123
|
+
throw error;
|
|
106
124
|
}
|
|
107
125
|
}
|
|
108
126
|
}
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
|
2
|
+
* NOTE: Using `maxSystemRetries = 3` and `backoffCoefficient = 10`, errant
|
|
3
|
+
* workflows will be retried on the following schedule (8 times in 27 hours):
|
|
4
|
+
* => 10ms, 100ms, 1000ms, 10s, 100s, 1_000s, 10_000s, 100_000s
|
|
17
5
|
*/
|
|
18
|
-
declare const getWorkflowYAML: (
|
|
19
|
-
declare const
|
|
20
|
-
|
|
6
|
+
declare const getWorkflowYAML: (app: string, version: string) => string;
|
|
7
|
+
declare const APP_VERSION = "1";
|
|
8
|
+
declare const APP_ID = "durable";
|
|
9
|
+
declare const ACTIVITY_SUBSCRIBES_TOPIC = "durable.activity.execute";
|
|
10
|
+
declare const ACTIVITY_PUBLISHES_TOPIC = "durable.activity.executed";
|
|
11
|
+
declare const SLEEP_SUBSCRIBES_TOPIC = "durable.sleep.execute";
|
|
12
|
+
declare const SLEEP_PUBLISHES_TOPIC = "durable.sleep.executed";
|
|
13
|
+
declare const WFS_SUBSCRIBES_TOPIC = "durable.wfs.execute";
|
|
14
|
+
declare const WFS_PUBLISHES_TOPIC = "durable.wfs.executed";
|
|
15
|
+
declare const WFSC_SUBSCRIBES_TOPIC = "durable.wfsc.execute";
|
|
16
|
+
declare const WFSC_PUBLISHES_TOPIC = "durable.wfsc.executed";
|
|
17
|
+
declare const SUBSCRIBES_TOPIC = "durable.execute";
|
|
18
|
+
declare const PUBLISHES_TOPIC = "durable.executed";
|
|
19
|
+
declare const HOOK_ID = "durable.awaken";
|
|
20
|
+
declare const ACTIVITY_HOOK_ID = "durable.activity.awaken";
|
|
21
|
+
declare const SLEEP_HOOK_ID = "durable.sleep.awaken";
|
|
22
|
+
declare const WFS_HOOK_ID = "durable.wfs.awaken";
|
|
23
|
+
declare const DEFAULT_COEFFICIENT = 10;
|
|
24
|
+
export { getWorkflowYAML, APP_VERSION, APP_ID, ACTIVITY_SUBSCRIBES_TOPIC, ACTIVITY_PUBLISHES_TOPIC, SLEEP_SUBSCRIBES_TOPIC, SLEEP_PUBLISHES_TOPIC, SUBSCRIBES_TOPIC, PUBLISHES_TOPIC, HOOK_ID, ACTIVITY_HOOK_ID, SLEEP_HOOK_ID, DEFAULT_COEFFICIENT, WFS_SUBSCRIBES_TOPIC, WFS_PUBLISHES_TOPIC, WFSC_SUBSCRIBES_TOPIC, WFSC_PUBLISHES_TOPIC, WFS_HOOK_ID, };
|