@hotmeshio/hotmesh 0.1.15 → 0.1.17
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 +623 -209
- package/build/index.d.ts +14 -3
- package/build/index.js +17 -4
- package/build/modules/enums.d.ts +12 -12
- package/build/modules/enums.js +15 -25
- package/build/modules/errors.d.ts +16 -16
- package/build/modules/errors.js +28 -28
- package/build/modules/key.d.ts +0 -37
- package/build/modules/key.js +4 -45
- package/build/modules/utils.d.ts +7 -15
- package/build/modules/utils.js +21 -44
- package/build/package.json +18 -15
- package/build/services/activities/activity.d.ts +0 -31
- package/build/services/activities/activity.js +1 -50
- 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 +4 -5
- package/build/services/activities/trigger.js +22 -16
- 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.js +0 -2
- package/build/services/connector/clients/redis.js +0 -2
- package/build/services/connector/index.js +0 -2
- package/build/services/engine/index.d.ts +1 -10
- package/build/services/engine/index.js +1 -48
- package/build/services/exporter/index.d.ts +0 -27
- package/build/services/exporter/index.js +0 -33
- package/build/services/hotmesh/index.d.ts +8 -4
- package/build/services/hotmesh/index.js +20 -19
- 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/meshcall/index.d.ts +21 -0
- package/build/services/meshcall/index.js +202 -0
- package/build/services/meshcall/schemas/factory.d.ts +2 -0
- package/build/services/meshcall/schemas/factory.js +179 -0
- package/build/services/meshdata/index.d.ts +75 -0
- package/build/services/meshdata/index.js +541 -0
- package/build/services/meshflow/client.d.ts +18 -0
- package/build/services/{durable → meshflow}/client.js +9 -40
- package/build/services/{durable → meshflow}/connection.d.ts +2 -1
- package/build/services/{durable → meshflow}/connection.js +1 -0
- package/build/services/meshflow/exporter.d.ts +29 -0
- package/build/services/{durable → meshflow}/exporter.js +0 -29
- package/build/services/meshflow/handle.d.ts +22 -0
- package/build/services/{durable → meshflow}/handle.js +0 -46
- package/build/services/meshflow/index.d.ts +17 -0
- package/build/services/meshflow/index.js +23 -0
- package/build/services/meshflow/schemas/factory.d.ts +4 -0
- package/build/services/{durable → meshflow}/schemas/factory.js +2 -30
- package/build/services/meshflow/search.d.ts +23 -0
- package/build/services/{durable → meshflow}/search.js +0 -99
- package/build/services/{durable → meshflow}/worker.d.ts +3 -2
- package/build/services/{durable → meshflow}/worker.js +23 -39
- package/build/services/meshflow/workflow.d.ts +27 -0
- package/build/services/{durable → meshflow}/workflow.js +27 -169
- 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 +1 -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 -30
- package/build/services/serializer/index.js +6 -23
- package/build/services/store/cache.d.ts +0 -19
- package/build/services/store/cache.js +0 -19
- package/build/services/store/clients/ioredis.d.ts +0 -6
- package/build/services/store/clients/ioredis.js +0 -7
- package/build/services/store/clients/redis.d.ts +0 -6
- package/build/services/store/clients/redis.js +0 -6
- package/build/services/store/index.d.ts +0 -55
- package/build/services/store/index.js +14 -87
- package/build/services/stream/clients/ioredis.js +1 -4
- 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 +1 -4
- package/build/services/worker/index.js +0 -6
- package/build/types/activity.d.ts +0 -81
- package/build/types/error.d.ts +5 -5
- package/build/types/exporter.d.ts +1 -14
- package/build/types/hotmesh.d.ts +4 -12
- package/build/types/hotmesh.js +0 -3
- package/build/types/index.d.ts +5 -3
- package/build/types/index.js +1 -1
- package/build/types/job.d.ts +1 -95
- package/build/types/meshcall.d.ts +54 -0
- package/build/types/meshdata.d.ts +59 -0
- package/build/types/meshdata.js +2 -0
- package/build/types/meshflow.d.ts +202 -0
- package/build/types/meshflow.js +2 -0
- package/build/types/pipe.d.ts +0 -65
- package/build/types/quorum.d.ts +0 -12
- package/build/types/redis.d.ts +0 -6
- package/build/types/stream.d.ts +0 -59
- package/build/types/stream.js +0 -4
- package/index.ts +12 -3
- package/package.json +18 -15
- package/typedoc.json +38 -0
- package/types/error.ts +5 -5
- package/types/exporter.ts +1 -1
- package/types/hotmesh.ts +3 -2
- package/types/index.ts +25 -7
- package/types/job.ts +19 -1
- package/types/meshcall.ts +192 -0
- package/types/meshdata.ts +273 -0
- package/types/{durable.ts → meshflow.ts} +33 -9
- package/build/services/durable/client.d.ts +0 -49
- package/build/services/durable/exporter.d.ts +0 -51
- package/build/services/durable/handle.d.ts +0 -58
- package/build/services/durable/index.d.ts +0 -19
- package/build/services/durable/index.js +0 -25
- package/build/services/durable/schemas/factory.d.ts +0 -33
- package/build/services/durable/search.d.ts +0 -120
- package/build/services/durable/workflow.d.ts +0 -143
- package/build/types/durable.d.ts +0 -467
- /package/build/types/{durable.js → meshcall.js} +0 -0
|
@@ -15,6 +15,7 @@ const stream_1 = require("../../types/stream");
|
|
|
15
15
|
const search_1 = require("./search");
|
|
16
16
|
const factory_1 = require("./schemas/factory");
|
|
17
17
|
class WorkerService {
|
|
18
|
+
constructor() { }
|
|
18
19
|
static async activateWorkflow(hotMesh) {
|
|
19
20
|
const app = await hotMesh.engine.store.getApp(hotMesh.engine.appId);
|
|
20
21
|
const appVersion = app?.version;
|
|
@@ -63,7 +64,6 @@ class WorkerService {
|
|
|
63
64
|
const baseTopic = `${config.taskQueue}-${workflowFunctionName}`;
|
|
64
65
|
const activityTopic = `${baseTopic}-activity`;
|
|
65
66
|
const workflowTopic = `${baseTopic}`;
|
|
66
|
-
//initialize supporting workflows
|
|
67
67
|
const worker = new WorkerService();
|
|
68
68
|
worker.activityRunner = await worker.initActivityWorker(config, activityTopic);
|
|
69
69
|
worker.workflowRunner = await worker.initWorkflowWorker(config, workflowTopic, workflowFunction);
|
|
@@ -95,7 +95,8 @@ class WorkerService {
|
|
|
95
95
|
const targetNamespace = config?.namespace ?? factory_1.APP_ID;
|
|
96
96
|
const optionsHash = (0, utils_1.hashOptions)(config?.connection?.options);
|
|
97
97
|
const targetTopic = `${optionsHash}.${targetNamespace}.${activityTopic}`;
|
|
98
|
-
const hotMeshWorker = await hotmesh_1.
|
|
98
|
+
const hotMeshWorker = await hotmesh_1.HotMesh.init({
|
|
99
|
+
guid: config.guid ? `${config.guid}XA` : undefined,
|
|
99
100
|
logLevel: config.options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
|
|
100
101
|
appId: targetNamespace,
|
|
101
102
|
engine: { redis: redisConfig },
|
|
@@ -113,7 +114,6 @@ class WorkerService {
|
|
|
113
114
|
wrapActivityFunctions() {
|
|
114
115
|
return async (data) => {
|
|
115
116
|
try {
|
|
116
|
-
//always run the activity function when instructed; return the response
|
|
117
117
|
const activityInput = data.data;
|
|
118
118
|
const activityName = activityInput.activityName;
|
|
119
119
|
const activityFunction = WorkerService.activityRegistry[activityName];
|
|
@@ -130,14 +130,12 @@ class WorkerService {
|
|
|
130
130
|
message: err.message,
|
|
131
131
|
stack: err.stack,
|
|
132
132
|
});
|
|
133
|
-
if (!(err instanceof errors_1.
|
|
134
|
-
!(err instanceof errors_1.
|
|
135
|
-
!(err instanceof errors_1.
|
|
136
|
-
//use code 599 as a proxy for all retryable errors
|
|
137
|
-
// (basically anything not 596, 597, 598)
|
|
133
|
+
if (!(err instanceof errors_1.MeshFlowTimeoutError) &&
|
|
134
|
+
!(err instanceof errors_1.MeshFlowMaxedError) &&
|
|
135
|
+
!(err instanceof errors_1.MeshFlowFatalError)) {
|
|
138
136
|
return {
|
|
139
137
|
status: stream_1.StreamStatus.SUCCESS,
|
|
140
|
-
code: enums_1.
|
|
138
|
+
code: enums_1.HMSH_CODE_MESHFLOW_RETRYABLE,
|
|
141
139
|
metadata: { ...data.metadata },
|
|
142
140
|
data: {
|
|
143
141
|
$error: {
|
|
@@ -149,8 +147,6 @@ class WorkerService {
|
|
|
149
147
|
};
|
|
150
148
|
}
|
|
151
149
|
return {
|
|
152
|
-
//always returrn success (the Durable module is just fine);
|
|
153
|
-
// it's the user's function that has failed
|
|
154
150
|
status: stream_1.StreamStatus.SUCCESS,
|
|
155
151
|
code: err.code,
|
|
156
152
|
stack: err.stack,
|
|
@@ -175,7 +171,8 @@ class WorkerService {
|
|
|
175
171
|
const targetNamespace = config?.namespace ?? factory_1.APP_ID;
|
|
176
172
|
const optionsHash = (0, utils_1.hashOptions)(config?.connection?.options);
|
|
177
173
|
const targetTopic = `${optionsHash}.${targetNamespace}.${workflowTopic}`;
|
|
178
|
-
const hotMeshWorker = await hotmesh_1.
|
|
174
|
+
const hotMeshWorker = await hotmesh_1.HotMesh.init({
|
|
175
|
+
guid: config.guid,
|
|
179
176
|
logLevel: config.options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
|
|
180
177
|
appId: config.namespace ?? factory_1.APP_ID,
|
|
181
178
|
engine: { redis: redisConfig },
|
|
@@ -196,7 +193,6 @@ class WorkerService {
|
|
|
196
193
|
const interruptionRegistry = [];
|
|
197
194
|
let isProcessing = false;
|
|
198
195
|
try {
|
|
199
|
-
//incoming data payload has arguments and workflowId
|
|
200
196
|
const workflowInput = data.data;
|
|
201
197
|
const context = new Map();
|
|
202
198
|
context.set('canRetry', workflowInput.canRetry);
|
|
@@ -207,20 +203,14 @@ class WorkerService {
|
|
|
207
203
|
context.set('raw', data);
|
|
208
204
|
context.set('workflowId', workflowInput.workflowId);
|
|
209
205
|
if (workflowInput.originJobId) {
|
|
210
|
-
//if present there is an origin job to which this job is subordinated;
|
|
211
|
-
// garbage collect (expire) this job when originJobId is expired
|
|
212
206
|
context.set('originJobId', workflowInput.originJobId);
|
|
213
207
|
}
|
|
214
208
|
let replayQuery = '';
|
|
215
209
|
if (workflowInput.workflowDimension) {
|
|
216
|
-
//every hook function runs in an isolated dimension controlled
|
|
217
|
-
//by the index assigned when the signal was received; even if the
|
|
218
|
-
//hook function re-runs, its scope will always remain constant
|
|
219
210
|
context.set('workflowDimension', workflowInput.workflowDimension);
|
|
220
211
|
replayQuery = `-*${workflowInput.workflowDimension}-*`;
|
|
221
212
|
}
|
|
222
213
|
else {
|
|
223
|
-
//last letter of words like 'hook', 'sleep', 'wait', 'signal', 'search', 'start', 'proxy', 'child', 'collator'
|
|
224
214
|
replayQuery = '-*[ehklptydr]-*';
|
|
225
215
|
}
|
|
226
216
|
context.set('workflowTopic', workflowTopic);
|
|
@@ -230,7 +220,7 @@ class WorkerService {
|
|
|
230
220
|
const store = this.workflowRunner.engine.store;
|
|
231
221
|
const [cursor, replay] = await store.findJobFields(workflowInput.workflowId, replayQuery, 50000, 5000);
|
|
232
222
|
context.set('replay', replay);
|
|
233
|
-
context.set('cursor', cursor);
|
|
223
|
+
context.set('cursor', cursor);
|
|
234
224
|
const workflowResponse = await storage_1.asyncLocalStorage.run(context, async () => {
|
|
235
225
|
return await workflowFunction.apply(this, workflowInput.arguments);
|
|
236
226
|
});
|
|
@@ -245,20 +235,19 @@ class WorkerService {
|
|
|
245
235
|
if (isProcessing) {
|
|
246
236
|
return;
|
|
247
237
|
}
|
|
248
|
-
if (err instanceof errors_1.
|
|
238
|
+
if (err instanceof errors_1.MeshFlowWaitForError ||
|
|
249
239
|
interruptionRegistry.length > 1) {
|
|
250
240
|
isProcessing = true;
|
|
251
|
-
//NOTE: this type is spawned when `Promise.all` is used OR if the interruption is a `waitFor`
|
|
252
241
|
const workflowInput = data.data;
|
|
253
242
|
const execIndex = counter.counter - interruptionRegistry.length + 1;
|
|
254
243
|
const { workflowId, workflowTopic, workflowDimension, originJobId } = workflowInput;
|
|
255
244
|
const collatorFlowId = `${(0, utils_1.guid)()}$C`;
|
|
256
245
|
return {
|
|
257
246
|
status: stream_1.StreamStatus.SUCCESS,
|
|
258
|
-
code: enums_1.
|
|
247
|
+
code: enums_1.HMSH_CODE_MESHFLOW_ALL,
|
|
259
248
|
metadata: { ...data.metadata },
|
|
260
249
|
data: {
|
|
261
|
-
code: enums_1.
|
|
250
|
+
code: enums_1.HMSH_CODE_MESHFLOW_ALL,
|
|
262
251
|
items: [...interruptionRegistry],
|
|
263
252
|
size: interruptionRegistry.length,
|
|
264
253
|
workflowDimension: workflowDimension || '',
|
|
@@ -270,8 +259,7 @@ class WorkerService {
|
|
|
270
259
|
},
|
|
271
260
|
};
|
|
272
261
|
}
|
|
273
|
-
else if (err instanceof errors_1.
|
|
274
|
-
//return the sleep interruption
|
|
262
|
+
else if (err instanceof errors_1.MeshFlowSleepError) {
|
|
275
263
|
isProcessing = true;
|
|
276
264
|
return {
|
|
277
265
|
status: stream_1.StreamStatus.SUCCESS,
|
|
@@ -290,8 +278,7 @@ class WorkerService {
|
|
|
290
278
|
},
|
|
291
279
|
};
|
|
292
280
|
}
|
|
293
|
-
else if (err instanceof errors_1.
|
|
294
|
-
//return the proxyActivity interruption
|
|
281
|
+
else if (err instanceof errors_1.MeshFlowProxyError) {
|
|
295
282
|
isProcessing = true;
|
|
296
283
|
return {
|
|
297
284
|
status: stream_1.StreamStatus.SUCCESS,
|
|
@@ -320,8 +307,7 @@ class WorkerService {
|
|
|
320
307
|
},
|
|
321
308
|
};
|
|
322
309
|
}
|
|
323
|
-
else if (err instanceof errors_1.
|
|
324
|
-
//return the child interruption
|
|
310
|
+
else if (err instanceof errors_1.MeshFlowChildError) {
|
|
325
311
|
isProcessing = true;
|
|
326
312
|
const msg = {
|
|
327
313
|
message: err.message,
|
|
@@ -335,12 +321,12 @@ class WorkerService {
|
|
|
335
321
|
data: {
|
|
336
322
|
arguments: err.arguments,
|
|
337
323
|
await: err.await,
|
|
338
|
-
backoffCoefficient: err.backoffCoefficient || enums_1.
|
|
324
|
+
backoffCoefficient: err.backoffCoefficient || enums_1.HMSH_MESHFLOW_EXP_BACKOFF,
|
|
339
325
|
code: err.code,
|
|
340
326
|
index: err.index,
|
|
341
327
|
message: JSON.stringify(msg),
|
|
342
|
-
maximumAttempts: err.maximumAttempts || enums_1.
|
|
343
|
-
maximumInterval: err.maximumInterval || (0, ms_1.default)(enums_1.
|
|
328
|
+
maximumAttempts: err.maximumAttempts || enums_1.HMSH_MESHFLOW_MAX_ATTEMPTS,
|
|
329
|
+
maximumInterval: err.maximumInterval || (0, ms_1.default)(enums_1.HMSH_MESHFLOW_MAX_INTERVAL) / 1000,
|
|
344
330
|
originJobId: err.originJobId,
|
|
345
331
|
parentWorkflowId: err.parentWorkflowId,
|
|
346
332
|
expire: err.expire,
|
|
@@ -351,12 +337,10 @@ class WorkerService {
|
|
|
351
337
|
},
|
|
352
338
|
};
|
|
353
339
|
}
|
|
354
|
-
// ALL other errors are actual fatal errors (598, 597, 596)
|
|
355
|
-
// OR will be retried (599)
|
|
356
340
|
isProcessing = true;
|
|
357
341
|
return {
|
|
358
342
|
status: stream_1.StreamStatus.SUCCESS,
|
|
359
|
-
code: err.code || new errors_1.
|
|
343
|
+
code: err.code || new errors_1.MeshFlowRetryError(err.message).code,
|
|
360
344
|
metadata: { ...data.metadata },
|
|
361
345
|
data: {
|
|
362
346
|
$error: {
|
|
@@ -364,7 +348,7 @@ class WorkerService {
|
|
|
364
348
|
type: err.name,
|
|
365
349
|
name: err.name,
|
|
366
350
|
stack: err.stack,
|
|
367
|
-
code: err.code || new errors_1.
|
|
351
|
+
code: err.code || new errors_1.MeshFlowRetryError(err.message).code,
|
|
368
352
|
},
|
|
369
353
|
},
|
|
370
354
|
};
|
|
@@ -378,7 +362,7 @@ class WorkerService {
|
|
|
378
362
|
}
|
|
379
363
|
}
|
|
380
364
|
_a = WorkerService;
|
|
381
|
-
WorkerService.activityRegistry = {};
|
|
365
|
+
WorkerService.activityRegistry = {};
|
|
382
366
|
WorkerService.instances = new Map();
|
|
383
367
|
WorkerService.getHotMesh = async (workflowTopic, config, options) => {
|
|
384
368
|
const targetNamespace = config?.namespace ?? factory_1.APP_ID;
|
|
@@ -387,7 +371,7 @@ WorkerService.getHotMesh = async (workflowTopic, config, options) => {
|
|
|
387
371
|
if (WorkerService.instances.has(targetTopic)) {
|
|
388
372
|
return await WorkerService.instances.get(targetTopic);
|
|
389
373
|
}
|
|
390
|
-
const hotMeshClient = hotmesh_1.
|
|
374
|
+
const hotMeshClient = hotmesh_1.HotMesh.init({
|
|
391
375
|
logLevel: options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
|
|
392
376
|
appId: targetNamespace,
|
|
393
377
|
engine: {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { HotMesh } from '../hotmesh';
|
|
2
|
+
import { ActivityConfig, HookOptions, ProxyType, WorkflowContext, WorkflowOptions } from '../../types/meshflow';
|
|
3
|
+
import { JobInterruptOptions } from '../../types/job';
|
|
4
|
+
import { MeshFlowChildErrorType, MeshFlowProxyErrorType } from '../../types/error';
|
|
5
|
+
import { Search } from './search';
|
|
6
|
+
export declare class WorkflowService {
|
|
7
|
+
constructor();
|
|
8
|
+
static didRun(prefix: string): Promise<[boolean, number, any]>;
|
|
9
|
+
static isSideEffectAllowed(hotMeshClient: HotMesh, prefix: string): Promise<boolean>;
|
|
10
|
+
static getContext(): WorkflowContext;
|
|
11
|
+
static getHotMesh(): Promise<HotMesh>;
|
|
12
|
+
static execChild<T>(options: WorkflowOptions): Promise<T>;
|
|
13
|
+
static getChildInterruptPayload(context: WorkflowContext, options: WorkflowOptions, execIndex: number): MeshFlowChildErrorType;
|
|
14
|
+
static startChild(options: WorkflowOptions): Promise<string>;
|
|
15
|
+
static proxyActivities<ACT>(options?: ActivityConfig): ProxyType<ACT>;
|
|
16
|
+
static wrapActivity<T>(activityName: string, options?: ActivityConfig): T;
|
|
17
|
+
static getProxyInterruptPayload(context: WorkflowContext, activityName: string, execIndex: number, args: any[], options?: ActivityConfig): MeshFlowProxyErrorType;
|
|
18
|
+
static search(): Promise<Search>;
|
|
19
|
+
static random(): number;
|
|
20
|
+
static signal(signalId: string, data: Record<any, any>): Promise<string>;
|
|
21
|
+
static hook(options: HookOptions): Promise<string>;
|
|
22
|
+
static once<T>(fn: (...args: any[]) => Promise<T>, ...args: any[]): Promise<T>;
|
|
23
|
+
static interrupt(jobId: string, options?: JobInterruptOptions): Promise<string | void>;
|
|
24
|
+
static all<T>(...promises: Promise<T>[]): Promise<T[]>;
|
|
25
|
+
static sleepFor(duration: string): Promise<number>;
|
|
26
|
+
static waitFor<T>(signalId: string): Promise<T>;
|
|
27
|
+
}
|
|
@@ -15,13 +15,7 @@ const enums_1 = require("../../modules/enums");
|
|
|
15
15
|
const worker_1 = require("./worker");
|
|
16
16
|
const search_1 = require("./search");
|
|
17
17
|
class WorkflowService {
|
|
18
|
-
|
|
19
|
-
* Returns the synchronous output from the activity (replay)
|
|
20
|
-
* if available locally, revealing whether or not the activity already
|
|
21
|
-
* ran during a prior execution cycle
|
|
22
|
-
* @param {string} prefix - one of: proxy, child, start, wait etc
|
|
23
|
-
* @returns
|
|
24
|
-
*/
|
|
18
|
+
constructor() { }
|
|
25
19
|
static async didRun(prefix) {
|
|
26
20
|
const { COUNTER, replay, workflowDimension } = WorkflowService.getContext();
|
|
27
21
|
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
@@ -32,12 +26,6 @@ class WorkflowService {
|
|
|
32
26
|
}
|
|
33
27
|
return [false, execIndex, null];
|
|
34
28
|
}
|
|
35
|
-
/**
|
|
36
|
-
* Those methods that may only be called once must be protected by flagging
|
|
37
|
-
* their execution with a unique key (the key is stored in the HASH alongside
|
|
38
|
-
* process state and job state)
|
|
39
|
-
* @private
|
|
40
|
-
*/
|
|
41
29
|
static async isSideEffectAllowed(hotMeshClient, prefix) {
|
|
42
30
|
const store = storage_1.asyncLocalStorage.getStore();
|
|
43
31
|
const workflowId = store.get('workflowId');
|
|
@@ -57,10 +45,6 @@ class WorkflowService {
|
|
|
57
45
|
const guidValue = Number((await hotMeshClient.engine.store.exec('HINCRBYFLOAT', workflowGuid, sessionId, '1')));
|
|
58
46
|
return guidValue === 1;
|
|
59
47
|
}
|
|
60
|
-
/**
|
|
61
|
-
* Returns the current workflow context restored
|
|
62
|
-
* from Redis
|
|
63
|
-
*/
|
|
64
48
|
static getContext() {
|
|
65
49
|
const store = storage_1.asyncLocalStorage.getStore();
|
|
66
50
|
const workflowId = store.get('workflowId');
|
|
@@ -95,10 +79,6 @@ class WorkflowService {
|
|
|
95
79
|
workflowSpan,
|
|
96
80
|
};
|
|
97
81
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Return a handle to the hotmesh client hosting the workflow execution
|
|
100
|
-
* @returns {Promise<HotMesh>} - a hotmesh client
|
|
101
|
-
*/
|
|
102
82
|
static async getHotMesh() {
|
|
103
83
|
const store = storage_1.asyncLocalStorage.getStore();
|
|
104
84
|
const workflowTopic = store.get('workflowTopic');
|
|
@@ -106,17 +86,7 @@ class WorkflowService {
|
|
|
106
86
|
const namespace = store.get('namespace');
|
|
107
87
|
return await worker_1.WorkerService.getHotMesh(workflowTopic, { connection, namespace });
|
|
108
88
|
}
|
|
109
|
-
/**
|
|
110
|
-
* Spawns a child workflow and awaits the return.
|
|
111
|
-
* @template T - the result type
|
|
112
|
-
* @param {WorkflowOptions} options - the workflow options
|
|
113
|
-
* @returns {Promise<T>} - the result of the child workflow
|
|
114
|
-
* @example
|
|
115
|
-
* const result = await Durable.workflow.execChild<typeof resultType>({ ...options });
|
|
116
|
-
*/
|
|
117
89
|
static async execChild(options) {
|
|
118
|
-
//SYNC
|
|
119
|
-
//check if the activity already ran (check $error/done)
|
|
120
90
|
const isStartChild = options.await === false;
|
|
121
91
|
const prefix = isStartChild ? 'start' : 'child';
|
|
122
92
|
const [didRun, execIndex, result] = await WorkflowService.didRun(prefix);
|
|
@@ -127,21 +97,20 @@ class WorkflowService {
|
|
|
127
97
|
(!result.$error.is_stream_error ||
|
|
128
98
|
result.$error.is_stream_error && !canRetry)) {
|
|
129
99
|
if (options?.config?.throwOnError !== false) {
|
|
130
|
-
//rethrow remote execution error (simulates local failure)
|
|
131
100
|
const code = result.$error.code;
|
|
132
101
|
const message = result.$error.message;
|
|
133
102
|
const stack = result.$error.stack;
|
|
134
|
-
if (code === enums_1.
|
|
135
|
-
throw new errors_1.
|
|
103
|
+
if (code === enums_1.HMSH_CODE_MESHFLOW_FATAL) {
|
|
104
|
+
throw new errors_1.MeshFlowFatalError(message, stack);
|
|
136
105
|
}
|
|
137
|
-
else if (code == enums_1.
|
|
138
|
-
throw new errors_1.
|
|
106
|
+
else if (code == enums_1.HMSH_CODE_MESHFLOW_MAXED) {
|
|
107
|
+
throw new errors_1.MeshFlowMaxedError(message, stack);
|
|
139
108
|
}
|
|
140
|
-
else if (code == enums_1.
|
|
141
|
-
throw new errors_1.
|
|
109
|
+
else if (code == enums_1.HMSH_CODE_MESHFLOW_TIMEOUT) {
|
|
110
|
+
throw new errors_1.MeshFlowTimeoutError(message, stack);
|
|
142
111
|
}
|
|
143
112
|
else {
|
|
144
|
-
throw new errors_1.
|
|
113
|
+
throw new errors_1.MeshFlowRetryError(message, stack);
|
|
145
114
|
}
|
|
146
115
|
}
|
|
147
116
|
return result.$error;
|
|
@@ -151,20 +120,13 @@ class WorkflowService {
|
|
|
151
120
|
}
|
|
152
121
|
}
|
|
153
122
|
const interruptionMessage = WorkflowService.getChildInterruptPayload(context, options, execIndex);
|
|
154
|
-
//push the packaged inputs to the registry
|
|
155
123
|
interruptionRegistry.push({
|
|
156
|
-
code: enums_1.
|
|
124
|
+
code: enums_1.HMSH_CODE_MESHFLOW_CHILD,
|
|
157
125
|
...interruptionMessage,
|
|
158
126
|
});
|
|
159
|
-
//ASYNC
|
|
160
|
-
//sleep (allow others to be packaged / registered) and throw the error
|
|
161
127
|
await (0, utils_1.sleepImmediate)();
|
|
162
|
-
throw new errors_1.
|
|
128
|
+
throw new errors_1.MeshFlowChildError(interruptionMessage);
|
|
163
129
|
}
|
|
164
|
-
/**
|
|
165
|
-
* constructs the payload necessary to spawn a child job
|
|
166
|
-
* @private
|
|
167
|
-
*/
|
|
168
130
|
static getChildInterruptPayload(context, options, execIndex) {
|
|
169
131
|
const { workflowId, originJobId, workflowDimension } = context;
|
|
170
132
|
let childJobId;
|
|
@@ -184,10 +146,10 @@ class WorkflowService {
|
|
|
184
146
|
return {
|
|
185
147
|
arguments: [...(options.args || [])],
|
|
186
148
|
await: options?.await ?? true,
|
|
187
|
-
backoffCoefficient: options?.config?.backoffCoefficient ?? enums_1.
|
|
149
|
+
backoffCoefficient: options?.config?.backoffCoefficient ?? enums_1.HMSH_MESHFLOW_EXP_BACKOFF,
|
|
188
150
|
index: execIndex,
|
|
189
|
-
maximumAttempts: options?.config?.maximumAttempts ?? enums_1.
|
|
190
|
-
maximumInterval: (0, ms_1.default)(options?.config?.maximumInterval ?? enums_1.
|
|
151
|
+
maximumAttempts: options?.config?.maximumAttempts ?? enums_1.HMSH_MESHFLOW_MAX_ATTEMPTS,
|
|
152
|
+
maximumInterval: (0, ms_1.default)(options?.config?.maximumInterval ?? enums_1.HMSH_MESHFLOW_MAX_INTERVAL) /
|
|
191
153
|
1000,
|
|
192
154
|
originJobId: originJobId ?? workflowId,
|
|
193
155
|
expire: options.expire,
|
|
@@ -198,37 +160,9 @@ class WorkflowService {
|
|
|
198
160
|
workflowTopic,
|
|
199
161
|
};
|
|
200
162
|
}
|
|
201
|
-
/**
|
|
202
|
-
* Spawns a child workflow and returns the child Job ID.
|
|
203
|
-
* This method guarantees the spawned child has reserved the Job ID,
|
|
204
|
-
* returning a 'DuplicateJobError' error if not. Otherwise,
|
|
205
|
-
* this is a fire-and-forget method.
|
|
206
|
-
*
|
|
207
|
-
* @param {WorkflowOptions} options - the workflow options
|
|
208
|
-
* @returns {Promise<string>} - the childJobId
|
|
209
|
-
* @example
|
|
210
|
-
* const childJobId = await Durable.workflow.startChild({ ...options });
|
|
211
|
-
*/
|
|
212
163
|
static async startChild(options) {
|
|
213
164
|
return WorkflowService.execChild({ ...options, await: false });
|
|
214
165
|
}
|
|
215
|
-
/**
|
|
216
|
-
* Wraps activities in a proxy that durably runs/re-runs them to completion.
|
|
217
|
-
* TODO: verify that activities do not collide if named same on same server but bound to different workflows
|
|
218
|
-
*
|
|
219
|
-
* @param {ActivityConfig} options - the activity configuration
|
|
220
|
-
* that will be used to wrap the activities.
|
|
221
|
-
* @returns {ProxyType<ACT>} - a proxy object with the same keys as the
|
|
222
|
-
* activities object, but with the values replaced by a wrapped function
|
|
223
|
-
*
|
|
224
|
-
* @example
|
|
225
|
-
* // import the activities
|
|
226
|
-
* import * as activities from './activities';
|
|
227
|
-
* const proxy = WorkflowService.proxyActivities<typeof activities>({ activities });
|
|
228
|
-
*
|
|
229
|
-
* //or destructure the proxy object, as the function names are the keys
|
|
230
|
-
* const { activity1, activity2 } = WorkflowService.proxyActivities<typeof activities>({ activities });
|
|
231
|
-
*/
|
|
232
166
|
static proxyActivities(options) {
|
|
233
167
|
if (options.activities) {
|
|
234
168
|
worker_1.WorkerService.registerActivities(options.activities);
|
|
@@ -245,49 +179,38 @@ class WorkflowService {
|
|
|
245
179
|
}
|
|
246
180
|
static wrapActivity(activityName, options) {
|
|
247
181
|
return async function () {
|
|
248
|
-
//SYNC
|
|
249
|
-
//check if the activity already ran
|
|
250
182
|
const [didRun, execIndex, result] = await WorkflowService.didRun('proxy');
|
|
251
183
|
if (didRun) {
|
|
252
184
|
if (result?.$error) {
|
|
253
185
|
if (options?.retryPolicy?.throwOnError !== false) {
|
|
254
|
-
//rethrow remote execution error (simulates throw)
|
|
255
186
|
const code = result.$error.code;
|
|
256
187
|
const message = result.$error.message;
|
|
257
188
|
const stack = result.$error.stack;
|
|
258
|
-
if (code === enums_1.
|
|
259
|
-
throw new errors_1.
|
|
189
|
+
if (code === enums_1.HMSH_CODE_MESHFLOW_FATAL) {
|
|
190
|
+
throw new errors_1.MeshFlowFatalError(message, stack);
|
|
260
191
|
}
|
|
261
|
-
else if (code == enums_1.
|
|
262
|
-
throw new errors_1.
|
|
192
|
+
else if (code == enums_1.HMSH_CODE_MESHFLOW_MAXED) {
|
|
193
|
+
throw new errors_1.MeshFlowMaxedError(message, stack);
|
|
263
194
|
}
|
|
264
|
-
else if (code == enums_1.
|
|
265
|
-
throw new errors_1.
|
|
195
|
+
else if (code == enums_1.HMSH_CODE_MESHFLOW_TIMEOUT) {
|
|
196
|
+
throw new errors_1.MeshFlowTimeoutError(message, stack);
|
|
266
197
|
}
|
|
267
198
|
}
|
|
268
199
|
return result.$error;
|
|
269
200
|
}
|
|
270
201
|
return result.data;
|
|
271
202
|
}
|
|
272
|
-
//package the interruption inputs
|
|
273
203
|
const context = WorkflowService.getContext();
|
|
274
204
|
const { interruptionRegistry } = context;
|
|
275
205
|
const interruptionMessage = WorkflowService.getProxyInterruptPayload(context, activityName, execIndex, Array.from(arguments), options);
|
|
276
|
-
//push the packaged inputs to the registry
|
|
277
206
|
interruptionRegistry.push({
|
|
278
|
-
code: enums_1.
|
|
207
|
+
code: enums_1.HMSH_CODE_MESHFLOW_PROXY,
|
|
279
208
|
...interruptionMessage,
|
|
280
209
|
});
|
|
281
|
-
//ASYNC
|
|
282
|
-
//sleep (allow others to be packaged / registered) and throw the error
|
|
283
210
|
await (0, utils_1.sleepImmediate)();
|
|
284
|
-
throw new errors_1.
|
|
211
|
+
throw new errors_1.MeshFlowProxyError(interruptionMessage);
|
|
285
212
|
};
|
|
286
213
|
}
|
|
287
|
-
/**
|
|
288
|
-
* constructs the payload necessary to spawn a proxyActivity job
|
|
289
|
-
* @private
|
|
290
|
-
*/
|
|
291
214
|
static getProxyInterruptPayload(context, activityName, execIndex, args, options) {
|
|
292
215
|
const { workflowDimension, workflowId, originJobId, workflowTopic } = context;
|
|
293
216
|
const activityTopic = `${workflowTopic}-activity`;
|
|
@@ -311,11 +234,6 @@ class WorkflowService {
|
|
|
311
234
|
maximumInterval: maximumInterval ?? undefined,
|
|
312
235
|
};
|
|
313
236
|
}
|
|
314
|
-
/**
|
|
315
|
-
* Returns a search session for use when reading/writing to the workflow HASH.
|
|
316
|
-
* The search session provides access to methods like `get`, `mget`, `set`, `del`, and `incr`.
|
|
317
|
-
* @returns {Promise<Search>} - a search session
|
|
318
|
-
*/
|
|
319
237
|
static async search() {
|
|
320
238
|
const store = storage_1.asyncLocalStorage.getStore();
|
|
321
239
|
const workflowId = store.get('workflowId');
|
|
@@ -329,29 +247,15 @@ class WorkflowService {
|
|
|
329
247
|
connection,
|
|
330
248
|
namespace,
|
|
331
249
|
});
|
|
332
|
-
//this ID is used as a item key with a hash (dash prefix ensures no collision)
|
|
333
250
|
const searchSessionId = `-search${workflowDimension}-${execIndex}`;
|
|
334
251
|
return new search_1.Search(workflowId, hotMeshClient, searchSessionId);
|
|
335
252
|
}
|
|
336
|
-
/**
|
|
337
|
-
* Returns a random number between 0 and 1. This number is deterministic
|
|
338
|
-
* and will never vary for a given seed. This is useful for randomizing
|
|
339
|
-
* pathways in a workflow that can be safely replayed.
|
|
340
|
-
* @returns {number} - a random number between 0 and 1
|
|
341
|
-
*/
|
|
342
253
|
static random() {
|
|
343
254
|
const store = storage_1.asyncLocalStorage.getStore();
|
|
344
255
|
const COUNTER = store.get('counter');
|
|
345
256
|
const seed = COUNTER.counter = COUNTER.counter + 1;
|
|
346
257
|
return (0, utils_1.deterministicRandom)(seed);
|
|
347
258
|
}
|
|
348
|
-
/**
|
|
349
|
-
* Sends signal data into any other paused thread (which is currently
|
|
350
|
-
* awaiting the signal)
|
|
351
|
-
* @param {string} signalId - the signal id
|
|
352
|
-
* @param {Record<any, any>} data - the signal data
|
|
353
|
-
* @returns {Promise<string>} - the stream id
|
|
354
|
-
*/
|
|
355
259
|
static async signal(signalId, data) {
|
|
356
260
|
const store = storage_1.asyncLocalStorage.getStore();
|
|
357
261
|
const workflowTopic = store.get('workflowTopic');
|
|
@@ -368,12 +272,6 @@ class WorkflowService {
|
|
|
368
272
|
});
|
|
369
273
|
}
|
|
370
274
|
}
|
|
371
|
-
/**
|
|
372
|
-
* Spawns a hook from either the main thread or a hook thread with
|
|
373
|
-
* the provided options; worflowId/TaskQueue/Name are optional and will
|
|
374
|
-
* default to the current workflowId/WorkflowTopic if not provided
|
|
375
|
-
* @param {HookOptions} options - the hook options
|
|
376
|
-
*/
|
|
377
275
|
static async hook(options) {
|
|
378
276
|
const { workflowId, connection, namespace, workflowTopic } = WorkflowService.getContext();
|
|
379
277
|
const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, {
|
|
@@ -393,18 +291,11 @@ class WorkflowService {
|
|
|
393
291
|
arguments: [...options.args],
|
|
394
292
|
id: targetWorkflowId,
|
|
395
293
|
workflowTopic,
|
|
396
|
-
backoffCoefficient: options.config?.backoffCoefficient || enums_1.
|
|
294
|
+
backoffCoefficient: options.config?.backoffCoefficient || enums_1.HMSH_MESHFLOW_EXP_BACKOFF,
|
|
397
295
|
};
|
|
398
296
|
return await hotMeshClient.hook(`${namespace}.flow.signal`, payload, stream_1.StreamStatus.PENDING, 202);
|
|
399
297
|
}
|
|
400
298
|
}
|
|
401
|
-
/**
|
|
402
|
-
* Executes a function once and caches the result. If the function is called
|
|
403
|
-
* again, the cached result is returned. This is useful for wrapping
|
|
404
|
-
* expensive activity calls that should only be run once, but which might
|
|
405
|
-
* not require the cost and safety provided by proxyActivities.
|
|
406
|
-
* @template T - the result type
|
|
407
|
-
*/
|
|
408
299
|
static async once(fn, ...args) {
|
|
409
300
|
const { COUNTER, connection, namespace, workflowId, workflowTopic, workflowDimension, replay, } = WorkflowService.getContext();
|
|
410
301
|
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
@@ -432,9 +323,6 @@ class WorkflowService {
|
|
|
432
323
|
await hotMeshClient.engine.store.exec('HSET', workflowGuid, sessionId, serializer_1.SerializerService.toString(payload));
|
|
433
324
|
return response;
|
|
434
325
|
}
|
|
435
|
-
/**
|
|
436
|
-
* Interrupts a running job
|
|
437
|
-
*/
|
|
438
326
|
static async interrupt(jobId, options = {}) {
|
|
439
327
|
const { workflowTopic, connection, namespace, } = WorkflowService.getContext();
|
|
440
328
|
const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, {
|
|
@@ -445,28 +333,15 @@ class WorkflowService {
|
|
|
445
333
|
return await hotMeshClient.interrupt(`${hotMeshClient.appId}.execute`, jobId, options);
|
|
446
334
|
}
|
|
447
335
|
}
|
|
448
|
-
/**
|
|
449
|
-
* Promise.all (limited to 25 total concurrent workflow)
|
|
450
|
-
*/
|
|
451
336
|
static async all(...promises) {
|
|
452
337
|
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
453
338
|
return await Promise.all(promises);
|
|
454
339
|
}
|
|
455
|
-
/**
|
|
456
|
-
* Sleeps the workflow for a duration. As the function is reentrant,
|
|
457
|
-
* upon reentry, the function will traverse prior execution paths up
|
|
458
|
-
* until the sleep command and then resume execution thereafter.
|
|
459
|
-
* @param {string} duration - See the `ms` package for syntax examples: '1 minute', '2 hours', '3 days'
|
|
460
|
-
* @returns {Promise<number>} - resolved duration in seconds
|
|
461
|
-
*/
|
|
462
340
|
static async sleepFor(duration) {
|
|
463
|
-
//SYNC
|
|
464
|
-
//return early if this sleep command has already run
|
|
465
341
|
const [didRun, execIndex, result] = await WorkflowService.didRun('sleep');
|
|
466
342
|
if (didRun) {
|
|
467
|
-
return result.duration;
|
|
343
|
+
return result.duration;
|
|
468
344
|
}
|
|
469
|
-
//package the interruption inputs
|
|
470
345
|
const store = storage_1.asyncLocalStorage.getStore();
|
|
471
346
|
const interruptionRegistry = store.get('interruptionRegistry');
|
|
472
347
|
const workflowId = store.get('workflowId');
|
|
@@ -478,31 +353,17 @@ class WorkflowService {
|
|
|
478
353
|
workflowDimension,
|
|
479
354
|
};
|
|
480
355
|
interruptionRegistry.push({
|
|
481
|
-
code: enums_1.
|
|
356
|
+
code: enums_1.HMSH_CODE_MESHFLOW_SLEEP,
|
|
482
357
|
...interruptionMessage,
|
|
483
358
|
});
|
|
484
|
-
//ASYNC
|
|
485
|
-
//sleep to allow other interruptions to be packaged and registered
|
|
486
359
|
await (0, utils_1.sleepImmediate)();
|
|
487
|
-
|
|
488
|
-
throw new errors_1.DurableSleepError(interruptionMessage);
|
|
360
|
+
throw new errors_1.MeshFlowSleepError(interruptionMessage);
|
|
489
361
|
}
|
|
490
|
-
/**
|
|
491
|
-
* Pauses the workflow until `signalId` is received.
|
|
492
|
-
* @template T - the result type
|
|
493
|
-
* @param {string} signalId - a unique, shareable guid (e.g, 'abc123')
|
|
494
|
-
* @returns {Promise<T>}
|
|
495
|
-
* @example
|
|
496
|
-
* const result = await Durable.workflow.waitFor<typeof resultType>('abc123');
|
|
497
|
-
*/
|
|
498
362
|
static async waitFor(signalId) {
|
|
499
|
-
//SYNC
|
|
500
|
-
//return early if this waitFor command has already run
|
|
501
363
|
const [didRun, execIndex, result] = await WorkflowService.didRun('wait');
|
|
502
364
|
if (didRun) {
|
|
503
365
|
return result.data.data;
|
|
504
366
|
}
|
|
505
|
-
//package the interruption inputs
|
|
506
367
|
const store = storage_1.asyncLocalStorage.getStore();
|
|
507
368
|
const interruptionRegistry = store.get('interruptionRegistry');
|
|
508
369
|
const workflowId = store.get('workflowId');
|
|
@@ -514,14 +375,11 @@ class WorkflowService {
|
|
|
514
375
|
workflowDimension,
|
|
515
376
|
};
|
|
516
377
|
interruptionRegistry.push({
|
|
517
|
-
code: enums_1.
|
|
378
|
+
code: enums_1.HMSH_CODE_MESHFLOW_WAIT,
|
|
518
379
|
...interruptionMessage,
|
|
519
380
|
});
|
|
520
|
-
//ASYNC
|
|
521
|
-
//sleep to allow other interruptions to be packaged and registered
|
|
522
381
|
await (0, utils_1.sleepImmediate)();
|
|
523
|
-
|
|
524
|
-
throw new errors_1.DurableWaitForError(interruptionMessage);
|
|
382
|
+
throw new errors_1.MeshFlowWaitForError(interruptionMessage);
|
|
525
383
|
}
|
|
526
384
|
}
|
|
527
385
|
exports.WorkflowService = WorkflowService;
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
type DateInput = Date | string | number;
|
|
2
2
|
declare class DateHandler {
|
|
3
|
-
/**
|
|
4
|
-
* It is so common in mapping operations to use a string (ISO) date as input. This helper
|
|
5
|
-
* method allows for a more-concise mapping ruleset by avoiding date initialization boilerplate
|
|
6
|
-
* code and instead handles the ISO, Milliseconds, and ECMAScript Date input types.
|
|
7
|
-
* @param input
|
|
8
|
-
* @returns
|
|
9
|
-
*/
|
|
10
3
|
static getDateInstance(input: DateInput): Date;
|
|
11
4
|
fromISOString(isoString: string): Date;
|
|
12
5
|
now(): number;
|
|
@@ -3,13 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.DateHandler = void 0;
|
|
4
4
|
const utils_1 = require("../../../modules/utils");
|
|
5
5
|
class DateHandler {
|
|
6
|
-
/**
|
|
7
|
-
* It is so common in mapping operations to use a string (ISO) date as input. This helper
|
|
8
|
-
* method allows for a more-concise mapping ruleset by avoiding date initialization boilerplate
|
|
9
|
-
* code and instead handles the ISO, Milliseconds, and ECMAScript Date input types.
|
|
10
|
-
* @param input
|
|
11
|
-
* @returns
|
|
12
|
-
*/
|
|
13
6
|
static getDateInstance(input) {
|
|
14
7
|
const ISO_REGEX = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z)?$/;
|
|
15
8
|
if (typeof input === 'string') {
|