@lolyjs/core 0.2.0-alpha.15 → 0.2.0-alpha.17

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/dist/index.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import http from 'http';
2
- import { Request, Response } from 'express';
3
- import { Socket, Server } from 'socket.io';
4
- export { c as bootstrapClient } from './bootstrap-DgvWWDim.js';
2
+ export { a as ApiContext, A as ApiMiddleware, G as GenerateStaticParams, L as LoaderResult, M as MetadataLoader, R as RouteMiddleware, S as ServerContext, b as ServerLoader, W as WssActions } from './index.types-DMOO-uvF.js';
3
+ import { Server, Socket } from 'socket.io';
4
+ export { c as bootstrapClient } from './bootstrap-BfGTMUkj.js';
5
5
  import { ZodSchema, z } from 'zod';
6
6
  import * as express_rate_limit from 'express-rate-limit';
7
7
  import pino, { Logger as Logger$1 } from 'pino';
8
+ import { Request, Response } from 'express';
8
9
 
9
10
  /**
10
11
  * Framework configuration interface.
@@ -63,6 +64,7 @@ declare const DEFAULT_CONFIG: FrameworkConfig;
63
64
  *
64
65
  * @param projectRoot - Root directory of the project
65
66
  * @returns Framework configuration
67
+ * @throws ConfigValidationError if configuration is invalid
66
68
  */
67
69
  declare function loadConfig(projectRoot: string): FrameworkConfig;
68
70
  /**
@@ -119,81 +121,271 @@ interface InitServerData {
119
121
  server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>;
120
122
  }
121
123
 
122
- type GenerateStaticParams = () => Array<Record<string, string>> | Promise<Array<Record<string, string>>>;
123
- interface ServerContext {
124
- req: Request;
125
- res: Response;
126
- params: Record<string, string>;
127
- pathname: string;
128
- locals: Record<string, any>;
124
+ /**
125
+ * Authentication context provided to the auth hook.
126
+ * Contains request metadata needed for authentication.
127
+ */
128
+ interface AuthContext {
129
+ /** Request headers */
130
+ req: {
131
+ headers: Record<string, string | string[] | undefined>;
132
+ ip?: string;
133
+ url?: string;
134
+ cookies?: Record<string, string>;
135
+ };
136
+ /** Socket.IO socket instance */
137
+ socket: Socket;
138
+ /** Namespace path (e.g., "/chat") */
139
+ namespace: string;
140
+ }
141
+ /**
142
+ * Rate limit configuration for events.
143
+ */
144
+ interface RateLimitCfg {
145
+ /** Maximum events per second */
146
+ eventsPerSecond: number;
147
+ /** Burst capacity (optional, defaults to eventsPerSecond * 2) */
148
+ burst?: number;
149
+ }
150
+ /**
151
+ * Guard function type. Returns true to allow, false to block.
152
+ */
153
+ type GuardFn<TUser = any> = (ctx: {
154
+ user: TUser | null;
155
+ req: AuthContext["req"];
156
+ socket: Socket;
157
+ namespace: string;
158
+ }) => boolean | Promise<boolean>;
159
+ /**
160
+ * Authentication function type.
161
+ * Returns the authenticated user or null.
162
+ */
163
+ type AuthFn<TUser = any> = (ctx: AuthContext) => TUser | null | Promise<TUser | null>;
164
+ /**
165
+ * Schema validation adapter.
166
+ * Must support Zod-like interface: schema.parse(data) or schema.safeParse(data)
167
+ */
168
+ type Schema = {
169
+ parse?: (data: any) => any;
170
+ safeParse?: (data: any) => {
171
+ success: boolean;
172
+ error?: any;
173
+ data?: any;
174
+ };
175
+ } & Record<string, any>;
176
+ /**
177
+ * Realtime state store interface.
178
+ * Provides key-value storage, lists, sets, and atomic operations.
179
+ */
180
+ interface RealtimeStateStore {
181
+ /** Get a value by key */
182
+ get<T = any>(key: string): Promise<T | null>;
183
+ /** Set a value with optional TTL */
184
+ set<T = any>(key: string, value: T, opts?: {
185
+ ttlMs?: number;
186
+ }): Promise<void>;
187
+ /** Delete a key */
188
+ del(key: string): Promise<void>;
189
+ /** Increment a numeric value */
190
+ incr(key: string, by?: number): Promise<number>;
191
+ /** Decrement a numeric value */
192
+ decr(key: string, by?: number): Promise<number>;
193
+ /** Push to a list (left push) */
194
+ listPush(key: string, value: any, opts?: {
195
+ maxLen?: number;
196
+ }): Promise<void>;
197
+ /** Get range from a list */
198
+ listRange<T = any>(key: string, start: number, end: number): Promise<T[]>;
199
+ /** Add member to a set */
200
+ setAdd(key: string, member: string): Promise<void>;
201
+ /** Remove member from a set */
202
+ setRem(key: string, member: string): Promise<void>;
203
+ /** Get all members of a set */
204
+ setMembers(key: string): Promise<string[]>;
205
+ /** Optional: Acquire a distributed lock (returns unlock function) */
206
+ lock?(key: string, ttlMs: number): Promise<() => Promise<void>>;
207
+ }
208
+ /**
209
+ * Logger interface for realtime events.
210
+ */
211
+ interface RealtimeLogger {
212
+ debug(message: string, meta?: Record<string, any>): void;
213
+ info(message: string, meta?: Record<string, any>): void;
214
+ warn(message: string, meta?: Record<string, any>): void;
215
+ error(message: string, meta?: Record<string, any>): void;
129
216
  }
