@hotmeshio/hotmesh 0.0.41 → 0.0.43
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/errors.d.ts +1 -8
- package/build/modules/errors.js +1 -12
- package/build/modules/utils.js +1 -1
- package/build/package.json +1 -1
- package/build/services/activities/activity.d.ts +8 -1
- package/build/services/activities/activity.js +17 -12
- package/build/services/collator/index.d.ts +20 -2
- package/build/services/collator/index.js +41 -7
- package/build/services/durable/client.d.ts +2 -1
- package/build/services/durable/client.js +17 -3
- package/build/services/durable/factory.d.ts +0 -1
- package/build/services/durable/factory.js +0 -138
- package/build/services/durable/meshos.js +3 -0
- package/build/services/durable/worker.js +0 -15
- package/build/services/durable/workflow.d.ts +0 -9
- package/build/services/durable/workflow.js +0 -29
- package/build/services/engine/index.d.ts +1 -1
- package/build/services/engine/index.js +5 -8
- package/build/services/quorum/index.d.ts +5 -2
- package/build/services/quorum/index.js +32 -15
- package/build/services/store/clients/redis.js +1 -0
- package/build/services/store/index.d.ts +13 -1
- package/build/services/store/index.js +22 -6
- package/build/types/hotmesh.d.ts +1 -1
- package/build/types/job.d.ts +1 -0
- package/modules/enums.ts +4 -0
- package/modules/errors.ts +0 -15
- package/modules/utils.ts +1 -1
- package/package.json +1 -1
- package/services/activities/activity.ts +30 -15
- package/services/collator/index.ts +41 -8
- package/services/durable/client.ts +19 -4
- package/services/durable/factory.ts +0 -138
- package/services/durable/meshos.ts +3 -0
- package/services/durable/worker.ts +0 -16
- package/services/durable/workflow.ts +0 -32
- package/services/engine/index.ts +5 -6
- package/services/quorum/index.ts +35 -12
- package/services/store/clients/redis.ts +1 -0
- package/services/store/index.ts +25 -7
- package/types/hotmesh.ts +1 -1
- package/types/job.ts +1 -0
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* ERROR CODES:
|
|
8
8
|
* 594: waitforsignal
|
|
9
9
|
* 592: sleepFor
|
|
10
|
-
* 595: sleep (deprecated)
|
|
11
10
|
* 596, 597, 598: fatal
|
|
12
11
|
* 599: retry
|
|
13
12
|
*/
|
|
@@ -122,19 +121,6 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
122
121
|
maps:
|
|
123
122
|
index: '{$self.output.data.index}'
|
|
124
123
|
signals: '{$self.output.data.signals}'
|
|
125
|
-
595:
|
|
126
|
-
schema:
|
|
127
|
-
type: object
|
|
128
|
-
properties:
|
|
129
|
-
duration:
|
|
130
|
-
type: number
|
|
131
|
-
description: sleep duration in seconds
|
|
132
|
-
index:
|
|
133
|
-
type: number
|
|
134
|
-
description: the current index
|
|
135
|
-
maps:
|
|
136
|
-
duration: '{$self.output.data.duration}'
|
|
137
|
-
index: '{$self.output.data.index}'
|
|
138
124
|
592:
|
|
139
125
|
schema:
|
|
140
126
|
type: object
|
|
@@ -229,19 +215,6 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
229
215
|
maps:
|
|
230
216
|
index: '{$self.output.data.index}'
|
|
231
217
|
signals: '{$self.output.data.signals}'
|
|
232
|
-
595:
|
|
233
|
-
schema:
|
|
234
|
-
type: object
|
|
235
|
-
properties:
|
|
236
|
-
duration:
|
|
237
|
-
type: number
|
|
238
|
-
description: sleep duration in seconds
|
|
239
|
-
index:
|
|
240
|
-
type: number
|
|
241
|
-
description: the current index
|
|
242
|
-
maps:
|
|
243
|
-
duration: '{$self.output.data.duration}'
|
|
244
|
-
index: '{$self.output.data.index}'
|
|
245
218
|
592:
|
|
246
219
|
schema:
|
|
247
220
|
type: object
|
|
@@ -320,57 +293,6 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
320
293
|
maps:
|
|
321
294
|
duration: '{siga1.output.data.duration}'
|
|
322
295
|
|
|
323
|
-
siga595:
|
|
324
|
-
title: Signal In - Sleep before trying again
|
|
325
|
-
type: await
|
|
326
|
-
topic: ${app}.sleep.execute
|
|
327
|
-
input:
|
|
328
|
-
schema:
|
|
329
|
-
type: object
|
|
330
|
-
properties:
|
|
331
|
-
duration:
|
|
332
|
-
type: number
|
|
333
|
-
index:
|
|
334
|
-
type: number
|
|
335
|
-
workflowId:
|
|
336
|
-
type: string
|
|
337
|
-
parentWorkflowId:
|
|
338
|
-
type: string
|
|
339
|
-
originJobId:
|
|
340
|
-
type: string
|
|
341
|
-
maps:
|
|
342
|
-
duration: '{sigw1.output.data.duration}'
|
|
343
|
-
index: '{sigw1.output.data.index}'
|
|
344
|
-
parentWorkflowId:
|
|
345
|
-
'@pipe':
|
|
346
|
-
- ['{$job.metadata.jid}', '-s']
|
|
347
|
-
- ['{@string.concat}']
|
|
348
|
-
originJobId:
|
|
349
|
-
'@pipe':
|
|
350
|
-
- ['{t1.output.data.originJobId}', '{t1.output.data.originJobId}', '{$job.metadata.jid}']
|
|
351
|
-
- ['{@conditional.ternary}']
|
|
352
|
-
|
|
353
|
-
workflowId:
|
|
354
|
-
'@pipe':
|
|
355
|
-
- ['-', '{$job.metadata.jid}', '-$sleep', '{sig.output.metadata.dad}', '-', '{sigw1.output.data.index}']
|
|
356
|
-
- ['{@string.concat}']
|
|
357
|
-
output:
|
|
358
|
-
schema:
|
|
359
|
-
type: object
|
|
360
|
-
properties:
|
|
361
|
-
done:
|
|
362
|
-
type: boolean
|
|
363
|
-
maps:
|
|
364
|
-
done: '{sigw1.output.data.done}'
|
|
365
|
-
|
|
366
|
-
sigc595:
|
|
367
|
-
title: Signal In - Goto Activity siga1
|
|
368
|
-
type: cycle
|
|
369
|
-
ancestor: siga1
|
|
370
|
-
input:
|
|
371
|
-
maps:
|
|
372
|
-
duration: '{siga1.output.data.duration}'
|
|
373
|
-
|
|
374
296
|
siga592:
|
|
375
297
|
title: Signal In - Sleep For a duration and then cycle/goto
|
|
376
298
|
type: hook
|
|
@@ -464,56 +386,6 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
464
386
|
maps:
|
|
465
387
|
duration: '{a1.output.data.duration}'
|
|
466
388
|
|
|
467
|
-
a595:
|
|
468
|
-
title: Sleep before trying again
|
|
469
|
-
type: await
|
|
470
|
-
topic: ${app}.sleep.execute
|
|
471
|
-
input:
|
|
472
|
-
schema:
|
|
473
|
-
type: object
|
|
474
|
-
properties:
|
|
475
|
-
duration:
|
|
476
|
-
type: number
|
|
477
|
-
index:
|
|
478
|
-
type: number
|
|
479
|
-
workflowId:
|
|
480
|
-
type: string
|
|
481
|
-
parentWorkflowId:
|
|
482
|
-
type: string
|
|
483
|
-
originJobId:
|
|
484
|
-
type: string
|
|
485
|
-
maps:
|
|
486
|
-
duration: '{w1.output.data.duration}'
|
|
487
|
-
index: '{w1.output.data.index}'
|
|
488
|
-
parentWorkflowId:
|
|
489
|
-
'@pipe':
|
|
490
|
-
- ['{$job.metadata.jid}', '-s']
|
|
491
|
-
- ['{@string.concat}']
|
|
492
|
-
originJobId:
|
|
493
|
-
'@pipe':
|
|
494
|
-
- ['{t1.output.data.originJobId}', '{t1.output.data.originJobId}', '{$job.metadata.jid}']
|
|
495
|
-
- ['{@conditional.ternary}']
|
|
496
|
-
workflowId:
|
|
497
|
-
'@pipe':
|
|
498
|
-
- ['-', '{$job.metadata.jid}', '-$sleep-', '{w1.output.data.index}']
|
|
499
|
-
- ['{@string.concat}']
|
|
500
|
-
output:
|
|
501
|
-
schema:
|
|
502
|
-
type: object
|
|
503
|
-
properties:
|
|
504
|
-
done:
|
|
505
|
-
type: boolean
|
|
506
|
-
maps:
|
|
507
|
-
done: '{w1.output.data.done}'
|
|
508
|
-
|
|
509
|
-
c595:
|
|
510
|
-
title: Goto Activity a1
|
|
511
|
-
type: cycle
|
|
512
|
-
ancestor: a1
|
|
513
|
-
input:
|
|
514
|
-
maps:
|
|
515
|
-
duration: '{a1.output.data.duration}'
|
|
516
|
-
|
|
517
389
|
a592:
|
|
518
390
|
title: Sleep For a duration and then cycle/goto
|
|
519
391
|
type: hook
|
|
@@ -571,9 +443,6 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
571
443
|
- to: siga594
|
|
572
444
|
conditions:
|
|
573
445
|
code: 594
|
|
574
|
-
- to: siga595
|
|
575
|
-
conditions:
|
|
576
|
-
code: 595
|
|
577
446
|
- to: siga592
|
|
578
447
|
conditions:
|
|
579
448
|
code: 592
|
|
@@ -582,8 +451,6 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
582
451
|
code: 599
|
|
583
452
|
siga594:
|
|
584
453
|
- to: sigc594
|
|
585
|
-
siga595:
|
|
586
|
-
- to: sigc595
|
|
587
454
|
siga592:
|
|
588
455
|
- to: sigc592
|
|
589
456
|
siga599:
|
|
@@ -594,9 +461,6 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
594
461
|
- to: a594
|
|
595
462
|
conditions:
|
|
596
463
|
code: 594
|
|
597
|
-
- to: a595
|
|
598
|
-
conditions:
|
|
599
|
-
code: 595
|
|
600
464
|
- to: a592
|
|
601
465
|
conditions:
|
|
602
466
|
code: 592
|
|
@@ -608,8 +472,6 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
608
472
|
code: [200, 598, 597, 596]
|
|
609
473
|
a594:
|
|
610
474
|
- to: c594
|
|
611
|
-
a595:
|
|
612
|
-
- to: c595
|
|
613
475
|
a592:
|
|
614
476
|
- to: c592
|
|
615
477
|
a599:
|
|
@@ -295,6 +295,9 @@ export class MeshOSService {
|
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
static generateSearchQuery(query: FindWhereQuery[]) {
|
|
298
|
+
if (!Array.isArray(query) || query.length === 0) {
|
|
299
|
+
return '*';
|
|
300
|
+
}
|
|
298
301
|
const my = new this();
|
|
299
302
|
let queryString = query.map(q => {
|
|
300
303
|
const { field, is, value, type } = q;
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
DurableIncompleteSignalError,
|
|
4
4
|
DurableMaxedError,
|
|
5
5
|
DurableRetryError,
|
|
6
|
-
DurableSleepError,
|
|
7
6
|
DurableSleepForError,
|
|
8
7
|
DurableTimeoutError,
|
|
9
8
|
DurableWaitForSignalError} from '../../modules/errors';
|
|
@@ -249,21 +248,6 @@ export class WorkerService {
|
|
|
249
248
|
}
|
|
250
249
|
} as StreamDataResponse;
|
|
251
250
|
|
|
252
|
-
//deprecated format; not an error...just a trigger to sleep
|
|
253
|
-
} else if (err instanceof DurableSleepError) {
|
|
254
|
-
return {
|
|
255
|
-
status: StreamStatus.SUCCESS,
|
|
256
|
-
code: err.code,
|
|
257
|
-
metadata: { ...data.metadata },
|
|
258
|
-
data: {
|
|
259
|
-
code: err.code,
|
|
260
|
-
message: JSON.stringify({ duration: err.duration, index: err.index, dimension: err.dimension }),
|
|
261
|
-
duration: err.duration,
|
|
262
|
-
index: err.index,
|
|
263
|
-
dimension: err.dimension
|
|
264
|
-
}
|
|
265
|
-
} as StreamDataResponse;
|
|
266
|
-
|
|
267
251
|
//not an error...just a trigger to wait for a signal
|
|
268
252
|
} else if (err instanceof DurableWaitForSignalError) {
|
|
269
253
|
return {
|
|
@@ -2,7 +2,6 @@ import ms from 'ms';
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
DurableIncompleteSignalError,
|
|
5
|
-
DurableSleepError,
|
|
6
5
|
DurableSleepForError,
|
|
7
6
|
DurableWaitForSignalError } from '../../modules/errors';
|
|
8
7
|
import { KeyService, KeyType } from '../../modules/key';
|
|
@@ -376,37 +375,6 @@ export class WorkflowService {
|
|
|
376
375
|
return seconds;
|
|
377
376
|
}
|
|
378
377
|
|
|
379
|
-
/**
|
|
380
|
-
* Sleeps the workflow for a duration. As the function is reentrant,
|
|
381
|
-
* upon reentry, the function will traverse prior execution paths up
|
|
382
|
-
* until the sleep command and then resume execution from that point.
|
|
383
|
-
* @param {string} duration - for example: '1 minute', '2 hours', '3 days'
|
|
384
|
-
* @returns {Promise<number>}
|
|
385
|
-
* @deprecated - use `sleepFor` instead
|
|
386
|
-
*/
|
|
387
|
-
static async sleep(duration: string): Promise<number> {
|
|
388
|
-
const seconds = ms(duration) / 1000;
|
|
389
|
-
|
|
390
|
-
const store = asyncLocalStorage.getStore();
|
|
391
|
-
const COUNTER = store.get('counter');
|
|
392
|
-
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
393
|
-
const workflowId = store.get('workflowId');
|
|
394
|
-
const workflowTopic = store.get('workflowTopic');
|
|
395
|
-
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
396
|
-
const namespace = store.get('namespace');
|
|
397
|
-
const sleepJobId = `-${workflowId}-$sleep${workflowDimension}-${execIndex}`;
|
|
398
|
-
|
|
399
|
-
try {
|
|
400
|
-
const hotMeshClient = await WorkerService.getHotMesh(workflowTopic, { namespace });
|
|
401
|
-
await hotMeshClient.getState(`${hotMeshClient.appId}.sleep.execute`, sleepJobId);
|
|
402
|
-
//if no error is thrown, we've already slept, return the delay
|
|
403
|
-
return seconds;
|
|
404
|
-
} catch (e) {
|
|
405
|
-
// spawn a new sleep job if error code 595 is thrown by the worker)
|
|
406
|
-
throw new DurableSleepError(workflowId, seconds, execIndex, workflowDimension);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
378
|
/**
|
|
411
379
|
* Waits for a signal to awaken
|
|
412
380
|
* @param {string[]} signals - the signals to wait for
|
package/services/engine/index.ts
CHANGED
|
@@ -332,6 +332,7 @@ class EngineService {
|
|
|
332
332
|
});
|
|
333
333
|
const context: PartialJobState = {
|
|
334
334
|
metadata: {
|
|
335
|
+
guid: streamData.metadata.guid,
|
|
335
336
|
jid: streamData.metadata.jid,
|
|
336
337
|
gid: streamData.metadata.gid,
|
|
337
338
|
dad: streamData.metadata.dad,
|
|
@@ -490,17 +491,15 @@ class EngineService {
|
|
|
490
491
|
};
|
|
491
492
|
return await this.router.publishMessage(null, streamData) as string;
|
|
492
493
|
}
|
|
493
|
-
async hookTime(jobId: string, gId: string,
|
|
494
|
-
if (type === 'interrupt') {
|
|
494
|
+
async hookTime(jobId: string, gId: string, topicOrActivity: string, type?: WorkListTaskType): Promise<string | void> {
|
|
495
|
+
if (type === 'interrupt' || type === 'expire') {
|
|
495
496
|
return await this.interrupt(
|
|
496
|
-
|
|
497
|
+
topicOrActivity,
|
|
497
498
|
jobId,
|
|
498
499
|
{ suppress: true, expire: 1 },
|
|
499
500
|
);
|
|
500
|
-
} else if (type === 'expire') {
|
|
501
|
-
return await this.store.expireJob(jobId, 1);
|
|
502
501
|
}
|
|
503
|
-
const [aid, ...dimensions] =
|
|
502
|
+
const [aid, ...dimensions] = topicOrActivity.split(',');
|
|
504
503
|
const dad = `,${dimensions.join(',')}`;
|
|
505
504
|
const streamData: StreamData = {
|
|
506
505
|
type: StreamDataType.TIMEHOOK,
|
package/services/quorum/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HMSH_ACTIVATION_MAX_RETRY, HMSH_QUORUM_DELAY_MS } from '../../modules/enums';
|
|
2
2
|
import { identifyRedisType, sleepFor } from '../../modules/utils';
|
|
3
3
|
import { CompilerService } from '../compiler';
|
|
4
4
|
import { EngineService } from '../engine';
|
|
@@ -10,6 +10,7 @@ import { SubService } from '../sub';
|
|
|
10
10
|
import { IORedisSubService as IORedisSub } from '../sub/clients/ioredis';
|
|
11
11
|
import { RedisSubService as RedisSub } from '../sub/clients/redis';
|
|
12
12
|
import { CacheMode } from '../../types/cache';
|
|
13
|
+
import { HotMeshConfig, KeyType } from '../../types/hotmesh';
|
|
13
14
|
import { RedisClientType as IORedisClientType } from '../../types/ioredisclient';
|
|
14
15
|
import {
|
|
15
16
|
QuorumMessage,
|
|
@@ -18,13 +19,9 @@ import {
|
|
|
18
19
|
SubscriptionCallback,
|
|
19
20
|
ThrottleMessage
|
|
20
21
|
} from '../../types/quorum';
|
|
21
|
-
import { HotMeshApps, HotMeshConfig } from '../../types/hotmesh';
|
|
22
22
|
import { RedisClient, RedisMulti } from '../../types/redis';
|
|
23
23
|
import { RedisClientType } from '../../types/redisclient';
|
|
24
24
|
|
|
25
|
-
//wait time to see if quorum is reached
|
|
26
|
-
const QUORUM_DELAY = 250;
|
|
27
|
-
|
|
28
25
|
class QuorumService {
|
|
29
26
|
namespace: string;
|
|
30
27
|
appId: string;
|
|
@@ -59,8 +56,18 @@ class QuorumService {
|
|
|
59
56
|
//note: `quorum` shares/re-uses the engine's `store`/`sub` Redis clients
|
|
60
57
|
await instance.initStoreChannel(config.engine.store);
|
|
61
58
|
await instance.initSubChannel(config.engine.sub);
|
|
62
|
-
|
|
63
|
-
await instance.subscribe.subscribe(
|
|
59
|
+
//general quorum subscription
|
|
60
|
+
await instance.subscribe.subscribe(
|
|
61
|
+
KeyType.QUORUM,
|
|
62
|
+
instance.subscriptionHandler(),
|
|
63
|
+
appId
|
|
64
|
+
);
|
|
65
|
+
//app-specific quorum subscription (used for pubsub one-time request/response)
|
|
66
|
+
await instance.subscribe.subscribe(
|
|
67
|
+
KeyType.QUORUM,
|
|
68
|
+
instance.subscriptionHandler(),
|
|
69
|
+
appId, instance.guid
|
|
70
|
+
);
|
|
64
71
|
|
|
65
72
|
instance.engine.processWebHooks();
|
|
66
73
|
instance.engine.processTimeHooks();
|
|
@@ -152,7 +159,7 @@ class QuorumService {
|
|
|
152
159
|
);
|
|
153
160
|
}
|
|
154
161
|
|
|
155
|
-
async requestQuorum(delay =
|
|
162
|
+
async requestQuorum(delay = HMSH_QUORUM_DELAY_MS, details = false): Promise<number> {
|
|
156
163
|
const quorum = this.quorum;
|
|
157
164
|
this.quorum = 0;
|
|
158
165
|
this.profiles.length = 0;
|
|
@@ -188,7 +195,7 @@ class QuorumService {
|
|
|
188
195
|
|
|
189
196
|
|
|
190
197
|
// ************* COMPILER METHODS *************
|
|
191
|
-
async rollCall(delay =
|
|
198
|
+
async rollCall(delay = HMSH_QUORUM_DELAY_MS): Promise<QuorumProfile[]> {
|
|
192
199
|
await this.requestQuorum(delay, true);
|
|
193
200
|
const targetStreams = [];
|
|
194
201
|
const multi = this.store.getMulti();
|
|
@@ -209,10 +216,20 @@ class QuorumService {
|
|
|
209
216
|
});
|
|
210
217
|
return this.profiles;
|
|
211
218
|
}
|
|
212
|
-
|
|
219
|
+
/**
|
|
220
|
+
* request a quorum; if successful activate the app version
|
|
221
|
+
*/
|
|
222
|
+
async activate(version: string, delay = HMSH_QUORUM_DELAY_MS, count = 0): Promise<boolean> {
|
|
213
223
|
version = version.toString();
|
|
224
|
+
const canActivate = await this.store.reserveScoutRole('activate', Math.ceil(delay * 6 / 1000) + 1);
|
|
225
|
+
if (!canActivate) {
|
|
226
|
+
//another engine is already activating the app version
|
|
227
|
+
this.logger.debug('quorum-activation-awaiting', { version });
|
|
228
|
+
await sleepFor(delay * 6);
|
|
229
|
+
const app = await this.store.getApp(this.appId, true);
|
|
230
|
+
return app?.active == true && app?.version === version;
|
|
231
|
+
}
|
|
214
232
|
const config = await this.engine.getVID();
|
|
215
|
-
//request a quorum to activate the version
|
|
216
233
|
await this.requestQuorum(delay);
|
|
217
234
|
const q1 = await this.requestQuorum(delay);
|
|
218
235
|
const q2 = await this.requestQuorum(delay);
|
|
@@ -225,6 +242,7 @@ class QuorumService {
|
|
|
225
242
|
this.appId
|
|
226
243
|
);
|
|
227
244
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
245
|
+
await this.store.releaseScoutRole('activate');
|
|
228
246
|
//confirm we received the activation message
|
|
229
247
|
if (this.engine.untilVersion === version) {
|
|
230
248
|
this.logger.info('quorum-activation-succeeded', { version });
|
|
@@ -236,7 +254,12 @@ class QuorumService {
|
|
|
236
254
|
throw new Error(`UntilVersion Not Received. Version ${version} not activated`);
|
|
237
255
|
}
|
|
238
256
|
} else {
|
|
239
|
-
this.logger.
|
|
257
|
+
this.logger.warn('quorum-rollcall-error', { q1, q2, q3, count });
|
|
258
|
+
this.store.releaseScoutRole('activate');
|
|
259
|
+
if (count < HMSH_ACTIVATION_MAX_RETRY) {
|
|
260
|
+
//increase the delay (give the quorum time to respond) and try again
|
|
261
|
+
return await this.activate(version, delay * 2, count + 1);
|
|
262
|
+
}
|
|
240
263
|
throw new Error(`Quorum not reached. Version ${version} not activated.`);
|
|
241
264
|
}
|
|
242
265
|
}
|
package/services/store/index.ts
CHANGED
|
@@ -45,6 +45,7 @@ abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
45
45
|
appId: string
|
|
46
46
|
logger: ILogger;
|
|
47
47
|
commands: Record<string, string> = {
|
|
48
|
+
set: 'set',
|
|
48
49
|
setnx: 'setnx',
|
|
49
50
|
del: 'del',
|
|
50
51
|
expire: 'expire',
|
|
@@ -178,14 +179,16 @@ abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
178
179
|
* check for and process work items in the
|
|
179
180
|
* time and signal task queues.
|
|
180
181
|
*/
|
|
181
|
-
async reserveScoutRole(scoutType: 'time' | 'signal', delay = HMSH_SCOUT_INTERVAL_SECONDS): Promise<boolean> {
|
|
182
|
+
async reserveScoutRole(scoutType: 'time' | 'signal' | 'activate', delay = HMSH_SCOUT_INTERVAL_SECONDS): Promise<boolean> {
|
|
182
183
|
const key = this.mintKey(KeyType.WORK_ITEMS, { appId: this.appId, scoutType });
|
|
183
|
-
const success = await this.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
184
|
+
const success = await this.exec('SET', key, `${scoutType}:${formatISODate(new Date())}`, 'NX', 'EX', `${delay - 1}`);
|
|
185
|
+
return this.isSuccessful(success);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async releaseScoutRole(scoutType: 'time' | 'signal' | 'activate'): Promise<boolean> {
|
|
189
|
+
const key = this.mintKey(KeyType.WORK_ITEMS, { appId: this.appId, scoutType });
|
|
190
|
+
const success = await this.exec('DEL', key);
|
|
191
|
+
return this.isSuccessful(success);
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
async getSettings(bCreate = false): Promise<HotMeshSettings> {
|
|
@@ -586,6 +589,10 @@ abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
586
589
|
}
|
|
587
590
|
}
|
|
588
591
|
|
|
592
|
+
/**
|
|
593
|
+
* collate is a generic method for incrementing a value in a hash
|
|
594
|
+
* in order to track their progress during processing.
|
|
595
|
+
*/
|
|
589
596
|
async collate(jobId: string, activityId: string, amount: number, dIds: StringStringType, multi? : U): Promise<number> {
|
|
590
597
|
const jobKey = this.mintKey(KeyType.JOB_STATE, { appId: this.appId, jobId });
|
|
591
598
|
const collationKey = `${activityId}/output/metadata/as`; //activity state
|
|
@@ -600,6 +607,17 @@ abstract class StoreService<T, U extends AbstractRedisClient> {
|
|
|
600
607
|
return await (multi || this.redisClient)[this.commands.hincrbyfloat](jobKey, targetId, amount);
|
|
601
608
|
}
|
|
602
609
|
|
|
610
|
+
/**
|
|
611
|
+
* synthentic collation affects those activities in the graph
|
|
612
|
+
* that represent the synthetic DAG that was materialized during compilation;
|
|
613
|
+
* Synthetic targeting ensures that re-entry due to failure can be distinguished from
|
|
614
|
+
* purposeful re-entry.
|
|
615
|
+
*/
|
|
616
|
+
async collateSynthetic(jobId: string, guid: string, amount: number, multi? : U): Promise<number> {
|
|
617
|
+
const jobKey = this.mintKey(KeyType.JOB_STATE, { appId: this.appId, jobId });
|
|
618
|
+
return await (multi || this.redisClient)[this.commands.hincrbyfloat](jobKey, guid, amount);
|
|
619
|
+
}
|
|
620
|
+
|
|
603
621
|
async setStateNX(jobId: string, appId: string): Promise<boolean> {
|
|
604
622
|
const hashKey = this.mintKey(KeyType.JOB_STATE, { appId, jobId });
|
|
605
623
|
const result = await this.redisClient[this.commands.hsetnx](hashKey, ':', '1');
|
package/types/hotmesh.ts
CHANGED
|
@@ -44,7 +44,7 @@ type KeyStoreParams = {
|
|
|
44
44
|
facet?: string; //data path starting at root with values separated by colons (e.g. "object/type:bar")
|
|
45
45
|
topic?: string; //topic name (e.g., "foo" or "" for top-level)
|
|
46
46
|
timeValue?: number; //time value (rounded to minute) (for delete range)
|
|
47
|
-
scoutType?: 'signal' | 'time'; //a single member of the quorum serves as the 'scout' for the group, triaging tasks for the collective
|
|
47
|
+
scoutType?: 'signal' | 'time' | 'activate'; //a single member of the quorum serves as the 'scout' for the group, triaging tasks for the collective
|
|
48
48
|
};
|
|
49
49
|
|
|
50
50
|
type HotMesh = typeof HotMeshService;
|
package/types/job.ts
CHANGED
|
@@ -8,6 +8,7 @@ type ActivityData = {
|
|
|
8
8
|
|
|
9
9
|
type JobMetadata = {
|
|
10
10
|
key?: string; //job_key
|
|
11
|
+
guid?: string; //system assigned guid that corresponds to the transition message guid that spawned reentry
|
|
11
12
|
gid: string; //system assigned guid; ensured created/deleted/created jobs are unique
|
|
12
13
|
jid: string; //job_id (jid+dad+aid) is composite key for activity
|
|
13
14
|
dad: string; //dimensional address for the activity (,0,0,1)
|