@hotmeshio/hotmesh 0.0.38 → 0.0.39

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 (51) hide show
  1. package/README.md +13 -7
  2. package/build/modules/enums.d.ts +29 -23
  3. package/build/modules/enums.js +38 -29
  4. package/build/modules/errors.d.ts +1 -1
  5. package/build/modules/errors.js +9 -7
  6. package/build/modules/key.d.ts +1 -34
  7. package/build/modules/key.js +24 -47
  8. package/build/package.json +1 -1
  9. package/build/services/activities/activity.js +1 -1
  10. package/build/services/activities/hook.js +4 -9
  11. package/build/services/activities/trigger.d.ts +3 -2
  12. package/build/services/activities/trigger.js +10 -6
  13. package/build/services/durable/client.d.ts +9 -1
  14. package/build/services/durable/client.js +30 -14
  15. package/build/services/durable/handle.js +2 -2
  16. package/build/services/durable/worker.js +4 -3
  17. package/build/services/engine/index.d.ts +2 -1
  18. package/build/services/engine/index.js +6 -6
  19. package/build/services/router/index.js +16 -14
  20. package/build/services/store/index.d.ts +14 -9
  21. package/build/services/store/index.js +46 -23
  22. package/build/services/task/index.d.ts +10 -3
  23. package/build/services/task/index.js +35 -17
  24. package/build/types/durable.d.ts +3 -2
  25. package/build/types/hotmesh.d.ts +43 -2
  26. package/build/types/hotmesh.js +28 -0
  27. package/build/types/index.d.ts +3 -2
  28. package/build/types/index.js +3 -1
  29. package/build/types/logger.d.ts +1 -0
  30. package/build/types/logger.js +1 -0
  31. package/build/types/task.d.ts +1 -0
  32. package/build/types/task.js +2 -0
  33. package/modules/enums.ts +49 -35
  34. package/modules/errors.ts +17 -8
  35. package/modules/key.ts +3 -40
  36. package/package.json +1 -1
  37. package/services/activities/activity.ts +2 -2
  38. package/services/activities/hook.ts +18 -9
  39. package/services/activities/trigger.ts +10 -6
  40. package/services/durable/client.ts +31 -15
  41. package/services/durable/handle.ts +3 -3
  42. package/services/durable/worker.ts +4 -3
  43. package/services/engine/index.ts +13 -12
  44. package/services/router/index.ts +26 -24
  45. package/services/store/index.ts +59 -25
  46. package/services/task/index.ts +66 -24
  47. package/types/durable.ts +6 -5
  48. package/types/hotmesh.ts +47 -2
  49. package/types/index.ts +8 -1
  50. package/types/logger.ts +3 -1
  51. package/types/task.ts +1 -0
package/README.md CHANGED
@@ -19,8 +19,17 @@ HotMesh inverts the relationship to Redis: those functions that once used Redis
19
19
 
20
20
  HotMesh creates an *ad hoc*, Redis-backed network of functions and organizes them into a unified service mesh. *Any service with access to Redis can join in the network, bypassing the legacy clutter.*
21
21
 
22
+ <img src="./docs/img/hotmesh_emergent_properties.png" alt="HotMesh elevates Redis to a Service Mesh, Message Router, Integration Bus, and Business Process Engine." style="max-width:100%;width:600px;">
23
+
24
+
22
25
  ## Design