217
+ /**
218
+ * Metrics interface (optional for v1).
219
+ */
220
+ interface RealtimeMetrics {
221
+ incrementCounter(name: string, labels?: Record<string, string>): void;
222
+ recordLatency(name: string, ms: number, labels?: Record<string, string>): void;
223
+ }
224
+ /**
225
+ * Extended WssActions with full RFC support.
226
+ */
130
227
  interface WssActions {
131
228
  /**
132
- * Emit an event to all clients in the namespace
229
+ * Emit to current socket only (reply)
230
+ */
231
+ reply(event: string, payload?: any): void;
232
+ /**
233
+ * Emit to all sockets in current namespace
234
+ */
235
+ emit(event: string, payload?: any): void;
236
+ /**
237
+ * Emit to everyone except current socket
238
+ */
239
+ broadcast(event: string, payload?: any, opts?: {
240
+ excludeSelf?: boolean;
241
+ }): void;
242
+ /**
243
+ * Join a room
244
+ */
245
+ join(room: string): Promise<void>;
246
+ /**
247
+ * Leave a room
133
248
  */
134
- emit: (event: string, ...args: any[]) => void;
249
+ leave(room: string): Promise<void>;
135
250
  /**
136
- * Emit an event to a specific socket by Socket.IO socket ID
251
+ * Emit to a specific room
137
252
  */
138
- emitTo: (socketId: string, event: string, ...args: any[]) => void;
253
+ toRoom(room: string): {
254
+ emit(event: string, payload?: any): void;
255
+ };
256
+ /**
257
+ * Emit to a specific user (by userId)
258
+ * Uses presence mapping to find user's sockets
259
+ */
260
+ toUser(userId: string): {
261
+ emit(event: string, payload?: any): void;
262
+ };
263
+ /**
264
+ * Emit error event (reserved event: __loly:error)
265
+ */
266
+ error(code: string, message: string, details?: any): void;
139
267
  /**
140
- * Emit an event to a specific client by custom clientId
141
- * Requires clientId to be stored in socket.data.clientId during connection
268
+ * Legacy: Emit to a specific socket by Socket.IO socket ID
269
+ * @deprecated Use toUser() for user targeting
142
270
  */
143
- emitToClient: (clientId: string, event: string, ...args: any[]) => void;
271
+ emitTo?: (socketId: string, event: string, ...args: any[]) => void;
144
272
  /**
145
- * Broadcast an event to all clients in the namespace except the sender
273
+ * Legacy: Emit to a specific client by custom clientId
274
+ * @deprecated Use toUser() for user targeting
146
275
  */
147
- broadcast: (event: string, ...args: any[]) => void;
276
+ emitToClient?: (clientId: string, event: string, ...args: any[]) => void;
148
277
  }
