@hotmeshio/hotmesh 0.0.52 → 0.0.53

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 (126) 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 +98 -18
  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 +4 -4
  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.js +12 -3
  20. package/build/services/activities/worker.js +6 -6
  21. package/build/services/compiler/deployer.js +33 -5
  22. package/build/services/compiler/validator.d.ts +2 -0
  23. package/build/services/compiler/validator.js +5 -1
  24. package/build/services/durable/client.d.ts +7 -1
  25. package/build/services/durable/client.js +56 -30
  26. package/build/services/durable/exporter.d.ts +7 -72
  27. package/build/services/durable/exporter.js +105 -295
  28. package/build/services/durable/handle.d.ts +11 -6
  29. package/build/services/durable/handle.js +59 -46
  30. package/build/services/durable/index.d.ts +0 -2
  31. package/build/services/durable/index.js +0 -2
  32. package/build/services/durable/schemas/factory.d.ts +33 -0
  33. package/build/services/durable/schemas/factory.js +2356 -0
  34. package/build/services/durable/search.js +8 -8
  35. package/build/services/durable/worker.js +117 -25
  36. package/build/services/durable/workflow.d.ts +46 -43
  37. package/build/services/durable/workflow.js +273 -277
  38. package/build/services/engine/index.js +3 -0
  39. package/build/services/exporter/index.d.ts +2 -4
  40. package/build/services/exporter/index.js +4 -5
  41. package/build/services/mapper/index.d.ts +6 -2
  42. package/build/services/mapper/index.js +6 -2
  43. package/build/services/pipe/functions/array.d.ts +2 -10
  44. package/build/services/pipe/functions/array.js +30 -28
  45. package/build/services/pipe/functions/conditional.d.ts +1 -0
  46. package/build/services/pipe/functions/conditional.js +3 -0
  47. package/build/services/pipe/functions/date.d.ts +1 -0
  48. package/build/services/pipe/functions/date.js +4 -0
  49. package/build/services/pipe/functions/index.d.ts +2 -0
  50. package/build/services/pipe/functions/index.js +2 -0
  51. package/build/services/pipe/functions/logical.d.ts +5 -0
  52. package/build/services/pipe/functions/logical.js +12 -0
  53. package/build/services/pipe/functions/object.d.ts +3 -0
  54. package/build/services/pipe/functions/object.js +25 -7
  55. package/build/services/pipe/index.d.ts +20 -3
  56. package/build/services/pipe/index.js +82 -16
  57. package/build/services/router/index.js +14 -3
  58. package/build/services/serializer/index.d.ts +3 -2
  59. package/build/services/serializer/index.js +11 -4
  60. package/build/services/store/clients/ioredis.js +6 -6
  61. package/build/services/store/clients/redis.js +7 -7
  62. package/build/services/store/index.d.ts +2 -0
  63. package/build/services/store/index.js +4 -1
  64. package/build/services/stream/clients/ioredis.js +8 -8
  65. package/build/services/stream/clients/redis.js +1 -1
  66. package/build/types/activity.d.ts +60 -5
  67. package/build/types/durable.d.ts +168 -33
  68. package/build/types/exporter.d.ts +26 -4
  69. package/build/types/index.d.ts +2 -2
  70. package/build/types/job.d.ts +69 -5
  71. package/build/types/pipe.d.ts +81 -3
  72. package/build/types/stream.d.ts +61 -1
  73. package/build/types/stream.js +4 -0
  74. package/index.ts +1 -2
  75. package/modules/enums.ts +16 -8
  76. package/modules/errors.ts +174 -32
  77. package/package.json +7 -2
  78. package/services/activities/activity.ts +63 -14
  79. package/services/activities/await.ts +6 -6
  80. package/services/activities/cycle.ts +7 -6
  81. package/services/activities/hook.ts +4 -4
  82. package/services/activities/interrupt.ts +19 -9
  83. package/services/activities/signal.ts +6 -5
  84. package/services/activities/trigger.ts +16 -4
  85. package/services/activities/worker.ts +7 -7
  86. package/services/compiler/deployer.ts +33 -6
  87. package/services/compiler/validator.ts +7 -3
  88. package/services/durable/client.ts +47 -14
  89. package/services/durable/exporter.ts +110 -318
  90. package/services/durable/handle.ts +63 -50
  91. package/services/durable/index.ts +0 -2
  92. package/services/durable/schemas/factory.ts +2358 -0
  93. package/services/durable/search.ts +8 -8
  94. package/services/durable/worker.ts +128 -29
  95. package/services/durable/workflow.ts +304 -288
  96. package/services/engine/index.ts +4 -0
  97. package/services/exporter/index.ts +10 -12
  98. package/services/mapper/index.ts +6 -2
  99. package/services/pipe/functions/array.ts +24 -37
  100. package/services/pipe/functions/conditional.ts +4 -0
  101. package/services/pipe/functions/date.ts +6 -0
  102. package/services/pipe/functions/index.ts +7 -5
  103. package/services/pipe/functions/logical.ts +11 -0
  104. package/services/pipe/functions/object.ts +26 -7
  105. package/services/pipe/index.ts +99 -21
  106. package/services/quorum/index.ts +1 -3
  107. package/services/router/index.ts +14 -3
  108. package/services/serializer/index.ts +12 -5
  109. package/services/store/clients/ioredis.ts +6 -6
  110. package/services/store/clients/redis.ts +7 -7
  111. package/services/store/index.ts +4 -1
  112. package/services/stream/clients/ioredis.ts +8 -8
  113. package/services/stream/clients/redis.ts +1 -1
  114. package/types/activity.ts +87 -15
  115. package/types/durable.ts +246 -73
  116. package/types/exporter.ts +31 -5
  117. package/types/index.ts +6 -7
  118. package/types/job.ts +130 -36
  119. package/types/pipe.ts +84 -3
  120. package/types/stream.ts +82 -23
  121. package/build/services/durable/factory.d.ts +0 -17
  122. package/build/services/durable/factory.js +0 -817
  123. package/build/services/durable/meshos.d.ts +0 -127
  124. package/build/services/durable/meshos.js +0 -380
  125. package/services/durable/factory.ts +0 -818
  126. 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;
