@hotmeshio/hotmesh 0.0.51 → 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 +13 -9
  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 +65 -16
  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 +67 -18
  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
@@ -143,19 +143,19 @@ class Activity {
143
143
  this.transitionAdjacent(multiResponse, telemetry);
144
144
  } catch (error) {
145
145
  if (error instanceof CollationError) {
146
- this.logger.info('process-event-inactive-error', { error });
146
+ this.logger.info('process-event-inactive-error', { ...error });
147
147
  return;
148
148
  } else if (error instanceof InactiveJobError) {
149
- this.logger.info('process-event-inactive-job-error', { error });
149
+ this.logger.info('process-event-inactive-job-error', { ...error });
150
150
  return;
151
151
  } else if (error instanceof GenerationalError) {
152
- this.logger.info('process-event-generational-job-error', { error });
152
+ this.logger.info('process-event-generational-job-error', { ...error });
153
153
  return;
154
154
  } else if (error instanceof GetStateError) {
155
- this.logger.info('process-event-get-job-error', { error });
155
+ this.logger.info('process-event-get-job-error', { ...error });
156
156
  return;
157
157
  }
158
- this.logger.error('activity-process-event-error', { error });
158
+ this.logger.error('activity-process-event-error', { ...error, message: error.message, stack: error.stack, name: error.name });
159
159
  telemetry && telemetry.setActivityError(error.message);
160
160
  throw error;
161
161
  } finally {
@@ -191,6 +191,10 @@ class Activity {
191
191
  async processError(telemetry: TelemetryService, type: string): Promise<MultiResponseFlags> {
192
192
  this.bindActivityError(this.data);
193
193
  this.adjacencyList = await this.filterAdjacent();
194
+ if (!this.adjacencyList.length) {
195
+ this.bindJobError(this.data);
196
+ }
197
+ this.mapJobData();
194
198
  const multi = this.store.getMulti();
195
199
  await this.setState(multi);
196
200
  await CollatorService.notarizeCompletion(this, multi);
@@ -205,7 +209,7 @@ class Activity {
205
209
  const attrs: StringScalarType = { 'app.job.jss': jobStatus };
206
210
  //adjacencyList membership has already been set at this point (according to activity status)
207
211
  const messageIds = await this.transition(this.adjacencyList, jobStatus);
208
- if (messageIds.length) {
212
+ if (messageIds?.length) {
209
213
  attrs['app.activity.mids'] = messageIds.join(',')
210
214
  }
211
215
  telemetry.setActivityAttributes(attrs);
@@ -223,7 +227,30 @@ class Activity {
223
227
  mapJobData(): void {
224
228
  if(this.config.job?.maps) {
225
229
  const mapper = new MapperService(this.config.job.maps, this.context);
226
- this.context.data = mapper.mapRules();
230
+ const output = mapper.mapRules();
231
+ if (output) {
232
+ for (const key in output) {
233
+ const f1 = key.indexOf('[');
234
+ //keys with array notation suffix `somekey[]` represent
235
+ //dynamically-keyed mappings whose `value` must be moved to the output.
236
+ //The `value` must be an object with keys appropriate to the
237
+ //notation type: `somekey[0] (array)`, `somekey[-] (mark)`, OR `somekey[_] (search)`
238
+ if (f1 > -1) {
239
+ const amount = key.substring(f1 + 1).split(']')[0];
240
+ if (!isNaN(Number(amount))) {
241
+ const left = key.substring(0, f1);
242
+ output[left] = output[key];
243
+ delete output[key];
244
+ } else if (amount === '-' || amount === '_') {
245
+ const obj = output[key];
246
+ Object.keys(obj).forEach((newKey) => {
247
+ output[newKey] = obj[newKey];
248
+ });
249
+ }
250
+ }
251
+ }
252
+ }
253
+ this.context.data = output;
227
254
  }
228
255
  }
229
256
 
@@ -249,10 +276,22 @@ class Activity {
249
276
  //set timeout in support of hook and/or duplex
250
277
  }
251
278
 
279
+ /**
280
+ * Any StreamMessage with a status of ERROR is bound to the activity
281
+ */
252
282
  bindActivityError(data: Record<string, unknown>): void {
253
- //todo: map activity error data into the job error (if defined)
254
- // map job status via: (500: [3**, 4**, 5**], 202: [$pending])
255
- this.context.metadata.err = JSON.stringify(data);
283
+ const md = this.context[this.metadata.aid].output.metadata;
284
+ md.err = JSON.stringify(this.data);
285
+ //(temporary...useful for mapping error parts in the app.yaml)
286
+ md.$error = { ...data, is_stream_error: true };
287
+ }
288
+
289
+ /**
290
+ * unhandled activity errors (activities that return an ERROR StreamMessage
291
+ * status and have no adjacent children to transition to) are bound to the job
292
+ */
293
+ bindJobError(data: Record<string, unknown>): void {
294
+ this.context.metadata.err = JSON.stringify({ ...data, is_stream_error: true });
256
295
  }
257
296
 
258
297
  async getTriggerConfig(): Promise<ActivityType> {
@@ -322,9 +361,9 @@ class Activity {
322
361
  if (this.status === StreamStatus.ERROR) {
323
362
  self.output.metadata.err = JSON.stringify(this.data);
324
363
  }
325
- //todo: verify leg2 never overwrites leg1 `ac`
326
- self.output.metadata.ac =
327
- self.output.metadata.au = formatISODate(new Date());
364
+ const ts = formatISODate(new Date());
365
+ self.output.metadata.ac = ts;
366
+ self.output.metadata.au = ts;
328
367
  self.output.metadata.atp = this.config.type;
329
368
  if (this.config.subtype) {
330
369
  self.output.metadata.stp = this.config.subtype;
@@ -344,6 +383,11 @@ class Activity {
344
383
  state[path] = value;
345
384
  }
346
385
  }
386
+ for (let key in this.context?.data ?? {}) {
387
+ if (key.startsWith('-') || key.startsWith('_')) {
388
+ state[key] = this.context.data[key];
389
+ }
390
+ }
347
391
  TelemetryService.bindJobTelemetryToState(state, this.config, this.context);
348
392
  }
349
393
 
@@ -398,9 +442,9 @@ class Activity {
398
442
  let { dad, jid } = this.context.metadata;
399
443
  const dIds = CollatorService.getDimensionsById([...this.config.ancestors, this.metadata.aid], dad || '');
400
444
  //`state` is a unidimensional hash; context is a tree
401
- const [state, status] = await this.store.getState(jid, consumes, dIds);
445
+ const [state, _status] = await this.store.getState(jid, consumes, dIds);
402
446
  this.context = restoreHierarchy(state) as JobState;
403
- this.assertGenerationalId(this.context.metadata.gid, gid);
447
+ this.assertGenerationalId(this.context?.metadata?.gid, gid);
404
448
  this.initDimensionalAddress(dad);
405
449
  this.initSelf(this.context);
406
450
  this.initPolicies(this.context);
@@ -417,9 +461,9 @@ class Activity {
417
461
  throw new GenerationalError(
418
462
  jobGID,
419
463
  msgGID,
420
- this.context.metadata.jid,
421
- this.context.metadata.aid,
422
- this.context.metadata.dad
464
+ this.context?.metadata?.jid ?? '',
465
+ this.context?.metadata?.aid ?? '',
466
+ this.context?.metadata?.dad ?? ''
423
467
  );
424
468
  }
425
469
  }
@@ -443,6 +487,11 @@ class Activity {
443
487
  if (!self.hook) {
444
488
  self.hook = { };
445
489
  }
490
+ if (!self.output.metadata) {
491
+ self.output.metadata = { };
492
+ }
493
+ //prebind the updated timestamp (mappings need the time)
494
+ self.output.metadata.au = formatISODate(new Date());
446
495
  context['$self'] = self;
447
496
  context['$job'] = context; //NEVER call STRINGIFY! (now circular)
448
497
  return context as JobState;
@@ -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