@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
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Secured SQL adapter for worker routers.
3
+ *
4
+ * Replaces raw SQL with calls to SECURITY DEFINER stored procedures.
5
+ * Workers using this adapter can only access their allowed streams —
6
+ * the stored procedures validate `app.allowed_streams` before executing.
7
+ */
8
+ import { ILogger } from '../../../logger';
9
+ import { PostgresClientType } from '../../../../types/postgres';
10
+ import { ProviderClient } from '../../../../types/provider';
11
+ import { StreamMessage } from '../../../../types/stream';
12
+ /**
13
+ * Dequeue messages from worker_streams via stored procedure.
14
+ */
15
+ export declare function fetchMessagesSecured(client: PostgresClientType & ProviderClient, schema: string, streamName: string, consumerName: string, options: {
16
+ batchSize?: number;
17
+ reservationTimeout?: number;
18
+ enableBackoff?: boolean;
19
+ initialBackoff?: number;
20
+ maxBackoff?: number;
21
+ maxRetries?: number;
22
+ }, logger: ILogger): Promise<StreamMessage[]>;
23
+ /**
24
+ * Ack/delete messages via stored procedure.
25
+ */
26
+ export declare function ackAndDeleteSecured(client: PostgresClientType & ProviderClient, schema: string, streamName: string, messageIds: string[], logger: ILogger): Promise<number>;
27
+ /**
28
+ * Dead-letter messages via stored procedure.
29
+ */
30
+ export declare function deadLetterMessagesSecured(client: PostgresClientType & ProviderClient, schema: string, streamName: string, messageIds: string[], logger: ILogger): Promise<number>;
31
+ /**
32
+ * Publish a response to engine_streams via stored procedure.
33
+ */
34
+ export declare function publishMessagesSecured(client: PostgresClientType & ProviderClient, schema: string, streamName: string, messages: string[], logger: ILogger): Promise<string[]>;
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ /**
3
+ * Secured SQL adapter for worker routers.
4
+ *
5
+ * Replaces raw SQL with calls to SECURITY DEFINER stored procedures.
6
+ * Workers using this adapter can only access their allowed streams —
7
+ * the stored procedures validate `app.allowed_streams` before executing.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.publishMessagesSecured = exports.deadLetterMessagesSecured = exports.ackAndDeleteSecured = exports.fetchMessagesSecured = void 0;
11
+ const utils_1 = require("../../../../modules/utils");
12
+ const utils_2 = require("../../../../modules/utils");
13
+ /**
14
+ * Dequeue messages from worker_streams via stored procedure.
15
+ */
16
+ async function fetchMessagesSecured(client, schema, streamName, consumerName, options = {}, logger) {
17
+ const enableBackoff = options?.enableBackoff ?? false;
18
+ const initialBackoff = options?.initialBackoff ?? 100;
19
+ const maxBackoff = options?.maxBackoff ?? 3000;
20
+ const maxRetries = options?.maxRetries ?? 3;
21
+ const batchSize = options?.batchSize || 1;
22
+ const reservationTimeout = options?.reservationTimeout || 30;
23
+ let backoff = initialBackoff;
24
+ let retries = 0;
25
+ try {
26
+ while (retries < maxRetries) {
27
+ retries++;
28
+ const res = await client.query(`SELECT * FROM ${schema}.worker_dequeue($1, $2, $3, $4)`, [streamName, batchSize, consumerName, reservationTimeout]);
29
+ const messages = res.rows.map((row) => {
30
+ const data = (0, utils_2.parseStreamMessage)(row.message);
31
+ const hasDefaultRetryPolicy = (row.max_retry_attempts === 3 || row.max_retry_attempts === 5) &&
32
+ parseFloat(row.backoff_coefficient) === 10 &&
33
+ row.maximum_interval_seconds === 120;
34
+ if (row.max_retry_attempts !== null && !hasDefaultRetryPolicy) {
35
+ data._streamRetryConfig = {
36
+ max_retry_attempts: row.max_retry_attempts,
37
+ backoff_coefficient: parseFloat(row.backoff_coefficient),
38
+ maximum_interval_seconds: row.maximum_interval_seconds,
39
+ };
40
+ }
41
+ if (row.retry_attempt !== undefined && row.retry_attempt !== null) {
42
+ data._retryAttempt = row.retry_attempt;
43
+ }
44
+ if (row.workflow_name) {
45
+ if (data.metadata) {
46
+ data.metadata.wfn = row.workflow_name;
47
+ }
48
+ }
49
+ return {
50
+ id: row.id.toString(),
51
+ data,
52
+ retry: row.max_retry_attempts !== null && !hasDefaultRetryPolicy
53
+ ? {
54
+ maximumAttempts: row.max_retry_attempts,
55
+ backoffCoefficient: parseFloat(row.backoff_coefficient),
56
+ maximumInterval: row.maximum_interval_seconds,
57
+ }
58
+ : undefined,
59
+ };
60
+ });
61
+ if (messages.length > 0 || !enableBackoff) {
62
+ return messages;
63
+ }
64
+ await (0, utils_1.sleepFor)(backoff);
65
+ backoff = Math.min(backoff * 2, maxBackoff);
66
+ }
67
+ return [];
68
+ }
69
+ catch (error) {
70
+ logger.error(`postgres-secured-dequeue-error-${streamName}`, { error });
71
+ throw error;
72
+ }
73
+ }
74
+ exports.fetchMessagesSecured = fetchMessagesSecured;
75
+ /**
76
+ * Ack/delete messages via stored procedure.
77
+ */
78
+ async function ackAndDeleteSecured(client, schema, streamName, messageIds, logger) {
79
+ try {
80
+ const ids = messageIds.map((id) => parseInt(id));
81
+ const res = await client.query(`SELECT ${schema}.worker_ack($1, $2)`, [streamName, ids]);
82
+ return res.rows[0]?.worker_ack ?? messageIds.length;
83
+ }
84
+ catch (error) {
85
+ logger.error(`postgres-secured-ack-error-${streamName}`, { error });
86
+ throw error;
87
+ }
88
+ }
89
+ exports.ackAndDeleteSecured = ackAndDeleteSecured;
90
+ /**
91
+ * Dead-letter messages via stored procedure.
92
+ */
93
+ async function deadLetterMessagesSecured(client, schema, streamName, messageIds, logger) {
94
+ try {
95
+ const ids = messageIds.map((id) => parseInt(id));
96
+ const res = await client.query(`SELECT ${schema}.worker_dead_letter($1, $2)`, [streamName, ids]);
97
+ return res.rows[0]?.worker_dead_letter ?? 0;
98
+ }
99
+ catch (error) {
100
+ logger.error(`postgres-secured-dead-letter-error-${streamName}`, {
101
+ error,
102
+ messageIds,
103
+ });
104
+ throw error;
105
+ }
106
+ }
107
+ exports.deadLetterMessagesSecured = deadLetterMessagesSecured;
108
+ /**
109
+ * Publish a response to engine_streams via stored procedure.
110
+ */
111
+ async function publishMessagesSecured(client, schema, streamName, messages, logger) {
112
+ try {
113
+ const ids = [];
114
+ for (const msg of messages) {
115
+ const data = JSON.parse(msg);
116
+ const retryConfig = data._streamRetryConfig;
117
+ const visibilityDelayMs = data._visibilityDelayMs;
118
+ const retryAttempt = data._retryAttempt ?? 0;
119
+ // Remove internal fields
120
+ delete data._streamRetryConfig;
121
+ delete data._visibilityDelayMs;
122
+ delete data._retryAttempt;
123
+ const cleanMessage = JSON.stringify(data);
124
+ const visibleAt = visibilityDelayMs && visibilityDelayMs > 0
125
+ ? new Date(Date.now() + visibilityDelayMs).toISOString()
126
+ : new Date().toISOString();
127
+ const hasRetryConfig = retryConfig && 'max_retry_attempts' in retryConfig;
128
+ const res = await client.query(`SELECT ${schema}.worker_respond($1, $2, $3, $4, $5, $6, $7)`, [
129
+ streamName,
130
+ cleanMessage,
131
+ hasRetryConfig ? retryConfig.max_retry_attempts : null,
132
+ hasRetryConfig ? retryConfig.backoff_coefficient : null,
133
+ hasRetryConfig ? retryConfig.maximum_interval_seconds : null,
134
+ visibleAt,
135
+ retryAttempt,
136
+ ]);
137
+ ids.push(res.rows[0]?.worker_respond?.toString() ?? '0');
138
+ }
139
+ return ids;
140
+ }
141
+ catch (error) {
142
+ logger.error(`postgres-secured-respond-error-${streamName}`, { error });
143
+ throw error;
144
+ }
145
+ }
146
+ exports.publishMessagesSecured = publishMessagesSecured;
@@ -44,6 +44,7 @@ export declare function getProviderSpecificFeatures(config?: StreamConfig): {
44
44
  supportsTrimming: boolean;
45
45
  supportsRetry: boolean;
46
46
  supportsNotifications: boolean;
47
+ supportsParallelProcessing: boolean;
47
48
  maxMessageSize: number;
48
49
  maxBatchSize: number;
49
50
  };
