@buenojs/bueno 0.8.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 (120) hide show
  1. package/.env.example +109 -0
  2. package/.github/workflows/ci.yml +31 -0
  3. package/LICENSE +21 -0
  4. package/README.md +892 -0
  5. package/architecture.md +652 -0
  6. package/bun.lock +70 -0
  7. package/dist/cli/index.js +3233 -0
  8. package/dist/index.js +9014 -0
  9. package/package.json +77 -0
  10. package/src/cache/index.ts +795 -0
  11. package/src/cli/ARCHITECTURE.md +837 -0
  12. package/src/cli/bin.ts +10 -0
  13. package/src/cli/commands/build.ts +425 -0
  14. package/src/cli/commands/dev.ts +248 -0
  15. package/src/cli/commands/generate.ts +541 -0
  16. package/src/cli/commands/help.ts +55 -0
  17. package/src/cli/commands/index.ts +112 -0
  18. package/src/cli/commands/migration.ts +355 -0
  19. package/src/cli/commands/new.ts +804 -0
  20. package/src/cli/commands/start.ts +208 -0
  21. package/src/cli/core/args.ts +283 -0
  22. package/src/cli/core/console.ts +349 -0
  23. package/src/cli/core/index.ts +60 -0
  24. package/src/cli/core/prompt.ts +424 -0
  25. package/src/cli/core/spinner.ts +265 -0
  26. package/src/cli/index.ts +135 -0
  27. package/src/cli/templates/deploy.ts +295 -0
  28. package/src/cli/templates/docker.ts +307 -0
  29. package/src/cli/templates/index.ts +24 -0
  30. package/src/cli/utils/fs.ts +428 -0
  31. package/src/cli/utils/index.ts +8 -0
  32. package/src/cli/utils/strings.ts +197 -0
  33. package/src/config/env.ts +408 -0
  34. package/src/config/index.ts +506 -0
  35. package/src/config/loader.ts +329 -0
  36. package/src/config/merge.ts +285 -0
  37. package/src/config/types.ts +320 -0
  38. package/src/config/validation.ts +441 -0
  39. package/src/container/forward-ref.ts +143 -0
  40. package/src/container/index.ts +386 -0
  41. package/src/context/index.ts +360 -0
  42. package/src/database/index.ts +1142 -0
  43. package/src/database/migrations/index.ts +371 -0
  44. package/src/database/schema/index.ts +619 -0
  45. package/src/frontend/api-routes.ts +640 -0
  46. package/src/frontend/bundler.ts +643 -0
  47. package/src/frontend/console-client.ts +419 -0
  48. package/src/frontend/console-stream.ts +587 -0
  49. package/src/frontend/dev-server.ts +846 -0
  50. package/src/frontend/file-router.ts +611 -0
  51. package/src/frontend/frameworks/index.ts +106 -0
  52. package/src/frontend/frameworks/react.ts +85 -0
  53. package/src/frontend/frameworks/solid.ts +104 -0
  54. package/src/frontend/frameworks/svelte.ts +110 -0
  55. package/src/frontend/frameworks/vue.ts +92 -0
  56. package/src/frontend/hmr-client.ts +663 -0
  57. package/src/frontend/hmr.ts +728 -0
  58. package/src/frontend/index.ts +342 -0
  59. package/src/frontend/islands.ts +552 -0
  60. package/src/frontend/isr.ts +555 -0
  61. package/src/frontend/layout.ts +475 -0
  62. package/src/frontend/ssr/react.ts +446 -0
  63. package/src/frontend/ssr/solid.ts +523 -0
  64. package/src/frontend/ssr/svelte.ts +546 -0
  65. package/src/frontend/ssr/vue.ts +504 -0
  66. package/src/frontend/ssr.ts +699 -0
  67. package/src/frontend/types.ts +2274 -0
  68. package/src/health/index.ts +604 -0
  69. package/src/index.ts +410 -0
  70. package/src/lock/index.ts +587 -0
  71. package/src/logger/index.ts +444 -0
  72. package/src/logger/transports/index.ts +969 -0
  73. package/src/metrics/index.ts +494 -0
  74. package/src/middleware/built-in.ts +360 -0
  75. package/src/middleware/index.ts +94 -0
  76. package/src/modules/filters.ts +458 -0
  77. package/src/modules/guards.ts +405 -0
  78. package/src/modules/index.ts +1256 -0
  79. package/src/modules/interceptors.ts +574 -0
  80. package/src/modules/lazy.ts +418 -0
  81. package/src/modules/lifecycle.ts +478 -0
  82. package/src/modules/metadata.ts +90 -0
  83. package/src/modules/pipes.ts +626 -0
  84. package/src/router/index.ts +339 -0
  85. package/src/router/linear.ts +371 -0
  86. package/src/router/regex.ts +292 -0
  87. package/src/router/tree.ts +562 -0
  88. package/src/rpc/index.ts +1263 -0
  89. package/src/security/index.ts +436 -0
  90. package/src/ssg/index.ts +631 -0
  91. package/src/storage/index.ts +456 -0
  92. package/src/telemetry/index.ts +1097 -0
  93. package/src/testing/index.ts +1586 -0
  94. package/src/types/index.ts +236 -0
  95. package/src/types/optional-deps.d.ts +219 -0
  96. package/src/validation/index.ts +276 -0
  97. package/src/websocket/index.ts +1004 -0
  98. package/tests/integration/cli.test.ts +1016 -0
  99. package/tests/integration/fullstack.test.ts +234 -0
  100. package/tests/unit/cache.test.ts +174 -0
  101. package/tests/unit/cli-commands.test.ts +892 -0
  102. package/tests/unit/cli.test.ts +1258 -0
  103. package/tests/unit/container.test.ts +279 -0
  104. package/tests/unit/context.test.ts +221 -0
  105. package/tests/unit/database.test.ts +183 -0
  106. package/tests/unit/linear-router.test.ts +280 -0
  107. package/tests/unit/lock.test.ts +336 -0
  108. package/tests/unit/middleware.test.ts +184 -0
  109. package/tests/unit/modules.test.ts +142 -0
  110. package/tests/unit/pubsub.test.ts +257 -0
  111. package/tests/unit/regex-router.test.ts +265 -0
  112. package/tests/unit/router.test.ts +373 -0
  113. package/tests/unit/rpc.test.ts +1248 -0
  114. package/tests/unit/security.test.ts +174 -0
  115. package/tests/unit/telemetry.test.ts +371 -0
  116. package/tests/unit/test-cache.test.ts +110 -0
  117. package/tests/unit/test-database.test.ts +282 -0
  118. package/tests/unit/tree-router.test.ts +325 -0
  119. package/tests/unit/validation.test.ts +794 -0
  120. package/tsconfig.json +27 -0
