@hotmeshio/hotmesh 0.0.19 → 0.0.20

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 (58) hide show
  1. package/README.md +4 -4
  2. package/build/modules/errors.d.ts +2 -1
  3. package/build/modules/errors.js +2 -1
  4. package/build/package.json +2 -1
  5. package/build/services/activities/activity.d.ts +2 -2
  6. package/build/services/activities/activity.js +10 -8
  7. package/build/services/activities/hook.d.ts +2 -1
  8. package/build/services/activities/hook.js +12 -9
  9. package/build/services/activities/signal.d.ts +4 -0
  10. package/build/services/activities/signal.js +16 -2
  11. package/build/services/durable/client.d.ts +15 -5
  12. package/build/services/durable/client.js +37 -14
  13. package/build/services/durable/factory.d.ts +2 -16
  14. package/build/services/durable/factory.js +276 -46
  15. package/build/services/durable/handle.d.ts +1 -1
  16. package/build/services/durable/handle.js +18 -5
  17. package/build/services/durable/search.d.ts +8 -1
  18. package/build/services/durable/search.js +34 -7
  19. package/build/services/durable/worker.d.ts +7 -9
  20. package/build/services/durable/worker.js +29 -23
  21. package/build/services/durable/workflow.d.ts +18 -1
  22. package/build/services/durable/workflow.js +99 -35
  23. package/build/services/engine/index.d.ts +2 -2
  24. package/build/services/engine/index.js +7 -12
  25. package/build/services/hotmesh/index.d.ts +2 -2
  26. package/build/services/hotmesh/index.js +2 -2
  27. package/build/services/signaler/store.d.ts +2 -2
  28. package/build/services/signaler/store.js +17 -7
  29. package/build/services/signaler/stream.js +1 -0
  30. package/build/services/store/clients/redis.js +1 -1
  31. package/build/services/store/index.js +3 -0
  32. package/build/services/telemetry/index.js +7 -1
  33. package/build/types/activity.d.ts +5 -3
  34. package/build/types/durable.d.ts +12 -1
  35. package/build/types/hook.d.ts +0 -1
  36. package/build/types/index.d.ts +1 -1
  37. package/modules/errors.ts +4 -2
  38. package/package.json +2 -1
  39. package/services/activities/activity.ts +10 -8
  40. package/services/activities/hook.ts +13 -10
  41. package/services/activities/signal.ts +17 -3
  42. package/services/durable/client.ts +40 -15
  43. package/services/durable/factory.ts +274 -46
  44. package/services/durable/handle.ts +18 -5
  45. package/services/durable/search.ts +36 -7
  46. package/services/durable/worker.ts +30 -24
  47. package/services/durable/workflow.ts +111 -38
  48. package/services/engine/index.ts +8 -12
  49. package/services/hotmesh/index.ts +3 -3
  50. package/services/signaler/store.ts +18 -8
  51. package/services/signaler/stream.ts +1 -0
  52. package/services/store/clients/redis.ts +1 -1
  53. package/services/store/index.ts +2 -0
  54. package/services/telemetry/index.ts +6 -1
  55. package/types/activity.ts +10 -8
  56. package/types/durable.ts +13 -0
  57. package/types/hook.ts +0 -1
  58. package/types/index.ts +1 -0
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # HotMesh
2
2
  ![alpha release](https://img.shields.io/badge/release-alpha-yellow)
3
3
 
4
- Elevate Redis from an in-memory data store to a game-changing [service mesh](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/faq.md#what-is-hotmesh). Turn your unpredictable functions into unbreakable workflows.
4
+ Elevate Redis from an in-memory data cache to a game-changing [service mesh](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/faq.md#what-is-hotmesh). Turn your unpredictable functions into unbreakable workflows.
5
5
 
6
6
  ## Install
7
7
  [![npm version](https://badge.fury.io/js/%40hotmeshio%2Fhotmesh.svg)](https://badge.fury.io/js/%40hotmeshio%2Fhotmesh)
@@ -13,7 +13,7 @@ npm install @hotmeshio/hotmesh
13
13
  ## Design
14
14
  The HotMesh SDK is designed to keep your code front-and-center. Write functions as you normally would, then use HotMesh to make them durable.
15
15
 
16
- 1. Start by defining **activities**. Activities are those functions that will be invoked by your workflow. They are commonly used to read and write to databases and invoke external services. They can be written in any style, using any framework, and can even be legacy functions you've already written. The only requirement is that they return a Promise. *Note how the `saludar` example throws an error 50% of the time. It doesn't matter how unpredictable your functions are, HotMesh will retry as necessary until they succeed.*
16
+ 1. Start by defining **activities**. Activities can be written in any style, using any framework, and can even be legacy functions you've already written. The only requirement is that they return a Promise. *Note how the `saludar` example throws an error 50% of the time. It doesn't matter how unpredictable your functions are, HotMesh will retry as necessary until they succeed.*
17
17
  ```javascript
18
18
  //activities.ts
19
19
 
@@ -47,7 +47,7 @@ The HotMesh SDK is designed to keep your code front-and-center. Write functions
47
47
  }
48
48
  ```
49
49
 
50
- 3. Although you could call your workflow directly (it's just a vanilla function), it's only durable when invoked and orchestrated via HotMesh. By using a HotMesh **client** to trigger the function, it's guaranteed to return a result.
50
+ 3. Although you could call your workflow directly (it's just a vanilla function), it's only durable when invoked and orchestrated via HotMesh.
51
51
  ```javascript
52
52
  //client.ts
53
53
 
@@ -75,7 +75,7 @@ The HotMesh SDK is designed to keep your code front-and-center. Write functions
75
75
  }
76
76
  ```
77
77
 
78
- 4. The last step is to create a **worker** and link it to your workflow function. Workers listen for tasks on their assigned channel, executing the targeted workflow function until it succeeds.
78
+ 4. Finally, create a **worker** and link your workflow function. Workers listen for tasks on their assigned Redis stream and invoke your workflow function each time they receive an event.
79
79
  ```javascript
80
80
  //worker.ts
81
81
 
@@ -25,7 +25,8 @@ declare class DurableSleepError extends Error {
25
25
  code: number;
26
26
  duration: number;
27
27
  index: number;
28
- constructor(message: string, duration: number, index: number);
28
+ dimension: string;
29
+ constructor(message: string, duration: number, index: number, dimension: string);
29
30
  }
30
31
  declare class DurableTimeoutError extends Error {
31
32
  code: number;
@@ -32,10 +32,11 @@ class DurableWaitForSignalError extends Error {
32
32
  }
33
33
  exports.DurableWaitForSignalError = DurableWaitForSignalError;
34
34
  class DurableSleepError extends Error {
35
- constructor(message, duration, index) {
35
+ constructor(message, duration, index, dimension) {
36
36
  super(message);
37
37
  this.duration = duration;
38
38
  this.index = index;
39
+ this.dimension = dimension;
39
40
  this.code = 595;
40
41
  }
41
42
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.19",
3
+ "version": "0.0.20",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -45,6 +45,7 @@
45
45
  "test:durable": "NODE_ENV=test jest ./tests/durable/*/index.test.ts --detectOpenHandles --forceExit --verbose",
46
46
  "test:durable:hello": "NODE_ENV=test jest ./tests/durable/helloworld/index.test.ts --detectOpenHandles --forceExit --verbose",
47
47
  "test:durable:goodbye": "NODE_ENV=test jest ./tests/durable/goodbye/index.test.ts --detectOpenHandles --forceExit --verbose",
48
+ "test:durable:hook": "NODE_ENV=test jest ./tests/durable/hook/index.test.ts --detectOpenHandles --forceExit --verbose",
48
49
  "test:durable:retry": "NODE_ENV=test jest ./tests/durable/retry/index.test.ts --detectOpenHandles --forceExit --verbose",
49
50
  "test:durable:fatal": "NODE_ENV=test jest ./tests/durable/fatal/index.test.ts --detectOpenHandles --forceExit --verbose",
50
51
  "test:durable:sleep": "NODE_ENV=test jest ./tests/durable/sleep/index.test.ts --detectOpenHandles --forceExit --verbose",
@@ -26,7 +26,7 @@ declare class Activity {
26
26
  adjacentIndex: number;
27
27
  constructor(config: ActivityType, data: ActivityData, metadata: ActivityMetadata, hook: ActivityData | null, engine: EngineService, context?: JobState);
28
28
  setLeg(leg: ActivityLeg): void;
29
- processEvent(status?: StreamStatus, code?: StreamCode, type?: 'hook' | 'output', jobId?: string): Promise<void>;
29
+ processEvent(status?: StreamStatus, code?: StreamCode, type?: 'hook' | 'output'): Promise<void>;
30
30
  processPending(telemetry: TelemetryService, type: 'hook' | 'output'): Promise<MultiResponseFlags>;
31
31
  processSuccess(telemetry: TelemetryService, type: 'hook' | 'output'): Promise<MultiResponseFlags>;
32
32
  processError(telemetry: TelemetryService, type: string): Promise<MultiResponseFlags>;
@@ -49,7 +49,7 @@ declare class Activity {
49
49
  bindActivityState(state: StringAnyType): void;
50
50
  bindJobMetadataPaths(): string[];
51
51
  bindActivityMetadataPaths(): string[];
52
- getState(jobId?: string): Promise<void>;
52
+ getState(): Promise<void>;
53
53
  initDimensionalAddress(dad: string): void;
54
54
  initSelf(context: StringAnyType): JobState;
55
55
  initPolicies(context: JobState): void;
@@ -30,16 +30,20 @@ class Activity {
30
30
  this.leg = leg;
31
31
  }
32
32
  //******** DUPLEX RE-ENTRY POINT ********//
33
- async processEvent(status = stream_1.StreamStatus.SUCCESS, code = 200, type = 'output', jobId) {
33
+ async processEvent(status = stream_1.StreamStatus.SUCCESS, code = 200, type = 'output') {
34
34
  this.setLeg(2);
35
- const jid = this.context.metadata.jid || jobId;
35
+ const jid = this.context.metadata.jid;
36
+ if (!jid) {
37
+ this.logger.error('activity-process-event-error', { message: 'job id is undefined' });
38
+ return;
39
+ }
36
40
  const aid = this.metadata.aid;
37
41
  this.status = status;
38
42
  this.code = code;
39
43
  this.logger.debug('activity-process-event', { topic: this.config.subtype, jid, aid, status, code });
40
44
  let telemetry;
41
45
  try {
42
- await this.getState(jobId);
46
+ await this.getState();
43
47
  const aState = await collator_1.CollatorService.notarizeReentry(this);
44
48
  this.adjacentIndex = collator_1.CollatorService.getDimensionalIndex(aState);
45
49
  telemetry = new telemetry_1.TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
@@ -67,7 +71,6 @@ class Activity {
67
71
  this.logger.info('process-event-inactive-error', { error });
68
72
  return;
69
73
  }
70
- console.error(error);
71
74
  this.logger.error('activity-process-event-error', { error });
72
75
  telemetry && telemetry.setActivityError(error.message);
73
76
  throw error;
@@ -251,7 +254,7 @@ class Activity {
251
254
  const keys_to_save = this.leg === 1 ? 'ACTIVITY' : 'ACTIVITY_UPDATE';
252
255
  return serializer_1.MDATA_SYMBOLS[keys_to_save].KEYS.map((key) => `output/metadata/${key}`);
253
256
  }
254
- async getState(jobId) {
257
+ async getState() {
255
258
  //assemble list of paths necessary to create 'job state' from the 'symbol hash'
256
259
  const jobSymbolHashName = `$${this.config.subscribes}`;
257
260
  const consumes = {
@@ -277,10 +280,9 @@ class Activity {
277
280
  }
278
281
  telemetry_1.TelemetryService.addTargetTelemetryPaths(consumes, this.config, this.metadata, this.leg);
279
282
  let { dad, jid } = this.context.metadata;
280
- jobId = jobId || jid;
281
- const dIds = collator_1.CollatorService.getDimensionsById([...this.config.ancestors, this.metadata.aid], dad);
283
+ const dIds = collator_1.CollatorService.getDimensionsById([...this.config.ancestors, this.metadata.aid], dad || '');
282
284
  //`state` is a flat hash; context is a tree
283
- const [state, status] = await this.store.getState(jobId, consumes, dIds);
285
+ const [state, status] = await this.store.getState(jid, consumes, dIds);
284
286
  this.context = (0, utils_1.restoreHierarchy)(state);
285
287
  this.initDimensionalAddress(dad);
286
288
  this.initSelf(this.context);
@@ -4,6 +4,7 @@ import { ActivityData, ActivityMetadata, ActivityType, HookActivity } from '../.
4
4
  import { HookRule } from '../../types/hook';
5
5
  import { JobState, JobStatus } from '../../types/job';
6
6
  import { RedisMulti } from '../../types/redis';
7
+ import { StreamCode, StreamStatus } from '../../types/stream';
7
8
  /**
8
9
  * Listens for `webhook`, `timehook`, and `cycle` (repeat) signals
9
10
  */
@@ -14,7 +15,7 @@ declare class Hook extends Activity {
14
15
  doesHook(): boolean;
15
16
  getHookRule(topic: string): Promise<HookRule | undefined>;
16
17
  registerHook(multi?: RedisMulti): Promise<string | void>;
17
- processWebHookEvent(): Promise<JobStatus | void>;
18
+ processWebHookEvent(status?: StreamStatus, code?: StreamCode): Promise<JobStatus | void>;
18
19
  processTimeHookEvent(jobId: string): Promise<JobStatus | void>;
19
20
  }
20
21
  export { Hook };
@@ -85,7 +85,7 @@ class Hook extends activity_1.Activity {
85
85
  async registerHook(multi) {
86
86
  if (this.config.hook?.topic) {
87
87
  const signaler = new store_1.StoreSignaler(this.store, this.logger);
88
- return await signaler.registerWebHook(this.config.hook.topic, this.context, multi);
88
+ return await signaler.registerWebHook(this.config.hook.topic, this.context, this.resolveDad(), multi);
89
89
  }
90
90
  else if (this.config.sleep) {
91
91
  const durationInSeconds = pipe_1.Pipe.resolve(this.config.sleep, this.context);
@@ -96,19 +96,22 @@ class Hook extends activity_1.Activity {
96
96
  return jobId;
97
97
  }
98
98
  }
99
- async processWebHookEvent() {
99
+ async processWebHookEvent(status = stream_1.StreamStatus.SUCCESS, code = 200) {
100
100
  this.logger.debug('hook-process-web-hook-event', {
101
101
  topic: this.config.hook.topic,
102
- aid: this.metadata.aid
102
+ aid: this.metadata.aid,
103
+ status,
104
+ code,
103
105
  });
104
106
  const signaler = new store_1.StoreSignaler(this.store, this.logger);
105
107
  const data = { ...this.data };
106
- const jobId = await signaler.processWebHookSignal(this.config.hook.topic, data);
107
- if (jobId) {
108
- //if a webhook signal is sent that includes 'keep_alive' the hook will remain open
109
- const code = data.keep_alive ? 202 : 200;
110
- await this.processEvent(stream_1.StreamStatus.SUCCESS, code, 'hook', jobId);
111
- if (code === 200) {
108
+ const signal = await signaler.processWebHookSignal(this.config.hook.topic, data);
109
+ if (signal) {
110
+ const [jobId, aid, dad] = signal;
111
+ this.context.metadata.jid = jobId;
112
+ this.context.metadata.dad = dad;
113
+ await this.processEvent(status, code, 'hook');
114
+ if (code === 200) { //otherwise 202 for pending/keepalive
112
115
  await signaler.deleteWebHookSignal(this.config.hook.topic, data);
113
116
  }
114
117
  } //else => already resolved
@@ -8,6 +8,10 @@ declare class Signal extends Activity {
8
8
  process(): Promise<string>;
9
9
  mapSignalData(): Record<string, any>;
10
10
  mapResolverData(): Record<string, any>;
11
+ /**
12
+ * The signal activity will hook one
13
+ */
14
+ hookOne(): Promise<string>;
11
15
  /**
12
16
  * The signal activity will hook all paused jobs that share the same job key.
13
17
  */
@@ -31,8 +31,12 @@ class Signal extends activity_1.Activity {
31
31
  await collator_1.CollatorService.notarizeEarlyCompletion(this, multi);
32
32
  await this.setStatus(this.adjacencyList.length - 1, multi);
33
33
  const multiResponse = await multi.exec();
34
- //signal to awaken all paused jobs that share the targeted job key
35
- await this.hookAll();
34
+ if (this.config.subtype === 'all') {
35
+ await this.hookAll();
36
+ }
37
+ else {
38
+ await this.hookOne();
39
+ }
36
40
  //transition to adjacent activities
37
41
  const jobStatus = this.resolveStatus(multiResponse);
38
42
  const attrs = { 'app.job.jss': jobStatus };
@@ -71,6 +75,16 @@ class Signal extends activity_1.Activity {
71
75
  return mapper.mapRules();
72
76
  }
73
77
  }
78
+ /**
79
+ * The signal activity will hook one
80
+ */
81
+ async hookOne() {
82
+ const topic = pipe_1.Pipe.resolve(this.config.topic, this.context);
83
+ const signalInputData = this.mapSignalData();
84
+ const status = pipe_1.Pipe.resolve(this.config.status, this.context);
85
+ const code = pipe_1.Pipe.resolve(this.config.code, this.context);
86
+ return await this.engine.hook(topic, signalInputData, status, code);
87
+ }
74
88
  /**
75
89
  * The signal activity will hook all paused jobs that share the same job key.
76
90
  */
@@ -1,12 +1,12 @@
1
1
  import { WorkflowHandleService } from './handle';
2
2
  import { HotMeshService as HotMesh } from '../hotmesh';
3
- import { ClientConfig, Connection, WorkflowOptions, WorkflowSearchOptions } from '../../types/durable';
3
+ import { ClientConfig, Connection, HookOptions, WorkflowOptions, WorkflowSearchOptions } from '../../types/durable';
4
4
  export declare class ClientService {
5
5
  connection: Connection;
6
6
  options: WorkflowOptions;
7
7
  static instances: Map<string, HotMesh | Promise<HotMesh>>;
8
8
  constructor(config: ClientConfig);
9
- getHotMeshClient: (worflowTopic: string) => Promise<HotMesh>;
9
+ getHotMeshClient: (worflowTopic: string, namespace?: string) => Promise<HotMesh>;
10
10
  /**
11
11
  * For those deployments with a redis stack backend (with the FT module),
12
12
  * this method will configure the search index for the workflow.
@@ -15,9 +15,19 @@ export declare class ClientService {
15
15
  search: (hotMeshClient: HotMesh, index: string, query: string[]) => Promise<string[]>;
16
16
  workflow: {
17
17
  start: (options: WorkflowOptions) => Promise<WorkflowHandleService>;
18
- signal: (signalId: string, data: Record<any, any>) => Promise<string>;
19
- getHandle: (taskQueue: string, workflowName: string, workflowId: string) => Promise<WorkflowHandleService>;
20
- search: (taskQueue: string, workflowName: string, index: string, ...query: string[]) => Promise<string[]>;
18
+ /**
19
+ * send a message to a running workflow that is paused and awaiting the signal
20
+ */
21
+ signal: (signalId: string, data: Record<any, any>, namespace?: string) => Promise<string>;
22
+ /**
23
+ * send a message to spawn an parallel in-process thread of execution
24
+ * with the same job state as the main thread but bound to a different
25
+ * handler function. All job state will be journaled to the same hash
26
+ * as is used by the main thread.
27
+ */
28
+ hook: (options: HookOptions) => Promise<string>;
29
+ getHandle: (taskQueue: string, workflowName: string, workflowId: string, namespace?: string) => Promise<WorkflowHandleService>;
30
+ search: (taskQueue: string, workflowName: string, namespace: null | string, index: string, ...query: string[]) => Promise<string[]>;
21
31
  };
22
32
  activateWorkflow(hotMesh: HotMesh, appId?: string, version?: string): Promise<void>;
23
33
  static shutdown(): Promise<void>;
@@ -7,15 +7,16 @@ const handle_1 = require("./handle");
7
7
  const hotmesh_1 = require("../hotmesh");
8
8
  const key_1 = require("../../modules/key");
9
9
  const search_1 = require("./search");
10
+ const types_1 = require("../../types");
10
11
  class ClientService {
11
12
  constructor(config) {
12
- this.getHotMeshClient = async (worflowTopic) => {
13
+ this.getHotMeshClient = async (worflowTopic, namespace) => {
13
14
  //NOTE: every unique topic inits a new engine
14
15
  if (ClientService.instances.has(worflowTopic)) {
15
16
  return await ClientService.instances.get(worflowTopic);
16
17
  }
17
18
  const hotMeshClient = hotmesh_1.HotMeshService.init({
18
- appId: factory_1.APP_ID,
19
+ appId: namespace ?? factory_1.APP_ID,
19
20
  engine: {
20
21
  redis: {
21
22
  class: this.connection.class,
@@ -26,7 +27,7 @@ class ClientService {
26
27
  ClientService.instances.set(worflowTopic, hotMeshClient);
27
28
  //since the YAML topic is dynamic, it MUST be manually created before use
28
29
  const store = (await hotMeshClient).engine.store;
29
- const params = { appId: factory_1.APP_ID, topic: worflowTopic };
30
+ const params = { appId: namespace ?? factory_1.APP_ID, topic: worflowTopic };
30
31
  const streamKey = store.mintKey(key_1.KeyType.STREAMS, params);
31
32
  try {
32
33
  await store.xgroup('CREATE', streamKey, 'WORKER', '$', 'MKSTREAM');
@@ -34,7 +35,7 @@ class ClientService {
34
35
  catch (err) {
35
36
  //ignore if already exists
36
37
  }
37
- await this.activateWorkflow(await hotMeshClient);
38
+ await this.activateWorkflow(await hotMeshClient, namespace ?? factory_1.APP_ID);
38
39
  return hotMeshClient;
39
40
  };
40
41
  /**
@@ -46,7 +47,7 @@ class ClientService {
46
47
  const store = hotMeshClient.engine.store;
47
48
  const schema = [];
48
49
  for (const [key, value] of Object.entries(search.schema)) {
49
- //prefix with a comma (avoids collisions with hotmesh reserved words)
50
+ //prefix with an underscore (avoids collisions with hotmesh reserved symbols)
50
51
  schema.push(`_${key}`);
51
52
  schema.push(value.type);
52
53
  if (value.sortable) {
@@ -79,7 +80,7 @@ class ClientService {
79
80
  const spn = options.workflowSpan;
80
81
  //topic is concat of taskQueue and workflowName
81
82
  const workflowTopic = `${taskQueueName}-${workflowName}`;
82
- const hotMeshClient = await this.getHotMeshClient(workflowTopic);
83
+ const hotMeshClient = await this.getHotMeshClient(workflowTopic, options.namespace);
83
84
  this.configureSearchIndex(hotMeshClient, options.search);
84
85
  const payload = {
85
86
  arguments: [...options.args],
@@ -89,27 +90,49 @@ class ClientService {
89
90
  backoffCoefficient: options.config?.backoffCoefficient || factory_1.DEFAULT_COEFFICIENT,
90
91
  };
91
92
  const context = { metadata: { trc, spn }, data: {} };
92
- const jobId = await hotMeshClient.pub(factory_1.SUBSCRIBES_TOPIC, payload, context);
93
+ const jobId = await hotMeshClient.pub(`${options.namespace ?? factory_1.APP_ID}.execute`, payload, context);
93
94
  if (jobId && options.search?.data) {
94
95
  //job successfully kicked off; there is default job data to persist
95
- const search = new search_1.Search(jobId, hotMeshClient);
96
+ const searchSessionId = `-search-0`;
97
+ const search = new search_1.Search(jobId, hotMeshClient, searchSessionId);
96
98
  for (const [key, value] of Object.entries(options.search.data)) {
97
99
  search.set(key, value);
98
100
  }
99
101
  }
100
102
  return new handle_1.WorkflowHandleService(hotMeshClient, workflowTopic, jobId);
101
103
  },
102
- signal: async (signalId, data) => {
103
- return await (await this.getHotMeshClient('durable.wfs.signal')).hook('durable.wfs.signal', { id: signalId, data });
104
+ /**
105
+ * send a message to a running workflow that is paused and awaiting the signal
106
+ */
107
+ signal: async (signalId, data, namespace) => {
108
+ const topic = `${namespace ?? factory_1.APP_ID}.wfs.signal`;
109
+ return await (await this.getHotMeshClient(topic, namespace)).hook(topic, { id: signalId, data });
104
110
  },
105
- getHandle: async (taskQueue, workflowName, workflowId) => {
111
+ /**
112
+ * send a message to spawn an parallel in-process thread of execution
113
+ * with the same job state as the main thread but bound to a different
114
+ * handler function. All job state will be journaled to the same hash
115
+ * as is used by the main thread.
116
+ */
117
+ hook: async (options) => {
118
+ const workflowTopic = `${options.taskQueue}-${options.workflowName}`;
119
+ const payload = {
120
+ arguments: [...options.args],
121
+ id: options.workflowId,
122
+ workflowTopic,
123
+ backoffCoefficient: options.config?.backoffCoefficient || factory_1.DEFAULT_COEFFICIENT,
124
+ };
125
+ const hotMeshClient = await this.getHotMeshClient(workflowTopic, options.namespace);
126
+ return await hotMeshClient.hook(`${hotMeshClient.appId}.flow.signal`, payload, types_1.StreamStatus.PENDING, 202);
127
+ },
128
+ getHandle: async (taskQueue, workflowName, workflowId, namespace) => {
106
129
  const workflowTopic = `${taskQueue}-${workflowName}`;
107
- const hotMeshClient = await this.getHotMeshClient(workflowTopic);
130
+ const hotMeshClient = await this.getHotMeshClient(workflowTopic, namespace);
108
131
  return new handle_1.WorkflowHandleService(hotMeshClient, workflowTopic, workflowId);
109
132
  },
110
- search: async (taskQueue, workflowName, index, ...query) => {
133
+ search: async (taskQueue, workflowName, namespace, index, ...query) => {
111
134
  const workflowTopic = `${taskQueue}-${workflowName}`;
112
- const hotMeshClient = await this.getHotMeshClient(workflowTopic);
135
+ const hotMeshClient = await this.getHotMeshClient(workflowTopic, namespace);
113
136
  try {
114
137
  return await this.search(hotMeshClient, index, query);
115
138
  }
@@ -2,7 +2,7 @@
2
2
  * NOTE: Using `maxSystemRetries = 3` and `backoffCoefficient = 10`, errant
3
3
  * workflows will be retried on the following schedule (8 times in 27 hours):
4
4
  * => 10ms, 100ms, 1000ms, 10s, 100s, 1_000s, 10_000s, 100_000s
5
- * 593:
5
+ *
6
6
  * 594: waitforsignal
7
7
  * 595: sleep
8
8
  * 596, 597, 598: fatal
@@ -11,19 +11,5 @@
11
11
  declare const getWorkflowYAML: (app: string, version: string) => string;
12
12
  declare const APP_VERSION = "1";
13
13
  declare const APP_ID = "durable";
14
- declare const ACTIVITY_SUBSCRIBES_TOPIC = "durable.activity.execute";
15
- declare const ACTIVITY_PUBLISHES_TOPIC = "durable.activity.executed";
16
- declare const SLEEP_SUBSCRIBES_TOPIC = "durable.sleep.execute";
17
- declare const SLEEP_PUBLISHES_TOPIC = "durable.sleep.executed";
18
- declare const WFS_SUBSCRIBES_TOPIC = "durable.wfs.execute";
19
- declare const WFS_PUBLISHES_TOPIC = "durable.wfs.executed";
20
- declare const WFSC_SUBSCRIBES_TOPIC = "durable.wfsc.execute";
21
- declare const WFSC_PUBLISHES_TOPIC = "durable.wfsc.executed";
22
- declare const SUBSCRIBES_TOPIC = "durable.execute";
23
- declare const PUBLISHES_TOPIC = "durable.executed";
24
- declare const HOOK_ID = "durable.awaken";
25
- declare const ACTIVITY_HOOK_ID = "durable.activity.awaken";
26
- declare const SLEEP_HOOK_ID = "durable.sleep.awaken";
27
- declare const WFS_HOOK_ID = "durable.wfs.awaken";
28
14
  declare const DEFAULT_COEFFICIENT = 10;
29
- export { getWorkflowYAML, APP_VERSION, APP_ID, ACTIVITY_SUBSCRIBES_TOPIC, ACTIVITY_PUBLISHES_TOPIC, SLEEP_SUBSCRIBES_TOPIC, SLEEP_PUBLISHES_TOPIC, SUBSCRIBES_TOPIC, PUBLISHES_TOPIC, HOOK_ID, ACTIVITY_HOOK_ID, SLEEP_HOOK_ID, DEFAULT_COEFFICIENT, WFS_SUBSCRIBES_TOPIC, WFS_PUBLISHES_TOPIC, WFSC_SUBSCRIBES_TOPIC, WFSC_PUBLISHES_TOPIC, WFS_HOOK_ID, };
15
+ export { getWorkflowYAML, APP_VERSION, APP_ID, DEFAULT_COEFFICIENT, };