@voltrix/websocket 0.3.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pipeline/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAC;AAE5B;;;;GAIG;AACH,MAAM,OAAO,UAAU;IACJ,WAAW,GAA0B,EAAE,CAAC;IAEzD;;OAEG;IACH,GAAG,CAAC,UAA+B;QACjC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAClC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,GAAG,CACP,MAAuB,EACvB,KAAa,EACb,OAAY,EACZ,OAAqE;QAErE,MAAM,OAAO,GAA4B;YACvC,MAAM;YACN,KAAK;YACL,OAAO;SACR,CAAC;QAEF,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC;QAEf,MAAM,QAAQ,GAAG,KAAK,EAAE,CAAS,EAAiB,EAAE;YAClD,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC;gBACf,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;YACpE,CAAC;YACD,KAAK,GAAG,CAAC,CAAC;YAEV,iFAAiF;YACjF,IAAI,CAAC,KAAK,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC;gBAClC,MAAM,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;gBAC/C,OAAO;YACT,CAAC;YAED,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAE,CAAC;YACxC,MAAM,UAAU,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE;gBACnC,MAAM,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACxB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;CACF"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * 📨 Normalization interface for WebSocket packets
3
+ */
4
+ export interface WsPacket {
5
+ readonly event: string;
6
+ readonly data: any;
7
+ }
8
+ /**
9
+ * ⚙️ High-performance packet parser/serializer.
10
+ */
11
+ export declare class WsPacketParser {
12
+ /**
13
+ * Parses a raw string or ArrayBuffer payload into a normalized WsPacket.
14
+ * Returns null if the payload does not conform to the expected format.
15
+ */
16
+ static parse(payload: string | ArrayBuffer | Buffer): WsPacket | null;
17
+ /**
18
+ * Serializes an event and data object into a JSON string payload.
19
+ */
20
+ static serialize(event: string, data: any): string;
21
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * ⚙️ High-performance packet parser/serializer.
3
+ */
4
+ export class WsPacketParser {
5
+ /**
6
+ * Parses a raw string or ArrayBuffer payload into a normalized WsPacket.
7
+ * Returns null if the payload does not conform to the expected format.
8
+ */
9
+ static parse(payload) {
10
+ let rawStr;
11
+ if (payload instanceof ArrayBuffer) {
12
+ rawStr = Buffer.from(payload).toString('utf8');
13
+ }
14
+ else if (Buffer.isBuffer(payload)) {
15
+ rawStr = payload.toString('utf8');
16
+ }
17
+ else {
18
+ rawStr = payload;
19
+ }
20
+ const trimmed = rawStr.trim();
21
+ // Quick early boundary validation: Must look like a JSON object
22
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
23
+ return null;
24
+ }
25
+ try {
26
+ const parsed = JSON.parse(trimmed);
27
+ if (parsed && typeof parsed === 'object' && typeof parsed.event === 'string') {
28
+ return {
29
+ event: parsed.event,
30
+ data: parsed.data !== undefined ? parsed.data : null
31
+ };
32
+ }
33
+ }
34
+ catch {
35
+ // Fail gracefully on invalid JSON structure
36
+ }
37
+ return null;
38
+ }
39
+ /**
40
+ * Serializes an event and data object into a JSON string payload.
41
+ */
42
+ static serialize(event, data) {
43
+ return JSON.stringify({ event, data });
44
+ }
45
+ }
46
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/pipeline/parser.ts"],"names":[],"mappings":"AAQA;;GAEG;AACH,MAAM,OAAO,cAAc;IACzB;;;OAGG;IACH,MAAM,CAAC,KAAK,CAAC,OAAsC;QACjD,IAAI,MAAc,CAAC;QAEnB,IAAI,OAAO,YAAY,WAAW,EAAE,CAAC;YACnC,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACjD,CAAC;aAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YACpC,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACpC,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,OAAO,CAAC;QACnB,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;QAE9B,gEAAgE;QAChE,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACvD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACnC,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC7E,OAAO;oBACL,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,IAAI,EAAE,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI;iBACrD,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,4CAA4C;QAC9C,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,SAAS,CAAC,KAAa,EAAE,IAAS;QACvC,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,CAAC;CACF"}
@@ -0,0 +1,12 @@
1
+ import { DIContainer } from '@voltrix/injector';
2
+ /**
3
+ * 🚀 Voltrix WebSocket Processor (The Compiler)
4
+ * Discovers gateways, binds dependency injection, and pre-compiles execution pipelines.
5
+ */
6
+ export declare class WebSocketProcessor {
7
+ /**
8
+ * Compiles and registers all decorated WebSocket Gateways within the Voltrix application.
9
+ * Auto-resolves dependencies from the DI Container and attaches to the uWS server instance.
10
+ */
11
+ static process(container: DIContainer, uwsApp?: any, port?: number): void;
12
+ }
@@ -0,0 +1,108 @@
1
+ import { Metadata } from '@voltrix/core';
2
+ import { WS_KEYS } from '../decorators/index.js';
3
+ import { UwsWebSocketAdapter } from '../adapters/uws-adapter.js';
4
+ /**
5
+ * 🚀 Voltrix WebSocket Processor (The Compiler)
6
+ * Discovers gateways, binds dependency injection, and pre-compiles execution pipelines.
7
+ */
8
+ export class WebSocketProcessor {
9
+ /**
10
+ * Compiles and registers all decorated WebSocket Gateways within the Voltrix application.
11
+ * Auto-resolves dependencies from the DI Container and attaches to the uWS server instance.
12
+ */
13
+ static process(container, uwsApp, port = 3000) {
14
+ // 1. Scan the global metadata index for tracked classes
15
+ const classes = Metadata.getTrackedClasses();
16
+ for (const ctor of classes) {
17
+ const meta = Metadata.get(ctor);
18
+ const gatewayOptions = meta[WS_KEYS.GATEWAY];
19
+ // Skip classes that do not declare `@WebSocketGateway()`
20
+ if (!gatewayOptions)
21
+ continue;
22
+ // 2. Resolve the Gateway instance from the DI Container with full dependency injection
23
+ const gatewayInstance = container.resolve(ctor);
24
+ if (!gatewayInstance) {
25
+ throw new Error(`Failed to resolve WebSocket Gateway: ${ctor.name}`);
26
+ }
27
+ // 3. Configure Handshake Guard if defined
28
+ const path = gatewayOptions.path || '/ws';
29
+ let onUpgradeHook = undefined;
30
+ if (gatewayOptions.guard) {
31
+ const guardInstance = container.resolve(gatewayOptions.guard);
32
+ if (!guardInstance) {
33
+ throw new Error(`Failed to resolve WebSocket Guard: ${gatewayOptions.guard.name}`);
34
+ }
35
+ onUpgradeHook = async (req, res) => {
36
+ let resolvedUser = null;
37
+ const context = {
38
+ getRequest: () => req,
39
+ getResponse: () => res,
40
+ setUser: (user) => { resolvedUser = user; },
41
+ getUser: () => resolvedUser
42
+ };
43
+ const canActivate = await guardInstance.canActivate(context);
44
+ if (!canActivate)
45
+ return false;
46
+ return resolvedUser || true;
47
+ };
48
+ }
49
+ // 4. Instantiate Adapter and create the server WebSocket listener
50
+ const adapter = new UwsWebSocketAdapter();
51
+ const server = adapter.create(port, {
52
+ app: uwsApp,
53
+ path,
54
+ onUpgrade: onUpgradeHook
55
+ });
56
+ // 5. Bind Connection events and lifecycles
57
+ const onConnectKey = meta[WS_KEYS.ON_CONNECT];
58
+ const onDisconnectKey = meta[WS_KEYS.ON_DISCONNECT];
59
+ adapter.bindClientConnect(server, async (socket) => {
60
+ // Trigger OnConnect lifecycle hook if declared
61
+ if (onConnectKey) {
62
+ const paramsMeta = Metadata.get(ctor, onConnectKey)[WS_KEYS.PARAMS] || [];
63
+ const args = new Array(paramsMeta.length);
64
+ paramsMeta.forEach((p) => {
65
+ if (p.type === 'socket')
66
+ args[p.index] = socket;
67
+ });
68
+ await gatewayInstance[onConnectKey](...args);
69
+ }
70
+ // Trigger OnDisconnect lifecycle hook if declared
71
+ if (onDisconnectKey) {
72
+ adapter.bindClientDisconnect(socket, async (client, code, reason) => {
73
+ const paramsMeta = Metadata.get(ctor, onDisconnectKey)[WS_KEYS.PARAMS] || [];
74
+ const args = new Array(paramsMeta.index + 1 || paramsMeta.length);
75
+ paramsMeta.forEach((p) => {
76
+ if (p.type === 'socket')
77
+ args[p.index] = client;
78
+ });
79
+ await gatewayInstance[onDisconnectKey](...args);
80
+ });
81
+ }
82
+ });
83
+ // 6. Compile and bind message handlers
84
+ const eventsList = meta[WS_KEYS.EVENTS] || [];
85
+ const handlers = eventsList.map((e) => {
86
+ const methodKey = e.propertyKey;
87
+ const paramsMeta = Metadata.get(ctor, methodKey)[WS_KEYS.PARAMS] || [];
88
+ return {
89
+ event: e.event,
90
+ handler: async (client, data) => {
91
+ // Build the arguments array programmatically in O(1)
92
+ const args = new Array(paramsMeta.length);
93
+ paramsMeta.forEach((p) => {
94
+ if (p.type === 'socket')
95
+ args[p.index] = client;
96
+ else if (p.type === 'body')
97
+ args[p.index] = data;
98
+ });
99
+ await gatewayInstance[methodKey](...args);
100
+ }
101
+ };
102
+ });
103
+ // Bind all pre-compiled handlers globally on the adapter
104
+ adapter.bindMessageHandlers(server, handlers);
105
+ }
106
+ }
107
+ }
108
+ //# sourceMappingURL=websocket.processor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.processor.js","sourceRoot":"","sources":["../../src/processors/websocket.processor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export * from '../types/index.js';
2
+ export * from './memory-engine.js';
3
+ export * from './redis-engine.js';
@@ -0,0 +1,4 @@
1
+ export * from '../types/index.js';
2
+ export * from './memory-engine.js';
3
+ export * from './redis-engine.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pubsub/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { IPubSubEngine, WsSocket } from '../types/index.js';
2
+ /**
3
+ * 📡 In-Memory Pub/Sub Engine using native C++ uWebSockets.js capabilities.
4
+ * Delivers extreme performance and near-zero memory footprint.
5
+ */
6
+ export declare class MemoryEngine implements IPubSubEngine {
7
+ private readonly app;
8
+ /**
9
+ * @param app The raw uWS TemplatedApp instance.
10
+ */
11
+ constructor(app: any);
12
+ subscribe(client: WsSocket, topic: string): void;
13
+ unsubscribe(client: WsSocket, topic: string): void;
14
+ publish(topic: string, message: any, isBinary?: boolean): void;
15
+ broadcast(message: any, isBinary?: boolean): void;
16
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * 📡 In-Memory Pub/Sub Engine using native C++ uWebSockets.js capabilities.
3
+ * Delivers extreme performance and near-zero memory footprint.
4
+ */
5
+ export class MemoryEngine {
6
+ app;
7
+ /**
8
+ * @param app The raw uWS TemplatedApp instance.
9
+ */
10
+ constructor(app) {
11
+ this.app = app;
12
+ }
13
+ subscribe(client, topic) {
14
+ client.subscribe(topic);
15
+ }
16
+ unsubscribe(client, topic) {
17
+ client.unsubscribe(topic);
18
+ }
19
+ publish(topic, message, isBinary) {
20
+ const payload = typeof message === 'string' ? message : JSON.stringify(message);
21
+ this.app.publish(topic, payload, isBinary ?? false);
22
+ }
23
+ broadcast(message, isBinary) {
24
+ // Standardized global room for broadcasts
25
+ const payload = typeof message === 'string' ? message : JSON.stringify(message);
26
+ this.app.publish('global', payload, isBinary ?? false);
27
+ }
28
+ }
29
+ //# sourceMappingURL=memory-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-engine.js","sourceRoot":"","sources":["../../src/pubsub/memory-engine.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,OAAO,YAAY;IAIM;IAH7B;;OAEG;IACH,YAA6B,GAAQ;QAAR,QAAG,GAAH,GAAG,CAAK;IAAG,CAAC;IAEzC,SAAS,CAAC,MAAgB,EAAE,KAAa;QACvC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED,WAAW,CAAC,MAAgB,EAAE,KAAa;QACzC,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IAED,OAAO,CAAC,KAAa,EAAE,OAAY,EAAE,QAAkB;QACrD,MAAM,OAAO,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAChF,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,IAAI,KAAK,CAAC,CAAC;IACtD,CAAC;IAED,SAAS,CAAC,OAAY,EAAE,QAAkB;QACxC,0CAA0C;QAC1C,MAAM,OAAO,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAChF,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,KAAK,CAAC,CAAC;IACzD,CAAC;CACF"}
@@ -0,0 +1,31 @@
1
+ import { type RedisOptions } from 'ioredis';
2
+ import type { IPubSubEngine, WsSocket } from '../types/index.js';
3
+ /**
4
+ * 📡 Distributed Redis Pub/Sub Engine.
5
+ * Syncs Rooms/Topics in real-time across a cluster of multiple Voltrix server instances.
6
+ */
7
+ export declare class RedisEngine implements IPubSubEngine {
8
+ private readonly localEngine;
9
+ private readonly pubClient;
10
+ private readonly subClient;
11
+ private readonly prefix;
12
+ private isSubscribed;
13
+ /**
14
+ * @param app The raw uWS TemplatedApp instance.
15
+ * @param redisConfig Either a Redis URL string or full connection options.
16
+ */
17
+ constructor(app: any, redisConfig: string | RedisOptions);
18
+ subscribe(client: WsSocket, topic: string): void;
19
+ unsubscribe(client: WsSocket, topic: string): void;
20
+ publish(topic: string, message: any, isBinary?: boolean): Promise<void>;
21
+ broadcast(message: any, isBinary?: boolean): Promise<void>;
22
+ /**
23
+ * 🚏 Clustered pattern subscription bridge.
24
+ * Listens to Redis channels and pipes messages into local uWS channels natively.
25
+ */
26
+ private setupSubscriptionBridge;
27
+ /**
28
+ * Securely disconnects the Redis client connections
29
+ */
30
+ close(): Promise<void>;
31
+ }
@@ -0,0 +1,99 @@
1
+ import { Redis } from 'ioredis';
2
+ import { MemoryEngine } from './memory-engine.js';
3
+ /**
4
+ * 📡 Distributed Redis Pub/Sub Engine.
5
+ * Syncs Rooms/Topics in real-time across a cluster of multiple Voltrix server instances.
6
+ */
7
+ export class RedisEngine {
8
+ localEngine;
9
+ pubClient;
10
+ subClient;
11
+ prefix = 'vltx:ws:';
12
+ isSubscribed = false;
13
+ /**
14
+ * @param app The raw uWS TemplatedApp instance.
15
+ * @param redisConfig Either a Redis URL string or full connection options.
16
+ */
17
+ constructor(app, redisConfig) {
18
+ this.localEngine = new MemoryEngine(app);
19
+ // Initialize Redis clients for Publishing and Subscribing
20
+ if (typeof redisConfig === 'string') {
21
+ this.pubClient = new Redis(redisConfig, { maxRetriesPerRequest: null });
22
+ this.subClient = new Redis(redisConfig, { maxRetriesPerRequest: null });
23
+ }
24
+ else {
25
+ this.pubClient = new Redis({ ...redisConfig, maxRetriesPerRequest: null });
26
+ this.subClient = new Redis({ ...redisConfig, maxRetriesPerRequest: null });
27
+ }
28
+ // Suppress unhandled connection errors to prevent crashes if Redis is offline
29
+ this.pubClient.on('error', () => { });
30
+ this.subClient.on('error', () => { });
31
+ this.setupSubscriptionBridge();
32
+ }
33
+ subscribe(client, topic) {
34
+ this.localEngine.subscribe(client, topic);
35
+ if (topic !== 'global') {
36
+ this.localEngine.subscribe(client, 'global');
37
+ }
38
+ }
39
+ unsubscribe(client, topic) {
40
+ this.localEngine.unsubscribe(client, topic);
41
+ }
42
+ async publish(topic, message, isBinary) {
43
+ this.localEngine.publish(topic, message, isBinary);
44
+ const payload = typeof message === 'string' ? message : JSON.stringify(message);
45
+ const packet = JSON.stringify({
46
+ message: payload,
47
+ isBinary: isBinary ?? false
48
+ });
49
+ try {
50
+ await this.pubClient.publish(`${this.prefix}${topic}`, packet);
51
+ }
52
+ catch (err) {
53
+ // Degrade gracefully if Redis publisher is offline
54
+ }
55
+ }
56
+ async broadcast(message, isBinary) {
57
+ await this.publish('global', message, isBinary);
58
+ }
59
+ /**
60
+ * 🚏 Clustered pattern subscription bridge.
61
+ * Listens to Redis channels and pipes messages into local uWS channels natively.
62
+ */
63
+ setupSubscriptionBridge() {
64
+ if (this.isSubscribed)
65
+ return;
66
+ this.isSubscribed = true;
67
+ // Pattern subscribe to all voltrix ws channels
68
+ this.subClient.psubscribe(`${this.prefix}*`).catch(() => {
69
+ this.isSubscribed = false;
70
+ });
71
+ this.subClient.on('pmessage', (_pattern, channel, message) => {
72
+ const topic = channel.slice(this.prefix.length);
73
+ try {
74
+ const data = JSON.parse(message);
75
+ if (data && data.message !== undefined) {
76
+ this.localEngine.publish(topic, data.message, data.isBinary);
77
+ }
78
+ }
79
+ catch (err) {
80
+ // Suppress parsing errors
81
+ }
82
+ });
83
+ }
84
+ /**
85
+ * Securely disconnects the Redis client connections
86
+ */
87
+ async close() {
88
+ try {
89
+ await Promise.all([
90
+ this.pubClient.quit(),
91
+ this.subClient.quit()
92
+ ]);
93
+ }
94
+ catch {
95
+ // Safe exit
96
+ }
97
+ }
98
+ }
99
+ //# sourceMappingURL=redis-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-engine.js","sourceRoot":"","sources":["../../src/pubsub/redis-engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,134 @@
1
+ import type { IRequest, IResponse } from '@voltrix/core';
2
+ /**
3
+ * 🔌 Core WsSocket normalization interface wrapper.
4
+ * Encapsulates client socket operations for engine-agnostic code.
5
+ */
6
+ export interface WsSocket<TUser = any, TRawSocket = any> {
7
+ /**
8
+ * Unique connection ID.
9
+ */
10
+ readonly id: string;
11
+ /**
12
+ * Authenticated user object/context.
13
+ */
14
+ user?: TUser;
15
+ /**
16
+ * Send a message to this specific client.
17
+ */
18
+ send(message: string | ArrayBufferView | ArrayBuffer, isBinary?: boolean): void;
19
+ /**
20
+ * Subscribe this client to a room/topic.
21
+ */
22
+ subscribe(topic: string): void;
23
+ /**
24
+ * Unsubscribe this client from a room/topic.
25
+ */
26
+ unsubscribe(topic: string): void;
27
+ /**
28
+ * Publish a message to a topic.
29
+ */
30
+ publish(topic: string, message: string | ArrayBufferView | ArrayBuffer, isBinary?: boolean): void;
31
+ /**
32
+ * Close the connection securely.
33
+ */
34
+ close(code?: number, reason?: string): void;
35
+ /**
36
+ * The raw underlying engine socket instance (e.g. uWS.WebSocket).
37
+ */
38
+ readonly raw: TRawSocket;
39
+ }
40
+ /**
41
+ * 🎯 Structure of a registered message handler.
42
+ */
43
+ export interface WsMessageHandler<TClient extends WsSocket = WsSocket> {
44
+ readonly event: string;
45
+ readonly handler: (client: TClient, data: any) => void | Promise<void>;
46
+ }
47
+ /**
48
+ * 🔌 Pluggable WebSocket Adapter Contract.
49
+ */
50
+ export interface IWebSocketAdapter<TServer = any, TClient extends WsSocket = WsSocket, TOptions = any> {
51
+ /**
52
+ * Creates the WebSocket server.
53
+ */
54
+ create(port: number, options?: TOptions): TServer;
55
+ /**
56
+ * Binds callback to client connection events.
57
+ */
58
+ bindClientConnect(server: TServer, callback: (client: TClient) => void | Promise<void>): void;
59
+ /**
60
+ * Binds callback to client disconnection events.
61
+ */
62
+ bindClientDisconnect(client: TClient, callback: (client: TClient, code: number, reason: ArrayBuffer) => void | Promise<void>): void;
63
+ /**
64
+ * Binds handlers for events sent by the client.
65
+ */
66
+ bindMessageHandlers(client: TClient, handlers: WsMessageHandler<TClient>[]): void;
67
+ /**
68
+ * Closes the server.
69
+ */
70
+ close(): void | Promise<void>;
71
+ }
72
+ /**
73
+ * 📡 Pluggable Pub/Sub and Distributed Broadcasting Engine.
74
+ */
75
+ export interface IPubSubEngine<TClient extends WsSocket = WsSocket, TPayload = any> {
76
+ /**
77
+ * Subscribes a client to a topic.
78
+ */
79
+ subscribe(client: TClient, topic: string): void | Promise<void>;
80
+ /**
81
+ * Unsubscribes a client from a topic.
82
+ */
83
+ unsubscribe(client: TClient, topic: string): void | Promise<void>;
84
+ /**
85
+ * Publishes a message to a topic.
86
+ */
87
+ publish(topic: string, message: TPayload, isBinary?: boolean): void | Promise<void>;
88
+ /**
89
+ * Broadcasts a message globally to all connections.
90
+ */
91
+ broadcast(message: TPayload, isBinary?: boolean): void | Promise<void>;
92
+ }
93
+ /**
94
+ * 🔒 Handshake Context for Pre-Upgrade Authentication.
95
+ */
96
+ export interface HandshakeContext<TUser = any> {
97
+ /**
98
+ * Returns the underlying HTTP Request interface.
99
+ */
100
+ getRequest(): IRequest;
101
+ /**
102
+ * Returns the underlying HTTP Response interface.
103
+ */
104
+ getResponse(): IResponse;
105
+ /**
106
+ * Sets the resolved/authenticated user metadata.
107
+ */
108
+ setUser(user: TUser): void;
109
+ /**
110
+ * Gets the active user metadata.
111
+ */
112
+ getUser(): TUser | undefined;
113
+ }
114
+ /**
115
+ * 📨 WebSocket Message Context for Pipelines.
116
+ */
117
+ export interface WsMessageContext<TUser = any> {
118
+ /**
119
+ * The client socket that sent the message.
120
+ */
121
+ readonly client: WsSocket<TUser>;
122
+ /**
123
+ * The event name.
124
+ */
125
+ readonly event: string;
126
+ /**
127
+ * The parsed payload.
128
+ */
129
+ payload: any;
130
+ }
131
+ /**
132
+ * ⚙️ Interceptor / Middleware for message events.
133
+ */
134
+ export type WsMiddleware<TUser = any> = (context: WsMessageContext<TUser>, next: (err?: Error) => void | Promise<void>) => void | Promise<void>;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@voltrix/websocket",
3
+ "version": "0.3.0",
4
+ "description": "High-performance, programmatic-first and decorator-driven WebSocket package with pluggable adapters and distributed pub/sub for Voltrix",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.js"
14
+ }
15
+ },
16
+ "keywords": [
17
+ "voltrix",
18
+ "websocket",
19
+ "uWebSockets.js",
20
+ "pubsub",
21
+ "redis",
22
+ "realtime",
23
+ "gateway"
24
+ ],
25
+ "author": "Voltrix Team",
26
+ "license": "MIT",
27
+ "dependencies": {
28
+ "ioredis": "^5.4.1",
29
+ "reflect-metadata": "^0.2.2",
30
+ "tslib": "^2.8.1",
31
+ "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.60.0",
32
+ "@voltrix/core": "0.3.0",
33
+ "@voltrix/injector": "0.2.2"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^20.0.0",
37
+ "typescript": "5.9.3",
38
+ "vitest": "^4.1.0",
39
+ "ws": "^8.18.0",
40
+ "@types/ws": "^8.5.10",
41
+ "@voltrix/decorator": "0.2.2",
42
+ "@voltrix/server": "0.2.1"
43
+ },
44
+ "files": [
45
+ "dist",
46
+ "README.md",
47
+ "LICENSE"
48
+ ],
49
+ "engines": {
50
+ "node": ">=18.0.0"
51
+ },
52
+ "publishConfig": {
53
+ "access": "public"
54
+ },
55
+ "scripts": {
56
+ "build": "tsc -p tsconfig.json",
57
+ "dev": "tsc --watch",
58
+ "test": "vitest run",
59
+ "type-check": "tsc --noEmit"
60
+ }
61
+ }