Files
agentic-dev/apps/web-next/server.ts
Danijel Martinek 6a0ac63bb9 fix(realtime): CI gate fixes surfaced by Phase 11 smoke tests
- no-direct-socket-io: extend allowlist to cover apps/*/src/**/*.test.ts so
  the realtime-ping e2e integration test can import socket.io/socket.io-client
  directly; add two valid test cases to keep the rule's own test suite green
- tsconfig.json (root): add root-level tsconfig with experimentalDecorators +
  emitDecoratorMetadata and no "include" so tsx 4.21.0's createFilesMatcher
  resolves decorator config for all workspace packages, not just web-next's
  own source tree
- web-next dev script: pass TSX_TSCONFIG_PATH=../../tsconfig.json so the
  custom Node server uses the root tsconfig for all modules it loads
- next.config.mjs: add @repo/core-events and @repo/core-realtime to
  transpilePackages so Next.js webpack can resolve their workspace source files
- server.ts: replace static authContainer import with a dynamic import inside
  IRealtimeAuthenticator.authenticate so Inversify decorators are applied only
  after bindAll() has already populated the container

All CI gates pass: lint (0 errors), typecheck, 20 tests (incl. realtime-ping
e2e), boundaries (0 issues).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 01:32:43 +02:00

67 lines
2.2 KiB
TypeScript

// apps/web-next/server.ts
// SERVER-ONLY entry. Boots Next.js + Socket.IO on the same Node http server.
import "reflect-metadata";
import { createServer } from "node:http";
import next from "next";
import { Server as IOServer } from "socket.io";
import {
RealtimeHandlerRegistry,
SocketIORealtimeBroadcaster,
SocketIORealtimeServer,
type IRealtimeAuthenticator,
} from "@repo/core-realtime";
import { SESSION_COOKIE } from "@repo/auth";
import { AUTH_SYMBOLS } from "@repo/auth/di/symbols";
import { bindAll } from "./src/server/bind-production.js";
const dev = process.env.NODE_ENV !== "production";
const port = Number(process.env.PORT ?? 3000);
const app = next({ dev });
const handle = app.getRequestHandler();
await app.prepare();
const httpServer = createServer((req, res) => handle(req, res));
const io = new IOServer(httpServer);
const broadcaster = new SocketIORealtimeBroadcaster(io);
const registry = new RealtimeHandlerRegistry();
await bindAll({ realtime: broadcaster, realtimeRegistry: registry });
const authenticator: IRealtimeAuthenticator = {
authenticate: async ({ cookies }) => {
const sessionId = cookies[SESSION_COOKIE];
if (!sessionId) return null;
// Lazy-import the auth container after bindAll() has already populated it.
// Dynamic import defers tsx's module transformation to runtime, which lets
// reflect-metadata and the DI decorators resolve correctly in this server
// entry point.
const { authContainer } = await import("@repo/auth/di/container");
const authService = authContainer.get<{
validateSession: (id: string) => Promise<{ user: { id: string }; session: unknown } | null>;
}>(AUTH_SYMBOLS.IAuthenticationService);
const result = await authService.validateSession(sessionId);
return result
? {
userId: result.user.id,
// Roles are not yet in the session shape; extend here when DB-backed roles ship.
roles: (result as unknown as { roles?: string[] }).roles ?? [],
}
: null;
},
};
const realtimeServer = new SocketIORealtimeServer({
httpServer,
io,
authenticator,
registry,
});
await realtimeServer.start();
httpServer.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`);
});