@hotmeshio/hotmesh 0.8.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.
Files changed (86) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/README.md +158 -38
  3. package/build/package.json +62 -67
  4. package/build/services/activities/activity.d.ts +58 -7
  5. package/build/services/activities/activity.js +66 -37
  6. package/build/services/activities/await.d.ts +101 -0
  7. package/build/services/activities/await.js +101 -0
  8. package/build/services/activities/cycle.d.ts +82 -0
  9. package/build/services/activities/cycle.js +86 -8
  10. package/build/services/activities/hook.d.ts +139 -1
  11. package/build/services/activities/hook.js +140 -2
  12. package/build/services/activities/interrupt.d.ts +112 -0
  13. package/build/services/activities/interrupt.js +118 -5
  14. package/build/services/activities/signal.d.ts +108 -3
  15. package/build/services/activities/signal.js +113 -8
  16. package/build/services/activities/trigger.d.ts +56 -4
  17. package/build/services/activities/trigger.js +119 -35
  18. package/build/services/activities/worker.d.ts +107 -0
  19. package/build/services/activities/worker.js +107 -0
  20. package/build/services/collator/index.d.ts +3 -15
  21. package/build/services/collator/index.js +7 -34
  22. package/build/services/engine/index.d.ts +18 -2
  23. package/build/services/engine/index.js +14 -4
  24. package/build/services/exporter/index.d.ts +2 -0
  25. package/build/services/exporter/index.js +1 -0
  26. package/build/services/hotmesh/index.d.ts +471 -236
  27. package/build/services/hotmesh/index.js +473 -238
  28. package/build/services/memflow/client.js +2 -2
  29. package/build/services/memflow/handle.js +1 -1
  30. package/build/services/memflow/index.d.ts +1 -1
  31. package/build/services/memflow/index.js +1 -1
  32. package/build/services/memflow/workflow/all.d.ts +28 -3
  33. package/build/services/memflow/workflow/all.js +28 -3
  34. package/build/services/memflow/workflow/context.d.ts +44 -1
  35. package/build/services/memflow/workflow/context.js +44 -1
  36. package/build/services/memflow/workflow/didRun.d.ts +23 -3
  37. package/build/services/memflow/workflow/didRun.js +23 -3
  38. package/build/services/memflow/workflow/emit.d.ts +43 -4
  39. package/build/services/memflow/workflow/emit.js +43 -4
  40. package/build/services/memflow/workflow/enrich.d.ts +32 -4
  41. package/build/services/memflow/workflow/enrich.js +32 -4
  42. package/build/services/memflow/workflow/entityMethods.d.ts +54 -7
  43. package/build/services/memflow/workflow/entityMethods.js +54 -7
  44. package/build/services/memflow/workflow/execChild.d.ts +96 -8
  45. package/build/services/memflow/workflow/execChild.js +96 -8
  46. package/build/services/memflow/workflow/execHook.d.ts +54 -39
  47. package/build/services/memflow/workflow/execHook.js +52 -38
  48. package/build/services/memflow/workflow/execHookBatch.d.ts +82 -29
  49. package/build/services/memflow/workflow/execHookBatch.js +80 -28
  50. package/build/services/memflow/workflow/hook.d.ts +68 -3
  51. package/build/services/memflow/workflow/hook.js +69 -4
  52. package/build/services/memflow/workflow/index.d.ts +65 -10
  53. package/build/services/memflow/workflow/index.js +65 -10
  54. package/build/services/memflow/workflow/interrupt.d.ts +50 -4
  55. package/build/services/memflow/workflow/interrupt.js +50 -4
  56. package/build/services/memflow/workflow/interruption.d.ts +49 -16
  57. package/build/services/memflow/workflow/interruption.js +49 -16
  58. package/build/services/memflow/workflow/isSideEffectAllowed.d.ts +21 -4
  59. package/build/services/memflow/workflow/isSideEffectAllowed.js +21 -4
  60. package/build/services/memflow/workflow/proxyActivities.d.ts +70 -42
  61. package/build/services/memflow/workflow/proxyActivities.js +70 -42
  62. package/build/services/memflow/workflow/random.d.ts +33 -3
  63. package/build/services/memflow/workflow/random.js +33 -3
  64. package/build/services/memflow/workflow/searchMethods.d.ts +49 -2
  65. package/build/services/memflow/workflow/searchMethods.js +49 -2
  66. package/build/services/memflow/workflow/signal.d.ts +51 -22
  67. package/build/services/memflow/workflow/signal.js +52 -23
  68. package/build/services/memflow/workflow/sleepFor.d.ts +57 -18
  69. package/build/services/memflow/workflow/sleepFor.js +57 -18
  70. package/build/services/memflow/workflow/trace.d.ts +39 -6
  71. package/build/services/memflow/workflow/trace.js +39 -6
  72. package/build/services/memflow/workflow/waitFor.d.ts +55 -18
  73. package/build/services/memflow/workflow/waitFor.js +55 -18
  74. package/build/services/store/index.d.ts +1 -1
  75. package/build/services/store/providers/postgres/postgres.d.ts +1 -1
  76. package/build/services/store/providers/postgres/postgres.js +4 -3
  77. package/build/services/telemetry/index.js +6 -0
  78. package/build/types/activity.d.ts +1 -1
  79. package/build/types/hotmesh.d.ts +1 -1
  80. package/build/types/job.d.ts +1 -1
  81. package/build/types/memflow.d.ts +1 -1
  82. package/build/types/quorum.d.ts +2 -2
  83. package/build/vitest.config.d.ts +2 -0
  84. package/build/vitest.config.js +18 -0
  85. package/package.json +62 -67
  86. package/vitest.config.ts +17 -0
