Skip to content

Commit

Permalink
feat(blob): allow folder creation (#559)
Browse files Browse the repository at this point in the history
This change allows the creation of empty folders. Before the SDK would always require a body, which is prohibited by the API.
  • Loading branch information
luismeyer committed Jan 12, 2024
1 parent 8b1ff23 commit fd1781f
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 16 deletions.
16 changes: 16 additions & 0 deletions .changeset/great-fans-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@vercel/blob": patch
"vercel-storage-integration-test-suite": patch
---

feat(blob): allow folder creation

This allows the creation of empty folders in the blob store. Before this change the SDK would always require a body, which is prohibited by the API.
Now the the SDK validates if the operation is a folder creation by checking if the pathname ends with a trailling slash.

```ts
const blob = await vercelBlob.put('folder/', {
access: 'public',
addRandomSuffix: false,
});
```
6 changes: 3 additions & 3 deletions packages/blob/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ export type { PutBlobResult, PutCommandOptions } from './put';
*
* If you want to upload from the browser directly, check out the documentation for client uploads: https://vercel.com/docs/storage/vercel-blob/using-blob-sdk#client-uploads
*
* @param pathname - The pathname to upload the blob to. This includes the filename.
* @param body - The contents of your blob. This has to be a supported fetch body type https://developer.mozilla.org/en-US/docs/Web/API/fetch#body.
* @param options - Additional options like `token` or `contentType`.
* @param pathname - The pathname to upload the blob to. For file upload this includes the filename. Pathnames that end with a slash are treated as folder creations.
* @param bodyOrOptions - Either the contents of your blob or the options object. For file uploads this has to be a supported fetch body type https://developer.mozilla.org/en-US/docs/Web/API/fetch#body. For folder creations this is the options object since no body is required.
* @param options - Additional options like `token` or `contentType` for file uploads. For folder creations this argument can be ommited.
*/
export const put = createPutMethod<PutCommandOptions>({
allowedOptions: ['cacheControlMaxAge', 'addRandomSuffix', 'contentType'],
Expand Down
42 changes: 29 additions & 13 deletions packages/blob/src/put.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,26 +42,42 @@ export function createPutMethod<
getToken?: (pathname: string, options: T) => Promise<string>;
extraChecks?: (options: T) => void;
}) {
return async function put(
pathname: string,
body:
| string
| Readable
| Blob
| ArrayBuffer
| FormData
| ReadableStream
| File,
options?: T,
return async function put<TPath extends string>(
pathname: TPath,
bodyOrOptions: TPath extends `${string}/`
? T
:
| string
| Readable
| Blob
| ArrayBuffer
| FormData
| ReadableStream
| File,
optionsInput?: T,
): Promise<PutBlobResult> {
if (!pathname) {
throw new BlobError('pathname is required');
}

if (!body) {
const isFolderCreation = pathname.endsWith('/');

// prevent empty bodies for files
if (!bodyOrOptions && !isFolderCreation) {
throw new BlobError('body is required');
}

// runtime check for non TS users that provide all three args
if (bodyOrOptions && optionsInput && isFolderCreation) {
throw new BlobError('body is not allowed for creating empty folders');
}

// avoid using the options as body
const body = isFolderCreation ? undefined : (bodyOrOptions as BodyInit);

// when no body is required options are the second argument
const options = isFolderCreation ? (bodyOrOptions as T) : optionsInput;

if (!options) {
throw new BlobError('missing options, see usage');
}
Expand Down Expand Up @@ -105,7 +121,7 @@ export function createPutMethod<

const blobApiResponse = await fetch(getApiUrl(`/${pathname}`), {
method: 'PUT',
body: body as BodyInit,
body,
headers,
// required in order to stream some body types to Cloudflare
// currently only supported in Node.js, we may have to feature detect this
Expand Down
14 changes: 14 additions & 0 deletions test/next/src/app/vercel/blob/script.mts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ async function run(): Promise<void> {
weirdCharactersExample(),
copyTextFile(),
listFolders(),
createFolder(),
]);

await Promise.all(
Expand Down Expand Up @@ -291,3 +292,16 @@ async function listFolders() {

return blob.url;
}

async function createFolder() {
const start = Date.now();

const blob = await vercelBlob.put('foolder/', {
access: 'public',
addRandomSuffix: false,
});

console.log('create folder example:', blob, `(${Date.now() - start}ms)`);

return blob.url;
}

1 comment on commit fd1781f

@vercel
Copy link

@vercel vercel bot commented on fd1781f Jan 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.