@hotmeshio/hotmesh 0.13.0 → 0.14.1

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 (194) 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 -3
  5. package/build/modules/errors.js +17 -2
  6. package/build/package.json +6 -1
  7. package/build/services/activities/activity/context.d.ts +22 -0
  8. package/build/services/activities/activity/context.js +76 -0
  9. package/build/services/activities/activity/index.d.ts +116 -0
  10. package/build/services/activities/activity/index.js +299 -0
  11. package/build/services/activities/activity/mapping.d.ts +12 -0
  12. package/build/services/activities/activity/mapping.js +63 -0
  13. package/build/services/activities/activity/process.d.ts +28 -0
  14. package/build/services/activities/activity/process.js +100 -0
  15. package/build/services/activities/activity/protocol.d.ts +39 -0
  16. package/build/services/activities/activity/protocol.js +151 -0
  17. package/build/services/activities/activity/state.d.ts +40 -0
  18. package/build/services/activities/activity/state.js +143 -0
  19. package/build/services/activities/activity/transition.d.ts +23 -0
  20. package/build/services/activities/activity/transition.js +71 -0
  21. package/build/services/activities/activity/verify.d.ts +22 -0
  22. package/build/services/activities/activity/verify.js +85 -0
  23. package/build/services/activities/await.d.ts +1 -4
  24. package/build/services/activities/await.js +2 -36
  25. package/build/services/activities/cycle.d.ts +1 -11
  26. package/build/services/activities/cycle.js +3 -46
  27. package/build/services/activities/hook.d.ts +2 -11
  28. package/build/services/activities/hook.js +30 -50
  29. package/build/services/activities/interrupt.d.ts +2 -4
  30. package/build/services/activities/interrupt.js +4 -38
  31. package/build/services/activities/signal.d.ts +1 -11
  32. package/build/services/activities/signal.js +3 -48
  33. package/build/services/activities/trigger.d.ts +1 -3
  34. package/build/services/activities/trigger.js +0 -3
  35. package/build/services/activities/worker.d.ts +3 -6
  36. package/build/services/activities/worker.js +4 -40
  37. package/build/services/connector/factory.d.ts +6 -0
  38. package/build/services/connector/factory.js +24 -0
  39. package/build/services/durable/activity.d.ts +1 -1
  40. package/build/services/durable/activity.js +2 -2
  41. package/build/services/durable/client.d.ts +24 -29
  42. package/build/services/durable/client.js +24 -29
  43. package/build/services/durable/connection.d.ts +13 -7
  44. package/build/services/durable/connection.js +13 -7
  45. package/build/services/durable/handle.d.ts +58 -40
  46. package/build/services/durable/handle.js +60 -40
  47. package/build/services/durable/index.d.ts +148 -286
  48. package/build/services/durable/index.js +157 -292
  49. package/build/services/durable/interceptor.d.ts +43 -33
  50. package/build/services/durable/interceptor.js +59 -39
  51. package/build/services/durable/schemas/factory.d.ts +1 -1
  52. package/build/services/durable/schemas/factory.js +168 -38
  53. package/build/services/durable/telemetry.d.ts +80 -0
  54. package/build/services/durable/telemetry.js +137 -0
  55. package/build/services/durable/worker.d.ts +100 -21
  56. package/build/services/durable/worker.js +304 -63
  57. package/build/services/durable/workflow/all.d.ts +1 -1
  58. package/build/services/durable/workflow/all.js +1 -1
  59. package/build/services/durable/workflow/cancellationScope.d.ts +104 -0
  60. package/build/services/durable/workflow/cancellationScope.js +139 -0
  61. package/build/services/durable/workflow/common.d.ts +5 -4
  62. package/build/services/durable/workflow/common.js +6 -1
  63. package/build/services/durable/workflow/{waitFor.d.ts → condition.d.ts} +9 -8
  64. package/build/services/durable/workflow/{waitFor.js → condition.js} +44 -11
  65. package/build/services/durable/workflow/continueAsNew.d.ts +65 -0
  66. package/build/services/durable/workflow/continueAsNew.js +92 -0
  67. package/build/services/durable/workflow/didRun.d.ts +1 -1
  68. package/build/services/durable/workflow/didRun.js +3 -3
  69. package/build/services/durable/workflow/enrich.d.ts +5 -0
  70. package/build/services/durable/workflow/enrich.js +5 -0
  71. package/build/services/durable/workflow/entityMethods.d.ts +7 -0
  72. package/build/services/durable/workflow/entityMethods.js +7 -0
  73. package/build/services/durable/workflow/execHook.js +3 -3
  74. package/build/services/durable/workflow/execHookBatch.js +2 -2
  75. package/build/services/durable/workflow/{execChild.d.ts → executeChild.d.ts} +4 -40
  76. package/build/services/durable/workflow/{execChild.js → executeChild.js} +36 -45
  77. package/build/services/durable/workflow/hook.d.ts +1 -1
  78. package/build/services/durable/workflow/hook.js +4 -3
  79. package/build/services/durable/workflow/index.d.ts +45 -50
  80. package/build/services/durable/workflow/index.js +46 -51
  81. package/build/services/durable/workflow/interruption.d.ts +7 -6
  82. package/build/services/durable/workflow/interruption.js +11 -7
  83. package/build/services/durable/workflow/patched.d.ts +72 -0
  84. package/build/services/durable/workflow/patched.js +110 -0
  85. package/build/services/durable/workflow/proxyActivities.d.ts +7 -7
  86. package/build/services/durable/workflow/proxyActivities.js +50 -15
  87. package/build/services/durable/workflow/searchMethods.d.ts +7 -0
  88. package/build/services/durable/workflow/searchMethods.js +7 -0
  89. package/build/services/durable/workflow/signal.d.ts +4 -4
  90. package/build/services/durable/workflow/signal.js +4 -4
  91. package/build/services/durable/workflow/{sleepFor.d.ts → sleep.d.ts} +7 -7
  92. package/build/services/durable/workflow/{sleepFor.js → sleep.js} +39 -10
  93. package/build/services/durable/workflow/terminate.d.ts +55 -0
  94. package/build/services/durable/workflow/{interrupt.js → terminate.js} +21 -21
  95. package/build/services/durable/workflow/trace.js +2 -2
  96. package/build/services/durable/workflow/uuid4.d.ts +14 -0
  97. package/build/services/durable/workflow/uuid4.js +39 -0
  98. package/build/services/durable/workflow/{context.d.ts → workflowInfo.d.ts} +5 -5
  99. package/build/services/durable/workflow/{context.js → workflowInfo.js} +7 -7
  100. package/build/services/engine/compiler.d.ts +19 -0
  101. package/build/services/engine/compiler.js +20 -0
  102. package/build/services/engine/completion.d.ts +46 -0
  103. package/build/services/engine/completion.js +145 -0
  104. package/build/services/engine/dispatch.d.ts +24 -0
  105. package/build/services/engine/dispatch.js +98 -0
  106. package/build/services/engine/index.d.ts +49 -81
  107. package/build/services/engine/index.js +175 -573
  108. package/build/services/engine/init.d.ts +42 -0
  109. package/build/services/engine/init.js +74 -0
  110. package/build/services/engine/pubsub.d.ts +50 -0
  111. package/build/services/engine/pubsub.js +118 -0
  112. package/build/services/engine/reporting.d.ts +20 -0
  113. package/build/services/engine/reporting.js +38 -0
  114. package/build/services/engine/schema.d.ts +23 -0
  115. package/build/services/engine/schema.js +62 -0
  116. package/build/services/engine/signal.d.ts +57 -0
  117. package/build/services/engine/signal.js +117 -0
  118. package/build/services/engine/state.d.ts +35 -0
  119. package/build/services/engine/state.js +61 -0
  120. package/build/services/engine/version.d.ts +31 -0
  121. package/build/services/engine/version.js +73 -0
  122. package/build/services/hotmesh/deployment.d.ts +21 -0
  123. package/build/services/hotmesh/deployment.js +25 -0
  124. package/build/services/hotmesh/index.d.ts +141 -532
  125. package/build/services/hotmesh/index.js +222 -673
  126. package/build/services/hotmesh/init.d.ts +42 -0
  127. package/build/services/hotmesh/init.js +93 -0
  128. package/build/services/hotmesh/jobs.d.ts +67 -0
  129. package/build/services/hotmesh/jobs.js +99 -0
  130. package/build/services/hotmesh/pubsub.d.ts +38 -0
  131. package/build/services/hotmesh/pubsub.js +54 -0
  132. package/build/services/hotmesh/quorum.d.ts +30 -0
  133. package/build/services/hotmesh/quorum.js +62 -0
  134. package/build/services/hotmesh/validation.d.ts +6 -0
  135. package/build/services/hotmesh/validation.js +28 -0
  136. package/build/services/quorum/index.js +1 -0
  137. package/build/services/router/consumption/index.d.ts +11 -5
  138. package/build/services/router/consumption/index.js +24 -17
  139. package/build/services/router/error-handling/index.d.ts +2 -2
  140. package/build/services/router/error-handling/index.js +21 -15
  141. package/build/services/router/index.d.ts +1 -1
  142. package/build/services/router/index.js +2 -2
  143. package/build/services/serializer/index.d.ts +22 -0
  144. package/build/services/serializer/index.js +39 -1
  145. package/build/services/store/index.d.ts +1 -0
  146. package/build/services/store/providers/postgres/exporter-sql.d.ts +2 -2
  147. package/build/services/store/providers/postgres/exporter-sql.js +4 -4
  148. package/build/services/store/providers/postgres/kvtables.js +7 -6
  149. package/build/services/store/providers/postgres/kvtypes/hash/basic.js +67 -52
  150. package/build/services/store/providers/postgres/kvtypes/hash/jsonb.js +87 -72
  151. package/build/services/store/providers/postgres/kvtypes/hash/udata.js +106 -79
  152. package/build/services/store/providers/postgres/kvtypes/hash/utils.d.ts +16 -0
  153. package/build/services/store/providers/postgres/kvtypes/hash/utils.js +29 -16
  154. package/build/services/store/providers/postgres/postgres.d.ts +1 -0
  155. package/build/services/store/providers/postgres/postgres.js +14 -4
  156. package/build/services/stream/factory.d.ts +3 -1
  157. package/build/services/stream/factory.js +2 -2
  158. package/build/services/stream/index.d.ts +1 -0
  159. package/build/services/stream/providers/nats/nats.d.ts +1 -0
  160. package/build/services/stream/providers/nats/nats.js +1 -0
  161. package/build/services/stream/providers/postgres/credentials.d.ts +56 -0
  162. package/build/services/stream/providers/postgres/credentials.js +129 -0
  163. package/build/services/stream/providers/postgres/kvtables.js +18 -0
  164. package/build/services/stream/providers/postgres/messages.js +7 -7
  165. package/build/services/stream/providers/postgres/notifications.js +16 -2
  166. package/build/services/stream/providers/postgres/postgres.d.ts +7 -0
  167. package/build/services/stream/providers/postgres/postgres.js +35 -4
  168. package/build/services/stream/providers/postgres/procedures.d.ts +21 -0
  169. package/build/services/stream/providers/postgres/procedures.js +213 -0
  170. package/build/services/stream/providers/postgres/secured.d.ts +34 -0
  171. package/build/services/stream/providers/postgres/secured.js +146 -0
  172. package/build/services/stream/providers/postgres/stats.d.ts +1 -0
  173. package/build/services/stream/providers/postgres/stats.js +1 -0
  174. package/build/services/stream/registry.d.ts +1 -1
  175. package/build/services/stream/registry.js +5 -2
  176. package/build/services/telemetry/index.d.ts +10 -1
  177. package/build/services/telemetry/index.js +40 -7
  178. package/build/services/worker/credentials.d.ts +51 -0
  179. package/build/services/worker/credentials.js +87 -0
  180. package/build/services/worker/index.d.ts +2 -2
  181. package/build/services/worker/index.js +7 -6
  182. package/build/types/codec.d.ts +84 -0
  183. package/build/types/codec.js +2 -0
  184. package/build/types/durable.d.ts +104 -28
  185. package/build/types/error.d.ts +10 -1
  186. package/build/types/hotmesh.d.ts +67 -4
  187. package/build/types/index.d.ts +2 -1
  188. package/build/types/provider.d.ts +2 -2
  189. package/build/types/quorum.d.ts +35 -1
  190. package/build/types/stream.d.ts +12 -6
  191. package/package.json +6 -1
  192. package/build/services/activities/activity.d.ts +0 -192
  193. package/build/services/activities/activity.js +0 -786
  194. package/build/services/durable/workflow/interrupt.d.ts +0 -55
