@zintrust/cache-redis 2.1.3 → 2.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/README.md +13 -0
- package/dist/RedisProxyAdapter.js +2 -2
- package/dist/build-manifest.json +11 -19
- package/dist/index.d.ts +3 -0
- package/dist/index.js +74 -6
- package/package.json +1 -1
- package/dist/RedisWorkersDurableObjectAdapter.d.ts +0 -5
- package/dist/RedisWorkersDurableObjectAdapter.js +0 -129
package/README.md
CHANGED
|
@@ -32,6 +32,19 @@ Then set your cache driver config (see docs for the full set of env vars):
|
|
|
32
32
|
CACHE_DRIVER=redis
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
## Cloudflare Workers
|
|
36
|
+
|
|
37
|
+
When the app runs in Cloudflare Workers or another runtime without Redis TCP access, use Redis RPC:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm i @zintrust/redis-rpc
|
|
41
|
+
|
|
42
|
+
USE_REDIS_PROXY=true
|
|
43
|
+
REDIS_RPC_URL=https://queues.example.com
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Run the backend with `zin redis-rpc` or `zin s redis-rpc`. The Redis cache driver will send `get`, `set`, `del`, `exists`, and `flushdb` through the core Redis RPC transport. The older Redis HTTP proxy remains available for simple command forwarding through `REDIS_PROXY_URL`.
|
|
47
|
+
|
|
35
48
|
## Docs
|
|
36
49
|
|
|
37
50
|
- https://zintrust.com/cache
|
|
@@ -2,8 +2,8 @@ import { Env } from '@zintrust/core/config';
|
|
|
2
2
|
import { ErrorFactory } from '@zintrust/core/errors';
|
|
3
3
|
import { createRedisConnection } from '@zintrust/core/redis';
|
|
4
4
|
const createProxyClient = () => {
|
|
5
|
-
if (Env.
|
|
6
|
-
throw ErrorFactory.createConfigError('Redis proxy
|
|
5
|
+
if (Env.USE_REDIS_PROXY !== true) {
|
|
6
|
+
throw ErrorFactory.createConfigError('Redis proxy transport requires USE_REDIS_PROXY=true. Add REDIS_RPC_URL for Redis RPC or REDIS_PROXY_URL/REDIS_PROXY_HOST for the legacy Redis HTTP proxy.');
|
|
7
7
|
}
|
|
8
8
|
return createRedisConnection({
|
|
9
9
|
host: Env.get('REDIS_HOST', 'localhost'),
|
package/dist/build-manifest.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zintrust/cache-redis",
|
|
3
|
-
"version": "2.1
|
|
4
|
-
"buildDate": "2026-05-
|
|
3
|
+
"version": "2.4.1",
|
|
4
|
+
"buildDate": "2026-05-31T11:36:02.431Z",
|
|
5
5
|
"buildEnvironment": {
|
|
6
6
|
"node": "v22.22.1",
|
|
7
7
|
"platform": "darwin",
|
|
8
8
|
"arch": "arm64"
|
|
9
9
|
},
|
|
10
10
|
"git": {
|
|
11
|
-
"commit": "
|
|
11
|
+
"commit": "e97b7b3d",
|
|
12
12
|
"branch": "release"
|
|
13
13
|
},
|
|
14
14
|
"package": {
|
|
@@ -28,28 +28,20 @@
|
|
|
28
28
|
"sha256": "91df2fbb1916b073c3bafa1dc253ab5ee8998cc924c3cd52308c09798c1d829f"
|
|
29
29
|
},
|
|
30
30
|
"RedisProxyAdapter.js": {
|
|
31
|
-
"size":
|
|
32
|
-
"sha256": "
|
|
33
|
-
},
|
|
34
|
-
"RedisWorkersDurableObjectAdapter.d.ts": {
|
|
35
|
-
"size": 193,
|
|
36
|
-
"sha256": "859d3177d302c65f93add57216eae91d8c45874801bb75bdea3d6f6b00fb00c3"
|
|
37
|
-
},
|
|
38
|
-
"RedisWorkersDurableObjectAdapter.js": {
|
|
39
|
-
"size": 4482,
|
|
40
|
-
"sha256": "5b0ae2883872ce974ecaf01f07d35b72e6da468439912a57734ccd5222705bfd"
|
|
31
|
+
"size": 1851,
|
|
32
|
+
"sha256": "6cbca2f311ac0573e63a1cd60e7bfe8b3eaaedcf23696ec170b234b2fd56f5a9"
|
|
41
33
|
},
|
|
42
34
|
"build-manifest.json": {
|
|
43
|
-
"size":
|
|
44
|
-
"sha256": "
|
|
35
|
+
"size": 1251,
|
|
36
|
+
"sha256": "6feb4171104bdeedf319a588a822a62e56c1db73f61d4ef5238ad48c828778c3"
|
|
45
37
|
},
|
|
46
38
|
"index.d.ts": {
|
|
47
|
-
"size":
|
|
48
|
-
"sha256": "
|
|
39
|
+
"size": 1000,
|
|
40
|
+
"sha256": "889b6f7ba479e62988d08472d823c41bb026c261992ba6ad65df2fc4048e6a72"
|
|
49
41
|
},
|
|
50
42
|
"index.js": {
|
|
51
|
-
"size":
|
|
52
|
-
"sha256": "
|
|
43
|
+
"size": 10002,
|
|
44
|
+
"sha256": "3eefc49885300ddadeecef3ff6af2bc0405c43a5498dbc4efb9888144dc6f330"
|
|
53
45
|
},
|
|
54
46
|
"register.d.ts": {
|
|
55
47
|
"size": 184,
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,9 @@ export interface CacheDriver {
|
|
|
4
4
|
delete(key: string): Promise<void>;
|
|
5
5
|
clear(): Promise<void>;
|
|
6
6
|
has(key: string): Promise<boolean>;
|
|
7
|
+
increment?(key: string, amount?: number): Promise<number>;
|
|
8
|
+
decrement?(key: string, amount?: number): Promise<number>;
|
|
9
|
+
getRedisClient?(): unknown;
|
|
7
10
|
}
|
|
8
11
|
export type RedisCacheConfig = {
|
|
9
12
|
driver: 'redis';
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,42 @@ const createCacheFailureState = () => {
|
|
|
30
30
|
},
|
|
31
31
|
};
|
|
32
32
|
};
|
|
33
|
-
const
|
|
33
|
+
const incrementValue = async (ensureClient, operations, failureState, defaultTtl, key, amount) => {
|
|
34
|
+
if (failureState.isDisabled())
|
|
35
|
+
return 0;
|
|
36
|
+
try {
|
|
37
|
+
const client = await ensureClient();
|
|
38
|
+
if (operations.increment)
|
|
39
|
+
return operations.increment(client, key, amount);
|
|
40
|
+
const current = Number(safeJsonParse((await operations.get(client, key)) ?? '0') ?? 0);
|
|
41
|
+
const next = current + amount;
|
|
42
|
+
await operations.set(client, key, JSON.stringify(next), defaultTtl);
|
|
43
|
+
return next;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
failureState.disableAfterFailure('SET', error);
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const decrementValue = async (ensureClient, operations, failureState, defaultTtl, key, amount) => {
|
|
51
|
+
if (failureState.isDisabled())
|
|
52
|
+
return 0;
|
|
53
|
+
try {
|
|
54
|
+
const client = await ensureClient();
|
|
55
|
+
if (operations.decrement)
|
|
56
|
+
return operations.decrement(client, key, amount);
|
|
57
|
+
const current = Number(safeJsonParse((await operations.get(client, key)) ?? '0') ?? 0);
|
|
58
|
+
const next = current - amount;
|
|
59
|
+
await operations.set(client, key, JSON.stringify(next), defaultTtl);
|
|
60
|
+
return next;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
failureState.disableAfterFailure('SET', error);
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
// Driver objects intentionally keep the cache method table in one sealed return value.
|
|
68
|
+
const createCacheOperations = (ensureClient, operations, defaultTtl, getRedisClient) => {
|
|
34
69
|
const failureState = createCacheFailureState();
|
|
35
70
|
return {
|
|
36
71
|
async get(key) {
|
|
@@ -96,6 +131,13 @@ const createCacheOperations = (ensureClient, operations, defaultTtl) => {
|
|
|
96
131
|
return false;
|
|
97
132
|
}
|
|
98
133
|
},
|
|
134
|
+
async increment(key, amount = 1) {
|
|
135
|
+
return incrementValue(ensureClient, operations, failureState, defaultTtl, key, amount);
|
|
136
|
+
},
|
|
137
|
+
async decrement(key, amount = 1) {
|
|
138
|
+
return decrementValue(ensureClient, operations, failureState, defaultTtl, key, amount);
|
|
139
|
+
},
|
|
140
|
+
...(getRedisClient ? { getRedisClient } : {}),
|
|
99
141
|
};
|
|
100
142
|
};
|
|
101
143
|
const createWorkersCacheDriver = (config) => {
|
|
@@ -118,7 +160,7 @@ const createWorkersCacheDriver = (config) => {
|
|
|
118
160
|
get: (redisClient, key) => redisClient.get(key),
|
|
119
161
|
set: (redisClient, key, json, ttl) => {
|
|
120
162
|
if (Number.isFinite(ttl) && ttl > 0) {
|
|
121
|
-
return redisClient.set(key, json,
|
|
163
|
+
return redisClient.set(key, json, 'EX', ttl);
|
|
122
164
|
}
|
|
123
165
|
else {
|
|
124
166
|
return redisClient.set(key, json);
|
|
@@ -137,7 +179,31 @@ const createWorkersCacheDriver = (config) => {
|
|
|
137
179
|
return Promise.resolve();
|
|
138
180
|
},
|
|
139
181
|
exists: (redisClient, key) => redisClient.exists(key),
|
|
140
|
-
|
|
182
|
+
increment: async (redisClient, key, amount) => {
|
|
183
|
+
if (typeof redisClient.incrBy === 'function') {
|
|
184
|
+
return redisClient.incrBy(key, amount);
|
|
185
|
+
}
|
|
186
|
+
if (typeof redisClient.incrby === 'function') {
|
|
187
|
+
return redisClient.incrby(key, amount);
|
|
188
|
+
}
|
|
189
|
+
const current = Number((await redisClient.get(key)) ?? 0);
|
|
190
|
+
const next = current + amount;
|
|
191
|
+
await redisClient.set(key, String(next));
|
|
192
|
+
return next;
|
|
193
|
+
},
|
|
194
|
+
decrement: async (redisClient, key, amount) => {
|
|
195
|
+
if (typeof redisClient.decrBy === 'function') {
|
|
196
|
+
return redisClient.decrBy(key, amount);
|
|
197
|
+
}
|
|
198
|
+
if (typeof redisClient.decrby === 'function') {
|
|
199
|
+
return redisClient.decrby(key, amount);
|
|
200
|
+
}
|
|
201
|
+
const current = Number((await redisClient.get(key)) ?? 0);
|
|
202
|
+
const next = current - amount;
|
|
203
|
+
await redisClient.set(key, String(next));
|
|
204
|
+
return next;
|
|
205
|
+
},
|
|
206
|
+
}, config.ttl ?? 300, () => client);
|
|
141
207
|
};
|
|
142
208
|
const createNodeCacheDriver = (config) => {
|
|
143
209
|
let client;
|
|
@@ -172,10 +238,12 @@ const createNodeCacheDriver = (config) => {
|
|
|
172
238
|
},
|
|
173
239
|
clear: (redisClient) => redisClient.flushDb(),
|
|
174
240
|
exists: (redisClient, key) => redisClient.exists(key),
|
|
175
|
-
|
|
241
|
+
increment: (redisClient, key, amount) => redisClient.incrBy(key, amount),
|
|
242
|
+
decrement: (redisClient, key, amount) => redisClient.decrBy(key, amount),
|
|
243
|
+
}, config.ttl ?? 300, () => client);
|
|
176
244
|
};
|
|
177
245
|
const shouldUseProxy = () => {
|
|
178
|
-
return Env.
|
|
246
|
+
return Env.USE_REDIS_PROXY === true;
|
|
179
247
|
};
|
|
180
248
|
export const RedisCacheDriver = Object.freeze({
|
|
181
249
|
create(config) {
|
|
@@ -184,7 +252,7 @@ export const RedisCacheDriver = Object.freeze({
|
|
|
184
252
|
return createWorkersCacheDriver(config);
|
|
185
253
|
}
|
|
186
254
|
if (isWorkers && Cloudflare.isCloudflareSocketsEnabled() === false) {
|
|
187
|
-
throw ErrorFactory.createConfigError('Redis cache driver requires
|
|
255
|
+
throw ErrorFactory.createConfigError('Redis cache driver in Cloudflare Workers requires USE_REDIS_PROXY=true with REDIS_RPC_URL/REDIS_PROXY_URL, or ENABLE_CLOUDFLARE_SOCKETS=true.');
|
|
188
256
|
}
|
|
189
257
|
return isWorkers ? createWorkersCacheDriver(config) : createNodeCacheDriver(config);
|
|
190
258
|
},
|
package/package.json
CHANGED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { ErrorFactory, Logger } from '@zintrust/core';
|
|
2
|
-
const createSendCommandFunction = (getStub, connect) => {
|
|
3
|
-
return async (command, args) => {
|
|
4
|
-
await connect();
|
|
5
|
-
const stub = getStub();
|
|
6
|
-
const executePath = 'http://do/execute'; //NOSONAR
|
|
7
|
-
const payload = JSON.stringify({
|
|
8
|
-
command,
|
|
9
|
-
params: args,
|
|
10
|
-
});
|
|
11
|
-
const response = await stub.fetch(executePath, {
|
|
12
|
-
method: 'POST',
|
|
13
|
-
headers: { 'Content-Type': 'application/json' },
|
|
14
|
-
body: payload,
|
|
15
|
-
});
|
|
16
|
-
if (!response.ok) {
|
|
17
|
-
const text = await response.text();
|
|
18
|
-
let errDetail;
|
|
19
|
-
try {
|
|
20
|
-
errDetail = JSON.parse(text);
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
errDetail = { error: text };
|
|
24
|
-
}
|
|
25
|
-
const msg = errDetail.error ||
|
|
26
|
-
errDetail.message ||
|
|
27
|
-
response.statusText;
|
|
28
|
-
throw ErrorFactory.createGeneralError(`DO Command Failed: ${msg}`);
|
|
29
|
-
}
|
|
30
|
-
const json = (await response.json());
|
|
31
|
-
if (json !== null && typeof json === 'object' && 'result' in json) {
|
|
32
|
-
return json.result;
|
|
33
|
-
}
|
|
34
|
-
return json;
|
|
35
|
-
};
|
|
36
|
-
};
|
|
37
|
-
const createConnectionManager = (getNamespace) => {
|
|
38
|
-
let connected = false;
|
|
39
|
-
const getStub = () => {
|
|
40
|
-
const namespace = getNamespace();
|
|
41
|
-
if (!namespace) {
|
|
42
|
-
throw ErrorFactory.createConfigError('REDIS_POOL binding not found. Cannot connect to Durable Object pool.');
|
|
43
|
-
}
|
|
44
|
-
const id = namespace.idFromName('default');
|
|
45
|
-
return namespace.get(id);
|
|
46
|
-
};
|
|
47
|
-
const connect = async () => {
|
|
48
|
-
if (connected)
|
|
49
|
-
return;
|
|
50
|
-
try {
|
|
51
|
-
const stub = getStub();
|
|
52
|
-
const health = 'http://do/health'; //NOSONAR
|
|
53
|
-
const res = await stub.fetch(health, {
|
|
54
|
-
method: 'POST',
|
|
55
|
-
});
|
|
56
|
-
if (!res.ok) {
|
|
57
|
-
throw ErrorFactory.createGeneralError(`DO health check failed: ${res.status}`);
|
|
58
|
-
}
|
|
59
|
-
const body = (await res.json());
|
|
60
|
-
if (!body.connected) {
|
|
61
|
-
Logger.info('[RedisWorkersDurableObjectAdapter] DO not connected yet, will init on first command');
|
|
62
|
-
}
|
|
63
|
-
connected = true;
|
|
64
|
-
}
|
|
65
|
-
catch (err) {
|
|
66
|
-
Logger.error('[RedisWorkersDurableObjectAdapter] Connection failed', err);
|
|
67
|
-
throw ErrorFactory.createGeneralError('Failed to connect to Redis DO', err);
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
const sendCommand = createSendCommandFunction(getStub, connect);
|
|
71
|
-
return {
|
|
72
|
-
connect,
|
|
73
|
-
sendCommand,
|
|
74
|
-
disconnect: () => {
|
|
75
|
-
connected = false;
|
|
76
|
-
},
|
|
77
|
-
isConnected: () => connected,
|
|
78
|
-
};
|
|
79
|
-
};
|
|
80
|
-
const toNumber = (value) => {
|
|
81
|
-
if (typeof value === 'number')
|
|
82
|
-
return value;
|
|
83
|
-
if (typeof value === 'string') {
|
|
84
|
-
const parsed = Number(value);
|
|
85
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
86
|
-
}
|
|
87
|
-
return 0;
|
|
88
|
-
};
|
|
89
|
-
export const RedisWorkersDurableObjectAdapter = Object.freeze({
|
|
90
|
-
create() {
|
|
91
|
-
const connectionManager = createConnectionManager(() => {
|
|
92
|
-
const globalEnv = globalThis.env;
|
|
93
|
-
return globalEnv?.['REDIS_POOL'];
|
|
94
|
-
});
|
|
95
|
-
return {
|
|
96
|
-
async get(key) {
|
|
97
|
-
const raw = await connectionManager.sendCommand('GET', [key]);
|
|
98
|
-
if (raw === null || raw === undefined)
|
|
99
|
-
return null;
|
|
100
|
-
try {
|
|
101
|
-
return JSON.parse(String(raw));
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
},
|
|
107
|
-
async set(key, value, ttl) {
|
|
108
|
-
const json = JSON.stringify(value);
|
|
109
|
-
if (Number.isFinite(ttl) && (ttl ?? 0) > 0) {
|
|
110
|
-
await connectionManager.sendCommand('SET', [key, json, 'EX', ttl]);
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
await connectionManager.sendCommand('SET', [key, json]);
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
async delete(key) {
|
|
117
|
-
await connectionManager.sendCommand('DEL', [key]);
|
|
118
|
-
},
|
|
119
|
-
async clear() {
|
|
120
|
-
await connectionManager.sendCommand('FLUSHDB', []);
|
|
121
|
-
},
|
|
122
|
-
async has(key) {
|
|
123
|
-
const count = await connectionManager.sendCommand('EXISTS', [key]);
|
|
124
|
-
return toNumber(count) > 0;
|
|
125
|
-
},
|
|
126
|
-
};
|
|
127
|
-
},
|
|
128
|
-
});
|
|
129
|
-
export default RedisWorkersDurableObjectAdapter;
|