@fitzzero/quickdraw-core 0.1.0 → 1.1.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.
@@ -1,7 +1,7 @@
1
- import { a as QuickdrawSocket, P as PrismaDelegate, b as BaseServiceOptions, I as InstallAdminMethodsOptions, S as ServiceRegistryInstance, B as BaseServiceInstance, c as QuickdrawServerOptions, Q as QuickdrawServerResult } from '../types-OhRxDBID.js';
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-D7oHWh4O.js';
2
+ import { Server, Socket } from 'socket.io';
2
3
  import { z } from 'zod';
3
- import { ACL, Logger, AccessLevel, ServiceMethodContext, ServiceMethodDefinition } from '../shared/index.js';
4
- import { Server } from 'socket.io';
4
+ import { ACL, Logger, AccessLevel, ServiceMethodContext, ServiceMethodDefinition, AdminServiceMeta, AdminFieldConfig } from '../shared/index.js';
5
5
  import 'http';
6
6
 
7
7
  /**
@@ -52,20 +52,33 @@ 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;
57
+ private adminMeta;
56
58
  constructor(options: BaseServiceOptions);
57
59
  /**
58
60
  * Set the Prisma delegate for this service.
59
61
  * Must be called in the constructor of derived classes.
60
62
  */
61
63
  protected setDelegate(delegate: PrismaDelegate<TEntity, TCreateInput, TUpdateInput>): void;
64
+ /**
65
+ * Set the Socket.io server instance for room-based broadcasts.
66
+ * Called automatically by ServiceRegistry during registration.
67
+ */
68
+ setIo(io: Server): void;
62
69
  /**
63
70
  * Get the delegate, throwing if not set.
64
71
  */
65
72
  protected getDelegate(): PrismaDelegate<TEntity, TCreateInput, TUpdateInput>;
73
+ /**
74
+ * Get the Socket.io room name for an entry.
75
+ * Used for room-based broadcasting across services.
76
+ */
77
+ getRoomName(entryId: string): string;
66
78
  /**
67
79
  * Subscribe a socket to an entity's updates.
68
80
  * Returns the current entity data if access is granted, null otherwise.
81
+ * Also joins the socket to the entity's Socket.io room for cross-service events.
69
82
  */
70
83
  subscribe(entryId: string, socket: QuickdrawSocket, requiredLevel?: AccessLevel): Promise<TEntity | null>;
71
84
  /**
@@ -76,8 +89,33 @@ declare abstract class BaseService<TEntity extends {
76
89
  * Remove a socket from all subscriptions (called on disconnect).
77
90
  */
78
91
  unsubscribeSocket(socket: QuickdrawSocket): void;
92
+ /**
93
+ * Emit a custom event to all subscribers of an entry via Socket.io rooms.
94
+ * This is useful for cross-service events (e.g., message service notifying chat subscribers).
95
+ *
96
+ * @param roomName - The Socket.io room name (use getRoomName for service rooms)
97
+ * @param eventName - The event name to emit
98
+ * @param data - The data to emit
99
+ *
100
+ * @example
101
+ * ```typescript
102
+ * // In MessageService, notify chat subscribers of a new message
103
+ * this.emitToRoom(
104
+ * `chatService:${message.chatId}`,
105
+ * 'chat:message',
106
+ * messageDTO
107
+ * );
108
+ * ```
109
+ */
110
+ protected emitToRoom(roomName: string, eventName: string, data: unknown): void;
111
+ /**
112
+ * Get any active socket for room-based operations.
113
+ */
114
+ private getAnySubscriberSocket;
79
115
  /**
80
116
  * Emit an update to all subscribers of an entity.
117
+ * Uses tier-based pre-filtering: computes filtered versions once,
118
+ * then selects the appropriate version per subscriber.
81
119
  */
82
120
  protected emitUpdate(entryId: string, data: Partial<TEntity>): void;
