@hotmeshio/hotmesh 0.5.0 β 0.5.2
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 +234 -237
- package/build/modules/errors.d.ts +9 -0
- package/build/modules/errors.js +9 -0
- package/build/package.json +16 -14
- package/build/services/hotmesh/index.d.ts +9 -11
- package/build/services/hotmesh/index.js +9 -11
- package/build/services/memflow/entity.d.ts +168 -4
- package/build/services/memflow/entity.js +177 -15
- 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/index.d.ts +2 -4
- package/build/services/memflow/workflow/index.js +2 -4
- package/build/services/memflow/workflow/interruption.d.ts +28 -0
- package/build/services/memflow/workflow/interruption.js +43 -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 +4 -4
- package/build/services/search/index.d.ts +10 -0
- package/build/services/search/providers/postgres/postgres.d.ts +12 -0
- package/build/services/search/providers/postgres/postgres.js +209 -0
- package/build/services/search/providers/redis/ioredis.d.ts +4 -0
- package/build/services/search/providers/redis/ioredis.js +13 -0
- package/build/services/search/providers/redis/redis.d.ts +4 -0
- package/build/services/search/providers/redis/redis.js +13 -0
- package/build/services/store/providers/postgres/kvsql.d.ts +13 -37
- package/build/services/store/providers/postgres/kvsql.js +2 -2
- package/build/services/store/providers/postgres/kvtypes/hash/basic.d.ts +16 -0
- package/build/services/store/providers/postgres/kvtypes/hash/basic.js +480 -0
- package/build/services/store/providers/postgres/kvtypes/hash/expire.d.ts +5 -0
- package/build/services/store/providers/postgres/kvtypes/hash/expire.js +33 -0
- package/build/services/store/providers/postgres/kvtypes/hash/index.d.ts +29 -0
- package/build/services/store/providers/postgres/kvtypes/hash/index.js +190 -0
- package/build/services/store/providers/postgres/kvtypes/hash/jsonb.d.ts +14 -0
- package/build/services/store/providers/postgres/kvtypes/hash/jsonb.js +699 -0
- package/build/services/store/providers/postgres/kvtypes/hash/scan.d.ts +10 -0
- package/build/services/store/providers/postgres/kvtypes/hash/scan.js +91 -0
- package/build/services/store/providers/postgres/kvtypes/hash/types.d.ts +19 -0
- package/build/services/store/providers/postgres/kvtypes/hash/types.js +2 -0
- package/build/services/store/providers/postgres/kvtypes/hash/utils.d.ts +18 -0
- package/build/services/store/providers/postgres/kvtypes/hash/utils.js +90 -0
- package/build/types/memflow.d.ts +1 -1
- package/build/types/meshdata.d.ts +1 -1
- package/package.json +16 -14
- package/build/services/store/providers/postgres/kvtypes/hash.d.ts +0 -60
- package/build/services/store/providers/postgres/kvtypes/hash.js +0 -1287
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
|
|
75
|
+
### System Benefits
|
|
70
76
|
|
|
71
|
-
* **
|
|
72
|
-
* **
|
|
73
|
-
* **
|
|
74
|
-
* **
|
|
75
|
-
* **
|
|
76
|
-
* **Index-friendly** - entity data is stored as JSONB; add partial indexes for improved query analysis.
|
|
77
|
-
|
|
78
|
-
**Example: Adding a Partial Index for Specific Entity Types**
|
|
79
|
-
```sql
|
|
80
|
-
-- Create a partial index for 'user' entities with specific entity values
|
|
81
|
-
CREATE INDEX idx_user_premium ON your_app.jobs (id)
|
|
82
|
-
WHERE entity = 'user' AND (context->>'isPremium')::boolean = true;
|
|
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
|
-
##
|
|
85
|
+
## Permanent Memory Architecture
|
|
89
86
|
|
|
90
|
-
|
|
87
|
+
Every workflow in HotMesh is backed by an "entity": a versioned, JSONB record that tracks its memory and state transitions.
|
|
91
88
|
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
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
|
|
97
94
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
import { MemFlow } from '@hotmeshio/hotmesh';
|
|
95
|
+
### Example: Partial Index for Premium Users
|
|
102
96
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
});
|
|
155
|
-
|
|
156
|
-
const worker = await mf.worker.create({
|
|
157
|
-
taskQueue: 'entityqueue',
|
|
158
|
-
workflow: example
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
await mf.worker.create({
|
|
162
|
-
taskQueue: 'entityqueue',
|
|
163
|
-
workflow: hook1
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
await mf.worker.create({
|
|
167
|
-
taskQueue: 'entityqueue',
|
|
168
|
-
workflow: hook2
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
console.log('Workers and hooks started and listening...');
|
|
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
|
|
213
108
|
|
|
214
|
-
|
|
109
|
+
Agents often require memoryβcontext that persists between invocations, spans multiple perspectives, or outlives a single process. HotMesh supports that natively.
|
|
215
110
|
|
|
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
|
|
111
|
+
The following example builds a "research agent" that runs sub-flows and self-reflects on the results.
|
|
220
112
|
|
|
221
|
-
###
|
|
113
|
+
### Research Agent Example
|
|
222
114
|
|
|
223
|
-
|
|
224
|
-
|
|
115
|
+
#### Main Coordinator Agent
|
|
116
|
+
|
|
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,225 @@ 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
|
+
```
|
|
272
165
|
|
|
273
|
-
|
|
274
|
-
|
|
166
|
+
> π‘ **Developer Note**: A complete implementation of this Research Agent example with tests, OpenAI integration, and multi-perspective analysis can be found in the [test suite](./tests/memflow/agent).
|
|
167
|
+
|
|
168
|
+
#### Hooks: Perspectives
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
// Optimistic hook looks for affirming evidence
|
|
172
|
+
export async function optimisticPerspective(query: string, config: {signal: string}): Promise<void> {
|
|
275
173
|
const entity = await MemFlow.workflow.entity();
|
|
276
|
-
|
|
277
|
-
// Optimistic perspective: look for supporting evidence
|
|
278
174
|
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
|
-
});
|
|
175
|
+
await entity.merge({ perspectives: { optimistic: { findings, confidence: 0.8 }}});
|
|
176
|
+
//signal the caller to notify all done
|
|
177
|
+
await MemFlow.workflow.signal(config.signal, {});
|
|
289
178
|
}
|
|
290
179
|
|
|
291
|
-
|
|
292
|
-
export async function skepticalPerspective(query: string): Promise<void> {
|
|
180
|
+
// Skeptical hook seeks out contradictions and counterpoints
|
|
181
|
+
export async function skepticalPerspective(query: string, config: {signal: string}): Promise<void> {
|
|
293
182
|
const entity = await MemFlow.workflow.entity();
|
|
294
|
-
|
|
295
|
-
// Skeptical perspective: challenge assumptions
|
|
296
183
|
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
|
-
});
|
|
184
|
+
await entity.merge({ perspectives: { skeptical: { counterEvidence, confidence: 0.6 }}});
|
|
185
|
+
await MemFlow.workflow.signal(config.signal, {});
|
|
307
186
|
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### Child Agent: Fact Checker
|
|
308
190
|
|
|
309
|
-
|
|
191
|
+
```ts
|
|
192
|
+
// A dedicated child agent with its own entity type and context
|
|
310
193
|
export async function factCheckAgent(query: string): Promise<any> {
|
|
311
|
-
// This child has its own entity type for specialized fact-checking context
|
|
312
194
|
const entity = await MemFlow.workflow.entity();
|
|
313
|
-
|
|
314
|
-
await entity.set({
|
|
315
|
-
query,
|
|
316
|
-
sources: [],
|
|
317
|
-
verifications: [],
|
|
318
|
-
credibilityScore: 0
|
|
319
|
-
});
|
|
195
|
+
await entity.set({ query, sources: [], verifications: [] });
|
|
320
196
|
|
|
321
|
-
// Fact-checker can spawn its own specialized hooks
|
|
322
197
|
await MemFlow.workflow.execHook({
|
|
323
|
-
taskQueue: '
|
|
198
|
+
taskQueue: 'agents',
|
|
324
199
|
workflowName: 'verifySourceCredibility',
|
|
325
200
|
args: [query]
|
|
326
201
|
});
|
|
327
202
|
|
|
328
203
|
return await entity.get();
|
|
329
204
|
}
|
|
205
|
+
```
|
|
330
206
|
|
|
331
|
-
|
|
332
|
-
|
|
207
|
+
#### Synthesis
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
// Synthesis hook aggregates different viewpoints
|
|
211
|
+
export async function synthesizePerspectives(config: {signal: string}): Promise<void> {
|
|
333
212
|
const entity = await MemFlow.workflow.entity();
|
|
334
213
|
const context = await entity.get();
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
214
|
+
|
|
215
|
+
const result = await analyzePerspectives(context.perspectives);
|
|
216
|
+
|
|
339
217
|
await entity.merge({
|
|
340
218
|
perspectives: {
|
|
341
219
|
synthesis: {
|
|
342
|
-
finalAssessment:
|
|
343
|
-
confidence: calculateConfidence(context.perspectives)
|
|
344
|
-
reasoning: 'Balanced analysis of optimistic and skeptical viewpoints'
|
|
220
|
+
finalAssessment: result,
|
|
221
|
+
confidence: calculateConfidence(context.perspectives)
|
|
345
222
|
}
|
|
346
223
|
},
|
|
347
224
|
status: 'completed'
|
|
348
225
|
});
|
|
226
|
+
await MemFlow.workflow.signal(config.signal, {});
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Building Pipelines with State
|
|
233
|
+
|
|
234
|
+
HotMesh treats pipelines as long-lived records, not ephemeral jobs. Every pipeline run is stateful, resumable, and traceable.
|
|
235
|
+
|
|
236
|
+
### Setup a Data Pipeline
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
export async function dataPipeline(source: string): Promise<void> {
|
|
240
|
+
const entity = await MemFlow.workflow.entity();
|
|
241
|
+
|
|
242
|
+
// Initial policy and tracking setup
|
|
243
|
+
await entity.set({
|
|
244
|
+
source,
|
|
245
|
+
pipeline: { version: 1, policy: { refreshInterval: '24 hours' } },
|
|
246
|
+
changeLog: []
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Trigger the recurring orchestration pipeline
|
|
250
|
+
await MemFlow.workflow.execHook({
|
|
251
|
+
taskQueue: 'pipeline',
|
|
252
|
+
workflowName: 'runPipeline',
|
|
253
|
+
args: [true]
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Orchestration Hook
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
export async function runPipeline(repeat = false): Promise<void> {
|
|
262
|
+
do {
|
|
263
|
+
// Perform transformation step
|
|
264
|
+
await MemFlow.workflow.execHook({
|
|
265
|
+
taskQueue: 'transform',
|
|
266
|
+
workflowName: 'cleanData',
|
|
267
|
+
args: []
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
if (repeat) {
|
|
271
|
+
// Schedule next execution
|
|
272
|
+
await MemFlow.workflow.execHook({
|
|
273
|
+
taskQueue: 'scheduler',
|
|
274
|
+
workflowName: 'scheduleRefresh',
|
|
275
|
+
args: []
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
} while (repeat)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Hook to clean and transform data
|
|
283
|
+
*/
|
|
284
|
+
export async function cleanData(signalInfo?: { signal: string }): Promise<void> {
|
|
285
|
+
const entity = await MemFlow.workflow.entity();
|
|
286
|
+
|
|
287
|
+
// Simulate data cleaning
|
|
288
|
+
await entity.merge({
|
|
289
|
+
status: 'cleaning',
|
|
290
|
+
lastCleanedAt: new Date().toISOString()
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Add to changelog
|
|
294
|
+
await entity.append('changeLog', {
|
|
295
|
+
action: 'clean',
|
|
296
|
+
timestamp: new Date().toISOString()
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Signal completion if called via execHook
|
|
300
|
+
if (signalInfo?.signal) {
|
|
301
|
+
await MemFlow.workflow.signal(signalInfo.signal, {
|
|
302
|
+
status: 'cleaned',
|
|
303
|
+
timestamp: new Date().toISOString()
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Hook to schedule the next refresh based on policy
|
|
310
|
+
*/
|
|
311
|
+
export async function scheduleRefresh(signalInfo?: { signal: string }): Promise<void> {
|
|
312
|
+
const entity = await MemFlow.workflow.entity();
|
|
313
|
+
|
|
314
|
+
// Get refresh interval from policy
|
|
315
|
+
const currentEntity = await entity.get();
|
|
316
|
+
const refreshInterval = currentEntity.pipeline.policy.refreshInterval;
|
|
317
|
+
|
|
318
|
+
// Sleep for the configured interval
|
|
319
|
+
await MemFlow.workflow.sleepFor(refreshInterval);
|
|
320
|
+
|
|
321
|
+
// Update status after sleep
|
|
322
|
+
await entity.merge({
|
|
323
|
+
status: 'ready_for_refresh',
|
|
324
|
+
nextRefreshAt: new Date().toISOString()
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Add to changelog
|
|
328
|
+
await entity.append('changeLog', {
|
|
329
|
+
action: 'schedule_refresh',
|
|
330
|
+
timestamp: new Date().toISOString(),
|
|
331
|
+
nextRefresh: new Date().toISOString()
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Signal completion if called via execHook
|
|
335
|
+
if (signalInfo?.signal) {
|
|
336
|
+
await MemFlow.workflow.signal(signalInfo.signal, {
|
|
337
|
+
status: 'scheduled',
|
|
338
|
+
nextRefresh: new Date().toISOString()
|
|
339
|
+
});
|
|
340
|
+
}
|
|
349
341
|
}
|
|
350
342
|
```
|
|
351
343
|
|
|
352
|
-
###
|
|
344
|
+
### Trigger from Outside
|
|
353
345
|
|
|
354
|
-
|
|
346
|
+
```ts
|
|
347
|
+
// External systems can trigger a single pipeline run
|
|
348
|
+
export async function triggerRefresh() {
|
|
349
|
+
const client = new MemFlow.Client({/*...*/});
|
|
355
350
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
351
|
+
await client.workflow.hook({
|
|
352
|
+
workflowId: 'pipeline-123',
|
|
353
|
+
taskQueue: 'pipeline',
|
|
354
|
+
workflowName: 'runPipeline',
|
|
355
|
+
args: []
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
```
|
|
362
359
|
|
|
363
|
-
|
|
360
|
+
> π‘ **Developer Note**: A complete implementation of this Pipeline example with OpenAI Vision integration, processing hooks, and document workflow automation can be found in the [test suite](./tests/memflow/pipeline).
|
|
364
361
|
|
|
365
362
|
---
|
|
366
363
|
|
|
367
|
-
##
|
|
364
|
+
## Documentation & Links
|
|
368
365
|
|
|
369
|
-
* SDK
|
|
370
|
-
* Examples β [
|
|
366
|
+
* SDK Reference β [hotmeshio.github.io/sdk-typescript](https://hotmeshio.github.io/sdk-typescript)
|
|
367
|
+
* Examples β [github.com/hotmeshio/samples-typescript](https://github.com/hotmeshio/samples-typescript)
|
|
371
368
|
|
|
372
369
|
---
|
|
373
370
|
|
|
374
371
|
## License
|
|
375
372
|
|
|
376
|
-
Apache 2.0 β
|
|
373
|
+
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 {
|