@hotmeshio/hotmesh 0.6.1 → 0.7.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 +179 -142
- package/build/modules/enums.d.ts +7 -0
- package/build/modules/enums.js +16 -1
- package/build/modules/utils.d.ts +27 -0
- package/build/modules/utils.js +52 -1
- package/build/package.json +10 -8
- package/build/services/connector/providers/postgres.js +3 -0
- package/build/services/hotmesh/index.d.ts +66 -15
- package/build/services/hotmesh/index.js +84 -15
- package/build/services/memflow/index.d.ts +100 -14
- package/build/services/memflow/index.js +100 -14
- package/build/services/memflow/worker.d.ts +97 -0
- package/build/services/memflow/worker.js +217 -0
- package/build/services/memflow/workflow/proxyActivities.d.ts +74 -3
- package/build/services/memflow/workflow/proxyActivities.js +81 -4
- package/build/services/router/consumption/index.d.ts +2 -1
- package/build/services/router/consumption/index.js +38 -2
- package/build/services/router/error-handling/index.d.ts +3 -3
- package/build/services/router/error-handling/index.js +48 -13
- package/build/services/router/index.d.ts +1 -0
- package/build/services/router/index.js +2 -1
- package/build/services/store/index.d.ts +3 -2
- package/build/services/store/providers/postgres/kvtypes/hash/basic.js +36 -6
- package/build/services/store/providers/postgres/kvtypes/hash/expire.js +12 -2
- package/build/services/store/providers/postgres/kvtypes/hash/scan.js +30 -10
- package/build/services/store/providers/postgres/kvtypes/list.js +68 -10
- package/build/services/store/providers/postgres/kvtypes/string.js +60 -10
- package/build/services/store/providers/postgres/kvtypes/zset.js +92 -22
- package/build/services/store/providers/postgres/postgres.d.ts +3 -3
- package/build/services/store/providers/redis/_base.d.ts +3 -3
- package/build/services/store/providers/redis/ioredis.js +17 -7
- package/build/services/stream/providers/postgres/kvtables.js +76 -23
- package/build/services/stream/providers/postgres/lifecycle.d.ts +19 -0
- package/build/services/stream/providers/postgres/lifecycle.js +54 -0
- package/build/services/stream/providers/postgres/messages.d.ts +56 -0
- package/build/services/stream/providers/postgres/messages.js +253 -0
- package/build/services/stream/providers/postgres/notifications.d.ts +59 -0
- package/build/services/stream/providers/postgres/notifications.js +357 -0
- package/build/services/stream/providers/postgres/postgres.d.ts +110 -11
- package/build/services/stream/providers/postgres/postgres.js +196 -488
- package/build/services/stream/providers/postgres/scout.d.ts +68 -0
- package/build/services/stream/providers/postgres/scout.js +233 -0
- package/build/services/stream/providers/postgres/stats.d.ts +49 -0
- package/build/services/stream/providers/postgres/stats.js +113 -0
- package/build/services/sub/providers/postgres/postgres.js +37 -5
- package/build/services/sub/providers/redis/ioredis.js +13 -2
- package/build/services/sub/providers/redis/redis.js +13 -2
- package/build/services/worker/index.d.ts +1 -0
- package/build/services/worker/index.js +2 -0
- package/build/types/hotmesh.d.ts +42 -2
- package/build/types/index.d.ts +3 -3
- package/build/types/memflow.d.ts +32 -0
- package/build/types/provider.d.ts +16 -0
- package/build/types/stream.d.ts +92 -1
- package/package.json +10 -8
package/README.md
CHANGED
|
@@ -1,189 +1,226 @@
|
|
|
1
1
|
# HotMesh
|
|
2
2
|
|
|
3
|
-
**Integrate AI automation into your current stack — without breaking it**
|
|
4
|
-
|
|
5
3
|

