@hotmeshio/hotmesh 0.0.17 → 0.0.18

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 (51) hide show
  1. package/build/modules/utils.d.ts +3 -0
  2. package/build/modules/utils.js +10 -1
  3. package/build/package.json +1 -1
  4. package/build/services/activities/activity.d.ts +4 -12
  5. package/build/services/activities/activity.js +14 -156
  6. package/build/services/activities/hook.d.ts +20 -0
  7. package/build/services/activities/hook.js +124 -0
  8. package/build/services/activities/index.d.ts +2 -0
  9. package/build/services/activities/index.js +2 -0
  10. package/build/services/collator/index.js +0 -1
  11. package/build/services/compiler/deployer.d.ts +2 -0
  12. package/build/services/compiler/deployer.js +29 -2
  13. package/build/services/durable/client.d.ts +8 -1
  14. package/build/services/durable/client.js +46 -0
  15. package/build/services/durable/factory.js +11 -10
  16. package/build/services/durable/search.d.ts +15 -0
  17. package/build/services/durable/search.js +45 -0
  18. package/build/services/durable/workflow.d.ts +1 -0
  19. package/build/services/durable/workflow.js +34 -0
  20. package/build/services/engine/index.d.ts +7 -2
  21. package/build/services/engine/index.js +2 -1
  22. package/build/services/store/clients/ioredis.d.ts +1 -0
  23. package/build/services/store/clients/ioredis.js +12 -0
  24. package/build/services/store/clients/redis.d.ts +1 -0
  25. package/build/services/store/clients/redis.js +3 -0
  26. package/build/services/store/index.d.ts +1 -0
  27. package/build/services/telemetry/index.js +2 -1
  28. package/build/types/activity.d.ts +6 -3
  29. package/build/types/durable.d.ts +10 -1
  30. package/build/types/hook.d.ts +1 -0
  31. package/build/types/index.d.ts +2 -2
  32. package/modules/utils.ts +11 -0
  33. package/package.json +1 -1
  34. package/services/activities/activity.ts +15 -167
  35. package/services/activities/hook.ts +149 -0
  36. package/services/activities/index.ts +2 -0
  37. package/services/collator/index.ts +0 -1
  38. package/services/compiler/deployer.ts +32 -2
  39. package/services/durable/client.ts +50 -2
  40. package/services/durable/factory.ts +11 -10
  41. package/services/durable/search.ts +54 -0
  42. package/services/durable/workflow.ts +32 -1
  43. package/services/engine/index.ts +8 -4
  44. package/services/store/clients/ioredis.ts +13 -0
  45. package/services/store/clients/redis.ts +4 -0
  46. package/services/store/index.ts +1 -0
  47. package/services/telemetry/index.ts +2 -1
  48. package/types/activity.ts +7 -2
  49. package/types/durable.ts +8 -0
  50. package/types/hook.ts +1 -0
  51. package/types/index.ts +2 -0
@@ -6,6 +6,9 @@ import { StringAnyType } from "../types/serializer";
6
6
  import { StreamCode, StreamStatus } from "../types/stream";
7
7
  export declare function sleepFor(ms: number): Promise<unknown>;
8
8
  export declare function identifyRedisType(redisInstance: any): 'redis' | 'ioredis' | null;
9
+ export declare const polyfill: {
10
+ resolveActivityType(activityType: string): string;
11
+ };
9
12
  export declare function identifyRedisTypeFromClass(redisClass: any): 'redis' | 'ioredis' | null;
10
13
  export declare function matchesStatusCode(code: StreamCode, pattern: string | RegExp): boolean;
11
14
  export declare function matchesStatus(status: StreamStatus, targetStatus: StreamStatus): boolean;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.restoreHierarchy = exports.getValueByPath = exports.getIndexedHash = exports.getSymVal = exports.getSymKey = exports.formatISODate = exports.getTimeSeries = exports.getSubscriptionTopic = exports.findSubscriptionForTrigger = exports.findTopKey = exports.XSleepFor = exports.matchesStatus = exports.matchesStatusCode = exports.identifyRedisTypeFromClass = exports.identifyRedisType = exports.sleepFor = void 0;
