@hotmeshio/hotmesh 0.0.53 → 0.0.55

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 (41) hide show
  1. package/README.md +0 -3
  2. package/build/modules/errors.d.ts +6 -48
  3. package/build/modules/errors.js +5 -5
  4. package/build/package.json +1 -1
  5. package/build/services/activities/hook.js +5 -1
  6. package/build/services/activities/trigger.d.ts +5 -2
  7. package/build/services/activities/trigger.js +22 -1
  8. package/build/services/durable/client.js +1 -8
  9. package/build/services/durable/exporter.d.ts +24 -13
  10. package/build/services/durable/exporter.js +145 -127
  11. package/build/services/durable/handle.d.ts +2 -2
  12. package/build/services/durable/handle.js +2 -2
  13. package/build/services/durable/worker.js +1 -1
  14. package/build/services/durable/workflow.d.ts +29 -17
  15. package/build/services/durable/workflow.js +117 -97
  16. package/build/services/engine/index.d.ts +2 -2
  17. package/build/services/engine/index.js +2 -2
  18. package/build/services/hotmesh/index.d.ts +2 -2
  19. package/build/services/hotmesh/index.js +2 -2
  20. package/build/types/durable.d.ts +15 -3
  21. package/build/types/error.d.ts +48 -0
  22. package/build/types/error.js +2 -0
  23. package/build/types/exporter.d.ts +26 -20
  24. package/build/types/index.d.ts +2 -1
  25. package/build/types/job.d.ts +24 -1
  26. package/modules/errors.ts +18 -55
  27. package/package.json +1 -1
  28. package/services/activities/hook.ts +8 -1
  29. package/services/activities/trigger.ts +27 -2
  30. package/services/durable/client.ts +2 -8
  31. package/services/durable/exporter.ts +149 -128
  32. package/services/durable/handle.ts +3 -3
  33. package/services/durable/worker.ts +1 -1
  34. package/services/durable/workflow.ts +137 -104
  35. package/services/engine/index.ts +4 -3
  36. package/services/hotmesh/index.ts +4 -3
  37. package/types/durable.ts +18 -3
  38. package/types/error.ts +52 -0
  39. package/types/exporter.ts +31 -23
  40. package/types/index.ts +8 -1
  41. package/types/job.ts +27 -0
@@ -4,11 +4,12 @@ export { AsyncSignal } from './async';
4
4
  export { CacheMode } from './cache';
5
5
  export { CollationFaultType, CollationStage } from './collator';
6
6
  export { ActivityConfig, ActivityWorkflowDataType, ChildResponseType, ClientConfig, ContextType, ConnectionConfig, Connection, ProxyResponseType, ProxyType, Registry, SignalOptions, FindOptions, FindWhereOptions, FindWhereQuery, HookOptions, WorkflowConfig, WorkerConfig, WorkerOptions, WorkflowContext, WorkflowSearchOptions, WorkflowDataType, WorkflowOptions, } from './durable';
7
+ export { DurableChildErrorType, DurableProxyErrorType, DurableSleepErrorType, DurableWaitForAllErrorType, DurableWaitForErrorType } from "./error";
7
8
  export { ActivityAction, DependencyExport, DurableJobExport, ExportCycles, ExportItem, ExportOptions, ExportTransitions, JobAction, JobExport, JobActionExport, JobTimeline } from './exporter';
8
9
  export { HookCondition, HookConditions, HookGate, HookInterface, HookRule, HookRules, HookSignal } from './hook';
9
10
  export { RedisClientType as IORedisClientType, RedisMultiType as IORedisMultiType } from './ioredisclient';
10
11
  export { ILogger } from './logger';
11
- export { JobData, JobsData, JobMetadata, JobOutput, JobState, JobStatus, PartialJobState } from './job';
12
+ export { JobData, JobsData, JobMetadata, JobOutput, JobState, JobStatus, PartialJobState, ExtensionType } from './job';
12
13
  export { MappingStatements } from './map';
