@hotmeshio/hotmesh 0.1.2 → 0.1.4

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 (48) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  3. package/README.md +17 -14
  4. package/build/modules/enums.d.ts +1 -0
  5. package/build/modules/enums.js +2 -1
  6. package/build/modules/key.d.ts +1 -2
  7. package/build/modules/key.js +3 -4
  8. package/build/modules/utils.js +10 -11
  9. package/build/package.json +3 -2
  10. package/build/services/activities/activity.js +1 -1
  11. package/build/services/activities/await.js +1 -1
  12. package/build/services/activities/cycle.js +1 -1
  13. package/build/services/activities/hook.js +1 -1
  14. package/build/services/activities/interrupt.js +1 -1
  15. package/build/services/activities/signal.js +1 -1
  16. package/build/services/activities/trigger.d.ts +2 -1
  17. package/build/services/activities/trigger.js +21 -9
  18. package/build/services/activities/worker.js +1 -1
  19. package/build/services/compiler/deployer.js +1 -1
  20. package/build/services/durable/client.js +5 -1
  21. package/build/services/durable/worker.js +1 -1
  22. package/build/services/engine/index.d.ts +1 -1
  23. package/build/services/engine/index.js +13 -6
  24. package/build/services/hotmesh/index.js +13 -1
  25. package/build/services/router/index.d.ts +2 -0
  26. package/build/services/router/index.js +37 -15
  27. package/build/services/store/clients/ioredis.js +28 -8
  28. package/build/services/store/clients/redis.js +27 -10
  29. package/build/services/store/index.d.ts +6 -2
  30. package/build/services/store/index.js +37 -5
  31. package/build/services/stream/clients/ioredis.js +1 -1
  32. package/build/services/stream/clients/redis.js +23 -23
  33. package/build/services/worker/index.d.ts +1 -1
  34. package/build/services/worker/index.js +4 -2
  35. package/build/types/durable.d.ts +11 -0
  36. package/build/types/hotmesh.d.ts +1 -1
  37. package/build/types/hotmesh.js +1 -1
  38. package/build/types/job.d.ts +11 -0
  39. package/build/types/quorum.d.ts +2 -4
  40. package/build/types/redis.d.ts +3 -0
  41. package/build/types/stream.d.ts +2 -0
  42. package/package.json +3 -2
  43. package/types/durable.ts +14 -1
  44. package/types/hotmesh.ts +1 -1
  45. package/types/job.ts +12 -0
  46. package/types/quorum.ts +2 -4
  47. package/types/redis.ts +3 -0
  48. package/types/stream.ts +2 -0
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Describe the bug**
11
+ A clear and concise description of what the bug is.
12
+
13
+ **To Reproduce**
14
+ Steps to reproduce the behavior:
15
+ 1. Go to '...'
16
+ 2. Click on '....'
17
+ 3. Scroll down to '....'
18
+ 4. See error
19
+
20
+ **Expected behavior**
21
+ A clear and concise description of what you expected to happen.
22
+
23
+ **Screenshots**
24
+ If applicable, add screenshots to help explain your problem.
25
+
26
+ **Desktop (please complete the following information):**
27
+ - OS: [e.g. iOS]
28
+ - Browser [e.g. chrome, safari]
29
+ - Version [e.g. 22]
30
+
31
+ **Smartphone (please complete the following information):**
32
+ - Device: [e.g. iPhone6]
33
+ - OS: [e.g. iOS8.1]
34
+ - Browser [e.g. stock browser, safari]
35
+ - Version [e.g. 22]
36
+
37
+ **Additional context**
38
+ Add any other context about the problem here.
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an idea for this project
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Is your feature request related to a problem? Please describe.**
11
+ A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12
+
13
+ **Describe the solution you'd like**
14
+ A clear and concise description of what you want to happen.
15
+
16
+ **Describe alternatives you've considered**
17
+ A clear and concise description of any alternative solutions or features you've considered.
18
+
19
+ **Additional context**
20
+ Add any other context or screenshots about the feature request here.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  HotMesh transforms Redis into an Orchestration Engine.
5
5
 
6
- *Write functions in your own preferred style, and let Redis govern their execution, reliably and durably.*
6
+ *Write functions in your own style, and let Redis govern their execution, reliably and durably.*
7
7
 
8
8
  ## Install
