@mxweb/core 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +239 -0
- package/dist/application.d.ts +88 -27
- package/dist/application.js +1 -1
- package/dist/application.mjs +1 -1
- package/dist/common.d.ts +50 -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 +11 -4
- package/dist/execute.d.ts +312 -58
- package/dist/execute.js +1 -1
- package/dist/execute.mjs +1 -1
- package/dist/feature.d.ts +9 -7
- 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 +107 -3
- 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,245 @@ 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.1.0] - 2026-01-02
|
|
9
|
+
|
|
10
|
+
### ⚠️ Breaking Changes
|
|
11
|
+
|
|
12
|
+
- **Framework Independence**: Removed all direct dependencies on Next.js
|
|
13
|
+
- The framework is now fully framework-agnostic
|
|
14
|
+
- Next.js is still the primary target and fully compatible
|
|
15
|
+
- Removed `next` from `peerDependencies`
|
|
16
|
+
- Removed `@mxweb/utils` dependency (utilities are now built-in)
|
|
17
|
+
- Use `CoreRequest` interface instead of `NextRequest`
|
|
18
|
+
- Use `CoreResponse` class instead of `NextResponse`
|
|
19
|
+
|
|
20
|
+
- **Interceptor Redesign**: Interceptors now transform responses instead of wrapping handlers
|
|
21
|
+
- Old pattern: `intercept(context, next)` - wrapped handler execution
|
|
22
|
+
- New pattern: `transform(response)` - receives `CoreResponse` and transforms it
|
|
23
|
+
- Interceptors no longer have access to `CallHandler` (deprecated)
|
|
24
|
+
- Use `ResponseInterceptorHandler` interface instead of `InterceptorHandler`
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **Application Options**: New configuration options for framework-agnostic response handling
|
|
29
|
+
- `response?: ResponseClass` - Pass response class (NextResponse, Response) for final response creation
|
|
30
|
+
- Defaults to standard `Response` if not provided
|
|
31
|
+
- Example: `Application.create({ response: NextResponse })`
|
|
32
|
+
- `poweredBy?: string | false` - Customize X-Powered-By header
|
|
33
|
+
- Defaults to `"MxWeb"`
|
|
34
|
+
- Set to `false` to disable the header
|
|
35
|
+
- Example: `Application.create({ poweredBy: "MyAPI/1.0" })`
|
|
36
|
+
|
|
37
|
+
- **Response**: Added `ResponseClass` interface for response class compatibility
|
|
38
|
+
- Interface for classes with static `json(body, init)` method
|
|
39
|
+
- Compatible with `NextResponse`, `Response`, and custom response classes
|
|
40
|
+
- Allows passing response class directly to `Application.create()`
|
|
41
|
+
|
|
42
|
+
- **Response**: Added `ResponseInterceptor` type and `ResponseInterceptorHandler` interface
|
|
43
|
+
- `ResponseInterceptor<T>` - Function type for simple response transformers
|
|
44
|
+
- `ResponseInterceptorHandler<T>` - Interface for class-based interceptors
|
|
45
|
+
- `transform(response: CoreResponse)` method for transforming responses
|
|
46
|
+
- Interceptors can add headers, modify body, or return new `CoreResponse`
|
|
47
|
+
|
|
48
|
+
- **Response**: Added `applyTransformer()` helper function
|
|
49
|
+
- Handles both class-based and function-based transformers
|
|
50
|
+
- Automatically detects if transformer is a class with `json()` method
|
|
51
|
+
|
|
52
|
+
- **Response**: Added `CoreResponse` class and `CoreResponseBody` interface
|
|
53
|
+
- Framework-agnostic response wrapper
|
|
54
|
+
- `CoreResponseBody<T>` - Standardized JSON response structure
|
|
55
|
+
- `CoreResponse.json()` - Returns the response body for custom response handling
|
|
56
|
+
- `ResponseTransformer<R>` - Type for custom response transformers
|
|
57
|
+
- All `ServerResponse` methods now return `CoreResponse` instead of `NextResponse`
|
|
58
|
+
|
|
59
|
+
- **Route**: Added fluent chain API helper methods for easier route configuration
|
|
60
|
+
- `decorators(...decorators)` - Add multiple decorator results directly (lazy applied on `getReflect()`)
|
|
61
|
+
- `middlewares(...middlewares)` - Add middleware functions
|
|
62
|
+
- `guards(...guards)` - Add guard classes
|
|
63
|
+
- `filters(...filters)` - Add exception filter classes
|
|
64
|
+
- `interceptors(...interceptors)` - Add interceptor classes
|
|
65
|
+
- `pipes(...pipes)` - Add pipe classes
|
|
66
|
+
- `metadata(key, value)` - Set metadata key-value pairs
|
|
67
|
+
- All chain methods return new Route instance (immutable pattern)
|
|
68
|
+
- Example: `Route.get("/users", "findAll").guards(AuthGuard).metadata("roles", ["admin"])`
|
|
69
|
+
|
|
70
|
+
- **Common**: Added `Switchable<T>` interface for safe inject facade pattern
|
|
71
|
+
- Provides type-safe way to expose controlled API from injects
|
|
72
|
+
- `ApplicationInject` and `FeatureInject` now extend `Partial<Switchable>`
|
|
73
|
+
- Example: `class Connection implements ApplicationInject, Switchable<DbClient> { ... }`
|
|
74
|
+
|
|
75
|
+
- **Common**: Added `Callback<R, A>` utility type (previously from `@mxweb/utils`)
|
|
76
|
+
|
|
77
|
+
- **ExecuteContext**: Added `switch()` and `switchSync()` methods for inject context switching
|
|
78
|
+
- `switch<T>(name)` - Async method to get inject and call its `switch()` method (throws if not found)
|
|
79
|
+
- `switchSync<T>(name)` - Sync version (only works if inject already initialized)
|
|
80
|
+
- Searches feature injects first (priority), then falls back to global injects
|
|
81
|
+
- Example: `const db = await this.context.switch<DatabaseClient>("db")`
|
|
82
|
+
|
|
83
|
+
- **ExecuteContext**: Added `CoreRequest` interface to abstract HTTP request
|
|
84
|
+
- Framework-agnostic request interface (compatible with Next.js, Express, Fastify, Fetch API)
|
|
85
|
+
- Properties: `nextUrl?`, `url?`, `query?`, `headers`, `body`, `json()`, `text()`, `formData()`, `blob()`, `arrayBuffer()`
|
|
86
|
+
- `RequestContext.req` now uses `CoreRequest` instead of `NextRequest`
|
|
87
|
+
- `switchHttp().getRequest<T>()` is now generic for type-safe request access
|
|
88
|
+
|
|
89
|
+
- **ExecuteContext**: Added `CoreSearchParams` class for unified query parameter handling
|
|
90
|
+
- Constructor accepts `URLSearchParams`, query string, or query object
|
|
91
|
+
- Type-safe getter methods: `get()`, `getAll()`, `getNumber()`, `getBoolean()`
|
|
92
|
+
- Helper methods: `has()`, `keys()`, `values()`, `entries()`, `forEach()`, `size`
|
|
93
|
+
- Conversion methods: `toObject()`, `toObjectAll()`, `toURLSearchParams()`, `toString()`
|
|
94
|
+
- `switchHttp().searchParams()` returns cached `CoreSearchParams` instance
|
|
95
|
+
- `switchHttp().query()` now uses `searchParams().toObject()` for backward compatibility
|
|
96
|
+
- Fallback chain: `nextUrl.searchParams` → `url.searchParams` → `url` string → `query` object
|
|
97
|
+
|
|
98
|
+
- **Config**: Added `switch()` method implementation
|
|
99
|
+
- Returns the Config instance itself for context switching
|
|
100
|
+
- Example: `const config = await this.context.getInject<Config>("config")`
|
|
101
|
+
|
|
102
|
+
### Changed
|
|
103
|
+
|
|
104
|
+
- **Application**: Response handling is now centralized in `toResponse()` method
|
|
105
|
+
- All responses go through `Application.toResponse()` before being returned
|
|
106
|
+
- Adds `X-Powered-By` header automatically (configurable via `poweredBy` option)
|
|
107
|
+
- Converts `CoreResponse` to configured response class (NextResponse, Response, etc.)
|
|
108
|
+
|
|
109
|
+
- **Interceptor**: Complete redesign of interceptor pattern
|
|
110
|
+
- Interceptors now receive `CoreResponse` and return transformed `CoreResponse`
|
|
111
|
+
- No longer wrap handler execution - called after handler returns
|
|
112
|
+
- Route interceptors run first, then global interceptors
|
|
113
|
+
- Example:
|
|
114
|
+
```ts
|
|
115
|
+
class LoggingInterceptor implements ResponseInterceptorHandler {
|
|
116
|
+
transform(response: CoreResponse): CoreResponse {
|
|
117
|
+
console.log(`[${response.status}] ${response.body.message}`);
|
|
118
|
+
response.headers.set("X-Logged", "true");
|
|
119
|
+
return response;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- **ExecuteContext**: `getInject()` and `getLocalInject()` now return `switch()` result
|
|
125
|
+
- If inject implements `switch()`, returns the switched value instead of raw instance
|
|
126
|
+
- If inject doesn't implement `switch()`, returns the raw instance (backward compatible)
|
|
127
|
+
- Same behavior for `getInjectSync()` and `getLocalInjectSync()`
|
|
128
|
+
- This provides a safe API by default, hiding dangerous methods like `close()`, `destroy()`
|
|
129
|
+
- Lifecycle hooks (`onInit`, `onDestroy`) are still called on raw instance
|
|
130
|
+
|
|
131
|
+
- **Feature**: `getInject()` and `getInjectSync()` now return `switch()` result
|
|
132
|
+
- Same pattern as ExecuteContext for consistency
|
|
133
|
+
- Lifecycle hooks (`onFeature`, `onFeatureDestroy`) are still called on raw instance
|
|
134
|
+
|
|
135
|
+
- **Service**: `getInject()` and `getInjectSync()` now return `switch()` result
|
|
136
|
+
- Follows the same pattern as ExecuteContext for consistency
|
|
137
|
+
|
|
138
|
+
- **Hooks**: `onRequest` hook now receives `CoreRequest` instead of `NextRequest`
|
|
139
|
+
|
|
140
|
+
- **Decorator**: `Request()` function now returns `CoreRequest` and is generic
|
|
141
|
+
- Example: `const req = Request<NextRequest>()` for Next.js type-safe access
|
|
142
|
+
|
|
143
|
+
### Deprecated
|
|
144
|
+
|
|
145
|
+
- **Common**: `CallHandler<T>` type is deprecated
|
|
146
|
+
- Interceptors no longer use the `intercept(context, next)` pattern
|
|
147
|
+
- Use `ResponseInterceptorHandler` with `transform(response)` instead
|
|
148
|
+
|
|
149
|
+
### Removed
|
|
150
|
+
|
|
151
|
+
- Removed `next` from `peerDependencies` (no longer required)
|
|
152
|
+
- Removed `@mxweb/utils` dependency (utilities are now built-in)
|
|
153
|
+
- Removed `InterceptorHandler` interface (replaced by `ResponseInterceptorHandler`)
|
|
154
|
+
|
|
155
|
+
### Fixed
|
|
156
|
+
|
|
157
|
+
- **Application**: Fixed inject lifecycle `onInit` not being called at startup
|
|
158
|
+
- Previously, `onInit` was only called when inject was first accessed (lazy initialization)
|
|
159
|
+
- Now, all registered injects are initialized during `registerInjectFactories()`
|
|
160
|
+
- Added `initializeInjects()` method to ensure `onInit` runs for all injects at application startup
|
|
161
|
+
- This is critical for injects like Migration that need to run immediately
|
|
162
|
+
|
|
163
|
+
### Migration Guide
|
|
164
|
+
|
|
165
|
+
#### For Next.js users
|
|
166
|
+
|
|
167
|
+
The framework remains fully compatible with Next.js. Configure your application:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
import { Application } from "@mxweb/core";
|
|
171
|
+
import { NextResponse } from "next/server";
|
|
172
|
+
|
|
173
|
+
const app = Application.create({
|
|
174
|
+
response: NextResponse, // Use NextResponse for final responses
|
|
175
|
+
poweredBy: "MyAPI/1.0", // Optional: customize X-Powered-By header
|
|
176
|
+
// ... other options
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
export const GET = app.GET;
|
|
180
|
+
export const POST = app.POST;
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### Request handling
|
|
184
|
+
|
|
185
|
+
Use `CoreRequest` interface, which is compatible with `NextRequest`:
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
// Before
|
|
189
|
+
const req: NextRequest = ...
|
|
190
|
+
|
|
191
|
+
// After (CoreRequest is compatible)
|
|
192
|
+
const req: CoreRequest = nextRequest; // Works as-is
|
|
193
|
+
|
|
194
|
+
// Or for type-safe Next.js specific features
|
|
195
|
+
const req = Request<NextRequest>();
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
#### Response handling
|
|
199
|
+
|
|
200
|
+
`ServerResponse` methods return `CoreResponse`, which is automatically converted:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
// The framework handles conversion internally
|
|
204
|
+
// For custom handling in filters:
|
|
205
|
+
const coreResponse = response.success(data);
|
|
206
|
+
return NextResponse.json(coreResponse.json(), {
|
|
207
|
+
status: coreResponse.status,
|
|
208
|
+
headers: coreResponse.headers
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
#### Interceptor migration
|
|
213
|
+
|
|
214
|
+
Update interceptors to use the new `transform()` pattern:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
// Before (v1.0.x)
|
|
218
|
+
class LoggingInterceptor implements InterceptorHandler {
|
|
219
|
+
async intercept(context: ExecuteContext, next: CallHandler): Promise<unknown> {
|
|
220
|
+
const start = Date.now();
|
|
221
|
+
const result = await next();
|
|
222
|
+
console.log(`Request took ${Date.now() - start}ms`);
|
|
223
|
+
return result;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// After (v1.1.0)
|
|
228
|
+
class LoggingInterceptor implements ResponseInterceptorHandler {
|
|
229
|
+
transform(response: CoreResponse): CoreResponse {
|
|
230
|
+
console.log(`[${response.status}] ${response.body.message}`);
|
|
231
|
+
response.headers.set("X-Response-Time", Date.now().toString());
|
|
232
|
+
return response;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## [1.0.2] - 2024-12-18
|
|
238
|
+
|
|
239
|
+
### Changed
|
|
240
|
+
|
|
241
|
+
- **Build**: Improved terser configuration for logger module
|
|
242
|
+
- Added custom `terserWithOptions()` plugin for file-specific minification
|
|
243
|
+
- Logger module now keeps console calls while still being minified
|
|
244
|
+
- Other modules continue to have console calls removed during minification
|
|
245
|
+
- Added `loggerMinifyOptions` with `drop_console: false` for logger
|
|
246
|
+
|
|
8
247
|
## [1.0.1] - 2024-12-16
|
|
9
248
|
|
|
10
249
|
### Changed
|
package/dist/application.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { NextRequest } from "next/server";
|
|
2
1
|
import { Feature } from "./feature";
|
|
3
|
-
import {
|
|
2
|
+
import { CoreResponse, ResponseClass } from "./response";
|
|
3
|
+
import { CoreRequest, Filter, Guard, Interceptor } from "./execute";
|
|
4
4
|
import { ApplicationHooksOptions } from "./hooks";
|
|
5
5
|
import { ApplicationInject, InjectEntry, InjectFactory, InjectRegistry, Pipe, RoutePayload } from "./common";
|
|
6
6
|
/**
|
|
@@ -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,11 +67,44 @@ 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
78
|
export interface ApplicationOptions<Key extends string = "path"> extends ApplicationHooksOptions {
|
|
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
110
|
/** Global guards applied to all routes */
|
|
@@ -82,7 +119,8 @@ export interface ApplicationOptions<Key extends string = "path"> extends Applica
|
|
|
82
119
|
cors?: ApplicationCors;
|
|
83
120
|
}
|
|
84
121
|
/**
|
|
85
|
-
* Main Application class for handling HTTP requests
|
|
122
|
+
* Main Application class for handling HTTP requests.
|
|
123
|
+
* Designed to be framework-agnostic with primary support for Next.js App Router.
|
|
86
124
|
*
|
|
87
125
|
* This class provides a NestJS-inspired framework for building APIs with:
|
|
88
126
|
* - Dependency injection
|
|
@@ -91,11 +129,11 @@ export interface ApplicationOptions<Key extends string = "path"> extends Applica
|
|
|
91
129
|
* - Lifecycle hooks
|
|
92
130
|
* - CORS support
|
|
93
131
|
*
|
|
94
|
-
* @template Key - The key used to extract path segments from
|
|
132
|
+
* @template Key - The key used to extract path segments from catch-all route params
|
|
95
133
|
*
|
|
96
134
|
* @example
|
|
97
135
|
* ```ts
|
|
98
|
-
* // In app/api/[[...path]]/route.ts
|
|
136
|
+
* // In app/api/[[...path]]/route.ts (Next.js)
|
|
99
137
|
* import { Application } from "@mxweb/core";
|
|
100
138
|
* import "@/features/products/product.feature";
|
|
101
139
|
*
|
|
@@ -121,6 +159,8 @@ export declare class Application<Key extends string = "path"> {
|
|
|
121
159
|
private readonly request;
|
|
122
160
|
private readonly method;
|
|
123
161
|
private readonly payload;
|
|
162
|
+
/** Logger instance for Application */
|
|
163
|
+
private static logger;
|
|
124
164
|
/** Registered features (modules) */
|
|
125
165
|
private static features;
|
|
126
166
|
/** Global guards */
|
|
@@ -135,6 +175,10 @@ export declare class Application<Key extends string = "path"> {
|
|
|
135
175
|
private static corsOrigins;
|
|
136
176
|
/** Application configuration options */
|
|
137
177
|
private static options;
|
|
178
|
+
/** Response class for creating final responses */
|
|
179
|
+
private static ResponseClass;
|
|
180
|
+
/** X-Powered-By header value (false to disable) */
|
|
181
|
+
private static poweredBy;
|
|
138
182
|
/** Application-level hooks manager */
|
|
139
183
|
private static hooks;
|
|
140
184
|
/** Flag indicating if features are initialized */
|
|
@@ -151,11 +195,18 @@ export declare class Application<Key extends string = "path"> {
|
|
|
151
195
|
private route;
|
|
152
196
|
/** Request-scoped hooks manager */
|
|
153
197
|
private requestHooks;
|
|
198
|
+
/**
|
|
199
|
+
* Converts a CoreResponse to the configured Response class.
|
|
200
|
+
*
|
|
201
|
+
* @param coreResponse - The CoreResponse to convert
|
|
202
|
+
* @returns Response instance (NextResponse, Response, etc.)
|
|
203
|
+
*/
|
|
204
|
+
private static toResponse;
|
|
154
205
|
/**
|
|
155
206
|
* Creates a new Application instance for handling a request.
|
|
156
207
|
* This constructor is private - use Application.create() to set up the application.
|
|
157
208
|
*
|
|
158
|
-
* @param request - The incoming
|
|
209
|
+
* @param request - The incoming CoreRequest (compatible with NextRequest)
|
|
159
210
|
* @param method - The HTTP method of the request
|
|
160
211
|
* @param payload - The route payload containing path params
|
|
161
212
|
*/
|
|
@@ -228,6 +279,16 @@ export declare class Application<Key extends string = "path"> {
|
|
|
228
279
|
* - Loads CORS configuration (may need injects)
|
|
229
280
|
*/
|
|
230
281
|
private static registerInjectFactories;
|
|
282
|
+
/**
|
|
283
|
+
* Initializes all registered injects by calling their onInit lifecycle hook.
|
|
284
|
+
* This ensures injects like Migration run at application startup.
|
|
285
|
+
*
|
|
286
|
+
* @remarks
|
|
287
|
+
* - Injects are initialized in registration order
|
|
288
|
+
* - Each inject's factory is called to create the instance
|
|
289
|
+
* - onInit is called if the inject implements it
|
|
290
|
+
*/
|
|
291
|
+
private static initializeInjects;
|
|
231
292
|
/**
|
|
232
293
|
* Registers process shutdown handlers for graceful cleanup.
|
|
233
294
|
* Handles SIGTERM and SIGINT signals to properly destroy injects.
|
|
@@ -284,24 +345,23 @@ export declare class Application<Key extends string = "path"> {
|
|
|
284
345
|
* Route-level filters run first, then global filters.
|
|
285
346
|
*
|
|
286
347
|
* @param error - The error to handle
|
|
287
|
-
* @returns A Response if a filter handles the error, null otherwise
|
|
348
|
+
* @returns A CoreResponse or Response if a filter handles the error, null otherwise
|
|
288
349
|
*
|
|
289
350
|
* @remarks
|
|
290
|
-
* Filters are tried in order until one returns a Response.
|
|
351
|
+
* Filters are tried in order until one returns a Response or CoreResponse.
|
|
291
352
|
* If no filter handles the error, null is returned and default error handling applies.
|
|
292
353
|
*/
|
|
293
354
|
private executeFilters;
|
|
294
355
|
/**
|
|
295
|
-
*
|
|
296
|
-
* Route-level interceptors
|
|
356
|
+
* Applies interceptors to transform the response.
|
|
357
|
+
* Route-level interceptors run first, then global interceptors.
|
|
297
358
|
*
|
|
298
|
-
* @
|
|
299
|
-
* @
|
|
300
|
-
* @returns The result of executing the interceptor chain
|
|
359
|
+
* @param response - The CoreResponse to transform
|
|
360
|
+
* @returns The transformed CoreResponse after passing through all interceptors
|
|
301
361
|
*
|
|
302
362
|
* @remarks
|
|
303
|
-
* Interceptors
|
|
304
|
-
*
|
|
363
|
+
* Interceptors are applied in order: route interceptors first, then global.
|
|
364
|
+
* Each interceptor receives the response and can transform headers or body.
|
|
305
365
|
*/
|
|
306
366
|
private executeInterceptors;
|
|
307
367
|
/**
|
|
@@ -320,10 +380,11 @@ export declare class Application<Key extends string = "path"> {
|
|
|
320
380
|
*/
|
|
321
381
|
private checkCors;
|
|
322
382
|
/**
|
|
323
|
-
* Applies CORS headers to the
|
|
383
|
+
* Applies CORS headers to the CoreResponse.
|
|
384
|
+
* Does not convert to Response - that's the responsibility of the response transformer.
|
|
324
385
|
*
|
|
325
|
-
* @param response - The
|
|
326
|
-
* @returns
|
|
386
|
+
* @param response - The CoreResponse to apply CORS headers to
|
|
387
|
+
* @returns The same CoreResponse with CORS headers applied
|
|
327
388
|
*/
|
|
328
389
|
private applyCorsHeaders;
|
|
329
390
|
/**
|
|
@@ -361,7 +422,7 @@ export declare class Application<Key extends string = "path"> {
|
|
|
361
422
|
*
|
|
362
423
|
* @template Key - The key used to extract path segments from route params
|
|
363
424
|
* @param method - The HTTP method this handler will process
|
|
364
|
-
* @returns An async function that handles
|
|
425
|
+
* @returns An async function that handles HTTP requests
|
|
365
426
|
*
|
|
366
427
|
* @remarks
|
|
367
428
|
* The returned handler:
|
|
@@ -398,13 +459,13 @@ export declare class Application<Key extends string = "path"> {
|
|
|
398
459
|
* ```
|
|
399
460
|
*/
|
|
400
461
|
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:
|
|
462
|
+
GET: (req: CoreRequest, payload: RoutePayload<"path">) => Promise<Response>;
|
|
463
|
+
POST: (req: CoreRequest, payload: RoutePayload<"path">) => Promise<Response>;
|
|
464
|
+
PUT: (req: CoreRequest, payload: RoutePayload<"path">) => Promise<Response>;
|
|
465
|
+
PATCH: (req: CoreRequest, payload: RoutePayload<"path">) => Promise<Response>;
|
|
466
|
+
DELETE: (req: CoreRequest, payload: RoutePayload<"path">) => Promise<Response>;
|
|
467
|
+
HEAD: (req: CoreRequest, payload: RoutePayload) => Promise<Response>;
|
|
468
|
+
OPTIONS: (req: CoreRequest) => Promise<Response | CoreResponse<unknown>>;
|
|
408
469
|
};
|
|
409
470
|
/**
|
|
410
471
|
* 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.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.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,...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,...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.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");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.route?.route.getReflect().getGuards()??new Set,s=[...u.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,...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,...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.route?.route.getReflect().getPipes()??new Set,s=[...u.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=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};
|
package/dist/common.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Generic callback function type.
|
|
3
|
+
* @template R - Return type
|
|
4
|
+
* @template A - Arguments tuple type
|
|
5
|
+
*/
|
|
6
|
+
export type Callback<R = void, A extends any[] = []> = (...args: A) => R;
|
|
2
7
|
/**
|
|
3
8
|
* @fileoverview Common types, interfaces, and utilities used throughout the @mxweb/core framework.
|
|
4
9
|
* This module provides foundational types for dependency injection, routing, and lifecycle management.
|
|
@@ -29,7 +34,46 @@ import { Callback } from "@mxweb/utils";
|
|
|
29
34
|
* }
|
|
30
35
|
* ```
|
|
31
36
|
*/
|
|
32
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Interface for injects that support context switching.
|
|
39
|
+
* Implement this interface to expose a safe, controlled API via `context.switch()`.
|
|
40
|
+
*
|
|
41
|
+
* @template T - The type of object returned by the switch() method
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* This interface is used to expose a **safe facade** instead of the raw inject instance.
|
|
45
|
+
* The switch() method should return an object that only exposes safe operations,
|
|
46
|
+
* hiding dangerous methods like close(), destroy(), or direct state manipulation.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* interface DbSwitchable {
|
|
51
|
+
* query<T>(sql: string): Promise<T>;
|
|
52
|
+
* getRepo<T>(entity: EntityClass<T>): Repository<T>;
|
|
53
|
+
* }
|
|
54
|
+
*
|
|
55
|
+
* class Connection implements ApplicationInject, Switchable<DbSwitchable> {
|
|
56
|
+
* private client: DatabaseClient;
|
|
57
|
+
*
|
|
58
|
+
* switch(): DbSwitchable {
|
|
59
|
+
* return {
|
|
60
|
+
* query: (sql) => this.client.query(sql),
|
|
61
|
+
* getRepo: (entity) => this.getRepo(entity),
|
|
62
|
+
* };
|
|
63
|
+
* }
|
|
64
|
+
* }
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export interface Switchable<T = unknown> {
|
|
68
|
+
/**
|
|
69
|
+
* Returns a safe, controlled interface for this inject.
|
|
70
|
+
* Called by `context.switch()` to get the public API.
|
|
71
|
+
*
|
|
72
|
+
* @returns The switched context or utility object
|
|
73
|
+
*/
|
|
74
|
+
switch(): T;
|
|
75
|
+
}
|
|
76
|
+
export interface ApplicationInject extends Partial<Switchable> {
|
|
33
77
|
/**
|
|
34
78
|
* Called when the inject is initialized (on first request).
|
|
35
79
|
* Use this to establish connections, load resources, or perform async setup.
|
|
@@ -62,7 +106,7 @@ export interface ApplicationInject {
|
|
|
62
106
|
* }
|
|
63
107
|
* ```
|
|
64
108
|
*/
|
|
65
|
-
export interface FeatureInject {
|
|
109
|
+
export interface FeatureInject extends Partial<Switchable> {
|
|
66
110
|
/**
|
|
67
111
|
* Called when the feature is loaded.
|
|
68
112
|
* Use this for feature-specific initialization.
|
|
@@ -192,6 +236,9 @@ export type RouteNextHandler = () => void;
|
|
|
192
236
|
* Function type for call handlers used in interceptors.
|
|
193
237
|
* Returns a promise of the handler result.
|
|
194
238
|
*
|
|
239
|
+
* @deprecated Since v1.1.0, interceptors no longer wrap handlers.
|
|
240
|
+
* Use `ResponseInterceptorHandler` from `response.ts` instead.
|
|
241
|
+
*
|
|
195
242
|
* @template T - The type of the resolved value
|
|
196
243
|
*/
|
|
197
244
|
export type CallHandler<T = unknown> = () => Promise<T>;
|