@@ -3,6 +3,7 @@ var _a;
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.WorkerService = void 0;
5
5
  const enums_1 = require("../../modules/enums");
6
+ const cancellationScope_1 = require("./workflow/cancellationScope");
6
7
  const errors_1 = require("../../modules/errors");
7
8
  const storage_1 = require("../../modules/storage");
8
9
  const utils_1 = require("../../modules/utils");
@@ -10,30 +11,95 @@ const hotmesh_1 = require("../hotmesh");
10
11
  const stream_1 = require("../../types/stream");
11
12
  const search_1 = require("./search");
12
13
  const factory_1 = require("./schemas/factory");
14
+ const telemetry_1 = require("./telemetry");
15
+ const telemetry_2 = require("../telemetry");
16
+ const telemetry_3 = require("../../types/telemetry");
13
17
  const index_1 = require("./index");
14
18
  /**
15
- * The *Worker* service Registers worker functions and connects them to the mesh,
16
- * using the target backend provider/s (Postgres, NATS, etc).
19
+ * Hosts workflow and activity functions, connecting them to Postgres
20
+ * for durable execution, replay, and automatic retry.
17
21
  *
18
- * @example
22
+ * ## Connection Modes
23
+ *
24
+ * ### Standard (legacy) — full admin access
25
+ *
26
+ * The worker connects with the same Postgres credentials as the engine.
27
+ * Simple to set up; all workers share the same connection pool.
28
+ *
29
+ * ```typescript
30
+ * const worker = await Durable.Worker.create({
31
+ * connection: {
32
+ * class: Postgres,
33
+ * options: { connectionString: 'postgres://user:pass@host:5432/hotmesh' },
34
+ * },
35
+ * taskQueue: 'orders',
36
+ * workflow: orderWorkflow,
37
+ * });
38
+ * ```
39
+ *
40
+ * ### Secured — scoped Postgres role (recommended for production)
41
+ *
42
+ * The worker connects as a restricted Postgres role that can only
43
+ * dequeue/ack/respond on its assigned stream names. All data access
44
+ * goes through SECURITY DEFINER stored procedures that validate the
45
+ * role's `app.allowed_streams` session variable before executing.
46
+ *
47
+ * **Step 1**: Provision a scoped credential (run once, from the engine/admin):
48
+ * ```typescript
49
+ * const cred = await Durable.provisionWorkerRole({
50
+ * connection: { class: Postgres, options: adminPgOptions },
51
+ * namespace: 'durable',
52
+ * streamNames: ['orders-activity'],
53
+ * });
54
+ * // cred = { roleName: 'hmsh_wrk_durable_orders_activity', password: '...' }
55
+ * ```
56
+ *
57
+ * **Step 2**: Pass the credential when creating the worker:
19
58
  * ```typescript
20
- * import { Durable } from '@hotmeshio/hotmesh';
21
- * import { Client as Postgres } from 'pg';
22
- * import * as workflows from './workflows';
59
+ * const worker = await Durable.Worker.create({
60
+ * connection: {
61
+ * class: Postgres,
62
+ * options: { host: 'pg.prod', port: 5432, database: 'hotmesh' },
63
+ * },
64
+ * taskQueue: 'orders',
65
+ * workflow: orderWorkflow,
66
+ * workerCredentials: { user: cred.roleName, password: cred.password },
67
+ * });
68
+ * ```
69
+ *
70
+ * The worker role **cannot**:
71
+ * - SELECT/INSERT/UPDATE/DELETE any table directly
72
+ * - Access `jobs`, `jobs_attributes`, or any engine tables
73
+ * - Dequeue messages from other workers' streams
74
+ * - LISTEN on other workers' notification channels
75
+ *
76
+ * See {@link Durable.provisionWorkerRole} for credential lifecycle management.
23
77
  *
24
- * async function run() {
25
- * const worker = await Durable.Worker.create({
26
- * connection: {
27
- * class: Postgres,
28
- * options: { connectionString: 'postgres://user:password@localhost:5432/db' }
29
- * },
30
- * taskQueue: 'default',
31
- * workflow: workflows.example,
32
- * });
78
+ * ## Telemetry
79
+ *
80
+ * Workers automatically emit OpenTelemetry spans when an OTel SDK is
81
+ * registered. Initialize the SDK **before** calling `create()`:
82
+ *
83
+ * ```typescript
84
+ * import { NodeSDK } from '@opentelemetry/sdk-node';
85
+ * import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
86
+ * import { resourceFromAttributes } from '@opentelemetry/resources';
87
+ * import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
33
88
  *
34
- * await worker.run();
35
- * }
89
+ * const sdk = new NodeSDK({
90
+ * resource: resourceFromAttributes({ [ATTR_SERVICE_NAME]: 'my-service' }),
91
+ * traceExporter: new OTLPTraceExporter({
92
+ * url: 'https://api.honeycomb.io/v1/traces',
93
+ * headers: { 'x-honeycomb-team': process.env.HONEYCOMB_API_KEY },
94
+ * }),
95
+ * });
96
+ * sdk.start();
36
97
  * ```
98
+ *
99
+ * | `HMSH_TELEMETRY` | Spans emitted |
100
+ * |-------|---------------|
101
+ * | `'info'` (default) | `WORKFLOW/START`, `WORKFLOW/COMPLETE`, `WORKFLOW/ERROR`, `ACTIVITY/{name}` |
102
+ * | `'debug'` | All `info` spans + `DISPATCH/RETURN` per operation + engine internals |
37
103
  */
