@hotmeshio/hotmesh 0.0.23 → 0.0.25

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 (41) hide show
  1. package/README.md +58 -66
  2. package/build/index.d.ts +2 -1
  3. package/build/index.js +3 -1
  4. package/build/package.json +2 -2
  5. package/build/services/durable/factory.js +6 -6
  6. package/build/services/durable/handle.js +2 -4
  7. package/build/services/durable/index.d.ts +2 -2
  8. package/build/services/durable/index.js +2 -2
  9. package/build/services/durable/meshos.d.ts +112 -0
  10. package/build/services/durable/meshos.js +306 -0
  11. package/build/services/durable/search.js +0 -1
  12. package/build/services/durable/worker.d.ts +1 -1
  13. package/build/services/durable/worker.js +8 -4
  14. package/build/services/durable/workflow.d.ts +4 -0
  15. package/build/services/durable/workflow.js +21 -9
  16. package/build/services/signaler/stream.js +1 -2
  17. package/build/services/store/clients/ioredis.js +2 -2
  18. package/build/services/store/clients/redis.js +2 -3
  19. package/build/services/stream/clients/redis.js +1 -1
  20. package/build/services/sub/clients/redis.js +1 -1
  21. package/build/types/durable.d.ts +28 -13
  22. package/build/types/index.d.ts +1 -1
  23. package/index.ts +2 -1
  24. package/package.json +2 -2
  25. package/services/durable/factory.ts +6 -6
  26. package/services/durable/handle.ts +2 -4
  27. package/services/durable/index.ts +2 -2
  28. package/services/durable/meshos.ts +366 -0
  29. package/services/durable/search.ts +0 -1
  30. package/services/durable/worker.ts +8 -5
  31. package/services/durable/workflow.ts +23 -10
  32. package/services/signaler/stream.ts +1 -2
  33. package/services/store/clients/ioredis.ts +2 -3
  34. package/services/store/clients/redis.ts +2 -3
  35. package/services/stream/clients/redis.ts +1 -1
  36. package/services/sub/clients/redis.ts +1 -1
  37. package/types/durable.ts +38 -12
  38. package/types/index.ts +8 -3
  39. package/build/services/durable/meshdb.d.ts +0 -113
  40. package/build/services/durable/meshdb.js +0 -211
  41. package/services/durable/meshdb.ts +0 -254
