@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.
Files changed (49) hide show
  1. package/bin/zintrust-main.d.ts.map +1 -1
  2. package/bin/zintrust-main.js +11 -1
  3. package/package.json +2 -2
  4. package/src/cache/Cache.d.ts +6 -0
  5. package/src/cache/Cache.d.ts.map +1 -1
  6. package/src/cache/Cache.js +50 -0
  7. package/src/cache/CacheDriver.d.ts +8 -0
  8. package/src/cache/CacheDriver.d.ts.map +1 -1
  9. package/src/cache/drivers/RedisDriver.d.ts.map +1 -1
  10. package/src/cache/drivers/RedisDriver.js +64 -2
  11. package/src/cli/CLI.d.ts.map +1 -1
  12. package/src/cli/CLI.js +2 -0
  13. package/src/cli/commands/DoctorArchitectureCommand.d.ts.map +1 -1
  14. package/src/cli/commands/DoctorArchitectureCommand.js +4 -2
  15. package/src/cli/commands/RedisRpcCommand.d.ts +6 -0
  16. package/src/cli/commands/RedisRpcCommand.d.ts.map +1 -0
  17. package/src/cli/commands/RedisRpcCommand.js +96 -0
  18. package/src/cli/commands/index.d.ts +1 -0
  19. package/src/cli/commands/index.d.ts.map +1 -1
  20. package/src/cli/commands/index.js +1 -0
  21. package/src/cli/scaffolding/env.d.ts.map +1 -1
  22. package/src/cli/scaffolding/env.js +9 -0
  23. package/src/cli/services/WorkerStartupDiagnostics.d.ts.map +1 -1
  24. package/src/cli/services/WorkerStartupDiagnostics.js +4 -2
  25. package/src/config/app.d.ts.map +1 -1
  26. package/src/config/app.js +3 -0
  27. package/src/config/env.d.ts +7 -0
  28. package/src/config/env.d.ts.map +1 -1
  29. package/src/config/env.js +7 -0
  30. package/src/config/queue.d.ts.map +1 -1
  31. package/src/config/queue.js +4 -42
  32. package/src/config/workers.d.ts.map +1 -1
  33. package/src/config/workers.js +12 -6
  34. package/src/index.js +3 -3
  35. package/src/orm/adapters/D1RemoteAdapter.d.ts.map +1 -1
  36. package/src/orm/adapters/D1RemoteAdapter.js +2 -1
  37. package/src/proxy/redis/RedisProxyServer.d.ts.map +1 -1
  38. package/src/proxy/redis/RedisProxyServer.js +77 -241
  39. package/src/tools/mail/drivers/Cloudflare.d.ts.map +1 -1
  40. package/src/tools/mail/drivers/Cloudflare.js +2 -0
  41. package/src/tools/redis/RedisTransport.d.ts +2 -27
  42. package/src/tools/redis/RedisTransport.d.ts.map +1 -1
  43. package/src/tools/redis/RedisTransport.js +123 -155
  44. package/src/zintrust.plugins.d.ts +6 -3
  45. package/src/zintrust.plugins.d.ts.map +1 -1
  46. package/src/zintrust.plugins.js +6 -3
  47. package/src/proxy/redis/RedisProxyActions.d.ts +0 -15
  48. package/src/proxy/redis/RedisProxyActions.d.ts.map +0 -1
  49. package/src/proxy/redis/RedisProxyActions.js +0 -124
@@ -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: proxyConfig?.host ??
111
- readWorkersFallbackString('WORKERS_REDIS_HOST', 'REDIS_HOST', 'localhost'),
112
- port: proxyConfig?.port ?? readWorkersFallbackInt('WORKERS_REDIS_PORT', 'REDIS_PORT', 6379),
113
- password: proxyConfig?.password ??
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;AA6XnC,eAAO,MAAM,qBAAqB,GAChC,QAAQ,WAAW,EACnB,mBAAc,EACd,UAAU,qBAAqB,KAC9B,OAkDF,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"}
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"}
@@ -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 || (Env.get('REDIS_PROXY_URL', '') || '').trim() !== '';
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
- export const createRedisConnection = (config, maxRetries = 3, options) => {
289
- const mode = ensureRedisTransportMode(config, options);
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.0
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-30T23:27:53.523Z
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-30T23:27:53.490Z'; // Replaced during build
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;AA0T1F,eAAO,MAAM,eAAe;oBACV,cAAc,GAAG,gBAAgB;EAgBjD,CAAC;AAEH,eAAe,eAAe,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;AAsBjC,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;AAybL,eAAO,MAAM,gBAAgB;sBACJ,cAAc,GAAQ,OAAO,CAAC,IAAI,CAAC;EAgC1D,CAAC;AAEH,eAAe,gBAAgB,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 { resolveProxyRpcService } from '../CloudflareProxyShared.js';
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
- return import('ioredis');
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 normalizeRpcPayload = (payload) => {
88
- return payload ?? {};
89
- };
90
- const parseRedisCommandArgs = (payload) => {
91
- return Array.isArray(payload['args']) ? payload['args'] : [];
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
- else {
144
- Logger.warn('[RedisProxyServer] Script LOAD returned non-string SHA', {
145
- sha,
146
- type: typeof sha,
147
- });
100
+ if (typeof client.call === 'function') {
101
+ return client.call(trimmed, ...args);
148
102
  }
149
- return { status: 200, body: { result: sha } };
103
+ throw ErrorFactory.createValidationError(`Unsupported Redis command: ${trimmed}`);
150
104
  };
151
- const handleStandardRedisCommand = async (client, action, args) => {
152
- const command = action.trim();
153
- const lower = command.toLowerCase();
154
- if (lower === 'evalsha' && args.length > 0) {
155
- const sha = String(args[0]);
156
- Logger.info('[RedisProxyServer] EVALSHA command received', {
157
- sha,
158
- cacheSize: scriptCache.size,
159
- hasScript: scriptCache.has(sha),
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
- else {
165
- Logger.warn('[RedisProxyServer] Script not in cache, will attempt direct EVALSHA', {
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
- const directCandidate = client[command];
172
- if (typeof directCandidate === 'function') {
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
- throw error;
209
- }
210
- };
211
- const handleRedisRequest = async (request, config, queueMonitor) => {
212
- Logger.info('[RedisProxyServer] Handling request', {
213
- method: request.method,
214
- path: request.path,
215
- scriptCacheSize: scriptCache.size,
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
- return await handleStandardRedisCommand(client, validated.action ?? '', parsedArgs);
251
- }
252
- finally {
253
- await client.quit();
254
- }
255
- };
256
- const handleRedisHealth = async (config) => {
257
- try {
258
- const client = await createClient(config);
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 handleStandardRedisCommand(client, 'PING', []);
163
+ await executeCommand(client, 'PING', []);
266
164
  }
165
+ await client.quit();
267
166
  return { status: 200, body: { status: 'healthy' } };
268
167
  }
269
- finally {
270
- await client.quit();
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
- catch (error) {
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 = await createBackend(config);
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":"AAKA,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;AA+IF,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"}
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, registry?: ProxyScriptRegistry) => RedisProxyConnection;
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":"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"}
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"}