@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.
Files changed (202) hide show
  1. package/README.md +18 -22
  2. package/build/modules/enums.d.ts +60 -5
  3. package/build/modules/enums.js +62 -7
  4. package/build/modules/errors.d.ts +15 -2
  5. package/build/modules/errors.js +17 -1
  6. package/build/modules/storage.d.ts +1 -0
  7. package/build/modules/storage.js +2 -1
  8. package/build/package.json +8 -2
  9. package/build/services/activities/activity/context.d.ts +22 -0
  10. package/build/services/activities/activity/context.js +76 -0
  11. package/build/services/activities/activity/index.d.ts +116 -0
  12. package/build/services/activities/activity/index.js +299 -0
  13. package/build/services/activities/activity/mapping.d.ts +12 -0
  14. package/build/services/activities/activity/mapping.js +63 -0
  15. package/build/services/activities/activity/process.d.ts +28 -0
  16. package/build/services/activities/activity/process.js +100 -0
  17. package/build/services/activities/activity/protocol.d.ts +39 -0
  18. package/build/services/activities/activity/protocol.js +151 -0
  19. package/build/services/activities/activity/state.d.ts +40 -0
  20. package/build/services/activities/activity/state.js +143 -0
  21. package/build/services/activities/activity/transition.d.ts +23 -0
  22. package/build/services/activities/activity/transition.js +71 -0
  23. package/build/services/activities/activity/verify.d.ts +22 -0
  24. package/build/services/activities/activity/verify.js +85 -0
  25. package/build/services/activities/await.d.ts +1 -4
  26. package/build/services/activities/await.js +2 -36
  27. package/build/services/activities/cycle.d.ts +1 -11
  28. package/build/services/activities/cycle.js +3 -46
  29. package/build/services/activities/hook.d.ts +2 -11
  30. package/build/services/activities/hook.js +30 -50
  31. package/build/services/activities/interrupt.d.ts +2 -4
  32. package/build/services/activities/interrupt.js +4 -38
  33. package/build/services/activities/signal.d.ts +1 -11
  34. package/build/services/activities/signal.js +3 -48
  35. package/build/services/activities/trigger.d.ts +1 -3
  36. package/build/services/activities/trigger.js +0 -3
  37. package/build/services/activities/worker.d.ts +3 -6
  38. package/build/services/activities/worker.js +4 -40
  39. package/build/services/connector/factory.d.ts +6 -0
  40. package/build/services/connector/factory.js +24 -0
  41. package/build/services/dba/index.d.ts +14 -4
  42. package/build/services/dba/index.js +57 -18
  43. package/build/services/durable/activity.d.ts +30 -0
  44. package/build/services/durable/activity.js +46 -0
  45. package/build/services/durable/client.d.ts +26 -31
  46. package/build/services/durable/client.js +26 -31
  47. package/build/services/durable/connection.d.ts +13 -7
  48. package/build/services/durable/connection.js +13 -7
  49. package/build/services/durable/exporter.d.ts +2 -2
  50. package/build/services/durable/exporter.js +27 -12
  51. package/build/services/durable/handle.d.ts +59 -41
  52. package/build/services/durable/handle.js +61 -41
  53. package/build/services/durable/index.d.ts +152 -283
  54. package/build/services/durable/index.js +161 -289
  55. package/build/services/durable/interceptor.d.ts +43 -33
  56. package/build/services/durable/interceptor.js +59 -39
  57. package/build/services/durable/schemas/factory.d.ts +2 -3
  58. package/build/services/durable/schemas/factory.js +180 -30
  59. package/build/services/durable/telemetry.d.ts +80 -0
  60. package/build/services/durable/telemetry.js +137 -0
  61. package/build/services/durable/worker.d.ts +100 -21
  62. package/build/services/durable/worker.js +314 -60
  63. package/build/services/durable/workflow/all.d.ts +1 -1
  64. package/build/services/durable/workflow/all.js +1 -1
  65. package/build/services/durable/workflow/cancellationScope.d.ts +104 -0
  66. package/build/services/durable/workflow/cancellationScope.js +139 -0
  67. package/build/services/durable/workflow/common.d.ts +5 -4
  68. package/build/services/durable/workflow/common.js +6 -1
  69. package/build/services/durable/workflow/{waitFor.d.ts → condition.d.ts} +9 -8
  70. package/build/services/durable/workflow/{waitFor.js → condition.js} +44 -11
  71. package/build/services/durable/workflow/continueAsNew.d.ts +65 -0
  72. package/build/services/durable/workflow/continueAsNew.js +92 -0
  73. package/build/services/durable/workflow/didRun.d.ts +2 -2
  74. package/build/services/durable/workflow/didRun.js +4 -4
  75. package/build/services/durable/workflow/enrich.d.ts +5 -0
  76. package/build/services/durable/workflow/enrich.js +5 -0
  77. package/build/services/durable/workflow/entityMethods.d.ts +7 -0
  78. package/build/services/durable/workflow/entityMethods.js +7 -0
  79. package/build/services/durable/workflow/execHook.js +3 -3
  80. package/build/services/durable/workflow/execHookBatch.js +2 -2
  81. package/build/services/durable/workflow/{execChild.d.ts → executeChild.d.ts} +4 -40
  82. package/build/services/durable/workflow/{execChild.js → executeChild.js} +36 -45
  83. package/build/services/durable/workflow/hook.d.ts +1 -1
  84. package/build/services/durable/workflow/hook.js +4 -3
  85. package/build/services/durable/workflow/index.d.ts +45 -50
  86. package/build/services/durable/workflow/index.js +46 -51
  87. package/build/services/durable/workflow/interruption.d.ts +7 -6
  88. package/build/services/durable/workflow/interruption.js +11 -7
  89. package/build/services/durable/workflow/patched.d.ts +72 -0
  90. package/build/services/durable/workflow/patched.js +110 -0
  91. package/build/services/durable/workflow/proxyActivities.d.ts +7 -7
  92. package/build/services/durable/workflow/proxyActivities.js +51 -15
  93. package/build/services/durable/workflow/searchMethods.d.ts +7 -0
  94. package/build/services/durable/workflow/searchMethods.js +7 -0
  95. package/build/services/durable/workflow/signal.d.ts +4 -4
  96. package/build/services/durable/workflow/signal.js +4 -4
  97. package/build/services/durable/workflow/{sleepFor.d.ts → sleep.d.ts} +7 -7
  98. package/build/services/durable/workflow/{sleepFor.js → sleep.js} +39 -10
  99. package/build/services/durable/workflow/terminate.d.ts +55 -0
  100. package/build/services/durable/workflow/{interrupt.js → terminate.js} +21 -21
  101. package/build/services/durable/workflow/trace.js +2 -2
  102. package/build/services/durable/workflow/uuid4.d.ts +14 -0
  103. package/build/services/durable/workflow/uuid4.js +39 -0
  104. package/build/services/durable/workflow/{context.d.ts → workflowInfo.d.ts} +5 -5
  105. package/build/services/durable/workflow/{context.js → workflowInfo.js} +7 -7
  106. package/build/services/engine/compiler.d.ts +19 -0
  107. package/build/services/engine/compiler.js +20 -0
  108. package/build/services/engine/completion.d.ts +46 -0
  109. package/build/services/engine/completion.js +145 -0
  110. package/build/services/engine/dispatch.d.ts +24 -0
  111. package/build/services/engine/dispatch.js +98 -0
  112. package/build/services/engine/index.d.ts +49 -81
  113. package/build/services/engine/index.js +175 -573
  114. package/build/services/engine/init.d.ts +42 -0
  115. package/build/services/engine/init.js +74 -0
  116. package/build/services/engine/pubsub.d.ts +50 -0
  117. package/build/services/engine/pubsub.js +118 -0
  118. package/build/services/engine/reporting.d.ts +20 -0
  119. package/build/services/engine/reporting.js +38 -0
  120. package/build/services/engine/schema.d.ts +23 -0
  121. package/build/services/engine/schema.js +62 -0
  122. package/build/services/engine/signal.d.ts +57 -0
  123. package/build/services/engine/signal.js +117 -0
  124. package/build/services/engine/state.d.ts +35 -0
  125. package/build/services/engine/state.js +61 -0
  126. package/build/services/engine/version.d.ts +31 -0
  127. package/build/services/engine/version.js +73 -0
  128. package/build/services/hotmesh/deployment.d.ts +21 -0
  129. package/build/services/hotmesh/deployment.js +25 -0
  130. package/build/services/hotmesh/index.d.ts +142 -533
  131. package/build/services/hotmesh/index.js +223 -674
  132. package/build/services/hotmesh/init.d.ts +42 -0
  133. package/build/services/hotmesh/init.js +93 -0
  134. package/build/services/hotmesh/jobs.d.ts +67 -0
  135. package/build/services/hotmesh/jobs.js +99 -0
  136. package/build/services/hotmesh/pubsub.d.ts +38 -0
  137. package/build/services/hotmesh/pubsub.js +54 -0
  138. package/build/services/hotmesh/quorum.d.ts +30 -0
  139. package/build/services/hotmesh/quorum.js +62 -0
  140. package/build/services/hotmesh/validation.d.ts +6 -0
  141. package/build/services/hotmesh/validation.js +28 -0
  142. package/build/services/quorum/index.js +1 -0
  143. package/build/services/router/consumption/index.d.ts +11 -5
  144. package/build/services/router/consumption/index.js +24 -17
  145. package/build/services/router/error-handling/index.d.ts +2 -2
  146. package/build/services/router/error-handling/index.js +14 -14
  147. package/build/services/router/index.d.ts +1 -1
  148. package/build/services/router/index.js +2 -2
  149. package/build/services/serializer/index.d.ts +22 -0
  150. package/build/services/serializer/index.js +39 -1
  151. package/build/services/store/index.d.ts +1 -0
  152. package/build/services/store/providers/postgres/exporter-sql.d.ts +2 -2
  153. package/build/services/store/providers/postgres/exporter-sql.js +4 -4
  154. package/build/services/store/providers/postgres/kvtables.js +7 -6
  155. package/build/services/store/providers/postgres/kvtypes/hash/basic.js +67 -52
  156. package/build/services/store/providers/postgres/kvtypes/hash/jsonb.js +87 -72
  157. package/build/services/store/providers/postgres/kvtypes/hash/udata.js +106 -79
  158. package/build/services/store/providers/postgres/kvtypes/hash/utils.d.ts +16 -0
  159. package/build/services/store/providers/postgres/kvtypes/hash/utils.js +29 -16
  160. package/build/services/store/providers/postgres/postgres.d.ts +1 -0
  161. package/build/services/store/providers/postgres/postgres.js +14 -4
  162. package/build/services/stream/factory.d.ts +3 -1
  163. package/build/services/stream/factory.js +2 -2
  164. package/build/services/stream/index.d.ts +1 -0
  165. package/build/services/stream/providers/nats/nats.d.ts +1 -0
  166. package/build/services/stream/providers/nats/nats.js +1 -0
  167. package/build/services/stream/providers/postgres/credentials.d.ts +56 -0
  168. package/build/services/stream/providers/postgres/credentials.js +129 -0
  169. package/build/services/stream/providers/postgres/kvtables.js +18 -0
  170. package/build/services/stream/providers/postgres/messages.js +7 -7
  171. package/build/services/stream/providers/postgres/notifications.js +16 -2
  172. package/build/services/stream/providers/postgres/postgres.d.ts +7 -0
  173. package/build/services/stream/providers/postgres/postgres.js +35 -4
  174. package/build/services/stream/providers/postgres/procedures.d.ts +21 -0
  175. package/build/services/stream/providers/postgres/procedures.js +213 -0
  176. package/build/services/stream/providers/postgres/secured.d.ts +34 -0
  177. package/build/services/stream/providers/postgres/secured.js +146 -0
  178. package/build/services/stream/providers/postgres/stats.d.ts +1 -0
  179. package/build/services/stream/providers/postgres/stats.js +1 -0
  180. package/build/services/stream/registry.d.ts +1 -1
  181. package/build/services/stream/registry.js +5 -2
  182. package/build/services/telemetry/index.d.ts +10 -1
  183. package/build/services/telemetry/index.js +40 -7
  184. package/build/services/worker/credentials.d.ts +51 -0
  185. package/build/services/worker/credentials.js +87 -0
  186. package/build/services/worker/index.d.ts +2 -2
  187. package/build/services/worker/index.js +7 -6
  188. package/build/types/codec.d.ts +84 -0
  189. package/build/types/codec.js +2 -0
  190. package/build/types/dba.d.ts +39 -3
  191. package/build/types/durable.d.ts +123 -25
  192. package/build/types/error.d.ts +10 -0
  193. package/build/types/exporter.d.ts +1 -1
  194. package/build/types/hotmesh.d.ts +67 -4
  195. package/build/types/index.d.ts +2 -1
  196. package/build/types/provider.d.ts +2 -2
  197. package/build/types/quorum.d.ts +35 -1
  198. package/build/types/stream.d.ts +12 -6
  199. package/package.json +8 -2
  200. package/build/services/activities/activity.d.ts +0 -192
  201. package/build/services/activities/activity.js +0 -786
  202. 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
