@hotmeshio/hotmesh 0.0.12 → 0.0.14
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 +2 -2
- package/build/modules/errors.d.ts +22 -1
- package/build/modules/errors.js +28 -1
- package/build/modules/utils.d.ts +2 -1
- package/build/modules/utils.js +5 -1
- package/build/package.json +7 -2
- package/build/services/activities/activity.d.ts +2 -0
- package/build/services/activities/activity.js +16 -10
- package/build/services/activities/await.d.ts +2 -6
- package/build/services/activities/await.js +12 -75
- package/build/services/activities/cycle.js +2 -2
- package/build/services/activities/index.d.ts +2 -2
- package/build/services/activities/index.js +2 -2
- package/build/services/activities/signal.d.ts +16 -0
- package/build/services/activities/signal.js +94 -0
- package/build/services/activities/trigger.js +4 -3
- package/build/services/activities/worker.d.ts +2 -1
- package/build/services/activities/worker.js +11 -6
- package/build/services/compiler/deployer.js +3 -1
- package/build/services/durable/client.d.ts +3 -2
- package/build/services/durable/client.js +39 -21
- package/build/services/durable/factory.d.ts +22 -18
- package/build/services/durable/factory.js +722 -50
- package/build/services/durable/handle.d.ts +1 -0
- package/build/services/durable/handle.js +5 -1
- package/build/services/durable/worker.d.ts +3 -8
- package/build/services/durable/worker.js +75 -73
- package/build/services/durable/workflow.d.ts +5 -0
- package/build/services/durable/workflow.js +93 -24
- package/build/services/engine/index.d.ts +6 -6
- package/build/services/engine/index.js +25 -15
- package/build/services/hotmesh/index.d.ts +2 -1
- package/build/services/hotmesh/index.js +3 -1
- package/build/services/mapper/index.js +1 -1
- package/build/services/pipe/functions/array.d.ts +1 -0
- package/build/services/pipe/functions/array.js +3 -0
- package/build/services/reporter/index.js +9 -2
- package/build/services/signaler/store.js +8 -3
- package/build/services/signaler/stream.js +3 -3
- package/build/services/store/clients/ioredis.js +15 -15
- package/build/services/store/clients/redis.js +18 -18
- package/build/services/store/index.d.ts +1 -1
- package/build/services/store/index.js +11 -3
- package/build/services/task/index.js +3 -3
- package/build/types/activity.d.ts +15 -6
- package/build/types/durable.d.ts +15 -2
- package/build/types/index.d.ts +2 -2
- package/build/types/stats.d.ts +1 -0
- package/modules/errors.ts +35 -0
- package/modules/utils.ts +5 -1
- package/package.json +7 -2
- package/services/activities/activity.ts +19 -9
- package/services/activities/await.ts +14 -90
- package/services/activities/cycle.ts +2 -2
- package/services/activities/index.ts +2 -2
- package/services/activities/signal.ts +124 -0
- package/services/activities/trigger.ts +4 -3
- package/services/activities/worker.ts +13 -13
- package/services/compiler/deployer.ts +3 -1
- package/services/durable/client.ts +48 -23
- package/services/durable/factory.ts +723 -49
- package/services/durable/handle.ts +6 -1
- package/services/durable/worker.ts +92 -79
- package/services/durable/workflow.ts +95 -25
- package/services/engine/index.ts +33 -24
- package/services/hotmesh/index.ts +7 -4
- package/services/mapper/index.ts +1 -1
- package/services/pipe/functions/array.ts +4 -0
- package/services/reporter/index.ts +10 -2
- package/services/signaler/store.ts +8 -3
- package/services/signaler/stream.ts +3 -3
- package/services/store/clients/ioredis.ts +15 -15
- package/services/store/clients/redis.ts +18 -18
- package/services/store/index.ts +12 -3
- package/services/task/index.ts +3 -3
- package/types/activity.ts +16 -7
- package/types/durable.ts +18 -1
- package/types/index.ts +2 -1
- package/types/stats.ts +1 -0
- package/build/services/activities/emit.d.ts +0 -9
- package/build/services/activities/emit.js +0 -13
- package/services/activities/emit.ts +0 -25
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.WorkflowHandleService = void 0;
|
|
4
|
+
const factory_1 = require("./factory");
|
|
4
5
|
class WorkflowHandleService {
|
|
5
6
|
constructor(hotMesh, workflowTopic, workflowId) {
|
|
6
7
|
this.workflowTopic = workflowTopic;
|
|
7
8
|
this.workflowId = workflowId;
|
|
8
9
|
this.hotMesh = hotMesh;
|
|
9
10
|
}
|
|
11
|
+
async signal(signalId, data) {
|
|
12
|
+
await this.hotMesh.hook('durable.wfs.signal', { id: signalId, data });
|
|
13
|
+
}
|
|
10
14
|
async result() {
|
|
11
15
|
let status = await this.hotMesh.getStatus(this.workflowId);
|
|
12
|
-
const topic = `${
|
|
16
|
+
const topic = `${factory_1.PUBLISHES_TOPIC}.${this.workflowId}`;
|
|
13
17
|
return new Promise((resolve, reject) => {
|
|
14
18
|
let isResolved = false;
|
|
15
19
|
//common fulfill/unsubscribe
|
|
@@ -7,12 +7,8 @@ export declare class WorkerService {
|
|
|
7
7
|
workflowRunner: HotMesh;
|
|
8
8
|
activityRunner: HotMesh;
|
|
9
9
|
static getHotMesh: (worflowTopic: string, options?: WorkerOptions) => Promise<HotMesh>;
|
|
10
|
-
static activateWorkflow(hotMesh: HotMesh
|
|
10
|
+
static activateWorkflow(hotMesh: HotMesh): Promise<void>;
|
|
11
11
|
/**
|
|
12
|
-
* The `worker` calls `registerActivities` immediately BEFORE
|
|
13
|
-
* dynamically importing the user's workflow module. That file
|
|
14
|
-
* contains a call, `proxyActivities`, which needs this info.
|
|
15
|
-
*
|
|
16
12
|
* NOTE: Because the worker imports the workflows dynamically AFTER
|
|
17
13
|
* the activities are loaded, there will be items in the registry,
|
|
18
14
|
* allowing proxyActivities to succeed.
|
|
@@ -21,10 +17,9 @@ export declare class WorkerService {
|
|
|
21
17
|
static create(config: WorkerConfig): Promise<WorkerService>;
|
|
22
18
|
static resolveWorkflowTarget(workflow: object | Function): [string, Function];
|
|
23
19
|
run(): Promise<void>;
|
|
24
|
-
|
|
20
|
+
initActivityWorker(config: WorkerConfig, activityTopic: string): Promise<HotMesh>;
|
|
25
21
|
wrapActivityFunctions(): Function;
|
|
26
|
-
|
|
27
|
-
initWorkerWorkflow(config: WorkerConfig, workflowTopic: string, workflowFunction: Function): Promise<HotMesh>;
|
|
22
|
+
initWorkflowWorker(config: WorkerConfig, workflowTopic: string, workflowFunction: Function): Promise<HotMesh>;
|
|
28
23
|
static Context: {
|
|
29
24
|
info: () => {
|
|
30
25
|
workflowId: string;
|
|
@@ -2,34 +2,32 @@
|
|
|
2
2
|
var _a;
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.WorkerService = void 0;
|
|
5
|
+
const errors_1 = require("../../modules/errors");
|
|
5
6
|
const asyncLocalStorage_1 = require("./asyncLocalStorage");
|
|
7
|
+
const factory_1 = require("./factory");
|
|
6
8
|
const hotmesh_1 = require("../hotmesh");
|
|
7
9
|
const stream_1 = require("../../types/stream");
|
|
8
|
-
const factory_1 = require("./factory");
|
|
9
|
-
const errors_1 = require("../../modules/errors");
|
|
10
10
|
/*
|
|
11
11
|
Here is an example of how the methods in this file are used:
|
|
12
12
|
|
|
13
13
|
./worker.ts
|
|
14
14
|
|
|
15
|
-
import { Durable
|
|
15
|
+
import { Durable } from '@hotmeshio/hotmesh';
|
|
16
16
|
import Redis from 'ioredis'; //OR `import * as Redis from 'redis';`
|
|
17
17
|
|
|
18
18
|
import * as workflows from './workflows';
|
|
19
19
|
|
|
20
20
|
async function run() {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
const worker = await Durable.Worker.create({
|
|
22
|
+
connection: {
|
|
23
|
+
class: Redis,
|
|
24
|
+
options: {
|
|
25
|
+
host: 'localhost',
|
|
26
|
+
port: 6379,
|
|
27
|
+
},
|
|
26
28
|
},
|
|
27
|
-
});
|
|
28
|
-
const worker = await Worker.create({
|
|
29
|
-
connection,
|
|
30
29
|
taskQueue: 'hello-world',
|
|
31
30
|
workflow: workflows.example,
|
|
32
|
-
activities,
|
|
33
31
|
});
|
|
34
32
|
await worker.run();
|
|
35
33
|
}
|
|
@@ -40,14 +38,13 @@ run().catch((err) => {
|
|
|
40
38
|
});
|
|
41
39
|
*/
|
|
42
40
|
class WorkerService {
|
|
43
|
-
static async activateWorkflow(hotMesh
|
|
44
|
-
const
|
|
45
|
-
const app = await hotMesh.engine.store.getApp(topic);
|
|
41
|
+
static async activateWorkflow(hotMesh) {
|
|
42
|
+
const app = await hotMesh.engine.store.getApp(factory_1.APP_ID);
|
|
46
43
|
const appVersion = app?.version;
|
|
47
44
|
if (!appVersion) {
|
|
48
45
|
try {
|
|
49
|
-
await hotMesh.deploy(
|
|
50
|
-
await hotMesh.activate(
|
|
46
|
+
await hotMesh.deploy((0, factory_1.getWorkflowYAML)(factory_1.APP_ID, factory_1.APP_VERSION));
|
|
47
|
+
await hotMesh.activate(factory_1.APP_VERSION);
|
|
51
48
|
}
|
|
52
49
|
catch (err) {
|
|
53
50
|
hotMesh.engine.logger.error('durable-worker-deploy-activate-err', err);
|
|
@@ -56,7 +53,7 @@ class WorkerService {
|
|
|
56
53
|
}
|
|
57
54
|
else if (app && !app.active) {
|
|
58
55
|
try {
|
|
59
|
-
await hotMesh.activate(
|
|
56
|
+
await hotMesh.activate(factory_1.APP_VERSION);
|
|
60
57
|
}
|
|
61
58
|
catch (err) {
|
|
62
59
|
hotMesh.engine.logger.error('durable-worker-activate-err', err);
|
|
@@ -65,10 +62,6 @@ class WorkerService {
|
|
|
65
62
|
}
|
|
66
63
|
}
|
|
67
64
|
/**
|
|
68
|
-
* The `worker` calls `registerActivities` immediately BEFORE
|
|
69
|
-
* dynamically importing the user's workflow module. That file
|
|
70
|
-
* contains a call, `proxyActivities`, which needs this info.
|
|
71
|
-
*
|
|
72
65
|
* NOTE: Because the worker imports the workflows dynamically AFTER
|
|
73
66
|
* the activities are loaded, there will be items in the registry,
|
|
74
67
|
* allowing proxyActivities to succeed.
|
|
@@ -89,7 +82,6 @@ class WorkerService {
|
|
|
89
82
|
static async create(config) {
|
|
90
83
|
//always call `registerActivities` before `import`
|
|
91
84
|
WorkerService.connection = config.connection;
|
|
92
|
-
//user can provide the workflow file directly
|
|
93
85
|
const workflow = config.workflow;
|
|
94
86
|
const [workflowFunctionName, workflowFunction] = WorkerService.resolveWorkflowTarget(workflow);
|
|
95
87
|
const baseTopic = `${config.taskQueue}-${workflowFunctionName}`;
|
|
@@ -97,10 +89,9 @@ class WorkerService {
|
|
|
97
89
|
const workflowTopic = `${baseTopic}`;
|
|
98
90
|
//initialize supporting workflows
|
|
99
91
|
const worker = new WorkerService();
|
|
100
|
-
worker.activityRunner = await worker.
|
|
101
|
-
await
|
|
102
|
-
|
|
103
|
-
await WorkerService.activateWorkflow(worker.workflowRunner, workflowTopic, factory_1.getWorkflowYAML, config.options);
|
|
92
|
+
worker.activityRunner = await worker.initActivityWorker(config, activityTopic);
|
|
93
|
+
worker.workflowRunner = await worker.initWorkflowWorker(config, workflowTopic, workflowFunction);
|
|
94
|
+
await WorkerService.activateWorkflow(worker.workflowRunner);
|
|
104
95
|
return worker;
|
|
105
96
|
}
|
|
106
97
|
static resolveWorkflowTarget(workflow) {
|
|
@@ -118,13 +109,13 @@ class WorkerService {
|
|
|
118
109
|
async run() {
|
|
119
110
|
this.workflowRunner.engine.logger.info('WorkerService is running');
|
|
120
111
|
}
|
|
121
|
-
async
|
|
112
|
+
async initActivityWorker(config, activityTopic) {
|
|
122
113
|
const redisConfig = {
|
|
123
114
|
class: config.connection.class,
|
|
124
115
|
options: config.connection.options
|
|
125
116
|
};
|
|
126
|
-
const
|
|
127
|
-
appId:
|
|
117
|
+
const hotMeshWorker = await hotmesh_1.HotMeshService.init({
|
|
118
|
+
appId: factory_1.APP_ID,
|
|
128
119
|
engine: { redis: redisConfig },
|
|
129
120
|
workers: [
|
|
130
121
|
{ topic: activityTopic,
|
|
@@ -133,8 +124,8 @@ class WorkerService {
|
|
|
133
124
|
}
|
|
134
125
|
]
|
|
135
126
|
});
|
|
136
|
-
WorkerService.instances.set(activityTopic,
|
|
137
|
-
return
|
|
127
|
+
WorkerService.instances.set(activityTopic, hotMeshWorker);
|
|
128
|
+
return hotMeshWorker;
|
|
138
129
|
}
|
|
139
130
|
wrapActivityFunctions() {
|
|
140
131
|
return async (data) => {
|
|
@@ -166,55 +157,30 @@ class WorkerService {
|
|
|
166
157
|
}
|
|
167
158
|
};
|
|
168
159
|
}
|
|
169
|
-
async
|
|
170
|
-
const version = '1';
|
|
171
|
-
const app = await hotMesh.engine.store.getApp(activityTopic);
|
|
172
|
-
const appVersion = app?.version;
|
|
173
|
-
if (isNaN(appVersion)) {
|
|
174
|
-
try {
|
|
175
|
-
await hotMesh.deploy((0, factory_1.getActivityYAML)(activityTopic, version));
|
|
176
|
-
await hotMesh.activate(version);
|
|
177
|
-
}
|
|
178
|
-
catch (err) {
|
|
179
|
-
hotMesh.engine.logger.error('durable-worker-activity-deploy-activate-error', err);
|
|
180
|
-
throw err;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
else if (app && !app.active) {
|
|
184
|
-
try {
|
|
185
|
-
await hotMesh.activate(version);
|
|
186
|
-
}
|
|
187
|
-
catch (err) {
|
|
188
|
-
hotMesh.engine.logger.error('durable-worker-activity-activate-err', err);
|
|
189
|
-
throw err;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
async initWorkerWorkflow(config, workflowTopic, workflowFunction) {
|
|
160
|
+
async initWorkflowWorker(config, workflowTopic, workflowFunction) {
|
|
194
161
|
const redisConfig = {
|
|
195
162
|
class: config.connection.class,
|
|
196
163
|
options: config.connection.options
|
|
197
164
|
};
|
|
198
|
-
const
|
|
199
|
-
appId:
|
|
165
|
+
const hotMeshWorker = await hotmesh_1.HotMeshService.init({
|
|
166
|
+
appId: factory_1.APP_ID,
|
|
200
167
|
engine: { redis: redisConfig },
|
|
201
|
-
workers: [
|
|
202
|
-
|
|
168
|
+
workers: [{
|
|
169
|
+
topic: workflowTopic,
|
|
203
170
|
redis: redisConfig,
|
|
204
171
|
callback: this.wrapWorkflowFunction(workflowFunction, workflowTopic).bind(this)
|
|
205
|
-
}
|
|
206
|
-
]
|
|
172
|
+
}]
|
|
207
173
|
});
|
|
208
|
-
WorkerService.instances.set(workflowTopic,
|
|
209
|
-
return
|
|
174
|
+
WorkerService.instances.set(workflowTopic, hotMeshWorker);
|
|
175
|
+
return hotMeshWorker;
|
|
210
176
|
}
|
|
211
177
|
wrapWorkflowFunction(workflowFunction, workflowTopic) {
|
|
212
178
|
return async (data) => {
|
|
179
|
+
const counter = { counter: 0 };
|
|
213
180
|
try {
|
|
214
181
|
//incoming data payload has arguments and workflowId
|
|
215
182
|
const workflowInput = data.data;
|
|
216
183
|
const context = new Map();
|
|
217
|
-
const counter = { counter: 0 };
|
|
218
184
|
context.set('counter', counter);
|
|
219
185
|
context.set('workflowId', workflowInput.workflowId);
|
|
220
186
|
context.set('workflowTopic', workflowTopic);
|
|
@@ -228,11 +194,47 @@ class WorkerService {
|
|
|
228
194
|
code: 200,
|
|
229
195
|
status: stream_1.StreamStatus.SUCCESS,
|
|
230
196
|
metadata: { ...data.metadata },
|
|
231
|
-
data: { response: workflowResponse }
|
|
197
|
+
data: { response: workflowResponse, done: true }
|
|
232
198
|
};
|
|
233
199
|
}
|
|
234
200
|
catch (err) {
|
|
235
|
-
//
|
|
201
|
+
//not an error...just a trigger to sleep
|
|
202
|
+
if (err instanceof errors_1.DurableSleepError) {
|
|
203
|
+
return {
|
|
204
|
+
status: stream_1.StreamStatus.SUCCESS,
|
|
205
|
+
code: err.code,
|
|
206
|
+
metadata: { ...data.metadata },
|
|
207
|
+
data: {
|
|
208
|
+
code: err.code,
|
|
209
|
+
message: JSON.stringify({ duration: err.duration, index: err.index }),
|
|
210
|
+
duration: err.duration,
|
|
211
|
+
index: err.index
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
//not an error...just a trigger to wait for a signal
|
|
215
|
+
}
|
|
216
|
+
else if (err instanceof errors_1.DurableWaitForSignalError) {
|
|
217
|
+
return {
|
|
218
|
+
status: stream_1.StreamStatus.SUCCESS,
|
|
219
|
+
code: err.code,
|
|
220
|
+
metadata: { ...data.metadata },
|
|
221
|
+
data: {
|
|
222
|
+
code: err.code,
|
|
223
|
+
signals: err.signals,
|
|
224
|
+
index: err.signals[0].index
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
//not an error...still waiting for all the signals to arrive
|
|
228
|
+
}
|
|
229
|
+
else if (err instanceof errors_1.DurableIncompleteSignalError) {
|
|
230
|
+
return {
|
|
231
|
+
status: stream_1.StreamStatus.SUCCESS,
|
|
232
|
+
code: err.code,
|
|
233
|
+
metadata: { ...data.metadata },
|
|
234
|
+
data: { code: err.code }
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
// all other errors are fatal (598, 597, 596) or will be retried (599)
|
|
236
238
|
return {
|
|
237
239
|
status: stream_1.StreamStatus.ERROR,
|
|
238
240
|
code: err.code || new errors_1.DurableRetryError(err.message).code,
|
|
@@ -256,13 +258,13 @@ WorkerService.getHotMesh = async (worflowTopic, options) => {
|
|
|
256
258
|
if (WorkerService.instances.has(worflowTopic)) {
|
|
257
259
|
return await WorkerService.instances.get(worflowTopic);
|
|
258
260
|
}
|
|
259
|
-
const
|
|
260
|
-
appId:
|
|
261
|
+
const hotMeshClient = hotmesh_1.HotMeshService.init({
|
|
262
|
+
appId: factory_1.APP_ID,
|
|
261
263
|
engine: { redis: { ...WorkerService.connection } }
|
|
262
264
|
});
|
|
263
|
-
WorkerService.instances.set(worflowTopic,
|
|
264
|
-
await WorkerService.activateWorkflow(await
|
|
265
|
-
return
|
|
265
|
+
WorkerService.instances.set(worflowTopic, hotMeshClient);
|
|
266
|
+
await WorkerService.activateWorkflow(await hotMeshClient);
|
|
267
|
+
return hotMeshClient;
|
|
266
268
|
};
|
|
267
269
|
WorkerService.Context = {
|
|
268
270
|
info: () => {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { ActivityConfig, ProxyType, WorkflowOptions } from "../../types/durable";
|
|
2
2
|
export declare class WorkflowService {
|
|
3
|
+
/**
|
|
4
|
+
* Spawn a child workflow. await the result.
|
|
5
|
+
*/
|
|
3
6
|
static executeChild<T>(options: WorkflowOptions): Promise<T>;
|
|
4
7
|
static proxyActivities<ACT>(options?: ActivityConfig): ProxyType<ACT>;
|
|
8
|
+
static sleep(duration: string): Promise<number>;
|
|
9
|
+
static waitForSignal(signals: string[], options?: Record<string, string>): Promise<Record<any, any>[]>;
|
|
5
10
|
static wrapActivity<T>(activityName: string, options?: ActivityConfig): T;
|
|
6
11
|
}
|
|
@@ -9,25 +9,14 @@ const asyncLocalStorage_1 = require("./asyncLocalStorage");
|
|
|
9
9
|
const worker_1 = require("./worker");
|
|
10
10
|
const client_1 = require("./client");
|
|
11
11
|
const connection_1 = require("./connection");
|
|
12
|
+
const factory_1 = require("./factory");
|
|
13
|
+
const errors_1 = require("../../modules/errors");
|
|
12
14
|
/*
|
|
13
15
|
`proxyActivities` returns a wrapped instance of the
|
|
14
16
|
target activity, so that when the workflow calls a
|
|
15
17
|
proxied activity, it is actually calling the proxy
|
|
16
18
|
function, which in turn calls the activity function.
|
|
17
19
|
|
|
18
|
-
`proxyActivities` must be called AFTER the activities
|
|
19
|
-
have been registered in order to work properly.
|
|
20
|
-
If the activities are not already registered,
|
|
21
|
-
`proxyActivities` will throw an error. This is OK.
|
|
22
|
-
|
|
23
|
-
The `client` (client.ts) is equivalent to the
|
|
24
|
-
HotMesh `engine`. The jobs it creates will be
|
|
25
|
-
put in the taskQueue. When the `worker` (worker.ts)
|
|
26
|
-
is eventually initialized (if it happens to be inited later),
|
|
27
|
-
it will see the items in the queue and process them. If it happens
|
|
28
|
-
to already be inited, the jobs will immediately be dequeued and
|
|
29
|
-
processed. In either case, the jobs will be processed.
|
|
30
|
-
|
|
31
20
|
Here is an example of how the methods in this file are used:
|
|
32
21
|
|
|
33
22
|
./workflows.ts
|
|
@@ -51,6 +40,9 @@ export async function example(name: string): Promise<string> {
|
|
|
51
40
|
}
|
|
52
41
|
*/
|
|
53
42
|
class WorkflowService {
|
|
43
|
+
/**
|
|
44
|
+
* Spawn a child workflow. await the result.
|
|
45
|
+
*/
|
|
54
46
|
static async executeChild(options) {
|
|
55
47
|
const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
|
|
56
48
|
if (!store) {
|
|
@@ -62,7 +54,6 @@ class WorkflowService {
|
|
|
62
54
|
const client = new client_1.ClientService({
|
|
63
55
|
connection: await connection_1.ConnectionService.connect(worker_1.WorkerService.connection),
|
|
64
56
|
});
|
|
65
|
-
//todo: allow cross/app callback (pj:'@DURABLE@hello-world@<pjid>'/pa: <paid>/pd: <pdad>)
|
|
66
57
|
const handle = await client.workflow.start({
|
|
67
58
|
...options,
|
|
68
59
|
workflowId: `${workflowId}${options.workflowId}`,
|
|
@@ -86,6 +77,83 @@ class WorkflowService {
|
|
|
86
77
|
}
|
|
87
78
|
return proxy;
|
|
88
79
|
}
|
|
80
|
+
static async sleep(duration) {
|
|
81
|
+
const seconds = (0, ms_1.default)(duration) / 1000;
|
|
82
|
+
const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
|
|
83
|
+
if (!store) {
|
|
84
|
+
throw new Error('durable-store-not-found');
|
|
85
|
+
}
|
|
86
|
+
const COUNTER = store.get('counter');
|
|
87
|
+
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
88
|
+
const workflowId = store.get('workflowId');
|
|
89
|
+
const workflowTopic = store.get('workflowTopic');
|
|
90
|
+
const sleepJobId = `${workflowId}-$sleep-${execIndex}`;
|
|
91
|
+
try {
|
|
92
|
+
const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic);
|
|
93
|
+
await hotMeshClient.getState(factory_1.SLEEP_SUBSCRIBES_TOPIC, sleepJobId);
|
|
94
|
+
//if no error is thrown, we've already slept, return the delay
|
|
95
|
+
return seconds;
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
//if an error, the sleep job was not found...rethrow error; sleep job
|
|
99
|
+
// will be automatically created according to the DAG rules (they
|
|
100
|
+
// spawn a new sleep job if error code 595 is thrown by the worker)
|
|
101
|
+
throw new errors_1.DurableSleepError(workflowId, seconds, execIndex);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
static async waitForSignal(signals, options) {
|
|
105
|
+
const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
|
|
106
|
+
if (!store) {
|
|
107
|
+
throw new Error('durable-store-not-found');
|
|
108
|
+
}
|
|
109
|
+
const COUNTER = store.get('counter');
|
|
110
|
+
const workflowId = store.get('workflowId');
|
|
111
|
+
const workflowTopic = store.get('workflowTopic');
|
|
112
|
+
const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic);
|
|
113
|
+
//iterate the list of signals and check for done
|
|
114
|
+
let allAreComplete = true;
|
|
115
|
+
let noneAreComplete = false;
|
|
116
|
+
const signalResults = [];
|
|
117
|
+
for (const signal of signals) {
|
|
118
|
+
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
119
|
+
const wfsJobId = `${workflowId}-$wfs-${execIndex}`;
|
|
120
|
+
try {
|
|
121
|
+
if (allAreComplete) {
|
|
122
|
+
const state = await hotMeshClient.getState(factory_1.WFS_SUBSCRIBES_TOPIC, wfsJobId);
|
|
123
|
+
if (state.data?.signalData) {
|
|
124
|
+
//user data is nested to isolate from the signal id; unpackage it
|
|
125
|
+
const signalData = state.data.signalData;
|
|
126
|
+
signalResults.push(signalData.data);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
allAreComplete = false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
signalResults.push({ signal, index: execIndex });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
//todo: options.startToCloseTimeout
|
|
138
|
+
allAreComplete = false;
|
|
139
|
+
noneAreComplete = true;
|
|
140
|
+
signalResults.push({ signal, index: execIndex });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
;
|
|
144
|
+
if (allAreComplete) {
|
|
145
|
+
return signalResults;
|
|
146
|
+
}
|
|
147
|
+
else if (noneAreComplete) {
|
|
148
|
+
//this error is caught by the workflow runner
|
|
149
|
+
//it is then returned as the workflow result (594)
|
|
150
|
+
throw new errors_1.DurableWaitForSignalError(workflowId, signalResults);
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
//this error happens when a signal is received but others are still open
|
|
154
|
+
throw new errors_1.DurableIncompleteSignalError(workflowId);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
89
157
|
static wrapActivity(activityName, options) {
|
|
90
158
|
return async function () {
|
|
91
159
|
const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
|
|
@@ -103,21 +171,20 @@ class WorkflowService {
|
|
|
103
171
|
const activityJobId = `${workflowId}-${activityName}-${execIndex}`;
|
|
104
172
|
let activityState;
|
|
105
173
|
try {
|
|
106
|
-
const
|
|
107
|
-
activityState = await
|
|
174
|
+
const hotMeshClient = await worker_1.WorkerService.getHotMesh(activityTopic);
|
|
175
|
+
activityState = await hotMeshClient.getState(factory_1.ACTIVITY_SUBSCRIBES_TOPIC, activityJobId);
|
|
108
176
|
if (activityState.metadata.err) {
|
|
109
|
-
await
|
|
177
|
+
await hotMeshClient.scrub(activityJobId);
|
|
110
178
|
throw new Error(activityState.metadata.err);
|
|
111
179
|
}
|
|
112
|
-
else if (activityState.metadata.js === 0) {
|
|
113
|
-
//return immediately
|
|
180
|
+
else if (activityState.metadata.js === 0 || activityState.data?.done) {
|
|
114
181
|
return activityState.data?.response;
|
|
115
182
|
}
|
|
116
183
|
//one time subscription
|
|
117
184
|
return await new Promise((resolve, reject) => {
|
|
118
|
-
|
|
185
|
+
hotMeshClient.sub(`${factory_1.ACTIVITY_PUBLISHES_TOPIC}.${activityJobId}`, async (topic, message) => {
|
|
119
186
|
const response = message.data?.response;
|
|
120
|
-
|
|
187
|
+
hotMeshClient.unsub(`${factory_1.ACTIVITY_PUBLISHES_TOPIC}.${activityJobId}`);
|
|
121
188
|
// Resolve the Promise when the callback is triggered with a message
|
|
122
189
|
resolve(response);
|
|
123
190
|
});
|
|
@@ -128,14 +195,16 @@ class WorkflowService {
|
|
|
128
195
|
const duration = (0, ms_1.default)(options?.startToCloseTimeout || '1 minute');
|
|
129
196
|
const payload = {
|
|
130
197
|
arguments: Array.from(arguments),
|
|
198
|
+
//the parent id is provided to categorize this activity for later cleanup
|
|
199
|
+
parentWorkflowId: `${workflowId}-a`,
|
|
131
200
|
workflowId: activityJobId,
|
|
132
|
-
workflowTopic,
|
|
201
|
+
workflowTopic: activityTopic,
|
|
133
202
|
activityName,
|
|
134
203
|
};
|
|
135
204
|
//start the job
|
|
136
|
-
const
|
|
205
|
+
const hotMeshClient = await worker_1.WorkerService.getHotMesh(activityTopic);
|
|
137
206
|
const context = { metadata: { trc, spn }, data: {} };
|
|
138
|
-
const jobOutput = await
|
|
207
|
+
const jobOutput = await hotMeshClient.pubsub(factory_1.ACTIVITY_SUBSCRIBES_TOPIC, payload, context, duration);
|
|
139
208
|
return jobOutput.data.response;
|
|
140
209
|
}
|
|
141
210
|
};
|
|
@@ -54,27 +54,27 @@ declare class EngineService {
|
|
|
54
54
|
getIds(topic: string, query: JobStatsInput, queryFacets?: any[]): Promise<IdsResponse>;
|
|
55
55
|
resolveQuery(topic: string, query: JobStatsInput): Promise<GetStatsOptions>;
|
|
56
56
|
processStreamMessage(streamData: StreamDataResponse): Promise<void>;
|
|
57
|
-
execAdjacentParent(context: JobState, jobOutput: JobOutput): Promise<string>;
|
|
57
|
+
execAdjacentParent(context: JobState, jobOutput: JobOutput, emit?: boolean): Promise<string>;
|
|
58
58
|
hasParentJob(context: JobState): boolean;
|
|
59
59
|
resolveError(metadata: JobMetadata): StreamError | undefined;
|
|
60
60
|
scrub(jobId: string): Promise<void>;
|
|
61
|
-
hook(topic: string, data: JobData, dad?: string): Promise<
|
|
61
|
+
hook(topic: string, data: JobData, dad?: string): Promise<string>;
|
|
62
62
|
hookTime(jobId: string, activityId: string): Promise<JobStatus | void>;
|
|
63
|
-
hookAll(hookTopic: string, data: JobData,
|
|
63
|
+
hookAll(hookTopic: string, data: JobData, keyResolver: JobStatsInput, queryFacets?: string[]): Promise<string[]>;
|
|
64
64
|
pub(topic: string, data: JobData, context?: JobState): Promise<string>;
|
|
65
65
|
sub(topic: string, callback: JobMessageCallback): Promise<void>;
|
|
66
66
|
unsub(topic: string): Promise<void>;
|
|
67
67
|
psub(wild: string, callback: JobMessageCallback): Promise<void>;
|
|
68
68
|
punsub(wild: string): Promise<void>;
|
|
69
69
|
pubsub(topic: string, data: JobData, context?: JobState | null, timeout?: number): Promise<JobOutput>;
|
|
70
|
-
resolveOneTimeSubscription(context: JobState, jobOutput: JobOutput): Promise<void>;
|
|
70
|
+
resolveOneTimeSubscription(context: JobState, jobOutput: JobOutput, emit?: boolean): Promise<void>;
|
|
71
71
|
getPublishesTopic(context: JobState): Promise<string>;
|
|
72
|
-
resolvePersistentSubscriptions(context: JobState, jobOutput: JobOutput): Promise<void>;
|
|
72
|
+
resolvePersistentSubscriptions(context: JobState, jobOutput: JobOutput, emit?: boolean): Promise<void>;
|
|
73
73
|
add(streamData: StreamData | StreamDataResponse): Promise<string>;
|
|
74
74
|
registerJobCallback(jobId: string, jobCallback: JobMessageCallback): void;
|
|
75
75
|
delistJobCallback(jobId: string): void;
|
|
76
76
|
hasOneTimeSubscription(context: JobState): boolean;
|
|
77
|
-
runJobCompletionTasks(context: JobState): Promise<void>;
|
|
77
|
+
runJobCompletionTasks(context: JobState, emit?: boolean): Promise<void>;
|
|
78
78
|
getStatus(jobId: string): Promise<JobStatus>;
|
|
79
79
|
getState(topic: string, jobId: string): Promise<JobOutput>;
|
|
80
80
|
compress(terms: string[]): Promise<boolean>;
|
|
@@ -23,6 +23,7 @@ const stream_2 = require("../../types/stream");
|
|
|
23
23
|
//wait time to see if a job is complete
|
|
24
24
|
const OTT_WAIT_TIME = 1000;
|
|
25
25
|
const STATUS_CODE_SUCCESS = 200;
|
|
26
|
+
const STATUS_CODE_PENDING = 202;
|
|
26
27
|
const STATUS_CODE_TIMEOUT = 504;
|
|
27
28
|
class EngineService {
|
|
28
29
|
constructor() {
|
|
@@ -275,7 +276,7 @@ class EngineService {
|
|
|
275
276
|
});
|
|
276
277
|
}
|
|
277
278
|
// ***************** `AWAIT` ACTIVITY RETURN RESPONSE ****************
|
|
278
|
-
async execAdjacentParent(context, jobOutput) {
|
|
279
|
+
async execAdjacentParent(context, jobOutput, emit = false) {
|
|
279
280
|
if (this.hasParentJob(context)) {
|
|
280
281
|
//errors are stringified `StreamError` objects
|
|
281
282
|
const error = this.resolveError(jobOutput.metadata);
|
|
@@ -296,6 +297,10 @@ class EngineService {
|
|
|
296
297
|
streamData.data = error;
|
|
297
298
|
streamData.code = error.code;
|
|
298
299
|
}
|
|
300
|
+
else if (emit) {
|
|
301
|
+
streamData.status = stream_2.StreamStatus.PENDING;
|
|
302
|
+
streamData.code = STATUS_CODE_PENDING;
|
|
303
|
+
}
|
|
299
304
|
else {
|
|
300
305
|
streamData.status = stream_2.StreamStatus.SUCCESS;
|
|
301
306
|
streamData.code = STATUS_CODE_SUCCESS;
|
|
@@ -336,7 +341,7 @@ class EngineService {
|
|
|
336
341
|
},
|
|
337
342
|
data,
|
|
338
343
|
};
|
|
339
|
-
await this.streamSignaler.publishMessage(null, streamData);
|
|
344
|
+
return await this.streamSignaler.publishMessage(null, streamData);
|
|
340
345
|
}
|
|
341
346
|
async hookTime(jobId, activityId) {
|
|
342
347
|
//the activityid is concatenated with its dimensional address (dad); split to resolve
|
|
@@ -353,17 +358,19 @@ class EngineService {
|
|
|
353
358
|
};
|
|
354
359
|
await this.streamSignaler.publishMessage(null, streamData);
|
|
355
360
|
}
|
|
356
|
-
async hookAll(hookTopic, data,
|
|
361
|
+
async hookAll(hookTopic, data, keyResolver, queryFacets = []) {
|
|
357
362
|
const config = await this.getVID();
|
|
358
363
|
const hookRule = await this.storeSignaler.getHookRule(hookTopic);
|
|
359
364
|
if (hookRule) {
|
|
360
365
|
const subscriptionTopic = await (0, utils_1.getSubscriptionTopic)(hookRule.to, this.store, config);
|
|
361
|
-
const resolvedQuery = await this.resolveQuery(subscriptionTopic,
|
|
366
|
+
const resolvedQuery = await this.resolveQuery(subscriptionTopic, keyResolver);
|
|
362
367
|
const reporter = new reporter_1.ReporterService(config, this.store, this.logger);
|
|
363
368
|
const workItems = await reporter.getWorkItems(resolvedQuery, queryFacets);
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
369
|
+
if (workItems.length) {
|
|
370
|
+
const taskService = new task_1.TaskService(this.store, this.logger);
|
|
371
|
+
await taskService.enqueueWorkItems(workItems.map(workItem => `${hookTopic}::${workItem}::${keyResolver.scrub || false}::${JSON.stringify(data)}`));
|
|
372
|
+
this.store.publish(key_1.KeyType.QUORUM, { type: 'work', originator: this.guid }, this.appId);
|
|
373
|
+
}
|
|
367
374
|
return workItems;
|
|
368
375
|
}
|
|
369
376
|
else {
|
|
@@ -436,7 +443,7 @@ class EngineService {
|
|
|
436
443
|
}, timeout);
|
|
437
444
|
});
|
|
438
445
|
}
|
|
439
|
-
async resolveOneTimeSubscription(context, jobOutput) {
|
|
446
|
+
async resolveOneTimeSubscription(context, jobOutput, emit = false) {
|
|
440
447
|
//todo: subscriber should query for the job...only publish minimum context needed
|
|
441
448
|
if (this.hasOneTimeSubscription(context)) {
|
|
442
449
|
const message = {
|
|
@@ -453,7 +460,7 @@ class EngineService {
|
|
|
453
460
|
const schema = await this.store.getSchema(activityId, config);
|
|
454
461
|
return schema.publishes;
|
|
455
462
|
}
|
|
456
|
-
async resolvePersistentSubscriptions(context, jobOutput) {
|
|
463
|
+
async resolvePersistentSubscriptions(context, jobOutput, emit = false) {
|
|
457
464
|
const topic = await this.getPublishesTopic(context);
|
|
458
465
|
if (topic) {
|
|
459
466
|
const message = {
|
|
@@ -476,20 +483,23 @@ class EngineService {
|
|
|
476
483
|
hasOneTimeSubscription(context) {
|
|
477
484
|
return Boolean(context.metadata.ngn);
|
|
478
485
|
}
|
|
479
|
-
//
|
|
480
|
-
async runJobCompletionTasks(context) {
|
|
486
|
+
// ********** JOB COMPLETION/CLEANUP (AND JOB EMIT) ***********
|
|
487
|
+
async runJobCompletionTasks(context, emit = false) {
|
|
488
|
+
//if 'emit' is true, the job isn't done. it's just emitting
|
|
481
489
|
const isAwait = this.hasParentJob(context);
|
|
482
490
|
const isOneTimeSubscription = this.hasOneTimeSubscription(context);
|
|
483
491
|
const topic = await this.getPublishesTopic(context);
|
|
484
492
|
if (isAwait || isOneTimeSubscription || topic) {
|
|
485
493
|
const jobOutput = await this.getState(context.metadata.tpc, context.metadata.jid);
|
|
486
494
|
//always wait for stream pub/sub
|
|
487
|
-
await this.execAdjacentParent(context, jobOutput);
|
|
495
|
+
await this.execAdjacentParent(context, jobOutput, emit);
|
|
488
496
|
//no need to wait for standard pub/sub
|
|
489
|
-
this.resolveOneTimeSubscription(context, jobOutput);
|
|
490
|
-
this.resolvePersistentSubscriptions(context, jobOutput);
|
|
497
|
+
this.resolveOneTimeSubscription(context, jobOutput, emit);
|
|
498
|
+
this.resolvePersistentSubscriptions(context, jobOutput, emit);
|
|
499
|
+
}
|
|
500
|
+
if (!emit) {
|
|
501
|
+
this.task.registerJobForCleanup(context.metadata.jid, context.metadata.expire);
|
|
491
502
|
}
|
|
492
|
-
this.task.registerJobForCleanup(context.metadata.jid, context.metadata.expire);
|
|
493
503
|
}
|
|
494
504
|
// ****** GET JOB STATE/COLLATION STATUS BY ID *********
|
|
495
505
|
async getStatus(jobId) {
|