@@ -0,0 +1,306 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MeshOSService = void 0;
4
+ const nanoid_1 = require("nanoid");
5
+ const _1 = require(".");
6
+ const asyncLocalStorage_1 = require("./asyncLocalStorage");
7
+ const client_1 = require("./client");
8
+ const search_1 = require("./search");
9
+ const worker_1 = require("./worker");
10
+ const workflow_1 = require("./workflow");
11
+ const stream_1 = require("../signaler/stream");
12
+ /**
13
+ * The base class for running MeshOS workflows.
14
+ * Extend this class, add your Redis config, and add functions to
15
+ * execute as durable `hooks`, `workflows`, and `activities`.
16
+ */
17
+ class MeshOSService {
18
+ static async getHotMeshClient(redisClass, redisOptions, namespace, taskQueue) {
19
+ const client = new client_1.ClientService({
20
+ connection: {
21
+ class: redisClass,
22
+ options: redisOptions,
23
+ }
24
+ });
25
+ return await client.getHotMeshClient(taskQueue, namespace);
26
+ }
27
+ /**
28
+ * mints a workflow ID, using the search prefix.
29
+ * NOTE: The prefix is necesary when indexing
30
+ * HASHes when FT search is enabled.
31
+ * @returns {string}
32
+ */
33
+ static mintGuid() {
34
+ const my = new this();
35
+ return `${my.search?.prefix?.[0]}${(0, nanoid_1.nanoid)()}}`;
36
+ }
37
+ /**
38
+ * Creates an FT search index
39
+ */
40
+ static async createIndex() {
41
+ const my = new this();
42
+ const hmClient = await MeshOSService.getHotMeshClient(my.redisClass, my.redisOptions, my.namespace, my.taskQueue);
43
+ search_1.Search.configureSearchIndex(hmClient, my.search);
44
+ }
45
+ /**
46
+ * stop the workers
47
+ * @returns {Promise<void>}
48
+ */
49
+ static async stopWorkers() {
50
+ await _1.Durable.Client.shutdown();
51
+ await _1.Durable.Worker.shutdown();
52
+ await stream_1.StreamSignaler.stopConsuming();
53
+ }
54
+ /**
55
+ * Initializes the worker(s). This is a static
56
+ * method that allows for optional task Queue targeting.
57
+ * An `allowList` may be optionally provided to start
58
+ * specific `worker` and `hook` methods.
59
+ * @param {MeshOSWorkerOptions} [options]
60
+ */
61
+ static async startWorkers(options) {
62
+ const taskQueue = options && options.taskQueue;
63
+ const allowList = options && options.allowList || [];
64
+ const my = new this();
65
+ //helper functions
66
+ const resolveFunctionNames = (arr) => arr.map(item => typeof item === 'string' ? item : item.name);
67
+ const belongsTo = (name, target) => {
68
+ const isWorkflow = target.find((item) => {
69
+ return typeof item === 'string' ? item === name : item.name === name;
70
+ });
71
+ return isWorkflow !== undefined;
72
+ };
73
+ // proxy registered activities
74
+ const proxyFunctionNames = resolveFunctionNames([...my.proxyFunctions]);
75
+ if (proxyFunctionNames.length) {
76
+ const proxyActivities = proxyFunctionNames.reduce((acc, funcName) => {
77
+ let originalMethod = my[funcName];
78
+ if (typeof originalMethod === 'function') {
79
+ acc[funcName] = async (...args) => {
80
+ return await originalMethod.apply(my, args);
81
+ };
82
+ }
83
+ return acc;
84
+ }, {});
85
+ const proxiedActivities = _1.Durable.workflow.proxyActivities({
86
+ activities: proxyActivities
87
+ });
88
+ Object.assign(my, proxiedActivities);
89
+ }
90
+ const functionsToIterate = allowList.length ? resolveFunctionNames(allowList) : resolveFunctionNames([...my.workflowFunctions, ...my.hookFunctions]);
91
+ // Iterating through the functions sequentially
92
+ for (const funcName of functionsToIterate) {
93
+ const originalMethod = my[funcName];
94
+ if (typeof originalMethod === 'function') {
95
+ //wrap the function to return
96
+ const wrappedFunction = {
97
+ [funcName]: async (...args) => {
98
+ const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
99
+ const workflowId = store.get('workflowId');
100
+ //use a Proxy to wrap hook methods
101
+ const context = new Proxy(my, {
102
+ get: (target, prop, receiver) => {
103
+ if (prop === 'id') {
104
+ return workflowId;
105
+ }
106
+ else if (typeof target[prop] === 'function') {
107
+ return (...args) => {
108
+ return new Promise(async (resolve, reject) => {
109
+ if (belongsTo(prop, my.hookFunctions)) {
110
+ return workflow_1.WorkflowService.hook({
111
+ namespace: my.namespace,
112
+ taskQueue: my.taskQueue,
113
+ workflowName: prop,
114
+ workflowId,
115
+ args,
116
+ }).then(resolve).catch(reject);
117
+ }
118
+ //otherwise, call the method as a standard instance method.
119
+ target[prop].apply(this, args).then(resolve).catch(reject);
120
+ });
121
+ };
122
+ }
123
+ return Reflect.get(target, prop, receiver);
124
+ },
125
+ });
126
+ return await originalMethod.apply(context, args);
127
+ }
128
+ };
129
+ //start the worker
130
+ await worker_1.WorkerService.create({
131
+ namespace: my.namespace,
132
+ connection: {
133
+ class: my.redisClass,
134
+ options: my.redisOptions,
135
+ },
136
+ taskQueue: taskQueue ?? my.taskQueue,
137
+ workflow: wrappedFunction,
138
+ });
139
+ }
140
+ }
141
+ }
142
+ /**
143
+ * executes the redis FT search query
144
+ * @example '@_quantity:[89 89]'
145
+ * @param {any[]} args
146
+ * @returns {string}
147
+ */
148
+ static async find(options, ...args) {
149
+ const my = new this();
150
+ const client = new client_1.ClientService({ connection: {
151
+ class: my.redisClass,
152
+ options: my.redisOptions
153
+ } });
154
+ //workflow name is the function name driving the workflow
155
+ let workflowName;
156
+ if (options?.workflowName) {
157
+ workflowName = options?.workflowName;
158
+ }
159
+ else if (my.workflowFunctions?.length) {
160
+ let target = my.workflowFunctions[0];
161
+ if (typeof target === 'string') {
162
+ workflowName = target;
163
+ }
164
+ else {
165
+ workflowName = target.name;
166
+ }
167
+ }
168
+ return await client.workflow.search(options?.taskQueue ?? my.taskQueue, workflowName, my.namespace, my.search.index, ...args); //[count, [id, fields[], id, fields[], id, fields[], ...]]
169
+ }
170
+ /**
171
+ * returns the workflow handle. The handle can then be
172
+ * used to query for status, state, custom state, etc.
173
+ * @param {string} id
174
+ * @returns {Promise<WorkflowHandleService>}
175
+ */
176
+ static async get(id) {
177
+ const my = new this();
178
+ const client = new client_1.ClientService({ connection: {
179
+ class: my.redisClass,
180
+ options: my.redisOptions
181
+ } });
182
+ let workflowName;
183
+ let target = my.workflowFunctions[0];
184
+ if (typeof target === 'string') {
185
+ workflowName = target;
186
+ }
187
+ else {
188
+ workflowName = target.name;
189
+ }
190
+ return await client.workflow.getHandle(my.taskQueue, workflowName, id, my.namespace);
191
+ }
192
+ /**
193
+ * Optionally include a target taskQueue to exec the
194
+ * workflow's call on a specific worker queue.
195
+ */
196
+ constructor(id, options) {
197
+ /**
198
+ * The top-level Redis isolation. All workflow data is
199
+ * isolated within this namespace. Values should be
200
+ * lower-case with no spaces (e.g, 'staging', 'prod', 'test',
201
+ * 'routing-stagig', 'reporting-prod', etc.).
202
+ * 1) only url-safe values are allowed;
203
+ * 2) the 'a' symbol is reserved by HotMesh for indexing apps
204
+ */
205
+ this.namespace = 'durable';
206
+ /**
207
+ * Data is routed to workers that specify this task queue.
208
+ * Setting the task queue when the worker is created will
209
+ * ensure that the worker only receives messages destined
210
+ * for the queue. Callers can specify the taskQue to when
211
+ * starting a job to call those workers.
212
+ */
213
+ this.taskQueue = 'default';
214
+ /**
215
+ * These methods run as durable workflows
216
+ */
217
+ this.workflowFunctions = [];
218
+ /**
219
+ * These methods run as hooks (hook into a running workflow)
220
+ */
221
+ this.hookFunctions = [];
222
+ /**
223
+ * These methods run as proxied activities (and are safely memoized)
224
+ */
225
+ this.proxyFunctions = [];
226
+ /**
227
+ * The Redis connection options. NOTE: Redis and IORedis
228
+ * use different formats for their connection config.
229
+ */
230
+ this.redisOptions = {
231
+ host: 'localhost',
232
+ port: 6379,
233
+ password: '',
234
+ db: 0,
235
+ };
236
+ if (typeof (id) === 'string') {
237
+ this.id = id;
238
+ }
239
+ else if (id?.id) {
240
+ this.id = id.id;
241
+ options = id;
242
+ id = undefined;
243
+ }
244
+ ;
245
+ if (options?.taskQueue) {
246
+ this.taskQueue = options.taskQueue;
247
+ }
248
+ else if (!id && !options?.taskQueue) {
249
+ return this;
250
+ }
251
+ function belongsTo(name, target) {
252
+ const isWorkflow = target.find((item) => {
253
+ return typeof item === 'string' ? item === name : item.name === name;
254
+ });
255
+ return isWorkflow !== undefined;
256
+ }
257
+ return new Proxy(this, {
258
+ get: (target, prop, receiver) => {
259
+ if (typeof target[prop] === 'function') {
260
+ return (...args) => {
261
+ return new Promise(async (resolve, reject) => {
262
+ const client = new client_1.ClientService({ connection: {
263
+ class: this.redisClass,
264
+ options: this.redisOptions
265
+ } });
266
+ if (belongsTo(prop, this.workflowFunctions)) {
267
+ //start a new workflow
268
+ const handle = await client.workflow.start({
269
+ namespace: this.namespace,
270
+ args,
271
+ taskQueue: this.taskQueue,
272
+ workflowName: prop,
273
+ workflowId: this.id,
274
+ });
275
+ if (options?.await) {
276
+ //wait for the workflow to complete
277
+ const result = await handle.result();
278
+ return resolve(result);
279
+ }
280
+ else {
281
+ //return the workflow handle
282
+ return resolve(handle);
283
+ }
284
+ }
285
+ else if (belongsTo(prop, this.hookFunctions)) {
286
+ //hook into a running workflow
287
+ return client.workflow.hook({
288
+ namespace: this.namespace,
289
+ taskQueue: this.taskQueue,
290
+ workflowName: prop,
291
+ workflowId: this.id,
292
+ args,
293
+ }).then(resolve).catch(reject);
294
+ }
295
+ //otherwise, call the method as a standard instance method.
296
+ target[prop].apply(this, args).then(resolve).catch(reject);
297
+ });
298
+ };
299
+ }
300
+ return Reflect.get(target, prop, receiver);
301
+ },
302
+ });
303
+ }
304
+ }
305
+ MeshOSService.MeshOS = workflow_1.WorkflowService;
306
+ exports.MeshOSService = MeshOSService;
@@ -36,7 +36,6 @@ class Search {
36
36
  await store.exec('FT.CREATE', `${search.index}`, 'ON', 'HASH', 'PREFIX', prefixes.length.toString(), ...prefixes, 'SCHEMA', ...schema);
37
37
  }