- const worker_1 = require("../worker");
12
- const enums_1 = require("../../modules/enums");
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** — Temporal-style durable workflow functions.
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
- if (!namespace) {
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
- if (!appId?.match(/^[A-Za-z0-9-]+$/)) {
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
- * Instance initializer. Workers are configured
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
- * 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.
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
- * - **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.
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 instance.initEngine(config, instance.logger);
293
- await instance.initQuorum(config, instance.engine, instance.logger);
294
- await instance.doWork(config, instance.logger);
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). Use `pubsub`
405
- * to block until the workflow completes.
197
+ * Returns the job ID immediately (fire-and-forget).
406
198
  *
407
- * @example
408
- * ```typescript
409
- * const jobId = await hotMesh.pub('order.placed', {
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 await this.engine?.pub(topic, data, context, extended);
204
+ return PubSub.pub(this, topic, data, context, extended);
418
205
  }
419
206
  /**
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.
207
+ * Subscribes to all output and interim emissions from a workflow topic.
423
208
  *
424
- * @example
425
- * ```typescript
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 await this.engine?.sub(topic, callback);
213
+ return PubSub.sub(this, topic, callback);
433
214
  }
434
215
  /**
435
- * Unsubscribes from a single workflow topic previously registered
436
- * with `sub()`.
216
+ * Unsubscribes from a workflow topic previously registered with {@link sub}.
437
217
  */
