@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.
- package/CHANGELOG.md +9 -0
- package/package.json +3 -3
- package/src/config.ts +0 -212
- package/src/context/event-context.ts +0 -66
- package/src/context/index.ts +0 -32
- package/src/context/lithia-context.ts +0 -59
- package/src/context/route-context.ts +0 -89
- package/src/env.ts +0 -31
- package/src/errors.ts +0 -96
- package/src/hooks/dependency-hooks.ts +0 -122
- package/src/hooks/event-hooks.ts +0 -69
- package/src/hooks/index.ts +0 -58
- package/src/hooks/route-hooks.ts +0 -177
- package/src/lib.ts +0 -27
- package/src/lithia.ts +0 -777
- package/src/logger.ts +0 -66
- package/src/module-loader.ts +0 -45
- package/src/server/event-processor.ts +0 -344
- package/src/server/http-server.ts +0 -371
- package/src/server/middlewares/validation.ts +0 -46
- package/src/server/request-processor.ts +0 -860
- package/src/server/request.ts +0 -247
- package/src/server/response.ts +0 -204
|
@@ -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
|
-
}
|