@hotmeshio/hotmesh 0.0.43 → 0.0.45
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 +1 -1
- package/build/package.json +1 -1
- package/build/services/activities/trigger.js +7 -1
- package/build/services/durable/client.js +7 -8
- package/build/services/durable/exporter.d.ts +105 -0
- package/build/services/durable/exporter.js +374 -0
- package/build/services/durable/factory.js +6 -63
- package/build/services/durable/handle.d.ts +4 -0
- package/build/services/durable/handle.js +5 -0
- package/build/services/durable/workflow.js +24 -21
- package/build/services/engine/index.d.ts +6 -1
- package/build/services/engine/index.js +9 -2
- package/build/services/exporter/index.d.ts +46 -0
- package/build/services/exporter/index.js +126 -0
- package/build/services/hotmesh/index.d.ts +4 -1
- package/build/services/hotmesh/index.js +6 -0
- package/build/services/quorum/index.js +4 -1
- package/build/services/router/index.d.ts +3 -0
- package/build/services/router/index.js +3 -0
- package/build/services/store/index.d.ts +5 -2
- package/build/services/store/index.js +54 -6
- package/build/services/task/index.js +5 -1
- package/build/services/worker/index.js +6 -4
- package/build/types/activity.d.ts +6 -1
- package/build/types/exporter.d.ts +51 -0
- package/build/types/exporter.js +8 -0
- package/build/types/index.d.ts +1 -0
- package/build/types/quorum.d.ts +2 -0
- package/build/types/task.d.ts +1 -1
- package/package.json +1 -1
- package/services/activities/trigger.ts +14 -0
- package/services/durable/client.ts +7 -8
- package/services/durable/exporter.ts +408 -0
- package/services/durable/factory.ts +6 -63
- package/services/durable/handle.ts +12 -0
- package/services/durable/workflow.ts +24 -22
- package/services/engine/index.ts +20 -5
- package/services/exporter/index.ts +147 -0
- package/services/hotmesh/index.ts +8 -1
- package/services/quorum/index.ts +8 -2
- package/services/router/index.ts +4 -0
- package/services/store/index.ts +56 -7
- package/services/task/index.ts +4 -1
- package/services/worker/index.ts +7 -5
- package/types/activity.ts +6 -1
- package/types/exporter.ts +61 -0
- package/types/index.ts +13 -1
- package/types/quorum.ts +2 -0
- package/types/task.ts +1 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { StringAnyType } from "./serializer";
|
|
2
|
+
export type ExportItem = [(string | null), string, any];
|
|
3
|
+
export interface ExportOptions {
|
|
4
|
+
}
|
|
5
|
+
export type JobAction = {
|
|
6
|
+
cursor: number;
|
|
7
|
+
items: ExportItem[];
|
|
8
|
+
};
|
|
9
|
+
export interface JobActionExport {
|
|
10
|
+
hooks: {
|
|
11
|
+
[key: string]: JobAction;
|
|
12
|
+
};
|
|
13
|
+
main: JobAction;
|
|
14
|
+
}
|
|
15
|
+
export interface ActivityAction {
|
|
16
|
+
action: string;
|
|
17
|
+
target: string;
|
|
18
|
+
}
|
|
19
|
+
export interface JobTimeline {
|
|
20
|
+
activity: string;
|
|
21
|
+
dimension: string;
|
|
22
|
+
duplex: 'entry' | 'exit';
|
|
23
|
+
timestamp: string;
|
|
24
|
+
actions?: ActivityAction[];
|
|
25
|
+
}
|
|
26
|
+
export interface DependencyExport {
|
|
27
|
+
type: string;
|
|
28
|
+
topic: string;
|
|
29
|
+
gid: string;
|
|
30
|
+
jid: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ExportTransitions {
|
|
33
|
+
[key: string]: string[];
|
|
34
|
+
}
|
|
35
|
+
export interface ExportCycles {
|
|
36
|
+
[key: string]: string[];
|
|
37
|
+
}
|
|
38
|
+
export interface DurableJobExport {
|
|
39
|
+
data: StringAnyType;
|
|
40
|
+
dependencies: DependencyExport[];
|
|
41
|
+
state: StringAnyType;
|
|
42
|
+
status: string;
|
|
43
|
+
timeline: JobTimeline[];
|
|
44
|
+
transitions: ExportTransitions;
|
|
45
|
+
cycles: ExportCycles;
|
|
46
|
+
}
|
|
47
|
+
export interface JobExport {
|
|
48
|
+
dependencies: DependencyExport[];
|
|
49
|
+
process: StringAnyType;
|
|
50
|
+
status: string;
|
|
51
|
+
}
|
package/build/types/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export { AsyncSignal } from './async';
|
|
|
4
4
|
export { CacheMode } from './cache';
|
|
5
5
|
export { CollationFaultType, CollationStage } from './collator';
|
|
6
6
|
export { ActivityConfig, ActivityWorkflowDataType, ClientConfig, ContextType, ConnectionConfig, Connection, ProxyType, Registry, SignalOptions, FindOptions, FindWhereOptions, FindWhereQuery, HookOptions, MeshOSActivityOptions, MeshOSWorkerOptions, MeshOSClassConfig, MeshOSConfig, MeshOSOptions, WorkflowConfig, WorkerConfig, WorkerOptions, WorkflowContext, WorkflowSearchOptions, WorkflowDataType, WorkflowOptions, } from './durable';
|
|
7
|
+
export { ActivityAction, DependencyExport, DurableJobExport, ExportCycles, ExportItem, ExportOptions, ExportTransitions, JobAction, JobExport, JobActionExport, JobTimeline } from './exporter';
|
|
7
8
|
export { HookCondition, HookConditions, HookGate, HookInterface, HookRule, HookRules, HookSignal } from './hook';
|
|
8
9
|
export { RedisClientType as IORedisClientType, RedisMultiType as IORedisMultiType } from './ioredisclient';
|
|
9
10
|
export { ILogger } from './logger';
|
package/build/types/quorum.d.ts
CHANGED
package/build/types/task.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export type WorkListTaskType = 'sleep' | 'expire' | 'interrupt' | 'delist';
|
|
1
|
+
export type WorkListTaskType = 'sleep' | 'expire' | 'expire-child' | 'interrupt' | 'delist' | 'child';
|
package/package.json
CHANGED
|
@@ -186,11 +186,15 @@ class Trigger extends Activity {
|
|
|
186
186
|
async registerJobDependency(multi?: RedisMulti): Promise<void> {
|
|
187
187
|
const depKey = this.config.stats?.parent ?? this.context.metadata.pj;
|
|
188
188
|
let resolvedDepKey = depKey ? Pipe.resolve(depKey, this.context) : '';
|
|
189
|
+
const adjKey = this.config.stats?.adjacent;
|
|
190
|
+
let resolvedAdjKey = depKey ? Pipe.resolve(adjKey, this.context) : '';
|
|
189
191
|
if (!resolvedDepKey) {
|
|
190
192
|
resolvedDepKey = this.context.metadata.pj;
|
|
191
193
|
}
|
|
192
194
|
if (resolvedDepKey) {
|
|
195
|
+
const isParentOrigin = (resolvedDepKey === this.context.metadata.pj) || (resolvedDepKey === resolvedAdjKey);
|
|
193
196
|
await this.store.registerJobDependency(
|
|
197
|
+
isParentOrigin ? 'expire-child' : 'expire',
|
|
194
198
|
resolvedDepKey,
|
|
195
199
|
this.context.metadata.tpc,
|
|
196
200
|
this.context.metadata.jid,
|
|
@@ -198,6 +202,16 @@ class Trigger extends Activity {
|
|
|
198
202
|
multi,
|
|
199
203
|
);
|
|
200
204
|
}
|
|
205
|
+
if (resolvedAdjKey && resolvedAdjKey !== resolvedDepKey) {
|
|
206
|
+
await this.store.registerJobDependency(
|
|
207
|
+
'child',
|
|
208
|
+
resolvedAdjKey,
|
|
209
|
+
this.context.metadata.tpc,
|
|
210
|
+
this.context.metadata.jid,
|
|
211
|
+
this.context.metadata.gid,
|
|
212
|
+
multi,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
201
215
|
}
|
|
202
216
|
|
|
203
217
|
async setStats(multi?: RedisMulti): Promise<void> {
|
|
@@ -26,11 +26,10 @@ export class ClientService {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
getHotMeshClient = async (workflowTopic: string, namespace?: string) => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
await this.verifyWorkflowActive(hotMeshClient, namespace ?? APP_ID);
|
|
29
|
+
const targetNS = namespace ?? APP_ID;
|
|
30
|
+
if (ClientService.instances.has(targetNS)) {
|
|
31
|
+
const hotMeshClient = await ClientService.instances.get(targetNS);
|
|
32
|
+
await this.verifyWorkflowActive(hotMeshClient, targetNS);
|
|
34
33
|
if (!ClientService.topics.includes(workflowTopic)) {
|
|
35
34
|
ClientService.topics.push(workflowTopic);
|
|
36
35
|
await this.createStream(hotMeshClient, workflowTopic, namespace);
|
|
@@ -40,7 +39,7 @@ export class ClientService {
|
|
|
40
39
|
|
|
41
40
|
//create and cache an instance
|
|
42
41
|
const hotMeshClient = HotMesh.init({
|
|
43
|
-
appId:
|
|
42
|
+
appId: targetNS,
|
|
44
43
|
logLevel: HMSH_LOGLEVEL,
|
|
45
44
|
engine: {
|
|
46
45
|
redis: {
|
|
@@ -49,9 +48,9 @@ export class ClientService {
|
|
|
49
48
|
}
|
|
50
49
|
}
|
|
51
50
|
});
|
|
52
|
-
ClientService.instances.set(
|
|
51
|
+
ClientService.instances.set(targetNS, hotMeshClient);
|
|
53
52
|
await this.createStream(await hotMeshClient, workflowTopic, namespace);
|
|
54
|
-
await this.activateWorkflow(await hotMeshClient,
|
|
53
|
+
await this.activateWorkflow(await hotMeshClient, targetNS);
|
|
55
54
|
return hotMeshClient;
|
|
56
55
|
}
|
|
57
56
|
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { ILogger } from '../logger';
|
|
2
|
+
import { StoreService } from '../store';
|
|
3
|
+
import { StringStringType, Symbols } from "../../types";
|
|
4
|
+
import { RedisClient, RedisMulti } from '../../types/redis';
|
|
5
|
+
import {
|
|
6
|
+
ActivityAction,
|
|
7
|
+
DependencyExport,
|
|
8
|
+
ExportItem,
|
|
9
|
+
ExportOptions,
|
|
10
|
+
JobAction,
|
|
11
|
+
JobActionExport,
|
|
12
|
+
DurableJobExport,
|
|
13
|
+
JobTimeline } from '../../types/exporter';
|
|
14
|
+
import { SerializerService } from '../serializer';
|
|
15
|
+
import { restoreHierarchy } from '../../modules/utils';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Downloads job data from Redis (hscan, hmget, hgetall)
|
|
19
|
+
* Splits, Inflates, and Sorts the job data for use in durable contexts
|
|
20
|
+
*/
|
|
21
|
+
class ExporterService {
|
|
22
|
+
appId: string;
|
|
23
|
+
logger: ILogger;
|
|
24
|
+
serializer: SerializerService
|
|
25
|
+
store: StoreService<RedisClient, RedisMulti>;
|
|
26
|
+
symbols: Promise<Symbols> | Symbols;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Friendly names for the activity ids
|
|
30
|
+
*/
|
|
31
|
+
activitySymbols: Symbols = {
|
|
32
|
+
t1: 'trigger',
|
|
33
|
+
a1: 'pivot',
|
|
34
|
+
w1: 'worker',
|
|
35
|
+
a592: 'sleeper',
|
|
36
|
+
a594: 'awaiter',
|
|
37
|
+
a599: 'retryer',
|
|
38
|
+
c592: 'sleep_cycler',
|
|
39
|
+
c594: 'await_cycler',
|
|
40
|
+
c599: 'retry_cycler',
|
|
41
|
+
s5: 'scrubber',
|
|
42
|
+
sig: 'hook',
|
|
43
|
+
siga1: 'hook_pivot',
|
|
44
|
+
sigw1: 'hook_worker',
|
|
45
|
+
siga592: 'hook_sleeper',
|
|
46
|
+
siga594: 'hook_awaiter',
|
|
47
|
+
siga599: 'hook_retryer',
|
|
48
|
+
sigc592: 'hook_sleep_cycler',
|
|
49
|
+
sigc594: 'hook_await_cycler',
|
|
50
|
+
sigc599: 'hook_retry_cycler',
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//adjacent transitions
|
|
54
|
+
transitions = {
|
|
55
|
+
trigger: ['pivot', 'hook'],
|
|
56
|
+
pivot: ['worker'],
|
|
57
|
+
worker: ['sleeper', 'awaiter', 'retryer', 'scrubber'],
|
|
58
|
+
sleeper: ['sleep_cycler'],
|
|
59
|
+
awaiter: ['await_cycler'],
|
|
60
|
+
retryer: ['retry_cycler'],
|
|
61
|
+
hook: ['hook_pivot'],
|
|
62
|
+
hook_pivot: ['hook_worker'],
|
|
63
|
+
hook_worker: ['hook_sleeper', 'hook_awaiter', 'hook_retryer'],
|
|
64
|
+
hook_sleeper: ['hook_sleep_cycler'],
|
|
65
|
+
hook_awaiter: ['hook_await_cycler'],
|
|
66
|
+
hook_retryer: ['hook_retry_cycler'],
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
//goto transitions
|
|
70
|
+
cycles = {
|
|
71
|
+
sleep_cycler: ['pivot'],
|
|
72
|
+
await_cycler: ['pivot'],
|
|
73
|
+
retry_cycler: ['pivot'],
|
|
74
|
+
hook_sleep_cycler: ['hook_pivot'],
|
|
75
|
+
hook_await_cycler: ['hook_pivot'],
|
|
76
|
+
hook_retry_cycler: ['hook_pivot'],
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
constructor(appId: string, store: StoreService<RedisClient, RedisMulti>, logger: ILogger) {
|
|
80
|
+
this.appId = appId;
|
|
81
|
+
this.logger = logger;
|
|
82
|
+
this.store = store;
|
|
83
|
+
this.serializer = new SerializerService();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Convert the job hash and dependency list into a DurableJobExport object.
|
|
88
|
+
* This object contains various facets that describe the interaction
|
|
89
|
+
* in terms relevant to narrative storytelling.
|
|
90
|
+
*/
|
|
91
|
+
async export(jobId: string, options: ExportOptions = {}): Promise<DurableJobExport> {
|
|
92
|
+
if (!this.symbols) {
|
|
93
|
+
this.symbols = this.store.getAllSymbols();
|
|
94
|
+
this.symbols = await this.symbols;
|
|
95
|
+
}
|
|
96
|
+
const depData = await this.store.getDependencies(jobId);
|
|
97
|
+
const jobData = await this.store.getRaw(jobId);
|
|
98
|
+
const jobExport = this.inflate(jobData, depData);
|
|
99
|
+
return jobExport;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Interleave actions into the replay timeline to create
|
|
104
|
+
* a time-ordered timeline of the entire interaction, beginning
|
|
105
|
+
* with the entry trigger and concluding with the scrubber
|
|
106
|
+
* activity. Using the returned timeline, it is possible to
|
|
107
|
+
* create an animated narrative of the job, highlighting
|
|
108
|
+
* activities in the graph according to the timeline's
|
|
109
|
+
* activity-created (/ac) and activity-updated (/au) entries.
|
|
110
|
+
*/
|
|
111
|
+
createTimeline(replay: ExportItem[], actions: JobActionExport): JobTimeline[] {
|
|
112
|
+
const timeline: JobTimeline[] = [];
|
|
113
|
+
replay.forEach((item) => {
|
|
114
|
+
const dimensions = item[0];
|
|
115
|
+
const parts = dimensions.split('/');
|
|
116
|
+
const activityName = item[1].split('/')[0];
|
|
117
|
+
const duplex = item[1].endsWith('/ac') ? 'entry' : 'exit';
|
|
118
|
+
const timestamp = item[2];
|
|
119
|
+
const event: JobTimeline = {
|
|
120
|
+
activity: activityName,
|
|
121
|
+
duplex: duplex as 'entry' | 'exit',
|
|
122
|
+
dimension: dimensions,
|
|
123
|
+
timestamp,
|
|
124
|
+
};
|
|
125
|
+
timeline.push(event);
|
|
126
|
+
|
|
127
|
+
if (this.isMainEntry(item[1])) {
|
|
128
|
+
event.actions = [] as ActivityAction[];
|
|
129
|
+
this.interleaveActions(actions.main, event.actions);
|
|
130
|
+
} else if (this.isHookEntry(item[1])) {
|
|
131
|
+
const hookDimension = `/${parts[1]}/${parts[2]}`;
|
|
132
|
+
const hookActions = actions.hooks[hookDimension];
|
|
133
|
+
event.actions = [] as ActivityAction[];
|
|
134
|
+
this.interleaveActions(hookActions, event.actions);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
return timeline;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Interleave actions into the 'worker' and 'hook_worker'
|
|
142
|
+
* activities (between their /ac and /au entries)
|
|
143
|
+
*/
|
|
144
|
+
interleaveActions(target: JobAction, actions: ActivityAction[]) {
|
|
145
|
+
if (target) {
|
|
146
|
+
for (let i = target.cursor + 1; i < target.items.length; i++) {
|
|
147
|
+
const [_, actionType, jobOrIndex] = target.items[i];
|
|
148
|
+
actions.push({ action: actionType, target: jobOrIndex });
|
|
149
|
+
target.cursor = i;
|
|
150
|
+
if (this.isPausingAction(actionType)) {
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
isPausingAction(actionType: string): boolean {
|
|
158
|
+
return actionType === 'sleep' || actionType === 'waitForSignal';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
isMainEntry(key: string): boolean {
|
|
162
|
+
return key.startsWith('worker/') && key.endsWith('/ac');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
isHookEntry(key: string): boolean {
|
|
166
|
+
return key.startsWith('hook_worker/') && key.endsWith('/ac');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Inflates the key from Redis, 3-character symbol
|
|
171
|
+
* into a human-readable JSON path, reflecting the
|
|
172
|
+
* tree-like structure of the unidimensional Hash
|
|
173
|
+
*/
|
|
174
|
+
inflateKey(key: string): string {
|
|
175
|
+
if (key in this.symbols) {
|
|
176
|
+
const path = this.symbols[key];
|
|
177
|
+
const parts = path.split('/');
|
|
178
|
+
if (parts[0] in this.activitySymbols) {
|
|
179
|
+
parts[0] = this.activitySymbols[parts[0]];
|
|
180
|
+
}
|
|
181
|
+
return parts.join('/');
|
|
182
|
+
}
|
|
183
|
+
return key;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Inflates the dependency data from Redis into a DurableJobExport object by
|
|
188
|
+
* organizing the dimensional isolate in sch a way asto interleave
|
|
189
|
+
* into a story
|
|
190
|
+
* @param data - the dependency data from Redis
|
|
191
|
+
* @returns - the organized dependency data
|
|
192
|
+
*/
|
|
193
|
+
inflateDependencyData(data: string[], actions: JobActionExport): DependencyExport[] {
|
|
194
|
+
//console.log('dependency data>', data);
|
|
195
|
+
const hookReg = /([0-9,]+)-(\d+)$/;
|
|
196
|
+
const flowReg = /-(\d+)$/;
|
|
197
|
+
return data.map((dependency, index: number): DependencyExport => {
|
|
198
|
+
const [action, topic, gid, ...jid] = dependency.split('::');
|
|
199
|
+
const jobId = jid.join('::');
|
|
200
|
+
const match = jobId.match(hookReg);
|
|
201
|
+
let prefix: string;
|
|
202
|
+
let type: 'hook' | 'flow' | 'other';
|
|
203
|
+
let dimensionKey: string = '';
|
|
204
|
+
|
|
205
|
+
if (match) {
|
|
206
|
+
//hook-originating dependency
|
|
207
|
+
const [_, dimension, counter] = match;
|
|
208
|
+
dimensionKey = dimension.split(',').join('/');
|
|
209
|
+
prefix = `${dimensionKey}[${counter}]`;
|
|
210
|
+
type = 'hook';
|
|
211
|
+
} else {
|
|
212
|
+
const match = jobId.match(flowReg);
|
|
213
|
+
if (match) {
|
|
214
|
+
//main workflow-originating dependency
|
|
215
|
+
const [_, counter] = match;
|
|
216
|
+
prefix = `[${counter}]`;
|
|
217
|
+
type = 'flow';
|
|
218
|
+
} else {
|
|
219
|
+
//'other' types like signal cleanup
|
|
220
|
+
prefix = '/';
|
|
221
|
+
type = 'other';
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
this.seedActions(
|
|
225
|
+
type,
|
|
226
|
+
action,
|
|
227
|
+
topic,
|
|
228
|
+
dependency,
|
|
229
|
+
prefix,
|
|
230
|
+
dimensionKey,
|
|
231
|
+
actions,
|
|
232
|
+
jobId,
|
|
233
|
+
);
|
|
234
|
+
return {
|
|
235
|
+
type: action,
|
|
236
|
+
topic,
|
|
237
|
+
gid,
|
|
238
|
+
jid: jobId,
|
|
239
|
+
} as unknown as DependencyExport;
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Adds historical actions (proxyActivity, executeChild)
|
|
245
|
+
* using the `dependency list` to determine
|
|
246
|
+
* after-the-fact what happened within the 'black-box'
|
|
247
|
+
* worker function. This is necessary to interleave the
|
|
248
|
+
* actions into the replay timeline, given that it isn't
|
|
249
|
+
* really possible to know the inner-workings of the user's
|
|
250
|
+
* function
|
|
251
|
+
*
|
|
252
|
+
*/
|
|
253
|
+
seedActions(type: 'flow'|'hook'|'other', action: string, topic: string, dep: string, prefix: string, dimensionKey: string, actions: JobActionExport, jobId: string) {
|
|
254
|
+
if (type !== 'other' && action === 'expire-child') {
|
|
255
|
+
let depType: string;
|
|
256
|
+
if (topic == `${this.appId}.activity.execute`) {
|
|
257
|
+
depType = 'proxyActivity';
|
|
258
|
+
} else if (topic == `${this.appId}.execute`) {
|
|
259
|
+
depType = 'executeChild';
|
|
260
|
+
} else if (topic == `${this.appId}.wfsc.execute`) {
|
|
261
|
+
depType = 'waitForSignal';
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (depType) {
|
|
265
|
+
if (type === 'flow') {
|
|
266
|
+
actions.main.items.push([prefix, depType, jobId]);
|
|
267
|
+
} else if (type === 'hook') {
|
|
268
|
+
if (!actions.hooks[dimensionKey]) {
|
|
269
|
+
actions.hooks[dimensionKey] = {
|
|
270
|
+
cursor: -1,
|
|
271
|
+
items: [],
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
actions.hooks[dimensionKey].items.push([prefix, depType, jobId]);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Inflates the job data from Redis into a DurableJobExport object
|
|
282
|
+
* @param jobHash - the job data from Redis
|
|
283
|
+
* @param dependencyList - the list of dependencies for the job
|
|
284
|
+
* @returns - the inflated job data
|
|
285
|
+
*/
|
|
286
|
+
inflate(jobHash: StringStringType, dependencyList: string[]): DurableJobExport {
|
|
287
|
+
//the list of actions taken in the workflow and hook functions
|
|
288
|
+
const actions: JobActionExport = {
|
|
289
|
+
hooks: {},
|
|
290
|
+
main: { cursor: -1, items: [] },
|
|
291
|
+
};
|
|
292
|
+
const dependencies = this.inflateDependencyData(dependencyList, actions);
|
|
293
|
+
const state: StringStringType = {};
|
|
294
|
+
const data: StringStringType = {};
|
|
295
|
+
const other: ExportItem[] = [];
|
|
296
|
+
const replay: ExportItem[] = [];
|
|
297
|
+
const regex = /^([a-zA-Z]{3}),(\d+(?:,\d+)*)/;
|
|
298
|
+
|
|
299
|
+
Object.entries(jobHash).forEach(([key, value]) => {
|
|
300
|
+
const match = key.match(regex);
|
|
301
|
+
if (match) {
|
|
302
|
+
//activity process state
|
|
303
|
+
this.inflateProcess(match, value, replay);
|
|
304
|
+
} else if (key.length === 3) {
|
|
305
|
+
//job state
|
|
306
|
+
state[this.inflateKey(key)] = this.serializer.fromString(value);
|
|
307
|
+
} else if (key.startsWith('_')) {
|
|
308
|
+
//job data
|
|
309
|
+
data[key.substring(1)] = value;
|
|
310
|
+
} else if (key.startsWith('-')) {
|
|
311
|
+
//actions with side effect (replayable)
|
|
312
|
+
this.inflateActions(key, value, actions);
|
|
313
|
+
} else {
|
|
314
|
+
//collator guids, etc
|
|
315
|
+
other.push([null, key, value]);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
replay.sort(this.dateSort)
|
|
320
|
+
actions.main.items.sort(this.reverseSort);
|
|
321
|
+
Object.entries(actions.hooks).forEach(([key, value]) => {
|
|
322
|
+
value.items.sort(this.reverseSort);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
data: restoreHierarchy(data),
|
|
327
|
+
dependencies,
|
|
328
|
+
state: Object.entries(restoreHierarchy(state))[0][1],
|
|
329
|
+
status: jobHash[':'],
|
|
330
|
+
timeline: this.createTimeline(replay, actions),
|
|
331
|
+
transitions: { ...this.transitions },
|
|
332
|
+
cycles: { ...this.cycles },
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
inflateProcess(match: RegExpMatchArray, value: string, replay: ExportItem[]) {
|
|
337
|
+
const [_, letters, numbers] = match;
|
|
338
|
+
const path = this.inflateKey(letters);
|
|
339
|
+
if (path.endsWith('/output/metadata/ac') ||
|
|
340
|
+
path.endsWith('/output/metadata/au')) {
|
|
341
|
+
const dimensions = `/${numbers.replace(/,/g, '/')}`;
|
|
342
|
+
const resolved = this.serializer.fromString(value);
|
|
343
|
+
replay.push([
|
|
344
|
+
dimensions,
|
|
345
|
+
path,
|
|
346
|
+
resolved,
|
|
347
|
+
]);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
inflateActions(key: string, value: string, actions: JobActionExport) {
|
|
352
|
+
let [_, dimensionalType, counter, subcounter] = key.split('-');
|
|
353
|
+
if (subcounter) {
|
|
354
|
+
counter = `${counter}.${subcounter}`;
|
|
355
|
+
}
|
|
356
|
+
const [type, ...dimensions] = dimensionalType.split(',');
|
|
357
|
+
let dimensionKey = '';
|
|
358
|
+
let isHook = false;
|
|
359
|
+
if (dimensions.length > 0) {
|
|
360
|
+
dimensionKey = `/${dimensions.join('/')}`;
|
|
361
|
+
isHook = true;
|
|
362
|
+
}
|
|
363
|
+
let targetList: ExportItem[];
|
|
364
|
+
if (isHook) {
|
|
365
|
+
if (!actions.hooks[dimensionKey]) {
|
|
366
|
+
actions.hooks[dimensionKey] = {
|
|
367
|
+
cursor: -1,
|
|
368
|
+
items: [],
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
targetList = actions.hooks[dimensionKey].items;
|
|
372
|
+
} else {
|
|
373
|
+
targetList = actions.main.items;
|
|
374
|
+
}
|
|
375
|
+
targetList.push([
|
|
376
|
+
`${dimensionKey}[${counter}]`,
|
|
377
|
+
type,
|
|
378
|
+
value,
|
|
379
|
+
]);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
reverseSort(aKey: ExportItem, bKey: ExportItem) {
|
|
383
|
+
if (aKey[0] > bKey[0]) {
|
|
384
|
+
return 1;
|
|
385
|
+
} else if (aKey[0] < bKey[0]) {
|
|
386
|
+
return -1;
|
|
387
|
+
} else {
|
|
388
|
+
if (aKey[1] > bKey[1]) {
|
|
389
|
+
return 1;
|
|
390
|
+
} else if (aKey[1] < bKey[1]) {
|
|
391
|
+
return -1;
|
|
392
|
+
}
|
|
393
|
+
return 0;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
dateSort(aKey: ExportItem, bKey: ExportItem) {
|
|
398
|
+
if (aKey[2] > bKey[2]) {
|
|
399
|
+
return 1;
|
|
400
|
+
} else if (aKey[2] < bKey[2]) {
|
|
401
|
+
return -1;
|
|
402
|
+
} else {
|
|
403
|
+
return 0;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export { ExporterService };
|
|
@@ -57,6 +57,7 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
57
57
|
id: '{$self.input.data.workflowId}'
|
|
58
58
|
key: '{$self.input.data.parentWorkflowId}'
|
|
59
59
|
parent: '{$self.input.data.originJobId}'
|
|
60
|
+
adjacent: '{$self.input.data.parentWorkflowId}'
|
|
60
61
|
job:
|
|
61
62
|
maps:
|
|
62
63
|
done: false
|
|
@@ -260,10 +261,7 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
260
261
|
description: index will be appended later
|
|
261
262
|
maps:
|
|
262
263
|
signals: '{sigw1.output.data.signals}'
|
|
263
|
-
parentWorkflowId:
|
|
264
|
-
'@pipe':
|
|
265
|
-
- ['{$job.metadata.jid}', '-w']
|
|
266
|
-
- ['{@string.concat}']
|
|
264
|
+
parentWorkflowId: '{$job.metadata.jid}'
|
|
267
265
|
originJobId:
|
|
268
266
|
'@pipe':
|
|
269
267
|
- ['{t1.output.data.originJobId}', '{t1.output.data.originJobId}', '{$job.metadata.jid}']
|
|
@@ -353,10 +351,7 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
353
351
|
description: index will be appended later
|
|
354
352
|
maps:
|
|
355
353
|
signals: '{w1.output.data.signals}'
|
|
356
|
-
parentWorkflowId:
|
|
357
|
-
'@pipe':
|
|
358
|
-
- ['{$job.metadata.jid}', '-w']
|
|
359
|
-
- ['{@string.concat}']
|
|
354
|
+
parentWorkflowId: '{$job.metadata.jid}'
|
|
360
355
|
originJobId:
|
|
361
356
|
'@pipe':
|
|
362
357
|
- ['{t1.output.data.originJobId}', '{t1.output.data.originJobId}', '{$job.metadata.jid}']
|
|
@@ -523,6 +518,7 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
523
518
|
id: '{$self.input.data.workflowId}'
|
|
524
519
|
key: '{$self.input.data.parentWorkflowId}'
|
|
525
520
|
parent: '{$self.input.data.originJobId}'
|
|
521
|
+
adjacent: '{$self.input.data.parentWorkflowId}'
|
|
526
522
|
|
|
527
523
|
w1a:
|
|
528
524
|
title: Activity Worker - Calls Activity Functions
|
|
@@ -563,61 +559,6 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
563
559
|
t1a:
|
|
564
560
|
- to: w1a
|
|
565
561
|
|
|
566
|
-
- subscribes: ${app}.sleep.execute
|
|
567
|
-
publishes: ${app}.sleep.executed
|
|
568
|
-
|
|
569
|
-
expire: 0
|
|
570
|
-
|
|
571
|
-
input:
|
|
572
|
-
schema:
|
|
573
|
-
type: object
|
|
574
|
-
properties:
|
|
575
|
-
parentWorkflowId:
|
|
576
|
-
type: string
|
|
577
|
-
originJobId:
|
|
578
|
-
type: string
|
|
579
|
-
workflowId:
|
|
580
|
-
type: string
|
|
581
|
-
duration:
|
|
582
|
-
type: number
|
|
583
|
-
description: in seconds
|
|
584
|
-
index:
|
|
585
|
-
type: number
|
|
586
|
-
output:
|
|
587
|
-
schema:
|
|
588
|
-
type: object
|
|
589
|
-
properties:
|
|
590
|
-
done:
|
|
591
|
-
type: boolean
|
|
592
|
-
duration:
|
|
593
|
-
type: number
|
|
594
|
-
index:
|
|
595
|
-
type: number
|
|
596
|
-
|
|
597
|
-
activities:
|
|
598
|
-
t1s:
|
|
599
|
-
title: Sleep Flow Trigger
|
|
600
|
-
type: trigger
|
|
601
|
-
stats:
|
|
602
|
-
id: '{$self.input.data.workflowId}'
|
|
603
|
-
key: '{$self.input.data.parentWorkflowId}'
|
|
604
|
-
parent: '{$self.input.data.originJobId}'
|
|
605
|
-
|
|
606
|
-
a1s:
|
|
607
|
-
title: Sleep for a duration
|
|
608
|
-
type: hook
|
|
609
|
-
sleep: '{t1s.output.data.duration}'
|
|
610
|
-
job:
|
|
611
|
-
maps:
|
|
612
|
-
done: true
|
|
613
|
-
duration: '{t1s.output.data.duration}'
|
|
614
|
-
index: '{t1s.output.data.index}'
|
|
615
|
-
workflowId: '{t1s.output.data.workflowId}'
|
|
616
|
-
|
|
617
|
-
transitions:
|
|
618
|
-
t1s:
|
|
619
|
-
- to: a1s
|
|
620
|
-
|
|
621
562
|
- subscribes: ${app}.wfsc.execute
|
|
622
563
|
publishes: ${app}.wfsc.executed
|
|
623
564
|
|
|
@@ -662,6 +603,7 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
662
603
|
stats:
|
|
663
604
|
id: '{$self.input.data.cycleWorkflowId}'
|
|
664
605
|
parent: '{$self.input.data.originJobId}'
|
|
606
|
+
adjacent: '{$self.input.data.parentWorkflowId}'
|
|
665
607
|
|
|
666
608
|
a1wc:
|
|
667
609
|
title: Pivot - All Cycling Descendants Point Here
|
|
@@ -833,6 +775,7 @@ const getWorkflowYAML = (app: string, version: string) => {
|
|
|
833
775
|
id: '{$self.input.data.workflowId}'
|
|
834
776
|
key: '{$self.input.data.parentWorkflowId}'
|
|
835
777
|
parent: '{$self.input.data.originJobId}'
|
|
778
|
+
adjacent: '{$self.input.data.parentWorkflowId}'
|
|
836
779
|
|
|
837
780
|
a1ww:
|
|
838
781
|
title: WFS - signal entry point
|