import MimeTypeMap from './MimeTypeMap'; /** * Represents a response object for handling HTTP responses. */ export default class LayResponse { #routing = undefined; #body = 'Not Implemented'; #data = undefined; #statusCode = undefined; #headerList = new Map(); #mimeType = 'text/plain'; #options = new Map([ ['type', 'default'], ['disableFileCheck', false], ['layout', undefined], ]); getData() { return this.#data; } getBody() { return this.#body; } /** * Creates a new instance of LayResponse. * @param {Object} routing - The routing object. */ constructor(routing) { this.#routing = routing; if(this.#routing.getServer().hasLayout()) { this.#options.set('layout', this.#routing.getServer().getLayout()); } } /** * Returns the headers of the response. * @returns {Map} The headers map. */ headers() { if (!this.#headerList.get('Content-Type')) { this.#headerList.set('Content-Type', `${this.#mimeType}`); } return this.#headerList; } /** * Sets or gets the layout for the response. * @param {string} [layout] - The layout to set. If not provided, returns the current layout. * @returns {string|LayResponse} The current layout or the LayResponse instance. */ layout (layout = undefined) { if(typeof(layout) == 'undefined') return this.#options.get('layout'); this.#options.set('layout', layout); return this; } /** * Builds the response based on the specified options. * @param {Object} [options={}] - The options for building the response. * @returns {Promise} A promise that resolves to the built response. */ async build(options = {}) { switch(this.#options.get('type')) { case 'redirect': return Response.redirect(this.#body, this.status()); case 'file': const file = Bun.file(this.#body); if(await file.exists()) { return new Response(file); } return undefined; case 'render': const loader = await this.#routing.getServer().load(this.#body); if(!loader) { return new Response('Not Found', { status: 404 }); } console.log(loader); const { data, content } = await loader.parse(this.#data); this.#data = data || this.#data; this.#body = content; this.type('text'); break; default: case 'default': break; } if(['text/plain', 'text/html'].includes(this.#mimeType)) { await this.#handleLayout(this.#body, options); } if(typeof(this.#statusCode) == 'undefined') this.status(this.#body ? 200 : 500); return new Response(this.#body, { status: this.status(), headers: this.headers(), }); } /** * Handles the layout rendering for the response. * @param {string} content - The content to render within the layout. * @param {Object} [data={}] - The data to pass to the layout. * @returns {Promise} * @private */ async #handleLayout(content, data = {}) { if(!this.#options.get('layout')) return const layout = await this.#routing.getServer().load(this.#options.get('layout')); data = { ...(this.#data || {}), ...data }; if(layout) { const rendered = await layout.parse({ content: String(content), ...(data ?? {}) }); this.#body = rendered.content ?? String(content); this.#mimeType = layout.mimeType; } } /** * Sets a header for the response. * @param {string} key - The header key. * @param {string} value - The header value. * @returns {LayResponse} The LayResponse instance. */ header(key, value) { this.#headerList.set(key, value); return this; } /** * Sets the MIME type of the response based on the provided key. * @param {string} key - The key to determine the MIME type. * @returns {LayResponse} The LayResponse instance. */ type(key) { if (!MimeTypeMap.has(key)) { if(this.#mimeType != 'text/plain') this.#mimeType = MimeTypeMap.get('text'); return this; } this.mimeType = MimeTypeMap.get(key); return this; } /** * Sets or gets the status code of the response. * @param {number} [value] - The status code to set. If not provided, returns the current status code. * @returns {number|LayResponse} The current status code or the LayResponse instance. */ status(value = undefined) { if (value == undefined) return this.#statusCode; if (isNaN(value)) throw new TypeError('status has to be a number'); this.#statusCode = value || 502; return this.#statusCode; } /** * Sends the response with the provided arguments. * @returns {LayResponse} The LayResponse instance. */ send() { this.text(...arguments); return this; } /** * Sends a file as the response. * @returns {LayResponse} The LayResponse instance. */ sendFile() { this.#body = arguments[0]; this.#options.set('type', 'file'); return this; } /** * Renders a template with the provided path and data. * @returns {LayResponse} The LayResponse instance. */ render() { const [ path, data ] = arguments; this.#body = path; this.#data = data; this.#options.set('type', 'render'); return this; } /** * Sets the response body as plain text. * @returns {LayResponse} The LayResponse instance. */ text() { this.#body = arguments[0] ?? ''; this.#data = arguments[1] ?? {}; this.#options.set('type', 'default'); return this; } /** * Redirects the response to the specified value with the provided status code. * @param {string} [value=null] - The value to redirect to. If not provided, returns the current redirect value. * @param {number} [status=302] - The status code for the redirect. * @returns {number|null} The redirect status code or null if no redirect is set. */ redirect(value = null, status = 302) { if (value === null) { if(!this.#options.get('type') == 'redirect') return null; return this.#body }; this.#body = value; this.#options.set('type', 'redirect'); return this.status(status); } /** * Sets the response body as JSON. * @returns {LayResponse} The LayResponse instance. */ json() { const [ data, options ] = arguments; let { replacer = null, space = 0 } = (options || {}); if (!Array.isArray(replacer) && typeof replacer != 'function') replacer = null; if (isNaN(space) || Math.floor(space) < 0) space = 0; this.type('json'); this.#body = JSON.stringify(data, replacer, space); this.#data = options; return this; } }