@hotmeshio/hotmesh 0.0.19 → 0.0.21
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 +4 -4
- package/build/modules/errors.d.ts +2 -1
- package/build/modules/errors.js +2 -1
- package/build/package.json +2 -1
- package/build/services/activities/activity.d.ts +2 -2
- package/build/services/activities/activity.js +10 -8
- package/build/services/activities/hook.d.ts +2 -1
- package/build/services/activities/hook.js +12 -9
- package/build/services/activities/signal.d.ts +4 -0
- package/build/services/activities/signal.js +16 -2
- package/build/services/durable/client.d.ts +15 -5
- package/build/services/durable/client.js +37 -14
- package/build/services/durable/factory.d.ts +2 -16
- package/build/services/durable/factory.js +276 -46
- package/build/services/durable/handle.d.ts +1 -1
- package/build/services/durable/handle.js +18 -5
- package/build/services/durable/search.d.ts +8 -1
- package/build/services/durable/search.js +36 -10
- package/build/services/durable/worker.d.ts +7 -9
- package/build/services/durable/worker.js +29 -23
- package/build/services/durable/workflow.d.ts +23 -2
- package/build/services/durable/workflow.js +143 -37
- package/build/services/engine/index.d.ts +2 -2
- package/build/services/engine/index.js +7 -12
- package/build/services/hotmesh/index.d.ts +2 -2
- package/build/services/hotmesh/index.js +2 -2
- package/build/services/signaler/store.d.ts +2 -2
- package/build/services/signaler/store.js +17 -7
- package/build/services/signaler/stream.js +1 -0
- package/build/services/store/clients/redis.js +1 -1
- package/build/services/store/index.js +3 -0
- package/build/services/telemetry/index.js +7 -1
- package/build/types/activity.d.ts +5 -3
- package/build/types/durable.d.ts +13 -2
- package/build/types/hook.d.ts +0 -1
- package/build/types/index.d.ts +1 -1
- package/modules/errors.ts +4 -2
- package/package.json +2 -1
- package/services/activities/activity.ts +10 -8
- package/services/activities/hook.ts +13 -10
- package/services/activities/signal.ts +17 -3
- package/services/durable/client.ts +40 -15
- package/services/durable/factory.ts +274 -46
- package/services/durable/handle.ts +18 -5
- package/services/durable/search.ts +38 -10
- package/services/durable/worker.ts +30 -24
- package/services/durable/workflow.ts +158 -40
- package/services/engine/index.ts +8 -12
- package/services/hotmesh/index.ts +3 -3
- package/services/signaler/store.ts +18 -8
- package/services/signaler/stream.ts +1 -0
- package/services/store/clients/redis.ts +1 -1
- package/services/store/index.ts +2 -0
- package/services/telemetry/index.ts +6 -1
- package/types/activity.ts +10 -8
- package/types/durable.ts +14 -1
- package/types/hook.ts +0 -1
- package/types/index.ts +1 -0
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# HotMesh
|
|
2
2
|

