@hotmeshio/hotmesh 0.6.0 → 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/index.d.ts +3 -1
- package/build/index.js +5 -1
- package/build/modules/enums.d.ts +18 -0
- package/build/modules/enums.js +27 -1
- package/build/modules/utils.d.ts +27 -0
- package/build/modules/utils.js +79 -1
- package/build/package.json +24 -10
- package/build/services/connector/factory.d.ts +1 -1
- package/build/services/connector/factory.js +15 -1
- package/build/services/connector/providers/ioredis.d.ts +9 -0
- package/build/services/connector/providers/ioredis.js +26 -0
- package/build/services/connector/providers/postgres.js +3 -0
- package/build/services/connector/providers/redis.d.ts +9 -0
- package/build/services/connector/providers/redis.js +38 -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/search/factory.js +8 -0
- package/build/services/search/providers/redis/ioredis.d.ts +23 -0
- package/build/services/search/providers/redis/ioredis.js +189 -0
- package/build/services/search/providers/redis/redis.d.ts +23 -0
- package/build/services/search/providers/redis/redis.js +202 -0
- package/build/services/store/factory.js +9 -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 +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 +190 -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/stream/factory.js +17 -1
- 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/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/sub/factory.js +8 -0
- package/build/services/sub/providers/postgres/postgres.js +37 -5
- package/build/services/sub/providers/redis/ioredis.d.ts +20 -0
- package/build/services/sub/providers/redis/ioredis.js +161 -0
- package/build/services/sub/providers/redis/redis.d.ts +18 -0
- package/build/services/sub/providers/redis/redis.js +148 -0
- 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 +4 -3
- package/build/types/index.js +4 -1
- package/build/types/memflow.d.ts +32 -0
- package/build/types/provider.d.ts +17 -1
- package/build/types/redis.d.ts +258 -0
- package/build/types/redis.js +11 -0
- package/build/types/stream.d.ts +92 -1
- package/index.ts +4 -0
- package/package.json +24 -10
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisSearchService = void 0;
|
|
4
|
+
const index_1 = require("../../index");
|
|
5
|
+
class RedisSearchService extends index_1.SearchService {
|
|
6
|
+
constructor(searchClient, storeClient) {
|
|
7
|
+
super(searchClient, storeClient);
|
|
8
|
+
}
|
|
9
|
+
async init(namespace, appId, logger) {
|
|
10
|
+
this.namespace = namespace;
|
|
11
|
+
this.appId = appId;
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
}
|
|
14
|
+
async createSearchIndex(indexName, prefixes, schema) {
|
|
15
|
+
try {
|
|
16
|
+
await this.searchClient.sendCommand([
|
|
17
|
+
'FT.CREATE',
|
|
18
|
+
indexName,
|
|
19
|
+
'ON',
|
|
20
|
+
'HASH',
|
|
21
|
+
'PREFIX',
|
|
22
|
+
prefixes.length.toString(),
|
|
23
|
+
...prefixes,
|
|
24
|
+
'SCHEMA',
|
|
25
|
+
...schema,
|
|
26
|
+
]);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
this.logger.info('Error creating search index', { error });
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async listSearchIndexes() {
|
|
34
|
+
try {
|
|
35
|
+
const indexes = await this.searchClient.sendCommand(['FT._LIST']);
|
|
36
|
+
return indexes;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
this.logger.info('Error listing search indexes', { error });
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async updateContext(key, fields) {
|
|
44
|
+
// Find replay ID if present (field with hyphen, not the @udata field)
|
|
45
|
+
const replayId = Object.keys(fields).find((k) => k.includes('-') && !k.startsWith('@'));
|
|
46
|
+
// Route based on @udata operation
|
|
47
|
+
if ('@udata:set' in fields) {
|
|
48
|
+
const udata = JSON.parse(fields['@udata:set']);
|
|
49
|
+
const fieldsToSet = Array.isArray(udata)
|
|
50
|
+
? Object.fromEntries(Array.from({ length: udata.length / 2 }, (_, i) => [
|
|
51
|
+
udata[i * 2],
|
|
52
|
+
udata[i * 2 + 1],
|
|
53
|
+
]))
|
|
54
|
+
: udata;
|
|
55
|
+
const result = await this.setFields(key, fieldsToSet);
|
|
56
|
+
if (replayId)
|
|
57
|
+
await this.searchClient.HSET(key, { [replayId]: String(result) });
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
if ('@udata:get' in fields) {
|
|
61
|
+
const result = await this.getField(key, fields['@udata:get']);
|
|
62
|
+
if (replayId)
|
|
63
|
+
await this.searchClient.HSET(key, { [replayId]: result });
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
if ('@udata:mget' in fields) {
|
|
67
|
+
const result = await this.getFields(key, JSON.parse(fields['@udata:mget']));
|
|
68
|
+
if (replayId)
|
|
69
|
+
await this.searchClient.HSET(key, { [replayId]: result.join('|||') });
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
if ('@udata:delete' in fields) {
|
|
73
|
+
const result = await this.deleteFields(key, JSON.parse(fields['@udata:delete']));
|
|
74
|
+
if (replayId)
|
|
75
|
+
await this.searchClient.HSET(key, { [replayId]: String(result) });
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
if ('@udata:increment' in fields) {
|
|
79
|
+
const { field, value } = JSON.parse(fields['@udata:increment']);
|
|
80
|
+
const result = await this.incrementFieldByFloat(key, field, value);
|
|
81
|
+
if (replayId)
|
|
82
|
+
await this.searchClient.HSET(key, { [replayId]: String(result) });
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
if ('@udata:multiply' in fields) {
|
|
86
|
+
const { field, value } = JSON.parse(fields['@udata:multiply']);
|
|
87
|
+
const result = await this.incrementFieldByFloat(key, field, Math.log(value));
|
|
88
|
+
if (replayId)
|
|
89
|
+
await this.searchClient.HSET(key, { [replayId]: String(result) });
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
if ('@udata:all' in fields) {
|
|
93
|
+
const all = await this.getAllFields(key);
|
|
94
|
+
const result = Object.fromEntries(Object.entries(all).filter(([k]) => k.startsWith('_')));
|
|
95
|
+
if (replayId)
|
|
96
|
+
await this.searchClient.HSET(key, { [replayId]: JSON.stringify(result) });
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
// Default: call setFields
|
|
100
|
+
return await this.setFields(key, fields);
|
|
101
|
+
}
|
|
102
|
+
async setFields(key, fields) {
|
|
103
|
+
try {
|
|
104
|
+
const result = await this.searchClient.HSET(key, fields);
|
|
105
|
+
return Number(result);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
this.logger.error(`Error setting fields for key: ${key}`, { error });
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async getField(key, field) {
|
|
113
|
+
try {
|
|
114
|
+
return await this.searchClient.HGET(key, field);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
this.logger.error(`Error getting field ${field} for key: ${key}`, {
|
|
118
|
+
error,
|
|
119
|
+
});
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async getFields(key, fields) {
|
|
124
|
+
try {
|
|
125
|
+
return await this.searchClient.HMGET(key, [...fields]);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
this.logger.error(`Error getting fields for key: ${key}`, { error });
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async getAllFields(key) {
|
|
133
|
+
try {
|
|
134
|
+
return await this.searchClient.HGETALL(key);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
this.logger.error(`Error getting fields for key: ${key}`, { error });
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
async deleteFields(key, fields) {
|
|
142
|
+
try {
|
|
143
|
+
const result = await this.searchClient.HDEL(key, fields);
|
|
144
|
+
return Number(result);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
this.logger.error(`Error deleting fields for key: ${key}`, { error });
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async incrementFieldByFloat(key, field, increment) {
|
|
152
|
+
try {
|
|
153
|
+
const result = await this.searchClient.HINCRBYFLOAT(key, field, increment);
|
|
154
|
+
return Number(result);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
this.logger.error(`Error incrementing field ${field} for key: ${key}`, {
|
|
158
|
+
error,
|
|
159
|
+
});
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async sendQuery(...query) {
|
|
164
|
+
try {
|
|
165
|
+
return await this.searchClient.sendCommand(query);
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
this.logger.error('Error executing query', { error });
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
async sendIndexedQuery(index, query) {
|
|
173
|
+
try {
|
|
174
|
+
if (query[0]?.startsWith('FT.')) {
|
|
175
|
+
return (await this.searchClient.sendCommand(query));
|
|
176
|
+
}
|
|
177
|
+
return (await this.searchClient.sendCommand([
|
|
178
|
+
'FT.SEARCH',
|
|
179
|
+
index,
|
|
180
|
+
...query,
|
|
181
|
+
]));
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
this.logger.error('Error executing query', { error });
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Entity methods - not implemented for Redis (postgres-specific JSONB operations)
|
|
189
|
+
async findEntities() {
|
|
190
|
+
throw new Error('Entity findEntities not supported in Redis - use PostgreSQL');
|
|
191
|
+
}
|
|
192
|
+
async findEntityById() {
|
|
193
|
+
throw new Error('Entity findEntityById not supported in Redis - use PostgreSQL');
|
|
194
|
+
}
|
|
195
|
+
async findEntitiesByCondition() {
|
|
196
|
+
throw new Error('Entity findEntitiesByCondition not supported in Redis - use PostgreSQL');
|
|
197
|
+
}
|
|
198
|
+
async createEntityIndex() {
|
|
199
|
+
throw new Error('Entity createEntityIndex not supported in Redis - use PostgreSQL');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
exports.RedisSearchService = RedisSearchService;
|
|
@@ -2,11 +2,19 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.StoreServiceFactory = void 0;
|
|
4
4
|
const utils_1 = require("../../modules/utils");
|
|
5
|
+
const ioredis_1 = require("./providers/redis/ioredis");
|
|
6
|
+
const redis_1 = require("./providers/redis/redis");
|
|
5
7
|
const postgres_1 = require("./providers/postgres/postgres");
|
|
6
8
|
class StoreServiceFactory {
|
|
7
9
|
static async init(providerClient, namespace, appId, logger) {
|
|
8
10
|
let service;
|
|
9
|
-
if ((0, utils_1.identifyProvider)(providerClient) === '
|
|
11
|
+
if ((0, utils_1.identifyProvider)(providerClient) === 'redis') {
|
|
12
|
+
service = new redis_1.RedisStoreService(providerClient);
|
|
13
|
+
}
|
|
14
|
+
else if ((0, utils_1.identifyProvider)(providerClient) === 'ioredis') {
|
|
15
|
+
service = new ioredis_1.IORedisStoreService(providerClient);
|
|
16
|
+
}
|
|
17
|
+
else if ((0, utils_1.identifyProvider)(providerClient) === 'postgres') {
|
|
10
18
|
service = new postgres_1.PostgresStoreService(providerClient);
|
|
11
19
|
} //etc
|
|
12
20
|
await service.init(namespace, appId, logger);
|
|
@@ -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) {
|