83
121
  /**
@@ -107,6 +145,56 @@ declare abstract class BaseService<TEntity extends {
107
145
  * Throws if access is denied.
108
146
  */
109
147
  ensureAccessForMethod(requiredLevel: AccessLevel, socket: QuickdrawSocket, entryId?: string): Promise<void>;
148
+ /**
149
+ * Get the list of fields that should be stripped for non-elevated subscribers.
150
+ * Override in derived classes to customize which fields are protected.
151
+ *
152
+ * @example
153
+ * ```typescript
154
+ * protected override getProtectedFields(): (keyof User)[] {
155
+ * return ['email', 'serviceAccess', 'discordId'];
156
+ * }
157
+ * ```
158
+ */
159
+ protected getProtectedFields(): (keyof TEntity)[];
160
+ /**
161
+ * Check if a socket has elevated access to an entity (receives full data).
162
+ * Default: owner (socket.userId === entryId) or service-level Admin.
163
+ * Override in derived classes for custom logic.
164
+ *
165
+ * @example
166
+ * ```typescript
167
+ * protected override hasElevatedAccess(socket: QuickdrawSocket, entryId: string): boolean {
168
+ * // Friends can see more data
169
+ * return super.hasElevatedAccess(socket, entryId) ||
170
+ * this.isFriend(socket.userId, entryId);
171
+ * }
172
+ * ```
173
+ */
174
+ protected hasElevatedAccess(socket: QuickdrawSocket, entryId: string): boolean;
175
+ /**
176
+ * Strip protected fields from an entity.
177
+ * Used internally by filterEntityForSubscriber.
178
+ */
179
+ protected stripProtectedFields<T extends Partial<TEntity>>(entity: T): T;
180
+ /**
181
+ * Filter entity data based on subscriber's access level.
182
+ * Override for complex filtering logic (e.g., multiple tiers).
183
+ *
184
+ * @example
185
+ * ```typescript
186
+ * protected override filterEntityForSubscriber(
187
+ * entity: Partial<TEntity>,
188
+ * socket: QuickdrawSocket,
189
+ * entryId: string
190
+ * ): Partial<TEntity> {
191
+ * if (this.hasElevatedAccess(socket, entryId)) return entity;
192
+ * if (this.isFriend(socket.userId, entryId)) return this.stripForFriends(entity);
193
+ * return this.stripProtectedFields(entity);
194
+ * }
195
+ * ```
196
+ */
197
+ protected filterEntityForSubscriber(entity: Partial<TEntity>, socket: QuickdrawSocket, entryId: string): Partial<TEntity>;
110
198
  /**
111
199
  * Find an entity by ID.
112
200
  */
