@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
|
@@ -26,12 +26,9 @@ class QuorumService {
|
|
|
26
26
|
instance.guid = guid;
|
|
27
27
|
instance.logger = logger;
|
|
28
28
|
instance.engine = engine;
|
|
29
|
-
//note: `quorum` shares/re-uses the engine's `store`/`sub` Redis clients
|
|
30
29
|
await instance.initStoreChannel(config.engine.store);
|
|
31
30
|
await instance.initSubChannel(config.engine.sub);
|
|
32
|
-
//general quorum subscription
|
|
33
31
|
await instance.subscribe.subscribe(hotmesh_1.KeyType.QUORUM, instance.subscriptionHandler(), appId);
|
|
34
|
-
//app-specific quorum subscription (used for pubsub one-time request/response)
|
|
35
32
|
await instance.subscribe.subscribe(hotmesh_1.KeyType.QUORUM, instance.subscriptionHandler(), appId, instance.guid);
|
|
36
33
|
instance.engine.processWebHooks();
|
|
37
34
|
instance.engine.processTimeHooks();
|
|
@@ -93,7 +90,6 @@ class QuorumService {
|
|
|
93
90
|
else if (message.type === 'rollcall') {
|
|
94
91
|
self.doRollCall(message);
|
|
95
92
|
}
|
|
96
|
-
//if there are any callbacks, call them
|
|
97
93
|
if (self.callbacks.length > 0) {
|
|
98
94
|
self.callbacks.forEach(cb => cb(topic, message));
|
|
99
95
|
}
|
|
@@ -135,10 +131,6 @@ class QuorumService {
|
|
|
135
131
|
await (0, utils_1.sleepFor)(delay);
|
|
136
132
|
return quorum;
|
|
137
133
|
}
|
|
138
|
-
/**
|
|
139
|
-
* A quorum-wide command to broadcaset system details.
|
|
140
|
-
*
|
|
141
|
-
*/
|
|
142
134
|
async doRollCall(message) {
|
|
143
135
|
let iteration = 0;
|
|
144
136
|
let max = !isNaN(message.max) ? message.max : enums_1.HMSH_QUORUM_ROLLCALL_CYCLES;
|
|
@@ -165,22 +157,15 @@ class QuorumService {
|
|
|
165
157
|
stop() {
|
|
166
158
|
this.cancelRollCall();
|
|
167
159
|
}
|
|
168
|
-
// ************* PUB/SUB METHODS *************
|
|
169
|
-
//publish a message to the quorum
|
|
170
160
|
async pub(quorumMessage) {
|
|
171
161
|
return await this.store.publish(hotmesh_1.KeyType.QUORUM, quorumMessage, this.appId, quorumMessage.topic || quorumMessage.guid);
|
|
172
162
|
}
|
|
173
|
-
//subscribe user to quorum messages
|
|
174
163
|
async sub(callback) {
|
|
175
|
-
//the quorum is always subscribed to the `quorum` topic; just register the fn
|
|
176
164
|
this.callbacks.push(callback);
|
|
177
165
|
}
|
|
178
|
-
//unsubscribe user from quorum messages
|
|
179
166
|
async unsub(callback) {
|
|
180
|
-
//the quorum is always subscribed to the `quorum` topic; just unregister the fn
|
|
181
167
|
this.callbacks = this.callbacks.filter(cb => cb !== callback);
|
|
182
168
|
}
|
|
183
|
-
// ************* COMPILER METHODS *************
|
|
184
169
|
async rollCall(delay = enums_1.HMSH_QUORUM_DELAY_MS) {
|
|
185
170
|
await this.requestQuorum(delay, true);
|
|
186
171
|
const targetStreams = [];
|
|
@@ -202,14 +187,10 @@ class QuorumService {
|
|
|
202
187
|
});
|
|
203
188
|
return this.profiles;
|
|
204
189
|
}
|
|
205
|
-
/**
|
|
206
|
-
* request a quorum; if successful activate the app version
|
|
207
|
-
*/
|
|
208
190
|
async activate(version, delay = enums_1.HMSH_QUORUM_DELAY_MS, count = 0) {
|
|
209
191
|
version = version.toString();
|
|
210
192
|
const canActivate = await this.store.reserveScoutRole('activate', Math.ceil(delay * 6 / 1000) + 1);
|
|
211
193
|
if (!canActivate) {
|
|
212
|
-
//another engine is already activating the app version
|
|
213
194
|
this.logger.debug('quorum-activation-awaiting', { version });
|
|
214
195
|
await (0, utils_1.sleepFor)(delay * 6);
|
|
215
196
|
const app = await this.store.getApp(this.appId, true);
|
|
@@ -225,7 +206,6 @@ class QuorumService {
|
|
|
225
206
|
this.store.publish(hotmesh_1.KeyType.QUORUM, { type: 'activate', cache_mode: 'nocache', until_version: version }, this.appId);
|
|
226
207
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
227
208
|
await this.store.releaseScoutRole('activate');
|
|
228
|
-
//confirm we received the activation message
|
|
229
209
|
if (this.engine.untilVersion === version) {
|
|
230
210
|
this.logger.info('quorum-activation-succeeded', { version });
|
|
231
211
|
const { id } = config;
|
|
@@ -241,7 +221,6 @@ class QuorumService {
|
|
|
241
221
|
this.logger.warn('quorum-rollcall-error', { q1, q2, q3, count });
|
|
242
222
|
this.store.releaseScoutRole('activate');
|
|
243
223
|
if (count < enums_1.HMSH_ACTIVATION_MAX_RETRY) {
|
|
244
|
-
//increase the delay (give the quorum time to respond) and try again
|
|
245
224
|
return await this.activate(version, delay * 2, count + 1);
|
|
246
225
|
}
|
|
247
226
|
throw new Error(`Quorum not reached. Version ${version} not activated.`);
|
|
@@ -28,11 +28,6 @@ declare class ReporterService {
|
|
|
28
28
|
getTargetForTime(key: string): string;
|
|
29
29
|
getWorkItems(options: GetStatsOptions, facets: string[]): Promise<string[]>;
|
|
30
30
|
private buildWorkerLists;
|
|
31
|
-
/**
|
|
32
|
-
* called by `trigger` activity to generate the stats that should
|
|
33
|
-
* be saved to the database. doesn't actually save the stats, but
|
|
34
|
-
* just generates the info that should be saved
|
|
35
|
-
*/
|
|
36
31
|
resolveTriggerStatistics({ stats: statsConfig }: TriggerActivity, context: JobState): StatsType;
|
|
37
32
|
isGeneralMetric(metric: string): boolean;
|
|
38
33
|
isMedianMetric(metric: string): boolean;
|
|
@@ -27,11 +27,9 @@ class ReporterService {
|
|
|
27
27
|
}
|
|
28
28
|
generateDateTimeSets(granularity, range, end, start) {
|
|
29
29
|
if (granularity === 'infinity') {
|
|
30
|
-
//if granularity is infinity, it means a date/time sequence/slice is not used to further segment the statistics
|
|
31
30
|
return ['0'];
|
|
32
31
|
}
|
|
33
32
|
if (!range) {
|
|
34
|
-
//pluck just a single value when no range provided
|
|
35
33
|
range = '0m';
|
|
36
34
|
}
|
|
37
35
|
const granularitiesInMinutes = {
|
|
@@ -49,7 +47,6 @@ class ReporterService {
|
|
|
49
47
|
if (rangeMinutes === null) {
|
|
50
48
|
throw new Error('Invalid range value.');
|
|
51
49
|
}
|
|
52
|
-
// If start is provided, use it. Otherwise, calculate it from the end time and range.
|
|
53
50
|
let startTime;
|
|
54
51
|
let endTime;
|
|
55
52
|
if (start) {
|
|
@@ -60,7 +57,6 @@ class ReporterService {
|
|
|
60
57
|
endTime = end === 'NOW' ? new Date() : new Date(end);
|
|
61
58
|
startTime = new Date(endTime.getTime() - rangeMinutes * 60 * 1000);
|
|
62
59
|
}
|
|
63
|
-
// Round the start time to the nearest granularity unit
|
|
64
60
|
startTime.setUTCMinutes(Math.floor(startTime.getUTCMinutes() / granularityMinutes) * granularityMinutes);
|
|
65
61
|
const dateTimeSets = [];
|
|
66
62
|
for (let time = startTime; time <= endTime; time.setUTCMinutes(time.getUTCMinutes() + granularityMinutes)) {
|
|
@@ -270,11 +266,6 @@ class ReporterService {
|
|
|
270
266
|
}
|
|
271
267
|
return workerLists;
|
|
272
268
|
}
|
|
273
|
-
/**
|
|
274
|
-
* called by `trigger` activity to generate the stats that should
|
|
275
|
-
* be saved to the database. doesn't actually save the stats, but
|
|
276
|
-
* just generates the info that should be saved
|
|
277
|
-
*/
|
|
278
269
|
resolveTriggerStatistics({ stats: statsConfig }, context) {
|
|
279
270
|
const stats = {
|
|
280
271
|
general: [],
|
|
@@ -30,15 +30,6 @@ declare class Router {
|
|
|
30
30
|
private resetThrottleState;
|
|
31
31
|
createGroup(stream: string, group: string): Promise<void>;
|
|
32
32
|
publishMessage(topic: string, streamData: StreamData | StreamDataResponse, multi?: RedisMulti): Promise<string | RedisMulti>;
|
|
33
|
-
/**
|
|
34
|
-
* An adjustable throttle that will interrupt a sleeping
|
|
35
|
-
* router if the throttle is reduced and the sleep time
|
|
36
|
-
* has elapsed. If the throttle is increased, or if
|
|
37
|
-
* the sleep time has not elapsed, the router will continue
|
|
38
|
-
* to sleep until the new termination point. This
|
|
39
|
-
* allows for dynamic, elastic throttling with smooth
|
|
40
|
-
* acceleration and deceleration.
|
|
41
|
-
*/
|
|
42
33
|
customSleep(): Promise<void>;
|
|
43
34
|
consumeMessages(stream: string, group: string, consumer: string, callback: (streamData: StreamData) => Promise<StreamDataResponse | void>): Promise<void>;
|
|
44
35
|
isStreamMessage(result: any): boolean;
|
|
@@ -47,22 +47,13 @@ class Router {
|
|
|
47
47
|
const stream = this.store.mintKey(key_1.KeyType.STREAMS, { appId: this.store.appId, topic });
|
|
48
48
|
return await this.store.xadd(stream, '*', 'message', JSON.stringify(streamData), multi);
|
|
49
49
|
}
|
|
50
|
-
/**
|
|
51
|
-
* An adjustable throttle that will interrupt a sleeping
|
|
52
|
-
* router if the throttle is reduced and the sleep time
|
|
53
|
-
* has elapsed. If the throttle is increased, or if
|
|
54
|
-
* the sleep time has not elapsed, the router will continue
|
|
55
|
-
* to sleep until the new termination point. This
|
|
56
|
-
* allows for dynamic, elastic throttling with smooth
|
|
57
|
-
* acceleration and deceleration.
|
|
58
|
-
*/
|
|
59
50
|
async customSleep() {
|
|
60
51
|
if (this.throttle === 0)
|
|
61
52
|
return;
|
|
62
53
|
if (this.isSleeping)
|
|
63
54
|
return;
|
|
64
55
|
this.isSleeping = true;
|
|
65
|
-
let startTime = Date.now();
|
|
56
|
+
let startTime = Date.now();
|
|
66
57
|
await new Promise(async (outerResolve) => {
|
|
67
58
|
this.sleepPromiseResolve = outerResolve;
|
|
68
59
|
let elapsedTime = Date.now() - startTime;
|
|
@@ -90,7 +81,6 @@ class Router {
|
|
|
90
81
|
return;
|
|
91
82
|
}
|
|
92
83
|
try {
|
|
93
|
-
//randomizer that asymptotes at 150% of `HMSH_BLOCK_TIME_MS`
|
|
94
84
|
const streamDuration = enums_1.HMSH_BLOCK_TIME_MS + Math.round((enums_1.HMSH_BLOCK_TIME_MS * Math.random()));
|
|
95
85
|
const result = await this.stream.xreadgroup('GROUP', group, consumer, 'BLOCK', streamDuration, 'STREAMS', stream, '>');
|
|
96
86
|
if (this.isStreamMessage(result)) {
|
|
@@ -99,7 +89,6 @@ class Router {
|
|
|
99
89
|
await this.consumeOne(stream, group, id, message, callback);
|
|
100
90
|
}
|
|
101
91
|
}
|
|
102
|
-
// Check for pending messages (note: Redis 6.2 simplifies)
|
|
103
92
|
const now = Date.now();
|
|
104
93
|
if (now - lastCheckedPendingMessagesAt > this.reclaimDelay) {
|
|
105
94
|
lastCheckedPendingMessagesAt = now;
|
|
@@ -173,7 +162,6 @@ class Router {
|
|
|
173
162
|
await (0, utils_1.sleepFor)(timeout);
|
|
174
163
|
return await this.publishMessage(input.metadata.topic, {
|
|
175
164
|
data: input.data,
|
|
176
|
-
//note: retain guid (this is a retry attempt)
|
|
177
165
|
metadata: { ...input.metadata, try: (input.metadata.try || 0) + 1 },
|
|
178
166
|
policies: input.policies,
|
|
179
167
|
});
|
|
@@ -193,24 +181,12 @@ class Router {
|
|
|
193
181
|
}
|
|
194
182
|
}
|
|
195
183
|
shouldRetry(input, output) {
|
|
196
|
-
//const isUnhandledEngineError = output.code === 500;
|
|
197
184
|
const policies = input.policies?.retry;
|
|
198
185
|
const errorCode = output.code.toString();
|
|
199
186
|
const policy = policies?.[errorCode];
|
|
200
187
|
const maxRetries = policy?.[0];
|
|
201
|
-
// if (isUnhandledEngineError && !policy) {
|
|
202
|
-
// //if main goes down, replicas take over within 5s
|
|
203
|
-
// //if this is not system/platform related, the exponential
|
|
204
|
-
// //backoff will be applied and eventually slow to a crawl while
|
|
205
|
-
// //the root cause is identified
|
|
206
|
-
// input.policies = { retry: { [errorCode]: [10] } };
|
|
207
|
-
// return [true, 0];
|
|
208
|
-
// }
|
|
209
188
|
const tryCount = Math.min(input.metadata.try || 0, enums_1.HMSH_MAX_RETRIES);
|
|
210
|
-
//only possible values for maxRetries are 1, 2, 3
|
|
211
|
-
//only possible values for tryCount are 0, 1, 2
|
|
212
189
|
if (maxRetries > tryCount) {
|
|
213
|
-
// 10ms, 100ms, or 1000ms delays between system retries
|
|
214
190
|
return [true, Math.pow(10, tryCount + 1)];
|
|
215
191
|
}
|
|
216
192
|
return [false, 0];
|
|
@@ -246,7 +222,6 @@ class Router {
|
|
|
246
222
|
code,
|
|
247
223
|
data,
|
|
248
224
|
};
|
|
249
|
-
//send unacknowleded errors to the engine (it has no topic)
|
|
250
225
|
delete output.metadata.topic;
|
|
251
226
|
return output;
|
|
252
227
|
}
|
|
@@ -290,7 +265,6 @@ class Router {
|
|
|
290
265
|
}
|
|
291
266
|
const wasDecreased = delayInMillis < this.throttle;
|
|
292
267
|
this.throttle = delayInMillis;
|
|
293
|
-
// If the throttle was decreased, and we're in the middle of a sleep cycle, adjust immediately
|
|
294
268
|
if (wasDecreased) {
|
|
295
269
|
if (this.sleepTimout) {
|
|
296
270
|
clearTimeout(this.sleepTimout);
|
|
@@ -302,7 +276,7 @@ class Router {
|
|
|
302
276
|
}
|
|
303
277
|
async claimUnacknowledged(stream, group, consumer, idleTimeMs = this.reclaimDelay, limit = enums_1.HMSH_XPENDING_COUNT) {
|
|
304
278
|
let pendingMessages = [];
|
|
305
|
-
const pendingMessagesInfo = await this.stream.xpending(stream, group, '-', '+', limit);
|
|
279
|
+
const pendingMessagesInfo = await this.stream.xpending(stream, group, '-', '+', limit);
|
|
306
280
|
for (const pendingMessageInfo of pendingMessagesInfo) {
|
|
307
281
|
if (Array.isArray(pendingMessageInfo)) {
|
|
308
282
|
const [id, , elapsedTimeMs, deliveryCount] = pendingMessageInfo;
|
|
@@ -322,16 +296,8 @@ class Router {
|
|
|
322
296
|
return pendingMessages;
|
|
323
297
|
}
|
|
324
298
|
async expireUnacknowledged(reclaimedMessage, stream, group, consumer, id, count) {
|
|
325
|
-
//The stream activity was not processed within established limits. Possibilities Include:
|
|
326
|
-
// 1) user error: the workers were not properly configured and are timing out
|
|
327
|
-
// 2a) system error: JSON is corrupt
|
|
328
|
-
// i) unwitting actor
|
|
329
|
-
// ii) corrupt hardware/network/transport/etc
|
|
330
|
-
// 3b) system error: Redis unable to accept `xadd` request
|
|
331
|
-
// 4c) system error: Redis unable to accept `xdel`/`xack` request
|
|
332
299
|
this.logger.error('stream-message-max-delivery-count-exceeded', { id, stream, group, consumer, code: enums_1.HMSH_CODE_UNACKED, count });
|
|
333
300
|
const streamData = reclaimedMessage[0]?.[1]?.[1];
|
|
334
|
-
//fatal risk point 1 of 3): json is corrupt
|
|
335
301
|
const [err, input] = this.parseStreamData(streamData);
|
|
336
302
|
if (err) {
|
|
337
303
|
return this.logger.error('expire-unacknowledged-parse-fatal-error', { id, err });
|
|
@@ -345,11 +311,9 @@ class Router {
|
|
|
345
311
|
telemetry = new telemetry_1.TelemetryService(this.appId);
|
|
346
312
|
telemetry.startStreamSpan(input, stream_1.StreamRole.SYSTEM);
|
|
347
313
|
telemetry.setStreamError(`Stream Message Max Delivery Count Exceeded`);
|
|
348
|
-
//fatal risk point 2 of 3): unable to publish error message (to notify the parent job)
|
|
349
314
|
const output = this.structureUnacknowledgedError(input);
|
|
350
315
|
messageId = await this.publishResponse(input, output);
|
|
351
316
|
telemetry.setStreamAttributes({ 'app.worker.mid': messageId });
|
|
352
|
-
//fatal risk point 3 of 3): unable to ack and delete stream message
|
|
353
317
|
await this.ackAndDelete(stream, group, id);
|
|
354
318
|
}
|
|
355
319
|
catch (err) {
|
|
@@ -49,7 +49,6 @@ class SerializerService {
|
|
|
49
49
|
return this.dIds[activityId];
|
|
50
50
|
}
|
|
51
51
|
else if ('$ADJACENT' in this.dIds) {
|
|
52
|
-
//else=> pre-authorizing adjacent activity entry
|
|
53
52
|
return this.dIds['$ADJACENT'];
|
|
54
53
|
}
|
|
55
54
|
return ',0';
|
|
@@ -118,7 +117,6 @@ class SerializerService {
|
|
|
118
117
|
result[shortDimensionalKey] = source[key];
|
|
119
118
|
}
|
|
120
119
|
else if (!(key in result) && this.isLiteralKeyType(key)) {
|
|
121
|
-
//mark (-) and search (_)
|
|
122
120
|
result[key] = source[key];
|
|
123
121
|
}
|
|
124
122
|
}
|
|
@@ -142,7 +140,6 @@ class SerializerService {
|
|
|
142
140
|
const inflateWithMap = (abbreviationMap, id) => {
|
|
143
141
|
const reversedAbbreviationMap = this.getReverseKeyMap(abbreviationMap, id);
|
|
144
142
|
for (let key in result) {
|
|
145
|
-
//strip dimensional index from key
|
|
146
143
|
const shortKey = key.split(',')[0];
|
|
147
144
|
let longKey = reversedAbbreviationMap.get(shortKey);
|
|
148
145
|
if (longKey) {
|
|
@@ -159,38 +156,22 @@ class SerializerService {
|
|
|
159
156
|
}
|
|
160
157
|
return result;
|
|
161
158
|
}
|
|
162
|
-
//stringify: convert a multi-dimensional document to a 2-d hash
|
|
163
159
|
stringify(document) {
|
|
164
160
|
let result = {};
|
|
165
161
|
for (let key in document) {
|
|
166
162
|
let value = SerializerService.toString(document[key]);
|
|
167
163
|
if (value) {
|
|
168
|
-
if (/^:*[a-zA-Z]{2}$/.test(value)) {
|
|
169
|
-
value = ':' + value;
|
|
170
|
-
}
|
|
171
|
-
else if (this.symValReverseMaps.has(value)) {
|
|
172
|
-
value = this.symValReverseMaps.get(value);
|
|
173
|
-
}
|
|
174
164
|
result[key] = value;
|
|
175
165
|
}
|
|
176
166
|
}
|
|
177
167
|
return result;
|
|
178
168
|
}
|
|
179
|
-
//parse: convert a 2-d hash to a multi-dimensional document
|
|
180
169
|
parse(document) {
|
|
181
170
|
let result = {};
|
|
182
171
|
for (let [key, value] of Object.entries(document)) {
|
|
183
172
|
if (value === undefined || value === null)
|
|
184
173
|
continue;
|
|
185
|
-
|
|
186
|
-
result[key] = value.slice(1);
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
if (value?.length === 2 && this.symValMaps.has(value)) {
|
|
190
|
-
value = this.symValMaps.get(value);
|
|
191
|
-
}
|
|
192
|
-
result[key] = SerializerService.fromString(value);
|
|
193
|
-
}
|
|
174
|
+
result[key] = SerializerService.fromString(value);
|
|
194
175
|
}
|
|
195
176
|
return result;
|
|
196
177
|
}
|
|
@@ -223,20 +204,20 @@ class SerializerService {
|
|
|
223
204
|
const prefix = value.slice(0, 2);
|
|
224
205
|
const rest = value.slice(2);
|
|
225
206
|
switch (prefix) {
|
|
226
|
-
case '/t':
|
|
207
|
+
case '/t':
|
|
227
208
|
return true;
|
|
228
|
-
case '/f':
|
|
209
|
+
case '/f':
|
|
229
210
|
return false;
|
|
230
|
-
case '/d':
|
|
211
|
+
case '/d':
|
|
231
212
|
return Number(rest);
|
|
232
|
-
case '/n':
|
|
213
|
+
case '/n':
|
|
233
214
|
return null;
|
|
234
|
-
case '/s':
|
|
215
|
+
case '/s':
|
|
235
216
|
if (dateReg.exec(rest)) {
|
|
236
217
|
return new Date(JSON.parse(rest));
|
|
237
218
|
}
|
|
238
219
|
return JSON.parse(rest);
|
|
239
|
-
default:
|
|
220
|
+
default:
|
|
240
221
|
return value;
|
|
241
222
|
}
|
|
242
223
|
}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Cache is a key/value store and used to store commonly accessed Redis metadata
|
|
3
|
-
* (mainly the execution rules for the app) to save time accessing them as they
|
|
4
|
-
* are immutable per verison. The only time the rules are ejected are when
|
|
5
|
-
* a new version is deployed to the quorum and the cache is invalidated/cleared.
|
|
6
|
-
*/
|
|
7
1
|
import { ActivityType } from "../../types/activity";
|
|
8
2
|
import { HookRule } from "../../types/hook";
|
|
9
3
|
import { HotMeshApp, HotMeshSettings } from "../../types/hotmesh";
|
|
@@ -20,19 +14,7 @@ declare class Cache {
|
|
|
20
14
|
transitions: Record<string, Record<string, unknown>>;
|
|
21
15
|
hookRules: Record<string, Record<string, HookRule[]>>;
|
|
22
16
|
workItems: Record<string, string>;
|
|
23
|
-
/**
|
|
24
|
-
* The cache is ALWAYS initialized with HotMeshSettings. The other parameters are optional.
|
|
25
|
-
* @param settings
|
|
26
|
-
* @param apps
|
|
27
|
-
* @param schemas
|
|
28
|
-
* @param subscriptions
|
|
29
|
-
* @param transitions
|
|
30
|
-
* @param hookRules
|
|
31
|
-
*/
|
|
32
17
|
constructor(appId: string, settings: HotMeshSettings, apps?: Record<string, HotMeshApp>, schemas?: Record<string, ActivityType>, subscriptions?: Record<string, Record<string, string>>, symbols?: Record<string, Symbols>, symvals?: Record<string, Symbols>, transitions?: Record<string, Record<string, unknown>>, hookRules?: Record<string, Record<string, HookRule[]>>, workItems?: Record<string, string>);
|
|
33
|
-
/**
|
|
34
|
-
* invalidate the cache; settings are not invalidated!
|
|
35
|
-
*/
|
|
36
18
|
invalidate(): void;
|
|
37
19
|
getSettings(): HotMeshSettings;
|
|
38
20
|
setSettings(settings: HotMeshSettings): void;
|
|
@@ -1,22 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* The Cache is a key/value store and used to store commonly accessed Redis metadata
|
|
4
|
-
* (mainly the execution rules for the app) to save time accessing them as they
|
|
5
|
-
* are immutable per verison. The only time the rules are ejected are when
|
|
6
|
-
* a new version is deployed to the quorum and the cache is invalidated/cleared.
|
|
7
|
-
*/
|
|
8
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
3
|
exports.Cache = void 0;
|
|
10
4
|
class Cache {
|
|
11
|
-
/**
|
|
12
|
-
* The cache is ALWAYS initialized with HotMeshSettings. The other parameters are optional.
|
|
13
|
-
* @param settings
|
|
14
|
-
* @param apps
|
|
15
|
-
* @param schemas
|
|
16
|
-
* @param subscriptions
|
|
17
|
-
* @param transitions
|
|
18
|
-
* @param hookRules
|
|
19
|
-
*/
|
|
20
5
|
constructor(appId, settings, apps = {}, schemas = {}, subscriptions = {}, symbols = {}, symvals = {}, transitions = {}, hookRules = {}, workItems = {}) {
|
|
21
6
|
this.appId = appId;
|
|
22
7
|
this.settings = settings;
|
|
@@ -29,9 +14,6 @@ class Cache {
|
|
|
29
14
|
this.hookRules = hookRules;
|
|
30
15
|
this.workItems = workItems;
|
|
31
16
|
}
|
|
32
|
-
/**
|
|
33
|
-
* invalidate the cache; settings are not invalidated!
|
|
34
|
-
*/
|
|
35
17
|
invalidate() {
|
|
36
18
|
this.apps = {};
|
|
37
19
|
this.schemas = {};
|
|
@@ -3,7 +3,7 @@ import { ILogger } from '../../logger';
|
|
|
3
3
|
import { SerializerService as Serializer } from '../../serializer';
|
|
4
4
|
import { Cache } from '../cache';
|
|
5
5
|
import { StoreService } from '../index';
|
|
6
|
-
import { RedisClientType, RedisMultiType } from '../../../types/
|
|
6
|
+
import { IORedisClientType as RedisClientType, IORedisMultiType as RedisMultiType } from '../../../types/redis';
|
|
7
7
|
import { ReclaimedMessageType } from '../../../types/stream';
|
|
8
8
|
declare class IORedisStoreService extends StoreService<RedisClientType, RedisMultiType> {
|
|
9
9
|
redisClient: RedisClientType;
|
|
@@ -3,7 +3,7 @@ import { ILogger } from '../../logger';
|
|
|
3
3
|
import { SerializerService as Serializer } from '../../serializer';
|
|
4
4
|
import { Cache } from '../cache';
|
|
5
5
|
import { StoreService } from '../index';
|
|
6
|
-
import { RedisClientType, RedisMultiType } from '../../../types/
|
|
6
|
+
import { RedisRedisClientType as RedisClientType, RedisRedisMultiType as RedisMultiType } from '../../../types/redis';
|
|
7
7
|
import { ReclaimedMessageType } from '../../../types/stream';
|
|
8
8
|
declare class RedisStoreService extends StoreService<RedisClientType, RedisMultiType> {
|
|
9
9
|
redisClient: RedisClientType;
|
|
@@ -41,11 +41,6 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
41
41
|
zRangeByScore(key: string, score: number | string, value: string | number): Promise<string | null>;
|
|
42
42
|
mintKey(type: KeyType, params: KeyStoreParams): string;
|
|
43
43
|
invalidateCache(): void;
|
|
44
|
-
/**
|
|
45
|
-
* At any given time only a single engine will
|
|
46
|
-
* check for and process work items in the
|
|
47
|
-
* time and signal task queues.
|
|
48
|
-
*/
|
|
49
44
|
reserveScoutRole(scoutType: 'time' | 'signal' | 'activate', delay?: number): Promise<boolean>;
|
|
50
45
|
releaseScoutRole(scoutType: 'time' | 'signal' | 'activate'): Promise<boolean>;
|
|
51
46
|
getSettings(bCreate?: boolean): Promise<HotMeshSettings>;
|
|
@@ -64,16 +59,7 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
64
59
|
setApp(id: string, version: string): Promise<HotMeshApp>;
|
|
65
60
|
activateAppVersion(id: string, version: string): Promise<boolean>;
|
|
66
61
|
registerAppVersion(appId: string, version: string): Promise<any>;
|
|
67
|
-
/**
|
|
68
|
-
* Registers the job, `jobId`, with `originJobId`. In the future,
|
|
69
|
-
* when `originJobId` is interrupted/expired, the items in the
|
|
70
|
-
* list (added via RPUSH) will be interrupted/expired (removed via LPOPed).
|
|
71
|
-
*/
|
|
72
62
|
registerJobDependency(depType: WorkListTaskType, originJobId: string, topic: string, jobId: string, gId: string, pd?: string, multi?: U): Promise<any>;
|
|
73
|
-
/**
|
|
74
|
-
* Ensures a `hook signal` is delisted when its parent activity/job
|
|
75
|
-
* is interrupted/expired.
|
|
76
|
-
*/
|
|
77
63
|
registerSignalDependency(jobId: string, signalKey: string, dad: string, multi?: U): Promise<any>;
|
|
78
64
|
setStats(jobKey: string, jobId: string, dateTime: string, stats: StatsType, appVersion: AppVID, multi?: U): Promise<any>;
|
|
79
65
|
hGetAllResult(result: any): any;
|
|
@@ -82,24 +68,10 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
82
68
|
setStatus(collationKeyStatus: number, jobId: string, appId: string, multi?: U): Promise<any>;
|
|
83
69
|
getStatus(jobId: string, appId: string): Promise<number>;
|
|
84
70
|
setState({ ...state }: StringAnyType, status: number | null, jobId: string, symbolNames: string[], dIds: StringStringType, multi?: U): Promise<string>;
|
|
85
|
-
/**
|
|
86
|
-
* Returns custom search fields and values.
|
|
87
|
-
* NOTE: The `fields` param should NOT prefix items with an underscore.
|
|
88
|
-
*/
|
|
89
71
|
getQueryState(jobId: string, fields: string[]): Promise<StringAnyType>;
|
|
90
72
|
getState(jobId: string, consumes: Consumes, dIds: StringStringType): Promise<[StringAnyType, number] | undefined>;
|
|
91
73
|
getRaw(jobId: string): Promise<StringStringType>;
|
|
92
|
-
/**
|
|
93
|
-
* collate is a generic method for incrementing a value in a hash
|
|
94
|
-
* in order to track their progress during processing.
|
|
95
|
-
*/
|
|
96
74
|
collate(jobId: string, activityId: string, amount: number, dIds: StringStringType, multi?: U): Promise<number>;
|
|
97
|
-
/**
|
|
98
|
-
* synthentic collation affects those activities in the graph
|
|
99
|
-
* that represent the synthetic DAG that was materialized during compilation;
|
|
100
|
-
* Synthetic targeting ensures that re-entry due to failure can be distinguished from
|
|
101
|
-
* purposeful re-entry.
|
|
102
|
-
*/
|
|
103
75
|
collateSynthetic(jobId: string, guid: string, amount: number, multi?: U): Promise<number>;
|
|
104
76
|
setStateNX(jobId: string, appId: string): Promise<boolean>;
|
|
105
77
|
getSchema(activityId: string, appVersion: AppVID): Promise<ActivityType>;
|
|
@@ -120,38 +92,11 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
120
92
|
deleteProcessedTaskQueue(workItemKey: string, key: string, processedKey: string, scrub?: boolean): Promise<void>;
|
|
121
93
|
processTaskQueue(sourceKey: string, destinationKey: string): Promise<any>;
|
|
122
94
|
expireJob(jobId: string, inSeconds: number): Promise<void>;
|
|
123
|
-
/**
|
|
124
|
-
* register the descendants of an expired origin flow to be
|
|
125
|
-
* expired at a future date; options indicate whether this
|
|
126
|
-
* is a standard `expire` or an `interrupt`
|
|
127
|
-
*/
|
|
128
95
|
registerDependenciesForCleanup(jobId: string, deletionTime: number, options: JobCompletionOptions): Promise<void>;
|
|
129
96
|
getDependencies(jobId: string): Promise<string[]>;
|
|
130
|
-
/**
|
|
131
|
-
* registers a hook activity to be awakened (uses ZSET to
|
|
132
|
-
* store the 'sleep group' and LIST to store the events
|
|
133
|
-
* for the given sleep group. Sleep groups are
|
|
134
|
-
* organized into 'n'-second blocks (LISTS))
|
|
135
|
-
*/
|
|
136
97
|
registerTimeHook(jobId: string, gId: string, activityId: string, type: WorkListTaskType, deletionTime: number, dad: string, multi?: U): Promise<void>;
|
|
137
98
|
getNextTask(listKey?: string): Promise<[listKey: string, jobId: string, gId: string, activityId: string, type: WorkListTaskType] | boolean>;
|
|
138
|
-
/**
|
|
139
|
-
* when processing time jobs, the target LIST ID returned
|
|
140
|
-
* from the ZSET query can be prefixed to denote what to
|
|
141
|
-
* do with the work list. (not everything is known in advance,
|
|
142
|
-
* so the ZSET key defines HOW to approach the work in the
|
|
143
|
-
* generic LIST (lists typically contain target job ids)
|
|
144
|
-
* @param {string} listKey - composite key
|
|
145
|
-
*/
|
|
146
99
|
resolveTaskKeyContext(listKey: string): [WorkListTaskType, string];
|
|
147
|
-
/**
|
|
148
|
-
* Interrupts a job and sets sets a job error (410), if 'throw'!=false.
|
|
149
|
-
* This method is called by the engine and not by an activity and is
|
|
150
|
-
* followed by a call to execute job completion/cleanup tasks
|
|
151
|
-
* associated with a job completion event.
|
|
152
|
-
*
|
|
153
|
-
* Todo: move most of this logic to the engine (too much logic for the store)
|
|
154
|
-
*/
|
|
155
100
|
interrupt(topic: string, jobId: string, options?: JobInterruptOptions): Promise<void>;
|
|
156
101
|
scrub(jobId: string): Promise<void>;
|
|
157
102
|
findJobs(queryString?: string, limit?: number, batchSize?: number, cursor?: string): Promise<[string, string[]]>;
|