@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.
Files changed (85) hide show
  1. package/README.md +179 -142
  2. package/build/index.d.ts +3 -1
  3. package/build/index.js +5 -1
  4. package/build/modules/enums.d.ts +18 -0
  5. package/build/modules/enums.js +27 -1
  6. package/build/modules/utils.d.ts +27 -0
  7. package/build/modules/utils.js +79 -1
  8. package/build/package.json +24 -10
  9. package/build/services/connector/factory.d.ts +1 -1
  10. package/build/services/connector/factory.js +15 -1
  11. package/build/services/connector/providers/ioredis.d.ts +9 -0
  12. package/build/services/connector/providers/ioredis.js +26 -0
  13. package/build/services/connector/providers/postgres.js +3 -0
  14. package/build/services/connector/providers/redis.d.ts +9 -0
  15. package/build/services/connector/providers/redis.js +38 -0
  16. package/build/services/hotmesh/index.d.ts +66 -15
  17. package/build/services/hotmesh/index.js +84 -15
  18. package/build/services/memflow/index.d.ts +100 -14
  19. package/build/services/memflow/index.js +100 -14
  20. package/build/services/memflow/worker.d.ts +97 -0
  21. package/build/services/memflow/worker.js +217 -0
  22. package/build/services/memflow/workflow/proxyActivities.d.ts +74 -3
  23. package/build/services/memflow/workflow/proxyActivities.js +81 -4
  24. package/build/services/router/consumption/index.d.ts +2 -1
  25. package/build/services/router/consumption/index.js +38 -2
  26. package/build/services/router/error-handling/index.d.ts +3 -3
  27. package/build/services/router/error-handling/index.js +48 -13
  28. package/build/services/router/index.d.ts +1 -0
  29. package/build/services/router/index.js +2 -1
  30. package/build/services/search/factory.js +8 -0
  31. package/build/services/search/providers/redis/ioredis.d.ts +23 -0
  32. package/build/services/search/providers/redis/ioredis.js +189 -0
  33. package/build/services/search/providers/redis/redis.d.ts +23 -0
  34. package/build/services/search/providers/redis/redis.js +202 -0
  35. package/build/services/store/factory.js +9 -1
  36. package/build/services/store/index.d.ts +3 -2
  37. package/build/services/store/providers/postgres/kvtypes/hash/basic.js +36 -6
  38. package/build/services/store/providers/postgres/kvtypes/hash/expire.js +12 -2
  39. package/build/services/store/providers/postgres/kvtypes/hash/scan.js +30 -10
  40. package/build/services/store/providers/postgres/kvtypes/list.js +68 -10
  41. package/build/services/store/providers/postgres/kvtypes/string.js +60 -10
  42. package/build/services/store/providers/postgres/kvtypes/zset.js +92 -22
  43. package/build/services/store/providers/postgres/postgres.d.ts +3 -3
  44. package/build/services/store/providers/redis/_base.d.ts +137 -0
  45. package/build/services/store/providers/redis/_base.js +980 -0
  46. package/build/services/store/providers/redis/ioredis.d.ts +20 -0
  47. package/build/services/store/providers/redis/ioredis.js +190 -0
  48. package/build/services/store/providers/redis/redis.d.ts +18 -0
  49. package/build/services/store/providers/redis/redis.js +199 -0
  50. package/build/services/stream/factory.js +17 -1
  51. package/build/services/stream/providers/postgres/kvtables.js +76 -23
  52. package/build/services/stream/providers/postgres/lifecycle.d.ts +19 -0
  53. package/build/services/stream/providers/postgres/lifecycle.js +54 -0
  54. package/build/services/stream/providers/postgres/messages.d.ts +56 -0
  55. package/build/services/stream/providers/postgres/messages.js +253 -0
  56. package/build/services/stream/providers/postgres/notifications.d.ts +59 -0
  57. package/build/services/stream/providers/postgres/notifications.js +357 -0
  58. package/build/services/stream/providers/postgres/postgres.d.ts +110 -11
  59. package/build/services/stream/providers/postgres/postgres.js +196 -488
  60. package/build/services/stream/providers/postgres/scout.d.ts +68 -0
  61. package/build/services/stream/providers/postgres/scout.js +233 -0
  62. package/build/services/stream/providers/postgres/stats.d.ts +49 -0
  63. package/build/services/stream/providers/postgres/stats.js +113 -0
  64. package/build/services/stream/providers/redis/ioredis.d.ts +61 -0
  65. package/build/services/stream/providers/redis/ioredis.js +272 -0
  66. package/build/services/stream/providers/redis/redis.d.ts +61 -0
  67. package/build/services/stream/providers/redis/redis.js +305 -0
  68. package/build/services/sub/factory.js +8 -0
  69. package/build/services/sub/providers/postgres/postgres.js +37 -5
  70. package/build/services/sub/providers/redis/ioredis.d.ts +20 -0
  71. package/build/services/sub/providers/redis/ioredis.js +161 -0
  72. package/build/services/sub/providers/redis/redis.d.ts +18 -0
  73. package/build/services/sub/providers/redis/redis.js +148 -0
  74. package/build/services/worker/index.d.ts +1 -0
  75. package/build/services/worker/index.js +2 -0
  76. package/build/types/hotmesh.d.ts +42 -2
  77. package/build/types/index.d.ts +4 -3
  78. package/build/types/index.js +4 -1
  79. package/build/types/memflow.d.ts +32 -0
  80. package/build/types/provider.d.ts +17 -1
  81. package/build/types/redis.d.ts +258 -0
  82. package/build/types/redis.js +11 -0
  83. package/build/types/stream.d.ts +92 -1
  84. package/index.ts +4 -0
  85. 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. Workflow Composition
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
- * ### 5. Workflow Interceptors
127
+ * ### 6. Workflow Interceptors
104
128
  * Add cross-cutting concerns through interceptors that run as durable functions:
105
129
  * ```typescript
106
- * // Add audit interceptor that uses MemFlow functions
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 can use MemFlow functions and participate in replay
111
- * const entity = await MemFlow.workflow.entity();
112
- * await entity.append('auditLog', {
113
- * action: 'started',
114
- * timestamp: new Date().toISOString()
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
- * // Rate limiting with durable sleep
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 entity.append('auditLog', {
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. Workflow Composition
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
- * ### 5. Workflow Interceptors
131
+ * ### 6. Workflow Interceptors
108
132
  * Add cross-cutting concerns through interceptors that run as durable functions:
109
133
  * ```typescript
110
- * // Add audit interceptor that uses MemFlow functions
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 can use MemFlow functions and participate in replay
115
- * const entity = await MemFlow.workflow.entity();
116
- * await entity.append('auditLog', {
117
- * action: 'started',
118
- * timestamp: new Date().toISOString()
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
- * // Rate limiting with durable sleep
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 entity.append('auditLog', {
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,