diff --git a/.changeset/remove-blob-use-cache.md b/.changeset/remove-blob-use-cache.md new file mode 100644 index 000000000..ab3bf13d7 --- /dev/null +++ b/.changeset/remove-blob-use-cache.md @@ -0,0 +1,5 @@ +--- +'@vercel/blob': minor +--- + +Deprecate the `useCache` option on `get()`. The backend no longer honors the `cache=0` query parameter it produced, so the option is now a no-op — reads always go through the standard caching path. The option is still accepted (and ignored) to avoid breaking existing callers, and will be removed in a future major version. diff --git a/packages/blob/src/get.ts b/packages/blob/src/get.ts index df7cc25dd..24b293ac5 100644 --- a/packages/blob/src/get.ts +++ b/packages/blob/src/get.ts @@ -13,10 +13,9 @@ export interface GetCommandOptions extends BlobCommandOptions { */ access: BlobAccessType; /** - * Whether to allow the blob to be served from CDN cache. - * When false, fetches directly from origin storage. - * Only effective for private blobs (ignored for public blobs). - * @defaultValue true + * @deprecated No longer has any effect. The backend no longer supports + * bypassing the cache, so this option is ignored. It is kept only to avoid + * breaking existing callers and will be removed in a future major version. */ useCache?: boolean; /** @@ -100,9 +99,6 @@ function extractPathnameFromUrl(url: string): string { * ```ts * // Basic usage * const { stream, headers, blob } = await get('user123/avatar.png', { access: 'private' }); - * - * // Bypass cache for private blobs (always fetch fresh from storage) - * const { stream, headers, blob } = await get('user123/data.json', { access: 'private', useCache: false }); * ``` * * Detailed documentation can be found here: https://vercel.com/docs/vercel-blob/using-blob-sdk @@ -110,7 +106,6 @@ function extractPathnameFromUrl(url: string): string { * @param urlOrPathname - The URL or pathname of the blob to fetch. * @param options - Configuration options including: * - access - (Required) Must be 'public' or 'private'. Determines the access level of the blob. - * - useCache - (Optional) When false, fetches directly from origin storage instead of CDN cache. Only effective for private blobs. Defaults to true. * - oidcToken - (Optional) Vercel OIDC token for authentication with `storeId` (or `BLOB_STORE_ID`); overrides `VERCEL_OIDC_TOKEN`. * - storeId - (Optional) Store id when using Vercel OIDC token for authentication; overrides `BLOB_STORE_ID`. * - token - (Optional) Read-write token when not using Vercel OIDC token for authentication, or set `BLOB_READ_WRITE_TOKEN`. @@ -177,15 +172,7 @@ export async function get( ...options.headers, // low-level escape hatch, applied last to override anything }; - // Construct fetch URL with optional cache bypass - let fetchUrl = blobUrl; - if (options.useCache === false) { - const url = new URL(blobUrl); - url.searchParams.set('cache', '0'); - fetchUrl = url.toString(); - } - - const response = await fetch(fetchUrl, { + const response = await fetch(blobUrl, { method: 'GET', headers: requestHeaders, signal: options.abortSignal, diff --git a/packages/blob/src/index.node.test.ts b/packages/blob/src/index.node.test.ts index 22bf05056..ab4aff8ca 100644 --- a/packages/blob/src/index.node.test.ts +++ b/packages/blob/src/index.node.test.ts @@ -1517,167 +1517,6 @@ describe('blob client', () => { expect(result?.blob.pathname).toEqual('file.txt'); }); - describe('useCache option', () => { - // undici normalizes hostnames to lowercase - const BLOB_STORE_BASE_URL_LOWERCASE = - 'https://12345fakestoreid.public.blob.vercel-storage.com'; - const BLOB_STORE_BASE_URL_STOREID_LOWERCASE = - 'https://storeid.public.blob.vercel-storage.com'; - - it('should append cache=0 to fetch URL when useCache is false', async () => { - const mockAgent = new MockAgent(); - mockAgent.disableNetConnect(); - setGlobalDispatcher(mockAgent); - const blobStoreMockClient = mockAgent.get( - BLOB_STORE_BASE_URL_LOWERCASE, - ); - - // Mock the exact path with cache=0 query param - blobStoreMockClient - .intercept({ - path: '/foo.txt?cache=0', - method: 'GET', - }) - .reply(200, 'blob content', { - headers: { - 'content-type': 'text/plain', - 'content-length': '12', - }, - }); - - const result = await get('foo.txt', { - access: 'public', - useCache: false, - }); - - // If the path didn't include ?cache=0, the mock wouldn't match and test would fail - expect(result).not.toBeNull(); - expect(result?.blob.pathname).toEqual('foo.txt'); - }); - - it('should not append cache=0 when useCache is true', async () => { - const mockAgent = new MockAgent(); - mockAgent.disableNetConnect(); - setGlobalDispatcher(mockAgent); - const blobStoreMockClient = mockAgent.get( - BLOB_STORE_BASE_URL_LOWERCASE, - ); - - // Mock the exact path without cache=0 - blobStoreMockClient - .intercept({ - path: '/foo.txt', - method: 'GET', - }) - .reply(200, 'blob content', { - headers: { - 'content-type': 'text/plain', - 'content-length': '12', - }, - }); - - const result = await get('foo.txt', { - access: 'public', - useCache: true, - }); - - // If the path included ?cache=0, the mock wouldn't match and test would fail - expect(result).not.toBeNull(); - expect(result?.blob.pathname).toEqual('foo.txt'); - }); - - it('should not append cache=0 when useCache is omitted', async () => { - const mockAgent = new MockAgent(); - mockAgent.disableNetConnect(); - setGlobalDispatcher(mockAgent); - const blobStoreMockClient = mockAgent.get( - BLOB_STORE_BASE_URL_LOWERCASE, - ); - - // Mock the exact path without cache=0 - blobStoreMockClient - .intercept({ - path: '/foo.txt', - method: 'GET', - }) - .reply(200, 'blob content', { - headers: { - 'content-type': 'text/plain', - 'content-length': '12', - }, - }); - - const result = await get('foo.txt', { - access: 'public', - }); - - // If the path included ?cache=0, the mock wouldn't match and test would fail - expect(result).not.toBeNull(); - expect(result?.blob.pathname).toEqual('foo.txt'); - }); - - it('should not include cache=0 in returned downloadUrl', async () => { - const mockAgent = new MockAgent(); - mockAgent.disableNetConnect(); - setGlobalDispatcher(mockAgent); - const blobStoreMockClient = mockAgent.get( - BLOB_STORE_BASE_URL_LOWERCASE, - ); - - blobStoreMockClient - .intercept({ - path: '/foo.txt?cache=0', - method: 'GET', - }) - .reply(200, 'blob content', { - headers: { - 'content-type': 'text/plain', - 'content-length': '12', - }, - }); - - const result = await get('foo.txt', { - access: 'public', - useCache: false, - }); - - expect(result?.blob.downloadUrl).toEqual( - 'https://12345fakestoreid.public.blob.vercel-storage.com/foo.txt?download=1', - ); - expect(result?.blob.url).toEqual( - 'https://12345fakeStoreId.public.blob.vercel-storage.com/foo.txt', - ); - }); - - it('should work with URL input and useCache false', async () => { - const mockAgent = new MockAgent(); - mockAgent.disableNetConnect(); - setGlobalDispatcher(mockAgent); - // Use lowercase since undici normalizes hostnames - const storeMock = mockAgent.get(BLOB_STORE_BASE_URL_STOREID_LOWERCASE); - - storeMock - .intercept({ - path: '/foo.txt?cache=0', - method: 'GET', - }) - .reply(200, 'blob content', { - headers: { - 'content-type': 'text/plain', - 'content-length': '12', - }, - }); - - const result = await get(`${BLOB_STORE_BASE_URL}/foo.txt`, { - access: 'public', - useCache: false, - }); - - // If the path didn't include ?cache=0, the mock wouldn't match and test would fail - expect(result).not.toBeNull(); - }); - }); - describe('conditional requests (304 Not Modified)', () => { const BLOB_STORE_BASE_URL_LOWERCASE = 'https://12345fakestoreid.private.blob.vercel-storage.com';