@hotmeshio/hotmesh 0.0.54 → 0.0.56

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 (182) hide show
  1. package/README.md +0 -3
  2. package/build/modules/enums.js +1 -10
  3. package/build/modules/key.d.ts +0 -38
  4. package/build/modules/key.js +4 -46
  5. package/build/modules/utils.d.ts +0 -8
  6. package/build/modules/utils.js +0 -14
  7. package/build/package.json +11 -4
  8. package/build/services/activities/activity.d.ts +0 -28
  9. package/build/services/activities/activity.js +1 -46
  10. package/build/services/activities/await.js +0 -4
  11. package/build/services/activities/cycle.d.ts +0 -7
  12. package/build/services/activities/cycle.js +1 -16
  13. package/build/services/activities/hook.d.ts +0 -6
  14. package/build/services/activities/hook.js +2 -12
  15. package/build/services/activities/interrupt.js +0 -8
  16. package/build/services/activities/signal.d.ts +0 -6
  17. package/build/services/activities/signal.js +0 -15
  18. package/build/services/activities/trigger.d.ts +0 -4
  19. package/build/services/activities/trigger.js +1 -7
  20. package/build/services/activities/worker.js +0 -4
  21. package/build/services/collator/index.d.ts +0 -70
  22. package/build/services/collator/index.js +1 -91
  23. package/build/services/compiler/deployer.js +6 -38
  24. package/build/services/compiler/index.d.ts +0 -15
  25. package/build/services/compiler/index.js +0 -20
  26. package/build/services/compiler/validator.d.ts +0 -3
  27. package/build/services/compiler/validator.js +0 -25
  28. package/build/services/connector/clients/ioredis.d.ts +2 -2
  29. package/build/services/connector/clients/ioredis.js +0 -2
  30. package/build/services/connector/clients/redis.d.ts +4 -4
  31. package/build/services/connector/clients/redis.js +1 -3
  32. package/build/services/connector/index.d.ts +1 -1
  33. package/build/services/connector/index.js +0 -2
  34. package/build/services/durable/client.d.ts +1 -26
  35. package/build/services/durable/client.js +0 -56
  36. package/build/services/durable/exporter.d.ts +0 -22
  37. package/build/services/durable/exporter.js +1 -30
  38. package/build/services/durable/handle.d.ts +0 -36
  39. package/build/services/durable/handle.js +0 -46
  40. package/build/services/durable/index.d.ts +0 -4
  41. package/build/services/durable/index.js +0 -4
  42. package/build/services/durable/schemas/factory.d.ts +0 -29
  43. package/build/services/durable/schemas/factory.js +0 -29
  44. package/build/services/durable/search.d.ts +1 -36
  45. package/build/services/durable/search.js +57 -56
  46. package/build/services/durable/worker.js +2 -22
  47. package/build/services/durable/workflow.d.ts +0 -114
  48. package/build/services/durable/workflow.js +4 -144
  49. package/build/services/engine/index.d.ts +1 -6
  50. package/build/services/engine/index.js +1 -43
  51. package/build/services/exporter/index.d.ts +0 -27
  52. package/build/services/exporter/index.js +0 -33
  53. package/build/services/hotmesh/index.d.ts +2 -2
  54. package/build/services/hotmesh/index.js +1 -9
  55. package/build/services/logger/index.js +0 -2
  56. package/build/services/mapper/index.d.ts +0 -14
  57. package/build/services/mapper/index.js +0 -14
  58. package/build/services/pipe/functions/date.d.ts +0 -7
  59. package/build/services/pipe/functions/date.js +0 -7
  60. package/build/services/pipe/functions/math.js +0 -2
  61. package/build/services/pipe/index.d.ts +0 -15
  62. package/build/services/pipe/index.js +2 -23
  63. package/build/services/quorum/index.d.ts +0 -7
  64. package/build/services/quorum/index.js +0 -21
  65. package/build/services/reporter/index.d.ts +0 -5
  66. package/build/services/reporter/index.js +0 -9
  67. package/build/services/router/index.d.ts +0 -9
  68. package/build/services/router/index.js +2 -38
  69. package/build/services/serializer/index.js +7 -26
  70. package/build/services/store/cache.d.ts +0 -18
  71. package/build/services/store/cache.js +0 -18
  72. package/build/services/store/clients/ioredis.d.ts +1 -1
  73. package/build/services/store/clients/ioredis.js +0 -1
  74. package/build/services/store/clients/redis.d.ts +1 -1
  75. package/build/services/store/index.d.ts +0 -55
  76. package/build/services/store/index.js +5 -81
  77. package/build/services/stream/clients/ioredis.d.ts +1 -1
  78. package/build/services/stream/clients/ioredis.js +1 -4
  79. package/build/services/stream/clients/redis.d.ts +1 -1
  80. package/build/services/sub/clients/ioredis.d.ts +1 -1
  81. package/build/services/sub/clients/redis.d.ts +1 -1
  82. package/build/services/task/index.d.ts +0 -9
  83. package/build/services/task/index.js +0 -31
  84. package/build/services/telemetry/index.d.ts +0 -7
  85. package/build/services/telemetry/index.js +1 -13
  86. package/build/services/worker/index.d.ts +0 -4
  87. package/build/services/worker/index.js +2 -6
  88. package/build/types/activity.d.ts +0 -81
  89. package/build/types/durable.d.ts +25 -177
  90. package/build/types/exporter.d.ts +0 -13
  91. package/build/types/hotmesh.d.ts +4 -16
  92. package/build/types/hotmesh.js +0 -3
  93. package/build/types/index.d.ts +4 -6
  94. package/build/types/index.js +4 -3
  95. package/build/types/job.d.ts +1 -86
  96. package/build/types/pipe.d.ts +0 -65
  97. package/build/types/quorum.d.ts +15 -10
  98. package/build/types/redis.d.ts +225 -7
  99. package/build/types/redis.js +9 -0
  100. package/build/types/stream.d.ts +0 -58
  101. package/build/types/stream.js +0 -4
  102. package/package.json +11 -4
  103. package/types/durable.ts +121 -3
  104. package/types/hotmesh.ts +3 -6
  105. package/types/index.ts +23 -10
  106. package/types/job.ts +1 -1
  107. package/types/quorum.ts +22 -0
  108. package/types/redis.ts +267 -18
  109. package/build/types/ioredisclient.d.ts +0 -5
  110. package/build/types/ioredisclient.js +0 -5
  111. package/build/types/redisclient.d.ts +0 -26
  112. package/build/types/redisclient.js +0 -2
  113. package/modules/enums.ts +0 -62
  114. package/modules/errors.ts +0 -280
  115. package/modules/key.ts +0 -101
  116. package/modules/storage.ts +0 -3
  117. package/modules/utils.ts +0 -242
  118. package/services/activities/activity.ts +0 -589
  119. package/services/activities/await.ts +0 -113
  120. package/services/activities/cycle.ts +0 -115
  121. package/services/activities/hook.ts +0 -197
  122. package/services/activities/index.ts +0 -19
  123. package/services/activities/interrupt.ts +0 -172
  124. package/services/activities/signal.ts +0 -148
  125. package/services/activities/trigger.ts +0 -295
  126. package/services/activities/worker.ts +0 -107
  127. package/services/collator/README.md +0 -102
  128. package/services/collator/index.ts +0 -291
  129. package/services/compiler/deployer.ts +0 -504
  130. package/services/compiler/index.ts +0 -98
  131. package/services/compiler/validator.ts +0 -158
  132. package/services/connector/clients/ioredis.ts +0 -57
  133. package/services/connector/clients/redis.ts +0 -72
  134. package/services/connector/index.ts +0 -42
  135. package/services/durable/client.ts +0 -266
  136. package/services/durable/connection.ts +0 -10
  137. package/services/durable/exporter.ts +0 -232
  138. package/services/durable/handle.ts +0 -160
  139. package/services/durable/index.ts +0 -27
  140. package/services/durable/schemas/factory.ts +0 -2358
  141. package/services/durable/search.ts +0 -196
  142. package/services/durable/worker.ts +0 -401
  143. package/services/durable/workflow.ts +0 -557
  144. package/services/engine/index.ts +0 -761
  145. package/services/exporter/index.ts +0 -146
  146. package/services/hotmesh/index.ts +0 -237
  147. package/services/logger/index.ts +0 -79
  148. package/services/mapper/index.ts +0 -89
  149. package/services/pipe/functions/array.ts +0 -78
  150. package/services/pipe/functions/bitwise.ts +0 -27
  151. package/services/pipe/functions/conditional.ts +0 -35
  152. package/services/pipe/functions/date.ts +0 -220
  153. package/services/pipe/functions/index.ts +0 -27
  154. package/services/pipe/functions/json.ts +0 -11
  155. package/services/pipe/functions/logical.ts +0 -11
  156. package/services/pipe/functions/math.ts +0 -217
  157. package/services/pipe/functions/number.ts +0 -75
  158. package/services/pipe/functions/object.ts +0 -98
  159. package/services/pipe/functions/string.ts +0 -86
  160. package/services/pipe/functions/symbol.ts +0 -39
  161. package/services/pipe/functions/unary.ts +0 -19
  162. package/services/pipe/index.ts +0 -216
  163. package/services/quorum/index.ts +0 -319
  164. package/services/reporter/index.ts +0 -387
  165. package/services/router/index.ts +0 -426
  166. package/services/serializer/README.md +0 -10
  167. package/services/serializer/index.ts +0 -285
  168. package/services/store/cache.ts +0 -172
  169. package/services/store/clients/ioredis.ts +0 -145
  170. package/services/store/clients/redis.ts +0 -191
  171. package/services/store/index.ts +0 -1091
  172. package/services/stream/clients/ioredis.ts +0 -157
  173. package/services/stream/clients/redis.ts +0 -158
  174. package/services/stream/index.ts +0 -58
  175. package/services/sub/clients/ioredis.ts +0 -83
  176. package/services/sub/clients/redis.ts +0 -74
  177. package/services/sub/index.ts +0 -25
  178. package/services/task/index.ts +0 -250
  179. package/services/telemetry/index.ts +0 -273
  180. package/services/worker/index.ts +0 -248
  181. package/types/ioredisclient.ts +0 -10
  182. package/types/redisclient.ts +0 -30
