@mxweb/core 1.0.2 → 1.2.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/CHANGELOG.md CHANGED
@@ -5,6 +5,300 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.2.0] - 2026-01-03
9
+
10
+ ### Added
11
+
12
+ - **Feature**: Added feature-level guards, filters, interceptors, and pipes support
13
+ - `FeatureInitialize` now extends `DecoratorOptions` for full decorator support
14
+ - Features can now define their own guards, filters, interceptors, and pipes
15
+ - Execution order: Application → Feature → Route for guards and pipes
16
+ - Execution order: Route → Feature → Application for filters and interceptors
17
+ - Example:
18
+ ```ts
19
+ Feature.create({
20
+ controller: ProductController,
21
+ router: productRouter,
22
+ guards: [FeatureAuthGuard],
23
+ filters: [FeatureExceptionFilter],
24
+ interceptors: [FeatureLoggingInterceptor],
25
+ pipes: [FeatureValidationPipe],
26
+ });
27
+ ```
28
+
29
+ - **Common**: Added `DecoratorOptions` interface for shared decorator configuration
30
+ - Provides a common base for guards, filters, interceptors, and pipes configuration
31
+ - Used by both `ApplicationOptions` and `FeatureInitialize`
32
+ - Properties: `guards?: Guard[]`, `filters?: Filter[]`, `interceptors?: Interceptor[]`, `pipes?: Pipe[]`
33
+
34
+ - **Common**: Added `FeatureContext` interface to avoid circular dependencies
35
+ - Abstract interface for feature context accessible from `ExecuteContext`
36
+ - Methods: `hasInject()`, `getInject()`, `getInjectSync()`
37
+ - `RequestContext.feature` now uses `FeatureContext` type
38
+
39
+ - **Common**: Moved `Guard`, `Filter`, `Interceptor` type definitions to `common.ts`
40
+ - Centralized type definitions for better code organization
41
+ - Types are no longer re-exported from `execute.ts`
42
+
43
+ - **Response**: Added `CoreResponseInterceptorHandler` interface
44
+ - Specialized interface for interceptors working with `CoreResponse`
45
+ - Separated from generic `ResponseInterceptorHandler` in `common.ts`
46
+
47
+ ### Changed
48
+
49
+ - **Application**: Updated to include feature-level decorators in execution chains
50
+ - `executeGuards()` now includes feature guards: App → Feature → Route
51
+ - `executeFilters()` now includes feature filters: Route → Feature → App
52
+ - `executeInterceptors()` now includes feature interceptors: Route → Feature → App
53
+ - `executePipes()` now includes feature pipes: App → Feature → Route
54
+
55
+ - **ExecuteContext**: Simplified `getFeature()` method signature
56
+ - Now uses generic `<Feature extends FeatureContext>` for type flexibility
57
+ - Returns `FeatureContext` by default, can be cast to specific Feature type
58
+
59
+ ### Fixed
60
+
61
+ - **Circular Dependencies**: Resolved all circular import issues
62
+ - Removed `Feature` import from `execute.ts` (was causing circular: execute → feature → controller/router → execute)
63
+ - Introduced `FeatureContext` interface in `common.ts` as abstraction
64
+ - All 6 circular dependencies have been eliminated
65
+
66
+ ### Internal
67
+
68
+ - **Type Simplification**: Simplified generic types in decorator interfaces
69
+ - `ResponseInterceptorHandler` now uses single generic `<Response>`
70
+ - `Interceptor` type simplified to single generic
71
+ - `DecoratorOptions` no longer requires generic parameters
72
+
73
+ ## [1.1.0] - 2026-01-02
74
+
75
+ ### ⚠️ Breaking Changes
76
+
77
+ - **Framework Independence**: Removed all direct dependencies on Next.js
78
+ - The framework is now fully framework-agnostic
79
+ - Next.js is still the primary target and fully compatible
80
+ - Removed `next` from `peerDependencies`
81
+ - Removed `@mxweb/utils` dependency (utilities are now built-in)
82
+ - Use `CoreRequest` interface instead of `NextRequest`
83
+ - Use `CoreResponse` class instead of `NextResponse`
84
+
85
+ - **Interceptor Redesign**: Interceptors now transform responses instead of wrapping handlers
86
+ - Old pattern: `intercept(context, next)` - wrapped handler execution
87
+ - New pattern: `transform(response)` - receives `CoreResponse` and transforms it
88
+ - Interceptors no longer have access to `CallHandler` (deprecated)
89
+ - Use `ResponseInterceptorHandler` interface instead of `InterceptorHandler`
90
+
91
+ ### Added
92
+
93
+ - **Application Options**: New configuration options for framework-agnostic response handling
94
+ - `response?: ResponseClass` - Pass response class (NextResponse, Response) for final response creation
95
+ - Defaults to standard `Response` if not provided
96
+ - Example: `Application.create({ response: NextResponse })`
97
+ - `poweredBy?: string | false` - Customize X-Powered-By header
98
+ - Defaults to `"MxWeb"`
99
+ - Set to `false` to disable the header
100
+ - Example: `Application.create({ poweredBy: "MyAPI/1.0" })`
101
+
102
+ - **Response**: Added `ResponseClass` interface for response class compatibility
103
+ - Interface for classes with static `json(body, init)` method
104
+ - Compatible with `NextResponse`, `Response`, and custom response classes
105
+ - Allows passing response class directly to `Application.create()`
106
+
107
+ - **Response**: Added `ResponseInterceptor` type and `ResponseInterceptorHandler` interface
108
+ - `ResponseInterceptor<T>` - Function type for simple response transformers
109
+ - `ResponseInterceptorHandler<T>` - Interface for class-based interceptors
110
+ - `transform(response: CoreResponse)` method for transforming responses
111
+ - Interceptors can add headers, modify body, or return new `CoreResponse`
112
+
113
+ - **Response**: Added `applyTransformer()` helper function
114
+ - Handles both class-based and function-based transformers
115
+ - Automatically detects if transformer is a class with `json()` method
116
+
117
+ - **Response**: Added `CoreResponse` class and `CoreResponseBody` interface
118
+ - Framework-agnostic response wrapper
119
+ - `CoreResponseBody<T>` - Standardized JSON response structure
120
+ - `CoreResponse.json()` - Returns the response body for custom response handling
121
+ - `ResponseTransformer<R>` - Type for custom response transformers
122
+ - All `ServerResponse` methods now return `CoreResponse` instead of `NextResponse`
123
+
124
+ - **Route**: Added fluent chain API helper methods for easier route configuration
125
+ - `decorators(...decorators)` - Add multiple decorator results directly (lazy applied on `getReflect()`)
126
+ - `middlewares(...middlewares)` - Add middleware functions
127
+ - `guards(...guards)` - Add guard classes
128
+ - `filters(...filters)` - Add exception filter classes
129
+ - `interceptors(...interceptors)` - Add interceptor classes
130
+ - `pipes(...pipes)` - Add pipe classes
131
+ - `metadata(key, value)` - Set metadata key-value pairs
132
+ - All chain methods return new Route instance (immutable pattern)
133
+ - Example: `Route.get("/users", "findAll").guards(AuthGuard).metadata("roles", ["admin"])`
134
+
135
+ - **Common**: Added `Switchable<T>` interface for safe inject facade pattern
136
+ - Provides type-safe way to expose controlled API from injects
137
+ - `ApplicationInject` and `FeatureInject` now extend `Partial<Switchable>`
138
+ - Example: `class Connection implements ApplicationInject, Switchable<DbClient> { ... }`
139
+
140
+ - **Common**: Added `Callback<R, A>` utility type (previously from `@mxweb/utils`)
141
+
142
+ - **ExecuteContext**: Added `switch()` and `switchSync()` methods for inject context switching
143
+ - `switch<T>(name)` - Async method to get inject and call its `switch()` method (throws if not found)
144
+ - `switchSync<T>(name)` - Sync version (only works if inject already initialized)
145
+ - Searches feature injects first (priority), then falls back to global injects
146
+ - Example: `const db = await this.context.switch<DatabaseClient>("db")`
147
+
148
+ - **ExecuteContext**: Added `CoreRequest` interface to abstract HTTP request
149
+ - Framework-agnostic request interface (compatible with Next.js, Express, Fastify, Fetch API)
150
+ - Properties: `nextUrl?`, `url?`, `query?`, `headers`, `body`, `json()`, `text()`, `formData()`, `blob()`, `arrayBuffer()`
151
+ - `RequestContext.req` now uses `CoreRequest` instead of `NextRequest`
152
+ - `switchHttp().getRequest<T>()` is now generic for type-safe request access
153
+
154
+ - **ExecuteContext**: Added `CoreSearchParams` class for unified query parameter handling
155
+ - Constructor accepts `URLSearchParams`, query string, or query object
156
+ - Type-safe getter methods: `get()`, `getAll()`, `getNumber()`, `getBoolean()`
157
+ - Helper methods: `has()`, `keys()`, `values()`, `entries()`, `forEach()`, `size`
158
+ - Conversion methods: `toObject()`, `toObjectAll()`, `toURLSearchParams()`, `toString()`
159
+ - `switchHttp().searchParams()` returns cached `CoreSearchParams` instance
160
+ - `switchHttp().query()` now uses `searchParams().toObject()` for backward compatibility
161
+ - Fallback chain: `nextUrl.searchParams` → `url.searchParams` → `url` string → `query` object
162
+
163
+ - **Config**: Added `switch()` method implementation
164
+ - Returns the Config instance itself for context switching
165
+ - Example: `const config = await this.context.getInject<Config>("config")`
166
+
167
+ ### Changed
168
+
169
+ - **Application**: Response handling is now centralized in `toResponse()` method
170
+ - All responses go through `Application.toResponse()` before being returned
171
+ - Adds `X-Powered-By` header automatically (configurable via `poweredBy` option)
172
+ - Converts `CoreResponse` to configured response class (NextResponse, Response, etc.)
173
+
174
+ - **Interceptor**: Complete redesign of interceptor pattern
175
+ - Interceptors now receive `CoreResponse` and return transformed `CoreResponse`
176
+ - No longer wrap handler execution - called after handler returns
177
+ - Route interceptors run first, then global interceptors
178
+ - Example:
179
+ ```ts
180
+ class LoggingInterceptor implements ResponseInterceptorHandler {
181
+ transform(response: CoreResponse): CoreResponse {
182
+ console.log(`[${response.status}] ${response.body.message}`);
183
+ response.headers.set("X-Logged", "true");
184
+ return response;
185
+ }
186
+ }
187
+ ```
188
+
189
+ - **ExecuteContext**: `getInject()` and `getLocalInject()` now return `switch()` result
190
+ - If inject implements `switch()`, returns the switched value instead of raw instance
191
+ - If inject doesn't implement `switch()`, returns the raw instance (backward compatible)
192
+ - Same behavior for `getInjectSync()` and `getLocalInjectSync()`
193
+ - This provides a safe API by default, hiding dangerous methods like `close()`, `destroy()`
194
+ - Lifecycle hooks (`onInit`, `onDestroy`) are still called on raw instance
195
+
196
+ - **Feature**: `getInject()` and `getInjectSync()` now return `switch()` result
197
+ - Same pattern as ExecuteContext for consistency
198
+ - Lifecycle hooks (`onFeature`, `onFeatureDestroy`) are still called on raw instance
199
+
200
+ - **Service**: `getInject()` and `getInjectSync()` now return `switch()` result
201
+ - Follows the same pattern as ExecuteContext for consistency
202
+
203
+ - **Hooks**: `onRequest` hook now receives `CoreRequest` instead of `NextRequest`
204
+
205
+ - **Decorator**: `Request()` function now returns `CoreRequest` and is generic
206
+ - Example: `const req = Request<NextRequest>()` for Next.js type-safe access
207
+
208
+ ### Deprecated
209
+
210
+ - **Common**: `CallHandler<T>` type is deprecated
211
+ - Interceptors no longer use the `intercept(context, next)` pattern
212
+ - Use `ResponseInterceptorHandler` with `transform(response)` instead
213
+
214
+ ### Removed
215
+
216
+ - Removed `next` from `peerDependencies` (no longer required)
217
+ - Removed `@mxweb/utils` dependency (utilities are now built-in)
218
+ - Removed `InterceptorHandler` interface (replaced by `ResponseInterceptorHandler`)
219
+
220
+ ### Fixed
221
+
222
+ - **Application**: Fixed inject lifecycle `onInit` not being called at startup
223
+ - Previously, `onInit` was only called when inject was first accessed (lazy initialization)
224
+ - Now, all registered injects are initialized during `registerInjectFactories()`
225
+ - Added `initializeInjects()` method to ensure `onInit` runs for all injects at application startup
226
+ - This is critical for injects like Migration that need to run immediately
227
+
228
+ ### Migration Guide
229
+
230
+ #### For Next.js users
231
+
232
+ The framework remains fully compatible with Next.js. Configure your application:
233
+
234
+ ```ts
235
+ import { Application } from "@mxweb/core";
236
+ import { NextResponse } from "next/server";
237
+
238
+ const app = Application.create({
239
+ response: NextResponse, // Use NextResponse for final responses
240
+ poweredBy: "MyAPI/1.0", // Optional: customize X-Powered-By header
241
+ // ... other options
242
+ });
243
+
244
+ export const GET = app.GET;
245
+ export const POST = app.POST;
246
+ ```
247
+
248
+ #### Request handling
249
+
250
+ Use `CoreRequest` interface, which is compatible with `NextRequest`:
251
+
252
+ ```ts
253
+ // Before
254
+ const req: NextRequest = ...
255
+
256
+ // After (CoreRequest is compatible)
257
+ const req: CoreRequest = nextRequest; // Works as-is
258
+
259
+ // Or for type-safe Next.js specific features
260
+ const req = Request<NextRequest>();
261
+ ```
262
+
263
+ #### Response handling
264
+
265
+ `ServerResponse` methods return `CoreResponse`, which is automatically converted:
266
+
267
+ ```ts
268
+ // The framework handles conversion internally
269
+ // For custom handling in filters:
270
+ const coreResponse = response.success(data);
271
+ return NextResponse.json(coreResponse.json(), {
272
+ status: coreResponse.status,
273
+ headers: coreResponse.headers
274
+ });
275
+ ```
276
+
277
+ #### Interceptor migration
278
+
279
+ Update interceptors to use the new `transform()` pattern:
280
+
281
+ ```ts
282
+ // Before (v1.0.x)
283
+ class LoggingInterceptor implements InterceptorHandler {
284
+ async intercept(context: ExecuteContext, next: CallHandler): Promise<unknown> {
285
+ const start = Date.now();
286
+ const result = await next();
287
+ console.log(`Request took ${Date.now() - start}ms`);
288
+ return result;
289
+ }
290
+ }
291
+
292
+ // After (v1.1.0)
293
+ class LoggingInterceptor implements ResponseInterceptorHandler {
294
+ transform(response: CoreResponse): CoreResponse {
295
+ console.log(`[${response.status}] ${response.body.message}`);
296
+ response.headers.set("X-Response-Time", Date.now().toString());
297
+ return response;
298
+ }
299
+ }
300
+ ```
301
+
8
302
  ## [1.0.2] - 2024-12-18
