@hotmeshio/hotmesh 0.1.0 → 0.1.1

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.
@@ -1,5 +1,6 @@
1
1
  import { LogLevel } from '../types/logger';
2
2
  export declare const HMSH_LOGLEVEL: LogLevel;
3
+ export declare const HMSH_IS_CLUSTER: boolean;
3
4
  export declare const HMSH_CODE_SUCCESS = 200;
4
5
  export declare const HMSH_CODE_PENDING = 202;
5
6
  export declare const HMSH_CODE_NOTFOUND = 404;
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HMSH_GUID_SIZE = exports.HMSH_SCOUT_INTERVAL_SECONDS = exports.HMSH_FIDELITY_SECONDS = exports.HMSH_EXPIRE_DURATION = exports.HMSH_XPENDING_COUNT = exports.HMSH_XCLAIM_COUNT = exports.HMSH_XCLAIM_DELAY_MS = exports.HMSH_BLOCK_TIME_MS = exports.HMSH_DURABLE_EXP_BACKOFF = exports.HMSH_DURABLE_MAX_INTERVAL = exports.HMSH_DURABLE_MAX_ATTEMPTS = exports.HMSH_GRADUATED_INTERVAL_MS = exports.HMSH_MAX_TIMEOUT_MS = exports.HMSH_MAX_RETRIES = exports.HMSH_EXPIRE_JOB_SECONDS = exports.HMSH_OTT_WAIT_TIME = exports.HMSH_ACTIVATION_MAX_RETRY = exports.HMSH_QUORUM_DELAY_MS = exports.HMSH_QUORUM_ROLLCALL_CYCLES = exports.HMSH_STATUS_UNKNOWN = exports.HMSH_CODE_DURABLE_RETRYABLE = exports.HMSH_CODE_DURABLE_FATAL = exports.HMSH_CODE_DURABLE_MAXED = exports.HMSH_CODE_DURABLE_TIMEOUT = exports.HMSH_CODE_DURABLE_WAIT = exports.HMSH_CODE_DURABLE_PROXY = exports.HMSH_CODE_DURABLE_CHILD = exports.HMSH_CODE_DURABLE_ALL = exports.HMSH_CODE_DURABLE_SLEEP = exports.HMSH_CODE_UNACKED = exports.HMSH_CODE_TIMEOUT = exports.HMSH_CODE_UNKNOWN = exports.HMSH_CODE_INTERRUPT = exports.HMSH_CODE_NOTFOUND = exports.HMSH_CODE_PENDING = exports.HMSH_CODE_SUCCESS = exports.HMSH_LOGLEVEL = void 0;
3
+ exports.HMSH_GUID_SIZE = exports.HMSH_SCOUT_INTERVAL_SECONDS = exports.HMSH_FIDELITY_SECONDS = exports.HMSH_EXPIRE_DURATION = exports.HMSH_XPENDING_COUNT = exports.HMSH_XCLAIM_COUNT = exports.HMSH_XCLAIM_DELAY_MS = exports.HMSH_BLOCK_TIME_MS = exports.HMSH_DURABLE_EXP_BACKOFF = exports.HMSH_DURABLE_MAX_INTERVAL = exports.HMSH_DURABLE_MAX_ATTEMPTS = exports.HMSH_GRADUATED_INTERVAL_MS = exports.HMSH_MAX_TIMEOUT_MS = exports.HMSH_MAX_RETRIES = exports.HMSH_EXPIRE_JOB_SECONDS = exports.HMSH_OTT_WAIT_TIME = exports.HMSH_ACTIVATION_MAX_RETRY = exports.HMSH_QUORUM_DELAY_MS = exports.HMSH_QUORUM_ROLLCALL_CYCLES = exports.HMSH_STATUS_UNKNOWN = exports.HMSH_CODE_DURABLE_RETRYABLE = exports.HMSH_CODE_DURABLE_FATAL = exports.HMSH_CODE_DURABLE_MAXED = exports.HMSH_CODE_DURABLE_TIMEOUT = exports.HMSH_CODE_DURABLE_WAIT = exports.HMSH_CODE_DURABLE_PROXY = exports.HMSH_CODE_DURABLE_CHILD = exports.HMSH_CODE_DURABLE_ALL = exports.HMSH_CODE_DURABLE_SLEEP = exports.HMSH_CODE_UNACKED = exports.HMSH_CODE_TIMEOUT = exports.HMSH_CODE_UNKNOWN = exports.HMSH_CODE_INTERRUPT = exports.HMSH_CODE_NOTFOUND = exports.HMSH_CODE_PENDING = exports.HMSH_CODE_SUCCESS = exports.HMSH_IS_CLUSTER = exports.HMSH_LOGLEVEL = void 0;
4
4
  // HOTMESH SYSTEM
