@hotmeshio/hotmesh 0.9.0 → 0.10.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 +38 -23
- package/build/index.d.ts +12 -11
- package/build/index.js +15 -13
- package/build/modules/enums.d.ts +23 -34
- package/build/modules/enums.js +26 -38
- package/build/modules/errors.d.ts +16 -16
- package/build/modules/errors.js +37 -37
- package/build/package.json +23 -22
- package/build/services/activities/activity.js +1 -1
- package/build/services/dba/index.d.ts +171 -0
- package/build/services/dba/index.js +280 -0
- package/build/services/{memflow → durable}/client.d.ts +3 -3
- package/build/services/{memflow → durable}/client.js +13 -13
- package/build/services/{memflow → durable}/connection.d.ts +2 -2
- package/build/services/{memflow → durable}/connection.js +1 -1
- package/build/services/{memflow → durable}/exporter.d.ts +6 -6
- package/build/services/{memflow → durable}/exporter.js +2 -2
- package/build/services/{memflow → durable}/handle.d.ts +4 -4
- package/build/services/{memflow → durable}/handle.js +2 -2
- package/build/services/{memflow → durable}/index.d.ts +125 -33
- package/build/services/{memflow → durable}/index.js +145 -49
- package/build/services/{memflow → durable}/interceptor.d.ts +45 -22
- package/build/services/{memflow → durable}/interceptor.js +54 -21
- package/build/services/{memflow → durable}/schemas/factory.d.ts +4 -4
- package/build/services/{memflow → durable}/schemas/factory.js +5 -5
- package/build/services/{memflow → durable}/search.d.ts +1 -1
- package/build/services/{memflow → durable}/search.js +4 -4
- package/build/services/{memflow → durable}/worker.d.ts +11 -11
- package/build/services/{memflow → durable}/worker.js +61 -60
- package/build/services/{memflow → durable}/workflow/all.d.ts +5 -5
- package/build/services/{memflow → durable}/workflow/all.js +5 -5
- package/build/services/{memflow → durable}/workflow/common.d.ts +5 -5
- package/build/services/durable/workflow/common.js +47 -0
- package/build/services/{memflow → durable}/workflow/context.d.ts +5 -5
- package/build/services/{memflow → durable}/workflow/context.js +5 -5
- package/build/services/{memflow → durable}/workflow/emit.d.ts +5 -5
- package/build/services/{memflow → durable}/workflow/emit.js +5 -5
- package/build/services/{memflow → durable}/workflow/enrich.d.ts +4 -4
- package/build/services/{memflow → durable}/workflow/enrich.js +4 -4
- package/build/services/{memflow → durable}/workflow/entityMethods.d.ts +4 -4
- package/build/services/{memflow → durable}/workflow/entityMethods.js +4 -4
- package/build/services/{memflow → durable}/workflow/execChild.d.ts +9 -9
- package/build/services/{memflow → durable}/workflow/execChild.js +22 -22
- package/build/services/{memflow → durable}/workflow/execHook.d.ts +8 -8
- package/build/services/{memflow → durable}/workflow/execHook.js +10 -10
- package/build/services/{memflow → durable}/workflow/execHookBatch.d.ts +5 -5
- package/build/services/{memflow → durable}/workflow/execHookBatch.js +8 -8
- package/build/services/{memflow → durable}/workflow/hook.d.ts +5 -5
- package/build/services/{memflow → durable}/workflow/hook.js +11 -11
- package/build/services/{memflow → durable}/workflow/index.d.ts +6 -6
- package/build/services/{memflow → durable}/workflow/index.js +6 -6
- package/build/services/{memflow → durable}/workflow/interrupt.d.ts +7 -7
- package/build/services/{memflow → durable}/workflow/interrupt.js +7 -7
- package/build/services/{memflow → durable}/workflow/interruption.d.ts +10 -10
- package/build/services/{memflow → durable}/workflow/interruption.js +19 -19
- package/build/services/{memflow → durable}/workflow/proxyActivities.d.ts +7 -7
- package/build/services/{memflow → durable}/workflow/proxyActivities.js +31 -21
- package/build/services/{memflow → durable}/workflow/random.d.ts +4 -4
- package/build/services/{memflow → durable}/workflow/random.js +4 -4
- package/build/services/{memflow → durable}/workflow/searchMethods.d.ts +5 -5
- package/build/services/{memflow → durable}/workflow/searchMethods.js +5 -5
- package/build/services/{memflow → durable}/workflow/signal.d.ts +8 -8
- package/build/services/{memflow → durable}/workflow/signal.js +8 -8
- package/build/services/{memflow → durable}/workflow/sleepFor.d.ts +7 -7
- package/build/services/{memflow → durable}/workflow/sleepFor.js +10 -10
- package/build/services/{memflow → durable}/workflow/trace.d.ts +5 -5
- package/build/services/{memflow → durable}/workflow/trace.js +5 -5
- package/build/services/{memflow → durable}/workflow/waitFor.d.ts +9 -9
- package/build/services/{memflow → durable}/workflow/waitFor.js +12 -12
- package/build/services/hotmesh/index.d.ts +3 -3
- package/build/services/hotmesh/index.js +3 -3
- package/build/services/{meshcall → virtual}/index.d.ts +29 -29
- package/build/services/{meshcall → virtual}/index.js +49 -49
- package/build/services/{meshcall → virtual}/schemas/factory.d.ts +1 -1
- package/build/services/{meshcall → virtual}/schemas/factory.js +1 -1
- package/build/types/dba.d.ts +64 -0
- package/build/types/{memflow.d.ts → durable.d.ts} +74 -18
- package/build/types/error.d.ts +5 -5
- package/build/types/exporter.d.ts +1 -1
- package/build/types/index.d.ts +5 -4
- package/build/types/{meshcall.d.ts → virtual.d.ts} +15 -15
- package/build/types/virtual.js +2 -0
- package/index.ts +15 -13
- package/package.json +23 -22
- package/.claude/settings.local.json +0 -8
- package/build/services/memflow/workflow/common.js +0 -47
- package/build/vitest.config.d.ts +0 -2
- package/build/vitest.config.js +0 -18
- /package/build/services/{memflow → durable}/entity.d.ts +0 -0
- /package/build/services/{memflow → durable}/entity.js +0 -0
- /package/build/services/{memflow → durable}/workflow/didRun.d.ts +0 -0
- /package/build/services/{memflow → durable}/workflow/didRun.js +0 -0
- /package/build/services/{memflow → durable}/workflow/isSideEffectAllowed.d.ts +0 -0
- /package/build/services/{memflow → durable}/workflow/isSideEffectAllowed.js +0 -0
- /package/build/types/{memflow.js → dba.js} +0 -0
- /package/build/types/{meshcall.js → durable.js} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.Durable = void 0;
|
|
4
4
|
const hotmesh_1 = require("../hotmesh");
|
|
5
5
|
const utils_1 = require("../../modules/utils");
|
|
6
6
|
const client_1 = require("./client");
|
|
@@ -13,9 +13,9 @@ const handle_1 = require("./handle");
|
|
|
13
13
|
const interruption_1 = require("./workflow/interruption");
|
|
14
14
|
const interceptor_1 = require("./interceptor");
|
|
15
15
|
/**
|
|
16
|
-
* The
|
|
17
|
-
* Postgres. It offers
|
|
18
|
-
*
|
|
16
|
+
* The Durable service provides a Temporal-compatible workflow framework backed
|
|
17
|
+
* by Postgres. It offers entity-based memory management and composable,
|
|
18
|
+
* fault-tolerant workflows.
|
|
19
19
|
*
|
|
20
20
|
* ## Core Features
|
|
21
21
|
*
|
|
@@ -23,7 +23,7 @@ const interceptor_1 = require("./interceptor");
|
|
|
23
23
|
* Each workflow has a durable JSONB entity that serves as its memory:
|
|
24
24
|
* ```typescript
|
|
25
25
|
* export async function researchAgent(query: string) {
|
|
26
|
-
* const agent = await
|
|
26
|
+
* const agent = await Durable.workflow.entity();
|
|
27
27
|
*
|
|
28
28
|
* // Initialize entity state
|
|
29
29
|
* await agent.set({
|
|
@@ -42,14 +42,14 @@ const interceptor_1 = require("./interceptor");
|
|
|
42
42
|
* Spawn and coordinate multiple perspectives/phases:
|
|
43
43
|
* ```typescript
|
|
44
44
|
* // Launch parallel research perspectives
|
|
45
|
-
* await
|
|
45
|
+
* await Durable.workflow.execHook({
|
|
46
46
|
* taskQueue: 'research',
|
|
47
47
|
* workflowName: 'optimisticView',
|
|
48
48
|
* args: [query],
|
|
49
49
|
* signalId: 'optimistic-complete'
|
|
50
50
|
* });
|
|
51
51
|
*
|
|
52
|
-
* await
|
|
52
|
+
* await Durable.workflow.execHook({
|
|
53
53
|
* taskQueue: 'research',
|
|
54
54
|
* workflowName: 'skepticalView',
|
|
55
55
|
* args: [query],
|
|
@@ -58,8 +58,8 @@ const interceptor_1 = require("./interceptor");
|
|
|
58
58
|
*
|
|
59
59
|
* // Wait for both perspectives
|
|
60
60
|
* await Promise.all([
|
|
61
|
-
*
|
|
62
|
-
*
|
|
61
|
+
* Durable.workflow.waitFor('optimistic-complete'),
|
|
62
|
+
* Durable.workflow.waitFor('skeptical-complete')
|
|
63
63
|
* ]);
|
|
64
64
|
* ```
|
|
65
65
|
*
|
|
@@ -67,7 +67,7 @@ const interceptor_1 = require("./interceptor");
|
|
|
67
67
|
* Define and execute durable activities with automatic retry:
|
|
68
68
|
* ```typescript
|
|
69
69
|
* // Default: activities use workflow's task queue
|
|
70
|
-
* const activities =
|
|
70
|
+
* const activities = Durable.workflow.proxyActivities<{
|
|
71
71
|
* analyzeDocument: typeof analyzeDocument;
|
|
72
72
|
* validateFindings: typeof validateFindings;
|
|
73
73
|
* }>({
|
|
@@ -87,7 +87,7 @@ const interceptor_1 = require("./interceptor");
|
|
|
87
87
|
* Register activity workers explicitly before workflows start:
|
|
88
88
|
* ```typescript
|
|
89
89
|
* // Register shared activity pool for interceptors
|
|
90
|
-
* await
|
|
90
|
+
* await Durable.registerActivityWorker({
|
|
91
91
|
* connection: {
|
|
92
92
|
* class: Postgres,
|
|
93
93
|
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
@@ -96,7 +96,7 @@ const interceptor_1 = require("./interceptor");
|
|
|
96
96
|
* }, sharedActivities, 'shared-activities');
|
|
97
97
|
*
|
|
98
98
|
* // Register custom activity pool for specific use cases
|
|
99
|
-
* await
|
|
99
|
+
* await Durable.registerActivityWorker({
|
|
100
100
|
* connection: {
|
|
101
101
|
* class: Postgres,
|
|
102
102
|
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
@@ -109,7 +109,7 @@ const interceptor_1 = require("./interceptor");
|
|
|
109
109
|
* Build complex workflows through composition:
|
|
110
110
|
* ```typescript
|
|
111
111
|
* // Start a child workflow
|
|
112
|
-
* const childResult = await
|
|
112
|
+
* const childResult = await Durable.workflow.execChild({
|
|
113
113
|
* taskQueue: 'analysis',
|
|
114
114
|
* workflowName: 'detailedAnalysis',
|
|
115
115
|
* args: [data],
|
|
@@ -121,7 +121,7 @@ const interceptor_1 = require("./interceptor");
|
|
|
121
121
|
* });
|
|
122
122
|
*
|
|
123
123
|
* // Fire-and-forget child workflow
|
|
124
|
-
* await
|
|
124
|
+
* await Durable.workflow.startChild({
|
|
125
125
|
* taskQueue: 'notifications',
|
|
126
126
|
* workflowName: 'sendUpdates',
|
|
127
127
|
* args: [updates]
|
|
@@ -132,7 +132,7 @@ const interceptor_1 = require("./interceptor");
|
|
|
132
132
|
* Add cross-cutting concerns through interceptors that run as durable functions:
|
|
133
133
|
* ```typescript
|
|
134
134
|
* // First register shared activity worker for interceptors
|
|
135
|
-
* await
|
|
135
|
+
* await Durable.registerActivityWorker({
|
|
136
136
|
* connection: {
|
|
137
137
|
* class: Postgres,
|
|
138
138
|
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
@@ -141,11 +141,11 @@ const interceptor_1 = require("./interceptor");
|
|
|
141
141
|
* }, { auditLog }, 'interceptor-activities');
|
|
142
142
|
*
|
|
143
143
|
* // Add audit interceptor that uses activities with explicit taskQueue
|
|
144
|
-
*
|
|
144
|
+
* Durable.registerInterceptor({
|
|
145
145
|
* async execute(ctx, next) {
|
|
146
146
|
* try {
|
|
147
147
|
* // Interceptors use explicit taskQueue to prevent per-workflow queues
|
|
148
|
-
* const { auditLog } =
|
|
148
|
+
* const { auditLog } = Durable.workflow.proxyActivities<typeof activities>({
|
|
149
149
|
* activities: { auditLog },
|
|
150
150
|
* taskQueue: 'interceptor-activities', // Explicit shared queue
|
|
151
151
|
* retryPolicy: { maximumAttempts: 3 }
|
|
@@ -160,7 +160,7 @@ const interceptor_1 = require("./interceptor");
|
|
|
160
160
|
* return result;
|
|
161
161
|
* } catch (err) {
|
|
162
162
|
* // CRITICAL: Always check for HotMesh interruptions
|
|
163
|
-
* if (
|
|
163
|
+
* if (Durable.didInterrupt(err)) {
|
|
164
164
|
* throw err; // Rethrow for replay system
|
|
165
165
|
* }
|
|
166
166
|
* throw err;
|
|
@@ -169,15 +169,71 @@ const interceptor_1 = require("./interceptor");
|
|
|
169
169
|
* });
|
|
170
170
|
* ```
|
|
171
171
|
*
|
|
172
|
+
* ### 7. Activity Interceptors
|
|
173
|
+
* Wrap individual proxied activity calls with cross-cutting logic.
|
|
174
|
+
* Unlike workflow interceptors (which wrap the entire workflow), activity
|
|
175
|
+
* interceptors execute around each `proxyActivities` call. They run inside
|
|
176
|
+
* the workflow's execution context and have access to all Durable workflow
|
|
177
|
+
* methods (`proxyActivities`, `sleepFor`, `waitFor`, `execChild`, etc.).
|
|
178
|
+
* Multiple activity interceptors execute in onion order (first registered
|
|
179
|
+
* is outermost).
|
|
180
|
+
* ```typescript
|
|
181
|
+
* // Simple logging interceptor
|
|
182
|
+
* Durable.registerActivityInterceptor({
|
|
183
|
+
* async execute(activityCtx, workflowCtx, next) {
|
|
184
|
+
* console.log(`Activity ${activityCtx.activityName} starting`);
|
|
185
|
+
* try {
|
|
186
|
+
* const result = await next();
|
|
187
|
+
* console.log(`Activity ${activityCtx.activityName} completed`);
|
|
188
|
+
* return result;
|
|
189
|
+
* } catch (err) {
|
|
190
|
+
* if (Durable.didInterrupt(err)) throw err;
|
|
191
|
+
* console.error(`Activity ${activityCtx.activityName} failed`);
|
|
192
|
+
* throw err;
|
|
193
|
+
* }
|
|
194
|
+
* }
|
|
195
|
+
* });
|
|
196
|
+
*
|
|
197
|
+
* // Interceptor that calls its own proxy activities
|
|
198
|
+
* await Durable.registerActivityWorker({
|
|
199
|
+
* connection: {
|
|
200
|
+
* class: Postgres,
|
|
201
|
+
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
202
|
+
* },
|
|
203
|
+
* taskQueue: 'audit-activities'
|
|
204
|
+
* }, { auditLog }, 'audit-activities');
|
|
205
|
+
*
|
|
206
|
+
* Durable.registerActivityInterceptor({
|
|
207
|
+
* async execute(activityCtx, workflowCtx, next) {
|
|
208
|
+
* try {
|
|
209
|
+
* const { auditLog } = Durable.workflow.proxyActivities<{
|
|
210
|
+
* auditLog: (id: string, action: string) => Promise<void>;
|
|
211
|
+
* }>({
|
|
212
|
+
* taskQueue: 'audit-activities',
|
|
213
|
+
* retryPolicy: { maximumAttempts: 3 }
|
|
214
|
+
* });
|
|
215
|
+
*
|
|
216
|
+
* await auditLog(workflowCtx.get('workflowId'), `before:${activityCtx.activityName}`);
|
|
217
|
+
* const result = await next();
|
|
218
|
+
* await auditLog(workflowCtx.get('workflowId'), `after:${activityCtx.activityName}`);
|
|
219
|
+
* return result;
|
|
220
|
+
* } catch (err) {
|
|
221
|
+
* if (Durable.didInterrupt(err)) throw err;
|
|
222
|
+
* throw err;
|
|
223
|
+
* }
|
|
224
|
+
* }
|
|
225
|
+
* });
|
|
226
|
+
* ```
|
|
227
|
+
*
|
|
172
228
|
* ## Basic Usage Example
|
|
173
229
|
*
|
|
174
230
|
* ```typescript
|
|
175
|
-
* import { Client, Worker,
|
|
231
|
+
* import { Client, Worker, Durable } from '@hotmeshio/hotmesh';
|
|
176
232
|
* import { Client as Postgres } from 'pg';
|
|
177
233
|
* import * as activities from './activities';
|
|
178
234
|
*
|
|
179
235
|
* // (Optional) Register shared activity workers for interceptors
|
|
180
|
-
* await
|
|
236
|
+
* await Durable.registerActivityWorker({
|
|
181
237
|
* connection: {
|
|
182
238
|
* class: Postgres,
|
|
183
239
|
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
@@ -208,17 +264,17 @@ const interceptor_1 = require("./interceptor");
|
|
|
208
264
|
* args: ['input data'],
|
|
209
265
|
* taskQueue: 'default',
|
|
210
266
|
* workflowName: 'example',
|
|
211
|
-
* workflowId:
|
|
267
|
+
* workflowId: Durable.guid()
|
|
212
268
|
* });
|
|
213
269
|
*
|
|
214
270
|
* // Get result
|
|
215
271
|
* const result = await handle.result();
|
|
216
272
|
*
|
|
217
273
|
* // Cleanup
|
|
218
|
-
* await
|
|
274
|
+
* await Durable.shutdown();
|
|
219
275
|
* ```
|
|
220
276
|
*/
|
|
221
|
-
class
|
|
277
|
+
class DurableClass {
|
|
222
278
|
/**
|
|
223
279
|
* @private
|
|
224
280
|
*/
|
|
@@ -228,62 +284,102 @@ class MemFlowClass {
|
|
|
228
284
|
* @param interceptor The interceptor to register
|
|
229
285
|
*/
|
|
230
286
|
static registerInterceptor(interceptor) {
|
|
231
|
-
|
|
287
|
+
DurableClass.interceptorService.register(interceptor);
|
|
232
288
|
}
|
|
233
289
|
/**
|
|
234
|
-
* Clear all registered workflow
|
|
290
|
+
* Clear all registered interceptors (both workflow and activity)
|
|
235
291
|
*/
|
|
236
292
|
static clearInterceptors() {
|
|
237
|
-
|
|
293
|
+
DurableClass.interceptorService.clear();
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Register an activity interceptor that wraps individual proxied
|
|
297
|
+
* activity calls within workflows. Interceptors execute in registration
|
|
298
|
+
* order (first registered is outermost) using the onion pattern.
|
|
299
|
+
*
|
|
300
|
+
* Activity interceptors run inside the workflow's execution context
|
|
301
|
+
* and have access to all Durable workflow methods (`proxyActivities`,
|
|
302
|
+
* `sleepFor`, `waitFor`, `execChild`, etc.). The `activityCtx` parameter
|
|
303
|
+
* provides `activityName`, `args`, and `options` for the call being
|
|
304
|
+
* intercepted. The `workflowCtx` map provides workflow metadata
|
|
305
|
+
* (`workflowId`, `workflowName`, `namespace`, etc.).
|
|
306
|
+
*
|
|
307
|
+
* @param interceptor The activity interceptor to register
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* ```typescript
|
|
311
|
+
* Durable.registerActivityInterceptor({
|
|
312
|
+
* async execute(activityCtx, workflowCtx, next) {
|
|
313
|
+
* const start = Date.now();
|
|
314
|
+
* try {
|
|
315
|
+
* const result = await next();
|
|
316
|
+
* console.log(`${activityCtx.activityName} took ${Date.now() - start}ms`);
|
|
317
|
+
* return result;
|
|
318
|
+
* } catch (err) {
|
|
319
|
+
* if (Durable.didInterrupt(err)) throw err;
|
|
320
|
+
* throw err;
|
|
321
|
+
* }
|
|
322
|
+
* }
|
|
323
|
+
* });
|
|
324
|
+
* ```
|
|
325
|
+
*/
|
|
326
|
+
static registerActivityInterceptor(interceptor) {
|
|
327
|
+
DurableClass.interceptorService.registerActivity(interceptor);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Clear all registered activity interceptors
|
|
331
|
+
*/
|
|
332
|
+
static clearActivityInterceptors() {
|
|
333
|
+
DurableClass.interceptorService.clearActivity();
|
|
238
334
|
}
|
|
239
335
|
/**
|
|
240
336
|
* Get the interceptor service instance
|
|
241
337
|
* @internal
|
|
242
338
|
*/
|
|
243
339
|
static getInterceptorService() {
|
|
244
|
-
return
|
|
340
|
+
return DurableClass.interceptorService;
|
|
245
341
|
}
|
|
246
342
|
/**
|
|
247
343
|
* Shutdown everything. All connections, workers, and clients will be closed.
|
|
248
344
|
* Include in your signal handlers to ensure a clean shutdown.
|
|
249
345
|
*/
|
|
250
346
|
static async shutdown() {
|
|
251
|
-
await
|
|
252
|
-
await
|
|
347
|
+
await DurableClass.Client.shutdown();
|
|
348
|
+
await DurableClass.Worker.shutdown();
|
|
253
349
|
await hotmesh_1.HotMesh.stop();
|
|
254
350
|
}
|
|
255
351
|
}
|
|
256
|
-
exports.
|
|
352
|
+
exports.Durable = DurableClass;
|
|
257
353
|
/**
|
|
258
|
-
* The
|
|
354
|
+
* The Durable `Client` service is functionally
|
|
259
355
|
* equivalent to the Temporal `Client` service.
|
|
260
356
|
*/
|
|
261
|
-
|
|
357
|
+
DurableClass.Client = client_1.ClientService;
|
|
262
358
|
/**
|
|
263
|
-
* The
|
|
359
|
+
* The Durable `Connection` service is functionally
|
|
264
360
|
* equivalent to the Temporal `Connection` service.
|
|
265
361
|
*/
|
|
266
|
-
|
|
362
|
+
DurableClass.Connection = connection_1.ConnectionService;
|
|
267
363
|
/**
|
|
268
364
|
* @private
|
|
269
365
|
*/
|
|
270
|
-
|
|
366
|
+
DurableClass.Search = search_1.Search;
|
|
271
367
|
/**
|
|
272
368
|
* @private
|
|
273
369
|
*/
|
|
274
|
-
|
|
370
|
+
DurableClass.Entity = entity_1.Entity;
|
|
275
371
|
/**
|
|
276
372
|
* The Handle provides methods to interact with a running
|
|
277
373
|
* workflow. This includes exporting the workflow, sending signals, and
|
|
278
374
|
* querying the state of the workflow. An instance of the Handle service
|
|
279
|
-
* is typically accessed via the
|
|
375
|
+
* is typically accessed via the Durable.Client class (workflow.getHandle).
|
|
280
376
|
*/
|
|
281
|
-
|
|
377
|
+
DurableClass.Handle = handle_1.WorkflowHandleService;
|
|
282
378
|
/**
|
|
283
|
-
* The
|
|
379
|
+
* The Durable `Worker` service is functionally
|
|
284
380
|
* equivalent to the Temporal `Worker` service.
|
|
285
381
|
*/
|
|
286
|
-
|
|
382
|
+
DurableClass.Worker = worker_1.WorkerService;
|
|
287
383
|
/**
|
|
288
384
|
* Register activity workers for a task queue. Activities execute via message queue
|
|
289
385
|
* and can run on different servers from workflows.
|
|
@@ -296,7 +392,7 @@ MemFlowClass.Worker = worker_1.WorkerService;
|
|
|
296
392
|
* async sendEmail(to: string, msg: string) { /* ... *\/ }
|
|
297
393
|
* };
|
|
298
394
|
*
|
|
299
|
-
* await
|
|
395
|
+
* await Durable.registerActivityWorker({
|
|
300
396
|
* connection: {
|
|
301
397
|
* class: Postgres,
|
|
302
398
|
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' }
|
|
@@ -306,7 +402,7 @@ MemFlowClass.Worker = worker_1.WorkerService;
|
|
|
306
402
|
*
|
|
307
403
|
* // Workflow worker (can be on different server)
|
|
308
404
|
* async function orderWorkflow(amount: number) {
|
|
309
|
-
* const { processPayment, sendEmail } =
|
|
405
|
+
* const { processPayment, sendEmail } = Durable.workflow.proxyActivities<{
|
|
310
406
|
* processPayment: (amount: number) => Promise<string>;
|
|
311
407
|
* sendEmail: (to: string, msg: string) => Promise<void>;
|
|
312
408
|
* }>({
|
|
@@ -319,30 +415,30 @@ MemFlowClass.Worker = worker_1.WorkerService;
|
|
|
319
415
|
* return result;
|
|
320
416
|
* }
|
|
321
417
|
*
|
|
322
|
-
* await
|
|
418
|
+
* await Durable.Worker.create({
|
|
323
419
|
* connection: { class: Postgres, options: { connectionString: '...' } },
|
|
324
420
|
* taskQueue: 'orders',
|
|
325
421
|
* workflow: orderWorkflow
|
|
326
422
|
* });
|
|
327
423
|
* ```
|
|
328
424
|
*/
|
|
329
|
-
|
|
425
|
+
DurableClass.registerActivityWorker = worker_1.WorkerService.registerActivityWorker;
|
|
330
426
|
/**
|
|
331
|
-
* The
|
|
427
|
+
* The Durable `workflow` service is functionally
|
|
332
428
|
* equivalent to the Temporal `Workflow` service
|
|
333
429
|
* with additional methods for managing workflows,
|
|
334
430
|
* including: `execChild`, `waitFor`, `sleep`, etc
|
|
335
431
|
*/
|
|
336
|
-
|
|
432
|
+
DurableClass.workflow = workflow_1.WorkflowService;
|
|
337
433
|
/**
|
|
338
434
|
* Checks if an error is a HotMesh reserved error type that indicates
|
|
339
435
|
* a workflow interruption rather than a true error condition.
|
|
340
436
|
*
|
|
341
437
|
* @see {@link didInterrupt} for detailed documentation
|
|
342
438
|
*/
|
|
343
|
-
|
|
344
|
-
|
|
439
|
+
DurableClass.didInterrupt = interruption_1.didInterrupt;
|
|
440
|
+
DurableClass.interceptorService = new interceptor_1.InterceptorService();
|
|
345
441
|
/**
|
|
346
442
|
* Generate a unique identifier for workflow IDs
|
|
347
443
|
*/
|
|
348
|
-
|
|
444
|
+
DurableClass.guid = utils_1.guid;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WorkflowInterceptor, InterceptorRegistry } from '../../types/
|
|
1
|
+
import { WorkflowInterceptor, InterceptorRegistry, ActivityInterceptor, ActivityInterceptorContext } from '../../types/durable';
|
|
2
2
|
/**
|
|
3
3
|
* Service for managing workflow interceptors that wrap workflow execution
|
|
4
4
|
* in an onion-like pattern. Each interceptor can perform actions before
|
|
@@ -49,32 +49,32 @@ import { WorkflowInterceptor, InterceptorRegistry } from '../../types/memflow';
|
|
|
49
49
|
* });
|
|
50
50
|
* ```
|
|
51
51
|
*
|
|
52
|
-
* ## Durable Interceptors with
|
|
52
|
+
* ## Durable Interceptors with Durable Functions
|
|
53
53
|
*
|
|
54
54
|
* Interceptors run within the workflow's async local storage context, which means
|
|
55
|
-
* they can use
|
|
55
|
+
* they can use Durable functions like `sleepFor`, `entity`, `proxyActivities`, etc.
|
|
56
56
|
* These interceptors participate in the HotMesh interruption/replay pattern.
|
|
57
57
|
*
|
|
58
58
|
* @example
|
|
59
59
|
* ```typescript
|
|
60
|
-
* import {
|
|
60
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
61
61
|
*
|
|
62
62
|
* // Rate limiting interceptor that sleeps before execution
|
|
63
63
|
* const rateLimitInterceptor: WorkflowInterceptor = {
|
|
64
64
|
* async execute(ctx, next) {
|
|
65
65
|
* try {
|
|
66
66
|
* // This sleep will cause an interruption on first execution
|
|
67
|
-
* await
|
|
67
|
+
* await Durable.workflow.sleepFor('1 second');
|
|
68
68
|
*
|
|
69
69
|
* const result = await next();
|
|
70
70
|
*
|
|
71
71
|
* // Another sleep after workflow completes
|
|
72
|
-
* await
|
|
72
|
+
* await Durable.workflow.sleepFor('500 milliseconds');
|
|
73
73
|
*
|
|
74
74
|
* return result;
|
|
75
75
|
* } catch (err) {
|
|
76
76
|
* // CRITICAL: Always check for HotMesh interruptions
|
|
77
|
-
* if (
|
|
77
|
+
* if (Durable.didInterrupt(err)) {
|
|
78
78
|
* throw err; // Rethrow interruptions for replay system
|
|
79
79
|
* }
|
|
80
80
|
* // Handle actual errors
|
|
@@ -88,7 +88,7 @@ import { WorkflowInterceptor, InterceptorRegistry } from '../../types/memflow';
|
|
|
88
88
|
* const auditInterceptor: WorkflowInterceptor = {
|
|
89
89
|
* async execute(ctx, next) {
|
|
90
90
|
* try {
|
|
91
|
-
* const entity = await
|
|
91
|
+
* const entity = await Durable.workflow.entity();
|
|
92
92
|
* await entity.append('auditLog', {
|
|
93
93
|
* action: 'workflow_started',
|
|
94
94
|
* timestamp: new Date().toISOString(),
|
|
@@ -108,12 +108,12 @@ import { WorkflowInterceptor, InterceptorRegistry } from '../../types/memflow';
|
|
|
108
108
|
*
|
|
109
109
|
* return result;
|
|
110
110
|
* } catch (err) {
|
|
111
|
-
* if (
|
|
111
|
+
* if (Durable.didInterrupt(err)) {
|
|
112
112
|
* throw err;
|
|
113
113
|
* }
|
|
114
114
|
*
|
|
115
115
|
* // Log failure to entity
|
|
116
|
-
* const entity = await
|
|
116
|
+
* const entity = await Durable.workflow.entity();
|
|
117
117
|
* await entity.append('auditLog', {
|
|
118
118
|
* action: 'workflow_failed',
|
|
119
119
|
* timestamp: new Date().toISOString(),
|
|
@@ -126,35 +126,35 @@ import { WorkflowInterceptor, InterceptorRegistry } from '../../types/memflow';
|
|
|
126
126
|
* };
|
|
127
127
|
*
|
|
128
128
|
* // Register interceptors
|
|
129
|
-
*
|
|
130
|
-
*
|
|
129
|
+
* Durable.registerInterceptor(rateLimitInterceptor);
|
|
130
|
+
* Durable.registerInterceptor(auditInterceptor);
|
|
131
131
|
* ```
|
|
132
132
|
*
|
|
133
133
|
* ## Execution Pattern with Interruptions
|
|
134
134
|
*
|
|
135
|
-
* When interceptors use
|
|
135
|
+
* When interceptors use Durable functions, the workflow will execute multiple times
|
|
136
136
|
* due to the interruption/replay pattern:
|
|
137
137
|
*
|
|
138
|
-
* 1. **First execution**: Interceptor calls `sleepFor` → throws `
|
|
139
|
-
* 2. **Second execution**: Interceptor sleep replays (skipped), workflow runs → proxy activity throws `
|
|
140
|
-
* 3. **Third execution**: All previous operations replay, interceptor sleep after workflow → throws `
|
|
138
|
+
* 1. **First execution**: Interceptor calls `sleepFor` → throws `DurableSleepError` → workflow pauses
|
|
139
|
+
* 2. **Second execution**: Interceptor sleep replays (skipped), workflow runs → proxy activity throws `DurableProxyError` → workflow pauses
|
|
140
|
+
* 3. **Third execution**: All previous operations replay, interceptor sleep after workflow → throws `DurableSleepError` → workflow pauses
|
|
141
141
|
* 4. **Fourth execution**: Everything replays successfully, workflow completes
|
|
142
142
|
*
|
|
143
143
|
* This pattern ensures deterministic, durable execution across all interceptors and workflow code.
|
|
144
144
|
*
|
|
145
145
|
* @example
|
|
146
146
|
* ```typescript
|
|
147
|
-
* // Interceptor with complex
|
|
147
|
+
* // Interceptor with complex Durable operations
|
|
148
148
|
* const complexInterceptor: WorkflowInterceptor = {
|
|
149
149
|
* async execute(ctx, next) {
|
|
150
150
|
* try {
|
|
151
151
|
* // Get persistent state
|
|
152
|
-
* const entity = await
|
|
152
|
+
* const entity = await Durable.workflow.entity();
|
|
153
153
|
* const state = await entity.get() as any;
|
|
154
154
|
*
|
|
155
155
|
* // Conditional durable operations
|
|
156
156
|
* if (!state.preProcessed) {
|
|
157
|
-
* await
|
|
157
|
+
* await Durable.workflow.sleepFor('100 milliseconds');
|
|
158
158
|
* await entity.merge({ preProcessed: true });
|
|
159
159
|
* }
|
|
160
160
|
*
|
|
@@ -163,7 +163,7 @@ import { WorkflowInterceptor, InterceptorRegistry } from '../../types/memflow';
|
|
|
163
163
|
*
|
|
164
164
|
* // Post-processing with child workflow
|
|
165
165
|
* if (!state.postProcessed) {
|
|
166
|
-
* await
|
|
166
|
+
* await Durable.workflow.execChild({
|
|
167
167
|
* taskQueue: 'cleanup',
|
|
168
168
|
* workflowName: 'cleanupWorkflow',
|
|
169
169
|
* args: [result]
|
|
@@ -173,7 +173,7 @@ import { WorkflowInterceptor, InterceptorRegistry } from '../../types/memflow';
|
|
|
173
173
|
*
|
|
174
174
|
* return result;
|
|
175
175
|
* } catch (err) {
|
|
176
|
-
* if (
|
|
176
|
+
* if (Durable.didInterrupt(err)) {
|
|
177
177
|
* throw err;
|
|
178
178
|
* }
|
|
179
179
|
* throw err;
|
|
@@ -184,6 +184,7 @@ import { WorkflowInterceptor, InterceptorRegistry } from '../../types/memflow';
|
|
|
184
184
|
*/
|
|
185
185
|
export declare class InterceptorService implements InterceptorRegistry {
|
|
186
186
|
interceptors: WorkflowInterceptor[];
|
|
187
|
+
activityInterceptors: ActivityInterceptor[];
|
|
187
188
|
/**
|
|
188
189
|
* Register a new workflow interceptor that will wrap workflow execution.
|
|
189
190
|
* Interceptors are executed in the order they are registered, with the
|
|
@@ -230,7 +231,29 @@ export declare class InterceptorService implements InterceptorRegistry {
|
|
|
230
231
|
*/
|
|
231
232
|
executeChain(ctx: Map<string, any>, fn: () => Promise<any>): Promise<any>;
|
|
232
233
|
/**
|
|
233
|
-
*
|
|
234
|
+
* Register a new activity interceptor that will wrap individual
|
|
235
|
+
* proxied activity calls. Interceptors are executed in the order
|
|
236
|
+
* they are registered, with the first registered being the outermost wrapper.
|
|
237
|
+
*
|
|
238
|
+
* @param interceptor The activity interceptor to register
|
|
239
|
+
*/
|
|
240
|
+
registerActivity(interceptor: ActivityInterceptor): void;
|
|
241
|
+
/**
|
|
242
|
+
* Execute the activity interceptor chain around an activity invocation.
|
|
243
|
+
* Uses the same onion/reduceRight pattern as executeChain.
|
|
244
|
+
*
|
|
245
|
+
* @param activityCtx - Metadata about the activity (name, args, options)
|
|
246
|
+
* @param workflowCtx - The workflow context Map
|
|
247
|
+
* @param fn - The core activity function (registers with interruptionRegistry, sleeps, throws)
|
|
248
|
+
* @returns The result of the activity (from replay or after future execution)
|
|
249
|
+
*/
|
|
250
|
+
executeActivityChain(activityCtx: ActivityInterceptorContext, workflowCtx: Map<string, any>, fn: () => Promise<any>): Promise<any>;
|
|
251
|
+
/**
|
|
252
|
+
* Clear all registered activity interceptors.
|
|
253
|
+
*/
|
|
254
|
+
clearActivity(): void;
|
|
255
|
+
/**
|
|
256
|
+
* Clear all registered interceptors (both workflow and activity).
|
|
234
257
|
*
|
|
235
258
|
* @example
|
|
236
259
|
* ```typescript
|