@@ -1,18 +1,106 @@
1
1
  import { WorkflowOptions } from './common';
2
2
  /**
3
- * Spawns a child workflow and awaits the result, or if `await` is false, returns immediately.
4
- * @template T
5
- * @param {WorkflowOptions} options - Workflow options.
6
- * @returns {Promise<T>} Result of the child workflow.
3
+ * Spawns a child workflow and awaits its result. The child runs as an
4
+ * independent job with its own lifecycle, retry policy, and dimensional
5
+ * isolation. If the child fails, the error is propagated to the parent
6
+ * as a typed error (`MemFlowFatalError`, `MemFlowMaxedError`,
7
+ * `MemFlowTimeoutError`, or `MemFlowRetryError`).
8
+ *
9
+ * On replay, the stored child result is returned immediately without
10
+ * re-spawning the child workflow.
11
+ *
12
+ * ## Child Job ID
13
+ *
14
+ * If `options.workflowId` is provided, it is used directly. Otherwise,
15
+ * the child ID is generated from the entity/workflow name, a GUID, the
16
+ * parent's dimensional coordinates, and the execution index — ensuring
17
+ * uniqueness across parallel and re-entrant executions.
18
+ *
19
+ * ## Examples
20
+ *
21
+ * ```typescript
22
+ * import { MemFlow } from '@hotmeshio/hotmesh';
23
+ *
24
+ * // Spawn a child workflow and await its result
25
+ * export async function parentWorkflow(orderId: string): Promise<string> {
26
+ * const result = await MemFlow.workflow.execChild<{ status: string }>({
27
+ * taskQueue: 'payments',
28
+ * workflowName: 'processPayment',
29
+ * args: [orderId, 99.99],
30
+ * config: {
31
+ * maximumAttempts: 3,
32
+ * backoffCoefficient: 2,
33
+ * },
34
+ * });
35
+ * return result.status;
36
+ * }
37
+ * ```
38
+ *
39
+ * ```typescript
40
+ * // Fan-out: spawn multiple children in parallel
41
+ * export async function batchWorkflow(items: string[]): Promise<string[]> {
42
+ * const results = await Promise.all(
43
+ * items.map((item) =>
44
+ * MemFlow.workflow.execChild<string>({
45
+ * taskQueue: 'processors',
46
+ * workflowName: 'processItem',
47
+ * args: [item],
48
+ * }),
49
+ * ),
50
+ * );
51
+ * return results;
52
+ * }
53
+ * ```
54
+ *
55
+ * ```typescript
56
+ * // Entity-based child (uses entity name as task queue)
57
+ * const user = await MemFlow.workflow.execChild<UserRecord>({
58
+ * entity: 'user',
59
+ * args: [{ name: 'Alice', email: 'alice@example.com' }],
60
+ * workflowId: 'user-alice', // deterministic ID
61
+ * expire: 3600, // 1 hour TTL
62
+ * });
63
+ * ```
64
+ *
65
+ * @template T - The return type of the child workflow.
66
+ * @param {WorkflowOptions} options - Child workflow configuration.
67
+ * @returns {Promise<T>} The child workflow's return value.
7
68
  */
