@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.
- package/README.md +0 -3
- package/build/modules/errors.d.ts +6 -48
- package/build/modules/errors.js +5 -5
- package/build/package.json +1 -1
- package/build/services/activities/hook.js +5 -1
- package/build/services/activities/trigger.d.ts +5 -2
- package/build/services/activities/trigger.js +22 -1
- package/build/services/durable/client.js +1 -8
- package/build/services/durable/exporter.d.ts +24 -13
- package/build/services/durable/exporter.js +145 -127
- package/build/services/durable/handle.d.ts +2 -2
- package/build/services/durable/handle.js +2 -2
- package/build/services/durable/worker.js +1 -1
- package/build/services/durable/workflow.d.ts +29 -17
- package/build/services/durable/workflow.js +117 -97
- package/build/services/engine/index.d.ts +2 -2
- package/build/services/engine/index.js +2 -2
- package/build/services/hotmesh/index.d.ts +2 -2
- package/build/services/hotmesh/index.js +2 -2
- package/build/types/durable.d.ts +15 -3
- package/build/types/error.d.ts +48 -0
- package/build/types/error.js +2 -0
- package/build/types/exporter.d.ts +26 -20
- package/build/types/index.d.ts +2 -1
- package/build/types/job.d.ts +24 -1
- package/modules/errors.ts +18 -55
- package/package.json +1 -1
- package/services/activities/hook.ts +8 -1
- package/services/activities/trigger.ts +27 -2
- package/services/durable/client.ts +2 -8
- package/services/durable/exporter.ts +149 -128
- package/services/durable/handle.ts +3 -3
- package/services/durable/worker.ts +1 -1
- package/services/durable/workflow.ts +137 -104
- package/services/engine/index.ts +4 -3
- package/services/hotmesh/index.ts +4 -3
- package/types/durable.ts +18 -3
- package/types/error.ts +52 -0
- package/types/exporter.ts +31 -23
- package/types/index.ts +8 -1
- package/types/job.ts +27 -0
package/build/types/index.d.ts
CHANGED
|
@@ -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';
|
package/build/types/job.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
@@ -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
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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.
|
|
33
|
-
|
|
34
|
-
this.
|
|
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
|
|
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
|
|
85
|
-
const state:
|
|
47
|
+
inflate(jobHash: StringStringType, options: ExportOptions): DurableJobExport {
|
|
48
|
+
const timeline: TimelineType[] = [];
|
|
49
|
+
const state: StringAnyType = {};
|
|
86
50
|
const data: StringStringType = {};
|
|
87
|
-
const
|
|
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
|
-
//
|
|
95
|
-
this.
|
|
58
|
+
//transitions
|
|
59
|
+
this.inflateTransition(match, value, transitionsObject);
|
|
60
|
+
|
|
96
61
|
} else if (key.length === 3) {
|
|
97
|
-
//
|
|
62
|
+
//state
|
|
98
63
|
state[this.inflateKey(key)] = SerializerService.fromString(value);
|
|
64
|
+
|
|
99
65
|
} else if (key.startsWith('_')) {
|
|
100
|
-
//
|
|
66
|
+
//data
|
|
101
67
|
data[key.substring(1)] = value;
|
|
68
|
+
|
|
102
69
|
} else if (key.startsWith('-')) {
|
|
103
|
-
//
|
|
104
|
-
|
|
70
|
+
//timeline
|
|
71
|
+
const keyParts = this.keyToObject(key); //key parts have meaning
|
|
72
|
+
timeline.push({
|
|
73
|
+
...keyParts,
|
|
105
74
|
key,
|
|
106
|
-
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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 =
|
|
152
|
+
let target = transitionsObject[targetName];
|
|
197
153
|
if (!target) {
|
|
198
|
-
|
|
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}
|
|
291
|
+
const collatorFlowId = `-${workflowId}-$${workflowDimension || ''}-$${execIndex}`;
|
|
292
292
|
return {
|
|
293
293
|
status: StreamStatus.SUCCESS,
|
|
294
294
|
code: HMSH_CODE_DURABLE_ALL,
|