@lolyjs/core 0.2.0-alpha.9 → 0.3.0-alpha.0

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.
Files changed (55) hide show
  1. package/README.md +1593 -762
  2. package/dist/{bootstrap-DgvWWDim.d.mts → bootstrap-BfGTMUkj.d.mts} +12 -0
  3. package/dist/{bootstrap-DgvWWDim.d.ts → bootstrap-BfGTMUkj.d.ts} +12 -0
  4. package/dist/cli.cjs +16397 -2601
  5. package/dist/cli.cjs.map +1 -1
  6. package/dist/cli.mjs +19096 -0
  7. package/dist/cli.mjs.map +1 -0
  8. package/dist/index.cjs +17419 -3204
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.mts +323 -56
  11. package/dist/index.d.ts +323 -56
  12. package/dist/index.mjs +20236 -0
  13. package/dist/index.mjs.map +1 -0
  14. package/dist/index.types-Duhjyfit.d.mts +280 -0
  15. package/dist/index.types-Duhjyfit.d.ts +280 -0
  16. package/dist/react/cache.cjs +82 -32
  17. package/dist/react/cache.cjs.map +1 -1
  18. package/dist/react/cache.d.mts +29 -21
  19. package/dist/react/cache.d.ts +29 -21
  20. package/dist/react/{cache.js → cache.mjs} +84 -34
  21. package/dist/react/cache.mjs.map +1 -0
  22. package/dist/react/components.cjs +11 -12
  23. package/dist/react/components.cjs.map +1 -1
  24. package/dist/react/{components.js → components.mjs} +12 -13
  25. package/dist/react/components.mjs.map +1 -0
  26. package/dist/react/hooks.cjs +16 -6
  27. package/dist/react/hooks.cjs.map +1 -1
  28. package/dist/react/{hooks.js → hooks.mjs} +24 -14
  29. package/dist/react/hooks.mjs.map +1 -0
  30. package/dist/react/sockets.cjs +5 -6
  31. package/dist/react/sockets.cjs.map +1 -1
  32. package/dist/react/sockets.mjs +21 -0
  33. package/dist/react/sockets.mjs.map +1 -0
  34. package/dist/react/themes.cjs +61 -18
  35. package/dist/react/themes.cjs.map +1 -1
  36. package/dist/react/{themes.js → themes.mjs} +64 -21
  37. package/dist/react/themes.mjs.map +1 -0
  38. package/dist/runtime.cjs +465 -117
  39. package/dist/runtime.cjs.map +1 -1
  40. package/dist/runtime.d.mts +2 -2
  41. package/dist/runtime.d.ts +2 -2
  42. package/dist/{runtime.js → runtime.mjs} +466 -118
  43. package/dist/runtime.mjs.map +1 -0
  44. package/package.json +26 -26
  45. package/dist/cli.js +0 -5291
  46. package/dist/cli.js.map +0 -1
  47. package/dist/index.js +0 -6015
  48. package/dist/index.js.map +0 -1
  49. package/dist/react/cache.js.map +0 -1
  50. package/dist/react/components.js.map +0 -1
  51. package/dist/react/hooks.js.map +0 -1
  52. package/dist/react/sockets.js +0 -22
  53. package/dist/react/sockets.js.map +0 -1
  54. package/dist/react/themes.js.map +0 -1
  55. package/dist/runtime.js.map +0 -1
package/dist/index.d.mts CHANGED
@@ -1,7 +1,8 @@
1
1
  import http from 'http';
2
+ export { a as ApiContext, A as ApiMiddleware, b as GenerateStaticParams, G as GlobalMiddleware, L as LoaderResult, M as MetadataLoader, N as NotFoundResponse, d as RedirectResponse, R as RouteMiddleware, S as ServerContext, c as ServerLoader, W as WssActions } from './index.types-Duhjyfit.mjs';
3
+ import { Socket } from 'socket.io';
2
4
  import { Request, Response } from 'express';
3
- import { Socket, Server } from 'socket.io';
4
- export { c as bootstrapClient } from './bootstrap-DgvWWDim.mjs';
5
+ export { c as bootstrapClient } from './bootstrap-BfGTMUkj.mjs';
5
6
  import { ZodSchema, z } from 'zod';