8
69
  export declare function execChild<T>(options: WorkflowOptions): Promise<T>;
9
70
  /**
10
- * Alias for execChild.
71
+ * Alias for {@link execChild}.
11
72
  */
12
73
  export declare const executeChild: typeof execChild;
13
74
  /**
14
- * Spawns a child workflow and returns the child Job ID without awaiting its completion.
15
- * @param {WorkflowOptions} options - Workflow options.
16
- * @returns {Promise<string>} The child job ID.
75
+ * Spawns a child workflow in fire-and-forget mode. The parent workflow
76
+ * continues immediately without waiting for the child to complete.
77
+ * Returns the child's job ID for later reference (e.g., to interrupt
78
+ * or query the child).
79
+ *
80
+ * This is a convenience wrapper around `execChild` with `await: false`.
81
+ *
82
+ * ## Example
83
+ *
84
+ * ```typescript
85
+ * import { MemFlow } from '@hotmeshio/hotmesh';
86
+ *
87
+ * export async function dispatchWorkflow(taskId: string): Promise<string> {
88
+ * // Fire-and-forget: start the child and continue immediately
89
+ * const childJobId = await MemFlow.workflow.startChild({
90
+ * taskQueue: 'background',
91
+ * workflowName: 'longRunningTask',
92
+ * args: [taskId],
93
+ * });
94
+ *
95
+ * // Optionally store the child ID for monitoring
96
+ * const search = await MemFlow.workflow.search();
97
+ * await search.set({ childJobId });
98
+ *
99
+ * return childJobId;
100
+ * }
101
+ * ```
102
+ *
103
+ * @param {WorkflowOptions} options - Child workflow configuration.
104
+ * @returns {Promise<string>} The child workflow's job ID.
17
105
  */
18
106
  export declare function startChild(options: WorkflowOptions): Promise<string>;
@@ -45,10 +45,71 @@ function getChildInterruptPayload(context, options, execIndex) {
45
45
  };
46
46
  }
