@lithia-js/core 1.0.0-canary.1 → 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 +18 -0
- package/package.json +3 -3
- package/.turbo/turbo-build.log +0 -4
- 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
- package/tsconfig.build.tsbuildinfo +0 -1
package/src/logger.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { blue, bold, green, red, white, yellow } from "@lithia-js/utils";
|
|
2
|
-
|
|
3
|
-
export type LogLevel =
|
|
4
|
-
| "info"
|
|
5
|
-
| "warn"
|
|
6
|
-
| "error"
|
|
7
|
-
| "debug"
|
|
8
|
-
| "success"
|
|
9
|
-
| "ready"
|
|
10
|
-
| "event"
|
|
11
|
-
| "wait";
|
|
12
|
-
|
|
13
|
-
function formatMeta(meta: any) {
|
|
14
|
-
if (meta === undefined) return "";
|
|
15
|
-
try {
|
|
16
|
-
if (typeof meta === "string") return meta;
|
|
17
|
-
return JSON.stringify(meta);
|
|
18
|
-
} catch {
|
|
19
|
-
return String(meta);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export class Logger {
|
|
24
|
-
info(msg: any, meta?: any) {
|
|
25
|
-
const m = formatMeta(meta);
|
|
26
|
-
console.log(` ${msg}${m ? ` — ${m}` : ""}`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
warn(msg: any, meta?: any) {
|
|
30
|
-
const symbol = yellow(bold("○"));
|
|
31
|
-
const m = formatMeta(meta);
|
|
32
|
-
console.warn(`${symbol} ${msg}${m ? ` — ${m}` : ""}`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
error(msg: any, meta?: any) {
|
|
36
|
-
const symbol = red(bold("○"));
|
|
37
|
-
const m = formatMeta(meta);
|
|
38
|
-
console.error(`${symbol} ${msg}${m ? ` — ${m}` : ""}`);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
success(msg: any, meta?: any) {
|
|
42
|
-
const symbol = green(bold("▲"));
|
|
43
|
-
const m = formatMeta(meta);
|
|
44
|
-
console.log(`${symbol} ${msg}${m ? ` — ${m}` : ""}`);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
event(msg: any, meta?: any) {
|
|
48
|
-
const symbol = blue(bold("▲"));
|
|
49
|
-
const m = formatMeta(meta);
|
|
50
|
-
console.log(`${symbol} ${msg}${m ? ` — ${m}` : ""}`);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
debug(msg: any, meta?: any) {
|
|
54
|
-
const symbol = white(bold("»"));
|
|
55
|
-
const m = formatMeta(meta);
|
|
56
|
-
console.log(`${symbol} ${msg}${m ? ` — ${m}` : ""}`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
wait(msg: any, meta?: any) {
|
|
60
|
-
const symbol = white(bold("○"));
|
|
61
|
-
const m = formatMeta(meta);
|
|
62
|
-
console.log(`${symbol} ${msg}${m ? ` — ${m}` : ""}`);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export const logger = new Logger();
|
package/src/module-loader.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { pathToFileURL } from "node:url";
|
|
2
|
-
import importFresh from "import-fresh";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Import a module dynamically, bypassing the cache in development.
|
|
6
|
-
* This is useful for hot-reloading routes and configuration files.
|
|
7
|
-
*
|
|
8
|
-
* @param filePath Absolute path to the file to import
|
|
9
|
-
* @param isDevelopment Whether to use cache-busting (dev) or native import (prod)
|
|
10
|
-
*/
|
|
11
|
-
export async function coldImport<T = any>(
|
|
12
|
-
filePath: string,
|
|
13
|
-
isDevelopment: boolean,
|
|
14
|
-
): Promise<T> {
|
|
15
|
-
let mod: any;
|
|
16
|
-
|
|
17
|
-
if (isDevelopment) {
|
|
18
|
-
// Use import-fresh in development for cache-free imports
|
|
19
|
-
// Note: import-fresh uses require() under the hood
|
|
20
|
-
mod = importFresh(filePath);
|
|
21
|
-
} else {
|
|
22
|
-
// Production: use normal dynamic import via file URL
|
|
23
|
-
const importUrl = pathToFileURL(filePath).href;
|
|
24
|
-
mod = await import(importUrl);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Normalize CommonJS module structure
|
|
28
|
-
// When importing CommonJS via dynamic import or some bundlers, it can return:
|
|
29
|
-
// { default: { default: fn, ... } }
|
|
30
|
-
// We need to unwrap it to: { default: fn, ... }
|
|
31
|
-
if (mod.default && typeof mod.default === "object" && mod.default.default) {
|
|
32
|
-
return mod.default as T;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return mod as T;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const AsyncFunction = (async () => {}).constructor;
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Check if a value is an async function.
|
|
42
|
-
*/
|
|
43
|
-
export function isAsyncFunction(fn: unknown): boolean {
|
|
44
|
-
return fn instanceof AsyncFunction;
|
|
45
|
-
}
|
|
@@ -1,344 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Event processor module for Socket.IO event handling.
|
|
3
|
-
*
|
|
4
|
-
* This module is the core of Lithia's Socket.IO event processing pipeline. It handles:
|
|
5
|
-
* - Event handler module loading with cache busting in development
|
|
6
|
-
* - Event context initialization for hooks
|
|
7
|
-
* - Error handling with logging and digest generation
|
|
8
|
-
* - Error event emission for centralized error handling
|
|
9
|
-
*
|
|
10
|
-
* @module server/event-processor
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { createHash } from "node:crypto";
|
|
14
|
-
import type { Event } from "@lithia-js/native";
|
|
15
|
-
import { green, red } from "@lithia-js/utils";
|
|
16
|
-
import type { Socket } from "socket.io";
|
|
17
|
-
import { type EventContext, eventContext } from "../context/event-context";
|
|
18
|
-
import { InvalidEventModuleError } from "../errors";
|
|
19
|
-
import type { Lithia } from "../lithia";
|
|
20
|
-
import { logger } from "../logger";
|
|
21
|
-
import { coldImport, isAsyncFunction } from "../module-loader";
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Event handler function signature.
|
|
25
|
-
*
|
|
26
|
-
* The primary function exported from event files to handle Socket.IO events.
|
|
27
|
-
*
|
|
28
|
-
* @param socket - The Socket.IO socket instance that triggered the event
|
|
29
|
-
*/
|
|
30
|
-
export type EventHandler = (socket: Socket) => Promise<void>;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Structure of an event module loaded from the file system.
|
|
34
|
-
*
|
|
35
|
-
* Event files must export a default async function that handles the event.
|
|
36
|
-
*/
|
|
37
|
-
export interface EventModule {
|
|
38
|
-
/**
|
|
39
|
-
* The default event handler function.
|
|
40
|
-
*
|
|
41
|
-
* This function is called when the event is triggered by a client.
|
|
42
|
-
*/
|
|
43
|
-
default: (socket: Socket) => Promise<void>;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Standardized error information for event errors.
|
|
48
|
-
*
|
|
49
|
-
* Contains details about errors that occur during event processing.
|
|
50
|
-
*
|
|
51
|
-
* @internal
|
|
52
|
-
*/
|
|
53
|
-
export interface EventErrorInfo {
|
|
54
|
-
/** The event name that caused the error. */
|
|
55
|
-
eventName: string;
|
|
56
|
-
/** Human-readable error message. */
|
|
57
|
-
message: string;
|
|
58
|
-
/** ISO timestamp of when the error occurred. */
|
|
59
|
-
timestamp: string;
|
|
60
|
-
/** Short error digest for correlation with logs. */
|
|
61
|
-
digest: string;
|
|
62
|
-
/** Socket ID that triggered the error. */
|
|
63
|
-
socketId: string;
|
|
64
|
-
/** Error stack trace (development only). */
|
|
65
|
-
stack?: string;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Event processor for the Lithia Socket.IO pipeline.
|
|
70
|
-
*
|
|
71
|
-
* This class orchestrates the entire event processing flow from receiving
|
|
72
|
-
* a Socket.IO event to executing the handler. It manages:
|
|
73
|
-
*
|
|
74
|
-
* - **Event context**: Initializes context with event data for hooks
|
|
75
|
-
* - **Module loading**: Loads event handlers with cache busting in development
|
|
76
|
-
* - **Error handling**: Catches and logs errors with digest generation
|
|
77
|
-
* - **Error events**: Emits error events for centralized error handling
|
|
78
|
-
* - **Logging**: Logs all events with timing and error details
|
|
79
|
-
*
|
|
80
|
-
* @remarks
|
|
81
|
-
* The processor uses AsyncLocalStorage to provide event context to hooks,
|
|
82
|
-
* allowing event handlers to access data without explicit parameters.
|
|
83
|
-
*
|
|
84
|
-
* In development mode, event modules are reloaded on each event (cache busting).
|
|
85
|
-
* In production, modules are cached for better performance.
|
|
86
|
-
*/
|
|
87
|
-
export class EventProcessor {
|
|
88
|
-
/**
|
|
89
|
-
* Creates a new event processor.
|
|
90
|
-
*
|
|
91
|
-
* @param lithia - The Lithia application instance
|
|
92
|
-
*/
|
|
93
|
-
constructor(private lithia: Lithia) {}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Processes an incoming Socket.IO event through the complete pipeline.
|
|
97
|
-
*
|
|
98
|
-
* This is the main entry point for event processing. The method executes
|
|
99
|
-
* the following steps in order:
|
|
100
|
-
*
|
|
101
|
-
* 1. Initialize event context with data for hooks
|
|
102
|
-
* 2. Load the event handler module
|
|
103
|
-
* 3. Execute the event handler
|
|
104
|
-
* 4. Log the event with timing
|
|
105
|
-
*
|
|
106
|
-
* Any errors thrown during this process are caught and handled by
|
|
107
|
-
* {@link handleError}, which logs the error and emits an error event.
|
|
108
|
-
*
|
|
109
|
-
* @param socket - The Socket.IO socket that triggered the event
|
|
110
|
-
* @param event - The event definition from the manifest
|
|
111
|
-
* @param args - Additional arguments passed with the event
|
|
112
|
-
*
|
|
113
|
-
* @example
|
|
114
|
-
* ```typescript
|
|
115
|
-
* const processor = new EventProcessor(lithia);
|
|
116
|
-
* await processor.processEvent(socket, event, messageData);
|
|
117
|
-
* ```
|
|
118
|
-
*/
|
|
119
|
-
async processEvent(
|
|
120
|
-
socket: Socket,
|
|
121
|
-
event: Event,
|
|
122
|
-
...args: any[]
|
|
123
|
-
): Promise<void> {
|
|
124
|
-
const start = process.hrtime.bigint();
|
|
125
|
-
|
|
126
|
-
// Initialize context for hooks
|
|
127
|
-
const eventCtx: EventContext = {
|
|
128
|
-
data: args[0],
|
|
129
|
-
socket,
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
await this.lithia.runWithContext(async () => {
|
|
133
|
-
await eventContext.run(eventCtx, async () => {
|
|
134
|
-
try {
|
|
135
|
-
// Import event module
|
|
136
|
-
const module = await this.importEventModule(event);
|
|
137
|
-
|
|
138
|
-
// Execute event handler
|
|
139
|
-
await module.default(socket);
|
|
140
|
-
|
|
141
|
-
// Log successful event
|
|
142
|
-
this.logEvent(event, start);
|
|
143
|
-
} catch (err) {
|
|
144
|
-
// Handle and log error
|
|
145
|
-
await this.handleError(err, event, socket, start);
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// ==================== Error Handling ====================
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Centralized error handler for the event pipeline.
|
|
155
|
-
*
|
|
156
|
-
* This method handles all errors thrown during event processing:
|
|
157
|
-
*
|
|
158
|
-
* **Development mode:**
|
|
159
|
-
* - Returns detailed error messages to client
|
|
160
|
-
* - Includes full error stacks
|
|
161
|
-
* - Logs with error digest
|
|
162
|
-
*
|
|
163
|
-
* **Production mode:**
|
|
164
|
-
* - Returns generic error messages to client
|
|
165
|
-
* - Includes error digest for log correlation
|
|
166
|
-
* - Hides sensitive error details
|
|
167
|
-
*
|
|
168
|
-
* After logging, emits an 'error' event to the socket so the client
|
|
169
|
-
* can handle errors gracefully.
|
|
170
|
-
*
|
|
171
|
-
* @param err - The error that was thrown
|
|
172
|
-
* @param event - The event definition that caused the error
|
|
173
|
-
* @param socket - The Socket.IO socket that triggered the event
|
|
174
|
-
* @param start - High-resolution timestamp when event processing started
|
|
175
|
-
*
|
|
176
|
-
* @private
|
|
177
|
-
*/
|
|
178
|
-
private async handleError(
|
|
179
|
-
err: unknown,
|
|
180
|
-
event: Event,
|
|
181
|
-
socket: Socket,
|
|
182
|
-
start: bigint,
|
|
183
|
-
): Promise<void> {
|
|
184
|
-
const isDevelopment = this.lithia.getEnvironment() === "development";
|
|
185
|
-
|
|
186
|
-
// Generate error digest
|
|
187
|
-
const digest = this.generateErrorDigest(err);
|
|
188
|
-
|
|
189
|
-
// Build error details
|
|
190
|
-
const errorMessage =
|
|
191
|
-
err instanceof Error ? err.message : "Internal Server Error";
|
|
192
|
-
const errorStack = err instanceof Error ? err.stack : undefined;
|
|
193
|
-
|
|
194
|
-
// Log error with digest (same format for both environments)
|
|
195
|
-
logger.error(
|
|
196
|
-
`[Event: ${event.name}] [Digest: ${red(digest)}] ${errorStack || errorMessage}`,
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
// Build error information for client
|
|
200
|
-
const errorInfo: EventErrorInfo = {
|
|
201
|
-
eventName: event.name,
|
|
202
|
-
message: isDevelopment
|
|
203
|
-
? errorMessage
|
|
204
|
-
: "An error occurred while processing the event",
|
|
205
|
-
timestamp: new Date().toISOString(),
|
|
206
|
-
digest: digest,
|
|
207
|
-
socketId: socket.id,
|
|
208
|
-
stack: isDevelopment ? errorStack : undefined,
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
// Emit error event to the client
|
|
212
|
-
socket.emit("error", errorInfo);
|
|
213
|
-
|
|
214
|
-
// Log the event as failed
|
|
215
|
-
this.logEvent(event, start, true);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// ==================== Logging & Diagnostics ====================
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Logs a completed event with timing information.
|
|
222
|
-
*
|
|
223
|
-
* The log includes:
|
|
224
|
-
* - Event name
|
|
225
|
-
* - Socket ID
|
|
226
|
-
* - Processing duration in milliseconds
|
|
227
|
-
* - Success or error status
|
|
228
|
-
*
|
|
229
|
-
* Respects the `logging.events` configuration flag. Critical errors
|
|
230
|
-
* are always logged regardless of the flag.
|
|
231
|
-
*
|
|
232
|
-
* @param event - The event that was processed
|
|
233
|
-
* @param socket - The socket that triggered the event
|
|
234
|
-
* @param start - High-resolution timestamp when processing started
|
|
235
|
-
* @param isError - Whether the event resulted in an error
|
|
236
|
-
*
|
|
237
|
-
* @private
|
|
238
|
-
*/
|
|
239
|
-
private logEvent(event: Event, start: bigint, isError = false): void {
|
|
240
|
-
// Always log errors, otherwise respect the logging.events flag
|
|
241
|
-
const shouldLog = isError || this.lithia.options.logging?.events !== false;
|
|
242
|
-
|
|
243
|
-
if (!shouldLog) return;
|
|
244
|
-
|
|
245
|
-
const end = process.hrtime.bigint();
|
|
246
|
-
const duration = Number(end - start) / 1_000_000;
|
|
247
|
-
const durationStr = `${duration.toFixed(2)}ms`;
|
|
248
|
-
|
|
249
|
-
// Use same color scheme as requests for consistency
|
|
250
|
-
const statusStr = isError ? red("ERROR") : green("OK");
|
|
251
|
-
|
|
252
|
-
logger.info(`[${statusStr}] EVENT ${event.name} - ${durationStr}`);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Generates a short hexadecimal digest for error correlation.
|
|
257
|
-
*
|
|
258
|
-
* The digest is used to correlate server-side error logs with client-facing
|
|
259
|
-
* error responses. This allows developers to find the detailed error in logs
|
|
260
|
-
* using the digest shown to the client.
|
|
261
|
-
*
|
|
262
|
-
* The digest is generated from:
|
|
263
|
-
* - Error message and stack
|
|
264
|
-
* - Current timestamp
|
|
265
|
-
* - Random factor for uniqueness
|
|
266
|
-
*
|
|
267
|
-
* @param err - The error to generate a digest for
|
|
268
|
-
* @returns An 8-character hexadecimal digest
|
|
269
|
-
*
|
|
270
|
-
* @private
|
|
271
|
-
*/
|
|
272
|
-
private generateErrorDigest(err: unknown): string {
|
|
273
|
-
// Create a unique digest based on error message, timestamp, and random factor
|
|
274
|
-
const errorString =
|
|
275
|
-
err instanceof Error ? `${err.message}${err.stack}` : String(err);
|
|
276
|
-
|
|
277
|
-
const hash = createHash("sha256")
|
|
278
|
-
.update(`${errorString}${Date.now()}${Math.random()}`)
|
|
279
|
-
.digest("hex");
|
|
280
|
-
|
|
281
|
-
// Return first 8 characters (similar to request-processor)
|
|
282
|
-
return hash.substring(0, 8);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// ==================== Module Loading ====================
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Imports an event module from the file system.
|
|
289
|
-
*
|
|
290
|
-
* In **development mode**, uses cache-busting to reload the module on each
|
|
291
|
-
* event, enabling hot reloading without server restart.
|
|
292
|
-
*
|
|
293
|
-
* In **production mode**, uses standard dynamic imports with caching for
|
|
294
|
-
* better performance.
|
|
295
|
-
*
|
|
296
|
-
* The method also validates the module structure:
|
|
297
|
-
* - Must have a default export
|
|
298
|
-
* - Default export must be a function
|
|
299
|
-
* - Default export must be async
|
|
300
|
-
*
|
|
301
|
-
* @param event - The event whose module should be imported
|
|
302
|
-
* @returns The loaded and validated event module
|
|
303
|
-
* @throws {InvalidEventModuleError} If the module structure is invalid
|
|
304
|
-
* @throws {Error} If the module fails to load
|
|
305
|
-
*
|
|
306
|
-
* @private
|
|
307
|
-
*/
|
|
308
|
-
private async importEventModule(event: Event): Promise<EventModule> {
|
|
309
|
-
try {
|
|
310
|
-
const isDevelopment = this.lithia.getEnvironment() === "development";
|
|
311
|
-
|
|
312
|
-
const mod = await coldImport<EventModule>(event.filePath, isDevelopment);
|
|
313
|
-
|
|
314
|
-
if (!mod.default) {
|
|
315
|
-
throw new InvalidEventModuleError(
|
|
316
|
-
event.filePath,
|
|
317
|
-
"Missing default export",
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (typeof mod.default !== "function") {
|
|
322
|
-
throw new InvalidEventModuleError(
|
|
323
|
-
event.filePath,
|
|
324
|
-
"Default export is not a function",
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
if (!isAsyncFunction(mod.default)) {
|
|
329
|
-
throw new InvalidEventModuleError(
|
|
330
|
-
event.filePath,
|
|
331
|
-
"Default export is not an async function",
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
return mod;
|
|
336
|
-
} catch (err) {
|
|
337
|
-
if (err instanceof InvalidEventModuleError) {
|
|
338
|
-
throw err;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
throw new Error(`Failed to import event: ${event.name}`);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|