5
5
  exports.HMSH_LOGLEVEL = process.env.HMSH_LOGLEVEL || 'info';
6
+ exports.HMSH_IS_CLUSTER = process.env.HMSH_IS_CLUSTER === 'true';
6
7
  // HOTMESH STATUS CODES
7
8
  exports.HMSH_CODE_SUCCESS = 200;
8
9
  exports.HMSH_CODE_PENDING = 202;
@@ -8,6 +8,7 @@ import { SystemHealth } from '../types/quorum';
8
8
  export declare function getSystemHealth(): Promise<SystemHealth>;
9
9
  export declare function sleepFor(ms: number): Promise<unknown>;
10
10
  export declare function sleepImmediate(): Promise<void>;
11
+ export declare function deepCopy<T>(obj: T): T;
11
12
  export declare function guid(size?: number): string;
12
13
  export declare function deterministicRandom(seed: number): number;
13
14
  export declare function identifyRedisType(redisInstance: any): 'redis' | 'ioredis' | null;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.restoreHierarchy = exports.getValueByPath = exports.getIndexedHash = exports.getSymVal = exports.getSymKey = exports.formatISODate = exports.getTimeSeries = exports.getSubscriptionTopic = exports.findSubscriptionForTrigger = exports.findTopKey = exports.XSleepFor = exports.matchesStatus = exports.matchesStatusCode = exports.identifyRedisTypeFromClass = exports.polyfill = exports.identifyRedisType = exports.deterministicRandom = exports.guid = exports.sleepImmediate = exports.sleepFor = exports.getSystemHealth = void 0;
6
+ exports.restoreHierarchy = exports.getValueByPath = exports.getIndexedHash = exports.getSymVal = exports.getSymKey = exports.formatISODate = exports.getTimeSeries = exports.getSubscriptionTopic = exports.findSubscriptionForTrigger = exports.findTopKey = exports.XSleepFor = exports.matchesStatus = exports.matchesStatusCode = exports.identifyRedisTypeFromClass = exports.polyfill = exports.identifyRedisType = exports.deterministicRandom = exports.guid = exports.deepCopy = exports.sleepImmediate = exports.sleepFor = exports.getSystemHealth = void 0;
7
7
  const os_1 = __importDefault(require("os"));
8
8
  const systeminformation_1 = __importDefault(require("systeminformation"));
9
9
  const nanoid_1 = require("nanoid");
@@ -50,6 +50,10 @@ function sleepImmediate() {
50
50
  return new Promise((resolve) => setImmediate(resolve));
51
51
  }
52
52
  exports.sleepImmediate = sleepImmediate;
