@hotmeshio/hotmesh 0.5.0 β 0.5.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 +230 -237
- package/build/modules/errors.d.ts +9 -0
- package/build/modules/errors.js +9 -0
- package/build/package.json +1 -1
- package/build/services/memflow/index.d.ts +8 -0
- package/build/services/memflow/index.js +8 -0
- package/build/services/memflow/worker.js +25 -0
- package/build/services/memflow/workflow/execChild.js +1 -0
- package/build/services/memflow/workflow/execHook.d.ts +2 -2
- package/build/services/memflow/workflow/execHook.js +19 -9
- package/build/services/memflow/workflow/interruption.d.ts +26 -0
- package/build/services/memflow/workflow/interruption.js +41 -0
- package/build/services/memflow/workflow/proxyActivities.js +1 -0
- package/build/services/memflow/workflow/sleepFor.js +1 -0
- package/build/services/memflow/workflow/waitFor.js +3 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,44 +1,52 @@
|
|
|
1
1
|
# HotMesh
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**π§ Workflow That Remembers**
|
|
4
4
|
|
|
5
5
|
 
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
HotMesh brings a **memory model** to your automation: durable entities that hold context, support concurrency, and evolve across runs. Built on PostgreSQL, it treats your database not just as storageβbut as the runtime hub for agents, pipelines, and long-lived processes.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Use HotMesh to:
|
|
10
10
|
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* **Store Evolving State** β Retain memory/state between executions
|
|
12
|
+
* **Coordinate Distributed Work** β Safely allow multiple workers to act on shared state
|
|
13
|
+
* **Track and Replay** β Full audit history and replay support by default
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
17
|
## Table of Contents
|
|
18
18
|
|
|
19
|
-
1.
|
|
20
|
-
2.
|
|
21
|
-
3.
|
|
22
|
-
4.
|
|
23
|
-
5.
|
|
24
|
-
6. π Documentation & Links
|
|
19
|
+
1. [Quick Start](#-quick-start)
|
|
20
|
+
2. [Permanent Memory Architecture](#-permanent-memory-architecture)
|
|
21
|
+
3. [Durable AI Agents](#-durable-ai-agents)
|
|
22
|
+
4. [Building Pipelines with State](#-building-pipelines-with-state)
|
|
23
|
+
5. [Documentation & Links](#-documentation--links)
|
|
25
24
|
|
|
26
25
|
---
|
|
27
26
|
|
|
28
|
-
##
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
### Prerequisites
|
|
30
|
+
|
|
31
|
+
* PostgreSQL (or Supabase)
|
|
32
|
+
* Node.js 16+
|
|
29
33
|
|
|
30
34
|
### Install
|
|
35
|
+
|
|
31
36
|
```bash
|
|
32
37
|
npm install @hotmeshio/hotmesh
|
|
33
38
|
```
|
|
34
39
|
|
|
35
|
-
###
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
### Connect to a Database
|
|
41
|
+
|
|
42
|
+
HotMesh leverages Temporal.io's developer-friendly syntax for authoring workers, workflows, and clients. The `init` and `start` methods should look familiar.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
38
45
|
import { MemFlow } from '@hotmeshio/hotmesh';
|
|
39
46
|
import { Client as Postgres } from 'pg';
|
|
40
47
|
|
|
41
48
|
async function main() {
|
|
49
|
+
// MemFlow will auto-provision the database upon init
|
|
42
50
|
const mf = await MemFlow.init({
|
|
43
51
|
appId: 'my-app',
|
|
44
52
|
engine: {
|
|
@@ -49,13 +57,13 @@ async function main() {
|
|
|
49
57
|
}
|
|
50
58
|
});
|
|
51
59
|
|
|
52
|
-
//
|
|
60
|
+
// Start a workflow with an assigned ID and arguments
|
|
53
61
|
const handle = await mf.workflow.start({
|
|
54
|
-
entity: '
|
|
55
|
-
workflowName: '
|
|
56
|
-
workflowId: 'jane
|
|
57
|
-
args: ['
|
|
58
|
-
taskQueue: '
|
|
62
|
+
entity: 'research-agent',
|
|
63
|
+
workflowName: 'researchAgent',
|
|
64
|
+
workflowId: 'agent-session-jane-001',
|
|
65
|
+
args: ['What are the long-term impacts of renewable energy subsidies?'],
|
|
66
|
+
taskQueue: 'agents'
|
|
59
67
|
});
|
|
60
68
|
|
|
61
69
|
console.log('Result:', await handle.result());
|
|
@@ -64,168 +72,53 @@ async function main() {
|
|
|
64
72
|
main().catch(console.error);
|
|
65
73
|
```
|
|
66
74
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
## π§ How Permanent Memory Works
|
|
70
|
-
|
|
71
|
-
* **Entity = persistent JSON record** β each workflow's memory is stored as a JSONB row in your Postgres database
|
|
72
|
-
* **Atomic operations** (`set`, `merge`, `append`, `increment`, `toggle`, `delete`, β¦)
|
|
73
|
-
* **Transactional** β every update participates in the workflow/DB transaction
|
|
74
|
-
* **Time-travel-safe** β full replay compatibility; side-effect detector guarantees determinism
|
|
75
|
-
* **Hook-friendly** β any worker with the record ID can attach and mutate its slice of the JSON
|
|
76
|
-
* **Index-friendly** - entity data is stored as JSONB; add partial indexes for improved query analysis.
|
|
75
|
+
### System Benefits
|
|
77
76
|
|
|
78
|
-
**
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
```
|
|
84
|
-
This index will only be used for queries that match both conditions, making lookups for premium users much faster.
|
|
77
|
+
* **No Setup Required** β Tables and indexes are provisioned automatically
|
|
78
|
+
* **Shared State** β Every worker shares access to the same entity memory
|
|
79
|
+
* **Coordination by Design** β PostgreSQL handles consistency and isolation
|
|
80
|
+
* **Tenant Isolation** β Each app maintains its own schema
|
|
81
|
+
* **Scalable Defaults** β Partitioned tables and index support included
|
|
85
82
|
|
|
86
83
|
---
|
|
87
84
|
|
|
88
|
-
##
|
|
89
|
-
|
|
90
|
-
HotMesh hooks are powerful because they can be called both internally (from within a workflow) and externally (from outside, even after the workflow completes). This means you can:
|
|
91
|
-
|
|
92
|
-
* Start a workflow that sets up initial state
|
|
93
|
-
* Have the workflow call some hooks internally
|
|
94
|
-
* Let the workflow complete
|
|
95
|
-
* Continue to update the workflow's entity state from the outside via hooks
|
|
96
|
-
* Build long-running processes that evolve over time
|
|
97
|
-
|
|
98
|
-
Here's a complete example showing both internal and external hook usage:
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
import { MemFlow } from '@hotmeshio/hotmesh';
|
|
102
|
-
|
|
103
|
-
/* ------------ Main workflow ------------ */
|
|
104
|
-
export async function userExample(name: string): Promise<any> {
|
|
105
|
-
//the entity method provides transactional, replayable access to shared job state
|
|
106
|
-
const entity = await MemFlow.workflow.entity();
|
|
107
|
-
|
|
108
|
-
//create the initial entity (even arrays are supported)
|
|
109
|
-
await entity.set({
|
|
110
|
-
user: { name },
|
|
111
|
-
hooks: {},
|
|
112
|
-
metrics: { count: 0 }
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// Call one hook internally
|
|
116
|
-
const result1 = await MemFlow.workflow.execHook({
|
|
117
|
-
taskQueue: 'entityqueue',
|
|
118
|
-
workflowName: 'hook1',
|
|
119
|
-
args: [name, 'hook1'],
|
|
120
|
-
signalId: 'hook1-complete'
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// merge the result
|
|
124
|
-
await entity.merge({ hooks: { r1: result1 } });
|
|
125
|
-
await entity.increment('metrics.count', 1);
|
|
126
|
-
|
|
127
|
-
return "The main has completed; the db record persists and can be hydrated; hook in from the outside!";
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/* ------------ Hook 1 (hooks have access to methods like sleepFor) ------------ */
|
|
131
|
-
export async function hook1(name: string, kind: string): Promise<any> {
|
|
132
|
-
await MemFlow.workflow.sleepFor('2 seconds');
|
|
133
|
-
const res = { kind, processed: true, at: Date.now() };
|
|
134
|
-
await MemFlow.workflow.signal('hook1-complete', res);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/* ------------ Hook 2 (hooks can access shared job entity) ------------ */
|
|
138
|
-
export async function hook2(name: string, kind: string): Promise<void> {
|
|
139
|
-
const entity = await MemFlow.workflow.entity();
|
|
140
|
-
await entity.merge({ user: { lastSeen: new Date().toISOString() } });
|
|
141
|
-
await MemFlow.workflow.signal('hook2-complete', { ok: true });
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/* ------------ Worker/Hook Registration ------------ */
|
|
145
|
-
async function startWorker() {
|
|
146
|
-
const mf = await MemFlow.init({
|
|
147
|
-
appId: 'my-app',
|
|
148
|
-
engine: {
|
|
149
|
-
connection: {
|
|
150
|
-
class: Postgres,
|
|
151
|
-
options: { connectionString: process.env.DATABASE_URL }
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
});
|
|
85
|
+
## Permanent Memory Architecture
|
|
155
86
|
|
|
156
|
-
|
|
157
|
-
taskQueue: 'entityqueue',
|
|
158
|
-
workflow: example
|
|
159
|
-
});
|
|
87
|
+
Every workflow in HotMesh is backed by an "entity": a versioned, JSONB record that tracks its memory and state transitions.
|
|
160
88
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
89
|
+
* **Entities** β Represent long-lived state for a workflow or agent
|
|
90
|
+
* **Commands** β Modify state with methods like `set`, `merge`, `append`, `increment`
|
|
91
|
+
* **Consistency** β All updates are transactional with Postgres
|
|
92
|
+
* **Replay Safety** β Protects against duplicated side effects during re-execution
|
|
93
|
+
* **Partial Indexing** β Optimized querying of fields within large JSON structures
|
|
165
94
|
|
|
166
|
-
|
|
167
|
-
taskQueue: 'entityqueue',
|
|
168
|
-
workflow: hook2
|
|
169
|
-
});
|
|
95
|
+
### Example: Partial Index for Premium Users
|
|
170
96
|
|
|
171
|
-
|
|
172
|
-
|
|
97
|
+
```sql
|
|
98
|
+
-- Index only those user entities that are marked as premium
|
|
99
|
+
CREATE INDEX idx_user_premium ON your_app.jobs (id)
|
|
100
|
+
WHERE entity = 'user' AND (context->>'isPremium')::boolean = true;
|
|
173
101
|
```
|
|
174
102
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
One of HotMesh's most powerful features is that workflow entities remain accessible even after the main workflow completes. By providing the original workflow ID, any authorized client can:
|
|
178
|
-
|
|
179
|
-
* Hook into existing workflow entities
|
|
180
|
-
* Update state and trigger new processing
|
|
181
|
-
* Build evolving, long-running processes
|
|
182
|
-
* Enable human-in-the-loop workflows
|
|
183
|
-
* Create AI agents that learn over time
|
|
184
|
-
|
|
185
|
-
Here's how to hook into an existing workflow from the outside:
|
|
186
|
-
|
|
187
|
-
```typescript
|
|
188
|
-
/* ------------ External Hook Example ------------ */
|
|
189
|
-
async function externalHookExample() {
|
|
190
|
-
const client = new MemFlow.Client({
|
|
191
|
-
appId: 'my-app',
|
|
192
|
-
engine: {
|
|
193
|
-
connection: {
|
|
194
|
-
class: Postgres,
|
|
195
|
-
options: { connectionString: process.env.DATABASE_URL }
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
// Start hook2 externally by providing the original workflow ID
|
|
201
|
-
await client.workflow.hook({
|
|
202
|
-
workflowId: 'jane@hotmesh.com', //id of the target workflow
|
|
203
|
-
taskQueue: 'entityqueue',
|
|
204
|
-
workflowName: 'hook2',
|
|
205
|
-
args: [name, 'external-hook']
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
```
|
|
103
|
+
This index improves performance for filtered queries while reducing index size.
|
|
209
104
|
|
|
210
105
|
---
|
|
211
106
|
|
|
212
|
-
##
|
|
107
|
+
## Durable AI Agents
|
|
108
|
+
|
|
109
|
+
Agents often require memoryβcontext that persists between invocations, spans multiple perspectives, or outlives a single process. HotMesh supports that natively.
|
|
213
110
|
|
|
214
|
-
|
|
111
|
+
The following example builds a "research agent" that runs sub-flows and self-reflects on the results.
|
|
215
112
|
|
|
216
|
-
|
|
217
|
-
* **Hooks** = different perspectives that operate on that context
|
|
218
|
-
* **Child workflows** = specialized co-agents with their own entity types
|
|
219
|
-
* **Shared memory** = enables rich self-reflection and multi-agent collaboration
|
|
113
|
+
### Research Agent Example
|
|
220
114
|
|
|
221
|
-
|
|
115
|
+
#### Main Coordinator Agent
|
|
222
116
|
|
|
223
|
-
```
|
|
224
|
-
/* ------------ Main Research Agent ------------ */
|
|
117
|
+
```ts
|
|
225
118
|
export async function researchAgent(query: string): Promise<any> {
|
|
226
119
|
const entity = await MemFlow.workflow.entity();
|
|
227
|
-
|
|
228
|
-
//
|
|
120
|
+
|
|
121
|
+
// Set up shared memory for this agent session
|
|
229
122
|
await entity.set({
|
|
230
123
|
query,
|
|
231
124
|
findings: [],
|
|
@@ -234,20 +127,20 @@ export async function researchAgent(query: string): Promise<any> {
|
|
|
234
127
|
status: 'researching'
|
|
235
128
|
});
|
|
236
129
|
|
|
237
|
-
//
|
|
238
|
-
const
|
|
239
|
-
taskQueue: '
|
|
130
|
+
// Launch perspective hooks in parallel (no need to await here)
|
|
131
|
+
const optimistic = MemFlow.workflow.execHook({
|
|
132
|
+
taskQueue: 'agents',
|
|
240
133
|
workflowName: 'optimisticPerspective',
|
|
241
134
|
args: [query]
|
|
242
135
|
});
|
|
243
136
|
|
|
244
|
-
const
|
|
245
|
-
taskQueue: '
|
|
137
|
+
const skeptical = MemFlow.workflow.execHook({
|
|
138
|
+
taskQueue: 'agents',
|
|
246
139
|
workflowName: 'skepticalPerspective',
|
|
247
140
|
args: [query]
|
|
248
141
|
});
|
|
249
142
|
|
|
250
|
-
//
|
|
143
|
+
// Launch a child workflow with its own isolated entity/state
|
|
251
144
|
const factChecker = await MemFlow.workflow.execChild({
|
|
252
145
|
entity: 'fact-checker',
|
|
253
146
|
workflowName: 'factCheckAgent',
|
|
@@ -256,121 +149,221 @@ export async function researchAgent(query: string): Promise<any> {
|
|
|
256
149
|
taskQueue: 'agents'
|
|
257
150
|
});
|
|
258
151
|
|
|
259
|
-
// Wait for all
|
|
260
|
-
await Promise.all([
|
|
152
|
+
// Wait for all views to complete before analyzing
|
|
153
|
+
await Promise.all([optimistic, skeptical, factChecker]);
|
|
261
154
|
|
|
262
|
-
//
|
|
155
|
+
// Final synthesis: aggregate and compare all perspectives
|
|
263
156
|
await MemFlow.workflow.execHook({
|
|
264
157
|
taskQueue: 'perspectives',
|
|
265
158
|
workflowName: 'synthesizePerspectives',
|
|
266
159
|
args: []
|
|
267
160
|
});
|
|
268
161
|
|
|
269
|
-
|
|
270
|
-
return finalContext;
|
|
162
|
+
return await entity.get();
|
|
271
163
|
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
#### Hooks: Perspectives
|
|
272
167
|
|
|
273
|
-
|
|
274
|
-
|
|
168
|
+
```ts
|
|
169
|
+
// Optimistic hook looks for affirming evidence
|
|
170
|
+
export async function optimisticPerspective(query: string, config: {signal: string}): Promise<void> {
|
|
275
171
|
const entity = await MemFlow.workflow.entity();
|
|
276
|
-
|
|
277
|
-
// Optimistic perspective: look for supporting evidence
|
|
278
172
|
const findings = await searchForSupportingEvidence(query);
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
optimistic: {
|
|
283
|
-
findings,
|
|
284
|
-
confidence: 0.8,
|
|
285
|
-
bias: 'Tends to emphasize positive evidence'
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
});
|
|
173
|
+
await entity.merge({ perspectives: { optimistic: { findings, confidence: 0.8 }}});
|
|
174
|
+
//signal the caller to notify all done
|
|
175
|
+
await MemFlow.workflow.signal(config.signal, {});
|
|
289
176
|
}
|
|
290
177
|
|
|
291
|
-
|
|
292
|
-
export async function skepticalPerspective(query: string): Promise<void> {
|
|
178
|
+
// Skeptical hook seeks out contradictions and counterpoints
|
|
179
|
+
export async function skepticalPerspective(query: string, config: {signal: string}): Promise<void> {
|
|
293
180
|
const entity = await MemFlow.workflow.entity();
|
|
294
|
-
|
|
295
|
-
// Skeptical perspective: challenge assumptions
|
|
296
181
|
const counterEvidence = await searchForCounterEvidence(query);
|
|
297
|
-
|
|
298
|
-
await
|
|
299
|
-
perspectives: {
|
|
300
|
-
skeptical: {
|
|
301
|
-
counterEvidence,
|
|
302
|
-
confidence: 0.6,
|
|
303
|
-
bias: 'Challenges assumptions and seeks contradictory evidence'
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
});
|
|
182
|
+
await entity.merge({ perspectives: { skeptical: { counterEvidence, confidence: 0.6 }}});
|
|
183
|
+
await MemFlow.workflow.signal(config.signal, {});
|
|
307
184
|
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### Child Agent: Fact Checker
|
|
308
188
|
|
|
309
|
-
|
|
189
|
+
```ts
|
|
190
|
+
// A dedicated child agent with its own entity type and context
|
|
310
191
|
export async function factCheckAgent(query: string): Promise<any> {
|
|
311
|
-
// This child has its own entity type for specialized fact-checking context
|
|
312
192
|
const entity = await MemFlow.workflow.entity();
|
|
313
|
-
|
|
314
|
-
await entity.set({
|
|
315
|
-
query,
|
|
316
|
-
sources: [],
|
|
317
|
-
verifications: [],
|
|
318
|
-
credibilityScore: 0
|
|
319
|
-
});
|
|
193
|
+
await entity.set({ query, sources: [], verifications: [] });
|
|
320
194
|
|
|
321
|
-
// Fact-checker can spawn its own specialized hooks
|
|
322
195
|
await MemFlow.workflow.execHook({
|
|
323
|
-
taskQueue: '
|
|
196
|
+
taskQueue: 'agents',
|
|
324
197
|
workflowName: 'verifySourceCredibility',
|
|
325
198
|
args: [query]
|
|
326
199
|
});
|
|
327
200
|
|
|
328
201
|
return await entity.get();
|
|
329
202
|
}
|
|
203
|
+
```
|
|
330
204
|
|
|
331
|
-
|
|
332
|
-
|
|
205
|
+
#### Synthesis
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
// Synthesis hook aggregates different viewpoints
|
|
209
|
+
export async function synthesizePerspectives(config: {signal: string}): Promise<void> {
|
|
333
210
|
const entity = await MemFlow.workflow.entity();
|
|
334
211
|
const context = await entity.get();
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
212
|
+
|
|
213
|
+
const result = await analyzePerspectives(context.perspectives);
|
|
214
|
+
|
|
339
215
|
await entity.merge({
|
|
340
216
|
perspectives: {
|
|
341
217
|
synthesis: {
|
|
342
|
-
finalAssessment:
|
|
343
|
-
confidence: calculateConfidence(context.perspectives)
|
|
344
|
-
reasoning: 'Balanced analysis of optimistic and skeptical viewpoints'
|
|
218
|
+
finalAssessment: result,
|
|
219
|
+
confidence: calculateConfidence(context.perspectives)
|
|
345
220
|
}
|
|
346
221
|
},
|
|
347
222
|
status: 'completed'
|
|
348
223
|
});
|
|
224
|
+
await MemFlow.workflow.signal(config.signal, {});
|
|
349
225
|
}
|
|
350
226
|
```
|
|
351
227
|
|
|
352
|
-
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Building Pipelines with State
|
|
353
231
|
|
|
354
|
-
|
|
232
|
+
HotMesh treats pipelines as long-lived records, not ephemeral jobs. Every pipeline run is stateful, resumable, and traceable.
|
|
355
233
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
* **Self-reflect** β synthesis hooks analyze conflicting perspectives and improve decision-making
|
|
234
|
+
### Setup a Data Pipeline
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
export async function dataPipeline(source: string): Promise<void> {
|
|
238
|
+
const entity = await MemFlow.workflow.entity();
|
|
362
239
|
|
|
363
|
-
|
|
240
|
+
// Initial policy and tracking setup
|
|
241
|
+
await entity.set({
|
|
242
|
+
source,
|
|
243
|
+
pipeline: { version: 1, policy: { refreshInterval: '24 hours' } },
|
|
244
|
+
changeLog: []
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Trigger the recurring orchestration pipeline
|
|
248
|
+
await MemFlow.workflow.execHook({
|
|
249
|
+
taskQueue: 'pipeline',
|
|
250
|
+
workflowName: 'runPipeline',
|
|
251
|
+
args: [true]
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Orchestration Hook
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
export async function runPipeline(repeat = false): Promise<void> {
|
|
260
|
+
do {
|
|
261
|
+
// Perform transformation step
|
|
262
|
+
await MemFlow.workflow.execHook({
|
|
263
|
+
taskQueue: 'transform',
|
|
264
|
+
workflowName: 'cleanData',
|
|
265
|
+
args: []
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
if (repeat) {
|
|
269
|
+
// Schedule next execution
|
|
270
|
+
await MemFlow.workflow.execHook({
|
|
271
|
+
taskQueue: 'scheduler',
|
|
272
|
+
workflowName: 'scheduleRefresh',
|
|
273
|
+
args: []
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
} while (repeat)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Hook to clean and transform data
|
|
281
|
+
*/
|
|
282
|
+
export async function cleanData(signalInfo?: { signal: string }): Promise<void> {
|
|
283
|
+
const entity = await MemFlow.workflow.entity();
|
|
284
|
+
|
|
285
|
+
// Simulate data cleaning
|
|
286
|
+
await entity.merge({
|
|
287
|
+
status: 'cleaning',
|
|
288
|
+
lastCleanedAt: new Date().toISOString()
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Add to changelog
|
|
292
|
+
await entity.append('changeLog', {
|
|
293
|
+
action: 'clean',
|
|
294
|
+
timestamp: new Date().toISOString()
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// Signal completion if called via execHook
|
|
298
|
+
if (signalInfo?.signal) {
|
|
299
|
+
await MemFlow.workflow.signal(signalInfo.signal, {
|
|
300
|
+
status: 'cleaned',
|
|
301
|
+
timestamp: new Date().toISOString()
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Hook to schedule the next refresh based on policy
|
|
308
|
+
*/
|
|
309
|
+
export async function scheduleRefresh(signalInfo?: { signal: string }): Promise<void> {
|
|
310
|
+
const entity = await MemFlow.workflow.entity();
|
|
311
|
+
|
|
312
|
+
// Get refresh interval from policy
|
|
313
|
+
const currentEntity = await entity.get();
|
|
314
|
+
const refreshInterval = currentEntity.pipeline.policy.refreshInterval;
|
|
315
|
+
|
|
316
|
+
// Sleep for the configured interval
|
|
317
|
+
await MemFlow.workflow.sleepFor(refreshInterval);
|
|
318
|
+
|
|
319
|
+
// Update status after sleep
|
|
320
|
+
await entity.merge({
|
|
321
|
+
status: 'ready_for_refresh',
|
|
322
|
+
nextRefreshAt: new Date().toISOString()
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Add to changelog
|
|
326
|
+
await entity.append('changeLog', {
|
|
327
|
+
action: 'schedule_refresh',
|
|
328
|
+
timestamp: new Date().toISOString(),
|
|
329
|
+
nextRefresh: new Date().toISOString()
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Signal completion if called via execHook
|
|
333
|
+
if (signalInfo?.signal) {
|
|
334
|
+
await MemFlow.workflow.signal(signalInfo.signal, {
|
|
335
|
+
status: 'scheduled',
|
|
336
|
+
nextRefresh: new Date().toISOString()
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Trigger from Outside
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
// External systems can trigger a single pipeline run
|
|
346
|
+
export async function triggerRefresh() {
|
|
347
|
+
const client = new MemFlow.Client({/*...*/});
|
|
348
|
+
|
|
349
|
+
await client.workflow.hook({
|
|
350
|
+
workflowId: 'pipeline-123',
|
|
351
|
+
taskQueue: 'pipeline',
|
|
352
|
+
workflowName: 'runPipeline',
|
|
353
|
+
args: []
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
```
|
|
364
357
|
|
|
365
358
|
---
|
|
366
359
|
|
|
367
|
-
##
|
|
360
|
+
## Documentation & Links
|
|
368
361
|
|
|
369
|
-
* SDK
|
|
370
|
-
* Examples β [
|
|
362
|
+
* SDK Reference β [hotmeshio.github.io/sdk-typescript](https://hotmeshio.github.io/sdk-typescript)
|
|
363
|
+
* Examples β [github.com/hotmeshio/samples-typescript](https://github.com/hotmeshio/samples-typescript)
|
|
371
364
|
|
|
372
365
|
---
|
|
373
366
|
|
|
374
367
|
## License
|
|
375
368
|
|
|
376
|
-
Apache 2.0 β
|
|
369
|
+
Apache 2.0 β See `LICENSE` for details.
|
|
@@ -15,6 +15,7 @@ declare class MemFlowWaitForError extends Error {
|
|
|
15
15
|
workflowId: string;
|
|
16
16
|
index: number;
|
|
17
17
|
workflowDimension: string;
|
|
18
|
+
type: string;
|
|
18
19
|
constructor(params: MemFlowWaitForErrorType);
|
|
19
20
|
}
|
|
20
21
|
declare class MemFlowProxyError extends Error {
|
|
@@ -31,6 +32,7 @@ declare class MemFlowProxyError extends Error {
|
|
|
31
32
|
workflowDimension: string;
|
|
32
33
|
workflowId: string;
|
|
33
34
|
workflowTopic: string;
|
|
35
|
+
type: string;
|
|
34
36
|
constructor(params: MemFlowProxyErrorType);
|
|
35
37
|
}
|
|
36
38
|
declare class MemFlowChildError extends Error {
|
|
@@ -50,6 +52,7 @@ declare class MemFlowChildError extends Error {
|
|
|
50
52
|
parentWorkflowId: string;
|
|
51
53
|
workflowId: string;
|
|
52
54
|
workflowTopic: string;
|
|
55
|
+
type: string;
|
|
53
56
|
constructor(params: MemFlowChildErrorType);
|
|
54
57
|
}
|
|
55
58
|
declare class MemFlowWaitForAllError extends Error {
|
|
@@ -62,6 +65,7 @@ declare class MemFlowWaitForAllError extends Error {
|
|
|
62
65
|
parentWorkflowId: string;
|
|
63
66
|
workflowId: string;
|
|
64
67
|
workflowTopic: string;
|
|
68
|
+
type: string;
|
|
65
69
|
constructor(params: MemFlowWaitForAllErrorType);
|
|
66
70
|
}
|
|
67
71
|
declare class MemFlowSleepError extends Error {
|
|
@@ -70,22 +74,27 @@ declare class MemFlowSleepError extends Error {
|
|
|
70
74
|
duration: number;
|
|
71
75
|
index: number;
|
|
72
76
|
workflowDimension: string;
|
|
77
|
+
type: string;
|
|
73
78
|
constructor(params: MemFlowSleepErrorType);
|
|
74
79
|
}
|
|
75
80
|
declare class MemFlowTimeoutError extends Error {
|
|
76
81
|
code: number;
|
|
82
|
+
type: string;
|
|
77
83
|
constructor(message: string, stack?: string);
|
|
78
84
|
}
|
|
79
85
|
declare class MemFlowMaxedError extends Error {
|
|
80
86
|
code: number;
|
|
87
|
+
type: string;
|
|
81
88
|
constructor(message: string, stackTrace?: string);
|
|
82
89
|
}
|
|
83
90
|
declare class MemFlowFatalError extends Error {
|
|
84
91
|
code: number;
|
|
92
|
+
type: string;
|
|
85
93
|
constructor(message: string, stackTrace?: string);
|
|
86
94
|
}
|
|
87
95
|
declare class MemFlowRetryError extends Error {
|
|
88
96
|
code: number;
|
|
97
|
+
type: string;
|
|
89
98
|
constructor(message: string, stackTrace?: string);
|
|
90
99
|
}
|
|
91
100
|
declare class MapDataError extends Error {
|
package/build/modules/errors.js
CHANGED
|
@@ -19,6 +19,7 @@ exports.SetStateError = SetStateError;
|
|
|
19
19
|
class MemFlowWaitForError extends Error {
|
|
20
20
|
constructor(params) {
|
|
21
21
|
super(`WaitFor Interruption`);
|
|
22
|
+
this.type = 'MemFlowWaitForError';
|
|
22
23
|
this.signalId = params.signalId;
|
|
23
24
|
this.index = params.index;
|
|
24
25
|
this.workflowDimension = params.workflowDimension;
|
|
@@ -29,6 +30,7 @@ exports.MemFlowWaitForError = MemFlowWaitForError;
|
|
|
29
30
|
class MemFlowProxyError extends Error {
|
|
30
31
|
constructor(params) {
|
|
31
32
|
super(`ProxyActivity Interruption`);
|
|
33
|
+
this.type = 'MemFlowProxyError';
|
|
32
34
|
this.arguments = params.arguments;
|
|
33
35
|
this.workflowId = params.workflowId;
|
|
34
36
|
this.workflowTopic = params.workflowTopic;
|
|
@@ -48,6 +50,7 @@ exports.MemFlowProxyError = MemFlowProxyError;
|
|
|
48
50
|
class MemFlowChildError extends Error {
|
|
49
51
|
constructor(params) {
|
|
50
52
|
super(`ExecChild Interruption`);
|
|
53
|
+
this.type = 'MemFlowChildError';
|
|
51
54
|
this.arguments = params.arguments;
|
|
52
55
|
this.workflowId = params.workflowId;
|
|
53
56
|
this.workflowTopic = params.workflowTopic;
|
|
@@ -70,6 +73,7 @@ exports.MemFlowChildError = MemFlowChildError;
|
|
|
70
73
|
class MemFlowWaitForAllError extends Error {
|
|
71
74
|
constructor(params) {
|
|
72
75
|
super(`Collation Interruption`);
|
|
76
|
+
this.type = 'MemFlowWaitForAllError';
|
|
73
77
|
this.items = params.items;
|
|
74
78
|
this.size = params.size;
|
|
75
79
|
this.workflowId = params.workflowId;
|
|
@@ -85,6 +89,7 @@ exports.MemFlowWaitForAllError = MemFlowWaitForAllError;
|
|
|
85
89
|
class MemFlowSleepError extends Error {
|
|
86
90
|
constructor(params) {
|
|
87
91
|
super(`SleepFor Interruption`);
|
|
92
|
+
this.type = 'MemFlowSleepError';
|
|
88
93
|
this.duration = params.duration;
|
|
89
94
|
this.workflowId = params.workflowId;
|
|
90
95
|
this.index = params.index;
|
|
@@ -96,6 +101,7 @@ exports.MemFlowSleepError = MemFlowSleepError;
|
|
|
96
101
|
class MemFlowTimeoutError extends Error {
|
|
97
102
|
constructor(message, stack) {
|
|
98
103
|
super(message);
|
|
104
|
+
this.type = 'MemFlowTimeoutError';
|
|
99
105
|
if (this.stack) {
|
|
100
106
|
this.stack = stack;
|
|
101
107
|
}
|
|
@@ -106,6 +112,7 @@ exports.MemFlowTimeoutError = MemFlowTimeoutError;
|
|
|
106
112
|
class MemFlowMaxedError extends Error {
|
|
107
113
|
constructor(message, stackTrace) {
|
|
108
114
|
super(message);
|
|
115
|
+
this.type = 'MemFlowMaxedError';
|
|
109
116
|
if (stackTrace) {
|
|
110
117
|
this.stack = stackTrace;
|
|
111
118
|
}
|
|
@@ -116,6 +123,7 @@ exports.MemFlowMaxedError = MemFlowMaxedError;
|
|
|
116
123
|
class MemFlowFatalError extends Error {
|
|
117
124
|
constructor(message, stackTrace) {
|
|
118
125
|
super(message);
|
|
126
|
+
this.type = 'MemFlowFatalError';
|
|
119
127
|
if (stackTrace) {
|
|
120
128
|
this.stack = stackTrace;
|
|
121
129
|
}
|
|
@@ -126,6 +134,7 @@ exports.MemFlowFatalError = MemFlowFatalError;
|
|
|
126
134
|
class MemFlowRetryError extends Error {
|
|
127
135
|
constructor(message, stackTrace) {
|
|
128
136
|
super(message);
|
|
137
|
+
this.type = 'MemFlowRetryError';
|
|
129
138
|
if (stackTrace) {
|
|
130
139
|
this.stack = stackTrace;
|
|
131
140
|
}
|
package/build/package.json
CHANGED
|
@@ -6,6 +6,7 @@ import { Entity } from './entity';
|
|
|
6
6
|
import { WorkerService } from './worker';
|
|
7
7
|
import { WorkflowService } from './workflow';
|
|
8
8
|
import { WorkflowHandleService } from './handle';
|
|
9
|
+
import { didInterrupt } from './workflow/interruption';
|
|
9
10
|
/**
|
|
10
11
|
* The MemFlow service is a collection of services that
|
|
11
12
|
* emulate Temporal's capabilities, but instead are
|
|
@@ -106,6 +107,13 @@ declare class MemFlowClass {
|
|
|
106
107
|
* including: `execChild`, `waitFor`, `sleep`, etc
|
|
107
108
|
*/
|
|
108
109
|
static workflow: typeof WorkflowService;
|
|
110
|
+
/**
|
|
111
|
+
* Checks if an error is a HotMesh reserved error type that indicates
|
|
112
|
+
* a workflow interruption rather than a true error condition.
|
|
113
|
+
*
|
|
114
|
+
* @see {@link utils/interruption.didInterrupt} for detailed documentation
|
|
115
|
+
*/
|
|
116
|
+
static didInterrupt: typeof didInterrupt;
|
|
109
117
|
/**
|
|
110
118
|
* Shutdown everything. All connections, workers, and clients will be closed.
|
|
111
119
|
* Include in your signal handlers to ensure a clean shutdown.
|
|
@@ -9,6 +9,7 @@ const entity_1 = require("./entity");
|
|
|
9
9
|
const worker_1 = require("./worker");
|
|
10
10
|
const workflow_1 = require("./workflow");
|
|
11
11
|
const handle_1 = require("./handle");
|
|
12
|
+
const interruption_1 = require("./workflow/interruption");
|
|
12
13
|
/**
|
|
13
14
|
* The MemFlow service is a collection of services that
|
|
14
15
|
* emulate Temporal's capabilities, but instead are
|
|
@@ -120,3 +121,10 @@ MemFlowClass.Worker = worker_1.WorkerService;
|
|
|
120
121
|
* including: `execChild`, `waitFor`, `sleep`, etc
|
|
121
122
|
*/
|
|
122
123
|
MemFlowClass.workflow = workflow_1.WorkflowService;
|
|
124
|
+
/**
|
|
125
|
+
* Checks if an error is a HotMesh reserved error type that indicates
|
|
126
|
+
* a workflow interruption rather than a true error condition.
|
|
127
|
+
*
|
|
128
|
+
* @see {@link utils/interruption.didInterrupt} for detailed documentation
|
|
129
|
+
*/
|
|
130
|
+
MemFlowClass.didInterrupt = interruption_1.didInterrupt;
|
|
@@ -322,6 +322,31 @@ class WorkerService {
|
|
|
322
322
|
const workflowResponse = await storage_1.asyncLocalStorage.run(context, async () => {
|
|
323
323
|
return await workflowFunction.apply(this, workflowInput.arguments);
|
|
324
324
|
});
|
|
325
|
+
//if the embedded function has a try/catch, it can interrup the throw
|
|
326
|
+
// throw here to interrupt the workflow if the embedded function caught and suppressed
|
|
327
|
+
if (interruptionRegistry.length > 0) {
|
|
328
|
+
const payload = interruptionRegistry[0];
|
|
329
|
+
switch (payload.type) {
|
|
330
|
+
case 'MemFlowWaitForError':
|
|
331
|
+
throw new errors_1.MemFlowWaitForError(payload);
|
|
332
|
+
case 'MemFlowProxyError':
|
|
333
|
+
throw new errors_1.MemFlowProxyError(payload);
|
|
334
|
+
case 'MemFlowChildError':
|
|
335
|
+
throw new errors_1.MemFlowChildError(payload);
|
|
336
|
+
case 'MemFlowSleepError':
|
|
337
|
+
throw new errors_1.MemFlowSleepError(payload);
|
|
338
|
+
case 'MemFlowTimeoutError':
|
|
339
|
+
throw new errors_1.MemFlowTimeoutError(payload.message, payload.stack);
|
|
340
|
+
case 'MemFlowMaxedError':
|
|
341
|
+
throw new errors_1.MemFlowMaxedError(payload.message, payload.stack);
|
|
342
|
+
case 'MemFlowFatalError':
|
|
343
|
+
throw new errors_1.MemFlowFatalError(payload.message, payload.stack);
|
|
344
|
+
case 'MemFlowRetryError':
|
|
345
|
+
throw new errors_1.MemFlowRetryError(payload.message, payload.stack);
|
|
346
|
+
default:
|
|
347
|
+
throw new errors_1.MemFlowRetryError(`Unknown interruption type: ${payload.type}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
325
350
|
return {
|
|
326
351
|
code: 200,
|
|
327
352
|
status: stream_1.StreamStatus.SUCCESS,
|
|
@@ -82,6 +82,7 @@ async function execChild(options) {
|
|
|
82
82
|
const interruptionMessage = getChildInterruptPayload(context, options, execIndex);
|
|
83
83
|
interruptionRegistry.push({
|
|
84
84
|
code: common_1.HMSH_CODE_MEMFLOW_CHILD,
|
|
85
|
+
type: 'MemFlowChildError',
|
|
85
86
|
...interruptionMessage,
|
|
86
87
|
});
|
|
87
88
|
await (0, common_1.sleepImmediate)();
|
|
@@ -3,8 +3,8 @@ import { HookOptions } from './common';
|
|
|
3
3
|
* Extended hook options that include signal configuration
|
|
4
4
|
*/
|
|
5
5
|
export interface ExecHookOptions extends HookOptions {
|
|
6
|
-
/** Signal ID to send after hook execution */
|
|
7
|
-
signalId
|
|
6
|
+
/** Signal ID to send after hook execution; if not provided, a random one will be generated */
|
|
7
|
+
signalId?: string;
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* Executes a hook function and awaits the signal response.
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.execHook = void 0;
|
|
4
4
|
const hook_1 = require("./hook");
|
|
5
5
|
const waitFor_1 = require("./waitFor");
|
|
6
|
+
const interruption_1 = require("./interruption");
|
|
6
7
|
/**
|
|
7
8
|
* Executes a hook function and awaits the signal response.
|
|
8
9
|
* This is a convenience method that combines `hook()` and `waitFor()` operations.
|
|
@@ -60,14 +61,23 @@ const waitFor_1 = require("./waitFor");
|
|
|
60
61
|
* ```
|
|
61
62
|
*/
|
|
62
63
|
async function execHook(options) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
try {
|
|
65
|
+
if (!options.signalId) {
|
|
66
|
+
options.signalId = 'memflow-hook-' + crypto.randomUUID();
|
|
67
|
+
}
|
|
68
|
+
const hookOptions = {
|
|
69
|
+
...options,
|
|
70
|
+
args: [...options.args, { signal: options.signalId, $memflow: true }]
|
|
71
|
+
};
|
|
72
|
+
// Execute the hook with the signal information
|
|
73
|
+
await (0, hook_1.hook)(hookOptions);
|
|
74
|
+
// Wait for the signal response and return it
|
|
75
|
+
return await (0, waitFor_1.waitFor)(options.signalId);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if ((0, interruption_1.didInterrupt)(error)) {
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
72
82
|
}
|
|
73
83
|
exports.execHook = execHook;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if an error is a HotMesh reserved error type that indicates
|
|
3
|
+
* a workflow interruption rather than a true error condition.
|
|
4
|
+
*
|
|
5
|
+
* When this returns true, you can safely return from your workflow function.
|
|
6
|
+
* The workflow engine will handle the interruption automatically.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* try {
|
|
11
|
+
* await someWorkflowOperation();
|
|
12
|
+
* } catch (error) {
|
|
13
|
+
* // Check if this is a HotMesh interruption
|
|
14
|
+
* if (didInterrupt(error)) {
|
|
15
|
+
* // Rethrow the error if HotMesh interruption
|
|
16
|
+
* throw error;
|
|
17
|
+
* }
|
|
18
|
+
* // Handle actual error
|
|
19
|
+
* console.error('Workflow failed:', error);
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @param error - The error to check
|
|
24
|
+
* @returns true if the error is a HotMesh interruption
|
|
25
|
+
*/
|
|
26
|
+
export declare function didInterrupt(error: Error): boolean;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.didInterrupt = void 0;
|
|
4
|
+
const errors_1 = require("../../../modules/errors");
|
|
5
|
+
/**
|
|
6
|
+
* Checks if an error is a HotMesh reserved error type that indicates
|
|
7
|
+
* a workflow interruption rather than a true error condition.
|
|
8
|
+
*
|
|
9
|
+
* When this returns true, you can safely return from your workflow function.
|
|
10
|
+
* The workflow engine will handle the interruption automatically.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* try {
|
|
15
|
+
* await someWorkflowOperation();
|
|
16
|
+
* } catch (error) {
|
|
17
|
+
* // Check if this is a HotMesh interruption
|
|
18
|
+
* if (didInterrupt(error)) {
|
|
19
|
+
* // Rethrow the error if HotMesh interruption
|
|
20
|
+
* throw error;
|
|
21
|
+
* }
|
|
22
|
+
* // Handle actual error
|
|
23
|
+
* console.error('Workflow failed:', error);
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @param error - The error to check
|
|
28
|
+
* @returns true if the error is a HotMesh interruption
|
|
29
|
+
*/
|
|
30
|
+
function didInterrupt(error) {
|
|
31
|
+
return (error instanceof errors_1.MemFlowChildError ||
|
|
32
|
+
error instanceof errors_1.MemFlowFatalError ||
|
|
33
|
+
error instanceof errors_1.MemFlowMaxedError ||
|
|
34
|
+
error instanceof errors_1.MemFlowProxyError ||
|
|
35
|
+
error instanceof errors_1.MemFlowRetryError ||
|
|
36
|
+
error instanceof errors_1.MemFlowSleepError ||
|
|
37
|
+
error instanceof errors_1.MemFlowTimeoutError ||
|
|
38
|
+
error instanceof errors_1.MemFlowWaitForError ||
|
|
39
|
+
error instanceof errors_1.MemFlowWaitForAllError);
|
|
40
|
+
}
|
|
41
|
+
exports.didInterrupt = didInterrupt;
|
|
@@ -67,6 +67,7 @@ function wrapActivity(activityName, options) {
|
|
|
67
67
|
const interruptionMessage = getProxyInterruptPayload(context, activityName, execIndex, args, options);
|
|
68
68
|
interruptionRegistry.push({
|
|
69
69
|
code: common_1.HMSH_CODE_MEMFLOW_PROXY,
|
|
70
|
+
type: 'MemFlowProxyError',
|
|
70
71
|
...interruptionMessage,
|
|
71
72
|
});
|
|
72
73
|
await (0, common_1.sleepImmediate)();
|
|
@@ -45,11 +45,10 @@ async function waitFor(signalId) {
|
|
|
45
45
|
signalId,
|
|
46
46
|
index: execIndex,
|
|
47
47
|
workflowDimension,
|
|
48
|
-
|
|
49
|
-
interruptionRegistry.push({
|
|
48
|
+
type: 'MemFlowWaitForError',
|
|
50
49
|
code: common_1.HMSH_CODE_MEMFLOW_WAIT,
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
};
|
|
51
|
+
interruptionRegistry.push(interruptionMessage);
|
|
53
52
|
await (0, common_1.sleepImmediate)();
|
|
54
53
|
throw new common_1.MemFlowWaitForError(interruptionMessage);
|
|
55
54
|
}
|