@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 +294 -0
- package/dist/application.d.ts +90 -37
- package/dist/application.js +1 -1
- package/dist/application.mjs +1 -1
- package/dist/common.d.ts +255 -3
- package/dist/config.d.ts +18 -0
- package/dist/config.js +1 -1
- package/dist/config.mjs +1 -1
- package/dist/decorator.d.ts +12 -5
- package/dist/execute.d.ts +293 -135
- package/dist/execute.js +1 -1
- package/dist/execute.mjs +1 -1
- package/dist/feature.d.ts +48 -10
- package/dist/feature.js +1 -1
- package/dist/feature.mjs +1 -1
- package/dist/hooks.d.ts +5 -6
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/logger.js +1 -1
- package/dist/logger.mjs +1 -1
- package/dist/response.d.ts +297 -227
- package/dist/response.js +1 -1
- package/dist/response.mjs +1 -1
- package/dist/route.d.ts +106 -2
- package/dist/route.js +1 -1
- package/dist/route.mjs +1 -1
- package/dist/service.d.ts +9 -8
- package/package.json +1 -6
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
|
package/dist/application.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { NextRequest } from "next/server";
|
|
2
1
|
import { Feature } from "./feature";
|
|
3
|
-
import {
|
|
2
|
+
import { CoreResponse, ResponseClass } from "./response";
|
|
3
|
+
import { CoreRequest } from "./execute";
|
|
4
4
|
import { ApplicationHooksOptions } from "./hooks";
|
|
5
|
-
import { ApplicationInject, InjectEntry, InjectFactory, InjectRegistry,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
296
|
-
* Route-level interceptors
|
|
348
|
+
* Applies interceptors to transform the response.
|
|
349
|
+
* Route-level interceptors run first, then global interceptors.
|
|
297
350
|
*
|
|
298
|
-
* @
|
|
299
|
-
* @
|
|
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
|
|
304
|
-
*
|
|
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
|
|
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
|
|
326
|
-
* @returns
|
|
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
|
|
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:
|
|
402
|
-
POST: (req:
|
|
403
|
-
PUT: (req:
|
|
404
|
-
PATCH: (req:
|
|
405
|
-
DELETE: (req:
|
|
406
|
-
HEAD: (req:
|
|
407
|
-
OPTIONS: (req:
|
|
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.
|
package/dist/application.js
CHANGED
|
@@ -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
|
|
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;
|
package/dist/application.mjs
CHANGED
|
@@ -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
|
|
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};
|