# Server TypeScript Conversion Plan (With Compilation)

## Overview
Convert the Node.js server from JavaScript to TypeScript with a build step for production. This approach uses tsx for development (fast iteration) but compiles to optimized JavaScript for production (zero runtime overhead).

## Key Differences from TSX-Only Approach
- **Development**: Uses `tsx` for fast iteration and hot reload
- **Production**: Compiles to JavaScript, runs pure Node.js (no TypeScript overhead)
- **Build Process**: Adds a server compilation step
- **Docker**: Smaller production image (no tsx runtime needed)

## Current State Analysis

### Files to Convert
1. **`server.js`** (57 lines) - Main server entry point
2. **`server/socket.js`** (89 lines) - Socket.IO server

### Existing TypeScript Configuration
- **`tsconfig.node.json`** already includes `server/**/*.ts`
- Module resolution is set to "bundler" with ES2022 target
- Path aliases configured: `~/` for app, `~/database/` for database

## Implementation Steps

### Step 1: Install Required Dependencies

```bash
# Development dependencies
npm install --save-dev tsx @types/express @types/morgan @types/compression esbuild

# Why these packages?
# - tsx: Fast TypeScript execution for development
# - @types/*: TypeScript definitions
# - esbuild: Ultra-fast TypeScript compiler for production builds
```

### Step 2: Create Server TypeScript Configuration

**File: `tsconfig.server.json`**

```json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "composite": false,
    "noEmit": false,
    "outDir": "./dist/server",
    "rootDir": ".",
    "target": "ES2022",
    "module": "ES2022",
    "moduleResolution": "node",
    "lib": ["ES2022"],
    "types": ["node"],
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "allowSyntheticDefaultImports": true,
    "baseUrl": ".",
    "paths": {
      "~/*": ["./app/*"],
      "~/database/*": ["./database/*"]
    }
  },
  "include": [
    "server.ts",
    "server/**/*.ts",
    "database/**/*.ts",
    "app/database/**/*.ts"
  ],
  "exclude": [
    "node_modules",
    "build",
    "dist",
    "**/*.test.ts",
    "**/*.spec.ts"
  ]
}
```

### Step 3: Create Build Script for Server

**File: `scripts/build-server.mjs`**

```javascript
import * as esbuild from 'esbuild';
import { nodeExternalsPlugin } from 'esbuild-node-externals';
import path from 'path';
import { fileURLToPath } from 'url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

async function build() {
  try {
    await esbuild.build({
      entryPoints: ['./server.ts'],
      bundle: true,
      platform: 'node',
      target: 'node20',
      format: 'esm',
      outfile: 'dist/server.js',
      sourcemap: true,
      minify: process.env.NODE_ENV === 'production',
      
      // Handle TypeScript path aliases
      alias: {
        '~': path.resolve(__dirname, '../app'),
        '~/database': path.resolve(__dirname, '../database'),
      },
      
      // External dependencies (don't bundle node_modules)
      external: [
        'express',
        'compression',
        'morgan',
        'vite',
        'socket.io',
        'drizzle-orm',
        'postgres',
        '@clerk/*',
        './build/server/index.js', // Production build reference
      ],
      
      // Keep import.meta.url working
      banner: {
        js: `
import { createRequire } from 'module';
import { fileURLToPath } from 'url';
import path from 'path';
const require = createRequire(import.meta.url);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
        `.trim()
      }
    });
    
    console.log('✅ Server build complete');
  } catch (error) {
    console.error('❌ Build failed:', error);
    process.exit(1);
  }
}

build();
```

### Step 4: Convert server.js to server.ts

**File: `server.ts`**

