@hotmeshio/hotmesh 0.0.54 → 0.0.56
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 +0 -3
- 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 +57 -56
- package/build/services/durable/worker.js +2 -22
- package/build/services/durable/workflow.d.ts +0 -114
- package/build/services/durable/workflow.js +4 -144
- 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 +25 -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 +121 -3
- package/types/hotmesh.ts +3 -6
- package/types/index.ts +23 -10
- package/types/job.ts +1 -1
- package/types/quorum.ts +22 -0
- package/types/redis.ts +267 -18
- package/build/types/ioredisclient.d.ts +0 -5
- package/build/types/ioredisclient.js +0 -5
- package/build/types/redisclient.d.ts +0 -26
- package/build/types/redisclient.js +0 -2
- package/modules/enums.ts +0 -62
- package/modules/errors.ts +0 -280
- package/modules/key.ts +0 -101
- package/modules/storage.ts +0 -3
- package/modules/utils.ts +0 -242
- package/services/activities/activity.ts +0 -589
- package/services/activities/await.ts +0 -113
- package/services/activities/cycle.ts +0 -115
- package/services/activities/hook.ts +0 -197
- package/services/activities/index.ts +0 -19
- package/services/activities/interrupt.ts +0 -172
- package/services/activities/signal.ts +0 -148
- package/services/activities/trigger.ts +0 -295
- package/services/activities/worker.ts +0 -107
- package/services/collator/README.md +0 -102
- package/services/collator/index.ts +0 -291
- package/services/compiler/deployer.ts +0 -504
- package/services/compiler/index.ts +0 -98
- package/services/compiler/validator.ts +0 -158
- package/services/connector/clients/ioredis.ts +0 -57
- package/services/connector/clients/redis.ts +0 -72
- package/services/connector/index.ts +0 -42
- package/services/durable/client.ts +0 -266
- package/services/durable/connection.ts +0 -10
- package/services/durable/exporter.ts +0 -232
- package/services/durable/handle.ts +0 -160
- package/services/durable/index.ts +0 -27
- package/services/durable/schemas/factory.ts +0 -2358
- package/services/durable/search.ts +0 -196
- package/services/durable/worker.ts +0 -401
- package/services/durable/workflow.ts +0 -557
- package/services/engine/index.ts +0 -761
- package/services/exporter/index.ts +0 -146
- package/services/hotmesh/index.ts +0 -237
- package/services/logger/index.ts +0 -79
- package/services/mapper/index.ts +0 -89
- package/services/pipe/functions/array.ts +0 -78
- package/services/pipe/functions/bitwise.ts +0 -27
- package/services/pipe/functions/conditional.ts +0 -35
- package/services/pipe/functions/date.ts +0 -220
- package/services/pipe/functions/index.ts +0 -27
- package/services/pipe/functions/json.ts +0 -11
- package/services/pipe/functions/logical.ts +0 -11
- package/services/pipe/functions/math.ts +0 -217
- package/services/pipe/functions/number.ts +0 -75
- package/services/pipe/functions/object.ts +0 -98
- package/services/pipe/functions/string.ts +0 -86
- package/services/pipe/functions/symbol.ts +0 -39
- package/services/pipe/functions/unary.ts +0 -19
- package/services/pipe/index.ts +0 -216
- package/services/quorum/index.ts +0 -319
- package/services/reporter/index.ts +0 -387
- package/services/router/index.ts +0 -426
- package/services/serializer/README.md +0 -10
- package/services/serializer/index.ts +0 -285
- package/services/store/cache.ts +0 -172
- package/services/store/clients/ioredis.ts +0 -145
- package/services/store/clients/redis.ts +0 -191
- package/services/store/index.ts +0 -1091
- package/services/stream/clients/ioredis.ts +0 -157
- package/services/stream/clients/redis.ts +0 -158
- package/services/stream/index.ts +0 -58
- package/services/sub/clients/ioredis.ts +0 -83
- package/services/sub/clients/redis.ts +0 -74
- package/services/sub/index.ts +0 -25
- package/services/task/index.ts +0 -250
- package/services/telemetry/index.ts +0 -273
- package/services/worker/index.ts +0 -248
- package/types/ioredisclient.ts +0 -10
- package/types/redisclient.ts +0 -30
|
@@ -1,557 +0,0 @@
|
|
|
1
|
-
import ms from 'ms';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
DurableChildError,
|
|
5
|
-
DurableFatalError,
|
|
6
|
-
DurableMaxedError,
|
|
7
|
-
DurableProxyError,
|
|
8
|
-
DurableRetryError,
|
|
9
|
-
DurableSleepError,
|
|
10
|
-
DurableTimeoutError,
|
|
11
|
-
DurableWaitForError } from '../../modules/errors';
|
|
12
|
-
import { KeyService, KeyType } from '../../modules/key';
|
|
13
|
-
import { asyncLocalStorage } from '../../modules/storage';
|
|
14
|
-
import {
|
|
15
|
-
deterministicRandom,
|
|
16
|
-
formatISODate,
|
|
17
|
-
guid,
|
|
18
|
-
sleepFor } from '../../modules/utils';
|
|
19
|
-
import { Search } from './search';
|
|
20
|
-
import { WorkerService } from './worker';
|
|
21
|
-
import { HotMeshService as HotMesh } from '../hotmesh';
|
|
22
|
-
import { SerializerService } from '../serializer';
|
|
23
|
-
import {
|
|
24
|
-
ActivityConfig,
|
|
25
|
-
ChildResponseType,
|
|
26
|
-
HookOptions,
|
|
27
|
-
ProxyResponseType,
|
|
28
|
-
ProxyType,
|
|
29
|
-
WorkflowContext,
|
|
30
|
-
WorkflowOptions
|
|
31
|
-
} from "../../types/durable";
|
|
32
|
-
import { JobInterruptOptions } from '../../types/job';
|
|
33
|
-
import { StreamCode, StreamStatus } from '../../types/stream';
|
|
34
|
-
import { StringStringType } from '../../types/serializer';
|
|
35
|
-
import {
|
|
36
|
-
HMSH_CODE_DURABLE_CHILD,
|
|
37
|
-
HMSH_CODE_DURABLE_FATAL,
|
|
38
|
-
HMSH_CODE_DURABLE_MAXED,
|
|
39
|
-
HMSH_CODE_DURABLE_PROXY,
|
|
40
|
-
HMSH_CODE_DURABLE_SLEEP,
|
|
41
|
-
HMSH_CODE_DURABLE_TIMEOUT,
|
|
42
|
-
HMSH_CODE_DURABLE_WAIT,
|
|
43
|
-
HMSH_DURABLE_EXP_BACKOFF,
|
|
44
|
-
HMSH_DURABLE_MAX_ATTEMPTS,
|
|
45
|
-
HMSH_DURABLE_MAX_INTERVAL} from '../../modules/enums';
|
|
46
|
-
import { DurableChildErrorType, DurableProxyErrorType } from '../../types/error';
|
|
47
|
-
|
|
48
|
-
export class WorkflowService {
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Returns the synchronous output from the activity (replay)
|
|
52
|
-
* if available locally, revealing whether or not the activity already
|
|
53
|
-
* ran during a prior execution cycle
|
|
54
|
-
* @param {string} prefix - one of: proxy, child, start, wait etc
|
|
55
|
-
* @returns
|
|
56
|
-
*/
|
|
57
|
-
static async didRun(prefix: string): Promise<[boolean, number, any]> {
|
|
58
|
-
const {
|
|
59
|
-
COUNTER,
|
|
60
|
-
replay,
|
|
61
|
-
workflowDimension,
|
|
62
|
-
} = WorkflowService.getContext();
|
|
63
|
-
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
64
|
-
const sessionId = `-${prefix}${workflowDimension}-${execIndex}-`;
|
|
65
|
-
if (sessionId in replay) {
|
|
66
|
-
const restored = SerializerService.fromString(replay[sessionId]);
|
|
67
|
-
return [true, execIndex, restored];
|
|
68
|
-
}
|
|
69
|
-
return [false, execIndex, null];
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Those methods that may only be called once must be protected by flagging
|
|
74
|
-
* their execution with a unique key (the key is stored in the HASH alongside
|
|
75
|
-
* process state and job state)
|
|
76
|
-
* @private
|
|
77
|
-
*/
|
|
78
|
-
static async isSideEffectAllowed(hotMeshClient: HotMesh, prefix: string): Promise<boolean> {
|
|
79
|
-
const store = asyncLocalStorage.getStore();
|
|
80
|
-
const workflowId = store.get('workflowId');
|
|
81
|
-
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
82
|
-
const COUNTER = store.get('counter');
|
|
83
|
-
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
84
|
-
const sessionId = `-${prefix}${workflowDimension}-${execIndex}-`;
|
|
85
|
-
const replay = store.get('replay') as StringStringType;
|
|
86
|
-
if (sessionId in replay) {
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
const keyParams = {
|
|
90
|
-
appId: hotMeshClient.appId,
|
|
91
|
-
jobId: workflowId
|
|
92
|
-
}
|
|
93
|
-
const workflowGuid = KeyService.mintKey(hotMeshClient.namespace, KeyType.JOB_STATE, keyParams);
|
|
94
|
-
const guidValue = Number(
|
|
95
|
-
await hotMeshClient.engine.store.exec(
|
|
96
|
-
'HINCRBYFLOAT',
|
|
97
|
-
workflowGuid,
|
|
98
|
-
sessionId,
|
|
99
|
-
'1') as string
|
|
100
|
-
);
|
|
101
|
-
return guidValue === 1;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Returns the current workflow context restored
|
|
106
|
-
* from Redis
|
|
107
|
-
*/
|
|
108
|
-
static getContext(): WorkflowContext {
|
|
109
|
-
const store = asyncLocalStorage.getStore();
|
|
110
|
-
const workflowId = store.get('workflowId');
|
|
111
|
-
const replay = store.get('replay');
|
|
112
|
-
const cursor = store.get('cursor');
|
|
113
|
-
const interruptionRegistry = store.get('interruptionRegistry');
|
|
114
|
-
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
115
|
-
const workflowTopic = store.get('workflowTopic');
|
|
116
|
-
const namespace = store.get('namespace');
|
|
117
|
-
const originJobId = store.get('originJobId');
|
|
118
|
-
const workflowTrace = store.get('workflowTrace');
|
|
119
|
-
const canRetry = store.get('canRetry');
|
|
120
|
-
const workflowSpan = store.get('workflowSpan');
|
|
121
|
-
const COUNTER = store.get('counter');
|
|
122
|
-
const raw = store.get('raw');
|
|
123
|
-
return {
|
|
124
|
-
canRetry,
|
|
125
|
-
COUNTER,
|
|
126
|
-
counter: COUNTER.counter,
|
|
127
|
-
cursor,
|
|
128
|
-
interruptionRegistry,
|
|
129
|
-
namespace,
|
|
130
|
-
originJobId,
|
|
131
|
-
raw,
|
|
132
|
-
replay,
|
|
133
|
-
workflowId,
|
|
134
|
-
workflowDimension,
|
|
135
|
-
workflowTopic,
|
|
136
|
-
workflowTrace,
|
|
137
|
-
workflowSpan,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Return a handle to the hotmesh client hosting the workflow execution
|
|
143
|
-
* @returns {Promise<HotMesh>} - a hotmesh client
|
|
144
|
-
*/
|
|
145
|
-
static async getHotMesh(): Promise<HotMesh> {
|
|
146
|
-
const store = asyncLocalStorage.getStore();
|
|
147
|
-
const workflowTopic = store.get('workflowTopic');
|
|
148
|
-
const namespace = store.get('namespace');
|
|
149
|
-
return await WorkerService.getHotMesh(workflowTopic, { namespace });
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Spawns a child workflow and awaits the return.
|
|
154
|
-
* @template T - the result type
|
|
155
|
-
* @param {WorkflowOptions} options - the workflow options
|
|
156
|
-
* @returns {Promise<T>} - the result of the child workflow
|
|
157
|
-
* @example
|
|
158
|
-
* const result = await Durable.workflow.execChild<typeof resultType>({ ...options });
|
|
159
|
-
*/
|
|
160
|
-
static async execChild<T>(options: WorkflowOptions): Promise<T> {
|
|
161
|
-
//SYNC
|
|
162
|
-
//check if the activity already ran (check $error/done)
|
|
163
|
-
const isStartChild = options.await === false;
|
|
164
|
-
const prefix = isStartChild ? 'start' : 'child';
|
|
165
|
-
const [didRun, execIndex, result]: [boolean, number, ChildResponseType<T>] = await WorkflowService.didRun(prefix);
|
|
166
|
-
const context = this.getContext();
|
|
167
|
-
const { canRetry, interruptionRegistry } = context;
|
|
168
|
-
|
|
169
|
-
if (didRun) {
|
|
170
|
-
if (result?.$error && (!result.$error.is_stream_error || (result.$error.is_stream_error && !canRetry))) {
|
|
171
|
-
if (options?.config?.throwOnError !== false) {
|
|
172
|
-
//rethrow remote execution error (simulates local failure)
|
|
173
|
-
const code: StreamCode = result.$error.code;
|
|
174
|
-
const message = result.$error.message;
|
|
175
|
-
const stack = result.$error.stack;
|
|
176
|
-
if (code === HMSH_CODE_DURABLE_FATAL) {
|
|
177
|
-
throw new DurableFatalError(message, stack);
|
|
178
|
-
} else if (code == HMSH_CODE_DURABLE_MAXED) {
|
|
179
|
-
throw new DurableMaxedError(message, stack);
|
|
180
|
-
} else if (code == HMSH_CODE_DURABLE_TIMEOUT) {
|
|
181
|
-
throw new DurableTimeoutError(message, stack);
|
|
182
|
-
} else {
|
|
183
|
-
throw new DurableRetryError(message, stack);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return result.$error as T;
|
|
187
|
-
} else if (result.data) {
|
|
188
|
-
return result.data as T;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
const interruptionMessage = this.getChildInterruptPayload(context, options, execIndex);
|
|
192
|
-
//push the packaged inputs to the registry
|
|
193
|
-
interruptionRegistry.push({
|
|
194
|
-
code: HMSH_CODE_DURABLE_CHILD,
|
|
195
|
-
...interruptionMessage,
|
|
196
|
-
});
|
|
197
|
-
//ASYNC
|
|
198
|
-
//sleep (allow others to be packaged / registered) and throw the error
|
|
199
|
-
await sleepFor(0);
|
|
200
|
-
throw new DurableChildError(interruptionMessage );
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* constructs the payload necessary to spawn a child job
|
|
205
|
-
* @private
|
|
206
|
-
*/
|
|
207
|
-
static getChildInterruptPayload(context: WorkflowContext, options: WorkflowOptions, execIndex: number): DurableChildErrorType {
|
|
208
|
-
const { workflowId, originJobId, workflowDimension } = context; let childJobId: string;
|
|
209
|
-
if (options.workflowId) {
|
|
210
|
-
childJobId = options.workflowId;
|
|
211
|
-
} else if (options.entity) {
|
|
212
|
-
childJobId = `${options.entity}-${workflowId.substring(0, 7)}-${guid()}-${workflowDimension}-${execIndex}`;
|
|
213
|
-
} else {
|
|
214
|
-
childJobId = `-${options.workflowName}-${guid()}-${workflowDimension}-${execIndex}`;
|
|
215
|
-
}
|
|
216
|
-
const parentWorkflowId = workflowId;
|
|
217
|
-
const taskQueueName = options.entity ?? options.taskQueue;
|
|
218
|
-
const workflowName = options.entity ?? options.workflowName;
|
|
219
|
-
const workflowTopic = `${taskQueueName}-${workflowName}`;
|
|
220
|
-
return {
|
|
221
|
-
arguments: [...(options.args || [])],
|
|
222
|
-
await: options?.await ?? true,
|
|
223
|
-
backoffCoefficient: options?.config?.backoffCoefficient ?? HMSH_DURABLE_EXP_BACKOFF,
|
|
224
|
-
index: execIndex,
|
|
225
|
-
maximumAttempts: options?.config?.maximumAttempts ?? HMSH_DURABLE_MAX_ATTEMPTS,
|
|
226
|
-
maximumInterval: ms(options?.config?.maximumInterval ?? HMSH_DURABLE_MAX_INTERVAL) / 1000,
|
|
227
|
-
originJobId: originJobId ?? workflowId,
|
|
228
|
-
parentWorkflowId,
|
|
229
|
-
workflowDimension: workflowDimension,
|
|
230
|
-
workflowId: childJobId,
|
|
231
|
-
workflowTopic,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Spawns a child workflow and returns the child Job ID.
|
|
237
|
-
* This method guarantees the spawned child has reserved the Job ID,
|
|
238
|
-
* returning a 'DuplicateJobError' error if not. Otherwise,
|
|
239
|
-
* this is a fire-and-forget method.
|
|
240
|
-
*
|
|
241
|
-
* @param {WorkflowOptions} options - the workflow options
|
|
242
|
-
* @returns {Promise<string>} - the childJobId
|
|
243
|
-
* @example
|
|
244
|
-
* const childJobId = await Durable.workflow.startChild({ ...options });
|
|
245
|
-
*/
|
|
246
|
-
static async startChild(options: WorkflowOptions): Promise<string> {
|
|
247
|
-
return this.execChild({ ...options, await: false });
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Wraps activities in a proxy that durably runs/re-runs them to completion.
|
|
252
|
-
* TODO: verify that activities do not collide if named same on same server but bound to different workflows
|
|
253
|
-
*
|
|
254
|
-
* @param {ActivityConfig} options - the activity configuration
|
|
255
|
-
* that will be used to wrap the activities.
|
|
256
|
-
* @returns {ProxyType<ACT>} - a proxy object with the same keys as the
|
|
257
|
-
* activities object, but with the values replaced by a wrapped function
|
|
258
|
-
*
|
|
259
|
-
* @example
|
|
260
|
-
* // import the activities
|
|
261
|
-
* import * as activities from './activities';
|
|
262
|
-
* const proxy = WorkflowService.proxyActivities<typeof activities>({ activities });
|
|
263
|
-
*
|
|
264
|
-
* //or destructure the proxy object, as the function names are the keys
|
|
265
|
-
* const { activity1, activity2 } = WorkflowService.proxyActivities<typeof activities>({ activities });
|
|
266
|
-
*/
|
|
267
|
-
static proxyActivities<ACT>(options?: ActivityConfig): ProxyType<ACT> {
|
|
268
|
-
if (options.activities) {
|
|
269
|
-
WorkerService.registerActivities(options.activities);
|
|
270
|
-
}
|
|
271
|
-
const proxy: any = {};
|
|
272
|
-
const keys = Object.keys(WorkerService.activityRegistry);
|
|
273
|
-
if (keys.length) {
|
|
274
|
-
keys.forEach((key: string) => {
|
|
275
|
-
const activityFunction = WorkerService.activityRegistry[key];
|
|
276
|
-
proxy[key] = WorkflowService.wrapActivity<typeof activityFunction>(key, options);
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
return proxy;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
static wrapActivity<T>(activityName: string, options?: ActivityConfig): T {
|
|
283
|
-
return async function () {
|
|
284
|
-
//SYNC
|
|
285
|
-
//check if the activity already ran
|
|
286
|
-
const [didRun, execIndex, result]: [boolean, number, ProxyResponseType<T>] = await WorkflowService.didRun('proxy');
|
|
287
|
-
if (didRun) {
|
|
288
|
-
if (result?.$error) {
|
|
289
|
-
if (options?.retryPolicy?.throwOnError !== false) {
|
|
290
|
-
//rethrow remote execution error (simulates throw)
|
|
291
|
-
const code: StreamCode = result.$error.code;
|
|
292
|
-
const message = result.$error.message;
|
|
293
|
-
const stack = result.$error.stack;
|
|
294
|
-
if (code === HMSH_CODE_DURABLE_FATAL) {
|
|
295
|
-
throw new DurableFatalError(message, stack);
|
|
296
|
-
} else if (code == HMSH_CODE_DURABLE_MAXED) {
|
|
297
|
-
throw new DurableMaxedError(message, stack);
|
|
298
|
-
} else if (code == HMSH_CODE_DURABLE_TIMEOUT) {
|
|
299
|
-
throw new DurableTimeoutError(message, stack);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
return result.$error as T;
|
|
303
|
-
}
|
|
304
|
-
return result.data as T;
|
|
305
|
-
}
|
|
306
|
-
//package the interruption inputs
|
|
307
|
-
const context = WorkflowService.getContext();
|
|
308
|
-
const { interruptionRegistry } = context;
|
|
309
|
-
const interruptionMessage = WorkflowService.getProxyInterruptPayload(
|
|
310
|
-
context,
|
|
311
|
-
activityName,
|
|
312
|
-
execIndex,
|
|
313
|
-
Array.from(arguments),
|
|
314
|
-
options
|
|
315
|
-
);
|
|
316
|
-
//push the packaged inputs to the registry
|
|
317
|
-
interruptionRegistry.push({
|
|
318
|
-
code: HMSH_CODE_DURABLE_PROXY,
|
|
319
|
-
...interruptionMessage,
|
|
320
|
-
});
|
|
321
|
-
//ASYNC
|
|
322
|
-
//sleep (allow others to be packaged / registered) and throw the error
|
|
323
|
-
await sleepFor(0);
|
|
324
|
-
throw new DurableProxyError(interruptionMessage);
|
|
325
|
-
} as T;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
/**
|
|
329
|
-
* constructs the payload necessary to spawn a proxyActivity job
|
|
330
|
-
* @private
|
|
331
|
-
*/
|
|
332
|
-
static getProxyInterruptPayload(context: WorkflowContext, activityName: string, execIndex: number, args: any[], options?: ActivityConfig): DurableProxyErrorType {
|
|
333
|
-
const { workflowDimension, workflowId, originJobId, workflowTopic } = context;
|
|
334
|
-
const activityTopic = `${workflowTopic}-activity`;
|
|
335
|
-
const activityJobId = `-${workflowId}-$${activityName}${workflowDimension}-${execIndex}`;
|
|
336
|
-
let maximumInterval: number;
|
|
337
|
-
if (options.retryPolicy?.maximumInterval) {
|
|
338
|
-
maximumInterval = ms(options.retryPolicy.maximumInterval) / 1000;
|
|
339
|
-
}
|
|
340
|
-
return {
|
|
341
|
-
arguments: args,
|
|
342
|
-
workflowDimension: workflowDimension,
|
|
343
|
-
index: execIndex,
|
|
344
|
-
originJobId: originJobId || workflowId,
|
|
345
|
-
parentWorkflowId: workflowId,
|
|
346
|
-
workflowId: activityJobId,
|
|
347
|
-
workflowTopic: activityTopic,
|
|
348
|
-
activityName,
|
|
349
|
-
backoffCoefficient: options?.retryPolicy?.backoffCoefficient ?? undefined,
|
|
350
|
-
maximumAttempts: options?.retryPolicy?.maximumAttempts ?? undefined,
|
|
351
|
-
maximumInterval: maximumInterval ?? undefined,
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Returns a search session for use when reading/writing to the workflow HASH.
|
|
357
|
-
* The search session provides access to methods like `get`, `mget`, `set`, `del`, and `incr`.
|
|
358
|
-
* @returns {Promise<Search>} - a search session
|
|
359
|
-
*/
|
|
360
|
-
static async search(): Promise<Search> {
|
|
361
|
-
const store = asyncLocalStorage.getStore();
|
|
362
|
-
const workflowId = store.get('workflowId');
|
|
363
|
-
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
364
|
-
const workflowTopic = store.get('workflowTopic');
|
|
365
|
-
const namespace = store.get('namespace');
|
|
366
|
-
const COUNTER = store.get('counter');
|
|
367
|
-
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
368
|
-
const hotMeshClient = await WorkerService.getHotMesh(workflowTopic, { namespace });
|
|
369
|
-
//this ID is used as a item key with a hash (dash prefix ensures no collision)
|
|
370
|
-
const searchSessionId = `-search${workflowDimension}-${execIndex}`;
|
|
371
|
-
return new Search(workflowId, hotMeshClient, searchSessionId);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Returns a random number between 0 and 1. This number is deterministic
|
|
376
|
-
* and will never vary for a given seed. This is useful for randomizing
|
|
377
|
-
* pathways in a workflow that can be safely replayed.
|
|
378
|
-
* @returns {number} - a random number between 0 and 1
|
|
379
|
-
*/
|
|
380
|
-
static random(): number {
|
|
381
|
-
const store = asyncLocalStorage.getStore();
|
|
382
|
-
const COUNTER = store.get('counter');
|
|
383
|
-
const seed = COUNTER.counter = COUNTER.counter + 1;
|
|
384
|
-
return deterministicRandom(seed);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Sends signal data into any other paused thread (which is currently
|
|
389
|
-
* awaiting the signal)
|
|
390
|
-
* @param {string} signalId - the signal id
|
|
391
|
-
* @param {Record<any, any>} data - the signal data
|
|
392
|
-
* @returns {Promise<string>} - the stream id
|
|
393
|
-
*/
|
|
394
|
-
static async signal(signalId: string, data: Record<any, any>): Promise<string> {
|
|
395
|
-
const store = asyncLocalStorage.getStore();
|
|
396
|
-
const workflowTopic = store.get('workflowTopic');
|
|
397
|
-
const namespace = store.get('namespace');
|
|
398
|
-
const hotMeshClient = await WorkerService.getHotMesh(workflowTopic, { namespace });
|
|
399
|
-
if (await WorkflowService.isSideEffectAllowed(hotMeshClient, 'signal')) {
|
|
400
|
-
return await hotMeshClient.hook(`${namespace}.wfs.signal`, { id: signalId, data });
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Spawns a hook from either the main thread or a hook thread with
|
|
406
|
-
* the provided options; worflowId/TaskQueue/Name are optional and will
|
|
407
|
-
* default to the current workflowId/WorkflowTopic if not provided
|
|
408
|
-
* @param {HookOptions} options - the hook options
|
|
409
|
-
*/
|
|
410
|
-
static async hook(options: HookOptions): Promise<string> {
|
|
411
|
-
const {
|
|
412
|
-
workflowId,
|
|
413
|
-
namespace,
|
|
414
|
-
workflowTopic,
|
|
415
|
-
} = WorkflowService.getContext();
|
|
416
|
-
const hotMeshClient = await WorkerService.getHotMesh(workflowTopic, { namespace });
|
|
417
|
-
if (await WorkflowService.isSideEffectAllowed(hotMeshClient, 'hook')) {
|
|
418
|
-
const targetWorkflowId = options.workflowId ?? workflowId;
|
|
419
|
-
let targetTopic: string;
|
|
420
|
-
if (options.entity || (options.taskQueue && options.workflowName)) {
|
|
421
|
-
targetTopic = `${options.entity ?? options.taskQueue}-${options.entity ?? options.workflowName}`;
|
|
422
|
-
} else {
|
|
423
|
-
targetTopic = workflowTopic;
|
|
424
|
-
}
|
|
425
|
-
const payload = {
|
|
426
|
-
arguments: [...options.args],
|
|
427
|
-
id: targetWorkflowId,
|
|
428
|
-
workflowTopic,
|
|
429
|
-
backoffCoefficient: options.config?.backoffCoefficient || HMSH_DURABLE_EXP_BACKOFF,
|
|
430
|
-
}
|
|
431
|
-
return await hotMeshClient.hook(`${namespace}.flow.signal`, payload, StreamStatus.PENDING, 202);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
/**
|
|
436
|
-
* Executes a function once and caches the result. If the function is called
|
|
437
|
-
* again, the cached result is returned. This is useful for wrapping
|
|
438
|
-
* expensive activity calls that should only be run once, but which might
|
|
439
|
-
* not require the cost and safety provided by proxyActivities.
|
|
440
|
-
* @template T - the result type
|
|
441
|
-
*/
|
|
442
|
-
static async once<T>(fn: (...args: any[]) => Promise<T>, ...args: any[]): Promise<T> {
|
|
443
|
-
const {
|
|
444
|
-
COUNTER,
|
|
445
|
-
namespace,
|
|
446
|
-
workflowId,
|
|
447
|
-
workflowTopic,
|
|
448
|
-
workflowDimension,
|
|
449
|
-
replay,
|
|
450
|
-
} = WorkflowService.getContext();
|
|
451
|
-
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
452
|
-
const sessionId = `-once${workflowDimension}-${execIndex}-`;
|
|
453
|
-
if (sessionId in replay) {
|
|
454
|
-
return SerializerService.fromString(replay[sessionId]).data as T;
|
|
455
|
-
}
|
|
456
|
-
const hotMeshClient = await WorkerService.getHotMesh(workflowTopic, { namespace });
|
|
457
|
-
const keyParams = {
|
|
458
|
-
appId: hotMeshClient.appId,
|
|
459
|
-
jobId: workflowId
|
|
460
|
-
}
|
|
461
|
-
const workflowGuid = KeyService.mintKey(hotMeshClient.namespace, KeyType.JOB_STATE, keyParams);
|
|
462
|
-
const t1 = new Date();
|
|
463
|
-
const response = await fn(...args);
|
|
464
|
-
const t2 = new Date();
|
|
465
|
-
const payload = {
|
|
466
|
-
data: response,
|
|
467
|
-
ac: formatISODate(t1),
|
|
468
|
-
au: formatISODate(t2),
|
|
469
|
-
};
|
|
470
|
-
await hotMeshClient.engine.store.exec('HSET', workflowGuid, sessionId, SerializerService.toString(payload));
|
|
471
|
-
return response;
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
/**
|
|
475
|
-
* Interrupts a running job
|
|
476
|
-
*/
|
|
477
|
-
static async interrupt(jobId: string, options: JobInterruptOptions = {}): Promise<string | void> {
|
|
478
|
-
const { workflowTopic, namespace } = WorkflowService.getContext();
|
|
479
|
-
const hotMeshClient = await WorkerService.getHotMesh(workflowTopic, { namespace });
|
|
480
|
-
if (await WorkflowService.isSideEffectAllowed(hotMeshClient, 'interrupt')) {
|
|
481
|
-
return await hotMeshClient.interrupt(`${hotMeshClient.appId}.execute`, jobId, options);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* Sleeps the workflow for a duration. As the function is reentrant,
|
|
487
|
-
* upon reentry, the function will traverse prior execution paths up
|
|
488
|
-
* until the sleep command and then resume execution thereafter.
|
|
489
|
-
* @param {string} duration - See the `ms` package for syntax examples: '1 minute', '2 hours', '3 days'
|
|
490
|
-
* @returns {Promise<number>} - resolved duration in seconds
|
|
491
|
-
*/
|
|
492
|
-
static async sleepFor(duration: string): Promise<number> {
|
|
493
|
-
//SYNC
|
|
494
|
-
//return early if this sleep command has already run
|
|
495
|
-
const [didRun, execIndex, result] = await WorkflowService.didRun('sleep');
|
|
496
|
-
if (didRun) {
|
|
497
|
-
return (result as { completion: string, duration: number }).duration; //in seconds
|
|
498
|
-
}
|
|
499
|
-
//package the interruption inputs
|
|
500
|
-
const store = asyncLocalStorage.getStore();
|
|
501
|
-
const interruptionRegistry = store.get('interruptionRegistry');
|
|
502
|
-
const workflowId = store.get('workflowId');
|
|
503
|
-
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
504
|
-
const interruptionMessage = {
|
|
505
|
-
workflowId,
|
|
506
|
-
duration: ms(duration) / 1000,
|
|
507
|
-
index: execIndex,
|
|
508
|
-
workflowDimension,
|
|
509
|
-
}
|
|
510
|
-
interruptionRegistry.push({
|
|
511
|
-
code: HMSH_CODE_DURABLE_SLEEP,
|
|
512
|
-
...interruptionMessage,
|
|
513
|
-
});
|
|
514
|
-
//ASYNC
|
|
515
|
-
//sleep to allow other interruptions to be packaged and registered
|
|
516
|
-
await sleepFor(0);
|
|
517
|
-
// NOTE: If you are reading this in the stack trace, await `sleepFor`
|
|
518
|
-
throw new DurableSleepError(interruptionMessage);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
/**
|
|
522
|
-
* Pauses the workflow until `signalId` is received.
|
|
523
|
-
* @template T - the result type
|
|
524
|
-
* @param {string} signalId - a unique, shareable guid (e.g, 'abc123')
|
|
525
|
-
* @returns {Promise<T>}
|
|
526
|
-
* @example
|
|
527
|
-
* const result = await Durable.workflow.waitFor<typeof resultType>('abc123');
|
|
528
|
-
*/
|
|
529
|
-
static async waitFor<T>(signalId: string): Promise<T> {
|
|
530
|
-
//SYNC
|
|
531
|
-
//return early if this waitFor command has already run
|
|
532
|
-
const [didRun, execIndex, result] = await WorkflowService.didRun('wait');
|
|
533
|
-
if (didRun) {
|
|
534
|
-
return (result as { id: string, data: { data: T }}).data.data as T;
|
|
535
|
-
}
|
|
536
|
-
//package the interruption inputs
|
|
537
|
-
const store = asyncLocalStorage.getStore();
|
|
538
|
-
const interruptionRegistry = store.get('interruptionRegistry');
|
|
539
|
-
const workflowId = store.get('workflowId');
|
|
540
|
-
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
541
|
-
const interruptionMessage = {
|
|
542
|
-
workflowId,
|
|
543
|
-
signalId,
|
|
544
|
-
index: execIndex,
|
|
545
|
-
workflowDimension,
|
|
546
|
-
}
|
|
547
|
-
interruptionRegistry.push({
|
|
548
|
-
code: HMSH_CODE_DURABLE_WAIT,
|
|
549
|
-
...interruptionMessage,
|
|
550
|
-
});
|
|
551
|
-
//ASYNC
|
|
552
|
-
//sleep to allow other interruptions to be packaged and registered
|
|
553
|
-
await sleepFor(0);
|
|
554
|
-
// NOTE: If you are reading this in the stack trace, await `waitFor`
|
|
555
|
-
throw new DurableWaitForError(interruptionMessage);
|
|
556
|
-
}
|
|
557
|
-
}
|