@hotmeshio/hotmesh 0.0.17 → 0.0.19

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 (54) hide show
  1. package/build/modules/utils.d.ts +3 -0
  2. package/build/modules/utils.js +17 -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 +54 -40
  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/worker.d.ts +6 -1
  19. package/build/services/durable/worker.js +34 -30
  20. package/build/services/durable/workflow.d.ts +2 -0
  21. package/build/services/durable/workflow.js +11 -28
  22. package/build/services/engine/index.d.ts +7 -2
  23. package/build/services/engine/index.js +2 -1
  24. package/build/services/store/clients/ioredis.d.ts +1 -0
  25. package/build/services/store/clients/ioredis.js +12 -0
  26. package/build/services/store/clients/redis.d.ts +1 -0
  27. package/build/services/store/clients/redis.js +3 -0
  28. package/build/services/store/index.d.ts +1 -0
  29. package/build/services/telemetry/index.js +2 -1
  30. package/build/types/activity.d.ts +6 -3
  31. package/build/types/durable.d.ts +12 -1
  32. package/build/types/hook.d.ts +1 -0
  33. package/build/types/index.d.ts +2 -2
  34. package/modules/utils.ts +17 -0
  35. package/package.json +1 -1
  36. package/services/activities/activity.ts +15 -167
  37. package/services/activities/hook.ts +149 -0
  38. package/services/activities/index.ts +2 -0
  39. package/services/collator/index.ts +0 -1
  40. package/services/compiler/deployer.ts +32 -2
  41. package/services/durable/client.ts +58 -43
  42. package/services/durable/factory.ts +11 -10
  43. package/services/durable/search.ts +54 -0
  44. package/services/durable/worker.ts +36 -32
  45. package/services/durable/workflow.ts +14 -30
  46. package/services/engine/index.ts +8 -4
  47. package/services/store/clients/ioredis.ts +13 -0
  48. package/services/store/clients/redis.ts +4 -0
  49. package/services/store/index.ts +1 -0
  50. package/services/telemetry/index.ts +2 -1
  51. package/types/activity.ts +7 -2
  52. package/types/durable.ts +10 -0
  53. package/types/hook.ts +1 -0
  54. package/types/index.ts +2 -0
@@ -6,46 +6,7 @@ const factory_1 = require("./factory");
6
6
  const handle_1 = require("./handle");
7
7
  const hotmesh_1 = require("../hotmesh");
8
8
  const key_1 = require("../../modules/key");
