@hotmeshio/hotmesh 0.8.0 → 0.9.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/.claude/settings.local.json +2 -1
- package/README.md +158 -38
- package/build/package.json +62 -67
- package/build/services/activities/activity.d.ts +58 -7
- package/build/services/activities/activity.js +66 -37
- package/build/services/activities/await.d.ts +101 -0
- package/build/services/activities/await.js +101 -0
- package/build/services/activities/cycle.d.ts +82 -0
- package/build/services/activities/cycle.js +86 -8
- package/build/services/activities/hook.d.ts +139 -1
- package/build/services/activities/hook.js +140 -2
- package/build/services/activities/interrupt.d.ts +112 -0
- package/build/services/activities/interrupt.js +118 -5
- package/build/services/activities/signal.d.ts +108 -3
- package/build/services/activities/signal.js +113 -8
- package/build/services/activities/trigger.d.ts +56 -4
- package/build/services/activities/trigger.js +119 -35
- package/build/services/activities/worker.d.ts +107 -0
- package/build/services/activities/worker.js +107 -0
- package/build/services/collator/index.d.ts +3 -15
- package/build/services/collator/index.js +7 -34
- package/build/services/engine/index.d.ts +18 -2
- package/build/services/engine/index.js +14 -4
- package/build/services/exporter/index.d.ts +2 -0
- package/build/services/exporter/index.js +1 -0
- package/build/services/hotmesh/index.d.ts +471 -236
- package/build/services/hotmesh/index.js +473 -238
- package/build/services/memflow/client.js +2 -2
- package/build/services/memflow/handle.js +1 -1
- package/build/services/memflow/index.d.ts +1 -1
- package/build/services/memflow/index.js +1 -1
- package/build/services/memflow/workflow/all.d.ts +28 -3
- package/build/services/memflow/workflow/all.js +28 -3
- package/build/services/memflow/workflow/context.d.ts +44 -1
- package/build/services/memflow/workflow/context.js +44 -1
- package/build/services/memflow/workflow/didRun.d.ts +23 -3
- package/build/services/memflow/workflow/didRun.js +23 -3
- package/build/services/memflow/workflow/emit.d.ts +43 -4
- package/build/services/memflow/workflow/emit.js +43 -4
- package/build/services/memflow/workflow/enrich.d.ts +32 -4
- package/build/services/memflow/workflow/enrich.js +32 -4
- package/build/services/memflow/workflow/entityMethods.d.ts +54 -7
- package/build/services/memflow/workflow/entityMethods.js +54 -7
- package/build/services/memflow/workflow/execChild.d.ts +96 -8
- package/build/services/memflow/workflow/execChild.js +96 -8
- package/build/services/memflow/workflow/execHook.d.ts +54 -39
- package/build/services/memflow/workflow/execHook.js +52 -38
- package/build/services/memflow/workflow/execHookBatch.d.ts +82 -29
- package/build/services/memflow/workflow/execHookBatch.js +80 -28
- package/build/services/memflow/workflow/hook.d.ts +68 -3
- package/build/services/memflow/workflow/hook.js +69 -4
- package/build/services/memflow/workflow/index.d.ts +65 -10
- package/build/services/memflow/workflow/index.js +65 -10
- package/build/services/memflow/workflow/interrupt.d.ts +50 -4
- package/build/services/memflow/workflow/interrupt.js +50 -4
- package/build/services/memflow/workflow/interruption.d.ts +49 -16
- package/build/services/memflow/workflow/interruption.js +49 -16
- package/build/services/memflow/workflow/isSideEffectAllowed.d.ts +21 -4
- package/build/services/memflow/workflow/isSideEffectAllowed.js +21 -4
- package/build/services/memflow/workflow/proxyActivities.d.ts +70 -42
- package/build/services/memflow/workflow/proxyActivities.js +70 -42
- package/build/services/memflow/workflow/random.d.ts +33 -3
- package/build/services/memflow/workflow/random.js +33 -3
- package/build/services/memflow/workflow/searchMethods.d.ts +49 -2
- package/build/services/memflow/workflow/searchMethods.js +49 -2
- package/build/services/memflow/workflow/signal.d.ts +51 -22
- package/build/services/memflow/workflow/signal.js +52 -23
- package/build/services/memflow/workflow/sleepFor.d.ts +57 -18
- package/build/services/memflow/workflow/sleepFor.js +57 -18
- package/build/services/memflow/workflow/trace.d.ts +39 -6
- package/build/services/memflow/workflow/trace.js +39 -6
- package/build/services/memflow/workflow/waitFor.d.ts +55 -18
- package/build/services/memflow/workflow/waitFor.js +55 -18
- package/build/services/store/index.d.ts +1 -1
- package/build/services/store/providers/postgres/postgres.d.ts +1 -1
- package/build/services/store/providers/postgres/postgres.js +4 -3
- package/build/services/telemetry/index.js +6 -0
- package/build/types/activity.d.ts +1 -1
- package/build/types/hotmesh.d.ts +1 -1
- package/build/types/job.d.ts +1 -1
- package/build/types/memflow.d.ts +1 -1
- package/build/types/quorum.d.ts +2 -2
- package/build/vitest.config.d.ts +2 -0
- package/build/vitest.config.js +18 -0
- package/package.json +62 -67
- package/vitest.config.ts +17 -0
|
@@ -11,178 +11,160 @@ const router_1 = require("../router");
|
|
|
11
11
|
const worker_1 = require("../worker");
|
|
12
12
|
const enums_1 = require("../../modules/enums");
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* A distributed service mesh that turns Postgres into a durable workflow
|
|
15
|
+
* orchestration engine. Every `HotMesh.init()` call creates a **point of
|
|
16
|
+
* presence** — an engine, a quorum member, and zero or more workers — that
|
|
17
|
+
* collaborates with its peers through Postgres LISTEN/NOTIFY to form a
|
|
18
|
+
* self-coordinating mesh with no external dependencies.
|
|
16
19
|
*
|
|
17
|
-
* ##
|
|
20
|
+
* ## Service Mesh Architecture
|
|
18
21
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* - **Pattern Matching**: Pub/sub with wildcard pattern support
|
|
25
|
-
* - **Throttling**: Dynamic flow control and backpressure management
|
|
26
|
-
* - **Retry Policies**: PostgreSQL-native retry configuration with exponential backoff
|
|
22
|
+
* Each HotMesh instance joins a **quorum** — a real-time pub/sub channel
|
|
23
|
+
* backed by Postgres LISTEN/NOTIFY. The quorum is the mesh's nervous
|
|
24
|
+
* system: version activations, throttle commands, roll calls, and custom
|
|
25
|
+
* user messages all propagate instantly to every connected engine and
|
|
26
|
+
* worker across all processes and servers.
|
|
27
27
|
*
|
|
28
|
-
* ##
|
|
28
|
+
* ## Quick Start
|
|
29
29
|
*
|
|
30
|
-
* HotMesh consists of several specialized modules:
|
|
31
|
-
* - **HotMesh**: Core orchestration engine (this class)
|
|
32
|
-
* - **MemFlow**: Temporal.io-compatible workflow framework
|
|
33
|
-
* - **MeshCall**: Durable function execution (Temporal-like clone)
|
|
34
|
-
*
|
|
35
|
-
* ## Lifecycle Overview
|
|
36
|
-
*
|
|
37
|
-
* 1. **Initialize**: Create HotMesh instance with provider configuration
|
|
38
|
-
* 2. **Deploy**: Upload YAML workflow definitions to the backend
|
|
39
|
-
* 3. **Activate**: Coordinate quorum to enable the workflow version
|
|
40
|
-
* 4. **Execute**: Publish events to trigger workflow execution
|
|
41
|
-
* 5. **Monitor**: Track progress via OpenTelemetry and built-in observability
|
|
42
|
-
*
|
|
43
|
-
* ## Basic Usage
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
30
|
* ```typescript
|
|
47
31
|
* import { HotMesh } from '@hotmeshio/hotmesh';
|
|
48
32
|
* import { Client as Postgres } from 'pg';
|
|
49
33
|
*
|
|
50
|
-
* // Initialize with Postgres backend
|
|
51
34
|
* const hotMesh = await HotMesh.init({
|
|
52
|
-
* appId: '
|
|
35
|
+
* appId: 'myapp',
|
|
53
36
|
* engine: {
|
|
54
37
|
* connection: {
|
|
55
38
|
* class: Postgres,
|
|
56
|
-
* options: {
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
39
|
+
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' },
|
|
40
|
+
* },
|
|
41
|
+
* },
|
|
42
|
+
* workers: [{
|
|
43
|
+
* topic: 'order.process',
|
|
44
|
+
* connection: {
|
|
45
|
+
* class: Postgres,
|
|
46
|
+
* options: { connectionString: 'postgresql://usr:pwd@localhost:5432/db' },
|
|
47
|
+
* },
|
|
48
|
+
* callback: async (data) => ({
|
|
49
|
+
* metadata: { ...data.metadata },
|
|
50
|
+
* data: { orderId: data.data.id, status: 'fulfilled' },
|
|
51
|
+
* }),
|
|
52
|
+
* }],
|
|
61
53
|
* });
|
|
62
54
|
*
|
|
63
|
-
* // Deploy workflow
|
|
55
|
+
* // Deploy a YAML workflow graph
|
|
64
56
|
* await hotMesh.deploy(`
|
|
65
57
|
* app:
|
|
66
|
-
* id:
|
|
58
|
+
* id: myapp
|
|
67
59
|
* version: '1'
|
|
68
60
|
* graphs:
|
|
69
|
-
* - subscribes: order.
|
|
61
|
+
* - subscribes: order.placed
|
|
62
|
+
* publishes: order.fulfilled
|
|
63
|
+
* expire: 600
|
|
64
|
+
*
|
|
70
65
|
* activities:
|
|
71
|
-
*
|
|
72
|
-
* type:
|
|
73
|
-
*
|
|
74
|
-
* approve:
|
|
75
|
-
* type: hook
|
|
76
|
-
* topic: order.approve
|
|
77
|
-
* fulfill:
|
|
66
|
+
* t1:
|
|
67
|
+
* type: trigger
|
|
68
|
+
* process:
|
|
78
69
|
* type: worker
|
|
79
|
-
* topic: order.
|
|
70
|
+
* topic: order.process
|
|
71
|
+
* input:
|
|
72
|
+
* schema:
|
|
73
|
+
* type: object
|
|
74
|
+
* properties:
|
|
75
|
+
* id: { type: string }
|
|
76
|
+
* maps:
|
|
77
|
+
* id: '{t1.output.data.id}'
|
|
80
78
|
* transitions:
|
|
81
|
-
*
|
|
82
|
-
* - to:
|
|
83
|
-
* approve:
|
|
84
|
-
* - to: fulfill
|
|
79
|
+
* t1:
|
|
80
|
+
* - to: process
|
|
85
81
|
* `);
|
|
86
82
|
*
|
|
87
|
-
* // Activate the workflow version
|
|
88
83
|
* await hotMesh.activate('1');
|
|
89
84
|
*
|
|
90
|
-
* //
|
|
91
|
-
* const jobId = await hotMesh.pub('order.
|
|
92
|
-
* orderId: '12345',
|
|
93
|
-
* amount: 99.99
|
|
94
|
-
* });
|
|
85
|
+
* // Fire-and-forget
|
|
86
|
+
* const jobId = await hotMesh.pub('order.placed', { id: 'ORD-123' });
|
|
95
87
|
*
|
|
96
|
-
* //
|
|
97
|
-
* const result = await hotMesh.pubsub('order.
|
|
98
|
-
* orderId: '12345',
|
|
99
|
-
* amount: 99.99
|
|
100
|
-
* });
|
|
88
|
+
* // Request/response (blocks until workflow completes)
|
|
89
|
+
* const result = await hotMesh.pubsub('order.placed', { id: 'ORD-456' });
|
|
101
90
|
* ```
|
|
102
91
|
*
|
|
103
|
-
* ##
|
|
92
|
+
* ## Quorum: The Mesh Control Plane
|
|
104
93
|
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* import { HotMesh } from '@hotmeshio/hotmesh';
|
|
108
|
-
* import { Client as Postgres } from 'pg';
|
|
94
|
+
* The quorum channel is a broadcast bus available to every mesh member.
|
|
95
|
+
* Use it for operational control, observability, and custom messaging.
|
|
109
96
|
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
97
|
+
* ```typescript
|
|
98
|
+
* // Roll call — discover every engine and worker in the mesh
|
|
99
|
+
* const members = await hotMesh.rollCall();
|
|
100
|
+
* // => [{ engine_id, worker_topic, throttle, system: { CPULoad, ... } }, ...]
|
|
101
|
+
*
|
|
102
|
+
* // Subscribe to ALL quorum traffic (throttle, activate, pong, job, user)
|
|
103
|
+
* await hotMesh.subQuorum((topic, message) => {
|
|
104
|
+
* switch (message.type) {
|
|
105
|
+
* case 'pong': // roll call response from a mesh member
|
|
106
|
+
* console.log(`Member ${message.guid} on topic ${message.profile?.worker_topic}`);
|
|
107
|
+
* break;
|
|
108
|
+
* case 'throttle': // a throttle command was broadcast
|
|
109
|
+
* console.log(`Throttle ${message.throttle}ms on ${message.topic ?? 'all'}`);
|
|
110
|
+
* break;
|
|
111
|
+
* case 'activate': // a version activation is in progress
|
|
112
|
+
* console.log(`Activating version ${message.until_version}`);
|
|
113
|
+
* break;
|
|
114
|
+
* case 'job': // a workflow completed and published its result
|
|
115
|
+
* console.log(`Job done on ${message.topic}:`, message.job);
|
|
116
|
+
* break;
|
|
117
|
+
* case 'user': // a custom user message
|
|
118
|
+
* console.log(`User event ${message.topic}:`, message.message);
|
|
119
|
+
* break;
|
|
119
120
|
* }
|
|
120
121
|
* });
|
|
121
|
-
* ```
|
|
122
122
|
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
* console.log(`Received ${topic}:`, message);
|
|
123
|
+
* // Publish a custom message to every mesh member
|
|
124
|
+
* await hotMesh.pubQuorum({
|
|
125
|
+
* type: 'user',
|
|
126
|
+
* topic: 'deploy.notify',
|
|
127
|
+
* message: { version: '2.1.0', deployer: 'ci-pipeline' },
|
|
129
128
|
* });
|
|
130
129
|
* ```
|
|
131
130
|
*
|
|
132
|
-
*
|
|
133
|
-
* ```typescript
|
|
134
|
-
* // Pause all processing for 5 seconds
|
|
135
|
-
* await hotMesh.throttle({ throttle: 5000 });
|
|
136
|
-
*
|
|
137
|
-
* // Emergency stop (pause indefinitely)
|
|
138
|
-
* await hotMesh.throttle({ throttle: -1 });
|
|
139
|
-
* ```
|
|
131
|
+
* ## Throttling: Backpressure Across the Mesh
|
|
140
132
|
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* await hotMesh.interrupt('order.process', jobId, {
|
|
144
|
-
* reason: 'User cancellation'
|
|
145
|
-
* });
|
|
146
|
-
* ```
|
|
133
|
+
* Throttle commands propagate instantly to every targeted member via
|
|
134
|
+
* the quorum channel, providing fine-grained flow control.
|
|
147
135
|
*
|
|
148
|
-
* **State Inspection**: Query workflow state and progress
|
|
149
136
|
* ```typescript
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* ```
|
|
137
|
+
* // Pause the ENTIRE mesh (emergency stop)
|
|
138
|
+
* await hotMesh.throttle({ throttle: -1 });
|
|
153
139
|
*
|
|
154
|
-
*
|
|
140
|
+
* // Resume the entire mesh
|
|
141
|
+
* await hotMesh.throttle({ throttle: 0 });
|
|
155
142
|
*
|
|
156
|
-
*
|
|
143
|
+
* // Slow a specific worker topic to 1 message per 500ms
|
|
144
|
+
* await hotMesh.throttle({ throttle: 500, topic: 'order.process' });
|
|
157
145
|
*
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
-
* const members = await hotMesh.rollCall();
|
|
146
|
+
* // Throttle a single engine/worker instance by GUID
|
|
147
|
+
* await hotMesh.throttle({ throttle: 2000, guid: 'abc-123' });
|
|
161
148
|
*
|
|
162
|
-
* //
|
|
163
|
-
* await hotMesh.
|
|
149
|
+
* // Combine: throttle a specific topic on a specific instance
|
|
150
|
+
* await hotMesh.throttle({ throttle: 1000, guid: 'abc-123', topic: 'order.process' });
|
|
164
151
|
* ```
|
|
165
152
|
*
|
|
166
|
-
* ##
|
|
153
|
+
* ## Lifecycle
|
|
167
154
|
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
155
|
+
* 1. **`init`** — Create an engine + workers; join the quorum.
|
|
156
|
+
* 2. **`deploy`** — Upload a YAML graph to Postgres (inactive).
|
|
157
|
+
* 3. **`activate`** — Coordinate the quorum to switch to the new version.
|
|
158
|
+
* 4. **`pub` / `pubsub`** — Trigger workflow execution.
|
|
159
|
+
* 5. **`stop`** — Leave the quorum and release connections.
|
|
171
160
|
*
|
|
172
|
-
* ##
|
|
161
|
+
* ## Higher-Level Modules
|
|
173
162
|
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* hotMesh.stop();
|
|
163
|
+
* For most use cases, prefer the higher-level wrappers:
|
|
164
|
+
* - **MemFlow** — Temporal-style durable workflow functions.
|
|
165
|
+
* - **MeshCall** — Durable function calls and RPC patterns.
|
|
178
166
|
*
|
|
179
|
-
*
|
|
180
|
-
* await HotMesh.stop();
|
|
181
|
-
* ```
|
|
182
|
-
*
|
|
183
|
-
* @see {@link https://docs.hotmesh.io/} - Complete documentation
|
|
184
|
-
* @see {@link https://github.com/hotmeshio/samples-typescript} - Examples and tutorials
|
|
185
|
-
* @see {@link https://zenodo.org/records/12168558} - White paper on the architecture
|
|
167
|
+
* @see {@link https://hotmeshio.github.io/sdk-typescript/} - API reference
|
|
186
168
|
*/
|
|
187
169
|
class HotMesh {
|
|
188
170
|
/**
|
|
@@ -220,12 +202,19 @@ class HotMesh {
|
|
|
220
202
|
*
|
|
221
203
|
* ## Retry Policy Configuration
|
|
222
204
|
*
|
|
223
|
-
* HotMesh supports
|
|
224
|
-
*
|
|
205
|
+
* HotMesh supports retry policies with exponential backoff. Retry behavior
|
|
206
|
+
* can be configured independently on both the `engine` and individual
|
|
207
|
+
* `workers`. They are **not inherited**; each operates at its own level.
|
|
208
|
+
*
|
|
209
|
+
* - **Engine `retryPolicy`**: Stamps messages the engine publishes with
|
|
210
|
+
* retry metadata (stored as Postgres columns). Workers that consume
|
|
211
|
+
* these messages will use the embedded config when handling failures.
|
|
212
|
+
* - **Worker `retryPolicy`**: Used as the fallback when the consumed
|
|
213
|
+
* message does not carry explicit retry metadata.
|
|
225
214
|
*
|
|
226
215
|
* @example Basic Configuration
|
|
227
216
|
* ```typescript
|
|
228
|
-
* const
|
|
217
|
+
* const hotMesh = await HotMesh.init({
|
|
229
218
|
* appId: 'myapp',
|
|
230
219
|
* engine: {
|
|
231
220
|
* connection: {
|
|
@@ -235,46 +224,49 @@ class HotMesh {
|
|
|
235
224
|
* }
|
|
236
225
|
* }
|
|
237
226
|
* },
|
|
238
|
-
* workers [...]
|
|
239
|
-
* };
|
|
240
|
-
* const hotMesh = await HotMesh.init(config);
|
|
227
|
+
* workers: [...]
|
|
228
|
+
* });
|
|
241
229
|
* ```
|
|
242
230
|
*
|
|
243
|
-
* @example
|
|
231
|
+
* @example Engine Retry Policy
|
|
244
232
|
* ```typescript
|
|
245
|
-
* import { HotMesh } from '@hotmeshio/hotmesh';
|
|
246
|
-
* import { Client as Postgres } from 'pg';
|
|
247
|
-
*
|
|
248
233
|
* const hotMesh = await HotMesh.init({
|
|
249
|
-
* appId: '
|
|
234
|
+
* appId: 'myapp',
|
|
250
235
|
* engine: {
|
|
251
236
|
* connection: {
|
|
252
237
|
* class: Postgres,
|
|
253
238
|
* options: { connectionString: 'postgresql://...' }
|
|
254
239
|
* },
|
|
255
|
-
* // Default retry policy for engine streams
|
|
256
240
|
* retryPolicy: {
|
|
257
|
-
* maximumAttempts: 5,
|
|
258
|
-
* backoffCoefficient: 2,
|
|
259
|
-
* maximumInterval: '300s'
|
|
241
|
+
* maximumAttempts: 5,
|
|
242
|
+
* backoffCoefficient: 2,
|
|
243
|
+
* maximumInterval: '300s'
|
|
260
244
|
* }
|
|
261
|
-
* }
|
|
245
|
+
* }
|
|
246
|
+
* });
|
|
247
|
+
* ```
|
|
248
|
+
*
|
|
249
|
+
* @example Worker Retry Policy
|
|
250
|
+
* ```typescript
|
|
251
|
+
* const hotMesh = await HotMesh.init({
|
|
252
|
+
* appId: 'myapp',
|
|
253
|
+
* engine: { connection },
|
|
262
254
|
* workers: [{
|
|
263
255
|
* topic: 'order.process',
|
|
264
|
-
* connection
|
|
265
|
-
* class: Postgres,
|
|
266
|
-
* options: { connectionString: 'postgresql://...' }
|
|
267
|
-
* },
|
|
268
|
-
* // Worker-specific retry policy
|
|
256
|
+
* connection,
|
|
269
257
|
* retryPolicy: {
|
|
270
|
-
* maximumAttempts:
|
|
271
|
-
* backoffCoefficient:
|
|
272
|
-
* maximumInterval: '
|
|
258
|
+
* maximumAttempts: 5,
|
|
259
|
+
* backoffCoefficient: 2,
|
|
260
|
+
* maximumInterval: '30s',
|
|
273
261
|
* },
|
|
274
|
-
* callback: async (data) => {
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
262
|
+
* callback: async (data: StreamData) => {
|
|
263
|
+
* const result = await doWork(data.data);
|
|
264
|
+
* return {
|
|
265
|
+
* code: 200,
|
|
266
|
+
* status: StreamStatus.SUCCESS,
|
|
267
|
+
* metadata: { ...data.metadata },
|
|
268
|
+
* data: { result },
|
|
269
|
+
* } as StreamDataResponse;
|
|
278
270
|
* }
|
|
279
271
|
* }]
|
|
280
272
|
* });
|
|
@@ -303,8 +295,8 @@ class HotMesh {
|
|
|
303
295
|
return instance;
|
|
304
296
|
}
|
|
305
297
|
/**
|
|
306
|
-
*
|
|
307
|
-
*
|
|
298
|
+
* Generates a unique ID using the same nanoid generator used
|
|
299
|
+
* internally by HotMesh for job IDs and GUIDs.
|
|
308
300
|
*/
|
|
309
301
|
static guid() {
|
|
310
302
|
return (0, utils_1.guid)();
|
|
@@ -408,24 +400,31 @@ class HotMesh {
|
|
|
408
400
|
}
|
|
409
401
|
// ************* PUB/SUB METHODS *************
|
|
410
402
|
/**
|
|
411
|
-
*
|
|
403
|
+
* Publishes a message to a workflow topic, starting a new job.
|
|
404
|
+
* Returns the job ID immediately (fire-and-forget). Use `pubsub`
|
|
405
|
+
* to block until the workflow completes.
|
|
406
|
+
*
|
|
412
407
|
* @example
|
|
413
408
|
* ```typescript
|
|
414
|
-
* await hotMesh.pub('
|
|
409
|
+
* const jobId = await hotMesh.pub('order.placed', {
|
|
410
|
+
* id: 'ORD-123',
|
|
411
|
+
* amount: 99.99,
|
|
412
|
+
* });
|
|
413
|
+
* console.log(`Started job ${jobId}`);
|
|
415
414
|
* ```
|
|
416
415
|
*/
|
|
417
416
|
async pub(topic, data = {}, context, extended) {
|
|
418
417
|
return await this.engine?.pub(topic, data, context, extended);
|
|
419
418
|
}
|
|
420
419
|
/**
|
|
421
|
-
*
|
|
422
|
-
* workflow topic.
|
|
423
|
-
*
|
|
420
|
+
* Subscribes to all output and interim emissions from a specific
|
|
421
|
+
* workflow topic. The callback fires each time a job on that topic
|
|
422
|
+
* completes or emits an interim result.
|
|
424
423
|
*
|
|
425
424
|
* @example
|
|
426
425
|
* ```typescript
|
|
427
|
-
* await hotMesh.
|
|
428
|
-
*
|
|
426
|
+
* await hotMesh.sub('order.fulfilled', (topic, message) => {
|
|
427
|
+
* console.log(`Order completed:`, message.data);
|
|
429
428
|
* });
|
|
430
429
|
* ```
|
|
431
430
|
*/
|
|
@@ -433,18 +432,21 @@ class HotMesh {
|
|
|
433
432
|
return await this.engine?.sub(topic, callback);
|
|
434
433
|
}
|
|
435
434
|
/**
|
|
436
|
-
*
|
|
435
|
+
* Unsubscribes from a single workflow topic previously registered
|
|
436
|
+
* with `sub()`.
|
|
437
437
|
*/
|
|
438
438
|
async unsub(topic) {
|
|
439
439
|
return await this.engine?.unsub(topic);
|
|
440
440
|
}
|
|
441
441
|
/**
|
|
442
|
-
*
|
|
443
|
-
*
|
|
442
|
+
* Subscribes to workflow emissions matching a wildcard pattern.
|
|
443
|
+
* Useful for monitoring an entire domain of workflows at once.
|
|
444
|
+
*
|
|
444
445
|
* @example
|
|
445
446
|
* ```typescript
|
|
446
|
-
*
|
|
447
|
-
*
|
|
447
|
+
* // Listen to all order-related workflow completions
|
|
448
|
+
* await hotMesh.psub('order.*', (topic, message) => {
|
|
449
|
+
* console.log(`${topic} completed:`, message.data);
|
|
448
450
|
* });
|
|
449
451
|
* ```
|
|
450
452
|
*/
|
|
@@ -452,56 +454,107 @@ class HotMesh {
|
|
|
452
454
|
return await this.engine?.psub(wild, callback);
|
|
453
455
|
}
|
|
454
456
|
/**
|
|
455
|
-
*
|
|
456
|
-
* unsubscription, so this method is not supported for Postgres.
|
|
457
|
+
* Unsubscribes from a wildcard pattern previously registered with `psub()`.
|
|
457
458
|
*/
|
|
458
459
|
async punsub(wild) {
|
|
459
460
|
return await this.engine?.punsub(wild);
|
|
460
461
|
}
|
|
461
462
|
/**
|
|
462
|
-
*
|
|
463
|
+
* Publishes a message to a workflow topic and blocks until the workflow
|
|
464
|
+
* completes, returning the final job output. Internally subscribes to
|
|
465
|
+
* the workflow's `publishes` topic before publishing, then unsubscribes
|
|
466
|
+
* after receiving the result.
|
|
467
|
+
*
|
|
463
468
|
* @example
|
|
464
469
|
* ```typescript
|
|
465
|
-
* await hotMesh.pubsub('
|
|
470
|
+
* const result = await hotMesh.pubsub('order.placed', {
|
|
471
|
+
* id: 'ORD-789',
|
|
472
|
+
* amount: 49.99,
|
|
473
|
+
* });
|
|
474
|
+
* console.log('Order result:', result.data);
|
|
466
475
|
* ```
|
|
467
476
|
*/
|
|
468
477
|
async pubsub(topic, data = {}, context, timeout) {
|
|
469
478
|
return await this.engine?.pubsub(topic, data, context, timeout);
|
|
470
479
|
}
|
|
471
480
|
/**
|
|
472
|
-
*
|
|
473
|
-
* reentrant activity (e.g., await
|
|
481
|
+
* Adds a transition message to the workstream, resuming Leg 2 of a
|
|
482
|
+
* paused reentrant activity (e.g., `await`, `worker`, `hook`). This
|
|
483
|
+
* is typically called by the engine internally but is exposed for
|
|
484
|
+
* advanced use cases like custom activity implementations.
|
|
474
485
|
*/
|
|
475
486
|
async add(streamData) {
|
|
476
487
|
return (await this.engine.add(streamData));
|
|
477
488
|
}
|
|
478
489
|
// ************* QUORUM METHODS *************
|
|
479
490
|
/**
|
|
480
|
-
*
|
|
491
|
+
* Broadcasts a roll call across the mesh and collects responses from
|
|
492
|
+
* every connected engine and worker. Each member replies with its
|
|
493
|
+
* `QuorumProfile` — including GUID, worker topic, stream depth,
|
|
494
|
+
* throttle rate, and system health (CPU, memory, network).
|
|
495
|
+
*
|
|
496
|
+
* Use this for service discovery, health checks, and capacity planning.
|
|
497
|
+
*
|
|
498
|
+
* @example
|
|
499
|
+
* ```typescript
|
|
500
|
+
* const members = await hotMesh.rollCall();
|
|
501
|
+
* for (const member of members) {
|
|
502
|
+
* console.log(
|
|
503
|
+
* `${member.engine_id} | topic=${member.worker_topic ?? 'engine'} ` +
|
|
504
|
+
* `| throttle=${member.throttle}ms | depth=${member.stream_depth}`,
|
|
505
|
+
* );
|
|
506
|
+
* }
|
|
507
|
+
* ```
|
|
481
508
|
*/
|
|
482
509
|
async rollCall(delay) {
|
|
483
510
|
return await this.quorum?.rollCall(delay);
|
|
484
511
|
}
|
|
485
512
|
/**
|
|
486
|
-
*
|
|
487
|
-
*
|
|
488
|
-
*
|
|
513
|
+
* Broadcasts a throttle command to the mesh via the quorum channel.
|
|
514
|
+
* Targeted members insert a delay (in milliseconds) before processing
|
|
515
|
+
* their next stream message, providing instant backpressure control
|
|
516
|
+
* across any combination of engines and workers.
|
|
517
|
+
*
|
|
518
|
+
* Throttling is **stateless** — no data is lost. Messages accumulate
|
|
519
|
+
* in Postgres streams and are processed once the throttle is lifted.
|
|
489
520
|
*
|
|
490
|
-
*
|
|
491
|
-
* before processing the next message. Target specific engines and
|
|
492
|
-
* workers by passing a `guid` and/or `topic`. Pass no arguments to
|
|
493
|
-
* throttle the entire quorum.
|
|
521
|
+
* ## Targeting
|
|
494
522
|
*
|
|
495
|
-
*
|
|
496
|
-
*
|
|
523
|
+
* | Option | Effect |
|
|
524
|
+
* |---------|--------|
|
|
525
|
+
* | *(none)* | Throttle the **entire mesh** (all engines + all workers) |
|
|
526
|
+
* | `topic` | Throttle all workers subscribed to this topic |
|
|
527
|
+
* | `guid` | Throttle a single engine or worker instance |
|
|
528
|
+
* | `topic` + `guid` | Throttle a specific topic on a specific instance |
|
|
497
529
|
*
|
|
498
|
-
*
|
|
499
|
-
* and down with no loss of data.
|
|
530
|
+
* ## Special Values
|
|
500
531
|
*
|
|
532
|
+
* | Value | Effect |
|
|
533
|
+
* |--------|--------|
|
|
534
|
+
* | `0` | Resume normal processing (remove throttle) |
|
|
535
|
+
* | `-1` | Pause indefinitely (emergency stop) |
|
|
536
|
+
* | `500` | 500ms delay between messages |
|
|
501
537
|
*
|
|
502
538
|
* @example
|
|
503
539
|
* ```typescript
|
|
540
|
+
* // Emergency stop: pause the entire mesh
|
|
504
541
|
* await hotMesh.throttle({ throttle: -1 });
|
|
542
|
+
*
|
|
543
|
+
* // Resume the entire mesh
|
|
544
|
+
* await hotMesh.throttle({ throttle: 0 });
|
|
545
|
+
*
|
|
546
|
+
* // Slow a specific worker topic to 1 msg per second
|
|
547
|
+
* await hotMesh.throttle({ throttle: 1000, topic: 'order.process' });
|
|
548
|
+
*
|
|
549
|
+
* // Throttle a single instance by GUID
|
|
550
|
+
* await hotMesh.throttle({ throttle: 2000, guid: 'abc-123' });
|
|
551
|
+
*
|
|
552
|
+
* // Throttle a specific topic on a specific instance
|
|
553
|
+
* await hotMesh.throttle({
|
|
554
|
+
* throttle: 500,
|
|
555
|
+
* guid: 'abc-123',
|
|
556
|
+
* topic: 'payment.charge',
|
|
557
|
+
* });
|
|
505
558
|
* ```
|
|
506
559
|
*/
|
|
507
560
|
async throttle(options) {
|
|
@@ -529,19 +582,114 @@ class HotMesh {
|
|
|
529
582
|
return await this.quorum?.pub(throttleMessage);
|
|
530
583
|
}
|
|
531
584
|
/**
|
|
532
|
-
*
|
|
585
|
+
* Publishes a message to every mesh member via the quorum channel
|
|
586
|
+
* (Postgres LISTEN/NOTIFY). Any `QuorumMessage` type can be sent,
|
|
587
|
+
* but the `user` type is the most common for application-level
|
|
588
|
+
* messaging.
|
|
589
|
+
*
|
|
590
|
+
* @example
|
|
591
|
+
* ```typescript
|
|
592
|
+
* // Broadcast a custom event to all mesh members
|
|
593
|
+
* await hotMesh.pubQuorum({
|
|
594
|
+
* type: 'user',
|
|
595
|
+
* topic: 'deploy.notify',
|
|
596
|
+
* message: { version: '2.1.0', deployer: 'ci-pipeline' },
|
|
597
|
+
* });
|
|
598
|
+
*
|
|
599
|
+
* // Broadcast a config-reload signal
|
|
600
|
+
* await hotMesh.pubQuorum({
|
|
601
|
+
* type: 'user',
|
|
602
|
+
* topic: 'config.reload',
|
|
603
|
+
* message: { features: { darkMode: true } },
|
|
604
|
+
* });
|
|
605
|
+
* ```
|
|
533
606
|
*/
|
|
534
607
|
async pubQuorum(quorumMessage) {
|
|
535
608
|
return await this.quorum?.pub(quorumMessage);
|
|
536
609
|
}
|
|
537
610
|
/**
|
|
538
|
-
*
|
|
611
|
+
* Subscribes to the quorum channel, receiving **every** message
|
|
612
|
+
* broadcast across the mesh in real time. This is the primary
|
|
613
|
+
* observability hook into the service mesh — use it to monitor
|
|
614
|
+
* version activations, throttle commands, roll call responses,
|
|
615
|
+
* workflow completions, and custom user events.
|
|
616
|
+
*
|
|
617
|
+
* Messages arrive as typed `QuorumMessage` unions. Switch on
|
|
618
|
+
* `message.type` to handle each:
|
|
619
|
+
*
|
|
620
|
+
* | Type | When it fires |
|
|
621
|
+
* |-------------|---------------|
|
|
622
|
+
* | `pong` | A mesh member responds to a roll call |
|
|
623
|
+
* | `throttle` | A throttle command was broadcast |
|
|
624
|
+
* | `activate` | A version activation is in progress |
|
|
625
|
+
* | `job` | A workflow completed and published its result |
|
|
626
|
+
* | `user` | A custom user message (via `pubQuorum`) |
|
|
627
|
+
* | `ping` | A roll call was initiated |
|
|
628
|
+
* | `work` | A work distribution event |
|
|
629
|
+
* | `cron` | A cron/scheduled event |
|
|
630
|
+
*
|
|
631
|
+
* @example
|
|
632
|
+
* ```typescript
|
|
633
|
+
* // Build a real-time mesh dashboard
|
|
634
|
+
* await hotMesh.subQuorum((topic, message) => {
|
|
635
|
+
* switch (message.type) {
|
|
636
|
+
* case 'pong':
|
|
637
|
+
* dashboard.updateMember(message.guid, {
|
|
638
|
+
* topic: message.profile?.worker_topic,
|
|
639
|
+
* throttle: message.profile?.throttle,
|
|
640
|
+
* depth: message.profile?.stream_depth,
|
|
641
|
+
* cpu: message.profile?.system?.CPULoad,
|
|
642
|
+
* });
|
|
643
|
+
* break;
|
|
644
|
+
*
|
|
645
|
+
* case 'throttle':
|
|
646
|
+
* dashboard.logThrottle(
|
|
647
|
+
* message.throttle,
|
|
648
|
+
* message.topic,
|
|
649
|
+
* message.guid,
|
|
650
|
+
* );
|
|
651
|
+
* break;
|
|
652
|
+
*
|
|
653
|
+
* case 'job':
|
|
654
|
+
* dashboard.logCompletion(message.topic, message.job);
|
|
655
|
+
* break;
|
|
656
|
+
*
|
|
657
|
+
* case 'user':
|
|
658
|
+
* dashboard.logUserEvent(message.topic, message.message);
|
|
659
|
+
* break;
|
|
660
|
+
* }
|
|
661
|
+
* });
|
|
662
|
+
* ```
|
|
663
|
+
*
|
|
664
|
+
* @example
|
|
665
|
+
* ```typescript
|
|
666
|
+
* // React to custom deployment events
|
|
667
|
+
* await hotMesh.subQuorum((topic, message) => {
|
|
668
|
+
* if (message.type === 'user' && message.topic === 'config.reload') {
|
|
669
|
+
* reloadFeatureFlags(message.message);
|
|
670
|
+
* }
|
|
671
|
+
* });
|
|
672
|
+
* ```
|
|
673
|
+
*
|
|
674
|
+
* @example
|
|
675
|
+
* ```typescript
|
|
676
|
+
* // Log all mesh activity for audit
|
|
677
|
+
* await hotMesh.subQuorum((topic, message) => {
|
|
678
|
+
* auditLog.append({
|
|
679
|
+
* timestamp: Date.now(),
|
|
680
|
+
* type: message.type,
|
|
681
|
+
* guid: message.guid,
|
|
682
|
+
* topic: message.topic,
|
|
683
|
+
* payload: message,
|
|
684
|
+
* });
|
|
685
|
+
* });
|
|
686
|
+
* ```
|
|
539
687
|
*/
|
|
540
688
|
async subQuorum(callback) {
|
|
541
689
|
return await this.quorum?.sub(callback);
|
|
542
690
|
}
|
|
543
691
|
/**
|
|
544
|
-
*
|
|
692
|
+
* Unsubscribes a callback previously registered with `subQuorum()`.
|
|
545
693
|
*/
|
|
546
694
|
async unsubQuorum(callback) {
|
|
547
695
|
return await this.quorum?.unsub(callback);
|
|
@@ -556,43 +704,78 @@ class HotMesh {
|
|
|
556
704
|
return await this.engine?.plan(path);
|
|
557
705
|
}
|
|
558
706
|
/**
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
*
|
|
562
|
-
*
|
|
707
|
+
* Deploys a YAML workflow graph to Postgres. Accepts a file path or
|
|
708
|
+
* an inline YAML string. Referenced `$ref` files are resolved and
|
|
709
|
+
* merged. The deployed version is **inactive** until `activate()` is
|
|
710
|
+
* called.
|
|
711
|
+
*
|
|
712
|
+
* @example
|
|
713
|
+
* ```typescript
|
|
714
|
+
* // Deploy from an inline YAML string
|
|
715
|
+
* await hotMesh.deploy(`
|
|
716
|
+
* app:
|
|
717
|
+
* id: myapp
|
|
718
|
+
* version: '2'
|
|
719
|
+
* graphs:
|
|
720
|
+
* - subscribes: order.placed
|
|
721
|
+
* activities:
|
|
722
|
+
* t1:
|
|
723
|
+
* type: trigger
|
|
724
|
+
* process:
|
|
725
|
+
* type: worker
|
|
726
|
+
* topic: order.process
|
|
727
|
+
* transitions:
|
|
728
|
+
* t1:
|
|
729
|
+
* - to: process
|
|
730
|
+
* `);
|
|
563
731
|
*
|
|
564
|
-
*
|
|
732
|
+
* // Deploy from a file path (resolves $ref references)
|
|
733
|
+
* await hotMesh.deploy('./workflows/order.yaml');
|
|
734
|
+
* ```
|
|
565
735
|
*/
|
|
566
736
|
async deploy(pathOrYAML) {
|
|
567
737
|
return await this.engine?.deploy(pathOrYAML);
|
|
568
738
|
}
|
|
569
739
|
/**
|
|
570
|
-
*
|
|
571
|
-
*
|
|
740
|
+
* Activates a deployed version across the entire mesh. The quorum
|
|
741
|
+
* coordinates a synchronized switch-over:
|
|
742
|
+
*
|
|
743
|
+
* 1. Roll call to verify quorum health.
|
|
744
|
+
* 2. Broadcast `nocache` mode — all engines consult Postgres for the
|
|
745
|
+
* active version on every request.
|
|
746
|
+
* 3. Set the new version as active.
|
|
747
|
+
* 4. Broadcast `cache` mode — engines resume caching.
|
|
748
|
+
*
|
|
749
|
+
* The optional `delay` adds a pause (in ms) for the quorum to reach
|
|
750
|
+
* consensus under heavy traffic. Combine with `throttle()` for
|
|
751
|
+
* zero-downtime version switches.
|
|
572
752
|
*
|
|
573
|
-
*
|
|
574
|
-
*
|
|
575
|
-
*
|
|
576
|
-
*
|
|
577
|
-
* call is processed. This ensures that all engines are running the same version
|
|
578
|
-
* of the app, switching over at the same moment and then enabling `cache` mode
|
|
579
|
-
* to improve performance.
|
|
753
|
+
* @example
|
|
754
|
+
* ```typescript
|
|
755
|
+
* // Simple activation
|
|
756
|
+
* await hotMesh.activate('2');
|
|
580
757
|
*
|
|
581
|
-
*
|
|
582
|
-
*
|
|
758
|
+
* // With consensus delay under heavy traffic
|
|
759
|
+
* await hotMesh.throttle({ throttle: 500 }); // slow the mesh
|
|
760
|
+
* await hotMesh.activate('2', 2000); // activate with 2s consensus window
|
|
761
|
+
* await hotMesh.throttle({ throttle: 0 }); // resume full speed
|
|
762
|
+
* ```
|
|
583
763
|
*/
|
|
584
764
|
async activate(version, delay) {
|
|
585
765
|
return await this.quorum?.activate(version, delay);
|
|
586
766
|
}
|
|
587
767
|
/**
|
|
588
|
-
*
|
|
589
|
-
*
|
|
768
|
+
* Exports the full job state as a structured JSON object, including
|
|
769
|
+
* activity data, transitions, and dependency chains. Useful for
|
|
770
|
+
* debugging, auditing, and visualizing workflow execution.
|
|
590
771
|
*/
|
|
591
772
|
async export(jobId) {
|
|
592
773
|
return await this.engine?.export(jobId);
|
|
593
774
|
}
|
|
594
775
|
/**
|
|
595
|
-
* Returns all
|
|
776
|
+
* Returns all raw key-value pairs for a job's HASH record. This is
|
|
777
|
+
* the lowest-level read — it returns internal engine fields alongside
|
|
778
|
+
* user data. Prefer `getState()` for structured output.
|
|
596
779
|
*/
|
|
597
780
|
async getRaw(jobId) {
|
|
598
781
|
return await this.engine?.getRaw(jobId);
|
|
@@ -605,36 +788,42 @@ class HotMesh {
|
|
|
605
788
|
return await this.engine?.getStats(topic, query);
|
|
606
789
|
}
|
|
607
790
|
/**
|
|
608
|
-
* Returns the status
|
|
609
|
-
*
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
* positive value are still running.
|
|
791
|
+
* Returns the numeric status semaphore for a job.
|
|
792
|
+
*
|
|
793
|
+
* | Value | Meaning |
|
|
794
|
+
* |------------------|---------|
|
|
795
|
+
* | `> 0` | Running (count of open activities) |
|
|
796
|
+
* | `0` | Completed normally |
|
|
797
|
+
* | `-1` | Pending (awaiting activation) |
|
|
798
|
+
* | `< -100,000,000` | Interrupted (abnormal termination) |
|
|
617
799
|
*/
|
|
618
800
|
async getStatus(jobId) {
|
|
619
801
|
return this.engine?.getStatus(jobId);
|
|
620
802
|
}
|
|
621
803
|
/**
|
|
622
|
-
* Returns the job state (data and metadata) for a job
|
|
804
|
+
* Returns the structured job state (data and metadata) for a job,
|
|
805
|
+
* scoped to the given workflow topic.
|
|
806
|
+
*
|
|
807
|
+
* @example
|
|
808
|
+
* ```typescript
|
|
809
|
+
* const state = await hotMesh.getState('order.placed', jobId);
|
|
810
|
+
* console.log(state.data); // workflow output data
|
|
811
|
+
* console.log(state.metadata); // jid, aid, timestamps, etc.
|
|
812
|
+
* ```
|
|
623
813
|
*/
|
|
624
814
|
async getState(topic, jobId) {
|
|
625
815
|
return this.engine?.getState(topic, jobId);
|
|
626
816
|
}
|
|
627
817
|
/**
|
|
628
|
-
* Returns searchable
|
|
629
|
-
*
|
|
630
|
-
*
|
|
631
|
-
* it can be read but not written).
|
|
818
|
+
* Returns specific searchable fields from a job's HASH record.
|
|
819
|
+
* Pass field names to retrieve; use `":"` to read the reserved
|
|
820
|
+
* status field.
|
|
632
821
|
*
|
|
633
822
|
* @example
|
|
634
823
|
* ```typescript
|
|
635
|
-
* const fields = ['
|
|
636
|
-
* const
|
|
637
|
-
* //
|
|
824
|
+
* const fields = ['orderId', 'status', '":"'];
|
|
825
|
+
* const data = await hotMesh.getQueryState(jobId, fields);
|
|
826
|
+
* // => { orderId: 'ORD-123', status: 'paid', ':': '0' }
|
|
638
827
|
* ```
|
|
639
828
|
*/
|
|
640
829
|
async getQueryState(jobId, fields) {
|
|
@@ -653,39 +842,83 @@ class HotMesh {
|
|
|
653
842
|
return await this.engine?.resolveQuery(topic, query);
|
|
654
843
|
}
|
|
655
844
|
/**
|
|
656
|
-
*
|
|
845
|
+
* Interrupts (terminates) an active workflow job. The job's status is
|
|
846
|
+
* set to an error code indicating abnormal termination, and any pending
|
|
847
|
+
* activities or timers are cancelled.
|
|
848
|
+
*
|
|
849
|
+
* @example
|
|
850
|
+
* ```typescript
|
|
851
|
+
* await hotMesh.interrupt('order.placed', jobId, {
|
|
852
|
+
* reason: 'Customer cancelled',
|
|
853
|
+
* descend: true, // also interrupt child workflows
|
|
854
|
+
* });
|
|
855
|
+
* ```
|
|
657
856
|
*/
|
|
658
857
|
async interrupt(topic, jobId, options = {}) {
|
|
659
858
|
return await this.engine?.interrupt(topic, jobId, options);
|
|
660
859
|
}
|
|
661
860
|
/**
|
|
662
|
-
* Immediately deletes
|
|
663
|
-
*
|
|
664
|
-
*
|
|
861
|
+
* Immediately deletes a completed job from the system. The job must
|
|
862
|
+
* have a non-positive status (completed or interrupted). Running jobs
|
|
863
|
+
* must be interrupted first.
|
|
665
864
|
*/
|
|
666
865
|
async scrub(jobId) {
|
|
667
866
|
await this.engine?.scrub(jobId);
|
|
668
867
|
}
|
|
669
868
|
/**
|
|
670
|
-
*
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
*
|
|
869
|
+
* Sends a signal to a paused workflow, resuming its execution.
|
|
870
|
+
* The `topic` must match a hook rule defined in the YAML graph's
|
|
871
|
+
* `hooks` section. The engine locates the exact activity and
|
|
872
|
+
* dimension for reentry based on the hook rule's match conditions.
|
|
674
873
|
*
|
|
675
|
-
*
|
|
676
|
-
*
|
|
874
|
+
* Use this to deliver external data (approval decisions, webhook
|
|
875
|
+
* payloads, partner responses) into a workflow that is sleeping
|
|
876
|
+
* on a hook activity or awaiting a `waitFor()` signal.
|
|
877
|
+
*
|
|
878
|
+
* @example
|
|
879
|
+
* ```typescript
|
|
880
|
+
* // Resume a paused approval workflow with external data
|
|
881
|
+
* await hotMesh.signal('order.approval', {
|
|
882
|
+
* id: jobId,
|
|
883
|
+
* approved: true,
|
|
884
|
+
* reviewer: 'manager@example.com',
|
|
885
|
+
* });
|
|
886
|
+
* ```
|
|
887
|
+
*
|
|
888
|
+
* @example
|
|
889
|
+
* ```typescript
|
|
890
|
+
* // Signal a MemFlow workflow waiting on waitFor('payment-received')
|
|
891
|
+
* await hotMesh.signal(`${appId}.wfs.signal`, {
|
|
892
|
+
* id: 'payment-received',
|
|
893
|
+
* data: { amount: 99.99, currency: 'USD' },
|
|
894
|
+
* });
|
|
895
|
+
* ```
|
|
677
896
|
*/
|
|
678
|
-
async
|
|
679
|
-
return await this.engine?.
|
|
897
|
+
async signal(topic, data, status, code) {
|
|
898
|
+
return await this.engine?.signal(topic, data, status, code);
|
|
680
899
|
}
|
|
681
900
|
/**
|
|
901
|
+
* Fan-out variant of `signal()` that delivers data to **all**
|
|
902
|
+
* paused workflows matching a search query. Useful for resuming
|
|
903
|
+
* a batch of workflows waiting on the same external event.
|
|
904
|
+
*
|
|
682
905
|
* @private
|
|
683
906
|
*/
|
|
684
|
-
async
|
|
685
|
-
return await this.engine?.
|
|
907
|
+
async signalAll(hookTopic, data, query, queryFacets = []) {
|
|
908
|
+
return await this.engine?.signalAll(hookTopic, data, query, queryFacets);
|
|
686
909
|
}
|
|
687
910
|
/**
|
|
688
|
-
*
|
|
911
|
+
* Stops **all** HotMesh instances in the current process — engines,
|
|
912
|
+
* workers, and connections. Typically called in signal handlers
|
|
913
|
+
* (`SIGTERM`, `SIGINT`) for graceful shutdown.
|
|
914
|
+
*
|
|
915
|
+
* @example
|
|
916
|
+
* ```typescript
|
|
917
|
+
* process.on('SIGTERM', async () => {
|
|
918
|
+
* await HotMesh.stop();
|
|
919
|
+
* process.exit(0);
|
|
920
|
+
* });
|
|
921
|
+
* ```
|
|
689
922
|
*/
|
|
690
923
|
static async stop() {
|
|
691
924
|
if (!this.disconnecting) {
|
|
@@ -695,7 +928,9 @@ class HotMesh {
|
|
|
695
928
|
}
|
|
696
929
|
}
|
|
697
930
|
/**
|
|
698
|
-
*
|
|
931
|
+
* Stops this specific HotMesh instance — its engine, quorum
|
|
932
|
+
* membership, and all attached workers. Other instances in the
|
|
933
|
+
* same process are unaffected.
|
|
699
934
|
*/
|
|
700
935
|
stop() {
|
|
701
936
|
this.engine?.taskService.cancelCleanup();
|