```typescript
import compression from "compression";
import express, { Express, Request, Response, NextFunction } from "express";
import morgan from "morgan";
import { createServer } from "http";
import type { ViteDevServer } from "vite";

// Configuration
const BUILD_PATH = "./build/server/index.js";
const DEVELOPMENT = process.env.NODE_ENV === "development";
const PORT = Number.parseInt(process.env.PORT || "3000", 10);

async function createAppServer(): Promise<void> {
  const app: Express = express();
  
  app.use(compression());
  app.disable("x-powered-by");

  if (DEVELOPMENT) {
    console.log("Starting development server");
    
    // Dynamic import Vite only in development
    const { createServer: createViteServer } = await import("vite");
    
    const viteDevServer: ViteDevServer = await createViteServer({
      server: { middlewareMode: true },
    });
    
    app.use(viteDevServer.middlewares);
    
    app.use(async (req: Request, res: Response, next: NextFunction) => {
      try {
        const source = await viteDevServer.ssrLoadModule("./server/app.ts");
        return await source.app(req, res, next);
      } catch (error) {
        if (error instanceof Error) {
          viteDevServer.ssrFixStacktrace(error);
        }
        next(error);
      }
    });
  } else {
    console.log("Starting production server");
    
    app.use(
      "/assets",
      express.static("build/client/assets", { 
        immutable: true, 
        maxAge: "1y" 
      })
    );
    
    app.use(morgan("tiny"));
    app.use(express.static("build/client", { maxAge: "1h" }));
    
    // Import the built React Router app
    const { app: productionApp } = await import(BUILD_PATH);
    app.use(productionApp);
  }

  // Create HTTP server
  const httpServer = createServer(app);

  // Initialize Socket.IO - will be compiled to .js in production
  const { initializeSocketIO } = await import("./server/socket");
  initializeSocketIO(httpServer);

  // Start server
  httpServer.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
  });
}

// Start the server with error handling
createAppServer().catch((error) => {
  console.error("Failed to start server:", error);
  process.exit(1);
});
```

### Step 5: Convert server/socket.js to server/socket.ts

**File: `server/socket.ts`**

```typescript
import { Server as SocketIOServer, Socket } from "socket.io";
import type { Server as HTTPServer } from "http";

// Socket event types
interface ServerToClientEvents {
  "test-message": (data: {
    originalMessage: any;
    serverResponse: string;
    serverTimestamp: string;
    socketId: string;
  }) => void;
  "pick-made": (data: any) => void;
  "draft-started": (data: { seasonId: string; currentPickNumber: number }) => void;
  "draft-completed": () => void;
  "timer-update": (data: {
    seasonId: string;
    teamId: string;
    timeRemaining: number;
    currentPickNumber: number;
  }) => void;
}

interface ClientToServerEvents {
  "join-draft": (seasonId: string) => void;
  "leave-draft": (seasonId: string) => void;
  "test-event": (data: any) => void;
}

// Global type augmentation
declare global {
  var __socketIO: SocketIOServer | undefined;
}

let io: SocketIOServer<ClientToServerEvents, ServerToClientEvents> | null = null;

/**
 * Initialize Socket.IO server
 */
export function initializeSocketIO(httpServer: HTTPServer): SocketIOServer {
  if (io) {
    console.log("Socket.IO already initialized");
    return io;
  }

  // Create typed Socket.IO server
  io = new SocketIOServer<ClientToServerEvents, ServerToClientEvents>(httpServer, {
    cors: process.env.NODE_ENV === "production" && process.env.APP_URL
      ? {
          origin: process.env.APP_URL,
          credentials: true,
        }
      : undefined,
  });

  // Connection handling
  io.on("connection", (socket: Socket<ClientToServerEvents, ServerToClientEvents>) => {
    console.log("Client connected:", socket.id);

    socket.on("join-draft", (seasonId: string) => {
      if (!seasonId) {
        console.error("No seasonId provided for join-draft");
        return;
      }
      socket.join(`draft-${seasonId}`);
      console.log(`Socket ${socket.id} joined draft-${seasonId}`);
    });

    socket.on("leave-draft", (seasonId: string) => {
      if (!seasonId) return;
      socket.leave(`draft-${seasonId}`);
      console.log(`Socket ${socket.id} left draft-${seasonId}`);
    });

    socket.on("test-event", (data: any) => {
      console.log("📨 Received test-event from client:", socket.id, data);
      
      socket.emit("test-message", {
        originalMessage: data,
        serverResponse: "Hello from server!",
        serverTimestamp: new Date().toISOString(),
        socketId: socket.id,
      });
      
      console.log("✅ Sent test-message response to client:", socket.id);
    });

    socket.on("disconnect", () => {
      console.log("Client disconnected:", socket.id);
    });
  });

  // Store globally for route handlers
  global.__socketIO = io;

  console.log("Socket.IO initialized");
  return io;
}

/**
 * Get the Socket.IO server instance
 */
export function getSocketIO(): SocketIOServer {
  const instance = io || global.__socketIO;
  if (!instance) {
    throw new Error("Socket.IO not initialized. Call initializeSocketIO first.");
  }
  return instance;
}
```

