@hotmeshio/hotmesh 0.9.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -24
- package/build/index.d.ts +12 -11
- package/build/index.js +15 -13
- package/build/modules/enums.d.ts +23 -34
- package/build/modules/enums.js +26 -38
- package/build/modules/errors.d.ts +16 -16
- package/build/modules/errors.js +37 -37
- package/build/package.json +25 -25
- package/build/services/activities/activity.js +1 -1
- package/build/services/dba/index.d.ts +171 -0
- package/build/services/dba/index.js +280 -0
- package/build/services/{memflow → durable}/client.d.ts +3 -3
- package/build/services/{memflow → durable}/client.js +13 -13
- package/build/services/{memflow → durable}/connection.d.ts +2 -2
- package/build/services/{memflow → durable}/connection.js +1 -1
- package/build/services/{memflow → durable}/exporter.d.ts +6 -6
- package/build/services/{memflow → durable}/exporter.js +2 -2
- package/build/services/{memflow → durable}/handle.d.ts +4 -4
- package/build/services/{memflow → durable}/handle.js +2 -2
- package/build/services/{memflow → durable}/index.d.ts +155 -34
- package/build/services/{memflow → durable}/index.js +175 -50
- package/build/services/{memflow → durable}/interceptor.d.ts +118 -22
- package/build/services/{memflow → durable}/interceptor.js +127 -21
- package/build/services/{memflow → durable}/schemas/factory.d.ts +4 -4
- package/build/services/{memflow → durable}/schemas/factory.js +5 -5
- package/build/services/{memflow → durable}/search.d.ts +1 -1
- package/build/services/{memflow → durable}/search.js +4 -4
- package/build/services/{memflow → durable}/worker.d.ts +11 -11
- package/build/services/{memflow → durable}/worker.js +61 -60
- package/build/services/{memflow → durable}/workflow/all.d.ts +5 -5
- package/build/services/{memflow → durable}/workflow/all.js +5 -5
- package/build/services/{memflow → durable}/workflow/common.d.ts +5 -5
- package/build/services/durable/workflow/common.js +47 -0
- package/build/services/{memflow → durable}/workflow/context.d.ts +5 -5
- package/build/services/{memflow → durable}/workflow/context.js +5 -5
- package/build/services/{memflow → durable}/workflow/emit.d.ts +5 -5
- package/build/services/{memflow → durable}/workflow/emit.js +5 -5
- package/build/services/{memflow → durable}/workflow/enrich.d.ts +4 -4
- package/build/services/{memflow → durable}/workflow/enrich.js +4 -4
- package/build/services/{memflow → durable}/workflow/entityMethods.d.ts +4 -4
- package/build/services/{memflow → durable}/workflow/entityMethods.js +4 -4
- package/build/services/{memflow → durable}/workflow/execChild.d.ts +9 -9
- package/build/services/{memflow → durable}/workflow/execChild.js +22 -22
- package/build/services/{memflow → durable}/workflow/execHook.d.ts +8 -8
- package/build/services/{memflow → durable}/workflow/execHook.js +10 -10
- package/build/services/{memflow → durable}/workflow/execHookBatch.d.ts +5 -5
- package/build/services/{memflow → durable}/workflow/execHookBatch.js +8 -8
- package/build/services/{memflow → durable}/workflow/hook.d.ts +5 -5
- package/build/services/{memflow → durable}/workflow/hook.js +11 -11
- package/build/services/{memflow → durable}/workflow/index.d.ts +6 -6
- package/build/services/{memflow → durable}/workflow/index.js +6 -6
- package/build/services/{memflow → durable}/workflow/interrupt.d.ts +7 -7
- package/build/services/{memflow → durable}/workflow/interrupt.js +7 -7
- package/build/services/{memflow → durable}/workflow/interruption.d.ts +10 -10
- package/build/services/{memflow → durable}/workflow/interruption.js +19 -19
- package/build/services/{memflow → durable}/workflow/proxyActivities.d.ts +7 -7
- package/build/services/{memflow → durable}/workflow/proxyActivities.js +49 -35
- package/build/services/{memflow → durable}/workflow/random.d.ts +4 -4
- package/build/services/{memflow → durable}/workflow/random.js +4 -4
- package/build/services/{memflow → durable}/workflow/searchMethods.d.ts +5 -5
- package/build/services/{memflow → durable}/workflow/searchMethods.js +5 -5
- package/build/services/{memflow → durable}/workflow/signal.d.ts +8 -8
- package/build/services/{memflow → durable}/workflow/signal.js +8 -8
- package/build/services/{memflow → durable}/workflow/sleepFor.d.ts +7 -7
- package/build/services/{memflow → durable}/workflow/sleepFor.js +10 -10
- package/build/services/{memflow → durable}/workflow/trace.d.ts +5 -5
- package/build/services/{memflow → durable}/workflow/trace.js +5 -5
- package/build/services/{memflow → durable}/workflow/waitFor.d.ts +9 -9
- package/build/services/{memflow → durable}/workflow/waitFor.js +12 -12
- package/build/services/hotmesh/index.d.ts +3 -3
- package/build/services/hotmesh/index.js +3 -3
- package/build/services/{meshcall → virtual}/index.d.ts +29 -29
- package/build/services/{meshcall → virtual}/index.js +49 -49
- package/build/services/{meshcall → virtual}/schemas/factory.d.ts +1 -1
- package/build/services/{meshcall → virtual}/schemas/factory.js +1 -1
- package/build/types/dba.d.ts +64 -0
- package/build/types/{memflow.d.ts → durable.d.ts} +96 -22
- package/build/types/error.d.ts +5 -5
- package/build/types/exporter.d.ts +1 -1
- package/build/types/index.d.ts +5 -4
- package/build/types/{meshcall.d.ts → virtual.d.ts} +15 -15
- package/build/types/virtual.js +2 -0
- package/index.ts +15 -13
- package/package.json +25 -25
- package/.claude/settings.local.json +0 -8
- package/build/services/memflow/workflow/common.js +0 -47
- package/build/vitest.config.d.ts +0 -2
- package/build/vitest.config.js +0 -18
- /package/build/services/{memflow → durable}/entity.d.ts +0 -0
- /package/build/services/{memflow → durable}/entity.js +0 -0
- /package/build/services/{memflow → durable}/workflow/didRun.d.ts +0 -0
- /package/build/services/{memflow → durable}/workflow/didRun.js +0 -0
- /package/build/services/{memflow → durable}/workflow/isSideEffectAllowed.d.ts +0 -0
- /package/build/services/{memflow → durable}/workflow/isSideEffectAllowed.js +0 -0
- /package/build/types/{memflow.js → dba.js} +0 -0
- /package/build/types/{meshcall.js → durable.js} +0 -0
- /package/{vitest.config.ts → vitest.config.mts} +0 -0
|
@@ -14,20 +14,20 @@ import { StringStringType } from './common';
|
|
|
14
14
|
* ## Example
|
|
15
15
|
*
|
|
16
16
|
* ```typescript
|
|
17
|
-
* import {
|
|
17
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
18
18
|
*
|
|
19
19
|
* export async function onboardingWorkflow(userId: string): Promise<void> {
|
|
20
20
|
* // Tag the workflow record with queryable metadata
|
|
21
|
-
* await
|
|
21
|
+
* await Durable.workflow.enrich({
|
|
22
22
|
* userId,
|
|
23
23
|
* stage: 'verification',
|
|
24
24
|
* startedAt: new Date().toISOString(),
|
|
25
25
|
* });
|
|
26
26
|
*
|
|
27
|
-
* const { verifyIdentity } =
|
|
27
|
+
* const { verifyIdentity } = Durable.workflow.proxyActivities<typeof activities>();
|
|
28
28
|
* await verifyIdentity(userId);
|
|
29
29
|
*
|
|
30
|
-
* await
|
|
30
|
+
* await Durable.workflow.enrich({ stage: 'complete' });
|
|
31
31
|
* }
|
|
32
32
|
* ```
|
|
33
33
|
*
|
|
@@ -17,20 +17,20 @@ const searchMethods_1 = require("./searchMethods");
|
|
|
17
17
|
* ## Example
|
|
18
18
|
*
|
|
19
19
|
* ```typescript
|
|
20
|
-
* import {
|
|
20
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
21
21
|
*
|
|
22
22
|
* export async function onboardingWorkflow(userId: string): Promise<void> {
|
|
23
23
|
* // Tag the workflow record with queryable metadata
|
|
24
|
-
* await
|
|
24
|
+
* await Durable.workflow.enrich({
|
|
25
25
|
* userId,
|
|
26
26
|
* stage: 'verification',
|
|
27
27
|
* startedAt: new Date().toISOString(),
|
|
28
28
|
* });
|
|
29
29
|
*
|
|
30
|
-
* const { verifyIdentity } =
|
|
30
|
+
* const { verifyIdentity } = Durable.workflow.proxyActivities<typeof activities>();
|
|
31
31
|
* await verifyIdentity(userId);
|
|
32
32
|
*
|
|
33
|
-
* await
|
|
33
|
+
* await Durable.workflow.enrich({ stage: 'complete' });
|
|
34
34
|
* }
|
|
35
35
|
* ```
|
|
36
36
|
*
|
|
@@ -11,10 +11,10 @@ import { Entity } from './common';
|
|
|
11
11
|
* ## Examples
|
|
12
12
|
*
|
|
13
13
|
* ```typescript
|
|
14
|
-
* import {
|
|
14
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
15
15
|
*
|
|
16
16
|
* export async function userProfileWorkflow(userId: string): Promise<UserProfile> {
|
|
17
|
-
* const entity = await
|
|
17
|
+
* const entity = await Durable.workflow.entity();
|
|
18
18
|
*
|
|
19
19
|
* // Initialize a structured document
|
|
20
20
|
* await entity.set({
|
|
@@ -40,8 +40,8 @@ import { Entity } from './common';
|
|
|
40
40
|
* ```typescript
|
|
41
41
|
* // Accumulate state across activities
|
|
42
42
|
* export async function pipelineWorkflow(input: string): Promise<PipelineResult> {
|
|
43
|
-
* const entity = await
|
|
44
|
-
* const { step1, step2, step3 } =
|
|
43
|
+
* const entity = await Durable.workflow.entity();
|
|
44
|
+
* const { step1, step2, step3 } = Durable.workflow.proxyActivities<typeof activities>();
|
|
45
45
|
*
|
|
46
46
|
* const r1 = await step1(input);
|
|
47
47
|
* await entity.merge({ pipeline: { step1: r1 } });
|
|
@@ -14,10 +14,10 @@ const common_1 = require("./common");
|
|
|
14
14
|
* ## Examples
|
|
15
15
|
*
|
|
16
16
|
* ```typescript
|
|
17
|
-
* import {
|
|
17
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
18
18
|
*
|
|
19
19
|
* export async function userProfileWorkflow(userId: string): Promise<UserProfile> {
|
|
20
|
-
* const entity = await
|
|
20
|
+
* const entity = await Durable.workflow.entity();
|
|
21
21
|
*
|
|
22
22
|
* // Initialize a structured document
|
|
23
23
|
* await entity.set({
|
|
@@ -43,8 +43,8 @@ const common_1 = require("./common");
|
|
|
43
43
|
* ```typescript
|
|
44
44
|
* // Accumulate state across activities
|
|
45
45
|
* export async function pipelineWorkflow(input: string): Promise<PipelineResult> {
|
|
46
|
-
* const entity = await
|
|
47
|
-
* const { step1, step2, step3 } =
|
|
46
|
+
* const entity = await Durable.workflow.entity();
|
|
47
|
+
* const { step1, step2, step3 } = Durable.workflow.proxyActivities<typeof activities>();
|
|
48
48
|
*
|
|
49
49
|
* const r1 = await step1(input);
|
|
50
50
|
* await entity.merge({ pipeline: { step1: r1 } });
|
|
@@ -3,8 +3,8 @@ import { WorkflowOptions } from './common';
|
|
|
3
3
|
* Spawns a child workflow and awaits its result. The child runs as an
|
|
4
4
|
* independent job with its own lifecycle, retry policy, and dimensional
|
|
5
5
|
* isolation. If the child fails, the error is propagated to the parent
|
|
6
|
-
* as a typed error (`
|
|
7
|
-
* `
|
|
6
|
+
* as a typed error (`DurableFatalError`, `DurableMaxedError`,
|
|
7
|
+
* `DurableTimeoutError`, or `DurableRetryError`).
|
|
8
8
|
*
|
|
9
9
|
* On replay, the stored child result is returned immediately without
|
|
10
10
|
* re-spawning the child workflow.
|
|
@@ -19,11 +19,11 @@ import { WorkflowOptions } from './common';
|
|
|
19
19
|
* ## Examples
|
|
20
20
|
*
|
|
21
21
|
* ```typescript
|
|
22
|
-
* import {
|
|
22
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
23
23
|
*
|
|
24
24
|
* // Spawn a child workflow and await its result
|
|
25
25
|
* export async function parentWorkflow(orderId: string): Promise<string> {
|
|
26
|
-
* const result = await
|
|
26
|
+
* const result = await Durable.workflow.execChild<{ status: string }>({
|
|
27
27
|
* taskQueue: 'payments',
|
|
28
28
|
* workflowName: 'processPayment',
|
|
29
29
|
* args: [orderId, 99.99],
|
|
@@ -41,7 +41,7 @@ import { WorkflowOptions } from './common';
|
|
|
41
41
|
* export async function batchWorkflow(items: string[]): Promise<string[]> {
|
|
42
42
|
* const results = await Promise.all(
|
|
43
43
|
* items.map((item) =>
|
|
44
|
-
*
|
|
44
|
+
* Durable.workflow.execChild<string>({
|
|
45
45
|
* taskQueue: 'processors',
|
|
46
46
|
* workflowName: 'processItem',
|
|
47
47
|
* args: [item],
|
|
@@ -54,7 +54,7 @@ import { WorkflowOptions } from './common';
|
|
|
54
54
|
*
|
|
55
55
|
* ```typescript
|
|
56
56
|
* // Entity-based child (uses entity name as task queue)
|
|
57
|
-
* const user = await
|
|
57
|
+
* const user = await Durable.workflow.execChild<UserRecord>({
|
|
58
58
|
* entity: 'user',
|
|
59
59
|
* args: [{ name: 'Alice', email: 'alice@example.com' }],
|
|
60
60
|
* workflowId: 'user-alice', // deterministic ID
|
|
@@ -82,18 +82,18 @@ export declare const executeChild: typeof execChild;
|
|
|
82
82
|
* ## Example
|
|
83
83
|
*
|
|
84
84
|
* ```typescript
|
|
85
|
-
* import {
|
|
85
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
86
86
|
*
|
|
87
87
|
* export async function dispatchWorkflow(taskId: string): Promise<string> {
|
|
88
88
|
* // Fire-and-forget: start the child and continue immediately
|
|
89
|
-
* const childJobId = await
|
|
89
|
+
* const childJobId = await Durable.workflow.startChild({
|
|
90
90
|
* taskQueue: 'background',
|
|
91
91
|
* workflowName: 'longRunningTask',
|
|
92
92
|
* args: [taskId],
|
|
93
93
|
* });
|
|
94
94
|
*
|
|
95
95
|
* // Optionally store the child ID for monitoring
|
|
96
|
-
* const search = await
|
|
96
|
+
* const search = await Durable.workflow.search();
|
|
97
97
|
* await search.set({ childJobId });
|
|
98
98
|
*
|
|
99
99
|
* return childJobId;
|
|
@@ -29,10 +29,10 @@ function getChildInterruptPayload(context, options, execIndex) {
|
|
|
29
29
|
return {
|
|
30
30
|
arguments: [...(options.args || [])],
|
|
31
31
|
await: options?.await ?? true,
|
|
32
|
-
backoffCoefficient: options?.config?.backoffCoefficient ?? common_1.
|
|
32
|
+
backoffCoefficient: options?.config?.backoffCoefficient ?? common_1.HMSH_DURABLE_EXP_BACKOFF,
|
|
33
33
|
index: execIndex,
|
|
34
|
-
maximumAttempts: options?.config?.maximumAttempts ?? common_1.
|
|
35
|
-
maximumInterval: (0, common_1.s)(options?.config?.maximumInterval ?? common_1.
|
|
34
|
+
maximumAttempts: options?.config?.maximumAttempts ?? common_1.HMSH_DURABLE_MAX_ATTEMPTS,
|
|
35
|
+
maximumInterval: (0, common_1.s)(options?.config?.maximumInterval ?? common_1.HMSH_DURABLE_MAX_INTERVAL),
|
|
36
36
|
originJobId: originJobId ?? workflowId,
|
|
37
37
|
entity: options.entity,
|
|
38
38
|
expire: options.expire ?? expire,
|
|
@@ -48,8 +48,8 @@ function getChildInterruptPayload(context, options, execIndex) {
|
|
|
48
48
|
* Spawns a child workflow and awaits its result. The child runs as an
|
|
49
49
|
* independent job with its own lifecycle, retry policy, and dimensional
|
|
50
50
|
* isolation. If the child fails, the error is propagated to the parent
|
|
51
|
-
* as a typed error (`
|
|
52
|
-
* `
|
|
51
|
+
* as a typed error (`DurableFatalError`, `DurableMaxedError`,
|
|
52
|
+
* `DurableTimeoutError`, or `DurableRetryError`).
|
|
53
53
|
*
|
|
54
54
|
* On replay, the stored child result is returned immediately without
|
|
55
55
|
* re-spawning the child workflow.
|
|
@@ -64,11 +64,11 @@ function getChildInterruptPayload(context, options, execIndex) {
|
|
|
64
64
|
* ## Examples
|
|
65
65
|
*
|
|
66
66
|
* ```typescript
|
|
67
|
-
* import {
|
|
67
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
68
68
|
*
|
|
69
69
|
* // Spawn a child workflow and await its result
|
|
70
70
|
* export async function parentWorkflow(orderId: string): Promise<string> {
|
|
71
|
-
* const result = await
|
|
71
|
+
* const result = await Durable.workflow.execChild<{ status: string }>({
|
|
72
72
|
* taskQueue: 'payments',
|
|
73
73
|
* workflowName: 'processPayment',
|
|
74
74
|
* args: [orderId, 99.99],
|
|
@@ -86,7 +86,7 @@ function getChildInterruptPayload(context, options, execIndex) {
|
|
|
86
86
|
* export async function batchWorkflow(items: string[]): Promise<string[]> {
|
|
87
87
|
* const results = await Promise.all(
|
|
88
88
|
* items.map((item) =>
|
|
89
|
-
*
|
|
89
|
+
* Durable.workflow.execChild<string>({
|
|
90
90
|
* taskQueue: 'processors',
|
|
91
91
|
* workflowName: 'processItem',
|
|
92
92
|
* args: [item],
|
|
@@ -99,7 +99,7 @@ function getChildInterruptPayload(context, options, execIndex) {
|
|
|
99
99
|
*
|
|
100
100
|
* ```typescript
|
|
101
101
|
* // Entity-based child (uses entity name as task queue)
|
|
102
|
-
* const user = await
|
|
102
|
+
* const user = await Durable.workflow.execChild<UserRecord>({
|
|
103
103
|
* entity: 'user',
|
|
104
104
|
* args: [{ name: 'Alice', email: 'alice@example.com' }],
|
|
105
105
|
* workflowId: 'user-alice', // deterministic ID
|
|
@@ -123,17 +123,17 @@ async function execChild(options) {
|
|
|
123
123
|
const code = result.$error.code;
|
|
124
124
|
const message = result.$error.message;
|
|
125
125
|
const stack = result.$error.stack;
|
|
126
|
-
if (code === common_1.
|
|
127
|
-
throw new common_1.
|
|
126
|
+
if (code === common_1.HMSH_CODE_DURABLE_FATAL) {
|
|
127
|
+
throw new common_1.DurableFatalError(message, stack);
|
|
128
128
|
}
|
|
129
|
-
else if (code === common_1.
|
|
130
|
-
throw new common_1.
|
|
129
|
+
else if (code === common_1.HMSH_CODE_DURABLE_MAXED) {
|
|
130
|
+
throw new common_1.DurableMaxedError(message, stack);
|
|
131
131
|
}
|
|
132
|
-
else if (code === common_1.
|
|
133
|
-
throw new common_1.
|
|
132
|
+
else if (code === common_1.HMSH_CODE_DURABLE_TIMEOUT) {
|
|
133
|
+
throw new common_1.DurableTimeoutError(message, stack);
|
|
134
134
|
}
|
|
135
135
|
else {
|
|
136
|
-
throw new common_1.
|
|
136
|
+
throw new common_1.DurableRetryError(message, stack);
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
return result.$error;
|
|
@@ -144,12 +144,12 @@ async function execChild(options) {
|
|
|
144
144
|
}
|
|
145
145
|
const interruptionMessage = getChildInterruptPayload(context, options, execIndex);
|
|
146
146
|
interruptionRegistry.push({
|
|
147
|
-
code: common_1.
|
|
148
|
-
type: '
|
|
147
|
+
code: common_1.HMSH_CODE_DURABLE_CHILD,
|
|
148
|
+
type: 'DurableChildError',
|
|
149
149
|
...interruptionMessage,
|
|
150
150
|
});
|
|
151
151
|
await (0, common_1.sleepImmediate)();
|
|
152
|
-
throw new common_1.
|
|
152
|
+
throw new common_1.DurableChildError(interruptionMessage);
|
|
153
153
|
}
|
|
154
154
|
exports.execChild = execChild;
|
|
155
155
|
/**
|
|
@@ -167,18 +167,18 @@ exports.executeChild = execChild;
|
|
|
167
167
|
* ## Example
|
|
168
168
|
*
|
|
169
169
|
* ```typescript
|
|
170
|
-
* import {
|
|
170
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
171
171
|
*
|
|
172
172
|
* export async function dispatchWorkflow(taskId: string): Promise<string> {
|
|
173
173
|
* // Fire-and-forget: start the child and continue immediately
|
|
174
|
-
* const childJobId = await
|
|
174
|
+
* const childJobId = await Durable.workflow.startChild({
|
|
175
175
|
* taskQueue: 'background',
|
|
176
176
|
* workflowName: 'longRunningTask',
|
|
177
177
|
* args: [taskId],
|
|
178
178
|
* });
|
|
179
179
|
*
|
|
180
180
|
* // Optionally store the child ID for monitoring
|
|
181
|
-
* const search = await
|
|
181
|
+
* const search = await Durable.workflow.search();
|
|
182
182
|
* await search.set({ childJobId });
|
|
183
183
|
*
|
|
184
184
|
* return childJobId;
|
|
@@ -17,8 +17,8 @@ export interface ExecHookOptions extends HookOptions {
|
|
|
17
17
|
*
|
|
18
18
|
* A `signalId` is automatically generated (or use the one you provide)
|
|
19
19
|
* and injected as the **last argument** to the hooked function as
|
|
20
|
-
* `{ signal: string, $
|
|
21
|
-
* `
|
|
20
|
+
* `{ signal: string, $durable: true }`. The hook function must call
|
|
21
|
+
* `Durable.workflow.signal(signalInfo.signal, result)` to deliver
|
|
22
22
|
* its response back to the waiting workflow.
|
|
23
23
|
*
|
|
24
24
|
* ## Difference from `execChild`
|
|
@@ -31,11 +31,11 @@ export interface ExecHookOptions extends HookOptions {
|
|
|
31
31
|
* ## Examples
|
|
32
32
|
*
|
|
33
33
|
* ```typescript
|
|
34
|
-
* import {
|
|
34
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
35
35
|
*
|
|
36
36
|
* // Orchestrator: spawn a hook and await its result
|
|
37
37
|
* export async function reviewWorkflow(docId: string): Promise<string> {
|
|
38
|
-
* const verdict = await
|
|
38
|
+
* const verdict = await Durable.workflow.execHook<{ approved: boolean }>({
|
|
39
39
|
* taskQueue: 'reviewers',
|
|
40
40
|
* workflowName: 'reviewDocument',
|
|
41
41
|
* args: [docId],
|
|
@@ -49,15 +49,15 @@ export interface ExecHookOptions extends HookOptions {
|
|
|
49
49
|
* // The hooked function (runs on the 'reviewers' worker)
|
|
50
50
|
* export async function reviewDocument(
|
|
51
51
|
* docId: string,
|
|
52
|
-
* signalInfo?: { signal: string; $
|
|
52
|
+
* signalInfo?: { signal: string; $durable: boolean },
|
|
53
53
|
* ): Promise<{ approved: boolean }> {
|
|
54
|
-
* const { analyzeDocument } =
|
|
54
|
+
* const { analyzeDocument } = Durable.workflow.proxyActivities<typeof activities>();
|
|
55
55
|
* const score = await analyzeDocument(docId);
|
|
56
56
|
* const result = { approved: score > 0.8 };
|
|
57
57
|
*
|
|
58
58
|
* // Signal the waiting workflow with the result
|
|
59
59
|
* if (signalInfo?.signal) {
|
|
60
|
-
* await
|
|
60
|
+
* await Durable.workflow.signal(signalInfo.signal, result);
|
|
61
61
|
* }
|
|
62
62
|
* return result;
|
|
63
63
|
* }
|
|
@@ -65,7 +65,7 @@ export interface ExecHookOptions extends HookOptions {
|
|
|
65
65
|
*
|
|
66
66
|
* ```typescript
|
|
67
67
|
* // With explicit signalId for traceability
|
|
68
|
-
* const result = await
|
|
68
|
+
* const result = await Durable.workflow.execHook<AnalysisResult>({
|
|
69
69
|
* taskQueue: 'analyzers',
|
|
70
70
|
* workflowName: 'runAnalysis',
|
|
71
71
|
* args: [datasetId],
|
|
@@ -14,8 +14,8 @@ const interruption_1 = require("./interruption");
|
|
|
14
14
|
*
|
|
15
15
|
* A `signalId` is automatically generated (or use the one you provide)
|
|
16
16
|
* and injected as the **last argument** to the hooked function as
|
|
17
|
-
* `{ signal: string, $
|
|
18
|
-
* `
|
|
17
|
+
* `{ signal: string, $durable: true }`. The hook function must call
|
|
18
|
+
* `Durable.workflow.signal(signalInfo.signal, result)` to deliver
|
|
19
19
|
* its response back to the waiting workflow.
|
|
20
20
|
*
|
|
21
21
|
* ## Difference from `execChild`
|
|
@@ -28,11 +28,11 @@ const interruption_1 = require("./interruption");
|
|
|
28
28
|
* ## Examples
|
|
29
29
|
*
|
|
30
30
|
* ```typescript
|
|
31
|
-
* import {
|
|
31
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
32
32
|
*
|
|
33
33
|
* // Orchestrator: spawn a hook and await its result
|
|
34
34
|
* export async function reviewWorkflow(docId: string): Promise<string> {
|
|
35
|
-
* const verdict = await
|
|
35
|
+
* const verdict = await Durable.workflow.execHook<{ approved: boolean }>({
|
|
36
36
|
* taskQueue: 'reviewers',
|
|
37
37
|
* workflowName: 'reviewDocument',
|
|
38
38
|
* args: [docId],
|
|
@@ -46,15 +46,15 @@ const interruption_1 = require("./interruption");
|
|
|
46
46
|
* // The hooked function (runs on the 'reviewers' worker)
|
|
47
47
|
* export async function reviewDocument(
|
|
48
48
|
* docId: string,
|
|
49
|
-
* signalInfo?: { signal: string; $
|
|
49
|
+
* signalInfo?: { signal: string; $durable: boolean },
|
|
50
50
|
* ): Promise<{ approved: boolean }> {
|
|
51
|
-
* const { analyzeDocument } =
|
|
51
|
+
* const { analyzeDocument } = Durable.workflow.proxyActivities<typeof activities>();
|
|
52
52
|
* const score = await analyzeDocument(docId);
|
|
53
53
|
* const result = { approved: score > 0.8 };
|
|
54
54
|
*
|
|
55
55
|
* // Signal the waiting workflow with the result
|
|
56
56
|
* if (signalInfo?.signal) {
|
|
57
|
-
* await
|
|
57
|
+
* await Durable.workflow.signal(signalInfo.signal, result);
|
|
58
58
|
* }
|
|
59
59
|
* return result;
|
|
60
60
|
* }
|
|
@@ -62,7 +62,7 @@ const interruption_1 = require("./interruption");
|
|
|
62
62
|
*
|
|
63
63
|
* ```typescript
|
|
64
64
|
* // With explicit signalId for traceability
|
|
65
|
-
* const result = await
|
|
65
|
+
* const result = await Durable.workflow.execHook<AnalysisResult>({
|
|
66
66
|
* taskQueue: 'analyzers',
|
|
67
67
|
* workflowName: 'runAnalysis',
|
|
68
68
|
* args: [datasetId],
|
|
@@ -77,11 +77,11 @@ const interruption_1 = require("./interruption");
|
|
|
77
77
|
async function execHook(options) {
|
|
78
78
|
try {
|
|
79
79
|
if (!options.signalId) {
|
|
80
|
-
options.signalId = '
|
|
80
|
+
options.signalId = 'durable-hook-' + crypto.randomUUID();
|
|
81
81
|
}
|
|
82
82
|
const hookOptions = {
|
|
83
83
|
...options,
|
|
84
|
-
args: [...options.args, { signal: options.signalId, $
|
|
84
|
+
args: [...options.args, { signal: options.signalId, $durable: true }],
|
|
85
85
|
};
|
|
86
86
|
// Execute the hook with the signal information
|
|
87
87
|
await (0, hook_1.hook)(hookOptions);
|
|
@@ -20,17 +20,17 @@ export interface BatchHookConfig<T = any> {
|
|
|
20
20
|
*
|
|
21
21
|
* 1. **Fire all hooks** via `Promise.all` (registers streams immediately).
|
|
22
22
|
* 2. **Await all signals** via `Promise.all` (all `waitFor` registrations
|
|
23
|
-
* happen together before any `
|
|
23
|
+
* happen together before any `DurableWaitForError` is thrown).
|
|
24
24
|
* 3. **Combine results** into a `{ [key]: result }` map.
|
|
25
25
|
*
|
|
26
26
|
* ## Examples
|
|
27
27
|
*
|
|
28
28
|
* ```typescript
|
|
29
|
-
* import {
|
|
29
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
30
30
|
*
|
|
31
31
|
* // Fan-out to multiple AI agents, gather all perspectives
|
|
32
32
|
* export async function researchWorkflow(query: string): Promise<Summary> {
|
|
33
|
-
* const perspectives = await
|
|
33
|
+
* const perspectives = await Durable.workflow.execHookBatch<{
|
|
34
34
|
* optimistic: PerspectiveResult;
|
|
35
35
|
* skeptical: PerspectiveResult;
|
|
36
36
|
* neutral: PerspectiveResult;
|
|
@@ -62,7 +62,7 @@ export interface BatchHookConfig<T = any> {
|
|
|
62
62
|
* ]);
|
|
63
63
|
*
|
|
64
64
|
* // All three results are available as typed properties
|
|
65
|
-
* const { synthesize } =
|
|
65
|
+
* const { synthesize } = Durable.workflow.proxyActivities<typeof activities>();
|
|
66
66
|
* return await synthesize(
|
|
67
67
|
* perspectives.optimistic,
|
|
68
68
|
* perspectives.skeptical,
|
|
@@ -73,7 +73,7 @@ export interface BatchHookConfig<T = any> {
|
|
|
73
73
|
*
|
|
74
74
|
* ```typescript
|
|
75
75
|
* // Parallel validation with different services
|
|
76
|
-
* const checks = await
|
|
76
|
+
* const checks = await Durable.workflow.execHookBatch<{
|
|
77
77
|
* fraud: { safe: boolean };
|
|
78
78
|
* compliance: { approved: boolean };
|
|
79
79
|
* }>([
|
|
@@ -14,17 +14,17 @@ const waitFor_1 = require("./waitFor");
|
|
|
14
14
|
*
|
|
15
15
|
* 1. **Fire all hooks** via `Promise.all` (registers streams immediately).
|
|
16
16
|
* 2. **Await all signals** via `Promise.all` (all `waitFor` registrations
|
|
17
|
-
* happen together before any `
|
|
17
|
+
* happen together before any `DurableWaitForError` is thrown).
|
|
18
18
|
* 3. **Combine results** into a `{ [key]: result }` map.
|
|
19
19
|
*
|
|
20
20
|
* ## Examples
|
|
21
21
|
*
|
|
22
22
|
* ```typescript
|
|
23
|
-
* import {
|
|
23
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
24
24
|
*
|
|
25
25
|
* // Fan-out to multiple AI agents, gather all perspectives
|
|
26
26
|
* export async function researchWorkflow(query: string): Promise<Summary> {
|
|
27
|
-
* const perspectives = await
|
|
27
|
+
* const perspectives = await Durable.workflow.execHookBatch<{
|
|
28
28
|
* optimistic: PerspectiveResult;
|
|
29
29
|
* skeptical: PerspectiveResult;
|
|
30
30
|
* neutral: PerspectiveResult;
|
|
@@ -56,7 +56,7 @@ const waitFor_1 = require("./waitFor");
|
|
|
56
56
|
* ]);
|
|
57
57
|
*
|
|
58
58
|
* // All three results are available as typed properties
|
|
59
|
-
* const { synthesize } =
|
|
59
|
+
* const { synthesize } = Durable.workflow.proxyActivities<typeof activities>();
|
|
60
60
|
* return await synthesize(
|
|
61
61
|
* perspectives.optimistic,
|
|
62
62
|
* perspectives.skeptical,
|
|
@@ -67,7 +67,7 @@ const waitFor_1 = require("./waitFor");
|
|
|
67
67
|
*
|
|
68
68
|
* ```typescript
|
|
69
69
|
* // Parallel validation with different services
|
|
70
|
-
* const checks = await
|
|
70
|
+
* const checks = await Durable.workflow.execHookBatch<{
|
|
71
71
|
* fraud: { safe: boolean };
|
|
72
72
|
* compliance: { approved: boolean };
|
|
73
73
|
* }>([
|
|
@@ -104,7 +104,7 @@ async function execHookBatch(hookConfigs) {
|
|
|
104
104
|
...config,
|
|
105
105
|
options: {
|
|
106
106
|
...config.options,
|
|
107
|
-
signalId: config.options.signalId || `
|
|
107
|
+
signalId: config.options.signalId || `durable-hook-${crypto.randomUUID()}`
|
|
108
108
|
}
|
|
109
109
|
}));
|
|
110
110
|
// STEP 1: Fire off all hooks (but don't await them)
|
|
@@ -114,14 +114,14 @@ async function execHookBatch(hookConfigs) {
|
|
|
114
114
|
...config.options,
|
|
115
115
|
args: [...config.options.args, {
|
|
116
116
|
signal: config.options.signalId,
|
|
117
|
-
$
|
|
117
|
+
$durable: true
|
|
118
118
|
}]
|
|
119
119
|
};
|
|
120
120
|
return (0, hook_1.hook)(hookOptions);
|
|
121
121
|
}));
|
|
122
122
|
// STEP 2: Await all waitFor operations
|
|
123
123
|
// This ensures all waitFor registrations happen in the same call stack
|
|
124
|
-
// before any
|
|
124
|
+
// before any DurableWaitForError is thrown (via setImmediate mechanism)
|
|
125
125
|
const results = await Promise.all(processedConfigs.map(config => (0, waitFor_1.waitFor)(config.options.signalId)));
|
|
126
126
|
// STEP 3: Return results as a keyed object
|
|
127
127
|
return Object.fromEntries(processedConfigs.map((config, i) => [config.key, results[i]]));
|
|
@@ -25,11 +25,11 @@ import { HookOptions } from './common';
|
|
|
25
25
|
* ## Examples
|
|
26
26
|
*
|
|
27
27
|
* ```typescript
|
|
28
|
-
* import {
|
|
28
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
29
29
|
*
|
|
30
30
|
* // Fire-and-forget: spawn a hook without waiting for its result
|
|
31
31
|
* export async function notifyWorkflow(userId: string): Promise<void> {
|
|
32
|
-
* await
|
|
32
|
+
* await Durable.workflow.hook({
|
|
33
33
|
* taskQueue: 'notifications',
|
|
34
34
|
* workflowName: 'sendNotification',
|
|
35
35
|
* args: [userId, 'Your order has shipped'],
|
|
@@ -43,20 +43,20 @@ import { HookOptions } from './common';
|
|
|
43
43
|
* export async function manualHookPattern(itemId: string): Promise<string> {
|
|
44
44
|
* const signalId = `process-${itemId}`;
|
|
45
45
|
*
|
|
46
|
-
* await
|
|
46
|
+
* await Durable.workflow.hook({
|
|
47
47
|
* taskQueue: 'processors',
|
|
48
48
|
* workflowName: 'processItem',
|
|
49
49
|
* args: [itemId, signalId],
|
|
50
50
|
* });
|
|
51
51
|
*
|
|
52
52
|
* // Manually wait for the hook to signal back
|
|
53
|
-
* return await
|
|
53
|
+
* return await Durable.workflow.waitFor<string>(signalId);
|
|
54
54
|
* }
|
|
55
55
|
* ```
|
|
56
56
|
*
|
|
57
57
|
* ```typescript
|
|
58
58
|
* // Hook with retry configuration
|
|
59
|
-
* await
|
|
59
|
+
* await Durable.workflow.hook({
|
|
60
60
|
* taskQueue: 'enrichment',
|
|
61
61
|
* workflowName: 'enrichProfile',
|
|
62
62
|
* args: [profileId],
|
|
@@ -30,11 +30,11 @@ const isSideEffectAllowed_1 = require("./isSideEffectAllowed");
|
|
|
30
30
|
* ## Examples
|
|
31
31
|
*
|
|
32
32
|
* ```typescript
|
|
33
|
-
* import {
|
|
33
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
34
34
|
*
|
|
35
35
|
* // Fire-and-forget: spawn a hook without waiting for its result
|
|
36
36
|
* export async function notifyWorkflow(userId: string): Promise<void> {
|
|
37
|
-
* await
|
|
37
|
+
* await Durable.workflow.hook({
|
|
38
38
|
* taskQueue: 'notifications',
|
|
39
39
|
* workflowName: 'sendNotification',
|
|
40
40
|
* args: [userId, 'Your order has shipped'],
|
|
@@ -48,20 +48,20 @@ const isSideEffectAllowed_1 = require("./isSideEffectAllowed");
|
|
|
48
48
|
* export async function manualHookPattern(itemId: string): Promise<string> {
|
|
49
49
|
* const signalId = `process-${itemId}`;
|
|
50
50
|
*
|
|
51
|
-
* await
|
|
51
|
+
* await Durable.workflow.hook({
|
|
52
52
|
* taskQueue: 'processors',
|
|
53
53
|
* workflowName: 'processItem',
|
|
54
54
|
* args: [itemId, signalId],
|
|
55
55
|
* });
|
|
56
56
|
*
|
|
57
57
|
* // Manually wait for the hook to signal back
|
|
58
|
-
* return await
|
|
58
|
+
* return await Durable.workflow.waitFor<string>(signalId);
|
|
59
59
|
* }
|
|
60
60
|
* ```
|
|
61
61
|
*
|
|
62
62
|
* ```typescript
|
|
63
63
|
* // Hook with retry configuration
|
|
64
|
-
* await
|
|
64
|
+
* await Durable.workflow.hook({
|
|
65
65
|
* taskQueue: 'enrichment',
|
|
66
66
|
* workflowName: 'enrichProfile',
|
|
67
67
|
* args: [profileId],
|
|
@@ -95,12 +95,12 @@ async function hook(options) {
|
|
|
95
95
|
if (targetTopic === workflowTopic &&
|
|
96
96
|
!options.entity &&
|
|
97
97
|
!options.taskQueue) {
|
|
98
|
-
throw new Error(`
|
|
98
|
+
throw new Error(`Durable Hook Error: Potential infinite loop detected!\n\n` +
|
|
99
99
|
`The hook would target the same workflow topic ('${workflowTopic}') as the current workflow, ` +
|
|
100
100
|
`creating an infinite loop.\n\n` +
|
|
101
101
|
`To fix this, provide either:\n` +
|
|
102
|
-
`1. 'taskQueue' parameter:
|
|
103
|
-
`2. 'entity' parameter:
|
|
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
104
|
`Current workflow topic: ${workflowTopic}\n` +
|
|
105
105
|
`Target topic would be: ${targetTopic}\n` +
|
|
106
106
|
`Provided options: ${JSON.stringify({
|
|
@@ -113,9 +113,9 @@ async function hook(options) {
|
|
|
113
113
|
arguments: [...options.args],
|
|
114
114
|
id: targetWorkflowId,
|
|
115
115
|
workflowTopic: targetTopic,
|
|
116
|
-
backoffCoefficient: options.config?.backoffCoefficient || common_1.
|
|
117
|
-
maximumAttempts: options.config?.maximumAttempts || common_1.
|
|
118
|
-
maximumInterval: (0, common_1.s)(options?.config?.maximumInterval ?? common_1.
|
|
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
119
|
};
|
|
120
120
|
return await hotMeshClient.signal(`${namespace}.flow.signal`, payload, common_1.StreamStatus.PENDING, 202);
|
|
121
121
|
}
|
|
@@ -20,7 +20,7 @@ import { waitFor } from './waitFor';
|
|
|
20
20
|
import { HotMesh } from './common';
|
|
21
21
|
import { entity } from './entityMethods';
|
|
22
22
|
/**
|
|
23
|
-
* The workflow-internal API surface, exposed as `
|
|
23
|
+
* The workflow-internal API surface, exposed as `Durable.workflow`. Every
|
|
24
24
|
* method on this class is designed to be called **inside** a workflow
|
|
25
25
|
* function — they participate in deterministic replay and durable state
|
|
26
26
|
* management.
|
|
@@ -62,13 +62,13 @@ import { entity } from './entityMethods';
|
|
|
62
62
|
* ## Example
|
|
63
63
|
*
|
|
64
64
|
* ```typescript
|
|
65
|
-
* import {
|
|
65
|
+
* import { Durable } from '@hotmeshio/hotmesh';
|
|
66
66
|
* import * as activities from './activities';
|
|
67
67
|
*
|
|
68
68
|
* export async function orderWorkflow(orderId: string): Promise<string> {
|
|
69
69
|
* // Proxy activities for durable execution
|
|
70
70
|
* const { validateOrder, processPayment, sendReceipt } =
|
|
71
|
-
*
|
|
71
|
+
* Durable.workflow.proxyActivities<typeof activities>({
|
|
72
72
|
* activities,
|
|
73
73
|
* retryPolicy: { maximumAttempts: 3 },
|
|
74
74
|
* });
|
|
@@ -76,15 +76,15 @@ import { entity } from './entityMethods';
|
|
|
76
76
|
* await validateOrder(orderId);
|
|
77
77
|
*
|
|
78
78
|
* // Durable sleep (survives restarts)
|
|
79
|
-
* await
|
|
79
|
+
* await Durable.workflow.sleepFor('5 seconds');
|
|
80
80
|
*
|
|
81
81
|
* const receipt = await processPayment(orderId);
|
|
82
82
|
*
|
|
83
83
|
* // Store searchable metadata
|
|
84
|
-
* await
|
|
84
|
+
* await Durable.workflow.enrich({ orderId, status: 'paid' });
|
|
85
85
|
*
|
|
86
86
|
* // Wait for external approval signal
|
|
87
|
-
* const approval = await
|
|
87
|
+
* const approval = await Durable.workflow.waitFor<{ ok: boolean }>('approve');
|
|
88
88
|
* if (!approval.ok) return 'cancelled';
|
|
89
89
|
*
|
|
90
90
|
* await sendReceipt(orderId, receipt);
|