Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(blob): allow folder creation #559

Merged
merged 6 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
luismeyer marked this conversation as resolved.
Show resolved Hide resolved
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;
}
Loading