### Step 6: Update package.json Scripts

```json
{
  "scripts": {
    "build": "npm run build:remix && npm run build:server",
    "build:remix": "react-router build",
    "build:server": "node scripts/build-server.mjs",
    "db:generate": "dotenv -- drizzle-kit generate",
    "db:migrate": "dotenv -- drizzle-kit migrate",
    "dev": "dotenv -- tsx watch server.ts",
    "start": "node dist/server.js",
    "start:production": "drizzle-kit migrate && node dist/server.js",
    "typecheck": "react-router typegen && tsc -b && tsc -p tsconfig.server.json --noEmit"
  }
}
```

**Key Changes:**
- `build`: Now builds both React Router app AND server
- `build:server`: Compiles TypeScript server to JavaScript
- `dev`: Uses `tsx watch` for development (fast, no compilation)
- `start`: Runs compiled JavaScript (no TypeScript overhead)
- `typecheck`: Also checks server TypeScript

### Step 7: Update Dockerfile (Optimized for Production)

```dockerfile
# Stage 1: Install all dependencies
FROM node:20-alpine AS dependencies
WORKDIR /app
COPY package*.json ./
RUN npm ci

# Stage 2: Build the application
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
COPY --from=dependencies /app/node_modules ./node_modules
COPY . .
# Build both React Router app and server
RUN npm run build

# Stage 3: Production dependencies only
FROM node:20-alpine AS prod-dependencies
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev

# Stage 4: Final production image (minimal)
FROM node:20-alpine
WORKDIR /app

# Copy only what's needed for production
COPY package*.json ./
COPY --from=prod-dependencies /app/node_modules ./node_modules
COPY --from=builder /app/build ./build
COPY --from=builder /app/dist ./dist
COPY ./drizzle ./drizzle
COPY ./drizzle.config.ts ./drizzle.config.ts

# No TypeScript files needed in production!
# The server runs pure JavaScript from dist/server.js

EXPOSE 3000
CMD ["npm", "run", "start:production"]
```

### Step 8: Create Timer System (With Proper Imports)

**File: `server/timer.ts`**

```typescript
import { database } from "~/database/context";
import * as schema from "~/database/schema";
import { eq, and, desc, asc, inArray, notInArray } from "drizzle-orm";
import { getSocketIO } from "./socket";

let timerInterval: NodeJS.Timeout | null = null;

export function startDraftTimerSystem(): void {
  if (timerInterval) {
    clearInterval(timerInterval);
  }

  timerInterval = setInterval(async () => {
    try {
      await updateDraftTimers();
    } catch (error) {
      console.error("[Timer] Error updating draft timers:", error);
    }
  }, 1000);
  
  console.log("[Timer] Draft timer system started");
}

async function updateDraftTimers(): Promise<void> {
  const db = database();
  const io = getSocketIO();
  
  // Get all active drafts
  const activeDrafts = await db.query.seasons.findMany({
    where: eq(schema.seasons.status, "draft"),
  });

  for (const season of activeDrafts) {
    if (season.draftPaused) continue;
    
    const currentPickNumber = season.currentPickNumber || 1;
    
    // Get draft slots
    const draftSlots = await db.query.draftSlots.findMany({
      where: eq(schema.draftSlots.seasonId, season.id),
      orderBy: schema.draftSlots.draftOrder,
    });
    
    // Calculate current team
    const totalTeams = draftSlots.length;
    const currentRound = Math.ceil(currentPickNumber / totalTeams);
    const isEvenRound = currentRound % 2 === 0;
    let pickInRound = ((currentPickNumber - 1) % totalTeams) + 1;
    
    if (isEvenRound) {
      pickInRound = totalTeams - pickInRound + 1;
    }
    
    const currentDraftSlot = draftSlots.find(
      (slot) => slot.draftOrder === pickInRound
    );
    
    if (!currentDraftSlot) continue;
    
    // Update timer
    const timer = await db.query.draftTimers.findFirst({
      where: and(
        eq(schema.draftTimers.seasonId, season.id),
        eq(schema.draftTimers.teamId, currentDraftSlot.teamId)
      ),
    });
    
    if (!timer) continue;
    
    const newTimeRemaining = Math.max(0, timer.timeRemaining - 1);
    
    // Update in database
    await db
      .update(schema.draftTimers)
      .set({
        timeRemaining: newTimeRemaining,
        updatedAt: new Date(),
      })
      .where(eq(schema.draftTimers.id, timer.id));
    
    // Broadcast update
    io.to(`draft-${season.id}`).emit("timer-update", {
      seasonId: season.id,
      teamId: currentDraftSlot.teamId,
      timeRemaining: newTimeRemaining,
      currentPickNumber,
    });
    
    // Trigger auto-pick if timer expired
    if (newTimeRemaining === 0 && timer.timeRemaining > 0) {
      console.log(`[Timer] Timer expired for team ${currentDraftSlot.teamId}`);
      // Auto-pick logic here...
    }
  }
}
```

