@hotmeshio/hotmesh 0.0.54 → 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.
Files changed (182) hide show
  1. package/README.md +0 -3
  2. package/build/modules/enums.js +1 -10
  3. package/build/modules/key.d.ts +0 -38
  4. package/build/modules/key.js +4 -46
  5. package/build/modules/utils.d.ts +0 -8
  6. package/build/modules/utils.js +0 -14
  7. package/build/package.json +11 -4
  8. package/build/services/activities/activity.d.ts +0 -28
  9. package/build/services/activities/activity.js +1 -46
  10. package/build/services/activities/await.js +0 -4
  11. package/build/services/activities/cycle.d.ts +0 -7
  12. package/build/services/activities/cycle.js +1 -16
  13. package/build/services/activities/hook.d.ts +0 -6
  14. package/build/services/activities/hook.js +2 -12
  15. package/build/services/activities/interrupt.js +0 -8
  16. package/build/services/activities/signal.d.ts +0 -6
  17. package/build/services/activities/signal.js +0 -15
  18. package/build/services/activities/trigger.d.ts +0 -4
  19. package/build/services/activities/trigger.js +1 -7
  20. package/build/services/activities/worker.js +0 -4
  21. package/build/services/collator/index.d.ts +0 -70
  22. package/build/services/collator/index.js +1 -91
  23. package/build/services/compiler/deployer.js +6 -38
  24. package/build/services/compiler/index.d.ts +0 -15
  25. package/build/services/compiler/index.js +0 -20
  26. package/build/services/compiler/validator.d.ts +0 -3
  27. package/build/services/compiler/validator.js +0 -25
  28. package/build/services/connector/clients/ioredis.d.ts +2 -2
  29. package/build/services/connector/clients/ioredis.js +0 -2
  30. package/build/services/connector/clients/redis.d.ts +4 -4
  31. package/build/services/connector/clients/redis.js +1 -3
  32. package/build/services/connector/index.d.ts +1 -1
  33. package/build/services/connector/index.js +0 -2
  34. package/build/services/durable/client.d.ts +1 -26
  35. package/build/services/durable/client.js +0 -56
  36. package/build/services/durable/exporter.d.ts +0 -22
  37. package/build/services/durable/exporter.js +1 -30
  38. package/build/services/durable/handle.d.ts +0 -36
  39. package/build/services/durable/handle.js +0 -46
  40. package/build/services/durable/index.d.ts +0 -4
  41. package/build/services/durable/index.js +0 -4
  42. package/build/services/durable/schemas/factory.d.ts +0 -29
  43. package/build/services/durable/schemas/factory.js +0 -29
  44. package/build/services/durable/search.d.ts +1 -36
  45. package/build/services/durable/search.js +57 -56
  46. package/build/services/durable/worker.js +2 -22
  47. package/build/services/durable/workflow.d.ts +0 -114
  48. package/build/services/durable/workflow.js +4 -144
  49. package/build/services/engine/index.d.ts +1 -6
  50. package/build/services/engine/index.js +1 -43
  51. package/build/services/exporter/index.d.ts +0 -27
  52. package/build/services/exporter/index.js +0 -33
  53. package/build/services/hotmesh/index.d.ts +2 -2
  54. package/build/services/hotmesh/index.js +1 -9
  55. package/build/services/logger/index.js +0 -2
  56. package/build/services/mapper/index.d.ts +0 -14
  57. package/build/services/mapper/index.js +0 -14
  58. package/build/services/pipe/functions/date.d.ts +0 -7
  59. package/build/services/pipe/functions/date.js +0 -7
  60. package/build/services/pipe/functions/math.js +0 -2
  61. package/build/services/pipe/index.d.ts +0 -15
  62. package/build/services/pipe/index.js +2 -23
  63. package/build/services/quorum/index.d.ts +0 -7
  64. package/build/services/quorum/index.js +0 -21
  65. package/build/services/reporter/index.d.ts +0 -5
  66. package/build/services/reporter/index.js +0 -9
  67. package/build/services/router/index.d.ts +0 -9
  68. package/build/services/router/index.js +2 -38
  69. package/build/services/serializer/index.js +7 -26
  70. package/build/services/store/cache.d.ts +0 -18
  71. package/build/services/store/cache.js +0 -18
  72. package/build/services/store/clients/ioredis.d.ts +1 -1
  73. package/build/services/store/clients/ioredis.js +0 -1
  74. package/build/services/store/clients/redis.d.ts +1 -1
  75. package/build/services/store/index.d.ts +0 -55
  76. package/build/services/store/index.js +5 -81
  77. package/build/services/stream/clients/ioredis.d.ts +1 -1
  78. package/build/services/stream/clients/ioredis.js +1 -4
  79. package/build/services/stream/clients/redis.d.ts +1 -1
  80. package/build/services/sub/clients/ioredis.d.ts +1 -1
  81. package/build/services/sub/clients/redis.d.ts +1 -1
  82. package/build/services/task/index.d.ts +0 -9
  83. package/build/services/task/index.js +0 -31
  84. package/build/services/telemetry/index.d.ts +0 -7
  85. package/build/services/telemetry/index.js +1 -13
  86. package/build/services/worker/index.d.ts +0 -4
  87. package/build/services/worker/index.js +2 -6
  88. package/build/types/activity.d.ts +0 -81
  89. package/build/types/durable.d.ts +25 -177
  90. package/build/types/exporter.d.ts +0 -13
  91. package/build/types/hotmesh.d.ts +4 -16
  92. package/build/types/hotmesh.js +0 -3
  93. package/build/types/index.d.ts +4 -6
  94. package/build/types/index.js +4 -3
  95. package/build/types/job.d.ts +1 -86
  96. package/build/types/pipe.d.ts +0 -65
  97. package/build/types/quorum.d.ts +15 -10
  98. package/build/types/redis.d.ts +225 -7
  99. package/build/types/redis.js +9 -0
  100. package/build/types/stream.d.ts +0 -58
  101. package/build/types/stream.js +0 -4
  102. package/package.json +11 -4
  103. package/types/durable.ts +121 -3
  104. package/types/hotmesh.ts +3 -6
  105. package/types/index.ts +23 -10
  106. package/types/job.ts +1 -1
  107. package/types/quorum.ts +22 -0
  108. package/types/redis.ts +267 -18
  109. package/build/types/ioredisclient.d.ts +0 -5
  110. package/build/types/ioredisclient.js +0 -5
  111. package/build/types/redisclient.d.ts +0 -26
  112. package/build/types/redisclient.js +0 -2
  113. package/modules/enums.ts +0 -62
  114. package/modules/errors.ts +0 -280
  115. package/modules/key.ts +0 -101
  116. package/modules/storage.ts +0 -3
  117. package/modules/utils.ts +0 -242
  118. package/services/activities/activity.ts +0 -589
  119. package/services/activities/await.ts +0 -113
  120. package/services/activities/cycle.ts +0 -115
  121. package/services/activities/hook.ts +0 -197
  122. package/services/activities/index.ts +0 -19
  123. package/services/activities/interrupt.ts +0 -172
  124. package/services/activities/signal.ts +0 -148
  125. package/services/activities/trigger.ts +0 -295
  126. package/services/activities/worker.ts +0 -107
  127. package/services/collator/README.md +0 -102
  128. package/services/collator/index.ts +0 -291
  129. package/services/compiler/deployer.ts +0 -504
  130. package/services/compiler/index.ts +0 -98
  131. package/services/compiler/validator.ts +0 -158
  132. package/services/connector/clients/ioredis.ts +0 -57
  133. package/services/connector/clients/redis.ts +0 -72
  134. package/services/connector/index.ts +0 -42
  135. package/services/durable/client.ts +0 -266
  136. package/services/durable/connection.ts +0 -10
  137. package/services/durable/exporter.ts +0 -232
  138. package/services/durable/handle.ts +0 -160
  139. package/services/durable/index.ts +0 -27
  140. package/services/durable/schemas/factory.ts +0 -2358
  141. package/services/durable/search.ts +0 -196
  142. package/services/durable/worker.ts +0 -401
  143. package/services/durable/workflow.ts +0 -557
  144. package/services/engine/index.ts +0 -761
  145. package/services/exporter/index.ts +0 -146
  146. package/services/hotmesh/index.ts +0 -237
  147. package/services/logger/index.ts +0 -79
  148. package/services/mapper/index.ts +0 -89
  149. package/services/pipe/functions/array.ts +0 -78
  150. package/services/pipe/functions/bitwise.ts +0 -27
  151. package/services/pipe/functions/conditional.ts +0 -35
  152. package/services/pipe/functions/date.ts +0 -220
  153. package/services/pipe/functions/index.ts +0 -27
  154. package/services/pipe/functions/json.ts +0 -11
  155. package/services/pipe/functions/logical.ts +0 -11
  156. package/services/pipe/functions/math.ts +0 -217
  157. package/services/pipe/functions/number.ts +0 -75
  158. package/services/pipe/functions/object.ts +0 -98
  159. package/services/pipe/functions/string.ts +0 -86
  160. package/services/pipe/functions/symbol.ts +0 -39
  161. package/services/pipe/functions/unary.ts +0 -19
  162. package/services/pipe/index.ts +0 -216
  163. package/services/quorum/index.ts +0 -319
  164. package/services/reporter/index.ts +0 -387
  165. package/services/router/index.ts +0 -426
  166. package/services/serializer/README.md +0 -10
  167. package/services/serializer/index.ts +0 -285
  168. package/services/store/cache.ts +0 -172
  169. package/services/store/clients/ioredis.ts +0 -145
  170. package/services/store/clients/redis.ts +0 -191
  171. package/services/store/index.ts +0 -1091
  172. package/services/stream/clients/ioredis.ts +0 -157
  173. package/services/stream/clients/redis.ts +0 -158
  174. package/services/stream/index.ts +0 -58
  175. package/services/sub/clients/ioredis.ts +0 -83
  176. package/services/sub/clients/redis.ts +0 -74
  177. package/services/sub/index.ts +0 -25
  178. package/services/task/index.ts +0 -250
  179. package/services/telemetry/index.ts +0 -273
  180. package/services/worker/index.ts +0 -248
  181. package/types/ioredisclient.ts +0 -10
  182. package/types/redisclient.ts +0 -30
