@hotmeshio/hotmesh 0.0.33 → 0.0.35
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 +30 -18
- package/build/modules/enums.d.ts +22 -0
- package/build/modules/enums.js +29 -0
- package/build/modules/errors.d.ts +10 -2
- package/build/modules/errors.js +14 -3
- package/build/modules/key.d.ts +16 -15
- package/build/modules/key.js +18 -15
- package/build/modules/utils.d.ts +1 -0
- package/build/modules/utils.js +6 -1
- package/build/package.json +4 -1
- package/build/services/activities/activity.d.ts +5 -0
- package/build/services/activities/activity.js +27 -6
- package/build/services/activities/await.js +11 -3
- package/build/services/activities/cycle.js +10 -2
- package/build/services/activities/hook.js +8 -2
- package/build/services/activities/index.d.ts +2 -2
- package/build/services/activities/index.js +2 -2
- package/build/services/activities/interrupt.d.ts +16 -0
- package/build/services/activities/interrupt.js +129 -0
- package/build/services/activities/signal.js +9 -2
- package/build/services/activities/trigger.d.ts +4 -0
- package/build/services/activities/trigger.js +14 -4
- package/build/services/activities/worker.js +10 -2
- package/build/services/collator/index.d.ts +4 -0
- package/build/services/collator/index.js +8 -0
- package/build/services/compiler/deployer.js +1 -3
- package/build/services/connector/index.js +2 -3
- package/build/services/durable/client.js +9 -6
- package/build/services/durable/factory.js +65 -284
- package/build/services/durable/handle.d.ts +37 -0
- package/build/services/durable/handle.js +52 -9
- package/build/services/durable/index.d.ts +5 -0
- package/build/services/durable/index.js +10 -0
- package/build/services/durable/meshos.js +3 -6
- package/build/services/durable/worker.js +11 -5
- package/build/services/durable/workflow.d.ts +24 -0
- package/build/services/durable/workflow.js +56 -1
- package/build/services/engine/index.d.ts +14 -6
- package/build/services/engine/index.js +52 -27
- package/build/services/hotmesh/index.d.ts +6 -2
- package/build/services/hotmesh/index.js +23 -5
- package/build/services/quorum/index.d.ts +1 -0
- package/build/services/quorum/index.js +10 -0
- package/build/services/signaler/stream.js +25 -29
- package/build/services/store/index.d.ts +40 -4
- package/build/services/store/index.js +114 -9
- package/build/services/task/index.d.ts +5 -4
- package/build/services/task/index.js +12 -14
- package/build/types/activity.d.ts +35 -5
- package/build/types/durable.d.ts +4 -0
- package/build/types/index.d.ts +1 -1
- package/build/types/job.d.ts +18 -1
- package/build/types/quorum.d.ts +11 -7
- package/build/types/stream.d.ts +4 -1
- package/build/types/stream.js +2 -0
- package/modules/enums.ts +32 -0
- package/modules/errors.ts +24 -9
- package/modules/key.ts +4 -1
- package/modules/utils.ts +5 -0
- package/package.json +4 -1
- package/services/activities/activity.ts +34 -8
- package/services/activities/await.ts +11 -4
- package/services/activities/cycle.ts +10 -3
- package/services/activities/hook.ts +8 -3
- package/services/activities/index.ts +2 -2
- package/services/activities/interrupt.ts +159 -0
- package/services/activities/signal.ts +9 -3
- package/services/activities/trigger.ts +21 -5
- package/services/activities/worker.ts +10 -3
- package/services/collator/index.ts +10 -1
- package/services/compiler/deployer.ts +1 -3
- package/services/connector/index.ts +3 -5
- package/services/durable/client.ts +10 -7
- package/services/durable/factory.ts +65 -284
- package/services/durable/handle.ts +55 -9
- package/services/durable/index.ts +11 -0
- package/services/durable/meshos.ts +3 -7
- package/services/durable/worker.ts +11 -5
- package/services/durable/workflow.ts +66 -2
- package/services/engine/index.ts +74 -26
- package/services/hotmesh/index.ts +28 -6
- package/services/quorum/index.ts +9 -0
- package/services/signaler/stream.ts +28 -25
- package/services/store/index.ts +119 -11
- package/services/task/index.ts +18 -18
- package/types/activity.ts +38 -8
- package/types/durable.ts +8 -4
- package/types/index.ts +1 -1
- package/types/job.ts +30 -1
- package/types/quorum.ts +13 -8
- package/types/stream.ts +3 -0
- package/build/services/activities/iterate.d.ts +0 -9
- package/build/services/activities/iterate.js +0 -13
- package/services/activities/iterate.ts +0 -26
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { MetricTypes } from "./stats";
|
|
2
2
|
import { StreamRetryPolicy } from "./stream";
|
|
3
|
-
type ActivityExecutionType = 'trigger' | 'await' | 'worker' | 'activity' | 'emit' | '
|
|
3
|
+
type ActivityExecutionType = 'trigger' | 'await' | 'worker' | 'activity' | 'emit' | 'interrupt' | 'cycle' | 'signal' | 'hook';
|
|
4
4
|
type Consumes = Record<string, string[]>;
|
|
5
5
|
interface BaseActivity {
|
|
6
6
|
title?: string;
|
|
@@ -32,13 +32,33 @@ interface Measure {
|
|
|
32
32
|
target: string;
|
|
33
33
|
}
|
|
34
34
|
interface TriggerActivityStats {
|
|
35
|
+
/**
|
|
36
|
+
* parent job; including this allows the parent's
|
|
37
|
+
* expiration/interruption events to cascade; set
|
|
38
|
+
* `expire` in the YAML for the dependent graph
|
|
39
|
+
* to 0 and provide the parent for dependent,
|
|
40
|
+
* cascading interruption and cleanup
|
|
41
|
+
*/
|
|
42
|
+
parent?: string;
|
|
35
43
|
id?: {
|
|
36
44
|
[key: string]: unknown;
|
|
37
45
|
} | string;
|
|
38
46
|
key?: {
|
|
39
47
|
[key: string]: unknown;
|
|
40
48
|
} | string;
|
|
49
|
+
/**
|
|
50
|
+
* @deprecated
|
|
51
|
+
* return 'infinity' to disable; default behavior
|
|
52
|
+
* is to always segment keys by time to ensure
|
|
53
|
+
* indexes (Redis LIST) never grow unbounded
|
|
54
|
+
* as a default behavior; for now, 5m is default
|
|
55
|
+
* and infinity can be set to override
|
|
56
|
+
*/
|
|
41
57
|
granularity?: string;
|
|
58
|
+
/**
|
|
59
|
+
* @deprecated
|
|
60
|
+
* what to capture
|
|
61
|
+
*/
|
|
42
62
|
measures?: Measure[];
|
|
43
63
|
}
|
|
44
64
|
interface TriggerActivity extends BaseActivity {
|
|
@@ -74,10 +94,20 @@ interface SignalActivity extends BaseActivity {
|
|
|
74
94
|
status?: string;
|
|
75
95
|
code?: number;
|
|
76
96
|
}
|
|
77
|
-
interface
|
|
78
|
-
type: '
|
|
97
|
+
interface InterruptActivity extends BaseActivity {
|
|
98
|
+
type: 'interrupt';
|
|
99
|
+
/** Optional Reason; will be used as the error `message` when thrown; NOTE: 410 is the error `code` */
|
|
100
|
+
reason?: string;
|
|
101
|
+
/** default is `true` (throw JobInterrupted error upon interrupting) */
|
|
102
|
+
throw?: boolean;
|
|
103
|
+
/** TODO: // default is `false` (do not interrupt child jobs) */
|
|
104
|
+
descend?: boolean;
|
|
105
|
+
/** target job id (if not present the current job will be targeted) */
|
|
106
|
+
target?: string;
|
|
107
|
+
/** topic to publish the interrupt message (if not present the current job topic will be used) */
|
|
108
|
+
topic?: string;
|
|
79
109
|
}
|
|
80
|
-
type ActivityType = BaseActivity | TriggerActivity | AwaitActivity | WorkerActivity |
|
|
110
|
+
type ActivityType = BaseActivity | TriggerActivity | AwaitActivity | WorkerActivity | InterruptActivity | HookActivity | SignalActivity | CycleActivity;
|
|
81
111
|
type ActivityData = Record<string, any>;
|
|
82
112
|
type ActivityMetadata = {
|
|
83
113
|
aid: string;
|
|
@@ -103,4 +133,4 @@ type ActivityDataType = {
|
|
|
103
133
|
hook?: Record<string, unknown>;
|
|
104
134
|
};
|
|
105
135
|
type ActivityLeg = 1 | 2;
|
|
106
|
-
export { ActivityContext, ActivityData, ActivityDataType, ActivityDuplex, ActivityLeg, ActivityMetadata, ActivityType, Consumes, TriggerActivityStats, AwaitActivity, CycleActivity, HookActivity, SignalActivity, BaseActivity,
|
|
136
|
+
export { ActivityContext, ActivityData, ActivityDataType, ActivityDuplex, ActivityLeg, ActivityMetadata, ActivityType, Consumes, TriggerActivityStats, AwaitActivity, CycleActivity, HookActivity, SignalActivity, BaseActivity, InterruptActivity, TriggerActivity, WorkerActivity };
|
package/build/types/durable.d.ts
CHANGED
|
@@ -52,10 +52,12 @@ type WorkflowOptions = {
|
|
|
52
52
|
entity?: string;
|
|
53
53
|
workflowName?: string;
|
|
54
54
|
parentWorkflowId?: string;
|
|
55
|
+
originJobId?: string;
|
|
55
56
|
workflowTrace?: string;
|
|
56
57
|
workflowSpan?: string;
|
|
57
58
|
search?: WorkflowSearchOptions;
|
|
58
59
|
config?: WorkflowConfig;
|
|
60
|
+
expire?: number;
|
|
59
61
|
};
|
|
60
62
|
type HookOptions = {
|
|
61
63
|
namespace?: string;
|
|
@@ -83,6 +85,8 @@ type WorkflowDataType = {
|
|
|
83
85
|
arguments: any[];
|
|
84
86
|
workflowId: string;
|
|
85
87
|
workflowTopic: string;
|
|
88
|
+
workflowDimension?: string;
|
|
89
|
+
originJobId?: string;
|
|
86
90
|
};
|
|
87
91
|
type MeshOSClassConfig = {
|
|
88
92
|
namespace: string;
|
package/build/types/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { ActivityType, ActivityDataType, ActivityContext, ActivityData, ActivityDuplex, ActivityLeg, ActivityMetadata, Consumes, AwaitActivity, BaseActivity, CycleActivity, HookActivity, WorkerActivity,
|
|
1
|
+
export { ActivityType, ActivityDataType, ActivityContext, ActivityData, ActivityDuplex, ActivityLeg, ActivityMetadata, Consumes, AwaitActivity, BaseActivity, CycleActivity, HookActivity, WorkerActivity, InterruptActivity, SignalActivity, TriggerActivity, TriggerActivityStats } from './activity';
|
|
2
2
|
export { App, AppVID, AppTransitions, AppSubscriptions } from './app';
|
|
3
3
|
export { AsyncSignal } from './async';
|
|
4
4
|
export { CacheMode } from './cache';
|
package/build/types/job.d.ts
CHANGED
|
@@ -39,6 +39,18 @@ type JobState = {
|
|
|
39
39
|
errors: ActivityData;
|
|
40
40
|
};
|
|
41
41
|
};
|
|
42
|
+
type JobInterruptOptions = {
|
|
43
|
+
/** Optional reason when throwing the error */
|
|
44
|
+
reason?: string;
|
|
45
|
+
/** default is `true` when `undefined` (throw JobInterrupted/410 error) */
|
|
46
|
+
throw?: boolean;
|
|
47
|
+
/** default behavior is `false` when `undefined` (do NOT interrupt child jobs) */
|
|
48
|
+
descend?: boolean;
|
|
49
|
+
/** default is false; if true, errors related to inactivation (like overage...already inactive) are suppressed/ignored */
|
|
50
|
+
suppress?: boolean;
|
|
51
|
+
/** how long to wait in seconds before fully expiring/removing the hash from Redis; the job is inactive, but can remain in the cache indefinitely. minimum 1 second.*/
|
|
52
|
+
expire?: number;
|
|
53
|
+
};
|
|
42
54
|
type JobOutput = {
|
|
43
55
|
metadata: JobMetadata;
|
|
44
56
|
data: JobData;
|
|
@@ -47,4 +59,9 @@ type PartialJobState = {
|
|
|
47
59
|
metadata: JobMetadata | Pick<JobMetadata, 'jid' | 'dad' | 'aid'>;
|
|
48
60
|
data: JobData;
|
|
49
61
|
};
|
|
50
|
-
|
|
62
|
+
type JobCompletionOptions = {
|
|
63
|
+
emit?: boolean;
|
|
64
|
+
interrupt?: boolean;
|
|
65
|
+
expire?: number;
|
|
66
|
+
};
|
|
67
|
+
export { JobCompletionOptions, JobInterruptOptions, JobData, JobsData, JobMetadata, JobOutput, JobState, JobStatus, PartialJobState, };
|
package/build/types/quorum.d.ts
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
import { JobOutput } from "./job";
|
|
2
|
-
/**
|
|
3
|
-
* The types in this file are used to define those messages that are sent
|
|
4
|
-
* to hotmesh client instances when a new version is about to be activated.
|
|
5
|
-
* These messages serve to coordinate the cache invalidation and switch-over
|
|
6
|
-
* to the new version without any downtime and a coordinating parent server.
|
|
7
|
-
*/
|
|
8
|
-
export type QuorumMessage = PingMessage | PongMessage | ActivateMessage | WorkMessage | JobMessage | ThrottleMessage;
|
|
9
2
|
export interface PingMessage {
|
|
10
3
|
type: 'ping';
|
|
11
4
|
originator: string;
|
|
@@ -14,6 +7,10 @@ export interface WorkMessage {
|
|
|
14
7
|
type: 'work';
|
|
15
8
|
originator: string;
|
|
16
9
|
}
|
|
10
|
+
export interface CronMessage {
|
|
11
|
+
type: 'cron';
|
|
12
|
+
originator: string;
|
|
13
|
+
}
|
|
17
14
|
export interface PongMessage {
|
|
18
15
|
type: 'pong';
|
|
19
16
|
originator: string;
|
|
@@ -44,3 +41,10 @@ export interface SubscriptionCallback {
|
|
|
44
41
|
export interface QuorumMessageCallback {
|
|
45
42
|
(topic: string, message: QuorumMessage): void;
|
|
46
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* The types in this file are used to define those messages that are sent
|
|
46
|
+
* to hotmesh client instances when a new version is about to be activated.
|
|
47
|
+
* These messages serve to coordinate the cache invalidation and switch-over
|
|
48
|
+
* to the new version without any downtime and a coordinating parent server.
|
|
49
|
+
*/
|
|
50
|
+
export type QuorumMessage = PingMessage | PongMessage | ActivateMessage | WorkMessage | JobMessage | ThrottleMessage | CronMessage;
|
package/build/types/stream.d.ts
CHANGED
|
@@ -22,10 +22,13 @@ export declare enum StreamDataType {
|
|
|
22
22
|
RESULT = "result",
|
|
23
23
|
WORKER = "worker",
|
|
24
24
|
RESPONSE = "response",
|
|
25
|
-
TRANSITION = "transition"
|
|
25
|
+
TRANSITION = "transition",
|
|
26
|
+
SIGNAL = "signal",
|
|
27
|
+
INTERRUPT = "interrupt"
|
|
26
28
|
}
|
|
27
29
|
export interface StreamData {
|
|
28
30
|
metadata: {
|
|
31
|
+
guid: string;
|
|
29
32
|
topic?: string;
|
|
30
33
|
jid?: string;
|
|
31
34
|
dad?: string;
|
package/build/types/stream.js
CHANGED
|
@@ -16,6 +16,8 @@ var StreamDataType;
|
|
|
16
16
|
StreamDataType["WORKER"] = "worker";
|
|
17
17
|
StreamDataType["RESPONSE"] = "response";
|
|
18
18
|
StreamDataType["TRANSITION"] = "transition";
|
|
19
|
+
StreamDataType["SIGNAL"] = "signal";
|
|
20
|
+
StreamDataType["INTERRUPT"] = "interrupt";
|
|
19
21
|
})(StreamDataType = exports.StreamDataType || (exports.StreamDataType = {}));
|
|
20
22
|
var StreamRole;
|
|
21
23
|
(function (StreamRole) {
|
package/modules/enums.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Engine Constants
|
|
2
|
+
export const STATUS_CODE_SUCCESS = 200;
|
|
3
|
+
export const STATUS_CODE_PENDING = 202;
|
|
4
|
+
export const STATUS_CODE_TIMEOUT = 504;
|
|
5
|
+
export const STATUS_CODE_INTERRUPT = 410;
|
|
6
|
+
export const OTT_WAIT_TIME = 1000;
|
|
7
|
+
|
|
8
|
+
// Stream Constants
|
|
9
|
+
export const MAX_RETRIES = 3; //local retry; 10, 100, 1000ms
|
|
10
|
+
export const MAX_TIMEOUT_MS = 60000;
|
|
11
|
+
export const GRADUATED_INTERVAL_MS = 5000;
|
|
12
|
+
|
|
13
|
+
export const BLOCK_DURATION = 15000; //Set to `15` so SIGINT/SIGTERM can interrupt; set to `0` to BLOCK indefinitely
|
|
14
|
+
export const TEST_BLOCK_DURATION = 1000; //Set to `1000` so tests can interrupt quickly
|
|
15
|
+
export const BLOCK_TIME_MS = process.env.NODE_ENV === 'test' ? TEST_BLOCK_DURATION : BLOCK_DURATION;
|
|
16
|
+
|
|
17
|
+
export const XCLAIM_DELAY_MS = 1000 * 60; //max time a message can be unacked before it is claimed by another
|
|
18
|
+
export const XCLAIM_COUNT = 3; //max number of times a message can be claimed by another before it is dead-lettered
|
|
19
|
+
export const XPENDING_COUNT = 10;
|
|
20
|
+
|
|
21
|
+
export const STATUS_CODE_UNACKED = 999;
|
|
22
|
+
export const STATUS_CODE_UNKNOWN = 500;
|
|
23
|
+
export const STATUS_MESSAGE_UNKNOWN = 'unknown';
|
|
24
|
+
|
|
25
|
+
// HotMesh Constants
|
|
26
|
+
export const EXPIRE_DURATION = 15; // default expire in seconds; once job state semaphore reaches '0', this is applied to set Redis to expire the job HASH
|
|
27
|
+
export const BASE_FIDELITY_SECONDS = 15; // granularity resolution window size
|
|
28
|
+
export const TEST_FIDELITY_SECONDS = 5;
|
|
29
|
+
export const FIDELITY_SECONDS = process.env.NODE_ENV === 'test' ? TEST_FIDELITY_SECONDS : BASE_FIDELITY_SECONDS
|
|
30
|
+
|
|
31
|
+
// DURABLE CONSTANTS
|
|
32
|
+
export const DURABLE_EXPIRE_SECONDS = 1;
|
package/modules/errors.ts
CHANGED
|
@@ -2,8 +2,11 @@ import { ActivityDuplex } from "../types/activity";
|
|
|
2
2
|
import { CollationFaultType, CollationStage } from "../types/collator";
|
|
3
3
|
|
|
4
4
|
class GetStateError extends Error {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
jobId: string;
|
|
6
|
+
code: 404;
|
|
7
|
+
constructor(jobId: string) {
|
|
8
|
+
super(`${jobId} Not Found`);
|
|
9
|
+
this.jobId = jobId;
|
|
7
10
|
}
|
|
8
11
|
}
|
|
9
12
|
class SetStateError extends Error {
|
|
@@ -107,7 +110,18 @@ class DuplicateJobError extends Error {
|
|
|
107
110
|
this.message = `Duplicate job: ${jobId}`;
|
|
108
111
|
}
|
|
109
112
|
}
|
|
110
|
-
|
|
113
|
+
class InactiveJobError extends Error {
|
|
114
|
+
jobId: string;
|
|
115
|
+
activityId: string;
|
|
116
|
+
status: number; //non-positive integer
|
|
117
|
+
constructor(jobId: string, status: number, activityId: string) {
|
|
118
|
+
super("Inactive job");
|
|
119
|
+
this.jobId = jobId;
|
|
120
|
+
this.activityId = activityId;
|
|
121
|
+
this.message = `Inactive job: ${jobId}`;
|
|
122
|
+
this.status = status;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
111
125
|
class ExecActivityError extends Error {
|
|
112
126
|
constructor() {
|
|
113
127
|
super("Error occurred while executing activity");
|
|
@@ -131,18 +145,19 @@ class CollationError extends Error {
|
|
|
131
145
|
|
|
132
146
|
export {
|
|
133
147
|
CollationError,
|
|
134
|
-
DurableTimeoutError,
|
|
135
|
-
DurableMaxedError,
|
|
136
148
|
DurableFatalError,
|
|
137
|
-
DurableRetryError,
|
|
138
|
-
DurableWaitForSignalError,
|
|
139
149
|
DurableIncompleteSignalError,
|
|
150
|
+
DurableMaxedError,
|
|
151
|
+
DurableRetryError,
|
|
140
152
|
DurableSleepError,
|
|
141
153
|
DurableSleepForError,
|
|
154
|
+
DurableTimeoutError,
|
|
155
|
+
DurableWaitForSignalError,
|
|
142
156
|
DuplicateJobError,
|
|
157
|
+
ExecActivityError,
|
|
143
158
|
GetStateError,
|
|
144
|
-
|
|
159
|
+
InactiveJobError,
|
|
145
160
|
MapDataError,
|
|
146
161
|
RegisterTimeoutError,
|
|
147
|
-
|
|
162
|
+
SetStateError,
|
|
148
163
|
};
|
package/modules/key.ts
CHANGED
|
@@ -34,6 +34,7 @@ enum KeyType {
|
|
|
34
34
|
APP,
|
|
35
35
|
ENGINE_ID,
|
|
36
36
|
HOOKS,
|
|
37
|
+
JOB_DEPENDENTS,
|
|
37
38
|
JOB_STATE,
|
|
38
39
|
JOB_STATS_GENERAL,
|
|
39
40
|
JOB_STATS_MEDIAN,
|
|
@@ -93,7 +94,9 @@ class KeyService {
|
|
|
93
94
|
case KeyType.QUORUM:
|
|
94
95
|
return `${namespace}:${params.appId}:q:${params.engineId || ''}`;
|
|
95
96
|
case KeyType.JOB_STATE:
|
|
96
|
-
|
|
97
|
+
return `${namespace}:${params.appId}:j:${params.jobId}`;
|
|
98
|
+
case KeyType.JOB_DEPENDENTS:
|
|
99
|
+
return `${namespace}:${params.appId}:d:${params.jobId}`;
|
|
97
100
|
case KeyType.JOB_STATS_GENERAL:
|
|
98
101
|
return `${namespace}:${params.appId}:s:${params.jobKey}:${params.dateTime}`;
|
|
99
102
|
case KeyType.JOB_STATS_MEDIAN:
|
package/modules/utils.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { nanoid } from "nanoid";
|
|
1
2
|
import { StoreService } from "../services/store";
|
|
2
3
|
import { AppSubscriptions, AppTransitions, AppVID } from "../types/app";
|
|
3
4
|
import { RedisClient, RedisMulti } from "../types/redis";
|
|
@@ -8,6 +9,10 @@ export async function sleepFor(ms: number) {
|
|
|
8
9
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
10
|
}
|
|
10
11
|
|
|
12
|
+
export function guid(): string {
|
|
13
|
+
return nanoid();
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
export function deterministicRandom(seed: number): number {
|
|
12
17
|
let x = Math.sin(seed) * 10000;
|
|
13
18
|
return x - Math.floor(x);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotmeshio/hotmesh",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.35",
|
|
4
4
|
"description": "Unbreakable Workflows",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -23,12 +23,14 @@
|
|
|
23
23
|
"test:hmsh": "NODE_ENV=test jest ./tests/functional/index.test.ts --detectOpenHandles --verbose",
|
|
24
24
|
"test:compile": "NODE_ENV=test jest ./tests/functional/compile/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
25
25
|
"test:cycle": "NODE_ENV=test jest ./tests/functional/cycle/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
26
|
+
"test:trigger": "NODE_ENV=test jest ./tests/unit/services/activities/trigger.test.ts --detectOpenHandles --forceExit --verbose",
|
|
26
27
|
"test:connect": "NODE_ENV=test jest ./tests/unit/services/connector/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
27
28
|
"test:connect:redis": "NODE_ENV=test jest ./tests/unit/services/connector/clients/redis.test.ts --detectOpenHandles --forceExit --verbose",
|
|
28
29
|
"test:connect:ioredis": "NODE_ENV=test jest ./tests/unit/services/connector/clients/ioredis.test.ts --detectOpenHandles --forceExit --verbose",
|
|
29
30
|
"test:emit": "NODE_ENV=test jest ./tests/functional/emit/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
30
31
|
"test:hook": "NODE_ENV=test jest ./tests/functional/hook/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
31
32
|
"test:signal": "NODE_ENV=test jest ./tests/functional/signal/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
33
|
+
"test:interrupt": "NODE_ENV=test jest ./tests/functional/interrupt/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
32
34
|
"test:parallel": "NODE_ENV=test jest ./tests/functional/parallel/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
33
35
|
"test:sequence": "NODE_ENV=test jest ./tests/functional/sequence/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
34
36
|
"test:quorum": "NODE_ENV=test jest ./tests/functional/quorum/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
@@ -45,6 +47,7 @@
|
|
|
45
47
|
"test:durable": "NODE_ENV=test jest ./tests/durable/*/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
46
48
|
"test:durable:meshos": "NODE_ENV=test jest ./tests/durable/meshos/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
47
49
|
"test:durable:hello": "NODE_ENV=test jest ./tests/durable/helloworld/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
50
|
+
"test:durable:interrupt": "NODE_ENV=test jest ./tests/durable/interrupt/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
48
51
|
"test:durable:goodbye": "NODE_ENV=test jest ./tests/durable/goodbye/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
49
52
|
"test:durable:hook": "NODE_ENV=test jest ./tests/durable/hook/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
50
53
|
"test:durable:retry": "NODE_ENV=test jest ./tests/durable/retry/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { CollationError } from '../../modules/errors';
|
|
1
|
+
import { CollationError, GetStateError, InactiveJobError } from '../../modules/errors';
|
|
2
2
|
import {
|
|
3
3
|
formatISODate,
|
|
4
4
|
getValueByPath,
|
|
5
|
+
guid,
|
|
5
6
|
restoreHierarchy } from '../../modules/utils';
|
|
6
7
|
import { CollatorService } from '../collator';
|
|
7
8
|
import { EngineService } from '../engine';
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
StreamDataType,
|
|
30
31
|
StreamStatus } from '../../types/stream';
|
|
31
32
|
import { TransitionRule } from '../../types/transition';
|
|
33
|
+
import { EXPIRE_DURATION } from '../../modules/enums';
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
36
|
* The base class for all activities
|
|
@@ -84,6 +86,7 @@ class Activity {
|
|
|
84
86
|
let telemetry: TelemetryService;
|
|
85
87
|
try {
|
|
86
88
|
await this.getState();
|
|
89
|
+
CollatorService.assertJobActive(this.context.metadata.js, this.context.metadata.jid, this.metadata.aid);
|
|
87
90
|
const aState = await CollatorService.notarizeReentry(this);
|
|
88
91
|
this.adjacentIndex = CollatorService.getDimensionalIndex(aState);
|
|
89
92
|
|
|
@@ -110,12 +113,18 @@ class Activity {
|
|
|
110
113
|
if (error instanceof CollationError) {
|
|
111
114
|
this.logger.info('process-event-inactive-error', { error });
|
|
112
115
|
return;
|
|
116
|
+
} else if (error instanceof InactiveJobError) {
|
|
117
|
+
this.logger.info('process-event-inactive-job-error', { error });
|
|
118
|
+
return;
|
|
119
|
+
} else if (error instanceof GetStateError) {
|
|
120
|
+
this.logger.info('process-event-get-job-error', { error });
|
|
121
|
+
return;
|
|
113
122
|
}
|
|
114
123
|
this.logger.error('activity-process-event-error', { error });
|
|
115
124
|
telemetry && telemetry.setActivityError(error.message);
|
|
116
125
|
throw error;
|
|
117
126
|
} finally {
|
|
118
|
-
telemetry
|
|
127
|
+
telemetry?.endActivitySpan();
|
|
119
128
|
this.logger.debug('activity-process-event-end', { jid, aid });
|
|
120
129
|
}
|
|
121
130
|
}
|
|
@@ -159,6 +168,7 @@ class Activity {
|
|
|
159
168
|
telemetry.mapActivityAttributes();
|
|
160
169
|
const jobStatus = this.resolveStatus(multiResponse);
|
|
161
170
|
const attrs: StringScalarType = { 'app.job.jss': jobStatus };
|
|
171
|
+
//adjacencyList membership has already been set at this point (according to activity status)
|
|
162
172
|
const messageIds = await this.transition(this.adjacencyList, jobStatus);
|
|
163
173
|
if (messageIds.length) {
|
|
164
174
|
attrs['app.activity.mids'] = messageIds.join(',')
|
|
@@ -380,12 +390,16 @@ class Activity {
|
|
|
380
390
|
self.hook = { };
|
|
381
391
|
}
|
|
382
392
|
context['$self'] = self;
|
|
383
|
-
context['$job'] = context; //NEVER call STRINGIFY! (circular)
|
|
393
|
+
context['$job'] = context; //NEVER call STRINGIFY! (now circular)
|
|
384
394
|
return context as JobState;
|
|
385
395
|
}
|
|
386
396
|
|
|
387
397
|
initPolicies(context: JobState) {
|
|
388
|
-
|
|
398
|
+
const expire = Pipe.resolve(
|
|
399
|
+
this.config.expire ?? EXPIRE_DURATION,
|
|
400
|
+
context
|
|
401
|
+
);
|
|
402
|
+
context.metadata.expire = expire;
|
|
389
403
|
}
|
|
390
404
|
|
|
391
405
|
bindActivityData(type: 'output' | 'hook'): void {
|
|
@@ -418,6 +432,7 @@ class Activity {
|
|
|
418
432
|
if (MapperService.evaluate(transitionRule, this.context, this.code)) {
|
|
419
433
|
adjacencyList.push({
|
|
420
434
|
metadata: {
|
|
435
|
+
guid: guid(),
|
|
421
436
|
jid: this.context.metadata.jid,
|
|
422
437
|
dad: adjacentDad,
|
|
423
438
|
aid: toActivityId,
|
|
@@ -434,17 +449,20 @@ class Activity {
|
|
|
434
449
|
}
|
|
435
450
|
|
|
436
451
|
async transition(adjacencyList: StreamData[], jobStatus: JobStatus): Promise<string[]> {
|
|
452
|
+
if (this.jobWasInterrupted(jobStatus)) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
437
455
|
let mIds: string[] = [];
|
|
438
456
|
let emit: boolean = false;
|
|
439
457
|
if (this.config.emit) {
|
|
440
458
|
emit = Pipe.resolve(this.config.emit, this.context);
|
|
441
459
|
}
|
|
442
460
|
if (jobStatus <= 0 || emit) {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
461
|
+
await this.engine.runJobCompletionTasks(
|
|
462
|
+
this.context,
|
|
463
|
+
{ emit: jobStatus > 0 },
|
|
464
|
+
);
|
|
446
465
|
}
|
|
447
|
-
|
|
448
466
|
if (adjacencyList.length && jobStatus > 0) {
|
|
449
467
|
const multi = this.store.getMulti();
|
|
450
468
|
for (const execSignal of adjacencyList) {
|
|
@@ -454,6 +472,14 @@ class Activity {
|
|
|
454
472
|
}
|
|
455
473
|
return mIds;
|
|
456
474
|
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* A job with a vale < -100_000_000 is considered interrupted,
|
|
478
|
+
* as the interruption event decrements the job status by 1billion.
|
|
479
|
+
*/
|
|
480
|
+
jobWasInterrupted(jobStatus: JobStatus): boolean {
|
|
481
|
+
return jobStatus < -100_000_000;
|
|
482
|
+
}
|
|
457
483
|
}
|
|
458
484
|
|
|
459
485
|
export { Activity, ActivityType };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GetStateError } from '../../modules/errors';
|
|
1
|
+
import { GetStateError, InactiveJobError } from '../../modules/errors';
|
|
2
2
|
import { Activity } from './activity';
|
|
3
3
|
import { CollatorService } from '../collator';
|
|
4
4
|
import { EngineService } from '../engine';
|
|
@@ -12,6 +12,7 @@ import { MultiResponseFlags, RedisMulti } from '../../types/redis';
|
|
|
12
12
|
import { StreamData, StreamDataType } from '../../types/stream';
|
|
13
13
|
import { TelemetryService } from '../telemetry';
|
|
14
14
|
import { Pipe } from '../pipe';
|
|
15
|
+
import { guid } from '../../modules/utils';
|
|
15
16
|
|
|
16
17
|
class Await extends Activity {
|
|
17
18
|
config: AwaitActivity;
|
|
@@ -35,6 +36,7 @@ class Await extends Activity {
|
|
|
35
36
|
this.setLeg(1);
|
|
36
37
|
await CollatorService.notarizeEntry(this);
|
|
37
38
|
await this.getState();
|
|
39
|
+
CollatorService.assertJobActive(this.context.metadata.js, this.context.metadata.jid, this.metadata.aid);
|
|
38
40
|
telemetry = new TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
39
41
|
telemetry.startActivitySpan(this.leg);
|
|
40
42
|
this.mapInputData();
|
|
@@ -57,15 +59,19 @@ class Await extends Activity {
|
|
|
57
59
|
});
|
|
58
60
|
return this.context.metadata.aid;
|
|
59
61
|
} catch (error) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
if (error instanceof InactiveJobError) {
|
|
63
|
+
this.logger.error('await-inactive-job-error', { error });
|
|
64
|
+
return;
|
|
65
|
+
} else if (error instanceof GetStateError) {
|
|
62
66
|
this.logger.error('await-get-state-error', { error });
|
|
67
|
+
return;
|
|
63
68
|
} else {
|
|
64
69
|
this.logger.error('await-process-error', { error });
|
|
65
70
|
}
|
|
71
|
+
telemetry.setActivityError(error.message);
|
|
66
72
|
throw error;
|
|
67
73
|
} finally {
|
|
68
|
-
telemetry
|
|
74
|
+
telemetry?.endActivitySpan();
|
|
69
75
|
this.logger.debug('await-process-end', { jid: this.context.metadata.jid, aid: this.metadata.aid });
|
|
70
76
|
}
|
|
71
77
|
}
|
|
@@ -74,6 +80,7 @@ class Await extends Activity {
|
|
|
74
80
|
const topic = Pipe.resolve(this.config.subtype, this.context);
|
|
75
81
|
const streamData: StreamData = {
|
|
76
82
|
metadata: {
|
|
83
|
+
guid: guid(),
|
|
77
84
|
jid: this.context.metadata.jid,
|
|
78
85
|
dad: this.metadata.dad,
|
|
79
86
|
aid: this.metadata.aid,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GetStateError } from '../../modules/errors';
|
|
1
|
+
import { GetStateError, InactiveJobError } from '../../modules/errors';
|
|
2
2
|
import { CollatorService } from '../collator';
|
|
3
3
|
import { EngineService } from '../engine';
|
|
4
4
|
import { Activity, ActivityType } from './activity';
|
|
@@ -10,6 +10,7 @@ import { JobState } from '../../types/job';
|
|
|
10
10
|
import { MultiResponseFlags, RedisMulti } from '../../types/redis';
|
|
11
11
|
import { StreamData } from '../../types/stream';
|
|
12
12
|
import { TelemetryService } from '../telemetry';
|
|
13
|
+
import { guid } from '../../modules/utils';
|
|
13
14
|
|
|
14
15
|
class Cycle extends Activity {
|
|
15
16
|
config: CycleActivity;
|
|
@@ -34,6 +35,7 @@ class Cycle extends Activity {
|
|
|
34
35
|
this.setLeg(1);
|
|
35
36
|
await CollatorService.notarizeEntry(this);
|
|
36
37
|
await this.getState();
|
|
38
|
+
CollatorService.assertJobActive(this.context.metadata.js, this.context.metadata.jid, this.metadata.aid);
|
|
37
39
|
telemetry = new TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
38
40
|
telemetry.startActivitySpan(this.leg);
|
|
39
41
|
this.mapInputData();
|
|
@@ -60,15 +62,19 @@ class Cycle extends Activity {
|
|
|
60
62
|
|
|
61
63
|
return this.context.metadata.aid;
|
|
62
64
|
} catch (error) {
|
|
63
|
-
if (error instanceof
|
|
65
|
+
if (error instanceof InactiveJobError) {
|
|
66
|
+
this.logger.error('cycle-inactive-job-error', { error });
|
|
67
|
+
return;
|
|
68
|
+
} else if (error instanceof GetStateError) {
|
|
64
69
|
this.logger.error('cycle-get-state-error', { error });
|
|
70
|
+
return;
|
|
65
71
|
} else {
|
|
66
72
|
this.logger.error('cycle-process-error', { error });
|
|
67
73
|
}
|
|
68
74
|
telemetry.setActivityError(error.message);
|
|
69
75
|
throw error;
|
|
70
76
|
} finally {
|
|
71
|
-
telemetry
|
|
77
|
+
telemetry?.endActivitySpan();
|
|
72
78
|
this.logger.debug('cycle-process-end', { jid: this.context.metadata.jid, aid: this.metadata.aid });
|
|
73
79
|
}
|
|
74
80
|
}
|
|
@@ -88,6 +94,7 @@ class Cycle extends Activity {
|
|
|
88
94
|
this.mapInputData();
|
|
89
95
|
const streamData: StreamData = {
|
|
90
96
|
metadata: {
|
|
97
|
+
guid: guid(),
|
|
91
98
|
dad: CollatorService.resolveReentryDimension(this),
|
|
92
99
|
jid: this.context.metadata.jid,
|
|
93
100
|
aid: this.config.ancestor,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GetStateError } from '../../modules/errors';
|
|
1
|
+
import { GetStateError, InactiveJobError } from '../../modules/errors';
|
|
2
2
|
import { Activity } from './activity';
|
|
3
3
|
import { CollatorService } from '../collator';
|
|
4
4
|
import { EngineService } from '../engine';
|
|
@@ -43,6 +43,7 @@ class Hook extends Activity {
|
|
|
43
43
|
await CollatorService.notarizeEntry(this);
|
|
44
44
|
|
|
45
45
|
await this.getState();
|
|
46
|
+
CollatorService.assertJobActive(this.context.metadata.js, this.context.metadata.jid, this.metadata.aid);
|
|
46
47
|
telemetry = new TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
|
|
47
48
|
telemetry.startActivitySpan(this.leg);
|
|
48
49
|
let multiResponse: MultiResponseFlags;
|
|
@@ -81,15 +82,19 @@ class Hook extends Activity {
|
|
|
81
82
|
|
|
82
83
|
return this.context.metadata.aid;
|
|
83
84
|
} catch (error) {
|
|
84
|
-
if (error instanceof
|
|
85
|
+
if (error instanceof InactiveJobError) {
|
|
86
|
+
this.logger.error('hook-inactive-job-error', { error });
|
|
87
|
+
return;
|
|
88
|
+
} else if (error instanceof GetStateError) {
|
|
85
89
|
this.logger.error('hook-get-state-error', { error });
|
|
90
|
+
return;
|
|
86
91
|
} else {
|
|
87
92
|
this.logger.error('hook-process-error', { error });
|
|
88
93
|
}
|
|
89
94
|
telemetry.setActivityError(error.message);
|
|
90
95
|
throw error;
|
|
91
96
|
} finally {
|
|
92
|
-
telemetry
|
|
97
|
+
telemetry?.endActivitySpan();
|
|
93
98
|
this.logger.debug('hook-process-end', { jid: this.context.metadata.jid, aid: this.metadata.aid });
|
|
94
99
|
}
|
|
95
100
|
}
|
|
@@ -2,7 +2,7 @@ import { Activity } from './activity';
|
|
|
2
2
|
import { Await } from './await';
|
|
3
3
|
import { Cycle } from './cycle';
|
|
4
4
|
import { Hook } from './hook';
|
|
5
|
-
import {
|
|
5
|
+
import { Interrupt } from './interrupt';
|
|
6
6
|
import { Signal } from './signal';
|
|
7
7
|
import { Trigger } from './trigger';
|
|
8
8
|
import { Worker } from './worker';
|
|
@@ -12,7 +12,7 @@ export default {
|
|
|
12
12
|
await: Await,
|
|
13
13
|
cycle: Cycle,
|
|
14
14
|
hook: Hook,
|
|
15
|
-
|
|
15
|
+
interrupt: Interrupt,
|
|
16
16
|
signal: Signal,
|
|
17
17
|
trigger: Trigger,
|
|
18
18
|
worker: Worker,
|