13
14
  export { Pipe, PipeContext, PipeItem, PipeItems, PipeObject, ReduceObject } from './pipe';
14
15
  export { HotMesh, HotMeshApp, HotMeshApps, HotMeshConfig, HotMeshEngine, RedisConfig, HotMeshGraph, HotMeshManifest, HotMeshSettings, HotMeshWorker, KeyStoreParams, KeyType } from './hotmesh';
@@ -1,3 +1,4 @@
1
+ import { StringStringType } from "./serializer";
1
2
  type JobData = Record<string, unknown | Record<string, unknown>>;
2
3
  type JobsData = Record<string, unknown>;
3
4
  type ActivityData = {
@@ -55,6 +56,28 @@ type JobMetadata = {
55
56
  /** process data expire policy */
56
57
  expire?: number;
57
58
  };
59
+ /**
60
+ * User-defined (extended) types for job data. Users may interleave
61
+ * data into the job hash safely by using the `ExtensionType` interface.
62
+ * The data will be prefixed as necessary using an underscore or
63
+ * dash to ensure it is not confused with system process data.
64
+ */
65
+ type ExtensionType = {
66
+ /**
67
+ * Custom search data field (name/value pairs) to seed the Hash.
68
+ * Every field will be prefixed with an underscore before being
69
+ * stored with the initial Hash data set along side system
70
+ * process data.
71
+ */
72
+ search?: StringStringType;
73
+ /**
74
+ * Custom marker data field used for adding a searchable marker to the job.
75
+ * markers always begin with a dash (-). Any field that does not
76
+ * begin with a dash will be removed and will not be inserted with
77
+ * the initial data set.
78
+ */
79
+ marker?: StringStringType;
80
+ };
58
81
  /**
59
82
  * job_status semaphore
60
83
  */
@@ -132,4 +155,4 @@ type JobCompletionOptions = {
132
155
  */
133
156
  expire?: number;
134
157
  };
135
- export { JobCompletionOptions, JobInterruptOptions, JobData, JobsData, JobMetadata, JobOutput, JobState, JobStatus, PartialJobState, };
158
+ export { JobCompletionOptions, JobInterruptOptions, JobData, JobsData, JobMetadata, JobOutput, JobState, JobStatus, PartialJobState, ExtensionType, };
package/modules/errors.ts CHANGED
@@ -1,5 +1,3 @@
1
- import { ActivityDuplex } from "../types/activity";
2
- import { CollationFaultType, CollationStage } from "../types/collator";
3
1
  import {
4
2
  HMSH_CODE_DURABLE_MAXED,
5
3
  HMSH_CODE_DURABLE_TIMEOUT,
@@ -11,6 +9,14 @@ import {
11
9
  HMSH_CODE_DURABLE_CHILD,
12
10
  HMSH_CODE_DURABLE_ALL,
13
11
  HMSH_CODE_DURABLE_SLEEP } from "./enums";
12
+ import { ActivityDuplex } from "../types/activity";
13
+ import { CollationFaultType, CollationStage } from "../types/collator";
14
+ import {
15
+ DurableChildErrorType,
16
+ DurableProxyErrorType,
17
+ DurableSleepErrorType,
18
+ DurableWaitForAllErrorType,
19
+ DurableWaitForErrorType } from "../types/error";
14
20
 
15
21
  class GetStateError extends Error {
16
22
  jobId: string;
@@ -32,13 +38,8 @@ class DurableWaitForError extends Error {
32
38
  workflowId: string;
33
39
  index: number;
34
40
  workflowDimension: string; //hook workflowDimension (e.g., ',0,1,0') (use empty string for `null`)
35
- constructor(params: {
36
- signalId: string,
37
- index: number,
38
- workflowDimension: string
39
- workflowId: string;
40
- }) {
41
- super(`Durable WaitFor Error [${params.workflowId}]`);
41
+ constructor(params: DurableWaitForErrorType) {
42
+ super(`WaitFor Interruption`);
42
43
  this.signalId = params.signalId;
43
44
  this.index = params.index;
44
45
  this.workflowDimension = params.workflowDimension;
@@ -59,20 +60,8 @@ class DurableProxyError extends Error {
59
60
  workflowDimension: string;
60
61
  workflowId: string;
61
62
  workflowTopic: string;
62
- constructor(params: {
63
- arguments: string[],
64
- activityName: string,
65
- backoffCoefficient?: number,
66
- index: number,
67
- maximumAttempts?: number,
68
- maximumInterval?: number,
69
- originJobId: string | null,
70
- parentWorkflowId: string,
71
- workflowDimension: string,
72
- workflowId: string,
73
- workflowTopic: string,
74
- }) {
75
- super(`Durable Proxy Activity Error [${params.parentWorkflowId}] => [${params.workflowId}]`);
63
+ constructor(params: DurableProxyErrorType) {
64
+ super(`ProxyActivity Interruption`);
76
65
  this.arguments = params.arguments;
77
66
  this.workflowId = params.workflowId;
78
67
  this.workflowTopic = params.workflowTopic;
@@ -101,20 +90,8 @@ class DurableChildError extends Error {
101
90
  parentWorkflowId: string;
102
91
  workflowId: string;
103
92
  workflowTopic: string;
104
- constructor(params: {
105
- arguments: string[],
106
- await?: boolean,
107
- backoffCoefficient?: number,
108
- index: number,
109
- maximumAttempts?: number,
110
- maximumInterval?: number,
111
- originJobId: string | null,
112
- parentWorkflowId: string,
113
- workflowDimension: string,
114
- workflowId: string,
115
- workflowTopic: string,
116
- }) {
117
- super(`Durable Child Error [${params.parentWorkflowId}] => [${params.workflowId}]`);
93
+ constructor(params: DurableChildErrorType) {
94
+ super(`ExecChild Interruption`);
118
95
  this.arguments = params.arguments;
119
96
  this.workflowId = params.workflowId;
120
97
  this.workflowTopic = params.workflowTopic;
@@ -140,17 +117,8 @@ class DurableWaitForAllError extends Error {
140
117
  parentWorkflowId: string;
141
118
  workflowId: string;
142
119
  workflowTopic: string;
143
- constructor(params: {
144
- items: string[],
145
- workflowId: string,
146
- workflowTopic: string,
147
- parentWorkflowId: string,
148
- originJobId: string | null,
149
- size: number,
150
- index: number,
151
- workflowDimension: string
152
- }) {
153
- super(`Durable Wait for All Error [${params.parentWorkflowId}] => [${params.workflowId}]`);
120
+ constructor(params: DurableWaitForAllErrorType) {
121
+ super(`Collation Interruption`);
154
122
  this.items = params.items;
155
123
  this.size = params.size;
156
124
  this.workflowId = params.workflowId;
@@ -169,13 +137,8 @@ class DurableSleepError extends Error {
169
137
  duration: number; //seconds
170
138
  index: number;
171
139
  workflowDimension: string; //empty string for null
172
- constructor(params: {
173
- duration: number,
174
- index: number,
175
- workflowDimension: string,
176
- workflowId: string,
177
- }) {
178
- super(`Durable Sleep Error [${params.workflowId}]`);
140
+ constructor(params: DurableSleepErrorType) {
141
+ super(`SleepFor Interruption`);
179
142
  this.duration = params.duration;
180
143
  this.workflowId = params.workflowId;
181
144
  this.index = params.index;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/hotmesh",
3
- "version": "0.0.53",
3
+ "version": "0.0.55",
4
4
  "description": "Unbreakable Workflows",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -86,7 +86,14 @@ class Hook extends Activity {
86
86
  * does this activity use a time-hook or web-hook
87
87
  */
88
88
  doesHook(): boolean {
89
- return !!(this.config.hook?.topic || this.config.sleep);
89
+ if (this.config.sleep) {
90
+ const duration = Pipe.resolve(
91
+ this.config.sleep,
92
+ this.context,
93
+ );
94
+ return !isNaN(duration) && Number(duration) > 0
95
+ }
96
+ return !!this.config.hook?.topic;
90
97
  }
91
98
 
92
99
  async doHook(telemetry: TelemetryService) {
@@ -15,7 +15,7 @@ import {
15
15
  ActivityMetadata,
16
16
  ActivityType,
17
17
  TriggerActivity } from '../../types/activity';
18
- import { JobState } from '../../types/job';
18
+ import { JobState, ExtensionType } from '../../types/job';
19
19
  import { RedisMulti } from '../../types/redis';
20
20
  import { StringScalarType } from '../../types/serializer';
21
21
  import { WorkListTaskType } from '../../types/task';
@@ -33,7 +33,7 @@ class Trigger extends Activity {
33
33
  super(config, data, metadata, hook, engine, context);
34
34
  }
35
35
 
36
- async process(): Promise<string> {
36
+ async process(options?: ExtensionType): Promise<string> {
37
37
  this.logger.debug('trigger-process', { subscribes: this.config.subscribes });
38
38
  let telemetry: TelemetryService;
39
39
  try {
@@ -48,6 +48,9 @@ class Trigger extends Activity {
48
48
  this.adjacencyList = await this.filterAdjacent();
49
49
  await this.setStatus(this.adjacencyList.length);
50
50
 
51
+ this.bindSearchData(options);
52
+ this.bindMarkerData(options);
53
+
51
54
  const multi = this.store.getMulti();
52
55
  await this.setState(multi);
53
56
  await this.setStats(multi);
@@ -83,6 +86,28 @@ class Trigger extends Activity {
83
86
  }
84
87
  }
85
88
 
89
+ safeKey(key:string): string {
90
+ return `_${key}`;
91
+ }
92
+
93
+ bindSearchData(options?: ExtensionType): void {
94
+ if (options?.search) {
95
+ Object.keys(options.search).forEach((key) => {
96
+ this.context.data[this.safeKey(key)] = options.search[key].toString();
97
+ });
98
+ }
99
+ }
100
+
101
+ bindMarkerData(options?: ExtensionType): void {
102
+ if (options?.marker) {
103
+ Object.keys(options.marker).forEach((key) => {
104
+ if (key.startsWith('-')) {
105
+ this.context.data[key] = options.marker[key].toString();
106
+ }
107
+ });
108
+ }
109
+ }
110
+
86
111
  async setStatus(amount: number): Promise<void> {
87
112
  this.context.metadata.js = amount;
88
113
  }
@@ -164,15 +164,9 @@ export class ClientService {
164
164
  const jobId = await hotMeshClient.pub(
165
165
  `${options.namespace ?? APP_ID}.execute`,
166
166
  payload,
167
- context as JobState
167
+ context as JobState,
168
+ { search: options?.search?.data, marker: options?.marker},
168
169
  );
169
- // Seed search data
170
- if (jobId && options.search?.data) {
171
- const searchSessionId = `-search-0`;
172
- const search = new Search(jobId, hotMeshClient, searchSessionId);
173
- const entries = Object.entries(options.search.data).flat();
174
- await search.set(...entries);
175
- }
176
170
  return new WorkflowHandleService(hotMeshClient, workflowTopic, jobId);
177
171
  },
178
172
 
@@ -1,22 +1,22 @@
1
- import { VALSEP } from '../../modules/key';
2
1
  import { restoreHierarchy } from '../../modules/utils';
3
2
  import { ILogger } from '../logger';
4
3
  import { SerializerService } from '../serializer';
5
4
  import { StoreService } from '../store';
6
5
  import {
7
- ExportItem,
8
6
  ExportOptions,
9
7
  DurableJobExport,
10
- IdemType,
11
- TimelineEntry } from '../../types/exporter';
8
+ TimelineType,
9
+ TransitionType,
10
+ ExportFields} from '../../types/exporter';
12
11
  import { RedisClient, RedisMulti } from '../../types/redis';
13
- import { StringStringType, Symbols } from "../../types/serializer";
12
+ import { StringAnyType, StringStringType, Symbols } from "../../types/serializer";
14
13
 
15
14
  class ExporterService {
16
15
  appId: string;
17
16
  logger: ILogger;
18
17
  store: StoreService<RedisClient, RedisMulti>;
19
18
  symbols: Promise<Symbols> | Symbols;
19
+ private static symbols: Map<string, Symbols> = new Map();
20
20
 
21
21
  constructor(appId: string, store: StoreService<RedisClient, RedisMulti>, logger: ILogger) {
22
22
  this.appId = appId;
@@ -29,173 +29,129 @@ class ExporterService {
29
29
  * facets that describe the workflow in terms relevant to narrative storytelling.
30
30
  */
31
31
  async export(jobId: string, options: ExportOptions = {}): Promise<DurableJobExport> {
32
- if (!this.symbols) {
33
- this.symbols = this.store.getAllSymbols();
34
- this.symbols = await this.symbols;
32
+ if (!ExporterService.symbols.has(this.appId)) {
33
+ const symbols: Symbols | Promise<Symbols> = this.store.getAllSymbols();
34
+ ExporterService.symbols.set(this.appId, await symbols);
35
35
  }
36
36
  const jobData = await this.store.getRaw(jobId);
37
- const jobExport = this.inflate(jobData/*, depData*/);
37
+ const jobExport = this.inflate(jobData, options);
38
38
  return jobExport;
39
39
  }
40
40
 
41
- /**
42
- * Inflates the key from Redis, 3-character symbol
43
- * into a human-readable JSON path, reflecting the
44
- * tree-like structure of the unidimensional Hash
45
- */
46
- inflateKey(key: string): string {
47
- if (key in this.symbols) {
48
- const path = this.symbols[key];
49
- const parts = path.split('/');
50
- return parts.join('/');
51
- }
52
- return key;
53
- }
54
-
55
- /**
56
- * Inflates the dependency data from Redis into a DurableJobExport object by
57
- * organizing the dimensional isolate in sch a way asto interleave
58
- * into a story
59
- * @param data - the dependency data from Redis
60
- * @returns - the organized dependency data
61
- */
62
- inflateDependencyData(data: string[]): Record<string, any>[] {
63
- return data.map((dependency, index: number): Record<string, any> => {
64
- const [action, topic, gid, dimension, ...jid] = dependency.split(VALSEP);
65
- const job_id = jid.join(VALSEP);
66
- return {
67
- index,
68
- action,
69
- topic,
70
- gid,
71
- dimension,
72
- job_id,
73
- }
74
- });
75
- }
76
-
77
41
  /**
78
42
  * Inflates the job data from Redis into a DurableJobExport object
79
43
  * @param jobHash - the job data from Redis
80
44
  * @param dependencyList - the list of dependencies for the job
81
45
  * @returns - the inflated job data
82
46
  */
83
- inflate(jobHash: StringStringType): DurableJobExport {
84
- const idempotents: IdemType[] = [];
85
- const state: StringStringType = {};
47
+ inflate(jobHash: StringStringType, options: ExportOptions): DurableJobExport {
48
+ const timeline: TimelineType[] = [];
49
+ const state: StringAnyType = {};
86
50
  const data: StringStringType = {};
87
- const other: ExportItem[] = [];
88
- const replay: Record<string, TimelineEntry> = {};
51
+ const transitionsObject: Record<string, TransitionType> = {};
89
52
  const regex = /^([a-zA-Z]{3}),(\d+(?:,\d+)*)/;
90
53
 
91
54
  Object.entries(jobHash).forEach(([key, value]) => {
92
55
  const match = key.match(regex);
56
+
93
57
  if (match) {
94
- //activity process state
95
- this.inflateProcess(match, value, replay);
58
+ //transitions
59
+ this.inflateTransition(match, value, transitionsObject);
60
+
96
61
  } else if (key.length === 3) {
97
- //job state
62
+ //state
98
63
  state[this.inflateKey(key)] = SerializerService.fromString(value);
64
+
99
65
  } else if (key.startsWith('_')) {
100
- //job data
66
+ //data
101
67
  data[key.substring(1)] = value;
68
+
102
69
  } else if (key.startsWith('-')) {
103
- //actions with side effect (replayable)
104
- idempotents.push({
70
+ //timeline
71
+ const keyParts = this.keyToObject(key); //key parts have meaning
72
+ timeline.push({
73
+ ...keyParts,
105
74
  key,
106
- value: SerializerService.fromString(value),
107
- parts: extractParts(key),
75
+ value: this.resolveValue(value, options.values),
108
76
  });
109
- } else {
110
- //collator guids, etc
111
- other.push([null, key, value]);
112
77
  }
113
78
  });
114
79
 
115
- const sortEntriesByCreated = (obj: { [key: string]: TimelineEntry }): TimelineEntry[] => {
116
- const entriesArray: TimelineEntry[] = Object.values(obj);
117
- entriesArray.sort((a, b) => {
118
- return (a.created || a.updated).localeCompare(b.created || b.updated);
119
- });
120
- return entriesArray;
121
- }
80
+ return this.filterFields({
81
+ data: restoreHierarchy(data),
82
+ state: Object.entries(restoreHierarchy(state))[0][1],
83
+ status: parseInt(jobHash[':'], 10),
84
+ timeline: this.sortParts(timeline),
85
+ transitions: this.sortEntriesByCreated(transitionsObject),
86
+ }, options.block, options.allow);
87
+ }
122
88
 
123
- /**
124
- * idem list has a complicated sort order based on indexes and dimensions
125
- */
126
- const sortParts = (parts: IdemType[]): IdemType[]=> {
127
- return parts.sort((a, b) => {
128
- const { dimension: aDim, index: aIdx, secondary: aSec } = a.parts;
129
- const { dimension: bDim, index: bIdx, secondary: bSec } = b.parts;
130
-
131
- if (aDim === undefined && bDim !== undefined) return -1;
132
- if (aDim !== undefined && bDim === undefined) return 1;
133
- if (aDim !== undefined && bDim !== undefined) {
134
- if (aDim < bDim) return -1;
135
- if (aDim > bDim) return 1;
136
- }
137
-
138
- if (aIdx < bIdx) return -1;
139
- if (aIdx > bIdx) return 1;
140
-
141
- if (aSec === undefined && bSec !== undefined) return -1;
142
- if (aSec !== undefined && bSec === undefined) return 1;
143
- if (aSec !== undefined && bSec !== undefined) {
144
- if (aSec < bSec) return -1;
145
- if (aSec > bSec) return 1;
146
- }
147
-
148
- return 0;
149
- });
150
- };
151
-
152
- function extractParts(key: string): {index: number, dimension?: string, secondary?: number} {
153
- function extractDimension(label: string): string {
154
- const parts = label.split(',');
155
- if (parts.length > 1) {
156
- parts.shift();
157
- return parts.join(',');
158
- }
89
+ resolveValue(raw: string, withValues: boolean): Record<string, any> | string | number | null {
90
+ const resolved = SerializerService.fromString(raw);
91
+ if (withValues !== false) {
92
+ return resolved;
93
+ }
94
+ if (resolved && typeof resolved === 'object') {
95
+ if ('data' in resolved) {
96
+ resolved.data = {};
97
+ }
98
+ if ('$error' in resolved) {
99
+ resolved.$error = {};
159
100
  }
101
+ }
102
+ return resolved;
103
+ }
160
104
 
161
- const parts = key.split('-');
162
- if (parts.length === 4) {
163
- //-proxy-5- -search-1-1-
164
- return {
165
- index: parseInt(parts[2], 10),
166
- dimension: extractDimension(parts[1]),
105
+ /**
106
+ * Inflates the key from Redis, 3-character symbol
107
+ * into a human-readable JSON path, reflecting the
108
+ * tree-like structure of the unidimensional Hash
109
+ * @private
110
+ */
111
+ inflateKey(key: string): string {
112
+ const symbols = ExporterService.symbols.get(this.appId);
113
+ if (key in symbols) {
114
+ const path = symbols[key];
115
+ const parts = path.split('/');
116
+ return parts.join('/');
117
+ }
118
+ return key;
119
+ }
120
+
121
+ filterFields(fullObject: DurableJobExport, block: ExportFields[] = [], allow: ExportFields[] = []): Partial<DurableJobExport> {
122
+ let result: Partial<DurableJobExport> = {};
123
+ if (allow && allow.length > 0) {
124
+ allow.forEach(field => {
125
+ if (field in fullObject) {
126
+ result[field] = fullObject[field] as StringAnyType & number & TimelineType[] & TransitionType[];
167
127
  }
168
- } else {
169
- //-search,0,0-1-1- -proxy,0,0-1-
170
- return {
171
- index: parseInt(parts[2], 10),
172
- secondary: parseInt(parts[3], 10),
173
- dimension: extractDimension(parts[1]),
128
+ });
129
+ } else {
130
+ result = { ...fullObject };
131
+ }
132
+ if (block && block.length > 0) {
133
+ block.forEach(field => {
134
+ if (field in result) {
135
+ delete result[field];
174
136
  }
175
- }
137
+ });
176
138
  }
177
-
178
- return {
179
- data: restoreHierarchy(data),
180
- idempotents: sortParts(idempotents),
181
- state: Object.entries(restoreHierarchy(state))[0][1],
182
- status: jobHash[':'],
183
- replay: sortEntriesByCreated(replay),
184
- };
139
+ return result as DurableJobExport;
185
140
  }
186
141
 
187
- inflateProcess(match: RegExpMatchArray, value: string, replay: Record<string, Record<string, any>>) {
142
+ inflateTransition(match: RegExpMatchArray, value: string, transitionsObject: Record<string, TransitionType>) {
188
143
  const [_, letters, dimensions] = match;
189
144
  const path = this.inflateKey(letters);
190
145
  const parts = path.split('/');
191
146
  const activity = parts[0];
192
147
  const isCreate = path.endsWith('/output/metadata/ac');
193
148
  const isUpdate = path.endsWith('/output/metadata/au');
149
+ //for now only export activity start/stop; activity data would also be interesting
194
150
  if (isCreate || isUpdate) {
195
151
  const targetName = `${activity},${dimensions}`;
196
- let target = replay[targetName];
152
+ let target = transitionsObject[targetName];
197
153
  if (!target) {
198
- replay[targetName] = {
154
+ transitionsObject[targetName] = {
199
155
  activity,
200
156
  dimensions,
201
157
  created: isCreate ? value : null,
@@ -206,6 +162,71 @@ class ExporterService {
206
162
  }
207
163
  }
208
164
  }
165
+
166
+ sortEntriesByCreated(obj: { [key: string]: TransitionType }): TransitionType[] {
167
+ const entriesArray: TransitionType[] = Object.values(obj);
168
+ entriesArray.sort((a, b) => {
169
+ return (a.created || a.updated).localeCompare(b.created || b.updated);
170
+ });
171
+ return entriesArray;
172
+ }
173
+
174
+ /**
175
+ * marker names are overloaded with details like sequence, type, etc
176
+ */
177
+ keyToObject(key: string): {index: number, dimension?: string, secondary?: number} {
178
+ function extractDimension(label: string): string {
179
+ const parts = label.split(',');
180
+ if (parts.length > 1) {
181
+ parts.shift();
182
+ return parts.join(',');
183
+ }
184
+ }
185
+ const parts = key.split('-');
186
+ if (parts.length === 4) {
187
+ //-proxy-5- -search-1-1-
188
+ return {
189
+ index: parseInt(parts[2], 10),
190
+ dimension: extractDimension(parts[1]),
191
+ }
192
+ } else {
193
+ //-search,0,0-1-1- -proxy,0,0-1-
194
+ return {
195
+ index: parseInt(parts[2], 10),
196
+ secondary: parseInt(parts[3], 10),
197
+ dimension: extractDimension(parts[1]),
198
+ }
199
+ }
200
+ }
201
+
202
+ /**
203
+ * idem list has a complicated sort order based on indexes and dimensions
204
+ */
205
+ sortParts(parts: TimelineType[]): TimelineType[] {
206
+ return parts.sort((a, b) => {
207
+ const { dimension: aDim, index: aIdx, secondary: aSec } = a;
208
+ const { dimension: bDim, index: bIdx, secondary: bSec } = b;
209
+
210
+ if (aDim === undefined && bDim !== undefined) return -1;
211
+ if (aDim !== undefined && bDim === undefined) return 1;
212
+ if (aDim !== undefined && bDim !== undefined) {
213
+ if (aDim < bDim) return -1;
214
+ if (aDim > bDim) return 1;
215
+ }
216
+
217
+ if (aIdx < bIdx) return -1;
218
+ if (aIdx > bIdx) return 1;
219
+
220
+ if (aSec === undefined && bSec !== undefined) return -1;
221
+ if (aSec !== undefined && bSec === undefined) return 1;
222
+ if (aSec !== undefined && bSec !== undefined) {
223
+ if (aSec < bSec) return -1;
224
+ if (aSec > bSec) return 1;
225
+ }
226
+
227
+ return 0;
228
+ });
229
+ };
209
230
  }
210
231
 
211
232
  export { ExporterService };
@@ -1,6 +1,6 @@
1
1
  import { ExporterService } from './exporter';
2
2
  import { HotMeshService as HotMesh } from '../hotmesh';
3
- import { DurableJobExport } from '../../types/exporter';
3
+ import { DurableJobExport, ExportOptions } from '../../types/exporter';
4
4
  import { JobInterruptOptions, JobOutput } from '../../types/job';
5
5
  import { StreamError } from '../../types/stream';
6
6
 
@@ -21,8 +21,8 @@ export class WorkflowHandleService {
21
21
  );
22
22
  }
23
23
 
24
- async export(): Promise<DurableJobExport> {
25
- return this.exporter.export(this.workflowId);
24
+ async export(options?: ExportOptions): Promise<DurableJobExport> {
25
+ return this.exporter.export(this.workflowId, options);
26
26
  }
27
27
 
28
28
  /**
@@ -288,7 +288,7 @@ export class WorkerService {
288
288
  const workflowInput = data.data as unknown as WorkflowDataType;
289
289
  const execIndex = counter.counter - interruptionRegistry.length + 1;
290
290
  const { workflowId, workflowTopic, workflowDimension, originJobId } = workflowInput;
291
- const collatorFlowId = `-${workflowId}-$COLLATOR${workflowDimension || ''}-${execIndex}`;
291
+ const collatorFlowId = `-${workflowId}-$${workflowDimension || ''}-$${execIndex}`;
292
292
  return {
293
293
  status: StreamStatus.SUCCESS,
294
294
  code: HMSH_CODE_DURABLE_ALL,