@@ -107,6 +107,7 @@ function getProviderSpecificFeatures(config = {}) {
107
107
  supportsTrimming: true,
108
108
  supportsRetry: false,
109
109
  supportsNotifications: isNotificationsEnabled(config),
110
+ supportsParallelProcessing: true,
110
111
  maxMessageSize: 1024 * 1024,
111
112
  maxBatchSize: 256,
112
113
  };
@@ -23,7 +23,7 @@ declare class StreamConsumerRegistry {
23
23
  }, logger: ILogger, config?: {
24
24
  reclaimDelay?: number;
25
25
  reclaimCount?: number;
26
- retryPolicy?: any;
26
+ retry?: any;
27
27
  }): Promise<void>;
28
28
  /**
29
29
  * Register an engine callback for an appId.
@@ -30,7 +30,7 @@ class StreamConsumerRegistry {
30
30
  reclaimDelay: config?.reclaimDelay,
31
31
  reclaimCount: config?.reclaimCount,
32
32
  throttle,
33
- retryPolicy: config?.retryPolicy,
33
+ retry: config?.retry,
34
34
  }, stream, logger);
35
35
  entry = {
36
36
  router,
@@ -117,10 +117,13 @@ class StreamConsumerRegistry {
117
117
  }
118
118
  const callback = entry.callbacks.get(wfn);
119
119
  if (!callback) {
120
- entry.logger.debug('stream-consumer-registry-no-callback', {
120
+ entry.logger.info('stream-consumer-registry-replay', {
121
121
  key,
122
122
  wfn,
123
123
  registered: [...entry.callbacks.keys()],
124
+ reason: 'worker-not-yet-registered',
125
+ action: 'republish-with-delay',
126
+ delayMs: 500,
124
127
  });
125
128
  // Worker not registered yet. Re-publish with short visibility delay
126
129
  // so it retries after the worker has time to register.
@@ -13,9 +13,18 @@ declare class TelemetryService {
13
13
  metadata: ActivityMetadata;
14
14
  context: JobState;
15
15
  leg: number;
16
+ /**
17
+ * Namespaces registered by the durable module. Engine-layer spans
18
+ * for these namespaces are suppressed in 'info' mode — the
19
+ * DurableTelemetryService emits the user-facing story instead.
20
+ */
21
+ static durableNamespaces: Set<string>;
16
22
  constructor(appId: string, config?: ActivityType, metadata?: ActivityMetadata, context?: JobState);