23
- The simplest way to get started is to use the `Durable` module. It's organized using principles similar to temporal.io. If you're familiar with their SDK, the setup is similar.
26
+ HotMesh uses your existing Redis installation. If you already have an instance available, you have all the infrastructure you'll ever need.
27
+
28
+ ### Design | Pluck
29
+ The simplest way to get started is to use the [HotMesh Pluck](https://github.com/hotmeshio/pluck-typescript) package. It's designed for usability, and is backed by the HotMesh system if you need advanced features. It includes a detailed [SDK](https://hotmeshio.github.io/pluck-typescript/) and a wide variety of examples.
30
+
31
+ ### Design | Durable
32
+ HotMesh's *Durable* module is a TypeScript Library modeled after Temporal.io. If you're familiar with their SDK, the principles are the same.
24
33
 
25
34
  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.*
26
35
  ```javascript
@@ -99,7 +108,7 @@ The simplest way to get started is to use the `Durable` module. It's organized u
99
108
  }
100
109
  ```
101
110
 
102
- ### Workflow Extensions
111
+ #### Workflow Extensions
103
112
  Redis governance delivers more than just reliability. Externalizing state fundamentally changes the execution profile for your functions, allowing you to design long-running, durable workflows. The `Durable` base class (shown in the examples above) provides additional methods for solving the most common state management challenges.
104
113
 
105
114
  - `waitForSignal` Pause your function and wait for external event(s) before continuing. The *waitForSignal* method will collate and cache the signals and only awaken your function once all signals have arrived.
@@ -177,8 +186,8 @@ Redis governance delivers more than just reliability. Externalizing state fundam
177
186
 
178
187
  Refer to the [hotmeshio/samples-typescript](https://github.com/hotmeshio/samples-typescript) repo for usage examples.
179
188
 
180
- ## Advanced Design
181
- HotMesh's TypeScript SDK is the easiest way to make your functions durable. But if you need full control over your function lifecycles (including high-volume, high-speed use cases), you can use HotMesh's underlying YAML models to optimize your durable workflows. The following model depicts a sequence of activities orchestrated by HotMesh. Any function you associate with a `topic` in your YAML definition is guaranteed to be durable.
189
+ ### Design | Advanced
190
+ The *Pluck* and *Durable* modules are the easiest way to use HotMesh. But if you need full control over your function lifecycles (including high-volume, high-speed use cases), you can use HotMesh's underlying YAML models to optimize your durable workflows. The following model depicts a sequence of activities orchestrated by HotMesh. Any function you associate with a `topic` in your YAML definition is guaranteed to be durable.
182
191
 
183
192
  ```yaml
184
193
  app:
@@ -310,6 +319,3 @@ HotMesh is a distributed orchestration engine. Refer to the [Distributed Orchest
310
319
 
311
320
  ## System Lifecycle
312
321
  Gain insight into HotMesh's monitoring, exception handling, and alarm configurations via the [System Lifecycle Guide](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/system_lifecycle.md).
313
-
314
- ## Alpha Release
315
- So what exacty is an [alpha release](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/alpha.md)?
@@ -1,23 +1,29 @@
1
- export declare const STATUS_CODE_SUCCESS = 200;
2
- export declare const STATUS_CODE_PENDING = 202;
3
- export declare const STATUS_CODE_TIMEOUT = 504;
4
- export declare const STATUS_CODE_INTERRUPT = 410;
5
- export declare const OTT_WAIT_TIME = 1000;
6
- export declare const MAX_RETRIES = 3;
7
- export declare const MAX_TIMEOUT_MS = 60000;
8
- export declare const GRADUATED_INTERVAL_MS = 5000;
9
- export declare const BLOCK_DURATION = 15000;
10
- export declare const TEST_BLOCK_DURATION = 1000;
11
- export declare const BLOCK_TIME_MS: number;
12
- export declare const XCLAIM_DELAY_MS: number;
13
- export declare const XCLAIM_COUNT = 3;
14
- export declare const XPENDING_COUNT = 10;
15
- export declare const STATUS_CODE_UNACKED = 999;
16
- export declare const STATUS_CODE_UNKNOWN = 500;
17
- export declare const STATUS_MESSAGE_UNKNOWN = "unknown";
18
- export declare const EXPIRE_DURATION = 15;
19
- export declare const BASE_FIDELITY_SECONDS = 15;
20
- export declare const TEST_FIDELITY_SECONDS = 5;
21
- export declare const FIDELITY_SECONDS: number;
22
- export declare const DURABLE_EXPIRE_SECONDS = 1;
23
- export declare const SCOUT_INTERVAL_SECONDS = 60;
1
+ import { LogLevel } from "../types/logger";
2
+ export declare const HMSH_LOGLEVEL: LogLevel;
3
+ export declare const HMSH_CODE_SUCCESS = 200;
4
+ export declare const HMSH_CODE_PENDING = 202;
5
+ export declare const HMSH_CODE_NOTFOUND = 404;
6
+ export declare const HMSH_CODE_INTERRUPT = 410;
7
+ export declare const HMSH_CODE_UNKNOWN = 500;
8
+ export declare const HMSH_CODE_TIMEOUT = 504;
9
+ export declare const HMSH_CODE_UNACKED = 999;
10
+ export declare const HMSH_CODE_DURABLE_SLEEPFOR = 592;
11
+ export declare const HMSH_CODE_DURABLE_INCOMPLETE = 593;
12
+ export declare const HMSH_CODE_DURABLE_WAITFOR = 594;
13
+ export declare const HMSH_CODE_DURABLE_TIMEOUT = 596;
14
+ export declare const HMSH_CODE_DURABLE_MAXED = 597;
15
+ export declare const HMSH_CODE_DURABLE_FATAL = 598;
16
+ export declare const HMSH_CODE_DURABLE_RETRYABLE = 599;
17
+ export declare const HMSH_STATUS_UNKNOWN = "unknown";
18
+ export declare const HMSH_OTT_WAIT_TIME: number;
19
+ export declare const HMSH_EXPIRE_JOB_SECONDS: number;
20
+ export declare const HMSH_MAX_RETRIES: number;
21
+ export declare const HMSH_MAX_TIMEOUT_MS: number;
22
+ export declare const HMSH_GRADUATED_INTERVAL_MS: number;
23
+ export declare const HMSH_BLOCK_TIME_MS: number;
24
+ export declare const HMSH_XCLAIM_DELAY_MS: number;
25
+ export declare const HMSH_XCLAIM_COUNT: number;
26
+ export declare const HMSH_XPENDING_COUNT: number;
27
+ export declare const HMSH_EXPIRE_DURATION: number;
28
+ export declare const HMSH_FIDELITY_SECONDS: number;
29
+ export declare const HMSH_SCOUT_INTERVAL_SECONDS: number;
@@ -1,31 +1,40 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SCOUT_INTERVAL_SECONDS = exports.DURABLE_EXPIRE_SECONDS = exports.FIDELITY_SECONDS = exports.TEST_FIDELITY_SECONDS = exports.BASE_FIDELITY_SECONDS = exports.EXPIRE_DURATION = exports.STATUS_MESSAGE_UNKNOWN = exports.STATUS_CODE_UNKNOWN = exports.STATUS_CODE_UNACKED = exports.XPENDING_COUNT = exports.XCLAIM_COUNT = exports.XCLAIM_DELAY_MS = exports.BLOCK_TIME_MS = exports.TEST_BLOCK_DURATION = exports.BLOCK_DURATION = exports.GRADUATED_INTERVAL_MS = exports.MAX_TIMEOUT_MS = exports.MAX_RETRIES = exports.OTT_WAIT_TIME = exports.STATUS_CODE_INTERRUPT = exports.STATUS_CODE_TIMEOUT = exports.STATUS_CODE_PENDING = exports.STATUS_CODE_SUCCESS = void 0;
4
- // Engine Constants
5
- exports.STATUS_CODE_SUCCESS = 200;
6
- exports.STATUS_CODE_PENDING = 202;
7
- exports.STATUS_CODE_TIMEOUT = 504;
8
- exports.STATUS_CODE_INTERRUPT = 410;
9
- exports.OTT_WAIT_TIME = 1000;
10
- // Stream Constants
11
- exports.MAX_RETRIES = 3; //local retry; 10, 100, 1000ms
12
- exports.MAX_TIMEOUT_MS = 60000;
13
- exports.GRADUATED_INTERVAL_MS = 5000;
14
- exports.BLOCK_DURATION = 15000; //Set to `15` so SIGINT/SIGTERM can interrupt; set to `0` to BLOCK indefinitely
15
- exports.TEST_BLOCK_DURATION = 1000; //Set to `1000` so tests can interrupt quickly
16
- exports.BLOCK_TIME_MS = process.env.NODE_ENV === 'test' ? exports.TEST_BLOCK_DURATION : exports.BLOCK_DURATION;
17
- exports.XCLAIM_DELAY_MS = 1000 * 60; //max time a message can be unacked before it is claimed by another
18
- exports.XCLAIM_COUNT = 3; //max number of times a message can be claimed by another before it is dead-lettered
19
- exports.XPENDING_COUNT = 10;
20
- exports.STATUS_CODE_UNACKED = 999;
21
- exports.STATUS_CODE_UNKNOWN = 500;
22
- exports.STATUS_MESSAGE_UNKNOWN = 'unknown';
23
- // HotMesh Constants
24
- exports.EXPIRE_DURATION = 15; // default expire in seconds; once job state semaphore reaches '0', this is applied to set Redis to expire the job HASH
25
- exports.BASE_FIDELITY_SECONDS = 15; // granularity resolution window size
26
- exports.TEST_FIDELITY_SECONDS = 5;
27
- exports.FIDELITY_SECONDS = process.env.NODE_ENV === 'test' ? exports.TEST_FIDELITY_SECONDS : exports.BASE_FIDELITY_SECONDS;
28
- // DURABLE CONSTANTS
29
- exports.DURABLE_EXPIRE_SECONDS = 1;
30
- // TASK CONSTANTS
31
- exports.SCOUT_INTERVAL_SECONDS = 60;
3
+ exports.HMSH_SCOUT_INTERVAL_SECONDS = exports.HMSH_FIDELITY_SECONDS = exports.HMSH_EXPIRE_DURATION = exports.HMSH_XPENDING_COUNT = exports.HMSH_XCLAIM_COUNT = exports.HMSH_XCLAIM_DELAY_MS = exports.HMSH_BLOCK_TIME_MS = exports.HMSH_GRADUATED_INTERVAL_MS = exports.HMSH_MAX_TIMEOUT_MS = exports.HMSH_MAX_RETRIES = exports.HMSH_EXPIRE_JOB_SECONDS = exports.HMSH_OTT_WAIT_TIME = exports.HMSH_STATUS_UNKNOWN = exports.HMSH_CODE_DURABLE_RETRYABLE = exports.HMSH_CODE_DURABLE_FATAL = exports.HMSH_CODE_DURABLE_MAXED = exports.HMSH_CODE_DURABLE_TIMEOUT = exports.HMSH_CODE_DURABLE_WAITFOR = exports.HMSH_CODE_DURABLE_INCOMPLETE = exports.HMSH_CODE_DURABLE_SLEEPFOR = exports.HMSH_CODE_UNACKED = exports.HMSH_CODE_TIMEOUT = exports.HMSH_CODE_UNKNOWN = exports.HMSH_CODE_INTERRUPT = exports.HMSH_CODE_NOTFOUND = exports.HMSH_CODE_PENDING = exports.HMSH_CODE_SUCCESS = exports.HMSH_LOGLEVEL = void 0;
4
+ // HOTMESH SYSTEM
5
+ exports.HMSH_LOGLEVEL = process.env.HMSH_LOGLEVEL || 'info';
6
+ // STATUS CODES AND MESSAGES
7
+ exports.HMSH_CODE_SUCCESS = 200;
8
+ exports.HMSH_CODE_PENDING = 202;
9
+ exports.HMSH_CODE_NOTFOUND = 404;
10
+ exports.HMSH_CODE_INTERRUPT = 410;
11
+ exports.HMSH_CODE_UNKNOWN = 500;
12
+ exports.HMSH_CODE_TIMEOUT = 504;
13
+ exports.HMSH_CODE_UNACKED = 999;
14
+ exports.HMSH_CODE_DURABLE_SLEEPFOR = 592;
15
+ exports.HMSH_CODE_DURABLE_INCOMPLETE = 593;
16
+ exports.HMSH_CODE_DURABLE_WAITFOR = 594;
17
+ exports.HMSH_CODE_DURABLE_TIMEOUT = 596;
18
+ exports.HMSH_CODE_DURABLE_MAXED = 597;
19
+ exports.HMSH_CODE_DURABLE_FATAL = 598;
20
+ exports.HMSH_CODE_DURABLE_RETRYABLE = 599;
21
+ exports.HMSH_STATUS_UNKNOWN = 'unknown';
22
+ // ENGINE
23
+ exports.HMSH_OTT_WAIT_TIME = parseInt(process.env.HMSH_OTT_WAIT_TIME, 10) || 1000;
24
+ exports.HMSH_EXPIRE_JOB_SECONDS = parseInt(process.env.HMSH_EXPIRE_JOB_SECONDS, 10) || 1;
25
+ // STREAM ROUTER
26
+ exports.HMSH_MAX_RETRIES = parseInt(process.env.HMSH_MAX_RETRIES, 10) || 3;
27
+ exports.HMSH_MAX_TIMEOUT_MS = parseInt(process.env.HMSH_MAX_TIMEOUT_MS, 10) || 60000;
28
+ exports.HMSH_GRADUATED_INTERVAL_MS = parseInt(process.env.HMSH_GRADUATED_INTERVAL_MS, 10) || 5000;
29
+ const BASE_BLOCK_DURATION = 10000; // Modified for clarity
30
+ const TEST_BLOCK_DURATION = 1000; // Modified for clarity
31
+ exports.HMSH_BLOCK_TIME_MS = process.env.HMSH_BLOCK_TIME_MS ? parseInt(process.env.HMSH_BLOCK_TIME_MS, 10) : (process.env.NODE_ENV === 'test' ? TEST_BLOCK_DURATION : BASE_BLOCK_DURATION);
32
+ exports.HMSH_XCLAIM_DELAY_MS = parseInt(process.env.HMSH_XCLAIM_DELAY_MS, 10) || 1000 * 60;
33
+ exports.HMSH_XCLAIM_COUNT = parseInt(process.env.HMSH_XCLAIM_COUNT, 10) || 3;
34
+ exports.HMSH_XPENDING_COUNT = parseInt(process.env.HMSH_XPENDING_COUNT, 10) || 10;
35
+ // TASK WORKER
36
+ exports.HMSH_EXPIRE_DURATION = parseInt(process.env.HMSH_EXPIRE_DURATION, 10) || 1;
37
+ const BASE_FIDELITY_SECONDS = 5;
38
+ const TEST_FIDELITY_SECONDS = 5;
39
+ exports.HMSH_FIDELITY_SECONDS = process.env.HMSH_FIDELITY_SECONDS ? parseInt(process.env.HMSH_FIDELITY_SECONDS, 10) : (process.env.NODE_ENV === 'test' ? TEST_FIDELITY_SECONDS : BASE_FIDELITY_SECONDS);
40
+ exports.HMSH_SCOUT_INTERVAL_SECONDS = parseInt(process.env.HMSH_SCOUT_INTERVAL_SECONDS, 10) || 60;
@@ -2,7 +2,7 @@ import { ActivityDuplex } from "../types/activity";
2
2
  import { CollationFaultType, CollationStage } from "../types/collator";
3
3
  declare class GetStateError extends Error {
4
4
  jobId: string;
5
- code: 404;
5
+ code: number;
6
6
  constructor(jobId: string);
7
7
  }
8
8
  declare class SetStateError extends Error {
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SetStateError = exports.RegisterTimeoutError = exports.MapDataError = exports.InactiveJobError = exports.GetStateError = exports.GenerationalError = exports.ExecActivityError = exports.DuplicateJobError = exports.DurableWaitForSignalError = exports.DurableTimeoutError = exports.DurableSleepForError = exports.DurableSleepError = exports.DurableRetryError = exports.DurableMaxedError = exports.DurableIncompleteSignalError = exports.DurableFatalError = exports.CollationError = void 0;
4
+ const enums_1 = require("./enums");
4
5
  class GetStateError extends Error {
5
6
  constructor(jobId) {
6
7
  super(`${jobId} Not Found`);
8
+ this.code = enums_1.HMSH_CODE_NOTFOUND;
7
9
  this.jobId = jobId;
8
10
  }
9
11
  }
@@ -19,7 +21,7 @@ exports.SetStateError = SetStateError;
19
21
  class DurableIncompleteSignalError extends Error {
20
22
  constructor(message) {
21
23
  super(message);
22
- this.code = 593;
24
+ this.code = enums_1.HMSH_CODE_DURABLE_INCOMPLETE;
23
25
  }
24
26
  }
25
27
  exports.DurableIncompleteSignalError = DurableIncompleteSignalError;
@@ -28,7 +30,7 @@ class DurableWaitForSignalError extends Error {
28
30
  constructor(message, signals) {
29
31
  super(message);
30
32
  this.signals = signals;
31
- this.code = 594;
33
+ this.code = enums_1.HMSH_CODE_DURABLE_WAITFOR;
32
34
  }
33
35
  }
34
36
  exports.DurableWaitForSignalError = DurableWaitForSignalError;
@@ -49,35 +51,35 @@ class DurableSleepForError extends Error {
49
51
  this.duration = duration;
50
52
  this.index = index;
51
53
  this.dimension = dimension;
52
- this.code = 592;
54
+ this.code = enums_1.HMSH_CODE_DURABLE_SLEEPFOR;
53
55
  }
54
56
  }
55
57
  exports.DurableSleepForError = DurableSleepForError;
56
58
  class DurableTimeoutError extends Error {
57
59
  constructor(message) {
58
60
  super(message);
59
- this.code = 596;
61
+ this.code = enums_1.HMSH_CODE_DURABLE_TIMEOUT;
60
62
  }
61
63
  }
62
64
  exports.DurableTimeoutError = DurableTimeoutError;
63
65
  class DurableMaxedError extends Error {
64
66
  constructor(message) {
65
67
  super(message);
66
- this.code = 597;
68
+ this.code = enums_1.HMSH_CODE_DURABLE_MAXED;
67
69
  }
68
70
  }
69
71
  exports.DurableMaxedError = DurableMaxedError;
70
72
  class DurableFatalError extends Error {
71
73
  constructor(message) {
72
74
  super(message);
73
- this.code = 598;
75
+ this.code = enums_1.HMSH_CODE_DURABLE_FATAL;
74
76
  }
75
77
  }
76
78
  exports.DurableFatalError = DurableFatalError;
77
79
  class DurableRetryError extends Error {
78
80
  constructor(message) {
79
81
  super(message);
80
- this.code = 599;
82
+ this.code = enums_1.HMSH_CODE_DURABLE_RETRYABLE;
81
83
  }
82
84
  }
83
85
  exports.DurableRetryError = DurableRetryError;
@@ -1,3 +1,4 @@
1
+ import { KeyStoreParams, KeyType } from '../types/hotmesh';
1
2
  /**
2
3
  * Keys
3
4
  *
@@ -26,40 +27,6 @@
26
27
  * hmsh:<appid>:sym:vals: -> {hash} list of symbols for job values across all app versions
27
28
  */
28
29
  declare const HMNS = "hmsh";
29
- declare enum KeyType {
30
- APP = "APP",
31
- ENGINE_ID = "ENGINE",
32
- HOOKS = "HOOKS",
33
- JOB_DEPENDENTS = "JOB_DEPENDENTS",
34
- JOB_STATE = "JOB_STATE",
35
- JOB_STATS_GENERAL = "JOB_STATS_GENERAL",
36
- JOB_STATS_MEDIAN = "JOB_STATS_MEDIAN",
37
- JOB_STATS_INDEX = "JOB_STATS_INDEX",
38
- HOTMESH = "HOTMESH",
39
- QUORUM = "QUORUM",
40
- SCHEMAS = "SCHEMAS",
41
- SIGNALS = "SIGNALS",
42
- STREAMS = "STREAMS",
43
- SUBSCRIPTIONS = "SUBSCRIPTIONS",
44
- SUBSCRIPTION_PATTERNS = "SUBSCRIPTION_PATTERNS",
45
- SYMKEYS = "SYMKEYS",
46
- SYMVALS = "SYMVALS",
47
- TIME_RANGE = "TIME_RANGE",
48
- WORK_ITEMS = "WORK_ITEMS"
49
- }
50
- type KeyStoreParams = {
51
- appId?: string;
52
- engineId?: string;
53
- appVersion?: string;
54
- jobId?: string;
55
- activityId?: string;
56
- jobKey?: string;
57
- dateTime?: string;
58
- facet?: string;
59
- topic?: string;
60
- timeValue?: number;
61
- scoutType?: 'signal' | 'time';
62
- };
63
30
  declare class KeyService {
64
31
  /**
65
32
  * returns a key that can be used to access a value in the key/value store
@@ -1,4 +1,8 @@
1
1
  "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HMNS = exports.KeyType = exports.KeyService = void 0;
4
+ const hotmesh_1 = require("../types/hotmesh");
5
+ Object.defineProperty(exports, "KeyType", { enumerable: true, get: function () { return hotmesh_1.KeyType; } });
2
6
  /**
3
7
  * Keys
4
8
  *
@@ -26,35 +30,8 @@
26
30
  * hmsh:<appid>:sym:keys:<activityid|$subscribes> -> {hash} list of symbols based upon schema enums (initially) and adaptively optimized (later) during runtime; if '$subscribes' is used as the activityid, it is a top-level `job` symbol set (for job keys)
27
31
  * hmsh:<appid>:sym:vals: -> {hash} list of symbols for job values across all app versions
28
32
  */
29
- Object.defineProperty(exports, "__esModule", { value: true });
30
- exports.HMNS = exports.KeyType = exports.KeyService = void 0;
31
- //default namespace for hotmesh
32
- const HMNS = "hmsh";
33
+ const HMNS = "hmsh"; //default
33
34
  exports.HMNS = HMNS;
34
- //these are the entity types that are stored in the key/value store
35
- var KeyType;
36
- (function (KeyType) {
37
- KeyType["APP"] = "APP";
38
- KeyType["ENGINE_ID"] = "ENGINE";
39
- KeyType["HOOKS"] = "HOOKS";
40
- KeyType["JOB_DEPENDENTS"] = "JOB_DEPENDENTS";
41
- KeyType["JOB_STATE"] = "JOB_STATE";
42
- KeyType["JOB_STATS_GENERAL"] = "JOB_STATS_GENERAL";
43
- KeyType["JOB_STATS_MEDIAN"] = "JOB_STATS_MEDIAN";
44
- KeyType["JOB_STATS_INDEX"] = "JOB_STATS_INDEX";
45
- KeyType["HOTMESH"] = "HOTMESH";
46
- KeyType["QUORUM"] = "QUORUM";
47
- KeyType["SCHEMAS"] = "SCHEMAS";
48
- KeyType["SIGNALS"] = "SIGNALS";
49
- KeyType["STREAMS"] = "STREAMS";
50
- KeyType["SUBSCRIPTIONS"] = "SUBSCRIPTIONS";
51
- KeyType["SUBSCRIPTION_PATTERNS"] = "SUBSCRIPTION_PATTERNS";
52
- KeyType["SYMKEYS"] = "SYMKEYS";
53
- KeyType["SYMVALS"] = "SYMVALS";
54
- KeyType["TIME_RANGE"] = "TIME_RANGE";
55
- KeyType["WORK_ITEMS"] = "WORK_ITEMS";
56
- })(KeyType || (KeyType = {}));
57
- exports.KeyType = KeyType;
58
35
  class KeyService {
59
36
  /**
60
37
  * returns a key that can be used to access a value in the key/value store
@@ -69,47 +46,47 @@ class KeyService {
69
46
  */
70
47
  static mintKey(namespace, keyType, params) {
71
48
  switch (keyType) {
72
- case KeyType.HOTMESH:
49
+ case hotmesh_1.KeyType.HOTMESH:
73
50
  return namespace;
74
- case KeyType.ENGINE_ID:
51
+ case hotmesh_1.KeyType.ENGINE_ID:
75
52
  return `${namespace}:${params.appId}:e:${params.engineId}`;
76
- case KeyType.WORK_ITEMS:
53
+ case hotmesh_1.KeyType.WORK_ITEMS:
77
54
  return `${namespace}:${params.appId}:w:${params.scoutType || ''}`;
78
- case KeyType.TIME_RANGE:
55
+ case hotmesh_1.KeyType.TIME_RANGE:
79
56
  return `${namespace}:${params.appId}:t:${params.timeValue || ''}`;
80
- case KeyType.APP:
57
+ case hotmesh_1.KeyType.APP:
81
58
  return `${namespace}:a:${params.appId || ''}`;
82
- case KeyType.QUORUM:
59
+ case hotmesh_1.KeyType.QUORUM:
83
60
  return `${namespace}:${params.appId}:q:${params.engineId || ''}`;
84
- case KeyType.JOB_STATE:
61
+ case hotmesh_1.KeyType.JOB_STATE:
85
62
  return `${namespace}:${params.appId}:j:${params.jobId}`;
86
- case KeyType.JOB_DEPENDENTS:
63
+ case hotmesh_1.KeyType.JOB_DEPENDENTS:
87
64
  return `${namespace}:${params.appId}:d:${params.jobId}`;
88
- case KeyType.JOB_STATS_GENERAL:
65
+ case hotmesh_1.KeyType.JOB_STATS_GENERAL:
89
66
  return `${namespace}:${params.appId}:s:${params.jobKey}:${params.dateTime}`;
90
- case KeyType.JOB_STATS_MEDIAN:
67
+ case hotmesh_1.KeyType.JOB_STATS_MEDIAN:
91
68
  return `${namespace}:${params.appId}:s:${params.jobKey}:${params.dateTime}:${params.facet}`;
92
- case KeyType.JOB_STATS_INDEX:
69
+ case hotmesh_1.KeyType.JOB_STATS_INDEX:
93
70
  return `${namespace}:${params.appId}:s:${params.jobKey}:${params.dateTime}:${params.facet}`;
94
- case KeyType.SCHEMAS:
71
+ case hotmesh_1.KeyType.SCHEMAS:
95
72
  return `${namespace}:${params.appId}:v:${params.appVersion}:schemas`;
96
- case KeyType.SUBSCRIPTIONS:
73
+ case hotmesh_1.KeyType.SUBSCRIPTIONS:
97
74
  return `${namespace}:${params.appId}:v:${params.appVersion}:subscriptions`;
98
- case KeyType.SUBSCRIPTION_PATTERNS:
75
+ case hotmesh_1.KeyType.SUBSCRIPTION_PATTERNS:
99
76
  return `${namespace}:${params.appId}:v:${params.appVersion}:transitions`;
100
- case KeyType.HOOKS:
77
+ case hotmesh_1.KeyType.HOOKS:
101
78
  //`hooks` provide the pattern to resolve a value
102
79
  return `${namespace}:${params.appId}:hooks`;
103
- case KeyType.SIGNALS:
80
+ case hotmesh_1.KeyType.SIGNALS:
104
81
  //`signals` provide the registry of resolved values that link back to paused jobs
105
82
  return `${namespace}:${params.appId}:signals`;
106
- case KeyType.SYMKEYS:
83
+ case hotmesh_1.KeyType.SYMKEYS:
107
84
  //`symbol keys` provide the registry of replacement values for job keys
108
85
  return `${namespace}:${params.appId}:sym:keys:${params.activityId || ''}`;
109
- case KeyType.SYMVALS:
86
+ case hotmesh_1.KeyType.SYMVALS:
110
87
  //`symbol vals` provide the registry of replacement values for job vals
111
88
  return `${namespace}:${params.appId}:sym:vals:`;
112
- case KeyType.STREAMS:
89
+ case hotmesh_1.KeyType.STREAMS:
113
90
  return `${namespace}:${params.appId || ''}:x:${params.topic || ''}`;
114
91
  default:
115
92
  throw new Error("Invalid key type.");
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.38",
3
+ "version": "0.0.39",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -346,7 +346,7 @@ class Activity {
346
346
  return context;
347
347
  }
348
348
  initPolicies(context) {
349
- const expire = pipe_1.Pipe.resolve(this.config.expire ?? enums_1.EXPIRE_DURATION, context);
349
+ const expire = pipe_1.Pipe.resolve(this.config.expire ?? enums_1.HMSH_EXPIRE_DURATION, context);
350
350
  context.metadata.expire = expire;
351
351
  }
352
352
  bindActivityData(type) {
@@ -103,17 +103,12 @@ class Hook extends activity_1.Activity {
103
103
  }
104
104
  async registerHook(multi) {
105
105
  if (this.config.hook?.topic) {
106
- const taskService = new task_1.TaskService(this.store, this.logger);
107
- return await taskService.registerWebHook(this.config.hook.topic, this.context, this.resolveDad(), multi);
106
+ return await this.engine.taskService.registerWebHook(this.config.hook.topic, this.context, this.resolveDad(), multi);
108
107
  }
109
108
  else if (this.config.sleep) {
110
- const durationInSeconds = pipe_1.Pipe.resolve(this.config.sleep, this.context);
111
- const jobId = this.context.metadata.jid;
112
- const gId = this.context.metadata.gid;
113
- const activityId = this.metadata.aid;
114
- const dId = this.metadata.dad;
115
- await this.engine.taskService.registerTimeHook(jobId, gId, `${activityId}${dId || ''}`, 'sleep', durationInSeconds);
116
- return jobId;
109
+ const duration = pipe_1.Pipe.resolve(this.config.sleep, this.context);
110
+ await this.engine.taskService.registerTimeHook(this.context.metadata.jid, this.context.metadata.gid, `${this.metadata.aid}${this.metadata.dad || ''}`, 'sleep', duration);
111
+ return this.context.metadata.jid;
117
112
  }
118
113
  }
119
114
  //******** SIGNAL RE-ENTRY POINT ********//
@@ -18,9 +18,10 @@ declare class Trigger extends Activity {
18
18
  resolveJobKey(context: Partial<JobState>): string;
19
19
  setStateNX(): Promise<void>;
20
20
  /**
21
- * Registers this job as a dependent of the parent job
21
+ * Registers this job as a dependent of the parent job; when the
22
+ * parent job is interrupted, this job will be interrupted
22
23
  */
23
- setDependency(multi?: RedisMulti): Promise<void>;
24
+ registerJobDependency(multi?: RedisMulti): Promise<void>;
24
25
  setStats(multi?: RedisMulti): Promise<void>;
25
26
  }
26
27
  export { Trigger };
@@ -29,7 +29,7 @@ class Trigger extends activity_1.Activity {
29
29
  const multi = this.store.getMulti();
30
30
  await this.setState(multi);
31
31
  await this.setStats(multi);
32
- await this.setDependency(multi);
32
+ await this.registerJobDependency(multi);
33
33
  await multi.exec();
34
34
  telemetry.mapActivityAttributes();
35
35
  const jobStatus = Number(this.context.metadata.js);
@@ -152,13 +152,17 @@ class Trigger extends activity_1.Activity {
152
152
  }
153
153
  }
154
154
  /**
155
- * Registers this job as a dependent of the parent job
155
+ * Registers this job as a dependent of the parent job; when the
156
+ * parent job is interrupted, this job will be interrupted
156
157
  */
157
- async setDependency(multi) {
158
- const depKey = this.config.stats?.parent;
159
- const resolvedDepKey = depKey ? pipe_1.Pipe.resolve(depKey, this.context) : '';
158
+ async registerJobDependency(multi) {
159
+ const depKey = this.config.stats?.parent ?? this.context.metadata.pj;
160
+ let resolvedDepKey = depKey ? pipe_1.Pipe.resolve(depKey, this.context) : '';
161
+ if (!resolvedDepKey) {
162
+ resolvedDepKey = this.context.metadata.pj;
163
+ }
160
164
  if (resolvedDepKey) {
161
- await this.store.setDependency(resolvedDepKey, this.context.metadata.tpc, this.context.metadata.jid, this.context.metadata.gid, multi);
165
+ await this.store.registerJobDependency(resolvedDepKey, this.context.metadata.tpc, this.context.metadata.jid, this.context.metadata.gid, multi);
162
166
  }
163
167
  }
164
168
  async setStats(multi) {
@@ -3,10 +3,18 @@ import { HotMeshService as HotMesh } from '../hotmesh';
3
3
  import { ClientConfig, Connection, HookOptions, WorkflowOptions, WorkflowSearchOptions } from '../../types/durable';
4
4
  export declare class ClientService {
5
5
  connection: Connection;
6
+ topics: string[];
6
7
  options: WorkflowOptions;
7
8
  static instances: Map<string, HotMesh | Promise<HotMesh>>;
8
9
  constructor(config: ClientConfig);
9
- getHotMeshClient: (worflowTopic: string, namespace?: string) => Promise<HotMesh>;
10
+ getHotMeshClient: (workflowTopic: string, namespace?: string) => Promise<HotMesh>;
11
+ /**
12
+ * Creates a stream (Redis `XGROUP.CREATE`) where events can be published (XADD).
13
+ * It is possible that the worker that will read from this stream channel
14
+ * has not yet been initialized, so this call ensures that the channel
15
+ * exists and is ready to serve as a container for events.
16
+ */
17
+ createStream: (hotMeshClient: HotMesh, workflowTopic: string, namespace?: string) => Promise<void>;
10
18
  /**
11
19
  * For those deployments with a redis stack backend (with the FT module),
12
20
  * this method will configure the search index for the workflow.
@@ -10,13 +10,22 @@ const types_1 = require("../../types");
10
10
  const enums_1 = require("../../modules/enums");
11
11
  class ClientService {
12
12
  constructor(config) {
13
- this.getHotMeshClient = async (worflowTopic, namespace) => {
14
- //NOTE: every unique topic inits a new engine
15
- if (ClientService.instances.has(worflowTopic)) {
16
- return await ClientService.instances.get(worflowTopic);
13
+ this.topics = [];
14
+ this.getHotMeshClient = async (workflowTopic, namespace) => {
15
+ //use the cached instance
16
+ const instanceId = 'SINGLETON';
17
+ if (ClientService.instances.has(instanceId)) {
18
+ const hotMeshClient = await ClientService.instances.get(instanceId);
19
+ if (!this.topics.includes(workflowTopic)) {
20
+ this.topics.push(workflowTopic);
21
+ await this.createStream(hotMeshClient, workflowTopic, namespace);
22
+ }
23
+ return hotMeshClient;
17
24
  }
25
+ //create and cache an instance
18
26
  const hotMeshClient = hotmesh_1.HotMeshService.init({
19
27
  appId: namespace ?? factory_1.APP_ID,
28
+ logLevel: enums_1.HMSH_LOGLEVEL,
20
29
  engine: {
21
30
  redis: {
22
31
  class: this.connection.class,
@@ -24,10 +33,20 @@ class ClientService {
24
33
  }
25
34
  }
26
35
  });
27
- ClientService.instances.set(worflowTopic, hotMeshClient);
28
- //since the YAML topic is dynamic, it MUST be manually created before use
29
- const store = (await hotMeshClient).engine.store;
30
- const params = { appId: namespace ?? factory_1.APP_ID, topic: worflowTopic };
36
+ ClientService.instances.set(instanceId, hotMeshClient);
37
+ await this.createStream(await hotMeshClient, workflowTopic, namespace);
38
+ await this.activateWorkflow(await hotMeshClient, namespace ?? factory_1.APP_ID);
39
+ return hotMeshClient;
40
+ };
41
+ /**
42
+ * Creates a stream (Redis `XGROUP.CREATE`) where events can be published (XADD).
43
+ * It is possible that the worker that will read from this stream channel
44
+ * has not yet been initialized, so this call ensures that the channel
45
+ * exists and is ready to serve as a container for events.
46
+ */
47
+ this.createStream = async (hotMeshClient, workflowTopic, namespace) => {
48
+ const store = hotMeshClient.engine.store;
49
+ const params = { appId: namespace ?? factory_1.APP_ID, topic: workflowTopic };
31
50
  const streamKey = store.mintKey(key_1.KeyType.STREAMS, params);
32
51
  try {
33
52
  await store.xgroup('CREATE', streamKey, 'WORKER', '$', 'MKSTREAM');
@@ -35,8 +54,6 @@ class ClientService {
35
54
  catch (err) {
36
55
  //ignore if already exists
37
56
  }
38
- await this.activateWorkflow(await hotMeshClient, namespace ?? factory_1.APP_ID);
39
- return hotMeshClient;
40
57
  };
41
58
  /**
42
59
  * For those deployments with a redis stack backend (with the FT module),
@@ -89,7 +106,7 @@ class ClientService {
89
106
  const payload = {
90
107
  arguments: [...options.args],
91
108
  originJobId: options.originJobId,
92
- expire: options.expire ?? enums_1.DURABLE_EXPIRE_SECONDS,
109
+ expire: options.expire ?? enums_1.HMSH_EXPIRE_JOB_SECONDS,
93
110
  parentWorkflowId: options.parentWorkflowId,
94
111
  workflowId: options.workflowId || hotmesh_1.HotMeshService.guid(),
95
112
  workflowTopic: workflowTopic,
@@ -133,9 +150,8 @@ class ClientService {
133
150
  if (options.search?.data) {
134
151
  const searchSessionId = `-search-${hotmesh_1.HotMeshService.guid()}-0`;
135
152
  const search = new search_1.Search(options.workflowId, hotMeshClient, searchSessionId);
136
- for (const [key, value] of Object.entries(options.search.data)) {
137
- search.set(key, value);
138
- }
153
+ const entries = Object.entries(options.search.data).flat();
154
+ await search.set(...entries);
139
155
  }
140
156
  return msgId;
141
157
  },
@@ -91,7 +91,7 @@ class WorkflowHandleService {
91
91
  const state = await this.hotMesh.getState(`${this.hotMesh.appId}.execute`, this.workflowId);
92
92
  if (state.metadata.err) {
93
93
  const error = JSON.parse(state.metadata.err);
94
- if (error.code === enums_1.STATUS_CODE_INTERRUPT || !state.data) {
94
+ if (error.code === enums_1.HMSH_CODE_INTERRUPT || !state.data) {
95
95
  return reject({ ...error, job_id: this.workflowId });
96
96
  }
97
97
  }
@@ -107,7 +107,7 @@ class WorkflowHandleService {
107
107
  this.hotMesh.sub(topic, async (topic, state) => {
108
108
  if (state.metadata.err) {
109
109
  const error = JSON.parse(state.metadata.err);
110
- if (error.code === enums_1.STATUS_CODE_INTERRUPT || !state.data) {
110
+ if (error.code === enums_1.HMSH_CODE_INTERRUPT || !state.data) {
111
111
  return await complete(null, state.metadata.err);
112
112
  }
113
113
  }