@scpxl/nodejs-framework 1.0.48 → 1.0.50
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.
- package/dist/application/base-application.d.ts.map +1 -1
- package/dist/application/base-application.js +10 -0
- package/dist/application/base-application.js.map +2 -2
- package/dist/application/index.d.ts +2 -0
- package/dist/application/index.d.ts.map +1 -1
- package/dist/application/index.js +3 -1
- package/dist/application/index.js.map +2 -2
- package/dist/application/worker-application.d.ts +72 -0
- package/dist/application/worker-application.d.ts.map +1 -0
- package/dist/application/worker-application.interface.d.ts +17 -0
- package/dist/application/worker-application.interface.d.ts.map +1 -0
- package/dist/application/worker-application.interface.js +1 -0
- package/dist/application/worker-application.interface.js.map +7 -0
- package/dist/application/worker-application.js +99 -0
- package/dist/application/worker-application.js.map +7 -0
- package/dist/lifecycle/lifecycle-manager.d.ts +6 -0
- package/dist/lifecycle/lifecycle-manager.d.ts.map +1 -1
- package/dist/lifecycle/lifecycle-manager.js +28 -0
- package/dist/lifecycle/lifecycle-manager.js.map +2 -2
- package/dist/queue/manager.d.ts +6 -0
- package/dist/queue/manager.d.ts.map +1 -1
- package/dist/queue/manager.js +39 -4
- package/dist/queue/manager.js.map +2 -2
- package/dist/queue/worker.d.ts +5 -0
- package/dist/queue/worker.d.ts.map +1 -1
- package/dist/queue/worker.js +8 -0
- package/dist/queue/worker.js.map +2 -2
- package/dist/redis/index.d.ts +1 -1
- package/dist/redis/index.d.ts.map +1 -1
- package/dist/redis/index.js +3 -2
- package/dist/redis/index.js.map +2 -2
- package/dist/redis/instance.js +1 -1
- package/dist/redis/instance.js.map +2 -2
- package/dist/redis/manager.d.ts +5 -0
- package/dist/redis/manager.d.ts.map +1 -1
- package/dist/redis/manager.js +13 -0
- package/dist/redis/manager.js.map +2 -2
- package/dist/util/file.d.ts +13 -2
- package/dist/util/file.d.ts.map +1 -1
- package/dist/util/file.js.map +2 -2
- package/dist/webserver/controller/entity.d.ts +1 -0
- package/dist/webserver/controller/entity.d.ts.map +1 -1
- package/dist/webserver/controller/entity.js +7 -0
- package/dist/webserver/controller/entity.js.map +2 -2
- package/dist/websocket/websocket-client-manager.d.ts +2 -0
- package/dist/websocket/websocket-client-manager.d.ts.map +1 -1
- package/dist/websocket/websocket-client-manager.js +8 -1
- package/dist/websocket/websocket-client-manager.js.map +2 -2
- package/dist/websocket/websocket-client.d.ts +5 -0
- package/dist/websocket/websocket-client.d.ts.map +1 -1
- package/dist/websocket/websocket-client.js +22 -0
- package/dist/websocket/websocket-client.js.map +2 -2
- package/package.json +1 -1
package/dist/util/file.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/util/file.ts"],
|
|
4
|
-
"sourcesContent": ["import * as fs from 'fs';\nimport { access, mkdir } from 'fs/promises';\nimport * as path from 'path';\nimport * as https from 'https';\nimport { pipeline } from 'stream';\nimport { promisify } from 'node:util';\nimport ffmpeg from 'fluent-ffmpeg';\n\nconst pipelineAsync = promisify(pipeline);\n\n/**\n * Check if a file or directory exists asynchronously\n * @param pathToCheck - Path to check\n * @returns Promise<boolean> - true if exists, false otherwise\n */\nasync function pathExists(pathToCheck: string): Promise<boolean> {\n try {\n await access(pathToCheck);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Ensure directory exists, create if it doesn't\n * @param dirPath - Directory path to ensure\n */\nasync function ensureDir(dirPath: string): Promise<void> {\n try {\n await access(dirPath);\n } catch {\n await mkdir(dirPath, { recursive: true });\n }\n}\n\nasync function convertFile({\n inputFilePath,\n outputFilePath,\n format,\n}: {\n inputFilePath: string;\n outputFilePath: string;\n format: string;\n}): Promise<void> {\n return new Promise((resolve, reject) => {\n console.log(`Starting conversion: ${inputFilePath} -> ${outputFilePath} (format: ${format})`);\n\n const command = ffmpeg(inputFilePath)\n .output(outputFilePath)\n .outputFormat(format === 'jpg' ? 'mjpeg' : format) // Using 'mjpeg' for jpg\n .on('progress', (progress: any) => {\n console.log(`Processing: ${Math.round(progress.percent)}% done`);\n })\n .on('end', () => {\n console.log('Conversion finished successfully');\n resolve();\n })\n .on('error', (err: Error) => {\n console.error('Error during conversion:', err);\n reject(err);\n });\n\n // Start processing\n command.run();\n });\n}\n\n/**\n * Copy a file or directory synchronously\n *\n * @param src The source path\n * @param dest The destination path\n */\nfunction copySync(src: string, dest: string): void {\n const stats = fs.statSync(src);\n\n if (stats.isDirectory()) {\n // Create destination directory if it doesn't exist\n if (!fs.existsSync(dest)) {\n fs.mkdirSync(dest);\n }\n\n // Read directory contents\n for (const entry of fs.readdirSync(src)) {\n const srcPath = path.join(src, entry);\n const destPath = path.join(dest, entry);\n copySync(srcPath, destPath);\n }\n } else {\n // Copy file\n fs.copyFileSync(src, dest);\n }\n}\n\n/**\n * Download file from URL\n *\n * @param url The URL to download the file from\n * @param destinationPath The path to save the downloaded file\n */\nasync function downloadFile({ url, destinationPath }: { url: string; destinationPath: string }): Promise<void> {\n return new Promise((resolve, reject) => {\n const file = fs.createWriteStream(destinationPath);\n\n https\n .get(url, response => {\n // Check if response status is OK (200\u2013299)\n if (response.statusCode && response.statusCode >= 200 && response.statusCode < 300) {\n pipelineAsync(response, file)\n .then(() => resolve())\n .catch(err => {\n fs.unlink(destinationPath, () => reject(err)); // Clean up partially written file on error\n });\n } else {\n fs.unlink(destinationPath, () => {\n reject(new Error(`Failed to download file, status code: ${response.statusCode}`));\n });\n }\n })\n .on('error', err => {\n fs.unlink(destinationPath, () => reject(err)); // Handle request errors\n });\n\n // Handle file stream errors\n file.on('error', err => {\n fs.unlink(destinationPath, () => reject(err));\n });\n });\n}\n\n/**\n * Format file size.\n *\n * @param bytes The file size in bytes\n */\nfunction formatFileSize({ bytes }: { bytes: number }): string {\n if (bytes === 0) return '0 bytes';\n const units = ['bytes', 'kB', 'MB', 'GB', 'TB'] as const;\n let idx = Math.floor(Math.log(bytes) / Math.log(1024));\n if (idx < 0) idx = 0;\n if (idx >= units.length) idx = units.length - 1;\n const fileSize = (bytes / Math.pow(1024, idx)).toFixed(1);\n let unit: string;\n switch (idx) {\n case 0:\n unit = 'bytes';\n break;\n case 1:\n unit = 'kB';\n break;\n case 2:\n unit = 'MB';\n break;\n case 3:\n unit = 'GB';\n break;\n default:\n unit = 'TB';\n }\n return `${fileSize} ${unit}`;\n}\n\n/**\n * Remove a file or directory synchronously\n *\n * @param target The path to the file or directory to remove\n */\nfunction removeSync(target: string): void {\n if (fs.existsSync(target)) {\n const stats = fs.statSync(target);\n\n if (stats.isDirectory()) {\n // Read the directory contents\n for (const entry of fs.readdirSync(target)) {\n const entryPath = path.join(target, entry);\n removeSync(entryPath);\n }\n\n // Remove the directory itself\n fs.rmdirSync(target);\n } else {\n // Remove the file\n fs.unlinkSync(target);\n }\n } else {\n console.warn(`Path ${target} does not exist.`);\n }\n}\n\nexport default {\n convertFile,\n copySync,\n downloadFile,\n formatFileSize,\n removeSync,\n pathExists,\n ensureDir,\n};\n"],
|
|
5
|
-
"mappings": ";;AAAA,YAAY,QAAQ;AACpB,SAAS,QAAQ,aAAa;AAC9B,YAAY,UAAU;AACtB,YAAY,WAAW;AACvB,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAC1B,OAAO,YAAY;AAEnB,MAAM,gBAAgB,UAAU,QAAQ;AAOxC,eAAe,WAAW,aAAuC;AAC/D,MAAI;AACF,UAAM,OAAO,WAAW;AACxB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAPe;AAaf,eAAe,UAAU,SAAgC;AACvD,MAAI;AACF,UAAM,OAAO,OAAO;AAAA,EACtB,QAAQ;AACN,UAAM,MAAM,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,EAC1C;AACF;AANe;AAQf,eAAe,YAAY;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AACF,GAIkB;AAChB,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,YAAQ,IAAI,wBAAwB,aAAa,OAAO,cAAc,aAAa,MAAM,GAAG;AAE5F,UAAM,UAAU,OAAO,aAAa,EACjC,OAAO,cAAc,EACrB,aAAa,WAAW,QAAQ,UAAU,MAAM,EAChD,GAAG,YAAY,CAAC,aAAkB;AACjC,cAAQ,IAAI,eAAe,KAAK,MAAM,SAAS,OAAO,CAAC,QAAQ;AAAA,IACjE,CAAC,EACA,GAAG,OAAO,MAAM;AACf,cAAQ,IAAI,kCAAkC;AAC9C,cAAQ;AAAA,IACV,CAAC,EACA,GAAG,SAAS,CAAC,QAAe;AAC3B,cAAQ,MAAM,4BAA4B,GAAG;AAC7C,aAAO,GAAG;AAAA,IACZ,CAAC;AAGH,YAAQ,IAAI;AAAA,EACd,CAAC;AACH;AA9Be;
|
|
4
|
+
"sourcesContent": ["import * as fs from 'fs';\nimport { access, mkdir } from 'fs/promises';\nimport * as path from 'path';\nimport * as https from 'https';\nimport { pipeline } from 'stream';\nimport { promisify } from 'node:util';\nimport ffmpeg from 'fluent-ffmpeg';\n\nconst pipelineAsync = promisify(pipeline);\n\n/**\n * Check if a file or directory exists asynchronously\n * @param pathToCheck - Path to check\n * @returns Promise<boolean> - true if exists, false otherwise\n */\nasync function pathExists(pathToCheck: string): Promise<boolean> {\n try {\n await access(pathToCheck);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Ensure directory exists, create if it doesn't\n * @param dirPath - Directory path to ensure\n */\nasync function ensureDir(dirPath: string): Promise<void> {\n try {\n await access(dirPath);\n } catch {\n await mkdir(dirPath, { recursive: true });\n }\n}\n\nasync function convertFile({\n inputFilePath,\n outputFilePath,\n format,\n}: {\n inputFilePath: string;\n outputFilePath: string;\n format: string;\n}): Promise<void> {\n return new Promise((resolve, reject) => {\n console.log(`Starting conversion: ${inputFilePath} -> ${outputFilePath} (format: ${format})`);\n\n const command = ffmpeg(inputFilePath)\n .output(outputFilePath)\n .outputFormat(format === 'jpg' ? 'mjpeg' : format) // Using 'mjpeg' for jpg\n .on('progress', (progress: any) => {\n console.log(`Processing: ${Math.round(progress.percent)}% done`);\n })\n .on('end', () => {\n console.log('Conversion finished successfully');\n resolve();\n })\n .on('error', (err: Error) => {\n console.error('Error during conversion:', err);\n reject(err);\n });\n\n // Start processing\n command.run();\n });\n}\n\n/**\n * Copy a file or directory synchronously.\n *\n * **Note**: This function uses synchronous file system operations which block\n * the event loop. It is intended for use during application initialization,\n * build scripts, or CLI tools where blocking is acceptable.\n * For production runtime code with high concurrency, consider using async alternatives.\n *\n * @param src The source path\n * @param dest The destination path\n */\nfunction copySync(src: string, dest: string): void {\n const stats = fs.statSync(src);\n\n if (stats.isDirectory()) {\n // Create destination directory if it doesn't exist\n if (!fs.existsSync(dest)) {\n fs.mkdirSync(dest);\n }\n\n // Read directory contents\n for (const entry of fs.readdirSync(src)) {\n const srcPath = path.join(src, entry);\n const destPath = path.join(dest, entry);\n copySync(srcPath, destPath);\n }\n } else {\n // Copy file\n fs.copyFileSync(src, dest);\n }\n}\n\n/**\n * Download file from URL\n *\n * @param url The URL to download the file from\n * @param destinationPath The path to save the downloaded file\n */\nasync function downloadFile({ url, destinationPath }: { url: string; destinationPath: string }): Promise<void> {\n return new Promise((resolve, reject) => {\n const file = fs.createWriteStream(destinationPath);\n\n https\n .get(url, response => {\n // Check if response status is OK (200\u2013299)\n if (response.statusCode && response.statusCode >= 200 && response.statusCode < 300) {\n pipelineAsync(response, file)\n .then(() => resolve())\n .catch(err => {\n fs.unlink(destinationPath, () => reject(err)); // Clean up partially written file on error\n });\n } else {\n fs.unlink(destinationPath, () => {\n reject(new Error(`Failed to download file, status code: ${response.statusCode}`));\n });\n }\n })\n .on('error', err => {\n fs.unlink(destinationPath, () => reject(err)); // Handle request errors\n });\n\n // Handle file stream errors\n file.on('error', err => {\n fs.unlink(destinationPath, () => reject(err));\n });\n });\n}\n\n/**\n * Format file size.\n *\n * @param bytes The file size in bytes\n */\nfunction formatFileSize({ bytes }: { bytes: number }): string {\n if (bytes === 0) return '0 bytes';\n const units = ['bytes', 'kB', 'MB', 'GB', 'TB'] as const;\n let idx = Math.floor(Math.log(bytes) / Math.log(1024));\n if (idx < 0) idx = 0;\n if (idx >= units.length) idx = units.length - 1;\n const fileSize = (bytes / Math.pow(1024, idx)).toFixed(1);\n let unit: string;\n switch (idx) {\n case 0:\n unit = 'bytes';\n break;\n case 1:\n unit = 'kB';\n break;\n case 2:\n unit = 'MB';\n break;\n case 3:\n unit = 'GB';\n break;\n default:\n unit = 'TB';\n }\n return `${fileSize} ${unit}`;\n}\n\n/**\n * Remove a file or directory synchronously.\n *\n * **Note**: This function uses synchronous file system operations which block\n * the event loop. It is intended for use during application cleanup, build scripts,\n * or CLI tools where blocking is acceptable.\n * For production runtime code with high concurrency, consider using async alternatives\n * like `fs.promises.rm(target, { recursive: true, force: true })`.\n *\n * @param target The path to the file or directory to remove\n */\nfunction removeSync(target: string): void {\n if (fs.existsSync(target)) {\n const stats = fs.statSync(target);\n\n if (stats.isDirectory()) {\n // Read the directory contents\n for (const entry of fs.readdirSync(target)) {\n const entryPath = path.join(target, entry);\n removeSync(entryPath);\n }\n\n // Remove the directory itself\n fs.rmdirSync(target);\n } else {\n // Remove the file\n fs.unlinkSync(target);\n }\n } else {\n console.warn(`Path ${target} does not exist.`);\n }\n}\n\nexport default {\n convertFile,\n copySync,\n downloadFile,\n formatFileSize,\n removeSync,\n pathExists,\n ensureDir,\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -6,6 +6,7 @@ import type { DynamicEntity } from '../../database/dynamic-entity.js';
|
|
|
6
6
|
export default abstract class EntityController extends BaseController {
|
|
7
7
|
protected abstract entityName: string;
|
|
8
8
|
private static entityCache;
|
|
9
|
+
private static entityPropertiesCache;
|
|
9
10
|
/**
|
|
10
11
|
* Get request-scoped EntityManager with automatic cleanup
|
|
11
12
|
* Creates a new EM fork per request, cleaned up after response
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../../../src/webserver/controller/entity.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,aAAa,EAAyB,MAAM,iBAAiB,CAAC;AAC5E,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE5D,OAAO,cAAc,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAItE,MAAM,CAAC,OAAO,CAAC,QAAQ,OAAO,gBAAiB,SAAQ,cAAc;IACnE,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAGtC,OAAO,CAAC,MAAM,CAAC,WAAW,CAA2C;
|
|
1
|
+
{"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../../../src/webserver/controller/entity.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,aAAa,EAAyB,MAAM,iBAAiB,CAAC;AAC5E,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE5D,OAAO,cAAc,MAAM,WAAW,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAItE,MAAM,CAAC,OAAO,CAAC,QAAQ,OAAO,gBAAiB,SAAQ,cAAc;IACnE,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAGtC,OAAO,CAAC,MAAM,CAAC,WAAW,CAA2C;IAGrE,OAAO,CAAC,MAAM,CAAC,qBAAqB,CAAqC;IAEzE;;;;;OAKG;IACH,OAAO,CAAC,uBAAuB;IAO/B,SAAS,CAAC,SAAS,QAAa,OAAO,CAAC,OAAO,aAAa,GAAG,SAAS,CAAC,CA+BvE;IAEF,OAAO,CAAC,mBAAmB;IA2BpB,OAAO,GAAU,SAAS,cAAc,EAAE,OAAO,YAAY,mBAqBlE;IAEK,QAAQ,GAAU,SAAS,cAAc,EAAE,OAAO,YAAY,mBAqBnE;cAGc,UAAU,CAAC,CAAC,EAAE;QAC5B,aAAa,EAAE,aAAa,CAAC;QAC7B,OAAO,EAAE,cAAc,CAAC;QACxB,KAAK,EAAE,YAAY,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC;cAMD,WAAW,CAAC,CAAC,EAAE;QAC7B,aAAa,EAAE,aAAa,CAAC;QAC7B,OAAO,EAAE,cAAc,CAAC;QACxB,KAAK,EAAE,YAAY,CAAC;QACpB,IAAI,EAAE;YACJ,KAAK,EAAE,GAAG,EAAE,CAAC;YACb,KAAK,EAAE,MAAM,CAAC;YACd,IAAI,EAAE,MAAM,CAAC;YACb,UAAU,EAAE,MAAM,CAAC;YACnB,KAAK,EAAE,MAAM,CAAC;SACf,CAAC;KACH,GAAG,OAAO,CAAC,IAAI,CAAC;IAIV,OAAO,GACZ,SAAS,cAAc,CAAC;QACtB,WAAW,EAAE;YACX,IAAI,EAAE,MAAM,CAAC;YACb,KAAK,EAAE,MAAM,CAAC;YACd,OAAO,EAAE,MAAM,CAAC;YAChB,IAAI,EAAE,MAAM,CAAC;YACb,YAAY,EAAE,MAAM,CAAC;YACrB,MAAM,EAAE,MAAM,CAAC;YACf,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;SACpB,CAAC;KACH,CAAC,EACF,OAAO,YAAY,mBAyMnB;cAEc,SAAS,CAAC,CAAC,EAAE;QAC3B,aAAa,EAAE,aAAa,CAAC;QAC7B,OAAO,EAAE,cAAc,CAAC;QACxB,KAAK,EAAE,YAAY,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC;cAID,UAAU,CAAC,CAAC,EAAE;QAC5B,aAAa,EAAE,aAAa,CAAC;QAC7B,OAAO,EAAE,cAAc,CAAC;QACxB,KAAK,EAAE,YAAY,CAAC;QACpB,IAAI,EAAE,GAAG,CAAC;KACX,GAAG,OAAO,CAAC,IAAI,CAAC;IAIV,MAAM,GACX,SAAS,cAAc,CAAC;QACtB,MAAM,EAAE;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,CAAC;QACvB,WAAW,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC;KACnC,CAAC,EACF,OAAO,YAAY,mBA+CnB;IAEF,SAAS,CAAC,YAAY,GAAI,qBAGvB;QACD,OAAO,EAAE,cAAc,CAAC;QACxB,KAAK,EAAE,YAAY,CAAC;KACrB,KAAG;QAAE,OAAO,EAAE,cAAc,CAAC;QAAC,KAAK,EAAE,YAAY,CAAA;KAAE,CAElD;cAEc,aAAa,CAAC,CAAC,EAAE;QAC/B,aAAa,EAAE,aAAa,CAAC;QAC7B,OAAO,EAAE,cAAc,CAAC;QACxB,KAAK,EAAE,YAAY,CAAC;QACpB,IAAI,EAAE,GAAG,CAAC;KACX,GAAG,OAAO,CAAC,IAAI,CAAC;IAEV,SAAS,GAAU,SAAS,cAAc,EAAE,OAAO,YAAY,mBA6CpE;cAEc,aAAa,CAAC,CAAC,EAAE;QAC/B,aAAa,EAAE,aAAa,CAAC;QAC7B,OAAO,EAAE,cAAc,CAAC;QACxB,KAAK,EAAE,YAAY,CAAC;QACpB,IAAI,EAAE,GAAG,CAAC;KACX,GAAG,OAAO,CAAC,IAAI,CAAC;IAEV,SAAS,GAAU,SAAS,cAAc,CAAC;QAAE,MAAM,EAAE;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC,EAAE,OAAO,YAAY,mBA0ChG;IAEK,SAAS,GAAU,SAAS,cAAc,CAAC;QAAE,MAAM,EAAE;YAAE,EAAE,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC,EAAE,OAAO,YAAY,mBA2BhG;CACH"}
|
|
@@ -12,6 +12,8 @@ class EntityController extends BaseController {
|
|
|
12
12
|
}
|
|
13
13
|
// Cache for entity modules to avoid repeated dynamic imports
|
|
14
14
|
static entityCache = /* @__PURE__ */ new Map();
|
|
15
|
+
// Cache for entity properties to avoid repeated prototype iteration
|
|
16
|
+
static entityPropertiesCache = /* @__PURE__ */ new WeakMap();
|
|
15
17
|
/**
|
|
16
18
|
* Get request-scoped EntityManager with automatic cleanup
|
|
17
19
|
* Creates a new EM fork per request, cleaned up after response
|
|
@@ -45,6 +47,10 @@ class EntityController extends BaseController {
|
|
|
45
47
|
return EntityClass;
|
|
46
48
|
}, "getEntity");
|
|
47
49
|
getEntityProperties(entityClass) {
|
|
50
|
+
const cached = EntityController.entityPropertiesCache.get(entityClass);
|
|
51
|
+
if (cached) {
|
|
52
|
+
return cached;
|
|
53
|
+
}
|
|
48
54
|
const properties = [];
|
|
49
55
|
const reservedPropertyKeys = ["constructor", "toJSON"];
|
|
50
56
|
for (const propertyKey of Object.getOwnPropertyNames(entityClass.prototype)) {
|
|
@@ -55,6 +61,7 @@ class EntityController extends BaseController {
|
|
|
55
61
|
}
|
|
56
62
|
properties.push(propertyKey);
|
|
57
63
|
}
|
|
64
|
+
EntityController.entityPropertiesCache.set(entityClass, properties);
|
|
58
65
|
return properties;
|
|
59
66
|
}
|
|
60
67
|
options = /* @__PURE__ */ __name(async (request, reply) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/webserver/controller/entity.ts"],
|
|
4
|
-
"sourcesContent": ["import 'reflect-metadata';\nimport path from 'path';\nimport type { EntityManager, FilterQuery, Populate } from '@mikro-orm/core';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { StatusCodes } from 'http-status-codes';\nimport BaseController from './base.js';\nimport type { DynamicEntity } from '../../database/dynamic-entity.js';\nimport { generateFormFields } from '../../database/dynamic-entity-form-decorators.js';\nimport { Helper } from '../../util/index.js';\n\nexport default abstract class EntityController extends BaseController {\n protected abstract entityName: string;\n\n // Cache for entity modules to avoid repeated dynamic imports\n private static entityCache = new Map<string, typeof DynamicEntity>();\n\n /**\n * Get request-scoped EntityManager with automatic cleanup\n * Creates a new EM fork per request, cleaned up after response\n *\n * @internal Used by route handlers, do not call directly\n */\n private getRequestEntityManager(request: FastifyRequest): EntityManager {\n if (!(request as any).__entityManager) {\n (request as any).__entityManager = this.databaseInstance.getEntityManager();\n }\n return (request as any).__entityManager;\n }\n\n protected getEntity = async (): Promise<typeof DynamicEntity | undefined> => {\n if (this.applicationConfig.database?.enabled !== true) {\n throw new Error(`Database not enabled (Entity: ${this.entityName})`);\n }\n\n // Check cache first\n const cacheKey = `${this.applicationConfig.database.entitiesDirectory}:${this.entityName}`;\n if (EntityController.entityCache.has(cacheKey)) {\n return EntityController.entityCache.get(cacheKey);\n }\n\n // Define entity module path\n const entityModulePath = path.join(\n this.applicationConfig.database.entitiesDirectory,\n `${this.entityName}.${Helper.getScriptFileExtension()}`,\n );\n\n // Import entity module\n const entityModule = await import(entityModulePath);\n\n if (!entityModule?.[this.entityName]) {\n throw new Error(`Entity not found (Entity: ${this.entityName})`);\n }\n\n // Get entity class\n const EntityClass = entityModule[this.entityName];\n\n // Cache the entity for future use\n EntityController.entityCache.set(cacheKey, EntityClass);\n\n return EntityClass;\n };\n\n private getEntityProperties(entityClass: any): string[] {\n const properties: string[] = [];\n\n const reservedPropertyKeys = ['constructor', 'toJSON'];\n\n for (const propertyKey of Object.getOwnPropertyNames(entityClass.prototype)) {\n if (propertyKey.startsWith('__')) {\n continue;\n } else if (reservedPropertyKeys.includes(propertyKey)) {\n continue;\n }\n\n properties.push(propertyKey);\n }\n\n return properties;\n }\n\n public options = async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n const EntityClass = await this.getEntity();\n\n if (!EntityClass) {\n this.sendErrorResponse({ reply, error: 'Entity not found' });\n\n return;\n }\n\n const formFields = generateFormFields({ model: EntityClass });\n\n this.sendSuccessResponse({\n reply,\n data: {\n formFields,\n },\n });\n } catch (error) {\n this.sendErrorResponse({ reply, error });\n }\n };\n\n public metadata = async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n const EntityClass = await this.getEntity();\n\n if (!EntityClass) {\n this.sendErrorResponse({ reply, error: 'Entity not found' });\n\n return;\n }\n\n const formFields = generateFormFields({ model: EntityClass });\n\n this.sendSuccessResponse({\n reply,\n data: {\n formFields,\n },\n });\n } catch (error) {\n this.sendErrorResponse({ reply, error });\n }\n };\n\n // Pre-getMany hook (can be overridden in the child controller)\n protected async preGetMany(_: {\n entityManager: EntityManager;\n request: FastifyRequest;\n reply: FastifyReply;\n }): Promise<void> {\n // Default implementation: do nothing\n }\n\n // Post-getMany hook (can be overridden in the child controller)\n // await this.postGetMany({ entityManager: this.entityManager, request, reply, data });\n protected async postGetMany(_: {\n entityManager: EntityManager;\n request: FastifyRequest;\n reply: FastifyReply;\n data: {\n items: any[];\n total: number;\n page: number;\n totalPages: number;\n limit: number;\n };\n }): Promise<void> {\n // Default implementation: do nothing\n }\n\n public getMany = async (\n request: FastifyRequest<{\n Querystring: {\n page: string;\n limit: string;\n filters: string;\n sort: string;\n 'sort-order': string;\n search: string;\n [key: string]: any;\n };\n }>,\n reply: FastifyReply,\n ) => {\n try {\n // Get request-scoped EntityManager\n const em = this.getRequestEntityManager(request);\n\n // Call preGetMany hook\n await this.preGetMany({\n entityManager: em,\n request,\n reply,\n });\n\n const EntityClass = await this.getEntity();\n\n if (!EntityClass) {\n this.sendErrorResponse({ reply, error: 'Entity not found' });\n\n return;\n }\n\n // Pagination parameters\n const page = parseInt(request.query.page) || 1;\n const limit = parseInt(request.query.limit);\n const offset = (page - 1) * (limit > 0 ? limit : 0);\n\n // Filtering and sorting\n const filters = request.query.filters ? JSON.parse(request.query.filters) : {};\n const sortOrder = request.query['sort-order'] || 'ASC';\n const orderBy = request.query.sort ? { [request.query.sort]: sortOrder } : { id: sortOrder };\n\n const normalizedQuery: { [key: string]: any } = {};\n\n for (const key in request.query) {\n // Skip prototype pollution attempts\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n continue;\n }\n\n // Only process own properties\n if (!Object.prototype.hasOwnProperty.call(request.query, key)) {\n continue;\n }\n\n if (key.endsWith('[]')) {\n const normalizedKey = key.slice(0, -2);\n\n // Safe property assignment\n if (normalizedKey !== '__proto__' && normalizedKey !== 'constructor' && normalizedKey !== 'prototype') {\n Reflect.set(normalizedQuery, normalizedKey, Reflect.get(request.query, key));\n }\n } else {\n Reflect.set(normalizedQuery, key, Reflect.get(request.query, key));\n }\n }\n\n // Build query options\n const options: {\n limit?: number;\n offset?: number;\n filters: FilterQuery<any>;\n orderBy: { [key: string]: string };\n } = {\n filters,\n offset,\n orderBy,\n };\n\n if (limit > 0) {\n options.limit = limit;\n }\n\n const entityProperties = this.getEntityProperties(EntityClass);\n const reservedQueryKeys = ['page', 'limit', 'filters', 'sort', 'populate', 'search'];\n const searchQuery = request.query.search || '';\n\n for (const key in normalizedQuery) {\n // Skip prototype pollution attempts\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n continue;\n }\n\n // Only process own properties\n if (!Object.prototype.hasOwnProperty.call(normalizedQuery, key)) {\n continue;\n }\n\n if (reservedQueryKeys.includes(key)) {\n continue;\n }\n\n if (!entityProperties.includes(key)) {\n const [relation, subProperty] = key.split('.');\n\n if (relation && subProperty) {\n // Validate relation and subProperty names\n if (\n relation === '__proto__' ||\n relation === 'constructor' ||\n relation === 'prototype' ||\n subProperty === '__proto__' ||\n subProperty === 'constructor' ||\n subProperty === 'prototype'\n ) {\n continue;\n }\n\n let queryValue = Reflect.get(normalizedQuery, key);\n\n if (!queryValue) continue;\n\n if (typeof queryValue === 'string' && queryValue.includes(',')) {\n queryValue = queryValue.split(',');\n }\n\n if (Array.isArray(queryValue)) {\n Reflect.set(options.filters, relation, {\n [subProperty]: { $in: queryValue },\n });\n } else {\n Reflect.set(options.filters, relation, {\n [subProperty]: queryValue,\n });\n }\n }\n\n continue;\n }\n\n let queryValue = Reflect.get(normalizedQuery, key);\n\n if (!queryValue) {\n continue;\n }\n\n if (typeof queryValue === 'string' && queryValue.includes(',')) {\n queryValue = queryValue.split(',');\n }\n\n if (Array.isArray(queryValue)) {\n Reflect.set(options.filters, key, { $in: queryValue });\n } else {\n Reflect.set(options.filters, key, queryValue);\n }\n }\n\n // Add search filter if a search query is provided\n if (searchQuery) {\n const searchFields = EntityClass.getSearchFields();\n\n options.filters.$or = searchFields\n .filter(field => {\n const isIntegerField = ['id', 'originId'].includes(field);\n\n return !isIntegerField;\n })\n .map(field => {\n return {\n [field]: { $like: `%${searchQuery}%` },\n };\n });\n }\n\n const populate = request.query.populate ? request.query.populate.split(',') : [];\n\n // Fetch items from the database\n const [items, total] = await em.findAndCount(this.entityName, options.filters, {\n limit: options.limit,\n offset: options.offset,\n orderBy: options.orderBy,\n populate,\n });\n\n const totalPages = limit > 0 ? Math.ceil(total / limit) : 1;\n\n const data = {\n items,\n total,\n page,\n totalPages,\n limit: limit > 0 ? limit : total,\n };\n\n // Call postGetMany hook\n await this.postGetMany({\n entityManager: em,\n request,\n reply,\n data,\n });\n\n reply.send({\n data: data.items,\n total_items: data.total,\n page: data.page,\n total_pages: data.totalPages,\n limit: data.limit,\n });\n } catch (error) {\n this.sendErrorResponse({ reply, error });\n }\n };\n\n protected async preGetOne(_: {\n entityManager: EntityManager;\n request: FastifyRequest;\n reply: FastifyReply;\n }): Promise<void> {\n // Default implementation: do nothing\n }\n\n protected async postGetOne(_: {\n entityManager: EntityManager;\n request: FastifyRequest;\n reply: FastifyReply;\n item: any;\n }): Promise<void> {\n // Default implementation: do nothing\n }\n\n public getOne = async (\n request: FastifyRequest<{\n Params: { id: number };\n Querystring: { populate: string };\n }>,\n reply: FastifyReply,\n ) => {\n try {\n // Get request-scoped EntityManager\n const em = this.getRequestEntityManager(request);\n\n await this.preGetOne({\n entityManager: em,\n request,\n reply,\n });\n\n const queryPopulate = request.query.populate || null;\n const populateList: string[] = queryPopulate ? queryPopulate.split(',') : [];\n\n // Ensure populate is typed correctly for MikroORM\n const populate = populateList.map(field => `${field}.*`) as unknown as Populate<\n object,\n `${string}.*` | `${string}.$infer`\n >;\n\n const EntityClass = await this.getEntity();\n\n if (!EntityClass) {\n this.sendErrorResponse({ reply, error: 'Entity not found' });\n return;\n }\n\n const id = request.params.id;\n\n const item = await em.findOne(this.entityName, { id }, { populate });\n\n if (!item) {\n return this.sendNotFoundResponse(reply, `${EntityClass.singularNameCapitalized} not found`);\n }\n\n await this.postGetOne({\n entityManager: em,\n request,\n reply,\n item,\n });\n\n this.sendSuccessResponse({ reply, data: item });\n } catch (error) {\n this.sendErrorResponse({ reply, error });\n }\n };\n\n protected preCreateOne = ({\n request,\n reply,\n }: {\n request: FastifyRequest;\n reply: FastifyReply;\n }): { request: FastifyRequest; reply: FastifyReply } => {\n return { request, reply };\n };\n\n protected async postCreateOne(_: {\n entityManager: EntityManager;\n request: FastifyRequest;\n reply: FastifyReply;\n item: any;\n }): Promise<void> {}\n\n public createOne = async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n // Get request-scoped EntityManager\n const em = this.getRequestEntityManager(request);\n\n const EntityClass = await this.getEntity();\n\n if (!EntityClass) {\n this.sendErrorResponse({ reply, error: 'Entity not found' });\n return;\n }\n\n // Listen for preCreateOne hook\n if (this.preCreateOne) {\n const { request: preCreateOneRequest } = await this.preCreateOne({\n request,\n reply,\n });\n if (preCreateOneRequest) {\n request = preCreateOneRequest;\n }\n }\n\n const { error, value } = EntityClass.validateCreate(request.body);\n\n if (error) {\n return this.sendErrorResponse({ reply, error: error.message });\n }\n\n const item = em.create(this.entityName, value as object);\n\n await em.persistAndFlush(item);\n\n // Call postCreateOne hook\n await this.postCreateOne({\n entityManager: em,\n request,\n reply,\n item,\n });\n\n this.sendSuccessResponse({ reply, data: item, statusCode: StatusCodes.CREATED });\n } catch (error) {\n this.sendErrorResponse({ reply, error });\n }\n };\n\n protected async postUpdateOne(_: {\n entityManager: EntityManager;\n request: FastifyRequest;\n reply: FastifyReply;\n item: any;\n }): Promise<void> {}\n\n public updateOne = async (request: FastifyRequest<{ Params: { id: number } }>, reply: FastifyReply) => {\n try {\n // Get request-scoped EntityManager\n const em = this.getRequestEntityManager(request);\n\n const EntityClass = await this.getEntity();\n\n if (!EntityClass) {\n this.sendErrorResponse({ reply, error: 'Entity not found' });\n return;\n }\n\n const id = request.params.id;\n\n const { error, value } = EntityClass.validateUpdate(request.body);\n\n if (error) {\n return this.sendErrorResponse({ reply, error: error.message });\n }\n\n const item = await em.findOne(this.entityName, { id });\n\n if (!item) {\n return this.sendNotFoundResponse(reply, `${EntityClass.singularNameCapitalized} not found`);\n }\n\n em.assign(item, value as object);\n\n await em.persistAndFlush(item);\n\n // Call postUpdateOne hook\n await this.postUpdateOne({\n entityManager: em,\n request,\n reply,\n item,\n });\n\n this.sendSuccessResponse({ reply, data: item });\n } catch (error) {\n this.sendErrorResponse({ reply, error });\n }\n };\n\n public deleteOne = async (request: FastifyRequest<{ Params: { id: number } }>, reply: FastifyReply) => {\n try {\n // Get request-scoped EntityManager\n const em = this.getRequestEntityManager(request);\n\n const EntityClass = await this.getEntity();\n\n if (!EntityClass) {\n this.sendErrorResponse({ reply, error: 'Entity not found' });\n\n return;\n }\n\n const id = request.params.id;\n\n const item = await em.findOne(this.entityName, { id });\n\n if (!item) {\n return this.sendNotFoundResponse(reply, `${EntityClass.singularNameCapitalized} not found`);\n }\n\n await em.removeAndFlush(item);\n\n reply.status(StatusCodes.NO_CONTENT).send();\n } catch (error) {\n this.sendErrorResponse({ reply, error });\n }\n };\n}\n"],
|
|
5
|
-
"mappings": ";;AAAA,OAAO;AACP,OAAO,UAAU;AAGjB,SAAS,mBAAmB;AAC5B,OAAO,oBAAoB;AAE3B,SAAS,0BAA0B;AACnC,SAAS,cAAc;AAEvB,MAAO,yBAAgD,eAAe;AAAA,EAVtE,OAUsE;AAAA;AAAA;AAAA;AAAA,EAIpE,OAAe,cAAc,oBAAI,IAAkC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,
|
|
4
|
+
"sourcesContent": ["import 'reflect-metadata';\nimport path from 'path';\nimport type { EntityManager, FilterQuery, Populate } from '@mikro-orm/core';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { StatusCodes } from 'http-status-codes';\nimport BaseController from './base.js';\nimport type { DynamicEntity } from '../../database/dynamic-entity.js';\nimport { generateFormFields } from '../../database/dynamic-entity-form-decorators.js';\nimport { Helper } from '../../util/index.js';\n\nexport default abstract class EntityController extends BaseController {\n protected abstract entityName: string;\n\n // Cache for entity modules to avoid repeated dynamic imports\n private static entityCache = new Map<string, typeof DynamicEntity>();\n\n // Cache for entity properties to avoid repeated prototype iteration\n private static entityPropertiesCache = new WeakMap<Function, string[]>();\n\n /**\n * Get request-scoped EntityManager with automatic cleanup\n * Creates a new EM fork per request, cleaned up after response\n *\n * @internal Used by route handlers, do not call directly\n */\n private getRequestEntityManager(request: FastifyRequest): EntityManager {\n if (!(request as any).__entityManager) {\n (request as any).__entityManager = this.databaseInstance.getEntityManager();\n }\n return (request as any).__entityManager;\n }\n\n protected getEntity = async (): Promise<typeof DynamicEntity | undefined> => {\n if (this.applicationConfig.database?.enabled !== true) {\n throw new Error(`Database not enabled (Entity: ${this.entityName})`);\n }\n\n // Check cache first\n const cacheKey = `${this.applicationConfig.database.entitiesDirectory}:${this.entityName}`;\n if (EntityController.entityCache.has(cacheKey)) {\n return EntityController.entityCache.get(cacheKey);\n }\n\n // Define entity module path\n const entityModulePath = path.join(\n this.applicationConfig.database.entitiesDirectory,\n `${this.entityName}.${Helper.getScriptFileExtension()}`,\n );\n\n // Import entity module\n const entityModule = await import(entityModulePath);\n\n if (!entityModule?.[this.entityName]) {\n throw new Error(`Entity not found (Entity: ${this.entityName})`);\n }\n\n // Get entity class\n const EntityClass = entityModule[this.entityName];\n\n // Cache the entity for future use\n EntityController.entityCache.set(cacheKey, EntityClass);\n\n return EntityClass;\n };\n\n private getEntityProperties(entityClass: any): string[] {\n // Check cache first to avoid repeated prototype iteration\n const cached = EntityController.entityPropertiesCache.get(entityClass);\n if (cached) {\n return cached;\n }\n\n const properties: string[] = [];\n\n const reservedPropertyKeys = ['constructor', 'toJSON'];\n\n for (const propertyKey of Object.getOwnPropertyNames(entityClass.prototype)) {\n if (propertyKey.startsWith('__')) {\n continue;\n } else if (reservedPropertyKeys.includes(propertyKey)) {\n continue;\n }\n\n properties.push(propertyKey);\n }\n\n // Cache the result for future calls\n EntityController.entityPropertiesCache.set(entityClass, properties);\n\n return properties;\n }\n\n public options = async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n const EntityClass = await this.getEntity();\n\n if (!EntityClass) {\n this.sendErrorResponse({ reply, error: 'Entity not found' });\n\n return;\n }\n\n const formFields = generateFormFields({ model: EntityClass });\n\n this.sendSuccessResponse({\n reply,\n data: {\n formFields,\n },\n });\n } catch (error) {\n this.sendErrorResponse({ reply, error });\n }\n };\n\n public metadata = async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n const EntityClass = await this.getEntity();\n\n if (!EntityClass) {\n this.sendErrorResponse({ reply, error: 'Entity not found' });\n\n return;\n }\n\n const formFields = generateFormFields({ model: EntityClass });\n\n this.sendSuccessResponse({\n reply,\n data: {\n formFields,\n },\n });\n } catch (error) {\n this.sendErrorResponse({ reply, error });\n }\n };\n\n // Pre-getMany hook (can be overridden in the child controller)\n protected async preGetMany(_: {\n entityManager: EntityManager;\n request: FastifyRequest;\n reply: FastifyReply;\n }): Promise<void> {\n // Default implementation: do nothing\n }\n\n // Post-getMany hook (can be overridden in the child controller)\n // await this.postGetMany({ entityManager: this.entityManager, request, reply, data });\n protected async postGetMany(_: {\n entityManager: EntityManager;\n request: FastifyRequest;\n reply: FastifyReply;\n data: {\n items: any[];\n total: number;\n page: number;\n totalPages: number;\n limit: number;\n };\n }): Promise<void> {\n // Default implementation: do nothing\n }\n\n public getMany = async (\n request: FastifyRequest<{\n Querystring: {\n page: string;\n limit: string;\n filters: string;\n sort: string;\n 'sort-order': string;\n search: string;\n [key: string]: any;\n };\n }>,\n reply: FastifyReply,\n ) => {\n try {\n // Get request-scoped EntityManager\n const em = this.getRequestEntityManager(request);\n\n // Call preGetMany hook\n await this.preGetMany({\n entityManager: em,\n request,\n reply,\n });\n\n const EntityClass = await this.getEntity();\n\n if (!EntityClass) {\n this.sendErrorResponse({ reply, error: 'Entity not found' });\n\n return;\n }\n\n // Pagination parameters\n const page = parseInt(request.query.page) || 1;\n const limit = parseInt(request.query.limit);\n const offset = (page - 1) * (limit > 0 ? limit : 0);\n\n // Filtering and sorting\n const filters = request.query.filters ? JSON.parse(request.query.filters) : {};\n const sortOrder = request.query['sort-order'] || 'ASC';\n const orderBy = request.query.sort ? { [request.query.sort]: sortOrder } : { id: sortOrder };\n\n const normalizedQuery: { [key: string]: any } = {};\n\n for (const key in request.query) {\n // Skip prototype pollution attempts\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n continue;\n }\n\n // Only process own properties\n if (!Object.prototype.hasOwnProperty.call(request.query, key)) {\n continue;\n }\n\n if (key.endsWith('[]')) {\n const normalizedKey = key.slice(0, -2);\n\n // Safe property assignment\n if (normalizedKey !== '__proto__' && normalizedKey !== 'constructor' && normalizedKey !== 'prototype') {\n Reflect.set(normalizedQuery, normalizedKey, Reflect.get(request.query, key));\n }\n } else {\n Reflect.set(normalizedQuery, key, Reflect.get(request.query, key));\n }\n }\n\n // Build query options\n const options: {\n limit?: number;\n offset?: number;\n filters: FilterQuery<any>;\n orderBy: { [key: string]: string };\n } = {\n filters,\n offset,\n orderBy,\n };\n\n if (limit > 0) {\n options.limit = limit;\n }\n\n const entityProperties = this.getEntityProperties(EntityClass);\n const reservedQueryKeys = ['page', 'limit', 'filters', 'sort', 'populate', 'search'];\n const searchQuery = request.query.search || '';\n\n for (const key in normalizedQuery) {\n // Skip prototype pollution attempts\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n continue;\n }\n\n // Only process own properties\n if (!Object.prototype.hasOwnProperty.call(normalizedQuery, key)) {\n continue;\n }\n\n if (reservedQueryKeys.includes(key)) {\n continue;\n }\n\n if (!entityProperties.includes(key)) {\n const [relation, subProperty] = key.split('.');\n\n if (relation && subProperty) {\n // Validate relation and subProperty names\n if (\n relation === '__proto__' ||\n relation === 'constructor' ||\n relation === 'prototype' ||\n subProperty === '__proto__' ||\n subProperty === 'constructor' ||\n subProperty === 'prototype'\n ) {\n continue;\n }\n\n let queryValue = Reflect.get(normalizedQuery, key);\n\n if (!queryValue) continue;\n\n if (typeof queryValue === 'string' && queryValue.includes(',')) {\n queryValue = queryValue.split(',');\n }\n\n if (Array.isArray(queryValue)) {\n Reflect.set(options.filters, relation, {\n [subProperty]: { $in: queryValue },\n });\n } else {\n Reflect.set(options.filters, relation, {\n [subProperty]: queryValue,\n });\n }\n }\n\n continue;\n }\n\n let queryValue = Reflect.get(normalizedQuery, key);\n\n if (!queryValue) {\n continue;\n }\n\n if (typeof queryValue === 'string' && queryValue.includes(',')) {\n queryValue = queryValue.split(',');\n }\n\n if (Array.isArray(queryValue)) {\n Reflect.set(options.filters, key, { $in: queryValue });\n } else {\n Reflect.set(options.filters, key, queryValue);\n }\n }\n\n // Add search filter if a search query is provided\n if (searchQuery) {\n const searchFields = EntityClass.getSearchFields();\n\n options.filters.$or = searchFields\n .filter(field => {\n const isIntegerField = ['id', 'originId'].includes(field);\n\n return !isIntegerField;\n })\n .map(field => {\n return {\n [field]: { $like: `%${searchQuery}%` },\n };\n });\n }\n\n const populate = request.query.populate ? request.query.populate.split(',') : [];\n\n // Fetch items from the database\n const [items, total] = await em.findAndCount(this.entityName, options.filters, {\n limit: options.limit,\n offset: options.offset,\n orderBy: options.orderBy,\n populate,\n });\n\n const totalPages = limit > 0 ? Math.ceil(total / limit) : 1;\n\n const data = {\n items,\n total,\n page,\n totalPages,\n limit: limit > 0 ? limit : total,\n };\n\n // Call postGetMany hook\n await this.postGetMany({\n entityManager: em,\n request,\n reply,\n data,\n });\n\n reply.send({\n data: data.items,\n total_items: data.total,\n page: data.page,\n total_pages: data.totalPages,\n limit: data.limit,\n });\n } catch (error) {\n this.sendErrorResponse({ reply, error });\n }\n };\n\n protected async preGetOne(_: {\n entityManager: EntityManager;\n request: FastifyRequest;\n reply: FastifyReply;\n }): Promise<void> {\n // Default implementation: do nothing\n }\n\n protected async postGetOne(_: {\n entityManager: EntityManager;\n request: FastifyRequest;\n reply: FastifyReply;\n item: any;\n }): Promise<void> {\n // Default implementation: do nothing\n }\n\n public getOne = async (\n request: FastifyRequest<{\n Params: { id: number };\n Querystring: { populate: string };\n }>,\n reply: FastifyReply,\n ) => {\n try {\n // Get request-scoped EntityManager\n const em = this.getRequestEntityManager(request);\n\n await this.preGetOne({\n entityManager: em,\n request,\n reply,\n });\n\n const queryPopulate = request.query.populate || null;\n const populateList: string[] = queryPopulate ? queryPopulate.split(',') : [];\n\n // Ensure populate is typed correctly for MikroORM\n const populate = populateList.map(field => `${field}.*`) as unknown as Populate<\n object,\n `${string}.*` | `${string}.$infer`\n >;\n\n const EntityClass = await this.getEntity();\n\n if (!EntityClass) {\n this.sendErrorResponse({ reply, error: 'Entity not found' });\n return;\n }\n\n const id = request.params.id;\n\n const item = await em.findOne(this.entityName, { id }, { populate });\n\n if (!item) {\n return this.sendNotFoundResponse(reply, `${EntityClass.singularNameCapitalized} not found`);\n }\n\n await this.postGetOne({\n entityManager: em,\n request,\n reply,\n item,\n });\n\n this.sendSuccessResponse({ reply, data: item });\n } catch (error) {\n this.sendErrorResponse({ reply, error });\n }\n };\n\n protected preCreateOne = ({\n request,\n reply,\n }: {\n request: FastifyRequest;\n reply: FastifyReply;\n }): { request: FastifyRequest; reply: FastifyReply } => {\n return { request, reply };\n };\n\n protected async postCreateOne(_: {\n entityManager: EntityManager;\n request: FastifyRequest;\n reply: FastifyReply;\n item: any;\n }): Promise<void> {}\n\n public createOne = async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n // Get request-scoped EntityManager\n const em = this.getRequestEntityManager(request);\n\n const EntityClass = await this.getEntity();\n\n if (!EntityClass) {\n this.sendErrorResponse({ reply, error: 'Entity not found' });\n return;\n }\n\n // Listen for preCreateOne hook\n if (this.preCreateOne) {\n const { request: preCreateOneRequest } = await this.preCreateOne({\n request,\n reply,\n });\n if (preCreateOneRequest) {\n request = preCreateOneRequest;\n }\n }\n\n const { error, value } = EntityClass.validateCreate(request.body);\n\n if (error) {\n return this.sendErrorResponse({ reply, error: error.message });\n }\n\n const item = em.create(this.entityName, value as object);\n\n await em.persistAndFlush(item);\n\n // Call postCreateOne hook\n await this.postCreateOne({\n entityManager: em,\n request,\n reply,\n item,\n });\n\n this.sendSuccessResponse({ reply, data: item, statusCode: StatusCodes.CREATED });\n } catch (error) {\n this.sendErrorResponse({ reply, error });\n }\n };\n\n protected async postUpdateOne(_: {\n entityManager: EntityManager;\n request: FastifyRequest;\n reply: FastifyReply;\n item: any;\n }): Promise<void> {}\n\n public updateOne = async (request: FastifyRequest<{ Params: { id: number } }>, reply: FastifyReply) => {\n try {\n // Get request-scoped EntityManager\n const em = this.getRequestEntityManager(request);\n\n const EntityClass = await this.getEntity();\n\n if (!EntityClass) {\n this.sendErrorResponse({ reply, error: 'Entity not found' });\n return;\n }\n\n const id = request.params.id;\n\n const { error, value } = EntityClass.validateUpdate(request.body);\n\n if (error) {\n return this.sendErrorResponse({ reply, error: error.message });\n }\n\n const item = await em.findOne(this.entityName, { id });\n\n if (!item) {\n return this.sendNotFoundResponse(reply, `${EntityClass.singularNameCapitalized} not found`);\n }\n\n em.assign(item, value as object);\n\n await em.persistAndFlush(item);\n\n // Call postUpdateOne hook\n await this.postUpdateOne({\n entityManager: em,\n request,\n reply,\n item,\n });\n\n this.sendSuccessResponse({ reply, data: item });\n } catch (error) {\n this.sendErrorResponse({ reply, error });\n }\n };\n\n public deleteOne = async (request: FastifyRequest<{ Params: { id: number } }>, reply: FastifyReply) => {\n try {\n // Get request-scoped EntityManager\n const em = this.getRequestEntityManager(request);\n\n const EntityClass = await this.getEntity();\n\n if (!EntityClass) {\n this.sendErrorResponse({ reply, error: 'Entity not found' });\n\n return;\n }\n\n const id = request.params.id;\n\n const item = await em.findOne(this.entityName, { id });\n\n if (!item) {\n return this.sendNotFoundResponse(reply, `${EntityClass.singularNameCapitalized} not found`);\n }\n\n await em.removeAndFlush(item);\n\n reply.status(StatusCodes.NO_CONTENT).send();\n } catch (error) {\n this.sendErrorResponse({ reply, error });\n }\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["queryValue"]
|
|
7
7
|
}
|
|
@@ -2,6 +2,8 @@ import WebSocket from 'ws';
|
|
|
2
2
|
import type { WebSocketClientData } from './websocket-client-manager.interface.js';
|
|
3
3
|
export default class WebSocketClientManager {
|
|
4
4
|
private clients;
|
|
5
|
+
/** Reverse lookup map for O(1) clientId lookup by WebSocket instance */
|
|
6
|
+
private wsToClientId;
|
|
5
7
|
addClient({ clientId, ws, lastActivity, user, }: {
|
|
6
8
|
clientId: string;
|
|
7
9
|
ws: WebSocket | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket-client-manager.d.ts","sourceRoot":"","sources":["../../src/websocket/websocket-client-manager.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,IAAI,CAAC;AAE3B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AAKnF,MAAM,CAAC,OAAO,OAAO,sBAAsB;IACzC,OAAO,CAAC,OAAO,CAA+C;
|
|
1
|
+
{"version":3,"file":"websocket-client-manager.d.ts","sourceRoot":"","sources":["../../src/websocket/websocket-client-manager.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,IAAI,CAAC;AAE3B,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AAKnF,MAAM,CAAC,OAAO,OAAO,sBAAsB;IACzC,OAAO,CAAC,OAAO,CAA+C;IAC9D,wEAAwE;IACxE,OAAO,CAAC,YAAY,CAAqC;IAElD,SAAS,CAAC,EACf,QAAQ,EACR,EAAE,EACF,YAAY,EACZ,IAAI,GACL,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;QACjB,EAAE,EAAE,SAAS,GAAG,IAAI,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,IAAI,CAAC,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,GAAG,CAAA;SAAE,GAAG,IAAI,CAAC;KAChD;IAsBM,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE;QAAE,EAAE,EAAE,SAAS,CAAA;KAAE,GAAG,MAAM,GAAG,SAAS;IAK1D,SAAS,CAAC,EACf,QAAQ,EACR,SAAS,GACV,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,CAAC,EAAE,OAAO,CAAC;KACrB,GAAG,mBAAmB,GAAG,SAAS;IAU5B,YAAY,CAAC,EAClB,QAAQ,EACR,GAAG,EACH,IAAI,EACJ,mBAAmB,GACpB,EAAE;QACD,QAAQ,EAAE,MAAM,CAAC;QACjB,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,GAAG,CAAC;QACV,mBAAmB,CAAC,EAAE,OAAO,CAAC;KAC/B;IA2CM,YAAY,CAAC,QAAQ,EAAE,MAAM;IAyB7B,aAAa;;kBAEN,MAAM;;IAcb,cAAc,CAAC,EACpB,GAAG,EACH,KAAK,EACL,SAAS,EACT,QAAQ,GACT,EAAE;QACD,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB;;;;;;IA6BM,UAAU,CAAC,EAAE,QAAQ,EAAE,GAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO;IAgBnD,gBAAgB,CAAC,EAAE,QAAQ,EAAE,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE;IAwBnD,YAAY;IAwBZ,mBAAmB,CAAC,IAAI,EAAE,MAAM;IA0BhC,OAAO,IAAI,IAAI;CAuBvB"}
|
|
@@ -10,6 +10,8 @@ class WebSocketClientManager {
|
|
|
10
10
|
__name(this, "WebSocketClientManager");
|
|
11
11
|
}
|
|
12
12
|
clients = /* @__PURE__ */ new Map();
|
|
13
|
+
/** Reverse lookup map for O(1) clientId lookup by WebSocket instance */
|
|
14
|
+
wsToClientId = /* @__PURE__ */ new Map();
|
|
13
15
|
addClient({
|
|
14
16
|
clientId,
|
|
15
17
|
ws,
|
|
@@ -21,6 +23,9 @@ class WebSocketClientManager {
|
|
|
21
23
|
lastActivity,
|
|
22
24
|
user
|
|
23
25
|
});
|
|
26
|
+
if (ws) {
|
|
27
|
+
this.wsToClientId.set(ws, clientId);
|
|
28
|
+
}
|
|
24
29
|
this.broadcastClientList("addClient");
|
|
25
30
|
log("Client connected", {
|
|
26
31
|
ID: clientId,
|
|
@@ -29,7 +34,7 @@ class WebSocketClientManager {
|
|
|
29
34
|
this.printClients();
|
|
30
35
|
}
|
|
31
36
|
getClientId({ ws }) {
|
|
32
|
-
return
|
|
37
|
+
return this.wsToClientId.get(ws);
|
|
33
38
|
}
|
|
34
39
|
getClient({
|
|
35
40
|
clientId,
|
|
@@ -81,6 +86,7 @@ class WebSocketClientManager {
|
|
|
81
86
|
removeClient(clientId) {
|
|
82
87
|
const client = this.clients.get(clientId);
|
|
83
88
|
if (client?.ws) {
|
|
89
|
+
this.wsToClientId.delete(client.ws);
|
|
84
90
|
try {
|
|
85
91
|
client.ws.removeAllListeners();
|
|
86
92
|
if (client.ws.readyState === WebSocket.OPEN) {
|
|
@@ -217,6 +223,7 @@ class WebSocketClientManager {
|
|
|
217
223
|
}
|
|
218
224
|
});
|
|
219
225
|
this.clients.clear();
|
|
226
|
+
this.wsToClientId.clear();
|
|
220
227
|
log("WebSocket client manager cleaned up");
|
|
221
228
|
}
|
|
222
229
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/websocket/websocket-client-manager.ts"],
|
|
4
|
-
"sourcesContent": ["import WebSocket from 'ws';\nimport { log } from './utils.js';\nimport type { WebSocketClientData } from './websocket-client-manager.interface.js';\nimport { Helper, Time } from '../util/index.js';\nimport cluster from 'cluster';\nimport { safeSerializeError } from '../error/error-reporter.js';\n\nexport default class WebSocketClientManager {\n private clients: Map<string, WebSocketClientData> = new Map();\n\n public addClient({\n clientId,\n ws,\n lastActivity,\n user,\n }: {\n clientId: string;\n ws: WebSocket | null;\n lastActivity: number;\n user?: { userId: number; payload: any } | null;\n }) {\n this.clients.set(clientId, {\n ws,\n lastActivity,\n user,\n });\n\n this.broadcastClientList('addClient');\n\n log('Client connected', {\n ID: clientId,\n UserId: user?.userId ?? 'unauthenticated',\n });\n\n this.printClients();\n }\n\n public getClientId({ ws }: { ws: WebSocket }): string | undefined {\n
|
|
5
|
-
"mappings": ";;AAAA,OAAO,eAAe;AACtB,SAAS,WAAW;AAEpB,SAAS,QAAQ,YAAY;AAC7B,OAAO,aAAa;AACpB,SAAS,0BAA0B;AAEnC,MAAO,uBAAqC;AAAA,EAP5C,OAO4C;AAAA;AAAA;AAAA,EAClC,UAA4C,oBAAI,IAAI;AAAA,
|
|
4
|
+
"sourcesContent": ["import WebSocket from 'ws';\nimport { log } from './utils.js';\nimport type { WebSocketClientData } from './websocket-client-manager.interface.js';\nimport { Helper, Time } from '../util/index.js';\nimport cluster from 'cluster';\nimport { safeSerializeError } from '../error/error-reporter.js';\n\nexport default class WebSocketClientManager {\n private clients: Map<string, WebSocketClientData> = new Map();\n /** Reverse lookup map for O(1) clientId lookup by WebSocket instance */\n private wsToClientId: Map<WebSocket, string> = new Map();\n\n public addClient({\n clientId,\n ws,\n lastActivity,\n user,\n }: {\n clientId: string;\n ws: WebSocket | null;\n lastActivity: number;\n user?: { userId: number; payload: any } | null;\n }) {\n this.clients.set(clientId, {\n ws,\n lastActivity,\n user,\n });\n\n // Maintain reverse lookup map for O(1) clientId lookups\n if (ws) {\n this.wsToClientId.set(ws, clientId);\n }\n\n this.broadcastClientList('addClient');\n\n log('Client connected', {\n ID: clientId,\n UserId: user?.userId ?? 'unauthenticated',\n });\n\n this.printClients();\n }\n\n public getClientId({ ws }: { ws: WebSocket }): string | undefined {\n // O(1) lookup using reverse map instead of O(n) iteration\n return this.wsToClientId.get(ws);\n }\n\n public getClient({\n clientId,\n requireWs,\n }: {\n clientId: string;\n requireWs?: boolean;\n }): WebSocketClientData | undefined {\n const client = this.clients.get(clientId);\n\n if (requireWs && !client?.ws) {\n return undefined;\n }\n\n return client;\n }\n\n public updateClient({\n clientId,\n key,\n data,\n broadcastClientList,\n }: {\n clientId: string;\n key: string;\n data: any;\n broadcastClientList?: boolean;\n }) {\n const client = this.clients.get(clientId);\n\n if (!client) {\n return;\n }\n\n // Prevent prototype pollution attacks\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n log('Blocked attempt to modify dangerous property', { Property: key });\n return;\n }\n\n // Define allowed client properties to prevent unauthorized modifications\n const allowedClientProperties = [\n 'id',\n 'ws',\n 'room',\n 'userId',\n 'username',\n 'metadata',\n 'connectedAt',\n 'lastActivity',\n 'status',\n 'permissions',\n ];\n\n if (!allowedClientProperties.includes(key)) {\n log('Blocked attempt to modify unauthorized property', { Property: key });\n return;\n }\n\n Reflect.set(client, key, data);\n\n this.clients.set(clientId, client);\n\n if (broadcastClientList !== false) {\n this.broadcastClientList('updateClient');\n }\n\n this.printClients();\n }\n\n public removeClient(clientId: string) {\n const client = this.clients.get(clientId);\n\n // Clean up WebSocket connection if it exists\n if (client?.ws) {\n // Remove from reverse lookup map\n this.wsToClientId.delete(client.ws);\n\n try {\n client.ws.removeAllListeners();\n if (client.ws.readyState === WebSocket.OPEN) {\n client.ws.close();\n }\n } catch (error) {\n log('Error cleaning up WebSocket connection', {\n error: error instanceof Error ? error.message : safeSerializeError(error),\n });\n }\n }\n\n this.clients.delete(clientId);\n this.broadcastClientList('removeClient');\n this.printClients();\n }\n\n public getClientList() {\n const clientList: {\n clientId: string;\n [key: string]: any;\n }[] = [];\n\n this.clients.forEach((clientData, clientId) => {\n clientList.push({\n clientId,\n ...clientData,\n });\n });\n\n return clientList;\n }\n\n public getClientByKey({\n key,\n value,\n requireWs,\n userType,\n }: {\n key: string;\n value: string;\n requireWs?: boolean;\n userType?: string;\n }) {\n const clients = [...this.clients.entries()];\n\n const client = clients.find(([_, clientData]) => {\n const deepKeyValue = Helper.getValueFromObject(clientData, key);\n\n const isValueMatching = deepKeyValue === value;\n\n if (userType && clientData.user?.userType !== userType) {\n return false;\n }\n\n return isValueMatching;\n });\n\n const formattedClient = client\n ? {\n clientId: client[0],\n ...client[1],\n }\n : undefined;\n\n if (requireWs && !formattedClient?.ws) {\n return undefined;\n }\n\n return formattedClient;\n }\n\n public getClients({ userType }: { userType?: string } = {}) {\n const clients: WebSocketClientData[] = [];\n\n this.clients.forEach((clientData, clientId) => {\n if (userType && clientData.user?.userType !== userType) {\n return;\n }\n\n clientData.clientId = clientId;\n\n clients.push(clientData);\n });\n\n return clients;\n }\n\n public disconnectClient({ clientId }: { clientId: string }) {\n const clientInfo = this.clients.get(clientId);\n\n if (clientInfo?.ws) {\n const connectedTime = Date.now() - clientInfo.lastActivity;\n\n clientInfo.ws.close();\n\n log('WebSocket client was disconnected due to inactivity', {\n ID: clientId,\n 'Time Connected': Time.formatTime({\n time: connectedTime,\n format: 's',\n }),\n });\n }\n\n this.removeClient(clientId);\n\n log('Client disconnected', { ID: clientId });\n\n this.printClients();\n }\n\n public printClients() {\n const numClients = this.clients.size;\n\n const workerId = cluster.isWorker && cluster.worker ? cluster.worker.id : null;\n\n let logOutput = '\\n-------------------------------------------------------\\n';\n logOutput += `Connected clients (Count: ${numClients}${workerId ? ` | Worker: ${workerId}` : ''}):\\n`;\n logOutput += '-------------------------------------------------------\\n';\n\n if (numClients > 0) {\n this.clients.forEach((client, clientId) => {\n logOutput += `ID: ${clientId} | Username: ${client?.user?.username ?? '-'} | User Type: ${client?.user?.userType ?? '-'} | Email: ${client.user?.email ?? '-'}\\n`;\n });\n } else {\n logOutput += 'No clients';\n }\n\n logOutput += '\\n';\n\n log(logOutput, undefined, {\n muteWorker: true,\n });\n }\n\n public broadcastClientList(type: string) {\n const clientList = this.getClientList();\n\n this.clients.forEach(({ ws }) => {\n if (!ws || ws.readyState !== WebSocket.OPEN) {\n return;\n }\n\n try {\n ws.send(\n JSON.stringify({\n type: 'system',\n action: 'clientList',\n clientListType: type,\n data: clientList,\n }),\n );\n } catch (error) {\n // Handle send errors (e.g., connection closed)\n log('Error broadcasting client list', {\n error: error instanceof Error ? error.message : safeSerializeError(error),\n });\n }\n });\n }\n\n public cleanup(): void {\n // Clean up all client connections\n this.clients.forEach((client, clientId) => {\n if (client.ws) {\n try {\n client.ws.removeAllListeners();\n if (client.ws.readyState === WebSocket.OPEN) {\n client.ws.close();\n }\n } catch (error) {\n log('Error cleaning up client connection', {\n clientId,\n error: error instanceof Error ? error.message : safeSerializeError(error),\n });\n }\n }\n });\n\n // Clear all maps\n this.clients.clear();\n this.wsToClientId.clear();\n log('WebSocket client manager cleaned up');\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -60,5 +60,10 @@ export default class WebSocketClient extends WebSocketBase {
|
|
|
60
60
|
reconnectAttempts: number;
|
|
61
61
|
autoReconnectEnabled: boolean;
|
|
62
62
|
};
|
|
63
|
+
/**
|
|
64
|
+
* Destroy the client and clean up all resources.
|
|
65
|
+
* This should be called when the client is no longer needed to prevent memory leaks.
|
|
66
|
+
*/
|
|
67
|
+
destroy(): void;
|
|
63
68
|
}
|
|
64
69
|
//# sourceMappingURL=websocket-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"websocket-client.d.ts","sourceRoot":"","sources":["../../src/websocket/websocket-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAoB,cAAc,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAChG,OAAO,KAAK,aAAa,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,YAAY,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,gBAAgB,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAE5E,OAAO,aAAa,MAAM,qBAAqB,CAAC;AAMhD,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,aAAa;IACxD,SAAS,CAAC,aAAa,EAAE,cAAc,EAAE,CAMvC;IAEF,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,EAAE,CAAC,CAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,iBAAiB,CAAa;IACtC,OAAO,CAAC,oBAAoB,CAAc;IAC1C,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,eAAe,CAAiB;gBAE5B,KAAK,EAAE,oBAAoB;IAWvC,IAAW,IAAI,IAAI,aAAa,CAE/B;IAEY,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAQrB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IA+E7C,SAAS,CAAC,yBAAyB,IAAI;QACrC,WAAW,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;QACrC,aAAa,EAAE,aAAa,CAAC;QAC7B,YAAY,EAAE,YAAY,CAAC;QAC3B,gBAAgB,EAAE,gBAAgB,CAAC;KACpC;IAQD,SAAS,CAAC,iBAAiB,IAAI,OAAO;IAItC,OAAO,CAAC,qBAAqB,CAqB3B;IAEF,SAAS,CAAC,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAI5D,iBAAiB,GAAI,MAAM,OAAO,EAAE,SAAQ,OAAe,KAAG,IAAI,CAUvE;IAEK,WAAW,GAAI,MAAM,OAAO,KAAG,IAAI,CAExC;IAEK,UAAU,IAAI,IAAI;IAoBlB,iBAAiB,IAAI,OAAO;IAInC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAqCzB;;OAEG;YACW,gBAAgB;IA4B9B;;OAEG;IACI,mBAAmB,IAAI,IAAI;IAIlC;;OAEG;IACI,oBAAoB,IAAI,IAAI;IASnC;;OAEG;IACI,mBAAmB,IAAI;QAC5B,WAAW,EAAE,OAAO,CAAC;QACrB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,oBAAoB,EAAE,OAAO,CAAC;KAC/B;
|
|
1
|
+
{"version":3,"file":"websocket-client.d.ts","sourceRoot":"","sources":["../../src/websocket/websocket-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAoB,cAAc,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAChG,OAAO,KAAK,aAAa,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,YAAY,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,gBAAgB,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AAE5E,OAAO,aAAa,MAAM,qBAAqB,CAAC;AAMhD,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,aAAa;IACxD,SAAS,CAAC,aAAa,EAAE,cAAc,EAAE,CAMvC;IAEF,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,EAAE,CAAC,CAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,iBAAiB,CAAa;IACtC,OAAO,CAAC,oBAAoB,CAAc;IAC1C,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,cAAc,CAAC,CAAiB;IACxC,OAAO,CAAC,eAAe,CAAiB;gBAE5B,KAAK,EAAE,oBAAoB;IAWvC,IAAW,IAAI,IAAI,aAAa,CAE/B;IAEY,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAQrB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IA+E7C,SAAS,CAAC,yBAAyB,IAAI;QACrC,WAAW,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;QACrC,aAAa,EAAE,aAAa,CAAC;QAC7B,YAAY,EAAE,YAAY,CAAC;QAC3B,gBAAgB,EAAE,gBAAgB,CAAC;KACpC;IAQD,SAAS,CAAC,iBAAiB,IAAI,OAAO;IAItC,OAAO,CAAC,qBAAqB,CAqB3B;IAEF,SAAS,CAAC,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAI5D,iBAAiB,GAAI,MAAM,OAAO,EAAE,SAAQ,OAAe,KAAG,IAAI,CAUvE;IAEK,WAAW,GAAI,MAAM,OAAO,KAAG,IAAI,CAExC;IAEK,UAAU,IAAI,IAAI;IAoBlB,iBAAiB,IAAI,OAAO;IAInC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAqCzB;;OAEG;YACW,gBAAgB;IA4B9B;;OAEG;IACI,mBAAmB,IAAI,IAAI;IAIlC;;OAEG;IACI,oBAAoB,IAAI,IAAI;IASnC;;OAEG;IACI,mBAAmB,IAAI;QAC5B,WAAW,EAAE,OAAO,CAAC;QACrB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,oBAAoB,EAAE,OAAO,CAAC;KAC/B;IAQD;;;OAGG;IACI,OAAO,IAAI,IAAI;CAyBvB"}
|
|
@@ -245,6 +245,28 @@ class WebSocketClient extends WebSocketBase {
|
|
|
245
245
|
autoReconnectEnabled: this.shouldReconnect
|
|
246
246
|
};
|
|
247
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Destroy the client and clean up all resources.
|
|
250
|
+
* This should be called when the client is no longer needed to prevent memory leaks.
|
|
251
|
+
*/
|
|
252
|
+
destroy() {
|
|
253
|
+
if (this.reconnectTimer) {
|
|
254
|
+
clearTimeout(this.reconnectTimer);
|
|
255
|
+
this.reconnectTimer = void 0;
|
|
256
|
+
}
|
|
257
|
+
this.shouldReconnect = false;
|
|
258
|
+
if (this.ws) {
|
|
259
|
+
this.ws.removeAllListeners();
|
|
260
|
+
if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {
|
|
261
|
+
this.ws.close();
|
|
262
|
+
}
|
|
263
|
+
this.ws = void 0;
|
|
264
|
+
}
|
|
265
|
+
this.clientId = void 0;
|
|
266
|
+
this.isConnected = false;
|
|
267
|
+
this.reconnectAttempts = 0;
|
|
268
|
+
log("WebSocket client destroyed");
|
|
269
|
+
}
|
|
248
270
|
}
|
|
249
271
|
export {
|
|
250
272
|
WebSocketClient as default
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/websocket/websocket-client.ts"],
|
|
4
|
-
"sourcesContent": ["import WebSocket, { type RawData } from 'ws';\nimport type { WebSocketOptions, WebSocketRoute, WebSocketType } from './websocket.interface.js';\nimport type RedisInstance from '../redis/instance.js';\nimport type QueueManager from '../queue/manager.js';\nimport type DatabaseInstance from '../database/instance.js';\nimport type { WebSocketClientProps } from './websocket-client.interface.js';\nimport { generateClientId, log, parseServerMessage } from './utils.js';\nimport WebSocketBase from './websocket-base.js';\nimport type { ApplicationConfig } from '../application/base-application.interface.js';\nimport path from 'path';\nimport { safeSerializeError } from '../error/error-reporter.js';\nimport { baseDir } from '../index.js';\n\nexport default class WebSocketClient extends WebSocketBase {\n protected defaultRoutes: WebSocketRoute[] = [\n {\n type: 'system',\n action: 'clientList',\n controllerName: 'system',\n },\n ];\n\n private applicationConfig: ApplicationConfig;\n private options: WebSocketOptions;\n private redisInstance: RedisInstance;\n private queueManager: QueueManager;\n private databaseInstance: DatabaseInstance;\n private ws?: WebSocket;\n private clientId?: string;\n private isConnected: boolean = false;\n private reconnectAttempts: number = 0;\n private maxReconnectAttempts: number = 10;\n private reconnectDelay: number = 1000; // Start with 1 second\n private reconnectTimer?: NodeJS.Timeout;\n private shouldReconnect: boolean = true;\n\n constructor(props: WebSocketClientProps) {\n super();\n\n this.applicationConfig = props.applicationConfig;\n this.options = props.options;\n this.redisInstance = props.redisInstance;\n this.queueManager = props.queueManager;\n this.databaseInstance = props.databaseInstance;\n this.routes = props.routes;\n }\n\n public get type(): WebSocketType {\n return 'client';\n }\n\n public async load(): Promise<void> {\n const libraryControllersDirectory = path.join(baseDir, 'websocket', 'controllers', 'client');\n\n await this.configureRoutes(this.defaultRoutes, libraryControllersDirectory);\n\n await this.configureRoutes(this.routes, this.options.controllersDirectory);\n }\n\n public async connectToServer(): Promise<void> {\n const url = this.options.url;\n // const host = this.options.host;\n // const port = this.options.port;\n\n return new Promise(resolve => {\n const ws = new WebSocket(url);\n\n ws.on('open', () => {\n this.clientId = generateClientId();\n this.isConnected = true;\n\n log('Connected to server', { ID: this.clientId });\n\n if (this.options.events?.onConnected) {\n this.options.events.onConnected({\n ws,\n clientId: this.clientId,\n joinRoom: ({\n userId,\n userType,\n username,\n roomName,\n }: {\n userId?: string;\n userType?: string;\n username: string;\n roomName: string;\n }) => {\n this.sendClientMessage({\n type: 'system',\n action: 'joinRoom',\n data: {\n userId,\n userType,\n username,\n roomName,\n },\n });\n },\n });\n }\n\n resolve();\n });\n\n ws.on('message', this.handleIncomingMessage);\n\n ws.on('close', code => {\n this.isConnected = false;\n log('Connection to server closed', { Code: code });\n\n if (this.options.events?.onDisconnected) {\n this.options.events.onDisconnected({ clientId: this.clientId });\n }\n\n // Clean up event listeners to prevent memory leaks\n ws.removeAllListeners();\n this.ws = undefined;\n this.clientId = undefined;\n\n // Attempt to reconnect if not manually disconnected\n if (this.shouldReconnect) {\n this.scheduleReconnect();\n }\n });\n\n ws.on('error', error => {\n log('WebSocket error', { error: error.message });\n\n if (this.options.events?.onError) {\n this.options.events.onError({ error });\n }\n });\n\n this.ws = ws;\n });\n }\n\n protected getControllerDependencies(): {\n sendMessage: (data: unknown) => void;\n redisInstance: RedisInstance;\n queueManager: QueueManager;\n databaseInstance: DatabaseInstance;\n } {\n return {\n sendMessage: this.sendMessage,\n redisInstance: this.redisInstance,\n queueManager: this.queueManager,\n databaseInstance: this.databaseInstance,\n };\n }\n protected shouldPrintRoutes(): boolean {\n return this.options.debug?.printRoutes ?? false;\n }\n\n private handleIncomingMessage = async (message: RawData): Promise<void> => {\n if (!this.ws || !this.clientId) {\n log('WebSocket not initialized or client ID not set');\n\n return;\n }\n\n if (this.options.events?.onMessage) {\n const parsedMessage = parseServerMessage(message);\n\n this.options.events.onMessage({\n ws: this.ws,\n clientId: this.clientId,\n data: parsedMessage as { type: string; action: string; data: unknown },\n redisInstance: this.redisInstance,\n queueManager: this.queueManager,\n databaseInstance: this.databaseInstance,\n });\n }\n\n await this.handleServerMessage(this.ws, message, this.clientId);\n };\n\n protected handleMessageError(clientId: string, error: string): void {\n log(error);\n }\n\n public sendClientMessage = (data: unknown, binary: boolean = false): void => {\n if (!this.ws) {\n log('WebSocket not initialized');\n\n return;\n }\n\n const webSocketMessage = JSON.stringify(data);\n\n this.ws.send(webSocketMessage, { binary });\n };\n\n public sendMessage = (data: unknown): void => {\n this.sendClientMessage(data);\n };\n\n public disconnect(): void {\n // Disable auto-reconnect on manual disconnect\n this.shouldReconnect = false;\n\n // Clear any pending reconnect timer\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = undefined;\n }\n\n if (this.ws && this.isConnected) {\n this.ws.removeAllListeners();\n this.ws.close();\n this.ws = undefined;\n this.clientId = undefined;\n this.isConnected = false;\n log('WebSocket client disconnected');\n }\n }\n\n public isClientConnected(): boolean {\n return this.isConnected && this.ws?.readyState === WebSocket.OPEN;\n }\n\n /**\n * Schedule a reconnection attempt with exponential backoff\n */\n private scheduleReconnect(): void {\n // Don't reconnect if we've exceeded max attempts\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n log('Max reconnection attempts reached', {\n Attempts: this.reconnectAttempts,\n });\n\n if (this.options.events?.onReconnectFailed) {\n this.options.events.onReconnectFailed({\n attempts: this.reconnectAttempts,\n });\n }\n\n return;\n }\n\n // Calculate delay with exponential backoff (max 30 seconds)\n const delay = Math.min(this.reconnectDelay * Math.pow(2, this.reconnectAttempts), 30000);\n this.reconnectAttempts++;\n\n log('Scheduling reconnection', {\n Attempt: this.reconnectAttempts,\n Delay: `${delay}ms`,\n });\n\n if (this.options.events?.onReconnecting) {\n this.options.events.onReconnecting({\n attempt: this.reconnectAttempts,\n delay,\n });\n }\n\n this.reconnectTimer = setTimeout(() => {\n this.attemptReconnect();\n }, delay);\n }\n\n /**\n * Attempt to reconnect to the server\n */\n private async attemptReconnect(): Promise<void> {\n try {\n log('Attempting to reconnect...', {\n Attempt: this.reconnectAttempts,\n });\n\n await this.connectToServer();\n\n // Reset reconnect attempts on successful connection\n this.reconnectAttempts = 0;\n\n log('Reconnection successful');\n\n if (this.options.events?.onReconnected) {\n this.options.events.onReconnected({\n clientId: this.clientId,\n });\n }\n } catch (error) {\n log('Reconnection failed', {\n Error: error instanceof Error ? error.message : safeSerializeError(error),\n });\n\n // Schedule next attempt\n this.scheduleReconnect();\n }\n }\n\n /**\n * Enable auto-reconnection\n */\n public enableAutoReconnect(): void {\n this.shouldReconnect = true;\n }\n\n /**\n * Disable auto-reconnection\n */\n public disableAutoReconnect(): void {\n this.shouldReconnect = false;\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = undefined;\n }\n }\n\n /**\n * Get connection status\n */\n public getConnectionStatus(): {\n isConnected: boolean;\n reconnectAttempts: number;\n autoReconnectEnabled: boolean;\n } {\n return {\n isConnected: this.isConnected,\n reconnectAttempts: this.reconnectAttempts,\n autoReconnectEnabled: this.shouldReconnect,\n };\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import WebSocket, { type RawData } from 'ws';\nimport type { WebSocketOptions, WebSocketRoute, WebSocketType } from './websocket.interface.js';\nimport type RedisInstance from '../redis/instance.js';\nimport type QueueManager from '../queue/manager.js';\nimport type DatabaseInstance from '../database/instance.js';\nimport type { WebSocketClientProps } from './websocket-client.interface.js';\nimport { generateClientId, log, parseServerMessage } from './utils.js';\nimport WebSocketBase from './websocket-base.js';\nimport type { ApplicationConfig } from '../application/base-application.interface.js';\nimport path from 'path';\nimport { safeSerializeError } from '../error/error-reporter.js';\nimport { baseDir } from '../index.js';\n\nexport default class WebSocketClient extends WebSocketBase {\n protected defaultRoutes: WebSocketRoute[] = [\n {\n type: 'system',\n action: 'clientList',\n controllerName: 'system',\n },\n ];\n\n private applicationConfig: ApplicationConfig;\n private options: WebSocketOptions;\n private redisInstance: RedisInstance;\n private queueManager: QueueManager;\n private databaseInstance: DatabaseInstance;\n private ws?: WebSocket;\n private clientId?: string;\n private isConnected: boolean = false;\n private reconnectAttempts: number = 0;\n private maxReconnectAttempts: number = 10;\n private reconnectDelay: number = 1000; // Start with 1 second\n private reconnectTimer?: NodeJS.Timeout;\n private shouldReconnect: boolean = true;\n\n constructor(props: WebSocketClientProps) {\n super();\n\n this.applicationConfig = props.applicationConfig;\n this.options = props.options;\n this.redisInstance = props.redisInstance;\n this.queueManager = props.queueManager;\n this.databaseInstance = props.databaseInstance;\n this.routes = props.routes;\n }\n\n public get type(): WebSocketType {\n return 'client';\n }\n\n public async load(): Promise<void> {\n const libraryControllersDirectory = path.join(baseDir, 'websocket', 'controllers', 'client');\n\n await this.configureRoutes(this.defaultRoutes, libraryControllersDirectory);\n\n await this.configureRoutes(this.routes, this.options.controllersDirectory);\n }\n\n public async connectToServer(): Promise<void> {\n const url = this.options.url;\n // const host = this.options.host;\n // const port = this.options.port;\n\n return new Promise(resolve => {\n const ws = new WebSocket(url);\n\n ws.on('open', () => {\n this.clientId = generateClientId();\n this.isConnected = true;\n\n log('Connected to server', { ID: this.clientId });\n\n if (this.options.events?.onConnected) {\n this.options.events.onConnected({\n ws,\n clientId: this.clientId,\n joinRoom: ({\n userId,\n userType,\n username,\n roomName,\n }: {\n userId?: string;\n userType?: string;\n username: string;\n roomName: string;\n }) => {\n this.sendClientMessage({\n type: 'system',\n action: 'joinRoom',\n data: {\n userId,\n userType,\n username,\n roomName,\n },\n });\n },\n });\n }\n\n resolve();\n });\n\n ws.on('message', this.handleIncomingMessage);\n\n ws.on('close', code => {\n this.isConnected = false;\n log('Connection to server closed', { Code: code });\n\n if (this.options.events?.onDisconnected) {\n this.options.events.onDisconnected({ clientId: this.clientId });\n }\n\n // Clean up event listeners to prevent memory leaks\n ws.removeAllListeners();\n this.ws = undefined;\n this.clientId = undefined;\n\n // Attempt to reconnect if not manually disconnected\n if (this.shouldReconnect) {\n this.scheduleReconnect();\n }\n });\n\n ws.on('error', error => {\n log('WebSocket error', { error: error.message });\n\n if (this.options.events?.onError) {\n this.options.events.onError({ error });\n }\n });\n\n this.ws = ws;\n });\n }\n\n protected getControllerDependencies(): {\n sendMessage: (data: unknown) => void;\n redisInstance: RedisInstance;\n queueManager: QueueManager;\n databaseInstance: DatabaseInstance;\n } {\n return {\n sendMessage: this.sendMessage,\n redisInstance: this.redisInstance,\n queueManager: this.queueManager,\n databaseInstance: this.databaseInstance,\n };\n }\n protected shouldPrintRoutes(): boolean {\n return this.options.debug?.printRoutes ?? false;\n }\n\n private handleIncomingMessage = async (message: RawData): Promise<void> => {\n if (!this.ws || !this.clientId) {\n log('WebSocket not initialized or client ID not set');\n\n return;\n }\n\n if (this.options.events?.onMessage) {\n const parsedMessage = parseServerMessage(message);\n\n this.options.events.onMessage({\n ws: this.ws,\n clientId: this.clientId,\n data: parsedMessage as { type: string; action: string; data: unknown },\n redisInstance: this.redisInstance,\n queueManager: this.queueManager,\n databaseInstance: this.databaseInstance,\n });\n }\n\n await this.handleServerMessage(this.ws, message, this.clientId);\n };\n\n protected handleMessageError(clientId: string, error: string): void {\n log(error);\n }\n\n public sendClientMessage = (data: unknown, binary: boolean = false): void => {\n if (!this.ws) {\n log('WebSocket not initialized');\n\n return;\n }\n\n const webSocketMessage = JSON.stringify(data);\n\n this.ws.send(webSocketMessage, { binary });\n };\n\n public sendMessage = (data: unknown): void => {\n this.sendClientMessage(data);\n };\n\n public disconnect(): void {\n // Disable auto-reconnect on manual disconnect\n this.shouldReconnect = false;\n\n // Clear any pending reconnect timer\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = undefined;\n }\n\n if (this.ws && this.isConnected) {\n this.ws.removeAllListeners();\n this.ws.close();\n this.ws = undefined;\n this.clientId = undefined;\n this.isConnected = false;\n log('WebSocket client disconnected');\n }\n }\n\n public isClientConnected(): boolean {\n return this.isConnected && this.ws?.readyState === WebSocket.OPEN;\n }\n\n /**\n * Schedule a reconnection attempt with exponential backoff\n */\n private scheduleReconnect(): void {\n // Don't reconnect if we've exceeded max attempts\n if (this.reconnectAttempts >= this.maxReconnectAttempts) {\n log('Max reconnection attempts reached', {\n Attempts: this.reconnectAttempts,\n });\n\n if (this.options.events?.onReconnectFailed) {\n this.options.events.onReconnectFailed({\n attempts: this.reconnectAttempts,\n });\n }\n\n return;\n }\n\n // Calculate delay with exponential backoff (max 30 seconds)\n const delay = Math.min(this.reconnectDelay * Math.pow(2, this.reconnectAttempts), 30000);\n this.reconnectAttempts++;\n\n log('Scheduling reconnection', {\n Attempt: this.reconnectAttempts,\n Delay: `${delay}ms`,\n });\n\n if (this.options.events?.onReconnecting) {\n this.options.events.onReconnecting({\n attempt: this.reconnectAttempts,\n delay,\n });\n }\n\n this.reconnectTimer = setTimeout(() => {\n this.attemptReconnect();\n }, delay);\n }\n\n /**\n * Attempt to reconnect to the server\n */\n private async attemptReconnect(): Promise<void> {\n try {\n log('Attempting to reconnect...', {\n Attempt: this.reconnectAttempts,\n });\n\n await this.connectToServer();\n\n // Reset reconnect attempts on successful connection\n this.reconnectAttempts = 0;\n\n log('Reconnection successful');\n\n if (this.options.events?.onReconnected) {\n this.options.events.onReconnected({\n clientId: this.clientId,\n });\n }\n } catch (error) {\n log('Reconnection failed', {\n Error: error instanceof Error ? error.message : safeSerializeError(error),\n });\n\n // Schedule next attempt\n this.scheduleReconnect();\n }\n }\n\n /**\n * Enable auto-reconnection\n */\n public enableAutoReconnect(): void {\n this.shouldReconnect = true;\n }\n\n /**\n * Disable auto-reconnection\n */\n public disableAutoReconnect(): void {\n this.shouldReconnect = false;\n\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = undefined;\n }\n }\n\n /**\n * Get connection status\n */\n public getConnectionStatus(): {\n isConnected: boolean;\n reconnectAttempts: number;\n autoReconnectEnabled: boolean;\n } {\n return {\n isConnected: this.isConnected,\n reconnectAttempts: this.reconnectAttempts,\n autoReconnectEnabled: this.shouldReconnect,\n };\n }\n\n /**\n * Destroy the client and clean up all resources.\n * This should be called when the client is no longer needed to prevent memory leaks.\n */\n public destroy(): void {\n // Ensure all timers are cleared\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer);\n this.reconnectTimer = undefined;\n }\n\n // Disable reconnection\n this.shouldReconnect = false;\n\n // Clean up WebSocket connection\n if (this.ws) {\n this.ws.removeAllListeners();\n if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {\n this.ws.close();\n }\n this.ws = undefined;\n }\n\n this.clientId = undefined;\n this.isConnected = false;\n this.reconnectAttempts = 0;\n\n log('WebSocket client destroyed');\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|