@velajs/vela 0.10.0 → 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.
Files changed (91) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/README.md +36 -2
  3. package/dist/cache/cache.decorators.d.ts +2 -2
  4. package/dist/cache/cache.module.d.ts +1 -1
  5. package/dist/cache/cache.module.js +1 -1
  6. package/dist/constants.d.ts +29 -26
  7. package/dist/constants.js +26 -29
  8. package/dist/container/container.js +2 -2
  9. package/dist/container/decorators.js +8 -11
  10. package/dist/container/types.d.ts +1 -0
  11. package/dist/errors/http-exception.d.ts +21 -20
  12. package/dist/errors/http-exception.js +6 -6
  13. package/dist/event-emitter/event-emitter.decorators.js +2 -3
  14. package/dist/event-emitter/event-emitter.subscriber.js +2 -1
  15. package/dist/factory.d.ts +0 -1
  16. package/dist/factory.js +2 -32
  17. package/dist/fetch/fetch.module.d.ts +2 -2
  18. package/dist/fetch/fetch.module.js +2 -2
  19. package/dist/health/health.service.js +6 -2
  20. package/dist/http/decorators.d.ts +6 -5
  21. package/dist/http/decorators.js +28 -76
  22. package/dist/http/execution-context.d.ts +4 -0
  23. package/dist/http/execution-context.js +15 -0
  24. package/dist/http/index.d.ts +2 -2
  25. package/dist/http/index.js +1 -1
  26. package/dist/http/route.manager.d.ts +1 -2
  27. package/dist/http/route.manager.js +20 -31
  28. package/dist/http/types.d.ts +0 -1
  29. package/dist/index.d.ts +4 -10
  30. package/dist/index.js +7 -12
  31. package/dist/internal.d.ts +11 -0
  32. package/dist/internal.js +13 -0
  33. package/dist/metadata.js +6 -15
  34. package/dist/module/decorators.d.ts +1 -3
  35. package/dist/module/decorators.js +8 -20
  36. package/dist/module/graph.d.ts +10 -0
  37. package/dist/module/graph.js +35 -0
  38. package/dist/module/index.d.ts +2 -0
  39. package/dist/module/index.js +1 -0
  40. package/dist/{http/middleware-consumer.d.ts → module/middleware.d.ts} +4 -13
  41. package/dist/{http/middleware-consumer.js → module/middleware.js} +0 -13
  42. package/dist/module/module-loader.d.ts +1 -1
  43. package/dist/module/module-loader.js +17 -13
  44. package/dist/module/types.d.ts +1 -29
  45. package/dist/openapi/decorators.js +15 -10
  46. package/dist/openapi/document.js +7 -42
  47. package/dist/pipeline/app-providers.d.ts +10 -0
  48. package/dist/pipeline/app-providers.js +43 -0
  49. package/dist/pipeline/component.manager.d.ts +1 -0
  50. package/dist/pipeline/component.manager.js +10 -34
  51. package/dist/pipeline/decorators.d.ts +5 -5
  52. package/dist/pipeline/decorators.js +1 -9
  53. package/dist/pipeline/reflector.d.ts +8 -24
  54. package/dist/pipeline/reflector.js +10 -55
  55. package/dist/registry/index.d.ts +1 -1
  56. package/dist/registry/metadata.registry.d.ts +20 -15
  57. package/dist/registry/metadata.registry.js +135 -154
  58. package/dist/registry/paths.d.ts +3 -0
  59. package/dist/registry/paths.js +15 -0
  60. package/dist/registry/types.d.ts +24 -14
  61. package/dist/registry/types.js +0 -1
  62. package/dist/registry/util.d.ts +3 -0
  63. package/dist/registry/util.js +8 -0
  64. package/dist/schedule/cron-matcher.d.ts +2 -0
  65. package/dist/schedule/cron-matcher.js +62 -0
  66. package/dist/schedule/index.d.ts +4 -3
  67. package/dist/schedule/index.js +2 -2
  68. package/dist/schedule/schedule.decorators.js +3 -6
  69. package/dist/schedule/schedule.module.d.ts +2 -6
  70. package/dist/schedule/schedule.module.js +4 -42
  71. package/dist/schedule/schedule.registry.js +3 -2
  72. package/dist/schedule/schedule.tokens.d.ts +0 -3
  73. package/dist/schedule/schedule.tokens.js +0 -2
  74. package/dist/schedule/schedule.types.d.ts +0 -3
  75. package/dist/schedule-node/index.d.ts +2 -0
  76. package/dist/schedule-node/index.js +2 -0
  77. package/dist/schedule-node/schedule-node.module.d.ts +4 -0
  78. package/dist/schedule-node/schedule-node.module.js +18 -0
  79. package/dist/{schedule → schedule-node}/schedule.executor.d.ts +2 -5
  80. package/dist/schedule-node/schedule.executor.js +91 -0
  81. package/dist/services/logger.d.ts +9 -8
  82. package/dist/services/logger.js +14 -15
  83. package/dist/throttler/throttler.decorators.d.ts +2 -2
  84. package/package.json +9 -9
  85. package/dist/schedule/schedule.executor.js +0 -169
  86. package/dist/testing/index.d.ts +0 -2
  87. package/dist/testing/index.js +0 -2
  88. package/dist/testing/testing.builder.d.ts +0 -29
  89. package/dist/testing/testing.builder.js +0 -148
  90. package/dist/testing/testing.module.d.ts +0 -9
  91. package/dist/testing/testing.module.js +0 -15