### Step 9: Update .gitignore

Add these entries:
```
# Compiled server output
/dist
dist/

# Keep source TypeScript files
!server.ts
!server/**/*.ts
```

## Alternative: Use Vite to Build Server

If you prefer to use Vite for consistency, create:

**File: `vite.config.server.ts`**

```typescript
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';

export default defineConfig({
  build: {
    ssr: true,
    target: 'node20',
    outDir: 'dist',
    rollupOptions: {
      input: './server.ts',
      external: [
        /^node:/,
        'express',
        'compression',
        'morgan',
        'vite',
        'socket.io',
        'drizzle-orm',
        'postgres',
      ],
      output: {
        format: 'es',
      },
    },
  },
  plugins: [tsconfigPaths()],
  ssr: {
    noExternal: ['~/database', '~/app'],
  },
});
```

Then update build script:
```json
"build:server": "vite build --config vite.config.server.ts"
```

## Testing Plan

### 1. Development Testing
```bash
# Start with tsx (fast, no compilation)
npm run dev

# Verify:
- Server starts quickly
- Hot reload works
- Can import TypeScript modules
- Socket.IO connects
```

### 2. Build Testing
```bash
# Build server
npm run build:server

# Check output
ls -la dist/
# Should see: server.js, server.js.map

# Test compiled server
NODE_ENV=production npm start

# Verify:
- Runs pure JavaScript (no tsx)
- All features work
- No TypeScript overhead
```

### 3. Performance Comparison
```bash
# Measure startup time with tsx
time npm run dev

# Measure startup time with compiled JS
time npm start

# Compiled version should be faster
```

## Benefits of This Approach

1. **Development Speed**: tsx provides fast iteration in development
2. **Production Performance**: Zero TypeScript overhead in production
3. **Type Safety**: Full TypeScript benefits during development
4. **Smaller Docker Image**: No tsx or TypeScript in production container
5. **Faster Startup**: Compiled JavaScript starts faster than tsx
6. **Debugging**: Source maps available in both dev and production

## Migration Checklist

- [ ] Install dependencies (tsx, esbuild, types)
- [ ] Create tsconfig.server.json
- [ ] Create build-server.mjs script
- [ ] Convert server.js to server.ts
- [ ] Convert server/socket.js to server/socket.ts
- [ ] Update package.json scripts
- [ ] Test development mode with tsx
- [ ] Test build process
- [ ] Test production with compiled JS
- [ ] Update Dockerfile
- [ ] Update .gitignore
- [ ] Implement timer system
- [ ] Remove old .js files

## Performance Metrics

Expected improvements:
- **Startup time**: 30-50% faster (no TypeScript compilation)
- **Memory usage**: 20-30% less (no tsx runtime)
- **CPU usage**: Lower (no runtime transpilation)
- **Docker image size**: ~50MB smaller (no TypeScript dependencies)

## Rollback Plan

1. Keep original .js files until testing complete
2. Git commit before conversion
3. Can revert package.json to use .js files
4. Compiled output is separate from source
