@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,473 @@
1
+ import { GetStateError } from '../../modules/errors';
2
+ import {
3
+ formatISODate,
4
+ getValueByPath,
5
+ restoreHierarchy } from '../../modules/utils';
6
+ import { CollatorService } from '../collator';
7
+ import { DimensionService } from '../dimension';
8
+ import { EngineService } from '../engine';
9
+ import { ILogger } from '../logger';
10
+ import { MapperService } from '../mapper';
11
+ import { Pipe } from '../pipe';
12
+ import { MDATA_SYMBOLS } from '../serializer';
13
+ import { StoreSignaler } from '../signaler/store';
14
+ import { StoreService } from '../store';
15
+ import { TelemetryService } from '../telemetry';
16
+ import {
17
+ ActivityData,
18
+ ActivityLeg,
19
+ ActivityMetadata,
20
+ ActivityType,
21
+ Consumes } from '../../types/activity';
22
+ import { JobState, JobStatus } from '../../types/job';
23
+ import {
24
+ MultiResponseFlags,
25
+ RedisClient,
26
+ RedisMulti } from '../../types/redis';
27
+ import { StringAnyType, StringScalarType } from '../../types/serializer';
28
+ import {
29
+ StreamCode,
30
+ StreamData,
31
+ StreamDataType,
32
+ StreamStatus } from '../../types/stream';
33
+ import { TransitionRule } from '../../types/transition';
34
+
35
+ /**
36
+ * The base class for all activities
37
+ */
38
+ class Activity {
39
+ config: ActivityType;
40
+ data: ActivityData;
41
+ hook: ActivityData;
42
+ metadata: ActivityMetadata;
43
+ store: StoreService<RedisClient, RedisMulti>
44
+ context: JobState;
45
+ engine: EngineService;
46
+ logger: ILogger;
47
+ status: StreamStatus = StreamStatus.SUCCESS;
48
+ code: StreamCode = 200;
49
+ leg: ActivityLeg;
50
+ adjacencyList: StreamData[];
51
+ adjacentIndex = 0; //can be updated by leg2 using 'as' metadata hincrby output
52
+
53
+ constructor(
54
+ config: ActivityType,
55
+ data: ActivityData,
56
+ metadata: ActivityMetadata,
57
+ hook: ActivityData | null,
58
+ engine: EngineService,
59
+ context?: JobState) {
60
+ this.config = config;
61
+ this.data = data;
62
+ this.metadata = metadata;
63
+ this.hook = hook;
64
+ this.engine = engine;
65
+ this.context = context || { data: {}, metadata: {} } as JobState;
66
+ this.logger = engine.logger;
67
+ this.store = engine.store;
68
+ }
69
+
70
+ //******** INITIAL ENTRY POINT (A) ********//
71
+ async process(): Promise<string> {
72
+ this.logger.debug('activity-process', { jid: this.context.metadata.jid, aid: this.metadata.aid });
73
+ let telemetry: TelemetryService;
74
+ try {
75
+ this.setLeg(1);
76
+ await CollatorService.notarizeEntry(this);
77
+
78
+ await this.getState();
79
+ telemetry = new TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
80
+ telemetry.startActivitySpan(this.leg);
81
+ let multiResponse: MultiResponseFlags;
82
+
83
+ const multi = this.store.getMulti();
84
+ if (this.doesHook()) {
85
+ //sleep and wait to awaken upon a signal
86
+ await this.registerHook(multi);
87
+ this.mapJobData();
88
+ await this.setState(multi);
89
+ await CollatorService.authorizeReentry(this, multi);
90
+
91
+ await this.setStatus(0, multi);
92
+ await multi.exec();
93
+ telemetry.mapActivityAttributes();
94
+ } else {
95
+ //end the activity and transition to its children
96
+ this.adjacencyList = await this.filterAdjacent();
97
+ this.mapJobData();
98
+ await this.setState(multi);
99
+ await CollatorService.notarizeEarlyCompletion(this, multi);
100
+
101
+ await this.setStatus(this.adjacencyList.length - 1, multi);
102
+ multiResponse = await multi.exec() as MultiResponseFlags;
103
+ telemetry.mapActivityAttributes();
104
+ const jobStatus = this.resolveStatus(multiResponse);
105
+ const attrs: StringScalarType = { 'app.job.jss': jobStatus };
106
+ const messageIds = await this.transition(this.adjacencyList, jobStatus);
107
+ if (messageIds.length) {
108
+ attrs['app.activity.mids'] = messageIds.join(',')
109
+ }
110
+ telemetry.setActivityAttributes(attrs);
111
+ }
112
+
113
+ return this.context.metadata.aid;
114
+ } catch (error) {
115
+ if (error instanceof GetStateError) {
116
+ this.logger.error('activity-get-state-error', error);
117
+ } else {
118
+ this.logger.error('activity-process-error', error);
119
+ }
120
+ telemetry.setActivityError(error.message);
121
+ throw error;
122
+ } finally {
123
+ telemetry.endActivitySpan();
124
+ this.logger.debug('activity-process-end', { jid: this.context.metadata.jid, aid: this.metadata.aid });
125
+ }
126
+ }
127
+
128
+ setLeg(leg: ActivityLeg): void {
129
+ this.leg = leg;
130
+ }
131
+
132
+ //******** SIGNALER RE-ENTRY POINT (B) ********//
133
+ doesHook(): boolean {
134
+ return !!(this.config.hook?.topic || this.config.sleep);
135
+ }
136
+
137
+ async registerHook(multi?: RedisMulti): Promise<string | void> {
138
+ if (this.config.hook?.topic) {
139
+ const signaler = new StoreSignaler(this.store, this.logger);
140
+ return await signaler.registerWebHook(this.config.hook.topic, this.context, multi);
141
+ } else if (this.config.sleep) {
142
+ const durationInSeconds = Pipe.resolve(this.config.sleep, this.context);
143
+ const jobId = this.context.metadata.jid;
144
+ const activityId = this.metadata.aid;
145
+ await this.engine.task.registerTimeHook(jobId, activityId, 'sleep', durationInSeconds);
146
+ return jobId;
147
+ }
148
+ }
149
+
150
+ async processWebHookEvent(): Promise<JobStatus | void> {
151
+ this.logger.debug('engine-process-web-hook-event', {
152
+ topic: this.config.hook.topic,
153
+ aid: this.metadata.aid
154
+ });
155
+ const signaler = new StoreSignaler(this.store, this.logger);
156
+ const data = { ...this.data };
157
+ const jobId = await signaler.processWebHookSignal(this.config.hook.topic, data);
158
+ if (jobId) {
159
+ await this.processHookEvent(jobId);
160
+ await signaler.deleteWebHookSignal(this.config.hook.topic, data);
161
+ } //else => already resolved
162
+ }
163
+
164
+ async processTimeHookEvent(jobId: string): Promise<JobStatus | void> {
165
+ this.logger.debug('engine-process-time-hook-event', {
166
+ jid: jobId,
167
+ aid: this.metadata.aid
168
+ });
169
+ return await this.processHookEvent(jobId);
170
+ }
171
+
172
+ //todo: hooks are currently singletons. but they can support
173
+ // dimensional threads like `await` and `worker` do.
174
+ // Copy code from those activities to support cyclical
175
+ // timehook and eventhook inputs by adding a 'pending'
176
+ // flag to hooks that allows for repeated signals
177
+ async processHookEvent(jobId: string): Promise<JobStatus | void> {
178
+ this.logger.debug('activity-process-hook-event', { jobId });
179
+ let telemetry: TelemetryService;
180
+ try {
181
+ this.setLeg(2);
182
+ await this.getState(jobId);
183
+ const aState = await CollatorService.notarizeReentry(this);
184
+ this.adjacentIndex = CollatorService.getDimensionalIndex(aState);
185
+
186
+ telemetry = new TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
187
+ telemetry.startActivitySpan(this.leg);
188
+ this.bindActivityData('hook');
189
+ this.mapJobData();
190
+ this.adjacencyList = await this.filterAdjacent();
191
+
192
+ const multi = this.engine.store.getMulti();
193
+ await this.setState(multi);
194
+ await CollatorService.notarizeCompletion(this, multi);
195
+
196
+ await this.setStatus(this.adjacencyList.length - 1, multi);
197
+ const multiResponse = await multi.exec() as MultiResponseFlags;
198
+
199
+ telemetry.mapActivityAttributes();
200
+ const jobStatus = this.resolveStatus(multiResponse);
201
+ const attrs: StringScalarType = { 'app.job.jss': jobStatus };
202
+ const messageIds = await this.transition(this.adjacencyList, jobStatus);
203
+ if (messageIds.length) {
204
+ attrs['app.activity.mids'] = messageIds.join(',')
205
+ }
206
+ telemetry.setActivityAttributes(attrs);
207
+ return jobStatus as number;
208
+ } catch (error) {
209
+ this.logger.error('engine-process-hook-event-error', error);
210
+ telemetry.setActivityError(error.message);
211
+ throw error;
212
+ } finally {
213
+ telemetry.endActivitySpan();
214
+ }
215
+ }
216
+
217
+ resolveStatus(multiResponse: MultiResponseFlags): number {
218
+ const activityStatus = multiResponse[multiResponse.length - 1];
219
+ if (Array.isArray(activityStatus)) {
220
+ return Number(activityStatus[1]);
221
+ } else {
222
+ return Number(activityStatus);
223
+ }
224
+ }
225
+
226
+ mapJobData(): void {
227
+ if(this.config.job?.maps) {
228
+ const mapper = new MapperService(this.config.job.maps, this.context);
229
+ this.context.data = mapper.mapRules();
230
+ }
231
+ }
232
+
233
+ mapInputData(): void {
234
+ if(this.config.input?.maps) {
235
+ const mapper = new MapperService(this.config.input.maps, this.context);
236
+ this.context.data = mapper.mapRules();
237
+ }
238
+ }
239
+
240
+ async registerTimeout(): Promise<void> {
241
+ //set timeout in support of hook and/or duplex
242
+ }
243
+
244
+ bindActivityError(data: Record<string, unknown>): void {
245
+ //todo: map activity error data into the job error (if defined)
246
+ // map job status via: (500: [3**, 4**, 5**], 202: [$pending])
247
+ this.context.metadata.err = JSON.stringify(data);
248
+ }
249
+
250
+ async getTriggerConfig(): Promise<ActivityType> {
251
+ return await this.store.getSchema(
252
+ this.config.trigger,
253
+ await this.engine.getVID()
254
+ );
255
+ }
256
+
257
+ getJobStatus(): null | number {
258
+ return null;
259
+ }
260
+
261
+ async setStatus(amount: number, multi?: RedisMulti): Promise<void> {
262
+ const { id: appId } = await this.engine.getVID();
263
+ await this.store.setStatus(
264
+ amount,
265
+ this.context.metadata.jid,
266
+ appId,
267
+ multi
268
+ );
269
+ }
270
+
271
+ authorizeEntry(state: StringAnyType): string[] {
272
+ //pre-authorize activity state to allow entry for adjacent activities
273
+ return this.adjacencyList?.map((streamData) => {
274
+ const { metadata: { aid } } = streamData;
275
+ state[`${aid}/output/metadata/as`] = CollatorService.getSeed();
276
+ return aid;
277
+ }) ?? [];
278
+ }
279
+
280
+ bindDimensionalAddress(state: StringAnyType) {
281
+ const { aid, dad } = this.metadata;
282
+ state[`${aid}/output/metadata/dad`] = dad;
283
+ }
284
+
285
+ async setState(multi?: RedisMulti): Promise<string> {
286
+ const { id: appId } = await this.engine.getVID();
287
+ const jobId = this.context.metadata.jid;
288
+ this.bindJobMetadata();
289
+ this.bindActivityMetadata();
290
+ let state: StringAnyType = {};
291
+ await this.bindJobState(state);
292
+ const presets = this.authorizeEntry(state);
293
+ this.bindDimensionalAddress(state);
294
+ this.bindActivityState(state);
295
+ //symbolNames holds symkeys
296
+ const symbolNames = [
297
+ `$${this.config.subscribes}`,
298
+ this.metadata.aid,
299
+ ...presets
300
+ ];
301
+ return await this.store.setState(state, this.getJobStatus(), jobId, appId, symbolNames, multi);
302
+ }
303
+
304
+ bindJobMetadata(): void {
305
+ //both legs of the most recently run activity (1 and 2) modify ju (job_updated)
306
+ this.context.metadata.ju = formatISODate(new Date());
307
+ }
308
+
309
+ bindActivityMetadata(): void {
310
+ const self: StringAnyType = this.context['$self'];
311
+ if (!self.output.metadata) {
312
+ self.output.metadata = {};
313
+ }
314
+ if (this.status === StreamStatus.ERROR) {
315
+ self.output.metadata.err = JSON.stringify(this.data);
316
+ }
317
+ self.output.metadata.ac =
318
+ self.output.metadata.au = formatISODate(new Date());
319
+ self.output.metadata.atp = this.config.type;
320
+ if (this.config.subtype) {
321
+ self.output.metadata.stp = this.config.subtype;
322
+ }
323
+ self.output.metadata.aid = this.metadata.aid;
324
+ }
325
+
326
+ async bindJobState(state: StringAnyType): Promise<void> {
327
+ const triggerConfig = await this.getTriggerConfig();
328
+ const PRODUCES = [
329
+ ...(triggerConfig.PRODUCES || []),
330
+ ...this.bindJobMetadataPaths()
331
+ ];
332
+ for (const path of PRODUCES) {
333
+ const value = getValueByPath(this.context, path);
334
+ if (value !== undefined) {
335
+ state[path] = value;
336
+ }
337
+ }
338
+ TelemetryService.bindJobTelemetryToState(state, this.config, this.context);
339
+ }
340
+
341
+ bindActivityState(state: StringAnyType,): void {
342
+ const produces = [
343
+ ...this.config.produces,
344
+ ...this.bindActivityMetadataPaths()
345
+ ];
346
+ for (const path of produces) {
347
+ const prefixedPath = `${this.metadata.aid}/${path}`;
348
+ const value = getValueByPath(this.context, prefixedPath);
349
+ if (value !== undefined) {
350
+ state[prefixedPath] = value;
351
+ }
352
+ }
353
+ TelemetryService.bindActivityTelemetryToState(state, this.config, this.metadata, this.context, this.leg);
354
+ }
355
+
356
+ bindJobMetadataPaths(): string[] {
357
+ return MDATA_SYMBOLS.JOB_UPDATE.KEYS.map((key) => `metadata/${key}`);
358
+ }
359
+
360
+ bindActivityMetadataPaths(): string[] {
361
+ const keys_to_save = this.leg === 1 ? 'ACTIVITY': 'ACTIVITY_UPDATE'
362
+ return MDATA_SYMBOLS[keys_to_save].KEYS.map((key) => `output/metadata/${key}`);
363
+ }
364
+
365
+ async getState(jobId?: string) {
366
+ //assemble list of paths necessary to create 'job state' from the 'symbol hash'
367
+ const jobSymbolHashName = `$${this.config.subscribes}`;
368
+ const consumes: Consumes = {
369
+ [jobSymbolHashName]: MDATA_SYMBOLS.JOB.KEYS.map((key) => `metadata/${key}`)
370
+ };
371
+ for (let [activityId, paths] of Object.entries(this.config.consumes)) {
372
+ if(activityId === '$job') {
373
+ for (const path of paths) {
374
+ consumes[jobSymbolHashName].push(path);
375
+ }
376
+ } else {
377
+ if (activityId === '$self') {
378
+ activityId = this.metadata.aid;
379
+ }
380
+ if (!consumes[activityId]) {
381
+ consumes[activityId] = [];
382
+ }
383
+ for (const path of paths) {
384
+ consumes[activityId].push(`${activityId}/${path}`);
385
+ }
386
+ }
387
+ }
388
+ TelemetryService.addTargetTelemetryPaths(consumes, this.config, this.metadata, this.leg);
389
+ const { dad, jid } = this.context.metadata;
390
+ jobId = jobId || jid;
391
+ //`state` is a flat hash
392
+ const [state, status] = await this.store.getState(jobId, consumes);
393
+ //`context` is a tree
394
+ this.context = restoreHierarchy(state) as JobState;
395
+ this.initDimensionalAddress(dad);
396
+ this.initSelf(this.context);
397
+ this.initPolicies(this.context);
398
+ }
399
+
400
+ initDimensionalAddress(dad: string): void {
401
+ this.metadata.dad = dad;
402
+ }
403
+
404
+ initSelf(context: StringAnyType): JobState {
405
+ const activityId = this.metadata.aid;
406
+ if (!context[activityId]) {
407
+ context[activityId] = { };
408
+ }
409
+ const self = context[activityId];
410
+ if (!self.output) {
411
+ self.output = { };
412
+ }
413
+ if (!self.input) {
414
+ self.input = { };
415
+ }
416
+ if (!self.hook) {
417
+ self.hook = { };
418
+ }
419
+ context['$self'] = self;
420
+ context['$job'] = context; //NEVER call STRINGIFY! (circular)
421
+ return context as JobState;
422
+ }
423
+
424
+ initPolicies(context: JobState) {
425
+ context.metadata.expire = this.config.expire;
426
+ }
427
+
428
+ bindActivityData(type: 'output' | 'hook'): void {
429
+ this.context[this.metadata.aid][type].data = this.data;
430
+ }
431
+
432
+ async filterAdjacent(): Promise<StreamData[]> {
433
+ const adjacencyList: StreamData[] = [];
434
+ const transitions = await this.store.getTransitions(await this.engine.getVID());
435
+ const transition = transitions[`.${this.metadata.aid}`];
436
+ const adjacentSuffix = DimensionService.getSeed(this.adjacentIndex);
437
+ if (transition) {
438
+ for (const toActivityId in transition) {
439
+ const transitionRule: boolean | TransitionRule = transition[toActivityId];
440
+ if (MapperService.evaluate(transitionRule, this.context, this.code)) {
441
+ adjacencyList.push({
442
+ metadata: {
443
+ jid: this.context.metadata.jid,
444
+ dad: `${this.metadata.dad}${adjacentSuffix}`,
445
+ aid: toActivityId,
446
+ spn: this.context['$self'].output.metadata?.l2s,
447
+ trc: this.context.metadata.trc,
448
+ },
449
+ type: StreamDataType.TRANSITION,
450
+ data: {}
451
+ });
452
+ }
453
+ }
454
+ }
455
+ return adjacencyList;
456
+ }
457
+
458
+ async transition(adjacencyList: StreamData[], jobStatus: JobStatus): Promise<string[]> {
459
+ let mIds: string[] = [];
460
+ if (adjacencyList.length) {
461
+ const multi = this.store.getMulti();
462
+ for (const execSignal of adjacencyList) {
463
+ await this.engine.streamSignaler?.publishMessage(null, execSignal, multi);
464
+ }
465
+ mIds = (await multi.exec()) as string[];
466
+ } else if (jobStatus <= 0) {
467
+ await this.engine.runJobCompletionTasks(this.context);
468
+ }
469
+ return mIds;
470
+ }
471
+ }
472
+
473
+ export { Activity, ActivityType };
@@ -0,0 +1,172 @@
1
+ import { GetStateError } from '../../modules/errors';
2
+ import { Activity } from './activity';
3
+ import { EngineService } from '../engine';
4
+ import {
5
+ ActivityData,
6
+ ActivityMetadata,
7
+ AwaitActivity,
8
+ ActivityType } from '../../types/activity';
9
+ import { JobState } from '../../types/job';
10
+ import { MultiResponseFlags } from '../../types/redis';
11
+ import { StringScalarType } from '../../types/serializer';
12
+ import {
13
+ StreamCode,
14
+ StreamData,
15
+ StreamDataType,
16
+ StreamStatus } from '../../types/stream';
17
+ import { TelemetryService } from '../telemetry';
18
+ import { CollatorService } from '../collator';
19
+
20
+ class Await extends Activity {
21
+ config: AwaitActivity;
22
+
23
+ constructor(
24
+ config: ActivityType,
25
+ data: ActivityData,
26
+ metadata: ActivityMetadata,
27
+ hook: ActivityData | null,
28
+ engine: EngineService,
29
+ context?: JobState) {
30
+ super(config, data, metadata, hook, engine, context);
31
+ }
32
+
33
+ //******** INITIAL ENTRY POINT (A) ********//
34
+ async process(): Promise<string> {
35
+ this.logger.debug('await-process', { jid: this.context.metadata.jid, aid: this.metadata.aid });
36
+ let telemetry: TelemetryService;
37
+ try {
38
+ this.setLeg(1);
39
+ await CollatorService.notarizeEntry(this);
40
+
41
+ await this.getState();
42
+ telemetry = new TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
43
+ telemetry.startActivitySpan(this.leg);
44
+ this.mapInputData();
45
+
46
+ const multi = this.store.getMulti();
47
+ //await this.registerTimeout();
48
+ await CollatorService.authorizeReentry(this, multi);
49
+ await this.setState(multi);
50
+ await this.setStatus(0, multi);
51
+ const multiResponse = await multi.exec() as MultiResponseFlags;
52
+
53
+ telemetry.mapActivityAttributes();
54
+ const jobStatus = this.resolveStatus(multiResponse);
55
+ const messageId = await this.execActivity();
56
+ telemetry.setActivityAttributes({
57
+ 'app.activity.mid': messageId,
58
+ 'app.job.jss': jobStatus
59
+ });
60
+ return this.context.metadata.aid;
61
+ } catch (error) {
62
+ telemetry.setActivityError(error.message);
63
+ if (error instanceof GetStateError) {
64
+ this.logger.error('await-get-state-error', error);
65
+ } else {
66
+ this.logger.error('await-process-error', error);
67
+ }
68
+ throw error;
69
+ } finally {
70
+ telemetry.endActivitySpan();
71
+ this.logger.debug('await-process-end', { jid: this.context.metadata.jid, aid: this.metadata.aid });
72
+ }
73
+ }
74
+
75
+
76
+ async execActivity(): Promise<string> {
77
+ const streamData: StreamData = {
78
+ metadata: {
79
+ jid: this.context.metadata.jid,
80
+ dad: this.metadata.dad,
81
+ aid: this.metadata.aid,
82
+ topic: this.config.subtype,
83
+ spn: this.context['$self'].output.metadata?.l1s,
84
+ trc: this.context.metadata.trc,
85
+ },
86
+ type: StreamDataType.AWAIT,
87
+ data: this.context.data
88
+ };
89
+ if (this.config.retry) {
90
+ streamData.policies = {
91
+ retry: this.config.retry
92
+ };
93
+ }
94
+ return (await this.engine.streamSignaler?.publishMessage(null, streamData)) as string;
95
+ }
96
+
97
+
98
+ //******** `RESOLVE` ENTRY POINT (B) ********//
99
+ //this method is invoked when the job spawned by this job ends;
100
+ //`this.data` is the job data produced by the spawned job
101
+ async processEvent(status: StreamStatus = StreamStatus.SUCCESS, code: StreamCode = 200): Promise<void> {
102
+ this.setLeg(2);
103
+ const jid = this.context.metadata.jid;
104
+ const aid = this.metadata.aid;
105
+ if (!jid) {
106
+ throw new Error('await-process-event-error');
107
+ }
108
+ this.logger.debug('await-resolve-await', { jid, aid, status, code });
109
+ this.status = status;
110
+ this.code = code;
111
+ let telemetry: TelemetryService;
112
+ try {
113
+ await this.getState();
114
+ const aState = await CollatorService.notarizeReentry(this);
115
+ this.adjacentIndex = CollatorService.getDimensionalIndex(aState);
116
+
117
+ telemetry = new TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
118
+ telemetry.startActivitySpan(this.leg);
119
+
120
+ let multiResponse: MultiResponseFlags = [];
121
+ if (status === StreamStatus.SUCCESS) {
122
+ this.bindActivityData('output');
123
+ this.adjacencyList = await this.filterAdjacent();
124
+ multiResponse = await this.processSuccess(this.adjacencyList);
125
+ } else {
126
+ this.bindActivityError(this.data);
127
+ this.adjacencyList = await this.filterAdjacent();
128
+ multiResponse = await this.processError(this.adjacencyList);
129
+ }
130
+
131
+ telemetry.mapActivityAttributes();
132
+ const jobStatus = this.resolveStatus(multiResponse);
133
+ const attrs: StringScalarType = { 'app.job.jss': jobStatus };
134
+ const messageIds = await this.transition(this.adjacencyList, jobStatus);
135
+ if (messageIds.length) {
136
+ attrs['app.activity.mids'] = messageIds.join(',')
137
+ }
138
+ telemetry.setActivityAttributes(attrs);
139
+ } catch (error) {
140
+ this.logger.error('await-resolve-await-error', error);
141
+ telemetry.setActivityError(error.message);
142
+ throw error;
143
+ } finally {
144
+ telemetry.endActivitySpan();
145
+ this.logger.debug('await-resolve-await-end', { jid, aid, status, code });
146
+ }
147
+ }
148
+
149
+ async processSuccess(adjacencyList: StreamData[]): Promise<MultiResponseFlags> {
150
+ this.mapJobData();
151
+ const multi = this.store.getMulti();
152
+ await this.setState(multi);
153
+ await CollatorService.notarizeCompletion(this, multi);
154
+
155
+ await this.setStatus(adjacencyList.length - 1, multi);
156
+ return await multi.exec() as MultiResponseFlags;
157
+ }
158
+
159
+ async processError(adjacencyList: StreamData[]): Promise<MultiResponseFlags> {
160
+ //todo: if adjacencyList.length == 0, then map to the job output
161
+ // this method would be added to Base activity class
162
+ //this.mapJobData();
163
+ const multi = this.store.getMulti();
164
+ await this.setState(multi);
165
+ await CollatorService.notarizeCompletion(this, multi);
166
+
167
+ await this.setStatus(adjacencyList.length - 1, multi);
168
+ return await multi.exec() as MultiResponseFlags;
169
+ }
170
+ }
171
+
172
+ export { Await };
@@ -0,0 +1,25 @@
1
+ import { EngineService } from '../engine';
2
+ import { Activity, ActivityType } from './activity';
3
+ import {
4
+ ActivityData,
5
+ ActivityMetadata,
6
+ EmitActivity } from '../../types/activity';
7
+
8
+ class Emit extends Activity {
9
+ config: EmitActivity;
10
+
11
+ constructor(
12
+ config: ActivityType,
13
+ data: ActivityData,
14
+ metadata: ActivityMetadata,
15
+ hook: ActivityData | null,
16
+ engine: EngineService) {
17
+ super(config, data, metadata, hook, engine);
18
+ }
19
+
20
+ async mapInputData(): Promise<void> {
21
+ this.logger.info('emit-map-input-data');
22
+ }
23
+ }
24
+
25
+ export { Emit };
@@ -0,0 +1,15 @@
1
+ import { Activity } from './activity';
2
+ import { Await } from './await';
3
+ import { Worker } from './worker';
4
+ import { Iterate } from './iterate';
5
+ import { Emit } from './emit';
6
+ import { Trigger } from './trigger';
7
+
8
+ export default {
9
+ activity: Activity,
10
+ await: Await,
11
+ iterate: Iterate,
12
+ emit: Emit,
13
+ trigger: Trigger,
14
+ worker: Worker,
15
+ };