package/CHANGELOG.md CHANGED
@@ -1,5 +1,118 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.0 (2026-04-30)
4
+
5
+ Architectural remodel: one metadata model, one storage, public surface trimmed, internal primitives exposed via a stable subpath.
6
+
7
+ ### Breaking changes
8
+
9
+ - **`createApplication` removed.** Use `VelaFactory.create(AppModule)` directly.
10
+
11
+ ```diff
12
+ - import { createApplication } from '@velajs/vela';
13
+ - const app = await createApplication(AppModule);
14
+ + import { VelaFactory } from '@velajs/vela';
15
+ + const app = await VelaFactory.create(AppModule);
16
+ ```
17
+
18
+ - **`RequestMethod` removed; `HttpMethod` is canonical and now uppercase.** The two enums had the same purpose but different cases (`'get'` vs `'GET'`). Unified to uppercase to match HTTP spec and Hono's request `method` field.
19
+
20
+ ```diff
21
+ - import { RequestMethod } from '@velajs/vela';
22
+ - .forRoutes({ path: '/api', method: RequestMethod.POST });
23
+ + import { HttpMethod } from '@velajs/vela';
24
+ + .forRoutes({ path: '/api', method: HttpMethod.POST });
25
+ ```
26
+
27
+ - **`@Controller({ prefix })` removed; `@Controller({ path })` only.** `prefix` was a non-canonical alias.
28
+
29
+ ```diff
30
+ - @Controller({ prefix: '/users', version: 1 })
31
+ + @Controller({ path: '/users', version: 1 })
32
+ ```
33
+
34
+ - **`HttpModule.register` / `registerAsync` renamed to `forRoot` / `forRootAsync`.** Same for `CacheModule.registerAsync` → `forRootAsync`. Matches NestJS canonical naming and aligns the rest of the framework's dynamic-module shape.
35
+
36
+ ```diff
37
+ - HttpModule.register({ baseURL: '...' })
38
+ - HttpModule.registerAsync({ ... })
39
+ - CacheModule.registerAsync({ ... })
40
+ + HttpModule.forRoot({ baseURL: '...' })
41
+ + HttpModule.forRootAsync({ ... })
42
+ + CacheModule.forRootAsync({ ... })
43
+ ```
44
+
45
+ - **`MetadataRegistry`, `RouteManager`, `ModuleLoader`, `ComponentManager`, `Container`, `VelaApplication` (the class), `bindAppProviders`, and the `APP_*` tokens moved to a stable internal subpath.** The public root barrel still re-exports `MetadataRegistry` (used by tests for `clear()`), but plugin authors should consume framework primitives from `/internal`:
46
+
47
+ ```diff
48
+ - import { RouteManager, ModuleLoader, ComponentManager } from '@velajs/vela';
49
+ + import { RouteManager, ModuleLoader, ComponentManager } from '@velajs/vela/internal';
50
+ ```
51
+
52
+ - **`Test`, `TestingModule`, `TestingModuleBuilder` removed from `@velajs/vela`'s root barrel.** Use `@velajs/testing` (≥ 0.2.0) — it's the canonical home and was rewritten on top of `@velajs/vela/internal`.
53
+
54
+ - **`HttpException._response` typed as `string | Record<string, unknown>`** (was `string | object`). `getResponse()` now returns `Record<string, unknown>`.
55
+
56
+ - **`applyDecorators` return type** changed from the impossible-at-runtime `ClassDecorator & MethodDecorator & PropertyDecorator` intersection to a `ComposedDecorator` polymorphic shape that matches every decorator slot structurally.
57
+
58
+ - **`Reflector` API takes `ExecutionContext` directly.** Was a duck-typed `{ getClass(): Constructor; getHandler(): string|symbol }` parameter; now the real type. Existing call sites that already pass an `ExecutionContext` need no change.
59
+
60
+ - **Inverted peer dep removed**: `@velajs/vela` no longer declares `peerDependencies['@velajs/crud']`. Crud is the consumer; that line was reversed.
61
+
62
+ ### New
63
+
64
+ - **`@velajs/vela/internal` subpath export.** Re-exports `MetadataRegistry`, `Container`, `ModuleRef`, `RouteManager`, `ModuleLoader`, `ComponentManager`, `VelaApplication`, `bindAppProviders`, `APP_GUARD`/`APP_PIPE`/`APP_INTERCEPTOR`/`APP_FILTER`/`APP_MIDDLEWARE`, `getModuleMetadata`, `isModule`. Stable target for plugins.
65
+
66
+ ### Internal cleanup
67
+
68
+ - **One metadata storage.** Decorators no longer dual-write to a typed `MetadataRegistry` slot AND a parallel `WeakMap` polyfill. The `Reflect.metadata` polyfill now funnels into `MetadataRegistry.classMeta`/`handlerMeta`, which also backs `setCustomClassMeta`/`setCustomHandlerMeta` and the `appendCustomClassMeta`/`appendCustomHandlerMeta` helpers.
69
+ - **`MetadataRegistry.clear()`** now clears only app-time global components (the only mutable run-time slot). Decoration metadata persists across `clear()`, so tests no longer need a fallback path. Use `MetadataRegistry.reset()` for a full wipe.
70
+ - **No `!` non-null assertions** in `MetadataRegistry`. New `getOrCreate`/`getOrCreateMap`/`getOrCreateArray` helpers in `registry/util.ts`.
71
+ - **No `as unknown as` casts** in `MetadataRegistry`. Component stores are now mapped-typed instead of union-typed.
72
+ - **`Constructor` is now `abstract new (...args: any[]) => unknown`** (was the bare unsafe `Function`). Every decorator's `target` casts to `Constructor` consistently.
73
+ - **Single home for shared types**: `Type`, `Token`, `Constructor`, `ProviderOptions`, `InjectableOptions`, etc. canonical in `container/types.ts`. `ModuleOptions`, `DynamicModule`, `ModuleImport`, `AsyncModuleOptions`, `ModuleMetadata` canonical in `registry/types.ts`. `module/types.ts` is a thin re-export. The `InjectionTokenLike` duck-type and the duplicated `ProviderOptions` are gone.
74
+ - **Layering fixed**: `MiddlewareConsumer`/`MiddlewareBuilder`/`NestModule`/`RouteInfo` moved from `http/` to `module/`. `module/module-loader.ts` no longer imports from `http/`.
75
+ - **`module-loader` `new moduleClass()` footgun fixed.** `NestModule.configure()` modules are now resolved through the container, so they can have constructor-injected deps.
76
+ - **One path util** (`registry/paths.ts`: `normalizePath`, `joinPaths`, `toOpenApiPath`). Replaces 2× duplicated implementations in `http/decorators.ts`, `openapi/document.ts`, and `route.manager.ts`.
77
+ - **One `ExecutionContext` factory** (`http/execution-context.ts`: `buildExecutionContext`). Replaces 2× duplicated literal construction.
78
+ - **One `bindAppProviders` helper** (`pipeline/app-providers.ts`). Implements the NestJS APP_* provider convention in one place; replaces 5× duplicated APP_* wiring blocks across `factory.ts` and `testing.builder.ts`.
79
+ - **One module-graph walk** (`module/graph.ts`: `collectControllers`). Replaces the duplicate implementation in `openapi/document.ts`.
80
+ - **schedule/event-emitter/openapi decorators** now use `MetadataRegistry.appendCustomClassMeta`/`appendCustomHandlerMeta` instead of direct `Reflect.defineMetadata` calls.
81
+ - **Stub `pnpm-workspace.yaml` and `bunfig.toml` deleted** — they only set `onlyBuiltDependencies`, which lives in `package.json#pnpm`. `bun.lock` deleted; pnpm is the source of truth.
82
+
83
+ ## 0.10.0 (2026-04-28)
84
+
85
+ Edge-runtime audit and AI-drift cleanup.
86
+
87
+ ### Breaking changes
88
+
89
+ - **Schedule module split.** `ScheduleExecutor`, `SCHEDULE_MODULE_OPTIONS`, and `ScheduleModuleOptions` are no longer exported from `@velajs/vela`. The `setInterval`-based timer executor moved to a new opt-in sub-export at `@velajs/vela/schedule-node`. Consumers on Node or Bun should now do:
90
+
91
+ ```ts
92
+ import { ScheduleNodeModule } from '@velajs/vela/schedule-node';
93
+
94
+ @Module({ imports: [ScheduleNodeModule.forRoot()], providers: [JobsService] })
95
+ class AppModule {}
96
+ ```
97
+
98
+ `ScheduleModule.forRoot()` is now metadata-only — `enableTimers` is no longer accepted (drop the option entirely). `ScheduleModule.forRootAsync()` was removed (no options to async-resolve).
99
+
100
+ Edge runtimes without `setInterval` (Cloudflare Workers, etc.) should continue to use platform cron triggers — `@velajs/cloudflare` ≥ 0.2.0 dispatches core `@Cron` jobs via its `scheduled()` handler.
101
+
102
+ - **TypeScript enums replaced with `as const` objects** for `HttpMethod`, `ParamType`, `Scope`, `RequestMethod`, and `LogLevel`. Value access (`HttpMethod.GET`) keeps working; type-position usages (`: HttpMethod`) keep working via same-name type aliases. Code that imported the enum *type* with structural assumptions about enum runtime shape may need adjustment.
103
+
104
+ ### Fixes
105
+
106
+ - `@Head()` is now HEAD-only. Previously it registered as a plain GET handler, so `GET` requests would also hit `@Head()`-decorated methods.
107
+ - Handler-level guards / pipes / interceptors / filters now key off the controller constructor instead of `${className}:${method}`, fixing a metadata collision when two controllers shared a class name (across feature modules or after minification).
108
+ - Removed an obsolete narration comment in `route.manager.ts`.
109
+
110
+ ### New
111
+
112
+ - `@velajs/vela/schedule-node` — opt-in entry for Node/Bun cron and interval execution.
113
+ - `parseCron(expression)` and `CronMatcher` exported from the core for use by platform cron adapters.
114
+ - Edge-runtime audit test (`src/__tests__/edge-runtime-audit.test.ts`) — fails CI if any file under `src/` (excluding `schedule-node/`) references forbidden APIs (`node:*`, `Buffer`, `process.*`, `__dirname/__filename`, `fs/path/os/child_process`, `setInterval`, `Bun.serve`).
115
+
3
116
  ## 0.1.0 (2026-02-19)
