@warlock.js/core 4.15.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -2
- package/bin/warlock.js +154 -1
- package/esm/application/application.d.mts +65 -1
- package/esm/application/application.d.mts.map +1 -1
- package/esm/application/application.mjs +62 -0
- package/esm/application/application.mjs.map +1 -1
- package/esm/application/index.d.mts +1 -1
- package/esm/cli/cli-commands.manager.mjs +60 -10
- package/esm/cli/cli-commands.manager.mjs.map +1 -1
- package/esm/cli/commands/dev-server.command.mjs +1 -0
- package/esm/cli/commands/dev-server.command.mjs.map +1 -1
- package/esm/cli/commands/doctor/checks/handler-signature.check.mjs +40 -0
- package/esm/cli/commands/doctor/checks/handler-signature.check.mjs.map +1 -0
- package/esm/cli/commands/doctor/checks/index.mjs +4 -2
- package/esm/cli/commands/doctor/checks/index.mjs.map +1 -1
- package/esm/commands/cli-command.d.mts +1 -1
- package/esm/commands/cli-command.d.mts.map +1 -1
- package/esm/commands/cli-command.mjs.map +1 -1
- package/esm/commands/types.d.mts +10 -2
- package/esm/commands/types.d.mts.map +1 -1
- package/esm/config/config-handlers.d.mts +1 -2
- package/esm/config/config-handlers.d.mts.map +1 -1
- package/esm/config/config-handlers.mjs +3 -2
- package/esm/config/config-handlers.mjs.map +1 -1
- package/esm/config/locale-configuration.mjs +29 -0
- package/esm/config/locale-configuration.mjs.map +1 -0
- package/esm/connectors/assert-no-reserved-connector-names.d.mts +7 -0
- package/esm/connectors/assert-no-reserved-connector-names.d.mts.map +1 -0
- package/esm/connectors/assert-no-reserved-connector-names.mjs +44 -0
- package/esm/connectors/assert-no-reserved-connector-names.mjs.map +1 -0
- package/esm/connectors/assert-unique-connector-names.d.mts +7 -0
- package/esm/connectors/assert-unique-connector-names.d.mts.map +1 -0
- package/esm/connectors/assert-unique-connector-names.mjs +20 -0
- package/esm/connectors/assert-unique-connector-names.mjs.map +1 -0
- package/esm/connectors/connectors-manager.d.mts +38 -0
- package/esm/connectors/connectors-manager.d.mts.map +1 -1
- package/esm/connectors/connectors-manager.mjs +45 -12
- package/esm/connectors/connectors-manager.mjs.map +1 -1
- package/esm/connectors/describe-server-address.mjs +75 -0
- package/esm/connectors/describe-server-address.mjs.map +1 -0
- package/esm/connectors/http-connector.d.mts.map +1 -1
- package/esm/connectors/http-connector.mjs +6 -3
- package/esm/connectors/http-connector.mjs.map +1 -1
- package/esm/connectors/index.d.mts +4 -1
- package/esm/connectors/index.mjs +4 -1
- package/esm/connectors/register-configured-connectors.d.mts +14 -0
- package/esm/connectors/register-configured-connectors.d.mts.map +1 -0
- package/esm/connectors/register-configured-connectors.mjs +99 -0
- package/esm/connectors/register-configured-connectors.mjs.map +1 -0
- package/esm/connectors/socket-connector.d.mts.map +1 -1
- package/esm/connectors/socket-connector.mjs +4 -1
- package/esm/connectors/socket-connector.mjs.map +1 -1
- package/esm/connectors/types.d.mts +76 -1
- package/esm/connectors/types.d.mts.map +1 -1
- package/esm/connectors/types.mjs.map +1 -1
- package/esm/database/models/database-log/database-log.d.mts +50 -2
- package/esm/database/models/database-log/database-log.d.mts.map +1 -1
- package/esm/database/utils.d.mts +2 -2
- package/esm/database/utils.d.mts.map +1 -1
- package/esm/dev-server/development-server.mjs +1 -0
- package/esm/dev-server/development-server.mjs.map +1 -1
- package/esm/dev-server/layer-executor.mjs +2 -1
- package/esm/dev-server/layer-executor.mjs.map +1 -1
- package/esm/dev-server/read-config-ast.mjs +48 -0
- package/esm/dev-server/read-config-ast.mjs.map +1 -0
- package/esm/dev-server/run-typings-generation.mjs +18 -0
- package/esm/dev-server/run-typings-generation.mjs.map +1 -0
- package/esm/dev-server/type-generator.mjs +30 -39
- package/esm/dev-server/type-generator.mjs.map +1 -1
- package/esm/generations/add-command.action.mjs +169 -1
- package/esm/generations/add-command.action.mjs.map +1 -1
- package/esm/generations/stubs.mjs +93 -1
- package/esm/generations/stubs.mjs.map +1 -1
- package/esm/http/context/request-context.d.mts +14 -3
- package/esm/http/context/request-context.d.mts.map +1 -1
- package/esm/http/context/request-context.mjs +11 -1
- package/esm/http/context/request-context.mjs.map +1 -1
- package/esm/http/context/request-memo.d.mts +21 -0
- package/esm/http/context/request-memo.d.mts.map +1 -0
- package/esm/http/context/request-memo.mjs +48 -0
- package/esm/http/context/request-memo.mjs.map +1 -0
- package/esm/http/database/RequestLog.d.mts +164 -2
- package/esm/http/database/RequestLog.d.mts.map +1 -1
- package/esm/http/index.d.mts +6 -5
- package/esm/http/index.mjs +2 -1
- package/esm/http/middleware/cache-response-middleware.d.mts +2 -2
- package/esm/http/middleware/cache-response-middleware.d.mts.map +1 -1
- package/esm/http/middleware/cache-response-middleware.mjs +1 -1
- package/esm/http/middleware/cache-response-middleware.mjs.map +1 -1
- package/esm/http/middleware/concurrency-limit.middleware.mjs +1 -1
- package/esm/http/middleware/concurrency-limit.middleware.mjs.map +1 -1
- package/esm/http/middleware/idempotency.middleware.mjs +1 -1
- package/esm/http/middleware/idempotency.middleware.mjs.map +1 -1
- package/esm/http/middleware/index.d.mts +1 -1
- package/esm/http/middleware/index.mjs +1 -1
- package/esm/http/middleware/inject-request-context.d.mts +1 -8
- package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
- package/esm/http/middleware/inject-request-context.mjs +9 -22
- package/esm/http/middleware/inject-request-context.mjs.map +1 -1
- package/esm/http/middleware/ip-filter.middleware.d.mts +6 -3
- package/esm/http/middleware/ip-filter.middleware.d.mts.map +1 -1
- package/esm/http/middleware/ip-filter.middleware.mjs +7 -4
- package/esm/http/middleware/ip-filter.middleware.mjs.map +1 -1
- package/esm/http/middleware/maintenance.middleware.mjs +1 -1
- package/esm/http/middleware/maintenance.middleware.mjs.map +1 -1
- package/esm/http/middleware/max-body-size.middleware.mjs +1 -1
- package/esm/http/middleware/max-body-size.middleware.mjs.map +1 -1
- package/esm/http/middleware/rate-limit.middleware.mjs +1 -1
- package/esm/http/middleware/rate-limit.middleware.mjs.map +1 -1
- package/esm/http/middleware/utils/idempotency-key.d.mts +0 -14
- package/esm/http/middleware/utils/idempotency-key.d.mts.map +1 -1
- package/esm/http/middleware/utils/idempotency-key.mjs +19 -1
- package/esm/http/middleware/utils/idempotency-key.mjs.map +1 -1
- package/esm/http/request.d.mts +149 -44
- package/esm/http/request.d.mts.map +1 -1
- package/esm/http/request.mjs +132 -43
- package/esm/http/request.mjs.map +1 -1
- package/esm/http/response.mjs +1 -1
- package/esm/http/response.mjs.map +1 -1
- package/esm/http/server.d.mts.map +1 -1
- package/esm/http/server.mjs.map +1 -1
- package/esm/http/types.d.mts +58 -1
- package/esm/http/types.d.mts.map +1 -1
- package/esm/http/uploaded-file.d.mts +51 -19
- package/esm/http/uploaded-file.d.mts.map +1 -1
- package/esm/http/uploaded-file.mjs +36 -8
- package/esm/http/uploaded-file.mjs.map +1 -1
- package/esm/index.d.mts +17 -9
- package/esm/index.mjs +10 -4
- package/esm/production/build-contributions.mjs +115 -0
- package/esm/production/build-contributions.mjs.map +1 -0
- package/esm/production/production-builder.mjs +97 -2
- package/esm/production/production-builder.mjs.map +1 -1
- package/esm/production/resolve-build-config.d.mts +25 -0
- package/esm/production/resolve-build-config.d.mts.map +1 -0
- package/esm/production/resolve-build-config.mjs +43 -1
- package/esm/production/resolve-build-config.mjs.map +1 -1
- package/esm/repositories/adapters/cascade/cascade-query-builder.d.mts +1 -1
- package/esm/repositories/adapters/cascade/filter-applicator.mjs +20 -9
- package/esm/repositories/adapters/cascade/filter-applicator.mjs.map +1 -1
- package/esm/restful/restful.d.mts +29 -8
- package/esm/restful/restful.d.mts.map +1 -1
- package/esm/restful/restful.mjs +31 -13
- package/esm/restful/restful.mjs.map +1 -1
- package/esm/router/describe-route-for-log.mjs +29 -0
- package/esm/router/describe-route-for-log.mjs.map +1 -0
- package/esm/router/index.d.mts +3 -1
- package/esm/router/index.mjs +2 -0
- package/esm/router/log-request-lifecycle.mjs +38 -0
- package/esm/router/log-request-lifecycle.mjs.map +1 -0
- package/esm/router/positional-handler-diagnostics.d.mts +90 -0
- package/esm/router/positional-handler-diagnostics.d.mts.map +1 -0
- package/esm/router/positional-handler-diagnostics.mjs +247 -0
- package/esm/router/positional-handler-diagnostics.mjs.map +1 -0
- package/esm/router/route-registry.d.mts +34 -0
- package/esm/router/route-registry.d.mts.map +1 -0
- package/esm/router/router.d.mts.map +1 -1
- package/esm/router/router.mjs +87 -63
- package/esm/router/router.mjs.map +1 -1
- package/esm/router/types.d.mts +23 -5
- package/esm/router/types.d.mts.map +1 -1
- package/esm/storage/storage.d.mts.map +1 -1
- package/esm/storage/storage.mjs +1 -1
- package/esm/storage/storage.mjs.map +1 -1
- package/esm/storage/utils/safe-fetch.d.mts +11 -1
- package/esm/storage/utils/safe-fetch.d.mts.map +1 -1
- package/esm/storage/utils/safe-fetch.mjs +51 -4
- package/esm/storage/utils/safe-fetch.mjs.map +1 -1
- package/esm/tests/vitest-setup.mjs +1 -1
- package/esm/utils/load-environment.mjs +33 -2
- package/esm/utils/load-environment.mjs.map +1 -1
- package/esm/utils/types.d.mts +4 -0
- package/esm/utils/types.d.mts.map +1 -1
- package/esm/validation/validateAll.mjs +4 -1
- package/esm/validation/validateAll.mjs.map +1 -1
- package/esm/validation/validators/file-validator.d.mts +25 -1
- package/esm/validation/validators/file-validator.d.mts.map +1 -1
- package/esm/warlock-config/index.mjs +1 -1
- package/esm/warlock-config/normalize-build-config.mjs.map +1 -1
- package/esm/warlock-config/types.d.mts +36 -2
- package/esm/warlock-config/types.d.mts.map +1 -1
- package/llms-full.txt +179 -23
- package/llms.txt +1 -1
- package/package.json +23 -20
- package/skills/request-memo/SKILL.md +142 -0
- package/skills/use-middleware/SKILL.md +12 -1
- package/skills/use-request-locals/SKILL.md +144 -0
package/esm/http/request.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request.mjs","names":[],"sources":["../../../../../../../core/src/http/request.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport events from \"@mongez/events\";\nimport { trans, transFrom } from \"@mongez/localization\";\nimport { Random, except, get, only, rtrim, set, unset } from \"@mongez/reinforcements\";\nimport { isEmpty } from \"@mongez/supportive-is\";\nimport type { LogLevel } from \"@warlock.js/logger\";\nimport { log } from \"@warlock.js/logger\";\nimport { BaseValidator, v } from \"@warlock.js/seal\";\nimport type { FastifyRequest } from \"fastify\";\nimport { type IncomingHttpHeaders } from \"node:http2\";\nimport { config } from \"../config/config-getter\";\nimport type { Middleware, Route } from \"../router\";\nimport { validateAll } from \"../validation/validateAll\";\nimport { createRequestStore } from \"./middleware/inject-request-context\";\nimport { Response } from \"./response\";\nimport type { RequestEvent } from \"./types\";\nimport { UploadedFile } from \"./uploaded-file\";\n\ntype StandardHeaders = {\n // copy every declared property from http.IncomingHttpHeaders\n // but remove index signatures\n [K in keyof IncomingHttpHeaders as string extends K\n ? never\n : number extends K\n ? never\n : K]: IncomingHttpHeaders[K];\n};\n\ntype HeaderKeys = keyof StandardHeaders;\n\nexport class Request<RequestValidation = any> {\n /**\n * Underlying Fastify request — a public escape hatch to capabilities the\n * framework's high-level helpers don't yet cover.\n *\n * **Prefer framework methods first**: `request.input()`, `request.header()`,\n * `request.body`, `request.query`, `request.params`, `request.file()`,\n * `request.user`, `request.detectIp()`, etc. They handle locale, parsing,\n * trust-proxy, and validation pipeline integration correctly.\n *\n * **Reach for `baseRequest` only** when the framework genuinely lacks a\n * helper for what you need — and when you do, file an issue so we can add\n * it. The escape hatch is the release valve that lets consumers move\n * faster than the framework, but every long-term reach here is a missing\n * helper waiting to be added.\n */\n public baseRequest!: FastifyRequest;\n\n /**\n * Response Object\n */\n public response!: Response;\n\n /**\n * Route Object\n */\n public route!: Route;\n\n /**\n * Parsed Request Payload\n */\n protected payload: any = {};\n\n /**\n * Decoded access token payload (set by auth middleware)\n */\n public decodedAccessToken?: any;\n\n /**\n * Current request instance\n */\n public static current: Request;\n\n /**\n * Translation method\n * Type of it is the same as the type of trans function\n */\n public trans: ReturnType<typeof trans> = trans;\n\n /**\n * Alias to trans method\n */\n public t: ReturnType<typeof trans> = trans;\n\n /**\n * Dynamic properties index signature\n *\n * This allows attaching custom properties to the request instance,\n * commonly used during validation middleware to attach fetched models.\n *\n * @example\n * // In validation middleware:\n * const post = await Post.find(request.int(\"id\"));\n * if (!post) return response.notFound();\n * request.post = post; // Attach the model to the request\n *\n * // In route handler:\n * const post = request.post;\n * // Work with the pre-fetched model\n */\n [key: string]: any;\n\n /**\n * Locale code\n */\n protected _locale = \"\";\n\n /**\n * Validated data\n */\n protected validatedData?: RequestValidation;\n\n /**\n * Request id\n */\n public id = Random.string(32);\n\n /**\n * Start Time\n */\n public startTime = Date.now();\n\n /**\n * End Time\n */\n public endTime?: undefined | number;\n\n /**\n * Set request handler\n */\n public setRequest(request: FastifyRequest) {\n this.baseRequest = request;\n\n this.resolveRequestId();\n\n this.parsePayload();\n\n const localeCode = this.getLocaleCode();\n\n this.trans = this.t = transFrom.bind(null, localeCode);\n\n return this;\n }\n\n /**\n * Inherit `X-Request-Id` from the incoming request, fall back to a custom\n * generator, then to the field-init default (`Random.string(32)`).\n *\n * Inherited values are validated (length cap + printable-ASCII) to prevent\n * log-injection from a malicious client. Disable the whole behavior by\n * setting `http.requestId.enabled = false` — in which case the field-init\n * default is used regardless of any incoming header.\n */\n protected resolveRequestId() {\n const requestIdConfig = config.key(\"http.requestId\") || {};\n\n if (requestIdConfig.enabled === false) return;\n\n const headerName = (requestIdConfig.header || \"x-request-id\").toLowerCase();\n const incoming = this.baseRequest.headers[headerName];\n\n if (Request.isValidRequestId(incoming)) {\n this.id = incoming;\n\n return;\n }\n\n if (typeof requestIdConfig.generator === \"function\") {\n this.id = requestIdConfig.generator();\n }\n }\n\n /**\n * Validate a candidate request-id value. Accepts non-empty printable ASCII\n * up to 128 characters — tight enough to reject newline / control-character\n * log-injection, loose enough to accept UUIDs, ULIDs, snowflakes, etc.\n */\n protected static isValidRequestId(value: unknown): value is string {\n return (\n typeof value === \"string\" &&\n value.length > 0 &&\n value.length <= 128 &&\n /^[\\x21-\\x7e]+$/.test(value)\n );\n }\n\n /**\n * Translate from the given locale code\n */\n public transFrom(localeCode: string, keyword: string, placeholders?: any) {\n return transFrom(localeCode, keyword, placeholders);\n }\n\n /**\n * Get current locale code\n */\n public get locale() {\n if (this._locale) return this._locale;\n\n return this.header(\"translation-locale-code\") || this.localized;\n }\n\n /**\n * Set locale code\n */\n public set locale(localeCode: string) {\n this._locale = localeCode;\n }\n\n /**\n * Get locale code that will be used for translation\n */\n public get localized() {\n if (this._locale) return this._locale;\n\n return (this._locale = this.header(\"locale\") || this.query[\"locale\"]);\n }\n\n /**\n * Set locale code\n */\n public setLocaleCode(localeCode: string) {\n this._locale = localeCode;\n\n return this;\n }\n\n /**\n * Get current locale code or return default locale code\n */\n public getLocaleCode(defaultLocaleCode: string = config.key(\"app.localeCode\") || \"en\") {\n return this.locale || defaultLocaleCode;\n }\n\n /**\n * Get http protocol\n */\n public get protocol() {\n return this.baseRequest.protocol;\n }\n\n /**\n * Validate the given validation schema\n */\n public async validate(validation: BaseValidator, selectedInputs?: string[]) {\n return await v.validate(validation, selectedInputs ? this.only(selectedInputs) : this.all());\n }\n\n /**\n * Clear current user\n */\n public clearCurrentUser() {\n this.user = undefined;\n }\n\n /**\n * Get value of the given header\n */\n public header<TCustomHeader extends string = HeaderKeys>(\n name: TCustomHeader | HeaderKeys,\n defaultValue: any = null,\n ) {\n return this.baseRequest.headers[name.toLocaleLowerCase()] ?? defaultValue;\n }\n\n /**\n * Get all cookies from the current request\n */\n public get cookies(): Record<string, string | undefined> {\n return this.baseRequest.cookies || {};\n }\n\n /**\n * Get a particular cookie value or fallback to default\n */\n public cookie(name: string, defaultValue?: any): string | any {\n const value = this.cookies[name] ?? defaultValue;\n\n try {\n return JSON.parse(value);\n } catch (error) {\n return value;\n }\n }\n\n /**\n * Determine if the request has the specified cookie\n */\n public hasCookie(name: string): boolean {\n return this.cookies[name] !== undefined;\n }\n\n /**\n * Get the current request domain\n */\n public get domain() {\n return this.baseRequest.hostname.replace(/^www\\./, \"\");\n }\n\n /**\n * Get hostname\n */\n public get hostname() {\n return this.domain;\n }\n\n /**\n * Get request origin\n */\n public get origin() {\n return this.baseRequest.headers.origin as string;\n }\n\n /**\n * Get the domain of the origin\n */\n public get originDomain() {\n const domain = this.origin ? new URL(this.origin).hostname : null;\n\n if (domain?.startsWith(\"www.\")) {\n return domain.replace(/^www\\./, \"\");\n }\n\n return domain;\n }\n\n /**\n * Get authorization header value\n */\n public get authorizationValue(): string {\n const authorization = this.header(\"authorization\");\n\n if (!authorization) return \"\";\n\n const [type, value] = authorization.split(\" \");\n\n if (![\"bearer\", \"key\"].includes(type.toLowerCase())) return \"\";\n\n return value || \"\";\n }\n\n /**\n * Get access token from Authorization header\n *\n * If the Authorization header does not start with `Bearer` value then return null\n */\n public get accessToken(): string | undefined {\n const authorization = this.header(\"authorization\");\n\n if (!authorization) return;\n\n const [type, value] = authorization.split(\" \");\n\n if (type.toLowerCase() !== \"bearer\") return;\n\n return value;\n }\n\n /**\n * Get the authorization header\n */\n public get authorization() {\n return this.header(\"authorization\");\n }\n\n /**\n * Get current request method\n */\n public get method(): string {\n return this.baseRequest.method;\n }\n\n /**\n * Parse the payload and merge it from the request body, params and query string\n */\n protected parsePayload() {\n this.payload.body = this.parseBody(this.baseRequest.body);\n\n this.payload.query = this.parseBody(this.baseRequest.query);\n this.payload.params = { ...(this.baseRequest.params || {}) };\n this.payload.all = {\n ...this.payload.body,\n ...this.payload.query,\n ...this.payload.params,\n };\n }\n\n /**\n * Parse body payload\n */\n protected parseBody(data: any) {\n try {\n if (!data) return {};\n\n const body: any = {};\n\n const arrayOfObjectValues: any = {};\n\n for (let key in data) {\n const value = data[key];\n\n let isArrayKey = false;\n\n if (key.endsWith(\"[]\")) {\n isArrayKey = true;\n }\n\n key = rtrim(key, \"[]\");\n\n // check if the key is has a square brackets, then convert it into object\n // i.e user[email] => user: {email: \"value\"}\n // also check if its an array of objects\n\n if (key.includes(\"[\")) {\n // check if its an array of objects\n if (key.includes(\"][\")) {\n const keyParts = key.split(\"[\");\n\n const keyName = keyParts[0];\n if (!arrayOfObjectValues[keyName]) {\n arrayOfObjectValues[keyName] = [];\n }\n\n const keyNameParts = keyParts[1].split(\"]\");\n\n const index = Number(keyNameParts[0]);\n\n if (!arrayOfObjectValues[keyName][index]) {\n arrayOfObjectValues[keyName][index] = {};\n }\n\n // now get the key after the index\n const keyNameParts2 = keyParts[2].split(\"]\");\n const keyName2 = keyNameParts2[0];\n\n arrayOfObjectValues[keyName][index][keyName2] = this.parseValue(value);\n\n continue;\n }\n\n const keyParts = key.split(\"[\");\n const keyName = keyParts[0];\n const keyNameParts = keyParts[1].split(\"]\");\n\n set(\n body,\n keyName + \".\" + keyNameParts[0],\n Array.isArray(value) ? value.map(this.parseValue.bind(this)) : this.parseValue(value),\n );\n\n continue;\n }\n\n if (Array.isArray(value)) {\n set(body, key, value.map(this.parseValue.bind(this)));\n } else if (isArrayKey) {\n if (body[key]) {\n body[key].push(this.parseValue(value));\n } else {\n body[key] = [this.parseValue(value)];\n\n continue;\n }\n } else {\n set(body, key, this.parseValue(value));\n }\n }\n\n // now merge the array of objects into the body\n for (const key in arrayOfObjectValues) {\n body[key] = arrayOfObjectValues[key];\n }\n\n return body;\n } catch (error) {\n console.log(error);\n this.log(error, \"error\");\n }\n }\n\n /**\n * Parse the given data\n */\n protected parseValue(data: any) {\n // data.value appears only in the multipart form data\n // if it json, then just return the data\n if (data?.file) return new UploadedFile(data);\n if (data?.value !== undefined && data?.fields && data?.type) {\n data = data.value;\n }\n\n if (data === \"false\") return false;\n\n if (data === \"true\") return true;\n\n if (data === \"null\") return null;\n\n if (typeof data === \"string\") return data.trim();\n\n return data;\n }\n\n /**\n * Set route handler\n */\n public setRoute(route: Route) {\n this.route = route;\n\n // pass the route to the response object\n this.response.setRoute(route);\n\n return this;\n }\n\n /**\n * Trigger an http event\n */\n public trigger(eventName: RequestEvent, ...args: any[]) {\n return events.trigger(`request.${eventName}`, ...args, this);\n }\n\n /**\n * Listen to the given event\n */\n public on(eventName: RequestEvent, callback: any) {\n return this.subscribe(eventName, callback);\n }\n\n /**\n * Make a log message\n */\n public log(message: any, level: LogLevel = \"info\") {\n if (!config.key(\"http.log\")) return;\n\n log.log({\n module: \"request\",\n action: this.route.method + \" \" + this.route.path.replace(\"/*\", \"\") + `:${this.id}`,\n message,\n type: level,\n context: {\n request: this,\n },\n });\n }\n\n /**\n * Get current request path\n */\n public get path() {\n return this.baseRequest.url;\n }\n\n /**\n * {@alias}\n */\n public get url() {\n return this.baseRequest.url;\n }\n\n /**\n * Get full url\n */\n public get fullUrl() {\n return this.protocol + \"://\" + this.hostname + this.path;\n }\n\n /**\n * Drive the middleware chain for the current route, then defer to the\n * controller. Returns the first response value any middleware short-circuits\n * with, or `undefined` to continue into validation + handler.\n *\n * @internal Framework orchestration — do not call from app code. Will move\n * to a dedicated controller dispatcher in a future refactor.\n */\n public async runMiddleware() {\n // measure request time\n // check for middleware first\n const middlewareOutput = await this.executeMiddleware();\n\n if (middlewareOutput !== undefined) {\n // 👇🏻 make sure first its not a response instance\n if (middlewareOutput instanceof Response) return middlewareOutput;\n // 👇🏻 send the response\n return this.response.send(middlewareOutput);\n }\n\n const handler = this.route.handler;\n\n if (!handler.validation) return;\n\n // 👇🏻 check for validation using validateAll helper function\n const validationOutput = await validateAll(handler.validation, this, this.response);\n\n return validationOutput;\n }\n\n /**\n * Return the request handler attached to the current route.\n *\n * @internal Framework orchestration — do not call from app code.\n */\n public getHandler() {\n return this.route.handler;\n }\n\n /**\n * Get inputs that has been validated only\n * You can also pass an array of inputs to get only the validated inputs\n */\n public validated<Output = RequestValidation>(inputs?: (keyof Output | (string & {}))[]): Output {\n if (this.validatedData) {\n return inputs\n ? only(this.validatedData as Output, inputs as string[])\n : (this.validatedData as Output);\n }\n\n return {} as Output;\n }\n\n /**\n * Get inputs that has been validated except the given inputs\n */\n public validatedExcept(...inputs: string[]): RequestValidation {\n return except(this.validated(), inputs);\n }\n\n /**\n * Set validated data\n */\n public setValidatedData(data: RequestValidation) {\n this.validatedData = data;\n }\n\n /**\n * Top-level entry into the request lifecycle — opens the context store,\n * runs middleware, drives the handler, handles errors.\n *\n * @internal Framework orchestration — do not call from app code. Wired\n * from the Fastify route handler in `router.scan()`.\n */\n public async execute() {\n try {\n // call executingAction event\n\n this.log(\"Executing the request\");\n\n return await createRequestStore(this, this.response);\n } catch (error) {\n this.log(error, \"error\");\n\n throw error;\n }\n }\n\n /**\n * Iterate the collected middlewares in order; return the first short-circuit\n * value or `undefined` when every middleware passes through.\n *\n * @internal Framework orchestration — do not call from app code.\n */\n protected async executeMiddleware() {\n // collect all middlewares for current route\n const middlewares = this.collectMiddlewares();\n\n // check if there are no middlewares, then return\n if (middlewares.length === 0) return;\n\n this.log(\"About to execute request middlewares\");\n\n // trigger the executingMiddleware event\n this.trigger(\"executingMiddleware\", middlewares, this.route);\n\n for (const middleware of middlewares) {\n this.log(\"Executing middleware \" + colors.yellowBright(middleware.name));\n const output = await middleware(this, this.response);\n this.log(\"Executed middleware \" + colors.yellowBright(middleware.name), \"success\");\n\n if (output !== undefined) {\n this.log(\n colors.yellow(\"request intercepted by middleware \") + colors.cyanBright(middleware.name),\n \"warn\",\n );\n\n this.trigger(\"executedMiddleware\");\n\n this.log(\"Request middlewares executed\", \"success\");\n\n return output;\n }\n }\n\n this.log(\"Request middlewares executed\", \"success\");\n\n // trigger the executedMiddleware event\n this.trigger(\"executedMiddleware\", middlewares, this.route);\n }\n\n /**\n * Gather the middleware list for the current route — today just the\n * route-level array; future extraction may merge group + app-wide layers.\n *\n * @internal Framework orchestration — do not call from app code.\n */\n protected collectMiddlewares(): Middleware[] {\n const middlewaresList: Middleware[] = [];\n\n // collect route middlewares\n if (this.route.middleware) {\n middlewaresList.push(...this.route.middleware);\n }\n\n return middlewaresList;\n }\n\n /**\n * Get request input value from query string, params or body\n */\n public input(key: string, defaultValue?: any) {\n return get(this.payload.all, key, defaultValue);\n }\n\n /**\n * Get email input value, this will lowercase the value\n */\n public email(key: string = \"email\", defaultValue: string = \"\"): string {\n return this.input(key, defaultValue)?.toLowerCase() || defaultValue;\n }\n\n /**\n * @alias input\n */\n public get(key: string, defaultValue?: any) {\n return this.input(key, defaultValue);\n }\n\n /**\n * Determine if request has input value\n */\n public has(key: string) {\n return get(this.payload.all, key, undefined) !== undefined;\n }\n\n /**\n * Set request input value\n */\n public set(key: string, value: any) {\n set(this.payload.all, key, value);\n\n return this;\n }\n\n /**\n * Set the given value if the request does not have the input\n */\n public setDefault(key: string, value: any) {\n if (this.has(key)) return this;\n\n set(this.payload.all, key, value);\n\n return this;\n }\n\n /**\n * Unset request payload keys\n */\n public unset(...keys: string[]) {\n this.payload.all = unset(this.payload.all, keys);\n\n return this;\n }\n\n /**\n * Get request body\n */\n public get body() {\n return this.payload.body;\n }\n\n /**\n * Set request body value\n */\n public setBody(key: string, value: any) {\n set(this.payload.body, key, value);\n\n return this;\n }\n\n /**\n * Get body inputs except files\n */\n public get bodyInputs() {\n const inputs = this.payload.body;\n\n const bodyInputs: any = {};\n\n for (const key in inputs) {\n const value = inputs[key];\n\n if (value.file && value.fieldname) continue;\n\n bodyInputs[key] = value;\n }\n\n return bodyInputs;\n }\n\n /**\n * Get request file in UploadedFile instance\n */\n public file(key: string): UploadedFile | undefined {\n const file = this.input(key);\n\n return file;\n }\n\n /**\n * Get uploaded files from the request for the given name\n * If the given name is not present in the request, return an empty array\n */\n public files(name: string): UploadedFile[] {\n return this.input(name) || [];\n }\n\n /**\n * Get request params\n */\n public get params() {\n return this.payload.params;\n }\n\n /**\n * Set request params value\n */\n public setParam(key: string, value: any) {\n set(this.payload.params, key, value);\n\n return this;\n }\n\n /**\n * Get request query\n */\n public get query() {\n return this.payload.query;\n }\n\n /**\n * Set request query value\n */\n public setQuery(key: string, value: any) {\n set(this.payload.query, key, value);\n\n return this;\n }\n\n /**\n * Get all inputs\n */\n public all() {\n return this.payload.all;\n }\n\n /**\n * Get all inputs except params\n */\n public allExceptParams() {\n return {\n ...this.payload.query,\n ...this.payload.body,\n };\n }\n\n /**\n * Get all heavy inputs except params\n */\n public heavyExceptParams() {\n const inputs = this.allExceptParams();\n\n const heavyInputs: any = {};\n\n for (const key in inputs) {\n const value = inputs[key];\n\n if (isEmpty(value) && value !== null) continue;\n\n heavyInputs[key] = value;\n }\n\n return heavyInputs;\n }\n\n /**\n * Get only heavy inputs, the input with a value\n */\n public heavy() {\n const inputs = this.all();\n\n const heavyInputs: any = {};\n\n for (const key in inputs) {\n const value = inputs[key];\n\n if (isEmpty(value) && value !== null) continue;\n\n heavyInputs[key] = value;\n }\n\n return heavyInputs;\n }\n\n /**\n * Get only the given keys from the request data\n */\n public only(keys: string[]) {\n return only(this.all(), keys);\n }\n\n /**\n * Pluck the given keys from the request data\n */\n public pluck(keys: string[]) {\n const data = this.only(keys);\n\n this.unset(...keys);\n\n return data;\n }\n\n /**\n * Get all request inputs except the given keys\n */\n public except(keys: string[]) {\n return except(this.all(), keys);\n }\n\n /**\n * Get boolean input value\n */\n public bool(key: string, defaultValue = false) {\n const value = this.input(key, defaultValue);\n\n if (value === \"true\") {\n return true;\n }\n\n if (value === \"false\") {\n return false;\n }\n\n if (value === 0) {\n return false;\n }\n\n return Boolean(value);\n }\n\n /**\n * Get integer input value\n */\n public int(key: string, defaultValue: number = 0): number | undefined {\n const value = this.input(key, defaultValue);\n\n if (!value && value !== 0) return undefined;\n\n return parseInt(value);\n }\n\n /**\n * Shorthand getter to get id param\n */\n public get idParam() {\n return this.int(\"id\");\n }\n\n /**\n * Get string input value\n */\n public string(key: string, defaultValue: string = \"\"): string {\n const value = this.input(key, defaultValue);\n\n return String(value);\n }\n\n /**\n * Get float input value\n */\n public float(key: string, defaultValue: number = 0): number {\n const value = this.input(key, defaultValue);\n\n return parseFloat(value) || 0;\n }\n\n /**\n * Get number input value\n */\n public number(key: string, defaultValue: number = 0): number {\n const value = Number(this.input(key, defaultValue));\n\n return isNaN(value) ? defaultValue : value;\n }\n\n /**\n * Immediate-peer IP as Fastify reports it — the address that connected to\n * the server socket, with `trustProxy` resolution applied. Use this when\n * you specifically need the peer address (rate-limit-by-direct-connection,\n * health-check origin verification).\n *\n * **For most use cases prefer `request.detectIp()`** — behind any proxy\n * (load balancer, CDN, sidecar) `ip` reports the proxy, not the real client.\n */\n public get ip() {\n return this.baseRequest.ip;\n }\n\n /**\n * Best-effort real client IP — checks `X-Real-IP` and `X-Forwarded-For`\n * headers first, falls back to `baseRequest.ip` when neither is present.\n *\n * For a multi-hop `X-Forwarded-For` (`client, proxy1, proxy2`) only the\n * FIRST entry is returned (the original client), comma-split and trimmed.\n * Without this, downstream scoping (ip-filter, rate-limit, idempotency)\n * would key off the whole header string and break behind chained proxies.\n *\n * **Prefer this over `request.ip` for any caller behind a proxy** (load\n * balancer, CDN, reverse proxy, k8s ingress). Only trust the result as\n * far as you trust the upstream proxy chain — `X-Forwarded-For` is\n * client-settable, so the leftmost entry is spoofable by the immediate\n * client when the request did NOT pass through a trusted edge that\n * appends/overwrites the header. Verify the request came through your\n * trusted edge before treating the value as authoritative.\n */\n public detectIp() {\n // as the server maybe used behind a proxy\n // then we need to check first if there is a forwarded ip\n // check for the real-ip header\n\n const realIp = this.header(\"x-real-ip\");\n\n if (realIp) return realIp;\n\n const forwardedIp = this.header(\"x-forwarded-for\");\n\n if (forwardedIp) {\n // `X-Forwarded-For` may carry the full proxy chain (\"client, proxy1, ...\").\n // The original client is the leftmost entry; split + trim so multi-hop\n // values don't leak the whole header string into IP-scoped logic.\n const firstHop = String(forwardedIp).split(\",\")[0].trim();\n\n if (firstHop) return firstHop;\n }\n\n return this.baseRequest.ip;\n }\n\n /**\n * An alias to detectIp\n */\n public get realIp() {\n return this.detectIp();\n }\n\n /**\n * Get request ips\n */\n public get ips() {\n return this.baseRequest.ips;\n }\n\n /**\n * Get request referer\n */\n public get referer() {\n return this.baseRequest.headers.referer;\n }\n\n /**\n * Get user agent\n */\n public get userAgent() {\n return this.baseRequest.headers[\"user-agent\"];\n }\n\n /**\n * Get request headers\n */\n public get headers(): typeof this.baseRequest.headers {\n return this.baseRequest.headers;\n }\n\n /**\n * Set the given header\n */\n public setHeader(key: HeaderKeys, value: string) {\n this.baseRequest.headers[key.toLowerCase()] = value;\n\n return this;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"request.mjs","names":[],"sources":["../../../../../../../core/src/http/request.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport events from \"@mongez/events\";\nimport { trans, transFrom } from \"@mongez/localization\";\nimport { Random, except, get, only, rtrim, set, unset } from \"@mongez/reinforcements\";\nimport { isEmpty } from \"@mongez/supportive-is\";\nimport type { LogLevel } from \"@warlock.js/logger\";\nimport { log } from \"@warlock.js/logger\";\nimport { BaseValidator, v } from \"@warlock.js/seal\";\nimport type { FastifyRequest } from \"fastify\";\nimport { randomBytes } from \"node:crypto\";\nimport { type IncomingHttpHeaders } from \"node:http2\";\nimport { config } from \"../config/config-getter\";\nimport { resolveLocaleConfiguration } from \"../config/locale-configuration\";\nimport type { Middleware, Route } from \"../router\";\nimport { validateAll } from \"../validation/validateAll\";\nimport { createRequestStore } from \"./middleware/inject-request-context\";\nimport { Response } from \"./response\";\nimport type { RequestEvent, RequestLocals, RequestUser } from \"./types\";\nimport { UploadedFile } from \"./uploaded-file\";\n\ntype StandardHeaders = {\n // copy every declared property from http.IncomingHttpHeaders\n // but remove index signatures\n [K in keyof IncomingHttpHeaders as string extends K\n ? never\n : number extends K\n ? never\n : K]: IncomingHttpHeaders[K];\n};\n\ntype HeaderKeys = keyof StandardHeaders;\n\nexport class Request<RequestValidation = any> {\n /**\n * Underlying Fastify request — a public escape hatch to capabilities the\n * framework's high-level helpers don't yet cover.\n *\n * **Prefer framework methods first**: `request.input()`, `request.header()`,\n * `request.body`, `request.query`, `request.params`, `request.file()`,\n * `request.user`, `request.detectIp()`, etc. They handle locale, parsing,\n * trust-proxy, and validation pipeline integration correctly.\n *\n * **Reach for `baseRequest` only** when the framework genuinely lacks a\n * helper for what you need — and when you do, file an issue so we can add\n * it. The escape hatch is the release valve that lets consumers move\n * faster than the framework, but every long-term reach here is a missing\n * helper waiting to be added.\n */\n public baseRequest!: FastifyRequest;\n\n /**\n * Response Object\n */\n public response!: Response;\n\n /**\n * Route Object\n */\n public route!: Route;\n\n /**\n * Parsed Request Payload\n */\n protected payload: any = {};\n\n /**\n * Decoded access token payload (set by auth middleware)\n */\n public decodedAccessToken?: any;\n\n /**\n * The authenticated user attached to this request, if any.\n *\n * `RequestUser` is empty by default, so ANY shape is assignable here at the\n * declaration site — the app or auth package declares its real fields via\n * module augmentation:\n *\n * ```typescript\n * declare module \"@warlock.js/core\" {\n * interface RequestUser {\n * id: string | number;\n * }\n * }\n * ```\n *\n * Replaces the v4 `GuardedRequest` convention\n * (`create-warlock/.../guarded.request.ts` — `Request<T> & { user: User }`,\n * an intersection type hand-declared per app) with a property core itself\n * declares and types. `clearCurrentUser()` below is the one place core\n * writes it directly; auth middleware is expected to do the same after a\n * successful token resolution.\n */\n public user?: RequestUser;\n\n /**\n * Private, server-only, per-request data bag.\n *\n * Distinct from the input payload (`body` / `query` / `params` / `all()`):\n * a write here never surfaces in `request.all()`, `request.validated()`, or\n * `request.input()`. That is the trap `request.set()` sets for private data\n * — it writes into the payload `all` bag, so anything stored there leaks\n * into every input accessor and, from there, into the client-facing\n * payload. `locals` is the correct home for private per-request app data\n * (a resolved session, a fetched-once model) that must never be mistaken\n * for client input.\n *\n * Augmentable via module augmentation, in the module that OWNS the key:\n *\n * ```typescript\n * declare module \"@warlock.js/core\" {\n * interface RequestLocals {\n * session?: { token: string };\n * }\n * }\n * ```\n *\n * A plain class-field initializer is sufficient for \"fresh per request\":\n * `router.ts:925` constructs `new Request()` for every incoming request —\n * `Request` instances are not pooled or reused across requests — so this\n * initializer runs exactly once per request and no value can leak in from\n * a prior one.\n */\n public locals: RequestLocals = {};\n\n /**\n * Backing field for the lazily-generated CSP nonce. Left `undefined` until\n * the first `request.nonce` read; see the `nonce` getter below.\n */\n protected _nonce?: string;\n\n /**\n * Per-request Content-Security-Policy nonce — a fresh, unguessable value\n * the web layer hands to `<Scripts nonce={...} />` (the inline payload\n * script) and to the `Content-Security-Policy` header, so a strict\n * `script-src 'nonce-...'` allows only the script this request actually\n * rendered.\n *\n * Generated LAZILY on first access, not eagerly in `setRequest()`: most\n * requests (API routes, anything that isn't rendering HTML) never read it,\n * and spending a `randomBytes` call on every single request for a value\n * most of them discard is wasted entropy draw + CPU. Once generated it is\n * cached in `_nonce`, so every subsequent read within the SAME request\n * returns the identical value — required, since the header and the inline\n * `<script>` tag must agree on one nonce. `_nonce` is a plain field on a\n * per-request `Request` instance (see `locals` above — `router.ts:925`,\n * no pooling), so the cache can never leak into the next request; a fresh\n * `Request` means a fresh, unset `_nonce`.\n *\n * 16 random bytes, base64-encoded — the size the CSP Level 3 spec's own\n * examples use, and far more entropy than an attacker could feasibly guess\n * to defeat the policy.\n */\n public get nonce(): string {\n if (!this._nonce) {\n this._nonce = randomBytes(16).toString(\"base64\");\n }\n\n return this._nonce;\n }\n\n /**\n * Current request instance\n */\n public static current: Request;\n\n /**\n * Translation method\n * Type of it is the same as the type of trans function\n */\n public trans: ReturnType<typeof trans> = trans;\n\n /**\n * Alias to trans method\n */\n public t: ReturnType<typeof trans> = trans;\n\n /*\n * v5 removed the `[key: string]: any` index signature (eed20184). Attaching\n * arbitrary properties compiled silently and hid real bugs behind `any`.\n * The sanctioned extension paths are:\n * - `request.locals` (augment `RequestLocals` via module augmentation) for\n * per-request attached data, e.g. models fetched in validation middleware.\n * - `requestMemo(key, fn)` for per-request memoized computation.\n * - Module augmentation of the `Request` class itself for new typed members.\n */\n\n /**\n * Locale code\n */\n protected _locale = \"\";\n\n /**\n * Validated data\n */\n protected validatedData?: RequestValidation;\n\n /**\n * Request id\n */\n public id = Random.string(32);\n\n /**\n * Start Time\n */\n public startTime = Date.now();\n\n /**\n * End Time\n */\n public endTime?: undefined | number;\n\n /**\n * Set request handler\n */\n public setRequest(request: FastifyRequest) {\n this.baseRequest = request;\n\n this.resolveRequestId();\n\n this.parsePayload();\n\n // Resolve the locale at CALL time, never at bind time. `setRequest` runs\n // before routing, so a locale set later (path locale, `setLocaleCode`, the\n // web layer's C3 derivation) must steer translations too — the old\n // `transFrom.bind(null, localeCode)` snapshot made `request.locale` and\n // `request.trans()` silently disagree for the rest of the request.\n this.trans = this.t = (keyword: string, placeholders?: any) =>\n transFrom(this.getLocaleCode(), keyword, placeholders);\n\n return this;\n }\n\n /**\n * Inherit `X-Request-Id` from the incoming request, fall back to a custom\n * generator, then to the field-init default (`Random.string(32)`).\n *\n * Inherited values are validated (length cap + printable-ASCII) to prevent\n * log-injection from a malicious client. Disable the whole behavior by\n * setting `http.requestId.enabled = false` — in which case the field-init\n * default is used regardless of any incoming header.\n */\n protected resolveRequestId() {\n const requestIdConfig = config.key(\"http.requestId\") || {};\n\n if (requestIdConfig.enabled === false) return;\n\n const headerName = (requestIdConfig.header || \"x-request-id\").toLowerCase();\n const incoming = this.baseRequest.headers[headerName];\n\n if (Request.isValidRequestId(incoming)) {\n this.id = incoming;\n\n return;\n }\n\n if (typeof requestIdConfig.generator === \"function\") {\n this.id = requestIdConfig.generator();\n }\n }\n\n /**\n * Validate a candidate request-id value. Accepts non-empty printable ASCII\n * up to 128 characters — tight enough to reject newline / control-character\n * log-injection, loose enough to accept UUIDs, ULIDs, snowflakes, etc.\n */\n protected static isValidRequestId(value: unknown): value is string {\n return (\n typeof value === \"string\" &&\n value.length > 0 &&\n value.length <= 128 &&\n /^[\\x21-\\x7e]+$/.test(value)\n );\n }\n\n /**\n * Translate from the given locale code\n */\n public transFrom(localeCode: string, keyword: string, placeholders?: any) {\n return transFrom(localeCode, keyword, placeholders);\n }\n\n /**\n * Cache one supported locale without coercing request-controlled input.\n *\n * The default is the answer for a client that asked for NOTHING. A client\n * that did ask is only overridden when its value fails a declared\n * `app.localeCodes` allow-list; with no list declared there is nothing to\n * fail, so the requested locale passes through unchanged.\n */\n protected cacheLocale(candidate: unknown): string {\n const { defaultLocaleCode, localeCodes } = resolveLocaleConfiguration(\n config.key(\"app.localeCode\"),\n config.key(\"app.localeCodes\"),\n );\n\n const requested = typeof candidate === \"string\" && candidate.length > 0 ? candidate : undefined;\n\n this._locale =\n requested !== undefined && (localeCodes === undefined || localeCodes.includes(requested))\n ? requested\n : defaultLocaleCode;\n\n return this._locale;\n }\n\n /**\n * Resolve the first present Mode B source. Unsupported values fail closed to\n * the configured default instead of widening the application's locale set.\n */\n protected resolveLocale(): string {\n const candidate = [\n this.query[\"locale\"],\n this.cookies[\"locale\"],\n this.header(\"locale\"),\n ].find((value) => typeof value === \"string\" && value.length > 0);\n\n return this.cacheLocale(candidate);\n }\n\n /**\n * Get current locale code\n */\n public get locale(): string {\n if (this._locale) return this._locale;\n\n return this.resolveLocale();\n }\n\n /**\n * Set locale code\n */\n public set locale(localeCode: string) {\n this.cacheLocale(localeCode);\n }\n\n /**\n * Set locale code\n */\n public setLocaleCode(localeCode: string) {\n this.locale = localeCode;\n\n return this;\n }\n\n /**\n * @deprecated Use `request.locale`. This alias is removed after one version.\n * The legacy default argument is accepted for source compatibility but the\n * resolved default is owned exclusively by app configuration.\n */\n public getLocaleCode(_legacyDefaultLocaleCode?: string): string {\n return this.locale;\n }\n\n /**\n * Get http protocol\n */\n public get protocol() {\n return this.baseRequest.protocol;\n }\n\n /**\n * Validate the given validation schema\n */\n public async validate(validation: BaseValidator, selectedInputs?: string[]) {\n return await v.validate(validation, selectedInputs ? this.only(selectedInputs) : this.all());\n }\n\n /**\n * Get value of the given header\n */\n public header<TCustomHeader extends string = HeaderKeys>(\n name: TCustomHeader | HeaderKeys,\n defaultValue: any = null,\n ) {\n return this.baseRequest.headers[name.toLocaleLowerCase()] ?? defaultValue;\n }\n\n /**\n * Get all cookies from the current request\n */\n public get cookies(): Record<string, string | undefined> {\n return this.baseRequest.cookies || {};\n }\n\n /**\n * Get a particular cookie value or fallback to default\n */\n public cookie(name: string, defaultValue?: any): string | any {\n const value = this.cookies[name] ?? defaultValue;\n\n try {\n return JSON.parse(value);\n } catch (error) {\n return value;\n }\n }\n\n /**\n * Determine if the request has the specified cookie\n */\n public hasCookie(name: string): boolean {\n return this.cookies[name] !== undefined;\n }\n\n /**\n * Get the current request domain\n */\n public get domain() {\n return this.baseRequest.hostname.replace(/^www\\./, \"\");\n }\n\n /**\n * Get hostname\n */\n public get hostname() {\n return this.domain;\n }\n\n /**\n * Get request origin\n */\n public get origin() {\n return this.baseRequest.headers.origin as string;\n }\n\n /**\n * Get the domain of the origin\n */\n public get originDomain() {\n const domain = this.origin ? new URL(this.origin).hostname : null;\n\n if (domain?.startsWith(\"www.\")) {\n return domain.replace(/^www\\./, \"\");\n }\n\n return domain;\n }\n\n /**\n * Get authorization header value\n */\n public get authorizationValue(): string {\n const authorization = this.header(\"authorization\");\n\n if (!authorization) return \"\";\n\n const [type, value] = authorization.split(\" \");\n\n if (![\"bearer\", \"key\"].includes(type.toLowerCase())) return \"\";\n\n return value || \"\";\n }\n\n /**\n * Get access token from Authorization header\n *\n * If the Authorization header does not start with `Bearer` value then return null\n */\n public get accessToken(): string | undefined {\n const authorization = this.header(\"authorization\");\n\n if (!authorization) return;\n\n const [type, value] = authorization.split(\" \");\n\n if (type.toLowerCase() !== \"bearer\") return;\n\n return value;\n }\n\n /**\n * Get the authorization header\n */\n public get authorization() {\n return this.header(\"authorization\");\n }\n\n /**\n * Get current request method\n */\n public get method(): string {\n return this.baseRequest.method;\n }\n\n /**\n * Parse the payload and merge it from the request body, params and query string\n */\n /**\n * Turn a bracket-notation key into the dotted path `set()` expects.\n *\n * `a[b][c]` -> `a.b.c`. Used only for NON-numeric nesting; numeric indices\n * keep the array-of-objects path in {@link parseBody}, which builds real\n * arrays rather than objects with numeric keys.\n */\n protected bracketKeyToPath(key: string): string {\n return key\n .replace(/\\]\\[/g, \".\")\n .replace(/\\[/g, \".\")\n .replace(/\\]/g, \"\");\n }\n\n /**\n * Apply the `key[]` array marker to a parsed value.\n *\n * The subtlety this exists to remove: a key declared `[]` should ALWAYS be an\n * array, but the underlying query/body parser only hands us one when the\n * caller sent the key more than once. Deciding the TYPE from the number of\n * occurrences means one selected filter is a string and two are an array —\n * a shape that changes under the user's hands.\n */\n protected arrayValueFor(value: any, isArrayKey: boolean, parse: (value: any) => any) {\n if (Array.isArray(value)) return value.map(parse);\n\n return isArrayKey ? [parse(value)] : parse(value);\n }\n\n protected parsePayload() {\n this.payload.body = this.parseBody(this.baseRequest.body);\n\n this.payload.query = this.parseBody(this.baseRequest.query);\n this.payload.params = { ...(this.baseRequest.params || {}) };\n this.payload.all = {\n ...this.payload.body,\n ...this.payload.query,\n ...this.payload.params,\n };\n }\n\n /**\n * Parse body payload\n */\n protected parseBody(data: any) {\n try {\n if (!data) return {};\n\n const body: any = {};\n\n const arrayOfObjectValues: any = {};\n\n for (let key in data) {\n const value = data[key];\n\n let isArrayKey = false;\n\n if (key.endsWith(\"[]\")) {\n isArrayKey = true;\n }\n\n key = rtrim(key, \"[]\");\n\n // check if the key is has a square brackets, then convert it into object\n // i.e user[email] => user: {email: \"value\"}\n // also check if its an array of objects\n\n if (key.includes(\"[\")) {\n // check if its an array of objects\n if (key.includes(\"][\")) {\n const keyParts = key.split(\"[\");\n\n const keyName = keyParts[0];\n\n const keyNameParts = keyParts[1].split(\"]\");\n\n const index = Number(keyNameParts[0]);\n\n /*\n A NON-NUMERIC first segment is not an array index — it is a deeper\n nested object. `a[b][c]=x` reaches this branch because it contains\n \"][\", but `Number(\"b\")` is NaN, and the code below used to write to\n `[NaN]`: that sets a \"NaN\" PROPERTY on an array whose length stays\n 0, so the request arrived as `{a: []}` and the value was gone. No\n error, no warning — the caller simply never got `x`.\n\n Deciding between refusing (4xx) and interpreting: a doubly-nested\n key is unambiguous and is exactly what every bracket-notation\n parser means by it, so we interpret. Refusing would reject a URL\n shape that is standard elsewhere and that we ourselves already\n honour one level shallower, five lines below. What was definitely\n wrong was answering with a shape the caller did not send.\n\n Numeric indices keep the array-of-objects path below unchanged —\n `items[0][name]` is still an array.\n */\n if (Number.isNaN(index)) {\n set(\n body,\n this.bracketKeyToPath(key),\n this.arrayValueFor(value, isArrayKey, this.parseValue.bind(this)),\n );\n\n continue;\n }\n\n if (!arrayOfObjectValues[keyName]) {\n arrayOfObjectValues[keyName] = [];\n }\n\n if (!arrayOfObjectValues[keyName][index]) {\n arrayOfObjectValues[keyName][index] = {};\n }\n\n // now get the key after the index\n const keyNameParts2 = keyParts[2].split(\"]\");\n const keyName2 = keyNameParts2[0];\n\n arrayOfObjectValues[keyName][index][keyName2] = this.parseValue(value);\n\n continue;\n }\n\n const keyParts = key.split(\"[\");\n const keyName = keyParts[0];\n const keyNameParts = keyParts[1].split(\"]\");\n\n /*\n `isArrayKey` is honoured HERE, and used not to be. `filter[tags][]=a`\n sets the flag at the top of the loop, but this branch only wrapped\n when the underlying value was ALREADY an array — which it is for two\n or more occurrences and is not for one. So `filter[tags][]=a` arrived\n as `{filter:{tags:\"a\"}}` while `…=a&…=b` arrived as `{tags:[\"a\",\"b\"]}`:\n the same declared shape, two different types, decided by how many\n times the caller happened to send it.\n\n That single-element case is the one a UI hits first — one filter\n chip selected — and `@warlock.js/web`'s decoder reads it as an array,\n so the page and the server disagreed about the same URL.\n */\n set(\n body,\n keyName + \".\" + keyNameParts[0],\n this.arrayValueFor(value, isArrayKey, this.parseValue.bind(this)),\n );\n\n continue;\n }\n\n if (Array.isArray(value)) {\n set(body, key, value.map(this.parseValue.bind(this)));\n } else if (isArrayKey) {\n if (body[key]) {\n body[key].push(this.parseValue(value));\n } else {\n body[key] = [this.parseValue(value)];\n\n continue;\n }\n } else {\n set(body, key, this.parseValue(value));\n }\n }\n\n // now merge the array of objects into the body\n for (const key in arrayOfObjectValues) {\n body[key] = arrayOfObjectValues[key];\n }\n\n return body;\n } catch (error) {\n console.log(error);\n this.log(error, \"error\");\n }\n }\n\n /**\n * Parse the given data\n */\n protected parseValue(data: any) {\n // data.value appears only in the multipart form data\n // if it json, then just return the data\n if (data?.file) return new UploadedFile(data);\n if (data?.value !== undefined && data?.fields && data?.type) {\n data = data.value;\n }\n\n if (data === \"false\") return false;\n\n if (data === \"true\") return true;\n\n if (data === \"null\") return null;\n\n if (typeof data === \"string\") return data.trim();\n\n return data;\n }\n\n /**\n * Set route handler\n */\n public setRoute(route: Route) {\n this.route = route;\n\n // pass the route to the response object\n this.response.setRoute(route);\n\n return this;\n }\n\n /**\n * Trigger an http event\n */\n public trigger(eventName: RequestEvent, ...args: any[]) {\n return events.trigger(`request.${eventName}`, ...args, this);\n }\n\n /**\n * Listen to the given event\n */\n public on(eventName: RequestEvent, callback: any) {\n return events.subscribe(`request.${eventName}`, callback);\n }\n\n /**\n * Make a log message\n */\n public log(message: any, level: LogLevel = \"info\") {\n if (!config.key(\"http.log\")) return;\n\n log.log({\n module: \"request\",\n action: this.route.method + \" \" + this.route.path.replace(\"/*\", \"\") + `:${this.id}`,\n message,\n type: level,\n context: {\n request: this,\n },\n });\n }\n\n /**\n * Get current request path\n */\n public get path() {\n return this.baseRequest.url;\n }\n\n /**\n * {@alias}\n */\n public get url() {\n return this.baseRequest.url;\n }\n\n /**\n * Get full url\n */\n public get fullUrl() {\n return this.protocol + \"://\" + this.hostname + this.path;\n }\n\n /**\n * Drive the middleware chain for the current route, then defer to the\n * controller. Returns the first response value any middleware short-circuits\n * with, or `undefined` to continue into validation + handler.\n *\n * @internal Framework orchestration — do not call from app code. Will move\n * to a dedicated controller dispatcher in a future refactor.\n */\n public async runMiddleware() {\n // measure request time\n // check for middleware first\n const middlewareOutput = await this.executeMiddleware();\n\n if (middlewareOutput !== undefined) {\n // 👇🏻 make sure first its not a response instance\n if (middlewareOutput instanceof Response) return middlewareOutput;\n // 👇🏻 send the response\n return this.response.send(middlewareOutput);\n }\n\n const handler = this.route.handler;\n\n if (!handler.validation) return;\n\n // 👇🏻 check for validation using validateAll helper function\n const validationOutput = await validateAll(handler.validation, this, this.response);\n\n return validationOutput;\n }\n\n /**\n * Return the request handler attached to the current route.\n *\n * @internal Framework orchestration — do not call from app code.\n */\n public getHandler() {\n return this.route.handler;\n }\n\n /**\n * Get inputs that has been validated only\n * You can also pass an array of inputs to get only the validated inputs\n */\n public validated<Output = RequestValidation>(inputs?: (keyof Output | (string & {}))[]): Output {\n if (this.validatedData) {\n return inputs\n ? only(this.validatedData as Output, inputs as string[])\n : (this.validatedData as Output);\n }\n\n return {} as Output;\n }\n\n /**\n * Get inputs that has been validated except the given inputs\n */\n public validatedExcept(...inputs: string[]): RequestValidation {\n return except(this.validated(), inputs);\n }\n\n /**\n * Set validated data\n */\n public setValidatedData(data: RequestValidation) {\n this.validatedData = data;\n }\n\n /**\n * Top-level entry into the request lifecycle — opens the context store,\n * runs middleware, drives the handler, handles errors.\n *\n * @internal Framework orchestration — do not call from app code. Wired\n * from the Fastify route handler in `router.scan()`.\n */\n public async execute() {\n try {\n // call executingAction event\n\n this.log(\"Executing the request\");\n\n return await createRequestStore(this, this.response);\n } catch (error) {\n this.log(error, \"error\");\n\n throw error;\n }\n }\n\n /**\n * Iterate the collected middlewares in order; return the first short-circuit\n * value or `undefined` when every middleware passes through.\n *\n * @internal Framework orchestration — do not call from app code.\n */\n protected async executeMiddleware() {\n // collect all middlewares for current route\n const middlewares = this.collectMiddlewares();\n\n // check if there are no middlewares, then return\n if (middlewares.length === 0) return;\n\n this.log(\"About to execute request middlewares\");\n\n // trigger the executingMiddleware event\n this.trigger(\"executingMiddleware\", middlewares, this.route);\n\n for (const middleware of middlewares) {\n this.log(\"Executing middleware \" + colors.yellowBright(middleware.name));\n const output = await middleware({ request: this, response: this.response });\n this.log(\"Executed middleware \" + colors.yellowBright(middleware.name), \"success\");\n\n if (output !== undefined) {\n this.log(\n colors.yellow(\"request intercepted by middleware \") + colors.cyanBright(middleware.name),\n \"warn\",\n );\n\n this.trigger(\"executedMiddleware\");\n\n this.log(\"Request middlewares executed\", \"success\");\n\n return output;\n }\n }\n\n this.log(\"Request middlewares executed\", \"success\");\n\n // trigger the executedMiddleware event\n this.trigger(\"executedMiddleware\", middlewares, this.route);\n }\n\n /**\n * Gather the middleware list for the current route — today just the\n * route-level array; future extraction may merge group + app-wide layers.\n *\n * @internal Framework orchestration — do not call from app code.\n */\n protected collectMiddlewares(): Middleware[] {\n const middlewaresList: Middleware[] = [];\n\n // collect route middlewares\n if (this.route.middleware) {\n middlewaresList.push(...this.route.middleware);\n }\n\n return middlewaresList;\n }\n\n /**\n * Get request input value from query string, params or body\n */\n public input(key: string, defaultValue?: any) {\n return get(this.payload.all, key, defaultValue);\n }\n\n /**\n * Get email input value, this will lowercase the value\n */\n public email(key: string = \"email\", defaultValue: string = \"\"): string {\n return this.input(key, defaultValue)?.toLowerCase() || defaultValue;\n }\n\n /**\n * @alias input\n */\n public get(key: string, defaultValue?: any) {\n return this.input(key, defaultValue);\n }\n\n /**\n * Determine if request has input value\n */\n public has(key: string) {\n return get(this.payload.all, key, undefined) !== undefined;\n }\n\n /**\n * Set request input value\n */\n public set(key: string, value: any) {\n set(this.payload.all, key, value);\n\n return this;\n }\n\n /**\n * Set the given value if the request does not have the input\n */\n public setDefault(key: string, value: any) {\n if (this.has(key)) return this;\n\n set(this.payload.all, key, value);\n\n return this;\n }\n\n /**\n * Unset request payload keys\n */\n public unset(...keys: string[]) {\n this.payload.all = unset(this.payload.all, keys);\n\n return this;\n }\n\n /**\n * Get request body\n */\n public get body() {\n return this.payload.body;\n }\n\n /**\n * Set request body value\n */\n public setBody(key: string, value: any) {\n set(this.payload.body, key, value);\n\n return this;\n }\n\n /**\n * Get body inputs except files\n */\n public get bodyInputs() {\n const inputs = this.payload.body;\n\n const bodyInputs: any = {};\n\n for (const key in inputs) {\n const value = inputs[key];\n\n if (value.file && value.fieldname) continue;\n\n bodyInputs[key] = value;\n }\n\n return bodyInputs;\n }\n\n /**\n * Get request file in UploadedFile instance\n */\n public file(key: string): UploadedFile | undefined {\n const file = this.input(key);\n\n return file;\n }\n\n /**\n * Get uploaded files from the request for the given name\n * If the given name is not present in the request, return an empty array\n */\n public files(name: string): UploadedFile[] {\n return this.input(name) || [];\n }\n\n /**\n * Get request params\n */\n public get params() {\n return this.payload.params;\n }\n\n /**\n * Set request params value\n */\n public setParam(key: string, value: any) {\n set(this.payload.params, key, value);\n\n return this;\n }\n\n /**\n * Get request query\n */\n public get query() {\n return this.payload.query;\n }\n\n /**\n * Set request query value\n */\n public setQuery(key: string, value: any) {\n set(this.payload.query, key, value);\n\n return this;\n }\n\n /**\n * Get all inputs\n */\n public all() {\n return this.payload.all;\n }\n\n /**\n * Get all inputs except params\n */\n public allExceptParams() {\n return {\n ...this.payload.query,\n ...this.payload.body,\n };\n }\n\n /**\n * Get all heavy inputs except params\n */\n public heavyExceptParams() {\n const inputs = this.allExceptParams();\n\n const heavyInputs: any = {};\n\n for (const key in inputs) {\n const value = inputs[key];\n\n if (isEmpty(value) && value !== null) continue;\n\n heavyInputs[key] = value;\n }\n\n return heavyInputs;\n }\n\n /**\n * Get only heavy inputs, the input with a value\n */\n public heavy() {\n const inputs = this.all();\n\n const heavyInputs: any = {};\n\n for (const key in inputs) {\n const value = inputs[key];\n\n if (isEmpty(value) && value !== null) continue;\n\n heavyInputs[key] = value;\n }\n\n return heavyInputs;\n }\n\n /**\n * Get only the given keys from the request data\n */\n public only(keys: string[]) {\n return only(this.all(), keys);\n }\n\n /**\n * Pluck the given keys from the request data\n */\n public pluck(keys: string[]) {\n const data = this.only(keys);\n\n this.unset(...keys);\n\n return data;\n }\n\n /**\n * Get all request inputs except the given keys\n */\n public except(keys: string[]) {\n return except(this.all(), keys);\n }\n\n /**\n * Get boolean input value\n */\n public bool(key: string, defaultValue = false) {\n const value = this.input(key, defaultValue);\n\n if (value === \"true\") {\n return true;\n }\n\n if (value === \"false\") {\n return false;\n }\n\n if (value === 0) {\n return false;\n }\n\n return Boolean(value);\n }\n\n /**\n * Get integer input value\n */\n public int(key: string, defaultValue: number = 0): number | undefined {\n const value = this.input(key, defaultValue);\n\n if (!value && value !== 0) return undefined;\n\n return parseInt(value);\n }\n\n /**\n * Shorthand getter to get id param\n */\n public get idParam() {\n return this.int(\"id\");\n }\n\n /**\n * Get string input value\n */\n public string(key: string, defaultValue: string = \"\"): string {\n const value = this.input(key, defaultValue);\n\n return String(value);\n }\n\n /**\n * Get float input value\n */\n public float(key: string, defaultValue: number = 0): number {\n const value = this.input(key, defaultValue);\n\n return parseFloat(value) || 0;\n }\n\n /**\n * Get number input value\n */\n public number(key: string, defaultValue: number = 0): number {\n const value = Number(this.input(key, defaultValue));\n\n return isNaN(value) ? defaultValue : value;\n }\n\n /**\n * Immediate-peer IP as Fastify reports it — the address that connected to\n * the server socket, with `trustProxy` resolution applied. Use this when\n * you specifically need the peer address (rate-limit-by-direct-connection,\n * health-check origin verification).\n *\n * **For most use cases prefer `request.detectIp()`** — behind any proxy\n * (load balancer, CDN, sidecar) `ip` reports the proxy, not the real client.\n */\n public get ip() {\n return this.baseRequest.ip;\n }\n\n /**\n * Best-effort real client IP — the value everything IP-scoped keys on\n * (ip-filter allowlists, rate-limit buckets, idempotency scoping).\n *\n * `X-Forwarded-For` resolution is **delegated to Fastify**: `baseRequest.ip`\n * is already the client address Fastify's `trustProxy` machinery picked out\n * of the chain, so every shape `http.trustProxy` accepts is honoured here\n * with exactly the semantics Fastify documents:\n *\n * - `false` (default) — no header is trusted; the socket peer address wins.\n * Both forwarding headers are client-settable, so without a trusted edge\n * that rewrites them any client could otherwise forge its own IP.\n * - `true` — the whole chain is trusted; the leftmost hop (original client)\n * wins.\n * - `number` — that many rightmost hops are trusted, so an edge that\n * APPENDS to `X-Forwarded-For` yields the real client rather than whatever\n * the client prepended.\n * - CIDR / IP list (string, comma-separated string, or array) or a custom\n * predicate — the chain is walked right-to-left and stops at the first hop\n * that isn't a trusted proxy.\n *\n * `X-Real-IP` is NOT part of that resolution — Fastify never looks at it,\n * and unlike `X-Forwarded-For` it carries no chain, so there is nothing to\n * validate a hop count or proxy allowlist against. It is therefore honoured\n * only under `trustProxy: true` (\"everything upstream is mine\"), where it is\n * no weaker than the trust already granted. Under a bounded `trustProxy`\n * (hop count / CIDR list) it is ignored: a trusted-but-passthrough edge that\n * forwards the client's own `X-Real-IP` verbatim would otherwise hand any\n * client a way around the bound.\n *\n * **Prefer this over `request.ip` for any caller behind a proxy** (load\n * balancer, CDN, reverse proxy, k8s ingress).\n */\n public detectIp() {\n // Trusting `X-Real-IP` is only sound when the config trusts the entire\n // upstream chain; bounded shapes get chain-aware resolution instead.\n if (config.get(\"http.trustProxy\", false) === true) {\n const realIp = this.header(\"x-real-ip\");\n\n if (realIp) {\n const address = String(realIp).split(\",\")[0].trim();\n\n if (address) return address;\n }\n }\n\n // Fastify resolved this against the configured `trustProxy` already:\n // socket peer when trust is off, the correct hop of `X-Forwarded-For`\n // when it is on. Re-parsing the header here would mean a second, weaker\n // trust model that could disagree with `request.ip` and with the plugins\n // (rate limit, proxy) that key on it.\n return this.baseRequest.ip;\n }\n\n /**\n * An alias to detectIp\n */\n public get realIp() {\n return this.detectIp();\n }\n\n /**\n * Get request ips\n */\n public get ips() {\n return this.baseRequest.ips;\n }\n\n /**\n * Get request referer\n */\n public get referer() {\n return this.baseRequest.headers.referer;\n }\n\n /**\n * Get user agent\n */\n public get userAgent() {\n return this.baseRequest.headers[\"user-agent\"];\n }\n\n /**\n * Get request headers\n */\n public get headers(): typeof this.baseRequest.headers {\n return this.baseRequest.headers;\n }\n\n /**\n * Set the given header\n */\n public setHeader(key: HeaderKeys, value: string) {\n this.baseRequest.headers[key.toLowerCase()] = value;\n\n return this;\n }\n}\n"],"mappings":"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"}
|
package/esm/http/response.mjs
CHANGED
|
@@ -887,7 +887,7 @@ var Response = class Response {
|
|
|
887
887
|
errors: "errors",
|
|
888
888
|
inputKey: "input",
|
|
889
889
|
inputError: "error",
|
|
890
|
-
status:
|
|
890
|
+
status: 422
|
|
891
891
|
});
|
|
892
892
|
log.error("request", "validation", `${this.request.id} - Validation failed`);
|
|
893
893
|
return this.send({ [errors]: result.errors.map((error) => ({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response.mjs","names":[],"sources":["../../../../../../../core/src/http/response.ts"],"sourcesContent":["import type { CookieSerializeOptions } from \"@fastify/cookie\";\r\nimport config from \"@mongez/config\";\r\nimport type { EventSubscription } from \"@mongez/events\";\r\nimport events from \"@mongez/events\";\r\nimport { fileExistsAsync } from \"@warlock.js/fs\";\r\nimport { isIterable, isPlainObject, isScalar } from \"@mongez/supportive-is\";\r\nimport type { LogLevel } from \"@warlock.js/logger\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport type { ValidationResult } from \"@warlock.js/seal\";\r\nimport type { FastifyReply } from \"fastify\";\r\nimport fs from \"fs\";\r\nimport mime from \"mime\";\r\nimport path from \"path\";\r\nimport type React from \"react\";\r\nimport { type ReactNode } from \"react\";\r\nimport { Application } from \"../application/application\";\r\nimport type { Route } from \"../router\";\r\nimport { StorageFile } from \"../storage\";\r\nimport { renderReact } from \"./../react\";\r\nimport type { Request } from \"./request\";\r\nimport type { ResponseEvent, ResponseSSEController, ResponseStreamController } from \"./types\";\r\n\r\ntype CookieValue = string | number | boolean | Record<string, any> | Array<any>;\r\n\r\n/**\r\n * Cookie options accepted by `response.cookie()`.\r\n *\r\n * Extends Fastify's `CookieSerializeOptions` with `raw` — set to `true` to\r\n * skip the default `JSON.stringify` of the value and write it as-is. Use for\r\n * plain-string cookies (session tokens, opaque IDs) that shouldn't be JSON-quoted.\r\n *\r\n * When `raw: true`, non-string values are coerced via `String(value)`. The\r\n * read side (`request.cookie(name)`) tries `JSON.parse` first and falls back\r\n * to the raw string on parse failure, so round-tripping a raw string cookie\r\n * Just Works.\r\n */\r\nexport type CookieOptions = CookieSerializeOptions & {\r\n /**\r\n * Skip JSON.stringify and write the value as-is.\r\n *\r\n * @default false\r\n */\r\n raw?: boolean;\r\n};\r\n\r\nexport enum ResponseStatus {\r\n OK = 200,\r\n CREATED = 201,\r\n ACCEPTED = 202,\r\n MOVED_PERMANENTLY = 301,\r\n FOUND = 302,\r\n SEE_OTHER = 303,\r\n NOT_MODIFIED = 304,\r\n TEMPORARY_REDIRECT = 307,\r\n PERMANENT_REDIRECT = 308,\r\n NO_CONTENT = 204,\r\n BAD_REQUEST = 400,\r\n UNAUTHORIZED = 401,\r\n FORBIDDEN = 403,\r\n NOT_FOUND = 404,\r\n METHOD_NOT_ALLOWED = 405,\r\n CONFLICT = 409,\r\n TOO_MANY_REQUESTS = 429,\r\n INTERNAL_SERVER_ERROR = 500,\r\n SERVICE_UNAVAILABLE = 503,\r\n}\r\n\r\n/**\r\n * Options for sending files\r\n */\r\nexport type SendFileOptions = {\r\n cacheTime?: number;\r\n immutable?: boolean;\r\n inline?: boolean;\r\n filename?: string;\r\n};\r\n\r\n/**\r\n * Options for sending buffers\r\n */\r\nexport type SendBufferOptions = SendFileOptions & {\r\n contentType?: string;\r\n etag?: string;\r\n};\r\n\r\n/**\r\n * The cookie flags every response cookie gets unless something overrides them.\r\n *\r\n * Computed per call rather than hoisted to a constant because `secure` depends\r\n * on the environment, which is not known at module-evaluation time.\r\n *\r\n * `secure` is relaxed in development only: a `Secure` cookie is dropped by the\r\n * browser over plain http, which would silently break every local login. It\r\n * stays on everywhere else, including test and staging.\r\n */\r\nfunction secureCookieDefaults(): CookieSerializeOptions {\r\n return {\r\n httpOnly: true,\r\n sameSite: \"lax\",\r\n secure: !Application.isDevelopment,\r\n };\r\n}\r\n\r\nexport class Response {\r\n /**\r\n * Current route\r\n */\r\n protected route!: Route;\r\n\r\n /**\r\n * Underlying Fastify reply — a public escape hatch to capabilities the\r\n * framework's high-level helpers don't yet cover.\r\n *\r\n * **Prefer framework methods first**: `response.send()`, `response.header()`,\r\n * `response.cookie()`, `response.sendFile()`, `response.stream()`, etc.\r\n * They wire status codes, content-type detection, the event lifecycle, and\r\n * the cache-pattern replay path correctly.\r\n *\r\n * **Reach for `baseResponse` only** when the framework genuinely lacks a\r\n * helper for what you need — and when you do, file an issue so we can add\r\n * it. Streaming and SSE are the precedent here: they bypass `send()`\r\n * deliberately because the framework didn't ship chunked-write support\r\n * natively at the time. Reaching here for non-streaming work means a\r\n * missing helper, not an answer.\r\n */\r\n public baseResponse!: FastifyReply;\r\n\r\n /**\r\n * Current status code\r\n */\r\n protected currentStatusCode = 200;\r\n\r\n /**\r\n * Current response body\r\n */\r\n protected currentBody: any;\r\n\r\n /**\r\n * Request object\r\n */\r\n public request!: Request;\r\n\r\n /**\r\n * Internal events related to this particular response object\r\n */\r\n protected events = new Map<string, any[]>();\r\n\r\n /**\r\n * Parsed body\r\n * This will return the parsed body of the response\r\n * Please note that if this property is called before the response is sent, it will return undefined\r\n */\r\n public parsedBody: any;\r\n\r\n /**\r\n * Get raw response\r\n */\r\n public get raw() {\r\n return this.baseResponse.raw;\r\n }\r\n\r\n /**\r\n * Get Current response body\r\n */\r\n public get body() {\r\n return this.currentBody;\r\n }\r\n\r\n /**\r\n * Set response body\r\n */\r\n public set body(body: any) {\r\n this.currentBody = body;\r\n }\r\n\r\n /**\r\n * Add event on sending response\r\n */\r\n public onSending(callback: any) {\r\n this.events.set(\"sending\", [...(this.events.get(\"sending\") || []), callback]);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Add event on sent response\r\n */\r\n public onSent(callback: any) {\r\n this.events.set(\"sent\", [...(this.events.get(\"sent\") || []), callback]);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Set the Fastify response object\r\n */\r\n public setResponse(response: FastifyReply) {\r\n this.baseResponse = response;\r\n\r\n // Listen to the 'finish' event to track when response is fully sent\r\n // This works for all response types: JSON, streams, buffers, files, etc.\r\n this.baseResponse.raw.once(\"finish\", () => {\r\n this.request.endTime = Date.now();\r\n });\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Reset the response state\r\n */\r\n public reset() {\r\n this.route = {} as Route;\r\n this.currentBody = null;\r\n this.currentStatusCode = 200;\r\n }\r\n\r\n /**\r\n * Set current route\r\n */\r\n public setRoute(route: Route) {\r\n this.route = route;\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get the content type\r\n */\r\n public get contentType() {\r\n return this.baseResponse.getHeader(\"Content-Type\");\r\n }\r\n\r\n /**\r\n * Set the content type\r\n */\r\n public setContentType(contentType: string) {\r\n this.baseResponse.header(\"Content-Type\", contentType);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get the status code\r\n */\r\n public get statusCode(): number {\r\n return this.currentStatusCode ?? this.baseResponse.statusCode;\r\n }\r\n\r\n /**\r\n * Check if response status is ok\r\n */\r\n public get isOk() {\r\n return this.currentStatusCode >= 200 && this.currentStatusCode < 300;\r\n }\r\n\r\n /**\r\n * Check if the response has been sent\r\n */\r\n public get sent() {\r\n return this.baseResponse.sent;\r\n }\r\n\r\n /**\r\n * Add a listener to the response event\r\n */\r\n public static on(\r\n event: ResponseEvent,\r\n listener: (response: Response) => void,\r\n ): EventSubscription {\r\n return events.subscribe(`response.${event}`, listener);\r\n }\r\n\r\n /**\r\n * Trigger the response event\r\n */\r\n protected static async trigger(event: ResponseEvent, ...args: any[]) {\r\n // make a timeout to make sure the request events is executed first\r\n return new Promise((resolve) => {\r\n setTimeout(async () => {\r\n await events.triggerAllAsync(`response.${event}`, ...args);\r\n resolve(true);\r\n }, 0);\r\n });\r\n }\r\n\r\n /**\r\n * Parse body\r\n */\r\n protected async parseBody() {\r\n return await this.parse(this.currentBody);\r\n }\r\n\r\n /**\r\n * Parse the given value\r\n */\r\n public async parse(value: any): Promise<any> {\r\n // if it is a falsy value, return it\r\n if (!value || isScalar(value)) return value;\r\n\r\n // if it has a `toJSON` method, call it and await the result then return it\r\n if (value.toJSON) {\r\n value.request = this.request;\r\n return await value.toJSON();\r\n }\r\n\r\n // if it is iterable, an array or array-like object then parse each item\r\n if (isIterable(value)) {\r\n const values = Array.from(value);\r\n\r\n return Promise.all(\r\n values.map(async (item: any) => {\r\n return await this.parse(item);\r\n }),\r\n );\r\n }\r\n\r\n // if not plain object, then return it\r\n if (!isPlainObject(value)) {\r\n return value;\r\n }\r\n\r\n // loop over the object and check if the value and call `parse` on it\r\n for (const key in value) {\r\n const subValue = value[key];\r\n\r\n value[key] = await this.parse(subValue);\r\n }\r\n\r\n return value;\r\n }\r\n\r\n /**\r\n * Make a log message\r\n */\r\n public log(message: string, level: LogLevel = \"info\") {\r\n if (!config.get(\"http.log\")) return;\r\n\r\n log.log({\r\n module: \"response\",\r\n action: this.route.method + \" \" + this.route.path.replace(\"/*\", \"\") + `:${this.request.id}`,\r\n message,\r\n type: level,\r\n context: {\r\n request: this.request,\r\n response: this,\r\n },\r\n });\r\n }\r\n\r\n /**\r\n * Check if returning response is json\r\n */\r\n public get isJson() {\r\n return this.getHeader(\"Content-Type\") === \"application/json\";\r\n }\r\n\r\n /**\r\n * Send the response\r\n * @param data - Response data\r\n * @param statusCode - HTTP status code\r\n * @param triggerEvents - Whether to trigger response events (default: true)\r\n */\r\n public async send(data?: any, statusCode?: number, triggerEvents = true): Promise<Response> {\r\n // Defensive guard against double-send. The underlying Fastify reply silently\r\n // ignores subsequent sends once `sent === true`, which has historically hidden\r\n // middleware bugs (cache-pattern replay paths that returned `baseResponse.send`\r\n // ended up re-entering `Response.send` with the FastifyReply as the body).\r\n // Surfacing the misuse via `error`-level log makes the bug loud without\r\n // crashing production traffic.\r\n if (this.baseResponse.sent) {\r\n log.error(\r\n \"response\",\r\n \"send\",\r\n `send() called on already-sent response (request:${this.request?.id ?? \"unknown\"}) — likely a middleware bug`,\r\n );\r\n\r\n return this;\r\n }\r\n\r\n if (statusCode) {\r\n this.currentStatusCode = statusCode;\r\n }\r\n\r\n if (data === this) return this;\r\n\r\n if (data) {\r\n this.currentBody = data;\r\n }\r\n\r\n if (!this.currentStatusCode) {\r\n this.currentStatusCode = 200;\r\n }\r\n\r\n this.log(\"Sending response\");\r\n // Auto-pick `application/json` only when no content-type was set by the caller.\r\n // This preserves explicit overrides (e.g. `application/vnd.api+json` from a\r\n // cache replay, `application/problem+json` from an RFC 7807 error response)\r\n // while keeping the convenience default for the common object-body path.\r\n if (Array.isArray(this.currentBody) || isPlainObject(this.currentBody)) {\r\n if (!this.baseResponse.getHeader(\"Content-Type\")) {\r\n this.setContentType(\"application/json\");\r\n }\r\n }\r\n\r\n if (triggerEvents) {\r\n await Response.trigger(\"sending\", this);\r\n\r\n for (const callback of this.events.get(\"sending\") || []) {\r\n await callback(this);\r\n }\r\n\r\n if (this.isJson) {\r\n await Response.trigger(\"sendingJson\", this);\r\n for (const callback of this.events.get(\"sendingJson\") || []) {\r\n await callback(this);\r\n }\r\n\r\n if (this.isOk) {\r\n await Response.trigger(\"sendingSuccessJson\", this);\r\n for (const callback of this.events.get(\"sendingSuccessJson\") || []) {\r\n await callback(this);\r\n }\r\n }\r\n }\r\n }\r\n\r\n // parse the body and make sure it is transformed to sync data instead of async data\r\n if (typeof this.currentBody !== \"string\") {\r\n this.parsedBody = await this.parseBody();\r\n } else {\r\n this.parsedBody = data;\r\n }\r\n\r\n // Set the status first\r\n this.baseResponse.status(this.currentStatusCode);\r\n\r\n // Then send the response with the parsed body\r\n await this.baseResponse.send(this.parsedBody);\r\n\r\n this.log(\"Response sent\");\r\n\r\n if (triggerEvents) {\r\n // trigger the sent event\r\n Response.trigger(\"sent\", this);\r\n\r\n for (const callback of this.events.get(\"sent\") || []) {\r\n callback(this);\r\n }\r\n\r\n // trigger the success event if the status code is 2xx\r\n if (this.currentStatusCode >= 200 && this.currentStatusCode < 300) {\r\n Response.trigger(\"success\", this);\r\n }\r\n\r\n // trigger the successCreate event if the status code is 201\r\n if (this.currentStatusCode === 201) {\r\n Response.trigger(\"successCreate\", this);\r\n }\r\n\r\n // trigger the badRequest event if the status code is 400\r\n if (this.currentStatusCode === 400) {\r\n Response.trigger(\"badRequest\", this);\r\n }\r\n\r\n // trigger the unauthorized event if the status code is 401\r\n if (this.currentStatusCode === 401) {\r\n Response.trigger(\"unauthorized\", this);\r\n }\r\n\r\n // trigger the forbidden event if the status code is 403\r\n if (this.currentStatusCode === 403) {\r\n Response.trigger(\"forbidden\", this);\r\n }\r\n\r\n // trigger the notFound event if the status code is 404\r\n if (this.currentStatusCode === 404) {\r\n Response.trigger(\"notFound\", this);\r\n }\r\n\r\n // trigger the content too large event if the status code is 413\r\n if (this.currentStatusCode === 413) {\r\n Response.trigger(\"contentTooLarge\", this);\r\n }\r\n\r\n // trigger the throttled event if the status code is 429\r\n if (this.currentStatusCode === 429) {\r\n Response.trigger(\"throttled\", this);\r\n }\r\n\r\n // trigger the serverError event if the status code is 500\r\n if (this.currentStatusCode === 500) {\r\n Response.trigger(\"serverError\", this);\r\n }\r\n\r\n // trigger the error event if the status code is 4xx or 5xx\r\n if (this.currentStatusCode >= 400) {\r\n Response.trigger(\"error\", this);\r\n }\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Replay a previously-captured response shape — used by cache-pattern\r\n * middlewares (idempotency, response cache) to send a cached response\r\n * without re-running the controller.\r\n *\r\n * Preserves the cached status code, content-type, and any extra headers,\r\n * then sends the body through the standard `send()` pipeline so the full\r\n * event lifecycle still fires (`sent`, `success`, status-specific events).\r\n * That keeps cross-cutting observers (logger, metrics, audit) consistent\r\n * between fresh and replayed responses.\r\n *\r\n * @example\r\n * // Inside a cache-pattern middleware on HIT:\r\n * return response.header(\"X-Cache\", \"HIT\").replay({\r\n * status: cached.status,\r\n * body: cached.body,\r\n * contentType: cached.contentType,\r\n * });\r\n */\r\n public replay(cached: {\r\n status: number;\r\n body: unknown;\r\n contentType?: string;\r\n headers?: Record<string, string>;\r\n }): Promise<Response> {\r\n this.setStatusCode(cached.status);\r\n\r\n if (cached.contentType) {\r\n this.setContentType(cached.contentType);\r\n }\r\n\r\n if (cached.headers) {\r\n for (const [name, value] of Object.entries(cached.headers)) {\r\n this.header(name, value);\r\n }\r\n }\r\n\r\n return this.send(cached.body);\r\n }\r\n\r\n /**\r\n * Send html response\r\n */\r\n public html(data: string, statusCode?: number) {\r\n return this.setContentType(\"text/html\").send(data, statusCode);\r\n }\r\n\r\n /**\r\n * Render the given react component\r\n */\r\n public render(element: React.ReactElement | React.ComponentType, status = 200) {\r\n return this.setStatusCode(status).html(renderReact(element));\r\n }\r\n\r\n /**\r\n * Send xml response\r\n */\r\n public xml(data: string, statusCode?: number) {\r\n return this.setContentType(\"text/xml\").send(data, statusCode);\r\n }\r\n\r\n /**\r\n * Send plain text response\r\n */\r\n public text(data: string, statusCode?: number) {\r\n return this.setContentType(\"text/plain\").send(data, statusCode);\r\n }\r\n\r\n /**\r\n * Create a streaming response for progressive/chunked data sending\r\n *\r\n * This method allows you to send data in chunks and control when the response ends.\r\n * Perfect for Server-Sent Events (SSE), progressive rendering, or streaming large responses.\r\n *\r\n * @example\r\n * ```ts\r\n * const stream = response.stream(\"text/html\");\r\n * stream.send(\"<html><body>\");\r\n * stream.send(\"<h1>Hello</h1>\");\r\n * stream.render(<MyComponent />);\r\n * stream.send(\"</body></html>\");\r\n * stream.end();\r\n * ```\r\n *\r\n * @param contentType - The content type for the stream (default: \"text/plain\")\r\n * @returns Stream controller with send(), render(), and end() methods\r\n */\r\n public stream(contentType = \"text/plain\"): ResponseStreamController {\r\n // Set headers using the response API\r\n this.setContentType(contentType);\r\n this.header(\"Transfer-Encoding\", \"chunked\");\r\n this.header(\"Cache-Control\", \"no-cache\");\r\n this.header(\"Connection\", \"keep-alive\");\r\n this.header(\"X-Content-Type-Options\", \"nosniff\");\r\n\r\n // Trigger sending events\r\n Response.trigger(\"sending\", this);\r\n for (const callback of this.events.get(\"sending\") || []) {\r\n callback(this);\r\n }\r\n\r\n this.log(\"Starting stream\");\r\n\r\n // Track stream state\r\n let isEnded = false;\r\n const chunks: any[] = [];\r\n\r\n // Write headers to start the stream\r\n // Note: We use raw here because we need chunked encoding control\r\n // This is the only valid use case for bypassing Fastify's abstraction\r\n this.baseResponse.raw.writeHead(this.statusCode, this.getHeaders() as any);\r\n\r\n return {\r\n /**\r\n * Send a chunk of data to the client\r\n * @param data - Data to send (string, Buffer, or any serializable data)\r\n */\r\n send: (data: any) => {\r\n if (isEnded) {\r\n throw new Error(\"Cannot send data: stream has already ended\");\r\n }\r\n\r\n this.baseResponse.raw.write(data);\r\n\r\n return this;\r\n },\r\n\r\n /**\r\n * Render a React component and send it as a chunk\r\n * @param element - React element or component to render\r\n */\r\n render: (element: ReactNode) => {\r\n if (isEnded) {\r\n throw new Error(\"Cannot render: stream has already ended\");\r\n }\r\n\r\n const html = renderReact(element);\r\n chunks.push(html);\r\n this.baseResponse.raw.write(html);\r\n\r\n return this;\r\n },\r\n\r\n /**\r\n * End the stream and trigger completion events\r\n */\r\n end: () => {\r\n if (isEnded) {\r\n return this;\r\n }\r\n\r\n isEnded = true;\r\n\r\n // Store the streamed content for logging/debugging\r\n this.currentBody = chunks;\r\n this.parsedBody = chunks;\r\n\r\n // End the response\r\n this.baseResponse.raw.end();\r\n\r\n this.log(\"Stream ended\");\r\n\r\n // Trigger sent events\r\n Response.trigger(\"sent\", this);\r\n for (const callback of this.events.get(\"sent\") || []) {\r\n callback(this);\r\n }\r\n\r\n // Trigger success event if status is 2xx\r\n if (this.isOk) {\r\n Response.trigger(\"success\", this);\r\n }\r\n\r\n // Trigger status-specific events\r\n if (this.currentStatusCode === 201) {\r\n Response.trigger(\"successCreate\", this);\r\n }\r\n\r\n return this;\r\n },\r\n\r\n /**\r\n * Check if the stream has ended\r\n */\r\n get ended() {\r\n return isEnded;\r\n },\r\n };\r\n }\r\n\r\n /**\r\n * Create a Server-Sent Events (SSE) stream\r\n *\r\n * SSE is a standard for pushing real-time updates from server to client.\r\n * Perfect for live notifications, progress updates, or real-time data feeds.\r\n *\r\n * @example\r\n * ```ts\r\n * const sse = response.sse();\r\n *\r\n * // Send events\r\n * sse.send(\"message\", { text: \"Hello!\" });\r\n * sse.send(\"notification\", { type: \"info\", message: \"Update available\" }, \"msg-123\");\r\n *\r\n * // Keep connection alive\r\n * const keepAlive = setInterval(() => sse.comment(\"ping\"), 30000);\r\n *\r\n * // Clean up when done\r\n * clearInterval(keepAlive);\r\n * sse.end();\r\n * ```\r\n *\r\n * @returns SSE controller with send(), comment(), and end() methods\r\n */\r\n public sse(): ResponseSSEController {\r\n // Set SSE-specific headers\r\n this.setContentType(\"text/event-stream\");\r\n this.header(\"Cache-Control\", \"no-cache, no-store, must-revalidate\");\r\n this.header(\"Connection\", \"keep-alive\");\r\n this.header(\"X-Accel-Buffering\", \"no\"); // Disable nginx buffering\r\n\r\n // Trigger sending events\r\n Response.trigger(\"sending\", this);\r\n for (const callback of this.events.get(\"sending\") || []) {\r\n callback(this);\r\n }\r\n\r\n this.log(\"Starting SSE stream\");\r\n\r\n // Track stream state\r\n let isEnded = false;\r\n const events: any[] = [];\r\n const disconnectHandlers: Array<() => void> = [];\r\n\r\n // Write headers to start the stream\r\n this.baseResponse.raw.writeHead(this.statusCode, this.getHeaders() as any);\r\n\r\n // Detect client disconnect — set isEnded silently and invoke cleanup handlers.\r\n // Without this, background jobs keep writing to a dead socket after the client drops.\r\n this.baseResponse.raw.on(\"close\", () => {\r\n if (!isEnded) {\r\n isEnded = true;\r\n this.log(\"SSE client disconnected\");\r\n for (const handler of disconnectHandlers) {\r\n handler();\r\n }\r\n }\r\n });\r\n\r\n const controller: ResponseSSEController = {\r\n /**\r\n * Send an SSE event\r\n * @param event - Event name (e.g., \"message\", \"chunk\", \"done\")\r\n * @param data - Event data (will be JSON stringified)\r\n * @param id - Optional event ID for client-side Last-Event-ID tracking (reconnect support)\r\n */\r\n send: (event: string, data: any, id?: string): ResponseSSEController => {\r\n // Silent no-op after disconnect — background jobs should not crash when\r\n // the client drops mid-stream. The onDisconnect handler handles cleanup.\r\n if (isEnded) return controller;\r\n\r\n let message = \"\";\r\n if (id) message += `id: ${id}\\n`;\r\n message += `event: ${event}\\n`;\r\n message += `data: ${JSON.stringify(data)}\\n\\n`;\r\n\r\n events.push({ event, data, id });\r\n this.baseResponse.raw.write(message);\r\n\r\n return controller;\r\n },\r\n\r\n /**\r\n * Send a comment (keeps connection alive, invisible to client)\r\n * Useful for preventing timeout on long-lived connections\r\n * @param text - Comment text\r\n */\r\n comment: (text: string): ResponseSSEController => {\r\n // Silent no-op after disconnect\r\n if (isEnded) return controller;\r\n\r\n this.baseResponse.raw.write(`: ${text}\\n\\n`);\r\n\r\n return controller;\r\n },\r\n\r\n /**\r\n * End the SSE stream and trigger completion events\r\n */\r\n end: (): ResponseSSEController => {\r\n if (isEnded) return controller;\r\n\r\n isEnded = true;\r\n\r\n // Store the events for logging/debugging\r\n this.currentBody = events;\r\n this.parsedBody = events;\r\n\r\n // End the response\r\n this.baseResponse.raw.end();\r\n\r\n this.log(\"SSE stream ended\");\r\n\r\n // Trigger sent events\r\n Response.trigger(\"sent\", this);\r\n for (const callback of this.events.get(\"sent\") || []) {\r\n callback(this);\r\n }\r\n\r\n // Trigger success event if status is 2xx\r\n if (this.isOk) {\r\n Response.trigger(\"success\", this);\r\n }\r\n\r\n return controller;\r\n },\r\n\r\n /**\r\n * Register a handler to be called when the client disconnects.\r\n * Use this to clean up EventEmitter listeners, cancel background jobs, etc.\r\n *\r\n * @example\r\n * ```ts\r\n * const sse = response.sse();\r\n * const listener = (chunk) => sse.send(\"chunk\", { chunk });\r\n * eventBus.on(aiMessageId, listener);\r\n * sse.onDisconnect(() => eventBus.off(aiMessageId, listener));\r\n * ```\r\n */\r\n onDisconnect: (handler: () => void): ResponseSSEController => {\r\n disconnectHandlers.push(handler);\r\n return controller;\r\n },\r\n\r\n /**\r\n * Check if the stream has ended (either via end() or client disconnect)\r\n */\r\n get ended() {\r\n return isEnded;\r\n },\r\n };\r\n\r\n return controller;\r\n }\r\n\r\n /**\r\n * Set the status code\r\n */\r\n public setStatusCode(statusCode: number) {\r\n this.currentStatusCode = statusCode;\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Redirect the user to another route\r\n */\r\n public redirect(url: string, statusCode = 302) {\r\n this.baseResponse.redirect(url, statusCode);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Permanent redirect\r\n */\r\n public permanentRedirect(url: string) {\r\n this.baseResponse.redirect(url, 301);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get the response time\r\n */\r\n public getResponseTime() {\r\n return this.baseResponse.elapsedTime;\r\n }\r\n\r\n /**\r\n * Remove a specific header\r\n */\r\n public removeHeader(key: string) {\r\n this.baseResponse.removeHeader(key);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get a specific header\r\n */\r\n public getHeader(key: string) {\r\n return this.baseResponse.getHeader(key);\r\n }\r\n\r\n /**\r\n * Get the response headers\r\n */\r\n public getHeaders() {\r\n return this.baseResponse.getHeaders();\r\n }\r\n\r\n /**\r\n * Set multiple headers\r\n */\r\n public headers(headers: Record<string, string>) {\r\n this.baseResponse.headers(headers);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Set the response header\r\n */\r\n public header(key: string, value: any) {\r\n this.baseResponse.header(key, value);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Set a cookie on the response.\r\n *\r\n * Values are JSON-stringified by default so structured cookies round-trip\r\n * cleanly with `request.cookie(name)`. Pass `{ raw: true }` to skip the\r\n * JSON wrapping for plain-string cookies (session tokens, opaque IDs).\r\n *\r\n * **Secure by default.** `httpOnly: true`, `sameSite: \"lax\"`, and — outside\r\n * development — `secure: true` are applied unless you override them. These\r\n * are the flags whose absence never fails a test and is fatal in production:\r\n * without `httpOnly` any injected script can read the cookie, without\r\n * `secure` it travels in cleartext, without `sameSite` it rides along on\r\n * cross-site requests. Opting out is explicit, per call or via\r\n * `http.cookies.options`.\r\n *\r\n * Precedence, lowest to highest: framework defaults → `http.cookies.options`\r\n * → the per-call `options` argument.\r\n *\r\n * @example\r\n * // JSON-wrapped (default) — round-trips with request.cookie()\r\n * response.cookie(\"prefs\", { theme: \"dark\" }, { maxAge: 3600 });\r\n *\r\n * @example\r\n * // Raw string — no JSON quoting; useful for tokens / opaque IDs\r\n * response.cookie(\"session\", \"abc.def.ghi\", { raw: true });\r\n *\r\n * @example\r\n * // Deliberately readable by client-side JS\r\n * response.cookie(\"theme\", \"dark\", { httpOnly: false });\r\n */\r\n public cookie(name: string, value: CookieValue, options: CookieOptions = {}) {\r\n const { raw, ...cookieOptions } = options;\r\n const defaultOptions = config.get(\"http.cookies.options\", {});\r\n const serializedValue = raw ? String(value) : JSON.stringify(value);\r\n\r\n this.baseResponse.setCookie(name, serializedValue, {\r\n ...secureCookieDefaults(),\r\n ...defaultOptions,\r\n ...cookieOptions,\r\n });\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Clear a cookie from the response\r\n *\r\n * @example\r\n * response.clearCookie('token', { path: '/' });\r\n */\r\n public clearCookie(name: string, options?: CookieSerializeOptions) {\r\n const defaultOptions = config.get(\"http.cookies.options\", {});\r\n this.baseResponse.clearCookie(name, { ...defaultOptions, ...options });\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Alias to header method\r\n */\r\n public setHeader(key: string, value: any) {\r\n return this.header(key, value);\r\n }\r\n\r\n /**\r\n * Send an error response with status code 500\r\n */\r\n public serverError(data: any) {\r\n return this.send(data, 500);\r\n }\r\n\r\n /**\r\n * Send a forbidden response with status code 403\r\n */\r\n public forbidden(\r\n data: any = {\r\n error: \"You are not allowed to access this resource, FORBIDDEN\",\r\n },\r\n ) {\r\n return this.send(data, 403);\r\n }\r\n\r\n /**\r\n * Send a service unavailable response with status code 503\r\n */\r\n public serviceUnavailable(data: any) {\r\n return this.send(data, 503);\r\n }\r\n\r\n /**\r\n * Send an unauthorized response with status code 401\r\n */\r\n public unauthorized(\r\n data: any = {\r\n error: \"unauthorized\",\r\n },\r\n ) {\r\n return this.send(data, 401);\r\n }\r\n\r\n /**\r\n * Send a not found response with status code 404\r\n */\r\n public notFound(\r\n data: any = {\r\n error: \"notFound\",\r\n },\r\n ) {\r\n return this.send(data, 404);\r\n }\r\n\r\n /**\r\n * Send a bad request response with status code 400\r\n */\r\n public badRequest(data: any) {\r\n return this.send(data, 400);\r\n }\r\n\r\n /**\r\n * Send a content too large response with status code 413\r\n */\r\n public contentTooLarge(data: any) {\r\n return this.send(data, 413);\r\n }\r\n\r\n /**\r\n * Send a success response with status code 201\r\n */\r\n public successCreate(data: any) {\r\n return this.send(data, 201);\r\n }\r\n\r\n /**\r\n * Send a success response\r\n */\r\n public success(data: any = { success: true }) {\r\n return this.send(data);\r\n }\r\n\r\n /**\r\n * Send a no content response with status code 204\r\n */\r\n public noContent() {\r\n return this.baseResponse.status(204).send();\r\n }\r\n\r\n /**\r\n * Send an accepted response with status code 202\r\n * Used for async operations that have been accepted but not yet processed\r\n */\r\n public accepted(data: any = { message: \"Request accepted for processing\" }) {\r\n return this.send(data, 202);\r\n }\r\n\r\n /**\r\n * Send a conflict response with status code 409\r\n */\r\n public conflict(data: any = { error: \"Resource conflict\" }) {\r\n return this.send(data, 409);\r\n }\r\n\r\n /**\r\n * Send a too many requests response with status code 429\r\n */\r\n public tooManyRequests(data: any) {\r\n return this.send(data, 429);\r\n }\r\n\r\n /**\r\n * Send an unprocessable entity response with status code 422\r\n * Used for semantic validation errors\r\n */\r\n public unprocessableEntity(data: any) {\r\n return this.send(data, 422);\r\n }\r\n\r\n /**\r\n * Apply response options (cache, disposition, etag)\r\n * Shared helper for sendFile and sendBuffer\r\n */\r\n /**\r\n * Build an RFC 6266–safe `Content-Disposition` value for a file name.\r\n *\r\n * HTTP header values must be ISO-8859-1; Node's `setHeader` throws\r\n * `ERR_INVALID_CHAR` on any other byte, so a raw `filename=\"تقرير.pdf\"` would\r\n * crash the response (a 500 the moment the name is non-ASCII). RFC 6266 solves\r\n * this with two parameters emitted together:\r\n * - `filename=\"<ascii>\"` — sanitised ASCII fallback for legacy clients\r\n * - `filename*=UTF-8''<pct>` — RFC 5987 ext-value; modern browsers restore\r\n * the real (e.g. Arabic) name.\r\n */\r\n private contentDisposition(type: \"inline\" | \"attachment\", rawName: string): string {\r\n // Strip CR/LF first so a crafted file name can never inject extra headers.\r\n const name = (rawName || \"file\").replace(/[\\r\\n]/g, \"\");\r\n\r\n // ASCII fallback: replace quote/backslash + every non-printable-ASCII byte\r\n // (covers all multibyte characters) so the quoted-string is always legal.\r\n const ascii =\r\n name\r\n .replace(/[\"\\\\]/g, \"_\")\r\n .replace(/[^\\x20-\\x7E]/g, \"_\")\r\n .trim() || \"file\";\r\n\r\n // Pure-ASCII name → the quoted form is enough; no ext-value needed.\r\n if (!/[^\\x20-\\x7E]/.test(name)) {\r\n return `${type}; filename=\"${ascii}\"`;\r\n }\r\n\r\n // RFC 5987 ext-value. `encodeURIComponent` leaves ' ( ) * unescaped, but they\r\n // are not valid `attr-char`, so percent-encode those too.\r\n const encoded = encodeURIComponent(name).replace(\r\n /['()*]/g,\r\n (char) => \"%\" + char.charCodeAt(0).toString(16).toUpperCase(),\r\n );\r\n\r\n return `${type}; filename=\"${ascii}\"; filename*=UTF-8''${encoded}`;\r\n }\r\n\r\n private applyResponseOptions(options: SendBufferOptions, defaultFilename?: string): boolean {\r\n // Set content type if provided\r\n if (options.contentType) {\r\n this.baseResponse.type(options.contentType);\r\n }\r\n\r\n // Set cache headers if specified\r\n if (options.cacheTime) {\r\n const cacheControl = options.immutable\r\n ? `public, max-age=${options.cacheTime}, immutable`\r\n : `public, max-age=${options.cacheTime}`;\r\n this.header(\"Cache-Control\", cacheControl);\r\n this.header(\"Expires\", new Date(Date.now() + options.cacheTime * 1000).toUTCString());\r\n }\r\n\r\n // Set ETag if provided (for conditional requests)\r\n if (options.etag) {\r\n this.header(\"ETag\", options.etag);\r\n\r\n // Check If-None-Match for conditional request\r\n const ifNoneMatch = this.request.header(\"if-none-match\");\r\n if (ifNoneMatch && ifNoneMatch === options.etag) {\r\n this.log(\"Content not modified (ETag match), sending 304\");\r\n this.baseResponse.status(304).send();\r\n return true; // Indicates 304 was sent\r\n }\r\n }\r\n\r\n // Set Content-Disposition if inline or filename is specified\r\n if (options.inline !== undefined || options.filename) {\r\n const disposition = options.inline ? \"inline\" : \"attachment\";\r\n const filename = options.filename || defaultFilename || \"file\";\r\n this.header(\"Content-Disposition\", this.contentDisposition(disposition, filename));\r\n }\r\n\r\n return false; // No 304 sent\r\n }\r\n\r\n /**\r\n * Send a file as a response\r\n */\r\n public async sendFile(filePath: string | StorageFile, options?: number | SendFileOptions) {\r\n if (filePath instanceof StorageFile) {\r\n filePath = filePath.absolutePath!;\r\n }\r\n\r\n this.log(`Sending file: ${filePath}`);\r\n\r\n // Check if file exists first\r\n if (!(await fileExistsAsync(filePath))) {\r\n return this.notFound({\r\n error: \"File Not Found\",\r\n });\r\n }\r\n\r\n try {\r\n // Normalize options to object format\r\n const opts = typeof options === \"number\" ? { cacheTime: options } : options || {};\r\n\r\n // Get file stats for ETag and Last-Modified\r\n const stats = await fs.promises.stat(filePath);\r\n const lastModified = stats.mtime;\r\n\r\n // Generate ETag based on file size and modification time\r\n const etag = `\"${stats.size}-${stats.mtime.getTime()}\"`;\r\n\r\n // Set Last-Modified header\r\n this.header(\"Last-Modified\", lastModified.toUTCString());\r\n this.header(\"ETag\", etag);\r\n\r\n // Set content type\r\n const contentType = this.getFileContentType(filePath);\r\n this.baseResponse.type(contentType);\r\n\r\n // Apply common response options (cache, disposition)\r\n const defaultFilename = path.basename(filePath);\r\n const sent304 = this.applyResponseOptions({ ...opts, etag, contentType }, defaultFilename);\r\n if (sent304) return this.baseResponse;\r\n\r\n // Check conditional request headers\r\n const ifNoneMatch = this.request.header(\"if-none-match\");\r\n const ifModifiedSince = this.request.header(\"if-modified-since\");\r\n\r\n // Handle If-None-Match (ETag validation)\r\n if (ifNoneMatch && ifNoneMatch === etag) {\r\n this.log(\"File not modified (ETag match), sending 304\");\r\n return this.baseResponse.status(304).send();\r\n }\r\n\r\n // Handle If-Modified-Since (Last-Modified validation)\r\n if (ifModifiedSince) {\r\n const modifiedSinceDate = new Date(ifModifiedSince);\r\n if (lastModified.getTime() <= modifiedSinceDate.getTime()) {\r\n this.log(\"File not modified (Last-Modified check), sending 304\");\r\n return this.baseResponse.status(304).send();\r\n }\r\n }\r\n\r\n // Use streaming for efficient file sending\r\n const stream = fs.createReadStream(filePath);\r\n\r\n // Handle stream errors\r\n stream.on(\"error\", (error) => {\r\n this.log(`Error reading file: ${error.message}`, \"error\");\r\n if (!this.baseResponse.sent) {\r\n this.serverError({\r\n error: \"Error reading file\",\r\n message: error.message,\r\n });\r\n }\r\n });\r\n\r\n // Send the stream (endTime will be set by finish event listener)\r\n return this.baseResponse.send(stream);\r\n } catch (error: any) {\r\n this.log(`Error sending file: ${error.message}`, \"error\");\r\n return this.serverError({\r\n error: \"Error sending file\",\r\n message: error.message,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Send buffer as a response\r\n * Useful for dynamically generated content (e.g., resized images, generated PDFs)\r\n */\r\n public sendBuffer(buffer: Buffer, options?: number | SendBufferOptions) {\r\n this.log(\"Sending buffer\");\r\n\r\n // Normalize options to object format\r\n const opts = typeof options === \"number\" ? { cacheTime: options } : options || {};\r\n\r\n // Apply common response options (cache, disposition, etag)\r\n const sent304 = this.applyResponseOptions(opts);\r\n if (sent304) return this.baseResponse;\r\n\r\n // Note: endTime is set in the main send() method for non-streaming responses\r\n return this.baseResponse.send(buffer);\r\n }\r\n\r\n /**\r\n * Send an Image instance as a response\r\n * Automatically detects image format and sets content type\r\n */\r\n public async sendImage(\r\n image: any, // Type as 'any' to avoid circular dependency with Image class\r\n options?: number | (Omit<SendBufferOptions, \"contentType\"> & { contentType?: string }),\r\n ) {\r\n this.log(\"Sending image\");\r\n\r\n // Normalize options to object format\r\n const opts = typeof options === \"number\" ? { cacheTime: options } : options || {};\r\n\r\n // Get image metadata to determine format\r\n const metadata = await image.metadata();\r\n const format = metadata.format || \"jpeg\";\r\n\r\n // Convert image to buffer\r\n const buffer = await image.toBuffer();\r\n\r\n // Auto-set content type if not provided\r\n const contentType = opts.contentType || `image/${format}`;\r\n\r\n // Auto-generate ETag if not provided\r\n // Format: \"format-widthxheight-size\" (e.g., \"jpeg-800x600-45231\")\r\n // This catches changes in dimensions, quality, filters, and format\r\n if (!opts.etag) {\r\n const width = metadata.width || 0;\r\n const height = metadata.height || 0;\r\n opts.etag = `\"${format}-${width}x${height}-${buffer.length}\"`;\r\n }\r\n\r\n // Apply common response options with auto-detected content type\r\n const sent304 = this.applyResponseOptions({ ...opts, contentType });\r\n if (sent304) return this.baseResponse;\r\n\r\n // Note: endTime is set in the main send() method for non-streaming responses\r\n return this.baseResponse.send(buffer);\r\n }\r\n\r\n /**\r\n * Send file and cache it\r\n * Cache time in seconds\r\n * Cache time will be one year\r\n */\r\n public sendCachedFile(path: string | StorageFile, cacheTime = 31536000) {\r\n return this.sendFile(path, cacheTime);\r\n }\r\n\r\n /**\r\n * Download the given file path\r\n */\r\n public download(path: string, filename?: string) {\r\n return this.downloadFile(path, filename);\r\n }\r\n\r\n /**\r\n * Download the given file path\r\n */\r\n public async downloadFile(filePath: string, filename?: string) {\r\n // Check if file exists first\r\n if (!(await fileExistsAsync(filePath))) {\r\n return this.notFound({\r\n error: \"File Not Found\",\r\n });\r\n }\r\n\r\n try {\r\n if (!filename) {\r\n filename = path.basename(filePath);\r\n }\r\n\r\n this.baseResponse.header(\r\n \"Content-Disposition\",\r\n this.contentDisposition(\"attachment\", filename),\r\n );\r\n\r\n // this.baseResponse.header(\"Content-Type\", this.getFileContentType(filePath));\r\n this.baseResponse.header(\"Content-Type\", \"application/octet-stream\");\r\n\r\n const stream = fs.createReadStream(filePath);\r\n\r\n // Handle stream errors\r\n stream.on(\"error\", (error) => {\r\n this.log(`Error reading file for download: ${error.message}`, \"error\");\r\n if (!this.baseResponse.sent) {\r\n this.serverError({\r\n error: \"Error reading file\",\r\n message: error.message,\r\n });\r\n }\r\n });\r\n\r\n // Send the stream (endTime will be set by finish event listener)\r\n return this.baseResponse.send(stream);\r\n } catch (error: any) {\r\n this.log(`Error downloading file: ${error.message}`, \"error\");\r\n return this.serverError({\r\n error: \"Error downloading file\",\r\n message: error.message,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Get content type of the given path\r\n */\r\n public getFileContentType(filePath: string) {\r\n const type = mime.getType(filePath) || \"application/octet-stream\";\r\n return type;\r\n }\r\n\r\n /**\r\n * Mark the response as failed\r\n */\r\n public failedSchema(result: ValidationResult) {\r\n const { errors, inputKey, inputError, status } = config.get(\"validation.response\", {\r\n errors: \"errors\",\r\n inputKey: \"input\",\r\n inputError: \"error\",\r\n status: 400,\r\n });\r\n\r\n log.error(\"request\", \"validation\", `${this.request.id} - Validation failed`);\r\n\r\n return this.send(\r\n {\r\n [errors]: result.errors.map((error) => ({\r\n [inputKey]: error.input,\r\n [inputError]: error.error,\r\n })),\r\n },\r\n status,\r\n );\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;AA6CA,IAAY,iBAAL;CACL;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;;AACF;;;;;;;;;;;AA8BA,SAAS,uBAA+C;CACtD,OAAO;EACL,UAAU;EACV,UAAU;EACV,QAAQ,CAAC,YAAY;CACvB;AACF;AAEA,IAAa,WAAb,MAAa,SAAS;;2BA2BU;gCAeX,IAAI,IAAmB;;;;;CAY1C,IAAW,MAAM;EACf,OAAO,KAAK,aAAa;CAC3B;;;;CAKA,IAAW,OAAO;EAChB,OAAO,KAAK;CACd;;;;CAKA,IAAW,KAAK,MAAW;EACzB,KAAK,cAAc;CACrB;;;;CAKA,AAAO,UAAU,UAAe;EAC9B,KAAK,OAAO,IAAI,WAAW,CAAC,GAAI,KAAK,OAAO,IAAI,SAAS,KAAK,CAAC,GAAI,QAAQ,CAAC;EAE5E,OAAO;CACT;;;;CAKA,AAAO,OAAO,UAAe;EAC3B,KAAK,OAAO,IAAI,QAAQ,CAAC,GAAI,KAAK,OAAO,IAAI,MAAM,KAAK,CAAC,GAAI,QAAQ,CAAC;EAEtE,OAAO;CACT;;;;CAKA,AAAO,YAAY,UAAwB;EACzC,KAAK,eAAe;EAIpB,KAAK,aAAa,IAAI,KAAK,gBAAgB;GACzC,KAAK,QAAQ,UAAU,KAAK,IAAI;EAClC,CAAC;EAED,OAAO;CACT;;;;CAKA,AAAO,QAAQ;EACb,KAAK,QAAQ,CAAC;EACd,KAAK,cAAc;EACnB,KAAK,oBAAoB;CAC3B;;;;CAKA,AAAO,SAAS,OAAc;EAC5B,KAAK,QAAQ;EAEb,OAAO;CACT;;;;CAKA,IAAW,cAAc;EACvB,OAAO,KAAK,aAAa,UAAU,cAAc;CACnD;;;;CAKA,AAAO,eAAe,aAAqB;EACzC,KAAK,aAAa,OAAO,gBAAgB,WAAW;EAEpD,OAAO;CACT;;;;CAKA,IAAW,aAAqB;EAC9B,OAAO,KAAK,qBAAqB,KAAK,aAAa;CACrD;;;;CAKA,IAAW,OAAO;EAChB,OAAO,KAAK,qBAAqB,OAAO,KAAK,oBAAoB;CACnE;;;;CAKA,IAAW,OAAO;EAChB,OAAO,KAAK,aAAa;CAC3B;;;;CAKA,OAAc,GACZ,OACA,UACmB;EACnB,OAAO,OAAO,UAAU,YAAY,SAAS,QAAQ;CACvD;;;;CAKA,aAAuB,QAAQ,OAAsB,GAAG,MAAa;EAEnE,OAAO,IAAI,SAAS,YAAY;GAC9B,WAAW,YAAY;IACrB,MAAM,OAAO,gBAAgB,YAAY,SAAS,GAAG,IAAI;IACzD,QAAQ,IAAI;GACd,GAAG,CAAC;EACN,CAAC;CACH;;;;CAKA,MAAgB,YAAY;EAC1B,OAAO,MAAM,KAAK,MAAM,KAAK,WAAW;CAC1C;;;;CAKA,MAAa,MAAM,OAA0B;EAE3C,IAAI,CAAC,SAAS,SAAS,KAAK,GAAG,OAAO;EAGtC,IAAI,MAAM,QAAQ;GAChB,MAAM,UAAU,KAAK;GACrB,OAAO,MAAM,MAAM,OAAO;EAC5B;EAGA,IAAI,WAAW,KAAK,GAAG;GACrB,MAAM,SAAS,MAAM,KAAK,KAAK;GAE/B,OAAO,QAAQ,IACb,OAAO,IAAI,OAAO,SAAc;IAC9B,OAAO,MAAM,KAAK,MAAM,IAAI;GAC9B,CAAC,CACH;EACF;EAGA,IAAI,CAAC,cAAc,KAAK,GACtB,OAAO;EAIT,KAAK,MAAM,OAAO,OAAO;GACvB,MAAM,WAAW,MAAM;GAEvB,MAAM,OAAO,MAAM,KAAK,MAAM,QAAQ;EACxC;EAEA,OAAO;CACT;;;;CAKA,AAAO,IAAI,SAAiB,QAAkB,QAAQ;EACpD,IAAI,CAAC,OAAO,IAAI,UAAU,GAAG;EAE7B,IAAI,IAAI;GACN,QAAQ;GACR,QAAQ,KAAK,MAAM,SAAS,MAAM,KAAK,MAAM,KAAK,QAAQ,MAAM,EAAE,IAAI,IAAI,KAAK,QAAQ;GACvF;GACA,MAAM;GACN,SAAS;IACP,SAAS,KAAK;IACd,UAAU;GACZ;EACF,CAAC;CACH;;;;CAKA,IAAW,SAAS;EAClB,OAAO,KAAK,UAAU,cAAc,MAAM;CAC5C;;;;;;;CAQA,MAAa,KAAK,MAAY,YAAqB,gBAAgB,MAAyB;EAO1F,IAAI,KAAK,aAAa,MAAM;GAC1B,IAAI,MACF,YACA,QACA,mDAAmD,KAAK,SAAS,MAAM,UAAU,4BACnF;GAEA,OAAO;EACT;EAEA,IAAI,YACF,KAAK,oBAAoB;EAG3B,IAAI,SAAS,MAAM,OAAO;EAE1B,IAAI,MACF,KAAK,cAAc;EAGrB,IAAI,CAAC,KAAK,mBACR,KAAK,oBAAoB;EAG3B,KAAK,IAAI,kBAAkB;EAK3B,IAAI,MAAM,QAAQ,KAAK,WAAW,KAAK,cAAc,KAAK,WAAW,GACnE;OAAI,CAAC,KAAK,aAAa,UAAU,cAAc,GAC7C,KAAK,eAAe,kBAAkB;EACxC;EAGF,IAAI,eAAe;GACjB,MAAM,SAAS,QAAQ,WAAW,IAAI;GAEtC,KAAK,MAAM,YAAY,KAAK,OAAO,IAAI,SAAS,KAAK,CAAC,GACpD,MAAM,SAAS,IAAI;GAGrB,IAAI,KAAK,QAAQ;IACf,MAAM,SAAS,QAAQ,eAAe,IAAI;IAC1C,KAAK,MAAM,YAAY,KAAK,OAAO,IAAI,aAAa,KAAK,CAAC,GACxD,MAAM,SAAS,IAAI;IAGrB,IAAI,KAAK,MAAM;KACb,MAAM,SAAS,QAAQ,sBAAsB,IAAI;KACjD,KAAK,MAAM,YAAY,KAAK,OAAO,IAAI,oBAAoB,KAAK,CAAC,GAC/D,MAAM,SAAS,IAAI;IAEvB;GACF;EACF;EAGA,IAAI,OAAO,KAAK,gBAAgB,UAC9B,KAAK,aAAa,MAAM,KAAK,UAAU;OAEvC,KAAK,aAAa;EAIpB,KAAK,aAAa,OAAO,KAAK,iBAAiB;EAG/C,MAAM,KAAK,aAAa,KAAK,KAAK,UAAU;EAE5C,KAAK,IAAI,eAAe;EAExB,IAAI,eAAe;GAEjB,SAAS,QAAQ,QAAQ,IAAI;GAE7B,KAAK,MAAM,YAAY,KAAK,OAAO,IAAI,MAAM,KAAK,CAAC,GACjD,SAAS,IAAI;GAIf,IAAI,KAAK,qBAAqB,OAAO,KAAK,oBAAoB,KAC5D,SAAS,QAAQ,WAAW,IAAI;GAIlC,IAAI,KAAK,sBAAsB,KAC7B,SAAS,QAAQ,iBAAiB,IAAI;GAIxC,IAAI,KAAK,sBAAsB,KAC7B,SAAS,QAAQ,cAAc,IAAI;GAIrC,IAAI,KAAK,sBAAsB,KAC7B,SAAS,QAAQ,gBAAgB,IAAI;GAIvC,IAAI,KAAK,sBAAsB,KAC7B,SAAS,QAAQ,aAAa,IAAI;GAIpC,IAAI,KAAK,sBAAsB,KAC7B,SAAS,QAAQ,YAAY,IAAI;GAInC,IAAI,KAAK,sBAAsB,KAC7B,SAAS,QAAQ,mBAAmB,IAAI;GAI1C,IAAI,KAAK,sBAAsB,KAC7B,SAAS,QAAQ,aAAa,IAAI;GAIpC,IAAI,KAAK,sBAAsB,KAC7B,SAAS,QAAQ,eAAe,IAAI;GAItC,IAAI,KAAK,qBAAqB,KAC5B,SAAS,QAAQ,SAAS,IAAI;EAElC;EAEA,OAAO;CACT;;;;;;;;;;;;;;;;;;;;CAqBA,AAAO,OAAO,QAKQ;EACpB,KAAK,cAAc,OAAO,MAAM;EAEhC,IAAI,OAAO,aACT,KAAK,eAAe,OAAO,WAAW;EAGxC,IAAI,OAAO,SACT,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,OAAO,OAAO,GACvD,KAAK,OAAO,MAAM,KAAK;EAI3B,OAAO,KAAK,KAAK,OAAO,IAAI;CAC9B;;;;CAKA,AAAO,KAAK,MAAc,YAAqB;EAC7C,OAAO,KAAK,eAAe,WAAW,CAAC,CAAC,KAAK,MAAM,UAAU;CAC/D;;;;CAKA,AAAO,OAAO,SAAmD,SAAS,KAAK;EAC7E,OAAO,KAAK,cAAc,MAAM,CAAC,CAAC,KAAK,YAAY,OAAO,CAAC;CAC7D;;;;CAKA,AAAO,IAAI,MAAc,YAAqB;EAC5C,OAAO,KAAK,eAAe,UAAU,CAAC,CAAC,KAAK,MAAM,UAAU;CAC9D;;;;CAKA,AAAO,KAAK,MAAc,YAAqB;EAC7C,OAAO,KAAK,eAAe,YAAY,CAAC,CAAC,KAAK,MAAM,UAAU;CAChE;;;;;;;;;;;;;;;;;;;;CAqBA,AAAO,OAAO,cAAc,cAAwC;EAElE,KAAK,eAAe,WAAW;EAC/B,KAAK,OAAO,qBAAqB,SAAS;EAC1C,KAAK,OAAO,iBAAiB,UAAU;EACvC,KAAK,OAAO,cAAc,YAAY;EACtC,KAAK,OAAO,0BAA0B,SAAS;EAG/C,SAAS,QAAQ,WAAW,IAAI;EAChC,KAAK,MAAM,YAAY,KAAK,OAAO,IAAI,SAAS,KAAK,CAAC,GACpD,SAAS,IAAI;EAGf,KAAK,IAAI,iBAAiB;EAG1B,IAAI,UAAU;EACd,MAAM,SAAgB,CAAC;EAKvB,KAAK,aAAa,IAAI,UAAU,KAAK,YAAY,KAAK,WAAW,CAAQ;EAEzE,OAAO;;;;;GAKL,OAAO,SAAc;IACnB,IAAI,SACF,MAAM,IAAI,MAAM,4CAA4C;IAG9D,KAAK,aAAa,IAAI,MAAM,IAAI;IAEhC,OAAO;GACT;;;;;GAMA,SAAS,YAAuB;IAC9B,IAAI,SACF,MAAM,IAAI,MAAM,yCAAyC;IAG3D,MAAM,OAAO,YAAY,OAAO;IAChC,OAAO,KAAK,IAAI;IAChB,KAAK,aAAa,IAAI,MAAM,IAAI;IAEhC,OAAO;GACT;;;;GAKA,WAAW;IACT,IAAI,SACF,OAAO;IAGT,UAAU;IAGV,KAAK,cAAc;IACnB,KAAK,aAAa;IAGlB,KAAK,aAAa,IAAI,IAAI;IAE1B,KAAK,IAAI,cAAc;IAGvB,SAAS,QAAQ,QAAQ,IAAI;IAC7B,KAAK,MAAM,YAAY,KAAK,OAAO,IAAI,MAAM,KAAK,CAAC,GACjD,SAAS,IAAI;IAIf,IAAI,KAAK,MACP,SAAS,QAAQ,WAAW,IAAI;IAIlC,IAAI,KAAK,sBAAsB,KAC7B,SAAS,QAAQ,iBAAiB,IAAI;IAGxC,OAAO;GACT;;;;GAKA,IAAI,QAAQ;IACV,OAAO;GACT;EACF;CACF;;;;;;;;;;;;;;;;;;;;;;;;;CA0BA,AAAO,MAA6B;EAElC,KAAK,eAAe,mBAAmB;EACvC,KAAK,OAAO,iBAAiB,qCAAqC;EAClE,KAAK,OAAO,cAAc,YAAY;EACtC,KAAK,OAAO,qBAAqB,IAAI;EAGrC,SAAS,QAAQ,WAAW,IAAI;EAChC,KAAK,MAAM,YAAY,KAAK,OAAO,IAAI,SAAS,KAAK,CAAC,GACpD,SAAS,IAAI;EAGf,KAAK,IAAI,qBAAqB;EAG9B,IAAI,UAAU;EACd,MAAM,SAAgB,CAAC;EACvB,MAAM,qBAAwC,CAAC;EAG/C,KAAK,aAAa,IAAI,UAAU,KAAK,YAAY,KAAK,WAAW,CAAQ;EAIzE,KAAK,aAAa,IAAI,GAAG,eAAe;GACtC,IAAI,CAAC,SAAS;IACZ,UAAU;IACV,KAAK,IAAI,yBAAyB;IAClC,KAAK,MAAM,WAAW,oBACpB,QAAQ;GAEZ;EACF,CAAC;EAED,MAAM,aAAoC;;;;;;;GAOxC,OAAO,OAAe,MAAW,OAAuC;IAGtE,IAAI,SAAS,OAAO;IAEpB,IAAI,UAAU;IACd,IAAI,IAAI,WAAW,OAAO,GAAG;IAC7B,WAAW,UAAU,MAAM;IAC3B,WAAW,SAAS,KAAK,UAAU,IAAI,EAAE;IAEzC,OAAO,KAAK;KAAE;KAAO;KAAM;IAAG,CAAC;IAC/B,KAAK,aAAa,IAAI,MAAM,OAAO;IAEnC,OAAO;GACT;;;;;;GAOA,UAAU,SAAwC;IAEhD,IAAI,SAAS,OAAO;IAEpB,KAAK,aAAa,IAAI,MAAM,KAAK,KAAK,KAAK;IAE3C,OAAO;GACT;;;;GAKA,WAAkC;IAChC,IAAI,SAAS,OAAO;IAEpB,UAAU;IAGV,KAAK,cAAc;IACnB,KAAK,aAAa;IAGlB,KAAK,aAAa,IAAI,IAAI;IAE1B,KAAK,IAAI,kBAAkB;IAG3B,SAAS,QAAQ,QAAQ,IAAI;IAC7B,KAAK,MAAM,YAAY,KAAK,OAAO,IAAI,MAAM,KAAK,CAAC,GACjD,SAAS,IAAI;IAIf,IAAI,KAAK,MACP,SAAS,QAAQ,WAAW,IAAI;IAGlC,OAAO;GACT;;;;;;;;;;;;;GAcA,eAAe,YAA+C;IAC5D,mBAAmB,KAAK,OAAO;IAC/B,OAAO;GACT;;;;GAKA,IAAI,QAAQ;IACV,OAAO;GACT;EACF;EAEA,OAAO;CACT;;;;CAKA,AAAO,cAAc,YAAoB;EACvC,KAAK,oBAAoB;EAEzB,OAAO;CACT;;;;CAKA,AAAO,SAAS,KAAa,aAAa,KAAK;EAC7C,KAAK,aAAa,SAAS,KAAK,UAAU;EAE1C,OAAO;CACT;;;;CAKA,AAAO,kBAAkB,KAAa;EACpC,KAAK,aAAa,SAAS,KAAK,GAAG;EAEnC,OAAO;CACT;;;;CAKA,AAAO,kBAAkB;EACvB,OAAO,KAAK,aAAa;CAC3B;;;;CAKA,AAAO,aAAa,KAAa;EAC/B,KAAK,aAAa,aAAa,GAAG;EAElC,OAAO;CACT;;;;CAKA,AAAO,UAAU,KAAa;EAC5B,OAAO,KAAK,aAAa,UAAU,GAAG;CACxC;;;;CAKA,AAAO,aAAa;EAClB,OAAO,KAAK,aAAa,WAAW;CACtC;;;;CAKA,AAAO,QAAQ,SAAiC;EAC9C,KAAK,aAAa,QAAQ,OAAO;EAEjC,OAAO;CACT;;;;CAKA,AAAO,OAAO,KAAa,OAAY;EACrC,KAAK,aAAa,OAAO,KAAK,KAAK;EAEnC,OAAO;CACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCA,AAAO,OAAO,MAAc,OAAoB,UAAyB,CAAC,GAAG;EAC3E,MAAM,EAAE,KAAK,GAAG,kBAAkB;EAClC,MAAM,iBAAiB,OAAO,IAAI,wBAAwB,CAAC,CAAC;EAC5D,MAAM,kBAAkB,MAAM,OAAO,KAAK,IAAI,KAAK,UAAU,KAAK;EAElE,KAAK,aAAa,UAAU,MAAM,iBAAiB;GACjD,GAAG,qBAAqB;GACxB,GAAG;GACH,GAAG;EACL,CAAC;EAED,OAAO;CACT;;;;;;;CAQA,AAAO,YAAY,MAAc,SAAkC;EACjE,MAAM,iBAAiB,OAAO,IAAI,wBAAwB,CAAC,CAAC;EAC5D,KAAK,aAAa,YAAY,MAAM;GAAE,GAAG;GAAgB,GAAG;EAAQ,CAAC;EAErE,OAAO;CACT;;;;CAKA,AAAO,UAAU,KAAa,OAAY;EACxC,OAAO,KAAK,OAAO,KAAK,KAAK;CAC/B;;;;CAKA,AAAO,YAAY,MAAW;EAC5B,OAAO,KAAK,KAAK,MAAM,GAAG;CAC5B;;;;CAKA,AAAO,UACL,OAAY,EACV,OAAO,yDACT,GACA;EACA,OAAO,KAAK,KAAK,MAAM,GAAG;CAC5B;;;;CAKA,AAAO,mBAAmB,MAAW;EACnC,OAAO,KAAK,KAAK,MAAM,GAAG;CAC5B;;;;CAKA,AAAO,aACL,OAAY,EACV,OAAO,eACT,GACA;EACA,OAAO,KAAK,KAAK,MAAM,GAAG;CAC5B;;;;CAKA,AAAO,SACL,OAAY,EACV,OAAO,WACT,GACA;EACA,OAAO,KAAK,KAAK,MAAM,GAAG;CAC5B;;;;CAKA,AAAO,WAAW,MAAW;EAC3B,OAAO,KAAK,KAAK,MAAM,GAAG;CAC5B;;;;CAKA,AAAO,gBAAgB,MAAW;EAChC,OAAO,KAAK,KAAK,MAAM,GAAG;CAC5B;;;;CAKA,AAAO,cAAc,MAAW;EAC9B,OAAO,KAAK,KAAK,MAAM,GAAG;CAC5B;;;;CAKA,AAAO,QAAQ,OAAY,EAAE,SAAS,KAAK,GAAG;EAC5C,OAAO,KAAK,KAAK,IAAI;CACvB;;;;CAKA,AAAO,YAAY;EACjB,OAAO,KAAK,aAAa,OAAO,GAAG,CAAC,CAAC,KAAK;CAC5C;;;;;CAMA,AAAO,SAAS,OAAY,EAAE,SAAS,kCAAkC,GAAG;EAC1E,OAAO,KAAK,KAAK,MAAM,GAAG;CAC5B;;;;CAKA,AAAO,SAAS,OAAY,EAAE,OAAO,oBAAoB,GAAG;EAC1D,OAAO,KAAK,KAAK,MAAM,GAAG;CAC5B;;;;CAKA,AAAO,gBAAgB,MAAW;EAChC,OAAO,KAAK,KAAK,MAAM,GAAG;CAC5B;;;;;CAMA,AAAO,oBAAoB,MAAW;EACpC,OAAO,KAAK,KAAK,MAAM,GAAG;CAC5B;;;;;;;;;;;;;;;;CAiBA,AAAQ,mBAAmB,MAA+B,SAAyB;EAEjF,MAAM,QAAQ,WAAW,OAAM,CAAE,QAAQ,WAAW,EAAE;EAItD,MAAM,QACJ,KACG,QAAQ,UAAU,GAAG,CAAC,CACtB,QAAQ,iBAAiB,GAAG,CAAC,CAC7B,KAAK,KAAK;EAGf,IAAI,CAAC,eAAe,KAAK,IAAI,GAC3B,OAAO,GAAG,KAAK,cAAc,MAAM;EAUrC,OAAO,GAAG,KAAK,cAAc,MAAM,sBALnB,mBAAmB,IAAI,CAAC,CAAC,QACvC,YACC,SAAS,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,YAAY,CAGC;CACjE;CAEA,AAAQ,qBAAqB,SAA4B,iBAAmC;EAE1F,IAAI,QAAQ,aACV,KAAK,aAAa,KAAK,QAAQ,WAAW;EAI5C,IAAI,QAAQ,WAAW;GACrB,MAAM,eAAe,QAAQ,YACzB,mBAAmB,QAAQ,UAAU,eACrC,mBAAmB,QAAQ;GAC/B,KAAK,OAAO,iBAAiB,YAAY;GACzC,KAAK,OAAO,WAAW,IAAI,KAAK,KAAK,IAAI,IAAI,QAAQ,YAAY,GAAI,CAAC,CAAC,YAAY,CAAC;EACtF;EAGA,IAAI,QAAQ,MAAM;GAChB,KAAK,OAAO,QAAQ,QAAQ,IAAI;GAGhC,MAAM,cAAc,KAAK,QAAQ,OAAO,eAAe;GACvD,IAAI,eAAe,gBAAgB,QAAQ,MAAM;IAC/C,KAAK,IAAI,gDAAgD;IACzD,KAAK,aAAa,OAAO,GAAG,CAAC,CAAC,KAAK;IACnC,OAAO;GACT;EACF;EAGA,IAAI,QAAQ,WAAW,UAAa,QAAQ,UAAU;GACpD,MAAM,cAAc,QAAQ,SAAS,WAAW;GAChD,MAAM,WAAW,QAAQ,YAAY,mBAAmB;GACxD,KAAK,OAAO,uBAAuB,KAAK,mBAAmB,aAAa,QAAQ,CAAC;EACnF;EAEA,OAAO;CACT;;;;CAKA,MAAa,SAAS,UAAgC,SAAoC;EACxF,IAAI,oBAAoB,aACtB,WAAW,SAAS;EAGtB,KAAK,IAAI,iBAAiB,UAAU;EAGpC,IAAI,CAAE,MAAM,gBAAgB,QAAQ,GAClC,OAAO,KAAK,SAAS,EACnB,OAAO,iBACT,CAAC;EAGH,IAAI;GAEF,MAAM,OAAO,OAAO,YAAY,WAAW,EAAE,WAAW,QAAQ,IAAI,WAAW,CAAC;GAGhF,MAAM,QAAQ,MAAM,GAAG,SAAS,KAAK,QAAQ;GAC7C,MAAM,eAAe,MAAM;GAG3B,MAAM,OAAO,IAAI,MAAM,KAAK,GAAG,MAAM,MAAM,QAAQ,EAAE;GAGrD,KAAK,OAAO,iBAAiB,aAAa,YAAY,CAAC;GACvD,KAAK,OAAO,QAAQ,IAAI;GAGxB,MAAM,cAAc,KAAK,mBAAmB,QAAQ;GACpD,KAAK,aAAa,KAAK,WAAW;GAGlC,MAAM,kBAAkB,KAAK,SAAS,QAAQ;GAE9C,IADgB,KAAK,qBAAqB;IAAE,GAAG;IAAM;IAAM;GAAY,GAAG,eAChE,GAAG,OAAO,KAAK;GAGzB,MAAM,cAAc,KAAK,QAAQ,OAAO,eAAe;GACvD,MAAM,kBAAkB,KAAK,QAAQ,OAAO,mBAAmB;GAG/D,IAAI,eAAe,gBAAgB,MAAM;IACvC,KAAK,IAAI,6CAA6C;IACtD,OAAO,KAAK,aAAa,OAAO,GAAG,CAAC,CAAC,KAAK;GAC5C;GAGA,IAAI,iBAAiB;IACnB,MAAM,oBAAoB,IAAI,KAAK,eAAe;IAClD,IAAI,aAAa,QAAQ,KAAK,kBAAkB,QAAQ,GAAG;KACzD,KAAK,IAAI,sDAAsD;KAC/D,OAAO,KAAK,aAAa,OAAO,GAAG,CAAC,CAAC,KAAK;IAC5C;GACF;GAGA,MAAM,SAAS,GAAG,iBAAiB,QAAQ;GAG3C,OAAO,GAAG,UAAU,UAAU;IAC5B,KAAK,IAAI,uBAAuB,MAAM,WAAW,OAAO;IACxD,IAAI,CAAC,KAAK,aAAa,MACrB,KAAK,YAAY;KACf,OAAO;KACP,SAAS,MAAM;IACjB,CAAC;GAEL,CAAC;GAGD,OAAO,KAAK,aAAa,KAAK,MAAM;EACtC,SAAS,OAAY;GACnB,KAAK,IAAI,uBAAuB,MAAM,WAAW,OAAO;GACxD,OAAO,KAAK,YAAY;IACtB,OAAO;IACP,SAAS,MAAM;GACjB,CAAC;EACH;CACF;;;;;CAMA,AAAO,WAAW,QAAgB,SAAsC;EACtE,KAAK,IAAI,gBAAgB;EAGzB,MAAM,OAAO,OAAO,YAAY,WAAW,EAAE,WAAW,QAAQ,IAAI,WAAW,CAAC;EAIhF,IADgB,KAAK,qBAAqB,IAChC,GAAG,OAAO,KAAK;EAGzB,OAAO,KAAK,aAAa,KAAK,MAAM;CACtC;;;;;CAMA,MAAa,UACX,OACA,SACA;EACA,KAAK,IAAI,eAAe;EAGxB,MAAM,OAAO,OAAO,YAAY,WAAW,EAAE,WAAW,QAAQ,IAAI,WAAW,CAAC;EAGhF,MAAM,WAAW,MAAM,MAAM,SAAS;EACtC,MAAM,SAAS,SAAS,UAAU;EAGlC,MAAM,SAAS,MAAM,MAAM,SAAS;EAGpC,MAAM,cAAc,KAAK,eAAe,SAAS;EAKjD,IAAI,CAAC,KAAK,MAGR,KAAK,OAAO,IAAI,OAAO,GAFT,SAAS,SAAS,EAEA,GADjB,SAAS,UAAU,EACQ,GAAG,OAAO,OAAO;EAK7D,IADgB,KAAK,qBAAqB;GAAE,GAAG;GAAM;EAAY,CACvD,GAAG,OAAO,KAAK;EAGzB,OAAO,KAAK,aAAa,KAAK,MAAM;CACtC;;;;;;CAOA,AAAO,eAAe,MAA4B,YAAY,SAAU;EACtE,OAAO,KAAK,SAAS,MAAM,SAAS;CACtC;;;;CAKA,AAAO,SAAS,MAAc,UAAmB;EAC/C,OAAO,KAAK,aAAa,MAAM,QAAQ;CACzC;;;;CAKA,MAAa,aAAa,UAAkB,UAAmB;EAE7D,IAAI,CAAE,MAAM,gBAAgB,QAAQ,GAClC,OAAO,KAAK,SAAS,EACnB,OAAO,iBACT,CAAC;EAGH,IAAI;GACF,IAAI,CAAC,UACH,WAAW,KAAK,SAAS,QAAQ;GAGnC,KAAK,aAAa,OAChB,uBACA,KAAK,mBAAmB,cAAc,QAAQ,CAChD;GAGA,KAAK,aAAa,OAAO,gBAAgB,0BAA0B;GAEnE,MAAM,SAAS,GAAG,iBAAiB,QAAQ;GAG3C,OAAO,GAAG,UAAU,UAAU;IAC5B,KAAK,IAAI,oCAAoC,MAAM,WAAW,OAAO;IACrE,IAAI,CAAC,KAAK,aAAa,MACrB,KAAK,YAAY;KACf,OAAO;KACP,SAAS,MAAM;IACjB,CAAC;GAEL,CAAC;GAGD,OAAO,KAAK,aAAa,KAAK,MAAM;EACtC,SAAS,OAAY;GACnB,KAAK,IAAI,2BAA2B,MAAM,WAAW,OAAO;GAC5D,OAAO,KAAK,YAAY;IACtB,OAAO;IACP,SAAS,MAAM;GACjB,CAAC;EACH;CACF;;;;CAKA,AAAO,mBAAmB,UAAkB;EAE1C,OADa,KAAK,QAAQ,QAAQ,KAAK;CAEzC;;;;CAKA,AAAO,aAAa,QAA0B;EAC5C,MAAM,EAAE,QAAQ,UAAU,YAAY,WAAW,OAAO,IAAI,uBAAuB;GACjF,QAAQ;GACR,UAAU;GACV,YAAY;GACZ,QAAQ;EACV,CAAC;EAED,IAAI,MAAM,WAAW,cAAc,GAAG,KAAK,QAAQ,GAAG,qBAAqB;EAE3E,OAAO,KAAK,KACV,GACG,SAAS,OAAO,OAAO,KAAK,WAAW;IACrC,WAAW,MAAM;IACjB,aAAa,MAAM;EACtB,EAAE,EACJ,GACA,MACF;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"response.mjs","names":[],"sources":["../../../../../../../core/src/http/response.ts"],"sourcesContent":["import type { CookieSerializeOptions } from \"@fastify/cookie\";\r\nimport config from \"@mongez/config\";\r\nimport type { EventSubscription } from \"@mongez/events\";\r\nimport events from \"@mongez/events\";\r\nimport { fileExistsAsync } from \"@warlock.js/fs\";\r\nimport { isIterable, isPlainObject, isScalar } from \"@mongez/supportive-is\";\r\nimport type { LogLevel } from \"@warlock.js/logger\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport type { ValidationResult } from \"@warlock.js/seal\";\r\nimport type { FastifyReply } from \"fastify\";\r\nimport fs from \"fs\";\r\nimport mime from \"mime\";\r\nimport path from \"path\";\r\nimport type React from \"react\";\r\nimport { type ReactNode } from \"react\";\r\nimport { Application } from \"../application/application\";\r\nimport type { Route } from \"../router\";\r\nimport { StorageFile } from \"../storage\";\r\nimport { renderReact } from \"./../react\";\r\nimport type { Request } from \"./request\";\r\nimport type { ResponseEvent, ResponseSSEController, ResponseStreamController } from \"./types\";\r\n\r\ntype CookieValue = string | number | boolean | Record<string, any> | Array<any>;\r\n\r\n/**\r\n * Cookie options accepted by `response.cookie()`.\r\n *\r\n * Extends Fastify's `CookieSerializeOptions` with `raw` — set to `true` to\r\n * skip the default `JSON.stringify` of the value and write it as-is. Use for\r\n * plain-string cookies (session tokens, opaque IDs) that shouldn't be JSON-quoted.\r\n *\r\n * When `raw: true`, non-string values are coerced via `String(value)`. The\r\n * read side (`request.cookie(name)`) tries `JSON.parse` first and falls back\r\n * to the raw string on parse failure, so round-tripping a raw string cookie\r\n * Just Works.\r\n */\r\nexport type CookieOptions = CookieSerializeOptions & {\r\n /**\r\n * Skip JSON.stringify and write the value as-is.\r\n *\r\n * @default false\r\n */\r\n raw?: boolean;\r\n};\r\n\r\nexport enum ResponseStatus {\r\n OK = 200,\r\n CREATED = 201,\r\n ACCEPTED = 202,\r\n MOVED_PERMANENTLY = 301,\r\n FOUND = 302,\r\n SEE_OTHER = 303,\r\n NOT_MODIFIED = 304,\r\n TEMPORARY_REDIRECT = 307,\r\n PERMANENT_REDIRECT = 308,\r\n NO_CONTENT = 204,\r\n BAD_REQUEST = 400,\r\n UNAUTHORIZED = 401,\r\n FORBIDDEN = 403,\r\n NOT_FOUND = 404,\r\n METHOD_NOT_ALLOWED = 405,\r\n CONFLICT = 409,\r\n TOO_MANY_REQUESTS = 429,\r\n INTERNAL_SERVER_ERROR = 500,\r\n SERVICE_UNAVAILABLE = 503,\r\n}\r\n\r\n/**\r\n * Options for sending files\r\n */\r\nexport type SendFileOptions = {\r\n cacheTime?: number;\r\n immutable?: boolean;\r\n inline?: boolean;\r\n filename?: string;\r\n};\r\n\r\n/**\r\n * Options for sending buffers\r\n */\r\nexport type SendBufferOptions = SendFileOptions & {\r\n contentType?: string;\r\n etag?: string;\r\n};\r\n\r\n/**\r\n * The cookie flags every response cookie gets unless something overrides them.\r\n *\r\n * Computed per call rather than hoisted to a constant because `secure` depends\r\n * on the environment, which is not known at module-evaluation time.\r\n *\r\n * `secure` is relaxed in development only: a `Secure` cookie is dropped by the\r\n * browser over plain http, which would silently break every local login. It\r\n * stays on everywhere else, including test and staging.\r\n */\r\nfunction secureCookieDefaults(): CookieSerializeOptions {\r\n return {\r\n httpOnly: true,\r\n sameSite: \"lax\",\r\n secure: !Application.isDevelopment,\r\n };\r\n}\r\n\r\nexport class Response {\r\n /**\r\n * Current route\r\n */\r\n protected route!: Route;\r\n\r\n /**\r\n * Underlying Fastify reply — a public escape hatch to capabilities the\r\n * framework's high-level helpers don't yet cover.\r\n *\r\n * **Prefer framework methods first**: `response.send()`, `response.header()`,\r\n * `response.cookie()`, `response.sendFile()`, `response.stream()`, etc.\r\n * They wire status codes, content-type detection, the event lifecycle, and\r\n * the cache-pattern replay path correctly.\r\n *\r\n * **Reach for `baseResponse` only** when the framework genuinely lacks a\r\n * helper for what you need — and when you do, file an issue so we can add\r\n * it. Streaming and SSE are the precedent here: they bypass `send()`\r\n * deliberately because the framework didn't ship chunked-write support\r\n * natively at the time. Reaching here for non-streaming work means a\r\n * missing helper, not an answer.\r\n */\r\n public baseResponse!: FastifyReply;\r\n\r\n /**\r\n * Current status code\r\n */\r\n protected currentStatusCode = 200;\r\n\r\n /**\r\n * Current response body\r\n */\r\n protected currentBody: any;\r\n\r\n /**\r\n * Request object\r\n */\r\n public request!: Request;\r\n\r\n /**\r\n * Internal events related to this particular response object\r\n */\r\n protected events = new Map<string, any[]>();\r\n\r\n /**\r\n * Parsed body\r\n * This will return the parsed body of the response\r\n * Please note that if this property is called before the response is sent, it will return undefined\r\n */\r\n public parsedBody: any;\r\n\r\n /**\r\n * Get raw response\r\n */\r\n public get raw() {\r\n return this.baseResponse.raw;\r\n }\r\n\r\n /**\r\n * Get Current response body\r\n */\r\n public get body() {\r\n return this.currentBody;\r\n }\r\n\r\n /**\r\n * Set response body\r\n */\r\n public set body(body: any) {\r\n this.currentBody = body;\r\n }\r\n\r\n /**\r\n * Add event on sending response\r\n */\r\n public onSending(callback: any) {\r\n this.events.set(\"sending\", [...(this.events.get(\"sending\") || []), callback]);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Add event on sent response\r\n */\r\n public onSent(callback: any) {\r\n this.events.set(\"sent\", [...(this.events.get(\"sent\") || []), callback]);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Set the Fastify response object\r\n */\r\n public setResponse(response: FastifyReply) {\r\n this.baseResponse = response;\r\n\r\n // Listen to the 'finish' event to track when response is fully sent\r\n // This works for all response types: JSON, streams, buffers, files, etc.\r\n this.baseResponse.raw.once(\"finish\", () => {\r\n this.request.endTime = Date.now();\r\n });\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Reset the response state\r\n */\r\n public reset() {\r\n this.route = {} as Route;\r\n this.currentBody = null;\r\n this.currentStatusCode = 200;\r\n }\r\n\r\n /**\r\n * Set current route\r\n */\r\n public setRoute(route: Route) {\r\n this.route = route;\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get the content type\r\n */\r\n public get contentType() {\r\n return this.baseResponse.getHeader(\"Content-Type\");\r\n }\r\n\r\n /**\r\n * Set the content type\r\n */\r\n public setContentType(contentType: string) {\r\n this.baseResponse.header(\"Content-Type\", contentType);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get the status code\r\n */\r\n public get statusCode(): number {\r\n return this.currentStatusCode ?? this.baseResponse.statusCode;\r\n }\r\n\r\n /**\r\n * Check if response status is ok\r\n */\r\n public get isOk() {\r\n return this.currentStatusCode >= 200 && this.currentStatusCode < 300;\r\n }\r\n\r\n /**\r\n * Check if the response has been sent\r\n */\r\n public get sent() {\r\n return this.baseResponse.sent;\r\n }\r\n\r\n /**\r\n * Add a listener to the response event\r\n */\r\n public static on(\r\n event: ResponseEvent,\r\n listener: (response: Response) => void,\r\n ): EventSubscription {\r\n return events.subscribe(`response.${event}`, listener);\r\n }\r\n\r\n /**\r\n * Trigger the response event\r\n */\r\n protected static async trigger(event: ResponseEvent, ...args: any[]) {\r\n // make a timeout to make sure the request events is executed first\r\n return new Promise((resolve) => {\r\n setTimeout(async () => {\r\n await events.triggerAllAsync(`response.${event}`, ...args);\r\n resolve(true);\r\n }, 0);\r\n });\r\n }\r\n\r\n /**\r\n * Parse body\r\n */\r\n protected async parseBody() {\r\n return await this.parse(this.currentBody);\r\n }\r\n\r\n /**\r\n * Parse the given value\r\n */\r\n public async parse(value: any): Promise<any> {\r\n // if it is a falsy value, return it\r\n if (!value || isScalar(value)) return value;\r\n\r\n // if it has a `toJSON` method, call it and await the result then return it\r\n if (value.toJSON) {\r\n value.request = this.request;\r\n return await value.toJSON();\r\n }\r\n\r\n // if it is iterable, an array or array-like object then parse each item\r\n if (isIterable(value)) {\r\n const values = Array.from(value);\r\n\r\n return Promise.all(\r\n values.map(async (item: any) => {\r\n return await this.parse(item);\r\n }),\r\n );\r\n }\r\n\r\n // if not plain object, then return it\r\n if (!isPlainObject(value)) {\r\n return value;\r\n }\r\n\r\n // loop over the object and check if the value and call `parse` on it\r\n for (const key in value) {\r\n const subValue = value[key];\r\n\r\n value[key] = await this.parse(subValue);\r\n }\r\n\r\n return value;\r\n }\r\n\r\n /**\r\n * Make a log message\r\n */\r\n public log(message: string, level: LogLevel = \"info\") {\r\n if (!config.get(\"http.log\")) return;\r\n\r\n log.log({\r\n module: \"response\",\r\n action: this.route.method + \" \" + this.route.path.replace(\"/*\", \"\") + `:${this.request.id}`,\r\n message,\r\n type: level,\r\n context: {\r\n request: this.request,\r\n response: this,\r\n },\r\n });\r\n }\r\n\r\n /**\r\n * Check if returning response is json\r\n */\r\n public get isJson() {\r\n return this.getHeader(\"Content-Type\") === \"application/json\";\r\n }\r\n\r\n /**\r\n * Send the response\r\n * @param data - Response data\r\n * @param statusCode - HTTP status code\r\n * @param triggerEvents - Whether to trigger response events (default: true)\r\n */\r\n public async send(data?: any, statusCode?: number, triggerEvents = true): Promise<Response> {\r\n // Defensive guard against double-send. The underlying Fastify reply silently\r\n // ignores subsequent sends once `sent === true`, which has historically hidden\r\n // middleware bugs (cache-pattern replay paths that returned `baseResponse.send`\r\n // ended up re-entering `Response.send` with the FastifyReply as the body).\r\n // Surfacing the misuse via `error`-level log makes the bug loud without\r\n // crashing production traffic.\r\n if (this.baseResponse.sent) {\r\n log.error(\r\n \"response\",\r\n \"send\",\r\n `send() called on already-sent response (request:${this.request?.id ?? \"unknown\"}) — likely a middleware bug`,\r\n );\r\n\r\n return this;\r\n }\r\n\r\n if (statusCode) {\r\n this.currentStatusCode = statusCode;\r\n }\r\n\r\n if (data === this) return this;\r\n\r\n if (data) {\r\n this.currentBody = data;\r\n }\r\n\r\n if (!this.currentStatusCode) {\r\n this.currentStatusCode = 200;\r\n }\r\n\r\n this.log(\"Sending response\");\r\n // Auto-pick `application/json` only when no content-type was set by the caller.\r\n // This preserves explicit overrides (e.g. `application/vnd.api+json` from a\r\n // cache replay, `application/problem+json` from an RFC 7807 error response)\r\n // while keeping the convenience default for the common object-body path.\r\n if (Array.isArray(this.currentBody) || isPlainObject(this.currentBody)) {\r\n if (!this.baseResponse.getHeader(\"Content-Type\")) {\r\n this.setContentType(\"application/json\");\r\n }\r\n }\r\n\r\n if (triggerEvents) {\r\n await Response.trigger(\"sending\", this);\r\n\r\n for (const callback of this.events.get(\"sending\") || []) {\r\n await callback(this);\r\n }\r\n\r\n if (this.isJson) {\r\n await Response.trigger(\"sendingJson\", this);\r\n for (const callback of this.events.get(\"sendingJson\") || []) {\r\n await callback(this);\r\n }\r\n\r\n if (this.isOk) {\r\n await Response.trigger(\"sendingSuccessJson\", this);\r\n for (const callback of this.events.get(\"sendingSuccessJson\") || []) {\r\n await callback(this);\r\n }\r\n }\r\n }\r\n }\r\n\r\n // parse the body and make sure it is transformed to sync data instead of async data\r\n if (typeof this.currentBody !== \"string\") {\r\n this.parsedBody = await this.parseBody();\r\n } else {\r\n this.parsedBody = data;\r\n }\r\n\r\n // Set the status first\r\n this.baseResponse.status(this.currentStatusCode);\r\n\r\n // Then send the response with the parsed body\r\n await this.baseResponse.send(this.parsedBody);\r\n\r\n this.log(\"Response sent\");\r\n\r\n if (triggerEvents) {\r\n // trigger the sent event\r\n Response.trigger(\"sent\", this);\r\n\r\n for (const callback of this.events.get(\"sent\") || []) {\r\n callback(this);\r\n }\r\n\r\n // trigger the success event if the status code is 2xx\r\n if (this.currentStatusCode >= 200 && this.currentStatusCode < 300) {\r\n Response.trigger(\"success\", this);\r\n }\r\n\r\n // trigger the successCreate event if the status code is 201\r\n if (this.currentStatusCode === 201) {\r\n Response.trigger(\"successCreate\", this);\r\n }\r\n\r\n // trigger the badRequest event if the status code is 400\r\n if (this.currentStatusCode === 400) {\r\n Response.trigger(\"badRequest\", this);\r\n }\r\n\r\n // trigger the unauthorized event if the status code is 401\r\n if (this.currentStatusCode === 401) {\r\n Response.trigger(\"unauthorized\", this);\r\n }\r\n\r\n // trigger the forbidden event if the status code is 403\r\n if (this.currentStatusCode === 403) {\r\n Response.trigger(\"forbidden\", this);\r\n }\r\n\r\n // trigger the notFound event if the status code is 404\r\n if (this.currentStatusCode === 404) {\r\n Response.trigger(\"notFound\", this);\r\n }\r\n\r\n // trigger the content too large event if the status code is 413\r\n if (this.currentStatusCode === 413) {\r\n Response.trigger(\"contentTooLarge\", this);\r\n }\r\n\r\n // trigger the throttled event if the status code is 429\r\n if (this.currentStatusCode === 429) {\r\n Response.trigger(\"throttled\", this);\r\n }\r\n\r\n // trigger the serverError event if the status code is 500\r\n if (this.currentStatusCode === 500) {\r\n Response.trigger(\"serverError\", this);\r\n }\r\n\r\n // trigger the error event if the status code is 4xx or 5xx\r\n if (this.currentStatusCode >= 400) {\r\n Response.trigger(\"error\", this);\r\n }\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Replay a previously-captured response shape — used by cache-pattern\r\n * middlewares (idempotency, response cache) to send a cached response\r\n * without re-running the controller.\r\n *\r\n * Preserves the cached status code, content-type, and any extra headers,\r\n * then sends the body through the standard `send()` pipeline so the full\r\n * event lifecycle still fires (`sent`, `success`, status-specific events).\r\n * That keeps cross-cutting observers (logger, metrics, audit) consistent\r\n * between fresh and replayed responses.\r\n *\r\n * @example\r\n * // Inside a cache-pattern middleware on HIT:\r\n * return response.header(\"X-Cache\", \"HIT\").replay({\r\n * status: cached.status,\r\n * body: cached.body,\r\n * contentType: cached.contentType,\r\n * });\r\n */\r\n public replay(cached: {\r\n status: number;\r\n body: unknown;\r\n contentType?: string;\r\n headers?: Record<string, string>;\r\n }): Promise<Response> {\r\n this.setStatusCode(cached.status);\r\n\r\n if (cached.contentType) {\r\n this.setContentType(cached.contentType);\r\n }\r\n\r\n if (cached.headers) {\r\n for (const [name, value] of Object.entries(cached.headers)) {\r\n this.header(name, value);\r\n }\r\n }\r\n\r\n return this.send(cached.body);\r\n }\r\n\r\n /**\r\n * Send html response\r\n */\r\n public html(data: string, statusCode?: number) {\r\n return this.setContentType(\"text/html\").send(data, statusCode);\r\n }\r\n\r\n /**\r\n * Render the given react component\r\n */\r\n public render(element: React.ReactElement | React.ComponentType, status = 200) {\r\n return this.setStatusCode(status).html(renderReact(element));\r\n }\r\n\r\n /**\r\n * Send xml response\r\n */\r\n public xml(data: string, statusCode?: number) {\r\n return this.setContentType(\"text/xml\").send(data, statusCode);\r\n }\r\n\r\n /**\r\n * Send plain text response\r\n */\r\n public text(data: string, statusCode?: number) {\r\n return this.setContentType(\"text/plain\").send(data, statusCode);\r\n }\r\n\r\n /**\r\n * Create a streaming response for progressive/chunked data sending\r\n *\r\n * This method allows you to send data in chunks and control when the response ends.\r\n * Perfect for Server-Sent Events (SSE), progressive rendering, or streaming large responses.\r\n *\r\n * @example\r\n * ```ts\r\n * const stream = response.stream(\"text/html\");\r\n * stream.send(\"<html><body>\");\r\n * stream.send(\"<h1>Hello</h1>\");\r\n * stream.render(<MyComponent />);\r\n * stream.send(\"</body></html>\");\r\n * stream.end();\r\n * ```\r\n *\r\n * @param contentType - The content type for the stream (default: \"text/plain\")\r\n * @returns Stream controller with send(), render(), and end() methods\r\n */\r\n public stream(contentType = \"text/plain\"): ResponseStreamController {\r\n // Set headers using the response API\r\n this.setContentType(contentType);\r\n this.header(\"Transfer-Encoding\", \"chunked\");\r\n this.header(\"Cache-Control\", \"no-cache\");\r\n this.header(\"Connection\", \"keep-alive\");\r\n this.header(\"X-Content-Type-Options\", \"nosniff\");\r\n\r\n // Trigger sending events\r\n Response.trigger(\"sending\", this);\r\n for (const callback of this.events.get(\"sending\") || []) {\r\n callback(this);\r\n }\r\n\r\n this.log(\"Starting stream\");\r\n\r\n // Track stream state\r\n let isEnded = false;\r\n const chunks: any[] = [];\r\n\r\n // Write headers to start the stream\r\n // Note: We use raw here because we need chunked encoding control\r\n // This is the only valid use case for bypassing Fastify's abstraction\r\n this.baseResponse.raw.writeHead(this.statusCode, this.getHeaders() as any);\r\n\r\n return {\r\n /**\r\n * Send a chunk of data to the client\r\n * @param data - Data to send (string, Buffer, or any serializable data)\r\n */\r\n send: (data: any) => {\r\n if (isEnded) {\r\n throw new Error(\"Cannot send data: stream has already ended\");\r\n }\r\n\r\n this.baseResponse.raw.write(data);\r\n\r\n return this;\r\n },\r\n\r\n /**\r\n * Render a React component and send it as a chunk\r\n * @param element - React element or component to render\r\n */\r\n render: (element: ReactNode) => {\r\n if (isEnded) {\r\n throw new Error(\"Cannot render: stream has already ended\");\r\n }\r\n\r\n const html = renderReact(element);\r\n chunks.push(html);\r\n this.baseResponse.raw.write(html);\r\n\r\n return this;\r\n },\r\n\r\n /**\r\n * End the stream and trigger completion events\r\n */\r\n end: () => {\r\n if (isEnded) {\r\n return this;\r\n }\r\n\r\n isEnded = true;\r\n\r\n // Store the streamed content for logging/debugging\r\n this.currentBody = chunks;\r\n this.parsedBody = chunks;\r\n\r\n // End the response\r\n this.baseResponse.raw.end();\r\n\r\n this.log(\"Stream ended\");\r\n\r\n // Trigger sent events\r\n Response.trigger(\"sent\", this);\r\n for (const callback of this.events.get(\"sent\") || []) {\r\n callback(this);\r\n }\r\n\r\n // Trigger success event if status is 2xx\r\n if (this.isOk) {\r\n Response.trigger(\"success\", this);\r\n }\r\n\r\n // Trigger status-specific events\r\n if (this.currentStatusCode === 201) {\r\n Response.trigger(\"successCreate\", this);\r\n }\r\n\r\n return this;\r\n },\r\n\r\n /**\r\n * Check if the stream has ended\r\n */\r\n get ended() {\r\n return isEnded;\r\n },\r\n };\r\n }\r\n\r\n /**\r\n * Create a Server-Sent Events (SSE) stream\r\n *\r\n * SSE is a standard for pushing real-time updates from server to client.\r\n * Perfect for live notifications, progress updates, or real-time data feeds.\r\n *\r\n * @example\r\n * ```ts\r\n * const sse = response.sse();\r\n *\r\n * // Send events\r\n * sse.send(\"message\", { text: \"Hello!\" });\r\n * sse.send(\"notification\", { type: \"info\", message: \"Update available\" }, \"msg-123\");\r\n *\r\n * // Keep connection alive\r\n * const keepAlive = setInterval(() => sse.comment(\"ping\"), 30000);\r\n *\r\n * // Clean up when done\r\n * clearInterval(keepAlive);\r\n * sse.end();\r\n * ```\r\n *\r\n * @returns SSE controller with send(), comment(), and end() methods\r\n */\r\n public sse(): ResponseSSEController {\r\n // Set SSE-specific headers\r\n this.setContentType(\"text/event-stream\");\r\n this.header(\"Cache-Control\", \"no-cache, no-store, must-revalidate\");\r\n this.header(\"Connection\", \"keep-alive\");\r\n this.header(\"X-Accel-Buffering\", \"no\"); // Disable nginx buffering\r\n\r\n // Trigger sending events\r\n Response.trigger(\"sending\", this);\r\n for (const callback of this.events.get(\"sending\") || []) {\r\n callback(this);\r\n }\r\n\r\n this.log(\"Starting SSE stream\");\r\n\r\n // Track stream state\r\n let isEnded = false;\r\n const events: any[] = [];\r\n const disconnectHandlers: Array<() => void> = [];\r\n\r\n // Write headers to start the stream\r\n this.baseResponse.raw.writeHead(this.statusCode, this.getHeaders() as any);\r\n\r\n // Detect client disconnect — set isEnded silently and invoke cleanup handlers.\r\n // Without this, background jobs keep writing to a dead socket after the client drops.\r\n this.baseResponse.raw.on(\"close\", () => {\r\n if (!isEnded) {\r\n isEnded = true;\r\n this.log(\"SSE client disconnected\");\r\n for (const handler of disconnectHandlers) {\r\n handler();\r\n }\r\n }\r\n });\r\n\r\n const controller: ResponseSSEController = {\r\n /**\r\n * Send an SSE event\r\n * @param event - Event name (e.g., \"message\", \"chunk\", \"done\")\r\n * @param data - Event data (will be JSON stringified)\r\n * @param id - Optional event ID for client-side Last-Event-ID tracking (reconnect support)\r\n */\r\n send: (event: string, data: any, id?: string): ResponseSSEController => {\r\n // Silent no-op after disconnect — background jobs should not crash when\r\n // the client drops mid-stream. The onDisconnect handler handles cleanup.\r\n if (isEnded) return controller;\r\n\r\n let message = \"\";\r\n if (id) message += `id: ${id}\\n`;\r\n message += `event: ${event}\\n`;\r\n message += `data: ${JSON.stringify(data)}\\n\\n`;\r\n\r\n events.push({ event, data, id });\r\n this.baseResponse.raw.write(message);\r\n\r\n return controller;\r\n },\r\n\r\n /**\r\n * Send a comment (keeps connection alive, invisible to client)\r\n * Useful for preventing timeout on long-lived connections\r\n * @param text - Comment text\r\n */\r\n comment: (text: string): ResponseSSEController => {\r\n // Silent no-op after disconnect\r\n if (isEnded) return controller;\r\n\r\n this.baseResponse.raw.write(`: ${text}\\n\\n`);\r\n\r\n return controller;\r\n },\r\n\r\n /**\r\n * End the SSE stream and trigger completion events\r\n */\r\n end: (): ResponseSSEController => {\r\n if (isEnded) return controller;\r\n\r\n isEnded = true;\r\n\r\n // Store the events for logging/debugging\r\n this.currentBody = events;\r\n this.parsedBody = events;\r\n\r\n // End the response\r\n this.baseResponse.raw.end();\r\n\r\n this.log(\"SSE stream ended\");\r\n\r\n // Trigger sent events\r\n Response.trigger(\"sent\", this);\r\n for (const callback of this.events.get(\"sent\") || []) {\r\n callback(this);\r\n }\r\n\r\n // Trigger success event if status is 2xx\r\n if (this.isOk) {\r\n Response.trigger(\"success\", this);\r\n }\r\n\r\n return controller;\r\n },\r\n\r\n /**\r\n * Register a handler to be called when the client disconnects.\r\n * Use this to clean up EventEmitter listeners, cancel background jobs, etc.\r\n *\r\n * @example\r\n * ```ts\r\n * const sse = response.sse();\r\n * const listener = (chunk) => sse.send(\"chunk\", { chunk });\r\n * eventBus.on(aiMessageId, listener);\r\n * sse.onDisconnect(() => eventBus.off(aiMessageId, listener));\r\n * ```\r\n */\r\n onDisconnect: (handler: () => void): ResponseSSEController => {\r\n disconnectHandlers.push(handler);\r\n return controller;\r\n },\r\n\r\n /**\r\n * Check if the stream has ended (either via end() or client disconnect)\r\n */\r\n get ended() {\r\n return isEnded;\r\n },\r\n };\r\n\r\n return controller;\r\n }\r\n\r\n /**\r\n * Set the status code\r\n */\r\n public setStatusCode(statusCode: number) {\r\n this.currentStatusCode = statusCode;\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Redirect the user to another route\r\n */\r\n public redirect(url: string, statusCode = 302) {\r\n this.baseResponse.redirect(url, statusCode);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Permanent redirect\r\n */\r\n public permanentRedirect(url: string) {\r\n this.baseResponse.redirect(url, 301);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get the response time\r\n */\r\n public getResponseTime() {\r\n return this.baseResponse.elapsedTime;\r\n }\r\n\r\n /**\r\n * Remove a specific header\r\n */\r\n public removeHeader(key: string) {\r\n this.baseResponse.removeHeader(key);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get a specific header\r\n */\r\n public getHeader(key: string) {\r\n return this.baseResponse.getHeader(key);\r\n }\r\n\r\n /**\r\n * Get the response headers\r\n */\r\n public getHeaders() {\r\n return this.baseResponse.getHeaders();\r\n }\r\n\r\n /**\r\n * Set multiple headers\r\n */\r\n public headers(headers: Record<string, string>) {\r\n this.baseResponse.headers(headers);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Set the response header\r\n */\r\n public header(key: string, value: any) {\r\n this.baseResponse.header(key, value);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Set a cookie on the response.\r\n *\r\n * Values are JSON-stringified by default so structured cookies round-trip\r\n * cleanly with `request.cookie(name)`. Pass `{ raw: true }` to skip the\r\n * JSON wrapping for plain-string cookies (session tokens, opaque IDs).\r\n *\r\n * **Secure by default.** `httpOnly: true`, `sameSite: \"lax\"`, and — outside\r\n * development — `secure: true` are applied unless you override them. These\r\n * are the flags whose absence never fails a test and is fatal in production:\r\n * without `httpOnly` any injected script can read the cookie, without\r\n * `secure` it travels in cleartext, without `sameSite` it rides along on\r\n * cross-site requests. Opting out is explicit, per call or via\r\n * `http.cookies.options`.\r\n *\r\n * Precedence, lowest to highest: framework defaults → `http.cookies.options`\r\n * → the per-call `options` argument.\r\n *\r\n * @example\r\n * // JSON-wrapped (default) — round-trips with request.cookie()\r\n * response.cookie(\"prefs\", { theme: \"dark\" }, { maxAge: 3600 });\r\n *\r\n * @example\r\n * // Raw string — no JSON quoting; useful for tokens / opaque IDs\r\n * response.cookie(\"session\", \"abc.def.ghi\", { raw: true });\r\n *\r\n * @example\r\n * // Deliberately readable by client-side JS\r\n * response.cookie(\"theme\", \"dark\", { httpOnly: false });\r\n */\r\n public cookie(name: string, value: CookieValue, options: CookieOptions = {}) {\r\n const { raw, ...cookieOptions } = options;\r\n const defaultOptions = config.get(\"http.cookies.options\", {});\r\n const serializedValue = raw ? String(value) : JSON.stringify(value);\r\n\r\n this.baseResponse.setCookie(name, serializedValue, {\r\n ...secureCookieDefaults(),\r\n ...defaultOptions,\r\n ...cookieOptions,\r\n });\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Clear a cookie from the response\r\n *\r\n * @example\r\n * response.clearCookie('token', { path: '/' });\r\n */\r\n public clearCookie(name: string, options?: CookieSerializeOptions) {\r\n const defaultOptions = config.get(\"http.cookies.options\", {});\r\n this.baseResponse.clearCookie(name, { ...defaultOptions, ...options });\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Alias to header method\r\n */\r\n public setHeader(key: string, value: any) {\r\n return this.header(key, value);\r\n }\r\n\r\n /**\r\n * Send an error response with status code 500\r\n */\r\n public serverError(data: any) {\r\n return this.send(data, 500);\r\n }\r\n\r\n /**\r\n * Send a forbidden response with status code 403\r\n */\r\n public forbidden(\r\n data: any = {\r\n error: \"You are not allowed to access this resource, FORBIDDEN\",\r\n },\r\n ) {\r\n return this.send(data, 403);\r\n }\r\n\r\n /**\r\n * Send a service unavailable response with status code 503\r\n */\r\n public serviceUnavailable(data: any) {\r\n return this.send(data, 503);\r\n }\r\n\r\n /**\r\n * Send an unauthorized response with status code 401\r\n */\r\n public unauthorized(\r\n data: any = {\r\n error: \"unauthorized\",\r\n },\r\n ) {\r\n return this.send(data, 401);\r\n }\r\n\r\n /**\r\n * Send a not found response with status code 404\r\n */\r\n public notFound(\r\n data: any = {\r\n error: \"notFound\",\r\n },\r\n ) {\r\n return this.send(data, 404);\r\n }\r\n\r\n /**\r\n * Send a bad request response with status code 400\r\n */\r\n public badRequest(data: any) {\r\n return this.send(data, 400);\r\n }\r\n\r\n /**\r\n * Send a content too large response with status code 413\r\n */\r\n public contentTooLarge(data: any) {\r\n return this.send(data, 413);\r\n }\r\n\r\n /**\r\n * Send a success response with status code 201\r\n */\r\n public successCreate(data: any) {\r\n return this.send(data, 201);\r\n }\r\n\r\n /**\r\n * Send a success response\r\n */\r\n public success(data: any = { success: true }) {\r\n return this.send(data);\r\n }\r\n\r\n /**\r\n * Send a no content response with status code 204\r\n */\r\n public noContent() {\r\n return this.baseResponse.status(204).send();\r\n }\r\n\r\n /**\r\n * Send an accepted response with status code 202\r\n * Used for async operations that have been accepted but not yet processed\r\n */\r\n public accepted(data: any = { message: \"Request accepted for processing\" }) {\r\n return this.send(data, 202);\r\n }\r\n\r\n /**\r\n * Send a conflict response with status code 409\r\n */\r\n public conflict(data: any = { error: \"Resource conflict\" }) {\r\n return this.send(data, 409);\r\n }\r\n\r\n /**\r\n * Send a too many requests response with status code 429\r\n */\r\n public tooManyRequests(data: any) {\r\n return this.send(data, 429);\r\n }\r\n\r\n /**\r\n * Send an unprocessable entity response with status code 422\r\n * Used for semantic validation errors\r\n */\r\n public unprocessableEntity(data: any) {\r\n return this.send(data, 422);\r\n }\r\n\r\n /**\r\n * Apply response options (cache, disposition, etag)\r\n * Shared helper for sendFile and sendBuffer\r\n */\r\n /**\r\n * Build an RFC 6266–safe `Content-Disposition` value for a file name.\r\n *\r\n * HTTP header values must be ISO-8859-1; Node's `setHeader` throws\r\n * `ERR_INVALID_CHAR` on any other byte, so a raw `filename=\"تقرير.pdf\"` would\r\n * crash the response (a 500 the moment the name is non-ASCII). RFC 6266 solves\r\n * this with two parameters emitted together:\r\n * - `filename=\"<ascii>\"` — sanitised ASCII fallback for legacy clients\r\n * - `filename*=UTF-8''<pct>` — RFC 5987 ext-value; modern browsers restore\r\n * the real (e.g. Arabic) name.\r\n */\r\n private contentDisposition(type: \"inline\" | \"attachment\", rawName: string): string {\r\n // Strip CR/LF first so a crafted file name can never inject extra headers.\r\n const name = (rawName || \"file\").replace(/[\\r\\n]/g, \"\");\r\n\r\n // ASCII fallback: replace quote/backslash + every non-printable-ASCII byte\r\n // (covers all multibyte characters) so the quoted-string is always legal.\r\n const ascii =\r\n name\r\n .replace(/[\"\\\\]/g, \"_\")\r\n .replace(/[^\\x20-\\x7E]/g, \"_\")\r\n .trim() || \"file\";\r\n\r\n // Pure-ASCII name → the quoted form is enough; no ext-value needed.\r\n if (!/[^\\x20-\\x7E]/.test(name)) {\r\n return `${type}; filename=\"${ascii}\"`;\r\n }\r\n\r\n // RFC 5987 ext-value. `encodeURIComponent` leaves ' ( ) * unescaped, but they\r\n // are not valid `attr-char`, so percent-encode those too.\r\n const encoded = encodeURIComponent(name).replace(\r\n /['()*]/g,\r\n (char) => \"%\" + char.charCodeAt(0).toString(16).toUpperCase(),\r\n );\r\n\r\n return `${type}; filename=\"${ascii}\"; filename*=UTF-8''${encoded}`;\r\n }\r\n\r\n private applyResponseOptions(options: SendBufferOptions, defaultFilename?: string): boolean {\r\n // Set content type if provided\r\n if (options.contentType) {\r\n this.baseResponse.type(options.contentType);\r\n }\r\n\r\n // Set cache headers if specified\r\n if (options.cacheTime) {\r\n const cacheControl = options.immutable\r\n ? `public, max-age=${options.cacheTime}, immutable`\r\n : `public, max-age=${options.cacheTime}`;\r\n this.header(\"Cache-Control\", cacheControl);\r\n this.header(\"Expires\", new Date(Date.now() + options.cacheTime * 1000).toUTCString());\r\n }\r\n\r\n // Set ETag if provided (for conditional requests)\r\n if (options.etag) {\r\n this.header(\"ETag\", options.etag);\r\n\r\n // Check If-None-Match for conditional request\r\n const ifNoneMatch = this.request.header(\"if-none-match\");\r\n if (ifNoneMatch && ifNoneMatch === options.etag) {\r\n this.log(\"Content not modified (ETag match), sending 304\");\r\n this.baseResponse.status(304).send();\r\n return true; // Indicates 304 was sent\r\n }\r\n }\r\n\r\n // Set Content-Disposition if inline or filename is specified\r\n if (options.inline !== undefined || options.filename) {\r\n const disposition = options.inline ? \"inline\" : \"attachment\";\r\n const filename = options.filename || defaultFilename || \"file\";\r\n this.header(\"Content-Disposition\", this.contentDisposition(disposition, filename));\r\n }\r\n\r\n return false; // No 304 sent\r\n }\r\n\r\n /**\r\n * Send a file as a response\r\n */\r\n public async sendFile(filePath: string | StorageFile, options?: number | SendFileOptions) {\r\n if (filePath instanceof StorageFile) {\r\n filePath = filePath.absolutePath!;\r\n }\r\n\r\n this.log(`Sending file: ${filePath}`);\r\n\r\n // Check if file exists first\r\n if (!(await fileExistsAsync(filePath))) {\r\n return this.notFound({\r\n error: \"File Not Found\",\r\n });\r\n }\r\n\r\n try {\r\n // Normalize options to object format\r\n const opts = typeof options === \"number\" ? { cacheTime: options } : options || {};\r\n\r\n // Get file stats for ETag and Last-Modified\r\n const stats = await fs.promises.stat(filePath);\r\n const lastModified = stats.mtime;\r\n\r\n // Generate ETag based on file size and modification time\r\n const etag = `\"${stats.size}-${stats.mtime.getTime()}\"`;\r\n\r\n // Set Last-Modified header\r\n this.header(\"Last-Modified\", lastModified.toUTCString());\r\n this.header(\"ETag\", etag);\r\n\r\n // Set content type\r\n const contentType = this.getFileContentType(filePath);\r\n this.baseResponse.type(contentType);\r\n\r\n // Apply common response options (cache, disposition)\r\n const defaultFilename = path.basename(filePath);\r\n const sent304 = this.applyResponseOptions({ ...opts, etag, contentType }, defaultFilename);\r\n if (sent304) return this.baseResponse;\r\n\r\n // Check conditional request headers\r\n const ifNoneMatch = this.request.header(\"if-none-match\");\r\n const ifModifiedSince = this.request.header(\"if-modified-since\");\r\n\r\n // Handle If-None-Match (ETag validation)\r\n if (ifNoneMatch && ifNoneMatch === etag) {\r\n this.log(\"File not modified (ETag match), sending 304\");\r\n return this.baseResponse.status(304).send();\r\n }\r\n\r\n // Handle If-Modified-Since (Last-Modified validation)\r\n if (ifModifiedSince) {\r\n const modifiedSinceDate = new Date(ifModifiedSince);\r\n if (lastModified.getTime() <= modifiedSinceDate.getTime()) {\r\n this.log(\"File not modified (Last-Modified check), sending 304\");\r\n return this.baseResponse.status(304).send();\r\n }\r\n }\r\n\r\n // Use streaming for efficient file sending\r\n const stream = fs.createReadStream(filePath);\r\n\r\n // Handle stream errors\r\n stream.on(\"error\", (error) => {\r\n this.log(`Error reading file: ${error.message}`, \"error\");\r\n if (!this.baseResponse.sent) {\r\n this.serverError({\r\n error: \"Error reading file\",\r\n message: error.message,\r\n });\r\n }\r\n });\r\n\r\n // Send the stream (endTime will be set by finish event listener)\r\n return this.baseResponse.send(stream);\r\n } catch (error: any) {\r\n this.log(`Error sending file: ${error.message}`, \"error\");\r\n return this.serverError({\r\n error: \"Error sending file\",\r\n message: error.message,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Send buffer as a response\r\n * Useful for dynamically generated content (e.g., resized images, generated PDFs)\r\n */\r\n public sendBuffer(buffer: Buffer, options?: number | SendBufferOptions) {\r\n this.log(\"Sending buffer\");\r\n\r\n // Normalize options to object format\r\n const opts = typeof options === \"number\" ? { cacheTime: options } : options || {};\r\n\r\n // Apply common response options (cache, disposition, etag)\r\n const sent304 = this.applyResponseOptions(opts);\r\n if (sent304) return this.baseResponse;\r\n\r\n // Note: endTime is set in the main send() method for non-streaming responses\r\n return this.baseResponse.send(buffer);\r\n }\r\n\r\n /**\r\n * Send an Image instance as a response\r\n * Automatically detects image format and sets content type\r\n */\r\n public async sendImage(\r\n image: any, // Type as 'any' to avoid circular dependency with Image class\r\n options?: number | (Omit<SendBufferOptions, \"contentType\"> & { contentType?: string }),\r\n ) {\r\n this.log(\"Sending image\");\r\n\r\n // Normalize options to object format\r\n const opts = typeof options === \"number\" ? { cacheTime: options } : options || {};\r\n\r\n // Get image metadata to determine format\r\n const metadata = await image.metadata();\r\n const format = metadata.format || \"jpeg\";\r\n\r\n // Convert image to buffer\r\n const buffer = await image.toBuffer();\r\n\r\n // Auto-set content type if not provided\r\n const contentType = opts.contentType || `image/${format}`;\r\n\r\n // Auto-generate ETag if not provided\r\n // Format: \"format-widthxheight-size\" (e.g., \"jpeg-800x600-45231\")\r\n // This catches changes in dimensions, quality, filters, and format\r\n if (!opts.etag) {\r\n const width = metadata.width || 0;\r\n const height = metadata.height || 0;\r\n opts.etag = `\"${format}-${width}x${height}-${buffer.length}\"`;\r\n }\r\n\r\n // Apply common response options with auto-detected content type\r\n const sent304 = this.applyResponseOptions({ ...opts, contentType });\r\n if (sent304) return this.baseResponse;\r\n\r\n // Note: endTime is set in the main send() method for non-streaming responses\r\n return this.baseResponse.send(buffer);\r\n }\r\n\r\n /**\r\n * Send file and cache it\r\n * Cache time in seconds\r\n * Cache time will be one year\r\n */\r\n public sendCachedFile(path: string | StorageFile, cacheTime = 31536000) {\r\n return this.sendFile(path, cacheTime);\r\n }\r\n\r\n /**\r\n * Download the given file path\r\n */\r\n public download(path: string, filename?: string) {\r\n return this.downloadFile(path, filename);\r\n }\r\n\r\n /**\r\n * Download the given file path\r\n */\r\n public async downloadFile(filePath: string, filename?: string) {\r\n // Check if file exists first\r\n if (!(await fileExistsAsync(filePath))) {\r\n return this.notFound({\r\n error: \"File Not Found\",\r\n });\r\n }\r\n\r\n try {\r\n if (!filename) {\r\n filename = path.basename(filePath);\r\n }\r\n\r\n this.baseResponse.header(\r\n \"Content-Disposition\",\r\n this.contentDisposition(\"attachment\", filename),\r\n );\r\n\r\n // this.baseResponse.header(\"Content-Type\", this.getFileContentType(filePath));\r\n this.baseResponse.header(\"Content-Type\", \"application/octet-stream\");\r\n\r\n const stream = fs.createReadStream(filePath);\r\n\r\n // Handle stream errors\r\n stream.on(\"error\", (error) => {\r\n this.log(`Error reading file for download: ${error.message}`, \"error\");\r\n if (!this.baseResponse.sent) {\r\n this.serverError({\r\n error: \"Error reading file\",\r\n message: error.message,\r\n });\r\n }\r\n });\r\n\r\n // Send the stream (endTime will be set by finish event listener)\r\n return this.baseResponse.send(stream);\r\n } catch (error: any) {\r\n this.log(`Error downloading file: ${error.message}`, \"error\");\r\n return this.serverError({\r\n error: \"Error downloading file\",\r\n message: error.message,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Get content type of the given path\r\n */\r\n public getFileContentType(filePath: string) {\r\n const type = mime.getType(filePath) || \"application/octet-stream\";\r\n return type;\r\n }\r\n\r\n /**\r\n * Mark the response as failed\r\n */\r\n public failedSchema(result: ValidationResult) {\r\n const { errors, inputKey, inputError, status } = config.get(\"validation.response\", {\r\n errors: \"errors\",\r\n inputKey: \"input\",\r\n inputError: \"error\",\r\n status: 422,\r\n });\r\n\r\n log.error(\"request\", \"validation\", `${this.request.id} - Validation failed`);\r\n\r\n return this.send(\r\n {\r\n [errors]: result.errors.map((error) => ({\r\n [inputKey]: error.input,\r\n [inputError]: error.error,\r\n })),\r\n },\r\n status,\r\n );\r\n }\r\n}\r\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.mts","names":[],"sources":["../../../../../../../core/src/http/server.ts"],"mappings":";;;KAGY,iBAAA,GAAkB,UAAU,QAAQ,OAAA;AAAA,iBAKhC,eAAA,CAAgB,OAAA,GAAU,oBAAA,GAAuB,iBAAe;AALhF;;;AAAA,
|
|
1
|
+
{"version":3,"file":"server.d.mts","names":[],"sources":["../../../../../../../core/src/http/server.ts"],"mappings":";;;KAGY,iBAAA,GAAkB,UAAU,QAAQ,OAAA;AAAA,iBAKhC,eAAA,CAAgB,OAAA,GAAU,oBAAA,GAAuB,iBAAe;AALhF;;;AAAA,iBAsCgB,aAAA,IAAiB,iBAAe;AAtCO;AAKvD;;;AALuD,KA8C3C,cAAA;EAAmB,KAAA,QAAa,OAAO;AAAA;;;AAzC6B;AAiChF;;;;iBAiBsB,sBAAA,CACpB,MAAA,EAAQ,cAAA,EACR,SAAA,WACC,OAAO"}
|
package/esm/http/server.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.mjs","names":[],"sources":["../../../../../../../core/src/http/server.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport Fastify, { FastifyServerOptions } from \"fastify\";\r\n\r\nexport type FastifyInstance = ReturnType<typeof Fastify>;\r\n\r\n// Instantiate Fastify server\r\nlet server: FastifyInstance | undefined = undefined;\r\n\r\nexport function startHttpServer(options?: FastifyServerOptions): FastifyInstance {\r\n // `config.set(key, undefined)` stores null rather than unsetting, so an app\r\n // that clears a key would otherwise hand Fastify `null` and crash the boot.\r\n const bodyLimit = config.get(\"http.bodyLimit\") ?? undefined;\r\n\r\n return (server = Fastify({\r\n // `X-Forwarded-For` is client-settable and spoofable, and `request.ip` is\r\n // what @fastify/rate-limit keys its buckets on — so trusting it by default\r\n // makes rate limiting bypassable on any deployment NOT behind a proxy that\r\n // strips the header. Apps behind such a proxy opt in explicitly.\r\n trustProxy: config.get(\"http.trustProxy\", false),\r\n // No default: an app that configures nothing keeps Fastify's own 1MB limit\r\n // rather than the historical 200GB, which silently removed the protection\r\n // Fastify provides. Per-route caps go through `serverOptions.bodyLimit`;\r\n // the `maxBodySize()` middleware runs AFTER parsing and cannot reject\r\n // before the bytes are resident.\r\n ...(bodyLimit !== undefined && { bodyLimit }),\r\n // Close idle keep-alive connections on shutdown while letting in-flight\r\n // requests finish — the basis for graceful draining. Override via\r\n // `http.gracefulShutdown.forceCloseConnections`.\r\n forceCloseConnections: config.get(\"http.gracefulShutdown.forceCloseConnections\", \"idle\"),\r\n ...options,\r\n }));\r\n}\r\n\r\n/**\r\n * Expose the server to be publicly accessible\r\n */\r\nexport function getHttpServer(): FastifyInstance {\r\n return server;\r\n}\r\n\r\n/**\r\n * Minimal shape needed to close a server — lets {@link closeServerWithTimeout}\r\n * be unit-tested with a fake instead of a real Fastify instance.\r\n */\r\nexport type ClosableServer = { close: () => Promise<unknown> };\r\n\r\n/**\r\n * Close a server, bounded by a timeout. Fastify's `close()` stops accepting new\r\n * requests (it answers 503 while closing) and drains the in-flight ones; this\r\n * wraps it so a single stuck request can't hang shutdown forever.\r\n *\r\n * @returns `true` if the server drained cleanly, `false` if the timeout fired first.\r\n */\r\nexport async function closeServerWithTimeout(\r\n server: ClosableServer,\r\n timeoutMs: number,\r\n): Promise<boolean> {\r\n let timer: ReturnType<typeof setTimeout> | undefined;\r\n\r\n const drained = server.close().then(() => true);\r\n\r\n const timedOut = new Promise<boolean>((resolve) => {\r\n timer = setTimeout(() => resolve(false), timeoutMs);\r\n });\r\n\r\n try {\r\n return await Promise.race([drained, timedOut]);\r\n } finally {\r\n if (timer) {\r\n clearTimeout(timer);\r\n }\r\n }\r\n}\r\n"],"mappings":";;;;AAMA,IAAI,SAAsC;AAE1C,SAAgB,gBAAgB,SAAiD;CAG/E,MAAM,YAAY,OAAO,IAAI,gBAAgB,KAAK;CAElD,OAAQ,SAAS,QAAQ;
|
|
1
|
+
{"version":3,"file":"server.mjs","names":[],"sources":["../../../../../../../core/src/http/server.ts"],"sourcesContent":["import config from \"@mongez/config\";\r\nimport Fastify, { FastifyServerOptions } from \"fastify\";\r\n\r\nexport type FastifyInstance = ReturnType<typeof Fastify>;\r\n\r\n// Instantiate Fastify server\r\nlet server: FastifyInstance | undefined = undefined;\r\n\r\nexport function startHttpServer(options?: FastifyServerOptions): FastifyInstance {\r\n // `config.set(key, undefined)` stores null rather than unsetting, so an app\r\n // that clears a key would otherwise hand Fastify `null` and crash the boot.\r\n const bodyLimit = config.get(\"http.bodyLimit\") ?? undefined;\r\n\r\n return (server = Fastify({\r\n // `X-Forwarded-For` is client-settable and spoofable, and `request.ip` is\r\n // what @fastify/rate-limit keys its buckets on — so trusting it by default\r\n // makes rate limiting bypassable on any deployment NOT behind a proxy that\r\n // strips the header. Apps behind such a proxy opt in explicitly.\r\n //\r\n // The value is passed through untouched so every shape Fastify supports\r\n // works: `true`, a hop count, a CIDR/IP list (string, comma-separated\r\n // string or array), or a predicate. `request.detectIp()` reads the client\r\n // off `request.ip`, so it resolves the chain exactly the same way.\r\n trustProxy: config.get(\"http.trustProxy\", false),\r\n // No default: an app that configures nothing keeps Fastify's own 1MB limit\r\n // rather than the historical 200GB, which silently removed the protection\r\n // Fastify provides. Per-route caps go through `serverOptions.bodyLimit`;\r\n // the `maxBodySize()` middleware runs AFTER parsing and cannot reject\r\n // before the bytes are resident.\r\n ...(bodyLimit !== undefined && { bodyLimit }),\r\n // Close idle keep-alive connections on shutdown while letting in-flight\r\n // requests finish — the basis for graceful draining. Override via\r\n // `http.gracefulShutdown.forceCloseConnections`.\r\n forceCloseConnections: config.get(\"http.gracefulShutdown.forceCloseConnections\", \"idle\"),\r\n ...options,\r\n }));\r\n}\r\n\r\n/**\r\n * Expose the server to be publicly accessible\r\n */\r\nexport function getHttpServer(): FastifyInstance {\r\n return server;\r\n}\r\n\r\n/**\r\n * Minimal shape needed to close a server — lets {@link closeServerWithTimeout}\r\n * be unit-tested with a fake instead of a real Fastify instance.\r\n */\r\nexport type ClosableServer = { close: () => Promise<unknown> };\r\n\r\n/**\r\n * Close a server, bounded by a timeout. Fastify's `close()` stops accepting new\r\n * requests (it answers 503 while closing) and drains the in-flight ones; this\r\n * wraps it so a single stuck request can't hang shutdown forever.\r\n *\r\n * @returns `true` if the server drained cleanly, `false` if the timeout fired first.\r\n */\r\nexport async function closeServerWithTimeout(\r\n server: ClosableServer,\r\n timeoutMs: number,\r\n): Promise<boolean> {\r\n let timer: ReturnType<typeof setTimeout> | undefined;\r\n\r\n const drained = server.close().then(() => true);\r\n\r\n const timedOut = new Promise<boolean>((resolve) => {\r\n timer = setTimeout(() => resolve(false), timeoutMs);\r\n });\r\n\r\n try {\r\n return await Promise.race([drained, timedOut]);\r\n } finally {\r\n if (timer) {\r\n clearTimeout(timer);\r\n }\r\n }\r\n}\r\n"],"mappings":";;;;AAMA,IAAI,SAAsC;AAE1C,SAAgB,gBAAgB,SAAiD;CAG/E,MAAM,YAAY,OAAO,IAAI,gBAAgB,KAAK;CAElD,OAAQ,SAAS,QAAQ;EAUvB,YAAY,OAAO,IAAI,mBAAmB,KAAK;EAM/C,GAAI,cAAc,UAAa,EAAE,UAAU;EAI3C,uBAAuB,OAAO,IAAI,+CAA+C,MAAM;EACvF,GAAG;CACL,CAAC;AACH;;;;AAKA,SAAgB,gBAAiC;CAC/C,OAAO;AACT;;;;;;;;AAeA,eAAsB,uBACpB,QACA,WACkB;CAClB,IAAI;CAEJ,MAAM,UAAU,OAAO,MAAM,CAAC,CAAC,WAAW,IAAI;CAE9C,MAAM,WAAW,IAAI,SAAkB,YAAY;EACjD,QAAQ,iBAAiB,QAAQ,KAAK,GAAG,SAAS;CACpD,CAAC;CAED,IAAI;EACF,OAAO,MAAM,QAAQ,KAAK,CAAC,SAAS,QAAQ,CAAC;CAC/C,UAAU;EACR,IAAI,OACF,aAAa,KAAK;CAEtB;AACF"}
|