438
218
  async unsub(topic) {
439
- return await this.engine?.unsub(topic);
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
- * @example
446
- * ```typescript
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 await this.engine?.psub(wild, callback);
228
+ return PubSub.psub(this, wild, callback);
455
229
  }
456
230
  /**
457
- * Unsubscribes from a wildcard pattern previously registered with `psub()`.
231
+ * Unsubscribes from a wildcard pattern previously registered with {@link psub}.
458
232
  */
459
233
  async punsub(wild) {
460
- return await this.engine?.punsub(wild);
234
+ return PubSub.punsub(this, wild);
461
235
  }
462
236
  /**
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.
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
- * @example
469
- * ```typescript
470
- * const result = await hotMesh.pubsub('order.placed', {
471
- * id: 'ORD-789',
472
- * amount: 49.99,
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 await this.engine?.pubsub(topic, data, context, timeout);
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 (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.
252
+ * paused reentrant activity.
253
+ * @private
485
254
  */
486
255
  async add(streamData) {
487
- return (await this.engine.add(streamData));
256
+ return PubSub.add(this, streamData);
488
257
  }
489
258
  // ************* QUORUM METHODS *************
490
259
  /**
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).
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 service discovery, health checks, and capacity planning.
266
+ * Use this for health checks, topology discovery, and throughput
267
+ * monitoring across the mesh.
497
268
  *
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
- * ```
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 await this.quorum?.rollCall(delay);
273
+ return Quorum.rollCall(this, delay);
511
274
  }
512
275
  /**
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.
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
- * @example
539
- * ```typescript
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
- let throttle;
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 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
- * ```
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 await this.quorum?.pub(quorumMessage);
290
+ return Quorum.pubQuorum(this, quorumMessage);
609
291
  }
610
292
  /**
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
- * ```
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 await this.quorum?.sub(callback);
297
+ return Quorum.subQuorum(this, callback);
690
298
  }
691
299
  /**
692
- * Unsubscribes a callback previously registered with `subQuorum()`.
300
+ * Unsubscribes a callback previously registered with {@link subQuorum}.
693
301
  */
694
302
  async unsubQuorum(callback) {
695
- return await this.quorum?.unsub(callback);
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 await this.engine?.plan(path);
308
+ return Deployment.plan(this, path);
705
309
  }
706
310
  /**
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
- * `);
311
+ * Deploys a YAML workflow graph to Postgres. The graph is stored but
312
+ * remains **inactive** until {@link activate} is called.
731
313
  *
732
- * // Deploy from a file path (resolves $ref references)
733
- * await hotMesh.deploy('./workflows/order.yaml');
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 await this.engine?.deploy(pathOrYAML);
318
+ return Deployment.deploy(this, pathOrYAML);
738
319
  }
739
320
  /**
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.
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
- * // 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
- * ```
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 await this.quorum?.activate(version, delay);
329
+ return Deployment.activate(this, version, delay);
766
330
  }
331
+ // ************* JOB METHODS *************
767
332
  /**
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.
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 await this.engine?.export(jobId, options);
340
+ return Jobs.exportJob(this, jobId, options);
774
341
  }
775
342
  /**
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.
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 await this.engine?.getRaw(jobId);
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 await this.engine?.getStats(topic, query);
351
+ return Jobs.getStats(this, topic, query);
789
352
  }
790
353
  /**
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) |
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 this.engine?.getStatus(jobId);
358
+ return Jobs.getStatus(this, jobId);
802
359
  }
803
360
  /**
804
- * Returns the structured job state (data and metadata) for a job,
805
- * scoped to the given workflow topic.
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
- * @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
- * ```
365
+ * @param topic - The workflow topic.
366
+ * @param jobId - The job/workflow ID.
813
367
  */
814
368
  async getState(topic, jobId) {
815
- return this.engine?.getState(topic, jobId);
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
- * @example
823
- * ```typescript
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 await this.engine?.getQueryState(jobId, fields);
378
+ return Jobs.getQueryState(this, jobId, fields);
831
379
  }
832
- /**
833
- * @private
834
- */
380
+ /** @private */
835
381
  async getIds(topic, query, queryFacets = []) {
836
- return await this.engine?.getIds(topic, query, queryFacets);
382
+ return Jobs.getIds(this, topic, query, queryFacets);
837
383
  }
838
- /**
839
- * @private
840
- */
384
+ /** @private */
841
385
  async resolveQuery(topic, query) {
842
- return await this.engine?.resolveQuery(topic, query);
386
+ return Jobs.resolveQuery(this, topic, query);
843
387
  }
844
388
  /**
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.
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
- * @example
850
- * ```typescript
851
- * await hotMesh.interrupt('order.placed', jobId, {
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 await this.engine?.interrupt(topic, jobId, options);
398
+ return Jobs.interrupt(this, topic, jobId, options);
859
399
  }
860
400
  /**
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.
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 scrub(jobId) {
866
- await this.engine?.scrub(jobId);
407
+ async cancel(jobId) {
408
+ return Jobs.cancel(this, jobId);
867
409
  }
868
410
  /**
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.
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 signal(topic, data, status, code) {
898
- return await this.engine?.signal(topic, data, status, code);
413
+ async scrub(jobId) {
414
+ return Jobs.scrub(this, jobId);
899
415
  }
900
416
  /**
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.
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
- * @private
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 await this.engine?.signalAll(hookTopic, data, query, queryFacets);
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 — 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
- * ```
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 — its engine, quorum
932
- * membership, and all attached workers. Other instances in the
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;