@zintrust/core 2.2.4 → 2.2.6

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zintrust/core",
3
- "version": "2.2.4",
3
+ "version": "2.2.6",
4
4
  "description": "Production-grade TypeScript backend framework for JavaScript",
5
5
  "homepage": "https://zintrust.com",
6
6
  "repository": {
package/src/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
- * @zintrust/core v2.2.4
2
+ * @zintrust/core v2.2.6
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-28T17:28:12.580Z
8
+ * Built: 2026-05-28T20:15:36.566Z
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-28T17:28:12.543Z'; // Replaced during build
24
+ export const ZINTRUST_BUILD_DATE = '2026-05-28T20:15:36.526Z'; // 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,5 +1,5 @@
1
- import { type QueueDriver } from '@zintrust/queue-monitor/driver';
2
- import { type Metrics } from '@zintrust/queue-monitor/metrics';
1
+ import type { QueueDriver } from '@zintrust/queue-monitor/driver';
2
+ import type { Metrics } from '@zintrust/queue-monitor/metrics';
3
3
  export type QueueMonitorContext = Readonly<{
4
4
  driver: QueueDriver;
5
5
  metrics: Metrics;
@@ -10,6 +10,6 @@ export type RedisProxyRedisConfig = Readonly<{
10
10
  password: string;
11
11
  db: number;
12
12
  }>;
13
- export declare const createQueueMonitorContext: (redis: RedisProxyRedisConfig) => QueueMonitorContext;
13
+ export declare const createQueueMonitorContext: (redis: RedisProxyRedisConfig) => Promise<QueueMonitorContext>;
14
14
  export declare const dispatchServiceCommand: (service: string, action: string, payload: Record<string, unknown>, queueMonitor: QueueMonitorContext) => Promise<unknown>;
15
15
  //# sourceMappingURL=RedisProxyActions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RedisProxyActions.d.ts","sourceRoot":"","sources":["../../../../src/proxy/redis/RedisProxyActions.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,gCAAgC,CAAC;AACtF,OAAO,EAAiB,KAAK,OAAO,EAAE,MAAM,iCAAiC,CAAC;AAa9E,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC,CAAC;AAEH,eAAO,MAAM,yBAAyB,GAAI,OAAO,qBAAqB,KAAG,mBAKxE,CAAC;AAyHF,eAAO,MAAM,sBAAsB,GACjC,SAAS,MAAM,EACf,QAAQ,MAAM,EACd,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,cAAc,mBAAmB,KAChC,OAAO,CAAC,OAAO,CAUjB,CAAC"}
1
+ {"version":3,"file":"RedisProxyActions.d.ts","sourceRoot":"","sources":["../../../../src/proxy/redis/RedisProxyActions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iCAAiC,CAAC;AAG/D,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC,CAAC;AAEH,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC,CAAC;AA4BH,eAAO,MAAM,yBAAyB,GACpC,OAAO,qBAAqB,KAC3B,OAAO,CAAC,mBAAmB,CAU7B,CAAC;AAuIF,eAAO,MAAM,sBAAsB,GACjC,SAAS,MAAM,EACf,QAAQ,MAAM,EACd,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,cAAc,mBAAmB,KAChC,OAAO,CAAC,OAAO,CAUjB,CAAC"}
@@ -1,13 +1,27 @@
1
1
  import { ErrorFactory } from '../../exceptions/ZintrustError.js';
2
- import { createBullMQDriver } from '@zintrust/queue-monitor/driver';
3
- import { createMetrics } from '@zintrust/queue-monitor/metrics';
4
- import { getRecentJobsForQueue, getRecentJobsForSelection, } from '../../../packages/queue-monitor/src/QueueMonitoringService.js';
5
- import { getWorkerDetails, getWorkers, toggleAutoStart, } from '../../../packages/workers/src/dashboard/workers-api.js';
6
- import { WorkerFactory } from '../../../packages/workers/src/WorkerFactory.js';
7
- export const createQueueMonitorContext = (redis) => {
2
+ const loadQueueMonitorDriverModule = async () => {
3
+ return import('@zintrust/queue-monitor/driver');
4
+ };
5
+ const loadQueueMonitorMetricsModule = async () => {
6
+ return import('@zintrust/queue-monitor/metrics');
7
+ };
8
+ const loadQueueMonitoringServiceModule = async () => {
9
+ return import('../../../packages/queue-monitor/src/QueueMonitoringService.js');
10
+ };
11
+ const loadWorkersApiModule = async () => {
12
+ return import('../../../packages/workers/src/dashboard/workers-api.js');
13
+ };
14
+ const loadWorkerFactoryModule = async () => {
15
+ return import('../../../packages/workers/src/WorkerFactory.js');
16
+ };
17
+ export const createQueueMonitorContext = async (redis) => {
18
+ const [driverModule, metricsModule] = await Promise.all([
19
+ loadQueueMonitorDriverModule(),
20
+ loadQueueMonitorMetricsModule(),
21
+ ]);
8
22
  return {
9
- driver: createBullMQDriver(redis),
10
- metrics: createMetrics(redis),
23
+ driver: driverModule.createBullMQDriver(redis),
24
+ metrics: metricsModule.createMetrics(redis),
11
25
  };
12
26
  };
13
27
  const readString = (value) => {
@@ -38,16 +52,20 @@ const resolveWorkerPersistenceOverride = (payload) => {
38
52
  };
39
53
  const workerActionHandlers = {
40
54
  getWorkers: async (payload) => {
41
- return getWorkers(payload);
55
+ const module = await loadWorkersApiModule();
56
+ return module.getWorkers(payload);
42
57
  },
43
58
  getWorkerDetails: async (payload) => {
44
- return getWorkerDetails(readString(payload['name'] ?? payload['workerName']) ?? '', readString(payload['driver']));
59
+ const module = await loadWorkersApiModule();
60
+ return module.getWorkerDetails(readString(payload['name'] ?? payload['workerName']) ?? '', readString(payload['driver']));
45
61
  },
46
62
  toggleAutoStart: async (payload) => {
47
- return toggleAutoStart(readString(payload['name'] ?? payload['workerName']) ?? '', readBoolean(payload['enabled']));
63
+ const module = await loadWorkersApiModule();
64
+ return module.toggleAutoStart(readString(payload['name'] ?? payload['workerName']) ?? '', readBoolean(payload['enabled']));
48
65
  },
49
66
  listPersistedRecords: async (payload) => {
50
- return WorkerFactory.listPersistedRecords(resolveWorkerPersistenceOverride(payload), {
67
+ const module = await loadWorkerFactoryModule();
68
+ return module.WorkerFactory.listPersistedRecords(resolveWorkerPersistenceOverride(payload), {
51
69
  offset: readNumber(payload['offset']),
52
70
  limit: readNumber(payload['limit']),
53
71
  search: readString(payload['search']),
@@ -55,24 +73,30 @@ const workerActionHandlers = {
55
73
  });
56
74
  },
57
75
  listFileBackedRecords: async () => {
58
- return WorkerFactory.listFileBackedRecords();
76
+ const module = await loadWorkerFactoryModule();
77
+ return module.WorkerFactory.listFileBackedRecords();
59
78
  },
60
79
  getPersisted: async (payload) => {
61
- return WorkerFactory.getPersisted(readString(payload['name'] ?? payload['workerName']) ?? '', resolveWorkerPersistenceOverride(payload));
80
+ const module = await loadWorkerFactoryModule();
81
+ return module.WorkerFactory.getPersisted(readString(payload['name'] ?? payload['workerName']) ?? '', resolveWorkerPersistenceOverride(payload));
62
82
  },
63
83
  getHealth: async (payload) => {
64
- return WorkerFactory.getHealth(readString(payload['name'] ?? payload['workerName']) ?? '');
84
+ const module = await loadWorkerFactoryModule();
85
+ return module.WorkerFactory.getHealth(readString(payload['name'] ?? payload['workerName']) ?? '');
65
86
  },
66
87
  getMetrics: async (payload) => {
67
- return WorkerFactory.getMetrics(readString(payload['name'] ?? payload['workerName']) ?? '');
88
+ const module = await loadWorkerFactoryModule();
89
+ return module.WorkerFactory.getMetrics(readString(payload['name'] ?? payload['workerName']) ?? '');
68
90
  },
69
91
  };
70
92
  const queueMonitorActionHandlers = {
71
93
  getRecentJobsForQueue: async (payload, queueMonitor) => {
72
- return getRecentJobsForQueue(readString(payload['queue'] ?? payload['queueName']) ?? '', queueMonitor.metrics, queueMonitor.driver);
94
+ const module = await loadQueueMonitoringServiceModule();
95
+ return module.getRecentJobsForQueue(readString(payload['queue'] ?? payload['queueName']) ?? '', queueMonitor.metrics, queueMonitor.driver);
73
96
  },
74
97
  getRecentJobsForSelection: async (payload, queueMonitor) => {
75
- return getRecentJobsForSelection(readString(payload['queue'] ?? payload['queueName']) ?? '', queueMonitor.metrics, queueMonitor.driver, readQueueNames(payload['queueNames']));
98
+ const module = await loadQueueMonitoringServiceModule();
99
+ return module.getRecentJobsForSelection(readString(payload['queue'] ?? payload['queueName']) ?? '', queueMonitor.metrics, queueMonitor.driver, readQueueNames(payload['queueNames']));
76
100
  },
77
101
  };
78
102
  const dispatchWorkerAction = async (action, payload) => {
@@ -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;AAwZL,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;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"}
@@ -149,7 +149,8 @@ const handleScriptCommand = async (args, config) => {
149
149
  return { status: 200, body: { result: sha } };
150
150
  };
151
151
  const handleStandardRedisCommand = async (client, action, args) => {
152
- const lower = action.trim().toLowerCase();
152
+ const command = action.trim();
153
+ const lower = command.toLowerCase();
153
154
  if (lower === 'evalsha' && args.length > 0) {
154
155
  const sha = String(args[0]);
155
156
  Logger.info('[RedisProxyServer] EVALSHA command received', {
@@ -167,25 +168,45 @@ const handleStandardRedisCommand = async (client, action, args) => {
167
168
  });
168
169
  }
169
170
  }
170
- const candidate = client[lower];
171
- if (typeof candidate === 'function') {
171
+ const directCandidate = client[command];
172
+ if (typeof directCandidate === 'function') {
172
173
  return {
173
174
  status: 200,
174
175
  body: {
175
- result: await candidate.apply(client, args),
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),
176
186
  },
177
187
  };
178
188
  }
179
189
  if (typeof client.call === 'function') {
180
- return { status: 200, body: { result: await client.call(action.trim(), ...args) } };
190
+ return { status: 200, body: { result: await client.call(command, ...args) } };
181
191
  }
182
192
  throw ErrorFactory.createValidationError(`Unsupported Redis command: ${action}`);
183
193
  };
184
- const handleServiceRpc = async (validated, queueMonitor) => {
194
+ const handleServiceRpc = async (client, validated, queueMonitor) => {
185
195
  const startedAt = Date.now();
186
- const result = await dispatchServiceCommand(validated.service, validated.action ?? '', validated.payload, queueMonitor);
187
- SystemTraceBridge.emitRedis(`${validated.service}:${validated.action ?? 'unknown'}`, Date.now() - startedAt);
188
- return { status: 200, body: { result } };
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);
207
+ }
208
+ throw error;
209
+ }
189
210
  };
190
211
  const handleRedisRequest = async (request, config, queueMonitor) => {
191
212
  Logger.info('[RedisProxyServer] Handling request', {
@@ -217,10 +238,10 @@ const handleRedisRequest = async (request, config, queueMonitor) => {
217
238
  },
218
239
  };
219
240
  }
241
+ const client = await createClient(config);
220
242
  if (validated.service === 'worker' || validated.service === 'queue-monitor') {
221
- return handleServiceRpc(validated, queueMonitor);
243
+ return handleServiceRpc(client, validated, queueMonitor);
222
244
  }
223
- const client = await createClient(config);
224
245
  try {
225
246
  const parsedArgs = parseRedisCommandArgs(validated.payload);
226
247
  if (validated.action?.toLowerCase() === 'script') {
@@ -254,8 +275,8 @@ const handleRedisHealth = async (config) => {
254
275
  return { status: 503, body: { status: 'unhealthy', error: String(error) } };
255
276
  }
256
277
  };
257
- const createBackend = (config) => {
258
- const queueMonitor = createQueueMonitorContext(config.redis);
278
+ const createBackend = async (config) => {
279
+ const queueMonitor = await createQueueMonitorContext(config.redis);
259
280
  return {
260
281
  name: 'redis',
261
282
  handle: async (request) => handleRedisRequest(request, config, queueMonitor),
@@ -307,7 +328,7 @@ const getScriptCacheClient = async (config) => {
307
328
  export const RedisProxyServer = Object.freeze({
308
329
  async start(overrides = {}) {
309
330
  const config = resolveConfig(overrides);
310
- const backend = createBackend(config);
331
+ const backend = await createBackend(config);
311
332
  Logger.info('[RedisProxyServer] Starting Redis proxy', {
312
333
  host: config.host,
313
334
  port: config.port,
@@ -1 +1 @@
1
- {"version":3,"file":"PluginAutoImports.d.ts","sourceRoot":"","sources":["../../../src/runtime/PluginAutoImports.ts"],"names":[],"mappings":"AAKA,OAAO,EAAmB,KAAK,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAEzF,KAAK,YAAY,GACb;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAChC;IACE,EAAE,EAAE,KAAK,CAAC;IACV,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,WAAW,GAAG,eAAe,CAAC;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AA4ON,eAAO,MAAM,iBAAiB;uCACY,uBAAuB,GAAY,OAAO,CAAC,YAAY,CAAC;IAkBhG;;;;;;OAMG;mCACkC,OAAO,CAAC,YAAY,CAAC;qCAkEnB,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC;EAavE,CAAC"}
1
+ {"version":3,"file":"PluginAutoImports.d.ts","sourceRoot":"","sources":["../../../src/runtime/PluginAutoImports.ts"],"names":[],"mappings":"AAKA,OAAO,EAAmB,KAAK,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAEzF,KAAK,YAAY,GACb;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAChC;IACE,EAAE,EAAE,KAAK,CAAC;IACV,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,WAAW,GAAG,eAAe,CAAC;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AA2RN,eAAO,MAAM,iBAAiB;uCACY,uBAAuB,GAAY,OAAO,CAAC,YAAY,CAAC;IAkBhG;;;;;;OAMG;mCACkC,OAAO,CAAC,YAAY,CAAC;qCAgFnB,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC;EAavE,CAAC"}
@@ -145,21 +145,58 @@ const importFromLocalFallback = async (specifier, fallback) => {
145
145
  return getMissingPackageStatus(fallbackError, specifier);
146
146
  }
147
147
  };
148
+ const importFromFileContents = async (filePath, specifier) => {
149
+ try {
150
+ const raw = await readFile(filePath, 'utf-8');
151
+ const fileSpecifiers = extractImportSpecifiers(raw);
152
+ if (fileSpecifiers.length === 0) {
153
+ return 'missing';
154
+ }
155
+ const summary = await importSpecifiers(fileSpecifiers.map((importSpecifier) => ({ filePath, specifier: importSpecifier })));
156
+ if (summary.loaded > 0) {
157
+ Logger.debug('[plugins] Loaded auto-import specifiers from file contents', {
158
+ specifier,
159
+ filePath,
160
+ loadedCount: summary.loaded,
161
+ });
162
+ return 'loaded';
163
+ }
164
+ if (summary.missing > 0 && summary.failed === 0) {
165
+ return 'missing';
166
+ }
167
+ return 'failed';
168
+ }
169
+ catch (error) {
170
+ return getMissingPackageStatus(error, specifier);
171
+ }
172
+ };
148
173
  const importSingleSpecifier = async (entry) => {
149
174
  const target = entry.specifier.startsWith('.')
150
175
  ? resolveRelativeSpecifier(entry)
151
176
  : entry.specifier;
152
- try {
153
- await import(target);
154
- Logger.debug('[plugins] Loaded auto-import specifier', { specifier: entry.specifier });
155
- return 'loaded';
177
+ if (target.endsWith('.ts')) {
178
+ const sourceLoad = await importFromFileContents(entry.filePath, entry.specifier);
179
+ if (sourceLoad !== 'failed') {
180
+ return sourceLoad;
181
+ }
156
182
  }
157
- catch (error) {
158
- const fallback = resolveLocalPackageSpecifier(entry.specifier);
159
- if (fallback !== null)
160
- return importFromLocalFallback(entry.specifier, fallback);
161
- return getMissingPackageStatus(error, entry.specifier);
183
+ else {
184
+ try {
185
+ await import(target);
186
+ Logger.debug('[plugins] Loaded auto-import specifier', { specifier: entry.specifier });
187
+ return 'loaded';
188
+ }
189
+ catch (error) {
190
+ const fallback = resolveLocalPackageSpecifier(entry.specifier);
191
+ if (fallback !== null)
192
+ return importFromLocalFallback(entry.specifier, fallback);
193
+ return getMissingPackageStatus(error, entry.specifier);
194
+ }
162
195
  }
196
+ const fallback = resolveLocalPackageSpecifier(entry.specifier);
197
+ if (fallback !== null)
198
+ return importFromLocalFallback(entry.specifier, fallback);
199
+ return 'failed';
163
200
  };
164
201
  const importSpecifiers = async (specifiers) => {
165
202
  // Import all specifiers in parallel
@@ -225,6 +262,18 @@ export const PluginAutoImports = Object.freeze({
225
262
  return { ok: false, reason: 'not-found' };
226
263
  }
227
264
  const tryImportCandidate = async (candidate) => {
265
+ if (candidate.endsWith('.ts')) {
266
+ const fallbackResult = await this.tryImportFromFileContents([candidate]);
267
+ if (fallbackResult.ok) {
268
+ return { ok: true, loadedPath: candidate };
269
+ }
270
+ return {
271
+ ok: false,
272
+ loadedPath: candidate,
273
+ reason: fallbackResult.reason,
274
+ errorMessage: fallbackResult.errorMessage,
275
+ };
276
+ }
228
277
  try {
229
278
  const url = pathToFileURL(candidate).href;
230
279
  await import(url);
@@ -49,8 +49,16 @@ type RedisProxyConnection = {
49
49
  off: (event: string, handler: (...args: unknown[]) => void) => unknown;
50
50
  };
51
51
  };
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
+ };
52
60
  export declare const resolveRedisTransportMode: () => RedisTransportMode;
53
- export declare const createRedisProxyConnection: (config: RedisConfig, options?: RedisTransportOptions) => RedisProxyConnection;
61
+ export declare const createRedisProxyConnection: (config: RedisConfig, options?: RedisTransportOptions, registry?: ProxyScriptRegistry) => RedisProxyConnection;
54
62
  export declare const ensureRedisTransportMode: (config: RedisConfig, options?: RedisTransportOptions) => RedisTransportMode;
55
63
  export {};
56
64
  //# 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":"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"}
@@ -62,12 +62,12 @@ const createRequestId = () => {
62
62
  return crypto.randomUUID();
63
63
  return `req_${Date.now()}_${Math.random().toString(16).slice(2)}`;
64
64
  };
65
- const resolveProxySettings = (options) => ({
65
+ const resolveProxySettings = (_options) => ({
66
66
  baseUrl: resolveProxyBaseUrl(),
67
- keyId: Env.REDIS_PROXY_KEY_ID.trim() === '' ? undefined : Env.REDIS_PROXY_KEY_ID,
68
- secret: Env.REDIS_PROXY_SECRET.trim() === '' ? undefined : Env.REDIS_PROXY_SECRET,
67
+ keyId: Env.get('REDIS_PROXY_KEY_ID').trim() === '' ? undefined : Env.get('REDIS_PROXY_KEY_ID'),
68
+ secret: Env.REDIS_PROXY_SECRET.trim() === '' ? undefined : Env.get('REDIS_PROXY_SECRET'),
69
69
  timeoutMs: Env.REDIS_PROXY_TIMEOUT_MS,
70
- service: resolveProxyRpcService(options?.subsystem),
70
+ service: resolveProxyRpcService('redis'),
71
71
  customHeaders: parseCustomHeadersFromEnv('REDIS'),
72
72
  });
73
73
  const buildHeaders = async (settings, requestUrl, body) => {
@@ -114,11 +114,34 @@ const requestProxyCommand = async (settings, action, payload) => {
114
114
  });
115
115
  if (!response.ok) {
116
116
  const text = await response.text();
117
- throw ErrorFactory.createTryCatchError(`Redis proxy request failed (${response.status})`, text);
117
+ // Don't log HTML responses (e.g., 502 Bad Gateway pages)
118
+ const isHtml = text.trim().toLowerCase().startsWith('<!doctype html') ||
119
+ text.trim().toLowerCase().startsWith('<html');
120
+ const errorMessage = isHtml ? 'Non-JSON response from proxy (proxy may be unavailable)' : text;
121
+ throw ErrorFactory.createTryCatchError(`Redis proxy request failed (${response.status})`, errorMessage);
118
122
  }
119
123
  const parsed = (await response.json());
120
124
  return parsed.result;
121
125
  };
126
+ const loadScriptDefinition = async (settings, definition) => {
127
+ const loaded = await requestProxyCommand(settings, 'SCRIPT', {
128
+ args: ['LOAD', definition.lua],
129
+ });
130
+ return loaded;
131
+ };
132
+ const getDefinedScriptSha = async (settings, registry, command) => {
133
+ const cached = registry.shaByCommand.get(command);
134
+ if (cached !== undefined) {
135
+ return cached;
136
+ }
137
+ const definition = registry.definitions.get(command);
138
+ if (definition === undefined) {
139
+ return undefined;
140
+ }
141
+ const sha = await loadScriptDefinition(settings, definition);
142
+ registry.shaByCommand.set(command, sha);
143
+ return sha;
144
+ };
122
145
  const logTransportSelection = (mode, config, options) => {
123
146
  const rawSubsystem = options?.subsystem?.trim();
124
147
  const subsystem = rawSubsystem === undefined || rawSubsystem === '' ? 'redis' : rawSubsystem;
@@ -253,6 +276,19 @@ export const resolveRedisTransportMode = () => {
253
276
  const createCommandFunction = (settings, command) => {
254
277
  return async (...args) => requestProxyCommand(settings, command, { args });
255
278
  };
279
+ const createDefinedScriptFunction = (settings, command, registry) => {
280
+ return async (...args) => {
281
+ const sha = await getDefinedScriptSha(settings, registry, command);
282
+ if (sha === undefined) {
283
+ return requestProxyCommand(settings, command, { args });
284
+ }
285
+ const definition = registry.definitions.get(command);
286
+ const numberOfKeys = definition?.numberOfKeys ?? 0;
287
+ return requestProxyCommand(settings, 'EVALSHA', {
288
+ args: [sha, numberOfKeys, ...args],
289
+ });
290
+ };
291
+ };
256
292
  const createScriptsHandler = (settings) => {
257
293
  return new Proxy({}, {
258
294
  get(_target, prop) {
@@ -262,7 +298,7 @@ const createScriptsHandler = (settings) => {
262
298
  },
263
299
  });
264
300
  };
265
- const handlePropertyAccess = (obj, prop, client, settings) => {
301
+ const handlePropertyAccess = (obj, prop, client, settings, registry) => {
266
302
  if (typeof prop !== 'string')
267
303
  return Reflect.get(obj, prop);
268
304
  if (prop === 'then')
@@ -277,12 +313,15 @@ const handlePropertyAccess = (obj, prop, client, settings) => {
277
313
  return Infinity;
278
314
  };
279
315
  }
316
+ if (registry.definitions.has(prop)) {
317
+ return createDefinedScriptFunction(settings, prop, registry);
318
+ }
280
319
  if (prop in obj) {
281
320
  return Reflect.get(obj, prop);
282
321
  }
283
322
  return createCommandFunction(settings, prop);
284
323
  };
285
- const createProxyTarget = (config, options, settings, client) => {
324
+ const createProxyTarget = (config, options, settings, client, registry) => {
286
325
  const target = {
287
326
  __bullmq_iredis: true,
288
327
  isCluster: false,
@@ -292,11 +331,14 @@ const createProxyTarget = (config, options, settings, client) => {
292
331
  // eslint-disable-next-line @typescript-eslint/require-await
293
332
  quit: async () => 'OK',
294
333
  disconnect: () => undefined,
295
- duplicate: () => createRedisProxyConnection(config, options),
334
+ duplicate: () => createRedisProxyConnection(config, options, registry),
296
335
  defineCommand: (name, definition) => {
297
- Logger.debug('[redis][proxy][bullmq] defineCommand ignored on frontend proxy', {
336
+ registry.definitions.set(name, definition);
337
+ registry.shaByCommand.delete(name);
338
+ Logger.debug('[redis][proxy][bullmq] registered defined command', {
298
339
  commandName: name,
299
340
  numberOfKeys: definition.numberOfKeys,
341
+ luaLength: definition.lua.length,
300
342
  });
301
343
  },
302
344
  runCommand: async (name, args) => requestProxyCommand(settings, name, { args }),
@@ -314,24 +356,33 @@ const createProxyTarget = (config, options, settings, client) => {
314
356
  };
315
357
  return target;
316
358
  };
317
- export const createRedisProxyConnection = (config, options) => {
359
+ export const createRedisProxyConnection = (config, options, registry) => {
318
360
  const settings = resolveProxySettings(options);
361
+ const scriptRegistry = registry ?? {
362
+ definitions: new Map(),
363
+ shaByCommand: new Map(),
364
+ };
319
365
  logTransportSelection('proxy', config, options);
320
366
  Logger.info('[redis][proxy] Creating opaque proxy connection', {
321
367
  transport: 'BullMQ',
322
368
  });
323
- const proxyTarget = createProxyTarget(config, options, settings, null);
369
+ const proxyTarget = createProxyTarget(config, options, settings, null, scriptRegistry);
324
370
  const client = new Proxy(proxyTarget, {
325
371
  get(obj, prop) {
326
- return handlePropertyAccess(obj, prop, client, settings);
372
+ return handlePropertyAccess(obj, prop, client, settings, scriptRegistry);
327
373
  },
328
374
  });
329
375
  return client;
330
376
  };
331
377
  export const ensureRedisTransportMode = (config, options) => {
332
378
  const mode = resolveRedisTransportMode();
379
+ const subsystem = options?.subsystem ?? 'redis';
380
+ const requireDirectForScripts = options?.requireDirectForScripts ?? Env.REDIS_REQUIRE_DIRECT_FOR_SCRIPTS;
333
381
  if (mode === 'proxy' && options?.requireDirect === true) {
334
- throw ErrorFactory.createConfigError(`Redis subsystem '${options.subsystem ?? 'redis'}' requires a direct Redis connection, but proxy mode is enabled.`);
382
+ throw ErrorFactory.createConfigError(`Redis subsystem '${subsystem}' requires a direct Redis connection, but proxy mode is enabled.`);
383
+ }
384
+ if (mode === 'proxy' && requireDirectForScripts) {
385
+ throw ErrorFactory.createConfigError(`Redis subsystem '${subsystem}' requires a direct Redis connection for scripts, but proxy mode is enabled.`);
335
386
  }
336
387
  if (mode === 'direct') {
337
388
  logTransportSelection(mode, config, options);