@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.
- package/.env.example +109 -0
- package/.github/workflows/ci.yml +31 -0
- package/LICENSE +21 -0
- package/README.md +892 -0
- package/architecture.md +652 -0
- package/bun.lock +70 -0
- package/dist/cli/index.js +3233 -0
- package/dist/index.js +9014 -0
- package/package.json +77 -0
- package/src/cache/index.ts +795 -0
- package/src/cli/ARCHITECTURE.md +837 -0
- package/src/cli/bin.ts +10 -0
- package/src/cli/commands/build.ts +425 -0
- package/src/cli/commands/dev.ts +248 -0
- package/src/cli/commands/generate.ts +541 -0
- package/src/cli/commands/help.ts +55 -0
- package/src/cli/commands/index.ts +112 -0
- package/src/cli/commands/migration.ts +355 -0
- package/src/cli/commands/new.ts +804 -0
- package/src/cli/commands/start.ts +208 -0
- package/src/cli/core/args.ts +283 -0
- package/src/cli/core/console.ts +349 -0
- package/src/cli/core/index.ts +60 -0
- package/src/cli/core/prompt.ts +424 -0
- package/src/cli/core/spinner.ts +265 -0
- package/src/cli/index.ts +135 -0
- package/src/cli/templates/deploy.ts +295 -0
- package/src/cli/templates/docker.ts +307 -0
- package/src/cli/templates/index.ts +24 -0
- package/src/cli/utils/fs.ts +428 -0
- package/src/cli/utils/index.ts +8 -0
- package/src/cli/utils/strings.ts +197 -0
- package/src/config/env.ts +408 -0
- package/src/config/index.ts +506 -0
- package/src/config/loader.ts +329 -0
- package/src/config/merge.ts +285 -0
- package/src/config/types.ts +320 -0
- package/src/config/validation.ts +441 -0
- package/src/container/forward-ref.ts +143 -0
- package/src/container/index.ts +386 -0
- package/src/context/index.ts +360 -0
- package/src/database/index.ts +1142 -0
- package/src/database/migrations/index.ts +371 -0
- package/src/database/schema/index.ts +619 -0
- package/src/frontend/api-routes.ts +640 -0
- package/src/frontend/bundler.ts +643 -0
- package/src/frontend/console-client.ts +419 -0
- package/src/frontend/console-stream.ts +587 -0
- package/src/frontend/dev-server.ts +846 -0
- package/src/frontend/file-router.ts +611 -0
- package/src/frontend/frameworks/index.ts +106 -0
- package/src/frontend/frameworks/react.ts +85 -0
- package/src/frontend/frameworks/solid.ts +104 -0
- package/src/frontend/frameworks/svelte.ts +110 -0
- package/src/frontend/frameworks/vue.ts +92 -0
- package/src/frontend/hmr-client.ts +663 -0
- package/src/frontend/hmr.ts +728 -0
- package/src/frontend/index.ts +342 -0
- package/src/frontend/islands.ts +552 -0
- package/src/frontend/isr.ts +555 -0
- package/src/frontend/layout.ts +475 -0
- package/src/frontend/ssr/react.ts +446 -0
- package/src/frontend/ssr/solid.ts +523 -0
- package/src/frontend/ssr/svelte.ts +546 -0
- package/src/frontend/ssr/vue.ts +504 -0
- package/src/frontend/ssr.ts +699 -0
- package/src/frontend/types.ts +2274 -0
- package/src/health/index.ts +604 -0
- package/src/index.ts +410 -0
- package/src/lock/index.ts +587 -0
- package/src/logger/index.ts +444 -0
- package/src/logger/transports/index.ts +969 -0
- package/src/metrics/index.ts +494 -0
- package/src/middleware/built-in.ts +360 -0
- package/src/middleware/index.ts +94 -0
- package/src/modules/filters.ts +458 -0
- package/src/modules/guards.ts +405 -0
- package/src/modules/index.ts +1256 -0
- package/src/modules/interceptors.ts +574 -0
- package/src/modules/lazy.ts +418 -0
- package/src/modules/lifecycle.ts +478 -0
- package/src/modules/metadata.ts +90 -0
- package/src/modules/pipes.ts +626 -0
- package/src/router/index.ts +339 -0
- package/src/router/linear.ts +371 -0
- package/src/router/regex.ts +292 -0
- package/src/router/tree.ts +562 -0
- package/src/rpc/index.ts +1263 -0
- package/src/security/index.ts +436 -0
- package/src/ssg/index.ts +631 -0
- package/src/storage/index.ts +456 -0
- package/src/telemetry/index.ts +1097 -0
- package/src/testing/index.ts +1586 -0
- package/src/types/index.ts +236 -0
- package/src/types/optional-deps.d.ts +219 -0
- package/src/validation/index.ts +276 -0
- package/src/websocket/index.ts +1004 -0
- package/tests/integration/cli.test.ts +1016 -0
- package/tests/integration/fullstack.test.ts +234 -0
- package/tests/unit/cache.test.ts +174 -0
- package/tests/unit/cli-commands.test.ts +892 -0
- package/tests/unit/cli.test.ts +1258 -0
- package/tests/unit/container.test.ts +279 -0
- package/tests/unit/context.test.ts +221 -0
- package/tests/unit/database.test.ts +183 -0
- package/tests/unit/linear-router.test.ts +280 -0
- package/tests/unit/lock.test.ts +336 -0
- package/tests/unit/middleware.test.ts +184 -0
- package/tests/unit/modules.test.ts +142 -0
- package/tests/unit/pubsub.test.ts +257 -0
- package/tests/unit/regex-router.test.ts +265 -0
- package/tests/unit/router.test.ts +373 -0
- package/tests/unit/rpc.test.ts +1248 -0
- package/tests/unit/security.test.ts +174 -0
- package/tests/unit/telemetry.test.ts +371 -0
- package/tests/unit/test-cache.test.ts +110 -0
- package/tests/unit/test-database.test.ts +282 -0
- package/tests/unit/tree-router.test.ts +325 -0
- package/tests/unit/validation.test.ts +794 -0
- 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
|
+
}
|