@hotmeshio/hotmesh 0.2.4 → 0.3.1

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 (52) 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 +27 -8
  11. package/build/services/activities/await.js +10 -0
  12. package/build/services/activities/cycle.js +10 -0
  13. package/build/services/activities/hook.js +13 -2
  14. package/build/services/activities/interrupt.js +10 -0
  15. package/build/services/activities/signal.js +10 -0
  16. package/build/services/activities/trigger.js +15 -5
  17. package/build/services/activities/worker.js +10 -0
  18. package/build/services/collator/index.d.ts +2 -0
  19. package/build/services/collator/index.js +12 -0
  20. package/build/services/compiler/deployer.js +1 -0
  21. package/build/services/exporter/index.js +1 -1
  22. package/build/services/meshcall/index.js +3 -9
  23. package/build/services/meshdata/index.d.ts +2 -2
  24. package/build/services/meshdata/index.js +25 -20
  25. package/build/services/meshflow/client.js +3 -8
  26. package/build/services/meshflow/schemas/factory.d.ts +1 -1
  27. package/build/services/meshflow/schemas/factory.js +56 -10
  28. package/build/services/meshflow/worker.js +5 -6
  29. package/build/services/meshflow/workflow.js +10 -12
  30. package/build/services/store/clients/ioredis.d.ts +1 -0
  31. package/build/services/store/clients/ioredis.js +4 -0
  32. package/build/services/store/clients/redis.d.ts +1 -0
  33. package/build/services/store/clients/redis.js +5 -0
  34. package/build/services/store/index.d.ts +2 -3
  35. package/build/services/store/index.js +5 -36
  36. package/build/services/task/index.d.ts +1 -1
  37. package/build/services/task/index.js +3 -6
  38. package/build/types/activity.d.ts +2 -0
  39. package/build/types/error.d.ts +1 -0
  40. package/build/types/hook.d.ts +1 -0
  41. package/build/types/hotmesh.d.ts +1 -0
  42. package/build/types/job.d.ts +1 -1
  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/meshdata.ts +9 -5
  52. package/types/meshflow.ts +12 -1
