@velajs/vela 1.10.0 → 1.12.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 (77) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/dist/application.d.ts +8 -0
  3. package/dist/application.js +20 -0
  4. package/dist/container/container.d.ts +25 -0
  5. package/dist/container/container.js +67 -11
  6. package/dist/container/disposable.js +1 -1
  7. package/dist/cors/cors.module.d.ts +5 -5
  8. package/dist/cors/cors.module.js +26 -25
  9. package/dist/discovery/discoverable.decorator.d.ts +38 -0
  10. package/dist/discovery/discoverable.decorator.js +42 -0
  11. package/dist/discovery/discovery.service.d.ts +94 -0
  12. package/dist/discovery/discovery.service.js +166 -0
  13. package/dist/discovery/index.d.ts +2 -0
  14. package/dist/discovery/index.js +2 -0
  15. package/dist/entrypoint/entrypoint.registry.d.ts +40 -0
  16. package/dist/entrypoint/entrypoint.registry.js +106 -0
  17. package/dist/entrypoint/entrypoint.types.d.ts +41 -0
  18. package/dist/entrypoint/entrypoint.types.js +3 -0
  19. package/dist/entrypoint/execution-context.d.ts +15 -0
  20. package/dist/entrypoint/execution-context.js +21 -0
  21. package/dist/entrypoint/execution-scope.d.ts +21 -0
  22. package/dist/entrypoint/execution-scope.js +28 -0
  23. package/dist/entrypoint/index.d.ts +4 -0
  24. package/dist/entrypoint/index.js +4 -0
  25. package/dist/event-emitter/event-emitter.subscriber.d.ts +3 -3
  26. package/dist/event-emitter/event-emitter.subscriber.js +12 -29
  27. package/dist/factory/adapter.d.ts +36 -0
  28. package/dist/factory/adapter.js +18 -0
  29. package/dist/factory/bootstrap.js +10 -2
  30. package/dist/factory.d.ts +11 -1
  31. package/dist/factory.js +23 -2
  32. package/dist/http/handler-executor.js +22 -19
  33. package/dist/http/route-contributor.d.ts +68 -0
  34. package/dist/http/route-contributor.js +22 -0
  35. package/dist/http/route.manager.js +28 -19
  36. package/dist/i18n/i18n.module.js +4 -1
  37. package/dist/index.d.ts +12 -3
  38. package/dist/index.js +9 -2
  39. package/dist/internal.d.ts +0 -2
  40. package/dist/internal.js +0 -7
  41. package/dist/module/configurable-module.builder.d.ts +12 -0
  42. package/dist/module/configurable-module.builder.js +21 -114
  43. package/dist/module/define-module.d.ts +105 -0
  44. package/dist/module/define-module.js +227 -0
  45. package/dist/module/index.d.ts +3 -1
  46. package/dist/module/index.js +3 -1
  47. package/dist/module/lazy-provider.d.ts +65 -0
  48. package/dist/module/lazy-provider.js +111 -0
  49. package/dist/openapi/document.js +16 -15
  50. package/dist/pipeline/component.manager.d.ts +15 -9
  51. package/dist/pipeline/component.manager.js +32 -46
  52. package/dist/pipeline/index.d.ts +2 -0
  53. package/dist/pipeline/index.js +1 -0
  54. package/dist/pipeline/pipeline-runner.d.ts +31 -0
  55. package/dist/pipeline/pipeline-runner.js +40 -0
  56. package/dist/registry/metadata.registry.d.ts +9 -3
  57. package/dist/registry/metadata.registry.js +55 -25
  58. package/dist/schedule/schedule.module.d.ts +5 -0
  59. package/dist/schedule/schedule.module.js +18 -8
  60. package/dist/schedule/schedule.registry.d.ts +3 -3
  61. package/dist/schedule/schedule.registry.js +23 -44
  62. package/dist/schedule-node/schedule-node.module.d.ts +5 -0
  63. package/dist/schedule-node/schedule-node.module.js +20 -10
  64. package/dist/seeder/seeder.module.d.ts +8 -10
  65. package/dist/seeder/seeder.module.js +16 -15
  66. package/dist/websocket/index.d.ts +1 -1
  67. package/dist/websocket/index.js +1 -1
  68. package/dist/websocket/websocket.module.d.ts +20 -3
  69. package/dist/websocket/websocket.module.js +50 -39
  70. package/dist/websocket/websocket.tokens.d.ts +2 -1
  71. package/dist/websocket/websocket.tokens.js +3 -2
  72. package/dist/websocket/ws-dispatcher.d.ts +21 -9
  73. package/dist/websocket/ws-dispatcher.js +44 -38
  74. package/dist/websocket-node/register-gateways.js +3 -3
  75. package/package.json +1 -1
  76. package/dist/http/crud-bridge.d.ts +0 -90
  77. package/dist/http/crud-bridge.js +0 -25