38
38
  catch (err) {
39
- console.error(err);
40
39
  hotMeshClient.engine.logger.info('durable-client-search-err', { err });
41
40
  }
42
41
  }
@@ -10,7 +10,7 @@ export declare class WorkerService {
10
10
  static activateWorkflow(hotMesh: HotMesh): Promise<void>;
11
11
  static registerActivities<ACT>(activities: ACT): Registry;
12
12
  static create(config: WorkerConfig): Promise<WorkerService>;
13
- static resolveWorkflowTarget(workflow: object | Function): [string, Function];
13
+ static resolveWorkflowTarget(workflow: object | Function, name?: string): [string, Function];
14
14
  run(): Promise<void>;
15
15
  initActivityWorker(config: WorkerConfig, activityTopic: string): Promise<HotMesh>;
16
16
  wrapActivityFunctions(): Function;
@@ -41,6 +41,9 @@ class WorkerService {
41
41
  if (activities[key].name && typeof WorkerService.activityRegistry[activities[key].name] !== 'function') {
42
42
  WorkerService.activityRegistry[activities[key].name] = activities[key];
43
43
  }
44
+ else if (typeof activities[key] === 'function') {
45
+ WorkerService.activityRegistry[key] = activities[key];
46
+ }
44
47
  });
45
48
  }
