@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/dist/common.d.ts CHANGED
@@ -1,9 +1,169 @@
1
- import { Callback } from "@mxweb/utils";
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.
5
10
  * @module common
6
11
  */
12
+ /**
13
+ * Interface representing the feature context accessible from ExecuteContext.
14
+ * This abstraction allows execute.ts to work with features without importing Feature class directly,
15
+ * avoiding circular dependencies.
16
+ *
17
+ * @remarks
18
+ * Feature class implements this interface.
19
+ * Used in RequestContext and ExecuteContext methods.
20
+ */
21
+ export interface FeatureContext {
22
+ /**
23
+ * Checks if an inject exists in this feature.
24
+ * @param name - The inject name to check
25
+ * @returns true if the inject exists
26
+ */
27
+ hasInject(name: string): boolean;
28
+ /**
29
+ * Retrieves an inject by name with lazy initialization.
30
+ * @template T - Expected return type
31
+ * @param name - The name of the inject to retrieve
32
+ * @returns Promise resolving to the inject or undefined
33
+ */
34
+ getInject<T = unknown>(name: string): Promise<T | undefined>;
35
+ /**
36
+ * Retrieves an inject synchronously (only if already initialized).
37
+ * @template T - Expected return type
38
+ * @param name - The name of the inject to retrieve
39
+ * @returns The inject or undefined
40
+ */
41
+ getInjectSync<T = unknown>(name: string): T | undefined;
42
+ }
43
+ /**
44
+ * Interface for guard classes that determine if a request should proceed.
45
+ * Guards are used for authorization, authentication, and access control.
46
+ *
47
+ * @template Context - The execution context type (defaults to unknown for flexibility)
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * class AuthGuard implements CanActivate {
52
+ * canActivate(context: ExecuteContext): boolean {
53
+ * const token = context.switchHttp().headers()["authorization"];
54
+ * return !!token && isValidToken(token);
55
+ * }
56
+ * }
57
+ * ```
58
+ */
59
+ export interface CanActivate<Context = unknown> {
60
+ /**
61
+ * Determines if the request should be allowed to proceed.
62
+ *
63
+ * @param context - The execution context for the current request
64
+ * @returns true to allow, false to deny (results in 403 Forbidden)
65
+ */
66
+ canActivate(context: Context): boolean | Promise<boolean>;
67
+ }
68
+ /**
69
+ * Constructor type for guard classes.
70
+ *
71
+ * @template Context - The execution context type
72
+ */
73
+ export type Guard<Context = unknown> = new (...args: unknown[]) => CanActivate<Context>;
74
+ /**
75
+ * Interface for exception filter classes that handle errors.
76
+ * Filters can transform exceptions into custom responses.
77
+ *
78
+ * @template Response - The type of response the filter returns
79
+ * @template Context - The execution context type (defaults to unknown for flexibility)
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * class HttpExceptionFilter implements ExceptionFilter<Response> {
84
+ * catch(exception: unknown, context: ExecuteContext): Response {
85
+ * if (exception instanceof HttpError) {
86
+ * return new Response(JSON.stringify({ error: exception.message }), {
87
+ * status: exception.statusCode,
88
+ * });
89
+ * }
90
+ * throw exception; // Re-throw if not handled
91
+ * }
92
+ * }
93
+ * ```
94
+ */
95
+ export interface ExceptionFilter<Response = unknown, Context = unknown> {
96
+ /**
97
+ * Handles an exception and optionally returns a response.
98
+ *
99
+ * @param exception - The thrown exception
100
+ * @param context - The execution context for the current request
101
+ * @returns A response to send, or re-throw to pass to next filter
102
+ */
103
+ catch(exception: unknown, context: Context): Response | Promise<Response>;
104
+ }
105
+ /**
106
+ * Constructor type for exception filter classes.
107
+ *
108
+ * @template Response - The type of response the filter returns
109
+ * @template Context - The execution context type
110
+ */
111
+ export type Filter<Response = unknown, Context = unknown> = new (...args: unknown[]) => ExceptionFilter<Response, Context>;
112
+ /**
113
+ * Interface for class-based response interceptors.
114
+ * Provides more flexibility than function interceptors with access to instance state.
115
+ *
116
+ * @template T - The type of the response data
117
+ * @template Response - The response type (defaults to unknown for flexibility)
118
+ *
119
+ * @example
120
+ * ```ts
121
+ * class LoggingInterceptor implements ResponseInterceptorHandler {
122
+ * transform(response: CoreResponse): CoreResponse {
123
+ * console.log(`[${response.status}] ${response.body.message}`);
124
+ * return response;
125
+ * }
126
+ * }
127
+ *
128
+ * class CacheHeaderInterceptor implements ResponseInterceptorHandler {
129
+ * transform(response: CoreResponse): CoreResponse {
130
+ * if (response.status >= 200 && response.status < 300) {
131
+ * response.headers.set("Cache-Control", "max-age=3600");
132
+ * }
133
+ * return response;
134
+ * }
135
+ * }
136
+ * ```
137
+ */
138
+ export interface ResponseInterceptorHandler<Response = unknown> {
139
+ /**
140
+ * Transforms the response.
141
+ * Called after the handler returns, allowing post-processing of the response.
142
+ *
143
+ * @param response - The response to transform
144
+ * @returns The transformed response (can be the same instance or a new one)
145
+ */
146
+ transform(response: Response): Response | Promise<Response>;
147
+ }
148
+ /**
149
+ * Constructor type for response interceptor classes.
150
+ * Interceptors transform the response after the handler returns.
151
+ *
152
+ * @template Response - The response type
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * import { ResponseInterceptorHandler, CoreResponse } from "@mxweb/core";
157
+ *
158
+ * class LoggingInterceptor implements ResponseInterceptorHandler {
159
+ * transform(response: CoreResponse): CoreResponse {
160
+ * console.log(`Response: ${response.status} ${response.body.message}`);
161
+ * return response;
162
+ * }
163
+ * }
164
+ * ```
165
+ */
166
+ export type Interceptor<Response = unknown> = new (...args: unknown[]) => ResponseInterceptorHandler<Response>;
7
167
  /**
8
168
  * Interface for Application-level dependency injects.
9
169
  * All global injects must implement this interface to ensure consistent lifecycle management.
@@ -29,7 +189,46 @@ import { Callback } from "@mxweb/utils";
29
189
  * }
30
190
  * ```
31
191
  */
