@hotmeshio/hotmesh 0.0.55 → 0.0.57

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 +1 -1
  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 +56 -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 +1 -141
  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 +26 -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 +131 -4
  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,761 +0,0 @@
1
- import { KeyType, VALSEP } from '../../modules/key';
2
- import {
3
- HMSH_OTT_WAIT_TIME,
4
- HMSH_CODE_SUCCESS,
5
- HMSH_CODE_PENDING,
6
- HMSH_CODE_TIMEOUT,
7
- HMSH_EXPIRE_JOB_SECONDS } from '../../modules/enums';
8
- import {
9
- formatISODate,
10
- getSubscriptionTopic,
11
- guid,
12
- identifyRedisType,
13
- polyfill,
14
- restoreHierarchy } from '../../modules/utils';
15
- import Activities from '../activities';
16
- import { Await } from '../activities/await';
17
- import { Cycle } from '../activities/cycle';
18
- import { Hook } from '../activities/hook';
19
- import { Interrupt } from '../activities/interrupt';
20
- import { Signal } from '../activities/signal';
21
- import { Worker } from '../activities/worker';
22
- import { Trigger } from '../activities/trigger';
23
- import { CompilerService } from '../compiler';
24
- import { ExporterService } from '../exporter';
25
- import { ILogger } from '../logger';
26
- import { ReporterService } from '../reporter';
27
- import { Router } from '../router';
28
- import { SerializerService } from '../serializer';
29
- import { StoreService } from '../store';
30
- import { RedisStoreService as RedisStore } from '../store/clients/redis';
31
- import { IORedisStoreService as IORedisStore } from '../store/clients/ioredis';
32
- import { StreamService } from '../stream';
33
- import { RedisStreamService as RedisStream } from '../stream/clients/redis';
34
- import { IORedisStreamService as IORedisStream } from '../stream/clients/ioredis';
35
- import { SubService } from '../sub';
36
- import { IORedisSubService as IORedisSub } from '../sub/clients/ioredis';
37
- import { RedisSubService as RedisSub } from '../sub/clients/redis';
38
- import { TaskService } from '../task';
39
- import { AppVID } from '../../types/app';
40
- import {
41
- ActivityMetadata,
42
- ActivityType,
43
- Consumes } from '../../types/activity';
44
- import { CacheMode } from '../../types/cache';
45
- import { RedisClientType as IORedisClientType } from '../../types/ioredisclient';
46
- import {
47
- JobState,
48
- JobData,
49
- JobMetadata,
50
- JobOutput,
51
- PartialJobState,
52
- JobStatus,
53
- JobInterruptOptions,
54
- JobCompletionOptions,
55
- ExtensionType} from '../../types/job';
56
- import {
57
- HotMeshApps,
58
- HotMeshConfig,
59
- HotMeshManifest,
60
- HotMeshSettings } from '../../types/hotmesh';
61
- import {
62
- JobMessage,
63
- JobMessageCallback,
64
- SubscriptionCallback } from '../../types/quorum';
65
- import { RedisClient, RedisMulti } from '../../types/redis';
66
- import { RedisClientType } from '../../types/redisclient';
67
- import { StringAnyType, StringStringType } from '../../types/serializer';
68
- import {
69
- GetStatsOptions,
70
- IdsResponse,
71
- JobStatsInput,
72
- StatsResponse
73
- } from '../../types/stats';
74
- import {
75
- StreamCode,
76
- StreamData,
77
- StreamDataResponse,
78
- StreamDataType,
79
- StreamError,
80
- StreamRole,
81
- StreamStatus } from '../../types/stream';
82
- import { WorkListTaskType } from '../../types/task';
83
- import { JobExport } from '../../types/exporter';
84
-
85
- class EngineService {
86
- namespace: string;
87
- apps: HotMeshApps | null;
88
- appId: string;
89
- guid: string;
90
- exporter: ExporterService | null;
91
- router: Router | null;
92
- store: StoreService<RedisClient, RedisMulti> | null;
93
- stream: StreamService<RedisClient, RedisMulti> | null;
94
- subscribe: SubService<RedisClient, RedisMulti> | null;
95
- taskService: TaskService | null;
96
- logger: ILogger;
97
- cacheMode: CacheMode = 'cache';
98
- untilVersion: string | null = null;
99
- jobCallbacks: Record<string, JobMessageCallback> = {};
100
- reporting = false;
101
- jobId = 1;
102
- inited: string;
103
-
104
- static async init(namespace: string, appId: string, guid: string, config: HotMeshConfig, logger: ILogger): Promise<EngineService> {
105
- if (config.engine) {
106
- const instance = new EngineService();
107
- instance.verifyEngineFields(config);
108
-
109
- instance.namespace = namespace;
110
- instance.appId = appId;
111
- instance.guid = guid;
112
- instance.logger = logger;
113
-
114
- await instance.initStoreChannel(config.engine.store);
115
- await instance.initSubChannel(config.engine.sub);
116
- await instance.initStreamChannel(config.engine.stream);
117
-
118
- instance.router = instance.initRouter(config);
119
- instance.router.consumeMessages(
120
- instance.stream.mintKey(
121
- KeyType.STREAMS,
122
- { appId: instance.appId },
123
- ),
124
- 'ENGINE',
125
- instance.guid,
126
- instance.processStreamMessage.bind(instance)
127
- );
128
-
129
- instance.taskService = new TaskService(
130
- instance.store,
131
- logger
132
- );
133
- instance.exporter = new ExporterService(
134
- instance.appId,
135
- instance.store,
136
- logger,
137
- );
138
- instance.inited = formatISODate(new Date());
139
- return instance;
140
- }
141
- }
142
-
143
- verifyEngineFields(config: HotMeshConfig) {
144
- if (!identifyRedisType(config.engine.store) ||
145
- !identifyRedisType(config.engine.stream) ||
146
- !identifyRedisType(config.engine.sub)) {
147
- throw new Error('engine config must reference 3 redis client instances');
148
- }
149
- }
150
-
151
- async initStoreChannel(store: RedisClient) {
152
- if (identifyRedisType(store) === 'redis') {
153
- this.store = new RedisStore(store as RedisClientType);
154
- } else {
155
- this.store = new IORedisStore(store as IORedisClientType);
156
- }
157
- await this.store.init(
158
- this.namespace,
159
- this.appId,
160
- this.logger
161
- );
162
- }
163
-
164
- async initSubChannel(sub: RedisClient) {
165
- if (identifyRedisType(sub) === 'redis') {
166
- this.subscribe = new RedisSub(sub as RedisClientType);
167
- } else {
168
- this.subscribe = new IORedisSub(sub as IORedisClientType);
169
- }
170
- await this.subscribe.init(
171
- this.namespace,
172
- this.appId,
173
- this.guid,
174
- this.logger
175
- );
176
- }
177
-
178
- async initStreamChannel(stream: RedisClient) {
179
- if (identifyRedisType(stream) === 'redis') {
180
- this.stream = new RedisStream(stream as RedisClientType);
181
- } else {
182
- this.stream = new IORedisStream(stream as IORedisClientType);
183
- }
184
- await this.stream.init(
185
- this.namespace,
186
- this.appId,
187
- this.logger
188
- );
189
- }
190
-
191
- initRouter(config: HotMeshConfig): Router {
192
- return new Router(
193
- {
194
- namespace: this.namespace,
195
- appId: this.appId,
196
- guid: this.guid,
197
- role: StreamRole.ENGINE,
198
- reclaimDelay: config.engine.reclaimDelay,
199
- reclaimCount: config.engine.reclaimCount,
200
- },
201
- this.stream,
202
- this.store,
203
- this.logger,
204
- );
205
- }
206
-
207
- async getVID(vid?: AppVID): Promise<AppVID> {
208
- if (this.cacheMode === 'nocache') {
209
- const app = await this.store.getApp(this.appId, true);
210
- if (app.version.toString() === this.untilVersion.toString()) {
211
- //new version is deployed; OK to cache again
212
- if (!this.apps) this.apps = {};
213
- this.apps[this.appId] = app;
214
- this.setCacheMode('cache', app.version.toString());
215
- }
216
- return { id: this.appId, version: app.version };
217
- } else if (!this.apps && vid) {
218
- this.apps = {};
219
- this.apps[this.appId] = vid;
220
- return vid;
221
- } else {
222
- return { id: this.appId, version: this.apps?.[this.appId].version };
223
- }
224
- }
225
-
226
- setCacheMode(cacheMode: CacheMode, untilVersion: string) {
227
- this.logger.info(`engine-rule-cache-updated`, { mode: cacheMode, until: untilVersion });
228
- this.cacheMode = cacheMode;
229
- this.untilVersion = untilVersion;
230
- }
231
-
232
- async routeToSubscribers(topic: string, message: JobOutput) {
233
- const jobCallback = this.jobCallbacks[message.metadata.jid];
234
- if (jobCallback) {
235
- this.delistJobCallback(message.metadata.jid);
236
- jobCallback(topic, message);
237
- }
238
- }
239
-
240
- async processWebHooks() {
241
- this.taskService.processWebHooks((this.hook).bind(this));
242
- }
243
-
244
- async processTimeHooks() {
245
- this.taskService.processTimeHooks((this.hookTime).bind(this));
246
- }
247
-
248
- async throttle(delayInMillis: number) {
249
- this.router.setThrottle(delayInMillis);
250
- }
251
-
252
- // ************* METADATA/MODEL METHODS *************
253
- async initActivity(topic: string, data: JobData = {}, context?: JobState): Promise<Await|Cycle|Hook|Signal|Trigger|Worker|Interrupt> {
254
- const [activityId, schema] = await this.getSchema(topic);
255
- const ActivityHandler = Activities[polyfill.resolveActivityType(schema.type)];
256
- if (ActivityHandler) {
257
- const utc = formatISODate(new Date());
258
- const metadata: ActivityMetadata = {
259
- aid: activityId,
260
- atp: schema.type,
261
- stp: schema.subtype,
262
- ac: utc,
263
- au: utc
264
- };
265
- const hook = null;
266
- return new ActivityHandler(schema, data, metadata, hook, this, context);
267
- } else {
268
- throw new Error(`activity type ${schema.type} not found`);
269
- }
270
- }
271
- async getSchema(topic: string): Promise<[activityId: string, schema: ActivityType]> {
272
- const app = await this.store.getApp(this.appId) as AppVID;
273
- if (!app) {
274
- throw new Error(`no app found for id ${this.appId}`);
275
- }
276
- if (this.isPrivate(topic)) {
277
- //private subscriptions use the schema id (.activityId)
278
- const activityId = topic.substring(1)
279
- const schema = await this.store.getSchema(activityId, await this.getVID(app));
280
- return [activityId, schema];
281
- } else {
282
- //public subscriptions use a topic (a.b.c) that is associated with a schema id
283
- const activityId = await this.store.getSubscription(topic, await this.getVID(app));
284
- if (activityId) {
285
- const schema = await this.store.getSchema(activityId, await this.getVID(app));
286
- return [activityId, schema];
287
- }
288
- }
289
- throw new Error(`no subscription found for topic ${topic} in app ${this.appId} for app version ${app.version}`);
290
- }
291
- async getSettings(): Promise<HotMeshSettings> {
292
- return await this.store.getSettings();
293
- }
294
- isPrivate(topic: string) {
295
- return topic.startsWith('.');
296
- }
297
-
298
- // ************* COMPILER METHODS *************
299
- async plan(pathOrYAML: string): Promise<HotMeshManifest> {
300
- const compiler = new CompilerService(this.store, this.logger);
301
- return await compiler.plan(pathOrYAML);
302
- }
303
- async deploy(pathOrYAML: string): Promise<HotMeshManifest> {
304
- const compiler = new CompilerService(this.store, this.logger);
305
- return await compiler.deploy(pathOrYAML);
306
- }
307
-
308
- // ************* REPORTER METHODS *************
309
- async getStats(topic: string, query: JobStatsInput): Promise<StatsResponse> {
310
- const { id, version } = await this.getVID();
311
- const reporter = new ReporterService({ id, version }, this.store, this.logger);
312
- const resolvedQuery = await this.resolveQuery(topic, query);
313
- return await reporter.getStats(resolvedQuery);
314
- }
315
- async getIds(topic: string, query: JobStatsInput, queryFacets = []): Promise<IdsResponse> {
316
- const { id, version } = await this.getVID();
317
- const reporter = new ReporterService({ id, version }, this.store, this.logger);
318
- const resolvedQuery = await this.resolveQuery(topic, query);
319
- return await reporter.getIds(resolvedQuery, queryFacets);
320
- }
321
- async resolveQuery(topic: string, query: JobStatsInput): Promise<GetStatsOptions> {
322
- const trigger = await this.initActivity(topic, query.data) as Trigger;
323
- await trigger.getState();
324
- return {
325
- end: query.end,
326
- start: query.start,
327
- range: query.range,
328
- granularity: trigger.resolveGranularity(),
329
- key: trigger.resolveJobKey(trigger.createInputContext()),
330
- sparse: query.sparse,
331
- } as GetStatsOptions;
332
- }
333
-
334
- // ****************** STREAM RE-ENTRY POINT *****************
335
- async processStreamMessage(streamData: StreamDataResponse): Promise<void> {
336
- this.logger.debug('engine-process-stream-message', {
337
- jid: streamData.metadata.jid,
338
- gid: streamData.metadata.gid,
339
- dad: streamData.metadata.dad,
340
- aid: streamData.metadata.aid,
341
- status: streamData.status || StreamStatus.SUCCESS,
342
- code: streamData.code || 200,
343
- type: streamData.type,
344
- });
345
- const context: PartialJobState = {
346
- metadata: {
347
- guid: streamData.metadata.guid,
348
- jid: streamData.metadata.jid,
349
- gid: streamData.metadata.gid,
350
- dad: streamData.metadata.dad,
351
- aid: streamData.metadata.aid,
352
- },
353
- data: streamData.data,
354
- };
355
- if (streamData.type === StreamDataType.TIMEHOOK) {
356
- //TIMEHOOK AWAKEN
357
- const activityHandler = await this.initActivity(
358
- `.${streamData.metadata.aid}`,
359
- context.data,
360
- context as JobState,
361
- ) as Hook;
362
- await activityHandler.processTimeHookEvent(streamData.metadata.jid);
363
- } else if (streamData.type === StreamDataType.WEBHOOK) {
364
- //WEBHOOK AWAKEN (SIGNAL IN)
365
- const activityHandler = await this.initActivity(
366
- `.${streamData.metadata.aid}`,
367
- context.data,
368
- context as JobState,
369
- ) as Hook;
370
- await activityHandler.processWebHookEvent(
371
- streamData.status,
372
- streamData.code
373
- );
374
- } else if (streamData.type === StreamDataType.TRANSITION) {
375
- //TRANSITION (ADJACENT ACTIVITY)
376
- const activityHandler = await this.initActivity(
377
- `.${streamData.metadata.aid}`,
378
- context.data,
379
- context as JobState,
380
- ) as Hook; //todo: `as Activity` (type is more generic)
381
- await activityHandler.process();
382
- } else if (streamData.type === StreamDataType.AWAIT) {
383
- //TRIGGER JOB
384
- context.metadata = {
385
- ...context.metadata,
386
- pj: streamData.metadata.jid,
387
- pg: streamData.metadata.gid,
388
- pd: streamData.metadata.dad,
389
- pa: streamData.metadata.aid,
390
- px: streamData.metadata.await === false, //sever the parent connection (px)
391
- trc: streamData.metadata.trc,
392
- spn: streamData.metadata.spn,
393
- };
394
- const activityHandler = await this.initActivity(
395
- streamData.metadata.topic,
396
- streamData.data,
397
- context as JobState
398
- ) as Trigger;
399
- await activityHandler.process();
400
- } else if (streamData.type === StreamDataType.RESULT) {
401
- //AWAIT RESULT
402
- const activityHandler = await this.initActivity(
403
- `.${context.metadata.aid}`,
404
- streamData.data,
405
- context as JobState,
406
- ) as Await;
407
- await activityHandler.processEvent(
408
- streamData.status,
409
- streamData.code,
410
- );
411
- } else {
412
- //WORKER RESULT
413
- const activityHandler = await this.initActivity(
414
- `.${streamData.metadata.aid}`,
415
- streamData.data,
416
- context as JobState,
417
- ) as Worker;
418
- await activityHandler.processEvent(
419
- streamData.status,
420
- streamData.code,
421
- 'output'
422
- );
423
- }
424
- this.logger.debug('engine-process-stream-message-end', {
425
- jid: streamData.metadata.jid,
426
- gid: streamData.metadata.gid,
427
- aid: streamData.metadata.aid
428
- });
429
- }
430
-
431
- // ***************** `AWAIT` ACTIVITY RETURN RESPONSE ****************
432
- async execAdjacentParent(context: JobState, jobOutput: JobOutput, emit = false): Promise<string> {
433
- if (this.hasParentJob(context)) {
434
- //errors are stringified `StreamError` objects
435
- const error = this.resolveError(jobOutput.metadata);
436
- const spn = context['$self']?.output?.metadata?.l2s || context['$self']?.output?.metadata?.l1s;
437
- const streamData: StreamData = {
438
- metadata: {
439
- guid: guid(),
440
- jid: context.metadata.pj,
441
- gid: context.metadata.pg,
442
- dad: context.metadata.pd,
443
- aid: context.metadata.pa,
444
- trc: context.metadata.trc,
445
- spn,
446
- },
447
- type: StreamDataType.RESULT,
448
- data: jobOutput.data,
449
- };
450
- if (error && error.code) {
451
- streamData.status = StreamStatus.ERROR;
452
- streamData.data = error;
453
- streamData.code = error.code;
454
- streamData.stack = error.stack;
455
- } else if (emit) {
456
- streamData.status = StreamStatus.PENDING;
457
- streamData.code = HMSH_CODE_PENDING;
458
- } else {
459
- streamData.status = StreamStatus.SUCCESS;
460
- streamData.code = HMSH_CODE_SUCCESS;
461
- }
462
- return (await this.router?.publishMessage(null, streamData)) as string;
463
- }
464
- }
465
- hasParentJob(context: JobState, checkSevered = false): boolean {
466
- if (checkSevered) {
467
- return Boolean(context.metadata.pj && context.metadata.pa && !context.metadata.px);
468
- }
469
- return Boolean(context.metadata.pj && context.metadata.pa);
470
- }
471
- resolveError(metadata: JobMetadata): StreamError | undefined {
472
- if (metadata && metadata.err) {
473
- return JSON.parse(metadata.err) as StreamError;
474
- }
475
- }
476
-
477
- // ****************** `INTERRUPT` ACTIVE JOBS *****************
478
- async interrupt(topic: string, jobId: string, options: JobInterruptOptions = {}): Promise<string> {
479
- //immediately interrupt the job, going directly to the data source
480
- await this.store.interrupt(topic, jobId, options);
481
-
482
- //now that the job is interrupted, we can clean up
483
- const context = await this.getState(topic, jobId) as JobState;
484
- const completionOpts: JobCompletionOptions = {
485
- interrupt: options.descend,
486
- expire: options.expire,
487
- };
488
- return await this.runJobCompletionTasks(context, completionOpts) as string;
489
- }
490
-
491
- // ****************** `SCRUB` CLEAN COMPLETED JOBS *****************
492
- async scrub(jobId: string) {
493
- //todo: do not allow scrubbing of non-existent or actively running job
494
- await this.store.scrub(jobId);
495
- }
496
-
497
- // ****************** `HOOK` ACTIVITY RE-ENTRY POINT *****************
498
- async hook(topic: string, data: JobData, status: StreamStatus = StreamStatus.SUCCESS, code: StreamCode = 200): Promise<string> {
499
- const hookRule = await this.taskService.getHookRule(topic);
500
- const [aid] = await this.getSchema(`.${hookRule.to}`);
501
- const streamData: StreamData = {
502
- type: StreamDataType.WEBHOOK,
503
- status,
504
- code,
505
- metadata: {
506
- guid: guid(),
507
- aid,
508
- topic
509
- },
510
- data,
511
- };
512
- return await this.router.publishMessage(null, streamData) as string;
513
- }
514
- async hookTime(jobId: string, gId: string, topicOrActivity: string, type?: WorkListTaskType): Promise<string | void> {
515
- if (type === 'interrupt' || type === 'expire') {
516
- return await this.interrupt(
517
- topicOrActivity,
518
- jobId,
519
- { suppress: true, expire: 1 },
520
- );
521
- }
522
- const [aid, ...dimensions] = topicOrActivity.split(',');
523
- const dad = `,${dimensions.join(',')}`;
524
- const streamData: StreamData = {
525
- type: StreamDataType.TIMEHOOK,
526
- metadata: {
527
- guid: guid(),
528
- jid: jobId,
529
- gid: gId,
530
- dad,
531
- aid,
532
- },
533
- data: { timestamp: Date.now() },
534
- };
535
- await this.router.publishMessage(null, streamData);
536
- }
537
- async hookAll(hookTopic: string, data: JobData, keyResolver: JobStatsInput, queryFacets: string[] = []): Promise<string[]> {
538
- const config = await this.getVID();
539
- const hookRule = await this.taskService.getHookRule(hookTopic);
540
- if (hookRule) {
541
- const subscriptionTopic = await getSubscriptionTopic(hookRule.to, this.store, config)
542
- const resolvedQuery = await this.resolveQuery(subscriptionTopic, keyResolver);
543
- const reporter = new ReporterService(config, this.store, this.logger);
544
- const workItems = await reporter.getWorkItems(resolvedQuery, queryFacets);
545
- if (workItems.length) {
546
- const taskService = new TaskService(this.store, this.logger);
547
- await taskService.enqueueWorkItems(
548
- workItems.map(
549
- workItem => [
550
- hookTopic,
551
- workItem,
552
- keyResolver.scrub || false,
553
- JSON.stringify(data)].join(VALSEP)
554
- ));
555
- this.store.publish(
556
- KeyType.QUORUM,
557
- { type: 'work', originator: this.guid },
558
- this.appId
559
- );
560
- }
561
- return workItems;
562
- } else {
563
- throw new Error(`unable to find hook rule for topic ${hookTopic}`);
564
- }
565
- }
566
-
567
-
568
- // ********************** PUB/SUB ENTRY POINT **********************
569
- //publish (returns just the job id)
570
- async pub(topic: string, data: JobData, context?: JobState, extended?: ExtensionType): Promise<string> {
571
- const activityHandler = await this.initActivity(topic, data, context);
572
- if (activityHandler) {
573
- return await activityHandler.process(extended);
574
- } else {
575
- throw new Error(`unable to process activity for topic ${topic}`);
576
- }
577
- }
578
- //subscribe to all jobs for a topic
579
- async sub(topic: string, callback: JobMessageCallback): Promise<void> {
580
- const subscriptionCallback: SubscriptionCallback = async (topic: string, message: {topic: string, job: JobOutput}) => {
581
- callback(message.topic, message.job);
582
- };
583
- return await this.subscribe.subscribe(KeyType.QUORUM, subscriptionCallback, this.appId, topic);
584
- }
585
- //unsubscribe to all jobs for a topic
586
- async unsub(topic: string): Promise<void> {
587
- return await this.subscribe.unsubscribe(KeyType.QUORUM, this.appId, topic);
588
- }
589
- //subscribe to all jobs for a wildcard topic
590
- async psub(wild: string, callback: JobMessageCallback): Promise<void> {
591
- const subscriptionCallback: SubscriptionCallback = async (topic: string, message: {topic: string, job: JobOutput}) => {
592
- callback(message.topic, message.job);
593
- };
594
- return await this.subscribe.psubscribe(KeyType.QUORUM, subscriptionCallback, this.appId, wild);
595
- }
596
- //unsubscribe to all jobs for a wildcard topic
597
- async punsub(wild: string): Promise<void> {
598
- return await this.subscribe.punsubscribe(KeyType.QUORUM, this.appId, wild);
599
- }
600
- //publish and await (returns the job and data (if ready)); throws error with jobid if not
601
- async pubsub(topic: string, data: JobData, context?: JobState | null, timeout = HMSH_OTT_WAIT_TIME): Promise<JobOutput> {
602
- context = {
603
- metadata: {
604
- ngn: this.guid,
605
- trc: context?.metadata?.trc,
606
- spn: context?.metadata?.spn
607
- }
608
- } as JobState;
609
- const jobId = await this.pub(topic, data, context);
610
- return new Promise((resolve, reject) => {
611
- this.registerJobCallback(jobId, (topic: string, output: JobOutput) => {
612
- if (output.metadata.err) {
613
- const error = JSON.parse(output.metadata.err) as StreamError;
614
- reject({
615
- ...error,
616
- job_id: output.metadata.jid,
617
- });
618
- } else {
619
- resolve(output);
620
- }
621
- });
622
- setTimeout(() => {
623
- //note: job is still active (the subscriber timed out)
624
- this.delistJobCallback(jobId);
625
- reject({
626
- code: HMSH_CODE_TIMEOUT,
627
- message: 'timeout',
628
- job_id: jobId
629
- } as StreamError);
630
- }, timeout);
631
- });
632
- }
633
- async pubOneTimeSubs(context: JobState, jobOutput: JobOutput, emit = false) {
634
- //todo: subscriber should query for the job...only publish minimum context needed
635
- if (this.hasOneTimeSubscription(context)) {
636
- const message: JobMessage = {
637
- type: 'job',
638
- topic: context.metadata.jid,
639
- job: restoreHierarchy(jobOutput) as JobOutput,
640
- };
641
- this.store.publish(KeyType.QUORUM, message, this.appId, context.metadata.ngn);
642
- }
643
- }
644
- async getPublishesTopic(context: JobState): Promise<string> {
645
- const config = await this.getVID();
646
- const activityId = context.metadata.aid || context['$self']?.output?.metadata?.aid;
647
- const schema = await this.store.getSchema(activityId, config);
648
- return schema.publishes;
649
- }
650
- async pubPermSubs(context: JobState, jobOutput: JobOutput, emit = false) {
651
- const topic = await this.getPublishesTopic(context);
652
- if (topic) {
653
- const message: JobMessage = {
654
- type: 'job',
655
- topic,
656
- job: restoreHierarchy(jobOutput) as JobOutput,
657
- };
658
- this.store.publish(KeyType.QUORUM, message, this.appId, `${topic}.${context.metadata.jid}`);
659
- }
660
- }
661
- async add(streamData: StreamData|StreamDataResponse): Promise<string> {
662
- return await this.router.publishMessage(null, streamData) as string;
663
- }
664
-
665
- registerJobCallback(jobId: string, jobCallback: JobMessageCallback) {
666
- this.jobCallbacks[jobId] = jobCallback;
667
- }
668
- delistJobCallback(jobId: string) {
669
- delete this.jobCallbacks[jobId];
670
- }
671
- hasOneTimeSubscription(context: JobState): boolean {
672
- return Boolean(context.metadata.ngn);
673
- }
674
-
675
-
676
- // ********** JOB COMPLETION/CLEANUP (AND JOB EMIT) ***********
677
- async runJobCompletionTasks(context: JobState, options: JobCompletionOptions = {}): Promise<string | void> {
678
- //'emit' indicates the job is still active
679
- const isAwait = this.hasParentJob(context, true);
680
- const isOneTimeSub = this.hasOneTimeSubscription(context);
681
- const topic = await this.getPublishesTopic(context);
682
- let msgId: string;
683
- if (isAwait || isOneTimeSub || topic) {
684
- const jobOutput = await this.getState(
685
- context.metadata.tpc,
686
- context.metadata.jid,
687
- );
688
- msgId = await this.execAdjacentParent(
689
- context,
690
- jobOutput,
691
- options.emit,
692
- );
693
- this.pubOneTimeSubs(context, jobOutput, options.emit);
694
- this.pubPermSubs(context, jobOutput, options.emit);
695
- }
696
- if (!options.emit) {
697
- this.taskService.registerJobForCleanup(
698
- context.metadata.jid,
699
- this.resolveExpires(context, options),
700
- options,
701
- );
702
- }
703
- return msgId;
704
- }
705
-
706
- /**
707
- * Job hash expiration is typically reliant on the metadata field
708
- * if the activity concludes normally. However, if the job is `interrupted`,
709
- * it will be expired immediately.
710
- */
711
- resolveExpires(context: JobState, options: JobCompletionOptions): number {
712
- return options.expire ?? context.metadata.expire ?? HMSH_EXPIRE_JOB_SECONDS;
713
- }
714
-
715
-
716
- // ****** GET JOB STATE/COLLATION STATUS BY ID *********
717
- async export(jobId: string): Promise<JobExport> {
718
- return await this.exporter.export(jobId);
719
- }
720
- async getRaw(jobId: string): Promise<StringStringType> {
721
- return await this.store.getRaw(jobId);
722
- }
723
- async getStatus(jobId: string): Promise<JobStatus> {
724
- const { id: appId } = await this.getVID();
725
- return await this.store.getStatus(jobId, appId);
726
- }
727
- //todo: add 'options' parameter;
728
- // (e.g, if {dimensions:true}, use hscan to deliver
729
- // the full set of dimensional job data)
730
- async getState(topic: string, jobId: string): Promise<JobOutput> {
731
- const jobSymbols = await this.store.getSymbols(`$${topic}`);
732
- const consumes: Consumes = {
733
- [`$${topic}`]: Object.keys(jobSymbols)
734
- }
735
- //job data exists at the 'zero' dimension; pass an empty object
736
- const dIds = {} as StringStringType;
737
- const output = await this.store.getState(jobId, consumes, dIds);
738
- if (!output) {
739
- throw new Error(`not found ${jobId}`);
740
- }
741
- const [state, status] = output;
742
- const stateTree = restoreHierarchy(state) as JobOutput;
743
- if (status && stateTree.metadata) {
744
- stateTree.metadata.js = status;
745
- }
746
- return stateTree;
747
- }
748
- async getQueryState(jobId: string, fields: string[]): Promise<StringAnyType> {
749
- return await this.store.getQueryState(jobId, fields);
750
- }
751
-
752
- async compress(terms: string[]): Promise<boolean> {
753
- const existingSymbols = await this.store.getSymbolValues();
754
- const startIndex = Object.keys(existingSymbols).length;
755
- const maxIndex = Math.pow(52, 2) - 1;
756
- const newSymbols = SerializerService.filterSymVals(startIndex, maxIndex, existingSymbols, new Set(terms));
757
- return await this.store.addSymbolValues(newSymbols);
758
- }
759
- }
760
-
761
- export { EngineService };