Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions run-classifier.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#!/usr/bin/env node

import { runCompleteAnalysisDemo } from './src/completePostAnalysis.js';
import { runCategorization } from './src/classifyWithOpenAI.js';
import { analyzeMultiplePosts } from './src/analyzeSentiment.js';
import { generateTitlesForPosts } from './src/generateTitle.js';
import { runCompleteAnalysisDemo } from './src/post/completePostAnalysis.js';
import { runCategorization } from './src/openai/classifyWithOpenAI.js';
import { analyzeMultiplePosts } from './src/analysis/analyzeSentiment.js';
import { generateTitlesForPosts } from './src/analysis/generateTitle.js';

// Get command line argument
const command = process.argv[2];
Expand Down
7 changes: 5 additions & 2 deletions src/analyzeSentiment.ts → src/analysis/analyzeSentiment.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// This file has been moved to the analysis subfolder.
// Please update your imports accordingly.
import { analyzeMultiplePosts } from './analyzeSentiment.js';
Comment on lines +1 to +3
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Remove incorrect self-referential import.

Lines 1-3 contain a relocation comment and a self-referential import that imports analyzeMultiplePosts from ./analyzeSentiment.js. This creates an incorrect circular dependency since the file is trying to import a function that it itself defines (the analyzeMultiplePosts function is defined in this same file at lines 22-54).

Apply this diff to remove the incorrect lines:

-// This file has been moved to the analysis subfolder.
-// Please update your imports accordingly.
-import { analyzeMultiplePosts } from './analyzeSentiment.js';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// This file has been moved to the analysis subfolder.
// Please update your imports accordingly.
import { analyzeMultiplePosts } from './analyzeSentiment.js';
🤖 Prompt for AI Agents
In src/analysis/analyzeSentiment.ts lines 1-3, remove the self-referential
relocation comment and the import statement that imports analyzeMultiplePosts
from './analyzeSentiment.js' because analyzeMultiplePosts is defined in this
same file (lines 22-54) which creates a circular dependency; delete those lines
and ensure any external modules import this file from its new analysis/ path
instead of relying on this file to re-import itself.

import type OpenAI from 'openai';
import openai from './openaiClient.js';
import { callOpenAIWithValidation } from './openaiValidationUtil.js';
import openai from '../openai/openaiClient.js';
import { callOpenAIWithValidation } from '../openai/openaiValidationUtil.js';
import { z } from 'zod';
import { generateTitleForPost } from './generateTitle.js';

Expand Down
7 changes: 5 additions & 2 deletions src/generateTitle.ts → src/analysis/generateTitle.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// This file has been moved to the analysis subfolder.
// Please update your imports accordingly.
import { generateTitleForPost } from './generateTitle.js';
import type OpenAI from 'openai';
import openai from './openaiClient.js';
import { callOpenAIWithValidation } from './openaiValidationUtil.js';
import openai from '../openai/openaiClient.js';
import { callOpenAIWithValidation } from '../openai/openaiValidationUtil.js';
import { z } from 'zod';

// Title generation result type
Expand Down
3 changes: 3 additions & 0 deletions src/constants.ts → src/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// This file has been moved to the analysis subfolder.
// Please update your imports accordingly.
export const CATEGORIES = [...]; // Keep the original content
Comment on lines +1 to +3
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Remove malformed placeholder export.

Lines 1-3 contain a relocation comment with a malformed export statement that has incomplete syntax (export const CATEGORIES = [...];). This causes a parse error as reported by static analysis tools. The actual CATEGORIES export is correctly defined on lines 6-25.

Additionally, the relocation comment states the file has been moved to the analysis subfolder, but the file path shows it's still in src/lib/constants.ts. This creates confusion about the actual file location.

Apply this diff to remove the malformed lines:

-// This file has been moved to the analysis subfolder.
-// Please update your imports accordingly.
-export const CATEGORIES = [...]; // Keep the original content
-// src/constants.ts
-

Based on static analysis hints.

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome (2.1.2)

[error] 3-3: Expected an expression, or an assignment but instead found ']'.

Expected an expression, or an assignment here.

(parse)

