From 82b1770864cdcc312675aea4c73a87163f75ca1e Mon Sep 17 00:00:00 2001 From: Guy Tepper Date: Sun, 11 Jan 2026 09:58:33 +0100 Subject: [PATCH 1/3] add docs for creating custom proxy implementation --- packages/proxy/README.md | 32 +++++++++++++- packages/proxy/src/core/types.ts | 74 ++++++++++++++++++++++++++++++++ packages/proxy/src/index.ts | 4 +- 3 files changed, 107 insertions(+), 3 deletions(-) diff --git a/packages/proxy/README.md b/packages/proxy/README.md index ab4e39c..64324ed 100644 --- a/packages/proxy/README.md +++ b/packages/proxy/README.md @@ -18,7 +18,7 @@ We offer built in integrations for the following libraries: - [Express](./src/express/README.md) - [Next.js](./src/nextjs/README.md) -You can also create your own custom integration. See docs here. +You can also [create your own custom adapter](#custom-adapters). ## Supported Endpoints @@ -33,4 +33,34 @@ The proxy supports all model endpoints, apart from the realtime models. 4. Proxy forwards the request to `https://api.decart.ai` 5. Proxy returns the response to the client +## Custom Adapters +You can create a proxy adapter for any HTTP framework by implementing the `ProxyBehavior` interface with the framework specific implementation, and passing it to `handleRequest()`. + +### Example: Fastify Adapter +In the following example we pass Fastify + +```typescript +import { handleRequest, type DecartProxyOptions } from "@decartai/proxy"; +import type { FastifyRequest, FastifyReply } from "fastify"; + +export function createDecartProxy(options?: DecartProxyOptions) { + return async (request: FastifyRequest, reply: FastifyReply) => { + await handleRequest({ + id: "1.0.0/fastify", + baseUrl: options?.baseUrl, + integration: options?.integration, + method: request.method, + getHeaders: () => request.headers as Record, + getHeader: (name) => request.headers[name], + getRequestBody: JSON.stringify(body), + sendHeader: (name, value) => reply.header(name, value), + respondWith: (status, data) => { + reply.status(status).send(data); + }, + getRequestPath: () => request.url, + sendResponse: (response) => reply.status(response.status).send(response.body), + }); + }; +} +``` diff --git a/packages/proxy/src/core/types.ts b/packages/proxy/src/core/types.ts index e40b684..1b1086e 100644 --- a/packages/proxy/src/core/types.ts +++ b/packages/proxy/src/core/types.ts @@ -20,17 +20,91 @@ export type DecartProxyOptions = { export type HeaderValue = string | string[] | undefined | null; export interface ProxyBehavior { + /** + * Identifier for the adapter, typically in format "version/framework-name". + * @example "1.0.0/express", "2.1.0/fastify" + */ id: string; + + /** + * HTTP method of the incoming request (GET, POST, etc.) + */ method: string; + + /** + * Optional API key for authenticating with the Decart API. + * If not provided, falls back to the DECART_API_KEY environment variable. + */ apiKey?: string; + + /** + * Optional base URL for the Decart API. + * Defaults to "https://api.decart.ai" + */ baseUrl?: string; + + /** + * Optional integration identifier included in the User-Agent header. + */ integration?: string; + + /** + * Send an error response to the client. + * Called when the proxy encounters an error (e.g., missing API key). + * + * @param status - HTTP status code + * @param data - Error message or data to send + * @returns The framework's response type + */ // biome-ignore lint/suspicious/noExplicitAny: data can be any type respondWith(status: number, data: string | any): ResponseType; + + /** + * Forward the successful proxy response to the client. + * Handle the response body as appropriate for your framework. + * + * @param response - The fetch Response from the Decart API + * @returns Promise resolving to the framework's response type + */ sendResponse(response: Response): Promise; + + /** + * Get all request headers as a record. + * Header names should be lowercase for consistent matching. + * + * @returns Record of header names to values + */ getHeaders(): Record; + + /** + * Get a specific request header value. + * + * @param name - Header name + * @returns The header value, or undefined if not present + */ getHeader(name: string): HeaderValue; + + /** + * Set a response header to forward to the client. + * + * @param name - Header name + * @param value - Header value + */ sendHeader(name: string, value: string): void; + + /** + * Get the request body. + * Return as ArrayBuffer to preserve binary data (e.g., for FormData/multipart). + * + * @returns Promise resolving to the request body, or undefined if no body + */ getRequestBody(): Promise; + + /** + * Get the request path to proxy to the Decart API. + * Should return the path after your proxy route (e.g., "/v1/generate/lucy-pro-t2i"). + * + * @returns The request path + */ getRequestPath(): string; } diff --git a/packages/proxy/src/index.ts b/packages/proxy/src/index.ts index 6491434..37c21a2 100644 --- a/packages/proxy/src/index.ts +++ b/packages/proxy/src/index.ts @@ -1,2 +1,2 @@ -export { handleRequest } from "./core/proxy-handler"; -export type { DecartProxyOptions } from "./core/types"; +export { handleRequest, fromHeaders, DEFAULT_PROXY_ROUTE } from "./core/proxy-handler"; +export type { DecartProxyOptions, ProxyBehavior, HeaderValue } from "./core/types"; From 931f7565a00efd59aae6e142b5887074e0849ba8 Mon Sep 17 00:00:00 2001 From: Guy Tepper Date: Sun, 11 Jan 2026 10:13:33 +0100 Subject: [PATCH 2/3] swap `id` with `integration` for custom adapters --- packages/proxy/README.md | 10 +++------- packages/proxy/src/core/proxy-handler.ts | 6 +++--- packages/proxy/src/core/types.ts | 6 +++--- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/proxy/README.md b/packages/proxy/README.md index 64324ed..1472618 100644 --- a/packages/proxy/README.md +++ b/packages/proxy/README.md @@ -38,7 +38,6 @@ The proxy supports all model endpoints, apart from the realtime models. You can create a proxy adapter for any HTTP framework by implementing the `ProxyBehavior` interface with the framework specific implementation, and passing it to `handleRequest()`. ### Example: Fastify Adapter -In the following example we pass Fastify ```typescript import { handleRequest, type DecartProxyOptions } from "@decartai/proxy"; @@ -47,17 +46,14 @@ import type { FastifyRequest, FastifyReply } from "fastify"; export function createDecartProxy(options?: DecartProxyOptions) { return async (request: FastifyRequest, reply: FastifyReply) => { await handleRequest({ - id: "1.0.0/fastify", + integration: "fastify", baseUrl: options?.baseUrl, - integration: options?.integration, method: request.method, getHeaders: () => request.headers as Record, getHeader: (name) => request.headers[name], - getRequestBody: JSON.stringify(body), + getRequestBody: async () => JSON.stringify(request.body), sendHeader: (name, value) => reply.header(name, value), - respondWith: (status, data) => { - reply.status(status).send(data); - }, + respondWith: (status, data) => reply.status(status).send(data), getRequestPath: () => request.url, sendResponse: (response) => reply.status(response.status).send(response.body), }); diff --git a/packages/proxy/src/core/proxy-handler.ts b/packages/proxy/src/core/proxy-handler.ts index 798fff9..96333d0 100644 --- a/packages/proxy/src/core/proxy-handler.ts +++ b/packages/proxy/src/core/proxy-handler.ts @@ -64,10 +64,10 @@ export async function handleRequest(behavior: ProxyBehavior { /** - * Identifier for the adapter, typically in format "version/framework-name". - * @example "1.0.0/express", "2.1.0/fastify" + * Internal identifier for built-in adapters. Custom adapters should use + * `integration` instead to identify themselves. */ - id: string; + id?: string; /** * HTTP method of the incoming request (GET, POST, etc.) From a0a661daeceab5fe289f39b71dd4b67a8d12f0ef Mon Sep 17 00:00:00 2001 From: Guy Tepper Date: Sun, 11 Jan 2026 10:19:52 +0100 Subject: [PATCH 3/3] add `ProxyBehavior` usage example --- packages/proxy/README.md | 16 ++++++++++++---- packages/proxy/src/core/types.ts | 2 +- packages/proxy/src/index.ts | 4 ++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/proxy/README.md b/packages/proxy/README.md index 1472618..dcc295e 100644 --- a/packages/proxy/README.md +++ b/packages/proxy/README.md @@ -40,12 +40,16 @@ You can create a proxy adapter for any HTTP framework by implementing the `Proxy ### Example: Fastify Adapter ```typescript -import { handleRequest, type DecartProxyOptions } from "@decartai/proxy"; +import { + handleRequest, + type DecartProxyOptions, + type ProxyBehavior, +} from "@decartai/proxy"; import type { FastifyRequest, FastifyReply } from "fastify"; export function createDecartProxy(options?: DecartProxyOptions) { return async (request: FastifyRequest, reply: FastifyReply) => { - await handleRequest({ + const behavior: ProxyBehavior = { integration: "fastify", baseUrl: options?.baseUrl, method: request.method, @@ -55,8 +59,12 @@ export function createDecartProxy(options?: DecartProxyOptions) { sendHeader: (name, value) => reply.header(name, value), respondWith: (status, data) => reply.status(status).send(data), getRequestPath: () => request.url, - sendResponse: (response) => reply.status(response.status).send(response.body), - }); + sendResponse: async (response) => { + reply.status(response.status).send(response.body); + }, + }; + + await handleRequest(behavior); }; } ``` diff --git a/packages/proxy/src/core/types.ts b/packages/proxy/src/core/types.ts index c6a2198..b85d12b 100644 --- a/packages/proxy/src/core/types.ts +++ b/packages/proxy/src/core/types.ts @@ -19,7 +19,7 @@ export type DecartProxyOptions = { export type HeaderValue = string | string[] | undefined | null; -export interface ProxyBehavior { +export interface ProxyBehavior { /** * Internal identifier for built-in adapters. Custom adapters should use * `integration` instead to identify themselves. diff --git a/packages/proxy/src/index.ts b/packages/proxy/src/index.ts index 37c21a2..be41d54 100644 --- a/packages/proxy/src/index.ts +++ b/packages/proxy/src/index.ts @@ -1,2 +1,2 @@ -export { handleRequest, fromHeaders, DEFAULT_PROXY_ROUTE } from "./core/proxy-handler"; -export type { DecartProxyOptions, ProxyBehavior, HeaderValue } from "./core/types"; +export { DEFAULT_PROXY_ROUTE, fromHeaders, handleRequest } from "./core/proxy-handler"; +export type { DecartProxyOptions, HeaderValue, ProxyBehavior } from "./core/types";