@scpxl/nodejs-framework 1.0.46 → 1.0.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/database/instance.d.ts +29 -0
- package/dist/database/instance.d.ts.map +1 -1
- package/dist/database/instance.js +45 -0
- package/dist/database/instance.js.map +2 -2
- package/dist/queue/manager.d.ts.map +1 -1
- package/dist/queue/manager.js +19 -5
- package/dist/queue/manager.js.map +3 -3
- package/dist/queue/processor/base.d.ts +46 -0
- package/dist/queue/processor/base.d.ts.map +1 -1
- package/dist/queue/processor/base.js +50 -0
- package/dist/queue/processor/base.js.map +2 -2
- package/dist/webserver/controller/entity.d.ts +7 -3
- package/dist/webserver/controller/entity.d.ts.map +1 -1
- package/dist/webserver/controller/entity.js +31 -20
- package/dist/webserver/controller/entity.js.map +2 -2
- package/dist/webserver/webserver.d.ts.map +1 -1
- package/dist/webserver/webserver.js +5 -0
- package/dist/webserver/webserver.js.map +2 -2
- package/dist/websocket/controller/server/base.d.ts +32 -0
- package/dist/websocket/controller/server/base.d.ts.map +1 -1
- package/dist/websocket/controller/server/base.js.map +2 -2
- package/dist/websocket/websocket-server.js +13 -12
- package/dist/websocket/websocket-server.js.map +2 -2
- package/package.json +1 -1
|
@@ -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';\nimport type { WebServerBaseControllerConstructorParams } from './base.interface.js';\n\nexport default abstract class EntityController extends BaseController {\n protected abstract entityName: string;\n\n protected entityManager: EntityManager;\n\n // Cache for entity modules to avoid repeated dynamic imports\n private static entityCache = new Map<string, typeof DynamicEntity>();\n\n constructor(props: WebServerBaseControllerConstructorParams) {\n super(props);\n\n const { databaseInstance } = props;\n\n this.entityManager = databaseInstance.getEntityManager();\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 // Call preGetMany hook\n await this.preGetMany({\n entityManager: this.entityManager,\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 this.entityManager.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: this.entityManager,\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 await this.preGetOne({\n entityManager: this.entityManager,\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 this.entityManager.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: this.entityManager,\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 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 = this.entityManager.create(this.entityName, value as object);\n\n await this.entityManager.persistAndFlush(item);\n\n // Call postCreateOne hook\n await this.postCreateOne({\n entityManager: this.entityManager,\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 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 this.entityManager.findOne(this.entityName, { id });\n\n if (!item) {\n return this.sendNotFoundResponse(reply, `${EntityClass.singularNameCapitalized} not found`);\n }\n\n this.entityManager.assign(item, value as object);\n\n await this.entityManager.persistAndFlush(item);\n\n // Call postUpdateOne hook\n await this.postUpdateOne({\n entityManager: this.entityManager,\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 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 this.entityManager.findOne(this.entityName, { id });\n\n if (!item) {\n return this.sendNotFoundResponse(reply, `${EntityClass.singularNameCapitalized} not found`);\n }\n\n await this.entityManager.removeAndFlush(item);\n\n reply.status(StatusCodes.NO_CONTENT).send();\n } catch (error) {\n this.sendErrorResponse({ reply, error });\n }\n };\n}\n"],
|
|
5
|
-
"mappings": ";;AAAA,OAAO;AACP,OAAO,UAAU;AAGjB,SAAS,mBAAmB;AAC5B,OAAO,oBAAoB;AAE3B,SAAS,0BAA0B;AACnC,SAAS,cAAc;
|
|
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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",
|
|
6
6
|
"names": ["queryValue"]
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webserver.d.ts","sourceRoot":"","sources":["../../src/webserver/webserver.ts"],"names":[],"mappings":"AACA,OAAgB,EACd,KAAK,eAAe,EAIpB,KAAK,WAAW,EACjB,MAAM,SAAS,CAAC;AAKjB,OAAO,EACL,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAIhC,MAAM,0BAA0B,CAAC;AAMlC,OAAO,KAAK,EAAE,gBAAgB,EAA+B,MAAM,gCAAgC,CAAC;AAGpG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAI1E,OAAO,EAAE,KAAK,eAAe,EAAyC,MAAM,2BAA2B,CAAC;AAExG,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,cAAc;QACtB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;CACF;AAED,cAAM,SAAS;IACb,OAAO,CAAC,MAAM,CAAyB;IAEvC,OAAO,CAAC,iBAAiB,CAAoB;IAE7C,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAU;IAEnD,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,gBAAgB,CAAmB;IAEpC,aAAa,EAAE,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;IAE3E,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,QAAQ,CAAS;gBAEb,MAAM,EAAE,0BAA0B,GAAG;QAAE,gBAAgB,EAAE,gBAAgB,CAAA;KAAE;IAuDvF;;OAEG;IACU,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBlC;;OAEG;YACW,iBAAiB;IAiD/B;;OAEG;IACH,OAAO,CAAC,cAAc;YAeR,QAAQ;YAeR,SAAS;YA8BT,UAAU;
|
|
1
|
+
{"version":3,"file":"webserver.d.ts","sourceRoot":"","sources":["../../src/webserver/webserver.ts"],"names":[],"mappings":"AACA,OAAgB,EACd,KAAK,eAAe,EAIpB,KAAK,WAAW,EACjB,MAAM,SAAS,CAAC;AAKjB,OAAO,EACL,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAIhC,MAAM,0BAA0B,CAAC;AAMlC,OAAO,KAAK,EAAE,gBAAgB,EAA+B,MAAM,gCAAgC,CAAC;AAGpG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAI1E,OAAO,EAAE,KAAK,eAAe,EAAyC,MAAM,2BAA2B,CAAC;AAExG,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,cAAc;QACtB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;CACF;AAED,cAAM,SAAS;IACb,OAAO,CAAC,MAAM,CAAyB;IAEvC,OAAO,CAAC,iBAAiB,CAAoB;IAE7C,OAAO,CAAC,OAAO,CAAmB;IAClC,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAU;IAEnD,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,gBAAgB,CAAmB;IAEpC,aAAa,EAAE,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;IAE3E,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,QAAQ,CAAS;gBAEb,MAAM,EAAE,0BAA0B,GAAG;QAAE,gBAAgB,EAAE,gBAAgB,CAAA;KAAE;IAuDvF;;OAEG;IACU,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBlC;;OAEG;YACW,iBAAiB;IAiD/B;;OAEG;IACH,OAAO,CAAC,cAAc;YAeR,QAAQ;YAeR,SAAS;YA8BT,UAAU;YA8CV,OAAO;YAMP,OAAO;IAIrB,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,yBAAyB;IAcjC;;OAEG;YACW,eAAe;YAkPf,uBAAuB;IA4CrC,OAAO,CAAC,oBAAoB;IA8C5B,OAAO,CAAC,YAAY;IA6CP,WAAW,CAAC,EACvB,kBAAkB,EAClB,cAAc,EACd,WAAW,EACX,SAAS,EACT,WAAW,EACX,WAAW,EACX,eAAe,GAChB,EAAE;QACD,kBAAkB,EAAE,GAAG,CAAC;QACxB,cAAc,EAAE,MAAM,CAAC;QACvB,WAAW,EAAE,WAAW,GAAG,WAAW,EAAE,CAAC;QACzC,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,wBAAwB,CAAC;QACvC,eAAe,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAC;KACzC,GAAG,OAAO,CAAC,IAAI,CAAC;IA2CjB;;OAEG;IACU,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAanC;;OAEG;IACU,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAMlC,OAAO,CAAC,kBAAkB;IAgC1B;;OAEG;IACI,OAAO,IAAI,OAAO;IAIzB;;OAEG;IACI,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;CAGlE;AAED,eAAe,SAAS,CAAC"}
|
|
@@ -171,6 +171,11 @@ class WebServer {
|
|
|
171
171
|
if (request.requestId) {
|
|
172
172
|
reply.header("X-Request-ID", request.requestId);
|
|
173
173
|
}
|
|
174
|
+
const em = request.__entityManager;
|
|
175
|
+
if (em && typeof em.clear === "function") {
|
|
176
|
+
em.clear();
|
|
177
|
+
delete request.__entityManager;
|
|
178
|
+
}
|
|
174
179
|
if (!request.startTime) {
|
|
175
180
|
return;
|
|
176
181
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/webserver/webserver.ts"],
|
|
4
|
-
"sourcesContent": ["import crypto from 'node:crypto';\nimport Fastify, {\n type FastifyInstance,\n type FastifyReply,\n type FastifyRequest,\n type FastifySchema,\n type HTTPMethods,\n} from 'fastify';\nimport cors from '@fastify/cors';\nimport helmet from '@fastify/helmet';\nimport rateLimit from '@fastify/rate-limit';\nimport multipart from '@fastify/multipart';\nimport {\n type AnyRouteSchemaDefinition,\n type WebServerConstructorParams,\n type WebServerOptions,\n type WebServerRoute,\n WebServerRouteType,\n} from './webserver.interface.js';\nimport { Logger } from '../logger/index.js';\nimport { File, Helper, Loader, Time } from '../util/index.js';\nimport WebServerUtil from './util.js';\nimport type { RedisInstance } from '../redis/index.js';\nimport type { DatabaseInstance } from '../database/index.js';\nimport type { ControllerAction, WebServerBaseControllerType } from './controller/base.interface.js';\nimport type { QueueManager } from '../queue/index.js';\nimport { WebServerHealthController } from '../index.js';\nimport type { LifecycleManager } from '../lifecycle/lifecycle-manager.js';\nimport type { ApplicationConfig } from '../application/base-application.interface.js';\nimport type EventManager from '../event/manager.js';\nimport { enterRequestContext } from '../request-context/index.js';\nimport { type ZodTypeProvider, serializerCompiler, validatorCompiler } from 'fastify-type-provider-zod';\n\ndeclare module 'fastify' {\n interface FastifyRequest {\n startTime?: number;\n requestId?: string;\n }\n}\n\nclass WebServer {\n private logger: typeof Logger = Logger;\n\n private applicationConfig: ApplicationConfig;\n\n private options: WebServerOptions;\n private routes: WebServerRoute[];\n private readonly explicitRoutesConfigured: boolean;\n\n private redisInstance: RedisInstance;\n private queueManager: QueueManager;\n private eventManager: EventManager;\n private databaseInstance: DatabaseInstance;\n\n public fastifyServer: FastifyInstance<any, any, any, any, ZodTypeProvider>;\n\n private lifecycleManager: LifecycleManager;\n private _isReady = false;\n\n constructor(params: WebServerConstructorParams & { lifecycleManager: LifecycleManager }) {\n // Define default options\n const defaultOptions: Partial<WebServerOptions> = {\n host: '0.0.0.0',\n port: 3001,\n cors: {\n enabled: false,\n },\n errors: {\n verbose: false,\n },\n debug: {\n printRoutes: false,\n simulateSlowConnection: {\n enabled: false,\n delay: 250,\n },\n },\n log: {\n startUp: true,\n },\n };\n\n // Merge default options\n const mergedOptions = Helper.defaultsDeep(params.options, defaultOptions);\n\n this.applicationConfig = params.applicationConfig;\n\n this.options = mergedOptions;\n\n const staticRoutes = Array.isArray(params.routes) ? params.routes : [];\n this.explicitRoutesConfigured = Array.isArray(params.routes);\n this.routes = [...staticRoutes];\n\n this.redisInstance = params.redisInstance;\n this.queueManager = params.queueManager;\n this.eventManager = params.eventManager;\n this.databaseInstance = params.databaseInstance;\n this.lifecycleManager = params.lifecycleManager;\n\n // Create Fastify server\n const defaultBodyLimit = 25 * 1024 * 1024; // 25MB (safer default)\n const defaultConnectionTimeout = 10 * 1000; // 10 seconds (safer default)\n\n this.fastifyServer = Fastify({\n logger: false,\n bodyLimit: this.options.bodyLimit ?? defaultBodyLimit,\n connectionTimeout: this.options.connectionTimeout ?? defaultConnectionTimeout,\n }).withTypeProvider<ZodTypeProvider>();\n\n // Set up Zod validators and serializers for automatic schema validation\n this.fastifyServer.setValidatorCompiler(validatorCompiler);\n this.fastifyServer.setSerializerCompiler(serializerCompiler);\n }\n\n /**\n * Load web server.\n */\n public async load(): Promise<void> {\n // Configure security (helmet, rate limiting)\n await this.configureSecurity();\n\n // Configure hooks\n this.configureHooks();\n\n // Configure CORS\n this.configureCORS();\n\n // Configure multipart uploads\n this.configureMultipartUploads();\n\n // Configure routes\n await this.configureRoutes();\n }\n\n /**\n * Configure security features (Helmet, Rate Limiting)\n */\n private async configureSecurity(): Promise<void> {\n const security = this.options.security ?? {};\n\n // Configure Helmet for security headers\n const helmetConfig = security.helmet ?? { enabled: true };\n if (helmetConfig.enabled !== false) {\n await this.fastifyServer.register(helmet, {\n contentSecurityPolicy: helmetConfig.contentSecurityPolicy !== false,\n crossOriginEmbedderPolicy: helmetConfig.crossOriginEmbedderPolicy !== false,\n crossOriginOpenerPolicy: helmetConfig.crossOriginOpenerPolicy !== false,\n crossOriginResourcePolicy: helmetConfig.crossOriginResourcePolicy !== false,\n dnsPrefetchControl: helmetConfig.dnsPrefetchControl !== false,\n frameguard: helmetConfig.frameguard !== false,\n hidePoweredBy: helmetConfig.hidePoweredBy !== false,\n hsts: helmetConfig.hsts !== false,\n ieNoOpen: helmetConfig.ieNoOpen !== false,\n noSniff: helmetConfig.noSniff !== false,\n originAgentCluster: helmetConfig.originAgentCluster !== false,\n permittedCrossDomainPolicies: helmetConfig.permittedCrossDomainPolicies !== false,\n referrerPolicy: helmetConfig.referrerPolicy !== false,\n xssFilter: helmetConfig.xssFilter !== false,\n });\n }\n\n // Configure rate limiting\n const rateLimitConfig = security.rateLimit ?? { enabled: true };\n if (rateLimitConfig.enabled !== false) {\n await this.fastifyServer.register(rateLimit, {\n max: rateLimitConfig.max ?? 1000,\n timeWindow: rateLimitConfig.timeWindow ?? '1 minute',\n ban: rateLimitConfig.ban,\n cache: rateLimitConfig.cache ?? 5000,\n });\n }\n\n // Warn about wildcard CORS in production\n if (process.env.NODE_ENV === 'production' && this.options.cors?.enabled) {\n const corsConfig = this.options.cors as { enabled: true; urls: string[] };\n if (corsConfig.urls?.includes('*')) {\n this.logger.warn({\n message: 'Wildcard CORS (*) is enabled in production - this is a security risk',\n meta: {\n recommendation: 'Specify allowed origins explicitly',\n },\n });\n }\n }\n }\n\n /**\n * Configure hooks.\n */\n private configureHooks(): void {\n this.fastifyServer.addHook('onListen', async () => this.onListen());\n this.fastifyServer.addHook('onRequest', async request => this.onRequest(request));\n this.fastifyServer.addHook('onResponse', async (request, reply) => this.onResponse(request, reply));\n this.fastifyServer.addHook('onError', async (request, reply, error) => this.onError(request, reply, error));\n this.fastifyServer.addHook('onClose', async () => this.onClose());\n\n // if (process.env.NODE_ENV === 'local') {\n // this.fastifyServer.addHook('onSend', (request, reply, payload, done) => {\n // reply.header('Cache-Control', 'no-store');\n // done();\n // });\n // }\n }\n\n private async onListen(): Promise<void> {\n const address = this.fastifyServer.server.address();\n const port = typeof address === 'string' ? address : address?.port;\n\n if (this.options.log?.startUp) {\n this.log('Started', {\n Host: this.options.host,\n Port: port,\n // CORS: this.options.cors?.enabled && this.options.cors?..length > 0 ? this.options.corsUrls.join(', ') : 'Disabled',\n CORS: this.options.cors?.enabled ? this.options.cors.urls.join(', ') : 'Disabled',\n 'Fastify Version': this.fastifyServer.version,\n });\n }\n }\n\n private async onRequest(request: FastifyRequest): Promise<void> {\n if (\n this.options.debug?.simulateSlowConnection?.enabled &&\n this.options.debug?.simulateSlowConnection?.delay &&\n this.options.debug?.simulateSlowConnection?.delay > 0\n ) {\n await new Promise(resolve => setTimeout(resolve, this.options.debug?.simulateSlowConnection?.delay));\n }\n\n // Generate or use existing request ID for correlation\n const requestId = (request.headers['x-request-id'] as string | undefined) ?? crypto.randomUUID();\n request.requestId = requestId;\n\n // Default health check paths to ignore from logging\n const defaultPathsToIgnore: string[] = [];\n const configuredExcludePaths = this.options.log?.excludePaths ?? [];\n const pathsToIgnore = [...defaultPathsToIgnore, ...configuredExcludePaths];\n\n if (pathsToIgnore.includes(request.url) || request.method === 'OPTIONS') {\n // ...\n } else {\n const startTime = Time.now();\n request.startTime = startTime;\n\n // Initialize AsyncLocalStorage context for this request\n // Using enterWith() to set context for the current async execution\n enterRequestContext({ requestId, startTime });\n }\n }\n\n private async onResponse(request: FastifyRequest, reply: FastifyReply): Promise<void> {\n // Add request ID to response headers for client-side correlation\n if (request.requestId) {\n reply.header('X-Request-ID', request.requestId);\n }\n\n if (!request.startTime) {\n return;\n }\n\n const executionTime = Time.calculateElapsedTimeMs({\n startTime: request.startTime,\n });\n const formattedExecutionTime = Time.formatTime({\n time: executionTime,\n numDecimals: 3,\n });\n\n const ip = request.headers['x-forwarded-for'] ?? request.ip;\n\n const logParams: Record<string, unknown> = {\n Method: request.method,\n Path: request.url,\n Status: reply.statusCode,\n };\n\n if (process.env.NODE_ENV !== 'development') {\n logParams.IP = ip.toString();\n }\n\n logParams.Time = formattedExecutionTime;\n\n // if (cluster.isWorker && cluster.worker) {\n // logParams.Worker = cluster.worker.id;\n // }\n\n this.log('API Request', logParams);\n }\n\n private async onError(request: FastifyRequest, reply: FastifyReply, error: Error): Promise<void> {\n // Adjusted for Fastify types\n Logger.error({ error });\n // Implement any additional logic here\n }\n\n private async onClose(): Promise<void> {\n this.log('Stopped');\n }\n\n private configureCORS(): void {\n if (!this.options.cors?.enabled) {\n return;\n }\n\n // Handle wildcard origin for development\n const origin = this.options.cors.urls.includes('*') ? true : this.options.cors.urls;\n\n this.fastifyServer.register(cors, {\n origin,\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n preflightContinue: false,\n optionsSuccessStatus: 204,\n // credentials: true,\n });\n }\n\n private configureMultipartUploads(): void {\n this.fastifyServer.register(multipart, {\n // attachFieldsToBody: true,\n limits: {\n fieldNameSize: 100,\n fieldSize: 1024 * 1024 * 10,\n fields: 10,\n fileSize: 1024 * 1024 * 1024 * 10, // 10GB file size limit\n files: 1,\n headerPairs: 2000,\n },\n });\n }\n\n /**\n * Configure routes.\n */\n private async configureRoutes(): Promise<void> {\n if (this.options.routesDirectory && this.explicitRoutesConfigured) {\n const baseMessage =\n 'Invalid web server configuration: choose either \"routesDirectory\" for automatic discovery or provide a \"routes\" array.';\n const guidance =\n this.routes.length === 0\n ? ' Remove the empty routes array when using \"routesDirectory\".'\n : ' Remove one of these options so only a single routes source is configured.';\n\n throw new Error(`${baseMessage}${guidance}`);\n }\n\n await this.loadRoutesFromDirectory();\n\n // Check if controllers directory exists\n const controllersDirectoryExists = await File.pathExists(this.options.controllersDirectory ?? '');\n\n if (!controllersDirectoryExists) {\n const routesRequiringControllers = this.routes.length === 0 || this.routes.some(route => !route.handler);\n\n if (routesRequiringControllers) {\n Logger.warn({\n message: 'Web server controllers directory not found',\n meta: {\n Directory: this.options.controllersDirectory,\n },\n });\n\n return;\n }\n }\n\n // Load controllers\n const controllers = controllersDirectoryExists\n ? await Loader.loadModulesInDirectory({\n directory: this.options.controllersDirectory,\n extensions: ['.ts', '.js'],\n })\n : {};\n\n // Add health check routes\n this.routes.push(\n {\n type: WebServerRouteType.Default,\n method: 'GET',\n path: '/health/live',\n controller: WebServerHealthController,\n action: 'live',\n },\n {\n type: WebServerRouteType.Default,\n method: 'GET',\n path: '/health/ready',\n controller: WebServerHealthController,\n action: 'ready',\n },\n );\n\n // Go through each route\n for (const route of this.routes) {\n let ControllerClass: WebServerBaseControllerType;\n\n let controllerName;\n\n if (route.handler && !route.controller && !route.controllerName) {\n if (route.type && route.type !== WebServerRouteType.Default) {\n throw new Error('Handler-only routes are only supported for default route type');\n }\n\n if (!('method' in route)) {\n throw new Error('Handler-only routes require an HTTP method');\n }\n\n const schema = this.buildFastifySchema(route.schema);\n\n this.fastifyServer.route({\n method: route.method,\n url: route.path,\n handler: route.handler,\n ...(schema ? { schema } : {}),\n });\n\n continue;\n }\n\n if (route.controller) {\n ControllerClass = route.controller;\n\n controllerName = ControllerClass.name;\n } else if (route.controllerName) {\n ControllerClass = controllers[route.controllerName] as WebServerBaseControllerType;\n\n controllerName = route.controllerName;\n } else {\n throw new Error('Web server controller config not found');\n }\n\n if (typeof ControllerClass !== 'function') {\n const controllerPath = `${this.options.controllersDirectory}/${route.controllerName}.ts`;\n\n Logger.warn({\n message: 'Web server controller not found',\n meta: {\n Controller: route.controllerName,\n Path: controllerPath,\n Route: `${route.path}`,\n },\n });\n\n continue;\n }\n\n // Initialize controller instance\n const controllerInstance = new ControllerClass({\n applicationConfig: this.applicationConfig,\n webServerOptions: this.options,\n redisInstance: this.redisInstance,\n queueManager: this.queueManager,\n eventManager: this.eventManager,\n databaseInstance: this.databaseInstance,\n lifecycleManager: this.lifecycleManager,\n });\n\n let routeMethod;\n let routeAction;\n let routePath;\n\n switch (route.type) {\n case WebServerRouteType.Default: {\n routeMethod = route.method;\n routeAction = route.action;\n routePath = route.path;\n\n this.defineRoute({\n controllerInstance,\n controllerName,\n routeMethod,\n routePath,\n routeAction,\n routeSchema: route.schema,\n handlerOverride: route.handler?.bind(controllerInstance),\n });\n\n break;\n }\n case WebServerRouteType.Entity: {\n if (this.applicationConfig.database?.enabled === true) {\n const entityModel = await Loader.loadEntityModule({\n entitiesDirectory: this.applicationConfig.database.entitiesDirectory,\n entityName: route.entityName,\n });\n\n const entitySchemaSource = (entityModel as { schema?: { describe?: () => unknown } }).schema;\n\n if (entitySchemaSource && typeof entitySchemaSource.describe !== 'function') {\n const reportedType =\n typeof entitySchemaSource === 'object'\n ? (entitySchemaSource?.constructor?.name ?? 'object')\n : typeof entitySchemaSource;\n\n throw new Error(\n `Entity route auto-validation requires a Joi schema with a describe() method. ` +\n `Entity \"${route.entityName}\" provided a ${reportedType}. ` +\n `If you're using Zod (schema/schemaUpdate) for this entity, migrate to the new DynamicEntity helpers or ` +\n `attach typed route validators instead of relying on WebServerRouteType.Entity auto-validation.`,\n );\n }\n\n const entitySchemaDescription =\n typeof entitySchemaSource?.describe === 'function'\n ? (entitySchemaSource.describe() as\n | {\n keys?: Record<string, { type: string; flags?: { presence?: string }; [key: string]: unknown }>;\n [key: string]: unknown;\n }\n | undefined)\n : undefined;\n\n if (\n entitySchemaSource &&\n (entitySchemaDescription === undefined ||\n typeof entitySchemaDescription !== 'object' ||\n !('keys' in entitySchemaDescription))\n ) {\n const detectedType =\n entitySchemaDescription && typeof entitySchemaDescription === 'object'\n ? (entitySchemaDescription.constructor?.name ?? 'object')\n : typeof entitySchemaDescription;\n\n throw new Error(\n `Entity route auto-validation expected Joi.describe() output with a \"keys\" map, ` +\n `but entity \"${route.entityName}\" returned ${detectedType}. ` +\n `This usually means the entity uses Zod schemas. ` +\n `Switch the entity to use DynamicEntity.defineSchemas or provide Joi-based validation for \"${route.path}\".`,\n );\n }\n\n const schemaKeys = entitySchemaDescription?.keys;\n\n const formattedEntityValidationSchema =\n entitySchemaDescription && schemaKeys\n ? {\n type: 'object',\n properties: Object.fromEntries(\n Object.entries(schemaKeys).map(([key, value]) => [key, { type: value.type }]),\n ),\n required: Object.keys(schemaKeys).filter(\n // Dynamic schema inspection of joi describe output; keys are from trusted entity definitions\n // eslint-disable-next-line security/detect-object-injection\n key => schemaKeys[key].flags?.presence === 'required',\n ),\n }\n : {};\n\n const entityRouteDefinitions = WebServerUtil.getEntityRouteDefinitions({\n basePath: route.path,\n entityValidationSchema: formattedEntityValidationSchema,\n });\n\n for (const entityRouteDefinition of entityRouteDefinitions) {\n this.defineRoute({\n controllerInstance,\n controllerName,\n routeMethod: entityRouteDefinition.method,\n routePath: entityRouteDefinition.path,\n routeAction: entityRouteDefinition.action,\n routeSchema: route.schema,\n handlerOverride: route.handler?.bind(controllerInstance),\n });\n }\n }\n\n break;\n }\n }\n }\n\n if (this.options.debug?.printRoutes) {\n this.log(`Routes:\\n${this.fastifyServer.printRoutes()}`);\n }\n }\n\n private async loadRoutesFromDirectory(): Promise<void> {\n const { routesDirectory } = this.options;\n\n if (!routesDirectory) {\n return;\n }\n\n const directoryExists = await File.pathExists(routesDirectory);\n\n if (!directoryExists) {\n this.logger.warn({\n message: 'Web server routes directory not found',\n meta: {\n Directory: routesDirectory,\n },\n });\n\n return;\n }\n\n const routeModules = await Loader.loadModulesInDirectory<\n WebServerRoute | WebServerRoute[] | { routes?: WebServerRoute[] }\n >({\n directory: routesDirectory,\n extensions: ['.ts', '.js'],\n });\n\n const loadedRoutes: WebServerRoute[] = [];\n\n for (const [moduleName, exportedRoutes] of Object.entries(routeModules)) {\n const normalizedRoutes = this.normalizeRouteExport(exportedRoutes, moduleName);\n\n if (normalizedRoutes.length === 0) {\n continue;\n }\n\n loadedRoutes.push(...normalizedRoutes);\n }\n\n if (loadedRoutes.length > 0) {\n this.routes.push(...loadedRoutes);\n }\n }\n\n private normalizeRouteExport(exportedValue: unknown, moduleName: string): WebServerRoute[] {\n const ensureRouteArray = (value: unknown): WebServerRoute[] => {\n if (Array.isArray(value)) {\n return value;\n }\n\n if (value && typeof value === 'object') {\n const maybeRoute = value as { routes?: unknown };\n\n if (Array.isArray(maybeRoute.routes)) {\n return maybeRoute.routes as WebServerRoute[];\n }\n }\n\n return value ? [value as WebServerRoute] : [];\n };\n\n const routeCandidates = ensureRouteArray(exportedValue);\n const validRoutes: WebServerRoute[] = [];\n\n for (const [index, candidate] of routeCandidates.entries()) {\n if (this.isValidRoute(candidate)) {\n validRoutes.push(candidate);\n } else {\n this.logger.warn({\n message: 'Invalid web server route definition skipped',\n meta: {\n Module: moduleName,\n Index: index,\n },\n });\n }\n }\n\n if (validRoutes.length === 0 && routeCandidates.length > 0) {\n this.logger.warn({\n message: 'No valid routes exported from module',\n meta: {\n Module: moduleName,\n },\n });\n }\n\n return validRoutes;\n }\n\n private isValidRoute(route: unknown): route is WebServerRoute {\n if (!route || typeof route !== 'object') {\n return false;\n }\n\n const candidate = route as Record<string, unknown>;\n const routePath = candidate.path;\n\n if (typeof routePath !== 'string' || routePath.length === 0) {\n return false;\n }\n\n const routeType = candidate.type ?? WebServerRouteType.Default;\n\n const controllerProvided =\n typeof candidate.controller === 'function' || typeof candidate.controllerName === 'string';\n const handlerProvided = typeof candidate.handler === 'function';\n\n if (routeType === WebServerRouteType.Entity || routeType === 'entity') {\n return controllerProvided && typeof candidate.entityName === 'string' && candidate.entityName.length > 0;\n }\n\n // For default routes, either controller+action OR handler must be provided\n if (!controllerProvided && !handlerProvided) {\n return false;\n }\n\n const method = candidate.method;\n\n const isValidMethod =\n typeof method === 'string' ||\n (Array.isArray(method) && method.length > 0 && method.every(m => typeof m === 'string'));\n\n // If handler is provided, we don't need action\n if (handlerProvided) {\n return isValidMethod;\n }\n\n // If controller is provided, we need action\n const action = candidate.action;\n const isValidAction = typeof action === 'string' && action.length > 0;\n\n return isValidMethod && isValidAction;\n }\n\n public async defineRoute({\n controllerInstance,\n controllerName,\n routeMethod,\n routePath,\n routeAction,\n routeSchema,\n handlerOverride,\n }: {\n controllerInstance: any;\n controllerName: string;\n routeMethod: HTTPMethods | HTTPMethods[];\n routePath: string;\n routeAction?: string;\n routeSchema?: AnyRouteSchemaDefinition;\n handlerOverride?: ControllerAction<any>;\n }): Promise<void> {\n let handler = handlerOverride;\n\n if (!handler) {\n if (!routeAction) {\n throw new Error('Route action is required when handler override is not provided');\n }\n\n if (!/^[A-Za-z0-9_]+$/.test(routeAction) || ['__proto__', 'prototype', 'constructor'].includes(routeAction)) {\n throw new Error('Invalid controller action name');\n }\n\n const controllerHandler = controllerInstance[routeAction as keyof typeof controllerInstance];\n\n if (!controllerHandler) {\n Logger.warn({\n message: 'Web server controller action not found',\n meta: {\n Controller: controllerName,\n Action: routeAction,\n },\n });\n\n throw new Error('Web server controller action not found');\n }\n\n handler = controllerHandler.bind(controllerInstance) as ControllerAction<any>;\n }\n\n const fastifySchema = this.buildFastifySchema(routeSchema);\n\n if (!handler) {\n throw new Error('Route handler could not be resolved');\n }\n\n this.fastifyServer.route({\n method: routeMethod,\n url: routePath,\n handler: handler as unknown as (request: FastifyRequest, reply: FastifyReply) => unknown,\n ...(fastifySchema ? { schema: fastifySchema } : {}),\n });\n }\n\n /**\n * Start web server.\n */\n public async start(): Promise<void> {\n try {\n await this.fastifyServer.listen({\n host: this.options.host,\n port: this.options.port,\n });\n this._isReady = true;\n } catch (error) {\n Logger.error({ error });\n throw error;\n }\n }\n\n /**\n * Stop web server.\n */\n public async stop(): Promise<void> {\n this._isReady = false;\n // Close Fastify server\n await this.fastifyServer.close();\n }\n\n private buildFastifySchema(routeSchema?: AnyRouteSchemaDefinition): FastifySchema | undefined {\n if (!routeSchema) {\n return undefined;\n }\n\n const schema: FastifySchema = {};\n\n // With ZodTypeProvider, we can pass Zod schemas directly\n // The type provider handles validation automatically\n if (routeSchema.params) {\n schema.params = routeSchema.params;\n }\n\n if (routeSchema.querystring) {\n schema.querystring = routeSchema.querystring;\n }\n\n if (routeSchema.body) {\n schema.body = routeSchema.body;\n }\n\n if (routeSchema.headers) {\n schema.headers = routeSchema.headers;\n }\n\n if (routeSchema.response) {\n schema.response = routeSchema.response;\n }\n\n return schema;\n }\n\n /**\n * Check if web server is ready to accept traffic.\n */\n public isReady(): boolean {\n return this._isReady && this.fastifyServer.server?.listening === true;\n }\n\n /**\n * Log web server message\n */\n public log(message: string, meta?: Record<string, unknown>): void {\n this.logger.custom({ level: 'webServer', message, meta });\n }\n}\n\nexport default WebServer;\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import crypto from 'node:crypto';\nimport Fastify, {\n type FastifyInstance,\n type FastifyReply,\n type FastifyRequest,\n type FastifySchema,\n type HTTPMethods,\n} from 'fastify';\nimport cors from '@fastify/cors';\nimport helmet from '@fastify/helmet';\nimport rateLimit from '@fastify/rate-limit';\nimport multipart from '@fastify/multipart';\nimport {\n type AnyRouteSchemaDefinition,\n type WebServerConstructorParams,\n type WebServerOptions,\n type WebServerRoute,\n WebServerRouteType,\n} from './webserver.interface.js';\nimport { Logger } from '../logger/index.js';\nimport { File, Helper, Loader, Time } from '../util/index.js';\nimport WebServerUtil from './util.js';\nimport type { RedisInstance } from '../redis/index.js';\nimport type { DatabaseInstance } from '../database/index.js';\nimport type { ControllerAction, WebServerBaseControllerType } from './controller/base.interface.js';\nimport type { QueueManager } from '../queue/index.js';\nimport { WebServerHealthController } from '../index.js';\nimport type { LifecycleManager } from '../lifecycle/lifecycle-manager.js';\nimport type { ApplicationConfig } from '../application/base-application.interface.js';\nimport type EventManager from '../event/manager.js';\nimport { enterRequestContext } from '../request-context/index.js';\nimport { type ZodTypeProvider, serializerCompiler, validatorCompiler } from 'fastify-type-provider-zod';\n\ndeclare module 'fastify' {\n interface FastifyRequest {\n startTime?: number;\n requestId?: string;\n }\n}\n\nclass WebServer {\n private logger: typeof Logger = Logger;\n\n private applicationConfig: ApplicationConfig;\n\n private options: WebServerOptions;\n private routes: WebServerRoute[];\n private readonly explicitRoutesConfigured: boolean;\n\n private redisInstance: RedisInstance;\n private queueManager: QueueManager;\n private eventManager: EventManager;\n private databaseInstance: DatabaseInstance;\n\n public fastifyServer: FastifyInstance<any, any, any, any, ZodTypeProvider>;\n\n private lifecycleManager: LifecycleManager;\n private _isReady = false;\n\n constructor(params: WebServerConstructorParams & { lifecycleManager: LifecycleManager }) {\n // Define default options\n const defaultOptions: Partial<WebServerOptions> = {\n host: '0.0.0.0',\n port: 3001,\n cors: {\n enabled: false,\n },\n errors: {\n verbose: false,\n },\n debug: {\n printRoutes: false,\n simulateSlowConnection: {\n enabled: false,\n delay: 250,\n },\n },\n log: {\n startUp: true,\n },\n };\n\n // Merge default options\n const mergedOptions = Helper.defaultsDeep(params.options, defaultOptions);\n\n this.applicationConfig = params.applicationConfig;\n\n this.options = mergedOptions;\n\n const staticRoutes = Array.isArray(params.routes) ? params.routes : [];\n this.explicitRoutesConfigured = Array.isArray(params.routes);\n this.routes = [...staticRoutes];\n\n this.redisInstance = params.redisInstance;\n this.queueManager = params.queueManager;\n this.eventManager = params.eventManager;\n this.databaseInstance = params.databaseInstance;\n this.lifecycleManager = params.lifecycleManager;\n\n // Create Fastify server\n const defaultBodyLimit = 25 * 1024 * 1024; // 25MB (safer default)\n const defaultConnectionTimeout = 10 * 1000; // 10 seconds (safer default)\n\n this.fastifyServer = Fastify({\n logger: false,\n bodyLimit: this.options.bodyLimit ?? defaultBodyLimit,\n connectionTimeout: this.options.connectionTimeout ?? defaultConnectionTimeout,\n }).withTypeProvider<ZodTypeProvider>();\n\n // Set up Zod validators and serializers for automatic schema validation\n this.fastifyServer.setValidatorCompiler(validatorCompiler);\n this.fastifyServer.setSerializerCompiler(serializerCompiler);\n }\n\n /**\n * Load web server.\n */\n public async load(): Promise<void> {\n // Configure security (helmet, rate limiting)\n await this.configureSecurity();\n\n // Configure hooks\n this.configureHooks();\n\n // Configure CORS\n this.configureCORS();\n\n // Configure multipart uploads\n this.configureMultipartUploads();\n\n // Configure routes\n await this.configureRoutes();\n }\n\n /**\n * Configure security features (Helmet, Rate Limiting)\n */\n private async configureSecurity(): Promise<void> {\n const security = this.options.security ?? {};\n\n // Configure Helmet for security headers\n const helmetConfig = security.helmet ?? { enabled: true };\n if (helmetConfig.enabled !== false) {\n await this.fastifyServer.register(helmet, {\n contentSecurityPolicy: helmetConfig.contentSecurityPolicy !== false,\n crossOriginEmbedderPolicy: helmetConfig.crossOriginEmbedderPolicy !== false,\n crossOriginOpenerPolicy: helmetConfig.crossOriginOpenerPolicy !== false,\n crossOriginResourcePolicy: helmetConfig.crossOriginResourcePolicy !== false,\n dnsPrefetchControl: helmetConfig.dnsPrefetchControl !== false,\n frameguard: helmetConfig.frameguard !== false,\n hidePoweredBy: helmetConfig.hidePoweredBy !== false,\n hsts: helmetConfig.hsts !== false,\n ieNoOpen: helmetConfig.ieNoOpen !== false,\n noSniff: helmetConfig.noSniff !== false,\n originAgentCluster: helmetConfig.originAgentCluster !== false,\n permittedCrossDomainPolicies: helmetConfig.permittedCrossDomainPolicies !== false,\n referrerPolicy: helmetConfig.referrerPolicy !== false,\n xssFilter: helmetConfig.xssFilter !== false,\n });\n }\n\n // Configure rate limiting\n const rateLimitConfig = security.rateLimit ?? { enabled: true };\n if (rateLimitConfig.enabled !== false) {\n await this.fastifyServer.register(rateLimit, {\n max: rateLimitConfig.max ?? 1000,\n timeWindow: rateLimitConfig.timeWindow ?? '1 minute',\n ban: rateLimitConfig.ban,\n cache: rateLimitConfig.cache ?? 5000,\n });\n }\n\n // Warn about wildcard CORS in production\n if (process.env.NODE_ENV === 'production' && this.options.cors?.enabled) {\n const corsConfig = this.options.cors as { enabled: true; urls: string[] };\n if (corsConfig.urls?.includes('*')) {\n this.logger.warn({\n message: 'Wildcard CORS (*) is enabled in production - this is a security risk',\n meta: {\n recommendation: 'Specify allowed origins explicitly',\n },\n });\n }\n }\n }\n\n /**\n * Configure hooks.\n */\n private configureHooks(): void {\n this.fastifyServer.addHook('onListen', async () => this.onListen());\n this.fastifyServer.addHook('onRequest', async request => this.onRequest(request));\n this.fastifyServer.addHook('onResponse', async (request, reply) => this.onResponse(request, reply));\n this.fastifyServer.addHook('onError', async (request, reply, error) => this.onError(request, reply, error));\n this.fastifyServer.addHook('onClose', async () => this.onClose());\n\n // if (process.env.NODE_ENV === 'local') {\n // this.fastifyServer.addHook('onSend', (request, reply, payload, done) => {\n // reply.header('Cache-Control', 'no-store');\n // done();\n // });\n // }\n }\n\n private async onListen(): Promise<void> {\n const address = this.fastifyServer.server.address();\n const port = typeof address === 'string' ? address : address?.port;\n\n if (this.options.log?.startUp) {\n this.log('Started', {\n Host: this.options.host,\n Port: port,\n // CORS: this.options.cors?.enabled && this.options.cors?..length > 0 ? this.options.corsUrls.join(', ') : 'Disabled',\n CORS: this.options.cors?.enabled ? this.options.cors.urls.join(', ') : 'Disabled',\n 'Fastify Version': this.fastifyServer.version,\n });\n }\n }\n\n private async onRequest(request: FastifyRequest): Promise<void> {\n if (\n this.options.debug?.simulateSlowConnection?.enabled &&\n this.options.debug?.simulateSlowConnection?.delay &&\n this.options.debug?.simulateSlowConnection?.delay > 0\n ) {\n await new Promise(resolve => setTimeout(resolve, this.options.debug?.simulateSlowConnection?.delay));\n }\n\n // Generate or use existing request ID for correlation\n const requestId = (request.headers['x-request-id'] as string | undefined) ?? crypto.randomUUID();\n request.requestId = requestId;\n\n // Default health check paths to ignore from logging\n const defaultPathsToIgnore: string[] = [];\n const configuredExcludePaths = this.options.log?.excludePaths ?? [];\n const pathsToIgnore = [...defaultPathsToIgnore, ...configuredExcludePaths];\n\n if (pathsToIgnore.includes(request.url) || request.method === 'OPTIONS') {\n // ...\n } else {\n const startTime = Time.now();\n request.startTime = startTime;\n\n // Initialize AsyncLocalStorage context for this request\n // Using enterWith() to set context for the current async execution\n enterRequestContext({ requestId, startTime });\n }\n }\n\n private async onResponse(request: FastifyRequest, reply: FastifyReply): Promise<void> {\n // Add request ID to response headers for client-side correlation\n if (request.requestId) {\n reply.header('X-Request-ID', request.requestId);\n }\n\n // Clean up request-scoped EntityManager if it exists\n const em = (request as any).__entityManager;\n if (em && typeof em.clear === 'function') {\n em.clear();\n delete (request as any).__entityManager;\n }\n\n if (!request.startTime) {\n return;\n }\n\n const executionTime = Time.calculateElapsedTimeMs({\n startTime: request.startTime,\n });\n const formattedExecutionTime = Time.formatTime({\n time: executionTime,\n numDecimals: 3,\n });\n\n const ip = request.headers['x-forwarded-for'] ?? request.ip;\n\n const logParams: Record<string, unknown> = {\n Method: request.method,\n Path: request.url,\n Status: reply.statusCode,\n };\n\n if (process.env.NODE_ENV !== 'development') {\n logParams.IP = ip.toString();\n }\n\n logParams.Time = formattedExecutionTime;\n\n // if (cluster.isWorker && cluster.worker) {\n // logParams.Worker = cluster.worker.id;\n // }\n\n this.log('API Request', logParams);\n }\n\n private async onError(request: FastifyRequest, reply: FastifyReply, error: Error): Promise<void> {\n // Adjusted for Fastify types\n Logger.error({ error });\n // Implement any additional logic here\n }\n\n private async onClose(): Promise<void> {\n this.log('Stopped');\n }\n\n private configureCORS(): void {\n if (!this.options.cors?.enabled) {\n return;\n }\n\n // Handle wildcard origin for development\n const origin = this.options.cors.urls.includes('*') ? true : this.options.cors.urls;\n\n this.fastifyServer.register(cors, {\n origin,\n methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],\n allowedHeaders: ['Content-Type', 'Authorization'],\n preflightContinue: false,\n optionsSuccessStatus: 204,\n // credentials: true,\n });\n }\n\n private configureMultipartUploads(): void {\n this.fastifyServer.register(multipart, {\n // attachFieldsToBody: true,\n limits: {\n fieldNameSize: 100,\n fieldSize: 1024 * 1024 * 10,\n fields: 10,\n fileSize: 1024 * 1024 * 1024 * 10, // 10GB file size limit\n files: 1,\n headerPairs: 2000,\n },\n });\n }\n\n /**\n * Configure routes.\n */\n private async configureRoutes(): Promise<void> {\n if (this.options.routesDirectory && this.explicitRoutesConfigured) {\n const baseMessage =\n 'Invalid web server configuration: choose either \"routesDirectory\" for automatic discovery or provide a \"routes\" array.';\n const guidance =\n this.routes.length === 0\n ? ' Remove the empty routes array when using \"routesDirectory\".'\n : ' Remove one of these options so only a single routes source is configured.';\n\n throw new Error(`${baseMessage}${guidance}`);\n }\n\n await this.loadRoutesFromDirectory();\n\n // Check if controllers directory exists\n const controllersDirectoryExists = await File.pathExists(this.options.controllersDirectory ?? '');\n\n if (!controllersDirectoryExists) {\n const routesRequiringControllers = this.routes.length === 0 || this.routes.some(route => !route.handler);\n\n if (routesRequiringControllers) {\n Logger.warn({\n message: 'Web server controllers directory not found',\n meta: {\n Directory: this.options.controllersDirectory,\n },\n });\n\n return;\n }\n }\n\n // Load controllers\n const controllers = controllersDirectoryExists\n ? await Loader.loadModulesInDirectory({\n directory: this.options.controllersDirectory,\n extensions: ['.ts', '.js'],\n })\n : {};\n\n // Add health check routes\n this.routes.push(\n {\n type: WebServerRouteType.Default,\n method: 'GET',\n path: '/health/live',\n controller: WebServerHealthController,\n action: 'live',\n },\n {\n type: WebServerRouteType.Default,\n method: 'GET',\n path: '/health/ready',\n controller: WebServerHealthController,\n action: 'ready',\n },\n );\n\n // Go through each route\n for (const route of this.routes) {\n let ControllerClass: WebServerBaseControllerType;\n\n let controllerName;\n\n if (route.handler && !route.controller && !route.controllerName) {\n if (route.type && route.type !== WebServerRouteType.Default) {\n throw new Error('Handler-only routes are only supported for default route type');\n }\n\n if (!('method' in route)) {\n throw new Error('Handler-only routes require an HTTP method');\n }\n\n const schema = this.buildFastifySchema(route.schema);\n\n this.fastifyServer.route({\n method: route.method,\n url: route.path,\n handler: route.handler,\n ...(schema ? { schema } : {}),\n });\n\n continue;\n }\n\n if (route.controller) {\n ControllerClass = route.controller;\n\n controllerName = ControllerClass.name;\n } else if (route.controllerName) {\n ControllerClass = controllers[route.controllerName] as WebServerBaseControllerType;\n\n controllerName = route.controllerName;\n } else {\n throw new Error('Web server controller config not found');\n }\n\n if (typeof ControllerClass !== 'function') {\n const controllerPath = `${this.options.controllersDirectory}/${route.controllerName}.ts`;\n\n Logger.warn({\n message: 'Web server controller not found',\n meta: {\n Controller: route.controllerName,\n Path: controllerPath,\n Route: `${route.path}`,\n },\n });\n\n continue;\n }\n\n // Initialize controller instance\n const controllerInstance = new ControllerClass({\n applicationConfig: this.applicationConfig,\n webServerOptions: this.options,\n redisInstance: this.redisInstance,\n queueManager: this.queueManager,\n eventManager: this.eventManager,\n databaseInstance: this.databaseInstance,\n lifecycleManager: this.lifecycleManager,\n });\n\n let routeMethod;\n let routeAction;\n let routePath;\n\n switch (route.type) {\n case WebServerRouteType.Default: {\n routeMethod = route.method;\n routeAction = route.action;\n routePath = route.path;\n\n this.defineRoute({\n controllerInstance,\n controllerName,\n routeMethod,\n routePath,\n routeAction,\n routeSchema: route.schema,\n handlerOverride: route.handler?.bind(controllerInstance),\n });\n\n break;\n }\n case WebServerRouteType.Entity: {\n if (this.applicationConfig.database?.enabled === true) {\n const entityModel = await Loader.loadEntityModule({\n entitiesDirectory: this.applicationConfig.database.entitiesDirectory,\n entityName: route.entityName,\n });\n\n const entitySchemaSource = (entityModel as { schema?: { describe?: () => unknown } }).schema;\n\n if (entitySchemaSource && typeof entitySchemaSource.describe !== 'function') {\n const reportedType =\n typeof entitySchemaSource === 'object'\n ? (entitySchemaSource?.constructor?.name ?? 'object')\n : typeof entitySchemaSource;\n\n throw new Error(\n `Entity route auto-validation requires a Joi schema with a describe() method. ` +\n `Entity \"${route.entityName}\" provided a ${reportedType}. ` +\n `If you're using Zod (schema/schemaUpdate) for this entity, migrate to the new DynamicEntity helpers or ` +\n `attach typed route validators instead of relying on WebServerRouteType.Entity auto-validation.`,\n );\n }\n\n const entitySchemaDescription =\n typeof entitySchemaSource?.describe === 'function'\n ? (entitySchemaSource.describe() as\n | {\n keys?: Record<string, { type: string; flags?: { presence?: string }; [key: string]: unknown }>;\n [key: string]: unknown;\n }\n | undefined)\n : undefined;\n\n if (\n entitySchemaSource &&\n (entitySchemaDescription === undefined ||\n typeof entitySchemaDescription !== 'object' ||\n !('keys' in entitySchemaDescription))\n ) {\n const detectedType =\n entitySchemaDescription && typeof entitySchemaDescription === 'object'\n ? (entitySchemaDescription.constructor?.name ?? 'object')\n : typeof entitySchemaDescription;\n\n throw new Error(\n `Entity route auto-validation expected Joi.describe() output with a \"keys\" map, ` +\n `but entity \"${route.entityName}\" returned ${detectedType}. ` +\n `This usually means the entity uses Zod schemas. ` +\n `Switch the entity to use DynamicEntity.defineSchemas or provide Joi-based validation for \"${route.path}\".`,\n );\n }\n\n const schemaKeys = entitySchemaDescription?.keys;\n\n const formattedEntityValidationSchema =\n entitySchemaDescription && schemaKeys\n ? {\n type: 'object',\n properties: Object.fromEntries(\n Object.entries(schemaKeys).map(([key, value]) => [key, { type: value.type }]),\n ),\n required: Object.keys(schemaKeys).filter(\n // Dynamic schema inspection of joi describe output; keys are from trusted entity definitions\n // eslint-disable-next-line security/detect-object-injection\n key => schemaKeys[key].flags?.presence === 'required',\n ),\n }\n : {};\n\n const entityRouteDefinitions = WebServerUtil.getEntityRouteDefinitions({\n basePath: route.path,\n entityValidationSchema: formattedEntityValidationSchema,\n });\n\n for (const entityRouteDefinition of entityRouteDefinitions) {\n this.defineRoute({\n controllerInstance,\n controllerName,\n routeMethod: entityRouteDefinition.method,\n routePath: entityRouteDefinition.path,\n routeAction: entityRouteDefinition.action,\n routeSchema: route.schema,\n handlerOverride: route.handler?.bind(controllerInstance),\n });\n }\n }\n\n break;\n }\n }\n }\n\n if (this.options.debug?.printRoutes) {\n this.log(`Routes:\\n${this.fastifyServer.printRoutes()}`);\n }\n }\n\n private async loadRoutesFromDirectory(): Promise<void> {\n const { routesDirectory } = this.options;\n\n if (!routesDirectory) {\n return;\n }\n\n const directoryExists = await File.pathExists(routesDirectory);\n\n if (!directoryExists) {\n this.logger.warn({\n message: 'Web server routes directory not found',\n meta: {\n Directory: routesDirectory,\n },\n });\n\n return;\n }\n\n const routeModules = await Loader.loadModulesInDirectory<\n WebServerRoute | WebServerRoute[] | { routes?: WebServerRoute[] }\n >({\n directory: routesDirectory,\n extensions: ['.ts', '.js'],\n });\n\n const loadedRoutes: WebServerRoute[] = [];\n\n for (const [moduleName, exportedRoutes] of Object.entries(routeModules)) {\n const normalizedRoutes = this.normalizeRouteExport(exportedRoutes, moduleName);\n\n if (normalizedRoutes.length === 0) {\n continue;\n }\n\n loadedRoutes.push(...normalizedRoutes);\n }\n\n if (loadedRoutes.length > 0) {\n this.routes.push(...loadedRoutes);\n }\n }\n\n private normalizeRouteExport(exportedValue: unknown, moduleName: string): WebServerRoute[] {\n const ensureRouteArray = (value: unknown): WebServerRoute[] => {\n if (Array.isArray(value)) {\n return value;\n }\n\n if (value && typeof value === 'object') {\n const maybeRoute = value as { routes?: unknown };\n\n if (Array.isArray(maybeRoute.routes)) {\n return maybeRoute.routes as WebServerRoute[];\n }\n }\n\n return value ? [value as WebServerRoute] : [];\n };\n\n const routeCandidates = ensureRouteArray(exportedValue);\n const validRoutes: WebServerRoute[] = [];\n\n for (const [index, candidate] of routeCandidates.entries()) {\n if (this.isValidRoute(candidate)) {\n validRoutes.push(candidate);\n } else {\n this.logger.warn({\n message: 'Invalid web server route definition skipped',\n meta: {\n Module: moduleName,\n Index: index,\n },\n });\n }\n }\n\n if (validRoutes.length === 0 && routeCandidates.length > 0) {\n this.logger.warn({\n message: 'No valid routes exported from module',\n meta: {\n Module: moduleName,\n },\n });\n }\n\n return validRoutes;\n }\n\n private isValidRoute(route: unknown): route is WebServerRoute {\n if (!route || typeof route !== 'object') {\n return false;\n }\n\n const candidate = route as Record<string, unknown>;\n const routePath = candidate.path;\n\n if (typeof routePath !== 'string' || routePath.length === 0) {\n return false;\n }\n\n const routeType = candidate.type ?? WebServerRouteType.Default;\n\n const controllerProvided =\n typeof candidate.controller === 'function' || typeof candidate.controllerName === 'string';\n const handlerProvided = typeof candidate.handler === 'function';\n\n if (routeType === WebServerRouteType.Entity || routeType === 'entity') {\n return controllerProvided && typeof candidate.entityName === 'string' && candidate.entityName.length > 0;\n }\n\n // For default routes, either controller+action OR handler must be provided\n if (!controllerProvided && !handlerProvided) {\n return false;\n }\n\n const method = candidate.method;\n\n const isValidMethod =\n typeof method === 'string' ||\n (Array.isArray(method) && method.length > 0 && method.every(m => typeof m === 'string'));\n\n // If handler is provided, we don't need action\n if (handlerProvided) {\n return isValidMethod;\n }\n\n // If controller is provided, we need action\n const action = candidate.action;\n const isValidAction = typeof action === 'string' && action.length > 0;\n\n return isValidMethod && isValidAction;\n }\n\n public async defineRoute({\n controllerInstance,\n controllerName,\n routeMethod,\n routePath,\n routeAction,\n routeSchema,\n handlerOverride,\n }: {\n controllerInstance: any;\n controllerName: string;\n routeMethod: HTTPMethods | HTTPMethods[];\n routePath: string;\n routeAction?: string;\n routeSchema?: AnyRouteSchemaDefinition;\n handlerOverride?: ControllerAction<any>;\n }): Promise<void> {\n let handler = handlerOverride;\n\n if (!handler) {\n if (!routeAction) {\n throw new Error('Route action is required when handler override is not provided');\n }\n\n if (!/^[A-Za-z0-9_]+$/.test(routeAction) || ['__proto__', 'prototype', 'constructor'].includes(routeAction)) {\n throw new Error('Invalid controller action name');\n }\n\n const controllerHandler = controllerInstance[routeAction as keyof typeof controllerInstance];\n\n if (!controllerHandler) {\n Logger.warn({\n message: 'Web server controller action not found',\n meta: {\n Controller: controllerName,\n Action: routeAction,\n },\n });\n\n throw new Error('Web server controller action not found');\n }\n\n handler = controllerHandler.bind(controllerInstance) as ControllerAction<any>;\n }\n\n const fastifySchema = this.buildFastifySchema(routeSchema);\n\n if (!handler) {\n throw new Error('Route handler could not be resolved');\n }\n\n this.fastifyServer.route({\n method: routeMethod,\n url: routePath,\n handler: handler as unknown as (request: FastifyRequest, reply: FastifyReply) => unknown,\n ...(fastifySchema ? { schema: fastifySchema } : {}),\n });\n }\n\n /**\n * Start web server.\n */\n public async start(): Promise<void> {\n try {\n await this.fastifyServer.listen({\n host: this.options.host,\n port: this.options.port,\n });\n this._isReady = true;\n } catch (error) {\n Logger.error({ error });\n throw error;\n }\n }\n\n /**\n * Stop web server.\n */\n public async stop(): Promise<void> {\n this._isReady = false;\n // Close Fastify server\n await this.fastifyServer.close();\n }\n\n private buildFastifySchema(routeSchema?: AnyRouteSchemaDefinition): FastifySchema | undefined {\n if (!routeSchema) {\n return undefined;\n }\n\n const schema: FastifySchema = {};\n\n // With ZodTypeProvider, we can pass Zod schemas directly\n // The type provider handles validation automatically\n if (routeSchema.params) {\n schema.params = routeSchema.params;\n }\n\n if (routeSchema.querystring) {\n schema.querystring = routeSchema.querystring;\n }\n\n if (routeSchema.body) {\n schema.body = routeSchema.body;\n }\n\n if (routeSchema.headers) {\n schema.headers = routeSchema.headers;\n }\n\n if (routeSchema.response) {\n schema.response = routeSchema.response;\n }\n\n return schema;\n }\n\n /**\n * Check if web server is ready to accept traffic.\n */\n public isReady(): boolean {\n return this._isReady && this.fastifyServer.server?.listening === true;\n }\n\n /**\n * Log web server message\n */\n public log(message: string, meta?: Record<string, unknown>): void {\n this.logger.custom({ level: 'webServer', message, meta });\n }\n}\n\nexport default WebServer;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,6 +3,38 @@ import type QueueManager from '../../../queue/manager.js';
|
|
|
3
3
|
import type { RedisInstance } from '../../../redis/index.js';
|
|
4
4
|
import type WebSocketServer from '../../websocket-server.js';
|
|
5
5
|
import type { WebSocketServerBaseControllerConstructorParams } from './base.interface.js';
|
|
6
|
+
/**
|
|
7
|
+
* Base WebSocket Server Controller
|
|
8
|
+
*
|
|
9
|
+
* ⚠️ IMPORTANT MEMORY MANAGEMENT:
|
|
10
|
+
*
|
|
11
|
+
* WebSocket controllers are LONG-LIVED SINGLETONS - one instance handles
|
|
12
|
+
* ALL client connections and messages throughout the application lifetime.
|
|
13
|
+
*
|
|
14
|
+
* ❌ WRONG - Memory Leak:
|
|
15
|
+
* ```typescript
|
|
16
|
+
* class MyController extends WebSocketServerBaseController {
|
|
17
|
+
* private em = this.databaseInstance.getEntityManager(); // LEAK!
|
|
18
|
+
* async handleMessage(ws, data) {
|
|
19
|
+
* await this.em.findOne('User', { id: data.userId }); // Identity map grows forever
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* ✅ CORRECT - Per-message EntityManager:
|
|
25
|
+
* ```typescript
|
|
26
|
+
* class MyController extends WebSocketServerBaseController {
|
|
27
|
+
* async handleMessage(ws, data) {
|
|
28
|
+
* await this.databaseInstance.withEntityManager(async (em) => {
|
|
29
|
+
* const user = await em.findOne('User', { id: data.userId });
|
|
30
|
+
* // em automatically cleaned up after this block
|
|
31
|
+
* });
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @see DatabaseInstance.withEntityManager for safe EntityManager usage
|
|
37
|
+
*/
|
|
6
38
|
export default abstract class WebSocketServerBaseController {
|
|
7
39
|
protected webSocketServer: WebSocketServer;
|
|
8
40
|
protected redisInstance: RedisInstance;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/websocket/controller/server/base.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,gBAAgB,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,YAAY,MAAM,2BAA2B,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,KAAK,eAAe,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,EAAE,8CAA8C,EAAE,MAAM,qBAAqB,CAAC;AAE1F,MAAM,CAAC,OAAO,CAAC,QAAQ,OAAO,6BAA6B;IACzD,SAAS,CAAC,eAAe,EAAE,eAAe,CAAC;IAC3C,SAAS,CAAC,aAAa,EAAE,aAAa,CAAC;IACvC,SAAS,CAAC,YAAY,EAAE,YAAY,CAAC;IACrC,SAAS,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;gBAEjC,EACV,eAAe,EACf,aAAa,EACb,YAAY,EACZ,gBAAgB,GACjB,EAAE,8CAA8C;CAMlD"}
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/websocket/controller/server/base.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,gBAAgB,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,YAAY,MAAM,2BAA2B,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,KAAK,eAAe,MAAM,2BAA2B,CAAC;AAC7D,OAAO,KAAK,EAAE,8CAA8C,EAAE,MAAM,qBAAqB,CAAC;AAE1F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,CAAC,OAAO,CAAC,QAAQ,OAAO,6BAA6B;IACzD,SAAS,CAAC,eAAe,EAAE,eAAe,CAAC;IAC3C,SAAS,CAAC,aAAa,EAAE,aAAa,CAAC;IACvC,SAAS,CAAC,YAAY,EAAE,YAAY,CAAC;IACrC,SAAS,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;gBAEjC,EACV,eAAe,EACf,aAAa,EACb,YAAY,EACZ,gBAAgB,GACjB,EAAE,8CAA8C;CAMlD"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/websocket/controller/server/base.ts"],
|
|
4
|
-
"sourcesContent": ["import type DatabaseInstance from '../../../database/instance.js';\nimport type QueueManager from '../../../queue/manager.js';\nimport type { RedisInstance } from '../../../redis/index.js';\nimport type WebSocketServer from '../../websocket-server.js';\nimport type { WebSocketServerBaseControllerConstructorParams } from './base.interface.js';\n\nexport default abstract class WebSocketServerBaseController {\n protected webSocketServer: WebSocketServer;\n protected redisInstance: RedisInstance;\n protected queueManager: QueueManager;\n protected databaseInstance: DatabaseInstance;\n\n constructor({\n webSocketServer,\n redisInstance,\n queueManager,\n databaseInstance,\n }: WebSocketServerBaseControllerConstructorParams) {\n this.webSocketServer = webSocketServer;\n this.redisInstance = redisInstance;\n this.queueManager = queueManager;\n this.databaseInstance = databaseInstance;\n }\n}\n"],
|
|
5
|
-
"mappings": ";;
|
|
4
|
+
"sourcesContent": ["import type DatabaseInstance from '../../../database/instance.js';\nimport type QueueManager from '../../../queue/manager.js';\nimport type { RedisInstance } from '../../../redis/index.js';\nimport type WebSocketServer from '../../websocket-server.js';\nimport type { WebSocketServerBaseControllerConstructorParams } from './base.interface.js';\n\n/**\n * Base WebSocket Server Controller\n *\n * \u26A0\uFE0F IMPORTANT MEMORY MANAGEMENT:\n *\n * WebSocket controllers are LONG-LIVED SINGLETONS - one instance handles\n * ALL client connections and messages throughout the application lifetime.\n *\n * \u274C WRONG - Memory Leak:\n * ```typescript\n * class MyController extends WebSocketServerBaseController {\n * private em = this.databaseInstance.getEntityManager(); // LEAK!\n * async handleMessage(ws, data) {\n * await this.em.findOne('User', { id: data.userId }); // Identity map grows forever\n * }\n * }\n * ```\n *\n * \u2705 CORRECT - Per-message EntityManager:\n * ```typescript\n * class MyController extends WebSocketServerBaseController {\n * async handleMessage(ws, data) {\n * await this.databaseInstance.withEntityManager(async (em) => {\n * const user = await em.findOne('User', { id: data.userId });\n * // em automatically cleaned up after this block\n * });\n * }\n * }\n * ```\n *\n * @see DatabaseInstance.withEntityManager for safe EntityManager usage\n */\nexport default abstract class WebSocketServerBaseController {\n protected webSocketServer: WebSocketServer;\n protected redisInstance: RedisInstance;\n protected queueManager: QueueManager;\n protected databaseInstance: DatabaseInstance;\n\n constructor({\n webSocketServer,\n redisInstance,\n queueManager,\n databaseInstance,\n }: WebSocketServerBaseControllerConstructorParams) {\n this.webSocketServer = webSocketServer;\n this.redisInstance = redisInstance;\n this.queueManager = queueManager;\n this.databaseInstance = databaseInstance;\n }\n}\n"],
|
|
5
|
+
"mappings": ";;AAsCA,MAAO,8BAAqD;AAAA,EAtC5D,OAsC4D;AAAA;AAAA;AAAA,EAChD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEV,YAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAAmD;AACjD,SAAK,kBAAkB;AACvB,SAAK,gBAAgB;AACrB,SAAK,eAAe;AACpB,SAAK,mBAAmB;AAAA,EAC1B;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -937,18 +937,19 @@ class WebSocketServer extends WebSocketBase {
|
|
|
937
937
|
}
|
|
938
938
|
let userData = {};
|
|
939
939
|
if (userId) {
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
940
|
+
userData = await this.databaseInstance.withEntityManager(async (em) => {
|
|
941
|
+
const getUserQuery = "SELECT email FROM users WHERE id = ?";
|
|
942
|
+
const getUserParams = [userId];
|
|
943
|
+
const getUserResult = await em.execute(getUserQuery, getUserParams);
|
|
944
|
+
if (!getUserResult || getUserResult.length === 0) {
|
|
945
|
+
throw new Error("User not found in database");
|
|
946
|
+
}
|
|
947
|
+
const user = getUserResult[0];
|
|
948
|
+
return {
|
|
949
|
+
id: userId,
|
|
950
|
+
...user
|
|
951
|
+
};
|
|
952
|
+
});
|
|
952
953
|
}
|
|
953
954
|
if (username) {
|
|
954
955
|
userData.username = username;
|