@hotmeshio/hotmesh 0.10.0 → 0.10.1
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 +2 -2
- package/build/package.json +3 -4
- package/build/services/durable/index.d.ts +31 -2
- package/build/services/durable/index.js +31 -2
- package/build/services/durable/interceptor.d.ts +73 -0
- package/build/services/durable/interceptor.js +73 -0
- package/build/services/durable/workflow/proxyActivities.js +33 -29
- package/build/types/durable.d.ts +30 -12
- package/package.json +3 -4
- /package/{vitest.config.ts → vitest.config.mts} +0 -0
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ npm install @hotmeshio/hotmesh
|
|
|
11
11
|
## Use HotMesh for
|
|
12
12
|
|
|
13
13
|
- **Durable pipelines** — Orchestrate long-running, multi-step pipelines transactionally.
|
|
14
|
-
- **Temporal
|
|
14
|
+
- **Temporal alternative** — The `Durable` module provides a Temporal-compatible API (`Client`, `Worker`, `proxyActivities`, `sleepFor`, `startChild`, signals) that runs directly on Postgres. No app server required.
|
|
15
15
|
- **Distributed state machines** — Build stateful applications where every component can [fail and recover](https://github.com/hotmeshio/sdk-typescript/blob/main/services/collator/README.md).
|
|
16
16
|
- **AI and training pipelines** — Multi-step AI workloads where each stage is expensive and must not be repeated on failure. A crashed pipeline resumes from the last committed step, not from the beginning.
|
|
17
17
|
|
|
@@ -337,7 +337,7 @@ Durable is designed as a drop-in-compatible alternative for common Temporal patt
|
|
|
337
337
|
|
|
338
338
|
**What's the same:** `Client`, `Worker`, `proxyActivities`, `sleepFor`, `startChild`/`execChild`, signals (`waitFor`/`signal`), retry policies, and the overall workflow-as-code programming model.
|
|
339
339
|
|
|
340
|
-
**What's different:**
|
|
340
|
+
**What's different:** Postgres is the only infrastructure dependency — it stores state and coordinates workers.
|
|
341
341
|
|
|
342
342
|
## Running tests
|
|
343
343
|
|
package/build/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotmeshio/hotmesh",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "Permanent-Memory Workflows & AI Agents",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -110,13 +110,12 @@
|
|
|
110
110
|
"nats": "^2.28.0",
|
|
111
111
|
"openai": "^5.9.0",
|
|
112
112
|
"pg": "^8.10.0",
|
|
113
|
-
"rimraf": "^
|
|
113
|
+
"rimraf": "^6.1.3",
|
|
114
114
|
"terser": "^5.37.0",
|
|
115
115
|
"ts-node": "^10.9.1",
|
|
116
|
-
"ts-node-dev": "^2.0.0",
|
|
117
116
|
"typedoc": "^0.26.4",
|
|
118
117
|
"typescript": "^5.0.4",
|
|
119
|
-
"vitest": "^
|
|
118
|
+
"vitest": "^4.0.18"
|
|
120
119
|
},
|
|
121
120
|
"peerDependencies": {
|
|
122
121
|
"nats": "^2.0.0",
|
|
@@ -364,12 +364,41 @@ declare class DurableClass {
|
|
|
364
364
|
static didInterrupt: typeof didInterrupt;
|
|
365
365
|
private static interceptorService;
|
|
366
366
|
/**
|
|
367
|
-
* Register a workflow interceptor
|
|
367
|
+
* Register a workflow interceptor that wraps the entire workflow execution
|
|
368
|
+
* in an onion-like pattern. Interceptors execute in registration order
|
|
369
|
+
* (first registered is outermost) and can perform actions before and after
|
|
370
|
+
* workflow execution, handle errors, and add cross-cutting concerns like
|
|
371
|
+
* logging, metrics, or tracing.
|
|
372
|
+
*
|
|
373
|
+
* Workflow interceptors run inside the workflow's async local storage context,
|
|
374
|
+
* so all Durable workflow methods (`proxyActivities`, `sleepFor`, `waitFor`,
|
|
375
|
+
* `execChild`, etc.) are available. When using Durable functions, always check
|
|
376
|
+
* for interruptions with `Durable.didInterrupt(err)` and rethrow them.
|
|
377
|
+
*
|
|
368
378
|
* @param interceptor The interceptor to register
|
|
379
|
+
*
|
|
380
|
+
* @example
|
|
381
|
+
* ```typescript
|
|
382
|
+
* // Logging interceptor
|
|
383
|
+
* Durable.registerInterceptor({
|
|
384
|
+
* async execute(ctx, next) {
|
|
385
|
+
* console.log(`Workflow ${ctx.get('workflowName')} starting`);
|
|
386
|
+
* try {
|
|
387
|
+
* const result = await next();
|
|
388
|
+
* console.log(`Workflow ${ctx.get('workflowName')} completed`);
|
|
389
|
+
* return result;
|
|
390
|
+
* } catch (err) {
|
|
391
|
+
* if (Durable.didInterrupt(err)) throw err;
|
|
392
|
+
* console.error(`Workflow ${ctx.get('workflowName')} failed`);
|
|
393
|
+
* throw err;
|
|
394
|
+
* }
|
|
395
|
+
* }
|
|
396
|
+
* });
|
|
397
|
+
* ```
|
|
369
398
|
*/
|
|
370
399
|
static registerInterceptor(interceptor: WorkflowInterceptor): void;
|
|
371
400
|
/**
|
|
372
|
-
* Clear all registered interceptors (both workflow and activity)
|
|
401
|
+
* Clear all registered interceptors (both workflow and activity).
|
|
373
402
|
*/
|
|
374
403
|
static clearInterceptors(): void;
|
|
375
404
|
/**
|
|
@@ -280,14 +280,43 @@ class DurableClass {
|
|
|
280
280
|
*/
|
|
281
281
|
constructor() { }
|
|
282
282
|
/**
|
|
283
|
-
* Register a workflow interceptor
|
|
283
|
+
* Register a workflow interceptor that wraps the entire workflow execution
|
|
284
|
+
* in an onion-like pattern. Interceptors execute in registration order
|
|
285
|
+
* (first registered is outermost) and can perform actions before and after
|
|
286
|
+
* workflow execution, handle errors, and add cross-cutting concerns like
|
|
287
|
+
* logging, metrics, or tracing.
|
|
288
|
+
*
|
|
289
|
+
* Workflow interceptors run inside the workflow's async local storage context,
|
|
290
|
+
* so all Durable workflow methods (`proxyActivities`, `sleepFor`, `waitFor`,
|
|
291
|
+
* `execChild`, etc.) are available. When using Durable functions, always check
|
|
292
|
+
* for interruptions with `Durable.didInterrupt(err)` and rethrow them.
|
|
293
|
+
*
|
|
284
294
|
* @param interceptor The interceptor to register
|
|
295
|
+
*
|
|
296
|
+
* @example
|
|
297
|
+
* ```typescript
|
|
298
|
+
* // Logging interceptor
|
|
299
|
+
* Durable.registerInterceptor({
|
|
300
|
+
* async execute(ctx, next) {
|
|
301
|
+
* console.log(`Workflow ${ctx.get('workflowName')} starting`);
|
|
302
|
+
* try {
|
|
303
|
+
* const result = await next();
|
|
304
|
+
* console.log(`Workflow ${ctx.get('workflowName')} completed`);
|
|
305
|
+
* return result;
|
|
306
|
+
* } catch (err) {
|
|
307
|
+
* if (Durable.didInterrupt(err)) throw err;
|
|
308
|
+
* console.error(`Workflow ${ctx.get('workflowName')} failed`);
|
|
309
|
+
* throw err;
|
|
310
|
+
* }
|
|
311
|
+
* }
|
|
312
|
+
* });
|
|
313
|
+
* ```
|
|
285
314
|
*/
|
|
286
315
|
static registerInterceptor(interceptor) {
|
|
287
316
|
DurableClass.interceptorService.register(interceptor);
|
|
288
317
|
}
|
|
289
318
|
/**
|
|
290
|
-
* Clear all registered interceptors (both workflow and activity)
|
|
319
|
+
* Clear all registered interceptors (both workflow and activity).
|
|
291
320
|
*/
|
|
292
321
|
static clearInterceptors() {
|
|
293
322
|
DurableClass.interceptorService.clear();
|
|
@@ -181,6 +181,79 @@ import { WorkflowInterceptor, InterceptorRegistry, ActivityInterceptor, Activity
|
|
|
181
181
|
* }
|
|
182
182
|
* };
|
|
183
183
|
* ```
|
|
184
|
+
*
|
|
185
|
+
* ## Activity Interceptors
|
|
186
|
+
*
|
|
187
|
+
* Activity interceptors wrap individual proxied activity calls, supporting
|
|
188
|
+
* both **before** and **after** phases. The before phase receives the activity
|
|
189
|
+
* input (and can modify `activityCtx.args`). The after phase receives the
|
|
190
|
+
* activity output as the return value of `next()`.
|
|
191
|
+
*
|
|
192
|
+
* This enables patterns like publishing activity results to an external
|
|
193
|
+
* system (e.g., SNS, audit log) without modifying the workflow itself.
|
|
194
|
+
*
|
|
195
|
+
* **Important:** The after-phase proxy activity calls go through the same
|
|
196
|
+
* interceptor chain. Guard against recursion by checking `activityCtx.activityName`
|
|
197
|
+
* to skip the interceptor's own calls.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```typescript
|
|
201
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
202
|
+
* import type { ActivityInterceptor } from '@hotmeshio/hotmesh/types/durable';
|
|
203
|
+
* import * as activities from './activities';
|
|
204
|
+
*
|
|
205
|
+
* // Activity interceptor that publishes results via a proxy activity
|
|
206
|
+
* const publishResultInterceptor: ActivityInterceptor = {
|
|
207
|
+
* async execute(activityCtx, workflowCtx, next) {
|
|
208
|
+
* try {
|
|
209
|
+
* // BEFORE: inspect or modify the activity input
|
|
210
|
+
* console.log(`Calling ${activityCtx.activityName}`, activityCtx.args);
|
|
211
|
+
*
|
|
212
|
+
* // Execute the activity (returns stored result on replay)
|
|
213
|
+
* const result = await next();
|
|
214
|
+
*
|
|
215
|
+
* // AFTER: use the activity output (only runs on replay,
|
|
216
|
+
* // once the result is available)
|
|
217
|
+
*
|
|
218
|
+
* // Guard: skip for the interceptor's own proxy calls
|
|
219
|
+
* if (activityCtx.activityName !== 'publishToSNS') {
|
|
220
|
+
* const { publishToSNS } = Durable.workflow.proxyActivities<{
|
|
221
|
+
* publishToSNS: (topic: string, payload: any) => Promise<void>;
|
|
222
|
+
* }>({
|
|
223
|
+
* taskQueue: 'shared-notifications',
|
|
224
|
+
* retryPolicy: { maximumAttempts: 3, throwOnError: true },
|
|
225
|
+
* });
|
|
226
|
+
*
|
|
227
|
+
* await publishToSNS('activity-results', {
|
|
228
|
+
* workflowId: workflowCtx.get('workflowId'),
|
|
229
|
+
* activityName: activityCtx.activityName,
|
|
230
|
+
* input: activityCtx.args,
|
|
231
|
+
* output: result,
|
|
232
|
+
* });
|
|
233
|
+
* }
|
|
234
|
+
*
|
|
235
|
+
* return result;
|
|
236
|
+
* } catch (err) {
|
|
237
|
+
* if (Durable.didInterrupt(err)) throw err;
|
|
238
|
+
* throw err;
|
|
239
|
+
* }
|
|
240
|
+
* },
|
|
241
|
+
* };
|
|
242
|
+
*
|
|
243
|
+
* Durable.registerActivityInterceptor(publishResultInterceptor);
|
|
244
|
+
* ```
|
|
245
|
+
*
|
|
246
|
+
* ## Activity Interceptor Replay Pattern
|
|
247
|
+
*
|
|
248
|
+
* Activity interceptors participate in the interruption/replay cycle:
|
|
249
|
+
*
|
|
250
|
+
* 1. **First execution**: Before-phase runs → `next()` registers the activity
|
|
251
|
+
* interruption and throws `DurableProxyError` → workflow pauses
|
|
252
|
+
* 2. **Second execution**: Before-phase replays → `next()` returns the stored
|
|
253
|
+
* activity result → after-phase runs → after-phase proxy call (e.g.,
|
|
254
|
+
* `publishToSNS`) registers its own interruption → workflow pauses
|
|
255
|
+
* 3. **Third execution**: Everything replays → after-phase proxy call returns
|
|
256
|
+
* its stored result → interceptor returns → workflow continues
|
|
184
257
|
*/
|
|
185
258
|
export declare class InterceptorService implements InterceptorRegistry {
|
|
186
259
|
interceptors: WorkflowInterceptor[];
|
|
@@ -183,6 +183,79 @@ exports.InterceptorService = void 0;
|
|
|
183
183
|
* }
|
|
184
184
|
* };
|
|
185
185
|
* ```
|
|
186
|
+
*
|
|
187
|
+
* ## Activity Interceptors
|
|
188
|
+
*
|
|
189
|
+
* Activity interceptors wrap individual proxied activity calls, supporting
|
|
190
|
+
* both **before** and **after** phases. The before phase receives the activity
|
|
191
|
+
* input (and can modify `activityCtx.args`). The after phase receives the
|
|
192
|
+
* activity output as the return value of `next()`.
|
|
193
|
+
*
|
|
194
|
+
* This enables patterns like publishing activity results to an external
|
|
195
|
+
* system (e.g., SNS, audit log) without modifying the workflow itself.
|
|
196
|
+
*
|
|
197
|
+
* **Important:** The after-phase proxy activity calls go through the same
|
|
198
|
+
* interceptor chain. Guard against recursion by checking `activityCtx.activityName`
|
|
199
|
+
* to skip the interceptor's own calls.
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```typescript
|
|
203
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
204
|
+
* import type { ActivityInterceptor } from '@hotmeshio/hotmesh/types/durable';
|
|
205
|
+
* import * as activities from './activities';
|
|
206
|
+
*
|
|
207
|
+
* // Activity interceptor that publishes results via a proxy activity
|
|
208
|
+
* const publishResultInterceptor: ActivityInterceptor = {
|
|
209
|
+
* async execute(activityCtx, workflowCtx, next) {
|
|
210
|
+
* try {
|
|
211
|
+
* // BEFORE: inspect or modify the activity input
|
|
212
|
+
* console.log(`Calling ${activityCtx.activityName}`, activityCtx.args);
|
|
213
|
+
*
|
|
214
|
+
* // Execute the activity (returns stored result on replay)
|
|
215
|
+
* const result = await next();
|
|
216
|
+
*
|
|
217
|
+
* // AFTER: use the activity output (only runs on replay,
|
|
218
|
+
* // once the result is available)
|
|
219
|
+
*
|
|
220
|
+
* // Guard: skip for the interceptor's own proxy calls
|
|
221
|
+
* if (activityCtx.activityName !== 'publishToSNS') {
|
|
222
|
+
* const { publishToSNS } = Durable.workflow.proxyActivities<{
|
|
223
|
+
* publishToSNS: (topic: string, payload: any) => Promise<void>;
|
|
224
|
+
* }>({
|
|
225
|
+
* taskQueue: 'shared-notifications',
|
|
226
|
+
* retryPolicy: { maximumAttempts: 3, throwOnError: true },
|
|
227
|
+
* });
|
|
228
|
+
*
|
|
229
|
+
* await publishToSNS('activity-results', {
|
|
230
|
+
* workflowId: workflowCtx.get('workflowId'),
|
|
231
|
+
* activityName: activityCtx.activityName,
|
|
232
|
+
* input: activityCtx.args,
|
|
233
|
+
* output: result,
|
|
234
|
+
* });
|
|
235
|
+
* }
|
|
236
|
+
*
|
|
237
|
+
* return result;
|
|
238
|
+
* } catch (err) {
|
|
239
|
+
* if (Durable.didInterrupt(err)) throw err;
|
|
240
|
+
* throw err;
|
|
241
|
+
* }
|
|
242
|
+
* },
|
|
243
|
+
* };
|
|
244
|
+
*
|
|
245
|
+
* Durable.registerActivityInterceptor(publishResultInterceptor);
|
|
246
|
+
* ```
|
|
247
|
+
*
|
|
248
|
+
* ## Activity Interceptor Replay Pattern
|
|
249
|
+
*
|
|
250
|
+
* Activity interceptors participate in the interruption/replay cycle:
|
|
251
|
+
*
|
|
252
|
+
* 1. **First execution**: Before-phase runs → `next()` registers the activity
|
|
253
|
+
* interruption and throws `DurableProxyError` → workflow pauses
|
|
254
|
+
* 2. **Second execution**: Before-phase replays → `next()` returns the stored
|
|
255
|
+
* activity result → after-phase runs → after-phase proxy call (e.g.,
|
|
256
|
+
* `publishToSNS`) registers its own interruption → workflow pauses
|
|
257
|
+
* 3. **Third execution**: Everything replays → after-phase proxy call returns
|
|
258
|
+
* its stored result → interceptor returns → workflow continues
|
|
186
259
|
*/
|
|
187
260
|
class InterceptorService {
|
|
188
261
|
constructor() {
|
|
@@ -42,36 +42,40 @@ exports.getProxyInterruptPayload = getProxyInterruptPayload;
|
|
|
42
42
|
*/
|
|
43
43
|
function wrapActivity(activityName, options) {
|
|
44
44
|
return async function (...args) {
|
|
45
|
+
// Increment counter first for deterministic replay ordering
|
|
45
46
|
const [didRunAlready, execIndex, result] = await (0, didRun_1.didRun)('proxy');
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
47
|
+
const context = (0, context_1.getContext)();
|
|
48
|
+
const { interruptionRegistry } = context;
|
|
49
|
+
// Build activityCtx so interceptors can inspect/modify args
|
|
50
|
+
const activityCtx = { activityName, args, options };
|
|
51
|
+
// Core function: returns stored result on replay, or registers
|
|
52
|
+
// the interruption and throws on first execution. Reads args
|
|
53
|
+
// from activityCtx so "before" interceptors can modify them.
|
|
54
|
+
const coreFunction = async () => {
|
|
55
|
+
if (didRunAlready) {
|
|
56
|
+
if (result?.$error) {
|
|
57
|
+
if (options?.retryPolicy?.throwOnError !== false) {
|
|
58
|
+
const code = result.$error.code;
|
|
59
|
+
const message = result.$error.message;
|
|
60
|
+
const stack = result.$error.stack;
|
|
61
|
+
if (code === common_1.HMSH_CODE_DURABLE_FATAL) {
|
|
62
|
+
throw new common_1.DurableFatalError(message, stack);
|
|
63
|
+
}
|
|
64
|
+
else if (code === common_1.HMSH_CODE_DURABLE_MAXED) {
|
|
65
|
+
throw new common_1.DurableMaxedError(message, stack);
|
|
66
|
+
}
|
|
67
|
+
else if (code === common_1.HMSH_CODE_DURABLE_TIMEOUT) {
|
|
68
|
+
throw new common_1.DurableTimeoutError(message, stack);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
throw new common_1.DurableFatalError(message, stack);
|
|
72
|
+
}
|
|
64
73
|
}
|
|
74
|
+
return result.$error;
|
|
65
75
|
}
|
|
66
|
-
return result
|
|
76
|
+
return result.data;
|
|
67
77
|
}
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
const context = (0, context_1.getContext)();
|
|
71
|
-
const { interruptionRegistry } = context;
|
|
72
|
-
// Core activity registration logic
|
|
73
|
-
const executeActivity = async () => {
|
|
74
|
-
const interruptionMessage = getProxyInterruptPayload(context, activityName, execIndex, args, options);
|
|
78
|
+
const interruptionMessage = getProxyInterruptPayload(context, activityName, execIndex, activityCtx.args, options);
|
|
75
79
|
interruptionRegistry.push({
|
|
76
80
|
code: common_1.HMSH_CODE_DURABLE_PROXY,
|
|
77
81
|
type: 'DurableProxyError',
|
|
@@ -80,13 +84,13 @@ function wrapActivity(activityName, options) {
|
|
|
80
84
|
await (0, common_1.sleepImmediate)();
|
|
81
85
|
throw new common_1.DurableProxyError(interruptionMessage);
|
|
82
86
|
};
|
|
83
|
-
//
|
|
87
|
+
// Run through interceptor chain if interceptors exist
|
|
84
88
|
const store = common_1.asyncLocalStorage.getStore();
|
|
85
89
|
const interceptorService = store?.get('activityInterceptorService');
|
|
86
90
|
if (interceptorService?.activityInterceptors?.length > 0) {
|
|
87
|
-
return await interceptorService.executeActivityChain(
|
|
91
|
+
return await interceptorService.executeActivityChain(activityCtx, store, coreFunction);
|
|
88
92
|
}
|
|
89
|
-
return await
|
|
93
|
+
return await coreFunction();
|
|
90
94
|
};
|
|
91
95
|
}
|
|
92
96
|
exports.wrapActivity = wrapActivity;
|
package/build/types/durable.d.ts
CHANGED
|
@@ -596,15 +596,21 @@ export interface WorkflowInterceptor {
|
|
|
596
596
|
execute(ctx: Map<string, any>, next: () => Promise<any>): Promise<any>;
|
|
597
597
|
}
|
|
598
598
|
/**
|
|
599
|
-
* Registry for workflow interceptors that are executed in order
|
|
600
|
-
* for each workflow execution
|
|
599
|
+
* Registry for workflow and activity interceptors that are executed in order
|
|
600
|
+
* for each workflow or activity execution.
|
|
601
601
|
*/
|
|
602
602
|
export interface InterceptorRegistry {
|
|
603
603
|
/**
|
|
604
|
-
* Array of registered interceptors that will wrap workflow execution
|
|
605
|
-
* in the order they were registered (first registered = outermost wrapper)
|
|
604
|
+
* Array of registered workflow interceptors that will wrap workflow execution
|
|
605
|
+
* in the order they were registered (first registered = outermost wrapper).
|
|
606
606
|
*/
|
|
607
607
|
interceptors: WorkflowInterceptor[];
|
|
608
|
+
/**
|
|
609
|
+
* Array of registered activity interceptors that will wrap individual
|
|
610
|
+
* proxied activity calls in the order they were registered
|
|
611
|
+
* (first registered = outermost wrapper).
|
|
612
|
+
*/
|
|
613
|
+
activityInterceptors: ActivityInterceptor[];
|
|
608
614
|
}
|
|
609
615
|
/**
|
|
610
616
|
* Context provided to an activity interceptor, containing metadata
|
|
@@ -624,11 +630,21 @@ export interface ActivityInterceptorContext {
|
|
|
624
630
|
* workflow methods (proxyActivities, sleepFor, waitFor, execChild, etc.)
|
|
625
631
|
* are available.
|
|
626
632
|
*
|
|
627
|
-
* Activity interceptors
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
*
|
|
631
|
-
*
|
|
633
|
+
* Activity interceptors wrap proxied activity calls in an onion pattern,
|
|
634
|
+
* supporting both **before** and **after** phases:
|
|
635
|
+
*
|
|
636
|
+
* - **Before phase** (code before `await next()`): Runs before the activity
|
|
637
|
+
* executes. The interceptor can inspect or modify `activityCtx.args` to
|
|
638
|
+
* transform the activity input before it is sent.
|
|
639
|
+
*
|
|
640
|
+
* - **After phase** (code after `await next()`): Runs on replay once the
|
|
641
|
+
* activity result is available. The interceptor receives the activity
|
|
642
|
+
* output as the return value of `next()` and can inspect or transform it.
|
|
643
|
+
*
|
|
644
|
+
* On first execution, `next()` registers the activity with the interruption
|
|
645
|
+
* system and throws (the activity has not completed yet). On replay, `next()`
|
|
646
|
+
* returns the stored result and the after-phase code executes. This follows
|
|
647
|
+
* the same deterministic replay pattern as workflow interceptors.
|
|
632
648
|
*
|
|
633
649
|
* @example
|
|
634
650
|
* ```typescript
|
|
@@ -653,11 +669,13 @@ export interface ActivityInterceptorContext {
|
|
|
653
669
|
*/
|
|
654
670
|
export interface ActivityInterceptor {
|
|
655
671
|
/**
|
|
656
|
-
* Called
|
|
672
|
+
* Called around each proxied activity invocation. Code before `next()`
|
|
673
|
+
* runs in the before phase; code after `next()` runs in the after phase
|
|
674
|
+
* once the activity result is available on replay.
|
|
657
675
|
*
|
|
658
|
-
* @param activityCtx - Metadata about the activity being called
|
|
676
|
+
* @param activityCtx - Metadata about the activity being called (args may be modified)
|
|
659
677
|
* @param workflowCtx - The workflow context map (same as WorkflowInterceptor receives)
|
|
660
|
-
* @param next - Call to proceed to the next interceptor or the
|
|
678
|
+
* @param next - Call to proceed to the next interceptor or the core activity function
|
|
661
679
|
* @returns The activity result (from replay or after interruption/re-execution)
|
|
662
680
|
*/
|
|
663
681
|
execute(activityCtx: ActivityInterceptorContext, workflowCtx: Map<string, any>, next: () => Promise<any>): Promise<any>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotmeshio/hotmesh",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"description": "Permanent-Memory Workflows & AI Agents",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -110,13 +110,12 @@
|
|
|
110
110
|
"nats": "^2.28.0",
|
|
111
111
|
"openai": "^5.9.0",
|
|
112
112
|
"pg": "^8.10.0",
|
|
113
|
-
"rimraf": "^
|
|
113
|
+
"rimraf": "^6.1.3",
|
|
114
114
|
"terser": "^5.37.0",
|
|
115
115
|
"ts-node": "^10.9.1",
|
|
116
|
-
"ts-node-dev": "^2.0.0",
|
|
117
116
|
"typedoc": "^0.26.4",
|
|
118
117
|
"typescript": "^5.0.4",
|
|
119
|
-
"vitest": "^
|
|
118
|
+
"vitest": "^4.0.18"
|
|
120
119
|
},
|
|
121
120
|
"peerDependencies": {
|
|
122
121
|
"nats": "^2.0.0",
|
|
File without changes
|