@hotmeshio/hotmesh 0.0.17 → 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 +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 +46 -0
- 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/workflow.d.ts +1 -0
- package/build/services/durable/workflow.js +34 -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 +10 -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 +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 +50 -2
- package/services/durable/factory.ts +11 -10
- package/services/durable/search.ts +54 -0
- package/services/durable/workflow.ts +32 -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 +8 -0
- package/types/hook.ts +1 -0
- package/types/index.ts +2 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { HotMeshService as HotMesh } from '../hotmesh'
|
|
2
|
+
import { RedisClient, RedisMulti } from '../../types/redis';
|
|
3
|
+
import { StoreService } from '../store';
|
|
4
|
+
import { KeyService, KeyType } from '../../modules/key';
|
|
5
|
+
|
|
6
|
+
export class Search {
|
|
7
|
+
jobId: string;
|
|
8
|
+
hotMeshClient: HotMesh;
|
|
9
|
+
store: StoreService<RedisClient, RedisMulti> | null;
|
|
10
|
+
|
|
11
|
+
safeKey(key:string): string {
|
|
12
|
+
//note: protect the execution namespace with a prefix,
|
|
13
|
+
//so its design never conflicts with the hotmesh keyspace
|
|
14
|
+
return `_${key}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
constructor(workflowId: string, hotMeshClient: HotMesh) {
|
|
18
|
+
const keyParams = {
|
|
19
|
+
appId: hotMeshClient.appId,
|
|
20
|
+
jobId: ''
|
|
21
|
+
}
|
|
22
|
+
const hotMeshPrefix = KeyService.mintKey(hotMeshClient.namespace, KeyType.JOB_STATE, keyParams);
|
|
23
|
+
this.jobId = `${hotMeshPrefix}${workflowId}`;
|
|
24
|
+
this.hotMeshClient = hotMeshClient;
|
|
25
|
+
this.store = hotMeshClient.engine.store as StoreService<RedisClient, RedisMulti>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async set(key: string, value: string): Promise<void> {
|
|
29
|
+
await this.store.exec('HSET', this.jobId, this.safeKey(key), value.toString());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async get(key: string): Promise<string> {
|
|
33
|
+
try {
|
|
34
|
+
return await this.store.exec('HGET',this.jobId, this.safeKey(key)) as string;
|
|
35
|
+
} catch (err) {
|
|
36
|
+
this.hotMeshClient.logger.error('durable-search-get-error', { err });
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async del(key: string): Promise<void> {
|
|
42
|
+
await this.store.exec('HDEL', this.jobId, this.safeKey(key));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async incr(key: string, val: number): Promise<number> {
|
|
46
|
+
return Number(await this.store.exec('HINCRBYFLOAT', this.jobId, this.safeKey(key), val.toString()) as string);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async mult(key: string, val: number): Promise<number> {
|
|
50
|
+
const log = Math.log(val);
|
|
51
|
+
const logTotal = Number(await this.store.exec('HINCRBYFLOAT', this.jobId, this.safeKey(key), log.toString()) as string);
|
|
52
|
+
return Math.exp(logTotal);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -4,10 +4,11 @@ import { asyncLocalStorage } from './asyncLocalStorage';
|
|
|
4
4
|
import { WorkerService } from './worker';
|
|
5
5
|
import { ClientService as Client } from './client';
|
|
6
6
|
import { ConnectionService as Connection } from './connection';
|
|
7
|
-
import { ActivityConfig, ProxyType, WorkflowOptions } from "../../types/durable";
|
|
7
|
+
import { ActivityConfig, ProxyType, WorkflowConfig, WorkflowOptions, WorkflowSearchOptions } from "../../types/durable";
|
|
8
8
|
import { JobOutput, JobState } from '../../types';
|
|
9
9
|
import { ACTIVITY_PUBLISHES_TOPIC, ACTIVITY_SUBSCRIBES_TOPIC, SLEEP_SUBSCRIBES_TOPIC, WFS_SUBSCRIBES_TOPIC } from './factory';
|
|
10
10
|
import { DurableIncompleteSignalError, DurableSleepError, DurableWaitForSignalError } from '../../modules/errors';
|
|
11
|
+
import { Search } from './search';
|
|
11
12
|
|
|
12
13
|
/*
|
|
13
14
|
`proxyActivities` returns a wrapped instance of the
|
|
@@ -97,6 +98,36 @@ export class WorkflowService {
|
|
|
97
98
|
return proxy;
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
static async data(command: 'del' | 'get' | 'set' | 'incr' | 'mult', ...args: string[]): Promise<number | boolean | string> {
|
|
102
|
+
const store = asyncLocalStorage.getStore();
|
|
103
|
+
if (!store) {
|
|
104
|
+
throw new Error('durable-store-not-found');
|
|
105
|
+
}
|
|
106
|
+
const workflowId = store.get('workflowId');
|
|
107
|
+
const workflowTopic = store.get('workflowTopic');
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const hotMeshClient = await WorkerService.getHotMesh(workflowTopic);
|
|
111
|
+
const search = new Search(workflowId, hotMeshClient);
|
|
112
|
+
if (command === 'get') {
|
|
113
|
+
return await search.get(args[0]) as string;
|
|
114
|
+
} else if (command === 'set') {
|
|
115
|
+
await search.set(args[0], args[1]);
|
|
116
|
+
return true;
|
|
117
|
+
} else if (command === 'del') {
|
|
118
|
+
await search.del(args[0]);
|
|
119
|
+
return true;
|
|
120
|
+
} else if (command === 'incr') {
|
|
121
|
+
return await search.incr(args[0], Number(args[1])) as number;
|
|
122
|
+
} else if (command === 'mult') {
|
|
123
|
+
return await search.mult(args[0], Number(args[1])) as number;
|
|
124
|
+
}
|
|
125
|
+
} catch (e) {
|
|
126
|
+
console.error(e);
|
|
127
|
+
return '';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
100
131
|
static async sleep(duration: string): Promise<number> {
|
|
101
132
|
const seconds = ms(duration) / 1000;
|
|
102
133
|
|
package/services/engine/index.ts
CHANGED
|
@@ -3,10 +3,13 @@ import {
|
|
|
3
3
|
formatISODate,
|
|
4
4
|
getSubscriptionTopic,
|
|
5
5
|
identifyRedisType,
|
|
6
|
+
polyfill,
|
|
6
7
|
restoreHierarchy } from '../../modules/utils';
|
|
7
8
|
import Activities from '../activities';
|
|
8
|
-
import { Activity } from '../activities/activity';
|
|
9
9
|
import { Await } from '../activities/await';
|
|
10
|
+
import { Cycle } from '../activities/cycle';
|
|
11
|
+
import { Hook } from '../activities/hook';
|
|
12
|
+
import { Signal } from '../activities/signal';
|
|
10
13
|
import { Worker } from '../activities/worker';
|
|
11
14
|
import { Trigger } from '../activities/trigger';
|
|
12
15
|
import { CompilerService } from '../compiler';
|
|
@@ -235,9 +238,10 @@ class EngineService {
|
|
|
235
238
|
}
|
|
236
239
|
|
|
237
240
|
// ************* METADATA/MODEL METHODS *************
|
|
238
|
-
async initActivity(topic: string, data: JobData = {}, context?: JobState): Promise<
|
|
241
|
+
async initActivity(topic: string, data: JobData = {}, context?: JobState): Promise<Await|Cycle|Hook|Signal|Trigger|Worker> {
|
|
239
242
|
const [activityId, schema] = await this.getSchema(topic);
|
|
240
|
-
|
|
243
|
+
polyfill
|
|
244
|
+
const ActivityHandler = Activities[polyfill.resolveActivityType(schema.type)];
|
|
241
245
|
if (ActivityHandler) {
|
|
242
246
|
const utc = formatISODate(new Date());
|
|
243
247
|
const metadata: ActivityMetadata = {
|
|
@@ -334,7 +338,7 @@ class EngineService {
|
|
|
334
338
|
data: streamData.data,
|
|
335
339
|
};
|
|
336
340
|
if (streamData.type === StreamDataType.TIMEHOOK || streamData.type === StreamDataType.WEBHOOK || streamData.type === StreamDataType.TRANSITION) {
|
|
337
|
-
const activityHandler = await this.initActivity(`.${streamData.metadata.aid}`, context.data, context as JobState) as
|
|
341
|
+
const activityHandler = await this.initActivity(`.${streamData.metadata.aid}`, context.data, context as JobState) as Hook;
|
|
338
342
|
if (streamData.type === StreamDataType.TIMEHOOK) {
|
|
339
343
|
await activityHandler.processTimeHookEvent(streamData.metadata.jid);
|
|
340
344
|
} else if (streamData.type === StreamDataType.TRANSITION) {
|
|
@@ -5,6 +5,7 @@ import { Cache } from '../cache';
|
|
|
5
5
|
import { StoreService } from '../index';
|
|
6
6
|
import { RedisClientType, RedisMultiType } from '../../../types/ioredisclient';
|
|
7
7
|
import { ReclaimedMessageType } from '../../../types/stream';
|
|
8
|
+
import { type } from 'os';
|
|
8
9
|
|
|
9
10
|
class IORedisStoreService extends StoreService<RedisClientType, RedisMultiType> {
|
|
10
11
|
redisClient: RedisClientType;
|
|
@@ -22,6 +23,18 @@ class IORedisStoreService extends StoreService<RedisClientType, RedisMultiType>
|
|
|
22
23
|
return this.redisClient.multi();
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
async exec(...args: any[]): Promise<string|string[]|string[][]> {
|
|
27
|
+
const response = await this.redisClient.call.apply(this.redisClient, args as any);
|
|
28
|
+
if (typeof response === 'string') {
|
|
29
|
+
return response as string;
|
|
30
|
+
} else if (Array.isArray(response)) {
|
|
31
|
+
if (Array.isArray(response[0])) {
|
|
32
|
+
return response as string[][];
|
|
33
|
+
}
|
|
34
|
+
return response as string[];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
hGetAllResult(result: any) {
|
|
26
39
|
//ioredis response signature is [null, {}] or [null, null]
|
|
27
40
|
return result[1];
|
|
@@ -50,6 +50,10 @@ class RedisStoreService extends StoreService<RedisClientType, RedisMultiType> {
|
|
|
50
50
|
return multi as unknown as RedisMultiType;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
async exec(...args: any[]): Promise<string|string[]|string[][]> {
|
|
54
|
+
return await this.redisClient.sendCommand(args);
|
|
55
|
+
}
|
|
56
|
+
|
|
53
57
|
async publish(keyType: KeyType.QUORUM, message: Record<string, any>, appId: string, engineId?: string): Promise<boolean> {
|
|
54
58
|
const topic = this.mintKey(keyType, { appId, engineId });
|
|
55
59
|
const status: number = await this.redisClient.publish(topic, JSON.stringify(message));
|
package/services/store/index.ts
CHANGED
|
@@ -70,6 +70,7 @@ abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
70
70
|
|
|
71
71
|
//todo: standardize signatures and move concrete methods to this class
|
|
72
72
|
abstract getMulti(): U;
|
|
73
|
+
abstract exec(...args: any[]): Promise<string|string[]|string[][]>;
|
|
73
74
|
abstract publish(
|
|
74
75
|
keyType: KeyType.QUORUM,
|
|
75
76
|
message: Record<string, any>,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
Context,
|
|
19
19
|
context,
|
|
20
20
|
SpanStatusCode } from '../../types/telemetry';
|
|
21
|
+
import { polyfill } from '../../modules/utils';
|
|
21
22
|
|
|
22
23
|
class TelemetryService {
|
|
23
24
|
span: Span;
|
|
@@ -253,7 +254,7 @@ class TelemetryService {
|
|
|
253
254
|
if (config.type === 'trigger') {
|
|
254
255
|
state[`${metadata.aid}/output/metadata/l1s`] = context['$self'].output.metadata.l1s;
|
|
255
256
|
state[`${metadata.aid}/output/metadata/l2s`] = context['$self'].output.metadata.l2s;
|
|
256
|
-
} else if (config.type === '
|
|
257
|
+
} else if (polyfill.resolveActivityType(config.type) === 'hook' && leg === 1) {
|
|
257
258
|
//activities run non-duplexed and only have a single leg
|
|
258
259
|
state[`${metadata.aid}/output/metadata/l1s`] = context['$self'].output.metadata.l1s;
|
|
259
260
|
state[`${metadata.aid}/output/metadata/l2s`] = context['$self'].output.metadata.l1s;
|
package/types/activity.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MetricTypes } from "./stats";
|
|
2
2
|
import { StreamRetryPolicy } from "./stream";
|
|
3
3
|
|
|
4
|
-
type ActivityExecutionType = 'trigger' | 'await' | 'worker' | 'activity' | 'emit' | 'iterate' | 'cycle' | 'signal';
|
|
4
|
+
type ActivityExecutionType = 'trigger' | 'await' | 'worker' | 'activity' | 'emit' | 'iterate' | 'cycle' | 'signal' | 'hook';
|
|
5
5
|
|
|
6
6
|
type Consumes = Record<string, string[]>;
|
|
7
7
|
|
|
@@ -65,6 +65,10 @@ interface CycleActivity extends BaseActivity {
|
|
|
65
65
|
ancestor: string; //ancestor activity id
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
interface HookActivity extends BaseActivity {
|
|
69
|
+
type: 'hook';
|
|
70
|
+
}
|
|
71
|
+
|
|
68
72
|
interface SignalActivity extends BaseActivity {
|
|
69
73
|
type: 'signal'; //signal activities call hook/hookAll
|
|
70
74
|
subtype: 'one' | 'all'; //trigger: hook(One) or hookAll
|
|
@@ -80,7 +84,7 @@ interface IterateActivity extends BaseActivity {
|
|
|
80
84
|
type: 'iterate';
|
|
81
85
|
}
|
|
82
86
|
|
|
83
|
-
type ActivityType = BaseActivity | TriggerActivity | AwaitActivity | WorkerActivity | IterateActivity;
|
|
87
|
+
type ActivityType = BaseActivity | TriggerActivity | AwaitActivity | WorkerActivity | IterateActivity | HookActivity;
|
|
84
88
|
|
|
85
89
|
type ActivityData = Record<string, any>;
|
|
86
90
|
type ActivityMetadata = {
|
|
@@ -124,6 +128,7 @@ export {
|
|
|
124
128
|
TriggerActivityStats,
|
|
125
129
|
AwaitActivity,
|
|
126
130
|
CycleActivity,
|
|
131
|
+
HookActivity,
|
|
127
132
|
SignalActivity,
|
|
128
133
|
BaseActivity,
|
|
129
134
|
IterateActivity,
|
package/types/durable.ts
CHANGED
|
@@ -7,6 +7,12 @@ type WorkflowConfig = {
|
|
|
7
7
|
initialInterval?: string; //default 1s
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
type WorkflowSearchOptions = {
|
|
11
|
+
index: string; //FT index name (myapp:myindex)
|
|
12
|
+
prefix: string[]; //FT prefixes (['myapp:myindex:prefix1', 'myapp:myindex:prefix2'])
|
|
13
|
+
schema: Record<string, {type: 'TEXT' | 'NUMERIC' | 'TAG', sortable: boolean}>;
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
type WorkflowOptions = {
|
|
11
17
|
taskQueue: string;
|
|
12
18
|
args: any[]; //input arguments to pass in
|
|
@@ -15,6 +21,7 @@ type WorkflowOptions = {
|
|
|
15
21
|
parentWorkflowId?: string; //system reserved; the id of the parent; if present the flow will not self-clean until the parent that spawned it self-cleans
|
|
16
22
|
workflowTrace?: string;
|
|
17
23
|
workflowSpan?: string;
|
|
24
|
+
search?: WorkflowSearchOptions
|
|
18
25
|
config?: WorkflowConfig;
|
|
19
26
|
}
|
|
20
27
|
|
|
@@ -101,6 +108,7 @@ export {
|
|
|
101
108
|
WorkerConfig,
|
|
102
109
|
WorkflowConfig,
|
|
103
110
|
WorkerOptions,
|
|
111
|
+
WorkflowSearchOptions,
|
|
104
112
|
WorkflowDataType,
|
|
105
113
|
WorkflowOptions,
|
|
106
114
|
};
|
package/types/hook.ts
CHANGED
package/types/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export {
|
|
|
10
10
|
AwaitActivity,
|
|
11
11
|
BaseActivity,
|
|
12
12
|
CycleActivity,
|
|
13
|
+
HookActivity,
|
|
13
14
|
WorkerActivity,
|
|
14
15
|
IterateActivity,
|
|
15
16
|
SignalActivity,
|
|
@@ -39,6 +40,7 @@ export {
|
|
|
39
40
|
WorkflowConfig,
|
|
40
41
|
WorkerConfig,
|
|
41
42
|
WorkerOptions,
|
|
43
|
+
WorkflowSearchOptions,
|
|
42
44
|
WorkflowDataType,
|
|
43
45
|
WorkflowOptions,
|
|
44
46
|
}from './durable'
|