47
47
  /**
48
- * Spawns a child workflow and awaits the result, or if `await` is false, returns immediately.
49
- * @template T
50
- * @param {WorkflowOptions} options - Workflow options.
51
- * @returns {Promise<T>} Result of the child workflow.
48
+ * Spawns a child workflow and awaits its result. The child runs as an
49
+ * independent job with its own lifecycle, retry policy, and dimensional
50
+ * isolation. If the child fails, the error is propagated to the parent
51
+ * as a typed error (`MemFlowFatalError`, `MemFlowMaxedError`,
52
+ * `MemFlowTimeoutError`, or `MemFlowRetryError`).
53
+ *
54
+ * On replay, the stored child result is returned immediately without
55
+ * re-spawning the child workflow.
56
+ *
57
+ * ## Child Job ID
58
+ *
59
+ * If `options.workflowId` is provided, it is used directly. Otherwise,
60
+ * the child ID is generated from the entity/workflow name, a GUID, the
61
+ * parent's dimensional coordinates, and the execution index — ensuring
62
+ * uniqueness across parallel and re-entrant executions.
63
+ *
64
+ * ## Examples
65
+ *
66
+ * ```typescript
67
+ * import { MemFlow } from '@hotmeshio/hotmesh';
68
+ *
69
+ * // Spawn a child workflow and await its result
70
+ * export async function parentWorkflow(orderId: string): Promise<string> {
71
+ * const result = await MemFlow.workflow.execChild<{ status: string }>({
72
+ * taskQueue: 'payments',
73
+ * workflowName: 'processPayment',
74
+ * args: [orderId, 99.99],
75
+ * config: {
76
+ * maximumAttempts: 3,
77
+ * backoffCoefficient: 2,
78
+ * },
79
+ * });
80
+ * return result.status;
81
+ * }
82
+ * ```
83
+ *
84
+ * ```typescript
85
+ * // Fan-out: spawn multiple children in parallel
86
+ * export async function batchWorkflow(items: string[]): Promise<string[]> {
87
+ * const results = await Promise.all(
88
+ * items.map((item) =>
89
+ * MemFlow.workflow.execChild<string>({
90
+ * taskQueue: 'processors',
91
+ * workflowName: 'processItem',
92
+ * args: [item],
93
+ * }),
94
+ * ),
95
+ * );
96
+ * return results;
97
+ * }
98
+ * ```
99
+ *
100
+ * ```typescript
101
+ * // Entity-based child (uses entity name as task queue)
102
+ * const user = await MemFlow.workflow.execChild<UserRecord>({
103
+ * entity: 'user',
104
+ * args: [{ name: 'Alice', email: 'alice@example.com' }],
105
+ * workflowId: 'user-alice', // deterministic ID
106
+ * expire: 3600, // 1 hour TTL
107
+ * });
108
+ * ```
109
+ *
110
+ * @template T - The return type of the child workflow.
111
+ * @param {WorkflowOptions} options - Child workflow configuration.
112
+ * @returns {Promise<T>} The child workflow's return value.
52
113
  */
