@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
@@ -1,5 +1,5 @@
1
1
  import { HotMeshService as HotMesh } from '../hotmesh';
2
- import { Connection, Registry, WorkerConfig, WorkerOptions, WorkflowSearchOptions } from '../../types/durable';
2
+ import { Connection, Registry, WorkerConfig, WorkerOptions } from '../../types/durable';
3
3
  export declare class WorkerService {
4
4
  static activityRegistry: Registry;
5
5
  static connection: Connection;
@@ -9,14 +9,6 @@ export declare class WorkerService {
9
9
  static getHotMesh: (workflowTopic: string, config?: Partial<WorkerConfig>, options?: WorkerOptions) => Promise<HotMesh>;
10
10
  static activateWorkflow(hotMesh: HotMesh): Promise<void>;
11
11
  static registerActivities<ACT>(activities: ACT): Registry;
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 fail gracefully. In all cases, the values
16
- * will be stored in the workflow's central HASH data structure, allowing
17
- * for manual traversal and inspection as well.
18
- */
19
- static configureSearchIndex(hotMeshClient: HotMesh, search?: WorkflowSearchOptions): Promise<void>;
20
12
  static create(config: WorkerConfig): Promise<WorkerService>;
21
13
  static resolveWorkflowTarget(workflow: object | Function): [string, Function];
22
14
  run(): Promise<void>;
@@ -3,10 +3,10 @@ var _a;
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.WorkerService = void 0;
5
5
  const errors_1 = require("../../modules/errors");
6
- const key_1 = require("../../modules/key");
7
6
  const asyncLocalStorage_1 = require("./asyncLocalStorage");
8
7
  const factory_1 = require("./factory");
9
8
  const hotmesh_1 = require("../hotmesh");
9
+ const search_1 = require("./search");
10
10
  const stream_1 = require("../../types/stream");
11
11
  class WorkerService {
12
12
  static async activateWorkflow(hotMesh) {
@@ -45,39 +45,6 @@ class WorkerService {
45
45
  }
46
46
  return WorkerService.activityRegistry;
47
47
  }
48
- /**
49
- * For those deployments with a redis stack backend (with the FT module),
50
- * this method will configure the search index for the workflow. For all
51
- * others, this method will fail gracefully. In all cases, the values
52
- * will be stored in the workflow's central HASH data structure, allowing
53
- * for manual traversal and inspection as well.
54
- */
55
- static async configureSearchIndex(hotMeshClient, search) {
56
- if (search?.schema) {
57
- const store = hotMeshClient.engine.store;
58
- const schema = [];
59
- for (const [key, value] of Object.entries(search.schema)) {
60
- //prefix with a comma (avoids collisions with hotmesh reserved words)
61
- schema.push(`_${key}`);
62
- schema.push(value.type);
63
- if (value.sortable) {
64
- schema.push('SORTABLE');
65
- }
66
- }
67
- try {
68
- const keyParams = {
69
- appId: hotMeshClient.appId,
70
- jobId: ''
71
- };
72
- const hotMeshPrefix = key_1.KeyService.mintKey(hotMeshClient.namespace, key_1.KeyType.JOB_STATE, keyParams);
73
- const prefixes = search.prefix.map((prefix) => `${hotMeshPrefix}${prefix}`);
74
- await store.exec('FT.CREATE', `${search.index}`, 'ON', 'HASH', 'PREFIX', prefixes.length, ...prefixes, 'SCHEMA', ...schema);
75
- }
76
- catch (err) {
77
- hotMeshClient.engine.logger.info('durable-client-search-err', { err });
78
- }
79
- }
80
- }
81
48
  static async create(config) {
82
49
  WorkerService.connection = config.connection;
83
50
  const workflow = config.workflow;
@@ -89,7 +56,7 @@ class WorkerService {
89
56
  const worker = new WorkerService();
90
57
  worker.activityRunner = await worker.initActivityWorker(config, activityTopic);
91
58
  worker.workflowRunner = await worker.initWorkflowWorker(config, workflowTopic, workflowFunction);
92
- WorkerService.configureSearchIndex(worker.workflowRunner, config.search);
59
+ search_1.Search.configureSearchIndex(worker.workflowRunner, config.search);
93
60
  await WorkerService.activateWorkflow(worker.workflowRunner);
94
61
  return worker;
95
62
  }
@@ -10,11 +10,18 @@ export declare class WorkflowService {
10
10
  * spawn a child workflow. return the childJobId.
11
11
  */
12
12
  static startChild<T>(options: WorkflowOptions): Promise<string>;
13
+ /**
14
+ * wrap all activities in a proxy that will durably run them
15
+ */
13
16
  static proxyActivities<ACT>(options?: ActivityConfig): ProxyType<ACT>;
17
+ /**
18
+ * return a search session for use when reading/writing to the workflow HASH
19
+ */
14
20
  static search(): Promise<Search>;
15
21
  /**
16
22
  * those methods that may only be called once must be protected by flagging
17
- * their execution with a unique key (the key is stored in the workflow state)
23
+ * their execution with a unique key (the key is stored in the HASH alongside
24
+ * process state and job state)
18
25
  */
19
26
  static isSideEffectAllowed(hotMeshClient: HotMesh, prefix: string): Promise<boolean>;
20
27
  /**
@@ -89,6 +89,9 @@ class WorkflowService {
89
89
  return childJobId;
90
90
  }
91
91
  }
92
+ /**
93
+ * wrap all activities in a proxy that will durably run them
94
+ */
92
95
  static proxyActivities(options) {
93
96
  if (options.activities) {
94
97
  worker_1.WorkerService.registerActivities(options.activities);
@@ -103,6 +106,9 @@ class WorkflowService {
103
106
  }
104
107
  return proxy;
105
108
  }
109
+ /**
110
+ * return a search session for use when reading/writing to the workflow HASH
111
+ */
106
112
  static async search() {
107
113
  const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
108
114
  const workflowId = store.get('workflowId');
@@ -111,14 +117,15 @@ class WorkflowService {
111
117
  const namespace = store.get('namespace');
112
118
  const COUNTER = store.get('counter');
113
119
  const execIndex = COUNTER.counter = COUNTER.counter + 1;
114
- //this ID is used as a item key with a hash (dash prefix ensures no collision)
115
120
  const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, { namespace });
121
+ //this ID is used as a item key with a hash (dash prefix ensures no collision)
116
122
  const searchSessionId = `-search${workflowDimension}-${execIndex}`;
117
123
  return new search_1.Search(workflowId, hotMeshClient, searchSessionId);
118
124
  }
119
125
  /**
120
126
  * those methods that may only be called once must be protected by flagging
121
- * their execution with a unique key (the key is stored in the workflow state)
127
+ * their execution with a unique key (the key is stored in the HASH alongside
128
+ * process state and job state)
122
129
  */
123
130
  static async isSideEffectAllowed(hotMeshClient, prefix) {
124
131
  const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
@@ -126,15 +133,12 @@ class WorkflowService {
126
133
  const workflowDimension = store.get('workflowDimension') ?? '';
127
134
  const COUNTER = store.get('counter');
128
135
  const execIndex = COUNTER.counter = COUNTER.counter + 1;
129
- //this ID is used as a item key with a hash (dash prefix ensures no collision)
130
136
  const sessionId = `-${prefix}${workflowDimension}-${execIndex}-`;
131
- //this ID is used as a item key with a hash (dash prefix ensures no collision)
132
137
  const keyParams = {
133
138
  appId: hotMeshClient.appId,
134
- jobId: ''
139
+ jobId: workflowId
135
140
  };
136
- const hotMeshPrefix = key_1.KeyService.mintKey(hotMeshClient.namespace, key_1.KeyType.JOB_STATE, keyParams);
137
- const workflowGuid = `${hotMeshPrefix}${workflowId}`;
141
+ const workflowGuid = key_1.KeyService.mintKey(hotMeshClient.namespace, key_1.KeyType.JOB_STATE, keyParams);
138
142
  const guidValue = Number(await hotMeshClient.engine.store.exec('HINCRBYFLOAT', workflowGuid, sessionId, '1'));
139
143
  return guidValue === 1;
140
144
  }
@@ -161,16 +165,11 @@ class WorkflowService {
161
165
  const hotMeshClient = await worker_1.WorkerService.getHotMesh(`${namespace}.flow.signal`, { namespace });
162
166
  if (await WorkflowService.isSideEffectAllowed(hotMeshClient, 'hook')) {
163
167
  const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
164
- let workflowId;
165
- let workflowTopic;
166
- if (options.workflowId && options.taskQueue && options.workflowName) {
167
- workflowId = options.workflowId;
168
+ const workflowId = options.workflowId ?? store.get('workflowId');
169
+ let workflowTopic = store.get('workflowTopic');
170
+ if (options.taskQueue && options.workflowName) {
168
171
  workflowTopic = `${options.taskQueue}-${options.workflowName}`;
169
- }
170
- else {
171
- workflowId = store.get('workflowId');
172
- workflowTopic = store.get('workflowTopic');
173
- }
172
+ } //else this is essentially recursion as the function calls itself
174
173
  const payload = {
175
174
  arguments: [...options.args],
176
175
  id: workflowId,
@@ -18,6 +18,7 @@ import { JobState, JobData, JobMetadata, JobOutput, JobStatus } from '../../type
18
18
  import { HotMeshApps, HotMeshConfig, HotMeshManifest, HotMeshSettings } from '../../types/hotmesh';
19
19
  import { JobMessageCallback } from '../../types/quorum';
20
20
  import { RedisClient, RedisMulti } from '../../types/redis';
21
+ import { StringAnyType } from '../../types/serializer';
21
22
  import { GetStatsOptions, IdsResponse, JobStatsInput, StatsResponse } from '../../types/stats';
22
23
  import { StreamCode, StreamData, StreamDataResponse, StreamError, StreamStatus } from '../../types/stream';
23
24
  declare class EngineService {
@@ -82,6 +83,7 @@ declare class EngineService {
82
83
  runJobCompletionTasks(context: JobState, emit?: boolean): Promise<void>;
83
84
  getStatus(jobId: string): Promise<JobStatus>;
84
85
  getState(topic: string, jobId: string): Promise<JobOutput>;
86
+ getQueryState(jobId: string, fields: string[]): Promise<StringAnyType>;
85
87
  compress(terms: string[]): Promise<boolean>;
86
88
  }
87
89
  export { EngineService };
@@ -523,6 +523,9 @@ class EngineService {
523
523
  }
524
524
  return stateTree;
525
525
  }
526
+ async getQueryState(jobId, fields) {
527
+ return await this.store.getQueryState(jobId, fields);
528
+ }
526
529
  async compress(terms) {
527
530
  const existingSymbols = await this.store.getSymbolValues();
528
531
  const startIndex = Object.keys(existingSymbols).length;
@@ -7,6 +7,7 @@ import { HotMeshConfig, HotMeshManifest } from '../../types/hotmesh';
7
7
  import { JobMessageCallback } from '../../types/quorum';
8
8
  import { JobStatsInput, GetStatsOptions, IdsResponse, StatsResponse } from '../../types/stats';
9
9
  import { StreamCode, StreamData, StreamDataResponse, StreamStatus } from '../../types/stream';
10
+ import { StringAnyType } from '../../types/serializer';
10
11
  declare class HotMeshService {
11
12
  namespace: string;
12
13
  appId: string;
@@ -21,7 +22,7 @@ declare class HotMeshService {
21
22
  static guid(): string;
22
23
  initEngine(config: HotMeshConfig, logger: ILogger): Promise<void>;
23
24
  initQuorum(config: HotMeshConfig, engine: EngineService, logger: ILogger): Promise<void>;
24
- initWorkers(config: HotMeshConfig, logger: ILogger): Promise<void>;
25
+ doWork(config: HotMeshConfig, logger: ILogger): Promise<void>;
25
26
  pub(topic: string, data?: JobData, context?: JobState): Promise<string>;
26
27
  sub(topic: string, callback: JobMessageCallback): Promise<void>;
27
28
  unsub(topic: string): Promise<void>;
@@ -35,6 +36,7 @@ declare class HotMeshService {
35
36
  getStats(topic: string, query: JobStatsInput): Promise<StatsResponse>;
36
37
  getStatus(jobId: string): Promise<JobStatus>;
37
38
  getState(topic: string, jobId: string): Promise<JobOutput>;
39
+ getQueryState(jobId: string, fields: string[]): Promise<StringAnyType>;
38
40
  getIds(topic: string, query: JobStatsInput, queryFacets?: any[]): Promise<IdsResponse>;
39
41
  resolveQuery(topic: string, query: JobStatsInput): Promise<GetStatsOptions>;
40
42
  scrub(jobId: string): Promise<void>;
@@ -45,7 +45,7 @@ class HotMeshService {
45
45
  instance.logger = new logger_1.LoggerService(config.appId, instance.guid, config.name || '', config.logLevel);
46
46
  await instance.initEngine(config, instance.logger);
47
47
  await instance.initQuorum(config, instance.engine, instance.logger);
48
- await instance.initWorkers(config, instance.logger);
48
+ await instance.doWork(config, instance.logger);
49
49
  return instance;
50
50
  }
51
51
  static guid() {
@@ -62,7 +62,7 @@ class HotMeshService {
62
62
  this.quorum = await quorum_1.QuorumService.init(this.namespace, this.appId, this.guid, config, engine, logger);
63
63
  }
64
64
  }
65
- async initWorkers(config, logger) {
65
+ async doWork(config, logger) {
66
66
  this.workers = await worker_1.WorkerService.init(this.namespace, this.appId, this.guid, config, logger);
67
67
  }
68
68
  // ************* PUB/SUB METHODS *************
@@ -108,6 +108,9 @@ class HotMeshService {
108
108
  async getState(topic, jobId) {
109
109
  return this.engine?.getState(topic, jobId);
110
110
  }
111
+ async getQueryState(jobId, fields) {
112
+ return await this.engine?.getQueryState(jobId, fields);
113
+ }
111
114
  async getIds(topic, query, queryFacets = []) {
112
115
  return await this.engine?.getIds(topic, query, queryFacets);
113
116
  }
@@ -61,6 +61,11 @@ declare abstract class StoreService<T, U extends AbstractRedisClient> {
61
61
  setStatus(collationKeyStatus: number, jobId: string, appId: string, multi?: U): Promise<any>;
62
62
  getStatus(jobId: string, appId: string): Promise<number>;
63
63
  setState({ ...state }: StringAnyType, status: number | null, jobId: string, symbolNames: string[], dIds: StringStringType, multi?: U): Promise<string>;
64
+ /**
65
+ * returns custom search fields and values. The fields param
66
+ * should not prefix items with an underscore.
67
+ */
68
+ getQueryState(jobId: string, fields: string[]): Promise<StringAnyType>;
64
69
  getState(jobId: string, consumes: Consumes, dIds: StringStringType): Promise<[StringAnyType, number] | undefined>;
65
70
  collate(jobId: string, activityId: string, amount: number, dIds: StringStringType, multi?: U): Promise<number>;
66
71
  setStateNX(jobId: string, appId: string): Promise<boolean>;
@@ -394,6 +394,20 @@ class StoreService {
394
394
  await (multi || this.redisClient)[this.commands.hset](hashKey, hashData);
395
395
  return jobId;
396
396
  }
397
+ /**
398
+ * returns custom search fields and values. The fields param
399
+ * should not prefix items with an underscore.
400
+ */
401
+ async getQueryState(jobId, fields) {
402
+ const key = this.mintKey(key_1.KeyType.JOB_STATE, { appId: this.appId, jobId });
403
+ const _fields = fields.map(field => `_${field}`);
404
+ const jobDataArray = await this.redisClient[this.commands.hmget](key, _fields);
405
+ const jobData = {};
406
+ fields.forEach((field, index) => {
407
+ jobData[field] = jobDataArray[index];
408
+ });
409
+ return jobData;
410
+ }
397
411
  async getState(jobId, consumes, dIds) {
398
412
  //get abbreviated field list (the symbols for the paths)
399
413
  const key = this.mintKey(key_1.KeyType.JOB_STATE, { appId: this.appId, jobId });
@@ -28,9 +28,9 @@ type WorkflowOptions = {
28
28
  };
29
29
  type HookOptions = {
30
30
  namespace?: string;
31
- taskQueue: string;
31
+ taskQueue?: string;
32
32
  args: any[];
33
- workflowId: string;
33
+ workflowId?: string;
34
34
  workflowName?: string;
35
35
  search?: WorkflowSearchOptions;
36
36
  config?: WorkflowConfig;
@@ -52,12 +52,28 @@ type WorkflowDataType = {
52
52
  workflowId: string;
53
53
  workflowTopic: string;
54
54
  };
55
+ type MeshDBClassConfig = {
56
+ namespace: string;
57
+ taskQueue: string;
58
+ redisOptions: RedisOptions;
59
+ redisClass: RedisClass;
60
+ };
61
+ type MeshDBConfig = {
62
+ taskQueue?: string;
63
+ index?: {
64
+ index: string;
65
+ prefix: string[];
66
+ schema: Record<string, {
67
+ type: 'TEXT' | 'NUMERIC' | 'TAG';
68
+ sortable: boolean;
69
+ }>;
70
+ };
71
+ };
55
72
  type ConnectionConfig = {
56
73
  class: RedisClass;
57
74
  options: RedisOptions;
58
75
  };
59
76
  type Connection = ConnectionConfig;
60
- type NativeConnection = ConnectionConfig;
61
77
  type ClientConfig = {
62
78
  connection: Connection;
63
79
  };
@@ -95,4 +111,4 @@ type ActivityConfig = {
95
111
  maximumInterval: string;
96
112
  };
97
113
  };
98
- export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, NativeConnection, ProxyType, Registry, SignalOptions, HookOptions, WorkerConfig, WorkflowConfig, WorkerOptions, WorkflowSearchOptions, WorkflowDataType, WorkflowOptions, };
114
+ export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, ProxyType, Registry, SignalOptions, HookOptions, MeshDBClassConfig, MeshDBConfig, WorkerConfig, WorkflowConfig, WorkerOptions, WorkflowSearchOptions, WorkflowDataType, WorkflowOptions, };
@@ -3,7 +3,7 @@ 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, HookOptions, WorkflowConfig, WorkerConfig, WorkerOptions, WorkflowSearchOptions, WorkflowDataType, WorkflowOptions, } from './durable';
6
+ export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, ProxyType, Registry, HookOptions, MeshDBClassConfig, MeshDBConfig, 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';
package/package.json CHANGED
@@ -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,5 @@
1
1
  import { Connection, ConnectionConfig } from "../../types/durable";
2
2
 
3
- /*
4
- Here is an example of how the methods in this file are used:
5
-
6
- ./client.ts
7
-
8
- import { Durable } from '@hotmeshio/hotmesh';
9
- import Redis from 'ioredis';
10
- import { nanoid } from 'nanoid';
11
-
12
- async function run() {
13
- const connection = await Durable.Connection.connect({
14
- class: Redis,
15
- options: {
16
- host: 'localhost',
17
- port: 6379,
18
- },
19
- });
20
-
21
- const client = new Durable.Client({
22
- connection,
23
- });
24
-
25
- const handle = await client.workflow.start(example, {
26
- taskQueue: 'hello-world',
27
- args: ['HotMesh'],
28
- workflowName: 'example',
29
- workflowId: nanoid(),
30
- });
31
-
32
- console.log(`Started workflow ${handle.workflowId}`);
33
- console.log(await handle.result());
34
- }
35
-
36
- run().catch((err) => {
37
- console.error(err);
38
- process.exit(1);
39
- });
40
-
41
- */
42
-
43
3
  export class ConnectionService {
44
4
  static async connect(config: ConnectionConfig): Promise<Connection> {
45
5
  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
  const getWorkflowYAML = (app: string, version: string) => {
12
14
  return `app:
@@ -16,6 +16,22 @@ export class WorkflowHandleService {
16
16
  await this.hotMesh.hook(`${this.hotMesh.appId}.wfs.signal`, { id: signalId, data });
17
17
  }
18
18
 
19
+ async state(metadata = false): Promise<Record<string, any>> {
20
+ const state = await this.hotMesh.getState(`${this.hotMesh.appId}.execute`, this.workflowId);
21
+ if (!state.data && state.metadata.err) {
22
+ throw new Error(JSON.parse(state.metadata.err));
23
+ }
24
+ return metadata ? state : state.data;
25
+ }
26
+
27
+ async queryState(fields: string[]): Promise<Record<string, any>> {
28
+ return await this.hotMesh.getQueryState(this.workflowId, fields);
29
+ }
30
+
31
+ async status(): Promise<number> {
32
+ return await this.hotMesh.getStatus(this.workflowId);
33
+ }
34
+
19
35
  async result(loadState?: boolean): Promise<any> {
20
36
  if (loadState) {
21
37
  const state = await this.hotMesh.getState(`${this.hotMesh.appId}.execute`, this.workflowId);
@@ -26,8 +42,7 @@ export class WorkflowHandleService {
26
42
  //child flows are never technically 'done' as they have an open hook
27
43
  //that is tied to the parent flow's completion. so, we need to check
28
44
  //the 'done' flag on the child flow's payload (not the 'js' metadata field
29
- //which is typically used); the loadState parameter ensures this
30
- //check happens early
45
+ //which is typically used); the `loadState` parameter triggers this
31
46
  return state.data.response;
32
47
  }
33
48
  }
@@ -1,58 +1,14 @@
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
7
 
8
- /**
9
- * As a durable integration platform, HotMesh
10
- * can model and emulate other durable systems
11
- * (like Temporal). As you review the code in
12
- * this file, note the following:
13
- *
14
- * 1) There is no central governing server.
15
- * HotMesh is a client-side SDK that connects to Redis
16
- * using CQRS principles to implicitly drive
17
- * orchestrations using a headless quorum. Stream
18
- * semantics guarantee that all events are
19
- * processed by the quorum of connected clients.
20
- *
21
- * 2) Every developer-defined `workflow` function
22
- * is assigned a HotMesh workflow (which runs in
23
- * the background) to support it.
24
- *
25
- * If the HotMesh workflow is not yet defined,
26
- * it will be deployed and activated on-the-fly.
27
- * (The generated DAG will have one Trigger Activity
28
- * and one Worker Activity.) The Worker Activity
29
- * is configured to catch execution errors and
30
- * return them, using a 'pending' status to
31
- * indicate that the workflow is still running.
32
- * It is possible for workflow activities to throw
33
- * errors that will force the entire workflow to
34
- * fail. This is not the case here. The workflow will
35
- * continue to run until it is completed or cancelled.
36
- *
37
- * 2) Every developer-defined `activity` function
38
- * is assigned a HotMesh workflow (which runs in
39
- * the background) to support it.
40
- *
41
- * (The generated DAG will have one Trigger Activity and one
42
- * Worker Activity.) The JOB ID for Worker Activity executions
43
- * is derived from the containing JOB ID,
44
- * allowing Activity state to be 'replayed' when the workflow
45
- * is run again. The Activity Function Runner (activity proxy)
46
- * is configured similar to how the workflow worker is and will
47
- * catch execution errors and return them to the caller, using a
48
- * 'pending' status to indicate that the activity is still running.
49
- * This allows the activity to be retried until it succeeds.
50
- */
51
-
52
8
  export const Durable = {
53
9
  Client: ClientService,
54
10
  Connection: ConnectionService,
55
- NativeConnection: NativeConnectionService,
11
+ MeshDB: MeshDBService,
56
12
  Worker: WorkerService,
57
13
  workflow: WorkflowService,
58
14
  };