@hotmeshio/hotmesh 0.3.31 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +128 -823
- package/build/index.d.ts +9 -9
- package/build/index.js +10 -10
- package/build/package.json +22 -21
- package/build/types/error.d.ts +5 -5
- package/build/types/exporter.d.ts +1 -1
- package/build/types/index.d.ts +3 -3
- package/build/types/manifest.d.ts +2 -2
- package/build/types/{meshflow.d.ts → memflow.d.ts} +15 -15
- package/build/types/meshdata.d.ts +3 -3
- package/build/types/postgres.d.ts +7 -0
- package/build/types/stream.d.ts +3 -0
- package/index.ts +11 -11
- package/package.json +22 -21
- package/typedoc.json +8 -7
- package/types/error.ts +5 -5
- package/types/exporter.ts +1 -1
- package/types/index.ts +7 -7
- package/types/manifest.ts +2 -2
- package/types/{meshflow.ts → memflow.ts} +15 -15
- package/types/meshdata.ts +3 -3
- package/types/postgres.ts +9 -0
- package/types/stream.ts +4 -0
- package/build/modules/enums.d.ts +0 -108
- package/build/modules/enums.js +0 -132
- package/build/modules/errors.d.ts +0 -124
- package/build/modules/errors.js +0 -191
- package/build/modules/key.d.ts +0 -66
- package/build/modules/key.js +0 -1
- package/build/modules/storage.d.ts +0 -3
- package/build/modules/storage.js +0 -5
- package/build/modules/utils.d.ts +0 -119
- package/build/modules/utils.js +0 -1
- package/build/services/activities/activity.d.ts +0 -104
- package/build/services/activities/activity.js +0 -1
- package/build/services/activities/await.d.ts +0 -12
- package/build/services/activities/await.js +0 -1
- package/build/services/activities/cycle.d.ts +0 -19
- package/build/services/activities/cycle.js +0 -1
- package/build/services/activities/hook.d.ts +0 -27
- package/build/services/activities/hook.js +0 -1
- package/build/services/activities/index.d.ts +0 -19
- package/build/services/activities/index.js +0 -1
- package/build/services/activities/interrupt.d.ts +0 -16
- package/build/services/activities/interrupt.js +0 -1
- package/build/services/activities/signal.d.ts +0 -20
- package/build/services/activities/signal.js +0 -1
- package/build/services/activities/trigger.d.ts +0 -37
- package/build/services/activities/trigger.js +0 -1
- package/build/services/activities/worker.d.ts +0 -12
- package/build/services/activities/worker.js +0 -1
- package/build/services/collator/index.d.ts +0 -111
- package/build/services/collator/index.js +0 -1
- package/build/services/compiler/deployer.d.ts +0 -40
- package/build/services/compiler/deployer.js +0 -1
- package/build/services/compiler/index.d.ts +0 -32
- package/build/services/compiler/index.js +0 -1
- package/build/services/compiler/validator.d.ts +0 -34
- package/build/services/compiler/validator.js +0 -1
- package/build/services/connector/factory.d.ts +0 -22
- package/build/services/connector/factory.js +0 -99
- package/build/services/connector/index.d.ts +0 -30
- package/build/services/connector/index.js +0 -54
- package/build/services/connector/providers/ioredis.d.ts +0 -9
- package/build/services/connector/providers/ioredis.js +0 -26
- package/build/services/connector/providers/nats.d.ts +0 -9
- package/build/services/connector/providers/nats.js +0 -34
- package/build/services/connector/providers/postgres.d.ts +0 -20
- package/build/services/connector/providers/postgres.js +0 -102
- package/build/services/connector/providers/redis.d.ts +0 -9
- package/build/services/connector/providers/redis.js +0 -38
- package/build/services/engine/index.d.ts +0 -264
- package/build/services/engine/index.js +0 -1
- package/build/services/exporter/index.d.ts +0 -44
- package/build/services/exporter/index.js +0 -1
- package/build/services/hotmesh/index.d.ts +0 -340
- package/build/services/hotmesh/index.js +0 -479
- package/build/services/logger/index.d.ts +0 -16
- package/build/services/logger/index.js +0 -54
- package/build/services/mapper/index.d.ts +0 -28
- package/build/services/mapper/index.js +0 -1
- package/build/services/meshcall/index.d.ts +0 -194
- package/build/services/meshcall/index.js +0 -452
- package/build/services/meshcall/schemas/factory.d.ts +0 -9
- package/build/services/meshcall/schemas/factory.js +0 -189
- package/build/services/meshdata/index.d.ts +0 -795
- package/build/services/meshdata/index.js +0 -1235
- package/build/services/meshflow/client.d.ts +0 -108
- package/build/services/meshflow/client.js +0 -371
- package/build/services/meshflow/connection.d.ts +0 -23
- package/build/services/meshflow/connection.js +0 -33
- package/build/services/meshflow/exporter.d.ts +0 -51
- package/build/services/meshflow/exporter.js +0 -1
- package/build/services/meshflow/handle.d.ts +0 -90
- package/build/services/meshflow/handle.js +0 -176
- package/build/services/meshflow/index.d.ts +0 -111
- package/build/services/meshflow/index.js +0 -117
- package/build/services/meshflow/schemas/factory.d.ts +0 -29
- package/build/services/meshflow/schemas/factory.js +0 -2492
- package/build/services/meshflow/search.d.ts +0 -142
- package/build/services/meshflow/search.js +0 -320
- package/build/services/meshflow/worker.d.ts +0 -124
- package/build/services/meshflow/worker.js +0 -514
- package/build/services/meshflow/workflow/all.d.ts +0 -7
- package/build/services/meshflow/workflow/all.js +0 -15
- package/build/services/meshflow/workflow/common.d.ts +0 -18
- package/build/services/meshflow/workflow/common.js +0 -45
- package/build/services/meshflow/workflow/context.d.ts +0 -6
- package/build/services/meshflow/workflow/context.js +0 -45
- package/build/services/meshflow/workflow/didRun.d.ts +0 -7
- package/build/services/meshflow/workflow/didRun.js +0 -22
- package/build/services/meshflow/workflow/emit.d.ts +0 -11
- package/build/services/meshflow/workflow/emit.js +0 -29
- package/build/services/meshflow/workflow/enrich.d.ts +0 -9
- package/build/services/meshflow/workflow/enrich.js +0 -17
- package/build/services/meshflow/workflow/execChild.d.ts +0 -18
- package/build/services/meshflow/workflow/execChild.js +0 -102
- package/build/services/meshflow/workflow/hook.d.ts +0 -9
- package/build/services/meshflow/workflow/hook.js +0 -40
- package/build/services/meshflow/workflow/index.d.ts +0 -70
- package/build/services/meshflow/workflow/index.js +0 -83
- package/build/services/meshflow/workflow/interrupt.d.ts +0 -9
- package/build/services/meshflow/workflow/interrupt.js +0 -24
- package/build/services/meshflow/workflow/isSideEffectAllowed.d.ts +0 -10
- package/build/services/meshflow/workflow/isSideEffectAllowed.js +0 -33
- package/build/services/meshflow/workflow/proxyActivities.d.ts +0 -20
- package/build/services/meshflow/workflow/proxyActivities.js +0 -97
- package/build/services/meshflow/workflow/random.d.ts +0 -6
- package/build/services/meshflow/workflow/random.js +0 -16
- package/build/services/meshflow/workflow/searchMethods.d.ts +0 -6
- package/build/services/meshflow/workflow/searchMethods.js +0 -25
- package/build/services/meshflow/workflow/signal.d.ts +0 -7
- package/build/services/meshflow/workflow/signal.js +0 -28
- package/build/services/meshflow/workflow/sleepFor.d.ts +0 -8
- package/build/services/meshflow/workflow/sleepFor.js +0 -35
- package/build/services/meshflow/workflow/trace.d.ts +0 -14
- package/build/services/meshflow/workflow/trace.js +0 -33
- package/build/services/meshflow/workflow/waitFor.d.ts +0 -8
- package/build/services/meshflow/workflow/waitFor.js +0 -35
- package/build/services/meshos/index.d.ts +0 -293
- package/build/services/meshos/index.js +0 -547
- package/build/services/pipe/functions/array.d.ts +0 -17
- package/build/services/pipe/functions/array.js +0 -1
- package/build/services/pipe/functions/bitwise.d.ts +0 -9
- package/build/services/pipe/functions/bitwise.js +0 -1
- package/build/services/pipe/functions/conditional.d.ts +0 -13
- package/build/services/pipe/functions/conditional.js +0 -1
- package/build/services/pipe/functions/cron.d.ts +0 -12
- package/build/services/pipe/functions/cron.js +0 -1
- package/build/services/pipe/functions/date.d.ts +0 -58
- package/build/services/pipe/functions/date.js +0 -1
- package/build/services/pipe/functions/index.d.ts +0 -29
- package/build/services/pipe/functions/index.js +0 -1
- package/build/services/pipe/functions/json.d.ts +0 -5
- package/build/services/pipe/functions/json.js +0 -1
- package/build/services/pipe/functions/logical.d.ts +0 -5
- package/build/services/pipe/functions/logical.js +0 -1
- package/build/services/pipe/functions/math.d.ts +0 -42
- package/build/services/pipe/functions/math.js +0 -1
- package/build/services/pipe/functions/number.d.ts +0 -21
- package/build/services/pipe/functions/number.js +0 -1
- package/build/services/pipe/functions/object.d.ts +0 -25
- package/build/services/pipe/functions/object.js +0 -1
- package/build/services/pipe/functions/string.d.ts +0 -23
- package/build/services/pipe/functions/string.js +0 -1
- package/build/services/pipe/functions/symbol.d.ts +0 -12
- package/build/services/pipe/functions/symbol.js +0 -1
- package/build/services/pipe/functions/unary.d.ts +0 -7
- package/build/services/pipe/functions/unary.js +0 -1
- package/build/services/pipe/index.d.ts +0 -48
- package/build/services/pipe/index.js +0 -1
- package/build/services/quorum/index.d.ts +0 -90
- package/build/services/quorum/index.js +0 -1
- package/build/services/reporter/index.d.ts +0 -50
- package/build/services/reporter/index.js +0 -1
- package/build/services/router/index.d.ts +0 -60
- package/build/services/router/index.js +0 -1
- package/build/services/search/factory.d.ts +0 -7
- package/build/services/search/factory.js +0 -24
- package/build/services/search/index.d.ts +0 -22
- package/build/services/search/index.js +0 -10
- package/build/services/search/providers/postgres/postgres.d.ts +0 -24
- package/build/services/search/providers/postgres/postgres.js +0 -1
- package/build/services/search/providers/redis/ioredis.d.ts +0 -18
- package/build/services/search/providers/redis/ioredis.js +0 -1
- package/build/services/search/providers/redis/redis.d.ts +0 -18
- package/build/services/search/providers/redis/redis.js +0 -1
- package/build/services/serializer/index.d.ts +0 -42
- package/build/services/serializer/index.js +0 -1
- package/build/services/store/cache.d.ts +0 -67
- package/build/services/store/cache.js +0 -128
- package/build/services/store/factory.d.ts +0 -8
- package/build/services/store/factory.js +0 -24
- package/build/services/store/index.d.ts +0 -89
- package/build/services/store/index.js +0 -9
- package/build/services/store/providers/postgres/kvsql.d.ts +0 -168
- package/build/services/store/providers/postgres/kvsql.js +0 -1
- package/build/services/store/providers/postgres/kvtables.d.ts +0 -20
- package/build/services/store/providers/postgres/kvtables.js +0 -1
- package/build/services/store/providers/postgres/kvtransaction.d.ts +0 -36
- package/build/services/store/providers/postgres/kvtransaction.js +0 -1
- package/build/services/store/providers/postgres/kvtypes/hash.d.ts +0 -60
- package/build/services/store/providers/postgres/kvtypes/hash.js +0 -1
- package/build/services/store/providers/postgres/kvtypes/list.d.ts +0 -33
- package/build/services/store/providers/postgres/kvtypes/list.js +0 -1
- package/build/services/store/providers/postgres/kvtypes/string.d.ts +0 -20
- package/build/services/store/providers/postgres/kvtypes/string.js +0 -1
- package/build/services/store/providers/postgres/kvtypes/zset.d.ts +0 -41
- package/build/services/store/providers/postgres/kvtypes/zset.js +0 -1
- package/build/services/store/providers/postgres/postgres.d.ts +0 -145
- package/build/services/store/providers/postgres/postgres.js +0 -1
- package/build/services/store/providers/redis/_base.d.ts +0 -137
- package/build/services/store/providers/redis/_base.js +0 -1
- package/build/services/store/providers/redis/ioredis.d.ts +0 -20
- package/build/services/store/providers/redis/ioredis.js +0 -1
- package/build/services/store/providers/redis/redis.d.ts +0 -18
- package/build/services/store/providers/redis/redis.js +0 -1
- package/build/services/store/providers/store-initializable.d.ts +0 -5
- package/build/services/store/providers/store-initializable.js +0 -1
- package/build/services/stream/factory.d.ts +0 -8
- package/build/services/stream/factory.js +0 -37
- package/build/services/stream/index.d.ts +0 -64
- package/build/services/stream/index.js +0 -11
- package/build/services/stream/providers/nats/nats.d.ts +0 -59
- package/build/services/stream/providers/nats/nats.js +0 -1
- package/build/services/stream/providers/postgres/kvtables.d.ts +0 -2
- package/build/services/stream/providers/postgres/kvtables.js +0 -1
- package/build/services/stream/providers/postgres/postgres.d.ts +0 -88
- package/build/services/stream/providers/postgres/postgres.js +0 -1
- package/build/services/stream/providers/redis/ioredis.d.ts +0 -60
- package/build/services/stream/providers/redis/ioredis.js +0 -1
- package/build/services/stream/providers/redis/redis.d.ts +0 -60
- package/build/services/stream/providers/redis/redis.js +0 -1
- package/build/services/stream/providers/stream-initializable.d.ts +0 -4
- package/build/services/stream/providers/stream-initializable.js +0 -1
- package/build/services/sub/factory.d.ts +0 -7
- package/build/services/sub/factory.js +0 -29
- package/build/services/sub/index.d.ts +0 -22
- package/build/services/sub/index.js +0 -10
- package/build/services/sub/providers/nats/nats.d.ts +0 -19
- package/build/services/sub/providers/nats/nats.js +0 -1
- package/build/services/sub/providers/postgres/postgres.d.ts +0 -19
- package/build/services/sub/providers/postgres/postgres.js +0 -1
- package/build/services/sub/providers/redis/ioredis.d.ts +0 -17
- package/build/services/sub/providers/redis/ioredis.js +0 -1
- package/build/services/sub/providers/redis/redis.d.ts +0 -17
- package/build/services/sub/providers/redis/redis.js +0 -1
- package/build/services/task/index.d.ts +0 -36
- package/build/services/task/index.js +0 -1
- package/build/services/telemetry/index.d.ts +0 -52
- package/build/services/telemetry/index.js +0 -1
- package/build/services/worker/index.d.ts +0 -77
- package/build/services/worker/index.js +0 -1
- /package/build/types/{meshflow.js → memflow.js} +0 -0
package/README.md
CHANGED
|
@@ -1,867 +1,172 @@
|
|
|
1
|
-
# HotMesh
|
|
2
|
-

