@zintrust/cache-redis 0.1.30 → 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.
@@ -0,0 +1,5 @@
1
+ import type { CacheDriver } from './index.js';
2
+ export declare const RedisProxyAdapter: Readonly<{
3
+ create(): CacheDriver;
4
+ }>;
5
+ export default RedisProxyAdapter;
@@ -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;
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@zintrust/cache-redis",
3
3
  "version": "0.1.27",
4
- "buildDate": "2026-01-29T08:05:42.892Z",
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": "1555458",
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": 736,
28
- "sha256": "0c9eae934ad4888d5ed29d62250010ee904692daace4f47f6461f81ebcf5f3ff"
35
+ "size": 842,
36
+ "sha256": "b47614dfdb736524165785958f4a685818c055315961c2e2b48d99ef6ca491e2"
29
37
  },
30
38
  "index.js": {
31
- "size": 2661,
32
- "sha256": "06184af521b126c04a8a05790398867a382d3355c02a813b4dd52144547e2060"
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
- export const RedisCacheDriver = Object.freeze({
6
- create(config) {
7
- let client;
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
- return JSON.parse(value);
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
- return {
29
- async get(key) {
30
- try {
31
- const c = await ensureClient();
32
- const value = await c.get(key);
33
- if (value === null)
34
- return null;
35
- return safeJsonParse(value);
36
- }
37
- catch (error) {
38
- Logger.error('Redis cache GET failed', error);
39
- return null;
40
- }
41
- },
42
- async set(key, value, ttl) {
43
- const c = await ensureClient();
44
- const json = JSON.stringify(value);
45
- const effectiveTtl = ttl ?? config.ttl;
46
- if (Number.isFinite(effectiveTtl) && effectiveTtl > 0) {
47
- await c.set(key, json, { EX: effectiveTtl });
48
- }
49
- else {
50
- await c.set(key, json);
51
- }
52
- },
53
- async delete(key) {
54
- const c = await ensureClient();
55
- await c.del(key);
56
- },
57
- async clear() {
58
- const c = await ensureClient();
59
- await c.flushDb();
60
- },
61
- async has(key) {
62
- const c = await ensureClient();
63
- const count = await c.exists(key);
64
- return count > 0;
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.30",
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.30"
25
+ "@zintrust/core": "^0.1.41"
26
26
  },
27
27
  "publishConfig": {
28
28
  "access": "public"