9
303
 
10
304
  ### Changed
@@ -1,8 +1,8 @@
1
- import { NextRequest } from "next/server";
2
1
  import { Feature } from "./feature";
3
- import { Filter, Guard, Interceptor } from "./execute";
2
+ import { CoreResponse, ResponseClass } from "./response";
3
+ import { CoreRequest } from "./execute";
4
4
  import { ApplicationHooksOptions } from "./hooks";
5
- import { ApplicationInject, InjectEntry, InjectFactory, InjectRegistry, Pipe, RoutePayload } from "./common";
5
+ import { ApplicationInject, DecoratorOptions, InjectEntry, InjectFactory, InjectRegistry, RoutePayload } from "./common";
6
6
  /**
7
7
  * Type for application-level dependency injection configuration.
8
8
  * An array of inject factory functions that register injects via InjectRegistry.
@@ -44,12 +44,16 @@ export type ApplicationCors = string[] | ((registry: InjectRegistry) => string[]
44
44
  /**
45
45
  * Configuration options for creating an Application instance.
46
46
  *
47
- * @template Key - The key used to extract path segments from Next.js catch-all route params
47
+ * @template Key - The key used to extract path segments from catch-all route params
48
48
  *
49
49
  * @example
50
50
  * ```ts
51
+ * // With Next.js
52
+ * import { NextResponse } from "next/server";
53
+ *
51
54
  * const options: ApplicationOptions = {
52
55
  * key: "path",
56
+ * response: NextResponse, // Use NextResponse for final response
53
57
  * injects: [
54
58
  * Config.forRoot(),
55
59
  * Database.forRoot(),
@@ -63,26 +67,52 @@ export type ApplicationCors = string[] | ((registry: InjectRegistry) => string[]
63
67
  * onRequest: (req, method) => console.log(`${method} request`),
64
68
  * onResponse: (ctx) => console.log("Response sent"),
65
69
  * };
70
+ *
71
+ * // Without response option - uses standard Response
72
+ * const options: ApplicationOptions = {
73
+ * key: "path",
74
+ * // response defaults to Response
75
+ * };
66
76
  * ```
67
77
  */