38
104
  class WorkerService {
39
105
  static hashOptions(connection) {
@@ -151,7 +217,7 @@ class WorkerService {
151
217
  * sendEmail: (to: string, subject: string) => Promise<void>;
152
218
  * }>({
153
219
  * taskQueue: 'payment',
154
- * retryPolicy: { maximumAttempts: 3 }
220
+ * retry: { maximumAttempts: 3 }
155
221
  * });
156
222
  *
157
223
  * const result = await processPayment(amount);
@@ -180,28 +246,47 @@ class WorkerService {
180
246
  * taskQueue: 'shared'
181
247
  * }, { auditLog, collectMetrics }, 'shared');
182
248
  *
183
- * const interceptor: WorkflowInterceptor = {
249
+ * const interceptor: WorkflowInboundCallsInterceptor = {
184
250
  * async execute(ctx, next) {
185
251
  * const { auditLog } = Durable.workflow.proxyActivities<{
186
252
  * auditLog: (id: string, action: string) => Promise<void>;
187
253
  * }>({
188
254
  * taskQueue: 'shared',
189
- * retryPolicy: { maximumAttempts: 3 }
255
+ * retry: { maximumAttempts: 3 }
190
256
  * });
191
257
  * await auditLog(ctx.get('workflowId'), 'started');
192
258
  * return next();
193
259
  * }
194
260
  * };
195
261
  * ```
262
+ *
263
+ * @example
264
+ * ```typescript
265
+ * // Secured worker with scoped Postgres credentials (VNF-style isolation)
266
+ * // Step 1: Admin provisions a credential (one-time)
267
+ * const cred = await Durable.provisionWorkerRole({
268
+ * connection: { class: Postgres, options: adminOptions },
269
+ * streamNames: ['payment-activity'],
270
+ * });
271
+ *
272
+ * // Step 2: Worker connects with scoped role — can only access payment-activity
273
+ * await Durable.registerActivityWorker({
274
+ * connection: { class: Postgres, options: { host: 'pg.prod', database: 'hotmesh' } },
275
+ * taskQueue: 'payment',
276
+ * workerCredentials: { user: cred.roleName, password: cred.password },
277
+ * }, { processPayment, refundPayment });
278
+ * ```
196
279
  */
197
280
  static async registerActivityWorker(config, activities, activityTaskQueue) {
281
+ // Register as durable namespace so engine-layer spans are suppressed in 'info' mode
282
+ const targetNamespace = config?.namespace ?? factory_1.APP_ID;
283
+ telemetry_2.TelemetryService.durableNamespaces.add(targetNamespace);
198
284
  // Register activities globally in the registry
199
285
  WorkerService.registerActivities(activities);
200
286
  // Use provided activityTaskQueue or fall back to config.taskQueue
201
287
  const taskQueue = activityTaskQueue || config.taskQueue || 'durable-activities';
202
288
  // Append '-activity' suffix for the worker topic
203
289
  const activityTopic = `${taskQueue}-activity`;
204
- const targetNamespace = config?.namespace ?? factory_1.APP_ID;
205
290
  const optionsHash = WorkerService.hashOptions(config?.connection);
206
291
  const targetTopic = `${optionsHash}.${targetNamespace}.${activityTopic}`;
207
292
  // Return existing worker if already initialized (idempotent)
@@ -209,19 +294,21 @@ class WorkerService {
209
294
  return await WorkerService.instances.get(targetTopic);
210
295
  }
211
296
  // Create activity worker that listens on '{taskQueue}-activity' topic
297
+ const workerEntry = {
298
+ topic: activityTopic,
299
+ connection: config.connection,
300
+ callback: WorkerService.createActivityCallback(),
301
+ };
302
+ if (config.workerCredentials) {
303
+ workerEntry.workerCredentials = config.workerCredentials;
304
+ }
212
305
  const hotMeshWorker = await hotmesh_1.HotMesh.init({
213
306
  guid: config.guid ? `${config.guid}XA` : undefined,
214
307
  taskQueue,
215
308
  logLevel: config.options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
216
309
  appId: targetNamespace,
217
310
  engine: { connection: config.connection },
218
- workers: [
219
- {
220
- topic: activityTopic,
221
- connection: config.connection,
222
- callback: WorkerService.createActivityCallback(),
223
- },
224
- ],
311
+ workers: [workerEntry],
225
312
  });
226
313
  WorkerService.instances.set(targetTopic, hotMeshWorker);
227
314
  return hotMeshWorker;
@@ -232,6 +319,7 @@ class WorkerService {
232
319
  */
233
320
  static createActivityCallback() {
234
321
  return async (data) => {
322
+ let activitySpan = null;
235
323
  try {
236
324
  //always run the activity function when instructed; return the response
237
325
  const activityInput = data.data;
@@ -243,10 +331,28 @@ class WorkerService {
243
331
  const activityContext = new Map();
244
332
  activityContext.set('activityName', activityName);
245
333
  activityContext.set('arguments', activityInput.arguments);
246
- activityContext.set('argumentMetadata', activityInput.argumentMetadata ?? {});
334
+ activityContext.set('headers', activityInput.headers ?? {});
247
335
  activityContext.set('workflowId', activityInput.workflowId);
248
336
  activityContext.set('workflowTopic', activityInput.workflowTopic);
249
- const pojoResponse = await storage_1.activityAsyncLocalStorage.run(activityContext, () => activityFunction.apply(null, activityInput.arguments));
337
+ // Start an ACTIVITY span if telemetry is enabled
338
+ const trc = data.metadata.trc;
339
+ const spn = data.metadata.spn;
340
+ activitySpan =
341
+ telemetry_1.DurableTelemetryService.isEnabled() && trc && spn
342
+ ? telemetry_1.DurableTelemetryService.startSpan(trc, spn, `ACTIVITY/${activityName}`, {
343
+ 'durable.activity.name': activityName,
344
+ 'durable.workflow.id': activityInput.workflowId,
345
+ })
346
+ : null;
347
+ const interceptorService = index_1.Durable.getInterceptorService();
348
+ const pojoResponse = await storage_1.activityAsyncLocalStorage.run(activityContext, () => {
349
+ const executeFn = () => activityFunction.apply(null, activityInput.arguments);
350
+ if (interceptorService?.activityInbound?.length > 0) {
351
+ return interceptorService.executeActivityInboundChain(activityName, activityInput.arguments, executeFn);
352
+ }
353
+ return executeFn();
354
+ });
355
+ activitySpan?.end();
250
356
  return {
251
357
  status: stream_1.StreamStatus.SUCCESS,
252
358
  metadata: { ...data.metadata },
@@ -254,6 +360,11 @@ class WorkerService {
254
360
  };
255
361
  }
256
362
  catch (err) {
363
+ activitySpan?.setStatus({
364
+ code: telemetry_3.SpanStatusCode.ERROR,
365
+ message: err.message,
366
+ });
367
+ activitySpan?.end();
257
368
  // Log error (note: we don't have access to this.activityRunner here)
258
369
  console.error('durable-worker-activity-err', {
259
370
  name: err.name,
@@ -324,7 +435,7 @@ class WorkerService {
324
435
  };
325
436
  }
326
437
  /**
327
- * Connects a worker to the mesh.
438
+ * Creates and starts a workflow worker.
328
439
  *
329
440
  * @example
330
441
  * ```typescript
@@ -349,6 +460,9 @@ class WorkerService {
349
460
  * ```
350
461
  */
351
462
  static async create(config) {
463
+ // Register as durable namespace so engine-layer spans are suppressed in 'info' mode
464
+ const targetNamespace = config?.namespace ?? factory_1.APP_ID;
465
+ telemetry_2.TelemetryService.durableNamespaces.add(targetNamespace);
352
466
  const workflow = config.workflow;
353
467
  const [workflowFunctionName, workflowFunction] = WorkerService.resolveWorkflowTarget(workflow);
354
468
  // Separate taskQueue from workflowName - no concatenation for stream_name
@@ -356,6 +470,10 @@ class WorkerService {
356
470
  const activityTopic = `${taskQueue}-activity`;
357
471
  // workflowTopic remains concatenated for engine-internal routing (graph.subscribes)
358
472
  const workflowTopic = `${taskQueue}-${workflowFunctionName}`;
473
+ // Register activities passed via config
474
+ if (config.activities) {
475
+ WorkerService.registerActivities(config.activities);
476
+ }
359
477
  //initialize supporting workflows
360
478
  const worker = new WorkerService();
361
479
  worker.activityRunner = await worker.initActivityWorker(config, activityTopic);
@@ -398,19 +516,21 @@ class WorkerService {
398
516
  if (WorkerService.instances.has(targetTopic)) {
399
517
  return await WorkerService.instances.get(targetTopic);
400
518
  }
519
+ const workerEntry = {
520
+ topic: activityTopic,
521
+ connection: providerConfig,
522
+ callback: this.wrapActivityFunctions().bind(this),
523
+ };
524
+ if (config.workerCredentials) {
525
+ workerEntry.workerCredentials = config.workerCredentials;
526
+ }
401
527
  const hotMeshWorker = await hotmesh_1.HotMesh.init({
402
528
  guid: config.guid ? `${config.guid}XA` : undefined,
403
529
  taskQueue: config.taskQueue,
404
530
  logLevel: config.options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
405
531
  appId: targetNamespace,
406
532
  engine: { connection: providerConfig },
407
- workers: [
408
- {
409
- topic: activityTopic,
410
- connection: providerConfig,
411
- callback: this.wrapActivityFunctions().bind(this),
412
- },
413
- ],
533
+ workers: [workerEntry],
414
534
  });
415
535
  WorkerService.instances.set(targetTopic, hotMeshWorker);
416
536
  return hotMeshWorker;
@@ -420,6 +540,7 @@ class WorkerService {
420
540
  */
421
541
  wrapActivityFunctions() {
422
542
  return async (data) => {
543
+ let activitySpan = null;
423
544
  try {
424
545
  //always run the activity function when instructed; return the response
425
546
  const activityInput = data.data;
@@ -428,10 +549,39 @@ class WorkerService {
428
549
  const activityContext = new Map();
429
550
  activityContext.set('activityName', activityName);
430
551
  activityContext.set('arguments', activityInput.arguments);
431
- activityContext.set('argumentMetadata', activityInput.argumentMetadata ?? {});
552
+ activityContext.set('headers', activityInput.headers ?? {});
432
553
  activityContext.set('workflowId', activityInput.workflowId);
433
554
  activityContext.set('workflowTopic', activityInput.workflowTopic);
434
- const pojoResponse = await storage_1.activityAsyncLocalStorage.run(activityContext, () => activityFunction.apply(this, activityInput.arguments));
555
+ const interceptorService = index_1.Durable.getInterceptorService();
556
+ // Start an ACTIVITY span if telemetry is enabled
557
+ const trc = data.metadata.trc;
558
+ const spn = data.metadata.spn;
559
+ activitySpan =
560
+ telemetry_1.DurableTelemetryService.isEnabled() && trc && spn
561
+ ? telemetry_1.DurableTelemetryService.startSpan(trc, spn, `ACTIVITY/${activityName}`, {
562
+ 'durable.activity.name': activityName,
563
+ 'durable.workflow.id': activityInput.workflowId,
564
+ })
565
+ : null;
566
+ const activityPromise = storage_1.activityAsyncLocalStorage.run(activityContext, () => {
567
+ const executeFn = () => activityFunction.apply(this, activityInput.arguments);
568
+ if (interceptorService?.activityInbound?.length > 0) {
569
+ return interceptorService.executeActivityInboundChain(activityName, activityInput.arguments, executeFn);
570
+ }
571
+ return executeFn();
572
+ });
573
+ let pojoResponse;
574
+ if (activityInput.startToCloseTimeout && activityInput.startToCloseTimeout > 0) {
575
+ const timeoutMs = activityInput.startToCloseTimeout * 1000;
576
+ pojoResponse = await Promise.race([
577
+ activityPromise,
578
+ new Promise((_, reject) => setTimeout(() => reject(new errors_1.DurableTimeoutError(`Activity '${activityName}' exceeded startToCloseTimeout of ${activityInput.startToCloseTimeout}s`)), timeoutMs)),
579
+ ]);
580
+ }
581
+ else {
582
+ pojoResponse = await activityPromise;
583
+ }
584
+ activitySpan?.end();
435
585
  return {
436
586
  status: stream_1.StreamStatus.SUCCESS,
437
587
  metadata: { ...data.metadata },
@@ -439,6 +589,11 @@ class WorkerService {
439
589
  };
440
590
  }
441
591
  catch (err) {
592
+ activitySpan?.setStatus({
593
+ code: telemetry_3.SpanStatusCode.ERROR,
594
+ message: err.message,
595
+ });
596
+ activitySpan?.end();
442
597
  this.activityRunner.engine.logger.error('durable-worker-activity-err', {
443
598
  name: err.name,
444
599
  message: err.message,
@@ -489,20 +644,22 @@ class WorkerService {
489
644
  const targetNamespace = config?.namespace ?? factory_1.APP_ID;
490
645
  const optionsHash = WorkerService.hashOptions(config?.connection);
491
646
  const targetTopic = `${optionsHash}.${targetNamespace}.${workflowTopic}`;
647
+ const workerEntry = {
648
+ topic: taskQueue,
649
+ workflowName: workflowFunctionName,
650
+ connection: providerConfig,
651
+ callback: this.wrapWorkflowFunction(workflowFunction, workflowTopic, workflowFunctionName, config).bind(this),
652
+ };
653
+ if (config.workerCredentials) {
654
+ workerEntry.workerCredentials = config.workerCredentials;
655
+ }
492
656
  const hotMeshWorker = await hotmesh_1.HotMesh.init({
493
657
  guid: config.guid,
494
658
  taskQueue: config.taskQueue,
495
659
  logLevel: config.options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
496
660
  appId: config.namespace ?? factory_1.APP_ID,
497
661
  engine: { connection: providerConfig },
498
- workers: [
499
- {
500
- topic: taskQueue,
501
- workflowName: workflowFunctionName,
502
- connection: providerConfig,
503
- callback: this.wrapWorkflowFunction(workflowFunction, workflowTopic, workflowFunctionName, config).bind(this),
504
- },
505
- ],
662
+ workers: [workerEntry],
506
663
  });
507
664
  WorkerService.instances.set(targetTopic, hotMeshWorker);
508
665
  return hotMeshWorker;
@@ -514,7 +671,19 @@ class WorkerService {
514
671
  return async (data) => {
515
672
  const counter = { counter: 0 };
516
673
  const interruptionRegistry = [];
674
+ const patchMarkers = {};
517
675
  let isProcessing = false;
676
+ // Injects accumulated patch markers into any worker response so the
677
+ // engine can persist them via the YAML schema's job.maps mechanism.
678
+ // Include patchMarkers only when non-empty. The schema's
679
+ // job.maps patch-marker[-] is a no-op when the reference is absent.
680
+ const withPatchMarkers = (response) => {
681
+ if (Object.keys(patchMarkers).length > 0) {
682
+ response.data = response.data || {};
683
+ response.data.patchMarkers = patchMarkers;
684
+ }
685
+ return response;
686
+ };
518
687
  try {
519
688
  //incoming data payload has arguments and workflowId
520
689
  const workflowInput = data.data;
@@ -523,6 +692,7 @@ class WorkerService {
523
692
  context.set('expire', workflowInput.expire);
524
693
  context.set('counter', counter);
525
694
  context.set('interruptionRegistry', interruptionRegistry);
695
+ context.set('patchMarkers', patchMarkers);
526
696
  context.set('connection', config.connection);
527
697
  context.set('namespace', config.namespace ?? factory_1.APP_ID);
528
698
  context.set('raw', data);
@@ -542,6 +712,13 @@ class WorkerService {
542
712
  context.set('workflowDimension', workflowInput.workflowDimension);
543
713
  replayQuery = `-*${workflowInput.workflowDimension}-*`;
544
714
  }
715
+ else if (workflowInput.continueGeneration) {
716
+ //continueAsNew: use generation prefix for replay isolation;
717
+ //old generation keys remain but are invisible to the new execution
718
+ const genPrefix = `$${workflowInput.continueGeneration}`;
719
+ context.set('workflowDimension', genPrefix);
720
+ replayQuery = `-*${genPrefix}-*`;
721
+ }
545
722
  else {
546
723
  //last letter of words like 'hook', 'sleep', 'wait', 'signal', 'search', 'start', 'proxy', 'child', 'collator', 'trace', 'enrich', 'publish'
547
724
  replayQuery = '-*[ehklptydr]-*';
@@ -558,6 +735,15 @@ class WorkerService {
558
735
  context.set('activityInterceptorService', index_1.Durable.getInterceptorService());
559
736
  // Execute workflow with interceptors
560
737
  const workflowResponse = await storage_1.asyncLocalStorage.run(context, async () => {
738
+ // Emit WORKFLOW/START on first execution (not replay)
739
+ if (telemetry_1.DurableTelemetryService.isEnabled() &&
740
+ Object.keys(replay).length === 0) {
741
+ telemetry_1.DurableTelemetryService.emitPointSpan(data.metadata.trc, data.metadata.spn, `WORKFLOW/START/${workflowFunctionName}`, {
742
+ 'durable.workflow.id': workflowInput.workflowId,
743
+ 'durable.workflow.name': workflowFunctionName,
744
+ 'durable.workflow.topic': workflowTopic,
745
+ });
746
+ }
561
747
  // Get the interceptor service
562
748
  const interceptorService = index_1.Durable.getInterceptorService();
563
749
  // Create the workflow execution function
@@ -565,8 +751,16 @@ class WorkerService {
565
751
  return await workflowFunction.apply(this, workflowInput.arguments);
566
752
  };
567
753
  // Execute the workflow through the interceptor chain
568
- return await interceptorService.executeChain(context, execWorkflow);
754
+ return await interceptorService.executeInboundChain(context, execWorkflow);
569
755
  });
756
+ // Emit WORKFLOW/COMPLETE when the workflow finishes successfully
757
+ if (telemetry_1.DurableTelemetryService.isEnabled()) {
758
+ telemetry_1.DurableTelemetryService.emitPointSpan(data.metadata.trc, data.metadata.spn, `WORKFLOW/COMPLETE/${workflowFunctionName}`, {
759
+ 'durable.workflow.id': workflowInput.workflowId,
760
+ 'durable.workflow.name': workflowFunctionName,
761
+ 'durable.workflow.topic': workflowTopic,
762
+ });
763
+ }
570
764
  //if the embedded function has a try/catch, it can interrup the throw
571
765
  // throw here to interrupt the workflow if the embedded function caught and suppressed
572
766
  if (interruptionRegistry.length > 0) {
@@ -580,6 +774,8 @@ class WorkerService {
580
774
  throw new errors_1.DurableChildError(payload);
581
775
  case 'DurableSleepError':
582
776
  throw new errors_1.DurableSleepError(payload);
777
+ case 'DurableContinueAsNewError':
778
+ throw new errors_1.DurableContinueAsNewError(payload);
583
779
  case 'DurableTimeoutError':
584
780
  throw new errors_1.DurableTimeoutError(payload.message, payload.stack);
585
781
  case 'DurableMaxedError':
@@ -592,12 +788,12 @@ class WorkerService {
592
788
  throw new errors_1.DurableRetryError(`Unknown interruption type: ${payload.type}`);
593
789
  }
594
790
  }
595
- return {
791
+ return withPatchMarkers({
596
792
  code: 200,
597
793
  status: stream_1.StreamStatus.SUCCESS,
598
794
  metadata: { ...data.metadata },
599
795
  data: { response: workflowResponse, done: true },
600
- };
796
+ });
601
797
  }
602
798
  catch (err) {
603
799
  if (isProcessing) {
@@ -606,12 +802,12 @@ class WorkerService {
606
802
  if (err instanceof errors_1.DurableWaitForError ||
607
803
  interruptionRegistry.length > 1) {
608
804
  isProcessing = true;
609
- //NOTE: this type is spawned when `Promise.all` is used OR if the interruption is a `waitFor`
805
+ //NOTE: this type is spawned when `Promise.all` is used OR if the interruption is a `condition`
610
806
  const workflowInput = data.data;
611
807
  const execIndex = counter.counter - interruptionRegistry.length + 1;
612
808
  const { workflowId, workflowTopic, workflowDimension, originJobId, expire, } = workflowInput;
613
809
  const collatorFlowId = `${(0, utils_1.guid)()}$C`;
614
- return {
810
+ return withPatchMarkers({
615
811
  status: stream_1.StreamStatus.SUCCESS,
616
812
  code: enums_1.HMSH_CODE_DURABLE_ALL,
617
813
  metadata: { ...data.metadata },
@@ -627,12 +823,12 @@ class WorkerService {
627
823
  workflowTopic: workflowTopic,
628
824
  expire,
629
825
  },
630
- };
826
+ });
631
827
  }
632
828
  else if (err instanceof errors_1.DurableSleepError) {
633
829
  //return the sleep interruption
634
830
  isProcessing = true;
635
- return {
831
+ return withPatchMarkers({
636
832
  status: stream_1.StreamStatus.SUCCESS,
637
833
  code: err.code,
638
834
  metadata: { ...data.metadata },
@@ -647,12 +843,12 @@ class WorkerService {
647
843
  index: err.index,
648
844
  workflowDimension: err.workflowDimension,
649
845
  },
650
- };
846
+ });
651
847
  }
652
848
  else if (err instanceof errors_1.DurableProxyError) {
653
849
  //return the proxyActivity interruption
654
850
  isProcessing = true;
655
- return {
851
+ return withPatchMarkers({
656
852
  status: stream_1.StreamStatus.SUCCESS,
657
853
  code: err.code,
658
854
  metadata: { ...data.metadata },
@@ -665,7 +861,7 @@ class WorkerService {
665
861
  dimension: err.workflowDimension,
666
862
  }),
667
863
  arguments: err.arguments,
668
- argumentMetadata: err.argumentMetadata,
864
+ headers: err.headers,
669
865
  workflowDimension: err.workflowDimension,
670
866
  index: err.index,
671
867
  originJobId: err.originJobId,
@@ -675,10 +871,12 @@ class WorkerService {
675
871
  workflowTopic: err.workflowTopic,
676
872
  activityName: err.activityName,
677
873
  backoffCoefficient: err.backoffCoefficient,
874
+ initialInterval: err.initialInterval,
678
875
  maximumAttempts: err.maximumAttempts,
679
876
  maximumInterval: err.maximumInterval,
877
+ startToCloseTimeout: err.startToCloseTimeout,
680
878
  },
681
- };
879
+ });
682
880
  }
683
881
  else if (err instanceof errors_1.DurableChildError) {
684
882
  //return the child interruption
@@ -688,7 +886,7 @@ class WorkerService {
688
886
  workflowId: err.workflowId,
689
887
  dimension: err.workflowDimension,
690
888
  };
691
- return {
889
+ return withPatchMarkers({
692
890
  status: stream_1.StreamStatus.SUCCESS,
693
891
  code: err.code,
694
892
  metadata: { ...data.metadata },
@@ -698,6 +896,7 @@ class WorkerService {
698
896
  backoffCoefficient: err.backoffCoefficient || enums_1.HMSH_DURABLE_EXP_BACKOFF,
699
897
  code: err.code,
700
898
  index: err.index,
899
+ initialInterval: err.initialInterval,
701
900
  message: JSON.stringify(msg),
702
901
  maximumAttempts: err.maximumAttempts || enums_1.HMSH_DURABLE_MAX_ATTEMPTS,
703
902
  maximumInterval: err.maximumInterval || (0, utils_1.s)(enums_1.HMSH_DURABLE_MAX_INTERVAL),
@@ -713,12 +912,54 @@ class WorkerService {
713
912
  taskQueue: err.taskQueue,
714
913
  workflowName: err.workflowName,
715
914
  },
716
- };
915
+ });
916
+ }
917
+ else if (err instanceof errors_1.DurableContinueAsNewError) {
918
+ //return the continueAsNew interruption
919
+ isProcessing = true;
920
+ return withPatchMarkers({
921
+ status: stream_1.StreamStatus.SUCCESS,
922
+ code: err.code,
923
+ metadata: { ...data.metadata },
924
+ data: {
925
+ code: err.code,
926
+ arguments: err.arguments,
927
+ index: err.index,
928
+ workflowDimension: err.workflowDimension,
929
+ },
930
+ });
931
+ }
932
+ if (err instanceof cancellationScope_1.CancelledFailure) {
933
+ // CancelledFailure that wasn't caught by the workflow:
934
+ // treat as fatal (no retry) so the workflow terminates.
935
+ isProcessing = true;
936
+ return withPatchMarkers({
937
+ status: stream_1.StreamStatus.SUCCESS,
938
+ code: enums_1.HMSH_CODE_DURABLE_FATAL,
939
+ metadata: { ...data.metadata },
940
+ data: {
941
+ $error: {
942
+ message: err.message,
943
+ type: 'CancelledFailure',
944
+ name: 'CancelledFailure',
945
+ stack: err.stack,
946
+ code: enums_1.HMSH_CODE_DURABLE_FATAL,
947
+ },
948
+ },
949
+ });
717
950
  }
718
951
  // ALL other errors are actual fatal errors (598, 597, 596)
719
952
  // OR will be retried (599)
953
+ if (telemetry_1.DurableTelemetryService.isEnabled()) {
954
+ telemetry_1.DurableTelemetryService.emitPointSpan(data.metadata.trc, data.metadata.spn, `WORKFLOW/ERROR/${workflowFunctionName}`, {
955
+ 'durable.workflow.id': data.data.workflowId,
956
+ 'durable.workflow.name': workflowFunctionName,
957
+ 'error.message': err.message,
958
+ 'error.type': err.name || 'Error',
959
+ }, telemetry_3.SpanStatusCode.ERROR, err.message);
960
+ }
720
961
  isProcessing = true;
721
- return {
962
+ return withPatchMarkers({
722
963
  status: stream_1.StreamStatus.SUCCESS,
723
964
  code: err.code || new errors_1.DurableRetryError(err.message).code,
724
965
  metadata: { ...data.metadata },
@@ -731,7 +972,7 @@ class WorkerService {
731
972
  code: err.code || new errors_1.DurableRetryError(err.message).code,
732
973
  },
733
974
  },
734
- };
975
+ });
735
976
  }
736
977
  };
737
978
  }
@@ -5,7 +5,7 @@
5
5
  * multiple durable operations concurrently within a workflow function.
6
6
  *
7
7
  * In most cases, standard `Promise.all` works correctly for Durable
8
- * operations (e.g., parallel `waitFor` calls). Use `Durable.workflow.all`
8
+ * operations (e.g., parallel `condition` calls). Use `Durable.workflow.all`
9
9
  * when you observe counter-sequencing issues with complex parallel
10
10
  * patterns.
11
11
  *
@@ -8,7 +8,7 @@ exports.all = void 0;
8
8
  * multiple durable operations concurrently within a workflow function.
9
9
  *
10
10
  * In most cases, standard `Promise.all` works correctly for Durable
11
- * operations (e.g., parallel `waitFor` calls). Use `Durable.workflow.all`
11
+ * operations (e.g., parallel `condition` calls). Use `Durable.workflow.all`
12
12
  * when you observe counter-sequencing issues with complex parallel
13
13
  * patterns.
14
14
  *