UNPKG

svelte-kit-sst

Version:

Adapter for SvelteKit apps to work on AWS Lambda and AWS Lambda@Edge.

139 lines (125 loc) 3.97 kB
import fs from "node:fs"; import path from "node:path"; import { installPolyfills } from "@sveltejs/kit/node/polyfills"; import type { Server as ServerType } from "@sveltejs/kit/types"; // @ts-ignore import { Server } from "../index.js"; // @ts-ignore import { manifest } from "../manifest.js"; // @ts-ignore import prerenderedFiles from "./prerendered-file-list.js"; import type { APIGatewayProxyEventV2, APIGatewayProxyEvent, CloudFrontRequestEvent, } from "aws-lambda"; import { InternalEvent, convertFrom, convertTo } from "./event-mapper.js"; import { debug } from "./logger.js"; import { isBinaryContentType } from "./binary.js"; installPolyfills(); const app: ServerType = new Server(manifest); await app.init({ env: process.env as Record<string, string> }); export async function handler( event: APIGatewayProxyEventV2 | CloudFrontRequestEvent | APIGatewayProxyEvent ) { debug("event", event); // Parse Lambda event const internalEvent = convertFrom(event); // Set correct host header if (internalEvent.headers["x-forwarded-host"]) { internalEvent.headers.host = internalEvent.headers["x-forwarded-host"]; } // Check request is for prerendered file if (internalEvent.method === "GET") { const filePath = isPrerenderedFile(internalEvent.rawPath); if (filePath) { return internalEvent.type === "cf" ? formatCloudFrontPrerenderedResponse( event as CloudFrontRequestEvent, filePath ) : formatAPIGatewayPrerenderedResponse(internalEvent, filePath); } } // Process request const requestUrl = `https://${internalEvent.headers.host}${internalEvent.url}`; const requestProps = { method: internalEvent.method, headers: internalEvent.headers, body: ["GET", "HEAD"].includes(internalEvent.method) ? undefined : internalEvent.body, }; debug("request", requestUrl, requestProps); const request = new Request(requestUrl, requestProps); const response: Response = await app.respond(request, { getClientAddress: () => internalEvent.remoteAddress, }); debug("response", response); //Parse the response into lambda proxy response if (response) { const headers: Record<string, string[]> = {}; response.headers.forEach((value, key) => { headers[key] = headers[key] || []; headers[key].push(value); }); const isBase64Encoded = isBinaryContentType( Array.isArray(headers["content-type"]) ? headers["content-type"][0] : headers["content-type"] ); const body = isBase64Encoded ? Buffer.from(await response.arrayBuffer()).toString("base64") : await response.text(); return convertTo({ type: internalEvent.type, statusCode: response.status, headers, isBase64Encoded, body, }); } return { statusCode: 404, body: "Not found.", }; } function isPrerenderedFile(uri: string) { // remove leading and trailing slashes uri = uri.replace(/^\/|\/$/g, ""); if (uri === "") { return prerenderedFiles.includes("index.html") ? "index.html" : undefined; } if (prerenderedFiles.includes(uri)) { return uri; } if (prerenderedFiles.includes(uri + "/index.html")) { return uri + "/index.html"; } if (prerenderedFiles.includes(uri + ".html")) { return uri + ".html"; } } function formatCloudFrontPrerenderedResponse( event: CloudFrontRequestEvent, filePath: string ) { const request = event.Records[0].cf.request; request.uri = `/${filePath}`; return request; } function formatAPIGatewayPrerenderedResponse( internalEvent: InternalEvent, filePath: string ) { return convertTo({ type: internalEvent.type, statusCode: 200, headers: { "content-type": "text/html", "cache-control": "public, max-age=0, s-maxage=31536000, must-revalidate", }, isBase64Encoded: false, body: fs.readFileSync(path.join("prerendered", filePath), "utf8"), }); }