6
7
  import * as express_rate_limit from 'express-rate-limit';
7
8
  import pino, { Logger as Logger$1 } from 'pino';
@@ -32,7 +33,6 @@ interface FrameworkConfig {
32
33
  build: {
33
34
  clientBundler: 'rspack' | 'webpack' | 'vite';
34
35
  serverBundler: 'esbuild' | 'tsup' | 'swc';
35
- outputFormat: 'cjs' | 'esm';
36
36
  };
37
37
  server: {
38
38
  adapter: 'express' | 'fastify' | 'koa';
@@ -63,6 +63,7 @@ declare const DEFAULT_CONFIG: FrameworkConfig;
63
63
  *
64
64
  * @param projectRoot - Root directory of the project
65
65
  * @returns Framework configuration
66
+ * @throws ConfigValidationError if configuration is invalid
66
67
  */
67
68
  declare function loadConfig(projectRoot: string): FrameworkConfig;
68
69
  /**
@@ -119,77 +120,301 @@ interface InitServerData {
119
120
  server: http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>;
120
121
  }
121
122
 
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>;
123
+ /**
124
+ * Authentication context provided to the auth hook.
125
+ * Contains request metadata needed for authentication.
126
+ */
127
+ interface AuthContext {
128
+ /** Request headers */
129
+ req: {
130
+ headers: Record<string, string | string[] | undefined>;
131
+ ip?: string;
132
+ url?: string;
133
+ cookies?: Record<string, string>;
134
+ };
135
+ /** Socket.IO socket instance */
136
+ socket: Socket;
137
+ /** Namespace path (e.g., "/chat") */
138
+ namespace: string;
129
139
  }
140
+ /**
141
+ * Rate limit configuration for events.
142
+ */
143
+ interface RateLimitCfg {
144
+ /** Maximum events per second */
145
+ eventsPerSecond: number;
146
+ /** Burst capacity (optional, defaults to eventsPerSecond * 2) */
147
+ burst?: number;
148
+ }
149
+ /**
150
+ * Guard function type. Returns true to allow, false to block.
151
+ */
152
+ type GuardFn<TUser = any> = (ctx: {
153
+ user: TUser | null;
154
+ req: AuthContext["req"];
155
+ namespace: string;
156
+ }) => boolean | Promise<boolean>;
157
+ /**
158
+ * Authentication function type.
159
+ * Returns the authenticated user or null.
160
+ */
161
+ type AuthFn<TUser = any> = (ctx: AuthContext) => TUser | null | Promise<TUser | null>;
162
+ /**
163
+ * Schema validation adapter.
164
+ * Must support Zod-like interface: schema.parse(data) or schema.safeParse(data)
165
+ */
166
+ type Schema = {
167
+ parse?: (data: any) => any;
168
+ safeParse?: (data: any) => {
169
+ success: boolean;
170
+ error?: any;
171
+ data?: any;
172
+ };
173
+ } & Record<string, any>;
174
+ /**
175
+ * Realtime state store interface.
176
+ * Provides key-value storage, lists, sets, and atomic operations.
177
+ */
178
+ interface RealtimeStateStore {
179
+ /** Get a value by key */
180
+ get<T = any>(key: string): Promise<T | null>;
181
+ /** Set a value with optional TTL */
182
+ set<T = any>(key: string, value: T, opts?: {
183
+ ttlMs?: number;
184
+ }): Promise<void>;
185
+ /** Delete a key */
186
+ del(key: string): Promise<void>;
187
+ /** Increment a numeric value */
188
+ incr(key: string, by?: number): Promise<number>;
189
+ /** Decrement a numeric value */
190
+ decr(key: string, by?: number): Promise<number>;
191
+ /** Push to a list (left push) */
192
+ listPush(key: string, value: any, opts?: {
193
+ maxLen?: number;
194
+ }): Promise<void>;
195
+ /** Get range from a list */
196
+ listRange<T = any>(key: string, start: number, end: number): Promise<T[]>;
197
+ /** Add member to a set */
198
+ setAdd(key: string, member: string): Promise<void>;
199
+ /** Remove member from a set */
200
+ setRem(key: string, member: string): Promise<void>;
201
+ /** Get all members of a set */
202
+ setMembers(key: string): Promise<string[]>;
203
+ /** Optional: Acquire a distributed lock (returns unlock function) */
204
+ lock?(key: string, ttlMs: number): Promise<() => Promise<void>>;
205
+ }
206
+ /**
207
+ * Logger interface for realtime events.
208
+ */
209
+ interface RealtimeLogger {
210
+ debug(message: string, meta?: Record<string, any>): void;
211
+ info(message: string, meta?: Record<string, any>): void;
212
+ warn(message: string, meta?: Record<string, any>): void;
213
+ error(message: string, meta?: Record<string, any>): void;
214
+ }
215
+ /**
216
+ * Metrics interface (optional for v1).
217
+ */
218
+ interface RealtimeMetrics {
219
+ incrementCounter(name: string, labels?: Record<string, string>): void;
220
+ recordLatency(name: string, ms: number, labels?: Record<string, string>): void;
221
+ }
222
+ /**
223
+ * Extended WssActions with full RFC support.
224
+ */
130
225
  interface WssActions {
131
226
  /**
132
- * Emit an event to all clients in the namespace
227
+ * Emit to current socket only (reply)
228
+ */
229
+ reply(event: string, payload?: any): void;
230
+ /**
231
+ * Emit to all sockets in current namespace
232
+ */
233
+ emit(event: string, payload?: any): void;
234
+ /**
235
+ * Emit to everyone except current socket
133
236
  */
134
- emit: (event: string, ...args: any[]) => void;
237
+ broadcast(event: string, payload?: any, opts?: {
238
+ excludeSelf?: boolean;
239
+ }): void;
240
+ /**
241
+ * Join a room
242
+ */
243
+ join(room: string): Promise<void>;
244
+ /**
245
+ * Leave a room
246
+ */
247
+ leave(room: string): Promise<void>;
248
+ /**
249
+ * Emit to a specific room
250
+ */
251
+ toRoom(room: string): {
252
+ emit(event: string, payload?: any): void;
253
+ };
254
+ /**
255
+ * Emit to a specific user (by userId)
256
+ * Uses presence mapping to find user's sockets
257
+ */
258
+ toUser(userId: string): {
259
+ emit(event: string, payload?: any): void;
260
+ };
135
261
  /**
136
- * Emit an event to a specific socket by Socket.IO socket ID
262
+ * Emit error event (reserved event: __loly:error)
137
263
  */
138
- emitTo: (socketId: string, event: string, ...args: any[]) => void;
264
+ error(code: string, message: string, details?: any): void;
139
265
  /**
140
- * Emit an event to a specific client by custom clientId
141
- * Requires clientId to be stored in socket.data.clientId during connection
266
+ * Legacy: Emit to a specific socket by Socket.IO socket ID
267
+ * @deprecated Use toUser() for user targeting
142
268
  */
143
- emitToClient: (clientId: string, event: string, ...args: any[]) => void;
269
+ emitTo?: (socketId: string, event: string, ...args: any[]) => void;
144
270
  /**
145
- * Broadcast an event to all clients in the namespace except the sender
271
+ * Legacy: Emit to a specific client by custom clientId
272
+ * @deprecated Use toUser() for user targeting
146
273
  */
147
- broadcast: (event: string, ...args: any[]) => void;
274
+ emitToClient?: (clientId: string, event: string, ...args: any[]) => void;
148
275
  }
149
- interface WssContext {
150
- socket: Socket;
151
- io: Server;
276
+ /**
277
+ * Extended WssContext with full RFC support.
278
+ */
279
+ interface WssContext<TData = any, TUser = any> {
280
+ /** Request metadata */
281
+ req: {
282
+ headers: Record<string, string | string[] | undefined>;
283
+ ip?: string;
284
+ url?: string;
285
+ cookies?: Record<string, string>;
286
+ };
287
+ /** Authenticated user (set by auth hook) */
288
+ user: TUser | null;
289
+ /** Incoming payload for current event */
290
+ data: TData;
291
+ /** Route params (from dynamic routes) */
152
292
  params: Record<string, string>;
293
+ /** Route pathname */
153
294
  pathname: string;
154
- data?: any;
295
+ /** Framework utilities */
155
296
  actions: WssActions;
297
+ /** State store for shared state */
298
+ state: RealtimeStateStore;
299
+ /** Logger with WSS context */
300
+ log: RealtimeLogger;
301
+ /** Metrics (optional) */
302
+ metrics?: RealtimeMetrics;
156
303
  }
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;
304
+ /**
305
+ * WSS event handler type.
306
+ */
307
+ type WssHandler<TData = any, TUser = any> = (ctx: WssContext<TData, TUser>) => void | Promise<void>;
308
+ /**
309
+ * WSS event definition.
310
+ * Can be a simple handler or an object with validation, guards, and rate limiting.
311
+ */
312
+ type WssEventDefinition<TData = any, TUser = any> = WssHandler<TData, TUser> | {
313
+ /** Schema for validation (Zod/Valibot compatible) */
314
+ schema?: Schema;
315
+ /** Per-event rate limit config */
316
+ rateLimit?: RateLimitCfg;
317
+ /** Guard function (auth/roles/permissions) */
318
+ guard?: GuardFn<TUser>;
319
+ /** Event handler */
320
+ handler: WssHandler<TData, TUser>;
321
+ };
322
+ /**
323
+ * WSS route definition (result of defineWssRoute).
324
+ */
325
+ interface WssRouteDefinition<TUser = any> {
326
+ /** Namespace (optional, inferred from folder name) */
327
+ namespace?: string;
328
+ /** Authentication hook */
329
+ auth?: AuthFn<TUser>;
330
+ /** Connection hook */
331
+ onConnect?: WssHandler<any, TUser>;
332
+ /** Disconnection hook */
333
+ onDisconnect?: (ctx: WssContext<any, TUser>, reason?: string) => void | Promise<void>;
334
+ /** Event handlers */
335
+ events: Record<string, WssEventDefinition<any, TUser>>;
170
336
  }
171
- type ServerLoader = (ctx: ServerContext) => Promise<LoaderResult>;
172
- interface PageMetadata {
173
- title?: string;
174
- description?: string;
175
- metaTags?: {
176
- name?: string;
177
- property?: string;
178
- content: string;
179
- }[];
337
+
338
+ /**
339
+ * Rewrite condition types.
340
+ * Used in the `has` array to conditionally apply rewrites.
341
+ */
342
+ type RewriteConditionType = "host" | "header" | "cookie" | "query";
343
+ /**
344
+ * Rewrite condition.
345
+ * Defines when a rewrite should be applied.
346
+ */
347
+ interface RewriteCondition {
348
+ type: RewriteConditionType;
349
+ key?: string;
350
+ value: string;
180
351
  }
181
- type MetadataLoader = (ctx: ServerContext) => PageMetadata | Promise<PageMetadata>;
182
- interface ApiContext {
183
- req: Request;
184
- res: Response;
185
- Response: (body?: any, status?: number) => Response<any, Record<string, any>>;
186
- NotFound: (body?: any) => Response<any, Record<string, any>>;
187
- params: Record<string, string>;
188
- pathname: string;
189
- locals: Record<string, any>;
352
+ /**
353
+ * Rewrite destination can be:
354
+ * - A string (static or with :param placeholders)
355
+ * - An async function that returns a string (for dynamic rewrites)
356
+ */
357
+ type RewriteDestination = string | ((params: Record<string, string>, req: Request) => Promise<string> | string);
358
+ /**
359
+ * Rewrite rule configuration.
360
+ * Defines how to rewrite a URL pattern.
361
+ */
362
+ interface RewriteRule {
363
+ source: string;
364
+ destination: RewriteDestination;
365
+ has?: RewriteCondition[];
190
366
  }
191
- type ApiMiddleware = (ctx: ApiContext, next: () => Promise<void>) => void | Promise<void>;
367
+ /**
368
+ * Rewrite configuration.
369
+ * Array of rewrite rules to apply in order.
370
+ */
371
+ type RewriteConfig = RewriteRule[];
192
372
 
373
+ /**
374
+ * Realtime/WebSocket configuration
375
+ */
376
+ interface RealtimeConfig {
377
+ /** Enable realtime features */
378
+ enabled?: boolean;
379
+ /** Socket.IO server settings */
380
+ path?: string;
381
+ transports?: ("websocket" | "polling")[];
382
+ pingIntervalMs?: number;
383
+ pingTimeoutMs?: number;
384
+ maxPayloadBytes?: number;
385
+ /** Security */
386
+ allowedOrigins?: string | string[];
387
+ cors?: {
388
+ credentials?: boolean;
389
+ allowedHeaders?: string[];
390
+ };
391
+ /** Scaling configuration */
392
+ scale?: {
393
+ mode?: "single" | "cluster";
394
+ adapter?: {
395
+ name: "redis";
396
+ url: string;
397
+ pubClientName?: string;
398
+ subClientName?: string;
399
+ };
400
+ stateStore?: {
401
+ name: "memory" | "redis";
402
+ url?: string;
403
+ prefix?: string;
404
+ };
405
+ };
406
+ /** Rate limiting */
407
+ limits?: {
408
+ connectionsPerIp?: number;
409
+ eventsPerSecond?: number;
410
+ burst?: number;
411
+ };
412
+ /** Logging */
413
+ logging?: {
414
+ level?: "debug" | "info" | "warn" | "error";
415
+ pretty?: boolean;
416
+ };
417
+ }
193
418
  interface ServerConfig {
194
419
  bodyLimit?: string;
195
420
  corsOrigin?: string | string[] | boolean | ((origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) => void);
@@ -207,6 +432,8 @@ interface ServerConfig {
207
432
  includeSubDomains?: boolean;
208
433
  };
209
434
  };
435
+ /** Realtime/WebSocket configuration */
436
+ realtime?: RealtimeConfig;
210
437
  }
211
438
 
212
439
  interface BuildAppOptions {
@@ -216,6 +443,43 @@ interface BuildAppOptions {
216
443
  }
217
444
  declare function buildApp(options?: BuildAppOptions): Promise<void>;
218
445
 
446
+ /**
447
+ * Defines a WebSocket route with authentication, hooks, and event handlers.
448
+ *
449
+ * This is the new, recommended way to define WSS routes. It provides:
450
+ * - Type safety
451
+ * - Built-in validation
452
+ * - Auth hooks
453
+ * - Connection/disconnection hooks
454
+ * - Per-event validation, guards, and rate limiting
455
+ *
456
+ * @example
457
+ * ```ts
458
+ * import { defineWssRoute } from "@lolyjs/core";
459
+ * import { z } from "zod";
460
+ *
461
+ * export default defineWssRoute({
462
+ * auth: async (ctx) => {
463
+ * const token = ctx.req.headers.authorization;
464
+ * return await verifyToken(token);
465
+ * },
466
+ * onConnect: (ctx) => {
467
+ * console.log("User connected:", ctx.user?.id);
468
+ * },
469
+ * events: {
470
+ * message: {
471
+ * schema: z.object({ text: z.string() }),
472
+ * guard: ({ user }) => !!user,
473
+ * handler: (ctx) => {
474
+ * ctx.actions.broadcast("message", ctx.data);
475
+ * }
476
+ * }
477
+ * }
478
+ * });
479
+ * ```
480
+ */
481
+ declare function defineWssRoute<TUser = any>(definition: WssRouteDefinition<TUser>): WssRouteDefinition<TUser>;
482
+
219
483
  declare function withCache(fn: any, options: any): any;
220
484
 
221
485
  /**
@@ -332,12 +596,15 @@ interface RateLimitConfig {
332
596
  legacyHeaders?: boolean;
333
597
  skipSuccessfulRequests?: boolean;
334
598
  skipFailedRequests?: boolean;
599
+ keyGenerator?: (req: any) => string;
600
+ skip?: (req: any) => boolean;
335
601
  }
336
602
  /**
337
603
  * Creates a rate limiter middleware with configurable options.
338
604
  *
339
605
  * @param config - Rate limiting configuration
340
606
  * @returns Express rate limit middleware
607
+ * @throws Error if configuration is invalid
341
608
  */
342
609
  declare function createRateLimiter(config?: RateLimitConfig): express_rate_limit.RateLimitRequestHandler;
343
610
  /**
@@ -442,4 +709,4 @@ declare function requestLoggerMiddleware(options?: {
442
709
  */
443
710
  declare function getRequestLogger(req: Request): Logger;
444
711
 
445
- 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 };
712
+ 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 RewriteCondition, type RewriteConfig, type RewriteDestination, type RewriteRule, 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 };