23
+ private isDurableApp;
17
24
  /**
18
- * too chatty for production; only output traces, jobs, triggers and workers
25
+ * Gate engine-layer span creation. For durable namespaces in 'info'
26
+ * mode, returns false — DurableTelemetryService handles telemetry
27
+ * at the right abstraction level. In 'debug' mode, both layers emit.
19
28
  */
20
29
  private shouldCreateSpan;
21
30
  private static createNoopSpan;
@@ -17,10 +17,18 @@ class TelemetryService {
17
17
  this.metadata = metadata;
18
18
  this.context = context;
19
19
  }
20
+ isDurableApp() {
21
+ return TelemetryService.durableNamespaces.has(this.appId);
22
+ }
20
23
  /**
21
- * too chatty for production; only output traces, jobs, triggers and workers
24
+ * Gate engine-layer span creation. For durable namespaces in 'info'
25
+ * mode, returns false — DurableTelemetryService handles telemetry
26
+ * at the right abstraction level. In 'debug' mode, both layers emit.
22
27
  */
23
28
  shouldCreateSpan() {
29
+ if (this.isDurableApp() && enums_1.HMSH_TELEMETRY !== 'debug') {
30
+ return false;
31
+ }
24
32
  return (enums_1.HMSH_TELEMETRY === 'debug' ||
25
33
  this.config?.type === 'trigger' ||
26
34
  this.config?.type === 'worker');
@@ -73,16 +81,24 @@ class TelemetryService {
73
81
  }
74
82
  getActivityParentSpanId(leg) {
75
83
  if (leg === 1) {
76
- return this.context[this.config.parent].output?.metadata?.l2s;
84
+ return this.context?.[this.config?.parent]?.output?.metadata?.l2s;
77
85
  }
78
86
  else {
79
- return this.context['$self'].output?.metadata?.l1s;
87
+ return this.context?.['$self']?.output?.metadata?.l1s;
80
88
  }
81
89
  }
82
90
  getTraceId() {
83
91
  return this.context.metadata.trc;
84
92
  }
85
93
  startJobSpan() {
94
+ if (this.isDurableApp() && enums_1.HMSH_TELEMETRY !== 'debug') {
95
+ const trc = this.context?.metadata?.trc ?? '';
96
+ const spn = this.context?.metadata?.spn ?? '';
97
+ this.jobSpan = TelemetryService.createNoopSpan(trc, spn);
98
+ this.traceId = trc;
99
+ this.spanId = spn;
100
+ return this;
101
+ }
86
102
  const spanName = `JOB/${this.appId}/${this.config.subscribes}/1`;
87
103
  const traceId = this.getTraceId();
88
104
  const spanId = this.getJobParentSpanId();
@@ -117,6 +133,14 @@ class TelemetryService {
117
133
  });
118
134
  }
