@fitzzero/quickdraw-core 0.1.0 → 1.0.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.
- package/README.md +122 -28
- package/dist/client/index.d.ts +30 -37
- package/dist/client/index.js +81 -42
- package/dist/client/index.js.map +1 -1
- package/dist/client/testing.d.ts +1 -1
- package/dist/client/testing.js +32 -0
- package/dist/client/testing.js.map +1 -1
- package/dist/server/index.d.ts +417 -5
- package/dist/server/index.js +643 -31
- package/dist/server/index.js.map +1 -1
- package/dist/server/testing.d.ts +1 -1
- package/dist/server/testing.js +45 -20
- package/dist/server/testing.js.map +1 -1
- package/dist/shared/index.d.ts +63 -1
- package/dist/shared/index.js.map +1 -1
- package/dist/{types-CUExGF7F.d.ts → types-BADirRXn.d.ts} +50 -8
- package/dist/{types-OhRxDBID.d.ts → types-CDhCEpTU.d.ts} +31 -1
- package/package.json +12 -13
package/dist/server/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as QuickdrawSocket, P as PrismaDelegate, b as BaseServiceOptions, I as InstallAdminMethodsOptions, S as ServiceRegistryInstance, B as BaseServiceInstance,
|
|
1
|
+
import { a as QuickdrawSocket, P as PrismaDelegate, b as BaseServiceOptions, I as InstallAdminMethodsOptions, A as AdminListPayload, c as AdminListResponse, d as AdminSetACLPayload, e as AdminSubscribersResponse, S as ServiceRegistryInstance, B as BaseServiceInstance, f as QuickdrawServerOptions, Q as QuickdrawServerResult } from '../types-CDhCEpTU.js';
|
|
2
|
+
import { Server, Socket } from 'socket.io';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import { ACL, Logger, AccessLevel, ServiceMethodContext, ServiceMethodDefinition } from '../shared/index.js';
|
|
4
|
-
import { Server } from 'socket.io';
|
|
5
5
|
import 'http';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -52,6 +52,7 @@ declare abstract class BaseService<TEntity extends {
|
|
|
52
52
|
protected readonly logger: Logger;
|
|
53
53
|
protected readonly subscribers: Map<string, Set<QuickdrawSocket>>;
|
|
54
54
|
protected delegate: PrismaDelegate<TEntity, TCreateInput, TUpdateInput> | undefined;
|
|
55
|
+
protected io: Server | null;
|
|
55
56
|
private readonly publicMethods;
|
|
56
57
|
constructor(options: BaseServiceOptions);
|
|
57
58
|
/**
|
|
@@ -59,13 +60,24 @@ declare abstract class BaseService<TEntity extends {
|
|
|
59
60
|
* Must be called in the constructor of derived classes.
|
|
60
61
|
*/
|
|
61
62
|
protected setDelegate(delegate: PrismaDelegate<TEntity, TCreateInput, TUpdateInput>): void;
|
|
63
|
+
/**
|
|
64
|
+
* Set the Socket.io server instance for room-based broadcasts.
|
|
65
|
+
* Called automatically by ServiceRegistry during registration.
|
|
66
|
+
*/
|
|
67
|
+
setIo(io: Server): void;
|
|
62
68
|
/**
|
|
63
69
|
* Get the delegate, throwing if not set.
|
|
64
70
|
*/
|
|
65
71
|
protected getDelegate(): PrismaDelegate<TEntity, TCreateInput, TUpdateInput>;
|
|
72
|
+
/**
|
|
73
|
+
* Get the Socket.io room name for an entry.
|
|
74
|
+
* Used for room-based broadcasting across services.
|
|
75
|
+
*/
|
|
76
|
+
getRoomName(entryId: string): string;
|
|
66
77
|
/**
|
|
67
78
|
* Subscribe a socket to an entity's updates.
|
|
68
79
|
* Returns the current entity data if access is granted, null otherwise.
|
|
80
|
+
* Also joins the socket to the entity's Socket.io room for cross-service events.
|
|
69
81
|
*/
|
|
70
82
|
subscribe(entryId: string, socket: QuickdrawSocket, requiredLevel?: AccessLevel): Promise<TEntity | null>;
|
|
71
83
|
/**
|
|
@@ -76,8 +88,33 @@ declare abstract class BaseService<TEntity extends {
|
|
|
76
88
|
* Remove a socket from all subscriptions (called on disconnect).
|
|
77
89
|
*/
|
|
78
90
|
unsubscribeSocket(socket: QuickdrawSocket): void;
|
|
91
|
+
/**
|
|
92
|
+
* Emit a custom event to all subscribers of an entry via Socket.io rooms.
|
|
93
|
+
* This is useful for cross-service events (e.g., message service notifying chat subscribers).
|
|
94
|
+
*
|
|
95
|
+
* @param roomName - The Socket.io room name (use getRoomName for service rooms)
|
|
96
|
+
* @param eventName - The event name to emit
|
|
97
|
+
* @param data - The data to emit
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```typescript
|
|
101
|
+
* // In MessageService, notify chat subscribers of a new message
|
|
102
|
+
* this.emitToRoom(
|
|
103
|
+
* `chatService:${message.chatId}`,
|
|
104
|
+
* 'chat:message',
|
|
105
|
+
* messageDTO
|
|
106
|
+
* );
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
protected emitToRoom(roomName: string, eventName: string, data: unknown): void;
|
|
110
|
+
/**
|
|
111
|
+
* Get any active socket for room-based operations.
|
|
112
|
+
*/
|
|
113
|
+
private getAnySubscriberSocket;
|
|
79
114
|
/**
|
|
80
115
|
* Emit an update to all subscribers of an entity.
|
|
116
|
+
* Uses tier-based pre-filtering: computes filtered versions once,
|
|
117
|
+
* then selects the appropriate version per subscriber.
|
|
81
118
|
*/
|
|
82
119
|
protected emitUpdate(entryId: string, data: Partial<TEntity>): void;
|
|
83
120
|
/**
|
|
@@ -107,6 +144,56 @@ declare abstract class BaseService<TEntity extends {
|
|
|
107
144
|
* Throws if access is denied.
|
|
108
145
|
*/
|
|
109
146
|
ensureAccessForMethod(requiredLevel: AccessLevel, socket: QuickdrawSocket, entryId?: string): Promise<void>;
|
|
147
|
+
/**
|
|
148
|
+
* Get the list of fields that should be stripped for non-elevated subscribers.
|
|
149
|
+
* Override in derived classes to customize which fields are protected.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```typescript
|
|
153
|
+
* protected override getProtectedFields(): (keyof User)[] {
|
|
154
|
+
* return ['email', 'serviceAccess', 'discordId'];
|
|
155
|
+
* }
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
protected getProtectedFields(): (keyof TEntity)[];
|
|
159
|
+
/**
|
|
160
|
+
* Check if a socket has elevated access to an entity (receives full data).
|
|
161
|
+
* Default: owner (socket.userId === entryId) or service-level Admin.
|
|
162
|
+
* Override in derived classes for custom logic.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* protected override hasElevatedAccess(socket: QuickdrawSocket, entryId: string): boolean {
|
|
167
|
+
* // Friends can see more data
|
|
168
|
+
* return super.hasElevatedAccess(socket, entryId) ||
|
|
169
|
+
* this.isFriend(socket.userId, entryId);
|
|
170
|
+
* }
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
protected hasElevatedAccess(socket: QuickdrawSocket, entryId: string): boolean;
|
|
174
|
+
/**
|
|
175
|
+
* Strip protected fields from an entity.
|
|
176
|
+
* Used internally by filterEntityForSubscriber.
|
|
177
|
+
*/
|
|
178
|
+
protected stripProtectedFields<T extends Partial<TEntity>>(entity: T): T;
|
|
179
|
+
/**
|
|
180
|
+
* Filter entity data based on subscriber's access level.
|
|
181
|
+
* Override for complex filtering logic (e.g., multiple tiers).
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```typescript
|
|
185
|
+
* protected override filterEntityForSubscriber(
|
|
186
|
+
* entity: Partial<TEntity>,
|
|
187
|
+
* socket: QuickdrawSocket,
|
|
188
|
+
* entryId: string
|
|
189
|
+
* ): Partial<TEntity> {
|
|
190
|
+
* if (this.hasElevatedAccess(socket, entryId)) return entity;
|
|
191
|
+
* if (this.isFriend(socket.userId, entryId)) return this.stripForFriends(entity);
|
|
192
|
+
* return this.stripProtectedFields(entity);
|
|
193
|
+
* }
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
protected filterEntityForSubscriber(entity: Partial<TEntity>, socket: QuickdrawSocket, entryId: string): Partial<TEntity>;
|
|
110
197
|
/**
|
|
111
198
|
* Find an entity by ID.
|
|
112
199
|
*/
|
|
@@ -139,8 +226,72 @@ declare abstract class BaseService<TEntity extends {
|
|
|
139
226
|
/**
|
|
140
227
|
* Install standard admin CRUD methods.
|
|
141
228
|
* Call this in derived class constructor to expose admin endpoints.
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* ```typescript
|
|
232
|
+
* this.installAdminMethods({
|
|
233
|
+
* expose: { list: true, get: true, create: true, update: true, delete: true },
|
|
234
|
+
* access: {
|
|
235
|
+
* list: "Admin",
|
|
236
|
+
* get: "Admin",
|
|
237
|
+
* create: "Admin",
|
|
238
|
+
* update: "Admin",
|
|
239
|
+
* delete: "Admin",
|
|
240
|
+
* setEntryACL: "Admin",
|
|
241
|
+
* getSubscribers: "Admin",
|
|
242
|
+
* reemit: "Admin",
|
|
243
|
+
* unsubscribeAll: "Admin",
|
|
244
|
+
* },
|
|
245
|
+
* });
|
|
246
|
+
* ```
|
|
247
|
+
*/
|
|
248
|
+
protected installAdminMethods(options: InstallAdminMethodsOptions): void;
|
|
249
|
+
/**
|
|
250
|
+
* Admin method: List entities with pagination and filters.
|
|
251
|
+
*/
|
|
252
|
+
protected adminList(payload: AdminListPayload): Promise<AdminListResponse<TEntity>>;
|
|
253
|
+
/**
|
|
254
|
+
* Admin method: Get a single entity by ID.
|
|
255
|
+
*/
|
|
256
|
+
protected adminGet(id: string): Promise<TEntity | null>;
|
|
257
|
+
/**
|
|
258
|
+
* Admin method: Create an entity.
|
|
259
|
+
*/
|
|
260
|
+
protected adminCreate(data: TCreateInput): Promise<TEntity>;
|
|
261
|
+
/**
|
|
262
|
+
* Admin method: Update an entity.
|
|
263
|
+
*/
|
|
264
|
+
protected adminUpdate(id: string, data: TUpdateInput): Promise<TEntity | null>;
|
|
265
|
+
/**
|
|
266
|
+
* Admin method: Delete an entity.
|
|
267
|
+
*/
|
|
268
|
+
protected adminDelete(id: string): Promise<{
|
|
269
|
+
success: boolean;
|
|
270
|
+
id: string;
|
|
271
|
+
}>;
|
|
272
|
+
/**
|
|
273
|
+
* Admin method: Set entry-level ACL.
|
|
274
|
+
*/
|
|
275
|
+
protected adminSetEntryACL(payload: AdminSetACLPayload): Promise<TEntity | null>;
|
|
276
|
+
/**
|
|
277
|
+
* Admin method: Get active subscribers for an entity.
|
|
278
|
+
*/
|
|
279
|
+
protected adminGetSubscribers(entryId: string): AdminSubscribersResponse;
|
|
280
|
+
/**
|
|
281
|
+
* Admin method: Re-emit current entity state to all subscribers.
|
|
282
|
+
* Useful when you've made direct DB changes or need to force-refresh clients.
|
|
283
|
+
*/
|
|
284
|
+
protected adminReemit(entryId: string): Promise<{
|
|
285
|
+
success: boolean;
|
|
286
|
+
subscriberCount: number;
|
|
287
|
+
}>;
|
|
288
|
+
/**
|
|
289
|
+
* Admin method: Unsubscribe all sockets from an entity.
|
|
142
290
|
*/
|
|
143
|
-
protected
|
|
291
|
+
protected adminUnsubscribeAll(entryId: string): {
|
|
292
|
+
success: boolean;
|
|
293
|
+
unsubscribedCount: number;
|
|
294
|
+
};
|
|
144
295
|
/**
|
|
145
296
|
* Fields that should never be exposed or editable via admin methods.
|
|
146
297
|
*/
|
|
@@ -155,6 +306,7 @@ declare abstract class BaseService<TEntity extends {
|
|
|
155
306
|
* - Wires methods to Socket.io events with standardized naming
|
|
156
307
|
* - Handles subscription lifecycle
|
|
157
308
|
* - Provides consistent error handling and logging
|
|
309
|
+
* - Uses a single connection handler for all services (memory efficient)
|
|
158
310
|
*
|
|
159
311
|
* @example
|
|
160
312
|
* ```typescript
|
|
@@ -167,15 +319,26 @@ declare class ServiceRegistry implements ServiceRegistryInstance {
|
|
|
167
319
|
private readonly io;
|
|
168
320
|
private readonly services;
|
|
169
321
|
private readonly logger;
|
|
322
|
+
private connectionHandlerInstalled;
|
|
323
|
+
private readonly methodRegistry;
|
|
170
324
|
constructor(io: Server, options?: {
|
|
171
325
|
logger?: Logger;
|
|
172
326
|
});
|
|
327
|
+
/**
|
|
328
|
+
* Install the single connection handler that wires all registered services.
|
|
329
|
+
* This is called lazily on first service registration.
|
|
330
|
+
*/
|
|
331
|
+
private installConnectionHandler;
|
|
332
|
+
/**
|
|
333
|
+
* Set up all event listeners for a newly connected socket.
|
|
334
|
+
*/
|
|
335
|
+
private setupSocketListeners;
|
|
173
336
|
/**
|
|
174
337
|
* Register a service and auto-discover its public methods.
|
|
175
338
|
*/
|
|
176
339
|
registerService(serviceName: string, service: BaseServiceInstance): void;
|
|
177
340
|
/**
|
|
178
|
-
* Discover and
|
|
341
|
+
* Discover and store all public methods from a service.
|
|
179
342
|
*/
|
|
180
343
|
private discoverServiceMethods;
|
|
181
344
|
/**
|
|
@@ -227,6 +390,255 @@ declare class ServiceRegistry implements ServiceRegistryInstance {
|
|
|
227
390
|
*/
|
|
228
391
|
declare function createQuickdrawServer(options: QuickdrawServerOptions): QuickdrawServerResult;
|
|
229
392
|
|
|
393
|
+
/**
|
|
394
|
+
* Redis adapter utilities for horizontal scaling.
|
|
395
|
+
*
|
|
396
|
+
* This module provides optional Redis integration for Socket.io
|
|
397
|
+
* to enable running multiple server instances.
|
|
398
|
+
*
|
|
399
|
+
* @example
|
|
400
|
+
* ```typescript
|
|
401
|
+
* import { createQuickdrawServer } from '@fitzzero/quickdraw-core/server';
|
|
402
|
+
* import { setupRedisAdapter } from '@fitzzero/quickdraw-core/server';
|
|
403
|
+
*
|
|
404
|
+
* const { io } = createQuickdrawServer({ ... });
|
|
405
|
+
*
|
|
406
|
+
* // Enable Redis for horizontal scaling
|
|
407
|
+
* await setupRedisAdapter(io, {
|
|
408
|
+
* host: process.env.REDIS_HOST ?? 'localhost',
|
|
409
|
+
* port: parseInt(process.env.REDIS_PORT ?? '6379'),
|
|
410
|
+
* });
|
|
411
|
+
* ```
|
|
412
|
+
*/
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Redis adapter configuration options.
|
|
416
|
+
*/
|
|
417
|
+
interface RedisAdapterOptions {
|
|
418
|
+
/**
|
|
419
|
+
* Redis host (default: 'localhost')
|
|
420
|
+
*/
|
|
421
|
+
host?: string;
|
|
422
|
+
/**
|
|
423
|
+
* Redis port (default: 6379)
|
|
424
|
+
*/
|
|
425
|
+
port?: number;
|
|
426
|
+
/**
|
|
427
|
+
* Redis password (optional)
|
|
428
|
+
*/
|
|
429
|
+
password?: string;
|
|
430
|
+
/**
|
|
431
|
+
* Redis database number (default: 0)
|
|
432
|
+
*/
|
|
433
|
+
db?: number;
|
|
434
|
+
/**
|
|
435
|
+
* Key prefix for Socket.io adapter (default: 'socket.io')
|
|
436
|
+
*/
|
|
437
|
+
keyPrefix?: string;
|
|
438
|
+
/**
|
|
439
|
+
* Logger instance
|
|
440
|
+
*/
|
|
441
|
+
logger?: Logger;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Result of Redis adapter setup.
|
|
445
|
+
*/
|
|
446
|
+
interface RedisAdapterResult {
|
|
447
|
+
/**
|
|
448
|
+
* Whether the adapter was successfully set up.
|
|
449
|
+
*/
|
|
450
|
+
success: boolean;
|
|
451
|
+
/**
|
|
452
|
+
* Cleanup function to disconnect from Redis.
|
|
453
|
+
*/
|
|
454
|
+
cleanup: () => Promise<void>;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Set up Redis adapter for Socket.io horizontal scaling.
|
|
458
|
+
*
|
|
459
|
+
* This function dynamically imports the @socket.io/redis-adapter package
|
|
460
|
+
* to avoid requiring it as a hard dependency.
|
|
461
|
+
*
|
|
462
|
+
* @param io - Socket.io server instance
|
|
463
|
+
* @param options - Redis configuration options
|
|
464
|
+
* @returns Promise resolving to setup result
|
|
465
|
+
*
|
|
466
|
+
* @example
|
|
467
|
+
* ```typescript
|
|
468
|
+
* const { io } = createQuickdrawServer({ ... });
|
|
469
|
+
*
|
|
470
|
+
* const { success, cleanup } = await setupRedisAdapter(io, {
|
|
471
|
+
* host: 'redis.example.com',
|
|
472
|
+
* port: 6379,
|
|
473
|
+
* password: process.env.REDIS_PASSWORD,
|
|
474
|
+
* });
|
|
475
|
+
*
|
|
476
|
+
* if (success) {
|
|
477
|
+
* console.log('Redis adapter enabled - horizontal scaling ready');
|
|
478
|
+
* }
|
|
479
|
+
*
|
|
480
|
+
* // On shutdown:
|
|
481
|
+
* await cleanup();
|
|
482
|
+
* ```
|
|
483
|
+
*/
|
|
484
|
+
declare function setupRedisAdapter(io: Server, options?: RedisAdapterOptions): Promise<RedisAdapterResult>;
|
|
485
|
+
/**
|
|
486
|
+
* Check if Redis adapter packages are available.
|
|
487
|
+
*
|
|
488
|
+
* @returns Promise resolving to true if packages are installed
|
|
489
|
+
*/
|
|
490
|
+
declare function isRedisAdapterAvailable(): Promise<boolean>;
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Rate limiting middleware for Socket.io events.
|
|
494
|
+
*
|
|
495
|
+
* Provides per-socket rate limiting to prevent abuse and ensure
|
|
496
|
+
* fair resource usage across clients.
|
|
497
|
+
*
|
|
498
|
+
* @example
|
|
499
|
+
* ```typescript
|
|
500
|
+
* import { createRateLimiter, applyRateLimitMiddleware } from '@fitzzero/quickdraw-core/server';
|
|
501
|
+
*
|
|
502
|
+
* // Create a rate limiter
|
|
503
|
+
* const rateLimiter = createRateLimiter({
|
|
504
|
+
* windowMs: 60000, // 1 minute window
|
|
505
|
+
* maxRequests: 100, // 100 requests per window
|
|
506
|
+
* });
|
|
507
|
+
*
|
|
508
|
+
* // Apply to Socket.io server
|
|
509
|
+
* applyRateLimitMiddleware(io, rateLimiter);
|
|
510
|
+
* ```
|
|
511
|
+
*/
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Rate limiter configuration options.
|
|
515
|
+
*/
|
|
516
|
+
interface RateLimitOptions {
|
|
517
|
+
/**
|
|
518
|
+
* Time window in milliseconds (default: 60000 = 1 minute)
|
|
519
|
+
*/
|
|
520
|
+
windowMs?: number;
|
|
521
|
+
/**
|
|
522
|
+
* Maximum number of requests per window (default: 100)
|
|
523
|
+
*/
|
|
524
|
+
maxRequests?: number;
|
|
525
|
+
/**
|
|
526
|
+
* Events to exclude from rate limiting (e.g., ['ping', 'pong'])
|
|
527
|
+
*/
|
|
528
|
+
excludeEvents?: string[];
|
|
529
|
+
/**
|
|
530
|
+
* Custom key generator for grouping requests.
|
|
531
|
+
* Default uses socket.id.
|
|
532
|
+
*/
|
|
533
|
+
keyGenerator?: (socket: Socket, eventName: string) => string;
|
|
534
|
+
/**
|
|
535
|
+
* Callback when rate limit is exceeded.
|
|
536
|
+
*/
|
|
537
|
+
onRateLimitExceeded?: (socket: Socket, eventName: string) => void;
|
|
538
|
+
/**
|
|
539
|
+
* Logger instance
|
|
540
|
+
*/
|
|
541
|
+
logger?: Logger;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Rate limiter instance.
|
|
545
|
+
*/
|
|
546
|
+
interface RateLimiter {
|
|
547
|
+
/**
|
|
548
|
+
* Check if a request should be allowed.
|
|
549
|
+
* Returns true if allowed, false if rate limited.
|
|
550
|
+
*/
|
|
551
|
+
check: (key: string) => boolean;
|
|
552
|
+
/**
|
|
553
|
+
* Get remaining requests for a key.
|
|
554
|
+
*/
|
|
555
|
+
getRemaining: (key: string) => number;
|
|
556
|
+
/**
|
|
557
|
+
* Get time until rate limit resets for a key.
|
|
558
|
+
*/
|
|
559
|
+
getResetTime: (key: string) => number;
|
|
560
|
+
/**
|
|
561
|
+
* Clear all rate limit entries (useful for testing).
|
|
562
|
+
*/
|
|
563
|
+
clear: () => void;
|
|
564
|
+
/**
|
|
565
|
+
* Configuration options.
|
|
566
|
+
*/
|
|
567
|
+
options: Required<Pick<RateLimitOptions, "windowMs" | "maxRequests" | "excludeEvents">>;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Create a rate limiter instance.
|
|
571
|
+
*
|
|
572
|
+
* @param options - Rate limiter configuration
|
|
573
|
+
* @returns Rate limiter instance
|
|
574
|
+
*
|
|
575
|
+
* @example
|
|
576
|
+
* ```typescript
|
|
577
|
+
* const limiter = createRateLimiter({
|
|
578
|
+
* windowMs: 60000,
|
|
579
|
+
* maxRequests: 100,
|
|
580
|
+
* });
|
|
581
|
+
*
|
|
582
|
+
* if (limiter.check('user-123')) {
|
|
583
|
+
* // Request allowed
|
|
584
|
+
* } else {
|
|
585
|
+
* // Rate limited
|
|
586
|
+
* }
|
|
587
|
+
* ```
|
|
588
|
+
*/
|
|
589
|
+
declare function createRateLimiter(options?: RateLimitOptions): RateLimiter;
|
|
590
|
+
/**
|
|
591
|
+
* Apply rate limiting middleware to a Socket.io server.
|
|
592
|
+
*
|
|
593
|
+
* This intercepts all incoming events and checks them against
|
|
594
|
+
* the rate limiter before allowing them to proceed.
|
|
595
|
+
*
|
|
596
|
+
* @param io - Socket.io server instance
|
|
597
|
+
* @param rateLimiter - Rate limiter instance
|
|
598
|
+
* @param options - Additional options
|
|
599
|
+
*
|
|
600
|
+
* @example
|
|
601
|
+
* ```typescript
|
|
602
|
+
* const rateLimiter = createRateLimiter({ maxRequests: 100 });
|
|
603
|
+
*
|
|
604
|
+
* applyRateLimitMiddleware(io, rateLimiter, {
|
|
605
|
+
* keyGenerator: (socket) => socket.userId ?? socket.id,
|
|
606
|
+
* onRateLimitExceeded: (socket, event) => {
|
|
607
|
+
* socket.emit('error', { code: 'RATE_LIMITED', event });
|
|
608
|
+
* },
|
|
609
|
+
* });
|
|
610
|
+
* ```
|
|
611
|
+
*/
|
|
612
|
+
declare function applyRateLimitMiddleware(io: Server, rateLimiter: RateLimiter, options?: Pick<RateLimitOptions, "keyGenerator" | "onRateLimitExceeded" | "logger">): void;
|
|
613
|
+
/**
|
|
614
|
+
* Create a tiered rate limiter with different limits for different event types.
|
|
615
|
+
*
|
|
616
|
+
* @param tiers - Map of event patterns to rate limit options
|
|
617
|
+
* @param defaultOptions - Default options for events not matching any tier
|
|
618
|
+
* @returns Rate limiter that applies different limits based on event name
|
|
619
|
+
*
|
|
620
|
+
* @example
|
|
621
|
+
* ```typescript
|
|
622
|
+
* const tieredLimiter = createTieredRateLimiter(
|
|
623
|
+
* {
|
|
624
|
+
* // Strict limits for write operations
|
|
625
|
+
* 'create*': { maxRequests: 10, windowMs: 60000 },
|
|
626
|
+
* 'update*': { maxRequests: 20, windowMs: 60000 },
|
|
627
|
+
* 'delete*': { maxRequests: 5, windowMs: 60000 },
|
|
628
|
+
* // Generous limits for reads
|
|
629
|
+
* 'list*': { maxRequests: 200, windowMs: 60000 },
|
|
630
|
+
* 'get*': { maxRequests: 200, windowMs: 60000 },
|
|
631
|
+
* },
|
|
632
|
+
* { maxRequests: 100, windowMs: 60000 } // Default
|
|
633
|
+
* );
|
|
634
|
+
* ```
|
|
635
|
+
*/
|
|
636
|
+
declare function createTieredRateLimiter(tiers: Record<string, RateLimitOptions>, defaultOptions?: RateLimitOptions): {
|
|
637
|
+
check: (key: string, eventName: string) => boolean;
|
|
638
|
+
getRemaining: (key: string, eventName: string) => number;
|
|
639
|
+
clear: () => void;
|
|
640
|
+
};
|
|
641
|
+
|
|
230
642
|
interface JWTPayload {
|
|
231
643
|
userId: string;
|
|
232
644
|
email?: string;
|
|
@@ -333,4 +745,4 @@ interface GoogleUser {
|
|
|
333
745
|
*/
|
|
334
746
|
declare const googleProvider: OAuthProvider<GoogleUser>;
|
|
335
747
|
|
|
336
|
-
export { BaseService, BaseServiceInstance, BaseServiceOptions, type DiscordUser, type GoogleUser, InstallAdminMethodsOptions, type JWTPayload, type OAuthConfig, type OAuthProvider, type OAuthTokenResponse, PrismaDelegate, QuickdrawServerOptions, QuickdrawServerResult, QuickdrawSocket, ServiceRegistry, ServiceRegistryInstance, createJWT, createOAuthURL, createQuickdrawServer, discordProvider, exchangeOAuthCode, googleProvider, verifyJWT };
|
|
748
|
+
export { AdminListPayload, AdminListResponse, AdminSetACLPayload, AdminSubscribersResponse, BaseService, BaseServiceInstance, BaseServiceOptions, type DiscordUser, type GoogleUser, InstallAdminMethodsOptions, type JWTPayload, type OAuthConfig, type OAuthProvider, type OAuthTokenResponse, PrismaDelegate, QuickdrawServerOptions, QuickdrawServerResult, QuickdrawSocket, type RateLimitOptions, type RateLimiter, type RedisAdapterOptions, type RedisAdapterResult, ServiceRegistry, ServiceRegistryInstance, applyRateLimitMiddleware, createJWT, createOAuthURL, createQuickdrawServer, createRateLimiter, createTieredRateLimiter, discordProvider, exchangeOAuthCode, googleProvider, isRedisAdapterAvailable, setupRedisAdapter, verifyJWT };
|