@scpxl/nodejs-framework 1.0.49 → 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.
Files changed (41) hide show
  1. package/dist/application/base-application.d.ts.map +1 -1
  2. package/dist/application/base-application.js +6 -0
  3. package/dist/application/base-application.js.map +2 -2
  4. package/dist/lifecycle/lifecycle-manager.d.ts +6 -0
  5. package/dist/lifecycle/lifecycle-manager.d.ts.map +1 -1
  6. package/dist/lifecycle/lifecycle-manager.js +28 -0
  7. package/dist/lifecycle/lifecycle-manager.js.map +2 -2
  8. package/dist/queue/manager.d.ts +6 -0
  9. package/dist/queue/manager.d.ts.map +1 -1
  10. package/dist/queue/manager.js +39 -4
  11. package/dist/queue/manager.js.map +2 -2
  12. package/dist/queue/worker.d.ts +5 -0
  13. package/dist/queue/worker.d.ts.map +1 -1
  14. package/dist/queue/worker.js +8 -0
  15. package/dist/queue/worker.js.map +2 -2
  16. package/dist/redis/index.d.ts +1 -1
  17. package/dist/redis/index.d.ts.map +1 -1
  18. package/dist/redis/index.js +3 -2
  19. package/dist/redis/index.js.map +2 -2
  20. package/dist/redis/instance.js +1 -1
  21. package/dist/redis/instance.js.map +2 -2
  22. package/dist/redis/manager.d.ts +5 -0
  23. package/dist/redis/manager.d.ts.map +1 -1
  24. package/dist/redis/manager.js +13 -0
  25. package/dist/redis/manager.js.map +2 -2
  26. package/dist/util/file.d.ts +13 -2
  27. package/dist/util/file.d.ts.map +1 -1
  28. package/dist/util/file.js.map +2 -2
  29. package/dist/webserver/controller/entity.d.ts +1 -0
  30. package/dist/webserver/controller/entity.d.ts.map +1 -1
  31. package/dist/webserver/controller/entity.js +7 -0
  32. package/dist/webserver/controller/entity.js.map +2 -2
  33. package/dist/websocket/websocket-client-manager.d.ts +2 -0
  34. package/dist/websocket/websocket-client-manager.d.ts.map +1 -1
  35. package/dist/websocket/websocket-client-manager.js +8 -1
  36. package/dist/websocket/websocket-client-manager.js.map +2 -2
  37. package/dist/websocket/websocket-client.d.ts +5 -0
  38. package/dist/websocket/websocket-client.d.ts.map +1 -1
  39. package/dist/websocket/websocket-client.js +22 -0
  40. package/dist/websocket/websocket-client.js.map +2 -2
  41. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/queue/worker.ts"],