119
135
  startActivitySpan(leg = this.leg) {
136
+ if (this.isDurableApp() && enums_1.HMSH_TELEMETRY !== 'debug') {
137
+ const trc = this.context?.metadata?.trc ?? '';
138
+ const spn = this.context?.metadata?.spn ?? '';
139
+ this.span = TelemetryService.createNoopSpan(trc, spn);
140
+ this.traceId = trc;
141
+ this.spanId = spn;
142
+ return this;
143
+ }
120
144
  const spanName = `${this.config.type.toUpperCase()}/${this.appId}/${this.metadata.aid}/${leg}`;
121
145
  const traceId = this.getTraceId();
122
146
  const spanId = this.getActivityParentSpanId(leg);
@@ -141,10 +165,13 @@ class TelemetryService {
141
165
  else {
142
166
  type = 'FANOUT'; //exiting engine router (to worker router)
143
167
  }
144
- // `EXECUTE` refers to the 'worker router' NOT the 'worker activity' run by the 'engine router'
145
- // (Regardless, it's worker-related, so it matters and will be traced)
146
- // `SYSTEM` refers to catastrophic errors, which are always traced
147
- if (this.shouldCreateSpan() || type === 'EXECUTE' || type === 'SYSTEM') {
168
+ // For durable namespaces in 'info' mode, only SYSTEM spans pass
169
+ // DurableTelemetryService emits the user-facing workflow story.
170
+ // In 'debug' mode or for non-durable apps, original gating applies:
171
+ // `EXECUTE` = worker router, `SYSTEM` = catastrophic errors (always traced).
172
+ if (this.isDurableApp() && enums_1.HMSH_TELEMETRY !== 'debug'
173
+ ? type === 'SYSTEM'
174
+ : this.shouldCreateSpan() || type === 'EXECUTE' || type === 'SYSTEM') {
148
175
  const topic = data.metadata.topic ? `/${data.metadata.topic}` : '';
149
176
  const spanName = `${type}/${this.appId}/${data.metadata.aid}${topic}`;
150
177
  const attributes = this.getStreamSpanAttrs(data);
@@ -310,3 +337,9 @@ class TelemetryService {
310
337
  }
311
338
  }
312
339
  exports.TelemetryService = TelemetryService;
340
+ /**
341
+ * Namespaces registered by the durable module. Engine-layer spans
342
+ * for these namespaces are suppressed in 'info' mode — the
343
+ * DurableTelemetryService emits the user-facing story instead.
344
+ */
345
+ TelemetryService.durableNamespaces = new Set();
@@ -0,0 +1,51 @@
1
+ /**
2
+ * High-level worker credential lifecycle management.
3
+ *
4
+ * Creates an admin client from a connection config, delegates to the
5
+ * low-level Postgres credential functions, and cleans up the client.
6
+ * This is the canonical API — `HotMesh` and `Durable` re-export these
7
+ * as static convenience methods.
8
+ */
9
+ import type { WorkerCredential, WorkerCredentialInfo } from '../stream/providers/postgres/credentials';
10
+ import { ProviderConfig, ProvidersConfig } from '../../types/provider';
11
+ /**
12
+ * Provision a scoped Postgres role for a worker router.
13
+ *
14
+ * The role can only dequeue/ack/respond on the specified stream
15
+ * names via SECURITY DEFINER stored procedures — it has **zero
16
+ * direct table access**.
17
+ */
18
+ export declare function provisionWorkerRole(config: {
19
+ connection: ProviderConfig | ProvidersConfig;
20
+ namespace?: string;
21
+ streamNames: string[];
22
+ password?: string;
23
+ }): Promise<WorkerCredential>;
24
+ /**
25
+ * Rotate the password for an existing worker role.
26
+ */
27
+ export declare function rotateWorkerPassword(config: {
28
+ connection: ProviderConfig | ProvidersConfig;
29
+ namespace?: string;
30
+ roleName: string;
31
+ newPassword?: string;
32
+ }): Promise<{
33
+ password: string;
34
+ }>;
35
+ /**
36
+ * Revoke a worker role by disabling login. The role is not dropped,
37
+ * preserving the audit trail in the `worker_credentials` table.
38
+ */
39
+ export declare function revokeWorkerRole(config: {
40
+ connection: ProviderConfig | ProvidersConfig;
41
+ namespace?: string;
42
+ roleName: string;
43
+ }): Promise<void>;
44
+ /**
45
+ * List all provisioned worker roles for a namespace.
46
+ */
47
+ export declare function listWorkerRoles(config: {
48
+ connection: ProviderConfig | ProvidersConfig;
49
+ namespace?: string;
50
+ }): Promise<WorkerCredentialInfo[]>;
51
+ export type { WorkerCredential, WorkerCredentialInfo };
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ /**
3
+ * High-level worker credential lifecycle management.
4
+ *
5
+ * Creates an admin client from a connection config, delegates to the
6
+ * low-level Postgres credential functions, and cleans up the client.
7
+ * This is the canonical API — `HotMesh` and `Durable` re-export these
8
+ * as static convenience methods.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.listWorkerRoles = exports.revokeWorkerRole = exports.rotateWorkerPassword = exports.provisionWorkerRole = void 0;
12
+ const credentials_1 = require("../stream/providers/postgres/credentials");
13
+ /**
14
+ * Create a raw Postgres client from a connection config for admin
15
+ * operations (credential provisioning, rotation, revocation).
16
+ */
17
+ async function createAdminClient(connection) {
18
+ const config = 'options' in connection
19
+ ? connection
20
+ : connection.store ?? connection;
21
+ const ClientClass = config.class;
22
+ const options = config.options;
23
+ const client = new ClientClass(options);
24
+ await client.connect();
25
+ return client;
26
+ }
27
+ /**
28
+ * Provision a scoped Postgres role for a worker router.
29
+ *
30
+ * The role can only dequeue/ack/respond on the specified stream
31
+ * names via SECURITY DEFINER stored procedures — it has **zero
32
+ * direct table access**.
33
+ */
34
+ async function provisionWorkerRole(config) {
35
+ const namespace = config.namespace ?? 'durable';
36
+ const pgClient = await createAdminClient(config.connection);
37
+ try {
38
+ return await (0, credentials_1.provisionWorkerRole)(pgClient, namespace, config.streamNames, config.password);
39
+ }
40
+ finally {
41
+ await pgClient.end?.();
42
+ }
43
+ }
44
+ exports.provisionWorkerRole = provisionWorkerRole;
45
+ /**
46
+ * Rotate the password for an existing worker role.
47
+ */
48
+ async function rotateWorkerPassword(config) {
49
+ const namespace = config.namespace ?? 'durable';
50
+ const pgClient = await createAdminClient(config.connection);
51
+ try {
52
+ return await (0, credentials_1.rotateWorkerPassword)(pgClient, namespace, config.roleName, config.newPassword);
53
+ }
54
+ finally {
55
+ await pgClient.end?.();
56
+ }
57
+ }
58
+ exports.rotateWorkerPassword = rotateWorkerPassword;
59
+ /**
60
+ * Revoke a worker role by disabling login. The role is not dropped,
61
+ * preserving the audit trail in the `worker_credentials` table.
62
+ */
63
+ async function revokeWorkerRole(config) {
64
+ const namespace = config.namespace ?? 'durable';
65
+ const pgClient = await createAdminClient(config.connection);
66
+ try {
67
+ await (0, credentials_1.revokeWorkerRole)(pgClient, namespace, config.roleName);
68
+ }
69
+ finally {
70
+ await pgClient.end?.();
71
+ }
72
+ }
73
+ exports.revokeWorkerRole = revokeWorkerRole;
74
+ /**
75
+ * List all provisioned worker roles for a namespace.
76
+ */
77
+ async function listWorkerRoles(config) {
78
+ const namespace = config.namespace ?? 'durable';
79
+ const pgClient = await createAdminClient(config.connection);
80
+ try {
81
+ return await (0, credentials_1.listWorkerRoles)(pgClient, namespace);
82
+ }
83
+ finally {
84
+ await pgClient.end?.();
85
+ }
86
+ }
87
+ exports.listWorkerRoles = listWorkerRoles;
@@ -23,7 +23,7 @@ declare class WorkerService {
23
23
  reporting: boolean;
24
24
  inited: string;
25
25
  rollCallInterval: NodeJS.Timeout;
26
- retryPolicy: import('../../types/stream').RetryPolicy | undefined;
26
+ retry: import('../../types/stream').RetryPolicy | undefined;
27
27
  /**
28
28
  * @private
29
29
  */
@@ -47,7 +47,7 @@ declare class WorkerService {
47
47
  /**
48
48
  * @private
49
49
  */
50
- initStreamChannel(service: WorkerService, stream: ProviderClient, store: ProviderClient): Promise<void>;
50
+ initStreamChannel(service: WorkerService, stream: ProviderClient, store: ProviderClient, securedWorker?: boolean): Promise<void>;
51
51
  /**
52
52
  * @private
53
53
  */
@@ -39,19 +39,19 @@ class WorkerService {
39
39
  service.topic = worker.topic;
40
40
  service.config = config;
41
41
  service.logger = logger;
42
- service.retryPolicy = worker.retryPolicy;
42
+ service.retry = worker.retry;
43
43
  await service.initStoreChannel(service, worker.store);
44
44
  await service.initSubChannel(service, worker.sub, worker.pub ?? worker.store);
45
45
  await service.subscribe.subscribe(key_1.KeyType.QUORUM, service.subscriptionHandler(), appId);
46
46
  await service.subscribe.subscribe(key_1.KeyType.QUORUM, service.subscriptionHandler(), appId, service.topic);
47
47
  await service.subscribe.subscribe(key_1.KeyType.QUORUM, service.subscriptionHandler(), appId, service.guid);
48
- await service.initStreamChannel(service, worker.stream, worker.store);
48
+ await service.initStreamChannel(service, worker.stream, worker.store, !!worker.workerCredentials);
49
49
  if (worker.workflowName) {
50
50
  // Use singleton consumer via registry (batched fetch + dispatch by workflow_name)
51
51
  await registry_1.StreamConsumerRegistry.registerWorker(namespace, appId, guid, worker.topic, worker.workflowName, worker.callback, service.stream, service.store, logger, {
52
52
  reclaimDelay: worker.reclaimDelay,
53
53
  reclaimCount: worker.reclaimCount,
54
- retryPolicy: worker.retryPolicy,
54
+ retry: worker.retry,
55
55
  });
56
56
  // Still need a router for publishing responses back to engine
57
57
  service.router = await service.initRouter(worker, logger);
@@ -97,8 +97,8 @@ class WorkerService {
97
97
  /**
98
98
  * @private
99
99
  */
100
- async initStreamChannel(service, stream, store) {
101
- service.stream = await factory_2.StreamServiceFactory.init(stream, store, service.namespace, service.appId, service.logger);
100
+ async initStreamChannel(service, stream, store, securedWorker = false) {
101
+ service.stream = await factory_2.StreamServiceFactory.init(stream, store, service.namespace, service.appId, service.logger, { securedWorker });
102
102
  }
103
103
  /**
104
104
  * @private
@@ -114,7 +114,7 @@ class WorkerService {
114
114
  reclaimDelay: worker.reclaimDelay,
115
115
  reclaimCount: worker.reclaimCount,
116
116
  throttle,
117
- retryPolicy: worker.retryPolicy,
117
+ retry: worker.retry,
118
118
  }, this.stream, logger);
119
119
  }
120
120
  /**
@@ -191,6 +191,7 @@ class WorkerService {
191
191
  worker_topic: this.topic,
192
192
  stream: this.store.mintKey(key_1.KeyType.STREAMS, params),
193
193
  counts: this.router?.counts,
194
+ error_count: this.router?.errorCount,
194
195
  timestamp: (0, utils_1.formatISODate)(new Date()),
195
196
  inited: this.inited,
196
197
  throttle: this.router?.throttle,
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Synchronous codec for encoding/decoding serialized payload data.
3
+ *
4
+ * Register a `PayloadCodec` to transparently transform all serialized
5
+ * object values (`/s{json}`) before they are stored and after they are
6
+ * read. Common use cases: encryption at rest, compression, or custom
7
+ * binary encoding.
8
+ *
9
+ * ## How It Works
10
+ *
11
+ * The serializer stores JavaScript objects as `/s{json}`. When a codec
12
+ * is registered, objects are instead stored as `/b{encoded}`:
13
+ *
14
+ * ```
15
+ * Write: value → JSON.stringify → codec.encode(json) → "/b{encoded}"
16
+ * Read: "/b{encoded}" → codec.decode(encoded) → JSON.parse → value
17
+ * "/s{json}" → JSON.parse → value (backward compatible)
18
+ * ```
19
+ *
20
+ * ## Constraints
21
+ *
22
+ * - **Synchronous** — `encode` and `decode` must be synchronous.
23
+ * Use Node.js `crypto` (e.g., `createCipheriv`) or `zlib`
24
+ * (e.g., `deflateSync`) for sync operations.
25
+ * - **String-safe** — `encode` output must be a valid UTF-8 string
26
+ * (Postgres TEXT column). Use base64 encoding for binary output.
27
+ * - **Deterministic decode** — `decode(encode(x))` must equal `x`.
28
+ *
29
+ * ## Scope
30
+ *
31
+ * The codec applies to all data flowing through the serializer's
32
+ * `package`/`unpackage` path — job state, activity inputs/outputs,
33
+ * timeline markers, and workflow metadata. It does **not** apply to
34
+ * `search()`, `entity()`, or `enrich()` data, which bypass the
35
+ * serializer.
36
+ *
37
+ * ## Example: AES-256-GCM Encryption
38
+ *
39
+ * ```typescript
40
+ * import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
41
+ * import { PayloadCodec } from '@hotmeshio/hotmesh';
42
+ *
43
+ * const key = randomBytes(32);
44
+ *
45
+ * const codec: PayloadCodec = {
46
+ * encode(json: string): string {
47
+ * const iv = randomBytes(12);
48
+ * const cipher = createCipheriv('aes-256-gcm', key, iv);
49
+ * const enc = Buffer.concat([cipher.update(json, 'utf8'), cipher.final()]);
50
+ * const tag = cipher.getAuthTag();
51
+ * return Buffer.concat([iv, tag, enc]).toString('base64');
52
+ * },
53
+ * decode(encoded: string): string {
54
+ * const buf = Buffer.from(encoded, 'base64');
55
+ * const iv = buf.subarray(0, 12);
56
+ * const tag = buf.subarray(12, 28);
57
+ * const data = buf.subarray(28);
58
+ * const decipher = createDecipheriv('aes-256-gcm', key, iv);
59
+ * decipher.setAuthTag(tag);
60
+ * return Buffer.concat([decipher.update(data), decipher.final()]).toString('utf8');
61
+ * },
62
+ * };
63
+ *
64
+ * // Register globally — applies to all HotMesh and Durable instances
65
+ * HotMesh.registerCodec(codec);
66
+ * ```
67
+ */
68
+ export interface PayloadCodec {
69
+ /**
70
+ * Encode a JSON string before storage. The output replaces
71
+ * the `/s{json}` prefix with `/b{encoded}`.
72
+ *
73
+ * @param json - The raw JSON string (result of JSON.stringify)
74
+ * @returns The encoded string (must be valid UTF-8)
75
+ */
76
+ encode(json: string): string;
77
+ /**
78
+ * Decode a previously encoded string back to the original JSON.
79
+ *
80
+ * @param encoded - The encoded string (from a previous `encode` call)
81
+ * @returns The original JSON string
82
+ */
83
+ decode(encoded: string): string;
84
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });