@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
@@ -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;
@@ -240,7 +328,31 @@ class WorkerService {
240
328
  if (!activityFunction) {
241
329
  throw new Error(`Activity '${activityName}' not found in registry`);
242
330
  }
243
- const pojoResponse = await activityFunction.apply(null, activityInput.arguments);
331
+ const activityContext = new Map();
332
+ activityContext.set('activityName', activityName);
333
+ activityContext.set('arguments', activityInput.arguments);
334
+ activityContext.set('headers', activityInput.headers ?? {});
335
+ activityContext.set('workflowId', activityInput.workflowId);
336
+ activityContext.set('workflowTopic', activityInput.workflowTopic);
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();
244
356
  return {
245
357
  status: stream_1.StreamStatus.SUCCESS,
246
358
  metadata: { ...data.metadata },
@@ -248,6 +360,11 @@ class WorkerService {
248
360
  };
249
361
  }
250
362
  catch (err) {
363
+ activitySpan?.setStatus({
364
+ code: telemetry_3.SpanStatusCode.ERROR,
365
+ message: err.message,
366
+ });
367
+ activitySpan?.end();
251
368
  // Log error (note: we don't have access to this.activityRunner here)
252
369
  console.error('durable-worker-activity-err', {
253
370
  name: err.name,
@@ -318,7 +435,7 @@ class WorkerService {
318
435
  };
319
436
  }
320
437
  /**
321
- * Connects a worker to the mesh.
438
+ * Creates and starts a workflow worker.
322
439
  *
323
440
  * @example
324
441
  * ```typescript
@@ -343,6 +460,9 @@ class WorkerService {
343
460
  * ```
344
461
  */
345
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);
346
466
  const workflow = config.workflow;
347
467
  const [workflowFunctionName, workflowFunction] = WorkerService.resolveWorkflowTarget(workflow);
348
468
  // Separate taskQueue from workflowName - no concatenation for stream_name
@@ -350,6 +470,10 @@ class WorkerService {
350
470
  const activityTopic = `${taskQueue}-activity`;
351
471
  // workflowTopic remains concatenated for engine-internal routing (graph.subscribes)
352
472
  const workflowTopic = `${taskQueue}-${workflowFunctionName}`;
473
+ // Register activities passed via config
474
+ if (config.activities) {
475
+ WorkerService.registerActivities(config.activities);
476
+ }
353
477
  //initialize supporting workflows
354
478
  const worker = new WorkerService();
355
479
  worker.activityRunner = await worker.initActivityWorker(config, activityTopic);
@@ -392,19 +516,21 @@ class WorkerService {
392
516
  if (WorkerService.instances.has(targetTopic)) {
393
517
  return await WorkerService.instances.get(targetTopic);
394
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
+ }
395
527
  const hotMeshWorker = await hotmesh_1.HotMesh.init({
396
528
  guid: config.guid ? `${config.guid}XA` : undefined,
397
529
  taskQueue: config.taskQueue,
398
530
  logLevel: config.options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
399
531
  appId: targetNamespace,
400
532
  engine: { connection: providerConfig },
401
- workers: [
402
- {
403
- topic: activityTopic,
404
- connection: providerConfig,
405
- callback: this.wrapActivityFunctions().bind(this),
406
- },
407
- ],
533
+ workers: [workerEntry],
408
534
  });
409
535
  WorkerService.instances.set(targetTopic, hotMeshWorker);
410
536
  return hotMeshWorker;
@@ -414,12 +540,48 @@ class WorkerService {
414
540
  */
415
541
  wrapActivityFunctions() {
416
542
  return async (data) => {
543
+ let activitySpan = null;
417
544
  try {
418
545
  //always run the activity function when instructed; return the response
419
546
  const activityInput = data.data;
420
547
  const activityName = activityInput.activityName;
421
548
  const activityFunction = WorkerService.activityRegistry[activityName];
422
- const pojoResponse = await activityFunction.apply(this, activityInput.arguments);
549
+ const activityContext = new Map();
550
+ activityContext.set('activityName', activityName);
551
+ activityContext.set('arguments', activityInput.arguments);
552
+ activityContext.set('headers', activityInput.headers ?? {});
553
+ activityContext.set('workflowId', activityInput.workflowId);
554
+ activityContext.set('workflowTopic', activityInput.workflowTopic);
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();
423
585
  return {
424
586
  status: stream_1.StreamStatus.SUCCESS,
425
587
  metadata: { ...data.metadata },
@@ -427,6 +589,11 @@ class WorkerService {
427
589
  };
428
590
  }
429
591
  catch (err) {
592
+ activitySpan?.setStatus({
593
+ code: telemetry_3.SpanStatusCode.ERROR,
594
+ message: err.message,
595
+ });
596
+ activitySpan?.end();
430
597
  this.activityRunner.engine.logger.error('durable-worker-activity-err', {
431
598
  name: err.name,
432
599
  message: err.message,
@@ -477,20 +644,22 @@ class WorkerService {
477
644
  const targetNamespace = config?.namespace ?? factory_1.APP_ID;
478
645
  const optionsHash = WorkerService.hashOptions(config?.connection);
479
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
+ }
480
656
  const hotMeshWorker = await hotmesh_1.HotMesh.init({
481
657
  guid: config.guid,
482
658
  taskQueue: config.taskQueue,
483
659
  logLevel: config.options?.logLevel ?? enums_1.HMSH_LOGLEVEL,
484
660
  appId: config.namespace ?? factory_1.APP_ID,
485
661
  engine: { connection: providerConfig },
486
- workers: [
487
- {
488
- topic: taskQueue,
489
- workflowName: workflowFunctionName,
490
- connection: providerConfig,
491
- callback: this.wrapWorkflowFunction(workflowFunction, workflowTopic, workflowFunctionName, config).bind(this),
492
- },
493
- ],
662
+ workers: [workerEntry],
494
663
  });
495
664
  WorkerService.instances.set(targetTopic, hotMeshWorker);
496
665
  return hotMeshWorker;
@@ -502,7 +671,19 @@ class WorkerService {
502
671
  return async (data) => {
503
672
  const counter = { counter: 0 };
504
673
  const interruptionRegistry = [];
674
+ const patchMarkers = {};
505
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
+ };
506
687
  try {
507
688
  //incoming data payload has arguments and workflowId
508
689
  const workflowInput = data.data;
@@ -511,6 +692,7 @@ class WorkerService {
511
692
  context.set('expire', workflowInput.expire);
512
693
  context.set('counter', counter);
513
694
  context.set('interruptionRegistry', interruptionRegistry);
695
+ context.set('patchMarkers', patchMarkers);
514
696
  context.set('connection', config.connection);
515
697
  context.set('namespace', config.namespace ?? factory_1.APP_ID);
516
698
  context.set('raw', data);
@@ -530,6 +712,13 @@ class WorkerService {
530
712
  context.set('workflowDimension', workflowInput.workflowDimension);
531
713
  replayQuery = `-*${workflowInput.workflowDimension}-*`;
532
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
+ }
533
722
  else {
534
723
  //last letter of words like 'hook', 'sleep', 'wait', 'signal', 'search', 'start', 'proxy', 'child', 'collator', 'trace', 'enrich', 'publish'
535
724
  replayQuery = '-*[ehklptydr]-*';
@@ -546,6 +735,15 @@ class WorkerService {
546
735
  context.set('activityInterceptorService', index_1.Durable.getInterceptorService());
547
736
  // Execute workflow with interceptors
548
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
+ }
549
747
  // Get the interceptor service
550
748
  const interceptorService = index_1.Durable.getInterceptorService();
551
749
  // Create the workflow execution function
@@ -553,8 +751,16 @@ class WorkerService {
553
751
  return await workflowFunction.apply(this, workflowInput.arguments);
554
752
  };
555
753
  // Execute the workflow through the interceptor chain
556
- return await interceptorService.executeChain(context, execWorkflow);
754
+ return await interceptorService.executeInboundChain(context, execWorkflow);
557
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
+ }
558
764
  //if the embedded function has a try/catch, it can interrup the throw
559
765
  // throw here to interrupt the workflow if the embedded function caught and suppressed
560
766
  if (interruptionRegistry.length > 0) {
@@ -568,6 +774,8 @@ class WorkerService {
568
774
  throw new errors_1.DurableChildError(payload);
569
775
  case 'DurableSleepError':
570
776
  throw new errors_1.DurableSleepError(payload);
777
+ case 'DurableContinueAsNewError':
778
+ throw new errors_1.DurableContinueAsNewError(payload);
571
779
  case 'DurableTimeoutError':
572
780
  throw new errors_1.DurableTimeoutError(payload.message, payload.stack);
573
781
  case 'DurableMaxedError':
@@ -580,12 +788,12 @@ class WorkerService {
580
788
  throw new errors_1.DurableRetryError(`Unknown interruption type: ${payload.type}`);
581
789
  }
582
790
  }
583
- return {
791
+ return withPatchMarkers({
584
792
  code: 200,
585
793
  status: stream_1.StreamStatus.SUCCESS,
586
794
  metadata: { ...data.metadata },
587
795
  data: { response: workflowResponse, done: true },
588
- };
796
+ });
589
797
  }
590
798
  catch (err) {
591
799
  if (isProcessing) {
@@ -594,12 +802,12 @@ class WorkerService {
594
802
  if (err instanceof errors_1.DurableWaitForError ||
595
803
  interruptionRegistry.length > 1) {
596
804
  isProcessing = true;
597
- //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`
598
806
  const workflowInput = data.data;
599
807
  const execIndex = counter.counter - interruptionRegistry.length + 1;
600
808
  const { workflowId, workflowTopic, workflowDimension, originJobId, expire, } = workflowInput;
601
809
  const collatorFlowId = `${(0, utils_1.guid)()}$C`;
602
- return {
810
+ return withPatchMarkers({
603
811
  status: stream_1.StreamStatus.SUCCESS,
604
812
  code: enums_1.HMSH_CODE_DURABLE_ALL,
605
813
  metadata: { ...data.metadata },
@@ -615,12 +823,12 @@ class WorkerService {
615
823
  workflowTopic: workflowTopic,
616
824
  expire,
617
825
  },
618
- };
826
+ });
619
827
  }
620
828
  else if (err instanceof errors_1.DurableSleepError) {
621
829
  //return the sleep interruption
622
830
  isProcessing = true;
623
- return {
831
+ return withPatchMarkers({
624
832
  status: stream_1.StreamStatus.SUCCESS,
625
833
  code: err.code,
626
834
  metadata: { ...data.metadata },
@@ -635,12 +843,12 @@ class WorkerService {
635
843
  index: err.index,
636
844
  workflowDimension: err.workflowDimension,
637
845
  },
638
- };
846
+ });
639
847
  }
640
848
  else if (err instanceof errors_1.DurableProxyError) {
641
849
  //return the proxyActivity interruption
642
850
  isProcessing = true;
643
- return {
851
+ return withPatchMarkers({
644
852
  status: stream_1.StreamStatus.SUCCESS,
645
853
  code: err.code,
646
854
  metadata: { ...data.metadata },
@@ -653,6 +861,7 @@ class WorkerService {
653
861
  dimension: err.workflowDimension,
654
862
  }),
655
863
  arguments: err.arguments,
864
+ headers: err.headers,
656
865
  workflowDimension: err.workflowDimension,
657
866
  index: err.index,
658
867
  originJobId: err.originJobId,
@@ -662,10 +871,12 @@ class WorkerService {
662
871
  workflowTopic: err.workflowTopic,
663
872
  activityName: err.activityName,
664
873
  backoffCoefficient: err.backoffCoefficient,
874
+ initialInterval: err.initialInterval,
665
875
  maximumAttempts: err.maximumAttempts,
666
876
  maximumInterval: err.maximumInterval,
877
+ startToCloseTimeout: err.startToCloseTimeout,
667
878
  },
668
- };
879
+ });
669
880
  }
670
881
  else if (err instanceof errors_1.DurableChildError) {
671
882
  //return the child interruption
@@ -675,7 +886,7 @@ class WorkerService {
675
886
  workflowId: err.workflowId,
676
887
  dimension: err.workflowDimension,
677
888
  };
678
- return {
889
+ return withPatchMarkers({
679
890
  status: stream_1.StreamStatus.SUCCESS,
680
891
  code: err.code,
681
892
  metadata: { ...data.metadata },
@@ -685,6 +896,7 @@ class WorkerService {
685
896
  backoffCoefficient: err.backoffCoefficient || enums_1.HMSH_DURABLE_EXP_BACKOFF,
686
897
  code: err.code,
687
898
  index: err.index,
899
+ initialInterval: err.initialInterval,
688
900
  message: JSON.stringify(msg),
689
901
  maximumAttempts: err.maximumAttempts || enums_1.HMSH_DURABLE_MAX_ATTEMPTS,
690
902
  maximumInterval: err.maximumInterval || (0, utils_1.s)(enums_1.HMSH_DURABLE_MAX_INTERVAL),
@@ -700,12 +912,54 @@ class WorkerService {
700
912
  taskQueue: err.taskQueue,
701
913
  workflowName: err.workflowName,
702
914
  },
703
- };
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
+ });
704
950
  }
705
951
  // ALL other errors are actual fatal errors (598, 597, 596)
706
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
+ }
707
961
  isProcessing = true;
708
- return {
962
+ return withPatchMarkers({
709
963
  status: stream_1.StreamStatus.SUCCESS,
710
964
  code: err.code || new errors_1.DurableRetryError(err.message).code,
711
965
  metadata: { ...data.metadata },
@@ -718,7 +972,7 @@ class WorkerService {
718
972
  code: err.code || new errors_1.DurableRetryError(err.message).code,
719
973
  },
720
974
  },
721
- };
975
+ });
722
976
  }
723
977
  };
724
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
  *