@@ -0,0 +1,574 @@
1
+ /**
2
+ * Interceptors System
3
+ *
4
+ * Interceptors wrap around the handler execution and can modify both the
5
+ * request before it reaches the handler and the response after.
6
+ * They run after guards but before pipes in the request pipeline.
7
+ *
8
+ * Execution Order:
9
+ * Incoming Request → Guards → Interceptors (pre) → Pipes → Handler → Interceptors (post) → Response
10
+ *
11
+ * Interceptors can:
12
+ * - Log request/response information
13
+ * - Transform response data
14
+ * - Handle timeouts
15
+ * - Cache responses
16
+ * - Add headers or modify request/response
17
+ */
18
+
19
+ import type { Context } from "../context";
20
+ import type { Token } from "../container";
21
+
22
+ // ============= Types =============
23
+
24
+ /**
25
+ * Call handler interface for continuing the interceptor chain
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * const result = await next.handle();
30
+ * ```
31
+ */
32
+ export interface CallHandler<T = unknown> {
33
+ /**
34
+ * Execute the next handler in the chain
35
+ * @returns Promise of the handler result
36
+ */
37
+ handle(): Promise<T>;
38
+ }
39
+
40
+ /**
41
+ * Interceptor interface for request/response transformation
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * @Injectable()
46
+ * class LoggingInterceptor implements NestInterceptor {
47
+ * async intercept(context: Context, next: CallHandler) {
48
+ * console.log('Before...');
49
+ * const result = await next.handle();
50
+ * console.log('After...');
51
+ * return result;
52
+ * }
53
+ * }
54
+ * ```
55
+ */
56
+ export interface NestInterceptor<T = unknown, R = unknown> {
57
+ intercept(context: Context, next: CallHandler<T>): Promise<R> | R;
58
+ }
59
+
60
+ /**
61
+ * Interceptor function type (for functional interceptors)
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * const loggingInterceptor: InterceptorFn = async (context, next) => {
66
+ * console.log('Request started');
67
+ * const result = await next();
68
+ * console.log('Request completed');
69
+ * return result;
70
+ * };
71
+ * ```
72
+ */
73
+ export type InterceptorFn = (
74
+ context: Context,
75
+ next: () => Promise<unknown>
76
+ ) => Promise<unknown> | unknown;
77
+
78
+ /**
79
+ * Interceptor type - can be:
80
+ * - A token for an interceptor class registered in the container
81
+ * - An interceptor class instance
82
+ * - An interceptor function
83
+ */
84
+ export type Interceptor = Token<NestInterceptor> | NestInterceptor | InterceptorFn;
85
+
86
+ // ============= Metadata Storage =============
87
+
88
+ // Type alias for class constructors
89
+ type Constructor = new (...args: unknown[]) => unknown;
90
+
91
+ // WeakMap for storing interceptors metadata on classes
92
+ const interceptorsClassMetadata = new WeakMap<Constructor, Interceptor[]>();
93
+
94
+ // WeakMap for storing interceptors metadata on method prototypes
95
+ const interceptorsMethodMetadata = new WeakMap<object, Map<string | symbol, Interceptor[]>>();
96
+
97
+ /**
98
+ * Set interceptors on a class constructor
99
+ */
100
+ function setClassInterceptors(target: Constructor, interceptors: Interceptor[]): void {
101
+ interceptorsClassMetadata.set(target, interceptors);
102
+ }
103
+
104
+ /**
105
+ * Get interceptors from a class constructor
106
+ */
107
+ export function getClassInterceptors(target: Constructor): Interceptor[] | undefined {
108
+ return interceptorsClassMetadata.get(target);
109
+ }
110
+
111
+ /**
112
+ * Set interceptors on a method
113
+ */
114
+ function setMethodInterceptors(
115
+ target: object,
116
+ propertyKey: string | symbol,
117
+ interceptors: Interceptor[],
118
+ ): void {
119
+ if (!interceptorsMethodMetadata.has(target)) {
120
+ interceptorsMethodMetadata.set(target, new Map());
121
+ }
122
+ interceptorsMethodMetadata.get(target)?.set(propertyKey, interceptors);
123
+ }
124
+
125
+ /**
126
+ * Get interceptors from a method
127
+ */
128
+ export function getMethodInterceptors(
129
+ target: object,
130
+ propertyKey: string | symbol,
131
+ ): Interceptor[] | undefined {
132
+ return interceptorsMethodMetadata.get(target)?.get(propertyKey);
133
+ }
134
+
135
+ // ============= Decorators =============
136
+
137
+ /**
138
+ * Decorator to apply interceptors to a controller class or method.
139
+ * Interceptors are executed in the order they are provided.
140
+ *
141
+ * @param interceptors - Interceptors to apply
142
+ * @returns ClassDecorator & MethodDecorator
143
+ *
144
+ * @example
145
+ * ```typescript
146
+ * // Apply to all methods in controller
147
+ * @Controller('users')
148
+ * @UseInterceptors(LoggingInterceptor)
149
+ * class UsersController {
150
+ * @Get()
151
+ * @UseInterceptors(TransformInterceptor) // Additional interceptor
152
+ * getUsers() {}
153
+ * }
154
+ * ```
155
+ */
156
+ export function UseInterceptors(...interceptors: Interceptor[]): MethodDecorator & ClassDecorator {
157
+ const decorator = (
158
+ target: unknown,
159
+ propertyKey?: string | symbol,
160
+ descriptor?: PropertyDescriptor,
161
+ ): PropertyDescriptor | void => {
162
+ if (propertyKey !== undefined && descriptor !== undefined) {
163
+ // Method decorator
164
+ const targetObj = target as object;
165
+ const existingInterceptors = getMethodInterceptors(targetObj, propertyKey) ?? [];
166
+ setMethodInterceptors(targetObj, propertyKey, [...existingInterceptors, ...interceptors]);
167
+ return descriptor;
168
+ } else {
169
+ // Class decorator
170
+ const targetClass = target as Constructor;
171
+ const existingInterceptors = getClassInterceptors(targetClass) ?? [];
172
+ setClassInterceptors(targetClass, [...existingInterceptors, ...interceptors]);
173
+ }
174
+ };
175
+ return decorator as MethodDecorator & ClassDecorator;
176
+ }
177
+
178
+ // ============= Built-in Interceptors =============
179
+
180
+ /**
181
+ * LoggingInterceptor - Logs request start, end, and duration
182
+ *
183
+ * @example
184
+ * ```typescript
185
+ * @Controller('api')
186
+ * @UseInterceptors(LoggingInterceptor)
187
+ * class ApiController {
188
+ * @Get('users')
189
+ * getUsers() {}
190
+ * }
191
+ * // Output:
192
+ * // [GET] /api/users - Started
193
+ * // [GET] /api/users - Completed in 15ms
194
+ * ```
195
+ */
196
+ export class LoggingInterceptor implements NestInterceptor {
197
+ async intercept(context: Context, next: CallHandler): Promise<unknown> {
198
+ const method = context.method;
199
+ const path = context.path;
200
+ const startTime = Date.now();
201
+
202
+ console.log(`[${method}] ${path} - Started`);
203
+
204
+ try {
205
+ const result = await next.handle();
206
+ const duration = Date.now() - startTime;
207
+ console.log(`[${method}] ${path} - Completed in ${duration}ms`);
208
+ return result;
209
+ } catch (error) {
210
+ const duration = Date.now() - startTime;
211
+ console.error(`[${method}] ${path} - Failed in ${duration}ms`, error);
212
+ throw error;
213
+ }
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Response wrapper interface for TransformInterceptor
219
+ */
220
+ export interface TransformResponse<T> {
221
+ data: T;
222
+ timestamp: string;
223
+ }
224
+
225
+ /**
226
+ * TransformInterceptor - Wraps response in standard format
227
+ *
228
+ * @example
229
+ * ```typescript
230
+ * @Get()
231
+ * @UseInterceptors(new TransformInterceptor())
232
+ * getUsers() {
233
+ * return [{ id: 1, name: 'John' }];
234
+ * }
235
+ * // Response: { data: [{ id: 1, name: 'John' }], timestamp: '2024-01-15T10:30:00.000Z' }
236
+ * ```
237
+ */
238
+ export class TransformInterceptor<T = unknown> implements NestInterceptor<T, TransformResponse<T>> {
239
+ async intercept(context: Context, next: CallHandler<T>): Promise<TransformResponse<T>> {
240
+ const result = await next.handle();
241
+ return {
242
+ data: result,
243
+ timestamp: new Date().toISOString(),
244
+ };
245
+ }
246
+ }
247
+
248
+ /**
249
+ * TimeoutInterceptor - Aborts request after specified timeout
250
+ *
251
+ * @example
252
+ * ```typescript
253
+ * @Get('slow-endpoint')
254
+ * @UseInterceptors(new TimeoutInterceptor(5000)) // 5 second timeout
255
+ * slowOperation() {}
256
+ * ```
257
+ */
258
+ export class TimeoutInterceptor implements NestInterceptor {
259
+ constructor(private timeoutMs: number) {}
260
+
261
+ async intercept(context: Context, next: CallHandler): Promise<unknown> {
262
+ // Create a timeout promise that rejects
263
+ const timeoutPromise = new Promise<never>((_, reject) => {
264
+ setTimeout(() => {
265
+ reject(new Error(`Request timeout after ${this.timeoutMs}ms`));
266
+ }, this.timeoutMs);
267
+ });
268
+
269
+ // Race between the handler and timeout
270
+ return Promise.race([next.handle(), timeoutPromise]);
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Cache entry interface
276
+ */
277
+ interface CacheEntry<T = unknown> {
278
+ value: T;
279
+ expiresAt: number;
280
+ }
281
+
282
+ /**
283
+ * CacheInterceptor - Caches responses in memory
284
+ *
285
+ * Note: This is a simple in-memory cache. For production use,
286
+ * consider using a distributed cache like Redis.
287
+ *
288
+ * @example
289
+ * ```typescript
290
+ * @Get('users')
291
+ * @UseInterceptors(new CacheInterceptor(60000)) // Cache for 60 seconds
292
+ * getUsers() {
293
+ * return this.userService.findAll(); // Only called if not cached
294
+ * }
295
+ * ```
296
+ */
297
+ export class CacheInterceptor implements NestInterceptor {
298
+ private static cache = new Map<string, CacheEntry>();
299
+ private static cleanupInterval: Timer | null = null;
300
+
301
+ constructor(private ttlMs: number = 60000) {
302
+ // Setup periodic cleanup of expired entries
303
+ CacheInterceptor.setupCleanup();
304
+ }
305
+
306
+ private static setupCleanup(): void {
307
+ if (CacheInterceptor.cleanupInterval === null) {
308
+ CacheInterceptor.cleanupInterval = setInterval(() => {
309
+ const now = Date.now();
310
+ for (const [key, entry] of CacheInterceptor.cache.entries()) {
311
+ if (entry.expiresAt < now) {
312
+ CacheInterceptor.cache.delete(key);
313
+ }
314
+ }
315
+ }, 60000); // Cleanup every minute
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Generate cache key from context
321
+ */
322
+ private getCacheKey(context: Context): string {
323
+ return `${context.method}:${context.path}:${context.url.search}`;
324
+ }
325
+
326
+ /**
327
+ * Clear all cached entries
328
+ */
329
+ static clearCache(): void {
330
+ CacheInterceptor.cache.clear();
331
+ }
332
+
333
+ /**
334
+ * Clear cache for a specific key pattern
335
+ */
336
+ static clearCachePattern(pattern: string): void {
337
+ for (const key of CacheInterceptor.cache.keys()) {
338
+ if (key.includes(pattern)) {
339
+ CacheInterceptor.cache.delete(key);
340
+ }
341
+ }
342
+ }
343
+
344
+ async intercept(context: Context, next: CallHandler): Promise<unknown> {
345
+ const cacheKey = this.getCacheKey(context);
346
+ const now = Date.now();
347
+
348
+ // Check if we have a valid cached response
349
+ const cached = CacheInterceptor.cache.get(cacheKey);
350
+ if (cached && cached.expiresAt > now) {
351
+ return cached.value;
352
+ }
353
+
354
+ // Execute handler and cache the result
355
+ const result = await next.handle();
356
+
357
+ // Only cache successful responses
358
+ CacheInterceptor.cache.set(cacheKey, {
359
+ value: result,
360
+ expiresAt: now + this.ttlMs,
361
+ });
362
+
363
+ return result;
364
+ }
365
+ }
366
+
367
+ /**
368
+ * HeaderInterceptor - Adds custom headers to responses
369
+ *
370
+ * @example
371
+ * ```typescript
372
+ * @Get('api/data')
373
+ * @UseInterceptors(new HeaderInterceptor({ 'X-Custom-Header': 'value' }))
374
+ * getData() {}
375
+ * ```
376
+ */
377
+ export class HeaderInterceptor implements NestInterceptor {
378
+ constructor(private headers: Record<string, string>) {}
379
+
380
+ async intercept(context: Context, next: CallHandler): Promise<unknown> {
381
+ const result = await next.handle();
382
+
383
+ // If result is a Response object, add headers
384
+ if (result instanceof Response) {
385
+ const newHeaders = new Headers(result.headers);
386
+ for (const [key, value] of Object.entries(this.headers)) {
387
+ newHeaders.set(key, value);
388
+ }
389
+ return new Response(result.body, {
390
+ status: result.status,
391
+ statusText: result.statusText,
392
+ headers: newHeaders,
393
+ });
394
+ }
395
+
396
+ return result;
397
+ }
398
+ }
399
+
400
+ // ============= Interceptor Executor =============
401
+
402
+ /**
403
+ * Interceptor executor options
404
+ */
405
+ export interface InterceptorExecutorOptions {
406
+ /** Global interceptors applied to all routes */
407
+ globalInterceptors?: Interceptor[];
408
+ /** Interceptors from controller class */
409
+ classInterceptors?: Interceptor[];
410
+ /** Interceptors from method */
411
+ methodInterceptors?: Interceptor[];
412
+ /** Container for resolving interceptor instances */
413
+ resolveInterceptor?: (interceptor: Interceptor) => NestInterceptor | InterceptorFn | null;
414
+ }
415
+
416
+ /**
417
+ * Create a call handler that chains interceptors
418
+ */
419
+ function createCallHandler(
420
+ interceptors: Array<NestInterceptor | InterceptorFn>,
421
+ context: Context,
422
+ finalHandler: () => Promise<unknown>,
423
+ resolveInterceptor?: (interceptor: Interceptor) => NestInterceptor | InterceptorFn | null,
424
+ ): CallHandler {
425
+ let index = 0;
426
+
427
+ const execute: () => Promise<unknown> = async () => {
428
+ if (index >= interceptors.length) {
429
+ return finalHandler();
430
+ }
431
+
432
+ const interceptor = interceptors[index++];
433
+ let interceptorInstance: NestInterceptor | InterceptorFn | null = interceptor;
434
+
435
+ // Resolve if needed
436
+ if (resolveInterceptor && !isNestInterceptor(interceptor) && !isInterceptorFn(interceptor)) {
437
+ interceptorInstance = resolveInterceptor(interceptor);
438
+ }
439
+
440
+ if (!interceptorInstance) {
441
+ console.warn("Interceptor could not be resolved:", interceptor);
442
+ return execute();
443
+ }
444
+
445
+ // Create the next call handler
446
+ const next: CallHandler = {
447
+ handle: () => execute(),
448
+ };
449
+
450
+ // Execute the interceptor
451
+ if (isInterceptorFn(interceptorInstance)) {
452
+ return interceptorInstance(context, () => execute());
453
+ } else {
454
+ return interceptorInstance.intercept(context, next);
455
+ }
456
+ };
457
+
458
+ return {
459
+ handle: execute,
460
+ };
461
+ }
462
+
463
+ /**
464
+ * Execute interceptors wrapping around the handler
465
+ *
466
+ * @param context - Request context
467
+ * @param handler - The final handler to execute
468
+ * @param options - Interceptor executor options
469
+ * @returns Promise of the result
470
+ */
471
+ export async function executeInterceptors(
472
+ context: Context,
473
+ handler: () => Promise<unknown>,
474
+ options: InterceptorExecutorOptions,
475
+ ): Promise<unknown> {
476
+ const {
477
+ globalInterceptors = [],
478
+ classInterceptors = [],
479
+ methodInterceptors = [],
480
+ resolveInterceptor,
481
+ } = options;
482
+
483
+ // Combine all interceptors in execution order
484
+ // Global → Class → Method
485
+ const allInterceptors: Interceptor[] = [
486
+ ...globalInterceptors,
487
+ ...classInterceptors,
488
+ ...methodInterceptors,
489
+ ];
490
+
491
+ // If no interceptors, just execute the handler
492
+ if (allInterceptors.length === 0) {
493
+ return handler();
494
+ }
495
+
496
+ // Resolve all interceptors
497
+ const resolvedInterceptors: Array<NestInterceptor | InterceptorFn> = [];
498
+
499
+ for (const interceptor of allInterceptors) {
500
+ let instance: NestInterceptor | InterceptorFn | null = null;
501
+
502
+ // Resolve the interceptor
503
+ if (typeof interceptor === "function") {
504
+ // Check if it's an interceptor function or a class constructor
505
+ const funcInterceptor = interceptor as { prototype?: unknown; intercept?: unknown };
506
+ if (
507
+ funcInterceptor.prototype &&
508
+ typeof funcInterceptor.prototype === "object" &&
509
+ "intercept" in (funcInterceptor.prototype as object)
510
+ ) {
511
+ // It's a class constructor - try to resolve from container or create instance
512
+ instance = resolveInterceptor ? resolveInterceptor(interceptor) : null;
513
+ if (!instance) {
514
+ // Create a new instance if not in container
515
+ const InterceptorClass = interceptor as unknown as new () => NestInterceptor;
516
+ instance = new InterceptorClass();
517
+ }
518
+ } else {
519
+ // It's an interceptor function
520
+ instance = interceptor as InterceptorFn;
521
+ }
522
+ } else if (typeof interceptor === "object" && interceptor !== null) {
523
+ // It's a token or already an instance
524
+ const objInterceptor = interceptor as { intercept?: unknown };
525
+ if (
526
+ "intercept" in objInterceptor &&
527
+ typeof objInterceptor.intercept === "function"
528
+ ) {
529
+ // It's already a NestInterceptor instance
530
+ instance = interceptor as NestInterceptor;
531
+ } else {
532
+ // It's a token - try to resolve
533
+ instance = resolveInterceptor ? resolveInterceptor(interceptor) : null;
534
+ }
535
+ }
536
+
537
+ if (instance) {
538
+ resolvedInterceptors.push(instance);
539
+ } else {
540
+ console.warn("Interceptor could not be resolved:", interceptor);
541
+ }
542
+ }
543
+
544
+ // Create the call handler chain
545
+ const callHandler = createCallHandler(
546
+ resolvedInterceptors,
547
+ context,
548
+ handler,
549
+ resolveInterceptor,
550
+ );
551
+
552
+ return callHandler.handle();
553
+ }
554
+
555
+ // ============= Type Guards =============
556
+
557
+ /**
558
+ * Check if a value is a NestInterceptor instance
559
+ */
560
+ export function isNestInterceptor(value: unknown): value is NestInterceptor {
561
+ return (
562
+ typeof value === "object" &&
563
+ value !== null &&
564
+ "intercept" in value &&
565
+ typeof (value as NestInterceptor).intercept === "function"
566
+ );
567
+ }
568
+
569
+ /**
570
+ * Check if a value is an interceptor function
571
+ */
572
+ export function isInterceptorFn(value: unknown): value is InterceptorFn {
573
+ return typeof value === "function" && !isNestInterceptor(value);
574
+ }