@hotmeshio/hotmesh 0.0.1

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 (263) hide show
  1. package/LICENSE +214 -0
  2. package/README.md +241 -0
  3. package/build/index.d.ts +4 -0
  4. package/build/index.js +7 -0
  5. package/build/modules/errors.d.ts +28 -0
  6. package/build/modules/errors.js +50 -0
  7. package/build/modules/key.d.ts +75 -0
  8. package/build/modules/key.js +116 -0
  9. package/build/modules/utils.d.ts +34 -0
  10. package/build/modules/utils.js +173 -0
  11. package/build/package.json +73 -0
  12. package/build/services/activities/activity.d.ts +59 -0
  13. package/build/services/activities/activity.js +396 -0
  14. package/build/services/activities/await.d.ts +16 -0
  15. package/build/services/activities/await.js +143 -0
  16. package/build/services/activities/emit.d.ts +9 -0
  17. package/build/services/activities/emit.js +13 -0
  18. package/build/services/activities/index.d.ts +15 -0
  19. package/build/services/activities/index.js +16 -0
  20. package/build/services/activities/iterate.d.ts +9 -0
  21. package/build/services/activities/iterate.js +13 -0
  22. package/build/services/activities/trigger.d.ts +22 -0
  23. package/build/services/activities/trigger.js +161 -0
  24. package/build/services/activities/worker.d.ts +17 -0
  25. package/build/services/activities/worker.js +164 -0
  26. package/build/services/collator/index.d.ts +54 -0
  27. package/build/services/collator/index.js +171 -0
  28. package/build/services/compiler/deployer.d.ts +35 -0
  29. package/build/services/compiler/deployer.js +412 -0
  30. package/build/services/compiler/index.d.ts +30 -0
  31. package/build/services/compiler/index.js +111 -0
  32. package/build/services/compiler/validator.d.ts +32 -0
  33. package/build/services/compiler/validator.js +134 -0
  34. package/build/services/connector/clients/ioredis.d.ts +13 -0
  35. package/build/services/connector/clients/ioredis.js +50 -0
  36. package/build/services/connector/clients/redis.d.ts +13 -0
  37. package/build/services/connector/clients/redis.js +62 -0
  38. package/build/services/connector/index.d.ts +5 -0
  39. package/build/services/connector/index.js +31 -0
  40. package/build/services/dimension/index.d.ts +29 -0
  41. package/build/services/dimension/index.js +35 -0
  42. package/build/services/durable/asyncLocalStorage.d.ts +3 -0
  43. package/build/services/durable/asyncLocalStorage.js +5 -0
  44. package/build/services/durable/client.d.ts +15 -0
  45. package/build/services/durable/client.js +108 -0
  46. package/build/services/durable/connection.d.ts +4 -0
  47. package/build/services/durable/connection.js +51 -0
  48. package/build/services/durable/factory.d.ts +3 -0
  49. package/build/services/durable/factory.js +123 -0
  50. package/build/services/durable/handle.d.ts +8 -0
  51. package/build/services/durable/handle.js +38 -0
  52. package/build/services/durable/index.d.ts +57 -0
  53. package/build/services/durable/index.js +58 -0
  54. package/build/services/durable/native.d.ts +4 -0
  55. package/build/services/durable/native.js +47 -0
  56. package/build/services/durable/worker.d.ts +36 -0
  57. package/build/services/durable/worker.js +266 -0
  58. package/build/services/durable/workflow.d.ts +6 -0
  59. package/build/services/durable/workflow.js +135 -0
  60. package/build/services/engine/index.d.ts +82 -0
  61. package/build/services/engine/index.js +511 -0
  62. package/build/services/hotmesh/index.d.ts +45 -0
  63. package/build/services/hotmesh/index.js +134 -0
  64. package/build/services/logger/index.d.ts +17 -0
  65. package/build/services/logger/index.js +73 -0
  66. package/build/services/mapper/index.d.ts +24 -0
  67. package/build/services/mapper/index.js +72 -0
  68. package/build/services/pipe/functions/array.d.ts +24 -0
  69. package/build/services/pipe/functions/array.js +69 -0
  70. package/build/services/pipe/functions/bitwise.d.ts +9 -0
  71. package/build/services/pipe/functions/bitwise.js +24 -0
  72. package/build/services/pipe/functions/conditional.d.ts +10 -0
  73. package/build/services/pipe/functions/conditional.js +27 -0
  74. package/build/services/pipe/functions/date.d.ts +57 -0
  75. package/build/services/pipe/functions/date.js +167 -0
  76. package/build/services/pipe/functions/index.d.ts +25 -0
  77. package/build/services/pipe/functions/index.js +26 -0
  78. package/build/services/pipe/functions/json.d.ts +5 -0
  79. package/build/services/pipe/functions/json.js +12 -0
  80. package/build/services/pipe/functions/math.d.ts +38 -0
  81. package/build/services/pipe/functions/math.js +111 -0
  82. package/build/services/pipe/functions/number.d.ts +25 -0
  83. package/build/services/pipe/functions/number.js +133 -0
  84. package/build/services/pipe/functions/object.d.ts +22 -0
  85. package/build/services/pipe/functions/object.js +63 -0
  86. package/build/services/pipe/functions/string.d.ts +23 -0
  87. package/build/services/pipe/functions/string.js +69 -0
  88. package/build/services/pipe/functions/symbol.d.ts +12 -0
  89. package/build/services/pipe/functions/symbol.js +33 -0
  90. package/build/services/pipe/functions/unary.d.ts +7 -0
  91. package/build/services/pipe/functions/unary.js +18 -0
  92. package/build/services/pipe/index.d.ts +30 -0
  93. package/build/services/pipe/index.js +128 -0
  94. package/build/services/quorum/index.d.ts +34 -0
  95. package/build/services/quorum/index.js +147 -0
  96. package/build/services/reporter/index.d.ts +47 -0
  97. package/build/services/reporter/index.js +330 -0
  98. package/build/services/serializer/index.d.ts +36 -0
  99. package/build/services/serializer/index.js +222 -0
  100. package/build/services/signaler/store.d.ts +15 -0
  101. package/build/services/signaler/store.js +53 -0
  102. package/build/services/signaler/stream.d.ts +43 -0
  103. package/build/services/signaler/stream.js +317 -0
  104. package/build/services/store/cache.d.ts +66 -0
  105. package/build/services/store/cache.js +127 -0
  106. package/build/services/store/clients/ioredis.d.ts +27 -0
  107. package/build/services/store/clients/ioredis.js +96 -0
  108. package/build/services/store/clients/redis.d.ts +29 -0
  109. package/build/services/store/clients/redis.js +143 -0
  110. package/build/services/store/index.d.ts +88 -0
  111. package/build/services/store/index.js +657 -0
  112. package/build/services/stream/clients/ioredis.d.ts +23 -0
  113. package/build/services/stream/clients/ioredis.js +115 -0
  114. package/build/services/stream/clients/redis.d.ts +23 -0
  115. package/build/services/stream/clients/redis.js +119 -0
  116. package/build/services/stream/index.d.ts +21 -0
  117. package/build/services/stream/index.js +9 -0
  118. package/build/services/sub/clients/ioredis.d.ts +20 -0
  119. package/build/services/sub/clients/ioredis.js +72 -0
  120. package/build/services/sub/clients/redis.d.ts +20 -0
  121. package/build/services/sub/clients/redis.js +63 -0
  122. package/build/services/sub/index.d.ts +18 -0
  123. package/build/services/sub/index.js +9 -0
  124. package/build/services/task/index.d.ts +18 -0
  125. package/build/services/task/index.js +73 -0
  126. package/build/services/telemetry/index.d.ts +49 -0
  127. package/build/services/telemetry/index.js +223 -0
  128. package/build/services/worker/index.d.ts +30 -0
  129. package/build/services/worker/index.js +105 -0
  130. package/build/types/activity.d.ts +86 -0
  131. package/build/types/activity.js +2 -0
  132. package/build/types/app.d.ts +16 -0
  133. package/build/types/app.js +2 -0
  134. package/build/types/async.d.ts +5 -0
  135. package/build/types/async.js +2 -0
  136. package/build/types/cache.d.ts +1 -0
  137. package/build/types/cache.js +2 -0
  138. package/build/types/collator.d.ts +8 -0
  139. package/build/types/collator.js +11 -0
  140. package/build/types/durable.d.ts +59 -0
  141. package/build/types/durable.js +2 -0
  142. package/build/types/hook.d.ts +31 -0
  143. package/build/types/hook.js +9 -0
  144. package/build/types/hotmesh.d.ts +82 -0
  145. package/build/types/hotmesh.js +2 -0
  146. package/build/types/index.d.ts +20 -0
  147. package/build/types/index.js +21 -0
  148. package/build/types/ioredisclient.d.ts +5 -0
  149. package/build/types/ioredisclient.js +5 -0
  150. package/build/types/job.d.ts +50 -0
  151. package/build/types/job.js +2 -0
  152. package/build/types/logger.d.ts +6 -0
  153. package/build/types/logger.js +2 -0
  154. package/build/types/map.d.ts +4 -0
  155. package/build/types/map.js +2 -0
  156. package/build/types/pipe.d.ts +4 -0
  157. package/build/types/pipe.js +2 -0
  158. package/build/types/quorum.d.ts +46 -0
  159. package/build/types/quorum.js +2 -0
  160. package/build/types/redis.d.ts +8 -0
  161. package/build/types/redis.js +2 -0
  162. package/build/types/redisclient.d.ts +25 -0
  163. package/build/types/redisclient.js +2 -0
  164. package/build/types/serializer.d.ts +33 -0
  165. package/build/types/serializer.js +2 -0
  166. package/build/types/stats.d.ts +83 -0
  167. package/build/types/stats.js +2 -0
  168. package/build/types/stream.d.ts +67 -0
  169. package/build/types/stream.js +25 -0
  170. package/build/types/telemetry.d.ts +1 -0
  171. package/build/types/telemetry.js +11 -0
  172. package/build/types/transition.d.ts +17 -0
  173. package/build/types/transition.js +2 -0
  174. package/index.ts +5 -0
  175. package/modules/errors.ts +55 -0
  176. package/modules/key.ts +129 -0
  177. package/modules/utils.ts +170 -0
  178. package/package.json +73 -0
  179. package/services/activities/activity.ts +473 -0
  180. package/services/activities/await.ts +172 -0
  181. package/services/activities/emit.ts +25 -0
  182. package/services/activities/index.ts +15 -0
  183. package/services/activities/iterate.ts +26 -0
  184. package/services/activities/trigger.ts +196 -0
  185. package/services/activities/worker.ts +190 -0
  186. package/services/collator/README.md +102 -0
  187. package/services/collator/index.ts +182 -0
  188. package/services/compiler/deployer.ts +432 -0
  189. package/services/compiler/index.ts +98 -0
  190. package/services/compiler/validator.ts +154 -0
  191. package/services/connector/clients/ioredis.ts +57 -0
  192. package/services/connector/clients/redis.ts +72 -0
  193. package/services/connector/index.ts +44 -0
  194. package/services/dimension/README.md +73 -0
  195. package/services/dimension/index.ts +39 -0
  196. package/services/durable/asyncLocalStorage.ts +3 -0
  197. package/services/durable/client.ts +116 -0
  198. package/services/durable/connection.ts +50 -0
  199. package/services/durable/factory.ts +124 -0
  200. package/services/durable/handle.ts +43 -0
  201. package/services/durable/index.ts +60 -0
  202. package/services/durable/native.ts +46 -0
  203. package/services/durable/worker.ts +254 -0
  204. package/services/durable/workflow.ts +136 -0
  205. package/services/engine/index.ts +615 -0
  206. package/services/hotmesh/index.ts +182 -0
  207. package/services/logger/index.ts +79 -0
  208. package/services/mapper/index.ts +84 -0
  209. package/services/pipe/functions/array.ts +87 -0
  210. package/services/pipe/functions/bitwise.ts +27 -0
  211. package/services/pipe/functions/conditional.ts +31 -0
  212. package/services/pipe/functions/date.ts +214 -0
  213. package/services/pipe/functions/index.ts +25 -0
  214. package/services/pipe/functions/json.ts +11 -0
  215. package/services/pipe/functions/math.ts +143 -0
  216. package/services/pipe/functions/number.ts +150 -0
  217. package/services/pipe/functions/object.ts +79 -0
  218. package/services/pipe/functions/string.ts +86 -0
  219. package/services/pipe/functions/symbol.ts +39 -0
  220. package/services/pipe/functions/unary.ts +19 -0
  221. package/services/pipe/index.ts +138 -0
  222. package/services/quorum/index.ts +200 -0
  223. package/services/reporter/index.ts +379 -0
  224. package/services/serializer/README.md +10 -0
  225. package/services/serializer/index.ts +243 -0
  226. package/services/signaler/store.ts +61 -0
  227. package/services/signaler/stream.ts +354 -0
  228. package/services/store/cache.ts +172 -0
  229. package/services/store/clients/ioredis.ts +123 -0
  230. package/services/store/clients/redis.ts +169 -0
  231. package/services/store/index.ts +757 -0
  232. package/services/stream/clients/ioredis.ts +148 -0
  233. package/services/stream/clients/redis.ts +144 -0
  234. package/services/stream/index.ts +57 -0
  235. package/services/sub/clients/ioredis.ts +83 -0
  236. package/services/sub/clients/redis.ts +74 -0
  237. package/services/sub/index.ts +25 -0
  238. package/services/task/index.ts +86 -0
  239. package/services/telemetry/index.ts +267 -0
  240. package/services/worker/index.ts +165 -0
  241. package/types/activity.ts +115 -0
  242. package/types/app.ts +20 -0
  243. package/types/async.ts +7 -0
  244. package/types/cache.ts +1 -0
  245. package/types/collator.ts +9 -0
  246. package/types/durable.ts +81 -0
  247. package/types/hook.ts +32 -0
  248. package/types/hotmesh.ts +102 -0
  249. package/types/index.ts +138 -0
  250. package/types/ioredisclient.ts +10 -0
  251. package/types/job.ts +59 -0
  252. package/types/logger.ts +6 -0
  253. package/types/map.ts +5 -0
  254. package/types/ms.d.ts +7 -0
  255. package/types/pipe.ts +7 -0
  256. package/types/quorum.ts +59 -0
  257. package/types/redis.ts +27 -0
  258. package/types/redisclient.ts +29 -0
  259. package/types/serializer.ts +38 -0
  260. package/types/stats.ts +100 -0
  261. package/types/stream.ts +75 -0
  262. package/types/telemetry.ts +15 -0
  263. package/types/transition.ts +20 -0