9
- /*
10
- Here is an example of how the methods in this file are used:
11
-
12
- ./client.ts
13
-
14
- import { Durable } from '@hotmeshio/hotmesh';
15
- import Redis from 'ioredis';
16
- import { example } from './workflows';
17
- import { nanoid } from 'nanoid';
18
-
19
- async function run() {
20
- const connection = await Durable.Connection.connect({
21
- class: Redis,
22
- options: {
23
- host: 'localhost',
24
- port: 6379,
25
- },
26
- });
27
-
28
- const client = new Durable.Client({
29
- connection,
30
- });
31
-
32
- const handle = await client.workflow.start({
33
- args: ['HotMesh'],
34
- taskQueue: 'hello-world',
35
- workflowName: 'example',
36
- workflowId: 'workflow-' + nanoid(),
37
- });
38
-
39
- console.log(`Started workflow ${handle.workflowId}`);
40
- console.log(await handle.result());
41
- }
42
-
43
- run().catch((err) => {
44
- console.error(err);
45
- process.exit(1);
46
- });
47
-
48
- */
9
+ const search_1 = require("./search");
49
10
  class ClientService {
50
11
  constructor(config) {
51
12
  this.getHotMeshClient = async (worflowTopic) => {
@@ -76,6 +37,40 @@ class ClientService {
76
37
  await this.activateWorkflow(await hotMeshClient);
77
38
  return hotMeshClient;
78
39
  };
40
+ /**
41
+ * For those deployments with a redis stack backend (with the FT module),
42
+ * this method will configure the search index for the workflow.
43
+ */
44
+ this.configureSearchIndex = async (hotMeshClient, search) => {
45
+ if (search?.schema) {
46
+ const store = hotMeshClient.engine.store;
47
+ const schema = [];
48
+ for (const [key, value] of Object.entries(search.schema)) {
49
+ //prefix with a comma (avoids collisions with hotmesh reserved words)
50
+ schema.push(`_${key}`);
51
+ schema.push(value.type);
52
+ if (value.sortable) {
53
+ schema.push('SORTABLE');
54
+ }
55
+ }
56
+ try {
57
+ const keyParams = {
58
+ appId: hotMeshClient.appId,
59
+ jobId: ''
60
+ };
61
+ const hotMeshPrefix = key_1.KeyService.mintKey(hotMeshClient.namespace, key_1.KeyType.JOB_STATE, keyParams);
62
+ const prefixes = search.prefix.map((prefix) => `${hotMeshPrefix}${prefix}`);
63
+ await store.exec('FT.CREATE', `${search.index}`, 'ON', 'HASH', 'PREFIX', prefixes.length, ...prefixes, 'SCHEMA', ...schema);
64
+ }
65
+ catch (err) {
66
+ hotMeshClient.engine.logger.info('durable-client-search-err', { err });
67
+ }
68
+ }
69
+ };
70
+ this.search = async (hotMeshClient, index, query) => {
71
+ const store = hotMeshClient.engine.store;
72
+ return await store.exec('FT.SEARCH', index, ...query);
73
+ };
79
74
  this.workflow = {
80
75
  start: async (options) => {
81
76
  const taskQueueName = options.taskQueue;
@@ -85,6 +80,7 @@ class ClientService {
85
80
  //topic is concat of taskQueue and workflowName
86
81
  const workflowTopic = `${taskQueueName}-${workflowName}`;
87
82
  const hotMeshClient = await this.getHotMeshClient(workflowTopic);
83
+ this.configureSearchIndex(hotMeshClient, options.search);
88
84
  const payload = {
89
85
  arguments: [...options.args],
90
86
  parentWorkflowId: options.parentWorkflowId,
@@ -94,6 +90,13 @@ class ClientService {
94
90
  };
95
91
  const context = { metadata: { trc, spn }, data: {} };
96
92
  const jobId = await hotMeshClient.pub(factory_1.SUBSCRIBES_TOPIC, payload, context);
93
+ if (jobId && options.search?.data) {
94
+ //job successfully kicked off; there is default job data to persist
95
+ const search = new search_1.Search(jobId, hotMeshClient);
96
+ for (const [key, value] of Object.entries(options.search.data)) {
97
+ search.set(key, value);
98
+ }
99
+ }
97
100
  return new handle_1.WorkflowHandleService(hotMeshClient, workflowTopic, jobId);
98
101
  },
99
102
  signal: async (signalId, data) => {
@@ -103,6 +106,17 @@ class ClientService {
103
106
  const workflowTopic = `${taskQueue}-${workflowName}`;
104
107
  const hotMeshClient = await this.getHotMeshClient(workflowTopic);
105
108
  return new handle_1.WorkflowHandleService(hotMeshClient, workflowTopic, workflowId);
109
+ },
110
+ search: async (taskQueue, workflowName, index, ...query) => {
111
+ const workflowTopic = `${taskQueue}-${workflowName}`;
112
+ const hotMeshClient = await this.getHotMeshClient(workflowTopic);
113
+ try {
114
+ return await this.search(hotMeshClient, index, query);
115
+ }
116
+ catch (err) {
117
+ hotMeshClient.engine.logger.error('durable-client-search-err', { err });
118
+ throw err;
119
+ }
106
120
  }
107
121
  };
108
122
  this.connection = config.connection;
@@ -59,7 +59,7 @@ const getWorkflowYAML = (app, version) => {
59
59
  done: false
60
60
 
61
61
  a1:
62
- type: activity
62
+ type: hook
63
63
  cycle: true
64
64
  output:
65
65
  schema:
@@ -132,7 +132,7 @@ const getWorkflowYAML = (app, version) => {
132
132
  done: '{$self.output.data.done}'
133
133
 
134
134
  a2:
135
- type: activity
135
+ type: hook
136
136
  title: Wait for cleanup signal
137
137
  hook:
138
138
  type: object
@@ -248,7 +248,7 @@ const getWorkflowYAML = (app, version) => {
248
248
 
249
249
  a599:
250
250
  title: Sleep exponentially longer before retrying
251
- type: activity
251
+ type: hook
252
252
  sleep: '{a1.output.data.duration}'
253
253
 
254
254
  c599:
@@ -540,7 +540,7 @@ const getWorkflowYAML = (app, version) => {
540
540
  done: true
541
541
 
542
542
  s1a:
543
- type: activity
543
+ type: hook
544
544
  title: Wait for cleanup signal
545
545
  hook:
546
546
  type: object
@@ -560,6 +560,7 @@ const getWorkflowYAML = (app, version) => {
560
560
  hooks:
561
561
  ${app}.activity.awaken:
562
562
  - to: s1a
563
+ keep_alive: true
563
564
  conditions:
564
565
  match:
565
566
  - expected: '{t1a.output.data.workflowId}'
@@ -606,13 +607,13 @@ const getWorkflowYAML = (app, version) => {
606
607
  target: '{$self.input.data.parentWorkflowId}'
607
608
 
608
609
  a1s:
609
- type: activity
610
+ type: hook
610
611
  title: Sleep for a duration
611
612
  sleep: '{t1s.output.data.duration}'
612
613
  emit: true
613
614
 
614
615
  a2s:
615
- type: activity
616
+ type: hook
616
617
  title: Wait for cleanup signal
617
618
  hook:
618
619
  type: object
@@ -684,7 +685,7 @@ const getWorkflowYAML = (app, version) => {
684
685
 
685
686
  a1wc:
686
687
  title: Split signal data
687
- type: activity
688
+ type: hook
688
689
  cycle: true
689
690
  output:
690
691
  schema:
@@ -722,7 +723,7 @@ const getWorkflowYAML = (app, version) => {
722
723
  - ['{t1wc.output.data.signals}', 1]
723
724
  - ['{@array.slice}']
724
725
  a2wc:
725
- type: activity
726
+ type: hook
726
727
  output:
727
728
  schema:
728
729
  type: object
@@ -849,7 +850,7 @@ const getWorkflowYAML = (app, version) => {
849
850
  target: '{$self.input.data.parentWorkflowId}'
850
851
 
851
852
  a1ww:
852
- type: activity
853
+ type: hook
853
854
  title: Wait for custom signal
854
855
  emit: true
855
856
  hook:
@@ -864,7 +865,7 @@ const getWorkflowYAML = (app, version) => {
864
865
  signalId: '{t1ww.output.data.signalId}'
865
866
 
866
867
  a2ww:
867
- type: activity
868
+ type: hook
868
869
  title: Wait for cleanup signal
869
870
  hook:
870
871
  type: object
@@ -0,0 +1,15 @@
1
+ import { HotMeshService as HotMesh } from '../hotmesh';
2
+ import { RedisClient, RedisMulti } from '../../types/redis';
3
+ import { StoreService } from '../store';
4
+ export declare class Search {
5
+ jobId: string;
6
+ hotMeshClient: HotMesh;
7
+ store: StoreService<RedisClient, RedisMulti> | null;
8
+ safeKey(key: string): string;
9
+ constructor(workflowId: string, hotMeshClient: HotMesh);
10
+ set(key: string, value: string): Promise<void>;
11
+ get(key: string): Promise<string>;
12
+ del(key: string): Promise<void>;
13
+ incr(key: string, val: number): Promise<number>;
14
+ mult(key: string, val: number): Promise<number>;
15
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Search = void 0;
4
+ const key_1 = require("../../modules/key");
5
+ class Search {
6
+ safeKey(key) {
7
+ //note: protect the execution namespace with a prefix,
8
+ //so its design never conflicts with the hotmesh keyspace
9
+ return `_${key}`;
10
+ }
11
+ constructor(workflowId, hotMeshClient) {
12
+ const keyParams = {
13
+ appId: hotMeshClient.appId,
14
+ jobId: ''
15
+ };
16
+ const hotMeshPrefix = key_1.KeyService.mintKey(hotMeshClient.namespace, key_1.KeyType.JOB_STATE, keyParams);
17
+ this.jobId = `${hotMeshPrefix}${workflowId}`;
18
+ this.hotMeshClient = hotMeshClient;
19
+ this.store = hotMeshClient.engine.store;
20
+ }
21
+ async set(key, value) {
22
+ await this.store.exec('HSET', this.jobId, this.safeKey(key), value.toString());
23
+ }
24
+ async get(key) {
25
+ try {
26
+ return await this.store.exec('HGET', this.jobId, this.safeKey(key));
27
+ }
28
+ catch (err) {
29
+ this.hotMeshClient.logger.error('durable-search-get-error', { err });
30
+ return '';
31
+ }
32
+ }
33
+ async del(key) {
34
+ await this.store.exec('HDEL', this.jobId, this.safeKey(key));
35
+ }
36
+ async incr(key, val) {
37
+ return Number(await this.store.exec('HINCRBYFLOAT', this.jobId, this.safeKey(key), val.toString()));
38
+ }
39
+ async mult(key, val) {
40
+ const log = Math.log(val);
41
+ const logTotal = Number(await this.store.exec('HINCRBYFLOAT', this.jobId, this.safeKey(key), log.toString()));
42
+ return Math.exp(logTotal);
43
+ }
44
+ }
45
+ exports.Search = Search;
@@ -1,5 +1,5 @@
1
1
  import { HotMeshService as HotMesh } from '../hotmesh';
2
- import { Connection, Registry, WorkerConfig, WorkerOptions } from "../../types/durable";
2
+ import { Connection, Registry, WorkerConfig, WorkerOptions, WorkflowSearchOptions } from "../../types/durable";
3
3
  export declare class WorkerService {
4
4
  static activityRegistry: Registry;
5
5
  static connection: Connection;
@@ -14,6 +14,11 @@ export declare class WorkerService {
14
14
  * allowing proxyActivities to succeed.
15
15
  */
16
16
  static registerActivities<ACT>(activities: ACT): Registry;
17
+ /**
18
+ * For those deployments with a redis stack backend (with the FT module),
19
+ * this method will configure the search index for the workflow.
20
+ */
21
+ static configureSearchIndex(hotMeshClient: HotMesh, search?: WorkflowSearchOptions): Promise<void>;
17
22
  static create(config: WorkerConfig): Promise<WorkerService>;
18
23
  static resolveWorkflowTarget(workflow: object | Function): [string, Function];
19
24
  run(): Promise<void>;
@@ -7,36 +7,7 @@ const asyncLocalStorage_1 = require("./asyncLocalStorage");
7
7
  const factory_1 = require("./factory");
8
8
  const hotmesh_1 = require("../hotmesh");
9
9
  const stream_1 = require("../../types/stream");
10
- /*
11
- Here is an example of how the methods in this file are used:
12
-
13
- ./worker.ts
14
-
15
- import { Durable } from '@hotmeshio/hotmesh';
16
- import Redis from 'ioredis'; //OR `import * as Redis from 'redis';`
17
-
18
- import * as workflows from './workflows';
19
-
20
- async function run() {
21
- const worker = await Durable.Worker.create({
22
- connection: {
23
- class: Redis,
24
- options: {
25
- host: 'localhost',
26
- port: 6379,
27
- },
28
- },
29
- taskQueue: 'hello-world',
30
- workflow: workflows.example,
31
- });
32
- await worker.run();
33
- }
34
-
35
- run().catch((err) => {
36
- console.error(err);
37
- process.exit(1);
38
- });
39
- */
10
+ const key_1 = require("../../modules/key");
40
11
  class WorkerService {
41
12
  static async activateWorkflow(hotMesh) {
42
13
  const app = await hotMesh.engine.store.getApp(factory_1.APP_ID);
@@ -79,6 +50,38 @@ class WorkerService {
79
50
  }
80
51
  return WorkerService.activityRegistry;
81
52
  }
53
+ /**
54
+ * For those deployments with a redis stack backend (with the FT module),
55
+ * this method will configure the search index for the workflow.
56
+ */
57
+ //todo: bind this to the Search service; update constructor to expect hotMeshClient as first param (id is optional
58
+ //refactor and delete other one as well)
59
+ static async configureSearchIndex(hotMeshClient, search) {
60
+ if (search?.schema) {
61
+ const store = hotMeshClient.engine.store;
62
+ const schema = [];
63
+ for (const [key, value] of Object.entries(search.schema)) {
64
+ //prefix with a comma (avoids collisions with hotmesh reserved words)
65
+ schema.push(`_${key}`);
66
+ schema.push(value.type);
67
+ if (value.sortable) {
68
+ schema.push('SORTABLE');
69
+ }
70
+ }
71
+ try {
72
+ const keyParams = {
73
+ appId: hotMeshClient.appId,
74
+ jobId: ''
75
+ };
76
+ const hotMeshPrefix = key_1.KeyService.mintKey(hotMeshClient.namespace, key_1.KeyType.JOB_STATE, keyParams);
77
+ const prefixes = search.prefix.map((prefix) => `${hotMeshPrefix}${prefix}`);
78
+ await store.exec('FT.CREATE', `${search.index}`, 'ON', 'HASH', 'PREFIX', prefixes.length, ...prefixes, 'SCHEMA', ...schema);
79
+ }
80
+ catch (err) {
81
+ hotMeshClient.engine.logger.info('durable-client-search-err', { err });
82
+ }
83
+ }
84
+ }
82
85
  static async create(config) {
83
86
  //always call `registerActivities` before `import`
84
87
  WorkerService.connection = config.connection;
@@ -91,6 +94,7 @@ class WorkerService {
91
94
  const worker = new WorkerService();
92
95
  worker.activityRunner = await worker.initActivityWorker(config, activityTopic);
93
96
  worker.workflowRunner = await worker.initWorkflowWorker(config, workflowTopic, workflowFunction);
97
+ WorkerService.configureSearchIndex(worker.workflowRunner, config.search);
94
98
  await WorkerService.activateWorkflow(worker.workflowRunner);
95
99
  return worker;
96
100
  }
@@ -1,10 +1,12 @@
1
1
  import { ActivityConfig, ProxyType, WorkflowOptions } from "../../types/durable";
2
+ import { Search } from './search';
2
3
  export declare class WorkflowService {
3
4
  /**
4
5
  * Spawn a child workflow. await the result.
5
6
  */
6
7
  static executeChild<T>(options: WorkflowOptions): Promise<T>;
7
8
  static proxyActivities<ACT>(options?: ActivityConfig): ProxyType<ACT>;
9
+ static search(): Promise<Search>;
8
10
  static sleep(duration: string): Promise<number>;
9
11
  static waitForSignal(signals: string[], options?: Record<string, string>): Promise<Record<any, any>[]>;
10
12
  static wrapActivity<T>(activityName: string, options?: ActivityConfig): T;
@@ -11,34 +11,7 @@ const client_1 = require("./client");
11
11
  const connection_1 = require("./connection");
12
12
  const factory_1 = require("./factory");
13
13
  const errors_1 = require("../../modules/errors");
14
- /*
15
- `proxyActivities` returns a wrapped instance of the
16
- target activity, so that when the workflow calls a
17
- proxied activity, it is actually calling the proxy
18
- function, which in turn calls the activity function.
19
-
20
- Here is an example of how the methods in this file are used:
21
-
22
- ./workflows.ts
23
-
24
- import { Durable } from '@hotmeshio/hotmesh';
25
- import * as activities from './activities';
26
-
27
- const { greet } = Durable.workflow.proxyActivities<typeof activities>({
28
- activities: activities,
29
- startToCloseTimeout: '1 minute',
30
- retryPolicy: {
31
- initialInterval: '5 seconds', // Initial delay between retries
32
- maximumAttempts: 3, // Max number of retry attempts
33
- backoffCoefficient: 2.0, // Backoff factor for delay between retries: delay = initialInterval * (backoffCoefficient ^ retry_attempt)
34
- maximumInterval: '30 seconds', // Max delay between retries
35
- },
36
- });
37
-
38
- export async function example(name: string): Promise<string> {
39
- return await greet(name);
40
- }
41
- */
14
+ const search_1 = require("./search");
42
15
  class WorkflowService {
43
16
  /**
44
17
  * Spawn a child workflow. await the result.
@@ -88,6 +61,16 @@ class WorkflowService {
88
61
  }
89
62
  return proxy;
90
63
  }
64
+ static async search() {
65
+ const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
66
+ if (!store) {
67
+ throw new Error('durable-store-not-found');
68
+ }
69
+ const workflowId = store.get('workflowId');
70
+ const workflowTopic = store.get('workflowTopic');
71
+ const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic);
72
+ return new search_1.Search(workflowId, hotMeshClient);
73
+ }
91
74
  static async sleep(duration) {
92
75
  const seconds = (0, ms_1.default)(duration) / 1000;
93
76
  const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
@@ -1,4 +1,9 @@
1
- import { Activity } from '../activities/activity';
1
+ import { Await } from '../activities/await';
2
+ import { Cycle } from '../activities/cycle';
3
+ import { Hook } from '../activities/hook';
4
+ import { Signal } from '../activities/signal';
5
+ import { Worker } from '../activities/worker';
6
+ import { Trigger } from '../activities/trigger';
2
7
  import { ILogger } from '../logger';
3
8
  import { StoreSignaler } from '../signaler/store';
4
9
  import { StreamSignaler } from '../signaler/stream';
@@ -44,7 +49,7 @@ declare class EngineService {
44
49
  processWebHooks(): Promise<void>;
45
50
  processTimeHooks(): Promise<void>;
46
51
  throttle(delayInMillis: number): Promise<void>;
47
- initActivity(topic: string, data?: JobData, context?: JobState): Promise<Activity>;
52
+ initActivity(topic: string, data?: JobData, context?: JobState): Promise<Await | Cycle | Hook | Signal | Trigger | Worker>;
48
53
  getSchema(topic: string): Promise<[activityId: string, schema: ActivityType]>;
49
54
  getSettings(): Promise<HotMeshSettings>;
50
55
  isPrivate(topic: string): boolean;
@@ -143,7 +143,8 @@ class EngineService {
143
143
  // ************* METADATA/MODEL METHODS *************
144
144
  async initActivity(topic, data = {}, context) {
145
145
  const [activityId, schema] = await this.getSchema(topic);
146
- const ActivityHandler = activities_1.default[schema.type];
146
+ utils_1.polyfill;
147
+ const ActivityHandler = activities_1.default[utils_1.polyfill.resolveActivityType(schema.type)];
147
148
  if (ActivityHandler) {
148
149
  const utc = (0, utils_1.formatISODate)(new Date());
149
150
  const metadata = {
@@ -14,6 +14,7 @@ declare class IORedisStoreService extends StoreService<RedisClientType, RedisMul
14
14
  serializer: Serializer;
15
15
  constructor(redisClient: RedisClientType);
16
16
  getMulti(): RedisMultiType;
17
+ exec(...args: any[]): Promise<string | string[] | string[][]>;
17
18
  hGetAllResult(result: any): any;
18
19
  addTaskQueues(keys: string[]): Promise<void>;
19
20
  publish(keyType: KeyType.QUORUM, message: Record<string, any>, appId: string, engineId?: string): Promise<boolean>;
@@ -10,6 +10,18 @@ class IORedisStoreService extends index_1.StoreService {
10
10
  getMulti() {
11
11
  return this.redisClient.multi();
12
12
  }
13
+ async exec(...args) {
14
+ const response = await this.redisClient.call.apply(this.redisClient, args);
15
+ if (typeof response === 'string') {
16
+ return response;
17
+ }
18
+ else if (Array.isArray(response)) {
19
+ if (Array.isArray(response[0])) {
20
+ return response;
21
+ }
22
+ return response;
23
+ }
24
+ }
13
25
  hGetAllResult(result) {
14
26
  //ioredis response signature is [null, {}] or [null, null]
15
27
  return result[1];
@@ -15,6 +15,7 @@ declare class RedisStoreService extends StoreService<RedisClientType, RedisMulti
15
15
  commands: Record<string, string>;
16
16
  constructor(redisClient: RedisClientType);
17
17
  getMulti(): RedisMultiType;
18
+ exec(...args: any[]): Promise<string | string[] | string[][]>;
18
19
  publish(keyType: KeyType.QUORUM, message: Record<string, any>, appId: string, engineId?: string): Promise<boolean>;
19
20
  zAdd(key: string, score: number | string, value: string | number, redisMulti?: RedisMultiType): Promise<any>;
20
21
  zRangeByScoreWithScores(key: string, score: number | string, value: string | number): Promise<string | null>;
@@ -36,6 +36,9 @@ class RedisStoreService extends index_1.StoreService {
36
36
  const multi = this.redisClient.MULTI();
37
37
  return multi;
38
38
  }
39
+ async exec(...args) {
40
+ return await this.redisClient.sendCommand(args);
41
+ }
39
42
  async publish(keyType, message, appId, engineId) {
40
43
  const topic = this.mintKey(keyType, { appId, engineId });
41
44
  const status = await this.redisClient.publish(topic, JSON.stringify(message));
@@ -22,6 +22,7 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
22
22
  logger: ILogger;
23
23
  commands: Record<string, string>;
24
24
  abstract getMulti(): U;
25
+ abstract exec(...args: any[]): Promise<string | string[] | string[][]>;
25
26
  abstract publish(keyType: KeyType.QUORUM, message: Record<string, any>, appId: string, engineId?: string): Promise<boolean>;
26
27
  abstract xgroup(command: 'CREATE', key: string, groupName: string, id: string, mkStream?: 'MKSTREAM'): Promise<boolean>;
27
28
  abstract xadd(key: string, id: string, messageId: string, messageValue: string, multi?: U): Promise<string | U>;
@@ -8,6 +8,7 @@ const package_json_1 = __importDefault(require("../../package.json"));
8
8
  const mapper_1 = require("../mapper");
9
9
  const stream_1 = require("../../types/stream");
10
10
  const telemetry_1 = require("../../types/telemetry");
11
+ const utils_1 = require("../../modules/utils");
11
12
  class TelemetryService {
12
13
  constructor(appId, config, metadata, context) {
13
14
  this.leg = 1;
@@ -209,7 +210,7 @@ class TelemetryService {
209
210
  state[`${metadata.aid}/output/metadata/l1s`] = context['$self'].output.metadata.l1s;
210
211
  state[`${metadata.aid}/output/metadata/l2s`] = context['$self'].output.metadata.l2s;
211
212
  }
212
- else if (config.type === 'activity' && leg === 1) {
213
+ else if (utils_1.polyfill.resolveActivityType(config.type) === 'hook' && leg === 1) {
213
214
  //activities run non-duplexed and only have a single leg
214
215
  state[`${metadata.aid}/output/metadata/l1s`] = context['$self'].output.metadata.l1s;
215
216
  state[`${metadata.aid}/output/metadata/l2s`] = context['$self'].output.metadata.l1s;
@@ -1,6 +1,6 @@
1
1
  import { MetricTypes } from "./stats";
2
2
  import { StreamRetryPolicy } from "./stream";
3
- type ActivityExecutionType = 'trigger' | 'await' | 'worker' | 'activity' | 'emit' | 'iterate' | 'cycle' | 'signal';
3
+ type ActivityExecutionType = 'trigger' | 'await' | 'worker' | 'activity' | 'emit' | 'iterate' | 'cycle' | 'signal' | 'hook';
4
4
  type Consumes = Record<string, string[]>;
5
5
  interface BaseActivity {
6
6
  title?: string;
@@ -59,6 +59,9 @@ interface CycleActivity extends BaseActivity {
59
59
  type: 'cycle';
60
60
  ancestor: string;
61
61
  }
62
+ interface HookActivity extends BaseActivity {
63
+ type: 'hook';
64
+ }
62
65
  interface SignalActivity extends BaseActivity {
63
66
  type: 'signal';
64
67
  subtype: 'one' | 'all';
@@ -72,7 +75,7 @@ interface SignalActivity extends BaseActivity {
72
75
  interface IterateActivity extends BaseActivity {
73
76
  type: 'iterate';
74
77
  }
75
- type ActivityType = BaseActivity | TriggerActivity | AwaitActivity | WorkerActivity | IterateActivity;
78
+ type ActivityType = BaseActivity | TriggerActivity | AwaitActivity | WorkerActivity | IterateActivity | HookActivity;
76
79
  type ActivityData = Record<string, any>;
77
80
  type ActivityMetadata = {
78
81
  aid: string;
@@ -98,4 +101,4 @@ type ActivityDataType = {
98
101
  hook?: Record<string, unknown>;
99
102
  };
100
103
  type ActivityLeg = 1 | 2;
101
- export { ActivityContext, ActivityData, ActivityDataType, ActivityDuplex, ActivityLeg, ActivityMetadata, ActivityType, Consumes, TriggerActivityStats, AwaitActivity, CycleActivity, SignalActivity, BaseActivity, IterateActivity, TriggerActivity, WorkerActivity };
104
+ export { ActivityContext, ActivityData, ActivityDataType, ActivityDuplex, ActivityLeg, ActivityMetadata, ActivityType, Consumes, TriggerActivityStats, AwaitActivity, CycleActivity, HookActivity, SignalActivity, BaseActivity, IterateActivity, TriggerActivity, WorkerActivity };
@@ -5,6 +5,15 @@ type WorkflowConfig = {
5
5
  maximumInterval?: string;
6
6
  initialInterval?: string;
7
7
  };
8
+ type WorkflowSearchOptions = {
9
+ index?: string;
10
+ prefix?: string[];
11
+ schema?: Record<string, {
12
+ type: 'TEXT' | 'NUMERIC' | 'TAG';
13
+ sortable: boolean;
14
+ }>;
15
+ data?: Record<string, string>;
16
+ };
8
17
  type WorkflowOptions = {
9
18
  taskQueue: string;
10
19
  args: any[];
@@ -13,6 +22,7 @@ type WorkflowOptions = {
13
22
  parentWorkflowId?: string;
14
23
  workflowTrace?: string;
15
24
  workflowSpan?: string;
25
+ search?: WorkflowSearchOptions;
16
26
  config?: WorkflowConfig;
17
27
  };
18
28
  type SignalOptions = {
@@ -50,6 +60,7 @@ type WorkerConfig = {
50
60
  taskQueue: string;
51
61
  workflow: Function;
52
62
  options?: WorkerOptions;
63
+ search?: WorkflowSearchOptions;
53
64
  };
54
65
  type WorkerOptions = {
55
66
  maxSystemRetries?: number;
@@ -73,4 +84,4 @@ type ActivityConfig = {
73
84
  maximumInterval: string;
74
85
  };
75
86
  };
76
- export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, NativeConnection, ProxyType, Registry, SignalOptions, WorkerConfig, WorkflowConfig, WorkerOptions, WorkflowDataType, WorkflowOptions, };
87
+ export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, NativeConnection, ProxyType, Registry, SignalOptions, WorkerConfig, WorkflowConfig, WorkerOptions, WorkflowSearchOptions, WorkflowDataType, WorkflowOptions, };
@@ -12,6 +12,7 @@ interface HookConditions {
12
12
  }
13
13
  interface HookRule {
14
14
  to: string;
15
+ keep_alive?: boolean;
15
16
  conditions: HookConditions;
16
17
  }
17
18
  interface HookRules {
@@ -1,9 +1,9 @@
1
- export { ActivityType, ActivityDataType, ActivityContext, ActivityData, ActivityDuplex, ActivityLeg, ActivityMetadata, Consumes, AwaitActivity, BaseActivity, CycleActivity, WorkerActivity, IterateActivity, SignalActivity, TriggerActivity, TriggerActivityStats } from './activity';
1
+ export { ActivityType, ActivityDataType, ActivityContext, ActivityData, ActivityDuplex, ActivityLeg, ActivityMetadata, Consumes, AwaitActivity, BaseActivity, CycleActivity, HookActivity, WorkerActivity, IterateActivity, SignalActivity, TriggerActivity, TriggerActivityStats } from './activity';
2
2
  export { App, AppVID, AppTransitions, AppSubscriptions } from './app';
3
3
  export { AsyncSignal } from './async';
4
4
  export { CacheMode } from './cache';
5
5
  export { CollationFaultType, CollationStage } from './collator';
6
- export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, NativeConnection, ProxyType, Registry, WorkflowConfig, WorkerConfig, WorkerOptions, WorkflowDataType, WorkflowOptions, } from './durable';
6
+ export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, NativeConnection, ProxyType, Registry, WorkflowConfig, WorkerConfig, WorkerOptions, WorkflowSearchOptions, WorkflowDataType, WorkflowOptions, } from './durable';
7
7
  export { HookCondition, HookConditions, HookGate, HookInterface, HookRule, HookRules, HookSignal } from './hook';
8
8
  export { RedisClientType as IORedisClientType, RedisMultiType as IORedisMultiType } from './ioredisclient';
9
9
  export { ILogger } from './logger';