4
- "sourcesContent": ["import { type Job, type Processor, Worker } from 'bullmq';\nimport type { QueueWorkerConstructorParams } from './worker.interface.js';\nimport type { RedisInstance } from '../redis/index.js';\nimport { Logger } from '../logger/index.js';\nimport { Time } from '../util/index.js';\nimport { WebSocketRedisSubscriberEvent } from '../websocket/websocket.interface.js';\nimport type { ApplicationConfig } from '../application/base-application.interface.js';\nimport type QueueManager from './manager.js';\n\nexport default class QueueWorker extends Worker {\n private applicationConfig: ApplicationConfig;\n\n private queueManager: QueueManager;\n private redisInstance: RedisInstance;\n\n constructor({\n applicationConfig,\n queueManager,\n name,\n processor,\n options,\n redisInstance,\n }: QueueWorkerConstructorParams) {\n super(name, processor, options);\n\n this.applicationConfig = applicationConfig;\n\n this.queueManager = queueManager;\n this.redisInstance = redisInstance;\n\n this.on('active', this.onWorkerActive);\n this.on('error', this.onWorkerError);\n this.on('failed', this.onWorkerFailed);\n this.on('stalled', this.onWorkerStalled);\n this.on('completed', this.onWorkerCompleted);\n }\n\n private onWorkerActive = (job: Job): void => {\n this.queueManager.log('Worker active', {\n Queue: job.queueName,\n 'Job Name': job.name,\n 'Job ID': job.id,\n });\n };\n\n private onWorkerError = (error: Error): void => {\n Logger.error({ error });\n };\n\n private onWorkerFailed = (job: Job<any, Processor<any, any, string>, string> | undefined, error: Error): void => {\n // // Send job failed message to client\n // if (job && job.data.webSocketClientId) {\n // const errorMessage = {\n // webSocketClientId: job.data.webSocketClientId,\n // action: job.name,\n // error: error.message,\n // };\n\n // // Send error message to client\n // this.redisInstance.publisherClient.publish('queueJobError', JSON.stringify(errorMessage));\n // }\n\n Logger.error({ error });\n };\n\n private onWorkerStalled = (jobId: string): void => {\n this.queueManager.log('Worker stalled', { Job: jobId });\n };\n\n private onWorkerCompleted = (job: Job): void => {\n const jobData = job.data;\n\n if (job.returnvalue?.webSocketClientId) {\n // Send job completed message to client\n this.redisInstance.publisherClient.publish(\n WebSocketRedisSubscriberEvent.QueueJobCompleted,\n JSON.stringify(job.returnvalue),\n );\n }\n\n const startTime = jobData.startTime;\n\n const executionTimeMs = Time.calculateElapsedTimeMs({ startTime });\n const formattedExecutionTime = executionTimeMs.toFixed(2);\n\n if (this.applicationConfig.queue.log?.jobCompleted) {\n this.queueManager.log('Job completed', {\n Queue: job.queueName,\n 'Job Name': job.name,\n 'Job ID': job.id,\n Time: `${formattedExecutionTime}ms`,\n });\n }\n };\n}\n"],
5
- "mappings": ";;AAAA,SAAmC,cAAc;AAGjD,SAAS,cAAc;AACvB,SAAS,YAAY;AACrB,SAAS,qCAAqC;AAI9C,MAAO,oBAAkC,OAAO;AAAA,EAThD,OASgD;AAAA;AAAA;AAAA,EACtC;AAAA,EAEA;AAAA,EACA;AAAA,EAER,YAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAAiC;AAC/B,UAAM,MAAM,WAAW,OAAO;AAE9B,SAAK,oBAAoB;AAEzB,SAAK,eAAe;AACpB,SAAK,gBAAgB;AAErB,SAAK,GAAG,UAAU,KAAK,cAAc;AACrC,SAAK,GAAG,SAAS,KAAK,aAAa;AACnC,SAAK,GAAG,UAAU,KAAK,cAAc;AACrC,SAAK,GAAG,WAAW,KAAK,eAAe;AACvC,SAAK,GAAG,aAAa,KAAK,iBAAiB;AAAA,EAC7C;AAAA,EAEQ,iBAAiB,wBAAC,QAAmB;AAC3C,SAAK,aAAa,IAAI,iBAAiB;AAAA,MACrC,OAAO,IAAI;AAAA,MACX,YAAY,IAAI;AAAA,MAChB,UAAU,IAAI;AAAA,IAChB,CAAC;AAAA,EACH,GANyB;AAAA,EAQjB,gBAAgB,wBAAC,UAAuB;AAC9C,WAAO,MAAM,EAAE,MAAM,CAAC;AAAA,EACxB,GAFwB;AAAA,EAIhB,iBAAiB,wBAAC,KAAgE,UAAuB;AAa/G,WAAO,MAAM,EAAE,MAAM,CAAC;AAAA,EACxB,GAdyB;AAAA,EAgBjB,kBAAkB,wBAAC,UAAwB;AACjD,SAAK,aAAa,IAAI,kBAAkB,EAAE,KAAK,MAAM,CAAC;AAAA,EACxD,GAF0B;AAAA,EAIlB,oBAAoB,wBAAC,QAAmB;AAC9C,UAAM,UAAU,IAAI;AAEpB,QAAI,IAAI,aAAa,mBAAmB;AAEtC,WAAK,cAAc,gBAAgB;AAAA,QACjC,8BAA8B;AAAA,QAC9B,KAAK,UAAU,IAAI,WAAW;AAAA,MAChC;AAAA,IACF;AAEA,UAAM,YAAY,QAAQ;AAE1B,UAAM,kBAAkB,KAAK,uBAAuB,EAAE,UAAU,CAAC;AACjE,UAAM,yBAAyB,gBAAgB,QAAQ,CAAC;AAExD,QAAI,KAAK,kBAAkB,MAAM,KAAK,cAAc;AAClD,WAAK,aAAa,IAAI,iBAAiB;AAAA,QACrC,OAAO,IAAI;AAAA,QACX,YAAY,IAAI;AAAA,QAChB,UAAU,IAAI;AAAA,QACd,MAAM,GAAG,sBAAsB;AAAA,MACjC,CAAC;AAAA,IACH;AAAA,EACF,GAxB4B;AAyB9B;",
4
+ "sourcesContent": ["import { type Job, type Processor, Worker } from 'bullmq';\nimport type { QueueWorkerConstructorParams } from './worker.interface.js';\nimport type { RedisInstance } from '../redis/index.js';\nimport { Logger } from '../logger/index.js';\nimport { Time } from '../util/index.js';\nimport { WebSocketRedisSubscriberEvent } from '../websocket/websocket.interface.js';\nimport type { ApplicationConfig } from '../application/base-application.interface.js';\nimport type QueueManager from './manager.js';\n\nexport default class QueueWorker extends Worker {\n private applicationConfig: ApplicationConfig;\n\n private queueManager: QueueManager;\n private redisInstance: RedisInstance;\n\n constructor({\n applicationConfig,\n queueManager,\n name,\n processor,\n options,\n redisInstance,\n }: QueueWorkerConstructorParams) {\n super(name, processor, options);\n\n this.applicationConfig = applicationConfig;\n\n this.queueManager = queueManager;\n this.redisInstance = redisInstance;\n\n this.on('active', this.onWorkerActive);\n this.on('error', this.onWorkerError);\n this.on('failed', this.onWorkerFailed);\n this.on('stalled', this.onWorkerStalled);\n this.on('completed', this.onWorkerCompleted);\n }\n\n private onWorkerActive = (job: Job): void => {\n this.queueManager.log('Worker active', {\n Queue: job.queueName,\n 'Job Name': job.name,\n 'Job ID': job.id,\n });\n };\n\n private onWorkerError = (error: Error): void => {\n Logger.error({ error });\n };\n\n private onWorkerFailed = (job: Job<any, Processor<any, any, string>, string> | undefined, error: Error): void => {\n // // Send job failed message to client\n // if (job && job.data.webSocketClientId) {\n // const errorMessage = {\n // webSocketClientId: job.data.webSocketClientId,\n // action: job.name,\n // error: error.message,\n // };\n\n // // Send error message to client\n // this.redisInstance.publisherClient.publish('queueJobError', JSON.stringify(errorMessage));\n // }\n\n Logger.error({ error });\n };\n\n private onWorkerStalled = (jobId: string): void => {\n this.queueManager.log('Worker stalled', { Job: jobId });\n };\n\n private onWorkerCompleted = (job: Job): void => {\n const jobData = job.data;\n\n if (job.returnvalue?.webSocketClientId) {\n // Send job completed message to client\n this.redisInstance.publisherClient.publish(\n WebSocketRedisSubscriberEvent.QueueJobCompleted,\n JSON.stringify(job.returnvalue),\n );\n }\n\n const startTime = jobData.startTime;\n\n const executionTimeMs = Time.calculateElapsedTimeMs({ startTime });\n const formattedExecutionTime = executionTimeMs.toFixed(2);\n\n if (this.applicationConfig.queue.log?.jobCompleted) {\n this.queueManager.log('Job completed', {\n Queue: job.queueName,\n 'Job Name': job.name,\n 'Job ID': job.id,\n Time: `${formattedExecutionTime}ms`,\n });\n }\n };\n\n /**\n * Cleanup worker resources before shutdown.\n * Removes all event listeners and closes the worker connection.\n */\n public async cleanup(): Promise<void> {\n this.removeAllListeners();\n await this.close();\n }\n}\n"],
5
+ "mappings": ";;AAAA,SAAmC,cAAc;AAGjD,SAAS,cAAc;AACvB,SAAS,YAAY;AACrB,SAAS,qCAAqC;AAI9C,MAAO,oBAAkC,OAAO;AAAA,EAThD,OASgD;AAAA;AAAA;AAAA,EACtC;AAAA,EAEA;AAAA,EACA;AAAA,EAER,YAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAAiC;AAC/B,UAAM,MAAM,WAAW,OAAO;AAE9B,SAAK,oBAAoB;AAEzB,SAAK,eAAe;AACpB,SAAK,gBAAgB;AAErB,SAAK,GAAG,UAAU,KAAK,cAAc;AACrC,SAAK,GAAG,SAAS,KAAK,aAAa;AACnC,SAAK,GAAG,UAAU,KAAK,cAAc;AACrC,SAAK,GAAG,WAAW,KAAK,eAAe;AACvC,SAAK,GAAG,aAAa,KAAK,iBAAiB;AAAA,EAC7C;AAAA,EAEQ,iBAAiB,wBAAC,QAAmB;AAC3C,SAAK,aAAa,IAAI,iBAAiB;AAAA,MACrC,OAAO,IAAI;AAAA,MACX,YAAY,IAAI;AAAA,MAChB,UAAU,IAAI;AAAA,IAChB,CAAC;AAAA,EACH,GANyB;AAAA,EAQjB,gBAAgB,wBAAC,UAAuB;AAC9C,WAAO,MAAM,EAAE,MAAM,CAAC;AAAA,EACxB,GAFwB;AAAA,EAIhB,iBAAiB,wBAAC,KAAgE,UAAuB;AAa/G,WAAO,MAAM,EAAE,MAAM,CAAC;AAAA,EACxB,GAdyB;AAAA,EAgBjB,kBAAkB,wBAAC,UAAwB;AACjD,SAAK,aAAa,IAAI,kBAAkB,EAAE,KAAK,MAAM,CAAC;AAAA,EACxD,GAF0B;AAAA,EAIlB,oBAAoB,wBAAC,QAAmB;AAC9C,UAAM,UAAU,IAAI;AAEpB,QAAI,IAAI,aAAa,mBAAmB;AAEtC,WAAK,cAAc,gBAAgB;AAAA,QACjC,8BAA8B;AAAA,QAC9B,KAAK,UAAU,IAAI,WAAW;AAAA,MAChC;AAAA,IACF;AAEA,UAAM,YAAY,QAAQ;AAE1B,UAAM,kBAAkB,KAAK,uBAAuB,EAAE,UAAU,CAAC;AACjE,UAAM,yBAAyB,gBAAgB,QAAQ,CAAC;AAExD,QAAI,KAAK,kBAAkB,MAAM,KAAK,cAAc;AAClD,WAAK,aAAa,IAAI,iBAAiB;AAAA,QACrC,OAAO,IAAI;AAAA,QACX,YAAY,IAAI;AAAA,QAChB,UAAU,IAAI;AAAA,QACd,MAAM,GAAG,sBAAsB;AAAA,MACjC,CAAC;AAAA,IACH;AAAA,EACF,GAxB4B;AAAA;AAAA;AAAA;AAAA;AAAA,EA8B5B,MAAa,UAAyB;AACpC,SAAK,mBAAmB;AACxB,UAAM,KAAK,MAAM;AAAA,EACnB;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,3 @@
1
- export { default as RedisManager } from './manager.js';
1
+ export { default as RedisManager, clearGlobalInMemoryRedisState } from './manager.js';
2
2
  export { default as RedisInstance } from './instance.js';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/redis/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/redis/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,6BAA6B,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,eAAe,CAAC"}