4
117
 
5
118
  Initial release.
package/README.md CHANGED
@@ -73,13 +73,47 @@ Vela runs on any runtime that supports the Web Standards API:
73
73
 
74
74
  No Node.js-specific APIs (`node:fs`, `Buffer`, `process`) are used.
75
75
 
76
- ## CRUD Module (Optional)
76
+ ## Dynamic modules
77
+
78
+ Configurable modules use `forRoot` (sync) and `forRootAsync` (DI-resolved):
79
+
80
+ ```ts
81
+ @Module({
82
+ imports: [
83
+ CacheModule.forRoot({ ttl: 60 }),
84
+ HttpModule.forRoot({ baseURL: 'https://api.example.com' }),
85
+ ConfigModule.forRootAsync({
86
+ useFactory: async (loader: ConfigLoader) => loader.load(),
87
+ inject: [ConfigLoader],
88
+ }),
89
+ ],
90
+ })
91
+ class AppModule {}
92
+ ```
93
+
94
+ ## Companion packages
95
+
96
+ | Package | Purpose |
97
+ |---|---|
98
+ | [`@velajs/cloudflare`](https://github.com/velajs/cloudflare) | Cloudflare Workers adapter — typed services for KV, D1, R2, Queues, DO, AI, Vectorize, Hyperdrive |
99
+ | [`@velajs/crud`](https://github.com/velajs/crud) | NestJS-style CRUD controllers on top of `hono-crud` |
100
+ | [`@velajs/testing`](https://github.com/velajs/testing) | `Test.createTestingModule()` with `overrideProvider/Guard/Pipe/Interceptor/Filter` |
77
101
 
78
102
  ```bash
103
+ pnpm add @velajs/testing -D
104
+ pnpm add @velajs/cloudflare @cloudflare/workers-types
79
105
  pnpm add @velajs/crud hono-crud @hono/zod-openapi zod
80
106
  ```
81
107
 
82
- See [`@velajs/crud`](https://github.com/velajs/crud) for documentation.
108
+ ## `/internal` subpath (for plugin authors)
109
+
110
+ Framework primitives — `MetadataRegistry`, `Container`, `RouteManager`, `ModuleLoader`, `ComponentManager`, `VelaApplication`, `bindAppProviders`, `APP_*` tokens — are exposed at `@velajs/vela/internal`. This is the stable target for plugin packages that need to reach below the public API.
111
+
112
+ ```ts
113
+ import { MetadataRegistry, Container } from '@velajs/vela/internal';
114
+ ```
115
+
116
+ The public root barrel still exports `MetadataRegistry` (used by tests for `clear()` between cases). Everything else lives at `/internal` only.
83
117
 
84
118
  ## License
85
119
 
@@ -1,2 +1,2 @@
1
- export declare const CacheKey: (key: string) => (target: Function | object, propertyKey?: string | symbol) => void;
2
- export declare const CacheTTL: (seconds: number) => (target: Function | object, propertyKey?: string | symbol) => void;
1
+ export declare const CacheKey: (key: string) => (target: object, propertyKey?: string | symbol) => void;
2
+ export declare const CacheTTL: (seconds: number) => (target: object, propertyKey?: string | symbol) => void;
@@ -2,7 +2,7 @@ import type { AsyncModuleOptions, DynamicModule } from '../module/types';
2
2
  import type { CacheModuleOptions } from './cache.types';
3
3
  export declare class CacheModule {
4
4
  static forRoot(options?: CacheModuleOptions): DynamicModule;
5
- static registerAsync(options: AsyncModuleOptions<CacheModuleOptions> & {
5
+ static forRootAsync(options: AsyncModuleOptions<CacheModuleOptions> & {
6
6
  isGlobal?: boolean;
7
7
  }): DynamicModule;
8
8
  }
@@ -36,7 +36,7 @@ export class CacheModule {
36
36
  ]
37
37
  };
38
38
  }
39
- static registerAsync(options) {
39
+ static forRootAsync(options) {
40
40
  const providers = [
41
41
  {
42
42
  provide: CACHE_MODULE_OPTIONS,
@@ -13,29 +13,32 @@ export declare const METADATA_KEYS: {
13
13
  readonly CATCH: "vela:catch";
14
14
  readonly CRUD: "vela:crud";
15
15
  };
16
- export declare enum HttpMethod {
17
- GET = "get",
18
- POST = "post",
19
- PUT = "put",
20
- PATCH = "patch",
21
- DELETE = "delete",
22
- OPTIONS = "options",
23
- HEAD = "head",
24
- ALL = "all"
25
- }
26
- export declare enum ParamType {
27
- BODY = "body",
28
- QUERY = "query",
29
- PARAM = "param",
30
- HEADERS = "headers",
31
- REQUEST = "request",
32
- RESPONSE = "response",
33
- IP = "ip",
34
- COOKIE = "cookie",
35
- RAW_BODY = "raw_body"
36
- }
37
- export declare enum Scope {
38
- SINGLETON = "singleton",
39
- TRANSIENT = "transient",
40
- REQUEST = "request"
41
- }
16
+ export declare const HttpMethod: {
17
+ readonly GET: "GET";
18
+ readonly POST: "POST";
19
+ readonly PUT: "PUT";
20
+ readonly PATCH: "PATCH";
21
+ readonly DELETE: "DELETE";
22
+ readonly OPTIONS: "OPTIONS";
23
+ readonly HEAD: "HEAD";
24
+ readonly ALL: "ALL";
25
+ };
26
+ export type HttpMethod = (typeof HttpMethod)[keyof typeof HttpMethod];
27
+ export declare const ParamType: {
28
+ readonly BODY: "body";
29
+ readonly QUERY: "query";
30
+ readonly PARAM: "param";
31
+ readonly HEADERS: "headers";
32
+ readonly REQUEST: "request";
33
+ readonly RESPONSE: "response";
34
+ readonly IP: "ip";
35
+ readonly COOKIE: "cookie";
36
+ readonly RAW_BODY: "raw_body";
37
+ };
38
+ export type ParamType = (typeof ParamType)[keyof typeof ParamType];
39
+ export declare const Scope: {
40
+ readonly SINGLETON: "singleton";
41
+ readonly TRANSIENT: "transient";
42
+ readonly REQUEST: "request";
43
+ };
44
+ export type Scope = (typeof Scope)[keyof typeof Scope];
package/dist/constants.js CHANGED
@@ -18,32 +18,29 @@ export const METADATA_KEYS = {
18
18
  // CRUD
19
19
  CRUD: 'vela:crud'
20
20
  };
21
- export var HttpMethod = /*#__PURE__*/ function(HttpMethod) {
22
- HttpMethod["GET"] = "get";
23
- HttpMethod["POST"] = "post";
24
- HttpMethod["PUT"] = "put";
25
- HttpMethod["PATCH"] = "patch";
26
- HttpMethod["DELETE"] = "delete";
27
- HttpMethod["OPTIONS"] = "options";
28
- HttpMethod["HEAD"] = "head";
29
- HttpMethod["ALL"] = "all";
30
- return HttpMethod;
31
- }({});
32
- export var ParamType = /*#__PURE__*/ function(ParamType) {
33
- ParamType["BODY"] = "body";
34
- ParamType["QUERY"] = "query";
35
- ParamType["PARAM"] = "param";
36
- ParamType["HEADERS"] = "headers";
37
- ParamType["REQUEST"] = "request";
38
- ParamType["RESPONSE"] = "response";
39
- ParamType["IP"] = "ip";
40
- ParamType["COOKIE"] = "cookie";
41
- ParamType["RAW_BODY"] = "raw_body";
42
- return ParamType;
43
- }({});
44
- export var Scope = /*#__PURE__*/ function(Scope) {
45
- Scope["SINGLETON"] = "singleton";
46
- Scope["TRANSIENT"] = "transient";
47
- Scope["REQUEST"] = "request";
48
- return Scope;
49
- }({});
21
+ export const HttpMethod = {
22
+ GET: 'GET',
23
+ POST: 'POST',
24
+ PUT: 'PUT',
25
+ PATCH: 'PATCH',
26
+ DELETE: 'DELETE',
27
+ OPTIONS: 'OPTIONS',
28
+ HEAD: 'HEAD',
29
+ ALL: 'ALL'
30
+ };
31
+ export const ParamType = {
32
+ BODY: 'body',
33
+ QUERY: 'query',
34
+ PARAM: 'param',
35
+ HEADERS: 'headers',
36
+ REQUEST: 'request',
37
+ RESPONSE: 'response',
38
+ IP: 'ip',
39
+ COOKIE: 'cookie',
40
+ RAW_BODY: 'raw_body'
41
+ };
42
+ export const Scope = {
43
+ SINGLETON: 'singleton',
44
+ TRANSIENT: 'transient',
45
+ REQUEST: 'request'
46
+ };
@@ -211,10 +211,10 @@ export class Container {
211
211
  }
212
212
  return this.resolve(token);
213
213
  }
214
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
215
214
  createLazyProxy(token) {
216
215
  const container = this;
217
- return new Proxy({}, {
216
+ const target = Object.create(null);
217
+ return new Proxy(target, {
218
218
  get (_target, prop) {
219
219
  const instance = container.resolve(token);
220
220
  const value = instance[prop];
@@ -1,14 +1,10 @@
1
- import { METADATA_KEYS, Scope } from "../constants.js";
2
- import { defineMetadata, getMetadata } from "../metadata.js";
1
+ import { Scope } from "../constants.js";
3
2
  import { MetadataRegistry } from "../registry/metadata.registry.js";
4
3
  export function Injectable(options = {}) {
5
4
  return (target)=>{
6
5
  const { scope = Scope.SINGLETON } = options;
7
6
  MetadataRegistry.markInjectable(target);
8
7
  MetadataRegistry.setScope(target, scope);
9
- // Keep WeakMap write for external package compat
10
- defineMetadata(METADATA_KEYS.INJECTABLE, true, target);
11
- defineMetadata(METADATA_KEYS.SCOPE, scope, target);
12
8
  };
13
9
  }
14
10
  export function Optional() {
@@ -24,7 +20,6 @@ export function Optional() {
24
20
  });
25
21
  }
26
22
  MetadataRegistry.setInjectTokens(target, existing);
27
- defineMetadata(METADATA_KEYS.INJECT, existing, target);
28
23
  };
29
24
  }
30
25
  export function Inject(token) {
@@ -40,18 +35,20 @@ export function Inject(token) {
40
35
  });
41
36
  }
42
37
  MetadataRegistry.setInjectTokens(target, existing);
43
- defineMetadata(METADATA_KEYS.INJECT, existing, target);
44
38
  };
45
39
  }
46
40
  export function isInjectable(target) {
47
- return MetadataRegistry.hasInjectable(target) || getMetadata(METADATA_KEYS.INJECTABLE, target) === true;
41
+ return MetadataRegistry.hasInjectable(target);
48
42
  }
49
43
  export function getScope(target) {
50
- return MetadataRegistry.getScope(target) ?? getMetadata(METADATA_KEYS.SCOPE, target) ?? Scope.SINGLETON;
44
+ return MetadataRegistry.getScope(target) ?? Scope.SINGLETON;
51
45
  }
52
46
  export function getConstructorDependencies(target) {
53
- return Reflect.getMetadata('design:paramtypes', target) || [];
47
+ // Read through Reflect so dist/src coexistence in tests routes through the
48
+ // single polyfill-installed registry — SWC writes `design:paramtypes` via
49
+ // Reflect, so reads must too.
50
+ return Reflect.getMetadata('design:paramtypes', target) ?? [];
54
51
  }
55
52
  export function getInjectMetadata(target) {
56
- return MetadataRegistry.getInjectTokens(target) ?? getMetadata(METADATA_KEYS.INJECT, target) ?? [];
53
+ return MetadataRegistry.getInjectTokens(target) ?? [];
57
54
  }
@@ -1,5 +1,6 @@
1
1
  import type { Scope } from '../constants';
2
2
  export type Type<T = any> = new (...args: any[]) => T;
3
+ export type Constructor<T = unknown> = abstract new (...args: any[]) => T;
3
4
  export interface InjectionTokenOptions<T> {
4
5
  factory?: () => T;
5
6
  }
@@ -1,61 +1,62 @@
1
+ export type ExceptionResponse = string | Record<string, unknown>;
1
2
  export declare class HttpException extends Error {
2
3
  readonly statusCode: number;
3
4
  private readonly _response;
4
- constructor(response: string | object, statusCode: number);
5
+ constructor(response: ExceptionResponse, statusCode: number);
5
6
  getStatus(): number;
6
- getResponse(): unknown;
7
+ getResponse(): Record<string, unknown>;
7
8
  }
8
9
  export declare class BadRequestException extends HttpException {
9
- constructor(message?: string | object);
10
+ constructor(message?: ExceptionResponse);
10
11
  }
11
12
  export declare class UnauthorizedException extends HttpException {
12
- constructor(message?: string | object);
13
+ constructor(message?: ExceptionResponse);
13
14
  }
14
15
  export declare class ForbiddenException extends HttpException {
15
- constructor(message?: string | object);
16
+ constructor(message?: ExceptionResponse);
16
17
  }
17
18
  export declare class NotFoundException extends HttpException {
18
- constructor(message?: string | object);
19
+ constructor(message?: ExceptionResponse);
19
20
  }
20
21
  export declare class MethodNotAllowedException extends HttpException {
21
- constructor(message?: string | object);
22
+ constructor(message?: ExceptionResponse);
22
23
  }
23
24
  export declare class NotAcceptableException extends HttpException {
24
- constructor(message?: string | object);
25
+ constructor(message?: ExceptionResponse);
25
26
  }
26
27
  export declare class RequestTimeoutException extends HttpException {
27
- constructor(message?: string | object);
28
+ constructor(message?: ExceptionResponse);
28
29
  }
29
30
  export declare class ConflictException extends HttpException {
30
- constructor(message?: string | object);
31
+ constructor(message?: ExceptionResponse);
31
32
  }
32
33
  export declare class GoneException extends HttpException {
33
- constructor(message?: string | object);
34
+ constructor(message?: ExceptionResponse);
34
35
  }
35
36
  export declare class PayloadTooLargeException extends HttpException {
36
- constructor(message?: string | object);
37
+ constructor(message?: ExceptionResponse);
37
38
  }
38
39
  export declare class UnsupportedMediaTypeException extends HttpException {
39
- constructor(message?: string | object);
40
+ constructor(message?: ExceptionResponse);
40
41
  }
41
42
  export declare class UnprocessableEntityException extends HttpException {
42
- constructor(message?: string | object);
43
+ constructor(message?: ExceptionResponse);
43
44
  }
44
45
  export declare class TooManyRequestsException extends HttpException {
45
- constructor(message?: string | object);
46
+ constructor(message?: ExceptionResponse);
46
47
  }
47
48
  export declare class InternalServerErrorException extends HttpException {
48
- constructor(message?: string | object);
49
+ constructor(message?: ExceptionResponse);
49
50
  }
50
51
  export declare class NotImplementedException extends HttpException {
51
- constructor(message?: string | object);
52
+ constructor(message?: ExceptionResponse);
52
53
  }
53
54
  export declare class BadGatewayException extends HttpException {
54
- constructor(message?: string | object);
55
+ constructor(message?: ExceptionResponse);
55
56
  }
56
57
  export declare class ServiceUnavailableException extends HttpException {
57
- constructor(message?: string | object);
58
+ constructor(message?: ExceptionResponse);
58
59
  }
59
60
  export declare class GatewayTimeoutException extends HttpException {
60
- constructor(message?: string | object);
61
+ constructor(message?: ExceptionResponse);
61
62
  }
@@ -13,13 +13,13 @@ export class HttpException extends Error {
13
13
  return this.statusCode;
14
14
  }
15
15
  getResponse() {
16
- if (typeof this._response === 'object' && this._response !== null) {
17
- return this._response;
16
+ if (typeof this._response === 'string') {
17
+ return {
18
+ statusCode: this.statusCode,
19
+ message: this._response
20
+ };
18
21
  }
19
- return {
20
- statusCode: this.statusCode,
21
- message: this._response
22
- };
22
+ return this._response;
23
23
  }
24
24
  }
25
25
  // 4xx
@@ -1,11 +1,10 @@
1
+ import { MetadataRegistry } from "../registry/metadata.registry.js";
1
2
  import { ON_EVENT_METADATA } from "./event-emitter.tokens.js";
2
3
  export function OnEvent(event) {
3
4
  return (target, propertyKey)=>{
4
- const existing = Reflect.getMetadata(ON_EVENT_METADATA, target.constructor) ?? [];
5
- existing.push({
5
+ MetadataRegistry.appendCustomClassMeta(target.constructor, ON_EVENT_METADATA, {
6
6
  event,
7
7
  methodName: String(propertyKey)
8
8
  });
9
- Reflect.defineMetadata(ON_EVENT_METADATA, existing, target.constructor);
10
9
  };
11
10
  }
@@ -14,6 +14,7 @@ function _ts_param(paramIndex, decorator) {
14
14
  }
15
15
  import { Injectable, Inject } from "../container/index.js";
16
16
  import { Container } from "../container/container.js";
17
+ import { MetadataRegistry } from "../registry/metadata.registry.js";
17
18
  import { EventEmitter } from "./event-emitter.service.js";
18
19
  import { ON_EVENT_METADATA } from "./event-emitter.tokens.js";
19
20
  export class EventEmitterSubscriber {
@@ -28,7 +29,7 @@ export class EventEmitterSubscriber {
28
29
  for (const token of tokens){
29
30
  // Skip non-class tokens
30
31
  if (typeof token !== 'function') continue;
31
- const metadata = Reflect.getMetadata(ON_EVENT_METADATA, token);
32
+ const metadata = MetadataRegistry.getCustomClassMeta(token, ON_EVENT_METADATA);
32
33
  if (!metadata || metadata.length === 0) continue;
33
34
  let instance;
34
35
  try {
package/dist/factory.d.ts CHANGED
@@ -4,4 +4,3 @@ import type { RouteManagerOptions } from './http/route.manager';
4
4
  export declare const VelaFactory: {
5
5
  create(rootModule: Type, options?: RouteManagerOptions): Promise<VelaApplication>;
6
6
  };
7
- export declare const createApplication: typeof VelaFactory.create;
package/dist/factory.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { VelaApplication } from "./application.js";
2
2
  import { Container } from "./container/container.js";
3
3
  import { ModuleRef } from "./container/module-ref.js";
4
+ import { bindAppProviders } from "./pipeline/app-providers.js";
4
5
  import { RouteManager } from "./http/route.manager.js";
5
6
  import { ModuleLoader } from "./module/module-loader.js";
6
7
  import { ComponentManager } from "./pipeline/component.manager.js";
7
- import { APP_GUARD, APP_PIPE, APP_INTERCEPTOR, APP_FILTER, APP_MIDDLEWARE } from "./pipeline/tokens.js";
8
8
  export const VelaFactory = {
9
9
  async create (rootModule, options = {}) {
10
10
  const container = new Container();
@@ -23,36 +23,7 @@ export const VelaFactory = {
23
23
  ComponentManager.init(container);
24
24
  const loader = new ModuleLoader(container, routeManager);
25
25
  loader.load(rootModule);
26
- const appGuards = loader.getAppProviderTokens(APP_GUARD);
27
- if (appGuards.length > 0) {
28
- routeManager.useGlobalGuardTokens(...appGuards);
29
- } else if (container.has(APP_GUARD)) {
30
- routeManager.useGlobalGuardTokens(APP_GUARD);
31
- }
32
- const appPipes = loader.getAppProviderTokens(APP_PIPE);
33
- if (appPipes.length > 0) {
34
- routeManager.useGlobalPipeTokens(...appPipes);
35
- } else if (container.has(APP_PIPE)) {
36
- routeManager.useGlobalPipeTokens(APP_PIPE);
37
- }
38
- const appInterceptors = loader.getAppProviderTokens(APP_INTERCEPTOR);
39
- if (appInterceptors.length > 0) {
40
- routeManager.useGlobalInterceptorTokens(...appInterceptors);
41
- } else if (container.has(APP_INTERCEPTOR)) {
42
- routeManager.useGlobalInterceptorTokens(APP_INTERCEPTOR);
43
- }
44
- const appFilters = loader.getAppProviderTokens(APP_FILTER);
45
- if (appFilters.length > 0) {
46
- routeManager.useGlobalFilterTokens(...appFilters);
47
- } else if (container.has(APP_FILTER)) {
48
- routeManager.useGlobalFilterTokens(APP_FILTER);
49
- }
50
- const appMiddleware = loader.getAppProviderTokens(APP_MIDDLEWARE);
51
- if (appMiddleware.length > 0) {
52
- routeManager.useGlobalMiddlewareTokens(...appMiddleware);
53
- } else if (container.has(APP_MIDDLEWARE)) {
54
- routeManager.useGlobalMiddlewareTokens(APP_MIDDLEWARE);
55
- }
26
+ bindAppProviders(routeManager, container, loader);
56
27
  routeManager.registerConsumerMiddleware(loader.getConsumerMiddlewareDefinitions());
57
28
  if (options.globalPrefix) {
58
29
  routeManager.setGlobalPrefix(options.globalPrefix);
@@ -73,4 +44,3 @@ export const VelaFactory = {
73
44
  return app;
74
45
  }
75
46
  };
76
- export const createApplication = VelaFactory.create.bind(VelaFactory);
@@ -1,6 +1,6 @@
1
1
  import type { AsyncModuleOptions, DynamicModule } from '../module/types';
2
2
  import type { HttpModuleOptions } from './fetch.types';
3
3
  export declare class HttpModule {
4
- static register(options?: HttpModuleOptions): DynamicModule;
5
- static registerAsync(options: AsyncModuleOptions<HttpModuleOptions>): DynamicModule;
4
+ static forRoot(options?: HttpModuleOptions): DynamicModule;
5
+ static forRootAsync(options: AsyncModuleOptions<HttpModuleOptions>): DynamicModule;
6
6
  }
@@ -8,7 +8,7 @@ import { createModuleRef } from "../module/decorators.js";
8
8
  import { Module } from "../module/decorators.js";
9
9
  import { HttpService, HTTP_MODULE_OPTIONS } from "./fetch.service.js";
10
10
  export class HttpModule {
11
- static register(options = {}) {
11
+ static forRoot(options = {}) {
12
12
  return {
13
13
  module: createModuleRef('HttpModule'),
14
14
  providers: [
@@ -24,7 +24,7 @@ export class HttpModule {
24
24
  ]
25
25
  };
26
26
  }
27
- static registerAsync(options) {
27
+ static forRootAsync(options) {
28
28
  return {
29
29
  module: createModuleRef('HttpModule'),
30
30
  imports: options.imports ?? [],
@@ -19,7 +19,9 @@ export class HealthCheckService {
19
19
  error: {},
20
20
  details: {}
21
21
  };
22
- throw new ServiceUnavailableException(result);
22
+ throw new ServiceUnavailableException({
23
+ ...result
24
+ });
23
25
  }
24
26
  const results = await Promise.allSettled(indicators.map((fn)=>fn()));
25
27
  const info = {};
@@ -55,7 +57,9 @@ export class HealthCheckService {
55
57
  details
56
58
  };
57
59
  if (status === 'error') {
58
- throw new ServiceUnavailableException(checkResult);
60
+ throw new ServiceUnavailableException({
61
+ ...checkResult
62
+ });
59
63
  }
60
64
  return checkResult;
61
65
  }