149
- interface WssContext {
150
- socket: Socket;
278
+ /**
279
+ * Extended WssContext with full RFC support.
280
+ */
281
+ interface WssContext<TData = any, TUser = any> {
282
+ /** Socket.IO server instance */
151
283
  io: Server;
284
+ /** Socket.IO socket instance */
285
+ socket: Socket;
286
+ /** Request metadata */
287
+ req: {
288
+ headers: Record<string, string | string[] | undefined>;
289
+ ip?: string;
290
+ url?: string;
291
+ cookies?: Record<string, string>;
292
+ };
293
+ /** Authenticated user (set by auth hook) */
294
+ user: TUser | null;
295
+ /** Incoming payload for current event */
296
+ data: TData;
297
+ /** Route params (from dynamic routes) */
152
298
  params: Record<string, string>;
299
+ /** Route pathname */
153
300
  pathname: string;
154
- data?: any;
301
+ /** Framework utilities */
155
302
  actions: WssActions;
156
- }
157
- type RouteMiddleware = (ctx: ServerContext & {
158
- theme?: string;
159
- }, next: () => Promise<void>) => Promise<void> | void;
160
- interface LoaderResult {
161
- props?: Record<string, any>;
162
- redirect?: {
163
- destination: string;
164
- permanent?: boolean;
165
- };
166
- notFound?: boolean;
167
- metadata?: PageMetadata | null;
168
- className?: string;
169
- theme?: string;
303
+ /** State store for shared state */
304
+ state: RealtimeStateStore;
305
+ /** Logger with WSS context */
306
+ log: RealtimeLogger;
307
+ /** Metrics (optional) */
308
+ metrics?: RealtimeMetrics;
170
309
  }
171
310
  /**
172
- * Server loader function type (getServerSideProps).
173
- * This function is exported from server.hook.ts files.
174
- */
175
- type ServerLoader = (ctx: ServerContext) => Promise<LoaderResult>;
176
- interface PageMetadata {
177
- title?: string;
178
- description?: string;
179
- metaTags?: {
180
- name?: string;
181
- property?: string;
182
- content: string;
183
- }[];
184
- }
185
- type MetadataLoader = (ctx: ServerContext) => PageMetadata | Promise<PageMetadata>;
186
- interface ApiContext {
187
- req: Request;
188
- res: Response;
189
- Response: (body?: any, status?: number) => Response<any, Record<string, any>>;
190
- NotFound: (body?: any) => Response<any, Record<string, any>>;
191
- params: Record<string, string>;
192
- pathname: string;
193
- locals: Record<string, any>;
311
+ * WSS event handler type.
312
+ */
313
+ type WssHandler<TData = any, TUser = any> = (ctx: WssContext<TData, TUser>) => void | Promise<void>;
314
+ /**
315
+ * WSS event definition.
316
+ * Can be a simple handler or an object with validation, guards, and rate limiting.
317
+ */
318
+ type WssEventDefinition<TData = any, TUser = any> = WssHandler<TData, TUser> | {
319
+ /** Schema for validation (Zod/Valibot compatible) */
320
+ schema?: Schema;
321
+ /** Per-event rate limit config */
322
+ rateLimit?: RateLimitCfg;
323
+ /** Guard function (auth/roles/permissions) */
324
+ guard?: GuardFn<TUser>;
325
+ /** Event handler */
326
+ handler: WssHandler<TData, TUser>;
327
+ };
328
+ /**
329
+ * WSS route definition (result of defineWssRoute).
330
+ */
331
+ interface WssRouteDefinition<TUser = any> {
332
+ /** Namespace (optional, inferred from folder name) */
333
+ namespace?: string;
334
+ /** Authentication hook */
335
+ auth?: AuthFn<TUser>;
336
+ /** Connection hook */
337
+ onConnect?: WssHandler<any, TUser>;
338
+ /** Disconnection hook */
339
+ onDisconnect?: (ctx: WssContext<any, TUser>, reason?: string) => void | Promise<void>;
340
+ /** Event handlers */
341
+ events: Record<string, WssEventDefinition<any, TUser>>;
194
342
  }
195
- type ApiMiddleware = (ctx: ApiContext, next: () => Promise<void>) => void | Promise<void>;
196
343
 
344
+ /**
345
+ * Realtime/WebSocket configuration
346
+ */
347
+ interface RealtimeConfig {
348
+ /** Enable realtime features */
349
+ enabled?: boolean;
350
+ /** Socket.IO server settings */
351
+ path?: string;
352
+ transports?: ("websocket" | "polling")[];
353
+ pingIntervalMs?: number;
354
+ pingTimeoutMs?: number;
355
+ maxPayloadBytes?: number;
356
+ /** Security */
357
+ allowedOrigins?: string | string[];
358
+ cors?: {
359
+ credentials?: boolean;
360
+ allowedHeaders?: string[];
361
+ };
362
+ /** Scaling configuration */
363
+ scale?: {
364
+ mode?: "single" | "cluster";
365
+ adapter?: {
366
+ name: "redis";
367
+ url: string;
368
+ pubClientName?: string;
369
+ subClientName?: string;
370
+ };
371
+ stateStore?: {
372
+ name: "memory" | "redis";
373
+ url?: string;
374
+ prefix?: string;
375
+ };
376
+ };
377
+ /** Rate limiting */
378
+ limits?: {
379
+ connectionsPerIp?: number;
380
+ eventsPerSecond?: number;
381
+ burst?: number;
382
+ };
383
+ /** Logging */
384
+ logging?: {
385
+ level?: "debug" | "info" | "warn" | "error";
386
+ pretty?: boolean;
387
+ };
388
+ }
197
389
  interface ServerConfig {
198
390
  bodyLimit?: string;
199
391
  corsOrigin?: string | string[] | boolean | ((origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) => void);
@@ -211,6 +403,8 @@ interface ServerConfig {
211
403
  includeSubDomains?: boolean;
212
404
  };
213
405
  };
406
+ /** Realtime/WebSocket configuration */
407
+ realtime?: RealtimeConfig;
214
408
  }
215
409
 
216
410
  interface BuildAppOptions {
@@ -220,6 +414,43 @@ interface BuildAppOptions {
220
414
  }
221
415
  declare function buildApp(options?: BuildAppOptions): Promise<void>;
222
416
 
417
+ /**
418
+ * Defines a WebSocket route with authentication, hooks, and event handlers.
419
+ *
420
+ * This is the new, recommended way to define WSS routes. It provides:
421
+ * - Type safety
422
+ * - Built-in validation
423
+ * - Auth hooks
424
+ * - Connection/disconnection hooks
425
+ * - Per-event validation, guards, and rate limiting
426
+ *
427
+ * @example
428
+ * ```ts
429
+ * import { defineWssRoute } from "@lolyjs/core";
430
+ * import { z } from "zod";
431
+ *
432
+ * export default defineWssRoute({
433
+ * auth: async (ctx) => {
434
+ * const token = ctx.req.headers.authorization;
435
+ * return await verifyToken(token);
436
+ * },
437
+ * onConnect: (ctx) => {
438
+ * console.log("User connected:", ctx.user?.id);
439
+ * },
440
+ * events: {
441
+ * message: {
442
+ * schema: z.object({ text: z.string() }),
443
+ * guard: ({ user }) => !!user,
444
+ * handler: (ctx) => {
445
+ * ctx.actions.broadcast("message", ctx.data);
446
+ * }
447
+ * }
448
+ * }
449
+ * });
450
+ * ```
451
+ */
452
+ declare function defineWssRoute<TUser = any>(definition: WssRouteDefinition<TUser>): WssRouteDefinition<TUser>;
453
+
223
454
  declare function withCache(fn: any, options: any): any;
224
455
 
225
456
  /**
@@ -336,12 +567,15 @@ interface RateLimitConfig {
336
567
  legacyHeaders?: boolean;
337
568
  skipSuccessfulRequests?: boolean;
338
569
  skipFailedRequests?: boolean;
570
+ keyGenerator?: (req: any) => string;
571
+ skip?: (req: any) => boolean;
339
572
  }
340
573
  /**
341
574
  * Creates a rate limiter middleware with configurable options.
342
575
  *
343
576
  * @param config - Rate limiting configuration
344
577
  * @returns Express rate limit middleware
578
+ * @throws Error if configuration is invalid
345
579
  */
346
580
  declare function createRateLimiter(config?: RateLimitConfig): express_rate_limit.RateLimitRequestHandler;
347
581
  /**
@@ -446,4 +680,4 @@ declare function requestLoggerMiddleware(options?: {
446
680
  */
447
681
  declare function getRequestLogger(req: Request): Logger;
448
682
 
449
- export { type ApiContext, type ApiMiddleware, DEFAULT_CONFIG, type FrameworkConfig, type GenerateStaticParams, type InitServerData, type LoaderResult, type LogLevel, Logger, type LoggerContext, type LoggerOptions, type MetadataLoader, type RouteMiddleware, type ServerConfig, type ServerContext, type ServerLoader, ValidationError, type WssContext, buildApp, commonSchemas, createModuleLogger, createRateLimiter, defaultRateLimiter, generateRequestId, getAppDir, getBuildDir, getLogger, getRequestLogger, getStaticDir, lenientRateLimiter, loadConfig, logger, requestLoggerMiddleware, resetLogger, safeValidate, sanitizeObject, sanitizeParams, sanitizeQuery, sanitizeString, setLogger, startDevServer, startProdServer, strictRateLimiter, validate, withCache };
683
+ export { type AuthContext, type AuthFn, DEFAULT_CONFIG, type FrameworkConfig, type GuardFn, type InitServerData, type LogLevel, Logger, type LoggerContext, type LoggerOptions, type RateLimitCfg, type RealtimeConfig, type RealtimeLogger, type RealtimeMetrics, type RealtimeStateStore, type Schema, type ServerConfig, ValidationError, type WssContext, type WssEventDefinition, type WssHandler, type WssRouteDefinition, buildApp, commonSchemas, createModuleLogger, createRateLimiter, defaultRateLimiter, defineWssRoute, generateRequestId, getAppDir, getBuildDir, getLogger, getRequestLogger, getStaticDir, lenientRateLimiter, loadConfig, logger, requestLoggerMiddleware, resetLogger, safeValidate, sanitizeObject, sanitizeParams, sanitizeQuery, sanitizeString, setLogger, startDevServer, startProdServer, strictRateLimiter, validate, withCache };