@@ -1,7 +1,8 @@
1
- import { default as default2 } from "./manager.js";
1
+ import { default as default2, clearGlobalInMemoryRedisState } from "./manager.js";
2
2
  import { default as default3 } from "./instance.js";
3
3
  export {
4
4
  default3 as RedisInstance,
5
- default2 as RedisManager
5
+ default2 as RedisManager,
6
+ clearGlobalInMemoryRedisState
6
7
  };
7
8
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/redis/index.ts"],
4
- "sourcesContent": ["export { default as RedisManager } from './manager.js';\nexport { default as RedisInstance } from './instance.js';\n"],
5
- "mappings": "AAAA,SAAoB,WAAXA,gBAA+B;AACxC,SAAoB,WAAXA,gBAAgC;",
4
+ "sourcesContent": ["export { default as RedisManager, clearGlobalInMemoryRedisState } from './manager.js';\nexport { default as RedisInstance } from './instance.js';\n"],
5
+ "mappings": "AAAA,SAAoB,WAAXA,UAAyB,qCAAqC;AACvE,SAAoB,WAAXA,gBAAgC;",
6
6
  "names": ["default"]
7
7
  }
@@ -87,7 +87,7 @@ class RedisInstance {
87
87
  }
88
88
  }
89
89
  async getCache({ key }) {
90
- const cacheValue = this.client.get(key);
90
+ const cacheValue = await this.client.get(key);
91
91
  return cacheValue;
92
92
  }
93
93
  async deleteCache({ key }) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/redis/instance.ts"],