🤖 Prompt for AI Agents
In src/lib/constants.ts lines 1-3, remove the relocation comment and the
malformed placeholder export (the entire lines containing "// This file has been
moved..." and "export const CATEGORIES = [...];") so the file parses cleanly and
keeps the correct CATEGORIES export defined on lines 6-25; if the constants were
actually moved to an analysis subfolder, update any imports across the codebase
to point to the new path, otherwise delete the misleading relocation note only.

// src/constants.ts

export const CATEGORIES = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
// This file has been moved to the analysis subfolder.
// Please update your imports accordingly.
import { categorizePost } from './analysis/classifyWithOpenAI.js';
Comment on lines +1 to +3
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Remove incorrect self-referential import.

Lines 1-3 contain a relocation comment and a self-referential import that imports categorizePost from ./analysis/classifyWithOpenAI.js. This creates an incorrect circular dependency since:

  1. The file is located at src/openai/classifyWithOpenAI.ts, so the path ./analysis/classifyWithOpenAI.js would incorrectly point to src/openai/analysis/classifyWithOpenAI.js
  2. The categorizePost function is defined in this same file at line 29

The relocation comment suggests this file should be moved to the analysis subfolder, but it's still in src/openai/.

Apply this diff to remove the incorrect lines:

-// This file has been moved to the analysis subfolder.
-// Please update your imports accordingly.
-import { categorizePost } from './analysis/classifyWithOpenAI.js';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// This file has been moved to the analysis subfolder.
// Please update your imports accordingly.
import { categorizePost } from './analysis/classifyWithOpenAI.js';
🤖 Prompt for AI Agents
In src/openai/classifyWithOpenAI.ts lines 1-3: remove the relocation comment and
the self-referential import of categorizePost from
./analysis/classifyWithOpenAI.js; keep the local categorizePost implementation
in this file (line ~29) as the source of truth, and update any external modules
that expect the function from the analysis subfolder to import from the correct
path (or move this file to src/analysis/ and update all imports accordingly) so
there is no circular or incorrect path reference.

// No top-level execution. Export the runner and allow explicit CLI invocation with --run.
import type OpenAI from 'openai';
import type { ChatCompletionMessageParam } from 'openai/resources';
import openai from './openaiClient.js';
import { callOpenAIWithValidation } from './openaiValidationUtil.js';
import { z } from 'zod';
import { generateTitleForPost } from './generateTitle.js';
import { CATEGORIES } from './constants.js';
import { generateTitleForPost } from '../analysis/generateTitle.js';
import { CATEGORIES } from '../lib/constants.js';

// Zod schema for categorization validation
const CategorizationSchema = z.object({
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// This file has been moved to the analysis subfolder.
// Please update your imports accordingly.
import { callOpenAIWithValidation } from './analysis/openaiValidationUtil.js';
Comment on lines +1 to +3
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Remove incorrect self-referential import.

Lines 1-3 contain a relocation comment and a self-referential import that imports callOpenAIWithValidation from ./analysis/openaiValidationUtil.js. This creates an incorrect circular dependency since the file is trying to import a function from itself (or from a path that doesn't align with the current file location).

The relocation comment indicates this file should be moved to the analysis subfolder, but the file path shows it's still in src/openai/. Either:

  1. The file needs to be moved to src/analysis/openaiValidationUtil.ts and the self-import removed, or
  2. The relocation comment and self-import should be removed if the file is staying in src/openai/

Apply this diff to remove the incorrect lines:

-// This file has been moved to the analysis subfolder.
-// Please update your imports accordingly.
-import { callOpenAIWithValidation } from './analysis/openaiValidationUtil.js';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// This file has been moved to the analysis subfolder.
// Please update your imports accordingly.
import { callOpenAIWithValidation } from './analysis/openaiValidationUtil.js';
🤖 Prompt for AI Agents
In src/openai/openaiValidationUtil.ts lines 1-3, remove the relocation comment
and the self-referential import statement that imports callOpenAIWithValidation
from ./analysis/openaiValidationUtil.js; if the file was intended to be
relocated instead, move this file to src/analysis/openaiValidationUtil.ts and
update any external import paths to point to the new location (ensuring no
self-import remains) so there are no circular or incorrect imports.

import type OpenAI from 'openai';
import type { ChatCompletionMessageParam } from 'openai/resources';
import { ZodSchema } from 'zod';
import { sleep, jitteredBackoff } from './lib/utils.js';
import { sleep, jitteredBackoff } from '../lib/utils.js';

/**
* Calls OpenAI chat completion API and validates the response with a Zod schema, retrying up to maxAttempts times.
Expand Down
11 changes: 7 additions & 4 deletions src/completePostAnalysis.ts → src/post/completePostAnalysis.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
// This file has been moved to the analysis subfolder.
// Please update your imports accordingly.
import { analyzeCompletePost } from './analysis/completePostAnalysis.js';
Comment on lines +1 to +3
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Potential circular import detected.

Line 3 imports analyzeCompletePost from './analysis/completePostAnalysis.js', but this file also defines and exports analyzeCompletePost at line 20. This creates a circular dependency risk if both files attempt to import from each other.

If this is a temporary forwarding stub during migration, ensure that './analysis/completePostAnalysis.js' is the canonical implementation and this file will be removed after all imports are updated. Otherwise, remove the import on line 3 to avoid the circular dependency.

Verify the module structure:


🏁 Script executed:

#!/bin/bash
# Check if ./analysis/completePostAnalysis.js exists and what it exports/imports
fd -t f 'completePostAnalysis' src/
rg -n "export.*analyzeCompletePost" src/
rg -n "from.*completePostAnalysis" src/

Length of output: 289


Fix missing target module and duplicate export. Line 3 imports from ./analysis/completePostAnalysis.js, which doesn’t exist, and line 20 redefines analyzeCompletePost. Move the implementation into src/post/analysis/completePostAnalysis.ts, update all imports, and remove the inline definition in this file.

🤖 Prompt for AI Agents
In src/post/completePostAnalysis.ts around lines 1 to 3, the file imports from
./analysis/completePostAnalysis.js which does not exist and also contains a
duplicate inline export of analyzeCompletePost (line ~20); move the existing
analyzeCompletePost implementation into a new file
src/post/analysis/completePostAnalysis.ts, export it there, update all project
imports to point to src/post/analysis/completePostAnalysis (use correct .ts path
or index barrel as per project convention), and remove the inline
analyzeCompletePost definition and duplicate export from
src/post/completePostAnalysis.ts so that the file only re-exports or imports the
implementation from the new module.

import type OpenAI from 'openai';
import openai from './openaiClient.js';
import { categorizePost, type Categorization } from './classifyWithOpenAI.js';
import { analyzeMultiplePosts, type SentimentResult } from './analyzeSentiment.js';
import { generateTitleForPost, type TitleResult } from './generateTitle.js';
import openai from '../openai/openaiClient.js';
import { categorizePost, type Categorization } from '../openai/classifyWithOpenAI.js';
import { analyzeMultiplePosts, type SentimentResult } from '../analysis/analyzeSentiment.js';
import { generateTitleForPost, type TitleResult } from '../analysis/generateTitle.js';

// Combined result type for complete post analysis
export type PostAnalysisResult = {
Expand Down
2 changes: 1 addition & 1 deletion src/postGroup.ts → src/redis/postGroup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import cron from 'node-cron';
import { generateTitleForPost, generateSentimentSummariesForGroup, type SentimentSummaries } from './generateTitle';
import { generateTitleForPost, generateSentimentSummariesForGroup, type SentimentSummaries } from '../analysis/generateTitle';
import { initRedis, getRedisClient } from './redisClient';

export type Post = {
Expand Down
30 changes: 30 additions & 0 deletions src/redis/redisCheck.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { initRedis } from './redis/redisClient.js';

async function checkRedisSeed() {
const client = await initRedis();
try {
const posts = JSON.parse((await client.get('posts')) || '[]');
console.log(JSON.stringify(posts));
} catch (err) {
console.error('Error checking Redis seed:', err);
} finally {
await client.disconnect();
}
}

checkRedisSeed();
import { initRedis } from './redisClient.js';

async function checkRedisSeed() {
const client = await initRedis();
try {
const posts = JSON.parse((await client.get('posts')) || '[]');
console.log(JSON.stringify(posts));
} catch (err) {
console.error('Error checking Redis seed:', err);
} finally {
await client.disconnect();
}
}

checkRedisSeed();
Comment on lines +1 to +30
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Eliminate duplicate block and fix Redis import path

The file currently includes two copies of checkRedisSeed. The first copy imports from './redis/redisClient.js', which does not exist relative to this file, and both copies redeclare the same symbols—TypeScript/Node will error and the script will fail to run. Drop the mistaken block and keep a single definition that imports from './redisClient.js'.

-import { initRedis } from './redis/redisClient.js';
-
-async function checkRedisSeed() {
-  const client = await initRedis();
-  try {
-    const posts = JSON.parse((await client.get('posts')) || '[]');
-    console.log(JSON.stringify(posts));
-  } catch (err) {
-    console.error('Error checking Redis seed:', err);
-  } finally {
-    await client.disconnect();
-  }
-}
-
-checkRedisSeed();
-import { initRedis } from './redisClient.js';
+import { initRedis } from './redisClient.js';
 
 async function checkRedisSeed() {
   const client = await initRedis();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { initRedis } from './redis/redisClient.js';
async function checkRedisSeed() {
const client = await initRedis();
try {
const posts = JSON.parse((await client.get('posts')) || '[]');
console.log(JSON.stringify(posts));
} catch (err) {
console.error('Error checking Redis seed:', err);
} finally {
await client.disconnect();
}
}
checkRedisSeed();
import { initRedis } from './redisClient.js';
async function checkRedisSeed() {
const client = await initRedis();
try {
const posts = JSON.parse((await client.get('posts')) || '[]');
console.log(JSON.stringify(posts));
} catch (err) {
console.error('Error checking Redis seed:', err);
} finally {
await client.disconnect();
}
}
checkRedisSeed();
import { initRedis } from './redisClient.js';
async function checkRedisSeed() {
const client = await initRedis();
try {
const posts = JSON.parse((await client.get('posts')) || '[]');
console.log(JSON.stringify(posts));
} catch (err) {
console.error('Error checking Redis seed:', err);
} finally {
await client.disconnect();
}
}
checkRedisSeed();
🧰 Tools
🪛 Biome (2.1.2)

[error] 16-16: Shouldn't redeclare 'initRedis'. Consider to delete it or rename it.

'initRedis' is defined here:

(lint/suspicious/noRedeclare)


[error] 18-18: Shouldn't redeclare 'checkRedisSeed'. Consider to delete it or rename it.

'checkRedisSeed' is defined here:

(lint/suspicious/noRedeclare)

🤖 Prompt for AI Agents
In src/redis/redisCheck.ts lines 1-30 there are two duplicate definitions of
checkRedisSeed and an incorrect import path; remove the entire duplicated block
(keep only one function) and ensure the remaining import is corrected to import
{ initRedis } from './redisClient.js' (not './redis/redisClient.js'), leaving a
single checkRedisSeed implementation that calls initRedis, handles
try/catch/finally, and invokes checkRedisSeed() once.

91 changes: 91 additions & 0 deletions src/redisClient.ts → src/redis/redisClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,94 @@ export function getRedisClient(): RedisClientType {

return client;
}
import dotenv from 'dotenv';
import { createClient, RedisClientType } from 'redis';

dotenv.config();

// Prefer IPv4 loopback by default to avoid ::1/IPv6 resolution issues on some systems
const REDIS_URL = (process.env.REDIS_URL || 'redis://127.0.0.1:6379').replace('localhost', '127.0.0.1');

function safeRedisUrlForLog(url: string) {
try {
const u = new URL(url);
// show protocol, host and port only; hide auth/userinfo
const host = u.hostname || '';
const port = u.port ? `:${u.port}` : '';
return `${u.protocol}//${host}${port}`;
} catch (e) {
// fallback: remove everything between // and @ if present
return url.replace(/\/\/.*@/, '//');
}
}

let client: RedisClientType | null = null;
let connecting: Promise<RedisClientType> | null = null;

export async function initRedis(): Promise<RedisClientType> {
if (client && client.isOpen) return client;

// If a connect is already in progress, wait for it and return the resulting client
if (connecting) {
try {
await connecting;
} catch (err) {
// if the previous connecting attempt failed, clear it and continue to try again
}
if (client && client.isOpen) return client;
}

// Start a single connecting promise that other callers can await
connecting = (async (): Promise<RedisClientType> => {
const newClient = createClient({ url: REDIS_URL });

newClient.on('error', (err: unknown) => {
console.error('Redis Client Error:', err);
});

const maxRetries = 5;
const baseDelayMs = 200; // exponential backoff base

for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
await newClient.connect();
// assign to module client (cast to satisfy TS) and return the concrete client
client = newClient as unknown as RedisClientType;
// Clear connecting before returning so subsequent callers don't wait
connecting = null;
return newClient as unknown as RedisClientType;
} catch (err) {
console.error(`Redis connect attempt ${attempt} failed:`, err);
// If last attempt, clean up and rethrow
if (attempt === maxRetries) {
try {
// Attempt to cleanly disconnect if partially connected
// ignore errors from disconnect
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((newClient as any).isOpen) await newClient.disconnect();
} catch (e) {}
client = null;
connecting = null;
console.error('All Redis connection attempts failed.');
throw err;
}
const delay = baseDelayMs * 2 ** (attempt - 1);
await new Promise((res) => setTimeout(res, delay));
}
}

// unreachable, but satisfy TypeScript
connecting = null;
throw new Error('Redis connect failed');
})();

return connecting;
}

export function getRedisClient(): RedisClientType {
if (!client || !client.isOpen) {
throw new Error('Redis not initialized or client is closed. Call initRedis() first.');
}

return client;
}
Comment on lines +92 to +182
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove duplicate code block.

The entire file content (lines 1-91) has been duplicated at lines 92-182. This will cause compilation errors due to redeclaration of all identifiers, as correctly flagged by the static analysis tool.

Remove the duplicate lines:

-import dotenv from 'dotenv';
-import { createClient, RedisClientType } from 'redis';
-
-dotenv.config();
-
-// Prefer IPv4 loopback by default to avoid ::1/IPv6 resolution issues on some systems
-const REDIS_URL = (process.env.REDIS_URL || 'redis://127.0.0.1:6379').replace('localhost', '127.0.0.1');
-
-function safeRedisUrlForLog(url: string) {
-  try {
-    const u = new URL(url);
-    // show protocol, host and port only; hide auth/userinfo
-    const host = u.hostname || '';
-    const port = u.port ? `:${u.port}` : '';
-    return `${u.protocol}//${host}${port}`;
-  } catch (e) {
-    // fallback: remove everything between // and @ if present
-    return url.replace(/\/\/.*@/, '//');
-  }
-}
-
-let client: RedisClientType | null = null;
-let connecting: Promise<RedisClientType> | null = null;
-
-export async function initRedis(): Promise<RedisClientType> {
-  if (client && client.isOpen) return client;
-
-  // If a connect is already in progress, wait for it and return the resulting client
-  if (connecting) {
-    try {
-      await connecting;
-    } catch (err) {
-      // if the previous connecting attempt failed, clear it and continue to try again
-    }
-    if (client && client.isOpen) return client;
-  }
-
-  // Start a single connecting promise that other callers can await
-  connecting = (async (): Promise<RedisClientType> => {
-    const newClient = createClient({ url: REDIS_URL });
-
-    newClient.on('error', (err: unknown) => {
-      console.error('Redis Client Error:', err);
-    });
-
-    const maxRetries = 5;
-    const baseDelayMs = 200; // exponential backoff base
-
-    for (let attempt = 1; attempt <= maxRetries; attempt++) {
-      try {
-        await newClient.connect();
-        // assign to module client (cast to satisfy TS) and return the concrete client
-        client = newClient as unknown as RedisClientType;
-        // Clear connecting before returning so subsequent callers don't wait
-        connecting = null;
-        return newClient as unknown as RedisClientType;
-      } catch (err) {
-        console.error(`Redis connect attempt ${attempt} failed:`, err);
-        // If last attempt, clean up and rethrow
-        if (attempt === maxRetries) {
-          try {
-            // Attempt to cleanly disconnect if partially connected
-            // ignore errors from disconnect
-            // eslint-disable-next-line @typescript-eslint/no-explicit-any
-            if ((newClient as any).isOpen) await newClient.disconnect();
-          } catch (e) {}
-          client = null;
-          connecting = null;
-          console.error('All Redis connection attempts failed.');
-          throw err;
-        }
-        const delay = baseDelayMs * 2 ** (attempt - 1);
-        await new Promise((res) => setTimeout(res, delay));
-      }
-    }
-
-    // unreachable, but satisfy TypeScript
-    connecting = null;
-    throw new Error('Redis connect failed');
-  })();
-
-  return connecting;
-}
-
-export function getRedisClient(): RedisClientType {
-  if (!client || !client.isOpen) {
-    throw new Error('Redis not initialized or client is closed. Call initRedis() first.');
-  }
-
-  return client;
-}

The file should only contain lines 1-91.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import dotenv from 'dotenv';
import { createClient, RedisClientType } from 'redis';
dotenv.config();
// Prefer IPv4 loopback by default to avoid ::1/IPv6 resolution issues on some systems
const REDIS_URL = (process.env.REDIS_URL || 'redis://127.0.0.1:6379').replace('localhost', '127.0.0.1');
function safeRedisUrlForLog(url: string) {
try {
const u = new URL(url);
// show protocol, host and port only; hide auth/userinfo
const host = u.hostname || '';
const port = u.port ? `:${u.port}` : '';
return `${u.protocol}//${host}${port}`;
} catch (e) {
// fallback: remove everything between // and @ if present
return url.replace(/\/\/.*@/, '//');
}
}
let client: RedisClientType | null = null;
let connecting: Promise<RedisClientType> | null = null;
export async function initRedis(): Promise<RedisClientType> {
if (client && client.isOpen) return client;
// If a connect is already in progress, wait for it and return the resulting client
if (connecting) {
try {
await connecting;
} catch (err) {
// if the previous connecting attempt failed, clear it and continue to try again
}
if (client && client.isOpen) return client;
}
// Start a single connecting promise that other callers can await
connecting = (async (): Promise<RedisClientType> => {
const newClient = createClient({ url: REDIS_URL });
newClient.on('error', (err: unknown) => {
console.error('Redis Client Error:', err);
});
const maxRetries = 5;
const baseDelayMs = 200; // exponential backoff base
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
await newClient.connect();
// assign to module client (cast to satisfy TS) and return the concrete client
client = newClient as unknown as RedisClientType;
// Clear connecting before returning so subsequent callers don't wait
connecting = null;
return newClient as unknown as RedisClientType;
} catch (err) {
console.error(`Redis connect attempt ${attempt} failed:`, err);
// If last attempt, clean up and rethrow
if (attempt === maxRetries) {
try {
// Attempt to cleanly disconnect if partially connected
// ignore errors from disconnect
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((newClient as any).isOpen) await newClient.disconnect();
} catch (e) {}
client = null;
connecting = null;
console.error('All Redis connection attempts failed.');
throw err;
}
const delay = baseDelayMs * 2 ** (attempt - 1);
await new Promise((res) => setTimeout(res, delay));
}
}
// unreachable, but satisfy TypeScript
connecting = null;
throw new Error('Redis connect failed');
})();
return connecting;
}
export function getRedisClient(): RedisClientType {
if (!client || !client.isOpen) {
throw new Error('Redis not initialized or client is closed. Call initRedis() first.');
}
return client;
}
🧰 Tools
🪛 Biome (2.1.2)

[error] 92-92: Shouldn't redeclare 'dotenv'. Consider to delete it or rename it.

'dotenv' is defined here:

(lint/suspicious/noRedeclare)


[error] 93-93: Shouldn't redeclare 'createClient'. Consider to delete it or rename it.

'createClient' is defined here:

(lint/suspicious/noRedeclare)


[error] 93-93: Shouldn't redeclare 'RedisClientType'. Consider to delete it or rename it.

'RedisClientType' is defined here:

(lint/suspicious/noRedeclare)


[error] 98-98: Shouldn't redeclare 'REDIS_URL'. Consider to delete it or rename it.

'REDIS_URL' is defined here:

(lint/suspicious/noRedeclare)


[error] 100-100: Shouldn't redeclare 'safeRedisUrlForLog'. Consider to delete it or rename it.

'safeRedisUrlForLog' is defined here:

(lint/suspicious/noRedeclare)


[error] 113-113: Shouldn't redeclare 'client'. Consider to delete it or rename it.

'client' is defined here:

(lint/suspicious/noRedeclare)


[error] 114-114: Shouldn't redeclare 'connecting'. Consider to delete it or rename it.

'connecting' is defined here:

(lint/suspicious/noRedeclare)


[error] 116-116: Shouldn't redeclare 'initRedis'. Consider to delete it or rename it.

'initRedis' is defined here:

(lint/suspicious/noRedeclare)


[error] 176-176: Shouldn't redeclare 'getRedisClient'. Consider to delete it or rename it.

'getRedisClient' is defined here:

(lint/suspicious/noRedeclare)

🤖 Prompt for AI Agents
In src/redis/redisClient.ts lines 92-182, the file contains a duplicate copy of
lines 1-91; remove the duplicated block so the file contains only the original
implementation (lines 1-91). After deleting the duplicate, ensure there is a
single declaration of imports, variables (client/connecting), and the exported
functions (initRedis and getRedisClient), then run the TypeScript build/linter
to verify no redeclarations remain.

Loading