@@ -1,86 +0,0 @@
1
- class StringHandler {
2
- split(input: string, delimiter: string): string[] {
3
- return input.split(delimiter);
4
- }
5
-
6
- charAt(input: string, index: number): string {
7
- return input.charAt(index);
8
- }
9
-
10
- concat(...strings: string[]): string {
11
- return strings.join('');
12
- }
13
-
14
- includes(input: string, searchString: string, position?: number): boolean {
15
- return input.includes(searchString, position);
16
- }
17
-
18
- indexOf(input: string, searchString: string, fromIndex?: number): number {
19
- return input.indexOf(searchString, fromIndex);
20
- }
21
-
22
- lastIndexOf(input: string, searchString: string, fromIndex?: number): number {
23
- return input.lastIndexOf(searchString, fromIndex);
24
- }
25
-
26
- slice(input: string, start?: number, end?: number): string {
27
- return input.slice(start, end);
28
- }
29
-
30
- toLowerCase(input: string): string {
31
- return input.toLowerCase();
32
- }
33
-
34
- toUpperCase(input: string): string {
35
- return input.toUpperCase();
36
- }
37
-
38
- trim(input: string): string {
39
- return input.trim();
40
- }
41
-
42
- trimStart(input: string): string {
43
- return input.trimStart();
44
- }
45
-
46
- trimEnd(input: string): string {
47
- return input.trimEnd();
48
- }
49
-
50
- padStart(input: string, maxLength: number, padString?: string): string {
51
- return input.padStart(maxLength, padString);
52
- }
53
-
54
- padEnd(input: string, maxLength: number, padString?: string): string {
55
- return input.padEnd(maxLength, padString);
56
- }
57
-
58
- replace(input: string, searchValue: string | RegExp, replaceValue: string): string {
59
- return input.replace(searchValue, replaceValue);
60
- }
61
-
62
- search(input: string, regexp: RegExp): number {
63
- return input.search(regexp);
64
- }
65
-
66
- substring(input: string, start: number, end?: number): string {
67
- return input.substring(start, end);
68
- }
69
-
70
- startsWith(str: string, searchString: string, position?: number): boolean {
71
- return str.startsWith(searchString, position);
72
- }
73
-
74
- endsWith(str: string, searchString: string, length?: number): boolean {
75
- return str.endsWith(searchString, length);
76
- }
77
-
78
- repeat(str: string, count: number): string {
79
- if (count < 0 || count === Infinity) {
80
- throw new RangeError('Invalid repeat count. Must be a positive finite number.');
81
- }
82
- return str.repeat(count);
83
- }
84
- }
85
-
86
- export { StringHandler };
@@ -1,39 +0,0 @@
1
- class SymbolHandler {
2
- null(): null {
3
- return null;
4
- }
5
-
6
- undefined(): undefined {
7
- return undefined;
8
- }
9
-
10
- whitespace(): string {
11
- return ' ';
12
- }
13
-
14
- object(): object {
15
- return {};
16
- }
17
-
18
- array(): any[] {
19
- return [];
20
- }
21
-
22
- posInfinity(): number {
23
- return Infinity;
24
- }
25
-
26
- negInfinity(): number {
27
- return -Infinity;
28
- }
29
-
30
- NaN(): number {
31
- return NaN;
32
- }
33
-
34
- date(): Date {
35
- return new Date();
36
- }
37
- }
38
-
39
- export { SymbolHandler };
@@ -1,19 +0,0 @@
1
- class UnaryHandler {
2
- not(value: boolean): boolean {
3
- return !value;
4
- }
5
-
6
- positive(value: number): number {
7
- return +value;
8
- }
9
-
10
- negative(value: number): number {
11
- return -value;
12
- }
13
-
14
- bitwise_not(value: number): number {
15
- return ~value;
16
- }
17
- }
18
-
19
- export { UnaryHandler };
@@ -1,216 +0,0 @@
1
- import FUNCTIONS from './functions'
2
- import { JobState, JobData, JobsData } from '../../types/job';
3
- import {
4
- PipeContext,
5
- PipeItem,
6
- PipeItems,
7
- PipeObject,
8
- Pipe as PipeType,
9
- ReduceObject } from '../../types/pipe';
10
-
11
- class Pipe {
12
- rules: PipeType;
13
- jobData: JobData;
14
- context: PipeContext;
15
-
16
- constructor(rules: PipeType, jobData: JobData, context?: PipeContext) {
17
- this.rules = rules;
18
- this.jobData = jobData;
19
- this.context = context;
20
- }
21
-
22
- private isPipeType(currentRow: PipeItem[] | PipeType | PipeObject | ReduceObject): currentRow is PipeType {
23
- return !Array.isArray(currentRow) && '@pipe' in currentRow;
24
- }
25
-
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 {
32
- return typeof obj === 'object' && obj !== null && !Array.isArray(obj) && '@pipe' in obj;
33
- }
34
-
35
- static resolve(unresolved: { [key: string]: unknown }|PipeItem, context: Partial<JobState>): any {
36
- let pipe: Pipe;
37
- if (Pipe.isPipeObject(unresolved)) {
38
- pipe = new Pipe(unresolved['@pipe'], context);
39
- } else {
40
- pipe = new Pipe([[unresolved as unknown as PipeItem]], context);
41
- }
42
- return pipe.process();
43
- }
44
-
45
- /**
46
- * loop through each PipeItem row in this Pipe, resolving and transforming line by line
47
- * @returns {any} the result of the pipe
48
- */
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
- }
55
- const len = this.rules.length;
56
- const subPipeQueue = [];
57
- for (let i = index; i < len; i++) {
58
- resolved = this.processRow(this.rules[i], resolved, subPipeQueue);
59
- }
60
- return resolved[0];
61
- }
62
-
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);
102
- subPipeQueue.push(subPipe.process());
103
- return resolvedPriorRow as PipeItem[];
104
- } else {
105
- //pivot the subPipeQueue into the arguments array (resolvedPriorRow)
106
- if (subPipeQueue.length > 0) {
107
- resolvedPriorRow = [...subPipeQueue];
108
- subPipeQueue.length = 0;
109
- }
110
-
111
- if (!resolvedPriorRow) {
112
- //if no prior row, use current row as prior row
113
- return [].concat(this.processCells(Array.isArray(currentRow) ? [...currentRow] as PipeItem[] : []));
114
- } else {
115
- const [functionName, ...params] = currentRow as PipeItem[]; // Add type assertion
116
- //use resolved values from prior row (n - 1) as input params to cell 1 function
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
- }
123
- //resolve remaining cells in row and return concatenated with resolvedValue
124
- return [(resolvedValue as PipeItem)].concat(this.processCells([...params]));
125
- }
126
- }
127
- }
128
-
129
- static resolveFunction(functionName: string) {
130
- let [prefix, suffix] = functionName.split('.');
131
- prefix = prefix.substring(2);
132
- suffix = suffix.substring(0, suffix.length - 1);
133
- const domain = FUNCTIONS[prefix];
134
- if (!domain) {
135
- throw new Error(`Unknown domain name [${functionName}]: ${prefix}`);
136
- }
137
- if (!domain[suffix]) {
138
- throw new Error(`Unknown domain function [${functionName}]: ${prefix}.${suffix}`);
139
- }
140
- return domain[suffix];
141
- }
142
-
143
- processCells(cells: PipeItems): unknown[] {
144
- const resolved = [];
145
- if (Array.isArray(cells)) {
146
- for (const currentCell of cells) {
147
- resolved.push(this.resolveCellValue(currentCell));
148
- }
149
- }
150
- return resolved;
151
- }
152
-
153
- private isFunction(currentCell: PipeItem): boolean {
154
- return typeof currentCell === 'string' && currentCell.startsWith('{@') && currentCell.endsWith('}');
155
- }
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
-
163
- private isMappable(currentCell: PipeItem): boolean {
164
- return typeof currentCell === 'string' && currentCell.startsWith('{') && currentCell.endsWith('}');
165
- }
166
-
167
- resolveCellValue(currentCell: PipeItem): unknown {
168
- if (this.isFunction(currentCell)) {
169
- const fn = Pipe.resolveFunction(currentCell as string);
170
- return fn.call();
171
- } else if (this.isContextVariable(currentCell)) {
172
- return this.resolveContextValue(currentCell as string);
173
- } else if (this.isMappable(currentCell)) {
174
- return this.resolveMappableValue(currentCell as string);
175
- } else {
176
- return currentCell;
177
- }
178
- }
179
-
180
- private getNestedProperty(obj: JobsData|unknown, path: string): any {
181
- const pathParts = path.split('.');
182
- let current = obj;
183
- for (const part of pathParts) {
184
- if (current === null || typeof current !== 'object' || !current.hasOwnProperty(part)) {
185
- return undefined;
186
- }
187
- current = current[part];
188
- }
189
-
190
- return current;
191
- }
192
-
193
- resolveMappableValue(currentCell: string): unknown {
194
- const term = this.resolveMapTerm(currentCell);
195
- return this.getNestedProperty(this.jobData, term);
196
- }
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
-
207
- resolveFunctionTerm(currentCell: string): string {
208
- return currentCell.substring(2, currentCell.length - 1);
209
- }
210
-
211
- resolveMapTerm(currentCell: string): string {
212
- return currentCell.substring(1, currentCell.length - 1);
213
- }
214
- }
215
-
216
- export { Pipe };
@@ -1,319 +0,0 @@
1
- import {
2
- HMSH_ACTIVATION_MAX_RETRY,
3
- HMSH_QUORUM_DELAY_MS,
4
- HMSH_QUORUM_ROLLCALL_CYCLES} from '../../modules/enums';
5
- import {
6
- XSleepFor,
7
- formatISODate,
8
- getSystemHealth,
9
- identifyRedisType,
10
- sleepFor } from '../../modules/utils';
11
- import { CompilerService } from '../compiler';
12
- import { EngineService } from '../engine';
13
- import { ILogger } from '../logger';
14
- import { StoreService } from '../store';
15
- import { IORedisStoreService as IORedisStore } from '../store/clients/ioredis';
16
- import { RedisStoreService as RedisStore } from '../store/clients/redis';
17
- import { SubService } from '../sub';
18
- import { IORedisSubService as IORedisSub } from '../sub/clients/ioredis';
19
- import { RedisSubService as RedisSub } from '../sub/clients/redis';
20
- import { CacheMode } from '../../types/cache';
21
- import { HotMeshConfig, KeyType } from '../../types/hotmesh';
22
- import { RedisClientType as IORedisClientType } from '../../types/ioredisclient';
23
- import {
24
- QuorumMessage,
25
- QuorumMessageCallback,
26
- QuorumProfile,
27
- RollCallMessage,
28
- SubscriptionCallback } from '../../types/quorum';
29
- import { RedisClient, RedisMulti } from '../../types/redis';
30
- import { RedisClientType } from '../../types/redisclient';
31
-
32
- class QuorumService {
33
- namespace: string;
34
- appId: string;
35
- guid: string;
36
- engine: EngineService;
37
- profiles: QuorumProfile[] = [];
38
- store: StoreService<RedisClient, RedisMulti> | null;
39
- subscribe: SubService<RedisClient, RedisMulti> | null;
40
- logger: ILogger;
41
- cacheMode: CacheMode = 'cache';
42
- untilVersion: string | null = null;
43
- quorum: number | null = null;
44
- callbacks: QuorumMessageCallback[] = [];
45
- rollCallInterval: NodeJS.Timeout;
46
-
47
- static async init(
48
- namespace: string,
49
- appId: string,
50
- guid: string,
51
- config: HotMeshConfig,
52
- engine: EngineService,
53
- logger: ILogger
54
- ): Promise<QuorumService> {
55
- if (config.engine) {
56
- const instance = new QuorumService();
57
- instance.verifyQuorumFields(config);
58
- instance.namespace = namespace;
59
- instance.appId = appId;
60
- instance.guid = guid;
61
- instance.logger = logger;
62
- instance.engine = engine;
63
-
64
- //note: `quorum` shares/re-uses the engine's `store`/`sub` Redis clients
65
- await instance.initStoreChannel(config.engine.store);
66
- await instance.initSubChannel(config.engine.sub);
67
- //general quorum subscription
68
- await instance.subscribe.subscribe(
69
- KeyType.QUORUM,
70
- instance.subscriptionHandler(),
71
- appId
72
- );
73
- //app-specific quorum subscription (used for pubsub one-time request/response)
74
- await instance.subscribe.subscribe(
75
- KeyType.QUORUM,
76
- instance.subscriptionHandler(),
77
- appId, instance.guid
78
- );
79
-
80
- instance.engine.processWebHooks();
81
- instance.engine.processTimeHooks();
82
- return instance;
83
- }
84
- }
85
-
86
- verifyQuorumFields(config: HotMeshConfig) {
87
- if (!identifyRedisType(config.engine.store) ||
88
- !identifyRedisType(config.engine.sub)) {
89
- throw new Error('quorum config must include `store` and `sub` fields.');
90
- }
91
- }
92
-
93
- async initStoreChannel(store: RedisClient) {
94
- if (identifyRedisType(store) === 'redis') {
95
- this.store = new RedisStore(store as RedisClientType);
96
- } else {
97
- this.store = new IORedisStore(store as IORedisClientType);
98
- }
99
- await this.store.init(
100
- this.namespace,
101
- this.appId,
102
- this.logger
103
- );
104
- }
105
-
106
- async initSubChannel(sub: RedisClient) {
107
- if (identifyRedisType(sub) === 'redis') {
108
- this.subscribe = new RedisSub(sub as RedisClientType);
109
- } else {
110
- this.subscribe = new IORedisSub(sub as IORedisClientType);
111
- }
112
- await this.subscribe.init(
113
- this.namespace,
114
- this.appId,
115
- this.guid,
116
- this.logger
117
- );
118
- }
119
-
120
- subscriptionHandler(): SubscriptionCallback {
121
- const self = this;
122
- return async (topic: string, message: QuorumMessage) => {
123
- self.logger.debug('quorum-event-received', { topic, type: message.type});
124
- if (message.type === 'activate') {
125
- self.engine.setCacheMode(message.cache_mode, message.until_version);
126
- } else if (message.type === 'ping') {
127
- self.sayPong(self.appId, self.guid, message.originator, message.details);
128
- } else if (message.type === 'pong' && self.guid === message.originator) {
129
- self.quorum = self.quorum + 1;
130
- if (message.profile) {
131
- self.profiles.push(message.profile);
132
- }
133
- } else if (message.type === 'throttle') {
134
- self.engine.throttle(message.throttle);
135
- } else if (message.type === 'work') {
136
- self.engine.processWebHooks()
137
- } else if (message.type === 'job') {
138
- self.engine.routeToSubscribers(message.topic, message.job)
139
- } else if (message.type === 'cron') {
140
- self.engine.processTimeHooks();
141
- } else if (message.type === 'rollcall') {
142
- self.doRollCall(message);
143
- }
144
- //if there are any callbacks, call them
145
- if (self.callbacks.length > 0) {
146
- self.callbacks.forEach(cb => cb(topic, message));
147
- }
148
- };
149
- }
150
-
151
- async sayPong(appId: string, guid: string, originator: string, details = false) {
152
- let profile: QuorumProfile;
153
- if (details) {
154
- const stream = this.engine.stream.mintKey(
155
- KeyType.STREAMS,
156
- { appId: this.appId }
157
- );
158
- profile = {
159
- engine_id: this.guid,
160
- namespace: this.namespace,
161
- app_id: this.appId,
162
- stream,
163
- counts: this.engine.router.counts,
164
- timestamp: formatISODate(new Date()),
165
- inited: this.engine.inited,
166
- throttle: this.engine.router.throttle,
167
- reclaimDelay: this.engine.router.reclaimDelay,
168
- reclaimCount: this.engine.router.reclaimCount,
169
- system: await getSystemHealth(),
170
- };
171
- }
172
- this.store.publish(
173
- KeyType.QUORUM,
174
- {
175
- type: 'pong',
176
- guid, originator,
177
- profile,
178
- },
179
- appId,
180
- );
181
- }
182
-
183
- async requestQuorum(delay = HMSH_QUORUM_DELAY_MS, details = false): Promise<number> {
184
- const quorum = this.quorum;
185
- this.quorum = 0;
186
- this.profiles.length = 0;
187
- await this.store.publish(
188
- KeyType.QUORUM,
189
- {
190
- type: 'ping',
191
- originator: this.guid,
192
- details,
193
- },
194
- this.appId,
195
- );
196
- await sleepFor(delay);
197
- return quorum;
198
- }
199
-
200
- /**
201
- * A quorum-wide command to broadcaset system details.
202
- *
203
- */
204
- async doRollCall(message: RollCallMessage) {
205
- let iteration = 0;
206
- let max = !isNaN(message.max) ? message.max : HMSH_QUORUM_ROLLCALL_CYCLES;
207
- if (this.rollCallInterval) clearTimeout(this.rollCallInterval);
208
- const base = (message.interval / 2);
209
- const amount = base + Math.ceil(Math.random() * base);
210
- do {
211
- await sleepFor(Math.ceil(Math.random() * 1000));
212
- await this.sayPong(this.appId, this.guid, null, true);
213
- if (!message.interval) return;
214
- const { promise, timerId } = XSleepFor(amount * 1000);
215
- this.rollCallInterval = timerId;
216
- await promise;
217
- } while (this.rollCallInterval && iteration++ < max - 1);
218
- }
219
-
220
- cancelRollCall() {
221
- if (this.rollCallInterval) {
222
- clearTimeout(this.rollCallInterval);
223
- delete this.rollCallInterval;
224
- }
225
- }
226
-
227
- stop() {
228
- this.cancelRollCall();
229
- }
230
-
231
- // ************* PUB/SUB METHODS *************
232
- //publish a message to the quorum
233
- async pub(quorumMessage: QuorumMessage) {
234
- return await this.store.publish(KeyType.QUORUM, quorumMessage, this.appId, quorumMessage.topic || quorumMessage.guid);
235
- }
236
- //subscribe user to quorum messages
237
- async sub(callback: QuorumMessageCallback): Promise<void> {
238
- //the quorum is always subscribed to the `quorum` topic; just register the fn
239
- this.callbacks.push(callback);
240
- }
241
- //unsubscribe user from quorum messages
242
- async unsub(callback: QuorumMessageCallback): Promise<void> {
243
- //the quorum is always subscribed to the `quorum` topic; just unregister the fn
244
- this.callbacks = this.callbacks.filter(cb => cb !== callback);
245
- }
246
-
247
-
248
- // ************* COMPILER METHODS *************
249
- async rollCall(delay = HMSH_QUORUM_DELAY_MS): Promise<QuorumProfile[]> {
250
- await this.requestQuorum(delay, true);
251
- const targetStreams = [];
252
- const multi = this.store.getMulti();
253
- this.profiles.forEach((profile: QuorumProfile) => {
254
- if (!targetStreams.includes(profile.stream)) {
255
- targetStreams.push(profile.stream);
256
- this.store.xlen(profile.stream, multi);
257
- }
258
- });
259
- const stream_depths = await multi.exec() as number[];
260
- this.profiles.forEach(async (profile: QuorumProfile) => {
261
- const index = targetStreams.indexOf(profile.stream);
262
- if (index != -1) {
263
- profile.stream_depth = Array.isArray(stream_depths[index]) ?
264
- stream_depths[index][1] :
265
- stream_depths[index];
266
- }
267
- });
268
- return this.profiles;
269
- }
270
- /**
271
- * request a quorum; if successful activate the app version
272
- */
273
- async activate(version: string, delay = HMSH_QUORUM_DELAY_MS, count = 0): Promise<boolean> {
274
- version = version.toString();
275
- const canActivate = await this.store.reserveScoutRole('activate', Math.ceil(delay * 6 / 1000) + 1);
276
- if (!canActivate) {
277
- //another engine is already activating the app version
278
- this.logger.debug('quorum-activation-awaiting', { version });
279
- await sleepFor(delay * 6);
280
- const app = await this.store.getApp(this.appId, true);
281
- return app?.active == true && app?.version === version;
282
- }
283
- const config = await this.engine.getVID();
284
- await this.requestQuorum(delay);
285
- const q1 = await this.requestQuorum(delay);
286
- const q2 = await this.requestQuorum(delay);
287
- const q3 = await this.requestQuorum(delay);
288
- if (q1 && q1 === q2 && q2 === q3) {
289
- this.logger.info('quorum-rollcall-succeeded', { q1, q2, q3 });
290
- this.store.publish(
291
- KeyType.QUORUM,
292
- { type: 'activate', cache_mode: 'nocache', until_version: version },
293
- this.appId
294
- );
295
- await new Promise(resolve => setTimeout(resolve, delay));
296
- await this.store.releaseScoutRole('activate');
297
- //confirm we received the activation message
298
- if (this.engine.untilVersion === version) {
299
- this.logger.info('quorum-activation-succeeded', { version });
300
- const { id } = config;
301
- const compiler = new CompilerService(this.store, this.logger);
302
- return await compiler.activate(id, version);
303
- } else {
304
- this.logger.error('quorum-activation-error', { version });
305
- throw new Error(`UntilVersion Not Received. Version ${version} not activated`);
306
- }
307
- } else {
308
- this.logger.warn('quorum-rollcall-error', { q1, q2, q3, count });
309
- this.store.releaseScoutRole('activate');
310
- if (count < HMSH_ACTIVATION_MAX_RETRY) {
311
- //increase the delay (give the quorum time to respond) and try again
312
- return await this.activate(version, delay * 2, count + 1);
313
- }
314
- throw new Error(`Quorum not reached. Version ${version} not activated.`);
315
- }
316
- }
317
- }
318
-
319
- export { QuorumService }