@zintrust/cache-redis 0.1.27 → 0.1.41
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/dist/RedisProxyAdapter.d.ts +5 -0
- package/dist/RedisProxyAdapter.js +144 -0
- package/dist/build-manifest.json +15 -7
- package/dist/index.d.ts +3 -0
- package/dist/index.js +137 -59
- package/package.json +2 -2
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Env, ErrorFactory, Logger, SignedRequest } from '@zintrust/core';
|
|
2
|
+
const resolveSigningPrefix = (baseUrl) => {
|
|
3
|
+
try {
|
|
4
|
+
const parsed = new URL(baseUrl);
|
|
5
|
+
const path = parsed.pathname.endsWith('/') ? parsed.pathname.slice(0, -1) : parsed.pathname;
|
|
6
|
+
if (path === '' || path === '/')
|
|
7
|
+
return undefined;
|
|
8
|
+
return path;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const buildRequestUrl = (baseUrl, path) => {
|
|
15
|
+
const url = new URL(baseUrl);
|
|
16
|
+
const basePath = url.pathname.endsWith('/') ? url.pathname.slice(0, -1) : url.pathname;
|
|
17
|
+
const requestPath = path.startsWith('/') ? path : `/${path}`;
|
|
18
|
+
url.pathname = `${basePath}${requestPath}`;
|
|
19
|
+
return url;
|
|
20
|
+
};
|
|
21
|
+
const buildSigningUrl = (requestUrl, baseUrl) => {
|
|
22
|
+
const prefix = resolveSigningPrefix(baseUrl);
|
|
23
|
+
if (!prefix)
|
|
24
|
+
return requestUrl;
|
|
25
|
+
if (requestUrl.pathname === prefix || requestUrl.pathname.startsWith(`${prefix}/`)) {
|
|
26
|
+
const signingUrl = new URL(requestUrl.toString());
|
|
27
|
+
const stripped = requestUrl.pathname.slice(prefix.length);
|
|
28
|
+
signingUrl.pathname = stripped.startsWith('/') ? stripped : `/${stripped}`;
|
|
29
|
+
return signingUrl;
|
|
30
|
+
}
|
|
31
|
+
return requestUrl;
|
|
32
|
+
};
|
|
33
|
+
const resolveBaseUrl = () => {
|
|
34
|
+
const explicit = Env.get('REDIS_PROXY_URL', '').trim();
|
|
35
|
+
if (explicit !== '')
|
|
36
|
+
return explicit;
|
|
37
|
+
const host = Env.get('REDIS_PROXY_HOST', '127.0.0.1');
|
|
38
|
+
const port = Env.getInt('REDIS_PROXY_PORT', 8791);
|
|
39
|
+
return `http://${host}:${port}`;
|
|
40
|
+
};
|
|
41
|
+
const buildProxySettings = () => {
|
|
42
|
+
const baseUrl = resolveBaseUrl();
|
|
43
|
+
const rawKeyId = Env.get('REDIS_PROXY_KEY_ID', '').trim();
|
|
44
|
+
const fallbackKeyId = (Env.APP_NAME ?? 'zintrust').trim().toLowerCase().replaceAll(/\s+/g, '_');
|
|
45
|
+
const keyId = (rawKeyId === '' ? fallbackKeyId : rawKeyId) || undefined;
|
|
46
|
+
const secret = Env.get('REDIS_PROXY_SECRET', '') || Env.APP_KEY || undefined;
|
|
47
|
+
const timeoutMs = Env.getInt('REDIS_PROXY_TIMEOUT_MS', Env.ZT_PROXY_TIMEOUT_MS);
|
|
48
|
+
return { baseUrl, keyId, secret, timeoutMs };
|
|
49
|
+
};
|
|
50
|
+
const buildHeaders = async (settings, requestUrl, body) => {
|
|
51
|
+
const headers = {
|
|
52
|
+
'Content-Type': 'application/json',
|
|
53
|
+
};
|
|
54
|
+
if (settings.keyId && settings.secret) {
|
|
55
|
+
const signingUrl = buildSigningUrl(requestUrl, settings.baseUrl);
|
|
56
|
+
const signed = await SignedRequest.createHeaders({
|
|
57
|
+
method: 'POST',
|
|
58
|
+
url: signingUrl,
|
|
59
|
+
body,
|
|
60
|
+
keyId: settings.keyId,
|
|
61
|
+
secret: settings.secret,
|
|
62
|
+
});
|
|
63
|
+
Object.assign(headers, signed);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
Logger.warn('[redis-proxy] Proxy signing disabled; sending unsigned request.');
|
|
67
|
+
}
|
|
68
|
+
return headers;
|
|
69
|
+
};
|
|
70
|
+
const requestProxy = async (settings, path, payload) => {
|
|
71
|
+
if (settings.baseUrl.trim() === '') {
|
|
72
|
+
throw ErrorFactory.createConfigError('Redis proxy URL is missing (REDIS_PROXY_URL)');
|
|
73
|
+
}
|
|
74
|
+
const body = JSON.stringify(payload);
|
|
75
|
+
const url = buildRequestUrl(settings.baseUrl, path);
|
|
76
|
+
const headers = await buildHeaders(settings, url, body);
|
|
77
|
+
const timeoutSignal = typeof AbortSignal !== 'undefined' && 'timeout' in AbortSignal;
|
|
78
|
+
const signal = timeoutSignal ? AbortSignal.timeout(settings.timeoutMs) : undefined;
|
|
79
|
+
const response = await fetch(url.toString(), {
|
|
80
|
+
method: 'POST',
|
|
81
|
+
headers,
|
|
82
|
+
body,
|
|
83
|
+
signal,
|
|
84
|
+
});
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
const text = await response.text();
|
|
87
|
+
throw ErrorFactory.createTryCatchError(`Redis proxy request failed (${response.status})`, text);
|
|
88
|
+
}
|
|
89
|
+
return (await response.json());
|
|
90
|
+
};
|
|
91
|
+
const toNumber = (value) => {
|
|
92
|
+
if (typeof value === 'number')
|
|
93
|
+
return value;
|
|
94
|
+
if (typeof value === 'string') {
|
|
95
|
+
const parsed = Number(value);
|
|
96
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
97
|
+
}
|
|
98
|
+
return 0;
|
|
99
|
+
};
|
|
100
|
+
export const RedisProxyAdapter = Object.freeze({
|
|
101
|
+
create() {
|
|
102
|
+
const settings = buildProxySettings();
|
|
103
|
+
const sendCommand = async (command, args) => {
|
|
104
|
+
const response = await requestProxy(settings, '/zin/redis/command', {
|
|
105
|
+
command,
|
|
106
|
+
args,
|
|
107
|
+
});
|
|
108
|
+
return response.result;
|
|
109
|
+
};
|
|
110
|
+
return {
|
|
111
|
+
async get(key) {
|
|
112
|
+
const result = await sendCommand('GET', [key]);
|
|
113
|
+
if (result === null)
|
|
114
|
+
return null;
|
|
115
|
+
try {
|
|
116
|
+
return JSON.parse(result);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
async set(key, value, ttl) {
|
|
123
|
+
const json = JSON.stringify(value);
|
|
124
|
+
if (Number.isFinite(ttl) && (ttl ?? 0) > 0) {
|
|
125
|
+
await sendCommand('SET', [key, json, 'EX', ttl]);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
await sendCommand('SET', [key, json]);
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
async delete(key) {
|
|
132
|
+
await sendCommand('DEL', [key]);
|
|
133
|
+
},
|
|
134
|
+
async clear() {
|
|
135
|
+
await sendCommand('FLUSHDB', []);
|
|
136
|
+
},
|
|
137
|
+
async has(key) {
|
|
138
|
+
const result = await sendCommand('EXISTS', [key]);
|
|
139
|
+
return toNumber(result) > 0;
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
export default RedisProxyAdapter;
|
package/dist/build-manifest.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zintrust/cache-redis",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"buildDate": "2026-
|
|
3
|
+
"version": "0.1.27",
|
|
4
|
+
"buildDate": "2026-02-15T07:16:17.299Z",
|
|
5
5
|
"buildEnvironment": {
|
|
6
6
|
"node": "v20.20.0",
|
|
7
7
|
"platform": "linux",
|
|
8
8
|
"arch": "x64"
|
|
9
9
|
},
|
|
10
10
|
"git": {
|
|
11
|
-
"commit": "
|
|
11
|
+
"commit": "33b681d",
|
|
12
12
|
"branch": "master"
|
|
13
13
|
},
|
|
14
14
|
"package": {
|
|
@@ -23,13 +23,21 @@
|
|
|
23
23
|
]
|
|
24
24
|
},
|
|
25
25
|
"files": {
|
|
26
|
+
"RedisProxyAdapter.d.ts": {
|
|
27
|
+
"size": 163,
|
|
28
|
+
"sha256": "91df2fbb1916b073c3bafa1dc253ab5ee8998cc924c3cd52308c09798c1d829f"
|
|
29
|
+
},
|
|
30
|
+
"RedisProxyAdapter.js": {
|
|
31
|
+
"size": 5296,
|
|
32
|
+
"sha256": "a58538d3284829c8d0f3b2a7ed48e7db65098c8047f9f84c1015a22944df0100"
|
|
33
|
+
},
|
|
26
34
|
"index.d.ts": {
|
|
27
|
-
"size":
|
|
28
|
-
"sha256": "
|
|
35
|
+
"size": 842,
|
|
36
|
+
"sha256": "b47614dfdb736524165785958f4a685818c055315961c2e2b48d99ef6ca491e2"
|
|
29
37
|
},
|
|
30
38
|
"index.js": {
|
|
31
|
-
"size":
|
|
32
|
-
"sha256": "
|
|
39
|
+
"size": 5422,
|
|
40
|
+
"sha256": "462d16289788ef6d27d1688be9dbd17a8baeb939ec088fc68294dfc8ce4ca0f8"
|
|
33
41
|
},
|
|
34
42
|
"register.d.ts": {
|
|
35
43
|
"size": 184,
|
package/dist/index.d.ts
CHANGED
|
@@ -10,11 +10,14 @@ export type RedisCacheConfig = {
|
|
|
10
10
|
host: string;
|
|
11
11
|
port: number;
|
|
12
12
|
ttl: number;
|
|
13
|
+
password?: string;
|
|
14
|
+
database?: number;
|
|
13
15
|
};
|
|
14
16
|
export declare const RedisCacheDriver: Readonly<{
|
|
15
17
|
create(config: RedisCacheConfig): CacheDriver;
|
|
16
18
|
}>;
|
|
17
19
|
export default RedisCacheDriver;
|
|
20
|
+
export { RedisProxyAdapter } from './RedisProxyAdapter.js';
|
|
18
21
|
/**
|
|
19
22
|
* Package version and build metadata
|
|
20
23
|
* Available at runtime for debugging and health checks
|
package/dist/index.js
CHANGED
|
@@ -1,72 +1,150 @@
|
|
|
1
|
-
import { Logger } from '@zintrust/core';
|
|
1
|
+
import { Cloudflare, Env, ErrorFactory, Logger, createRedisConnection } from '@zintrust/core';
|
|
2
|
+
import { RedisProxyAdapter } from './RedisProxyAdapter.js';
|
|
3
|
+
const safeJsonParse = (value) => {
|
|
4
|
+
try {
|
|
5
|
+
return JSON.parse(value);
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
2
11
|
async function importRedis() {
|
|
3
12
|
return (await import('redis'));
|
|
4
13
|
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
let connected = false;
|
|
9
|
-
const ensureClient = async () => {
|
|
10
|
-
if (client === undefined) {
|
|
11
|
-
const { createClient } = await importRedis();
|
|
12
|
-
client = createClient({ socket: { host: config.host, port: config.port } });
|
|
13
|
-
}
|
|
14
|
-
if (!connected) {
|
|
15
|
-
await client.connect();
|
|
16
|
-
connected = true;
|
|
17
|
-
}
|
|
18
|
-
return client;
|
|
19
|
-
};
|
|
20
|
-
const safeJsonParse = (value) => {
|
|
14
|
+
const createCacheOperations = (ensureClient, operations, defaultTtl) => {
|
|
15
|
+
return {
|
|
16
|
+
async get(key) {
|
|
21
17
|
try {
|
|
22
|
-
|
|
18
|
+
const client = await ensureClient();
|
|
19
|
+
const value = await operations.get(client, key);
|
|
20
|
+
if (value === null)
|
|
21
|
+
return null;
|
|
22
|
+
return safeJsonParse(value);
|
|
23
23
|
}
|
|
24
|
-
catch {
|
|
24
|
+
catch (error) {
|
|
25
|
+
Logger.error('Redis cache GET failed', error);
|
|
25
26
|
return null;
|
|
26
27
|
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
28
|
+
},
|
|
29
|
+
async set(key, value, ttl) {
|
|
30
|
+
const client = await ensureClient();
|
|
31
|
+
const json = JSON.stringify(value);
|
|
32
|
+
const effectiveTtl = ttl ?? defaultTtl;
|
|
33
|
+
await operations.set(client, key, json, effectiveTtl);
|
|
34
|
+
},
|
|
35
|
+
async delete(key) {
|
|
36
|
+
const client = await ensureClient();
|
|
37
|
+
await operations.del(client, key);
|
|
38
|
+
},
|
|
39
|
+
async clear() {
|
|
40
|
+
const client = await ensureClient();
|
|
41
|
+
await operations.clear(client);
|
|
42
|
+
},
|
|
43
|
+
async has(key) {
|
|
44
|
+
const client = await ensureClient();
|
|
45
|
+
const count = await operations.exists(client, key);
|
|
46
|
+
return count > 0;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
const createWorkersCacheDriver = (config) => {
|
|
51
|
+
let client;
|
|
52
|
+
let connected = false;
|
|
53
|
+
const ensureClient = async () => {
|
|
54
|
+
if (client === undefined) {
|
|
55
|
+
client = createRedisConnection({
|
|
56
|
+
host: config.host,
|
|
57
|
+
port: config.port,
|
|
58
|
+
password: config.password,
|
|
59
|
+
db: config.database ?? 0,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (!connected && typeof client.connect === 'function') {
|
|
63
|
+
await client.connect();
|
|
64
|
+
connected = true;
|
|
65
|
+
}
|
|
66
|
+
return client;
|
|
67
|
+
};
|
|
68
|
+
return createCacheOperations(ensureClient, {
|
|
69
|
+
get: (redisClient, key) => redisClient.get(key),
|
|
70
|
+
set: (redisClient, key, json, ttl) => {
|
|
71
|
+
if (Number.isFinite(ttl) && ttl > 0) {
|
|
72
|
+
return redisClient.set(key, json, { EX: ttl });
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
return redisClient.set(key, json);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
del: (redisClient, key) => {
|
|
79
|
+
redisClient.del(key);
|
|
80
|
+
return Promise.resolve();
|
|
81
|
+
},
|
|
82
|
+
clear: (redisClient) => {
|
|
83
|
+
if (typeof redisClient.flushDb === 'function') {
|
|
84
|
+
return redisClient.flushDb();
|
|
85
|
+
}
|
|
86
|
+
else if (typeof redisClient.flushdb === 'function') {
|
|
87
|
+
return redisClient.flushdb();
|
|
88
|
+
}
|
|
89
|
+
return Promise.resolve();
|
|
90
|
+
},
|
|
91
|
+
exists: (redisClient, key) => redisClient.exists(key),
|
|
92
|
+
}, config.ttl ?? 300);
|
|
93
|
+
};
|
|
94
|
+
const createNodeCacheDriver = (config) => {
|
|
95
|
+
let client;
|
|
96
|
+
let connected = false;
|
|
97
|
+
const ensureClient = async () => {
|
|
98
|
+
if (client === undefined) {
|
|
99
|
+
const { createClient } = await importRedis();
|
|
100
|
+
client = createClient({ socket: { host: config.host, port: config.port } });
|
|
101
|
+
}
|
|
102
|
+
if (!connected) {
|
|
103
|
+
await client.connect();
|
|
104
|
+
connected = true;
|
|
105
|
+
}
|
|
106
|
+
return client;
|
|
107
|
+
};
|
|
108
|
+
return createCacheOperations(ensureClient, {
|
|
109
|
+
get: (redisClient, key) => redisClient.get(key),
|
|
110
|
+
set: (redisClient, key, json, ttl) => {
|
|
111
|
+
if (Number.isFinite(ttl) && ttl > 0) {
|
|
112
|
+
return redisClient.set(key, json, { EX: ttl });
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
return redisClient.set(key, json);
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
del: (redisClient, key) => {
|
|
119
|
+
redisClient.del(key);
|
|
120
|
+
return Promise.resolve();
|
|
121
|
+
},
|
|
122
|
+
clear: (redisClient) => {
|
|
123
|
+
redisClient.flushDb();
|
|
124
|
+
return Promise.resolve();
|
|
125
|
+
},
|
|
126
|
+
exists: (redisClient, key) => redisClient.exists(key),
|
|
127
|
+
}, config.ttl ?? 300);
|
|
128
|
+
};
|
|
129
|
+
const shouldUseProxy = () => {
|
|
130
|
+
if (Env.REDIS_PROXY_URL.trim() !== '')
|
|
131
|
+
return true;
|
|
132
|
+
return Env.USE_REDIS_PROXY === true;
|
|
133
|
+
};
|
|
134
|
+
export const RedisCacheDriver = Object.freeze({
|
|
135
|
+
create(config) {
|
|
136
|
+
const isWorkers = Cloudflare.getWorkersEnv() !== null;
|
|
137
|
+
if (shouldUseProxy()) {
|
|
138
|
+
return RedisProxyAdapter.create();
|
|
139
|
+
}
|
|
140
|
+
if (isWorkers && Cloudflare.isCloudflareSocketsEnabled() === false) {
|
|
141
|
+
throw ErrorFactory.createConfigError('Redis cache driver requires ENABLE_CLOUDFLARE_SOCKETS=true in Cloudflare Workers.');
|
|
142
|
+
}
|
|
143
|
+
return isWorkers ? createWorkersCacheDriver(config) : createNodeCacheDriver(config);
|
|
67
144
|
},
|
|
68
145
|
});
|
|
69
146
|
export default RedisCacheDriver;
|
|
147
|
+
export { RedisProxyAdapter } from './RedisProxyAdapter.js';
|
|
70
148
|
/**
|
|
71
149
|
* Package version and build metadata
|
|
72
150
|
* Available at runtime for debugging and health checks
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zintrust/cache-redis",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.41",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"node": ">=20.0.0"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@zintrust/core": "^0.1.
|
|
25
|
+
"@zintrust/core": "^0.1.41"
|
|
26
26
|
},
|
|
27
27
|
"publishConfig": {
|
|
28
28
|
"access": "public"
|