diff --git a/.zed/settings.json b/.zed/settings.json index a58d028..66234f7 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -1,35 +1,31 @@ -// Folder-specific settings -// -// For a full list of overridable settings, and general information on folder-specific settings, -// see the documentation: https://zed.dev/docs/configuring-zed#settings-files { - "lsp": { - "deno": { - "settings": { - "deno": { - "enable": true - } - } - } - }, - "languages": { - "TypeScript": { - "language_servers": [ - "deno", - "!typescript-language-server", - "!vtsls", - "!eslint" - ], - "formatter": "language_server" - }, - "TSX": { - "language_servers": [ - "deno", - "!typescript-language-server", - "!vtsls", - "!eslint" - ], - "formatter": "language_server" - } - } + "lsp": { + "deno": { + "settings": { + "deno": { + "enable": true + } + } + } + }, + "languages": { + "TypeScript": { + "language_servers": [ + "deno", + "!typescript-language-server", + "!vtsls", + "!eslint" + ], + "formatter": "language_server" + }, + "TSX": { + "language_servers": [ + "deno", + "!typescript-language-server", + "!vtsls", + "!eslint" + ], + "formatter": "language_server" + } + } } diff --git a/src/providers/builder.io.ts b/src/providers/builder.io.ts index bb1ae3f..3543aef 100644 --- a/src/providers/builder.io.ts +++ b/src/providers/builder.io.ts @@ -1,5 +1,6 @@ -import { OperationExtractor, Operations, URLTransformer } from "../types.ts"; +import { OperationExtractor, Operations } from "../types.ts"; import { + createExtractAndGenerate, createOperationsGenerator, extractFromURL, toCanonicalUrlString, @@ -59,16 +60,4 @@ export const generate: URLGenerator = ( return toCanonicalUrlString(url); }; -export const transform: URLTransformer = ( - src, - operations, -) => { - const base = extract(src); - if (!base) { - return generate(src, operations); - } - return generate(base.src, { - ...base.operations, - ...operations, - }); -}; +export const transform = createExtractAndGenerate(extract, generate); diff --git a/src/providers/bunny.ts b/src/providers/bunny.ts index c7021e8..20ad4c7 100644 --- a/src/providers/bunny.ts +++ b/src/providers/bunny.ts @@ -5,6 +5,7 @@ import { URLTransformer, } from "../types.ts"; import { + createExtractAndGenerate, createOperationsGenerator, extractFromURL, toCanonicalUrlString, @@ -132,6 +133,8 @@ export const generate: URLGenerator = ( return toCanonicalUrlString(url); }; +const extractAndGenerate = createExtractAndGenerate(extract, generate); + export const transform: URLTransformer = ( src, operations, @@ -142,12 +145,5 @@ export const transform: URLTransformer = ( Math.round(Number(height)) }`; } - const base = extract(src); - if (!base) { - return generate(src, operations); - } - return generate(base.src, { - ...base.operations, - ...operations, - }); + return extractAndGenerate(src, operations); }; diff --git a/src/providers/cloudflare.ts b/src/providers/cloudflare.ts index 71fc1f9..5e80f66 100644 --- a/src/providers/cloudflare.ts +++ b/src/providers/cloudflare.ts @@ -1,12 +1,8 @@ import { getImageCdnForUrlByPath } from "../detect.ts"; -import { - OperationExtractor, - Operations, - URLGenerator, - URLTransformer, -} from "../types.ts"; +import { OperationExtractor, Operations, URLGenerator } from "../types.ts"; import { ImageFormat } from "../types.ts"; import { + createExtractAndGenerate, createOperationsHandlers, stripLeadingSlash, toCanonicalUrlString, @@ -123,40 +119,21 @@ export const extract: OperationExtractor< CloudflareOperations, CloudflareOptions > = (url, options) => { - const parsedUrl = toUrl(url); - if (!parsedUrl.pathname.startsWith("/cdn-cgi/image/")) { + if (getImageCdnForUrlByPath(url) !== "cloudflare") { return null; } + const parsedUrl = toUrl(url); + const [, , , modifiers, ...src] = parsedUrl.pathname.split("/"); const operations = operationsParser(modifiers); return { src: toCanonicalUrlString(toUrl(src.join("/"))), operations, options: { - domain: options?.domain || parsedUrl.hostname === "n" - ? undefined - : parsedUrl.hostname, + domain: options?.domain ?? + (parsedUrl.hostname === "n" ? undefined : parsedUrl.hostname), }, }; }; -export const transform: URLTransformer< - CloudflareOperations, - CloudflareOptions -> = ( - src, - operations, - options, -) => { - const url = toUrl(src); - if (getImageCdnForUrlByPath(url) === "cloudflare") { - const base = extract(url, options); - if (base) { - return generate(base.src, { - ...base.operations, - ...operations, - }, base.options); - } - } - return generate(src, operations, options); -}; +export const transform = createExtractAndGenerate(extract, generate); diff --git a/src/providers/cloudimage.ts b/src/providers/cloudimage.ts index 8deb2bf..33f0b4b 100644 --- a/src/providers/cloudimage.ts +++ b/src/providers/cloudimage.ts @@ -4,9 +4,13 @@ import { OperationExtractor, Operations, URLGenerator, - URLTransformer, } from "../types.ts"; -import { createOperationsGenerator, extractFromURL, toUrl } from "../utils.ts"; +import { + createExtractAndGenerate, + createOperationsGenerator, + extractFromURL, + toUrl, +} from "../utils.ts"; export interface CloudimageOperations extends Operations { /** @@ -127,6 +131,9 @@ export const generate: URLGenerator = ( modifiers = {}, { token } = {}, ) => { + if (!token) { + throw new Error("Token is required for Cloudimage URLs"); + } let srcString = src.toString(); if (srcString.includes("?")) { modifiers.ci_url_encoded = 1; @@ -143,6 +150,9 @@ export const extract: OperationExtractor< CloudimageOperations, CloudimageOptions > = (url, options = {}) => { + if (getImageCdnForUrlByDomain(url) !== "cloudimage") { + return null; + } const result = extractFromURL(url); if (!result) { return null; @@ -154,30 +164,4 @@ export const extract: OperationExtractor< }; }; -export const transform: URLTransformer< - CloudimageOperations, - CloudimageOptions -> = ( - src, - operations, - options, -) => { - const url = toUrl(src); - // This is a cloudimage URL, so extract the image and operations from the URL - if (getImageCdnForUrlByDomain(url) === "cloudimage") { - const base = extract(url, options); - if (!base) { - console.error("Invalid Cloudimage URL", url.href); - return url.toString(); - } - return generate(url.pathname, { - ...base.operations, - ...operations, - }, base.options); - } - if (!options.token) { - throw new Error("Token is required for Cloudimage URLs"); - } - // This is a regular URL, so just append the operations - return generate(url, operations, options); -}; +export const transform = createExtractAndGenerate(extract, generate); diff --git a/src/providers/contentful.ts b/src/providers/contentful.ts index 6653c51..95b73ad 100644 --- a/src/providers/contentful.ts +++ b/src/providers/contentful.ts @@ -6,6 +6,7 @@ import { } from "../types.ts"; import { clampDimensions, + createExtractAndGenerate, createOperationsGenerator, extractFromURL, toCanonicalUrlString, @@ -126,18 +127,14 @@ export const generate: URLGenerator = ( export const extract: OperationExtractor = extractFromURL; +const extractAndGenerate = createExtractAndGenerate(extract, generate); + export const transform: URLTransformer = ( src, operations, ) => { const { width, height } = clampDimensions(operations, 4000, 4000); - - const base = extract(src); - if (!base) { - return generate(src, operations); - } - return generate(base.src, { - ...base.operations, + return extractAndGenerate(src, { ...operations, width, height, diff --git a/src/providers/contentstack.test.ts b/src/providers/contentstack.test.ts index 5f4255e..898aa1d 100644 --- a/src/providers/contentstack.test.ts +++ b/src/providers/contentstack.test.ts @@ -34,7 +34,7 @@ Deno.test("contentstack transform", async (t) => { await t.step( "should apply defaults when no operations are provided", () => { - const result = transform(img, {}); + const result = transform(img, {}, {}); assertEqualIgnoringQueryOrder( result, `${img}?fit=crop&auto=webp&disable=upscale`, @@ -51,7 +51,7 @@ Deno.test("contentstack transform", async (t) => { fit: "bounds", auto: "avif", disable: false, - }); + }, {}); assertEqualIgnoringQueryOrder( result, `${img}?width=200&height=100&fit=bounds&auto=avif&disable=false`, @@ -62,7 +62,7 @@ Deno.test("contentstack transform", async (t) => { await t.step("should handle edge case: pjpg format", () => { const result = transform(img, { format: "pjpg", - }); + }, {}); assertEqualIgnoringQueryOrder( result, `${img}?auto=webp&format=pjpg&fit=crop&disable=upscale`, @@ -73,7 +73,7 @@ Deno.test("contentstack transform", async (t) => { const result = transform(img, { overlay: "overlay-image.png", "overlay-align": "top,left", - }); + }, {}); assertEqualIgnoringQueryOrder( result, `${img}?overlay=overlay-image.png&overlay-align=top,left&fit=crop&auto=webp&disable=upscale`, diff --git a/src/providers/contentstack.ts b/src/providers/contentstack.ts index 7bc7bda..e188ae8 100644 --- a/src/providers/contentstack.ts +++ b/src/providers/contentstack.ts @@ -3,9 +3,9 @@ import { OperationExtractor, Operations, URLGenerator, - URLTransformer, } from "../types.ts"; import { + createExtractAndGenerate, createOperationsGenerator, extractFromURL, toCanonicalUrlString, @@ -180,19 +180,4 @@ export const extract: OperationExtractor< }; }; -export const transform: URLTransformer = ( - src, - operations, -) => { - const base = extract(src); - if (!base) { - return generate(src, operations); - } - - const combinedOperations = { - ...base.operations, - ...operations, - }; - - return generate(base.src, combinedOperations); -}; +export const transform = createExtractAndGenerate(extract, generate); diff --git a/src/providers/directus.ts b/src/providers/directus.ts index e9a35e3..1e775cc 100644 --- a/src/providers/directus.ts +++ b/src/providers/directus.ts @@ -1,10 +1,6 @@ +import { OperationExtractor, Operations, URLGenerator } from "../types.ts"; import { - OperationExtractor, - Operations, - URLGenerator, - URLTransformer, -} from "../types.ts"; -import { + createExtractAndGenerate, createOperationsHandlers, extractFromURL, toCanonicalUrlString, @@ -67,17 +63,4 @@ export const extract: OperationExtractor = (url) => { return base; }; -export const transform: URLTransformer = ( - src, - operations, -) => { - const base = extract(src); - if (!base) { - return generate(src, operations); - } - - return generate(base.src, { - ...base.operations, - ...operations, - }); -}; +export const transform = createExtractAndGenerate(extract, generate); diff --git a/src/providers/imageengine.ts b/src/providers/imageengine.ts index 7926a81..a3a3e4c 100644 --- a/src/providers/imageengine.ts +++ b/src/providers/imageengine.ts @@ -1,14 +1,8 @@ -import { getImageCdnForUrl } from "../detect.ts"; -import { - OperationExtractor, - Operations, - URLGenerator, - URLTransformer, -} from "../types.ts"; +import { OperationExtractor, Operations, URLGenerator } from "../types.ts"; import { ImageFormat } from "../types.ts"; import { + createExtractAndGenerate, createOperationsHandlers, - createParser, toCanonicalUrlString, toUrl, } from "../utils.ts"; @@ -128,21 +122,4 @@ export const extract: OperationExtractor< }; }; -export const transform: URLTransformer< - ImageEngineOperations -> = ( - src, - operations, -) => { - const url = toUrl(src); - if (getImageCdnForUrl(url) === "imageengine") { - const base = extract(url); - if (base) { - return generate(base.src, { - ...base.operations, - ...operations, - }); - } - } - return generate(src, operations); -}; +export const transform = createExtractAndGenerate(extract, generate); diff --git a/src/providers/imagekit.ts b/src/providers/imagekit.ts index 6cdc6b3..6ff9ed8 100644 --- a/src/providers/imagekit.ts +++ b/src/providers/imagekit.ts @@ -3,9 +3,9 @@ import { OperationExtractor, Operations, URLGenerator, - URLTransformer, } from "../types.ts"; import { + createExtractAndGenerate, createOperationsHandlers, toCanonicalUrlString, toUrl, @@ -191,17 +191,4 @@ export const extract: OperationExtractor = (url) => { }; }; -export const transform: URLTransformer = ( - src, - operations, -) => { - const url = toUrl(src); - const base = extract(url); - if (base) { - return generate(base.src, { - ...base.operations, - ...operations, - }); - } - return generate(src, operations); -}; +export const transform = createExtractAndGenerate(extract, generate); diff --git a/src/providers/imgix.ts b/src/providers/imgix.ts index acb8e26..566676e 100644 --- a/src/providers/imgix.ts +++ b/src/providers/imgix.ts @@ -5,6 +5,7 @@ import { URLGenerator, } from "../types.ts"; import { + createExtractAndGenerate, createOperationsHandlers, toCanonicalUrlString, toUrl, @@ -239,13 +240,4 @@ export const generate: URLGenerator = (src, operations) => { return toCanonicalUrlString(url); }; -export const transform: URLGenerator = (src, operations) => { - const base = extract(src); - if (!base) { - return generate(src, operations); - } - return generate(base.src, { - ...base.operations, - ...operations, - }); -}; +export const transform = createExtractAndGenerate(extract, generate); diff --git a/src/providers/keycdn.ts b/src/providers/keycdn.ts index 7f803e3..803344a 100644 --- a/src/providers/keycdn.ts +++ b/src/providers/keycdn.ts @@ -3,9 +3,9 @@ import type { OperationExtractor, Operations, URLGenerator, - URLTransformer, } from "../types.ts"; import { + createExtractAndGenerate, createOperationsHandlers, paramToBoolean, toCanonicalUrlString, @@ -298,17 +298,4 @@ export const extract: OperationExtractor = (url) => { }; }; -export const transform: URLTransformer = ( - src, - operations, -) => { - const url = toUrl(src); - const base = extract(url); - if (!base) { - return generate(src, operations); - } - return generate(base.src, { - ...base.operations, - ...operations, - }); -}; +export const transform = createExtractAndGenerate(extract, generate); diff --git a/src/providers/kontent.ai.ts b/src/providers/kontent.ai.ts index 03506f6..9680b82 100644 --- a/src/providers/kontent.ai.ts +++ b/src/providers/kontent.ai.ts @@ -3,9 +3,9 @@ import type { OperationExtractor, Operations, URLGenerator, - URLTransformer, } from "../types.ts"; import { + createExtractAndGenerate, createOperationsHandlers, paramToBoolean, toCanonicalUrlString, @@ -136,17 +136,4 @@ export const extract: OperationExtractor = (url) => { }; }; -export const transform: URLTransformer = ( - src, - operations, -) => { - const url = toUrl(src); - const base = extract(url); - if (!base) { - return generate(src, operations); - } - return generate(base.src, { - ...base.operations, - ...operations, - }); -}; +export const transform = createExtractAndGenerate(extract, generate); diff --git a/src/providers/netlify.test.ts b/src/providers/netlify.test.ts index 80ff867..5c8f682 100644 --- a/src/providers/netlify.test.ts +++ b/src/providers/netlify.test.ts @@ -65,10 +65,13 @@ Deno.test("Netlify Image CDN - extract", async (t) => { assertEquals(parsed, { src: "/cappadocia.jpg", operations: { - width: 800, // Long form: width - height: 600, // Long form: height - format: "webp", // Long form: format - quality: 75, // Long form: quality + width: 800, + height: 600, + format: "webp", + quality: 75, + }, + options: { + baseUrl, }, }); }, diff --git a/src/providers/netlify.ts b/src/providers/netlify.ts index 0ed0253..9cf5ed8 100644 --- a/src/providers/netlify.ts +++ b/src/providers/netlify.ts @@ -1,11 +1,12 @@ +import { getImageCdnForUrlByPath } from "../../mod.ts"; import type { ImageFormat, OperationExtractor, Operations, URLGenerator, - URLTransformer, } from "../types.ts"; import { + createExtractAndGenerate, createOperationsHandlers, toCanonicalUrlString, toUrl, @@ -89,7 +90,13 @@ export const generate: URLGenerator< return toCanonicalUrlString(url); }; -export const extract: OperationExtractor = (url) => { +export const extract: OperationExtractor< + NetlifyImageOperations, + NetlifyImageOptions +> = (url) => { + if (!getImageCdnForUrlByPath(url)) { + return null; + } const parsedUrl = toUrl(url); const operations = operationsParser(parsedUrl); // deno-lint-ignore no-explicit-any @@ -101,28 +108,10 @@ export const extract: OperationExtractor = (url) => { return { src: sourceUrl, operations, + options: { + baseUrl: parsedUrl.hostname === "n" ? undefined : parsedUrl.origin, + }, }; }; -export const transform: URLTransformer< - NetlifyImageOperations, - NetlifyImageOptions -> = ( - src, - operations, - options = {}, -) => { - const url = toUrl(src); - if (url.pathname.startsWith("/.netlify/images")) { - const base = extract(src); - if (base) { - return generate( - base.src, - { ...base.operations, ...operations }, - options, - ); - } - return toCanonicalUrlString(url); - } - return generate(src, operations, options); -}; +export const transform = createExtractAndGenerate(extract, generate); diff --git a/src/providers/nextjs.test.ts b/src/providers/nextjs.test.ts index 6f01d56..5668d79 100644 --- a/src/providers/nextjs.test.ts +++ b/src/providers/nextjs.test.ts @@ -59,6 +59,7 @@ Deno.test("Next.js Image CDN - extract", async (t) => { width: 828, // Long form: width quality: 75, // Long form: quality }, + options: {}, }); }, ); diff --git a/src/providers/nextjs.ts b/src/providers/nextjs.ts index 6bcc60c..ab11bd1 100644 --- a/src/providers/nextjs.ts +++ b/src/providers/nextjs.ts @@ -22,7 +22,8 @@ export const generate: URLGenerator = ( options = {}, ) => vercelGenerate(src, operations, { ...options, prefix: "_next" }); -export const extract: OperationExtractor = vercelExtract; +export const extract: OperationExtractor = + vercelExtract; export const transform: URLTransformer = ( src, diff --git a/src/providers/scene7.ts b/src/providers/scene7.ts index ef7ef54..99b73e4 100644 --- a/src/providers/scene7.ts +++ b/src/providers/scene7.ts @@ -1,13 +1,9 @@ -import type { - OperationExtractor, - Operations, - URLGenerator, - URLTransformer, -} from "../types.ts"; +import { getImageCdnForUrl } from "../../mod.ts"; +import type { OperationExtractor, Operations, URLGenerator } from "../types.ts"; import { + createExtractAndGenerate, createOperationsHandlers, toCanonicalUrlString, - toUrl, } from "../utils.ts"; type Scene7Formats = @@ -220,6 +216,9 @@ export const generate: URLGenerator< export const extract: OperationExtractor = ( url, ) => { + if (getImageCdnForUrl(url) !== "scene7") { + return null; + } const parsedUrl = new URL(url, BASE); const operations = operationsParser(parsedUrl); @@ -231,21 +230,4 @@ export const extract: OperationExtractor = ( }; }; -export const transform: URLTransformer< - Scene7Operations -> = ( - src, - operations, -) => { - const url = toUrl(src); - if (url.pathname.startsWith("/is/image/")) { - const base = extract(src); - if (base) { - return generate(base.src, { - ...base.operations, - ...operations, - }); - } - } - return generate(src, operations); -}; +export const transform = createExtractAndGenerate(extract, generate); diff --git a/src/providers/shopify.ts b/src/providers/shopify.ts index 128443b..f8c7f05 100644 --- a/src/providers/shopify.ts +++ b/src/providers/shopify.ts @@ -1,10 +1,6 @@ -import type { - OperationExtractor, - Operations, - URLGenerator, - URLTransformer, -} from "../types.ts"; +import type { OperationExtractor, Operations, URLGenerator } from "../types.ts"; import { + createExtractAndGenerate, createOperationsHandlers, toCanonicalUrlString, toUrl, @@ -79,16 +75,4 @@ export const extract: OperationExtractor = (url) => { operations, }; }; -export const transform: URLTransformer = ( - src, - operations, -) => { - const base = extract(src); - if (base) { - return generate(base.src, { - ...base.operations, - ...operations, - }); - } - return generate(src, operations); -}; +export const transform = createExtractAndGenerate(extract, generate); diff --git a/src/providers/supabase.ts b/src/providers/supabase.ts new file mode 100644 index 0000000..d5dee31 --- /dev/null +++ b/src/providers/supabase.ts @@ -0,0 +1,84 @@ +import type { OperationExtractor, Operations, URLGenerator } from "../types.ts"; +import { + createExtractAndGenerate, + createOperationsHandlers, + toCanonicalUrlString, + toUrl, +} from "../utils.ts"; + +const STORAGE_URL_PREFIX = "/storage/v1/object/public/"; +const RENDER_URL_PREFIX = "/storage/v1/render/image/public/"; + +const isRenderUrl = (url: URL) => url.pathname.startsWith(RENDER_URL_PREFIX); + +/** + * Supabase Image Transformation API operations + */ +interface SupabaseOperations extends Operations<"origin"> { + /** + * You can use different resizing modes: + * - `cover`: resizes the image while keeping the aspect ratio to fill a given size and crops projecting parts. + * - `contain`: resizes the image while keeping the aspect ratio to fit a given size. + * - `fill`: resizes the image without keeping the aspect ratio. + */ + resize?: "cover" | "contain" | "fill"; + + /** + * When using the image transformation API, Storage will automatically find the best format supported + * by the client and return that to the client. + * In case you'd like to return the original format of the image and opt-out from the automatic image + * optimization detection, you can pass the format=origin parameter when requesting a transformed image + */ + format?: "origin"; +} + +const { operationsGenerator, operationsParser } = createOperationsHandlers< + SupabaseOperations +>({}); + +export const generate: URLGenerator = (src, operations) => { + const url = toUrl(src); + const basePath = url.pathname.replace(RENDER_URL_PREFIX, STORAGE_URL_PREFIX); + + // Update the pathname with the cleaned version + url.pathname = basePath; + + // Supabase uses auto-format unless set to origin. Specific formats are not supported + if (operations.format && operations.format !== "origin") { + delete operations.format; + } + + // Add query parameters for image transformation + url.search = operationsGenerator(operations); + + // Replace with the render prefix for rendering + return toCanonicalUrlString(url).replace( + STORAGE_URL_PREFIX, + RENDER_URL_PREFIX, + ); +}; + +export const extract: OperationExtractor = (url) => { + const parsedUrl = toUrl(url); + const operations = operationsParser(parsedUrl); + const isRender = isRenderUrl(parsedUrl); + + const imagePath = parsedUrl.pathname.replace(RENDER_URL_PREFIX, "").replace( + STORAGE_URL_PREFIX, + "", + ); + + if (!isRender) { + return { + src: toCanonicalUrlString(parsedUrl), + operations, + }; + } + + return { + src: `${parsedUrl.origin}${STORAGE_URL_PREFIX}${imagePath}`, + operations, + }; +}; + +export const transform = createExtractAndGenerate(extract, generate); diff --git a/src/providers/vercel.test.ts b/src/providers/vercel.test.ts index e384640..a5b2253 100644 --- a/src/providers/vercel.test.ts +++ b/src/providers/vercel.test.ts @@ -52,8 +52,11 @@ Deno.test("Vercel Image CDN - extract", async (t) => { assertEquals(parsed, { src: "/image.jpg", operations: { - width: 800, // Long form: width - quality: 75, // Long form: quality + width: 800, + quality: 75, + }, + options: { + baseUrl: "https://example.com", }, }); }, diff --git a/src/providers/vercel.ts b/src/providers/vercel.ts index 227b2e1..bfad0c6 100644 --- a/src/providers/vercel.ts +++ b/src/providers/vercel.ts @@ -5,6 +5,7 @@ import type { URLTransformer, } from "../types.ts"; import { + createExtractAndGenerate, createOperationsHandlers, toCanonicalUrlString, toUrl, @@ -64,19 +65,25 @@ export const generate: URLGenerator = ( return toCanonicalUrlString(url); }; -export const extract: OperationExtractor = (url) => { - const parsedUrl = toUrl(url); - const sourceUrl = parsedUrl.searchParams.get("url") || ""; - parsedUrl.searchParams.delete("url"); - const operations = operationsParser(parsedUrl); +export const extract: OperationExtractor = + (url, options = {}) => { + const parsedUrl = toUrl(url); + const sourceUrl = parsedUrl.searchParams.get("url") || ""; + parsedUrl.searchParams.delete("url"); + const operations = operationsParser(parsedUrl); - parsedUrl.search = ""; + parsedUrl.search = ""; - return { - src: sourceUrl, - operations, + return { + src: sourceUrl, + operations, + options: { + baseUrl: options.baseUrl ?? parsedUrl.origin, + }, + }; }; -}; + +const extractAndGenerate = createExtractAndGenerate(extract, generate); export const transform: URLTransformer< VercelOperations, @@ -88,14 +95,7 @@ export const transform: URLTransformer< ) => { const url = toUrl(src); if (url.pathname.startsWith(`/${options.prefix || "_vercel"}/image`)) { - const base = extract(src); - if (base) { - return generate( - base.src, - { ...base.operations, ...operations }, - options, - ); - } + return extractAndGenerate(src, operations, options); } return generate(src, operations, options); }; diff --git a/src/utils.ts b/src/utils.ts index 1a88d61..e16fa0f 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,9 +1,12 @@ import { + OperationExtractor, OperationFormatter, OperationMap, OperationParser, Operations, ProviderConfig, + URLGenerator, + URLTransformer, } from "./types.ts"; export function roundIfNumeric( @@ -343,3 +346,26 @@ export function paramToBoolean( return Boolean(value); } } + +export function createExtractAndGenerate< + TOperations extends Operations = Operations, + TOptions = undefined, +>( + extract: OperationExtractor, + generate: URLGenerator, +): URLTransformer { + return ((src: string | URL, operations: TOperations, options?: TOptions) => { + const base = extract(src, options); + if (!base) { + return generate(src, operations, options); + } + return generate(base.src, { + ...base.operations, + ...operations, + }, { + // deno-lint-ignore no-explicit-any + ...(base as any).options, + ...options, + }); + }) as URLTransformer; +}