@hotmeshio/hotmesh 0.7.0 → 0.9.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/.claude/settings.local.json +8 -0
- package/README.md +158 -38
- package/build/index.d.ts +1 -3
- package/build/index.js +1 -5
- package/build/modules/utils.js +3 -31
- package/build/package.json +63 -79
- package/build/services/activities/activity.d.ts +97 -9
- package/build/services/activities/activity.js +323 -86
- package/build/services/activities/await.d.ts +101 -0
- package/build/services/activities/await.js +103 -2
- package/build/services/activities/cycle.d.ts +82 -0
- package/build/services/activities/cycle.js +86 -8
- package/build/services/activities/hook.d.ts +144 -1
- package/build/services/activities/hook.js +162 -21
- package/build/services/activities/interrupt.d.ts +112 -0
- package/build/services/activities/interrupt.js +134 -29
- package/build/services/activities/signal.d.ts +111 -4
- package/build/services/activities/signal.js +136 -28
- package/build/services/activities/trigger.d.ts +56 -4
- package/build/services/activities/trigger.js +119 -35
- package/build/services/activities/worker.d.ts +107 -0
- package/build/services/activities/worker.js +109 -2
- package/build/services/collator/index.d.ts +116 -30
- package/build/services/collator/index.js +211 -115
- package/build/services/connector/factory.d.ts +1 -1
- package/build/services/connector/factory.js +1 -11
- package/build/services/engine/index.d.ts +22 -6
- package/build/services/engine/index.js +49 -18
- package/build/services/exporter/index.d.ts +2 -0
- package/build/services/exporter/index.js +1 -0
- package/build/services/hotmesh/index.d.ts +471 -236
- package/build/services/hotmesh/index.js +473 -238
- package/build/services/memflow/client.js +2 -2
- package/build/services/memflow/handle.js +1 -1
- package/build/services/memflow/index.d.ts +1 -1
- package/build/services/memflow/index.js +1 -1
- package/build/services/memflow/workflow/all.d.ts +28 -3
- package/build/services/memflow/workflow/all.js +28 -3
- package/build/services/memflow/workflow/context.d.ts +44 -1
- package/build/services/memflow/workflow/context.js +44 -1
- package/build/services/memflow/workflow/didRun.d.ts +23 -3
- package/build/services/memflow/workflow/didRun.js +23 -3
- package/build/services/memflow/workflow/emit.d.ts +43 -4
- package/build/services/memflow/workflow/emit.js +43 -4
- package/build/services/memflow/workflow/enrich.d.ts +32 -4
- package/build/services/memflow/workflow/enrich.js +32 -4
- package/build/services/memflow/workflow/entityMethods.d.ts +54 -7
- package/build/services/memflow/workflow/entityMethods.js +54 -7
- package/build/services/memflow/workflow/execChild.d.ts +96 -8
- package/build/services/memflow/workflow/execChild.js +96 -8
- package/build/services/memflow/workflow/execHook.d.ts +54 -39
- package/build/services/memflow/workflow/execHook.js +52 -38
- package/build/services/memflow/workflow/execHookBatch.d.ts +82 -29
- package/build/services/memflow/workflow/execHookBatch.js +80 -28
- package/build/services/memflow/workflow/hook.d.ts +68 -3
- package/build/services/memflow/workflow/hook.js +69 -4
- package/build/services/memflow/workflow/index.d.ts +65 -10
- package/build/services/memflow/workflow/index.js +65 -10
- package/build/services/memflow/workflow/interrupt.d.ts +50 -4
- package/build/services/memflow/workflow/interrupt.js +50 -4
- package/build/services/memflow/workflow/interruption.d.ts +49 -16
- package/build/services/memflow/workflow/interruption.js +49 -16
- package/build/services/memflow/workflow/isSideEffectAllowed.d.ts +21 -4
- package/build/services/memflow/workflow/isSideEffectAllowed.js +21 -4
- package/build/services/memflow/workflow/proxyActivities.d.ts +70 -42
- package/build/services/memflow/workflow/proxyActivities.js +70 -42
- package/build/services/memflow/workflow/random.d.ts +33 -3
- package/build/services/memflow/workflow/random.js +33 -3
- package/build/services/memflow/workflow/searchMethods.d.ts +49 -2
- package/build/services/memflow/workflow/searchMethods.js +49 -2
- package/build/services/memflow/workflow/signal.d.ts +51 -22
- package/build/services/memflow/workflow/signal.js +52 -23
- package/build/services/memflow/workflow/sleepFor.d.ts +57 -18
- package/build/services/memflow/workflow/sleepFor.js +57 -18
- package/build/services/memflow/workflow/trace.d.ts +39 -6
- package/build/services/memflow/workflow/trace.js +39 -6
- package/build/services/memflow/workflow/waitFor.d.ts +55 -18
- package/build/services/memflow/workflow/waitFor.js +55 -18
- package/build/services/router/consumption/index.js +1 -1
- package/build/services/search/factory.js +1 -9
- package/build/services/store/factory.js +1 -9
- package/build/services/store/index.d.ts +6 -1
- package/build/services/store/providers/postgres/kvsql.d.ts +4 -0
- package/build/services/store/providers/postgres/kvsql.js +4 -0
- package/build/services/store/providers/postgres/kvtransaction.d.ts +2 -0
- package/build/services/store/providers/postgres/kvtransaction.js +23 -0
- package/build/services/store/providers/postgres/kvtypes/hash/basic.d.ts +51 -0
- package/build/services/store/providers/postgres/kvtypes/hash/basic.js +193 -1
- package/build/services/store/providers/postgres/kvtypes/hash/index.d.ts +4 -0
- package/build/services/store/providers/postgres/kvtypes/hash/index.js +6 -0
- package/build/services/store/providers/postgres/postgres.d.ts +21 -1
- package/build/services/store/providers/postgres/postgres.js +42 -4
- package/build/services/stream/factory.js +1 -17
- package/build/services/stream/providers/postgres/scout.js +2 -2
- package/build/services/sub/factory.js +1 -9
- package/build/services/sub/index.d.ts +1 -1
- package/build/services/sub/providers/postgres/postgres.d.ts +1 -1
- package/build/services/sub/providers/postgres/postgres.js +25 -10
- package/build/services/task/index.d.ts +1 -1
- package/build/services/task/index.js +2 -6
- package/build/services/telemetry/index.js +6 -0
- package/build/types/activity.d.ts +1 -1
- package/build/types/hotmesh.d.ts +1 -1
- package/build/types/index.d.ts +0 -1
- package/build/types/index.js +1 -4
- package/build/types/job.d.ts +1 -1
- package/build/types/memflow.d.ts +1 -1
- package/build/types/provider.d.ts +1 -1
- package/build/types/quorum.d.ts +2 -2
- package/build/vitest.config.d.ts +2 -0
- package/build/vitest.config.js +18 -0
- package/index.ts +0 -4
- package/package.json +63 -79
- package/vitest.config.ts +17 -0
- package/build/services/connector/providers/ioredis.d.ts +0 -9
- package/build/services/connector/providers/ioredis.js +0 -26
- package/build/services/connector/providers/redis.d.ts +0 -9
- package/build/services/connector/providers/redis.js +0 -38
- package/build/services/search/providers/redis/ioredis.d.ts +0 -23
- package/build/services/search/providers/redis/ioredis.js +0 -189
- package/build/services/search/providers/redis/redis.d.ts +0 -23
- package/build/services/search/providers/redis/redis.js +0 -202
- package/build/services/store/providers/redis/_base.d.ts +0 -137
- package/build/services/store/providers/redis/_base.js +0 -980
- package/build/services/store/providers/redis/ioredis.d.ts +0 -20
- package/build/services/store/providers/redis/ioredis.js +0 -190
- package/build/services/store/providers/redis/redis.d.ts +0 -18
- package/build/services/store/providers/redis/redis.js +0 -199
- package/build/services/stream/providers/redis/ioredis.d.ts +0 -61
- package/build/services/stream/providers/redis/ioredis.js +0 -272
- package/build/services/stream/providers/redis/redis.d.ts +0 -61
- package/build/services/stream/providers/redis/redis.js +0 -305
- package/build/services/sub/providers/redis/ioredis.d.ts +0 -20
- package/build/services/sub/providers/redis/ioredis.js +0 -161
- package/build/services/sub/providers/redis/redis.d.ts +0 -18
- package/build/services/sub/providers/redis/redis.js +0 -148
- package/build/types/redis.d.ts +0 -258
- package/build/types/redis.js +0 -11
|
@@ -3,31 +3,64 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.didInterrupt = void 0;
|
|
4
4
|
const errors_1 = require("../../../modules/errors");
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Type guard that returns `true` if an error is a MemFlow engine
|
|
7
|
+
* control-flow signal rather than a genuine application error.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* MemFlow uses thrown errors internally to suspend workflow execution
|
|
10
|
+
* for durable operations like `sleepFor`, `waitFor`, `proxyActivities`,
|
|
11
|
+
* and `execChild`. These errors must be re-thrown (not swallowed) so
|
|
12
|
+
* the engine can persist state and schedule the next step.
|
|
13
|
+
*
|
|
14
|
+
* **Always use `didInterrupt` in `catch` blocks inside workflow
|
|
15
|
+
* functions** to avoid accidentally swallowing engine signals.
|
|
16
|
+
*
|
|
17
|
+
* ## Recognized Error Types
|
|
18
|
+
*
|
|
19
|
+
* `MemFlowChildError`, `MemFlowFatalError`, `MemFlowMaxedError`,
|
|
20
|
+
* `MemFlowProxyError`, `MemFlowRetryError`, `MemFlowSleepError`,
|
|
21
|
+
* `MemFlowTimeoutError`, `MemFlowWaitForError`, `MemFlowWaitForAllError`
|
|
22
|
+
*
|
|
23
|
+
* ## Examples
|
|
11
24
|
*
|
|
12
|
-
* @example
|
|
13
25
|
* ```typescript
|
|
14
26
|
* import { MemFlow } from '@hotmeshio/hotmesh';
|
|
15
27
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* throw
|
|
28
|
+
* export async function safeWorkflow(): Promise<string> {
|
|
29
|
+
* const { riskyOperation } = MemFlow.workflow.proxyActivities<typeof activities>();
|
|
30
|
+
*
|
|
31
|
+
* try {
|
|
32
|
+
* return await riskyOperation();
|
|
33
|
+
* } catch (error) {
|
|
34
|
+
* // CRITICAL: re-throw engine signals
|
|
35
|
+
* if (MemFlow.workflow.didInterrupt(error)) {
|
|
36
|
+
* throw error;
|
|
37
|
+
* }
|
|
38
|
+
* // Handle real application errors
|
|
39
|
+
* return 'fallback-value';
|
|
23
40
|
* }
|
|
24
|
-
* // Handle actual error
|
|
25
|
-
* console.error('Workflow failed:', error);
|
|
26
41
|
* }
|
|
27
42
|
* ```
|
|
28
43
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
44
|
+
* ```typescript
|
|
45
|
+
* // Common pattern in interceptors
|
|
46
|
+
* const interceptor: WorkflowInterceptor = {
|
|
47
|
+
* async execute(ctx, next) {
|
|
48
|
+
* try {
|
|
49
|
+
* return await next();
|
|
50
|
+
* } catch (error) {
|
|
51
|
+
* if (MemFlow.workflow.didInterrupt(error)) {
|
|
52
|
+
* throw error; // always re-throw engine signals
|
|
53
|
+
* }
|
|
54
|
+
* // Log and re-throw application errors
|
|
55
|
+
* console.error('Workflow failed:', error);
|
|
56
|
+
* throw error;
|
|
57
|
+
* }
|
|
58
|
+
* },
|
|
59
|
+
* };
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @param {Error} error - The error to check.
|
|
63
|
+
* @returns {boolean} `true` if the error is a MemFlow engine interruption signal.
|
|
31
64
|
*/
|
|
32
65
|
function didInterrupt(error) {
|
|
33
66
|
return (error instanceof errors_1.MemFlowChildError ||
|
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
import { HotMesh } from './common';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Guards side-effectful operations (`emit`, `signal`, `hook`, `trace`,
|
|
4
|
+
* `interrupt`) against duplicate execution during replay. Unlike
|
|
5
|
+
* `didRun()` (which replays stored results), this guard is for
|
|
6
|
+
* operations that don't produce a return value to cache — they simply
|
|
7
|
+
* must not run twice.
|
|
8
|
+
*
|
|
9
|
+
* ## Mechanism
|
|
10
|
+
*
|
|
11
|
+
* 1. Increments the execution counter to produce a `sessionId`.
|
|
12
|
+
* 2. If that `sessionId` already exists in the `replay` hash, the
|
|
13
|
+
* operation already ran in a previous execution → return `false`.
|
|
14
|
+
* 3. Otherwise, atomically increments a field on the job's backend
|
|
15
|
+
* HASH via `incrementFieldByFloat`. If the result is exactly `1`,
|
|
16
|
+
* this is the first worker to reach this point → return `true`.
|
|
17
|
+
* If `> 1`, a concurrent worker already executed it → return `false`.
|
|
18
|
+
*
|
|
19
|
+
* This provides **distributed idempotency** for side effects across
|
|
20
|
+
* replays and concurrent worker instances.
|
|
21
|
+
*
|
|
5
22
|
* @private
|
|
6
23
|
* @param {HotMesh} hotMeshClient - The HotMesh client.
|
|
7
|
-
* @param {string} prefix -
|
|
8
|
-
* @returns {Promise<boolean>}
|
|
24
|
+
* @param {string} prefix - Operation type (`'trace'`, `'emit'`, `'signal'`, `'hook'`, `'interrupt'`).
|
|
25
|
+
* @returns {Promise<boolean>} `true` if the side effect should execute, `false` if it already ran.
|
|
9
26
|
*/
|
|
10
27
|
export declare function isSideEffectAllowed(hotMeshClient: HotMesh, prefix: string): Promise<boolean>;
|
|
@@ -3,12 +3,29 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.isSideEffectAllowed = void 0;
|
|
4
4
|
const common_1 = require("./common");
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Guards side-effectful operations (`emit`, `signal`, `hook`, `trace`,
|
|
7
|
+
* `interrupt`) against duplicate execution during replay. Unlike
|
|
8
|
+
* `didRun()` (which replays stored results), this guard is for
|
|
9
|
+
* operations that don't produce a return value to cache — they simply
|
|
10
|
+
* must not run twice.
|
|
11
|
+
*
|
|
12
|
+
* ## Mechanism
|
|
13
|
+
*
|
|
14
|
+
* 1. Increments the execution counter to produce a `sessionId`.
|
|
15
|
+
* 2. If that `sessionId` already exists in the `replay` hash, the
|
|
16
|
+
* operation already ran in a previous execution → return `false`.
|
|
17
|
+
* 3. Otherwise, atomically increments a field on the job's backend
|
|
18
|
+
* HASH via `incrementFieldByFloat`. If the result is exactly `1`,
|
|
19
|
+
* this is the first worker to reach this point → return `true`.
|
|
20
|
+
* If `> 1`, a concurrent worker already executed it → return `false`.
|
|
21
|
+
*
|
|
22
|
+
* This provides **distributed idempotency** for side effects across
|
|
23
|
+
* replays and concurrent worker instances.
|
|
24
|
+
*
|
|
8
25
|
* @private
|
|
9
26
|
* @param {HotMesh} hotMeshClient - The HotMesh client.
|
|
10
|
-
* @param {string} prefix -
|
|
11
|
-
* @returns {Promise<boolean>}
|
|
27
|
+
* @param {string} prefix - Operation type (`'trace'`, `'emit'`, `'signal'`, `'hook'`, `'interrupt'`).
|
|
28
|
+
* @returns {Promise<boolean>} `true` if the side effect should execute, `false` if it already ran.
|
|
12
29
|
*/
|
|
13
30
|
async function isSideEffectAllowed(hotMeshClient, prefix) {
|
|
14
31
|
const store = common_1.asyncLocalStorage.getStore();
|
|
@@ -11,81 +11,109 @@ declare function getProxyInterruptPayload(context: ReturnType<typeof getContext>
|
|
|
11
11
|
*/
|
|
12
12
|
declare function wrapActivity<T>(activityName: string, options?: ActivityConfig): T;
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* Creates a typed proxy for calling activity functions with durable execution,
|
|
15
|
+
* automatic retry, and deterministic replay. This is the primary way to invoke
|
|
16
|
+
* side-effectful code (HTTP calls, database writes, file I/O) from within a
|
|
17
|
+
* workflow function.
|
|
16
18
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
+
* Activities execute on a **separate worker process** via message queue,
|
|
20
|
+
* isolating side effects from the deterministic workflow function. Each
|
|
21
|
+
* proxied call is assigned a unique execution index, and on replay the
|
|
22
|
+
* stored result is returned without re-executing the activity.
|
|
19
23
|
*
|
|
20
|
-
*
|
|
21
|
-
* `registerActivityWorker()`, you can reference them by providing just the `taskQueue`
|
|
22
|
-
* and a TypeScript interface.
|
|
24
|
+
* ## Routing
|
|
23
25
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* - **Default**: Activities route to `{workflowTaskQueue}-activity`.
|
|
27
|
+
* - **Explicit `taskQueue`**: Activities route to `{taskQueue}-activity`,
|
|
28
|
+
* enabling shared/global activity worker pools across workflows.
|
|
29
|
+
*
|
|
30
|
+
* ## Retry Policy
|
|
31
|
+
*
|
|
32
|
+
* | Option | Default | Description |
|
|
33
|
+
* |----------------------|---------|-------------|
|
|
34
|
+
* | `maximumAttempts` | 50 | Max retries before the activity is marked as failed |
|
|
35
|
+
* | `backoffCoefficient` | 2 | Exponential backoff multiplier |
|
|
36
|
+
* | `maximumInterval` | `'5m'` | Cap on delay between retries |
|
|
37
|
+
* | `throwOnError` | `true` | Throw on activity failure (set `false` to return the error) |
|
|
38
|
+
*
|
|
39
|
+
* ## Examples
|
|
30
40
|
*
|
|
31
|
-
* @example
|
|
32
41
|
* ```typescript
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
42
|
+
* import { MemFlow } from '@hotmeshio/hotmesh';
|
|
43
|
+
* import * as activities from './activities';
|
|
44
|
+
*
|
|
45
|
+
* // Standard pattern: register and proxy activities inline
|
|
46
|
+
* export async function orderWorkflow(orderId: string): Promise<string> {
|
|
47
|
+
* const { validateOrder, chargePayment, sendConfirmation } =
|
|
48
|
+
* MemFlow.workflow.proxyActivities<typeof activities>({
|
|
49
|
+
* activities,
|
|
50
|
+
* retryPolicy: {
|
|
51
|
+
* maximumAttempts: 3,
|
|
52
|
+
* backoffCoefficient: 2,
|
|
53
|
+
* maximumInterval: '30s',
|
|
54
|
+
* },
|
|
55
|
+
* });
|
|
38
56
|
*
|
|
39
|
-
*
|
|
57
|
+
* await validateOrder(orderId);
|
|
58
|
+
* const receipt = await chargePayment(orderId);
|
|
59
|
+
* await sendConfirmation(orderId, receipt);
|
|
60
|
+
* return receipt;
|
|
61
|
+
* }
|
|
40
62
|
* ```
|
|
41
63
|
*
|
|
42
|
-
* @example
|
|
43
64
|
* ```typescript
|
|
44
|
-
* //
|
|
65
|
+
* // Remote activities: reference a pre-registered worker pool by taskQueue
|
|
45
66
|
* interface PaymentActivities {
|
|
46
67
|
* processPayment: (amount: number) => Promise<string>;
|
|
47
|
-
*
|
|
68
|
+
* refundPayment: (txId: string) => Promise<void>;
|
|
48
69
|
* }
|
|
49
70
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
71
|
+
* export async function refundWorkflow(txId: string): Promise<void> {
|
|
72
|
+
* const { refundPayment } =
|
|
73
|
+
* MemFlow.workflow.proxyActivities<PaymentActivities>({
|
|
74
|
+
* taskQueue: 'payments',
|
|
75
|
+
* retryPolicy: { maximumAttempts: 5 },
|
|
76
|
+
* });
|
|
55
77
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
78
|
+
* await refundPayment(txId);
|
|
79
|
+
* }
|
|
58
80
|
* ```
|
|
59
81
|
*
|
|
60
|
-
* @example
|
|
61
82
|
* ```typescript
|
|
62
|
-
* //
|
|
63
|
-
* const
|
|
83
|
+
* // Interceptor with shared activity pool
|
|
84
|
+
* const auditInterceptor: WorkflowInterceptor = {
|
|
64
85
|
* async execute(ctx, next) {
|
|
65
86
|
* const { auditLog } = MemFlow.workflow.proxyActivities<{
|
|
66
87
|
* auditLog: (id: string, action: string) => Promise<void>;
|
|
67
88
|
* }>({
|
|
68
|
-
* taskQueue: 'shared',
|
|
69
|
-
* retryPolicy: { maximumAttempts: 3 }
|
|
89
|
+
* taskQueue: 'shared-audit',
|
|
90
|
+
* retryPolicy: { maximumAttempts: 3 },
|
|
70
91
|
* });
|
|
71
92
|
*
|
|
72
93
|
* await auditLog(ctx.get('workflowId'), 'started');
|
|
73
94
|
* const result = await next();
|
|
74
95
|
* await auditLog(ctx.get('workflowId'), 'completed');
|
|
75
96
|
* return result;
|
|
76
|
-
* }
|
|
97
|
+
* },
|
|
77
98
|
* };
|
|
78
99
|
* ```
|
|
79
100
|
*
|
|
80
|
-
* @example
|
|
81
101
|
* ```typescript
|
|
82
|
-
* //
|
|
83
|
-
* const
|
|
84
|
-
* activities
|
|
85
|
-
*
|
|
86
|
-
* retryPolicy: { maximumAttempts: 5 }
|
|
102
|
+
* // Graceful error handling (no throw)
|
|
103
|
+
* const { riskyOperation } = MemFlow.workflow.proxyActivities<typeof activities>({
|
|
104
|
+
* activities,
|
|
105
|
+
* retryPolicy: { maximumAttempts: 1, throwOnError: false },
|
|
87
106
|
* });
|
|
107
|
+
*
|
|
108
|
+
* const result = await riskyOperation();
|
|
109
|
+
* if (result instanceof Error) {
|
|
110
|
+
* // handle gracefully
|
|
111
|
+
* }
|
|
88
112
|
* ```
|
|
113
|
+
*
|
|
114
|
+
* @template ACT - The activity type map (use `typeof activities` for inline registration).
|
|
115
|
+
* @param {ActivityConfig} [options] - Activity configuration including retry policy and routing.
|
|
116
|
+
* @returns {ProxyType<ACT>} A typed proxy object mapping activity names to their durable wrappers.
|
|
89
117
|
*/
|
|
90
118
|
export declare function proxyActivities<ACT>(options?: ActivityConfig): ProxyType<ACT>;
|
|
91
119
|
export { wrapActivity, getProxyInterruptPayload };
|
|
@@ -81,81 +81,109 @@ function wrapActivity(activityName, options) {
|
|
|
81
81
|
}
|
|
82
82
|
exports.wrapActivity = wrapActivity;
|
|
83
83
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
84
|
+
* Creates a typed proxy for calling activity functions with durable execution,
|
|
85
|
+
* automatic retry, and deterministic replay. This is the primary way to invoke
|
|
86
|
+
* side-effectful code (HTTP calls, database writes, file I/O) from within a
|
|
87
|
+
* workflow function.
|
|
86
88
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
+
* Activities execute on a **separate worker process** via message queue,
|
|
90
|
+
* isolating side effects from the deterministic workflow function. Each
|
|
91
|
+
* proxied call is assigned a unique execution index, and on replay the
|
|
92
|
+
* stored result is returned without re-executing the activity.
|
|
89
93
|
*
|
|
90
|
-
*
|
|
91
|
-
* `registerActivityWorker()`, you can reference them by providing just the `taskQueue`
|
|
92
|
-
* and a TypeScript interface.
|
|
94
|
+
* ## Routing
|
|
93
95
|
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
96
|
+
* - **Default**: Activities route to `{workflowTaskQueue}-activity`.
|
|
97
|
+
* - **Explicit `taskQueue`**: Activities route to `{taskQueue}-activity`,
|
|
98
|
+
* enabling shared/global activity worker pools across workflows.
|
|
99
|
+
*
|
|
100
|
+
* ## Retry Policy
|
|
101
|
+
*
|
|
102
|
+
* | Option | Default | Description |
|
|
103
|
+
* |----------------------|---------|-------------|
|
|
104
|
+
* | `maximumAttempts` | 50 | Max retries before the activity is marked as failed |
|
|
105
|
+
* | `backoffCoefficient` | 2 | Exponential backoff multiplier |
|
|
106
|
+
* | `maximumInterval` | `'5m'` | Cap on delay between retries |
|
|
107
|
+
* | `throwOnError` | `true` | Throw on activity failure (set `false` to return the error) |
|
|
108
|
+
*
|
|
109
|
+
* ## Examples
|
|
100
110
|
*
|
|
101
|
-
* @example
|
|
102
111
|
* ```typescript
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
112
|
+
* import { MemFlow } from '@hotmeshio/hotmesh';
|
|
113
|
+
* import * as activities from './activities';
|
|
114
|
+
*
|
|
115
|
+
* // Standard pattern: register and proxy activities inline
|
|
116
|
+
* export async function orderWorkflow(orderId: string): Promise<string> {
|
|
117
|
+
* const { validateOrder, chargePayment, sendConfirmation } =
|
|
118
|
+
* MemFlow.workflow.proxyActivities<typeof activities>({
|
|
119
|
+
* activities,
|
|
120
|
+
* retryPolicy: {
|
|
121
|
+
* maximumAttempts: 3,
|
|
122
|
+
* backoffCoefficient: 2,
|
|
123
|
+
* maximumInterval: '30s',
|
|
124
|
+
* },
|
|
125
|
+
* });
|
|
108
126
|
*
|
|
109
|
-
*
|
|
127
|
+
* await validateOrder(orderId);
|
|
128
|
+
* const receipt = await chargePayment(orderId);
|
|
129
|
+
* await sendConfirmation(orderId, receipt);
|
|
130
|
+
* return receipt;
|
|
131
|
+
* }
|
|
110
132
|
* ```
|
|
111
133
|
*
|
|
112
|
-
* @example
|
|
113
134
|
* ```typescript
|
|
114
|
-
* //
|
|
135
|
+
* // Remote activities: reference a pre-registered worker pool by taskQueue
|
|
115
136
|
* interface PaymentActivities {
|
|
116
137
|
* processPayment: (amount: number) => Promise<string>;
|
|
117
|
-
*
|
|
138
|
+
* refundPayment: (txId: string) => Promise<void>;
|
|
118
139
|
* }
|
|
119
140
|
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
141
|
+
* export async function refundWorkflow(txId: string): Promise<void> {
|
|
142
|
+
* const { refundPayment } =
|
|
143
|
+
* MemFlow.workflow.proxyActivities<PaymentActivities>({
|
|
144
|
+
* taskQueue: 'payments',
|
|
145
|
+
* retryPolicy: { maximumAttempts: 5 },
|
|
146
|
+
* });
|
|
125
147
|
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
148
|
+
* await refundPayment(txId);
|
|
149
|
+
* }
|
|
128
150
|
* ```
|
|
129
151
|
*
|
|
130
|
-
* @example
|
|
131
152
|
* ```typescript
|
|
132
|
-
* //
|
|
133
|
-
* const
|
|
153
|
+
* // Interceptor with shared activity pool
|
|
154
|
+
* const auditInterceptor: WorkflowInterceptor = {
|
|
134
155
|
* async execute(ctx, next) {
|
|
135
156
|
* const { auditLog } = MemFlow.workflow.proxyActivities<{
|
|
136
157
|
* auditLog: (id: string, action: string) => Promise<void>;
|
|
137
158
|
* }>({
|
|
138
|
-
* taskQueue: 'shared',
|
|
139
|
-
* retryPolicy: { maximumAttempts: 3 }
|
|
159
|
+
* taskQueue: 'shared-audit',
|
|
160
|
+
* retryPolicy: { maximumAttempts: 3 },
|
|
140
161
|
* });
|
|
141
162
|
*
|
|
142
163
|
* await auditLog(ctx.get('workflowId'), 'started');
|
|
143
164
|
* const result = await next();
|
|
144
165
|
* await auditLog(ctx.get('workflowId'), 'completed');
|
|
145
166
|
* return result;
|
|
146
|
-
* }
|
|
167
|
+
* },
|
|
147
168
|
* };
|
|
148
169
|
* ```
|
|
149
170
|
*
|
|
150
|
-
* @example
|
|
151
171
|
* ```typescript
|
|
152
|
-
* //
|
|
153
|
-
* const
|
|
154
|
-
* activities
|
|
155
|
-
*
|
|
156
|
-
* retryPolicy: { maximumAttempts: 5 }
|
|
172
|
+
* // Graceful error handling (no throw)
|
|
173
|
+
* const { riskyOperation } = MemFlow.workflow.proxyActivities<typeof activities>({
|
|
174
|
+
* activities,
|
|
175
|
+
* retryPolicy: { maximumAttempts: 1, throwOnError: false },
|
|
157
176
|
* });
|
|
177
|
+
*
|
|
178
|
+
* const result = await riskyOperation();
|
|
179
|
+
* if (result instanceof Error) {
|
|
180
|
+
* // handle gracefully
|
|
181
|
+
* }
|
|
158
182
|
* ```
|
|
183
|
+
*
|
|
184
|
+
* @template ACT - The activity type map (use `typeof activities` for inline registration).
|
|
185
|
+
* @param {ActivityConfig} [options] - Activity configuration including retry policy and routing.
|
|
186
|
+
* @returns {ProxyType<ACT>} A typed proxy object mapping activity names to their durable wrappers.
|
|
159
187
|
*/
|
|
160
188
|
function proxyActivities(options) {
|
|
161
189
|
// Register activities if provided (optional - may already be registered remotely)
|
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Returns a deterministic random number between 0 and 1. The
|
|
3
|
-
* current execution
|
|
4
|
-
*
|
|
2
|
+
* Returns a deterministic pseudo-random number between 0 and 1. The value
|
|
3
|
+
* is derived from the current execution counter, so it produces the
|
|
4
|
+
* **same result** on every replay of the workflow at this execution point.
|
|
5
|
+
*
|
|
6
|
+
* Use this instead of `Math.random()` inside workflow functions.
|
|
7
|
+
* `Math.random()` is non-deterministic and would break replay correctness.
|
|
8
|
+
*
|
|
9
|
+
* ## Examples
|
|
10
|
+
*
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { MemFlow } from '@hotmeshio/hotmesh';
|
|
13
|
+
*
|
|
14
|
+
* // Generate a deterministic unique suffix
|
|
15
|
+
* export async function uniqueWorkflow(): Promise<string> {
|
|
16
|
+
* const suffix = Math.floor(MemFlow.workflow.random() * 10000);
|
|
17
|
+
* return `item-${suffix}`;
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* ```typescript
|
|
22
|
+
* // A/B test routing (deterministic per workflow execution)
|
|
23
|
+
* export async function experimentWorkflow(userId: string): Promise<string> {
|
|
24
|
+
* const { variantA, variantB } = MemFlow.workflow.proxyActivities<typeof activities>();
|
|
25
|
+
*
|
|
26
|
+
* if (MemFlow.workflow.random() < 0.5) {
|
|
27
|
+
* return await variantA(userId);
|
|
28
|
+
* } else {
|
|
29
|
+
* return await variantB(userId);
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @returns {number} A deterministic pseudo-random number in [0, 1).
|
|
5
35
|
*/
|
|
6
36
|
export declare function random(): number;
|
|
@@ -3,9 +3,39 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.random = void 0;
|
|
4
4
|
const common_1 = require("./common");
|
|
5
5
|
/**
|
|
6
|
-
* Returns a deterministic random number between 0 and 1. The
|
|
7
|
-
* current execution
|
|
8
|
-
*
|
|
6
|
+
* Returns a deterministic pseudo-random number between 0 and 1. The value
|
|
7
|
+
* is derived from the current execution counter, so it produces the
|
|
8
|
+
* **same result** on every replay of the workflow at this execution point.
|
|
9
|
+
*
|
|
10
|
+
* Use this instead of `Math.random()` inside workflow functions.
|
|
11
|
+
* `Math.random()` is non-deterministic and would break replay correctness.
|
|
12
|
+
*
|
|
13
|
+
* ## Examples
|
|
14
|
+
*
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { MemFlow } from '@hotmeshio/hotmesh';
|
|
17
|
+
*
|
|
18
|
+
* // Generate a deterministic unique suffix
|
|
19
|
+
* export async function uniqueWorkflow(): Promise<string> {
|
|
20
|
+
* const suffix = Math.floor(MemFlow.workflow.random() * 10000);
|
|
21
|
+
* return `item-${suffix}`;
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* ```typescript
|
|
26
|
+
* // A/B test routing (deterministic per workflow execution)
|
|
27
|
+
* export async function experimentWorkflow(userId: string): Promise<string> {
|
|
28
|
+
* const { variantA, variantB } = MemFlow.workflow.proxyActivities<typeof activities>();
|
|
29
|
+
*
|
|
30
|
+
* if (MemFlow.workflow.random() < 0.5) {
|
|
31
|
+
* return await variantA(userId);
|
|
32
|
+
* } else {
|
|
33
|
+
* return await variantB(userId);
|
|
34
|
+
* }
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @returns {number} A deterministic pseudo-random number in [0, 1).
|
|
9
39
|
*/
|
|
10
40
|
function random() {
|
|
11
41
|
const store = common_1.asyncLocalStorage.getStore();
|
|
@@ -1,6 +1,53 @@
|
|
|
1
1
|
import { Search } from './common';
|
|
2
2
|
/**
|
|
3
|
-
* Returns a
|
|
4
|
-
*
|
|
3
|
+
* Returns a `Search` session handle for reading and writing key-value data
|
|
4
|
+
* on the workflow's backend HASH record. Search fields are flat string
|
|
5
|
+
* key-value pairs stored alongside the job state, making them queryable
|
|
6
|
+
* via `MemFlow.Client.workflow.search()` (FT.SEARCH).
|
|
7
|
+
*
|
|
8
|
+
* Each call produces a unique session ID tied to the deterministic
|
|
9
|
+
* execution counter, ensuring correct replay behavior.
|
|
10
|
+
*
|
|
11
|
+
* Use `search()` for flat, indexable key-value data. For structured
|
|
12
|
+
* JSON documents, use `entity()` instead.
|
|
13
|
+
*
|
|
14
|
+
* ## Examples
|
|
15
|
+
*
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { MemFlow } from '@hotmeshio/hotmesh';
|
|
18
|
+
*
|
|
19
|
+
* export async function orderWorkflow(orderId: string): Promise<void> {
|
|
20
|
+
* const search = await MemFlow.workflow.search();
|
|
21
|
+
*
|
|
22
|
+
* // Write searchable fields
|
|
23
|
+
* await search.set({
|
|
24
|
+
* orderId,
|
|
25
|
+
* status: 'processing',
|
|
26
|
+
* createdAt: new Date().toISOString(),
|
|
27
|
+
* });
|
|
28
|
+
*
|
|
29
|
+
* const { processOrder } = MemFlow.workflow.proxyActivities<typeof activities>();
|
|
30
|
+
* await processOrder(orderId);
|
|
31
|
+
*
|
|
32
|
+
* // Update status
|
|
33
|
+
* await search.set({ status: 'completed' });
|
|
34
|
+
*
|
|
35
|
+
* // Read a field back
|
|
36
|
+
* const status = await search.get('status');
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* ```typescript
|
|
41
|
+
* // Increment a numeric counter
|
|
42
|
+
* export async function counterWorkflow(): Promise<number> {
|
|
43
|
+
* const search = await MemFlow.workflow.search();
|
|
44
|
+
* await search.set({ count: '0' });
|
|
45
|
+
* await search.incr('count', 1);
|
|
46
|
+
* await search.incr('count', 1);
|
|
47
|
+
* return Number(await search.get('count')); // 2
|
|
48
|
+
* }
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @returns {Promise<Search>} A search session scoped to the current workflow job.
|
|
5
52
|
*/
|
|
6
53
|
export declare function search(): Promise<Search>;
|
|
@@ -3,8 +3,55 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.search = void 0;
|
|
4
4
|
const common_1 = require("./common");
|
|
5
5
|
/**
|
|
6
|
-
* Returns a
|
|
7
|
-
*
|
|
6
|
+
* Returns a `Search` session handle for reading and writing key-value data
|
|
7
|
+
* on the workflow's backend HASH record. Search fields are flat string
|
|
8
|
+
* key-value pairs stored alongside the job state, making them queryable
|
|
9
|
+
* via `MemFlow.Client.workflow.search()` (FT.SEARCH).
|
|
10
|
+
*
|
|
11
|
+
* Each call produces a unique session ID tied to the deterministic
|
|
12
|
+
* execution counter, ensuring correct replay behavior.
|
|
13
|
+
*
|
|
14
|
+
* Use `search()` for flat, indexable key-value data. For structured
|
|
15
|
+
* JSON documents, use `entity()` instead.
|
|
16
|
+
*
|
|
17
|
+
* ## Examples
|
|
18
|
+
*
|
|
19
|
+
* ```typescript
|
|
20
|
+
* import { MemFlow } from '@hotmeshio/hotmesh';
|
|
21
|
+
*
|
|
22
|
+
* export async function orderWorkflow(orderId: string): Promise<void> {
|
|
23
|
+
* const search = await MemFlow.workflow.search();
|
|
24
|
+
*
|
|
25
|
+
* // Write searchable fields
|
|
26
|
+
* await search.set({
|
|
27
|
+
* orderId,
|
|
28
|
+
* status: 'processing',
|
|
29
|
+
* createdAt: new Date().toISOString(),
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* const { processOrder } = MemFlow.workflow.proxyActivities<typeof activities>();
|
|
33
|
+
* await processOrder(orderId);
|
|
34
|
+
*
|
|
35
|
+
* // Update status
|
|
36
|
+
* await search.set({ status: 'completed' });
|
|
37
|
+
*
|
|
38
|
+
* // Read a field back
|
|
39
|
+
* const status = await search.get('status');
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* ```typescript
|
|
44
|
+
* // Increment a numeric counter
|
|
45
|
+
* export async function counterWorkflow(): Promise<number> {
|
|
46
|
+
* const search = await MemFlow.workflow.search();
|
|
47
|
+
* await search.set({ count: '0' });
|
|
48
|
+
* await search.incr('count', 1);
|
|
49
|
+
* await search.incr('count', 1);
|
|
50
|
+
* return Number(await search.get('count')); // 2
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @returns {Promise<Search>} A search session scoped to the current workflow job.
|
|
8
55
|
*/
|
|
9
56
|
async function search() {
|
|
10
57
|
const store = common_1.asyncLocalStorage.getStore();
|