@zintrust/core 2.4.0 → 2.4.2
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/bin/zintrust-main.d.ts.map +1 -1
- package/bin/zintrust-main.js +11 -1
- package/package.json +2 -2
- package/src/cache/Cache.d.ts +6 -0
- package/src/cache/Cache.d.ts.map +1 -1
- package/src/cache/Cache.js +50 -0
- package/src/cache/CacheDriver.d.ts +8 -0
- package/src/cache/CacheDriver.d.ts.map +1 -1
- package/src/cache/drivers/RedisDriver.d.ts.map +1 -1
- package/src/cache/drivers/RedisDriver.js +64 -2
- package/src/cli/CLI.d.ts.map +1 -1
- package/src/cli/CLI.js +2 -0
- package/src/cli/commands/DoctorArchitectureCommand.d.ts.map +1 -1
- package/src/cli/commands/DoctorArchitectureCommand.js +4 -2
- package/src/cli/commands/RedisRpcCommand.d.ts +6 -0
- package/src/cli/commands/RedisRpcCommand.d.ts.map +1 -0
- package/src/cli/commands/RedisRpcCommand.js +96 -0
- package/src/cli/commands/index.d.ts +1 -0
- package/src/cli/commands/index.d.ts.map +1 -1
- package/src/cli/commands/index.js +1 -0
- package/src/cli/scaffolding/env.d.ts.map +1 -1
- package/src/cli/scaffolding/env.js +9 -0
- package/src/cli/services/WorkerStartupDiagnostics.d.ts.map +1 -1
- package/src/cli/services/WorkerStartupDiagnostics.js +4 -2
- package/src/config/app.d.ts.map +1 -1
- package/src/config/app.js +3 -0
- package/src/config/env.d.ts +7 -0
- package/src/config/env.d.ts.map +1 -1
- package/src/config/env.js +7 -0
- package/src/config/queue.d.ts.map +1 -1
- package/src/config/queue.js +4 -42
- package/src/config/workers.d.ts.map +1 -1
- package/src/config/workers.js +12 -6
- package/src/index.js +3 -3
- package/src/orm/adapters/D1RemoteAdapter.d.ts.map +1 -1
- package/src/orm/adapters/D1RemoteAdapter.js +2 -1
- package/src/proxy/redis/RedisProxyServer.d.ts.map +1 -1
- package/src/proxy/redis/RedisProxyServer.js +77 -241
- package/src/tools/mail/drivers/Cloudflare.d.ts.map +1 -1
- package/src/tools/mail/drivers/Cloudflare.js +2 -0
- package/src/tools/redis/RedisTransport.d.ts +2 -27
- package/src/tools/redis/RedisTransport.d.ts.map +1 -1
- package/src/tools/redis/RedisTransport.js +123 -155
- package/src/zintrust.plugins.d.ts +6 -3
- package/src/zintrust.plugins.d.ts.map +1 -1
- package/src/zintrust.plugins.js +6 -3
- package/src/proxy/redis/RedisProxyActions.d.ts +0 -15
- package/src/proxy/redis/RedisProxyActions.d.ts.map +0 -1
- package/src/proxy/redis/RedisProxyActions.js +0 -124
package/src/config/queue.js
CHANGED
|
@@ -69,51 +69,13 @@ const readWorkersFallbackBool = (workersKey, fallbackKey, fallback) => {
|
|
|
69
69
|
}
|
|
70
70
|
return Env.getBool(fallbackKey, fallback);
|
|
71
71
|
};
|
|
72
|
-
const parseRedisUrl = (rawUrl) => {
|
|
73
|
-
try {
|
|
74
|
-
const url = new URL(rawUrl);
|
|
75
|
-
if (url.protocol !== 'redis:' && url.protocol !== 'rediss:')
|
|
76
|
-
return null;
|
|
77
|
-
const host = url.hostname;
|
|
78
|
-
const port = url.port ? Number.parseInt(url.port, 10) : 6379;
|
|
79
|
-
const password = url.password ? decodeURIComponent(url.password) : undefined;
|
|
80
|
-
const db = url.pathname ? Number.parseInt(url.pathname.replace('/', ''), 10) : undefined;
|
|
81
|
-
return { host, port, password, database: Number.isFinite(db ?? Number.NaN) ? db : undefined };
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
const resolveRedisProxyConfig = () => {
|
|
88
|
-
const isWorkersRuntime = Cloudflare.getWorkersEnv() !== null;
|
|
89
|
-
if (!isWorkersRuntime) {
|
|
90
|
-
return null;
|
|
91
|
-
}
|
|
92
|
-
const proxyUrl = Env.get('REDIS_PROXY_URL', '').trim();
|
|
93
|
-
const parsed = proxyUrl ? parseRedisUrl(proxyUrl) : null;
|
|
94
|
-
if (parsed)
|
|
95
|
-
return parsed;
|
|
96
|
-
if (Env.getBool('USE_REDIS_PROXY', false)) {
|
|
97
|
-
return {
|
|
98
|
-
host: Env.get('REDIS_PROXY_HOST', ''),
|
|
99
|
-
port: Env.getInt('REDIS_PROXY_PORT', 6379),
|
|
100
|
-
password: Env.get('REDIS_PASSWORD', ''),
|
|
101
|
-
database: Env.getInt('REDIS_QUEUE_DB', ZintrustLang.REDIS_DEFAULT_DB),
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
return null;
|
|
105
|
-
};
|
|
106
72
|
const createRedisQueueDriver = () => {
|
|
107
|
-
const proxyConfig = resolveRedisProxyConfig();
|
|
108
73
|
return {
|
|
109
74
|
driver: 'redis',
|
|
110
|
-
host:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
readWorkersFallbackString('WORKERS_REDIS_PASSWORD', 'REDIS_PASSWORD'),
|
|
115
|
-
database: proxyConfig?.database ??
|
|
116
|
-
readWorkersFallbackInt('WORKERS_REDIS_QUEUE_DB', 'REDIS_QUEUE_DB', ZintrustLang.REDIS_DEFAULT_DB),
|
|
75
|
+
host: readWorkersFallbackString('WORKERS_REDIS_HOST', 'REDIS_HOST', 'localhost'),
|
|
76
|
+
port: readWorkersFallbackInt('WORKERS_REDIS_PORT', 'REDIS_PORT', 6379),
|
|
77
|
+
password: readWorkersFallbackString('WORKERS_REDIS_PASSWORD', 'REDIS_PASSWORD'),
|
|
78
|
+
database: readWorkersFallbackInt('WORKERS_REDIS_QUEUE_DB', 'REDIS_QUEUE_DB', ZintrustLang.REDIS_DEFAULT_DB),
|
|
117
79
|
// Cloudflare tunnel-specific ioredis options
|
|
118
80
|
connectTimeout: readWorkersFallbackInt('WORKERS_REDIS_CONNECT_TIMEOUT', 'REDIS_CONNECT_TIMEOUT', Env.REDIS_CONNECT_TIMEOUT),
|
|
119
81
|
keepAlive: readWorkersFallbackInt('WORKERS_REDIS_KEEP_ALIVE', 'REDIS_KEEP_ALIVE', Env.REDIS_KEEP_ALIVE),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workers.d.ts","sourceRoot":"","sources":["../../../src/config/workers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,8BAA8B,CAAC;AAItC,OAAO,KAAK,EACV,WAAW,EAIX,mBAAmB,EACpB,MAAM,cAAc,CAAC;AAItB,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"workers.d.ts","sourceRoot":"","sources":["../../../src/config/workers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,8BAA8B,CAAC;AAItC,OAAO,KAAK,EACV,WAAW,EAIX,mBAAmB,EACpB,MAAM,cAAc,CAAC;AAItB,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAibnC,eAAO,MAAM,qBAAqB,GAChC,QAAQ,WAAW,EACnB,mBAAc,EACd,UAAU,qBAAqB,KAC9B,OASF,CAAC;AAEF,eAAO,MAAM,wBAAwB,QAAa,OAAO,CAAC,IAAI,CAmB7D,CAAC;AAgFF,QAAA,MAAM,mBAAmB,QAAO,mBA2D/B,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAkBnE,eAAO,MAAM,aAAa,EAAE,aAY1B,CAAC"}
|
package/src/config/workers.js
CHANGED
|
@@ -219,7 +219,7 @@ const validateRedisConfig = (_config, effectiveConfig, isWorkersRuntime, proxySe
|
|
|
219
219
|
if (!isWorkersRuntime && stillTargetsHttpProxy) {
|
|
220
220
|
throw ErrorFactory.createConfigError('Redis config points to an HTTP proxy endpoint. Set REDIS_HOST/REDIS_PORT to your TCP Redis server (redis://), not REDIS_PROXY_URL/REDIS_PROXY_PORT.');
|
|
221
221
|
}
|
|
222
|
-
const shouldUseProxy = Env.USE_REDIS_PROXY === true
|
|
222
|
+
const shouldUseProxy = Env.USE_REDIS_PROXY === true;
|
|
223
223
|
if (!shouldUseProxy && isWorkersRuntime && Cloudflare.isCloudflareSocketsEnabled() === false) {
|
|
224
224
|
throw ErrorFactory.createConfigError('Redis connections in Cloudflare Workers require ENABLE_CLOUDFLARE_SOCKETS=true.');
|
|
225
225
|
}
|
|
@@ -285,11 +285,8 @@ const resolveEffectiveRedisConfig = (config, isWorkersRuntime, proxySettings) =>
|
|
|
285
285
|
}
|
|
286
286
|
return config;
|
|
287
287
|
};
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if (mode === 'proxy') {
|
|
291
|
-
return createRedisProxyConnection(config, options);
|
|
292
|
-
}
|
|
288
|
+
const BULLMQ_SUBSYSTEMS = new Set(['queue-bullmq', 'queue-monitor', 'worker-queue']);
|
|
289
|
+
const createDirectRedisConnection = (config, maxRetries, options) => {
|
|
293
290
|
const isWorkersRuntime = Cloudflare.getWorkersEnv() !== null;
|
|
294
291
|
const proxySettings = getProxySettings();
|
|
295
292
|
const effectiveConfig = resolveEffectiveRedisConfig(config, isWorkersRuntime, proxySettings);
|
|
@@ -328,6 +325,15 @@ export const createRedisConnection = (config, maxRetries = 3, options) => {
|
|
|
328
325
|
setupRedisErrorHandler(client);
|
|
329
326
|
return trackRedisConnection(client, cacheKey);
|
|
330
327
|
};
|
|
328
|
+
export const createRedisConnection = (config, maxRetries = 3, options) => {
|
|
329
|
+
const subsystem = options?.subsystem ?? '';
|
|
330
|
+
const skipProxy = BULLMQ_SUBSYSTEMS.has(subsystem);
|
|
331
|
+
const mode = skipProxy ? 'direct' : ensureRedisTransportMode(config, options);
|
|
332
|
+
if (mode === 'proxy' || mode === 'rpc') {
|
|
333
|
+
return createRedisProxyConnection(config, options);
|
|
334
|
+
}
|
|
335
|
+
return createDirectRedisConnection(config, maxRetries, options);
|
|
336
|
+
};
|
|
331
337
|
export const shutdownRedisConnections = async () => {
|
|
332
338
|
const registry = getRedisConnectionRegistry();
|
|
333
339
|
const trackedConnections = Array.from(registry.activeConnections);
|
package/src/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @zintrust/core v2.4.
|
|
2
|
+
* @zintrust/core v2.4.2
|
|
3
3
|
*
|
|
4
4
|
* ZinTrust Framework - Production-Grade TypeScript Backend
|
|
5
5
|
* Built for performance, type safety, and exceptional developer experience
|
|
6
6
|
*
|
|
7
7
|
* Build Information:
|
|
8
|
-
* Built: 2026-05-
|
|
8
|
+
* Built: 2026-05-31T12:25:06.738Z
|
|
9
9
|
* Node: >=20.0.0
|
|
10
10
|
* License: MIT
|
|
11
11
|
*
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* Available at runtime for debugging and health checks
|
|
22
22
|
*/
|
|
23
23
|
export const ZINTRUST_VERSION = '0.1.41';
|
|
24
|
-
export const ZINTRUST_BUILD_DATE = '2026-05-
|
|
24
|
+
export const ZINTRUST_BUILD_DATE = '2026-05-31T12:25:06.703Z'; // Replaced during build
|
|
25
25
|
export { Application } from './boot/Application.js';
|
|
26
26
|
export { AwsSigV4 } from './common/index.js';
|
|
27
27
|
export { SignedRequest } from './security/SignedRequest.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"D1RemoteAdapter.d.ts","sourceRoot":"","sources":["../../../../src/orm/adapters/D1RemoteAdapter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAe,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"D1RemoteAdapter.d.ts","sourceRoot":"","sources":["../../../../src/orm/adapters/D1RemoteAdapter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAe,MAAM,sBAAsB,CAAC;AA2T1F,eAAO,MAAM,eAAe;oBACV,cAAc,GAAG,gBAAgB;EAgBjD,CAAC;AAEH,eAAe,eAAe,CAAC"}
|
|
@@ -8,7 +8,7 @@ import { Env } from '../../config/env.js';
|
|
|
8
8
|
import { Logger } from '../../config/logger.js';
|
|
9
9
|
import { ErrorFactory } from '../../exceptions/ZintrustError.js';
|
|
10
10
|
import { AdaptersEnum } from '../../migrations/enum/index.js';
|
|
11
|
-
import { isRecord } from '../adapters/SqlProxyAdapterUtils.js';
|
|
11
|
+
import { isRecord, parseCustomHeadersFromEnv } from '../adapters/SqlProxyAdapterUtils.js';
|
|
12
12
|
import { createStatementId } from '../adapters/SqlProxyRegistryMode.js';
|
|
13
13
|
import { QueryBuilder } from '../QueryBuilder.js';
|
|
14
14
|
import { SchemaWriter } from '../SchemaStatemenWriter.js';
|
|
@@ -54,6 +54,7 @@ const createRemoteConfig = () => {
|
|
|
54
54
|
secret: settings.secret,
|
|
55
55
|
timeoutMs: settings.timeoutMs,
|
|
56
56
|
signaturePathPrefixToStrip: resolveSigningPrefix(settings.baseUrl),
|
|
57
|
+
customHeaders: parseCustomHeadersFromEnv('D1_REMOTE'),
|
|
57
58
|
missingUrlMessage: 'D1 remote proxy URL is missing (D1_REMOTE_URL)',
|
|
58
59
|
missingCredentialsMessage: `D1 remote signing credentials are missing (${['D1_REMOTE', 'KEY_ID'].join('_')} / ${['D1_REMOTE', 'SECRET'].join('_')}). Fallbacks: APP_NAME and ${['APP', 'KEY'].join('_')}.`,
|
|
59
60
|
messages: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RedisProxyServer.d.ts","sourceRoot":"","sources":["../../../../src/proxy/redis/RedisProxyServer.ts"],"names":[],"mappings":"AAQA,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"RedisProxyServer.d.ts","sourceRoot":"","sources":["../../../../src/proxy/redis/RedisProxyServer.ts"],"names":[],"mappings":"AAQA,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,yBAAyB,CAAC;AAiBjC,KAAK,cAAc,GAAG,kBAAkB,GACtC,OAAO,CAAC;IACN,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC,CAAC;AA2NL,eAAO,MAAM,gBAAgB;sBACJ,cAAc,GAAQ,OAAO,CAAC,IAAI,CAAC;EAqB1D,CAAC;AAEH,eAAe,gBAAgB,CAAC"}
|
|
@@ -2,13 +2,10 @@ import { SystemTraceBridge } from '../../trace/SystemTraceBridge.js';
|
|
|
2
2
|
import { Env } from '../../config/env.js';
|
|
3
3
|
import { Logger } from '../../config/logger.js';
|
|
4
4
|
import { ErrorFactory } from '../../exceptions/ZintrustError.js';
|
|
5
|
-
import {
|
|
5
|
+
import { ErrorHandler } from '../ErrorHandler.js';
|
|
6
6
|
import { createProxyServer } from '../ProxyServer.js';
|
|
7
7
|
import { resolveBaseConfig, resolveBaseSigningConfig, verifyRequestSignature, } from '../ProxyServerUtils.js';
|
|
8
8
|
import { RequestValidator } from '../RequestValidator.js';
|
|
9
|
-
import { createQueueMonitorContext, dispatchServiceCommand, } from '../redis/RedisProxyActions.js';
|
|
10
|
-
const scriptCache = new Map();
|
|
11
|
-
let scriptCacheClient = null;
|
|
12
9
|
const resolveRedisConfig = (overrides = {}) => {
|
|
13
10
|
const host = overrides.redisHost ?? Env.get('REDIS_PROXY_TARGET_HOST', Env.get('REDIS_HOST', '127.0.0.1'));
|
|
14
11
|
const port = overrides.redisPort ?? Env.getInt('REDIS_PROXY_TARGET_PORT', Env.getInt('REDIS_PORT', 6379));
|
|
@@ -34,8 +31,17 @@ const resolveConfig = (overrides = {}) => {
|
|
|
34
31
|
},
|
|
35
32
|
};
|
|
36
33
|
};
|
|
34
|
+
const validateCommandPayload = (payload) => {
|
|
35
|
+
const command = payload['command'];
|
|
36
|
+
const args = Array.isArray(payload['args']) ? payload['args'] : [];
|
|
37
|
+
if (typeof command !== 'string' || command.trim() === '') {
|
|
38
|
+
return { valid: false, error: { code: 'VALIDATION_ERROR', message: 'command is required' } };
|
|
39
|
+
}
|
|
40
|
+
return { valid: true, command, args };
|
|
41
|
+
};
|
|
37
42
|
const getRedisModule = async () => {
|
|
38
|
-
|
|
43
|
+
const mod = await import('ioredis');
|
|
44
|
+
return mod;
|
|
39
45
|
};
|
|
40
46
|
const createClient = async (config) => {
|
|
41
47
|
const module = (await getRedisModule());
|
|
@@ -84,258 +90,91 @@ const createClient = async (config) => {
|
|
|
84
90
|
}
|
|
85
91
|
return client;
|
|
86
92
|
};
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const validateCommandPayload = (payload) => {
|
|
94
|
-
const requestId = typeof payload['requestId'] === 'string' && payload['requestId'].trim() !== ''
|
|
95
|
-
? payload['requestId'].trim()
|
|
96
|
-
: 'unknown';
|
|
97
|
-
const service = resolveProxyRpcService(typeof payload['service'] === 'string' ? payload['service'] : undefined);
|
|
98
|
-
let actionValue;
|
|
99
|
-
if (typeof payload['action'] === 'string') {
|
|
100
|
-
actionValue = payload['action'];
|
|
101
|
-
}
|
|
102
|
-
else if (typeof payload['command'] === 'string') {
|
|
103
|
-
actionValue = payload['command'];
|
|
104
|
-
}
|
|
105
|
-
const normalizedPayload = normalizeRpcPayload(payload['payload'] ?? undefined);
|
|
106
|
-
if (typeof actionValue !== 'string' || actionValue.trim() === '') {
|
|
107
|
-
return {
|
|
108
|
-
valid: false,
|
|
109
|
-
requestId,
|
|
110
|
-
service,
|
|
111
|
-
payload: normalizedPayload,
|
|
112
|
-
error: { code: 'VALIDATION_ERROR', message: 'action is required' },
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
return {
|
|
116
|
-
valid: true,
|
|
117
|
-
requestId,
|
|
118
|
-
service,
|
|
119
|
-
action: actionValue.trim(),
|
|
120
|
-
payload: normalizedPayload,
|
|
121
|
-
};
|
|
122
|
-
};
|
|
123
|
-
const handleScriptCommand = async (args, config) => {
|
|
124
|
-
const subCommand = String(args[0]).toLowerCase();
|
|
125
|
-
Logger.info('[RedisProxyServer] SCRIPT command received', {
|
|
126
|
-
subCommand,
|
|
127
|
-
argsCount: args.length,
|
|
128
|
-
});
|
|
129
|
-
if (subCommand !== 'load' || args.length <= 1) {
|
|
130
|
-
return { status: 200, body: { result: null } };
|
|
131
|
-
}
|
|
132
|
-
const script = String(args[1]);
|
|
133
|
-
Logger.info('[RedisProxyServer] Loading script into Redis', { scriptLength: script.length });
|
|
134
|
-
const cacheClient = await getScriptCacheClient(config);
|
|
135
|
-
const sha = await cacheClient.call('SCRIPT', 'LOAD', script);
|
|
136
|
-
if (typeof sha === 'string') {
|
|
137
|
-
scriptCache.set(sha, script);
|
|
138
|
-
Logger.info('[RedisProxyServer] Script loaded into cache', {
|
|
139
|
-
sha,
|
|
140
|
-
cacheSize: scriptCache.size,
|
|
141
|
-
});
|
|
93
|
+
const executeCommand = async (client, command, args) => {
|
|
94
|
+
const trimmed = command.trim();
|
|
95
|
+
const lower = trimmed.toLowerCase();
|
|
96
|
+
const candidate = client[lower];
|
|
97
|
+
if (typeof candidate === 'function') {
|
|
98
|
+
return candidate.apply(client, args);
|
|
142
99
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
sha,
|
|
146
|
-
type: typeof sha,
|
|
147
|
-
});
|
|
100
|
+
if (typeof client.call === 'function') {
|
|
101
|
+
return client.call(trimmed, ...args);
|
|
148
102
|
}
|
|
149
|
-
|
|
103
|
+
throw ErrorFactory.createValidationError(`Unsupported Redis command: ${trimmed}`);
|
|
150
104
|
};
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
});
|
|
161
|
-
if (scriptCache.has(sha)) {
|
|
162
|
-
Logger.info('[RedisProxyServer] Using cached script for EVALSHA', { sha });
|
|
105
|
+
const createBackend = (config) => ({
|
|
106
|
+
name: 'redis',
|
|
107
|
+
handle: async (request) => {
|
|
108
|
+
const methodError = RequestValidator.requirePost(request.method);
|
|
109
|
+
if (methodError) {
|
|
110
|
+
return {
|
|
111
|
+
status: 405,
|
|
112
|
+
body: { code: methodError.code, message: methodError.message },
|
|
113
|
+
};
|
|
163
114
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
sha,
|
|
167
|
-
availableScripts: Array.from(scriptCache.keys()).slice(0, 5),
|
|
168
|
-
});
|
|
115
|
+
if (request.path !== '/zin/redis/command') {
|
|
116
|
+
return { status: 404, body: { code: 'NOT_FOUND', message: 'Unknown endpoint' } };
|
|
169
117
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
status: 200,
|
|
175
|
-
body: {
|
|
176
|
-
result: await directCandidate.apply(client, args),
|
|
177
|
-
},
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
const lowerCandidate = client[lower];
|
|
181
|
-
if (typeof lowerCandidate === 'function') {
|
|
182
|
-
return {
|
|
183
|
-
status: 200,
|
|
184
|
-
body: {
|
|
185
|
-
result: await lowerCandidate.apply(client, args),
|
|
186
|
-
},
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
if (typeof client.call === 'function') {
|
|
190
|
-
return { status: 200, body: { result: await client.call(command, ...args) } };
|
|
191
|
-
}
|
|
192
|
-
throw ErrorFactory.createValidationError(`Unsupported Redis command: ${action}`);
|
|
193
|
-
};
|
|
194
|
-
const handleServiceRpc = async (client, validated, queueMonitor) => {
|
|
195
|
-
const startedAt = Date.now();
|
|
196
|
-
try {
|
|
197
|
-
const result = await dispatchServiceCommand(validated.service, validated.action ?? '', validated.payload, queueMonitor);
|
|
198
|
-
SystemTraceBridge.emitRedis(`${validated.service}:${validated.action ?? 'unknown'}`, Date.now() - startedAt);
|
|
199
|
-
return { status: 200, body: { result } };
|
|
200
|
-
}
|
|
201
|
-
catch (error) {
|
|
202
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
203
|
-
if (message.includes('Unsupported worker action:') ||
|
|
204
|
-
message.includes('Unsupported queue-monitor action:')) {
|
|
205
|
-
const parsedArgs = parseRedisCommandArgs(validated.payload);
|
|
206
|
-
return handleStandardRedisCommand(client, validated.action ?? '', parsedArgs);
|
|
118
|
+
const parsed = RequestValidator.parseJson(request.body);
|
|
119
|
+
if (!parsed.ok) {
|
|
120
|
+
return { status: 400, body: { code: parsed.error.code, message: parsed.error.message } };
|
|
207
121
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const methodError = RequestValidator.requirePost(request.method);
|
|
218
|
-
if (methodError) {
|
|
219
|
-
return {
|
|
220
|
-
status: 405,
|
|
221
|
-
body: { code: methodError.code, message: methodError.message },
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
if (request.path !== '/zin/redis/command') {
|
|
225
|
-
return { status: 404, body: { code: 'NOT_FOUND', message: 'Unknown endpoint' } };
|
|
226
|
-
}
|
|
227
|
-
const parsed = RequestValidator.parseJson(request.body);
|
|
228
|
-
if (!parsed.ok) {
|
|
229
|
-
return { status: 400, body: { code: parsed.error.code, message: parsed.error.message } };
|
|
230
|
-
}
|
|
231
|
-
const validated = validateCommandPayload(parsed.value);
|
|
232
|
-
if (!validated.valid) {
|
|
233
|
-
return {
|
|
234
|
-
status: 400,
|
|
235
|
-
body: {
|
|
236
|
-
code: validated.error?.code ?? 'VALIDATION_ERROR',
|
|
237
|
-
message: validated.error?.message ?? 'Invalid request',
|
|
238
|
-
},
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
const client = await createClient(config);
|
|
242
|
-
if (validated.service === 'worker' || validated.service === 'queue-monitor') {
|
|
243
|
-
return handleServiceRpc(client, validated, queueMonitor);
|
|
244
|
-
}
|
|
245
|
-
try {
|
|
246
|
-
const parsedArgs = parseRedisCommandArgs(validated.payload);
|
|
247
|
-
if (validated.action?.toLowerCase() === 'script') {
|
|
248
|
-
return await handleScriptCommand(parsedArgs, config);
|
|
122
|
+
const validated = validateCommandPayload(parsed.value);
|
|
123
|
+
if (!validated.valid) {
|
|
124
|
+
return {
|
|
125
|
+
status: 400,
|
|
126
|
+
body: {
|
|
127
|
+
code: validated.error?.code ?? 'VALIDATION_ERROR',
|
|
128
|
+
message: validated.error?.message ?? 'Invalid request',
|
|
129
|
+
},
|
|
130
|
+
};
|
|
249
131
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
132
|
+
const command = validated.command ?? '';
|
|
133
|
+
if (command.trim() === '') {
|
|
134
|
+
return {
|
|
135
|
+
status: 400,
|
|
136
|
+
body: { code: 'VALIDATION_ERROR', message: 'command is required' },
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const client = await createClient(config);
|
|
141
|
+
try {
|
|
142
|
+
const startedAt = Date.now();
|
|
143
|
+
const result = await executeCommand(client, command, validated.args ?? []);
|
|
144
|
+
SystemTraceBridge.emitRedis(command, Date.now() - startedAt);
|
|
145
|
+
return { status: 200, body: { result } };
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
await client.quit();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
return ErrorHandler.toProxyError(500, 'REDIS_PROXY_ERROR', String(error));
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
health: async () => {
|
|
259
156
|
try {
|
|
157
|
+
const client = await createClient(config);
|
|
260
158
|
const pingFn = client.ping;
|
|
261
159
|
if (typeof pingFn === 'function') {
|
|
262
160
|
await pingFn.apply(client);
|
|
263
161
|
}
|
|
264
162
|
else {
|
|
265
|
-
await
|
|
163
|
+
await executeCommand(client, 'PING', []);
|
|
266
164
|
}
|
|
165
|
+
await client.quit();
|
|
267
166
|
return { status: 200, body: { status: 'healthy' } };
|
|
268
167
|
}
|
|
269
|
-
|
|
270
|
-
|
|
168
|
+
catch (error) {
|
|
169
|
+
Logger.warn('[RedisProxyServer] health check failed', error);
|
|
170
|
+
return { status: 503, body: { status: 'unhealthy', error: String(error) } };
|
|
271
171
|
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
Logger.warn('[RedisProxyServer] health check failed', error);
|
|
275
|
-
return { status: 503, body: { status: 'unhealthy', error: String(error) } };
|
|
276
|
-
}
|
|
277
|
-
};
|
|
278
|
-
const createBackend = async (config) => {
|
|
279
|
-
const queueMonitor = await createQueueMonitorContext(config.redis);
|
|
280
|
-
return {
|
|
281
|
-
name: 'redis',
|
|
282
|
-
handle: async (request) => handleRedisRequest(request, config, queueMonitor),
|
|
283
|
-
health: async () => handleRedisHealth(config),
|
|
284
|
-
shutdown: async () => {
|
|
285
|
-
await Promise.all([queueMonitor.driver.close(), queueMonitor.metrics.close()]);
|
|
286
|
-
},
|
|
287
|
-
};
|
|
288
|
-
};
|
|
289
|
-
const getScriptCacheClient = async (config) => {
|
|
290
|
-
if (scriptCacheClient !== null && scriptCacheClient.status === 'ready') {
|
|
291
|
-
return scriptCacheClient;
|
|
292
|
-
}
|
|
293
|
-
const module = (await getRedisModule());
|
|
294
|
-
const moduleDefault = module['default'];
|
|
295
|
-
const candidates = [
|
|
296
|
-
module['Redis'],
|
|
297
|
-
module['default'],
|
|
298
|
-
moduleDefault?.['Redis'],
|
|
299
|
-
moduleDefault?.['default'],
|
|
300
|
-
module,
|
|
301
|
-
];
|
|
302
|
-
const RedisCtor = candidates.find((candidate) => typeof candidate === 'function');
|
|
303
|
-
if (typeof RedisCtor !== 'function') {
|
|
304
|
-
throw ErrorFactory.createConfigError("Redis proxy could not resolve a Redis constructor from 'ioredis'.");
|
|
305
|
-
}
|
|
306
|
-
scriptCacheClient = new RedisCtor({
|
|
307
|
-
host: config.redis.host,
|
|
308
|
-
port: config.redis.port,
|
|
309
|
-
password: config.redis.password,
|
|
310
|
-
db: config.redis.db,
|
|
311
|
-
maxRetriesPerRequest: 3,
|
|
312
|
-
enableOfflineQueue: true,
|
|
313
|
-
lazyConnect: false,
|
|
314
|
-
retryStrategy: (times) => {
|
|
315
|
-
if (times > 3)
|
|
316
|
-
return null;
|
|
317
|
-
return Math.min(times * 100, 1000);
|
|
318
|
-
},
|
|
319
|
-
});
|
|
320
|
-
if (typeof scriptCacheClient.connect === 'function') {
|
|
321
|
-
await scriptCacheClient.connect();
|
|
322
|
-
}
|
|
323
|
-
scriptCacheClient.on('error', (error) => {
|
|
324
|
-
Logger.warn('[RedisProxyServer] script cache client error', error);
|
|
325
|
-
});
|
|
326
|
-
return scriptCacheClient;
|
|
327
|
-
};
|
|
172
|
+
},
|
|
173
|
+
});
|
|
328
174
|
export const RedisProxyServer = Object.freeze({
|
|
329
175
|
async start(overrides = {}) {
|
|
330
176
|
const config = resolveConfig(overrides);
|
|
331
|
-
const backend =
|
|
332
|
-
Logger.info('[RedisProxyServer] Starting Redis proxy', {
|
|
333
|
-
host: config.host,
|
|
334
|
-
port: config.port,
|
|
335
|
-
redisHost: config.redis.host,
|
|
336
|
-
redisPort: config.redis.port,
|
|
337
|
-
redisDb: config.redis.db,
|
|
338
|
-
});
|
|
177
|
+
const backend = createBackend(config);
|
|
339
178
|
const server = createProxyServer({
|
|
340
179
|
host: config.host,
|
|
341
180
|
port: config.port,
|
|
@@ -350,10 +189,7 @@ export const RedisProxyServer = Object.freeze({
|
|
|
350
189
|
},
|
|
351
190
|
});
|
|
352
191
|
await server.start();
|
|
353
|
-
Logger.info(`[redis-proxy] Listening on http://${config.host}:${config.port}
|
|
354
|
-
scriptCacheSize: scriptCache.size,
|
|
355
|
-
luaScriptSupport: 'enabled',
|
|
356
|
-
});
|
|
192
|
+
Logger.info(`[redis-proxy] Listening on http://${config.host}:${config.port}`);
|
|
357
193
|
},
|
|
358
194
|
});
|
|
359
195
|
export default RedisProxyServer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Cloudflare.d.ts","sourceRoot":"","sources":["../../../../../src/tools/mail/drivers/Cloudflare.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Cloudflare.d.ts","sourceRoot":"","sources":["../../../../../src/tools/mail/drivers/Cloudflare.ts"],"names":[],"mappings":"AAMA,OAAO,EAAyC,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAElG,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE,IAAI,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAgJF,eAAO,MAAM,gBAAgB;iBAEjB,oBAAoB,WACnB,WAAW,GACnB,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;EA8B/C,CAAC;AAEH,eAAe,gBAAgB,CAAC"}
|
|
@@ -2,6 +2,7 @@ import { RemoteSignedJson } from '../../../common/RemoteSignedJson.js';
|
|
|
2
2
|
import { Cloudflare } from '../../../config/cloudflare.js';
|
|
3
3
|
import { Env } from '../../../config/env.js';
|
|
4
4
|
import { ErrorFactory } from '../../../exceptions/ZintrustError.js';
|
|
5
|
+
import { parseCustomHeadersFromEnv } from '../../../orm/adapters/SqlProxyAdapterUtils.js';
|
|
5
6
|
import { buildRfc2822Message } from '../../mail/MailMessage.js';
|
|
6
7
|
const MAIL_PROXY_KEY_ID_ENV = 'MAIL_CLOUDFLARE_PROXY_KEY_ID';
|
|
7
8
|
const MAIL_PROXY_SECRET_ENV = ['MAIL_CLOUDFLARE_PROXY', 'SECRET'].join('_');
|
|
@@ -33,6 +34,7 @@ const createRemoteConfig = () => {
|
|
|
33
34
|
secret: Env.get('MAIL_CLOUDFLARE_PROXY_SECRET', ''),
|
|
34
35
|
timeoutMs,
|
|
35
36
|
signaturePathPrefixToStrip: resolveSigningPrefix(baseUrl),
|
|
37
|
+
customHeaders: parseCustomHeadersFromEnv('MAIL_CLOUDFLARE_PROXY'),
|
|
36
38
|
missingUrlMessage: 'Cloudflare mail proxy URL is missing (MAIL_CLOUDFLARE_PROXY_URL)',
|
|
37
39
|
missingCredentialsMessage: createMissingMailProxyCredentialsMessage(),
|
|
38
40
|
messages: {
|
|
@@ -1,43 +1,26 @@
|
|
|
1
1
|
import type { RedisConfig } from '../../config/type';
|
|
2
|
-
export type RedisTransportMode = 'direct' | 'proxy';
|
|
2
|
+
export type RedisTransportMode = 'direct' | 'proxy' | 'rpc';
|
|
3
3
|
export type RedisTransportOptions = Readonly<{
|
|
4
4
|
subsystem?: string;
|
|
5
5
|
requireDirect?: boolean;
|
|
6
|
-
requireDirectForScripts?: boolean;
|
|
7
6
|
}>;
|
|
8
7
|
type RedisProxyConnection = {
|
|
9
8
|
[x: string]: unknown;
|
|
10
|
-
__bullmq_iredis?: true;
|
|
11
|
-
isCluster?: false;
|
|
12
|
-
options?: Readonly<Record<string, unknown>>;
|
|
13
9
|
status: 'ready';
|
|
14
10
|
connect: () => Promise<void>;
|
|
15
11
|
quit: () => Promise<'OK'>;
|
|
16
12
|
disconnect: () => void;
|
|
17
|
-
duplicate: () => RedisProxyConnection;
|
|
18
|
-
defineCommand: (name: string, definition: {
|
|
19
|
-
numberOfKeys: number;
|
|
20
|
-
lua: string;
|
|
21
|
-
}) => void;
|
|
22
|
-
runCommand: (name: string, args: unknown[]) => Promise<unknown>;
|
|
23
13
|
on: (event: string, handler: (...args: unknown[]) => void) => RedisProxyConnection;
|
|
24
14
|
once: (event: string, handler: (...args: unknown[]) => void) => RedisProxyConnection;
|
|
25
15
|
off: (event: string, handler: (...args: unknown[]) => void) => RedisProxyConnection;
|
|
26
16
|
removeListener: (event: string, handler: (...args: unknown[]) => void) => RedisProxyConnection;
|
|
27
|
-
setMaxListeners: (count: number) => RedisProxyConnection;
|
|
28
|
-
getMaxListeners: () => number;
|
|
29
17
|
call: (command: string, ...args: unknown[]) => Promise<unknown>;
|
|
30
|
-
scripts: {
|
|
31
|
-
[key: string]: (...args: unknown[]) => Promise<unknown>;
|
|
32
|
-
};
|
|
33
18
|
pipeline: () => {
|
|
34
19
|
exec: () => Promise<Array<[Error | null, unknown]>>;
|
|
35
|
-
runCommand?: (name: string, args: unknown[]) => unknown;
|
|
36
20
|
[key: string]: unknown;
|
|
37
21
|
};
|
|
38
22
|
multi: () => {
|
|
39
23
|
exec: () => Promise<Array<[Error | null, unknown]>>;
|
|
40
|
-
runCommand?: (name: string, args: unknown[]) => unknown;
|
|
41
24
|
[key: string]: unknown;
|
|
42
25
|
};
|
|
43
26
|
scanStream: (options?: {
|
|
@@ -49,16 +32,8 @@ type RedisProxyConnection = {
|
|
|
49
32
|
off: (event: string, handler: (...args: unknown[]) => void) => unknown;
|
|
50
33
|
};
|
|
51
34
|
};
|
|
52
|
-
type ScriptDefinition = Readonly<{
|
|
53
|
-
numberOfKeys: number;
|
|
54
|
-
lua: string;
|
|
55
|
-
}>;
|
|
56
|
-
type ProxyScriptRegistry = {
|
|
57
|
-
definitions: Map<string, ScriptDefinition>;
|
|
58
|
-
shaByCommand: Map<string, string>;
|
|
59
|
-
};
|
|
60
35
|
export declare const resolveRedisTransportMode: () => RedisTransportMode;
|
|
61
|
-
export declare const createRedisProxyConnection: (config: RedisConfig, options?: RedisTransportOptions
|
|
36
|
+
export declare const createRedisProxyConnection: (config: RedisConfig, options?: RedisTransportOptions) => RedisProxyConnection;
|
|
62
37
|
export declare const ensureRedisTransportMode: (config: RedisConfig, options?: RedisTransportOptions) => RedisTransportMode;
|
|
63
38
|
export {};
|
|
64
39
|
//# sourceMappingURL=RedisTransport.d.ts.map
|
|
@@ -1 +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;
|
|
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;AAKhD,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,OAAO,GAAG,KAAK,CAAC;AAE5D,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC,CAAC;AAgBH,KAAK,oBAAoB,GAAG;IAC1B,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACrB,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,KAAK,EAAE,MAAM;QACX,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;AA6YF,eAAO,MAAM,yBAAyB,QAAO,kBAG5C,CAAC;AAEF,eAAO,MAAM,0BAA0B,GACrC,QAAQ,WAAW,EACnB,UAAU,qBAAqB,KAC9B,oBAwDF,CAAC;AAEF,eAAO,MAAM,wBAAwB,GACnC,QAAQ,WAAW,EACnB,UAAU,qBAAqB,KAC9B,kBAaF,CAAC"}
|