@hotmeshio/hotmesh 0.0.21 → 0.0.23

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 (42) hide show
  1. package/README.md +0 -3
  2. package/build/package.json +2 -1
  3. package/build/services/durable/connection.js +0 -39
  4. package/build/services/durable/factory.d.ts +6 -4
  5. package/build/services/durable/factory.js +6 -4
  6. package/build/services/durable/handle.d.ts +3 -0
  7. package/build/services/durable/handle.js +14 -2
  8. package/build/services/durable/index.d.ts +2 -45
  9. package/build/services/durable/index.js +2 -45
  10. package/build/services/durable/meshdb.d.ts +113 -0
  11. package/build/services/durable/meshdb.js +211 -0
  12. package/build/services/durable/search.d.ts +9 -0
  13. package/build/services/durable/search.js +35 -2
  14. package/build/services/durable/worker.d.ts +1 -9
  15. package/build/services/durable/worker.js +2 -35
  16. package/build/services/durable/workflow.d.ts +8 -1
  17. package/build/services/durable/workflow.js +15 -16
  18. package/build/services/engine/index.d.ts +2 -0
  19. package/build/services/engine/index.js +3 -0
  20. package/build/services/hotmesh/index.d.ts +3 -1
  21. package/build/services/hotmesh/index.js +5 -2
  22. package/build/services/store/index.d.ts +5 -0
  23. package/build/services/store/index.js +14 -0
  24. package/build/types/durable.d.ts +20 -4
  25. package/build/types/index.d.ts +1 -1
  26. package/package.json +2 -1
  27. package/services/durable/connection.ts +0 -40
  28. package/services/durable/factory.ts +6 -4
  29. package/services/durable/handle.ts +17 -2
  30. package/services/durable/index.ts +2 -46
  31. package/services/durable/meshdb.ts +254 -0
  32. package/services/durable/search.ts +36 -2
  33. package/services/durable/worker.ts +3 -37
  34. package/services/durable/workflow.ts +15 -15
  35. package/services/engine/index.ts +4 -1
  36. package/services/hotmesh/index.ts +6 -2
  37. package/services/store/index.ts +15 -0
  38. package/types/durable.ts +20 -4
  39. package/types/index.ts +2 -1
  40. package/build/services/durable/native.d.ts +0 -4
  41. package/build/services/durable/native.js +0 -46
  42. package/services/durable/native.ts +0 -45
package/README.md CHANGED
@@ -226,9 +226,6 @@ Exchanging data between activities is central to HotMesh. For detailed informati
226
226
  ## Composition
227
227
  While the simplest graphs are linear, detailing a consistent sequence of non-cyclical activities, graphs can be layered to represent intricate business scenarios. Some can even be designed to accommodate long-lasting workflows that span months. For more details, check out the [Composable Workflow Guide](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/composable_workflow.md).
228
228
 
229
- ## Architectural First Principles
230
- For a deep dive into HotMesh's distributed orchestration philosophy, refer to the [Architectural First Principles Overview](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/architecture.md).
231
-
232
229
  ## Distributed Orchestration
233
230
  HotMesh is a distributed orchestration engine. Refer to the [Distributed Orchestration Guide](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/distributed_orchestration.md) for a detailed breakdown of the approach.
234
231
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.21",
3
+ "version": "0.0.23",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -43,6 +43,7 @@
43
43
  "test:sub:redis": "NODE_ENV=test jest ./tests/functional/sub/clients/redis.test.ts --detectOpenHandles --forceExit --verbose",
44
44
  "test:sub:ioredis": "NODE_ENV=test jest ./tests/functional/sub/clients/ioredis.test.ts --detectOpenHandles --forceExit --verbose",
45
45
  "test:durable": "NODE_ENV=test jest ./tests/durable/*/index.test.ts --detectOpenHandles --forceExit --verbose",
46
+ "test:durable:meshdb": "NODE_ENV=test jest ./tests/durable/meshdb/index.test.ts --detectOpenHandles --forceExit --verbose",
46
47
  "test:durable:hello": "NODE_ENV=test jest ./tests/durable/helloworld/index.test.ts --detectOpenHandles --forceExit --verbose",
47
48
  "test:durable:goodbye": "NODE_ENV=test jest ./tests/durable/goodbye/index.test.ts --detectOpenHandles --forceExit --verbose",
48
49
  "test:durable:hook": "NODE_ENV=test jest ./tests/durable/hook/index.test.ts --detectOpenHandles --forceExit --verbose",