@@ -33,6 +33,7 @@ const cache_1 = require("./cache");
33
33
  class StoreService {
34
34
  constructor(redisClient) {
35
35
  this.commands = {
36
+ get: 'get',
36
37
  set: 'set',
37
38
  setnx: 'setnx',
38
39
  del: 'del',
@@ -355,27 +356,6 @@ class StoreService {
355
356
  return await this.redisClient[this.commands.hset](key, payload);
356
357
  }
357
358
  async registerJobDependency(depType, originJobId, topic, jobId, gId, pd = '', multi) {
358
- const privateMulti = multi || this.getMulti();
359
- const dependencyParams = {
360
- appId: this.appId,
361
- jobId: originJobId,
362
- };
363
- const depKey = this.mintKey(key_1.KeyType.JOB_DEPENDENTS, dependencyParams);
364
- const expireTask = [depType, topic, gId, pd, jobId].join(key_1.VALSEP);
365
- privateMulti[this.commands.rpush](depKey, expireTask);
366
- if (!multi) {
367
- return await privateMulti.exec();
368
- }
369
- }
370
- async registerSignalDependency(jobId, signalKey, dad, multi) {
371
- const privateMulti = multi || this.getMulti();
372
- const dependencyParams = { appId: this.appId, jobId };
373
- const dependencyKey = this.mintKey(key_1.KeyType.JOB_DEPENDENTS, dependencyParams);
374
- const delistTask = ['delist', 'signal', jobId, dad, signalKey].join(key_1.VALSEP);
375
- privateMulti[this.commands.rpush](dependencyKey, delistTask);
376
- if (!multi) {
377
- return await privateMulti.exec();
378
- }
379
359
  }
380
360
  async setStats(jobKey, jobId, dateTime, stats, appVersion, multi) {
381
361
  const params = {
@@ -561,7 +541,7 @@ class StoreService {
561
541
  appId: this.appId,
562
542
  jobId,
563
543
  });
564
- return await (multi || this.redisClient)[this.commands.hincrbyfloat](jobKey, guid, amount);
544
+ return await (multi || this.redisClient)[this.commands.hincrbyfloat](jobKey, guid, amount.toString());
565
545
  }
566
546
  async setStateNX(jobId, appId, status) {
567
547
  const hashKey = this.mintKey(key_1.KeyType.JOB_STATE, { appId, jobId });
@@ -718,19 +698,16 @@ class StoreService {
718
698
  const key = this.mintKey(key_1.KeyType.SIGNALS, { appId: this.appId });
719
699
  const { topic, resolved, jobId } = hook;
720
700
  const signalKey = `${topic}:${resolved}`;
721
- const payload = { [signalKey]: jobId };
722
- await (multi || this.redisClient)[this.commands.hset](key, payload);
723
- const [_aid, dad, _gid, jid] = jobId.split(key_1.VALSEP);
724
- return await this.registerSignalDependency(jid, signalKey, dad, multi);
701
+ await this.setnxex(`${key}:${signalKey}`, jobId, Math.max(hook.expire, enums_1.HMSH_SIGNAL_EXPIRE));
725
702
  }
726
703
  async getHookSignal(topic, resolved) {
727
704
  const key = this.mintKey(key_1.KeyType.SIGNALS, { appId: this.appId });
728
- const response = await this.redisClient[this.commands.hget](key, `${topic}:${resolved}`);
705
+ const response = await this.redisClient[this.commands.get](`${key}:${topic}:${resolved}`);
729
706
  return response ? response.toString() : undefined;
730
707
  }
731
708
  async deleteHookSignal(topic, resolved) {
732
709
  const key = this.mintKey(key_1.KeyType.SIGNALS, { appId: this.appId });
733
- const response = await this.redisClient[this.commands.hdel](key, `${topic}:${resolved}`);
710
+ const response = await this.redisClient[this.commands.del](`${key}:${topic}:${resolved}`);
734
711
  return response ? Number(response) : undefined;
735
712
  }
736
713
  async addTaskQueues(keys) {
@@ -779,14 +756,6 @@ class StoreService {
779
756
  await (redisMulti || this.redisClient)[this.commands.expire](jobKey, inSeconds);
780
757
  }
781
758
  }
782
- async registerDependenciesForCleanup(jobId, deletionTime, options) {
783
- const depParams = { appId: this.appId, jobId };
784
- const depKey = this.mintKey(key_1.KeyType.JOB_DEPENDENTS, depParams);
785
- const context = options.interrupt ? 'INTERRUPT' : 'EXPIRE';
786
- const depKeyContext = `${key_1.TYPSEP}${context}${key_1.TYPSEP}${depKey}`;
787
- const zsetKey = this.mintKey(key_1.KeyType.TIME_RANGE, { appId: this.appId });
788
- await this.zAdd(zsetKey, deletionTime.toString(), depKeyContext);
789
- }
790
759
  async getDependencies(jobId) {
791
760
  const depParams = { appId: this.appId, jobId };
792
761
  const depKey = this.mintKey(key_1.KeyType.JOB_DEPENDENTS, depParams);
@@ -20,7 +20,7 @@ declare class TaskService {
20
20
  processTimeHooks(timeEventCallback: (jobId: string, gId: string, activityId: string, type: WorkListTaskType) => Promise<void>, listKey?: string): Promise<void>;
21
21
  cancelCleanup(): void;
22
22
  getHookRule(topic: string): Promise<HookRule | undefined>;
23
- registerWebHook(topic: string, context: JobState, dad: string, multi?: RedisMulti): Promise<string>;
23
+ registerWebHook(topic: string, context: JobState, dad: string, expire: number, multi?: RedisMulti): Promise<string>;
24
24
  processWebHookSignal(topic: string, data: Record<string, unknown>): Promise<[string, string, string, string] | undefined>;
25
25
  deleteWebHookSignal(topic: string, data: Record<string, unknown>): Promise<number>;
26
26
  }
@@ -36,10 +36,6 @@ class TaskService {
36
36
  async registerJobForCleanup(jobId, inSeconds = enums_1.HMSH_EXPIRE_DURATION, options) {
37
37
  if (inSeconds > 0) {
38
38
  await this.store.expireJob(jobId, inSeconds);
39
- const fromNow = Date.now() + inSeconds * 1000;
40
- const fidelityMS = enums_1.HMSH_FIDELITY_SECONDS * 1000;
41
- const timeSlot = Math.floor(fromNow / fidelityMS) * fidelityMS;
42
- await this.store.registerDependenciesForCleanup(jobId, timeSlot, options);
43
39
  }
44
40
  }
45
41
  async registerTimeHook(jobId, gId, activityId, type, inSeconds = enums_1.HMSH_FIDELITY_SECONDS, dad, multi) {
@@ -73,7 +69,7 @@ class TaskService {
73
69
  const key = this.store.mintKey(hotmesh_1.KeyType.SIGNALS, {
74
70
  appId: this.store.appId,
75
71
  });
76
- await this.store.redisClient[this.store.commands.hdel](key, target);
72
+ await this.store.redisClient[this.store.commands.del](`${key}:${target}`);
77
73
  }
78
74
  else {
79
75
  await timeEventCallback(target, gId, activityId, type);
@@ -120,7 +116,7 @@ class TaskService {
120
116
  const rules = await this.store.getHookRules();
121
117
  return rules?.[topic]?.[0];
122
118
  }
123
- async registerWebHook(topic, context, dad, multi) {
119
+ async registerWebHook(topic, context, dad, expire, multi) {
124
120
  const hookRule = await this.getHookRule(topic);
125
121
  if (hookRule) {
126
122
  const mapExpression = hookRule.conditions.match[0].expected;
@@ -133,6 +129,7 @@ class TaskService {
133
129
  topic,
134
130
  resolved,
135
131
  jobId: compositeJobKey,
132
+ expire,
136
133
  };
137
134
  await this.store.setHookSignal(hook, multi);
138
135
  return jobId;
@@ -17,6 +17,8 @@ interface BaseActivity {
17
17
  emit?: boolean;
18
18
  sleep?: number;
19
19
  expire?: number;
20
+ persistent?: boolean;
21
+ persist?: boolean;
20
22
  retry?: StreamRetryPolicy;
21
23
  cycle?: boolean;
22
24
  collationInt?: number;
@@ -4,6 +4,7 @@ export type MeshFlowChildErrorType = {
4
4
  backoffCoefficient?: number;
5
5
  index: number;
6
6
  expire?: number;
7
+ persistent?: boolean;
7
8
  signalIn?: boolean;
8
9
  maximumAttempts?: number;
9
10
  maximumInterval?: number;
@@ -21,6 +21,7 @@ type HookSignal = {
21
21
  topic: string;
22
22
  resolved: string;
23
23
  jobId: string;
24
+ expire: number;
24
25
  };
25
26
  interface HookInterface {
26
27
  (topic: string, data: {
@@ -76,6 +76,7 @@ type HotMeshGraph = {
76
76
  subscribes: string;
77
77
  publishes?: string;
78
78
  expire?: number;
79
+ persistent?: boolean;
79
80
  output?: {
80
81
  schema: Record<string, any>;
81
82
  };
@@ -31,12 +31,12 @@ type JobMetadata = {
31
31
  trc: string;
32
32
  err?: string;
33
33
  expire?: number;
34
+ persistent?: boolean;
34
35
  };
35
36
  type ExtensionType = {
36
37
  search?: StringStringType;
37
38
  marker?: StringStringType;
38
39
  pending?: number;
39
- statusThreshold?: number;
40
40
  };
41
41
  type JobStatus = number;
42
42
  type JobState = {
@@ -15,7 +15,6 @@ export type CallOptions = {
15
15
  pending?: number;
16
16
  };
17
17
  export type ConnectOptions = {
18
- ttl?: string;
19
18
  taskQueue?: string;
20
19
  prefix?: string;
21
20
  namespace?: string;
@@ -56,4 +55,5 @@ export type MeshDataWorkflowOptions = {
56
55
  pending?: number;
57
56
  expire?: number;
58
57
  signalIn?: boolean;
58
+ persistent?: boolean;
59
59
  };
@@ -27,6 +27,7 @@ type WorkflowContext = {
27
27
  workflowSpan: string;
28
28
  raw: StreamData;
29
29
  connection: Connection;
30
+ expire?: number;
30
31
  };
31
32
  export type WorkflowSearchSchema = Record<string, {
32
33
  type: 'TEXT' | 'NUMERIC' | 'TAG';
@@ -68,6 +69,7 @@ type WorkflowOptions = {
68
69
  marker?: StringStringType;
69
70
  config?: WorkflowConfig;
70
71
  expire?: number;
72
+ persistent?: boolean;
71
73
  signalIn?: boolean;
72
74
  await?: boolean;
73
75
  pending?: number;
@@ -102,6 +104,7 @@ type WorkflowDataType = {
102
104
  workflowDimension?: string;
103
105
  originJobId?: string;
104
106
  canRetry?: boolean;
107
+ expire?: number;
105
108
  };
106
109
  type ConnectionConfig = {
107
110
  class: Partial<RedisClass>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.2.4",
3
+ "version": "0.3.1",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
package/types/activity.ts CHANGED
@@ -28,7 +28,9 @@ interface BaseActivity {
28
28
  telemetry?: Record<string, any>;
29
29
  emit?: boolean; //if true, the activity will emit a message to the `publishes` topic immediately before transitioning to adjacent activities
30
30
  sleep?: number; //@pipe /in seconds
31
- expire?: number; //-1 forever; 0 persists the flow until the parent flow that expired it is dismissed; 15 seconds is the default
31
+ expire?: number; //-1 forever; 0 persists the flow until the parent flow that expired it is dismissed; 15 seconds is the default (copied from the YAML at compile time)
32
+ persistent?: boolean; //if true, the job will persist beyond completion (copied from the YAML at compile time)
33
+ persist?: boolean; //when true, the activity will emit the job completed event and start the `persistence countdown`, using the 'expire' value
32
34
  retry?: StreamRetryPolicy;
33
35
  cycle?: boolean; //if true, the `notary` will leave leg 2 open, so it can be re/cycled
34
36
  collationInt?: number; //compiler
package/types/error.ts CHANGED
@@ -4,6 +4,7 @@ export type MeshFlowChildErrorType = {
4
4
  backoffCoefficient?: number;
5
5
  index: number;
6
6
  expire?: number;
7
+ persistent?: boolean;
7
8
  signalIn?: boolean;
8
9
  maximumAttempts?: number;
9
10
  maximumInterval?: number;
package/types/hook.ts CHANGED
@@ -22,7 +22,12 @@ interface HookRules {
22
22
  [eventName: string]: HookRule[];
23
23
  }
24
24
 
25
- type HookSignal = { topic: string; resolved: string; jobId: string };
25
+ type HookSignal = {
26
+ topic: string;
27
+ resolved: string;
28
+ jobId: string;
29
+ expire: number;
30
+ };
26
31
 
27
32
  interface HookInterface {
28
33
  (topic: string, data: { [key: string]: any; id: string }): Promise<void>;
package/types/hotmesh.ts CHANGED
@@ -88,17 +88,52 @@ type HotMeshConfig = {
88
88
  };
89
89
 
90
90
  type HotMeshGraph = {
91
+ /**
92
+ * the unique topic that the graph subscribes to, creating one
93
+ * job for each idempotent message that is received
94
+ */
91
95
  subscribes: string;
96
+ /**
97
+ * the unique topic that the graph publishes/emits to when the job completes
98
+ */
92
99
  publishes?: string;
100
+ /**
101
+ * the number of seconds that the completed job should be
102
+ * left in the store before it is deleted
103
+ */
93
104
  expire?: number;
105
+ /**
106
+ * if the graph is reentrant and has open activities, the
107
+ * `persistent` flag will emit the job completed event.
108
+ * This allows the 'main' thread/trigger that started the job to
109
+ * signal to subscribers (or the parent) that the job
110
+ * is 'done', while still leaving the job in a
111
+ * state that allows for reentry (such as cyclical hooks).
112
+ */
113
+ persistent?: boolean;
114
+ /**
115
+ * the schema for the output of the graph
116
+ */
94
117
  output?: {
95
118
  schema: Record<string, any>;
96
119
  };
120
+ /**
121
+ * the schema for the input of the graph
122
+ */
97
123
  input?: {
98
124
  schema: Record<string, any>;
99
125
  };
126
+ /**
127
+ * the activities that define the graph
128
+ */
100
129
  activities: Record<string, any>;
130
+ /**
131
+ * the transitions that define how activities are connected
132
+ */
101
133
  transitions?: Record<string, any>;
134
+ /**
135
+ * the reentrant hook rules that define how to reenter a running graph
136
+ */
102
137
  hooks?: HookRules;
103
138
  };
104
139
 
package/types/job.ts CHANGED
@@ -63,6 +63,7 @@ type JobMetadata = {
63
63
  /** GMT updated //job_updated */
64
64
  ju: string;
65
65
 
66
+ /** job status semaphore */
66
67
  js: JobStatus;
67
68
 
68
69
  /** activity_type */
@@ -82,6 +83,9 @@ type JobMetadata = {
82
83
 
83
84
  /** process data expire policy */
84
85
  expire?: number;
86
+
87
+ /** job persistence (beyond main thread completion) */
88
+ persistent?: boolean;
85
89
  };
86
90
 
87
91
  /**
@@ -122,20 +126,6 @@ type ExtensionType = {
122
126
  * will be added after the job is resumed if relevant.
123
127
  */
124
128
  pending?: number;
125
-
126
- /**
127
- * Workflows that apply a status threshold will be initialized
128
- * with a status value of 1m - statusThreshold.
129
- *
130
- * The value provided should be the count of descendant activities
131
- * (those that descend from the trigger) that should be allowed to
132
- * remain open once 'done' event is emitted.
133
- *
134
- * If the job should not be removed from the cache, the `expire` field
135
- * should be set to `0`.
136
- *
137
- */
138
- statusThreshold?: number;
139
129
  };
140
130
 
141
131
  /**
package/types/meshdata.ts CHANGED
@@ -63,10 +63,6 @@ export type CallOptions = {
63
63
  };
64
64
 
65
65
  export type ConnectOptions = {
66
- /**
67
- * if set to infinity, callers may not override (the function will be durable)
68
- */
69
- ttl?: string;
70
66
  /**
71
67
  * the task queue for the connected function for greater specificity
72
68
  */
@@ -265,8 +261,16 @@ export type MeshDataWorkflowOptions = {
265
261
  expire?: number;
266
262
 
267
263
  /**
268
- * set to false to optimize workflows that do not require a `signal in`
264
+ * set to false to optimize workflows that do not require a `signal in`.
265
+ * explicitly set to true to force the workflow to remain open and persistent.
269
266
  * @default true
270
267
  */
271
268
  signalIn?: boolean;
269
+
270
+ /**
271
+ * set to `true` by the system when a workflow is started with a ttl, ensuring
272
+ * that the workflow will remain active until the ttl expires and the workflow
273
+ * is scrubbed. This is a system flag and should not be set by the user.
274
+ */
275
+ persistent?: boolean;
272
276
  };
package/types/meshflow.ts CHANGED
@@ -113,6 +113,11 @@ type WorkflowContext = {
113
113
  * the HotMesh connection configuration (io/redis NPM package reference and login credentials)
114
114
  */
115
115
  connection: Connection;
116
+
117
+ /**
118
+ * if present, the workflow will delay expiration for the specified number of seconds
119
+ */
120
+ expire?: number;
116
121
  };
117
122
 
118
123
  /**
@@ -316,6 +321,11 @@ type WorkflowOptions = {
316
321
  */
317
322
  expire?: number;
318
323
 
324
+ /**
325
+ * system flag to indicate that the flow should remain open beyond main method completion while still emitting the 'job done' event
326
+ */
327
+ persistent?: boolean;
328
+
319
329
  /**
320
330
  * default is true; set to false to optimize workflows that do not require a `signal in`
321
331
  */
@@ -417,8 +427,9 @@ type WorkflowDataType = {
417
427
  workflowId: string;
418
428
  workflowTopic: string;
419
429
  workflowDimension?: string; //is present if hook (not main workflow)
420
- originJobId?: string; //is present if there is an originating ancestor job (should rename to originJobId)
430
+ originJobId?: string; //is present if there is an originating ancestor job
421
431
  canRetry?: boolean;
432
+ expire?: number;
422
433
  };
423
434
 
424
435
  type ConnectionConfig = {