@@ -5,25 +5,26 @@ function _ts_decorate(decorators, target, key, desc) {
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  }
7
7
  import { Module } from "../module/decorators.js";
8
+ import { defineModule } from "../module/define-module.js";
8
9
  import { stableHash } from "../module/stable-hash.js";
9
10
  import { SeederRegistry } from "./seeder.registry.js";
10
- export class SeederModule {
11
- /**
12
- * Register seeder classes as providers so they're discovered at bootstrap.
13
- * (Seeders declared in other modules' providers are also discovered — this is
14
- * just a convenience to co-locate them.)
15
- */ static forRoot(options = {}) {
16
- const seeders = options.seeders ?? [];
17
- return {
18
- module: SeederModule,
19
- key: stableHash({
20
- seeders: seeders.map((s)=>s.name)
21
- }),
11
+ const { ConfigurableModuleClass } = defineModule({
12
+ name: 'Seeder',
13
+ // Class references aren't value-hashable key on the seeder names, the same
14
+ // identity the hand-rolled forRoot used.
15
+ key: (options)=>stableHash({
16
+ seeders: (options.seeders ?? []).map((s)=>s.name)
17
+ }),
18
+ setup: ({ options })=>({
19
+ // Register seeder classes as providers so they're discovered at bootstrap.
20
+ // (Seeders declared in other modules' providers are also discovered — this
21
+ // is just a convenience to co-locate them.)
22
22
  providers: [
23
- ...seeders
23
+ ...options.seeders ?? []
24
24
  ]
25
- };
26
- }
25
+ })
26
+ });
27
+ export class SeederModule extends ConfigurableModuleClass {
27
28
  }
28
29
  SeederModule = _ts_decorate([
29
30
  Module({
@@ -1,7 +1,7 @@
1
1
  export { WebSocketGateway, SubscribeMessage, MessageBody, ConnectedSocket, WebSocketServer, } from './websocket.decorators';
2
2
  export { WebSocketModule } from './websocket.module';
3
3
  export type { WebSocketModuleOptions } from './websocket.module';
4
- export { WsDispatcher } from './ws-dispatcher';
4
+ export { WsDispatcher, type WsEntrypointMeta } from './ws-dispatcher';
5
5
  export { WsServerImpl, BroadcastOperatorImpl } from './ws-server';
6
6
  export { InMemoryRoomRegistry, local } from './ws-sync';
7
7
  export type { SyncDriver, RoomRegistry } from './ws-sync';
@@ -2,7 +2,7 @@
2
2
  export { WebSocketGateway, SubscribeMessage, MessageBody, ConnectedSocket, WebSocketServer } from "./websocket.decorators.js";
3
3
  // Module
4
4
  export { WebSocketModule } from "./websocket.module.js";
5
- // Dispatcher (injected/called by transports)
5
+ // Dispatcher (injected/called by transports) + the 'websocket' entrypoint meta
6
6
  export { WsDispatcher } from "./ws-dispatcher.js";
7
7
  // Server handle + rooms + sync
8
8
  export { WsServerImpl, BroadcastOperatorImpl } from "./ws-server.js";
@@ -1,4 +1,3 @@
1
- import type { DynamicModule } from '../module/types';
2
1
  import { type RoomRegistry, type SyncDriver } from './ws-sync';
3
2
  export interface WebSocketModuleOptions {
4
3
  /**
@@ -13,6 +12,24 @@ export interface WebSocketModuleOptions {
13
12
  */
14
13
  registry?: RoomRegistry;
15
14
  }
16
- export declare class WebSocketModule {
17
- static forRoot(options?: WebSocketModuleOptions): DynamicModule;
15
+ /**
16
+ * Registers the WebSocket gateway machinery: the message dispatcher (discovers
17
+ * `@WebSocketGateway` classes via DiscoveryService at bootstrap), the room
18
+ * registry, the sync driver, and the `@WebSocketServer()`-injected server
19
+ * handle.
20
+ *
21
+ * Construction lives in chained provider factories — registry → driver
22
+ * (bound to THAT registry) → server — so the single shared registry instance
23
+ * is preserved and everything materializes at bootstrap's eager
24
+ * instantiation, before any lifecycle hook or message dispatch.
25
+ *
26
+ * The instance key derives from the sync driver kind: two `forRoot()` calls
27
+ * with the same driver kind dedup (HMR-idempotent); pass an explicit `key`
28
+ * to run multiple same-kind instances side by side.
29
+ */
30
+ declare const ConfigurableModuleClass: import("..").ConfigurableModuleClassType<WebSocketModuleOptions, "forRoot", "create", {
31
+ isGlobal?: boolean;
32
+ }>;
33
+ export declare class WebSocketModule extends ConfigurableModuleClass {
18
34
  }
35
+ export {};
@@ -1,52 +1,63 @@
1
+ import { defineModule } from "../module/define-module.js";
1
2
  import { WsDispatcher } from "./ws-dispatcher.js";
2
3
  import { WsServerImpl } from "./ws-server.js";
3
4
  import { InMemoryRoomRegistry, local } from "./ws-sync.js";
4
5
  import { WS_MODULE_OPTIONS, WS_ROOM_REGISTRY, WS_SERVER, WS_SYNC_DRIVER } from "./websocket.tokens.js";
5
6
  /**
6
7
  * Registers the WebSocket gateway machinery: the message dispatcher (discovers
7
- * `@WebSocketGateway` classes at bootstrap), the room registry, the sync driver,
8
- * and the `@WebSocketServer()`-injected server handle. Mirrors
9
- * `CacheModule.forRoot` / `ScheduleModule.forRoot`.
10
- */ // forRoot() carries non-serializable per-call state (a fresh registry/driver/
11
- // server), so each call is its own module instance. A unique key prevents the
12
- // module loader from deduping two distinct configurations into one (which would
13
- // silently discard the second registry/server).
14
- let instanceCounter = 0;
15
- export class WebSocketModule {
16
- static forRoot(options = {}) {
17
- const registry = options.registry ?? new InMemoryRoomRegistry();
18
- const driver = options.sync ?? local();
19
- driver.bind(registry);
20
- const server = new WsServerImpl(driver);
21
- const providers = [
22
- {
23
- provide: WS_MODULE_OPTIONS,
24
- useValue: options
25
- },
26
- {
27
- provide: WS_ROOM_REGISTRY,
28
- useValue: registry
29
- },
30
- {
31
- provide: WS_SYNC_DRIVER,
32
- useValue: driver
33
- },
34
- {
35
- provide: WS_SERVER,
36
- useValue: server
37
- },
38
- WsDispatcher
39
- ];
40
- return {
41
- module: WebSocketModule,
42
- key: `${driver.kind}#${instanceCounter += 1}`,
43
- providers,
8
+ * `@WebSocketGateway` classes via DiscoveryService at bootstrap), the room
9
+ * registry, the sync driver, and the `@WebSocketServer()`-injected server
10
+ * handle.
11
+ *
12
+ * Construction lives in chained provider factories registry driver
13
+ * (bound to THAT registry) server so the single shared registry instance
14
+ * is preserved and everything materializes at bootstrap's eager
15
+ * instantiation, before any lifecycle hook or message dispatch.
16
+ *
17
+ * The instance key derives from the sync driver kind: two `forRoot()` calls
18
+ * with the same driver kind dedup (HMR-idempotent); pass an explicit `key`
19
+ * to run multiple same-kind instances side by side.
20
+ */ const { ConfigurableModuleClass } = defineModule({
21
+ name: 'WebSocket',
22
+ optionsToken: WS_MODULE_OPTIONS,
23
+ key: (options)=>`ws#${options.sync?.kind ?? 'local'}`,
24
+ setup: ({ OPTIONS })=>({
25
+ providers: [
26
+ {
27
+ provide: WS_ROOM_REGISTRY,
28
+ useFactory: (o)=>o.registry ?? new InMemoryRoomRegistry(),
29
+ inject: [
30
+ OPTIONS
31
+ ]
32
+ },
33
+ {
34
+ provide: WS_SYNC_DRIVER,
35
+ useFactory: (o, registry)=>{
36
+ const driver = o.sync ?? local();
37
+ driver.bind(registry);
38
+ return driver;
39
+ },
40
+ inject: [
41
+ OPTIONS,
42
+ WS_ROOM_REGISTRY
43
+ ]
44
+ },
45
+ {
46
+ provide: WS_SERVER,
47
+ useFactory: (driver)=>new WsServerImpl(driver),
48
+ inject: [
49
+ WS_SYNC_DRIVER
50
+ ]
51
+ },
52
+ WsDispatcher
53
+ ],
44
54
  exports: [
45
55
  WS_SERVER,
46
56
  WS_SYNC_DRIVER,
47
57
  WS_ROOM_REGISTRY,
48
58
  WsDispatcher
49
59
  ]
50
- };
51
- }
60
+ })
61
+ });
62
+ export class WebSocketModule extends ConfigurableModuleClass {
52
63
  }
@@ -1,4 +1,5 @@
1
1
  import { InjectionToken } from '../container/types';
2
+ import type { WebSocketModuleOptions } from './websocket.module';
2
3
  import type { RoomRegistry, SyncDriver } from './ws-sync';
3
4
  import type { WsServer } from './websocket.types';
4
5
  export declare const WS_GATEWAY_METADATA = "vela:ws-gateway";
@@ -19,4 +20,4 @@ export declare const WS_SERVER: InjectionToken<WsServer>;
19
20
  export declare const WS_SYNC_DRIVER: InjectionToken<SyncDriver>;
20
21
  /** The local room-membership registry a transport reads/writes. */
21
22
  export declare const WS_ROOM_REGISTRY: InjectionToken<RoomRegistry>;
22
- export declare const WS_MODULE_OPTIONS = "vela:ws-module-options";
23
+ export declare const WS_MODULE_OPTIONS: InjectionToken<WebSocketModuleOptions>;
@@ -16,5 +16,6 @@ export const WsParamType = {
16
16
  */ export const WS_SERVER = new InjectionToken('WS_SERVER');
17
17
  /** The active cross-instance sync driver (`local()` by default). */ export const WS_SYNC_DRIVER = new InjectionToken('WS_SYNC_DRIVER');
18
18
  /** The local room-membership registry a transport reads/writes. */ export const WS_ROOM_REGISTRY = new InjectionToken('WS_ROOM_REGISTRY');
19
- // forRoot() options carrier.
20
- export const WS_MODULE_OPTIONS = 'vela:ws-module-options';
19
+ // forRoot() options carrier — a typed InjectionToken like every other module
20
+ // options token (the raw-string form was the odd one out).
21
+ export const WS_MODULE_OPTIONS = new InjectionToken('WS_MODULE_OPTIONS');
@@ -1,24 +1,37 @@
1
1
  import { Container } from '../container/container';
2
+ import { DiscoveryService } from '../discovery/discovery.service';
3
+ import type { ContributesEntrypoints, Entrypoint } from '../entrypoint/entrypoint.types';
2
4
  import { RouteManager } from '../http/route.manager';
3
5
  import type { OnApplicationBootstrap } from '../lifecycle/index';
4
6
  import type { WsClient, WsServer } from './websocket.types';
7
+ /** Metadata carried by each `'websocket'` entrypoint a transport consumes. */
8
+ export interface WsEntrypointMeta {
9
+ /** The gateway's route path (from `@WebSocketGateway({ path })`). */
10
+ path: string;
11
+ /** The dispatcher that routes frames for this gateway. */
12
+ dispatcher: WsDispatcher;
13
+ }
5
14
  /**
6
- * Discovers `@WebSocketGateway` classes at bootstrap (same `container.getTokens()`
7
- * scan as `EventEmitterSubscriber` / `ScheduleRegistry`) and routes inbound
8
- * messages to `@SubscribeMessage` handlers. Transports call `handleOpen`,
9
- * `dispatchMessage`, `handleClose`, and `handleError`; the guard → pipe →
10
- * interceptor filter pipeline (including `APP_*` global components) is reused
11
- * from `ComponentManager`.
15
+ * Discovers `@WebSocketGateway` classes at bootstrap (via `DiscoveryService`)
16
+ * and routes inbound messages to `@SubscribeMessage` handlers. Transports read
17
+ * the gateways from `app.entrypoints.ofKind('websocket')` (this dispatcher
18
+ * contributes them) and call `handleOpen`, `dispatchMessage`, `handleClose`,
19
+ * and `handleError`; the guard pipe interceptor filter pipeline
20
+ * (including `APP_*` global components) runs through the shared
21
+ * `PipelineRunner`.
12
22
  */
13
- export declare class WsDispatcher implements OnApplicationBootstrap {
23
+ export declare class WsDispatcher implements OnApplicationBootstrap, ContributesEntrypoints {
14
24
  private readonly container;
25
+ private readonly discovery;
15
26
  private readonly server?;
16
27
  private readonly routeManager?;
17
28
  private readonly gateways;
18
- constructor(container: Container, server?: WsServer | undefined, routeManager?: RouteManager | undefined);
29
+ constructor(container: Container, discovery: DiscoveryService, server?: WsServer | undefined, routeManager?: RouteManager | undefined);
19
30
  /** Paths of every discovered `@WebSocketGateway` — used by transports to register routes. */
20
31
  get gatewayPaths(): string[];
21
32
  onApplicationBootstrap(): Promise<void>;
33
+ /** One `'websocket'` entrypoint per discovered gateway (authoritative). */
34
+ collectEntrypoints(): Entrypoint<WsEntrypointMeta>[];
22
35
  handleOpen(path: string, client: WsClient): Promise<void>;
23
36
  handleClose(path: string, client: WsClient, _code: number, _reason: string): Promise<void>;
24
37
  handleError(path: string, _client: WsClient, err: unknown): Promise<void>;
@@ -28,6 +41,5 @@ export declare class WsDispatcher implements OnApplicationBootstrap {
28
41
  private trySend;
29
42
  private reportBootstrapError;
30
43
  private parse;
31
- private resolveGateway;
32
44
  private buildGatewayEntry;
33
45
  }
@@ -14,10 +14,12 @@ function _ts_param(paramIndex, decorator) {
14
14
  }
15
15
  import { Container } from "../container/container.js";
16
16
  import { Inject, Injectable, Optional } from "../container/decorators.js";
17
+ import { DiscoveryService } from "../discovery/discovery.service.js";
17
18
  import { instantiateMany } from "../http/instantiate.js";
18
19
  import { RouteManager } from "../http/route.manager.js";
19
20
  import { ComponentManager } from "../pipeline/component.manager.js";
20
21
  import { shouldFilterCatch } from "../pipeline/decorators.js";
22
+ import { PipelineRunner } from "../pipeline/pipeline-runner.js";
21
23
  import { MetadataRegistry } from "../registry/metadata.registry.js";
22
24
  import { resolveWsArgs } from "./ws-argument-resolver.js";
23
25
  import { buildWsExecutionContext } from "./ws-execution-context.js";
@@ -37,11 +39,13 @@ function isWsResponse(x) {
37
39
  }
38
40
  export class WsDispatcher {
39
41
  container;
42
+ discovery;
40
43
  server;
41
44
  routeManager;
42
45
  gateways = new Map();
43
- constructor(container, server, routeManager){
46
+ constructor(container, discovery, server, routeManager){
44
47
  this.container = container;
48
+ this.discovery = discovery;
45
49
  this.server = server;
46
50
  this.routeManager = routeManager;
47
51
  }
@@ -51,17 +55,15 @@ export class WsDispatcher {
51
55
  ];
52
56
  }
53
57
  async onApplicationBootstrap() {
54
- for (const token of this.container.getTokens()){
55
- if (typeof token !== 'function') continue;
56
- const options = MetadataRegistry.getCustomClassMeta(token, WS_GATEWAY_METADATA);
57
- if (!options) continue;
58
- const instance = this.resolveGateway(token);
59
- if (!instance) continue;
60
- const entry = this.buildGatewayEntry(token, options, instance);
58
+ for (const found of this.discovery.providersWithMeta(WS_GATEWAY_METADATA)){
59
+ if (!found.instance) continue;
60
+ const gatewayClass = found.metatype;
61
+ const instance = found.instance;
62
+ const entry = this.buildGatewayEntry(gatewayClass, found.meta, instance);
61
63
  // Two gateways on the same path (or both defaulting to '') would silently
62
64
  // overwrite each other in the routing Map — surface it instead.
63
65
  if (this.gateways.has(entry.path)) {
64
- const msg = `[vela] duplicate @WebSocketGateway path '${entry.path}' ` + `(${token.name}); keeping the first. Give each gateway a distinct path.`;
66
+ const msg = `[vela] duplicate @WebSocketGateway path '${entry.path}' ` + `(${gatewayClass.name}); keeping the first. Give each gateway a distinct path.`;
65
67
  if (this.container.getDiagnostics() === 'throw') throw new Error(msg);
66
68
  console.warn(msg);
67
69
  continue;
@@ -71,11 +73,24 @@ export class WsDispatcher {
71
73
  try {
72
74
  await instance.afterInit(this.server);
73
75
  } catch (err) {
74
- this.reportBootstrapError(token.name, err);
76
+ this.reportBootstrapError(gatewayClass.name, err);
75
77
  }
76
78
  }
77
79
  }
78
80
  }
81
+ /** One `'websocket'` entrypoint per discovered gateway (authoritative). */ collectEntrypoints() {
82
+ return [
83
+ ...this.gateways.values()
84
+ ].map((entry)=>({
85
+ kind: 'websocket',
86
+ token: entry.gatewayClass,
87
+ instance: entry.instance,
88
+ meta: {
89
+ path: entry.path,
90
+ dispatcher: this
91
+ }
92
+ }));
93
+ }
79
94
  async handleOpen(path, client) {
80
95
  const entry = this.gateways.get(path);
81
96
  if (entry && hasHandleConnection(entry.instance)) {
@@ -131,15 +146,17 @@ export class WsDispatcher {
131
146
  ...instantiateMany(globals?.filters ?? [], this.container)
132
147
  ];
133
148
  try {
134
- for (const guard of guards){
135
- if (!await guard.canActivate(ctx)) {
136
- throw new WsException('Forbidden');
137
- }
138
- }
139
- const args = await resolveWsArgs(handler.paramMeta, client, message.data, pipes, handler.paramTypes);
140
149
  const method = entry.instance[handler.methodName];
141
- const core = async ()=>method.apply(entry.instance, args);
142
- const result = await ComponentManager.runInterceptorChain(interceptors, ctx, core);
150
+ // Guards args + pipes → interceptor chain → handler, via the shared
151
+ // runner (WS keeps guards-first, unlike HTTP's args-before-guards).
152
+ const result = await PipelineRunner.run({
153
+ context: ctx,
154
+ guards,
155
+ interceptors,
156
+ resolveArgs: ()=>resolveWsArgs(handler.paramMeta, client, message.data, pipes, handler.paramTypes),
157
+ invoke: async (args)=>method.apply(entry.instance, args),
158
+ onGuardReject: ()=>new WsException('Forbidden')
159
+ });
143
160
  this.reply(client, message, result);
144
161
  } catch (error) {
145
162
  await this.runFilters(client, message, error, filters, ctx);
@@ -193,19 +210,6 @@ export class WsDispatcher {
193
210
  }
194
211
  return parsed;
195
212
  }
196
- resolveGateway(token) {
197
- try {
198
- return this.container.resolve(token);
199
- } catch (err) {
200
- const mode = this.container.getDiagnostics();
201
- if (mode === 'throw') throw err;
202
- if (mode === 'log') {
203
- const name = typeof token === 'function' ? token.name : String(token);
204
- console.warn(`[vela] websocket gateway discovery: cannot resolve ${name}:`, err);
205
- }
206
- return undefined;
207
- }
208
- }
209
213
  buildGatewayEntry(gatewayClass, options, instance) {
210
214
  const ctor = gatewayClass;
211
215
  const subs = MetadataRegistry.getCustomClassMeta(ctor, WS_SUBSCRIBE_METADATA) ?? [];
@@ -220,12 +224,12 @@ export class WsDispatcher {
220
224
  methodName,
221
225
  paramMeta,
222
226
  paramTypes,
223
- guards: ComponentManager.getComponents('guard', ctor, methodName),
224
- pipes: ComponentManager.getComponents('pipe', ctor, methodName),
225
- interceptors: ComponentManager.getComponents('interceptor', ctor, methodName),
227
+ guards: ComponentManager.getScopedComponents('guard', ctor, methodName),
228
+ pipes: ComponentManager.getScopedComponents('pipe', ctor, methodName),
229
+ interceptors: ComponentManager.getScopedComponents('interceptor', ctor, methodName),
226
230
  // Handler → controller → global, so the closest filter runs first (mirrors HandlerExecutor).
227
231
  filters: [
228
- ...ComponentManager.getComponents('filter', ctor, methodName)
232
+ ...ComponentManager.getScopedComponents('filter', ctor, methodName)
229
233
  ].reverse()
230
234
  });
231
235
  }
@@ -240,13 +244,15 @@ export class WsDispatcher {
240
244
  WsDispatcher = _ts_decorate([
241
245
  Injectable(),
242
246
  _ts_param(0, Inject(Container)),
243
- _ts_param(1, Optional()),
244
- _ts_param(1, Inject(WS_SERVER)),
247
+ _ts_param(1, Inject(DiscoveryService)),
245
248
  _ts_param(2, Optional()),
246
- _ts_param(2, Inject(RouteManager)),
249
+ _ts_param(2, Inject(WS_SERVER)),
250
+ _ts_param(3, Optional()),
251
+ _ts_param(3, Inject(RouteManager)),
247
252
  _ts_metadata("design:type", Function),
248
253
  _ts_metadata("design:paramtypes", [
249
254
  typeof Container === "undefined" ? Object : Container,
255
+ typeof DiscoveryService === "undefined" ? Object : DiscoveryService,
250
256
  typeof WsServer === "undefined" ? Object : WsServer,
251
257
  typeof RouteManager === "undefined" ? Object : RouteManager
252
258
  ])
@@ -1,4 +1,4 @@
1
- import { WsDispatcher, WS_ROOM_REGISTRY } from "../websocket/index.js";
1
+ import { WS_ROOM_REGISTRY } from "../websocket/index.js";
2
2
  import { NodeWsClient } from "./node-ws-client.js";
3
3
  function toText(data) {
4
4
  if (typeof data === 'string') return data;
@@ -24,9 +24,9 @@ function toText(data) {
24
24
  * ```
25
25
  */ export function registerWebSocketGateways(app, upgradeWebSocket) {
26
26
  const hono = app.getHonoApp();
27
- const dispatcher = app.get(WsDispatcher);
28
27
  const registry = app.get(WS_ROOM_REGISTRY);
29
- for (const path of dispatcher.gatewayPaths){
28
+ for (const { meta } of app.entrypoints.ofKind('websocket')){
29
+ const { path, dispatcher } = meta;
30
30
  hono.get(path, upgradeWebSocket((c)=>{
31
31
  const roomId = c.req.param('id') ?? path;
32
32
  let client;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velajs/vela",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "description": "NestJS-compatible framework for edge runtimes, powered by Hono",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,90 +0,0 @@
1
- import type { Hono } from 'hono';
2
- import type { Type } from '../container/types';
3
- import type { CanActivate } from '../pipeline/types';
4
- import type { OpenApiPathItem } from '../openapi/types';
5
- /**
6
- * Context passed to {@link CrudBridge.buildRoutes} when mounting CRUD
7
- * sub-applications for a `@Crud()`-decorated controller.
8
- *
9
- * The bridge receives the framework's already-resolved view of the world so
10
- * it can produce identical routing behavior to a hand-written controller:
11
- * the same global prefix is prepended to every path, the same global guard
12
- * instances run before any generated handler, and the same path-joining
13
- * helper composes URL segments without double slashes.
14
- */
15
- export interface CrudBridgeRouteContext {
16
- /** Application-wide path prefix (e.g. `/api`), already normalized. */
17
- globalPrefix: string;
18
- /** Global guard instances, pre-resolved from the root container. */
19
- globalGuards: CanActivate[];
20
- /** Path joiner used by the framework — handles slash normalization. */
21
- joinPaths: (...parts: string[]) => string;
22
- }
23
- /**
24
- * Context passed to {@link CrudBridge.buildOpenApiPaths} when contributing
25
- * OpenAPI path items for a `@Crud()`-decorated controller.
26
- *
27
- * The bridge is responsible for producing fully-qualified path keys that
28
- * match the routes it actually mounts at request time. The framework supplies
29
- * the prefixes so the bridge does not have to re-derive them.
30
- */
31
- export interface CrudBridgeOpenApiContext {
32
- /** Application-wide path prefix (e.g. `/api`), already normalized. */
33
- globalPrefix: string;
34
- /** Controller-level prefix as registered with `@Controller(...)`. */
35
- controllerPrefix: string;
36
- }
37
- /**
38
- * Contract between `@velajs/vela` and a CRUD route generator (typically
39
- * `@velajs/crud`). The bridge inverts the previous dynamic-import pattern,
40
- * which was incompatible with Cloudflare Workers because esbuild leaves
41
- * `await import(variable)` calls as runtime imports the Workers loader
42
- * cannot resolve.
43
- *
44
- * A consumer registers an implementation once via {@link registerCrudBridge}
45
- * — usually as a side effect of importing `@velajs/crud` — and the framework
46
- * delegates both route building (at bootstrap) and OpenAPI generation
47
- * (at document creation) to it.
48
- */
49
- export interface CrudBridge {
50
- /**
51
- * Mount the CRUD sub-app for a controller with `vela:crud` metadata.
52
- *
53
- * Called during {@link RouteManager.build} after all `@Get`/`@Post`/etc.
54
- * routes have been registered. The bridge is expected to attach generated
55
- * routes (list, read, create, update, delete, …) onto the supplied Hono
56
- * app under `ctx.globalPrefix + prefix`.
57
- */
58
- buildRoutes(app: Hono, controller: Type, prefix: string, crudConfig: unknown, ctx: CrudBridgeRouteContext): Promise<void>;
59
- /**
60
- * Contribute OpenAPI path items for a `@Crud()`-decorated controller.
61
- *
62
- * Called once per CRUD controller from `createOpenApiDocument` after the
63
- * standard `@Get`/`@Post`/etc. paths have been collected. The returned
64
- * record maps full OpenAPI path strings (e.g. `/api/users/{id}`) to path
65
- * items, and is merged into the document — entries already present from
66
- * hand-written handlers are preserved verb-by-verb.
67
- */
68
- buildOpenApiPaths(controller: Type, crudConfig: unknown, ctx: CrudBridgeOpenApiContext): Record<string, OpenApiPathItem>;
69
- }
70
- /**
71
- * Register a {@link CrudBridge} implementation. Intended to be called once at
72
- * import time by the CRUD package; subsequent calls overwrite the previous
73
- * registration (last-writer-wins). This is deliberate so test harnesses can
74
- * swap in mocks without a teardown step on the global state.
75
- */
76
- export declare function registerCrudBridge(bridge: CrudBridge): void;
77
- /**
78
- * Return the currently registered {@link CrudBridge}, or `undefined` if no
79
- * bridge has been registered. Consumers using `@Crud()` without a registered
80
- * bridge will receive a clear install-it error from the framework at build
81
- * time — see `RouteManager.build`.
82
- */
83
- export declare function getCrudBridge(): CrudBridge | undefined;
84
- /**
85
- * Reset the global bridge registration. Test-only — not exported from
86
- * `@velajs/vela/internal`. Use this in `beforeEach` to keep cases isolated.
87
- *
88
- * @internal
89
- */
90
- export declare function _resetCrudBridge(): void;
@@ -1,25 +0,0 @@
1
- let registered;
2
- /**
3
- * Register a {@link CrudBridge} implementation. Intended to be called once at
4
- * import time by the CRUD package; subsequent calls overwrite the previous
5
- * registration (last-writer-wins). This is deliberate so test harnesses can
6
- * swap in mocks without a teardown step on the global state.
7
- */ export function registerCrudBridge(bridge) {
8
- registered = bridge;
9
- }
10
- /**
11
- * Return the currently registered {@link CrudBridge}, or `undefined` if no
12
- * bridge has been registered. Consumers using `@Crud()` without a registered
13
- * bridge will receive a clear install-it error from the framework at build
14
- * time — see `RouteManager.build`.
15
- */ export function getCrudBridge() {
16
- return registered;
17
- }
18
- /**
19
- * Reset the global bridge registration. Test-only — not exported from
20
- * `@velajs/vela/internal`. Use this in `beforeEach` to keep cases isolated.
21
- *
22
- * @internal
23
- */ export function _resetCrudBridge() {
24
- registered = undefined;
25
- }