@hotmeshio/hotmesh 0.12.1 → 0.14.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 +18 -22
- package/build/modules/enums.d.ts +60 -5
- package/build/modules/enums.js +62 -7
- package/build/modules/errors.d.ts +15 -2
- package/build/modules/errors.js +17 -1
- package/build/modules/storage.d.ts +1 -0
- package/build/modules/storage.js +2 -1
- package/build/package.json +8 -2
- package/build/services/activities/activity/context.d.ts +22 -0
- package/build/services/activities/activity/context.js +76 -0
- package/build/services/activities/activity/index.d.ts +116 -0
- package/build/services/activities/activity/index.js +299 -0
- package/build/services/activities/activity/mapping.d.ts +12 -0
- package/build/services/activities/activity/mapping.js +63 -0
- package/build/services/activities/activity/process.d.ts +28 -0
- package/build/services/activities/activity/process.js +100 -0
- package/build/services/activities/activity/protocol.d.ts +39 -0
- package/build/services/activities/activity/protocol.js +151 -0
- package/build/services/activities/activity/state.d.ts +40 -0
- package/build/services/activities/activity/state.js +143 -0
- package/build/services/activities/activity/transition.d.ts +23 -0
- package/build/services/activities/activity/transition.js +71 -0
- package/build/services/activities/activity/verify.d.ts +22 -0
- package/build/services/activities/activity/verify.js +85 -0
- package/build/services/activities/await.d.ts +1 -4
- package/build/services/activities/await.js +2 -36
- package/build/services/activities/cycle.d.ts +1 -11
- package/build/services/activities/cycle.js +3 -46
- package/build/services/activities/hook.d.ts +2 -11
- package/build/services/activities/hook.js +30 -50
- package/build/services/activities/interrupt.d.ts +2 -4
- package/build/services/activities/interrupt.js +4 -38
- package/build/services/activities/signal.d.ts +1 -11
- package/build/services/activities/signal.js +3 -48
- package/build/services/activities/trigger.d.ts +1 -3
- package/build/services/activities/trigger.js +0 -3
- package/build/services/activities/worker.d.ts +3 -6
- package/build/services/activities/worker.js +4 -40
- package/build/services/connector/factory.d.ts +6 -0
- package/build/services/connector/factory.js +24 -0
- package/build/services/dba/index.d.ts +14 -4
- package/build/services/dba/index.js +57 -18
- package/build/services/durable/activity.d.ts +30 -0
- package/build/services/durable/activity.js +46 -0
- package/build/services/durable/client.d.ts +26 -31
- package/build/services/durable/client.js +26 -31
- package/build/services/durable/connection.d.ts +13 -7
- package/build/services/durable/connection.js +13 -7
- package/build/services/durable/exporter.d.ts +2 -2
- package/build/services/durable/exporter.js +27 -12
- package/build/services/durable/handle.d.ts +59 -41
- package/build/services/durable/handle.js +61 -41
- package/build/services/durable/index.d.ts +152 -283
- package/build/services/durable/index.js +161 -289
- package/build/services/durable/interceptor.d.ts +43 -33
- package/build/services/durable/interceptor.js +59 -39
- package/build/services/durable/schemas/factory.d.ts +2 -3
- package/build/services/durable/schemas/factory.js +180 -30
- package/build/services/durable/telemetry.d.ts +80 -0
- package/build/services/durable/telemetry.js +137 -0
- package/build/services/durable/worker.d.ts +100 -21
- package/build/services/durable/worker.js +314 -60
- package/build/services/durable/workflow/all.d.ts +1 -1
- package/build/services/durable/workflow/all.js +1 -1
- package/build/services/durable/workflow/cancellationScope.d.ts +104 -0
- package/build/services/durable/workflow/cancellationScope.js +139 -0
- package/build/services/durable/workflow/common.d.ts +5 -4
- package/build/services/durable/workflow/common.js +6 -1
- package/build/services/durable/workflow/{waitFor.d.ts → condition.d.ts} +9 -8
- package/build/services/durable/workflow/{waitFor.js → condition.js} +44 -11
- package/build/services/durable/workflow/continueAsNew.d.ts +65 -0
- package/build/services/durable/workflow/continueAsNew.js +92 -0
- package/build/services/durable/workflow/didRun.d.ts +2 -2
- package/build/services/durable/workflow/didRun.js +4 -4
- package/build/services/durable/workflow/enrich.d.ts +5 -0
- package/build/services/durable/workflow/enrich.js +5 -0
- package/build/services/durable/workflow/entityMethods.d.ts +7 -0
- package/build/services/durable/workflow/entityMethods.js +7 -0
- package/build/services/durable/workflow/execHook.js +3 -3
- package/build/services/durable/workflow/execHookBatch.js +2 -2
- package/build/services/durable/workflow/{execChild.d.ts → executeChild.d.ts} +4 -40
- package/build/services/durable/workflow/{execChild.js → executeChild.js} +36 -45
- package/build/services/durable/workflow/hook.d.ts +1 -1
- package/build/services/durable/workflow/hook.js +4 -3
- package/build/services/durable/workflow/index.d.ts +45 -50
- package/build/services/durable/workflow/index.js +46 -51
- package/build/services/durable/workflow/interruption.d.ts +7 -6
- package/build/services/durable/workflow/interruption.js +11 -7
- package/build/services/durable/workflow/patched.d.ts +72 -0
- package/build/services/durable/workflow/patched.js +110 -0
- package/build/services/durable/workflow/proxyActivities.d.ts +7 -7
- package/build/services/durable/workflow/proxyActivities.js +51 -15
- package/build/services/durable/workflow/searchMethods.d.ts +7 -0
- package/build/services/durable/workflow/searchMethods.js +7 -0
- package/build/services/durable/workflow/signal.d.ts +4 -4
- package/build/services/durable/workflow/signal.js +4 -4
- package/build/services/durable/workflow/{sleepFor.d.ts → sleep.d.ts} +7 -7
- package/build/services/durable/workflow/{sleepFor.js → sleep.js} +39 -10
- package/build/services/durable/workflow/terminate.d.ts +55 -0
- package/build/services/durable/workflow/{interrupt.js → terminate.js} +21 -21
- package/build/services/durable/workflow/trace.js +2 -2
- package/build/services/durable/workflow/uuid4.d.ts +14 -0
- package/build/services/durable/workflow/uuid4.js +39 -0
- package/build/services/durable/workflow/{context.d.ts → workflowInfo.d.ts} +5 -5
- package/build/services/durable/workflow/{context.js → workflowInfo.js} +7 -7
- package/build/services/engine/compiler.d.ts +19 -0
- package/build/services/engine/compiler.js +20 -0
- package/build/services/engine/completion.d.ts +46 -0
- package/build/services/engine/completion.js +145 -0
- package/build/services/engine/dispatch.d.ts +24 -0
- package/build/services/engine/dispatch.js +98 -0
- package/build/services/engine/index.d.ts +49 -81
- package/build/services/engine/index.js +175 -573
- package/build/services/engine/init.d.ts +42 -0
- package/build/services/engine/init.js +74 -0
- package/build/services/engine/pubsub.d.ts +50 -0
- package/build/services/engine/pubsub.js +118 -0
- package/build/services/engine/reporting.d.ts +20 -0
- package/build/services/engine/reporting.js +38 -0
- package/build/services/engine/schema.d.ts +23 -0
- package/build/services/engine/schema.js +62 -0
- package/build/services/engine/signal.d.ts +57 -0
- package/build/services/engine/signal.js +117 -0
- package/build/services/engine/state.d.ts +35 -0
- package/build/services/engine/state.js +61 -0
- package/build/services/engine/version.d.ts +31 -0
- package/build/services/engine/version.js +73 -0
- package/build/services/hotmesh/deployment.d.ts +21 -0
- package/build/services/hotmesh/deployment.js +25 -0
- package/build/services/hotmesh/index.d.ts +142 -533
- package/build/services/hotmesh/index.js +223 -674
- package/build/services/hotmesh/init.d.ts +42 -0
- package/build/services/hotmesh/init.js +93 -0
- package/build/services/hotmesh/jobs.d.ts +67 -0
- package/build/services/hotmesh/jobs.js +99 -0
- package/build/services/hotmesh/pubsub.d.ts +38 -0
- package/build/services/hotmesh/pubsub.js +54 -0
- package/build/services/hotmesh/quorum.d.ts +30 -0
- package/build/services/hotmesh/quorum.js +62 -0
- package/build/services/hotmesh/validation.d.ts +6 -0
- package/build/services/hotmesh/validation.js +28 -0
- package/build/services/quorum/index.js +1 -0
- package/build/services/router/consumption/index.d.ts +11 -5
- package/build/services/router/consumption/index.js +24 -17
- package/build/services/router/error-handling/index.d.ts +2 -2
- package/build/services/router/error-handling/index.js +14 -14
- package/build/services/router/index.d.ts +1 -1
- package/build/services/router/index.js +2 -2
- package/build/services/serializer/index.d.ts +22 -0
- package/build/services/serializer/index.js +39 -1
- package/build/services/store/index.d.ts +1 -0
- package/build/services/store/providers/postgres/exporter-sql.d.ts +2 -2
- package/build/services/store/providers/postgres/exporter-sql.js +4 -4
- package/build/services/store/providers/postgres/kvtables.js +7 -6
- package/build/services/store/providers/postgres/kvtypes/hash/basic.js +67 -52
- package/build/services/store/providers/postgres/kvtypes/hash/jsonb.js +87 -72
- package/build/services/store/providers/postgres/kvtypes/hash/udata.js +106 -79
- package/build/services/store/providers/postgres/kvtypes/hash/utils.d.ts +16 -0
- package/build/services/store/providers/postgres/kvtypes/hash/utils.js +29 -16
- package/build/services/store/providers/postgres/postgres.d.ts +1 -0
- package/build/services/store/providers/postgres/postgres.js +14 -4
- package/build/services/stream/factory.d.ts +3 -1
- package/build/services/stream/factory.js +2 -2
- package/build/services/stream/index.d.ts +1 -0
- package/build/services/stream/providers/nats/nats.d.ts +1 -0
- package/build/services/stream/providers/nats/nats.js +1 -0
- package/build/services/stream/providers/postgres/credentials.d.ts +56 -0
- package/build/services/stream/providers/postgres/credentials.js +129 -0
- package/build/services/stream/providers/postgres/kvtables.js +18 -0
- package/build/services/stream/providers/postgres/messages.js +7 -7
- package/build/services/stream/providers/postgres/notifications.js +16 -2
- package/build/services/stream/providers/postgres/postgres.d.ts +7 -0
- package/build/services/stream/providers/postgres/postgres.js +35 -4
- package/build/services/stream/providers/postgres/procedures.d.ts +21 -0
- package/build/services/stream/providers/postgres/procedures.js +213 -0
- package/build/services/stream/providers/postgres/secured.d.ts +34 -0
- package/build/services/stream/providers/postgres/secured.js +146 -0
- package/build/services/stream/providers/postgres/stats.d.ts +1 -0
- package/build/services/stream/providers/postgres/stats.js +1 -0
- package/build/services/stream/registry.d.ts +1 -1
- package/build/services/stream/registry.js +5 -2
- package/build/services/telemetry/index.d.ts +10 -1
- package/build/services/telemetry/index.js +40 -7
- package/build/services/worker/credentials.d.ts +51 -0
- package/build/services/worker/credentials.js +87 -0
- package/build/services/worker/index.d.ts +2 -2
- package/build/services/worker/index.js +7 -6
- package/build/types/codec.d.ts +84 -0
- package/build/types/codec.js +2 -0
- package/build/types/dba.d.ts +39 -3
- package/build/types/durable.d.ts +123 -25
- package/build/types/error.d.ts +10 -0
- package/build/types/exporter.d.ts +1 -1
- package/build/types/hotmesh.d.ts +67 -4
- package/build/types/index.d.ts +2 -1
- package/build/types/provider.d.ts +2 -2
- package/build/types/quorum.d.ts +35 -1
- package/build/types/stream.d.ts +12 -6
- package/package.json +8 -2
- package/build/services/activities/activity.d.ts +0 -192
- package/build/services/activities/activity.js +0 -786
- package/build/services/durable/workflow/interrupt.d.ts +0 -55
|
@@ -1,15 +1,43 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
2
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
26
|
exports.HotMesh = void 0;
|
|
4
|
-
const key_1 = require("../../modules/key");
|
|
5
27
|
const utils_1 = require("../../modules/utils");
|
|
6
28
|
const factory_1 = require("../connector/factory");
|
|
7
|
-
const engine_1 = require("../engine");
|
|
8
29
|
const logger_1 = require("../logger");
|
|
9
|
-
const quorum_1 = require("../quorum");
|
|
10
30
|
const router_1 = require("../router");
|
|
11
|
-
|
|
12
|
-
const
|
|
31
|
+
// Submodules
|
|
32
|
+
const Validation = __importStar(require("./validation"));
|
|
33
|
+
const Init = __importStar(require("./init"));
|
|
34
|
+
const PubSub = __importStar(require("./pubsub"));
|
|
35
|
+
const Quorum = __importStar(require("./quorum"));
|
|
36
|
+
const Deployment = __importStar(require("./deployment"));
|
|
37
|
+
const Jobs = __importStar(require("./jobs"));
|
|
38
|
+
// Codec and credential re-exports
|
|
39
|
+
const serializer_1 = require("../serializer");
|
|
40
|
+
const WorkerCredentials = __importStar(require("../worker/credentials"));
|
|
13
41
|
/**
|
|
14
42
|
* A distributed service mesh that turns Postgres into a durable workflow
|
|
15
43
|
* orchestration engine. Every `HotMesh.init()` call creates a **point of
|
|
@@ -89,67 +117,6 @@ const enums_1 = require("../../modules/enums");
|
|
|
89
117
|
* const result = await hotMesh.pubsub('order.placed', { id: 'ORD-456' });
|
|
90
118
|
* ```
|
|
91
119
|
*
|
|
92
|
-
* ## Quorum: The Mesh Control Plane
|
|
93
|
-
*
|
|
94
|
-
* The quorum channel is a broadcast bus available to every mesh member.
|
|
95
|
-
* Use it for operational control, observability, and custom messaging.
|
|
96
|
-
*
|
|
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;
|
|
120
|
-
* }
|
|
121
|
-
* });
|
|
122
|
-
*
|
|
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' },
|
|
128
|
-
* });
|
|
129
|
-
* ```
|
|
130
|
-
*
|
|
131
|
-
* ## Throttling: Backpressure Across the Mesh
|
|
132
|
-
*
|
|
133
|
-
* Throttle commands propagate instantly to every targeted member via
|
|
134
|
-
* the quorum channel, providing fine-grained flow control.
|
|
135
|
-
*
|
|
136
|
-
* ```typescript
|
|
137
|
-
* // Pause the ENTIRE mesh (emergency stop)
|
|
138
|
-
* await hotMesh.throttle({ throttle: -1 });
|
|
139
|
-
*
|
|
140
|
-
* // Resume the entire mesh
|
|
141
|
-
* await hotMesh.throttle({ throttle: 0 });
|
|
142
|
-
*
|
|
143
|
-
* // Slow a specific worker topic to 1 message per 500ms
|
|
144
|
-
* await hotMesh.throttle({ throttle: 500, topic: 'order.process' });
|
|
145
|
-
*
|
|
146
|
-
* // Throttle a single engine/worker instance by GUID
|
|
147
|
-
* await hotMesh.throttle({ throttle: 2000, guid: 'abc-123' });
|
|
148
|
-
*
|
|
149
|
-
* // Combine: throttle a specific topic on a specific instance
|
|
150
|
-
* await hotMesh.throttle({ throttle: 1000, guid: 'abc-123', topic: 'order.process' });
|
|
151
|
-
* ```
|
|
152
|
-
*
|
|
153
120
|
* ## Lifecycle
|
|
154
121
|
*
|
|
155
122
|
* 1. **`init`** — Create an engine + workers; join the quorum.
|
|
@@ -161,127 +128,50 @@ const enums_1 = require("../../modules/enums");
|
|
|
161
128
|
* ## Higher-Level Modules
|
|
162
129
|
*
|
|
163
130
|
* For most use cases, prefer the higher-level wrappers:
|
|
164
|
-
* - **Durable** —
|
|
131
|
+
* - **Durable** — Durable workflow functions with replay and retry.
|
|
165
132
|
* - **Virtual** — Virtual network functions and idempotent RPC.
|
|
166
133
|
*
|
|
167
134
|
* @see {@link https://hotmeshio.github.io/sdk-typescript/} - API reference
|
|
168
135
|
*/
|
|
169
136
|
class HotMesh {
|
|
137
|
+
/**
|
|
138
|
+
* @private
|
|
139
|
+
*/
|
|
140
|
+
constructor() {
|
|
141
|
+
/**
|
|
142
|
+
* @private
|
|
143
|
+
*/
|
|
144
|
+
this.engine = null;
|
|
145
|
+
/**
|
|
146
|
+
* @private
|
|
147
|
+
*/
|
|
148
|
+
this.quorum = null;
|
|
149
|
+
/**
|
|
150
|
+
* @private
|
|
151
|
+
*/
|
|
152
|
+
this.workers = [];
|
|
153
|
+
}
|
|
170
154
|
/**
|
|
171
155
|
* @private
|
|
172
156
|
*/
|
|
173
157
|
verifyAndSetNamespace(namespace) {
|
|
174
|
-
|
|
175
|
-
this.namespace = key_1.HMNS;
|
|
176
|
-
}
|
|
177
|
-
else if (!namespace.match(/^[A-Za-z0-9-]+$/)) {
|
|
178
|
-
throw new Error(`config.namespace [${namespace}] is invalid`);
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
this.namespace = namespace;
|
|
182
|
-
}
|
|
158
|
+
Validation.verifyAndSetNamespace(this, namespace);
|
|
183
159
|
}
|
|
184
160
|
/**
|
|
185
161
|
* @private
|
|
186
162
|
*/
|
|
187
163
|
verifyAndSetAppId(appId) {
|
|
188
|
-
|
|
189
|
-
throw new Error(`config.appId [${appId}] is invalid`);
|
|
190
|
-
}
|
|
191
|
-
else if (appId === 'a') {
|
|
192
|
-
throw new Error(`config.appId [${appId}] is reserved`);
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
this.appId = appId;
|
|
196
|
-
}
|
|
164
|
+
Validation.verifyAndSetAppId(this, appId);
|
|
197
165
|
}
|
|
198
166
|
/**
|
|
199
|
-
*
|
|
200
|
-
* similarly to the engine, but as an array with
|
|
201
|
-
* multiple worker objects.
|
|
202
|
-
*
|
|
203
|
-
* ## Retry Policy Configuration
|
|
167
|
+
* Create a HotMesh instance with an engine and optional workers.
|
|
204
168
|
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
169
|
+
* The engine manages workflow state in Postgres. Workers are callback
|
|
170
|
+
* functions that consume messages from Postgres streams — they can
|
|
171
|
+
* run on the same process or on entirely separate servers.
|
|
208
172
|
*
|
|
209
|
-
* -
|
|
210
|
-
*
|
|
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.
|
|
214
|
-
*
|
|
215
|
-
* @example Basic Configuration
|
|
216
|
-
* ```typescript
|
|
217
|
-
* const hotMesh = await HotMesh.init({
|
|
218
|
-
* appId: 'myapp',
|
|
219
|
-
* engine: {
|
|
220
|
-
* connection: {
|
|
221
|
-
* class: Postgres,
|
|
222
|
-
* options: {
|
|
223
|
-
* connectionString: 'postgresql://usr:pwd@localhost:5432/db',
|
|
224
|
-
* }
|
|
225
|
-
* }
|
|
226
|
-
* },
|
|
227
|
-
* workers: [...]
|
|
228
|
-
* });
|
|
229
|
-
* ```
|
|
230
|
-
*
|
|
231
|
-
* @example Engine Retry Policy
|
|
232
|
-
* ```typescript
|
|
233
|
-
* const hotMesh = await HotMesh.init({
|
|
234
|
-
* appId: 'myapp',
|
|
235
|
-
* engine: {
|
|
236
|
-
* connection: {
|
|
237
|
-
* class: Postgres,
|
|
238
|
-
* options: { connectionString: 'postgresql://...' }
|
|
239
|
-
* },
|
|
240
|
-
* retryPolicy: {
|
|
241
|
-
* maximumAttempts: 5,
|
|
242
|
-
* backoffCoefficient: 2,
|
|
243
|
-
* maximumInterval: '300s'
|
|
244
|
-
* }
|
|
245
|
-
* }
|
|
246
|
-
* });
|
|
247
|
-
* ```
|
|
248
|
-
*
|
|
249
|
-
* @example Worker Retry Policy
|
|
250
|
-
* ```typescript
|
|
251
|
-
* const hotMesh = await HotMesh.init({
|
|
252
|
-
* appId: 'myapp',
|
|
253
|
-
* engine: { connection },
|
|
254
|
-
* workers: [{
|
|
255
|
-
* topic: 'order.process',
|
|
256
|
-
* connection,
|
|
257
|
-
* retryPolicy: {
|
|
258
|
-
* maximumAttempts: 5,
|
|
259
|
-
* backoffCoefficient: 2,
|
|
260
|
-
* maximumInterval: '30s',
|
|
261
|
-
* },
|
|
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;
|
|
270
|
-
* }
|
|
271
|
-
* }]
|
|
272
|
-
* });
|
|
273
|
-
* ```
|
|
274
|
-
*
|
|
275
|
-
* **Retry Policy Options**:
|
|
276
|
-
* - `maximumAttempts` - Maximum retry attempts before failure (default: 3)
|
|
277
|
-
* - `backoffCoefficient` - Base for exponential backoff calculation (default: 10)
|
|
278
|
-
* - `maximumInterval` - Maximum delay between retries in seconds or duration string (default: '120s')
|
|
279
|
-
*
|
|
280
|
-
* **Retry Delays**: For `backoffCoefficient: 2`, delays are: 2s, 4s, 8s, 16s, 32s...
|
|
281
|
-
* capped at `maximumInterval`.
|
|
282
|
-
*
|
|
283
|
-
* **Note**: Retry policies are stored in PostgreSQL columns for efficient querying and
|
|
284
|
-
* observability. Each retry creates a new message, preserving message immutability.
|
|
173
|
+
* @param config - Engine connection, worker definitions, app ID, and options.
|
|
174
|
+
* @returns A running HotMesh instance joined to the quorum.
|
|
285
175
|
*/
|
|
286
176
|
static async init(config) {
|
|
287
177
|
const instance = new HotMesh();
|
|
@@ -289,9 +179,9 @@ class HotMesh {
|
|
|
289
179
|
instance.verifyAndSetNamespace(config.namespace);
|
|
290
180
|
instance.verifyAndSetAppId(config.appId);
|
|
291
181
|
instance.logger = new logger_1.LoggerService(config.appId, instance.guid, config.name || '', config.logLevel);
|
|
292
|
-
await
|
|
293
|
-
await
|
|
294
|
-
await
|
|
182
|
+
await Init.initEngine(instance, config, instance.logger);
|
|
183
|
+
await Init.initQuorum(instance, config, instance.engine, instance.logger);
|
|
184
|
+
await Init.doWork(instance, config, instance.logger);
|
|
295
185
|
return instance;
|
|
296
186
|
}
|
|
297
187
|
/**
|
|
@@ -301,624 +191,245 @@ class HotMesh {
|
|
|
301
191
|
static guid() {
|
|
302
192
|
return (0, utils_1.guid)();
|
|
303
193
|
}
|
|
304
|
-
/**
|
|
305
|
-
* @private
|
|
306
|
-
*/
|
|
307
|
-
async initEngine(config, logger) {
|
|
308
|
-
if (config.engine) {
|
|
309
|
-
//connections that are 'readonly' transfer
|
|
310
|
-
//this property directly to the engine,
|
|
311
|
-
//and ALWAYS take precendence.
|
|
312
|
-
if (config.engine.connection.readonly) {
|
|
313
|
-
config.engine.readonly = true;
|
|
314
|
-
}
|
|
315
|
-
// Apply retry policy to stream connection if provided
|
|
316
|
-
if (config.engine.retryPolicy) {
|
|
317
|
-
this.applyRetryPolicy(config.engine.connection, config.engine.retryPolicy);
|
|
318
|
-
}
|
|
319
|
-
// Initialize task queue for engine
|
|
320
|
-
config.engine.taskQueue = this.initTaskQueue(config.engine.taskQueue, config.taskQueue);
|
|
321
|
-
await factory_1.ConnectorService.initClients(config.engine);
|
|
322
|
-
this.engine = await engine_1.EngineService.init(this.namespace, this.appId, this.guid, config, logger);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
/**
|
|
326
|
-
* @private
|
|
327
|
-
*/
|
|
328
|
-
async initQuorum(config, engine, logger) {
|
|
329
|
-
if (engine) {
|
|
330
|
-
this.quorum = await quorum_1.QuorumService.init(this.namespace, this.appId, this.guid, config, engine, logger);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
/**
|
|
334
|
-
* @private
|
|
335
|
-
*/
|
|
336
|
-
constructor() {
|
|
337
|
-
/**
|
|
338
|
-
* @private
|
|
339
|
-
*/
|
|
340
|
-
this.engine = null;
|
|
341
|
-
/**
|
|
342
|
-
* @private
|
|
343
|
-
*/
|
|
344
|
-
this.quorum = null;
|
|
345
|
-
/**
|
|
346
|
-
* @private
|
|
347
|
-
*/
|
|
348
|
-
this.workers = [];
|
|
349
|
-
}
|
|
350
|
-
/**
|
|
351
|
-
* @private
|
|
352
|
-
*/
|
|
353
|
-
async doWork(config, logger) {
|
|
354
|
-
// Initialize task queues for workers
|
|
355
|
-
if (config.workers) {
|
|
356
|
-
for (const worker of config.workers) {
|
|
357
|
-
// Apply retry policy to stream connection if provided
|
|
358
|
-
if (worker.retryPolicy) {
|
|
359
|
-
this.applyRetryPolicy(worker.connection, worker.retryPolicy);
|
|
360
|
-
}
|
|
361
|
-
worker.taskQueue = this.initTaskQueue(worker.taskQueue, config.taskQueue);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
this.workers = await worker_1.WorkerService.init(this.namespace, this.appId, this.guid, config, logger);
|
|
365
|
-
}
|
|
366
|
-
/**
|
|
367
|
-
* Initialize task queue with proper precedence:
|
|
368
|
-
* 1. Use component-specific queue if set (engine/worker)
|
|
369
|
-
* 2. Use global config queue if set
|
|
370
|
-
* 3. Use default queue as fallback
|
|
371
|
-
* @private
|
|
372
|
-
*/
|
|
373
|
-
initTaskQueue(componentQueue, globalQueue) {
|
|
374
|
-
// Component-specific queue takes precedence
|
|
375
|
-
if (componentQueue) {
|
|
376
|
-
return componentQueue;
|
|
377
|
-
}
|
|
378
|
-
// Global config queue is next
|
|
379
|
-
if (globalQueue) {
|
|
380
|
-
return globalQueue;
|
|
381
|
-
}
|
|
382
|
-
// Default queue as fallback
|
|
383
|
-
return enums_1.DEFAULT_TASK_QUEUE;
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* Apply retry policy to the stream connection within a ProviderConfig or ProvidersConfig.
|
|
387
|
-
* Handles both short-form (ProviderConfig) and long-form (ProvidersConfig) connection configs.
|
|
388
|
-
* @private
|
|
389
|
-
*/
|
|
390
|
-
applyRetryPolicy(connection, retryPolicy) {
|
|
391
|
-
// Check if this is ProvidersConfig (has 'stream' property)
|
|
392
|
-
if ('stream' in connection && connection.stream) {
|
|
393
|
-
// Long-form: apply to the stream sub-config
|
|
394
|
-
connection.stream.retryPolicy = retryPolicy;
|
|
395
|
-
}
|
|
396
|
-
else {
|
|
397
|
-
// Short-form: apply directly to the connection
|
|
398
|
-
connection.retryPolicy = retryPolicy;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
194
|
// ************* PUB/SUB METHODS *************
|
|
402
195
|
/**
|
|
403
196
|
* Publishes a message to a workflow topic, starting a new job.
|
|
404
|
-
* Returns the job ID immediately (fire-and-forget).
|
|
405
|
-
* to block until the workflow completes.
|
|
197
|
+
* Returns the job ID immediately (fire-and-forget).
|
|
406
198
|
*
|
|
407
|
-
* @
|
|
408
|
-
*
|
|
409
|
-
*
|
|
410
|
-
* id: 'ORD-123',
|
|
411
|
-
* amount: 99.99,
|
|
412
|
-
* });
|
|
413
|
-
* console.log(`Started job ${jobId}`);
|
|
414
|
-
* ```
|
|
199
|
+
* @param topic - The workflow topic (must match a deployed graph's `subscribes`).
|
|
200
|
+
* @param data - Input data for the workflow.
|
|
201
|
+
* @returns The new job ID.
|
|
415
202
|
*/
|
|
416
203
|
async pub(topic, data = {}, context, extended) {
|
|
417
|
-
return
|
|
204
|
+
return PubSub.pub(this, topic, data, context, extended);
|
|
418
205
|
}
|
|
419
206
|
/**
|
|
420
|
-
* Subscribes to all output and interim emissions from a
|
|
421
|
-
* workflow topic. The callback fires each time a job on that topic
|
|
422
|
-
* completes or emits an interim result.
|
|
207
|
+
* Subscribes to all output and interim emissions from a workflow topic.
|
|
423
208
|
*
|
|
424
|
-
* @
|
|
425
|
-
*
|
|
426
|
-
* await hotMesh.sub('order.fulfilled', (topic, message) => {
|
|
427
|
-
* console.log(`Order completed:`, message.data);
|
|
428
|
-
* });
|
|
429
|
-
* ```
|
|
209
|
+
* @param topic - The topic to subscribe to.
|
|
210
|
+
* @param callback - Invoked with each job output or interim emission.
|
|
430
211
|
*/
|
|
431
212
|
async sub(topic, callback) {
|
|
432
|
-
return
|
|
213
|
+
return PubSub.sub(this, topic, callback);
|
|
433
214
|
}
|
|
434
215
|
/**
|
|
435
|
-
* Unsubscribes from a
|
|
436
|
-
* with `sub()`.
|
|
216
|
+
* Unsubscribes from a workflow topic previously registered with {@link sub}.
|
|
437
217
|
*/
|
|
438
218
|
async unsub(topic) {
|
|
439
|
-
return
|
|
219
|
+
return PubSub.unsub(this, topic);
|
|
440
220
|
}
|
|
441
221
|
/**
|
|
442
222
|
* Subscribes to workflow emissions matching a wildcard pattern.
|
|
443
|
-
* Useful for monitoring an entire domain of workflows at once.
|
|
444
223
|
*
|
|
445
|
-
* @
|
|
446
|
-
*
|
|
447
|
-
* // Listen to all order-related workflow completions
|
|
448
|
-
* await hotMesh.psub('order.*', (topic, message) => {
|
|
449
|
-
* console.log(`${topic} completed:`, message.data);
|
|
450
|
-
* });
|
|
451
|
-
* ```
|
|
224
|
+
* @param wild - The wildcard pattern (e.g., `'order.*'`).
|
|
225
|
+
* @param callback - Invoked with each matching emission.
|
|
452
226
|
*/
|
|
453
227
|
async psub(wild, callback) {
|
|
454
|
-
return
|
|
228
|
+
return PubSub.psub(this, wild, callback);
|
|
455
229
|
}
|
|
456
230
|
/**
|
|
457
|
-
* Unsubscribes from a wildcard pattern previously registered with
|
|
231
|
+
* Unsubscribes from a wildcard pattern previously registered with {@link psub}.
|
|
458
232
|
*/
|
|
459
233
|
async punsub(wild) {
|
|
460
|
-
return
|
|
234
|
+
return PubSub.punsub(this, wild);
|
|
461
235
|
}
|
|
462
236
|
/**
|
|
463
|
-
* Publishes a message
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
* after receiving the result.
|
|
237
|
+
* Publishes a message and blocks until the workflow completes,
|
|
238
|
+
* returning the final job output. Combines {@link pub} + {@link sub}
|
|
239
|
+
* into a single request/response call.
|
|
467
240
|
*
|
|
468
|
-
* @
|
|
469
|
-
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
* });
|
|
474
|
-
* console.log('Order result:', result.data);
|
|
475
|
-
* ```
|
|
241
|
+
* @param topic - The workflow topic.
|
|
242
|
+
* @param data - Input data for the workflow.
|
|
243
|
+
* @param context - Optional job state context.
|
|
244
|
+
* @param timeout - Optional timeout in milliseconds.
|
|
245
|
+
* @returns The completed job output.
|
|
476
246
|
*/
|
|
477
247
|
async pubsub(topic, data = {}, context, timeout) {
|
|
478
|
-
return
|
|
248
|
+
return PubSub.pubsub(this, topic, data, context, timeout);
|
|
479
249
|
}
|
|
480
250
|
/**
|
|
481
251
|
* Adds a transition message to the workstream, resuming Leg 2 of a
|
|
482
|
-
* paused reentrant activity
|
|
483
|
-
*
|
|
484
|
-
* advanced use cases like custom activity implementations.
|
|
252
|
+
* paused reentrant activity.
|
|
253
|
+
* @private
|
|
485
254
|
*/
|
|
486
255
|
async add(streamData) {
|
|
487
|
-
return
|
|
256
|
+
return PubSub.add(this, streamData);
|
|
488
257
|
}
|
|
489
258
|
// ************* QUORUM METHODS *************
|
|
490
259
|
/**
|
|
491
|
-
* Broadcasts a
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
260
|
+
* Broadcasts a PING to all connected engines and workers via
|
|
261
|
+
* LISTEN/NOTIFY and collects their profiles. Returns one
|
|
262
|
+
* {@link QuorumProfile} per responding instance, including
|
|
263
|
+
* cumulative message `counts`, `error_count`, `stream_depth`,
|
|
264
|
+
* `throttle` state, and host-level `system` health (memory/CPU).
|
|
495
265
|
*
|
|
496
|
-
* Use this for
|
|
266
|
+
* Use this for health checks, topology discovery, and throughput
|
|
267
|
+
* monitoring across the mesh.
|
|
497
268
|
*
|
|
498
|
-
* @
|
|
499
|
-
*
|
|
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
|
-
* ```
|
|
269
|
+
* @param delay - Time in ms to wait for PONG responses (default: quorum config).
|
|
270
|
+
* @returns One profile per responding engine/worker instance.
|
|
508
271
|
*/
|
|
509
272
|
async rollCall(delay) {
|
|
510
|
-
return
|
|
273
|
+
return Quorum.rollCall(this, delay);
|
|
511
274
|
}
|
|
512
275
|
/**
|
|
513
|
-
* Broadcasts a throttle command to
|
|
514
|
-
*
|
|
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.
|
|
520
|
-
*
|
|
521
|
-
* ## Targeting
|
|
522
|
-
*
|
|
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 |
|
|
529
|
-
*
|
|
530
|
-
* ## Special Values
|
|
531
|
-
*
|
|
532
|
-
* | Value | Effect |
|
|
533
|
-
* |--------|--------|
|
|
534
|
-
* | `0` | Resume normal processing (remove throttle) |
|
|
535
|
-
* | `-1` | Pause indefinitely (emergency stop) |
|
|
536
|
-
* | `500` | 500ms delay between messages |
|
|
276
|
+
* Broadcasts a throttle command to all instances. Use to slow down or
|
|
277
|
+
* pause message consumption across the mesh.
|
|
537
278
|
*
|
|
538
|
-
* @
|
|
539
|
-
*
|
|
540
|
-
* // Emergency stop: pause the entire mesh
|
|
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
|
-
* });
|
|
558
|
-
* ```
|
|
279
|
+
* @param options - Throttle rate in ms (0 = no throttle, -1 = pause indefinitely).
|
|
280
|
+
* Optionally scope by `guid` (single instance) or `topic` (single worker).
|
|
559
281
|
*/
|
|
560
282
|
async throttle(options) {
|
|
561
|
-
|
|
562
|
-
if (options.throttle === -1) {
|
|
563
|
-
throttle = enums_1.MAX_DELAY;
|
|
564
|
-
}
|
|
565
|
-
else {
|
|
566
|
-
throttle = options.throttle;
|
|
567
|
-
}
|
|
568
|
-
if (!Number.isInteger(throttle) || throttle < 0 || throttle > enums_1.MAX_DELAY) {
|
|
569
|
-
throw new Error(`Throttle must be a non-negative integer and not exceed ${enums_1.MAX_DELAY} ms; send -1 to throttle indefinitely`);
|
|
570
|
-
}
|
|
571
|
-
const throttleMessage = {
|
|
572
|
-
type: 'throttle',
|
|
573
|
-
throttle: throttle,
|
|
574
|
-
};
|
|
575
|
-
if (options.guid) {
|
|
576
|
-
throttleMessage.guid = options.guid;
|
|
577
|
-
}
|
|
578
|
-
if (options.topic !== undefined) {
|
|
579
|
-
throttleMessage.topic = options.topic;
|
|
580
|
-
}
|
|
581
|
-
await this.engine.store.setThrottleRate(throttleMessage);
|
|
582
|
-
return await this.quorum?.pub(throttleMessage);
|
|
283
|
+
return Quorum.throttle(this, options);
|
|
583
284
|
}
|
|
584
285
|
/**
|
|
585
|
-
* Publishes a message to every
|
|
586
|
-
*
|
|
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
|
-
* ```
|
|
286
|
+
* Publishes a custom message to every instance via the quorum channel.
|
|
287
|
+
* Register a listener with {@link subQuorum} to receive these messages.
|
|
606
288
|
*/
|
|
607
289
|
async pubQuorum(quorumMessage) {
|
|
608
|
-
return
|
|
290
|
+
return Quorum.pubQuorum(this, quorumMessage);
|
|
609
291
|
}
|
|
610
292
|
/**
|
|
611
|
-
* Subscribes to the quorum channel
|
|
612
|
-
*
|
|
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
|
-
* ```
|
|
293
|
+
* Subscribes to the quorum channel to receive system messages (version
|
|
294
|
+
* activations, throttle commands, roll calls) and custom user messages.
|
|
687
295
|
*/
|
|
688
296
|
async subQuorum(callback) {
|
|
689
|
-
return
|
|
297
|
+
return Quorum.subQuorum(this, callback);
|
|
690
298
|
}
|
|
691
299
|
/**
|
|
692
|
-
* Unsubscribes a callback previously registered with
|
|
300
|
+
* Unsubscribes a callback previously registered with {@link subQuorum}.
|
|
693
301
|
*/
|
|
694
302
|
async unsubQuorum(callback) {
|
|
695
|
-
return
|
|
303
|
+
return Quorum.unsubQuorum(this, callback);
|
|
696
304
|
}
|
|
697
305
|
// ************* LIFECYCLE METHODS *************
|
|
698
|
-
/**
|
|
699
|
-
* Preview changes and provide an analysis of risk
|
|
700
|
-
* prior to deployment
|
|
701
|
-
* @private
|
|
702
|
-
*/
|
|
306
|
+
/** @private */
|
|
703
307
|
async plan(path) {
|
|
704
|
-
return
|
|
308
|
+
return Deployment.plan(this, path);
|
|
705
309
|
}
|
|
706
310
|
/**
|
|
707
|
-
* Deploys a YAML workflow graph to Postgres.
|
|
708
|
-
*
|
|
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
|
-
* `);
|
|
311
|
+
* Deploys a YAML workflow graph to Postgres. The graph is stored but
|
|
312
|
+
* remains **inactive** until {@link activate} is called.
|
|
731
313
|
*
|
|
732
|
-
*
|
|
733
|
-
*
|
|
734
|
-
* ```
|
|
314
|
+
* @param pathOrYAML - A file path or raw YAML string defining the workflow graph.
|
|
315
|
+
* @returns The parsed manifest with version and graph metadata.
|
|
735
316
|
*/
|
|
736
317
|
async deploy(pathOrYAML) {
|
|
737
|
-
return
|
|
318
|
+
return Deployment.deploy(this, pathOrYAML);
|
|
738
319
|
}
|
|
739
320
|
/**
|
|
740
|
-
* Activates a deployed version across
|
|
741
|
-
* coordinates a synchronized switch
|
|
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.
|
|
752
|
-
*
|
|
753
|
-
* @example
|
|
754
|
-
* ```typescript
|
|
755
|
-
* // Simple activation
|
|
756
|
-
* await hotMesh.activate('2');
|
|
321
|
+
* Activates a previously deployed version across all connected instances.
|
|
322
|
+
* The quorum coordinates a synchronized version switch so every engine
|
|
323
|
+
* and worker transitions together.
|
|
757
324
|
*
|
|
758
|
-
*
|
|
759
|
-
*
|
|
760
|
-
* await hotMesh.activate('2', 2000); // activate with 2s consensus window
|
|
761
|
-
* await hotMesh.throttle({ throttle: 0 }); // resume full speed
|
|
762
|
-
* ```
|
|
325
|
+
* @param version - The version string to activate (must match a deployed graph).
|
|
326
|
+
* @param delay - Optional delay in ms before activation takes effect.
|
|
763
327
|
*/
|
|
764
328
|
async activate(version, delay) {
|
|
765
|
-
return
|
|
329
|
+
return Deployment.activate(this, version, delay);
|
|
766
330
|
}
|
|
331
|
+
// ************* JOB METHODS *************
|
|
767
332
|
/**
|
|
768
|
-
* Exports the full job state
|
|
769
|
-
*
|
|
770
|
-
*
|
|
333
|
+
* Exports the full job state (data, metadata, activity results) as
|
|
334
|
+
* a structured JSON object.
|
|
335
|
+
*
|
|
336
|
+
* @param jobId - The job/workflow ID.
|
|
337
|
+
* @param options - Export options (e.g., include activity details).
|
|
771
338
|
*/
|
|
772
339
|
async export(jobId, options = {}) {
|
|
773
|
-
return
|
|
340
|
+
return Jobs.exportJob(this, jobId, options);
|
|
774
341
|
}
|
|
775
342
|
/**
|
|
776
|
-
* Returns all raw key-value pairs
|
|
777
|
-
*
|
|
778
|
-
* user data. Prefer `getState()` for structured output.
|
|
343
|
+
* Returns all raw key-value pairs from a job's HASH record in Postgres.
|
|
344
|
+
* Useful for debugging or low-level inspection.
|
|
779
345
|
*/
|
|
780
346
|
async getRaw(jobId) {
|
|
781
|
-
return
|
|
347
|
+
return Jobs.getRaw(this, jobId);
|
|
782
348
|
}
|
|
783
|
-
/**
|
|
784
|
-
* Reporter-related method to get the status of a job
|
|
785
|
-
* @private
|
|
786
|
-
*/
|
|
349
|
+
/** @private */
|
|
787
350
|
async getStats(topic, query) {
|
|
788
|
-
return
|
|
351
|
+
return Jobs.getStats(this, topic, query);
|
|
789
352
|
}
|
|
790
353
|
/**
|
|
791
|
-
* Returns the numeric status
|
|
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) |
|
|
354
|
+
* Returns the numeric status code for a job: `0` = completed,
|
|
355
|
+
* positive = still running, negative = interrupted/errored.
|
|
799
356
|
*/
|
|
800
357
|
async getStatus(jobId) {
|
|
801
|
-
return
|
|
358
|
+
return Jobs.getStatus(this, jobId);
|
|
802
359
|
}
|
|
803
360
|
/**
|
|
804
|
-
* Returns the structured job state (data
|
|
805
|
-
*
|
|
361
|
+
* Returns the structured job state (data + metadata). For a completed
|
|
362
|
+
* job this is the final output; for a running job it reflects the
|
|
363
|
+
* latest persisted state.
|
|
806
364
|
*
|
|
807
|
-
* @
|
|
808
|
-
*
|
|
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
|
-
* ```
|
|
365
|
+
* @param topic - The workflow topic.
|
|
366
|
+
* @param jobId - The job/workflow ID.
|
|
813
367
|
*/
|
|
814
368
|
async getState(topic, jobId) {
|
|
815
|
-
return
|
|
369
|
+
return Jobs.getState(this, topic, jobId);
|
|
816
370
|
}
|
|
817
371
|
/**
|
|
818
372
|
* Returns specific searchable fields from a job's HASH record.
|
|
819
|
-
* Pass field names to retrieve; use `":"` to read the reserved
|
|
820
|
-
* status field.
|
|
821
373
|
*
|
|
822
|
-
* @
|
|
823
|
-
*
|
|
824
|
-
* const fields = ['orderId', 'status', '":"'];
|
|
825
|
-
* const data = await hotMesh.getQueryState(jobId, fields);
|
|
826
|
-
* // => { orderId: 'ORD-123', status: 'paid', ':': '0' }
|
|
827
|
-
* ```
|
|
374
|
+
* @param jobId - The job/workflow ID.
|
|
375
|
+
* @param fields - The field names to retrieve.
|
|
828
376
|
*/
|
|
829
377
|
async getQueryState(jobId, fields) {
|
|
830
|
-
return
|
|
378
|
+
return Jobs.getQueryState(this, jobId, fields);
|
|
831
379
|
}
|
|
832
|
-
/**
|
|
833
|
-
* @private
|
|
834
|
-
*/
|
|
380
|
+
/** @private */
|
|
835
381
|
async getIds(topic, query, queryFacets = []) {
|
|
836
|
-
return
|
|
382
|
+
return Jobs.getIds(this, topic, query, queryFacets);
|
|
837
383
|
}
|
|
838
|
-
/**
|
|
839
|
-
* @private
|
|
840
|
-
*/
|
|
384
|
+
/** @private */
|
|
841
385
|
async resolveQuery(topic, query) {
|
|
842
|
-
return
|
|
386
|
+
return Jobs.resolveQuery(this, topic, query);
|
|
843
387
|
}
|
|
844
388
|
/**
|
|
845
|
-
*
|
|
846
|
-
*
|
|
847
|
-
*
|
|
389
|
+
* Immediately terminates a running job. The job is marked as
|
|
390
|
+
* interrupted and its HASH is expired. Unlike {@link cancel}, this
|
|
391
|
+
* does not give the workflow a chance to run cleanup code.
|
|
848
392
|
*
|
|
849
|
-
* @
|
|
850
|
-
*
|
|
851
|
-
*
|
|
852
|
-
* reason: 'Customer cancelled',
|
|
853
|
-
* descend: true, // also interrupt child workflows
|
|
854
|
-
* });
|
|
855
|
-
* ```
|
|
393
|
+
* @param topic - The workflow topic.
|
|
394
|
+
* @param jobId - The job/workflow ID.
|
|
395
|
+
* @param options - Optional interrupt configuration.
|
|
856
396
|
*/
|
|
857
397
|
async interrupt(topic, jobId, options = {}) {
|
|
858
|
-
return
|
|
398
|
+
return Jobs.interrupt(this, topic, jobId, options);
|
|
859
399
|
}
|
|
860
400
|
/**
|
|
861
|
-
*
|
|
862
|
-
*
|
|
863
|
-
*
|
|
401
|
+
* Requests cooperative cancellation of a running job. Sets a durable
|
|
402
|
+
* cancel flag; the workflow detects it at its next durable operation
|
|
403
|
+
* and throws `CancelledFailure`, which can be caught for cleanup.
|
|
404
|
+
*
|
|
405
|
+
* @param jobId - The job/workflow ID.
|
|
864
406
|
*/
|
|
865
|
-
async
|
|
866
|
-
|
|
407
|
+
async cancel(jobId) {
|
|
408
|
+
return Jobs.cancel(this, jobId);
|
|
867
409
|
}
|
|
868
410
|
/**
|
|
869
|
-
*
|
|
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.
|
|
873
|
-
*
|
|
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 Durable 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
|
-
* ```
|
|
411
|
+
* Immediately deletes a completed job's HASH record from Postgres.
|
|
896
412
|
*/
|
|
897
|
-
async
|
|
898
|
-
return
|
|
413
|
+
async scrub(jobId) {
|
|
414
|
+
return Jobs.scrub(this, jobId);
|
|
899
415
|
}
|
|
900
416
|
/**
|
|
901
|
-
*
|
|
902
|
-
*
|
|
903
|
-
* a batch of workflows waiting on the same external event.
|
|
417
|
+
* Sends a signal to a paused workflow, delivering data and resuming
|
|
418
|
+
* execution. Pairs with `condition()` in the Durable workflow API.
|
|
904
419
|
*
|
|
905
|
-
* @
|
|
420
|
+
* @param topic - The signal topic.
|
|
421
|
+
* @param data - Signal payload.
|
|
906
422
|
*/
|
|
423
|
+
async signal(topic, data, status, code) {
|
|
424
|
+
return Jobs.signal(this, topic, data, status, code);
|
|
425
|
+
}
|
|
426
|
+
/** @private */
|
|
907
427
|
async signalAll(hookTopic, data, query, queryFacets = []) {
|
|
908
|
-
return
|
|
428
|
+
return Jobs.signalAll(this, hookTopic, data, query, queryFacets);
|
|
909
429
|
}
|
|
430
|
+
// ************* STATIC LIFECYCLE *************
|
|
910
431
|
/**
|
|
911
|
-
* Stops **all** HotMesh instances in the current process
|
|
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
|
-
* ```
|
|
432
|
+
* Stops **all** HotMesh instances in the current process.
|
|
922
433
|
*/
|
|
923
434
|
static async stop() {
|
|
924
435
|
if (!this.disconnecting) {
|
|
@@ -928,9 +439,8 @@ class HotMesh {
|
|
|
928
439
|
}
|
|
929
440
|
}
|
|
930
441
|
/**
|
|
931
|
-
* Stops this specific HotMesh instance —
|
|
932
|
-
*
|
|
933
|
-
* same process are unaffected.
|
|
442
|
+
* Stops this specific HotMesh instance — leaves the quorum and
|
|
443
|
+
* stops all workers. Does not affect other instances in the process.
|
|
934
444
|
*/
|
|
935
445
|
stop() {
|
|
936
446
|
this.engine?.taskService.cancelCleanup();
|
|
@@ -946,6 +456,45 @@ class HotMesh {
|
|
|
946
456
|
async compress(terms) {
|
|
947
457
|
return await this.engine?.compress(terms);
|
|
948
458
|
}
|
|
459
|
+
// ************* CODEC *************
|
|
460
|
+
/**
|
|
461
|
+
* Register a global payload codec for encoding/decoding serialized
|
|
462
|
+
* object data at rest. Once registered, all object values flowing
|
|
463
|
+
* through the serializer are stored as `/b{encoded}` instead of
|
|
464
|
+
* `/s{json}`. Use this for encryption, compression, or custom encoding.
|
|
465
|
+
*
|
|
466
|
+
* The codec is global — it applies to all HotMesh and Durable instances
|
|
467
|
+
* in the process. Pass `null` to remove a previously registered codec.
|
|
468
|
+
*
|
|
469
|
+
* **Constraints:** The codec must be synchronous and its output must be
|
|
470
|
+
* a valid UTF-8 string. Use base64 encoding for binary output.
|
|
471
|
+
*
|
|
472
|
+
* @example
|
|
473
|
+
* ```typescript
|
|
474
|
+
* import { HotMesh } from '@hotmeshio/hotmesh';
|
|
475
|
+
*
|
|
476
|
+
* HotMesh.registerCodec({
|
|
477
|
+
* encode(json) { return Buffer.from(json).toString('base64'); },
|
|
478
|
+
* decode(encoded) { return Buffer.from(encoded, 'base64').toString('utf8'); },
|
|
479
|
+
* });
|
|
480
|
+
* ```
|
|
481
|
+
*/
|
|
482
|
+
static registerCodec(codec) {
|
|
483
|
+
serializer_1.SerializerService.registerCodec(codec);
|
|
484
|
+
}
|
|
949
485
|
}
|
|
950
486
|
exports.HotMesh = HotMesh;
|
|
951
487
|
HotMesh.disconnecting = false;
|
|
488
|
+
// ************* WORKER CREDENTIALS *************
|
|
489
|
+
/**
|
|
490
|
+
* Provision a scoped Postgres role for a worker. The role can only
|
|
491
|
+
* dequeue, ack, and respond on its assigned stream names via stored
|
|
492
|
+
* procedures — zero direct table access.
|
|
493
|
+
*/
|
|
494
|
+
HotMesh.provisionWorkerRole = WorkerCredentials.provisionWorkerRole;
|
|
495
|
+
/** Rotate a secured worker role's password. */
|
|
496
|
+
HotMesh.rotateWorkerPassword = WorkerCredentials.rotateWorkerPassword;
|
|
497
|
+
/** Revoke a secured worker role (disables login). */
|
|
498
|
+
HotMesh.revokeWorkerRole = WorkerCredentials.revokeWorkerRole;
|
|
499
|
+
/** List all provisioned secured worker roles. */
|
|
500
|
+
HotMesh.listWorkerRoles = WorkerCredentials.listWorkerRoles;
|