@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.
Files changed (49) hide show
  1. package/README.md +1 -1
  2. package/build/package.json +1 -1
  3. package/build/services/activities/trigger.js +7 -1
  4. package/build/services/durable/client.js +7 -8
  5. package/build/services/durable/exporter.d.ts +105 -0
  6. package/build/services/durable/exporter.js +374 -0
  7. package/build/services/durable/factory.js +6 -63
  8. package/build/services/durable/handle.d.ts +4 -0
  9. package/build/services/durable/handle.js +5 -0
  10. package/build/services/durable/workflow.js +24 -21
  11. package/build/services/engine/index.d.ts +6 -1
  12. package/build/services/engine/index.js +9 -2
  13. package/build/services/exporter/index.d.ts +46 -0
  14. package/build/services/exporter/index.js +126 -0
  15. package/build/services/hotmesh/index.d.ts +4 -1
  16. package/build/services/hotmesh/index.js +6 -0
  17. package/build/services/quorum/index.js +4 -1
  18. package/build/services/router/index.d.ts +3 -0
  19. package/build/services/router/index.js +3 -0
  20. package/build/services/store/index.d.ts +5 -2
  21. package/build/services/store/index.js +54 -6
  22. package/build/services/task/index.js +5 -1
  23. package/build/services/worker/index.js +6 -4
  24. package/build/types/activity.d.ts +6 -1
  25. package/build/types/exporter.d.ts +51 -0
  26. package/build/types/exporter.js +8 -0
  27. package/build/types/index.d.ts +1 -0
  28. package/build/types/quorum.d.ts +2 -0
  29. package/build/types/task.d.ts +1 -1
  30. package/package.json +1 -1
  31. package/services/activities/trigger.ts +14 -0
  32. package/services/durable/client.ts +7 -8
  33. package/services/durable/exporter.ts +408 -0
  34. package/services/durable/factory.ts +6 -63
  35. package/services/durable/handle.ts +12 -0
  36. package/services/durable/workflow.ts +24 -22
  37. package/services/engine/index.ts +20 -5
  38. package/services/exporter/index.ts +147 -0
  39. package/services/hotmesh/index.ts +8 -1
  40. package/services/quorum/index.ts +8 -2
  41. package/services/router/index.ts +4 -0
  42. package/services/store/index.ts +56 -7
  43. package/services/task/index.ts +4 -1
  44. package/services/worker/index.ts +7 -5
  45. package/types/activity.ts +6 -1
  46. package/types/exporter.ts +61 -0
  47. package/types/index.ts +13 -1
  48. package/types/quorum.ts +2 -0
  49. 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
+ }
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ ;
4
+ ;
5
+ ;
6
+ ;
7
+ ;
8
+ ;
@@ -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';
@@ -6,6 +6,8 @@ export interface QuorumProfile {
6
6
  worker_topic?: string;
7
7
  stream?: string;
8
8
  stream_depth?: number;
9
+ counts?: Record<string, number>;
10
+ timestamp?: string;
9
11
  }
10
12
  export interface PingMessage {
11
13
  type: 'ping';
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.43",
3
+ "version": "0.0.45",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -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
- //use the cached instance
30
- const instanceId = 'SINGLETON';
31
- if (ClientService.instances.has(instanceId)) {
32
- const hotMeshClient = await ClientService.instances.get(instanceId);
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: namespace ?? APP_ID,
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(instanceId, hotMeshClient);
51
+ ClientService.instances.set(targetNS, hotMeshClient);
53
52
  await this.createStream(await hotMeshClient, workflowTopic, namespace);
54
- await this.activateWorkflow(await hotMeshClient, namespace ?? APP_ID);
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