@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
|
@@ -4,47 +4,99 @@ exports.execHookBatch = void 0;
|
|
|
4
4
|
const hook_1 = require("./hook");
|
|
5
5
|
const waitFor_1 = require("./waitFor");
|
|
6
6
|
/**
|
|
7
|
-
* Executes multiple hooks in parallel and awaits all their signal responses
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Executes multiple hooks in parallel and awaits all their signal responses,
|
|
8
|
+
* returning a keyed object of results. This is the recommended way to run
|
|
9
|
+
* concurrent hooks — it solves a race condition where calling
|
|
10
|
+
* `Promise.all([execHook(), execHook()])` would throw before all `waitFor`
|
|
11
|
+
* registrations complete.
|
|
10
12
|
*
|
|
11
|
-
*
|
|
12
|
-
* preventing signals from being sent before the framework is ready to receive them.
|
|
13
|
+
* ## Three-Phase Execution
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* 1. **Fire all hooks** via `Promise.all` (registers streams immediately).
|
|
16
|
+
* 2. **Await all signals** via `Promise.all` (all `waitFor` registrations
|
|
17
|
+
* happen together before any `MemFlowWaitForError` is thrown).
|
|
18
|
+
* 3. **Combine results** into a `{ [key]: result }` map.
|
|
19
|
+
*
|
|
20
|
+
* ## Examples
|
|
21
|
+
*
|
|
22
|
+
* ```typescript
|
|
23
|
+
* import { MemFlow } from '@hotmeshio/hotmesh';
|
|
24
|
+
*
|
|
25
|
+
* // Fan-out to multiple AI agents, gather all perspectives
|
|
26
|
+
* export async function researchWorkflow(query: string): Promise<Summary> {
|
|
27
|
+
* const perspectives = await MemFlow.workflow.execHookBatch<{
|
|
28
|
+
* optimistic: PerspectiveResult;
|
|
29
|
+
* skeptical: PerspectiveResult;
|
|
30
|
+
* neutral: PerspectiveResult;
|
|
31
|
+
* }>([
|
|
32
|
+
* {
|
|
33
|
+
* key: 'optimistic',
|
|
34
|
+
* options: {
|
|
35
|
+
* taskQueue: 'agents',
|
|
36
|
+
* workflowName: 'analyzeOptimistic',
|
|
37
|
+
* args: [query],
|
|
38
|
+
* },
|
|
39
|
+
* },
|
|
40
|
+
* {
|
|
41
|
+
* key: 'skeptical',
|
|
42
|
+
* options: {
|
|
43
|
+
* taskQueue: 'agents',
|
|
44
|
+
* workflowName: 'analyzeSkeptical',
|
|
45
|
+
* args: [query],
|
|
46
|
+
* },
|
|
47
|
+
* },
|
|
48
|
+
* {
|
|
49
|
+
* key: 'neutral',
|
|
50
|
+
* options: {
|
|
51
|
+
* taskQueue: 'agents',
|
|
52
|
+
* workflowName: 'analyzeNeutral',
|
|
53
|
+
* args: [query],
|
|
54
|
+
* },
|
|
55
|
+
* },
|
|
56
|
+
* ]);
|
|
57
|
+
*
|
|
58
|
+
* // All three results are available as typed properties
|
|
59
|
+
* const { synthesize } = MemFlow.workflow.proxyActivities<typeof activities>();
|
|
60
|
+
* return await synthesize(
|
|
61
|
+
* perspectives.optimistic,
|
|
62
|
+
* perspectives.skeptical,
|
|
63
|
+
* perspectives.neutral,
|
|
64
|
+
* );
|
|
65
|
+
* }
|
|
66
|
+
* ```
|
|
17
67
|
*
|
|
18
|
-
* @example
|
|
19
68
|
* ```typescript
|
|
20
|
-
* //
|
|
21
|
-
* const
|
|
22
|
-
*
|
|
23
|
-
*
|
|
69
|
+
* // Parallel validation with different services
|
|
70
|
+
* const checks = await MemFlow.workflow.execHookBatch<{
|
|
71
|
+
* fraud: { safe: boolean };
|
|
72
|
+
* compliance: { approved: boolean };
|
|
24
73
|
* }>([
|
|
25
74
|
* {
|
|
26
|
-
* key: '
|
|
75
|
+
* key: 'fraud',
|
|
27
76
|
* options: {
|
|
28
|
-
* taskQueue: '
|
|
29
|
-
* workflowName: '
|
|
30
|
-
* args: [
|
|
31
|
-
*
|
|
32
|
-
* }
|
|
77
|
+
* taskQueue: 'fraud-detection',
|
|
78
|
+
* workflowName: 'checkFraud',
|
|
79
|
+
* args: [transactionId],
|
|
80
|
+
* },
|
|
33
81
|
* },
|
|
34
82
|
* {
|
|
35
|
-
* key: '
|
|
83
|
+
* key: 'compliance',
|
|
36
84
|
* options: {
|
|
37
|
-
* taskQueue: '
|
|
38
|
-
* workflowName: '
|
|
39
|
-
* args: [
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* }
|
|
85
|
+
* taskQueue: 'compliance',
|
|
86
|
+
* workflowName: 'checkCompliance',
|
|
87
|
+
* args: [transactionId],
|
|
88
|
+
* },
|
|
89
|
+
* },
|
|
43
90
|
* ]);
|
|
44
91
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
92
|
+
* if (checks.fraud.safe && checks.compliance.approved) {
|
|
93
|
+
* // proceed with transaction
|
|
94
|
+
* }
|
|
47
95
|
* ```
|
|
96
|
+
*
|
|
97
|
+
* @template T - Object type with keys matching the batch hook keys.
|
|
98
|
+
* @param {BatchHookConfig[]} hookConfigs - Array of hook configurations with unique keys.
|
|
99
|
+
* @returns {Promise<T>} Object mapping each config's `key` to its signal response.
|
|
48
100
|
*/
|
|
49
101
|
async function execHookBatch(hookConfigs) {
|
|
50
102
|
// Generate signal IDs for hooks that don't have them
|
|
@@ -1,9 +1,74 @@
|
|
|
1
1
|
import { HookOptions } from './common';
|
|
2
2
|
/**
|
|
3
|
-
* Spawns a hook
|
|
4
|
-
*
|
|
3
|
+
* Spawns a hook execution against an existing workflow job. The hook runs
|
|
4
|
+
* in an isolated dimensional thread within the target job's namespace,
|
|
5
|
+
* allowing it to read/write the same job state without interfering with
|
|
6
|
+
* the main workflow thread.
|
|
5
7
|
*
|
|
6
|
-
*
|
|
8
|
+
* This is the low-level primitive behind `execHook()`. Use `hook()`
|
|
9
|
+
* directly when you need fire-and-forget hook execution or when you
|
|
10
|
+
* manage signal coordination yourself.
|
|
11
|
+
*
|
|
12
|
+
* ## Target Resolution
|
|
13
|
+
*
|
|
14
|
+
* - If `taskQueue` and `workflowName` (or `entity`) are provided, the
|
|
15
|
+
* hook targets that specific workflow type.
|
|
16
|
+
* - If neither is provided, the hook targets the **current** workflow.
|
|
17
|
+
* However, targeting the same topic as the current workflow is
|
|
18
|
+
* rejected to prevent infinite loops.
|
|
19
|
+
*
|
|
20
|
+
* ## Idempotency
|
|
21
|
+
*
|
|
22
|
+
* The `isSideEffectAllowed` guard ensures hooks fire exactly once —
|
|
23
|
+
* on replay, the hook is not re-spawned.
|
|
24
|
+
*
|
|
25
|
+
* ## Examples
|
|
26
|
+
*
|
|
27
|
+
* ```typescript
|
|
28
|
+
* import { MemFlow } from '@hotmeshio/hotmesh';
|
|
29
|
+
*
|
|
30
|
+
* // Fire-and-forget: spawn a hook without waiting for its result
|
|
31
|
+
* export async function notifyWorkflow(userId: string): Promise<void> {
|
|
32
|
+
* await MemFlow.workflow.hook({
|
|
33
|
+
* taskQueue: 'notifications',
|
|
34
|
+
* workflowName: 'sendNotification',
|
|
35
|
+
* args: [userId, 'Your order has shipped'],
|
|
36
|
+
* });
|
|
37
|
+
* // Continues immediately, does not wait for the hook
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* ```typescript
|
|
42
|
+
* // Manual signal coordination (equivalent to execHook)
|
|
43
|
+
* export async function manualHookPattern(itemId: string): Promise<string> {
|
|
44
|
+
* const signalId = `process-${itemId}`;
|
|
45
|
+
*
|
|
46
|
+
* await MemFlow.workflow.hook({
|
|
47
|
+
* taskQueue: 'processors',
|
|
48
|
+
* workflowName: 'processItem',
|
|
49
|
+
* args: [itemId, signalId],
|
|
50
|
+
* });
|
|
51
|
+
*
|
|
52
|
+
* // Manually wait for the hook to signal back
|
|
53
|
+
* return await MemFlow.workflow.waitFor<string>(signalId);
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* ```typescript
|
|
58
|
+
* // Hook with retry configuration
|
|
59
|
+
* await MemFlow.workflow.hook({
|
|
60
|
+
* taskQueue: 'enrichment',
|
|
61
|
+
* workflowName: 'enrichProfile',
|
|
62
|
+
* args: [profileId],
|
|
63
|
+
* config: {
|
|
64
|
+
* maximumAttempts: 5,
|
|
65
|
+
* backoffCoefficient: 2,
|
|
66
|
+
* maximumInterval: '1m',
|
|
67
|
+
* },
|
|
68
|
+
* });
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @param {HookOptions} options - Hook configuration including target workflow and arguments.
|
|
7
72
|
* @returns {Promise<string>} The resulting hook/stream ID.
|
|
8
73
|
*/
|
|
9
74
|
export declare function hook(options: HookOptions): Promise<string>;
|
|
@@ -5,10 +5,75 @@ const common_1 = require("./common");
|
|
|
5
5
|
const context_1 = require("./context");
|
|
6
6
|
const isSideEffectAllowed_1 = require("./isSideEffectAllowed");
|
|
7
7
|
/**
|
|
8
|
-
* Spawns a hook
|
|
9
|
-
*
|
|
8
|
+
* Spawns a hook execution against an existing workflow job. The hook runs
|
|
9
|
+
* in an isolated dimensional thread within the target job's namespace,
|
|
10
|
+
* allowing it to read/write the same job state without interfering with
|
|
11
|
+
* the main workflow thread.
|
|
10
12
|
*
|
|
11
|
-
*
|
|
13
|
+
* This is the low-level primitive behind `execHook()`. Use `hook()`
|
|
14
|
+
* directly when you need fire-and-forget hook execution or when you
|
|
15
|
+
* manage signal coordination yourself.
|
|
16
|
+
*
|
|
17
|
+
* ## Target Resolution
|
|
18
|
+
*
|
|
19
|
+
* - If `taskQueue` and `workflowName` (or `entity`) are provided, the
|
|
20
|
+
* hook targets that specific workflow type.
|
|
21
|
+
* - If neither is provided, the hook targets the **current** workflow.
|
|
22
|
+
* However, targeting the same topic as the current workflow is
|
|
23
|
+
* rejected to prevent infinite loops.
|
|
24
|
+
*
|
|
25
|
+
* ## Idempotency
|
|
26
|
+
*
|
|
27
|
+
* The `isSideEffectAllowed` guard ensures hooks fire exactly once —
|
|
28
|
+
* on replay, the hook is not re-spawned.
|
|
29
|
+
*
|
|
30
|
+
* ## Examples
|
|
31
|
+
*
|
|
32
|
+
* ```typescript
|
|
33
|
+
* import { MemFlow } from '@hotmeshio/hotmesh';
|
|
34
|
+
*
|
|
35
|
+
* // Fire-and-forget: spawn a hook without waiting for its result
|
|
36
|
+
* export async function notifyWorkflow(userId: string): Promise<void> {
|
|
37
|
+
* await MemFlow.workflow.hook({
|
|
38
|
+
* taskQueue: 'notifications',
|
|
39
|
+
* workflowName: 'sendNotification',
|
|
40
|
+
* args: [userId, 'Your order has shipped'],
|
|
41
|
+
* });
|
|
42
|
+
* // Continues immediately, does not wait for the hook
|
|
43
|
+
* }
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* ```typescript
|
|
47
|
+
* // Manual signal coordination (equivalent to execHook)
|
|
48
|
+
* export async function manualHookPattern(itemId: string): Promise<string> {
|
|
49
|
+
* const signalId = `process-${itemId}`;
|
|
50
|
+
*
|
|
51
|
+
* await MemFlow.workflow.hook({
|
|
52
|
+
* taskQueue: 'processors',
|
|
53
|
+
* workflowName: 'processItem',
|
|
54
|
+
* args: [itemId, signalId],
|
|
55
|
+
* });
|
|
56
|
+
*
|
|
57
|
+
* // Manually wait for the hook to signal back
|
|
58
|
+
* return await MemFlow.workflow.waitFor<string>(signalId);
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* ```typescript
|
|
63
|
+
* // Hook with retry configuration
|
|
64
|
+
* await MemFlow.workflow.hook({
|
|
65
|
+
* taskQueue: 'enrichment',
|
|
66
|
+
* workflowName: 'enrichProfile',
|
|
67
|
+
* args: [profileId],
|
|
68
|
+
* config: {
|
|
69
|
+
* maximumAttempts: 5,
|
|
70
|
+
* backoffCoefficient: 2,
|
|
71
|
+
* maximumInterval: '1m',
|
|
72
|
+
* },
|
|
73
|
+
* });
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @param {HookOptions} options - Hook configuration including target workflow and arguments.
|
|
12
77
|
* @returns {Promise<string>} The resulting hook/stream ID.
|
|
13
78
|
*/
|
|
14
79
|
async function hook(options) {
|
|
@@ -52,7 +117,7 @@ async function hook(options) {
|
|
|
52
117
|
maximumAttempts: options.config?.maximumAttempts || common_1.HMSH_MEMFLOW_MAX_ATTEMPTS,
|
|
53
118
|
maximumInterval: (0, common_1.s)(options?.config?.maximumInterval ?? common_1.HMSH_MEMFLOW_MAX_INTERVAL),
|
|
54
119
|
};
|
|
55
|
-
return await hotMeshClient.
|
|
120
|
+
return await hotMeshClient.signal(`${namespace}.flow.signal`, payload, common_1.StreamStatus.PENDING, 202);
|
|
56
121
|
}
|
|
57
122
|
}
|
|
58
123
|
exports.hook = hook;
|
|
@@ -20,20 +20,75 @@ import { waitFor } from './waitFor';
|
|
|
20
20
|
import { HotMesh } from './common';
|
|
21
21
|
import { entity } from './entityMethods';
|
|
22
22
|
/**
|
|
23
|
-
* The
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* The workflow-internal API surface, exposed as `MemFlow.workflow`. Every
|
|
24
|
+
* method on this class is designed to be called **inside** a workflow
|
|
25
|
+
* function — they participate in deterministic replay and durable state
|
|
26
|
+
* management.
|
|
27
|
+
*
|
|
28
|
+
* ## Core Primitives
|
|
29
|
+
*
|
|
30
|
+
* | Method | Purpose |
|
|
31
|
+
* |--------|---------|
|
|
32
|
+
* | {@link proxyActivities} | Create durable activity proxies with retry |
|
|
33
|
+
* | {@link sleepFor} | Durable, crash-safe sleep |
|
|
34
|
+
* | {@link waitFor} | Pause until a signal is received |
|
|
35
|
+
* | {@link signal} | Send data to a waiting workflow |
|
|
36
|
+
* | {@link execChild} | Spawn and await a child workflow |
|
|
37
|
+
* | {@link startChild} | Spawn a child workflow (fire-and-forget) |
|
|
38
|
+
* | {@link execHook} | Spawn a hook and await its signal response |
|
|
39
|
+
* | {@link execHookBatch} | Spawn multiple hooks in parallel |
|
|
40
|
+
* | {@link hook} | Low-level hook spawning |
|
|
41
|
+
* | {@link interrupt} | Terminate a running workflow |
|
|
42
|
+
*
|
|
43
|
+
* ## Data & Observability
|
|
44
|
+
*
|
|
45
|
+
* | Method | Purpose |
|
|
46
|
+
* |--------|---------|
|
|
47
|
+
* | {@link search} | Read/write flat HASH key-value data |
|
|
48
|
+
* | {@link enrich} | One-shot HASH enrichment |
|
|
49
|
+
* | {@link entity} | Structured JSONB document storage |
|
|
50
|
+
* | {@link emit} | Publish events to the event bus |
|
|
51
|
+
* | {@link trace} | Emit OpenTelemetry trace spans |
|
|
52
|
+
*
|
|
53
|
+
* ## Utilities
|
|
54
|
+
*
|
|
55
|
+
* | Method | Purpose |
|
|
56
|
+
* |--------|---------|
|
|
57
|
+
* | {@link getContext} | Access workflow ID, namespace, replay state |
|
|
58
|
+
* | {@link random} | Deterministic pseudo-random numbers |
|
|
59
|
+
* | {@link all} | Workflow-safe `Promise.all` |
|
|
60
|
+
* | {@link didInterrupt} | Type guard for engine control-flow errors |
|
|
61
|
+
*
|
|
62
|
+
* ## Example
|
|
26
63
|
*
|
|
27
|
-
* @example
|
|
28
64
|
* ```typescript
|
|
29
65
|
* import { MemFlow } from '@hotmeshio/hotmesh';
|
|
66
|
+
* import * as activities from './activities';
|
|
67
|
+
*
|
|
68
|
+
* export async function orderWorkflow(orderId: string): Promise<string> {
|
|
69
|
+
* // Proxy activities for durable execution
|
|
70
|
+
* const { validateOrder, processPayment, sendReceipt } =
|
|
71
|
+
* MemFlow.workflow.proxyActivities<typeof activities>({
|
|
72
|
+
* activities,
|
|
73
|
+
* retryPolicy: { maximumAttempts: 3 },
|
|
74
|
+
* });
|
|
75
|
+
*
|
|
76
|
+
* await validateOrder(orderId);
|
|
77
|
+
*
|
|
78
|
+
* // Durable sleep (survives restarts)
|
|
79
|
+
* await MemFlow.workflow.sleepFor('5 seconds');
|
|
80
|
+
*
|
|
81
|
+
* const receipt = await processPayment(orderId);
|
|
82
|
+
*
|
|
83
|
+
* // Store searchable metadata
|
|
84
|
+
* await MemFlow.workflow.enrich({ orderId, status: 'paid' });
|
|
85
|
+
*
|
|
86
|
+
* // Wait for external approval signal
|
|
87
|
+
* const approval = await MemFlow.workflow.waitFor<{ ok: boolean }>('approve');
|
|
88
|
+
* if (!approval.ok) return 'cancelled';
|
|
30
89
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* MemFlow.workflow.waitFor<boolean>('my-sig-nal-1'),
|
|
34
|
-
* MemFlow.workflow.waitFor<number>('my-sig-nal-2')
|
|
35
|
-
* ]);
|
|
36
|
-
* return [s1, s2];
|
|
90
|
+
* await sendReceipt(orderId, receipt);
|
|
91
|
+
* return receipt;
|
|
37
92
|
* }
|
|
38
93
|
* ```
|
|
39
94
|
*/
|
|
@@ -23,20 +23,75 @@ const waitFor_1 = require("./waitFor");
|
|
|
23
23
|
const common_1 = require("./common");
|
|
24
24
|
const entityMethods_1 = require("./entityMethods");
|
|
25
25
|
/**
|
|
26
|
-
* The
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
* The workflow-internal API surface, exposed as `MemFlow.workflow`. Every
|
|
27
|
+
* method on this class is designed to be called **inside** a workflow
|
|
28
|
+
* function — they participate in deterministic replay and durable state
|
|
29
|
+
* management.
|
|
30
|
+
*
|
|
31
|
+
* ## Core Primitives
|
|
32
|
+
*
|
|
33
|
+
* | Method | Purpose |
|
|
34
|
+
* |--------|---------|
|
|
35
|
+
* | {@link proxyActivities} | Create durable activity proxies with retry |
|
|
36
|
+
* | {@link sleepFor} | Durable, crash-safe sleep |
|
|
37
|
+
* | {@link waitFor} | Pause until a signal is received |
|
|
38
|
+
* | {@link signal} | Send data to a waiting workflow |
|
|
39
|
+
* | {@link execChild} | Spawn and await a child workflow |
|
|
40
|
+
* | {@link startChild} | Spawn a child workflow (fire-and-forget) |
|
|
41
|
+
* | {@link execHook} | Spawn a hook and await its signal response |
|
|
42
|
+
* | {@link execHookBatch} | Spawn multiple hooks in parallel |
|
|
43
|
+
* | {@link hook} | Low-level hook spawning |
|
|
44
|
+
* | {@link interrupt} | Terminate a running workflow |
|
|
45
|
+
*
|
|
46
|
+
* ## Data & Observability
|
|
47
|
+
*
|
|
48
|
+
* | Method | Purpose |
|
|
49
|
+
* |--------|---------|
|
|
50
|
+
* | {@link search} | Read/write flat HASH key-value data |
|
|
51
|
+
* | {@link enrich} | One-shot HASH enrichment |
|
|
52
|
+
* | {@link entity} | Structured JSONB document storage |
|
|
53
|
+
* | {@link emit} | Publish events to the event bus |
|
|
54
|
+
* | {@link trace} | Emit OpenTelemetry trace spans |
|
|
55
|
+
*
|
|
56
|
+
* ## Utilities
|
|
57
|
+
*
|
|
58
|
+
* | Method | Purpose |
|
|
59
|
+
* |--------|---------|
|
|
60
|
+
* | {@link getContext} | Access workflow ID, namespace, replay state |
|
|
61
|
+
* | {@link random} | Deterministic pseudo-random numbers |
|
|
62
|
+
* | {@link all} | Workflow-safe `Promise.all` |
|
|
63
|
+
* | {@link didInterrupt} | Type guard for engine control-flow errors |
|
|
64
|
+
*
|
|
65
|
+
* ## Example
|
|
29
66
|
*
|
|
30
|
-
* @example
|
|
31
67
|
* ```typescript
|
|
32
68
|
* import { MemFlow } from '@hotmeshio/hotmesh';
|
|
69
|
+
* import * as activities from './activities';
|
|
70
|
+
*
|
|
71
|
+
* export async function orderWorkflow(orderId: string): Promise<string> {
|
|
72
|
+
* // Proxy activities for durable execution
|
|
73
|
+
* const { validateOrder, processPayment, sendReceipt } =
|
|
74
|
+
* MemFlow.workflow.proxyActivities<typeof activities>({
|
|
75
|
+
* activities,
|
|
76
|
+
* retryPolicy: { maximumAttempts: 3 },
|
|
77
|
+
* });
|
|
78
|
+
*
|
|
79
|
+
* await validateOrder(orderId);
|
|
80
|
+
*
|
|
81
|
+
* // Durable sleep (survives restarts)
|
|
82
|
+
* await MemFlow.workflow.sleepFor('5 seconds');
|
|
83
|
+
*
|
|
84
|
+
* const receipt = await processPayment(orderId);
|
|
85
|
+
*
|
|
86
|
+
* // Store searchable metadata
|
|
87
|
+
* await MemFlow.workflow.enrich({ orderId, status: 'paid' });
|
|
88
|
+
*
|
|
89
|
+
* // Wait for external approval signal
|
|
90
|
+
* const approval = await MemFlow.workflow.waitFor<{ ok: boolean }>('approve');
|
|
91
|
+
* if (!approval.ok) return 'cancelled';
|
|
33
92
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* MemFlow.workflow.waitFor<boolean>('my-sig-nal-1'),
|
|
37
|
-
* MemFlow.workflow.waitFor<number>('my-sig-nal-2')
|
|
38
|
-
* ]);
|
|
39
|
-
* return [s1, s2];
|
|
93
|
+
* await sendReceipt(orderId, receipt);
|
|
94
|
+
* return receipt;
|
|
40
95
|
* }
|
|
41
96
|
* ```
|
|
42
97
|
*/
|
|
@@ -1,9 +1,55 @@
|
|
|
1
1
|
import { JobInterruptOptions } from './common';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Terminates a running workflow job by its ID. The target job's status
|
|
4
|
+
* is set to an error code indicating abnormal termination, and any
|
|
5
|
+
* pending activities or timers are cancelled.
|
|
4
6
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* This is the workflow-internal interrupt — it can only be called from
|
|
8
|
+
* within a workflow function. For external interruption, use
|
|
9
|
+
* `hotMesh.interrupt()` directly.
|
|
10
|
+
*
|
|
11
|
+
* The interrupt fires exactly once per workflow execution — the
|
|
12
|
+
* `isSideEffectAllowed` guard prevents re-interrupting on replay.
|
|
13
|
+
*
|
|
14
|
+
* ## Examples
|
|
15
|
+
*
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { MemFlow } from '@hotmeshio/hotmesh';
|
|
18
|
+
*
|
|
19
|
+
* // Cancel a child workflow from the parent
|
|
20
|
+
* export async function supervisorWorkflow(): Promise<void> {
|
|
21
|
+
* const childId = await MemFlow.workflow.startChild({
|
|
22
|
+
* taskQueue: 'workers',
|
|
23
|
+
* workflowName: 'longTask',
|
|
24
|
+
* args: [],
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // Wait for a timeout, then cancel the child
|
|
28
|
+
* await MemFlow.workflow.sleepFor('5 minutes');
|
|
29
|
+
* await MemFlow.workflow.interrupt(childId, {
|
|
30
|
+
* reason: 'Timed out waiting for child',
|
|
31
|
+
* descend: true, // also interrupt any grandchild workflows
|
|
32
|
+
* });
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* ```typescript
|
|
37
|
+
* // Self-interrupt on validation failure
|
|
38
|
+
* export async function validatedWorkflow(input: string): Promise<void> {
|
|
39
|
+
* const { workflowId } = MemFlow.workflow.getContext();
|
|
40
|
+
* const { validate } = MemFlow.workflow.proxyActivities<typeof activities>();
|
|
41
|
+
*
|
|
42
|
+
* const isValid = await validate(input);
|
|
43
|
+
* if (!isValid) {
|
|
44
|
+
* await MemFlow.workflow.interrupt(workflowId, {
|
|
45
|
+
* reason: 'Invalid input',
|
|
46
|
+
* });
|
|
47
|
+
* }
|
|
48
|
+
* }
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @param {string} jobId - The ID of the workflow job to interrupt.
|
|
52
|
+
* @param {JobInterruptOptions} [options={}] - Interruption options (`reason`, `descend`, etc.).
|
|
53
|
+
* @returns {Promise<string | void>} The result of the interruption, if any.
|
|
8
54
|
*/
|
|
9
55
|
export declare function interrupt(jobId: string, options?: JobInterruptOptions): Promise<string | void>;
|
|
@@ -5,11 +5,57 @@ const common_1 = require("./common");
|
|
|
5
5
|
const context_1 = require("./context");
|
|
6
6
|
const isSideEffectAllowed_1 = require("./isSideEffectAllowed");
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* Terminates a running workflow job by its ID. The target job's status
|
|
9
|
+
* is set to an error code indicating abnormal termination, and any
|
|
10
|
+
* pending activities or timers are cancelled.
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* This is the workflow-internal interrupt — it can only be called from
|
|
13
|
+
* within a workflow function. For external interruption, use
|
|
14
|
+
* `hotMesh.interrupt()` directly.
|
|
15
|
+
*
|
|
16
|
+
* The interrupt fires exactly once per workflow execution — the
|
|
17
|
+
* `isSideEffectAllowed` guard prevents re-interrupting on replay.
|
|
18
|
+
*
|
|
19
|
+
* ## Examples
|
|
20
|
+
*
|
|
21
|
+
* ```typescript
|
|
22
|
+
* import { MemFlow } from '@hotmeshio/hotmesh';
|
|
23
|
+
*
|
|
24
|
+
* // Cancel a child workflow from the parent
|
|
25
|
+
* export async function supervisorWorkflow(): Promise<void> {
|
|
26
|
+
* const childId = await MemFlow.workflow.startChild({
|
|
27
|
+
* taskQueue: 'workers',
|
|
28
|
+
* workflowName: 'longTask',
|
|
29
|
+
* args: [],
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* // Wait for a timeout, then cancel the child
|
|
33
|
+
* await MemFlow.workflow.sleepFor('5 minutes');
|
|
34
|
+
* await MemFlow.workflow.interrupt(childId, {
|
|
35
|
+
* reason: 'Timed out waiting for child',
|
|
36
|
+
* descend: true, // also interrupt any grandchild workflows
|
|
37
|
+
* });
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* ```typescript
|
|
42
|
+
* // Self-interrupt on validation failure
|
|
43
|
+
* export async function validatedWorkflow(input: string): Promise<void> {
|
|
44
|
+
* const { workflowId } = MemFlow.workflow.getContext();
|
|
45
|
+
* const { validate } = MemFlow.workflow.proxyActivities<typeof activities>();
|
|
46
|
+
*
|
|
47
|
+
* const isValid = await validate(input);
|
|
48
|
+
* if (!isValid) {
|
|
49
|
+
* await MemFlow.workflow.interrupt(workflowId, {
|
|
50
|
+
* reason: 'Invalid input',
|
|
51
|
+
* });
|
|
52
|
+
* }
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* @param {string} jobId - The ID of the workflow job to interrupt.
|
|
57
|
+
* @param {JobInterruptOptions} [options={}] - Interruption options (`reason`, `descend`, etc.).
|
|
58
|
+
* @returns {Promise<string | void>} The result of the interruption, if any.
|
|
13
59
|
*/
|
|
14
60
|
async function interrupt(jobId, options = {}) {
|
|
15
61
|
const { workflowTopic, connection, namespace } = (0, context_1.getContext)();
|
|
@@ -1,28 +1,61 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Type guard that returns `true` if an error is a MemFlow engine
|
|
3
|
+
* control-flow signal rather than a genuine application error.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* MemFlow uses thrown errors internally to suspend workflow execution
|
|
6
|
+
* for durable operations like `sleepFor`, `waitFor`, `proxyActivities`,
|
|
7
|
+
* and `execChild`. These errors must be re-thrown (not swallowed) so
|
|
8
|
+
* the engine can persist state and schedule the next step.
|
|
9
|
+
*
|
|
10
|
+
* **Always use `didInterrupt` in `catch` blocks inside workflow
|
|
11
|
+
* functions** to avoid accidentally swallowing engine signals.
|
|
12
|
+
*
|
|
13
|
+
* ## Recognized Error Types
|
|
14
|
+
*
|
|
15
|
+
* `MemFlowChildError`, `MemFlowFatalError`, `MemFlowMaxedError`,
|
|
16
|
+
* `MemFlowProxyError`, `MemFlowRetryError`, `MemFlowSleepError`,
|
|
17
|
+
* `MemFlowTimeoutError`, `MemFlowWaitForError`, `MemFlowWaitForAllError`
|
|
18
|
+
*
|
|
19
|
+
* ## Examples
|
|
7
20
|
*
|
|
8
|
-
* @example
|
|
9
21
|
* ```typescript
|
|
10
22
|
* import { MemFlow } from '@hotmeshio/hotmesh';
|
|
11
23
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* throw
|
|
24
|
+
* export async function safeWorkflow(): Promise<string> {
|
|
25
|
+
* const { riskyOperation } = MemFlow.workflow.proxyActivities<typeof activities>();
|
|
26
|
+
*
|
|
27
|
+
* try {
|
|
28
|
+
* return await riskyOperation();
|
|
29
|
+
* } catch (error) {
|
|
30
|
+
* // CRITICAL: re-throw engine signals
|
|
31
|
+
* if (MemFlow.workflow.didInterrupt(error)) {
|
|
32
|
+
* throw error;
|
|
33
|
+
* }
|
|
34
|
+
* // Handle real application errors
|
|
35
|
+
* return 'fallback-value';
|
|
19
36
|
* }
|
|
20
|
-
* // Handle actual error
|
|
21
|
-
* console.error('Workflow failed:', error);
|
|
22
37
|
* }
|
|
23
38
|
* ```
|
|
24
39
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
40
|
+
* ```typescript
|
|
41
|
+
* // Common pattern in interceptors
|
|
42
|
+
* const interceptor: WorkflowInterceptor = {
|
|
43
|
+
* async execute(ctx, next) {
|
|
44
|
+
* try {
|
|
45
|
+
* return await next();
|
|
46
|
+
* } catch (error) {
|
|
47
|
+
* if (MemFlow.workflow.didInterrupt(error)) {
|
|
48
|
+
* throw error; // always re-throw engine signals
|
|
49
|
+
* }
|
|
50
|
+
* // Log and re-throw application errors
|
|
51
|
+
* console.error('Workflow failed:', error);
|
|
52
|
+
* throw error;
|
|
53
|
+
* }
|
|
54
|
+
* },
|
|
55
|
+
* };
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @param {Error} error - The error to check.
|
|
59
|
+
* @returns {boolean} `true` if the error is a MemFlow engine interruption signal.
|
|
27
60
|
*/
|
|
28
61
|
export declare function didInterrupt(error: Error): boolean;
|