@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.
Files changed (53) hide show
  1. package/README.md +14 -14
  2. package/build/modules/enums.d.ts +1 -0
  3. package/build/modules/enums.js +2 -1
  4. package/build/modules/errors.d.ts +1 -0
  5. package/build/modules/errors.js +1 -0
  6. package/build/modules/utils.d.ts +1 -0
  7. package/build/modules/utils.js +6 -10
  8. package/build/package.json +1 -1
  9. package/build/services/activities/activity.d.ts +3 -0
  10. package/build/services/activities/activity.js +24 -7
  11. package/build/services/activities/hook.js +3 -2
  12. package/build/services/activities/trigger.js +15 -5
  13. package/build/services/collator/index.d.ts +2 -0
  14. package/build/services/collator/index.js +12 -0
  15. package/build/services/compiler/deployer.js +1 -0
  16. package/build/services/engine/index.js +1 -0
  17. package/build/services/exporter/index.js +1 -1
  18. package/build/services/meshcall/index.d.ts +5 -5
  19. package/build/services/meshcall/index.js +45 -32
  20. package/build/services/meshcall/schemas/factory.js +2 -2
  21. package/build/services/meshdata/index.d.ts +2 -2
  22. package/build/services/meshdata/index.js +25 -20
  23. package/build/services/meshflow/client.js +3 -8
  24. package/build/services/meshflow/schemas/factory.d.ts +1 -1
  25. package/build/services/meshflow/schemas/factory.js +56 -10
  26. package/build/services/meshflow/worker.js +3 -5
  27. package/build/services/meshflow/workflow.js +10 -12
  28. package/build/services/router/index.js +3 -1
  29. package/build/services/store/clients/ioredis.d.ts +1 -0
  30. package/build/services/store/clients/ioredis.js +4 -0
  31. package/build/services/store/clients/redis.d.ts +1 -0
  32. package/build/services/store/clients/redis.js +5 -0
  33. package/build/services/store/index.d.ts +2 -3
  34. package/build/services/store/index.js +5 -36
  35. package/build/services/task/index.d.ts +1 -1
  36. package/build/services/task/index.js +3 -6
  37. package/build/types/activity.d.ts +2 -0
  38. package/build/types/error.d.ts +1 -0
  39. package/build/types/hook.d.ts +1 -0
  40. package/build/types/hotmesh.d.ts +1 -0
  41. package/build/types/job.d.ts +1 -1
  42. package/build/types/meshcall.d.ts +6 -2
  43. package/build/types/meshdata.d.ts +1 -1
  44. package/build/types/meshflow.d.ts +3 -0
  45. package/package.json +1 -1
  46. package/types/activity.ts +3 -1
  47. package/types/error.ts +1 -0
  48. package/types/hook.ts +6 -1
  49. package/types/hotmesh.ts +35 -0
  50. package/types/job.ts +4 -14
  51. package/types/meshcall.ts +19 -2
  52. package/types/meshdata.ts +9 -5
  53. 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
- [SDK Docs](https://hotmeshio.github.io/sdk-typescript/) | [Samples](https://github.com/hotmeshio/samples-typescript) | [Intro Video](https://www.loom.com/share/211bd4b4038d42f0ba34374ef5b6f961?sid=7b889a56-f60f-4ccc-84e7-8c2697e548a9)
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 functions to the Redis-backed mesh, exposing them 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.
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.*
@@ -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;
@@ -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;
@@ -39,6 +39,7 @@ declare class MeshFlowChildError extends Error {
39
39
  backoffCoefficient: number;
40
40
  code: number;
41
41
  expire: number;
42
+ persistent: boolean;
42
43
  signalIn: boolean;
43
44
  workflowDimension: string;
44
45
  index: number;
@@ -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;
@@ -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;
@@ -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;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -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
- let emit = false;
455
- if (this.config.emit) {
456
- emit = pipe_1.Pipe.resolve(this.config.emit, this.context);
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 > 0,
477
+ emit: !this.isJobComplete(jobStatus) && !this.shouldPersistJob(),
461
478
  });
462
479
  }
463
- if (adjacencyList.length && jobStatus > 0) {
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
- this.logger.error('duplicate-job-error', { job_id: error.jobId });
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();
@@ -125,6 +125,7 @@ class Deployer {
125
125
  activities[activityKey].publishes = graph.publishes;
126
126
  }
127
127
  activities[activityKey].expire = graph.expire ?? undefined;
128
+ activities[activityKey].persistent = graph.persistent ?? undefined;
128
129
  }
129
130
  }
130
131
  }
@@ -92,6 +92,7 @@ class EngineService {
92
92
  reclaimDelay: config.engine.reclaimDelay,
93
93
  reclaimCount: config.engine.reclaimCount,
94
94
  throttle,
95
+ readonly: config.engine.readonly,
95
96
  }, this.stream, this.store, this.logger);
96
97
  }
97
98
  async fetchAndVerifyVID(vid, count = 0) {
@@ -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 = await this.store.getDependencies(jobId);
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(workers: Map<string, HotMesh | Promise<HotMesh>>, namespace: string, config: RedisConfig): Promise<HotMesh | void>;
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<void>;
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(workers, namespace = key_1.HMNS, config) {
18
- for (const [id, hotMeshInstance] of workers) {
19
- if ((await hotMeshInstance).namespace === namespace) {
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
- return hotMeshInstance;
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 = await MeshCall.findFirstMatching(MeshCall.workers, namespace, redis);
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, ms_1.default)(params.options.ttl) / 1000;
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
- try {
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
- const TOPIC = `${params.namespace ?? key_1.HMNS}.cron`;
144
- let delay = params.options.delay
145
- ? (0, ms_1.default)(params.options.delay) / 1000
146
- : undefined;
147
- let cron;
148
- let interval = enums_1.HMSH_FIDELITY_SECONDS;
149
- if ((0, utils_1.isValidCron)(params.options.interval)) {
150
- cron = params.options.interval;
151
- delay = Math.max(new cron_1.CronHandler().nextDelay(cron), 0);
152
- }
153
- else {
154
- const seconds = (0, ms_1.default)(params.options.interval) / 1000;
155
- interval = Math.max(seconds, enums_1.HMSH_FIDELITY_SECONDS);
156
- }
157
- const maxCycles = params.options.maxCycles ?? 1000000;
158
- const hotMeshInstance = await MeshCall.getInstance(params.namespace, params.redis);
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
- await hotMeshInstance.interrupt(`${params.namespace ?? key_1.HMNS}.cron`, params.options.id, { throw: false, expire: 60 });
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, ConnectOptions, ExecInput, HookInput } from '../../types/meshdata';
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[], options: ConnectOptions, callOptions?: CallOptions): StringAnyType;
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>;