@@ -1,45 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConnectionService = void 0;
4
- /*
5
- Here is an example of how the methods in this file are used:
6
-
7
- ./client.ts
8
-
9
- import { Durable } from '@hotmeshio/hotmesh';
10
- import Redis from 'ioredis';
11
- import { nanoid } from 'nanoid';
12
-
13
- async function run() {
14
- const connection = await Durable.Connection.connect({
15
- class: Redis,
16
- options: {
17
- host: 'localhost',
18
- port: 6379,
19
- },
20
- });
21
-
22
- const client = new Durable.Client({
23
- connection,
24
- });
25
-
26
- const handle = await client.workflow.start(example, {
27
- taskQueue: 'hello-world',
28
- args: ['HotMesh'],
29
- workflowName: 'example',
30
- workflowId: nanoid(),
31
- });
32
-
33
- console.log(`Started workflow ${handle.workflowId}`);
34
- console.log(await handle.result());
35
- }
36
-
37
- run().catch((err) => {
38
- console.error(err);
39
- process.exit(1);
40
- });
41
-
42
- */
43
4
  class ConnectionService {
44
5
  static async connect(config) {
45
6
  return {
@@ -2,11 +2,13 @@
2
2
  * NOTE: Using `maxSystemRetries = 3` and `backoffCoefficient = 10`, errant
3
3
  * workflows will be retried on the following schedule (8 times in 27 hours):
4
4
  * => 10ms, 100ms, 1000ms, 10s, 100s, 1_000s, 10_000s, 100_000s
5
+ * TODO: Max Interval, Min Interval, Initial Interval
5
6
  *
6
- * 594: waitforsignal
7
- * 595: sleep
8
- * 596, 597, 598: fatal
9
- * 599: retry
7
+ * ERROR CODES:
8
+ * 594: waitforsignal
9
+ * 595: sleep
10
+ * 596, 597, 598: fatal
11
+ * 599: retry
10
12
  */
11
13
  declare const getWorkflowYAML: (app: string, version: string) => string;
12
14
  declare const APP_VERSION = "1";
@@ -5,11 +5,13 @@ exports.DEFAULT_COEFFICIENT = exports.APP_ID = exports.APP_VERSION = exports.get
5
5
  * NOTE: Using `maxSystemRetries = 3` and `backoffCoefficient = 10`, errant
6
6
  * workflows will be retried on the following schedule (8 times in 27 hours):
7
7
  * => 10ms, 100ms, 1000ms, 10s, 100s, 1_000s, 10_000s, 100_000s
8
+ * TODO: Max Interval, Min Interval, Initial Interval
8
9
  *
9
- * 594: waitforsignal
10
- * 595: sleep
11
- * 596, 597, 598: fatal
12
- * 599: retry
10
+ * ERROR CODES:
11
+ * 594: waitforsignal
12
+ * 595: sleep
13
+ * 596, 597, 598: fatal
14
+ * 599: retry
13
15
  */
14
16
  const getWorkflowYAML = (app, version) => {
15
17
  return `app:
@@ -5,5 +5,8 @@ export declare class WorkflowHandleService {
5
5
  workflowId: string;
6
6
  constructor(hotMesh: HotMesh, workflowTopic: string, workflowId: string);
7
7
  signal(signalId: string, data: Record<any, any>): Promise<void>;
8
+ state(metadata?: boolean): Promise<Record<string, any>>;
9
+ queryState(fields: string[]): Promise<Record<string, any>>;
10
+ status(): Promise<number>;
8
11
  result(loadState?: boolean): Promise<any>;
9
12
  }
@@ -10,6 +10,19 @@ class WorkflowHandleService {
10
10
  async signal(signalId, data) {
11
11
  await this.hotMesh.hook(`${this.hotMesh.appId}.wfs.signal`, { id: signalId, data });
12
12
  }
13
+ async state(metadata = false) {
14
+ const state = await this.hotMesh.getState(`${this.hotMesh.appId}.execute`, this.workflowId);
15
+ if (!state.data && state.metadata.err) {
16
+ throw new Error(JSON.parse(state.metadata.err));
17
+ }
18
+ return metadata ? state : state.data;
19
+ }
20
+ async queryState(fields) {
21
+ return await this.hotMesh.getQueryState(this.workflowId, fields);
22
+ }
23
+ async status() {
24
+ return await this.hotMesh.getStatus(this.workflowId);
25
+ }
13
26
  async result(loadState) {
14
27
  if (loadState) {
15
28
  const state = await this.hotMesh.getState(`${this.hotMesh.appId}.execute`, this.workflowId);
@@ -20,8 +33,7 @@ class WorkflowHandleService {
20
33
  //child flows are never technically 'done' as they have an open hook
21
34
  //that is tied to the parent flow's completion. so, we need to check
22
35
  //the 'done' flag on the child flow's payload (not the 'js' metadata field
23
- //which is typically used); the loadState parameter ensures this
24
- //check happens early
36
+ //which is typically used); the `loadState` parameter triggers this
25
37
  return state.data.response;
26
38
  }
27
39
  }
@@ -1,56 +1,13 @@
1
1
  import { ClientService } from './client';
2
2
  import { ConnectionService } from './connection';
3
- import { NativeConnectionService } from './native';
3
+ import { MeshDBService } from './meshdb';
4
4
  import { WorkerService } from './worker';
5
5
  import { WorkflowService } from './workflow';
6
6
  import { ContextType } from '../../types/durable';
7
- /**
8
- * As a durable integration platform, HotMesh
9
- * can model and emulate other durable systems
10
- * (like Temporal). As you review the code in
11
- * this file, note the following:
12
- *
13
- * 1) There is no central governing server.
14
- * HotMesh is a client-side SDK that connects to Redis
15
- * using CQRS principles to implicitly drive
16
- * orchestrations using a headless quorum. Stream
17
- * semantics guarantee that all events are
18
- * processed by the quorum of connected clients.
19
- *
20
- * 2) Every developer-defined `workflow` function
21
- * is assigned a HotMesh workflow (which runs in
22
- * the background) to support it.
23
- *
24
- * If the HotMesh workflow is not yet defined,
25
- * it will be deployed and activated on-the-fly.
26
- * (The generated DAG will have one Trigger Activity
27
- * and one Worker Activity.) The Worker Activity
28
- * is configured to catch execution errors and
29
- * return them, using a 'pending' status to
30
- * indicate that the workflow is still running.
31
- * It is possible for workflow activities to throw
32
- * errors that will force the entire workflow to
33
- * fail. This is not the case here. The workflow will
34
- * continue to run until it is completed or cancelled.
35
- *
36
- * 2) Every developer-defined `activity` function
37
- * is assigned a HotMesh workflow (which runs in
38
- * the background) to support it.
39
- *
40
- * (The generated DAG will have one Trigger Activity and one
41
- * Worker Activity.) The JOB ID for Worker Activity executions
42
- * is derived from the containing JOB ID,
43
- * allowing Activity state to be 'replayed' when the workflow
44
- * is run again. The Activity Function Runner (activity proxy)
45
- * is configured similar to how the workflow worker is and will
46
- * catch execution errors and return them to the caller, using a
47
- * 'pending' status to indicate that the activity is still running.
48
- * This allows the activity to be retried until it succeeds.
49
- */
50
7
  export declare const Durable: {
51
8
  Client: typeof ClientService;
52
9
  Connection: typeof ConnectionService;
53
- NativeConnection: typeof NativeConnectionService;
10
+ MeshDB: typeof MeshDBService;
54
11
  Worker: typeof WorkerService;
55
12
  workflow: typeof WorkflowService;
56
13
  };
@@ -3,56 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Durable = void 0;
4
4
  const client_1 = require("./client");
5
5
  const connection_1 = require("./connection");
6
- const native_1 = require("./native");
6
+ const meshdb_1 = require("./meshdb");
7
7
  const worker_1 = require("./worker");
8
8
  const workflow_1 = require("./workflow");
9
- /**
10
- * As a durable integration platform, HotMesh
11
- * can model and emulate other durable systems
12
- * (like Temporal). As you review the code in
13
- * this file, note the following:
14
- *
15
- * 1) There is no central governing server.
16
- * HotMesh is a client-side SDK that connects to Redis
17
- * using CQRS principles to implicitly drive
18
- * orchestrations using a headless quorum. Stream
19
- * semantics guarantee that all events are
20
- * processed by the quorum of connected clients.
21
- *
22
- * 2) Every developer-defined `workflow` function
23
- * is assigned a HotMesh workflow (which runs in
24
- * the background) to support it.
25
- *
26
- * If the HotMesh workflow is not yet defined,
27
- * it will be deployed and activated on-the-fly.
28
- * (The generated DAG will have one Trigger Activity
29
- * and one Worker Activity.) The Worker Activity
30
- * is configured to catch execution errors and
31
- * return them, using a 'pending' status to
32
- * indicate that the workflow is still running.
33
- * It is possible for workflow activities to throw
34
- * errors that will force the entire workflow to
35
- * fail. This is not the case here. The workflow will
36
- * continue to run until it is completed or cancelled.
37
- *
38
- * 2) Every developer-defined `activity` function
39
- * is assigned a HotMesh workflow (which runs in
40
- * the background) to support it.
41
- *
42
- * (The generated DAG will have one Trigger Activity and one
43
- * Worker Activity.) The JOB ID for Worker Activity executions
44
- * is derived from the containing JOB ID,
45
- * allowing Activity state to be 'replayed' when the workflow
46
- * is run again. The Activity Function Runner (activity proxy)
47
- * is configured similar to how the workflow worker is and will
48
- * catch execution errors and return them to the caller, using a
49
- * 'pending' status to indicate that the activity is still running.
50
- * This allows the activity to be retried until it succeeds.
51
- */
52
9
  exports.Durable = {
53
10
  Client: client_1.ClientService,
54
11
  Connection: connection_1.ConnectionService,
55
- NativeConnection: native_1.NativeConnectionService,
12
+ MeshDB: meshdb_1.MeshDBService,
56
13
  Worker: worker_1.WorkerService,
57
14
  workflow: workflow_1.WorkflowService,
58
15
  };
@@ -0,0 +1,113 @@
1
+ import { WorkflowHandleService } from './handle';
2
+ import { WorkflowSearchOptions } from '../../types/durable';
3
+ import { RedisOptions, RedisClass } from '../../types/redis';
4
+ /**
5
+ * A base class for configuration and setup of
6
+ * a reentrant process database. Entities modeled as
7
+ * subclasses of this class will execute as reentrant
8
+ * processes with a 'main' execution thread and 'n'
9
+ * parallel hook threads.
10
+ *
11
+ * @example
12
+ * //RUN (start a workflow)
13
+ * const myInstance = new MeshDB('someIdempotentGuid');
14
+ * const handle = await myInstance.create(100);
15
+ * await handle.result(); //100
16
+ *
17
+ * //UPDATE (update a workflow)
18
+ * const result = await myInstance.decrement(11);
19
+ */
20
+ export declare class MeshDBService {
21
+ /**
22
+ * The name of the main method. When this method
23
+ * is invoked/proxied, it is assumed that a new
24
+ * workflow instance is being created. In all other
25
+ * cases, the call is assumed to be a hook/update
26
+ */
27
+ main: string;
28
+ /**
29
+ * The GUID for the workflow (assigned when created). This
30
+ * value should be idempotent and will be rejected if an
31
+ * instance is already running with the same id.
32
+ */
33
+ id: string;
34
+ /**
35
+ * test value
36
+ */
37
+ value: number;
38
+ /**
39
+ * The top-level Redis isolation. All workflow data is
40
+ * isolated within this namespace. Values should be
41
+ * lower-case with no spaces (e.g, 'staging', 'prod', 'test',
42
+ * 'routing-stagig', 'reporting-prod', etc.).
43
+ * 1) only url-safe values are allowed;
44
+ * 2) the 'a' symbol is reserved by HotMesh for indexing apps
45
+ */
46
+ namespace: string;
47
+ /**
48
+ * The second-level isolation. Data is routed to workers
49
+ * that specify this task queue. Setting the task queue
50
+ * when the worker is created will ensure that the worker
51
+ * only receives messages destined for the queue. This
52
+ * allows callers to specify specific workers/containers
53
+ * for specific tasks. Only url-safe values are allowed.
54
+ */
55
+ taskQueue: string;
56
+ /**
57
+ * The Redis connection options. NOTE: Redis and IORedis
58
+ * use different formats for their connection config.
59
+ */
60
+ redisOptions: RedisOptions;
61
+ /**
62
+ * The Redis connection class. Import as follows
63
+ * within the base subclass as follows:
64
+ *
65
+ * @example
66
+ * import Redis from 'ioredis';
67
+ * import * as Redis from 'redis';
68
+ */
69
+ redisClass: RedisClass | null;
70
+ /**
71
+ * Configuration for the the Redis FT search index.
72
+ */
73
+ search: WorkflowSearchOptions;
74
+ static getHotMeshClient(redisClass: RedisClass, redisOptions: RedisOptions, namespace: string, taskQueue: string): Promise<import("../hotmesh").HotMeshService>;
75
+ /**
76
+ * mints a new key, using the provided search prefix, ensuring
77
+ * new workflows are properly indexed
78
+ * @returns {string}
79
+ */
80
+ static mintGuid(): string;
81
+ /**
82
+ * Creates an FT search index
83
+ */
84
+ static createIndex(): Promise<void>;
85
+ /**
86
+ * Initialize the worker(s) for the entity. This is a static
87
+ * method that allows for optional task Queue targeting.
88
+ * NOTE: Allow List may be optionally used
89
+ * @param {string} taskQueue
90
+ * @param {string[]} allowList
91
+ */
92
+ static doWork(taskQueue?: string, allowList?: string[]): Promise<void>;
93
+ /**
94
+ * executes the redis FT search query
95
+ * @example '@_quantity:[89 89]'
96
+ * @param {any[]} args
97
+ * @returns {string}
98
+ */
99
+ static find(...args: string[]): Promise<string[] | [number]>;
100
+ /**
101
+ * returns the workflow handle (use the handle to call:
102
+ * `state`, `status`, `queryStatus`, and `result`)
103
+ * @param {string} id
104
+ * @returns {Promise<WorkflowHandleService>}
105
+ */
106
+ static get(id: string): Promise<WorkflowHandleService>;
107
+ /**
108
+ * Initialize with an idempotent workflow identifier.
109
+ * Optionally include a target taskQueue to send
110
+ * events to a specific worker.
111
+ */
112
+ constructor(id?: string, taskQueue?: string);
113
+ }
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MeshDBService = void 0;
4
+ const nanoid_1 = require("nanoid");
5
+ const client_1 = require("./client");
6
+ const search_1 = require("./search");
7
+ const worker_1 = require("./worker");
8
+ /**
9
+ * A base class for configuration and setup of
10
+ * a reentrant process database. Entities modeled as
11
+ * subclasses of this class will execute as reentrant
12
+ * processes with a 'main' execution thread and 'n'
13
+ * parallel hook threads.
14
+ *
15
+ * @example
16
+ * //RUN (start a workflow)
17
+ * const myInstance = new MeshDB('someIdempotentGuid');
18
+ * const handle = await myInstance.create(100);
19
+ * await handle.result(); //100
20
+ *
21
+ * //UPDATE (update a workflow)
22
+ * const result = await myInstance.decrement(11);
23
+ */
24
+ class MeshDBService {
25
+ static async getHotMeshClient(redisClass, redisOptions, namespace, taskQueue) {
26
+ const client = new client_1.ClientService({
27
+ connection: {
28
+ class: redisClass,
29
+ options: redisOptions,
30
+ }
31
+ });
32
+ return await client.getHotMeshClient(taskQueue, namespace);
33
+ }
34
+ /**
35
+ * mints a new key, using the provided search prefix, ensuring
36
+ * new workflows are properly indexed
37
+ * @returns {string}
38
+ */
39
+ static mintGuid() {
40
+ const my = new this();
41
+ return `${my.search?.prefix?.[0]}${(0, nanoid_1.nanoid)()}}`;
42
+ }
43
+ /**
44
+ * Creates an FT search index
45
+ */
46
+ static async createIndex() {
47
+ const my = new this();
48
+ const hmClient = await MeshDBService.getHotMeshClient(my.redisClass, my.redisOptions, my.namespace, my.taskQueue);
49
+ search_1.Search.configureSearchIndex(hmClient, my.search);
50
+ }
51
+ /**
52
+ * Initialize the worker(s) for the entity. This is a static
53
+ * method that allows for optional task Queue targeting.
54
+ * NOTE: Allow List may be optionally used
55
+ * @param {string} taskQueue
56
+ * @param {string[]} allowList
57
+ */
58
+ static async doWork(taskQueue, allowList) {
59
+ const my = new this();
60
+ let prototype = Object.getPrototypeOf(my);
61
+ const durablePromises = [];
62
+ const found = [];
63
+ while (prototype !== null && !Object.getOwnPropertyNames(prototype).includes('__proto__')) {
64
+ const promises = Object.getOwnPropertyNames(prototype).map((prop) => {
65
+ if (found.includes(prop) || ['constructor'].includes(prop) || (allowList && !allowList.includes(prop))) {
66
+ return;
67
+ }
68
+ const originalMethod = my[prop];
69
+ if (typeof originalMethod === 'function') {
70
+ found.push(prop);
71
+ return worker_1.WorkerService.create({
72
+ namespace: my.namespace,
73
+ connection: {
74
+ class: my.redisClass,
75
+ options: my.redisOptions,
76
+ },
77
+ taskQueue: taskQueue ?? my.taskQueue,
78
+ workflow: originalMethod,
79
+ });
80
+ }
81
+ }).filter(p => p !== undefined); // filter out undefined values
82
+ durablePromises.push(...promises);
83
+ prototype = Object.getPrototypeOf(prototype);
84
+ }
85
+ await Promise.all(durablePromises);
86
+ }
87
+ /**
88
+ * executes the redis FT search query
89
+ * @example '@_quantity:[89 89]'
90
+ * @param {any[]} args
91
+ * @returns {string}
92
+ */
93
+ static async find(...args) {
94
+ const my = new this();
95
+ const client = new client_1.ClientService({ connection: {
96
+ class: my.redisClass,
97
+ options: my.redisOptions
98
+ } });
99
+ return await client.workflow.search(my.taskQueue, my.main, my.namespace, my.search.index, ...args);
100
+ //[count, [id, fields[], id, fields[], id, fields[], ...]]
101
+ }
102
+ /**
103
+ * returns the workflow handle (use the handle to call:
104
+ * `state`, `status`, `queryStatus`, and `result`)
105
+ * @param {string} id
106
+ * @returns {Promise<WorkflowHandleService>}
107
+ */
108
+ static async get(id) {
109
+ const my = new this();
110
+ const client = new client_1.ClientService({ connection: {
111
+ class: my.redisClass,
112
+ options: my.redisOptions
113
+ } });
114
+ return await client.workflow.getHandle(my.taskQueue, my.main, id, my.namespace);
115
+ }
116
+ /**
117
+ * Initialize with an idempotent workflow identifier.
118
+ * Optionally include a target taskQueue to send
119
+ * events to a specific worker.
120
+ */
121
+ constructor(id, taskQueue) {
122
+ /**
123
+ * The name of the main method. When this method
124
+ * is invoked/proxied, it is assumed that a new
125
+ * workflow instance is being created. In all other
126
+ * cases, the call is assumed to be a hook/update
127
+ */
128
+ this.main = 'create';
129
+ /**
130
+ * The top-level Redis isolation. All workflow data is
131
+ * isolated within this namespace. Values should be
132
+ * lower-case with no spaces (e.g, 'staging', 'prod', 'test',
133
+ * 'routing-stagig', 'reporting-prod', etc.).
134
+ * 1) only url-safe values are allowed;
135
+ * 2) the 'a' symbol is reserved by HotMesh for indexing apps
136
+ */
137
+ this.namespace = 'durable';
138
+ /**
139
+ * The second-level isolation. Data is routed to workers
140
+ * that specify this task queue. Setting the task queue
141
+ * when the worker is created will ensure that the worker
142
+ * only receives messages destined for the queue. This
143
+ * allows callers to specify specific workers/containers
144
+ * for specific tasks. Only url-safe values are allowed.
145
+ */
146
+ this.taskQueue = 'default';
147
+ /**
148
+ * The Redis connection options. NOTE: Redis and IORedis
149
+ * use different formats for their connection config.
150
+ */
151
+ this.redisOptions = {
152
+ host: 'localhost',
153
+ port: 6379,
154
+ password: '',
155
+ db: 0,
156
+ };
157
+ /**
158
+ * The Redis connection class. Import as follows
159
+ * within the base subclass as follows:
160
+ *
161
+ * @example
162
+ * import Redis from 'ioredis';
163
+ * import * as Redis from 'redis';
164
+ */
165
+ this.redisClass = null;
166
+ this.id = id;
167
+ if (taskQueue) {
168
+ this.taskQueue = taskQueue;
169
+ }
170
+ else if (!id && !taskQueue) {
171
+ return this;
172
+ }
173
+ return new Proxy(this, {
174
+ get: (target, prop, receiver) => {
175
+ if (typeof target[prop] === 'function') {
176
+ return (...args) => {
177
+ return new Promise(async (resolve, reject) => {
178
+ const client = new client_1.ClientService({ connection: {
179
+ class: this.redisClass,
180
+ options: this.redisOptions
181
+ } });
182
+ if (prop === this.main) {
183
+ //start a new workflow (main method was called)
184
+ return client.workflow.start({
185
+ namespace: this.namespace,
186
+ args,
187
+ taskQueue: this.taskQueue,
188
+ workflowName: prop,
189
+ workflowId: this.id,
190
+ }).then(resolve).catch(reject);
191
+ }
192
+ else if (prop !== 'constructor') {
193
+ //update an existing workflow (hook/signal-in)
194
+ return client.workflow.hook({
195
+ namespace: this.namespace,
196
+ taskQueue: this.taskQueue,
197
+ workflowName: prop,
198
+ workflowId: this.id,
199
+ args,
200
+ }).then(resolve).catch(reject);
201
+ }
202
+ target[prop].apply(this, args).then(resolve).catch(reject);
203
+ });
204
+ };
205
+ }
206
+ return Reflect.get(target, prop, receiver);
207
+ },
208
+ });
209
+ }
210
+ }
211
+ exports.MeshDBService = MeshDBService;
@@ -1,6 +1,7 @@
1
1
  import { HotMeshService as HotMesh } from '../hotmesh';
2
2
  import { RedisClient, RedisMulti } from '../../types/redis';
3
3
  import { StoreService } from '../store';
4
+ import { WorkflowSearchOptions } from '../../types/durable';
4
5
  export declare class Search {
5
6
  jobId: string;
6
7
  searchSessionId: string;
@@ -8,6 +9,14 @@ export declare class Search {
8
9
  hotMeshClient: HotMesh;
9
10
  store: StoreService<RedisClient, RedisMulti> | null;
10
11
  safeKey(key: string): string;
12
+ /**
13
+ * For those deployments with a redis stack backend (with the FT module),
14
+ * this method will configure the search index for the workflow. For all
15
+ * others, this method will exit/fail gracefully and not index
16
+ * the fields in the HASH. However, all values are still available
17
+ * in the HASH.
18
+ */
19
+ static configureSearchIndex(hotMeshClient: HotMesh, search?: WorkflowSearchOptions): Promise<void>;
11
20
  constructor(workflowId: string, hotMeshClient: HotMesh, searchSessionId: string);
12
21
  /**
13
22
  * increments the index to return a unique search session guid when
@@ -4,10 +4,43 @@ exports.Search = void 0;
4
4
  const key_1 = require("../../modules/key");
5
5
  class Search {
6
6
  safeKey(key) {
7
- //note: protect the execution namespace with a prefix,
8
- //so its design never conflicts with the hotmesh keyspace
7
+ //note: protect the execution namespace with a prefix
9
8
  return `_${key}`;
10
9
  }
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. For all
13
+ * others, this method will exit/fail gracefully and not index
14
+ * the fields in the HASH. However, all values are still available
15
+ * in the HASH.
16
+ */
17
+ static async configureSearchIndex(hotMeshClient, search) {
18
+ if (search?.schema) {
19
+ const store = hotMeshClient.engine.store;
20
+ const schema = [];
21
+ for (const [key, value] of Object.entries(search.schema)) {
22
+ //prefix with a comma (avoids collisions with hotmesh reserved words)
23
+ schema.push(`_${key}`);
24
+ schema.push(value.type);
25
+ if (value.sortable) {
26
+ schema.push('SORTABLE');
27
+ }
28
+ }
29
+ try {
30
+ const keyParams = {
31
+ appId: hotMeshClient.appId,
32
+ jobId: ''
33
+ };
34
+ const hotMeshPrefix = key_1.KeyService.mintKey(hotMeshClient.namespace, key_1.KeyType.JOB_STATE, keyParams);
35
+ const prefixes = search.prefix.map((prefix) => `${hotMeshPrefix}${prefix}`);
36
+ await store.exec('FT.CREATE', `${search.index}`, 'ON', 'HASH', 'PREFIX', prefixes.length.toString(), ...prefixes, 'SCHEMA', ...schema);
37
+ }
38
+ catch (err) {
39
+ console.error(err);
40
+ hotMeshClient.engine.logger.info('durable-client-search-err', { err });
41
+ }
42
+ }
43
+ }
11
44
  constructor(workflowId, hotMeshClient, searchSessionId) {
12
45
  this.searchSessionIndex = 0;
13
46
  const keyParams = {