@hotmeshio/hotmesh 0.0.20 → 0.0.22
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/package.json +1 -1
- package/build/services/durable/search.js +2 -3
- package/build/services/durable/workflow.d.ts +5 -1
- package/build/services/durable/workflow.js +49 -12
- package/build/types/durable.d.ts +3 -3
- package/package.json +1 -1
- package/services/durable/search.ts +2 -3
- package/services/durable/workflow.ts +52 -11
- package/types/durable.ts +3 -3
package/README.md
CHANGED
|
@@ -226,9 +226,6 @@ Exchanging data between activities is central to HotMesh. For detailed informati
|
|
|
226
226
|
## Composition
|
|
227
227
|
While the simplest graphs are linear, detailing a consistent sequence of non-cyclical activities, graphs can be layered to represent intricate business scenarios. Some can even be designed to accommodate long-lasting workflows that span months. For more details, check out the [Composable Workflow Guide](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/composable_workflow.md).
|
|
228
228
|
|
|
229
|
-
## Architectural First Principles
|
|
230
|
-
For a deep dive into HotMesh's distributed orchestration philosophy, refer to the [Architectural First Principles Overview](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/architecture.md).
|
|
231
|
-
|
|
232
229
|
## Distributed Orchestration
|
|
233
230
|
HotMesh is a distributed orchestration engine. Refer to the [Distributed Orchestration Guide](https://github.com/hotmeshio/sdk-typescript/blob/main/docs/distributed_orchestration.md) for a detailed breakdown of the approach.
|
|
234
231
|
|
package/build/package.json
CHANGED
|
@@ -12,10 +12,9 @@ class Search {
|
|
|
12
12
|
this.searchSessionIndex = 0;
|
|
13
13
|
const keyParams = {
|
|
14
14
|
appId: hotMeshClient.appId,
|
|
15
|
-
jobId:
|
|
15
|
+
jobId: workflowId
|
|
16
16
|
};
|
|
17
|
-
|
|
18
|
-
this.jobId = `${hotMeshPrefix}${workflowId}`;
|
|
17
|
+
this.jobId = key_1.KeyService.mintKey(hotMeshClient.namespace, key_1.KeyType.JOB_STATE, keyParams);
|
|
19
18
|
this.searchSessionId = searchSessionId;
|
|
20
19
|
this.hotMeshClient = hotMeshClient;
|
|
21
20
|
this.store = hotMeshClient.engine.store;
|
|
@@ -3,9 +3,13 @@ import { HotMeshService as HotMesh } from '../hotmesh';
|
|
|
3
3
|
import { ActivityConfig, HookOptions, ProxyType, WorkflowOptions } from "../../types/durable";
|
|
4
4
|
export declare class WorkflowService {
|
|
5
5
|
/**
|
|
6
|
-
* Spawn a child workflow. await the result.
|
|
6
|
+
* Spawn a child workflow. await and return the result.
|
|
7
7
|
*/
|
|
8
8
|
static executeChild<T>(options: WorkflowOptions): Promise<T>;
|
|
9
|
+
/**
|
|
10
|
+
* spawn a child workflow. return the childJobId.
|
|
11
|
+
*/
|
|
12
|
+
static startChild<T>(options: WorkflowOptions): Promise<string>;
|
|
9
13
|
static proxyActivities<ACT>(options?: ActivityConfig): ProxyType<ACT>;
|
|
10
14
|
static search(): Promise<Search>;
|
|
11
15
|
/**
|
|
@@ -16,37 +16,79 @@ const worker_1 = require("./worker");
|
|
|
16
16
|
const stream_1 = require("../../types/stream");
|
|
17
17
|
class WorkflowService {
|
|
18
18
|
/**
|
|
19
|
-
* Spawn a child workflow. await the result.
|
|
19
|
+
* Spawn a child workflow. await and return the result.
|
|
20
20
|
*/
|
|
21
21
|
static async executeChild(options) {
|
|
22
22
|
const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
|
|
23
|
+
const namespace = store.get('namespace');
|
|
23
24
|
const workflowId = store.get('workflowId');
|
|
24
25
|
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
25
26
|
const workflowTrace = store.get('workflowTrace');
|
|
26
27
|
const workflowSpan = store.get('workflowSpan');
|
|
27
28
|
const COUNTER = store.get('counter');
|
|
28
29
|
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
29
|
-
|
|
30
|
+
//this is risky but MUST be allowed. Users MAY set the workflowId,
|
|
31
|
+
//but if there is a naming collision, the data from the target entity will be used
|
|
32
|
+
//as there is know way of knowing if the item was generated via a prior run of the workflow
|
|
33
|
+
const childJobId = options.workflowId ?? `${workflowId}-$${options.workflowName}${workflowDimension}-${execIndex}`;
|
|
30
34
|
const parentWorkflowId = `${workflowId}-f`;
|
|
31
35
|
const client = new client_1.ClientService({
|
|
32
36
|
connection: await connection_1.ConnectionService.connect(worker_1.WorkerService.connection),
|
|
33
37
|
});
|
|
34
|
-
let handle = await client.workflow.getHandle(options.taskQueue, options.workflowName, childJobId);
|
|
38
|
+
let handle = await client.workflow.getHandle(options.taskQueue, options.workflowName, childJobId, namespace);
|
|
35
39
|
try {
|
|
36
40
|
return await handle.result(true);
|
|
37
41
|
}
|
|
38
42
|
catch (error) {
|
|
39
43
|
handle = await client.workflow.start({
|
|
40
44
|
...options,
|
|
45
|
+
namespace,
|
|
41
46
|
workflowId: childJobId,
|
|
42
47
|
parentWorkflowId,
|
|
43
48
|
workflowTrace,
|
|
44
49
|
workflowSpan,
|
|
45
50
|
});
|
|
51
|
+
//todo: options.startToCloseTimeout
|
|
46
52
|
const result = await handle.result();
|
|
47
53
|
return result;
|
|
48
54
|
}
|
|
49
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* spawn a child workflow. return the childJobId.
|
|
58
|
+
*/
|
|
59
|
+
static async startChild(options) {
|
|
60
|
+
const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
|
|
61
|
+
const namespace = store.get('namespace');
|
|
62
|
+
const workflowId = store.get('workflowId');
|
|
63
|
+
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
64
|
+
const workflowTrace = store.get('workflowTrace');
|
|
65
|
+
const workflowSpan = store.get('workflowSpan');
|
|
66
|
+
const COUNTER = store.get('counter');
|
|
67
|
+
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
68
|
+
const childJobId = options.workflowId ?? `${workflowId}-$${options.workflowName}${workflowDimension}-${execIndex}`;
|
|
69
|
+
const parentWorkflowId = `${workflowId}-f`;
|
|
70
|
+
const workflowTopic = `${options.taskQueue}-${options.workflowName}`;
|
|
71
|
+
try {
|
|
72
|
+
//get the status; if there is no error, return childJobId (what was spawned)
|
|
73
|
+
const hotMeshClient = await worker_1.WorkerService.getHotMesh(workflowTopic, { namespace });
|
|
74
|
+
await hotMeshClient.getStatus(childJobId);
|
|
75
|
+
return childJobId;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
const client = new client_1.ClientService({
|
|
79
|
+
connection: await connection_1.ConnectionService.connect(worker_1.WorkerService.connection),
|
|
80
|
+
});
|
|
81
|
+
await client.workflow.start({
|
|
82
|
+
...options,
|
|
83
|
+
namespace,
|
|
84
|
+
workflowId: childJobId,
|
|
85
|
+
parentWorkflowId,
|
|
86
|
+
workflowTrace,
|
|
87
|
+
workflowSpan,
|
|
88
|
+
});
|
|
89
|
+
return childJobId;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
50
92
|
static proxyActivities(options) {
|
|
51
93
|
if (options.activities) {
|
|
52
94
|
worker_1.WorkerService.registerActivities(options.activities);
|
|
@@ -119,16 +161,11 @@ class WorkflowService {
|
|
|
119
161
|
const hotMeshClient = await worker_1.WorkerService.getHotMesh(`${namespace}.flow.signal`, { namespace });
|
|
120
162
|
if (await WorkflowService.isSideEffectAllowed(hotMeshClient, 'hook')) {
|
|
121
163
|
const store = asyncLocalStorage_1.asyncLocalStorage.getStore();
|
|
122
|
-
|
|
123
|
-
let workflowTopic;
|
|
124
|
-
if (options.
|
|
125
|
-
workflowId = options.workflowId;
|
|
164
|
+
const workflowId = options.workflowId ?? store.get('workflowId');
|
|
165
|
+
let workflowTopic = store.get('workflowTopic');
|
|
166
|
+
if (options.taskQueue && options.workflowName) {
|
|
126
167
|
workflowTopic = `${options.taskQueue}-${options.workflowName}`;
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
workflowId = store.get('workflowId');
|
|
130
|
-
workflowTopic = store.get('workflowTopic');
|
|
131
|
-
}
|
|
168
|
+
} //else this is essentially recursion as the function calls itself
|
|
132
169
|
const payload = {
|
|
133
170
|
arguments: [...options.args],
|
|
134
171
|
id: workflowId,
|
package/build/types/durable.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ type WorkflowOptions = {
|
|
|
18
18
|
namespace?: string;
|
|
19
19
|
taskQueue: string;
|
|
20
20
|
args: any[];
|
|
21
|
-
workflowId
|
|
21
|
+
workflowId?: string;
|
|
22
22
|
workflowName?: string;
|
|
23
23
|
parentWorkflowId?: string;
|
|
24
24
|
workflowTrace?: string;
|
|
@@ -28,9 +28,9 @@ type WorkflowOptions = {
|
|
|
28
28
|
};
|
|
29
29
|
type HookOptions = {
|
|
30
30
|
namespace?: string;
|
|
31
|
-
taskQueue
|
|
31
|
+
taskQueue?: string;
|
|
32
32
|
args: any[];
|
|
33
|
-
workflowId
|
|
33
|
+
workflowId?: string;
|
|
34
34
|
workflowName?: string;
|
|
35
35
|
search?: WorkflowSearchOptions;
|
|
36
36
|
config?: WorkflowConfig;
|
package/package.json
CHANGED
|
@@ -19,10 +19,9 @@ export class Search {
|
|
|
19
19
|
constructor(workflowId: string, hotMeshClient: HotMesh, searchSessionId: string) {
|
|
20
20
|
const keyParams = {
|
|
21
21
|
appId: hotMeshClient.appId,
|
|
22
|
-
jobId:
|
|
22
|
+
jobId: workflowId
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
this.jobId = `${hotMeshPrefix}${workflowId}`;
|
|
24
|
+
this.jobId = KeyService.mintKey(hotMeshClient.namespace, KeyType.JOB_STATE, keyParams);
|
|
26
25
|
this.searchSessionId = searchSessionId;
|
|
27
26
|
this.hotMeshClient = hotMeshClient;
|
|
28
27
|
this.store = hotMeshClient.engine.store as StoreService<RedisClient, RedisMulti>;
|
|
@@ -23,17 +23,21 @@ import { StreamStatus } from '../../types/stream';
|
|
|
23
23
|
export class WorkflowService {
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Spawn a child workflow. await the result.
|
|
26
|
+
* Spawn a child workflow. await and return the result.
|
|
27
27
|
*/
|
|
28
28
|
static async executeChild<T>(options: WorkflowOptions): Promise<T> {
|
|
29
29
|
const store = asyncLocalStorage.getStore();
|
|
30
|
+
const namespace = store.get('namespace');
|
|
30
31
|
const workflowId = store.get('workflowId');
|
|
31
32
|
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
32
33
|
const workflowTrace = store.get('workflowTrace');
|
|
33
34
|
const workflowSpan = store.get('workflowSpan');
|
|
34
35
|
const COUNTER = store.get('counter');
|
|
35
36
|
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
36
|
-
|
|
37
|
+
//this is risky but MUST be allowed. Users MAY set the workflowId,
|
|
38
|
+
//but if there is a naming collision, the data from the target entity will be used
|
|
39
|
+
//as there is know way of knowing if the item was generated via a prior run of the workflow
|
|
40
|
+
const childJobId = options.workflowId ?? `${workflowId}-$${options.workflowName}${workflowDimension}-${execIndex}`;
|
|
37
41
|
const parentWorkflowId = `${workflowId}-f`;
|
|
38
42
|
|
|
39
43
|
const client = new Client({
|
|
@@ -43,7 +47,8 @@ export class WorkflowService {
|
|
|
43
47
|
let handle = await client.workflow.getHandle(
|
|
44
48
|
options.taskQueue,
|
|
45
49
|
options.workflowName,
|
|
46
|
-
childJobId
|
|
50
|
+
childJobId,
|
|
51
|
+
namespace,
|
|
47
52
|
);
|
|
48
53
|
|
|
49
54
|
try {
|
|
@@ -51,16 +56,56 @@ export class WorkflowService {
|
|
|
51
56
|
} catch (error) {
|
|
52
57
|
handle = await client.workflow.start({
|
|
53
58
|
...options,
|
|
59
|
+
namespace,
|
|
54
60
|
workflowId: childJobId,
|
|
55
61
|
parentWorkflowId,
|
|
56
62
|
workflowTrace,
|
|
57
63
|
workflowSpan,
|
|
58
64
|
});
|
|
65
|
+
//todo: options.startToCloseTimeout
|
|
59
66
|
const result = await handle.result();
|
|
60
67
|
return result as T;
|
|
61
68
|
}
|
|
62
69
|
}
|
|
63
70
|
|
|
71
|
+
/**
|
|
72
|
+
* spawn a child workflow. return the childJobId.
|
|
73
|
+
*/
|
|
74
|
+
static async startChild<T>(options: WorkflowOptions): Promise<string> {
|
|
75
|
+
const store = asyncLocalStorage.getStore();
|
|
76
|
+
const namespace = store.get('namespace');
|
|
77
|
+
const workflowId = store.get('workflowId');
|
|
78
|
+
const workflowDimension = store.get('workflowDimension') ?? '';
|
|
79
|
+
const workflowTrace = store.get('workflowTrace');
|
|
80
|
+
const workflowSpan = store.get('workflowSpan');
|
|
81
|
+
const COUNTER = store.get('counter');
|
|
82
|
+
const execIndex = COUNTER.counter = COUNTER.counter + 1;
|
|
83
|
+
const childJobId = options.workflowId ?? `${workflowId}-$${options.workflowName}${workflowDimension}-${execIndex}`;
|
|
84
|
+
const parentWorkflowId = `${workflowId}-f`;
|
|
85
|
+
const workflowTopic = `${options.taskQueue}-${options.workflowName}`;
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
//get the status; if there is no error, return childJobId (what was spawned)
|
|
89
|
+
const hotMeshClient = await WorkerService.getHotMesh(workflowTopic, { namespace });
|
|
90
|
+
await hotMeshClient.getStatus(childJobId);
|
|
91
|
+
return childJobId;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
const client = new Client({
|
|
94
|
+
connection: await Connection.connect(WorkerService.connection),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
await client.workflow.start({
|
|
98
|
+
...options,
|
|
99
|
+
namespace,
|
|
100
|
+
workflowId: childJobId,
|
|
101
|
+
parentWorkflowId,
|
|
102
|
+
workflowTrace,
|
|
103
|
+
workflowSpan,
|
|
104
|
+
});
|
|
105
|
+
return childJobId;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
64
109
|
static proxyActivities<ACT>(options?: ActivityConfig): ProxyType<ACT> {
|
|
65
110
|
if (options.activities) {
|
|
66
111
|
WorkerService.registerActivities(options.activities)
|
|
@@ -138,15 +183,11 @@ export class WorkflowService {
|
|
|
138
183
|
const hotMeshClient = await WorkerService.getHotMesh(`${namespace}.flow.signal`, { namespace });
|
|
139
184
|
if (await WorkflowService.isSideEffectAllowed(hotMeshClient, 'hook')) {
|
|
140
185
|
const store = asyncLocalStorage.getStore();
|
|
141
|
-
|
|
142
|
-
let workflowTopic
|
|
143
|
-
if (options.
|
|
144
|
-
workflowId = options.workflowId;
|
|
186
|
+
const workflowId = options.workflowId ?? store.get('workflowId');
|
|
187
|
+
let workflowTopic = store.get('workflowTopic');
|
|
188
|
+
if (options.taskQueue && options.workflowName) {
|
|
145
189
|
workflowTopic = `${options.taskQueue}-${options.workflowName}`;
|
|
146
|
-
} else
|
|
147
|
-
workflowId = store.get('workflowId');
|
|
148
|
-
workflowTopic = store.get('workflowTopic');
|
|
149
|
-
}
|
|
190
|
+
} //else this is essentially recursion as the function calls itself
|
|
150
191
|
const payload = {
|
|
151
192
|
arguments: [...options.args],
|
|
152
193
|
id: workflowId,
|
package/types/durable.ts
CHANGED
|
@@ -18,7 +18,7 @@ type WorkflowOptions = {
|
|
|
18
18
|
namespace?: string; //'durable' is the default namespace if not provided; similar to setting `appid` in the YAML
|
|
19
19
|
taskQueue: string;
|
|
20
20
|
args: any[]; //input arguments to pass in
|
|
21
|
-
workflowId
|
|
21
|
+
workflowId?: string; //execution id (the job id)
|
|
22
22
|
workflowName?: string; //the name of the user's workflow function
|
|
23
23
|
parentWorkflowId?: string; //system reserved; the id of the parent; if present the flow will not self-clean until the parent that spawned it self-cleans
|
|
24
24
|
workflowTrace?: string;
|
|
@@ -29,9 +29,9 @@ type WorkflowOptions = {
|
|
|
29
29
|
|
|
30
30
|
type HookOptions = {
|
|
31
31
|
namespace?: string; //'durable' is the default namespace if not provided; similar to setting `appid` in the YAML
|
|
32
|
-
taskQueue
|
|
32
|
+
taskQueue?: string;
|
|
33
33
|
args: any[]; //input arguments to pass into the hook
|
|
34
|
-
workflowId
|
|
34
|
+
workflowId?: string; //execution id (the job id to hook into)
|
|
35
35
|
workflowName?: string; //the name of the user's hook function
|
|
36
36
|
search?: WorkflowSearchOptions //bind additional search terms immediately before hook reentry
|
|
37
37
|
config?: WorkflowConfig; //hook function constraints (backoffCoefficient, maximumAttempts, maximumInterval, initialInterval)
|