@velajs/vela 0.4.0 → 0.4.2
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/health/health.http.d.ts +14 -0
- package/dist/health/health.http.js +75 -0
- package/dist/health/health.indicator.d.ts +9 -0
- package/dist/health/health.indicator.js +32 -0
- package/dist/health/health.module.d.ts +2 -0
- package/dist/health/health.module.js +26 -0
- package/dist/health/health.service.d.ts +7 -0
- package/dist/health/health.service.js +65 -0
- package/dist/health/health.types.d.ts +15 -0
- package/dist/health/health.types.js +1 -0
- package/dist/health/index.d.ts +6 -0
- package/dist/health/index.js +4 -0
- package/dist/http/route.manager.js +5 -3
- package/dist/index.d.ts +5 -0
- package/dist/index.js +8 -0
- package/dist/serialization/index.d.ts +2 -0
- package/dist/serialization/index.js +2 -0
- package/dist/serialization/serialize.decorator.d.ts +6 -0
- package/dist/serialization/serialize.decorator.js +7 -0
- package/dist/serialization/serializer.interceptor.d.ts +4 -0
- package/dist/serialization/serializer.interceptor.js +16 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +2 -0
- package/dist/testing/testing.builder.d.ts +29 -0
- package/dist/testing/testing.builder.js +134 -0
- package/dist/testing/testing.module.d.ts +9 -0
- package/dist/testing/testing.module.js +15 -0
- package/dist/validation/create-zod-dto.d.ts +8 -0
- package/dist/validation/create-zod-dto.js +6 -0
- package/dist/validation/index.d.ts +2 -0
- package/dist/validation/index.js +2 -0
- package/dist/validation/validation.pipe.d.ts +4 -0
- package/dist/validation/validation.pipe.js +19 -0
- package/package.json +3 -2
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { HealthIndicatorService } from './health.indicator';
|
|
2
|
+
import type { HealthIndicatorResult, ResponseCheckCallback } from './health.types';
|
|
3
|
+
export interface HttpPingOptions {
|
|
4
|
+
method?: string;
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
timeout?: number;
|
|
7
|
+
expectedStatus?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class HttpHealthIndicator {
|
|
10
|
+
private indicator;
|
|
11
|
+
constructor(indicator: HealthIndicatorService);
|
|
12
|
+
pingCheck(key: string, url: string, options?: HttpPingOptions): Promise<HealthIndicatorResult>;
|
|
13
|
+
responseCheck(key: string, url: string, callback: ResponseCheckCallback, options?: HttpPingOptions): Promise<HealthIndicatorResult>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
}
|
|
7
|
+
function _ts_metadata(k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
}
|
|
10
|
+
import { Injectable } from "../container/index.js";
|
|
11
|
+
import { HealthIndicatorService } from "./health.indicator.js";
|
|
12
|
+
export class HttpHealthIndicator {
|
|
13
|
+
indicator;
|
|
14
|
+
constructor(indicator){
|
|
15
|
+
this.indicator = indicator;
|
|
16
|
+
}
|
|
17
|
+
async pingCheck(key, url, options) {
|
|
18
|
+
const { method = 'GET', headers, timeout = 5000, expectedStatus } = options ?? {};
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch(url, {
|
|
21
|
+
method,
|
|
22
|
+
headers,
|
|
23
|
+
signal: AbortSignal.timeout(timeout)
|
|
24
|
+
});
|
|
25
|
+
const statusCode = response.status;
|
|
26
|
+
const isHealthy = expectedStatus !== undefined ? statusCode === expectedStatus : statusCode >= 200 && statusCode < 300;
|
|
27
|
+
if (isHealthy) {
|
|
28
|
+
return this.indicator.check(key).up({
|
|
29
|
+
statusCode
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return this.indicator.check(key).down({
|
|
33
|
+
statusCode,
|
|
34
|
+
message: response.statusText
|
|
35
|
+
});
|
|
36
|
+
} catch (error) {
|
|
37
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
38
|
+
return this.indicator.check(key).down({
|
|
39
|
+
message
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async responseCheck(key, url, callback, options) {
|
|
44
|
+
const { method = 'GET', headers, timeout = 5000 } = options ?? {};
|
|
45
|
+
try {
|
|
46
|
+
const response = await fetch(url, {
|
|
47
|
+
method,
|
|
48
|
+
headers,
|
|
49
|
+
signal: AbortSignal.timeout(timeout)
|
|
50
|
+
});
|
|
51
|
+
const statusCode = response.status;
|
|
52
|
+
const isHealthy = await callback(response);
|
|
53
|
+
if (isHealthy) {
|
|
54
|
+
return this.indicator.check(key).up({
|
|
55
|
+
statusCode
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return this.indicator.check(key).down({
|
|
59
|
+
statusCode
|
|
60
|
+
});
|
|
61
|
+
} catch (error) {
|
|
62
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
63
|
+
return this.indicator.check(key).down({
|
|
64
|
+
message
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
HttpHealthIndicator = _ts_decorate([
|
|
70
|
+
Injectable(),
|
|
71
|
+
_ts_metadata("design:type", Function),
|
|
72
|
+
_ts_metadata("design:paramtypes", [
|
|
73
|
+
typeof HealthIndicatorService === "undefined" ? Object : HealthIndicatorService
|
|
74
|
+
])
|
|
75
|
+
], HttpHealthIndicator);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { HealthIndicatorResult } from './health.types';
|
|
2
|
+
interface HealthIndicatorBuilder {
|
|
3
|
+
up(data?: Record<string, unknown>): HealthIndicatorResult;
|
|
4
|
+
down(data?: Record<string, unknown>): HealthIndicatorResult;
|
|
5
|
+
}
|
|
6
|
+
export declare class HealthIndicatorService {
|
|
7
|
+
check(key: string): HealthIndicatorBuilder;
|
|
8
|
+
}
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
}
|
|
7
|
+
import { Injectable } from "../container/index.js";
|
|
8
|
+
export class HealthIndicatorService {
|
|
9
|
+
check(key) {
|
|
10
|
+
return {
|
|
11
|
+
up (data) {
|
|
12
|
+
return {
|
|
13
|
+
[key]: {
|
|
14
|
+
status: 'up',
|
|
15
|
+
...data
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
},
|
|
19
|
+
down (data) {
|
|
20
|
+
return {
|
|
21
|
+
[key]: {
|
|
22
|
+
status: 'down',
|
|
23
|
+
...data
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
HealthIndicatorService = _ts_decorate([
|
|
31
|
+
Injectable()
|
|
32
|
+
], HealthIndicatorService);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
}
|
|
7
|
+
import { Module } from "../module/index.js";
|
|
8
|
+
import { HealthCheckService } from "./health.service.js";
|
|
9
|
+
import { HealthIndicatorService } from "./health.indicator.js";
|
|
10
|
+
import { HttpHealthIndicator } from "./health.http.js";
|
|
11
|
+
export class HealthModule {
|
|
12
|
+
}
|
|
13
|
+
HealthModule = _ts_decorate([
|
|
14
|
+
Module({
|
|
15
|
+
providers: [
|
|
16
|
+
HealthCheckService,
|
|
17
|
+
HealthIndicatorService,
|
|
18
|
+
HttpHealthIndicator
|
|
19
|
+
],
|
|
20
|
+
exports: [
|
|
21
|
+
HealthCheckService,
|
|
22
|
+
HealthIndicatorService,
|
|
23
|
+
HttpHealthIndicator
|
|
24
|
+
]
|
|
25
|
+
})
|
|
26
|
+
], HealthModule);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BeforeApplicationShutdown } from '../lifecycle/index';
|
|
2
|
+
import type { HealthCheckResult, HealthIndicatorFunction } from './health.types';
|
|
3
|
+
export declare class HealthCheckService implements BeforeApplicationShutdown {
|
|
4
|
+
private isShuttingDown;
|
|
5
|
+
beforeApplicationShutdown(): void;
|
|
6
|
+
check(indicators: HealthIndicatorFunction[]): Promise<HealthCheckResult>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
}
|
|
7
|
+
import { Injectable } from "../container/index.js";
|
|
8
|
+
import { ServiceUnavailableException } from "../errors/http-exception.js";
|
|
9
|
+
export class HealthCheckService {
|
|
10
|
+
isShuttingDown = false;
|
|
11
|
+
beforeApplicationShutdown() {
|
|
12
|
+
this.isShuttingDown = true;
|
|
13
|
+
}
|
|
14
|
+
async check(indicators) {
|
|
15
|
+
if (this.isShuttingDown) {
|
|
16
|
+
const result = {
|
|
17
|
+
status: 'shutting_down',
|
|
18
|
+
info: {},
|
|
19
|
+
error: {},
|
|
20
|
+
details: {}
|
|
21
|
+
};
|
|
22
|
+
throw new ServiceUnavailableException('Service Unavailable', result);
|
|
23
|
+
}
|
|
24
|
+
const results = await Promise.allSettled(indicators.map((fn)=>fn()));
|
|
25
|
+
const info = {};
|
|
26
|
+
const error = {};
|
|
27
|
+
const details = {};
|
|
28
|
+
for (const result of results){
|
|
29
|
+
if (result.status === 'fulfilled') {
|
|
30
|
+
const indicatorResult = result.value;
|
|
31
|
+
for (const [key, value] of Object.entries(indicatorResult)){
|
|
32
|
+
details[key] = value;
|
|
33
|
+
if (value.status === 'up') {
|
|
34
|
+
info[key] = value;
|
|
35
|
+
} else {
|
|
36
|
+
error[key] = value;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
const message = result.reason instanceof Error ? result.reason.message : 'Health check failed';
|
|
41
|
+
const key = 'unknown';
|
|
42
|
+
const value = {
|
|
43
|
+
status: 'down',
|
|
44
|
+
message
|
|
45
|
+
};
|
|
46
|
+
details[key] = value;
|
|
47
|
+
error[key] = value;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const status = Object.keys(error).length > 0 ? 'error' : 'ok';
|
|
51
|
+
const checkResult = {
|
|
52
|
+
status,
|
|
53
|
+
info,
|
|
54
|
+
error,
|
|
55
|
+
details
|
|
56
|
+
};
|
|
57
|
+
if (status === 'error') {
|
|
58
|
+
throw new ServiceUnavailableException('Service Unavailable', checkResult);
|
|
59
|
+
}
|
|
60
|
+
return checkResult;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
HealthCheckService = _ts_decorate([
|
|
64
|
+
Injectable()
|
|
65
|
+
], HealthCheckService);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type HealthCheckStatus = 'ok' | 'error' | 'shutting_down';
|
|
2
|
+
export interface HealthIndicatorResult {
|
|
3
|
+
[key: string]: {
|
|
4
|
+
status: 'up' | 'down';
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export interface HealthCheckResult {
|
|
9
|
+
status: HealthCheckStatus;
|
|
10
|
+
info: HealthIndicatorResult;
|
|
11
|
+
error: HealthIndicatorResult;
|
|
12
|
+
details: HealthIndicatorResult;
|
|
13
|
+
}
|
|
14
|
+
export type HealthIndicatorFunction = () => Promise<HealthIndicatorResult>;
|
|
15
|
+
export type ResponseCheckCallback = (response: Response) => boolean | Promise<boolean>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { HealthModule } from './health.module';
|
|
2
|
+
export { HealthCheckService } from './health.service';
|
|
3
|
+
export { HealthIndicatorService } from './health.indicator';
|
|
4
|
+
export { HttpHealthIndicator } from './health.http';
|
|
5
|
+
export type { HealthCheckResult, HealthCheckStatus, HealthIndicatorResult, HealthIndicatorFunction, ResponseCheckCallback, } from './health.types';
|
|
6
|
+
export type { HttpPingOptions } from './health.http';
|
|
@@ -148,6 +148,8 @@ export class RouteManager {
|
|
|
148
148
|
}
|
|
149
149
|
createHandler(instance, route, controller, allParamMetadata) {
|
|
150
150
|
const paramMetadata = (allParamMetadata.get(route.handlerName) || []).sort((a, b)=>a.index - b.index);
|
|
151
|
+
// Read param types once at build time for metatype population
|
|
152
|
+
const paramTypes = Reflect.getMetadata('design:paramtypes', instance.constructor.prototype, route.handlerName);
|
|
151
153
|
// Pre-resolve controller + method level components at build time
|
|
152
154
|
const methodGuards = ComponentManager.getComponents('guard', controller, route.handlerName);
|
|
153
155
|
const methodPipes = ComponentManager.getComponents('pipe', controller, route.handlerName);
|
|
@@ -188,7 +190,7 @@ export class RouteManager {
|
|
|
188
190
|
const executionContext = this.createExecutionContext(c, controller, route);
|
|
189
191
|
try {
|
|
190
192
|
// 1. Extract args + run pipes
|
|
191
|
-
const args = await this.extractArguments(c, paramMetadata, pipes);
|
|
193
|
+
const args = await this.extractArguments(c, paramMetadata, pipes, paramTypes);
|
|
192
194
|
// 2. Guards (fail-fast)
|
|
193
195
|
for (const guard of guards){
|
|
194
196
|
const canActivate = await guard.canActivate(executionContext);
|
|
@@ -245,7 +247,7 @@ export class RouteManager {
|
|
|
245
247
|
}
|
|
246
248
|
};
|
|
247
249
|
}
|
|
248
|
-
async extractArguments(c, paramMetadata, pipes) {
|
|
250
|
+
async extractArguments(c, paramMetadata, pipes, paramTypes) {
|
|
249
251
|
if (paramMetadata.length === 0) {
|
|
250
252
|
return [
|
|
251
253
|
c
|
|
@@ -258,7 +260,7 @@ export class RouteManager {
|
|
|
258
260
|
const metadata = {
|
|
259
261
|
type: param.type,
|
|
260
262
|
data: param.name,
|
|
261
|
-
metatype:
|
|
263
|
+
metatype: paramTypes?.[param.index]
|
|
262
264
|
};
|
|
263
265
|
// Run shared pipes (global + controller + method)
|
|
264
266
|
for (const pipe of pipes){
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export { EventEmitterModule, EventEmitter, EventEmitterSubscriber, OnEvent, ON_E
|
|
|
18
18
|
export type { EventHandler, OnEventMetadata } from './event-emitter/index';
|
|
19
19
|
export { ScheduleModule, ScheduleRegistry, ScheduleExecutor, Cron, Interval, SCHEDULE_MODULE_OPTIONS, CRON_METADATA, INTERVAL_METADATA, } from './schedule/index';
|
|
20
20
|
export type { RegisteredCronJob, RegisteredIntervalJob, CronMetadata, IntervalMetadata, ScheduleModuleOptions, } from './schedule/index';
|
|
21
|
+
export { HealthModule, HealthCheckService, HealthIndicatorService, HttpHealthIndicator, } from './health/index';
|
|
22
|
+
export type { HealthCheckResult, HealthCheckStatus, HealthIndicatorResult, HealthIndicatorFunction, ResponseCheckCallback, HttpPingOptions, } from './health/index';
|
|
21
23
|
export { ThrottlerModule, ThrottlerGuard, ThrottlerStorage, Throttle, SkipThrottle, THROTTLER_OPTIONS, THROTTLER_STORAGE, THROTTLE_METADATA, SKIP_THROTTLE_METADATA, } from './throttler/index';
|
|
22
24
|
export type { ThrottlerModuleOptions, ThrottleConfig, ThrottlerStore, ThrottlerStorageRecord, RateLimitInfo, } from './throttler/index';
|
|
23
25
|
export { Module } from './module/index';
|
|
@@ -32,3 +34,6 @@ export { MetadataRegistry } from './registry/index';
|
|
|
32
34
|
export { RouteManager } from './http/index';
|
|
33
35
|
export { ModuleLoader } from './module/index';
|
|
34
36
|
export { ComponentManager } from './pipeline/index';
|
|
37
|
+
export { createZodDto, ValidationPipe } from './validation/index';
|
|
38
|
+
export { Serialize, SerializerInterceptor, SERIALIZE_METADATA } from './serialization/index';
|
|
39
|
+
export { Test, TestingModule, TestingModuleBuilder } from './testing/index';
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,8 @@ export { CacheModule, CacheService, CacheInterceptor, MemoryCacheStore, CacheKey
|
|
|
21
21
|
export { EventEmitterModule, EventEmitter, EventEmitterSubscriber, OnEvent, ON_EVENT_METADATA } from "./event-emitter/index.js";
|
|
22
22
|
// Schedule
|
|
23
23
|
export { ScheduleModule, ScheduleRegistry, ScheduleExecutor, Cron, Interval, SCHEDULE_MODULE_OPTIONS, CRON_METADATA, INTERVAL_METADATA } from "./schedule/index.js";
|
|
24
|
+
// Health
|
|
25
|
+
export { HealthModule, HealthCheckService, HealthIndicatorService, HttpHealthIndicator } from "./health/index.js";
|
|
24
26
|
// Throttler
|
|
25
27
|
export { ThrottlerModule, ThrottlerGuard, ThrottlerStorage, Throttle, SkipThrottle, THROTTLER_OPTIONS, THROTTLER_STORAGE, THROTTLE_METADATA, SKIP_THROTTLE_METADATA } from "./throttler/index.js";
|
|
26
28
|
// Module
|
|
@@ -38,3 +40,9 @@ export { RouteManager } from "./http/index.js";
|
|
|
38
40
|
export { ModuleLoader } from "./module/index.js";
|
|
39
41
|
// Component Manager (for advanced usage)
|
|
40
42
|
export { ComponentManager } from "./pipeline/index.js";
|
|
43
|
+
// Validation
|
|
44
|
+
export { createZodDto, ValidationPipe } from "./validation/index.js";
|
|
45
|
+
// Serialization
|
|
46
|
+
export { Serialize, SerializerInterceptor, SERIALIZE_METADATA } from "./serialization/index.js";
|
|
47
|
+
// Testing
|
|
48
|
+
export { Test, TestingModule, TestingModuleBuilder } from "./testing/index.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { MetadataRegistry } from "../registry/metadata.registry.js";
|
|
2
|
+
export const SERIALIZE_METADATA = 'vela:serialize';
|
|
3
|
+
export function Serialize(dto) {
|
|
4
|
+
return (target, propertyKey)=>{
|
|
5
|
+
MetadataRegistry.setCustomHandlerMeta(target.constructor, propertyKey, SERIALIZE_METADATA, dto);
|
|
6
|
+
};
|
|
7
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { MetadataRegistry } from "../registry/metadata.registry.js";
|
|
2
|
+
import { SERIALIZE_METADATA } from "./serialize.decorator.js";
|
|
3
|
+
export class SerializerInterceptor {
|
|
4
|
+
async intercept(context, next) {
|
|
5
|
+
const result = await next.handle();
|
|
6
|
+
const controller = context.getClass();
|
|
7
|
+
const handler = context.getHandler();
|
|
8
|
+
const dto = MetadataRegistry.getCustomHandlerMeta(controller, handler, SERIALIZE_METADATA);
|
|
9
|
+
const schema = dto?.schema;
|
|
10
|
+
if (!schema?.parse) return result;
|
|
11
|
+
if (Array.isArray(result)) {
|
|
12
|
+
return result.map((item)=>schema.parse(item));
|
|
13
|
+
}
|
|
14
|
+
return schema.parse(result);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Token, Type } from '../container/types';
|
|
2
|
+
import type { ModuleOptions } from '../module/types';
|
|
3
|
+
import { TestingModule } from './testing.module';
|
|
4
|
+
export declare class OverrideBy {
|
|
5
|
+
private readonly builder;
|
|
6
|
+
private readonly token;
|
|
7
|
+
constructor(builder: TestingModuleBuilder, token: Token);
|
|
8
|
+
useValue(value: unknown): TestingModuleBuilder;
|
|
9
|
+
useClass(cls: Type): TestingModuleBuilder;
|
|
10
|
+
useFactory(options: {
|
|
11
|
+
factory: (...args: unknown[]) => unknown;
|
|
12
|
+
inject?: Token[];
|
|
13
|
+
}): TestingModuleBuilder;
|
|
14
|
+
}
|
|
15
|
+
export declare class TestingModuleBuilder {
|
|
16
|
+
private readonly metadata;
|
|
17
|
+
private overrides;
|
|
18
|
+
constructor(metadata: ModuleOptions);
|
|
19
|
+
overrideProvider(token: Token): OverrideBy;
|
|
20
|
+
overrideGuard(guard: Type): OverrideBy;
|
|
21
|
+
overridePipe(pipe: Type): OverrideBy;
|
|
22
|
+
overrideInterceptor(interceptor: Type): OverrideBy;
|
|
23
|
+
overrideFilter(filter: Type): OverrideBy;
|
|
24
|
+
private addOverride;
|
|
25
|
+
compile(): Promise<TestingModule>;
|
|
26
|
+
}
|
|
27
|
+
export declare class Test {
|
|
28
|
+
static createTestingModule(metadata: ModuleOptions): TestingModuleBuilder;
|
|
29
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { VelaApplication } from "../application.js";
|
|
2
|
+
import { METADATA_KEYS } from "../constants.js";
|
|
3
|
+
import { Container } from "../container/container.js";
|
|
4
|
+
import { RouteManager } from "../http/route.manager.js";
|
|
5
|
+
import { defineMetadata } from "../metadata.js";
|
|
6
|
+
import { ModuleLoader } from "../module/module-loader.js";
|
|
7
|
+
import { ComponentManager } from "../pipeline/component.manager.js";
|
|
8
|
+
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_MIDDLEWARE, APP_PIPE } from "../pipeline/tokens.js";
|
|
9
|
+
import { MetadataRegistry } from "../registry/metadata.registry.js";
|
|
10
|
+
import { TestingModule } from "./testing.module.js";
|
|
11
|
+
export class OverrideBy {
|
|
12
|
+
builder;
|
|
13
|
+
token;
|
|
14
|
+
constructor(builder, token){
|
|
15
|
+
this.builder = builder;
|
|
16
|
+
this.token = token;
|
|
17
|
+
}
|
|
18
|
+
useValue(value) {
|
|
19
|
+
this.builder['addOverride']({
|
|
20
|
+
token: this.token,
|
|
21
|
+
provider: {
|
|
22
|
+
token: this.token,
|
|
23
|
+
useValue: value
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
return this.builder;
|
|
27
|
+
}
|
|
28
|
+
useClass(cls) {
|
|
29
|
+
this.builder['addOverride']({
|
|
30
|
+
token: this.token,
|
|
31
|
+
provider: {
|
|
32
|
+
token: this.token,
|
|
33
|
+
useFactory: ()=>new cls()
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
return this.builder;
|
|
37
|
+
}
|
|
38
|
+
useFactory(options) {
|
|
39
|
+
this.builder['addOverride']({
|
|
40
|
+
token: this.token,
|
|
41
|
+
provider: {
|
|
42
|
+
token: this.token,
|
|
43
|
+
useFactory: options.factory,
|
|
44
|
+
inject: options.inject
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return this.builder;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
export class TestingModuleBuilder {
|
|
51
|
+
metadata;
|
|
52
|
+
overrides = [];
|
|
53
|
+
constructor(metadata){
|
|
54
|
+
this.metadata = metadata;
|
|
55
|
+
}
|
|
56
|
+
overrideProvider(token) {
|
|
57
|
+
return new OverrideBy(this, token);
|
|
58
|
+
}
|
|
59
|
+
overrideGuard(guard) {
|
|
60
|
+
return new OverrideBy(this, guard);
|
|
61
|
+
}
|
|
62
|
+
overridePipe(pipe) {
|
|
63
|
+
return new OverrideBy(this, pipe);
|
|
64
|
+
}
|
|
65
|
+
overrideInterceptor(interceptor) {
|
|
66
|
+
return new OverrideBy(this, interceptor);
|
|
67
|
+
}
|
|
68
|
+
overrideFilter(filter) {
|
|
69
|
+
return new OverrideBy(this, filter);
|
|
70
|
+
}
|
|
71
|
+
addOverride(entry) {
|
|
72
|
+
// Replace existing override for same token if any
|
|
73
|
+
const idx = this.overrides.findIndex((o)=>o.token === entry.token);
|
|
74
|
+
if (idx !== -1) {
|
|
75
|
+
this.overrides[idx] = entry;
|
|
76
|
+
} else {
|
|
77
|
+
this.overrides.push(entry);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
async compile() {
|
|
81
|
+
// Create a temporary module class with the provided metadata
|
|
82
|
+
let TestRootModule = class TestRootModule {
|
|
83
|
+
};
|
|
84
|
+
MetadataRegistry.setModuleOptions(TestRootModule, {
|
|
85
|
+
imports: this.metadata.imports,
|
|
86
|
+
providers: this.metadata.providers,
|
|
87
|
+
controllers: this.metadata.controllers,
|
|
88
|
+
exports: this.metadata.exports
|
|
89
|
+
});
|
|
90
|
+
defineMetadata(METADATA_KEYS.MODULE, true, TestRootModule);
|
|
91
|
+
// Bootstrap (mirrors VelaFactory.create)
|
|
92
|
+
const container = new Container();
|
|
93
|
+
container.register({
|
|
94
|
+
token: Container,
|
|
95
|
+
useValue: container
|
|
96
|
+
});
|
|
97
|
+
// Register overrides BEFORE module loading so ModuleLoader skips originals
|
|
98
|
+
for (const override of this.overrides){
|
|
99
|
+
container.register(override.provider);
|
|
100
|
+
}
|
|
101
|
+
const routeManager = new RouteManager(container);
|
|
102
|
+
ComponentManager.init(container);
|
|
103
|
+
const loader = new ModuleLoader(container, routeManager);
|
|
104
|
+
loader.load(TestRootModule);
|
|
105
|
+
// Resolve APP_* tokens
|
|
106
|
+
if (container.has(APP_GUARD)) {
|
|
107
|
+
routeManager.useGlobalGuards(container.resolve(APP_GUARD));
|
|
108
|
+
}
|
|
109
|
+
if (container.has(APP_PIPE)) {
|
|
110
|
+
routeManager.useGlobalPipes(container.resolve(APP_PIPE));
|
|
111
|
+
}
|
|
112
|
+
if (container.has(APP_INTERCEPTOR)) {
|
|
113
|
+
routeManager.useGlobalInterceptors(container.resolve(APP_INTERCEPTOR));
|
|
114
|
+
}
|
|
115
|
+
if (container.has(APP_FILTER)) {
|
|
116
|
+
routeManager.useGlobalFilters(container.resolve(APP_FILTER));
|
|
117
|
+
}
|
|
118
|
+
if (container.has(APP_MIDDLEWARE)) {
|
|
119
|
+
routeManager.useGlobalMiddleware(container.resolve(APP_MIDDLEWARE));
|
|
120
|
+
}
|
|
121
|
+
const app = new VelaApplication(container, routeManager);
|
|
122
|
+
const instances = loader.resolveAllInstances();
|
|
123
|
+
app.setInstances(instances);
|
|
124
|
+
await app.callOnModuleInit();
|
|
125
|
+
await app.callOnApplicationBootstrap();
|
|
126
|
+
await app.initRoutes();
|
|
127
|
+
return new TestingModule(app);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export class Test {
|
|
131
|
+
static createTestingModule(metadata) {
|
|
132
|
+
return new TestingModuleBuilder(metadata);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { VelaApplication } from '../application';
|
|
2
|
+
import type { Token } from '../container/types';
|
|
3
|
+
export declare class TestingModule {
|
|
4
|
+
private readonly app;
|
|
5
|
+
constructor(app: VelaApplication);
|
|
6
|
+
get<T>(token: Token<T>): T;
|
|
7
|
+
close(signal?: string): Promise<void>;
|
|
8
|
+
createNestApplication(): VelaApplication;
|
|
9
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BadRequestException } from "../errors/http-exception.js";
|
|
2
|
+
export class ValidationPipe {
|
|
3
|
+
transform(value, metadata) {
|
|
4
|
+
const metatype = metadata.metatype;
|
|
5
|
+
if (!metatype?.schema?.parse) return value;
|
|
6
|
+
try {
|
|
7
|
+
return metatype.schema.parse(value);
|
|
8
|
+
} catch (error) {
|
|
9
|
+
if (error && typeof error === 'object' && 'issues' in error && Array.isArray(error.issues)) {
|
|
10
|
+
throw new BadRequestException('Validation failed', {
|
|
11
|
+
statusCode: 400,
|
|
12
|
+
message: 'Validation failed',
|
|
13
|
+
errors: error.issues
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velajs/vela",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "NestJS-compatible framework for edge runtimes, powered by Hono",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -66,7 +66,8 @@
|
|
|
66
66
|
"@types/bun": "latest",
|
|
67
67
|
"typescript": "^5",
|
|
68
68
|
"unplugin-swc": "^1.5.9",
|
|
69
|
-
"vitest": "^4.0.18"
|
|
69
|
+
"vitest": "^4.0.18",
|
|
70
|
+
"zod": "^4.3.6"
|
|
70
71
|
},
|
|
71
72
|
"scripts": {
|
|
72
73
|
"build": "rm -rf dist && swc src -d dist --strip-leading-paths && tsc --emitDeclarationOnly",
|