@hotmeshio/hotmesh 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/README.md +178 -43
  2. package/build/index.d.ts +12 -11
  3. package/build/index.js +15 -13
  4. package/build/modules/enums.d.ts +23 -34
  5. package/build/modules/enums.js +26 -38
  6. package/build/modules/errors.d.ts +16 -16
  7. package/build/modules/errors.js +37 -37
  8. package/build/package.json +63 -67
  9. package/build/services/activities/activity.d.ts +58 -7
  10. package/build/services/activities/activity.js +67 -38
  11. package/build/services/activities/await.d.ts +101 -0
  12. package/build/services/activities/await.js +101 -0
  13. package/build/services/activities/cycle.d.ts +82 -0
  14. package/build/services/activities/cycle.js +86 -8
  15. package/build/services/activities/hook.d.ts +139 -1
  16. package/build/services/activities/hook.js +140 -2
  17. package/build/services/activities/interrupt.d.ts +112 -0
  18. package/build/services/activities/interrupt.js +118 -5
  19. package/build/services/activities/signal.d.ts +108 -3
  20. package/build/services/activities/signal.js +113 -8
  21. package/build/services/activities/trigger.d.ts +56 -4
  22. package/build/services/activities/trigger.js +119 -35
  23. package/build/services/activities/worker.d.ts +107 -0
  24. package/build/services/activities/worker.js +107 -0
  25. package/build/services/collator/index.d.ts +3 -15
  26. package/build/services/collator/index.js +7 -34
  27. package/build/services/dba/index.d.ts +171 -0
  28. package/build/services/dba/index.js +280 -0
  29. package/build/services/{memflow → durable}/client.d.ts +3 -3
  30. package/build/services/{memflow → durable}/client.js +15 -15
  31. package/build/services/{memflow → durable}/connection.d.ts +2 -2
  32. package/build/services/{memflow → durable}/connection.js +1 -1
  33. package/build/services/{memflow → durable}/exporter.d.ts +6 -6
  34. package/build/services/{memflow → durable}/exporter.js +2 -2
  35. package/build/services/{memflow → durable}/handle.d.ts +4 -4
  36. package/build/services/{memflow → durable}/handle.js +3 -3
  37. package/build/services/{memflow → durable}/index.d.ts +126 -34
  38. package/build/services/{memflow → durable}/index.js +146 -50
  39. package/build/services/{memflow → durable}/interceptor.d.ts +45 -22
  40. package/build/services/{memflow → durable}/interceptor.js +54 -21
  41. package/build/services/{memflow → durable}/schemas/factory.d.ts +4 -4
  42. package/build/services/{memflow → durable}/schemas/factory.js +5 -5
  43. package/build/services/{memflow → durable}/search.d.ts +1 -1
  44. package/build/services/{memflow → durable}/search.js +4 -4
  45. package/build/services/{memflow → durable}/worker.d.ts +11 -11
  46. package/build/services/{memflow → durable}/worker.js +61 -60
  47. package/build/services/durable/workflow/all.d.ts +32 -0
  48. package/build/services/durable/workflow/all.js +40 -0
  49. package/build/services/{memflow → durable}/workflow/common.d.ts +5 -5
  50. package/build/services/durable/workflow/common.js +47 -0
  51. package/build/services/durable/workflow/context.d.ts +49 -0
  52. package/build/services/durable/workflow/context.js +88 -0
  53. package/build/services/durable/workflow/didRun.d.ts +27 -0
  54. package/build/services/durable/workflow/didRun.js +42 -0
  55. package/build/services/durable/workflow/emit.d.ts +50 -0
  56. package/build/services/durable/workflow/emit.js +68 -0
  57. package/build/services/durable/workflow/enrich.d.ts +37 -0
  58. package/build/services/durable/workflow/enrich.js +45 -0
  59. package/build/services/durable/workflow/entityMethods.d.ts +61 -0
  60. package/build/services/durable/workflow/entityMethods.js +80 -0
  61. package/build/services/durable/workflow/execChild.d.ts +106 -0
  62. package/build/services/durable/workflow/execChild.js +194 -0
  63. package/build/services/durable/workflow/execHook.d.ts +80 -0
  64. package/build/services/durable/workflow/execHook.js +97 -0
  65. package/build/services/durable/workflow/execHookBatch.d.ts +107 -0
  66. package/build/services/durable/workflow/execHookBatch.js +129 -0
  67. package/build/services/durable/workflow/hook.d.ts +74 -0
  68. package/build/services/durable/workflow/hook.js +123 -0
  69. package/build/services/durable/workflow/index.d.ts +129 -0
  70. package/build/services/{memflow → durable}/workflow/index.js +66 -11
  71. package/build/services/durable/workflow/interrupt.d.ts +55 -0
  72. package/build/services/durable/workflow/interrupt.js +70 -0
  73. package/build/services/durable/workflow/interruption.d.ts +61 -0
  74. package/build/services/durable/workflow/interruption.js +76 -0
  75. package/build/services/durable/workflow/isSideEffectAllowed.d.ts +27 -0
  76. package/build/services/{memflow → durable}/workflow/isSideEffectAllowed.js +21 -4
  77. package/build/services/durable/workflow/proxyActivities.d.ts +119 -0
  78. package/build/services/durable/workflow/proxyActivities.js +214 -0
  79. package/build/services/durable/workflow/random.d.ts +36 -0
  80. package/build/services/durable/workflow/random.js +46 -0
  81. package/build/services/durable/workflow/searchMethods.d.ts +53 -0
  82. package/build/services/durable/workflow/searchMethods.js +72 -0
  83. package/build/services/durable/workflow/signal.d.ts +58 -0
  84. package/build/services/durable/workflow/signal.js +79 -0
  85. package/build/services/durable/workflow/sleepFor.d.ts +63 -0
  86. package/build/services/durable/workflow/sleepFor.js +91 -0
  87. package/build/services/durable/workflow/trace.d.ts +47 -0
  88. package/build/services/durable/workflow/trace.js +66 -0
  89. package/build/services/durable/workflow/waitFor.d.ts +66 -0
  90. package/build/services/durable/workflow/waitFor.js +93 -0
  91. package/build/services/engine/index.d.ts +18 -2
  92. package/build/services/engine/index.js +14 -4
  93. package/build/services/exporter/index.d.ts +2 -0
  94. package/build/services/exporter/index.js +1 -0
  95. package/build/services/hotmesh/index.d.ts +471 -236
  96. package/build/services/hotmesh/index.js +473 -238
  97. package/build/services/store/index.d.ts +1 -1
  98. package/build/services/store/providers/postgres/postgres.d.ts +1 -1
  99. package/build/services/store/providers/postgres/postgres.js +4 -3
  100. package/build/services/telemetry/index.js +6 -0
  101. package/build/services/{meshcall → virtual}/index.d.ts +29 -29
  102. package/build/services/{meshcall → virtual}/index.js +49 -49
  103. package/build/services/{meshcall → virtual}/schemas/factory.d.ts +1 -1
  104. package/build/services/{meshcall → virtual}/schemas/factory.js +1 -1
  105. package/build/types/activity.d.ts +1 -1
  106. package/build/types/dba.d.ts +64 -0
  107. package/build/types/{memflow.d.ts → durable.d.ts} +75 -19
  108. package/build/types/error.d.ts +5 -5
  109. package/build/types/exporter.d.ts +1 -1
  110. package/build/types/hotmesh.d.ts +1 -1
  111. package/build/types/index.d.ts +5 -4
  112. package/build/types/job.d.ts +1 -1
  113. package/build/types/quorum.d.ts +2 -2
  114. package/build/types/{meshcall.d.ts → virtual.d.ts} +15 -15
  115. package/build/types/virtual.js +2 -0
  116. package/index.ts +15 -13
  117. package/package.json +63 -67
  118. package/vitest.config.ts +17 -0
  119. package/.claude/settings.local.json +0 -7
  120. package/build/services/memflow/workflow/all.d.ts +0 -7
  121. package/build/services/memflow/workflow/all.js +0 -15
  122. package/build/services/memflow/workflow/common.js +0 -47
  123. package/build/services/memflow/workflow/context.d.ts +0 -6
  124. package/build/services/memflow/workflow/context.js +0 -45
  125. package/build/services/memflow/workflow/didRun.d.ts +0 -7
  126. package/build/services/memflow/workflow/didRun.js +0 -22
  127. package/build/services/memflow/workflow/emit.d.ts +0 -11
  128. package/build/services/memflow/workflow/emit.js +0 -29
  129. package/build/services/memflow/workflow/enrich.d.ts +0 -9
  130. package/build/services/memflow/workflow/enrich.js +0 -17
  131. package/build/services/memflow/workflow/entityMethods.d.ts +0 -14
  132. package/build/services/memflow/workflow/entityMethods.js +0 -33
  133. package/build/services/memflow/workflow/execChild.d.ts +0 -18
  134. package/build/services/memflow/workflow/execChild.js +0 -106
  135. package/build/services/memflow/workflow/execHook.d.ts +0 -65
  136. package/build/services/memflow/workflow/execHook.js +0 -83
  137. package/build/services/memflow/workflow/execHookBatch.d.ts +0 -54
  138. package/build/services/memflow/workflow/execHookBatch.js +0 -77
  139. package/build/services/memflow/workflow/hook.d.ts +0 -9
  140. package/build/services/memflow/workflow/hook.js +0 -58
  141. package/build/services/memflow/workflow/index.d.ts +0 -74
  142. package/build/services/memflow/workflow/interrupt.d.ts +0 -9
  143. package/build/services/memflow/workflow/interrupt.js +0 -24
  144. package/build/services/memflow/workflow/interruption.d.ts +0 -28
  145. package/build/services/memflow/workflow/interruption.js +0 -43
  146. package/build/services/memflow/workflow/isSideEffectAllowed.d.ts +0 -10
  147. package/build/services/memflow/workflow/proxyActivities.d.ts +0 -91
  148. package/build/services/memflow/workflow/proxyActivities.js +0 -176
  149. package/build/services/memflow/workflow/random.d.ts +0 -6
  150. package/build/services/memflow/workflow/random.js +0 -16
  151. package/build/services/memflow/workflow/searchMethods.d.ts +0 -6
  152. package/build/services/memflow/workflow/searchMethods.js +0 -25
  153. package/build/services/memflow/workflow/signal.d.ts +0 -29
  154. package/build/services/memflow/workflow/signal.js +0 -50
  155. package/build/services/memflow/workflow/sleepFor.d.ts +0 -24
  156. package/build/services/memflow/workflow/sleepFor.js +0 -52
  157. package/build/services/memflow/workflow/trace.d.ts +0 -14
  158. package/build/services/memflow/workflow/trace.js +0 -33
  159. package/build/services/memflow/workflow/waitFor.d.ts +0 -29
  160. package/build/services/memflow/workflow/waitFor.js +0 -56
  161. /package/build/services/{memflow → durable}/entity.d.ts +0 -0
  162. /package/build/services/{memflow → durable}/entity.js +0 -0
  163. /package/build/types/{memflow.js → dba.js} +0 -0
  164. /package/build/types/{meshcall.js → durable.js} +0 -0
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hook = void 0;
4
+ const common_1 = require("./common");
5
+ const context_1 = require("./context");
6
+ const isSideEffectAllowed_1 = require("./isSideEffectAllowed");
7
+ /**
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.
12
+ *
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 { Durable } 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 Durable.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 Durable.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 Durable.workflow.waitFor<string>(signalId);
59
+ * }
60
+ * ```
61
+ *
62
+ * ```typescript
63
+ * // Hook with retry configuration
64
+ * await Durable.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.
77
+ * @returns {Promise<string>} The resulting hook/stream ID.
78
+ */
79
+ async function hook(options) {
80
+ const { workflowId, connection, namespace, workflowTopic } = (0, context_1.getContext)();
81
+ const hotMeshClient = await common_1.WorkerService.getHotMesh(workflowTopic, {
82
+ connection,
83
+ namespace,
84
+ });
85
+ if (await (0, isSideEffectAllowed_1.isSideEffectAllowed)(hotMeshClient, 'hook')) {
86
+ const targetWorkflowId = options.workflowId ?? workflowId;
87
+ let targetTopic;
88
+ if (options.entity || (options.taskQueue && options.workflowName)) {
89
+ targetTopic = `${options.taskQueue ?? options.entity}-${options.entity ?? options.workflowName}`;
90
+ }
91
+ else {
92
+ targetTopic = workflowTopic;
93
+ }
94
+ // DEFENSIVE CHECK: Prevent infinite loops
95
+ if (targetTopic === workflowTopic &&
96
+ !options.entity &&
97
+ !options.taskQueue) {
98
+ throw new Error(`Durable Hook Error: Potential infinite loop detected!\n\n` +
99
+ `The hook would target the same workflow topic ('${workflowTopic}') as the current workflow, ` +
100
+ `creating an infinite loop.\n\n` +
101
+ `To fix this, provide either:\n` +
102
+ `1. 'taskQueue' parameter: Durable.workflow.hook({ taskQueue: 'your-queue', workflowName: '${options.workflowName}', args: [...] })\n` +
103
+ `2. 'entity' parameter: Durable.workflow.hook({ entity: 'your-entity', args: [...] })\n\n` +
104
+ `Current workflow topic: ${workflowTopic}\n` +
105
+ `Target topic would be: ${targetTopic}\n` +
106
+ `Provided options: ${JSON.stringify({
107
+ workflowName: options.workflowName,
108
+ taskQueue: options.taskQueue,
109
+ entity: options.entity,
110
+ }, null, 2)}`);
111
+ }
112
+ const payload = {
113
+ arguments: [...options.args],
114
+ id: targetWorkflowId,
115
+ workflowTopic: targetTopic,
116
+ backoffCoefficient: options.config?.backoffCoefficient || common_1.HMSH_DURABLE_EXP_BACKOFF,
117
+ maximumAttempts: options.config?.maximumAttempts || common_1.HMSH_DURABLE_MAX_ATTEMPTS,
118
+ maximumInterval: (0, common_1.s)(options?.config?.maximumInterval ?? common_1.HMSH_DURABLE_MAX_INTERVAL),
119
+ };
120
+ return await hotMeshClient.signal(`${namespace}.flow.signal`, payload, common_1.StreamStatus.PENDING, 202);
121
+ }
122
+ }
123
+ exports.hook = hook;
@@ -0,0 +1,129 @@
1
+ import { getContext } from './context';
2
+ import { didRun } from './didRun';
3
+ import { isSideEffectAllowed } from './isSideEffectAllowed';
4
+ import { trace } from './trace';
5
+ import { enrich } from './enrich';
6
+ import { emit } from './emit';
7
+ import { execChild, startChild } from './execChild';
8
+ import { execHook } from './execHook';
9
+ import { execHookBatch } from './execHookBatch';
10
+ import { proxyActivities } from './proxyActivities';
11
+ import { search } from './searchMethods';
12
+ import { random } from './random';
13
+ import { signal } from './signal';
14
+ import { hook } from './hook';
15
+ import { interrupt } from './interrupt';
16
+ import { didInterrupt } from './interruption';
17
+ import { all } from './all';
18
+ import { sleepFor } from './sleepFor';
19
+ import { waitFor } from './waitFor';
20
+ import { HotMesh } from './common';
21
+ import { entity } from './entityMethods';
22
+ /**
23
+ * The workflow-internal API surface, exposed as `Durable.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
63
+ *
64
+ * ```typescript
65
+ * import { Durable } 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
+ * Durable.workflow.proxyActivities<typeof activities>({
72
+ * activities,
73
+ * retryPolicy: { maximumAttempts: 3 },
74
+ * });
75
+ *
76
+ * await validateOrder(orderId);
77
+ *
78
+ * // Durable sleep (survives restarts)
79
+ * await Durable.workflow.sleepFor('5 seconds');
80
+ *
81
+ * const receipt = await processPayment(orderId);
82
+ *
83
+ * // Store searchable metadata
84
+ * await Durable.workflow.enrich({ orderId, status: 'paid' });
85
+ *
86
+ * // Wait for external approval signal
87
+ * const approval = await Durable.workflow.waitFor<{ ok: boolean }>('approve');
88
+ * if (!approval.ok) return 'cancelled';
89
+ *
90
+ * await sendReceipt(orderId, receipt);
91
+ * return receipt;
92
+ * }
93
+ * ```
94
+ */
95
+ export declare class WorkflowService {
96
+ /**
97
+ * @private
98
+ * The constructor is private to prevent instantiation;
99
+ * all methods are static.
100
+ */
101
+ private constructor();
102
+ static getContext: typeof getContext;
103
+ static didRun: typeof didRun;
104
+ static isSideEffectAllowed: typeof isSideEffectAllowed;
105
+ static trace: typeof trace;
106
+ static enrich: typeof enrich;
107
+ static emit: typeof emit;
108
+ static execChild: typeof execChild;
109
+ static executeChild: typeof execChild;
110
+ static startChild: typeof startChild;
111
+ static execHook: typeof execHook;
112
+ static execHookBatch: typeof execHookBatch;
113
+ static proxyActivities: typeof proxyActivities;
114
+ static search: typeof search;
115
+ static entity: typeof entity;
116
+ static random: typeof random;
117
+ static signal: typeof signal;
118
+ static hook: typeof hook;
119
+ static didInterrupt: typeof didInterrupt;
120
+ static interrupt: typeof interrupt;
121
+ static all: typeof all;
122
+ static sleepFor: typeof sleepFor;
123
+ static waitFor: typeof waitFor;
124
+ /**
125
+ * Return a handle to the HotMesh client hosting the workflow execution.
126
+ * @returns {Promise<HotMesh>} The HotMesh client instance.
127
+ */
128
+ static getHotMesh(): Promise<HotMesh>;
129
+ }
@@ -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 WorkflowService class provides a set of static methods to be used within a workflow function.
27
- * These methods ensure deterministic replay, persistence of state, and error handling across
28
- * re-entrant workflow executions.
26
+ * The workflow-internal API surface, exposed as `Durable.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
- * import { MemFlow } from '@hotmeshio/hotmesh';
68
+ * import { Durable } 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
+ * Durable.workflow.proxyActivities<typeof activities>({
75
+ * activities,
76
+ * retryPolicy: { maximumAttempts: 3 },
77
+ * });
78
+ *
79
+ * await validateOrder(orderId);
80
+ *
81
+ * // Durable sleep (survives restarts)
82
+ * await Durable.workflow.sleepFor('5 seconds');
83
+ *
84
+ * const receipt = await processPayment(orderId);
85
+ *
86
+ * // Store searchable metadata
87
+ * await Durable.workflow.enrich({ orderId, status: 'paid' });
88
+ *
89
+ * // Wait for external approval signal
90
+ * const approval = await Durable.workflow.waitFor<{ ok: boolean }>('approve');
91
+ * if (!approval.ok) return 'cancelled';
33
92
  *
34
- * export async function waitForExample(): Promise<[boolean, number]> {
35
- * const [s1, s2] = await Promise.all([
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
  */
@@ -0,0 +1,55 @@
1
+ import { JobInterruptOptions } from './common';
2
+ /**
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.
6
+ *
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 { Durable } from '@hotmeshio/hotmesh';
18
+ *
19
+ * // Cancel a child workflow from the parent
20
+ * export async function supervisorWorkflow(): Promise<void> {
21
+ * const childId = await Durable.workflow.startChild({
22
+ * taskQueue: 'workers',
23
+ * workflowName: 'longTask',
24
+ * args: [],
25
+ * });
26
+ *
27
+ * // Wait for a timeout, then cancel the child
28
+ * await Durable.workflow.sleepFor('5 minutes');
29
+ * await Durable.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 } = Durable.workflow.getContext();
40
+ * const { validate } = Durable.workflow.proxyActivities<typeof activities>();
41
+ *
42
+ * const isValid = await validate(input);
43
+ * if (!isValid) {
44
+ * await Durable.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.
54
+ */
55
+ export declare function interrupt(jobId: string, options?: JobInterruptOptions): Promise<string | void>;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.interrupt = void 0;
4
+ const common_1 = require("./common");
5
+ const context_1 = require("./context");
6
+ const isSideEffectAllowed_1 = require("./isSideEffectAllowed");
7
+ /**
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.
11
+ *
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 { Durable } from '@hotmeshio/hotmesh';
23
+ *
24
+ * // Cancel a child workflow from the parent
25
+ * export async function supervisorWorkflow(): Promise<void> {
26
+ * const childId = await Durable.workflow.startChild({
27
+ * taskQueue: 'workers',
28
+ * workflowName: 'longTask',
29
+ * args: [],
30
+ * });
31
+ *
32
+ * // Wait for a timeout, then cancel the child
33
+ * await Durable.workflow.sleepFor('5 minutes');
34
+ * await Durable.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 } = Durable.workflow.getContext();
45
+ * const { validate } = Durable.workflow.proxyActivities<typeof activities>();
46
+ *
47
+ * const isValid = await validate(input);
48
+ * if (!isValid) {
49
+ * await Durable.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.
59
+ */
60
+ async function interrupt(jobId, options = {}) {
61
+ const { workflowTopic, connection, namespace } = (0, context_1.getContext)();
62
+ const hotMeshClient = await common_1.WorkerService.getHotMesh(workflowTopic, {
63
+ connection,
64
+ namespace,
65
+ });
66
+ if (await (0, isSideEffectAllowed_1.isSideEffectAllowed)(hotMeshClient, 'interrupt')) {
67
+ return await hotMeshClient.interrupt(`${hotMeshClient.appId}.execute`, jobId, options);
68
+ }
69
+ }
70
+ exports.interrupt = interrupt;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Type guard that returns `true` if an error is a Durable engine
3
+ * control-flow signal rather than a genuine application error.
4
+ *
5
+ * Durable 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
+ * `DurableChildError`, `DurableFatalError`, `DurableMaxedError`,
16
+ * `DurableProxyError`, `DurableRetryError`, `DurableSleepError`,
17
+ * `DurableTimeoutError`, `DurableWaitForError`, `DurableWaitForAllError`
18
+ *
19
+ * ## Examples
20
+ *
21
+ * ```typescript
22
+ * import { Durable } from '@hotmeshio/hotmesh';
23
+ *
24
+ * export async function safeWorkflow(): Promise<string> {
25
+ * const { riskyOperation } = Durable.workflow.proxyActivities<typeof activities>();
26
+ *
27
+ * try {
28
+ * return await riskyOperation();
29
+ * } catch (error) {
30
+ * // CRITICAL: re-throw engine signals
31
+ * if (Durable.workflow.didInterrupt(error)) {
32
+ * throw error;
33
+ * }
34
+ * // Handle real application errors
35
+ * return 'fallback-value';
36
+ * }
37
+ * }
38
+ * ```
39
+ *
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 (Durable.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 Durable engine interruption signal.
60
+ */
61
+ export declare function didInterrupt(error: Error): boolean;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.didInterrupt = void 0;
4
+ const errors_1 = require("../../../modules/errors");
5
+ /**
6
+ * Type guard that returns `true` if an error is a Durable engine
7
+ * control-flow signal rather than a genuine application error.
8
+ *
9
+ * Durable 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
+ * `DurableChildError`, `DurableFatalError`, `DurableMaxedError`,
20
+ * `DurableProxyError`, `DurableRetryError`, `DurableSleepError`,
21
+ * `DurableTimeoutError`, `DurableWaitForError`, `DurableWaitForAllError`
22
+ *
23
+ * ## Examples
24
+ *
25
+ * ```typescript
26
+ * import { Durable } from '@hotmeshio/hotmesh';
27
+ *
28
+ * export async function safeWorkflow(): Promise<string> {
29
+ * const { riskyOperation } = Durable.workflow.proxyActivities<typeof activities>();
30
+ *
31
+ * try {
32
+ * return await riskyOperation();
33
+ * } catch (error) {
34
+ * // CRITICAL: re-throw engine signals
35
+ * if (Durable.workflow.didInterrupt(error)) {
36
+ * throw error;
37
+ * }
38
+ * // Handle real application errors
39
+ * return 'fallback-value';
40
+ * }
41
+ * }
42
+ * ```
43
+ *
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 (Durable.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 Durable engine interruption signal.
64
+ */
65
+ function didInterrupt(error) {
66
+ return (error instanceof errors_1.DurableChildError ||
67
+ error instanceof errors_1.DurableFatalError ||
68
+ error instanceof errors_1.DurableMaxedError ||
69
+ error instanceof errors_1.DurableProxyError ||
70
+ error instanceof errors_1.DurableRetryError ||
71
+ error instanceof errors_1.DurableSleepError ||
72
+ error instanceof errors_1.DurableTimeoutError ||
73
+ error instanceof errors_1.DurableWaitForError ||
74
+ error instanceof errors_1.DurableWaitForAllError);
75
+ }
76
+ exports.didInterrupt = didInterrupt;
@@ -0,0 +1,27 @@
1
+ import { HotMesh } from './common';
2
+ /**
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
+ *
22
+ * @private
23
+ * @param {HotMesh} hotMeshClient - The HotMesh client.
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.
26
+ */
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
- * Checks if a side-effect is allowed to run. This ensures certain actions
7
- * are executed exactly once.
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 - A unique prefix representing the action (e.g., 'trace', 'emit', etc.)
11
- * @returns {Promise<boolean>} True if the side effect can run, false otherwise.
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();