@hotmeshio/hotmesh 0.0.26 → 0.0.28

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.
package/README.md CHANGED
@@ -3,9 +3,11 @@
3
3
 
4
4
  Elevate Redis from an in-memory data cache, and turn your unpredictable functions into unbreakable workflows.
5
5
 
6
- **HotMesh** is a wrapper for Redis that exposes a higher level set of domain constructs like ‘activities’, ‘workflows’, 'jobs', etc. Behind the scenes, it uses *Redis Data* (Hash, ZSet, List); *Redis Streams* (XReadGroup, XAdd, XLen, etc); and *Redis Publish/Subscribe*.
6
+ **HotMesh** is a wrapper for Redis that exposes concepts like ‘activities’, ‘workflows’, and 'jobs'. Behind the scenes, it uses *Redis Data* (Hash, ZSet, List); *Redis Streams* (XReadGroup, XAdd, XLen, etc); and *Redis Publish/Subscribe*.
7
7
 
8
- It's still Redis in the background, but the information flow is reversed. Instead of your functions calling Redis (e.g., for caching a document), Redis governs your function execution. If your microservice container goes down or your function simply fails, HotMesh will restore function state at the point of failure and retry until it succeeds.
8
+ It's still Redis in the background, but your functions are run as *reentrant processes* and are executed in a distributed environment, with all the benefits of a distributed system, including fault tolerance, scalability, and high availability.
9
+
10
+ Write functions in your own preferred style, and let Redis govern their execution with its unmatched performance.
9
11
 
10
12
  ## Install
