@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.
Files changed (82) hide show
  1. package/README.md +2 -2
  2. package/build/modules/errors.d.ts +22 -1
  3. package/build/modules/errors.js +28 -1
  4. package/build/modules/utils.d.ts +2 -1
  5. package/build/modules/utils.js +5 -1
  6. package/build/package.json +7 -2
  7. package/build/services/activities/activity.d.ts +2 -0
  8. package/build/services/activities/activity.js +16 -10
  9. package/build/services/activities/await.d.ts +2 -6
  10. package/build/services/activities/await.js +12 -75
  11. package/build/services/activities/cycle.js +2 -2
  12. package/build/services/activities/index.d.ts +2 -2
  13. package/build/services/activities/index.js +2 -2
  14. package/build/services/activities/signal.d.ts +16 -0
  15. package/build/services/activities/signal.js +94 -0
  16. package/build/services/activities/trigger.js +4 -3
  17. package/build/services/activities/worker.d.ts +2 -1
  18. package/build/services/activities/worker.js +11 -6
  19. package/build/services/compiler/deployer.js +3 -1
  20. package/build/services/durable/client.d.ts +3 -2
  21. package/build/services/durable/client.js +39 -21
  22. package/build/services/durable/factory.d.ts +22 -18
  23. package/build/services/durable/factory.js +722 -50
  24. package/build/services/durable/handle.d.ts +1 -0
  25. package/build/services/durable/handle.js +5 -1
  26. package/build/services/durable/worker.d.ts +3 -8
  27. package/build/services/durable/worker.js +75 -73
  28. package/build/services/durable/workflow.d.ts +5 -0
  29. package/build/services/durable/workflow.js +93 -24
  30. package/build/services/engine/index.d.ts +6 -6
  31. package/build/services/engine/index.js +25 -15
  32. package/build/services/hotmesh/index.d.ts +2 -1
  33. package/build/services/hotmesh/index.js +3 -1
  34. package/build/services/mapper/index.js +1 -1
  35. package/build/services/pipe/functions/array.d.ts +1 -0
  36. package/build/services/pipe/functions/array.js +3 -0
  37. package/build/services/reporter/index.js +9 -2
  38. package/build/services/signaler/store.js +8 -3
  39. package/build/services/signaler/stream.js +3 -3
  40. package/build/services/store/clients/ioredis.js +15 -15
  41. package/build/services/store/clients/redis.js +18 -18
  42. package/build/services/store/index.d.ts +1 -1
  43. package/build/services/store/index.js +11 -3
  44. package/build/services/task/index.js +3 -3
  45. package/build/types/activity.d.ts +15 -6
  46. package/build/types/durable.d.ts +15 -2
  47. package/build/types/index.d.ts +2 -2
  48. package/build/types/stats.d.ts +1 -0
  49. package/modules/errors.ts +35 -0
  50. package/modules/utils.ts +5 -1
  51. package/package.json +7 -2
  52. package/services/activities/activity.ts +19 -9
  53. package/services/activities/await.ts +14 -90
  54. package/services/activities/cycle.ts +2 -2
  55. package/services/activities/index.ts +2 -2
  56. package/services/activities/signal.ts +124 -0
  57. package/services/activities/trigger.ts +4 -3
  58. package/services/activities/worker.ts +13 -13
  59. package/services/compiler/deployer.ts +3 -1
  60. package/services/durable/client.ts +48 -23
  61. package/services/durable/factory.ts +723 -49
  62. package/services/durable/handle.ts +6 -1
  63. package/services/durable/worker.ts +92 -79
  64. package/services/durable/workflow.ts +95 -25
  65. package/services/engine/index.ts +33 -24
  66. package/services/hotmesh/index.ts +7 -4
  67. package/services/mapper/index.ts +1 -1
  68. package/services/pipe/functions/array.ts +4 -0
  69. package/services/reporter/index.ts +10 -2
  70. package/services/signaler/store.ts +8 -3
  71. package/services/signaler/stream.ts +3 -3
  72. package/services/store/clients/ioredis.ts +15 -15
  73. package/services/store/clients/redis.ts +18 -18
  74. package/services/store/index.ts +12 -3
  75. package/services/task/index.ts +3 -3
  76. package/types/activity.ts +16 -7
  77. package/types/durable.ts +18 -1
  78. package/types/index.ts +2 -1
  79. package/types/stats.ts +1 -0
  80. package/build/services/activities/emit.d.ts +0 -9
  81. package/build/services/activities/emit.js +0 -13
  82. package/services/activities/emit.ts +0 -25
