@hotmeshio/hotmesh 0.2.4 → 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/exporter/index.js +1 -1
- package/build/services/meshcall/index.js +3 -9
- 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/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/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/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,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");
|
|
@@ -128,7 +124,7 @@ class MeshCall {
|
|
|
128
124
|
}
|
|
129
125
|
let expire = 1;
|
|
130
126
|
if (params.options?.ttl) {
|
|
131
|
-
expire = (0,
|
|
127
|
+
expire = (0, utils_1.s)(params.options.ttl);
|
|
132
128
|
}
|
|
133
129
|
const jobOutput = await hotMeshInstance.pubsub(TOPIC, { id, expire, topic: params.topic, args: params.args }, null, 30000);
|
|
134
130
|
return jobOutput?.data?.response;
|
|
@@ -159,11 +155,9 @@ class MeshCall {
|
|
|
159
155
|
delay = nextDelay > 0 ? nextDelay : undefined;
|
|
160
156
|
}
|
|
161
157
|
else {
|
|
162
|
-
const seconds = (0,
|
|
158
|
+
const seconds = (0, utils_1.s)(params.options.interval);
|
|
163
159
|
interval = Math.max(seconds, enums_1.HMSH_FIDELITY_SECONDS);
|
|
164
|
-
delay = params.options.delay
|
|
165
|
-
? (0, ms_1.default)(params.options.delay) / 1000
|
|
166
|
-
: undefined;
|
|
160
|
+
delay = params.options.delay ? (0, utils_1.s)(params.options.delay) : undefined;
|
|
167
161
|
}
|
|
168
162
|
try {
|
|
169
163
|
const hotMeshInstance = await MeshCall.getInstance(params.namespace, params.redis, { readonly: params.callback ? false : true, guid: params.guid });
|
|
@@ -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>;
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
var _a;
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.MeshData = void 0;
|
|
5
|
+
const utils_1 = require("../../modules/utils");
|
|
5
6
|
const meshflow_1 = require("../meshflow");
|
|
6
7
|
const hotmesh_1 = require("../hotmesh");
|
|
7
8
|
const hotmesh_2 = require("../../types/hotmesh");
|
|
9
|
+
const enums_1 = require("../../modules/enums");
|
|
8
10
|
class MeshData {
|
|
9
11
|
constructor(redisClass, redisOptions, search) {
|
|
10
12
|
this.connectionSignatures = {};
|
|
@@ -161,7 +163,7 @@ class MeshData {
|
|
|
161
163
|
this.connectionSignatures[entity] = target.toString();
|
|
162
164
|
const targetFunction = {
|
|
163
165
|
[entity]: async (...args) => {
|
|
164
|
-
const { callOptions } = this.bindCallOptions(args
|
|
166
|
+
const { callOptions } = this.bindCallOptions(args);
|
|
165
167
|
const result = (await target.apply(target, args));
|
|
166
168
|
await this.pauseForTTL(result, callOptions);
|
|
167
169
|
return result;
|
|
@@ -177,19 +179,11 @@ class MeshData {
|
|
|
177
179
|
});
|
|
178
180
|
return true;
|
|
179
181
|
}
|
|
180
|
-
bindCallOptions(args,
|
|
182
|
+
bindCallOptions(args, callOptions = {}) {
|
|
181
183
|
if (args.length) {
|
|
182
184
|
const lastArg = args[args.length - 1];
|
|
183
185
|
if (lastArg instanceof Object && lastArg?.$type === 'exec') {
|
|
184
186
|
callOptions = args.pop();
|
|
185
|
-
if (options.ttl === 'infinity') {
|
|
186
|
-
if (!callOptions) {
|
|
187
|
-
callOptions = { ttl: 'infinity' };
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
callOptions.ttl = 'infinity';
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
187
|
}
|
|
194
188
|
else if (lastArg instanceof Object && lastArg.$type === 'hook') {
|
|
195
189
|
callOptions = args.pop();
|
|
@@ -215,13 +209,6 @@ class MeshData {
|
|
|
215
209
|
});
|
|
216
210
|
const jobResponse = ['aAa', '/t', 'aBa', this.toString(result)];
|
|
217
211
|
await store?.exec('HSET', jobKey, ...jobResponse);
|
|
218
|
-
await this.publishDone(result, hotMesh, options);
|
|
219
|
-
if (options.ttl === 'infinity') {
|
|
220
|
-
await MeshData.workflow.waitFor(`flush-${options.$guid}`);
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
await MeshData.workflow.sleepFor(options.ttl);
|
|
224
|
-
}
|
|
225
212
|
}
|
|
226
213
|
}
|
|
227
214
|
async publishDone(result, hotMesh, options) {
|
|
@@ -278,9 +265,13 @@ class MeshData {
|
|
|
278
265
|
async hook({ entity, id, hookEntity, hookArgs, options = {}, }) {
|
|
279
266
|
const workflowId = MeshData.mintGuid(entity, id);
|
|
280
267
|
this.validate(workflowId);
|
|
268
|
+
const args = [
|
|
269
|
+
...hookArgs,
|
|
270
|
+
{ ...options, $guid: workflowId, $type: 'hook' },
|
|
271
|
+
];
|
|
281
272
|
return await this.getClient().workflow.hook({
|
|
282
273
|
namespace: options.namespace,
|
|
283
|
-
args
|
|
274
|
+
args,
|
|
284
275
|
taskQueue: options.taskQueue ?? hookEntity,
|
|
285
276
|
workflowName: hookEntity,
|
|
286
277
|
workflowId: options.workflowId ?? workflowId,
|
|
@@ -301,6 +292,19 @@ class MeshData {
|
|
|
301
292
|
}
|
|
302
293
|
catch (e) {
|
|
303
294
|
const optionsClone = { ...options };
|
|
295
|
+
let seconds;
|
|
296
|
+
if (optionsClone.ttl) {
|
|
297
|
+
if (optionsClone.signalIn !== false) {
|
|
298
|
+
optionsClone.signalIn = true;
|
|
299
|
+
}
|
|
300
|
+
if (optionsClone.ttl === 'infinity') {
|
|
301
|
+
delete optionsClone.ttl;
|
|
302
|
+
seconds = enums_1.MAX_DELAY;
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
seconds = (0, utils_1.s)(optionsClone.ttl);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
304
308
|
delete optionsClone.search;
|
|
305
309
|
delete optionsClone.config;
|
|
306
310
|
const handle = await client.workflow.start({
|
|
@@ -316,8 +320,9 @@ class MeshData {
|
|
|
316
320
|
await: options.await,
|
|
317
321
|
marker: options.marker,
|
|
318
322
|
pending: options.pending,
|
|
319
|
-
expire: options.expire,
|
|
320
|
-
|
|
323
|
+
expire: seconds ?? options.expire,
|
|
324
|
+
persistent: options.signalIn == false ? undefined : seconds && true,
|
|
325
|
+
signalIn: optionsClone.signalIn,
|
|
321
326
|
});
|
|
322
327
|
if (options.await === false) {
|
|
323
328
|
return handle.workflowId;
|
|
@@ -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.ClientService = void 0;
|
|
8
|
-
const ms_1 = __importDefault(require("ms"));
|
|
9
5
|
const enums_1 = require("../../modules/enums");
|
|
10
6
|
const utils_1 = require("../../modules/utils");
|
|
11
7
|
const hotmesh_1 = require("../hotmesh");
|
|
@@ -68,14 +64,14 @@ class ClientService {
|
|
|
68
64
|
arguments: [...options.args],
|
|
69
65
|
originJobId: options.originJobId,
|
|
70
66
|
expire: options.expire ?? enums_1.HMSH_EXPIRE_JOB_SECONDS,
|
|
67
|
+
persistent: options.persistent,
|
|
71
68
|
signalIn: options.signalIn,
|
|
72
69
|
parentWorkflowId: options.parentWorkflowId,
|
|
73
70
|
workflowId: options.workflowId || hotmesh_1.HotMesh.guid(),
|
|
74
71
|
workflowTopic: workflowTopic,
|
|
75
72
|
backoffCoefficient: options.config?.backoffCoefficient || enums_1.HMSH_MESHFLOW_EXP_BACKOFF,
|
|
76
73
|
maximumAttempts: options.config?.maximumAttempts || enums_1.HMSH_MESHFLOW_MAX_ATTEMPTS,
|
|
77
|
-
maximumInterval: (0,
|
|
78
|
-
1000,
|
|
74
|
+
maximumInterval: (0, utils_1.s)(options.config?.maximumInterval || enums_1.HMSH_MESHFLOW_MAX_INTERVAL),
|
|
79
75
|
};
|
|
80
76
|
const context = { metadata: { trc, spn }, data: {} };
|
|
81
77
|
const jobId = await hotMeshClient.pub(`${options.namespace ?? factory_1.APP_ID}.execute`, payload, context, {
|
|
@@ -97,8 +93,7 @@ class ClientService {
|
|
|
97
93
|
workflowTopic,
|
|
98
94
|
backoffCoefficient: options.config?.backoffCoefficient || enums_1.HMSH_MESHFLOW_EXP_BACKOFF,
|
|
99
95
|
maximumAttempts: options.config?.maximumAttempts || enums_1.HMSH_MESHFLOW_MAX_ATTEMPTS,
|
|
100
|
-
maximumInterval: (0,
|
|
101
|
-
1000,
|
|
96
|
+
maximumInterval: (0, utils_1.s)(options.config?.maximumInterval || enums_1.HMSH_MESHFLOW_MAX_INTERVAL),
|
|
102
97
|
};
|
|
103
98
|
const hotMeshClient = await this.getHotMeshClient(workflowTopic, options.namespace);
|
|
104
99
|
const msgId = await hotMeshClient.hook(`${hotMeshClient.appId}.flow.signal`, payload, types_1.StreamStatus.PENDING, 202);
|