@velajs/vela 1.9.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 (161) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/dist/__tests__/workers-als/entry-als.d.ts +9 -0
  3. package/dist/application.d.ts +18 -0
  4. package/dist/application.js +45 -0
  5. package/dist/cache/cache.module.d.ts +5 -7
  6. package/dist/cache/cache.module.js +44 -66
  7. package/dist/cache/cache.types.d.ts +26 -0
  8. package/dist/cache/cache.types.js +1 -1
  9. package/dist/cache/index.d.ts +2 -1
  10. package/dist/cache/index.js +1 -0
  11. package/dist/cache/tiered-cache.store.d.ts +19 -0
  12. package/dist/cache/tiered-cache.store.js +40 -0
  13. package/dist/config/config.module.d.ts +6 -5
  14. package/dist/config/config.module.js +46 -49
  15. package/dist/container/container.d.ts +58 -0
  16. package/dist/container/container.js +223 -18
  17. package/dist/container/disposable.d.ts +5 -0
  18. package/dist/container/disposable.js +33 -0
  19. package/dist/container/types.d.ts +8 -0
  20. package/dist/cors/cors.module.d.ts +5 -5
  21. package/dist/cors/cors.module.js +26 -25
  22. package/dist/discovery/discoverable.decorator.d.ts +38 -0
  23. package/dist/discovery/discoverable.decorator.js +42 -0
  24. package/dist/discovery/discovery.service.d.ts +94 -0
  25. package/dist/discovery/discovery.service.js +166 -0
  26. package/dist/discovery/index.d.ts +2 -0
  27. package/dist/discovery/index.js +2 -0
  28. package/dist/entrypoint/entrypoint.registry.d.ts +40 -0
  29. package/dist/entrypoint/entrypoint.registry.js +106 -0
  30. package/dist/entrypoint/entrypoint.types.d.ts +41 -0
  31. package/dist/entrypoint/entrypoint.types.js +3 -0
  32. package/dist/entrypoint/execution-context.d.ts +15 -0
  33. package/dist/entrypoint/execution-context.js +21 -0
  34. package/dist/entrypoint/execution-scope.d.ts +21 -0
  35. package/dist/entrypoint/execution-scope.js +28 -0
  36. package/dist/entrypoint/index.d.ts +4 -0
  37. package/dist/entrypoint/index.js +4 -0
  38. package/dist/event-emitter/event-emitter.subscriber.d.ts +3 -3
  39. package/dist/event-emitter/event-emitter.subscriber.js +12 -29
  40. package/dist/factory/adapter.d.ts +36 -0
  41. package/dist/factory/adapter.js +18 -0
  42. package/dist/factory/bootstrap.js +20 -2
  43. package/dist/factory.d.ts +11 -1
  44. package/dist/factory.js +23 -2
  45. package/dist/fetch/fetch.module.d.ts +5 -6
  46. package/dist/fetch/fetch.module.js +9 -42
  47. package/dist/http/ambient.d.ts +17 -0
  48. package/dist/http/ambient.js +44 -0
  49. package/dist/http/execution-context.js +4 -1
  50. package/dist/http/handler-executor.js +22 -19
  51. package/dist/http/index.d.ts +1 -0
  52. package/dist/http/index.js +1 -0
  53. package/dist/http/route-contributor.d.ts +68 -0
  54. package/dist/http/route-contributor.js +22 -0
  55. package/dist/http/route.manager.d.ts +19 -0
  56. package/dist/http/route.manager.js +112 -19
  57. package/dist/i18n/deep-merge.d.ts +2 -0
  58. package/dist/i18n/deep-merge.js +15 -0
  59. package/dist/i18n/i18n.middleware.d.ts +15 -0
  60. package/dist/i18n/i18n.middleware.js +94 -0
  61. package/dist/i18n/i18n.module.d.ts +13 -0
  62. package/dist/i18n/i18n.module.js +64 -0
  63. package/dist/i18n/i18n.options.d.ts +30 -0
  64. package/dist/i18n/i18n.options.js +16 -0
  65. package/dist/i18n/i18n.service.d.ts +20 -0
  66. package/dist/i18n/i18n.service.js +49 -0
  67. package/dist/i18n/i18n.tokens.d.ts +6 -0
  68. package/dist/i18n/i18n.tokens.js +3 -0
  69. package/dist/i18n/i18n.types.d.ts +6 -0
  70. package/dist/i18n/i18n.types.js +1 -0
  71. package/dist/i18n/index.d.ts +10 -0
  72. package/dist/i18n/index.js +8 -0
  73. package/dist/i18n/message-loader.service.d.ts +21 -0
  74. package/dist/i18n/message-loader.service.js +90 -0
  75. package/dist/i18n/message-registry.d.ts +16 -0
  76. package/dist/i18n/message-registry.js +49 -0
  77. package/dist/index.d.ts +17 -5
  78. package/dist/index.js +14 -3
  79. package/dist/internal.d.ts +0 -2
  80. package/dist/internal.js +0 -7
  81. package/dist/module/configurable-module.builder.d.ts +56 -0
  82. package/dist/module/configurable-module.builder.js +84 -0
  83. package/dist/module/configurable-module.types.d.ts +93 -0
  84. package/dist/module/configurable-module.types.js +5 -0
  85. package/dist/module/define-module.d.ts +105 -0
  86. package/dist/module/define-module.js +227 -0
  87. package/dist/module/index.d.ts +4 -0
  88. package/dist/module/index.js +3 -0
  89. package/dist/module/lazy-provider.d.ts +65 -0
  90. package/dist/module/lazy-provider.js +111 -0
  91. package/dist/openapi/document.js +16 -15
  92. package/dist/pipeline/component.manager.d.ts +15 -9
  93. package/dist/pipeline/component.manager.js +32 -46
  94. package/dist/pipeline/index.d.ts +2 -0
  95. package/dist/pipeline/index.js +1 -0
  96. package/dist/pipeline/pipeline-runner.d.ts +31 -0
  97. package/dist/pipeline/pipeline-runner.js +40 -0
  98. package/dist/pipeline/types.d.ts +21 -4
  99. package/dist/registry/metadata.registry.d.ts +24 -18
  100. package/dist/registry/metadata.registry.js +137 -49
  101. package/dist/schedule/schedule.module.d.ts +5 -0
  102. package/dist/schedule/schedule.module.js +18 -8
  103. package/dist/schedule/schedule.registry.d.ts +3 -3
  104. package/dist/schedule/schedule.registry.js +23 -44
  105. package/dist/schedule-node/schedule-node.module.d.ts +5 -0
  106. package/dist/schedule-node/schedule-node.module.js +20 -10
  107. package/dist/seeder/index.d.ts +5 -0
  108. package/dist/seeder/index.js +4 -0
  109. package/dist/seeder/seeder.decorator.d.ts +7 -0
  110. package/dist/seeder/seeder.decorator.js +15 -0
  111. package/dist/seeder/seeder.module.d.ts +16 -0
  112. package/dist/seeder/seeder.module.js +41 -0
  113. package/dist/seeder/seeder.registry.d.ts +23 -0
  114. package/dist/seeder/seeder.registry.js +83 -0
  115. package/dist/seeder/seeder.tokens.d.ts +2 -0
  116. package/dist/seeder/seeder.tokens.js +1 -0
  117. package/dist/seeder/seeder.types.d.ts +20 -0
  118. package/dist/seeder/seeder.types.js +1 -0
  119. package/dist/storage/index.d.ts +4 -0
  120. package/dist/storage/index.js +2 -0
  121. package/dist/storage/path-template.d.ts +9 -0
  122. package/dist/storage/path-template.js +20 -0
  123. package/dist/storage/signed-url.d.ts +12 -0
  124. package/dist/storage/signed-url.js +55 -0
  125. package/dist/storage/storage.types.d.ts +41 -0
  126. package/dist/storage/storage.types.js +5 -0
  127. package/dist/throttler/throttler.module.d.ts +5 -8
  128. package/dist/throttler/throttler.module.js +27 -55
  129. package/dist/websocket/index.d.ts +12 -0
  130. package/dist/websocket/index.js +16 -0
  131. package/dist/websocket/websocket.decorators.d.ts +33 -0
  132. package/dist/websocket/websocket.decorators.js +58 -0
  133. package/dist/websocket/websocket.module.d.ts +35 -0
  134. package/dist/websocket/websocket.module.js +63 -0
  135. package/dist/websocket/websocket.tokens.d.ts +23 -0
  136. package/dist/websocket/websocket.tokens.js +21 -0
  137. package/dist/websocket/websocket.types.d.ts +86 -0
  138. package/dist/websocket/websocket.types.js +4 -0
  139. package/dist/websocket/ws-argument-resolver.d.ts +13 -0
  140. package/dist/websocket/ws-argument-resolver.js +40 -0
  141. package/dist/websocket/ws-dispatcher.d.ts +45 -0
  142. package/dist/websocket/ws-dispatcher.js +259 -0
  143. package/dist/websocket/ws-exception.d.ts +16 -0
  144. package/dist/websocket/ws-exception.js +32 -0
  145. package/dist/websocket/ws-execution-context.d.ts +9 -0
  146. package/dist/websocket/ws-execution-context.js +24 -0
  147. package/dist/websocket/ws-server.d.ts +31 -0
  148. package/dist/websocket/ws-server.js +74 -0
  149. package/dist/websocket/ws-sync.d.ts +47 -0
  150. package/dist/websocket/ws-sync.js +74 -0
  151. package/dist/websocket-node/index.d.ts +4 -0
  152. package/dist/websocket-node/index.js +5 -0
  153. package/dist/websocket-node/node-ws-client.d.ts +24 -0
  154. package/dist/websocket-node/node-ws-client.js +49 -0
  155. package/dist/websocket-node/redis-sync.d.ts +33 -0
  156. package/dist/websocket-node/redis-sync.js +56 -0
  157. package/dist/websocket-node/register-gateways.d.ts +20 -0
  158. package/dist/websocket-node/register-gateways.js +61 -0
  159. package/package.json +31 -1
  160. package/dist/http/crud-bridge.d.ts +0 -90
  161. package/dist/http/crud-bridge.js +0 -25
