@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.
- package/build/modules/enums.d.ts +110 -0
- package/build/modules/enums.js +134 -0
- package/build/modules/errors.d.ts +124 -0
- package/build/modules/errors.js +191 -0
- package/build/modules/key.d.ts +66 -0
- package/build/modules/key.js +190 -0
- package/build/modules/storage.d.ts +3 -0
- package/build/modules/storage.js +5 -0
- package/build/modules/utils.d.ts +119 -0
- package/build/modules/utils.js +374 -0
- package/build/package.json +1 -1
- package/build/services/activities/activity.d.ts +104 -0
- package/build/services/activities/activity.js +549 -0
- package/build/services/activities/await.d.ts +12 -0
- package/build/services/activities/await.js +114 -0
- package/build/services/activities/cycle.d.ts +19 -0
- package/build/services/activities/cycle.js +112 -0
- package/build/services/activities/hook.d.ts +27 -0
- package/build/services/activities/hook.js +168 -0
- package/build/services/activities/index.d.ts +19 -0
- package/build/services/activities/index.js +20 -0
- package/build/services/activities/interrupt.d.ts +16 -0
- package/build/services/activities/interrupt.js +158 -0
- package/build/services/activities/signal.d.ts +20 -0
- package/build/services/activities/signal.js +134 -0
- package/build/services/activities/trigger.d.ts +37 -0
- package/build/services/activities/trigger.js +246 -0
- package/build/services/activities/worker.d.ts +12 -0
- package/build/services/activities/worker.js +106 -0
- package/build/services/collator/index.d.ts +111 -0
- package/build/services/collator/index.js +293 -0
- package/build/services/compiler/deployer.d.ts +40 -0
- package/build/services/compiler/deployer.js +488 -0
- package/build/services/compiler/index.d.ts +32 -0
- package/build/services/compiler/index.js +112 -0
- package/build/services/compiler/validator.d.ts +34 -0
- package/build/services/compiler/validator.js +147 -0
- package/build/services/connector/factory.d.ts +22 -0
- package/build/services/connector/factory.js +99 -0
- package/build/services/connector/index.d.ts +30 -0
- package/build/services/connector/index.js +54 -0
- package/build/services/connector/providers/ioredis.d.ts +9 -0
- package/build/services/connector/providers/ioredis.js +26 -0
- package/build/services/connector/providers/nats.d.ts +9 -0
- package/build/services/connector/providers/nats.js +34 -0
- package/build/services/connector/providers/postgres.d.ts +20 -0
- package/build/services/connector/providers/postgres.js +102 -0
- package/build/services/connector/providers/redis.d.ts +9 -0
- package/build/services/connector/providers/redis.js +38 -0
- package/build/services/engine/index.d.ts +264 -0
- package/build/services/engine/index.js +761 -0
- package/build/services/exporter/index.d.ts +44 -0
- package/build/services/exporter/index.js +126 -0
- package/build/services/hotmesh/index.d.ts +483 -0
- package/build/services/hotmesh/index.js +622 -0
- package/build/services/logger/index.d.ts +16 -0
- package/build/services/logger/index.js +54 -0
- package/build/services/mapper/index.d.ts +28 -0
- package/build/services/mapper/index.js +81 -0
- package/build/services/memflow/client.d.ts +108 -0
- package/build/services/memflow/client.js +372 -0
- package/build/services/memflow/connection.d.ts +23 -0
- package/build/services/memflow/connection.js +33 -0
- package/build/services/memflow/context.d.ts +143 -0
- package/build/services/memflow/context.js +299 -0
- package/build/services/memflow/exporter.d.ts +51 -0
- package/build/services/memflow/exporter.js +215 -0
- package/build/services/memflow/handle.d.ts +90 -0
- package/build/services/memflow/handle.js +176 -0
- package/build/services/memflow/index.d.ts +116 -0
- package/build/services/memflow/index.js +122 -0
- package/build/services/memflow/schemas/factory.d.ts +29 -0
- package/build/services/memflow/schemas/factory.js +2492 -0
- package/build/services/memflow/search.d.ts +142 -0
- package/build/services/memflow/search.js +320 -0
- package/build/services/memflow/worker.d.ts +124 -0
- package/build/services/memflow/worker.js +514 -0
- package/build/services/memflow/workflow/all.d.ts +7 -0
- package/build/services/memflow/workflow/all.js +15 -0
- package/build/services/memflow/workflow/common.d.ts +20 -0
- package/build/services/memflow/workflow/common.js +47 -0
- package/build/services/memflow/workflow/context.d.ts +6 -0
- package/build/services/memflow/workflow/context.js +45 -0
- package/build/services/memflow/workflow/contextMethods.d.ts +14 -0
- package/build/services/memflow/workflow/contextMethods.js +33 -0
- package/build/services/memflow/workflow/didRun.d.ts +7 -0
- package/build/services/memflow/workflow/didRun.js +22 -0
- package/build/services/memflow/workflow/emit.d.ts +11 -0
- package/build/services/memflow/workflow/emit.js +29 -0
- package/build/services/memflow/workflow/enrich.d.ts +9 -0
- package/build/services/memflow/workflow/enrich.js +17 -0
- package/build/services/memflow/workflow/execChild.d.ts +18 -0
- package/build/services/memflow/workflow/execChild.js +102 -0
- package/build/services/memflow/workflow/execHook.d.ts +65 -0
- package/build/services/memflow/workflow/execHook.js +73 -0
- package/build/services/memflow/workflow/hook.d.ts +9 -0
- package/build/services/memflow/workflow/hook.js +56 -0
- package/build/services/memflow/workflow/index.d.ts +74 -0
- package/build/services/memflow/workflow/index.js +87 -0
- package/build/services/memflow/workflow/interrupt.d.ts +9 -0
- package/build/services/memflow/workflow/interrupt.js +24 -0
- package/build/services/memflow/workflow/isSideEffectAllowed.d.ts +10 -0
- package/build/services/memflow/workflow/isSideEffectAllowed.js +33 -0
- package/build/services/memflow/workflow/proxyActivities.d.ts +20 -0
- package/build/services/memflow/workflow/proxyActivities.js +97 -0
- package/build/services/memflow/workflow/random.d.ts +6 -0
- package/build/services/memflow/workflow/random.js +16 -0
- package/build/services/memflow/workflow/searchMethods.d.ts +6 -0
- package/build/services/memflow/workflow/searchMethods.js +25 -0
- package/build/services/memflow/workflow/signal.d.ts +7 -0
- package/build/services/memflow/workflow/signal.js +28 -0
- package/build/services/memflow/workflow/sleepFor.d.ts +8 -0
- package/build/services/memflow/workflow/sleepFor.js +35 -0
- package/build/services/memflow/workflow/trace.d.ts +14 -0
- package/build/services/memflow/workflow/trace.js +33 -0
- package/build/services/memflow/workflow/waitFor.d.ts +8 -0
- package/build/services/memflow/workflow/waitFor.js +35 -0
- package/build/services/meshcall/index.d.ts +194 -0
- package/build/services/meshcall/index.js +452 -0
- package/build/services/meshcall/schemas/factory.d.ts +9 -0
- package/build/services/meshcall/schemas/factory.js +189 -0
- package/build/services/meshdata/index.d.ts +795 -0
- package/build/services/meshdata/index.js +1235 -0
- package/build/services/meshos/index.d.ts +293 -0
- package/build/services/meshos/index.js +547 -0
- package/build/services/pipe/functions/array.d.ts +17 -0
- package/build/services/pipe/functions/array.js +74 -0
- package/build/services/pipe/functions/bitwise.d.ts +9 -0
- package/build/services/pipe/functions/bitwise.js +24 -0
- package/build/services/pipe/functions/conditional.d.ts +13 -0
- package/build/services/pipe/functions/conditional.js +36 -0
- package/build/services/pipe/functions/cron.d.ts +12 -0
- package/build/services/pipe/functions/cron.js +40 -0
- package/build/services/pipe/functions/date.d.ts +58 -0
- package/build/services/pipe/functions/date.js +171 -0
- package/build/services/pipe/functions/index.d.ts +29 -0
- package/build/services/pipe/functions/index.js +30 -0
- package/build/services/pipe/functions/json.d.ts +5 -0
- package/build/services/pipe/functions/json.js +12 -0
- package/build/services/pipe/functions/logical.d.ts +5 -0
- package/build/services/pipe/functions/logical.js +12 -0
- package/build/services/pipe/functions/math.d.ts +42 -0
- package/build/services/pipe/functions/math.js +184 -0
- package/build/services/pipe/functions/number.d.ts +21 -0
- package/build/services/pipe/functions/number.js +60 -0
- package/build/services/pipe/functions/object.d.ts +25 -0
- package/build/services/pipe/functions/object.js +81 -0
- package/build/services/pipe/functions/string.d.ts +23 -0
- package/build/services/pipe/functions/string.js +69 -0
- package/build/services/pipe/functions/symbol.d.ts +12 -0
- package/build/services/pipe/functions/symbol.js +33 -0
- package/build/services/pipe/functions/unary.d.ts +7 -0
- package/build/services/pipe/functions/unary.js +18 -0
- package/build/services/pipe/index.d.ts +48 -0
- package/build/services/pipe/index.js +242 -0
- package/build/services/quorum/index.d.ts +90 -0
- package/build/services/quorum/index.js +263 -0
- package/build/services/reporter/index.d.ts +50 -0
- package/build/services/reporter/index.js +348 -0
- package/build/services/router/config/index.d.ts +11 -0
- package/build/services/router/config/index.js +36 -0
- package/build/services/router/consumption/index.d.ts +34 -0
- package/build/services/router/consumption/index.js +395 -0
- package/build/services/router/error-handling/index.d.ts +8 -0
- package/build/services/router/error-handling/index.js +98 -0
- package/build/services/router/index.d.ts +57 -0
- package/build/services/router/index.js +121 -0
- package/build/services/router/lifecycle/index.d.ts +27 -0
- package/build/services/router/lifecycle/index.js +80 -0
- package/build/services/router/telemetry/index.d.ts +11 -0
- package/build/services/router/telemetry/index.js +32 -0
- package/build/services/router/throttling/index.d.ts +23 -0
- package/build/services/router/throttling/index.js +76 -0
- package/build/services/search/factory.d.ts +7 -0
- package/build/services/search/factory.js +24 -0
- package/build/services/search/index.d.ts +23 -0
- package/build/services/search/index.js +10 -0
- package/build/services/search/providers/postgres/postgres.d.ts +25 -0
- package/build/services/search/providers/postgres/postgres.js +149 -0
- package/build/services/search/providers/redis/ioredis.d.ts +19 -0
- package/build/services/search/providers/redis/ioredis.js +121 -0
- package/build/services/search/providers/redis/redis.d.ts +19 -0
- package/build/services/search/providers/redis/redis.js +134 -0
- package/build/services/serializer/index.d.ts +42 -0
- package/build/services/serializer/index.js +282 -0
- package/build/services/store/cache.d.ts +67 -0
- package/build/services/store/cache.js +128 -0
- package/build/services/store/factory.d.ts +8 -0
- package/build/services/store/factory.js +24 -0
- package/build/services/store/index.d.ts +89 -0
- package/build/services/store/index.js +9 -0
- package/build/services/store/providers/postgres/kvsql.d.ts +168 -0
- package/build/services/store/providers/postgres/kvsql.js +198 -0
- package/build/services/store/providers/postgres/kvtables.d.ts +20 -0
- package/build/services/store/providers/postgres/kvtables.js +441 -0
- package/build/services/store/providers/postgres/kvtransaction.d.ts +36 -0
- package/build/services/store/providers/postgres/kvtransaction.js +248 -0
- package/build/services/store/providers/postgres/kvtypes/hash.d.ts +60 -0
- package/build/services/store/providers/postgres/kvtypes/hash.js +1287 -0
- package/build/services/store/providers/postgres/kvtypes/list.d.ts +33 -0
- package/build/services/store/providers/postgres/kvtypes/list.js +194 -0
- package/build/services/store/providers/postgres/kvtypes/string.d.ts +20 -0
- package/build/services/store/providers/postgres/kvtypes/string.js +115 -0
- package/build/services/store/providers/postgres/kvtypes/zset.d.ts +41 -0
- package/build/services/store/providers/postgres/kvtypes/zset.js +214 -0
- package/build/services/store/providers/postgres/postgres.d.ts +145 -0
- package/build/services/store/providers/postgres/postgres.js +1036 -0
- package/build/services/store/providers/redis/_base.d.ts +137 -0
- package/build/services/store/providers/redis/_base.js +980 -0
- package/build/services/store/providers/redis/ioredis.d.ts +20 -0
- package/build/services/store/providers/redis/ioredis.js +180 -0
- package/build/services/store/providers/redis/redis.d.ts +18 -0
- package/build/services/store/providers/redis/redis.js +199 -0
- package/build/services/store/providers/store-initializable.d.ts +5 -0
- package/build/services/store/providers/store-initializable.js +2 -0
- package/build/services/stream/factory.d.ts +8 -0
- package/build/services/stream/factory.js +37 -0
- package/build/services/stream/index.d.ts +69 -0
- package/build/services/stream/index.js +11 -0
- package/build/services/stream/providers/nats/nats.d.ts +60 -0
- package/build/services/stream/providers/nats/nats.js +225 -0
- package/build/services/stream/providers/postgres/kvtables.d.ts +3 -0
- package/build/services/stream/providers/postgres/kvtables.js +146 -0
- package/build/services/stream/providers/postgres/postgres.d.ts +107 -0
- package/build/services/stream/providers/postgres/postgres.js +519 -0
- package/build/services/stream/providers/redis/ioredis.d.ts +61 -0
- package/build/services/stream/providers/redis/ioredis.js +272 -0
- package/build/services/stream/providers/redis/redis.d.ts +61 -0
- package/build/services/stream/providers/redis/redis.js +305 -0
- package/build/services/stream/providers/stream-initializable.d.ts +4 -0
- package/build/services/stream/providers/stream-initializable.js +2 -0
- package/build/services/sub/factory.d.ts +7 -0
- package/build/services/sub/factory.js +29 -0
- package/build/services/sub/index.d.ts +22 -0
- package/build/services/sub/index.js +10 -0
- package/build/services/sub/providers/nats/nats.d.ts +19 -0
- package/build/services/sub/providers/nats/nats.js +105 -0
- package/build/services/sub/providers/postgres/postgres.d.ts +19 -0
- package/build/services/sub/providers/postgres/postgres.js +92 -0
- package/build/services/sub/providers/redis/ioredis.d.ts +17 -0
- package/build/services/sub/providers/redis/ioredis.js +81 -0
- package/build/services/sub/providers/redis/redis.d.ts +17 -0
- package/build/services/sub/providers/redis/redis.js +72 -0
- package/build/services/task/index.d.ts +36 -0
- package/build/services/task/index.js +206 -0
- package/build/services/telemetry/index.d.ts +52 -0
- package/build/services/telemetry/index.js +306 -0
- package/build/services/worker/index.d.ts +77 -0
- package/build/services/worker/index.js +197 -0
- package/package.json +1 -1
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
- package/typedoc.json +0 -47
- package/types/activity.ts +0 -268
- package/types/app.ts +0 -20
- package/types/async.ts +0 -6
- package/types/cache.ts +0 -1
- package/types/collator.ts +0 -9
- package/types/error.ts +0 -56
- package/types/exporter.ts +0 -102
- package/types/hook.ts +0 -44
- package/types/hotmesh.ts +0 -314
- package/types/index.ts +0 -306
- package/types/job.ts +0 -233
- package/types/logger.ts +0 -8
- package/types/manifest.ts +0 -70
- package/types/map.ts +0 -5
- package/types/memflow.ts +0 -645
- package/types/meshcall.ts +0 -235
- package/types/meshdata.ts +0 -278
- package/types/ms.d.ts +0 -7
- package/types/nats.ts +0 -270
- package/types/pipe.ts +0 -90
- package/types/postgres.ts +0 -114
- package/types/provider.ts +0 -161
- package/types/quorum.ts +0 -167
- package/types/redis.ts +0 -404
- package/types/serializer.ts +0 -40
- package/types/stats.ts +0 -117
- package/types/stream.ts +0 -231
- package/types/task.ts +0 -7
- package/types/telemetry.ts +0 -16
- 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 };
|