@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
@@ -450,6 +450,7 @@ class EngineService {
450
450
  streamData.status = StreamStatus.ERROR;
451
451
  streamData.data = error;
452
452
  streamData.code = error.code;
453
+ streamData.stack = error.stack;
453
454
  } else if (emit) {
454
455
  streamData.status = StreamStatus.PENDING;
455
456
  streamData.code = HMSH_CODE_PENDING;
@@ -474,7 +475,10 @@ class EngineService {
474
475
 
475
476
  // ****************** `INTERRUPT` ACTIVE JOBS *****************
476
477
  async interrupt(topic: string, jobId: string, options: JobInterruptOptions = {}): Promise<string> {
478
+ //immediately interrupt the job, going directly to the data source
477
479
  await this.store.interrupt(topic, jobId, options);
480
+
481
+ //now that the job is interrupted, we can clean up
478
482
  const context = await this.getState(topic, jobId) as JobState;
479
483
  const completionOpts: JobCompletionOptions = {
480
484
  interrupt: options.descend,
@@ -1,18 +1,18 @@
1
+ import { VALSEP } from '../../modules/key';
1
2
  import { ILogger } from '../logger';
3
+ import { restoreHierarchy } from '../../modules/utils';
4
+ import { SerializerService } from '../serializer';
2
5
  import { StoreService } from '../store';
3
- import {
4
- StringAnyType,
5
- StringStringType,
6
- Symbols } from "../../types/serializer";
7
- import { RedisClient, RedisMulti } from '../../types/redis';
8
6
  import {
9
7
  DependencyExport,
10
8
  ExportOptions,
11
9
  JobActionExport,
12
10
  JobExport } from '../../types/exporter';
13
- import { SerializerService } from '../serializer';
14
- import { restoreHierarchy } from '../../modules/utils';
15
- import { VALSEP } from '../../modules/key';
11
+ import { RedisClient, RedisMulti } from '../../types/redis';
12
+ import {
13
+ StringAnyType,
14
+ StringStringType,
15
+ Symbols } from "../../types/serializer";
16
16
 
17
17
  /**
18
18
  * Downloads job data from Redis (hscan, hmget, hgetall)
@@ -21,7 +21,6 @@ import { VALSEP } from '../../modules/key';
21
21
  class ExporterService {
22
22
  appId: string;
23
23
  logger: ILogger;
24
- serializer: SerializerService
25
24
  store: StoreService<RedisClient, RedisMulti>;
26
25
  symbols: Promise<Symbols> | Symbols;
27
26
 
@@ -29,7 +28,6 @@ class ExporterService {
29
28
  this.appId = appId;
30
29
  this.logger = logger;
31
30
  this.store = store;
32
- this.serializer = new SerializerService();
33
31
  }
34
32
 
35
33
  /**
@@ -83,11 +81,11 @@ class ExporterService {
83
81
  const [_, letters, numbers] = match;
84
82
  const path = this.inflateKey(letters);
85
83
  const dimensions = `${numbers.replace(/,/g, '/')}`;
86
- const resolved = this.serializer.fromString(value);
84
+ const resolved = SerializerService.fromString(value);
87
85
  process[`${dimensions}/${path}`] = resolved;
88
86
  } else if (key.length === 3) {
89
87
  //job state
90
- process[this.inflateKey(key)] = this.serializer.fromString(value);
88
+ process[this.inflateKey(key)] = SerializerService.fromString(value);
91
89
  }
92
90
  });
93
91
 
@@ -36,7 +36,7 @@ class MapperService {
36
36
  }
37
37
 
38
38
  /**
39
- * resolve a pipe expression of the form: { @pipe: [["{data.foo.bar}", 2, false, "hello world"]] }
39
+ * resolves a pipe expression of the form: { @pipe: [["{data.foo.bar}", 2, false, "hello world"]] }
40
40
  * @param value
41
41
  * @returns
42
42
  */
@@ -46,7 +46,7 @@ class MapperService {
46
46
  }
47
47
 
48
48
  /**
49
- * resolve a simple mapping expression in the form: "{data.foo.bar}" or 2 or false or "hello world"
49
+ * resolves a mapping expression in the form: "{data.foo.bar}" or 2 or false or "hello world"
50
50
  * @param value
51
51
  * @returns
52
52
  */
@@ -55,6 +55,10 @@ class MapperService {
55
55
  return pipe.process();
56
56
  }
57
57
 
58
+ /**
59
+ * Evaluates a transition rule against the current job state and incoming Stream message
60
+ * to determine which (if any) transition should be taken.
61
+ */
58
62
  static evaluate(transitionRule: TransitionRule | boolean, context: JobState, code: StreamCode): boolean {
59
63
  if (typeof transitionRule === 'boolean') {
60
64
  return transitionRule;
@@ -1,6 +1,6 @@
1
1
  class ArrayHandler {
2
2
  get(array: any[], index: number): any {
3
- return array[index];
3
+ return array?.[index || 0];
4
4
  }
5
5
 
6
6
  length(array: any[]): any {
@@ -11,26 +11,6 @@ class ArrayHandler {
11
11
  return array1.concat(array2);
12
12
  }
13
13
 
14
- every(array: any[], callback: (value: any, index: number, array: any[]) => boolean): boolean {
15
- return array.every(callback);
16
- }
17
-
18
- filter(array: any[], callback: (value: any, index: number, array: any[]) => boolean): any[] {
19
- return array.filter(callback);
20
- }
21
-
22
- find(array: any[], callback: (value: any, index: number, array: any[]) => boolean): any {
23
- return array.find(callback);
24
- }
25
-
26
- findIndex(array: any[], callback: (value: any, index: number, array: any[]) => boolean): number {
27
- return array.findIndex(callback);
28
- }
29
-
30
- forEach(array: any[], callback: (value: any, index: number, array: any[]) => void): void {
31
- array.forEach(callback);
32
- }
33
-
34
14
  indexOf(array: any[], searchElement: any, fromIndex?: number): number {
35
15
  return array.indexOf(searchElement, fromIndex);
36
16
  }
@@ -43,20 +23,13 @@ class ArrayHandler {
43
23
  return array.lastIndexOf(searchElement, fromIndex);
44
24
  }
45
25
 
46
- map(array: any[], callback: (value: any, index: number, array: any[]) => any): any[] {
47
- return array.map(callback);
48
- }
49
-
50
26
  pop(array: any[]): any {
51
27
  return array.pop();
52
28
  }
53
29
 
54
- push(array: any[], ...items: any[]): number {
55
- return array.push(...items);
56
- }
57
-
58
- reduce(array: any[], callback: (accumulator: any, currentValue: any, currentIndex: number, array: any[]) => any, initialValue?: any): any {
59
- return array.reduce(callback, initialValue);
30
+ push(array: any[], ...items: any[]): any[] {
31
+ array.push(...items);
32
+ return array;
60
33
  }
61
34
 
62
35
  reverse(array: any[]): any[] {
@@ -71,12 +44,26 @@ class ArrayHandler {
71
44
  return array.slice(start, end);
72
45
  }
73
46
 
74
- some(array: any[], callback: (value: any, index: number, array: any[]) => boolean): boolean {
75
- return array.some(callback);
76
- }
77
-
78
- sort(array: any[], compareFunction?: (a: any, b: any) => number): any[] {
79
- return array.sort(compareFunction);
47
+ sort(array: any[], order: 'ASCENDING' | 'DESCENDING' = 'ASCENDING'): any[] {
48
+ return array.sort((a, b) => {
49
+ if (order === 'ASCENDING') {
50
+ if (a === b) return 0;
51
+ if (a === null || a === undefined) return -1;
52
+ if (b === null || b === undefined) return 1;
53
+ if (typeof a === 'string' && typeof b === 'string') {
54
+ return a.localeCompare(b);
55
+ }
56
+ return a < b ? -1 : 1;
57
+ } else {
58
+ if (a === b) return 0;
59
+ if (a === null || a === undefined) return 1;
60
+ if (b === null || b === undefined) return -1;
61
+ if (typeof a === 'string' && typeof b === 'string') {
62
+ return b.localeCompare(a);
63
+ }
64
+ return a > b ? -1 : 1;
65
+ }
66
+ });
80
67
  }
81
68
 
82
69
  splice(array: any[], start: number, deleteCount?: number, ...items: any[]): any[] {
@@ -26,6 +26,10 @@ class ConditionalHandler {
26
26
  less_than_or_equal(value1: number, value2: number): boolean {
27
27
  return value1 <= value2;
28
28
  }
29
+
30
+ nullish(value1: any, value2: any): any {
31
+ return value1 ?? value2;
32
+ }
29
33
  }
30
34
 
31
35
  export { ConditionalHandler };
@@ -1,3 +1,5 @@
1
+ import { formatISODate } from "../../../modules/utils";
2
+
1
3
  type DateInput = Date | string | number;
2
4
 
3
5
  class DateHandler {
@@ -181,6 +183,10 @@ class DateHandler {
181
183
  return DateHandler.getDateInstance(date).toISOString();
182
184
  }
183
185
 
186
+ toISOXString(date?: DateInput): string {
187
+ return formatISODate(date ? DateHandler.getDateInstance(date) : new Date());
188
+ }
189
+
184
190
  toJSON(date: DateInput): string {
185
191
  return DateHandler.getDateInstance(date).toJSON();
186
192
  }
@@ -3,6 +3,7 @@ import { BitwiseHandler } from './bitwise';
3
3
  import { ConditionalHandler } from './conditional';
4
4
  import { DateHandler } from './date';
5
5
  import { JsonHandler } from './json';
6
+ import { LogicalHandler } from './logical';
6
7
  import { MathHandler } from './math';
7
8
  import { NumberHandler } from './number';
8
9
  import { ObjectHandler } from './object';
@@ -11,11 +12,12 @@ import { SymbolHandler } from './symbol';
11
12
  import { UnaryHandler } from './unary';
12
13
 
13
14
  export default {
14
- array: new ArrayHandler(),
15
- bitwise: new BitwiseHandler(),
16
- conditional: new ConditionalHandler(),
17
- date: new DateHandler(),
18
- json: new JsonHandler(),
15
+ array: new ArrayHandler(),
16
+ bitwise: new BitwiseHandler(),
17
+ conditional: new ConditionalHandler(),
18
+ date: new DateHandler(),
19
+ json: new JsonHandler(),
20
+ logical: new LogicalHandler(),
19
21
  math: new MathHandler(),
20
22
  number: new NumberHandler(),
21
23
  object: new ObjectHandler(),
@@ -0,0 +1,11 @@
1
+ class LogicalHandler {
2
+ and(firstValue: boolean, secondValue: boolean): boolean {
3
+ return firstValue && secondValue;
4
+ }
5
+
6
+ or(firstValue: boolean, secondValue: boolean): boolean {
7
+ return firstValue || secondValue;
8
+ }
9
+ }
10
+
11
+ export { LogicalHandler };
@@ -1,14 +1,33 @@
1
1
  class ObjectHandler {
2
+ get(obj: object, prop: string | symbol): any {
3
+ return obj?.[prop];
4
+ }
5
+
6
+ set(obj: object, prop: string | symbol, value: any): any {
7
+ if (!obj) obj = {};
8
+ obj[prop] = value;
9
+ return obj;
10
+ }
11
+
12
+ create(...args: any[]): object {
13
+ const obj = {};
14
+ if (args.length === 0) return obj;
15
+ for (let i = 0; i < args.length; i += 2) {
16
+ obj[args[i]] = args[i + 1];
17
+ }
18
+ return obj;
19
+ }
20
+
2
21
  keys(obj: object): string[] {
3
- return Object.keys(obj);
22
+ return obj && Object.keys(obj) || [];
4
23
  }
5
24
 
6
25
  values(obj: object): any[] {
7
- return Object.values(obj);
26
+ return obj && Object.values(obj) || [];
8
27
  }
9
28
 
10
29
  entries(obj: object): [string, any][] {
11
- return Object.entries(obj);
30
+ return obj && Object.entries(obj) || [];
12
31
  }
13
32
 
14
33
  fromEntries(iterable: Iterable<[string, any]>): object {
@@ -16,19 +35,19 @@ class ObjectHandler {
16
35
  }
17
36
 
18
37
  assign(target: object, ...sources: object[]): object {
19
- return Object.assign(target, ...sources);
38
+ return Object.assign(target || {}, ...sources);
20
39
  }
21
40
 
22
41
  getOwnPropertyNames(obj: object): string[] {
23
- return Object.getOwnPropertyNames(obj);
42
+ return Object.getOwnPropertyNames(obj || {});
24
43
  }
25
44
 
26
45
  getOwnPropertySymbols(obj: object): symbol[] {
27
- return Object.getOwnPropertySymbols(obj);
46
+ return Object.getOwnPropertySymbols(obj || {});
28
47
  }
29
48
 
30
49
  getOwnPropertyDescriptor(obj: object, prop: string | symbol): PropertyDescriptor | undefined {
31
- return Object.getOwnPropertyDescriptor(obj, prop);
50
+ return Object.getOwnPropertyDescriptor(obj || {}, prop);
32
51
  }
33
52
 
34
53
  defineProperty(obj: object, prop: string | symbol, descriptor: PropertyDescriptor): object {
@@ -1,21 +1,34 @@
1
1
  import FUNCTIONS from './functions'
2
2
  import { JobState, JobData, JobsData } from '../../types/job';
3
- import { PipeItem, PipeItems, Pipe as PipeType } from '../../types/pipe';
3
+ import {
4
+ PipeContext,
5
+ PipeItem,
6
+ PipeItems,
7
+ PipeObject,
8
+ Pipe as PipeType,
9
+ ReduceObject } from '../../types/pipe';
4
10
 
5
11
  class Pipe {
6
12
  rules: PipeType;
7
13
  jobData: JobData;
14
+ context: PipeContext;
8
15
 
9
- constructor(rules: PipeType, jobData: JobData) {
16
+ constructor(rules: PipeType, jobData: JobData, context?: PipeContext) {
10
17
  this.rules = rules;
11
18
  this.jobData = jobData;
19
+ this.context = context;
12
20
  }
13
21
 
14
- private isPipeType(currentRow: PipeItem[]|PipeType): currentRow is PipeType {
22
+ private isPipeType(currentRow: PipeItem[] | PipeType | PipeObject | ReduceObject): currentRow is PipeType {
15
23
  return !Array.isArray(currentRow) && '@pipe' in currentRow;
16
24
  }
17
25
 
18
- static isPipeObject(obj: { [key: string]: unknown }|PipeItem): boolean {
26
+ private isreduceType(currentRow: PipeItem[] | PipeType | PipeObject | ReduceObject): currentRow is PipeType {
27
+ return !Array.isArray(currentRow) && '@reduce' in currentRow;
28
+ }
29
+
30
+
31
+ static isPipeObject(obj: { [key: string]: unknown } | PipeItem): boolean {
19
32
  return typeof obj === 'object' && obj !== null && !Array.isArray(obj) && '@pipe' in obj;
20
33
  }
21
34
 
@@ -33,36 +46,82 @@ class Pipe {
33
46
  * loop through each PipeItem row in this Pipe, resolving and transforming line by line
34
47
  * @returns {any} the result of the pipe
35
48
  */
36
- process(): any {
37
- let resolved = this.processCells(this.rules[0] as PipeItem[]);
49
+ process(resolved: unknown[] | null = null): any {
50
+ let index = 0;
51
+ if (!(resolved || this.isPipeType(this.rules[0]) || this.isreduceType(this.rules[0]))) {
52
+ resolved = this.processCells(this.rules[0] as PipeItem[]); // Add type assertion
53
+ index = 1;
54
+ }
38
55
  const len = this.rules.length;
39
- for (let i = 1; i < len; i++) {
40
- resolved = this.processRow(this.rules[i], resolved, []);
56
+ const subPipeQueue = [];
57
+ for (let i = index; i < len; i++) {
58
+ resolved = this.processRow(this.rules[i], resolved, subPipeQueue);
41
59
  }
42
60
  return resolved[0];
43
61
  }
44
62
 
45
- private processRow(currentRow: PipeItem[]|PipeType, resolvedPriorRow: unknown[]|null, subPipeQueue: unknown[]): PipeItem[] {
46
- if (this.isPipeType(currentRow)) {
47
- //currentRow is a recursive subPipe
48
- const subPipe = new Pipe(currentRow['@pipe'], this.jobData);
63
+ /**
64
+ * Transforms iterable `input` into a single value. Vars $output, $item, $key
65
+ * and $input are available. The final statement in the iterator (the reduction)
66
+ * is assumed to be the return value. A default $output object may be provided
67
+ * to the iterator by placing the the second cell of the preceding row. Otherwise,
68
+ * construct the object during first run and ensure it is the first cell of the
69
+ * last row of the iterator, so it is returned as the $output for the next cycle
70
+ * @param {unknown[]} input
71
+ * @returns {unknown}
72
+ * @private
73
+ */
74
+ reduce(input: Array<unknown[]>): unknown {
75
+ let resolved = input[1] ?? null;
76
+
77
+ if (Array.isArray(input[0])) {
78
+ for (let index = 0; index < input[0].length; index++) {
79
+ this.context = { $input: input[0], $output: resolved, $item: input[0][index], $key: index.toString(), $index: index };
80
+ resolved = this.process([resolved]);
81
+ }
82
+ } else {
83
+ let index = -1;
84
+ for (let $key in (input[0] as Record<string, unknown>)) {
85
+ index++;
86
+ this.context = { $input: input[0], $output: resolved, $item: input[0][$key], $key, $index: index };
87
+ resolved = this.process([resolved]);
88
+ }
89
+ }
90
+ return [resolved];
91
+ }
92
+
93
+ private processRow(currentRow: PipeItem[] | PipeType | PipeObject | ReduceObject , resolvedPriorRow: unknown[]|null, subPipeQueue: unknown[]): PipeItem[] {
94
+ if (resolvedPriorRow && this.isreduceType(currentRow)) {
95
+ //reduce the resolvedPriorRow and return the output from the reducer
96
+ const subPipe = new Pipe(currentRow['@reduce'], this.jobData);
97
+ const reduced = subPipe.reduce(resolvedPriorRow as Array<unknown[]>)
98
+ return reduced as PipeItem[];
99
+ } else if (this.isPipeType(currentRow)) {
100
+ //process subPipe and push to subPipeQueue; echo resolvedPriorRow
101
+ const subPipe = new Pipe(currentRow['@pipe'], this.jobData, this.context);
49
102
  subPipeQueue.push(subPipe.process());
50
- //return prior row as if nothing happened
51
103
  return resolvedPriorRow as PipeItem[];
52
104
  } else {
105
+ //pivot the subPipeQueue into the arguments array (resolvedPriorRow)
53
106
  if (subPipeQueue.length > 0) {
54
- //if items in subPipeQueue, flush and use as resolvedPriorRow
55
107
  resolvedPriorRow = [...subPipeQueue];
56
108
  subPipeQueue.length = 0;
57
- } else if (!resolvedPriorRow) {
109
+ }
110
+
111
+ if (!resolvedPriorRow) {
58
112
  //if no prior row, use current row as prior row
59
- return [].concat(this.processCells([...currentRow]));
113
+ return [].concat(this.processCells(Array.isArray(currentRow) ? [...currentRow] as PipeItem[] : []));
60
114
  } else {
61
- const [functionName, ...params] = currentRow;
115
+ const [functionName, ...params] = currentRow as PipeItem[]; // Add type assertion
62
116
  //use resolved values from prior row (n - 1) as input params to cell 1 function
63
- const resolvedValue = Pipe.resolveFunction(functionName as string)(...resolvedPriorRow);
117
+ let resolvedValue: unknown;
118
+ if (this.isContextVariable(functionName)) {
119
+ resolvedValue = this.resolveContextValue(functionName as string);
120
+ } else {
121
+ resolvedValue = Pipe.resolveFunction(functionName as string)(...resolvedPriorRow);
122
+ }
64
123
  //resolve remaining cells in row and return concatenated with resolvedValue
65
- return [resolvedValue].concat(this.processCells([...params]));
124
+ return [(resolvedValue as PipeItem)].concat(this.processCells([...params]));
66
125
  }
67
126
  }
68
127
  }
@@ -83,8 +142,10 @@ class Pipe {
83
142
 
84
143
  processCells(cells: PipeItems): unknown[] {
85
144
  const resolved = [];
86
- for (const currentCell of cells) {
87
- resolved.push(this.resolveCellValue(currentCell));
145
+ if (Array.isArray(cells)) {
146
+ for (const currentCell of cells) {
147
+ resolved.push(this.resolveCellValue(currentCell));
148
+ }
88
149
  }
89
150
  return resolved;
90
151
  }
@@ -93,6 +154,12 @@ class Pipe {
93
154
  return typeof currentCell === 'string' && currentCell.startsWith('{@') && currentCell.endsWith('}');
94
155
  }
95
156
 
157
+ private isContextVariable(currentCell: PipeItem): boolean {
158
+ if(typeof currentCell === 'string' && currentCell.endsWith('}')) {
159
+ return currentCell.startsWith('{$item') || currentCell.startsWith('{$key') || currentCell.startsWith('{$index') || currentCell.startsWith('{$input') || currentCell.startsWith('{$output');
160
+ }
161
+ }
162
+
96
163
  private isMappable(currentCell: PipeItem): boolean {
97
164
  return typeof currentCell === 'string' && currentCell.startsWith('{') && currentCell.endsWith('}');
98
165
  }
@@ -101,6 +168,8 @@ class Pipe {
101
168
  if (this.isFunction(currentCell)) {
102
169
  const fn = Pipe.resolveFunction(currentCell as string);
103
170
  return fn.call();
171
+ } else if (this.isContextVariable(currentCell)) {
172
+ return this.resolveContextValue(currentCell as string);
104
173
  } else if (this.isMappable(currentCell)) {
105
174
  return this.resolveMappableValue(currentCell as string);
106
175
  } else {
@@ -126,6 +195,15 @@ class Pipe {
126
195
  return this.getNestedProperty(this.jobData, term);
127
196
  }
128
197
 
198
+ resolveContextValue(currentCell: string): unknown {
199
+ const term = this.resolveContextTerm(currentCell);
200
+ return this.getNestedProperty(this.context, term);
201
+ }
202
+
203
+ resolveContextTerm(currentCell: string): string {
204
+ return currentCell.substring(1, currentCell.length - 1);
205
+ }
206
+
129
207
  resolveFunctionTerm(currentCell: string): string {
130
208
  return currentCell.substring(2, currentCell.length - 1);
131
209
  }
@@ -25,9 +25,7 @@ import {
25
25
  QuorumMessageCallback,
26
26
  QuorumProfile,
27
27
  RollCallMessage,
28
- SubscriptionCallback,
29
- ThrottleMessage
30
- } from '../../types/quorum';
28
+ SubscriptionCallback } from '../../types/quorum';
31
29
  import { RedisClient, RedisMulti } from '../../types/redis';
32
30
  import { RedisClientType } from '../../types/redisclient';
33
31
 
@@ -194,7 +194,7 @@ class Router {
194
194
  try {
195
195
  output = await callback(input);
196
196
  } catch (error) {
197
- this.logger.error(`stream-call-function-error`, { error });
197
+ this.logger.error(`stream-call-function-error`, { ...error, input: input, stack: error.stack, message: error.message, name: error.name, stream, id });
198
198
  output = this.structureUnhandledError(input, error);
199
199
  }
200
200
  return output as StreamDataResponse;
@@ -233,10 +233,19 @@ class Router {
233
233
  }
234
234
 
235
235
  shouldRetry(input: StreamData, output: StreamDataResponse): [boolean, number] {
236
+ //const isUnhandledEngineError = output.code === 500;
236
237
  const policies = input.policies?.retry;
237
238
  const errorCode = output.code.toString();
238
239
  const policy = policies?.[errorCode];
239
240
  const maxRetries = policy?.[0];
241
+ // if (isUnhandledEngineError && !policy) {
242
+ // //if main goes down, replicas take over within 5s
243
+ // //if this is not system/platform related, the exponential
244
+ // //backoff will be applied and eventually slow to a crawl while
245
+ // //the root cause is identified
246
+ // input.policies = { retry: { [errorCode]: [10] } };
247
+ // return [true, 0];
248
+ // }
240
249
  const tryCount = Math.min(input.metadata.try || 0, HMSH_MAX_RETRIES);
241
250
  //only possible values for maxRetries are 1, 2, 3
242
251
  //only possible values for tryCount are 0, 1, 2
@@ -287,13 +296,15 @@ class Router {
287
296
  const message = output.data?.message ? output.data?.message.toString() : HMSH_STATUS_UNKNOWN;
288
297
  const statusCode = output.code || output.data?.code;
289
298
  const code = isNaN(statusCode as number) ? HMSH_CODE_UNKNOWN : parseInt(statusCode.toString());
290
- const data: StreamError = { message, code };
299
+ const stack = output.data?.stack ? output.data?.stack.toString() : undefined;
300
+ const data: StreamError = { message, code, stack };
291
301
  if (typeof output.data?.error === 'object') {
292
302
  data.error = { ...output.data.error };
293
303
  }
294
304
  return {
295
305
  status: StreamStatus.ERROR,
296
306
  code,
307
+ stack,
297
308
  metadata: { ...input.metadata, guid: guid() },
298
309
  data
299
310
  } as StreamDataResponse;
@@ -362,7 +373,7 @@ class Router {
362
373
  //The stream activity was not processed within established limits. Possibilities Include:
363
374
  // 1) user error: the workers were not properly configured and are timing out
364
375
  // 2a) system error: JSON is corrupt
365
- // i) bad/unwitting actor
376
+ // i) unwitting actor
366
377
  // ii) corrupt hardware/network/transport/etc
367
378
  // 3b) system error: Redis unable to accept `xadd` request
368
379
  // 4c) system error: Redis unable to accept `xdel`/`xack` request
@@ -136,7 +136,10 @@ export class SerializerService {
136
136
  let shortKey = abbreviationMap.get(key) || key;
137
137
  const shortDimensionalKey = `${shortKey}${dimensionalIndex}`;
138
138
  result[shortDimensionalKey] = source[key];
139
- }
139
+ } else if (!(key in result) && this.isLiteralKeyType(key)) {
140
+ //mark (-) and search (_)
141
+ result[key] = source[key];
142
+ }
140
143
  }
141
144
  };
142
145
  for (let id of ids) {
@@ -148,6 +151,10 @@ export class SerializerService {
148
151
  return result;
149
152
  }
150
153
 
154
+ isLiteralKeyType(key: string): boolean {
155
+ return key.startsWith('-') || key.startsWith('_');
156
+ }
157
+
151
158
  decompress(document: StringStringType, ids: string[]): StringStringType {
152
159
  if (this.symKeys.size === 0) {
153
160
  return document;
@@ -179,7 +186,7 @@ export class SerializerService {
179
186
  stringify(document: Record<string, any>): StringStringType {
180
187
  let result: StringStringType = {};
181
188
  for (let key in document) {
182
- let value = this.toString(document[key]);
189
+ let value = SerializerService.toString(document[key]);
183
190
  if (value) {
184
191
  if (/^:*[a-zA-Z]{2}$/.test(value)) {
185
192
  value = ':' + value;
@@ -203,13 +210,13 @@ export class SerializerService {
203
210
  if (value?.length === 2 && this.symValMaps.has(value)) {
204
211
  value = this.symValMaps.get(value);
205
212
  }
206
- result[key] = this.fromString(value);
213
+ result[key] = SerializerService.fromString(value);
207
214
  }
208
215
  }
209
216
  return result;
210
217
  }
211
218
 
212
- toString(value: any): string|undefined {
219
+ static toString(value: any): string|undefined {
213
220
  switch (typeof value) {
214
221
  case 'string':
215
222
  break;
@@ -232,7 +239,7 @@ export class SerializerService {
232
239
  return value;
233
240
  }
234
241
 
235
- fromString(value: string|undefined): any {
242
+ static fromString(value: string|undefined): any {
236
243
  if (typeof value !== 'string') return undefined;
237
244
  const prefix = value.slice(0, 2);
238
245
  const rest = value.slice(2);