@zintrust/core 0.4.34 → 0.4.36
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/package.json +4 -4
- package/src/cache/drivers/RedisDriver.d.ts.map +1 -1
- package/src/cache/drivers/RedisDriver.js +1 -1
- package/src/cli/OptionalCliExtensions.d.ts.map +1 -1
- package/src/cli/OptionalCliExtensions.js +1 -0
- package/src/cli/commands/WorkerCommands.d.ts +2 -1
- package/src/cli/commands/WorkerCommands.d.ts.map +1 -1
- package/src/cli/commands/WorkerCommands.js +111 -62
- package/src/cli/services/WorkerStartupDiagnostics.d.ts +41 -0
- package/src/cli/services/WorkerStartupDiagnostics.d.ts.map +1 -0
- package/src/cli/services/WorkerStartupDiagnostics.js +348 -0
- package/src/cli/utils/DistPackager.d.ts.map +1 -1
- package/src/cli/utils/DistPackager.js +22 -18
- package/src/config/workers.d.ts +2 -1
- package/src/config/workers.d.ts.map +1 -1
- package/src/config/workers.js +6 -1
- package/src/index.d.ts +11 -11
- package/src/index.d.ts.map +1 -1
- package/src/index.js +13 -13
- package/src/proxy/CloudflareProxyShared.d.ts +50 -0
- package/src/proxy/CloudflareProxyShared.d.ts.map +1 -0
- package/src/proxy/CloudflareProxyShared.js +117 -0
- package/src/proxy/d1/ZintrustD1Proxy.d.ts.map +1 -1
- package/src/proxy/d1/ZintrustD1Proxy.js +49 -158
- package/src/proxy/kv/ZintrustKvProxy.d.ts.map +1 -1
- package/src/proxy/kv/ZintrustKvProxy.js +41 -142
- package/src/security/CsrfTokenManager.d.ts.map +1 -1
- package/src/security/CsrfTokenManager.js +1 -1
- package/src/security/JwtSessions.d.ts.map +1 -1
- package/src/security/JwtSessions.js +1 -1
- package/src/security/TokenRevocation.d.ts.map +1 -1
- package/src/security/TokenRevocation.js +1 -1
- package/src/tools/queue/LockProvider.d.ts.map +1 -1
- package/src/tools/queue/LockProvider.js +1 -1
- package/src/tools/redis/RedisTransport.d.ts +34 -0
- package/src/tools/redis/RedisTransport.d.ts.map +1 -0
- package/src/tools/redis/RedisTransport.js +251 -0
|
@@ -1,104 +1,9 @@
|
|
|
1
1
|
import { isObject, isString } from '../../helper/index.js';
|
|
2
|
-
import {
|
|
2
|
+
import { getEnvInt, json, normalizeBindingName, readAndVerifyJson, toErrorResponse, } from '../CloudflareProxyShared.js';
|
|
3
3
|
import { RequestValidator } from '../RequestValidator.js';
|
|
4
|
-
import { SigningService } from '../SigningService.js';
|
|
5
4
|
const DEFAULT_SIGNING_WINDOW_MS = 60_000;
|
|
6
5
|
const DEFAULT_MAX_BODY_BYTES = 128 * 1024;
|
|
7
6
|
const DEFAULT_LIST_LIMIT = 100;
|
|
8
|
-
const json = (status, body) => {
|
|
9
|
-
return new Response(JSON.stringify(body), {
|
|
10
|
-
status,
|
|
11
|
-
headers: {
|
|
12
|
-
'Content-Type': 'application/json; charset=utf-8',
|
|
13
|
-
'Cache-Control': 'no-store',
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
};
|
|
17
|
-
const toErrorResponse = (status, code, message) => {
|
|
18
|
-
const error = ErrorHandler.toProxyError(status, code, message);
|
|
19
|
-
return json(error.status, error.body);
|
|
20
|
-
};
|
|
21
|
-
const getEnvInt = (env, name, fallback) => {
|
|
22
|
-
const raw = env[name];
|
|
23
|
-
if (!isString(raw))
|
|
24
|
-
return fallback;
|
|
25
|
-
const parsed = Number.parseInt(raw, 10);
|
|
26
|
-
return Number.isFinite(parsed) ? parsed : fallback;
|
|
27
|
-
};
|
|
28
|
-
const normalizeBindingName = (value) => {
|
|
29
|
-
if (!isString(value))
|
|
30
|
-
return null;
|
|
31
|
-
const trimmed = value.trim();
|
|
32
|
-
return trimmed === '' ? null : trimmed;
|
|
33
|
-
};
|
|
34
|
-
const readBodyBytes = async (request, maxBytes) => {
|
|
35
|
-
const buf = await request.arrayBuffer();
|
|
36
|
-
if (buf.byteLength > maxBytes) {
|
|
37
|
-
return {
|
|
38
|
-
ok: false,
|
|
39
|
-
response: toErrorResponse(413, 'PAYLOAD_TOO_LARGE', 'Body too large'),
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
const bytes = new Uint8Array(buf);
|
|
43
|
-
const text = new TextDecoder().decode(bytes);
|
|
44
|
-
return { ok: true, bytes, text };
|
|
45
|
-
};
|
|
46
|
-
const parseOptionalJson = (text) => {
|
|
47
|
-
if (text.trim() === '')
|
|
48
|
-
return { ok: true, payload: null };
|
|
49
|
-
const parsed = RequestValidator.parseJson(text);
|
|
50
|
-
if (!parsed.ok) {
|
|
51
|
-
let message = parsed.error.message;
|
|
52
|
-
if (parsed.error.code === 'INVALID_JSON') {
|
|
53
|
-
message = 'Invalid JSON body';
|
|
54
|
-
}
|
|
55
|
-
else if (parsed.error.code === 'VALIDATION_ERROR') {
|
|
56
|
-
message = 'Invalid body';
|
|
57
|
-
}
|
|
58
|
-
return { ok: false, response: toErrorResponse(400, parsed.error.code, message) };
|
|
59
|
-
}
|
|
60
|
-
return { ok: true, payload: parsed.value };
|
|
61
|
-
};
|
|
62
|
-
const loadSigningSecret = (env) => {
|
|
63
|
-
const direct = isString(env.KV_REMOTE_SECRET) ? env.KV_REMOTE_SECRET.trim() : '';
|
|
64
|
-
if (direct !== '')
|
|
65
|
-
return direct;
|
|
66
|
-
const fallback = isString(env.APP_KEY) ? env.APP_KEY.trim() : '';
|
|
67
|
-
if (fallback !== '')
|
|
68
|
-
return fallback;
|
|
69
|
-
return null;
|
|
70
|
-
};
|
|
71
|
-
const verifyNonceKv = async (kv, keyId, nonce, ttlMs) => {
|
|
72
|
-
const ttlSeconds = Math.max(1, Math.ceil(ttlMs / 1000));
|
|
73
|
-
const storageKey = `nonce:${keyId}:${nonce}`;
|
|
74
|
-
const existing = await kv.get(storageKey);
|
|
75
|
-
if (existing !== null)
|
|
76
|
-
return false;
|
|
77
|
-
await kv.put(storageKey, '1', { expirationTtl: ttlSeconds });
|
|
78
|
-
return true;
|
|
79
|
-
};
|
|
80
|
-
const verifySignedRequest = async (request, env, bodyBytes) => {
|
|
81
|
-
const secret = loadSigningSecret(env);
|
|
82
|
-
if (secret === null) {
|
|
83
|
-
return toErrorResponse(500, 'CONFIG_ERROR', 'Missing signing secret (KV_REMOTE_SECRET or APP_KEY)');
|
|
84
|
-
}
|
|
85
|
-
const windowMs = getEnvInt(env, 'ZT_PROXY_SIGNING_WINDOW_MS', DEFAULT_SIGNING_WINDOW_MS);
|
|
86
|
-
const verifyResult = await SigningService.verifyWithKeyProvider({
|
|
87
|
-
method: request.method,
|
|
88
|
-
url: request.url,
|
|
89
|
-
body: bodyBytes,
|
|
90
|
-
headers: request.headers,
|
|
91
|
-
windowMs,
|
|
92
|
-
getSecretForKeyId: (_keyId) => secret,
|
|
93
|
-
verifyNonce: env.ZT_NONCES === undefined
|
|
94
|
-
? undefined
|
|
95
|
-
: async (keyId, nonce, ttlMs) => verifyNonceKv(env.ZT_NONCES, keyId, nonce, ttlMs),
|
|
96
|
-
});
|
|
97
|
-
if (!verifyResult.ok) {
|
|
98
|
-
return toErrorResponse(verifyResult.status, verifyResult.code, verifyResult.message);
|
|
99
|
-
}
|
|
100
|
-
return { ok: true };
|
|
101
|
-
};
|
|
102
7
|
const requireCache = (env) => {
|
|
103
8
|
if (env.CACHE !== undefined && env.CACHE !== null)
|
|
104
9
|
return env.CACHE;
|
|
@@ -128,18 +33,20 @@ const buildStorageKey = (env, params) => {
|
|
|
128
33
|
parts.push(params.key);
|
|
129
34
|
return parts.join(':');
|
|
130
35
|
};
|
|
131
|
-
const
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
36
|
+
const resolveCacheRequest = async (request, env) => {
|
|
37
|
+
const check = await readAndVerifyJson(request, env, {
|
|
38
|
+
secretEnvVar: 'KV_REMOTE_SECRET',
|
|
39
|
+
missingSecretStatus: 500,
|
|
40
|
+
missingSecretMessage: 'Missing signing secret (KV_REMOTE_SECRET or APP_KEY)',
|
|
41
|
+
defaultSigningWindowMs: DEFAULT_SIGNING_WINDOW_MS,
|
|
42
|
+
defaultMaxBodyBytes: DEFAULT_MAX_BODY_BYTES,
|
|
43
|
+
});
|
|
44
|
+
if (!check.ok)
|
|
45
|
+
return { ok: false, response: check.response };
|
|
46
|
+
const cache = requireCache(env);
|
|
47
|
+
if (cache instanceof Response)
|
|
48
|
+
return { ok: false, response: cache };
|
|
49
|
+
return { ok: true, cache, payload: check.payload };
|
|
143
50
|
};
|
|
144
51
|
const parseGetPayload = (payload) => {
|
|
145
52
|
if (!isObject(payload)) {
|
|
@@ -154,25 +61,22 @@ const parseGetPayload = (payload) => {
|
|
|
154
61
|
return { ok: true, namespace: normalizeNamespace(payload['namespace']), key, type: typeValue };
|
|
155
62
|
};
|
|
156
63
|
const handleGet = async (request, env) => {
|
|
157
|
-
const
|
|
158
|
-
if (!
|
|
159
|
-
return
|
|
160
|
-
const
|
|
161
|
-
if (cache instanceof Response)
|
|
162
|
-
return cache;
|
|
163
|
-
const parsed = parseGetPayload(check.payload);
|
|
64
|
+
const resolved = await resolveCacheRequest(request, env);
|
|
65
|
+
if (!resolved.ok)
|
|
66
|
+
return resolved.response;
|
|
67
|
+
const parsed = parseGetPayload(resolved.payload);
|
|
164
68
|
if (!parsed.ok)
|
|
165
69
|
return parsed.response;
|
|
166
70
|
const storageKey = buildStorageKey(env, { namespace: parsed.namespace, key: parsed.key });
|
|
167
71
|
if (parsed.type === 'json') {
|
|
168
|
-
const value = await cache.get(storageKey, 'json');
|
|
72
|
+
const value = await resolved.cache.get(storageKey, 'json');
|
|
169
73
|
return json(200, { value: value ?? null });
|
|
170
74
|
}
|
|
171
75
|
if (parsed.type === 'arrayBuffer') {
|
|
172
|
-
const value = await cache.get(storageKey, 'arrayBuffer');
|
|
76
|
+
const value = await resolved.cache.get(storageKey, 'arrayBuffer');
|
|
173
77
|
return json(200, { value: value ?? null });
|
|
174
78
|
}
|
|
175
|
-
const value = await cache.get(storageKey);
|
|
79
|
+
const value = await resolved.cache.get(storageKey);
|
|
176
80
|
return json(200, { value: value ?? null });
|
|
177
81
|
};
|
|
178
82
|
const parsePutPayload = (payload) => {
|
|
@@ -196,13 +100,10 @@ const parsePutPayload = (payload) => {
|
|
|
196
100
|
};
|
|
197
101
|
};
|
|
198
102
|
const handlePut = async (request, env) => {
|
|
199
|
-
const
|
|
200
|
-
if (!
|
|
201
|
-
return
|
|
202
|
-
const
|
|
203
|
-
if (cache instanceof Response)
|
|
204
|
-
return cache;
|
|
205
|
-
const parsed = parsePutPayload(check.payload);
|
|
103
|
+
const resolved = await resolveCacheRequest(request, env);
|
|
104
|
+
if (!resolved.ok)
|
|
105
|
+
return resolved.response;
|
|
106
|
+
const parsed = parsePutPayload(resolved.payload);
|
|
206
107
|
if (!parsed.ok)
|
|
207
108
|
return parsed.response;
|
|
208
109
|
const storageKey = buildStorageKey(env, { namespace: parsed.namespace, key: parsed.key });
|
|
@@ -211,7 +112,7 @@ const handlePut = async (request, env) => {
|
|
|
211
112
|
if (parsed.ttlSeconds !== undefined) {
|
|
212
113
|
options.expirationTtl = Math.floor(parsed.ttlSeconds);
|
|
213
114
|
}
|
|
214
|
-
await cache.put(storageKey, value, options);
|
|
115
|
+
await resolved.cache.put(storageKey, value, options);
|
|
215
116
|
return json(200, { ok: true });
|
|
216
117
|
};
|
|
217
118
|
const parseDeletePayload = (payload) => {
|
|
@@ -225,17 +126,14 @@ const parseDeletePayload = (payload) => {
|
|
|
225
126
|
return { ok: true, namespace: normalizeNamespace(payload['namespace']), key };
|
|
226
127
|
};
|
|
227
128
|
const handleDelete = async (request, env) => {
|
|
228
|
-
const
|
|
229
|
-
if (!
|
|
230
|
-
return
|
|
231
|
-
const
|
|
232
|
-
if (cache instanceof Response)
|
|
233
|
-
return cache;
|
|
234
|
-
const parsed = parseDeletePayload(check.payload);
|
|
129
|
+
const resolved = await resolveCacheRequest(request, env);
|
|
130
|
+
if (!resolved.ok)
|
|
131
|
+
return resolved.response;
|
|
132
|
+
const parsed = parseDeletePayload(resolved.payload);
|
|
235
133
|
if (!parsed.ok)
|
|
236
134
|
return parsed.response;
|
|
237
135
|
const storageKey = buildStorageKey(env, { namespace: parsed.namespace, key: parsed.key });
|
|
238
|
-
await cache.delete(storageKey);
|
|
136
|
+
await resolved.cache.delete(storageKey);
|
|
239
137
|
return json(200, { ok: true });
|
|
240
138
|
};
|
|
241
139
|
const parseListPayload = (payload) => {
|
|
@@ -252,13 +150,10 @@ const parseListPayload = (payload) => {
|
|
|
252
150
|
return { ok: true, params: { namespace, prefix, cursor, limit: limitParsed } };
|
|
253
151
|
};
|
|
254
152
|
const handleList = async (request, env) => {
|
|
255
|
-
const
|
|
256
|
-
if (!
|
|
257
|
-
return
|
|
258
|
-
const
|
|
259
|
-
if (cache instanceof Response)
|
|
260
|
-
return cache;
|
|
261
|
-
const parsed = parseListPayload(check.payload);
|
|
153
|
+
const resolved = await resolveCacheRequest(request, env);
|
|
154
|
+
if (!resolved.ok)
|
|
155
|
+
return resolved.response;
|
|
156
|
+
const parsed = parseListPayload(resolved.payload);
|
|
262
157
|
if (!parsed.ok)
|
|
263
158
|
return parsed.response;
|
|
264
159
|
const envLimit = getEnvInt(env, 'ZT_KV_LIST_LIMIT', DEFAULT_LIST_LIMIT);
|
|
@@ -268,7 +163,11 @@ const handleList = async (request, env) => {
|
|
|
268
163
|
const namespacePrefix = normalizeNamespace(parsed.params.namespace);
|
|
269
164
|
const basePrefix = buildStorageKey(env, { namespace: namespacePrefix, key: '' });
|
|
270
165
|
const fullPrefix = prefixKey === undefined ? basePrefix : `${basePrefix}${prefixKey}`;
|
|
271
|
-
const out = await cache.list({
|
|
166
|
+
const out = await resolved.cache.list({
|
|
167
|
+
prefix: fullPrefix,
|
|
168
|
+
limit,
|
|
169
|
+
cursor: parsed.params.cursor,
|
|
170
|
+
});
|
|
272
171
|
return json(200, {
|
|
273
172
|
keys: out.keys.map((key) => key.name),
|
|
274
173
|
cursor: out.cursor,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CsrfTokenManager.d.ts","sourceRoot":"","sources":["../../../src/security/CsrfTokenManager.ts"],"names":[],"mappings":"AACA;;;GAGG;AAQH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClE,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAC/D,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxD,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,OAAO,CAAC,EAAE,uBAAuB,GAAG,iBAAiB,CAAC;CAC9D;AAED,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE/C,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;
|
|
1
|
+
{"version":3,"file":"CsrfTokenManager.d.ts","sourceRoot":"","sources":["../../../src/security/CsrfTokenManager.ts"],"names":[],"mappings":"AACA;;;GAGG;AAQH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAClE,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAC/D,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACxD,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,OAAO,CAAC,EAAE,uBAAuB,GAAG,iBAAiB,CAAC;CAC9D;AAED,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE/C,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AA6VF;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,oBAE7B,CAAC"}
|
|
@@ -117,7 +117,7 @@ const createRedisClientFactory = (options) => {
|
|
|
117
117
|
port: Env.getInt('REDIS_PORT', ZintrustLang.REDIS_DEFAULT_PORT),
|
|
118
118
|
password: Env.get('REDIS_PASSWORD'),
|
|
119
119
|
db: database,
|
|
120
|
-
});
|
|
120
|
+
}, 3, { subsystem: 'csrf' });
|
|
121
121
|
return redisClient;
|
|
122
122
|
};
|
|
123
123
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JwtSessions.d.ts","sourceRoot":"","sources":["../../../src/security/JwtSessions.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,IAAI,GAAG,WAAW,CAAC;AAEzF,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"JwtSessions.d.ts","sourceRoot":"","sources":["../../../src/security/JwtSessions.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,IAAI,GAAG,WAAW,CAAC;AAEzF,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;AAynBhE,eAAO,MAAM,WAAW;oBACA,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;oBAKtB,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;mBAM1B,mBAAmB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;mBAU5C,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;iBAQ9B,qBAAqB;sBAIhB,IAAI;EAItB,CAAC;AAEH,eAAe,WAAW,CAAC"}
|
|
@@ -246,7 +246,7 @@ const createRedisStore = (params) => {
|
|
|
246
246
|
port: Env.REDIS_PORT,
|
|
247
247
|
password: Env.REDIS_PASSWORD,
|
|
248
248
|
db: Env.getInt('JWT_REVOCATION_REDIS_DB', Env.REDIS_DB),
|
|
249
|
-
});
|
|
249
|
+
}, 3, { subsystem: 'jwt-sessions' });
|
|
250
250
|
const indexGet = async (sub) => {
|
|
251
251
|
const value = await client.get(encodeSubIndexKey(params.keyPrefix, sub));
|
|
252
252
|
if (value === null)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TokenRevocation.d.ts","sourceRoot":"","sources":["../../../src/security/TokenRevocation.ts"],"names":[],"mappings":"AAUA,KAAK,mBAAmB,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;AAEzD,MAAM,MAAM,yBAAyB,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,IAAI,GAAG,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"TokenRevocation.d.ts","sourceRoot":"","sources":["../../../src/security/TokenRevocation.ts"],"names":[],"mappings":"AAUA,KAAK,mBAAmB,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;AAEzD,MAAM,MAAM,yBAAyB,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,IAAI,GAAG,WAAW,CAAC;AA6sB7F,eAAO,MAAM,eAAe;IAC1B;;;;OAIG;mBACkB,mBAAmB,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAUjE;;OAEG;qBACoB,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAMhD;;OAEG;iBACU,yBAAyB;IAItC;;OAEG;sBACe,IAAI;EAItB,CAAC;AAEH,eAAe,eAAe,CAAC"}
|
|
@@ -203,7 +203,7 @@ const createRedisStore = (params) => {
|
|
|
203
203
|
port: Env.REDIS_PORT,
|
|
204
204
|
password: Env.REDIS_PASSWORD,
|
|
205
205
|
db: Env.getInt('JWT_REVOCATION_REDIS_DB', Env.REDIS_DB),
|
|
206
|
-
});
|
|
206
|
+
}, 3, { subsystem: 'jwt-revocation' });
|
|
207
207
|
return {
|
|
208
208
|
async revoke(key) {
|
|
209
209
|
const ttlMs = Math.max(0, key.expiresAtMs - Date.now());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LockProvider.d.ts","sourceRoot":"","sources":["../../../../src/tools/queue/LockProvider.ts"],"names":[],"mappings":"AACA;;;GAGG;AAGH,OAAO,KAAK,EAGV,YAAY,EACZ,kBAAkB,EAEnB,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"LockProvider.d.ts","sourceRoot":"","sources":["../../../../src/tools/queue/LockProvider.ts"],"names":[],"mappings":"AACA;;;GAGG;AAGH,OAAO,KAAK,EAGV,YAAY,EACZ,kBAAkB,EAEnB,MAAM,eAAe,CAAC;AA4BvB;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAUvD;AAkJD;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY,CAWhF;AAYD,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY,CA4FjF;AAOD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,IAAI,CAG/E;AACD,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAEtE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,GAAG,YAAY,CAS3E"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { RedisConfig } from '../../config/type';
|
|
2
|
+
export type RedisTransportMode = 'direct' | 'proxy';
|
|
3
|
+
export type RedisTransportOptions = Readonly<{
|
|
4
|
+
subsystem?: string;
|
|
5
|
+
requireDirect?: boolean;
|
|
6
|
+
}>;
|
|
7
|
+
type RedisProxyConnection = {
|
|
8
|
+
status: 'ready';
|
|
9
|
+
connect: () => Promise<void>;
|
|
10
|
+
quit: () => Promise<'OK'>;
|
|
11
|
+
disconnect: () => void;
|
|
12
|
+
on: (event: string, handler: (...args: unknown[]) => void) => RedisProxyConnection;
|
|
13
|
+
once: (event: string, handler: (...args: unknown[]) => void) => RedisProxyConnection;
|
|
14
|
+
off: (event: string, handler: (...args: unknown[]) => void) => RedisProxyConnection;
|
|
15
|
+
removeListener: (event: string, handler: (...args: unknown[]) => void) => RedisProxyConnection;
|
|
16
|
+
call: (command: string, ...args: unknown[]) => Promise<unknown>;
|
|
17
|
+
pipeline: () => {
|
|
18
|
+
exec: () => Promise<Array<[Error | null, unknown]>>;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
};
|
|
21
|
+
scanStream: (options?: {
|
|
22
|
+
match?: string;
|
|
23
|
+
count?: number;
|
|
24
|
+
}) => {
|
|
25
|
+
on: (event: string, handler: (...args: unknown[]) => void) => unknown;
|
|
26
|
+
once: (event: string, handler: (...args: unknown[]) => void) => unknown;
|
|
27
|
+
off: (event: string, handler: (...args: unknown[]) => void) => unknown;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
export declare const resolveRedisTransportMode: () => RedisTransportMode;
|
|
31
|
+
export declare const createRedisProxyConnection: (config: RedisConfig, options?: RedisTransportOptions) => RedisProxyConnection;
|
|
32
|
+
export declare const ensureRedisTransportMode: (config: RedisConfig, options?: RedisTransportOptions) => RedisTransportMode;
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=RedisTransport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RedisTransport.d.ts","sourceRoot":"","sources":["../../../../src/tools/redis/RedisTransport.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAIhD,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEpD,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC,CAAC;AASH,KAAK,oBAAoB,GAAG;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,KAAK,oBAAoB,CAAC;IACnF,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,KAAK,oBAAoB,CAAC;IACrF,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,KAAK,oBAAoB,CAAC;IACpF,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,KAAK,oBAAoB,CAAC;IAC/F,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAChE,QAAQ,EAAE,MAAM;QACd,IAAI,EAAE,MAAM,OAAO,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;QACpD,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;KACxB,CAAC;IACF,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK;QAC5D,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,KAAK,OAAO,CAAC;QACtE,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,KAAK,OAAO,CAAC;QACxE,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,KAAK,OAAO,CAAC;KACxE,CAAC;CACH,CAAC;AA0PF,eAAO,MAAM,yBAAyB,QAAO,kBAE5C,CAAC;AAEF,eAAO,MAAM,0BAA0B,GACrC,QAAQ,WAAW,EACnB,UAAU,qBAAqB,KAC9B,oBAoCF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,QAAQ,WAAW,EACnB,UAAU,qBAAqB,KAC9B,kBAaF,CAAC"}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { Env } from '../../config/env.js';
|
|
2
|
+
import { Logger } from '../../config/logger.js';
|
|
3
|
+
import { ErrorFactory } from '../../exceptions/ZintrustError.js';
|
|
4
|
+
import { SignedRequest } from '../../security/SignedRequest.js';
|
|
5
|
+
const loggedSelections = new Set();
|
|
6
|
+
const resolveSigningPrefix = (baseUrl) => {
|
|
7
|
+
try {
|
|
8
|
+
const parsed = new URL(baseUrl);
|
|
9
|
+
const path = parsed.pathname.endsWith('/') ? parsed.pathname.slice(0, -1) : parsed.pathname;
|
|
10
|
+
if (path === '' || path === '/')
|
|
11
|
+
return undefined;
|
|
12
|
+
return path;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const buildRequestUrl = (baseUrl, requestPath) => {
|
|
19
|
+
const url = new URL(baseUrl);
|
|
20
|
+
const basePath = url.pathname.endsWith('/') ? url.pathname.slice(0, -1) : url.pathname;
|
|
21
|
+
const normalizedPath = requestPath.startsWith('/') ? requestPath : `/${requestPath}`;
|
|
22
|
+
url.pathname = `${basePath}${normalizedPath}`;
|
|
23
|
+
return url;
|
|
24
|
+
};
|
|
25
|
+
const buildSigningUrl = (requestUrl, baseUrl) => {
|
|
26
|
+
const prefix = resolveSigningPrefix(baseUrl);
|
|
27
|
+
if (prefix === undefined)
|
|
28
|
+
return requestUrl;
|
|
29
|
+
if (requestUrl.pathname === prefix || requestUrl.pathname.startsWith(`${prefix}/`)) {
|
|
30
|
+
const signingUrl = new URL(requestUrl.toString());
|
|
31
|
+
const stripped = requestUrl.pathname.slice(prefix.length);
|
|
32
|
+
signingUrl.pathname = stripped.startsWith('/') ? stripped : `/${stripped}`;
|
|
33
|
+
return signingUrl;
|
|
34
|
+
}
|
|
35
|
+
return requestUrl;
|
|
36
|
+
};
|
|
37
|
+
const resolveProxyBaseUrl = () => {
|
|
38
|
+
const explicit = Env.REDIS_PROXY_URL.trim();
|
|
39
|
+
if (explicit !== '')
|
|
40
|
+
return explicit;
|
|
41
|
+
return `http://${Env.REDIS_PROXY_HOST}:${Env.REDIS_PROXY_PORT}`;
|
|
42
|
+
};
|
|
43
|
+
const resolveProxySettings = () => ({
|
|
44
|
+
baseUrl: resolveProxyBaseUrl(),
|
|
45
|
+
keyId: Env.REDIS_PROXY_KEY_ID.trim() === '' ? undefined : Env.REDIS_PROXY_KEY_ID,
|
|
46
|
+
secret: Env.REDIS_PROXY_SECRET.trim() === '' ? undefined : Env.REDIS_PROXY_SECRET,
|
|
47
|
+
timeoutMs: Env.REDIS_PROXY_TIMEOUT_MS,
|
|
48
|
+
});
|
|
49
|
+
const buildHeaders = async (settings, requestUrl, body) => {
|
|
50
|
+
const headers = {
|
|
51
|
+
'Content-Type': 'application/json',
|
|
52
|
+
};
|
|
53
|
+
if (settings.keyId !== undefined && settings.secret !== undefined) {
|
|
54
|
+
const signingUrl = buildSigningUrl(requestUrl, settings.baseUrl);
|
|
55
|
+
const signed = await SignedRequest.createHeaders({
|
|
56
|
+
method: 'POST',
|
|
57
|
+
url: signingUrl,
|
|
58
|
+
body,
|
|
59
|
+
keyId: settings.keyId,
|
|
60
|
+
secret: settings.secret,
|
|
61
|
+
});
|
|
62
|
+
Object.assign(headers, signed);
|
|
63
|
+
}
|
|
64
|
+
return headers;
|
|
65
|
+
};
|
|
66
|
+
const requestProxyCommand = async (settings, command, args) => {
|
|
67
|
+
if (settings.baseUrl.trim() === '') {
|
|
68
|
+
throw ErrorFactory.createConfigError('Redis proxy URL is missing (REDIS_PROXY_URL)');
|
|
69
|
+
}
|
|
70
|
+
const body = JSON.stringify({ command, args });
|
|
71
|
+
const requestUrl = buildRequestUrl(settings.baseUrl, '/zin/redis/command');
|
|
72
|
+
const headers = await buildHeaders(settings, requestUrl, body);
|
|
73
|
+
const signal = typeof AbortSignal !== 'undefined' && 'timeout' in AbortSignal
|
|
74
|
+
? AbortSignal.timeout(settings.timeoutMs)
|
|
75
|
+
: undefined;
|
|
76
|
+
const response = await fetch(requestUrl.toString(), {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers,
|
|
79
|
+
body,
|
|
80
|
+
signal,
|
|
81
|
+
});
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
const text = await response.text();
|
|
84
|
+
throw ErrorFactory.createTryCatchError(`Redis proxy request failed (${response.status})`, text);
|
|
85
|
+
}
|
|
86
|
+
const parsed = (await response.json());
|
|
87
|
+
return parsed.result;
|
|
88
|
+
};
|
|
89
|
+
const logTransportSelection = (mode, config, options) => {
|
|
90
|
+
const rawSubsystem = options?.subsystem?.trim();
|
|
91
|
+
const subsystem = rawSubsystem === undefined || rawSubsystem === '' ? 'redis' : rawSubsystem;
|
|
92
|
+
const cacheKey = `${subsystem}:${mode}:${config.db}`;
|
|
93
|
+
if (loggedSelections.has(cacheKey)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
loggedSelections.add(cacheKey);
|
|
97
|
+
Logger.info('[redis][transport] resolved transport', {
|
|
98
|
+
subsystem,
|
|
99
|
+
mode,
|
|
100
|
+
db: config.db,
|
|
101
|
+
host: mode === 'direct' ? config.host : undefined,
|
|
102
|
+
port: mode === 'direct' ? config.port : undefined,
|
|
103
|
+
proxyUrl: mode === 'proxy' ? resolveProxyBaseUrl() : undefined,
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
const normalizeScanResponse = (value) => {
|
|
107
|
+
if (!Array.isArray(value) || value.length < 2) {
|
|
108
|
+
return ['0', []];
|
|
109
|
+
}
|
|
110
|
+
const cursor = typeof value[0] === 'string' ? value[0] : String(value[0] ?? '0');
|
|
111
|
+
const batch = Array.isArray(value[1])
|
|
112
|
+
? value[1].filter((entry) => typeof entry === 'string')
|
|
113
|
+
: [];
|
|
114
|
+
return [cursor, batch];
|
|
115
|
+
};
|
|
116
|
+
const createScanStream = (settings, options) => {
|
|
117
|
+
const handlers = new Map();
|
|
118
|
+
const stream = {
|
|
119
|
+
on(event, handler) {
|
|
120
|
+
const current = handlers.get(event) ?? new Set();
|
|
121
|
+
current.add(handler);
|
|
122
|
+
handlers.set(event, current);
|
|
123
|
+
return stream;
|
|
124
|
+
},
|
|
125
|
+
once(event, handler) {
|
|
126
|
+
const wrapped = (...args) => {
|
|
127
|
+
stream.off(event, wrapped);
|
|
128
|
+
handler(...args);
|
|
129
|
+
};
|
|
130
|
+
return stream.on(event, wrapped);
|
|
131
|
+
},
|
|
132
|
+
off(event, handler) {
|
|
133
|
+
handlers.get(event)?.delete(handler);
|
|
134
|
+
return stream;
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
const emit = (event, ...args) => {
|
|
138
|
+
for (const handler of handlers.get(event) ?? []) {
|
|
139
|
+
handler(...args);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
queueMicrotask(() => {
|
|
143
|
+
void (async () => {
|
|
144
|
+
try {
|
|
145
|
+
let cursor = '0';
|
|
146
|
+
do {
|
|
147
|
+
// eslint-disable-next-line no-await-in-loop
|
|
148
|
+
const result = await requestProxyCommand(settings, 'SCAN', [
|
|
149
|
+
cursor,
|
|
150
|
+
'MATCH',
|
|
151
|
+
options?.match ?? '*',
|
|
152
|
+
'COUNT',
|
|
153
|
+
String(options?.count ?? 200),
|
|
154
|
+
]);
|
|
155
|
+
const [nextCursor, batch] = normalizeScanResponse(result);
|
|
156
|
+
cursor = nextCursor;
|
|
157
|
+
if (batch.length > 0) {
|
|
158
|
+
emit('data', batch);
|
|
159
|
+
}
|
|
160
|
+
} while (cursor !== '0');
|
|
161
|
+
emit('end');
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
emit('error', error);
|
|
165
|
+
}
|
|
166
|
+
})();
|
|
167
|
+
});
|
|
168
|
+
return stream;
|
|
169
|
+
};
|
|
170
|
+
const createPipeline = (settings) => {
|
|
171
|
+
const commands = [];
|
|
172
|
+
const target = {
|
|
173
|
+
async exec() {
|
|
174
|
+
const results = [];
|
|
175
|
+
for (const entry of commands) {
|
|
176
|
+
try {
|
|
177
|
+
// eslint-disable-next-line no-await-in-loop
|
|
178
|
+
const result = await requestProxyCommand(settings, entry.command, entry.args);
|
|
179
|
+
results.push([null, result]);
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
results.push([
|
|
183
|
+
error instanceof Error
|
|
184
|
+
? error
|
|
185
|
+
: ErrorFactory.createTryCatchError('Redis pipeline command failed', error),
|
|
186
|
+
null,
|
|
187
|
+
]);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return results;
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
const pipeline = new Proxy(target, {
|
|
194
|
+
get(obj, prop) {
|
|
195
|
+
if (typeof prop !== 'string')
|
|
196
|
+
return Reflect.get(obj, prop);
|
|
197
|
+
if (prop in obj)
|
|
198
|
+
return Reflect.get(obj, prop);
|
|
199
|
+
return (...args) => {
|
|
200
|
+
commands.push({ command: prop.toUpperCase(), args });
|
|
201
|
+
return pipeline;
|
|
202
|
+
};
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
return pipeline;
|
|
206
|
+
};
|
|
207
|
+
export const resolveRedisTransportMode = () => {
|
|
208
|
+
return Env.USE_REDIS_PROXY || Env.REDIS_PROXY_URL.trim() !== '' ? 'proxy' : 'direct';
|
|
209
|
+
};
|
|
210
|
+
export const createRedisProxyConnection = (config, options) => {
|
|
211
|
+
const settings = resolveProxySettings();
|
|
212
|
+
logTransportSelection('proxy', config, options);
|
|
213
|
+
const target = {
|
|
214
|
+
status: 'ready',
|
|
215
|
+
connect: async () => { },
|
|
216
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
217
|
+
quit: async () => 'OK',
|
|
218
|
+
disconnect: () => undefined,
|
|
219
|
+
on: (_event, _handler) => client,
|
|
220
|
+
once: (_event, _handler) => client,
|
|
221
|
+
off: (_event, _handler) => client,
|
|
222
|
+
removeListener: (_event, _handler) => client,
|
|
223
|
+
call: async (command, ...args) => {
|
|
224
|
+
return requestProxyCommand(settings, command, args);
|
|
225
|
+
},
|
|
226
|
+
pipeline: () => createPipeline(settings),
|
|
227
|
+
scanStream: (scanOptions) => createScanStream(settings, scanOptions),
|
|
228
|
+
};
|
|
229
|
+
const client = new Proxy(target, {
|
|
230
|
+
get(obj, prop) {
|
|
231
|
+
if (typeof prop !== 'string')
|
|
232
|
+
return Reflect.get(obj, prop);
|
|
233
|
+
if (prop === 'then')
|
|
234
|
+
return undefined;
|
|
235
|
+
if (prop in obj)
|
|
236
|
+
return Reflect.get(obj, prop);
|
|
237
|
+
return async (...args) => requestProxyCommand(settings, prop.toUpperCase(), args);
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
return client;
|
|
241
|
+
};
|
|
242
|
+
export const ensureRedisTransportMode = (config, options) => {
|
|
243
|
+
const mode = resolveRedisTransportMode();
|
|
244
|
+
if (mode === 'proxy' && options?.requireDirect === true) {
|
|
245
|
+
throw ErrorFactory.createConfigError(`Redis subsystem '${options.subsystem ?? 'redis'}' requires a direct Redis connection, but proxy mode is enabled.`);
|
|
246
|
+
}
|
|
247
|
+
if (mode === 'direct') {
|
|
248
|
+
logTransportSelection(mode, config, options);
|
|
249
|
+
}
|
|
250
|
+
return mode;
|
|
251
|
+
};
|