@hotmeshio/hotmesh 0.2.0 → 0.2.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.
- package/README.md +8 -8
- package/build/index.d.ts +1 -1
- package/build/package.json +1 -1
- package/build/services/engine/index.js +5 -2
- package/build/services/meshcall/index.d.ts +4 -4
- package/build/services/meshcall/index.js +6 -4
- package/build/services/meshdata/index.d.ts +3 -3
- package/build/services/meshdata/index.js +74 -51
- package/build/services/meshflow/workflow.js +5 -2
- package/build/types/index.d.ts +1 -1
- package/build/types/meshcall.d.ts +2 -2
- package/build/types/meshdata.d.ts +2 -2
- package/index.ts +1 -7
- package/package.json +1 -1
- package/types/index.ts +1 -4
- package/types/job.ts +2 -2
- package/types/meshcall.ts +4 -4
- package/types/meshdata.ts +6 -7
- package/types/meshflow.ts +19 -3
package/README.md
CHANGED
|
@@ -13,13 +13,13 @@ You have a Redis instance? Good. You're ready to go.
|
|
|
13
13
|
[Read the Docs](https://hotmeshio.github.io/sdk-typescript/)
|
|
14
14
|
|
|
15
15
|
## MeshCall | Connect Everything
|
|
16
|
-
|
|
16
|
+
[MeshCall](https://hotmeshio.github.io/sdk-typescript/classes/services_meshcall.MeshCall.html) connects your functions to the Redis-backed mesh, exposing them as idempotent endpoints. Function responses are cacheable and functions can even run as idempotent cron jobs. Make blazing fast interservice calls that return in milliseconds without the overhead of HTTP.
|
|
17
17
|
|
|
18
18
|
<details style="padding: .5em">
|
|
19
19
|
<summary style="font-size:1.25em;">Run an idempotent cron job</summary>
|
|
20
20
|
|
|
21
21
|
### Run a Cron
|
|
22
|
-
This example demonstrates an *idempotent* cron that runs every day. The `id` makes each cron job unique and ensures that only one instance runs, despite repeated invocations. *The `cron` method
|
|
22
|
+
This example demonstrates an *idempotent* cron that runs every day. The `id` makes each cron job unique and ensures that only one instance runs, despite repeated invocations. *The `cron` method returns `false` if a workflow is already running with the same `id`.*
|
|
23
23
|
|
|
24
24
|
Optionally set a `delay` and/or set `maxCycles` to limit the number of cycles.
|
|
25
25
|
|
|
@@ -29,8 +29,8 @@ The **MeshCall** module connects any function with a connection to Redis. Functi
|
|
|
29
29
|
import { MeshCall } from '@hotmeshio/hotmesh';
|
|
30
30
|
import * as Redis from 'redis';
|
|
31
31
|
|
|
32
|
-
export const runMyCron = (id: string, interval = '1 day') => {
|
|
33
|
-
MeshCall.cron({
|
|
32
|
+
export const runMyCron = async (id: string, interval = '1 day'): Promise<boolean> => {
|
|
33
|
+
return await MeshCall.cron({
|
|
34
34
|
topic: 'my.cron.function',
|
|
35
35
|
redis: {
|
|
36
36
|
class: Redis,
|
|
@@ -87,8 +87,8 @@ The **MeshCall** module connects any function with a connection to Redis. Functi
|
|
|
87
87
|
import { MeshCall, Types } from '@hotmeshio/hotmesh';
|
|
88
88
|
import * as Redis from 'redis';
|
|
89
89
|
|
|
90
|
-
export const connectMyFunction = () => {
|
|
91
|
-
MeshCall.connect({
|
|
90
|
+
export const connectMyFunction = async () => {
|
|
91
|
+
return await MeshCall.connect({
|
|
92
92
|
topic: 'my.demo.function',
|
|
93
93
|
redis: {
|
|
94
94
|
class: Redis,
|
|
@@ -168,7 +168,7 @@ The **MeshCall** module connects any function with a connection to Redis. Functi
|
|
|
168
168
|
</details>
|
|
169
169
|
|
|
170
170
|
## MeshFlow | Transactional Workflow
|
|
171
|
-
|
|
171
|
+
[MeshFlow](https://hotmeshio.github.io/sdk-typescript/classes/services_meshflow.MeshFlow.html) is a drop-in replacement for [Temporal.io](https://temporal.io). If you need to orchestrate your functions as durable workflows, MeshFlow combines the popular Temporal SDK with Redis' *in-memory execution speed*.
|
|
172
172
|
|
|
173
173
|
<details style="padding: .5em">
|
|
174
174
|
<summary style="font-size:1.25em;">Orchestrate unpredictable activities</summary>
|
|
@@ -503,7 +503,7 @@ This example calls an activity and then sleeps for a week. It runs indefinitely
|
|
|
503
503
|
</details>
|
|
504
504
|
|
|
505
505
|
## MeshData | Transactional Analytics
|
|
506
|
-
|
|
506
|
+
[MeshData](https://hotmeshio.github.io/sdk-typescript/classes/services_meshdata.MeshData.html) extends the **MeshFlow** service, combining data record concepts and transactional workflow principles into a single *Operational Data Layer*.
|
|
507
507
|
|
|
508
508
|
Deployments with the Redis `FT.SEARCH` module enabled can use the **MeshData** module to merge [OLTP](https://en.wikipedia.org/wiki/Online_transaction_processing) and [OLAP](https://en.wikipedia.org/wiki/Online_analytical_processing) operations into a hybrid transactional/analytics ([HTAP](https://en.wikipedia.org/wiki/Hybrid_transactional/analytical_processing)) system.
|
|
509
509
|
|
package/build/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@ import { MeshData } from './services/meshdata';
|
|
|
3
3
|
import { MeshFlow } from './services/meshflow';
|
|
4
4
|
import { HotMesh } from './services/hotmesh';
|
|
5
5
|
import { HotMeshConfig } from './types/hotmesh';
|
|
6
|
-
export { HotMesh, MeshCall, MeshData, MeshFlow, HotMeshConfig
|
|
6
|
+
export { HotMesh, MeshCall, MeshData, MeshFlow, HotMeshConfig };
|
|
7
7
|
export * as Types from './types';
|
package/build/package.json
CHANGED
|
@@ -108,7 +108,10 @@ class EngineService {
|
|
|
108
108
|
return await this.fetchAndVerifyVID(vid, count + 1);
|
|
109
109
|
}
|
|
110
110
|
else {
|
|
111
|
-
this.logger.error('engine-vid-resolution-error', {
|
|
111
|
+
this.logger.error('engine-vid-resolution-error', {
|
|
112
|
+
id: vid.id,
|
|
113
|
+
guid: this.guid,
|
|
114
|
+
});
|
|
112
115
|
}
|
|
113
116
|
}
|
|
114
117
|
return vid;
|
|
@@ -132,7 +135,7 @@ class EngineService {
|
|
|
132
135
|
else {
|
|
133
136
|
return await this.fetchAndVerifyVID({
|
|
134
137
|
id: this.appId,
|
|
135
|
-
version: this.apps?.[this.appId].version
|
|
138
|
+
version: this.apps?.[this.appId].version,
|
|
136
139
|
});
|
|
137
140
|
}
|
|
138
141
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { HotMesh } from
|
|
2
|
-
import { MeshCallConnectParams, MeshCallCronParams, MeshCallExecParams, MeshCallFlushParams, MeshCallInterruptParams } from
|
|
3
|
-
import { RedisConfig } from
|
|
1
|
+
import { HotMesh } from '../hotmesh';
|
|
2
|
+
import { MeshCallConnectParams, MeshCallCronParams, MeshCallExecParams, MeshCallFlushParams, MeshCallInterruptParams } from '../../types/meshcall';
|
|
3
|
+
import { RedisConfig } from '../../types';
|
|
4
4
|
declare class MeshCall {
|
|
5
5
|
static workers: Map<string, HotMesh | Promise<HotMesh>>;
|
|
6
6
|
static engines: Map<string, HotMesh | Promise<HotMesh>>;
|
|
7
7
|
static connections: Map<string, any>;
|
|
8
8
|
constructor();
|
|
9
|
-
static findFirstMatching(workers: Map<string,
|
|
9
|
+
static findFirstMatching(workers: Map<string, HotMesh | Promise<HotMesh>>, namespace: string, config: RedisConfig): Promise<HotMesh | void>;
|
|
10
10
|
static getHotMeshClient: (namespace: string, connection: RedisConfig) => Promise<HotMesh>;
|
|
11
11
|
static verifyWorkflowActive(hotMesh: HotMesh, appId?: string, count?: number): Promise<boolean>;
|
|
12
12
|
static activateWorkflow(hotMesh: HotMesh, appId?: string, version?: string): Promise<void>;
|
|
@@ -64,7 +64,7 @@ class MeshCall {
|
|
|
64
64
|
if (!hotMeshInstance) {
|
|
65
65
|
hotMeshInstance = await MeshCall.findFirstMatching(MeshCall.engines, namespace, redis);
|
|
66
66
|
if (!hotMeshInstance) {
|
|
67
|
-
hotMeshInstance = await MeshCall.getHotMeshClient(namespace, redis);
|
|
67
|
+
hotMeshInstance = (await MeshCall.getHotMeshClient(namespace, redis));
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
return hotMeshInstance;
|
|
@@ -89,7 +89,7 @@ class MeshCall {
|
|
|
89
89
|
data: { response },
|
|
90
90
|
};
|
|
91
91
|
},
|
|
92
|
-
}
|
|
92
|
+
},
|
|
93
93
|
],
|
|
94
94
|
});
|
|
95
95
|
MeshCall.workers.set(targetTopic, hotMeshWorker);
|
|
@@ -141,7 +141,9 @@ class MeshCall {
|
|
|
141
141
|
});
|
|
142
142
|
const TOPIC = `${params.namespace ?? key_1.HMNS}.cron`;
|
|
143
143
|
const interval = (0, ms_1.default)(params.options.interval) / 1000;
|
|
144
|
-
const delay = params.options.delay
|
|
144
|
+
const delay = params.options.delay
|
|
145
|
+
? (0, ms_1.default)(params.options.delay) / 1000
|
|
146
|
+
: undefined;
|
|
145
147
|
const maxCycles = params.options.maxCycles ?? 1000000;
|
|
146
148
|
const hotMeshInstance = await MeshCall.getInstance(params.namespace, params.redis);
|
|
147
149
|
await hotMeshInstance.pub(TOPIC, {
|
|
@@ -163,7 +165,7 @@ class MeshCall {
|
|
|
163
165
|
}
|
|
164
166
|
static async interrupt(params) {
|
|
165
167
|
const hotMeshInstance = await MeshCall.getInstance(params.namespace, params.redis);
|
|
166
|
-
await hotMeshInstance.interrupt(`${params.namespace ?? key_1.HMNS}.cron`, params.options.id, {
|
|
168
|
+
await hotMeshInstance.interrupt(`${params.namespace ?? key_1.HMNS}.cron`, params.options.id, { throw: false, expire: 60 });
|
|
167
169
|
}
|
|
168
170
|
static async shutdown() {
|
|
169
171
|
for (const [_, hotMeshInstance] of MeshCall.workers) {
|
|
@@ -10,7 +10,7 @@ declare class MeshData {
|
|
|
10
10
|
connectionSignatures: StringStringType;
|
|
11
11
|
redisOptions: RedisOptions;
|
|
12
12
|
redisClass: RedisClass;
|
|
13
|
-
instances: Map<string,
|
|
13
|
+
instances: Map<string, Promise<HotMesh> | HotMesh>;
|
|
14
14
|
search: WorkflowSearchOptions;
|
|
15
15
|
static workflow: {
|
|
16
16
|
sleep: typeof import("../meshflow/workflow").WorkflowService.sleepFor;
|
|
@@ -44,7 +44,7 @@ declare class MeshData {
|
|
|
44
44
|
pub: (message: QuorumMessage, options?: SubscriptionOptions) => Promise<void>;
|
|
45
45
|
unsub: (callback: QuorumMessageCallback, options?: SubscriptionOptions) => Promise<void>;
|
|
46
46
|
};
|
|
47
|
-
connect<T>({ entity, target, options }: ConnectionInput<T>): Promise<boolean>;
|
|
47
|
+
connect<T>({ entity, target, options, }: ConnectionInput<T>): Promise<boolean>;
|
|
48
48
|
bindCallOptions(args: any[], options: ConnectOptions, callOptions?: CallOptions): StringAnyType;
|
|
49
49
|
pauseForTTL<T>(result: T, options: CallOptions): Promise<void>;
|
|
50
50
|
publishDone<T>(result: T, hotMesh: HotMesh, options: CallOptions): Promise<void>;
|
|
@@ -53,7 +53,7 @@ declare class MeshData {
|
|
|
53
53
|
signal(guid: string, payload: StringAnyType, namespace?: string): Promise<string>;
|
|
54
54
|
rollCall(options?: RollCallOptions): Promise<QuorumProfile[]>;
|
|
55
55
|
throttle(options: ThrottleOptions): Promise<boolean>;
|
|
56
|
-
hook({ entity, id, hookEntity, hookArgs, options }: HookInput): Promise<string>;
|
|
56
|
+
hook({ entity, id, hookEntity, hookArgs, options, }: HookInput): Promise<string>;
|
|
57
57
|
exec<T>({ entity, args, options }: ExecInput): Promise<T>;
|
|
58
58
|
info(entity: string, id: string, options?: CallOptions): Promise<JobOutput>;
|
|
59
59
|
export(entity: string, id: string, options?: ExportOptions, namespace?: string): Promise<MeshFlowJobExport>;
|
|
@@ -41,7 +41,7 @@ class MeshData {
|
|
|
41
41
|
unsub: async (callback, options = {}) => {
|
|
42
42
|
const hotMesh = await this.getHotMesh(options.namespace || 'durable');
|
|
43
43
|
await hotMesh.quorum?.unsub(callback);
|
|
44
|
-
}
|
|
44
|
+
},
|
|
45
45
|
};
|
|
46
46
|
this.redisClass = redisClass;
|
|
47
47
|
this.redisOptions = redisOptions;
|
|
@@ -50,9 +50,7 @@ class MeshData {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
validate(entity) {
|
|
53
|
-
if (entity.includes(':') ||
|
|
54
|
-
entity.includes('$') ||
|
|
55
|
-
entity.includes(' ')) {
|
|
53
|
+
if (entity.includes(':') || entity.includes('$') || entity.includes(' ')) {
|
|
56
54
|
throw "Invalid string [':','$',' ' not allowed]";
|
|
57
55
|
}
|
|
58
56
|
}
|
|
@@ -67,25 +65,30 @@ class MeshData {
|
|
|
67
65
|
connection: {
|
|
68
66
|
class: this.redisClass,
|
|
69
67
|
options: this.redisOptions,
|
|
70
|
-
}
|
|
68
|
+
},
|
|
71
69
|
});
|
|
72
70
|
}
|
|
73
71
|
safeKey(key) {
|
|
74
72
|
return `_${key}`;
|
|
75
73
|
}
|
|
76
74
|
arrayToHash(input) {
|
|
77
|
-
const
|
|
78
|
-
const max = rest.length / 2;
|
|
75
|
+
const max = input.length;
|
|
79
76
|
const hashes = [];
|
|
80
|
-
for (let i =
|
|
81
|
-
const fields =
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
77
|
+
for (let i = 1; i < max; i++) {
|
|
78
|
+
const fields = input[i];
|
|
79
|
+
if (Array.isArray(fields)) {
|
|
80
|
+
const hash = {};
|
|
81
|
+
const hashId = input[i - 1];
|
|
82
|
+
for (let j = 0; j < fields.length; j += 2) {
|
|
83
|
+
const fieldKey = fields[j].replace(/^_/, '');
|
|
84
|
+
const fieldValue = fields[j + 1];
|
|
85
|
+
hash[fieldKey] = fieldValue;
|
|
86
|
+
}
|
|
87
|
+
if (typeof hashId === 'string') {
|
|
88
|
+
hash['$'] = hashId;
|
|
89
|
+
}
|
|
90
|
+
hashes.push(hash);
|
|
87
91
|
}
|
|
88
|
-
hashes.push(hash);
|
|
89
92
|
}
|
|
90
93
|
return hashes;
|
|
91
94
|
}
|
|
@@ -114,7 +117,7 @@ class MeshData {
|
|
|
114
117
|
}
|
|
115
118
|
static mintGuid(entity, id) {
|
|
116
119
|
if (!id && !entity) {
|
|
117
|
-
throw
|
|
120
|
+
throw 'Invalid arguments [entity and id are both null]';
|
|
118
121
|
}
|
|
119
122
|
else if (!id) {
|
|
120
123
|
id = hotmesh_1.HotMesh.guid();
|
|
@@ -136,8 +139,8 @@ class MeshData {
|
|
|
136
139
|
redis: {
|
|
137
140
|
class: this.redisClass,
|
|
138
141
|
options: this.redisOptions,
|
|
139
|
-
}
|
|
140
|
-
}
|
|
142
|
+
},
|
|
143
|
+
},
|
|
141
144
|
});
|
|
142
145
|
this.instances.set(namespace, hotMesh);
|
|
143
146
|
hotMesh = await hotMesh;
|
|
@@ -148,17 +151,22 @@ class MeshData {
|
|
|
148
151
|
async mintKey(entity, workflowId, namespace) {
|
|
149
152
|
const handle = await this.getClient().workflow.getHandle(entity, entity, workflowId, namespace);
|
|
150
153
|
const store = handle.hotMesh.engine?.store;
|
|
151
|
-
return store?.mintKey(hotmesh_2.KeyType.JOB_STATE, {
|
|
154
|
+
return store?.mintKey(hotmesh_2.KeyType.JOB_STATE, {
|
|
155
|
+
jobId: workflowId,
|
|
156
|
+
appId: handle.hotMesh.engine?.appId,
|
|
157
|
+
});
|
|
152
158
|
}
|
|
153
|
-
async connect({ entity, target, options = {} }) {
|
|
159
|
+
async connect({ entity, target, options = {}, }) {
|
|
154
160
|
this.validate(entity);
|
|
155
161
|
this.connectionSignatures[entity] = target.toString();
|
|
156
|
-
const targetFunction = {
|
|
162
|
+
const targetFunction = {
|
|
163
|
+
[entity]: async (...args) => {
|
|
157
164
|
const { callOptions } = this.bindCallOptions(args, options);
|
|
158
|
-
const result = await target.apply(target, args);
|
|
165
|
+
const result = (await target.apply(target, args));
|
|
159
166
|
await this.pauseForTTL(result, callOptions);
|
|
160
167
|
return result;
|
|
161
|
-
}
|
|
168
|
+
},
|
|
169
|
+
};
|
|
162
170
|
await meshflow_1.MeshFlow.Worker.create({
|
|
163
171
|
namespace: options.namespace,
|
|
164
172
|
options: options.options,
|
|
@@ -197,11 +205,14 @@ class MeshData {
|
|
|
197
205
|
if (`-${prefix}${workflowDimension}-${counter + 1}-` in replay) {
|
|
198
206
|
return;
|
|
199
207
|
}
|
|
200
|
-
await new Promise(resolve => setImmediate(resolve));
|
|
208
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
201
209
|
options.$guid = options.$guid ?? workflowId;
|
|
202
210
|
const hotMesh = await MeshData.workflow.getHotMesh();
|
|
203
211
|
const store = hotMesh.engine?.store;
|
|
204
|
-
const jobKey = store?.mintKey(hotmesh_2.KeyType.JOB_STATE, {
|
|
212
|
+
const jobKey = store?.mintKey(hotmesh_2.KeyType.JOB_STATE, {
|
|
213
|
+
jobId: options.$guid,
|
|
214
|
+
appId: hotMesh.engine?.appId,
|
|
215
|
+
});
|
|
205
216
|
const jobResponse = ['aAa', '/t', 'aBa', this.toString(result)];
|
|
206
217
|
await store?.exec('HSET', jobKey, ...jobResponse);
|
|
207
218
|
await this.publishDone(result, hotMesh, options);
|
|
@@ -225,24 +236,24 @@ class MeshData {
|
|
|
225
236
|
jid: options.$guid,
|
|
226
237
|
aid: 't1',
|
|
227
238
|
ts: '0',
|
|
228
|
-
js: 0
|
|
239
|
+
js: 0,
|
|
229
240
|
},
|
|
230
241
|
data: {
|
|
231
242
|
done: true,
|
|
232
243
|
response: result,
|
|
233
|
-
workflowId: options.$guid
|
|
234
|
-
}
|
|
235
|
-
}
|
|
244
|
+
workflowId: options.$guid,
|
|
245
|
+
},
|
|
246
|
+
},
|
|
236
247
|
}, hotMesh.engine.appId, `${hotMesh.engine.appId}.executed.${options.$guid}`);
|
|
237
248
|
}
|
|
238
249
|
async flush(entity, id, namespace) {
|
|
239
250
|
const workflowId = MeshData.mintGuid(entity, id);
|
|
240
251
|
await this.getClient().workflow.signal(`flush-${workflowId}`, {}, namespace);
|
|
241
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
252
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
242
253
|
await this.interrupt(entity, id, {
|
|
243
254
|
descend: true,
|
|
244
255
|
suppress: true,
|
|
245
|
-
expire: 1
|
|
256
|
+
expire: 1,
|
|
246
257
|
}, namespace);
|
|
247
258
|
}
|
|
248
259
|
async interrupt(entity, id, options = {}, namespace) {
|
|
@@ -264,7 +275,7 @@ class MeshData {
|
|
|
264
275
|
async throttle(options) {
|
|
265
276
|
return (await this.getHotMesh(options.namespace || 'durable')).throttle(options);
|
|
266
277
|
}
|
|
267
|
-
async hook({ entity, id, hookEntity, hookArgs, options = {} }) {
|
|
278
|
+
async hook({ entity, id, hookEntity, hookArgs, options = {}, }) {
|
|
268
279
|
const workflowId = MeshData.mintGuid(entity, id);
|
|
269
280
|
this.validate(workflowId);
|
|
270
281
|
return await this.getClient().workflow.hook({
|
|
@@ -286,7 +297,7 @@ class MeshData {
|
|
|
286
297
|
if (state?.data?.done) {
|
|
287
298
|
return state.data.response;
|
|
288
299
|
}
|
|
289
|
-
return await handle.result();
|
|
300
|
+
return (await handle.result());
|
|
290
301
|
}
|
|
291
302
|
catch (e) {
|
|
292
303
|
const optionsClone = { ...options };
|
|
@@ -311,7 +322,7 @@ class MeshData {
|
|
|
311
322
|
if (options.await === false) {
|
|
312
323
|
return handle.workflowId;
|
|
313
324
|
}
|
|
314
|
-
return await handle.result();
|
|
325
|
+
return (await handle.result());
|
|
315
326
|
}
|
|
316
327
|
}
|
|
317
328
|
async info(entity, id, options = {}) {
|
|
@@ -330,7 +341,7 @@ class MeshData {
|
|
|
330
341
|
this.validate(workflowId);
|
|
331
342
|
let prefixedFields = [];
|
|
332
343
|
if (Array.isArray(options.fields)) {
|
|
333
|
-
prefixedFields = options.fields.map(field => `_${field}`);
|
|
344
|
+
prefixedFields = options.fields.map((field) => `_${field}`);
|
|
334
345
|
}
|
|
335
346
|
else if (this.search?.schema) {
|
|
336
347
|
prefixedFields = Object.entries(this.search.schema).map(([key, value]) => {
|
|
@@ -353,7 +364,7 @@ class MeshData {
|
|
|
353
364
|
async all(entity, id, options = {}) {
|
|
354
365
|
const rawResponse = await this.raw(entity, id, options);
|
|
355
366
|
const responseObj = {};
|
|
356
|
-
for (
|
|
367
|
+
for (const key in rawResponse) {
|
|
357
368
|
if (key.startsWith('_')) {
|
|
358
369
|
responseObj[key.substring(1)] = rawResponse[key];
|
|
359
370
|
}
|
|
@@ -366,7 +377,7 @@ class MeshData {
|
|
|
366
377
|
const handle = await this.getClient().workflow.getHandle(entity, entity, workflowId, options.namespace);
|
|
367
378
|
const store = handle.hotMesh.engine?.store;
|
|
368
379
|
const jobKey = await this.mintKey(entity, workflowId, options.namespace);
|
|
369
|
-
const rawResponse = await store?.exec('HGETALL', jobKey);
|
|
380
|
+
const rawResponse = (await store?.exec('HGETALL', jobKey));
|
|
370
381
|
const responseObj = {};
|
|
371
382
|
for (let i = 0; i < rawResponse.length; i += 2) {
|
|
372
383
|
responseObj[rawResponse[i]] = rawResponse[i + 1];
|
|
@@ -380,10 +391,10 @@ class MeshData {
|
|
|
380
391
|
const store = handle.hotMesh.engine?.store;
|
|
381
392
|
const jobId = await this.mintKey(entity, workflowId, options.namespace);
|
|
382
393
|
const safeArgs = [];
|
|
383
|
-
for (
|
|
394
|
+
for (const key in options.search?.data) {
|
|
384
395
|
safeArgs.push(this.safeKey(key), options.search?.data[key].toString());
|
|
385
396
|
}
|
|
386
|
-
return await store?.exec('HSET', jobId, ...safeArgs);
|
|
397
|
+
return (await store?.exec('HSET', jobId, ...safeArgs));
|
|
387
398
|
}
|
|
388
399
|
async incr(entity, id, field, amount, options = {}) {
|
|
389
400
|
const workflowId = MeshData.mintGuid(options.prefix ?? entity, id);
|
|
@@ -398,9 +409,9 @@ class MeshData {
|
|
|
398
409
|
const workflowId = MeshData.mintGuid(options.prefix ?? entity, id);
|
|
399
410
|
this.validate(workflowId);
|
|
400
411
|
if (!Array.isArray(options.fields)) {
|
|
401
|
-
throw
|
|
412
|
+
throw 'Invalid arguments [options.fields is not an array]';
|
|
402
413
|
}
|
|
403
|
-
const prefixedFields = options.fields.map(field => `_${field}`);
|
|
414
|
+
const prefixedFields = options.fields.map((field) => `_${field}`);
|
|
404
415
|
const handle = await this.getClient().workflow.getHandle(entity, entity, workflowId, options.namespace);
|
|
405
416
|
const store = handle.hotMesh.engine?.store;
|
|
406
417
|
const jobKey = await this.mintKey(entity, workflowId, options.namespace);
|
|
@@ -409,14 +420,16 @@ class MeshData {
|
|
|
409
420
|
}
|
|
410
421
|
async findJobs(options = {}) {
|
|
411
422
|
const hotMesh = await this.getHotMesh(options.namespace);
|
|
412
|
-
return await hotMesh.engine?.store?.findJobs(options.match, options.limit, options.batch, options.cursor);
|
|
423
|
+
return (await hotMesh.engine?.store?.findJobs(options.match, options.limit, options.batch, options.cursor));
|
|
413
424
|
}
|
|
414
425
|
async find(entity, options, ...args) {
|
|
415
426
|
return await this.getClient().workflow.search(options.taskQueue ?? entity, options.workflowName ?? entity, options.namespace || 'durable', options.index ?? options.search?.index ?? this.search.index ?? '', ...args);
|
|
416
427
|
}
|
|
417
428
|
async findWhere(entity, options) {
|
|
418
429
|
const targetSearch = options.options?.search ?? this.search;
|
|
419
|
-
const args = [
|
|
430
|
+
const args = [
|
|
431
|
+
this.generateSearchQuery(options.query, targetSearch),
|
|
432
|
+
];
|
|
420
433
|
if (options.count) {
|
|
421
434
|
args.push('LIMIT', '0', '0');
|
|
422
435
|
}
|
|
@@ -424,7 +437,7 @@ class MeshData {
|
|
|
424
437
|
args.push('RETURN');
|
|
425
438
|
args.push(((options.return?.length ?? 0) + 1).toString());
|
|
426
439
|
args.push('$');
|
|
427
|
-
options.return?.forEach(returnField => {
|
|
440
|
+
options.return?.forEach((returnField) => {
|
|
428
441
|
if (returnField.startsWith('"')) {
|
|
429
442
|
args.push(returnField.slice(1, -1));
|
|
430
443
|
}
|
|
@@ -450,9 +463,10 @@ class MeshData {
|
|
|
450
463
|
}
|
|
451
464
|
generateSearchQuery(query, search) {
|
|
452
465
|
if (!Array.isArray(query) || query.length === 0) {
|
|
453
|
-
return typeof
|
|
466
|
+
return typeof query === 'string' ? query : '*';
|
|
454
467
|
}
|
|
455
|
-
|
|
468
|
+
const queryString = query
|
|
469
|
+
.map((q) => {
|
|
456
470
|
const { field, is, value, type } = q;
|
|
457
471
|
let prefixedFieldName;
|
|
458
472
|
if (search?.schema && field in search.schema) {
|
|
@@ -490,7 +504,8 @@ class MeshData {
|
|
|
490
504
|
default:
|
|
491
505
|
return '';
|
|
492
506
|
}
|
|
493
|
-
})
|
|
507
|
+
})
|
|
508
|
+
.join(' ');
|
|
494
509
|
return queryString;
|
|
495
510
|
}
|
|
496
511
|
async createSearchIndex(entity, options = {}, searchOptions) {
|
|
@@ -525,17 +540,25 @@ MeshData.workflow = {
|
|
|
525
540
|
await meshflow_1.MeshFlow.workflow.interrupt(jobId, options);
|
|
526
541
|
},
|
|
527
542
|
execChild: async (options = {}) => {
|
|
528
|
-
const pluckOptions = {
|
|
543
|
+
const pluckOptions = {
|
|
544
|
+
...options,
|
|
545
|
+
args: [...options.args, { $type: 'exec' }],
|
|
546
|
+
};
|
|
529
547
|
return meshflow_1.MeshFlow.workflow.execChild(pluckOptions);
|
|
530
548
|
},
|
|
531
549
|
executeChild: async (options = {}) => {
|
|
532
|
-
const pluckOptions = {
|
|
550
|
+
const pluckOptions = {
|
|
551
|
+
...options,
|
|
552
|
+
args: [...options.args, { $type: 'exec' }],
|
|
553
|
+
};
|
|
533
554
|
return meshflow_1.MeshFlow.workflow.execChild(pluckOptions);
|
|
534
555
|
},
|
|
535
556
|
startChild: async (options = {}) => {
|
|
536
|
-
const pluckOptions = {
|
|
557
|
+
const pluckOptions = {
|
|
558
|
+
...options,
|
|
559
|
+
args: [...options.args, { $type: 'exec' }],
|
|
560
|
+
};
|
|
537
561
|
return meshflow_1.MeshFlow.workflow.startChild(pluckOptions);
|
|
538
562
|
},
|
|
539
563
|
};
|
|
540
564
|
MeshData.proxyActivities = meshflow_1.MeshFlow.workflow.proxyActivities;
|
|
541
|
-
;
|
|
@@ -84,7 +84,10 @@ class WorkflowService {
|
|
|
84
84
|
const workflowTopic = store.get('workflowTopic');
|
|
85
85
|
const connection = store.get('connection');
|
|
86
86
|
const namespace = store.get('namespace');
|
|
87
|
-
return await worker_1.WorkerService.getHotMesh(workflowTopic, {
|
|
87
|
+
return await worker_1.WorkerService.getHotMesh(workflowTopic, {
|
|
88
|
+
connection,
|
|
89
|
+
namespace,
|
|
90
|
+
});
|
|
88
91
|
}
|
|
89
92
|
static async execChild(options) {
|
|
90
93
|
const isStartChild = options.await === false;
|
|
@@ -324,7 +327,7 @@ class WorkflowService {
|
|
|
324
327
|
return response;
|
|
325
328
|
}
|
|
326
329
|
static async interrupt(jobId, options = {}) {
|
|
327
|
-
const { workflowTopic, connection, namespace
|
|
330
|
+
const { workflowTopic, connection, namespace } = WorkflowService.getContext();
|
|
328
331
|
const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, {
|
|
329
332
|
connection,
|
|
330
333
|
namespace,
|
package/build/types/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export { ActivityConfig, ActivityWorkflowDataType, ChildResponseType, ClientConf
|
|
|
7
7
|
export { MeshFlowChildErrorType, MeshFlowProxyErrorType, MeshFlowSleepErrorType, MeshFlowWaitForAllErrorType, MeshFlowWaitForErrorType, } from './error';
|
|
8
8
|
export { ActivityAction, DependencyExport, MeshFlowJobExport, ExportCycles, ExportItem, ExportOptions, ExportTransitions, JobAction, JobExport, JobActionExport, JobTimeline, } from './exporter';
|
|
9
9
|
export { HookCondition, HookConditions, HookGate, HookInterface, HookRule, HookRules, HookSignal, } from './hook';
|
|
10
|
-
export { ILogger, LogLevel
|
|
10
|
+
export { ILogger, LogLevel } from './logger';
|
|
11
11
|
export { ExtensionType, JobCompletionOptions, JobData, JobsData, JobInterruptOptions, JobMetadata, JobOutput, JobState, JobStatus, PartialJobState, } from './job';
|
|
12
12
|
export { MappingStatements } from './map';
|
|
13
13
|
export { Pipe, PipeContext, PipeItem, PipeItems, PipeObject, ReduceObject, } from './pipe';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { HookOptions, WorkflowConfig, WorkflowSearchOptions } from
|
|
2
|
-
import { StringStringType } from
|
|
1
|
+
import { HookOptions, WorkflowConfig, WorkflowSearchOptions } from './meshflow';
|
|
2
|
+
import { StringStringType } from './serializer';
|
|
3
3
|
export type CallOptions = {
|
|
4
4
|
id?: string;
|
|
5
5
|
ttl?: string;
|
package/index.ts
CHANGED
|
@@ -4,12 +4,6 @@ import { MeshFlow } from './services/meshflow';
|
|
|
4
4
|
import { HotMesh } from './services/hotmesh';
|
|
5
5
|
import { HotMeshConfig } from './types/hotmesh';
|
|
6
6
|
|
|
7
|
-
export {
|
|
8
|
-
HotMesh,
|
|
9
|
-
MeshCall,
|
|
10
|
-
MeshData,
|
|
11
|
-
MeshFlow,
|
|
12
|
-
HotMeshConfig,
|
|
13
|
-
};
|
|
7
|
+
export { HotMesh, MeshCall, MeshData, MeshFlow, HotMeshConfig };
|
|
14
8
|
|
|
15
9
|
export * as Types from './types';
|
package/package.json
CHANGED
package/types/index.ts
CHANGED
package/types/job.ts
CHANGED
|
@@ -115,7 +115,7 @@ type ExtensionType = {
|
|
|
115
115
|
* If a `resume` signal is sent before the specified number of seconds,
|
|
116
116
|
* the job will resume as normal, transitioning to the adjacent children
|
|
117
117
|
* of the trigger.
|
|
118
|
-
*
|
|
118
|
+
*
|
|
119
119
|
* If the job is not resumed within the number
|
|
120
120
|
* of seconds specified, the job will be scrubbed. No dependencies
|
|
121
121
|
* are added for a job in a pending state; however, dependencies
|
|
@@ -126,7 +126,7 @@ type ExtensionType = {
|
|
|
126
126
|
/**
|
|
127
127
|
* Workflows that apply a status threshold will be initialized
|
|
128
128
|
* with a status value of 1m - statusThreshold.
|
|
129
|
-
*
|
|
129
|
+
*
|
|
130
130
|
* The value provided should be the count of descendant activities
|
|
131
131
|
* (those that descend from the trigger) that should be allowed to
|
|
132
132
|
* remain open once 'done' event is emitted.
|
package/types/meshcall.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { RedisConfig } from
|
|
2
|
-
import { LogLevel } from
|
|
1
|
+
import { RedisConfig } from './hotmesh';
|
|
2
|
+
import { LogLevel } from './logger';
|
|
3
3
|
|
|
4
4
|
interface MeshCallExecOptions {
|
|
5
5
|
/**
|
|
@@ -9,7 +9,7 @@ interface MeshCallExecOptions {
|
|
|
9
9
|
/**
|
|
10
10
|
* Time to live for the cache key. For example, `1 day`, `1 hour`. Refer to the syntax for the `ms` NPM package.
|
|
11
11
|
*/
|
|
12
|
-
ttl?: string;
|
|
12
|
+
ttl?: string;
|
|
13
13
|
/**
|
|
14
14
|
* If true, the cache will first be flushed and the function will be executed.
|
|
15
15
|
*/
|
|
@@ -99,7 +99,7 @@ interface MeshCallCronOptions {
|
|
|
99
99
|
* Idempotent GUID for the function
|
|
100
100
|
* */
|
|
101
101
|
id: string;
|
|
102
|
-
/**
|
|
102
|
+
/**
|
|
103
103
|
* For example, `1 day`, `1 hour`. Fidelity is generally
|
|
104
104
|
* within 5 seconds. Refer to the syntax for the `ms` NPM package.
|
|
105
105
|
*/
|
package/types/meshdata.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { HookOptions, WorkflowConfig, WorkflowSearchOptions } from
|
|
2
|
-
import { StringStringType } from
|
|
1
|
+
import { HookOptions, WorkflowConfig, WorkflowSearchOptions } from './meshflow';
|
|
2
|
+
import { StringStringType } from './serializer';
|
|
3
3
|
|
|
4
4
|
export type CallOptions = {
|
|
5
5
|
/**
|
|
@@ -74,7 +74,7 @@ export type ConnectOptions = {
|
|
|
74
74
|
/**
|
|
75
75
|
* prefix for the workflowId (defaults to entity value if not provided)
|
|
76
76
|
*/
|
|
77
|
-
prefix?: string;
|
|
77
|
+
prefix?: string;
|
|
78
78
|
/**
|
|
79
79
|
* optional namespace for the the worker; how it appears in Redis (defaults to 'durable')
|
|
80
80
|
*/
|
|
@@ -96,14 +96,14 @@ export type ConnectOptions = {
|
|
|
96
96
|
export type ConnectionInput<T> = {
|
|
97
97
|
/**
|
|
98
98
|
* The connected function's entity identifier
|
|
99
|
-
*
|
|
99
|
+
*
|
|
100
100
|
* @example
|
|
101
101
|
* user
|
|
102
102
|
*/
|
|
103
103
|
entity: string;
|
|
104
104
|
/**
|
|
105
105
|
* The target function reference
|
|
106
|
-
*
|
|
106
|
+
*
|
|
107
107
|
* @example
|
|
108
108
|
* function() { return "hello world" }
|
|
109
109
|
*/
|
|
@@ -122,7 +122,7 @@ export type ConnectionInput<T> = {
|
|
|
122
122
|
* removed by calling `flush`. During this time, the function will remain active and can
|
|
123
123
|
* its state can be augmented by calling `set`, `incr`, `del`, etc OR by calling a
|
|
124
124
|
* transactional 'hook' function.
|
|
125
|
-
*
|
|
125
|
+
*
|
|
126
126
|
* @template T The expected return type of the remote function.
|
|
127
127
|
*/
|
|
128
128
|
export type ExecInput = {
|
|
@@ -177,7 +177,6 @@ export type HookInput = {
|
|
|
177
177
|
options?: Partial<HookOptions>;
|
|
178
178
|
};
|
|
179
179
|
|
|
180
|
-
|
|
181
180
|
export type MeshDataWorkflowOptions = {
|
|
182
181
|
/**
|
|
183
182
|
* The app deployment namespace; how it appears in redis (e.g., 'durable')
|
package/types/meshflow.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { WorkflowHandleService } from '../services/meshflow/handle';
|
|
2
|
+
|
|
2
3
|
import { LogLevel } from './logger';
|
|
3
4
|
import { RedisClass, RedisOptions } from './redis';
|
|
4
5
|
import { StringAnyType, StringStringType } from './serializer';
|
|
@@ -582,10 +583,25 @@ type ChildResponseType<T> = {
|
|
|
582
583
|
|
|
583
584
|
interface ClientWorkflow {
|
|
584
585
|
start(options: WorkflowOptions): Promise<WorkflowHandleService>;
|
|
585
|
-
signal(
|
|
586
|
+
signal(
|
|
587
|
+
signalId: string,
|
|
588
|
+
data: StringAnyType,
|
|
589
|
+
namespace?: string,
|
|
590
|
+
): Promise<string>;
|
|
586
591
|
hook(options: HookOptions): Promise<string>;
|
|
587
|
-
getHandle(
|
|
588
|
-
|
|
592
|
+
getHandle(
|
|
593
|
+
taskQueue: string,
|
|
594
|
+
workflowName: string,
|
|
595
|
+
workflowId: string,
|
|
596
|
+
namespace?: string,
|
|
597
|
+
): Promise<WorkflowHandleService>;
|
|
598
|
+
search(
|
|
599
|
+
taskQueue: string,
|
|
600
|
+
workflowName: string,
|
|
601
|
+
namespace: string | null,
|
|
602
|
+
index: string,
|
|
603
|
+
...query: string[]
|
|
604
|
+
): Promise<string[]>;
|
|
589
605
|
}
|
|
590
606
|
|
|
591
607
|
export {
|