@@ -0,0 +1,36 @@
1
+ import { StringStringType, StringAnyType, SymbolMap, SymbolMaps, SymbolSets, Symbols } from '../../types/serializer';
2
+ export declare const MDATA_SYMBOLS: {
3
+ SLOTS: number;
4
+ ACTIVITY: {
5
+ KEYS: string[];
6
+ };
7
+ ACTIVITY_UPDATE: {
8
+ KEYS: string[];
9
+ };
10
+ JOB: {
11
+ KEYS: string[];
12
+ };
13
+ JOB_UPDATE: {
14
+ KEYS: string[];
15
+ };
16
+ };
17
+ export declare class SerializerService {
18
+ symKeys: SymbolMaps;
19
+ symReverseKeys: SymbolMaps;
20
+ symValMaps: SymbolMap;
21
+ symValReverseMaps: SymbolMap;
22
+ constructor();
23
+ resetSymbols(symKeys: SymbolSets, symVals: Symbols): void;
24
+ getReverseKeyMap(keyMap: SymbolMap, id?: string): SymbolMap;
25
+ getReverseValueMap(valueMap: SymbolMap): SymbolMap;
26
+ static filterSymVals(startIndex: number, maxIndex: number, existingSymbolValues: Symbols, proposedValues: Set<string>): Symbols;
27
+ compress(document: StringStringType, ids: string[]): StringStringType;
28
+ decompress(document: StringStringType, ids: string[]): StringStringType;
29
+ stringify(document: Record<string, any>): StringStringType;
30
+ parse(document: StringStringType): any;
31
+ toString(value: any): string | undefined;
32
+ fromString(value: string | undefined): any;
33
+ package(multiDimensionalDocument: StringAnyType, ids: string[]): StringStringType;
34
+ unpackage(document: StringStringType, ids: string[]): StringAnyType;
35
+ export(): SymbolSets;
36
+ }
@@ -0,0 +1,222 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SerializerService = exports.MDATA_SYMBOLS = void 0;
4
+ const utils_1 = require("../../modules/utils");
5
+ const dateReg = /^"\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z)?"$/;
6
+ exports.MDATA_SYMBOLS = {
7
+ SLOTS: 26,
8
+ ACTIVITY: {
9
+ KEYS: ['aid', 'dad', 'as', 'atp', 'stp', 'ac', 'au', 'err', 'l1s', 'l2s']
10
+ },
11
+ ACTIVITY_UPDATE: {
12
+ KEYS: ['au', 'err', 'l2s']
13
+ },
14
+ JOB: {
15
+ KEYS: ['ngn', 'tpc', 'pj', 'pd', 'pa', 'key', 'app', 'vrs', 'jid', 'aid', 'ts', 'jc', 'ju', 'js', 'err', 'trc']
16
+ },
17
+ JOB_UPDATE: {
18
+ KEYS: ['ju', 'err']
19
+ }
20
+ };
21
+ class SerializerService {
22
+ constructor() {
23
+ this.resetSymbols({}, {});
24
+ }
25
+ resetSymbols(symKeys, symVals) {
26
+ this.symKeys = new Map();
27
+ this.symReverseKeys = new Map();
28
+ for (const id in symKeys) {
29
+ this.symKeys.set(id, new Map(Object.entries(symKeys[id])));
30
+ }
31
+ this.symValMaps = new Map(Object.entries(symVals));
32
+ this.symValReverseMaps = this.getReverseValueMap(this.symValMaps);
33
+ }
34
+ getReverseKeyMap(keyMap, id) {
35
+ let map = this.symReverseKeys.get(id);
36
+ if (!map) {
37
+ map = new Map();
38
+ for (let [key, val] of keyMap.entries()) {
39
+ map.set(val, key);
40
+ }
41
+ this.symReverseKeys.set(id, map);
42
+ }
43
+ return map;
44
+ }
45
+ getReverseValueMap(valueMap) {
46
+ const map = new Map();
47
+ for (let [key, val] of valueMap.entries()) {
48
+ map.set(val, key);
49
+ }
50
+ return map;
51
+ }
52
+ static filterSymVals(startIndex, maxIndex, existingSymbolValues, proposedValues) {
53
+ let newSymbolValues = {};
54
+ let currentSymbolValues = { ...existingSymbolValues };
55
+ let currentValuesSet = new Set(Object.values(currentSymbolValues));
56
+ for (let value of proposedValues) {
57
+ if (!currentValuesSet.has(value)) {
58
+ if (startIndex > maxIndex) {
59
+ return newSymbolValues;
60
+ }
61
+ const symbol = (0, utils_1.getSymVal)(startIndex);
62
+ startIndex++;
63
+ newSymbolValues[symbol] = value;
64
+ currentValuesSet.add(value);
65
+ }
66
+ }
67
+ return newSymbolValues;
68
+ }
69
+ compress(document, ids) {
70
+ if (this.symKeys.size === 0) {
71
+ return document;
72
+ }
73
+ let result = { ...document };
74
+ const compressWithMap = (abbreviationMap) => {
75
+ for (let key in result) {
76
+ let safeKey = abbreviationMap.get(key) || key;
77
+ let value = result[key];
78
+ let safeValue = abbreviationMap.get(value) || value;
79
+ if (safeKey !== key || safeValue !== value) {
80
+ result[safeKey] = safeValue;
81
+ if (safeKey !== key) {
82
+ delete result[key];
83
+ }
84
+ }
85
+ }
86
+ };
87
+ for (let id of ids) {
88
+ const abbreviationMap = this.symKeys.get(id);
89
+ if (abbreviationMap) {
90
+ compressWithMap(abbreviationMap);
91
+ }
92
+ }
93
+ return result;
94
+ }
95
+ decompress(document, ids) {
96
+ if (this.symKeys.size === 0) {
97
+ return document;
98
+ }
99
+ let result = { ...document };
100
+ const inflateWithMap = (abbreviationMap, id) => {
101
+ const reversedAbbreviationMap = this.getReverseKeyMap(abbreviationMap, id);
102
+ for (let key in result) {
103
+ let safeKey = reversedAbbreviationMap.get(key) || key;
104
+ let value = result[key];
105
+ let safeValue = reversedAbbreviationMap.get(value) || value;
106
+ if (safeKey !== key || safeValue !== value) {
107
+ result[safeKey] = safeValue;
108
+ if (safeKey !== key) {
109
+ delete result[key];
110
+ }
111
+ }
112
+ }
113
+ };
114
+ for (let id of ids) {
115
+ const abbreviationMap = this.symKeys.get(id);
116
+ if (abbreviationMap) {
117
+ inflateWithMap(abbreviationMap, id);
118
+ }
119
+ }
120
+ return result;
121
+ }
122
+ //stringify: convert a multi-dimensional document to a 2-d hash
123
+ stringify(document) {
124
+ let result = {};
125
+ for (let key in document) {
126
+ let value = this.toString(document[key]);
127
+ if (value) {
128
+ if (/^:*[a-zA-Z]{2}$/.test(value)) {
129
+ value = ':' + value;
130
+ }
131
+ else if (this.symValReverseMaps.has(value)) {
132
+ value = this.symValReverseMaps.get(value);
133
+ }
134
+ result[key] = value;
135
+ }
136
+ }
137
+ return result;
138
+ }
139
+ //parse: convert a 2-d hash to a multi-dimensional document
140
+ parse(document) {
141
+ let result = {};
142
+ for (let [key, value] of Object.entries(document)) {
143
+ if (value === undefined || value === null)
144
+ continue;
145
+ if (/^:+[a-zA-Z]{2}$/.test(value)) {
146
+ result[key] = value.slice(1);
147
+ }
148
+ else {
149
+ if (value?.length === 2 && this.symValMaps.has(value)) {
150
+ value = this.symValMaps.get(value);
151
+ }
152
+ result[key] = this.fromString(value);
153
+ }
154
+ }
155
+ return result;
156
+ }
157
+ toString(value) {
158
+ switch (typeof value) {
159
+ case 'string':
160
+ break;
161
+ case 'boolean':
162
+ value = value ? '/t' : '/f';
163
+ break;
164
+ case 'number':
165
+ value = '/d' + value.toString();
166
+ break;
167
+ case 'undefined':
168
+ return undefined;
169
+ case 'object':
170
+ if (value === null) {
171
+ value = '/n';
172
+ }
173
+ else {
174
+ value = '/s' + JSON.stringify(value);
175
+ }
176
+ break;
177
+ }
178
+ return value;
179
+ }
180
+ fromString(value) {
181
+ if (typeof value !== 'string')
182
+ return undefined;
183
+ const prefix = value.slice(0, 2);
184
+ const rest = value.slice(2);
185
+ switch (prefix) {
186
+ case '/t': // boolean true
187
+ return true;
188
+ case '/f': // boolean false
189
+ return false;
190
+ case '/d': // number
191
+ return Number(rest);
192
+ case '/n': // null
193
+ return null;
194
+ case '/s': // object (JSON string)
195
+ if (dateReg.exec(rest)) {
196
+ return new Date(JSON.parse(rest));
197
+ }
198
+ return JSON.parse(rest);
199
+ default: // string
200
+ return value;
201
+ }
202
+ }
203
+ package(multiDimensionalDocument, ids) {
204
+ const flatDocument = this.stringify(multiDimensionalDocument);
205
+ return this.compress(flatDocument, ids);
206
+ }
207
+ unpackage(document, ids) {
208
+ const multiDimensionalDocument = this.decompress(document, ids);
209
+ return this.parse(multiDimensionalDocument);
210
+ }
211
+ export() {
212
+ const obj = {};
213
+ for (const [id, map] of this.symKeys.entries()) {
214
+ obj[id] = {};
215
+ for (const [key, value] of map.entries()) {
216
+ obj[id][key] = value;
217
+ }
218
+ }
219
+ return obj;
220
+ }
221
+ }
222
+ exports.SerializerService = SerializerService;
@@ -0,0 +1,15 @@
1
+ import { ILogger } from '../logger';
2
+ import { StoreService } from '../store';
3
+ import { HookRule } from '../../types/hook';
4
+ import { JobState } from '../../types/job';
5
+ import { RedisClient, RedisMulti } from '../../types/redis';
6
+ declare class StoreSignaler {
7
+ store: StoreService<RedisClient, RedisMulti>;
8
+ logger: ILogger;
9
+ constructor(store: StoreService<RedisClient, RedisMulti>, logger: ILogger);
10
+ getHookRule(topic: string): Promise<HookRule | undefined>;
11
+ registerWebHook(topic: string, context: JobState, multi?: RedisMulti): Promise<string>;
12
+ processWebHookSignal(topic: string, data: Record<string, unknown>): Promise<string>;
13
+ deleteWebHookSignal(topic: string, data: Record<string, unknown>): Promise<number>;
14
+ }
15
+ export { StoreSignaler };
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StoreSignaler = void 0;
4
+ class StoreSignaler {
5
+ constructor(store, logger) {
6
+ this.store = store;
7
+ this.logger = logger;
8
+ }
9
+ async getHookRule(topic) {
10
+ const rules = await this.store.getHookRules();
11
+ return rules?.[topic]?.[0];
12
+ }
13
+ async registerWebHook(topic, context, multi) {
14
+ const hookRule = await this.getHookRule(topic);
15
+ if (hookRule) {
16
+ const jobId = context.metadata.jid;
17
+ const hook = {
18
+ topic,
19
+ resolved: jobId,
20
+ jobId,
21
+ };
22
+ await this.store.setHookSignal(hook, multi);
23
+ return jobId;
24
+ }
25
+ else {
26
+ throw new Error('signaler.registerWebHook:error: hook rule not found');
27
+ }
28
+ }
29
+ async processWebHookSignal(topic, data) {
30
+ const hookRule = await this.getHookRule(topic);
31
+ if (hookRule) {
32
+ //todo: use the rule to generate `resolved`
33
+ const resolved = data.id;
34
+ const jobId = await this.store.getHookSignal(topic, resolved);
35
+ return jobId;
36
+ }
37
+ else {
38
+ throw new Error('signaler.process:error: hook rule not found');
39
+ }
40
+ }
41
+ async deleteWebHookSignal(topic, data) {
42
+ const hookRule = await this.getHookRule(topic);
43
+ if (hookRule) {
44
+ //todo: use the rule to generate `resolved`
45
+ const resolved = data.id;
46
+ return await this.store.deleteHookSignal(topic, resolved);
47
+ }
48
+ else {
49
+ throw new Error('signaler.process:error: hook rule not found');
50
+ }
51
+ }
52
+ }
53
+ exports.StoreSignaler = StoreSignaler;
@@ -0,0 +1,43 @@
1
+ /// <reference types="node" />
2
+ import { ILogger } from '../logger';
3
+ import { StoreService } from '../store';
4
+ import { StreamService } from '../stream';
5
+ import { RedisClient, RedisMulti } from '../../types/redis';
6
+ import { ReclaimedMessageType, StreamConfig, StreamData, StreamDataResponse, StreamRole } from '../../types/stream';
7
+ declare class StreamSignaler {
8
+ static signalers: Set<StreamSignaler>;
9
+ appId: string;
10
+ guid: string;
11
+ role: StreamRole;
12
+ topic: string | undefined;
13
+ store: StoreService<RedisClient, RedisMulti>;
14
+ stream: StreamService<RedisClient, RedisMulti>;
15
+ reclaimDelay: number;
16
+ reclaimCount: number;
17
+ logger: ILogger;
18
+ throttle: number;
19
+ errorCount: number;
20
+ currentTimerId: NodeJS.Timeout | null;
21
+ shouldConsume: boolean;
22
+ constructor(config: StreamConfig, stream: StreamService<RedisClient, RedisMulti>, store: StoreService<RedisClient, RedisMulti>, logger: ILogger);
23
+ createGroup(stream: string, group: string): Promise<void>;
24
+ publishMessage(topic: string, streamData: StreamData | StreamDataResponse, multi?: RedisMulti): Promise<string | RedisMulti>;
25
+ consumeMessages(stream: string, group: string, consumer: string, callback: (streamData: StreamData) => Promise<StreamDataResponse | void>): Promise<void>;
26
+ isStreamMessage(result: any): boolean;
27
+ consumeOne(stream: string, group: string, id: string, message: string[], callback: (streamData: StreamData) => Promise<StreamDataResponse | void>): Promise<void>;
28
+ execStreamLeg(input: StreamData, stream: string, id: string, callback: (streamData: StreamData) => Promise<StreamDataResponse | void>): Promise<StreamDataResponse>;
29
+ ackAndDelete(stream: string, group: string, id: string): Promise<void>;
30
+ publishResponse(input: StreamData, output: StreamDataResponse | void): Promise<string>;
31
+ shouldRetry(input: StreamData, output: StreamDataResponse): [boolean, number];
32
+ structureUnhandledError(input: StreamData, err: Error): StreamDataResponse;
33
+ structureUnacknowledgedError(input: StreamData): StreamDataResponse;
34
+ structureError(input: StreamData, output: StreamDataResponse): StreamDataResponse;
35
+ static stopConsuming(): Promise<void>;
36
+ stopConsuming(): Promise<void>;
37
+ cancelThrottle(): void;
38
+ setThrottle(delayInMillis: number): void;
39
+ claimUnacknowledged(stream: string, group: string, consumer: string, idleTimeMs?: number, limit?: number): Promise<[string, [string, string]][]>;
40
+ expireUnacknowledged(reclaimedMessage: ReclaimedMessageType, stream: string, group: string, consumer: string, id: string, count: number): Promise<void>;
41
+ parseStreamData(str: string): [undefined, StreamData] | [Error];
42
+ }
43
+ export { StreamSignaler };
@@ -0,0 +1,317 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StreamSignaler = void 0;
4
+ const key_1 = require("../../modules/key");
5
+ const utils_1 = require("../../modules/utils");
6
+ const telemetry_1 = require("../telemetry");
7
+ const stream_1 = require("../../types/stream");
8
+ const MAX_RETRIES = 4; //max delay (10s using exponential backoff);
9
+ const MAX_TIMEOUT_MS = 60000;
10
+ const GRADUATED_INTERVAL_MS = 5000;
11
+ const BLOCK_DURATION = 15000; //Set to `15` so SIGINT/SIGTERM can interrupt; set to `0` to BLOCK indefinitely
12
+ const TEST_BLOCK_DURATION = 1000; //Set to `1000` so tests can interrupt quickly
13
+ const BLOCK_TIME_MS = process.env.NODE_ENV === 'test' ? TEST_BLOCK_DURATION : BLOCK_DURATION;
14
+ const SYSTEM_STATUS_CODE = 999;
15
+ const UNKNOWN_STATUS_CODE = 500;
16
+ const UNKNOWN_STATUS_MESSAGE = 'unknown';
17
+ const XCLAIM_DELAY_MS = 1000 * 60; //max time a message can be unacked before it is claimed by another
18
+ const XCLAIM_COUNT = 3; //max number of times a message can be claimed by another before it is dead-lettered
19
+ const XPENDING_COUNT = 10;
20
+ class StreamSignaler {
21
+ constructor(config, stream, store, logger) {
22
+ this.throttle = 0;
23
+ this.errorCount = 0;
24
+ this.currentTimerId = null;
25
+ this.appId = config.appId;
26
+ this.guid = config.guid;
27
+ this.role = config.role;
28
+ this.topic = config.topic;
29
+ this.stream = stream;
30
+ this.store = store;
31
+ this.reclaimDelay = config.reclaimDelay || XCLAIM_DELAY_MS;
32
+ this.reclaimCount = config.reclaimCount || XCLAIM_COUNT;
33
+ this.logger = logger;
34
+ }
35
+ async createGroup(stream, group) {
36
+ try {
37
+ await this.store.xgroup('CREATE', stream, group, '$', 'MKSTREAM');
38
+ }
39
+ catch (err) {
40
+ this.logger.info('consumer-group-exists', { stream, group });
41
+ }
42
+ }
43
+ async publishMessage(topic, streamData, multi) {
44
+ const stream = this.store.mintKey(key_1.KeyType.STREAMS, { appId: this.store.appId, topic });
45
+ return await this.store.xadd(stream, '*', 'message', JSON.stringify(streamData), multi);
46
+ }
47
+ async consumeMessages(stream, group, consumer, callback) {
48
+ this.logger.info(`stream-consumer-starting`, { group, consumer, stream });
49
+ StreamSignaler.signalers.add(this);
50
+ this.shouldConsume = true;
51
+ await this.createGroup(stream, group);
52
+ let lastCheckedPendingMessagesAt = Date.now();
53
+ async function consume() {
54
+ let sleep = (0, utils_1.XSleepFor)(this.throttle);
55
+ this.currentTimerId = sleep.timerId;
56
+ await sleep.promise;
57
+ if (!this.shouldConsume) {
58
+ this.logger.info(`stream-consumer-stopping`, { group, consumer, stream });
59
+ return;
60
+ }
61
+ try {
62
+ const result = await this.stream.xreadgroup('GROUP', group, consumer, 'BLOCK', BLOCK_TIME_MS, 'STREAMS', stream, '>');
63
+ if (this.isStreamMessage(result)) {
64
+ const [[, messages]] = result;
65
+ for (const [id, message] of messages) {
66
+ await this.consumeOne(stream, group, id, message, callback);
67
+ }
68
+ }
69
+ // Check for pending messages (note: Redis 6.2 simplifies)
70
+ const now = Date.now();
71
+ if (now - lastCheckedPendingMessagesAt > this.reclaimDelay) {
72
+ lastCheckedPendingMessagesAt = now;
73
+ const pendingMessages = await this.claimUnacknowledged(stream, group, consumer);
74
+ for (const [id, message] of pendingMessages) {
75
+ await this.consumeOne(stream, group, id, message, callback);
76
+ }
77
+ }
78
+ setImmediate(consume.bind(this));
79
+ }
80
+ catch (err) {
81
+ if (this.shouldConsume && process.env.NODE_ENV !== 'test') {
82
+ this.logger.error(`stream-consume-message-error`, { err, stream, group, consumer });
83
+ this.errorCount++;
84
+ const timeout = Math.min(GRADUATED_INTERVAL_MS * (2 ** this.errorCount), MAX_TIMEOUT_MS);
85
+ setTimeout(consume.bind(this), timeout);
86
+ }
87
+ }
88
+ }
89
+ consume.call(this);
90
+ }
91
+ isStreamMessage(result) {
92
+ return Array.isArray(result) && Array.isArray(result[0]);
93
+ }
94
+ async consumeOne(stream, group, id, message, callback) {
95
+ this.logger.debug(`stream-consume-one`, { group, stream, id });
96
+ const [err, input] = this.parseStreamData(message[1]);
97
+ let output;
98
+ let telemetry;
99
+ try {
100
+ telemetry = new telemetry_1.TelemetryService(this.appId);
101
+ telemetry.startStreamSpan(input, this.role);
102
+ output = await this.execStreamLeg(input, stream, id, callback.bind(this));
103
+ if (output?.status === stream_1.StreamStatus.ERROR) {
104
+ telemetry.setStreamError(`Function Status Code ${output.code || UNKNOWN_STATUS_CODE}`);
105
+ }
106
+ this.errorCount = 0;
107
+ }
108
+ catch (err) {
109
+ this.logger.error(`stream-consume-one-error`, { group, stream, id, err });
110
+ telemetry.setStreamError(err.message);
111
+ }
112
+ const messageId = await this.publishResponse(input, output);
113
+ telemetry.setStreamAttributes({ 'app.worker.mid': messageId });
114
+ await this.ackAndDelete(stream, group, id);
115
+ telemetry.endStreamSpan();
116
+ this.logger.debug(`stream-consume-one-end`, { group, stream, id });
117
+ }
118
+ async execStreamLeg(input, stream, id, callback) {
119
+ let output;
120
+ try {
121
+ output = await callback(input);
122
+ }
123
+ catch (err) {
124
+ this.logger.error(`stream-call-function-error`, { stream, id, err });
125
+ output = this.structureUnhandledError(input, err);
126
+ }
127
+ return output;
128
+ }
129
+ async ackAndDelete(stream, group, id) {
130
+ const multi = this.stream.getMulti();
131
+ await this.stream.xack(stream, group, id, multi);
132
+ await this.stream.xdel(stream, id, multi);
133
+ await multi.exec();
134
+ }
135
+ async publishResponse(input, output) {
136
+ if (output && typeof output === 'object') {
137
+ if (output.status === 'error') {
138
+ const [shouldRetry, timeout] = this.shouldRetry(input, output);
139
+ if (shouldRetry) {
140
+ await (0, utils_1.sleepFor)(timeout);
141
+ return await this.publishMessage(input.metadata.topic, {
142
+ data: input.data,
143
+ metadata: { ...input.metadata, try: (input.metadata.try || 0) + 1 },
144
+ policies: input.policies,
145
+ });
146
+ }
147
+ else {
148
+ output = this.structureError(input, output);
149
+ }
150
+ }
151
+ output.type = stream_1.StreamDataType.RESPONSE;
152
+ return await this.publishMessage(null, output);
153
+ }
154
+ }
155
+ shouldRetry(input, output) {
156
+ const policies = input.policies?.retry;
157
+ const errorCode = output.code.toString();
158
+ const policy = policies?.[errorCode];
159
+ const maxRetries = policy?.[0];
160
+ const tryCount = Math.min(input.metadata.try || 0, MAX_RETRIES);
161
+ if (maxRetries >= tryCount) {
162
+ return [true, Math.pow(10, tryCount)];
163
+ }
164
+ return [false, 0];
165
+ }
166
+ structureUnhandledError(input, err) {
167
+ let error = {};
168
+ if (typeof err.message === 'string') {
169
+ error.message = err.message;
170
+ }
171
+ else {
172
+ error.message = UNKNOWN_STATUS_MESSAGE;
173
+ }
174
+ if (typeof err.stack === 'string') {
175
+ error.stack = err.stack;
176
+ }
177
+ if (typeof err.name === 'string') {
178
+ error.name = err.name;
179
+ }
180
+ return {
181
+ status: 'error',
182
+ code: UNKNOWN_STATUS_CODE,
183
+ metadata: { ...input.metadata },
184
+ data: error
185
+ };
186
+ }
187
+ structureUnacknowledgedError(input) {
188
+ const message = 'stream message max delivery count exceeded';
189
+ const code = SYSTEM_STATUS_CODE;
190
+ const data = { message, code };
191
+ const output = {
192
+ metadata: { ...input.metadata },
193
+ status: stream_1.StreamStatus.ERROR,
194
+ code,
195
+ data,
196
+ };
197
+ //send unacknowleded errors to the engine (it has no topic)
198
+ delete output.metadata.topic;
199
+ return output;
200
+ }
201
+ structureError(input, output) {
202
+ const message = output.data?.message ? output.data?.message.toString() : UNKNOWN_STATUS_MESSAGE;
203
+ const statusCode = output.code || output.data?.code;
204
+ const code = isNaN(statusCode) ? UNKNOWN_STATUS_CODE : parseInt(statusCode.toString());
205
+ const data = { message, code };
206
+ if (typeof output.data?.error === 'object') {
207
+ data.error = { ...output.data.error };
208
+ }
209
+ return {
210
+ status: stream_1.StreamStatus.ERROR,
211
+ code,
212
+ metadata: { ...input.metadata },
213
+ data
214
+ };
215
+ }
216
+ static async stopConsuming() {
217
+ for (const instance of [...StreamSignaler.signalers]) {
218
+ instance.stopConsuming();
219
+ }
220
+ await (0, utils_1.sleepFor)(BLOCK_TIME_MS);
221
+ }
222
+ async stopConsuming() {
223
+ this.shouldConsume = false;
224
+ this.logger.info(`stream-consumer-stopping`, this.topic ? { topic: this.topic } : undefined);
225
+ this.cancelThrottle();
226
+ await (0, utils_1.sleepFor)(BLOCK_TIME_MS);
227
+ }
228
+ cancelThrottle() {
229
+ if (this.currentTimerId !== undefined) {
230
+ clearTimeout(this.currentTimerId);
231
+ this.currentTimerId = undefined;
232
+ }
233
+ }
234
+ setThrottle(delayInMillis) {
235
+ if (!Number.isInteger(delayInMillis) || delayInMillis < 0) {
236
+ throw new Error('Throttle must be a non-negative integer');
237
+ }
238
+ this.throttle = delayInMillis;
239
+ this.logger.info(`stream-throttle-reset`, { delay: this.throttle, topic: this.topic });
240
+ }
241
+ async claimUnacknowledged(stream, group, consumer, idleTimeMs = this.reclaimDelay, limit = XPENDING_COUNT) {
242
+ let pendingMessages = [];
243
+ const pendingMessagesInfo = await this.stream.xpending(stream, group, '-', '+', limit); //[[ '1688768134881-0', 'testConsumer1', 1017, 1 ]]
244
+ for (const pendingMessageInfo of pendingMessagesInfo) {
245
+ if (Array.isArray(pendingMessageInfo)) {
246
+ const [id, , elapsedTimeMs, deliveryCount] = pendingMessageInfo;
247
+ if (elapsedTimeMs > idleTimeMs) {
248
+ const reclaimedMessage = await this.stream.xclaim(stream, group, consumer, idleTimeMs, id);
249
+ if (reclaimedMessage.length) {
250
+ if (deliveryCount <= this.reclaimCount) {
251
+ pendingMessages = pendingMessages.concat(reclaimedMessage);
252
+ }
253
+ else {
254
+ await this.expireUnacknowledged(reclaimedMessage, stream, group, consumer, id, deliveryCount);
255
+ }
256
+ }
257
+ }
258
+ }
259
+ }
260
+ return pendingMessages;
261
+ }
262
+ async expireUnacknowledged(reclaimedMessage, stream, group, consumer, id, count) {
263
+ //The stream activity was not processed within established limits. Possibilities Include:
264
+ // 1) user error: the workers were not properly configured and are timing out
265
+ // 2a) system error: JSON is corrupt
266
+ // i) bad/unwitting actor
267
+ // ii) corrupt hardware/network/transport/etc
268
+ // 3b) system error: Redis unable to accept `xadd` request
269
+ // 4c) system error: Redis unable to accept `xdel`/`xack` request
270
+ this.logger.error('stream-message-max-delivery-count-exceeded', { id, stream, group, consumer, code: SYSTEM_STATUS_CODE, count });
271
+ const streamData = reclaimedMessage[0]?.[1]?.[1];
272
+ //fatal risk point 1 of 3): json is corrupt
273
+ const [err, input] = this.parseStreamData(streamData);
274
+ if (err) {
275
+ return this.logger.error('expire-unacknowledged-parse-fatal-error', { id, err });
276
+ }
277
+ else if (!input || !input.metadata) {
278
+ return this.logger.error('expire-unacknowledged-parse-fatal-error', { id });
279
+ }
280
+ let telemetry;
281
+ let messageId;
282
+ try {
283
+ telemetry = new telemetry_1.TelemetryService(this.appId);
284
+ telemetry.startStreamSpan(input, stream_1.StreamRole.SYSTEM);
285
+ telemetry.setStreamError(`Stream Message Max Delivery Count Exceeded`);
286
+ //fatal risk point 2 of 3): unable to publish error message (to notify the parent job)
287
+ const output = this.structureUnacknowledgedError(input);
288
+ messageId = await this.publishResponse(input, output);
289
+ telemetry.setStreamAttributes({ 'app.worker.mid': messageId });
290
+ //fatal risk point 3 of 3): unable to ack and delete stream message
291
+ await this.ackAndDelete(stream, group, id);
292
+ }
293
+ catch (err) {
294
+ if (messageId) {
295
+ this.logger.error('expire-unacknowledged-pub-fatal-error', { id, err, ...input.metadata });
296
+ telemetry.setStreamAttributes({ 'app.system.fatal': 'expire-unacknowledged-pub-fatal-error' });
297
+ }
298
+ else {
299
+ this.logger.error('expire-unacknowledged-ack-fatal-error', { id, err, ...input.metadata });
300
+ telemetry.setStreamAttributes({ 'app.system.fatal': 'expire-unacknowledged-ack-fatal-error' });
301
+ }
302
+ }
303
+ finally {
304
+ telemetry.endStreamSpan();
305
+ }
306
+ }
307
+ parseStreamData(str) {
308
+ try {
309
+ return [, JSON.parse(str)];
310
+ }
311
+ catch (e) {
312
+ return [e];
313
+ }
314
+ }
315
+ }
316
+ exports.StreamSignaler = StreamSignaler;
317
+ StreamSignaler.signalers = new Set();