4
- "sourcesContent": ["import type { Redis } from 'ioredis';\nimport { Logger } from '../logger/index.js';\nimport type { RedisInstanceProps } from './instance.interface.js';\nimport type RedisManager from './manager.js';\n\nexport default class RedisInstance {\n private redisManager: RedisManager;\n\n public client: Redis;\n public publisherClient: Redis;\n public subscriberClient: Redis;\n\n constructor({ redisManager, client, publisherClient, subscriberClient }: RedisInstanceProps) {\n this.redisManager = redisManager;\n\n this.client = client;\n this.publisherClient = publisherClient;\n this.subscriberClient = subscriberClient;\n }\n\n public async disconnect(): Promise<void> {\n try {\n this.subscriberClient.disconnect();\n } catch (error) {\n Logger.error({ error, message: 'Could not disconnect Redis subscriber client' });\n }\n\n try {\n this.publisherClient.disconnect();\n } catch (error) {\n Logger.error({ error, message: 'Could not disconnect Redis publisherClient' });\n }\n\n try {\n this.client.disconnect();\n } catch (error) {\n Logger.error({ error, message: 'Could not disconnect Redis client' });\n }\n\n this.redisManager.log('Disconnected');\n }\n\n public isConnected(): Promise<boolean> {\n return new Promise((resolve, reject) => {\n if (this.client) {\n this.client.ping(error => {\n if (error) {\n reject(error);\n } else {\n resolve(true);\n }\n });\n } else {\n resolve(false);\n }\n });\n }\n\n /**\n * Sets a value in the cache with an optional expiration time.\n *\n * @param key - The key to set in the cache.\n * @param value - The value to set in the cache.\n * @param expiration - The expiration time in seconds (optional).\n * @throws Error if the value type is not supported.\n * @returns A Promise that resolves when the value is set in the cache.\n */\n public async setCache({\n key,\n value,\n expiration,\n }: {\n key: string;\n value: unknown;\n expiration?: number;\n }): Promise<void> {\n let formattedValue: string | number | Buffer;\n\n if (Buffer.isBuffer(value)) {\n formattedValue = value;\n } else if (value === null) {\n formattedValue = 'null';\n } else if (typeof value === 'object') {\n formattedValue = JSON.stringify(value);\n } else if (typeof value === 'number') {\n formattedValue = value;\n } else if (typeof value === 'string') {\n formattedValue = value;\n } else if (typeof value === 'boolean') {\n formattedValue = value ? 'true' : 'false';\n } else if (typeof value === 'bigint') {\n formattedValue = value.toString();\n } else {\n throw new Error('Unsupported value type');\n }\n\n if (expiration) {\n await this.client.set(key, formattedValue, 'EX', expiration);\n } else {\n await this.client.set(key, formattedValue);\n }\n }\n\n public async getCache({ key }: { key: string }): Promise<string | null> {\n const cacheValue = this.client.get(key);\n\n return cacheValue;\n }\n\n public async deleteCache({ key }: { key: string }): Promise<void> {\n await this.client.del(key);\n }\n}\n"],
5
- "mappings": ";;AACA,SAAS,cAAc;AAIvB,MAAO,cAA4B;AAAA,EALnC,OAKmC;AAAA;AAAA;AAAA,EACzB;AAAA,EAED;AAAA,EACA;AAAA,EACA;AAAA,EAEP,YAAY,EAAE,cAAc,QAAQ,iBAAiB,iBAAiB,GAAuB;AAC3F,SAAK,eAAe;AAEpB,SAAK,SAAS;AACd,SAAK,kBAAkB;AACvB,SAAK,mBAAmB;AAAA,EAC1B;AAAA,EAEA,MAAa,aAA4B;AACvC,QAAI;AACF,WAAK,iBAAiB,WAAW;AAAA,IACnC,SAAS,OAAO;AACd,aAAO,MAAM,EAAE,OAAO,SAAS,+CAA+C,CAAC;AAAA,IACjF;AAEA,QAAI;AACF,WAAK,gBAAgB,WAAW;AAAA,IAClC,SAAS,OAAO;AACd,aAAO,MAAM,EAAE,OAAO,SAAS,6CAA6C,CAAC;AAAA,IAC/E;AAEA,QAAI;AACF,WAAK,OAAO,WAAW;AAAA,IACzB,SAAS,OAAO;AACd,aAAO,MAAM,EAAE,OAAO,SAAS,oCAAoC,CAAC;AAAA,IACtE;AAEA,SAAK,aAAa,IAAI,cAAc;AAAA,EACtC;AAAA,EAEO,cAAgC;AACrC,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI,KAAK,QAAQ;AACf,aAAK,OAAO,KAAK,WAAS;AACxB,cAAI,OAAO;AACT,mBAAO,KAAK;AAAA,UACd,OAAO;AACL,oBAAQ,IAAI;AAAA,UACd;AAAA,QACF,CAAC;AAAA,MACH,OAAO;AACL,gBAAQ,KAAK;AAAA,MACf;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAa,SAAS;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAIkB;AAChB,QAAI;AAEJ,QAAI,OAAO,SAAS,KAAK,GAAG;AAC1B,uBAAiB;AAAA,IACnB,WAAW,UAAU,MAAM;AACzB,uBAAiB;AAAA,IACnB,WAAW,OAAO,UAAU,UAAU;AACpC,uBAAiB,KAAK,UAAU,KAAK;AAAA,IACvC,WAAW,OAAO,UAAU,UAAU;AACpC,uBAAiB;AAAA,IACnB,WAAW,OAAO,UAAU,UAAU;AACpC,uBAAiB;AAAA,IACnB,WAAW,OAAO,UAAU,WAAW;AACrC,uBAAiB,QAAQ,SAAS;AAAA,IACpC,WAAW,OAAO,UAAU,UAAU;AACpC,uBAAiB,MAAM,SAAS;AAAA,IAClC,OAAO;AACL,YAAM,IAAI,MAAM,wBAAwB;AAAA,IAC1C;AAEA,QAAI,YAAY;AACd,YAAM,KAAK,OAAO,IAAI,KAAK,gBAAgB,MAAM,UAAU;AAAA,IAC7D,OAAO;AACL,YAAM,KAAK,OAAO,IAAI,KAAK,cAAc;AAAA,IAC3C;AAAA,EACF;AAAA,EAEA,MAAa,SAAS,EAAE,IAAI,GAA4C;AACtE,UAAM,aAAa,KAAK,OAAO,IAAI,GAAG;AAEtC,WAAO;AAAA,EACT;AAAA,EAEA,MAAa,YAAY,EAAE,IAAI,GAAmC;AAChE,UAAM,KAAK,OAAO,IAAI,GAAG;AAAA,EAC3B;AACF;",
4
+ "sourcesContent": ["import type { Redis } from 'ioredis';\nimport { Logger } from '../logger/index.js';\nimport type { RedisInstanceProps } from './instance.interface.js';\nimport type RedisManager from './manager.js';\n\nexport default class RedisInstance {\n private redisManager: RedisManager;\n\n public client: Redis;\n public publisherClient: Redis;\n public subscriberClient: Redis;\n\n constructor({ redisManager, client, publisherClient, subscriberClient }: RedisInstanceProps) {\n this.redisManager = redisManager;\n\n this.client = client;\n this.publisherClient = publisherClient;\n this.subscriberClient = subscriberClient;\n }\n\n public async disconnect(): Promise<void> {\n try {\n this.subscriberClient.disconnect();\n } catch (error) {\n Logger.error({ error, message: 'Could not disconnect Redis subscriber client' });\n }\n\n try {\n this.publisherClient.disconnect();\n } catch (error) {\n Logger.error({ error, message: 'Could not disconnect Redis publisherClient' });\n }\n\n try {\n this.client.disconnect();\n } catch (error) {\n Logger.error({ error, message: 'Could not disconnect Redis client' });\n }\n\n this.redisManager.log('Disconnected');\n }\n\n public isConnected(): Promise<boolean> {\n return new Promise((resolve, reject) => {\n if (this.client) {\n this.client.ping(error => {\n if (error) {\n reject(error);\n } else {\n resolve(true);\n }\n });\n } else {\n resolve(false);\n }\n });\n }\n\n /**\n * Sets a value in the cache with an optional expiration time.\n *\n * @param key - The key to set in the cache.\n * @param value - The value to set in the cache.\n * @param expiration - The expiration time in seconds (optional).\n * @throws Error if the value type is not supported.\n * @returns A Promise that resolves when the value is set in the cache.\n */\n public async setCache({\n key,\n value,\n expiration,\n }: {\n key: string;\n value: unknown;\n expiration?: number;\n }): Promise<void> {\n let formattedValue: string | number | Buffer;\n\n if (Buffer.isBuffer(value)) {\n formattedValue = value;\n } else if (value === null) {\n formattedValue = 'null';\n } else if (typeof value === 'object') {\n formattedValue = JSON.stringify(value);\n } else if (typeof value === 'number') {\n formattedValue = value;\n } else if (typeof value === 'string') {\n formattedValue = value;\n } else if (typeof value === 'boolean') {\n formattedValue = value ? 'true' : 'false';\n } else if (typeof value === 'bigint') {\n formattedValue = value.toString();\n } else {\n throw new Error('Unsupported value type');\n }\n\n if (expiration) {\n await this.client.set(key, formattedValue, 'EX', expiration);\n } else {\n await this.client.set(key, formattedValue);\n }\n }\n\n public async getCache({ key }: { key: string }): Promise<string | null> {\n const cacheValue = await this.client.get(key);\n\n return cacheValue;\n }\n\n public async deleteCache({ key }: { key: string }): Promise<void> {\n await this.client.del(key);\n }\n}\n"],
5
+ "mappings": ";;AACA,SAAS,cAAc;AAIvB,MAAO,cAA4B;AAAA,EALnC,OAKmC;AAAA;AAAA;AAAA,EACzB;AAAA,EAED;AAAA,EACA;AAAA,EACA;AAAA,EAEP,YAAY,EAAE,cAAc,QAAQ,iBAAiB,iBAAiB,GAAuB;AAC3F,SAAK,eAAe;AAEpB,SAAK,SAAS;AACd,SAAK,kBAAkB;AACvB,SAAK,mBAAmB;AAAA,EAC1B;AAAA,EAEA,MAAa,aAA4B;AACvC,QAAI;AACF,WAAK,iBAAiB,WAAW;AAAA,IACnC,SAAS,OAAO;AACd,aAAO,MAAM,EAAE,OAAO,SAAS,+CAA+C,CAAC;AAAA,IACjF;AAEA,QAAI;AACF,WAAK,gBAAgB,WAAW;AAAA,IAClC,SAAS,OAAO;AACd,aAAO,MAAM,EAAE,OAAO,SAAS,6CAA6C,CAAC;AAAA,IAC/E;AAEA,QAAI;AACF,WAAK,OAAO,WAAW;AAAA,IACzB,SAAS,OAAO;AACd,aAAO,MAAM,EAAE,OAAO,SAAS,oCAAoC,CAAC;AAAA,IACtE;AAEA,SAAK,aAAa,IAAI,cAAc;AAAA,EACtC;AAAA,EAEO,cAAgC;AACrC,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI,KAAK,QAAQ;AACf,aAAK,OAAO,KAAK,WAAS;AACxB,cAAI,OAAO;AACT,mBAAO,KAAK;AAAA,UACd,OAAO;AACL,oBAAQ,IAAI;AAAA,UACd;AAAA,QACF,CAAC;AAAA,MACH,OAAO;AACL,gBAAQ,KAAK;AAAA,MACf;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,MAAa,SAAS;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAIkB;AAChB,QAAI;AAEJ,QAAI,OAAO,SAAS,KAAK,GAAG;AAC1B,uBAAiB;AAAA,IACnB,WAAW,UAAU,MAAM;AACzB,uBAAiB;AAAA,IACnB,WAAW,OAAO,UAAU,UAAU;AACpC,uBAAiB,KAAK,UAAU,KAAK;AAAA,IACvC,WAAW,OAAO,UAAU,UAAU;AACpC,uBAAiB;AAAA,IACnB,WAAW,OAAO,UAAU,UAAU;AACpC,uBAAiB;AAAA,IACnB,WAAW,OAAO,UAAU,WAAW;AACrC,uBAAiB,QAAQ,SAAS;AAAA,IACpC,WAAW,OAAO,UAAU,UAAU;AACpC,uBAAiB,MAAM,SAAS;AAAA,IAClC,OAAO;AACL,YAAM,IAAI,MAAM,wBAAwB;AAAA,IAC1C;AAEA,QAAI,YAAY;AACd,YAAM,KAAK,OAAO,IAAI,KAAK,gBAAgB,MAAM,UAAU;AAAA,IAC7D,OAAO;AACL,YAAM,KAAK,OAAO,IAAI,KAAK,cAAc;AAAA,IAC3C;AAAA,EACF;AAAA,EAEA,MAAa,SAAS,EAAE,IAAI,GAA4C;AACtE,UAAM,aAAa,MAAM,KAAK,OAAO,IAAI,GAAG;AAE5C,WAAO;AAAA,EACT;AAAA,EAEA,MAAa,YAAY,EAAE,IAAI,GAAmC;AAChE,UAAM,KAAK,OAAO,IAAI,GAAG;AAAA,EAC3B;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,10 @@
1
1
  import type { RedisManagerConfig as RedisManagerOptions } from './manager.interface.js';
