@lithia-js/core 1.0.0-canary.2 → 1.0.0-canary.3

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.
@@ -1,371 +0,0 @@
1
- import {
2
- createServer,
3
- type IncomingMessage,
4
- type Server,
5
- type ServerResponse,
6
- } from "node:http";
7
- import {
8
- createServer as createHttpsServer,
9
- type Server as HttpsServer,
10
- } from "node:https";
11
- import type { Socket } from "node:net";
12
- import { Server as SocketIOServer } from "socket.io";
13
- import type { LithiaOptions } from "../config";
14
- import type { Lithia } from "../lithia";
15
- import { logger } from "../logger";
16
- import { EventProcessor } from "./event-processor";
17
- import { LithiaRequest } from "./request";
18
- import { RequestProcessor } from "./request-processor";
19
- import { LithiaResponse } from "./response";
20
-
21
- /**
22
- * Configuration used to create the HTTP server.
23
- *
24
- * This interface defines the settings required to initialize and run
25
- * the Lithia HTTP server, including network bindings and optional SSL.
26
- */
27
- export interface HttpServerConfig {
28
- /**
29
- * Port to listen on.
30
- *
31
- * @default 3000
32
- */
33
- port: number;
34
-
35
- /**
36
- * Hostname or IP address to bind the server to.
37
- *
38
- * @default "localhost"
39
- */
40
- host: string;
41
-
42
- /**
43
- * Optional SSL/TLS configuration for HTTPS.
44
- *
45
- * If provided, the server will use HTTPS instead of HTTP.
46
- */
47
- ssl?: {
48
- /** Path to the private key file or key content. */
49
- key: string;
50
- /** Path to the certificate file or certificate content. */
51
- cert: string;
52
- /** Optional passphrase for the private key. */
53
- passphrase?: string;
54
- };
55
- }
56
-
57
- /**
58
- * Lightweight HTTP server wrapper used by Lithia.
59
- *
60
- * This class manages the lifecycle of a Node.js HTTP/HTTPS server and
61
- * integrates it with Lithia's request processing pipeline and Socket.IO
62
- * event handling. It provides a clean abstraction over the underlying
63
- * server infrastructure.
64
- *
65
- * @remarks
66
- * The server supports both HTTP and HTTPS protocols, WebSocket connections
67
- * via Socket.IO, and graceful shutdown with connection tracking.
68
- *
69
- * @example
70
- * ```typescript
71
- * const config = { port: 3000, host: 'localhost' };
72
- * const server = new HttpServer(config, lithia);
73
- * await server.listen();
74
- * ```
75
- */
76
- export class HttpServer {
77
- /** The underlying Node.js HTTP or HTTPS server instance. */
78
- private server?: Server | HttpsServer;
79
-
80
- /** Socket.IO server instance for real-time event handling. */
81
- private io?: SocketIOServer;
82
-
83
- /** Server configuration (port, host, SSL). */
84
- private config: HttpServerConfig;
85
-
86
- /** Processor responsible for handling HTTP requests. */
87
- private requestProcessor: RequestProcessor;
88
-
89
- /** Processor responsible for handling Socket.IO events. */
90
- private eventProcessor: EventProcessor;
91
-
92
- /** Set of active socket connections for graceful shutdown tracking. */
93
- private sockets = new Set<Socket>();
94
-
95
- /**
96
- * Creates a new HTTP server instance.
97
- *
98
- * @param config - Server configuration including port, host, and optional SSL
99
- * @param lithia - The main Lithia application instance
100
- */
101
- constructor(
102
- config: HttpServerConfig,
103
- private lithia: Lithia,
104
- ) {
105
- this.config = config;
106
- this.requestProcessor = new RequestProcessor(lithia, this);
107
- this.eventProcessor = new EventProcessor(lithia);
108
- }
109
-
110
- get socketIO(): SocketIOServer | undefined {
111
- return this.io;
112
- }
113
-
114
- /**
115
- * Creates the HTTP request handler function.
116
- *
117
- * This handler processes all incoming HTTP requests, including the internal
118
- * `/_lithia` health check endpoint and regular application routes.
119
- *
120
- * @returns An async request handler compatible with Node.js http.Server
121
- * @private
122
- */
123
- private createRequestHandler() {
124
- return async (req: IncomingMessage, res: ServerResponse) => {
125
- try {
126
- // Handle /_lithia internal health check endpoint
127
- const url = req.url || "/";
128
- if (url === "/_lithia") {
129
- res.writeHead(200, { "Content-Type": "application/json" });
130
- res.end(JSON.stringify({ ok: true, ts: Date.now() }));
131
- return;
132
- }
133
-
134
- // Process request through the Lithia pipeline
135
- const lithiaReq = new LithiaRequest(req, this.lithia);
136
- const lithiaRes = new LithiaResponse(res);
137
-
138
- await this.requestProcessor.processRequest(lithiaReq, lithiaRes);
139
- } catch (err) {
140
- logger.error("HttpServer request handler error:", err);
141
- try {
142
- res.writeHead(500, { "Content-Type": "text/plain" });
143
- res.end("Internal Server Error");
144
- } catch (_) {
145
- // Ignore errors when attempting to send error response
146
- }
147
- }
148
- };
149
- }
150
-
151
- /**
152
- * Creates the underlying HTTP or HTTPS server instance.
153
- *
154
- * Chooses between HTTP and HTTPS based on the SSL configuration.
155
- * Sets up connection tracking for graceful shutdown.
156
- *
157
- * @returns The created server instance
158
- * @private
159
- */
160
- private createBaseServer(): Server | HttpsServer {
161
- const handler = this.createRequestHandler();
162
-
163
- const server = this.config.ssl
164
- ? createHttpsServer(this.config.ssl, handler)
165
- : createServer(handler);
166
-
167
- // Track connections for graceful shutdown
168
- server.on("connection", (socket: Socket) => {
169
- this.sockets.add(socket);
170
- socket.on("close", () => {
171
- this.sockets.delete(socket);
172
- });
173
- });
174
-
175
- return server;
176
- }
177
-
178
- /**
179
- * Initializes the Socket.IO server with CORS configuration.
180
- *
181
- * Creates a Socket.IO instance attached to the HTTP server and configures
182
- * it with CORS settings from the Lithia options.
183
- *
184
- * @param server - The HTTP/HTTPS server to attach Socket.IO to
185
- * @returns The configured Socket.IO server instance
186
- * @private
187
- */
188
- private initializeSocketIO(server: Server | HttpsServer): SocketIOServer {
189
- return new SocketIOServer(server, {
190
- cors: {
191
- origin: this.lithia.options.http.cors?.origin || "*",
192
- methods: this.lithia.options.http.cors?.methods || ["GET", "POST"],
193
- credentials: this.lithia.options.http.cors?.credentials ?? true,
194
- },
195
- });
196
- }
197
-
198
- /**
199
- * Sets up Socket.IO event listeners for all registered Lithia events.
200
- *
201
- * This method configures handlers for:
202
- * - Connection events (when a client connects)
203
- * - Disconnection events (when a client disconnects)
204
- * - Custom application events (defined in the Lithia app)
205
- *
206
- * @private
207
- */
208
- private setupSocketIOEventListeners(): void {
209
- if (!this.io) return;
210
-
211
- const events = this.lithia.getEvents();
212
- const connectionEvent = events.find((e) => e.name === "connection");
213
- const disconnectEvent = events.find((e) => e.name === "disconnect");
214
-
215
- this.io.on("connection", async (socket) => {
216
- // Handle connection event
217
- if (connectionEvent) {
218
- await this.eventProcessor.processEvent(socket, connectionEvent);
219
- }
220
-
221
- // Handle disconnect event
222
- socket.on("disconnect", async () => {
223
- if (disconnectEvent) {
224
- await this.eventProcessor.processEvent(socket, disconnectEvent);
225
- }
226
- });
227
-
228
- // Handle all custom events
229
- socket.onAny(async (eventName, ...args) => {
230
- const event = events.find((e) => e.name === eventName);
231
-
232
- if (event) {
233
- await this.eventProcessor.processEvent(socket, event, ...args);
234
- }
235
- });
236
- });
237
- }
238
-
239
- /**
240
- * Creates (or returns) the underlying Node.js server instance.
241
- *
242
- * This method lazily initializes the server and all its dependencies:
243
- * - HTTP/HTTPS server
244
- * - Socket.IO server
245
- * - Event listeners
246
- *
247
- * Subsequent calls return the same server instance.
248
- *
249
- * @returns The server instance (HTTP or HTTPS depending on configuration)
250
- */
251
- async create(): Promise<Server | HttpsServer> {
252
- if (this.server) return this.server;
253
-
254
- this.server = this.createBaseServer();
255
- this.io = this.initializeSocketIO(this.server);
256
- this.setupSocketIOEventListeners();
257
-
258
- return this.server;
259
- }
260
-
261
- /**
262
- * Starts the server and begins listening for connections.
263
- *
264
- * This method will create the server if it hasn't been created yet,
265
- * then bind it to the configured host and port. The promise resolves
266
- * when the server is successfully listening.
267
- *
268
- * @throws {Error} If the server fails to start or if the port is already in use
269
- * @returns A promise that resolves when the server is listening
270
- *
271
- * @example
272
- * ```typescript
273
- * await server.listen();
274
- * // Server is now accepting connections
275
- * ```
276
- */
277
- async listen(): Promise<void> {
278
- if (!this.server) await this.create();
279
-
280
- return new Promise((resolve, reject) => {
281
- if (!this.server) return reject(new Error("Server not created"));
282
-
283
- // If already listening, resolve immediately
284
- if ((this.server as any).listening) return resolve();
285
-
286
- this.server.listen(this.config.port, this.config.host, () => {
287
- logger.event(
288
- `Server listening on http://${this.config.host}:${this.config.port}`,
289
- );
290
-
291
- resolve();
292
- });
293
-
294
- this.server.on("error", (err) => {
295
- logger.error("Server error:", err);
296
- reject(err);
297
- });
298
- });
299
- }
300
-
301
- /**
302
- * Gracefully shuts down the server.
303
- *
304
- * This method performs a clean shutdown by:
305
- * 1. Closing the Socket.IO server and all WebSocket connections
306
- * 2. Stopping the HTTP/HTTPS server from accepting new connections
307
- * 3. Destroying all active socket connections
308
- * 4. Clearing the socket tracking set
309
- *
310
- * @throws {Error} If an error occurs during shutdown
311
- * @returns A promise that resolves when the server is fully closed
312
- *
313
- * @example
314
- * ```typescript
315
- * await server.close();
316
- * // Server is now fully shut down
317
- * ```
318
- */
319
- async close(): Promise<void> {
320
- if (!this.server) return;
321
-
322
- // Close Socket.IO server
323
- await this.io?.close();
324
-
325
- // Close HTTP/HTTPS server
326
- this.server.close((err) => {
327
- if (err) throw err;
328
- });
329
-
330
- // Destroy all active connections
331
- for (const socket of this.sockets) {
332
- socket.destroy();
333
- }
334
-
335
- this.sockets.clear();
336
- }
337
- }
338
-
339
- /**
340
- * Creates an HTTP server instance from Lithia configuration.
341
- *
342
- * This factory function is the primary way to create an HttpServer from
343
- * a complete Lithia configuration object. It extracts the necessary HTTP
344
- * settings and instantiates the server.
345
- *
346
- * @param opts - Configuration object
347
- * @param opts.options - The complete Lithia options including HTTP settings
348
- * @param opts.lithia - The main Lithia application instance
349
- * @returns A configured HttpServer instance ready to be started
350
- *
351
- * @example
352
- * ```typescript
353
- * const server = createHttpServerFromConfig({
354
- * options: lithiaOptions,
355
- * lithia: lithiaInstance
356
- * });
357
- * await server.listen();
358
- * ```
359
- */
360
- export function createHttpServerFromConfig(opts: {
361
- options: LithiaOptions;
362
- lithia: Lithia;
363
- }) {
364
- const cfg: HttpServerConfig = {
365
- port: opts.options.http.port,
366
- host: opts.options.http.host,
367
- ssl: opts.options.http.ssl,
368
- };
369
-
370
- return new HttpServer(cfg, opts.lithia);
371
- }
@@ -1,46 +0,0 @@
1
- import { ZodError, type ZodType } from "zod";
2
- import { ValidationError } from "../../errors";
3
- import type { LithiaMiddleware } from "../request-processor";
4
-
5
- export interface ValidationSchemas {
6
- body?: ZodType;
7
- query?: ZodType;
8
- params?: ZodType;
9
- }
10
-
11
- /**
12
- * Creates a middleware that validates request data against Zod schemas.
13
- * Validated data is assigned back to the request object.
14
- */
15
- export function validate(schemas: ValidationSchemas): LithiaMiddleware {
16
- return async (req, _res, next) => {
17
- try {
18
- if (schemas.params) {
19
- req.params = (await schemas.params.parseAsync(req.params)) as Record<
20
- string,
21
- any
22
- >;
23
- }
24
-
25
- if (schemas.query) {
26
- req.query = (await schemas.query.parseAsync(req.query)) as Record<
27
- string,
28
- any
29
- >;
30
- }
31
-
32
- if (schemas.body) {
33
- const body = await req.body();
34
- const validatedBody = await schemas.body.parseAsync(body);
35
- req.setBody(validatedBody);
36
- }
37
-
38
- next();
39
- } catch (err) {
40
- if (err instanceof ZodError) {
41
- throw new ValidationError("Validation failed", err.issues);
42
- }
43
- throw err;
44
- }
45
- };
46
- }