3
+ exports.restoreHierarchy = exports.getValueByPath = exports.getIndexedHash = exports.getSymVal = exports.getSymKey = exports.formatISODate = exports.getTimeSeries = exports.getSubscriptionTopic = exports.findSubscriptionForTrigger = exports.findTopKey = exports.XSleepFor = exports.matchesStatus = exports.matchesStatusCode = exports.identifyRedisTypeFromClass = exports.polyfill = exports.identifyRedisType = exports.sleepFor = void 0;
4
4
  async function sleepFor(ms) {
5
5
  return new Promise((resolve) => setTimeout(resolve, ms));
6
6
  }
@@ -21,6 +21,15 @@ function identifyRedisType(redisInstance) {
21
21
  return null;
22
22
  }
23
23
  exports.identifyRedisType = identifyRedisType;
24
+ //todo: the polyfill methods will all be deleted in the `beta` release.
25
+ exports.polyfill = {
26
+ resolveActivityType(activityType) {
27
+ if (activityType === 'activity') {
28
+ return 'hook';
29
+ }
30
+ return activityType;
31
+ }
32
+ };
24
33
  function identifyRedisTypeFromClass(redisClass) {
25
34
  if (redisClass && redisClass.name === 'Redis' || redisClass.name === 'EventEmitter') {
26
35
  return 'ioredis';
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -7,7 +7,6 @@ import { JobState, JobStatus } from '../../types/job';
7
7
  import { MultiResponseFlags, RedisClient, RedisMulti } from '../../types/redis';
8
8
  import { StringAnyType } from '../../types/serializer';
9
9
  import { StreamCode, StreamData, StreamStatus } from '../../types/stream';
10
- import { HookRule } from '../../types/hook';
11
10
  /**
12
11
  * The base class for all activities
13
12
  */
@@ -26,18 +25,11 @@ declare class Activity {
26
25
  adjacencyList: StreamData[];
27
26
  adjacentIndex: number;
28
27
  constructor(config: ActivityType, data: ActivityData, metadata: ActivityMetadata, hook: ActivityData | null, engine: EngineService, context?: JobState);
29
- process(): Promise<string>;
30
28
  setLeg(leg: ActivityLeg): void;
31
- doesHook(): boolean;
32
- getHookRule(topic: string): Promise<HookRule | undefined>;
33
- registerHook(multi?: RedisMulti): Promise<string | void>;
34
- processWebHookEvent(): Promise<JobStatus | void>;
35
- processTimeHookEvent(jobId: string): Promise<JobStatus | void>;
36
- processHookEvent(jobId: string): Promise<JobStatus | void>;
37
- processEvent(status?: StreamStatus, code?: StreamCode): Promise<void>;
38
- processPending(telemetry: TelemetryService): Promise<MultiResponseFlags>;
39
- processSuccess(telemetry: TelemetryService): Promise<MultiResponseFlags>;
40
- processError(telemetry: TelemetryService): Promise<MultiResponseFlags>;
29
+ processEvent(status?: StreamStatus, code?: StreamCode, type?: 'hook' | 'output', jobId?: string): Promise<void>;
30
+ processPending(telemetry: TelemetryService, type: 'hook' | 'output'): Promise<MultiResponseFlags>;
31
+ processSuccess(telemetry: TelemetryService, type: 'hook' | 'output'): Promise<MultiResponseFlags>;
32
+ processError(telemetry: TelemetryService, type: string): Promise<MultiResponseFlags>;
41
33
  transitionAdjacent(multiResponse: MultiResponseFlags, telemetry: TelemetryService): Promise<void>;
42
34
  resolveStatus(multiResponse: MultiResponseFlags): number;
43
35
  mapJobData(): void;
@@ -7,7 +7,6 @@ const collator_1 = require("../collator");
7
7
  const mapper_1 = require("../mapper");
8
8
  const pipe_1 = require("../pipe");
9
9
  const serializer_1 = require("../serializer");
10
- const store_1 = require("../signaler/store");
11
10
  const telemetry_1 = require("../telemetry");
12
11
  const stream_1 = require("../../types/stream");
13
12
  /**
@@ -17,7 +16,7 @@ class Activity {
17
16
  constructor(config, data, metadata, hook, engine, context) {
18
17
  this.status = stream_1.StreamStatus.SUCCESS;
19
18
  this.code = 200;
20
- this.adjacentIndex = 0; //can be updated by leg2 using 'as' metadata hincrby output
19
+ this.adjacentIndex = 0;
21
20
  this.config = config;
22
21
  this.data = data;
23
22
  this.metadata = metadata;
@@ -27,161 +26,20 @@ class Activity {
27
26
  this.logger = engine.logger;
28
27
  this.store = engine.store;
29
28
  }
30
- //******** INITIAL ENTRY POINT (A) ********//
31
- async process() {
32
- this.logger.debug('activity-process', { jid: this.context.metadata.jid, aid: this.metadata.aid });
33
- let telemetry;
34
- try {
35
- this.setLeg(1);
36
- await collator_1.CollatorService.notarizeEntry(this);
37
- await this.getState();
38
- telemetry = new telemetry_1.TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
39
- telemetry.startActivitySpan(this.leg);
40
- let multiResponse;
41
- const multi = this.store.getMulti();
42
- if (this.doesHook()) {
43
- //sleep and wait to awaken upon a signal
44
- await this.registerHook(multi);
45
- this.mapOutputData();
46
- this.mapJobData();
47
- await this.setState(multi);
48
- await collator_1.CollatorService.authorizeReentry(this, multi);
49
- await this.setStatus(0, multi);
50
- await multi.exec();
51
- telemetry.mapActivityAttributes();
52
- }
53
- else {
54
- //end the activity and transition to its children
55
- this.adjacencyList = await this.filterAdjacent();
56
- this.mapOutputData();
57
- this.mapJobData();
58
- await this.setState(multi);
59
- await collator_1.CollatorService.notarizeEarlyCompletion(this, multi);
60
- await this.setStatus(this.adjacencyList.length - 1, multi);
61
- multiResponse = await multi.exec();
62
- telemetry.mapActivityAttributes();
63
- const jobStatus = this.resolveStatus(multiResponse);
64
- const attrs = { 'app.job.jss': jobStatus };
65
- const messageIds = await this.transition(this.adjacencyList, jobStatus);
66
- if (messageIds.length) {
67
- attrs['app.activity.mids'] = messageIds.join(',');
68
- }
69
- telemetry.setActivityAttributes(attrs);
70
- }
71
- return this.context.metadata.aid;
72
- }
73
- catch (error) {
74
- if (error instanceof errors_1.GetStateError) {
75
- this.logger.error('activity-get-state-error', { error });
76
- }
77
- else {
78
- this.logger.error('activity-process-error', { error });
79
- }
80
- telemetry.setActivityError(error.message);
81
- throw error;
82
- }
83
- finally {
84
- telemetry.endActivitySpan();
85
- this.logger.debug('activity-process-end', { jid: this.context.metadata.jid, aid: this.metadata.aid });
86
- }
87
- }
88
29
  setLeg(leg) {
89
30
  this.leg = leg;
90
31
  }
91
- //******** SIGNAL RE-ENTRY POINT ********//
92
- doesHook() {
93
- return !!(this.config.hook?.topic || this.config.sleep);
94
- }
95
- async getHookRule(topic) {
96
- const rules = await this.store.getHookRules();
97
- return rules?.[topic]?.[0];
98
- }
99
- async registerHook(multi) {
100
- if (this.config.hook?.topic) {
101
- const signaler = new store_1.StoreSignaler(this.store, this.logger);
102
- return await signaler.registerWebHook(this.config.hook.topic, this.context, multi);
103
- }
104
- else if (this.config.sleep) {
105
- const durationInSeconds = pipe_1.Pipe.resolve(this.config.sleep, this.context);
106
- const jobId = this.context.metadata.jid;
107
- const activityId = this.metadata.aid;
108
- const dId = this.metadata.dad;
109
- await this.engine.task.registerTimeHook(jobId, `${activityId}${dId || ''}`, 'sleep', durationInSeconds);
110
- return jobId;
111
- }
112
- }
113
- async processWebHookEvent() {
114
- this.logger.debug('engine-process-web-hook-event', {
115
- topic: this.config.hook.topic,
116
- aid: this.metadata.aid
117
- });
118
- const signaler = new store_1.StoreSignaler(this.store, this.logger);
119
- const data = { ...this.data };
120
- const jobId = await signaler.processWebHookSignal(this.config.hook.topic, data);
121
- if (jobId) {
122
- await this.processHookEvent(jobId);
123
- await signaler.deleteWebHookSignal(this.config.hook.topic, data);
124
- } //else => already resolved
125
- }
126
- async processTimeHookEvent(jobId) {
127
- this.logger.debug('engine-process-time-hook-event', {
128
- jid: jobId,
129
- aid: this.metadata.aid
130
- });
131
- return await this.processHookEvent(jobId);
132
- }
133
- async processHookEvent(jobId) {
134
- this.logger.debug('activity-process-hook-event', { jobId });
135
- let telemetry;
136
- try {
137
- this.setLeg(2);
138
- await this.getState(jobId);
139
- telemetry = new telemetry_1.TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
140
- telemetry.startActivitySpan(this.leg);
141
- const aState = await collator_1.CollatorService.notarizeReentry(this);
142
- this.adjacentIndex = collator_1.CollatorService.getDimensionalIndex(aState);
143
- this.bindActivityData('hook');
144
- this.mapJobData();
145
- this.adjacencyList = await this.filterAdjacent();
146
- const multi = this.engine.store.getMulti();
147
- await this.setState(multi);
148
- await collator_1.CollatorService.notarizeCompletion(this, multi);
149
- await this.setStatus(this.adjacencyList.length - 1, multi);
150
- const multiResponse = await multi.exec();
151
- telemetry.mapActivityAttributes();
152
- const jobStatus = this.resolveStatus(multiResponse);
153
- const attrs = { 'app.job.jss': jobStatus };
154
- const messageIds = await this.transition(this.adjacencyList, jobStatus);
155
- if (messageIds.length) {
156
- attrs['app.activity.mids'] = messageIds.join(',');
157
- }
158
- telemetry.setActivityAttributes(attrs);
159
- return jobStatus;
160
- }
161
- catch (error) {
162
- if (error instanceof errors_1.CollationError && error.fault === 'inactive') {
163
- this.logger.info('process-hook-event-inactive-error', { error });
164
- return;
165
- }
166
- this.logger.error('engine-process-hook-event-error', { error });
167
- telemetry.setActivityError(error.message);
168
- throw error;
169
- }
170
- finally {
171
- telemetry.endActivitySpan();
172
- }
173
- }
174
32
  //******** DUPLEX RE-ENTRY POINT ********//
175
- async processEvent(status = stream_1.StreamStatus.SUCCESS, code = 200) {
33
+ async processEvent(status = stream_1.StreamStatus.SUCCESS, code = 200, type = 'output', jobId) {
176
34
  this.setLeg(2);
177
- const jid = this.context.metadata.jid;
35
+ const jid = this.context.metadata.jid || jobId;
178
36
  const aid = this.metadata.aid;
179
37
  this.status = status;
180
38
  this.code = code;
181
39
  this.logger.debug('activity-process-event', { topic: this.config.subtype, jid, aid, status, code });
182
40
  let telemetry;
183
41
  try {
184
- await this.getState();
42
+ await this.getState(jobId);
185
43
  const aState = await collator_1.CollatorService.notarizeReentry(this);
186
44
  this.adjacentIndex = collator_1.CollatorService.getDimensionalIndex(aState);
187
45
  telemetry = new telemetry_1.TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
@@ -194,21 +52,22 @@ class Activity {
194
52
  telemetry.startActivitySpan(this.leg);
195
53
  let multiResponse;
196
54
  if (status === stream_1.StreamStatus.PENDING) {
197
- multiResponse = await this.processPending(telemetry);
55
+ multiResponse = await this.processPending(telemetry, type);
198
56
  }
199
57
  else if (status === stream_1.StreamStatus.SUCCESS) {
200
- multiResponse = await this.processSuccess(telemetry);
58
+ multiResponse = await this.processSuccess(telemetry, type);
201
59
  }
202
60
  else {
203
- multiResponse = await this.processError(telemetry);
61
+ multiResponse = await this.processError(telemetry, type);
204
62
  }
205
63
  this.transitionAdjacent(multiResponse, telemetry);
206
64
  }
207
65
  catch (error) {
208
- if (error instanceof errors_1.CollationError && error.fault === 'inactive') {
66
+ if (error instanceof errors_1.CollationError) {
209
67
  this.logger.info('process-event-inactive-error', { error });
210
68
  return;
211
69
  }
70
+ console.error(error);
212
71
  this.logger.error('activity-process-event-error', { error });
213
72
  telemetry && telemetry.setActivityError(error.message);
214
73
  throw error;
@@ -218,8 +77,8 @@ class Activity {
218
77
  this.logger.debug('activity-process-event-end', { jid, aid });
219
78
  }
220
79
  }
221
- async processPending(telemetry) {
222
- this.bindActivityData('output');
80
+ async processPending(telemetry, type) {
81
+ this.bindActivityData(type);
223
82
  this.adjacencyList = await this.filterAdjacent();
224
83
  this.mapJobData();
225
84
  const multi = this.store.getMulti();
@@ -228,8 +87,8 @@ class Activity {
228
87
  await this.setStatus(this.adjacencyList.length, multi);
229
88
  return await multi.exec();
230
89
  }
231
- async processSuccess(telemetry) {
232
- this.bindActivityData('output');
90
+ async processSuccess(telemetry, type) {
91
+ this.bindActivityData(type);
233
92
  this.adjacencyList = await this.filterAdjacent();
234
93
  this.mapJobData();
235
94
  const multi = this.store.getMulti();
@@ -238,7 +97,7 @@ class Activity {
238
97
  await this.setStatus(this.adjacencyList.length - 1, multi);
239
98
  return await multi.exec();
240
99
  }
241
- async processError(telemetry) {
100
+ async processError(telemetry, type) {
242
101
  this.bindActivityError(this.data);
243
102
  this.adjacencyList = await this.filterAdjacent();
244
103
  const multi = this.store.getMulti();
@@ -496,7 +355,6 @@ class Activity {
496
355
  }
497
356
  async transition(adjacencyList, jobStatus) {
498
357
  let mIds = [];
499
- //emit can be a mapping (allows emissions to be driven by the job state)
500
358
  let emit = false;
501
359
  if (this.config.emit) {
502
360
  emit = pipe_1.Pipe.resolve(this.config.emit, this.context);
@@ -0,0 +1,20 @@
1
+ import { EngineService } from '../engine';
2
+ import { ActivityData, ActivityMetadata, ActivityType, HookActivity } from '../../types/activity';
3
+ import { JobState, JobStatus } from '../../types/job';
4
+ import { RedisMulti } from '../../types/redis';
5
+ import { HookRule } from '../../types/hook';
6
+ import { Activity } from './activity';
7
+ /**
8
+ * Listens for `webhook`, `timehook`, and `cycle` (repeat) signals
9
+ */
10
+ declare class Hook extends Activity {
11
+ config: HookActivity;
12
+ constructor(config: ActivityType, data: ActivityData, metadata: ActivityMetadata, hook: ActivityData | null, engine: EngineService, context?: JobState);
13
+ process(): Promise<string>;
14
+ doesHook(): boolean;
15
+ getHookRule(topic: string): Promise<HookRule | undefined>;
16
+ registerHook(multi?: RedisMulti): Promise<string | void>;
17
+ processWebHookEvent(): Promise<JobStatus | void>;
18
+ processTimeHookEvent(jobId: string): Promise<JobStatus | void>;
19
+ }
20
+ export { Hook };
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Hook = void 0;
4
+ const errors_1 = require("../../modules/errors");
5
+ const collator_1 = require("../collator");
6
+ const pipe_1 = require("../pipe");
7
+ const store_1 = require("../signaler/store");
8
+ const telemetry_1 = require("../telemetry");
9
+ const activity_1 = require("./activity");
10
+ const types_1 = require("../../types");
11
+ /**
12
+ * Listens for `webhook`, `timehook`, and `cycle` (repeat) signals
13
+ */
14
+ class Hook extends activity_1.Activity {
15
+ constructor(config, data, metadata, hook, engine, context) {
16
+ super(config, data, metadata, hook, engine, context);
17
+ }
18
+ //******** INITIAL ENTRY POINT (A) ********//
19
+ async process() {
20
+ this.logger.debug('hook-process', { jid: this.context.metadata.jid, aid: this.metadata.aid });
21
+ let telemetry;
22
+ try {
23
+ this.setLeg(1);
24
+ await collator_1.CollatorService.notarizeEntry(this);
25
+ await this.getState();
26
+ telemetry = new telemetry_1.TelemetryService(this.engine.appId, this.config, this.metadata, this.context);
27
+ telemetry.startActivitySpan(this.leg);
28
+ let multiResponse;
29
+ const multi = this.store.getMulti();
30
+ if (this.doesHook()) {
31
+ //sleep and wait to awaken upon a signal
32
+ await this.registerHook(multi);
33
+ this.mapOutputData();
34
+ this.mapJobData();
35
+ await this.setState(multi);
36
+ await collator_1.CollatorService.authorizeReentry(this, multi);
37
+ await this.setStatus(0, multi);
38
+ await multi.exec();
39
+ telemetry.mapActivityAttributes();
40
+ }
41
+ else {
42
+ //end the activity and transition to its children
43
+ this.adjacencyList = await this.filterAdjacent();
44
+ this.mapOutputData();
45
+ this.mapJobData();
46
+ await this.setState(multi);
47
+ await collator_1.CollatorService.notarizeEarlyCompletion(this, multi);
48
+ await this.setStatus(this.adjacencyList.length - 1, multi);
49
+ multiResponse = await multi.exec();
50
+ telemetry.mapActivityAttributes();
51
+ const jobStatus = this.resolveStatus(multiResponse);
52
+ const attrs = { 'app.job.jss': jobStatus };
53
+ const messageIds = await this.transition(this.adjacencyList, jobStatus);
54
+ if (messageIds.length) {
55
+ attrs['app.activity.mids'] = messageIds.join(',');
56
+ }
57
+ telemetry.setActivityAttributes(attrs);
58
+ }
59
+ return this.context.metadata.aid;
60
+ }
61
+ catch (error) {
62
+ if (error instanceof errors_1.GetStateError) {
63
+ this.logger.error('hook-get-state-error', { error });
64
+ }
65
+ else {
66
+ this.logger.error('hook-process-error', { error });
67
+ }
68
+ telemetry.setActivityError(error.message);
69
+ throw error;
70
+ }
71
+ finally {
72
+ telemetry.endActivitySpan();
73
+ this.logger.debug('hook-process-end', { jid: this.context.metadata.jid, aid: this.metadata.aid });
74
+ }
75
+ }
76
+ //******** SIGNAL RE-ENTRY POINT ********//
77
+ doesHook() {
78
+ //does this activity use a time-hook or web-hook
79
+ return !!(this.config.hook?.topic || this.config.sleep);
80
+ }
81
+ async getHookRule(topic) {
82
+ const rules = await this.store.getHookRules();
83
+ return rules?.[topic]?.[0];
84
+ }
85
+ async registerHook(multi) {
86
+ if (this.config.hook?.topic) {
87
+ const signaler = new store_1.StoreSignaler(this.store, this.logger);
88
+ return await signaler.registerWebHook(this.config.hook.topic, this.context, multi);
89
+ }
90
+ else if (this.config.sleep) {
91
+ const durationInSeconds = pipe_1.Pipe.resolve(this.config.sleep, this.context);
92
+ const jobId = this.context.metadata.jid;
93
+ const activityId = this.metadata.aid;
94
+ const dId = this.metadata.dad;
95
+ await this.engine.task.registerTimeHook(jobId, `${activityId}${dId || ''}`, 'sleep', durationInSeconds);
96
+ return jobId;
97
+ }
98
+ }
99
+ async processWebHookEvent() {
100
+ this.logger.debug('hook-process-web-hook-event', {
101
+ topic: this.config.hook.topic,
102
+ aid: this.metadata.aid
103
+ });
104
+ const signaler = new store_1.StoreSignaler(this.store, this.logger);
105
+ const data = { ...this.data };
106
+ const jobId = await signaler.processWebHookSignal(this.config.hook.topic, data);
107
+ if (jobId) {
108
+ //if a webhook signal is sent that includes 'keep_alive' the hook will remain open
109
+ const code = data.keep_alive ? 202 : 200;
110
+ await this.processEvent(types_1.StreamStatus.SUCCESS, code, 'hook', jobId);
111
+ if (code === 200) {
112
+ await signaler.deleteWebHookSignal(this.config.hook.topic, data);
113
+ }
114
+ } //else => already resolved
115
+ }
116
+ async processTimeHookEvent(jobId) {
117
+ this.logger.debug('hook-process-time-hook-event', {
118
+ jid: jobId,
119
+ aid: this.metadata.aid
120
+ });
121
+ await this.processEvent(types_1.StreamStatus.SUCCESS, 200, 'hook');
122
+ }
123
+ }
124
+ exports.Hook = Hook;
@@ -1,6 +1,7 @@
1
1
  import { Activity } from './activity';
2
2
  import { Await } from './await';
3
3
  import { Cycle } from './cycle';
4
+ import { Hook } from './hook';
4
5
  import { Iterate } from './iterate';
5
6
  import { Signal } from './signal';
6
7
  import { Trigger } from './trigger';
@@ -9,6 +10,7 @@ declare const _default: {
9
10
  activity: typeof Activity;
10
11
  await: typeof Await;
11
12
  cycle: typeof Cycle;
13
+ hook: typeof Hook;
12
14
  iterate: typeof Iterate;
13
15
  signal: typeof Signal;
14
16
  trigger: typeof Trigger;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const activity_1 = require("./activity");
4
4
  const await_1 = require("./await");
5
5
  const cycle_1 = require("./cycle");
6
+ const hook_1 = require("./hook");
6
7
  const iterate_1 = require("./iterate");
7
8
  const signal_1 = require("./signal");
8
9
  const trigger_1 = require("./trigger");
@@ -11,6 +12,7 @@ exports.default = {
11
12
  activity: activity_1.Activity,
12
13
  await: await_1.Await,
13
14
  cycle: cycle_1.Cycle,
15
+ hook: hook_1.Hook,
14
16
  iterate: iterate_1.Iterate,
15
17
  signal: signal_1.Signal,
16
18
  trigger: trigger_1.Trigger,
@@ -45,7 +45,6 @@ class CollatorService {
45
45
  //set second digit to 8, allowing for re-entry
46
46
  //decrement by -10_000_000_000_000
47
47
  const amount = await activity.store.collate(activity.context.metadata.jid, activity.metadata.aid, -10000000000000, this.getDimensionalAddress(activity), multi);
48
- //this.verifyInteger(amount, 1, 'exit');
49
48
  return amount;
50
49
  }
51
50
  static async notarizeEarlyExit(activity, multi) {
@@ -16,7 +16,9 @@ declare class Deployer {
16
16
  bindSymbols(startIndex: number, maxIndex: number, existingSymbols: Symbols, prefix: string, produces: string[]): Symbols;
17
17
  copyJobSchemas(): void;
18
18
  bindBackRefs(): void;
19
+ bindCycleTarget(): void;
19
20
  convertTopicsToTypes(): void;
21
+ convertActivitiesToHooks(): void;
20
22
  bindParents(): Promise<void>;
21
23
  collectValues(schema: Record<string, any>, values: Set<string>): void;
22
24
  traverse(obj: any, values: Set<string>): void;
@@ -17,10 +17,12 @@ class Deployer {
17
17
  async deploy(store) {
18
18
  this.store = store;
19
19
  collator_1.CollatorService.compile(this.manifest.app.graphs);
20
+ this.convertActivitiesToHooks();
20
21
  this.convertTopicsToTypes();
21
22
  this.copyJobSchemas();
22
23
  this.bindBackRefs();
23
24
  this.bindParents();
25
+ this.bindCycleTarget();
24
26
  this.resolveMappingDependencies();
25
27
  this.resolveJobMapsPaths();
26
28
  await this.generateSymKeys();
@@ -136,8 +138,21 @@ class Deployer {
136
138
  }
137
139
  }
138
140
  }
139
- //more intuitive for SDK users to use 'topic',
140
- //but the compiler is desiged to be generic and uses 'subtypes'
141
+ //the cycle/goto activity includes and ancestor target;
142
+ //update with the cycle flag, so it can be rerun
143
+ bindCycleTarget() {
144
+ for (const graph of this.manifest.app.graphs) {
145
+ const activities = graph.activities;
146
+ for (const activityKey in activities) {
147
+ const activity = activities[activityKey];
148
+ if (activity.type === 'cycle') {
149
+ activities[activity.ancestor].cycle = true;
150
+ }
151
+ }
152
+ }
153
+ }
154
+ //it's more intuitive for SDK users to use 'topic',
155
+ //but the compiler is desiged to be generic and uses the attribute, 'subtypes'
141
156
  convertTopicsToTypes() {
142
157
  for (const graph of this.manifest.app.graphs) {
143
158
  const activities = graph.activities;
@@ -149,6 +164,18 @@ class Deployer {
149
164
  }
150
165
  }
151
166
  }
167
+ //legacy; remove at beta (assume no legacy refs to 'activity' at that point)
168
+ convertActivitiesToHooks() {
169
+ for (const graph of this.manifest.app.graphs) {
170
+ const activities = graph.activities;
171
+ for (const activityKey in activities) {
172
+ const activity = activities[activityKey];
173
+ if (['activity'].includes(activity.type)) {
174
+ activity.type = 'hook';
175
+ }
176
+ }
177
+ }
178
+ }
152
179
  async bindParents() {
153
180
  const graphs = this.manifest.app.graphs;
154
181
  for (const graph of graphs) {
@@ -1,16 +1,23 @@
1
1
  import { WorkflowHandleService } from './handle';
2
2
  import { HotMeshService as HotMesh } from '../hotmesh';
3
- import { ClientConfig, Connection, WorkflowOptions } from '../../types/durable';
3
+ import { ClientConfig, Connection, WorkflowOptions, WorkflowSearchOptions } from '../../types/durable';
4
4
  export declare class ClientService {
5
5
  connection: Connection;
6
6
  options: WorkflowOptions;
7
7
  static instances: Map<string, HotMesh | Promise<HotMesh>>;
8
8
  constructor(config: ClientConfig);
9
9
  getHotMeshClient: (worflowTopic: string) => Promise<HotMesh>;
10
+ /**
11
+ * For those deployments with a redis stack backend (with the FT module),
12
+ * this method will configure the search index for the workflow.
13
+ */
14
+ configureSearchIndex: (hotMeshClient: HotMesh, search?: WorkflowSearchOptions) => Promise<void>;
15
+ search: (hotMeshClient: HotMesh, index: string, query: string[]) => Promise<string[]>;
10
16
  workflow: {
11
17
  start: (options: WorkflowOptions) => Promise<WorkflowHandleService>;
12
18
  signal: (signalId: string, data: Record<any, any>) => Promise<string>;
13
19
  getHandle: (taskQueue: string, workflowName: string, workflowId: string) => Promise<WorkflowHandleService>;
20
+ search: (taskQueue: string, workflowName: string, index: string, ...query: string[]) => Promise<string[]>;
14
21
  };
15
22
  activateWorkflow(hotMesh: HotMesh, appId?: string, version?: string): Promise<void>;
16
23
  static shutdown(): Promise<void>;