@@ -1,158 +0,0 @@
1
- import { Pipe } from "../pipe";
2
- import { StoreService } from '../store';
3
- import { MappingStatements } from "../../types/map";
4
- import { HotMeshManifest } from "../../types/hotmesh";
5
- import { RedisClient, RedisMulti } from "../../types/redis";
6
-
7
- class Validator {
8
- manifest: HotMeshManifest | null = null;
9
- activityIds: string[] = [];
10
- mappingStatements: MappingStatements = {};
11
- store: StoreService<RedisClient, RedisMulti> | null = null;
12
-
13
- static SYS_VARS = ['$app', '$self', '$graph', '$job'];
14
- static CONTEXT_VARS = ['{$input}', '{$output}', '{$item}', '{$key}', '{$index}'];
15
-
16
- constructor(manifest: HotMeshManifest) {
17
- this.manifest = manifest;
18
- }
19
-
20
- /**
21
- * validate the manifest file
22
- */
23
- async validate(store: StoreService<RedisClient, RedisMulti>) {
24
- this.store = store;
25
- this.getMappingStatements();
26
- this.validateActivityIds();
27
- this.validateReferencedActivityIds();
28
- this.validateMappingStatements();
29
- this.validateTransitions();
30
- this.validateTransitionConditions();
31
- this.validateStats();
32
- this.validateSchemas();
33
- this.validateUniqueHandledTopics();
34
- this.validateGraphPublishSubscribe();
35
- this.validateHooks();
36
- this.validateConditionalStatements();
37
- }
38
-
39
- // 1.1) Validate the manifest file activity ids are unique (no duplicates)
40
- validateActivityIds() {
41
- const activityIdsSet: Set<string> = new Set();
42
- this.manifest.app.graphs.forEach((graph) => {
43
- const ids = Object.keys(graph.activities);
44
- // Check for duplicates and add ids to the set
45
- ids.forEach((id) => {
46
- if (activityIdsSet.has(id)) {
47
- throw new Error(`Duplicate activity id found: ${id}`);
48
- } else {
49
- activityIdsSet.add(id);
50
- }
51
- });
52
- });
53
- this.activityIds = Array.from(activityIdsSet);
54
- }
55
-
56
- isMappingStatement(value: string): boolean {
57
- return typeof value === 'string' && value.startsWith('{') && value.endsWith('}');
58
- }
59
-
60
- extractMappingStatements(obj: any, result: MappingStatements, currentActivityId: string): void {
61
- for (const key in obj) {
62
- if (typeof obj[key] === 'object' && obj[key] !== null) {
63
- this.extractMappingStatements(obj[key], result, currentActivityId);
64
- } else if (this.isMappingStatement(obj[key])) {
65
- if (!result[currentActivityId]) {
66
- result[currentActivityId] = [];
67
- }
68
- result[currentActivityId].push(obj[key]);
69
- }
70
- }
71
- }
72
-
73
- getMappingStatements() {
74
- const mappingStatements: MappingStatements = {};
75
- this.manifest.app.graphs.forEach((graph) => {
76
- const activities = graph.activities;
77
- for (const activityId in activities) {
78
- const activity = activities[activityId];
79
- this.extractMappingStatements(activity, mappingStatements, activityId);
80
- }
81
- });
82
- this.mappingStatements = mappingStatements;
83
- }
84
-
85
- // 1.2) Validate no activity ids are referenced that don't exist
86
- validateReferencedActivityIds() {
87
- // get list of all mapping statements and validate
88
- const mappingStatements = this.mappingStatements;
89
- const activityIds = this.activityIds;
90
- for (const activity in mappingStatements) {
91
- const statements = mappingStatements[activity];
92
- statements.forEach((statement) => {
93
- if (statement.startsWith('{') && statement.endsWith('}')) {
94
- const statementParts = statement.slice(1, -1).split('.');
95
- const referencedActivityId = statementParts[0];
96
- if (!(Validator.SYS_VARS.includes(referencedActivityId) || activityIds.includes(referencedActivityId) || this.isFunction(statement) || this.isContextVariable(statement))) {
97
- throw new Error(`Mapping statement references non-existent activity: ${statement}`);
98
- }
99
- }
100
- });
101
- }
102
- }
103
-
104
- isFunction(value: string): boolean {
105
- return value.startsWith('{@') && Pipe.resolveFunction(value);
106
- }
107
-
108
- isContextVariable(value: string): boolean {
109
- return ['{$input}', '{$output}', '{$item}', '{$key}', '{$index}'].includes(value);
110
- }
111
-
112
- // 1.3) Validate the mapping/@pipe statements are valid
113
- validateMappingStatements() {
114
- // Implement the method content
115
- }
116
-
117
- // 1.4) Validate the transitions are valid
118
- validateTransitions() {
119
- // Implement the method content
120
- }
121
-
122
- // 1.5) Validate the transition conditions are valid
123
- validateTransitionConditions() {
124
- // Implement the method content
125
- }
126
-
127
- // 1.6) Validate the stats
128
- validateStats() {
129
- // Implement the method content
130
- }
131
-
132
- // 1.7) Validate the schemas
133
- validateSchemas() {
134
- // Implement the method content
135
- }
136
-
137
- // 1.8) Validate the topics are unique and handled
138
- validateUniqueHandledTopics() {
139
- // Implement the method content
140
- }
141
-
142
- // 1.9) Validate that every graph has publishes and subscribes
143
- validateGraphPublishSubscribe() {
144
- // Implement the method content
145
- }
146
-
147
- // 1.10) Validate hooks, including mapping statements
148
- validateHooks() {
149
- // Implement the method content
150
- }
151
-
152
- // 1.11) Validate conditional statements
153
- validateConditionalStatements() {
154
- // Implement the method content
155
- }
156
- }
157
-
158
- export { Validator };
@@ -1,57 +0,0 @@
1
- import {
2
- RedisClientOptions,
3
- RedisClassType,
4
- RedisClientType } from '../../../types/ioredisclient';
5
-
6
- class RedisConnection {
7
- private connection: any | null = null;
8
- private static instances: Map<string, RedisConnection> = new Map();
9
- private id: string | null = null;
10
-
11
- private static clientOptions: RedisClientOptions = {
12
- host: 'localhost',
13
- port: 6379,
14
- //password: config.REDIS_PASSWORD,
15
- //db: config.REDIS_DATABASE,
16
- };
17
-
18
- private async createConnection(Redis: RedisClassType, options: RedisClientOptions): Promise<any> {
19
- return new Redis(options);
20
- }
21
-
22
- public getClient(): RedisClientType {
23
- if (!this.connection) {
24
- throw new Error('Redis client is not connected');
25
- }
26
- return this.connection;
27
- }
28
-
29
- public async disconnect(): Promise<void> {
30
- if (this.connection) {
31
- await this.connection.quit();
32
- this.connection = null;
33
- }
34
- if (this.id) {
35
- RedisConnection.instances.delete(this.id);
36
- }
37
- }
38
-
39
- public static async connect(id: string, Redis: RedisClassType, options?: RedisClientOptions): Promise<RedisConnection> {
40
- if (this.instances.has(id)) {
41
- return this.instances.get(id) as RedisConnection;
42
- }
43
- const instance = new RedisConnection();
44
- const opts = options ? { ...options } : { ...this.clientOptions };
45
- instance.connection = await instance.createConnection(Redis, opts);
46
- instance.id = id;
47
- this.instances.set(id, instance);
48
- return instance;
49
- }
50
-
51
- public static async disconnectAll(): Promise<void> {
52
- await Promise.all(Array.from(this.instances.values()).map((instance) => instance.disconnect()));
53
- this.instances.clear();
54
- }
55
- }
56
-
57
- export { RedisConnection };
@@ -1,72 +0,0 @@
1
- import {
2
- RedisClientType,
3
- RedisClientOptions,
4
- RedisClassType } from '../../../types/redisclient';
5
-
6
- class RedisConnection {
7
- private connection: RedisClientType | null = null;
8
- private static instances: Map<string, RedisConnection> = new Map();
9
- private id: string | null = null;
10
-
11
- private static clientOptions: RedisClientOptions = {
12
- socket: {
13
- host: 'localhost',
14
- port: 6379,
15
- tls: false,
16
- },
17
- //password: config.REDIS_PASSWORD,
18
- //database: config.REDIS_DATABASE,
19
- };
20
-
21
- private async createConnection(Redis: RedisClassType, options: RedisClientOptions): Promise<RedisClientType> {
22
- return new Promise((resolve, reject) => {
23
- const client = Redis.createClient(options);
24
-
25
- client.on('error', (error: any) => {
26
- reject(error);
27
- });
28
-
29
- client.on('ready', () => {
30
- resolve(client);
31
- });
32
-
33
- client.connect();
34
- });
35
- }
36
-
37
- public getClient(): RedisClientType {
38
- if (!this.connection) {
39
- throw new Error('Redis client is not connected');
40
- }
41
- return this.connection;
42
- }
43
-
44
- public async disconnect(): Promise<void> {
45
- if (this.connection) {
46
- await this.connection.quit();
47
- this.connection = null;
48
- }
49
- if (this.id) {
50
- RedisConnection.instances.delete(this.id);
51
- }
52
- }
53
-
54
- public static async connect(id: string, Redis: RedisClassType, options?: RedisClientOptions): Promise<RedisConnection> {
55
- if (this.instances.has(id)) {
56
- return this.instances.get(id)!;
57
- }
58
- const instance = new RedisConnection();
59
- const opts = options ? { ...options } : { ...this.clientOptions };
60
- instance.connection = await instance.createConnection(Redis, opts);
61
- instance.id = id;
62
- this.instances.set(id, instance);
63
- return instance;
64
- }
65
-
66
- public static async disconnectAll(): Promise<void> {
67
- await Promise.all(Array.from(this.instances.values()).map((instance) => instance.disconnect()));
68
- this.instances.clear();
69
- }
70
- }
71
-
72
- export { RedisConnection, RedisClientType };
@@ -1,42 +0,0 @@
1
- import { guid, identifyRedisTypeFromClass } from '../../modules/utils';
2
- import { RedisConnection as IORedisConnection } from '../connector/clients/ioredis';
3
- import { RedisConnection } from '../connector/clients/redis';
4
- import {
5
- RedisClassType as IORedisClassType,
6
- RedisClientOptions as IORedisClientOptions } from '../../types/ioredisclient';
7
- import {
8
- HotMeshEngine,
9
- HotMeshWorker } from '../../types/hotmesh';
10
- import { RedisClass, RedisOptions } from '../../types/redis';
11
- import {
12
- RedisClassType,
13
- RedisClientOptions } from '../../types/redisclient';
14
-
15
- export class ConnectorService {
16
- //1) Initialize `store`, `stream`, and `subscription` Redis clients.
17
- //2) Bind to the target if not already present
18
- static async initRedisClients(Redis: RedisClass, options: RedisOptions, target: HotMeshEngine | HotMeshWorker): Promise<void> {
19
- if (!target.store || !target.stream || !target.sub) {
20
- const instances = [];
21
- if (identifyRedisTypeFromClass(Redis) === 'redis') {
22
- for (let i = 1; i <= 3; i++) {
23
- instances.push(RedisConnection.connect(
24
- guid(),
25
- Redis as RedisClassType,
26
- options as RedisClientOptions));
27
- }
28
- } else {
29
- for (let i = 1; i <= 3; i++) {
30
- instances.push(IORedisConnection.connect(
31
- guid(),
32
- Redis as IORedisClassType,
33
- options as IORedisClientOptions));
34
- }
35
- }
36
- const [store, stream, sub] = await Promise.all(instances);
37
- target.store = target.store || store.getClient();
38
- target.stream = target.stream || stream.getClient();
39
- target.sub = target.sub || sub.getClient();
40
- }
41
- }
42
- }
@@ -1,266 +0,0 @@
1
- import ms from 'ms';
2
-
3
- import {
4
- APP_ID,
5
- APP_VERSION,
6
- getWorkflowYAML } from './schemas/factory';
7
- import {
8
- HMSH_LOGLEVEL,
9
- HMSH_EXPIRE_JOB_SECONDS,
10
- HMSH_QUORUM_DELAY_MS,
11
- HMSH_DURABLE_EXP_BACKOFF,
12
- HMSH_DURABLE_MAX_ATTEMPTS,
13
- HMSH_DURABLE_MAX_INTERVAL } from '../../modules/enums';
14
- import { sleepFor } from '../../modules/utils';
15
- import { WorkflowHandleService } from './handle';
16
- import { HotMeshService as HotMesh } from '../hotmesh';
17
- import {
18
- ClientConfig,
19
- Connection,
20
- HookOptions,
21
- WorkflowOptions,
22
- WorkflowSearchOptions} from '../../types/durable';
23
- import { JobState } from '../../types/job';
24
- import { KeyService, KeyType } from '../../modules/key';
25
- import { Search } from './search';
26
- import { StreamStatus } from '../../types';
27
-
28
- export class ClientService {
29
-
30
- connection: Connection;
31
- options: WorkflowOptions;
32
- static topics: string[] = [];
33
- static instances = new Map<string, HotMesh | Promise<HotMesh>>();
34
-
35
- constructor(config: ClientConfig) {
36
- this.connection = config.connection;
37
- }
38
-
39
- getHotMeshClient = async (workflowTopic: string, namespace?: string) => {
40
- const targetNS = namespace ?? APP_ID;
41
- if (ClientService.instances.has(targetNS)) {
42
- const hotMeshClient = await ClientService.instances.get(targetNS);
43
- await this.verifyWorkflowActive(hotMeshClient, targetNS);
44
- if (!ClientService.topics.includes(workflowTopic)) {
45
- ClientService.topics.push(workflowTopic);
46
- await ClientService.createStream(hotMeshClient, workflowTopic, namespace);
47
- }
48
- return hotMeshClient;
49
- }
50
-
51
- //create and cache an instance
52
- const hotMeshClient = HotMesh.init({
53
- appId: targetNS,
54
- logLevel: HMSH_LOGLEVEL,
55
- engine: {
56
- redis: {
57
- class: this.connection.class,
58
- options: this.connection.options,
59
- }
60
- }
61
- });
62
- ClientService.instances.set(targetNS, hotMeshClient);
63
- await ClientService.createStream(await hotMeshClient, workflowTopic, namespace);
64
- await this.activateWorkflow(await hotMeshClient, targetNS);
65
- return hotMeshClient;
66
- }
67
-
68
- /**
69
- * Creates a stream (Redis `XGROUP.CREATE`) where events can be published (XADD).
70
- * It is possible that the worker that will read from this stream channel
71
- * has not yet been initialized, so this call ensures that the channel
72
- * exists and is ready to serve as a container for events.
73
- */
74
- static createStream = async(hotMeshClient: HotMesh, workflowTopic: string, namespace?: string) => {
75
- const store = hotMeshClient.engine.store;
76
- const params = { appId: namespace ?? APP_ID, topic: workflowTopic };
77
- const streamKey = store.mintKey(KeyType.STREAMS, params);
78
- try {
79
- await store.xgroup('CREATE', streamKey, 'WORKER', '$', 'MKSTREAM');
80
- } catch (err) {
81
- //ignore if already exists
82
- }
83
- }
84
-
85
- /**
86
- * It is possible for a client to invoke a workflow without first
87
- * creating the stream. This method will verify that the stream
88
- * exists and if not, create it.
89
- */
90
- static verifyStream = async(workflowTopic: string, namespace?: string) => {
91
- const targetNS = namespace ?? APP_ID;
92
- if (ClientService.instances.has(targetNS)) {
93
- const hotMeshClient = await ClientService.instances.get(targetNS);
94
- if (!ClientService.topics.includes(workflowTopic)) {
95
- ClientService.topics.push(workflowTopic);
96
- await ClientService.createStream(hotMeshClient, workflowTopic, namespace);
97
- }
98
- return hotMeshClient;
99
- }
100
- }
101
-
102
- /**
103
- * For those deployments with a redis stack backend (with the FT module),
104
- * this method will configure the search index for the workflow.
105
- */
106
- configureSearchIndex = async (hotMeshClient: HotMesh, search?: WorkflowSearchOptions): Promise<void> => {
107
- if (search?.schema) {
108
- const store = hotMeshClient.engine.store;
109
- const schema: string[] = [];
110
- for (const [key, value] of Object.entries(search.schema)) {
111
- //prefix with an underscore (avoids collisions with hotmesh reserved symbols)
112
- schema.push(`_${key}`);
113
- schema.push(value.type);
114
- if (value.sortable) {
115
- schema.push('SORTABLE');
116
- }
117
- }
118
- try {
119
- const keyParams = {
120
- appId: hotMeshClient.appId,
121
- jobId: ''
122
- }
123
- const hotMeshPrefix = KeyService.mintKey(hotMeshClient.namespace, KeyType.JOB_STATE, keyParams);
124
- const prefixes = search.prefix.map((prefix) => `${hotMeshPrefix}${prefix}`);
125
- await store.exec('FT.CREATE', `${search.index}`, 'ON', 'HASH', 'PREFIX', prefixes.length, ...prefixes, 'SCHEMA', ...schema);
126
- } catch (error) {
127
- hotMeshClient.engine.logger.info('durable-client-search-err', { ...error });
128
- }
129
- }
130
- }
131
-
132
- search = async (hotMeshClient: HotMesh, index: string, query: string[]): Promise<string[]> => {
133
- const store = hotMeshClient.engine.store;
134
- if (query[0]?.startsWith('FT.')) {
135
- return await store.exec(...query) as string[];
136
- }
137
- return await store.exec('FT.SEARCH', index, ...query) as string[];
138
- }
139
-
140
- workflow = {
141
- start: async (options: WorkflowOptions): Promise<WorkflowHandleService> => {
142
- const taskQueueName = options.entity ?? options.taskQueue;
143
- const workflowName = options.entity ?? options.workflowName;
144
- const trc = options.workflowTrace;
145
- const spn = options.workflowSpan;
146
- //NOTE: HotMesh 'workflowTopic' is a created by concatenating
147
- // the taskQueue and workflowName used by the Durable module
148
- const workflowTopic = `${taskQueueName}-${workflowName}`;
149
- const hotMeshClient = await this.getHotMeshClient(workflowTopic, options.namespace);
150
- this.configureSearchIndex(hotMeshClient, options.search);
151
- const payload = {
152
- arguments: [...options.args],
153
- originJobId: options.originJobId,
154
- expire: options.expire ?? HMSH_EXPIRE_JOB_SECONDS,
155
- parentWorkflowId: options.parentWorkflowId,
156
- workflowId: options.workflowId || HotMesh.guid(),
157
- workflowTopic: workflowTopic,
158
- backoffCoefficient: options.config?.backoffCoefficient || HMSH_DURABLE_EXP_BACKOFF,
159
- maximumAttempts: options.config?.maximumAttempts || HMSH_DURABLE_MAX_ATTEMPTS,
160
- maximumInterval: ms(options.config?.maximumInterval || HMSH_DURABLE_MAX_INTERVAL) / 1000,
161
- }
162
-
163
- const context = { metadata: { trc, spn }, data: {}};
164
- const jobId = await hotMeshClient.pub(
165
- `${options.namespace ?? APP_ID}.execute`,
166
- payload,
167
- context as JobState,
168
- { search: options?.search?.data, marker: options?.marker},
169
- );
170
- return new WorkflowHandleService(hotMeshClient, workflowTopic, jobId);
171
- },
172
-
173
- /**
174
- * send a message to a running workflow that is paused and awaiting the signal
175
- */
176
- signal: async (signalId: string, data: Record<any, any>, namespace?: string): Promise<string> => {
177
- const topic = `${namespace ?? APP_ID}.wfs.signal`;
178
- return await (await this.getHotMeshClient(topic, namespace)).hook(topic, { id: signalId, data });
179
- },
180
-
181
- /**
182
- * send a message to spawn an parallel in-process thread of execution
183
- * with the same job state as the main thread but bound to a different
184
- * handler function. All job state will be journaled to the same hash
185
- * as is used by the main thread.
186
- */
187
- hook: async (options: HookOptions): Promise<string> => {
188
- const workflowTopic = `${options.taskQueue}-${options.workflowName}`;
189
- const payload = {
190
- arguments: [...options.args],
191
- id: options.workflowId,
192
- workflowTopic,
193
- backoffCoefficient: options.config?.backoffCoefficient || HMSH_DURABLE_EXP_BACKOFF,
194
- maximumAttempts: options.config?.maximumAttempts || HMSH_DURABLE_MAX_ATTEMPTS,
195
- maximumInterval: ms(options.config?.maximumInterval || HMSH_DURABLE_MAX_INTERVAL) / 1000,
196
- }
197
- //seed search data if presentthe hook before entering
198
- const hotMeshClient = await this.getHotMeshClient(workflowTopic, options.namespace);
199
- const msgId = await hotMeshClient.hook(`${hotMeshClient.appId}.flow.signal`, payload, StreamStatus.PENDING, 202);
200
- if (options.search?.data) {
201
- const searchSessionId = `-search-${HotMesh.guid()}-0`;
202
- const search = new Search(options.workflowId, hotMeshClient, searchSessionId);
203
- const entries = Object.entries(options.search.data).flat();
204
- await search.set(...entries);
205
- }
206
- return msgId;
207
- },
208
-
209
- getHandle: async (taskQueue: string, workflowName: string, workflowId: string, namespace?: string): Promise<WorkflowHandleService> => {
210
- const workflowTopic = `${taskQueue}-${workflowName}`;
211
- const hotMeshClient = await this.getHotMeshClient(workflowTopic, namespace);
212
- return new WorkflowHandleService(hotMeshClient, workflowTopic, workflowId);
213
- },
214
-
215
- search: async (taskQueue: string, workflowName: string, namespace: null | string, index: string, ...query: string[]): Promise<string[]> => {
216
- const workflowTopic = `${taskQueue}-${workflowName}`;
217
- const hotMeshClient = await this.getHotMeshClient(workflowTopic, namespace);
218
- try {
219
- return await this.search(hotMeshClient, index, query);
220
- } catch (error) {
221
- hotMeshClient.engine.logger.error('durable-client-search-err', { ...error });
222
- throw error;
223
- }
224
- }
225
- }
226
-
227
- async verifyWorkflowActive(hotMesh: HotMesh, appId = APP_ID, count = 0): Promise<boolean> {
228
- const app = await hotMesh.engine.store.getApp(appId);
229
- const appVersion = app?.version as unknown as number;
230
- if(isNaN(appVersion)) {
231
- if (count > 10) {
232
- throw new Error('Workflow failed to activate');
233
- }
234
- await sleepFor(HMSH_QUORUM_DELAY_MS * 2);
235
- return await this.verifyWorkflowActive(hotMesh, appId, count + 1);
236
- }
237
- return true;
238
- }
239
-
240
- async activateWorkflow(hotMesh: HotMesh, appId = APP_ID, version = APP_VERSION): Promise<void> {
241
- const app = await hotMesh.engine.store.getApp(appId);
242
- const appVersion = app?.version as unknown as number;
243
- if(isNaN(appVersion)) {
244
- try {
245
- await hotMesh.deploy(getWorkflowYAML(appId, version));
246
- await hotMesh.activate(version);
247
- } catch (error) {
248
- hotMesh.engine.logger.error('durable-client-deploy-activate-err', { ...error });
249
- throw error;
250
- }
251
- } else if(app && !app.active) {
252
- try {
253
- await hotMesh.activate(version);
254
- } catch (error) {
255
- hotMesh.engine.logger.error('durable-client-activate-err', { error});
256
- throw error;
257
- }
258
- }
259
- }
260
-
261
- static async shutdown(): Promise<void> {
262
- for (const [_, hotMeshInstance] of ClientService.instances) {
263
- (await hotMeshInstance).stop();
264
- }
265
- }
266
- }
@@ -1,10 +0,0 @@
1
- import { Connection, ConnectionConfig } from "../../types/durable";
2
-
3
- export class ConnectionService {
4
- static async connect(config: ConnectionConfig): Promise<Connection> {
5
- return {
6
- class: config.class,
7
- options: { ...config.options },
8
- } as Connection;
9
- }
10
- }