@hotmeshio/hotmesh 0.1.7 → 0.1.9
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 +9 -9
- package/build/modules/utils.d.ts +1 -0
- package/build/modules/utils.js +10 -3
- package/build/package.json +1 -2
- package/build/services/activities/activity.d.ts +5 -7
- package/build/services/activities/activity.js +8 -10
- package/build/services/durable/client.d.ts +1 -1
- package/build/services/durable/client.js +24 -26
- package/build/services/durable/worker.d.ts +1 -2
- package/build/services/durable/worker.js +20 -10
- package/build/services/durable/workflow.js +14 -4
- package/build/types/durable.d.ts +4 -0
- package/build/types/index.d.ts +1 -1
- package/package.json +1 -2
- package/types/durable.ts +5 -0
- package/types/index.ts +4 -1
package/README.md
CHANGED
|
@@ -295,31 +295,31 @@ const hotMesh = await HotMesh.init({
|
|
|
295
295
|
```
|
|
296
296
|
|
|
297
297
|
### Observability
|
|
298
|
-
Workflows and activities are run according to the rules you define, offering [Graph-Oriented](https://github.com/hotmeshio/sdk-typescript/
|
|
298
|
+
Workflows and activities are run according to the rules you define, offering [Graph-Oriented](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/system_lifecycle.md#telemetry) telemetry insights into your legacy function executions.
|
|
299
299
|
|
|
300
300
|
## FAQ
|
|
301
|
-
Refer to the [FAQ](https://github.com/hotmeshio/sdk-typescript/
|
|
301
|
+
Refer to the [FAQ](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/faq.md) for terminology, definitions, and an exploration of how HotMesh facilitates orchestration use cases.
|
|
302
302
|
|
|
303
303
|
## Quick Start
|
|
304
|
-
Refer to the [Quick Start](https://github.com/hotmeshio/sdk-typescript/
|
|
304
|
+
Refer to the [Quick Start](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/quickstart.md) for sample YAML workflows you can copy, paste, and modify to get started.
|
|
305
305
|
|
|
306
306
|
## Developer Guide
|
|
307
|
-
For more details on the complete development process, including information about schemas, APIs, and deployment, consult the [Developer Guide](https://github.com/hotmeshio/sdk-typescript/
|
|
307
|
+
For more details on the complete development process, including information about schemas, APIs, and deployment, consult the [Developer Guide](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/developer_guide.md).
|
|
308
308
|
|
|
309
309
|
## Model Driven Development
|
|
310
|
-
[Model Driven Development](https://github.com/hotmeshio/sdk-typescript/
|
|
310
|
+
[Model Driven Development](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/model_driven_development.md) is an established strategy for managing process-oriented tasks. Check out this guide to understand its foundational principles.
|
|
311
311
|
|
|
312
312
|
## Data Mapping
|
|
313
|
-
Exchanging data between activities is central to HotMesh. For detailed information on supported functions and the functional mapping syntax (@pipes), see the [Data Mapping Overview](https://github.com/hotmeshio/sdk-typescript/
|
|
313
|
+
Exchanging data between activities is central to HotMesh. For detailed information on supported functions and the functional mapping syntax (@pipes), see the [Data Mapping Overview](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/data_mapping.md).
|
|
314
314
|
|
|
315
315
|
## Composition
|
|
316
|
-
While the simplest graphs are linear, detailing a consistent sequence of non-cyclical activities, graphs can be layered to represent intricate business scenarios. Some can even be designed to accommodate long-lasting workflows that span months. For more details, check out the [Composable Workflow Guide](https://github.com/hotmeshio/sdk-typescript/
|
|
316
|
+
While the simplest graphs are linear, detailing a consistent sequence of non-cyclical activities, graphs can be layered to represent intricate business scenarios. Some can even be designed to accommodate long-lasting workflows that span months. For more details, check out the [Composable Workflow Guide](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/composable_workflow.md).
|
|
317
317
|
|
|
318
318
|
## System Lifecycle
|
|
319
|
-
Gain insight into HotMesh's monitoring, exception handling, and alarm configurations via the [System Lifecycle Guide](https://github.com/hotmeshio/sdk-typescript/
|
|
319
|
+
Gain insight into HotMesh's monitoring, exception handling, and alarm configurations via the [System Lifecycle Guide](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/system_lifecycle.md).
|
|
320
320
|
|
|
321
321
|
## Distributed Orchestration | System Overview
|
|
322
|
-
HotMesh is a distributed orchestration engine. Refer to the [Distributed Orchestration Guide](https://github.com/hotmeshio/sdk-typescript/
|
|
322
|
+
HotMesh is a distributed orchestration engine. Refer to the [Distributed Orchestration Guide](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/distributed_orchestration.md) for a high-level overview of the approach.
|
|
323
323
|
|
|
324
324
|
## Distributed Orchestration | System Design
|
|
325
325
|
HotMesh is more than Redis and TypeScript. The theory that underlies the architecture is applicable to any number of data storage and streaming backends: [A Message-Oriented Approach to Decentralized Process Orchestration](https://zenodo.org/records/12168558).
|
package/build/modules/utils.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { RedisClient, RedisMulti } from '../types/redis';
|
|
|
5
5
|
import { StringAnyType } from '../types/serializer';
|
|
6
6
|
import { StreamCode, StreamStatus } from '../types/stream';
|
|
7
7
|
import { SystemHealth } from '../types/quorum';
|
|
8
|
+
export declare const hashOptions: (options: any) => string;
|
|
8
9
|
export declare function getSystemHealth(): Promise<SystemHealth>;
|
|
9
10
|
export declare function sleepFor(ms: number): Promise<unknown>;
|
|
10
11
|
export declare function sleepImmediate(): Promise<void>;
|
package/build/modules/utils.js
CHANGED
|
@@ -3,8 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.restoreHierarchy = exports.getValueByPath = exports.getIndexedHash = exports.getSymVal = exports.getSymKey = exports.formatISODate = exports.getTimeSeries = exports.getSubscriptionTopic = exports.findSubscriptionForTrigger = exports.findTopKey = exports.XSleepFor = exports.matchesStatus = exports.matchesStatusCode = exports.identifyRedisTypeFromClass = exports.polyfill = exports.identifyRedisType = exports.deterministicRandom = exports.guid = exports.deepCopy = exports.sleepImmediate = exports.sleepFor = exports.getSystemHealth = void 0;
|
|
6
|
+
exports.restoreHierarchy = exports.getValueByPath = exports.getIndexedHash = exports.getSymVal = exports.getSymKey = exports.formatISODate = exports.getTimeSeries = exports.getSubscriptionTopic = exports.findSubscriptionForTrigger = exports.findTopKey = exports.XSleepFor = exports.matchesStatus = exports.matchesStatusCode = exports.identifyRedisTypeFromClass = exports.polyfill = exports.identifyRedisType = exports.deterministicRandom = exports.guid = exports.deepCopy = exports.sleepImmediate = exports.sleepFor = exports.getSystemHealth = exports.hashOptions = void 0;
|
|
7
7
|
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const crypto_1 = require("crypto");
|
|
8
9
|
const nanoid_1 = require("nanoid");
|
|
9
10
|
const enums_1 = require("./enums");
|
|
10
11
|
async function safeExecute(operation, defaultValue) {
|
|
@@ -16,10 +17,17 @@ async function safeExecute(operation, defaultValue) {
|
|
|
16
17
|
return defaultValue;
|
|
17
18
|
}
|
|
18
19
|
}
|
|
20
|
+
const hashOptions = (options) => {
|
|
21
|
+
const str = JSON.stringify(options);
|
|
22
|
+
return (0, crypto_1.createHash)('sha256').update(str).digest('hex');
|
|
23
|
+
};
|
|
24
|
+
exports.hashOptions = hashOptions;
|
|
19
25
|
async function getSystemHealth() {
|
|
20
26
|
const totalMemory = os_1.default.totalmem();
|
|
21
27
|
const freeMemory = os_1.default.freemem();
|
|
22
28
|
const usedMemory = totalMemory - freeMemory;
|
|
29
|
+
//NOTE: enable the following if desired; for now, only
|
|
30
|
+
// `memory` is emitted when system health is requested
|
|
23
31
|
//const cpus = os.cpus();
|
|
24
32
|
// CPU load calculation remains unchanged
|
|
25
33
|
// const cpuLoad = cpus.map((cpu, i) => {
|
|
@@ -28,9 +36,8 @@ async function getSystemHealth() {
|
|
|
28
36
|
// const usage = ((total - idle) / total) * 100;
|
|
29
37
|
// return { [`CPU ${i} Usage`]: `${usage.toFixed(2)}%` };
|
|
30
38
|
// });
|
|
31
|
-
// Wrap each systeminformation call with safeExecute
|
|
39
|
+
// Wrap each systeminformation call with safeExecute (systeminformation npm package)
|
|
32
40
|
//const networkStats = await safeExecute(si.networkStats(), []);
|
|
33
|
-
// Construct the system health object with error handling in mind
|
|
34
41
|
const systemHealth = {
|
|
35
42
|
TotalMemoryGB: `${(totalMemory / 1024 / 1024 / 1024).toFixed(2)} GB`,
|
|
36
43
|
FreeMemoryGB: `${(freeMemory / 1024 / 1024 / 1024).toFixed(2)} GB`,
|
package/build/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotmeshio/hotmesh",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Unbreakable Workflows",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -84,7 +84,6 @@
|
|
|
84
84
|
"js-yaml": "^4.1.0",
|
|
85
85
|
"ms": "^2.1.3",
|
|
86
86
|
"nanoid": "^3.3.6",
|
|
87
|
-
"systeminformation": "^5.22.2",
|
|
88
87
|
"winston": "^3.8.2"
|
|
89
88
|
},
|
|
90
89
|
"devDependencies": {
|
|
@@ -27,19 +27,17 @@ declare class Activity {
|
|
|
27
27
|
constructor(config: ActivityType, data: ActivityData, metadata: ActivityMetadata, hook: ActivityData | null, engine: EngineService, context?: JobState);
|
|
28
28
|
setLeg(leg: ActivityLeg): void;
|
|
29
29
|
/**
|
|
30
|
-
* Upon entering leg 1 of a duplexed
|
|
31
|
-
* all aspects of the entry including job and activty state
|
|
30
|
+
* Upon entering leg 1 of a duplexed activity
|
|
32
31
|
*/
|
|
33
32
|
verifyEntry(): Promise<void>;
|
|
34
33
|
/**
|
|
35
|
-
* Upon entering leg 2 of a duplexed
|
|
36
|
-
* all aspects of the re-entry including job and activty state
|
|
34
|
+
* Upon entering leg 2 of a duplexed activity
|
|
37
35
|
*/
|
|
38
36
|
verifyReentry(): Promise<number>;
|
|
39
37
|
processEvent(status?: StreamStatus, code?: StreamCode, type?: 'hook' | 'output'): Promise<void>;
|
|
40
|
-
processPending(
|
|
41
|
-
processSuccess(
|
|
42
|
-
processError(
|
|
38
|
+
processPending(type: 'hook' | 'output'): Promise<MultiResponseFlags>;
|
|
39
|
+
processSuccess(type: 'hook' | 'output'): Promise<MultiResponseFlags>;
|
|
40
|
+
processError(): Promise<MultiResponseFlags>;
|
|
43
41
|
transitionAdjacent(multiResponse: MultiResponseFlags, telemetry: TelemetryService): Promise<void>;
|
|
44
42
|
resolveStatus(multiResponse: MultiResponseFlags): number;
|
|
45
43
|
mapJobData(): void;
|
|
@@ -31,8 +31,7 @@ class Activity {
|
|
|
31
31
|
this.leg = leg;
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
-
* Upon entering leg 1 of a duplexed
|
|
35
|
-
* all aspects of the entry including job and activty state
|
|
34
|
+
* Upon entering leg 1 of a duplexed activity
|
|
36
35
|
*/
|
|
37
36
|
async verifyEntry() {
|
|
38
37
|
this.setLeg(1);
|
|
@@ -41,8 +40,7 @@ class Activity {
|
|
|
41
40
|
await collator_1.CollatorService.notarizeEntry(this);
|
|
42
41
|
}
|
|
43
42
|
/**
|
|
44
|
-
* Upon entering leg 2 of a duplexed
|
|
45
|
-
* all aspects of the re-entry including job and activty state
|
|
43
|
+
* Upon entering leg 2 of a duplexed activity
|
|
46
44
|
*/
|
|
47
45
|
async verifyReentry() {
|
|
48
46
|
const guid = this.context.metadata.guid;
|
|
@@ -79,13 +77,13 @@ class Activity {
|
|
|
79
77
|
telemetry.startActivitySpan(this.leg);
|
|
80
78
|
let multiResponse;
|
|
81
79
|
if (status === stream_1.StreamStatus.PENDING) {
|
|
82
|
-
multiResponse = await this.processPending(
|
|
80
|
+
multiResponse = await this.processPending(type);
|
|
83
81
|
}
|
|
84
82
|
else if (status === stream_1.StreamStatus.SUCCESS) {
|
|
85
|
-
multiResponse = await this.processSuccess(
|
|
83
|
+
multiResponse = await this.processSuccess(type);
|
|
86
84
|
}
|
|
87
85
|
else {
|
|
88
|
-
multiResponse = await this.processError(
|
|
86
|
+
multiResponse = await this.processError();
|
|
89
87
|
}
|
|
90
88
|
this.transitionAdjacent(multiResponse, telemetry);
|
|
91
89
|
}
|
|
@@ -120,7 +118,7 @@ class Activity {
|
|
|
120
118
|
this.logger.debug('activity-process-event-end', { jid, aid });
|
|
121
119
|
}
|
|
122
120
|
}
|
|
123
|
-
async processPending(
|
|
121
|
+
async processPending(type) {
|
|
124
122
|
this.bindActivityData(type);
|
|
125
123
|
this.adjacencyList = await this.filterAdjacent();
|
|
126
124
|
this.mapJobData();
|
|
@@ -130,7 +128,7 @@ class Activity {
|
|
|
130
128
|
await this.setStatus(this.adjacencyList.length, multi);
|
|
131
129
|
return (await multi.exec());
|
|
132
130
|
}
|
|
133
|
-
async processSuccess(
|
|
131
|
+
async processSuccess(type) {
|
|
134
132
|
this.bindActivityData(type);
|
|
135
133
|
this.adjacencyList = await this.filterAdjacent();
|
|
136
134
|
this.mapJobData();
|
|
@@ -140,7 +138,7 @@ class Activity {
|
|
|
140
138
|
await this.setStatus(this.adjacencyList.length - 1, multi);
|
|
141
139
|
return (await multi.exec());
|
|
142
140
|
}
|
|
143
|
-
async processError(
|
|
141
|
+
async processError() {
|
|
144
142
|
this.bindActivityError(this.data);
|
|
145
143
|
this.adjacencyList = await this.filterAdjacent();
|
|
146
144
|
if (!this.adjacencyList.length) {
|
|
@@ -20,7 +20,7 @@ export declare class ClientService {
|
|
|
20
20
|
* creating the stream. This method will verify that the stream
|
|
21
21
|
* exists and if not, create it.
|
|
22
22
|
*/
|
|
23
|
-
|
|
23
|
+
verifyStream: (hotMeshClient: HotMesh, workflowTopic: string, namespace?: string) => Promise<void>;
|
|
24
24
|
search: (hotMeshClient: HotMesh, index: string, query: string[]) => Promise<string[]>;
|
|
25
25
|
workflow: {
|
|
26
26
|
start: (options: WorkflowOptions) => Promise<WorkflowHandleService>;
|
|
@@ -17,14 +17,14 @@ const factory_1 = require("./schemas/factory");
|
|
|
17
17
|
class ClientService {
|
|
18
18
|
constructor(config) {
|
|
19
19
|
this.getHotMeshClient = async (workflowTopic, namespace) => {
|
|
20
|
+
//namespace isolation requires the connection options to be hashed
|
|
21
|
+
//as multiple intersecting databases can be used by the same service
|
|
22
|
+
const optionsHash = (0, utils_1.hashOptions)(this.connection.options);
|
|
20
23
|
const targetNS = namespace ?? factory_1.APP_ID;
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
const connectionNS = `${optionsHash}.${targetNS}`;
|
|
25
|
+
if (ClientService.instances.has(connectionNS)) {
|
|
26
|
+
const hotMeshClient = await ClientService.instances.get(connectionNS);
|
|
23
27
|
await this.verifyWorkflowActive(hotMeshClient, targetNS);
|
|
24
|
-
if (!ClientService.topics.includes(workflowTopic)) {
|
|
25
|
-
ClientService.topics.push(workflowTopic);
|
|
26
|
-
await ClientService.createStream(hotMeshClient, workflowTopic, namespace);
|
|
27
|
-
}
|
|
28
28
|
return hotMeshClient;
|
|
29
29
|
}
|
|
30
30
|
//create and cache an instance
|
|
@@ -38,11 +38,24 @@ class ClientService {
|
|
|
38
38
|
},
|
|
39
39
|
},
|
|
40
40
|
});
|
|
41
|
-
ClientService.instances.set(
|
|
42
|
-
await ClientService.createStream(await hotMeshClient, workflowTopic, namespace);
|
|
41
|
+
ClientService.instances.set(connectionNS, hotMeshClient);
|
|
43
42
|
await this.activateWorkflow(await hotMeshClient, targetNS);
|
|
44
43
|
return hotMeshClient;
|
|
45
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* It is possible for a client to invoke a workflow without first
|
|
47
|
+
* creating the stream. This method will verify that the stream
|
|
48
|
+
* exists and if not, create it.
|
|
49
|
+
*/
|
|
50
|
+
this.verifyStream = async (hotMeshClient, workflowTopic, namespace) => {
|
|
51
|
+
const optionsHash = (0, utils_1.hashOptions)(this.connection.options);
|
|
52
|
+
const targetNS = namespace ?? factory_1.APP_ID;
|
|
53
|
+
const targetTopic = `${optionsHash}.${targetNS}.${workflowTopic}`;
|
|
54
|
+
if (!ClientService.topics.includes(targetTopic)) {
|
|
55
|
+
ClientService.topics.push(targetTopic);
|
|
56
|
+
await ClientService.createStream(hotMeshClient, workflowTopic, namespace);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
46
59
|
this.search = async (hotMeshClient, index, query) => {
|
|
47
60
|
const store = hotMeshClient.engine.store;
|
|
48
61
|
if (query[0]?.startsWith('FT.')) {
|
|
@@ -56,10 +69,11 @@ class ClientService {
|
|
|
56
69
|
const workflowName = options.entity ?? options.workflowName;
|
|
57
70
|
const trc = options.workflowTrace;
|
|
58
71
|
const spn = options.workflowSpan;
|
|
59
|
-
//
|
|
60
|
-
// the taskQueue and workflowName used by the Durable module
|
|
72
|
+
//hotmesh topic is a combination of the durable queue+workflowname
|
|
61
73
|
const workflowTopic = `${taskQueueName}-${workflowName}`;
|
|
62
74
|
const hotMeshClient = await this.getHotMeshClient(workflowTopic, options.namespace);
|
|
75
|
+
//verify that the stream channel exists before enqueueing
|
|
76
|
+
await this.verifyStream(hotMeshClient, workflowTopic, options.namespace);
|
|
63
77
|
const payload = {
|
|
64
78
|
arguments: [...options.args],
|
|
65
79
|
originJobId: options.originJobId,
|
|
@@ -199,20 +213,4 @@ ClientService.createStream = async (hotMeshClient, workflowTopic, namespace) =>
|
|
|
199
213
|
//ignore if already exists
|
|
200
214
|
}
|
|
201
215
|
};
|
|
202
|
-
/**
|
|
203
|
-
* It is possible for a client to invoke a workflow without first
|
|
204
|
-
* creating the stream. This method will verify that the stream
|
|
205
|
-
* exists and if not, create it.
|
|
206
|
-
*/
|
|
207
|
-
ClientService.verifyStream = async (workflowTopic, namespace) => {
|
|
208
|
-
const targetNS = namespace ?? factory_1.APP_ID;
|
|
209
|
-
if (ClientService.instances.has(targetNS)) {
|
|
210
|
-
const hotMeshClient = await ClientService.instances.get(targetNS);
|
|
211
|
-
if (!ClientService.topics.includes(workflowTopic)) {
|
|
212
|
-
ClientService.topics.push(workflowTopic);
|
|
213
|
-
await ClientService.createStream(hotMeshClient, workflowTopic, namespace);
|
|
214
|
-
}
|
|
215
|
-
return hotMeshClient;
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
216
|
exports.ClientService = ClientService;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { HotMeshService as HotMesh } from '../hotmesh';
|
|
2
|
-
import {
|
|
2
|
+
import { Registry, WorkerConfig, WorkerOptions } from '../../types/durable';
|
|
3
3
|
export declare class WorkerService {
|
|
4
4
|
static activityRegistry: Registry;
|
|
5
|
-
static connection: Connection;
|
|
6
5
|
static instances: Map<string, HotMesh | Promise<HotMesh>>;
|
|
7
6
|
workflowRunner: HotMesh;
|
|
8
7
|
activityRunner: HotMesh;
|
|
@@ -58,7 +58,6 @@ class WorkerService {
|
|
|
58
58
|
return WorkerService.activityRegistry;
|
|
59
59
|
}
|
|
60
60
|
static async create(config) {
|
|
61
|
-
WorkerService.connection = config.connection;
|
|
62
61
|
const workflow = config.workflow;
|
|
63
62
|
const [workflowFunctionName, workflowFunction] = WorkerService.resolveWorkflowTarget(workflow);
|
|
64
63
|
const baseTopic = `${config.taskQueue}-${workflowFunctionName}`;
|
|
@@ -93,9 +92,12 @@ class WorkerService {
|
|
|
93
92
|
class: config.connection.class,
|
|
94
93
|
options: config.connection.options,
|
|
95
94
|
};
|
|
95
|
+
const targetNamespace = config?.namespace ?? factory_1.APP_ID;
|
|
96
|
+
const optionsHash = (0, utils_1.hashOptions)(config?.connection?.options);
|
|
97
|
+
const targetTopic = `${optionsHash}.${targetNamespace}.${activityTopic}`;
|
|
96
98
|
const hotMeshWorker = await hotmesh_1.HotMeshService.init({
|
|
97
99
|
logLevel: config.options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
|
|
98
|
-
appId:
|
|
100
|
+
appId: targetNamespace,
|
|
99
101
|
engine: { redis: redisConfig },
|
|
100
102
|
workers: [
|
|
101
103
|
{
|
|
@@ -105,10 +107,9 @@ class WorkerService {
|
|
|
105
107
|
},
|
|
106
108
|
],
|
|
107
109
|
});
|
|
108
|
-
WorkerService.instances.set(
|
|
110
|
+
WorkerService.instances.set(targetTopic, hotMeshWorker);
|
|
109
111
|
return hotMeshWorker;
|
|
110
112
|
}
|
|
111
|
-
//this is the linked worker function in the reentrant workflow test
|
|
112
113
|
wrapActivityFunctions() {
|
|
113
114
|
return async (data) => {
|
|
114
115
|
try {
|
|
@@ -171,6 +172,9 @@ class WorkerService {
|
|
|
171
172
|
class: config.connection.class,
|
|
172
173
|
options: config.connection.options,
|
|
173
174
|
};
|
|
175
|
+
const targetNamespace = config?.namespace ?? factory_1.APP_ID;
|
|
176
|
+
const optionsHash = (0, utils_1.hashOptions)(config?.connection?.options);
|
|
177
|
+
const targetTopic = `${optionsHash}.${targetNamespace}.${workflowTopic}`;
|
|
174
178
|
const hotMeshWorker = await hotmesh_1.HotMeshService.init({
|
|
175
179
|
logLevel: config.options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
|
|
176
180
|
appId: config.namespace ?? factory_1.APP_ID,
|
|
@@ -183,7 +187,7 @@ class WorkerService {
|
|
|
183
187
|
},
|
|
184
188
|
],
|
|
185
189
|
});
|
|
186
|
-
WorkerService.instances.set(
|
|
190
|
+
WorkerService.instances.set(targetTopic, hotMeshWorker);
|
|
187
191
|
return hotMeshWorker;
|
|
188
192
|
}
|
|
189
193
|
wrapWorkflowFunction(workflowFunction, workflowTopic, config) {
|
|
@@ -198,6 +202,7 @@ class WorkerService {
|
|
|
198
202
|
context.set('canRetry', workflowInput.canRetry);
|
|
199
203
|
context.set('counter', counter);
|
|
200
204
|
context.set('interruptionRegistry', interruptionRegistry);
|
|
205
|
+
context.set('connection', config.connection);
|
|
201
206
|
context.set('namespace', config.namespace ?? factory_1.APP_ID);
|
|
202
207
|
context.set('raw', data);
|
|
203
208
|
context.set('workflowId', workflowInput.workflowId);
|
|
@@ -373,15 +378,20 @@ _a = WorkerService;
|
|
|
373
378
|
WorkerService.activityRegistry = {}; //user's activities
|
|
374
379
|
WorkerService.instances = new Map();
|
|
375
380
|
WorkerService.getHotMesh = async (workflowTopic, config, options) => {
|
|
376
|
-
|
|
377
|
-
|
|
381
|
+
const targetNamespace = config?.namespace ?? factory_1.APP_ID;
|
|
382
|
+
const optionsHash = (0, utils_1.hashOptions)(config?.connection?.options);
|
|
383
|
+
const targetTopic = `${optionsHash}.${targetNamespace}.${workflowTopic}`;
|
|
384
|
+
if (WorkerService.instances.has(targetTopic)) {
|
|
385
|
+
return await WorkerService.instances.get(targetTopic);
|
|
378
386
|
}
|
|
379
387
|
const hotMeshClient = hotmesh_1.HotMeshService.init({
|
|
380
388
|
logLevel: options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
|
|
381
|
-
appId:
|
|
382
|
-
engine: {
|
|
389
|
+
appId: targetNamespace,
|
|
390
|
+
engine: {
|
|
391
|
+
redis: { ...config?.connection },
|
|
392
|
+
},
|
|
383
393
|
});
|
|
384
|
-
WorkerService.instances.set(
|
|
394
|
+
WorkerService.instances.set(targetTopic, hotMeshClient);
|
|
385
395
|
await WorkerService.activateWorkflow(await hotMeshClient);
|
|
386
396
|
return hotMeshClient;
|
|
387
397
|
};
|
|
@@ -69,6 +69,7 @@ class WorkflowService {
|
|
|
69
69
|
const interruptionRegistry = store.get('interruptionRegistry');
|
|
70
70
|
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
71
71
|
const workflowTopic = store.get('workflowTopic');
|
|
72
|
+
const connection = store.get('connection');
|
|
72
73
|
const namespace = store.get('namespace');
|
|
73
74
|
const originJobId = store.get('originJobId');
|
|
74
75
|
const workflowTrace = store.get('workflowTrace');
|
|
@@ -82,6 +83,7 @@ class WorkflowService {
|
|
|
82
83
|
counter: COUNTER.counter,
|
|
83
84
|
cursor,
|
|
84
85
|
interruptionRegistry,
|
|
86
|
+
connection,
|
|
85
87
|
namespace,
|
|
86
88
|
originJobId,
|
|
87
89
|
raw,
|
|
@@ -100,8 +102,9 @@ class WorkflowService {
|
|
|
100
102
|
static async getHotMesh() {
|
|
101
103
|
const store = storage_1.asyncLocalStorage.getStore();
|
|
102
104
|
const workflowTopic = store.get('workflowTopic');
|
|
105
|
+
const connection = store.get('connection');
|
|
103
106
|
const namespace = store.get('namespace');
|
|
104
|
-
return await worker_1.WorkerService.getHotMesh(workflowTopic, { namespace });
|
|
107
|
+
return await worker_1.WorkerService.getHotMesh(workflowTopic, { connection, namespace });
|
|
105
108
|
}
|
|
106
109
|
/**
|
|
107
110
|
* Spawns a child workflow and awaits the return.
|
|
@@ -315,10 +318,12 @@ class WorkflowService {
|
|
|
315
318
|
const workflowId = store.get('workflowId');
|
|
316
319
|
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
317
320
|
const workflowTopic = store.get('workflowTopic');
|
|
321
|
+
const connection = store.get('connection');
|
|
318
322
|
const namespace = store.get('namespace');
|
|
319
323
|
const COUNTER = store.get('counter');
|
|
320
324
|
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
321
325
|
const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, {
|
|
326
|
+
connection,
|
|
322
327
|
namespace,
|
|
323
328
|
});
|
|
324
329
|
//this ID is used as a item key with a hash (dash prefix ensures no collision)
|
|
@@ -347,8 +352,10 @@ class WorkflowService {
|
|
|
347
352
|
static async signal(signalId, data) {
|
|
348
353
|
const store = storage_1.asyncLocalStorage.getStore();
|
|
349
354
|
const workflowTopic = store.get('workflowTopic');
|
|
355
|
+
const connection = store.get('connection');
|
|
350
356
|
const namespace = store.get('namespace');
|
|
351
357
|
const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, {
|
|
358
|
+
connection,
|
|
352
359
|
namespace,
|
|
353
360
|
});
|
|
354
361
|
if (await WorkflowService.isSideEffectAllowed(hotMeshClient, 'signal')) {
|
|
@@ -365,8 +372,9 @@ class WorkflowService {
|
|
|
365
372
|
* @param {HookOptions} options - the hook options
|
|
366
373
|
*/
|
|
367
374
|
static async hook(options) {
|
|
368
|
-
const { workflowId, namespace, workflowTopic } = WorkflowService.getContext();
|
|
375
|
+
const { workflowId, connection, namespace, workflowTopic } = WorkflowService.getContext();
|
|
369
376
|
const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, {
|
|
377
|
+
connection,
|
|
370
378
|
namespace,
|
|
371
379
|
});
|
|
372
380
|
if (await WorkflowService.isSideEffectAllowed(hotMeshClient, 'hook')) {
|
|
@@ -395,13 +403,14 @@ class WorkflowService {
|
|
|
395
403
|
* @template T - the result type
|
|
396
404
|
*/
|
|
397
405
|
static async once(fn, ...args) {
|
|
398
|
-
const { COUNTER, namespace, workflowId, workflowTopic, workflowDimension, replay, } = WorkflowService.getContext();
|
|
406
|
+
const { COUNTER, connection, namespace, workflowId, workflowTopic, workflowDimension, replay, } = WorkflowService.getContext();
|
|
399
407
|
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
400
408
|
const sessionId = `-once${workflowDimension}-${execIndex}-`;
|
|
401
409
|
if (sessionId in replay) {
|
|
402
410
|
return serializer_1.SerializerService.fromString(replay[sessionId]).data;
|
|
403
411
|
}
|
|
404
412
|
const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, {
|
|
413
|
+
connection,
|
|
405
414
|
namespace,
|
|
406
415
|
});
|
|
407
416
|
const keyParams = {
|
|
@@ -424,8 +433,9 @@ class WorkflowService {
|
|
|
424
433
|
* Interrupts a running job
|
|
425
434
|
*/
|
|
426
435
|
static async interrupt(jobId, options = {}) {
|
|
427
|
-
const { workflowTopic, namespace } = WorkflowService.getContext();
|
|
436
|
+
const { workflowTopic, connection, namespace, } = WorkflowService.getContext();
|
|
428
437
|
const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, {
|
|
438
|
+
connection,
|
|
429
439
|
namespace,
|
|
430
440
|
});
|
|
431
441
|
if (await WorkflowService.isSideEffectAllowed(hotMeshClient, 'interrupt')) {
|
package/build/types/durable.d.ts
CHANGED
|
@@ -88,6 +88,10 @@ type WorkflowContext = {
|
|
|
88
88
|
* the native HotMesh message that encapsulates the arguments, metadata, and raw data for the workflow
|
|
89
89
|
*/
|
|
90
90
|
raw: StreamData;
|
|
91
|
+
/**
|
|
92
|
+
* the HotMesh connection configuration (io/redis NPM package reference and login credentials)
|
|
93
|
+
*/
|
|
94
|
+
connection: Connection;
|
|
91
95
|
};
|
|
92
96
|
/**
|
|
93
97
|
* The schema for the full-text-search (RediSearch) index.
|
package/build/types/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export { ActivityConfig, ActivityWorkflowDataType, ChildResponseType, ClientConf
|
|
|
7
7
|
export { DurableChildErrorType, DurableProxyErrorType, DurableSleepErrorType, DurableWaitForAllErrorType, DurableWaitForErrorType, } from './error';
|
|
8
8
|
export { ActivityAction, DependencyExport, DurableJobExport, ExportCycles, ExportItem, ExportOptions, ExportTransitions, JobAction, JobExport, JobActionExport, JobTimeline, } from './exporter';
|
|
9
9
|
export { HookCondition, HookConditions, HookGate, HookInterface, HookRule, HookRules, HookSignal, } from './hook';
|
|
10
|
-
export { ILogger } from './logger';
|
|
10
|
+
export { ILogger, LogLevel, } from './logger';
|
|
11
11
|
export { ExtensionType, JobCompletionOptions, JobData, JobsData, JobInterruptOptions, JobMetadata, JobOutput, JobState, JobStatus, PartialJobState, } from './job';
|
|
12
12
|
export { MappingStatements } from './map';
|
|
13
13
|
export { Pipe, PipeContext, PipeItem, PipeItems, PipeObject, ReduceObject, } from './pipe';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotmeshio/hotmesh",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "Unbreakable Workflows",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -84,7 +84,6 @@
|
|
|
84
84
|
"js-yaml": "^4.1.0",
|
|
85
85
|
"ms": "^2.1.3",
|
|
86
86
|
"nanoid": "^3.3.6",
|
|
87
|
-
"systeminformation": "^5.22.2",
|
|
88
87
|
"winston": "^3.8.2"
|
|
89
88
|
},
|
|
90
89
|
"devDependencies": {
|
package/types/durable.ts
CHANGED
|
@@ -106,6 +106,11 @@ type WorkflowContext = {
|
|
|
106
106
|
* the native HotMesh message that encapsulates the arguments, metadata, and raw data for the workflow
|
|
107
107
|
*/
|
|
108
108
|
raw: StreamData;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* the HotMesh connection configuration (io/redis NPM package reference and login credentials)
|
|
112
|
+
*/
|
|
113
|
+
connection: Connection;
|
|
109
114
|
};
|
|
110
115
|
|
|
111
116
|
/**
|