@hotmeshio/hotmesh 0.0.22 → 0.0.24
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 +66 -67
- package/build/index.d.ts +2 -1
- package/build/index.js +3 -1
- package/build/package.json +2 -1
- package/build/services/durable/connection.js +0 -39
- package/build/services/durable/factory.d.ts +6 -4
- package/build/services/durable/factory.js +12 -10
- package/build/services/durable/handle.d.ts +3 -0
- package/build/services/durable/handle.js +15 -5
- package/build/services/durable/index.d.ts +2 -45
- package/build/services/durable/index.js +2 -45
- package/build/services/durable/meshos.d.ts +108 -0
- package/build/services/durable/meshos.js +289 -0
- package/build/services/durable/search.d.ts +9 -0
- package/build/services/durable/search.js +34 -2
- package/build/services/durable/worker.d.ts +2 -10
- package/build/services/durable/worker.js +10 -39
- package/build/services/durable/workflow.d.ts +12 -1
- package/build/services/durable/workflow.js +32 -16
- package/build/services/engine/index.d.ts +2 -0
- package/build/services/engine/index.js +3 -0
- package/build/services/hotmesh/index.d.ts +3 -1
- package/build/services/hotmesh/index.js +5 -2
- package/build/services/signaler/stream.js +1 -2
- package/build/services/store/clients/ioredis.js +2 -2
- package/build/services/store/clients/redis.js +1 -1
- package/build/services/store/index.d.ts +5 -0
- package/build/services/store/index.js +14 -0
- package/build/types/durable.d.ts +34 -4
- package/build/types/index.d.ts +1 -1
- package/index.ts +2 -1
- package/package.json +2 -1
- package/services/durable/connection.ts +0 -40
- package/services/durable/factory.ts +12 -10
- package/services/durable/handle.ts +18 -5
- package/services/durable/index.ts +2 -46
- package/services/durable/meshos.ts +344 -0
- package/services/durable/search.ts +35 -2
- package/services/durable/worker.ts +11 -42
- package/services/durable/workflow.ts +34 -17
- package/services/engine/index.ts +4 -1
- package/services/hotmesh/index.ts +6 -2
- package/services/signaler/stream.ts +1 -2
- package/services/store/clients/ioredis.ts +2 -3
- package/services/store/clients/redis.ts +1 -1
- package/services/store/index.ts +15 -0
- package/types/durable.ts +40 -4
- package/types/index.ts +6 -1
- package/build/services/durable/native.d.ts +0 -4
- package/build/services/durable/native.js +0 -46
- package/services/durable/native.ts +0 -45
|
@@ -7,6 +7,7 @@ import { HotMeshConfig, HotMeshManifest } from '../../types/hotmesh';
|
|
|
7
7
|
import { JobMessageCallback } from '../../types/quorum';
|
|
8
8
|
import { JobStatsInput, GetStatsOptions, IdsResponse, StatsResponse } from '../../types/stats';
|
|
9
9
|
import { StreamCode, StreamData, StreamDataResponse, StreamStatus } from '../../types/stream';
|
|
10
|
+
import { StringAnyType } from '../../types/serializer';
|
|
10
11
|
declare class HotMeshService {
|
|
11
12
|
namespace: string;
|
|
12
13
|
appId: string;
|
|
@@ -21,7 +22,7 @@ declare class HotMeshService {
|
|
|
21
22
|
static guid(): string;
|
|
22
23
|
initEngine(config: HotMeshConfig, logger: ILogger): Promise<void>;
|
|
23
24
|
initQuorum(config: HotMeshConfig, engine: EngineService, logger: ILogger): Promise<void>;
|
|
24
|
-
|
|
25
|
+
doWork(config: HotMeshConfig, logger: ILogger): Promise<void>;
|
|
25
26
|
pub(topic: string, data?: JobData, context?: JobState): Promise<string>;
|
|
26
27
|
sub(topic: string, callback: JobMessageCallback): Promise<void>;
|
|
27
28
|
unsub(topic: string): Promise<void>;
|
|
@@ -35,6 +36,7 @@ declare class HotMeshService {
|
|
|
35
36
|
getStats(topic: string, query: JobStatsInput): Promise<StatsResponse>;
|
|
36
37
|
getStatus(jobId: string): Promise<JobStatus>;
|
|
37
38
|
getState(topic: string, jobId: string): Promise<JobOutput>;
|
|
39
|
+
getQueryState(jobId: string, fields: string[]): Promise<StringAnyType>;
|
|
38
40
|
getIds(topic: string, query: JobStatsInput, queryFacets?: any[]): Promise<IdsResponse>;
|
|
39
41
|
resolveQuery(topic: string, query: JobStatsInput): Promise<GetStatsOptions>;
|
|
40
42
|
scrub(jobId: string): Promise<void>;
|
|
@@ -45,7 +45,7 @@ class HotMeshService {
|
|
|
45
45
|
instance.logger = new logger_1.LoggerService(config.appId, instance.guid, config.name || '', config.logLevel);
|
|
46
46
|
await instance.initEngine(config, instance.logger);
|
|
47
47
|
await instance.initQuorum(config, instance.engine, instance.logger);
|
|
48
|
-
await instance.
|
|
48
|
+
await instance.doWork(config, instance.logger);
|
|
49
49
|
return instance;
|
|
50
50
|
}
|
|
51
51
|
static guid() {
|
|
@@ -62,7 +62,7 @@ class HotMeshService {
|
|
|
62
62
|
this.quorum = await quorum_1.QuorumService.init(this.namespace, this.appId, this.guid, config, engine, logger);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
-
async
|
|
65
|
+
async doWork(config, logger) {
|
|
66
66
|
this.workers = await worker_1.WorkerService.init(this.namespace, this.appId, this.guid, config, logger);
|
|
67
67
|
}
|
|
68
68
|
// ************* PUB/SUB METHODS *************
|
|
@@ -108,6 +108,9 @@ class HotMeshService {
|
|
|
108
108
|
async getState(topic, jobId) {
|
|
109
109
|
return this.engine?.getState(topic, jobId);
|
|
110
110
|
}
|
|
111
|
+
async getQueryState(jobId, fields) {
|
|
112
|
+
return await this.engine?.getQueryState(jobId, fields);
|
|
113
|
+
}
|
|
111
114
|
async getIds(topic, query, queryFacets = []) {
|
|
112
115
|
return await this.engine?.getIds(topic, query, queryFacets);
|
|
113
116
|
}
|
|
@@ -37,7 +37,7 @@ class StreamSignaler {
|
|
|
37
37
|
await this.store.xgroup('CREATE', stream, group, '$', 'MKSTREAM');
|
|
38
38
|
}
|
|
39
39
|
catch (err) {
|
|
40
|
-
this.logger.
|
|
40
|
+
this.logger.debug('consumer-group-exists', { stream, group });
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
async publishMessage(topic, streamData, multi) {
|
|
@@ -121,7 +121,6 @@ class StreamSignaler {
|
|
|
121
121
|
output = await callback(input);
|
|
122
122
|
}
|
|
123
123
|
catch (error) {
|
|
124
|
-
console.error(error);
|
|
125
124
|
this.logger.error(`stream-call-function-error`, { error });
|
|
126
125
|
output = this.structureUnhandledError(input, error);
|
|
127
126
|
}
|
|
@@ -45,7 +45,7 @@ class IORedisStoreService extends index_1.StoreService {
|
|
|
45
45
|
return (await this.redisClient.xgroup(command, key, groupName, id, mkStream)) === 'OK';
|
|
46
46
|
}
|
|
47
47
|
catch (err) {
|
|
48
|
-
this.logger.
|
|
48
|
+
this.logger.debug(`Consumer group not created with MKSTREAM for key: ${key} and group: ${groupName}`);
|
|
49
49
|
throw err;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -54,7 +54,7 @@ class IORedisStoreService extends index_1.StoreService {
|
|
|
54
54
|
return (await this.redisClient.xgroup(command, key, groupName, id)) === 'OK';
|
|
55
55
|
}
|
|
56
56
|
catch (err) {
|
|
57
|
-
this.logger.
|
|
57
|
+
this.logger.debug(`Consumer group not created for key: ${key} and group: ${groupName}`);
|
|
58
58
|
throw err;
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -68,7 +68,7 @@ class RedisStoreService extends index_1.StoreService {
|
|
|
68
68
|
}
|
|
69
69
|
catch (error) {
|
|
70
70
|
const streamType = mkStream === 'MKSTREAM' ? 'with MKSTREAM' : 'without MKSTREAM';
|
|
71
|
-
this.logger.
|
|
71
|
+
this.logger.debug(`x-group-error ${streamType} for key: ${key} and group: ${groupName}`, { error });
|
|
72
72
|
throw error;
|
|
73
73
|
}
|
|
74
74
|
}
|
|
@@ -61,6 +61,11 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
61
61
|
setStatus(collationKeyStatus: number, jobId: string, appId: string, multi?: U): Promise<any>;
|
|
62
62
|
getStatus(jobId: string, appId: string): Promise<number>;
|
|
63
63
|
setState({ ...state }: StringAnyType, status: number | null, jobId: string, symbolNames: string[], dIds: StringStringType, multi?: U): Promise<string>;
|
|
64
|
+
/**
|
|
65
|
+
* returns custom search fields and values. The fields param
|
|
66
|
+
* should not prefix items with an underscore.
|
|
67
|
+
*/
|
|
68
|
+
getQueryState(jobId: string, fields: string[]): Promise<StringAnyType>;
|
|
64
69
|
getState(jobId: string, consumes: Consumes, dIds: StringStringType): Promise<[StringAnyType, number] | undefined>;
|
|
65
70
|
collate(jobId: string, activityId: string, amount: number, dIds: StringStringType, multi?: U): Promise<number>;
|
|
66
71
|
setStateNX(jobId: string, appId: string): Promise<boolean>;
|
|
@@ -394,6 +394,20 @@ class StoreService {
|
|
|
394
394
|
await (multi || this.redisClient)[this.commands.hset](hashKey, hashData);
|
|
395
395
|
return jobId;
|
|
396
396
|
}
|
|
397
|
+
/**
|
|
398
|
+
* returns custom search fields and values. The fields param
|
|
399
|
+
* should not prefix items with an underscore.
|
|
400
|
+
*/
|
|
401
|
+
async getQueryState(jobId, fields) {
|
|
402
|
+
const key = this.mintKey(key_1.KeyType.JOB_STATE, { appId: this.appId, jobId });
|
|
403
|
+
const _fields = fields.map(field => `_${field}`);
|
|
404
|
+
const jobDataArray = await this.redisClient[this.commands.hmget](key, _fields);
|
|
405
|
+
const jobData = {};
|
|
406
|
+
fields.forEach((field, index) => {
|
|
407
|
+
jobData[field] = jobDataArray[index];
|
|
408
|
+
});
|
|
409
|
+
return jobData;
|
|
410
|
+
}
|
|
397
411
|
async getState(jobId, consumes, dIds) {
|
|
398
412
|
//get abbreviated field list (the symbols for the paths)
|
|
399
413
|
const key = this.mintKey(key_1.KeyType.JOB_STATE, { appId: this.appId, jobId });
|
package/build/types/durable.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ type WorkflowSearchOptions = {
|
|
|
10
10
|
prefix?: string[];
|
|
11
11
|
schema?: Record<string, {
|
|
12
12
|
type: 'TEXT' | 'NUMERIC' | 'TAG';
|
|
13
|
-
sortable
|
|
13
|
+
sortable?: boolean;
|
|
14
14
|
}>;
|
|
15
15
|
data?: Record<string, string>;
|
|
16
16
|
};
|
|
@@ -52,12 +52,28 @@ type WorkflowDataType = {
|
|
|
52
52
|
workflowId: string;
|
|
53
53
|
workflowTopic: string;
|
|
54
54
|
};
|
|
55
|
+
type MeshOSClassConfig = {
|
|
56
|
+
namespace: string;
|
|
57
|
+
taskQueue: string;
|
|
58
|
+
redisOptions: RedisOptions;
|
|
59
|
+
redisClass: RedisClass;
|
|
60
|
+
};
|
|
61
|
+
type MeshOSConfig = {
|
|
62
|
+
taskQueue?: string;
|
|
63
|
+
index?: {
|
|
64
|
+
index: string;
|
|
65
|
+
prefix: string[];
|
|
66
|
+
schema: Record<string, {
|
|
67
|
+
type: 'TEXT' | 'NUMERIC' | 'TAG';
|
|
68
|
+
sortable: boolean;
|
|
69
|
+
}>;
|
|
70
|
+
};
|
|
71
|
+
};
|
|
55
72
|
type ConnectionConfig = {
|
|
56
73
|
class: RedisClass;
|
|
57
74
|
options: RedisOptions;
|
|
58
75
|
};
|
|
59
76
|
type Connection = ConnectionConfig;
|
|
60
|
-
type NativeConnection = ConnectionConfig;
|
|
61
77
|
type ClientConfig = {
|
|
62
78
|
connection: Connection;
|
|
63
79
|
};
|
|
@@ -68,10 +84,24 @@ type WorkerConfig = {
|
|
|
68
84
|
connection: Connection;
|
|
69
85
|
namespace?: string;
|
|
70
86
|
taskQueue: string;
|
|
71
|
-
workflow: Function
|
|
87
|
+
workflow: Function | Record<string | symbol, Function>;
|
|
72
88
|
options?: WorkerOptions;
|
|
73
89
|
search?: WorkflowSearchOptions;
|
|
74
90
|
};
|
|
91
|
+
type FindOptions = {
|
|
92
|
+
workflowName?: string;
|
|
93
|
+
taskQueue?: string;
|
|
94
|
+
namespace?: string;
|
|
95
|
+
index?: string;
|
|
96
|
+
};
|
|
97
|
+
type MeshOSOptions = {
|
|
98
|
+
name: string;
|
|
99
|
+
options: WorkerOptions;
|
|
100
|
+
};
|
|
101
|
+
type MeshOSActivityOptions = {
|
|
102
|
+
name: string;
|
|
103
|
+
options: ActivityConfig;
|
|
104
|
+
};
|
|
75
105
|
type WorkerOptions = {
|
|
76
106
|
logLevel?: string;
|
|
77
107
|
maxSystemRetries?: number;
|
|
@@ -95,4 +125,4 @@ type ActivityConfig = {
|
|
|
95
125
|
maximumInterval: string;
|
|
96
126
|
};
|
|
97
127
|
};
|
|
98
|
-
export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection,
|
|
128
|
+
export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, ProxyType, Registry, SignalOptions, FindOptions, HookOptions, MeshOSActivityOptions, MeshOSClassConfig, MeshOSConfig, MeshOSOptions, WorkerConfig, WorkflowConfig, WorkerOptions, WorkflowSearchOptions, WorkflowDataType, WorkflowOptions, };
|
package/build/types/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ 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,
|
|
6
|
+
export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, ProxyType, Registry, SignalOptions, FindOptions, HookOptions, MeshOSActivityOptions, MeshOSClassConfig, MeshOSConfig, MeshOSOptions, 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/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Durable } from './services/durable';
|
|
2
|
+
import { MeshOSService as MeshOS } from './services/durable/meshos';
|
|
2
3
|
import { HotMeshService as HotMesh } from './services/hotmesh';
|
|
3
4
|
import { HotMeshConfig } from './types/hotmesh';
|
|
4
5
|
|
|
5
|
-
export { Durable, HotMesh, HotMeshConfig };
|
|
6
|
+
export { Durable, HotMesh, HotMeshConfig, MeshOS };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotmeshio/hotmesh",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.24",
|
|
4
4
|
"description": "Unbreakable Workflows",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"test:sub:redis": "NODE_ENV=test jest ./tests/functional/sub/clients/redis.test.ts --detectOpenHandles --forceExit --verbose",
|
|
44
44
|
"test:sub:ioredis": "NODE_ENV=test jest ./tests/functional/sub/clients/ioredis.test.ts --detectOpenHandles --forceExit --verbose",
|
|
45
45
|
"test:durable": "NODE_ENV=test jest ./tests/durable/*/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
46
|
+
"test:durable:meshos": "NODE_ENV=test jest ./tests/durable/meshos/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
46
47
|
"test:durable:hello": "NODE_ENV=test jest ./tests/durable/helloworld/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
47
48
|
"test:durable:goodbye": "NODE_ENV=test jest ./tests/durable/goodbye/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
48
49
|
"test:durable:hook": "NODE_ENV=test jest ./tests/durable/hook/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
@@ -1,45 +1,5 @@
|
|
|
1
1
|
import { Connection, ConnectionConfig } from "../../types/durable";
|
|
2
2
|
|
|
3
|
-
/*
|
|
4
|
-
Here is an example of how the methods in this file are used:
|
|
5
|
-
|
|
6
|
-
./client.ts
|
|
7
|
-
|
|
8
|
-
import { Durable } from '@hotmeshio/hotmesh';
|
|
9
|
-
import Redis from 'ioredis';
|
|
10
|
-
import { nanoid } from 'nanoid';
|
|
11
|
-
|
|
12
|
-
async function run() {
|
|
13
|
-
const connection = await Durable.Connection.connect({
|
|
14
|
-
class: Redis,
|
|
15
|
-
options: {
|
|
16
|
-
host: 'localhost',
|
|
17
|
-
port: 6379,
|
|
18
|
-
},
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
const client = new Durable.Client({
|
|
22
|
-
connection,
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const handle = await client.workflow.start(example, {
|
|
26
|
-
taskQueue: 'hello-world',
|
|
27
|
-
args: ['HotMesh'],
|
|
28
|
-
workflowName: 'example',
|
|
29
|
-
workflowId: nanoid(),
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
console.log(`Started workflow ${handle.workflowId}`);
|
|
33
|
-
console.log(await handle.result());
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
run().catch((err) => {
|
|
37
|
-
console.error(err);
|
|
38
|
-
process.exit(1);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
*/
|
|
42
|
-
|
|
43
3
|
export class ConnectionService {
|
|
44
4
|
static async connect(config: ConnectionConfig): Promise<Connection> {
|
|
45
5
|
return {
|
|
@@ -2,11 +2,13 @@
|
|
|
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
|
+
* TODO: Max Interval, Min Interval, Initial Interval
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* ERROR CODES:
|
|
8
|
+
* 594: waitforsignal
|
|
9
|
+
* 595: sleep
|
|
10
|
+
* 596, 597, 598: fatal
|
|
11
|
+
* 599: retry
|
|
10
12
|
*/
|
|
11
13
|
const getWorkflowYAML = (app: string, version: string) => {
|
|
12
14
|
return `app:
|
|
@@ -262,11 +264,11 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
262
264
|
- ['{@string.concat}']
|
|
263
265
|
cycleWorkflowId:
|
|
264
266
|
'@pipe':
|
|
265
|
-
- ['{$job.metadata.jid}', '-$wfc', '{sig.output.metadata.dad}', '-', '{sigw1.output.data.index}']
|
|
267
|
+
- ['-', '{$job.metadata.jid}', '-$wfc', '{sig.output.metadata.dad}', '-', '{sigw1.output.data.index}']
|
|
266
268
|
- ['{@string.concat}']
|
|
267
269
|
baseWorkflowId:
|
|
268
270
|
'@pipe':
|
|
269
|
-
- ['{$job.metadata.jid}', '-$wfs', '{sig.output.metadata.dad}', '-']
|
|
271
|
+
- ['-', '{$job.metadata.jid}', '-$wfs', '{sig.output.metadata.dad}', '-']
|
|
270
272
|
- ['{@string.concat}']
|
|
271
273
|
output:
|
|
272
274
|
schema:
|
|
@@ -310,7 +312,7 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
310
312
|
- ['{@string.concat}']
|
|
311
313
|
workflowId:
|
|
312
314
|
'@pipe':
|
|
313
|
-
- ['{$job.metadata.jid}', '-$sleep', '{sig.output.metadata.dad}', '-', '{sigw1.output.data.index}']
|
|
315
|
+
- ['-', '{$job.metadata.jid}', '-$sleep', '{sig.output.metadata.dad}', '-', '{sigw1.output.data.index}']
|
|
314
316
|
- ['{@string.concat}']
|
|
315
317
|
output:
|
|
316
318
|
schema:
|
|
@@ -380,11 +382,11 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
380
382
|
- ['{@string.concat}']
|
|
381
383
|
cycleWorkflowId:
|
|
382
384
|
'@pipe':
|
|
383
|
-
- ['{$job.metadata.jid}', '-$wfc-', '{w1.output.data.index}']
|
|
385
|
+
- ['-', '{$job.metadata.jid}', '-$wfc-', '{w1.output.data.index}']
|
|
384
386
|
- ['{@string.concat}']
|
|
385
387
|
baseWorkflowId:
|
|
386
388
|
'@pipe':
|
|
387
|
-
- ['{$job.metadata.jid}', '-$wfs-']
|
|
389
|
+
- ['-', '{$job.metadata.jid}', '-$wfs-']
|
|
388
390
|
- ['{@string.concat}']
|
|
389
391
|
output:
|
|
390
392
|
schema:
|
|
@@ -428,7 +430,7 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
428
430
|
- ['{@string.concat}']
|
|
429
431
|
workflowId:
|
|
430
432
|
'@pipe':
|
|
431
|
-
- ['{$job.metadata.jid}', '-$sleep-', '{w1.output.data.index}']
|
|
433
|
+
- ['-', '{$job.metadata.jid}', '-$sleep-', '{w1.output.data.index}']
|
|
432
434
|
- ['{@string.concat}']
|
|
433
435
|
output:
|
|
434
436
|
schema:
|
|
@@ -16,6 +16,22 @@ export class WorkflowHandleService {
|
|
|
16
16
|
await this.hotMesh.hook(`${this.hotMesh.appId}.wfs.signal`, { id: signalId, data });
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
async state(metadata = false): Promise<Record<string, any>> {
|
|
20
|
+
const state = await this.hotMesh.getState(`${this.hotMesh.appId}.execute`, this.workflowId);
|
|
21
|
+
if (!state.data && state.metadata.err) {
|
|
22
|
+
throw new Error(JSON.parse(state.metadata.err));
|
|
23
|
+
}
|
|
24
|
+
return metadata ? state : state.data;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async queryState(fields: string[]): Promise<Record<string, any>> {
|
|
28
|
+
return await this.hotMesh.getQueryState(this.workflowId, fields);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async status(): Promise<number> {
|
|
32
|
+
return await this.hotMesh.getStatus(this.workflowId);
|
|
33
|
+
}
|
|
34
|
+
|
|
19
35
|
async result(loadState?: boolean): Promise<any> {
|
|
20
36
|
if (loadState) {
|
|
21
37
|
const state = await this.hotMesh.getState(`${this.hotMesh.appId}.execute`, this.workflowId);
|
|
@@ -23,11 +39,8 @@ export class WorkflowHandleService {
|
|
|
23
39
|
throw new Error(JSON.parse(state.metadata.err));
|
|
24
40
|
}
|
|
25
41
|
if (state?.data?.done) {
|
|
26
|
-
//child flows are never
|
|
27
|
-
//that
|
|
28
|
-
//the 'done' flag on the child flow's payload (not the 'js' metadata field
|
|
29
|
-
//which is typically used); the loadState parameter ensures this
|
|
30
|
-
//check happens early
|
|
42
|
+
//child flows are never 'done'; they use a hook
|
|
43
|
+
//that only closes upon parent flow completion.
|
|
31
44
|
return state.data.response;
|
|
32
45
|
}
|
|
33
46
|
}
|
|
@@ -1,58 +1,14 @@
|
|
|
1
1
|
import { ClientService } from './client';
|
|
2
2
|
import { ConnectionService } from './connection';
|
|
3
|
-
import {
|
|
3
|
+
import { MeshOSService } from './meshos';
|
|
4
4
|
import { WorkerService } from './worker';
|
|
5
5
|
import { WorkflowService } from './workflow';
|
|
6
6
|
import { ContextType } from '../../types/durable';
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* As a durable integration platform, HotMesh
|
|
10
|
-
* can model and emulate other durable systems
|
|
11
|
-
* (like Temporal). As you review the code in
|
|
12
|
-
* this file, note the following:
|
|
13
|
-
*
|
|
14
|
-
* 1) There is no central governing server.
|
|
15
|
-
* HotMesh is a client-side SDK that connects to Redis
|
|
16
|
-
* using CQRS principles to implicitly drive
|
|
17
|
-
* orchestrations using a headless quorum. Stream
|
|
18
|
-
* semantics guarantee that all events are
|
|
19
|
-
* processed by the quorum of connected clients.
|
|
20
|
-
*
|
|
21
|
-
* 2) Every developer-defined `workflow` function
|
|
22
|
-
* is assigned a HotMesh workflow (which runs in
|
|
23
|
-
* the background) to support it.
|
|
24
|
-
*
|
|
25
|
-
* If the HotMesh workflow is not yet defined,
|
|
26
|
-
* it will be deployed and activated on-the-fly.
|
|
27
|
-
* (The generated DAG will have one Trigger Activity
|
|
28
|
-
* and one Worker Activity.) The Worker Activity
|
|
29
|
-
* is configured to catch execution errors and
|
|
30
|
-
* return them, using a 'pending' status to
|
|
31
|
-
* indicate that the workflow is still running.
|
|
32
|
-
* It is possible for workflow activities to throw
|
|
33
|
-
* errors that will force the entire workflow to
|
|
34
|
-
* fail. This is not the case here. The workflow will
|
|
35
|
-
* continue to run until it is completed or cancelled.
|
|
36
|
-
*
|
|
37
|
-
* 2) Every developer-defined `activity` function
|
|
38
|
-
* is assigned a HotMesh workflow (which runs in
|
|
39
|
-
* the background) to support it.
|
|
40
|
-
*
|
|
41
|
-
* (The generated DAG will have one Trigger Activity and one
|
|
42
|
-
* Worker Activity.) The JOB ID for Worker Activity executions
|
|
43
|
-
* is derived from the containing JOB ID,
|
|
44
|
-
* allowing Activity state to be 'replayed' when the workflow
|
|
45
|
-
* is run again. The Activity Function Runner (activity proxy)
|
|
46
|
-
* is configured similar to how the workflow worker is and will
|
|
47
|
-
* catch execution errors and return them to the caller, using a
|
|
48
|
-
* 'pending' status to indicate that the activity is still running.
|
|
49
|
-
* This allows the activity to be retried until it succeeds.
|
|
50
|
-
*/
|
|
51
|
-
|
|
52
8
|
export const Durable = {
|
|
53
9
|
Client: ClientService,
|
|
54
10
|
Connection: ConnectionService,
|
|
55
|
-
|
|
11
|
+
MeshOS: MeshOSService,
|
|
56
12
|
Worker: WorkerService,
|
|
57
13
|
workflow: WorkflowService,
|
|
58
14
|
};
|