32
- export interface ApplicationInject {
192
+ /**
193
+ * Interface for injects that support context switching.
194
+ * Implement this interface to expose a safe, controlled API via `context.switch()`.
195
+ *
196
+ * @template T - The type of object returned by the switch() method
197
+ *
198
+ * @remarks
199
+ * This interface is used to expose a **safe facade** instead of the raw inject instance.
200
+ * The switch() method should return an object that only exposes safe operations,
201
+ * hiding dangerous methods like close(), destroy(), or direct state manipulation.
202
+ *
203
+ * @example
204
+ * ```ts
205
+ * interface DbSwitchable {
206
+ * query<T>(sql: string): Promise<T>;
207
+ * getRepo<T>(entity: EntityClass<T>): Repository<T>;
208
+ * }
209
+ *
210
+ * class Connection implements ApplicationInject, Switchable<DbSwitchable> {
211
+ * private client: DatabaseClient;
212
+ *
213
+ * switch(): DbSwitchable {
214
+ * return {
215
+ * query: (sql) => this.client.query(sql),
216
+ * getRepo: (entity) => this.getRepo(entity),
217
+ * };
218
+ * }
219
+ * }
220
+ * ```
221
+ */
222
+ export interface Switchable<T = unknown> {
223
+ /**
224
+ * Returns a safe, controlled interface for this inject.
225
+ * Called by `context.switch()` to get the public API.
226
+ *
227
+ * @returns The switched context or utility object
228
+ */
229
+ switch(): T;
230
+ }
231
+ export interface ApplicationInject extends Partial<Switchable> {
33
232
  /**
34
233
  * Called when the inject is initialized (on first request).
35
234
  * Use this to establish connections, load resources, or perform async setup.
@@ -62,7 +261,7 @@ export interface ApplicationInject {
62
261
  * }
63
262
  * ```
64
263
  */
65
- export interface FeatureInject {
264
+ export interface FeatureInject extends Partial<Switchable> {
66
265
  /**
67
266
  * Called when the feature is loaded.
68
267
  * Use this for feature-specific initialization.
@@ -192,6 +391,9 @@ export type RouteNextHandler = () => void;
192
391
  * Function type for call handlers used in interceptors.
193
392
  * Returns a promise of the handler result.
194
393
  *
394
+ * @deprecated Since v1.1.0, interceptors no longer wrap handlers.
395
+ * Use `ResponseInterceptorHandler` from `response.ts` instead.
396
+ *
195
397
  * @template T - The type of the resolved value
196
398
  */
197
399
  export type CallHandler<T = unknown> = () => Promise<T>;
@@ -228,6 +430,56 @@ export interface PipeTransform<Input = unknown, Output = unknown> {
228
430
  * @template Output - The type of output value the pipe produces
229
431
  */
230
432
  export type Pipe<Input = unknown, Output = unknown> = new (...args: unknown[]) => PipeTransform<Input, Output>;
433
+ /**
434
+ * Generic constructor type for class instantiation.
435
+ */
436
+ export type ClassConstructor<T = unknown> = new (...args: any[]) => T;
437
+ /**
438
+ * Shared options for guards, filters, interceptors, and pipes.
439
+ * Used by both Application and Feature configurations.
440
+ *
441
+ * @remarks
442
+ * This interface provides a common base for decorator options across
443
+ * different levels of the application hierarchy (Application, Feature, Route).
444
+ *
445
+ * Execution order:
446
+ * - Guards: Application → Feature → Route
447
+ * - Filters: Route → Feature → Application (reverse for catch)
448
+ * - Interceptors: Route → Feature → Application (for transform)
449
+ * - Pipes: Application → Feature → Route
450
+ *
451
+ * @example
452
+ * ```ts
453
+ * const options: DecoratorOptions = {
454
+ * guards: [AuthGuard, RolesGuard],
455
+ * filters: [HttpExceptionFilter],
456
+ * interceptors: [LoggingInterceptor],
457
+ * pipes: [ValidationPipe],
458
+ * };
459
+ * ```
460
+ */
461
+ export interface DecoratorOptions {
462
+ /**
463
+ * Guards to apply at this level.
464
+ * Guards determine if a request should be processed.
465
+ */
466
+ guards?: Guard[];
467
+ /**
468
+ * Exception filters to apply at this level.
469
+ * Filters handle exceptions thrown during request processing.
470
+ */
471
+ filters?: Filter[];
472
+ /**
473
+ * Interceptors to apply at this level.
474
+ * Interceptors can transform the response.
475
+ */
476
+ interceptors?: Interceptor[];
477
+ /**
478
+ * Pipes to apply at this level.
479
+ * Pipes transform input data before it reaches the handler.
480
+ */
481
+ pipes?: Pipe[];
482
+ }
231
483
  /**
232
484
  * Constructor type for inject classes.
233
485
  * Used when registering injects that need to be instantiated.
package/dist/config.d.ts CHANGED
@@ -272,4 +272,22 @@ export declare class Config implements ApplicationInject {
272
272
  * ```
273
273
  */
274
274
  reset(): void;
275
+ /**
276
+ * Returns the Config instance itself for context switching.
277
+ * Allows accessing Config via `context.switch("config")`.
278
+ *
279
+ * @template T - The expected return type (defaults to Config)
280
+ * @returns The Config instance
281
+ *
282
+ * @example
283
+ * ```ts
284
+ * // In a controller or service
285
+ * const config = await this.context.switch<Config>("config");
286
+ * const dbHost = config.get<string>("db.host");
287
+ *
288
+ * // Or access config values directly
289
+ * const port = config.get<number>("port", 3000);
290
+ * ```
291
+ */
292
+ switch<T = Config>(): T;
275
293
  }
package/dist/config.js CHANGED
@@ -1 +1 @@
1
- "use strict";class t{static forRoot(r="config"){return s=>{s.set(r,()=>(t.instance||(t.instance=new t),t.instance))}}async onInit(){await t.buildAsync()}static register(r,s){return t.providers.set(r,s),t}static registerAsync(r,s){return t.asyncProviders.set(r,s),t}static build(){if(!t.built){for(const[r,s]of t.providers)t.store.has(r)||t.store.set(r,s());t.built=!0}}static async buildAsync(){if(t.build(),!t.asyncBuilt){for(const[r,s]of t.asyncProviders)t.store.has(r)||t.store.set(r,await s());t.asyncBuilt=!0}}get(r,s){return t.build(),t.store.get(r)??s}async getAsync(r,s){return await t.buildAsync(),t.store.get(r)??s}getOrThrow(r){if(t.build(),!t.store.has(r))throw new Error(`[Config] Config key "${r}" not found`);return t.store.get(r)}async getAsyncOrThrow(r){if(await t.buildAsync(),!t.store.has(r))throw new Error(`[Config] Config key "${r}" not found`);return t.store.get(r)}has(r){return t.store.has(r)||t.providers.has(r)||t.asyncProviders.has(r)}getAll(){return new Map(t.store)}reset(){t.instance=null,t.store.clear(),t.providers.clear(),t.asyncProviders.clear(),t.built=!1,t.asyncBuilt=!1}}t.instance=null,t.store=new Map,t.providers=new Map,t.asyncProviders=new Map,t.built=!1,t.asyncBuilt=!1,exports.Config=t;
1
+ "use strict";class t{static forRoot(r="config"){return s=>{s.set(r,()=>(t.instance||(t.instance=new t),t.instance))}}async onInit(){await t.buildAsync()}static register(r,s){return t.providers.set(r,s),t}static registerAsync(r,s){return t.asyncProviders.set(r,s),t}static build(){if(!t.built){for(const[r,s]of t.providers)t.store.has(r)||t.store.set(r,s());t.built=!0}}static async buildAsync(){if(t.build(),!t.asyncBuilt){for(const[r,s]of t.asyncProviders)t.store.has(r)||t.store.set(r,await s());t.asyncBuilt=!0}}get(r,s){return t.build(),t.store.get(r)??s}async getAsync(r,s){return await t.buildAsync(),t.store.get(r)??s}getOrThrow(r){if(t.build(),!t.store.has(r))throw new Error(`[Config] Config key "${r}" not found`);return t.store.get(r)}async getAsyncOrThrow(r){if(await t.buildAsync(),!t.store.has(r))throw new Error(`[Config] Config key "${r}" not found`);return t.store.get(r)}has(r){return t.store.has(r)||t.providers.has(r)||t.asyncProviders.has(r)}getAll(){return new Map(t.store)}reset(){t.instance=null,t.store.clear(),t.providers.clear(),t.asyncProviders.clear(),t.built=!1,t.asyncBuilt=!1}switch(){return this}}t.instance=null,t.store=new Map,t.providers=new Map,t.asyncProviders=new Map,t.built=!1,t.asyncBuilt=!1,exports.Config=t;
package/dist/config.mjs CHANGED
@@ -1 +1 @@
1
- class r{static forRoot(t="config"){return s=>{s.set(t,()=>(r.instance||(r.instance=new r),r.instance))}}async onInit(){await r.buildAsync()}static register(t,s){return r.providers.set(t,s),r}static registerAsync(t,s){return r.asyncProviders.set(t,s),r}static build(){if(!r.built){for(const[t,s]of r.providers)r.store.has(t)||r.store.set(t,s());r.built=!0}}static async buildAsync(){if(r.build(),!r.asyncBuilt){for(const[t,s]of r.asyncProviders)r.store.has(t)||r.store.set(t,await s());r.asyncBuilt=!0}}get(t,s){return r.build(),r.store.get(t)??s}async getAsync(t,s){return await r.buildAsync(),r.store.get(t)??s}getOrThrow(t){if(r.build(),!r.store.has(t))throw new Error(`[Config] Config key "${t}" not found`);return r.store.get(t)}async getAsyncOrThrow(t){if(await r.buildAsync(),!r.store.has(t))throw new Error(`[Config] Config key "${t}" not found`);return r.store.get(t)}has(t){return r.store.has(t)||r.providers.has(t)||r.asyncProviders.has(t)}getAll(){return new Map(r.store)}reset(){r.instance=null,r.store.clear(),r.providers.clear(),r.asyncProviders.clear(),r.built=!1,r.asyncBuilt=!1}}r.instance=null,r.store=new Map,r.providers=new Map,r.asyncProviders=new Map,r.built=!1,r.asyncBuilt=!1;export{r as Config};
1
+ class t{static forRoot(r="config"){return s=>{s.set(r,()=>(t.instance||(t.instance=new t),t.instance))}}async onInit(){await t.buildAsync()}static register(r,s){return t.providers.set(r,s),t}static registerAsync(r,s){return t.asyncProviders.set(r,s),t}static build(){if(!t.built){for(const[r,s]of t.providers)t.store.has(r)||t.store.set(r,s());t.built=!0}}static async buildAsync(){if(t.build(),!t.asyncBuilt){for(const[r,s]of t.asyncProviders)t.store.has(r)||t.store.set(r,await s());t.asyncBuilt=!0}}get(r,s){return t.build(),t.store.get(r)??s}async getAsync(r,s){return await t.buildAsync(),t.store.get(r)??s}getOrThrow(r){if(t.build(),!t.store.has(r))throw new Error(`[Config] Config key "${r}" not found`);return t.store.get(r)}async getAsyncOrThrow(r){if(await t.buildAsync(),!t.store.has(r))throw new Error(`[Config] Config key "${r}" not found`);return t.store.get(r)}has(r){return t.store.has(r)||t.providers.has(r)||t.asyncProviders.has(r)}getAll(){return new Map(t.store)}reset(){t.instance=null,t.store.clear(),t.providers.clear(),t.asyncProviders.clear(),t.built=!1,t.asyncBuilt=!1}switch(){return this}}t.instance=null,t.store=new Map,t.providers=new Map,t.asyncProviders=new Map,t.built=!1,t.asyncBuilt=!1;export{t as Config};
@@ -8,8 +8,8 @@
8
8
  *
9
9
  * @module decorator
10
10
  */
11
- import { ExecuteContext, Filter, Guard, Interceptor } from "./execute";
12
- import { FeatureInject, Pipe } from "./common";
11
+ import { CoreRequest, ExecuteContext } from "./execute";
12
+ import { FeatureInject, Filter, Guard, Interceptor, Pipe } from "./common";
13
13
  /**
14
14
  * Result object from decorator functions.
15
15
  * Contains sets of guards, filters, interceptors, pipes, and metadata
@@ -234,18 +234,25 @@ export declare function QueryParam(key: string): string | undefined;
234
234
  /**
235
235
  * Extracts the raw Request object.
236
236
  *
237
- * @returns The underlying Request object (NextRequest)
237
+ * @template T - The specific request type (e.g., NextRequest, Express.Request)
238
+ * @returns The underlying CoreRequest object, cast to the specified type
238
239
  *
239
240
  * @example
240
241
  * ```ts
242
+ * // Default (CoreRequest)
241
243
  * findAll() {
242
244
  * const request = Request();
243
245
  * const url = request.url;
244
- * const method = request.method;
246
+ * }
247
+ *
248
+ * // With specific type (Next.js)
249
+ * findAll() {
250
+ * const request = Request<NextRequest>();
251
+ * const nextUrl = request.nextUrl; // ✅ Type-safe
245
252
  * }
246
253
  * ```
247
254
  */
248
- export declare function Request(): globalThis.Request;
255
+ export declare function Request<T extends CoreRequest = CoreRequest>(): T;
249
256
  /**
250
257
  * Extracts and parses FormData from the request body.
251
258
  *