fix: resolved biome errors

This commit is contained in:
2025-07-13 20:12:17 +02:00
parent 42ad5b7c80
commit 6114e80e98
23 changed files with 7589 additions and 4180 deletions

View File

@ -199,3 +199,20 @@ Environment variables are managed through `lib/env.ts` with .env.local file supp
- JWT tokens with 24-hour expiration and secure cookie settings - JWT tokens with 24-hour expiration and secure cookie settings
- HttpOnly, Secure, SameSite cookies with proper CSP integration - HttpOnly, Secure, SameSite cookies with proper CSP integration
- Company isolation and multi-tenant security - Company isolation and multi-tenant security
**Code Quality & Linting:**
- **Biome Integration**: Primary linting and formatting tool
- Pre-commit hooks enforce code quality standards
- Some security-critical patterns require `biome-ignore` comments
- Non-null assertions (`!`) used intentionally in authenticated contexts require ignore comments
- Complex functions may need refactoring to meet complexity thresholds (max 15)
- Performance classes use static-only patterns which may trigger warnings
- **TypeScript Strict Mode**: Comprehensive type checking
- Avoid `any` types where possible; use proper type definitions
- Optional chaining vs non-null assertions: choose based on security context
- In authenticated API handlers, non-null assertions are often safer than optional chaining
- **Security vs Linting Balance**:
- Security takes precedence over linting rules when they conflict
- Document security-critical choices with detailed comments
- Use `// biome-ignore` with explanations for intentional rule violations

View File

@ -11,29 +11,34 @@ This document summarizes the comprehensive documentation audit performed on the
The following areas were found to have comprehensive, accurate documentation: The following areas were found to have comprehensive, accurate documentation:
1. **CSRF Protection** (`docs/CSRF_PROTECTION.md`) 1. **CSRF Protection** (`docs/CSRF_PROTECTION.md`)
- Multi-layer protection implementation - Multi-layer protection implementation
- Client-side integration guide - Client-side integration guide
- tRPC integration details - tRPC integration details
- Comprehensive examples - Comprehensive examples
2. **Enhanced CSP Implementation** (`docs/security/enhanced-csp.md`) 2. **Enhanced CSP Implementation** (`docs/security/enhanced-csp.md`)
- Nonce-based script execution - Nonce-based script execution
- Environment-specific policies - Environment-specific policies
- Violation reporting and monitoring - Violation reporting and monitoring
- Testing framework - Testing framework
3. **Security Headers** (`docs/security-headers.md`) 3. **Security Headers** (`docs/security-headers.md`)
- Complete header implementation details - Complete header implementation details
- Testing procedures - Testing procedures
- Compatibility information - Compatibility information
4. **Security Monitoring System** (`docs/security-monitoring.md`) 4. **Security Monitoring System** (`docs/security-monitoring.md`)
- Real-time threat detection - Real-time threat detection
- Alert management - Alert management
- API usage examples - API usage examples
- Performance considerations - Performance considerations
5. **Migration Guide** (`MIGRATION_GUIDE.md`) 5. **Migration Guide** (`MIGRATION_GUIDE.md`)
- Comprehensive v2.0.0 migration procedures - Comprehensive v2.0.0 migration procedures
- Rollback procedures - Rollback procedures
- Health checks and validation - Health checks and validation

View File