|
|
1
|
+
# HotMesh MemFlow
|
|
3
2
|
|
|
4
|
-
**
|
|
3
|
+
**Permanent-Memory Workflows & AI Agents**
|
|
5
4
|
|
|
5
|
+
 
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
MemFlow is a drop-in Temporal-style engine that runs natively on Postgres — but with a twist:
|
|
8
|
+
every workflow owns a *permanent*, JSON-backed context that lives beyond the main workflow.
|
|
9
|
+
Any number of *hooks* (lightweight, thread-safe workers) can attach to that record at any
|
|
10
|
+
time, read it, and safely write back incremental knowledge.
|
|
11
|
+
Think **durable execution** + **shared, evolving memory** → perfect for human-in-the-loop
|
|
12
|
+
processes and AI agents that learn over time.
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
---
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
- **No Vendor Lock-in**: Use your preferred database: *Postgres*, *Redis*, ...
|
|
13
|
-
- **Linear Scalability**: Scale your database to scale your application
|
|
14
|
-
- **Process Analytics**: Gain process insights with optional analytics
|
|
16
|
+
## Table of Contents
|
|
15
17
|
|
|
18
|
+
1. 🚀 Quick Start
|
|
19
|
+
2. 🧠 How Permanent Memory Works
|
|
20
|
+
3. 🔌 Hooks & Context API
|
|
21
|
+
4. 🤖 Building Durable AI Agents
|
|
22
|
+
5. 🔬 Advanced Patterns & Recipes
|
|
23
|
+
6. 📚 Documentation & Links
|
|
16
24
|
|
|
17
|
-
|
|
25
|
+
---
|
|
18
26
|
|
|
19
|
-
##
|
|
27
|
+
## 🚀 Quick Start
|
|
20
28
|
|
|
21
|
-
|
|
29
|
+
### Install
|
|
30
|
+
```bash
|
|
22
31
|
npm install @hotmeshio/hotmesh
|
|
23
32
|
```
|
|
24
33
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
<br/>
|
|
31
|
-
|
|
32
|
-
## MeshCall
|
|
33
|
-
[MeshCall](https://hotmeshio.github.io/sdk-typescript/classes/services_meshcall.MeshCall.html) connects any function to the mesh
|
|
34
|
-
|
|
35
|
-
<details style="padding: .5em">
|
|
36
|
-
<summary style="font-size:1.25em;">Run an idempotent cron job <small>[more]</small></summary>
|
|
37
|
-
|
|
38
|
-
### Run a Cron
|
|
39
|
-
This example demonstrates an *idempotent* cron that runs daily at midnight. The `id` makes each cron job unique and ensures that only one instance runs, despite repeated invocations. *The `cron` method returns `false` if a workflow is already running with the same `id`.*
|
|
40
|
-
|
|
41
|
-
Optionally set a `delay` and/or set `maxCycles` to limit the number of cycles. The `interval` can be any human-readable time format (e.g., `1 day`, `2 hours`, `30 minutes`, etc) or a standard cron expression.
|
|
42
|
-
|
|
43
|
-
1. Define the cron function.
|
|
44
|
-
```typescript
|
|
45
|
-
//cron.ts
|
|
46
|
-
import { MeshCall } from '@hotmeshio/hotmesh';
|
|
47
|
-
import { Client as Postgres } from 'pg';
|
|
48
|
-
|
|
49
|
-
export const runMyCron = async (id: string, interval = '0 0 * * *'): Promise<boolean> => {
|
|
50
|
-
return await MeshCall.cron({
|
|
51
|
-
topic: 'my.cron.function',
|
|
52
|
-
connection: {
|
|
53
|
-
class: Postgres,
|
|
54
|
-
options: {
|
|
55
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
callback: async () => {
|
|
59
|
-
//your code here...
|
|
60
|
-
},
|
|
61
|
-
options: { id, interval, maxCycles: 24 }
|
|
62
|
-
});
|
|
63
|
-
};
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
2. Call `runMyCron` at server startup (or call as needed to run multiple crons).
|
|
67
|
-
```typescript
|
|
68
|
-
//server.ts
|
|
69
|
-
import { runMyCron } from './cron';
|
|
70
|
-
|
|
71
|
-
runMyCron('myNightlyCron123');
|
|
72
|
-
```
|
|
73
|
-
</details>
|
|
74
|
-
|
|
75
|
-
<details style="padding: .5em">
|
|
76
|
-
<summary style="font-size:1.25em;">Interrupt a cron job <small>[more]</small></summary>
|
|
77
|
-
|
|
78
|
-
### Interrupt a Cron
|
|
79
|
-
This example demonstrates how to cancel a running cron job.
|
|
80
|
-
|
|
81
|
-
1. Use the same `id` and `topic` that were used to create the cron to cancel it.
|
|
82
|
-
```typescript
|
|
83
|
-
import { MeshCall } from '@hotmeshio/hotmesh';
|
|
84
|
-
import { Client as Postgres } from 'pg';
|
|
85
|
-
|
|
86
|
-
MeshCall.interrupt({
|
|
87
|
-
topic: 'my.cron.function',
|
|
88
|
-
connection: {
|
|
89
|
-
class: Postgres,
|
|
90
|
-
options: {
|
|
91
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
options: { id: 'myNightlyCron123' }
|
|
95
|
-
});
|
|
96
|
-
```
|
|
97
|
-
</details>
|
|
98
|
-
|
|
99
|
-
<details style="padding: .5em">
|
|
100
|
-
<summary style="font-size:1.25em;">Call any function in any service <small>[more]</small></summary>
|
|
101
|
-
|
|
102
|
-
### Call a Function
|
|
103
|
-
Make interservice calls that behave like HTTP but without the setup and performance overhead. This example demonstrates how to connect and call a function.
|
|
104
|
-
|
|
105
|
-
1. Call `MeshCall.connect` and provide a `topic` to uniquely identify the function.
|
|
106
|
-
|
|
107
|
-
```typescript
|
|
108
|
-
//myFunctionWrapper.ts
|
|
109
|
-
import { MeshCall, Types } from '@hotmeshio/hotmesh';
|
|
110
|
-
import { Client as Postgres } from 'pg';
|
|
111
|
-
|
|
112
|
-
export const connectMyFunction = async () => {
|
|
113
|
-
return await MeshCall.connect({
|
|
114
|
-
topic: 'my.demo.function',
|
|
115
|
-
connection: {
|
|
116
|
-
class: Postgres,
|
|
117
|
-
options: {
|
|
118
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
callback: async (input: string) => {
|
|
122
|
-
//your code goes here; response must be JSON serializable
|
|
123
|
-
return { hello: input }
|
|
124
|
-
},
|
|
125
|
-
});
|
|
126
|
-
};
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
2. Call `connectMyFunction` at server startup to connect your function to the mesh.
|
|
130
|
-
|
|
131
|
-
```typescript
|
|
132
|
-
//server.ts
|
|
133
|
-
import { connectMyFunction } from './myFunctionWrapper';
|
|
134
|
-
connectMyFunction();
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
3. Call your function from anywhere on the network (or even from the same service). Send any payload as long as it's JSON serializable.
|
|
138
|
-
|
|
139
|
-
```typescript
|
|
140
|
-
import { MeshCall } from '@hotmeshio/hotmesh';
|
|
141
|
-
import { Client as Postgres } from 'pg';
|
|
142
|
-
|
|
143
|
-
const result = await MeshCall.exec({
|
|
144
|
-
topic: 'my.demo.function',
|
|
145
|
-
args: ['something'],
|
|
146
|
-
connection: {
|
|
147
|
-
class: Postgres,
|
|
148
|
-
options: {
|
|
149
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
150
|
-
}
|
|
151
|
-
},
|
|
152
|
-
}); //returns `{ hello: 'something'}`
|
|
153
|
-
```
|
|
154
|
-
</details>
|
|
155
|
-
|
|
156
|
-
<details style="padding: .5em">
|
|
157
|
-
<summary style="font-size:1.25em;">Call and <b>cache</b> a function <small>[more]</small></summary>
|
|
158
|
-
|
|
159
|
-
### Cache a Function
|
|
160
|
-
This solution builds upon the previous example, caching the response. The linked function will only be re/called when the cached result expires. Everything remains the same, except the caller which specifies an `id` and `ttl`.
|
|
161
|
-
|
|
162
|
-
1. Make the call from another service (or even the same service). Include an `id` and `ttl` to cache the result for the specified duration.
|
|
163
|
-
|
|
164
|
-
```typescript
|
|
165
|
-
import { MeshCall } from '@hotmeshio/hotmesh';
|
|
166
|
-
import { Client as Postgres } from 'pg';
|
|
167
|
-
|
|
168
|
-
const result = await MeshCall.exec({
|
|
169
|
-
topic: 'my.demo.function',
|
|
170
|
-
args: ['anything'],
|
|
171
|
-
connection: {
|
|
172
|
-
class: Postgres,
|
|
173
|
-
options: {
|
|
174
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
175
|
-
}
|
|
176
|
-
},
|
|
177
|
-
options: { id: 'myid123', ttl: '15 minutes' },
|
|
178
|
-
}); //returns `{ hello: 'anything'}`
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
2. Flush the cache at any time, using the same `topic` and cache `id`.
|
|
182
|
-
|
|
183
|
-
```typescript
|
|
184
|
-
import { MeshCall } from '@hotmeshio/hotmesh';
|
|
185
|
-
import { Client as Postgres } from 'pg';
|
|
186
|
-
|
|
187
|
-
await MeshCall.flush({
|
|
188
|
-
topic: 'my.demo.function',
|
|
189
|
-
connection: {
|
|
190
|
-
class: Postgres,
|
|
191
|
-
options: {
|
|
192
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
193
|
-
}
|
|
194
|
-
},
|
|
195
|
-
options: { id: 'myid123' },
|
|
196
|
-
});
|
|
197
|
-
```
|
|
198
|
-
</details>
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
<br/>
|
|
202
|
-
|
|
203
|
-
## MeshFlow
|
|
204
|
-
[MeshFlow](https://hotmeshio.github.io/sdk-typescript/classes/services_meshflow.MeshFlow.html) is a serverless alternative to *Temporal.io*
|
|
205
|
-
|
|
206
|
-
<details style="padding: .5em">
|
|
207
|
-
<summary style="font-size:1.25em;">Orchestrate unpredictable activities <small>[more]</small></summary>
|
|
208
|
-
|
|
209
|
-
### Proxy Activities
|
|
210
|
-
When an endpoint is unpredictable, use `proxyActivities`. HotMesh will retry as necessary until the call succeeds. This example demonstrates a workflow that greets a user in both English and Spanish. Even though both activities throw random errors, the workflow always returns a successful result.
|
|
211
|
-
|
|
212
|
-
1. Start by defining **activities**. Note how each throws an error 50% of the time.
|
|
213
|
-
|
|
214
|
-
```typescript
|
|
215
|
-
//activities.ts
|
|
216
|
-
export async function greet(name: string): Promise<string> {
|
|
217
|
-
if (Math.random() > 0.5) throw new Error('Random error');
|
|
218
|
-
return `Hello, ${name}!`;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
export async function saludar(nombre: string): Promise<string> {
|
|
222
|
-
if (Math.random() > 0.5) throw new Error('Random error');
|
|
223
|
-
return `¡Hola, ${nombre}!`;
|
|
224
|
-
}
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
2. Define the **workflow** logic. Include conditional branching, loops, etc to control activity execution. It's vanilla JavaScript written in your own coding style. The only requirement is to use `proxyActivities`, ensuring your activities are executed with HotMesh's durability wrapper.
|
|
228
|
-
|
|
229
|
-
```typescript
|
|
230
|
-
//workflows.ts
|
|
231
|
-
import { workflow } from '@hotmeshio/hotmesh';
|
|
232
|
-
import * as activities from './activities';
|
|
233
|
-
|
|
234
|
-
const { greet, saludar } = workflow
|
|
235
|
-
.proxyActivities<typeof activities>({
|
|
236
|
-
activities
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
export async function example(name: string): Promise<[string, string]> {
|
|
240
|
-
return Promise.all([
|
|
241
|
-
greet(name),
|
|
242
|
-
saludar(name)
|
|
243
|
-
]);
|
|
244
|
-
}
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
3. Instance a HotMesh **client** to invoke the workflow.
|
|
248
|
-
|
|
249
|
-
```typescript
|
|
250
|
-
//client.ts
|
|
251
|
-
import { Client, HotMesh } from '@hotmeshio/hotmesh';
|
|
252
|
-
import { Client as Postgres } from 'pg';
|
|
253
|
-
|
|
254
|
-
async function run(): Promise<string> {
|
|
255
|
-
const client = new Client({
|
|
256
|
-
connection: {
|
|
257
|
-
class: Postgres,
|
|
258
|
-
options: {
|
|
259
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
const handle = await client.workflow.start<[string,string]>({
|
|
265
|
-
args: ['HotMesh'],
|
|
266
|
-
taskQueue: 'default',
|
|
267
|
-
workflowName: 'example',
|
|
268
|
-
workflowId: HotMesh.guid()
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
return await handle.result();
|
|
272
|
-
//returns ['Hello HotMesh', '¡Hola, HotMesh!']
|
|
273
|
-
}
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
4. Finally, create a **worker** and link the workflow function. Workers listen for tasks on their assigned task queue and invoke the workflow function each time they receive an event.
|
|
277
|
-
|
|
278
|
-
```typescript
|
|
279
|
-
//worker.ts
|
|
280
|
-
import { worker } from '@hotmeshio/hotmesh';
|
|
281
|
-
import { Client as Postgres } from 'pg';
|
|
282
|
-
import * as workflows from './workflows';
|
|
283
|
-
|
|
284
|
-
async function run() {
|
|
285
|
-
const worker = await Worker.create({
|
|
286
|
-
connection: {
|
|
287
|
-
class: Postgres,
|
|
288
|
-
options: {
|
|
289
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
290
|
-
}
|
|
291
|
-
},
|
|
292
|
-
taskQueue: 'default',
|
|
293
|
-
workflow: workflows.example,
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
await worker.run();
|
|
297
|
-
}
|
|
298
|
-
```
|
|
299
|
-
</details>
|
|
300
|
-
|
|
301
|
-
<details style="padding: .5em">
|
|
302
|
-
<summary style="font-size:1.25em;">Pause and wait for a signal <small>[more]</small></summary>
|
|
303
|
-
|
|
304
|
-
### Wait for Signal
|
|
305
|
-
Pause a function and only awaken when a matching signal is received from the outide.
|
|
306
|
-
|
|
307
|
-
1. Define the **workflow** logic. This one waits for the `my-sig-nal` signal, returning the signal payload (`{ hello: 'world' }`) when it eventually arrives. Interleave additional logic to meet your use case.
|
|
308
|
-
|
|
309
|
-
```typescript
|
|
310
|
-
//waitForWorkflow.ts
|
|
311
|
-
import { workflow } from '@hotmeshio/hotmesh';
|
|
312
|
-
|
|
313
|
-
export async function waitForExample(): Promise<{hello: string}> {
|
|
314
|
-
return await workflow.waitFor<{hello: string}>('my-sig-nal');
|
|
315
|
-
//continue processing, use the payload, etc...
|
|
316
|
-
}
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
2. Instance a HotMesh **client** and start a workflow. Use a custom workflow ID (`myWorkflow123`).
|
|
320
|
-
|
|
321
|
-
```typescript
|
|
322
|
-
//client.ts
|
|
323
|
-
import { Client, HotMesh } from '@hotmeshio/hotmesh';
|
|
324
|
-
import { Client as Postgres } from 'pg';
|
|
325
|
-
|
|
326
|
-
async function run(): Promise<string> {
|
|
327
|
-
const client = new Client({
|
|
328
|
-
connection: {
|
|
329
|
-
class: Postgres,
|
|
330
|
-
options: {
|
|
331
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
//start a workflow; it will immediately pause
|
|
337
|
-
await client.workflow.start({
|
|
338
|
-
args: ['HotMesh'],
|
|
339
|
-
taskQueue: 'default',
|
|
340
|
-
workflowName: 'waitForExample',
|
|
341
|
-
workflowId: 'myWorkflow123',
|
|
342
|
-
await: false,
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
3. Create a **worker** and link the `waitForExample` workflow function.
|
|
348
|
-
|
|
349
|
-
```typescript
|
|
350
|
-
//worker.ts
|
|
351
|
-
import { Worker } from '@hotmeshio/hotmesh';
|
|
352
|
-
import { Client as Postgres } from 'pg';
|
|
353
|
-
import * as workflows from './waitForWorkflow';
|
|
354
|
-
|
|
355
|
-
async function run() {
|
|
356
|
-
const worker = await Worker.create({
|
|
357
|
-
connection: {
|
|
358
|
-
class: Postgres,
|
|
359
|
-
options: {
|
|
360
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
361
|
-
}
|
|
362
|
-
},
|
|
363
|
-
taskQueue: 'default',
|
|
364
|
-
workflow: workflows.waitForExample,
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
await worker.run();
|
|
368
|
-
}
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
4. Send a signal to awaken the paused function; await the function result.
|
|
372
|
-
|
|
373
|
-
```typescript
|
|
374
|
-
import { Client } from '@hotmeshio/hotmesh';
|
|
375
|
-
import { Client as Postgres } from 'pg';
|
|
376
|
-
|
|
377
|
-
const client = new Client({
|
|
378
|
-
connection: {
|
|
379
|
-
class: Postgres,
|
|
380
|
-
options: {
|
|
381
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
//awaken the function by sending a signal
|
|
387
|
-
await client.signal('my-sig-nal', { hello: 'world' });
|
|
388
|
-
|
|
389
|
-
//get the workflow handle and await the result
|
|
390
|
-
const handle = await client.getHandle({
|
|
391
|
-
taskQueue: 'default',
|
|
392
|
-
workflowId: 'myWorkflow123'
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
const result = await handle.result();
|
|
396
|
-
//returns { hello: 'world' }
|
|
397
|
-
```
|
|
398
|
-
</details>
|
|
399
|
-
|
|
400
|
-
<details style="padding: .5em">
|
|
401
|
-
<summary style="font-size:1.25em;">Wait for multiple signals (collation) <small>[more]</small></summary>
|
|
402
|
-
|
|
403
|
-
### Collate Multiple Signals
|
|
404
|
-
Use a standard `Promise` to collate and cache multiple signals. HotMesh will only awaken once **all** signals have arrived. HotMesh will track up to 25 concurrent signals.
|
|
405
|
-
|
|
406
|
-
1. Update the **workflow** logic to await two signals using a promise: `my-sig-nal-1` and `my-sig-nal-2`. Add additional logic to meet your use case.
|
|
407
|
-
|
|
408
|
-
```typescript
|
|
409
|
-
//waitForWorkflows.ts
|
|
410
|
-
import { workflow } from '@hotmeshio/hotmesh';
|
|
411
|
-
|
|
412
|
-
export async function waitForExample(): Promise<[boolean, number]> {
|
|
413
|
-
const [s1, s2] = await Promise.all([
|
|
414
|
-
workflow.waitFor<boolean>('my-sig-nal-1'),
|
|
415
|
-
workflow.waitFor<number>('my-sig-nal-2')
|
|
416
|
-
]);
|
|
417
|
-
//do something with the signal payloads (s1, s2)
|
|
418
|
-
return [s1, s2];
|
|
419
|
-
}
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
2. Send **two** signals to awaken the paused function.
|
|
423
|
-
|
|
424
|
-
```typescript
|
|
425
|
-
import { Client } from '@hotmeshio/hotmesh';
|
|
426
|
-
import { Client as Postgres } from 'pg';
|
|
427
|
-
|
|
428
|
-
const client = new Client({
|
|
429
|
-
connection: {
|
|
430
|
-
class: Postgres,
|
|
431
|
-
options: {
|
|
432
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
//send 2 signals to awaken the function; order is unimportant
|
|
438
|
-
await client.signal('my-sig-nal-2', 12345);
|
|
439
|
-
await client.signal('my-sig-nal-1', true);
|
|
440
|
-
|
|
441
|
-
//get the workflow handle and await the collated result
|
|
442
|
-
const handle = await client.getHandle({
|
|
443
|
-
taskQueue: 'default',
|
|
444
|
-
workflowId: 'myWorkflow123'
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
const result = await handle.result();
|
|
448
|
-
//returns [true, 12345]
|
|
449
|
-
```
|
|
450
|
-
</details>
|
|
451
|
-
|
|
452
|
-
<details style="padding: .5em">
|
|
453
|
-
<summary style="font-size:1.25em;">Create a recurring, cyclical workflow <small>[more]</small></summary>
|
|
454
|
-
|
|
455
|
-
### Cyclical Workflow
|
|
456
|
-
This example calls an activity and then sleeps for a week. It runs indefinitely until it's manually stopped. It takes advantage of durable execution and can safely sleep for months or years.
|
|
457
|
-
|
|
458
|
-
>Container restarts have no impact on actively executing workflows as all state is retained in the backend.
|
|
459
|
-
|
|
460
|
-
1. Define the **workflow** logic. This one calls a legacy `statusDiagnostic` function once a week.
|
|
461
|
-
|
|
462
|
-
```typescript
|
|
463
|
-
//recurringWorkflow.ts
|
|
464
|
-
import { workflow } from '@hotmeshio/hotmesh';
|
|
465
|
-
import * as activities from './activities';
|
|
466
|
-
|
|
467
|
-
const { statusDiagnostic } = workflow
|
|
468
|
-
.proxyActivities<typeof activities>({
|
|
469
|
-
activities
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
export async function recurringExample(someValue: number): Promise<void> {
|
|
473
|
-
do {
|
|
474
|
-
await statusDiagnostic(someValue);
|
|
475
|
-
} while (await workflow.sleepFor('1 week'));
|
|
476
|
-
}
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
2. Instance a HotMesh **client** and start a workflow. Assign a custom workflow ID (e.g., `myRecurring123`) if the workflow should be idempotent.
|
|
480
|
-
|
|
481
|
-
```typescript
|
|
482
|
-
//client.ts
|
|
483
|
-
import { Client, HotMesh } from '@hotmeshio/hotmesh';
|
|
484
|
-
import { Client as Postgres } from 'pg';
|
|
485
|
-
|
|
486
|
-
async function run(): Promise<string> {
|
|
487
|
-
const client = new Client({
|
|
488
|
-
connection: {
|
|
489
|
-
class: Postgres,
|
|
490
|
-
options: {
|
|
491
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
});
|
|
495
|
-
|
|
496
|
-
//start a workflow; it will immediately pause
|
|
497
|
-
await client.workflow.start({
|
|
498
|
-
args: [55],
|
|
499
|
-
taskQueue: 'default',
|
|
500
|
-
workflowName: 'recurringExample',
|
|
501
|
-
workflowId: 'myRecurring123',
|
|
502
|
-
await: false,
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
3. Create a **worker** and link the `recurringExample` workflow function.
|
|
508
|
-
|
|
509
|
-
```typescript
|
|
510
|
-
//worker.ts
|
|
511
|
-
import { Worker } from '@hotmeshio/hotmesh';
|
|
512
|
-
import { Client as Postgres } from 'pg';
|
|
513
|
-
import * as workflows from './recurringWorkflow';
|
|
514
|
-
|
|
515
|
-
async function run() {
|
|
516
|
-
const worker = await Worker.create({
|
|
517
|
-
connection: {
|
|
518
|
-
class: Postgres,
|
|
519
|
-
options: {
|
|
520
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
521
|
-
}
|
|
522
|
-
},
|
|
523
|
-
taskQueue: 'default',
|
|
524
|
-
workflow: workflows.recurringExample,
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
await worker.run();
|
|
528
|
-
}
|
|
529
|
-
```
|
|
530
|
-
|
|
531
|
-
4. Cancel the recurring workflow (`myRecurring123`) by calling `interrupt`.
|
|
532
|
-
|
|
533
|
-
```typescript
|
|
534
|
-
import { Client } from '@hotmeshio/hotmesh';
|
|
535
|
-
import { Client as Postgres } from 'pg';
|
|
34
|
+
### Start a workflow
|
|
35
|
+
```typescript
|
|
36
|
+
// index.ts
|
|
37
|
+
import { MemFlow } from '@hotmeshio/hotmesh';
|
|
38
|
+
import { Client as Postgres } from 'pg';
|
|
536
39
|
|
|
537
|
-
|
|
40
|
+
async function main() {
|
|
41
|
+
const mf = await MemFlow.init({
|
|
42
|
+
appId: 'my-app',
|
|
43
|
+
engine: {
|
|
538
44
|
connection: {
|
|
539
45
|
class: Postgres,
|
|
540
|
-
options: {
|
|
541
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
542
|
-
}
|
|
46
|
+
options: { connectionString: process.env.DATABASE_URL }
|
|
543
47
|
}
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
//get the workflow handle and interrupt it
|
|
547
|
-
const handle = await client.getHandle({
|
|
548
|
-
taskQueue: 'default',
|
|
549
|
-
workflowId: 'myRecurring123'
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
const result = await handle.interrupt();
|
|
553
|
-
```
|
|
554
|
-
</details>
|
|
555
|
-
|
|
556
|
-
<br/>
|
|
557
|
-
|
|
558
|
-
## MeshData
|
|
559
|
-
[MeshData](https://hotmeshio.github.io/sdk-typescript/classes/services_meshdata.MeshData.html) adds analytics to running workflows
|
|
560
|
-
|
|
561
|
-
<details style="padding: .5em">
|
|
562
|
-
<summary style="font-size:1.25em;">Create a search index <small>[more]</small></summary>
|
|
563
|
-
|
|
564
|
-
### Workflow Data Indexes
|
|
565
|
-
|
|
566
|
-
This example demonstrates how to define a schema and deploy an index for a 'user' entity type.
|
|
567
|
-
|
|
568
|
-
1. Define the **schema** for the `user` entity. This one includes the 3 formats supported by the FT.SEARCH module: `TEXT`, `TAG` and `NUMERIC`.
|
|
569
|
-
|
|
570
|
-
```typescript
|
|
571
|
-
//schema.ts
|
|
572
|
-
export const schema: Types.WorkflowSearchOptions = {
|
|
573
|
-
schema: {
|
|
574
|
-
id: { type: 'TAG', sortable: false },
|
|
575
|
-
first: { type: 'TEXT', sortable: false, nostem: true },
|
|
576
|
-
active: { type: 'TAG', sortable: false },
|
|
577
|
-
created: { type: 'NUMERIC', sortable: true },
|
|
578
|
-
},
|
|
579
|
-
index: 'user',
|
|
580
|
-
prefix: ['user'],
|
|
581
|
-
};
|
|
582
|
-
```
|
|
583
|
-
|
|
584
|
-
2. Create the index upon server startup. This one initializes the 'user' index, using the schema defined in the previous step. It's OK to call `createSearchIndex` multiple times; it will only create the index if it doesn't already exist.
|
|
585
|
-
|
|
586
|
-
```typescript
|
|
587
|
-
//server.ts
|
|
588
|
-
import { MeshData } from '@hotmeshio/hotmesh';
|
|
589
|
-
import { Client as Postgres } from 'pg';
|
|
590
|
-
import { schema } from './schema';
|
|
591
|
-
|
|
592
|
-
const meshData = new MeshData({
|
|
593
|
-
class: Postgres,
|
|
594
|
-
options: {
|
|
595
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
596
|
-
}
|
|
597
|
-
},
|
|
598
|
-
schema,
|
|
599
|
-
);
|
|
600
|
-
await meshData.createSearchIndex('user', { namespace: 'meshdata' });
|
|
601
|
-
```
|
|
602
|
-
</details>
|
|
603
|
-
|
|
604
|
-
<details style="padding: .5em">
|
|
605
|
-
<summary style="font-size:1.25em;">Create an indexed, searchable record <small>[more]</small></summary>
|
|
606
|
-
|
|
607
|
-
### Workflow Record Data
|
|
608
|
-
This example demonstrates how to create a 'user' workflow backed by the searchable schema from the prior example.
|
|
609
|
-
|
|
610
|
-
1. Call MeshData `connect` to initialize a 'user' entity *worker*. It references a target worker function which will run the workflow. Data fields that are documented in the schema (like `active`) will be automatically indexed when set on the workflow record.
|
|
611
|
-
|
|
612
|
-
```typescript
|
|
613
|
-
//connect.ts
|
|
614
|
-
import { MeshData } from '@hotmeshio/hotmesh';
|
|
615
|
-
import { Client as Postgres } from 'pg';
|
|
616
|
-
import { schema } from './schema';
|
|
617
|
-
|
|
618
|
-
export const connectUserWorker = async (): Promise<void> => {
|
|
619
|
-
const meshData = new MeshData({
|
|
620
|
-
class: Postgres,
|
|
621
|
-
options: {
|
|
622
|
-
connectionString: 'postgresql:// usr:pwd@localhost:5432/db'
|
|
623
|
-
}
|
|
624
|
-
},
|
|
625
|
-
schema,
|
|
626
|
-
);
|
|
627
|
-
|
|
628
|
-
await meshData.connect({
|
|
629
|
-
entity: 'user',
|
|
630
|
-
target: async function(name: string): Promise<string> {
|
|
631
|
-
//add custom, searchable data (`active`) and return
|
|
632
|
-
const search = await MeshData.workflow.search();
|
|
633
|
-
await search.set('active', 'yes');
|
|
634
|
-
return `Welcome, ${name}.`;
|
|
635
|
-
},
|
|
636
|
-
options: { namespace: 'meshdata' },
|
|
637
|
-
});
|
|
638
48
|
}
|
|
639
|
-
|
|
49
|
+
});
|
|
640
50
|
|
|
641
|
-
|
|
51
|
+
// Kick off a workflow
|
|
52
|
+
const handle = await mf.workflow.start({
|
|
53
|
+
workflowName: 'example',
|
|
54
|
+
args: ['Jane'],
|
|
55
|
+
taskQueue: 'contextual'
|
|
56
|
+
});
|
|
642
57
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
import { connectUserWorker } from './connect';
|
|
646
|
-
await connectUserWorker();
|
|
647
|
-
```
|
|
58
|
+
console.log('Result:', await handle.result());
|
|
59
|
+
}
|
|
648
60
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
```typescript
|
|
652
|
-
//exec.ts
|
|
653
|
-
import { MeshData } from '@hotmeshio/hotmesh';
|
|
654
|
-
import { Client as Postgres } from 'pg';
|
|
655
|
-
|
|
656
|
-
const meshData = new MeshData({
|
|
657
|
-
class: Postgres,
|
|
658
|
-
options: {
|
|
659
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
660
|
-
}
|
|
661
|
-
},
|
|
662
|
-
schema,
|
|
663
|
-
);
|
|
664
|
-
|
|
665
|
-
export const newUser = async (id: string, name: string): Promise<string> => {
|
|
666
|
-
const response = await meshData.exec({
|
|
667
|
-
entity: 'user',
|
|
668
|
-
args: [name],
|
|
669
|
-
options: {
|
|
670
|
-
ttl: 'infinity',
|
|
671
|
-
id,
|
|
672
|
-
search: {
|
|
673
|
-
data: { id, name, timestamp: Date.now() }
|
|
674
|
-
},
|
|
675
|
-
namespace: 'meshdata',
|
|
676
|
-
},
|
|
677
|
-
});
|
|
678
|
-
return response;
|
|
679
|
-
};
|
|
680
|
-
```
|
|
681
|
-
|
|
682
|
-
4. Call the `newUser` function to create a searchable 'user' record.
|
|
683
|
-
|
|
684
|
-
```typescript
|
|
685
|
-
import { newUser } from './exec';
|
|
686
|
-
const response = await newUser('jim123', 'James');
|
|
687
|
-
```
|
|
688
|
-
</details>
|
|
689
|
-
|
|
690
|
-
<details style="padding: .5em">
|
|
691
|
-
<summary style="font-size:1.25em;">Fetch record data <small>[more]</small></summary>
|
|
692
|
-
|
|
693
|
-
### Read Record Data
|
|
694
|
-
This example demonstrates how to read data fields directly from a workflow.
|
|
695
|
-
|
|
696
|
-
1. Read data fields directly from the *jimbo123* 'user' record.
|
|
697
|
-
|
|
698
|
-
```typescript
|
|
699
|
-
//read.ts
|
|
700
|
-
import { MeshData } from '@hotmeshio/hotmesh';
|
|
701
|
-
import { Client as Postgres } from 'pg';
|
|
702
|
-
import { schema } from './schema';
|
|
703
|
-
|
|
704
|
-
const meshData = new MeshData({
|
|
705
|
-
class: Postgres,
|
|
706
|
-
options: {
|
|
707
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
708
|
-
}
|
|
709
|
-
},
|
|
710
|
-
schema,
|
|
711
|
-
);
|
|
712
|
-
|
|
713
|
-
const data = await meshData.get(
|
|
714
|
-
'user',
|
|
715
|
-
'jimbo123',
|
|
716
|
-
{
|
|
717
|
-
fields: ['id', 'name', 'timestamp', 'active'],
|
|
718
|
-
namespace: 'meshdata'
|
|
719
|
-
},
|
|
720
|
-
);
|
|
721
|
-
```
|
|
722
|
-
</details>
|
|
723
|
-
|
|
724
|
-
<details style="padding: .5em">
|
|
725
|
-
<summary style="font-size:1.25em;">Search record data <small>[more]</small></summary>
|
|
726
|
-
|
|
727
|
-
### Query Record Data
|
|
728
|
-
This example demonstrates how to search for those workflows where a given condition exists in the data. This one searches for active users. *NOTE: The native Redis FT.SEARCH syntax and SQL are currently supported. The JSON abstraction shown here is a convenience method for straight-forward, one-dimensional queries.*
|
|
729
|
-
|
|
730
|
-
1. Search for active users (where the value of the `active` field is `yes`).
|
|
731
|
-
|
|
732
|
-
```typescript
|
|
733
|
-
//read.ts
|
|
734
|
-
import { MeshData } from '@hotmeshio/hotmesh';
|
|
735
|
-
import { Client as Postgres } from 'pg';
|
|
736
|
-
import { schema } from './schema';
|
|
737
|
-
|
|
738
|
-
const meshData = new MeshData({
|
|
739
|
-
class: Postgres,
|
|
740
|
-
options: {
|
|
741
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
742
|
-
}
|
|
743
|
-
},
|
|
744
|
-
schema,
|
|
745
|
-
);
|
|
746
|
-
|
|
747
|
-
const results = await meshData.findWhere('user', {
|
|
748
|
-
query: [{ field: 'active', is: '=', value: 'yes' }],
|
|
749
|
-
limit: { start: 0, size: 100 },
|
|
750
|
-
return: ['id', 'name', 'timestamp', 'active']
|
|
751
|
-
});
|
|
752
|
-
```
|
|
753
|
-
</details>
|
|
754
|
-
|
|
755
|
-
<br/>
|
|
756
|
-
|
|
757
|
-
## Connect
|
|
758
|
-
HotMesh is pluggable and fully supports **Postgres** and **Redis/ValKey** backends.
|
|
759
|
-
|
|
760
|
-
**NATS** can be added for *pub-sub* support (when extended pattern matching is desired). And *streams* support is currently in alpha (**NATS** + **JetStream** + **Postgres**).
|
|
761
|
-
|
|
762
|
-
<details style="padding: .5em">
|
|
763
|
-
<summary style="font-size:1.25em;">Postgres <small>[more]</small></summary>
|
|
61
|
+
main().catch(console.error);
|
|
62
|
+
```
|
|
764
63
|
|
|
765
|
-
### Connect Postgres Client
|
|
766
|
-
```typescript
|
|
767
|
-
import { Client as PostgresClient } from 'pg';
|
|
768
64
|
|
|
769
|
-
|
|
770
|
-
const connection = {
|
|
771
|
-
class: PostgresClient,
|
|
772
|
-
options: {
|
|
773
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
774
|
-
}
|
|
775
|
-
};
|
|
65
|
+
## 🧠 How Permanent Memory Works
|
|
776
66
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
67
|
+
* **Context = JSONB row** in `<yourappname>.jobs` table
|
|
68
|
+
* **Atomic operations** (`set`, `merge`, `append`, `increment`, `toggle`, `delete`, …)
|
|
69
|
+
* **Transactional** – every update participates in the workflow/DB transaction
|
|
70
|
+
* **Time-travel-safe** – full replay compatibility; side-effect detector guarantees determinism
|
|
71
|
+
* **Hook-friendly** – any worker with the record ID can attach and mutate its slice of the JSON
|
|
782
72
|
|
|
783
|
-
|
|
784
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db'
|
|
785
|
-
});
|
|
73
|
+
* Context data is stored as JSONB; add partial indexes for improved query analysis.
|
|
786
74
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
75
|
+
**Example: Adding a Partial Index for Specific Entity Types**
|
|
76
|
+
```sql
|
|
77
|
+
-- Create a partial index for 'user' entities with specific context values
|
|
78
|
+
CREATE INDEX idx_user_premium ON your_app.jobs (id)
|
|
79
|
+
WHERE entity = 'user' AND (context->>'isPremium')::boolean = true;
|
|
792
80
|
```
|
|
81
|
+
This index will only be used for queries that match both conditions, making lookups for premium users much faster.
|
|
793
82
|
|
|
794
|
-
|
|
83
|
+
---
|
|
795
84
|
|
|
796
|
-
|
|
797
|
-
<summary style="font-size:1.25em;">Redis <small>[more]</small></summary>
|
|
85
|
+
## 🔌 Hooks & Context API – Full Example
|
|
798
86
|
|
|
799
|
-
### Redis/IORedis
|
|
800
87
|
```typescript
|
|
801
|
-
import
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
88
|
+
import { MemFlow } from '@hotmeshio/hotmesh';
|
|
89
|
+
|
|
90
|
+
/* ------------ Main workflow ------------ */
|
|
91
|
+
export async function example(name: string): Promise<any> {
|
|
92
|
+
//the context method provides transactional, replayable access to shared job state
|
|
93
|
+
const ctx = await MemFlow.workflow.context();
|
|
94
|
+
|
|
95
|
+
//create the initial context (even arrays are supported)
|
|
96
|
+
await ctx.set({
|
|
97
|
+
user: { name },
|
|
98
|
+
hooks: {},
|
|
99
|
+
metrics: { count: 0 }
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Call two hooks in parallel to updaet the same shared context
|
|
103
|
+
const [r1, r2] = await Promise.all([
|
|
104
|
+
MemFlow.workflow.execHook({
|
|
105
|
+
taskQueue: 'contextual',
|
|
106
|
+
workflowName: 'hook1',
|
|
107
|
+
args: [name, 'hook1'],
|
|
108
|
+
signalId: 'hook1-complete',
|
|
109
|
+
}),
|
|
110
|
+
MemFlow.workflow.execHook({
|
|
111
|
+
taskQueue: 'contextual',
|
|
112
|
+
workflowName: 'hook2',
|
|
113
|
+
args: [name, 'hook2'],
|
|
114
|
+
signalId: 'hook2-complete',
|
|
115
|
+
})
|
|
116
|
+
]);
|
|
117
|
+
|
|
118
|
+
// merge here (or have the hooks merge in...everyone can access context)
|
|
119
|
+
await ctx.merge({ hooks: { r1, r2 } });
|
|
120
|
+
await ctx.increment('metrics.count', 2);
|
|
121
|
+
|
|
122
|
+
return "The main has completed; the db record persists and can be hydrated; hook in from the outside!";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* ------------ Hook 1 (hooks have access to methods like sleepFor) ------------ */
|
|
126
|
+
export async function hook1(name: string, kind: string): Promise<any> {
|
|
127
|
+
await MemFlow.workflow.sleepFor('2 seconds');
|
|
128
|
+
const res = { kind, processed: true, at: Date.now() };
|
|
129
|
+
await MemFlow.workflow.signal('hook1-complete', res);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* ------------ Hook 2 (hooks can access shared job context) ------------ */
|
|
133
|
+
export async function hook2(name: string, kind: string): Promise<void> {
|
|
134
|
+
const ctx = await MemFlow.workflow.context();
|
|
135
|
+
await ctx.merge({ user: { lastSeen: new Date().toISOString() } });
|
|
136
|
+
await MemFlow.workflow.signal('hook2-complete', { ok: true });
|
|
137
|
+
}
|
|
810
138
|
```
|
|
811
|
-
</details>
|
|
812
|
-
|
|
813
|
-
<details style="padding: .5em">
|
|
814
|
-
<summary style="font-size:1.25em;">NATS <small>[more]</small></summary>
|
|
815
|
-
|
|
816
|
-
### NATS PubSub
|
|
817
|
-
Add NATS for improved PubSub support, including patterned subscriptions. Note the explicit channel subscription in the example below. *The NATS provider supports version 2.0 of the NATS client (the latest version). See ./package.json for details.*
|
|
818
139
|
|
|
819
|
-
|
|
820
|
-
import { Client as Postgres } from 'pg';
|
|
821
|
-
import { connect as NATS } from 'nats';
|
|
140
|
+
**Highlights**
|
|
822
141
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db',
|
|
828
|
-
}
|
|
829
|
-
},
|
|
830
|
-
stream: {
|
|
831
|
-
class: Postgres,
|
|
832
|
-
options: {
|
|
833
|
-
connectionString: 'postgresql://usr:pwd@localhost:5432/db',
|
|
834
|
-
}
|
|
835
|
-
},
|
|
836
|
-
sub: {
|
|
837
|
-
class: NATS,
|
|
838
|
-
options: { servers: ['nats:4222'] }
|
|
839
|
-
},
|
|
840
|
-
};
|
|
841
|
-
```
|
|
842
|
-
</details>
|
|
142
|
+
* Hook functions are replay-safe.
|
|
143
|
+
* Hook functions can safely read and write to the the *same* JSON context.
|
|
144
|
+
* All context operations (`set`, `merge`, `append`, etc.) execute transactionally.
|
|
145
|
+
* Context data is stored as JSONB; add partial indexes for improved query analysis.
|
|
843
146
|
|
|
844
|
-
|
|
147
|
+
---
|
|
845
148
|
|
|
846
|
-
##
|
|
847
|
-
HotMesh's **OpenTelemetry** output provides insight into long-running, cross-service transactions. Add an OpenTelemetry sink to any service where HotMesh is deployed and HotMesh will emit the full **OpenTelemetry** execution tree organized as a single, unified DAG.
|
|
149
|
+
## 🤖 Building Durable AI Agents
|
|
848
150
|
|
|
849
|
-
|
|
151
|
+
Permanent memory unlocks a straightforward pattern for agentic systems:
|
|
850
152
|
|
|
851
|
-
|
|
153
|
+
1. **Planner workflow** – sketches a task list, seeds context.
|
|
154
|
+
2. **Tool hooks** – execute individual tasks, feeding intermediate results back into context.
|
|
155
|
+
3. **Reflector hook** – periodically summarises context into long-term memory embeddings.
|
|
156
|
+
4. **Supervisor workflow** – monitors metrics stored in context and decides when to finish.
|
|
852
157
|
|
|
853
|
-
|
|
854
|
-
|
|
158
|
+
Because every step is durable *and* shares the same knowledge object, agents can pause,
|
|
159
|
+
restart, scale horizontally, and keep evolving their world-model indefinitely.
|
|
855
160
|
|
|
856
|
-
|
|
161
|
+
---
|
|
857
162
|
|
|
858
|
-
|
|
163
|
+
## 📚 Documentation & Links
|
|
859
164
|
|
|
860
|
-
|
|
861
|
-
|
|
165
|
+
* SDK API – [https://hotmeshio.github.io/sdk-typescript](https://hotmeshio.github.io/sdk-typescript)
|
|
166
|
+
* Examples – [https://github.com/hotmeshio/samples-typescript](https://github.com/hotmeshio/samples-typescript)
|
|
862
167
|
|
|
863
|
-
|
|
168
|
+
---
|
|
864
169
|
|
|
865
|
-
##
|
|
170
|
+
## License
|
|
866
171
|
|
|
867
|
-
|
|
172
|
+
Apache 2.0 – see `LICENSE` for details.
|