@hotmeshio/hotmesh 0.0.55 → 0.0.56
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/build/modules/enums.js +1 -10
- package/build/modules/key.d.ts +0 -38
- package/build/modules/key.js +4 -46
- package/build/modules/utils.d.ts +0 -8
- package/build/modules/utils.js +0 -14
- package/build/package.json +11 -4
- package/build/services/activities/activity.d.ts +0 -28
- package/build/services/activities/activity.js +1 -46
- package/build/services/activities/await.js +0 -4
- package/build/services/activities/cycle.d.ts +0 -7
- package/build/services/activities/cycle.js +1 -16
- package/build/services/activities/hook.d.ts +0 -6
- package/build/services/activities/hook.js +2 -12
- package/build/services/activities/interrupt.js +0 -8
- package/build/services/activities/signal.d.ts +0 -6
- package/build/services/activities/signal.js +0 -15
- package/build/services/activities/trigger.d.ts +0 -4
- package/build/services/activities/trigger.js +1 -7
- package/build/services/activities/worker.js +0 -4
- package/build/services/collator/index.d.ts +0 -70
- package/build/services/collator/index.js +1 -91
- package/build/services/compiler/deployer.js +6 -38
- package/build/services/compiler/index.d.ts +0 -15
- package/build/services/compiler/index.js +0 -20
- package/build/services/compiler/validator.d.ts +0 -3
- package/build/services/compiler/validator.js +0 -25
- package/build/services/connector/clients/ioredis.d.ts +2 -2
- package/build/services/connector/clients/ioredis.js +0 -2
- package/build/services/connector/clients/redis.d.ts +4 -4
- package/build/services/connector/clients/redis.js +1 -3
- package/build/services/connector/index.d.ts +1 -1
- package/build/services/connector/index.js +0 -2
- package/build/services/durable/client.d.ts +1 -26
- package/build/services/durable/client.js +0 -56
- package/build/services/durable/exporter.d.ts +0 -22
- package/build/services/durable/exporter.js +1 -30
- package/build/services/durable/handle.d.ts +0 -36
- package/build/services/durable/handle.js +0 -46
- package/build/services/durable/index.d.ts +0 -4
- package/build/services/durable/index.js +0 -4
- package/build/services/durable/schemas/factory.d.ts +0 -29
- package/build/services/durable/schemas/factory.js +0 -29
- package/build/services/durable/search.d.ts +1 -36
- package/build/services/durable/search.js +57 -56
- package/build/services/durable/worker.js +2 -22
- package/build/services/durable/workflow.d.ts +0 -114
- package/build/services/durable/workflow.js +1 -141
- package/build/services/engine/index.d.ts +1 -6
- package/build/services/engine/index.js +1 -43
- package/build/services/exporter/index.d.ts +0 -27
- package/build/services/exporter/index.js +0 -33
- package/build/services/hotmesh/index.d.ts +2 -2
- package/build/services/hotmesh/index.js +1 -9
- package/build/services/logger/index.js +0 -2
- package/build/services/mapper/index.d.ts +0 -14
- package/build/services/mapper/index.js +0 -14
- package/build/services/pipe/functions/date.d.ts +0 -7
- package/build/services/pipe/functions/date.js +0 -7
- package/build/services/pipe/functions/math.js +0 -2
- package/build/services/pipe/index.d.ts +0 -15
- package/build/services/pipe/index.js +2 -23
- package/build/services/quorum/index.d.ts +0 -7
- package/build/services/quorum/index.js +0 -21
- package/build/services/reporter/index.d.ts +0 -5
- package/build/services/reporter/index.js +0 -9
- package/build/services/router/index.d.ts +0 -9
- package/build/services/router/index.js +2 -38
- package/build/services/serializer/index.js +7 -26
- package/build/services/store/cache.d.ts +0 -18
- package/build/services/store/cache.js +0 -18
- package/build/services/store/clients/ioredis.d.ts +1 -1
- package/build/services/store/clients/ioredis.js +0 -1
- package/build/services/store/clients/redis.d.ts +1 -1
- package/build/services/store/index.d.ts +0 -55
- package/build/services/store/index.js +5 -81
- package/build/services/stream/clients/ioredis.d.ts +1 -1
- package/build/services/stream/clients/ioredis.js +1 -4
- package/build/services/stream/clients/redis.d.ts +1 -1
- package/build/services/sub/clients/ioredis.d.ts +1 -1
- package/build/services/sub/clients/redis.d.ts +1 -1
- package/build/services/task/index.d.ts +0 -9
- package/build/services/task/index.js +0 -31
- package/build/services/telemetry/index.d.ts +0 -7
- package/build/services/telemetry/index.js +1 -13
- package/build/services/worker/index.d.ts +0 -4
- package/build/services/worker/index.js +2 -6
- package/build/types/activity.d.ts +0 -81
- package/build/types/durable.d.ts +25 -177
- package/build/types/exporter.d.ts +0 -13
- package/build/types/hotmesh.d.ts +4 -16
- package/build/types/hotmesh.js +0 -3
- package/build/types/index.d.ts +4 -6
- package/build/types/index.js +4 -3
- package/build/types/job.d.ts +1 -86
- package/build/types/pipe.d.ts +0 -65
- package/build/types/quorum.d.ts +15 -10
- package/build/types/redis.d.ts +225 -7
- package/build/types/redis.js +9 -0
- package/build/types/stream.d.ts +0 -58
- package/build/types/stream.js +0 -4
- package/package.json +11 -4
- package/types/durable.ts +121 -3
- package/types/hotmesh.ts +3 -6
- package/types/index.ts +23 -10
- package/types/job.ts +1 -1
- package/types/quorum.ts +22 -0
- package/types/redis.ts +267 -18
- package/build/types/ioredisclient.d.ts +0 -5
- package/build/types/ioredisclient.js +0 -5
- package/build/types/redisclient.d.ts +0 -26
- package/build/types/redisclient.js +0 -2
- package/modules/enums.ts +0 -62
- package/modules/errors.ts +0 -280
- package/modules/key.ts +0 -101
- package/modules/storage.ts +0 -3
- package/modules/utils.ts +0 -242
- package/services/activities/activity.ts +0 -589
- package/services/activities/await.ts +0 -113
- package/services/activities/cycle.ts +0 -115
- package/services/activities/hook.ts +0 -197
- package/services/activities/index.ts +0 -19
- package/services/activities/interrupt.ts +0 -172
- package/services/activities/signal.ts +0 -148
- package/services/activities/trigger.ts +0 -295
- package/services/activities/worker.ts +0 -107
- package/services/collator/README.md +0 -102
- package/services/collator/index.ts +0 -291
- package/services/compiler/deployer.ts +0 -504
- package/services/compiler/index.ts +0 -98
- package/services/compiler/validator.ts +0 -158
- package/services/connector/clients/ioredis.ts +0 -57
- package/services/connector/clients/redis.ts +0 -72
- package/services/connector/index.ts +0 -42
- package/services/durable/client.ts +0 -266
- package/services/durable/connection.ts +0 -10
- package/services/durable/exporter.ts +0 -232
- package/services/durable/handle.ts +0 -160
- package/services/durable/index.ts +0 -27
- package/services/durable/schemas/factory.ts +0 -2358
- package/services/durable/search.ts +0 -196
- package/services/durable/worker.ts +0 -401
- package/services/durable/workflow.ts +0 -557
- package/services/engine/index.ts +0 -761
- package/services/exporter/index.ts +0 -146
- package/services/hotmesh/index.ts +0 -237
- package/services/logger/index.ts +0 -79
- package/services/mapper/index.ts +0 -89
- package/services/pipe/functions/array.ts +0 -78
- package/services/pipe/functions/bitwise.ts +0 -27
- package/services/pipe/functions/conditional.ts +0 -35
- package/services/pipe/functions/date.ts +0 -220
- package/services/pipe/functions/index.ts +0 -27
- package/services/pipe/functions/json.ts +0 -11
- package/services/pipe/functions/logical.ts +0 -11
- package/services/pipe/functions/math.ts +0 -217
- package/services/pipe/functions/number.ts +0 -75
- package/services/pipe/functions/object.ts +0 -98
- package/services/pipe/functions/string.ts +0 -86
- package/services/pipe/functions/symbol.ts +0 -39
- package/services/pipe/functions/unary.ts +0 -19
- package/services/pipe/index.ts +0 -216
- package/services/quorum/index.ts +0 -319
- package/services/reporter/index.ts +0 -387
- package/services/router/index.ts +0 -426
- package/services/serializer/README.md +0 -10
- package/services/serializer/index.ts +0 -285
- package/services/store/cache.ts +0 -172
- package/services/store/clients/ioredis.ts +0 -145
- package/services/store/clients/redis.ts +0 -191
- package/services/store/index.ts +0 -1091
- package/services/stream/clients/ioredis.ts +0 -157
- package/services/stream/clients/redis.ts +0 -158
- package/services/stream/index.ts +0 -58
- package/services/sub/clients/ioredis.ts +0 -83
- package/services/sub/clients/redis.ts +0 -74
- package/services/sub/index.ts +0 -25
- package/services/task/index.ts +0 -250
- package/services/telemetry/index.ts +0 -273
- package/services/worker/index.ts +0 -248
- package/types/ioredisclient.ts +0 -10
- package/types/redisclient.ts +0 -30
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import { HotMeshService as HotMesh } from '../hotmesh'
|
|
2
|
-
import { RedisClient, RedisMulti } from '../../types/redis';
|
|
3
|
-
import { StoreService } from '../store';
|
|
4
|
-
import { KeyService, KeyType } from '../../modules/key';
|
|
5
|
-
import { WorkflowSearchOptions } from '../../types/durable';
|
|
6
|
-
import { asyncLocalStorage } from '../../modules/storage';
|
|
7
|
-
|
|
8
|
-
export class Search {
|
|
9
|
-
jobId: string;
|
|
10
|
-
searchSessionId: string;
|
|
11
|
-
searchSessionIndex: number = 0;
|
|
12
|
-
hotMeshClient: HotMesh;
|
|
13
|
-
store: StoreService<RedisClient, RedisMulti> | null;
|
|
14
|
-
|
|
15
|
-
constructor(workflowId: string, hotMeshClient: HotMesh, searchSessionId: string) {
|
|
16
|
-
const keyParams = {
|
|
17
|
-
appId: hotMeshClient.appId,
|
|
18
|
-
jobId: workflowId
|
|
19
|
-
}
|
|
20
|
-
this.jobId = KeyService.mintKey(hotMeshClient.namespace, KeyType.JOB_STATE, keyParams);
|
|
21
|
-
this.searchSessionId = searchSessionId;
|
|
22
|
-
this.hotMeshClient = hotMeshClient;
|
|
23
|
-
this.store = hotMeshClient.engine.store as StoreService<RedisClient, RedisMulti>;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
safeKey(key:string): string {
|
|
27
|
-
return `_${key}`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* For those deployments with a redis stack backend (with the FT module),
|
|
32
|
-
* this method will configure the search index for the workflow. For all
|
|
33
|
-
* others, this method will exit/fail gracefully and not index
|
|
34
|
-
* the fields in the HASH. However, all values are still available
|
|
35
|
-
* in the HASH.
|
|
36
|
-
*/
|
|
37
|
-
static async configureSearchIndex(hotMeshClient: HotMesh, search?: WorkflowSearchOptions): Promise<void> {
|
|
38
|
-
if (search?.schema) {
|
|
39
|
-
const store = hotMeshClient.engine.store;
|
|
40
|
-
const schema: string[] = [];
|
|
41
|
-
for (const [key, value] of Object.entries(search.schema)) {
|
|
42
|
-
//prefix with a comma (avoids collisions with hotmesh reserved words)
|
|
43
|
-
schema.push(`_${key}`);
|
|
44
|
-
schema.push(value.type);
|
|
45
|
-
if (value.sortable) {
|
|
46
|
-
schema.push('SORTABLE');
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
try {
|
|
50
|
-
const keyParams = {
|
|
51
|
-
appId: hotMeshClient.appId,
|
|
52
|
-
jobId: ''
|
|
53
|
-
}
|
|
54
|
-
const hotMeshPrefix = KeyService.mintKey(hotMeshClient.namespace, KeyType.JOB_STATE, keyParams);
|
|
55
|
-
const prefixes = search.prefix.map((prefix) => `${hotMeshPrefix}${prefix}`);
|
|
56
|
-
await store.exec('FT.CREATE', `${search.index}`, 'ON', 'HASH', 'PREFIX', prefixes.length.toString(), ...prefixes, 'SCHEMA', ...schema);
|
|
57
|
-
} catch (error) {
|
|
58
|
-
hotMeshClient.engine.logger.info('durable-client-search-err', { ...error });
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* For those deployments with a redis stack backend (with the FT module),
|
|
65
|
-
* this method will list all search indexes.
|
|
66
|
-
* @param {HotMesh} hotMeshClient - the hotmesh client
|
|
67
|
-
* @returns {Promise<string[]>} - the list of search indexes
|
|
68
|
-
*/
|
|
69
|
-
static async listSearchIndexes(hotMeshClient: HotMesh): Promise<string[]> {
|
|
70
|
-
try {
|
|
71
|
-
const store = hotMeshClient.engine.store;
|
|
72
|
-
const searchIndexes = await store.exec('FT._LIST');
|
|
73
|
-
return searchIndexes as string[];
|
|
74
|
-
} catch (error) {
|
|
75
|
-
hotMeshClient.engine.logger.info('durable-client-search-list-err', { ...error });
|
|
76
|
-
return [];
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* increments the index to return a unique search session guid when
|
|
82
|
-
* calling any method that produces side effects (changes the value)
|
|
83
|
-
*/
|
|
84
|
-
getSearchSessionGuid(): string {
|
|
85
|
-
//return the search session as it would exist in the search session index
|
|
86
|
-
return `${this.searchSessionId}-${this.searchSessionIndex++}-`;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Sets the fields listed in args. Returns the
|
|
91
|
-
* count of new fields that were set (does not
|
|
92
|
-
* count fields that were updated)
|
|
93
|
-
*/
|
|
94
|
-
async set(...args: string[]): Promise<number> {
|
|
95
|
-
const ssGuid = this.getSearchSessionGuid();
|
|
96
|
-
const store = asyncLocalStorage.getStore();
|
|
97
|
-
const replay = store?.get('replay') ?? {};
|
|
98
|
-
if (ssGuid in replay) {
|
|
99
|
-
return Number(replay[ssGuid]);
|
|
100
|
-
}
|
|
101
|
-
const safeArgs: string[] = [];
|
|
102
|
-
for (let i = 0; i < args.length; i += 2) {
|
|
103
|
-
const key = this.safeKey(args[i]);
|
|
104
|
-
const value = args[i+1].toString();
|
|
105
|
-
safeArgs.push(key, value);
|
|
106
|
-
}
|
|
107
|
-
const fieldCount = await this.store.exec('HSET', this.jobId, ...safeArgs);
|
|
108
|
-
//no need to wait; set this interim value in the replay
|
|
109
|
-
this.store.exec('HSET', this.jobId, ssGuid, fieldCount.toString());
|
|
110
|
-
return Number(fieldCount);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async get(key: string): Promise<string> {
|
|
114
|
-
try {
|
|
115
|
-
return await this.store.exec('HGET',this.jobId, this.safeKey(key)) as string;
|
|
116
|
-
} catch (error) {
|
|
117
|
-
this.hotMeshClient.logger.error('durable-search-get-error', { ...error });
|
|
118
|
-
return '';
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async mget(...args: string[]): Promise<string[]> {
|
|
123
|
-
const safeArgs: string[] = [];
|
|
124
|
-
for (let i = 0; i < args.length; i++) {
|
|
125
|
-
safeArgs.push(this.safeKey(args[i]));
|
|
126
|
-
}
|
|
127
|
-
try {
|
|
128
|
-
return await this.store.exec('HMGET', this.jobId, ...safeArgs) as string[];
|
|
129
|
-
} catch (error) {
|
|
130
|
-
this.hotMeshClient.logger.error('durable-search-mget-error', { ...error });
|
|
131
|
-
return [];
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Deletes the fields listed in args. Returns the
|
|
137
|
-
* count of fields that were deleted.
|
|
138
|
-
*/
|
|
139
|
-
async del(...args: string[]): Promise<number | void> {
|
|
140
|
-
const ssGuid = this.getSearchSessionGuid();
|
|
141
|
-
const store = asyncLocalStorage.getStore();
|
|
142
|
-
const replay = store?.get('replay') ?? {};
|
|
143
|
-
if (ssGuid in replay) {
|
|
144
|
-
return Number(replay[ssGuid]);
|
|
145
|
-
}
|
|
146
|
-
const safeArgs: string[] = [];
|
|
147
|
-
for (let i = 0; i < args.length; i++) {
|
|
148
|
-
safeArgs.push(this.safeKey(args[i]));
|
|
149
|
-
}
|
|
150
|
-
const response = await this.store.exec('HDEL', this.jobId, ...safeArgs);
|
|
151
|
-
const formattedResponse = isNaN(response as unknown as number) ? 0 : Number(response);
|
|
152
|
-
//no need to wait; set this interim value in the replay
|
|
153
|
-
this.store.exec('HSET', this.jobId, ssGuid, formattedResponse.toString());
|
|
154
|
-
return formattedResponse;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Increments the value of a field by the given amount. Returns the
|
|
159
|
-
* new value of the field after the increment. Can be
|
|
160
|
-
* used to decrement the value of a field by specifying a negative.
|
|
161
|
-
*/
|
|
162
|
-
async incr(key: string, val: number): Promise<number> {
|
|
163
|
-
const ssGuid = this.getSearchSessionGuid();
|
|
164
|
-
const store = asyncLocalStorage.getStore();
|
|
165
|
-
const replay = store?.get('replay') ?? {};
|
|
166
|
-
if (ssGuid in replay) {
|
|
167
|
-
return Number(replay[ssGuid]);
|
|
168
|
-
}
|
|
169
|
-
const num = await this.store.exec('HINCRBYFLOAT', this.jobId, this.safeKey(key), val.toString()) as string;
|
|
170
|
-
//no need to wait; set this interim value in the replay
|
|
171
|
-
this.store.exec('HSET', this.jobId, ssGuid, num.toString());
|
|
172
|
-
return Number(num);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Multiplies the value of a field by the given amount. Returns the
|
|
177
|
-
* new value of the field after the multiplication. NOTE:
|
|
178
|
-
* this is exponential multiplication.
|
|
179
|
-
*/
|
|
180
|
-
async mult(key: string, val: number): Promise<number> {
|
|
181
|
-
const ssGuid = this.getSearchSessionGuid();
|
|
182
|
-
const store = asyncLocalStorage.getStore();
|
|
183
|
-
const replay = store?.get('replay') ?? {};
|
|
184
|
-
if (ssGuid in replay) {
|
|
185
|
-
return Math.exp(Number(replay[ssGuid]));
|
|
186
|
-
}
|
|
187
|
-
const ssGuidValue = Number(await this.store.exec('HINCRBYFLOAT', this.jobId, ssGuid, '1') as string);
|
|
188
|
-
if (ssGuidValue === 1) {
|
|
189
|
-
const log = Math.log(val);
|
|
190
|
-
const logTotal = await this.store.exec('HINCRBYFLOAT', this.jobId, this.safeKey(key), log.toString()) as string;
|
|
191
|
-
//no need to wait; set this interim value in the replay
|
|
192
|
-
this.store.exec('HSET', this.jobId, ssGuid, logTotal.toString());
|
|
193
|
-
return Math.exp(Number(logTotal));
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
@@ -1,401 +0,0 @@
|
|
|
1
|
-
import ms from 'ms';
|
|
2
|
-
import {
|
|
3
|
-
HMSH_CODE_DURABLE_ALL,
|
|
4
|
-
HMSH_CODE_DURABLE_RETRYABLE,
|
|
5
|
-
HMSH_DURABLE_EXP_BACKOFF,
|
|
6
|
-
HMSH_DURABLE_MAX_INTERVAL,
|
|
7
|
-
HMSH_DURABLE_MAX_ATTEMPTS,
|
|
8
|
-
HMSH_LOGLEVEL } from '../../modules/enums';
|
|
9
|
-
import {
|
|
10
|
-
DurableChildError,
|
|
11
|
-
DurableFatalError,
|
|
12
|
-
DurableMaxedError,
|
|
13
|
-
DurableProxyError,
|
|
14
|
-
DurableRetryError,
|
|
15
|
-
DurableSleepError,
|
|
16
|
-
DurableTimeoutError,
|
|
17
|
-
DurableWaitForError } from '../../modules/errors';
|
|
18
|
-
import { asyncLocalStorage } from '../../modules/storage';
|
|
19
|
-
import { APP_ID, APP_VERSION, getWorkflowYAML } from './schemas/factory';
|
|
20
|
-
import { HotMeshService as HotMesh } from '../hotmesh';
|
|
21
|
-
import {
|
|
22
|
-
ActivityWorkflowDataType,
|
|
23
|
-
Connection,
|
|
24
|
-
Registry,
|
|
25
|
-
WorkerConfig,
|
|
26
|
-
WorkerOptions,
|
|
27
|
-
WorkflowDataType } from '../../types/durable';
|
|
28
|
-
import { RedisClass, RedisOptions } from '../../types/redis';
|
|
29
|
-
import { Search } from './search';
|
|
30
|
-
import {
|
|
31
|
-
StreamData,
|
|
32
|
-
StreamDataResponse,
|
|
33
|
-
StreamStatus } from '../../types/stream';
|
|
34
|
-
import { formatISODate } from '../../modules/utils';
|
|
35
|
-
|
|
36
|
-
export class WorkerService {
|
|
37
|
-
static activityRegistry: Registry = {}; //user's activities
|
|
38
|
-
static connection: Connection;
|
|
39
|
-
static instances = new Map<string, HotMesh | Promise<HotMesh>>();
|
|
40
|
-
workflowRunner: HotMesh;
|
|
41
|
-
activityRunner: HotMesh;
|
|
42
|
-
|
|
43
|
-
static getHotMesh = async (workflowTopic: string, config?: Partial<WorkerConfig>, options?: WorkerOptions) => {
|
|
44
|
-
if (WorkerService.instances.has(workflowTopic)) {
|
|
45
|
-
return await WorkerService.instances.get(workflowTopic);
|
|
46
|
-
}
|
|
47
|
-
const hotMeshClient = HotMesh.init({
|
|
48
|
-
logLevel: options?.logLevel ?? HMSH_LOGLEVEL,
|
|
49
|
-
appId: config.namespace ?? APP_ID,
|
|
50
|
-
engine: { redis: { ...WorkerService.connection } }
|
|
51
|
-
});
|
|
52
|
-
WorkerService.instances.set(workflowTopic, hotMeshClient);
|
|
53
|
-
await WorkerService.activateWorkflow(await hotMeshClient);
|
|
54
|
-
return hotMeshClient;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
static async activateWorkflow(hotMesh: HotMesh) {
|
|
58
|
-
const app = await hotMesh.engine.store.getApp(hotMesh.engine.appId);
|
|
59
|
-
const appVersion = app?.version;
|
|
60
|
-
if(!appVersion) {
|
|
61
|
-
try {
|
|
62
|
-
await hotMesh.deploy(getWorkflowYAML(hotMesh.engine.appId, APP_VERSION));
|
|
63
|
-
await hotMesh.activate(APP_VERSION);
|
|
64
|
-
} catch (err) {
|
|
65
|
-
hotMesh.engine.logger.error('durable-worker-deploy-activate-err', err);
|
|
66
|
-
throw err;
|
|
67
|
-
}
|
|
68
|
-
} else if(app && !app.active) {
|
|
69
|
-
try {
|
|
70
|
-
await hotMesh.activate(APP_VERSION);
|
|
71
|
-
} catch (err) {
|
|
72
|
-
hotMesh.engine.logger.error('durable-worker-activate-err', err);
|
|
73
|
-
throw err;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
static registerActivities<ACT>(activities: ACT): Registry {
|
|
79
|
-
if (typeof activities === 'function' && typeof WorkerService.activityRegistry[activities.name] !== 'function') {
|
|
80
|
-
WorkerService.activityRegistry[activities.name] = activities as Function;
|
|
81
|
-
} else {
|
|
82
|
-
Object.keys(activities).forEach(key => {
|
|
83
|
-
if (activities[key].name && typeof WorkerService.activityRegistry[activities[key].name] !== 'function') {
|
|
84
|
-
WorkerService.activityRegistry[activities[key].name] = (activities as any)[key] as Function;
|
|
85
|
-
} else if (typeof (activities as any)[key] === 'function') {
|
|
86
|
-
WorkerService.activityRegistry[key] = (activities as any)[key] as Function;
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
return WorkerService.activityRegistry;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
static async create(config: WorkerConfig): Promise<WorkerService> {
|
|
94
|
-
WorkerService.connection = config.connection;
|
|
95
|
-
const workflow = config.workflow;
|
|
96
|
-
const [workflowFunctionName, workflowFunction] = WorkerService.resolveWorkflowTarget(workflow);
|
|
97
|
-
const baseTopic = `${config.taskQueue}-${workflowFunctionName}`;
|
|
98
|
-
const activityTopic = `${baseTopic}-activity`;
|
|
99
|
-
const workflowTopic = `${baseTopic}`;
|
|
100
|
-
|
|
101
|
-
//initialize supporting workflows
|
|
102
|
-
const worker = new WorkerService();
|
|
103
|
-
worker.activityRunner = await worker.initActivityWorker(config, activityTopic);
|
|
104
|
-
worker.workflowRunner = await worker.initWorkflowWorker(config, workflowTopic, workflowFunction);
|
|
105
|
-
Search.configureSearchIndex(worker.workflowRunner, config.search);
|
|
106
|
-
await WorkerService.activateWorkflow(worker.workflowRunner);
|
|
107
|
-
return worker;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
static resolveWorkflowTarget(workflow: object | Function, name?: string): [string, Function] {
|
|
111
|
-
let workflowFunction: Function;
|
|
112
|
-
if (typeof workflow === 'function') {
|
|
113
|
-
workflowFunction = workflow;
|
|
114
|
-
return [workflowFunction.name ?? name, workflowFunction];
|
|
115
|
-
} else {
|
|
116
|
-
const workflowFunctionNames = Object.keys(workflow);
|
|
117
|
-
const lastFunctionName = workflowFunctionNames[workflowFunctionNames.length - 1];
|
|
118
|
-
workflowFunction = workflow[lastFunctionName];
|
|
119
|
-
return WorkerService.resolveWorkflowTarget(workflowFunction, lastFunctionName);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async run() {
|
|
124
|
-
this.workflowRunner.engine.logger.info('WorkerService is running');
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async initActivityWorker(config: WorkerConfig, activityTopic: string): Promise<HotMesh> {
|
|
128
|
-
const redisConfig = {
|
|
129
|
-
class: config.connection.class as RedisClass,
|
|
130
|
-
options: config.connection.options as RedisOptions
|
|
131
|
-
};
|
|
132
|
-
const hotMeshWorker = await HotMesh.init({
|
|
133
|
-
logLevel: config.options?.logLevel ?? HMSH_LOGLEVEL,
|
|
134
|
-
appId: config.namespace ?? APP_ID,
|
|
135
|
-
engine: { redis: redisConfig },
|
|
136
|
-
workers: [
|
|
137
|
-
{ topic: activityTopic,
|
|
138
|
-
redis: redisConfig,
|
|
139
|
-
callback: this.wrapActivityFunctions().bind(this)
|
|
140
|
-
}
|
|
141
|
-
]
|
|
142
|
-
});
|
|
143
|
-
WorkerService.instances.set(activityTopic, hotMeshWorker);
|
|
144
|
-
return hotMeshWorker;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
//this is the linked worker function in the reentrant workflow test
|
|
148
|
-
wrapActivityFunctions(): Function {
|
|
149
|
-
return async (data: StreamData): Promise<StreamDataResponse> => {
|
|
150
|
-
try {
|
|
151
|
-
//always run the activity function when instructed; return the response
|
|
152
|
-
const activityInput = data.data as unknown as ActivityWorkflowDataType;
|
|
153
|
-
const activityName = activityInput.activityName;
|
|
154
|
-
const activityFunction = WorkerService.activityRegistry[activityName];
|
|
155
|
-
const pojoResponse = await activityFunction.apply(this, activityInput.arguments);
|
|
156
|
-
|
|
157
|
-
return {
|
|
158
|
-
status: StreamStatus.SUCCESS,
|
|
159
|
-
metadata: { ...data.metadata },
|
|
160
|
-
data: { response: pojoResponse }
|
|
161
|
-
};
|
|
162
|
-
} catch (err) {
|
|
163
|
-
this.activityRunner.engine.logger.error('durable-worker-activity-err', { name: err.name, message: err.message, stack: err.stack });
|
|
164
|
-
if (!(err instanceof DurableTimeoutError) &&
|
|
165
|
-
!(err instanceof DurableMaxedError) &&
|
|
166
|
-
!(err instanceof DurableFatalError)) {
|
|
167
|
-
|
|
168
|
-
//use code 599 as a proxy for all retryable errors
|
|
169
|
-
// (basically anything not 596, 597, 598)
|
|
170
|
-
return {
|
|
171
|
-
status: StreamStatus.SUCCESS,
|
|
172
|
-
code: HMSH_CODE_DURABLE_RETRYABLE,
|
|
173
|
-
metadata: { ...data.metadata },
|
|
174
|
-
data: {
|
|
175
|
-
$error: {
|
|
176
|
-
message: err.message,
|
|
177
|
-
stack: err.stack,
|
|
178
|
-
timestamp: formatISODate(new Date()),
|
|
179
|
-
}
|
|
180
|
-
},
|
|
181
|
-
} as StreamDataResponse;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
//always returrn success (the Durable module is just fine);
|
|
186
|
-
// it's the user's function that has failed
|
|
187
|
-
status: StreamStatus.SUCCESS,
|
|
188
|
-
code: err.code,
|
|
189
|
-
stack: err.stack,
|
|
190
|
-
metadata: { ...data.metadata },
|
|
191
|
-
data: {
|
|
192
|
-
$error: {
|
|
193
|
-
message: err.message,
|
|
194
|
-
stack: err.stack,
|
|
195
|
-
timestamp: formatISODate(new Date()),
|
|
196
|
-
code: err.code,
|
|
197
|
-
}
|
|
198
|
-
},
|
|
199
|
-
} as StreamDataResponse;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
async initWorkflowWorker(config: WorkerConfig, workflowTopic: string, workflowFunction: Function): Promise<HotMesh> {
|
|
205
|
-
const redisConfig = {
|
|
206
|
-
class: config.connection.class as RedisClass,
|
|
207
|
-
options: config.connection.options as RedisOptions
|
|
208
|
-
};
|
|
209
|
-
const hotMeshWorker = await HotMesh.init({
|
|
210
|
-
logLevel: config.options?.logLevel ?? HMSH_LOGLEVEL,
|
|
211
|
-
appId: config.namespace ?? APP_ID,
|
|
212
|
-
engine: { redis: redisConfig },
|
|
213
|
-
workers: [{
|
|
214
|
-
topic: workflowTopic,
|
|
215
|
-
redis: redisConfig,
|
|
216
|
-
callback: this.wrapWorkflowFunction(workflowFunction, workflowTopic, config).bind(this)
|
|
217
|
-
}]
|
|
218
|
-
});
|
|
219
|
-
WorkerService.instances.set(workflowTopic, hotMeshWorker);
|
|
220
|
-
return hotMeshWorker;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
static Context = {
|
|
224
|
-
info: () => {
|
|
225
|
-
return {
|
|
226
|
-
workflowId: '',
|
|
227
|
-
workflowTopic: '',
|
|
228
|
-
}
|
|
229
|
-
},
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
wrapWorkflowFunction(workflowFunction: Function, workflowTopic: string, config: WorkerConfig): Function {
|
|
233
|
-
return async (data: StreamData): Promise<StreamDataResponse> => {
|
|
234
|
-
const counter = { counter: 0 };
|
|
235
|
-
const interruptionRegistry: any[] = [];
|
|
236
|
-
try {
|
|
237
|
-
//incoming data payload has arguments and workflowId
|
|
238
|
-
const workflowInput = data.data as unknown as WorkflowDataType;
|
|
239
|
-
const context = new Map();
|
|
240
|
-
context.set('canRetry', workflowInput.canRetry);
|
|
241
|
-
context.set('counter', counter);
|
|
242
|
-
context.set('interruptionRegistry', interruptionRegistry);
|
|
243
|
-
context.set('namespace', config.namespace ?? APP_ID);
|
|
244
|
-
context.set('raw', data);
|
|
245
|
-
context.set('workflowId', workflowInput.workflowId);
|
|
246
|
-
if (workflowInput.originJobId) {
|
|
247
|
-
//if present there is an origin job to which this job is subordinated;
|
|
248
|
-
// garbage collect (expire) this job when originJobId is expired
|
|
249
|
-
context.set('originJobId', workflowInput.originJobId);
|
|
250
|
-
}
|
|
251
|
-
let replayQuery = '';
|
|
252
|
-
if (workflowInput.workflowDimension) {
|
|
253
|
-
//every hook function runs in an isolated dimension controlled
|
|
254
|
-
//by the index assigned when the signal was received; even if the
|
|
255
|
-
//hook function re-runs, its scope will always remain constant
|
|
256
|
-
context.set('workflowDimension', workflowInput.workflowDimension);
|
|
257
|
-
replayQuery = `-*${workflowInput.workflowDimension}-*`;
|
|
258
|
-
} else {
|
|
259
|
-
//last letter of words like 'hook', 'sleep', 'wait', 'signal', 'search', 'start', 'proxy', 'child', 'collator'
|
|
260
|
-
replayQuery = '-*[ehklptydr]-*';
|
|
261
|
-
}
|
|
262
|
-
context.set('workflowTopic', workflowTopic);
|
|
263
|
-
context.set('workflowName', workflowTopic.split('-').pop());
|
|
264
|
-
context.set('workflowTrace', data.metadata.trc);
|
|
265
|
-
context.set('workflowSpan', data.metadata.spn);
|
|
266
|
-
const store = this.workflowRunner.engine.store;
|
|
267
|
-
const [cursor, replay] = await store.findJobFields(
|
|
268
|
-
workflowInput.workflowId,
|
|
269
|
-
replayQuery,
|
|
270
|
-
50_000,
|
|
271
|
-
5_000,);
|
|
272
|
-
context.set('replay', replay);
|
|
273
|
-
context.set('cursor', cursor); // if != 0, more remain
|
|
274
|
-
const workflowResponse = await asyncLocalStorage.run(context, async () => {
|
|
275
|
-
return await workflowFunction.apply(this, workflowInput.arguments);
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
return {
|
|
279
|
-
code: 200,
|
|
280
|
-
status: StreamStatus.SUCCESS,
|
|
281
|
-
metadata: { ...data.metadata },
|
|
282
|
-
data: { response: workflowResponse, done: true }
|
|
283
|
-
};
|
|
284
|
-
} catch (err) {
|
|
285
|
-
if (err instanceof DurableWaitForError || interruptionRegistry.length > 1) {
|
|
286
|
-
|
|
287
|
-
//NOTE: this type is spawned when `Promise.all` is used OR if the interruption is a `waitFor`
|
|
288
|
-
const workflowInput = data.data as unknown as WorkflowDataType;
|
|
289
|
-
const execIndex = counter.counter - interruptionRegistry.length + 1;
|
|
290
|
-
const { workflowId, workflowTopic, workflowDimension, originJobId } = workflowInput;
|
|
291
|
-
const collatorFlowId = `-${workflowId}-$${workflowDimension || ''}-$${execIndex}`;
|
|
292
|
-
return {
|
|
293
|
-
status: StreamStatus.SUCCESS,
|
|
294
|
-
code: HMSH_CODE_DURABLE_ALL,
|
|
295
|
-
metadata: { ...data.metadata },
|
|
296
|
-
data: {
|
|
297
|
-
code: HMSH_CODE_DURABLE_ALL,
|
|
298
|
-
items: [...interruptionRegistry],
|
|
299
|
-
size: interruptionRegistry.length,
|
|
300
|
-
workflowDimension: workflowDimension || '',
|
|
301
|
-
index: execIndex,
|
|
302
|
-
originJobId: originJobId || workflowId,
|
|
303
|
-
parentWorkflowId: workflowId,
|
|
304
|
-
workflowId: collatorFlowId,
|
|
305
|
-
workflowTopic: workflowTopic,
|
|
306
|
-
},
|
|
307
|
-
} as StreamDataResponse;
|
|
308
|
-
|
|
309
|
-
} else if (err instanceof DurableSleepError) {
|
|
310
|
-
//return the sleep interruption
|
|
311
|
-
return {
|
|
312
|
-
status: StreamStatus.SUCCESS,
|
|
313
|
-
code: err.code,
|
|
314
|
-
metadata: { ...data.metadata },
|
|
315
|
-
data: {
|
|
316
|
-
code: err.code,
|
|
317
|
-
message: JSON.stringify({ duration: err.duration, index: err.index, workflowDimension: err.workflowDimension }),
|
|
318
|
-
duration: err.duration,
|
|
319
|
-
index: err.index,
|
|
320
|
-
workflowDimension: err.workflowDimension,
|
|
321
|
-
}
|
|
322
|
-
} as StreamDataResponse;
|
|
323
|
-
|
|
324
|
-
} else if (err instanceof DurableProxyError) {
|
|
325
|
-
//return the proxyActivity interruption
|
|
326
|
-
return {
|
|
327
|
-
status: StreamStatus.SUCCESS,
|
|
328
|
-
code: err.code,
|
|
329
|
-
metadata: { ...data.metadata },
|
|
330
|
-
data: {
|
|
331
|
-
code: err.code,
|
|
332
|
-
message: JSON.stringify({ message: err.message, workflowId: err.workflowId, activityName: err.activityName, dimension: err.workflowDimension }),
|
|
333
|
-
arguments: err.arguments,
|
|
334
|
-
workflowDimension: err.workflowDimension,
|
|
335
|
-
index: err.index,
|
|
336
|
-
originJobId: err.originJobId,
|
|
337
|
-
parentWorkflowId: err.parentWorkflowId,
|
|
338
|
-
workflowId: err.workflowId,
|
|
339
|
-
workflowTopic: err.workflowTopic,
|
|
340
|
-
activityName: err.activityName,
|
|
341
|
-
backoffCoefficient: err.backoffCoefficient,
|
|
342
|
-
maximumAttempts: err.maximumAttempts,
|
|
343
|
-
maximumInterval: err.maximumInterval,
|
|
344
|
-
}
|
|
345
|
-
} as StreamDataResponse;
|
|
346
|
-
|
|
347
|
-
} else if (err instanceof DurableChildError) {
|
|
348
|
-
//return the child interruption
|
|
349
|
-
const msg = {
|
|
350
|
-
message: err.message,
|
|
351
|
-
workflowId: err.workflowId,
|
|
352
|
-
dimension: err.workflowDimension
|
|
353
|
-
};
|
|
354
|
-
return {
|
|
355
|
-
status: StreamStatus.SUCCESS,
|
|
356
|
-
code: err.code,
|
|
357
|
-
metadata: { ...data.metadata },
|
|
358
|
-
data: {
|
|
359
|
-
arguments: err.arguments,
|
|
360
|
-
await: err.await,
|
|
361
|
-
backoffCoefficient: err.backoffCoefficient || HMSH_DURABLE_EXP_BACKOFF ,
|
|
362
|
-
code: err.code,
|
|
363
|
-
index: err.index,
|
|
364
|
-
message: JSON.stringify(msg),
|
|
365
|
-
maximumAttempts: err.maximumAttempts || HMSH_DURABLE_MAX_ATTEMPTS,
|
|
366
|
-
maximumInterval: err.maximumInterval || ms(HMSH_DURABLE_MAX_INTERVAL) / 1000,
|
|
367
|
-
originJobId: err.originJobId,
|
|
368
|
-
parentWorkflowId: err.parentWorkflowId,
|
|
369
|
-
workflowDimension: err.workflowDimension,
|
|
370
|
-
workflowId: err.workflowId,
|
|
371
|
-
workflowTopic: err.workflowTopic,
|
|
372
|
-
}
|
|
373
|
-
} as StreamDataResponse;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// ALL other errors are actual fatal errors (598, 597, 596)
|
|
377
|
-
// OR will be retried (599)
|
|
378
|
-
return {
|
|
379
|
-
status: StreamStatus.SUCCESS,
|
|
380
|
-
code: err.code || new DurableRetryError(err.message).code,
|
|
381
|
-
metadata: { ...data.metadata },
|
|
382
|
-
data: {
|
|
383
|
-
$error: {
|
|
384
|
-
message: err.message,
|
|
385
|
-
type: err.name,
|
|
386
|
-
name: err.name,
|
|
387
|
-
stack: err.stack,
|
|
388
|
-
code: err.code || new DurableRetryError(err.message).code,
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
} as StreamDataResponse;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
static async shutdown(): Promise<void> {
|
|
397
|
-
for (const [_, hotMeshInstance] of WorkerService.instances) {
|
|
398
|
-
(await hotMeshInstance).stop();
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
}
|