@hotmeshio/hotmesh 0.0.42 → 0.0.44
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/enums.d.ts +2 -0
- package/build/modules/enums.js +4 -1
- package/build/modules/utils.js +1 -1
- package/build/package.json +1 -1
- package/build/services/activities/trigger.js +7 -1
- package/build/services/durable/client.d.ts +2 -1
- package/build/services/durable/client.js +17 -3
- package/build/services/durable/exporter.d.ts +105 -0
- package/build/services/durable/exporter.js +374 -0
- package/build/services/durable/factory.js +6 -63
- package/build/services/durable/handle.d.ts +4 -0
- package/build/services/durable/handle.js +5 -0
- package/build/services/durable/meshos.js +3 -0
- package/build/services/durable/workflow.js +24 -21
- package/build/services/engine/index.d.ts +6 -1
- package/build/services/engine/index.js +9 -2
- package/build/services/exporter/index.d.ts +46 -0
- package/build/services/exporter/index.js +126 -0
- package/build/services/hotmesh/index.d.ts +4 -1
- package/build/services/hotmesh/index.js +6 -0
- package/build/services/quorum/index.d.ts +5 -2
- package/build/services/quorum/index.js +33 -15
- package/build/services/router/index.d.ts +3 -0
- package/build/services/router/index.js +3 -0
- package/build/services/store/clients/redis.js +1 -0
- package/build/services/store/index.d.ts +7 -3
- package/build/services/store/index.js +62 -12
- package/build/services/task/index.js +5 -1
- package/build/services/worker/index.js +5 -4
- package/build/types/activity.d.ts +6 -1
- package/build/types/exporter.d.ts +51 -0
- package/build/types/exporter.js +8 -0
- package/build/types/hotmesh.d.ts +1 -1
- package/build/types/index.d.ts +1 -0
- package/build/types/quorum.d.ts +1 -0
- package/build/types/task.d.ts +1 -1
- package/modules/enums.ts +4 -0
- package/modules/utils.ts +1 -1
- package/package.json +1 -1
- package/services/activities/trigger.ts +14 -0
- package/services/durable/client.ts +19 -4
- package/services/durable/exporter.ts +408 -0
- package/services/durable/factory.ts +6 -63
- package/services/durable/handle.ts +12 -0
- package/services/durable/meshos.ts +3 -0
- package/services/durable/workflow.ts +24 -22
- package/services/engine/index.ts +20 -5
- package/services/exporter/index.ts +147 -0
- package/services/hotmesh/index.ts +8 -1
- package/services/quorum/index.ts +37 -13
- package/services/router/index.ts +3 -0
- package/services/store/clients/redis.ts +1 -0
- package/services/store/index.ts +66 -14
- package/services/task/index.ts +4 -1
- package/services/worker/index.ts +6 -5
- package/types/activity.ts +6 -1
- package/types/exporter.ts +61 -0
- package/types/hotmesh.ts +1 -1
- package/types/index.ts +13 -1
- package/types/quorum.ts +1 -0
- package/types/task.ts +1 -1
|
@@ -10,6 +10,7 @@ class Router {
|
|
|
10
10
|
constructor(config, stream, store, logger) {
|
|
11
11
|
this.throttle = 0;
|
|
12
12
|
this.errorCount = 0;
|
|
13
|
+
this.counts = {};
|
|
13
14
|
this.currentTimerId = null;
|
|
14
15
|
this.appId = config.appId;
|
|
15
16
|
this.guid = config.guid;
|
|
@@ -146,6 +147,8 @@ class Router {
|
|
|
146
147
|
else {
|
|
147
148
|
output.metadata.guid = (0, utils_1.guid)();
|
|
148
149
|
}
|
|
150
|
+
const code = output.code || 200;
|
|
151
|
+
this.counts[code] = (this.counts[code] || 0) + 1;
|
|
149
152
|
output.type = stream_1.StreamDataType.RESPONSE;
|
|
150
153
|
return await this.publishMessage(null, output);
|
|
151
154
|
}
|
|
@@ -46,10 +46,12 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
46
46
|
* check for and process work items in the
|
|
47
47
|
* time and signal task queues.
|
|
48
48
|
*/
|
|
49
|
-
reserveScoutRole(scoutType: 'time' | 'signal', delay?: number): Promise<boolean>;
|
|
49
|
+
reserveScoutRole(scoutType: 'time' | 'signal' | 'activate', delay?: number): Promise<boolean>;
|
|
50
|
+
releaseScoutRole(scoutType: 'time' | 'signal' | 'activate'): Promise<boolean>;
|
|
50
51
|
getSettings(bCreate?: boolean): Promise<HotMeshSettings>;
|
|
51
52
|
setSettings(manifest: HotMeshSettings): Promise<any>;
|
|
52
53
|
reserveSymbolRange(target: string, size: number, type: 'JOB' | 'ACTIVITY'): Promise<[number, number, Symbols]>;
|
|
54
|
+
getAllSymbols(): Promise<Symbols>;
|
|
53
55
|
getSymbols(activityId: string): Promise<Symbols>;
|
|
54
56
|
addSymbols(activityId: string, symbols: Symbols): Promise<boolean>;
|
|
55
57
|
seedSymbols(target: string, type: 'JOB' | 'ACTIVITY', startIndex: number): StringStringType;
|
|
@@ -67,7 +69,7 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
67
69
|
* when `originJobId` is interrupted/expired, the items in the
|
|
68
70
|
* list (added via RPUSH) will be interrupted/expired (removed via LPOPed).
|
|
69
71
|
*/
|
|
70
|
-
registerJobDependency(originJobId: string, topic: string, jobId: string, gId: string, multi?: U): Promise<any>;
|
|
72
|
+
registerJobDependency(depType: WorkListTaskType, originJobId: string, topic: string, jobId: string, gId: string, multi?: U): Promise<any>;
|
|
71
73
|
/**
|
|
72
74
|
* Ensures a `hook signal` is delisted when its parent activity/job
|
|
73
75
|
* is interrupted/expired.
|
|
@@ -86,6 +88,7 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
86
88
|
*/
|
|
87
89
|
getQueryState(jobId: string, fields: string[]): Promise<StringAnyType>;
|
|
88
90
|
getState(jobId: string, consumes: Consumes, dIds: StringStringType): Promise<[StringAnyType, number] | undefined>;
|
|
91
|
+
getRaw(jobId: string): Promise<StringStringType>;
|
|
89
92
|
/**
|
|
90
93
|
* collate is a generic method for incrementing a value in a hash
|
|
91
94
|
* in order to track their progress during processing.
|
|
@@ -123,6 +126,7 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
123
126
|
* is a standard `expire` or an `interrupt`
|
|
124
127
|
*/
|
|
125
128
|
registerDependenciesForCleanup(jobId: string, deletionTime: number, options: JobCompletionOptions): Promise<void>;
|
|
129
|
+
getDependencies(jobId: string): Promise<string[]>;
|
|
126
130
|
/**
|
|
127
131
|
* registers a hook activity to be awakened (uses ZSET to
|
|
128
132
|
* store the 'sleep group' and LIST to store the events
|
|
@@ -139,7 +143,7 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
139
143
|
* generic LIST (lists typically contain target job ids)
|
|
140
144
|
* @param {string} listKey - for example `::INTERRUPT::job123` or `job123`
|
|
141
145
|
*/
|
|
142
|
-
resolveTaskKeyContext(listKey: string): [
|
|
146
|
+
resolveTaskKeyContext(listKey: string): [WorkListTaskType, string];
|
|
143
147
|
/**
|
|
144
148
|
* Interrupts a job and sets sets a job error (410), if 'throw'!=false.
|
|
145
149
|
* This method is called by the engine and not by an activity and is
|
|
@@ -33,6 +33,7 @@ const errors_1 = require("../../modules/errors");
|
|
|
33
33
|
class StoreService {
|
|
34
34
|
constructor(redisClient) {
|
|
35
35
|
this.commands = {
|
|
36
|
+
set: 'set',
|
|
36
37
|
setnx: 'setnx',
|
|
37
38
|
del: 'del',
|
|
38
39
|
expire: 'expire',
|
|
@@ -106,12 +107,13 @@ class StoreService {
|
|
|
106
107
|
*/
|
|
107
108
|
async reserveScoutRole(scoutType, delay = enums_1.HMSH_SCOUT_INTERVAL_SECONDS) {
|
|
108
109
|
const key = this.mintKey(key_1.KeyType.WORK_ITEMS, { appId: this.appId, scoutType });
|
|
109
|
-
const success = await this.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
110
|
+
const success = await this.exec('SET', key, `${scoutType}:${(0, utils_1.formatISODate)(new Date())}`, 'NX', 'EX', `${delay - 1}`);
|
|
111
|
+
return this.isSuccessful(success);
|
|
112
|
+
}
|
|
113
|
+
async releaseScoutRole(scoutType) {
|
|
114
|
+
const key = this.mintKey(key_1.KeyType.WORK_ITEMS, { appId: this.appId, scoutType });
|
|
115
|
+
const success = await this.exec('DEL', key);
|
|
116
|
+
return this.isSuccessful(success);
|
|
115
117
|
}
|
|
116
118
|
async getSettings(bCreate = false) {
|
|
117
119
|
let settings = this.cache?.getSettings();
|
|
@@ -162,6 +164,35 @@ class StoreService {
|
|
|
162
164
|
return [actualLowerLimit, upperLimit, symbols];
|
|
163
165
|
}
|
|
164
166
|
}
|
|
167
|
+
async getAllSymbols() {
|
|
168
|
+
//get hash with all reserved symbol ranges
|
|
169
|
+
const rangeKey = this.mintKey(key_1.KeyType.SYMKEYS, { appId: this.appId });
|
|
170
|
+
const ranges = await this.redisClient[this.commands.hgetall](rangeKey);
|
|
171
|
+
const rangeKeys = Object.keys(ranges).sort();
|
|
172
|
+
delete rangeKeys[':cursor'];
|
|
173
|
+
const multi = this.getMulti();
|
|
174
|
+
for (const rangeKey of rangeKeys) {
|
|
175
|
+
const symbolKey = this.mintKey(key_1.KeyType.SYMKEYS, { activityId: rangeKey, appId: this.appId });
|
|
176
|
+
multi[this.commands.hgetall](symbolKey);
|
|
177
|
+
}
|
|
178
|
+
const results = await multi.exec();
|
|
179
|
+
const symbolSets = {};
|
|
180
|
+
results.forEach((result, index) => {
|
|
181
|
+
if (result) {
|
|
182
|
+
let vals;
|
|
183
|
+
if (Array.isArray(result) && result.length === 2) {
|
|
184
|
+
vals = result[1];
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
vals = result;
|
|
188
|
+
}
|
|
189
|
+
for (const [key, value] of Object.entries(vals)) {
|
|
190
|
+
symbolSets[value] = key.startsWith(rangeKeys[index]) ? key : `${rangeKeys[index]}/${key}`;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
return symbolSets;
|
|
195
|
+
}
|
|
165
196
|
async getSymbols(activityId) {
|
|
166
197
|
let symbols = this.cache.getSymbols(this.appId, activityId);
|
|
167
198
|
if (symbols) {
|
|
@@ -312,15 +343,15 @@ class StoreService {
|
|
|
312
343
|
* when `originJobId` is interrupted/expired, the items in the
|
|
313
344
|
* list (added via RPUSH) will be interrupted/expired (removed via LPOPed).
|
|
314
345
|
*/
|
|
315
|
-
async registerJobDependency(originJobId, topic, jobId, gId, multi) {
|
|
346
|
+
async registerJobDependency(depType, originJobId, topic, jobId, gId, multi) {
|
|
316
347
|
const privateMulti = multi || this.getMulti();
|
|
317
348
|
const dependencyParams = {
|
|
318
349
|
appId: this.appId,
|
|
319
350
|
jobId: originJobId,
|
|
320
351
|
};
|
|
321
352
|
const depKey = this.mintKey(key_1.KeyType.JOB_DEPENDENTS, dependencyParams);
|
|
322
|
-
//
|
|
323
|
-
const expireTask =
|
|
353
|
+
//items listed as job dependencies have different relationships
|
|
354
|
+
const expireTask = `${depType}::${topic}::${gId}::${jobId}`;
|
|
324
355
|
privateMulti[this.commands.rpush](depKey, expireTask);
|
|
325
356
|
if (!multi) {
|
|
326
357
|
return await privateMulti.exec();
|
|
@@ -485,6 +516,14 @@ class StoreService {
|
|
|
485
516
|
throw new errors_1.GetStateError(jobId);
|
|
486
517
|
}
|
|
487
518
|
}
|
|
519
|
+
async getRaw(jobId) {
|
|
520
|
+
const jobKey = this.mintKey(key_1.KeyType.JOB_STATE, { appId: this.appId, jobId });
|
|
521
|
+
const job = await this.redisClient[this.commands.hgetall](jobKey);
|
|
522
|
+
if (!job) {
|
|
523
|
+
throw new errors_1.GetStateError(jobId);
|
|
524
|
+
}
|
|
525
|
+
return job;
|
|
526
|
+
}
|
|
488
527
|
/**
|
|
489
528
|
* collate is a generic method for incrementing a value in a hash
|
|
490
529
|
* in order to track their progress during processing.
|
|
@@ -718,6 +757,11 @@ class StoreService {
|
|
|
718
757
|
const zsetKey = this.mintKey(key_1.KeyType.TIME_RANGE, { appId: this.appId });
|
|
719
758
|
await this.zAdd(zsetKey, deletionTime.toString(), depKeyContext);
|
|
720
759
|
}
|
|
760
|
+
async getDependencies(jobId) {
|
|
761
|
+
const depParams = { appId: this.appId, jobId };
|
|
762
|
+
const depKey = this.mintKey(key_1.KeyType.JOB_DEPENDENTS, depParams);
|
|
763
|
+
return this.redisClient[this.commands.lrange](depKey, 0, -1);
|
|
764
|
+
}
|
|
721
765
|
/**
|
|
722
766
|
* registers a hook activity to be awakened (uses ZSET to
|
|
723
767
|
* store the 'sleep group' and LIST to store the events
|
|
@@ -740,12 +784,18 @@ class StoreService {
|
|
|
740
784
|
let [pType, pKey] = this.resolveTaskKeyContext(listKey);
|
|
741
785
|
const timeEvent = await this.redisClient[this.commands.lpop](pKey);
|
|
742
786
|
if (timeEvent) {
|
|
743
|
-
//there are
|
|
744
|
-
//1) sleep (awaken), 2) expire, 3) interrupt, 4) delist
|
|
745
|
-
|
|
787
|
+
//there are task types
|
|
788
|
+
//1) sleep (awaken), 2) expire (OR expire-child), 3) interrupt, 4) delist, 5) child (just an index helper; no work to do)
|
|
789
|
+
let [type, activityId, gId, ...jobId] = timeEvent.split('::');
|
|
746
790
|
if (type === 'delist') {
|
|
747
791
|
pType = 'delist';
|
|
748
792
|
}
|
|
793
|
+
else if (type === 'child') {
|
|
794
|
+
pType = 'child';
|
|
795
|
+
}
|
|
796
|
+
else if (type === 'expire-child') {
|
|
797
|
+
type = 'expire'; //use the same logic as 'expire'
|
|
798
|
+
}
|
|
749
799
|
return [listKey, jobId.join('::'), gId, activityId, pType];
|
|
750
800
|
}
|
|
751
801
|
await this.redisClient[this.commands.zrem](zsetKey, listKey);
|
|
@@ -75,7 +75,11 @@ class TaskService {
|
|
|
75
75
|
const workListTask = await this.store.getNextTask(listKey);
|
|
76
76
|
if (Array.isArray(workListTask)) {
|
|
77
77
|
const [listKey, target, gId, activityId, type] = workListTask;
|
|
78
|
-
if (type === '
|
|
78
|
+
if (type === 'child') {
|
|
79
|
+
//continue; this child is listed here for convenience, but
|
|
80
|
+
// will be expired by an origin ancestor and is listed there
|
|
81
|
+
}
|
|
82
|
+
else if (type === 'delist') {
|
|
79
83
|
//delist the signalKey (target)
|
|
80
84
|
const key = this.store.mintKey(hotmesh_1.KeyType.SIGNALS, { appId: this.store.appId });
|
|
81
85
|
await this.store.redisClient[this.store.commands.hdel](key, target);
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.WorkerService = void 0;
|
|
4
4
|
const key_1 = require("../../modules/key");
|
|
5
|
+
const utils_1 = require("../../modules/utils");
|
|
6
|
+
const connector_1 = require("../connector");
|
|
5
7
|
const router_1 = require("../router");
|
|
6
|
-
const redis_1 = require("../store/clients/redis");
|
|
7
8
|
const ioredis_1 = require("../store/clients/ioredis");
|
|
8
|
-
const
|
|
9
|
+
const redis_1 = require("../store/clients/redis");
|
|
9
10
|
const ioredis_2 = require("../stream/clients/ioredis");
|
|
11
|
+
const redis_2 = require("../stream/clients/redis");
|
|
10
12
|
const ioredis_3 = require("../sub/clients/ioredis");
|
|
11
13
|
const redis_3 = require("../sub/clients/redis");
|
|
12
14
|
const stream_1 = require("../../types/stream");
|
|
13
|
-
const utils_1 = require("../../modules/utils");
|
|
14
|
-
const connector_1 = require("../connector");
|
|
15
15
|
class WorkerService {
|
|
16
16
|
constructor() {
|
|
17
17
|
this.reporting = false;
|
|
@@ -114,6 +114,7 @@ class WorkerService {
|
|
|
114
114
|
app_id: this.appId,
|
|
115
115
|
worker_topic: this.topic,
|
|
116
116
|
stream: this.stream.mintKey(key_1.KeyType.STREAMS, params),
|
|
117
|
+
counts: this.router.counts,
|
|
117
118
|
};
|
|
118
119
|
}
|
|
119
120
|
this.store.publish(key_1.KeyType.QUORUM, {
|
|
@@ -33,13 +33,18 @@ interface Measure {
|
|
|
33
33
|
}
|
|
34
34
|
interface TriggerActivityStats {
|
|
35
35
|
/**
|
|
36
|
-
* parent job; including this allows the parent's
|
|
36
|
+
* dependent parent job id; including this allows the parent's
|
|
37
37
|
* expiration/interruption events to cascade; set
|
|
38
38
|
* `expire` in the YAML for the dependent graph
|
|
39
39
|
* to 0 and provide the parent for dependent,
|
|
40
40
|
* cascading interruption and cleanup
|
|
41
41
|
*/
|
|
42
42
|
parent?: string;
|
|
43
|
+
/**
|
|
44
|
+
* adjacent parent job id; this is the actual adjacent
|
|
45
|
+
* parent in the graph, but it is not used for cascading expiration
|
|
46
|
+
*/
|
|
47
|
+
adjacent?: string;
|
|
43
48
|
id?: {
|
|
44
49
|
[key: string]: unknown;
|
|
45
50
|
} | string;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { StringAnyType } from "./serializer";
|
|
2
|
+
export type ExportItem = [(string | null), string, any];
|
|
3
|
+
export interface ExportOptions {
|
|
4
|
+
}
|
|
5
|
+
export type JobAction = {
|
|
6
|
+
cursor: number;
|
|
7
|
+
items: ExportItem[];
|
|
8
|
+
};
|
|
9
|
+
export interface JobActionExport {
|
|
10
|
+
hooks: {
|
|
11
|
+
[key: string]: JobAction;
|
|
12
|
+
};
|
|
13
|
+
main: JobAction;
|
|
14
|
+
}
|
|
15
|
+
export interface ActivityAction {
|
|
16
|
+
action: string;
|
|
17
|
+
target: string;
|
|
18
|
+
}
|
|
19
|
+
export interface JobTimeline {
|
|
20
|
+
activity: string;
|
|
21
|
+
dimension: string;
|
|
22
|
+
duplex: 'entry' | 'exit';
|
|
23
|
+
timestamp: string;
|
|
24
|
+
actions?: ActivityAction[];
|
|
25
|
+
}
|
|
26
|
+
export interface DependencyExport {
|
|
27
|
+
type: string;
|
|
28
|
+
topic: string;
|
|
29
|
+
gid: string;
|
|
30
|
+
jid: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ExportTransitions {
|
|
33
|
+
[key: string]: string[];
|
|
34
|
+
}
|
|
35
|
+
export interface ExportCycles {
|
|
36
|
+
[key: string]: string[];
|
|
37
|
+
}
|
|
38
|
+
export interface DurableJobExport {
|
|
39
|
+
data: StringAnyType;
|
|
40
|
+
dependencies: DependencyExport[];
|
|
41
|
+
state: StringAnyType;
|
|
42
|
+
status: string;
|
|
43
|
+
timeline: JobTimeline[];
|
|
44
|
+
transitions: ExportTransitions;
|
|
45
|
+
cycles: ExportCycles;
|
|
46
|
+
}
|
|
47
|
+
export interface JobExport {
|
|
48
|
+
dependencies: DependencyExport[];
|
|
49
|
+
process: StringAnyType;
|
|
50
|
+
status: string;
|
|
51
|
+
}
|
package/build/types/hotmesh.d.ts
CHANGED
package/build/types/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export { AsyncSignal } from './async';
|
|
|
4
4
|
export { CacheMode } from './cache';
|
|
5
5
|
export { CollationFaultType, CollationStage } from './collator';
|
|
6
6
|
export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, ProxyType, Registry, SignalOptions, FindOptions, FindWhereOptions, FindWhereQuery, HookOptions, MeshOSActivityOptions, MeshOSWorkerOptions, MeshOSClassConfig, MeshOSConfig, MeshOSOptions, WorkflowConfig, WorkerConfig, WorkerOptions, WorkflowContext, WorkflowSearchOptions, WorkflowDataType, WorkflowOptions, } from './durable';
|
|
7
|
+
export { ActivityAction, DependencyExport, DurableJobExport, ExportCycles, ExportItem, ExportOptions, ExportTransitions, JobAction, JobExport, JobActionExport, JobTimeline } from './exporter';
|
|
7
8
|
export { HookCondition, HookConditions, HookGate, HookInterface, HookRule, HookRules, HookSignal } from './hook';
|
|
8
9
|
export { RedisClientType as IORedisClientType, RedisMultiType as IORedisMultiType } from './ioredisclient';
|
|
9
10
|
export { ILogger } from './logger';
|
package/build/types/quorum.d.ts
CHANGED
package/build/types/task.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export type WorkListTaskType = 'sleep' | 'expire' | 'interrupt' | 'delist';
|
|
1
|
+
export type WorkListTaskType = 'sleep' | 'expire' | 'expire-child' | 'interrupt' | 'delist' | 'child';
|
package/modules/enums.ts
CHANGED
|
@@ -22,6 +22,10 @@ export const HMSH_CODE_DURABLE_RETRYABLE = 599;
|
|
|
22
22
|
|
|
23
23
|
export const HMSH_STATUS_UNKNOWN = 'unknown';
|
|
24
24
|
|
|
25
|
+
// QUORUM
|
|
26
|
+
export const HMSH_QUORUM_DELAY_MS = 250;
|
|
27
|
+
export const HMSH_ACTIVATION_MAX_RETRY = 3;
|
|
28
|
+
|
|
25
29
|
// ENGINE
|
|
26
30
|
export const HMSH_OTT_WAIT_TIME = parseInt(process.env.HMSH_OTT_WAIT_TIME, 10) || 1000;
|
|
27
31
|
export const HMSH_EXPIRE_JOB_SECONDS = parseInt(process.env.HMSH_EXPIRE_JOB_SECONDS, 10) || 1;
|
package/modules/utils.ts
CHANGED
package/package.json
CHANGED
|
@@ -186,11 +186,15 @@ class Trigger extends Activity {
|
|
|
186
186
|
async registerJobDependency(multi?: RedisMulti): Promise<void> {
|
|
187
187
|
const depKey = this.config.stats?.parent ?? this.context.metadata.pj;
|
|
188
188
|
let resolvedDepKey = depKey ? Pipe.resolve(depKey, this.context) : '';
|
|
189
|
+
const adjKey = this.config.stats?.adjacent;
|
|
190
|
+
let resolvedAdjKey = depKey ? Pipe.resolve(adjKey, this.context) : '';
|
|
189
191
|
if (!resolvedDepKey) {
|
|
190
192
|
resolvedDepKey = this.context.metadata.pj;
|
|
191
193
|
}
|
|
192
194
|
if (resolvedDepKey) {
|
|
195
|
+
const isParentOrigin = (resolvedDepKey === this.context.metadata.pj) || (resolvedDepKey === resolvedAdjKey);
|
|
193
196
|
await this.store.registerJobDependency(
|
|
197
|
+
isParentOrigin ? 'expire-child' : 'expire',
|
|
194
198
|
resolvedDepKey,
|
|
195
199
|
this.context.metadata.tpc,
|
|
196
200
|
this.context.metadata.jid,
|
|
@@ -198,6 +202,16 @@ class Trigger extends Activity {
|
|
|
198
202
|
multi,
|
|
199
203
|
);
|
|
200
204
|
}
|
|
205
|
+
if (resolvedAdjKey && resolvedAdjKey !== resolvedDepKey) {
|
|
206
|
+
await this.store.registerJobDependency(
|
|
207
|
+
'child',
|
|
208
|
+
resolvedAdjKey,
|
|
209
|
+
this.context.metadata.tpc,
|
|
210
|
+
this.context.metadata.jid,
|
|
211
|
+
this.context.metadata.gid,
|
|
212
|
+
multi,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
201
215
|
}
|
|
202
216
|
|
|
203
217
|
async setStats(multi?: RedisMulti): Promise<void> {
|
|
@@ -11,13 +11,14 @@ import { JobState } from '../../types/job';
|
|
|
11
11
|
import { KeyService, KeyType } from '../../modules/key';
|
|
12
12
|
import { Search } from './search';
|
|
13
13
|
import { StreamStatus } from '../../types';
|
|
14
|
-
import { HMSH_LOGLEVEL, HMSH_EXPIRE_JOB_SECONDS } from '../../modules/enums';
|
|
14
|
+
import { HMSH_LOGLEVEL, HMSH_EXPIRE_JOB_SECONDS, HMSH_QUORUM_DELAY_MS } from '../../modules/enums';
|
|
15
|
+
import { sleepFor } from '../../modules/utils';
|
|
15
16
|
|
|
16
17
|
export class ClientService {
|
|
17
18
|
|
|
18
19
|
connection: Connection;
|
|
19
|
-
topics: string[] = [];
|
|
20
20
|
options: WorkflowOptions;
|
|
21
|
+
static topics: string[] = [];
|
|
21
22
|
static instances = new Map<string, HotMesh | Promise<HotMesh>>();
|
|
22
23
|
|
|
23
24
|
constructor(config: ClientConfig) {
|
|
@@ -29,8 +30,9 @@ export class ClientService {
|
|
|
29
30
|
const instanceId = 'SINGLETON';
|
|
30
31
|
if (ClientService.instances.has(instanceId)) {
|
|
31
32
|
const hotMeshClient = await ClientService.instances.get(instanceId);
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
await this.verifyWorkflowActive(hotMeshClient, namespace ?? APP_ID);
|
|
34
|
+
if (!ClientService.topics.includes(workflowTopic)) {
|
|
35
|
+
ClientService.topics.push(workflowTopic);
|
|
34
36
|
await this.createStream(hotMeshClient, workflowTopic, namespace);
|
|
35
37
|
}
|
|
36
38
|
return hotMeshClient;
|
|
@@ -196,6 +198,19 @@ export class ClientService {
|
|
|
196
198
|
}
|
|
197
199
|
}
|
|
198
200
|
|
|
201
|
+
async verifyWorkflowActive(hotMesh: HotMesh, appId = APP_ID, count = 0): Promise<boolean> {
|
|
202
|
+
const app = await hotMesh.engine.store.getApp(appId);
|
|
203
|
+
const appVersion = app?.version as unknown as number;
|
|
204
|
+
if(isNaN(appVersion)) {
|
|
205
|
+
if (count > 10) {
|
|
206
|
+
throw new Error('Workflow failed to activate');
|
|
207
|
+
}
|
|
208
|
+
await sleepFor(HMSH_QUORUM_DELAY_MS * 2);
|
|
209
|
+
return await this.verifyWorkflowActive(hotMesh, appId, count + 1);
|
|
210
|
+
}
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
199
214
|
async activateWorkflow(hotMesh: HotMesh, appId = APP_ID, version = APP_VERSION): Promise<void> {
|
|
200
215
|
const app = await hotMesh.engine.store.getApp(appId);
|
|
201
216
|
const appVersion = app?.version as unknown as number;
|