@@ -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';
@@ -67,9 +70,9 @@ class Trigger extends Activity {
67
70
  return this.context.metadata.jid;
68
71
  } catch (error) {
69
72
  if (error instanceof DuplicateJobError) {
70
- this.logger.error('duplicate-job-error', { error });
73
+ this.logger.error('duplicate-job-error', { job_id: error.jobId });
71
74
  } else {
72
- this.logger.error('trigger-process-error', { error });
75
+ this.logger.error('trigger-process-error', { ...error });
73
76
  }
74
77
  telemetry.setActivityError(error.message);
75
78
  throw error;
@@ -86,7 +89,16 @@ class Trigger extends Activity {
86
89
 
87
90
  async execAdjacentParent() {
88
91
  if (this.context.metadata.px) {
89
- await this.engine.execAdjacentParent(this.context, {metadata: this.context.metadata, data: { job_id: this.context.metadata.jid }});
92
+ const timestamp = formatISODate(new Date());
93
+ const jobStartedConfirmationMessage = {
94
+ metadata: this.context.metadata,
95
+ data: {
96
+ job_id: this.context.metadata.jid,
97
+ jc: timestamp,
98
+ ju: timestamp,
99
+ }
100
+ };
101
+ await this.engine.execAdjacentParent(this.context, jobStartedConfirmationMessage);
90
102
  }
91
103
  }
92
104
 
@@ -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,8 +155,11 @@ 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`,
@@ -165,7 +196,9 @@ export class ClientService {
165
196
  arguments: [...options.args],
166
197
  id: options.workflowId,
167
198
  workflowTopic,
168
- backoffCoefficient: options.config?.backoffCoefficient || DEFAULT_COEFFICIENT,
199
+ backoffCoefficient: options.config?.backoffCoefficient || HMSH_DURABLE_EXP_BACKOFF,
200
+ maximumAttempts: options.config?.maximumAttempts || HMSH_DURABLE_MAX_ATTEMPTS,
201
+ maximumInterval: ms(options.config?.maximumInterval || HMSH_DURABLE_MAX_INTERVAL) / 1000,
169
202
  }
170
203
  //seed search data if presentthe hook before entering
171
204
  const hotMeshClient = await this.getHotMeshClient(workflowTopic, options.namespace);
@@ -190,9 +223,9 @@ export class ClientService {
190
223
  const hotMeshClient = await this.getHotMeshClient(workflowTopic, namespace);
191
224
  try {
192
225
  return await this.search(hotMeshClient, index, query);
193
- } catch (err) {
194
- hotMeshClient.engine.logger.error('durable-client-search-err', { err });
195
- throw err;
226
+ } catch (error) {
227
+ hotMeshClient.engine.logger.error('durable-client-search-err', { ...error });
228
+ throw error;
196
229
  }
197
230
  }
198
231
  }
@@ -218,7 +251,7 @@ export class ClientService {
218
251
  await hotMesh.deploy(getWorkflowYAML(appId, version));
219
252
  await hotMesh.activate(version);
220
253
  } catch (error) {
221
- hotMesh.engine.logger.error('durable-client-deploy-activate-err', { error });
254
+ hotMesh.engine.logger.error('durable-client-deploy-activate-err', { ...error });
222
255
  throw error;
223
256
  }
224
257
  } else if(app && !app.active) {