diff --git a/libs/proxy/package.json b/libs/proxy/package.json index 2ce4d32..855a868 100644 --- a/libs/proxy/package.json +++ b/libs/proxy/package.json @@ -1,6 +1,6 @@ { "name": "@fal-ai/serverless-proxy", - "version": "0.7.5", + "version": "0.8.0-alpha.1", "license": "MIT", "repository": { "type": "git", diff --git a/libs/proxy/src/express.ts b/libs/proxy/src/express.ts index 5afb765..a49b312 100644 --- a/libs/proxy/src/express.ts +++ b/libs/proxy/src/express.ts @@ -17,11 +17,37 @@ export const handler: RequestHandler = async (request, response, next) => { await handleRequest({ id: 'express', method: request.method, - respondWith: (status, data) => response.status(status).json(data), + getRequestBody: async () => JSON.stringify(request.body), getHeaders: () => request.headers, getHeader: (name) => request.headers[name], sendHeader: (name, value) => response.setHeader(name, value), - getBody: async () => JSON.stringify(request.body), + respondWith: (status, data) => response.status(status).json(data), + sendResponse: async (res) => { + if (res.body instanceof ReadableStream) { + const reader = res.body.getReader(); + const stream = async () => { + const { done, value } = await reader.read(); + if (done) { + response.end(); + return response; + } + response.write(value); + return await stream(); + }; + + return await stream().catch((error) => { + if (!response.headersSent) { + response.status(500).send(error.message); + } else { + response.end(); + } + }); + } + if (res.headers.get('content-type')?.includes('application/json')) { + return response.status(res.status).json(await res.json()); + } + return response.status(res.status).send(await res.text()); + }, }); next(); }; diff --git a/libs/proxy/src/index.ts b/libs/proxy/src/index.ts index c920f75..79d1fad 100644 --- a/libs/proxy/src/index.ts +++ b/libs/proxy/src/index.ts @@ -19,10 +19,11 @@ export interface ProxyBehavior { method: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any respondWith(status: number, data: string | any): ResponseType; + sendResponse(response: Response): Promise; getHeaders(): Record; getHeader(name: string): HeaderValue; sendHeader(name: string, value: string): void; - getBody(): Promise; + getRequestBody(): Promise; resolveApiKey?: () => Promise; } @@ -109,7 +110,7 @@ export async function handleRequest( body: behavior.method?.toUpperCase() === 'GET' ? undefined - : await behavior.getBody(), + : await behavior.getRequestBody(), }); // copy headers from fal to the proxied response @@ -119,12 +120,7 @@ export async function handleRequest( } }); - if (res.headers.get('content-type')?.includes('application/json')) { - const data = await res.json(); - return behavior.respondWith(res.status, data); - } - const data = await res.text(); - return behavior.respondWith(res.status, data); + return behavior.sendResponse(res); } export function fromHeaders( @@ -138,3 +134,5 @@ export function fromHeaders( }); return result; } + +export const responsePassthrough = (res: Response) => Promise.resolve(res); diff --git a/libs/proxy/src/nextjs.ts b/libs/proxy/src/nextjs.ts index 4f941b7..f978f78 100644 --- a/libs/proxy/src/nextjs.ts +++ b/libs/proxy/src/nextjs.ts @@ -1,6 +1,11 @@ import { NextResponse, type NextRequest } from 'next/server'; import type { NextApiHandler } from 'next/types'; -import { DEFAULT_PROXY_ROUTE, fromHeaders, handleRequest } from './index'; +import { + DEFAULT_PROXY_ROUTE, + fromHeaders, + handleRequest, + responsePassthrough, +} from './index'; /** * The default Next API route for the fal.ai client proxy. @@ -11,6 +16,8 @@ export const PROXY_ROUTE = DEFAULT_PROXY_ROUTE; * The Next API route handler for the fal.ai client proxy. * Use it with the /pages router in Next.js. * + * Note: the page routers proxy doesn't support streaming responses. + * * @param request the Next API request object. * @param response the Next API response object. * @returns a promise that resolves when the request is handled. @@ -19,11 +26,17 @@ export const handler: NextApiHandler = async (request, response) => { return handleRequest({ id: 'nextjs-page-router', method: request.method || 'POST', - respondWith: (status, data) => response.status(status).json(data), + getRequestBody: async () => JSON.stringify(request.body), getHeaders: () => request.headers, getHeader: (name) => request.headers[name], sendHeader: (name, value) => response.setHeader(name, value), - getBody: async () => JSON.stringify(request.body), + respondWith: (status, data) => response.status(status).json(data), + sendResponse: async (res) => { + if (res.headers.get('content-type')?.includes('application/json')) { + return response.status(res.status).json(await res.json()); + } + return response.status(res.status).send(await res.text()); + }, }); }; @@ -36,18 +49,22 @@ export const handler: NextApiHandler = async (request, response) => { async function routeHandler(request: NextRequest) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const responseHeaders: Record = {}; + + // check if response if from a streaming request + return await handleRequest({ id: 'nextjs-app-router', method: request.method, + getRequestBody: async () => request.text(), + getHeaders: () => fromHeaders(request.headers), + getHeader: (name) => request.headers.get(name), + sendHeader: (name, value) => (responseHeaders[name] = value), respondWith: (status, data) => NextResponse.json(data, { status, headers: responseHeaders, }), - getHeaders: () => fromHeaders(request.headers), - getHeader: (name) => request.headers.get(name), - sendHeader: (name, value) => (responseHeaders[name] = value), - getBody: async () => request.text(), + sendResponse: responsePassthrough, }); } diff --git a/libs/proxy/src/svelte.ts b/libs/proxy/src/svelte.ts index e820919..56dea0d 100644 --- a/libs/proxy/src/svelte.ts +++ b/libs/proxy/src/svelte.ts @@ -1,5 +1,5 @@ import { type RequestHandler } from '@sveltejs/kit'; -import { fromHeaders, handleRequest } from './index'; +import { fromHeaders, handleRequest, responsePassthrough } from './index'; type RequestHandlerParams = { /** @@ -28,16 +28,17 @@ export const createRequestHandler = ({ return await handleRequest({ id: 'svelte-app-router', method: request.method, + getRequestBody: async () => request.text(), + getHeaders: () => fromHeaders(request.headers), + getHeader: (name) => request.headers.get(name), + sendHeader: (name, value) => (responseHeaders[name] = value), + resolveApiKey: () => Promise.resolve(FAL_KEY), respondWith: (status, data) => new Response(JSON.stringify(data), { status, headers: responseHeaders, }), - getHeaders: () => fromHeaders(request.headers), - getHeader: (name) => request.headers.get(name), - sendHeader: (name, value) => (responseHeaders[name] = value), - getBody: async () => request.text(), - resolveApiKey: () => Promise.resolve(FAL_KEY), + sendResponse: responsePassthrough, }); }; return { diff --git a/package-lock.json b/package-lock.json index 87ad66c..07dc2d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "http-proxy": "^1.18.1", "http-proxy-middleware": "^2.0.6", "js-base64": "^3.7.5", - "next": "^14.0.3", + "next": "^14.2.5", "open": "^10.0.3", "ora": "^8.0.1", "react": "^18.2.0", @@ -4764,9 +4764,9 @@ } }, "node_modules/@next/env": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.3.tgz", - "integrity": "sha512-7xRqh9nMvP5xrW4/+L0jgRRX+HoNRGnfJpD+5Wq6/13j3dsdzxO3BCXn7D3hMqsDb+vjZnJq+vI7+EtgrYZTeA==" + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.5.tgz", + "integrity": "sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==" }, "node_modules/@next/eslint-plugin-next": { "version": "14.0.3", @@ -4798,9 +4798,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.3.tgz", - "integrity": "sha512-64JbSvi3nbbcEtyitNn2LEDS/hcleAFpHdykpcnrstITFlzFgB/bW0ER5/SJJwUPj+ZPY+z3e+1jAfcczRLVGw==", + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz", + "integrity": "sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==", "cpu": [ "arm64" ], @@ -4813,9 +4813,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.3.tgz", - "integrity": "sha512-RkTf+KbAD0SgYdVn1XzqE/+sIxYGB7NLMZRn9I4Z24afrhUpVJx6L8hsRnIwxz3ERE2NFURNliPjJ2QNfnWicQ==", + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz", + "integrity": "sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==", "cpu": [ "x64" ], @@ -4828,9 +4828,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.3.tgz", - "integrity": "sha512-3tBWGgz7M9RKLO6sPWC6c4pAw4geujSwQ7q7Si4d6bo0l6cLs4tmO+lnSwFp1Tm3lxwfMk0SgkJT7EdwYSJvcg==", + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz", + "integrity": "sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==", "cpu": [ "arm64" ], @@ -4843,9 +4843,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.3.tgz", - "integrity": "sha512-v0v8Kb8j8T23jvVUWZeA2D8+izWspeyeDGNaT2/mTHWp7+37fiNfL8bmBWiOmeumXkacM/AB0XOUQvEbncSnHA==", + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz", + "integrity": "sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==", "cpu": [ "arm64" ], @@ -4858,9 +4858,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.3.tgz", - "integrity": "sha512-VM1aE1tJKLBwMGtyBR21yy+STfl0MapMQnNrXkxeyLs0GFv/kZqXS5Jw/TQ3TSUnbv0QPDf/X8sDXuMtSgG6eg==", + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz", + "integrity": "sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==", "cpu": [ "x64" ], @@ -4873,9 +4873,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.3.tgz", - "integrity": "sha512-64EnmKy18MYFL5CzLaSuUn561hbO1Gk16jM/KHznYP3iCIfF9e3yULtHaMy0D8zbHfxset9LTOv6cuYKJgcOxg==", + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz", + "integrity": "sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==", "cpu": [ "x64" ], @@ -4888,9 +4888,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.3.tgz", - "integrity": "sha512-WRDp8QrmsL1bbGtsh5GqQ/KWulmrnMBgbnb+59qNTW1kVi1nG/2ndZLkcbs2GX7NpFLlToLRMWSQXmPzQm4tog==", + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz", + "integrity": "sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==", "cpu": [ "arm64" ], @@ -4903,9 +4903,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.3.tgz", - "integrity": "sha512-EKffQeqCrj+t6qFFhIFTRoqb2QwX1mU7iTOvMyLbYw3QtqTw9sMwjykyiMlZlrfm2a4fA84+/aeW+PMg1MjuTg==", + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz", + "integrity": "sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==", "cpu": [ "ia32" ], @@ -4918,9 +4918,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.3.tgz", - "integrity": "sha512-ERhKPSJ1vQrPiwrs15Pjz/rvDHZmkmvbf/BjPN/UCOI++ODftT0GtasDPi0j+y6PPJi5HsXw+dpRaXUaw4vjuQ==", + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz", + "integrity": "sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==", "cpu": [ "x64" ], @@ -7616,17 +7616,16 @@ } }, "node_modules/@swc/counter": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz", - "integrity": "sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==", - "devOptional": true, - "peer": true + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" }, "node_modules/@swc/helpers": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", - "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", "dependencies": { + "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, @@ -10369,9 +10368,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001558", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001558.tgz", - "integrity": "sha512-/Et7DwLqpjS47JPEcz6VnxU9PwcIdVi0ciLXRWBQdj1XFye68pSQYpV0QtPTfUKWuOaEig+/Vez2l74eDc1tPQ==", + "version": "1.0.30001643", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz", + "integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==", "funding": [ { "type": "opencollective", @@ -15782,7 +15781,8 @@ "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true }, "node_modules/glob/node_modules/minimatch": { "version": "3.1.2", @@ -20608,17 +20608,17 @@ "dev": true }, "node_modules/next": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/next/-/next-14.0.3.tgz", - "integrity": "sha512-AbYdRNfImBr3XGtvnwOxq8ekVCwbFTv/UJoLwmaX89nk9i051AEY4/HAWzU0YpaTDw8IofUpmuIlvzWF13jxIw==", + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.5.tgz", + "integrity": "sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==", "dependencies": { - "@next/env": "14.0.3", - "@swc/helpers": "0.5.2", + "@next/env": "14.2.5", + "@swc/helpers": "0.5.5", "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001406", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", "postcss": "8.4.31", - "styled-jsx": "5.1.1", - "watchpack": "2.4.0" + "styled-jsx": "5.1.1" }, "bin": { "next": "dist/bin/next" @@ -20627,18 +20627,19 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.0.3", - "@next/swc-darwin-x64": "14.0.3", - "@next/swc-linux-arm64-gnu": "14.0.3", - "@next/swc-linux-arm64-musl": "14.0.3", - "@next/swc-linux-x64-gnu": "14.0.3", - "@next/swc-linux-x64-musl": "14.0.3", - "@next/swc-win32-arm64-msvc": "14.0.3", - "@next/swc-win32-ia32-msvc": "14.0.3", - "@next/swc-win32-x64-msvc": "14.0.3" + "@next/swc-darwin-arm64": "14.2.5", + "@next/swc-darwin-x64": "14.2.5", + "@next/swc-linux-arm64-gnu": "14.2.5", + "@next/swc-linux-arm64-musl": "14.2.5", + "@next/swc-linux-x64-gnu": "14.2.5", + "@next/swc-linux-x64-musl": "14.2.5", + "@next/swc-win32-arm64-msvc": "14.2.5", + "@next/swc-win32-ia32-msvc": "14.2.5", + "@next/swc-win32-x64-msvc": "14.2.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" @@ -20647,6 +20648,9 @@ "@opentelemetry/api": { "optional": true }, + "@playwright/test": { + "optional": true + }, "sass": { "optional": true } @@ -30163,6 +30167,7 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" diff --git a/package.json b/package.json index 72848b9..7b5074e 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "http-proxy": "^1.18.1", "http-proxy-middleware": "^2.0.6", "js-base64": "^3.7.5", - "next": "^14.0.3", + "next": "^14.2.5", "open": "^10.0.3", "ora": "^8.0.1", "react": "^18.2.0",