@ -50,14 +50,14 @@ function usePlatformSession() {
if (sessionData?.user?.isPlatformUser) { if (sessionData?.user?.isPlatformUser) {
setSession({ setSession({
user: { user: {
id: sessionData.user.id || '', id: sessionData.user.id || "",
email: sessionData.user.email || '', email: sessionData.user.email || "",
name: sessionData.user.name, name: sessionData.user.name,
role: sessionData.user.role || '', role: sessionData.user.role || "",
companyId: sessionData.user.companyId, companyId: sessionData.user.companyId,
isPlatformUser: sessionData.user.isPlatformUser, isPlatformUser: sessionData.user.isPlatformUser,
platformRole: sessionData.user.platformRole, platformRole: sessionData.user.platformRole,
} },
}); });
setStatus("authenticated"); setStatus("authenticated");
} else { } else {

View File

@ -104,11 +104,7 @@ function SystemHealthCard({
schedulerStatus, schedulerStatus,
}: { }: {
health: { status: string; message: string }; health: { status: string; message: string };
schedulerStatus: { schedulerStatus: SchedulerStatus;
csvImport?: boolean;
processing?: boolean;
batch?: boolean;
};
}) { }) {
return ( return (
<Card> <Card>
@ -125,25 +121,33 @@ function SystemHealthCard({
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span>CSV Import Scheduler:</span> <span>Batch Creation:</span>
<Badge <Badge
variant={schedulerStatus?.csvImport ? "default" : "secondary"} variant={
schedulerStatus?.createBatchesRunning ? "default" : "secondary"
}
> >
{schedulerStatus?.csvImport ? "Running" : "Stopped"} {schedulerStatus?.createBatchesRunning ? "Running" : "Stopped"}
</Badge> </Badge>
</div> </div>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span>Processing Scheduler:</span> <span>Status Check:</span>
<Badge <Badge
variant={schedulerStatus?.processing ? "default" : "secondary"} variant={
schedulerStatus?.checkStatusRunning ? "default" : "secondary"
}
> >
{schedulerStatus?.processing ? "Running" : "Stopped"} {schedulerStatus?.checkStatusRunning ? "Running" : "Stopped"}
</Badge> </Badge>
</div> </div>
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">
<span>Batch Scheduler:</span> <span>Result Processing:</span>
<Badge variant={schedulerStatus?.batch ? "default" : "secondary"}> <Badge
{schedulerStatus?.batch ? "Running" : "Stopped"} variant={
schedulerStatus?.processResultsRunning ? "default" : "secondary"
}
>
{schedulerStatus?.processResultsRunning ? "Running" : "Stopped"}
</Badge> </Badge>
</div> </div>
</div> </div>
@ -155,7 +159,7 @@ function SystemHealthCard({
function CircuitBreakerCard({ function CircuitBreakerCard({
circuitBreakerStatus, circuitBreakerStatus,
}: { }: {
circuitBreakerStatus: Record<string, string> | null; circuitBreakerStatus: Record<string, CircuitBreakerStatus> | null;
}) { }) {
return ( return (
<Card> <Card>
@ -172,10 +176,8 @@ function CircuitBreakerCard({
{Object.entries(circuitBreakerStatus).map(([key, status]) => ( {Object.entries(circuitBreakerStatus).map(([key, status]) => (
<div key={key} className="flex justify-between text-sm"> <div key={key} className="flex justify-between text-sm">
<span>{key}:</span> <span>{key}:</span>
<Badge <Badge variant={!status.isOpen ? "default" : "destructive"}>
variant={status === "CLOSED" ? "default" : "destructive"} {status.isOpen ? "OPEN" : "CLOSED"}
>
{status as string}
</Badge> </Badge>
</div> </div>
))} ))}
@ -412,13 +414,8 @@ export default function BatchMonitoringDashboard() {
return ( return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
<SystemHealthCard <SystemHealthCard health={health} schedulerStatus={schedulerStatus} />
health={health} <CircuitBreakerCard circuitBreakerStatus={circuitBreakerStatus} />
schedulerStatus={schedulerStatus as any}
/>
<CircuitBreakerCard
circuitBreakerStatus={circuitBreakerStatus as any}
/>
</div> </div>
); );
}; };

View File

@ -280,6 +280,84 @@ function addCORSHeaders(
} }
} }
/**
* Process authentication and authorization
*/
async function processAuthAndAuthz(
context: APIContext,
options: APIHandlerOptions
): Promise<void> {
if (options.requireAuth) {
await validateAuthentication(context);
}
if (options.requiredRole || options.requirePlatformAccess) {
await validateAuthorization(context, options);
}
}
/**
* Process input validation
*/
async function processValidation(
request: NextRequest,
options: APIHandlerOptions
): Promise<{ validatedData: unknown; validatedQuery: unknown }> {
let validatedData: unknown;
if (options.validateInput && request.method !== "GET") {
validatedData = await validateInput(request, options.validateInput);
}
let validatedQuery: unknown;
if (options.validateQuery) {
validatedQuery = validateQuery(request, options.validateQuery);
}
return { validatedData, validatedQuery };
}
/**
* Create and configure response
*/
function createAPIResponse<T>(
result: T,
context: APIContext,
options: APIHandlerOptions
): NextResponse {
const response = NextResponse.json(
createSuccessResponse(result, { requestId: context.requestId })
);
response.headers.set("X-Request-ID", context.requestId);
if (options.cacheControl) {
response.headers.set("Cache-Control", options.cacheControl);
}
addCORSHeaders(response, options);
return response;
}
/**
* Handle request execution with audit logging
*/
async function executeWithAudit<T>(
handler: APIHandler<T>,
context: APIContext,
validatedData: unknown,
validatedQuery: unknown,
request: NextRequest,
options: APIHandlerOptions
): Promise<T> {
const result = await handler(context, validatedData, validatedQuery);
if (options.auditLog) {
await logAPIAccess(context, "success", request.url);
}
return result;
}
/** /**
* Main API handler factory * Main API handler factory
*/ */
@ -291,64 +369,32 @@ export function createAPIHandler<T = unknown>(
let context: APIContext | undefined; let context: APIContext | undefined;
try { try {
// 1. Create request context
context = await createAPIContext(request); context = await createAPIContext(request);
// 2. Apply rate limiting
if (options.rateLimit) { if (options.rateLimit) {
await applyRateLimit(context, options.rateLimit); await applyRateLimit(context, options.rateLimit);
} }
// 3. Validate authentication await processAuthAndAuthz(context, options);
if (options.requireAuth) {
await validateAuthentication(context);
}
// 4. Validate authorization const { validatedData, validatedQuery } = await processValidation(
if (options.requiredRole || options.requirePlatformAccess) { request,
await validateAuthorization(context, options); options
}
// 5. Validate input
let validatedData;
if (options.validateInput && request.method !== "GET") {
validatedData = await validateInput(request, options.validateInput);
}
// 6. Validate query parameters
let validatedQuery;
if (options.validateQuery) {
validatedQuery = validateQuery(request, options.validateQuery);
}
// 7. Execute handler
const result = await handler(context, validatedData, validatedQuery);
// 8. Audit logging
if (options.auditLog) {
await logAPIAccess(context, "success", request.url);
}
// 9. Create response
const response = NextResponse.json(
createSuccessResponse(result, { requestId: context.requestId })
); );
// 10. Add headers const result = await executeWithAudit(
response.headers.set("X-Request-ID", context.requestId); handler,
context,
validatedData,
validatedQuery,
request,
options
);
if (options.cacheControl) { return createAPIResponse(result, context, options);
response.headers.set("Cache-Control", options.cacheControl);
}
addCORSHeaders(response, options);
return response;
} catch (error) { } catch (error) {
// Handle errors consistently
const requestId = context?.requestId || crypto.randomUUID(); const requestId = context?.requestId || crypto.randomUUID();
// Log failed requests
if (options.auditLog && context) { if (options.auditLog && context) {
await logAPIAccess(context, "error", request.url, error as Error); await logAPIAccess(context, "error", request.url, error as Error);
} }

View File

@ -137,7 +137,7 @@ export function stopOptimizedBatchScheduler(): void {
{ task: retryFailedTask, name: "retryFailedTask" }, { task: retryFailedTask, name: "retryFailedTask" },
]; ];
for (const { task, name } of tasks) { for (const { task } of tasks) {
if (task) { if (task) {
task.stop(); task.stop();
task.destroy(); task.destroy();

View File

@ -169,6 +169,10 @@ const ConfigSchema = z.object({
export type AppConfig = z.infer<typeof ConfigSchema>; export type AppConfig = z.infer<typeof ConfigSchema>;
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
/** /**
* Configuration provider class * Configuration provider class
*/ */
@ -230,8 +234,8 @@ class ConfigProvider {
/** /**
* Get environment-specific configuration * Get environment-specific configuration
*/ */
forEnvironment(env: Environment): Partial<AppConfig> { forEnvironment(env: Environment): DeepPartial<AppConfig> {
const overrides: Record<Environment, any> = { const overrides: Record<Environment, DeepPartial<AppConfig>> = {
development: { development: {
app: { app: {
logLevel: "debug", logLevel: "debug",
@ -291,28 +295,31 @@ class ConfigProvider {
} }
/** /**
* Extract configuration from environment variables * Extract app configuration from environment
*/ */
private extractFromEnvironment(): Partial<AppConfig> { private extractAppConfig(env: NodeJS.ProcessEnv, environment: Environment) {
const env = process.env;
const environment = (env.NODE_ENV as Environment) || "development";
return { return {
app: {
name: env.APP_NAME || "LiveDash", name: env.APP_NAME || "LiveDash",
version: env.APP_VERSION || "1.0.0", version: env.APP_VERSION || "1.0.0",
environment, environment,
baseUrl: env.NEXTAUTH_URL || "http://localhost:3000", baseUrl: env.NEXTAUTH_URL || "http://localhost:3000",
port: Number.parseInt(env.PORT || "3000", 10), port: Number.parseInt(env.PORT || "3000", 10),
logLevel: (env.LOG_LEVEL as any) || "info", logLevel:
(env.LOG_LEVEL as "debug" | "info" | "warn" | "error") || "info",
features: { features: {
enableMetrics: env.ENABLE_METRICS !== "false", enableMetrics: env.ENABLE_METRICS !== "false",
enableAnalytics: env.ENABLE_ANALYTICS !== "false", enableAnalytics: env.ENABLE_ANALYTICS !== "false",
enableCaching: env.ENABLE_CACHING !== "false", enableCaching: env.ENABLE_CACHING !== "false",
enableCompression: env.ENABLE_COMPRESSION !== "false", enableCompression: env.ENABLE_COMPRESSION !== "false",
}, },
}, };
database: { }
/**
* Extract database configuration from environment
*/
private extractDatabaseConfig(env: NodeJS.ProcessEnv) {
return {
url: env.DATABASE_URL || "", url: env.DATABASE_URL || "",
directUrl: env.DATABASE_URL_DIRECT, directUrl: env.DATABASE_URL_DIRECT,
maxConnections: Number.parseInt(env.DB_MAX_CONNECTIONS || "10", 10), maxConnections: Number.parseInt(env.DB_MAX_CONNECTIONS || "10", 10),
@ -323,8 +330,14 @@ class ConfigProvider {
queryTimeout: Number.parseInt(env.DB_QUERY_TIMEOUT || "60000", 10), queryTimeout: Number.parseInt(env.DB_QUERY_TIMEOUT || "60000", 10),
retryAttempts: Number.parseInt(env.DB_RETRY_ATTEMPTS || "3", 10), retryAttempts: Number.parseInt(env.DB_RETRY_ATTEMPTS || "3", 10),
retryDelay: Number.parseInt(env.DB_RETRY_DELAY || "1000", 10), retryDelay: Number.parseInt(env.DB_RETRY_DELAY || "1000", 10),
}, };
auth: { }
/**
* Extract auth configuration from environment
*/
private extractAuthConfig(env: NodeJS.ProcessEnv) {
return {
secret: env.NEXTAUTH_SECRET || "", secret: env.NEXTAUTH_SECRET || "",
url: env.NEXTAUTH_URL || "http://localhost:3000", url: env.NEXTAUTH_URL || "http://localhost:3000",
sessionMaxAge: Number.parseInt(env.AUTH_SESSION_MAX_AGE || "86400", 10), sessionMaxAge: Number.parseInt(env.AUTH_SESSION_MAX_AGE || "86400", 10),
@ -333,8 +346,14 @@ class ConfigProvider {
github: env.AUTH_GITHUB_ENABLED === "true", github: env.AUTH_GITHUB_ENABLED === "true",
google: env.AUTH_GOOGLE_ENABLED === "true", google: env.AUTH_GOOGLE_ENABLED === "true",
}, },
}, };
security: { }
/**
* Extract security configuration from environment
*/
private extractSecurityConfig(env: NodeJS.ProcessEnv) {
return {
csp: { csp: {
enabled: env.CSP_ENABLED !== "false", enabled: env.CSP_ENABLED !== "false",
reportUri: env.CSP_REPORT_URI, reportUri: env.CSP_REPORT_URI,
@ -347,18 +366,21 @@ class ConfigProvider {
rateLimit: { rateLimit: {
enabled: env.RATE_LIMIT_ENABLED !== "false", enabled: env.RATE_LIMIT_ENABLED !== "false",
windowMs: Number.parseInt(env.RATE_LIMIT_WINDOW_MS || "900000", 10), windowMs: Number.parseInt(env.RATE_LIMIT_WINDOW_MS || "900000", 10),
maxRequests: Number.parseInt( maxRequests: Number.parseInt(env.RATE_LIMIT_MAX_REQUESTS || "100", 10),
env.RATE_LIMIT_MAX_REQUESTS || "100",
10
),
}, },
audit: { audit: {
enabled: env.AUDIT_ENABLED !== "false", enabled: env.AUDIT_ENABLED !== "false",
retentionDays: Number.parseInt(env.AUDIT_RETENTION_DAYS || "90", 10), retentionDays: Number.parseInt(env.AUDIT_RETENTION_DAYS || "90", 10),
bufferSize: Number.parseInt(env.AUDIT_BUFFER_SIZE || "1000", 10), bufferSize: Number.parseInt(env.AUDIT_BUFFER_SIZE || "1000", 10),
}, },
}, };
openai: { }
/**
* Extract OpenAI configuration from environment
*/
private extractOpenAIConfig(env: NodeJS.ProcessEnv) {
return {
apiKey: env.OPENAI_API_KEY || "", apiKey: env.OPENAI_API_KEY || "",
organization: env.OPENAI_ORGANIZATION, organization: env.OPENAI_ORGANIZATION,
mockMode: env.OPENAI_MOCK_MODE === "true", mockMode: env.OPENAI_MOCK_MODE === "true",
@ -380,8 +402,14 @@ class ConfigProvider {
10 10
), ),
}, },
}, };
scheduler: { }
/**
* Extract scheduler configuration from environment
*/
private extractSchedulerConfig(env: NodeJS.ProcessEnv) {
return {
enabled: env.SCHEDULER_ENABLED !== "false", enabled: env.SCHEDULER_ENABLED !== "false",
csvImport: { csvImport: {
enabled: env.CSV_IMPORT_SCHEDULER_ENABLED !== "false", enabled: env.CSV_IMPORT_SCHEDULER_ENABLED !== "false",
@ -405,8 +433,14 @@ class ConfigProvider {
statusInterval: env.BATCH_STATUS_INTERVAL || "*/2 * * * *", statusInterval: env.BATCH_STATUS_INTERVAL || "*/2 * * * *",
resultInterval: env.BATCH_RESULT_INTERVAL || "*/1 * * * *", resultInterval: env.BATCH_RESULT_INTERVAL || "*/1 * * * *",
}, },
}, };
email: { }
/**
* Extract email configuration from environment
*/
private extractEmailConfig(env: NodeJS.ProcessEnv) {
return {
enabled: env.EMAIL_ENABLED === "true", enabled: env.EMAIL_ENABLED === "true",
smtp: { smtp: {
host: env.SMTP_HOST, host: env.SMTP_HOST,
@ -418,13 +452,29 @@ class ConfigProvider {
from: env.EMAIL_FROM || "noreply@livedash.com", from: env.EMAIL_FROM || "noreply@livedash.com",
templates: { templates: {
passwordReset: env.EMAIL_TEMPLATE_PASSWORD_RESET || "password-reset", passwordReset: env.EMAIL_TEMPLATE_PASSWORD_RESET || "password-reset",
userInvitation: userInvitation: env.EMAIL_TEMPLATE_USER_INVITATION || "user-invitation",
env.EMAIL_TEMPLATE_USER_INVITATION || "user-invitation",
},
}, },
}; };
} }
/**
* Extract configuration from environment variables
*/
private extractFromEnvironment(): Partial<AppConfig> {
const env = process.env;
const environment = (env.NODE_ENV as Environment) || "development";
return {
app: this.extractAppConfig(env, environment),
database: this.extractDatabaseConfig(env),
auth: this.extractAuthConfig(env),
security: this.extractSecurityConfig(env),
openai: this.extractOpenAIConfig(env),
scheduler: this.extractSchedulerConfig(env),
email: this.extractEmailConfig(env),
};
}
/** /**
* Log configuration status without sensitive information * Log configuration status without sensitive information
*/ */

View File

@ -191,7 +191,7 @@ export const DynamicAuditLogsPanel = createDynamicComponent(
// React wrapper for React.lazy with Suspense // React wrapper for React.lazy with Suspense
export function createLazyComponent< export function createLazyComponent<
T extends Record<string, any> = Record<string, any>, T extends Record<string, unknown> = Record<string, unknown>,
>( >(
importFunc: () => Promise<{ default: ComponentType<T> }>, importFunc: () => Promise<{ default: ComponentType<T> }>,
fallback: ComponentType = LoadingSpinner fallback: ComponentType = LoadingSpinner

View File

@ -15,6 +15,26 @@ import {
type MockResponseType, type MockResponseType,
} from "./openai-responses"; } from "./openai-responses";
interface ChatCompletionParams {
model: string;
messages: Array<{ role: string; content: string }>;
temperature?: number;
max_tokens?: number;
[key: string]: unknown;
}
interface BatchCreateParams {
input_file_id: string;
endpoint: string;
completion_window: string;
metadata?: Record<string, unknown>;
}
interface FileCreateParams {
file: string; // File content as string for mock purposes
purpose: string;
}
interface MockOpenAIConfig { interface MockOpenAIConfig {
enabled: boolean; enabled: boolean;
baseDelay: number; // Base delay in ms to simulate API latency baseDelay: number; // Base delay in ms to simulate API latency
@ -115,12 +135,9 @@ class OpenAIMockServer {
/** /**
* Mock chat completions endpoint * Mock chat completions endpoint
*/ */
async mockChatCompletion(request: { async mockChatCompletion(
model: string; request: ChatCompletionParams
messages: Array<{ role: string; content: string }>; ): Promise<MockChatCompletion> {
temperature?: number;
max_tokens?: number;
}): Promise<MockChatCompletion> {
this.requestCount++; this.requestCount++;
await this.simulateDelay(); await this.simulateDelay();
@ -172,12 +189,9 @@ class OpenAIMockServer {
/** /**
* Mock batch creation endpoint * Mock batch creation endpoint
*/ */
async mockCreateBatch(request: { async mockCreateBatch(
input_file_id: string; request: BatchCreateParams
endpoint: string; ): Promise<MockBatchResponse> {
completion_window: string;
metadata?: Record<string, string>;
}): Promise<MockBatchResponse> {
await this.simulateDelay(); await this.simulateDelay();
if (this.shouldSimulateError()) { if (this.shouldSimulateError()) {
@ -214,10 +228,7 @@ class OpenAIMockServer {
/** /**
* Mock file upload endpoint * Mock file upload endpoint
*/ */
async mockUploadFile(request: { async mockUploadFile(request: FileCreateParams): Promise<{
file: string; // File content
purpose: string;
}): Promise<{
id: string; id: string;
object: string; object: string;
purpose: string; purpose: string;
@ -364,23 +375,42 @@ export const openAIMock = new OpenAIMockServer();
/** /**
* Drop-in replacement for OpenAI client that uses mocks when enabled * Drop-in replacement for OpenAI client that uses mocks when enabled
*/ */
export class MockOpenAIClient { interface OpenAIClient {
private realClient: unknown; chat: {
completions: {
create: (params: ChatCompletionParams) => Promise<MockChatCompletion>;
};
};
batches: {
create: (params: BatchCreateParams) => Promise<MockBatchResponse>;
retrieve: (batchId: string) => Promise<MockBatchResponse>;
};
files: {
create: (params: FileCreateParams) => Promise<{
id: string;
object: string;
purpose: string;
filename: string;
}>;
content: (fileId: string) => Promise<string>;
};
}
constructor(realClient: unknown) { export class MockOpenAIClient {
private realClient: OpenAIClient;
constructor(realClient: OpenAIClient) {
this.realClient = realClient; this.realClient = realClient;
} }
get chat() { get chat() {
return { return {
completions: { completions: {
create: async (params: any) => { create: async (params: ChatCompletionParams) => {
if (openAIMock.isEnabled()) { if (openAIMock.isEnabled()) {
return openAIMock.mockChatCompletion(params as any); return openAIMock.mockChatCompletion(params);
} }
return (this.realClient as any).chat.completions.create( return this.realClient.chat.completions.create(params);
params as any
);
}, },
}, },
}; };
@ -388,34 +418,34 @@ export class MockOpenAIClient {
get batches() { get batches() {
return { return {
create: async (params: any) => { create: async (params: BatchCreateParams) => {
if (openAIMock.isEnabled()) { if (openAIMock.isEnabled()) {
return openAIMock.mockCreateBatch(params as any); return openAIMock.mockCreateBatch(params);
} }
return (this.realClient as any).batches.create(params as any); return this.realClient.batches.create(params);
}, },
retrieve: async (batchId: string) => { retrieve: async (batchId: string) => {
if (openAIMock.isEnabled()) { if (openAIMock.isEnabled()) {
return openAIMock.mockGetBatch(batchId); return openAIMock.mockGetBatch(batchId);
} }
return (this.realClient as any).batches.retrieve(batchId); return this.realClient.batches.retrieve(batchId);
}, },
}; };
} }
get files() { get files() {
return { return {
create: async (params: any) => { create: async (params: FileCreateParams) => {
if (openAIMock.isEnabled()) { if (openAIMock.isEnabled()) {
return openAIMock.mockUploadFile(params); return openAIMock.mockUploadFile(params);
} }
return (this.realClient as any).files.create(params); return this.realClient.files.create(params);
}, },
content: async (fileId: string) => { content: async (fileId: string) => {
if (openAIMock.isEnabled()) { if (openAIMock.isEnabled()) {
return openAIMock.mockGetFileContent(fileId); return openAIMock.mockGetFileContent(fileId);
} }
return (this.realClient as any).files.content(fileId); return this.realClient.files.content(fileId);
}, },
}; };
} }

View File

@ -181,7 +181,8 @@ class PerformanceMonitor {
// Placeholder for analytics integration // Placeholder for analytics integration
// You could send this to Google Analytics, Vercel Analytics, etc. // You could send this to Google Analytics, Vercel Analytics, etc.
if (typeof window !== "undefined" && "gtag" in window) { if (typeof window !== "undefined" && "gtag" in window) {
(window as any).gtag("event", "core_web_vital", { const gtag = (window as { gtag?: (...args: unknown[]) => void }).gtag;
gtag?.("event", "core_web_vital", {
name: metricName, name: metricName,
value: Math.round(value), value: Math.round(value),
metric_rating: this.getRating(metricName, value), metric_rating: this.getRating(metricName, value),

View File

@ -169,7 +169,7 @@ export class PerformanceCache<K extends {} = string, V = unknown> {
/** /**
* Memoize a function with caching * Memoize a function with caching
*/ */
memoize<Args extends any[], Return extends V>( memoize<Args extends unknown[], Return extends V>(
fn: (...args: Args) => Promise<Return> | Return, fn: (...args: Args) => Promise<Return> | Return,
keyGenerator?: (...args: Args) => K, keyGenerator?: (...args: Args) => K,
ttl?: number ttl?: number
@ -421,7 +421,7 @@ export class CacheUtils {
/** /**
* Cache the result of an async function * Cache the result of an async function
*/ */
static cached<T extends any[], R>( static cached<T extends unknown[], R>(
cacheName: string, cacheName: string,
fn: (...args: T) => Promise<R>, fn: (...args: T) => Promise<R>,
options: CacheOptions & { options: CacheOptions & {

View File

@ -155,12 +155,12 @@ export class RequestDeduplicator {
}> = []; }> = [];
// Create the main promise // Create the main promise
const promise = new Promise<T>(async (resolve, reject) => { const promise = new Promise<T>((resolve, reject) => {
resolvers.push({ resolve, reject }); resolvers.push({ resolve, reject });
try { // Execute the async function
const result = await fn(); fn()
.then((result) => {
// Cache the result // Cache the result
if (options.ttl && options.ttl > 0) { if (options.ttl && options.ttl > 0) {
this.results.set(key, { this.results.set(key, {
@ -172,17 +172,19 @@ export class RequestDeduplicator {
// Resolve all waiting promises // Resolve all waiting promises
resolvers.forEach(({ resolve: res }) => res(result)); resolvers.forEach(({ resolve: res }) => res(result));
} catch (error) { })
.catch((error) => {
this.stats.errors++; this.stats.errors++;
// Reject all waiting promises // Reject all waiting promises
const errorToReject = const errorToReject =
error instanceof Error ? error : new Error(String(error)); error instanceof Error ? error : new Error(String(error));
resolvers.forEach(({ reject: rej }) => rej(errorToReject)); resolvers.forEach(({ reject: rej }) => rej(errorToReject));
} finally { })
.finally(() => {
// Clean up pending request // Clean up pending request
this.pendingRequests.delete(key); this.pendingRequests.delete(key);
} });
}); });
// Set up timeout if specified // Set up timeout if specified

View File

@ -167,7 +167,11 @@ function startMonitoringIfEnabled(enabled?: boolean): void {
/** /**
* Helper function to record request metrics if enabled * Helper function to record request metrics if enabled
*/ */
function recordRequestIfEnabled(timer: ReturnType<typeof PerformanceUtils.createTimer>, isError: boolean, enabled?: boolean): void { function recordRequestIfEnabled(
timer: ReturnType<typeof PerformanceUtils.createTimer>,
isError: boolean,
enabled?: boolean
): void {
if (enabled) { if (enabled) {
performanceMonitor.recordRequest(timer.end(), isError); performanceMonitor.recordRequest(timer.end(), isError);
} }
@ -223,11 +227,9 @@ async function executeWithCacheOrDeduplication(
opts.deduplication?.deduplicatorName as keyof typeof deduplicators opts.deduplication?.deduplicatorName as keyof typeof deduplicators
] || deduplicators.api; ] || deduplicators.api;
return deduplicator.execute( return deduplicator.execute(cacheKey, () => originalHandler(req), {
cacheKey, ttl: opts.deduplication?.ttl,
() => originalHandler(req), });
{ ttl: opts.deduplication?.ttl }
);
} }
/** /**
@ -247,7 +249,12 @@ export function enhanceAPIRoute(
try { try {
startMonitoringIfEnabled(opts.monitoring?.enabled); startMonitoringIfEnabled(opts.monitoring?.enabled);
const response = await executeRequestWithOptimizations(req, opts, routeName, originalHandler); const response = await executeRequestWithOptimizations(
req,
opts,
routeName,
originalHandler
);
recordRequestIfEnabled(timer, false, opts.monitoring?.recordRequests); recordRequestIfEnabled(timer, false, opts.monitoring?.recordRequests);
return response; return response;
} catch (error) { } catch (error) {
@ -263,8 +270,10 @@ export function enhanceAPIRoute(
export function PerformanceEnhanced( export function PerformanceEnhanced(
options: PerformanceIntegrationOptions = {} options: PerformanceIntegrationOptions = {}
) { ) {
return <T extends new (...args: any[]) => {}>(constructor: T) => // biome-ignore lint/suspicious/noExplicitAny: Required for mixin class pattern - TypeScript requires any[] for constructor parameters in mixins
class extends constructor { return <T extends new (...args: any[]) => {}>(Constructor: T) =>
class extends Constructor {
// biome-ignore lint/suspicious/noExplicitAny: Required for mixin class pattern - TypeScript requires any[] for constructor parameters in mixins
constructor(...args: any[]) { constructor(...args: any[]) {
super(...args); super(...args);
@ -279,7 +288,7 @@ export function PerformanceEnhanced(
if (typeof originalMethod === "function") { if (typeof originalMethod === "function") {
(this as Record<string, unknown>)[methodName] = (this as Record<string, unknown>)[methodName] =
enhanceServiceMethod( enhanceServiceMethod(
`${constructor.name}.${methodName}`, `${Constructor.name}.${methodName}`,
originalMethod.bind(this), originalMethod.bind(this),
options options
); );

View File

@ -777,9 +777,8 @@ export class PerformanceUtils {
} }
descriptor.value = async function (...args: unknown[]) { descriptor.value = async function (...args: unknown[]) {
const { result } = await PerformanceUtils.measureAsync( const { result } = await PerformanceUtils.measureAsync(metricName, () =>
metricName, originalMethod.apply(this, args)
() => originalMethod.apply(this, args)
); );
return result; return result;
}; };

View File

@ -8,14 +8,18 @@
"build:analyze": "ANALYZE=true next build", "build:analyze": "ANALYZE=true next build",
"dev": "pnpm exec tsx server.ts", "dev": "pnpm exec tsx server.ts",
"dev:next-only": "next dev --turbopack", "dev:next-only": "next dev --turbopack",
"format": "npx prettier --write .", "format": "pnpm format:prettier && pnpm format:biome",
"format:check": "npx prettier --check .", "format:check": "pnpm format:check-prettier && pnpm format:check-biome",
"format:biome": "biome format --write",
"format:check-biome": "biome format",
"format:prettier": "npx prettier --write .",
"format:check-prettier": "npx prettier --check .",
"lint": "next lint", "lint": "next lint",
"lint:fix": "npx eslint --fix", "lint:fix": "npx eslint --fix",
"biome:check": "biome check .", "biome:check": "biome check",
"biome:fix": "biome check --write .", "biome:fix": "biome check --write",
"biome:format": "biome format --write .", "biome:format": "biome format --write",
"biome:lint": "biome lint .", "biome:lint": "biome lint",
"prisma:generate": "prisma generate", "prisma:generate": "prisma generate",
"prisma:migrate": "prisma migrate dev", "prisma:migrate": "prisma migrate dev",
"prisma:seed": "pnpm exec tsx prisma/seed.ts", "prisma:seed": "pnpm exec tsx prisma/seed.ts",
@ -37,6 +41,8 @@
"test:csp:full": "pnpm test:csp && pnpm test:csp:validate && pnpm test:vitest tests/unit/enhanced-csp.test.ts tests/integration/csp-middleware.test.ts tests/integration/csp-report-endpoint.test.ts", "test:csp:full": "pnpm test:csp && pnpm test:csp:validate && pnpm test:vitest tests/unit/enhanced-csp.test.ts tests/integration/csp-middleware.test.ts tests/integration/csp-report-endpoint.test.ts",
"lint:md": "markdownlint-cli2 \"**/*.md\" \"!.trunk/**\" \"!.venv/**\" \"!node_modules/**\"", "lint:md": "markdownlint-cli2 \"**/*.md\" \"!.trunk/**\" \"!.venv/**\" \"!node_modules/**\"",
"lint:md:fix": "markdownlint-cli2 --fix \"**/*.md\" \"!.trunk/**\" \"!.venv/**\" \"!node_modules/**\"", "lint:md:fix": "markdownlint-cli2 --fix \"**/*.md\" \"!.trunk/**\" \"!.venv/**\" \"!node_modules/**\"",
"lint:ci-warning": "biome ci --diagnostic-level=warn",
"lint:ci-error": "biome ci --diagnostic-level=error",
"migration:backup": "pnpm exec tsx scripts/migration/backup-database.ts full", "migration:backup": "pnpm exec tsx scripts/migration/backup-database.ts full",
"migration:backup:schema": "pnpm exec tsx scripts/migration/backup-database.ts schema", "migration:backup:schema": "pnpm exec tsx scripts/migration/backup-database.ts schema",
"migration:backup:data": "pnpm exec tsx scripts/migration/backup-database.ts data", "migration:backup:data": "pnpm exec tsx scripts/migration/backup-database.ts data",

10001
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -450,7 +450,7 @@ Examples:
`); `);
process.exit(1); process.exit(1);
} }
} };
runCommand() runCommand()
.then((result) => { .then((result) => {

View File

@ -517,7 +517,7 @@ if (import.meta.url === `file://${process.argv[1]}`) {
} }
return result; return result;
} };
runTests() runTests()
.then((result) => { .then((result) => {