@hotmeshio/hotmesh 0.0.16 → 0.0.18
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 +10 -1
- package/build/package.json +1 -1
- package/build/services/activities/activity.d.ts +4 -12
- package/build/services/activities/activity.js +19 -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/activities/trigger.js +1 -1
- 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 +47 -0
- package/build/services/durable/factory.js +88 -11
- package/build/services/durable/search.d.ts +15 -0
- package/build/services/durable/search.js +45 -0
- package/build/services/durable/workflow.d.ts +1 -0
- package/build/services/durable/workflow.js +36 -0
- 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 +11 -1
- package/build/types/hook.d.ts +1 -0
- package/build/types/index.d.ts +2 -2
- package/modules/utils.ts +11 -0
- package/package.json +1 -1
- package/services/activities/activity.ts +20 -167
- package/services/activities/hook.ts +149 -0
- package/services/activities/index.ts +2 -0
- package/services/activities/trigger.ts +1 -1
- package/services/collator/index.ts +0 -1
- package/services/compiler/deployer.ts +32 -2
- package/services/durable/client.ts +51 -2
- package/services/durable/factory.ts +88 -11
- package/services/durable/search.ts +54 -0
- package/services/durable/workflow.ts +34 -1
- 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 +9 -0
- package/types/hook.ts +1 -0
- package/types/index.ts +2 -0
|
@@ -76,6 +76,40 @@ class ClientService {
|
|
|
76
76
|
await this.activateWorkflow(await hotMeshClient);
|
|
77
77
|
return hotMeshClient;
|
|
78
78
|
};
|
|
79
|
+
/**
|
|
80
|
+
* For those deployments with a redis stack backend (with the FT module),
|
|
81
|
+
* this method will configure the search index for the workflow.
|
|
82
|
+
*/
|
|
83
|
+
this.configureSearchIndex = async (hotMeshClient, search) => {
|
|
84
|
+
if (search) {
|
|
85
|
+
const store = hotMeshClient.engine.store;
|
|
86
|
+
const schema = [];
|
|
87
|
+
for (const [key, value] of Object.entries(search.schema)) {
|
|
88
|
+
//prefix with a comma (avoids collisions with hotmesh reserved words)
|
|
89
|
+
schema.push(`_${key}`);
|
|
90
|
+
schema.push(value.type);
|
|
91
|
+
if (value.sortable) {
|
|
92
|
+
schema.push('SORTABLE');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const keyParams = {
|
|
97
|
+
appId: hotMeshClient.appId,
|
|
98
|
+
jobId: ''
|
|
99
|
+
};
|
|
100
|
+
const hotMeshPrefix = key_1.KeyService.mintKey(hotMeshClient.namespace, key_1.KeyType.JOB_STATE, keyParams);
|
|
101
|
+
const prefixes = search.prefix.map((prefix) => `${hotMeshPrefix}${prefix}`);
|
|
102
|
+
await store.exec('FT.CREATE', `${search.index}`, 'ON', 'HASH', 'PREFIX', prefixes.length, ...prefixes, 'SCHEMA', ...schema);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
hotMeshClient.engine.logger.info('durable-client-search-err', { err });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
this.search = async (hotMeshClient, index, query) => {
|
|
110
|
+
const store = hotMeshClient.engine.store;
|
|
111
|
+
return await store.exec('FT.SEARCH', index, ...query);
|
|
112
|
+
};
|
|
79
113
|
this.workflow = {
|
|
80
114
|
start: async (options) => {
|
|
81
115
|
const taskQueueName = options.taskQueue;
|
|
@@ -85,8 +119,10 @@ class ClientService {
|
|
|
85
119
|
//topic is concat of taskQueue and workflowName
|
|
86
120
|
const workflowTopic = `${taskQueueName}-${workflowName}`;
|
|
87
121
|
const hotMeshClient = await this.getHotMeshClient(workflowTopic);
|
|
122
|
+
this.configureSearchIndex(hotMeshClient, options.search);
|
|
88
123
|
const payload = {
|
|
89
124
|
arguments: [...options.args],
|
|
125
|
+
parentWorkflowId: options.parentWorkflowId,
|
|
90
126
|
workflowId: options.workflowId || (0, nanoid_1.nanoid)(),
|
|
91
127
|
workflowTopic: workflowTopic,
|
|
92
128
|
backoffCoefficient: options.config?.backoffCoefficient || factory_1.DEFAULT_COEFFICIENT,
|
|
@@ -102,6 +138,17 @@ class ClientService {
|
|
|
102
138
|
const workflowTopic = `${taskQueue}-${workflowName}`;
|
|
103
139
|
const hotMeshClient = await this.getHotMeshClient(workflowTopic);
|
|
104
140
|
return new handle_1.WorkflowHandleService(hotMeshClient, workflowTopic, workflowId);
|
|
141
|
+
},
|
|
142
|
+
search: async (taskQueue, workflowName, index, ...query) => {
|
|
143
|
+
const workflowTopic = `${taskQueue}-${workflowName}`;
|
|
144
|
+
const hotMeshClient = await this.getHotMeshClient(workflowTopic);
|
|
145
|
+
try {
|
|
146
|
+
return await this.search(hotMeshClient, index, query);
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
hotMeshClient.engine.logger.error('durable-client-search-err', { err });
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
105
152
|
}
|
|
106
153
|
};
|
|
107
154
|
this.connection = config.connection;
|
|
@@ -25,6 +25,8 @@ const getWorkflowYAML = (app, version) => {
|
|
|
25
25
|
schema:
|
|
26
26
|
type: object
|
|
27
27
|
properties:
|
|
28
|
+
parentWorkflowId:
|
|
29
|
+
type: string
|
|
28
30
|
workflowId:
|
|
29
31
|
type: string
|
|
30
32
|
arguments:
|
|
@@ -47,12 +49,17 @@ const getWorkflowYAML = (app, version) => {
|
|
|
47
49
|
type: trigger
|
|
48
50
|
stats:
|
|
49
51
|
id: '{$self.input.data.workflowId}'
|
|
52
|
+
key: '{$self.input.data.parentWorkflowId}'
|
|
53
|
+
granularity: infinity
|
|
54
|
+
measures:
|
|
55
|
+
- measure: index
|
|
56
|
+
target: '{$self.input.data.parentWorkflowId}'
|
|
50
57
|
job:
|
|
51
58
|
maps:
|
|
52
59
|
done: false
|
|
53
60
|
|
|
54
61
|
a1:
|
|
55
|
-
type:
|
|
62
|
+
type: hook
|
|
56
63
|
cycle: true
|
|
57
64
|
output:
|
|
58
65
|
schema:
|
|
@@ -66,6 +73,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
66
73
|
w1:
|
|
67
74
|
type: worker
|
|
68
75
|
topic: '{t1.output.data.workflowTopic}'
|
|
76
|
+
emit: '{$job.data.done}'
|
|
69
77
|
retry:
|
|
70
78
|
'599': [2]
|
|
71
79
|
input:
|
|
@@ -123,6 +131,19 @@ const getWorkflowYAML = (app, version) => {
|
|
|
123
131
|
response: '{$self.output.data.response}'
|
|
124
132
|
done: '{$self.output.data.done}'
|
|
125
133
|
|
|
134
|
+
a2:
|
|
135
|
+
type: hook
|
|
136
|
+
title: Wait for cleanup signal
|
|
137
|
+
hook:
|
|
138
|
+
type: object
|
|
139
|
+
properties:
|
|
140
|
+
done:
|
|
141
|
+
type: boolean
|
|
142
|
+
job:
|
|
143
|
+
maps:
|
|
144
|
+
workflowId: '{t1.output.data.workflowId}'
|
|
145
|
+
|
|
146
|
+
|
|
126
147
|
a594:
|
|
127
148
|
title: Wait for signals
|
|
128
149
|
type: await
|
|
@@ -227,7 +248,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
227
248
|
|
|
228
249
|
a599:
|
|
229
250
|
title: Sleep exponentially longer before retrying
|
|
230
|
-
type:
|
|
251
|
+
type: hook
|
|
231
252
|
sleep: '{a1.output.data.duration}'
|
|
232
253
|
|
|
233
254
|
c599:
|
|
@@ -280,7 +301,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
280
301
|
done: true
|
|
281
302
|
|
|
282
303
|
s2:
|
|
283
|
-
title: Awaken
|
|
304
|
+
title: Awaken sleeping flows so they end and self-clean
|
|
284
305
|
type: signal
|
|
285
306
|
subtype: all
|
|
286
307
|
key_name: parentWorkflowId
|
|
@@ -352,10 +373,55 @@ const getWorkflowYAML = (app, version) => {
|
|
|
352
373
|
type: boolean
|
|
353
374
|
maps:
|
|
354
375
|
done: true
|
|
376
|
+
|
|
377
|
+
s4:
|
|
378
|
+
title: Awaken child FLOWS so they end and self-clean
|
|
379
|
+
type: signal
|
|
380
|
+
subtype: all
|
|
381
|
+
key_name: parentWorkflowId
|
|
382
|
+
key_value:
|
|
383
|
+
'@pipe':
|
|
384
|
+
- ['{$job.metadata.jid}', '-f']
|
|
385
|
+
- ['{@string.concat}']
|
|
386
|
+
topic: ${app}.childflow.awaken
|
|
387
|
+
resolver:
|
|
388
|
+
schema:
|
|
389
|
+
type: object
|
|
390
|
+
properties:
|
|
391
|
+
data:
|
|
392
|
+
type: object
|
|
393
|
+
properties:
|
|
394
|
+
parentWorkflowId:
|
|
395
|
+
type: string
|
|
396
|
+
scrub:
|
|
397
|
+
type: boolean
|
|
398
|
+
maps:
|
|
399
|
+
data:
|
|
400
|
+
parentWorkflowId:
|
|
401
|
+
'@pipe':
|
|
402
|
+
- ['{$job.metadata.jid}', '-f']
|
|
403
|
+
- ['{@string.concat}']
|
|
404
|
+
scrub: true
|
|
405
|
+
signal:
|
|
406
|
+
schema:
|
|
407
|
+
type: object
|
|
408
|
+
properties:
|
|
409
|
+
done:
|
|
410
|
+
type: boolean
|
|
411
|
+
maps:
|
|
412
|
+
done: true
|
|
355
413
|
|
|
356
414
|
transitions:
|
|
357
415
|
t1:
|
|
358
416
|
- to: a1
|
|
417
|
+
- to: a2
|
|
418
|
+
conditions:
|
|
419
|
+
match:
|
|
420
|
+
- expected: true
|
|
421
|
+
actual:
|
|
422
|
+
'@pipe':
|
|
423
|
+
- ['{$job.metadata.key}', true, false]
|
|
424
|
+
- ['{@conditional.ternary}']
|
|
359
425
|
a1:
|
|
360
426
|
- to: w1
|
|
361
427
|
w1:
|
|
@@ -377,11 +443,13 @@ const getWorkflowYAML = (app, version) => {
|
|
|
377
443
|
- to: s3
|
|
378
444
|
conditions:
|
|
379
445
|
code: [200, 598, 597, 596]
|
|
446
|
+
- to: s4
|
|
447
|
+
conditions:
|
|
448
|
+
code: [200, 598, 597, 596]
|
|
380
449
|
a594:
|
|
381
450
|
- to: c594
|
|
382
451
|
conditions:
|
|
383
452
|
code: 202
|
|
384
|
-
|
|
385
453
|
a595:
|
|
386
454
|
- to: c595
|
|
387
455
|
conditions:
|
|
@@ -389,6 +457,14 @@ const getWorkflowYAML = (app, version) => {
|
|
|
389
457
|
a599:
|
|
390
458
|
- to: c599
|
|
391
459
|
|
|
460
|
+
hooks:
|
|
461
|
+
${app}.childflow.awaken:
|
|
462
|
+
- to: a2
|
|
463
|
+
conditions:
|
|
464
|
+
match:
|
|
465
|
+
- expected: '{t1.output.data.workflowId}'
|
|
466
|
+
actual: '{$self.hook.data.id}'
|
|
467
|
+
|
|
392
468
|
- subscribes: ${app}.activity.execute
|
|
393
469
|
publishes: ${app}.activity.executed
|
|
394
470
|
|
|
@@ -464,7 +540,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
464
540
|
done: true
|
|
465
541
|
|
|
466
542
|
s1a:
|
|
467
|
-
type:
|
|
543
|
+
type: hook
|
|
468
544
|
title: Wait for cleanup signal
|
|
469
545
|
hook:
|
|
470
546
|
type: object
|
|
@@ -484,6 +560,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
484
560
|
hooks:
|
|
485
561
|
${app}.activity.awaken:
|
|
486
562
|
- to: s1a
|
|
563
|
+
keep_alive: true
|
|
487
564
|
conditions:
|
|
488
565
|
match:
|
|
489
566
|
- expected: '{t1a.output.data.workflowId}'
|
|
@@ -530,13 +607,13 @@ const getWorkflowYAML = (app, version) => {
|
|
|
530
607
|
target: '{$self.input.data.parentWorkflowId}'
|
|
531
608
|
|
|
532
609
|
a1s:
|
|
533
|
-
type:
|
|
610
|
+
type: hook
|
|
534
611
|
title: Sleep for a duration
|
|
535
612
|
sleep: '{t1s.output.data.duration}'
|
|
536
613
|
emit: true
|
|
537
614
|
|
|
538
615
|
a2s:
|
|
539
|
-
type:
|
|
616
|
+
type: hook
|
|
540
617
|
title: Wait for cleanup signal
|
|
541
618
|
hook:
|
|
542
619
|
type: object
|
|
@@ -608,7 +685,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
608
685
|
|
|
609
686
|
a1wc:
|
|
610
687
|
title: Split signal data
|
|
611
|
-
type:
|
|
688
|
+
type: hook
|
|
612
689
|
cycle: true
|
|
613
690
|
output:
|
|
614
691
|
schema:
|
|
@@ -646,7 +723,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
646
723
|
- ['{t1wc.output.data.signals}', 1]
|
|
647
724
|
- ['{@array.slice}']
|
|
648
725
|
a2wc:
|
|
649
|
-
type:
|
|
726
|
+
type: hook
|
|
650
727
|
output:
|
|
651
728
|
schema:
|
|
652
729
|
type: object
|
|
@@ -773,7 +850,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
773
850
|
target: '{$self.input.data.parentWorkflowId}'
|
|
774
851
|
|
|
775
852
|
a1ww:
|
|
776
|
-
type:
|
|
853
|
+
type: hook
|
|
777
854
|
title: Wait for custom signal
|
|
778
855
|
emit: true
|
|
779
856
|
hook:
|
|
@@ -788,7 +865,7 @@ const getWorkflowYAML = (app, version) => {
|
|
|
788
865
|
signalId: '{t1ww.output.data.signalId}'
|
|
789
866
|
|
|
790
867
|
a2ww:
|
|
791
|
-
type:
|
|
868
|
+
type: hook
|
|
792
869
|
title: Wait for cleanup signal
|
|
793
870
|
hook:
|
|
794
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;
|
|
@@ -5,6 +5,7 @@ export declare class WorkflowService {
|
|
|
5
5
|
*/
|
|
6
6
|
static executeChild<T>(options: WorkflowOptions): Promise<T>;
|
|
7
7
|
static proxyActivities<ACT>(options?: ActivityConfig): ProxyType<ACT>;
|
|
8
|
+
static data(command: 'del' | 'get' | 'set' | 'incr' | 'mult', ...args: string[]): Promise<number | boolean | string>;
|
|
8
9
|
static sleep(duration: string): Promise<number>;
|
|
9
10
|
static waitForSignal(signals: string[], options?: Record<string, string>): Promise<Record<any, any>[]>;
|
|
10
11
|
static wrapActivity<T>(activityName: string, options?: ActivityConfig): T;
|
|
@@ -11,6 +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
|
+
const search_1 = require("./search");
|
|
14
15
|
/*
|
|
15
16
|
`proxyActivities` returns a wrapped instance of the
|
|
16
17
|
target activity, so that when the workflow calls a
|
|
@@ -54,6 +55,7 @@ class WorkflowService {
|
|
|
54
55
|
const COUNTER = store.get('counter');
|
|
55
56
|
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
56
57
|
const childJobId = `${workflowId}-$${options.workflowName}-${execIndex}`;
|
|
58
|
+
const parentWorkflowId = `${workflowId}-f`;
|
|
57
59
|
const client = new client_1.ClientService({
|
|
58
60
|
connection: await connection_1.ConnectionService.connect(worker_1.WorkerService.connection),
|
|
59
61
|
});
|
|
@@ -65,6 +67,7 @@ class WorkflowService {
|
|
|
65
67
|
handle = await client.workflow.start({
|
|
66
68
|
...options,
|
|
67
69
|
workflowId: childJobId,
|
|
70
|
+
parentWorkflowId,
|
|
68
71
|
workflowTrace,
|
|
69
72
|
workflowSpan,
|
|
70
73
|
});
|
|
@@ -86,6 +89,39 @@ class WorkflowService {
|
|
|
86
89
|
}
|
|
87
90
|
return proxy;
|
|
88
91
|
}
|
|
92
|
+
static async data(command, ...args) {
|
|
93
|
+
const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
|
|
94
|
+
if (!store) {
|
|
95
|
+
throw new Error('durable-store-not-found');
|
|
96
|
+
}
|
|
97
|
+
const workflowId = store.get('workflowId');
|
|
98
|
+
const workflowTopic = store.get('workflowTopic');
|
|
99
|
+
try {
|
|
100
|
+
const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic);
|
|
101
|
+
const search = new search_1.Search(workflowId, hotMeshClient);
|
|
102
|
+
if (command === 'get') {
|
|
103
|
+
return await search.get(args[0]);
|
|
104
|
+
}
|
|
105
|
+
else if (command === 'set') {
|
|
106
|
+
await search.set(args[0], args[1]);
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
else if (command === 'del') {
|
|
110
|
+
await search.del(args[0]);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
else if (command === 'incr') {
|
|
114
|
+
return await search.incr(args[0], Number(args[1]));
|
|
115
|
+
}
|
|
116
|
+
else if (command === 'mult') {
|
|
117
|
+
return await search.mult(args[0], Number(args[1]));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
console.error(e);
|
|
122
|
+
return '';
|
|
123
|
+
}
|
|
124
|
+
}
|
|
89
125
|
static async sleep(duration) {
|
|
90
126
|
const seconds = (0, ms_1.default)(duration) / 1000;
|
|
91
127
|
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,13 +5,23 @@ 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
|
+
};
|
|
8
16
|
type WorkflowOptions = {
|
|
9
17
|
taskQueue: string;
|
|
10
18
|
args: any[];
|
|
11
19
|
workflowId: string;
|
|
12
20
|
workflowName?: string;
|
|
21
|
+
parentWorkflowId?: string;
|
|
13
22
|
workflowTrace?: string;
|
|
14
23
|
workflowSpan?: string;
|
|
24
|
+
search?: WorkflowSearchOptions;
|
|
15
25
|
config?: WorkflowConfig;
|
|
16
26
|
};
|
|
17
27
|
type SignalOptions = {
|
|
@@ -72,4 +82,4 @@ type ActivityConfig = {
|
|
|
72
82
|
maximumInterval: string;
|
|
73
83
|
};
|
|
74
84
|
};
|
|
75
|
-
export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, NativeConnection, ProxyType, Registry, SignalOptions, WorkerConfig, WorkflowConfig, WorkerOptions, WorkflowDataType, WorkflowOptions, };
|
|
85
|
+
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';
|
package/modules/utils.ts
CHANGED
|
@@ -23,6 +23,17 @@ export function identifyRedisType(redisInstance: any): 'redis' | 'ioredis' | nul
|
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
//todo: the polyfill methods will all be deleted in the `beta` release.
|
|
27
|
+
export const polyfill = {
|
|
28
|
+
resolveActivityType(activityType: string): string {
|
|
29
|
+
if (activityType === 'activity') {
|
|
30
|
+
return 'hook';
|
|
31
|
+
}
|
|
32
|
+
return activityType;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
26
37
|
export function identifyRedisTypeFromClass(redisClass: any): 'redis' | 'ioredis' | null {
|
|
27
38
|
if (redisClass && redisClass.name === 'Redis' || redisClass.name === 'EventEmitter') {
|
|
28
39
|
return 'ioredis';
|