@hotmeshio/hotmesh 0.0.17 → 0.0.19
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/build/modules/utils.d.ts +3 -0
- package/build/modules/utils.js +17 -1
- package/build/package.json +1 -1
- package/build/services/activities/activity.d.ts +4 -12
- package/build/services/activities/activity.js +14 -156
- package/build/services/activities/hook.d.ts +20 -0
- package/build/services/activities/hook.js +124 -0
- package/build/services/activities/index.d.ts +2 -0
- package/build/services/activities/index.js +2 -0
- package/build/services/collator/index.js +0 -1
- package/build/services/compiler/deployer.d.ts +2 -0
- package/build/services/compiler/deployer.js +29 -2
- package/build/services/durable/client.d.ts +8 -1
- package/build/services/durable/client.js +54 -40
- package/build/services/durable/factory.js +11 -10
- package/build/services/durable/search.d.ts +15 -0
- package/build/services/durable/search.js +45 -0
- package/build/services/durable/worker.d.ts +6 -1
- package/build/services/durable/worker.js +34 -30
- package/build/services/durable/workflow.d.ts +2 -0
- package/build/services/durable/workflow.js +11 -28
- package/build/services/engine/index.d.ts +7 -2
- package/build/services/engine/index.js +2 -1
- package/build/services/store/clients/ioredis.d.ts +1 -0
- package/build/services/store/clients/ioredis.js +12 -0
- package/build/services/store/clients/redis.d.ts +1 -0
- package/build/services/store/clients/redis.js +3 -0
- package/build/services/store/index.d.ts +1 -0
- package/build/services/telemetry/index.js +2 -1
- package/build/types/activity.d.ts +6 -3
- package/build/types/durable.d.ts +12 -1
- package/build/types/hook.d.ts +1 -0
- package/build/types/index.d.ts +2 -2
- package/modules/utils.ts +17 -0
- package/package.json +1 -1
- package/services/activities/activity.ts +15 -167
- package/services/activities/hook.ts +149 -0
- package/services/activities/index.ts +2 -0
- package/services/collator/index.ts +0 -1
- package/services/compiler/deployer.ts +32 -2
- package/services/durable/client.ts +58 -43
- package/services/durable/factory.ts +11 -10
- package/services/durable/search.ts +54 -0
- package/services/durable/worker.ts +36 -32
- package/services/durable/workflow.ts +14 -30
- package/services/engine/index.ts +8 -4
- package/services/store/clients/ioredis.ts +13 -0
- package/services/store/clients/redis.ts +4 -0
- package/services/store/index.ts +1 -0
- package/services/telemetry/index.ts +2 -1
- package/types/activity.ts +7 -2
- package/types/durable.ts +10 -0
- package/types/hook.ts +1 -0
- package/types/index.ts +2 -0
|
@@ -6,46 +6,7 @@ const factory_1 = require("./factory");
|
|
|
6
6
|
const handle_1 = require("./handle");
|
|
7
7
|
const hotmesh_1 = require("../hotmesh");
|
|
8
8
|
const key_1 = require("../../modules/key");
|
|
9
|
-
|
|
10
|
-
Here is an example of how the methods in this file are used:
|
|
11
|
-
|
|
12
|
-
./client.ts
|
|
13
|
-
|
|
14
|
-
import { Durable } from '@hotmeshio/hotmesh';
|
|
15
|
-
import Redis from 'ioredis';
|
|
16
|
-
import { example } from './workflows';
|
|
17
|
-
import { nanoid } from 'nanoid';
|
|
18
|
-
|
|
19
|
-
async function run() {
|
|
20
|
-
const connection = await Durable.Connection.connect({
|
|
21
|
-
class: Redis,
|
|
22
|
-
options: {
|
|
23
|
-
host: 'localhost',
|
|
24
|
-
port: 6379,
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const client = new Durable.Client({
|
|
29
|
-
connection,
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const handle = await client.workflow.start({
|
|
33
|
-
args: ['HotMesh'],
|
|
34
|
-
taskQueue: 'hello-world',
|
|
35
|
-
workflowName: 'example',
|
|
36
|
-
workflowId: 'workflow-' + nanoid(),
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
console.log(`Started workflow ${handle.workflowId}`);
|
|
40
|
-
console.log(await handle.result());
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
run().catch((err) => {
|
|
44
|
-
console.error(err);
|
|
45
|
-
process.exit(1);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
*/
|
|
9
|
+
const search_1 = require("./search");
|
|
49
10
|
class ClientService {
|
|
50
11
|
constructor(config) {
|
|
51
12
|
this.getHotMeshClient = async (worflowTopic) => {
|
|
@@ -76,6 +37,40 @@ class ClientService {
|
|
|
76
37
|
await this.activateWorkflow(await hotMeshClient);
|
|
77
38
|
return hotMeshClient;
|
|
78
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* For those deployments with a redis stack backend (with the FT module),
|
|
42
|
+
* this method will configure the search index for the workflow.
|
|
43
|
+
*/
|
|
44
|
+
this.configureSearchIndex = async (hotMeshClient, search) => {
|
|
45
|
+
if (search?.schema) {
|
|
46
|
+
const store = hotMeshClient.engine.store;
|
|
47
|
+
const schema = [];
|
|
48
|
+
for (const [key, value] of Object.entries(search.schema)) {
|
|
49
|
+
//prefix with a comma (avoids collisions with hotmesh reserved words)
|
|
50
|
+
schema.push(`_${key}`);
|
|
51
|
+
schema.push(value.type);
|
|
52
|
+
if (value.sortable) {
|
|
53
|
+
schema.push('SORTABLE');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const keyParams = {
|
|
58
|
+
appId: hotMeshClient.appId,
|
|
59
|
+
jobId: ''
|
|
60
|
+
};
|
|
61
|
+
const hotMeshPrefix = key_1.KeyService.mintKey(hotMeshClient.namespace, key_1.KeyType.JOB_STATE, keyParams);
|
|
62
|
+
const prefixes = search.prefix.map((prefix) => `${hotMeshPrefix}${prefix}`);
|
|
63
|
+
await store.exec('FT.CREATE', `${search.index}`, 'ON', 'HASH', 'PREFIX', prefixes.length, ...prefixes, 'SCHEMA', ...schema);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
hotMeshClient.engine.logger.info('durable-client-search-err', { err });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
this.search = async (hotMeshClient, index, query) => {
|
|
71
|
+
const store = hotMeshClient.engine.store;
|
|
72
|
+
return await store.exec('FT.SEARCH', index, ...query);
|
|
73
|
+
};
|
|
79
74
|
this.workflow = {
|
|
80
75
|
start: async (options) => {
|
|
81
76
|
const taskQueueName = options.taskQueue;
|
|
@@ -85,6 +80,7 @@ class ClientService {
|
|
|
85
80
|
//topic is concat of taskQueue and workflowName
|
|
86
81
|
const workflowTopic = `${taskQueueName}-${workflowName}`;
|
|
87
82
|
const hotMeshClient = await this.getHotMeshClient(workflowTopic);
|
|
83
|
+
this.configureSearchIndex(hotMeshClient, options.search);
|
|
88
84
|
const payload = {
|
|
89
85
|
arguments: [...options.args],
|
|
90
86
|
parentWorkflowId: options.parentWorkflowId,
|
|
@@ -94,6 +90,13 @@ class ClientService {
|
|
|
94
90
|
};
|
|
95
91
|
const context = { metadata: { trc, spn }, data: {} };
|
|
96
92
|
const jobId = await hotMeshClient.pub(factory_1.SUBSCRIBES_TOPIC, payload, context);
|
|
93
|
+
if (jobId && options.search?.data) {
|
|
94
|
+
//job successfully kicked off; there is default job data to persist
|
|
95
|
+
const search = new search_1.Search(jobId, hotMeshClient);
|
|
96
|
+
for (const [key, value] of Object.entries(options.search.data)) {
|
|
97
|
+
search.set(key, value);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
97
100
|
return new handle_1.WorkflowHandleService(hotMeshClient, workflowTopic, jobId);
|
|
98
101
|
},
|
|
99
102
|
signal: async (signalId, data) => {
|
|
@@ -103,6 +106,17 @@ class ClientService {
|
|
|
103
106
|
const workflowTopic = `${taskQueue}-${workflowName}`;
|
|
104
107
|
const hotMeshClient = await this.getHotMeshClient(workflowTopic);
|
|
105
108
|
return new handle_1.WorkflowHandleService(hotMeshClient, workflowTopic, workflowId);
|
|
109
|
+
},
|
|
110
|
+
search: async (taskQueue, workflowName, index, ...query) => {
|
|
111
|
+
const workflowTopic = `${taskQueue}-${workflowName}`;
|
|
112
|
+
const hotMeshClient = await this.getHotMeshClient(workflowTopic);
|
|
113
|
+
try {
|
|
114
|
+
return await this.search(hotMeshClient, index, query);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
hotMeshClient.engine.logger.error('durable-client-search-err', { err });
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
106
120
|
}
|
|
107
121
|
};
|
|
108
122
|
this.connection = config.connection;
|
|
@@ -59,7 +59,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
59
59
|
done: false
|
|
60
60
|
|
|
61
61
|
a1:
|
|
62
|
-
type:
|
|
62
|
+
type: hook
|
|
63
63
|
cycle: true
|
|
64
64
|
output:
|
|
65
65
|
schema:
|
|
@@ -132,7 +132,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
132
132
|
done: '{$self.output.data.done}'
|
|
133
133
|
|
|
134
134
|
a2:
|
|
135
|
-
type:
|
|
135
|
+
type: hook
|
|
136
136
|
title: Wait for cleanup signal
|
|
137
137
|
hook:
|
|
138
138
|
type: object
|
|
@@ -248,7 +248,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
248
248
|
|
|
249
249
|
a599:
|
|
250
250
|
title: Sleep exponentially longer before retrying
|
|
251
|
-
type:
|
|
251
|
+
type: hook
|
|
252
252
|
sleep: '{a1.output.data.duration}'
|
|
253
253
|
|
|
254
254
|
c599:
|
|
@@ -540,7 +540,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
540
540
|
done: true
|
|
541
541
|
|
|
542
542
|
s1a:
|
|
543
|
-
type:
|
|
543
|
+
type: hook
|
|
544
544
|
title: Wait for cleanup signal
|
|
545
545
|
hook:
|
|
546
546
|
type: object
|
|
@@ -560,6 +560,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
560
560
|
hooks:
|
|
561
561
|
${app}.activity.awaken:
|
|
562
562
|
- to: s1a
|
|
563
|
+
keep_alive: true
|
|
563
564
|
conditions:
|
|
564
565
|
match:
|
|
565
566
|
- expected: '{t1a.output.data.workflowId}'
|
|
@@ -606,13 +607,13 @@ const getWorkflowYAML = (app, version) => {
|
|
|
606
607
|
target: '{$self.input.data.parentWorkflowId}'
|
|
607
608
|
|
|
608
609
|
a1s:
|
|
609
|
-
type:
|
|
610
|
+
type: hook
|
|
610
611
|
title: Sleep for a duration
|
|
611
612
|
sleep: '{t1s.output.data.duration}'
|
|
612
613
|
emit: true
|
|
613
614
|
|
|
614
615
|
a2s:
|
|
615
|
-
type:
|
|
616
|
+
type: hook
|
|
616
617
|
title: Wait for cleanup signal
|
|
617
618
|
hook:
|
|
618
619
|
type: object
|
|
@@ -684,7 +685,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
684
685
|
|
|
685
686
|
a1wc:
|
|
686
687
|
title: Split signal data
|
|
687
|
-
type:
|
|
688
|
+
type: hook
|
|
688
689
|
cycle: true
|
|
689
690
|
output:
|
|
690
691
|
schema:
|
|
@@ -722,7 +723,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
722
723
|
- ['{t1wc.output.data.signals}', 1]
|
|
723
724
|
- ['{@array.slice}']
|
|
724
725
|
a2wc:
|
|
725
|
-
type:
|
|
726
|
+
type: hook
|
|
726
727
|
output:
|
|
727
728
|
schema:
|
|
728
729
|
type: object
|
|
@@ -849,7 +850,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
849
850
|
target: '{$self.input.data.parentWorkflowId}'
|
|
850
851
|
|
|
851
852
|
a1ww:
|
|
852
|
-
type:
|
|
853
|
+
type: hook
|
|
853
854
|
title: Wait for custom signal
|
|
854
855
|
emit: true
|
|
855
856
|
hook:
|
|
@@ -864,7 +865,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
864
865
|
signalId: '{t1ww.output.data.signalId}'
|
|
865
866
|
|
|
866
867
|
a2ww:
|
|
867
|
-
type:
|
|
868
|
+
type: hook
|
|
868
869
|
title: Wait for cleanup signal
|
|
869
870
|
hook:
|
|
870
871
|
type: object
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { HotMeshService as HotMesh } from '../hotmesh';
|
|
2
|
+
import { RedisClient, RedisMulti } from '../../types/redis';
|
|
3
|
+
import { StoreService } from '../store';
|
|
4
|
+
export declare class Search {
|
|
5
|
+
jobId: string;
|
|
6
|
+
hotMeshClient: HotMesh;
|
|
7
|
+
store: StoreService<RedisClient, RedisMulti> | null;
|
|
8
|
+
safeKey(key: string): string;
|
|
9
|
+
constructor(workflowId: string, hotMeshClient: HotMesh);
|
|
10
|
+
set(key: string, value: string): Promise<void>;
|
|
11
|
+
get(key: string): Promise<string>;
|
|
12
|
+
del(key: string): Promise<void>;
|
|
13
|
+
incr(key: string, val: number): Promise<number>;
|
|
14
|
+
mult(key: string, val: number): Promise<number>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Search = void 0;
|
|
4
|
+
const key_1 = require("../../modules/key");
|
|
5
|
+
class Search {
|
|
6
|
+
safeKey(key) {
|
|
7
|
+
//note: protect the execution namespace with a prefix,
|
|
8
|
+
//so its design never conflicts with the hotmesh keyspace
|
|
9
|
+
return `_${key}`;
|
|
10
|
+
}
|
|
11
|
+
constructor(workflowId, hotMeshClient) {
|
|
12
|
+
const keyParams = {
|
|
13
|
+
appId: hotMeshClient.appId,
|
|
14
|
+
jobId: ''
|
|
15
|
+
};
|
|
16
|
+
const hotMeshPrefix = key_1.KeyService.mintKey(hotMeshClient.namespace, key_1.KeyType.JOB_STATE, keyParams);
|
|
17
|
+
this.jobId = `${hotMeshPrefix}${workflowId}`;
|
|
18
|
+
this.hotMeshClient = hotMeshClient;
|
|
19
|
+
this.store = hotMeshClient.engine.store;
|
|
20
|
+
}
|
|
21
|
+
async set(key, value) {
|
|
22
|
+
await this.store.exec('HSET', this.jobId, this.safeKey(key), value.toString());
|
|
23
|
+
}
|
|
24
|
+
async get(key) {
|
|
25
|
+
try {
|
|
26
|
+
return await this.store.exec('HGET', this.jobId, this.safeKey(key));
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
this.hotMeshClient.logger.error('durable-search-get-error', { err });
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async del(key) {
|
|
34
|
+
await this.store.exec('HDEL', this.jobId, this.safeKey(key));
|
|
35
|
+
}
|
|
36
|
+
async incr(key, val) {
|
|
37
|
+
return Number(await this.store.exec('HINCRBYFLOAT', this.jobId, this.safeKey(key), val.toString()));
|
|
38
|
+
}
|
|
39
|
+
async mult(key, val) {
|
|
40
|
+
const log = Math.log(val);
|
|
41
|
+
const logTotal = Number(await this.store.exec('HINCRBYFLOAT', this.jobId, this.safeKey(key), log.toString()));
|
|
42
|
+
return Math.exp(logTotal);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.Search = Search;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HotMeshService as HotMesh } from '../hotmesh';
|
|
2
|
-
import { Connection, Registry, WorkerConfig, WorkerOptions } from "../../types/durable";
|
|
2
|
+
import { Connection, Registry, WorkerConfig, WorkerOptions, WorkflowSearchOptions } from "../../types/durable";
|
|
3
3
|
export declare class WorkerService {
|
|
4
4
|
static activityRegistry: Registry;
|
|
5
5
|
static connection: Connection;
|
|
@@ -14,6 +14,11 @@ export declare class WorkerService {
|
|
|
14
14
|
* allowing proxyActivities to succeed.
|
|
15
15
|
*/
|
|
16
16
|
static registerActivities<ACT>(activities: ACT): Registry;
|
|
17
|
+
/**
|
|
18
|
+
* For those deployments with a redis stack backend (with the FT module),
|
|
19
|
+
* this method will configure the search index for the workflow.
|
|
20
|
+
*/
|
|
21
|
+
static configureSearchIndex(hotMeshClient: HotMesh, search?: WorkflowSearchOptions): Promise<void>;
|
|
17
22
|
static create(config: WorkerConfig): Promise<WorkerService>;
|
|
18
23
|
static resolveWorkflowTarget(workflow: object | Function): [string, Function];
|
|
19
24
|
run(): Promise<void>;
|
|
@@ -7,36 +7,7 @@ const asyncLocalStorage_1 = require("./asyncLocalStorage");
|
|
|
7
7
|
const factory_1 = require("./factory");
|
|
8
8
|
const hotmesh_1 = require("../hotmesh");
|
|
9
9
|
const stream_1 = require("../../types/stream");
|
|
10
|
-
|
|
11
|
-
Here is an example of how the methods in this file are used:
|
|
12
|
-
|
|
13
|
-
./worker.ts
|
|
14
|
-
|
|
15
|
-
import { Durable } from '@hotmeshio/hotmesh';
|
|
16
|
-
import Redis from 'ioredis'; //OR `import * as Redis from 'redis';`
|
|
17
|
-
|
|
18
|
-
import * as workflows from './workflows';
|
|
19
|
-
|
|
20
|
-
async function run() {
|
|
21
|
-
const worker = await Durable.Worker.create({
|
|
22
|
-
connection: {
|
|
23
|
-
class: Redis,
|
|
24
|
-
options: {
|
|
25
|
-
host: 'localhost',
|
|
26
|
-
port: 6379,
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
taskQueue: 'hello-world',
|
|
30
|
-
workflow: workflows.example,
|
|
31
|
-
});
|
|
32
|
-
await worker.run();
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
run().catch((err) => {
|
|
36
|
-
console.error(err);
|
|
37
|
-
process.exit(1);
|
|
38
|
-
});
|
|
39
|
-
*/
|
|
10
|
+
const key_1 = require("../../modules/key");
|
|
40
11
|
class WorkerService {
|
|
41
12
|
static async activateWorkflow(hotMesh) {
|
|
42
13
|
const app = await hotMesh.engine.store.getApp(factory_1.APP_ID);
|
|
@@ -79,6 +50,38 @@ class WorkerService {
|
|
|
79
50
|
}
|
|
80
51
|
return WorkerService.activityRegistry;
|
|
81
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* For those deployments with a redis stack backend (with the FT module),
|
|
55
|
+
* this method will configure the search index for the workflow.
|
|
56
|
+
*/
|
|
57
|
+
//todo: bind this to the Search service; update constructor to expect hotMeshClient as first param (id is optional
|
|
58
|
+
//refactor and delete other one as well)
|
|
59
|
+
static async configureSearchIndex(hotMeshClient, search) {
|
|
60
|
+
if (search?.schema) {
|
|
61
|
+
const store = hotMeshClient.engine.store;
|
|
62
|
+
const schema = [];
|
|
63
|
+
for (const [key, value] of Object.entries(search.schema)) {
|
|
64
|
+
//prefix with a comma (avoids collisions with hotmesh reserved words)
|
|
65
|
+
schema.push(`_${key}`);
|
|
66
|
+
schema.push(value.type);
|
|
67
|
+
if (value.sortable) {
|
|
68
|
+
schema.push('SORTABLE');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const keyParams = {
|
|
73
|
+
appId: hotMeshClient.appId,
|
|
74
|
+
jobId: ''
|
|
75
|
+
};
|
|
76
|
+
const hotMeshPrefix = key_1.KeyService.mintKey(hotMeshClient.namespace, key_1.KeyType.JOB_STATE, keyParams);
|
|
77
|
+
const prefixes = search.prefix.map((prefix) => `${hotMeshPrefix}${prefix}`);
|
|
78
|
+
await store.exec('FT.CREATE', `${search.index}`, 'ON', 'HASH', 'PREFIX', prefixes.length, ...prefixes, 'SCHEMA', ...schema);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
hotMeshClient.engine.logger.info('durable-client-search-err', { err });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
82
85
|
static async create(config) {
|
|
83
86
|
//always call `registerActivities` before `import`
|
|
84
87
|
WorkerService.connection = config.connection;
|
|
@@ -91,6 +94,7 @@ class WorkerService {
|
|
|
91
94
|
const worker = new WorkerService();
|
|
92
95
|
worker.activityRunner = await worker.initActivityWorker(config, activityTopic);
|
|
93
96
|
worker.workflowRunner = await worker.initWorkflowWorker(config, workflowTopic, workflowFunction);
|
|
97
|
+
WorkerService.configureSearchIndex(worker.workflowRunner, config.search);
|
|
94
98
|
await WorkerService.activateWorkflow(worker.workflowRunner);
|
|
95
99
|
return worker;
|
|
96
100
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { ActivityConfig, ProxyType, WorkflowOptions } from "../../types/durable";
|
|
2
|
+
import { Search } from './search';
|
|
2
3
|
export declare class WorkflowService {
|
|
3
4
|
/**
|
|
4
5
|
* Spawn a child workflow. await the result.
|
|
5
6
|
*/
|
|
6
7
|
static executeChild<T>(options: WorkflowOptions): Promise<T>;
|
|
7
8
|
static proxyActivities<ACT>(options?: ActivityConfig): ProxyType<ACT>;
|
|
9
|
+
static search(): Promise<Search>;
|
|
8
10
|
static sleep(duration: string): Promise<number>;
|
|
9
11
|
static waitForSignal(signals: string[], options?: Record<string, string>): Promise<Record<any, any>[]>;
|
|
10
12
|
static wrapActivity<T>(activityName: string, options?: ActivityConfig): T;
|
|
@@ -11,34 +11,7 @@ const client_1 = require("./client");
|
|
|
11
11
|
const connection_1 = require("./connection");
|
|
12
12
|
const factory_1 = require("./factory");
|
|
13
13
|
const errors_1 = require("../../modules/errors");
|
|
14
|
-
|
|
15
|
-
`proxyActivities` returns a wrapped instance of the
|
|
16
|
-
target activity, so that when the workflow calls a
|
|
17
|
-
proxied activity, it is actually calling the proxy
|
|
18
|
-
function, which in turn calls the activity function.
|
|
19
|
-
|
|
20
|
-
Here is an example of how the methods in this file are used:
|
|
21
|
-
|
|
22
|
-
./workflows.ts
|
|
23
|
-
|
|
24
|
-
import { Durable } from '@hotmeshio/hotmesh';
|
|
25
|
-
import * as activities from './activities';
|
|
26
|
-
|
|
27
|
-
const { greet } = Durable.workflow.proxyActivities<typeof activities>({
|
|
28
|
-
activities: activities,
|
|
29
|
-
startToCloseTimeout: '1 minute',
|
|
30
|
-
retryPolicy: {
|
|
31
|
-
initialInterval: '5 seconds', // Initial delay between retries
|
|
32
|
-
maximumAttempts: 3, // Max number of retry attempts
|
|
33
|
-
backoffCoefficient: 2.0, // Backoff factor for delay between retries: delay = initialInterval * (backoffCoefficient ^ retry_attempt)
|
|
34
|
-
maximumInterval: '30 seconds', // Max delay between retries
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
export async function example(name: string): Promise<string> {
|
|
39
|
-
return await greet(name);
|
|
40
|
-
}
|
|
41
|
-
*/
|
|
14
|
+
const search_1 = require("./search");
|
|
42
15
|
class WorkflowService {
|
|
43
16
|
/**
|
|
44
17
|
* Spawn a child workflow. await the result.
|
|
@@ -88,6 +61,16 @@ class WorkflowService {
|
|
|
88
61
|
}
|
|
89
62
|
return proxy;
|
|
90
63
|
}
|
|
64
|
+
static async search() {
|
|
65
|
+
const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
|
|
66
|
+
if (!store) {
|
|
67
|
+
throw new Error('durable-store-not-found');
|
|
68
|
+
}
|
|
69
|
+
const workflowId = store.get('workflowId');
|
|
70
|
+
const workflowTopic = store.get('workflowTopic');
|
|
71
|
+
const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic);
|
|
72
|
+
return new search_1.Search(workflowId, hotMeshClient);
|
|
73
|
+
}
|
|
91
74
|
static async sleep(duration) {
|
|
92
75
|
const seconds = (0, ms_1.default)(duration) / 1000;
|
|
93
76
|
const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Await } from '../activities/await';
|
|
2
|
+
import { Cycle } from '../activities/cycle';
|
|
3
|
+
import { Hook } from '../activities/hook';
|
|
4
|
+
import { Signal } from '../activities/signal';
|
|
5
|
+
import { Worker } from '../activities/worker';
|
|
6
|
+
import { Trigger } from '../activities/trigger';
|
|
2
7
|
import { ILogger } from '../logger';
|
|
3
8
|
import { StoreSignaler } from '../signaler/store';
|
|
4
9
|
import { StreamSignaler } from '../signaler/stream';
|
|
@@ -44,7 +49,7 @@ declare class EngineService {
|
|
|
44
49
|
processWebHooks(): Promise<void>;
|
|
45
50
|
processTimeHooks(): Promise<void>;
|
|
46
51
|
throttle(delayInMillis: number): Promise<void>;
|
|
47
|
-
initActivity(topic: string, data?: JobData, context?: JobState): Promise<
|
|
52
|
+
initActivity(topic: string, data?: JobData, context?: JobState): Promise<Await | Cycle | Hook | Signal | Trigger | Worker>;
|
|
48
53
|
getSchema(topic: string): Promise<[activityId: string, schema: ActivityType]>;
|
|
49
54
|
getSettings(): Promise<HotMeshSettings>;
|
|
50
55
|
isPrivate(topic: string): boolean;
|
|
@@ -143,7 +143,8 @@ class EngineService {
|
|
|
143
143
|
// ************* METADATA/MODEL METHODS *************
|
|
144
144
|
async initActivity(topic, data = {}, context) {
|
|
145
145
|
const [activityId, schema] = await this.getSchema(topic);
|
|
146
|
-
|
|
146
|
+
utils_1.polyfill;
|
|
147
|
+
const ActivityHandler = activities_1.default[utils_1.polyfill.resolveActivityType(schema.type)];
|
|
147
148
|
if (ActivityHandler) {
|
|
148
149
|
const utc = (0, utils_1.formatISODate)(new Date());
|
|
149
150
|
const metadata = {
|
|
@@ -14,6 +14,7 @@ declare class IORedisStoreService extends StoreService<RedisClientType, RedisMul
|
|
|
14
14
|
serializer: Serializer;
|
|
15
15
|
constructor(redisClient: RedisClientType);
|
|
16
16
|
getMulti(): RedisMultiType;
|
|
17
|
+
exec(...args: any[]): Promise<string | string[] | string[][]>;
|
|
17
18
|
hGetAllResult(result: any): any;
|
|
18
19
|
addTaskQueues(keys: string[]): Promise<void>;
|
|
19
20
|
publish(keyType: KeyType.QUORUM, message: Record<string, any>, appId: string, engineId?: string): Promise<boolean>;
|
|
@@ -10,6 +10,18 @@ class IORedisStoreService extends index_1.StoreService {
|
|
|
10
10
|
getMulti() {
|
|
11
11
|
return this.redisClient.multi();
|
|
12
12
|
}
|
|
13
|
+
async exec(...args) {
|
|
14
|
+
const response = await this.redisClient.call.apply(this.redisClient, args);
|
|
15
|
+
if (typeof response === 'string') {
|
|
16
|
+
return response;
|
|
17
|
+
}
|
|
18
|
+
else if (Array.isArray(response)) {
|
|
19
|
+
if (Array.isArray(response[0])) {
|
|
20
|
+
return response;
|
|
21
|
+
}
|
|
22
|
+
return response;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
13
25
|
hGetAllResult(result) {
|
|
14
26
|
//ioredis response signature is [null, {}] or [null, null]
|
|
15
27
|
return result[1];
|
|
@@ -15,6 +15,7 @@ declare class RedisStoreService extends StoreService<RedisClientType, RedisMulti
|
|
|
15
15
|
commands: Record<string, string>;
|
|
16
16
|
constructor(redisClient: RedisClientType);
|
|
17
17
|
getMulti(): RedisMultiType;
|
|
18
|
+
exec(...args: any[]): Promise<string | string[] | string[][]>;
|
|
18
19
|
publish(keyType: KeyType.QUORUM, message: Record<string, any>, appId: string, engineId?: string): Promise<boolean>;
|
|
19
20
|
zAdd(key: string, score: number | string, value: string | number, redisMulti?: RedisMultiType): Promise<any>;
|
|
20
21
|
zRangeByScoreWithScores(key: string, score: number | string, value: string | number): Promise<string | null>;
|
|
@@ -36,6 +36,9 @@ class RedisStoreService extends index_1.StoreService {
|
|
|
36
36
|
const multi = this.redisClient.MULTI();
|
|
37
37
|
return multi;
|
|
38
38
|
}
|
|
39
|
+
async exec(...args) {
|
|
40
|
+
return await this.redisClient.sendCommand(args);
|
|
41
|
+
}
|
|
39
42
|
async publish(keyType, message, appId, engineId) {
|
|
40
43
|
const topic = this.mintKey(keyType, { appId, engineId });
|
|
41
44
|
const status = await this.redisClient.publish(topic, JSON.stringify(message));
|
|
@@ -22,6 +22,7 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
22
22
|
logger: ILogger;
|
|
23
23
|
commands: Record<string, string>;
|
|
24
24
|
abstract getMulti(): U;
|
|
25
|
+
abstract exec(...args: any[]): Promise<string | string[] | string[][]>;
|
|
25
26
|
abstract publish(keyType: KeyType.QUORUM, message: Record<string, any>, appId: string, engineId?: string): Promise<boolean>;
|
|
26
27
|
abstract xgroup(command: 'CREATE', key: string, groupName: string, id: string, mkStream?: 'MKSTREAM'): Promise<boolean>;
|
|
27
28
|
abstract xadd(key: string, id: string, messageId: string, messageValue: string, multi?: U): Promise<string | U>;
|
|
@@ -8,6 +8,7 @@ const package_json_1 = __importDefault(require("../../package.json"));
|
|
|
8
8
|
const mapper_1 = require("../mapper");
|
|
9
9
|
const stream_1 = require("../../types/stream");
|
|
10
10
|
const telemetry_1 = require("../../types/telemetry");
|
|
11
|
+
const utils_1 = require("../../modules/utils");
|
|
11
12
|
class TelemetryService {
|
|
12
13
|
constructor(appId, config, metadata, context) {
|
|
13
14
|
this.leg = 1;
|
|
@@ -209,7 +210,7 @@ class TelemetryService {
|
|
|
209
210
|
state[`${metadata.aid}/output/metadata/l1s`] = context['$self'].output.metadata.l1s;
|
|
210
211
|
state[`${metadata.aid}/output/metadata/l2s`] = context['$self'].output.metadata.l2s;
|
|
211
212
|
}
|
|
212
|
-
else if (config.type === '
|
|
213
|
+
else if (utils_1.polyfill.resolveActivityType(config.type) === 'hook' && leg === 1) {
|
|
213
214
|
//activities run non-duplexed and only have a single leg
|
|
214
215
|
state[`${metadata.aid}/output/metadata/l1s`] = context['$self'].output.metadata.l1s;
|
|
215
216
|
state[`${metadata.aid}/output/metadata/l2s`] = context['$self'].output.metadata.l1s;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MetricTypes } from "./stats";
|
|
2
2
|
import { StreamRetryPolicy } from "./stream";
|
|
3
|
-
type ActivityExecutionType = 'trigger' | 'await' | 'worker' | 'activity' | 'emit' | 'iterate' | 'cycle' | 'signal';
|
|
3
|
+
type ActivityExecutionType = 'trigger' | 'await' | 'worker' | 'activity' | 'emit' | 'iterate' | 'cycle' | 'signal' | 'hook';
|
|
4
4
|
type Consumes = Record<string, string[]>;
|
|
5
5
|
interface BaseActivity {
|
|
6
6
|
title?: string;
|
|
@@ -59,6 +59,9 @@ interface CycleActivity extends BaseActivity {
|
|
|
59
59
|
type: 'cycle';
|
|
60
60
|
ancestor: string;
|
|
61
61
|
}
|
|
62
|
+
interface HookActivity extends BaseActivity {
|
|
63
|
+
type: 'hook';
|
|
64
|
+
}
|
|
62
65
|
interface SignalActivity extends BaseActivity {
|
|
63
66
|
type: 'signal';
|
|
64
67
|
subtype: 'one' | 'all';
|
|
@@ -72,7 +75,7 @@ interface SignalActivity extends BaseActivity {
|
|
|
72
75
|
interface IterateActivity extends BaseActivity {
|
|
73
76
|
type: 'iterate';
|
|
74
77
|
}
|
|
75
|
-
type ActivityType = BaseActivity | TriggerActivity | AwaitActivity | WorkerActivity | IterateActivity;
|
|
78
|
+
type ActivityType = BaseActivity | TriggerActivity | AwaitActivity | WorkerActivity | IterateActivity | HookActivity;
|
|
76
79
|
type ActivityData = Record<string, any>;
|
|
77
80
|
type ActivityMetadata = {
|
|
78
81
|
aid: string;
|
|
@@ -98,4 +101,4 @@ type ActivityDataType = {
|
|
|
98
101
|
hook?: Record<string, unknown>;
|
|
99
102
|
};
|
|
100
103
|
type ActivityLeg = 1 | 2;
|
|
101
|
-
export { ActivityContext, ActivityData, ActivityDataType, ActivityDuplex, ActivityLeg, ActivityMetadata, ActivityType, Consumes, TriggerActivityStats, AwaitActivity, CycleActivity, SignalActivity, BaseActivity, IterateActivity, TriggerActivity, WorkerActivity };
|
|
104
|
+
export { ActivityContext, ActivityData, ActivityDataType, ActivityDuplex, ActivityLeg, ActivityMetadata, ActivityType, Consumes, TriggerActivityStats, AwaitActivity, CycleActivity, HookActivity, SignalActivity, BaseActivity, IterateActivity, TriggerActivity, WorkerActivity };
|
package/build/types/durable.d.ts
CHANGED
|
@@ -5,6 +5,15 @@ type WorkflowConfig = {
|
|
|
5
5
|
maximumInterval?: string;
|
|
6
6
|
initialInterval?: string;
|
|
7
7
|
};
|
|
8
|
+
type WorkflowSearchOptions = {
|
|
9
|
+
index?: string;
|
|
10
|
+
prefix?: string[];
|
|
11
|
+
schema?: Record<string, {
|
|
12
|
+
type: 'TEXT' | 'NUMERIC' | 'TAG';
|
|
13
|
+
sortable: boolean;
|
|
14
|
+
}>;
|
|
15
|
+
data?: Record<string, string>;
|
|
16
|
+
};
|
|
8
17
|
type WorkflowOptions = {
|
|
9
18
|
taskQueue: string;
|
|
10
19
|
args: any[];
|
|
@@ -13,6 +22,7 @@ type WorkflowOptions = {
|
|
|
13
22
|
parentWorkflowId?: string;
|
|
14
23
|
workflowTrace?: string;
|
|
15
24
|
workflowSpan?: string;
|
|
25
|
+
search?: WorkflowSearchOptions;
|
|
16
26
|
config?: WorkflowConfig;
|
|
17
27
|
};
|
|
18
28
|
type SignalOptions = {
|
|
@@ -50,6 +60,7 @@ type WorkerConfig = {
|
|
|
50
60
|
taskQueue: string;
|
|
51
61
|
workflow: Function;
|
|
52
62
|
options?: WorkerOptions;
|
|
63
|
+
search?: WorkflowSearchOptions;
|
|
53
64
|
};
|
|
54
65
|
type WorkerOptions = {
|
|
55
66
|
maxSystemRetries?: number;
|
|
@@ -73,4 +84,4 @@ type ActivityConfig = {
|
|
|
73
84
|
maximumInterval: string;
|
|
74
85
|
};
|
|
75
86
|
};
|
|
76
|
-
export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, NativeConnection, ProxyType, Registry, SignalOptions, WorkerConfig, WorkflowConfig, WorkerOptions, WorkflowDataType, WorkflowOptions, };
|
|
87
|
+
export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, NativeConnection, ProxyType, Registry, SignalOptions, WorkerConfig, WorkflowConfig, WorkerOptions, WorkflowSearchOptions, WorkflowDataType, WorkflowOptions, };
|
package/build/types/hook.d.ts
CHANGED
package/build/types/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export { ActivityType, ActivityDataType, ActivityContext, ActivityData, ActivityDuplex, ActivityLeg, ActivityMetadata, Consumes, AwaitActivity, BaseActivity, CycleActivity, WorkerActivity, IterateActivity, SignalActivity, TriggerActivity, TriggerActivityStats } from './activity';
|
|
1
|
+
export { ActivityType, ActivityDataType, ActivityContext, ActivityData, ActivityDuplex, ActivityLeg, ActivityMetadata, Consumes, AwaitActivity, BaseActivity, CycleActivity, HookActivity, WorkerActivity, IterateActivity, SignalActivity, TriggerActivity, TriggerActivityStats } from './activity';
|
|
2
2
|
export { App, AppVID, AppTransitions, AppSubscriptions } from './app';
|
|
3
3
|
export { AsyncSignal } from './async';
|
|
4
4
|
export { CacheMode } from './cache';
|
|
5
5
|
export { CollationFaultType, CollationStage } from './collator';
|
|
6
|
-
export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, NativeConnection, ProxyType, Registry, WorkflowConfig, WorkerConfig, WorkerOptions, WorkflowDataType, WorkflowOptions, } from './durable';
|
|
6
|
+
export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, NativeConnection, ProxyType, Registry, WorkflowConfig, WorkerConfig, WorkerOptions, WorkflowSearchOptions, WorkflowDataType, WorkflowOptions, } from './durable';
|
|
7
7
|
export { HookCondition, HookConditions, HookGate, HookInterface, HookRule, HookRules, HookSignal } from './hook';
|
|
8
8
|
export { RedisClientType as IORedisClientType, RedisMultiType as IORedisMultiType } from './ioredisclient';
|
|
9
9
|
export { ILogger } from './logger';
|