@scpxl/nodejs-framework 1.0.42 → 1.0.43

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,227 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+ import { Logger } from "../logger/index.js";
4
+ function matchByProperty(key, value) {
5
+ return (context) => {
6
+ try {
7
+ const messageValue = getNestedProperty(context.message, key);
8
+ return messageValue === value;
9
+ } catch {
10
+ return false;
11
+ }
12
+ };
13
+ }
14
+ __name(matchByProperty, "matchByProperty");
15
+ function matchByPropertyPredicate(key, predicate) {
16
+ return (context) => {
17
+ try {
18
+ const messageValue = getNestedProperty(context.message, key);
19
+ return predicate(messageValue);
20
+ } catch {
21
+ return false;
22
+ }
23
+ };
24
+ }
25
+ __name(matchByPropertyPredicate, "matchByPropertyPredicate");
26
+ function getNestedProperty(obj, path) {
27
+ const keys = path.split(".");
28
+ let current = obj;
29
+ for (const key of keys) {
30
+ if (current == null) {
31
+ return void 0;
32
+ }
33
+ current = current[key];
34
+ }
35
+ return current;
36
+ }
37
+ __name(getNestedProperty, "getNestedProperty");
38
+ function withErrorHandler(handler, onError, _throwError = false) {
39
+ return async (context) => {
40
+ try {
41
+ return await handler(context);
42
+ } catch (error) {
43
+ const err = error instanceof Error ? error : new Error(String(error));
44
+ if (onError) {
45
+ try {
46
+ await onError(err, context);
47
+ } catch (callbackError) {
48
+ Logger.error({
49
+ message: "Error handler callback failed",
50
+ meta: { originalError: err.message, callbackError }
51
+ });
52
+ }
53
+ }
54
+ if (_throwError) {
55
+ throw err;
56
+ }
57
+ }
58
+ };
59
+ }
60
+ __name(withErrorHandler, "withErrorHandler");
61
+ function withLogging(handler, handlerName = "subscriber-handler") {
62
+ return async (context) => {
63
+ const startTime = Date.now();
64
+ const messageKeys = context.message && typeof context.message === "object" ? Object.keys(context.message).slice(0, 5) : [];
65
+ Logger.info({
66
+ message: `${handlerName}: Starting handler execution`,
67
+ meta: {
68
+ channel: context.channel,
69
+ messageKeys
70
+ }
71
+ });
72
+ try {
73
+ const result = await handler(context);
74
+ const duration = Date.now() - startTime;
75
+ Logger.info({
76
+ message: `${handlerName}: Handler completed successfully`,
77
+ meta: { channel: context.channel, durationMs: duration }
78
+ });
79
+ return result;
80
+ } catch (error) {
81
+ const duration = Date.now() - startTime;
82
+ Logger.error({
83
+ message: `${handlerName}: Handler failed`,
84
+ meta: {
85
+ channel: context.channel,
86
+ error: error instanceof Error ? error.message : String(error),
87
+ durationMs: duration
88
+ }
89
+ });
90
+ throw error;
91
+ }
92
+ };
93
+ }
94
+ __name(withLogging, "withLogging");
95
+ function withRateLimit(handler, maxExecutions, windowMs, onRateLimited) {
96
+ const executionTimes = [];
97
+ return async (context) => {
98
+ const now = Date.now();
99
+ while (executionTimes.length > 0 && executionTimes[0] < now - windowMs) {
100
+ executionTimes.shift();
101
+ }
102
+ if (executionTimes.length >= maxExecutions) {
103
+ if (onRateLimited) {
104
+ await onRateLimited(context);
105
+ }
106
+ return;
107
+ }
108
+ executionTimes.push(now);
109
+ return await handler(context);
110
+ };
111
+ }
112
+ __name(withRateLimit, "withRateLimit");
113
+ function withRetry(handler, maxRetries, delayMs, backoffMultiplier = 1) {
114
+ return async (context) => {
115
+ let lastError = null;
116
+ let currentDelay = delayMs;
117
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
118
+ try {
119
+ return await handler(context);
120
+ } catch (error) {
121
+ lastError = error instanceof Error ? error : new Error(String(error));
122
+ if (attempt < maxRetries) {
123
+ await new Promise((resolve) => setTimeout(resolve, currentDelay));
124
+ currentDelay = Math.floor(currentDelay * backoffMultiplier);
125
+ }
126
+ }
127
+ }
128
+ throw lastError;
129
+ };
130
+ }
131
+ __name(withRetry, "withRetry");
132
+ function composeHandlers(handlers) {
133
+ return async (context) => {
134
+ for (const handler of handlers) {
135
+ await handler(context);
136
+ }
137
+ };
138
+ }
139
+ __name(composeHandlers, "composeHandlers");
140
+ function withFilter(predicate, handler) {
141
+ return async (context) => {
142
+ const shouldExecute = await predicate(context);
143
+ if (shouldExecute) {
144
+ return await handler(context);
145
+ }
146
+ };
147
+ }
148
+ __name(withFilter, "withFilter");
149
+ function withValidation(validator, handler) {
150
+ return async (context) => {
151
+ try {
152
+ await validator(context.message);
153
+ return await handler(context);
154
+ } catch (error) {
155
+ Logger.warn({
156
+ message: "Message validation failed",
157
+ meta: {
158
+ channel: context.channel,
159
+ error: error instanceof Error ? error.message : String(error)
160
+ }
161
+ });
162
+ throw error;
163
+ }
164
+ };
165
+ }
166
+ __name(withValidation, "withValidation");
167
+ function withMetadata(metadata, handler) {
168
+ return async (context) => {
169
+ const enrichedContext = {
170
+ ...context,
171
+ metadata
172
+ };
173
+ return await handler(enrichedContext);
174
+ };
175
+ }
176
+ __name(withMetadata, "withMetadata");
177
+ function withDebounce(handler, delayMs) {
178
+ const timers = /* @__PURE__ */ new Map();
179
+ return async (context) => {
180
+ const channel = context.channel;
181
+ const existingTimer = timers.get(channel);
182
+ if (existingTimer) {
183
+ clearTimeout(existingTimer);
184
+ }
185
+ return new Promise((resolve) => {
186
+ const timer = setTimeout(async () => {
187
+ try {
188
+ await handler(context);
189
+ } finally {
190
+ timers.delete(channel);
191
+ resolve();
192
+ }
193
+ }, delayMs);
194
+ timers.set(channel, timer);
195
+ });
196
+ };
197
+ }
198
+ __name(withDebounce, "withDebounce");
199
+ function withThrottle(handler, intervalMs) {
200
+ const lastExecutionTime = /* @__PURE__ */ new Map();
201
+ return async (context) => {
202
+ const channel = context.channel;
203
+ const now = Date.now();
204
+ const lastTime = lastExecutionTime.get(channel) ?? 0;
205
+ if (now - lastTime >= intervalMs) {
206
+ lastExecutionTime.set(channel, now);
207
+ return await handler(context);
208
+ }
209
+ };
210
+ }
211
+ __name(withThrottle, "withThrottle");
212
+ export {
213
+ composeHandlers,
214
+ getNestedProperty,
215
+ matchByProperty,
216
+ matchByPropertyPredicate,
217
+ withDebounce,
218
+ withErrorHandler,
219
+ withFilter,
220
+ withLogging,
221
+ withMetadata,
222
+ withRateLimit,
223
+ withRetry,
224
+ withThrottle,
225
+ withValidation
226
+ };
227
+ //# sourceMappingURL=subscriber-utils.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/websocket/subscriber-utils.ts"],
4
+ "sourcesContent": ["import type {\n WebSocketSubscriberHandler,\n WebSocketSubscriberHandlerContext,\n WebSocketSubscriberMatcher,\n} from './websocket.interface.js';\nimport { Logger } from '../logger/index.js';\n\n/**\n * Utility functions for building and composing WebSocket subscriber handlers\n */\n\n/**\n * Create a predicate matcher for message properties\n * @param key - The property key to check\n * @param value - The expected value\n */\nexport function matchByProperty(key: string, value: unknown): WebSocketSubscriberMatcher {\n return (context: WebSocketSubscriberHandlerContext) => {\n try {\n const messageValue = getNestedProperty(context.message, key);\n return messageValue === value;\n } catch {\n return false;\n }\n };\n}\n\n/**\n * Create a predicate matcher for message property patterns\n * @param key - The property key to check\n * @param predicate - Function to test the value\n */\nexport function matchByPropertyPredicate<_T = unknown>(\n key: string,\n predicate: (value: unknown) => boolean,\n): WebSocketSubscriberMatcher {\n return (context: WebSocketSubscriberHandlerContext) => {\n try {\n const messageValue = getNestedProperty(context.message, key);\n return predicate(messageValue);\n } catch {\n return false;\n }\n };\n}\n\n/**\n * Safely get nested property from an object\n * @param obj - The object to search\n * @param path - Dot-notation path (e.g., 'user.id' or 'data.items.0.name')\n */\nexport function getNestedProperty(obj: any, path: string): unknown {\n const keys = path.split('.');\n let current = obj;\n\n for (const key of keys) {\n if (current == null) {\n return undefined;\n }\n // eslint-disable-next-line security/detect-object-injection\n current = current[key];\n }\n\n return current;\n}\n\n/**\n * Wrap a handler with error handling\n * @param handler - The handler to wrap\n * @param onError - Error handler callback\n * @param throwError - Whether to rethrow the error after handling\n */\nexport function withErrorHandler<TMessage = any>(\n handler: WebSocketSubscriberHandler<TMessage>,\n onError?: (error: Error, context: WebSocketSubscriberHandlerContext<TMessage>) => void | Promise<void>,\n _throwError = false,\n): WebSocketSubscriberHandler<TMessage> {\n return async (context: WebSocketSubscriberHandlerContext<TMessage>) => {\n try {\n return await handler(context);\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n\n if (onError) {\n try {\n await onError(err, context);\n } catch (callbackError) {\n Logger.error({\n message: 'Error handler callback failed',\n meta: { originalError: err.message, callbackError },\n });\n }\n }\n\n if (_throwError) {\n throw err;\n }\n }\n };\n}\n\n/**\n * Wrap a handler with logging\n * @param handler - The handler to wrap\n * @param handlerName - Name for logging purposes\n */\nexport function withLogging<TMessage = any>(\n handler: WebSocketSubscriberHandler<TMessage>,\n handlerName = 'subscriber-handler',\n): WebSocketSubscriberHandler<TMessage> {\n return async (context: WebSocketSubscriberHandlerContext<TMessage>) => {\n const startTime = Date.now();\n const messageKeys =\n context.message && typeof context.message === 'object'\n ? Object.keys(context.message as Record<string, unknown>).slice(0, 5)\n : [];\n Logger.info({\n message: `${handlerName}: Starting handler execution`,\n meta: {\n channel: context.channel,\n messageKeys,\n },\n });\n\n try {\n const result = await handler(context);\n const duration = Date.now() - startTime;\n Logger.info({\n message: `${handlerName}: Handler completed successfully`,\n meta: { channel: context.channel, durationMs: duration },\n });\n return result;\n } catch (error) {\n const duration = Date.now() - startTime;\n Logger.error({\n message: `${handlerName}: Handler failed`,\n meta: {\n channel: context.channel,\n error: error instanceof Error ? error.message : String(error),\n durationMs: duration,\n },\n });\n throw error;\n }\n };\n}\n\n/**\n * Wrap a handler with rate limiting\n * @param handler - The handler to wrap\n * @param maxExecutions - Max executions allowed\n * @param windowMs - Time window in milliseconds\n * @param onRateLimited - Optional callback when rate limited\n */\nexport function withRateLimit<TMessage = any>(\n handler: WebSocketSubscriberHandler<TMessage>,\n maxExecutions: number,\n windowMs: number,\n onRateLimited?: (context: WebSocketSubscriberHandlerContext<TMessage>) => void | Promise<void>,\n): WebSocketSubscriberHandler<TMessage> {\n const executionTimes: number[] = [];\n\n return async (context: WebSocketSubscriberHandlerContext<TMessage>) => {\n const now = Date.now();\n\n // Remove old entries outside the window\n while (executionTimes.length > 0 && executionTimes[0] < now - windowMs) {\n executionTimes.shift();\n }\n\n if (executionTimes.length >= maxExecutions) {\n if (onRateLimited) {\n await onRateLimited(context);\n }\n return;\n }\n\n executionTimes.push(now);\n return await handler(context);\n };\n}\n\n/**\n * Wrap a handler with retry logic\n * @param handler - The handler to wrap\n * @param maxRetries - Maximum number of retries\n * @param delayMs - Delay between retries in milliseconds\n * @param backoffMultiplier - Multiplier for exponential backoff (default: 1, no backoff)\n */\nexport function withRetry<TMessage = any>(\n handler: WebSocketSubscriberHandler<TMessage>,\n maxRetries: number,\n delayMs: number,\n backoffMultiplier = 1,\n): WebSocketSubscriberHandler<TMessage> {\n return async (context: WebSocketSubscriberHandlerContext<TMessage>) => {\n let lastError: Error | null = null;\n let currentDelay = delayMs;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n return await handler(context);\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n if (attempt < maxRetries) {\n await new Promise(resolve => setTimeout(resolve, currentDelay));\n currentDelay = Math.floor(currentDelay * backoffMultiplier);\n }\n }\n }\n\n throw lastError;\n };\n}\n\n/**\n * Compose multiple handlers into a single handler\n * Executes handlers sequentially, passing context through each one\n * @param handlers - Array of handlers to compose\n */\nexport function composeHandlers<TMessage = any>(\n handlers: WebSocketSubscriberHandler<TMessage>[],\n): WebSocketSubscriberHandler<TMessage> {\n return async (context: WebSocketSubscriberHandlerContext<TMessage>) => {\n for (const handler of handlers) {\n await handler(context);\n }\n };\n}\n\n/**\n * Create a filter handler that conditionally executes based on a predicate\n * @param predicate - Function that returns true if handler should execute\n * @param handler - The handler to execute conditionally\n */\nexport function withFilter<TMessage = any>(\n predicate: (context: WebSocketSubscriberHandlerContext<TMessage>) => boolean | Promise<boolean>,\n handler: WebSocketSubscriberHandler<TMessage>,\n): WebSocketSubscriberHandler<TMessage> {\n return async (context: WebSocketSubscriberHandlerContext<TMessage>) => {\n const shouldExecute = await predicate(context);\n if (shouldExecute) {\n return await handler(context);\n }\n };\n}\n\n/**\n * Validate message structure before execution\n * @param validator - Function that validates the message and throws if invalid\n * @param handler - The handler to wrap\n */\nexport function withValidation<TMessage = any>(\n validator: (message: TMessage) => void | Promise<void>,\n handler: WebSocketSubscriberHandler<TMessage>,\n): WebSocketSubscriberHandler<TMessage> {\n return async (context: WebSocketSubscriberHandlerContext<TMessage>) => {\n try {\n await validator(context.message);\n return await handler(context);\n } catch (error) {\n Logger.warn({\n message: 'Message validation failed',\n meta: {\n channel: context.channel,\n error: error instanceof Error ? error.message : String(error),\n },\n });\n throw error;\n }\n };\n}\n\n/**\n * Add metadata/context to a handler execution\n * @param metadata - Metadata to add to context\n * @param handler - The handler to wrap\n */\nexport function withMetadata<TMessage = any>(\n metadata: Record<string, unknown>,\n handler: WebSocketSubscriberHandler<TMessage>,\n): WebSocketSubscriberHandler<TMessage> {\n return async (context: WebSocketSubscriberHandlerContext<TMessage>) => {\n const enrichedContext = {\n ...context,\n metadata,\n } as any;\n\n return await handler(enrichedContext);\n };\n}\n\n/**\n * Debounce handler execution by channel\n * @param handler - The handler to wrap\n * @param delayMs - Debounce delay in milliseconds\n */\nexport function withDebounce<TMessage = any>(\n handler: WebSocketSubscriberHandler<TMessage>,\n delayMs: number,\n): WebSocketSubscriberHandler<TMessage> {\n const timers = new Map<string, NodeJS.Timeout>();\n\n return async (context: WebSocketSubscriberHandlerContext<TMessage>) => {\n const channel = context.channel;\n\n // Cancel previous timer for this channel\n const existingTimer = timers.get(channel);\n if (existingTimer) {\n clearTimeout(existingTimer);\n }\n\n // Set new timer\n return new Promise<void>(resolve => {\n const timer = setTimeout(async () => {\n try {\n await handler(context);\n } finally {\n timers.delete(channel);\n resolve();\n }\n }, delayMs);\n\n timers.set(channel, timer);\n });\n };\n}\n\n/**\n * Throttle handler execution by channel\n * @param handler - The handler to wrap\n * @param intervalMs - Throttle interval in milliseconds\n */\nexport function withThrottle<TMessage = any>(\n handler: WebSocketSubscriberHandler<TMessage>,\n intervalMs: number,\n): WebSocketSubscriberHandler<TMessage> {\n const lastExecutionTime = new Map<string, number>();\n\n return async (context: WebSocketSubscriberHandlerContext<TMessage>) => {\n const channel = context.channel;\n const now = Date.now();\n const lastTime = lastExecutionTime.get(channel) ?? 0;\n\n if (now - lastTime >= intervalMs) {\n lastExecutionTime.set(channel, now);\n return await handler(context);\n }\n };\n}\n"],
5
+ "mappings": ";;AAKA,SAAS,cAAc;AAWhB,SAAS,gBAAgB,KAAa,OAA4C;AACvF,SAAO,CAAC,YAA+C;AACrD,QAAI;AACF,YAAM,eAAe,kBAAkB,QAAQ,SAAS,GAAG;AAC3D,aAAO,iBAAiB;AAAA,IAC1B,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;AATgB;AAgBT,SAAS,yBACd,KACA,WAC4B;AAC5B,SAAO,CAAC,YAA+C;AACrD,QAAI;AACF,YAAM,eAAe,kBAAkB,QAAQ,SAAS,GAAG;AAC3D,aAAO,UAAU,YAAY;AAAA,IAC/B,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAZgB;AAmBT,SAAS,kBAAkB,KAAU,MAAuB;AACjE,QAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,MAAI,UAAU;AAEd,aAAW,OAAO,MAAM;AACtB,QAAI,WAAW,MAAM;AACnB,aAAO;AAAA,IACT;AAEA,cAAU,QAAQ,GAAG;AAAA,EACvB;AAEA,SAAO;AACT;AAbgB;AAqBT,SAAS,iBACd,SACA,SACA,cAAc,OACwB;AACtC,SAAO,OAAO,YAAyD;AACrE,QAAI;AACF,aAAO,MAAM,QAAQ,OAAO;AAAA,IAC9B,SAAS,OAAO;AACd,YAAM,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAEpE,UAAI,SAAS;AACX,YAAI;AACF,gBAAM,QAAQ,KAAK,OAAO;AAAA,QAC5B,SAAS,eAAe;AACtB,iBAAO,MAAM;AAAA,YACX,SAAS;AAAA,YACT,MAAM,EAAE,eAAe,IAAI,SAAS,cAAc;AAAA,UACpD,CAAC;AAAA,QACH;AAAA,MACF;AAEA,UAAI,aAAa;AACf,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;AA3BgB;AAkCT,SAAS,YACd,SACA,cAAc,sBACwB;AACtC,SAAO,OAAO,YAAyD;AACrE,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,cACJ,QAAQ,WAAW,OAAO,QAAQ,YAAY,WAC1C,OAAO,KAAK,QAAQ,OAAkC,EAAE,MAAM,GAAG,CAAC,IAClE,CAAC;AACP,WAAO,KAAK;AAAA,MACV,SAAS,GAAG,WAAW;AAAA,MACvB,MAAM;AAAA,QACJ,SAAS,QAAQ;AAAA,QACjB;AAAA,MACF;AAAA,IACF,CAAC;AAED,QAAI;AACF,YAAM,SAAS,MAAM,QAAQ,OAAO;AACpC,YAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,aAAO,KAAK;AAAA,QACV,SAAS,GAAG,WAAW;AAAA,QACvB,MAAM,EAAE,SAAS,QAAQ,SAAS,YAAY,SAAS;AAAA,MACzD,CAAC;AACD,aAAO;AAAA,IACT,SAAS,OAAO;AACd,YAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,aAAO,MAAM;AAAA,QACX,SAAS,GAAG,WAAW;AAAA,QACvB,MAAM;AAAA,UACJ,SAAS,QAAQ;AAAA,UACjB,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,UAC5D,YAAY;AAAA,QACd;AAAA,MACF,CAAC;AACD,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAvCgB;AAgDT,SAAS,cACd,SACA,eACA,UACA,eACsC;AACtC,QAAM,iBAA2B,CAAC;AAElC,SAAO,OAAO,YAAyD;AACrE,UAAM,MAAM,KAAK,IAAI;AAGrB,WAAO,eAAe,SAAS,KAAK,eAAe,CAAC,IAAI,MAAM,UAAU;AACtE,qBAAe,MAAM;AAAA,IACvB;AAEA,QAAI,eAAe,UAAU,eAAe;AAC1C,UAAI,eAAe;AACjB,cAAM,cAAc,OAAO;AAAA,MAC7B;AACA;AAAA,IACF;AAEA,mBAAe,KAAK,GAAG;AACvB,WAAO,MAAM,QAAQ,OAAO;AAAA,EAC9B;AACF;AA1BgB;AAmCT,SAAS,UACd,SACA,YACA,SACA,oBAAoB,GACkB;AACtC,SAAO,OAAO,YAAyD;AACrE,QAAI,YAA0B;AAC9B,QAAI,eAAe;AAEnB,aAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACtD,UAAI;AACF,eAAO,MAAM,QAAQ,OAAO;AAAA,MAC9B,SAAS,OAAO;AACd,oBAAY,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAEpE,YAAI,UAAU,YAAY;AACxB,gBAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,YAAY,CAAC;AAC9D,yBAAe,KAAK,MAAM,eAAe,iBAAiB;AAAA,QAC5D;AAAA,MACF;AAAA,IACF;AAEA,UAAM;AAAA,EACR;AACF;AAzBgB;AAgCT,SAAS,gBACd,UACsC;AACtC,SAAO,OAAO,YAAyD;AACrE,eAAW,WAAW,UAAU;AAC9B,YAAM,QAAQ,OAAO;AAAA,IACvB;AAAA,EACF;AACF;AARgB;AAeT,SAAS,WACd,WACA,SACsC;AACtC,SAAO,OAAO,YAAyD;AACrE,UAAM,gBAAgB,MAAM,UAAU,OAAO;AAC7C,QAAI,eAAe;AACjB,aAAO,MAAM,QAAQ,OAAO;AAAA,IAC9B;AAAA,EACF;AACF;AAVgB;AAiBT,SAAS,eACd,WACA,SACsC;AACtC,SAAO,OAAO,YAAyD;AACrE,QAAI;AACF,YAAM,UAAU,QAAQ,OAAO;AAC/B,aAAO,MAAM,QAAQ,OAAO;AAAA,IAC9B,SAAS,OAAO;AACd,aAAO,KAAK;AAAA,QACV,SAAS;AAAA,QACT,MAAM;AAAA,UACJ,SAAS,QAAQ;AAAA,UACjB,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,QAC9D;AAAA,MACF,CAAC;AACD,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAnBgB;AA0BT,SAAS,aACd,UACA,SACsC;AACtC,SAAO,OAAO,YAAyD;AACrE,UAAM,kBAAkB;AAAA,MACtB,GAAG;AAAA,MACH;AAAA,IACF;AAEA,WAAO,MAAM,QAAQ,eAAe;AAAA,EACtC;AACF;AAZgB;AAmBT,SAAS,aACd,SACA,SACsC;AACtC,QAAM,SAAS,oBAAI,IAA4B;AAE/C,SAAO,OAAO,YAAyD;AACrE,UAAM,UAAU,QAAQ;AAGxB,UAAM,gBAAgB,OAAO,IAAI,OAAO;AACxC,QAAI,eAAe;AACjB,mBAAa,aAAa;AAAA,IAC5B;AAGA,WAAO,IAAI,QAAc,aAAW;AAClC,YAAM,QAAQ,WAAW,YAAY;AACnC,YAAI;AACF,gBAAM,QAAQ,OAAO;AAAA,QACvB,UAAE;AACA,iBAAO,OAAO,OAAO;AACrB,kBAAQ;AAAA,QACV;AAAA,MACF,GAAG,OAAO;AAEV,aAAO,IAAI,SAAS,KAAK;AAAA,IAC3B,CAAC;AAAA,EACH;AACF;AA7BgB;AAoCT,SAAS,aACd,SACA,YACsC;AACtC,QAAM,oBAAoB,oBAAI,IAAoB;AAElD,SAAO,OAAO,YAAyD;AACrE,UAAM,UAAU,QAAQ;AACxB,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,WAAW,kBAAkB,IAAI,OAAO,KAAK;AAEnD,QAAI,MAAM,YAAY,YAAY;AAChC,wBAAkB,IAAI,SAAS,GAAG;AAClC,aAAO,MAAM,QAAQ,OAAO;AAAA,IAC9B;AAAA,EACF;AACF;AAhBgB;",
6
+ "names": []
7
+ }
@@ -78,12 +78,52 @@ export default class WebSocketServer extends WebSocketBase {
78
78
  * Broadcast a message to all connected WebSocket clients
79
79
  * @param data - The data to broadcast (will be JSON stringified)
80
80
  * @param excludeClientId - Optional client ID to exclude from broadcast
81
+ * @param predicate - Optional function to filter clients
81
82
  */
82
- broadcastToAllClients({ data, excludeClientId, }: {
83
+ broadcastToAllClients({ data, excludeClientId, predicate, }: {
83
84
  data: {
84
85
  [key: string]: any;
85
86
  };
86
87
  excludeClientId?: string;
88
+ predicate?: (clientData: {
89
+ clientId: string;
90
+ userData: any;
91
+ }) => boolean;
92
+ }): void;
93
+ /**
94
+ * Broadcast a message to all clients in a specific room
95
+ * @param roomName - The room to broadcast to
96
+ * @param data - The data to broadcast
97
+ * @param excludeClientId - Optional client ID to exclude from broadcast
98
+ */
99
+ broadcastToRoom({ roomName, data, excludeClientId, }: {
100
+ roomName: string;
101
+ data: {
102
+ [key: string]: any;
103
+ };
104
+ excludeClientId?: string;
105
+ }): void;
106
+ /**
107
+ * Broadcast a message to specific users
108
+ * @param userIds - Array of user IDs to broadcast to
109
+ * @param data - The data to broadcast
110
+ */
111
+ broadcastToUsers({ userIds, data }: {
112
+ userIds: (string | number)[];
113
+ data: {
114
+ [key: string]: any;
115
+ };
116
+ }): void;
117
+ /**
118
+ * Broadcast a message to a specific client
119
+ * @param clientId - The client ID to send to
120
+ * @param data - The data to send
121
+ */
122
+ broadcastToClient({ clientId, data }: {
123
+ clientId: string;
124
+ data: {
125
+ [key: string]: any;
126
+ };
87
127
  }): void;
88
128
  sendMessageError({ webSocketClientId, error }: {
89
129
  webSocketClientId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"websocket-server.d.ts","sourceRoot":"","sources":["../../src/websocket/websocket-server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"websocket-server.d.ts","sourceRoot":"","sources":["../../src/websocket/websocket-server.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,7 @@ import WebSocketRoomManager from "./websocket-room-manager.js";
14
14
  import logger from "../logger/logger.js";
15
15
  import { WebSocketAuthService } from "./websocket-auth.js";
16
16
  import { File, Loader } from "../util/index.js";
17
+ import { executeWithMiddleware } from "./subscriber-middleware.js";
17
18
  class WebSocketServer extends WebSocketBase {
18
19
  static {
19
20
  __name(this, "WebSocketServer");
@@ -326,15 +327,19 @@ class WebSocketServer extends WebSocketBase {
326
327
  return;
327
328
  }
328
329
  const orderedHandlers = Array.from(candidates).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
329
- for (const handler of orderedHandlers) {
330
+ for (const handlerDef of orderedHandlers) {
330
331
  try {
331
- await handler.handle(context);
332
+ if (handlerDef.middleware && handlerDef.middleware.length > 0) {
333
+ await executeWithMiddleware(handlerDef.handle, handlerDef.middleware, context);
334
+ } else {
335
+ await handlerDef.handle(context);
336
+ }
332
337
  } catch (error) {
333
338
  logger.error({
334
339
  error,
335
340
  message: "WebSocket subscriber handler failed",
336
341
  meta: {
337
- Handler: handler.name ?? "(anonymous)",
342
+ Handler: handlerDef.name ?? "(anonymous)",
338
343
  Channel: channel
339
344
  }
340
345
  });
@@ -729,10 +734,12 @@ class WebSocketServer extends WebSocketBase {
729
734
  * Broadcast a message to all connected WebSocket clients
730
735
  * @param data - The data to broadcast (will be JSON stringified)
731
736
  * @param excludeClientId - Optional client ID to exclude from broadcast
737
+ * @param predicate - Optional function to filter clients
732
738
  */
733
739
  broadcastToAllClients({
734
740
  data,
735
- excludeClientId
741
+ excludeClientId,
742
+ predicate
736
743
  }) {
737
744
  if (!this.server) {
738
745
  log("Server not started when broadcasting to all clients");
@@ -742,15 +749,91 @@ class WebSocketServer extends WebSocketBase {
742
749
  if (client.readyState !== WebSocket.OPEN) {
743
750
  continue;
744
751
  }
745
- if (excludeClientId) {
746
- const clientId = this.clientManager.getClientId({ ws: client });
747
- if (clientId === excludeClientId) {
752
+ const clientId = this.clientManager.getClientId({ ws: client });
753
+ if (excludeClientId && clientId === excludeClientId) {
754
+ continue;
755
+ }
756
+ if (predicate && clientId) {
757
+ const clientData = this.clientManager.getClient({ clientId });
758
+ if (!clientData || !predicate({ clientId, userData: clientData.user })) {
748
759
  continue;
749
760
  }
750
761
  }
751
762
  client.send(JSON.stringify(data));
752
763
  }
753
764
  }
765
+ /**
766
+ * Broadcast a message to all clients in a specific room
767
+ * @param roomName - The room to broadcast to
768
+ * @param data - The data to broadcast
769
+ * @param excludeClientId - Optional client ID to exclude from broadcast
770
+ */
771
+ broadcastToRoom({
772
+ roomName,
773
+ data,
774
+ excludeClientId
775
+ }) {
776
+ if (!this.server) {
777
+ log("Server not started when broadcasting to room", { roomName });
778
+ return;
779
+ }
780
+ const room = this.roomManager.rooms.get(roomName);
781
+ if (!room) {
782
+ log("Room not found when broadcasting", { roomName });
783
+ return;
784
+ }
785
+ for (const clientId of room) {
786
+ if (excludeClientId && clientId === excludeClientId) {
787
+ continue;
788
+ }
789
+ const client = this.clientManager.getClient({ clientId });
790
+ if (!client?.ws || client.ws.readyState !== WebSocket.OPEN) {
791
+ continue;
792
+ }
793
+ client.ws.send(JSON.stringify(data));
794
+ }
795
+ }
796
+ /**
797
+ * Broadcast a message to specific users
798
+ * @param userIds - Array of user IDs to broadcast to
799
+ * @param data - The data to broadcast
800
+ */
801
+ broadcastToUsers({ userIds, data }) {
802
+ if (!this.server) {
803
+ log("Server not started when broadcasting to users");
804
+ return;
805
+ }
806
+ const userIdSet = new Set(userIds.map((id) => String(id)));
807
+ for (const client of this.server.clients) {
808
+ if (client.readyState !== WebSocket.OPEN) {
809
+ continue;
810
+ }
811
+ const clientId = this.clientManager.getClientId({ ws: client });
812
+ const clientData = clientId ? this.clientManager.getClient({ clientId }) : null;
813
+ if (!clientData?.user) {
814
+ continue;
815
+ }
816
+ const clientUserId = String(clientData.user.id ?? clientData.user.userId);
817
+ if (userIdSet.has(clientUserId)) {
818
+ client.send(JSON.stringify(data));
819
+ }
820
+ }
821
+ }
822
+ /**
823
+ * Broadcast a message to a specific client
824
+ * @param clientId - The client ID to send to
825
+ * @param data - The data to send
826
+ */
827
+ broadcastToClient({ clientId, data }) {
828
+ const client = this.clientManager.getClient({ clientId });
829
+ if (!client?.ws || client.ws.readyState !== WebSocket.OPEN) {
830
+ log("Client not found or not connected when broadcasting to specific client", {
831
+ clientId
832
+ });
833
+ return;
834
+ }
835
+ client.ws.send(JSON.stringify(data));
836
+ }
754
837
  sendMessageError({ webSocketClientId, error }) {
755
838
  const client = this.clientManager.getClient({
756
839
  clientId: webSocketClientId