|
|
3
3
|
|
|
4
|
-
Elevate Redis from an in-memory data
|
|
4
|
+
Elevate Redis from an in-memory data cache to a game-changing [service mesh](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/faq.md#what-is-hotmesh). Turn your unpredictable functions into unbreakable workflows.
|
|
5
5
|
|
|
6
6
|
## Install
|
|
7
7
|
[](https://badge.fury.io/js/%40hotmeshio%2Fhotmesh)
|
|
@@ -13,7 +13,7 @@ npm install @hotmeshio/hotmesh
|
|
|
13
13
|
## Design
|
|
14
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
|
|
16
|
+
1. Start by defining **activities**. Activities 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
|
|
|
@@ -47,7 +47,7 @@ The HotMesh SDK is designed to keep your code front-and-center. Write functions
|
|
|
47
47
|
}
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
3. Although you could call your workflow directly (it's just a vanilla function), it's only durable when invoked and orchestrated via HotMesh.
|
|
50
|
+
3. Although you could call your workflow directly (it's just a vanilla function), it's only durable when invoked and orchestrated via HotMesh.
|
|
51
51
|
```javascript
|
|
52
52
|
//client.ts
|
|
53
53
|
|
|
@@ -75,7 +75,7 @@ The HotMesh SDK is designed to keep your code front-and-center. Write functions
|
|
|
75
75
|
}
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
4.
|
|
78
|
+
4. Finally, create a **worker** and link your workflow function. Workers listen for tasks on their assigned Redis stream and invoke your workflow function each time they receive an event.
|
|
79
79
|
```javascript
|
|
80
80
|
//worker.ts
|
|
81
81
|
|
|
@@ -25,7 +25,8 @@ declare class DurableSleepError extends Error {
|
|
|
25
25
|
code: number;
|
|
26
26
|
duration: number;
|
|
27
27
|
index: number;
|
|
28
|
-
|
|
28
|
+
dimension: string;
|
|
29
|
+
constructor(message: string, duration: number, index: number, dimension: string);
|
|
29
30
|
}
|
|
30
31
|
declare class DurableTimeoutError extends Error {
|
|
31
32
|
code: number;
|
package/build/modules/errors.js
CHANGED
|
@@ -32,10 +32,11 @@ class DurableWaitForSignalError extends Error {
|
|
|
32
32
|
}
|
|
33
33
|
exports.DurableWaitForSignalError = DurableWaitForSignalError;
|
|
34
34
|
class DurableSleepError extends Error {
|
|
35
|
-
constructor(message, duration, index) {
|
|
35
|
+
constructor(message, duration, index, dimension) {
|
|
36
36
|
super(message);
|
|
37
37
|
this.duration = duration;
|
|
38
38
|
this.index = index;
|
|
39
|
+
this.dimension = dimension;
|
|
39
40
|
this.code = 595;
|
|
40
41
|
}
|
|
41
42
|
}
|
package/build/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotmeshio/hotmesh",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.21",
|
|
4
4
|
"description": "Unbreakable Workflows",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"test:durable": "NODE_ENV=test jest ./tests/durable/*/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
46
46
|
"test:durable:hello": "NODE_ENV=test jest ./tests/durable/helloworld/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
47
47
|
"test:durable:goodbye": "NODE_ENV=test jest ./tests/durable/goodbye/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
48
|
+
"test:durable:hook": "NODE_ENV=test jest ./tests/durable/hook/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
48
49
|
"test:durable:retry": "NODE_ENV=test jest ./tests/durable/retry/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
49
50
|
"test:durable:fatal": "NODE_ENV=test jest ./tests/durable/fatal/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
50
51
|
"test:durable:sleep": "NODE_ENV=test jest ./tests/durable/sleep/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
@@ -26,7 +26,7 @@ declare class Activity {
|
|
|
26
26
|
adjacentIndex: number;
|
|
27
27
|
constructor(config: ActivityType, data: ActivityData, metadata: ActivityMetadata, hook: ActivityData | null, engine: EngineService, context?: JobState);
|
|
28
28
|
setLeg(leg: ActivityLeg): void;
|
|
29
|
-
processEvent(status?: StreamStatus, code?: StreamCode, type?: 'hook' | 'output'
|
|
29
|
+
processEvent(status?: StreamStatus, code?: StreamCode, type?: 'hook' | 'output'): Promise<void>;
|
|
30
30
|
processPending(telemetry: TelemetryService, type: 'hook' | 'output'): Promise<MultiResponseFlags>;
|
|
31
31
|
processSuccess(telemetry: TelemetryService, type: 'hook' | 'output'): Promise<MultiResponseFlags>;
|
|
32
32
|
processError(telemetry: TelemetryService, type: string): Promise<MultiResponseFlags>;
|
|
@@ -49,7 +49,7 @@ declare class Activity {
|
|
|
49
49
|
bindActivityState(state: StringAnyType): void;
|
|
50
50
|
bindJobMetadataPaths(): string[];
|
|
51
51
|
bindActivityMetadataPaths(): string[];
|
|
52
|
-
getState(
|
|
52
|
+
getState(): Promise<void>;
|
|
53
53
|
initDimensionalAddress(dad: string): void;
|
|
54
54
|
initSelf(context: StringAnyType): JobState;
|
|
55
55
|
initPolicies(context: JobState): void;
|
|
@@ -30,16 +30,20 @@ class Activity {
|
|
|
30
30
|
this.leg = leg;
|
|
31
31
|
}
|
|
32
32
|
//******** DUPLEX RE-ENTRY POINT ********//
|
|
33
|
-
async processEvent(status = stream_1.StreamStatus.SUCCESS, code = 200, type = 'output'
|
|
33
|
+
async processEvent(status = stream_1.StreamStatus.SUCCESS, code = 200, type = 'output') {
|
|
34
34
|
this.setLeg(2);
|
|
35
|
-
const jid = this.context.metadata.jid
|
|
35
|
+
const jid = this.context.metadata.jid;
|
|
36
|
+
if (!jid) {
|
|
37
|
+
this.logger.error('activity-process-event-error', { message: 'job id is undefined' });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
36
40
|
const aid = this.metadata.aid;
|
|
37
41
|
this.status = status;
|
|
38
42
|
this.code = code;
|
|
39
43
|
this.logger.debug('activity-process-event', { topic: this.config.subtype, jid, aid, status, code });
|
|
40
44
|
let telemetry;
|
|
41
45
|
try {
|
|
42
|
-
await this.getState(
|
|
46
|
+
await this.getState();
|
|
43
47
|
const aState = await collator_1.CollatorService.notarizeReentry(this);
|
|
44
48
|
this.adjacentIndex = collator_1.CollatorService.getDimensionalIndex(aState);
|
|
45
49
|
telemetry = new telemetry_1.TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
@@ -67,7 +71,6 @@ class Activity {
|
|
|
67
71
|
this.logger.info('process-event-inactive-error', { error });
|
|
68
72
|
return;
|
|
69
73
|
}
|
|
70
|
-
console.error(error);
|
|
71
74
|
this.logger.error('activity-process-event-error', { error });
|
|
72
75
|
telemetry && telemetry.setActivityError(error.message);
|
|
73
76
|
throw error;
|
|
@@ -251,7 +254,7 @@ class Activity {
|
|
|
251
254
|
const keys_to_save = this.leg === 1 ? 'ACTIVITY' : 'ACTIVITY_UPDATE';
|
|
252
255
|
return serializer_1.MDATA_SYMBOLS[keys_to_save].KEYS.map((key) => `output/metadata/${key}`);
|
|
253
256
|
}
|
|
254
|
-
async getState(
|
|
257
|
+
async getState() {
|
|
255
258
|
//assemble list of paths necessary to create 'job state' from the 'symbol hash'
|
|
256
259
|
const jobSymbolHashName = `$${this.config.subscribes}`;
|
|
257
260
|
const consumes = {
|
|
@@ -277,10 +280,9 @@ class Activity {
|
|
|
277
280
|
}
|
|
278
281
|
telemetry_1.TelemetryService.addTargetTelemetryPaths(consumes, this.config, this.metadata, this.leg);
|
|
279
282
|
let { dad, jid } = this.context.metadata;
|
|
280
|
-
|
|
281
|
-
const dIds = collator_1.CollatorService.getDimensionsById([...this.config.ancestors, this.metadata.aid], dad);
|
|
283
|
+
const dIds = collator_1.CollatorService.getDimensionsById([...this.config.ancestors, this.metadata.aid], dad || '');
|
|
282
284
|
//`state` is a flat hash; context is a tree
|
|
283
|
-
const [state, status] = await this.store.getState(
|
|
285
|
+
const [state, status] = await this.store.getState(jid, consumes, dIds);
|
|
284
286
|
this.context = (0, utils_1.restoreHierarchy)(state);
|
|
285
287
|
this.initDimensionalAddress(dad);
|
|
286
288
|
this.initSelf(this.context);
|
|
@@ -4,6 +4,7 @@ import { ActivityData, ActivityMetadata, ActivityType, HookActivity } from '../.
|
|
|
4
4
|
import { HookRule } from '../../types/hook';
|
|
5
5
|
import { JobState, JobStatus } from '../../types/job';
|
|
6
6
|
import { RedisMulti } from '../../types/redis';
|
|
7
|
+
import { StreamCode, StreamStatus } from '../../types/stream';
|
|
7
8
|
/**
|
|
8
9
|
* Listens for `webhook`, `timehook`, and `cycle` (repeat) signals
|
|
9
10
|
*/
|
|
@@ -14,7 +15,7 @@ declare class Hook extends Activity {
|
|
|
14
15
|
doesHook(): boolean;
|
|
15
16
|
getHookRule(topic: string): Promise<HookRule | undefined>;
|
|
16
17
|
registerHook(multi?: RedisMulti): Promise<string | void>;
|
|
17
|
-
processWebHookEvent(): Promise<JobStatus | void>;
|
|
18
|
+
processWebHookEvent(status?: StreamStatus, code?: StreamCode): Promise<JobStatus | void>;
|
|
18
19
|
processTimeHookEvent(jobId: string): Promise<JobStatus | void>;
|
|
19
20
|
}
|
|
20
21
|
export { Hook };
|
|
@@ -85,7 +85,7 @@ class Hook extends activity_1.Activity {
|
|
|
85
85
|
async registerHook(multi) {
|
|
86
86
|
if (this.config.hook?.topic) {
|
|
87
87
|
const signaler = new store_1.StoreSignaler(this.store, this.logger);
|
|
88
|
-
return await signaler.registerWebHook(this.config.hook.topic, this.context, multi);
|
|
88
|
+
return await signaler.registerWebHook(this.config.hook.topic, this.context, this.resolveDad(), multi);
|
|
89
89
|
}
|
|
90
90
|
else if (this.config.sleep) {
|
|
91
91
|
const durationInSeconds = pipe_1.Pipe.resolve(this.config.sleep, this.context);
|
|
@@ -96,19 +96,22 @@ class Hook extends activity_1.Activity {
|
|
|
96
96
|
return jobId;
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
-
async processWebHookEvent() {
|
|
99
|
+
async processWebHookEvent(status = stream_1.StreamStatus.SUCCESS, code = 200) {
|
|
100
100
|
this.logger.debug('hook-process-web-hook-event', {
|
|
101
101
|
topic: this.config.hook.topic,
|
|
102
|
-
aid: this.metadata.aid
|
|
102
|
+
aid: this.metadata.aid,
|
|
103
|
+
status,
|
|
104
|
+
code,
|
|
103
105
|
});
|
|
104
106
|
const signaler = new store_1.StoreSignaler(this.store, this.logger);
|
|
105
107
|
const data = { ...this.data };
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
const signal = await signaler.processWebHookSignal(this.config.hook.topic, data);
|
|
109
|
+
if (signal) {
|
|
110
|
+
const [jobId, aid, dad] = signal;
|
|
111
|
+
this.context.metadata.jid = jobId;
|
|
112
|
+
this.context.metadata.dad = dad;
|
|
113
|
+
await this.processEvent(status, code, 'hook');
|
|
114
|
+
if (code === 200) { //otherwise 202 for pending/keepalive
|
|
112
115
|
await signaler.deleteWebHookSignal(this.config.hook.topic, data);
|
|
113
116
|
}
|
|
114
117
|
} //else => already resolved
|
|
@@ -8,6 +8,10 @@ declare class Signal extends Activity {
|
|
|
8
8
|
process(): Promise<string>;
|
|
9
9
|
mapSignalData(): Record<string, any>;
|
|
10
10
|
mapResolverData(): Record<string, any>;
|
|
11
|
+
/**
|
|
12
|
+
* The signal activity will hook one
|
|
13
|
+
*/
|
|
14
|
+
hookOne(): Promise<string>;
|
|
11
15
|
/**
|
|
12
16
|
* The signal activity will hook all paused jobs that share the same job key.
|
|
13
17
|
*/
|
|
@@ -31,8 +31,12 @@ class Signal extends activity_1.Activity {
|
|
|
31
31
|
await collator_1.CollatorService.notarizeEarlyCompletion(this, multi);
|
|
32
32
|
await this.setStatus(this.adjacencyList.length - 1, multi);
|
|
33
33
|
const multiResponse = await multi.exec();
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
if (this.config.subtype === 'all') {
|
|
35
|
+
await this.hookAll();
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
await this.hookOne();
|
|
39
|
+
}
|
|
36
40
|
//transition to adjacent activities
|
|
37
41
|
const jobStatus = this.resolveStatus(multiResponse);
|
|
38
42
|
const attrs = { 'app.job.jss': jobStatus };
|
|
@@ -71,6 +75,16 @@ class Signal extends activity_1.Activity {
|
|
|
71
75
|
return mapper.mapRules();
|
|
72
76
|
}
|
|
73
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* The signal activity will hook one
|
|
80
|
+
*/
|
|
81
|
+
async hookOne() {
|
|
82
|
+
const topic = pipe_1.Pipe.resolve(this.config.topic, this.context);
|
|
83
|
+
const signalInputData = this.mapSignalData();
|
|
84
|
+
const status = pipe_1.Pipe.resolve(this.config.status, this.context);
|
|
85
|
+
const code = pipe_1.Pipe.resolve(this.config.code, this.context);
|
|
86
|
+
return await this.engine.hook(topic, signalInputData, status, code);
|
|
87
|
+
}
|
|
74
88
|
/**
|
|
75
89
|
* The signal activity will hook all paused jobs that share the same job key.
|
|
76
90
|
*/
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { WorkflowHandleService } from './handle';
|
|
2
2
|
import { HotMeshService as HotMesh } from '../hotmesh';
|
|
3
|
-
import { ClientConfig, Connection, WorkflowOptions, WorkflowSearchOptions } from '../../types/durable';
|
|
3
|
+
import { ClientConfig, Connection, HookOptions, WorkflowOptions, WorkflowSearchOptions } from '../../types/durable';
|
|
4
4
|
export declare class ClientService {
|
|
5
5
|
connection: Connection;
|
|
6
6
|
options: WorkflowOptions;
|
|
7
7
|
static instances: Map<string, HotMesh | Promise<HotMesh>>;
|
|
8
8
|
constructor(config: ClientConfig);
|
|
9
|
-
getHotMeshClient: (worflowTopic: string) => Promise<HotMesh>;
|
|
9
|
+
getHotMeshClient: (worflowTopic: string, namespace?: string) => Promise<HotMesh>;
|
|
10
10
|
/**
|
|
11
11
|
* For those deployments with a redis stack backend (with the FT module),
|
|
12
12
|
* this method will configure the search index for the workflow.
|
|
@@ -15,9 +15,19 @@ export declare class ClientService {
|
|
|
15
15
|
search: (hotMeshClient: HotMesh, index: string, query: string[]) => Promise<string[]>;
|
|
16
16
|
workflow: {
|
|
17
17
|
start: (options: WorkflowOptions) => Promise<WorkflowHandleService>;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
/**
|
|
19
|
+
* send a message to a running workflow that is paused and awaiting the signal
|
|
20
|
+
*/
|
|
21
|
+
signal: (signalId: string, data: Record<any, any>, namespace?: string) => Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* send a message to spawn an parallel in-process thread of execution
|
|
24
|
+
* with the same job state as the main thread but bound to a different
|
|
25
|
+
* handler function. All job state will be journaled to the same hash
|
|
26
|
+
* as is used by the main thread.
|
|
27
|
+
*/
|
|
28
|
+
hook: (options: HookOptions) => Promise<string>;
|
|
29
|
+
getHandle: (taskQueue: string, workflowName: string, workflowId: string, namespace?: string) => Promise<WorkflowHandleService>;
|
|
30
|
+
search: (taskQueue: string, workflowName: string, namespace: null | string, index: string, ...query: string[]) => Promise<string[]>;
|
|
21
31
|
};
|
|
22
32
|
activateWorkflow(hotMesh: HotMesh, appId?: string, version?: string): Promise<void>;
|
|
23
33
|
static shutdown(): Promise<void>;
|
|
@@ -7,15 +7,16 @@ const handle_1 = require("./handle");
|
|
|
7
7
|
const hotmesh_1 = require("../hotmesh");
|
|
8
8
|
const key_1 = require("../../modules/key");
|
|
9
9
|
const search_1 = require("./search");
|
|
10
|
+
const types_1 = require("../../types");
|
|
10
11
|
class ClientService {
|
|
11
12
|
constructor(config) {
|
|
12
|
-
this.getHotMeshClient = async (worflowTopic) => {
|
|
13
|
+
this.getHotMeshClient = async (worflowTopic, namespace) => {
|
|
13
14
|
//NOTE: every unique topic inits a new engine
|
|
14
15
|
if (ClientService.instances.has(worflowTopic)) {
|
|
15
16
|
return await ClientService.instances.get(worflowTopic);
|
|
16
17
|
}
|
|
17
18
|
const hotMeshClient = hotmesh_1.HotMeshService.init({
|
|
18
|
-
appId: factory_1.APP_ID,
|
|
19
|
+
appId: namespace ?? factory_1.APP_ID,
|
|
19
20
|
engine: {
|
|
20
21
|
redis: {
|
|
21
22
|
class: this.connection.class,
|
|
@@ -26,7 +27,7 @@ class ClientService {
|
|
|
26
27
|
ClientService.instances.set(worflowTopic, hotMeshClient);
|
|
27
28
|
//since the YAML topic is dynamic, it MUST be manually created before use
|
|
28
29
|
const store = (await hotMeshClient).engine.store;
|
|
29
|
-
const params = { appId: factory_1.APP_ID, topic: worflowTopic };
|
|
30
|
+
const params = { appId: namespace ?? factory_1.APP_ID, topic: worflowTopic };
|
|
30
31
|
const streamKey = store.mintKey(key_1.KeyType.STREAMS, params);
|
|
31
32
|
try {
|
|
32
33
|
await store.xgroup('CREATE', streamKey, 'WORKER', '$', 'MKSTREAM');
|
|
@@ -34,7 +35,7 @@ class ClientService {
|
|
|
34
35
|
catch (err) {
|
|
35
36
|
//ignore if already exists
|
|
36
37
|
}
|
|
37
|
-
await this.activateWorkflow(await hotMeshClient);
|
|
38
|
+
await this.activateWorkflow(await hotMeshClient, namespace ?? factory_1.APP_ID);
|
|
38
39
|
return hotMeshClient;
|
|
39
40
|
};
|
|
40
41
|
/**
|
|
@@ -46,7 +47,7 @@ class ClientService {
|
|
|
46
47
|
const store = hotMeshClient.engine.store;
|
|
47
48
|
const schema = [];
|
|
48
49
|
for (const [key, value] of Object.entries(search.schema)) {
|
|
49
|
-
//prefix with
|
|
50
|
+
//prefix with an underscore (avoids collisions with hotmesh reserved symbols)
|
|
50
51
|
schema.push(`_${key}`);
|
|
51
52
|
schema.push(value.type);
|
|
52
53
|
if (value.sortable) {
|
|
@@ -79,7 +80,7 @@ class ClientService {
|
|
|
79
80
|
const spn = options.workflowSpan;
|
|
80
81
|
//topic is concat of taskQueue and workflowName
|
|
81
82
|
const workflowTopic = `${taskQueueName}-${workflowName}`;
|
|
82
|
-
const hotMeshClient = await this.getHotMeshClient(workflowTopic);
|
|
83
|
+
const hotMeshClient = await this.getHotMeshClient(workflowTopic, options.namespace);
|
|
83
84
|
this.configureSearchIndex(hotMeshClient, options.search);
|
|
84
85
|
const payload = {
|
|
85
86
|
arguments: [...options.args],
|
|
@@ -89,27 +90,49 @@ class ClientService {
|
|
|
89
90
|
backoffCoefficient: options.config?.backoffCoefficient || factory_1.DEFAULT_COEFFICIENT,
|
|
90
91
|
};
|
|
91
92
|
const context = { metadata: { trc, spn }, data: {} };
|
|
92
|
-
const jobId = await hotMeshClient.pub(factory_1.
|
|
93
|
+
const jobId = await hotMeshClient.pub(`${options.namespace ?? factory_1.APP_ID}.execute`, payload, context);
|
|
93
94
|
if (jobId && options.search?.data) {
|
|
94
95
|
//job successfully kicked off; there is default job data to persist
|
|
95
|
-
const
|
|
96
|
+
const searchSessionId = `-search-0`;
|
|
97
|
+
const search = new search_1.Search(jobId, hotMeshClient, searchSessionId);
|
|
96
98
|
for (const [key, value] of Object.entries(options.search.data)) {
|
|
97
99
|
search.set(key, value);
|
|
98
100
|
}
|
|
99
101
|
}
|
|
100
102
|
return new handle_1.WorkflowHandleService(hotMeshClient, workflowTopic, jobId);
|
|
101
103
|
},
|
|
102
|
-
|
|
103
|
-
|
|
104
|
+
/**
|
|
105
|
+
* send a message to a running workflow that is paused and awaiting the signal
|
|
106
|
+
*/
|
|
107
|
+
signal: async (signalId, data, namespace) => {
|
|
108
|
+
const topic = `${namespace ?? factory_1.APP_ID}.wfs.signal`;
|
|
109
|
+
return await (await this.getHotMeshClient(topic, namespace)).hook(topic, { id: signalId, data });
|
|
104
110
|
},
|
|
105
|
-
|
|
111
|
+
/**
|
|
112
|
+
* send a message to spawn an parallel in-process thread of execution
|
|
113
|
+
* with the same job state as the main thread but bound to a different
|
|
114
|
+
* handler function. All job state will be journaled to the same hash
|
|
115
|
+
* as is used by the main thread.
|
|
116
|
+
*/
|
|
117
|
+
hook: async (options) => {
|
|
118
|
+
const workflowTopic = `${options.taskQueue}-${options.workflowName}`;
|
|
119
|
+
const payload = {
|
|
120
|
+
arguments: [...options.args],
|
|
121
|
+
id: options.workflowId,
|
|
122
|
+
workflowTopic,
|
|
123
|
+
backoffCoefficient: options.config?.backoffCoefficient || factory_1.DEFAULT_COEFFICIENT,
|
|
124
|
+
};
|
|
125
|
+
const hotMeshClient = await this.getHotMeshClient(workflowTopic, options.namespace);
|
|
126
|
+
return await hotMeshClient.hook(`${hotMeshClient.appId}.flow.signal`, payload, types_1.StreamStatus.PENDING, 202);
|
|
127
|
+
},
|
|
128
|
+
getHandle: async (taskQueue, workflowName, workflowId, namespace) => {
|
|
106
129
|
const workflowTopic = `${taskQueue}-${workflowName}`;
|
|
107
|
-
const hotMeshClient = await this.getHotMeshClient(workflowTopic);
|
|
130
|
+
const hotMeshClient = await this.getHotMeshClient(workflowTopic, namespace);
|
|
108
131
|
return new handle_1.WorkflowHandleService(hotMeshClient, workflowTopic, workflowId);
|
|
109
132
|
},
|
|
110
|
-
search: async (taskQueue, workflowName, index, ...query) => {
|
|
133
|
+
search: async (taskQueue, workflowName, namespace, index, ...query) => {
|
|
111
134
|
const workflowTopic = `${taskQueue}-${workflowName}`;
|
|
112
|
-
const hotMeshClient = await this.getHotMeshClient(workflowTopic);
|
|
135
|
+
const hotMeshClient = await this.getHotMeshClient(workflowTopic, namespace);
|
|
113
136
|
try {
|
|
114
137
|
return await this.search(hotMeshClient, index, query);
|
|
115
138
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* NOTE: Using `maxSystemRetries = 3` and `backoffCoefficient = 10`, errant
|
|
3
3
|
* workflows will be retried on the following schedule (8 times in 27 hours):
|
|
4
4
|
* => 10ms, 100ms, 1000ms, 10s, 100s, 1_000s, 10_000s, 100_000s
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
6
|
* 594: waitforsignal
|
|
7
7
|
* 595: sleep
|
|
8
8
|
* 596, 597, 598: fatal
|
|
@@ -11,19 +11,5 @@
|
|
|
11
11
|
declare const getWorkflowYAML: (app: string, version: string) => string;
|
|
12
12
|
declare const APP_VERSION = "1";
|
|
13
13
|
declare const APP_ID = "durable";
|
|
14
|
-
declare const ACTIVITY_SUBSCRIBES_TOPIC = "durable.activity.execute";
|
|
15
|
-
declare const ACTIVITY_PUBLISHES_TOPIC = "durable.activity.executed";
|
|
16
|
-
declare const SLEEP_SUBSCRIBES_TOPIC = "durable.sleep.execute";
|
|
17
|
-
declare const SLEEP_PUBLISHES_TOPIC = "durable.sleep.executed";
|
|
18
|
-
declare const WFS_SUBSCRIBES_TOPIC = "durable.wfs.execute";
|
|
19
|
-
declare const WFS_PUBLISHES_TOPIC = "durable.wfs.executed";
|
|
20
|
-
declare const WFSC_SUBSCRIBES_TOPIC = "durable.wfsc.execute";
|
|
21
|
-
declare const WFSC_PUBLISHES_TOPIC = "durable.wfsc.executed";
|
|
22
|
-
declare const SUBSCRIBES_TOPIC = "durable.execute";
|
|
23
|
-
declare const PUBLISHES_TOPIC = "durable.executed";
|
|
24
|
-
declare const HOOK_ID = "durable.awaken";
|
|
25
|
-
declare const ACTIVITY_HOOK_ID = "durable.activity.awaken";
|
|
26
|
-
declare const SLEEP_HOOK_ID = "durable.sleep.awaken";
|
|
27
|
-
declare const WFS_HOOK_ID = "durable.wfs.awaken";
|
|
28
14
|
declare const DEFAULT_COEFFICIENT = 10;
|
|
29
|
-
export { getWorkflowYAML, APP_VERSION, APP_ID,
|
|
15
|
+
export { getWorkflowYAML, APP_VERSION, APP_ID, DEFAULT_COEFFICIENT, };
|