@hotmeshio/hotmesh 0.0.52 → 0.0.54

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 (134) hide show
  1. package/README.md +22 -18
  2. package/build/index.d.ts +1 -2
  3. package/build/index.js +1 -3
  4. package/build/modules/enums.d.ts +8 -3
  5. package/build/modules/enums.js +16 -8
  6. package/build/modules/errors.d.ts +58 -20
  7. package/build/modules/errors.js +90 -33
  8. package/build/package.json +7 -2
  9. package/build/services/activities/activity.d.ts +8 -0
  10. package/build/services/activities/activity.js +63 -14
  11. package/build/services/activities/await.js +6 -6
  12. package/build/services/activities/cycle.d.ts +2 -2
  13. package/build/services/activities/cycle.js +5 -5
  14. package/build/services/activities/hook.js +9 -5
  15. package/build/services/activities/interrupt.d.ts +3 -3
  16. package/build/services/activities/interrupt.js +15 -6
  17. package/build/services/activities/signal.d.ts +2 -2
  18. package/build/services/activities/signal.js +4 -4
  19. package/build/services/activities/trigger.d.ts +5 -2
  20. package/build/services/activities/trigger.js +34 -4
  21. package/build/services/activities/worker.js +6 -6
  22. package/build/services/compiler/deployer.js +33 -5
  23. package/build/services/compiler/validator.d.ts +2 -0
  24. package/build/services/compiler/validator.js +5 -1
  25. package/build/services/durable/client.d.ts +7 -1
  26. package/build/services/durable/client.js +57 -38
  27. package/build/services/durable/exporter.d.ts +27 -81
  28. package/build/services/durable/exporter.js +153 -325
  29. package/build/services/durable/handle.d.ts +13 -8
  30. package/build/services/durable/handle.js +61 -48
  31. package/build/services/durable/index.d.ts +0 -2
  32. package/build/services/durable/index.js +0 -2
  33. package/build/services/durable/schemas/factory.d.ts +33 -0
  34. package/build/services/durable/schemas/factory.js +2356 -0
  35. package/build/services/durable/search.js +8 -8
  36. package/build/services/durable/worker.js +117 -25
  37. package/build/services/durable/workflow.d.ts +67 -52
  38. package/build/services/durable/workflow.js +322 -306
  39. package/build/services/engine/index.d.ts +2 -2
  40. package/build/services/engine/index.js +5 -2
  41. package/build/services/exporter/index.d.ts +2 -4
  42. package/build/services/exporter/index.js +4 -5
  43. package/build/services/hotmesh/index.d.ts +2 -2
  44. package/build/services/hotmesh/index.js +2 -2
  45. package/build/services/mapper/index.d.ts +6 -2
  46. package/build/services/mapper/index.js +6 -2
  47. package/build/services/pipe/functions/array.d.ts +2 -10
  48. package/build/services/pipe/functions/array.js +30 -28
  49. package/build/services/pipe/functions/conditional.d.ts +1 -0
  50. package/build/services/pipe/functions/conditional.js +3 -0
  51. package/build/services/pipe/functions/date.d.ts +1 -0
  52. package/build/services/pipe/functions/date.js +4 -0
  53. package/build/services/pipe/functions/index.d.ts +2 -0
  54. package/build/services/pipe/functions/index.js +2 -0
  55. package/build/services/pipe/functions/logical.d.ts +5 -0
  56. package/build/services/pipe/functions/logical.js +12 -0
  57. package/build/services/pipe/functions/object.d.ts +3 -0
  58. package/build/services/pipe/functions/object.js +25 -7
  59. package/build/services/pipe/index.d.ts +20 -3
  60. package/build/services/pipe/index.js +82 -16
  61. package/build/services/router/index.js +14 -3
  62. package/build/services/serializer/index.d.ts +3 -2
  63. package/build/services/serializer/index.js +11 -4
  64. package/build/services/store/clients/ioredis.js +6 -6
  65. package/build/services/store/clients/redis.js +7 -7
  66. package/build/services/store/index.d.ts +2 -0
  67. package/build/services/store/index.js +4 -1
  68. package/build/services/stream/clients/ioredis.js +8 -8
  69. package/build/services/stream/clients/redis.js +1 -1
  70. package/build/types/activity.d.ts +60 -5
  71. package/build/types/durable.d.ts +183 -36
  72. package/build/types/error.d.ts +48 -0
  73. package/build/types/error.js +2 -0
  74. package/build/types/exporter.d.ts +35 -7
  75. package/build/types/index.d.ts +4 -3
  76. package/build/types/job.d.ts +93 -6
  77. package/build/types/pipe.d.ts +81 -3
  78. package/build/types/stream.d.ts +61 -1
  79. package/build/types/stream.js +4 -0
  80. package/index.ts +1 -2
  81. package/modules/enums.ts +16 -8
  82. package/modules/errors.ts +139 -34
  83. package/package.json +7 -2
  84. package/services/activities/activity.ts +63 -14
  85. package/services/activities/await.ts +6 -6
  86. package/services/activities/cycle.ts +7 -6
  87. package/services/activities/hook.ts +12 -5
  88. package/services/activities/interrupt.ts +19 -9
  89. package/services/activities/signal.ts +6 -5
  90. package/services/activities/trigger.ts +43 -6
  91. package/services/activities/worker.ts +7 -7
  92. package/services/compiler/deployer.ts +33 -6
  93. package/services/compiler/validator.ts +7 -3
  94. package/services/durable/client.ts +49 -22
  95. package/services/durable/exporter.ts +162 -349
  96. package/services/durable/handle.ts +66 -53
  97. package/services/durable/index.ts +0 -2
  98. package/services/durable/schemas/factory.ts +2358 -0
  99. package/services/durable/search.ts +8 -8
  100. package/services/durable/worker.ts +128 -29
  101. package/services/durable/workflow.ts +371 -322
  102. package/services/engine/index.ts +8 -3
  103. package/services/exporter/index.ts +10 -12
  104. package/services/hotmesh/index.ts +4 -3
  105. package/services/mapper/index.ts +6 -2
  106. package/services/pipe/functions/array.ts +24 -37
  107. package/services/pipe/functions/conditional.ts +4 -0
  108. package/services/pipe/functions/date.ts +6 -0
  109. package/services/pipe/functions/index.ts +7 -5
  110. package/services/pipe/functions/logical.ts +11 -0
  111. package/services/pipe/functions/object.ts +26 -7
  112. package/services/pipe/index.ts +99 -21
  113. package/services/quorum/index.ts +1 -3
  114. package/services/router/index.ts +14 -3
  115. package/services/serializer/index.ts +12 -5
  116. package/services/store/clients/ioredis.ts +6 -6
  117. package/services/store/clients/redis.ts +7 -7
  118. package/services/store/index.ts +4 -1
  119. package/services/stream/clients/ioredis.ts +8 -8
  120. package/services/stream/clients/redis.ts +1 -1
  121. package/types/activity.ts +87 -15
  122. package/types/durable.ts +263 -75
  123. package/types/error.ts +52 -0
  124. package/types/exporter.ts +43 -9
  125. package/types/index.ts +14 -8
  126. package/types/job.ts +157 -36
  127. package/types/pipe.ts +84 -3
  128. package/types/stream.ts +82 -23
  129. package/build/services/durable/factory.d.ts +0 -17
  130. package/build/services/durable/factory.js +0 -817
  131. package/build/services/durable/meshos.d.ts +0 -127
  132. package/build/services/durable/meshos.js +0 -380
  133. package/services/durable/factory.ts +0 -818
  134. package/services/durable/meshos.ts +0 -441
