@hotmeshio/hotmesh 0.0.55 → 0.0.57
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 +1 -1
- package/build/modules/enums.js +1 -10
- package/build/modules/key.d.ts +0 -38
- package/build/modules/key.js +4 -46
- package/build/modules/utils.d.ts +0 -8
- package/build/modules/utils.js +0 -14
- package/build/package.json +11 -4
- package/build/services/activities/activity.d.ts +0 -28
- package/build/services/activities/activity.js +1 -46
- package/build/services/activities/await.js +0 -4
- package/build/services/activities/cycle.d.ts +0 -7
- package/build/services/activities/cycle.js +1 -16
- package/build/services/activities/hook.d.ts +0 -6
- package/build/services/activities/hook.js +2 -12
- package/build/services/activities/interrupt.js +0 -8
- package/build/services/activities/signal.d.ts +0 -6
- package/build/services/activities/signal.js +0 -15
- package/build/services/activities/trigger.d.ts +0 -4
- package/build/services/activities/trigger.js +1 -7
- package/build/services/activities/worker.js +0 -4
- package/build/services/collator/index.d.ts +0 -70
- package/build/services/collator/index.js +1 -91
- package/build/services/compiler/deployer.js +6 -38
- package/build/services/compiler/index.d.ts +0 -15
- package/build/services/compiler/index.js +0 -20
- package/build/services/compiler/validator.d.ts +0 -3
- package/build/services/compiler/validator.js +0 -25
- package/build/services/connector/clients/ioredis.d.ts +2 -2
- package/build/services/connector/clients/ioredis.js +0 -2
- package/build/services/connector/clients/redis.d.ts +4 -4
- package/build/services/connector/clients/redis.js +1 -3
- package/build/services/connector/index.d.ts +1 -1
- package/build/services/connector/index.js +0 -2
- package/build/services/durable/client.d.ts +1 -26
- package/build/services/durable/client.js +0 -56
- package/build/services/durable/exporter.d.ts +0 -22
- package/build/services/durable/exporter.js +1 -30
- package/build/services/durable/handle.d.ts +0 -36
- package/build/services/durable/handle.js +0 -46
- package/build/services/durable/index.d.ts +0 -4
- package/build/services/durable/index.js +0 -4
- package/build/services/durable/schemas/factory.d.ts +0 -29
- package/build/services/durable/schemas/factory.js +0 -29
- package/build/services/durable/search.d.ts +1 -36
- package/build/services/durable/search.js +56 -56
- package/build/services/durable/worker.js +2 -22
- package/build/services/durable/workflow.d.ts +0 -114
- package/build/services/durable/workflow.js +1 -141
- package/build/services/engine/index.d.ts +1 -6
- package/build/services/engine/index.js +1 -43
- package/build/services/exporter/index.d.ts +0 -27
- package/build/services/exporter/index.js +0 -33
- package/build/services/hotmesh/index.d.ts +2 -2
- package/build/services/hotmesh/index.js +1 -9
- package/build/services/logger/index.js +0 -2
- package/build/services/mapper/index.d.ts +0 -14
- package/build/services/mapper/index.js +0 -14
- package/build/services/pipe/functions/date.d.ts +0 -7
- package/build/services/pipe/functions/date.js +0 -7
- package/build/services/pipe/functions/math.js +0 -2
- package/build/services/pipe/index.d.ts +0 -15
- package/build/services/pipe/index.js +2 -23
- package/build/services/quorum/index.d.ts +0 -7
- package/build/services/quorum/index.js +0 -21
- package/build/services/reporter/index.d.ts +0 -5
- package/build/services/reporter/index.js +0 -9
- package/build/services/router/index.d.ts +0 -9
- package/build/services/router/index.js +2 -38
- package/build/services/serializer/index.js +7 -26
- package/build/services/store/cache.d.ts +0 -18
- package/build/services/store/cache.js +0 -18
- package/build/services/store/clients/ioredis.d.ts +1 -1
- package/build/services/store/clients/ioredis.js +0 -1
- package/build/services/store/clients/redis.d.ts +1 -1
- package/build/services/store/index.d.ts +0 -55
- package/build/services/store/index.js +5 -81
- package/build/services/stream/clients/ioredis.d.ts +1 -1
- package/build/services/stream/clients/ioredis.js +1 -4
- package/build/services/stream/clients/redis.d.ts +1 -1
- package/build/services/sub/clients/ioredis.d.ts +1 -1
- package/build/services/sub/clients/redis.d.ts +1 -1
- package/build/services/task/index.d.ts +0 -9
- package/build/services/task/index.js +0 -31
- package/build/services/telemetry/index.d.ts +0 -7
- package/build/services/telemetry/index.js +1 -13
- package/build/services/worker/index.d.ts +0 -4
- package/build/services/worker/index.js +2 -6
- package/build/types/activity.d.ts +0 -81
- package/build/types/durable.d.ts +26 -177
- package/build/types/exporter.d.ts +0 -13
- package/build/types/hotmesh.d.ts +4 -16
- package/build/types/hotmesh.js +0 -3
- package/build/types/index.d.ts +4 -6
- package/build/types/index.js +4 -3
- package/build/types/job.d.ts +1 -86
- package/build/types/pipe.d.ts +0 -65
- package/build/types/quorum.d.ts +15 -10
- package/build/types/redis.d.ts +225 -7
- package/build/types/redis.js +9 -0
- package/build/types/stream.d.ts +0 -58
- package/build/types/stream.js +0 -4
- package/package.json +11 -4
- package/types/durable.ts +131 -4
- package/types/hotmesh.ts +3 -6
- package/types/index.ts +23 -10
- package/types/job.ts +1 -1
- package/types/quorum.ts +22 -0
- package/types/redis.ts +267 -18
- package/build/types/ioredisclient.d.ts +0 -5
- package/build/types/ioredisclient.js +0 -5
- package/build/types/redisclient.d.ts +0 -26
- package/build/types/redisclient.js +0 -2
- package/modules/enums.ts +0 -62
- package/modules/errors.ts +0 -280
- package/modules/key.ts +0 -101
- package/modules/storage.ts +0 -3
- package/modules/utils.ts +0 -242
- package/services/activities/activity.ts +0 -589
- package/services/activities/await.ts +0 -113
- package/services/activities/cycle.ts +0 -115
- package/services/activities/hook.ts +0 -197
- package/services/activities/index.ts +0 -19
- package/services/activities/interrupt.ts +0 -172
- package/services/activities/signal.ts +0 -148
- package/services/activities/trigger.ts +0 -295
- package/services/activities/worker.ts +0 -107
- package/services/collator/README.md +0 -102
- package/services/collator/index.ts +0 -291
- package/services/compiler/deployer.ts +0 -504
- package/services/compiler/index.ts +0 -98
- package/services/compiler/validator.ts +0 -158
- package/services/connector/clients/ioredis.ts +0 -57
- package/services/connector/clients/redis.ts +0 -72
- package/services/connector/index.ts +0 -42
- package/services/durable/client.ts +0 -266
- package/services/durable/connection.ts +0 -10
- package/services/durable/exporter.ts +0 -232
- package/services/durable/handle.ts +0 -160
- package/services/durable/index.ts +0 -27
- package/services/durable/schemas/factory.ts +0 -2358
- package/services/durable/search.ts +0 -196
- package/services/durable/worker.ts +0 -401
- package/services/durable/workflow.ts +0 -557
- package/services/engine/index.ts +0 -761
- package/services/exporter/index.ts +0 -146
- package/services/hotmesh/index.ts +0 -237
- package/services/logger/index.ts +0 -79
- package/services/mapper/index.ts +0 -89
- package/services/pipe/functions/array.ts +0 -78
- package/services/pipe/functions/bitwise.ts +0 -27
- package/services/pipe/functions/conditional.ts +0 -35
- package/services/pipe/functions/date.ts +0 -220
- package/services/pipe/functions/index.ts +0 -27
- package/services/pipe/functions/json.ts +0 -11
- package/services/pipe/functions/logical.ts +0 -11
- package/services/pipe/functions/math.ts +0 -217
- package/services/pipe/functions/number.ts +0 -75
- package/services/pipe/functions/object.ts +0 -98
- package/services/pipe/functions/string.ts +0 -86
- package/services/pipe/functions/symbol.ts +0 -39
- package/services/pipe/functions/unary.ts +0 -19
- package/services/pipe/index.ts +0 -216
- package/services/quorum/index.ts +0 -319
- package/services/reporter/index.ts +0 -387
- package/services/router/index.ts +0 -426
- package/services/serializer/README.md +0 -10
- package/services/serializer/index.ts +0 -285
- package/services/store/cache.ts +0 -172
- package/services/store/clients/ioredis.ts +0 -145
- package/services/store/clients/redis.ts +0 -191
- package/services/store/index.ts +0 -1091
- package/services/stream/clients/ioredis.ts +0 -157
- package/services/stream/clients/redis.ts +0 -158
- package/services/stream/index.ts +0 -58
- package/services/sub/clients/ioredis.ts +0 -83
- package/services/sub/clients/redis.ts +0 -74
- package/services/sub/index.ts +0 -25
- package/services/task/index.ts +0 -250
- package/services/telemetry/index.ts +0 -273
- package/services/worker/index.ts +0 -248
- package/types/ioredisclient.ts +0 -10
- package/types/redisclient.ts +0 -30
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { Pipe } from "../pipe";
|
|
2
|
-
import { StoreService } from '../store';
|
|
3
|
-
import { MappingStatements } from "../../types/map";
|
|
4
|
-
import { HotMeshManifest } from "../../types/hotmesh";
|
|
5
|
-
import { RedisClient, RedisMulti } from "../../types/redis";
|
|
6
|
-
|
|
7
|
-
class Validator {
|
|
8
|
-
manifest: HotMeshManifest | null = null;
|
|
9
|
-
activityIds: string[] = [];
|
|
10
|
-
mappingStatements: MappingStatements = {};
|
|
11
|
-
store: StoreService<RedisClient, RedisMulti> | null = null;
|
|
12
|
-
|
|
13
|
-
static SYS_VARS = ['$app', '$self', '$graph', '$job'];
|
|
14
|
-
static CONTEXT_VARS = ['{$input}', '{$output}', '{$item}', '{$key}', '{$index}'];
|
|
15
|
-
|
|
16
|
-
constructor(manifest: HotMeshManifest) {
|
|
17
|
-
this.manifest = manifest;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* validate the manifest file
|
|
22
|
-
*/
|
|
23
|
-
async validate(store: StoreService<RedisClient, RedisMulti>) {
|
|
24
|
-
this.store = store;
|
|
25
|
-
this.getMappingStatements();
|
|
26
|
-
this.validateActivityIds();
|
|
27
|
-
this.validateReferencedActivityIds();
|
|
28
|
-
this.validateMappingStatements();
|
|
29
|
-
this.validateTransitions();
|
|
30
|
-
this.validateTransitionConditions();
|
|
31
|
-
this.validateStats();
|
|
32
|
-
this.validateSchemas();
|
|
33
|
-
this.validateUniqueHandledTopics();
|
|
34
|
-
this.validateGraphPublishSubscribe();
|
|
35
|
-
this.validateHooks();
|
|
36
|
-
this.validateConditionalStatements();
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// 1.1) Validate the manifest file activity ids are unique (no duplicates)
|
|
40
|
-
validateActivityIds() {
|
|
41
|
-
const activityIdsSet: Set<string> = new Set();
|
|
42
|
-
this.manifest.app.graphs.forEach((graph) => {
|
|
43
|
-
const ids = Object.keys(graph.activities);
|
|
44
|
-
// Check for duplicates and add ids to the set
|
|
45
|
-
ids.forEach((id) => {
|
|
46
|
-
if (activityIdsSet.has(id)) {
|
|
47
|
-
throw new Error(`Duplicate activity id found: ${id}`);
|
|
48
|
-
} else {
|
|
49
|
-
activityIdsSet.add(id);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
this.activityIds = Array.from(activityIdsSet);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
isMappingStatement(value: string): boolean {
|
|
57
|
-
return typeof value === 'string' && value.startsWith('{') && value.endsWith('}');
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
extractMappingStatements(obj: any, result: MappingStatements, currentActivityId: string): void {
|
|
61
|
-
for (const key in obj) {
|
|
62
|
-
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
|
63
|
-
this.extractMappingStatements(obj[key], result, currentActivityId);
|
|
64
|
-
} else if (this.isMappingStatement(obj[key])) {
|
|
65
|
-
if (!result[currentActivityId]) {
|
|
66
|
-
result[currentActivityId] = [];
|
|
67
|
-
}
|
|
68
|
-
result[currentActivityId].push(obj[key]);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
getMappingStatements() {
|
|
74
|
-
const mappingStatements: MappingStatements = {};
|
|
75
|
-
this.manifest.app.graphs.forEach((graph) => {
|
|
76
|
-
const activities = graph.activities;
|
|
77
|
-
for (const activityId in activities) {
|
|
78
|
-
const activity = activities[activityId];
|
|
79
|
-
this.extractMappingStatements(activity, mappingStatements, activityId);
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
this.mappingStatements = mappingStatements;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// 1.2) Validate no activity ids are referenced that don't exist
|
|
86
|
-
validateReferencedActivityIds() {
|
|
87
|
-
// get list of all mapping statements and validate
|
|
88
|
-
const mappingStatements = this.mappingStatements;
|
|
89
|
-
const activityIds = this.activityIds;
|
|
90
|
-
for (const activity in mappingStatements) {
|
|
91
|
-
const statements = mappingStatements[activity];
|
|
92
|
-
statements.forEach((statement) => {
|
|
93
|
-
if (statement.startsWith('{') && statement.endsWith('}')) {
|
|
94
|
-
const statementParts = statement.slice(1, -1).split('.');
|
|
95
|
-
const referencedActivityId = statementParts[0];
|
|
96
|
-
if (!(Validator.SYS_VARS.includes(referencedActivityId) || activityIds.includes(referencedActivityId) || this.isFunction(statement) || this.isContextVariable(statement))) {
|
|
97
|
-
throw new Error(`Mapping statement references non-existent activity: ${statement}`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
isFunction(value: string): boolean {
|
|
105
|
-
return value.startsWith('{@') && Pipe.resolveFunction(value);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
isContextVariable(value: string): boolean {
|
|
109
|
-
return ['{$input}', '{$output}', '{$item}', '{$key}', '{$index}'].includes(value);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// 1.3) Validate the mapping/@pipe statements are valid
|
|
113
|
-
validateMappingStatements() {
|
|
114
|
-
// Implement the method content
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// 1.4) Validate the transitions are valid
|
|
118
|
-
validateTransitions() {
|
|
119
|
-
// Implement the method content
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// 1.5) Validate the transition conditions are valid
|
|
123
|
-
validateTransitionConditions() {
|
|
124
|
-
// Implement the method content
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// 1.6) Validate the stats
|
|
128
|
-
validateStats() {
|
|
129
|
-
// Implement the method content
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// 1.7) Validate the schemas
|
|
133
|
-
validateSchemas() {
|
|
134
|
-
// Implement the method content
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// 1.8) Validate the topics are unique and handled
|
|
138
|
-
validateUniqueHandledTopics() {
|
|
139
|
-
// Implement the method content
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// 1.9) Validate that every graph has publishes and subscribes
|
|
143
|
-
validateGraphPublishSubscribe() {
|
|
144
|
-
// Implement the method content
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// 1.10) Validate hooks, including mapping statements
|
|
148
|
-
validateHooks() {
|
|
149
|
-
// Implement the method content
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// 1.11) Validate conditional statements
|
|
153
|
-
validateConditionalStatements() {
|
|
154
|
-
// Implement the method content
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export { Validator };
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
RedisClientOptions,
|
|
3
|
-
RedisClassType,
|
|
4
|
-
RedisClientType } from '../../../types/ioredisclient';
|
|
5
|
-
|
|
6
|
-
class RedisConnection {
|
|
7
|
-
private connection: any | null = null;
|
|
8
|
-
private static instances: Map<string, RedisConnection> = new Map();
|
|
9
|
-
private id: string | null = null;
|
|
10
|
-
|
|
11
|
-
private static clientOptions: RedisClientOptions = {
|
|
12
|
-
host: 'localhost',
|
|
13
|
-
port: 6379,
|
|
14
|
-
//password: config.REDIS_PASSWORD,
|
|
15
|
-
//db: config.REDIS_DATABASE,
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
private async createConnection(Redis: RedisClassType, options: RedisClientOptions): Promise<any> {
|
|
19
|
-
return new Redis(options);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
public getClient(): RedisClientType {
|
|
23
|
-
if (!this.connection) {
|
|
24
|
-
throw new Error('Redis client is not connected');
|
|
25
|
-
}
|
|
26
|
-
return this.connection;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
public async disconnect(): Promise<void> {
|
|
30
|
-
if (this.connection) {
|
|
31
|
-
await this.connection.quit();
|
|
32
|
-
this.connection = null;
|
|
33
|
-
}
|
|
34
|
-
if (this.id) {
|
|
35
|
-
RedisConnection.instances.delete(this.id);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
public static async connect(id: string, Redis: RedisClassType, options?: RedisClientOptions): Promise<RedisConnection> {
|
|
40
|
-
if (this.instances.has(id)) {
|
|
41
|
-
return this.instances.get(id) as RedisConnection;
|
|
42
|
-
}
|
|
43
|
-
const instance = new RedisConnection();
|
|
44
|
-
const opts = options ? { ...options } : { ...this.clientOptions };
|
|
45
|
-
instance.connection = await instance.createConnection(Redis, opts);
|
|
46
|
-
instance.id = id;
|
|
47
|
-
this.instances.set(id, instance);
|
|
48
|
-
return instance;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
public static async disconnectAll(): Promise<void> {
|
|
52
|
-
await Promise.all(Array.from(this.instances.values()).map((instance) => instance.disconnect()));
|
|
53
|
-
this.instances.clear();
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export { RedisConnection };
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
RedisClientType,
|
|
3
|
-
RedisClientOptions,
|
|
4
|
-
RedisClassType } from '../../../types/redisclient';
|
|
5
|
-
|
|
6
|
-
class RedisConnection {
|
|
7
|
-
private connection: RedisClientType | null = null;
|
|
8
|
-
private static instances: Map<string, RedisConnection> = new Map();
|
|
9
|
-
private id: string | null = null;
|
|
10
|
-
|
|
11
|
-
private static clientOptions: RedisClientOptions = {
|
|
12
|
-
socket: {
|
|
13
|
-
host: 'localhost',
|
|
14
|
-
port: 6379,
|
|
15
|
-
tls: false,
|
|
16
|
-
},
|
|
17
|
-
//password: config.REDIS_PASSWORD,
|
|
18
|
-
//database: config.REDIS_DATABASE,
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
private async createConnection(Redis: RedisClassType, options: RedisClientOptions): Promise<RedisClientType> {
|
|
22
|
-
return new Promise((resolve, reject) => {
|
|
23
|
-
const client = Redis.createClient(options);
|
|
24
|
-
|
|
25
|
-
client.on('error', (error: any) => {
|
|
26
|
-
reject(error);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
client.on('ready', () => {
|
|
30
|
-
resolve(client);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
client.connect();
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
public getClient(): RedisClientType {
|
|
38
|
-
if (!this.connection) {
|
|
39
|
-
throw new Error('Redis client is not connected');
|
|
40
|
-
}
|
|
41
|
-
return this.connection;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
public async disconnect(): Promise<void> {
|
|
45
|
-
if (this.connection) {
|
|
46
|
-
await this.connection.quit();
|
|
47
|
-
this.connection = null;
|
|
48
|
-
}
|
|
49
|
-
if (this.id) {
|
|
50
|
-
RedisConnection.instances.delete(this.id);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
public static async connect(id: string, Redis: RedisClassType, options?: RedisClientOptions): Promise<RedisConnection> {
|
|
55
|
-
if (this.instances.has(id)) {
|
|
56
|
-
return this.instances.get(id)!;
|
|
57
|
-
}
|
|
58
|
-
const instance = new RedisConnection();
|
|
59
|
-
const opts = options ? { ...options } : { ...this.clientOptions };
|
|
60
|
-
instance.connection = await instance.createConnection(Redis, opts);
|
|
61
|
-
instance.id = id;
|
|
62
|
-
this.instances.set(id, instance);
|
|
63
|
-
return instance;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
public static async disconnectAll(): Promise<void> {
|
|
67
|
-
await Promise.all(Array.from(this.instances.values()).map((instance) => instance.disconnect()));
|
|
68
|
-
this.instances.clear();
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export { RedisConnection, RedisClientType };
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { guid, identifyRedisTypeFromClass } from '../../modules/utils';
|
|
2
|
-
import { RedisConnection as IORedisConnection } from '../connector/clients/ioredis';
|
|
3
|
-
import { RedisConnection } from '../connector/clients/redis';
|
|
4
|
-
import {
|
|
5
|
-
RedisClassType as IORedisClassType,
|
|
6
|
-
RedisClientOptions as IORedisClientOptions } from '../../types/ioredisclient';
|
|
7
|
-
import {
|
|
8
|
-
HotMeshEngine,
|
|
9
|
-
HotMeshWorker } from '../../types/hotmesh';
|
|
10
|
-
import { RedisClass, RedisOptions } from '../../types/redis';
|
|
11
|
-
import {
|
|
12
|
-
RedisClassType,
|
|
13
|
-
RedisClientOptions } from '../../types/redisclient';
|
|
14
|
-
|
|
15
|
-
export class ConnectorService {
|
|
16
|
-
//1) Initialize `store`, `stream`, and `subscription` Redis clients.
|
|
17
|
-
//2) Bind to the target if not already present
|
|
18
|
-
static async initRedisClients(Redis: RedisClass, options: RedisOptions, target: HotMeshEngine | HotMeshWorker): Promise<void> {
|
|
19
|
-
if (!target.store || !target.stream || !target.sub) {
|
|
20
|
-
const instances = [];
|
|
21
|
-
if (identifyRedisTypeFromClass(Redis) === 'redis') {
|
|
22
|
-
for (let i = 1; i <= 3; i++) {
|
|
23
|
-
instances.push(RedisConnection.connect(
|
|
24
|
-
guid(),
|
|
25
|
-
Redis as RedisClassType,
|
|
26
|
-
options as RedisClientOptions));
|
|
27
|
-
}
|
|
28
|
-
} else {
|
|
29
|
-
for (let i = 1; i <= 3; i++) {
|
|
30
|
-
instances.push(IORedisConnection.connect(
|
|
31
|
-
guid(),
|
|
32
|
-
Redis as IORedisClassType,
|
|
33
|
-
options as IORedisClientOptions));
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
const [store, stream, sub] = await Promise.all(instances);
|
|
37
|
-
target.store = target.store || store.getClient();
|
|
38
|
-
target.stream = target.stream || stream.getClient();
|
|
39
|
-
target.sub = target.sub || sub.getClient();
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
import ms from 'ms';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
APP_ID,
|
|
5
|
-
APP_VERSION,
|
|
6
|
-
getWorkflowYAML } from './schemas/factory';
|
|
7
|
-
import {
|
|
8
|
-
HMSH_LOGLEVEL,
|
|
9
|
-
HMSH_EXPIRE_JOB_SECONDS,
|
|
10
|
-
HMSH_QUORUM_DELAY_MS,
|
|
11
|
-
HMSH_DURABLE_EXP_BACKOFF,
|
|
12
|
-
HMSH_DURABLE_MAX_ATTEMPTS,
|
|
13
|
-
HMSH_DURABLE_MAX_INTERVAL } from '../../modules/enums';
|
|
14
|
-
import { sleepFor } from '../../modules/utils';
|
|
15
|
-
import { WorkflowHandleService } from './handle';
|
|
16
|
-
import { HotMeshService as HotMesh } from '../hotmesh';
|
|
17
|
-
import {
|
|
18
|
-
ClientConfig,
|
|
19
|
-
Connection,
|
|
20
|
-
HookOptions,
|
|
21
|
-
WorkflowOptions,
|
|
22
|
-
WorkflowSearchOptions} from '../../types/durable';
|
|
23
|
-
import { JobState } from '../../types/job';
|
|
24
|
-
import { KeyService, KeyType } from '../../modules/key';
|
|
25
|
-
import { Search } from './search';
|
|
26
|
-
import { StreamStatus } from '../../types';
|
|
27
|
-
|
|
28
|
-
export class ClientService {
|
|
29
|
-
|
|
30
|
-
connection: Connection;
|
|
31
|
-
options: WorkflowOptions;
|
|
32
|
-
static topics: string[] = [];
|
|
33
|
-
static instances = new Map<string, HotMesh | Promise<HotMesh>>();
|
|
34
|
-
|
|
35
|
-
constructor(config: ClientConfig) {
|
|
36
|
-
this.connection = config.connection;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
getHotMeshClient = async (workflowTopic: string, namespace?: string) => {
|
|
40
|
-
const targetNS = namespace ?? APP_ID;
|
|
41
|
-
if (ClientService.instances.has(targetNS)) {
|
|
42
|
-
const hotMeshClient = await ClientService.instances.get(targetNS);
|
|
43
|
-
await this.verifyWorkflowActive(hotMeshClient, targetNS);
|
|
44
|
-
if (!ClientService.topics.includes(workflowTopic)) {
|
|
45
|
-
ClientService.topics.push(workflowTopic);
|
|
46
|
-
await ClientService.createStream(hotMeshClient, workflowTopic, namespace);
|
|
47
|
-
}
|
|
48
|
-
return hotMeshClient;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
//create and cache an instance
|
|
52
|
-
const hotMeshClient = HotMesh.init({
|
|
53
|
-
appId: targetNS,
|
|
54
|
-
logLevel: HMSH_LOGLEVEL,
|
|
55
|
-
engine: {
|
|
56
|
-
redis: {
|
|
57
|
-
class: this.connection.class,
|
|
58
|
-
options: this.connection.options,
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
ClientService.instances.set(targetNS, hotMeshClient);
|
|
63
|
-
await ClientService.createStream(await hotMeshClient, workflowTopic, namespace);
|
|
64
|
-
await this.activateWorkflow(await hotMeshClient, targetNS);
|
|
65
|
-
return hotMeshClient;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Creates a stream (Redis `XGROUP.CREATE`) where events can be published (XADD).
|
|
70
|
-
* It is possible that the worker that will read from this stream channel
|
|
71
|
-
* has not yet been initialized, so this call ensures that the channel
|
|
72
|
-
* exists and is ready to serve as a container for events.
|
|
73
|
-
*/
|
|
74
|
-
static createStream = async(hotMeshClient: HotMesh, workflowTopic: string, namespace?: string) => {
|
|
75
|
-
const store = hotMeshClient.engine.store;
|
|
76
|
-
const params = { appId: namespace ?? APP_ID, topic: workflowTopic };
|
|
77
|
-
const streamKey = store.mintKey(KeyType.STREAMS, params);
|
|
78
|
-
try {
|
|
79
|
-
await store.xgroup('CREATE', streamKey, 'WORKER', '$', 'MKSTREAM');
|
|
80
|
-
} catch (err) {
|
|
81
|
-
//ignore if already exists
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* It is possible for a client to invoke a workflow without first
|
|
87
|
-
* creating the stream. This method will verify that the stream
|
|
88
|
-
* exists and if not, create it.
|
|
89
|
-
*/
|
|
90
|
-
static verifyStream = async(workflowTopic: string, namespace?: string) => {
|
|
91
|
-
const targetNS = namespace ?? APP_ID;
|
|
92
|
-
if (ClientService.instances.has(targetNS)) {
|
|
93
|
-
const hotMeshClient = await ClientService.instances.get(targetNS);
|
|
94
|
-
if (!ClientService.topics.includes(workflowTopic)) {
|
|
95
|
-
ClientService.topics.push(workflowTopic);
|
|
96
|
-
await ClientService.createStream(hotMeshClient, workflowTopic, namespace);
|
|
97
|
-
}
|
|
98
|
-
return hotMeshClient;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* For those deployments with a redis stack backend (with the FT module),
|
|
104
|
-
* this method will configure the search index for the workflow.
|
|
105
|
-
*/
|
|
106
|
-
configureSearchIndex = async (hotMeshClient: HotMesh, search?: WorkflowSearchOptions): Promise<void> => {
|
|
107
|
-
if (search?.schema) {
|
|
108
|
-
const store = hotMeshClient.engine.store;
|
|
109
|
-
const schema: string[] = [];
|
|
110
|
-
for (const [key, value] of Object.entries(search.schema)) {
|
|
111
|
-
//prefix with an underscore (avoids collisions with hotmesh reserved symbols)
|
|
112
|
-
schema.push(`_${key}`);
|
|
113
|
-
schema.push(value.type);
|
|
114
|
-
if (value.sortable) {
|
|
115
|
-
schema.push('SORTABLE');
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
try {
|
|
119
|
-
const keyParams = {
|
|
120
|
-
appId: hotMeshClient.appId,
|
|
121
|
-
jobId: ''
|
|
122
|
-
}
|
|
123
|
-
const hotMeshPrefix = KeyService.mintKey(hotMeshClient.namespace, KeyType.JOB_STATE, keyParams);
|
|
124
|
-
const prefixes = search.prefix.map((prefix) => `${hotMeshPrefix}${prefix}`);
|
|
125
|
-
await store.exec('FT.CREATE', `${search.index}`, 'ON', 'HASH', 'PREFIX', prefixes.length, ...prefixes, 'SCHEMA', ...schema);
|
|
126
|
-
} catch (error) {
|
|
127
|
-
hotMeshClient.engine.logger.info('durable-client-search-err', { ...error });
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
search = async (hotMeshClient: HotMesh, index: string, query: string[]): Promise<string[]> => {
|
|
133
|
-
const store = hotMeshClient.engine.store;
|
|
134
|
-
if (query[0]?.startsWith('FT.')) {
|
|
135
|
-
return await store.exec(...query) as string[];
|
|
136
|
-
}
|
|
137
|
-
return await store.exec('FT.SEARCH', index, ...query) as string[];
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
workflow = {
|
|
141
|
-
start: async (options: WorkflowOptions): Promise<WorkflowHandleService> => {
|
|
142
|
-
const taskQueueName = options.entity ?? options.taskQueue;
|
|
143
|
-
const workflowName = options.entity ?? options.workflowName;
|
|
144
|
-
const trc = options.workflowTrace;
|
|
145
|
-
const spn = options.workflowSpan;
|
|
146
|
-
//NOTE: HotMesh 'workflowTopic' is a created by concatenating
|
|
147
|
-
// the taskQueue and workflowName used by the Durable module
|
|
148
|
-
const workflowTopic = `${taskQueueName}-${workflowName}`;
|
|
149
|
-
const hotMeshClient = await this.getHotMeshClient(workflowTopic, options.namespace);
|
|
150
|
-
this.configureSearchIndex(hotMeshClient, options.search);
|
|
151
|
-
const payload = {
|
|
152
|
-
arguments: [...options.args],
|
|
153
|
-
originJobId: options.originJobId,
|
|
154
|
-
expire: options.expire ?? HMSH_EXPIRE_JOB_SECONDS,
|
|
155
|
-
parentWorkflowId: options.parentWorkflowId,
|
|
156
|
-
workflowId: options.workflowId || HotMesh.guid(),
|
|
157
|
-
workflowTopic: workflowTopic,
|
|
158
|
-
backoffCoefficient: options.config?.backoffCoefficient || HMSH_DURABLE_EXP_BACKOFF,
|
|
159
|
-
maximumAttempts: options.config?.maximumAttempts || HMSH_DURABLE_MAX_ATTEMPTS,
|
|
160
|
-
maximumInterval: ms(options.config?.maximumInterval || HMSH_DURABLE_MAX_INTERVAL) / 1000,
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const context = { metadata: { trc, spn }, data: {}};
|
|
164
|
-
const jobId = await hotMeshClient.pub(
|
|
165
|
-
`${options.namespace ?? APP_ID}.execute`,
|
|
166
|
-
payload,
|
|
167
|
-
context as JobState,
|
|
168
|
-
{ search: options?.search?.data, marker: options?.marker},
|
|
169
|
-
);
|
|
170
|
-
return new WorkflowHandleService(hotMeshClient, workflowTopic, jobId);
|
|
171
|
-
},
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* send a message to a running workflow that is paused and awaiting the signal
|
|
175
|
-
*/
|
|
176
|
-
signal: async (signalId: string, data: Record<any, any>, namespace?: string): Promise<string> => {
|
|
177
|
-
const topic = `${namespace ?? APP_ID}.wfs.signal`;
|
|
178
|
-
return await (await this.getHotMeshClient(topic, namespace)).hook(topic, { id: signalId, data });
|
|
179
|
-
},
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* send a message to spawn an parallel in-process thread of execution
|
|
183
|
-
* with the same job state as the main thread but bound to a different
|
|
184
|
-
* handler function. All job state will be journaled to the same hash
|
|
185
|
-
* as is used by the main thread.
|
|
186
|
-
*/
|
|
187
|
-
hook: async (options: HookOptions): Promise<string> => {
|
|
188
|
-
const workflowTopic = `${options.taskQueue}-${options.workflowName}`;
|
|
189
|
-
const payload = {
|
|
190
|
-
arguments: [...options.args],
|
|
191
|
-
id: options.workflowId,
|
|
192
|
-
workflowTopic,
|
|
193
|
-
backoffCoefficient: options.config?.backoffCoefficient || HMSH_DURABLE_EXP_BACKOFF,
|
|
194
|
-
maximumAttempts: options.config?.maximumAttempts || HMSH_DURABLE_MAX_ATTEMPTS,
|
|
195
|
-
maximumInterval: ms(options.config?.maximumInterval || HMSH_DURABLE_MAX_INTERVAL) / 1000,
|
|
196
|
-
}
|
|
197
|
-
//seed search data if presentthe hook before entering
|
|
198
|
-
const hotMeshClient = await this.getHotMeshClient(workflowTopic, options.namespace);
|
|
199
|
-
const msgId = await hotMeshClient.hook(`${hotMeshClient.appId}.flow.signal`, payload, StreamStatus.PENDING, 202);
|
|
200
|
-
if (options.search?.data) {
|
|
201
|
-
const searchSessionId = `-search-${HotMesh.guid()}-0`;
|
|
202
|
-
const search = new Search(options.workflowId, hotMeshClient, searchSessionId);
|
|
203
|
-
const entries = Object.entries(options.search.data).flat();
|
|
204
|
-
await search.set(...entries);
|
|
205
|
-
}
|
|
206
|
-
return msgId;
|
|
207
|
-
},
|
|
208
|
-
|
|
209
|
-
getHandle: async (taskQueue: string, workflowName: string, workflowId: string, namespace?: string): Promise<WorkflowHandleService> => {
|
|
210
|
-
const workflowTopic = `${taskQueue}-${workflowName}`;
|
|
211
|
-
const hotMeshClient = await this.getHotMeshClient(workflowTopic, namespace);
|
|
212
|
-
return new WorkflowHandleService(hotMeshClient, workflowTopic, workflowId);
|
|
213
|
-
},
|
|
214
|
-
|
|
215
|
-
search: async (taskQueue: string, workflowName: string, namespace: null | string, index: string, ...query: string[]): Promise<string[]> => {
|
|
216
|
-
const workflowTopic = `${taskQueue}-${workflowName}`;
|
|
217
|
-
const hotMeshClient = await this.getHotMeshClient(workflowTopic, namespace);
|
|
218
|
-
try {
|
|
219
|
-
return await this.search(hotMeshClient, index, query);
|
|
220
|
-
} catch (error) {
|
|
221
|
-
hotMeshClient.engine.logger.error('durable-client-search-err', { ...error });
|
|
222
|
-
throw error;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
async verifyWorkflowActive(hotMesh: HotMesh, appId = APP_ID, count = 0): Promise<boolean> {
|
|
228
|
-
const app = await hotMesh.engine.store.getApp(appId);
|
|
229
|
-
const appVersion = app?.version as unknown as number;
|
|
230
|
-
if(isNaN(appVersion)) {
|
|
231
|
-
if (count > 10) {
|
|
232
|
-
throw new Error('Workflow failed to activate');
|
|
233
|
-
}
|
|
234
|
-
await sleepFor(HMSH_QUORUM_DELAY_MS * 2);
|
|
235
|
-
return await this.verifyWorkflowActive(hotMesh, appId, count + 1);
|
|
236
|
-
}
|
|
237
|
-
return true;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
async activateWorkflow(hotMesh: HotMesh, appId = APP_ID, version = APP_VERSION): Promise<void> {
|
|
241
|
-
const app = await hotMesh.engine.store.getApp(appId);
|
|
242
|
-
const appVersion = app?.version as unknown as number;
|
|
243
|
-
if(isNaN(appVersion)) {
|
|
244
|
-
try {
|
|
245
|
-
await hotMesh.deploy(getWorkflowYAML(appId, version));
|
|
246
|
-
await hotMesh.activate(version);
|
|
247
|
-
} catch (error) {
|
|
248
|
-
hotMesh.engine.logger.error('durable-client-deploy-activate-err', { ...error });
|
|
249
|
-
throw error;
|
|
250
|
-
}
|
|
251
|
-
} else if(app && !app.active) {
|
|
252
|
-
try {
|
|
253
|
-
await hotMesh.activate(version);
|
|
254
|
-
} catch (error) {
|
|
255
|
-
hotMesh.engine.logger.error('durable-client-activate-err', { error});
|
|
256
|
-
throw error;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
static async shutdown(): Promise<void> {
|
|
262
|
-
for (const [_, hotMeshInstance] of ClientService.instances) {
|
|
263
|
-
(await hotMeshInstance).stop();
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { Connection, ConnectionConfig } from "../../types/durable";
|
|
2
|
-
|
|
3
|
-
export class ConnectionService {
|
|
4
|
-
static async connect(config: ConnectionConfig): Promise<Connection> {
|
|
5
|
-
return {
|
|
6
|
-
class: config.class,
|
|
7
|
-
options: { ...config.options },
|
|
8
|
-
} as Connection;
|
|
9
|
-
}
|
|
10
|
-
}
|