@@ -139,8 +227,84 @@ declare abstract class BaseService<TEntity extends {
139
227
  /**
140
228
  * Install standard admin CRUD methods.
141
229
  * Call this in derived class constructor to expose admin endpoints.
230
+ *
231
+ * @example
232
+ * ```typescript
233
+ * this.installAdminMethods({
234
+ * expose: { list: true, get: true, create: true, update: true, delete: true },
235
+ * access: {
236
+ * list: "Admin",
237
+ * get: "Admin",
238
+ * create: "Admin",
239
+ * update: "Admin",
240
+ * delete: "Admin",
241
+ * setEntryACL: "Admin",
242
+ * getSubscribers: "Admin",
243
+ * reemit: "Admin",
244
+ * unsubscribeAll: "Admin",
245
+ * },
246
+ * schema: createEntitySchema,
247
+ * displayName: "Entities",
248
+ * });
249
+ * ```
250
+ */
251
+ protected installAdminMethods(options: InstallAdminMethodsOptions): void;
252
+ /**
253
+ * Convert service name to display name.
254
+ * e.g., "chatService" -> "Chats", "userService" -> "Users"
255
+ */
256
+ private toDisplayName;
257
+ /**
258
+ * Get admin metadata for this service.
259
+ * Returns the configured metadata or throws if not configured.
260
+ */
261
+ getAdminMeta(): AdminServiceMeta;
262
+ /**
263
+ * Admin method: List entities with pagination and filters.
142
264
  */
143
- protected installAdminMethods(_options: InstallAdminMethodsOptions): void;
265
+ protected adminList(payload: AdminListPayload): Promise<AdminListResponse<TEntity>>;
266
+ /**
267
+ * Admin method: Get a single entity by ID.
268
+ */
269
+ protected adminGet(id: string): Promise<TEntity | null>;
270
+ /**
271
+ * Admin method: Create an entity.
272
+ */
273
+ protected adminCreate(data: TCreateInput): Promise<TEntity>;
274
+ /**
275
+ * Admin method: Update an entity.
276
+ */
277
+ protected adminUpdate(id: string, data: TUpdateInput): Promise<TEntity | null>;
278
+ /**
279
+ * Admin method: Delete an entity.
280
+ */
281
+ protected adminDelete(id: string): Promise<{
282
+ success: boolean;
283
+ id: string;
284
+ }>;
285
+ /**
286
+ * Admin method: Set entry-level ACL.
287
+ */
288
+ protected adminSetEntryACL(payload: AdminSetACLPayload): Promise<TEntity | null>;
289
+ /**
290
+ * Admin method: Get active subscribers for an entity.
291
+ */
292
+ protected adminGetSubscribers(entryId: string): AdminSubscribersResponse;
293
+ /**
294
+ * Admin method: Re-emit current entity state to all subscribers.
295
+ * Useful when you've made direct DB changes or need to force-refresh clients.
296
+ */
297
+ protected adminReemit(entryId: string): Promise<{
298
+ success: boolean;
299
+ subscriberCount: number;
300
+ }>;
301
+ /**
302
+ * Admin method: Unsubscribe all sockets from an entity.
303
+ */
304
+ protected adminUnsubscribeAll(entryId: string): {
305
+ success: boolean;
306
+ unsubscribedCount: number;
307
+ };
144
308
  /**
145
309
  * Fields that should never be exposed or editable via admin methods.
146
310
  */
@@ -155,6 +319,7 @@ declare abstract class BaseService<TEntity extends {
155
319
  * - Wires methods to Socket.io events with standardized naming
156
320
  * - Handles subscription lifecycle
157
321
  * - Provides consistent error handling and logging
322
+ * - Uses a single connection handler for all services (memory efficient)
158
323
  *
159
324
  * @example
160
325
  * ```typescript
@@ -163,19 +328,43 @@ declare abstract class BaseService<TEntity extends {
163
328
  * registry.registerService('userService', userService);
164
329
  * ```
165
330
  */
331
+ interface ServiceRegistryOptions {
332
+ logger?: Logger;
333
+ /**
334
+ * Automatic method logging configuration.
335
+ * @default { enabled: true, logPayloads: false, logResponses: false }
336
+ */
337
+ methodLogging?: {
338
+ enabled?: boolean;
339
+ logPayloads?: boolean;
340
+ logResponses?: boolean;
341
+ };
342
+ }
166
343
  declare class ServiceRegistry implements ServiceRegistryInstance {
167
344
  private readonly io;
168
345
  private readonly services;
169
346
  private readonly logger;
170
- constructor(io: Server, options?: {
171
- logger?: Logger;
172
- });
347
+ private readonly methodLoggingEnabled;
348
+ private readonly logPayloads;
349
+ private readonly logResponses;
350
+ private connectionHandlerInstalled;
351
+ private readonly methodRegistry;
352
+ constructor(io: Server, options?: ServiceRegistryOptions);
353
+ /**
354
+ * Install the single connection handler that wires all registered services.
355
+ * This is called lazily on first service registration.
356
+ */
357
+ private installConnectionHandler;
358
+ /**
359
+ * Set up all event listeners for a newly connected socket.
360
+ */
361
+ private setupSocketListeners;
173
362
  /**
174
363
  * Register a service and auto-discover its public methods.
175
364
  */
176
365
  registerService(serviceName: string, service: BaseServiceInstance): void;
177
366
  /**
178
- * Discover and register all public methods from a service.
367
+ * Discover and store all public methods from a service.
179
368
  */
180
369
  private discoverServiceMethods;
181
370
  /**
@@ -227,6 +416,255 @@ declare class ServiceRegistry implements ServiceRegistryInstance {
227
416
  */
228
417
  declare function createQuickdrawServer(options: QuickdrawServerOptions): QuickdrawServerResult;
229
418
 
419
+ /**
420
+ * Redis adapter utilities for horizontal scaling.
421
+ *
422
+ * This module provides optional Redis integration for Socket.io
423
+ * to enable running multiple server instances.
424
+ *
425
+ * @example
426
+ * ```typescript
427
+ * import { createQuickdrawServer } from '@fitzzero/quickdraw-core/server';
428
+ * import { setupRedisAdapter } from '@fitzzero/quickdraw-core/server';
429
+ *
430
+ * const { io } = createQuickdrawServer({ ... });
431
+ *
432
+ * // Enable Redis for horizontal scaling
433
+ * await setupRedisAdapter(io, {
434
+ * host: process.env.REDIS_HOST ?? 'localhost',
435
+ * port: parseInt(process.env.REDIS_PORT ?? '6379'),
436
+ * });
437
+ * ```
438
+ */
439
+
440
+ /**
441
+ * Redis adapter configuration options.
442
+ */
443
+ interface RedisAdapterOptions {
444
+ /**
445
+ * Redis host (default: 'localhost')
446
+ */
447
+ host?: string;
448
+ /**
449
+ * Redis port (default: 6379)
450
+ */
451
+ port?: number;
452
+ /**
453
+ * Redis password (optional)
454
+ */
455
+ password?: string;
456
+ /**
457
+ * Redis database number (default: 0)
458
+ */
459
+ db?: number;
460
+ /**
461
+ * Key prefix for Socket.io adapter (default: 'socket.io')
462
+ */
463
+ keyPrefix?: string;
464
+ /**
465
+ * Logger instance
466
+ */
467
+ logger?: Logger;
468
+ }
469
+ /**
470
+ * Result of Redis adapter setup.
471
+ */
472
+ interface RedisAdapterResult {
473
+ /**
474
+ * Whether the adapter was successfully set up.
475
+ */
476
+ success: boolean;
477
+ /**
478
+ * Cleanup function to disconnect from Redis.
479
+ */
480
+ cleanup: () => Promise<void>;
481
+ }
482
+ /**
483
+ * Set up Redis adapter for Socket.io horizontal scaling.
484
+ *
485
+ * This function dynamically imports the @socket.io/redis-adapter package
486
+ * to avoid requiring it as a hard dependency.
487
+ *
488
+ * @param io - Socket.io server instance
489
+ * @param options - Redis configuration options
490
+ * @returns Promise resolving to setup result
491
+ *
492
+ * @example
493
+ * ```typescript
494
+ * const { io } = createQuickdrawServer({ ... });
495
+ *
496
+ * const { success, cleanup } = await setupRedisAdapter(io, {
497
+ * host: 'redis.example.com',
498
+ * port: 6379,
499
+ * password: process.env.REDIS_PASSWORD,
500
+ * });
501
+ *
502
+ * if (success) {
503
+ * console.log('Redis adapter enabled - horizontal scaling ready');
504
+ * }
505
+ *
506
+ * // On shutdown:
507
+ * await cleanup();
508
+ * ```
509
+ */
510
+ declare function setupRedisAdapter(io: Server, options?: RedisAdapterOptions): Promise<RedisAdapterResult>;
511
+ /**
512
+ * Check if Redis adapter packages are available.
513
+ *
514
+ * @returns Promise resolving to true if packages are installed
515
+ */
516
+ declare function isRedisAdapterAvailable(): Promise<boolean>;
517
+
518
+ /**
519
+ * Rate limiting middleware for Socket.io events.
520
+ *
521
+ * Provides per-socket rate limiting to prevent abuse and ensure
522
+ * fair resource usage across clients.
523
+ *
524
+ * @example
525
+ * ```typescript
526
+ * import { createRateLimiter, applyRateLimitMiddleware } from '@fitzzero/quickdraw-core/server';
527
+ *
528
+ * // Create a rate limiter
529
+ * const rateLimiter = createRateLimiter({
530
+ * windowMs: 60000, // 1 minute window
531
+ * maxRequests: 100, // 100 requests per window
532
+ * });
533
+ *
534
+ * // Apply to Socket.io server
535
+ * applyRateLimitMiddleware(io, rateLimiter);
536
+ * ```
537
+ */
538
+
539
+ /**
540
+ * Rate limiter configuration options.
541
+ */
542
+ interface RateLimitOptions {
543
+ /**
544
+ * Time window in milliseconds (default: 60000 = 1 minute)
545
+ */
546
+ windowMs?: number;
547
+ /**
548
+ * Maximum number of requests per window (default: 100)
549
+ */
550
+ maxRequests?: number;
551
+ /**
552
+ * Events to exclude from rate limiting (e.g., ['ping', 'pong'])
553
+ */
554
+ excludeEvents?: string[];
555
+ /**
556
+ * Custom key generator for grouping requests.
557
+ * Default uses socket.id.
558
+ */
559
+ keyGenerator?: (socket: Socket, eventName: string) => string;
560
+ /**
561
+ * Callback when rate limit is exceeded.
562
+ */
563
+ onRateLimitExceeded?: (socket: Socket, eventName: string) => void;
564
+ /**
565
+ * Logger instance
566
+ */
567
+ logger?: Logger;
568
+ }
569
+ /**
570
+ * Rate limiter instance.
571
+ */
572
+ interface RateLimiter {
573
+ /**
574
+ * Check if a request should be allowed.
575
+ * Returns true if allowed, false if rate limited.
576
+ */
577
+ check: (key: string) => boolean;
578
+ /**
579
+ * Get remaining requests for a key.
580
+ */
581
+ getRemaining: (key: string) => number;
582
+ /**
583
+ * Get time until rate limit resets for a key.
584
+ */
585
+ getResetTime: (key: string) => number;
586
+ /**
587
+ * Clear all rate limit entries (useful for testing).
588
+ */
589
+ clear: () => void;
590
+ /**
591
+ * Configuration options.
592
+ */
593
+ options: Required<Pick<RateLimitOptions, "windowMs" | "maxRequests" | "excludeEvents">>;
594
+ }
595
+ /**
596
+ * Create a rate limiter instance.
597
+ *
598
+ * @param options - Rate limiter configuration
599
+ * @returns Rate limiter instance
600
+ *
601
+ * @example
602
+ * ```typescript
603
+ * const limiter = createRateLimiter({
604
+ * windowMs: 60000,
605
+ * maxRequests: 100,
606
+ * });
607
+ *
608
+ * if (limiter.check('user-123')) {
609
+ * // Request allowed
610
+ * } else {
611
+ * // Rate limited
612
+ * }
613
+ * ```
614
+ */
615
+ declare function createRateLimiter(options?: RateLimitOptions): RateLimiter;
616
+ /**
617
+ * Apply rate limiting middleware to a Socket.io server.
618
+ *
619
+ * This intercepts all incoming events and checks them against
620
+ * the rate limiter before allowing them to proceed.
621
+ *
622
+ * @param io - Socket.io server instance
623
+ * @param rateLimiter - Rate limiter instance
624
+ * @param options - Additional options
625
+ *
626
+ * @example
627
+ * ```typescript
628
+ * const rateLimiter = createRateLimiter({ maxRequests: 100 });
629
+ *
630
+ * applyRateLimitMiddleware(io, rateLimiter, {
631
+ * keyGenerator: (socket) => socket.userId ?? socket.id,
632
+ * onRateLimitExceeded: (socket, event) => {
633
+ * socket.emit('error', { code: 'RATE_LIMITED', event });
634
+ * },
635
+ * });
636
+ * ```
637
+ */
638
+ declare function applyRateLimitMiddleware(io: Server, rateLimiter: RateLimiter, options?: Pick<RateLimitOptions, "keyGenerator" | "onRateLimitExceeded" | "logger">): void;
639
+ /**
640
+ * Create a tiered rate limiter with different limits for different event types.
641
+ *
642
+ * @param tiers - Map of event patterns to rate limit options
643
+ * @param defaultOptions - Default options for events not matching any tier
644
+ * @returns Rate limiter that applies different limits based on event name
645
+ *
646
+ * @example
647
+ * ```typescript
648
+ * const tieredLimiter = createTieredRateLimiter(
649
+ * {
650
+ * // Strict limits for write operations
651
+ * 'create*': { maxRequests: 10, windowMs: 60000 },
652
+ * 'update*': { maxRequests: 20, windowMs: 60000 },
653
+ * 'delete*': { maxRequests: 5, windowMs: 60000 },
654
+ * // Generous limits for reads
655
+ * 'list*': { maxRequests: 200, windowMs: 60000 },
656
+ * 'get*': { maxRequests: 200, windowMs: 60000 },
657
+ * },
658
+ * { maxRequests: 100, windowMs: 60000 } // Default
659
+ * );
660
+ * ```
661
+ */
662
+ declare function createTieredRateLimiter(tiers: Record<string, RateLimitOptions>, defaultOptions?: RateLimitOptions): {
663
+ check: (key: string, eventName: string) => boolean;
664
+ getRemaining: (key: string, eventName: string) => number;
665
+ clear: () => void;
666
+ };
667
+
230
668
  interface JWTPayload {
231
669
  userId: string;
232
670
  email?: string;
@@ -333,4 +771,134 @@ interface GoogleUser {
333
771
  */
334
772
  declare const googleProvider: OAuthProvider<GoogleUser>;
335
773
 
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 };
774
+ /**
775
+ * Environment variable validation utilities.
776
+ * Helps ensure required environment variables are set before the application starts.
777
+ */
778
+ interface ValidateEnvOptions {
779
+ /**
780
+ * List of required environment variable names.
781
+ * Validation will fail if any of these are missing or empty.
782
+ */
783
+ required: string[];
784
+ /**
785
+ * Only enforce validation in production environment.
786
+ * @default true
787
+ */
788
+ productionOnly?: boolean;
789
+ /**
790
+ * Custom error message prefix.
791
+ * @default "Missing required environment variables"
792
+ */
793
+ errorPrefix?: string;
794
+ }
795
+ interface EnvValidationResult {
796
+ success: boolean;
797
+ missing: string[];
798
+ message?: string;
799
+ }
800
+ /**
801
+ * Validate that required environment variables are set.
802
+ * Throws an error if any required variables are missing.
803
+ *
804
+ * @param options - Validation configuration
805
+ * @throws {Error} If required environment variables are missing
806
+ *
807
+ * @example
808
+ * ```typescript
809
+ * // Validate required vars on startup
810
+ * validateEnv({
811
+ * required: ['JWT_SECRET', 'DATABASE_URL', 'CLIENT_URL'],
812
+ * });
813
+ * ```
814
+ *
815
+ * @example
816
+ * ```typescript
817
+ * // Only validate in production
818
+ * validateEnv({
819
+ * required: ['JWT_SECRET', 'DATABASE_URL'],
820
+ * productionOnly: true,
821
+ * });
822
+ * ```
823
+ */
824
+ declare function validateEnv(options: ValidateEnvOptions): void;
825
+ /**
826
+ * Check environment variables without throwing.
827
+ * Returns a result object with validation status and missing variables.
828
+ *
829
+ * @param options - Validation configuration
830
+ * @returns Validation result with missing variables list
831
+ *
832
+ * @example
833
+ * ```typescript
834
+ * const result = checkEnv({ required: ['JWT_SECRET'] });
835
+ * if (!result.success) {
836
+ * console.warn('Missing env vars:', result.missing);
837
+ * }
838
+ * ```
839
+ */
840
+ declare function checkEnv(options: ValidateEnvOptions): EnvValidationResult;
841
+ /**
842
+ * Get an environment variable or throw if it's missing.
843
+ * Useful for individual variable access with validation.
844
+ *
845
+ * @param name - Environment variable name
846
+ * @param defaultValue - Optional default value (if provided, won't throw)
847
+ * @returns The environment variable value
848
+ * @throws {Error} If variable is missing and no default provided
849
+ *
850
+ * @example
851
+ * ```typescript
852
+ * const jwtSecret = requireEnv('JWT_SECRET');
853
+ * const port = requireEnv('PORT', '4000');
854
+ * ```
855
+ */
856
+ declare function requireEnv(name: string, defaultValue?: string): string;
857
+
858
+ /**
859
+ * Options for customizing field generation.
860
+ */
861
+ interface ZodToAdminFieldsOptions {
862
+ /** Fields to exclude from the result */
863
+ hiddenFields?: string[];
864
+ /** Override which fields appear in table */
865
+ tableColumns?: string[];
866
+ /** Override specific field configurations */
867
+ fieldOverrides?: Partial<Record<string, Partial<AdminFieldConfig>>>;
868
+ }
869
+ /**
870
+ * Convert a Zod object schema to AdminFieldConfig array.
871
+ *
872
+ * @param schema - A Zod object schema (z.object({ ... }))
873
+ * @param options - Optional customization
874
+ * @returns Array of AdminFieldConfig for each field
875
+ *
876
+ * @example
877
+ * ```typescript
878
+ * const userSchema = z.object({
879
+ * id: z.string(),
880
+ * name: z.string().optional(),
881
+ * email: z.string().email(),
882
+ * role: z.enum(["user", "admin"]),
883
+ * createdAt: z.date(),
884
+ * });
885
+ *
886
+ * const fields = zodToAdminFields(userSchema);
887
+ * // Returns field configs with appropriate types, labels, editability
888
+ * ```
889
+ */
890
+ declare function zodToAdminFields(schema: z.ZodTypeAny, options?: ZodToAdminFieldsOptions): AdminFieldConfig[];
891
+ /**
892
+ * Generate AdminFieldConfig for common entity fields that aren't in the create schema.
893
+ * These are typically auto-generated fields like id, createdAt, updatedAt.
894
+ */
895
+ declare function getDefaultEntityFields(): AdminFieldConfig[];
896
+ /**
897
+ * Merge schema-derived fields with default entity fields.
898
+ * Default fields come first (id, createdAt, updatedAt), then schema fields.
899
+ */
900
+ declare function mergeWithDefaultFields(schemaFields: AdminFieldConfig[], options?: {
901
+ includeDefaults?: string[];
902
+ }): AdminFieldConfig[];
903
+
904
+ export { AdminListPayload, AdminListResponse, AdminSetACLPayload, AdminSubscribersResponse, BaseService, BaseServiceInstance, BaseServiceOptions, type DiscordUser, type EnvValidationResult, 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, type ServiceRegistryOptions, type ValidateEnvOptions, type ZodToAdminFieldsOptions, applyRateLimitMiddleware, checkEnv, createJWT, createOAuthURL, createQuickdrawServer, createRateLimiter, createTieredRateLimiter, discordProvider, exchangeOAuthCode, getDefaultEntityFields, googleProvider, isRedisAdapterAvailable, mergeWithDefaultFields, requireEnv, setupRedisAdapter, validateEnv, verifyJWT, zodToAdminFields };