46
49
  return WorkerService.activityRegistry;
@@ -60,17 +63,18 @@ class WorkerService {
60
63
  await WorkerService.activateWorkflow(worker.workflowRunner);
61
64
  return worker;
62
65
  }
63
- static resolveWorkflowTarget(workflow) {
66
+ static resolveWorkflowTarget(workflow, name) {
64
67
  let workflowFunction;
65
68
  if (typeof workflow === 'function') {
66
69
  workflowFunction = workflow;
70
+ return [workflowFunction.name ?? name, workflowFunction];
67
71
  }
68
72
  else {
69
73
  const workflowFunctionNames = Object.keys(workflow);
70
- workflowFunction = workflow[workflowFunctionNames[workflowFunctionNames.length - 1]];
71
- return WorkerService.resolveWorkflowTarget(workflowFunction);
74
+ const lastFunctionName = workflowFunctionNames[workflowFunctionNames.length - 1];
75
+ workflowFunction = workflow[lastFunctionName];
76
+ return WorkerService.resolveWorkflowTarget(workflowFunction, lastFunctionName);
72
77
  }
73
- return [workflowFunction.name, workflowFunction];
74
78
  }
75
79
  async run() {
76
80
  this.workflowRunner.engine.logger.info('WorkerService is running');
@@ -18,6 +18,10 @@ export declare class WorkflowService {
18
18
  * return a search session for use when reading/writing to the workflow HASH
19
19
  */
20
20
  static search(): Promise<Search>;
21
+ /**
22
+ * return a handle to the hotmesh client currently running the workflow
23
+ */
24
+ static getHotMesh(): Promise<HotMesh>;
21
25
  /**
22
26
  * those methods that may only be called once must be protected by flagging
23
27
  * their execution with a unique key (the key is stored in the HASH alongside
@@ -30,7 +30,7 @@ class WorkflowService {
30
30
  //this is risky but MUST be allowed. Users MAY set the workflowId,
31
31
  //but if there is a naming collision, the data from the target entity will be used
32
32
  //as there is know way of knowing if the item was generated via a prior run of the workflow
33
- const childJobId = options.workflowId ?? `${workflowId}-$${options.workflowName}${workflowDimension}-${execIndex}`;
33
+ const childJobId = options.workflowId ?? `-${workflowId}-$${options.workflowName}${workflowDimension}-${execIndex}`;
34
34
  const parentWorkflowId = `${workflowId}-f`;
35
35
  const client = new client_1.ClientService({
36
36
  connection: await connection_1.ConnectionService.connect(worker_1.WorkerService.connection),
@@ -65,7 +65,7 @@ class WorkflowService {
65
65
  const workflowSpan = store.get('workflowSpan');
66
66
  const COUNTER = store.get('counter');
67
67
  const execIndex = COUNTER.counter = COUNTER.counter + 1;
68
- const childJobId = options.workflowId ?? `${workflowId}-$${options.workflowName}${workflowDimension}-${execIndex}`;
68
+ const childJobId = options.workflowId ?? `-${workflowId}-$${options.workflowName}${workflowDimension}-${execIndex}`;
69
69
  const parentWorkflowId = `${workflowId}-f`;
70
70
  const workflowTopic = `${options.taskQueue}-${options.workflowName}`;
71
71
  try {
@@ -122,6 +122,15 @@ class WorkflowService {
122
122
  const searchSessionId = `-search${workflowDimension}-${execIndex}`;
123
123
  return new search_1.Search(workflowId, hotMeshClient, searchSessionId);
124
124
  }
125
+ /**
126
+ * return a handle to the hotmesh client currently running the workflow
127
+ */
128
+ static async getHotMesh() {
129
+ const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
130
+ const workflowTopic = store.get('workflowTopic');
131
+ const namespace = store.get('namespace');
132
+ return await worker_1.WorkerService.getHotMesh(workflowTopic, { namespace });
133
+ }
125
134
  /**
126
135
  * those methods that may only be called once must be protected by flagging
127
136
  * their execution with a unique key (the key is stored in the HASH alongside
@@ -148,8 +157,11 @@ class WorkflowService {
148
157
  */
149
158
  static async signal(signalId, data) {
150
159
  const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
160
+ const workflowTopic = store.get('workflowTopic');
151
161
  const namespace = store.get('namespace');
152
- const hotMeshClient = await worker_1.WorkerService.getHotMesh(`${namespace}.wfs.signal`, { namespace });
162
+ const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, { namespace });
163
+ //todo: this particular one is better patterned as a get/set,
164
+ //since the receipt is a meaningful string (the stream id)
153
165
  if (await WorkflowService.isSideEffectAllowed(hotMeshClient, 'signal')) {
154
166
  return await hotMeshClient.hook(`${namespace}.wfs.signal`, { id: signalId, data });
155
167
  }
@@ -161,8 +173,9 @@ class WorkflowService {
161
173
  */
162
174
  static async hook(options) {
163
175
  const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
176
+ const workflowTopic = store.get('workflowTopic');
164
177
  const namespace = store.get('namespace');
165
- const hotMeshClient = await worker_1.WorkerService.getHotMesh(`${namespace}.flow.signal`, { namespace });
178
+ const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, { namespace });
166
179
  if (await WorkflowService.isSideEffectAllowed(hotMeshClient, 'hook')) {
167
180
  const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
168
181
  const workflowId = options.workflowId ?? store.get('workflowId');
@@ -188,7 +201,7 @@ class WorkflowService {
188
201
  const workflowTopic = store.get('workflowTopic');
189
202
  const workflowDimension = store.get('workflowDimension') ?? '';
190
203
  const namespace = store.get('namespace');
191
- const sleepJobId = `${workflowId}-$sleep${workflowDimension}-${execIndex}`;
204
+ const sleepJobId = `-${workflowId}-$sleep${workflowDimension}-${execIndex}`;
192
205
  try {
193
206
  const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, { namespace });
194
207
  await hotMeshClient.getState(`${hotMeshClient.appId}.sleep.execute`, sleepJobId);
@@ -196,9 +209,8 @@ class WorkflowService {
196
209
  return seconds;
197
210
  }
198
211
  catch (e) {
199
- //if an error, the sleep job was not found...rethrow error; sleep job
200
- // will be automatically created according to the DAG rules (they
201
212
  // spawn a new sleep job if error code 595 is thrown by the worker)
213
+ // NOTE: If this message shows up in your stack trace, you forgot to await `Durable.workflow.sleep()` in your workflow code.
202
214
  throw new errors_1.DurableSleepError(workflowId, seconds, execIndex, workflowDimension);
203
215
  }
204
216
  }
@@ -216,7 +228,7 @@ class WorkflowService {
216
228
  const signalResults = [];
217
229
  for (const signal of signals) {
218
230
  const execIndex = COUNTER.counter = COUNTER.counter + 1;
219
- const wfsJobId = `${workflowId}-$wfs${workflowDimension}-${execIndex}`;
231
+ const wfsJobId = `-${workflowId}-$wfs${workflowDimension}-${execIndex}`;
220
232
  try {
221
233
  if (allAreComplete) {
222
234
  const state = await hotMeshClient.getState(`${hotMeshClient.appId}.wfs.execute`, wfsJobId);
@@ -267,7 +279,7 @@ class WorkflowService {
267
279
  const spn = store.get('workflowSpan');
268
280
  const namespace = store.get('namespace');
269
281
  const activityTopic = `${workflowTopic}-activity`;
270
- const activityJobId = `${workflowId}-$${activityName}${workflowDimension}-${execIndex}`;
282
+ const activityJobId = `-${workflowId}-$${activityName}${workflowDimension}-${execIndex}`;
271
283
  let activityState;
272
284
  try {
273
285
  const hotMeshClient = await worker_1.WorkerService.getHotMesh(activityTopic, { namespace });
@@ -37,7 +37,7 @@ class StreamSignaler {
37
37
  await this.store.xgroup('CREATE', stream, group, '$', 'MKSTREAM');
38
38
  }
39
39
  catch (err) {
40
- this.logger.info('consumer-group-exists', { stream, group });
40
+ this.logger.debug('consumer-group-exists', { stream, group });
41
41
  }
42
42
  }
43
43
  async publishMessage(topic, streamData, multi) {
@@ -121,7 +121,6 @@ class StreamSignaler {
121
121
  output = await callback(input);
122
122
  }
123
123
  catch (error) {
124
- console.error(error);
125
124
  this.logger.error(`stream-call-function-error`, { error });
126
125
  output = this.structureUnhandledError(input, error);
127
126
  }
@@ -45,7 +45,7 @@ class IORedisStoreService extends index_1.StoreService {
45
45
  return (await this.redisClient.xgroup(command, key, groupName, id, mkStream)) === 'OK';
46
46
  }
47
47
  catch (err) {
48
- this.logger.info(`Consumer group not created with MKSTREAM for key: ${key} and group: ${groupName}`);
48
+ this.logger.debug(`Consumer group not created with MKSTREAM for key: ${key} and group: ${groupName}`);
49
49
  throw err;
50
50
  }
51
51
  }
@@ -54,7 +54,7 @@ class IORedisStoreService extends index_1.StoreService {
54
54
  return (await this.redisClient.xgroup(command, key, groupName, id)) === 'OK';
55
55
  }
56
56
  catch (err) {
57
- this.logger.info(`Consumer group not created for key: ${key} and group: ${groupName}`);
57
+ this.logger.debug(`Consumer group not created for key: ${key} and group: ${groupName}`);
58
58
  throw err;
59
59
  }
60
60
  }
@@ -33,8 +33,7 @@ class RedisStoreService extends index_1.StoreService {
33
33
  };
34
34
  }
35
35
  getMulti() {
36
- const multi = this.redisClient.MULTI();
37
- return multi;
36
+ return this.redisClient.multi();
38
37
  }
39
38
  async exec(...args) {
40
39
  return await this.redisClient.sendCommand(args);
@@ -68,7 +67,7 @@ class RedisStoreService extends index_1.StoreService {
68
67
  }
69
68
  catch (error) {
70
69
  const streamType = mkStream === 'MKSTREAM' ? 'with MKSTREAM' : 'without MKSTREAM';
71
- this.logger.info(`x-group-error ${streamType} for key: ${key} and group: ${groupName}`, { error });
70
+ this.logger.debug(`x-group-error ${streamType} for key: ${key} and group: ${groupName}`, { error });
72
71
  throw error;
73
72
  }
74
73
  }
@@ -13,7 +13,7 @@ class RedisStreamService extends index_1.StreamService {
13
13
  this.appId = appId;
14
14
  }
15
15
  getMulti() {
16
- return this.redisClient.MULTI();
16
+ return this.redisClient.multi();
17
17
  }
18
18
  mintKey(type, params) {
19
19
  if (!this.namespace)
@@ -13,7 +13,7 @@ class RedisSubService extends index_1.SubService {
13
13
  this.appId = appId;
14
14
  }
15
15
  getMulti() {
16
- const multi = this.redisClient.MULTI();
16
+ const multi = this.redisClient.multi();
17
17
  return multi;
18
18
  }
19
19
  mintKey(type, params) {
@@ -10,7 +10,7 @@ type WorkflowSearchOptions = {
10
10
  prefix?: string[];
11
11
  schema?: Record<string, {
12
12
  type: 'TEXT' | 'NUMERIC' | 'TAG';
13
- sortable: boolean;
13
+ sortable?: boolean;
14
14
  }>;
15
15
  data?: Record<string, string>;
16
16
  };
@@ -52,22 +52,16 @@ type WorkflowDataType = {
52
52
  workflowId: string;
53
53
  workflowTopic: string;
54
54
  };
55
- type MeshDBClassConfig = {
55
+ type MeshOSClassConfig = {
56
56
  namespace: string;
57
57
  taskQueue: string;
58
58
  redisOptions: RedisOptions;
59
59
  redisClass: RedisClass;
60
60
  };
61
- type MeshDBConfig = {
61
+ type MeshOSConfig = {
62
+ id?: string;
63
+ await?: boolean;
62
64
  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
65
  };
72
66
  type ConnectionConfig = {
73
67
  class: RedisClass;
@@ -84,10 +78,31 @@ type WorkerConfig = {
84
78
  connection: Connection;
85
79
  namespace?: string;
86
80
  taskQueue: string;
87
- workflow: Function;
81
+ workflow: Function | Record<string | symbol, Function>;
88
82
  options?: WorkerOptions;
89
83
  search?: WorkflowSearchOptions;
90
84
  };
85
+ type FindOptions = {
86
+ workflowName?: string;
87
+ taskQueue?: string;
88
+ namespace?: string;
89
+ index?: string;
90
+ };
91
+ type MeshOSOptions = {
92
+ name: string;
93
+ options: WorkerOptions;
94
+ };
95
+ type MeshOSActivityOptions = {
96
+ name: string;
97
+ options: ActivityConfig;
98
+ };
99
+ type MeshOSWorkerOptions = {
100
+ taskQueue?: string;
101
+ allowList?: Array<MeshOSOptions | string>;
102
+ logLevel?: string;
103
+ maxSystemRetries?: number;
104
+ backoffCoefficient?: number;
105
+ };
91
106
  type WorkerOptions = {
92
107
  logLevel?: string;
93
108
  maxSystemRetries?: number;
@@ -111,4 +126,4 @@ type ActivityConfig = {
111
126
  maximumInterval: string;
112
127
  };
113
128
  };
114
- export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, ProxyType, Registry, SignalOptions, HookOptions, MeshDBClassConfig, MeshDBConfig, WorkerConfig, WorkflowConfig, WorkerOptions, WorkflowSearchOptions, WorkflowDataType, WorkflowOptions, };
129
+ export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, ProxyType, Registry, SignalOptions, FindOptions, HookOptions, MeshOSActivityOptions, MeshOSWorkerOptions, MeshOSClassConfig, MeshOSConfig, MeshOSOptions, 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, ProxyType, Registry, HookOptions, MeshDBClassConfig, MeshDBConfig, WorkflowConfig, WorkerConfig, WorkerOptions, WorkflowSearchOptions, WorkflowDataType, WorkflowOptions, } from './durable';
6
+ export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, ProxyType, Registry, SignalOptions, FindOptions, HookOptions, MeshOSActivityOptions, MeshOSWorkerOptions, MeshOSClassConfig, MeshOSConfig, MeshOSOptions, 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/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Durable } from './services/durable';
2
+ import { MeshOSService as MeshOS } from './services/durable/meshos';
2
3
  import { HotMeshService as HotMesh } from './services/hotmesh';
3
4
  import { HotMeshConfig } from './types/hotmesh';
4
5
 
5
- export { Durable, HotMesh, HotMeshConfig };
6
+ export { Durable, HotMesh, HotMeshConfig, MeshOS };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.23",
3
+ "version": "0.0.25",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -43,7 +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
+ "test:durable:meshos": "NODE_ENV=test jest ./tests/durable/meshos/index.test.ts --detectOpenHandles --forceExit --verbose",
47
47
  "test:durable:hello": "NODE_ENV=test jest ./tests/durable/helloworld/index.test.ts --detectOpenHandles --forceExit --verbose",
48
48
  "test:durable:goodbye": "NODE_ENV=test jest ./tests/durable/goodbye/index.test.ts --detectOpenHandles --forceExit --verbose",
49
49
  "test:durable:hook": "NODE_ENV=test jest ./tests/durable/hook/index.test.ts --detectOpenHandles --forceExit --verbose",