11
13
  [![npm version](https://badge.fury.io/js/%40hotmeshio%2Fhotmesh.svg)](https://badge.fury.io/js/%40hotmeshio%2Fhotmesh)
@@ -97,10 +99,12 @@ Redis governance delivers more than just reliability. Externalizing state fundam
97
99
  - `random` | Generate a deterministic random number that can be used in a reentrant process workflow (replaces `Math.random()`).
98
100
  - `executeChild` | Call another durable function and await the response. *Design sophisticated, multi-process solutions by leveraging this command.*
99
101
  - `startChild` | Call another durable function, but do not await the response.
100
- - `set` | Set a value (e.g, `set('name', 'value')`)
101
- - `get` | Get a value (e.g, `get('name')`)
102
+ - `set` | Set one or more name/value pairs (e.g, `set('name1', 'value1', 'name1', 'value2')`)
103
+ - `get` | Get a single value by name(e.g, `get('name')`)
104
+ - `mget` | Get multiple values by name (e.g, `get('name1', 'name2')`)
105
+ - `del` | Delete one or more entries by name and return the number deleted (e.g, `del('name1', 'name1')`)
102
106
  - `incr` | Increment (or decrement) a number (e.g, `incr('name', -99)`)
103
- - `mult` | Multiply (or divide) a number (e.g, `mult('name', 12)`)
107
+ - `mult` | Multiply a number (e.g, `mult('name', 12)`)
104
108
 
105
109
  Refer to the [hotmeshio/samples-typescript](https://github.com/hotmeshio/samples-typescript) repo for usage examples.
106
110
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -32,7 +32,7 @@ class MeshOSService {
32
32
  */
33
33
  static mintGuid() {
34
34
  const my = new this();
35
- return `${my.search?.prefix?.[0]}${(0, nanoid_1.nanoid)()}}`;
35
+ return `${my.search?.prefix?.[0]}${(0, nanoid_1.nanoid)()}`;
36
36
  }
37
37
  /**
38
38
  * Creates an FT search index
@@ -206,16 +206,16 @@ class MeshOSService {
206
206
  return `${prefixedFieldName}:"${value}"`;
207
207
  case 'NUMERIC':
208
208
  let range = '';
209
- if (is.startsWith('=')) {
209
+ if (is.startsWith('=')) { //equal
210
210
  range = `[${value} ${value}]`;
211
211
  }
212
- else if (is === '<') {
212
+ else if (is.startsWith('<')) { //less than or equal
213
213
  range = `[-inf ${value}]`;
214
214
  }
215
- else if (is === '>') {
215
+ else if (is.startsWith('>')) { //greater than or equal
216
216
  range = `[${value} +inf]`;
217
217
  }
218
- else if (is === '[]') {
218
+ else if (is === '[]') { //between
219
219
  range = `[${value[0]} ${value[1]}]`;
220
220
  }
221
221
  return `${prefixedFieldName}:${range}`;
@@ -23,9 +23,10 @@ export declare class Search {
23
23
  * calling any method that produces side effects (changes the value)
24
24
  */
25
25
  getSearchSessionGuid(): string;
26
- set(key: string, value: string): Promise<void>;
26
+ set(...args: string[]): Promise<void>;
27
27
  get(key: string): Promise<string>;
28
- del(key: string): Promise<void>;
28
+ mget(...args: string[]): Promise<string[]>;
29
+ del(...args: string[]): Promise<number | void>;
29
30
  incr(key: string, val: number): Promise<number>;
30
31
  mult(key: string, val: number): Promise<number>;
31
32
  }
@@ -59,12 +59,17 @@ class Search {
59
59
  //return the search session as it would exist in the search session index
60
60
  return `${this.searchSessionId}-${this.searchSessionIndex++}-`;
61
61
  }
62
- async set(key, value) {
62
+ async set(...args) {
63
63
  const ssGuid = this.getSearchSessionGuid();
64
64
  const ssGuidValue = Number(await this.store.exec('HINCRBYFLOAT', this.jobId, ssGuid, '1'));
65
65
  if (ssGuidValue === 1) {
66
- //only allowed to set a value the first time
67
- await this.store.exec('HSET', this.jobId, this.safeKey(key), value.toString());
66
+ const safeArgs = [];
67
+ for (let i = 0; i < args.length; i += 2) {
68
+ const key = this.safeKey(args[i]);
69
+ const value = args[i + 1].toString();
70
+ safeArgs.push(key, value);
71
+ }
72
+ await this.store.exec('HSET', this.jobId, ...safeArgs);
68
73
  }
69
74
  }
70
75
  async get(key) {
@@ -76,11 +81,29 @@ class Search {
76
81
  return '';
77
82
  }
78
83
  }
79
- async del(key) {
84
+ async mget(...args) {
85
+ const safeArgs = [];
86
+ for (let i = 0; i < args.length; i++) {
87
+ safeArgs.push(this.safeKey(args[i]));
88
+ }
89
+ try {
90
+ return await this.store.exec('HMGET', this.jobId, ...safeArgs);
91
+ }
92
+ catch (err) {
93
+ this.hotMeshClient.logger.error('durable-search-mget-error', { err });
94
+ return [];
95
+ }
96
+ }
97
+ async del(...args) {
80
98
  const ssGuid = this.getSearchSessionGuid();
81
99
  const ssGuidValue = Number(await this.store.exec('HINCRBYFLOAT', this.jobId, ssGuid, '1'));
82
100
  if (ssGuidValue === 1) {
83
- await this.store.exec('HDEL', this.jobId, this.safeKey(key));
101
+ const safeArgs = [];
102
+ for (let i = 0; i < args.length; i++) {
103
+ safeArgs.push(this.safeKey(args[i]));
104
+ }
105
+ const response = await this.store.exec('HDEL', this.jobId, ...safeArgs);
106
+ return isNaN(response) ? undefined : Number(response);
84
107
  }
85
108
  }
86
109
  async incr(key, val) {
@@ -84,7 +84,7 @@ type WorkerConfig = {
84
84
  };
85
85
  type FindWhereQuery = {
86
86
  field: string;
87
- is: string;
87
+ is: '=' | '==' | '>=' | '<=' | '[]';
88
88
  value: string | boolean | number | [number, number];
89
89
  type?: string;
90
90
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -118,7 +118,7 @@ export class MeshOSService {
118
118
  */
119
119
  static mintGuid(): string {
120
120
  const my = new this();
121
- return `${my.search?.prefix?.[0]}${nanoid()}}`;
121
+ return `${my.search?.prefix?.[0]}${nanoid()}`;
122
122
  }
123
123
 
124
124
  /**
@@ -309,13 +309,13 @@ export class MeshOSService {
309
309
  return `${prefixedFieldName}:"${value}"`;
310
310
  case 'NUMERIC':
311
311
  let range = '';
312
- if (is.startsWith('=')) {
312
+ if (is.startsWith('=')) { //equal
313
313
  range = `[${value} ${value}]`;
314
- } else if (is === '<') {
314
+ } else if (is.startsWith('<')) { //less than or equal
315
315
  range = `[-inf ${value}]`;
316
- } else if (is === '>') {
316
+ } else if (is.startsWith('>')) { //greater than or equal
317
317
  range = `[${value} +inf]`;
318
- } else if (is === '[]') {
318
+ } else if (is === '[]') { //between
319
319
  range = `[${value[0]} ${value[1]}]`
320
320
  }
321
321
  return `${prefixedFieldName}:${range}`;
@@ -69,12 +69,17 @@ export class Search {
69
69
  return `${this.searchSessionId}-${this.searchSessionIndex++}-`;
70
70
  }
71
71
 
72
- async set(key: string, value: string): Promise<void> {
72
+ async set(...args: string[]): Promise<void> {
73
73
  const ssGuid = this.getSearchSessionGuid();
74
74
  const ssGuidValue = Number(await this.store.exec('HINCRBYFLOAT', this.jobId, ssGuid, '1') as string);
75
75
  if (ssGuidValue === 1) {
76
- //only allowed to set a value the first time
77
- await this.store.exec('HSET', this.jobId, this.safeKey(key), value.toString());
76
+ const safeArgs: string[] = [];
77
+ for (let i = 0; i < args.length; i += 2) {
78
+ const key = this.safeKey(args[i]);
79
+ const value = args[i+1].toString();
80
+ safeArgs.push(key, value);
81
+ }
82
+ await this.store.exec('HSET', this.jobId, ...safeArgs);
78
83
  }
79
84
  }
80
85
 
@@ -87,11 +92,29 @@ export class Search {
87
92
  }
88
93
  }
89
94
 
90
- async del(key: string): Promise<void> {
95
+ async mget(...args: string[]): Promise<string[]> {
96
+ const safeArgs: string[] = [];
97
+ for (let i = 0; i < args.length; i++) {
98
+ safeArgs.push(this.safeKey(args[i]));
99
+ }
100
+ try {
101
+ return await this.store.exec('HMGET', this.jobId, ...safeArgs) as string[];
102
+ } catch (err) {
103
+ this.hotMeshClient.logger.error('durable-search-mget-error', { err });
104
+ return [];
105
+ }
106
+ }
107
+
108
+ async del(...args: string[]): Promise<number | void> {
91
109
  const ssGuid = this.getSearchSessionGuid();
92
110
  const ssGuidValue = Number(await this.store.exec('HINCRBYFLOAT', this.jobId, ssGuid, '1') as string);
93
111
  if (ssGuidValue === 1) {
94
- await this.store.exec('HDEL', this.jobId, this.safeKey(key));
112
+ const safeArgs: string[] = [];
113
+ for (let i = 0; i < args.length; i++) {
114
+ safeArgs.push(this.safeKey(args[i]));
115
+ }
116
+ const response = await this.store.exec('HDEL', this.jobId, ...safeArgs);
117
+ return isNaN(response as unknown as number) ? undefined : Number(response);
95
118
  }
96
119
  }
97
120
 
package/types/durable.ts CHANGED
@@ -95,7 +95,7 @@ type WorkerConfig = {
95
95
 
96
96
  type FindWhereQuery = {
97
97
  field: string;
98
- is: string;
98
+ is: '=' | '==' | '>=' | '<=' | '[]';
99
99
  value: string | boolean | number | [number, number];
100
100
  type?: string; //default is TEXT
101
101
  }