@hotmeshio/hotmesh 0.4.0 → 0.4.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 (283) hide show
  1. package/build/modules/enums.d.ts +110 -0
  2. package/build/modules/enums.js +134 -0
  3. package/build/modules/errors.d.ts +124 -0
  4. package/build/modules/errors.js +191 -0
  5. package/build/modules/key.d.ts +66 -0
  6. package/build/modules/key.js +190 -0
  7. package/build/modules/storage.d.ts +3 -0
  8. package/build/modules/storage.js +5 -0
  9. package/build/modules/utils.d.ts +119 -0
  10. package/build/modules/utils.js +374 -0
  11. package/build/package.json +1 -1
  12. package/build/services/activities/activity.d.ts +104 -0
  13. package/build/services/activities/activity.js +549 -0
  14. package/build/services/activities/await.d.ts +12 -0
  15. package/build/services/activities/await.js +114 -0
  16. package/build/services/activities/cycle.d.ts +19 -0
  17. package/build/services/activities/cycle.js +112 -0
  18. package/build/services/activities/hook.d.ts +27 -0
  19. package/build/services/activities/hook.js +168 -0
  20. package/build/services/activities/index.d.ts +19 -0
  21. package/build/services/activities/index.js +20 -0
  22. package/build/services/activities/interrupt.d.ts +16 -0
  23. package/build/services/activities/interrupt.js +158 -0
  24. package/build/services/activities/signal.d.ts +20 -0
  25. package/build/services/activities/signal.js +134 -0
  26. package/build/services/activities/trigger.d.ts +37 -0
  27. package/build/services/activities/trigger.js +246 -0
  28. package/build/services/activities/worker.d.ts +12 -0
  29. package/build/services/activities/worker.js +106 -0
  30. package/build/services/collator/index.d.ts +111 -0
  31. package/build/services/collator/index.js +293 -0
  32. package/build/services/compiler/deployer.d.ts +40 -0
  33. package/build/services/compiler/deployer.js +488 -0
  34. package/build/services/compiler/index.d.ts +32 -0
  35. package/build/services/compiler/index.js +112 -0
  36. package/build/services/compiler/validator.d.ts +34 -0
  37. package/build/services/compiler/validator.js +147 -0
  38. package/build/services/connector/factory.d.ts +22 -0
  39. package/build/services/connector/factory.js +99 -0
  40. package/build/services/connector/index.d.ts +30 -0
  41. package/build/services/connector/index.js +54 -0
  42. package/build/services/connector/providers/ioredis.d.ts +9 -0
  43. package/build/services/connector/providers/ioredis.js +26 -0
  44. package/build/services/connector/providers/nats.d.ts +9 -0
  45. package/build/services/connector/providers/nats.js +34 -0
  46. package/build/services/connector/providers/postgres.d.ts +20 -0
  47. package/build/services/connector/providers/postgres.js +102 -0
  48. package/build/services/connector/providers/redis.d.ts +9 -0
  49. package/build/services/connector/providers/redis.js +38 -0
  50. package/build/services/engine/index.d.ts +264 -0
  51. package/build/services/engine/index.js +761 -0
  52. package/build/services/exporter/index.d.ts +44 -0
  53. package/build/services/exporter/index.js +126 -0
  54. package/build/services/hotmesh/index.d.ts +483 -0
  55. package/build/services/hotmesh/index.js +622 -0
  56. package/build/services/logger/index.d.ts +16 -0
  57. package/build/services/logger/index.js +54 -0
  58. package/build/services/mapper/index.d.ts +28 -0
  59. package/build/services/mapper/index.js +81 -0
  60. package/build/services/memflow/client.d.ts +108 -0
  61. package/build/services/memflow/client.js +372 -0
  62. package/build/services/memflow/connection.d.ts +23 -0
  63. package/build/services/memflow/connection.js +33 -0
  64. package/build/services/memflow/context.d.ts +143 -0
  65. package/build/services/memflow/context.js +299 -0
  66. package/build/services/memflow/exporter.d.ts +51 -0
  67. package/build/services/memflow/exporter.js +215 -0
  68. package/build/services/memflow/handle.d.ts +90 -0
  69. package/build/services/memflow/handle.js +176 -0
  70. package/build/services/memflow/index.d.ts +116 -0
  71. package/build/services/memflow/index.js +122 -0
  72. package/build/services/memflow/schemas/factory.d.ts +29 -0
  73. package/build/services/memflow/schemas/factory.js +2492 -0
  74. package/build/services/memflow/search.d.ts +142 -0
  75. package/build/services/memflow/search.js +320 -0
  76. package/build/services/memflow/worker.d.ts +124 -0
  77. package/build/services/memflow/worker.js +514 -0
  78. package/build/services/memflow/workflow/all.d.ts +7 -0
  79. package/build/services/memflow/workflow/all.js +15 -0
  80. package/build/services/memflow/workflow/common.d.ts +20 -0
  81. package/build/services/memflow/workflow/common.js +47 -0
  82. package/build/services/memflow/workflow/context.d.ts +6 -0
  83. package/build/services/memflow/workflow/context.js +45 -0
  84. package/build/services/memflow/workflow/contextMethods.d.ts +14 -0
  85. package/build/services/memflow/workflow/contextMethods.js +33 -0
  86. package/build/services/memflow/workflow/didRun.d.ts +7 -0
  87. package/build/services/memflow/workflow/didRun.js +22 -0
  88. package/build/services/memflow/workflow/emit.d.ts +11 -0
  89. package/build/services/memflow/workflow/emit.js +29 -0
  90. package/build/services/memflow/workflow/enrich.d.ts +9 -0
  91. package/build/services/memflow/workflow/enrich.js +17 -0
  92. package/build/services/memflow/workflow/execChild.d.ts +18 -0
  93. package/build/services/memflow/workflow/execChild.js +102 -0
  94. package/build/services/memflow/workflow/execHook.d.ts +65 -0
  95. package/build/services/memflow/workflow/execHook.js +73 -0
  96. package/build/services/memflow/workflow/hook.d.ts +9 -0
  97. package/build/services/memflow/workflow/hook.js +56 -0
  98. package/build/services/memflow/workflow/index.d.ts +74 -0
  99. package/build/services/memflow/workflow/index.js +87 -0
  100. package/build/services/memflow/workflow/interrupt.d.ts +9 -0
  101. package/build/services/memflow/workflow/interrupt.js +24 -0
  102. package/build/services/memflow/workflow/isSideEffectAllowed.d.ts +10 -0
  103. package/build/services/memflow/workflow/isSideEffectAllowed.js +33 -0
  104. package/build/services/memflow/workflow/proxyActivities.d.ts +20 -0
  105. package/build/services/memflow/workflow/proxyActivities.js +97 -0
  106. package/build/services/memflow/workflow/random.d.ts +6 -0
  107. package/build/services/memflow/workflow/random.js +16 -0
  108. package/build/services/memflow/workflow/searchMethods.d.ts +6 -0
  109. package/build/services/memflow/workflow/searchMethods.js +25 -0
  110. package/build/services/memflow/workflow/signal.d.ts +7 -0
  111. package/build/services/memflow/workflow/signal.js +28 -0
  112. package/build/services/memflow/workflow/sleepFor.d.ts +8 -0
  113. package/build/services/memflow/workflow/sleepFor.js +35 -0
  114. package/build/services/memflow/workflow/trace.d.ts +14 -0
  115. package/build/services/memflow/workflow/trace.js +33 -0
  116. package/build/services/memflow/workflow/waitFor.d.ts +8 -0
  117. package/build/services/memflow/workflow/waitFor.js +35 -0
  118. package/build/services/meshcall/index.d.ts +194 -0
  119. package/build/services/meshcall/index.js +452 -0
  120. package/build/services/meshcall/schemas/factory.d.ts +9 -0
  121. package/build/services/meshcall/schemas/factory.js +189 -0
  122. package/build/services/meshdata/index.d.ts +795 -0
  123. package/build/services/meshdata/index.js +1235 -0
  124. package/build/services/meshos/index.d.ts +293 -0
  125. package/build/services/meshos/index.js +547 -0
  126. package/build/services/pipe/functions/array.d.ts +17 -0
  127. package/build/services/pipe/functions/array.js +74 -0
  128. package/build/services/pipe/functions/bitwise.d.ts +9 -0
  129. package/build/services/pipe/functions/bitwise.js +24 -0
  130. package/build/services/pipe/functions/conditional.d.ts +13 -0
  131. package/build/services/pipe/functions/conditional.js +36 -0
  132. package/build/services/pipe/functions/cron.d.ts +12 -0
  133. package/build/services/pipe/functions/cron.js +40 -0
  134. package/build/services/pipe/functions/date.d.ts +58 -0
  135. package/build/services/pipe/functions/date.js +171 -0
  136. package/build/services/pipe/functions/index.d.ts +29 -0
  137. package/build/services/pipe/functions/index.js +30 -0
  138. package/build/services/pipe/functions/json.d.ts +5 -0
  139. package/build/services/pipe/functions/json.js +12 -0
  140. package/build/services/pipe/functions/logical.d.ts +5 -0
  141. package/build/services/pipe/functions/logical.js +12 -0
  142. package/build/services/pipe/functions/math.d.ts +42 -0
  143. package/build/services/pipe/functions/math.js +184 -0
  144. package/build/services/pipe/functions/number.d.ts +21 -0
  145. package/build/services/pipe/functions/number.js +60 -0
  146. package/build/services/pipe/functions/object.d.ts +25 -0
  147. package/build/services/pipe/functions/object.js +81 -0
  148. package/build/services/pipe/functions/string.d.ts +23 -0
  149. package/build/services/pipe/functions/string.js +69 -0
  150. package/build/services/pipe/functions/symbol.d.ts +12 -0
  151. package/build/services/pipe/functions/symbol.js +33 -0
  152. package/build/services/pipe/functions/unary.d.ts +7 -0
  153. package/build/services/pipe/functions/unary.js +18 -0
  154. package/build/services/pipe/index.d.ts +48 -0
  155. package/build/services/pipe/index.js +242 -0
  156. package/build/services/quorum/index.d.ts +90 -0
  157. package/build/services/quorum/index.js +263 -0
  158. package/build/services/reporter/index.d.ts +50 -0
  159. package/build/services/reporter/index.js +348 -0
  160. package/build/services/router/config/index.d.ts +11 -0
  161. package/build/services/router/config/index.js +36 -0
  162. package/build/services/router/consumption/index.d.ts +34 -0
  163. package/build/services/router/consumption/index.js +395 -0
  164. package/build/services/router/error-handling/index.d.ts +8 -0
  165. package/build/services/router/error-handling/index.js +98 -0
  166. package/build/services/router/index.d.ts +57 -0
  167. package/build/services/router/index.js +121 -0
  168. package/build/services/router/lifecycle/index.d.ts +27 -0
  169. package/build/services/router/lifecycle/index.js +80 -0
  170. package/build/services/router/telemetry/index.d.ts +11 -0
  171. package/build/services/router/telemetry/index.js +32 -0
  172. package/build/services/router/throttling/index.d.ts +23 -0
  173. package/build/services/router/throttling/index.js +76 -0
  174. package/build/services/search/factory.d.ts +7 -0
  175. package/build/services/search/factory.js +24 -0
  176. package/build/services/search/index.d.ts +23 -0
  177. package/build/services/search/index.js +10 -0
  178. package/build/services/search/providers/postgres/postgres.d.ts +25 -0
  179. package/build/services/search/providers/postgres/postgres.js +149 -0
  180. package/build/services/search/providers/redis/ioredis.d.ts +19 -0
  181. package/build/services/search/providers/redis/ioredis.js +121 -0
  182. package/build/services/search/providers/redis/redis.d.ts +19 -0
  183. package/build/services/search/providers/redis/redis.js +134 -0
  184. package/build/services/serializer/index.d.ts +42 -0
  185. package/build/services/serializer/index.js +282 -0
  186. package/build/services/store/cache.d.ts +67 -0
  187. package/build/services/store/cache.js +128 -0
  188. package/build/services/store/factory.d.ts +8 -0
  189. package/build/services/store/factory.js +24 -0
  190. package/build/services/store/index.d.ts +89 -0
  191. package/build/services/store/index.js +9 -0
  192. package/build/services/store/providers/postgres/kvsql.d.ts +168 -0
  193. package/build/services/store/providers/postgres/kvsql.js +198 -0
  194. package/build/services/store/providers/postgres/kvtables.d.ts +20 -0
  195. package/build/services/store/providers/postgres/kvtables.js +441 -0
  196. package/build/services/store/providers/postgres/kvtransaction.d.ts +36 -0
  197. package/build/services/store/providers/postgres/kvtransaction.js +248 -0
  198. package/build/services/store/providers/postgres/kvtypes/hash.d.ts +60 -0
  199. package/build/services/store/providers/postgres/kvtypes/hash.js +1287 -0
  200. package/build/services/store/providers/postgres/kvtypes/list.d.ts +33 -0
  201. package/build/services/store/providers/postgres/kvtypes/list.js +194 -0
  202. package/build/services/store/providers/postgres/kvtypes/string.d.ts +20 -0
  203. package/build/services/store/providers/postgres/kvtypes/string.js +115 -0
  204. package/build/services/store/providers/postgres/kvtypes/zset.d.ts +41 -0
  205. package/build/services/store/providers/postgres/kvtypes/zset.js +214 -0
  206. package/build/services/store/providers/postgres/postgres.d.ts +145 -0
  207. package/build/services/store/providers/postgres/postgres.js +1036 -0
  208. package/build/services/store/providers/redis/_base.d.ts +137 -0
  209. package/build/services/store/providers/redis/_base.js +980 -0
  210. package/build/services/store/providers/redis/ioredis.d.ts +20 -0
  211. package/build/services/store/providers/redis/ioredis.js +180 -0
  212. package/build/services/store/providers/redis/redis.d.ts +18 -0
  213. package/build/services/store/providers/redis/redis.js +199 -0
  214. package/build/services/store/providers/store-initializable.d.ts +5 -0
  215. package/build/services/store/providers/store-initializable.js +2 -0
  216. package/build/services/stream/factory.d.ts +8 -0
  217. package/build/services/stream/factory.js +37 -0
  218. package/build/services/stream/index.d.ts +69 -0
  219. package/build/services/stream/index.js +11 -0
  220. package/build/services/stream/providers/nats/nats.d.ts +60 -0
  221. package/build/services/stream/providers/nats/nats.js +225 -0
  222. package/build/services/stream/providers/postgres/kvtables.d.ts +3 -0
  223. package/build/services/stream/providers/postgres/kvtables.js +146 -0
  224. package/build/services/stream/providers/postgres/postgres.d.ts +107 -0
  225. package/build/services/stream/providers/postgres/postgres.js +519 -0
  226. package/build/services/stream/providers/redis/ioredis.d.ts +61 -0
  227. package/build/services/stream/providers/redis/ioredis.js +272 -0
  228. package/build/services/stream/providers/redis/redis.d.ts +61 -0
  229. package/build/services/stream/providers/redis/redis.js +305 -0
  230. package/build/services/stream/providers/stream-initializable.d.ts +4 -0
  231. package/build/services/stream/providers/stream-initializable.js +2 -0
  232. package/build/services/sub/factory.d.ts +7 -0
  233. package/build/services/sub/factory.js +29 -0
  234. package/build/services/sub/index.d.ts +22 -0
  235. package/build/services/sub/index.js +10 -0
  236. package/build/services/sub/providers/nats/nats.d.ts +19 -0
  237. package/build/services/sub/providers/nats/nats.js +105 -0
  238. package/build/services/sub/providers/postgres/postgres.d.ts +19 -0
  239. package/build/services/sub/providers/postgres/postgres.js +92 -0
  240. package/build/services/sub/providers/redis/ioredis.d.ts +17 -0
  241. package/build/services/sub/providers/redis/ioredis.js +81 -0
  242. package/build/services/sub/providers/redis/redis.d.ts +17 -0
  243. package/build/services/sub/providers/redis/redis.js +72 -0
  244. package/build/services/task/index.d.ts +36 -0
  245. package/build/services/task/index.js +206 -0
  246. package/build/services/telemetry/index.d.ts +52 -0
  247. package/build/services/telemetry/index.js +306 -0
  248. package/build/services/worker/index.d.ts +77 -0
  249. package/build/services/worker/index.js +197 -0
  250. package/package.json +1 -1
  251. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  252. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  253. package/typedoc.json +0 -47
  254. package/types/activity.ts +0 -268
  255. package/types/app.ts +0 -20
  256. package/types/async.ts +0 -6
  257. package/types/cache.ts +0 -1
  258. package/types/collator.ts +0 -9
  259. package/types/error.ts +0 -56
  260. package/types/exporter.ts +0 -102
  261. package/types/hook.ts +0 -44
  262. package/types/hotmesh.ts +0 -314
  263. package/types/index.ts +0 -306
  264. package/types/job.ts +0 -233
  265. package/types/logger.ts +0 -8
  266. package/types/manifest.ts +0 -70
  267. package/types/map.ts +0 -5
  268. package/types/memflow.ts +0 -645
  269. package/types/meshcall.ts +0 -235
  270. package/types/meshdata.ts +0 -278
  271. package/types/ms.d.ts +0 -7
  272. package/types/nats.ts +0 -270
  273. package/types/pipe.ts +0 -90
  274. package/types/postgres.ts +0 -114
  275. package/types/provider.ts +0 -161
  276. package/types/quorum.ts +0 -167
  277. package/types/redis.ts +0 -404
  278. package/types/serializer.ts +0 -40
  279. package/types/stats.ts +0 -117
  280. package/types/stream.ts +0 -231
  281. package/types/task.ts +0 -7
  282. package/types/telemetry.ts +0 -16
  283. package/types/transition.ts +0 -20
