@hotmeshio/hotmesh 0.2.3 → 0.3.0
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/README.md +14 -14
- package/build/modules/enums.d.ts +1 -0
- package/build/modules/enums.js +2 -1
- package/build/modules/errors.d.ts +1 -0
- package/build/modules/errors.js +1 -0
- package/build/modules/utils.d.ts +1 -0
- package/build/modules/utils.js +6 -10
- package/build/package.json +1 -1
- package/build/services/activities/activity.d.ts +3 -0
- package/build/services/activities/activity.js +24 -7
- package/build/services/activities/hook.js +3 -2
- package/build/services/activities/trigger.js +15 -5
- package/build/services/collator/index.d.ts +2 -0
- package/build/services/collator/index.js +12 -0
- package/build/services/compiler/deployer.js +1 -0
- package/build/services/engine/index.js +1 -0
- package/build/services/exporter/index.js +1 -1
- package/build/services/meshcall/index.d.ts +5 -5
- package/build/services/meshcall/index.js +45 -32
- package/build/services/meshcall/schemas/factory.js +2 -2
- package/build/services/meshdata/index.d.ts +2 -2
- package/build/services/meshdata/index.js +25 -20
- package/build/services/meshflow/client.js +3 -8
- package/build/services/meshflow/schemas/factory.d.ts +1 -1
- package/build/services/meshflow/schemas/factory.js +56 -10
- package/build/services/meshflow/worker.js +3 -5
- package/build/services/meshflow/workflow.js +10 -12
- package/build/services/router/index.js +3 -1
- package/build/services/store/clients/ioredis.d.ts +1 -0
- package/build/services/store/clients/ioredis.js +4 -0
- package/build/services/store/clients/redis.d.ts +1 -0
- package/build/services/store/clients/redis.js +5 -0
- package/build/services/store/index.d.ts +2 -3
- package/build/services/store/index.js +5 -36
- package/build/services/task/index.d.ts +1 -1
- package/build/services/task/index.js +3 -6
- package/build/types/activity.d.ts +2 -0
- package/build/types/error.d.ts +1 -0
- package/build/types/hook.d.ts +1 -0
- package/build/types/hotmesh.d.ts +1 -0
- package/build/types/job.d.ts +1 -1
- package/build/types/meshcall.d.ts +6 -2
- package/build/types/meshdata.d.ts +1 -1
- package/build/types/meshflow.d.ts +3 -0
- package/package.json +1 -1
- package/types/activity.ts +3 -1
- package/types/error.ts +1 -0
- package/types/hook.ts +6 -1
- package/types/hotmesh.ts +35 -0
- package/types/job.ts +4 -14
- package/types/meshcall.ts +19 -2
- package/types/meshdata.ts +9 -5
- package/types/meshflow.ts +12 -1
package/README.md
CHANGED
|
@@ -10,13 +10,13 @@ npm install @hotmeshio/hotmesh
|
|
|
10
10
|
You have a Redis instance? Good. You're ready to go.
|
|
11
11
|
|
|
12
12
|
## Learn
|
|
13
|
-
[
|
|
13
|
+
[📄 Docs](https://hotmeshio.github.io/sdk-typescript/) | [💼 Projects](https://github.com/hotmeshio/samples-typescript) | [🎥 Overview (3m)](https://www.loom.com/share/211bd4b4038d42f0ba34374ef5b6f961?sid=7b889a56-f60f-4ccc-84e7-8c2697e548a9) | [🎥 Transactional Workflow (9m)](https://www.loom.com/share/54ffd5266baf4ac6b287578abfd1d821?sid=0db2cef8-ef0d-4e02-a0b7-a1ee14f476ce)
|
|
14
14
|
|
|
15
15
|
## MeshCall | Connect Everything
|
|
16
|
-
[MeshCall](https://hotmeshio.github.io/sdk-typescript/classes/services_meshcall.MeshCall.html) connects your
|
|
16
|
+
[MeshCall](https://hotmeshio.github.io/sdk-typescript/classes/services_meshcall.MeshCall.html) connects your services as a singular mesh, exposing functions as idempotent endpoints. Function responses are cacheable and functions can even run as idempotent cron jobs. Make blazing fast interservice calls that return in milliseconds without the overhead of HTTP.
|
|
17
17
|
|
|
18
18
|
<details style="padding: .5em">
|
|
19
|
-
<summary style="font-size:1.25em;">Run an idempotent cron job</summary>
|
|
19
|
+
<summary style="font-size:1.25em;">Run an idempotent cron job <small>[more]</small></summary>
|
|
20
20
|
|
|
21
21
|
### Run a Cron
|
|
22
22
|
This example demonstrates an *idempotent* cron that runs daily at midnight. The `id` makes each cron job unique and ensures that only one instance runs, despite repeated invocations. *The `cron` method returns `false` if a workflow is already running with the same `id`.*
|
|
@@ -54,7 +54,7 @@ You have a Redis instance? Good. You're ready to go.
|
|
|
54
54
|
</details>
|
|
55
55
|
|
|
56
56
|
<details style="padding: .5em">
|
|
57
|
-
<summary style="font-size:1.25em;">Interrupt a cron job</summary>
|
|
57
|
+
<summary style="font-size:1.25em;">Interrupt a cron job <small>[more]</small></summary>
|
|
58
58
|
|
|
59
59
|
### Interrupt a Cron
|
|
60
60
|
This example demonstrates how to cancel a running cron job.
|
|
@@ -76,7 +76,7 @@ You have a Redis instance? Good. You're ready to go.
|
|
|
76
76
|
</details>
|
|
77
77
|
|
|
78
78
|
<details style="padding: .5em">
|
|
79
|
-
<summary style="font-size:1.25em;">Call any function in any service</summary>
|
|
79
|
+
<summary style="font-size:1.25em;">Call any function in any service <small>[more]</small></summary>
|
|
80
80
|
|
|
81
81
|
### Call a Function
|
|
82
82
|
Make blazing fast interservice calls that behave like HTTP but without the setup and performance overhead. This example demonstrates how to connect a function to the mesh and call it from anywhere on the network.
|
|
@@ -129,7 +129,7 @@ You have a Redis instance? Good. You're ready to go.
|
|
|
129
129
|
</details>
|
|
130
130
|
|
|
131
131
|
<details style="padding: .5em">
|
|
132
|
-
<summary style="font-size:1.25em;">Call and <b>cache</b> a function</summary>
|
|
132
|
+
<summary style="font-size:1.25em;">Call and <b>cache</b> a function <small>[more]</small></summary>
|
|
133
133
|
|
|
134
134
|
### Cache a Function
|
|
135
135
|
Redis is great for unburdening stressed services. This solution builds upon the previous example, caching the response. The linked function will only be re/called when the cached result expires. Everything remains the same, except the caller which specifies an `id` and `ttl`.
|
|
@@ -172,7 +172,7 @@ You have a Redis instance? Good. You're ready to go.
|
|
|
172
172
|
[MeshFlow](https://hotmeshio.github.io/sdk-typescript/classes/services_meshflow.MeshFlow.html) is a drop-in replacement for [Temporal.io](https://temporal.io). If you need to orchestrate your functions as durable workflows, MeshFlow combines the popular Temporal SDK with Redis' *in-memory execution speed*.
|
|
173
173
|
|
|
174
174
|
<details style="padding: .5em">
|
|
175
|
-
<summary style="font-size:1.25em;">Orchestrate unpredictable activities</summary>
|
|
175
|
+
<summary style="font-size:1.25em;">Orchestrate unpredictable activities <small>[more]</small></summary>
|
|
176
176
|
|
|
177
177
|
### Proxy Activities
|
|
178
178
|
When an endpoint is unpredictable, use `proxyActivities`. HotMesh will retry as necessary until the call succeeds. This example demonstrates a workflow that greets a user in both English and Spanish. Even though both activities throw random errors, the workflow always returns a successful result.
|
|
@@ -263,7 +263,7 @@ When an endpoint is unpredictable, use `proxyActivities`. HotMesh will retry as
|
|
|
263
263
|
</details>
|
|
264
264
|
|
|
265
265
|
<details style="padding: .5em">
|
|
266
|
-
<summary style="font-size:1.25em;">Pause and wait for a signal</summary>
|
|
266
|
+
<summary style="font-size:1.25em;">Pause and wait for a signal <small>[more]</small></summary>
|
|
267
267
|
|
|
268
268
|
### Wait for Signal
|
|
269
269
|
Pause a function and only awaken when a matching signal is received from the outide.
|
|
@@ -356,7 +356,7 @@ Pause a function and only awaken when a matching signal is received from the out
|
|
|
356
356
|
</details>
|
|
357
357
|
|
|
358
358
|
<details style="padding: .5em">
|
|
359
|
-
<summary style="font-size:1.25em;">Wait for multiple signals (collation)</summary>
|
|
359
|
+
<summary style="font-size:1.25em;">Wait for multiple signals (collation) <small>[more]</small></summary>
|
|
360
360
|
|
|
361
361
|
### Collate Multiple Signals
|
|
362
362
|
Use a standard `Promise` to collate and cache multiple signals. HotMesh will only awaken once **all** signals have arrived. HotMesh will track up to 25 concurrent signals.
|
|
@@ -406,7 +406,7 @@ Use a standard `Promise` to collate and cache multiple signals. HotMesh will onl
|
|
|
406
406
|
</details>
|
|
407
407
|
|
|
408
408
|
<details style="padding: .5em">
|
|
409
|
-
<summary style="font-size:1.25em;">Create a recurring, cyclical workflow</summary>
|
|
409
|
+
<summary style="font-size:1.25em;">Create a recurring, cyclical workflow <small>[more]</small></summary>
|
|
410
410
|
|
|
411
411
|
### Cyclical Workflow
|
|
412
412
|
This example calls an activity and then sleeps for a week. It runs indefinitely until it's manually stopped. It takes advantage of durable execution and can safely sleep for months or years.
|
|
@@ -511,7 +511,7 @@ Deployments with the Redis `FT.SEARCH` module enabled can use the **MeshData** m
|
|
|
511
511
|
*For those Redis deployments without the `FT.SEARCH` module, it's still useful to define a workflow schema. The MeshData class provides convenience methods for reading and writing hash field data to a workflow record (e.g., `get`, `del`, and `incr`).*
|
|
512
512
|
|
|
513
513
|
<details style="padding: .5em">
|
|
514
|
-
<summary style="font-size:1.25em;">Create a search index</summary>
|
|
514
|
+
<summary style="font-size:1.25em;">Create a search index <small>[more]</small></summary>
|
|
515
515
|
|
|
516
516
|
### Workflow Data Indexes
|
|
517
517
|
|
|
@@ -551,7 +551,7 @@ This example demonstrates how to define a schema and deploy an index for a 'user
|
|
|
551
551
|
</details>
|
|
552
552
|
|
|
553
553
|
<details style="padding: .5em">
|
|
554
|
-
<summary style="font-size:1.25em;">Create an indexed, searchable record</summary>
|
|
554
|
+
<summary style="font-size:1.25em;">Create an indexed, searchable record <small>[more]</small></summary>
|
|
555
555
|
|
|
556
556
|
### Workflow Record Data
|
|
557
557
|
This example demonstrates how to create a 'user' workflow backed by the searchable schema from the prior example.
|
|
@@ -631,7 +631,7 @@ This example demonstrates how to create a 'user' workflow backed by the searchab
|
|
|
631
631
|
</details>
|
|
632
632
|
|
|
633
633
|
<details style="padding: .5em">
|
|
634
|
-
<summary style="font-size:1.25em;">Fetch record data</summary>
|
|
634
|
+
<summary style="font-size:1.25em;">Fetch record data <small>[more]</small></summary>
|
|
635
635
|
|
|
636
636
|
### Read Record Data
|
|
637
637
|
This example demonstrates how to read data fields directly from a workflow.
|
|
@@ -662,7 +662,7 @@ This example demonstrates how to read data fields directly from a workflow.
|
|
|
662
662
|
</details>
|
|
663
663
|
|
|
664
664
|
<details style="padding: .5em">
|
|
665
|
-
<summary style="font-size:1.25em;">Search record data</summary>
|
|
665
|
+
<summary style="font-size:1.25em;">Search record data <small>[more]</small></summary>
|
|
666
666
|
|
|
667
667
|
### Query Record Data
|
|
668
668
|
This example demonstrates how to search for those workflows where a given condition exists in the data. This one searches for active users. *NOTE: The native Redis FT.SEARCH syntax is supported. The JSON abstraction shown here is a convenience method for straight-forward, one-dimensional queries.*
|
package/build/modules/enums.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { LogLevel } from '../types/logger';
|
|
2
2
|
export declare const HMSH_LOGLEVEL: LogLevel;
|
|
3
3
|
export declare const HMSH_IS_CLUSTER: boolean;
|
|
4
|
+
export declare const HMSH_SIGNAL_EXPIRE = 3600;
|
|
4
5
|
export declare const HMSH_CODE_SUCCESS = 200;
|
|
5
6
|
export declare const HMSH_CODE_PENDING = 202;
|
|
6
7
|
export declare const HMSH_CODE_NOTFOUND = 404;
|
package/build/modules/enums.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
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_MESHFLOW_EXP_BACKOFF = exports.HMSH_MESHFLOW_MAX_INTERVAL = exports.HMSH_MESHFLOW_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_MESHFLOW_RETRYABLE = exports.HMSH_CODE_MESHFLOW_FATAL = exports.HMSH_CODE_MESHFLOW_MAXED = exports.HMSH_CODE_MESHFLOW_TIMEOUT = exports.HMSH_CODE_MESHFLOW_WAIT = exports.HMSH_CODE_MESHFLOW_PROXY = exports.HMSH_CODE_MESHFLOW_CHILD = exports.HMSH_CODE_MESHFLOW_ALL = exports.HMSH_CODE_MESHFLOW_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_MESHFLOW_EXP_BACKOFF = exports.HMSH_MESHFLOW_MAX_INTERVAL = exports.HMSH_MESHFLOW_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_MESHFLOW_RETRYABLE = exports.HMSH_CODE_MESHFLOW_FATAL = exports.HMSH_CODE_MESHFLOW_MAXED = exports.HMSH_CODE_MESHFLOW_TIMEOUT = exports.HMSH_CODE_MESHFLOW_WAIT = exports.HMSH_CODE_MESHFLOW_PROXY = exports.HMSH_CODE_MESHFLOW_CHILD = exports.HMSH_CODE_MESHFLOW_ALL = exports.HMSH_CODE_MESHFLOW_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_SIGNAL_EXPIRE = exports.HMSH_IS_CLUSTER = exports.HMSH_LOGLEVEL = void 0;
|
|
4
4
|
exports.HMSH_LOGLEVEL = process.env.HMSH_LOGLEVEL || 'info';
|
|
5
5
|
exports.HMSH_IS_CLUSTER = process.env.HMSH_IS_CLUSTER === 'true';
|
|
6
|
+
exports.HMSH_SIGNAL_EXPIRE = 3600;
|
|
6
7
|
exports.HMSH_CODE_SUCCESS = 200;
|
|
7
8
|
exports.HMSH_CODE_PENDING = 202;
|
|
8
9
|
exports.HMSH_CODE_NOTFOUND = 404;
|
package/build/modules/errors.js
CHANGED
|
@@ -53,6 +53,7 @@ class MeshFlowChildError extends Error {
|
|
|
53
53
|
this.workflowTopic = params.workflowTopic;
|
|
54
54
|
this.parentWorkflowId = params.parentWorkflowId;
|
|
55
55
|
this.expire = params.expire;
|
|
56
|
+
this.persistent = params.persistent;
|
|
56
57
|
this.signalIn = params.signalIn;
|
|
57
58
|
this.originJobId = params.originJobId;
|
|
58
59
|
this.index = params.index;
|
package/build/modules/utils.d.ts
CHANGED
|
@@ -36,3 +36,4 @@ export declare function getValueByPath(obj: {
|
|
|
36
36
|
}, path: string): any;
|
|
37
37
|
export declare function restoreHierarchy(obj: StringAnyType): StringAnyType;
|
|
38
38
|
export declare function isValidCron(cronExpression: string): boolean;
|
|
39
|
+
export declare const s: (input: string) => number;
|
package/build/modules/utils.js
CHANGED
|
@@ -3,20 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.isValidCron = exports.restoreHierarchy = exports.getValueByPath = exports.getIndexedHash = exports.getSymVal = exports.getSymKey = exports.formatISODate = exports.getTimeSeries = exports.getSubscriptionTopic = exports.findSubscriptionForTrigger = exports.findTopKey = exports.matchesStatus = exports.matchesStatusCode = exports.identifyRedisTypeFromClass = exports.polyfill = exports.identifyRedisType = exports.XSleepFor = exports.sleepImmediate = exports.sleepFor = exports.guid = exports.deterministicRandom = exports.deepCopy = exports.getSystemHealth = exports.hashOptions = void 0;
|
|
6
|
+
exports.s = exports.isValidCron = exports.restoreHierarchy = exports.getValueByPath = exports.getIndexedHash = exports.getSymVal = exports.getSymKey = exports.formatISODate = exports.getTimeSeries = exports.getSubscriptionTopic = exports.findSubscriptionForTrigger = exports.findTopKey = exports.matchesStatus = exports.matchesStatusCode = exports.identifyRedisTypeFromClass = exports.polyfill = exports.identifyRedisType = exports.XSleepFor = exports.sleepImmediate = exports.sleepFor = exports.guid = exports.deterministicRandom = exports.deepCopy = exports.getSystemHealth = exports.hashOptions = void 0;
|
|
7
7
|
const os_1 = __importDefault(require("os"));
|
|
8
8
|
const crypto_1 = require("crypto");
|
|
9
9
|
const nanoid_1 = require("nanoid");
|
|
10
|
+
const ms_1 = __importDefault(require("ms"));
|
|
10
11
|
const enums_1 = require("./enums");
|
|
11
|
-
async function safeExecute(operation, defaultValue) {
|
|
12
|
-
try {
|
|
13
|
-
return await operation;
|
|
14
|
-
}
|
|
15
|
-
catch (error) {
|
|
16
|
-
console.error(`Operation Error: ${error}`);
|
|
17
|
-
return defaultValue;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
12
|
const hashOptions = (options) => {
|
|
21
13
|
const str = JSON.stringify(options);
|
|
22
14
|
return (0, crypto_1.createHash)('sha256').update(str).digest('hex');
|
|
@@ -244,3 +236,7 @@ function isValidCron(cronExpression) {
|
|
|
244
236
|
return cronRegex.test(cronExpression);
|
|
245
237
|
}
|
|
246
238
|
exports.isValidCron = isValidCron;
|
|
239
|
+
const s = (input) => {
|
|
240
|
+
return (0, ms_1.default)(input) / 1000;
|
|
241
|
+
};
|
|
242
|
+
exports.s = s;
|
package/build/package.json
CHANGED
|
@@ -59,6 +59,9 @@ declare class Activity {
|
|
|
59
59
|
resolveDad(): string;
|
|
60
60
|
resolveAdjacentDad(): string;
|
|
61
61
|
filterAdjacent(): Promise<StreamData[]>;
|
|
62
|
+
isJobComplete(jobStatus: JobStatus): boolean;
|
|
63
|
+
shouldEmit(): boolean;
|
|
64
|
+
shouldPersistJob(): boolean;
|
|
62
65
|
transition(adjacencyList: StreamData[], jobStatus: JobStatus): Promise<string[]>;
|
|
63
66
|
jobWasInterrupted(jobStatus: JobStatus): boolean;
|
|
64
67
|
}
|
|
@@ -405,6 +405,10 @@ class Activity {
|
|
|
405
405
|
initPolicies(context) {
|
|
406
406
|
const expire = pipe_1.Pipe.resolve(this.config.expire ?? enums_1.HMSH_EXPIRE_DURATION, context);
|
|
407
407
|
context.metadata.expire = expire;
|
|
408
|
+
if (this.config.persistent != undefined) {
|
|
409
|
+
const persistent = pipe_1.Pipe.resolve(this.config.persistent ?? false, context);
|
|
410
|
+
context.metadata.persistent = persistent;
|
|
411
|
+
}
|
|
408
412
|
}
|
|
409
413
|
bindActivityData(type) {
|
|
410
414
|
this.context[this.metadata.aid][type].data = this.data;
|
|
@@ -446,21 +450,34 @@ class Activity {
|
|
|
446
450
|
}
|
|
447
451
|
return adjacencyList;
|
|
448
452
|
}
|
|
453
|
+
isJobComplete(jobStatus) {
|
|
454
|
+
return jobStatus <= 0;
|
|
455
|
+
}
|
|
456
|
+
shouldEmit() {
|
|
457
|
+
if (this.config.emit) {
|
|
458
|
+
return pipe_1.Pipe.resolve(this.config.emit, this.context) === true;
|
|
459
|
+
}
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
shouldPersistJob() {
|
|
463
|
+
if (this.config.persist !== undefined) {
|
|
464
|
+
return pipe_1.Pipe.resolve(this.config.persist, this.context) === true;
|
|
465
|
+
}
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
449
468
|
async transition(adjacencyList, jobStatus) {
|
|
450
469
|
if (this.jobWasInterrupted(jobStatus)) {
|
|
451
470
|
return;
|
|
452
471
|
}
|
|
453
472
|
let mIds = [];
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
}
|
|
458
|
-
if (jobStatus <= 0 || emit) {
|
|
473
|
+
if (this.shouldEmit() ||
|
|
474
|
+
this.isJobComplete(jobStatus) ||
|
|
475
|
+
this.shouldPersistJob()) {
|
|
459
476
|
await this.engine.runJobCompletionTasks(this.context, {
|
|
460
|
-
emit: jobStatus
|
|
477
|
+
emit: !this.isJobComplete(jobStatus) && !this.shouldPersistJob(),
|
|
461
478
|
});
|
|
462
479
|
}
|
|
463
|
-
if (adjacencyList.length && jobStatus
|
|
480
|
+
if (adjacencyList.length && !this.isJobComplete(jobStatus)) {
|
|
464
481
|
const multi = this.store.getMulti();
|
|
465
482
|
for (const execSignal of adjacencyList) {
|
|
466
483
|
await this.engine.router?.publishMessage(null, execSignal, multi);
|
|
@@ -7,6 +7,7 @@ const pipe_1 = require("../pipe");
|
|
|
7
7
|
const task_1 = require("../task");
|
|
8
8
|
const telemetry_1 = require("../telemetry");
|
|
9
9
|
const stream_1 = require("../../types/stream");
|
|
10
|
+
const enums_1 = require("../../modules/enums");
|
|
10
11
|
const activity_1 = require("./activity");
|
|
11
12
|
class Hook extends activity_1.Activity {
|
|
12
13
|
constructor(config, data, metadata, hook, engine, context) {
|
|
@@ -68,7 +69,7 @@ class Hook extends activity_1.Activity {
|
|
|
68
69
|
}
|
|
69
70
|
async doHook(telemetry) {
|
|
70
71
|
const multi = this.store.getMulti();
|
|
71
|
-
await this.registerHook(multi);
|
|
72
|
+
await this.registerHook(enums_1.HMSH_IS_CLUSTER ? undefined : multi);
|
|
72
73
|
this.mapOutputData();
|
|
73
74
|
this.mapJobData();
|
|
74
75
|
await this.setState(multi);
|
|
@@ -102,7 +103,7 @@ class Hook extends activity_1.Activity {
|
|
|
102
103
|
}
|
|
103
104
|
async registerHook(multi) {
|
|
104
105
|
if (this.config.hook?.topic) {
|
|
105
|
-
return await this.engine.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(), this.context.metadata.expire, multi);
|
|
106
107
|
}
|
|
107
108
|
else if (this.config.sleep) {
|
|
108
109
|
const duration = pipe_1.Pipe.resolve(this.config.sleep, this.context);
|
|
@@ -40,6 +40,7 @@ class Trigger extends activity_1.Activity {
|
|
|
40
40
|
else {
|
|
41
41
|
await this.registerJobDependency(multi);
|
|
42
42
|
}
|
|
43
|
+
await collator_1.CollatorService.notarizeInception(this, this.context.metadata.guid, multi);
|
|
43
44
|
await multi.exec();
|
|
44
45
|
this.execAdjacentParent();
|
|
45
46
|
telemetry.mapActivityAttributes();
|
|
@@ -51,13 +52,24 @@ class Trigger extends activity_1.Activity {
|
|
|
51
52
|
return this.context.metadata.jid;
|
|
52
53
|
}
|
|
53
54
|
catch (error) {
|
|
55
|
+
telemetry?.setActivityError(error.message);
|
|
54
56
|
if (error instanceof errors_1.DuplicateJobError) {
|
|
55
|
-
|
|
57
|
+
const isOverage = await collator_1.CollatorService.isInceptionOverage(this, this.context.metadata.guid);
|
|
58
|
+
if (isOverage) {
|
|
59
|
+
this.logger.info('duplicate-job-overage', {
|
|
60
|
+
job_id: error.jobId,
|
|
61
|
+
guid: this.context.metadata.guid,
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
this.logger.error('duplicate-job-error', {
|
|
66
|
+
job_id: error.jobId,
|
|
67
|
+
guid: this.context.metadata.guid,
|
|
68
|
+
});
|
|
56
69
|
}
|
|
57
70
|
else {
|
|
58
71
|
this.logger.error('trigger-process-error', { ...error });
|
|
59
72
|
}
|
|
60
|
-
telemetry?.setActivityError(error.message);
|
|
61
73
|
throw error;
|
|
62
74
|
}
|
|
63
75
|
finally {
|
|
@@ -82,9 +94,6 @@ class Trigger extends activity_1.Activity {
|
|
|
82
94
|
if (options.pending) {
|
|
83
95
|
return -1;
|
|
84
96
|
}
|
|
85
|
-
else if (options.statusThreshold) {
|
|
86
|
-
return 1000000 - options.statusThreshold + count;
|
|
87
|
-
}
|
|
88
97
|
return count;
|
|
89
98
|
}
|
|
90
99
|
async setExpired(seconds, multi) {
|
|
@@ -166,6 +175,7 @@ class Trigger extends activity_1.Activity {
|
|
|
166
175
|
tpc: this.config.subscribes,
|
|
167
176
|
trc: this.context.metadata.trc,
|
|
168
177
|
spn: this.context.metadata.spn,
|
|
178
|
+
guid: this.context.metadata.guid,
|
|
169
179
|
jid: jobId,
|
|
170
180
|
dad: collator_1.CollatorService.getDimensionalSeed(),
|
|
171
181
|
key: jobKey,
|
|
@@ -13,6 +13,8 @@ declare class CollatorService {
|
|
|
13
13
|
static authorizeReentry(activity: Activity, multi?: RedisMulti): Promise<number>;
|
|
14
14
|
static notarizeEarlyExit(activity: Activity, multi?: RedisMulti): Promise<number>;
|
|
15
15
|
static notarizeEarlyCompletion(activity: Activity, multi?: RedisMulti): Promise<number>;
|
|
16
|
+
static notarizeInception(activity: Activity, guid: string, multi: RedisMulti): Promise<void>;
|
|
17
|
+
static isInceptionOverage(activity: Activity, guid: string): Promise<boolean>;
|
|
16
18
|
static notarizeReentry(activity: Activity, guid: string, multi?: RedisMulti): Promise<number>;
|
|
17
19
|
static notarizeContinuation(activity: Activity, multi?: RedisMulti): Promise<number>;
|
|
18
20
|
static notarizeCompletion(activity: Activity, multi?: RedisMulti): Promise<number>;
|
|
@@ -43,6 +43,18 @@ class CollatorService {
|
|
|
43
43
|
: 11000000000000;
|
|
44
44
|
return await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, 1000001 - decrement, this.getDimensionalAddress(activity), multi);
|
|
45
45
|
}
|
|
46
|
+
static async notarizeInception(activity, guid, multi) {
|
|
47
|
+
if (guid) {
|
|
48
|
+
await activity.store.collateSynthetic(activity.context.metadata.jid, guid, 1000000, multi);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
static async isInceptionOverage(activity, guid) {
|
|
52
|
+
if (guid) {
|
|
53
|
+
const amount = await activity.store.collateSynthetic(activity.context.metadata.jid, guid, 1000000);
|
|
54
|
+
return amount > 1000000;
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
46
58
|
static async notarizeReentry(activity, guid, multi) {
|
|
47
59
|
const jid = activity.context.metadata.jid;
|
|
48
60
|
const localMulti = multi || activity.store.getMulti();
|
|
@@ -15,7 +15,7 @@ class ExporterService {
|
|
|
15
15
|
this.symbols = this.store.getAllSymbols();
|
|
16
16
|
this.symbols = await this.symbols;
|
|
17
17
|
}
|
|
18
|
-
const depData =
|
|
18
|
+
const depData = [];
|
|
19
19
|
const jobData = await this.store.getRaw(jobId);
|
|
20
20
|
const jobExport = this.inflate(jobData, depData);
|
|
21
21
|
return jobExport;
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { HotMesh } from '../hotmesh';
|
|
2
|
-
import { MeshCallConnectParams, MeshCallCronParams, MeshCallExecParams, MeshCallFlushParams, MeshCallInterruptParams } from '../../types/meshcall';
|
|
2
|
+
import { MeshCallConnectParams, MeshCallCronParams, MeshCallExecParams, MeshCallFlushParams, MeshCallInstanceOptions, MeshCallInterruptParams } from '../../types/meshcall';
|
|
3
3
|
import { RedisConfig } from '../../types';
|
|
4
4
|
declare class MeshCall {
|
|
5
5
|
static workers: Map<string, HotMesh | Promise<HotMesh>>;
|
|
6
6
|
static engines: Map<string, HotMesh | Promise<HotMesh>>;
|
|
7
7
|
static connections: Map<string, any>;
|
|
8
8
|
constructor();
|
|
9
|
-
static findFirstMatching(
|
|
10
|
-
static getHotMeshClient: (namespace: string, connection: RedisConfig) => Promise<HotMesh>;
|
|
9
|
+
static findFirstMatching(targets: Map<string, HotMesh | Promise<HotMesh>>, namespace: string, config: RedisConfig, options?: MeshCallInstanceOptions): Promise<HotMesh | void>;
|
|
10
|
+
static getHotMeshClient: (namespace: string, connection: RedisConfig, options?: MeshCallInstanceOptions) => Promise<HotMesh>;
|
|
11
11
|
static verifyWorkflowActive(hotMesh: HotMesh, appId?: string, count?: number): Promise<boolean>;
|
|
12
12
|
static activateWorkflow(hotMesh: HotMesh, appId?: string, version?: string): Promise<void>;
|
|
13
|
-
static getInstance(namespace: string, redis: RedisConfig): Promise<HotMesh>;
|
|
13
|
+
static getInstance(namespace: string, redis: RedisConfig, options?: MeshCallInstanceOptions): Promise<HotMesh>;
|
|
14
14
|
static connect(params: MeshCallConnectParams): Promise<HotMesh>;
|
|
15
15
|
static exec<U>(params: MeshCallExecParams): Promise<U>;
|
|
16
16
|
static flush(params: MeshCallFlushParams): Promise<void>;
|
|
17
17
|
static cron(params: MeshCallCronParams): Promise<boolean>;
|
|
18
|
-
static interrupt(params: MeshCallInterruptParams): Promise<
|
|
18
|
+
static interrupt(params: MeshCallInterruptParams): Promise<boolean>;
|
|
19
19
|
static shutdown(): Promise<void>;
|
|
20
20
|
}
|
|
21
21
|
export { MeshCall };
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
var _a;
|
|
6
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
4
|
exports.MeshCall = void 0;
|
|
8
|
-
const ms_1 = __importDefault(require("ms"));
|
|
9
5
|
const hotmesh_1 = require("../hotmesh");
|
|
10
6
|
const enums_1 = require("../../modules/enums");
|
|
11
7
|
const utils_1 = require("../../modules/utils");
|
|
@@ -14,11 +10,15 @@ const cron_1 = require("../pipe/functions/cron");
|
|
|
14
10
|
const factory_1 = require("./schemas/factory");
|
|
15
11
|
class MeshCall {
|
|
16
12
|
constructor() { }
|
|
17
|
-
static async findFirstMatching(
|
|
18
|
-
for (const [id, hotMeshInstance] of
|
|
19
|
-
|
|
13
|
+
static async findFirstMatching(targets, namespace = key_1.HMNS, config, options = {}) {
|
|
14
|
+
for (const [id, hotMeshInstance] of targets) {
|
|
15
|
+
const hotMesh = await hotMeshInstance;
|
|
16
|
+
const appId = hotMesh.engine.appId;
|
|
17
|
+
if (appId === namespace) {
|
|
20
18
|
if (id.startsWith((0, utils_1.hashOptions)(config.options))) {
|
|
21
|
-
|
|
19
|
+
if (Boolean(options.readonly) == Boolean(hotMesh.engine.router.readonly)) {
|
|
20
|
+
return hotMeshInstance;
|
|
21
|
+
}
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
}
|
|
@@ -60,12 +60,15 @@ class MeshCall {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
static async getInstance(namespace, redis) {
|
|
64
|
-
let hotMeshInstance
|
|
63
|
+
static async getInstance(namespace, redis, options = {}) {
|
|
64
|
+
let hotMeshInstance;
|
|
65
|
+
if (!options.readonly) {
|
|
66
|
+
hotMeshInstance = await MeshCall.findFirstMatching(MeshCall.workers, namespace, redis, options);
|
|
67
|
+
}
|
|
65
68
|
if (!hotMeshInstance) {
|
|
66
|
-
hotMeshInstance = await MeshCall.findFirstMatching(MeshCall.engines, namespace, redis);
|
|
69
|
+
hotMeshInstance = await MeshCall.findFirstMatching(MeshCall.engines, namespace, redis, options);
|
|
67
70
|
if (!hotMeshInstance) {
|
|
68
|
-
hotMeshInstance = (await MeshCall.getHotMeshClient(namespace, redis));
|
|
71
|
+
hotMeshInstance = (await MeshCall.getHotMeshClient(namespace, redis, options));
|
|
69
72
|
}
|
|
70
73
|
}
|
|
71
74
|
return hotMeshInstance;
|
|
@@ -121,7 +124,7 @@ class MeshCall {
|
|
|
121
124
|
}
|
|
122
125
|
let expire = 1;
|
|
123
126
|
if (params.options?.ttl) {
|
|
124
|
-
expire = (0,
|
|
127
|
+
expire = (0, utils_1.s)(params.options.ttl);
|
|
125
128
|
}
|
|
126
129
|
const jobOutput = await hotMeshInstance.pubsub(TOPIC, { id, expire, topic: params.topic, args: params.args }, null, 30000);
|
|
127
130
|
return jobOutput?.data?.response;
|
|
@@ -131,7 +134,7 @@ class MeshCall {
|
|
|
131
134
|
await hotMeshInstance.scrub(params.id ?? params?.options?.id);
|
|
132
135
|
}
|
|
133
136
|
static async cron(params) {
|
|
134
|
-
|
|
137
|
+
if (params.callback) {
|
|
135
138
|
await MeshCall.connect({
|
|
136
139
|
logLevel: params.logLevel,
|
|
137
140
|
guid: params.guid,
|
|
@@ -140,22 +143,24 @@ class MeshCall {
|
|
|
140
143
|
callback: params.callback,
|
|
141
144
|
namespace: params.namespace,
|
|
142
145
|
});
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
146
|
+
}
|
|
147
|
+
const TOPIC = `${params.namespace ?? key_1.HMNS}.cron`;
|
|
148
|
+
const maxCycles = params.options.maxCycles ?? 100000;
|
|
149
|
+
let interval = enums_1.HMSH_FIDELITY_SECONDS;
|
|
150
|
+
let delay;
|
|
151
|
+
let cron;
|
|
152
|
+
if ((0, utils_1.isValidCron)(params.options.interval)) {
|
|
153
|
+
cron = params.options.interval;
|
|
154
|
+
const nextDelay = new cron_1.CronHandler().nextDelay(cron);
|
|
155
|
+
delay = nextDelay > 0 ? nextDelay : undefined;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
const seconds = (0, utils_1.s)(params.options.interval);
|
|
159
|
+
interval = Math.max(seconds, enums_1.HMSH_FIDELITY_SECONDS);
|
|
160
|
+
delay = params.options.delay ? (0, utils_1.s)(params.options.delay) : undefined;
|
|
161
|
+
}
|
|
162
|
+
try {
|
|
163
|
+
const hotMeshInstance = await MeshCall.getInstance(params.namespace, params.redis, { readonly: params.callback ? false : true, guid: params.guid });
|
|
159
164
|
await hotMeshInstance.pub(TOPIC, {
|
|
160
165
|
id: params.options.id,
|
|
161
166
|
topic: params.topic,
|
|
@@ -176,7 +181,13 @@ class MeshCall {
|
|
|
176
181
|
}
|
|
177
182
|
static async interrupt(params) {
|
|
178
183
|
const hotMeshInstance = await MeshCall.getInstance(params.namespace, params.redis);
|
|
179
|
-
|
|
184
|
+
try {
|
|
185
|
+
await hotMeshInstance.interrupt(`${params.namespace ?? key_1.HMNS}.cron`, params.options.id, { throw: false, expire: 1 });
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
180
191
|
}
|
|
181
192
|
static async shutdown() {
|
|
182
193
|
for (const [_, hotMeshInstance] of MeshCall.workers) {
|
|
@@ -193,7 +204,7 @@ _a = MeshCall;
|
|
|
193
204
|
MeshCall.workers = new Map();
|
|
194
205
|
MeshCall.engines = new Map();
|
|
195
206
|
MeshCall.connections = new Map();
|
|
196
|
-
MeshCall.getHotMeshClient = async (namespace, connection) => {
|
|
207
|
+
MeshCall.getHotMeshClient = async (namespace, connection, options = {}) => {
|
|
197
208
|
const optionsHash = (0, utils_1.hashOptions)(connection.options);
|
|
198
209
|
const targetNS = namespace ?? key_1.HMNS;
|
|
199
210
|
const connectionNS = `${optionsHash}.${targetNS}`;
|
|
@@ -203,6 +214,7 @@ MeshCall.getHotMeshClient = async (namespace, connection) => {
|
|
|
203
214
|
return hotMeshClient;
|
|
204
215
|
}
|
|
205
216
|
const hotMeshClient = hotmesh_1.HotMesh.init({
|
|
217
|
+
guid: options.guid,
|
|
206
218
|
appId: targetNS,
|
|
207
219
|
logLevel: enums_1.HMSH_LOGLEVEL,
|
|
208
220
|
engine: {
|
|
@@ -210,6 +222,7 @@ MeshCall.getHotMeshClient = async (namespace, connection) => {
|
|
|
210
222
|
class: connection.class,
|
|
211
223
|
options: connection.options,
|
|
212
224
|
},
|
|
225
|
+
readonly: options.readonly,
|
|
213
226
|
},
|
|
214
227
|
});
|
|
215
228
|
MeshCall.engines.set(connectionNS, hotMeshClient);
|
|
@@ -72,8 +72,6 @@ const getWorkflowYAML = (appId = key_1.HMNS, version = exports.VERSION) => {
|
|
|
72
72
|
|
|
73
73
|
- subscribes: ${appId}.cron
|
|
74
74
|
|
|
75
|
-
expire: 120
|
|
76
|
-
|
|
77
75
|
input:
|
|
78
76
|
schema:
|
|
79
77
|
type: object
|
|
@@ -123,6 +121,8 @@ const getWorkflowYAML = (appId = key_1.HMNS, version = exports.VERSION) => {
|
|
|
123
121
|
properties:
|
|
124
122
|
sleepSeconds:
|
|
125
123
|
type: number
|
|
124
|
+
iterationCount:
|
|
125
|
+
type: number
|
|
126
126
|
maps:
|
|
127
127
|
sleepSeconds:
|
|
128
128
|
'@pipe':
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HotMesh } from '../hotmesh';
|
|
2
2
|
import { WorkflowOptions, WorkflowSearchOptions, FindJobsOptions, FindOptions, FindWhereOptions, SearchResults, FindWhereQuery } from '../../types/meshflow';
|
|
3
|
-
import { CallOptions, ConnectionInput,
|
|
3
|
+
import { CallOptions, ConnectionInput, ExecInput, HookInput } from '../../types/meshdata';
|
|
4
4
|
import { RedisClass, RedisOptions } from '../../types/redis';
|
|
5
5
|
import { StringAnyType, StringStringType } from '../../types/serializer';
|
|
6
6
|
import { JobInterruptOptions, JobOutput } from '../../types/job';
|
|
@@ -45,7 +45,7 @@ declare class MeshData {
|
|
|
45
45
|
unsub: (callback: QuorumMessageCallback, options?: SubscriptionOptions) => Promise<void>;
|
|
46
46
|
};
|
|
47
47
|
connect<T>({ entity, target, options, }: ConnectionInput<T>): Promise<boolean>;
|
|
48
|
-
bindCallOptions(args: any[],
|
|
48
|
+
bindCallOptions(args: any[], callOptions?: CallOptions): StringAnyType;
|
|
49
49
|
pauseForTTL<T>(result: T, options: CallOptions): Promise<void>;
|
|
50
50
|
publishDone<T>(result: T, hotMesh: HotMesh, options: CallOptions): Promise<void>;
|
|
51
51
|
flush(entity: string, id: string, namespace?: string): Promise<string | void>;
|