@hotmeshio/hotmesh 0.1.14 → 0.1.16
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 +3 -31
- 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 +22 -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 +123 -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
|
@@ -7,8 +7,8 @@ const collator_1 = require("../collator");
|
|
|
7
7
|
const serializer_1 = require("../serializer");
|
|
8
8
|
const pipe_1 = require("../pipe");
|
|
9
9
|
const validator_1 = require("./validator");
|
|
10
|
-
const DEFAULT_METADATA_RANGE_SIZE = 26;
|
|
11
|
-
const DEFAULT_DATA_RANGE_SIZE = 260;
|
|
10
|
+
const DEFAULT_METADATA_RANGE_SIZE = 26;
|
|
11
|
+
const DEFAULT_DATA_RANGE_SIZE = 260;
|
|
12
12
|
const DEFAULT_RANGE_SIZE = DEFAULT_METADATA_RANGE_SIZE + DEFAULT_DATA_RANGE_SIZE;
|
|
13
13
|
class Deployer {
|
|
14
14
|
constructor(manifest) {
|
|
@@ -41,21 +41,18 @@ class Deployer {
|
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
43
|
async generateSymKeys() {
|
|
44
|
-
//note: symbol ranges are additive (per version); path assignments are immutable
|
|
45
44
|
for (const graph of this.manifest.app.graphs) {
|
|
46
|
-
//generate JOB symbols
|
|
47
45
|
const [, trigger] = this.findTrigger(graph);
|
|
48
46
|
const topic = trigger.subscribes;
|
|
49
47
|
const [lower, upper, symbols] = await this.store.reserveSymbolRange(`$${topic}`, DEFAULT_RANGE_SIZE, 'JOB');
|
|
50
|
-
const prefix = '';
|
|
48
|
+
const prefix = '';
|
|
51
49
|
const newSymbols = this.bindSymbols(lower, upper, symbols, prefix, trigger.PRODUCES);
|
|
52
50
|
if (Object.keys(newSymbols).length) {
|
|
53
51
|
await this.store.addSymbols(`$${topic}`, newSymbols);
|
|
54
52
|
}
|
|
55
|
-
//generate ACTIVITY symbols
|
|
56
53
|
for (const [activityId, activity] of Object.entries(graph.activities)) {
|
|
57
54
|
const [lower, upper, symbols] = await this.store.reserveSymbolRange(activityId, DEFAULT_RANGE_SIZE, 'ACTIVITY');
|
|
58
|
-
const prefix = `${activityId}/`;
|
|
55
|
+
const prefix = `${activityId}/`;
|
|
59
56
|
this.bindSelf(activity.consumes, activity.produces, activityId);
|
|
60
57
|
const newSymbols = this.bindSymbols(lower, upper, symbols, prefix, activity.produces);
|
|
61
58
|
if (Object.keys(newSymbols).length) {
|
|
@@ -65,7 +62,6 @@ class Deployer {
|
|
|
65
62
|
}
|
|
66
63
|
}
|
|
67
64
|
bindSelf(consumes, produces, activityId) {
|
|
68
|
-
//bind self-referential mappings
|
|
69
65
|
for (const selfId of [activityId, '$self']) {
|
|
70
66
|
const selfConsumes = consumes[selfId];
|
|
71
67
|
if (selfConsumes) {
|
|
@@ -89,7 +85,7 @@ class Deployer {
|
|
|
89
85
|
const symbol = (0, utils_1.getSymKey)(startIndex);
|
|
90
86
|
startIndex++;
|
|
91
87
|
newSymbols[fullPath] = symbol;
|
|
92
|
-
currentSymbols[fullPath] = symbol;
|
|
88
|
+
currentSymbols[fullPath] = symbol;
|
|
93
89
|
}
|
|
94
90
|
}
|
|
95
91
|
return newSymbols;
|
|
@@ -102,20 +98,16 @@ class Deployer {
|
|
|
102
98
|
if (!jobSchema && !outputSchema)
|
|
103
99
|
continue;
|
|
104
100
|
const activities = graph.activities;
|
|
105
|
-
// Find the trigger activity and bind the job schema to it
|
|
106
|
-
// at execution time, the trigger is a standin for the job
|
|
107
101
|
for (const activityKey in activities) {
|
|
108
102
|
if (activities[activityKey].type === 'trigger') {
|
|
109
103
|
const trigger = activities[activityKey];
|
|
110
104
|
if (jobSchema) {
|
|
111
|
-
//possible for trigger to have job mappings
|
|
112
105
|
if (!trigger.job) {
|
|
113
106
|
trigger.job = {};
|
|
114
107
|
}
|
|
115
108
|
trigger.job.schema = jobSchema;
|
|
116
109
|
}
|
|
117
110
|
if (outputSchema) {
|
|
118
|
-
//impossible for trigger to have output mappings.
|
|
119
111
|
trigger.output = { schema: outputSchema };
|
|
120
112
|
}
|
|
121
113
|
}
|
|
@@ -136,8 +128,6 @@ class Deployer {
|
|
|
136
128
|
}
|
|
137
129
|
}
|
|
138
130
|
}
|
|
139
|
-
//the cycle/goto activity includes and ancestor target;
|
|
140
|
-
//update with the cycle flag, so it can be rerun
|
|
141
131
|
bindCycleTarget() {
|
|
142
132
|
for (const graph of this.manifest.app.graphs) {
|
|
143
133
|
const activities = graph.activities;
|
|
@@ -149,8 +139,6 @@ class Deployer {
|
|
|
149
139
|
}
|
|
150
140
|
}
|
|
151
141
|
}
|
|
152
|
-
//it's more intuitive for SDK users to use 'topic',
|
|
153
|
-
//but the compiler is desiged to be generic and uses the attribute, 'subtypes'
|
|
154
142
|
convertTopicsToTypes() {
|
|
155
143
|
for (const graph of this.manifest.app.graphs) {
|
|
156
144
|
const activities = graph.activities;
|
|
@@ -164,7 +152,6 @@ class Deployer {
|
|
|
164
152
|
}
|
|
165
153
|
}
|
|
166
154
|
}
|
|
167
|
-
//legacy; remove at beta (assume no legacy refs to 'activity' at that point)
|
|
168
155
|
convertActivitiesToHooks() {
|
|
169
156
|
for (const graph of this.manifest.app.graphs) {
|
|
170
157
|
const activities = graph.activities;
|
|
@@ -184,11 +171,8 @@ class Deployer {
|
|
|
184
171
|
const toTransitions = graph.transitions[fromActivity];
|
|
185
172
|
for (const transition of toTransitions) {
|
|
186
173
|
const to = transition.to;
|
|
187
|
-
//DAGs have one parent; easy to optimize for
|
|
188
174
|
graph.activities[to].parent = fromActivity;
|
|
189
175
|
}
|
|
190
|
-
//temporarily bind the transitions to the parent activity,
|
|
191
|
-
// so the consumer/producer registrar picks up the bindings
|
|
192
176
|
graph.activities[fromActivity].transitions = toTransitions;
|
|
193
177
|
}
|
|
194
178
|
}
|
|
@@ -248,10 +232,6 @@ class Deployer {
|
|
|
248
232
|
traverse(obj[key], newPath);
|
|
249
233
|
}
|
|
250
234
|
else {
|
|
251
|
-
//wildcard mapping (e.g., 'friends[25]')
|
|
252
|
-
//when this is resolved, it will be expanded to
|
|
253
|
-
//`'friends/0', ..., 'friends/24'`, providing 25 dynamic
|
|
254
|
-
//slots in the flow's output data
|
|
255
235
|
const pathName = [...path, key].join('/');
|
|
256
236
|
if (!pathName.includes('[')) {
|
|
257
237
|
const finalPath = `data/${pathName}`;
|
|
@@ -261,17 +241,15 @@ class Deployer {
|
|
|
261
241
|
}
|
|
262
242
|
else {
|
|
263
243
|
const [left, right] = pathName.split('[');
|
|
264
|
-
//check if this variable isLiteralKeyType (#, -, or _)
|
|
265
244
|
const [amount, _] = right.split(']');
|
|
266
245
|
if (!isNaN(parseInt(amount))) {
|
|
267
|
-
//loop to create all possible paths (0 to amount)
|
|
268
246
|
for (let i = 0; i < parseInt(amount); i++) {
|
|
269
247
|
const finalPath = `data/${left}/${i}`;
|
|
270
248
|
if (!result.includes(finalPath)) {
|
|
271
249
|
result.push(finalPath);
|
|
272
250
|
}
|
|
273
251
|
}
|
|
274
|
-
}
|
|
252
|
+
}
|
|
275
253
|
}
|
|
276
254
|
}
|
|
277
255
|
}
|
|
@@ -293,7 +271,6 @@ class Deployer {
|
|
|
293
271
|
}
|
|
294
272
|
resolveMappingDependencies() {
|
|
295
273
|
const dynamicMappingRules = [];
|
|
296
|
-
//recursive function to descend into the object and find all dynamic mapping rules
|
|
297
274
|
function traverse(obj, consumes) {
|
|
298
275
|
for (const key in obj) {
|
|
299
276
|
if (typeof obj[key] === 'string') {
|
|
@@ -323,7 +300,6 @@ class Deployer {
|
|
|
323
300
|
}
|
|
324
301
|
}
|
|
325
302
|
const groupedRules = this.groupMappingRules(dynamicMappingRules);
|
|
326
|
-
// Iterate through the graph and add 'produces' field to each activity
|
|
327
303
|
for (const graph of graphs) {
|
|
328
304
|
const activities = graph.activities;
|
|
329
305
|
for (const activityId in activities) {
|
|
@@ -334,7 +310,6 @@ class Deployer {
|
|
|
334
310
|
}
|
|
335
311
|
groupMappingRules(rules) {
|
|
336
312
|
rules = Array.from(new Set(rules)).sort();
|
|
337
|
-
// Group by the first symbol before the period (this is the activity name)
|
|
338
313
|
const groupedRules = {};
|
|
339
314
|
for (const rule of rules) {
|
|
340
315
|
const [group, resolved] = this.resolveMappableValue(rule);
|
|
@@ -353,7 +328,6 @@ class Deployer {
|
|
|
353
328
|
return [group, path.join('/')];
|
|
354
329
|
}
|
|
355
330
|
else {
|
|
356
|
-
//normalize paths to be relative to the activity
|
|
357
331
|
const [group, type, subtype, ...path] = parts;
|
|
358
332
|
const prefix = {
|
|
359
333
|
hook: 'hook/data',
|
|
@@ -370,7 +344,6 @@ class Deployer {
|
|
|
370
344
|
const activities = graph.activities;
|
|
371
345
|
for (const activityKey in activities) {
|
|
372
346
|
const target = activities[activityKey];
|
|
373
|
-
//remove transitions; no longer necessary for runtime
|
|
374
347
|
delete target.transitions;
|
|
375
348
|
activitySchemas[activityKey] = target;
|
|
376
349
|
}
|
|
@@ -383,7 +356,6 @@ class Deployer {
|
|
|
383
356
|
for (const graph of graphs) {
|
|
384
357
|
const activities = graph.activities;
|
|
385
358
|
const subscribesTopic = graph.subscribes;
|
|
386
|
-
// Find the activity ID associated with the subscribes topic
|
|
387
359
|
for (const activityKey in activities) {
|
|
388
360
|
if (activities[activityKey].type === 'trigger') {
|
|
389
361
|
publicSubscriptions[subscribesTopic] = activityKey;
|
|
@@ -446,7 +418,6 @@ class Deployer {
|
|
|
446
418
|
if (!targetActivity.hook) {
|
|
447
419
|
targetActivity.hook = {};
|
|
448
420
|
}
|
|
449
|
-
//create back-reference to the hook topic
|
|
450
421
|
targetActivity.hook.topic = topic;
|
|
451
422
|
}
|
|
452
423
|
}
|
|
@@ -455,7 +426,6 @@ class Deployer {
|
|
|
455
426
|
await this.store.setHookRules(hookRules);
|
|
456
427
|
}
|
|
457
428
|
async deployConsumerGroups() {
|
|
458
|
-
//create one engine group
|
|
459
429
|
const params = { appId: this.manifest.app.id };
|
|
460
430
|
const key = this.store.mintKey(key_1.KeyType.STREAMS, params);
|
|
461
431
|
await this.deployConsumerGroup(key, 'ENGINE');
|
|
@@ -463,12 +433,10 @@ class Deployer {
|
|
|
463
433
|
const activities = graph.activities;
|
|
464
434
|
for (const activityKey in activities) {
|
|
465
435
|
const activity = activities[activityKey];
|
|
466
|
-
//only precreate if the topic is concrete and not `mappable`
|
|
467
436
|
if (activity.type === 'worker' &&
|
|
468
437
|
pipe_1.Pipe.resolve(activity.subtype, {}) === activity.subtype) {
|
|
469
438
|
params.topic = activity.subtype;
|
|
470
439
|
const key = this.store.mintKey(key_1.KeyType.STREAMS, params);
|
|
471
|
-
//create one worker group per unique activity subtype (the topic)
|
|
472
440
|
await this.deployConsumerGroup(key, 'WORKER');
|
|
473
441
|
}
|
|
474
442
|
}
|
|
@@ -2,28 +2,13 @@ import { ILogger } from '../logger';
|
|
|
2
2
|
import { StoreService } from '../store';
|
|
3
3
|
import { HotMeshManifest } from '../../types/hotmesh';
|
|
4
4
|
import { RedisClient, RedisMulti } from '../../types/redis';
|
|
5
|
-
/**
|
|
6
|
-
* The compiler service converts a graph into a executable program.
|
|
7
|
-
*/
|
|
8
5
|
declare class CompilerService {
|
|
9
6
|
store: StoreService<RedisClient, RedisMulti> | null;
|
|
10
7
|
logger: ILogger;
|
|
11
8
|
constructor(store: StoreService<RedisClient, RedisMulti>, logger: ILogger);
|
|
12
|
-
/**
|
|
13
|
-
* verifies and plans the deployment of an app to Redis; the app is not deployed yet
|
|
14
|
-
* @param path
|
|
15
|
-
*/
|
|
16
9
|
plan(mySchemaOrPath: string): Promise<HotMeshManifest>;
|
|
17
10
|
isPath(input: string): boolean;
|
|
18
|
-
/**
|
|
19
|
-
* deploys an app to Redis but does NOT activate it.
|
|
20
|
-
*/
|
|
21
11
|
deploy(mySchemaOrPath: string): Promise<HotMeshManifest>;
|
|
22
|
-
/**
|
|
23
|
-
* activates a deployed version of an app;
|
|
24
|
-
* @param appId
|
|
25
|
-
* @param appVersion
|
|
26
|
-
*/
|
|
27
12
|
activate(appId: string, appVersion: string): Promise<boolean>;
|
|
28
13
|
saveAsJSON(originalPath: string, schema: HotMeshManifest): Promise<void>;
|
|
29
14
|
}
|
|
@@ -33,18 +33,11 @@ const json_schema_ref_parser_1 = __importDefault(require("@apidevtools/json-sche
|
|
|
33
33
|
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
34
34
|
const deployer_1 = require("./deployer");
|
|
35
35
|
const validator_1 = require("./validator");
|
|
36
|
-
/**
|
|
37
|
-
* The compiler service converts a graph into a executable program.
|
|
38
|
-
*/
|
|
39
36
|
class CompilerService {
|
|
40
37
|
constructor(store, logger) {
|
|
41
38
|
this.store = store;
|
|
42
39
|
this.logger = logger;
|
|
43
40
|
}
|
|
44
|
-
/**
|
|
45
|
-
* verifies and plans the deployment of an app to Redis; the app is not deployed yet
|
|
46
|
-
* @param path
|
|
47
|
-
*/
|
|
48
41
|
async plan(mySchemaOrPath) {
|
|
49
42
|
try {
|
|
50
43
|
let schema;
|
|
@@ -54,10 +47,8 @@ class CompilerService {
|
|
|
54
47
|
else {
|
|
55
48
|
schema = js_yaml_1.default.load(mySchemaOrPath);
|
|
56
49
|
}
|
|
57
|
-
// 1) validate the manifest file
|
|
58
50
|
const validator = new validator_1.Validator(schema);
|
|
59
51
|
validator.validate(this.store);
|
|
60
|
-
// 2) todo: add a PlannerService module that will plan the deployment (what might break, drift, etc)
|
|
61
52
|
return schema;
|
|
62
53
|
}
|
|
63
54
|
catch (err) {
|
|
@@ -67,9 +58,6 @@ class CompilerService {
|
|
|
67
58
|
isPath(input) {
|
|
68
59
|
return !input.trim().startsWith('app:');
|
|
69
60
|
}
|
|
70
|
-
/**
|
|
71
|
-
* deploys an app to Redis but does NOT activate it.
|
|
72
|
-
*/
|
|
73
61
|
async deploy(mySchemaOrPath) {
|
|
74
62
|
try {
|
|
75
63
|
let schema;
|
|
@@ -80,13 +68,10 @@ class CompilerService {
|
|
|
80
68
|
else {
|
|
81
69
|
schema = js_yaml_1.default.load(mySchemaOrPath);
|
|
82
70
|
}
|
|
83
|
-
// 2) validate the manifest file (synchronous operation...no callbacks)
|
|
84
71
|
const validator = new validator_1.Validator(schema);
|
|
85
72
|
validator.validate(this.store);
|
|
86
|
-
// 3) deploy the schema (segment, optimize, etc; save to Redis)
|
|
87
73
|
const deployer = new deployer_1.Deployer(schema);
|
|
88
74
|
await deployer.deploy(this.store);
|
|
89
|
-
// 4) save the app version to Redis (so it can be activated later)
|
|
90
75
|
await this.store.setApp(schema.app.id, schema.app.version);
|
|
91
76
|
return schema;
|
|
92
77
|
}
|
|
@@ -94,11 +79,6 @@ class CompilerService {
|
|
|
94
79
|
this.logger.error('compiler-deploy-error', err);
|
|
95
80
|
}
|
|
96
81
|
}
|
|
97
|
-
/**
|
|
98
|
-
* activates a deployed version of an app;
|
|
99
|
-
* @param appId
|
|
100
|
-
* @param appVersion
|
|
101
|
-
*/
|
|
102
82
|
async activate(appId, appVersion) {
|
|
103
83
|
return await this.store.activateAppVersion(appId, appVersion);
|
|
104
84
|
}
|
|
@@ -10,9 +10,6 @@ declare class Validator {
|
|
|
10
10
|
static SYS_VARS: string[];
|
|
11
11
|
static CONTEXT_VARS: string[];
|
|
12
12
|
constructor(manifest: HotMeshManifest);
|
|
13
|
-
/**
|
|
14
|
-
* validate the manifest file
|
|
15
|
-
*/
|
|
16
13
|
validate(store: StoreService<RedisClient, RedisMulti>): Promise<void>;
|
|
17
14
|
validateActivityIds(): void;
|
|
18
15
|
isMappingStatement(value: string): boolean;
|
|
@@ -10,9 +10,6 @@ class Validator {
|
|
|
10
10
|
this.store = null;
|
|
11
11
|
this.manifest = manifest;
|
|
12
12
|
}
|
|
13
|
-
/**
|
|
14
|
-
* validate the manifest file
|
|
15
|
-
*/
|
|
16
13
|
async validate(store) {
|
|
17
14
|
this.store = store;
|
|
18
15
|
this.getMappingStatements();
|
|
@@ -28,12 +25,10 @@ class Validator {
|
|
|
28
25
|
this.validateHooks();
|
|
29
26
|
this.validateConditionalStatements();
|
|
30
27
|
}
|
|
31
|
-
// 1.1) Validate the manifest file activity ids are unique (no duplicates)
|
|
32
28
|
validateActivityIds() {
|
|
33
29
|
const activityIdsSet = new Set();
|
|
34
30
|
this.manifest.app.graphs.forEach((graph) => {
|
|
35
31
|
const ids = Object.keys(graph.activities);
|
|
36
|
-
// Check for duplicates and add ids to the set
|
|
37
32
|
ids.forEach((id) => {
|
|
38
33
|
if (activityIdsSet.has(id)) {
|
|
39
34
|
throw new Error(`Duplicate activity id found: ${id}`);
|
|
@@ -72,9 +67,7 @@ class Validator {
|
|
|
72
67
|
});
|
|
73
68
|
this.mappingStatements = mappingStatements;
|
|
74
69
|
}
|
|
75
|
-
// 1.2) Validate no activity ids are referenced that don't exist
|
|
76
70
|
validateReferencedActivityIds() {
|
|
77
|
-
// get list of all mapping statements and validate
|
|
78
71
|
const mappingStatements = this.mappingStatements;
|
|
79
72
|
const activityIds = this.activityIds;
|
|
80
73
|
for (const activity in mappingStatements) {
|
|
@@ -99,41 +92,23 @@ class Validator {
|
|
|
99
92
|
isContextVariable(value) {
|
|
100
93
|
return ['{$input}', '{$output}', '{$item}', '{$key}', '{$index}'].includes(value);
|
|
101
94
|
}
|
|
102
|
-
// 1.3) Validate the mapping/@pipe statements are valid
|
|
103
95
|
validateMappingStatements() {
|
|
104
|
-
// Implement the method content
|
|
105
96
|
}
|
|
106
|
-
// 1.4) Validate the transitions are valid
|
|
107
97
|
validateTransitions() {
|
|
108
|
-
// Implement the method content
|
|
109
98
|
}
|
|
110
|
-
// 1.5) Validate the transition conditions are valid
|
|
111
99
|
validateTransitionConditions() {
|
|
112
|
-
// Implement the method content
|
|
113
100
|
}
|
|
114
|
-
// 1.6) Validate the stats
|
|
115
101
|
validateStats() {
|
|
116
|
-
// Implement the method content
|
|
117
102
|
}
|
|
118
|
-
// 1.7) Validate the schemas
|
|
119
103
|
validateSchemas() {
|
|
120
|
-
// Implement the method content
|
|
121
104
|
}
|
|
122
|
-
// 1.8) Validate the topics are unique and handled
|
|
123
105
|
validateUniqueHandledTopics() {
|
|
124
|
-
// Implement the method content
|
|
125
106
|
}
|
|
126
|
-
// 1.9) Validate that every graph has publishes and subscribes
|
|
127
107
|
validateGraphPublishSubscribe() {
|
|
128
|
-
// Implement the method content
|
|
129
108
|
}
|
|
130
|
-
// 1.10) Validate hooks, including mapping statements
|
|
131
109
|
validateHooks() {
|
|
132
|
-
// Implement the method content
|
|
133
110
|
}
|
|
134
|
-
// 1.11) Validate conditional statements
|
|
135
111
|
validateConditionalStatements() {
|
|
136
|
-
// Implement the method content
|
|
137
112
|
}
|
|
138
113
|
}
|
|
139
114
|
exports.Validator = Validator;
|
|
@@ -5,8 +5,6 @@ const utils_1 = require("../../modules/utils");
|
|
|
5
5
|
const ioredis_1 = require("../connector/clients/ioredis");
|
|
6
6
|
const redis_1 = require("../connector/clients/redis");
|
|
7
7
|
class ConnectorService {
|
|
8
|
-
//1) Initialize `store`, `stream`, and `subscription` Redis clients.
|
|
9
|
-
//2) Bind to the target if not already present
|
|
10
8
|
static async initRedisClients(Redis, options, target) {
|
|
11
9
|
if (!target.store || !target.stream || !target.sub) {
|
|
12
10
|
const instances = [];
|
|
@@ -42,17 +42,13 @@ declare class EngineService {
|
|
|
42
42
|
reporting: boolean;
|
|
43
43
|
jobId: number;
|
|
44
44
|
inited: string;
|
|
45
|
+
constructor();
|
|
45
46
|
static init(namespace: string, appId: string, guid: string, config: HotMeshConfig, logger: ILogger): Promise<EngineService>;
|
|
46
47
|
verifyEngineFields(config: HotMeshConfig): void;
|
|
47
48
|
initStoreChannel(store: RedisClient): Promise<void>;
|
|
48
49
|
initSubChannel(sub: RedisClient): Promise<void>;
|
|
49
50
|
initStreamChannel(stream: RedisClient): Promise<void>;
|
|
50
51
|
initRouter(config: HotMeshConfig): Promise<Router>;
|
|
51
|
-
/**
|
|
52
|
-
* resolves the distributed executable version using a delay
|
|
53
|
-
* to allow deployment race conditions to resolve
|
|
54
|
-
* @private
|
|
55
|
-
*/
|
|
56
52
|
fetchAndVerifyVID(vid: AppVID, count?: number): Promise<AppVID>;
|
|
57
53
|
getVID(vid?: AppVID): Promise<AppVID>;
|
|
58
54
|
setCacheMode(cacheMode: CacheMode, untilVersion: string): void;
|
|
@@ -92,11 +88,6 @@ declare class EngineService {
|
|
|
92
88
|
delistJobCallback(jobId: string): void;
|
|
93
89
|
hasOneTimeSubscription(context: JobState): boolean;
|
|
94
90
|
runJobCompletionTasks(context: JobState, options?: JobCompletionOptions): Promise<string | void>;
|
|
95
|
-
/**
|
|
96
|
-
* Job hash expiration is typically reliant on the metadata field
|
|
97
|
-
* if the activity concludes normally. However, if the job is `interrupted`,
|
|
98
|
-
* it will be expired immediately.
|
|
99
|
-
*/
|
|
100
91
|
resolveExpires(context: JobState, options: JobCompletionOptions): number;
|
|
101
92
|
export(jobId: string): Promise<JobExport>;
|
|
102
93
|
getRaw(jobId: string): Promise<StringStringType>;
|
|
@@ -94,11 +94,6 @@ class EngineService {
|
|
|
94
94
|
throttle,
|
|
95
95
|
}, this.stream, this.store, this.logger);
|
|
96
96
|
}
|
|
97
|
-
/**
|
|
98
|
-
* resolves the distributed executable version using a delay
|
|
99
|
-
* to allow deployment race conditions to resolve
|
|
100
|
-
* @private
|
|
101
|
-
*/
|
|
102
97
|
async fetchAndVerifyVID(vid, count = 0) {
|
|
103
98
|
if (isNaN(Number(vid.version))) {
|
|
104
99
|
const app = await this.store.getApp(vid.id, true);
|
|
@@ -122,7 +117,6 @@ class EngineService {
|
|
|
122
117
|
if (this.cacheMode === 'nocache') {
|
|
123
118
|
const app = await this.store.getApp(this.appId, true);
|
|
124
119
|
if (app.version.toString() === this.untilVersion.toString()) {
|
|
125
|
-
//new version is deployed; OK to cache again
|
|
126
120
|
if (!this.apps)
|
|
127
121
|
this.apps = {};
|
|
128
122
|
this.apps[this.appId] = app;
|
|
@@ -171,7 +165,6 @@ class EngineService {
|
|
|
171
165
|
this.logger.error('engine-throttle-error', { error: e });
|
|
172
166
|
}
|
|
173
167
|
}
|
|
174
|
-
// ************* METADATA/MODEL METHODS *************
|
|
175
168
|
async initActivity(topic, data = {}, context) {
|
|
176
169
|
const [activityId, schema] = await this.getSchema(topic);
|
|
177
170
|
const ActivityHandler = activities_1.default[utils_1.polyfill.resolveActivityType(schema.type)];
|
|
@@ -197,13 +190,11 @@ class EngineService {
|
|
|
197
190
|
throw new Error(`no app found for id ${this.appId}`);
|
|
198
191
|
}
|
|
199
192
|
if (this.isPrivate(topic)) {
|
|
200
|
-
//private subscriptions use the schema id (.activityId)
|
|
201
193
|
const activityId = topic.substring(1);
|
|
202
194
|
const schema = await this.store.getSchema(activityId, await this.getVID(app));
|
|
203
195
|
return [activityId, schema];
|
|
204
196
|
}
|
|
205
197
|
else {
|
|
206
|
-
//public subscriptions use a topic (a.b.c) that is associated with a schema id
|
|
207
198
|
const activityId = await this.store.getSubscription(topic, await this.getVID(app));
|
|
208
199
|
if (activityId) {
|
|
209
200
|
const schema = await this.store.getSchema(activityId, await this.getVID(app));
|
|
@@ -218,7 +209,6 @@ class EngineService {
|
|
|
218
209
|
isPrivate(topic) {
|
|
219
210
|
return topic.startsWith('.');
|
|
220
211
|
}
|
|
221
|
-
// ************* COMPILER METHODS *************
|
|
222
212
|
async plan(pathOrYAML) {
|
|
223
213
|
const compiler = new compiler_1.CompilerService(this.store, this.logger);
|
|
224
214
|
return await compiler.plan(pathOrYAML);
|
|
@@ -227,7 +217,6 @@ class EngineService {
|
|
|
227
217
|
const compiler = new compiler_1.CompilerService(this.store, this.logger);
|
|
228
218
|
return await compiler.deploy(pathOrYAML);
|
|
229
219
|
}
|
|
230
|
-
// ************* REPORTER METHODS *************
|
|
231
220
|
async getStats(topic, query) {
|
|
232
221
|
const { id, version } = await this.getVID();
|
|
233
222
|
const reporter = new reporter_1.ReporterService({ id, version }, this.store, this.logger);
|
|
@@ -252,7 +241,6 @@ class EngineService {
|
|
|
252
241
|
sparse: query.sparse,
|
|
253
242
|
};
|
|
254
243
|
}
|
|
255
|
-
// ****************** STREAM RE-ENTRY POINT *****************
|
|
256
244
|
async processStreamMessage(streamData) {
|
|
257
245
|
this.logger.debug('engine-process', {
|
|
258
246
|
jid: streamData.metadata.jid,
|
|
@@ -274,22 +262,18 @@ class EngineService {
|
|
|
274
262
|
data: streamData.data,
|
|
275
263
|
};
|
|
276
264
|
if (streamData.type === stream_1.StreamDataType.TIMEHOOK) {
|
|
277
|
-
//TIMEHOOK AWAKEN
|
|
278
265
|
const activityHandler = (await this.initActivity(`.${streamData.metadata.aid}`, context.data, context));
|
|
279
266
|
await activityHandler.processTimeHookEvent(streamData.metadata.jid);
|
|
280
267
|
}
|
|
281
268
|
else if (streamData.type === stream_1.StreamDataType.WEBHOOK) {
|
|
282
|
-
//WEBHOOK AWAKEN (SIGNAL IN)
|
|
283
269
|
const activityHandler = (await this.initActivity(`.${streamData.metadata.aid}`, context.data, context));
|
|
284
270
|
await activityHandler.processWebHookEvent(streamData.status, streamData.code);
|
|
285
271
|
}
|
|
286
272
|
else if (streamData.type === stream_1.StreamDataType.TRANSITION) {
|
|
287
|
-
|
|
288
|
-
const activityHandler = (await this.initActivity(`.${streamData.metadata.aid}`, context.data, context)); //todo: `as Activity` (type is more generic)
|
|
273
|
+
const activityHandler = (await this.initActivity(`.${streamData.metadata.aid}`, context.data, context));
|
|
289
274
|
await activityHandler.process();
|
|
290
275
|
}
|
|
291
276
|
else if (streamData.type === stream_1.StreamDataType.AWAIT) {
|
|
292
|
-
//TRIGGER JOB
|
|
293
277
|
context.metadata = {
|
|
294
278
|
...context.metadata,
|
|
295
279
|
pj: streamData.metadata.jid,
|
|
@@ -304,12 +288,10 @@ class EngineService {
|
|
|
304
288
|
await activityHandler.process();
|
|
305
289
|
}
|
|
306
290
|
else if (streamData.type === stream_1.StreamDataType.RESULT) {
|
|
307
|
-
//AWAIT RESULT
|
|
308
291
|
const activityHandler = (await this.initActivity(`.${context.metadata.aid}`, streamData.data, context));
|
|
309
292
|
await activityHandler.processEvent(streamData.status, streamData.code);
|
|
310
293
|
}
|
|
311
294
|
else {
|
|
312
|
-
//WORKER RESULT
|
|
313
295
|
const activityHandler = (await this.initActivity(`.${streamData.metadata.aid}`, streamData.data, context));
|
|
314
296
|
await activityHandler.processEvent(streamData.status, streamData.code, 'output');
|
|
315
297
|
}
|
|
@@ -319,10 +301,8 @@ class EngineService {
|
|
|
319
301
|
aid: streamData.metadata.aid,
|
|
320
302
|
});
|
|
321
303
|
}
|
|
322
|
-
// ***************** `AWAIT` ACTIVITY RETURN RESPONSE ****************
|
|
323
304
|
async execAdjacentParent(context, jobOutput, emit = false) {
|
|
324
305
|
if (this.hasParentJob(context)) {
|
|
325
|
-
//errors are stringified `StreamError` objects
|
|
326
306
|
const error = this.resolveError(jobOutput.metadata);
|
|
327
307
|
const spn = context['$self']?.output?.metadata?.l2s ||
|
|
328
308
|
context['$self']?.output?.metadata?.l1s;
|
|
@@ -367,11 +347,8 @@ class EngineService {
|
|
|
367
347
|
return JSON.parse(metadata.err);
|
|
368
348
|
}
|
|
369
349
|
}
|
|
370
|
-
// ****************** `INTERRUPT` ACTIVE JOBS *****************
|
|
371
350
|
async interrupt(topic, jobId, options = {}) {
|
|
372
|
-
//immediately interrupt the job, going directly to the data source
|
|
373
351
|
await this.store.interrupt(topic, jobId, options);
|
|
374
|
-
//now that the job is interrupted, we can clean up
|
|
375
352
|
const context = (await this.getState(topic, jobId));
|
|
376
353
|
const completionOpts = {
|
|
377
354
|
interrupt: options.descend,
|
|
@@ -379,12 +356,9 @@ class EngineService {
|
|
|
379
356
|
};
|
|
380
357
|
return (await this.runJobCompletionTasks(context, completionOpts));
|
|
381
358
|
}
|
|
382
|
-
// ****************** `SCRUB` CLEAN COMPLETED JOBS *****************
|
|
383
359
|
async scrub(jobId) {
|
|
384
|
-
//todo: do not allow scrubbing of non-existent or actively running job
|
|
385
360
|
await this.store.scrub(jobId);
|
|
386
361
|
}
|
|
387
|
-
// ****************** `HOOK` ACTIVITY RE-ENTRY POINT *****************
|
|
388
362
|
async hook(topic, data, status = stream_1.StreamStatus.SUCCESS, code = 200) {
|
|
389
363
|
const hookRule = await this.taskService.getHookRule(topic);
|
|
390
364
|
const [aid] = await this.getSchema(`.${hookRule.to}`);
|
|
@@ -447,8 +421,6 @@ class EngineService {
|
|
|
447
421
|
throw new Error(`unable to find hook rule for topic ${hookTopic}`);
|
|
448
422
|
}
|
|
449
423
|
}
|
|
450
|
-
// ********************** PUB/SUB ENTRY POINT **********************
|
|
451
|
-
//publish (returns just the job id)
|
|
452
424
|
async pub(topic, data, context, extended) {
|
|
453
425
|
const activityHandler = await this.initActivity(topic, data, context);
|
|
454
426
|
if (activityHandler) {
|
|
@@ -458,29 +430,24 @@ class EngineService {
|
|
|
458
430
|
throw new Error(`unable to process activity for topic ${topic}`);
|
|
459
431
|
}
|
|
460
432
|
}
|
|
461
|
-
//subscribe to all jobs for a topic
|
|
462
433
|
async sub(topic, callback) {
|
|
463
434
|
const subscriptionCallback = async (topic, message) => {
|
|
464
435
|
callback(message.topic, message.job);
|
|
465
436
|
};
|
|
466
437
|
return await this.subscribe.subscribe(key_1.KeyType.QUORUM, subscriptionCallback, this.appId, topic);
|
|
467
438
|
}
|
|
468
|
-
//unsubscribe to all jobs for a topic
|
|
469
439
|
async unsub(topic) {
|
|
470
440
|
return await this.subscribe.unsubscribe(key_1.KeyType.QUORUM, this.appId, topic);
|
|
471
441
|
}
|
|
472
|
-
//subscribe to all jobs for a wildcard topic
|
|
473
442
|
async psub(wild, callback) {
|
|
474
443
|
const subscriptionCallback = async (topic, message) => {
|
|
475
444
|
callback(message.topic, message.job);
|
|
476
445
|
};
|
|
477
446
|
return await this.subscribe.psubscribe(key_1.KeyType.QUORUM, subscriptionCallback, this.appId, wild);
|
|
478
447
|
}
|
|
479
|
-
//unsubscribe to all jobs for a wildcard topic
|
|
480
448
|
async punsub(wild) {
|
|
481
449
|
return await this.subscribe.punsubscribe(key_1.KeyType.QUORUM, this.appId, wild);
|
|
482
450
|
}
|
|
483
|
-
//publish and await (returns the job and data (if ready)); throws error with jobid if not
|
|
484
451
|
async pubsub(topic, data, context, timeout = enums_1.HMSH_OTT_WAIT_TIME) {
|
|
485
452
|
context = {
|
|
486
453
|
metadata: {
|
|
@@ -504,7 +471,6 @@ class EngineService {
|
|
|
504
471
|
}
|
|
505
472
|
});
|
|
506
473
|
setTimeout(() => {
|
|
507
|
-
//note: job is still active (the subscriber timed out)
|
|
508
474
|
this.delistJobCallback(jobId);
|
|
509
475
|
reject({
|
|
510
476
|
code: enums_1.HMSH_CODE_TIMEOUT,
|
|
@@ -515,7 +481,6 @@ class EngineService {
|
|
|
515
481
|
});
|
|
516
482
|
}
|
|
517
483
|
async pubOneTimeSubs(context, jobOutput, emit = false) {
|
|
518
|
-
//todo: subscriber should query for the job...only publish minimum context needed
|
|
519
484
|
if (this.hasOneTimeSubscription(context)) {
|
|
520
485
|
const message = {
|
|
521
486
|
type: 'job',
|
|
@@ -554,9 +519,7 @@ class EngineService {
|
|
|
554
519
|
hasOneTimeSubscription(context) {
|
|
555
520
|
return Boolean(context.metadata.ngn);
|
|
556
521
|
}
|
|
557
|
-
// ********** JOB COMPLETION/CLEANUP (AND JOB EMIT) ***********
|
|
558
522
|
async runJobCompletionTasks(context, options = {}) {
|
|
559
|
-
//'emit' indicates the job is still active
|
|
560
523
|
const isAwait = this.hasParentJob(context, true);
|
|
561
524
|
const isOneTimeSub = this.hasOneTimeSubscription(context);
|
|
562
525
|
const topic = await this.getPublishesTopic(context);
|
|
@@ -572,15 +535,9 @@ class EngineService {
|
|
|
572
535
|
}
|
|
573
536
|
return msgId;
|
|
574
537
|
}
|
|
575
|
-
/**
|
|
576
|
-
* Job hash expiration is typically reliant on the metadata field
|
|
577
|
-
* if the activity concludes normally. However, if the job is `interrupted`,
|
|
578
|
-
* it will be expired immediately.
|
|
579
|
-
*/
|
|
580
538
|
resolveExpires(context, options) {
|
|
581
539
|
return options.expire ?? context.metadata.expire ?? enums_1.HMSH_EXPIRE_JOB_SECONDS;
|
|
582
540
|
}
|
|
583
|
-
// ****** GET JOB STATE/COLLATION STATUS BY ID *********
|
|
584
541
|
async export(jobId) {
|
|
585
542
|
return await this.exporter.export(jobId);
|
|
586
543
|
}
|
|
@@ -591,15 +548,11 @@ class EngineService {
|
|
|
591
548
|
const { id: appId } = await this.getVID();
|
|
592
549
|
return await this.store.getStatus(jobId, appId);
|
|
593
550
|
}
|
|
594
|
-
//todo: add 'options' parameter;
|
|
595
|
-
// (e.g, if {dimensions:true}, use hscan to deliver
|
|
596
|
-
// the full set of dimensional job data)
|
|
597
551
|
async getState(topic, jobId) {
|
|
598
552
|
const jobSymbols = await this.store.getSymbols(`$${topic}`);
|
|
599
553
|
const consumes = {
|
|
600
554
|
[`$${topic}`]: Object.keys(jobSymbols),
|
|
601
555
|
};
|
|
602
|
-
//job data exists at the 'zero' dimension; pass an empty object
|
|
603
556
|
const dIds = {};
|
|
604
557
|
const output = await this.store.getState(jobId, consumes, dIds);
|
|
605
558
|
if (!output) {
|