@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.
- package/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/README.md +17 -14
- package/build/modules/enums.d.ts +1 -0
- package/build/modules/enums.js +2 -1
- package/build/modules/key.d.ts +1 -2
- package/build/modules/key.js +3 -4
- package/build/modules/utils.js +10 -11
- package/build/package.json +3 -2
- package/build/services/activities/activity.js +1 -1
- package/build/services/activities/await.js +1 -1
- package/build/services/activities/cycle.js +1 -1
- package/build/services/activities/hook.js +1 -1
- package/build/services/activities/interrupt.js +1 -1
- package/build/services/activities/signal.js +1 -1
- package/build/services/activities/trigger.d.ts +2 -1
- package/build/services/activities/trigger.js +21 -9
- package/build/services/activities/worker.js +1 -1
- package/build/services/compiler/deployer.js +1 -1
- package/build/services/durable/client.js +5 -1
- package/build/services/durable/worker.js +1 -1
- package/build/services/engine/index.d.ts +1 -1
- package/build/services/engine/index.js +13 -6
- package/build/services/hotmesh/index.js +13 -1
- package/build/services/router/index.d.ts +2 -0
- package/build/services/router/index.js +37 -15
- package/build/services/store/clients/ioredis.js +28 -8
- package/build/services/store/clients/redis.js +27 -10
- package/build/services/store/index.d.ts +6 -2
- package/build/services/store/index.js +37 -5
- package/build/services/stream/clients/ioredis.js +1 -1
- package/build/services/stream/clients/redis.js +23 -23
- package/build/services/worker/index.d.ts +1 -1
- package/build/services/worker/index.js +4 -2
- package/build/types/durable.d.ts +11 -0
- package/build/types/hotmesh.d.ts +1 -1
- package/build/types/hotmesh.js +1 -1
- package/build/types/job.d.ts +11 -0
- package/build/types/quorum.d.ts +2 -4
- package/build/types/redis.d.ts +3 -0
- package/build/types/stream.d.ts +2 -0
- package/package.json +3 -2
- package/types/durable.ts +14 -1
- package/types/hotmesh.ts +1 -1
- package/types/job.ts +12 -0
- package/types/quorum.ts +2 -4
- package/types/redis.ts +3 -0
- 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
|
|
6
|
+
*Write functions in your own style, and let Redis govern their execution, reliably and durably.*
|
|
7
7
|
|
|
8
8
|
## Install
|
|
9
9
|
[](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
|
|
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-
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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).
|
package/build/modules/enums.d.ts
CHANGED
|
@@ -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;
|
package/build/modules/enums.js
CHANGED
|
@@ -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;
|
package/build/modules/key.d.ts
CHANGED
|
@@ -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>:
|
|
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[])
|
package/build/modules/key.js
CHANGED
|
@@ -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>:
|
|
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.
|
|
60
|
-
return `${namespace}:${params.appId}:
|
|
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:
|
package/build/modules/utils.js
CHANGED
|
@@ -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 =
|
|
23
|
+
//const cpus = os.cpus();
|
|
25
24
|
// CPU load calculation remains unchanged
|
|
26
|
-
const cpuLoad = cpus.map((cpu, i) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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(
|
|
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:
|
|
40
|
-
NetworkStats:
|
|
38
|
+
CPULoad: [],
|
|
39
|
+
NetworkStats: [],
|
|
41
40
|
};
|
|
42
41
|
return systemHealth;
|
|
43
42
|
}
|
package/build/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotmeshio/hotmesh",
|
|
3
|
-
"version": "0.1.
|
|
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",
|
|
@@ -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
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
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
|
}
|
|
@@ -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('
|
|
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, {
|
|
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('
|
|
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-
|
|
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
|
-
|
|
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
|
|
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-
|
|
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:
|
|
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>;
|