@hotmeshio/hotmesh 0.6.0 → 0.7.0
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 +179 -142
- package/build/index.d.ts +3 -1
- package/build/index.js +5 -1
- package/build/modules/enums.d.ts +18 -0
- package/build/modules/enums.js +27 -1
- package/build/modules/utils.d.ts +27 -0
- package/build/modules/utils.js +79 -1
- package/build/package.json +24 -10
- package/build/services/connector/factory.d.ts +1 -1
- package/build/services/connector/factory.js +15 -1
- package/build/services/connector/providers/ioredis.d.ts +9 -0
- package/build/services/connector/providers/ioredis.js +26 -0
- package/build/services/connector/providers/postgres.js +3 -0
- package/build/services/connector/providers/redis.d.ts +9 -0
- package/build/services/connector/providers/redis.js +38 -0
- package/build/services/hotmesh/index.d.ts +66 -15
- package/build/services/hotmesh/index.js +84 -15
- package/build/services/memflow/index.d.ts +100 -14
- package/build/services/memflow/index.js +100 -14
- package/build/services/memflow/worker.d.ts +97 -0
- package/build/services/memflow/worker.js +217 -0
- package/build/services/memflow/workflow/proxyActivities.d.ts +74 -3
- package/build/services/memflow/workflow/proxyActivities.js +81 -4
- package/build/services/router/consumption/index.d.ts +2 -1
- package/build/services/router/consumption/index.js +38 -2
- package/build/services/router/error-handling/index.d.ts +3 -3
- package/build/services/router/error-handling/index.js +48 -13
- package/build/services/router/index.d.ts +1 -0
- package/build/services/router/index.js +2 -1
- package/build/services/search/factory.js +8 -0
- package/build/services/search/providers/redis/ioredis.d.ts +23 -0
- package/build/services/search/providers/redis/ioredis.js +189 -0
- package/build/services/search/providers/redis/redis.d.ts +23 -0
- package/build/services/search/providers/redis/redis.js +202 -0
- package/build/services/store/factory.js +9 -1
- package/build/services/store/index.d.ts +3 -2
- package/build/services/store/providers/postgres/kvtypes/hash/basic.js +36 -6
- package/build/services/store/providers/postgres/kvtypes/hash/expire.js +12 -2
- package/build/services/store/providers/postgres/kvtypes/hash/scan.js +30 -10
- package/build/services/store/providers/postgres/kvtypes/list.js +68 -10
- package/build/services/store/providers/postgres/kvtypes/string.js +60 -10
- package/build/services/store/providers/postgres/kvtypes/zset.js +92 -22
- package/build/services/store/providers/postgres/postgres.d.ts +3 -3
- package/build/services/store/providers/redis/_base.d.ts +137 -0
- package/build/services/store/providers/redis/_base.js +980 -0
- package/build/services/store/providers/redis/ioredis.d.ts +20 -0
- package/build/services/store/providers/redis/ioredis.js +190 -0
- package/build/services/store/providers/redis/redis.d.ts +18 -0
- package/build/services/store/providers/redis/redis.js +199 -0
- package/build/services/stream/factory.js +17 -1
- package/build/services/stream/providers/postgres/kvtables.js +76 -23
- package/build/services/stream/providers/postgres/lifecycle.d.ts +19 -0
- package/build/services/stream/providers/postgres/lifecycle.js +54 -0
- package/build/services/stream/providers/postgres/messages.d.ts +56 -0
- package/build/services/stream/providers/postgres/messages.js +253 -0
- package/build/services/stream/providers/postgres/notifications.d.ts +59 -0
- package/build/services/stream/providers/postgres/notifications.js +357 -0
- package/build/services/stream/providers/postgres/postgres.d.ts +110 -11
- package/build/services/stream/providers/postgres/postgres.js +196 -488
- package/build/services/stream/providers/postgres/scout.d.ts +68 -0
- package/build/services/stream/providers/postgres/scout.js +233 -0
- package/build/services/stream/providers/postgres/stats.d.ts +49 -0
- package/build/services/stream/providers/postgres/stats.js +113 -0
- package/build/services/stream/providers/redis/ioredis.d.ts +61 -0
- package/build/services/stream/providers/redis/ioredis.js +272 -0
- package/build/services/stream/providers/redis/redis.d.ts +61 -0
- package/build/services/stream/providers/redis/redis.js +305 -0
- package/build/services/sub/factory.js +8 -0
- package/build/services/sub/providers/postgres/postgres.js +37 -5
- package/build/services/sub/providers/redis/ioredis.d.ts +20 -0
- package/build/services/sub/providers/redis/ioredis.js +161 -0
- package/build/services/sub/providers/redis/redis.d.ts +18 -0
- package/build/services/sub/providers/redis/redis.js +148 -0
- package/build/services/worker/index.d.ts +1 -0
- package/build/services/worker/index.js +2 -0
- package/build/types/hotmesh.d.ts +42 -2
- package/build/types/index.d.ts +4 -3
- package/build/types/index.js +4 -1
- package/build/types/memflow.d.ts +32 -0
- package/build/types/provider.d.ts +17 -1
- package/build/types/redis.d.ts +258 -0
- package/build/types/redis.js +11 -0
- package/build/types/stream.d.ts +92 -1
- package/index.ts +4 -0
- package/package.json +24 -10
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ContextType, WorkflowInterceptor } from '../../types/memflow';
|
|
2
|
+
import { guid } from '../../modules/utils';
|
|
2
3
|
import { ClientService } from './client';
|
|
3
4
|
import { ConnectionService } from './connection';
|
|
4
5
|
import { Search } from './search';
|
|
@@ -61,6 +62,7 @@ import { didInterrupt } from './workflow/interruption';
|
|
|
61
62
|
* ### 3. Durable Activities & Proxies
|
|
62
63
|
* Define and execute durable activities with automatic retry:
|
|
63
64
|
* ```typescript
|
|
65
|
+
* // Default: activities use workflow's task queue
|
|
64
66
|
* const activities = MemFlow.workflow.proxyActivities<{
|
|
65
67
|
* analyzeDocument: typeof analyzeDocument;
|
|
66
68
|
* validateFindings: typeof validateFindings;
|
|
@@ -77,7 +79,29 @@ import { didInterrupt } from './workflow/interruption';
|
|
|
77
79
|
* const validation = await activities.validateFindings(analysis);
|
|
78
80
|
* ```
|
|
79
81
|
*
|
|
80
|
-
* ### 4.
|
|
82
|
+
* ### 4. Explicit Activity Registration
|
|
83
|
+
* Register activity workers explicitly before workflows start:
|
|
84
|
+
* ```typescript
|
|
85
|
+
* // Register shared activity pool for interceptors
|
|
86
|
+
* await MemFlow.registerActivityWorker({
|
|
87
|
+
* connection: {
|
|
88
|
+
* class: Postgres,
|
|
89
|
+
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
90
|
+
* },
|
|
91
|
+
* taskQueue: 'shared-activities'
|
|
92
|
+
* }, sharedActivities, 'shared-activities');
|
|
93
|
+
*
|
|
94
|
+
* // Register custom activity pool for specific use cases
|
|
95
|
+
* await MemFlow.registerActivityWorker({
|
|
96
|
+
* connection: {
|
|
97
|
+
* class: Postgres,
|
|
98
|
+
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
99
|
+
* },
|
|
100
|
+
* taskQueue: 'priority-activities'
|
|
101
|
+
* }, priorityActivities, 'priority-activities');
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* ### 5. Workflow Composition
|
|
81
105
|
* Build complex workflows through composition:
|
|
82
106
|
* ```typescript
|
|
83
107
|
* // Start a child workflow
|
|
@@ -100,29 +124,34 @@ import { didInterrupt } from './workflow/interruption';
|
|
|
100
124
|
* });
|
|
101
125
|
* ```
|
|
102
126
|
*
|
|
103
|
-
* ###
|
|
127
|
+
* ### 6. Workflow Interceptors
|
|
104
128
|
* Add cross-cutting concerns through interceptors that run as durable functions:
|
|
105
129
|
* ```typescript
|
|
106
|
-
* //
|
|
130
|
+
* // First register shared activity worker for interceptors
|
|
131
|
+
* await MemFlow.registerActivityWorker({
|
|
132
|
+
* connection: {
|
|
133
|
+
* class: Postgres,
|
|
134
|
+
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
135
|
+
* },
|
|
136
|
+
* taskQueue: 'interceptor-activities'
|
|
137
|
+
* }, { auditLog }, 'interceptor-activities');
|
|
138
|
+
*
|
|
139
|
+
* // Add audit interceptor that uses activities with explicit taskQueue
|
|
107
140
|
* MemFlow.registerInterceptor({
|
|
108
141
|
* async execute(ctx, next) {
|
|
109
142
|
* try {
|
|
110
|
-
* // Interceptors
|
|
111
|
-
* const
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
143
|
+
* // Interceptors use explicit taskQueue to prevent per-workflow queues
|
|
144
|
+
* const { auditLog } = MemFlow.workflow.proxyActivities<typeof activities>({
|
|
145
|
+
* activities: { auditLog },
|
|
146
|
+
* taskQueue: 'interceptor-activities', // Explicit shared queue
|
|
147
|
+
* retryPolicy: { maximumAttempts: 3 }
|
|
115
148
|
* });
|
|
116
149
|
*
|
|
117
|
-
*
|
|
118
|
-
* await MemFlow.workflow.sleepFor('100 milliseconds');
|
|
150
|
+
* await auditLog(ctx.get('workflowId'), 'started');
|
|
119
151
|
*
|
|
120
152
|
* const result = await next();
|
|
121
153
|
*
|
|
122
|
-
* await
|
|
123
|
-
* action: 'completed',
|
|
124
|
-
* timestamp: new Date().toISOString()
|
|
125
|
-
* });
|
|
154
|
+
* await auditLog(ctx.get('workflowId'), 'completed');
|
|
126
155
|
*
|
|
127
156
|
* return result;
|
|
128
157
|
* } catch (err) {
|
|
@@ -141,6 +170,16 @@ import { didInterrupt } from './workflow/interruption';
|
|
|
141
170
|
* ```typescript
|
|
142
171
|
* import { Client, Worker, MemFlow } from '@hotmeshio/hotmesh';
|
|
143
172
|
* import { Client as Postgres } from 'pg';
|
|
173
|
+
* import * as activities from './activities';
|
|
174
|
+
*
|
|
175
|
+
* // (Optional) Register shared activity workers for interceptors
|
|
176
|
+
* await MemFlow.registerActivityWorker({
|
|
177
|
+
* connection: {
|
|
178
|
+
* class: Postgres,
|
|
179
|
+
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
180
|
+
* },
|
|
181
|
+
* taskQueue: 'shared-activities'
|
|
182
|
+
* }, sharedActivities, 'shared-activities');
|
|
144
183
|
*
|
|
145
184
|
* // Initialize worker
|
|
146
185
|
* await Worker.create({
|
|
@@ -210,6 +249,49 @@ declare class MemFlowClass {
|
|
|
210
249
|
* equivalent to the Temporal `Worker` service.
|
|
211
250
|
*/
|
|
212
251
|
static Worker: typeof WorkerService;
|
|
252
|
+
/**
|
|
253
|
+
* Register activity workers for a task queue. Activities execute via message queue
|
|
254
|
+
* and can run on different servers from workflows.
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* ```typescript
|
|
258
|
+
* // Activity worker
|
|
259
|
+
* const activities = {
|
|
260
|
+
* async processPayment(amount: number) { return `Processed $${amount}`; },
|
|
261
|
+
* async sendEmail(to: string, msg: string) { /* ... *\/ }
|
|
262
|
+
* };
|
|
263
|
+
*
|
|
264
|
+
* await MemFlow.registerActivityWorker({
|
|
265
|
+
* connection: {
|
|
266
|
+
* class: Postgres,
|
|
267
|
+
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
268
|
+
* },
|
|
269
|
+
* taskQueue: 'payment'
|
|
270
|
+
* }, activities, 'payment');
|
|
271
|
+
*
|
|
272
|
+
* // Workflow worker (can be on different server)
|
|
273
|
+
* async function orderWorkflow(amount: number) {
|
|
274
|
+
* const { processPayment, sendEmail } = MemFlow.workflow.proxyActivities<{
|
|
275
|
+
* processPayment: (amount: number) => Promise<string>;
|
|
276
|
+
* sendEmail: (to: string, msg: string) => Promise<void>;
|
|
277
|
+
* }>({
|
|
278
|
+
* taskQueue: 'payment',
|
|
279
|
+
* retryPolicy: { maximumAttempts: 3 }
|
|
280
|
+
* });
|
|
281
|
+
*
|
|
282
|
+
* const result = await processPayment(amount);
|
|
283
|
+
* await sendEmail('customer@example.com', result);
|
|
284
|
+
* return result;
|
|
285
|
+
* }
|
|
286
|
+
*
|
|
287
|
+
* await MemFlow.Worker.create({
|
|
288
|
+
* connection: { class: Postgres, options: { connectionString: '...' } },
|
|
289
|
+
* taskQueue: 'orders',
|
|
290
|
+
* workflow: orderWorkflow
|
|
291
|
+
* });
|
|
292
|
+
* ```
|
|
293
|
+
*/
|
|
294
|
+
static registerActivityWorker: typeof WorkerService.registerActivityWorker;
|
|
213
295
|
/**
|
|
214
296
|
* The MemFlow `workflow` service is functionally
|
|
215
297
|
* equivalent to the Temporal `Workflow` service
|
|
@@ -234,6 +316,10 @@ declare class MemFlowClass {
|
|
|
234
316
|
* Clear all registered workflow interceptors
|
|
235
317
|
*/
|
|
236
318
|
static clearInterceptors(): void;
|
|
319
|
+
/**
|
|
320
|
+
* Generate a unique identifier for workflow IDs
|
|
321
|
+
*/
|
|
322
|
+
static guid: typeof guid;
|
|
237
323
|
/**
|
|
238
324
|
* Shutdown everything. All connections, workers, and clients will be closed.
|
|
239
325
|
* Include in your signal handlers to ensure a clean shutdown.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.MemFlow = void 0;
|
|
4
4
|
const hotmesh_1 = require("../hotmesh");
|
|
5
|
+
const utils_1 = require("../../modules/utils");
|
|
5
6
|
const client_1 = require("./client");
|
|
6
7
|
const connection_1 = require("./connection");
|
|
7
8
|
const search_1 = require("./search");
|
|
@@ -65,6 +66,7 @@ const interceptor_1 = require("./interceptor");
|
|
|
65
66
|
* ### 3. Durable Activities & Proxies
|
|
66
67
|
* Define and execute durable activities with automatic retry:
|
|
67
68
|
* ```typescript
|
|
69
|
+
* // Default: activities use workflow's task queue
|
|
68
70
|
* const activities = MemFlow.workflow.proxyActivities<{
|
|
69
71
|
* analyzeDocument: typeof analyzeDocument;
|
|
70
72
|
* validateFindings: typeof validateFindings;
|
|
@@ -81,7 +83,29 @@ const interceptor_1 = require("./interceptor");
|
|
|
81
83
|
* const validation = await activities.validateFindings(analysis);
|
|
82
84
|
* ```
|
|
83
85
|
*
|
|
84
|
-
* ### 4.
|
|
86
|
+
* ### 4. Explicit Activity Registration
|
|
87
|
+
* Register activity workers explicitly before workflows start:
|
|
88
|
+
* ```typescript
|
|
89
|
+
* // Register shared activity pool for interceptors
|
|
90
|
+
* await MemFlow.registerActivityWorker({
|
|
91
|
+
* connection: {
|
|
92
|
+
* class: Postgres,
|
|
93
|
+
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
94
|
+
* },
|
|
95
|
+
* taskQueue: 'shared-activities'
|
|
96
|
+
* }, sharedActivities, 'shared-activities');
|
|
97
|
+
*
|
|
98
|
+
* // Register custom activity pool for specific use cases
|
|
99
|
+
* await MemFlow.registerActivityWorker({
|
|
100
|
+
* connection: {
|
|
101
|
+
* class: Postgres,
|
|
102
|
+
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
103
|
+
* },
|
|
104
|
+
* taskQueue: 'priority-activities'
|
|
105
|
+
* }, priorityActivities, 'priority-activities');
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* ### 5. Workflow Composition
|
|
85
109
|
* Build complex workflows through composition:
|
|
86
110
|
* ```typescript
|
|
87
111
|
* // Start a child workflow
|
|
@@ -104,29 +128,34 @@ const interceptor_1 = require("./interceptor");
|
|
|
104
128
|
* });
|
|
105
129
|
* ```
|
|
106
130
|
*
|
|
107
|
-
* ###
|
|
131
|
+
* ### 6. Workflow Interceptors
|
|
108
132
|
* Add cross-cutting concerns through interceptors that run as durable functions:
|
|
109
133
|
* ```typescript
|
|
110
|
-
* //
|
|
134
|
+
* // First register shared activity worker for interceptors
|
|
135
|
+
* await MemFlow.registerActivityWorker({
|
|
136
|
+
* connection: {
|
|
137
|
+
* class: Postgres,
|
|
138
|
+
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
139
|
+
* },
|
|
140
|
+
* taskQueue: 'interceptor-activities'
|
|
141
|
+
* }, { auditLog }, 'interceptor-activities');
|
|
142
|
+
*
|
|
143
|
+
* // Add audit interceptor that uses activities with explicit taskQueue
|
|
111
144
|
* MemFlow.registerInterceptor({
|
|
112
145
|
* async execute(ctx, next) {
|
|
113
146
|
* try {
|
|
114
|
-
* // Interceptors
|
|
115
|
-
* const
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
147
|
+
* // Interceptors use explicit taskQueue to prevent per-workflow queues
|
|
148
|
+
* const { auditLog } = MemFlow.workflow.proxyActivities<typeof activities>({
|
|
149
|
+
* activities: { auditLog },
|
|
150
|
+
* taskQueue: 'interceptor-activities', // Explicit shared queue
|
|
151
|
+
* retryPolicy: { maximumAttempts: 3 }
|
|
119
152
|
* });
|
|
120
153
|
*
|
|
121
|
-
*
|
|
122
|
-
* await MemFlow.workflow.sleepFor('100 milliseconds');
|
|
154
|
+
* await auditLog(ctx.get('workflowId'), 'started');
|
|
123
155
|
*
|
|
124
156
|
* const result = await next();
|
|
125
157
|
*
|
|
126
|
-
* await
|
|
127
|
-
* action: 'completed',
|
|
128
|
-
* timestamp: new Date().toISOString()
|
|
129
|
-
* });
|
|
158
|
+
* await auditLog(ctx.get('workflowId'), 'completed');
|
|
130
159
|
*
|
|
131
160
|
* return result;
|
|
132
161
|
* } catch (err) {
|
|
@@ -145,6 +174,16 @@ const interceptor_1 = require("./interceptor");
|
|
|
145
174
|
* ```typescript
|
|
146
175
|
* import { Client, Worker, MemFlow } from '@hotmeshio/hotmesh';
|
|
147
176
|
* import { Client as Postgres } from 'pg';
|
|
177
|
+
* import * as activities from './activities';
|
|
178
|
+
*
|
|
179
|
+
* // (Optional) Register shared activity workers for interceptors
|
|
180
|
+
* await MemFlow.registerActivityWorker({
|
|
181
|
+
* connection: {
|
|
182
|
+
* class: Postgres,
|
|
183
|
+
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
184
|
+
* },
|
|
185
|
+
* taskQueue: 'shared-activities'
|
|
186
|
+
* }, sharedActivities, 'shared-activities');
|
|
148
187
|
*
|
|
149
188
|
* // Initialize worker
|
|
150
189
|
* await Worker.create({
|
|
@@ -245,6 +284,49 @@ MemFlowClass.Handle = handle_1.WorkflowHandleService;
|
|
|
245
284
|
* equivalent to the Temporal `Worker` service.
|
|
246
285
|
*/
|
|
247
286
|
MemFlowClass.Worker = worker_1.WorkerService;
|
|
287
|
+
/**
|
|
288
|
+
* Register activity workers for a task queue. Activities execute via message queue
|
|
289
|
+
* and can run on different servers from workflows.
|
|
290
|
+
*
|
|
291
|
+
* @example
|
|
292
|
+
* ```typescript
|
|
293
|
+
* // Activity worker
|
|
294
|
+
* const activities = {
|
|
295
|
+
* async processPayment(amount: number) { return `Processed $${amount}`; },
|
|
296
|
+
* async sendEmail(to: string, msg: string) { /* ... *\/ }
|
|
297
|
+
* };
|
|
298
|
+
*
|
|
299
|
+
* await MemFlow.registerActivityWorker({
|
|
300
|
+
* connection: {
|
|
301
|
+
* class: Postgres,
|
|
302
|
+
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
303
|
+
* },
|
|
304
|
+
* taskQueue: 'payment'
|
|
305
|
+
* }, activities, 'payment');
|
|
306
|
+
*
|
|
307
|
+
* // Workflow worker (can be on different server)
|
|
308
|
+
* async function orderWorkflow(amount: number) {
|
|
309
|
+
* const { processPayment, sendEmail } = MemFlow.workflow.proxyActivities<{
|
|
310
|
+
* processPayment: (amount: number) => Promise<string>;
|
|
311
|
+
* sendEmail: (to: string, msg: string) => Promise<void>;
|
|
312
|
+
* }>({
|
|
313
|
+
* taskQueue: 'payment',
|
|
314
|
+
* retryPolicy: { maximumAttempts: 3 }
|
|
315
|
+
* });
|
|
316
|
+
*
|
|
317
|
+
* const result = await processPayment(amount);
|
|
318
|
+
* await sendEmail('customer@example.com', result);
|
|
319
|
+
* return result;
|
|
320
|
+
* }
|
|
321
|
+
*
|
|
322
|
+
* await MemFlow.Worker.create({
|
|
323
|
+
* connection: { class: Postgres, options: { connectionString: '...' } },
|
|
324
|
+
* taskQueue: 'orders',
|
|
325
|
+
* workflow: orderWorkflow
|
|
326
|
+
* });
|
|
327
|
+
* ```
|
|
328
|
+
*/
|
|
329
|
+
MemFlowClass.registerActivityWorker = worker_1.WorkerService.registerActivityWorker;
|
|
248
330
|
/**
|
|
249
331
|
* The MemFlow `workflow` service is functionally
|
|
250
332
|
* equivalent to the Temporal `Workflow` service
|
|
@@ -260,3 +342,7 @@ MemFlowClass.workflow = workflow_1.WorkflowService;
|
|
|
260
342
|
*/
|
|
261
343
|
MemFlowClass.didInterrupt = interruption_1.didInterrupt;
|
|
262
344
|
MemFlowClass.interceptorService = new interceptor_1.InterceptorService();
|
|
345
|
+
/**
|
|
346
|
+
* Generate a unique identifier for workflow IDs
|
|
347
|
+
*/
|
|
348
|
+
MemFlowClass.guid = utils_1.guid;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HotMesh } from '../hotmesh';
|
|
2
2
|
import { Connection, Registry, WorkerConfig, WorkerOptions } from '../../types/memflow';
|
|
3
|
+
import { StreamData, StreamDataResponse } from '../../types/stream';
|
|
3
4
|
/**
|
|
4
5
|
* The *Worker* service Registers worker functions and connects them to the mesh,
|
|
5
6
|
* using the target backend provider/s (Postgres, NATS, etc).
|
|
@@ -58,6 +59,102 @@ export declare class WorkerService {
|
|
|
58
59
|
* @private
|
|
59
60
|
*/
|
|
60
61
|
static registerActivities<ACT>(activities: ACT): Registry;
|
|
62
|
+
/**
|
|
63
|
+
* Register activity workers for a task queue. Activities are invoked via message queue,
|
|
64
|
+
* so they can run on different servers from workflows.
|
|
65
|
+
*
|
|
66
|
+
* The task queue name gets `-activity` appended automatically for the worker topic.
|
|
67
|
+
* For example, `taskQueue: 'payment'` creates a worker listening on `payment-activity`.
|
|
68
|
+
*
|
|
69
|
+
* @param config - Worker configuration (connection, namespace, taskQueue)
|
|
70
|
+
* @param activities - Activity functions to register
|
|
71
|
+
* @param activityTaskQueue - Task queue name (without `-activity` suffix).
|
|
72
|
+
* Defaults to `config.taskQueue` if not provided.
|
|
73
|
+
*
|
|
74
|
+
* @returns Promise<HotMesh> The initialized activity worker
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* // Activity worker (can be on separate server)
|
|
79
|
+
* import { MemFlow } from '@hotmeshio/hotmesh';
|
|
80
|
+
* import { Client as Postgres } from 'pg';
|
|
81
|
+
*
|
|
82
|
+
* const activities = {
|
|
83
|
+
* async processPayment(amount: number): Promise<string> {
|
|
84
|
+
* return `Processed $${amount}`;
|
|
85
|
+
* },
|
|
86
|
+
* async sendEmail(to: string, subject: string): Promise<void> {
|
|
87
|
+
* // Send email
|
|
88
|
+
* }
|
|
89
|
+
* };
|
|
90
|
+
*
|
|
91
|
+
* await MemFlow.registerActivityWorker({
|
|
92
|
+
* connection: {
|
|
93
|
+
* class: Postgres,
|
|
94
|
+
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
95
|
+
* },
|
|
96
|
+
* taskQueue: 'payment' // Listens on 'payment-activity'
|
|
97
|
+
* }, activities, 'payment');
|
|
98
|
+
* ```
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* // Workflow worker (can be on different server)
|
|
103
|
+
* async function orderWorkflow(orderId: string, amount: number) {
|
|
104
|
+
* const { processPayment, sendEmail } = MemFlow.workflow.proxyActivities<{
|
|
105
|
+
* processPayment: (amount: number) => Promise<string>;
|
|
106
|
+
* sendEmail: (to: string, subject: string) => Promise<void>;
|
|
107
|
+
* }>({
|
|
108
|
+
* taskQueue: 'payment',
|
|
109
|
+
* retryPolicy: { maximumAttempts: 3 }
|
|
110
|
+
* });
|
|
111
|
+
*
|
|
112
|
+
* const result = await processPayment(amount);
|
|
113
|
+
* await sendEmail('customer@example.com', 'Order confirmed');
|
|
114
|
+
* return result;
|
|
115
|
+
* }
|
|
116
|
+
*
|
|
117
|
+
* await MemFlow.Worker.create({
|
|
118
|
+
* connection: {
|
|
119
|
+
* class: Postgres,
|
|
120
|
+
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
121
|
+
* },
|
|
122
|
+
* taskQueue: 'orders',
|
|
123
|
+
* workflow: orderWorkflow
|
|
124
|
+
* });
|
|
125
|
+
* ```
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* // Shared activity pool for interceptors
|
|
130
|
+
* await MemFlow.registerActivityWorker({
|
|
131
|
+
* connection: {
|
|
132
|
+
* class: Postgres,
|
|
133
|
+
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
134
|
+
* },
|
|
135
|
+
* taskQueue: 'shared'
|
|
136
|
+
* }, { auditLog, collectMetrics }, 'shared');
|
|
137
|
+
*
|
|
138
|
+
* const interceptor: WorkflowInterceptor = {
|
|
139
|
+
* async execute(ctx, next) {
|
|
140
|
+
* const { auditLog } = MemFlow.workflow.proxyActivities<{
|
|
141
|
+
* auditLog: (id: string, action: string) => Promise<void>;
|
|
142
|
+
* }>({
|
|
143
|
+
* taskQueue: 'shared',
|
|
144
|
+
* retryPolicy: { maximumAttempts: 3 }
|
|
145
|
+
* });
|
|
146
|
+
* await auditLog(ctx.get('workflowId'), 'started');
|
|
147
|
+
* return next();
|
|
148
|
+
* }
|
|
149
|
+
* };
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
static registerActivityWorker(config: Partial<WorkerConfig>, activities: any, activityTaskQueue?: string): Promise<HotMesh>;
|
|
153
|
+
/**
|
|
154
|
+
* Create an activity callback function that can be used by activity workers
|
|
155
|
+
* @private
|
|
156
|
+
*/
|
|
157
|
+
static createActivityCallback(): (payload: StreamData) => Promise<StreamDataResponse>;
|
|
61
158
|
/**
|
|
62
159
|
* Connects a worker to the mesh.
|
|
63
160
|
*
|
|
@@ -104,6 +104,219 @@ class WorkerService {
|
|
|
104
104
|
}
|
|
105
105
|
return WorkerService.activityRegistry;
|
|
106
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Register activity workers for a task queue. Activities are invoked via message queue,
|
|
109
|
+
* so they can run on different servers from workflows.
|
|
110
|
+
*
|
|
111
|
+
* The task queue name gets `-activity` appended automatically for the worker topic.
|
|
112
|
+
* For example, `taskQueue: 'payment'` creates a worker listening on `payment-activity`.
|
|
113
|
+
*
|
|
114
|
+
* @param config - Worker configuration (connection, namespace, taskQueue)
|
|
115
|
+
* @param activities - Activity functions to register
|
|
116
|
+
* @param activityTaskQueue - Task queue name (without `-activity` suffix).
|
|
117
|
+
* Defaults to `config.taskQueue` if not provided.
|
|
118
|
+
*
|
|
119
|
+
* @returns Promise<HotMesh> The initialized activity worker
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* // Activity worker (can be on separate server)
|
|
124
|
+
* import { MemFlow } from '@hotmeshio/hotmesh';
|
|
125
|
+
* import { Client as Postgres } from 'pg';
|
|
126
|
+
*
|
|
127
|
+
* const activities = {
|
|
128
|
+
* async processPayment(amount: number): Promise<string> {
|
|
129
|
+
* return `Processed $${amount}`;
|
|
130
|
+
* },
|
|
131
|
+
* async sendEmail(to: string, subject: string): Promise<void> {
|
|
132
|
+
* // Send email
|
|
133
|
+
* }
|
|
134
|
+
* };
|
|
135
|
+
*
|
|
136
|
+
* await MemFlow.registerActivityWorker({
|
|
137
|
+
* connection: {
|
|
138
|
+
* class: Postgres,
|
|
139
|
+
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
140
|
+
* },
|
|
141
|
+
* taskQueue: 'payment' // Listens on 'payment-activity'
|
|
142
|
+
* }, activities, 'payment');
|
|
143
|
+
* ```
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```typescript
|
|
147
|
+
* // Workflow worker (can be on different server)
|
|
148
|
+
* async function orderWorkflow(orderId: string, amount: number) {
|
|
149
|
+
* const { processPayment, sendEmail } = MemFlow.workflow.proxyActivities<{
|
|
150
|
+
* processPayment: (amount: number) => Promise<string>;
|
|
151
|
+
* sendEmail: (to: string, subject: string) => Promise<void>;
|
|
152
|
+
* }>({
|
|
153
|
+
* taskQueue: 'payment',
|
|
154
|
+
* retryPolicy: { maximumAttempts: 3 }
|
|
155
|
+
* });
|
|
156
|
+
*
|
|
157
|
+
* const result = await processPayment(amount);
|
|
158
|
+
* await sendEmail('customer@example.com', 'Order confirmed');
|
|
159
|
+
* return result;
|
|
160
|
+
* }
|
|
161
|
+
*
|
|
162
|
+
* await MemFlow.Worker.create({
|
|
163
|
+
* connection: {
|
|
164
|
+
* class: Postgres,
|
|
165
|
+
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
166
|
+
* },
|
|
167
|
+
* taskQueue: 'orders',
|
|
168
|
+
* workflow: orderWorkflow
|
|
169
|
+
* });
|
|
170
|
+
* ```
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```typescript
|
|
174
|
+
* // Shared activity pool for interceptors
|
|
175
|
+
* await MemFlow.registerActivityWorker({
|
|
176
|
+
* connection: {
|
|
177
|
+
* class: Postgres,
|
|
178
|
+
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
179
|
+
* },
|
|
180
|
+
* taskQueue: 'shared'
|
|
181
|
+
* }, { auditLog, collectMetrics }, 'shared');
|
|
182
|
+
*
|
|
183
|
+
* const interceptor: WorkflowInterceptor = {
|
|
184
|
+
* async execute(ctx, next) {
|
|
185
|
+
* const { auditLog } = MemFlow.workflow.proxyActivities<{
|
|
186
|
+
* auditLog: (id: string, action: string) => Promise<void>;
|
|
187
|
+
* }>({
|
|
188
|
+
* taskQueue: 'shared',
|
|
189
|
+
* retryPolicy: { maximumAttempts: 3 }
|
|
190
|
+
* });
|
|
191
|
+
* await auditLog(ctx.get('workflowId'), 'started');
|
|
192
|
+
* return next();
|
|
193
|
+
* }
|
|
194
|
+
* };
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
static async registerActivityWorker(config, activities, activityTaskQueue) {
|
|
198
|
+
// Register activities globally in the registry
|
|
199
|
+
WorkerService.registerActivities(activities);
|
|
200
|
+
// Use provided activityTaskQueue or fall back to config.taskQueue
|
|
201
|
+
const taskQueue = activityTaskQueue || config.taskQueue || 'memflow-activities';
|
|
202
|
+
// Append '-activity' suffix for the worker topic
|
|
203
|
+
const activityTopic = `${taskQueue}-activity`;
|
|
204
|
+
const targetNamespace = config?.namespace ?? factory_1.APP_ID;
|
|
205
|
+
const optionsHash = WorkerService.hashOptions(config?.connection);
|
|
206
|
+
const targetTopic = `${optionsHash}.${targetNamespace}.${activityTopic}`;
|
|
207
|
+
// Return existing worker if already initialized (idempotent)
|
|
208
|
+
if (WorkerService.instances.has(targetTopic)) {
|
|
209
|
+
return await WorkerService.instances.get(targetTopic);
|
|
210
|
+
}
|
|
211
|
+
// Create activity worker that listens on '{taskQueue}-activity' topic
|
|
212
|
+
const hotMeshWorker = await hotmesh_1.HotMesh.init({
|
|
213
|
+
guid: config.guid ? `${config.guid}XA` : undefined,
|
|
214
|
+
taskQueue,
|
|
215
|
+
logLevel: config.options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
|
|
216
|
+
appId: targetNamespace,
|
|
217
|
+
engine: { connection: config.connection },
|
|
218
|
+
workers: [
|
|
219
|
+
{
|
|
220
|
+
topic: activityTopic,
|
|
221
|
+
connection: config.connection,
|
|
222
|
+
callback: WorkerService.createActivityCallback(),
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
});
|
|
226
|
+
WorkerService.instances.set(targetTopic, hotMeshWorker);
|
|
227
|
+
return hotMeshWorker;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Create an activity callback function that can be used by activity workers
|
|
231
|
+
* @private
|
|
232
|
+
*/
|
|
233
|
+
static createActivityCallback() {
|
|
234
|
+
return async (data) => {
|
|
235
|
+
try {
|
|
236
|
+
//always run the activity function when instructed; return the response
|
|
237
|
+
const activityInput = data.data;
|
|
238
|
+
const activityName = activityInput.activityName;
|
|
239
|
+
const activityFunction = WorkerService.activityRegistry[activityName];
|
|
240
|
+
if (!activityFunction) {
|
|
241
|
+
throw new Error(`Activity '${activityName}' not found in registry`);
|
|
242
|
+
}
|
|
243
|
+
const pojoResponse = await activityFunction.apply(null, activityInput.arguments);
|
|
244
|
+
return {
|
|
245
|
+
status: stream_1.StreamStatus.SUCCESS,
|
|
246
|
+
metadata: { ...data.metadata },
|
|
247
|
+
data: { response: pojoResponse },
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
// Log error (note: we don't have access to this.activityRunner here)
|
|
252
|
+
console.error('memflow-worker-activity-err', {
|
|
253
|
+
name: err.name,
|
|
254
|
+
message: err.message,
|
|
255
|
+
stack: err.stack,
|
|
256
|
+
});
|
|
257
|
+
if (!(err instanceof errors_1.MemFlowTimeoutError) &&
|
|
258
|
+
!(err instanceof errors_1.MemFlowMaxedError) &&
|
|
259
|
+
!(err instanceof errors_1.MemFlowFatalError)) {
|
|
260
|
+
//use code 599 as a proxy for all retryable errors
|
|
261
|
+
// (basically anything not 596, 597, 598)
|
|
262
|
+
return {
|
|
263
|
+
status: stream_1.StreamStatus.SUCCESS,
|
|
264
|
+
code: 599,
|
|
265
|
+
metadata: { ...data.metadata },
|
|
266
|
+
data: {
|
|
267
|
+
$error: {
|
|
268
|
+
message: err.message,
|
|
269
|
+
stack: err.stack,
|
|
270
|
+
code: enums_1.HMSH_CODE_MEMFLOW_RETRYABLE,
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
else if (err instanceof errors_1.MemFlowTimeoutError) {
|
|
276
|
+
return {
|
|
277
|
+
status: stream_1.StreamStatus.SUCCESS,
|
|
278
|
+
code: 596,
|
|
279
|
+
metadata: { ...data.metadata },
|
|
280
|
+
data: {
|
|
281
|
+
$error: {
|
|
282
|
+
message: err.message,
|
|
283
|
+
stack: err.stack,
|
|
284
|
+
code: enums_1.HMSH_CODE_MEMFLOW_TIMEOUT,
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
else if (err instanceof errors_1.MemFlowMaxedError) {
|
|
290
|
+
return {
|
|
291
|
+
status: stream_1.StreamStatus.SUCCESS,
|
|
292
|
+
code: 597,
|
|
293
|
+
metadata: { ...data.metadata },
|
|
294
|
+
data: {
|
|
295
|
+
$error: {
|
|
296
|
+
message: err.message,
|
|
297
|
+
stack: err.stack,
|
|
298
|
+
code: enums_1.HMSH_CODE_MEMFLOW_MAXED,
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
else if (err instanceof errors_1.MemFlowFatalError) {
|
|
304
|
+
return {
|
|
305
|
+
status: stream_1.StreamStatus.SUCCESS,
|
|
306
|
+
code: 598,
|
|
307
|
+
metadata: { ...data.metadata },
|
|
308
|
+
data: {
|
|
309
|
+
$error: {
|
|
310
|
+
message: err.message,
|
|
311
|
+
stack: err.stack,
|
|
312
|
+
code: enums_1.HMSH_CODE_MEMFLOW_FATAL,
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
107
320
|
/**
|
|
108
321
|
* Connects a worker to the mesh.
|
|
109
322
|
*
|
|
@@ -173,6 +386,10 @@ class WorkerService {
|
|
|
173
386
|
const targetNamespace = config?.namespace ?? factory_1.APP_ID;
|
|
174
387
|
const optionsHash = WorkerService.hashOptions(config?.connection);
|
|
175
388
|
const targetTopic = `${optionsHash}.${targetNamespace}.${activityTopic}`;
|
|
389
|
+
// Return existing worker if already initialized
|
|
390
|
+
if (WorkerService.instances.has(targetTopic)) {
|
|
391
|
+
return await WorkerService.instances.get(targetTopic);
|
|
392
|
+
}
|
|
176
393
|
const hotMeshWorker = await hotmesh_1.HotMesh.init({
|
|
177
394
|
guid: config.guid ? `${config.guid}XA` : undefined,
|
|
178
395
|
taskQueue: config.taskQueue,
|