@zintrust/core 2.2.5 → 2.2.7

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.5",
3
+ "version": "2.2.7",
4
4
  "description": "Production-grade TypeScript backend framework for JavaScript",
5
5
  "homepage": "https://zintrust.com",
6
6
  "repository": {
@@ -21,6 +21,7 @@ type CloudflareSecretSyncArgs = {
21
21
  target?: string;
22
22
  bulk?: boolean;
23
23
  requireSelection?: boolean;
24
+ all?: boolean;
24
25
  };
25
26
  export type CloudflareSecretSyncResult = {
26
27
  pushed: number;
@@ -31,9 +32,9 @@ export type CloudflareSecretSyncResult = {
31
32
  };
32
33
  export declare const uniq: (items: string[]) => string[];
33
34
  export declare const reportCloudflareSecretSync: (log: CloudflareSecretLog, result: Pick<CloudflareSecretSyncResult, "pushed" | "skippedEmptyKeys" | "failures">) => void;
34
- export declare const syncCloudflareSecrets: ({ log, cwd, wranglerEnvs, envPath, dryRun, configGroups, directKeys, inlineValues, configPath, target, bulk, requireSelection, }: CloudflareSecretSyncArgs) => Promise<CloudflareSecretSyncResult>;
35
+ export declare const syncCloudflareSecrets: ({ log, cwd, wranglerEnvs, envPath, dryRun, configGroups, directKeys, inlineValues, configPath, target, bulk, requireSelection, all, }: CloudflareSecretSyncArgs) => Promise<CloudflareSecretSyncResult>;
35
36
  declare const _default: Readonly<{
36
- syncCloudflareSecrets: ({ log, cwd, wranglerEnvs, envPath, dryRun, configGroups, directKeys, inlineValues, configPath, target, bulk, requireSelection, }: CloudflareSecretSyncArgs) => Promise<CloudflareSecretSyncResult>;
37
+ syncCloudflareSecrets: ({ log, cwd, wranglerEnvs, envPath, dryRun, configGroups, directKeys, inlineValues, configPath, target, bulk, requireSelection, all, }: CloudflareSecretSyncArgs) => Promise<CloudflareSecretSyncResult>;
37
38
  reportCloudflareSecretSync: (log: CloudflareSecretLog, result: Pick<CloudflareSecretSyncResult, "pushed" | "skippedEmptyKeys" | "failures">) => void;
38
39
  }>;
39
40
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"file":"CloudflareSecretSync.d.ts","sourceRoot":"","sources":["../../../../src/cli/cloudflare/CloudflareSecretSync.ts"],"names":[],"mappings":"AAaA,KAAK,mBAAmB,GAAG;IACzB,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAcF,KAAK,wBAAwB,GAAG;IAC9B,GAAG,EAAE,mBAAmB,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,QAAQ,EAAE,2BAA2B,EAAE,CAAC;IACxC,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AASF,eAAO,MAAM,IAAI,GAAI,OAAO,MAAM,EAAE,KAAG,MAAM,EAY5C,CAAC;AAwSF,eAAO,MAAM,0BAA0B,GACrC,KAAK,mBAAmB,EACxB,QAAQ,IAAI,CAAC,0BAA0B,EAAE,QAAQ,GAAG,kBAAkB,GAAG,UAAU,CAAC,KACnF,IAcF,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAAU,kIAazC,wBAAwB,KAAG,OAAO,CAAC,0BAA0B,CAiD/D,CAAC;;8JAjDC,wBAAwB,KAAG,OAAO,CAAC,0BAA0B,CAAC;sCA/B1D,mBAAmB,UAChB,IAAI,CAAC,0BAA0B,EAAE,QAAQ,GAAG,kBAAkB,GAAG,UAAU,CAAC,KACnF,IAAI;;AAgFP,wBAGG"}
1
+ {"version":3,"file":"CloudflareSecretSync.d.ts","sourceRoot":"","sources":["../../../../src/cli/cloudflare/CloudflareSecretSync.ts"],"names":[],"mappings":"AAaA,KAAK,mBAAmB,GAAG;IACzB,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAcF,KAAK,wBAAwB,GAAG;IAC9B,GAAG,EAAE,mBAAmB,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,GAAG,CAAC,EAAE,OAAO,CAAC;CACf,CAAC;AAcF,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,QAAQ,EAAE,2BAA2B,EAAE,CAAC;IACxC,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC;AASF,eAAO,MAAM,IAAI,GAAI,OAAO,MAAM,EAAE,KAAG,MAAM,EAY5C,CAAC;AA+TF,eAAO,MAAM,0BAA0B,GACrC,KAAK,mBAAmB,EACxB,QAAQ,IAAI,CAAC,0BAA0B,EAAE,QAAQ,GAAG,kBAAkB,GAAG,UAAU,CAAC,KACnF,IAcF,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAAU,uIAczC,wBAAwB,KAAG,OAAO,CAAC,0BAA0B,CAqD/D,CAAC;;mKArDC,wBAAwB,KAAG,OAAO,CAAC,0BAA0B,CAAC;sCAhC1D,mBAAmB,UAChB,IAAI,CAAC,0BAA0B,EAAE,QAAQ,GAAG,kBAAkB,GAAG,UAAU,CAAC,KACnF,IAAI;;AAqFP,wBAGG"}
@@ -38,16 +38,22 @@ const getConfigArray = (config, key) => {
38
38
  return [];
39
39
  return uniq(raw.filter((item) => typeof item === 'string'));
40
40
  };
41
- const resolveValue = (key, envMap) => {
41
+ const resolveValue = (key, envMap, envPath, all = false) => {
42
42
  const fromFile = envMap[key];
43
43
  const fromProcess = process.env[key];
44
+ const isCustomEnvFile = envPath !== '.env' && envPath.trim() !== '';
45
+ // If custom env file is provided and all is false, only use values from the custom file
46
+ if (isCustomEnvFile && !all) {
47
+ return fromFile ?? '';
48
+ }
49
+ // Default behavior: fallback to process.env
44
50
  return fromFile ?? fromProcess ?? '';
45
51
  };
46
- const resolveValueWithOverrides = (key, envMap, inlineValues) => {
52
+ const resolveValueWithOverrides = (key, envMap, inlineValues, envPath, all = false) => {
47
53
  const inlineValue = inlineValues[key];
48
54
  if (typeof inlineValue === 'string')
49
55
  return inlineValue;
50
- return resolveValue(key, envMap);
56
+ return resolveValue(key, envMap, envPath, all);
51
57
  };
52
58
  const getPutTimeoutMs = () => {
53
59
  const raw = process.env['ZT_PUT_TIMEOUT_MS'];
@@ -131,13 +137,13 @@ const resolveSelectedKeys = ({ log, config, cwd, wranglerEnvs, configGroups = []
131
137
  ? 'No secret keys resolved from explicit keys or .zintrust.json cloudflare.shared_env/cloudflare.targets/cloudflare.wrangler_envs. Use --key/--keys, --var <group>, or add a Cloudflare env manifest.'
132
138
  : 'No secret keys resolved from selected groups.');
133
139
  };
134
- const resolveBulkPayload = (log, wranglerEnv, selectedKeys, envMap, inlineValues) => {
140
+ const resolveBulkPayload = (log, wranglerEnv, selectedKeys, envMap, inlineValues, envPath, all = false) => {
135
141
  const payload = {};
136
142
  const includedKeys = [];
137
143
  const skippedEmptyKeys = [];
138
144
  const wranglerEnvLabel = describeWranglerEnv(wranglerEnv);
139
145
  for (const key of selectedKeys) {
140
- const value = resolveValueWithOverrides(key, envMap, inlineValues);
146
+ const value = resolveValueWithOverrides(key, envMap, inlineValues, envPath, all);
141
147
  if (value.trim() === '') {
142
148
  log.warn(`skip ${key} -> ${wranglerEnvLabel}: empty value`);
143
149
  skippedEmptyKeys.push(key);
@@ -148,11 +154,12 @@ const resolveBulkPayload = (log, wranglerEnv, selectedKeys, envMap, inlineValues
148
154
  }
149
155
  return { payload, includedKeys, skippedEmptyKeys };
150
156
  };
151
- const processSecretSync = (log, wranglerEnvs, selectedKeys, envMap, dryRun, configPath, inlineValues) => {
157
+ const processSecretSync = (args) => {
158
+ const { log, wranglerEnvs, selectedKeys, envMap, dryRun, configPath, inlineValues, envPath, all, } = args;
152
159
  const progress = createSyncProgress();
153
160
  forEachWranglerEnv(wranglerEnvs, (wranglerEnv, wranglerEnvLabel) => {
154
161
  for (const key of selectedKeys) {
155
- const value = resolveValueWithOverrides(key, envMap, inlineValues);
162
+ const value = resolveValueWithOverrides(key, envMap, inlineValues, envPath, all);
156
163
  if (value.trim() === '') {
157
164
  log.warn(`skip ${key} -> ${wranglerEnvLabel}: empty value`);
158
165
  progress.skippedEmptyKeys.push(key);
@@ -174,10 +181,11 @@ const processSecretSync = (log, wranglerEnvs, selectedKeys, envMap, dryRun, conf
174
181
  });
175
182
  return progress;
176
183
  };
177
- const processSecretBulkSync = (log, wranglerEnvs, selectedKeys, envMap, dryRun, configPath, inlineValues) => {
184
+ const processSecretBulkSync = (args) => {
185
+ const { log, wranglerEnvs, selectedKeys, envMap, dryRun, configPath, inlineValues, envPath, all, } = args;
178
186
  const progress = createSyncProgress();
179
187
  forEachWranglerEnv(wranglerEnvs, (wranglerEnv, wranglerEnvLabel) => {
180
- const { payload, includedKeys, skippedEmptyKeys: skippedForEnv, } = resolveBulkPayload(log, wranglerEnv, selectedKeys, envMap, inlineValues);
188
+ const { payload, includedKeys, skippedEmptyKeys: skippedForEnv, } = resolveBulkPayload(log, wranglerEnv, selectedKeys, envMap, inlineValues, envPath, all);
181
189
  progress.skippedEmptyKeys.push(...skippedForEnv);
182
190
  if (includedKeys.length === 0) {
183
191
  log.info(`skip bulk upload -> ${wranglerEnvLabel}: no non-empty keys`);
@@ -222,7 +230,7 @@ export const reportCloudflareSecretSync = (log, result) => {
222
230
  log.warn(`${item.key} -> ${describeWranglerEnv(item.wranglerEnv)}: ${item.reason}`);
223
231
  }
224
232
  };
225
- export const syncCloudflareSecrets = async ({ log, cwd, wranglerEnvs, envPath, dryRun = false, configGroups = [], directKeys = [], inlineValues = {}, configPath, target, bulk = false, requireSelection = true, }) => {
233
+ export const syncCloudflareSecrets = async ({ log, cwd, wranglerEnvs, envPath, dryRun = false, configGroups = [], directKeys = [], inlineValues = {}, configPath, target, bulk = false, requireSelection = true, all = false, }) => {
226
234
  const normalizedConfigPath = typeof configPath === 'string' && configPath.trim() !== '' ? configPath.trim() : undefined;
227
235
  if (normalizedConfigPath !== undefined && !existsSync(path.join(cwd, normalizedConfigPath))) {
228
236
  throw ErrorFactory.createCliError(`Wrangler config not found: ${normalizedConfigPath}`);
@@ -244,8 +252,28 @@ export const syncCloudflareSecrets = async ({ log, cwd, wranglerEnvs, envPath, d
244
252
  }
245
253
  const envMap = await EnvFile.read({ cwd, path: envPath });
246
254
  const syncResult = bulk
247
- ? processSecretBulkSync(log, wranglerEnvs, selectedKeys, envMap, dryRun, normalizedConfigPath, inlineValues)
248
- : processSecretSync(log, wranglerEnvs, selectedKeys, envMap, dryRun, normalizedConfigPath, inlineValues);
255
+ ? processSecretBulkSync({
256
+ log,
257
+ wranglerEnvs,
258
+ selectedKeys,
259
+ envMap,
260
+ dryRun,
261
+ configPath: normalizedConfigPath,
262
+ inlineValues,
263
+ envPath,
264
+ all,
265
+ })
266
+ : processSecretSync({
267
+ log,
268
+ wranglerEnvs,
269
+ selectedKeys,
270
+ envMap,
271
+ dryRun,
272
+ configPath: normalizedConfigPath,
273
+ inlineValues,
274
+ envPath,
275
+ all,
276
+ });
249
277
  return {
250
278
  ...syncResult,
251
279
  selectedKeys,
@@ -1 +1 @@
1
- {"version":3,"file":"DeployCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/DeployCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AA0LrE;;;GAGG;AACH,eAAO,MAAM,aAAa;IACxB;;OAEG;cACO,YAAY;EAGtB,CAAC"}
1
+ {"version":3,"file":"DeployCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/DeployCommand.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAgMrE;;;GAGG;AACH,eAAO,MAAM,aAAa;IACxB;;OAEG;cACO,YAAY;EAGtB,CAAC"}
@@ -58,6 +58,7 @@ const syncWranglerSecrets = async (cmd, cwd, wranglerEnv, options) => {
58
58
  configPath: typeof options.config === 'string' ? options.config.trim() : undefined,
59
59
  target: typeof options.target === 'string' ? options.target : undefined,
60
60
  requireSelection: false,
61
+ all: options.all === true,
61
62
  });
62
63
  if (result.selectedKeys.length === 0)
63
64
  return;
@@ -109,6 +110,7 @@ const createDeployCommand = () => {
109
110
  command.option('-c, --config <path>', 'Wrangler config file (e.g. wrangler.containers-proxy.jsonc)');
110
111
  command.option('--env-path <path>', 'Path to env file used when syncing Cloudflare secrets', '.env');
111
112
  command.option('--target <id>', 'Cloudflare worker target key from .zintrust.json cloudflare.targets');
113
+ command.option('--all', 'Sync both custom env file and process.env (only applies when custom env file is provided)');
112
114
  command.option('--no-sync-secrets', 'Skip Cloudflare secret sync before wrangler deploy');
113
115
  },
114
116
  execute: async (options) => {
@@ -1 +1 @@
1
- {"version":3,"file":"DeployContainersProxyCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/DeployContainersProxyCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AA6ErE,eAAO,MAAM,4BAA4B;cAC7B,YAAY;EAyBtB,CAAC"}
1
+ {"version":3,"file":"DeployContainersProxyCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/DeployContainersProxyCommand.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AA+ErE,eAAO,MAAM,4BAA4B;cAC7B,YAAY;EA6BtB,CAAC"}
@@ -31,6 +31,7 @@ const syncDeploySecrets = async (cmd, cwd, config, env, options) => {
31
31
  configPath: config,
32
32
  target: typeof options.target === 'string' ? options.target : undefined,
33
33
  requireSelection: false,
34
+ all: options.all === true,
34
35
  });
35
36
  if (result.selectedKeys.length === 0)
36
37
  return;
@@ -60,6 +61,7 @@ export const DeployContainersProxyCommand = Object.freeze({
60
61
  command.option('-c, --config <path>', 'Wrangler config file', DEFAULT_CONFIG);
61
62
  command.option('--env-path <path>', 'Path to env file used when syncing Cloudflare secrets', '.env');
62
63
  command.option('--target <id>', 'Cloudflare worker target key from .zintrust.json cloudflare.targets');
64
+ command.option('--all', 'Sync both custom env file and process.env (only applies when custom env file is provided)');
63
65
  command.option('--no-sync-secrets', 'Skip Cloudflare secret sync before wrangler deploy');
64
66
  },
65
67
  execute: async (options) => execute(cmd, options),
@@ -1 +1 @@
1
- {"version":3,"file":"PutCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/PutCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AA6GvF,eAAO,MAAM,UAAU;cACX,YAAY;EAWtB,CAAC;AAEH,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"PutCommand.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/PutCommand.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAmHvF,eAAO,MAAM,UAAU;cACX,YAAY;EAWtB,CAAC;AAEH,eAAe,UAAU,CAAC"}
@@ -50,6 +50,7 @@ const addOptions = (command) => {
50
50
  .option('--env_path <path>', 'Path to env file used as source values', '.env')
51
51
  .option('-c, --config <path>', 'Wrangler config file to target (optional)')
52
52
  .option('--bulk', 'Upload the final key set with one wrangler secret bulk call per target')
53
+ .option('--all', 'Sync both custom env file and process.env (only applies when custom env file is provided)')
53
54
  .option('--dry-run', 'Show what would be uploaded without calling wrangler');
54
55
  };
55
56
  const ensureCloudflareProvider = (providerRaw) => {
@@ -75,6 +76,7 @@ const execute = async (cmd, options) => {
75
76
  target: typeof options.target === 'string' ? options.target : undefined,
76
77
  bulk: options.bulk === true,
77
78
  requireSelection: true,
79
+ all: options.all === true,
78
80
  });
79
81
  reportCloudflareSecretSync(cmd, result);
80
82
  };
package/src/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
- * @zintrust/core v2.2.5
2
+ * @zintrust/core v2.2.7
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:42:54.893Z
8
+ * Built: 2026-05-29T08:10:13.516Z
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:42:54.858Z'; // Replaced during build
24
+ export const ZINTRUST_BUILD_DATE = '2026-05-29T08:10:13.484Z'; // 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":"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') {
@@ -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;AAgRN,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"}
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"}
@@ -147,11 +147,17 @@ const importFromLocalFallback = async (specifier, fallback) => {
147
147
  };
148
148
  const importFromFileContents = async (filePath, specifier) => {
149
149
  try {
150
- const summary = await importSpecifiers([{ filePath, specifier }]);
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 })));
151
156
  if (summary.loaded > 0) {
152
- Logger.debug('[plugins] Loaded auto-import specifier from file contents', {
157
+ Logger.debug('[plugins] Loaded auto-import specifiers from file contents', {
153
158
  specifier,
154
159
  filePath,
160
+ loadedCount: summary.loaded,
155
161
  });
156
162
  return 'loaded';
157
163
  }
@@ -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);