@hotmeshio/hotmesh 0.1.15 → 0.1.17
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 +623 -209
- package/build/index.d.ts +14 -3
- package/build/index.js +17 -4
- package/build/modules/enums.d.ts +12 -12
- package/build/modules/enums.js +15 -25
- package/build/modules/errors.d.ts +16 -16
- package/build/modules/errors.js +28 -28
- package/build/modules/key.d.ts +0 -37
- package/build/modules/key.js +4 -45
- package/build/modules/utils.d.ts +7 -15
- package/build/modules/utils.js +21 -44
- package/build/package.json +18 -15
- package/build/services/activities/activity.d.ts +0 -31
- package/build/services/activities/activity.js +1 -50
- package/build/services/activities/await.js +0 -4
- package/build/services/activities/cycle.d.ts +0 -7
- package/build/services/activities/cycle.js +1 -16
- package/build/services/activities/hook.d.ts +0 -6
- package/build/services/activities/hook.js +2 -12
- package/build/services/activities/interrupt.js +0 -8
- package/build/services/activities/signal.d.ts +0 -6
- package/build/services/activities/signal.js +0 -15
- package/build/services/activities/trigger.d.ts +4 -5
- package/build/services/activities/trigger.js +22 -16
- package/build/services/activities/worker.js +0 -4
- package/build/services/collator/index.d.ts +0 -70
- package/build/services/collator/index.js +1 -91
- package/build/services/compiler/deployer.js +6 -38
- package/build/services/compiler/index.d.ts +0 -15
- package/build/services/compiler/index.js +0 -20
- package/build/services/compiler/validator.d.ts +0 -3
- package/build/services/compiler/validator.js +0 -25
- package/build/services/connector/clients/ioredis.js +0 -2
- package/build/services/connector/clients/redis.js +0 -2
- package/build/services/connector/index.js +0 -2
- package/build/services/engine/index.d.ts +1 -10
- package/build/services/engine/index.js +1 -48
- package/build/services/exporter/index.d.ts +0 -27
- package/build/services/exporter/index.js +0 -33
- package/build/services/hotmesh/index.d.ts +8 -4
- package/build/services/hotmesh/index.js +20 -19
- package/build/services/logger/index.js +0 -2
- package/build/services/mapper/index.d.ts +0 -14
- package/build/services/mapper/index.js +0 -14
- package/build/services/meshcall/index.d.ts +21 -0
- package/build/services/meshcall/index.js +202 -0
- package/build/services/meshcall/schemas/factory.d.ts +2 -0
- package/build/services/meshcall/schemas/factory.js +179 -0
- package/build/services/meshdata/index.d.ts +75 -0
- package/build/services/meshdata/index.js +541 -0
- package/build/services/meshflow/client.d.ts +18 -0
- package/build/services/{durable → meshflow}/client.js +9 -40
- package/build/services/{durable → meshflow}/connection.d.ts +2 -1
- package/build/services/{durable → meshflow}/connection.js +1 -0
- package/build/services/meshflow/exporter.d.ts +29 -0
- package/build/services/{durable → meshflow}/exporter.js +0 -29
- package/build/services/meshflow/handle.d.ts +22 -0
- package/build/services/{durable → meshflow}/handle.js +0 -46
- package/build/services/meshflow/index.d.ts +17 -0
- package/build/services/meshflow/index.js +23 -0
- package/build/services/meshflow/schemas/factory.d.ts +4 -0
- package/build/services/{durable → meshflow}/schemas/factory.js +2 -30
- package/build/services/meshflow/search.d.ts +23 -0
- package/build/services/{durable → meshflow}/search.js +0 -99
- package/build/services/{durable → meshflow}/worker.d.ts +3 -2
- package/build/services/{durable → meshflow}/worker.js +23 -39
- package/build/services/meshflow/workflow.d.ts +27 -0
- package/build/services/{durable → meshflow}/workflow.js +27 -169
- package/build/services/pipe/functions/date.d.ts +0 -7
- package/build/services/pipe/functions/date.js +0 -7
- package/build/services/pipe/functions/math.js +0 -2
- package/build/services/pipe/index.d.ts +0 -15
- package/build/services/pipe/index.js +2 -23
- package/build/services/quorum/index.d.ts +1 -7
- package/build/services/quorum/index.js +0 -21
- package/build/services/reporter/index.d.ts +0 -5
- package/build/services/reporter/index.js +0 -9
- package/build/services/router/index.d.ts +0 -9
- package/build/services/router/index.js +2 -30
- package/build/services/serializer/index.js +6 -23
- package/build/services/store/cache.d.ts +0 -19
- package/build/services/store/cache.js +0 -19
- package/build/services/store/clients/ioredis.d.ts +0 -6
- package/build/services/store/clients/ioredis.js +0 -7
- package/build/services/store/clients/redis.d.ts +0 -6
- package/build/services/store/clients/redis.js +0 -6
- package/build/services/store/index.d.ts +0 -55
- package/build/services/store/index.js +14 -87
- package/build/services/stream/clients/ioredis.js +1 -4
- package/build/services/task/index.d.ts +0 -9
- package/build/services/task/index.js +0 -31
- package/build/services/telemetry/index.d.ts +0 -7
- package/build/services/telemetry/index.js +1 -13
- package/build/services/worker/index.d.ts +1 -4
- package/build/services/worker/index.js +0 -6
- package/build/types/activity.d.ts +0 -81
- package/build/types/error.d.ts +5 -5
- package/build/types/exporter.d.ts +1 -14
- package/build/types/hotmesh.d.ts +4 -12
- package/build/types/hotmesh.js +0 -3
- package/build/types/index.d.ts +5 -3
- package/build/types/index.js +1 -1
- package/build/types/job.d.ts +1 -95
- package/build/types/meshcall.d.ts +54 -0
- package/build/types/meshdata.d.ts +59 -0
- package/build/types/meshdata.js +2 -0
- package/build/types/meshflow.d.ts +202 -0
- package/build/types/meshflow.js +2 -0
- package/build/types/pipe.d.ts +0 -65
- package/build/types/quorum.d.ts +0 -12
- package/build/types/redis.d.ts +0 -6
- package/build/types/stream.d.ts +0 -59
- package/build/types/stream.js +0 -4
- package/index.ts +12 -3
- package/package.json +18 -15
- package/typedoc.json +38 -0
- package/types/error.ts +5 -5
- package/types/exporter.ts +1 -1
- package/types/hotmesh.ts +3 -2
- package/types/index.ts +25 -7
- package/types/job.ts +19 -1
- package/types/meshcall.ts +192 -0
- package/types/meshdata.ts +273 -0
- package/types/{durable.ts → meshflow.ts} +33 -9
- package/build/services/durable/client.d.ts +0 -49
- package/build/services/durable/exporter.d.ts +0 -51
- package/build/services/durable/handle.d.ts +0 -58
- package/build/services/durable/index.d.ts +0 -19
- package/build/services/durable/index.js +0 -25
- package/build/services/durable/schemas/factory.d.ts +0 -33
- package/build/services/durable/search.d.ts +0 -120
- package/build/services/durable/workflow.d.ts +0 -143
- package/build/types/durable.d.ts +0 -467
- /package/build/types/{durable.js → meshcall.js} +0 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var _a;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.MeshData = void 0;
|
|
5
|
+
const meshflow_1 = require("../meshflow");
|
|
6
|
+
const hotmesh_1 = require("../hotmesh");
|
|
7
|
+
const hotmesh_2 = require("../../types/hotmesh");
|
|
8
|
+
class MeshData {
|
|
9
|
+
constructor(redisClass, redisOptions, search) {
|
|
10
|
+
this.connectionSignatures = {};
|
|
11
|
+
this.instances = new Map();
|
|
12
|
+
this.mesh = {
|
|
13
|
+
sub: async (callback, options = {}) => {
|
|
14
|
+
const hotMesh = await this.getHotMesh(options.namespace || 'durable');
|
|
15
|
+
const callbackWrapper = (topic, message) => {
|
|
16
|
+
if (message.type === 'pong' && !message.originator) {
|
|
17
|
+
if (message.profile?.worker_topic) {
|
|
18
|
+
const [entity] = message.profile.worker_topic.split('-');
|
|
19
|
+
if (entity) {
|
|
20
|
+
message.profile.entity = message.entity = entity;
|
|
21
|
+
if (this.connectionSignatures[entity]) {
|
|
22
|
+
message.profile.signature = this.connectionSignatures[entity];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else if (message?.topic) {
|
|
28
|
+
const [entity] = message.topic.split('-');
|
|
29
|
+
if (entity) {
|
|
30
|
+
message.entity = entity;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
callback(topic, message);
|
|
34
|
+
};
|
|
35
|
+
await hotMesh.quorum?.sub(callbackWrapper);
|
|
36
|
+
},
|
|
37
|
+
pub: async (message, options = {}) => {
|
|
38
|
+
const hotMesh = await this.getHotMesh(options.namespace || 'durable');
|
|
39
|
+
await hotMesh.quorum?.pub(message);
|
|
40
|
+
},
|
|
41
|
+
unsub: async (callback, options = {}) => {
|
|
42
|
+
const hotMesh = await this.getHotMesh(options.namespace || 'durable');
|
|
43
|
+
await hotMesh.quorum?.unsub(callback);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
this.redisClass = redisClass;
|
|
47
|
+
this.redisOptions = redisOptions;
|
|
48
|
+
if (search) {
|
|
49
|
+
this.search = search;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
validate(entity) {
|
|
53
|
+
if (entity.includes(':') ||
|
|
54
|
+
entity.includes('$') ||
|
|
55
|
+
entity.includes(' ')) {
|
|
56
|
+
throw "Invalid string [':','$',' ' not allowed]";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async getConnection() {
|
|
60
|
+
return await meshflow_1.MeshFlow.Connection.connect({
|
|
61
|
+
class: this.redisClass,
|
|
62
|
+
options: this.redisOptions,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
getClient() {
|
|
66
|
+
return new meshflow_1.MeshFlow.Client({
|
|
67
|
+
connection: {
|
|
68
|
+
class: this.redisClass,
|
|
69
|
+
options: this.redisOptions,
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
safeKey(key) {
|
|
74
|
+
return `_${key}`;
|
|
75
|
+
}
|
|
76
|
+
arrayToHash(input) {
|
|
77
|
+
const [_count, ...rest] = input;
|
|
78
|
+
const max = rest.length / 2;
|
|
79
|
+
const hashes = [];
|
|
80
|
+
for (let i = 0; i < max * 2; i += 2) {
|
|
81
|
+
const fields = rest[i + 1];
|
|
82
|
+
const hash = { '$': rest[i] };
|
|
83
|
+
for (let j = 0; j < fields.length; j += 2) {
|
|
84
|
+
const fieldKey = fields[j].replace(/^_/, '');
|
|
85
|
+
const fieldValue = fields[j + 1];
|
|
86
|
+
hash[fieldKey] = fieldValue;
|
|
87
|
+
}
|
|
88
|
+
hashes.push(hash);
|
|
89
|
+
}
|
|
90
|
+
return hashes;
|
|
91
|
+
}
|
|
92
|
+
toString(value) {
|
|
93
|
+
switch (typeof value) {
|
|
94
|
+
case 'string':
|
|
95
|
+
break;
|
|
96
|
+
case 'boolean':
|
|
97
|
+
value = value ? '/t' : '/f';
|
|
98
|
+
break;
|
|
99
|
+
case 'number':
|
|
100
|
+
value = '/d' + value.toString();
|
|
101
|
+
break;
|
|
102
|
+
case 'undefined':
|
|
103
|
+
return undefined;
|
|
104
|
+
case 'object':
|
|
105
|
+
if (value === null) {
|
|
106
|
+
value = '/n';
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
value = '/s' + JSON.stringify(value);
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
static mintGuid(entity, id) {
|
|
116
|
+
if (!id && !entity) {
|
|
117
|
+
throw "Invalid arguments [entity and id are both null]";
|
|
118
|
+
}
|
|
119
|
+
else if (!id) {
|
|
120
|
+
id = hotmesh_1.HotMesh.guid();
|
|
121
|
+
}
|
|
122
|
+
else if (entity) {
|
|
123
|
+
entity = `${entity}-`;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
entity = '';
|
|
127
|
+
}
|
|
128
|
+
return `${entity}${id}`;
|
|
129
|
+
}
|
|
130
|
+
async getHotMesh(namespace = 'durable') {
|
|
131
|
+
let hotMesh = await this.instances.get(namespace);
|
|
132
|
+
if (!hotMesh) {
|
|
133
|
+
hotMesh = hotmesh_1.HotMesh.init({
|
|
134
|
+
appId: namespace,
|
|
135
|
+
engine: {
|
|
136
|
+
redis: {
|
|
137
|
+
class: this.redisClass,
|
|
138
|
+
options: this.redisOptions,
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
this.instances.set(namespace, hotMesh);
|
|
143
|
+
hotMesh = await hotMesh;
|
|
144
|
+
this.instances.set(namespace, hotMesh);
|
|
145
|
+
}
|
|
146
|
+
return hotMesh;
|
|
147
|
+
}
|
|
148
|
+
async mintKey(entity, workflowId, namespace) {
|
|
149
|
+
const handle = await this.getClient().workflow.getHandle(entity, entity, workflowId, namespace);
|
|
150
|
+
const store = handle.hotMesh.engine?.store;
|
|
151
|
+
return store?.mintKey(hotmesh_2.KeyType.JOB_STATE, { jobId: workflowId, appId: handle.hotMesh.engine?.appId });
|
|
152
|
+
}
|
|
153
|
+
async connect({ entity, target, options = {} }) {
|
|
154
|
+
this.validate(entity);
|
|
155
|
+
this.connectionSignatures[entity] = target.toString();
|
|
156
|
+
const targetFunction = { [entity]: async (...args) => {
|
|
157
|
+
const { callOptions } = this.bindCallOptions(args, options);
|
|
158
|
+
const result = await target.apply(target, args);
|
|
159
|
+
await this.pauseForTTL(result, callOptions);
|
|
160
|
+
return result;
|
|
161
|
+
} };
|
|
162
|
+
await meshflow_1.MeshFlow.Worker.create({
|
|
163
|
+
namespace: options.namespace,
|
|
164
|
+
options: options.options,
|
|
165
|
+
connection: await this.getConnection(),
|
|
166
|
+
taskQueue: options.taskQueue ?? entity,
|
|
167
|
+
workflow: targetFunction,
|
|
168
|
+
search: options.search,
|
|
169
|
+
});
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
bindCallOptions(args, options, callOptions = {}) {
|
|
173
|
+
if (args.length) {
|
|
174
|
+
const lastArg = args[args.length - 1];
|
|
175
|
+
if (lastArg instanceof Object && lastArg?.$type === 'exec') {
|
|
176
|
+
callOptions = args.pop();
|
|
177
|
+
if (options.ttl === 'infinity') {
|
|
178
|
+
if (!callOptions) {
|
|
179
|
+
callOptions = { ttl: 'infinity' };
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
callOptions.ttl = 'infinity';
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
else if (lastArg instanceof Object && lastArg.$type === 'hook') {
|
|
187
|
+
callOptions = args.pop();
|
|
188
|
+
delete callOptions.ttl;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return { callOptions };
|
|
192
|
+
}
|
|
193
|
+
async pauseForTTL(result, options) {
|
|
194
|
+
if (options?.ttl && options.$type === 'exec') {
|
|
195
|
+
const { counter, replay, workflowDimension, workflowId } = MeshData.workflow.getContext();
|
|
196
|
+
const prefix = options.ttl === 'infinity' ? 'wait' : 'sleep';
|
|
197
|
+
if (`-${prefix}${workflowDimension}-${counter + 1}-` in replay) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
201
|
+
options.$guid = options.$guid ?? workflowId;
|
|
202
|
+
const hotMesh = await MeshData.workflow.getHotMesh();
|
|
203
|
+
const store = hotMesh.engine?.store;
|
|
204
|
+
const jobKey = store?.mintKey(hotmesh_2.KeyType.JOB_STATE, { jobId: options.$guid, appId: hotMesh.engine?.appId });
|
|
205
|
+
const jobResponse = ['aAa', '/t', 'aBa', this.toString(result)];
|
|
206
|
+
await store?.exec('HSET', jobKey, ...jobResponse);
|
|
207
|
+
await this.publishDone(result, hotMesh, options);
|
|
208
|
+
if (options.ttl === 'infinity') {
|
|
209
|
+
await MeshData.workflow.waitFor(`flush-${options.$guid}`);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
await MeshData.workflow.sleepFor(options.ttl);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async publishDone(result, hotMesh, options) {
|
|
217
|
+
await hotMesh.engine?.store?.publish(hotmesh_2.KeyType.QUORUM, {
|
|
218
|
+
type: 'job',
|
|
219
|
+
topic: `${hotMesh.engine.appId}.executed`,
|
|
220
|
+
job: {
|
|
221
|
+
metadata: {
|
|
222
|
+
tpc: `${hotMesh.engine.appId}.execute`,
|
|
223
|
+
app: hotMesh.engine.appId,
|
|
224
|
+
vrs: '1',
|
|
225
|
+
jid: options.$guid,
|
|
226
|
+
aid: 't1',
|
|
227
|
+
ts: '0',
|
|
228
|
+
js: 0
|
|
229
|
+
},
|
|
230
|
+
data: {
|
|
231
|
+
done: true,
|
|
232
|
+
response: result,
|
|
233
|
+
workflowId: options.$guid
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}, hotMesh.engine.appId, `${hotMesh.engine.appId}.executed.${options.$guid}`);
|
|
237
|
+
}
|
|
238
|
+
async flush(entity, id, namespace) {
|
|
239
|
+
const workflowId = MeshData.mintGuid(entity, id);
|
|
240
|
+
await this.getClient().workflow.signal(`flush-${workflowId}`, {}, namespace);
|
|
241
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
242
|
+
await this.interrupt(entity, id, {
|
|
243
|
+
descend: true,
|
|
244
|
+
suppress: true,
|
|
245
|
+
expire: 1
|
|
246
|
+
}, namespace);
|
|
247
|
+
}
|
|
248
|
+
async interrupt(entity, id, options = {}, namespace) {
|
|
249
|
+
const workflowId = MeshData.mintGuid(entity, id);
|
|
250
|
+
try {
|
|
251
|
+
const handle = await this.getClient().workflow.getHandle(entity, entity, workflowId, namespace);
|
|
252
|
+
const hotMesh = handle.hotMesh;
|
|
253
|
+
await hotMesh.interrupt(`${hotMesh.appId}.execute`, workflowId, options);
|
|
254
|
+
}
|
|
255
|
+
catch (e) {
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async signal(guid, payload, namespace) {
|
|
259
|
+
return await this.getClient().workflow.signal(guid, payload, namespace);
|
|
260
|
+
}
|
|
261
|
+
async rollCall(options = {}) {
|
|
262
|
+
return (await this.getHotMesh(options.namespace || 'durable')).rollCall(options.delay || 1000);
|
|
263
|
+
}
|
|
264
|
+
async throttle(options) {
|
|
265
|
+
return (await this.getHotMesh(options.namespace || 'durable')).throttle(options);
|
|
266
|
+
}
|
|
267
|
+
async hook({ entity, id, hookEntity, hookArgs, options = {} }) {
|
|
268
|
+
const workflowId = MeshData.mintGuid(entity, id);
|
|
269
|
+
this.validate(workflowId);
|
|
270
|
+
return await this.getClient().workflow.hook({
|
|
271
|
+
namespace: options.namespace,
|
|
272
|
+
args: [...hookArgs, { ...options, $guid: workflowId, $type: 'hook' }],
|
|
273
|
+
taskQueue: options.taskQueue ?? hookEntity,
|
|
274
|
+
workflowName: hookEntity,
|
|
275
|
+
workflowId: options.workflowId ?? workflowId,
|
|
276
|
+
config: options.config ?? undefined,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
async exec({ entity, args = [], options = {} }) {
|
|
280
|
+
const workflowId = MeshData.mintGuid(options.prefix ?? entity, options.id);
|
|
281
|
+
this.validate(workflowId);
|
|
282
|
+
const client = this.getClient();
|
|
283
|
+
try {
|
|
284
|
+
const handle = await client.workflow.getHandle(entity, entity, workflowId, options.namespace);
|
|
285
|
+
const state = await handle.hotMesh.getState(`${handle.hotMesh.appId}.execute`, handle.workflowId);
|
|
286
|
+
if (state?.data?.done) {
|
|
287
|
+
return state.data.response;
|
|
288
|
+
}
|
|
289
|
+
return await handle.result();
|
|
290
|
+
}
|
|
291
|
+
catch (e) {
|
|
292
|
+
const optionsClone = { ...options };
|
|
293
|
+
delete optionsClone.search;
|
|
294
|
+
delete optionsClone.config;
|
|
295
|
+
const handle = await client.workflow.start({
|
|
296
|
+
args: [...args, { ...optionsClone, $guid: workflowId, $type: 'exec' }],
|
|
297
|
+
taskQueue: options.taskQueue ?? entity,
|
|
298
|
+
workflowName: entity,
|
|
299
|
+
workflowId: options.workflowId ?? workflowId,
|
|
300
|
+
config: options.config ?? undefined,
|
|
301
|
+
search: options.search,
|
|
302
|
+
workflowTrace: options.workflowTrace,
|
|
303
|
+
workflowSpan: options.workflowSpan,
|
|
304
|
+
namespace: options.namespace,
|
|
305
|
+
await: options.await,
|
|
306
|
+
marker: options.marker,
|
|
307
|
+
pending: options.pending,
|
|
308
|
+
expire: options.expire,
|
|
309
|
+
signalIn: options.signalIn,
|
|
310
|
+
});
|
|
311
|
+
if (options.await === false) {
|
|
312
|
+
return handle.workflowId;
|
|
313
|
+
}
|
|
314
|
+
return await handle.result();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
async info(entity, id, options = {}) {
|
|
318
|
+
const workflowId = MeshData.mintGuid(options.prefix ?? entity, id);
|
|
319
|
+
this.validate(workflowId);
|
|
320
|
+
const handle = await this.getClient().workflow.getHandle(options.taskQueue ?? entity, entity, workflowId, options.namespace);
|
|
321
|
+
return await handle.hotMesh.getState(`${handle.hotMesh.appId}.execute`, handle.workflowId);
|
|
322
|
+
}
|
|
323
|
+
async export(entity, id, options, namespace) {
|
|
324
|
+
const workflowId = MeshData.mintGuid(entity, id);
|
|
325
|
+
const handle = await this.getClient().workflow.getHandle(entity, entity, workflowId, namespace);
|
|
326
|
+
return await handle.export(options);
|
|
327
|
+
}
|
|
328
|
+
async get(entity, id, options = {}) {
|
|
329
|
+
const workflowId = MeshData.mintGuid(options.prefix ?? entity, id);
|
|
330
|
+
this.validate(workflowId);
|
|
331
|
+
let prefixedFields = [];
|
|
332
|
+
if (Array.isArray(options.fields)) {
|
|
333
|
+
prefixedFields = options.fields.map(field => `_${field}`);
|
|
334
|
+
}
|
|
335
|
+
else if (this.search?.schema) {
|
|
336
|
+
prefixedFields = Object.entries(this.search.schema).map(([key, value]) => {
|
|
337
|
+
return 'fieldName' in value ? value.fieldName.toString() : `_${key}`;
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
return await this.all(entity, id, options);
|
|
342
|
+
}
|
|
343
|
+
const handle = await this.getClient().workflow.getHandle(entity, entity, workflowId, options.namespace);
|
|
344
|
+
const store = handle.hotMesh.engine?.store;
|
|
345
|
+
const jobKey = await this.mintKey(entity, workflowId, options.namespace);
|
|
346
|
+
const vals = await store?.exec('HMGET', jobKey, ...prefixedFields);
|
|
347
|
+
const result = prefixedFields.reduce((obj, field, index) => {
|
|
348
|
+
obj[field.substring(1)] = vals?.[index];
|
|
349
|
+
return obj;
|
|
350
|
+
}, {});
|
|
351
|
+
return result;
|
|
352
|
+
}
|
|
353
|
+
async all(entity, id, options = {}) {
|
|
354
|
+
const rawResponse = await this.raw(entity, id, options);
|
|
355
|
+
const responseObj = {};
|
|
356
|
+
for (let key in rawResponse) {
|
|
357
|
+
if (key.startsWith('_')) {
|
|
358
|
+
responseObj[key.substring(1)] = rawResponse[key];
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return responseObj;
|
|
362
|
+
}
|
|
363
|
+
async raw(entity, id, options = {}) {
|
|
364
|
+
const workflowId = MeshData.mintGuid(options.prefix ?? entity, id);
|
|
365
|
+
this.validate(workflowId);
|
|
366
|
+
const handle = await this.getClient().workflow.getHandle(entity, entity, workflowId, options.namespace);
|
|
367
|
+
const store = handle.hotMesh.engine?.store;
|
|
368
|
+
const jobKey = await this.mintKey(entity, workflowId, options.namespace);
|
|
369
|
+
const rawResponse = await store?.exec('HGETALL', jobKey);
|
|
370
|
+
const responseObj = {};
|
|
371
|
+
for (let i = 0; i < rawResponse.length; i += 2) {
|
|
372
|
+
responseObj[rawResponse[i]] = rawResponse[i + 1];
|
|
373
|
+
}
|
|
374
|
+
return responseObj;
|
|
375
|
+
}
|
|
376
|
+
async set(entity, id, options = {}) {
|
|
377
|
+
const workflowId = MeshData.mintGuid(options.prefix ?? entity, id);
|
|
378
|
+
this.validate(workflowId);
|
|
379
|
+
const handle = await this.getClient().workflow.getHandle(entity, entity, workflowId, options.namespace);
|
|
380
|
+
const store = handle.hotMesh.engine?.store;
|
|
381
|
+
const jobId = await this.mintKey(entity, workflowId, options.namespace);
|
|
382
|
+
const safeArgs = [];
|
|
383
|
+
for (let key in options.search?.data) {
|
|
384
|
+
safeArgs.push(this.safeKey(key), options.search?.data[key].toString());
|
|
385
|
+
}
|
|
386
|
+
return await store?.exec('HSET', jobId, ...safeArgs);
|
|
387
|
+
}
|
|
388
|
+
async incr(entity, id, field, amount, options = {}) {
|
|
389
|
+
const workflowId = MeshData.mintGuid(options.prefix ?? entity, id);
|
|
390
|
+
this.validate(workflowId);
|
|
391
|
+
const handle = await this.getClient().workflow.getHandle(entity, entity, workflowId, options.namespace);
|
|
392
|
+
const store = handle.hotMesh.engine?.store;
|
|
393
|
+
const jobId = await this.mintKey(entity, workflowId, options.namespace);
|
|
394
|
+
const result = await store?.exec('HINCRBYFLOAT', jobId, this.safeKey(field), amount.toString());
|
|
395
|
+
return Number(result);
|
|
396
|
+
}
|
|
397
|
+
async del(entity, id, options) {
|
|
398
|
+
const workflowId = MeshData.mintGuid(options.prefix ?? entity, id);
|
|
399
|
+
this.validate(workflowId);
|
|
400
|
+
if (!Array.isArray(options.fields)) {
|
|
401
|
+
throw "Invalid arguments [options.fields is not an array]";
|
|
402
|
+
}
|
|
403
|
+
const prefixedFields = options.fields.map(field => `_${field}`);
|
|
404
|
+
const handle = await this.getClient().workflow.getHandle(entity, entity, workflowId, options.namespace);
|
|
405
|
+
const store = handle.hotMesh.engine?.store;
|
|
406
|
+
const jobKey = await this.mintKey(entity, workflowId, options.namespace);
|
|
407
|
+
const count = await store?.exec('HDEL', jobKey, ...prefixedFields);
|
|
408
|
+
return Number(count);
|
|
409
|
+
}
|
|
410
|
+
async findJobs(options = {}) {
|
|
411
|
+
const hotMesh = await this.getHotMesh(options.namespace);
|
|
412
|
+
return await hotMesh.engine?.store?.findJobs(options.match, options.limit, options.batch, options.cursor);
|
|
413
|
+
}
|
|
414
|
+
async find(entity, options, ...args) {
|
|
415
|
+
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
|
+
}
|
|
417
|
+
async findWhere(entity, options) {
|
|
418
|
+
const targetSearch = options.options?.search ?? this.search;
|
|
419
|
+
const args = [this.generateSearchQuery(options.query, targetSearch)];
|
|
420
|
+
if (options.count) {
|
|
421
|
+
args.push('LIMIT', '0', '0');
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
args.push('RETURN');
|
|
425
|
+
args.push(((options.return?.length ?? 0) + 1).toString());
|
|
426
|
+
args.push('$');
|
|
427
|
+
options.return?.forEach(returnField => {
|
|
428
|
+
if (returnField.startsWith('"')) {
|
|
429
|
+
args.push(returnField.slice(1, -1));
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
args.push(`_${returnField}`);
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
if (options.limit) {
|
|
436
|
+
args.push('LIMIT', options.limit.start.toString(), options.limit.size.toString());
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
const FTResults = await this.find(entity, options.options ?? {}, ...args);
|
|
440
|
+
const count = FTResults[0];
|
|
441
|
+
const sargs = `FT.SEARCH ${options.options?.index ?? targetSearch?.index} ${args.join(' ')}`;
|
|
442
|
+
if (options.count) {
|
|
443
|
+
return !isNaN(count) || count > 0 ? count : 0;
|
|
444
|
+
}
|
|
445
|
+
else if (count === 0) {
|
|
446
|
+
return { count, query: sargs, data: [] };
|
|
447
|
+
}
|
|
448
|
+
const hashes = this.arrayToHash(FTResults);
|
|
449
|
+
return { count, query: sargs, data: hashes };
|
|
450
|
+
}
|
|
451
|
+
generateSearchQuery(query, search) {
|
|
452
|
+
if (!Array.isArray(query) || query.length === 0) {
|
|
453
|
+
return typeof (query) === 'string' ? query : '*';
|
|
454
|
+
}
|
|
455
|
+
let queryString = query.map(q => {
|
|
456
|
+
const { field, is, value, type } = q;
|
|
457
|
+
let prefixedFieldName;
|
|
458
|
+
if (search?.schema && field in search.schema) {
|
|
459
|
+
if ('fieldName' in search.schema[field]) {
|
|
460
|
+
prefixedFieldName = `@${search.schema[field].fieldName}`;
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
prefixedFieldName = `@_${field}`;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
prefixedFieldName = `@${field}`;
|
|
468
|
+
}
|
|
469
|
+
const fieldType = search?.schema?.[field]?.type ?? type ?? 'TEXT';
|
|
470
|
+
switch (fieldType) {
|
|
471
|
+
case 'TAG':
|
|
472
|
+
return `${prefixedFieldName}:{${value}}`;
|
|
473
|
+
case 'TEXT':
|
|
474
|
+
return `${prefixedFieldName}:"${value}"`;
|
|
475
|
+
case 'NUMERIC':
|
|
476
|
+
let range = '';
|
|
477
|
+
if (is.startsWith('=')) {
|
|
478
|
+
range = `[${value} ${value}]`;
|
|
479
|
+
}
|
|
480
|
+
else if (is.startsWith('<')) {
|
|
481
|
+
range = `[-inf ${value}]`;
|
|
482
|
+
}
|
|
483
|
+
else if (is.startsWith('>')) {
|
|
484
|
+
range = `[${value} +inf]`;
|
|
485
|
+
}
|
|
486
|
+
else if (is === '[]') {
|
|
487
|
+
range = `[${value[0]} ${value[1]}]`;
|
|
488
|
+
}
|
|
489
|
+
return `${prefixedFieldName}:${range}`;
|
|
490
|
+
default:
|
|
491
|
+
return '';
|
|
492
|
+
}
|
|
493
|
+
}).join(' ');
|
|
494
|
+
return queryString;
|
|
495
|
+
}
|
|
496
|
+
async createSearchIndex(entity, options = {}, searchOptions) {
|
|
497
|
+
const workflowTopic = `${options.taskQueue ?? entity}-${entity}`;
|
|
498
|
+
const hotMeshClient = await this.getClient().getHotMeshClient(workflowTopic, options.namespace);
|
|
499
|
+
return await meshflow_1.MeshFlow.Search.configureSearchIndex(hotMeshClient, searchOptions ?? this.search);
|
|
500
|
+
}
|
|
501
|
+
async listSearchIndexes() {
|
|
502
|
+
const hotMeshClient = await this.getHotMesh();
|
|
503
|
+
return await meshflow_1.MeshFlow.Search.listSearchIndexes(hotMeshClient);
|
|
504
|
+
}
|
|
505
|
+
static async shutdown() {
|
|
506
|
+
await meshflow_1.MeshFlow.shutdown();
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
exports.MeshData = MeshData;
|
|
510
|
+
_a = MeshData;
|
|
511
|
+
MeshData.workflow = {
|
|
512
|
+
sleep: meshflow_1.MeshFlow.workflow.sleepFor,
|
|
513
|
+
sleepFor: meshflow_1.MeshFlow.workflow.sleepFor,
|
|
514
|
+
signal: meshflow_1.MeshFlow.workflow.signal,
|
|
515
|
+
hook: meshflow_1.MeshFlow.workflow.hook,
|
|
516
|
+
waitForSignal: meshflow_1.MeshFlow.workflow.waitFor,
|
|
517
|
+
waitFor: meshflow_1.MeshFlow.workflow.waitFor,
|
|
518
|
+
getHotMesh: meshflow_1.MeshFlow.workflow.getHotMesh,
|
|
519
|
+
random: meshflow_1.MeshFlow.workflow.random,
|
|
520
|
+
search: meshflow_1.MeshFlow.workflow.search,
|
|
521
|
+
getContext: meshflow_1.MeshFlow.workflow.getContext,
|
|
522
|
+
once: meshflow_1.MeshFlow.workflow.once,
|
|
523
|
+
interrupt: async (entity, id, options = {}) => {
|
|
524
|
+
const jobId = MeshData.mintGuid(entity, id);
|
|
525
|
+
await meshflow_1.MeshFlow.workflow.interrupt(jobId, options);
|
|
526
|
+
},
|
|
527
|
+
execChild: async (options = {}) => {
|
|
528
|
+
const pluckOptions = { ...options, args: [...options.args, { $type: 'exec' }] };
|
|
529
|
+
return meshflow_1.MeshFlow.workflow.execChild(pluckOptions);
|
|
530
|
+
},
|
|
531
|
+
executeChild: async (options = {}) => {
|
|
532
|
+
const pluckOptions = { ...options, args: [...options.args, { $type: 'exec' }] };
|
|
533
|
+
return meshflow_1.MeshFlow.workflow.execChild(pluckOptions);
|
|
534
|
+
},
|
|
535
|
+
startChild: async (options = {}) => {
|
|
536
|
+
const pluckOptions = { ...options, args: [...options.args, { $type: 'exec' }] };
|
|
537
|
+
return meshflow_1.MeshFlow.workflow.startChild(pluckOptions);
|
|
538
|
+
},
|
|
539
|
+
};
|
|
540
|
+
MeshData.proxyActivities = meshflow_1.MeshFlow.workflow.proxyActivities;
|
|
541
|
+
;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { HotMesh } from '../hotmesh';
|
|
2
|
+
import { ClientConfig, ClientWorkflow, Connection, WorkflowOptions } from '../../types/meshflow';
|
|
3
|
+
export declare class ClientService {
|
|
4
|
+
connection: Connection;
|
|
5
|
+
options: WorkflowOptions;
|
|
6
|
+
static topics: string[];
|
|
7
|
+
static instances: Map<string, HotMesh | Promise<HotMesh>>;
|
|
8
|
+
constructor(config: ClientConfig);
|
|
9
|
+
getHotMeshClient: (workflowTopic: string | null, namespace?: string) => Promise<HotMesh>;
|
|
10
|
+
static createStream: (hotMeshClient: HotMesh, workflowTopic: string, namespace?: string) => Promise<void>;
|
|
11
|
+
verifyStream: (hotMeshClient: HotMesh, workflowTopic: string, namespace?: string) => Promise<void>;
|
|
12
|
+
search: (hotMeshClient: HotMesh, index: string, query: string[]) => Promise<string[]>;
|
|
13
|
+
workflow: ClientWorkflow;
|
|
14
|
+
deployAndActivate(namespace?: string, version?: string): Promise<void>;
|
|
15
|
+
verifyWorkflowActive(hotMesh: HotMesh, appId?: string, count?: number): Promise<boolean>;
|
|
16
|
+
activateWorkflow(hotMesh: HotMesh, appId?: string, version?: string): Promise<void>;
|
|
17
|
+
static shutdown(): Promise<void>;
|
|
18
|
+
}
|
|
@@ -17,8 +17,6 @@ const factory_1 = require("./schemas/factory");
|
|
|
17
17
|
class ClientService {
|
|
18
18
|
constructor(config) {
|
|
19
19
|
this.getHotMeshClient = async (workflowTopic, namespace) => {
|
|
20
|
-
//namespace isolation requires the connection options to be hashed
|
|
21
|
-
//as multiple intersecting databases can be used by the same service
|
|
22
20
|
const optionsHash = (0, utils_1.hashOptions)(this.connection.options);
|
|
23
21
|
const targetNS = namespace ?? factory_1.APP_ID;
|
|
24
22
|
const connectionNS = `${optionsHash}.${targetNS}`;
|
|
@@ -27,8 +25,7 @@ class ClientService {
|
|
|
27
25
|
await this.verifyWorkflowActive(hotMeshClient, targetNS);
|
|
28
26
|
return hotMeshClient;
|
|
29
27
|
}
|
|
30
|
-
|
|
31
|
-
const hotMeshClient = hotmesh_1.HotMeshService.init({
|
|
28
|
+
const hotMeshClient = hotmesh_1.HotMesh.init({
|
|
32
29
|
appId: targetNS,
|
|
33
30
|
logLevel: enums_1.HMSH_LOGLEVEL,
|
|
34
31
|
engine: {
|
|
@@ -42,11 +39,6 @@ class ClientService {
|
|
|
42
39
|
await this.activateWorkflow(await hotMeshClient, targetNS);
|
|
43
40
|
return hotMeshClient;
|
|
44
41
|
};
|
|
45
|
-
/**
|
|
46
|
-
* It is possible for a client to invoke a workflow without first
|
|
47
|
-
* creating the stream. This method will verify that the stream
|
|
48
|
-
* exists and if not, create it.
|
|
49
|
-
*/
|
|
50
42
|
this.verifyStream = async (hotMeshClient, workflowTopic, namespace) => {
|
|
51
43
|
const optionsHash = (0, utils_1.hashOptions)(this.connection.options);
|
|
52
44
|
const targetNS = namespace ?? factory_1.APP_ID;
|
|
@@ -69,10 +61,8 @@ class ClientService {
|
|
|
69
61
|
const workflowName = options.entity ?? options.workflowName;
|
|
70
62
|
const trc = options.workflowTrace;
|
|
71
63
|
const spn = options.workflowSpan;
|
|
72
|
-
//hotmesh topic is a combination of the durable queue+workflowname
|
|
73
64
|
const workflowTopic = `${taskQueueName}-${workflowName}`;
|
|
74
65
|
const hotMeshClient = await this.getHotMeshClient(workflowTopic, options.namespace);
|
|
75
|
-
//verify that the stream channel exists before enqueueing
|
|
76
66
|
await this.verifyStream(hotMeshClient, workflowTopic, options.namespace);
|
|
77
67
|
const payload = {
|
|
78
68
|
arguments: [...options.args],
|
|
@@ -80,11 +70,11 @@ class ClientService {
|
|
|
80
70
|
expire: options.expire ?? enums_1.HMSH_EXPIRE_JOB_SECONDS,
|
|
81
71
|
signalIn: options.signalIn,
|
|
82
72
|
parentWorkflowId: options.parentWorkflowId,
|
|
83
|
-
workflowId: options.workflowId || hotmesh_1.
|
|
73
|
+
workflowId: options.workflowId || hotmesh_1.HotMesh.guid(),
|
|
84
74
|
workflowTopic: workflowTopic,
|
|
85
|
-
backoffCoefficient: options.config?.backoffCoefficient || enums_1.
|
|
86
|
-
maximumAttempts: options.config?.maximumAttempts || enums_1.
|
|
87
|
-
maximumInterval: (0, ms_1.default)(options.config?.maximumInterval || enums_1.
|
|
75
|
+
backoffCoefficient: options.config?.backoffCoefficient || enums_1.HMSH_MESHFLOW_EXP_BACKOFF,
|
|
76
|
+
maximumAttempts: options.config?.maximumAttempts || enums_1.HMSH_MESHFLOW_MAX_ATTEMPTS,
|
|
77
|
+
maximumInterval: (0, ms_1.default)(options.config?.maximumInterval || enums_1.HMSH_MESHFLOW_MAX_INTERVAL) /
|
|
88
78
|
1000,
|
|
89
79
|
};
|
|
90
80
|
const context = { metadata: { trc, spn }, data: {} };
|
|
@@ -95,35 +85,25 @@ class ClientService {
|
|
|
95
85
|
});
|
|
96
86
|
return new handle_1.WorkflowHandleService(hotMeshClient, workflowTopic, jobId);
|
|
97
87
|
},
|
|
98
|
-
/**
|
|
99
|
-
* send a message to a running workflow that is paused and awaiting the signal
|
|
100
|
-
*/
|
|
101
88
|
signal: async (signalId, data, namespace) => {
|
|
102
89
|
const topic = `${namespace ?? factory_1.APP_ID}.wfs.signal`;
|
|
103
90
|
return await (await this.getHotMeshClient(topic, namespace)).hook(topic, { id: signalId, data });
|
|
104
91
|
},
|
|
105
|
-
/**
|
|
106
|
-
* send a message to spawn an parallel in-process thread of execution
|
|
107
|
-
* with the same job state as the main thread but bound to a different
|
|
108
|
-
* handler function. All job state will be journaled to the same hash
|
|
109
|
-
* as is used by the main thread.
|
|
110
|
-
*/
|
|
111
92
|
hook: async (options) => {
|
|
112
93
|
const workflowTopic = `${options.taskQueue}-${options.workflowName}`;
|
|
113
94
|
const payload = {
|
|
114
95
|
arguments: [...options.args],
|
|
115
96
|
id: options.workflowId,
|
|
116
97
|
workflowTopic,
|
|
117
|
-
backoffCoefficient: options.config?.backoffCoefficient || enums_1.
|
|
118
|
-
maximumAttempts: options.config?.maximumAttempts || enums_1.
|
|
119
|
-
maximumInterval: (0, ms_1.default)(options.config?.maximumInterval || enums_1.
|
|
98
|
+
backoffCoefficient: options.config?.backoffCoefficient || enums_1.HMSH_MESHFLOW_EXP_BACKOFF,
|
|
99
|
+
maximumAttempts: options.config?.maximumAttempts || enums_1.HMSH_MESHFLOW_MAX_ATTEMPTS,
|
|
100
|
+
maximumInterval: (0, ms_1.default)(options.config?.maximumInterval || enums_1.HMSH_MESHFLOW_MAX_INTERVAL) /
|
|
120
101
|
1000,
|
|
121
102
|
};
|
|
122
|
-
//seed search data if presentthe hook before entering
|
|
123
103
|
const hotMeshClient = await this.getHotMeshClient(workflowTopic, options.namespace);
|
|
124
104
|
const msgId = await hotMeshClient.hook(`${hotMeshClient.appId}.flow.signal`, payload, types_1.StreamStatus.PENDING, 202);
|
|
125
105
|
if (options.search?.data) {
|
|
126
|
-
const searchSessionId = `-search-${hotmesh_1.
|
|
106
|
+
const searchSessionId = `-search-${hotmesh_1.HotMesh.guid()}-0`;
|
|
127
107
|
const search = new search_1.Search(options.workflowId, hotMeshClient, searchSessionId);
|
|
128
108
|
const entries = Object.entries(options.search.data).flat();
|
|
129
109
|
await search.set(...entries);
|
|
@@ -151,10 +131,6 @@ class ClientService {
|
|
|
151
131
|
};
|
|
152
132
|
this.connection = config.connection;
|
|
153
133
|
}
|
|
154
|
-
/**
|
|
155
|
-
* Any point of presence can be used to deploy and activate the HotMesh
|
|
156
|
-
* distributed executable to the active quorum.
|
|
157
|
-
*/
|
|
158
134
|
async deployAndActivate(namespace = factory_1.APP_ID, version = factory_1.APP_VERSION) {
|
|
159
135
|
if (isNaN(Number(version))) {
|
|
160
136
|
throw new Error('Invalid version number');
|
|
@@ -208,12 +184,6 @@ class ClientService {
|
|
|
208
184
|
_a = ClientService;
|
|
209
185
|
ClientService.topics = [];
|
|
210
186
|
ClientService.instances = new Map();
|
|
211
|
-
/**
|
|
212
|
-
* Creates a stream (Redis `XGROUP.CREATE`) where events can be published (XADD).
|
|
213
|
-
* It is possible that the worker that will read from this stream channel
|
|
214
|
-
* has not yet been initialized, so this call ensures that the channel
|
|
215
|
-
* exists and is ready to serve as a container for events.
|
|
216
|
-
*/
|
|
217
187
|
ClientService.createStream = async (hotMeshClient, workflowTopic, namespace) => {
|
|
218
188
|
const store = hotMeshClient.engine.store;
|
|
219
189
|
const params = { appId: namespace ?? factory_1.APP_ID, topic: workflowTopic };
|
|
@@ -222,7 +192,6 @@ ClientService.createStream = async (hotMeshClient, workflowTopic, namespace) =>
|
|
|
222
192
|
await store.xgroup('CREATE', streamKey, 'WORKER', '$', 'MKSTREAM');
|
|
223
193
|
}
|
|
224
194
|
catch (err) {
|
|
225
|
-
//ignore if already exists
|
|
226
195
|
}
|
|
227
196
|
};
|
|
228
197
|
exports.ClientService = ClientService;
|