@@ -0,0 +1,63 @@
1
+ import { defineModule } from "../module/define-module.js";
2
+ import { WsDispatcher } from "./ws-dispatcher.js";
3
+ import { WsServerImpl } from "./ws-server.js";
4
+ import { InMemoryRoomRegistry, local } from "./ws-sync.js";
5
+ import { WS_MODULE_OPTIONS, WS_ROOM_REGISTRY, WS_SERVER, WS_SYNC_DRIVER } from "./websocket.tokens.js";
6
+ /**
7
+ * Registers the WebSocket gateway machinery: the message dispatcher (discovers
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
+ ],
54
+ exports: [
55
+ WS_SERVER,
56
+ WS_SYNC_DRIVER,
57
+ WS_ROOM_REGISTRY,
58
+ WsDispatcher
59
+ ]
60
+ })
61
+ });
62
+ export class WebSocketModule extends ConfigurableModuleClass {
63
+ }
@@ -0,0 +1,23 @@
1
+ import { InjectionToken } from '../container/types';
2
+ import type { WebSocketModuleOptions } from './websocket.module';
3
+ import type { RoomRegistry, SyncDriver } from './ws-sync';
4
+ import type { WsServer } from './websocket.types';
5
+ export declare const WS_GATEWAY_METADATA = "vela:ws-gateway";
6
+ export declare const WS_SUBSCRIBE_METADATA = "vela:ws-subscribe";
7
+ export declare const WsParamType: {
8
+ readonly SOCKET: "ws_socket";
9
+ readonly BODY: "ws_body";
10
+ readonly SERVER: "ws_server";
11
+ };
12
+ export type WsParamType = (typeof WsParamType)[keyof typeof WsParamType];
13
+ /**
14
+ * The connected gateway server handle. Injected into gateways/controllers via
15
+ * `@WebSocketServer()` (constructor injection only — the container has no
16
+ * property-injection pass) or `@Inject(WS_SERVER)`.
17
+ */
18
+ export declare const WS_SERVER: InjectionToken<WsServer>;
19
+ /** The active cross-instance sync driver (`local()` by default). */
20
+ export declare const WS_SYNC_DRIVER: InjectionToken<SyncDriver>;
21
+ /** The local room-membership registry a transport reads/writes. */
22
+ export declare const WS_ROOM_REGISTRY: InjectionToken<RoomRegistry>;
23
+ export declare const WS_MODULE_OPTIONS: InjectionToken<WebSocketModuleOptions>;
@@ -0,0 +1,21 @@
1
+ import { InjectionToken } from "../container/types.js";
2
+ // Free-form metadata keys — same string-token convention as ON_EVENT_METADATA / CRON_METADATA.
3
+ export const WS_GATEWAY_METADATA = 'vela:ws-gateway';
4
+ export const WS_SUBSCRIBE_METADATA = 'vela:ws-subscribe';
5
+ // WebSocket parameter-decorator kinds — parallel to `ParamType` for HTTP, keyed
6
+ // by the WS argument resolver rather than the Hono-bound HTTP one.
7
+ export const WsParamType = {
8
+ SOCKET: 'ws_socket',
9
+ BODY: 'ws_body',
10
+ SERVER: 'ws_server'
11
+ };
12
+ /**
13
+ * The connected gateway server handle. Injected into gateways/controllers via
14
+ * `@WebSocketServer()` (constructor injection only — the container has no
15
+ * property-injection pass) or `@Inject(WS_SERVER)`.
16
+ */ export const WS_SERVER = new InjectionToken('WS_SERVER');
17
+ /** The active cross-instance sync driver (`local()` by default). */ export const WS_SYNC_DRIVER = new InjectionToken('WS_SYNC_DRIVER');
18
+ /** The local room-membership registry a transport reads/writes. */ export const WS_ROOM_REGISTRY = new InjectionToken('WS_ROOM_REGISTRY');
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');
@@ -0,0 +1,86 @@
1
+ import type { ExecutionContext, WsArgumentsHost } from '../pipeline/types';
2
+ export type { WsArgumentsHost };
3
+ /** The JSON envelope carried over the raw WebSocket. `id` is an optional client correlation id echoed on the reply. */
4
+ export interface WsMessage<T = unknown> {
5
+ id?: string;
6
+ event: string;
7
+ data?: T;
8
+ }
9
+ /** A handler's structured reply. Returning one frames `{ event, data }` back to the sender. */
10
+ export interface WsResponse<T = unknown> {
11
+ event: string;
12
+ data: T;
13
+ }
14
+ /**
15
+ * A single connected socket, normalized across runtimes. Per-connection state
16
+ * lives in `data`; mutations to `data`/room membership are persisted by
17
+ * `commit()` (on Cloudflare this writes the hibernation attachment).
18
+ */
19
+ export interface WsClient<TData = Record<string, unknown>> {
20
+ readonly id: string;
21
+ readonly rooms: ReadonlySet<string>;
22
+ data: TData;
23
+ /** Frame and send `{ event, data }` (optionally correlated by `id`) to this socket. */
24
+ send(event: string, data?: unknown, id?: string): void;
25
+ /** Send an already-serialized string. Escape hatch for custom framing. */
26
+ sendRaw(payload: string): void;
27
+ join(room: string): void | Promise<void>;
28
+ leave(room: string): void | Promise<void>;
29
+ /** Persist `data`/room mutations made during a handler. */
30
+ commit(): void | Promise<void>;
31
+ close(code?: number, reason?: string): void;
32
+ /** The native socket (`WSContext` on node/bun/deno, `WebSocket` in a Cloudflare DO). */
33
+ readonly raw: unknown;
34
+ }
35
+ /** Fluent broadcast builder. Terminal `.emit()` produces a `BroadcastCommand`. */
36
+ export interface BroadcastOperator {
37
+ to(room: string): BroadcastOperator;
38
+ in(room: string): BroadcastOperator;
39
+ except(room: string): BroadcastOperator;
40
+ emit(event: string, data?: unknown): void | Promise<void>;
41
+ }
42
+ /** The server handle injected via `@WebSocketServer()`. Server-origin broadcasts never exclude anyone. */
43
+ export interface WsServer {
44
+ emit(event: string, data?: unknown): void | Promise<void>;
45
+ to(room: string): BroadcastOperator;
46
+ in(room: string): BroadcastOperator;
47
+ except(room: string): BroadcastOperator;
48
+ }
49
+ /** An `ExecutionContext` whose transport is a WebSocket gateway. `switchToWs()` is guaranteed present. */
50
+ export interface WsExecutionContext extends ExecutionContext {
51
+ switchToWs(): WsArgumentsHost;
52
+ }
53
+ export interface OnGatewayInit {
54
+ afterInit(server: WsServer): void | Promise<void>;
55
+ }
56
+ export interface OnGatewayConnection {
57
+ handleConnection(client: WsClient): void | Promise<void>;
58
+ }
59
+ export interface OnGatewayDisconnect {
60
+ handleDisconnect(client: WsClient): void | Promise<void>;
61
+ }
62
+ export interface WebSocketGatewayOptions {
63
+ /** Route path the upgrade is served on (e.g. `/rooms/:id/ws`). */
64
+ path?: string;
65
+ /** Cloudflare Durable Object binding name that hosts this gateway's sockets. */
66
+ binding?: string;
67
+ }
68
+ /** Stored per `@SubscribeMessage` — a flat class-level list, mirroring `@OnEvent`. */
69
+ export interface SubscribeMessageMetadata {
70
+ event: string;
71
+ methodName: string;
72
+ }
73
+ /**
74
+ * A fully serializable broadcast instruction. Crosses isolate / Durable-Object /
75
+ * Redis boundaries as JSON, so every sync driver speaks the same command.
76
+ */
77
+ export interface BroadcastCommand {
78
+ /** Target rooms. An empty array means GLOBAL (every connection). */
79
+ rooms: string[];
80
+ exceptRooms?: string[];
81
+ exceptIds?: string[];
82
+ /** The exact bytes written to each socket: `JSON.stringify({ event, data })`. */
83
+ frame: string;
84
+ /** Origin instance/DO id — lets pub/sub drivers drop their own echo. */
85
+ origin?: string;
86
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * A fully serializable broadcast instruction. Crosses isolate / Durable-Object /
3
+ * Redis boundaries as JSON, so every sync driver speaks the same command.
4
+ */ export { };
@@ -0,0 +1,13 @@
1
+ import type { PipeTransform } from '../pipeline/types';
2
+ import type { ParameterMetadata } from '../registry/types';
3
+ import type { WsClient } from './websocket.types';
4
+ /**
5
+ * Builds handler arguments for a gateway message. WS sibling of the HTTP
6
+ * `ArgumentResolver`, but sourced from the envelope (`data`) and the socket
7
+ * (`client`) instead of the Hono `Context`.
8
+ *
9
+ * With no parameter decorators the handler receives `(client, data)` — the
10
+ * NestJS gateway convention. Pipes run only over `@MessageBody()` values, where
11
+ * validation/transformation is meaningful.
12
+ */
13
+ export declare function resolveWsArgs(paramMeta: ParameterMetadata[], client: WsClient, data: unknown, pipes: PipeTransform[], paramTypes?: unknown[]): Promise<unknown[]>;
@@ -0,0 +1,40 @@
1
+ import { WsParamType } from "./websocket.tokens.js";
2
+ /**
3
+ * Builds handler arguments for a gateway message. WS sibling of the HTTP
4
+ * `ArgumentResolver`, but sourced from the envelope (`data`) and the socket
5
+ * (`client`) instead of the Hono `Context`.
6
+ *
7
+ * With no parameter decorators the handler receives `(client, data)` — the
8
+ * NestJS gateway convention. Pipes run only over `@MessageBody()` values, where
9
+ * validation/transformation is meaningful.
10
+ */ export async function resolveWsArgs(paramMeta, client, data, pipes, paramTypes) {
11
+ if (paramMeta.length === 0) {
12
+ return [
13
+ client,
14
+ data
15
+ ];
16
+ }
17
+ const maxIndex = paramMeta[paramMeta.length - 1].index;
18
+ const args = new Array(maxIndex + 1).fill(undefined);
19
+ for (const param of paramMeta){
20
+ if (param.type === WsParamType.SOCKET) {
21
+ args[param.index] = client;
22
+ continue;
23
+ }
24
+ if (param.type === WsParamType.BODY) {
25
+ let value = data;
26
+ const metadata = {
27
+ type: param.type,
28
+ data: param.name,
29
+ metatype: paramTypes?.[param.index]
30
+ };
31
+ for (const pipe of pipes){
32
+ value = await pipe.transform(value, metadata);
33
+ }
34
+ args[param.index] = value;
35
+ continue;
36
+ }
37
+ args[param.index] = undefined;
38
+ }
39
+ return args;
40
+ }
@@ -0,0 +1,45 @@
1
+ import { Container } from '../container/container';
2
+ import { DiscoveryService } from '../discovery/discovery.service';
3
+ import type { ContributesEntrypoints, Entrypoint } from '../entrypoint/entrypoint.types';
4
+ import { RouteManager } from '../http/route.manager';
5
+ import type { OnApplicationBootstrap } from '../lifecycle/index';
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
+ }
14
+ /**
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`.
22
+ */
23
+ export declare class WsDispatcher implements OnApplicationBootstrap, ContributesEntrypoints {
24
+ private readonly container;
25
+ private readonly discovery;
26
+ private readonly server?;
27
+ private readonly routeManager?;
28
+ private readonly gateways;
29
+ constructor(container: Container, discovery: DiscoveryService, server?: WsServer | undefined, routeManager?: RouteManager | undefined);
30
+ /** Paths of every discovered `@WebSocketGateway` — used by transports to register routes. */
31
+ get gatewayPaths(): string[];
32
+ onApplicationBootstrap(): Promise<void>;
33
+ /** One `'websocket'` entrypoint per discovered gateway (authoritative). */
34
+ collectEntrypoints(): Entrypoint<WsEntrypointMeta>[];
35
+ handleOpen(path: string, client: WsClient): Promise<void>;
36
+ handleClose(path: string, client: WsClient, _code: number, _reason: string): Promise<void>;
37
+ handleError(path: string, _client: WsClient, err: unknown): Promise<void>;
38
+ dispatchMessage(path: string, client: WsClient, raw: string | ArrayBuffer): Promise<void>;
39
+ private reply;
40
+ private runFilters;
41
+ private trySend;
42
+ private reportBootstrapError;
43
+ private parse;
44
+ private buildGatewayEntry;
45
+ }
@@ -0,0 +1,259 @@
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
+ function _ts_param(paramIndex, decorator) {
11
+ return function(target, key) {
12
+ decorator(target, key, paramIndex);
13
+ };
14
+ }
15
+ import { Container } from "../container/container.js";
16
+ import { Inject, Injectable, Optional } from "../container/decorators.js";
17
+ import { DiscoveryService } from "../discovery/discovery.service.js";
18
+ import { instantiateMany } from "../http/instantiate.js";
19
+ import { RouteManager } from "../http/route.manager.js";
20
+ import { ComponentManager } from "../pipeline/component.manager.js";
21
+ import { shouldFilterCatch } from "../pipeline/decorators.js";
22
+ import { PipelineRunner } from "../pipeline/pipeline-runner.js";
23
+ import { MetadataRegistry } from "../registry/metadata.registry.js";
24
+ import { resolveWsArgs } from "./ws-argument-resolver.js";
25
+ import { buildWsExecutionContext } from "./ws-execution-context.js";
26
+ import { toErrorFrame, WsException } from "./ws-exception.js";
27
+ import { WS_GATEWAY_METADATA, WS_SERVER, WS_SUBSCRIBE_METADATA } from "./websocket.tokens.js";
28
+ function hasAfterInit(x) {
29
+ return typeof x?.afterInit === 'function';
30
+ }
31
+ function hasHandleConnection(x) {
32
+ return typeof x?.handleConnection === 'function';
33
+ }
34
+ function hasHandleDisconnect(x) {
35
+ return typeof x?.handleDisconnect === 'function';
36
+ }
37
+ function isWsResponse(x) {
38
+ return typeof x === 'object' && x !== null && typeof x.event === 'string';
39
+ }
40
+ export class WsDispatcher {
41
+ container;
42
+ discovery;
43
+ server;
44
+ routeManager;
45
+ gateways = new Map();
46
+ constructor(container, discovery, server, routeManager){
47
+ this.container = container;
48
+ this.discovery = discovery;
49
+ this.server = server;
50
+ this.routeManager = routeManager;
51
+ }
52
+ /** Paths of every discovered `@WebSocketGateway` — used by transports to register routes. */ get gatewayPaths() {
53
+ return [
54
+ ...this.gateways.keys()
55
+ ];
56
+ }
57
+ async onApplicationBootstrap() {
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);
63
+ // Two gateways on the same path (or both defaulting to '') would silently
64
+ // overwrite each other in the routing Map — surface it instead.
65
+ if (this.gateways.has(entry.path)) {
66
+ const msg = `[vela] duplicate @WebSocketGateway path '${entry.path}' ` + `(${gatewayClass.name}); keeping the first. Give each gateway a distinct path.`;
67
+ if (this.container.getDiagnostics() === 'throw') throw new Error(msg);
68
+ console.warn(msg);
69
+ continue;
70
+ }
71
+ this.gateways.set(entry.path, entry);
72
+ if (this.server && hasAfterInit(instance)) {
73
+ try {
74
+ await instance.afterInit(this.server);
75
+ } catch (err) {
76
+ this.reportBootstrapError(gatewayClass.name, err);
77
+ }
78
+ }
79
+ }
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
+ }
94
+ async handleOpen(path, client) {
95
+ const entry = this.gateways.get(path);
96
+ if (entry && hasHandleConnection(entry.instance)) {
97
+ await entry.instance.handleConnection(client);
98
+ }
99
+ }
100
+ async handleClose(path, client, _code, _reason) {
101
+ const entry = this.gateways.get(path);
102
+ if (entry && hasHandleDisconnect(entry.instance)) {
103
+ await entry.instance.handleDisconnect(client);
104
+ }
105
+ }
106
+ async handleError(path, _client, err) {
107
+ if (this.container.getDiagnostics() !== 'silent') {
108
+ console.warn(`[vela] websocket error on gateway ${path}:`, err);
109
+ }
110
+ }
111
+ async dispatchMessage(path, client, raw) {
112
+ const entry = this.gateways.get(path);
113
+ if (!entry) return;
114
+ let message;
115
+ try {
116
+ message = this.parse(raw);
117
+ } catch {
118
+ this.trySend(client, 'exception', {
119
+ message: 'Invalid message'
120
+ });
121
+ return;
122
+ }
123
+ const handler = entry.handlers.get(message.event);
124
+ if (!handler) return; // unknown event — ignored (NestJS parity)
125
+ const ctx = buildWsExecutionContext(client, message.data, entry.gatewayClass, handler.methodName, message.event);
126
+ // App-wide global components (APP_* provider tokens + imperative
127
+ // app.useGlobalX()) run first, then controller/handler-tier — mirroring the
128
+ // HTTP HandlerExecutor so global guards/pipes/interceptors/filters protect
129
+ // gateway messages too. Read live so late registrations propagate.
130
+ const globals = this.routeManager?.getGlobalComponents();
131
+ const guards = [
132
+ ...instantiateMany(globals?.guards ?? [], this.container),
133
+ ...instantiateMany(handler.guards, this.container)
134
+ ];
135
+ const pipes = [
136
+ ...instantiateMany(globals?.pipes ?? [], this.container),
137
+ ...instantiateMany(handler.pipes, this.container)
138
+ ];
139
+ const interceptors = [
140
+ ...instantiateMany(globals?.interceptors ?? [], this.container),
141
+ ...instantiateMany(handler.interceptors, this.container)
142
+ ];
143
+ // Handler/controller filters first (closest), then global filters last.
144
+ const filters = [
145
+ ...instantiateMany(handler.filters, this.container),
146
+ ...instantiateMany(globals?.filters ?? [], this.container)
147
+ ];
148
+ try {
149
+ const method = entry.instance[handler.methodName];
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
+ });
160
+ this.reply(client, message, result);
161
+ } catch (error) {
162
+ await this.runFilters(client, message, error, filters, ctx);
163
+ }
164
+ }
165
+ reply(client, message, result) {
166
+ if (result === undefined || result === null) return;
167
+ if (isWsResponse(result)) {
168
+ this.trySend(client, result.event, result.data, message.id);
169
+ } else {
170
+ this.trySend(client, message.event, result, message.id);
171
+ }
172
+ }
173
+ async runFilters(client, message, error, filters, ctx) {
174
+ for (const filter of filters){
175
+ if (shouldFilterCatch(filter, error)) {
176
+ try {
177
+ const handled = await filter.catch(error, ctx);
178
+ if (isWsResponse(handled)) {
179
+ this.trySend(client, handled.event, handled.data, message.id);
180
+ }
181
+ } catch {
182
+ // A throwing filter falls back to the default error frame (mirrors HandlerExecutor).
183
+ const frame = toErrorFrame(error);
184
+ this.trySend(client, frame.event, frame.data, message.id);
185
+ }
186
+ return;
187
+ }
188
+ }
189
+ const errorFrame = toErrorFrame(error);
190
+ this.trySend(client, errorFrame.event, errorFrame.data, message.id);
191
+ }
192
+ // Outbound sends are best-effort: the socket may have closed mid-dispatch.
193
+ trySend(client, event, data, id) {
194
+ try {
195
+ client.send(event, data, id);
196
+ } catch {
197
+ // socket closed — nothing to do
198
+ }
199
+ }
200
+ reportBootstrapError(name, err) {
201
+ const mode = this.container.getDiagnostics();
202
+ if (mode === 'throw') throw err;
203
+ if (mode === 'log') console.warn(`[vela] websocket gateway ${name} afterInit failed:`, err);
204
+ }
205
+ parse(raw) {
206
+ const text = typeof raw === 'string' ? raw : new TextDecoder().decode(raw);
207
+ const parsed = JSON.parse(text);
208
+ if (!parsed || typeof parsed !== 'object' || typeof parsed.event !== 'string') {
209
+ throw new Error('Invalid WebSocket message envelope');
210
+ }
211
+ return parsed;
212
+ }
213
+ buildGatewayEntry(gatewayClass, options, instance) {
214
+ const ctor = gatewayClass;
215
+ const subs = MetadataRegistry.getCustomClassMeta(ctor, WS_SUBSCRIBE_METADATA) ?? [];
216
+ const allParams = MetadataRegistry.getParameters(ctor);
217
+ const handlers = new Map();
218
+ for (const { event, methodName } of subs){
219
+ const paramMeta = [
220
+ ...allParams.get(methodName) ?? []
221
+ ].sort((a, b)=>a.index - b.index);
222
+ const paramTypes = Reflect.getMetadata('design:paramtypes', gatewayClass.prototype, methodName);
223
+ handlers.set(event, {
224
+ methodName,
225
+ paramMeta,
226
+ paramTypes,
227
+ guards: ComponentManager.getScopedComponents('guard', ctor, methodName),
228
+ pipes: ComponentManager.getScopedComponents('pipe', ctor, methodName),
229
+ interceptors: ComponentManager.getScopedComponents('interceptor', ctor, methodName),
230
+ // Handler → controller → global, so the closest filter runs first (mirrors HandlerExecutor).
231
+ filters: [
232
+ ...ComponentManager.getScopedComponents('filter', ctor, methodName)
233
+ ].reverse()
234
+ });
235
+ }
236
+ return {
237
+ path: options.path ?? '',
238
+ instance,
239
+ gatewayClass,
240
+ handlers
241
+ };
242
+ }
243
+ }
244
+ WsDispatcher = _ts_decorate([
245
+ Injectable(),
246
+ _ts_param(0, Inject(Container)),
247
+ _ts_param(1, Inject(DiscoveryService)),
248
+ _ts_param(2, Optional()),
249
+ _ts_param(2, Inject(WS_SERVER)),
250
+ _ts_param(3, Optional()),
251
+ _ts_param(3, Inject(RouteManager)),
252
+ _ts_metadata("design:type", Function),
253
+ _ts_metadata("design:paramtypes", [
254
+ typeof Container === "undefined" ? Object : Container,
255
+ typeof DiscoveryService === "undefined" ? Object : DiscoveryService,
256
+ typeof WsServer === "undefined" ? Object : WsServer,
257
+ typeof RouteManager === "undefined" ? Object : RouteManager
258
+ ])
259
+ ], WsDispatcher);
@@ -0,0 +1,16 @@
1
+ /**
2
+ * WebSocket analogue of `HttpException`. Thrown from guards/pipes/handlers to
3
+ * signal a client-facing error; the dispatcher serializes it to an
4
+ * `{ event: 'exception', data }` frame unless a matching exception filter
5
+ * handles it first.
6
+ */
7
+ export declare class WsException extends Error {
8
+ private readonly err;
9
+ constructor(err: string | Record<string, unknown>);
10
+ getError(): string | Record<string, unknown>;
11
+ }
12
+ /** Default serialization of an uncaught error into the outbound exception frame. */
13
+ export declare function toErrorFrame(error: unknown): {
14
+ event: 'exception';
15
+ data: unknown;
16
+ };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * WebSocket analogue of `HttpException`. Thrown from guards/pipes/handlers to
3
+ * signal a client-facing error; the dispatcher serializes it to an
4
+ * `{ event: 'exception', data }` frame unless a matching exception filter
5
+ * handles it first.
6
+ */ export class WsException extends Error {
7
+ err;
8
+ constructor(err){
9
+ super(typeof err === 'string' ? err : String(err.message ?? 'WsException')), this.err = err;
10
+ this.name = 'WsException';
11
+ }
12
+ getError() {
13
+ return this.err;
14
+ }
15
+ }
16
+ /** Default serialization of an uncaught error into the outbound exception frame. */ export function toErrorFrame(error) {
17
+ if (error instanceof WsException) {
18
+ const e = error.getError();
19
+ return {
20
+ event: 'exception',
21
+ data: typeof e === 'string' ? {
22
+ message: e
23
+ } : e
24
+ };
25
+ }
26
+ return {
27
+ event: 'exception',
28
+ data: {
29
+ message: 'Internal server error'
30
+ }
31
+ };
32
+ }
@@ -0,0 +1,9 @@
1
+ import type { Type } from '../container/types';
2
+ import type { WsClient, WsExecutionContext } from './websocket.types';
3
+ /**
4
+ * WebSocket sibling of `buildExecutionContext` (`http/execution-context.ts`).
5
+ * Guards/pipes/interceptors/filters that read `getClass()`/`getHandler()` or
6
+ * call `switchToWs()` reuse unchanged; the HTTP-only accessors throw rather
7
+ * than fabricate a fake `Request`.
8
+ */
9
+ export declare function buildWsExecutionContext(client: WsClient, data: unknown, controller: Type, handlerName: string | symbol, pattern: string): WsExecutionContext;
@@ -0,0 +1,24 @@
1
+ const HTTP_ON_WS = (accessor)=>()=>{
2
+ throw new Error(`${accessor} called on a WebSocket ExecutionContext. This handler runs over a gateway message, not HTTP — use switchToWs().`);
3
+ };
4
+ /**
5
+ * WebSocket sibling of `buildExecutionContext` (`http/execution-context.ts`).
6
+ * Guards/pipes/interceptors/filters that read `getClass()`/`getHandler()` or
7
+ * call `switchToWs()` reuse unchanged; the HTTP-only accessors throw rather
8
+ * than fabricate a fake `Request`.
9
+ */ export function buildWsExecutionContext(client, data, controller, handlerName, pattern) {
10
+ const host = {
11
+ getClient: ()=>client,
12
+ getData: ()=>data,
13
+ getPattern: ()=>pattern
14
+ };
15
+ return {
16
+ getType: ()=>'ws',
17
+ getClass: ()=>controller,
18
+ getHandler: ()=>handlerName,
19
+ getContext: HTTP_ON_WS('getContext()'),
20
+ getRequest: HTTP_ON_WS('getRequest()'),
21
+ switchToHttp: HTTP_ON_WS('switchToHttp()'),
22
+ switchToWs: ()=>host
23
+ };
24
+ }