53
114
  async function execChild(options) {
54
115
  const isStartChild = options.await === false;
@@ -92,13 +153,40 @@ async function execChild(options) {
92
153
  }
93
154
  exports.execChild = execChild;
94
155
  /**
95
- * Alias for execChild.
156
+ * Alias for {@link execChild}.
96
157
  */
97
158
  exports.executeChild = execChild;
98
159
  /**
99
- * Spawns a child workflow and returns the child Job ID without awaiting its completion.
100
- * @param {WorkflowOptions} options - Workflow options.
101
- * @returns {Promise<string>} The child job ID.
160
+ * Spawns a child workflow in fire-and-forget mode. The parent workflow
161
+ * continues immediately without waiting for the child to complete.
162
+ * Returns the child's job ID for later reference (e.g., to interrupt
163
+ * or query the child).
164
+ *
165
+ * This is a convenience wrapper around `execChild` with `await: false`.
166
+ *
167
+ * ## Example
168
+ *
169
+ * ```typescript
170
+ * import { MemFlow } from '@hotmeshio/hotmesh';
171
+ *
172
+ * export async function dispatchWorkflow(taskId: string): Promise<string> {
173
+ * // Fire-and-forget: start the child and continue immediately
174
+ * const childJobId = await MemFlow.workflow.startChild({
175
+ * taskQueue: 'background',
176
+ * workflowName: 'longRunningTask',
177
+ * args: [taskId],
178
+ * });
179
+ *
180
+ * // Optionally store the child ID for monitoring
181
+ * const search = await MemFlow.workflow.search();
182
+ * await search.set({ childJobId });
183
+ *
184
+ * return childJobId;
185
+ * }
186
+ * ```
187
+ *
188
+ * @param {WorkflowOptions} options - Child workflow configuration.
189
+ * @returns {Promise<string>} The child workflow's job ID.
102
190
  */
103
191
  async function startChild(options) {
104
192
  return execChild({ ...options, await: false });
@@ -1,65 +1,80 @@
1
1
  import { HookOptions } from './common';
2
2
  /**
3
- * Extended hook options that include signal configuration
3
+ * Extended hook options that include signal configuration.
4
+ * Used by `execHook()` and `execHookBatch()`.
4
5
  */
5
6
  export interface ExecHookOptions extends HookOptions {
6
7
  /** Signal ID to send after hook execution; if not provided, a random one will be generated */
7
8
  signalId?: string;
8
9
  }
9
10
  /**
10
- * Executes a hook function and awaits the signal response.
11
- * This is a convenience method that combines `hook()` and `waitFor()` operations.
11
+ * Combines `hook()` + `waitFor()` into a single call: spawns a hook
12
+ * function on a target workflow and suspends the current workflow until
13
+ * the hook signals completion. This is the recommended pattern for
14
+ * request/response communication between workflow threads.
12
15
  *
13
- * **Signal Injection**: The `signalId` is automatically injected as the LAST argument
14
- * to the hooked function. The hooked function should check for this signal parameter
15
- * and emit the signal when processing is complete.
16
+ * ## Signal Injection
16
17
  *
17
- * This behaves like `execChild` but targets the existing workflow instead of
18
- * spawning a new workflow.
18
+ * A `signalId` is automatically generated (or use the one you provide)
19
+ * and injected as the **last argument** to the hooked function as
20
+ * `{ signal: string, $memflow: true }`. The hook function must call
21
+ * `MemFlow.workflow.signal(signalInfo.signal, result)` to deliver
22
+ * its response back to the waiting workflow.
19
23
  *
20
- * @template T
21
- * @param {ExecHookOptions} options - Hook configuration with signal ID.
22
- * @returns {Promise<T>} The signal result from the hooked function.
24
+ * ## Difference from `execChild`
23
25
  *
24
- * @example
25
- * ```typescript
26
- * // Execute a hook and await its signal response
27
- * const result = await MemFlow.workflow.execHook({
28
- * taskQueue: 'processing',
29
- * workflowName: 'processData',
30
- * args: ['user123', 'batch-process'],
31
- * signalId: 'processing-complete'
32
- * });
26
+ * - `execChild` spawns a **new** workflow job with its own lifecycle.
27
+ * - `execHook` runs within an **existing** workflow's job, in an
28
+ * isolated dimensional thread. This is lighter-weight and shares
29
+ * the parent job's data namespace.
33
30
  *
34
- * // The hooked function receives the signal as the last argument:
35
- * export async function processData(userId: string, processType: string, signalInfo?: { signal: string }) {
36
- * // ... do processing work ...
37
- * const result = { userId, processType, status: 'completed' };
31
+ * ## Examples
38
32
  *
39
- * // Check if called via execHook (signalInfo will be present)
40
- * if (signalInfo?.signal) {
41
- * await MemFlow.workflow.signal(signalInfo.signal, result);
42
- * }
33
+ * ```typescript
34
+ * import { MemFlow } from '@hotmeshio/hotmesh';
43
35
  *
44
- * return result;
36
+ * // Orchestrator: spawn a hook and await its result
37
+ * export async function reviewWorkflow(docId: string): Promise<string> {
38
+ * const verdict = await MemFlow.workflow.execHook<{ approved: boolean }>({
39
+ * taskQueue: 'reviewers',
40
+ * workflowName: 'reviewDocument',
41
+ * args: [docId],
42
+ * });
43
+ *
44
+ * return verdict.approved ? 'accepted' : 'rejected';
45
45
  * }
46
46
  * ```
47
47
  *
48
- * @example
49
48
  * ```typescript
50
- * // Alternative pattern - check if last arg is signal object
51
- * export async function myHookFunction(arg1: string, arg2: number, ...rest: any[]) {
52
- * // ... process arg1 and arg2 ...
53
- * const result = { processed: true, data: [arg1, arg2] };
49
+ * // The hooked function (runs on the 'reviewers' worker)
50
+ * export async function reviewDocument(
51
+ * docId: string,
52
+ * signalInfo?: { signal: string; $memflow: boolean },
53
+ * ): Promise<{ approved: boolean }> {
54
+ * const { analyzeDocument } = MemFlow.workflow.proxyActivities<typeof activities>();
55
+ * const score = await analyzeDocument(docId);
56
+ * const result = { approved: score > 0.8 };
54
57
  *
55
- * // Check if last argument is a signal object
56
- * const lastArg = rest[rest.length - 1];
57
- * if (lastArg && typeof lastArg === 'object' && lastArg.signal) {
58
- * await MemFlow.workflow.signal(lastArg.signal, result);
58
+ * // Signal the waiting workflow with the result
59
+ * if (signalInfo?.signal) {
60
+ * await MemFlow.workflow.signal(signalInfo.signal, result);
59
61
  * }
60
- *
61
62
  * return result;
62
63
  * }
63
64
  * ```
65
+ *
66
+ * ```typescript
67
+ * // With explicit signalId for traceability
68
+ * const result = await MemFlow.workflow.execHook<AnalysisResult>({
69
+ * taskQueue: 'analyzers',
70
+ * workflowName: 'runAnalysis',
71
+ * args: [datasetId],
72
+ * signalId: `analysis-${datasetId}`,
73
+ * });
74
+ * ```
75
+ *
76
+ * @template T - The type of data returned by the hook function's signal.
77
+ * @param {ExecHookOptions} options - Hook configuration including target workflow and arguments.
78
+ * @returns {Promise<T>} The signal result from the hooked function.
64
79
  */
65
80
  export declare function execHook<T>(options: ExecHookOptions): Promise<T>;
@@ -5,60 +5,74 @@ const hook_1 = require("./hook");
5
5
  const waitFor_1 = require("./waitFor");
6
6
  const interruption_1 = require("./interruption");
7
7
  /**
8
- * Executes a hook function and awaits the signal response.
9
- * This is a convenience method that combines `hook()` and `waitFor()` operations.
8
+ * Combines `hook()` + `waitFor()` into a single call: spawns a hook
9
+ * function on a target workflow and suspends the current workflow until
10
+ * the hook signals completion. This is the recommended pattern for
11
+ * request/response communication between workflow threads.
10
12
  *
11
- * **Signal Injection**: The `signalId` is automatically injected as the LAST argument
12
- * to the hooked function. The hooked function should check for this signal parameter
13
- * and emit the signal when processing is complete.
13
+ * ## Signal Injection
14
14
  *
15
- * This behaves like `execChild` but targets the existing workflow instead of
16
- * spawning a new workflow.
15
+ * A `signalId` is automatically generated (or use the one you provide)
16
+ * and injected as the **last argument** to the hooked function as
17
+ * `{ signal: string, $memflow: true }`. The hook function must call
18
+ * `MemFlow.workflow.signal(signalInfo.signal, result)` to deliver
19
+ * its response back to the waiting workflow.
17
20
  *
18
- * @template T
19
- * @param {ExecHookOptions} options - Hook configuration with signal ID.
20
- * @returns {Promise<T>} The signal result from the hooked function.
21
+ * ## Difference from `execChild`
21
22
  *
22
- * @example
23
- * ```typescript
24
- * // Execute a hook and await its signal response
25
- * const result = await MemFlow.workflow.execHook({
26
- * taskQueue: 'processing',
27
- * workflowName: 'processData',
28
- * args: ['user123', 'batch-process'],
29
- * signalId: 'processing-complete'
30
- * });
23
+ * - `execChild` spawns a **new** workflow job with its own lifecycle.
24
+ * - `execHook` runs within an **existing** workflow's job, in an
25
+ * isolated dimensional thread. This is lighter-weight and shares
26
+ * the parent job's data namespace.
31
27
  *
32
- * // The hooked function receives the signal as the last argument:
33
- * export async function processData(userId: string, processType: string, signalInfo?: { signal: string }) {
34
- * // ... do processing work ...
35
- * const result = { userId, processType, status: 'completed' };
28
+ * ## Examples
36
29
  *
37
- * // Check if called via execHook (signalInfo will be present)
38
- * if (signalInfo?.signal) {
39
- * await MemFlow.workflow.signal(signalInfo.signal, result);
40
- * }
30
+ * ```typescript
31
+ * import { MemFlow } from '@hotmeshio/hotmesh';
41
32
  *
42
- * return result;
33
+ * // Orchestrator: spawn a hook and await its result
34
+ * export async function reviewWorkflow(docId: string): Promise<string> {
35
+ * const verdict = await MemFlow.workflow.execHook<{ approved: boolean }>({
36
+ * taskQueue: 'reviewers',
37
+ * workflowName: 'reviewDocument',
38
+ * args: [docId],
39
+ * });
40
+ *
41
+ * return verdict.approved ? 'accepted' : 'rejected';
43
42
  * }
44
43
  * ```
45
44
  *
46
- * @example
47
45
  * ```typescript
48
- * // Alternative pattern - check if last arg is signal object
49
- * export async function myHookFunction(arg1: string, arg2: number, ...rest: any[]) {
50
- * // ... process arg1 and arg2 ...
51
- * const result = { processed: true, data: [arg1, arg2] };
46
+ * // The hooked function (runs on the 'reviewers' worker)
47
+ * export async function reviewDocument(
48
+ * docId: string,
49
+ * signalInfo?: { signal: string; $memflow: boolean },
50
+ * ): Promise<{ approved: boolean }> {
51
+ * const { analyzeDocument } = MemFlow.workflow.proxyActivities<typeof activities>();
52
+ * const score = await analyzeDocument(docId);
53
+ * const result = { approved: score > 0.8 };
52
54
  *
53
- * // Check if last argument is a signal object
54
- * const lastArg = rest[rest.length - 1];
55
- * if (lastArg && typeof lastArg === 'object' && lastArg.signal) {
56
- * await MemFlow.workflow.signal(lastArg.signal, result);
55
+ * // Signal the waiting workflow with the result
56
+ * if (signalInfo?.signal) {
57
+ * await MemFlow.workflow.signal(signalInfo.signal, result);
57
58
  * }
58
- *
59
59
  * return result;
60
60
  * }
61
61
  * ```
62
+ *
63
+ * ```typescript
64
+ * // With explicit signalId for traceability
65
+ * const result = await MemFlow.workflow.execHook<AnalysisResult>({
66
+ * taskQueue: 'analyzers',
67
+ * workflowName: 'runAnalysis',
68
+ * args: [datasetId],
69
+ * signalId: `analysis-${datasetId}`,
70
+ * });
71
+ * ```
72
+ *
73
+ * @template T - The type of data returned by the hook function's signal.
74
+ * @param {ExecHookOptions} options - Hook configuration including target workflow and arguments.
75
+ * @returns {Promise<T>} The signal result from the hooked function.
62
76
  */
63
77
  async function execHook(options) {
64
78
  try {
@@ -1,6 +1,7 @@
1
1
  import { ExecHookOptions } from './execHook';
2
2
  /**
3
- * Configuration for a single hook in a batch execution
3
+ * Configuration for a single hook in a batch execution.
4
+ * @see {@link execHookBatch}
4
5
  */
5
6
  export interface BatchHookConfig<T = any> {
6
7
  /** Unique key to identify this hook's result in the returned object */
@@ -9,46 +10,98 @@ export interface BatchHookConfig<T = any> {
9
10
  options: ExecHookOptions;
10
11
  }
11
12
  /**
12
- * Executes multiple hooks in parallel and awaits all their signal responses.
13
- * This solves the race condition where Promise.all() with execHook() would prevent
14
- * all waitFor() registrations from completing.
13
+ * Executes multiple hooks in parallel and awaits all their signal responses,
14
+ * returning a keyed object of results. This is the recommended way to run
15
+ * concurrent hooks it solves a race condition where calling
16
+ * `Promise.all([execHook(), execHook()])` would throw before all `waitFor`
17
+ * registrations complete.
15
18
  *
16
- * The method ensures all waitFor() registrations happen before any hooks execute,
17
- * preventing signals from being sent before the framework is ready to receive them.
19
+ * ## Three-Phase Execution
18
20
  *
19
- * @template T - Object type with keys matching the batch hook keys and values as expected response types
20
- * @param {BatchHookConfig[]} hookConfigs - Array of hook configurations with unique keys
21
- * @returns {Promise<T>} Object with keys from hookConfigs and values as the signal responses
21
+ * 1. **Fire all hooks** via `Promise.all` (registers streams immediately).
22
+ * 2. **Await all signals** via `Promise.all` (all `waitFor` registrations
23
+ * happen together before any `MemFlowWaitForError` is thrown).
24
+ * 3. **Combine results** into a `{ [key]: result }` map.
25
+ *
26
+ * ## Examples
27
+ *
28
+ * ```typescript
29
+ * import { MemFlow } from '@hotmeshio/hotmesh';
30
+ *
31
+ * // Fan-out to multiple AI agents, gather all perspectives
32
+ * export async function researchWorkflow(query: string): Promise<Summary> {
33
+ * const perspectives = await MemFlow.workflow.execHookBatch<{
34
+ * optimistic: PerspectiveResult;
35
+ * skeptical: PerspectiveResult;
36
+ * neutral: PerspectiveResult;
37
+ * }>([
38
+ * {
39
+ * key: 'optimistic',
40
+ * options: {
41
+ * taskQueue: 'agents',
42
+ * workflowName: 'analyzeOptimistic',
43
+ * args: [query],
44
+ * },
45
+ * },
46
+ * {
47
+ * key: 'skeptical',
48
+ * options: {
49
+ * taskQueue: 'agents',
50
+ * workflowName: 'analyzeSkeptical',
51
+ * args: [query],
52
+ * },
53
+ * },
54
+ * {
55
+ * key: 'neutral',
56
+ * options: {
57
+ * taskQueue: 'agents',
58
+ * workflowName: 'analyzeNeutral',
59
+ * args: [query],
60
+ * },
61
+ * },
62
+ * ]);
63
+ *
64
+ * // All three results are available as typed properties
65
+ * const { synthesize } = MemFlow.workflow.proxyActivities<typeof activities>();
66
+ * return await synthesize(
67
+ * perspectives.optimistic,
68
+ * perspectives.skeptical,
69
+ * perspectives.neutral,
70
+ * );
71
+ * }
72
+ * ```
22
73
  *
23
- * @example
24
74
  * ```typescript
25
- * // Execute multiple research perspectives in parallel
26
- * const results = await MemFlow.workflow.execHookBatch<{
27
- * optimistic: OptimisticResult;
28
- * skeptical: SkepticalResult;
75
+ * // Parallel validation with different services
76
+ * const checks = await MemFlow.workflow.execHookBatch<{
77
+ * fraud: { safe: boolean };
78
+ * compliance: { approved: boolean };
29
79
  * }>([
30
80
  * {
31
- * key: 'optimistic',
81
+ * key: 'fraud',
32
82
  * options: {
33
- * taskQueue: 'agents',
34
- * workflowName: 'optimisticPerspective',
35
- * args: [query],
36
- * signalId: 'optimistic-complete'
37
- * }
83
+ * taskQueue: 'fraud-detection',
84
+ * workflowName: 'checkFraud',
85
+ * args: [transactionId],
86
+ * },
38
87
  * },
39
88
  * {
40
- * key: 'skeptical',
89
+ * key: 'compliance',
41
90
  * options: {
42
- * taskQueue: 'agents',
43
- * workflowName: 'skepticalPerspective',
44
- * args: [query],
45
- * signalId: 'skeptical-complete'
46
- * }
47
- * }
91
+ * taskQueue: 'compliance',
92
+ * workflowName: 'checkCompliance',
93
+ * args: [transactionId],
94
+ * },
95
+ * },
48
96
  * ]);
49
97
  *
50
- * // results.optimistic contains the OptimisticResult
51
- * // results.skeptical contains the SkepticalResult
98
+ * if (checks.fraud.safe && checks.compliance.approved) {
99
+ * // proceed with transaction
100
+ * }
52
101
  * ```
102
+ *
103
+ * @template T - Object type with keys matching the batch hook keys.
104
+ * @param {BatchHookConfig[]} hookConfigs - Array of hook configurations with unique keys.
105
+ * @returns {Promise<T>} Object mapping each config's `key` to its signal response.
53
106
  */
54
107
  export declare function execHookBatch<T extends Record<string, any>>(hookConfigs: BatchHookConfig[]): Promise<T>;