@@ -0,0 +1,519 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PostgresStreamService = void 0;
4
+ const key_1 = require("../../../../modules/key");
5
+ const utils_1 = require("../../../../modules/utils");
6
+ const index_1 = require("../../index");
7
+ const kvtables_1 = require("./kvtables");
8
+ class PostgresStreamService extends index_1.StreamService {
9
+ constructor(streamClient, storeClient, config = {}) {
10
+ super(streamClient, storeClient, config);
11
+ this.notificationConsumers = new Map();
12
+ this.fallbackIntervalId = null;
13
+ this.notificationHandlerBound = this.handleNotification.bind(this);
14
+ }
15
+ async init(namespace, appId, logger) {
16
+ this.namespace = namespace;
17
+ this.appId = appId;
18
+ this.logger = logger;
19
+ await (0, kvtables_1.deploySchema)(this.streamClient, this.appId, this.logger);
20
+ // Set up notification handler if supported
21
+ if (this.streamClient.on && this.isNotificationsEnabled()) {
22
+ this.streamClient.on('notification', this.notificationHandlerBound);
23
+ this.startFallbackPoller();
24
+ }
25
+ }
26
+ isNotificationsEnabled() {
27
+ return this.config?.postgres?.enableNotifications !== false; // Default: true
28
+ }
29
+ getFallbackInterval() {
30
+ return this.config?.postgres?.notificationFallbackInterval || 30000; // Default: 30 seconds
31
+ }
32
+ getNotificationTimeout() {
33
+ return this.config?.postgres?.notificationTimeout || 5000; // Default: 5 seconds
34
+ }
35
+ startFallbackPoller() {
36
+ if (this.fallbackIntervalId) {
37
+ clearInterval(this.fallbackIntervalId);
38
+ }
39
+ this.fallbackIntervalId = setInterval(() => {
40
+ this.checkForMissedMessages();
41
+ }, this.getFallbackInterval());
42
+ }
43
+ async checkForMissedMessages() {
44
+ const now = Date.now();
45
+ for (const [key, consumer] of this.notificationConsumers.entries()) {
46
+ if (consumer.isListening && now - consumer.lastFallbackCheck > this.getFallbackInterval()) {
47
+ try {
48
+ const messages = await this.fetchMessages(consumer.streamName, consumer.groupName, consumer.consumerName, { batchSize: 10, enableBackoff: false, maxRetries: 1 });
49
+ if (messages.length > 0) {
50
+ this.logger.debug('postgres-stream-fallback-messages', {
51
+ streamName: consumer.streamName,
52
+ groupName: consumer.groupName,
53
+ messageCount: messages.length
54
+ });
55
+ consumer.callback(messages);
56
+ }
57
+ consumer.lastFallbackCheck = now;
58
+ }
59
+ catch (error) {
60
+ this.logger.error('postgres-stream-fallback-error', {
61
+ streamName: consumer.streamName,
62
+ groupName: consumer.groupName,
63
+ error
64
+ });
65
+ }
66
+ }
67
+ }
68
+ }
69
+ handleNotification(notification) {
70
+ try {
71
+ const payload = JSON.parse(notification.payload);
72
+ const { stream_name, group_name } = payload;
73
+ if (!stream_name || !group_name) {
74
+ this.logger.warn('postgres-stream-invalid-notification', { notification });
75
+ return;
76
+ }
77
+ const consumerKey = this.getConsumerKey(stream_name, group_name);
78
+ const consumer = this.notificationConsumers.get(consumerKey);
79
+ if (consumer && consumer.isListening) {
80
+ // Trigger immediate message fetch for this consumer
81
+ this.fetchAndDeliverMessages(consumer);
82
+ }
83
+ }
84
+ catch (error) {
85
+ this.logger.error('postgres-stream-notification-parse-error', {
86
+ notification,
87
+ error
88
+ });
89
+ }
90
+ }
91
+ async fetchAndDeliverMessages(consumer) {
92
+ try {
93
+ const messages = await this.fetchMessages(consumer.streamName, consumer.groupName, consumer.consumerName, { batchSize: 10, enableBackoff: false, maxRetries: 1 });
94
+ if (messages.length > 0) {
95
+ consumer.callback(messages);
96
+ }
97
+ }
98
+ catch (error) {
99
+ this.logger.error('postgres-stream-fetch-deliver-error', {
100
+ streamName: consumer.streamName,
101
+ groupName: consumer.groupName,
102
+ error
103
+ });
104
+ }
105
+ }
106
+ getConsumerKey(streamName, groupName) {
107
+ return `${streamName}:${groupName}`;
108
+ }
109
+ mintKey(type, params) {
110
+ if (!this.namespace)
111
+ throw new Error('namespace not set');
112
+ return key_1.KeyService.mintKey(this.namespace, type, {
113
+ ...params,
114
+ appId: this.appId,
115
+ });
116
+ }
117
+ transact() {
118
+ return {};
119
+ }
120
+ getTableName() {
121
+ return `${this.safeName(this.appId)}.streams`;
122
+ }
123
+ safeName(appId) {
124
+ return appId.replace(/[^a-zA-Z0-9_]/g, '_');
125
+ }
126
+ async createStream(streamName) {
127
+ return true;
128
+ }
129
+ async deleteStream(streamName) {
130
+ const client = this.streamClient;
131
+ const tableName = this.getTableName();
132
+ try {
133
+ if (streamName === '*') {
134
+ await client.query(`DELETE FROM ${tableName}`);
135
+ }
136
+ else {
137
+ await client.query(`DELETE FROM ${tableName} WHERE stream_name = $1`, [
138
+ streamName,
139
+ ]);
140
+ }
141
+ return true;
142
+ }
143
+ catch (error) {
144
+ this.logger.error(`postgres-stream-delete-error-${streamName}`, {
145
+ error,
146
+ });
147
+ throw error;
148
+ }
149
+ }
150
+ async createConsumerGroup(streamName, groupName) {
151
+ return true;
152
+ }
153
+ async deleteConsumerGroup(streamName, groupName) {
154
+ const client = this.streamClient;
155
+ const tableName = this.getTableName();
156
+ try {
157
+ await client.query(`DELETE FROM ${tableName} WHERE stream_name = $1 AND group_name = $2`, [streamName, groupName]);
158
+ return true;
159
+ }
160
+ catch (error) {
161
+ this.logger.error(`postgres-stream-delete-group-error-${streamName}.${groupName}`, { error });
162
+ throw error;
163
+ }
164
+ }
165
+ /**
166
+ * `publishMessages` can be roped into a transaction by the `store`
167
+ * service. If so, it will add the SQL and params to the
168
+ * transaction. [Process Overview]: The engine keeps a reference
169
+ * to the `store` and `stream` providers; it asks the `store` to
170
+ * create a transaction and then starts adding store commands to the
171
+ * transaction. The engine then calls the router to publish a
172
+ * message using the `stream` provider (which the router keeps
173
+ * a reference to), and provides the transaction object.
174
+ * The `stream` provider then calls this method to generate
175
+ * the SQL and params for the transaction (but, of course, the sql
176
+ * is not executed until the engine calls the `exec` method on
177
+ * the transaction object provided by `store`).
178
+ *
179
+ * NOTE: this strategy keeps `stream` and `store` operations separate but
180
+ * allows calls to the stream to be roped into a single SQL transaction.
181
+ */
182
+ async publishMessages(streamName, messages, options) {
183
+ const { sql, params } = this._publishMessages(streamName, messages);
184
+ if (options?.transaction &&
185
+ typeof options.transaction.addCommand === 'function') {
186
+ //call addCommand and return the transaction object
187
+ options.transaction.addCommand(sql, params, 'array', (rows) => rows.map((row) => row.id.toString()));
188
+ return options.transaction;
189
+ }
190
+ else {
191
+ try {
192
+ const ids = [];
193
+ const res = await this.streamClient.query(sql, params);
194
+ for (const row of res.rows) {
195
+ ids.push(row.id.toString());
196
+ }
197
+ return ids;
198
+ }
199
+ catch (error) {
200
+ this.logger.error(`postgres-stream-publish-error-${streamName}`, {
201
+ error,
202
+ });
203
+ throw error;
204
+ }
205
+ }
206
+ }
207
+ _publishMessages(streamName, messages) {
208
+ const tableName = this.getTableName();
209
+ const groupName = streamName.endsWith(':') ? 'ENGINE' : 'WORKER';
210
+ const insertValues = messages
211
+ .map((_, idx) => `($1, $2, $${idx + 3})`)
212
+ .join(', ');
213
+ return {
214
+ sql: `INSERT INTO ${tableName} (stream_name, group_name, message) VALUES ${insertValues} RETURNING id`,
215
+ params: [streamName, groupName, ...messages],
216
+ };
217
+ }
218
+ async consumeMessages(streamName, groupName, consumerName, options) {
219
+ // If notification callback is provided and notifications are enabled, set up listener
220
+ if (options?.notificationCallback && this.shouldUseNotifications(options)) {
221
+ return this.setupNotificationConsumer(streamName, groupName, consumerName, options.notificationCallback, options);
222
+ }
223
+ // Otherwise, use traditional polling approach
224
+ return this.fetchMessages(streamName, groupName, consumerName, options);
225
+ }
226
+ shouldUseNotifications(options) {
227
+ const globalEnabled = this.isNotificationsEnabled();
228
+ const optionEnabled = options?.enableNotifications;
229
+ // If option is explicitly set, use that; otherwise use global setting
230
+ const enabled = optionEnabled !== undefined ? optionEnabled : globalEnabled;
231
+ // Also check if client supports notifications
232
+ return enabled && this.streamClient.on !== undefined;
233
+ }
234
+ async setupNotificationConsumer(streamName, groupName, consumerName, callback, options) {
235
+ const startTime = Date.now();
236
+ const consumerKey = this.getConsumerKey(streamName, groupName);
237
+ const channelName = (0, kvtables_1.getNotificationChannelName)(streamName, groupName);
238
+ // Set up LISTEN if not already listening
239
+ if (!this.notificationConsumers.has(consumerKey)) {
240
+ try {
241
+ const listenStart = Date.now();
242
+ await this.streamClient.query(`LISTEN "${channelName}"`);
243
+ this.logger.debug('postgres-stream-listen-start', {
244
+ streamName,
245
+ groupName,
246
+ channelName,
247
+ listenDuration: Date.now() - listenStart
248
+ });
249
+ }
250
+ catch (error) {
251
+ this.logger.error('postgres-stream-listen-error', {
252
+ streamName,
253
+ groupName,
254
+ channelName,
255
+ error
256
+ });
257
+ // Fall back to polling if LISTEN fails
258
+ return this.fetchMessages(streamName, groupName, consumerName, options);
259
+ }
260
+ }
261
+ // Register or update consumer
262
+ this.notificationConsumers.set(consumerKey, {
263
+ streamName,
264
+ groupName,
265
+ consumerName,
266
+ callback,
267
+ isListening: true,
268
+ lastFallbackCheck: Date.now()
269
+ });
270
+ this.logger.debug('postgres-stream-notification-setup-complete', {
271
+ streamName,
272
+ groupName,
273
+ setupDuration: Date.now() - startTime
274
+ });
275
+ // Do an initial fetch asynchronously to avoid blocking setup
276
+ // This ensures we don't miss any messages that were already in the queue
277
+ setImmediate(async () => {
278
+ try {
279
+ const fetchStart = Date.now();
280
+ const initialMessages = await this.fetchMessages(streamName, groupName, consumerName, {
281
+ ...options,
282
+ enableBackoff: false,
283
+ maxRetries: 1
284
+ });
285
+ this.logger.debug('postgres-stream-initial-fetch-complete', {
286
+ streamName,
287
+ groupName,
288
+ messageCount: initialMessages.length,
289
+ fetchDuration: Date.now() - fetchStart
290
+ });
291
+ // If we got messages, call the callback
292
+ if (initialMessages.length > 0) {
293
+ callback(initialMessages);
294
+ }
295
+ }
296
+ catch (error) {
297
+ this.logger.error('postgres-stream-initial-fetch-error', {
298
+ streamName,
299
+ groupName,
300
+ error
301
+ });
302
+ }
303
+ });
304
+ // Return empty array immediately to avoid blocking
305
+ return [];
306
+ }
307
+ async stopNotificationConsumer(streamName, groupName) {
308
+ const consumerKey = this.getConsumerKey(streamName, groupName);
309
+ const consumer = this.notificationConsumers.get(consumerKey);
310
+ if (consumer) {
311
+ consumer.isListening = false;
312
+ this.notificationConsumers.delete(consumerKey);
313
+ // If no more consumers for this channel, stop listening
314
+ const hasOtherConsumers = Array.from(this.notificationConsumers.values())
315
+ .some(c => c.streamName === streamName && c.groupName === groupName);
316
+ if (!hasOtherConsumers) {
317
+ const channelName = (0, kvtables_1.getNotificationChannelName)(streamName, groupName);
318
+ try {
319
+ await this.streamClient.query(`UNLISTEN "${channelName}"`);
320
+ this.logger.debug('postgres-stream-unlisten', {
321
+ streamName,
322
+ groupName,
323
+ channelName
324
+ });
325
+ }
326
+ catch (error) {
327
+ this.logger.error('postgres-stream-unlisten-error', {
328
+ streamName,
329
+ groupName,
330
+ channelName,
331
+ error
332
+ });
333
+ }
334
+ }
335
+ }
336
+ }
337
+ async fetchMessages(streamName, groupName, consumerName, options) {
338
+ const client = this.streamClient;
339
+ const tableName = this.getTableName();
340
+ const enableBackoff = options?.enableBackoff ?? false;
341
+ const initialBackoff = options?.initialBackoff ?? 100; // Default initial backoff: 100ms
342
+ const maxBackoff = options?.maxBackoff ?? 3000; // Default max backoff: 3 seconds
343
+ const maxRetries = options?.maxRetries ?? 3; // Set a finite default, e.g., 3 retries
344
+ let backoff = initialBackoff;
345
+ let retries = 0;
346
+ try {
347
+ while (retries < maxRetries) {
348
+ retries++;
349
+ const batchSize = options?.batchSize || 1;
350
+ const reservationTimeout = options?.reservationTimeout || 30;
351
+ // Simplified query for better performance - especially for notification-triggered fetches
352
+ const res = await client.query(`UPDATE ${tableName}
353
+ SET reserved_at = NOW(), reserved_by = $4
354
+ WHERE id IN (
355
+ SELECT id FROM ${tableName}
356
+ WHERE stream_name = $1
357
+ AND group_name = $2
358
+ AND (reserved_at IS NULL OR reserved_at < NOW() - INTERVAL '${reservationTimeout} seconds')
359
+ AND expired_at IS NULL
360
+ ORDER BY id
361
+ LIMIT $3
362
+ FOR UPDATE SKIP LOCKED
363
+ )
364
+ RETURNING id, message`, [streamName, groupName, batchSize, consumerName]);
365
+ const messages = res.rows.map((row) => ({
366
+ id: row.id.toString(),
367
+ data: (0, utils_1.parseStreamMessage)(row.message),
368
+ }));
369
+ if (messages.length > 0 || !enableBackoff) {
370
+ return messages;
371
+ }
372
+ // Apply backoff if enabled and no messages found
373
+ await (0, utils_1.sleepFor)(backoff);
374
+ backoff = Math.min(backoff * 2, maxBackoff); // Exponential backoff
375
+ }
376
+ // Return empty array if maxRetries is reached and still no messages
377
+ return [];
378
+ }
379
+ catch (error) {
380
+ this.logger.error(`postgres-stream-consumer-error-${streamName}`, {
381
+ error,
382
+ });
383
+ throw error;
384
+ }
385
+ }
386
+ async ackAndDelete(streamName, groupName, messageIds) {
387
+ return await this.deleteMessages(streamName, groupName, messageIds);
388
+ }
389
+ async acknowledgeMessages(streamName, groupName, messageIds, options) {
390
+ // No-op for this implementation
391
+ return messageIds.length;
392
+ }
393
+ async deleteMessages(streamName, groupName, messageIds, options) {
394
+ const client = this.streamClient;
395
+ const tableName = this.getTableName();
396
+ try {
397
+ const ids = messageIds.map((id) => parseInt(id));
398
+ // Perform a soft delete by setting `expired_at` to the current timestamp
399
+ await client.query(`UPDATE ${tableName}
400
+ SET expired_at = NOW()
401
+ WHERE stream_name = $1 AND id = ANY($2::bigint[]) AND group_name = $3`, [streamName, ids, groupName]);
402
+ return messageIds.length;
403
+ }
404
+ catch (error) {
405
+ this.logger.error(`postgres-stream-delete-error-${streamName}`, {
406
+ error,
407
+ });
408
+ throw error;
409
+ }
410
+ }
411
+ async retryMessages(streamName, groupName, options) {
412
+ // Implement retry logic if needed
413
+ return [];
414
+ }
415
+ async getStreamStats(streamName) {
416
+ const client = this.streamClient;
417
+ const tableName = this.getTableName();
418
+ try {
419
+ const res = await client.query(`SELECT COUNT(*) AS available_count
420
+ FROM ${tableName}
421
+ WHERE stream_name = $1 AND expired_at IS NULL`, [streamName]);
422
+ return {
423
+ messageCount: parseInt(res.rows[0].available_count, 10),
424
+ };
425
+ }
426
+ catch (error) {
427
+ this.logger.error(`postgres-stream-stats-error-${streamName}`, { error });
428
+ throw error;
429
+ }
430
+ }
431
+ async getStreamDepth(streamName) {
432
+ const stats = await this.getStreamStats(streamName);
433
+ return stats.messageCount;
434
+ }
435
+ async getStreamDepths(streamNames) {
436
+ const client = this.streamClient;
437
+ const tableName = this.getTableName();
438
+ try {
439
+ const streams = streamNames.map((s) => s.stream);
440
+ const res = await client.query(`SELECT stream_name, COUNT(*) AS count
441
+ FROM ${tableName}
442
+ WHERE stream_name = ANY($1::text[])
443
+ GROUP BY stream_name`, [streams]);
444
+ const result = res.rows.map((row) => ({
445
+ stream: row.stream_name,
446
+ depth: parseInt(row.count, 10),
447
+ }));
448
+ return result;
449
+ }
450
+ catch (error) {
451
+ this.logger.error('postgres-stream-depth-error', { error });
452
+ throw error;
453
+ }
454
+ }
455
+ async trimStream(streamName, options) {
456
+ const client = this.streamClient;
457
+ const tableName = this.getTableName();
458
+ try {
459
+ let expiredCount = 0;
460
+ if (options.maxLen !== undefined) {
461
+ const res = await client.query(`WITH to_expire AS (
462
+ SELECT id FROM ${tableName}
463
+ WHERE stream_name = $1
464
+ ORDER BY id ASC
465
+ OFFSET $2
466
+ )
467
+ UPDATE ${tableName}
468
+ SET expired_at = NOW()
469
+ WHERE id IN (SELECT id FROM to_expire)`, [streamName, options.maxLen]);
470
+ expiredCount += res.rowCount;
471
+ }
472
+ if (options.maxAge !== undefined) {
473
+ const res = await client.query(`UPDATE ${tableName}
474
+ SET expired_at = NOW()
475
+ WHERE stream_name = $1 AND created_at < NOW() - INTERVAL '${options.maxAge} milliseconds'`, [streamName]);
476
+ expiredCount += res.rowCount;
477
+ }
478
+ return expiredCount;
479
+ }
480
+ catch (error) {
481
+ this.logger.error(`postgres-stream-trim-error-${streamName}`, { error });
482
+ throw error;
483
+ }
484
+ }
485
+ getProviderSpecificFeatures() {
486
+ return {
487
+ supportsBatching: true,
488
+ supportsDeadLetterQueue: false,
489
+ supportsOrdering: true,
490
+ supportsTrimming: true,
491
+ supportsRetry: false,
492
+ supportsNotifications: this.isNotificationsEnabled(),
493
+ maxMessageSize: 1024 * 1024,
494
+ maxBatchSize: 256,
495
+ };
496
+ }
497
+ // Cleanup method to be called when shutting down
498
+ async cleanup() {
499
+ // Stop fallback poller
500
+ if (this.fallbackIntervalId) {
501
+ clearInterval(this.fallbackIntervalId);
502
+ this.fallbackIntervalId = null;
503
+ }
504
+ // Remove notification handler
505
+ if (this.streamClient.removeAllListeners) {
506
+ this.streamClient.removeAllListeners('notification');
507
+ }
508
+ else if (this.streamClient.off) {
509
+ this.streamClient.off('notification', this.notificationHandlerBound);
510
+ }
511
+ // Stop all consumers and unlisten from channels
512
+ const consumers = Array.from(this.notificationConsumers.entries());
513
+ for (const [key, consumer] of consumers) {
514
+ await this.stopNotificationConsumer(consumer.streamName, consumer.groupName);
515
+ }
516
+ this.notificationConsumers.clear();
517
+ }
518
+ }
519
+ exports.PostgresStreamService = PostgresStreamService;
@@ -0,0 +1,61 @@
1
+ import { ILogger } from '../../../logger';
2
+ import { StreamService } from '../../index';
3
+ import { IORedisClientType, IORedisMultiType } from '../../../../types/redis';
4
+ import { PublishMessageConfig, StreamConfig, StreamMessage, StreamStats } from '../../../../types/stream';
5
+ import { KeyStoreParams, StringAnyType } from '../../../../types';
6
+ import { KeyType } from '../../../../modules/key';
7
+ import { ProviderTransaction } from '../../../../types/provider';
8
+ declare class IORedisStreamService extends StreamService<IORedisClientType, IORedisMultiType> {
9
+ constructor(streamClient: IORedisClientType, storeClient: IORedisClientType, config?: StreamConfig);
10
+ init(namespace: string, appId: string, logger: ILogger): Promise<void>;
11
+ mintKey(type: KeyType, params: KeyStoreParams): string;
12
+ transact(): ProviderTransaction;
13
+ createStream(streamName: string): Promise<boolean>;
14
+ deleteStream(streamName: string): Promise<boolean>;
15
+ createConsumerGroup(key: string, groupName: string): Promise<boolean>;
16
+ deleteConsumerGroup(streamName: string, groupName: string): Promise<boolean>;
17
+ publishMessages(streamName: string, messages: string[], options?: PublishMessageConfig): Promise<string[]>;
18
+ consumeMessages(streamName: string, groupName: string, consumerName: string, options?: {
19
+ batchSize?: number;
20
+ blockTimeout?: number;
21
+ }): Promise<StreamMessage[]>;
22
+ ackAndDelete(stream: string, group: string, ids: string[]): Promise<number>;
23
+ acknowledgeMessages(stream: string, group: string, ids: string[], options?: StringAnyType): Promise<number | IORedisMultiType>;
24
+ deleteMessages(stream: string, group: string, ids: string[], options?: StringAnyType): Promise<number | IORedisMultiType>;
25
+ getPendingMessages(stream: string, group: string, count?: number, consumer?: string): Promise<[string, string, number, [string, number][]][] | [string, string, number, number] | unknown[]>;
26
+ retryMessages(streamName: string, groupName: string, options?: {
27
+ consumerName?: string;
28
+ minIdleTime?: number;
29
+ messageIds?: string[];
30
+ delay?: number;
31
+ maxRetries?: number;
32
+ limit?: number;
33
+ }): Promise<StreamMessage[]>;
34
+ claimMessage(streamName: string, groupName: string, consumerName: string, minIdleTime: number, messageId: string, ...args: string[]): Promise<StreamMessage>;
35
+ getStreamStats(streamName: string): Promise<StreamStats>;
36
+ getStreamDepth(streamName: string, options?: {
37
+ multi: IORedisMultiType;
38
+ }): Promise<number>;
39
+ getStreamDepths(streamNames: {
40
+ stream: string;
41
+ }[]): Promise<{
42
+ stream: string;
43
+ depth: number;
44
+ }[]>;
45
+ trimStream(streamName: string, options: {
46
+ maxLen?: number;
47
+ maxAge?: number;
48
+ exactLimit?: boolean;
49
+ }): Promise<number>;
50
+ getProviderSpecificFeatures(): {
51
+ supportsBatching: boolean;
52
+ supportsDeadLetterQueue: boolean;
53
+ supportsOrdering: boolean;
54
+ supportsTrimming: boolean;
55
+ supportsRetry: boolean;
56
+ supportsNotifications: boolean;
57
+ maxMessageSize: number;
58
+ maxBatchSize: number;
59
+ };
60
+ }
61
+ export { IORedisStreamService };