2
2
  import RedisInstance from './instance.js';
3
+ /**
4
+ * Clears the global in-memory Redis state including all timers.
5
+ * Useful for test cleanup to prevent timer leaks between tests.
6
+ */
7
+ export declare function clearGlobalInMemoryRedisState(): void;
3
8
  export default class RedisManager {
4
9
  private logger;
5
10
  private options;
@@ -1 +1 @@
1
- {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/redis/manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,IAAI,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AACxF,OAAO,aAAa,MAAM,eAAe,CAAC;AAwJ1C,MAAM,CAAC,OAAO,OAAO,YAAY;IAC/B,OAAO,CAAC,MAAM,CAAyB;IAEvC,OAAO,CAAC,OAAO,CAAsB;IAE9B,SAAS,EAAE,aAAa,EAAE,CAAM;gBAE3B,MAAM,EAAE,mBAAmB;IAI1B,OAAO,IAAI,OAAO,CAAC,aAAa,CAAC;IA8GjC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAiDxC;;OAEG;IACI,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;CAGlE"}
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/redis/manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,IAAI,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AACxF,OAAO,aAAa,MAAM,eAAe,CAAC;AAiC1C;;;GAGG;AACH,wBAAgB,6BAA6B,IAAI,IAAI,CAWpD;AAyHD,MAAM,CAAC,OAAO,OAAO,YAAY;IAC/B,OAAO,CAAC,MAAM,CAAyB;IAEvC,OAAO,CAAC,OAAO,CAAsB;IAE9B,SAAS,EAAE,aAAa,EAAE,CAAM;gBAE3B,MAAM,EAAE,mBAAmB;IAI1B,OAAO,IAAI,OAAO,CAAC,aAAa,CAAC;IA8GjC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAiDxC;;OAEG;IACI,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;CAGlE"}
@@ -20,6 +20,18 @@ function getGlobalInMemoryRedisState() {
20
20
  return globalInMemoryRedisState;
21
21
  }
22
22
  __name(getGlobalInMemoryRedisState, "getGlobalInMemoryRedisState");
23
+ function clearGlobalInMemoryRedisState() {
24
+ if (globalInMemoryRedisState) {
25
+ for (const timer of globalInMemoryRedisState.expirations.values()) {
26
+ clearTimeout(timer);
27
+ }
28
+ globalInMemoryRedisState.expirations.clear();
29
+ globalInMemoryRedisState.store.clear();
30
+ globalInMemoryRedisState.subscriptions.clear();
31
+ }
32
+ globalInMemoryRedisState = null;
33
+ }
34
+ __name(clearGlobalInMemoryRedisState, "clearGlobalInMemoryRedisState");
23
35
  class InMemoryRedisClient extends EventEmitter {
24
36
  static {
25
37
  __name(this, "InMemoryRedisClient");
@@ -265,6 +277,7 @@ class RedisManager {
265
277
  }
266
278
  }
267
279
  export {
280
+ clearGlobalInMemoryRedisState,
268
281
  RedisManager as default
269
282
  };
270
283
  //# sourceMappingURL=manager.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/redis/manager.ts"],
4
- "sourcesContent": ["import { Redis, type RedisOptions } from 'ioredis';\nimport { EventEmitter } from 'node:events';\nimport type { RedisManagerConfig as RedisManagerOptions } from './manager.interface.js';\nimport RedisInstance from './instance.js';\nimport { Logger } from '../logger/index.js';\nimport { CachePerformanceWrapper } from '../performance/index.js';\nimport { safeSerializeError } from '../error/error-reporter.js';\n\nconst truthyPattern = /^(1|true|yes|on)$/i;\nconst scheduleMicrotask =\n typeof (globalThis as any).queueMicrotask === 'function'\n ? (globalThis as any).queueMicrotask.bind(globalThis)\n : (callback: () => void) => {\n void Promise.resolve().then(callback);\n };\n\ntype RedisCallback = (error: Error | null, result?: string) => void;\n\ninterface InMemoryRedisSharedState {\n store: Map<string, string | Buffer>;\n expirations: Map<string, NodeJS.Timeout>;\n subscriptions: Map<string, Set<InMemoryRedisClient>>;\n}\n\n// Global singleton shared state for in-memory Redis\nlet globalInMemoryRedisState: InMemoryRedisSharedState | null = null;\n\nfunction getGlobalInMemoryRedisState(): InMemoryRedisSharedState {\n globalInMemoryRedisState ??= {\n store: new Map<string, string | Buffer>(),\n expirations: new Map<string, NodeJS.Timeout>(),\n subscriptions: new Map<string, Set<InMemoryRedisClient>>(),\n };\n return globalInMemoryRedisState;\n}\n\nclass InMemoryRedisClient extends EventEmitter {\n private shared: InMemoryRedisSharedState;\n\n constructor(shared: InMemoryRedisSharedState) {\n super();\n this.shared = shared;\n\n scheduleMicrotask(() => {\n this.emit('ready');\n });\n }\n\n private cleanupSubscriptions(): void {\n for (const subscribers of this.shared.subscriptions.values()) {\n subscribers.delete(this);\n }\n }\n\n private clearExpirationForKey(key: string): void {\n const timer = this.shared.expirations.get(key);\n if (timer) {\n clearTimeout(timer);\n this.shared.expirations.delete(key);\n }\n }\n\n public ping(callback?: RedisCallback): Promise<string> {\n if (callback) {\n callback(null, 'PONG');\n return Promise.resolve('PONG');\n }\n\n return Promise.resolve('PONG');\n }\n\n public async set(...args: any[]): Promise<'OK'> {\n const [key, value, mode, expiration] = args;\n const serializedValue: string | Buffer = value instanceof Buffer ? value : String(value);\n\n this.shared.store.set(key, serializedValue);\n this.clearExpirationForKey(key);\n\n if (typeof mode === 'string' && mode.toUpperCase() === 'EX' && typeof expiration === 'number') {\n const timer = setTimeout(() => {\n this.shared.store.delete(key);\n this.shared.expirations.delete(key);\n }, expiration * 1000);\n\n if (typeof timer.unref === 'function') {\n timer.unref();\n }\n\n this.shared.expirations.set(key, timer);\n }\n\n return 'OK';\n }\n\n public async get(key: string): Promise<string | null> {\n return (this.shared.store.get(key) as string | undefined) ?? null;\n }\n\n public async del(key: string): Promise<number> {\n const existed = this.shared.store.delete(key);\n this.clearExpirationForKey(key);\n return existed ? 1 : 0;\n }\n\n public async publish(channel: string, message: string): Promise<number> {\n const subscribers = this.shared.subscriptions.get(channel);\n\n if (!subscribers || subscribers.size === 0) {\n return 0;\n }\n\n for (const subscriber of subscribers) {\n scheduleMicrotask(() => {\n subscriber.emit('message', channel, message);\n });\n }\n\n return subscribers.size;\n }\n\n public async subscribe(channel: string): Promise<number> {\n let subscribers = this.shared.subscriptions.get(channel);\n if (!subscribers) {\n subscribers = new Set<InMemoryRedisClient>();\n this.shared.subscriptions.set(channel, subscribers);\n }\n subscribers.add(this);\n return subscribers.size;\n }\n\n public async unsubscribe(channel: string): Promise<number> {\n const subscribers = this.shared.subscriptions.get(channel);\n\n if (!subscribers) {\n return 0;\n }\n\n subscribers.delete(this);\n return subscribers.size;\n }\n\n public async quit(): Promise<'OK'> {\n this.cleanupSubscriptions();\n this.emit('end');\n this.removeAllListeners();\n return 'OK';\n }\n\n public disconnect(): void {\n this.cleanupSubscriptions();\n this.emit('end');\n this.removeAllListeners();\n }\n}\n\nexport default class RedisManager {\n private logger: typeof Logger = Logger;\n\n private options: RedisManagerOptions;\n\n public instances: RedisInstance[] = [];\n\n constructor(config: RedisManagerOptions) {\n this.options = config;\n }\n\n public async connect(): Promise<RedisInstance> {\n return CachePerformanceWrapper.monitorConnection(\n 'connect',\n async () => {\n const startTime = performance.now();\n\n const redisOptions: RedisOptions = {\n host: this.options.host,\n port: this.options.port,\n password: this.options.password,\n maxRetriesPerRequest: null, // Needed for bullmq\n lazyConnect: true, // Prevent automatic connection to avoid unhandled errors\n };\n\n const useInMemoryRedis =\n truthyPattern.test(process.env.PXL_REDIS_IN_MEMORY ?? '') ||\n truthyPattern.test(process.env.REDIS_IN_MEMORY ?? '');\n\n const createClient = (): Redis => {\n if (useInMemoryRedis) {\n return new InMemoryRedisClient(getGlobalInMemoryRedisState()) as unknown as Redis;\n }\n\n const client = new Redis(redisOptions);\n // Attach a temporary error handler to prevent unhandled errors during connection\n const errorHandler = (error: Error) => {\n // Error will be handled by the promise rejection below\n };\n client.once('error', errorHandler);\n return client;\n };\n\n const client = createClient();\n const publisherClient = createClient();\n const subscriberClient = createClient();\n\n try {\n // For non-in-memory clients, explicitly connect since we use lazyConnect\n if (!useInMemoryRedis) {\n await Promise.all([client.connect(), publisherClient.connect(), subscriberClient.connect()]);\n } else {\n // Wait for in-memory clients to be ready\n await Promise.all([\n new Promise<void>((resolve, reject) => {\n client.once('ready', () => resolve());\n client.once('error', (error: Error) => reject(error));\n }),\n new Promise<void>((resolve, reject) => {\n publisherClient.once('ready', () => resolve());\n publisherClient.once('error', (error: Error) => reject(error));\n }),\n new Promise<void>((resolve, reject) => {\n subscriberClient.once('ready', () => resolve());\n subscriberClient.once('error', (error: Error) => reject(error));\n }),\n ]);\n }\n\n const redisInstance = new RedisInstance({\n redisManager: this,\n client,\n publisherClient,\n subscriberClient,\n });\n\n this.instances.push(redisInstance);\n\n const duration = performance.now() - startTime;\n const meta = {\n Host: this.options.host,\n Port: this.options.port,\n Duration: `${duration.toFixed(2)}ms`,\n Mode: useInMemoryRedis ? 'in-memory' : 'network',\n };\n\n if (this.options.applicationConfig.log?.startUp) {\n this.log('Connected', meta);\n } else {\n this.logger.debug({ message: 'Redis connected', meta });\n }\n\n if (useInMemoryRedis) {\n this.logger.debug({ message: 'Using in-memory Redis stub' });\n }\n\n return redisInstance;\n } catch (error) {\n const duration = performance.now() - startTime;\n\n // Clean up clients on error\n await Promise.allSettled([client.quit(), publisherClient.quit(), subscriberClient.quit()]);\n\n this.logger.error({\n error: error instanceof Error ? error : new Error(safeSerializeError(error)),\n message: 'Redis connection failed',\n meta: {\n Host: this.options.host,\n Port: this.options.port,\n Duration: `${duration.toFixed(2)}ms`,\n Mode: useInMemoryRedis ? 'in-memory' : 'network',\n },\n });\n\n throw error;\n }\n },\n { host: this.options.host, port: this.options.port },\n );\n }\n\n public async disconnect(): Promise<void> {\n await CachePerformanceWrapper.monitorConnection(\n 'disconnect',\n async () => {\n const startTime = performance.now();\n const instanceCount = this.instances.length;\n\n try {\n await Promise.all(this.instances.map(instance => instance.disconnect()));\n\n const duration = performance.now() - startTime;\n\n if (instanceCount > 0) {\n const meta = {\n Instances: instanceCount,\n Host: this.options.host,\n Port: this.options.port,\n Duration: `${duration.toFixed(2)}ms`,\n };\n\n if (this.options.applicationConfig.log?.startUp) {\n this.log('Disconnected all Redis instances', meta);\n } else {\n this.logger.debug({ message: 'Redis instances disconnected', meta });\n }\n }\n\n this.instances = [];\n } catch (error) {\n const duration = performance.now() - startTime;\n\n this.logger.error({\n error: error instanceof Error ? error : new Error(safeSerializeError(error)),\n message: 'Redis disconnection failed',\n meta: {\n Host: this.options.host,\n Port: this.options.port,\n Instances: instanceCount,\n Duration: `${duration.toFixed(2)}ms`,\n },\n });\n\n throw error;\n }\n },\n { host: this.options.host, port: this.options.port },\n );\n }\n\n /**\n * Log Redis message\n */\n public log(message: string, meta?: Record<string, unknown>): void {\n this.logger.custom({ level: 'redis', message, meta });\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Redis, type RedisOptions } from 'ioredis';\nimport { EventEmitter } from 'node:events';\nimport type { RedisManagerConfig as RedisManagerOptions } from './manager.interface.js';\nimport RedisInstance from './instance.js';\nimport { Logger } from '../logger/index.js';\nimport { CachePerformanceWrapper } from '../performance/index.js';\nimport { safeSerializeError } from '../error/error-reporter.js';\n\nconst truthyPattern = /^(1|true|yes|on)$/i;\nconst scheduleMicrotask =\n typeof (globalThis as any).queueMicrotask === 'function'\n ? (globalThis as any).queueMicrotask.bind(globalThis)\n : (callback: () => void) => {\n void Promise.resolve().then(callback);\n };\n\ntype RedisCallback = (error: Error | null, result?: string) => void;\n\ninterface InMemoryRedisSharedState {\n store: Map<string, string | Buffer>;\n expirations: Map<string, NodeJS.Timeout>;\n subscriptions: Map<string, Set<InMemoryRedisClient>>;\n}\n\n// Global singleton shared state for in-memory Redis\nlet globalInMemoryRedisState: InMemoryRedisSharedState | null = null;\n\nfunction getGlobalInMemoryRedisState(): InMemoryRedisSharedState {\n globalInMemoryRedisState ??= {\n store: new Map<string, string | Buffer>(),\n expirations: new Map<string, NodeJS.Timeout>(),\n subscriptions: new Map<string, Set<InMemoryRedisClient>>(),\n };\n return globalInMemoryRedisState;\n}\n\n/**\n * Clears the global in-memory Redis state including all timers.\n * Useful for test cleanup to prevent timer leaks between tests.\n */\nexport function clearGlobalInMemoryRedisState(): void {\n if (globalInMemoryRedisState) {\n // Clear all expiration timers to prevent memory leaks\n for (const timer of globalInMemoryRedisState.expirations.values()) {\n clearTimeout(timer);\n }\n globalInMemoryRedisState.expirations.clear();\n globalInMemoryRedisState.store.clear();\n globalInMemoryRedisState.subscriptions.clear();\n }\n globalInMemoryRedisState = null;\n}\n\nclass InMemoryRedisClient extends EventEmitter {\n private shared: InMemoryRedisSharedState;\n\n constructor(shared: InMemoryRedisSharedState) {\n super();\n this.shared = shared;\n\n scheduleMicrotask(() => {\n this.emit('ready');\n });\n }\n\n private cleanupSubscriptions(): void {\n for (const subscribers of this.shared.subscriptions.values()) {\n subscribers.delete(this);\n }\n }\n\n private clearExpirationForKey(key: string): void {\n const timer = this.shared.expirations.get(key);\n if (timer) {\n clearTimeout(timer);\n this.shared.expirations.delete(key);\n }\n }\n\n public ping(callback?: RedisCallback): Promise<string> {\n if (callback) {\n callback(null, 'PONG');\n return Promise.resolve('PONG');\n }\n\n return Promise.resolve('PONG');\n }\n\n public async set(...args: any[]): Promise<'OK'> {\n const [key, value, mode, expiration] = args;\n const serializedValue: string | Buffer = value instanceof Buffer ? value : String(value);\n\n this.shared.store.set(key, serializedValue);\n this.clearExpirationForKey(key);\n\n if (typeof mode === 'string' && mode.toUpperCase() === 'EX' && typeof expiration === 'number') {\n const timer = setTimeout(() => {\n this.shared.store.delete(key);\n this.shared.expirations.delete(key);\n }, expiration * 1000);\n\n if (typeof timer.unref === 'function') {\n timer.unref();\n }\n\n this.shared.expirations.set(key, timer);\n }\n\n return 'OK';\n }\n\n public async get(key: string): Promise<string | null> {\n return (this.shared.store.get(key) as string | undefined) ?? null;\n }\n\n public async del(key: string): Promise<number> {\n const existed = this.shared.store.delete(key);\n this.clearExpirationForKey(key);\n return existed ? 1 : 0;\n }\n\n public async publish(channel: string, message: string): Promise<number> {\n const subscribers = this.shared.subscriptions.get(channel);\n\n if (!subscribers || subscribers.size === 0) {\n return 0;\n }\n\n for (const subscriber of subscribers) {\n scheduleMicrotask(() => {\n subscriber.emit('message', channel, message);\n });\n }\n\n return subscribers.size;\n }\n\n public async subscribe(channel: string): Promise<number> {\n let subscribers = this.shared.subscriptions.get(channel);\n if (!subscribers) {\n subscribers = new Set<InMemoryRedisClient>();\n this.shared.subscriptions.set(channel, subscribers);\n }\n subscribers.add(this);\n return subscribers.size;\n }\n\n public async unsubscribe(channel: string): Promise<number> {\n const subscribers = this.shared.subscriptions.get(channel);\n\n if (!subscribers) {\n return 0;\n }\n\n subscribers.delete(this);\n return subscribers.size;\n }\n\n public async quit(): Promise<'OK'> {\n this.cleanupSubscriptions();\n this.emit('end');\n this.removeAllListeners();\n return 'OK';\n }\n\n public disconnect(): void {\n this.cleanupSubscriptions();\n this.emit('end');\n this.removeAllListeners();\n }\n}\n\nexport default class RedisManager {\n private logger: typeof Logger = Logger;\n\n private options: RedisManagerOptions;\n\n public instances: RedisInstance[] = [];\n\n constructor(config: RedisManagerOptions) {\n this.options = config;\n }\n\n public async connect(): Promise<RedisInstance> {\n return CachePerformanceWrapper.monitorConnection(\n 'connect',\n async () => {\n const startTime = performance.now();\n\n const redisOptions: RedisOptions = {\n host: this.options.host,\n port: this.options.port,\n password: this.options.password,\n maxRetriesPerRequest: null, // Needed for bullmq\n lazyConnect: true, // Prevent automatic connection to avoid unhandled errors\n };\n\n const useInMemoryRedis =\n truthyPattern.test(process.env.PXL_REDIS_IN_MEMORY ?? '') ||\n truthyPattern.test(process.env.REDIS_IN_MEMORY ?? '');\n\n const createClient = (): Redis => {\n if (useInMemoryRedis) {\n return new InMemoryRedisClient(getGlobalInMemoryRedisState()) as unknown as Redis;\n }\n\n const client = new Redis(redisOptions);\n // Attach a temporary error handler to prevent unhandled errors during connection\n const errorHandler = (error: Error) => {\n // Error will be handled by the promise rejection below\n };\n client.once('error', errorHandler);\n return client;\n };\n\n const client = createClient();\n const publisherClient = createClient();\n const subscriberClient = createClient();\n\n try {\n // For non-in-memory clients, explicitly connect since we use lazyConnect\n if (!useInMemoryRedis) {\n await Promise.all([client.connect(), publisherClient.connect(), subscriberClient.connect()]);\n } else {\n // Wait for in-memory clients to be ready\n await Promise.all([\n new Promise<void>((resolve, reject) => {\n client.once('ready', () => resolve());\n client.once('error', (error: Error) => reject(error));\n }),\n new Promise<void>((resolve, reject) => {\n publisherClient.once('ready', () => resolve());\n publisherClient.once('error', (error: Error) => reject(error));\n }),\n new Promise<void>((resolve, reject) => {\n subscriberClient.once('ready', () => resolve());\n subscriberClient.once('error', (error: Error) => reject(error));\n }),\n ]);\n }\n\n const redisInstance = new RedisInstance({\n redisManager: this,\n client,\n publisherClient,\n subscriberClient,\n });\n\n this.instances.push(redisInstance);\n\n const duration = performance.now() - startTime;\n const meta = {\n Host: this.options.host,\n Port: this.options.port,\n Duration: `${duration.toFixed(2)}ms`,\n Mode: useInMemoryRedis ? 'in-memory' : 'network',\n };\n\n if (this.options.applicationConfig.log?.startUp) {\n this.log('Connected', meta);\n } else {\n this.logger.debug({ message: 'Redis connected', meta });\n }\n\n if (useInMemoryRedis) {\n this.logger.debug({ message: 'Using in-memory Redis stub' });\n }\n\n return redisInstance;\n } catch (error) {\n const duration = performance.now() - startTime;\n\n // Clean up clients on error\n await Promise.allSettled([client.quit(), publisherClient.quit(), subscriberClient.quit()]);\n\n this.logger.error({\n error: error instanceof Error ? error : new Error(safeSerializeError(error)),\n message: 'Redis connection failed',\n meta: {\n Host: this.options.host,\n Port: this.options.port,\n Duration: `${duration.toFixed(2)}ms`,\n Mode: useInMemoryRedis ? 'in-memory' : 'network',\n },\n });\n\n throw error;\n }\n },\n { host: this.options.host, port: this.options.port },\n );\n }\n\n public async disconnect(): Promise<void> {\n await CachePerformanceWrapper.monitorConnection(\n 'disconnect',\n async () => {\n const startTime = performance.now();\n const instanceCount = this.instances.length;\n\n try {\n await Promise.all(this.instances.map(instance => instance.disconnect()));\n\n const duration = performance.now() - startTime;\n\n if (instanceCount > 0) {\n const meta = {\n Instances: instanceCount,\n Host: this.options.host,\n Port: this.options.port,\n Duration: `${duration.toFixed(2)}ms`,\n };\n\n if (this.options.applicationConfig.log?.startUp) {\n this.log('Disconnected all Redis instances', meta);\n } else {\n this.logger.debug({ message: 'Redis instances disconnected', meta });\n }\n }\n\n this.instances = [];\n } catch (error) {\n const duration = performance.now() - startTime;\n\n this.logger.error({\n error: error instanceof Error ? error : new Error(safeSerializeError(error)),\n message: 'Redis disconnection failed',\n meta: {\n Host: this.options.host,\n Port: this.options.port,\n Instances: instanceCount,\n Duration: `${duration.toFixed(2)}ms`,\n },\n });\n\n throw error;\n }\n },\n { host: this.options.host, port: this.options.port },\n );\n }\n\n /**\n * Log Redis message\n */\n public log(message: string, meta?: Record<string, unknown>): void {\n this.logger.custom({ level: 'redis', message, meta });\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["client"]
7
7
  }
@@ -15,7 +15,12 @@ declare function convertFile({ inputFilePath, outputFilePath, format, }: {
15
15
  format: string;
16
16
  }): Promise<void>;
17
17
  /**
18
- * Copy a file or directory synchronously
18
+ * Copy a file or directory synchronously.
19
+ *
20
+ * **Note**: This function uses synchronous file system operations which block
21
+ * the event loop. It is intended for use during application initialization,
22
+ * build scripts, or CLI tools where blocking is acceptable.
23
+ * For production runtime code with high concurrency, consider using async alternatives.
19
24
  *
20
25
  * @param src The source path
21
26
  * @param dest The destination path
@@ -40,7 +45,13 @@ declare function formatFileSize({ bytes }: {
40
45
  bytes: number;
41
46
  }): string;
42
47
  /**
43
- * Remove a file or directory synchronously
48
+ * Remove a file or directory synchronously.
49
+ *
50
+ * **Note**: This function uses synchronous file system operations which block
51
+ * the event loop. It is intended for use during application cleanup, build scripts,
52
+ * or CLI tools where blocking is acceptable.
53
+ * For production runtime code with high concurrency, consider using async alternatives
54
+ * like `fs.promises.rm(target, { recursive: true, force: true })`.
44
55
  *
45
56
  * @param target The path to the file or directory to remove
46
57
  */
@@ -1 +1 @@
1
- {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/util/file.ts"],"names":[],"mappings":"AAUA;;;;GAIG;AACH,iBAAe,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO/D;AAED;;;GAGG;AACH,iBAAe,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMvD;AAED,iBAAe,WAAW,CAAC,EACzB,aAAa,EACb,cAAc,EACd,MAAM,GACP,EAAE;IACD,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBhB;AAED;;;;;GAKG;AACH,iBAAS,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAmBjD;AAED;;;;;GAKG;AACH,iBAAe,YAAY,CAAC,EAAE,GAAG,EAAE,eAAe,EAAE,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA4B7G;AAED;;;;GAIG;AACH,iBAAS,cAAc,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAyB5D;AAED;;;;GAIG;AACH,iBAAS,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAoBxC;;;;;;;;;;AAED,wBAQE"}
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/util/file.ts"],"names":[],"mappings":"AAUA;;;;GAIG;AACH,iBAAe,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO/D;AAED;;;GAGG;AACH,iBAAe,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMvD;AAED,iBAAe,WAAW,CAAC,EACzB,aAAa,EACb,cAAc,EACd,MAAM,GACP,EAAE;IACD,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBhB;AAED;;;;;;;;;;GAUG;AACH,iBAAS,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAmBjD;AAED;;;;;GAKG;AACH,iBAAe,YAAY,CAAC,EAAE,GAAG,EAAE,eAAe,EAAE,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA4B7G;AAED;;;;GAIG;AACH,iBAAS,cAAc,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAyB5D;AAED;;;;;;;;;;GAUG;AACH,iBAAS,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAoBxC;;;;;;;;;;AAED,wBAQE"}
@@ -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": "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",
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":"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"}
1
+ {"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../../../src/webserver/controller/entity.ts"],"names":[],"mappings":"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"}
@@ -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": "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",
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;IAEvD,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;IAiBM,WAAW,CAAC,EAAE,EAAE,EAAE,EAAE;QAAE,EAAE,EAAE,SAAS,CAAA;KAAE,GAAG,MAAM,GAAG,SAAS;IAI1D,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;IAsB7B,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;CAsBvB"}
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 [...this.clients.entries()].find(([_, value]) => value.ws === ws)?.[0];
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
  }