68
- export interface ApplicationOptions<Key extends string = "path"> extends ApplicationHooksOptions {
78
+ export interface ApplicationOptions<Key extends string = "path"> extends ApplicationHooksOptions, DecoratorOptions {
69
79
  /** The key used to extract path segments from catch-all route params. Defaults to "path". */
70
80
  key?: Key;
81
+ /**
82
+ * Response class to use for creating final responses.
83
+ * Pass NextResponse for Next.js, or any class with static json() method.
84
+ * Defaults to standard Response.
85
+ *
86
+ * @example
87
+ * ```ts
88
+ * import { NextResponse } from "next/server";
89
+ * Application.create({ response: NextResponse });
90
+ * ```
91
+ */
92
+ response?: ResponseClass;
93
+ /**
94
+ * Custom value for X-Powered-By header.
95
+ * Set to false to disable the header.
96
+ * Defaults to "MxWeb".
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * // Custom value
101
+ * Application.create({ poweredBy: "MyAPI/1.0" });
102
+ *
103
+ * // Disable header
104
+ * Application.create({ poweredBy: false });
105
+ * ```
106
+ */
107
+ poweredBy?: string | false;
71
108
  /** Array of inject factories for dependency injection */
72
109
  injects?: ApplicationInjects;
73
- /** Global guards applied to all routes */
74
- guards?: Guard[];
75
- /** Global exception filters applied to all routes */
76
- filters?: Filter[];
77
- /** Global interceptors applied to all routes */
78
- interceptors?: Interceptor[];
79
- /** Global pipes applied to all routes */
80
- pipes?: Pipe[];
81
110
  /** CORS configuration - allowed origins or resolver function */
82
111
  cors?: ApplicationCors;
83
112
  }
84
113
  /**
85
- * Main Application class for handling HTTP requests in Next.js App Router.
114
+ * Main Application class for handling HTTP requests.
115
+ * Designed to be framework-agnostic with primary support for Next.js App Router.
86
116
  *
87
117
  * This class provides a NestJS-inspired framework for building APIs with:
88
118
  * - Dependency injection
@@ -91,11 +121,11 @@ export interface ApplicationOptions<Key extends string = "path"> extends Applica
91
121
  * - Lifecycle hooks
92
122
  * - CORS support
93
123
  *
94
- * @template Key - The key used to extract path segments from Next.js catch-all route params
124
+ * @template Key - The key used to extract path segments from catch-all route params
95
125
  *
96
126
  * @example
97
127
  * ```ts
98
- * // In app/api/[[...path]]/route.ts
128
+ * // In app/api/[[...path]]/route.ts (Next.js)
99
129
  * import { Application } from "@mxweb/core";
100
130
  * import "@/features/products/product.feature";
101
131
  *
@@ -121,6 +151,8 @@ export declare class Application<Key extends string = "path"> {
121
151
  private readonly request;
122
152
  private readonly method;
123
153
  private readonly payload;
154
+ /** Logger instance for Application */
155
+ private static logger;
124
156
  /** Registered features (modules) */
125
157
  private static features;
126
158
  /** Global guards */
@@ -135,6 +167,10 @@ export declare class Application<Key extends string = "path"> {
135
167
  private static corsOrigins;
136
168
  /** Application configuration options */
137
169
  private static options;
170
+ /** Response class for creating final responses */
171
+ private static ResponseClass;
172
+ /** X-Powered-By header value (false to disable) */
173
+ private static poweredBy;
138
174
  /** Application-level hooks manager */
139
175
  private static hooks;
140
176
  /** Flag indicating if features are initialized */
@@ -151,11 +187,18 @@ export declare class Application<Key extends string = "path"> {
151
187
  private route;
152
188
  /** Request-scoped hooks manager */
153
189
  private requestHooks;
190
+ /**
191
+ * Converts a CoreResponse to the configured Response class.
192
+ *
193
+ * @param coreResponse - The CoreResponse to convert
194
+ * @returns Response instance (NextResponse, Response, etc.)
195
+ */
196
+ private static toResponse;
154
197
  /**
155
198
  * Creates a new Application instance for handling a request.
156
199
  * This constructor is private - use Application.create() to set up the application.
157
200
  *
158
- * @param request - The incoming Next.js request
201
+ * @param request - The incoming CoreRequest (compatible with NextRequest)
159
202
  * @param method - The HTTP method of the request
160
203
  * @param payload - The route payload containing path params
161
204
  */
@@ -228,6 +271,16 @@ export declare class Application<Key extends string = "path"> {
228
271
  * - Loads CORS configuration (may need injects)
229
272
  */
230
273
  private static registerInjectFactories;
274
+ /**
275
+ * Initializes all registered injects by calling their onInit lifecycle hook.
276
+ * This ensures injects like Migration run at application startup.
277
+ *
278
+ * @remarks
279
+ * - Injects are initialized in registration order
280
+ * - Each inject's factory is called to create the instance
281
+ * - onInit is called if the inject implements it
282
+ */
283
+ private static initializeInjects;
231
284
  /**
232
285
  * Registers process shutdown handlers for graceful cleanup.
233
286
  * Handles SIGTERM and SIGINT signals to properly destroy injects.
@@ -284,24 +337,23 @@ export declare class Application<Key extends string = "path"> {
284
337
  * Route-level filters run first, then global filters.
285
338
  *
286
339
  * @param error - The error to handle
287
- * @returns A Response if a filter handles the error, null otherwise
340
+ * @returns A CoreResponse or Response if a filter handles the error, null otherwise
288
341
  *
289
342
  * @remarks
290
- * Filters are tried in order until one returns a Response.
343
+ * Filters are tried in order until one returns a Response or CoreResponse.
291
344
  * If no filter handles the error, null is returned and default error handling applies.
292
345
  */
293
346
  private executeFilters;
294
347
  /**
295
- * Wraps the handler with interceptor chain.
296
- * Route-level interceptors are outermost, global interceptors are innermost.
348
+ * Applies interceptors to transform the response.
349
+ * Route-level interceptors run first, then global interceptors.
297
350
  *
298
- * @template T - The return type of the handler
299
- * @param handler - The original handler function to wrap
300
- * @returns The result of executing the interceptor chain
351
+ * @param response - The CoreResponse to transform
352
+ * @returns The transformed CoreResponse after passing through all interceptors
301
353
  *
302
354
  * @remarks
303
- * Interceptors form a chain where each can modify the request/response.
304
- * The chain is built from right to left (last interceptor wraps handler first).
355
+ * Interceptors are applied in order: route interceptors first, then global.
356
+ * Each interceptor receives the response and can transform headers or body.
305
357
  */
306
358
  private executeInterceptors;
307
359
  /**
@@ -320,10 +372,11 @@ export declare class Application<Key extends string = "path"> {
320
372
  */
321
373
  private checkCors;
322
374
  /**
323
- * Applies CORS headers to the response.
375
+ * Applies CORS headers to the CoreResponse.
376
+ * Does not convert to Response - that's the responsibility of the response transformer.
324
377
  *
325
- * @param response - The response to apply headers to
326
- * @returns A new Response with CORS headers applied
378
+ * @param response - The CoreResponse to apply CORS headers to
379
+ * @returns The same CoreResponse with CORS headers applied
327
380
  */
328
381
  private applyCorsHeaders;
329
382
  /**
@@ -361,7 +414,7 @@ export declare class Application<Key extends string = "path"> {
361
414
  *
362
415
  * @template Key - The key used to extract path segments from route params
363
416
  * @param method - The HTTP method this handler will process
364
- * @returns An async function that handles Next.js requests
417
+ * @returns An async function that handles HTTP requests
365
418
  *
366
419
  * @remarks
367
420
  * The returned handler:
@@ -398,13 +451,13 @@ export declare class Application<Key extends string = "path"> {
398
451
  * ```
399
452
  */
400
453
  static create<Key extends string = "path">(options?: ApplicationOptions<Key>): {
401
- GET: (req: NextRequest, payload: RoutePayload<"path">) => Promise<Response>;
402
- POST: (req: NextRequest, payload: RoutePayload<"path">) => Promise<Response>;
403
- PUT: (req: NextRequest, payload: RoutePayload<"path">) => Promise<Response>;
404
- PATCH: (req: NextRequest, payload: RoutePayload<"path">) => Promise<Response>;
405
- DELETE: (req: NextRequest, payload: RoutePayload<"path">) => Promise<Response>;
406
- HEAD: (req: NextRequest, payload: RoutePayload) => Promise<Response>;
407
- OPTIONS: (req: NextRequest) => Promise<Response>;
454
+ GET: (req: CoreRequest, payload: RoutePayload<"path">) => Promise<Response>;
455
+ POST: (req: CoreRequest, payload: RoutePayload<"path">) => Promise<Response>;
456
+ PUT: (req: CoreRequest, payload: RoutePayload<"path">) => Promise<Response>;
457
+ PATCH: (req: CoreRequest, payload: RoutePayload<"path">) => Promise<Response>;
458
+ DELETE: (req: CoreRequest, payload: RoutePayload<"path">) => Promise<Response>;
459
+ HEAD: (req: CoreRequest, payload: RoutePayload) => Promise<Response>;
460
+ OPTIONS: (req: CoreRequest) => Promise<Response | CoreResponse<unknown>>;
408
461
  };
409
462
  /**
410
463
  * Creates a handler for HEAD requests.
@@ -1 +1 @@
1
- "use strict";var e=require("./error.js"),t=require("./context.js"),s=require("./response.js"),r=require("./hooks.js"),n=require("./common.js");const o={key:"path"};class i{constructor(e,t,n){this.request=e,this.method=t,this.payload=n,this.feature=null,this.route=null,this.response=new s.ServerResponse,this.requestHooks=new r.RequestHooks(i.hooks)}static getInject(e){return t.executeContext.getInject(e)}static getInjectSync(e){return t.executeContext.getInjectSync(e)}static getInjects(){return new Map(t.executeContext.injections)}static createInjectRegistry(){return this.registryCreated||(this.registry={get:async e=>t.executeContext.getInject(e),set(e,s){t.executeContext.setInject(e,s)}},t.executeContext.setRegistry(this.registry),this.registryCreated=!0),this.registry}static async registerInjectFactories(){if(this.factoriesRegistered)return;const e=this.options.injects;if(!e||!e.length)return void(this.factoriesRegistered=!0);const t=this.createInjectRegistry();for(const s of e)await s(t);this.factoriesRegistered=!0,this.registerShutdown(),await this.loadCors()}static registerShutdown(){if(this.shutdownRegistered)return;this.shutdownRegistered=!0;const e=async()=>{for(const e of this.features)await e.destroyInjects();for(const[e,s]of t.executeContext.injections)if(s.instance&&"function"==typeof s.instance.onDestroy)try{await s.instance.onDestroy()}catch(e){}process.exit(0)};process.on("SIGTERM",e),process.on("SIGINT",e)}static async loadCors(){const e=this.options.cors;e&&(Array.isArray(e)?this.corsOrigins=e:this.corsOrigins=await e(this.registry))}static async loadImports(){if(!this.initialized)return this.initPromise||(this.initPromise=(async()=>{this.initialized=!0})()),this.initPromise}isMatchFeature(e,t){for(const s of i.features)if(this.route=s.matchRoute(e,t),this.route)return this.feature=s,!0;return null}async executeGuards(){const e=this.route?.route.getReflect().getGuards()??new Set,s=[...i.guards,...e];if(!s.length)return!0;for(const e of s)try{const s=new e;if(!await s.canActivate(t.executeContext))return!1}catch{return!1}return!0}async executeMiddlewares(){const e=this.route.route.getMiddlewares();if(!e.length)return!0;for(const s of e){let e=!1;const r=()=>{e=!0};try{if(await s(t.executeContext,r),!e)return!1}catch{return!1}}return!0}async executeFilters(e){const s=[...this.route?.route.getReflect().getFilters()??new Set,...i.filters];for(const r of s)try{const s=new r,n=await s.catch(e,t.executeContext);if(n instanceof Response)return n}catch{continue}return null}async executeInterceptors(e){const s=[...this.route?.route.getReflect().getInterceptors()??new Set,...i.interceptors];if(!s.length)return e();let r=e;for(let e=s.length-1;e>=0;e--){const n=new(0,s[e]),o=r;r=()=>n.intercept(t.executeContext,o)}return r()}async executePipes(e){const t=this.route?.route.getReflect().getPipes()??new Set,s=[...i.pipes,...t];if(!s.length)return e;let r=e;for(const e of s){const t=new e;r=await t.transform(r)}return r}checkCors(){const e=i.corsOrigins;if(!e.length)return!0;const t=this.request.headers.get("origin");return!t||(e.includes("*")||e.includes(t))}applyCorsHeaders(e){const t=i.corsOrigins;if(!t.length)return e;const s=this.request.headers.get("origin");if(!s)return e;const r=new Headers(e.headers);return t.includes("*")?r.set("Access-Control-Allow-Origin","*"):t.includes(s)&&(r.set("Access-Control-Allow-Origin",s),r.set("Vary","Origin")),r.set("Access-Control-Allow-Methods","GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS"),r.set("Access-Control-Allow-Headers","Content-Type, Authorization"),r.set("Access-Control-Max-Age","86400"),new Response(e.body,{status:e.status,statusText:e.statusText,headers:r})}async dispatch(){const s=this.route,r=this.feature,n=t.executeContext.getContextOrThrow();try{if(!await this.executeGuards())return this.response.forbidden();if(!await this.executeMiddlewares())return this.response.forbidden();const e=s.route.getAction();let t=null;if("string"==typeof e){const s=r.getController();"function"==typeof s._initDependencies&&await s._initDependencies(),"function"==typeof s[e]&&(t=s[e].bind(s))}else"function"==typeof e&&(t=e);if(!t)return this.response.internalServer(new Error("Route action handler not found"));await this.executePipes(n);const o=await this.executeInterceptors(()=>t(n));return this.response.success(o)}catch(t){const s=await this.executeFilters(t);if(s)return s;if(t instanceof e.HttpError){switch(t.statusCode){case 400:return this.response.badRequest(t.message,t.error);case 401:return this.response.unauthorized(t.message,t.error);case 403:return this.response.forbidden(t.message,t.error);case 404:return this.response.notFound(t.message,t.error);case 409:return this.response.conflict(t.message,t.error);case 422:return this.response.unprocessableEntity(t.message,t.error);case 429:return this.response.tooManyRequests(t.message,t.error);case 501:return this.response.notImplemented(t.message,t.error);case 502:return this.response.badGateway(t.message,t.error);case 503:return this.response.serviceUnavailable(t.message,t.error);case 504:return this.response.gatewayTimeout(t.message,t.error)}return this.response.internalServer(t)}return this.response.internalServer(t)}}async match(){let e=null;try{if(!this.checkCors())return this.applyCorsHeaders(this.response.forbidden("CORS not allowed"));const s=await this.payload.params,r=i.options.key||o.key,n=s?.[r]?.join("/")||"";if(!this.isMatchFeature(this.method,n))return this.applyCorsHeaders(this.response.notFound("Route not found",{method:this.method,path:n,params:s}));await this.feature.loadInjects(),e={req:this.request,res:this.response,method:this.method,path:n,params:this.route.params,wildcards:this.route.wildcards,reflect:this.route.route.getReflect(),feature:this.feature},this.requestHooks.setFeatureHooks(this.feature.getHooks()),await this.requestHooks.featureRequest(e);const a=await t.executeContext.run(e,()=>this.dispatch());return await this.requestHooks.featureResponse(e),await this.requestHooks.appResponse(e),this.applyCorsHeaders(a)}catch(t){return e&&(await this.requestHooks.featureResponse(e),await this.requestHooks.appResponse(e)),this.applyCorsHeaders(this.response.internalServer(t))}}static createHandler(e){return async(t,s)=>{await i.registerInjectFactories(),await i.loadImports();const r=new i(t,e,s);return await r.requestHooks.appRequest(t,e),await r.match.bind(r)()}}static create(e=o){return this.options=e,this.hooks=new r.ApplicationHooks(this.options),e.guards&&e.guards.forEach(e=>this.guards.add(e)),e.filters&&e.filters.forEach(e=>this.filters.add(e)),e.interceptors&&e.interceptors.forEach(e=>this.interceptors.add(e)),e.pipes&&e.pipes.forEach(e=>this.pipes.add(e)),{[n.RouteMethod.GET]:i.createHandler(n.RouteMethod.GET),[n.RouteMethod.POST]:i.createHandler(n.RouteMethod.POST),[n.RouteMethod.PUT]:i.createHandler(n.RouteMethod.PUT),[n.RouteMethod.PATCH]:i.createHandler(n.RouteMethod.PATCH),[n.RouteMethod.DELETE]:i.createHandler(n.RouteMethod.DELETE),[n.RouteMethod.HEAD]:i.createHeadHandler(),[n.RouteMethod.OPTIONS]:i.createOptionsHandler()}}static createHeadHandler(){return async(e,t)=>{await i.registerInjectFactories(),await i.loadImports();const s=new i(e,n.RouteMethod.HEAD,t);await s.requestHooks.appRequest(e,n.RouteMethod.HEAD);const r=await s.match.bind(s)();return new Response(null,{status:r.status,statusText:r.statusText,headers:r.headers})}}static createOptionsHandler(){return async e=>{await i.registerInjectFactories();const t=this.corsOrigins,r=e.headers.get("origin"),n=s.ServerResponse.options();if(!t.length||!r)return n;const o=new Headers(n.headers);return t.includes("*")?o.set("Access-Control-Allow-Origin","*"):t.includes(r)&&(o.set("Access-Control-Allow-Origin",r),o.set("Vary","Origin")),o.set("Access-Control-Allow-Methods","GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS"),o.set("Access-Control-Allow-Headers","Content-Type, Authorization"),o.set("Access-Control-Max-Age","86400"),new Response(null,{status:204,headers:o})}}static register(...e){return e.forEach(e=>this.features.add(e)),this}}i.features=new Set,i.guards=new Set,i.filters=new Set,i.interceptors=new Set,i.pipes=new Set,i.corsOrigins=[],i.options=o,i.initialized=!1,i.initPromise=null,i.shutdownRegistered=!1,i.registryCreated=!1,i.factoriesRegistered=!1,exports.Application=i;
1
+ "use strict";var e=require("./error.js"),t=require("./context.js"),s=require("./response.js"),r=require("./hooks.js"),o=require("./logger.js"),n=require("./common.js");const i={key:"path"};class a{static toResponse(e){return!1!==this.poweredBy&&e.headers.set("X-Powered-By",this.poweredBy),this.ResponseClass.json(e.body,{status:e.status,statusText:e.statusText,headers:e.headers})}constructor(e,t,o){this.request=e,this.method=t,this.payload=o,this.feature=null,this.route=null,this.response=new s.ServerResponse,this.requestHooks=new r.RequestHooks(a.hooks)}static getInject(e){return t.executeContext.getInject(e)}static getInjectSync(e){return t.executeContext.getInjectSync(e)}static getInjects(){return new Map(t.executeContext.injections)}static createInjectRegistry(){return this.registryCreated||(this.registry={get:async e=>t.executeContext.getInject(e),set(e,s){t.executeContext.setInject(e,s)}},t.executeContext.setRegistry(this.registry),this.registryCreated=!0),this.registry}static async registerInjectFactories(){if(this.factoriesRegistered)return;const e=this.options.injects;if(!e||!e.length)return void(this.factoriesRegistered=!0);const t=this.createInjectRegistry();for(const s of e)await s(t);this.factoriesRegistered=!0,await this.initializeInjects(),this.registerShutdown(),await this.loadCors()}static async initializeInjects(){for(const[e,s]of t.executeContext.injections)if(!s.instance)try{const e=this.createInjectRegistry();s.instance=await s.factory(e),s.instance&&"function"==typeof s.instance.onInit&&await s.instance.onInit()}catch(t){throw a.logger.error(`Error initializing inject ${e}:`,t),t}}static registerShutdown(){if(this.shutdownRegistered)return;this.shutdownRegistered=!0;const e=async()=>{a.logger.info("Shutting down...");for(const e of this.features)await e.destroyInjects();for(const[e,s]of t.executeContext.injections)if(s.instance&&"function"==typeof s.instance.onDestroy)try{await s.instance.onDestroy(),a.logger.info(`Destroyed inject: ${e}`)}catch(t){a.logger.error(`Error destroying inject ${e}:`,t)}a.logger.info("Shutdown complete"),process.exit(0)};process.on("SIGTERM",e),process.on("SIGINT",e)}static async loadCors(){const e=this.options.cors;e&&(Array.isArray(e)?this.corsOrigins=e:this.corsOrigins=await e(this.registry))}static async loadImports(){if(!this.initialized)return this.initPromise||(this.initPromise=(async()=>{this.initialized=!0})()),this.initPromise}isMatchFeature(e,t){for(const s of a.features)if(this.route=s.matchRoute(e,t),this.route)return this.feature=s,!0;return null}async executeGuards(){const e=this.feature?.getGuards()??new Set,s=this.route?.route.getReflect().getGuards()??new Set,r=[...a.guards,...e,...s];if(!r.length)return!0;for(const e of r)try{const s=new e;if(!await s.canActivate(t.executeContext))return!1}catch{return!1}return!0}async executeMiddlewares(){const e=this.route.route.getMiddlewares();if(!e.length)return!0;for(const s of e){let e=!1;const r=()=>{e=!0};try{if(await s(t.executeContext,r),!e)return!1}catch{return!1}}return!0}async executeFilters(e){const r=[...this.route?.route.getReflect().getFilters()??new Set,...this.feature?.getFilters()??new Set,...a.filters];for(const o of r)try{const r=new o,n=await r.catch(e,t.executeContext);if(n instanceof s.CoreResponse||n instanceof Response)return n}catch{continue}return null}async executeInterceptors(e){const t=[...this.route?.route.getReflect().getInterceptors()??new Set,...this.feature?.getInterceptors()??new Set,...a.interceptors];if(!t.length)return e;let s=e;for(const e of t){const t=new e;s=await t.transform(s)}return s}async executePipes(e){const t=this.feature?.getPipes()??new Set,s=this.route?.route.getReflect().getPipes()??new Set,r=[...a.pipes,...t,...s];if(!r.length)return e;let o=e;for(const e of r){const t=new e;o=await t.transform(o)}return o}checkCors(){const e=a.corsOrigins;if(!e.length)return!0;const t=this.request.headers.get("origin");return!t||(e.includes("*")||e.includes(t))}applyCorsHeaders(e){const t=a.corsOrigins;if(!t.length)return e;const s=this.request.headers.get("origin");return s?(t.includes("*")?e.headers.set("Access-Control-Allow-Origin","*"):t.includes(s)&&(e.headers.set("Access-Control-Allow-Origin",s),e.headers.set("Vary","Origin")),e.headers.set("Access-Control-Allow-Methods","GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS"),e.headers.set("Access-Control-Allow-Headers","Content-Type, Authorization"),e.headers.set("Access-Control-Max-Age","86400"),e):e}async dispatch(){const r=this.route,o=this.feature,n=t.executeContext.getContextOrThrow();try{if(!await this.executeGuards())return this.response.forbidden();if(!await this.executeMiddlewares())return this.response.forbidden();const e=r.route.getAction();let t=null;if("string"==typeof e){const s=o.getController();"function"==typeof s._initDependencies&&await s._initDependencies(),"function"==typeof s[e]&&(t=s[e].bind(s))}else"function"==typeof e&&(t=e);if(!t)return this.response.internalServer(new Error("Route action handler not found"));await this.executePipes(n);const s=await t(n);let i=this.response.success(s);return i=await this.executeInterceptors(i),i}catch(t){const r=await this.executeFilters(t);if(r)return r instanceof Response?s.CoreResponse.json({success:r.ok,message:r.statusText,code:"FILTER_RESPONSE",status:r.status,data:null,error:null},{status:r.status,statusText:r.statusText}):r;if(t instanceof e.HttpError)switch(t.statusCode){case 400:return this.response.badRequest(t.message,t.error);case 401:return this.response.unauthorized(t.message,t.error);case 403:return this.response.forbidden(t.message,t.error);case 404:return this.response.notFound(t.message,t.error);case 409:return this.response.conflict(t.message,t.error);case 422:return this.response.unprocessableEntity(t.message,t.error);case 429:return this.response.tooManyRequests(t.message,t.error);case 501:return this.response.notImplemented(t.message,t.error);case 502:return this.response.badGateway(t.message,t.error);case 503:return this.response.serviceUnavailable(t.message,t.error);case 504:return this.response.gatewayTimeout(t.message,t.error)}return a.logger.error("[dispatch] Error:",t instanceof Error?t.message:t),t instanceof Error&&t.stack&&a.logger.debug("Stack trace:",t.stack),this.response.internalServer(t)}}async match(){let e=null;try{if(!this.checkCors())return this.applyCorsHeaders(this.response.forbidden("CORS not allowed"));const s=await this.payload.params,r=a.options.key||i.key,o=s?.[r]?.join("/")||"";if(!this.isMatchFeature(this.method,o))return this.applyCorsHeaders(this.response.notFound("Route not found",{method:this.method,path:o,params:s}));await this.feature.loadInjects(),e={req:this.request,res:this.response,method:this.method,path:o,params:this.route.params,wildcards:this.route.wildcards,reflect:this.route.route.getReflect(),feature:this.feature},this.requestHooks.setFeatureHooks(this.feature.getHooks()),await this.requestHooks.featureRequest(e);const n=await t.executeContext.run(e,()=>this.dispatch());return await this.requestHooks.featureResponse(e),await this.requestHooks.appResponse(e),this.applyCorsHeaders(n)}catch(t){return e&&(await this.requestHooks.featureResponse(e),await this.requestHooks.appResponse(e)),this.applyCorsHeaders(this.response.internalServer(t))}}static createHandler(e){return async(t,s)=>{await a.registerInjectFactories(),await a.loadImports();const r=new a(t,e,s);await r.requestHooks.appRequest(t,e);const o=await r.match.bind(r)();return a.toResponse(o)}}static create(e=i){return this.options=e,this.hooks=new r.ApplicationHooks(this.options),this.ResponseClass=e.response??Response,this.poweredBy=e.poweredBy??"MxWeb",e.guards&&e.guards.forEach(e=>this.guards.add(e)),e.filters&&e.filters.forEach(e=>this.filters.add(e)),e.interceptors&&e.interceptors.forEach(e=>this.interceptors.add(e)),e.pipes&&e.pipes.forEach(e=>this.pipes.add(e)),{[n.RouteMethod.GET]:a.createHandler(n.RouteMethod.GET),[n.RouteMethod.POST]:a.createHandler(n.RouteMethod.POST),[n.RouteMethod.PUT]:a.createHandler(n.RouteMethod.PUT),[n.RouteMethod.PATCH]:a.createHandler(n.RouteMethod.PATCH),[n.RouteMethod.DELETE]:a.createHandler(n.RouteMethod.DELETE),[n.RouteMethod.HEAD]:a.createHeadHandler(),[n.RouteMethod.OPTIONS]:a.createOptionsHandler()}}static createHeadHandler(){return async(e,t)=>{await a.registerInjectFactories(),await a.loadImports();const s=new a(e,n.RouteMethod.HEAD,t);await s.requestHooks.appRequest(e,n.RouteMethod.HEAD);const r=await s.match.bind(s)();return new Response(null,{status:r.status,statusText:r.statusText,headers:r.headers})}}static createOptionsHandler(){return async e=>{await a.registerInjectFactories();const t=this.corsOrigins,r=e.headers.get("origin"),o=s.ServerResponse.options();if(!t.length||!r)return o;const n=new Headers(o.headers);return t.includes("*")?n.set("Access-Control-Allow-Origin","*"):t.includes(r)&&(n.set("Access-Control-Allow-Origin",r),n.set("Vary","Origin")),n.set("Access-Control-Allow-Methods","GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS"),n.set("Access-Control-Allow-Headers","Content-Type, Authorization"),n.set("Access-Control-Max-Age","86400"),new Response(null,{status:204,headers:n})}}static register(...e){return e.forEach(e=>this.features.add(e)),this}}a.logger=o.Logger.create("Application"),a.features=new Set,a.guards=new Set,a.filters=new Set,a.interceptors=new Set,a.pipes=new Set,a.corsOrigins=[],a.options=i,a.ResponseClass=Response,a.poweredBy="MxWeb",a.initialized=!1,a.initPromise=null,a.shutdownRegistered=!1,a.registryCreated=!1,a.factoriesRegistered=!1,exports.Application=a;
@@ -1 +1 @@
1
- import{HttpError as e}from"./error.mjs";import{executeContext as t}from"./context.mjs";import{ServerResponse as s}from"./response.mjs";import{RequestHooks as r,ApplicationHooks as n}from"./hooks.mjs";import{RouteMethod as i}from"./common.mjs";const o={key:"path"};class a{constructor(e,t,n){this.request=e,this.method=t,this.payload=n,this.feature=null,this.route=null,this.response=new s,this.requestHooks=new r(a.hooks)}static getInject(e){return t.getInject(e)}static getInjectSync(e){return t.getInjectSync(e)}static getInjects(){return new Map(t.injections)}static createInjectRegistry(){return this.registryCreated||(this.registry={get:async e=>t.getInject(e),set(e,s){t.setInject(e,s)}},t.setRegistry(this.registry),this.registryCreated=!0),this.registry}static async registerInjectFactories(){if(this.factoriesRegistered)return;const e=this.options.injects;if(!e||!e.length)return void(this.factoriesRegistered=!0);const t=this.createInjectRegistry();for(const s of e)await s(t);this.factoriesRegistered=!0,this.registerShutdown(),await this.loadCors()}static registerShutdown(){if(this.shutdownRegistered)return;this.shutdownRegistered=!0;const e=async()=>{for(const e of this.features)await e.destroyInjects();for(const[e,s]of t.injections)if(s.instance&&"function"==typeof s.instance.onDestroy)try{await s.instance.onDestroy()}catch(e){}process.exit(0)};process.on("SIGTERM",e),process.on("SIGINT",e)}static async loadCors(){const e=this.options.cors;e&&(Array.isArray(e)?this.corsOrigins=e:this.corsOrigins=await e(this.registry))}static async loadImports(){if(!this.initialized)return this.initPromise||(this.initPromise=(async()=>{this.initialized=!0})()),this.initPromise}isMatchFeature(e,t){for(const s of a.features)if(this.route=s.matchRoute(e,t),this.route)return this.feature=s,!0;return null}async executeGuards(){const e=this.route?.route.getReflect().getGuards()??new Set,s=[...a.guards,...e];if(!s.length)return!0;for(const e of s)try{const s=new e;if(!await s.canActivate(t))return!1}catch{return!1}return!0}async executeMiddlewares(){const e=this.route.route.getMiddlewares();if(!e.length)return!0;for(const s of e){let e=!1;const r=()=>{e=!0};try{if(await s(t,r),!e)return!1}catch{return!1}}return!0}async executeFilters(e){const s=[...this.route?.route.getReflect().getFilters()??new Set,...a.filters];for(const r of s)try{const s=new r,n=await s.catch(e,t);if(n instanceof Response)return n}catch{continue}return null}async executeInterceptors(e){const s=[...this.route?.route.getReflect().getInterceptors()??new Set,...a.interceptors];if(!s.length)return e();let r=e;for(let e=s.length-1;e>=0;e--){const n=new(0,s[e]),i=r;r=()=>n.intercept(t,i)}return r()}async executePipes(e){const t=this.route?.route.getReflect().getPipes()??new Set,s=[...a.pipes,...t];if(!s.length)return e;let r=e;for(const e of s){const t=new e;r=await t.transform(r)}return r}checkCors(){const e=a.corsOrigins;if(!e.length)return!0;const t=this.request.headers.get("origin");return!t||(e.includes("*")||e.includes(t))}applyCorsHeaders(e){const t=a.corsOrigins;if(!t.length)return e;const s=this.request.headers.get("origin");if(!s)return e;const r=new Headers(e.headers);return t.includes("*")?r.set("Access-Control-Allow-Origin","*"):t.includes(s)&&(r.set("Access-Control-Allow-Origin",s),r.set("Vary","Origin")),r.set("Access-Control-Allow-Methods","GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS"),r.set("Access-Control-Allow-Headers","Content-Type, Authorization"),r.set("Access-Control-Max-Age","86400"),new Response(e.body,{status:e.status,statusText:e.statusText,headers:r})}async dispatch(){const s=this.route,r=this.feature,n=t.getContextOrThrow();try{if(!await this.executeGuards())return this.response.forbidden();if(!await this.executeMiddlewares())return this.response.forbidden();const e=s.route.getAction();let t=null;if("string"==typeof e){const s=r.getController();"function"==typeof s._initDependencies&&await s._initDependencies(),"function"==typeof s[e]&&(t=s[e].bind(s))}else"function"==typeof e&&(t=e);if(!t)return this.response.internalServer(new Error("Route action handler not found"));await this.executePipes(n);const i=await this.executeInterceptors(()=>t(n));return this.response.success(i)}catch(t){const s=await this.executeFilters(t);if(s)return s;if(t instanceof e){switch(t.statusCode){case 400:return this.response.badRequest(t.message,t.error);case 401:return this.response.unauthorized(t.message,t.error);case 403:return this.response.forbidden(t.message,t.error);case 404:return this.response.notFound(t.message,t.error);case 409:return this.response.conflict(t.message,t.error);case 422:return this.response.unprocessableEntity(t.message,t.error);case 429:return this.response.tooManyRequests(t.message,t.error);case 501:return this.response.notImplemented(t.message,t.error);case 502:return this.response.badGateway(t.message,t.error);case 503:return this.response.serviceUnavailable(t.message,t.error);case 504:return this.response.gatewayTimeout(t.message,t.error)}return this.response.internalServer(t)}return this.response.internalServer(t)}}async match(){let e=null;try{if(!this.checkCors())return this.applyCorsHeaders(this.response.forbidden("CORS not allowed"));const s=await this.payload.params,r=a.options.key||o.key,n=s?.[r]?.join("/")||"";if(!this.isMatchFeature(this.method,n))return this.applyCorsHeaders(this.response.notFound("Route not found",{method:this.method,path:n,params:s}));await this.feature.loadInjects(),e={req:this.request,res:this.response,method:this.method,path:n,params:this.route.params,wildcards:this.route.wildcards,reflect:this.route.route.getReflect(),feature:this.feature},this.requestHooks.setFeatureHooks(this.feature.getHooks()),await this.requestHooks.featureRequest(e);const i=await t.run(e,()=>this.dispatch());return await this.requestHooks.featureResponse(e),await this.requestHooks.appResponse(e),this.applyCorsHeaders(i)}catch(t){return e&&(await this.requestHooks.featureResponse(e),await this.requestHooks.appResponse(e)),this.applyCorsHeaders(this.response.internalServer(t))}}static createHandler(e){return async(t,s)=>{await a.registerInjectFactories(),await a.loadImports();const r=new a(t,e,s);return await r.requestHooks.appRequest(t,e),await r.match.bind(r)()}}static create(e=o){return this.options=e,this.hooks=new n(this.options),e.guards&&e.guards.forEach(e=>this.guards.add(e)),e.filters&&e.filters.forEach(e=>this.filters.add(e)),e.interceptors&&e.interceptors.forEach(e=>this.interceptors.add(e)),e.pipes&&e.pipes.forEach(e=>this.pipes.add(e)),{[i.GET]:a.createHandler(i.GET),[i.POST]:a.createHandler(i.POST),[i.PUT]:a.createHandler(i.PUT),[i.PATCH]:a.createHandler(i.PATCH),[i.DELETE]:a.createHandler(i.DELETE),[i.HEAD]:a.createHeadHandler(),[i.OPTIONS]:a.createOptionsHandler()}}static createHeadHandler(){return async(e,t)=>{await a.registerInjectFactories(),await a.loadImports();const s=new a(e,i.HEAD,t);await s.requestHooks.appRequest(e,i.HEAD);const r=await s.match.bind(s)();return new Response(null,{status:r.status,statusText:r.statusText,headers:r.headers})}}static createOptionsHandler(){return async e=>{await a.registerInjectFactories();const t=this.corsOrigins,r=e.headers.get("origin"),n=s.options();if(!t.length||!r)return n;const i=new Headers(n.headers);return t.includes("*")?i.set("Access-Control-Allow-Origin","*"):t.includes(r)&&(i.set("Access-Control-Allow-Origin",r),i.set("Vary","Origin")),i.set("Access-Control-Allow-Methods","GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS"),i.set("Access-Control-Allow-Headers","Content-Type, Authorization"),i.set("Access-Control-Max-Age","86400"),new Response(null,{status:204,headers:i})}}static register(...e){return e.forEach(e=>this.features.add(e)),this}}a.features=new Set,a.guards=new Set,a.filters=new Set,a.interceptors=new Set,a.pipes=new Set,a.corsOrigins=[],a.options=o,a.initialized=!1,a.initPromise=null,a.shutdownRegistered=!1,a.registryCreated=!1,a.factoriesRegistered=!1;export{a as Application};
1
+ import{HttpError as e}from"./error.mjs";import{executeContext as t}from"./context.mjs";import{ServerResponse as s,CoreResponse as r}from"./response.mjs";import{RequestHooks as n,ApplicationHooks as i}from"./hooks.mjs";import{Logger as o}from"./logger.mjs";import{RouteMethod as a}from"./common.mjs";const c={key:"path"};class u{static toResponse(e){return!1!==this.poweredBy&&e.headers.set("X-Powered-By",this.poweredBy),this.ResponseClass.json(e.body,{status:e.status,statusText:e.statusText,headers:e.headers})}constructor(e,t,r){this.request=e,this.method=t,this.payload=r,this.feature=null,this.route=null,this.response=new s,this.requestHooks=new n(u.hooks)}static getInject(e){return t.getInject(e)}static getInjectSync(e){return t.getInjectSync(e)}static getInjects(){return new Map(t.injections)}static createInjectRegistry(){return this.registryCreated||(this.registry={get:async e=>t.getInject(e),set(e,s){t.setInject(e,s)}},t.setRegistry(this.registry),this.registryCreated=!0),this.registry}static async registerInjectFactories(){if(this.factoriesRegistered)return;const e=this.options.injects;if(!e||!e.length)return void(this.factoriesRegistered=!0);const t=this.createInjectRegistry();for(const s of e)await s(t);this.factoriesRegistered=!0,await this.initializeInjects(),this.registerShutdown(),await this.loadCors()}static async initializeInjects(){for(const[e,s]of t.injections)if(!s.instance)try{const e=this.createInjectRegistry();s.instance=await s.factory(e),s.instance&&"function"==typeof s.instance.onInit&&await s.instance.onInit()}catch(t){throw u.logger.error(`Error initializing inject ${e}:`,t),t}}static registerShutdown(){if(this.shutdownRegistered)return;this.shutdownRegistered=!0;const e=async()=>{u.logger.info("Shutting down...");for(const e of this.features)await e.destroyInjects();for(const[e,s]of t.injections)if(s.instance&&"function"==typeof s.instance.onDestroy)try{await s.instance.onDestroy(),u.logger.info(`Destroyed inject: ${e}`)}catch(t){u.logger.error(`Error destroying inject ${e}:`,t)}u.logger.info("Shutdown complete"),process.exit(0)};process.on("SIGTERM",e),process.on("SIGINT",e)}static async loadCors(){const e=this.options.cors;e&&(Array.isArray(e)?this.corsOrigins=e:this.corsOrigins=await e(this.registry))}static async loadImports(){if(!this.initialized)return this.initPromise||(this.initPromise=(async()=>{this.initialized=!0})()),this.initPromise}isMatchFeature(e,t){for(const s of u.features)if(this.route=s.matchRoute(e,t),this.route)return this.feature=s,!0;return null}async executeGuards(){const e=this.feature?.getGuards()??new Set,s=this.route?.route.getReflect().getGuards()??new Set,r=[...u.guards,...e,...s];if(!r.length)return!0;for(const e of r)try{const s=new e;if(!await s.canActivate(t))return!1}catch{return!1}return!0}async executeMiddlewares(){const e=this.route.route.getMiddlewares();if(!e.length)return!0;for(const s of e){let e=!1;const r=()=>{e=!0};try{if(await s(t,r),!e)return!1}catch{return!1}}return!0}async executeFilters(e){const s=[...this.route?.route.getReflect().getFilters()??new Set,...this.feature?.getFilters()??new Set,...u.filters];for(const n of s)try{const s=new n,i=await s.catch(e,t);if(i instanceof r||i instanceof Response)return i}catch{continue}return null}async executeInterceptors(e){const t=[...this.route?.route.getReflect().getInterceptors()??new Set,...this.feature?.getInterceptors()??new Set,...u.interceptors];if(!t.length)return e;let s=e;for(const e of t){const t=new e;s=await t.transform(s)}return s}async executePipes(e){const t=this.feature?.getPipes()??new Set,s=this.route?.route.getReflect().getPipes()??new Set,r=[...u.pipes,...t,...s];if(!r.length)return e;let n=e;for(const e of r){const t=new e;n=await t.transform(n)}return n}checkCors(){const e=u.corsOrigins;if(!e.length)return!0;const t=this.request.headers.get("origin");return!t||(e.includes("*")||e.includes(t))}applyCorsHeaders(e){const t=u.corsOrigins;if(!t.length)return e;const s=this.request.headers.get("origin");return s?(t.includes("*")?e.headers.set("Access-Control-Allow-Origin","*"):t.includes(s)&&(e.headers.set("Access-Control-Allow-Origin",s),e.headers.set("Vary","Origin")),e.headers.set("Access-Control-Allow-Methods","GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS"),e.headers.set("Access-Control-Allow-Headers","Content-Type, Authorization"),e.headers.set("Access-Control-Max-Age","86400"),e):e}async dispatch(){const s=this.route,n=this.feature,i=t.getContextOrThrow();try{if(!await this.executeGuards())return this.response.forbidden();if(!await this.executeMiddlewares())return this.response.forbidden();const e=s.route.getAction();let t=null;if("string"==typeof e){const s=n.getController();"function"==typeof s._initDependencies&&await s._initDependencies(),"function"==typeof s[e]&&(t=s[e].bind(s))}else"function"==typeof e&&(t=e);if(!t)return this.response.internalServer(new Error("Route action handler not found"));await this.executePipes(i);const r=await t(i);let o=this.response.success(r);return o=await this.executeInterceptors(o),o}catch(t){const s=await this.executeFilters(t);if(s)return s instanceof Response?r.json({success:s.ok,message:s.statusText,code:"FILTER_RESPONSE",status:s.status,data:null,error:null},{status:s.status,statusText:s.statusText}):s;if(t instanceof e)switch(t.statusCode){case 400:return this.response.badRequest(t.message,t.error);case 401:return this.response.unauthorized(t.message,t.error);case 403:return this.response.forbidden(t.message,t.error);case 404:return this.response.notFound(t.message,t.error);case 409:return this.response.conflict(t.message,t.error);case 422:return this.response.unprocessableEntity(t.message,t.error);case 429:return this.response.tooManyRequests(t.message,t.error);case 501:return this.response.notImplemented(t.message,t.error);case 502:return this.response.badGateway(t.message,t.error);case 503:return this.response.serviceUnavailable(t.message,t.error);case 504:return this.response.gatewayTimeout(t.message,t.error)}return u.logger.error("[dispatch] Error:",t instanceof Error?t.message:t),t instanceof Error&&t.stack&&u.logger.debug("Stack trace:",t.stack),this.response.internalServer(t)}}async match(){let e=null;try{if(!this.checkCors())return this.applyCorsHeaders(this.response.forbidden("CORS not allowed"));const s=await this.payload.params,r=u.options.key||c.key,n=s?.[r]?.join("/")||"";if(!this.isMatchFeature(this.method,n))return this.applyCorsHeaders(this.response.notFound("Route not found",{method:this.method,path:n,params:s}));await this.feature.loadInjects(),e={req:this.request,res:this.response,method:this.method,path:n,params:this.route.params,wildcards:this.route.wildcards,reflect:this.route.route.getReflect(),feature:this.feature},this.requestHooks.setFeatureHooks(this.feature.getHooks()),await this.requestHooks.featureRequest(e);const i=await t.run(e,()=>this.dispatch());return await this.requestHooks.featureResponse(e),await this.requestHooks.appResponse(e),this.applyCorsHeaders(i)}catch(t){return e&&(await this.requestHooks.featureResponse(e),await this.requestHooks.appResponse(e)),this.applyCorsHeaders(this.response.internalServer(t))}}static createHandler(e){return async(t,s)=>{await u.registerInjectFactories(),await u.loadImports();const r=new u(t,e,s);await r.requestHooks.appRequest(t,e);const n=await r.match.bind(r)();return u.toResponse(n)}}static create(e=c){return this.options=e,this.hooks=new i(this.options),this.ResponseClass=e.response??Response,this.poweredBy=e.poweredBy??"MxWeb",e.guards&&e.guards.forEach(e=>this.guards.add(e)),e.filters&&e.filters.forEach(e=>this.filters.add(e)),e.interceptors&&e.interceptors.forEach(e=>this.interceptors.add(e)),e.pipes&&e.pipes.forEach(e=>this.pipes.add(e)),{[a.GET]:u.createHandler(a.GET),[a.POST]:u.createHandler(a.POST),[a.PUT]:u.createHandler(a.PUT),[a.PATCH]:u.createHandler(a.PATCH),[a.DELETE]:u.createHandler(a.DELETE),[a.HEAD]:u.createHeadHandler(),[a.OPTIONS]:u.createOptionsHandler()}}static createHeadHandler(){return async(e,t)=>{await u.registerInjectFactories(),await u.loadImports();const s=new u(e,a.HEAD,t);await s.requestHooks.appRequest(e,a.HEAD);const r=await s.match.bind(s)();return new Response(null,{status:r.status,statusText:r.statusText,headers:r.headers})}}static createOptionsHandler(){return async e=>{await u.registerInjectFactories();const t=this.corsOrigins,r=e.headers.get("origin"),n=s.options();if(!t.length||!r)return n;const i=new Headers(n.headers);return t.includes("*")?i.set("Access-Control-Allow-Origin","*"):t.includes(r)&&(i.set("Access-Control-Allow-Origin",r),i.set("Vary","Origin")),i.set("Access-Control-Allow-Methods","GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS"),i.set("Access-Control-Allow-Headers","Content-Type, Authorization"),i.set("Access-Control-Max-Age","86400"),new Response(null,{status:204,headers:i})}}static register(...e){return e.forEach(e=>this.features.add(e)),this}}u.logger=o.create("Application"),u.features=new Set,u.guards=new Set,u.filters=new Set,u.interceptors=new Set,u.pipes=new Set,u.corsOrigins=[],u.options=c,u.ResponseClass=Response,u.poweredBy="MxWeb",u.initialized=!1,u.initPromise=null,u.shutdownRegistered=!1,u.registryCreated=!1,u.factoriesRegistered=!1;export{u as Application};