@@ -2,9 +2,11 @@ import {
2
2
  GenerationalError,
3
3
  GetStateError,
4
4
  InactiveJobError } from '../../modules/errors';
5
+ import { guid } from '../../modules/utils';
5
6
  import { Activity } from './activity';
6
7
  import { CollatorService } from '../collator';
7
8
  import { EngineService } from '../engine';
9
+ import { Pipe } from '../pipe';
8
10
  import { TelemetryService } from '../telemetry';
9
11
  import {
10
12
  ActivityData,
@@ -14,8 +16,6 @@ import {
14
16
  import { JobState } from '../../types/job';
15
17
  import { MultiResponseFlags, RedisMulti } from '../../types/redis';
16
18
  import { StreamData, StreamDataType } from '../../types/stream';
17
- import { Pipe } from '../pipe';
18
- import { guid } from '../../modules/utils';
19
19
 
20
20
  class Await extends Activity {
21
21
  config: AwaitActivity;
@@ -60,16 +60,16 @@ class Await extends Activity {
60
60
  return this.context.metadata.aid;
61
61
  } catch (error) {
62
62
  if (error instanceof InactiveJobError) {
63
- this.logger.error('await-inactive-job-error', { error });
63
+ this.logger.error('await-inactive-job-error', { ...error });
64
64
  return;
65
65
  } else if (error instanceof GenerationalError) {
66
- this.logger.info('process-event-generational-job-error', { error });
66
+ this.logger.info('process-event-generational-job-error', { ...error });
67
67
  return;
68
68
  } else if (error instanceof GetStateError) {
69
- this.logger.error('await-get-state-error', { error });
69
+ this.logger.error('await-get-state-error', { ...error });
70
70
  return;
71
71
  } else {
72
- this.logger.error('await-process-error', { error });
72
+ this.logger.error('await-process-error', { ...error });
73
73
  }
74
74
  telemetry.setActivityError(error.message);
75
75
  throw error;
@@ -2,18 +2,19 @@ import {
2
2
  GenerationalError,
3
3
  GetStateError,
4
4
  InactiveJobError } from '../../modules/errors';
5
+ import { guid } from '../../modules/utils';
5
6
  import { CollatorService } from '../collator';
6
7
  import { EngineService } from '../engine';
7
- import { Activity, ActivityType } from './activity';
8
+ import { Activity } from './activity';
8
9
  import {
9
10
  ActivityData,
10
11
  ActivityMetadata,
12
+ ActivityType,
11
13
  CycleActivity } from '../../types/activity';
12
14
  import { JobState } from '../../types/job';
13
15
  import { MultiResponseFlags, RedisMulti } from '../../types/redis';
14
16
  import { StreamData } from '../../types/stream';
15
17
  import { TelemetryService } from '../telemetry';
16
- import { guid } from '../../modules/utils';
17
18
 
18
19
  class Cycle extends Activity {
19
20
  config: CycleActivity;
@@ -63,16 +64,16 @@ class Cycle extends Activity {
63
64
  return this.context.metadata.aid;
64
65
  } catch (error) {
65
66
  if (error instanceof InactiveJobError) {
66
- this.logger.error('cycle-inactive-job-error', { error });
67
+ this.logger.error('cycle-inactive-job-error', { ...error });
67
68
  return;
68
69
  } else if (error instanceof GenerationalError) {
69
- this.logger.info('process-event-generational-job-error', { error });
70
+ this.logger.info('process-event-generational-job-error', { ...error });
70
71
  return;
71
72
  } else if (error instanceof GetStateError) {
72
- this.logger.error('cycle-get-state-error', { error });
73
+ this.logger.error('cycle-get-state-error', { ...error });
73
74
  return;
74
75
  } else {
75
- this.logger.error('cycle-process-error', { error });
76
+ this.logger.error('cycle-process-error', { ...error });
76
77
  }
77
78
  telemetry.setActivityError(error.message);
78
79
  throw error;
@@ -63,16 +63,16 @@ class Hook extends Activity {
63
63
  return this.context.metadata.aid;
64
64
  } catch (error) {
65
65
  if (error instanceof InactiveJobError) {
66
- this.logger.error('hook-inactive-job-error', { error });
66
+ this.logger.error('hook-inactive-job-error', { ...error });
67
67
  return;
68
68
  } else if (error instanceof GenerationalError) {
69
- this.logger.info('process-event-generational-job-error', { error });
69
+ this.logger.info('process-event-generational-job-error', { ...error });
70
70
  return;
71
71
  } else if (error instanceof GetStateError) {
72
- this.logger.error('hook-get-state-error', { error });
72
+ this.logger.error('hook-get-state-error', { ...error });
73
73
  return;
74
74
  } else {
75
- this.logger.error('hook-process-error', { error });
75
+ this.logger.error('hook-process-error', { ...error });
76
76
  }
77
77
  telemetry.setActivityError(error.message);
78
78
  throw error;
@@ -86,7 +86,14 @@ class Hook extends Activity {
86
86
  * does this activity use a time-hook or web-hook
87
87
  */
88
88
  doesHook(): boolean {
89
- return !!(this.config.hook?.topic || this.config.sleep);
89
+ if (this.config.sleep) {
90
+ const duration = Pipe.resolve(
91
+ this.config.sleep,
92
+ this.context,
93
+ );
94
+ return !isNaN(duration) && Number(duration) > 0
95
+ }
96
+ return !!this.config.hook?.topic;
90
97
  }
91
98
 
92
99
  async doHook(telemetry: TelemetryService) {
@@ -2,17 +2,18 @@ import {
2
2
  GenerationalError,
3
3
  GetStateError,
4
4
  InactiveJobError } from '../../modules/errors';
5
+ import { CollatorService } from '../collator';
6
+ import { Activity } from './activity';
5
7
  import { EngineService } from '../engine';
6
- import { Activity, ActivityType } from './activity';
8
+ import { Pipe } from '../pipe';
9
+ import { TelemetryService } from '../telemetry';
7
10
  import {
8
11
  ActivityData,
9
12
  ActivityMetadata,
13
+ ActivityType,
10
14
  InterruptActivity } from '../../types/activity';
11
- import { MultiResponseFlags } from '../../types';
12
- import { CollatorService } from '../collator';
13
15
  import { JobInterruptOptions, JobState } from '../../types/job';
14
- import { TelemetryService } from '../telemetry';
15
- import { Pipe } from '../pipe';
16
+ import { MultiResponseFlags } from '../../types/redis';
16
17
 
17
18
  class Interrupt extends Activity {
18
19
  config: InterruptActivity;
@@ -46,16 +47,16 @@ class Interrupt extends Activity {
46
47
  }
47
48
  } catch (error) {
48
49
  if (error instanceof InactiveJobError) {
49
- this.logger.error('interrupt-inactive-job-error', { error });
50
+ this.logger.error('interrupt-inactive-job-error', { ...error });
50
51
  return;
51
52
  } else if (error instanceof GenerationalError) {
52
- this.logger.info('process-event-generational-job-error', { error });
53
+ this.logger.info('process-event-generational-job-error', { ...error });
53
54
  return;
54
55
  } else if (error instanceof GetStateError) {
55
- this.logger.error('interrupt-get-state-error', { error });
56
+ this.logger.error('interrupt-get-state-error', { ...error });
56
57
  return;
57
58
  } else {
58
- this.logger.error('interrupt-process-error', { error });
59
+ this.logger.error('interrupt-process-error', { ...error });
59
60
  }
60
61
  telemetry.setActivityError(error.message);
61
62
  throw error;
@@ -142,6 +143,15 @@ class Interrupt extends Activity {
142
143
  descend: this.config.descend !== undefined
143
144
  ? Pipe.resolve(this.config.descend, this.context)
144
145
  : undefined,
146
+ code: this.config.code !== undefined
147
+ ? Pipe.resolve(this.config.code, this.context)
148
+ : undefined,
149
+ expire: this.config.expire !== undefined
150
+ ? Pipe.resolve(this.config.expire, this.context)
151
+ : undefined,
152
+ stack: this.config.stack !== undefined
153
+ ? Pipe.resolve(this.config.stack, this.context)
154
+ : undefined,
145
155
  };
146
156
  }
147
157
 
@@ -2,7 +2,7 @@ import {
2
2
  GenerationalError,
3
3
  GetStateError,
4
4
  InactiveJobError } from '../../modules/errors';
5
- import { Activity, ActivityType } from './activity';
5
+ import { Activity } from './activity';
6
6
  import { CollatorService } from '../collator';
7
7
  import { EngineService } from '../engine';
8
8
  import { MapperService } from '../mapper';
@@ -11,6 +11,7 @@ import { TelemetryService } from '../telemetry';
11
11
  import {
12
12
  ActivityData,
13
13
  ActivityMetadata,
14
+ ActivityType,
14
15
  SignalActivity } from '../../types/activity';
15
16
  import { JobState } from '../../types/job';
16
17
  import { MultiResponseFlags } from '../../types/redis';
@@ -71,16 +72,16 @@ class Signal extends Activity {
71
72
  return this.context.metadata.aid;
72
73
  } catch (error) {
73
74
  if (error instanceof InactiveJobError) {
74
- this.logger.error('signal-inactive-job-error', { error });
75
+ this.logger.error('signal-inactive-job-error', { ...error });
75
76
  return;
76
77
  } else if (error instanceof GenerationalError) {
77
- this.logger.info('process-event-generational-job-error', { error });
78
+ this.logger.info('process-event-generational-job-error', { ...error });
78
79
  return;
79
80
  } else if (error instanceof GetStateError) {
80
- this.logger.error('signal-get-state-error', { error });
81
+ this.logger.error('signal-get-state-error', { ...error });
81
82
  return;
82
83
  } else {
83
- this.logger.error('signal-process-error', { error });
84
+ this.logger.error('signal-process-error', { ...error });
84
85
  }
85
86
  telemetry.setActivityError(error.message);
86
87
  throw error;
@@ -1,5 +1,8 @@
1
1
  import { DuplicateJobError } from '../../modules/errors';
2
- import { formatISODate, getTimeSeries, guid } from '../../modules/utils';
2
+ import {
3
+ formatISODate,
4
+ getTimeSeries,
5
+ guid } from '../../modules/utils';
3
6
  import { Activity } from './activity';
4
7
  import { CollatorService } from '../collator';
5
8
  import { EngineService } from '../engine';
@@ -12,7 +15,7 @@ import {
12
15
  ActivityMetadata,
13
16
  ActivityType,
14
17
  TriggerActivity } from '../../types/activity';
15
- import { JobState } from '../../types/job';
18
+ import { JobState, ExtensionType } from '../../types/job';
16
19
  import { RedisMulti } from '../../types/redis';
17
20
  import { StringScalarType } from '../../types/serializer';
18
21
  import { WorkListTaskType } from '../../types/task';
@@ -30,7 +33,7 @@ class Trigger extends Activity {
30
33
  super(config, data, metadata, hook, engine, context);
31
34
  }
32
35
 
33
- async process(): Promise<string> {
36
+ async process(options?: ExtensionType): Promise<string> {
34
37
  this.logger.debug('trigger-process', { subscribes: this.config.subscribes });
35
38
  let telemetry: TelemetryService;
36
39
  try {
@@ -45,6 +48,9 @@ class Trigger extends Activity {
45
48
  this.adjacencyList = await this.filterAdjacent();
46
49
  await this.setStatus(this.adjacencyList.length);
47
50
 
51
+ this.bindSearchData(options);
52
+ this.bindMarkerData(options);
53
+
48
54
  const multi = this.store.getMulti();
49
55
  await this.setState(multi);
50
56
  await this.setStats(multi);
@@ -67,9 +73,9 @@ class Trigger extends Activity {
67
73
  return this.context.metadata.jid;
68
74
  } catch (error) {
69
75
  if (error instanceof DuplicateJobError) {
70
- this.logger.error('duplicate-job-error', { error });
76
+ this.logger.error('duplicate-job-error', { job_id: error.jobId });
71
77
  } else {
72
- this.logger.error('trigger-process-error', { error });
78
+ this.logger.error('trigger-process-error', { ...error });
73
79
  }
74
80
  telemetry.setActivityError(error.message);
75
81
  throw error;
@@ -80,13 +86,44 @@ class Trigger extends Activity {
80
86
  }
81
87
  }
82
88
 
89
+ safeKey(key:string): string {
90
+ return `_${key}`;
91
+ }
92
+
93
+ bindSearchData(options?: ExtensionType): void {
94
+ if (options?.search) {
95
+ Object.keys(options.search).forEach((key) => {
96
+ this.context.data[this.safeKey(key)] = options.search[key].toString();
97
+ });
98
+ }
99
+ }
100
+
101
+ bindMarkerData(options?: ExtensionType): void {
102
+ if (options?.marker) {
103
+ Object.keys(options.marker).forEach((key) => {
104
+ if (key.startsWith('-')) {
105
+ this.context.data[key] = options.marker[key].toString();
106
+ }
107
+ });
108
+ }
109
+ }
110
+
83
111
  async setStatus(amount: number): Promise<void> {
84
112
  this.context.metadata.js = amount;
85
113
  }
86
114
 
87
115
  async execAdjacentParent() {
88
116
  if (this.context.metadata.px) {
89
- await this.engine.execAdjacentParent(this.context, {metadata: this.context.metadata, data: { job_id: this.context.metadata.jid }});
117
+ const timestamp = formatISODate(new Date());
118
+ const jobStartedConfirmationMessage = {
119
+ metadata: this.context.metadata,
120
+ data: {
121
+ job_id: this.context.metadata.jid,
122
+ jc: timestamp,
123
+ ju: timestamp,
124
+ }
125
+ };
126
+ await this.engine.execAdjacentParent(this.context, jobStartedConfirmationMessage);
90
127
  }
91
128
  }
92
129
 
@@ -2,9 +2,12 @@ import {
2
2
  GenerationalError,
3
3
  GetStateError,
4
4
  InactiveJobError } from '../../modules/errors';
5
+ import { guid } from '../../modules/utils';
5
6
  import { Activity } from './activity';
6
7
  import { CollatorService } from '../collator';
7
8
  import { EngineService } from '../engine';
9
+ import { Pipe } from '../pipe';
10
+ import { TelemetryService } from '../telemetry';
8
11
  import {
9
12
  ActivityData,
10
13
  ActivityMetadata,
@@ -13,9 +16,6 @@ import {
13
16
  import { JobState } from '../../types/job';
14
17
  import { MultiResponseFlags, RedisMulti } from '../../types/redis';
15
18
  import { StreamData} from '../../types/stream';
16
- import { TelemetryService } from '../telemetry';
17
- import { Pipe } from '../pipe';
18
- import { guid } from '../../modules/utils';
19
19
 
20
20
  class Worker extends Activity {
21
21
  config: WorkerActivity;
@@ -61,16 +61,16 @@ class Worker extends Activity {
61
61
  return this.context.metadata.aid;
62
62
  } catch (error) {
63
63
  if (error instanceof InactiveJobError) {
64
- this.logger.error('await-inactive-job-error', { error });
64
+ this.logger.error('await-inactive-job-error', { ...error });
65
65
  return;
66
66
  } else if (error instanceof GenerationalError) {
67
- this.logger.info('process-event-generational-job-error', { error });
67
+ this.logger.info('process-event-generational-job-error', { ...error });
68
68
  return;
69
69
  } else if (error instanceof GetStateError) {
70
- this.logger.error('worker-get-state-error', { error });
70
+ this.logger.error('worker-get-state-error', { ...error });
71
71
  return;
72
72
  } else {
73
- this.logger.error('worker-process-error', { error });
73
+ this.logger.error('worker-process-error', { ...error });
74
74
  }
75
75
  telemetry.setActivityError(error.message);
76
76
  throw error;
@@ -9,6 +9,7 @@ import { HotMeshGraph, HotMeshManifest } from '../../types/hotmesh';
9
9
  import { RedisClient, RedisMulti } from '../../types/redis';
10
10
  import { StringAnyType, Symbols } from '../../types/serializer';
11
11
  import { Pipe } from '../pipe';
12
+ import { Validator } from './validator';
12
13
 
13
14
  const DEFAULT_METADATA_RANGE_SIZE = 26; //metadata is 26 slots ([a-z] * 1)
14
15
  const DEFAULT_DATA_RANGE_SIZE = 260; //data is 260 slots ([a-zA-Z] * 5)
@@ -201,6 +202,9 @@ class Deployer {
201
202
  //DAGs have one parent; easy to optimize for
202
203
  graph.activities[to].parent = fromActivity;
203
204
  }
205
+ //temporarily bind the transitions to the parent activity,
206
+ // so the consumer/producer registrar picks up the bindings
207
+ graph.activities[fromActivity].transitions = toTransitions;
204
208
  }
205
209
  }
206
210
  }
@@ -257,9 +261,29 @@ class Deployer {
257
261
  let newPath = [...path, key];
258
262
  traverse(obj[key], newPath);
259
263
  } else {
260
- const finalPath = `data/${[...path, key].join('/')}`;
261
- if (!result.includes(finalPath)) {
262
- result.push(finalPath);
264
+ //wildcard mapping (e.g., 'friends[25]')
265
+ //when this is resolved, it will be expanded to
266
+ //`'friends/0', ..., 'friends/24'`, providing 25 dynamic
267
+ //slots in the flow's output data
268
+ const pathName = [...path, key].join('/');
269
+ if (!pathName.includes('[')) {
270
+ const finalPath = `data/${pathName}`;
271
+ if (!result.includes(finalPath)) {
272
+ result.push(finalPath);
273
+ }
274
+ } else {
275
+ const [left, right] = pathName.split('[');
276
+ //check if this variable isLiteralKeyType (#, -, or _)
277
+ const [amount, _] = right.split(']');
278
+ if (!isNaN(parseInt(amount))) {
279
+ //loop to create all possible paths (0 to amount)
280
+ for (let i = 0; i < parseInt(amount); i++) {
281
+ const finalPath = `data/${left}/${i}`;
282
+ if (!result.includes(finalPath)) {
283
+ result.push(finalPath);
284
+ }
285
+ }
286
+ } //else ignore (amount might be '-' or '_') `-` is marker data; `_` is job data;
263
287
  }
264
288
  }
265
289
  }
@@ -280,7 +304,7 @@ class Deployer {
280
304
  trigger.PRODUCES = results;
281
305
  }
282
306
  }
283
-
307
+
284
308
  resolveMappingDependencies() {
285
309
  const dynamicMappingRules: string[] = [];
286
310
  //recursive function to descend into the object and find all dynamic mapping rules
@@ -289,7 +313,7 @@ class Deployer {
289
313
  if (typeof obj[key] === 'string') {
290
314
  const stringValue = obj[key] as string;
291
315
  const dynamicMappingRuleMatch = stringValue.match(/^\{[^@].*}$/);
292
- if (dynamicMappingRuleMatch) {
316
+ if (dynamicMappingRuleMatch && !Validator.CONTEXT_VARS.includes(stringValue)) {
293
317
  if (stringValue.split('.')[1] !== 'input') {
294
318
  dynamicMappingRules.push(stringValue);
295
319
  consumes.push(stringValue);
@@ -359,7 +383,10 @@ class Deployer {
359
383
  for (const graph of graphs) {
360
384
  const activities = graph.activities;
361
385
  for (const activityKey in activities) {
362
- activitySchemas[activityKey] = activities[activityKey];
386
+ const target = activities[activityKey];
387
+ //remove transitions; no longer necessary for runtime
388
+ delete target.transitions;
389
+ activitySchemas[activityKey] = target;
363
390
  }
364
391
  }
365
392
  await this.store.setSchemas(activitySchemas, this.getVID());
@@ -11,7 +11,8 @@ class Validator {
11
11
  store: StoreService<RedisClient, RedisMulti> | null = null;
12
12
 
13
13
  static SYS_VARS = ['$app', '$self', '$graph', '$job'];
14
-
14
+ static CONTEXT_VARS = ['{$input}', '{$output}', '{$item}', '{$key}', '{$index}'];
15
+
15
16
  constructor(manifest: HotMeshManifest) {
16
17
  this.manifest = manifest;
17
18
  }
@@ -92,8 +93,7 @@ class Validator {
92
93
  if (statement.startsWith('{') && statement.endsWith('}')) {
93
94
  const statementParts = statement.slice(1, -1).split('.');
94
95
  const referencedActivityId = statementParts[0];
95
-
96
- if (!(Validator.SYS_VARS.includes(referencedActivityId) || activityIds.includes(referencedActivityId) || this.isFunction(statement))) {
96
+ if (!(Validator.SYS_VARS.includes(referencedActivityId) || activityIds.includes(referencedActivityId) || this.isFunction(statement) || this.isContextVariable(statement))) {
97
97
  throw new Error(`Mapping statement references non-existent activity: ${statement}`);
98
98
  }
99
99
  }
@@ -105,6 +105,10 @@ class Validator {
105
105
  return value.startsWith('{@') && Pipe.resolveFunction(value);
106
106
  }
107
107
 
108
+ isContextVariable(value: string): boolean {
109
+ return ['{$input}', '{$output}', '{$item}', '{$key}', '{$index}'].includes(value);
110
+ }
111
+
108
112
  // 1.3) Validate the mapping/@pipe statements are valid
109
113
  validateMappingStatements() {
110
114
  // Implement the method content
@@ -1,4 +1,17 @@
1
- import { APP_ID, APP_VERSION, DEFAULT_COEFFICIENT, getWorkflowYAML } from './factory';
1
+ import ms from 'ms';
2
+
3
+ import {
4
+ APP_ID,
5
+ APP_VERSION,
6
+ getWorkflowYAML } from './schemas/factory';
7
+ import {
8
+ HMSH_LOGLEVEL,
9
+ HMSH_EXPIRE_JOB_SECONDS,
10
+ HMSH_QUORUM_DELAY_MS,
11
+ HMSH_DURABLE_EXP_BACKOFF,
12
+ HMSH_DURABLE_MAX_ATTEMPTS,
13
+ HMSH_DURABLE_MAX_INTERVAL } from '../../modules/enums';
14
+ import { sleepFor } from '../../modules/utils';
2
15
  import { WorkflowHandleService } from './handle';
3
16
  import { HotMeshService as HotMesh } from '../hotmesh';
4
17
  import {
@@ -11,8 +24,6 @@ import { JobState } from '../../types/job';
11
24
  import { KeyService, KeyType } from '../../modules/key';
12
25
  import { Search } from './search';
13
26
  import { StreamStatus } from '../../types';
14
- import { HMSH_LOGLEVEL, HMSH_EXPIRE_JOB_SECONDS, HMSH_QUORUM_DELAY_MS } from '../../modules/enums';
15
- import { sleepFor } from '../../modules/utils';
16
27
 
17
28
  export class ClientService {
18
29
 
@@ -32,7 +43,7 @@ export class ClientService {
32
43
  await this.verifyWorkflowActive(hotMeshClient, targetNS);
33
44
  if (!ClientService.topics.includes(workflowTopic)) {
34
45
  ClientService.topics.push(workflowTopic);
35
- await this.createStream(hotMeshClient, workflowTopic, namespace);
46
+ await ClientService.createStream(hotMeshClient, workflowTopic, namespace);
36
47
  }
37
48
  return hotMeshClient;
38
49
  }
@@ -49,7 +60,7 @@ export class ClientService {
49
60
  }
50
61
  });
51
62
  ClientService.instances.set(targetNS, hotMeshClient);
52
- await this.createStream(await hotMeshClient, workflowTopic, namespace);
63
+ await ClientService.createStream(await hotMeshClient, workflowTopic, namespace);
53
64
  await this.activateWorkflow(await hotMeshClient, targetNS);
54
65
  return hotMeshClient;
55
66
  }
@@ -60,7 +71,7 @@ export class ClientService {
60
71
  * has not yet been initialized, so this call ensures that the channel
61
72
  * exists and is ready to serve as a container for events.
62
73
  */
63
- createStream = async(hotMeshClient: HotMesh, workflowTopic: string, namespace?: string) => {
74
+ static createStream = async(hotMeshClient: HotMesh, workflowTopic: string, namespace?: string) => {
64
75
  const store = hotMeshClient.engine.store;
65
76
  const params = { appId: namespace ?? APP_ID, topic: workflowTopic };
66
77
  const streamKey = store.mintKey(KeyType.STREAMS, params);
@@ -71,6 +82,23 @@ export class ClientService {
71
82
  }
72
83
  }
73
84
 
85
+ /**
86
+ * It is possible for a client to invoke a workflow without first
87
+ * creating the stream. This method will verify that the stream
88
+ * exists and if not, create it.
89
+ */
90
+ static verifyStream = async(workflowTopic: string, namespace?: string) => {
91
+ const targetNS = namespace ?? APP_ID;
92
+ if (ClientService.instances.has(targetNS)) {
93
+ const hotMeshClient = await ClientService.instances.get(targetNS);
94
+ if (!ClientService.topics.includes(workflowTopic)) {
95
+ ClientService.topics.push(workflowTopic);
96
+ await ClientService.createStream(hotMeshClient, workflowTopic, namespace);
97
+ }
98
+ return hotMeshClient;
99
+ }
100
+ }
101
+
74
102
  /**
75
103
  * For those deployments with a redis stack backend (with the FT module),
76
104
  * this method will configure the search index for the workflow.
@@ -95,8 +123,8 @@ export class ClientService {
95
123
  const hotMeshPrefix = KeyService.mintKey(hotMeshClient.namespace, KeyType.JOB_STATE, keyParams);
96
124
  const prefixes = search.prefix.map((prefix) => `${hotMeshPrefix}${prefix}`);
97
125
  await store.exec('FT.CREATE', `${search.index}`, 'ON', 'HASH', 'PREFIX', prefixes.length, ...prefixes, 'SCHEMA', ...schema);
98
- } catch (err) {
99
- hotMeshClient.engine.logger.info('durable-client-search-err', { err });
126
+ } catch (error) {
127
+ hotMeshClient.engine.logger.info('durable-client-search-err', { ...error });
100
128
  }
101
129
  }
102
130
  }
@@ -127,21 +155,18 @@ export class ClientService {
127
155
  parentWorkflowId: options.parentWorkflowId,
128
156
  workflowId: options.workflowId || HotMesh.guid(),
129
157
  workflowTopic: workflowTopic,
130
- backoffCoefficient: options.config?.backoffCoefficient || DEFAULT_COEFFICIENT,
158
+ backoffCoefficient: options.config?.backoffCoefficient || HMSH_DURABLE_EXP_BACKOFF,
159
+ maximumAttempts: options.config?.maximumAttempts || HMSH_DURABLE_MAX_ATTEMPTS,
160
+ maximumInterval: ms(options.config?.maximumInterval || HMSH_DURABLE_MAX_INTERVAL) / 1000,
131
161
  }
162
+
132
163
  const context = { metadata: { trc, spn }, data: {}};
133
164
  const jobId = await hotMeshClient.pub(
134
165
  `${options.namespace ?? APP_ID}.execute`,
135
166
  payload,
136
- context as JobState
167
+ context as JobState,
168
+ { search: options?.search?.data, marker: options?.marker},
137
169
  );
138
- // Seed search data
139
- if (jobId && options.search?.data) {
140
- const searchSessionId = `-search-0`;
141
- const search = new Search(jobId, hotMeshClient, searchSessionId);
142
- const entries = Object.entries(options.search.data).flat();
143
- await search.set(...entries);
144
- }
145
170
  return new WorkflowHandleService(hotMeshClient, workflowTopic, jobId);
146
171
  },
147
172
 
@@ -165,7 +190,9 @@ export class ClientService {
165
190
  arguments: [...options.args],
166
191
  id: options.workflowId,
167
192
  workflowTopic,
168
- backoffCoefficient: options.config?.backoffCoefficient || DEFAULT_COEFFICIENT,
193
+ backoffCoefficient: options.config?.backoffCoefficient || HMSH_DURABLE_EXP_BACKOFF,
194
+ maximumAttempts: options.config?.maximumAttempts || HMSH_DURABLE_MAX_ATTEMPTS,
195
+ maximumInterval: ms(options.config?.maximumInterval || HMSH_DURABLE_MAX_INTERVAL) / 1000,
169
196
  }
170
197
  //seed search data if presentthe hook before entering
171
198
  const hotMeshClient = await this.getHotMeshClient(workflowTopic, options.namespace);
@@ -190,9 +217,9 @@ export class ClientService {
190
217
  const hotMeshClient = await this.getHotMeshClient(workflowTopic, namespace);
191
218
  try {
192
219
  return await this.search(hotMeshClient, index, query);
193
- } catch (err) {
194
- hotMeshClient.engine.logger.error('durable-client-search-err', { err });
195
- throw err;
220
+ } catch (error) {
221
+ hotMeshClient.engine.logger.error('durable-client-search-err', { ...error });
222
+ throw error;
196
223
  }
197
224
  }
198
225
  }
@@ -218,7 +245,7 @@ export class ClientService {
218
245
  await hotMesh.deploy(getWorkflowYAML(appId, version));
219
246
  await hotMesh.activate(version);
220
247
  } catch (error) {
221
- hotMesh.engine.logger.error('durable-client-deploy-activate-err', { error });
248
+ hotMesh.engine.logger.error('durable-client-deploy-activate-err', { ...error });
222
249
  throw error;
223
250
  }
224
251
  } else if(app && !app.active) {