|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
Each process runs with persistent memory in Postgres, surviving retries, crashes, and human delays.
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
npm install @hotmeshio/hotmesh
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## What It Solves
|
|
17
|
-
|
|
18
|
-
Modernization often stalls where systems meet people and AI.
|
|
19
|
-
HotMesh builds a **durable execution bridge** across those seams — linking your database, APIs, RPA, and AI agents into one recoverable process.
|
|
20
|
-
|
|
21
|
-
* **AI that can fail safely** — retries, resumable state, and confidence tracking
|
|
22
|
-
* **Human steps that don’t block** — pause for days, resume instantly
|
|
23
|
-
* **Legacy systems that stay connected** — SQL and RPA coexist seamlessly
|
|
24
|
-
* **Full visibility** — query workflows and outcomes directly in SQL
|
|
25
|
-
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
## Core Model
|
|
5
|
+
Run durable workflows on Postgres. No servers, no queues, just your database.
|
|
29
6
|
|
|
30
|
-
### Entity — the Business Process Record
|
|
31
7
|
|
|
32
|
-
|
|
33
|
-
It becomes the shared memory between APIs, RPA jobs, LLM agents, and human operators.
|
|
8
|
+
## Common Use Cases
|
|
34
9
|
|
|
35
|
-
|
|
36
|
-
|
|
10
|
+
### 1. Pipeline Database
|
|
11
|
+
Transform Postgres into a durable pipeline processor. Orchestrate long-running, multi-step pipelines transactionally and durably.
|
|
37
12
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
caseId: "A42",
|
|
41
|
-
stage: "verification",
|
|
42
|
-
retries: 0,
|
|
43
|
-
notes: []
|
|
44
|
-
});
|
|
13
|
+
### 2. Temporal You Own
|
|
14
|
+
Get the power of Temporal without the infrastructure. HotMesh includes MemFlow, a Temporal-compatible API that runs directly on your Postgres database. No app server required.
|
|
45
15
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
aiSummary: { result: "Verified coverage", confidence: 0.93 },
|
|
49
|
-
stage: "approval",
|
|
50
|
-
});
|
|
16
|
+
### 3. Distributed State Machine
|
|
17
|
+
Build resilient, stateful applications where every component can fail and recover. HotMesh manages state transitions, retries, and coordination.
|
|
51
18
|
|
|
52
|
-
|
|
53
|
-
|
|
19
|
+
### 4. Workflow-as-Code Platform
|
|
20
|
+
Choose your style: procedural workflows with MemFlow's Temporal API, or functional workflows with HotMesh's YAML syntax.
|
|
54
21
|
|
|
55
|
-
|
|
56
|
-
await e.increment("retries", 1);
|
|
22
|
+
## Installation
|
|
57
23
|
|
|
58
|
-
|
|
59
|
-
|
|
24
|
+
```bash
|
|
25
|
+
npm install @hotmeshio/hotmesh
|
|
60
26
|
```
|
|
61
27
|
|
|
62
|
-
|
|
28
|
+
## Two ways to write workflows
|
|
63
29
|
|
|
64
|
-
|
|
65
|
-
| ------------- | ---------------------------------- |
|
|
66
|
-
| `set()` | Initialize workflow state |
|
|
67
|
-
| `merge()` | Update any JSON path |
|
|
68
|
-
| `append()` | Add entries to lists (logs, notes) |
|
|
69
|
-
| `increment()` | Maintain counters or metrics |
|
|
70
|
-
| `get()` | Retrieve current state |
|
|
30
|
+
Both approaches reuse your activity functions:
|
|
71
31
|
|
|
72
|
-
|
|
32
|
+
```typescript
|
|
33
|
+
// activities.ts (shared between both approaches)
|
|
34
|
+
export async function checkInventory(itemId: string): Promise<number> {
|
|
35
|
+
return getInventoryCount(itemId);
|
|
36
|
+
}
|
|
73
37
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
WHERE entity = 'claims-review'
|
|
78
|
-
AND context->>'stage' != 'complete';
|
|
79
|
-
```
|
|
38
|
+
export async function reserveItem(itemId: string, quantity: number): Promise<string> {
|
|
39
|
+
return createReservation(itemId, quantity);
|
|
40
|
+
}
|
|
80
41
|
|
|
81
|
-
|
|
42
|
+
export async function notifyBackorder(itemId: string): Promise<void> {
|
|
43
|
+
await sendBackorderEmail(itemId);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
82
46
|
|
|
83
|
-
###
|
|
47
|
+
### Option 1: Code (Temporal-compatible API)
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// workflows.ts
|
|
51
|
+
import { MemFlow } from '@hotmeshio/hotmesh';
|
|
52
|
+
import * as activities from './activities';
|
|
53
|
+
|
|
54
|
+
export async function orderWorkflow(itemId: string, qty: number) {
|
|
55
|
+
const { checkInventory, reserveItem, notifyBackorder } =
|
|
56
|
+
MemFlow.workflow.proxyActivities<typeof activities>({
|
|
57
|
+
taskQueue: 'inventory-tasks'
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const available = await checkInventory(itemId);
|
|
61
|
+
|
|
62
|
+
if (available >= qty) {
|
|
63
|
+
return await reserveItem(itemId, qty);
|
|
64
|
+
} else {
|
|
65
|
+
await notifyBackorder(itemId);
|
|
66
|
+
return 'backordered';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
84
69
|
|
|
85
|
-
|
|
86
|
-
|
|
70
|
+
// main.ts
|
|
71
|
+
const connection = {
|
|
72
|
+
class: Postgres,
|
|
73
|
+
options: { connectionString: 'postgresql://localhost:5432/mydb' }
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
await MemFlow.registerActivityWorker({
|
|
77
|
+
connection,
|
|
78
|
+
taskQueue: 'inventory-tasks'
|
|
79
|
+
}, activities, 'inventory-activities');
|
|
80
|
+
|
|
81
|
+
await MemFlow.Worker.create({
|
|
82
|
+
connection,
|
|
83
|
+
taskQueue: 'orders',
|
|
84
|
+
workflow: orderWorkflow
|
|
85
|
+
});
|
|
87
86
|
|
|
88
|
-
|
|
89
|
-
await
|
|
90
|
-
|
|
91
|
-
|
|
87
|
+
const client = new MemFlow.Client({ connection });
|
|
88
|
+
const handle = await client.workflow.start({
|
|
89
|
+
args: ['item-123', 5],
|
|
90
|
+
taskQueue: 'orders',
|
|
91
|
+
workflowName: 'orderWorkflow',
|
|
92
|
+
workflowId: 'order-456'
|
|
92
93
|
});
|
|
93
|
-
```
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
```ts
|
|
98
|
-
// Run independent research perspectives in parallel using batch execution
|
|
99
|
-
await MemFlow.workflow.execHookBatch([
|
|
100
|
-
{
|
|
101
|
-
key: 'optimistic',
|
|
102
|
-
options: {
|
|
103
|
-
taskQueue: 'agents',
|
|
104
|
-
workflowName: 'optimisticPerspective',
|
|
105
|
-
args: [query],
|
|
106
|
-
signalId: 'optimistic-complete'
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
key: 'skeptical',
|
|
111
|
-
options: {
|
|
112
|
-
taskQueue: 'agents',
|
|
113
|
-
workflowName: 'skepticalPerspective',
|
|
114
|
-
args: [query],
|
|
115
|
-
signalId: 'skeptical-complete'
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
]);
|
|
95
|
+
const result = await handle.result();
|
|
119
96
|
```
|
|
120
97
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
98
|
+
### Option 2: YAML (functional approach)
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
# order.yaml
|
|
102
|
+
activities:
|
|
103
|
+
trigger:
|
|
104
|
+
type: trigger
|
|
105
|
+
|
|
106
|
+
checkInventory:
|
|
107
|
+
type: worker
|
|
108
|
+
topic: inventory.check
|
|
109
|
+
|
|
110
|
+
reserveItem:
|
|
111
|
+
type: worker
|
|
112
|
+
topic: inventory.reserve
|
|
113
|
+
|
|
114
|
+
notifyBackorder:
|
|
115
|
+
type: worker
|
|
116
|
+
topic: inventory.backorder.notify
|
|
117
|
+
|
|
118
|
+
transitions:
|
|
119
|
+
trigger:
|
|
120
|
+
- to: checkInventory
|
|
121
|
+
|
|
122
|
+
checkInventory:
|
|
123
|
+
- to: reserveItem
|
|
124
|
+
conditions:
|
|
125
|
+
match:
|
|
126
|
+
- expected: true
|
|
127
|
+
actual:
|
|
128
|
+
'@pipe':
|
|
129
|
+
- ['{checkInventory.output.data.availableQty}', '{trigger.output.data.requestedQty}']
|
|
130
|
+
- ['{@conditional.gte}']
|
|
131
|
+
|
|
132
|
+
- to: notifyBackorder
|
|
133
|
+
conditions:
|
|
134
|
+
match:
|
|
135
|
+
- expected: false
|
|
136
|
+
actual:
|
|
137
|
+
'@pipe':
|
|
138
|
+
- ['{checkInventory.output.data.availableQty}', '{trigger.output.data.requestedQty}']
|
|
139
|
+
- ['{@conditional.gte}']
|
|
140
|
+
```
|
|
126
141
|
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
await e.set({ caseId, stage: "intake", approved: false });
|
|
142
|
+
```typescript
|
|
143
|
+
// main.ts (reuses same activities.ts)
|
|
144
|
+
import * as activities from './activities';
|
|
131
145
|
|
|
132
|
-
|
|
133
|
-
|
|
146
|
+
const hotMesh = await HotMesh.init({
|
|
147
|
+
appId: 'orders',
|
|
148
|
+
engine: { connection },
|
|
149
|
+
workers: [
|
|
150
|
+
{
|
|
151
|
+
topic: 'inventory.check',
|
|
152
|
+
connection,
|
|
153
|
+
callback: async (data) => {
|
|
154
|
+
const availableQty = await activities.checkInventory(data.data.itemId);
|
|
155
|
+
return { metadata: { ...data.metadata }, data: { availableQty } };
|
|
156
|
+
}
|
|
157
|
+
},
|
|
134
158
|
{
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
signalId: 'verify-complete'
|
|
159
|
+
topic: 'inventory.reserve',
|
|
160
|
+
connection,
|
|
161
|
+
callback: async (data) => {
|
|
162
|
+
const reservationId = await activities.reserveItem(data.data.itemId, data.data.quantity);
|
|
163
|
+
return { metadata: { ...data.metadata }, data: { reservationId } };
|
|
141
164
|
}
|
|
142
165
|
},
|
|
143
166
|
{
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
signalId: 'summary-complete'
|
|
167
|
+
topic: 'inventory.backorder.notify',
|
|
168
|
+
connection,
|
|
169
|
+
callback: async (data) => {
|
|
170
|
+
await activities.notifyBackorder(data.data.itemId);
|
|
171
|
+
return { metadata: { ...data.metadata } };
|
|
150
172
|
}
|
|
151
173
|
}
|
|
152
|
-
]
|
|
174
|
+
]
|
|
175
|
+
});
|
|
153
176
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
await e.merge({ approved: approval === true, stage: "complete" });
|
|
177
|
+
await hotMesh.deploy('./order.yaml');
|
|
178
|
+
await hotMesh.activate('1');
|
|
157
179
|
|
|
158
|
-
|
|
159
|
-
|
|
180
|
+
const result = await hotMesh.pubsub('order.requested', {
|
|
181
|
+
itemId: 'item-123',
|
|
182
|
+
requestedQty: 5
|
|
183
|
+
});
|
|
160
184
|
```
|
|
161
185
|
|
|
162
|
-
|
|
186
|
+
Both compile to the same distributed execution model.
|
|
163
187
|
|
|
164
|
-
|
|
165
|
-
* LLM agents for data validation and summarization
|
|
166
|
-
* a human reviewer for final sign-off
|
|
188
|
+
## Core features
|
|
167
189
|
|
|
168
|
-
|
|
190
|
+
- **Durable execution** - Survives crashes, retries automatically
|
|
191
|
+
- **No infrastructure** - Runs on your existing Postgres
|
|
192
|
+
- **Temporal compatible** - Drop-in replacement for many use cases
|
|
193
|
+
- **Distributed** - Every client participates in execution
|
|
194
|
+
- **Observable** - Full execution history in your database
|
|
169
195
|
|
|
170
|
-
|
|
196
|
+
## Common patterns
|
|
171
197
|
|
|
172
|
-
|
|
198
|
+
**Long-running workflows**
|
|
173
199
|
|
|
174
|
-
|
|
200
|
+
```typescript
|
|
201
|
+
await sleep('30 days');
|
|
202
|
+
await sendFollowUp();
|
|
203
|
+
```
|
|
175
204
|
|
|
176
|
-
|
|
177
|
-
| ----------------------------- | ---------------------------------------- |
|
|
178
|
-
| Tie AI into legacy apps | Durable SQL bridge with full visibility |
|
|
179
|
-
| Keep human review steps | Wait-for-signal workflows |
|
|
180
|
-
| Handle unstable APIs | Built-in retries and exponential backoff |
|
|
181
|
-
| Trace process across systems | Unified JSON entity per workflow |
|
|
182
|
-
| Store long-running AI results | Durable state for agents and automations |
|
|
205
|
+
**Parallel execution**
|
|
183
206
|
|
|
184
|
-
|
|
207
|
+
```typescript
|
|
208
|
+
const results = await Promise.all([
|
|
209
|
+
processPayment(),
|
|
210
|
+
updateInventory(),
|
|
211
|
+
notifyWarehouse()
|
|
212
|
+
]);
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Child workflows**
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
const childHandle = await startChild(validateOrder, { args: [orderId] });
|
|
219
|
+
const validation = await childHandle.result();
|
|
220
|
+
```
|
|
185
221
|
|
|
186
222
|
## License
|
|
187
223
|
|
|
188
|
-
|
|
189
|
-
|
|
224
|
+
HotMesh is licensed under the Apache License, Version 2.0.
|
|
225
|
+
|
|
226
|
+
You may use, modify, and distribute HotMesh in accordance with the license, including as part of your own applications and services. However, offering HotMesh itself as a standalone, hosted commercial orchestration service (or a substantially similar service) requires prior written permission from the author.
|
package/build/modules/enums.d.ts
CHANGED
|
@@ -106,6 +106,8 @@ export declare const HMSH_XPENDING_COUNT: number;
|
|
|
106
106
|
export declare const HMSH_EXPIRE_DURATION: number;
|
|
107
107
|
export declare const HMSH_FIDELITY_SECONDS: number;
|
|
108
108
|
export declare const HMSH_SCOUT_INTERVAL_SECONDS: number;
|
|
109
|
+
export declare const HMSH_ROUTER_SCOUT_INTERVAL_SECONDS: number;
|
|
110
|
+
export declare const HMSH_ROUTER_SCOUT_INTERVAL_MS: number;
|
|
109
111
|
export declare const HMSH_GUID_SIZE: number;
|
|
110
112
|
/**
|
|
111
113
|
* Default task queue name used when no task queue is specified
|
|
@@ -117,6 +119,11 @@ export declare const DEFAULT_TASK_QUEUE = "default";
|
|
|
117
119
|
* PostgreSQL hard limit is 8000 bytes; default 7500 provides safety margin.
|
|
118
120
|
*/
|
|
119
121
|
export declare const HMSH_NOTIFY_PAYLOAD_LIMIT: number;
|
|
122
|
+
/**
|
|
123
|
+
* PostgreSQL LISTEN/NOTIFY fallback polling interval in milliseconds.
|
|
124
|
+
* Used when LISTEN/NOTIFY is unavailable or fails. Default 30 seconds.
|
|
125
|
+
*/
|
|
126
|
+
export declare const HMSH_ROUTER_POLL_FALLBACK_INTERVAL: number;
|
|
120
127
|
/**
|
|
121
128
|
* Serializer compression threshold. When a stringified object exceeds this size
|
|
122
129
|
* in bytes, it will be gzipped and base64 encoded (with /b prefix) to reduce
|
package/build/modules/enums.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.HMSH_NOTIFY_PAYLOAD_LIMIT = exports.DEFAULT_TASK_QUEUE = exports.HMSH_GUID_SIZE = exports.HMSH_ROUTER_SCOUT_INTERVAL_MS = exports.HMSH_ROUTER_SCOUT_INTERVAL_SECONDS = exports.HMSH_SCOUT_INTERVAL_SECONDS = exports.HMSH_FIDELITY_SECONDS = exports.HMSH_EXPIRE_DURATION = exports.HMSH_XPENDING_COUNT = exports.HMSH_XCLAIM_COUNT = exports.HMSH_XCLAIM_DELAY_MS = exports.HMSH_BLOCK_TIME_MS = exports.HMSH_MEMFLOW_EXP_BACKOFF = exports.HMSH_MEMFLOW_MAX_INTERVAL = exports.HMSH_MEMFLOW_MAX_ATTEMPTS = exports.HMSH_GRADUATED_INTERVAL_MS = exports.HMSH_MAX_TIMEOUT_MS = exports.HMSH_MAX_RETRIES = exports.MAX_DELAY = exports.MAX_STREAM_RETRIES = exports.INITIAL_STREAM_BACKOFF = exports.MAX_STREAM_BACKOFF = exports.HMSH_EXPIRE_JOB_SECONDS = exports.HMSH_OTT_WAIT_TIME = exports.HMSH_DEPLOYMENT_PAUSE = exports.HMSH_DEPLOYMENT_DELAY = exports.HMSH_ACTIVATION_MAX_RETRY = exports.HMSH_QUORUM_DELAY_MS = exports.HMSH_QUORUM_ROLLCALL_CYCLES = exports.HMSH_STATUS_UNKNOWN = exports.HMSH_CODE_MEMFLOW_RETRYABLE = exports.HMSH_CODE_MEMFLOW_FATAL = exports.HMSH_CODE_MEMFLOW_MAXED = exports.HMSH_CODE_MEMFLOW_TIMEOUT = exports.HMSH_CODE_MEMFLOW_WAIT = exports.HMSH_CODE_MEMFLOW_PROXY = exports.HMSH_CODE_MEMFLOW_CHILD = exports.HMSH_CODE_MEMFLOW_ALL = exports.HMSH_CODE_MEMFLOW_SLEEP = exports.HMSH_CODE_UNACKED = exports.HMSH_CODE_TIMEOUT = exports.HMSH_CODE_UNKNOWN = exports.HMSH_CODE_INTERRUPT = exports.HMSH_CODE_NOTFOUND = exports.HMSH_CODE_PENDING = exports.HMSH_CODE_SUCCESS = exports.HMSH_IS_CLUSTER = exports.HMSH_SIGNAL_EXPIRE = exports.HMSH_TELEMETRY = exports.HMSH_LOGLEVEL = void 0;
|
|
4
|
+
exports.HMSH_SERIALIZER_COMPRESSION_THRESHOLD = exports.HMSH_ROUTER_POLL_FALLBACK_INTERVAL = void 0;
|
|
4
5
|
/**
|
|
5
6
|
* Determines the log level for the application. The default is 'info'.
|
|
6
7
|
*/
|
|
@@ -129,6 +130,15 @@ exports.HMSH_FIDELITY_SECONDS = process.env.HMSH_FIDELITY_SECONDS
|
|
|
129
130
|
? TEST_FIDELITY_SECONDS
|
|
130
131
|
: BASE_FIDELITY_SECONDS;
|
|
131
132
|
exports.HMSH_SCOUT_INTERVAL_SECONDS = parseInt(process.env.HMSH_SCOUT_INTERVAL_SECONDS, 10) || 60;
|
|
133
|
+
// ROUTER SCOUT - polls for visible messages when LISTEN/NOTIFY is insufficient
|
|
134
|
+
exports.HMSH_ROUTER_SCOUT_INTERVAL_SECONDS = parseInt(process.env.HMSH_ROUTER_SCOUT_INTERVAL_SECONDS, 10) || 60;
|
|
135
|
+
const BASE_ROUTER_SCOUT_INTERVAL_MS = 7000;
|
|
136
|
+
const TEST_ROUTER_SCOUT_INTERVAL_MS = 7000;
|
|
137
|
+
exports.HMSH_ROUTER_SCOUT_INTERVAL_MS = process.env.HMSH_ROUTER_SCOUT_INTERVAL_MS
|
|
138
|
+
? parseInt(process.env.HMSH_ROUTER_SCOUT_INTERVAL_MS, 10)
|
|
139
|
+
: process.env.NODE_ENV === 'test'
|
|
140
|
+
? TEST_ROUTER_SCOUT_INTERVAL_MS
|
|
141
|
+
: BASE_ROUTER_SCOUT_INTERVAL_MS;
|
|
132
142
|
// UTILS
|
|
133
143
|
exports.HMSH_GUID_SIZE = Math.min(parseInt(process.env.HMSH_GUID_SIZE, 10) || 22, 32);
|
|
134
144
|
/**
|
|
@@ -141,6 +151,11 @@ exports.DEFAULT_TASK_QUEUE = 'default';
|
|
|
141
151
|
* PostgreSQL hard limit is 8000 bytes; default 7500 provides safety margin.
|
|
142
152
|
*/
|
|
143
153
|
exports.HMSH_NOTIFY_PAYLOAD_LIMIT = parseInt(process.env.HMSH_NOTIFY_PAYLOAD_LIMIT, 10) || 7500;
|
|
154
|
+
/**
|
|
155
|
+
* PostgreSQL LISTEN/NOTIFY fallback polling interval in milliseconds.
|
|
156
|
+
* Used when LISTEN/NOTIFY is unavailable or fails. Default 30 seconds.
|
|
157
|
+
*/
|
|
158
|
+
exports.HMSH_ROUTER_POLL_FALLBACK_INTERVAL = parseInt(process.env.HOTMESH_POSTGRES_FALLBACK_INTERVAL, 10) || 30000;
|
|
144
159
|
/**
|
|
145
160
|
* Serializer compression threshold. When a stringified object exceeds this size
|
|
146
161
|
* in bytes, it will be gzipped and base64 encoded (with /b prefix) to reduce
|
package/build/modules/utils.d.ts
CHANGED
|
@@ -97,6 +97,33 @@ export declare function isValidCron(cronExpression: string): boolean;
|
|
|
97
97
|
* used by the `ms` npm package as the input.
|
|
98
98
|
*/
|
|
99
99
|
export declare const s: (input: string) => number;
|
|
100
|
+
/**
|
|
101
|
+
* Normalizes retry policy configuration to a consistent format.
|
|
102
|
+
* Converts maximumInterval to seconds and applies defaults.
|
|
103
|
+
*
|
|
104
|
+
* @param policy - Retry policy to normalize
|
|
105
|
+
* @param defaults - Default values to use if not specified
|
|
106
|
+
* @returns Normalized retry policy with numeric values
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```typescript
|
|
110
|
+
* const normalized = normalizeRetryPolicy({
|
|
111
|
+
* maximumAttempts: 5,
|
|
112
|
+
* backoffCoefficient: 2,
|
|
113
|
+
* maximumInterval: '300s',
|
|
114
|
+
* });
|
|
115
|
+
* // Returns: { max_retry_attempts: 5, backoff_coefficient: 2, maximum_interval_seconds: 300 }
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
export declare function normalizeRetryPolicy(policy?: import('../types/stream').RetryPolicy, defaults?: {
|
|
119
|
+
maximumAttempts: number;
|
|
120
|
+
backoffCoefficient: number;
|
|
121
|
+
maximumInterval: number;
|
|
122
|
+
}): {
|
|
123
|
+
max_retry_attempts: number;
|
|
124
|
+
backoff_coefficient: number;
|
|
125
|
+
maximum_interval_seconds: number;
|
|
126
|
+
};
|
|
100
127
|
/**
|
|
101
128
|
* @private
|
|
102
129
|
*/
|
package/build/modules/utils.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.arrayToHash = exports.isStreamMessage = exports.parseStreamMessage = exports.s = exports.isValidCron = exports.restoreHierarchy = exports.getValueByPath = exports.getIndexedHash = exports.getSymVal = exports.getSymKey = exports.formatISODate = exports.getTimeSeries = exports.getSubscriptionTopic = exports.findSubscriptionForTrigger = exports.findTopKey = exports.matchesStatus = exports.matchesStatusCode = exports.polyfill = exports.identifyProvider = exports.XSleepFor = exports.sleepImmediate = exports.sleepFor = exports.guid = exports.deterministicRandom = exports.deepCopy = exports.getSystemHealth = exports.hashOptions = void 0;
|
|
6
|
+
exports.arrayToHash = exports.isStreamMessage = exports.parseStreamMessage = exports.normalizeRetryPolicy = exports.s = exports.isValidCron = exports.restoreHierarchy = exports.getValueByPath = exports.getIndexedHash = exports.getSymVal = exports.getSymKey = exports.formatISODate = exports.getTimeSeries = exports.getSubscriptionTopic = exports.findSubscriptionForTrigger = exports.findTopKey = exports.matchesStatus = exports.matchesStatusCode = exports.polyfill = exports.identifyProvider = exports.XSleepFor = exports.sleepImmediate = exports.sleepFor = exports.guid = exports.deterministicRandom = exports.deepCopy = exports.getSystemHealth = exports.hashOptions = void 0;
|
|
7
7
|
const os_1 = __importDefault(require("os"));
|
|
8
8
|
const crypto_1 = require("crypto");
|
|
9
9
|
const nanoid_1 = require("nanoid");
|
|
@@ -316,6 +316,57 @@ const s = (input) => {
|
|
|
316
316
|
return (0, ms_1.default)(input) / 1000;
|
|
317
317
|
};
|
|
318
318
|
exports.s = s;
|
|
319
|
+
/**
|
|
320
|
+
* Normalizes retry policy configuration to a consistent format.
|
|
321
|
+
* Converts maximumInterval to seconds and applies defaults.
|
|
322
|
+
*
|
|
323
|
+
* @param policy - Retry policy to normalize
|
|
324
|
+
* @param defaults - Default values to use if not specified
|
|
325
|
+
* @returns Normalized retry policy with numeric values
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* ```typescript
|
|
329
|
+
* const normalized = normalizeRetryPolicy({
|
|
330
|
+
* maximumAttempts: 5,
|
|
331
|
+
* backoffCoefficient: 2,
|
|
332
|
+
* maximumInterval: '300s',
|
|
333
|
+
* });
|
|
334
|
+
* // Returns: { max_retry_attempts: 5, backoff_coefficient: 2, maximum_interval_seconds: 300 }
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
337
|
+
function normalizeRetryPolicy(policy, defaults = {
|
|
338
|
+
maximumAttempts: 3,
|
|
339
|
+
backoffCoefficient: 10,
|
|
340
|
+
maximumInterval: 120,
|
|
341
|
+
}) {
|
|
342
|
+
if (!policy) {
|
|
343
|
+
return {
|
|
344
|
+
max_retry_attempts: defaults.maximumAttempts,
|
|
345
|
+
backoff_coefficient: defaults.backoffCoefficient,
|
|
346
|
+
maximum_interval_seconds: defaults.maximumInterval,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
const maxAttempts = policy.maximumAttempts ?? defaults.maximumAttempts;
|
|
350
|
+
const backoffCoeff = policy.backoffCoefficient ?? defaults.backoffCoefficient;
|
|
351
|
+
let maxIntervalSeconds;
|
|
352
|
+
if (policy.maximumInterval !== undefined) {
|
|
353
|
+
if (typeof policy.maximumInterval === 'string') {
|
|
354
|
+
maxIntervalSeconds = (0, exports.s)(policy.maximumInterval);
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
maxIntervalSeconds = policy.maximumInterval;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
maxIntervalSeconds = defaults.maximumInterval;
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
max_retry_attempts: maxAttempts,
|
|
365
|
+
backoff_coefficient: backoffCoeff,
|
|
366
|
+
maximum_interval_seconds: maxIntervalSeconds,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
exports.normalizeRetryPolicy = normalizeRetryPolicy;
|
|
319
370
|
/**
|
|
320
371
|
* @private
|
|
321
372
|
*/
|
package/build/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotmeshio/hotmesh",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Permanent-Memory Workflows & AI Agents",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"test:connect:nats": "NODE_ENV=test jest ./tests/unit/services/connector/providers/nats.test.ts --detectOpenHandles --forceExit --verbose",
|
|
30
30
|
"test:memflow": "NODE_ENV=test jest ./tests/memflow/*/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
31
31
|
"test:memflow:basic": "HMSH_LOGLEVEL=info NODE_ENV=test jest ./tests/memflow/basic/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
32
|
-
"test:memflow:collision": "NODE_ENV=test jest ./tests/memflow/collision
|
|
32
|
+
"test:memflow:collision": "NODE_ENV=test jest ./tests/memflow/collision/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
33
33
|
"test:memflow:fatal": "NODE_ENV=test jest ./tests/memflow/fatal/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
34
34
|
"test:memflow:goodbye": "NODE_ENV=test HMSH_LOGLEVEL=debug jest ./tests/memflow/goodbye/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
35
35
|
"test:memflow:interceptor": "NODE_ENV=test HMSH_LOGLEVEL=debug jest ./tests/memflow/interceptor/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
@@ -37,30 +37,32 @@
|
|
|
37
37
|
"test:memflow:agent": "NODE_ENV=test HMSH_LOGLEVEL=debug jest ./tests/memflow/agent/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
38
38
|
"test:memflow:hello": "HMSH_TELEMETRY=debug HMSH_LOGLEVEL=debug NODE_ENV=test jest ./tests/memflow/helloworld/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
39
39
|
"test:memflow:hook": "NODE_ENV=test jest ./tests/memflow/hook/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
40
|
-
"test:memflow:interrupt": "NODE_ENV=test jest ./tests/memflow/interrupt
|
|
41
|
-
"test:memflow:loopactivity": "NODE_ENV=test jest ./tests/memflow/loopactivity
|
|
40
|
+
"test:memflow:interrupt": "NODE_ENV=test jest ./tests/memflow/interrupt/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
41
|
+
"test:memflow:loopactivity": "NODE_ENV=test jest ./tests/memflow/loopactivity/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
42
42
|
"test:memflow:nested": "NODE_ENV=test jest ./tests/memflow/nested/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
43
43
|
"test:memflow:pipeline": "NODE_ENV=test jest ./tests/memflow/pipeline/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
44
44
|
"test:memflow:retry": "NODE_ENV=test jest ./tests/memflow/retry/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
45
|
+
"test:memflow:retrypolicy": "NODE_ENV=test jest ./tests/memflow/retry-policy/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
45
46
|
"test:memflow:sleep": "NODE_ENV=test jest ./tests/memflow/sleep/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
46
47
|
"test:memflow:signal": "NODE_ENV=test jest ./tests/memflow/signal/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
47
|
-
"test:memflow:unknown": "NODE_ENV=test jest ./tests/memflow/unknown
|
|
48
|
+
"test:memflow:unknown": "NODE_ENV=test jest ./tests/memflow/unknown/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
48
49
|
"test:cycle": "NODE_ENV=test jest ./tests/functional/cycle/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
49
50
|
"test:functional": "NODE_ENV=test jest ./tests/functional/* --detectOpenHandles --forceExit --verbose",
|
|
50
51
|
"test:emit": "NODE_ENV=test jest ./tests/functional/emit/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
51
52
|
"test:pending": "NODE_ENV=test jest ./tests/functional/pending/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
52
53
|
"test:hmsh": "NODE_ENV=test jest ./tests/functional/*.test.ts --detectOpenHandles --verbose --forceExit",
|
|
53
|
-
"test:hook": "NODE_ENV=test jest ./tests/functional/hook/
|
|
54
|
+
"test:hook": "NODE_ENV=test jest ./tests/functional/hook/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
54
55
|
"test:interrupt": "NODE_ENV=test jest ./tests/functional/interrupt/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
55
56
|
"test:parallel": "NODE_ENV=test jest ./tests/functional/parallel/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
56
57
|
"test:pipe": "NODE_ENV=test jest ./tests/unit/services/pipe/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
57
|
-
"test:quorum": "NODE_ENV=test jest ./tests/functional/quorum
|
|
58
|
+
"test:quorum": "NODE_ENV=test jest ./tests/functional/quorum/postgres.test.ts --detectOpenHandles --forceExit --verbose",
|
|
58
59
|
"test:reclaim": "NODE_ENV=test jest ./tests/functional/reclaim/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
59
60
|
"test:redeploy": "NODE_ENV=test jest ./tests/functional/redeploy/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
60
61
|
"test:reporter": "NODE_ENV=test jest ./tests/unit/services/reporter/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
61
62
|
"test:reentrant": "NODE_ENV=test jest ./tests/functional/reentrant/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
62
63
|
"test:retry": "NODE_ENV=test jest ./tests/functional/retry/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
63
|
-
"test:
|
|
64
|
+
"test:retrypolicy": "NODE_ENV=test jest ./tests/functional/retry-policy/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
65
|
+
"test:sequence": "NODE_ENV=test HMSH_LOGLEVEL=debug jest ./tests/functional/sequence/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
64
66
|
"test:signal": "NODE_ENV=test jest ./tests/functional/signal/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
65
67
|
"test:status": "NODE_ENV=test jest ./tests/functional/status/index.test.ts --detectOpenHandles --forceExit --verbose",
|
|
66
68
|
"test:providers": "NODE_ENV=test jest ./tests/functional/*/providers/*/*.test.ts --detectOpenHandles --forceExit --verbose",
|
|
@@ -121,7 +121,10 @@ class PostgresConnection extends __1.AbstractConnection {
|
|
|
121
121
|
this.disconnecting = true;
|
|
122
122
|
await this.disconnectPoolClients();
|
|
123
123
|
await this.disconnectConnections();
|
|
124
|
+
// Clear taskQueue connections cache when disconnecting all
|
|
124
125
|
this.taskQueueConnections.clear();
|
|
126
|
+
// Clear the base class instances cache to allow reconnection with same IDs
|
|
127
|
+
this.instances.clear();
|
|
125
128
|
this.disconnecting = false;
|
|
126
129
|
}
|
|
127
130
|
}
|