53
+ function deepCopy(obj) {
54
+ return JSON.parse(JSON.stringify(obj));
55
+ }
56
+ exports.deepCopy = deepCopy;
53
57
  function guid(size = enums_1.HMSH_GUID_SIZE) {
54
58
  return `H` + (0, nanoid_1.nanoid)(size);
55
59
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -175,7 +175,7 @@ class Activity {
175
175
  }
176
176
  mapJobData() {
177
177
  if (this.config.job?.maps) {
178
- const mapper = new mapper_1.MapperService(this.config.job.maps, this.context);
178
+ const mapper = new mapper_1.MapperService((0, utils_1.deepCopy)(this.config.job.maps), this.context);
179
179
  const output = mapper.mapRules();
180
180
  if (output) {
181
181
  for (const key in output) {
@@ -205,14 +205,14 @@ class Activity {
205
205
  }
206
206
  mapInputData() {
207
207
  if (this.config.input?.maps) {
208
- const mapper = new mapper_1.MapperService(this.config.input.maps, this.context);
208
+ const mapper = new mapper_1.MapperService((0, utils_1.deepCopy)(this.config.input.maps), this.context);
209
209
  this.context.data = mapper.mapRules();
210
210
  }
211
211
  }
212
212
  mapOutputData() {
213
213
  //activity YAML may include output map data that produces/extends activity output data.
214
214
  if (this.config.output?.maps) {
215
- const mapper = new mapper_1.MapperService(this.config.output.maps, this.context);
215
+ const mapper = new mapper_1.MapperService((0, utils_1.deepCopy)(this.config.output.maps), this.context);
216
216
  const actOutData = mapper.mapRules();
217
217
  const activityId = this.metadata.aid;
218
218
  const data = { ...this.context[activityId].output, ...actOutData };
@@ -13,6 +13,12 @@ declare class IORedisStoreService extends StoreService<RedisClientType, RedisMul
13
13
  logger: ILogger;
14
14
  serializer: Serializer;
15
15
  constructor(redisClient: RedisClientType);
16
+ /**
17
+ * When in cluster mode, the getMulti wrapper only
18
+ * sends commands to the same node/shard.
19
+ * All other commands are sent simultaneously
20
+ * using Promise.all and are then collated
21
+ */
16
22
  getMulti(): RedisMultiType;
17
23
  exec(...args: any[]): Promise<string | string[] | string[][]>;
18
24
  hGetAllResult(result: any): any;
@@ -7,7 +7,89 @@ class IORedisStoreService extends index_1.StoreService {
7
7
  constructor(redisClient) {
8
8
  super(redisClient);
9
9
  }
10
+ /**
11
+ * When in cluster mode, the getMulti wrapper only
12
+ * sends commands to the same node/shard.
13
+ * All other commands are sent simultaneously
14
+ * using Promise.all and are then collated
15
+ */
10
16
  getMulti() {
17
+ const my = this;
18
+ if (process.env.HMSH_IS_CLUSTER === 'true') {
19
+ const commands = [];
20
+ const addCommand = (command, args) => {
21
+ commands.push({ command, args });
22
+ return multiInstance;
23
+ };
24
+ const multiInstance = {
25
+ sendCommand(command) {
26
+ return my.redisClient.sendCommand(command);
27
+ },
28
+ async exec() {
29
+ if (commands.length === 0)
30
+ return [];
31
+ const sameCommand = commands.every(cmd => cmd.command === commands[0].command);
32
+ if (sameCommand) {
33
+ const multi = my.redisClient.multi();
34
+ commands.forEach(cmd => multi[cmd.command](...cmd.args));
35
+ const results = await multi.exec();
36
+ return results.map(item => item); // Extract the results from multi.exec response format
37
+ }
38
+ else {
39
+ return Promise.all(commands.map(cmd => my.redisClient[cmd.command](...cmd.args)));
40
+ }
41
+ },
42
+ xadd(key, id, fields, message) {
43
+ return addCommand('xadd', [key, id, fields, message]);
44
+ },
45
+ xack(key, group, id) {
46
+ return addCommand('xack', [key, group, id]);
47
+ },
48
+ xdel(key, id) {
49
+ return addCommand('xdel', [key, id]);
50
+ },
51
+ xlen(key) {
52
+ return addCommand('xlen', [key]);
53
+ },
54
+ xpending(key, group, start, end, count, consumer) {
55
+ return addCommand('xpending', [key, group, start, end, count, consumer]);
56
+ },
57
+ xclaim(key, group, consumer, minIdleTime, id, ...args) {
58
+ return addCommand('xclaim', [key, group, consumer, minIdleTime, id, ...args]);
59
+ },
60
+ hdel(key, itemId) {
61
+ return addCommand('hdel', [key, itemId]);
62
+ },
63
+ hget(key, itemId) {
64
+ return addCommand('hget', [key, itemId]);
65
+ },
66
+ hgetall(key) {
67
+ return addCommand('hgetall', [key]);
68
+ },
69
+ hincrbyfloat(key, itemId, value) {
70
+ return addCommand('hincrbyfloat', [key, itemId, value]);
71
+ },
72
+ hmget(key, itemIds) {
73
+ return addCommand('hmget', [key, itemIds]);
74
+ },
75
+ hset(key, values) {
76
+ return addCommand('hset', [key, values]);
77
+ },
78
+ lrange(key, start, end) {
79
+ return addCommand('lrange', [key, start, end]);
80
+ },
81
+ rpush(key, value) {
82
+ return addCommand('rpush', [key, value]);
83
+ },
84
+ zadd(...args) {
85
+ return addCommand('zadd', args);
86
+ },
87
+ xgroup(command, key, groupName, id, mkStream) {
88
+ return addCommand('xgroup', [command, key, groupName, id, mkStream]);
89
+ }
90
+ };
91
+ return multiInstance;
92
+ }
11
93
  return this.redisClient.multi();
12
94
  }
13
95
  async exec(...args) {
@@ -14,6 +14,12 @@ declare class RedisStoreService extends StoreService<RedisClientType, RedisMulti
14
14
  serializer: Serializer;
15
15
  commands: Record<string, string>;
16
16
  constructor(redisClient: RedisClientType);
17
+ /**
18
+ * When in cluster mode, the getMulti wrapper only
19
+ * sends commands to the same node/shard.
20
+ * All other commands are sent simultaneously
21
+ * using Promise.all and are then collated
22
+ */
17
23
  getMulti(): RedisMultiType;
18
24
  exec(...args: any[]): Promise<string | string[] | string[][]>;
19
25
  publish(keyType: KeyType.QUORUM, message: Record<string, any>, appId: string, engineId?: string): Promise<boolean>;
@@ -36,7 +36,123 @@ class RedisStoreService extends index_1.StoreService {
36
36
  xlen: 'XLEN',
37
37
  };
38
38
  }
39
+ /**
40
+ * When in cluster mode, the getMulti wrapper only
41
+ * sends commands to the same node/shard.
42
+ * All other commands are sent simultaneously
43
+ * using Promise.all and are then collated
44
+ */
39
45
  getMulti() {
46
+ const my = this;
47
+ if (process.env.HMSH_IS_CLUSTER === 'true') {
48
+ const commands = [];
49
+ const addCommand = (command, args) => {
50
+ commands.push({ command: command.toUpperCase(), args });
51
+ return multiInstance;
52
+ };
53
+ const multiInstance = {
54
+ sendCommand(command, ...args) {
55
+ return my.redisClient.sendCommand([command, ...args]);
56
+ },
57
+ async exec() {
58
+ if (commands.length === 0)
59
+ return [];
60
+ const sameCommand = commands.every(cmd => cmd.command === commands[0].command);
61
+ if (sameCommand) {
62
+ const multi = my.redisClient.multi();
63
+ commands.forEach(cmd => {
64
+ if (cmd.command === 'ZADD') {
65
+ return multi.ZADD(cmd.args[0], cmd.args[1], cmd.args[2]);
66
+ }
67
+ return multi[cmd.command](...cmd.args);
68
+ });
69
+ const results = await multi.exec();
70
+ return results.map(item => item); // Extract the results from multi.exec response format
71
+ }
72
+ else {
73
+ return Promise.all(commands.map(cmd => {
74
+ if (cmd.command === 'ZADD') {
75
+ return my.redisClient.ZADD(cmd.args[0], cmd.args[1], cmd.args[2]);
76
+ }
77
+ return my.redisClient[cmd.command](...cmd.args);
78
+ }));
79
+ }
80
+ },
81
+ XADD(key, id, fields, message) {
82
+ return addCommand('XADD', [key, id, fields, message]);
83
+ },
84
+ XACK(key, group, id) {
85
+ return addCommand('XACK', [key, group, id]);
86
+ },
87
+ XDEL(key, id) {
88
+ return addCommand('XDEL', [key, id]);
89
+ },
90
+ XLEN(key) {
91
+ return addCommand('XLEN', [key]);
92
+ },
93
+ XCLAIM(key, group, consumer, minIdleTime, id, ...args) {
94
+ return addCommand('XCLAIM', [key, group, consumer, minIdleTime, id, ...args]);
95
+ },
96
+ XPENDING(key, group, start, end, count, consumer) {
97
+ return addCommand('XPENDING', [key, group, start, end, count, consumer]);
98
+ },
99
+ HDEL(key, itemId) {
100
+ return addCommand('HDEL', [key, itemId]);
101
+ },
102
+ HGET(key, itemId) {
103
+ return addCommand('HGET', [key, itemId]);
104
+ },
105
+ HGETALL(key) {
106
+ return addCommand('HGETALL', [key]);
107
+ },
108
+ HINCRBYFLOAT(key, itemId, value) {
109
+ return addCommand('HINCRBYFLOAT', [key, itemId, value]);
110
+ },
111
+ HMGET(key, itemIds) {
112
+ return addCommand('HMGET', [key, itemIds]);
113
+ },
114
+ HSET(key, values) {
115
+ return addCommand('HSET', [key, values]);
116
+ },
117
+ LRANGE(key, start, end) {
118
+ return addCommand('LRANGE', [key, start, end]);
119
+ },
120
+ RPUSH(key, items) {
121
+ return addCommand('RPUSH', [key, items]);
122
+ },
123
+ ZADD(key, args, opts) {
124
+ return addCommand('ZADD', [key, args, opts]);
125
+ },
126
+ XGROUP(command, key, groupName, id, mkStream) {
127
+ return addCommand('XGROUP', [command, key, groupName, id, mkStream]);
128
+ },
129
+ DEL: function (key) {
130
+ throw new Error('Function not implemented.');
131
+ },
132
+ EXISTS: function (key) {
133
+ throw new Error('Function not implemented.');
134
+ },
135
+ HMPUSH: function (key, values) {
136
+ throw new Error('Function not implemented.');
137
+ },
138
+ LPUSH: function (key, items) {
139
+ throw new Error('Function not implemented.');
140
+ },
141
+ SET: function (key, value) {
142
+ throw new Error('Function not implemented.');
143
+ },
144
+ ZRANGE_WITHSCORES: function (key, start, end) {
145
+ throw new Error('Function not implemented.');
146
+ },
147
+ ZRANK: function (key, member) {
148
+ throw new Error('Function not implemented.');
149
+ },
150
+ ZSCORE: function (key, value) {
151
+ throw new Error('Function not implemented.');
152
+ }
153
+ };
154
+ return multiInstance;
155
+ }
40
156
  return this.redisClient.multi();
41
157
  }
42
158
  async exec(...args) {
@@ -168,7 +168,6 @@ class StoreService {
168
168
  const range = await this.redisClient[this.commands.hget](rangeKey, target);
169
169
  const [lowerLimitString] = range.split(':');
170
170
  if (lowerLimitString === '?') {
171
- console.log('symbol range collision!!!', tryCount);
172
171
  await (0, utils_1.sleepFor)(tryCount * 1000);
173
172
  if (tryCount < 5) {
174
173
  return this.reserveSymbolRange(target, size, type, tryCount + 1);
@@ -89,6 +89,86 @@ type WorkflowContext = {
89
89
  */
90
90
  raw: StreamData;
91
91
  };
92
+ /**
93
+ * The schema for the full-text-search (RediSearch) index.
94
+ */
95
+ export type WorkflowSearchSchema = Record<string, {
96
+ /**
97
+ * The FT.SEARCH field type. One of: TEXT, NUMERIC, TAG. TEXT is
98
+ * most expensive, but also most expressive.
99
+ */
100
+ type: 'TEXT' | 'NUMERIC' | 'TAG';
101
+ /**
102
+ * FT.SEARCH SORTABLE field. If true, results may be sorted according to this field
103
+ * @default false
104
+ */
105
+ sortable?: boolean;
106
+ /**
107
+ * FT.SEARCH NOSTEM field. applies to TEXT fields types.
108
+ * If true, the text field index will not stem words
109
+ * @default false
110
+ */
111
+ nostem?: boolean;
112
+ /**
113
+ * FT.SEARCH NOINDEX field. If true and if the field is sortable, the field will aid
114
+ * in sorting results but not be directly indexed as a standalone
115
+ * @default false
116
+ */
117
+ noindex?: boolean;
118
+ /**
119
+ * if true, the field is indexed and searchable within the FT.SEARCH index
120
+ * This is different from `noindex` which is FT.SEARCH specific and relates
121
+ * to sorting and indexing. This is a general flag for the field that will
122
+ * enable or disable indexing and searching entirely. Use for fields with
123
+ * absolutely no meaning to query or sorting but which are important
124
+ * nonetheless as part of the data record that is saved and returned.
125
+ * @default true
126
+ */
127
+ indexed?: boolean;
128
+ /**
129
+ * An array of possible values for the field
130
+ */
131
+ examples?: string[];
132
+ /**
133
+ * The 'nilable' setting may NOT be set to `true` for
134
+ * NUMBER types as it causes an indexing error;
135
+ * consider a custom (e.g., negative number) value to represent
136
+ * `null` if desired for a NUMERIC field.
137
+ * Set to true only if the field is a TEXT or TAG type and
138
+ * you wish to save the string `null` as a value to search
139
+ * on (the tag, {null}, or the string, (null)
140
+ * @default false
141
+ */
142
+ nilable?: boolean;
143
+ /**
144
+ * possible scalar/primitive types for the field. Use when
145
+ * serializing and restoring data to ensure the field is
146
+ * properly typed. If not provided, the field will be
147
+ * treated as a string.
148
+ */
149
+ primitive?: 'string' | 'number' | 'boolean' | 'array' | 'object';
150
+ /**
151
+ * if true, the field is required to be present in the data record
152
+ * @default false
153
+ */
154
+ required?: boolean;
155
+ /**
156
+ * an enumerated list of allowed values; if field is nilable, it is implied
157
+ * and therefore not necessary to include `null` in the list
158
+ * @default []
159
+ */
160
+ enum?: string[];
161
+ /**
162
+ * a regular expression pattern for the field
163
+ * @default '.*'
164
+ * @example '^[a-zA-Z0-9_]*$'
165
+ */
166
+ pattern?: string;
167
+ /**
168
+ * literal value to use for the indexed field name (without including the standard underscore (_) prefix isolate)
169
+ */
170
+ fieldName?: string;
171
+ }>;
92
172
  type WorkflowSearchOptions = {
93
173
  /** FT index name (myapp:myindex) */
94
174
  index?: string;
@@ -101,83 +181,7 @@ type WorkflowSearchOptions = {
101
181
  * key will be used as the indexed field name with an underscore prefix.
102
182
  *
103
183
  */
104
- schema?: Record<string, {
105
- /**
106
- * The FT.SEARCH field type. One of: TEXT, NUMERIC, TAG. TEXT is
107
- * most expensive, but also most expressive.
108
- */
109
- type: 'TEXT' | 'NUMERIC' | 'TAG';
110
- /**
111
- * FT.SEARCH SORTABLE field. If true, results may be sorted according to this field
112
- * @default false
113
- */
114
- sortable?: boolean;
115
- /**
116
- * FT.SEARCH NOSTEM field. applies to TEXT fields types.
117
- * If true, the text field index will not stem words
118
- * @default false
119
- */
120
- nostem?: boolean;
121
- /**
122
- * FT.SEARCH NOINDEX field. If true and if the field is sortable, the field will aid
123
- * in sorting results but not be directly indexed as a standalone
124
- * @default false
125
- */
126
- noindex?: boolean;
127
- /**
128
- * if true, the field is indexed and searchable within the FT.SEARCH index
129
- * This is different from `noindex` which is FT.SEARCH specific and relates
130
- * to sorting and indexing. This is a general flag for the field that will
131
- * enable or disable indexing and searching entirely. Use for fields with
132
- * absolutely no meaning to query or sorting but which are important
133
- * nonetheless as part of the data record that is saved and returned.
134
- * @default true
135
- */
136
- indexed?: boolean;
137
- /**
138
- * An array of possible values for the field
139
- */
140
- examples?: string[];
141
- /**
142
- * The 'nilable' setting may NOT be set to `true` for
143
- * NUMBER types as it causes an indexing error;
144
- * consider a custom (e.g., negative number) value to represent
145
- * `null` if desired for a NUMERIC field.
146
- * Set to true only if the field is a TEXT or TAG type and
147
- * you wish to save the string `null` as a value to search
148
- * on (the tag, {null}, or the string, (null)
149
- * @default false
150
- */
151
- nilable?: boolean;
152
- /**
153
- * possible scalar/primitive types for the field. Use when
154
- * serializing and restoring data to ensure the field is
155
- * properly typed. If not provided, the field will be
156
- * treated as a string.
157
- */
158
- primitive?: 'string' | 'number' | 'boolean' | 'array' | 'object';
159
- /**
160
- * if true, the field is required to be present in the data record
161
- * @default false
162
- */
163
- required?: boolean;
164
- /**
165
- * an enumerated list of allowed values; if field is nilable, it is implied
166
- * and therefore not necessary to include `null` in the list
167
- * @default []
168
- */
169
- enum?: string[];
170
- /**
171
- * a regular expression pattern for the field
172
- * @default '.*'
173
- * @example '^[a-zA-Z0-9_]*$'
174
- */
175
- pattern?: string;
176
- /**
177
- * literal value to use for the indexed field name (without including the standard underscore (_) prefix isolate)
178
- */
179
- fieldName?: string;
180
- }>;
184
+ schema?: WorkflowSearchSchema;
181
185
  /** Additional data as a key-value record */
182
186
  data?: StringStringType;
183
187
  };
@@ -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, ChildResponseType, ClientConfig, ContextType, ConnectionConfig, Connection, ProxyResponseType, ProxyType, Registry, SignalOptions, FindJobsOptions, FindOptions, FindWhereOptions, FindWhereQuery, HookOptions, SearchResults, WorkflowConfig, WorkerConfig, WorkerOptions, WorkflowContext, WorkflowSearchOptions, WorkflowDataType, WorkflowOptions, } from './durable';
6
+ export { ActivityConfig, ActivityWorkflowDataType, ChildResponseType, ClientConfig, ContextType, ConnectionConfig, Connection, ProxyResponseType, ProxyType, Registry, SignalOptions, FindJobsOptions, FindOptions, FindWhereOptions, FindWhereQuery, HookOptions, SearchResults, WorkflowConfig, WorkerConfig, WorkerOptions, WorkflowContext, WorkflowSearchOptions, WorkflowSearchSchema, WorkflowDataType, WorkflowOptions, } from './durable';
7
7
  export { DurableChildErrorType, DurableProxyErrorType, DurableSleepErrorType, DurableWaitForAllErrorType, DurableWaitForErrorType, } from './error';
8
8
  export { ActivityAction, DependencyExport, DurableJobExport, ExportCycles, ExportItem, ExportOptions, ExportTransitions, JobAction, JobExport, JobActionExport, JobTimeline, } from './exporter';
9
9
  export { HookCondition, HookConditions, HookGate, HookInterface, HookRule, HookRules, HookSignal, } from './hook';
@@ -72,12 +72,17 @@ interface RedisRedisMultiType {
72
72
  HGET(key: string, itemId: string): this;
73
73
  HGETALL(key: string): this;
74
74
  HINCRBYFLOAT(key: string, itemId: string, value: number): this;
75
- HMGET(key: string, itemIds: string[]): Promise<string[]>;
75
+ HMPUSH(key: string, values: Record<string, string>): this;
76
+ RPUSH(key: string, items: string[]): this;
77
+ HMGET(key: string, itemIds: string[]): this;
76
78
  HSET(key: string, values: Record<string, string>): this;
77
79
  LPUSH(key: string, items: string[]): this;
78
80
  LRANGE(key: string, start: number, end: number): this;
79
81
  RPUSH(key: string, items: string[]): this;
80
82
  SET(key: string, value: string): this;
83
+ XCLAIM(key: string, group: string, consumer: string, minIdleTime: number, id: string, ...args: string[]): this;
84
+ XGROUP(command: 'CREATE' | string, key: string, groupName: string, id: string, mkStream?: 'MKSTREAM'): this;
85
+ XPENDING(key: string, group: string, start?: string, end?: string, count?: number, consumer?: string): this;
81
86
  ZADD(key: string, values: {
82
87
  score: string;
83
88
  value: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
package/types/durable.ts CHANGED
@@ -108,6 +108,100 @@ type WorkflowContext = {
108
108
  raw: StreamData;
109
109
  };
110
110
 
111
+ /**
112
+ * The schema for the full-text-search (RediSearch) index.
113
+ */
114
+ export type WorkflowSearchSchema = Record<
115
+ string,
116
+ {
117
+ /**
118
+ * The FT.SEARCH field type. One of: TEXT, NUMERIC, TAG. TEXT is
119
+ * most expensive, but also most expressive.
120
+ */
121
+ type: 'TEXT' | 'NUMERIC' | 'TAG';
122
+
123
+ /**
124
+ * FT.SEARCH SORTABLE field. If true, results may be sorted according to this field
125
+ * @default false
126
+ */
127
+ sortable?: boolean;
128
+
129
+ /**
130
+ * FT.SEARCH NOSTEM field. applies to TEXT fields types.
131
+ * If true, the text field index will not stem words
132
+ * @default false
133
+ */
134
+ nostem?: boolean;
135
+
136
+ /**
137
+ * FT.SEARCH NOINDEX field. If true and if the field is sortable, the field will aid
138
+ * in sorting results but not be directly indexed as a standalone
139
+ * @default false
140
+ */
141
+ noindex?: boolean;
142
+
143
+ /**
144
+ * if true, the field is indexed and searchable within the FT.SEARCH index
145
+ * This is different from `noindex` which is FT.SEARCH specific and relates
146
+ * to sorting and indexing. This is a general flag for the field that will
147
+ * enable or disable indexing and searching entirely. Use for fields with
148
+ * absolutely no meaning to query or sorting but which are important
149
+ * nonetheless as part of the data record that is saved and returned.
150
+ * @default true
151
+ */
152
+ indexed?: boolean;
153
+
154
+ /**
155
+ * An array of possible values for the field
156
+ */
157
+ examples?: string[];
158
+
159
+ /**
160
+ * The 'nilable' setting may NOT be set to `true` for
161
+ * NUMBER types as it causes an indexing error;
162
+ * consider a custom (e.g., negative number) value to represent
163
+ * `null` if desired for a NUMERIC field.
164
+ * Set to true only if the field is a TEXT or TAG type and
165
+ * you wish to save the string `null` as a value to search
166
+ * on (the tag, {null}, or the string, (null)
167
+ * @default false
168
+ */
169
+ nilable?: boolean;
170
+
171
+ /**
172
+ * possible scalar/primitive types for the field. Use when
173
+ * serializing and restoring data to ensure the field is
174
+ * properly typed. If not provided, the field will be
175
+ * treated as a string.
176
+ */
177
+ primitive?: 'string' | 'number' | 'boolean' | 'array' | 'object';
178
+
179
+ /**
180
+ * if true, the field is required to be present in the data record
181
+ * @default false
182
+ */
183
+ required?: boolean;
184
+
185
+ /**
186
+ * an enumerated list of allowed values; if field is nilable, it is implied
187
+ * and therefore not necessary to include `null` in the list
188
+ * @default []
189
+ */
190
+ enum?: string[];
191
+
192
+ /**
193
+ * a regular expression pattern for the field
194
+ * @default '.*'
195
+ * @example '^[a-zA-Z0-9_]*$'
196
+ */
197
+ pattern?: string;
198
+
199
+ /**
200
+ * literal value to use for the indexed field name (without including the standard underscore (_) prefix isolate)
201
+ */
202
+ fieldName?: string;
203
+ }>;
204
+
111
205
  type WorkflowSearchOptions = {
112
206
  /** FT index name (myapp:myindex) */
113
207
  index?: string;
@@ -122,97 +216,7 @@ type WorkflowSearchOptions = {
122
216
  * key will be used as the indexed field name with an underscore prefix.
123
217
  *
124
218
  */
125
- schema?: Record<
126
- string,
127
- {
128
- /**
129
- * The FT.SEARCH field type. One of: TEXT, NUMERIC, TAG. TEXT is
130
- * most expensive, but also most expressive.
131
- */
132
- type: 'TEXT' | 'NUMERIC' | 'TAG';
133
-
134
- /**
135
- * FT.SEARCH SORTABLE field. If true, results may be sorted according to this field
136
- * @default false
137
- */
138
- sortable?: boolean;
139
-
140
- /**
141
- * FT.SEARCH NOSTEM field. applies to TEXT fields types.
142
- * If true, the text field index will not stem words
143
- * @default false
144
- */
145
- nostem?: boolean;
146
-
147
- /**
148
- * FT.SEARCH NOINDEX field. If true and if the field is sortable, the field will aid
149
- * in sorting results but not be directly indexed as a standalone
150
- * @default false
151
- */
152
- noindex?: boolean;
153
-
154
- /**
155
- * if true, the field is indexed and searchable within the FT.SEARCH index
156
- * This is different from `noindex` which is FT.SEARCH specific and relates
157
- * to sorting and indexing. This is a general flag for the field that will
158
- * enable or disable indexing and searching entirely. Use for fields with
159
- * absolutely no meaning to query or sorting but which are important
160
- * nonetheless as part of the data record that is saved and returned.
161
- * @default true
162
- */
163
- indexed?: boolean;
164
-
165
- /**
166
- * An array of possible values for the field
167
- */
168
- examples?: string[];
169
-
170
- /**
171
- * The 'nilable' setting may NOT be set to `true` for
172
- * NUMBER types as it causes an indexing error;
173
- * consider a custom (e.g., negative number) value to represent
174
- * `null` if desired for a NUMERIC field.
175
- * Set to true only if the field is a TEXT or TAG type and
176
- * you wish to save the string `null` as a value to search
177
- * on (the tag, {null}, or the string, (null)
178
- * @default false
179
- */
180
- nilable?: boolean;
181
-
182
- /**
183
- * possible scalar/primitive types for the field. Use when
184
- * serializing and restoring data to ensure the field is
185
- * properly typed. If not provided, the field will be
186
- * treated as a string.
187
- */
188
- primitive?: 'string' | 'number' | 'boolean' | 'array' | 'object';
189
-
190
- /**
191
- * if true, the field is required to be present in the data record
192
- * @default false
193
- */
194
- required?: boolean;
195
-
196
- /**
197
- * an enumerated list of allowed values; if field is nilable, it is implied
198
- * and therefore not necessary to include `null` in the list
199
- * @default []
200
- */
201
- enum?: string[];
202
-
203
- /**
204
- * a regular expression pattern for the field
205
- * @default '.*'
206
- * @example '^[a-zA-Z0-9_]*$'
207
- */
208
- pattern?: string;
209
-
210
- /**
211
- * literal value to use for the indexed field name (without including the standard underscore (_) prefix isolate)
212
- */
213
- fieldName?: string;
214
- }
215
- >;
219
+ schema?: WorkflowSearchSchema;
216
220
 
217
221
  /** Additional data as a key-value record */
218
222
  data?: StringStringType;
package/types/index.ts CHANGED
@@ -44,6 +44,7 @@ export {
44
44
  WorkerOptions,
45
45
  WorkflowContext,
46
46
  WorkflowSearchOptions,
47
+ WorkflowSearchSchema,
47
48
  WorkflowDataType,
48
49
  WorkflowOptions,
49
50
  } from './durable';
package/types/redis.ts CHANGED
@@ -78,12 +78,37 @@ interface RedisRedisMultiType {
78
78
  HGET(key: string, itemId: string): this;
79
79
  HGETALL(key: string): this;
80
80
  HINCRBYFLOAT(key: string, itemId: string, value: number): this;
81
- HMGET(key: string, itemIds: string[]): Promise<string[]>;
81
+ HMPUSH(key: string, values: Record<string, string>): this;
82
+ RPUSH(key: string, items: string[]): this;
83
+ HMGET(key: string, itemIds: string[]): this;
82
84
  HSET(key: string, values: Record<string, string>): this;
83
85
  LPUSH(key: string, items: string[]): this;
84
86
  LRANGE(key: string, start: number, end: number): this;
85
87
  RPUSH(key: string, items: string[]): this;
86
88
  SET(key: string, value: string): this;
89
+ XCLAIM(
90
+ key: string,
91
+ group: string,
92
+ consumer: string,
93
+ minIdleTime: number,
94
+ id: string,
95
+ ...args: string[]
96
+ ): this;
97
+ XGROUP(
98
+ command: 'CREATE' | string,
99
+ key: string,
100
+ groupName: string,
101
+ id: string,
102
+ mkStream?: 'MKSTREAM',
103
+ ): this;
104
+ XPENDING(
105
+ key: string,
106
+ group: string,
107
+ start?: string,
108
+ end?: string,
109
+ count?: number,
110
+ consumer?: string,
111
+ ): this;
87
112
  ZADD(
88
113
  key: string,
89
114
  values: { score: string; value: string },