@hotmeshio/hotmesh 0.6.1 → 0.7.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.
- package/README.md +179 -142
- package/build/modules/enums.d.ts +7 -0
- package/build/modules/enums.js +16 -1
- package/build/modules/utils.d.ts +27 -0
- package/build/modules/utils.js +52 -1
- package/build/package.json +10 -8
- package/build/services/connector/providers/postgres.js +3 -0
- package/build/services/hotmesh/index.d.ts +66 -15
- package/build/services/hotmesh/index.js +84 -15
- package/build/services/memflow/index.d.ts +100 -14
- package/build/services/memflow/index.js +100 -14
- package/build/services/memflow/worker.d.ts +97 -0
- package/build/services/memflow/worker.js +217 -0
- package/build/services/memflow/workflow/proxyActivities.d.ts +74 -3
- package/build/services/memflow/workflow/proxyActivities.js +81 -4
- package/build/services/router/consumption/index.d.ts +2 -1
- package/build/services/router/consumption/index.js +38 -2
- package/build/services/router/error-handling/index.d.ts +3 -3
- package/build/services/router/error-handling/index.js +48 -13
- package/build/services/router/index.d.ts +1 -0
- package/build/services/router/index.js +2 -1
- package/build/services/store/index.d.ts +3 -2
- package/build/services/store/providers/postgres/kvtypes/hash/basic.js +36 -6
- package/build/services/store/providers/postgres/kvtypes/hash/expire.js +12 -2
- package/build/services/store/providers/postgres/kvtypes/hash/scan.js +30 -10
- package/build/services/store/providers/postgres/kvtypes/list.js +68 -10
- package/build/services/store/providers/postgres/kvtypes/string.js +60 -10
- package/build/services/store/providers/postgres/kvtypes/zset.js +92 -22
- package/build/services/store/providers/postgres/postgres.d.ts +3 -3
- package/build/services/store/providers/redis/_base.d.ts +3 -3
- package/build/services/store/providers/redis/ioredis.js +17 -7
- package/build/services/stream/providers/postgres/kvtables.js +76 -23
- package/build/services/stream/providers/postgres/lifecycle.d.ts +19 -0
- package/build/services/stream/providers/postgres/lifecycle.js +54 -0
- package/build/services/stream/providers/postgres/messages.d.ts +56 -0
- package/build/services/stream/providers/postgres/messages.js +253 -0
- package/build/services/stream/providers/postgres/notifications.d.ts +59 -0
- package/build/services/stream/providers/postgres/notifications.js +357 -0
- package/build/services/stream/providers/postgres/postgres.d.ts +110 -11
- package/build/services/stream/providers/postgres/postgres.js +196 -488
- package/build/services/stream/providers/postgres/scout.d.ts +68 -0
- package/build/services/stream/providers/postgres/scout.js +233 -0
- package/build/services/stream/providers/postgres/stats.d.ts +49 -0
- package/build/services/stream/providers/postgres/stats.js +113 -0
- package/build/services/sub/providers/postgres/postgres.js +37 -5
- package/build/services/sub/providers/redis/ioredis.js +13 -2
- package/build/services/sub/providers/redis/redis.js +13 -2
- package/build/services/worker/index.d.ts +1 -0
- package/build/services/worker/index.js +2 -0
- package/build/types/hotmesh.d.ts +42 -2
- package/build/types/index.d.ts +3 -3
- package/build/types/memflow.d.ts +32 -0
- package/build/types/provider.d.ts +16 -0
- package/build/types/stream.d.ts +92 -1
- package/package.json +10 -8
|
@@ -5,18 +5,37 @@ const utils_1 = require("../../../modules/utils");
|
|
|
5
5
|
const config_1 = require("../config");
|
|
6
6
|
const stream_1 = require("../../../types/stream");
|
|
7
7
|
class ErrorHandler {
|
|
8
|
-
shouldRetry(input, output) {
|
|
9
|
-
|
|
8
|
+
shouldRetry(input, output, retryPolicy) {
|
|
9
|
+
const tryCount = input.metadata.try || 0;
|
|
10
|
+
// Priority 1: Use structured retry policy (from stream columns or config)
|
|
11
|
+
if (retryPolicy) {
|
|
12
|
+
const maxAttempts = retryPolicy.maximumAttempts || 3;
|
|
13
|
+
const backoffCoeff = retryPolicy.backoffCoefficient || 10;
|
|
14
|
+
const maxInterval = typeof retryPolicy.maximumInterval === 'string'
|
|
15
|
+
? parseInt(retryPolicy.maximumInterval)
|
|
16
|
+
: (retryPolicy.maximumInterval || 120);
|
|
17
|
+
// Check if we can retry (next attempt would be attempt #tryCount+2, must be <= maxAttempts)
|
|
18
|
+
// tryCount=0 is 1st attempt, tryCount=1 is 2nd attempt, etc.
|
|
19
|
+
// So after tryCount, we've made (tryCount + 1) attempts
|
|
20
|
+
// We can retry if (tryCount + 1) < maxAttempts
|
|
21
|
+
if ((tryCount + 1) < maxAttempts) {
|
|
22
|
+
// Exponential backoff: min(coefficient^(try+1), maxInterval)
|
|
23
|
+
// First retry (after try=0): coefficient^1
|
|
24
|
+
// Second retry (after try=1): coefficient^2, etc.
|
|
25
|
+
const backoffSeconds = Math.min(Math.pow(backoffCoeff, tryCount + 1), maxInterval);
|
|
26
|
+
return [true, backoffSeconds * 1000]; // Convert to milliseconds
|
|
27
|
+
}
|
|
28
|
+
return [false, 0];
|
|
29
|
+
}
|
|
30
|
+
// Priority 2: Use message-level policies (existing behavior)
|
|
10
31
|
const policies = input.policies?.retry;
|
|
11
32
|
const errorCode = output.code.toString();
|
|
12
33
|
const policy = policies?.[errorCode];
|
|
13
34
|
const maxRetries = policy?.[0];
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
//only possible values for tryCount are 0, 1, 2
|
|
17
|
-
if (maxRetries > tryCount) {
|
|
35
|
+
const cappedTryCount = Math.min(tryCount, config_1.HMSH_MAX_RETRIES);
|
|
36
|
+
if (maxRetries > cappedTryCount) {
|
|
18
37
|
// 10ms, 100ms, or 1000ms delays between system retries
|
|
19
|
-
return [true, Math.pow(10,
|
|
38
|
+
return [true, Math.pow(10, cappedTryCount + 1)];
|
|
20
39
|
}
|
|
21
40
|
return [false, 0];
|
|
22
41
|
}
|
|
@@ -78,16 +97,32 @@ class ErrorHandler {
|
|
|
78
97
|
data,
|
|
79
98
|
};
|
|
80
99
|
}
|
|
81
|
-
async handleRetry(input, output, publishMessage) {
|
|
82
|
-
const [shouldRetry, timeout] = this.shouldRetry(input, output);
|
|
100
|
+
async handleRetry(input, output, publishMessage, retryPolicy) {
|
|
101
|
+
const [shouldRetry, timeout] = this.shouldRetry(input, output, retryPolicy);
|
|
83
102
|
if (shouldRetry) {
|
|
84
|
-
|
|
85
|
-
|
|
103
|
+
// Only sleep if no retryPolicy (legacy behavior for backward compatibility)
|
|
104
|
+
// With retryPolicy, use visibility timeout instead of in-memory sleep
|
|
105
|
+
if (!retryPolicy) {
|
|
106
|
+
await (0, utils_1.sleepFor)(timeout);
|
|
107
|
+
}
|
|
108
|
+
// Create new message with incremented try count
|
|
109
|
+
const newMessage = {
|
|
86
110
|
data: input.data,
|
|
87
|
-
//note: retain guid (this is a retry attempt)
|
|
88
111
|
metadata: { ...input.metadata, try: (input.metadata.try || 0) + 1 },
|
|
89
112
|
policies: input.policies,
|
|
90
|
-
}
|
|
113
|
+
};
|
|
114
|
+
// Propagate retry config to new message (for immutable pattern)
|
|
115
|
+
if (input._streamRetryConfig) {
|
|
116
|
+
newMessage._streamRetryConfig = input._streamRetryConfig;
|
|
117
|
+
}
|
|
118
|
+
// Add visibility delay for production-ready retry with retryPolicy
|
|
119
|
+
if (retryPolicy && timeout > 0) {
|
|
120
|
+
newMessage._visibilityDelayMs = timeout;
|
|
121
|
+
}
|
|
122
|
+
// Track retry attempt count in database
|
|
123
|
+
const currentAttempt = input._retryAttempt || 0;
|
|
124
|
+
newMessage._retryAttempt = currentAttempt + 1;
|
|
125
|
+
return (await publishMessage(input.metadata.topic, newMessage));
|
|
91
126
|
}
|
|
92
127
|
else {
|
|
93
128
|
const structuredError = this.structureError(input, output);
|
|
@@ -13,6 +13,7 @@ declare class Router<S extends StreamService<ProviderClient, ProviderTransaction
|
|
|
13
13
|
reclaimCount: number;
|
|
14
14
|
logger: ILogger;
|
|
15
15
|
readonly: boolean;
|
|
16
|
+
retryPolicy: import('../../types/stream').RetryPolicy | undefined;
|
|
16
17
|
errorCount: number;
|
|
17
18
|
counts: {
|
|
18
19
|
[key: string]: number;
|
|
@@ -29,11 +29,12 @@ class Router {
|
|
|
29
29
|
this.reclaimCount = enhancedConfig.reclaimCount;
|
|
30
30
|
this.logger = logger;
|
|
31
31
|
this.readonly = enhancedConfig.readonly;
|
|
32
|
+
this.retryPolicy = enhancedConfig.retryPolicy;
|
|
32
33
|
// Initialize submodule managers
|
|
33
34
|
this.throttleManager = new throttling_1.ThrottleManager(enhancedConfig.throttle);
|
|
34
35
|
this.errorHandler = new error_handling_1.ErrorHandler();
|
|
35
36
|
this.lifecycleManager = new lifecycle_1.LifecycleManager(this.readonly, this.topic, this.logger, this.stream);
|
|
36
|
-
this.consumptionManager = new consumption_1.ConsumptionManager(this.stream, this.logger, this.throttleManager, this.errorHandler, this.lifecycleManager, this.reclaimDelay, this.reclaimCount, this.appId, this.role, this);
|
|
37
|
+
this.consumptionManager = new consumption_1.ConsumptionManager(this.stream, this.logger, this.throttleManager, this.errorHandler, this.lifecycleManager, this.reclaimDelay, this.reclaimCount, this.appId, this.role, this, this.retryPolicy);
|
|
37
38
|
this.resetThrottleState();
|
|
38
39
|
}
|
|
39
40
|
// Legacy compatibility methods
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { KeyStoreParams, KeyType } from '../../modules/key';
|
|
2
|
+
import { ScoutType } from '../../types/hotmesh';
|
|
2
3
|
import { ILogger } from '../logger';
|
|
3
4
|
import { SerializerService as Serializer } from '../serializer';
|
|
4
5
|
import { Consumes } from '../../types/activity';
|
|
@@ -27,8 +28,8 @@ declare abstract class StoreService<Provider extends ProviderClient, Transaction
|
|
|
27
28
|
abstract getApp(id: string, refresh?: boolean): Promise<any>;
|
|
28
29
|
abstract setApp(id: string, version: string): Promise<any>;
|
|
29
30
|
abstract activateAppVersion(id: string, version: string): Promise<boolean>;
|
|
30
|
-
abstract reserveScoutRole(scoutType:
|
|
31
|
-
abstract releaseScoutRole(scoutType:
|
|
31
|
+
abstract reserveScoutRole(scoutType: ScoutType, delay?: number): Promise<boolean>;
|
|
32
|
+
abstract releaseScoutRole(scoutType: ScoutType): Promise<boolean>;
|
|
32
33
|
abstract reserveSymbolRange(target: string, size: number, type: 'JOB' | 'ACTIVITY', tryCount?: number): Promise<[number, number, Symbols]>;
|
|
33
34
|
abstract getSymbols(activityId: string): Promise<Symbols>;
|
|
34
35
|
abstract addSymbols(activityId: string, symbols: Symbols): Promise<boolean>;
|
|
@@ -60,8 +60,18 @@ function createBasicOperations(context) {
|
|
|
60
60
|
return Promise.resolve(null);
|
|
61
61
|
}
|
|
62
62
|
else {
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
try {
|
|
64
|
+
const res = await context.pgClient.query(sql, params);
|
|
65
|
+
return res.rows[0]?.value || null;
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
// Connection closed during test cleanup - return null
|
|
69
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
// Re-throw unexpected errors
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
65
75
|
}
|
|
66
76
|
},
|
|
67
77
|
async hdel(key, fields, multi) {
|
|
@@ -75,8 +85,18 @@ function createBasicOperations(context) {
|
|
|
75
85
|
return Promise.resolve(0);
|
|
76
86
|
}
|
|
77
87
|
else {
|
|
78
|
-
|
|
79
|
-
|
|
88
|
+
try {
|
|
89
|
+
const res = await context.pgClient.query(sql, params);
|
|
90
|
+
return Number(res.rows[0]?.count || 0);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
// Connection closed during test cleanup - return 0
|
|
94
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
// Re-throw unexpected errors
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
80
100
|
}
|
|
81
101
|
},
|
|
82
102
|
async hmget(key, fields, multi) {
|
|
@@ -150,8 +170,18 @@ function createBasicOperations(context) {
|
|
|
150
170
|
return Promise.resolve(0);
|
|
151
171
|
}
|
|
152
172
|
else {
|
|
153
|
-
|
|
154
|
-
|
|
173
|
+
try {
|
|
174
|
+
const res = await context.pgClient.query(sql, params);
|
|
175
|
+
return parseFloat(res.rows[0].value);
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
// Connection closed during test cleanup - return 0
|
|
179
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
// Re-throw unexpected errors
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
155
185
|
}
|
|
156
186
|
},
|
|
157
187
|
};
|
|
@@ -10,8 +10,18 @@ function createExpireOperations(context) {
|
|
|
10
10
|
return Promise.resolve(true);
|
|
11
11
|
}
|
|
12
12
|
else {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
try {
|
|
14
|
+
const res = await context.pgClient.query(sql, params);
|
|
15
|
+
return res.rowCount > 0;
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
// Connection closed during test cleanup - return false
|
|
19
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
// Re-throw unexpected errors
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
15
25
|
}
|
|
16
26
|
},
|
|
17
27
|
};
|
|
@@ -17,13 +17,23 @@ function createScanOperations(context) {
|
|
|
17
17
|
return Promise.resolve({ cursor: '0', items: {} });
|
|
18
18
|
}
|
|
19
19
|
else {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
try {
|
|
21
|
+
const res = await context.pgClient.query(sql, params);
|
|
22
|
+
const items = {};
|
|
23
|
+
for (const row of res.rows) {
|
|
24
|
+
items[row.field] = row.value;
|
|
25
|
+
}
|
|
26
|
+
const newCursor = res.rowCount < count ? 0 : Number(cursor) + res.rowCount;
|
|
27
|
+
return { cursor: newCursor.toString(), items };
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
// Connection closed during test cleanup - return empty result
|
|
31
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
32
|
+
return { cursor: '0', items: {} };
|
|
33
|
+
}
|
|
34
|
+
// Re-throw unexpected errors
|
|
35
|
+
throw error;
|
|
24
36
|
}
|
|
25
|
-
const newCursor = res.rowCount < count ? 0 : Number(cursor) + res.rowCount;
|
|
26
|
-
return { cursor: newCursor.toString(), items };
|
|
27
37
|
}
|
|
28
38
|
},
|
|
29
39
|
async scan(cursor, count = 10, pattern, multi) {
|
|
@@ -37,10 +47,20 @@ function createScanOperations(context) {
|
|
|
37
47
|
return Promise.resolve({ cursor: 0, keys: [] });
|
|
38
48
|
}
|
|
39
49
|
else {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
try {
|
|
51
|
+
const res = await context.pgClient.query(sql, params);
|
|
52
|
+
const keys = res.rows.map((row) => row.key);
|
|
53
|
+
const newCursor = cursor + res.rowCount;
|
|
54
|
+
return { cursor: newCursor, keys };
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
// Connection closed during test cleanup - return empty result
|
|
58
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
59
|
+
return { cursor: 0, keys: [] };
|
|
60
|
+
}
|
|
61
|
+
// Re-throw unexpected errors
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
44
64
|
}
|
|
45
65
|
},
|
|
46
66
|
};
|
|
@@ -9,8 +9,18 @@ const listModule = (context) => ({
|
|
|
9
9
|
return Promise.resolve([]);
|
|
10
10
|
}
|
|
11
11
|
else {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
try {
|
|
13
|
+
const res = await context.pgClient.query(sql, params);
|
|
14
|
+
return res.rows.map((row) => row.value);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
// Connection closed during test cleanup - return empty array
|
|
18
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
// Re-throw unexpected errors
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
14
24
|
}
|
|
15
25
|
},
|
|
16
26
|
_lrange(key, start, end) {
|
|
@@ -44,8 +54,18 @@ const listModule = (context) => ({
|
|
|
44
54
|
return Promise.resolve(0);
|
|
45
55
|
}
|
|
46
56
|
else {
|
|
47
|
-
|
|
48
|
-
|
|
57
|
+
try {
|
|
58
|
+
const res = await context.pgClient.query(sql, params);
|
|
59
|
+
return Number(res.rows[0]?.count || 0);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
// Connection closed during test cleanup - return 0
|
|
63
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
// Re-throw unexpected errors
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
49
69
|
}
|
|
50
70
|
},
|
|
51
71
|
_rpush(key, value) {
|
|
@@ -72,8 +92,18 @@ const listModule = (context) => ({
|
|
|
72
92
|
return Promise.resolve(0);
|
|
73
93
|
}
|
|
74
94
|
else {
|
|
75
|
-
|
|
76
|
-
|
|
95
|
+
try {
|
|
96
|
+
const res = await context.pgClient.query(sql, params);
|
|
97
|
+
return Number(res.rows[0]?.count || 0);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
// Connection closed during test cleanup - return 0
|
|
101
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
// Re-throw unexpected errors
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
77
107
|
}
|
|
78
108
|
},
|
|
79
109
|
_lpush(key, value) {
|
|
@@ -100,8 +130,18 @@ const listModule = (context) => ({
|
|
|
100
130
|
return Promise.resolve(null);
|
|
101
131
|
}
|
|
102
132
|
else {
|
|
103
|
-
|
|
104
|
-
|
|
133
|
+
try {
|
|
134
|
+
const res = await context.pgClient.query(sql, params);
|
|
135
|
+
return res.rows[0]?.value || null;
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
// Connection closed during test cleanup - return null
|
|
139
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
// Re-throw unexpected errors
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
105
145
|
}
|
|
106
146
|
},
|
|
107
147
|
_lpop(key) {
|
|
@@ -131,7 +171,16 @@ const listModule = (context) => ({
|
|
|
131
171
|
return res.rows[0]?.value || null;
|
|
132
172
|
}
|
|
133
173
|
catch (err) {
|
|
134
|
-
|
|
174
|
+
// Connection closed during test cleanup - return null
|
|
175
|
+
if (err?.message?.includes('closed') || err?.message?.includes('queryable')) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
try {
|
|
179
|
+
await client.query('ROLLBACK');
|
|
180
|
+
}
|
|
181
|
+
catch (rollbackErr) {
|
|
182
|
+
// Ignore rollback errors if connection is closed
|
|
183
|
+
}
|
|
135
184
|
throw err;
|
|
136
185
|
}
|
|
137
186
|
}
|
|
@@ -177,7 +226,16 @@ const listModule = (context) => ({
|
|
|
177
226
|
await client.query('COMMIT');
|
|
178
227
|
}
|
|
179
228
|
catch (err) {
|
|
180
|
-
|
|
229
|
+
// Connection closed during test cleanup - silently return
|
|
230
|
+
if (err?.message?.includes('closed') || err?.message?.includes('queryable')) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
await client.query('ROLLBACK');
|
|
235
|
+
}
|
|
236
|
+
catch (rollbackErr) {
|
|
237
|
+
// Ignore rollback errors if connection is closed
|
|
238
|
+
}
|
|
181
239
|
throw err;
|
|
182
240
|
}
|
|
183
241
|
}
|
|
@@ -9,8 +9,18 @@ const stringModule = (context) => ({
|
|
|
9
9
|
return Promise.resolve(null);
|
|
10
10
|
}
|
|
11
11
|
else {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
try {
|
|
13
|
+
const res = await context.pgClient.query(sql, params);
|
|
14
|
+
return res.rows[0]?.value || null;
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
// Connection closed during test cleanup - log and return null
|
|
18
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
// Re-throw unexpected errors
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
14
24
|
}
|
|
15
25
|
},
|
|
16
26
|
_get(key) {
|
|
@@ -30,8 +40,18 @@ const stringModule = (context) => ({
|
|
|
30
40
|
return Promise.resolve(true);
|
|
31
41
|
}
|
|
32
42
|
else {
|
|
33
|
-
|
|
34
|
-
|
|
43
|
+
try {
|
|
44
|
+
const res = await context.pgClient.query(sql, params);
|
|
45
|
+
return res.rowCount > 0;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
// Connection closed during test cleanup - log and return false
|
|
49
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
// Re-throw unexpected errors
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
35
55
|
}
|
|
36
56
|
},
|
|
37
57
|
async setnxex(key, value, delay, multi) {
|
|
@@ -41,8 +61,18 @@ const stringModule = (context) => ({
|
|
|
41
61
|
return Promise.resolve(true);
|
|
42
62
|
}
|
|
43
63
|
else {
|
|
44
|
-
|
|
45
|
-
|
|
64
|
+
try {
|
|
65
|
+
const res = await context.pgClient.query(sql, params);
|
|
66
|
+
return res.rowCount > 0;
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
// Connection closed during test cleanup - log and return false
|
|
70
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
// Re-throw unexpected errors
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
46
76
|
}
|
|
47
77
|
},
|
|
48
78
|
async set(key, value, options, multi) {
|
|
@@ -52,8 +82,18 @@ const stringModule = (context) => ({
|
|
|
52
82
|
return Promise.resolve(true);
|
|
53
83
|
}
|
|
54
84
|
else {
|
|
55
|
-
|
|
56
|
-
|
|
85
|
+
try {
|
|
86
|
+
const res = await context.pgClient.query(sql, params);
|
|
87
|
+
return res.rowCount > 0;
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
// Connection closed during test cleanup - log and return false
|
|
91
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
// Re-throw unexpected errors
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
57
97
|
}
|
|
58
98
|
},
|
|
59
99
|
_set(key, value, options) {
|
|
@@ -95,8 +135,18 @@ const stringModule = (context) => ({
|
|
|
95
135
|
return Promise.resolve(0);
|
|
96
136
|
}
|
|
97
137
|
else {
|
|
98
|
-
|
|
99
|
-
|
|
138
|
+
try {
|
|
139
|
+
const res = await context.pgClient.query(sql, params);
|
|
140
|
+
return Number(res.rows[0]?.count || 0);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
// Connection closed during test cleanup - log and return 0
|
|
144
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
147
|
+
// Re-throw unexpected errors
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
100
150
|
}
|
|
101
151
|
},
|
|
102
152
|
_del(key) {
|
|
@@ -9,8 +9,18 @@ const zsetModule = (context) => ({
|
|
|
9
9
|
return Promise.resolve(0);
|
|
10
10
|
}
|
|
11
11
|
else {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
try {
|
|
13
|
+
const res = await context.pgClient.query(sql, params);
|
|
14
|
+
return Number(res.rows[0]?.count || 0);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
// Connection closed during test cleanup - return 0
|
|
18
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
// Re-throw unexpected errors
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
14
24
|
}
|
|
15
25
|
},
|
|
16
26
|
_zadd(key, score, member, options) {
|
|
@@ -50,13 +60,23 @@ const zsetModule = (context) => ({
|
|
|
50
60
|
return Promise.resolve([]);
|
|
51
61
|
}
|
|
52
62
|
else {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
63
|
+
try {
|
|
64
|
+
const res = await context.pgClient.query(sql, params);
|
|
65
|
+
if (facet === 'WITHSCORES') {
|
|
66
|
+
// Include scores in the result
|
|
67
|
+
return res.rows.flatMap((row) => [row.member, row.score.toString()]);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
return res.rows.map((row) => row.member);
|
|
71
|
+
}
|
|
57
72
|
}
|
|
58
|
-
|
|
59
|
-
return
|
|
73
|
+
catch (error) {
|
|
74
|
+
// Connection closed during test cleanup - return empty array
|
|
75
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
// Re-throw unexpected errors
|
|
79
|
+
throw error;
|
|
60
80
|
}
|
|
61
81
|
}
|
|
62
82
|
},
|
|
@@ -99,8 +119,18 @@ const zsetModule = (context) => ({
|
|
|
99
119
|
return Promise.resolve(null);
|
|
100
120
|
}
|
|
101
121
|
else {
|
|
102
|
-
|
|
103
|
-
|
|
122
|
+
try {
|
|
123
|
+
const res = await context.pgClient.query(sql, params);
|
|
124
|
+
return res.rows.length ? parseFloat(res.rows[0].score) : null;
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
// Connection closed during test cleanup - return null
|
|
128
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
// Re-throw unexpected errors
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
104
134
|
}
|
|
105
135
|
},
|
|
106
136
|
_zscore(key, member) {
|
|
@@ -122,8 +152,18 @@ const zsetModule = (context) => ({
|
|
|
122
152
|
return Promise.resolve([]);
|
|
123
153
|
}
|
|
124
154
|
else {
|
|
125
|
-
|
|
126
|
-
|
|
155
|
+
try {
|
|
156
|
+
const res = await context.pgClient.query(sql, params);
|
|
157
|
+
return res.rows.map((row) => row.member);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
// Connection closed during test cleanup - return empty array
|
|
161
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
// Re-throw unexpected errors
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
127
167
|
}
|
|
128
168
|
},
|
|
129
169
|
_zrangebyscore(key, min, max) {
|
|
@@ -143,8 +183,18 @@ const zsetModule = (context) => ({
|
|
|
143
183
|
return Promise.resolve([]);
|
|
144
184
|
}
|
|
145
185
|
else {
|
|
146
|
-
|
|
147
|
-
|
|
186
|
+
try {
|
|
187
|
+
const res = await context.pgClient.query(sql, params);
|
|
188
|
+
return res.rows.map((row) => ({ member: row.member, score: row.score }));
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
// Connection closed during test cleanup - return empty array
|
|
192
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
// Re-throw unexpected errors
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
148
198
|
}
|
|
149
199
|
},
|
|
150
200
|
_zrangebyscore_withscores(key, min, max) {
|
|
@@ -164,8 +214,18 @@ const zsetModule = (context) => ({
|
|
|
164
214
|
return Promise.resolve(0);
|
|
165
215
|
}
|
|
166
216
|
else {
|
|
167
|
-
|
|
168
|
-
|
|
217
|
+
try {
|
|
218
|
+
const res = await context.pgClient.query(sql, params);
|
|
219
|
+
return Number(res.rows[0]?.count || 0);
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
// Connection closed during test cleanup - return 0
|
|
223
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
224
|
+
return 0;
|
|
225
|
+
}
|
|
226
|
+
// Re-throw unexpected errors
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
169
229
|
}
|
|
170
230
|
},
|
|
171
231
|
_zrem(key, member) {
|
|
@@ -188,12 +248,22 @@ const zsetModule = (context) => ({
|
|
|
188
248
|
return Promise.resolve(null);
|
|
189
249
|
}
|
|
190
250
|
else {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
? parseInt(res.rows[0].rank, 10)
|
|
195
|
-
|
|
196
|
-
|
|
251
|
+
try {
|
|
252
|
+
const res = await context.pgClient.query(sql, params);
|
|
253
|
+
return res.rows[0]?.rank
|
|
254
|
+
? parseInt(res.rows[0].rank, 10) > 0
|
|
255
|
+
? parseInt(res.rows[0].rank, 10) - 1
|
|
256
|
+
: null
|
|
257
|
+
: null;
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
// Connection closed during test cleanup - return null
|
|
261
|
+
if (error?.message?.includes('closed') || error?.message?.includes('queryable')) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
// Re-throw unexpected errors
|
|
265
|
+
throw error;
|
|
266
|
+
}
|
|
197
267
|
}
|
|
198
268
|
},
|
|
199
269
|
_zrank(key, member) {
|