9
9
  [![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
  ```
14
14
 
15
15
  ## Understanding HotMesh
16
- HotMesh inverts the relationship to Redis: those functions that once used Redis as a cache, are instead *cached and governed* by Redis. Consider the following. It's a typical microservices network, with a tangled mess of services and functions. There's important business logic in there (functions *A*, *B* and *C* are critical!), but they're hard to find and access.
16
+ HotMesh inverts the relationship to Redis: those functions that once used Redis as a cache, are instead *cached and governed* by Redis. Consider the following. It's a typical microservices network, with a tangled mess of services and functions. There's important business logic in there (functions *A*, *B* and *C* are critical), but it's hard to find and access.
17
17
 
18
18
  <img src="./docs/img/operational_data_layer.png" alt="A Tangled Microservices Network with 3 valuable functions buried within" style="max-width:100%;width:600px;">
19
19
 
@@ -185,7 +185,7 @@ Externalizing state fundamentally changes the execution profile for your functio
185
185
  const value = await search.mult('name', 12);
186
186
  ```
187
187
 
188
- Refer to the [hotmeshio/samples-typescript](https://github.com/hotmeshio/samples-typescript) repo for usage examples.
188
+ Refer to the [hotmeshio/samples-javascript](https://github.com/hotmeshio/samples-javascript) repo for usage examples.
189
189
 
190
190
  ### Design | Advanced
191
191
  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.
@@ -295,28 +295,31 @@ const hotMesh = await HotMesh.init({
295
295
  ```
296
296
 
297
297
  ### Observability
298
- Workflows and activities are run according to the rules you define, offering [Graph-Oriented](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/system_lifecycle.md#telemetry) telemetry insights into your legacy function executions.
298
+ Workflows and activities are run according to the rules you define, offering [Graph-Oriented](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/system_lifecycle.md#telemetry) telemetry insights into your legacy function executions.
299
299
 
300
300
  ## FAQ
301
- Refer to the [FAQ](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/faq.md) for terminology, definitions, and an exploration of how HotMesh facilitates orchestration use cases.
301
+ Refer to the [FAQ](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/faq.md) for terminology, definitions, and an exploration of how HotMesh facilitates orchestration use cases.
302
302
 
303
303
  ## Quick Start
304
- Refer to the [Quick Start](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/quickstart.md) for sample YAML workflows you can copy, paste, and modify to get started.
304
+ Refer to the [Quick Start](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/quickstart.md) for sample YAML workflows you can copy, paste, and modify to get started.
305
305
 
306
306
  ## Developer Guide
307
- For more details on the complete development process, including information about schemas, APIs, and deployment, consult the [Developer Guide](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/developer_guide.md).
307
+ For more details on the complete development process, including information about schemas, APIs, and deployment, consult the [Developer Guide](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/developer_guide.md).
308
308
 
309
309
  ## Model Driven Development
310
- [Model Driven Development](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/model_driven_development.md) is an established strategy for managing process-oriented tasks. Check out this guide to understand its foundational principles.
310
+ [Model Driven Development](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/model_driven_development.md) is an established strategy for managing process-oriented tasks. Check out this guide to understand its foundational principles.
311
311
 
312
312
  ## Data Mapping
313
- Exchanging data between activities is central to HotMesh. For detailed information on supported functions and the functional mapping syntax (@pipes), see the [Data Mapping Overview](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/data_mapping.md).
313
+ Exchanging data between activities is central to HotMesh. For detailed information on supported functions and the functional mapping syntax (@pipes), see the [Data Mapping Overview](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/data_mapping.md).
314
314
 
315
315
  ## Composition
316
- While the simplest graphs are linear, detailing a consistent sequence of non-cyclical activities, graphs can be layered to represent intricate business scenarios. Some can even be designed to accommodate long-lasting workflows that span months. For more details, check out the [Composable Workflow Guide](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/composable_workflow.md).
317
-
318
- ## Distributed Orchestration
319
- HotMesh is a distributed orchestration engine. Refer to the [Distributed Orchestration Guide](https://github.com/hotmeshio/sdk-typescript/tree/main/docs/distributed_orchestration.md) for a detailed breakdown of the approach.
316
+ While the simplest graphs are linear, detailing a consistent sequence of non-cyclical activities, graphs can be layered to represent intricate business scenarios. Some can even be designed to accommodate long-lasting workflows that span months. For more details, check out the [Composable Workflow Guide](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/composable_workflow.md).
320
317
 
321
318
  ## System Lifecycle
322
- 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).
319
+ Gain insight into HotMesh's monitoring, exception handling, and alarm configurations via the [System Lifecycle Guide](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/system_lifecycle.md).
320
+
321
+ ## Distributed Orchestration | System Overview
322
+ HotMesh is a distributed orchestration engine. Refer to the [Distributed Orchestration Guide](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/distributed_orchestration.md) for a high-level overview of the approach.
323
+
324
+ ## Distributed Orchestration | System Design
325
+ HotMesh is more than Redis and TypeScript. The theory that underlies the architecture is applicable to any number of data storage and streaming backends: [A Message-Oriented Approach to Decentralized Process Orchestration](https://zenodo.org/records/12168558).
@@ -23,6 +23,7 @@ export declare const HMSH_QUORUM_DELAY_MS = 250;
23
23
  export declare const HMSH_ACTIVATION_MAX_RETRY = 3;
24
24
  export declare const HMSH_OTT_WAIT_TIME: number;
25
25
  export declare const HMSH_EXPIRE_JOB_SECONDS: number;
26
+ export declare const MAX_DELAY = 2147483647;
26
27
  export declare const HMSH_MAX_RETRIES: number;
27
28
  export declare const HMSH_MAX_TIMEOUT_MS: number;
28
29
  export declare const HMSH_GRADUATED_INTERVAL_MS: number;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HMSH_GUID_SIZE = 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_DURABLE_EXP_BACKOFF = exports.HMSH_DURABLE_MAX_INTERVAL = exports.HMSH_DURABLE_MAX_ATTEMPTS = 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_ACTIVATION_MAX_RETRY = exports.HMSH_QUORUM_DELAY_MS = exports.HMSH_QUORUM_ROLLCALL_CYCLES = 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_WAIT = exports.HMSH_CODE_DURABLE_PROXY = exports.HMSH_CODE_DURABLE_CHILD = exports.HMSH_CODE_DURABLE_ALL = exports.HMSH_CODE_DURABLE_SLEEP = 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_IS_CLUSTER = exports.HMSH_LOGLEVEL = void 0;
3
+ exports.HMSH_GUID_SIZE = 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_DURABLE_EXP_BACKOFF = exports.HMSH_DURABLE_MAX_INTERVAL = exports.HMSH_DURABLE_MAX_ATTEMPTS = exports.HMSH_GRADUATED_INTERVAL_MS = exports.HMSH_MAX_TIMEOUT_MS = exports.HMSH_MAX_RETRIES = exports.MAX_DELAY = exports.HMSH_EXPIRE_JOB_SECONDS = exports.HMSH_OTT_WAIT_TIME = exports.HMSH_ACTIVATION_MAX_RETRY = exports.HMSH_QUORUM_DELAY_MS = exports.HMSH_QUORUM_ROLLCALL_CYCLES = 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_WAIT = exports.HMSH_CODE_DURABLE_PROXY = exports.HMSH_CODE_DURABLE_CHILD = exports.HMSH_CODE_DURABLE_ALL = exports.HMSH_CODE_DURABLE_SLEEP = 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_IS_CLUSTER = exports.HMSH_LOGLEVEL = void 0;
4
4
  // HOTMESH SYSTEM
5
5
  exports.HMSH_LOGLEVEL = process.env.HMSH_LOGLEVEL || 'info';
6
6
  exports.HMSH_IS_CLUSTER = process.env.HMSH_IS_CLUSTER === 'true';
@@ -32,6 +32,7 @@ exports.HMSH_ACTIVATION_MAX_RETRY = 3;
32
32
  exports.HMSH_OTT_WAIT_TIME = parseInt(process.env.HMSH_OTT_WAIT_TIME, 10) || 1000;
33
33
  exports.HMSH_EXPIRE_JOB_SECONDS = parseInt(process.env.HMSH_EXPIRE_JOB_SECONDS, 10) || 1;
34
34
  // STREAM ROUTER
35
+ exports.MAX_DELAY = 2147483647; // Maximum allowed delay in milliseconds for setTimeout
35
36
  exports.HMSH_MAX_RETRIES = parseInt(process.env.HMSH_MAX_RETRIES, 10) || 3;
36
37
  exports.HMSH_MAX_TIMEOUT_MS = parseInt(process.env.HMSH_MAX_TIMEOUT_MS, 10) || 60000;
37
38
  exports.HMSH_GRADUATED_INTERVAL_MS = parseInt(process.env.HMSH_GRADUATED_INTERVAL_MS, 10) || 5000;
@@ -4,14 +4,13 @@ import { KeyStoreParams, KeyType } from '../types/hotmesh';
4
4
  *
5
5
  * hmsh -> {hash} hotmesh config {version: "0.0.1", namespace: "hmsh"}
6
6
  * hmsh:a:<appid> -> {hash} app profile { "id": "appid", "version": "2", "versions/1": "GMT", "versions/2": "GMT"}
7
- * hmsh:<appid>:e:<engineId> -> {string} setnx to ensure only one engine of given id
7
+ * hmsh:<appid>:r: -> {hash} throttle rates {':': '23', 'topic.thing': '555'} => {':i': 'all', 'topic.thing': '555seconds'}
8
8
  * hmsh:<appid>:w: -> {zset} work items/tasks an engine must do like garbage collect or hook a set of matching records (hookAll)
9
9
  * hmsh:<appid>:t: -> {zset} an ordered set of list (work lists) ids
10
10
  * hmsh:<appid>:t:<timeValue?> -> {list} a worklist of `jobId+activityId` items that should be awakened
11
11
  * hmsh:<appid>:q: -> {hash} quorum-wide messages
12
12
  * hmsh:<appid>:q:<ngnid> -> {hash} engine-targeted messages (targeted quorum-oriented message)
13
13
  * hmsh:<appid>:j:<jobid> -> {hash} job data
14
- * hmsh:<appid>:j:<jobid>:<activityid> -> {hash} job activity data (a1)
15
14
  * hmsh:<appid>:s:<jobkey>:<dateTime> -> {hash} job stats (general)
16
15
  * hmsh:<appid>:s:<jobkey>:<dateTime>:mdn:<field/path>:<fieldvalue> -> {zset} job stats (median)
17
16
  * hmsh:<appid>:s:<jobkey>:<dateTime>:index:<field/path>:<fieldvalue> -> {list} job stats (index of jobid[])
@@ -8,14 +8,13 @@ Object.defineProperty(exports, "KeyType", { enumerable: true, get: function () {
8
8
  *
9
9
  * hmsh -> {hash} hotmesh config {version: "0.0.1", namespace: "hmsh"}
10
10
  * hmsh:a:<appid> -> {hash} app profile { "id": "appid", "version": "2", "versions/1": "GMT", "versions/2": "GMT"}
11
- * hmsh:<appid>:e:<engineId> -> {string} setnx to ensure only one engine of given id
11
+ * hmsh:<appid>:r: -> {hash} throttle rates {':': '23', 'topic.thing': '555'} => {':i': 'all', 'topic.thing': '555seconds'}
12
12
  * hmsh:<appid>:w: -> {zset} work items/tasks an engine must do like garbage collect or hook a set of matching records (hookAll)
13
13
  * hmsh:<appid>:t: -> {zset} an ordered set of list (work lists) ids
14
14
  * hmsh:<appid>:t:<timeValue?> -> {list} a worklist of `jobId+activityId` items that should be awakened
15
15
  * hmsh:<appid>:q: -> {hash} quorum-wide messages
16
16
  * hmsh:<appid>:q:<ngnid> -> {hash} engine-targeted messages (targeted quorum-oriented message)
17
17
  * hmsh:<appid>:j:<jobid> -> {hash} job data
18
- * hmsh:<appid>:j:<jobid>:<activityid> -> {hash} job activity data (a1)
19
18
  * hmsh:<appid>:s:<jobkey>:<dateTime> -> {hash} job stats (general)
20
19
  * hmsh:<appid>:s:<jobkey>:<dateTime>:mdn:<field/path>:<fieldvalue> -> {zset} job stats (median)
21
20
  * hmsh:<appid>:s:<jobkey>:<dateTime>:index:<field/path>:<fieldvalue> -> {list} job stats (index of jobid[])
@@ -56,8 +55,8 @@ class KeyService {
56
55
  switch (keyType) {
57
56
  case hotmesh_1.KeyType.HOTMESH:
58
57
  return namespace;
59
- case hotmesh_1.KeyType.ENGINE_ID:
60
- return `${namespace}:${params.appId}:e:${params.engineId}`;
58
+ case hotmesh_1.KeyType.THROTTLE_RATE:
59
+ return `${namespace}:${params.appId}:r:`;
61
60
  case hotmesh_1.KeyType.WORK_ITEMS:
62
61
  return `${namespace}:${params.appId}:w:${params.scoutType || ''}`;
63
62
  case hotmesh_1.KeyType.TIME_RANGE:
@@ -5,7 +5,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.restoreHierarchy = exports.getValueByPath = exports.getIndexedHash = exports.getSymVal = exports.getSymKey = exports.formatISODate = exports.getTimeSeries = exports.getSubscriptionTopic = exports.findSubscriptionForTrigger = exports.findTopKey = exports.XSleepFor = exports.matchesStatus = exports.matchesStatusCode = exports.identifyRedisTypeFromClass = exports.polyfill = exports.identifyRedisType = exports.deterministicRandom = exports.guid = exports.deepCopy = exports.sleepImmediate = exports.sleepFor = exports.getSystemHealth = void 0;
7
7
  const os_1 = __importDefault(require("os"));
8
- const systeminformation_1 = __importDefault(require("systeminformation"));
9
8
  const nanoid_1 = require("nanoid");
10
9
  const enums_1 = require("./enums");
11
10
  async function safeExecute(operation, defaultValue) {
@@ -21,23 +20,23 @@ async function getSystemHealth() {
21
20
  const totalMemory = os_1.default.totalmem();
22
21
  const freeMemory = os_1.default.freemem();
23
22
  const usedMemory = totalMemory - freeMemory;
24
- const cpus = os_1.default.cpus();
23
+ //const cpus = os.cpus();
25
24
  // CPU load calculation remains unchanged
26
- const cpuLoad = cpus.map((cpu, i) => {
27
- const total = Object.values(cpu.times).reduce((acc, tv) => acc + tv, 0);
28
- const idle = cpu.times.idle;
29
- const usage = ((total - idle) / total) * 100;
30
- return { [`CPU ${i} Usage`]: `${usage.toFixed(2)}%` };
31
- });
25
+ // const cpuLoad = cpus.map((cpu, i) => {
26
+ // const total = Object.values(cpu.times).reduce((acc, tv) => acc + tv, 0);
27
+ // const idle = cpu.times.idle;
28
+ // const usage = ((total - idle) / total) * 100;
29
+ // return { [`CPU ${i} Usage`]: `${usage.toFixed(2)}%` };
30
+ // });
32
31
  // Wrap each systeminformation call with safeExecute
33
- const networkStats = await safeExecute(systeminformation_1.default.networkStats(), []);
32
+ //const networkStats = await safeExecute(si.networkStats(), []);
34
33
  // Construct the system health object with error handling in mind
35
34
  const systemHealth = {
36
35
  TotalMemoryGB: `${(totalMemory / 1024 / 1024 / 1024).toFixed(2)} GB`,
37
36
  FreeMemoryGB: `${(freeMemory / 1024 / 1024 / 1024).toFixed(2)} GB`,
38
37
  UsedMemoryGB: `${(usedMemory / 1024 / 1024 / 1024).toFixed(2)} GB`,
39
- CPULoad: cpuLoad,
40
- NetworkStats: networkStats,
38
+ CPULoad: [],
39
+ NetworkStats: [],
41
40
  };
42
41
  return systemHealth;
43
42
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -31,7 +31,7 @@
31
31
  "test:durable:collision": "NODE_ENV=test jest ./tests/durable/collision/index.test.ts --detectOpenHandles --forceExit --verbose",
32
32
  "test:durable:fatal": "NODE_ENV=test jest ./tests/durable/fatal/index.test.ts --detectOpenHandles --forceExit --verbose",
33
33
  "test:durable:goodbye": "NODE_ENV=test jest ./tests/durable/goodbye/index.test.ts --detectOpenHandles --forceExit --verbose",
34
- "test:durable:hello": "NODE_ENV=test jest ./tests/durable/helloworld/index.test.ts --detectOpenHandles --forceExit --verbose",
34
+ "test:durable:hello": "HMSH_IS_CLUSTER=true NODE_ENV=test jest ./tests/durable/helloworld/index.test.ts --detectOpenHandles --forceExit --verbose",
35
35
  "test:durable:hook": "NODE_ENV=test jest ./tests/durable/hook/index.test.ts --detectOpenHandles --forceExit --verbose",
36
36
  "test:durable:interrupt": "NODE_ENV=test jest ./tests/durable/interrupt/index.test.ts --detectOpenHandles --forceExit --verbose",
37
37
  "test:durable:loopactivity": "NODE_ENV=test jest ./tests/durable/loopactivity/index.test.ts --detectOpenHandles --forceExit --verbose",
@@ -41,6 +41,7 @@
41
41
  "test:durable:signal": "NODE_ENV=test jest ./tests/durable/signal/index.test.ts --detectOpenHandles --forceExit --verbose",
42
42
  "test:durable:unknown": "NODE_ENV=test jest ./tests/durable/unknown/index.test.ts --detectOpenHandles --forceExit --verbose",
43
43
  "test:emit": "NODE_ENV=test jest ./tests/functional/emit/index.test.ts --detectOpenHandles --forceExit --verbose",
44
+ "test:expired": "NODE_ENV=test jest ./tests/functional/expired/index.test.ts --detectOpenHandles --forceExit --verbose",
44
45
  "test:functional": "NODE_ENV=test jest ./tests/functional/*/index.test.ts --detectOpenHandles --forceExit --verbose",
45
46
  "test:hmsh": "NODE_ENV=test jest ./tests/functional/index.test.ts --detectOpenHandles --verbose",
46
47
  "test:hook": "NODE_ENV=test jest ./tests/functional/hook/index.test.ts --detectOpenHandles --forceExit --verbose",
@@ -112,7 +112,7 @@ class Activity {
112
112
  stack: error.stack,
113
113
  name: error.name,
114
114
  });
115
- telemetry && telemetry.setActivityError(error.message);
115
+ telemetry?.setActivityError(error.message);
116
116
  throw error;
117
117
  }
118
118
  finally {
@@ -58,7 +58,7 @@ class Await extends activity_1.Activity {
58
58
  else {
59
59
  this.logger.error('await-process-error', { ...error });
60
60
  }
61
- telemetry.setActivityError(error.message);
61
+ telemetry?.setActivityError(error.message);
62
62
  throw error;
63
63
  }
64
64
  finally {
@@ -58,7 +58,7 @@ class Cycle extends activity_1.Activity {
58
58
  else {
59
59
  this.logger.error('cycle-process-error', { ...error });
60
60
  }
61
- telemetry.setActivityError(error.message);
61
+ telemetry?.setActivityError(error.message);
62
62
  throw error;
63
63
  }
64
64
  finally {
@@ -53,7 +53,7 @@ class Hook extends activity_1.Activity {
53
53
  else {
54
54
  this.logger.error('hook-process-error', { ...error });
55
55
  }
56
- telemetry.setActivityError(error.message);
56
+ telemetry?.setActivityError(error.message);
57
57
  throw error;
58
58
  }
59
59
  finally {
@@ -45,7 +45,7 @@ class Interrupt extends activity_1.Activity {
45
45
  else {
46
46
  this.logger.error('interrupt-process-error', { ...error });
47
47
  }
48
- telemetry.setActivityError(error.message);
48
+ telemetry?.setActivityError(error.message);
49
49
  throw error;
50
50
  }
51
51
  finally {
@@ -66,7 +66,7 @@ class Signal extends activity_1.Activity {
66
66
  else {
67
67
  this.logger.error('signal-process-error', { ...error });
68
68
  }
69
- telemetry.setActivityError(error.message);
69
+ telemetry?.setActivityError(error.message);
70
70
  throw error;
71
71
  }
72
72
  finally {
@@ -7,6 +7,7 @@ declare class Trigger extends Activity {
7
7
  config: TriggerActivity;
8
8
  constructor(config: ActivityType, data: ActivityData, metadata: ActivityMetadata, hook: ActivityData | null, engine: EngineService, context?: JobState);
9
9
  process(options?: ExtensionType): Promise<string>;
10
+ setExpired(seconds: number, multi: RedisMulti): Promise<void>;
10
11
  safeKey(key: string): string;
11
12
  bindSearchData(options?: ExtensionType): void;
12
13
  bindMarkerData(options?: ExtensionType): void;
@@ -20,7 +21,7 @@ declare class Trigger extends Activity {
20
21
  getJobStatus(): number;
21
22
  resolveJobId(context: Partial<JobState>): string;
22
23
  resolveJobKey(context: Partial<JobState>): string;
23
- setStateNX(): Promise<void>;
24
+ setStateNX(status?: number): Promise<void>;
24
25
  /**
25
26
  * Registers this job as a dependent of the parent job; when the
26
27
  * parent job is interrupted, this job will be interrupted
@@ -25,15 +25,20 @@ class Trigger extends activity_1.Activity {
25
25
  telemetry.startJobSpan();
26
26
  telemetry.startActivitySpan(this.leg);
27
27
  this.mapJobData();
28
- await this.setStateNX();
28
+ await this.setStateNX(options?.expired ? -1 : undefined);
29
29
  this.adjacencyList = await this.filterAdjacent();
30
- await this.setStatus(this.adjacencyList.length);
30
+ await this.setStatus(options?.expired ? -1 : this.adjacencyList.length);
31
31
  this.bindSearchData(options);
32
32
  this.bindMarkerData(options);
33
33
  const multi = this.store.getMulti();
34
34
  await this.setState(multi);
35
35
  await this.setStats(multi);
36
- await this.registerJobDependency(multi);
36
+ if (options?.expired) {
37
+ await this.setExpired(options?.expired, multi);
38
+ }
39
+ else {
40
+ await this.registerJobDependency(multi);
41
+ }
37
42
  await multi.exec();
38
43
  //if the parent (spawner) chose not to await,
39
44
  // emit the job_id as the data payload { job_id }
@@ -42,9 +47,13 @@ class Trigger extends activity_1.Activity {
42
47
  const jobStatus = Number(this.context.metadata.js);
43
48
  telemetry.setJobAttributes({ 'app.job.jss': jobStatus });
44
49
  const attrs = { 'app.job.jss': jobStatus };
45
- const messageIds = await this.transition(this.adjacencyList, jobStatus);
46
- if (messageIds.length) {
47
- attrs['app.activity.mids'] = messageIds.join(',');
50
+ //only transition if not inited in an expired state
51
+ //todo: enable resume from expired state
52
+ if (jobStatus !== -1) {
53
+ const messageIds = await this.transition(this.adjacencyList, jobStatus);
54
+ if (messageIds.length) {
55
+ attrs['app.activity.mids'] = messageIds.join(',');
56
+ }
48
57
  }
49
58
  telemetry.setActivityAttributes(attrs);
50
59
  return this.context.metadata.jid;
@@ -56,7 +65,7 @@ class Trigger extends activity_1.Activity {
56
65
  else {
57
66
  this.logger.error('trigger-process-error', { ...error });
58
67
  }
59
- telemetry.setActivityError(error.message);
68
+ telemetry?.setActivityError(error.message);
60
69
  throw error;
61
70
  }
62
71
  finally {
@@ -69,6 +78,9 @@ class Trigger extends activity_1.Activity {
69
78
  });
70
79
  }
71
80
  }
81
+ async setExpired(seconds, multi) {
82
+ await this.store.expireJob(this.context.metadata.jid, seconds, multi);
83
+ }
72
84
  safeKey(key) {
73
85
  return `_${key}`;
74
86
  }
@@ -190,9 +202,9 @@ class Trigger extends activity_1.Activity {
190
202
  const jobKey = this.config.stats?.key;
191
203
  return jobKey ? pipe_1.Pipe.resolve(jobKey, context) : '';
192
204
  }
193
- async setStateNX() {
205
+ async setStateNX(status) {
194
206
  const jobId = this.context.metadata.jid;
195
- if (!await this.store.setStateNX(jobId, this.engine.appId)) {
207
+ if (!await this.store.setStateNX(jobId, this.engine.appId, status)) {
196
208
  throw new errors_1.DuplicateJobError(jobId);
197
209
  }
198
210
  }
@@ -57,7 +57,7 @@ class Worker extends activity_1.Activity {
57
57
  else {
58
58
  this.logger.error('worker-process-error', { ...error });
59
59
  }
60
- telemetry.setActivityError(error.message);
60
+ telemetry?.setActivityError(error.message);
61
61
  throw error;
62
62
  }
63
63
  finally {
@@ -479,7 +479,7 @@ class Deployer {
479
479
  await this.store.xgroup('CREATE', stream, group, '$', 'MKSTREAM');
480
480
  }
481
481
  catch (err) {
482
- this.store.logger.info('consumer-group-exists', { stream, group });
482
+ this.store.logger.info('router-stream-group-exists', { stream, group });
483
483
  }
484
484
  }
485
485
  }
@@ -73,7 +73,11 @@ class ClientService {
73
73
  1000,
74
74
  };
75
75
  const context = { metadata: { trc, spn }, data: {} };
76
- const jobId = await hotMeshClient.pub(`${options.namespace ?? factory_1.APP_ID}.execute`, payload, context, { search: options?.search?.data, marker: options?.marker });
76
+ const jobId = await hotMeshClient.pub(`${options.namespace ?? factory_1.APP_ID}.execute`, payload, context, {
77
+ search: options?.search?.data,
78
+ marker: options?.marker,
79
+ expired: options?.expired,
80
+ });
77
81
  return new handle_1.WorkflowHandleService(hotMeshClient, workflowTopic, jobId);
78
82
  },
79
83
  /**
@@ -86,7 +86,7 @@ class WorkerService {
86
86
  }
87
87
  }
88
88
  async run() {
89
- this.workflowRunner.engine.logger.info('WorkerService is running');
89
+ this.workflowRunner.engine.logger.info('durable-worker-running');
90
90
  }
91
91
  async initActivityWorker(config, activityTopic) {
92
92
  const redisConfig = {
@@ -47,7 +47,7 @@ declare class EngineService {
47
47
  initStoreChannel(store: RedisClient): Promise<void>;
48
48
  initSubChannel(sub: RedisClient): Promise<void>;
49
49
  initStreamChannel(stream: RedisClient): Promise<void>;
50
- initRouter(config: HotMeshConfig): Router;
50
+ initRouter(config: HotMeshConfig): Promise<Router>;
51
51
  getVID(vid?: AppVID): Promise<AppVID>;
52
52
  setCacheMode(cacheMode: CacheMode, untilVersion: string): void;
53
53
  routeToSubscribers(topic: string, message: JobOutput): Promise<void>;
@@ -40,7 +40,7 @@ class EngineService {
40
40
  await instance.initStoreChannel(config.engine.store);
41
41
  await instance.initSubChannel(config.engine.sub);
42
42
  await instance.initStreamChannel(config.engine.stream);
43
- instance.router = instance.initRouter(config);
43
+ instance.router = await instance.initRouter(config);
44
44
  instance.router.consumeMessages(instance.stream.mintKey(key_1.KeyType.STREAMS, { appId: instance.appId }), 'ENGINE', instance.guid, instance.processStreamMessage.bind(instance));
45
45
  instance.taskService = new task_1.TaskService(instance.store, logger);
46
46
  instance.exporter = new exporter_1.ExporterService(instance.appId, instance.store, logger);
@@ -82,7 +82,8 @@ class EngineService {
82
82
  }
83
83
  await this.stream.init(this.namespace, this.appId, this.logger);
84
84
  }
85
- initRouter(config) {
85
+ async initRouter(config) {
86
+ const throttle = await this.store.getThrottleRate(':');
86
87
  return new router_1.Router({
87
88
  namespace: this.namespace,
88
89
  appId: this.appId,
@@ -90,6 +91,7 @@ class EngineService {
90
91
  role: stream_1.StreamRole.ENGINE,
91
92
  reclaimDelay: config.engine.reclaimDelay,
92
93
  reclaimCount: config.engine.reclaimCount,
94
+ throttle,
93
95
  }, this.stream, this.store, this.logger);
94
96
  }
95
97
  async getVID(vid) {
@@ -114,7 +116,7 @@ class EngineService {
114
116
  }
115
117
  }
116
118
  setCacheMode(cacheMode, untilVersion) {
117
- this.logger.info(`engine-rule-cache-updated`, {
119
+ this.logger.info(`engine-cache-updated`, {
118
120
  mode: cacheMode,
119
121
  until: untilVersion,
120
122
  });
@@ -135,7 +137,12 @@ class EngineService {
135
137
  this.taskService.processTimeHooks(this.hookTime.bind(this));
136
138
  }
137
139
  async throttle(delayInMillis) {
138
- this.router.setThrottle(delayInMillis);
140
+ try {
141
+ this.router.setThrottle(delayInMillis);
142
+ }
143
+ catch (e) {
144
+ this.logger.error('engine-throttle-error', { error: e });
145
+ }
139
146
  }
140
147
  // ************* METADATA/MODEL METHODS *************
141
148
  async initActivity(topic, data = {}, context) {
@@ -220,7 +227,7 @@ class EngineService {
220
227
  }
221
228
  // ****************** STREAM RE-ENTRY POINT *****************
222
229
  async processStreamMessage(streamData) {
223
- this.logger.debug('engine-process-stream-message', {
230
+ this.logger.debug('engine-process', {
224
231
  jid: streamData.metadata.jid,
225
232
  gid: streamData.metadata.gid,
226
233
  dad: streamData.metadata.dad,
@@ -279,7 +286,7 @@ class EngineService {
279
286
  const activityHandler = (await this.initActivity(`.${streamData.metadata.aid}`, streamData.data, context));
280
287
  await activityHandler.processEvent(streamData.status, streamData.code, 'output');
281
288
  }
282
- this.logger.debug('engine-process-stream-message-end', {
289
+ this.logger.debug('engine-process-end', {
283
290
  jid: streamData.metadata.jid,
284
291
  gid: streamData.metadata.gid,
285
292
  aid: streamData.metadata.aid,
@@ -11,6 +11,7 @@ const logger_1 = require("../logger");
11
11
  const quorum_1 = require("../quorum");
12
12
  const router_1 = require("../router");
13
13
  const worker_1 = require("../worker");
14
+ const enums_1 = require("../../modules/enums");
14
15
  class HotMeshService {
15
16
  constructor() {
16
17
  this.engine = null;
@@ -94,9 +95,19 @@ class HotMeshService {
94
95
  return await this.quorum?.rollCall(delay);
95
96
  }
96
97
  async throttle(options) {
98
+ let throttle;
99
+ if (options.throttle === -1) {
100
+ throttle = enums_1.MAX_DELAY;
101
+ }
102
+ else {
103
+ throttle = options.throttle;
104
+ }
105
+ if (!Number.isInteger(throttle) || throttle < 0 || throttle > enums_1.MAX_DELAY) {
106
+ throw new Error(`Throttle must be a non-negative integer and not exceed ${enums_1.MAX_DELAY} ms; send -1 to throttle indefinitely`);
107
+ }
97
108
  const throttleMessage = {
98
109
  type: 'throttle',
99
- throttle: options.throttle,
110
+ throttle: throttle,
100
111
  };
101
112
  if (options.guid) {
102
113
  throttleMessage.guid = options.guid;
@@ -104,6 +115,7 @@ class HotMeshService {
104
115
  else if (options.topic) {
105
116
  throttleMessage.topic = options.topic;
106
117
  }
118
+ await this.engine.store.setThrottleRate(throttleMessage);
107
119
  return await this.quorum?.pub(throttleMessage);
108
120
  }
109
121
  // ************* COMPILER METHODS *************
@@ -42,6 +42,8 @@ declare class Router {
42
42
  customSleep(): Promise<void>;
43
43
  consumeMessages(stream: string, group: string, consumer: string, callback: (streamData: StreamData) => Promise<StreamDataResponse | void>): Promise<void>;
44
44
  isStreamMessage(result: any): boolean;
45
+ isPaused(): boolean;
46
+ isStopped(group: string, consumer: string, stream: string): boolean;
45
47
  consumeOne(stream: string, group: string, id: string, message: string[], callback: (streamData: StreamData) => Promise<StreamDataResponse | void>): Promise<void>;
46
48
  execStreamLeg(input: StreamData, stream: string, id: string, callback: (streamData: StreamData) => Promise<StreamDataResponse | void>): Promise<StreamData>;
47
49
  ackAndDelete(stream: string, group: string, id: string): Promise<void>;