@@ -4,5 +4,6 @@ export declare class WorkflowHandleService {
4
4
  workflowTopic: string;
5
5
  workflowId: string;
6
6
  constructor(hotMesh: HotMesh, workflowTopic: string, workflowId: string);
7
+ signal(signalId: string, data: Record<any, any>): Promise<void>;
7
8
  result(): Promise<any>;
8
9
  }
@@ -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 = `${this.workflowTopic}.${this.workflowId}`;
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, topic: string, dagFactory: Function, options?: WorkerOptions): Promise<void>;
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
- initActivityWorkflow(config: WorkerConfig, activityTopic: string): Promise<HotMesh>;
20
+ initActivityWorker(config: WorkerConfig, activityTopic: string): Promise<HotMesh>;
25
21
  wrapActivityFunctions(): Function;
26
- activateActivityWorkflow(hotMesh: HotMesh, activityTopic: string): Promise<void>;
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: { NativeConnection, Worker } } from '@hotmeshio/hotmesh';
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 connection = await NativeConnection.connect({
22
- class: Redis,
23
- options: {
24
- host: 'localhost',
25
- port: 6379,
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, topic, dagFactory, options = {}) {
44
- const version = '1';
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(dagFactory(topic, version, options.maxSystemRetries, options.backoffExponent));
50
- await hotMesh.activate(version);
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(version);
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.initActivityWorkflow(config, activityTopic);
101
- await WorkerService.activateWorkflow(worker.activityRunner, activityTopic, factory_1.getActivityYAML);
102
- worker.workflowRunner = await worker.initWorkerWorkflow(config, workflowTopic, workflowFunction);
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 initActivityWorkflow(config, activityTopic) {
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 hmshInstance = await hotmesh_1.HotMeshService.init({
127
- appId: activityTopic,
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, hmshInstance);
137
- return hmshInstance;
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 activateActivityWorkflow(hotMesh, activityTopic) {
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 hmshInstance = await hotmesh_1.HotMeshService.init({
199
- appId: workflowTopic,
165
+ const hotMeshWorker = await hotmesh_1.HotMeshService.init({
166
+ appId: factory_1.APP_ID,
200
167
  engine: { redis: redisConfig },
201
- workers: [
202
- { topic: workflowTopic,
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, hmshInstance);
209
- return hmshInstance;
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
- // 59* - Durable*Error
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 hotMesh = hotmesh_1.HotMeshService.init({
260
- appId: worflowTopic,
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, hotMesh);
264
- await WorkerService.activateWorkflow(await hotMesh, worflowTopic, factory_1.getWorkflowYAML, options);
265
- return hotMesh;
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 hmshInstance = await worker_1.WorkerService.getHotMesh(activityTopic);
107
- activityState = await hmshInstance.getState(activityTopic, activityJobId);
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 hmshInstance.scrub(activityJobId);
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
- hmshInstance.sub(activityTopic, async (topic, message) => {
185
+ hotMeshClient.sub(`${factory_1.ACTIVITY_PUBLISHES_TOPIC}.${activityJobId}`, async (topic, message) => {
119
186
  const response = message.data?.response;
120
- hmshInstance.unsub(activityTopic);
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 hmshInstance = await worker_1.WorkerService.getHotMesh(activityTopic);
205
+ const hotMeshClient = await worker_1.WorkerService.getHotMesh(activityTopic);
137
206
  const context = { metadata: { trc, spn }, data: {} };
138
- const jobOutput = await hmshInstance.pubsub(activityTopic, payload, context, duration);
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<JobStatus | void>;
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, query: JobStatsInput, queryFacets?: string[]): Promise<string[]>;
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, query, queryFacets = []) {
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, query);
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
- const taskService = new task_1.TaskService(this.store, this.logger);
365
- await taskService.enqueueWorkItems(workItems.map(workItem => `${hookTopic}::${workItem}::${JSON.stringify(data)}`));
366
- this.store.publish(key_1.KeyType.QUORUM, { type: 'work', originator: this.guid }, this.appId);
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
- // ***************** JOB COMPLETION/CLEANUP *****************
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) {